Skip to content

feat: add localisation keys to Astra tool result cards - #804

Merged
thomasluizon merged 2 commits into
redesign/mainfrom
thomasluizon/ticket-23-tool-result-keys
Oct 19, 2026
Merged

thomasluizon merged 2 commits into
redesign/mainfrom
thomasluizon/ticket-23-tool-result-keys

Conversation

@thomasluizon

@thomasluizon thomasluizon commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Astra action cards now carry optional messageKey and typed params, so clients can render localised card text while shipped clients continue reading the unchanged type, entityName, error, suggestion and clarification fields.

Closes thomasluizon/orbit-tickets#23 (ORB-17). The consumer is thomasluizon/orbit-tickets#24 and ships after the API.

ProcessUserChatCommand.cs appends the contract fields. ToolResultMessageParams.cs declares their types. ToolResultMessages.cs formats metadata from the existing tool call and repository-owned result payloads. Both card construction partials attach it to every emitted action, including policy failures, unknown tools, repeated calls, suggestions and clarification. This keeps executor, audit and persistence contracts unchanged and avoids parsing English summaries for counts. Both generated OpenAPI documents are included. Tests cover every formatter, real emoji execution through the handler, legacy JSON and enriched response round trips.

Assumptions

  • The cited path is verified on the checked-out redesign/main baseline; origin/main also has the same unkeyed action builder. The example now uses applied, matched, skipped and partial counts rather than the shorter quoted sentence, so metadata follows the current producer rather than restoring older copy.
  • Tool result cards means the frontend ActionResult collection. Read-only tool output, audit operation summaries and model free text are excluded rather than treating every model-facing tool payload as a display card.
  • The API has no generic legacy text property on action cards. Existing entityName and error fields are preserved rather than adding or renaming a legacy text field.
  • Params use a typed optional record rather than a dictionary of arbitrary JSON values. Counts come from existing bulk result payloads and successful request arrays rather than parsing English.
  • Existing command errors remain subject to the handler's existing sanitisation. Failure keys describe categories rather than passing English errors as translation params. Clarification questions and choices retain their existing contract.
  • Shared argument-error templates use fieldName, rather than duplicating keys for each field.
  • Registry coverage derives tools and supported actions from production classes and schemas, rather than maintaining a fixed tool-name list.
  • Numeric schema correction is scoped to this DTO, rather than changing global JSON number handling.

Manual steps

  • Release the merged API before shipping thomasluizon/orbit-tickets#24. In GitHub Actions for thomasluizon/orbit-api, run Release API (.github/workflows/release.yml) from main, with environment=staging and branch=redesign/main for staging. For production use environment=production, branch=main after the change reaches main. Verify a chat action response contains messageKey and object params, alongside the existing legacy fields. This worker does not merge or deploy.

Deploy the API before shipping client ticket #24. In GitHub Actions > Release API, dispatch the workflow from main with environment=staging and branch=redesign/main after integration. Production deployment uses environment=production and branch=main after promotion.

Proof: the release succeeds and chat cards return messageKey plus typed params, including numeric counts. No new environment variables, secrets, migrations, or backfills are required.

Test evidence

  • Before implementation: dotnet test tests/Orbit.Application.Tests --filter 'FullyQualifiedName~ProcessUserChatCommandHandlerTests|FullyQualifiedName~BulkUpdateHabitEmojisToolTests' --verbosity quiet passed all 254 unchanged tests despite the missing keys.
  • Before implementation: the new handler regression Handle_EmojiToolCard_EmitsTranslationKeyAndNumericCounts ran the real emoji tool for 1 and 185 habits. dotnet test tests/Orbit.Application.Tests --filter 'FullyQualifiedName~Handle_EmojiToolCard_EmitsTranslationKeyAndNumericCounts' --verbosity quiet failed both cases because messageKey was absent. A --no-build --verbosity normal repeat confirmed that exact assertion. The missing contract had no existing assertion to strengthen, so this adds the production-path regression instead.
  • After implementation: the same two cases passed. The focused handler, emoji and formatter selection passed 331 tests: dotnet test tests/Orbit.Application.Tests --filter 'FullyQualifiedName~ProcessUserChatCommandHandlerTests|FullyQualifiedName~ToolResultMessagesTests|FullyQualifiedName~BulkUpdateHabitEmojisToolTests' --verbosity quiet.
  • ActionContractSnapshot_OnlyAppendsOptionalFields asserts the complete legacy action JSON, omission of unset new fields, and deserialisation of both old and enriched JSON. The handler regression asserts numeric JSON params. Both generated OpenAPI documents retain the existing required action fields and schemas; only optional action properties and the params schema are added.
  • Post-commit build: dotnet build Orbit.slnx --verbosity quiet --disable-build-servers -m:1 passed with 0 errors. A default parallel post-commit build had MSBuild child-worker termination (MSB4166); the serial retry passed. The earlier default solution build also passed.
  • Post-commit focused regression and formatter selection: dotnet test tests/Orbit.Application.Tests --no-build --filter 'FullyQualifiedName~ToolResultMessagesTests|FullyQualifiedName~Handle_EmojiToolCard_EmitsTranslationKeyAndNumericCounts' --verbosity quiet passed 77 tests.
  • Broader suites use the post-commit solution build. dotnet test tests/Orbit.Domain.Tests --no-build --verbosity quiet passed 797 tests; dotnet test tests/Orbit.Application.Tests --no-build --verbosity quiet passed 5535 tests; dotnet test tests/Orbit.Analyzers.Tests --no-build --verbosity quiet passed 40 tests.
  • dotnet test tests/Orbit.Infrastructure.Tests --no-build --verbosity quiet passed 3951 tests. All four test projects passed, with 10323 tests total and no failures or skips.
  • Git hooks passed formatting, root allowlist, dash ban, timeless prose and suppression allowlist checks.

All final checks ran against commit 4a9c29a0:

Command Exit Result
dotnet build Orbit.slnx 0 Zero errors
dotnet test tests/Orbit.Domain.Tests --no-build --no-restore 0 797 passed
dotnet test tests/Orbit.Application.Tests --no-build --no-restore 0 5,630 passed
dotnet test tests/Orbit.Infrastructure.Tests --no-build --no-restore 0 3,954 passed

Focused verification passed 171 Application tests and 3 serializer/schema tests. Commit hooks and git diff --check passed.

Regression observations:

  • Before changes, dotnet test tests/Orbit.Application.Tests --filter FullyQualifiedName~ToolResultMessages --no-restore passed all 75 existing tests, including the incorrect empty policy-params assertion. The strengthened tests, run with the implementation still defective, failed eight cases: missing policy reason, stale sanitized key, and six tool-error mappings.

  • dotnet test tests/Orbit.Application.Tests --filter FullyQualifiedName~RegistryWriteTools_EverySupportedActionHasNonFallbackKey --no-restore failed when create_habit’s mapping was temporarily removed, identifying its completed fallback. The mapping was restored.

  • dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~ToolResultSerialization --no-restore failed both schema cases before correction because numeric fields advertised strings. All three cases pass afterward.

  • The first broader Application run caught an overlapping-prefix crash for Habit not found. in Handle_ToolCallWithFailure_ReturnsFailedAction. Added bare habit/goal regression cases failed before correction. The final focused command passed all 171 cases:

    dotnet test tests/Orbit.Application.Tests --no-build --no-restore --filter 'FullyQualifiedName~ToolResultMessages|FullyQualifiedName~ToolResultFailureMessages|FullyQualifiedName~Handle_ToolCallWithFailure_ReturnsFailedAction'
    

The schema investigation checked the OpenAPI generator source, the installed runtime’s nullable schema-export path, and the library’s schema properties. Both generated documents now emit {"type":["null","integer"],"format":"int32"} for count, totalCount, skippedCount, weekStartDay, and hours.

Comparison with redesign/main confirmed that ToolResultMessageParams is new and every pre-existing ActionResult property remains unchanged.

Interface evidence

No new runtime field is read from an external interface. applied_count, total_matched, skipped_count and partial come from this repository's BulkUpdateHabitsTool.BuildResult; formatter tests call that actual producer. BulkCreateResult and the tool argument schemas are repository-owned types. AgentOperationResponseFactory already carries ToolResult.Payload and EntityName through unchanged. The emoji regression uses the actual tool and asserts JSON from ChatStreamEvent.Final(...).ToJson().

Full message key list for #24

These are the exact message keys for the consumer dictionaries. Missing optional params are omitted, and an action with no interpolation data sends {}. Counts are raw JSON numbers for client pluralisation. Complete and partial bulk keys include the full summary, including skipped counts and completion state. Entity names and referral values are user data and are not translated. No new input feature or domain mutation is introduced, so no new validator or entity guard is needed.

Message key Params Existing card content or meaning
chat.toolResult.accountDeletionConfirmed {} Account deletion confirmed
chat.toolResult.accountReset {} Account reset completed
chat.toolResult.aiSummary.disabled {} AI summary disabled
chat.toolResult.aiSummary.enabled {} AI summary enabled
chat.toolResult.allNotificationsDeleted {} Deleted all notifications
chat.toolResult.allNotificationsRead {} Marked all notifications as read
chat.toolResult.apiKeyCreated entityName?: string ManageApiKeys create; user-authored name, never a summary
chat.toolResult.apiKeyRevoked {} Revoked API key
chat.toolResult.assignedTags entityName?: string AssignTags; user-authored name, never a summary
chat.toolResult.billingPortalCreated {} Created billing portal session
chat.toolResult.calendarAutoSync.disabled {} Calendar auto-sync disabled
chat.toolResult.calendarAutoSync.enabled {} Calendar auto-sync enabled
chat.toolResult.calendarImportDismissed {} Dismissed calendar import prompt
chat.toolResult.calendarSuggestionDismissed {} Dismissed calendar sync suggestion
chat.toolResult.calendarSyncRequested {} Calendar sync requested
chat.toolResult.checklistTemplateDeleted {} Deleted checklist template
chat.toolResult.checkoutCreated {} Created checkout session
chat.toolResult.clarificationLimitExceeded {} Tool arguments exceeded the clarification stash limit.
chat.toolResult.clockFormatSet hours: integer Clock set to 12-hour or 24-hour; hours is 12 or 24
chat.toolResult.completed {} Fallback for an unrecognised successful tool/action
chat.toolResult.confirmationRequired {} Confirmation required before this action can run.
chat.toolResult.createdChecklistTemplate entityName?: string CreateChecklistTemplate; user-authored name, never a summary
chat.toolResult.createdGoal entityName?: string CreateGoal; user-authored name, never a summary
chat.toolResult.createdHabit entityName?: string CreateHabit; user-authored name, never a summary
chat.toolResult.createdHabits count, totalCount: integers {count}/{totalCount} habits created
chat.toolResult.createdSubHabit entityName?: string CreateSubHabit; user-authored name, never a summary
chat.toolResult.createdTag entityName?: string CreateTag; user-authored name, never a summary
chat.toolResult.deletedGoal entityName?: string DeleteGoal; user-authored name, never a summary
chat.toolResult.deletedHabit entityName?: string DeleteHabit; user-authored name, never a summary
chat.toolResult.deletedHabits count, totalCount, skippedCount: integers; partial: boolean Deleted {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Complete result.
chat.toolResult.deletedHabitsPartial count, totalCount, skippedCount: integers; partial: boolean Deleted {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Partial result.
chat.toolResult.deletionCodeRequested {} Deletion code requested
chat.toolResult.duplicatedHabit entityName?: string DuplicateHabit; user-authored name, never a summary
chat.toolResult.failed {} Existing sanitised tool failure, without interpolating English errors
chat.toolResult.featureFlagsUpdated {} Feature flag mutation completed (MCP-only tool)
chat.toolResult.languageSet value: string Language set to {value}
chat.toolResult.linkedGoalsToHabit entityName?: string LinkGoalsToHabit; user-authored name, never a summary
chat.toolResult.linkedHabitsToGoal entityName?: string LinkHabitsToGoal; user-authored name, never a summary
chat.toolResult.loggedHabit entityName?: string LogHabit; user-authored name, never a summary
chat.toolResult.loggedHabits count, totalCount, skippedCount: integers; partial: boolean Logged {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Complete result.
chat.toolResult.loggedHabitsPartial count, totalCount, skippedCount: integers; partial: boolean Logged {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Partial result.
chat.toolResult.movedHabit entityName?: string MoveHabit; user-authored name, never a summary
chat.toolResult.movedHabitParent entityName?: string MoveHabitParent; user-authored name, never a summary
chat.toolResult.needsClarification {} Clarification card; question and choices remain in clarificationRequest
chat.toolResult.notificationDeleted {} Deleted notification
chat.toolResult.notificationRead {} Marked notification as read
chat.toolResult.notificationsDeleted count: integer Deleted {count} notifications
chat.toolResult.onboardingCompleted {} Onboarding completed
chat.toolResult.policyDenied {} Policy denied; the reason remains in the legacy Error field
chat.toolResult.proactiveAstra.disabled {} Proactive Astra disabled
chat.toolResult.proactiveAstra.enabled {} Proactive Astra enabled
chat.toolResult.pushSubscribed {} Push subscription registered
chat.toolResult.pushUnsubscribed {} Push subscription removed
chat.toolResult.referralCode value: string Referral code value
chat.toolResult.reorderedGoals count: integer {count} goals
chat.toolResult.reorderedHabits count: integer {count} habits
chat.toolResult.repeatedSingleEntity toolName, bulkTool: strings Repeated {toolName} calls were blocked. Use one {bulkTool} call for the full matching set.
chat.toolResult.rescheduledHabits count, totalCount, skippedCount: integers; partial: boolean Rescheduled {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Complete result.
chat.toolResult.rescheduledHabitsPartial count, totalCount, skippedCount: integers; partial: boolean Rescheduled {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Partial result.
chat.toolResult.skippedHabit entityName?: string SkipHabit; user-authored name, never a summary
chat.toolResult.skippedHabits count, totalCount, skippedCount: integers; partial: boolean Skipped {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Complete result.
chat.toolResult.skippedHabitsPartial count, totalCount, skippedCount: integers; partial: boolean Skipped {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Partial result.
chat.toolResult.suggestedBreakdown entityName?: string SuggestBreakdown; user-authored name, never a summary
chat.toolResult.supportRequestSent {} Support request sent
chat.toolResult.tagDeleted {} Deleted tag
chat.toolResult.testPushRequested {} Test push requested
chat.toolResult.themePreferenceUpdated {} Theme preference updated
chat.toolResult.timezoneSet value: string Timezone set to {value}
chat.toolResult.tourCompleted {} Tour completed
chat.toolResult.tourReset {} Tour reset
chat.toolResult.undidSkipHabit entityName?: string UndoSkipHabit; user-authored name, never a summary
chat.toolResult.unexpectedError {} An unexpected error occurred.
chat.toolResult.unknownTool toolName: string Unknown tool: {toolName}
chat.toolResult.unsupportedByPolicy {} Operation is unsupported by policy.
chat.toolResult.updatedChecklist entityName?: string UpdateChecklist; user-authored name, never a summary
chat.toolResult.updatedEmojis count, totalCount, skippedCount: integers; partial: boolean Updated emojis for {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Complete result.
chat.toolResult.updatedEmojisPartial count, totalCount, skippedCount: integers; partial: boolean Updated emojis for {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Partial result.
chat.toolResult.updatedGoal entityName?: string UpdateGoal; user-authored name, never a summary
chat.toolResult.updatedGoalProgress entityName?: string UpdateGoalProgress; user-authored name, never a summary
chat.toolResult.updatedGoalStatus entityName?: string UpdateGoalStatus; user-authored name, never a summary
chat.toolResult.updatedHabit entityName?: string UpdateHabit; user-authored name, never a summary
chat.toolResult.updatedHabits count, totalCount, skippedCount: integers; partial: boolean Updated {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Complete result.
chat.toolResult.updatedHabitsPartial count, totalCount, skippedCount: integers; partial: boolean Updated {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Partial result.
chat.toolResult.updatedTag entityName?: string UpdateTag; user-authored name, never a summary
chat.toolResult.weekStartDaySet weekStartDay: integer Week start day set to Sunday or Monday; 0 means Sunday, 1 means Monday

Key list

All keys below use the chat.toolResult. prefix. int means a JSON integer; ? means the parameter can be omitted. none means {}. Legacy text remains in entityName or error.

Key suffix Params with types Legacy English text
accountDeletionConfirmed none Account deletion confirmed
accountReset none Account reset completed
activeGoalNotFound entityName: string No active goal found matching '{entityName}'.
aiSummary.disabled none AI summary disabled
aiSummary.enabled none AI summary enabled
allNotificationsDeleted none Deleted all notifications
allNotificationsRead none Marked all notifications as read
apiKeyCreated entityName: string {entityName}
apiKeyRevoked none Revoked API key
argumentRequired fieldName: string {fieldName} is required.
arrayRequired fieldName: string {fieldName} is required and must be an array.
assignedTags entityName?: string {entityName}
billingPortalCreated none Created billing portal session
bulkOperationFailed none Bulk habit operation failed before any changes were applied.
bulkReplayExpired none This request was started by an earlier server version. Nothing more was changed. Check the habits and repeat the request.
calendarAutoSync.disabled none Calendar auto-sync disabled
calendarAutoSync.enabled none Calendar auto-sync enabled
calendarImportDismissed none Dismissed calendar import prompt
calendarSuggestionDismissed none Dismissed calendar sync suggestion
calendarSyncRequested none Calendar sync requested
cannotLogFutureDate none You can log a day once it arrives.
checklistTemplateDeleted none Deleted checklist template
checklistTemplateItemsRequired none name and at least one item are required.
checkoutCreated none Created checkout session
clarificationLimitExceeded none Tool arguments exceeded the clarification stash limit.
clockFormatSet hours: int Clock set to {hours}-hour
completed entityName?: string Legacy entityName, if present
createdChecklistTemplate entityName?: string {entityName}
createdGoal entityName?: string {entityName}
createdHabit entityName?: string {entityName}
createdHabits count: int, totalCount: int {count}/{totalCount} habits created
createdSubHabit entityName?: string {entityName}
createdTag entityName?: string {entityName}
deletedGoal entityName?: string {entityName}
deletedHabit entityName?: string {entityName}
deletedHabits count: int, totalCount: int, skippedCount: int, partial: bool Deleted {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Complete result.
deletedHabitsPartial count: int, totalCount: int, skippedCount: int, partial: bool Deleted {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Partial result.
deletionCodeRequested none Deletion code requested
duplicatedHabit entityName?: string {entityName}
emojiRequired none Provide emoji or set infer_from_title to true.
failed none Legacy error, if present
filterAllConflictsWithHabitIds none filter cannot combine all=true with habit_ids.
filterConflictsWithHabitIds none filter cannot combine with habit_ids.
filterFrequencyInvalid none filter.frequency must be Day, Week, Month, Year, or OneTime.
filterHabitIdsRequired none filter.habit_ids must be an array of valid habit IDs.
filterSelectorRequired none filter must explicitly select all habits or provide at least one predicate.
frequencyUnitInvalid none frequency_unit must be Day, Week, Month, Year, or null.
goalNotFound entityId?: string Goal {entityId} not found. / Goal not found. / That goal is not here any more.
goalSelectorRequired none Provide either goal_id or goal_name.
goalStatusRequired none status is required and must be Active, Completed, or Abandoned.
guidRequired fieldName: string {fieldName} is required and must be a valid GUID.
habitIdsSelectorRequired none habit_ids must be omitted or provided as a non-empty array of valid habit IDs.
habitIdsUnavailable none habit_ids is unavailable for this tool instance.
habitNotFound entityId?: string Habit {entityId} not found. / Habit not found. / That habit is not here any more. It may have been deleted on another device.
habitNotOwned none This habit belongs to another account.
habitSelectorRequired none Provide filter or habit_ids.
habitTitleRequired none Each habit requires a non-empty title.
invalidApiKeyExpiration none expires_at_utc must be a valid ISO-8601 UTC timestamp.
invalidArray fieldName: string {fieldName} must be an array.
invalidBoolean fieldName: string {fieldName} must be a boolean.
invalidChecklistItem none checklist_items contains an invalid item.
invalidDate fieldName: string {fieldName} must be a date in YYYY-MM-DD format.
invalidDateFormat none Invalid date format. Use YYYY-MM-DD.
invalidDateValue fieldName: string {fieldName} must use YYYY-MM-DD format.
invalidDays none days must contain valid weekday names.
invalidFilterHabitIds none filter.habit_ids must contain only valid habit IDs.
invalidGoalPosition none Each position requires a valid goal_id GUID and an integer position.
invalidGoalStatus none Pick a status from the list.
invalidGuid fieldName: string {fieldName} must be a valid GUID.
invalidHabitIdList none habit_ids must contain only valid habit IDs.
invalidHabitIds none habit_ids must contain only valid GUID strings.
invalidHabitPosition none Each position requires a valid habit_id GUID and an integer position.
invalidNotificationIds none notification_ids contains an invalid ID.
invalidNullableDate fieldName: string {fieldName} must be a date in YYYY-MM-DD format or null.
invalidNullableInteger fieldName: string {fieldName} must be an integer or null.
invalidNullableString fieldName: string {fieldName} must be a string or null.
invalidNullableTime fieldName: string {fieldName} must use HH:mm format or null.
invalidObject fieldName: string {fieldName} must be an object.
invalidParentId none parent_id must be a valid GUID when provided.
invalidReminderTimes none reminder_times must contain only integers.
invalidRevisedEmoji none revised_items contains an invalid emoji.
invalidRevisedHabitDate none revised_items contains an invalid habit or date.
invalidRevisedHabitId none revised_items contains an invalid habit ID.
invalidScheduledReminder none scheduled_reminders contains an invalid item.
invalidSkipId none Invalid skip_id.
invalidString fieldName: string {fieldName} must be a string.
languageSet language: string Language set to {language}
linkedGoalsToHabit entityName?: string {entityName}
linkedHabitsToGoal entityName?: string {entityName}
loggedHabit entityName?: string {entityName}
loggedHabits count: int, totalCount: int, skippedCount: int, partial: bool Logged {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Complete result.
loggedHabitsPartial count: int, totalCount: int, skippedCount: int, partial: bool Logged {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Partial result.
maxHabitsPerGoal count: int A goal can have at most {count} linked habits. / Link up to {count} habits to this goal.
movedHabit entityName?: string {entityName}
movedHabitParent entityName?: string {entityName}
nameAndColorRequired none name and color are required.
needsClarification none Clarification content
noHabitsProvided none No habits provided.
noHabitsToDelete none No matching habits found to delete.
noHabitsToLog none No matching habits found to log.
noHabitsToSkip none No matching habits found to skip.
noHabitsToUpdate none No matching habits found to update.
noMatchingHabits none No matching habits found.
nonEmptyArrayRequired fieldName: string {fieldName} must be a non-empty array.
notificationDeleted none Deleted notification
notificationRead none Marked notification as read
notificationsDeleted count: int Deleted {count} notifications
numberRequired fieldName: string {fieldName} is required and must be a number.
objectRequired fieldName: string {fieldName} is required and must be an object.
onboardingCompleted none Onboarding completed
policyDenied policyReason: string Policy denied: {policyReason}
proactiveAstra.disabled none Proactive Astra disabled
proactiveAstra.enabled none Proactive Astra enabled
pushCredentialsRequired none endpoint, p256dh, and auth are required.
pushSubscribed none Push subscription registered
pushUnsubscribed none Push subscription removed
referralCode referralCode: string {referralCode}
reorderedGoals count: int {count} goals
reorderedHabits count: int {count} habits
repeatedSingleEntity toolName: string, bulkTool: string Repeated {toolName} calls were blocked. Use one {bulkTool} call for the full matching set.
rescheduledHabits count: int, totalCount: int, skippedCount: int, partial: bool Rescheduled {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Complete result.
rescheduledHabitsPartial count: int, totalCount: int, skippedCount: int, partial: bool Rescheduled {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Partial result.
revisedChangesRequired none revised_items requires changes.
skippedHabit entityName?: string {entityName}
skippedHabits count: int, totalCount: int, skippedCount: int, partial: bool Skipped {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Complete result.
skippedHabitsPartial count: int, totalCount: int, skippedCount: int, partial: bool Skipped {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Partial result.
suggestedBreakdown entityName: string {entityName}
supportFieldsRequired none name, email, subject, and message are required.
supportRequestSent none Support request sent
tagDeleted none Deleted tag
tagNameRequired none At least one tag name is required.
tagNotFound none That tag is not here any more.
tagSelectorRequired none Provide either tag_ids or tag_names.
testPushRequested none Test push requested
themePreferenceUpdated none Theme preference updated
timezoneSet timeZone: string Timezone set to {timeZone}
toolFailure none I couldn't complete that. Please try again.
tourCompleted none Tour completed
tourReset none Tour reset
undidSkipHabit entityName?: string Absent
unexpectedError none An unexpected error occurred.
unknownTool toolName: string Unknown tool: {toolName}
unsupportedAction actionName: string Unsupported action '{actionName}'.
unsupportedByPolicy none Operation is unsupported by policy.
unsupportedFilterField none filter contains an unsupported field.
unsupportedUpdateField none updates contains an unsupported field.
updatedChecklist entityName?: string {entityName}
updatedEmojis count: int, totalCount: int, skippedCount: int, partial: bool Updated emojis for {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Complete result.
updatedEmojisPartial count: int, totalCount: int, skippedCount: int, partial: bool Updated emojis for {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Partial result.
updatedGoal entityName?: string {entityName}
updatedGoalProgress entityName?: string {entityName}
updatedGoalStatus entityName?: string {entityName}
updatedHabit entityName?: string {entityName}
updatedHabits count: int, totalCount: int, skippedCount: int, partial: bool Updated {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Complete result.
updatedHabitsPartial count: int, totalCount: int, skippedCount: int, partial: bool Updated {count} of {totalCount} matching habit(s). Skipped {skippedCount}. Partial result.
updatedTag entityName?: string {entityName}
weekStartDaySet weekStartDay: int Week start day set to Sunday/Monday

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes Reviewed the localisation metadata, action-card construction paths, serialization contracts, tests, and sibling consumer compatibility; no Critical or High findings identified.

  • Additive contract: Optional messageKey and typed params preserve legacy action fields, with matching additions to both OpenAPI documents.
  • Result formatting: Entity actions, bulk results, preferences, failures, suggestions, and clarification cards receive translation metadata; bulk counts and partial state match the existing producers.
  • Consumer sequencing: Checked orbit-ui-mobile schemas and paired PR #1885, which explicitly leaves message-key consumption pending API ticket #23.
  • Verification: All 331 focused formatter, chat-handler, and emoji-tool tests passed locally, including legacy JSON compatibility and final SSE numeric parameters. The full solution suite was not rerun.

Pullfrog  | View workflow run | Using gpt-6.1-sol | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new Critical or High issues found.

Reviewed changes Reviewed the changes since 77ece1ae, with the complete PR diff and sibling consumer schema as context.

  • Expanded failure metadata: Added specific failure keys and typed parameters for policy denials, argument validation, and entity lookup errors while preserving legacy error fields.
  • Aligned sanitization: Replaced failure metadata with the generic toolFailure key and empty parameters when existing turn-level error sanitization runs.
  • Refined the contract: Replaced the unshipped generic value parameter with named fields and restricted the new DTO's numeric OpenAPI properties to integer/null without changing existing action fields.
  • Strengthened verification: Added registry-derived write-action coverage and serializer/schema regressions. Sequential local runs passed all 426 focused Application tests and all 3 serializer/schema tests; the full solution suite was not rerun.

Pullfrog  | View workflow run | Using gpt-6.1-sol | 𝕏

@sonarqubecloud

Copy link
Copy Markdown

@thomasluizon
thomasluizon merged commit d90e3c4 into redesign/main Oct 19, 2026
22 checks passed
@thomasluizon
thomasluizon deleted the thomasluizon/ticket-23-tool-result-keys branch October 19, 2026 23:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant