@open-mercato/ai-assistant 0.6.2-develop.3406.1.2b403f40da → 0.6.2-develop.3446.1.bd060c6017

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +8 -1
  3. package/build.mjs +1 -0
  4. package/dist/frontend/components/AiChatButton.js +1 -1
  5. package/dist/frontend/components/AiChatButton.js.map +2 -2
  6. package/dist/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.js +16 -5
  7. package/dist/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.js.map +2 -2
  8. package/dist/modules/ai_assistant/ai-tools/meta-pack.js +58 -1
  9. package/dist/modules/ai_assistant/ai-tools/meta-pack.js.map +2 -2
  10. package/dist/modules/ai_assistant/api/ai/agents/route.js +2 -1
  11. package/dist/modules/ai_assistant/api/ai/agents/route.js.map +2 -2
  12. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js.map +2 -2
  13. package/dist/modules/ai_assistant/i18n/de.json +7 -1
  14. package/dist/modules/ai_assistant/i18n/en.json +7 -1
  15. package/dist/modules/ai_assistant/i18n/es.json +7 -1
  16. package/dist/modules/ai_assistant/i18n/pl.json +7 -1
  17. package/dist/modules/ai_assistant/lib/agent-registry.js +26 -6
  18. package/dist/modules/ai_assistant/lib/agent-registry.js.map +2 -2
  19. package/dist/modules/ai_assistant/lib/agent-runtime.js +21 -8
  20. package/dist/modules/ai_assistant/lib/agent-runtime.js.map +3 -3
  21. package/dist/modules/ai_assistant/lib/ai-agent-definition.js.map +2 -2
  22. package/dist/modules/ai_assistant/lib/pending-action-types.js.map +2 -2
  23. package/dist/modules/ai_assistant/lib/prepare-mutation.js +16 -6
  24. package/dist/modules/ai_assistant/lib/prepare-mutation.js.map +2 -2
  25. package/dist/modules/ai_assistant/lib/task-plan-labels.js +95 -0
  26. package/dist/modules/ai_assistant/lib/task-plan-labels.js.map +7 -0
  27. package/dist/modules/ai_assistant/lib/task-plan-stream.js +349 -0
  28. package/dist/modules/ai_assistant/lib/task-plan-stream.js.map +7 -0
  29. package/dist/modules/ai_assistant/lib/tool-test-fixtures.js +3 -0
  30. package/dist/modules/ai_assistant/lib/tool-test-fixtures.js.map +2 -2
  31. package/package.json +6 -6
  32. package/src/frontend/components/AiChatButton.tsx +1 -1
  33. package/src/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.ts +20 -8
  34. package/src/modules/ai_assistant/ai-tools/__tests__/meta-pack.test.ts +60 -4
  35. package/src/modules/ai_assistant/ai-tools/meta-pack.ts +79 -2
  36. package/src/modules/ai_assistant/api/ai/agents/route.ts +2 -1
  37. package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx +1 -0
  38. package/src/modules/ai_assistant/i18n/de.json +7 -1
  39. package/src/modules/ai_assistant/i18n/en.json +7 -1
  40. package/src/modules/ai_assistant/i18n/es.json +7 -1
  41. package/src/modules/ai_assistant/i18n/pl.json +7 -1
  42. package/src/modules/ai_assistant/lib/__tests__/agent-registry.test.ts +60 -0
  43. package/src/modules/ai_assistant/lib/__tests__/ai-agent-definition.test.ts +4 -0
  44. package/src/modules/ai_assistant/lib/__tests__/prepare-mutation.test.ts +43 -0
  45. package/src/modules/ai_assistant/lib/__tests__/task-plan-stream.test.ts +375 -0
  46. package/src/modules/ai_assistant/lib/agent-registry.ts +36 -5
  47. package/src/modules/ai_assistant/lib/agent-runtime.ts +26 -8
  48. package/src/modules/ai_assistant/lib/ai-agent-definition.ts +14 -0
  49. package/src/modules/ai_assistant/lib/pending-action-types.ts +4 -1
  50. package/src/modules/ai_assistant/lib/prepare-mutation.ts +17 -5
  51. package/src/modules/ai_assistant/lib/task-plan-labels.ts +112 -0
  52. package/src/modules/ai_assistant/lib/task-plan-stream.ts +463 -0
  53. package/src/modules/ai_assistant/lib/tool-test-fixtures.ts +3 -0
  54. package/src/modules/ai_assistant/lib/types.ts +16 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/pending-action-types.ts"],
4
- "sourcesContent": ["/**\n * Shared enums + error type for the Phase 3 WS-C mutation approval gate\n * (spec \u00A78 `AiPendingAction` + \u00A79 server contract).\n *\n * These values are referenced by the entity, the repository, the\n * `/api/ai/actions/*` routes (Steps 5.7 / 5.8 / 5.9), and the cleanup\n * worker (Step 5.12). Colocated here so every consumer shares the same\n * source of truth.\n */\n\nexport const AI_PENDING_ACTION_STATUSES = [\n 'pending',\n 'confirmed',\n 'cancelled',\n 'expired',\n 'executing',\n 'failed',\n] as const\n\nexport type AiPendingActionStatus = (typeof AI_PENDING_ACTION_STATUSES)[number]\n\nexport const AI_PENDING_ACTION_QUEUE_MODES = ['inline', 'stack'] as const\n\nexport type AiPendingActionQueueMode = (typeof AI_PENDING_ACTION_QUEUE_MODES)[number]\n\n/**\n * Allowed state-machine edges for `AiPendingAction.status`:\n *\n * ```\n * pending \u2500\u2500\u252C\u2500\u25B6 confirmed \u2500\u2500\u25B6 executing \u2500\u2500\u25B6 failed\n * \u2502 \u2514\u2500\u2500\u25B6 (terminal success keeps status = 'confirmed'\n * \u2502 and stores executionResult.recordId)\n * \u251C\u2500\u25B6 cancelled\n * \u2514\u2500\u25B6 expired\n * ```\n *\n * Every other transition is rejected with `AiPendingActionStateError`.\n */\nexport const AI_PENDING_ACTION_ALLOWED_TRANSITIONS: Record<\n AiPendingActionStatus,\n ReadonlyArray<AiPendingActionStatus>\n> = {\n pending: ['confirmed', 'cancelled', 'expired'],\n confirmed: ['executing'],\n executing: ['confirmed', 'failed'],\n cancelled: [],\n expired: [],\n failed: [],\n}\n\nexport const AI_PENDING_ACTION_TERMINAL_STATUSES: ReadonlyArray<AiPendingActionStatus> = [\n 'confirmed',\n 'cancelled',\n 'expired',\n 'failed',\n]\n\n/**\n * Per-record batch diff entry, mirrored in `AiPendingAction.records`.\n *\n * When present, the batch diff is authoritative and `fieldDiff` at the\n * top level is ignored by every consumer (spec \u00A78 rule 2).\n */\nexport type AiPendingActionRecordDiff = {\n recordId: string\n entityType: string\n label: string\n fieldDiff: Array<{ field: string; before: unknown; after: unknown }>\n recordVersion: string | null\n attachmentIds?: string[]\n}\n\n/**\n * Per-record failure shape populated by the confirm handler (Step 5.8)\n * when partial success occurs inside a batch.\n */\nexport type AiPendingActionFailedRecord = {\n recordId: string\n error: { code: string; message: string }\n}\n\nexport type AiPendingActionFieldDiff = {\n field: string\n before: unknown\n after: unknown\n}\n\n/**\n * Structured error context the confirm-executor stamps on\n * `executionResult.error` when a tool handler throws. Additive \u2014 every\n * field is optional so older serialized snapshots remain valid.\n *\n * - `details`: free-form structured payload extracted from the thrown\n * error (e.g. ZodError `issues`, `cause`, custom error properties).\n * Forwarded verbatim to the operator's \"Fix with AI\" prompt so the\n * model can correct the call instead of staring at \"Invalid input\".\n * - `input`: a JSON-serializable echo of the arguments the handler was\n * invoked with. Lets the model compare what it sent vs. what the\n * schema expected. PII-redaction is the caller's responsibility (the\n * confirm-executor passes through `normalizedInput` which has already\n * been Zod-parsed and stripped of unknown keys).\n * - `name`: the constructor name of the thrown error (e.g. `ZodError`,\n * `TypeError`) \u2014 useful when the message is generic.\n * - `stack`: short stack snippet for handler-side diagnostics. Kept off\n * by default (only populated when `OM_AI_INCLUDE_HANDLER_STACK=1`).\n */\nexport type AiPendingActionExecutionErrorDetails = {\n issues?: Array<{ path?: (string | number)[]; message?: string; code?: string }>\n fieldErrors?: Record<string, string[]>\n cause?: unknown\n [key: string]: unknown\n}\n\nexport type AiPendingActionExecutionResult = {\n recordId?: string\n commandName?: string\n error?: {\n code: string\n message: string\n name?: string\n details?: AiPendingActionExecutionErrorDetails\n input?: unknown\n stack?: string\n }\n}\n\n/**\n * Thrown by the repository when a caller attempts an illegal status\n * transition (e.g. `confirmed \u2192 pending`). Callers at the route layer\n * turn this into a `409 Conflict` response.\n */\nexport class AiPendingActionStateError extends Error {\n public readonly code = 'ai_pending_action_invalid_transition'\n\n constructor(\n public readonly from: AiPendingActionStatus,\n public readonly to: AiPendingActionStatus,\n ) {\n super(`Illegal AiPendingAction status transition: ${from} \u2192 ${to}`)\n this.name = 'AiPendingActionStateError'\n }\n}\n\nexport function isAiPendingActionStatus(\n value: unknown,\n): value is AiPendingActionStatus {\n return (\n typeof value === 'string' &&\n (AI_PENDING_ACTION_STATUSES as readonly string[]).includes(value)\n )\n}\n\nexport function isAiPendingActionQueueMode(\n value: unknown,\n): value is AiPendingActionQueueMode {\n return (\n typeof value === 'string' &&\n (AI_PENDING_ACTION_QUEUE_MODES as readonly string[]).includes(value)\n )\n}\n\nexport function isTerminalAiPendingActionStatus(\n status: AiPendingActionStatus,\n): boolean {\n return AI_PENDING_ACTION_TERMINAL_STATUSES.includes(status)\n}\n\nexport function isAllowedAiPendingActionTransition(\n from: AiPendingActionStatus,\n to: AiPendingActionStatus,\n): boolean {\n return (AI_PENDING_ACTION_ALLOWED_TRANSITIONS[from] ?? []).includes(to)\n}\n\n/**\n * Default TTL for a pending action (spec \u00A78 rule `expiresAt defaults to 10 min;\n * overridable per agent`). The runtime default is 15 min here because the\n * Step 5.5 brief pins it there; the repo reads `AI_PENDING_ACTION_TTL_SECONDS`\n * from the environment to allow override without a code change.\n */\nexport const AI_PENDING_ACTION_DEFAULT_TTL_SECONDS = 900\nexport const AI_PENDING_ACTION_TTL_ENV_VAR = 'AI_PENDING_ACTION_TTL_SECONDS'\n\nexport function resolveAiPendingActionTtlSeconds(\n env: NodeJS.ProcessEnv = process.env,\n): number {\n const raw = env[AI_PENDING_ACTION_TTL_ENV_VAR]\n if (raw == null) return AI_PENDING_ACTION_DEFAULT_TTL_SECONDS\n const parsed = Number.parseInt(String(raw), 10)\n if (!Number.isFinite(parsed) || parsed <= 0) {\n return AI_PENDING_ACTION_DEFAULT_TTL_SECONDS\n }\n return parsed\n}\n"],
5
- "mappings": "AAUO,MAAM,6BAA6B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAIO,MAAM,gCAAgC,CAAC,UAAU,OAAO;AAiBxD,MAAM,wCAGT;AAAA,EACF,SAAS,CAAC,aAAa,aAAa,SAAS;AAAA,EAC7C,WAAW,CAAC,WAAW;AAAA,EACvB,WAAW,CAAC,aAAa,QAAQ;AAAA,EACjC,WAAW,CAAC;AAAA,EACZ,SAAS,CAAC;AAAA,EACV,QAAQ,CAAC;AACX;AAEO,MAAM,sCAA4E;AAAA,EACvF;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AA4EO,MAAM,kCAAkC,MAAM;AAAA,EAGnD,YACkB,MACA,IAChB;AACA,UAAM,8CAA8C,IAAI,WAAM,EAAE,EAAE;AAHlD;AACA;AAJlB,SAAgB,OAAO;AAOrB,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,wBACd,OACgC;AAChC,SACE,OAAO,UAAU,YAChB,2BAAiD,SAAS,KAAK;AAEpE;AAEO,SAAS,2BACd,OACmC;AACnC,SACE,OAAO,UAAU,YAChB,8BAAoD,SAAS,KAAK;AAEvE;AAEO,SAAS,gCACd,QACS;AACT,SAAO,oCAAoC,SAAS,MAAM;AAC5D;AAEO,SAAS,mCACd,MACA,IACS;AACT,UAAQ,sCAAsC,IAAI,KAAK,CAAC,GAAG,SAAS,EAAE;AACxE;AAQO,MAAM,wCAAwC;AAC9C,MAAM,gCAAgC;AAEtC,SAAS,iCACd,MAAyB,QAAQ,KACzB;AACR,QAAM,MAAM,IAAI,6BAA6B;AAC7C,MAAI,OAAO,KAAM,QAAO;AACxB,QAAM,SAAS,OAAO,SAAS,OAAO,GAAG,GAAG,EAAE;AAC9C,MAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,GAAG;AAC3C,WAAO;AAAA,EACT;AACA,SAAO;AACT;",
4
+ "sourcesContent": ["/**\n * Shared enums + error type for the Phase 3 WS-C mutation approval gate\n * (spec \u00A78 `AiPendingAction` + \u00A79 server contract).\n *\n * These values are referenced by the entity, the repository, the\n * `/api/ai/actions/*` routes (Steps 5.7 / 5.8 / 5.9), and the cleanup\n * worker (Step 5.12). Colocated here so every consumer shares the same\n * source of truth.\n */\n\nexport const AI_PENDING_ACTION_STATUSES = [\n 'pending',\n 'confirmed',\n 'cancelled',\n 'expired',\n 'executing',\n 'failed',\n] as const\n\nexport type AiPendingActionStatus = (typeof AI_PENDING_ACTION_STATUSES)[number]\n\nexport const AI_PENDING_ACTION_QUEUE_MODES = ['inline', 'stack'] as const\n\nexport type AiPendingActionQueueMode = (typeof AI_PENDING_ACTION_QUEUE_MODES)[number]\n\n/**\n * Allowed state-machine edges for `AiPendingAction.status`:\n *\n * ```\n * pending \u2500\u2500\u252C\u2500\u25B6 confirmed \u2500\u2500\u25B6 executing \u2500\u2500\u25B6 failed\n * \u2502 \u2514\u2500\u2500\u25B6 (terminal success keeps status = 'confirmed'\n * \u2502 and stores executionResult.recordId)\n * \u251C\u2500\u25B6 cancelled\n * \u2514\u2500\u25B6 expired\n * ```\n *\n * Every other transition is rejected with `AiPendingActionStateError`.\n */\nexport const AI_PENDING_ACTION_ALLOWED_TRANSITIONS: Record<\n AiPendingActionStatus,\n ReadonlyArray<AiPendingActionStatus>\n> = {\n pending: ['confirmed', 'cancelled', 'expired'],\n confirmed: ['executing'],\n executing: ['confirmed', 'failed'],\n cancelled: [],\n expired: [],\n failed: [],\n}\n\nexport const AI_PENDING_ACTION_TERMINAL_STATUSES: ReadonlyArray<AiPendingActionStatus> = [\n 'confirmed',\n 'cancelled',\n 'expired',\n 'failed',\n]\n\n/**\n * Per-record batch diff entry, mirrored in `AiPendingAction.records`.\n *\n * When present, the batch diff is authoritative and `fieldDiff` at the\n * top level is ignored by every consumer (spec \u00A78 rule 2).\n */\nexport type AiPendingActionRecordDiff = {\n recordId: string\n entityType: string\n label: string\n fieldDiff: AiPendingActionFieldDiff[]\n recordVersion: string | null\n attachmentIds?: string[]\n}\n\n/**\n * Per-record failure shape populated by the confirm handler (Step 5.8)\n * when partial success occurs inside a batch.\n */\nexport type AiPendingActionFailedRecord = {\n recordId: string\n error: { code: string; message: string }\n}\n\nexport type AiPendingActionFieldDiff = {\n field: string\n fieldLabel?: string\n before: unknown\n after: unknown\n beforeDisplay?: unknown\n afterDisplay?: unknown\n}\n\n/**\n * Structured error context the confirm-executor stamps on\n * `executionResult.error` when a tool handler throws. Additive \u2014 every\n * field is optional so older serialized snapshots remain valid.\n *\n * - `details`: free-form structured payload extracted from the thrown\n * error (e.g. ZodError `issues`, `cause`, custom error properties).\n * Forwarded verbatim to the operator's \"Fix with AI\" prompt so the\n * model can correct the call instead of staring at \"Invalid input\".\n * - `input`: a JSON-serializable echo of the arguments the handler was\n * invoked with. Lets the model compare what it sent vs. what the\n * schema expected. PII-redaction is the caller's responsibility (the\n * confirm-executor passes through `normalizedInput` which has already\n * been Zod-parsed and stripped of unknown keys).\n * - `name`: the constructor name of the thrown error (e.g. `ZodError`,\n * `TypeError`) \u2014 useful when the message is generic.\n * - `stack`: short stack snippet for handler-side diagnostics. Kept off\n * by default (only populated when `OM_AI_INCLUDE_HANDLER_STACK=1`).\n */\nexport type AiPendingActionExecutionErrorDetails = {\n issues?: Array<{ path?: (string | number)[]; message?: string; code?: string }>\n fieldErrors?: Record<string, string[]>\n cause?: unknown\n [key: string]: unknown\n}\n\nexport type AiPendingActionExecutionResult = {\n recordId?: string\n commandName?: string\n error?: {\n code: string\n message: string\n name?: string\n details?: AiPendingActionExecutionErrorDetails\n input?: unknown\n stack?: string\n }\n}\n\n/**\n * Thrown by the repository when a caller attempts an illegal status\n * transition (e.g. `confirmed \u2192 pending`). Callers at the route layer\n * turn this into a `409 Conflict` response.\n */\nexport class AiPendingActionStateError extends Error {\n public readonly code = 'ai_pending_action_invalid_transition'\n\n constructor(\n public readonly from: AiPendingActionStatus,\n public readonly to: AiPendingActionStatus,\n ) {\n super(`Illegal AiPendingAction status transition: ${from} \u2192 ${to}`)\n this.name = 'AiPendingActionStateError'\n }\n}\n\nexport function isAiPendingActionStatus(\n value: unknown,\n): value is AiPendingActionStatus {\n return (\n typeof value === 'string' &&\n (AI_PENDING_ACTION_STATUSES as readonly string[]).includes(value)\n )\n}\n\nexport function isAiPendingActionQueueMode(\n value: unknown,\n): value is AiPendingActionQueueMode {\n return (\n typeof value === 'string' &&\n (AI_PENDING_ACTION_QUEUE_MODES as readonly string[]).includes(value)\n )\n}\n\nexport function isTerminalAiPendingActionStatus(\n status: AiPendingActionStatus,\n): boolean {\n return AI_PENDING_ACTION_TERMINAL_STATUSES.includes(status)\n}\n\nexport function isAllowedAiPendingActionTransition(\n from: AiPendingActionStatus,\n to: AiPendingActionStatus,\n): boolean {\n return (AI_PENDING_ACTION_ALLOWED_TRANSITIONS[from] ?? []).includes(to)\n}\n\n/**\n * Default TTL for a pending action (spec \u00A78 rule `expiresAt defaults to 10 min;\n * overridable per agent`). The runtime default is 15 min here because the\n * Step 5.5 brief pins it there; the repo reads `AI_PENDING_ACTION_TTL_SECONDS`\n * from the environment to allow override without a code change.\n */\nexport const AI_PENDING_ACTION_DEFAULT_TTL_SECONDS = 900\nexport const AI_PENDING_ACTION_TTL_ENV_VAR = 'AI_PENDING_ACTION_TTL_SECONDS'\n\nexport function resolveAiPendingActionTtlSeconds(\n env: NodeJS.ProcessEnv = process.env,\n): number {\n const raw = env[AI_PENDING_ACTION_TTL_ENV_VAR]\n if (raw == null) return AI_PENDING_ACTION_DEFAULT_TTL_SECONDS\n const parsed = Number.parseInt(String(raw), 10)\n if (!Number.isFinite(parsed) || parsed <= 0) {\n return AI_PENDING_ACTION_DEFAULT_TTL_SECONDS\n }\n return parsed\n}\n"],
5
+ "mappings": "AAUO,MAAM,6BAA6B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAIO,MAAM,gCAAgC,CAAC,UAAU,OAAO;AAiBxD,MAAM,wCAGT;AAAA,EACF,SAAS,CAAC,aAAa,aAAa,SAAS;AAAA,EAC7C,WAAW,CAAC,WAAW;AAAA,EACvB,WAAW,CAAC,aAAa,QAAQ;AAAA,EACjC,WAAW,CAAC;AAAA,EACZ,SAAS,CAAC;AAAA,EACV,QAAQ,CAAC;AACX;AAEO,MAAM,sCAA4E;AAAA,EACvF;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AA+EO,MAAM,kCAAkC,MAAM;AAAA,EAGnD,YACkB,MACA,IAChB;AACA,UAAM,8CAA8C,IAAI,WAAM,EAAE,EAAE;AAHlD;AACA;AAJlB,SAAgB,OAAO;AAOrB,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,wBACd,OACgC;AAChC,SACE,OAAO,UAAU,YAChB,2BAAiD,SAAS,KAAK;AAEpE;AAEO,SAAS,2BACd,OACmC;AACnC,SACE,OAAO,UAAU,YAChB,8BAAoD,SAAS,KAAK;AAEvE;AAEO,SAAS,gCACd,QACS;AACT,SAAO,oCAAoC,SAAS,MAAM;AAC5D;AAEO,SAAS,mCACd,MACA,IACS;AACT,UAAQ,sCAAsC,IAAI,KAAK,CAAC,GAAG,SAAS,EAAE;AACxE;AAQO,MAAM,wCAAwC;AAC9C,MAAM,gCAAgC;AAEtC,SAAS,iCACd,MAAyB,QAAQ,KACzB;AACR,QAAM,MAAM,IAAI,6BAA6B;AAC7C,MAAI,OAAO,KAAM,QAAO;AACxB,QAAM,SAAS,OAAO,SAAS,OAAO,GAAG,GAAG,EAAE;AAC9C,MAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,GAAG;AAC3C,WAAO;AAAA,EACT;AACA,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -77,7 +77,7 @@ function computeMutationIdempotencyKey(input) {
77
77
  });
78
78
  return createHash("sha256").update(canonical).digest("hex");
79
79
  }
80
- function computeFieldDiff(before, after) {
80
+ function computeFieldDiff(before, after, display) {
81
81
  const diff = [];
82
82
  const keys = /* @__PURE__ */ new Set([
83
83
  ...Object.keys(before ?? {}),
@@ -87,7 +87,17 @@ function computeFieldDiff(before, after) {
87
87
  const beforeValue = before ? before[field] : void 0;
88
88
  const afterValue = after ? after[field] : void 0;
89
89
  if (!Object.is(beforeValue, afterValue) && safeStringify(beforeValue) !== safeStringify(afterValue)) {
90
- diff.push({ field, before: beforeValue, after: afterValue });
90
+ const fieldLabel = display?.fieldLabels?.[field];
91
+ const beforeDisplay = display?.before?.[field];
92
+ const afterDisplay = display?.after?.[field];
93
+ diff.push({
94
+ field,
95
+ ...fieldLabel !== void 0 ? { fieldLabel } : {},
96
+ before: beforeValue,
97
+ after: afterValue,
98
+ ...beforeDisplay !== void 0 ? { beforeDisplay } : {},
99
+ ...afterDisplay !== void 0 ? { afterDisplay } : {}
100
+ });
91
101
  }
92
102
  }
93
103
  return diff;
@@ -168,8 +178,8 @@ async function buildSingleRecordDiff(tool, input, ctx) {
168
178
  sideEffectsSummary: null
169
179
  };
170
180
  }
171
- const patch = extractPatchFromArgs(input.toolCallArgs);
172
- const fieldDiff = computeFieldDiff(before.before, patch);
181
+ const patch = before.after ?? extractPatchFromArgs(input.toolCallArgs);
182
+ const fieldDiff = computeFieldDiff(before.before, patch, before.display);
173
183
  return {
174
184
  fieldDiff,
175
185
  targetEntityType: before.entityType,
@@ -203,12 +213,12 @@ async function buildBatchRecords(tool, input, ctx) {
203
213
  };
204
214
  }
205
215
  const diffs = rows.map((row) => {
206
- const patch = matchBatchPatch(input.toolCallArgs, row.recordId);
216
+ const patch = row.after ?? matchBatchPatch(input.toolCallArgs, row.recordId);
207
217
  return {
208
218
  recordId: row.recordId,
209
219
  entityType: row.entityType,
210
220
  label: row.label,
211
- fieldDiff: computeFieldDiff(row.before, patch),
221
+ fieldDiff: computeFieldDiff(row.before, patch, row.display),
212
222
  recordVersion: row.recordVersion ?? null
213
223
  };
214
224
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/ai_assistant/lib/prepare-mutation.ts"],
4
- "sourcesContent": ["import { createHash } from 'node:crypto'\nimport type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AiAgentDefinition, AiAgentMutationPolicy } from './ai-agent-definition'\nimport type { AiChatRequestContext, AiUiPart } from './attachment-bridge-types'\nimport type {\n AiToolDefinition,\n AiToolLoadBeforeRecord,\n AiToolLoadBeforeSingleRecord,\n McpToolContext,\n} from './types'\nimport { resolveEffectiveMutationPolicy } from './agent-policy'\nimport { AiPendingActionRepository } from '../data/repositories/AiPendingActionRepository'\nimport type { AiPendingAction } from '../data/entities'\nimport type {\n AiPendingActionFieldDiff,\n AiPendingActionRecordDiff,\n} from './pending-action-types'\n\n/**\n * Structured error raised by {@link prepareMutation}. Callers (today the\n * agent-runtime tool wrapper installed by `resolveAiAgentTools`) turn this\n * into a tool-call failure that the model surfaces back to the user without\n * leaking internals. The runtime NEVER reaches this helper when the agent\n * is declared read-only \u2014 the policy gate rejects the tool call upstream \u2014\n * but we keep the fail-closed check as a defensive guard.\n */\nexport class AiMutationPreparationError extends Error {\n constructor(\n public readonly code:\n | 'not_a_mutation_tool'\n | 'read_only_agent'\n | 'tenant_scope_missing'\n | 'container_missing'\n | 'em_missing',\n message: string,\n ) {\n super(message)\n this.name = 'AiMutationPreparationError'\n }\n}\n\nexport interface PrepareMutationInput {\n agent: AiAgentDefinition\n tool: AiToolDefinition\n toolCallArgs: Record<string, unknown>\n conversationId?: string | null\n /**\n * Optional downgrade the caller already resolved (mirror of\n * `resolveAiAgentTools({ mutationPolicyOverride })`). When omitted, the\n * agent's code-declared policy stands alone.\n */\n mutationPolicyOverride?: AiAgentMutationPolicy | null\n /**\n * Deterministic clock hook for tests. Defaults to `new Date()`.\n */\n now?: Date\n}\n\nexport interface PrepareMutationContext extends AiChatRequestContext {\n container: AwilixContainer\n}\n\nexport interface PrepareMutationResult {\n uiPart: AiUiPart\n pendingAction: AiPendingAction\n}\n\nconst MUTATION_PREVIEW_CARD_COMPONENT_ID = 'mutation-preview-card'\n\nconst NO_RESOLVER_SIDE_EFFECTS_MESSAGE =\n 'Tool did not declare a field-diff resolver; action will proceed without a preview.'\n\nfunction assertTenantScope(ctx: PrepareMutationContext): string {\n if (!ctx.tenantId) {\n throw new AiMutationPreparationError(\n 'tenant_scope_missing',\n 'prepareMutation requires a tenant-scoped request context.',\n )\n }\n return ctx.tenantId\n}\n\nfunction resolveEm(container: AwilixContainer): EntityManager {\n if (!container) {\n throw new AiMutationPreparationError(\n 'container_missing',\n 'prepareMutation requires an Awilix container to resolve the EntityManager.',\n )\n }\n let em: EntityManager | null = null\n try {\n em = container.resolve<EntityManager>('em')\n } catch {\n em = null\n }\n if (!em) {\n throw new AiMutationPreparationError(\n 'em_missing',\n 'prepareMutation could not resolve \"em\" from the container.',\n )\n }\n return em\n}\n\nfunction toolHandlerContext(ctx: PrepareMutationContext): McpToolContext {\n return {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n container: ctx.container,\n userFeatures: ctx.features,\n isSuperAdmin: ctx.isSuperAdmin,\n }\n}\n\nfunction safeStringify(value: unknown): string {\n const seen = new WeakSet<object>()\n return JSON.stringify(value, (_key, raw) => {\n if (raw && typeof raw === 'object') {\n if (seen.has(raw as object)) return '[Circular]'\n seen.add(raw as object)\n const entries = Object.entries(raw as Record<string, unknown>)\n entries.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n return entries.reduce<Record<string, unknown>>((acc, [k, v]) => {\n acc[k] = v\n return acc\n }, {})\n }\n return raw\n })\n}\n\n/**\n * Hashes `(tenantId, orgId, agentId, conversationId, toolName, normalizedInput)`\n * into a stable SHA-256 digest so that retries of the same tool call with the\n * same payload collapse to a single `AiPendingAction` row inside the TTL\n * window. The input is normalized through `safeStringify` to make object key\n * order irrelevant (spec \u00A78 rule `idempotencyKey prevents double-submission`).\n * Attachments are NOT included \u2014 the attachment set is captured separately on\n * the pending row so that re-uploading the same file set with a different\n * tool-call object never accidentally collides.\n */\nexport function computeMutationIdempotencyKey(input: {\n tenantId: string\n organizationId: string | null\n agentId: string\n conversationId: string | null\n toolName: string\n normalizedInput: Record<string, unknown>\n}): string {\n const canonical = safeStringify({\n tenant: input.tenantId,\n org: input.organizationId ?? null,\n agent: input.agentId,\n conversation: input.conversationId ?? null,\n tool: input.toolName,\n input: input.normalizedInput ?? {},\n })\n return createHash('sha256').update(canonical).digest('hex')\n}\n\nfunction computeFieldDiff(\n before: Record<string, unknown>,\n after: Record<string, unknown>,\n): AiPendingActionFieldDiff[] {\n const diff: AiPendingActionFieldDiff[] = []\n const keys = new Set<string>([\n ...Object.keys(before ?? {}),\n ...Object.keys(after ?? {}),\n ])\n for (const field of keys) {\n const beforeValue = before ? before[field] : undefined\n const afterValue = after ? after[field] : undefined\n if (!Object.is(beforeValue, afterValue) && safeStringify(beforeValue) !== safeStringify(afterValue)) {\n diff.push({ field, before: beforeValue, after: afterValue })\n }\n }\n return diff\n}\n\nfunction extractPatchFromArgs(\n args: Record<string, unknown>,\n): Record<string, unknown> {\n const raw = args?.patch\n if (raw && typeof raw === 'object' && !Array.isArray(raw)) {\n return raw as Record<string, unknown>\n }\n // Fall back: treat the whole args object (minus well-known envelope keys)\n // as the patch. This preserves compatibility with tools whose schema is\n // flat (`{ productId, name }`) rather than nested (`{ productId, patch }`).\n const envelope = new Set([\n 'id',\n 'recordId',\n 'records',\n 'attachmentIds',\n '_sessionToken',\n ])\n const reduced: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(args ?? {})) {\n if (envelope.has(key)) continue\n reduced[key] = value\n }\n return reduced\n}\n\nfunction matchBatchPatch(\n args: Record<string, unknown>,\n recordId: string,\n): Record<string, unknown> {\n const rawList = args?.records\n if (Array.isArray(rawList)) {\n const match = rawList.find((entry) => {\n if (!entry || typeof entry !== 'object') return false\n const candidate = entry as Record<string, unknown>\n return candidate.recordId === recordId || candidate.id === recordId\n })\n if (match && typeof match === 'object') {\n const patch = (match as Record<string, unknown>).patch\n if (patch && typeof patch === 'object' && !Array.isArray(patch)) {\n return patch as Record<string, unknown>\n }\n const envelope = new Set(['recordId', 'id'])\n const reduced: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(match as Record<string, unknown>)) {\n if (envelope.has(key)) continue\n reduced[key] = value\n }\n return reduced\n }\n }\n return {}\n}\n\nfunction normalizeAttachmentIds(args: Record<string, unknown>): string[] {\n const raw = args?.attachmentIds\n if (!Array.isArray(raw)) return []\n return raw.filter((value): value is string => typeof value === 'string' && value.length > 0)\n}\n\nasync function buildSingleRecordDiff(\n tool: AiToolDefinition,\n input: PrepareMutationInput,\n ctx: PrepareMutationContext,\n): Promise<{\n fieldDiff: AiPendingActionFieldDiff[]\n targetEntityType: string | null\n targetRecordId: string | null\n recordVersion: string | null\n sideEffectsSummary: string | null\n}> {\n const resolver = tool.loadBeforeRecord\n if (!resolver) {\n console.warn(\n `[AI Agents] prepareMutation: tool \"${tool.name}\" declared isMutation=true but no loadBeforeRecord resolver; shipping empty fieldDiff.`,\n )\n return {\n fieldDiff: [],\n targetEntityType: null,\n targetRecordId: null,\n recordVersion: null,\n sideEffectsSummary: NO_RESOLVER_SIDE_EFFECTS_MESSAGE,\n }\n }\n const handlerContext = toolHandlerContext(ctx)\n const before: AiToolLoadBeforeSingleRecord | null = await resolver(\n input.toolCallArgs as never,\n handlerContext,\n )\n if (!before) {\n return {\n fieldDiff: [],\n targetEntityType: null,\n targetRecordId: null,\n recordVersion: null,\n sideEffectsSummary: null,\n }\n }\n const patch = extractPatchFromArgs(input.toolCallArgs)\n const fieldDiff = computeFieldDiff(before.before, patch)\n return {\n fieldDiff,\n targetEntityType: before.entityType,\n targetRecordId: before.recordId,\n recordVersion: before.recordVersion,\n sideEffectsSummary: null,\n }\n}\n\nasync function buildBatchRecords(\n tool: AiToolDefinition,\n input: PrepareMutationInput,\n ctx: PrepareMutationContext,\n): Promise<{\n records: AiPendingActionRecordDiff[] | null\n targetEntityType: string | null\n sideEffectsSummary: string | null\n}> {\n const resolver = tool.loadBeforeRecords\n if (!resolver) {\n console.warn(\n `[AI Agents] prepareMutation: bulk tool \"${tool.name}\" declared isMutation=true but no loadBeforeRecords resolver; shipping empty records[].`,\n )\n return {\n records: null,\n targetEntityType: null,\n sideEffectsSummary: NO_RESOLVER_SIDE_EFFECTS_MESSAGE,\n }\n }\n const handlerContext = toolHandlerContext(ctx)\n const rows: AiToolLoadBeforeRecord[] = await resolver(\n input.toolCallArgs as never,\n handlerContext,\n )\n if (!Array.isArray(rows) || rows.length === 0) {\n return {\n records: null,\n targetEntityType: null,\n sideEffectsSummary: null,\n }\n }\n const diffs: AiPendingActionRecordDiff[] = rows.map((row) => {\n const patch = matchBatchPatch(input.toolCallArgs, row.recordId)\n return {\n recordId: row.recordId,\n entityType: row.entityType,\n label: row.label,\n fieldDiff: computeFieldDiff(row.before, patch),\n recordVersion: row.recordVersion ?? null,\n }\n })\n const [firstEntity] = rows\n return {\n records: diffs,\n targetEntityType: firstEntity ? firstEntity.entityType : null,\n sideEffectsSummary: null,\n }\n}\n\n/**\n * Intercepts a mutation tool call and turns it into an `AiPendingAction` +\n * `mutation-preview-card` UI part (spec Phase 3 WS-C \u00A79). The caller MUST\n * have already confirmed the agent's effective `mutationPolicy` is NOT\n * `read-only`; this helper repeats the check defensively because skipping it\n * would be a policy-bypass.\n *\n * The tool handler is NEVER invoked by this function \u2014 the write is\n * short-circuited and only runs from the Step 5.8 confirm route. See the\n * unit test `does not call the tool handler` for the guard.\n */\nexport async function prepareMutation(\n input: PrepareMutationInput,\n ctx: PrepareMutationContext,\n): Promise<PrepareMutationResult> {\n const { agent, tool } = input\n if (tool.isMutation !== true) {\n throw new AiMutationPreparationError(\n 'not_a_mutation_tool',\n `Tool \"${tool.name}\" is not a mutation tool; prepareMutation should not be invoked.`,\n )\n }\n const effectivePolicy = resolveEffectiveMutationPolicy(\n agent.mutationPolicy,\n input.mutationPolicyOverride ?? null,\n agent.id,\n )\n if (effectivePolicy === 'read-only') {\n throw new AiMutationPreparationError(\n 'read_only_agent',\n `Agent \"${agent.id}\" has effective mutationPolicy=read-only; mutation tool \"${tool.name}\" cannot be prepared.`,\n )\n }\n\n const tenantId = assertTenantScope(ctx)\n const em = resolveEm(ctx.container)\n const repo = new AiPendingActionRepository(em)\n\n const isBulk = tool.isBulk === true\n let fieldDiff: AiPendingActionFieldDiff[] = []\n let records: AiPendingActionRecordDiff[] | null = null\n let targetEntityType: string | null = null\n let targetRecordId: string | null = null\n let recordVersion: string | null = null\n let sideEffectsSummary: string | null = null\n\n if (isBulk) {\n const batch = await buildBatchRecords(tool, input, ctx)\n records = batch.records\n targetEntityType = batch.targetEntityType\n sideEffectsSummary = batch.sideEffectsSummary\n } else {\n const single = await buildSingleRecordDiff(tool, input, ctx)\n fieldDiff = single.fieldDiff\n targetEntityType = single.targetEntityType\n targetRecordId = single.targetRecordId\n recordVersion = single.recordVersion\n sideEffectsSummary = single.sideEffectsSummary\n }\n\n const normalizedInput = input.toolCallArgs ?? {}\n const conversationId = input.conversationId ?? null\n const idempotencyKey = computeMutationIdempotencyKey({\n tenantId,\n organizationId: ctx.organizationId ?? null,\n agentId: agent.id,\n conversationId,\n toolName: tool.name,\n normalizedInput,\n })\n\n const pendingAction = await repo.create(\n {\n agentId: agent.id,\n toolName: tool.name,\n idempotencyKey,\n createdByUserId: ctx.userId,\n normalizedInput,\n conversationId,\n targetEntityType,\n targetRecordId,\n fieldDiff,\n records,\n sideEffectsSummary,\n recordVersion,\n attachmentIds: normalizeAttachmentIds(normalizedInput),\n now: input.now,\n },\n {\n tenantId,\n organizationId: ctx.organizationId ?? null,\n userId: ctx.userId,\n },\n )\n\n const uiPart: AiUiPart = {\n componentId: MUTATION_PREVIEW_CARD_COMPONENT_ID,\n props: {\n pendingActionId: pendingAction.id,\n expiresAt: pendingAction.expiresAt.toISOString(),\n ...(records ? { records } : { fieldDiff }),\n ...(sideEffectsSummary ? { sideEffectsSummary } : {}),\n },\n }\n\n return { uiPart, pendingAction }\n}\n\nexport const MUTATION_PREVIEW_CARD_COMPONENT = MUTATION_PREVIEW_CARD_COMPONENT_ID\n"],
5
- "mappings": "AAAA,SAAS,kBAAkB;AAW3B,SAAS,sCAAsC;AAC/C,SAAS,iCAAiC;AAenC,MAAM,mCAAmC,MAAM;AAAA,EACpD,YACkB,MAMhB,SACA;AACA,UAAM,OAAO;AARG;AAShB,SAAK,OAAO;AAAA,EACd;AACF;AA4BA,MAAM,qCAAqC;AAE3C,MAAM,mCACJ;AAEF,SAAS,kBAAkB,KAAqC;AAC9D,MAAI,CAAC,IAAI,UAAU;AACjB,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,SAAO,IAAI;AACb;AAEA,SAAS,UAAU,WAA2C;AAC5D,MAAI,CAAC,WAAW;AACd,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,MAAI,KAA2B;AAC/B,MAAI;AACF,SAAK,UAAU,QAAuB,IAAI;AAAA,EAC5C,QAAQ;AACN,SAAK;AAAA,EACP;AACA,MAAI,CAAC,IAAI;AACP,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,mBAAmB,KAA6C;AACvE,SAAO;AAAA,IACL,UAAU,IAAI;AAAA,IACd,gBAAgB,IAAI;AAAA,IACpB,QAAQ,IAAI;AAAA,IACZ,WAAW,IAAI;AAAA,IACf,cAAc,IAAI;AAAA,IAClB,cAAc,IAAI;AAAA,EACpB;AACF;AAEA,SAAS,cAAc,OAAwB;AAC7C,QAAM,OAAO,oBAAI,QAAgB;AACjC,SAAO,KAAK,UAAU,OAAO,CAAC,MAAM,QAAQ;AAC1C,QAAI,OAAO,OAAO,QAAQ,UAAU;AAClC,UAAI,KAAK,IAAI,GAAa,EAAG,QAAO;AACpC,WAAK,IAAI,GAAa;AACtB,YAAM,UAAU,OAAO,QAAQ,GAA8B;AAC7D,cAAQ,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAE;AACvD,aAAO,QAAQ,OAAgC,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM;AAC9D,YAAI,CAAC,IAAI;AACT,eAAO;AAAA,MACT,GAAG,CAAC,CAAC;AAAA,IACP;AACA,WAAO;AAAA,EACT,CAAC;AACH;AAYO,SAAS,8BAA8B,OAOnC;AACT,QAAM,YAAY,cAAc;AAAA,IAC9B,QAAQ,MAAM;AAAA,IACd,KAAK,MAAM,kBAAkB;AAAA,IAC7B,OAAO,MAAM;AAAA,IACb,cAAc,MAAM,kBAAkB;AAAA,IACtC,MAAM,MAAM;AAAA,IACZ,OAAO,MAAM,mBAAmB,CAAC;AAAA,EACnC,CAAC;AACD,SAAO,WAAW,QAAQ,EAAE,OAAO,SAAS,EAAE,OAAO,KAAK;AAC5D;AAEA,SAAS,iBACP,QACA,OAC4B;AAC5B,QAAM,OAAmC,CAAC;AAC1C,QAAM,OAAO,oBAAI,IAAY;AAAA,IAC3B,GAAG,OAAO,KAAK,UAAU,CAAC,CAAC;AAAA,IAC3B,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC;AAAA,EAC5B,CAAC;AACD,aAAW,SAAS,MAAM;AACxB,UAAM,cAAc,SAAS,OAAO,KAAK,IAAI;AAC7C,UAAM,aAAa,QAAQ,MAAM,KAAK,IAAI;AAC1C,QAAI,CAAC,OAAO,GAAG,aAAa,UAAU,KAAK,cAAc,WAAW,MAAM,cAAc,UAAU,GAAG;AACnG,WAAK,KAAK,EAAE,OAAO,QAAQ,aAAa,OAAO,WAAW,CAAC;AAAA,IAC7D;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,qBACP,MACyB;AACzB,QAAM,MAAM,MAAM;AAClB,MAAI,OAAO,OAAO,QAAQ,YAAY,CAAC,MAAM,QAAQ,GAAG,GAAG;AACzD,WAAO;AAAA,EACT;AAIA,QAAM,WAAW,oBAAI,IAAI;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,QAAM,UAAmC,CAAC;AAC1C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,CAAC,CAAC,GAAG;AACrD,QAAI,SAAS,IAAI,GAAG,EAAG;AACvB,YAAQ,GAAG,IAAI;AAAA,EACjB;AACA,SAAO;AACT;AAEA,SAAS,gBACP,MACA,UACyB;AACzB,QAAM,UAAU,MAAM;AACtB,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,UAAM,QAAQ,QAAQ,KAAK,CAAC,UAAU;AACpC,UAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,YAAM,YAAY;AAClB,aAAO,UAAU,aAAa,YAAY,UAAU,OAAO;AAAA,IAC7D,CAAC;AACD,QAAI,SAAS,OAAO,UAAU,UAAU;AACtC,YAAM,QAAS,MAAkC;AACjD,UAAI,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AAC/D,eAAO;AAAA,MACT;AACA,YAAM,WAAW,oBAAI,IAAI,CAAC,YAAY,IAAI,CAAC;AAC3C,YAAM,UAAmC,CAAC;AAC1C,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAgC,GAAG;AAC3E,YAAI,SAAS,IAAI,GAAG,EAAG;AACvB,gBAAQ,GAAG,IAAI;AAAA,MACjB;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO,CAAC;AACV;AAEA,SAAS,uBAAuB,MAAyC;AACvE,QAAM,MAAM,MAAM;AAClB,MAAI,CAAC,MAAM,QAAQ,GAAG,EAAG,QAAO,CAAC;AACjC,SAAO,IAAI,OAAO,CAAC,UAA2B,OAAO,UAAU,YAAY,MAAM,SAAS,CAAC;AAC7F;AAEA,eAAe,sBACb,MACA,OACA,KAOC;AACD,QAAM,WAAW,KAAK;AACtB,MAAI,CAAC,UAAU;AACb,YAAQ;AAAA,MACN,sCAAsC,KAAK,IAAI;AAAA,IACjD;AACA,WAAO;AAAA,MACL,WAAW,CAAC;AAAA,MACZ,kBAAkB;AAAA,MAClB,gBAAgB;AAAA,MAChB,eAAe;AAAA,MACf,oBAAoB;AAAA,IACtB;AAAA,EACF;AACA,QAAM,iBAAiB,mBAAmB,GAAG;AAC7C,QAAM,SAA8C,MAAM;AAAA,IACxD,MAAM;AAAA,IACN;AAAA,EACF;AACA,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,MACL,WAAW,CAAC;AAAA,MACZ,kBAAkB;AAAA,MAClB,gBAAgB;AAAA,MAChB,eAAe;AAAA,MACf,oBAAoB;AAAA,IACtB;AAAA,EACF;AACA,QAAM,QAAQ,qBAAqB,MAAM,YAAY;AACrD,QAAM,YAAY,iBAAiB,OAAO,QAAQ,KAAK;AACvD,SAAO;AAAA,IACL;AAAA,IACA,kBAAkB,OAAO;AAAA,IACzB,gBAAgB,OAAO;AAAA,IACvB,eAAe,OAAO;AAAA,IACtB,oBAAoB;AAAA,EACtB;AACF;AAEA,eAAe,kBACb,MACA,OACA,KAKC;AACD,QAAM,WAAW,KAAK;AACtB,MAAI,CAAC,UAAU;AACb,YAAQ;AAAA,MACN,2CAA2C,KAAK,IAAI;AAAA,IACtD;AACA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,kBAAkB;AAAA,MAClB,oBAAoB;AAAA,IACtB;AAAA,EACF;AACA,QAAM,iBAAiB,mBAAmB,GAAG;AAC7C,QAAM,OAAiC,MAAM;AAAA,IAC3C,MAAM;AAAA,IACN;AAAA,EACF;AACA,MAAI,CAAC,MAAM,QAAQ,IAAI,KAAK,KAAK,WAAW,GAAG;AAC7C,WAAO;AAAA,MACL,SAAS;AAAA,MACT,kBAAkB;AAAA,MAClB,oBAAoB;AAAA,IACtB;AAAA,EACF;AACA,QAAM,QAAqC,KAAK,IAAI,CAAC,QAAQ;AAC3D,UAAM,QAAQ,gBAAgB,MAAM,cAAc,IAAI,QAAQ;AAC9D,WAAO;AAAA,MACL,UAAU,IAAI;AAAA,MACd,YAAY,IAAI;AAAA,MAChB,OAAO,IAAI;AAAA,MACX,WAAW,iBAAiB,IAAI,QAAQ,KAAK;AAAA,MAC7C,eAAe,IAAI,iBAAiB;AAAA,IACtC;AAAA,EACF,CAAC;AACD,QAAM,CAAC,WAAW,IAAI;AACtB,SAAO;AAAA,IACL,SAAS;AAAA,IACT,kBAAkB,cAAc,YAAY,aAAa;AAAA,IACzD,oBAAoB;AAAA,EACtB;AACF;AAaA,eAAsB,gBACpB,OACA,KACgC;AAChC,QAAM,EAAE,OAAO,KAAK,IAAI;AACxB,MAAI,KAAK,eAAe,MAAM;AAC5B,UAAM,IAAI;AAAA,MACR;AAAA,MACA,SAAS,KAAK,IAAI;AAAA,IACpB;AAAA,EACF;AACA,QAAM,kBAAkB;AAAA,IACtB,MAAM;AAAA,IACN,MAAM,0BAA0B;AAAA,IAChC,MAAM;AAAA,EACR;AACA,MAAI,oBAAoB,aAAa;AACnC,UAAM,IAAI;AAAA,MACR;AAAA,MACA,UAAU,MAAM,EAAE,4DAA4D,KAAK,IAAI;AAAA,IACzF;AAAA,EACF;AAEA,QAAM,WAAW,kBAAkB,GAAG;AACtC,QAAM,KAAK,UAAU,IAAI,SAAS;AAClC,QAAM,OAAO,IAAI,0BAA0B,EAAE;AAE7C,QAAM,SAAS,KAAK,WAAW;AAC/B,MAAI,YAAwC,CAAC;AAC7C,MAAI,UAA8C;AAClD,MAAI,mBAAkC;AACtC,MAAI,iBAAgC;AACpC,MAAI,gBAA+B;AACnC,MAAI,qBAAoC;AAExC,MAAI,QAAQ;AACV,UAAM,QAAQ,MAAM,kBAAkB,MAAM,OAAO,GAAG;AACtD,cAAU,MAAM;AAChB,uBAAmB,MAAM;AACzB,yBAAqB,MAAM;AAAA,EAC7B,OAAO;AACL,UAAM,SAAS,MAAM,sBAAsB,MAAM,OAAO,GAAG;AAC3D,gBAAY,OAAO;AACnB,uBAAmB,OAAO;AAC1B,qBAAiB,OAAO;AACxB,oBAAgB,OAAO;AACvB,yBAAqB,OAAO;AAAA,EAC9B;AAEA,QAAM,kBAAkB,MAAM,gBAAgB,CAAC;AAC/C,QAAM,iBAAiB,MAAM,kBAAkB;AAC/C,QAAM,iBAAiB,8BAA8B;AAAA,IACnD;AAAA,IACA,gBAAgB,IAAI,kBAAkB;AAAA,IACtC,SAAS,MAAM;AAAA,IACf;AAAA,IACA,UAAU,KAAK;AAAA,IACf;AAAA,EACF,CAAC;AAED,QAAM,gBAAgB,MAAM,KAAK;AAAA,IAC/B;AAAA,MACE,SAAS,MAAM;AAAA,MACf,UAAU,KAAK;AAAA,MACf;AAAA,MACA,iBAAiB,IAAI;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,eAAe,uBAAuB,eAAe;AAAA,MACrD,KAAK,MAAM;AAAA,IACb;AAAA,IACA;AAAA,MACE;AAAA,MACA,gBAAgB,IAAI,kBAAkB;AAAA,MACtC,QAAQ,IAAI;AAAA,IACd;AAAA,EACF;AAEA,QAAM,SAAmB;AAAA,IACvB,aAAa;AAAA,IACb,OAAO;AAAA,MACL,iBAAiB,cAAc;AAAA,MAC/B,WAAW,cAAc,UAAU,YAAY;AAAA,MAC/C,GAAI,UAAU,EAAE,QAAQ,IAAI,EAAE,UAAU;AAAA,MACxC,GAAI,qBAAqB,EAAE,mBAAmB,IAAI,CAAC;AAAA,IACrD;AAAA,EACF;AAEA,SAAO,EAAE,QAAQ,cAAc;AACjC;AAEO,MAAM,kCAAkC;",
4
+ "sourcesContent": ["import { createHash } from 'node:crypto'\nimport type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AiAgentDefinition, AiAgentMutationPolicy } from './ai-agent-definition'\nimport type { AiChatRequestContext, AiUiPart } from './attachment-bridge-types'\nimport type {\n AiToolDefinition,\n AiToolFieldDiffDisplayHints,\n AiToolLoadBeforeRecord,\n AiToolLoadBeforeSingleRecord,\n McpToolContext,\n} from './types'\nimport { resolveEffectiveMutationPolicy } from './agent-policy'\nimport { AiPendingActionRepository } from '../data/repositories/AiPendingActionRepository'\nimport type { AiPendingAction } from '../data/entities'\nimport type {\n AiPendingActionFieldDiff,\n AiPendingActionRecordDiff,\n} from './pending-action-types'\n\n/**\n * Structured error raised by {@link prepareMutation}. Callers (today the\n * agent-runtime tool wrapper installed by `resolveAiAgentTools`) turn this\n * into a tool-call failure that the model surfaces back to the user without\n * leaking internals. The runtime NEVER reaches this helper when the agent\n * is declared read-only \u2014 the policy gate rejects the tool call upstream \u2014\n * but we keep the fail-closed check as a defensive guard.\n */\nexport class AiMutationPreparationError extends Error {\n constructor(\n public readonly code:\n | 'not_a_mutation_tool'\n | 'read_only_agent'\n | 'tenant_scope_missing'\n | 'container_missing'\n | 'em_missing',\n message: string,\n ) {\n super(message)\n this.name = 'AiMutationPreparationError'\n }\n}\n\nexport interface PrepareMutationInput {\n agent: AiAgentDefinition\n tool: AiToolDefinition\n toolCallArgs: Record<string, unknown>\n conversationId?: string | null\n /**\n * Optional downgrade the caller already resolved (mirror of\n * `resolveAiAgentTools({ mutationPolicyOverride })`). When omitted, the\n * agent's code-declared policy stands alone.\n */\n mutationPolicyOverride?: AiAgentMutationPolicy | null\n /**\n * Deterministic clock hook for tests. Defaults to `new Date()`.\n */\n now?: Date\n}\n\nexport interface PrepareMutationContext extends AiChatRequestContext {\n container: AwilixContainer\n}\n\nexport interface PrepareMutationResult {\n uiPart: AiUiPart\n pendingAction: AiPendingAction\n}\n\nconst MUTATION_PREVIEW_CARD_COMPONENT_ID = 'mutation-preview-card'\n\nconst NO_RESOLVER_SIDE_EFFECTS_MESSAGE =\n 'Tool did not declare a field-diff resolver; action will proceed without a preview.'\n\nfunction assertTenantScope(ctx: PrepareMutationContext): string {\n if (!ctx.tenantId) {\n throw new AiMutationPreparationError(\n 'tenant_scope_missing',\n 'prepareMutation requires a tenant-scoped request context.',\n )\n }\n return ctx.tenantId\n}\n\nfunction resolveEm(container: AwilixContainer): EntityManager {\n if (!container) {\n throw new AiMutationPreparationError(\n 'container_missing',\n 'prepareMutation requires an Awilix container to resolve the EntityManager.',\n )\n }\n let em: EntityManager | null = null\n try {\n em = container.resolve<EntityManager>('em')\n } catch {\n em = null\n }\n if (!em) {\n throw new AiMutationPreparationError(\n 'em_missing',\n 'prepareMutation could not resolve \"em\" from the container.',\n )\n }\n return em\n}\n\nfunction toolHandlerContext(ctx: PrepareMutationContext): McpToolContext {\n return {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n container: ctx.container,\n userFeatures: ctx.features,\n isSuperAdmin: ctx.isSuperAdmin,\n }\n}\n\nfunction safeStringify(value: unknown): string {\n const seen = new WeakSet<object>()\n return JSON.stringify(value, (_key, raw) => {\n if (raw && typeof raw === 'object') {\n if (seen.has(raw as object)) return '[Circular]'\n seen.add(raw as object)\n const entries = Object.entries(raw as Record<string, unknown>)\n entries.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n return entries.reduce<Record<string, unknown>>((acc, [k, v]) => {\n acc[k] = v\n return acc\n }, {})\n }\n return raw\n })\n}\n\n/**\n * Hashes `(tenantId, orgId, agentId, conversationId, toolName, normalizedInput)`\n * into a stable SHA-256 digest so that retries of the same tool call with the\n * same payload collapse to a single `AiPendingAction` row inside the TTL\n * window. The input is normalized through `safeStringify` to make object key\n * order irrelevant (spec \u00A78 rule `idempotencyKey prevents double-submission`).\n * Attachments are NOT included \u2014 the attachment set is captured separately on\n * the pending row so that re-uploading the same file set with a different\n * tool-call object never accidentally collides.\n */\nexport function computeMutationIdempotencyKey(input: {\n tenantId: string\n organizationId: string | null\n agentId: string\n conversationId: string | null\n toolName: string\n normalizedInput: Record<string, unknown>\n}): string {\n const canonical = safeStringify({\n tenant: input.tenantId,\n org: input.organizationId ?? null,\n agent: input.agentId,\n conversation: input.conversationId ?? null,\n tool: input.toolName,\n input: input.normalizedInput ?? {},\n })\n return createHash('sha256').update(canonical).digest('hex')\n}\n\nfunction computeFieldDiff(\n before: Record<string, unknown>,\n after: Record<string, unknown>,\n display?: AiToolFieldDiffDisplayHints,\n): AiPendingActionFieldDiff[] {\n const diff: AiPendingActionFieldDiff[] = []\n const keys = new Set<string>([\n ...Object.keys(before ?? {}),\n ...Object.keys(after ?? {}),\n ])\n for (const field of keys) {\n const beforeValue = before ? before[field] : undefined\n const afterValue = after ? after[field] : undefined\n if (!Object.is(beforeValue, afterValue) && safeStringify(beforeValue) !== safeStringify(afterValue)) {\n const fieldLabel = display?.fieldLabels?.[field]\n const beforeDisplay = display?.before?.[field]\n const afterDisplay = display?.after?.[field]\n diff.push({\n field,\n ...(fieldLabel !== undefined ? { fieldLabel } : {}),\n before: beforeValue,\n after: afterValue,\n ...(beforeDisplay !== undefined ? { beforeDisplay } : {}),\n ...(afterDisplay !== undefined ? { afterDisplay } : {}),\n })\n }\n }\n return diff\n}\n\nfunction extractPatchFromArgs(\n args: Record<string, unknown>,\n): Record<string, unknown> {\n const raw = args?.patch\n if (raw && typeof raw === 'object' && !Array.isArray(raw)) {\n return raw as Record<string, unknown>\n }\n // Fall back: treat the whole args object (minus well-known envelope keys)\n // as the patch. This preserves compatibility with tools whose schema is\n // flat (`{ productId, name }`) rather than nested (`{ productId, patch }`).\n const envelope = new Set([\n 'id',\n 'recordId',\n 'records',\n 'attachmentIds',\n '_sessionToken',\n ])\n const reduced: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(args ?? {})) {\n if (envelope.has(key)) continue\n reduced[key] = value\n }\n return reduced\n}\n\nfunction matchBatchPatch(\n args: Record<string, unknown>,\n recordId: string,\n): Record<string, unknown> {\n const rawList = args?.records\n if (Array.isArray(rawList)) {\n const match = rawList.find((entry) => {\n if (!entry || typeof entry !== 'object') return false\n const candidate = entry as Record<string, unknown>\n return candidate.recordId === recordId || candidate.id === recordId\n })\n if (match && typeof match === 'object') {\n const patch = (match as Record<string, unknown>).patch\n if (patch && typeof patch === 'object' && !Array.isArray(patch)) {\n return patch as Record<string, unknown>\n }\n const envelope = new Set(['recordId', 'id'])\n const reduced: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(match as Record<string, unknown>)) {\n if (envelope.has(key)) continue\n reduced[key] = value\n }\n return reduced\n }\n }\n return {}\n}\n\nfunction normalizeAttachmentIds(args: Record<string, unknown>): string[] {\n const raw = args?.attachmentIds\n if (!Array.isArray(raw)) return []\n return raw.filter((value): value is string => typeof value === 'string' && value.length > 0)\n}\n\nasync function buildSingleRecordDiff(\n tool: AiToolDefinition,\n input: PrepareMutationInput,\n ctx: PrepareMutationContext,\n): Promise<{\n fieldDiff: AiPendingActionFieldDiff[]\n targetEntityType: string | null\n targetRecordId: string | null\n recordVersion: string | null\n sideEffectsSummary: string | null\n}> {\n const resolver = tool.loadBeforeRecord\n if (!resolver) {\n console.warn(\n `[AI Agents] prepareMutation: tool \"${tool.name}\" declared isMutation=true but no loadBeforeRecord resolver; shipping empty fieldDiff.`,\n )\n return {\n fieldDiff: [],\n targetEntityType: null,\n targetRecordId: null,\n recordVersion: null,\n sideEffectsSummary: NO_RESOLVER_SIDE_EFFECTS_MESSAGE,\n }\n }\n const handlerContext = toolHandlerContext(ctx)\n const before: AiToolLoadBeforeSingleRecord | null = await resolver(\n input.toolCallArgs as never,\n handlerContext,\n )\n if (!before) {\n return {\n fieldDiff: [],\n targetEntityType: null,\n targetRecordId: null,\n recordVersion: null,\n sideEffectsSummary: null,\n }\n }\n const patch = before.after ?? extractPatchFromArgs(input.toolCallArgs)\n const fieldDiff = computeFieldDiff(before.before, patch, before.display)\n return {\n fieldDiff,\n targetEntityType: before.entityType,\n targetRecordId: before.recordId,\n recordVersion: before.recordVersion,\n sideEffectsSummary: null,\n }\n}\n\nasync function buildBatchRecords(\n tool: AiToolDefinition,\n input: PrepareMutationInput,\n ctx: PrepareMutationContext,\n): Promise<{\n records: AiPendingActionRecordDiff[] | null\n targetEntityType: string | null\n sideEffectsSummary: string | null\n}> {\n const resolver = tool.loadBeforeRecords\n if (!resolver) {\n console.warn(\n `[AI Agents] prepareMutation: bulk tool \"${tool.name}\" declared isMutation=true but no loadBeforeRecords resolver; shipping empty records[].`,\n )\n return {\n records: null,\n targetEntityType: null,\n sideEffectsSummary: NO_RESOLVER_SIDE_EFFECTS_MESSAGE,\n }\n }\n const handlerContext = toolHandlerContext(ctx)\n const rows: AiToolLoadBeforeRecord[] = await resolver(\n input.toolCallArgs as never,\n handlerContext,\n )\n if (!Array.isArray(rows) || rows.length === 0) {\n return {\n records: null,\n targetEntityType: null,\n sideEffectsSummary: null,\n }\n }\n const diffs: AiPendingActionRecordDiff[] = rows.map((row) => {\n const patch = row.after ?? matchBatchPatch(input.toolCallArgs, row.recordId)\n return {\n recordId: row.recordId,\n entityType: row.entityType,\n label: row.label,\n fieldDiff: computeFieldDiff(row.before, patch, row.display),\n recordVersion: row.recordVersion ?? null,\n }\n })\n const [firstEntity] = rows\n return {\n records: diffs,\n targetEntityType: firstEntity ? firstEntity.entityType : null,\n sideEffectsSummary: null,\n }\n}\n\n/**\n * Intercepts a mutation tool call and turns it into an `AiPendingAction` +\n * `mutation-preview-card` UI part (spec Phase 3 WS-C \u00A79). The caller MUST\n * have already confirmed the agent's effective `mutationPolicy` is NOT\n * `read-only`; this helper repeats the check defensively because skipping it\n * would be a policy-bypass.\n *\n * The tool handler is NEVER invoked by this function \u2014 the write is\n * short-circuited and only runs from the Step 5.8 confirm route. See the\n * unit test `does not call the tool handler` for the guard.\n */\nexport async function prepareMutation(\n input: PrepareMutationInput,\n ctx: PrepareMutationContext,\n): Promise<PrepareMutationResult> {\n const { agent, tool } = input\n if (tool.isMutation !== true) {\n throw new AiMutationPreparationError(\n 'not_a_mutation_tool',\n `Tool \"${tool.name}\" is not a mutation tool; prepareMutation should not be invoked.`,\n )\n }\n const effectivePolicy = resolveEffectiveMutationPolicy(\n agent.mutationPolicy,\n input.mutationPolicyOverride ?? null,\n agent.id,\n )\n if (effectivePolicy === 'read-only') {\n throw new AiMutationPreparationError(\n 'read_only_agent',\n `Agent \"${agent.id}\" has effective mutationPolicy=read-only; mutation tool \"${tool.name}\" cannot be prepared.`,\n )\n }\n\n const tenantId = assertTenantScope(ctx)\n const em = resolveEm(ctx.container)\n const repo = new AiPendingActionRepository(em)\n\n const isBulk = tool.isBulk === true\n let fieldDiff: AiPendingActionFieldDiff[] = []\n let records: AiPendingActionRecordDiff[] | null = null\n let targetEntityType: string | null = null\n let targetRecordId: string | null = null\n let recordVersion: string | null = null\n let sideEffectsSummary: string | null = null\n\n if (isBulk) {\n const batch = await buildBatchRecords(tool, input, ctx)\n records = batch.records\n targetEntityType = batch.targetEntityType\n sideEffectsSummary = batch.sideEffectsSummary\n } else {\n const single = await buildSingleRecordDiff(tool, input, ctx)\n fieldDiff = single.fieldDiff\n targetEntityType = single.targetEntityType\n targetRecordId = single.targetRecordId\n recordVersion = single.recordVersion\n sideEffectsSummary = single.sideEffectsSummary\n }\n\n const normalizedInput = input.toolCallArgs ?? {}\n const conversationId = input.conversationId ?? null\n const idempotencyKey = computeMutationIdempotencyKey({\n tenantId,\n organizationId: ctx.organizationId ?? null,\n agentId: agent.id,\n conversationId,\n toolName: tool.name,\n normalizedInput,\n })\n\n const pendingAction = await repo.create(\n {\n agentId: agent.id,\n toolName: tool.name,\n idempotencyKey,\n createdByUserId: ctx.userId,\n normalizedInput,\n conversationId,\n targetEntityType,\n targetRecordId,\n fieldDiff,\n records,\n sideEffectsSummary,\n recordVersion,\n attachmentIds: normalizeAttachmentIds(normalizedInput),\n now: input.now,\n },\n {\n tenantId,\n organizationId: ctx.organizationId ?? null,\n userId: ctx.userId,\n },\n )\n\n const uiPart: AiUiPart = {\n componentId: MUTATION_PREVIEW_CARD_COMPONENT_ID,\n props: {\n pendingActionId: pendingAction.id,\n expiresAt: pendingAction.expiresAt.toISOString(),\n ...(records ? { records } : { fieldDiff }),\n ...(sideEffectsSummary ? { sideEffectsSummary } : {}),\n },\n }\n\n return { uiPart, pendingAction }\n}\n\nexport const MUTATION_PREVIEW_CARD_COMPONENT = MUTATION_PREVIEW_CARD_COMPONENT_ID\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,95 @@
1
+ const TASK_PLAN_TOOL_NAME = "meta.update_task_plan";
2
+ const TASK_PLAN_TOOL_NAME_SDK = "meta__update_task_plan";
3
+ const TASK_PLAN_MAX_TASKS = 8;
4
+ const TASK_PLAN_LABEL_MAX_CHARS = 80;
5
+ const TASK_PLAN_DETAIL_MAX_CHARS = 160;
6
+ const TASK_PLAN_ID_MAX_CHARS = 80;
7
+ const TASK_PLAN_TOOL_NAME_MAX_CHARS = 160;
8
+ const TASK_PLAN_RUNTIME_PROMPT_SECTION = [
9
+ "TASK PLAN (RUNTIME)",
10
+ "For every tool-using turn, first call `meta.update_task_plan` with 2-5 concise user-visible steps. Then call the domain/search/attachment/mutation tools.",
11
+ "Task labels are visible progress UI. Never include hidden reasoning, chain-of-thought, scratchpad notes, or XML thinking tags.",
12
+ "When a planned step maps to a known tool, include `toolName` so the chat can advance that row from pending to running to done.",
13
+ "Skip `meta.update_task_plan` for pure capability, example-question, or how-can-you-help prompts where no data tool is needed."
14
+ ].join("\n");
15
+ const HIDDEN_REASONING_PATTERNS = [
16
+ /\bchain[-\s]?of[-\s]?thought\b/i,
17
+ /\binternal\s+(?:reasoning|thoughts?)\b/i,
18
+ /\bprivate\s+(?:reasoning|thoughts?)\b/i,
19
+ /\bhidden\s+(?:reasoning|thoughts?)\b/i,
20
+ /\bscratch\s*pad\b/i,
21
+ /\bscratchpad\b/i,
22
+ /\b(?:my\s+)?reasoning\s*:/i,
23
+ /<\/?\s*(?:thinking|thought|reasoning|scratchpad)\b/i
24
+ ];
25
+ const CONTROL_CHARS = /[\u0000-\u001f\u007f]/g;
26
+ const WHITESPACE = /\s+/g;
27
+ function normalizeTaskPlanToolName(toolName) {
28
+ if (typeof toolName !== "string") return void 0;
29
+ const trimmed = toolName.trim();
30
+ if (!trimmed) return void 0;
31
+ const dotted = trimmed.replace(/__/g, ".");
32
+ const safe = dotted.replace(/[^a-zA-Z0-9._:-]/g, "").slice(0, TASK_PLAN_TOOL_NAME_MAX_CHARS);
33
+ return safe.length > 0 ? safe : void 0;
34
+ }
35
+ function isTaskPlanToolName(toolName) {
36
+ return normalizeTaskPlanToolName(toolName) === TASK_PLAN_TOOL_NAME;
37
+ }
38
+ function looksLikeHiddenReasoning(value) {
39
+ return HIDDEN_REASONING_PATTERNS.some((pattern) => pattern.test(value));
40
+ }
41
+ function sanitizeTaskPlanText(value, maxChars) {
42
+ if (typeof value !== "string") return null;
43
+ const normalized = value.replace(CONTROL_CHARS, " ").replace(WHITESPACE, " ").trim();
44
+ if (!normalized) return null;
45
+ if (looksLikeHiddenReasoning(normalized)) return null;
46
+ return normalized.slice(0, maxChars);
47
+ }
48
+ function sanitizeTaskPlanId(value) {
49
+ if (typeof value !== "string") return void 0;
50
+ const normalized = value.trim().replace(/\s+/g, "-").replace(/[^a-zA-Z0-9._:-]/g, "").slice(0, TASK_PLAN_ID_MAX_CHARS);
51
+ return normalized.length > 0 ? normalized : void 0;
52
+ }
53
+ function sanitizeAgentTaskPlanInput(input) {
54
+ if (!input || typeof input !== "object") {
55
+ return { tasks: [] };
56
+ }
57
+ const rawTasks = input.tasks;
58
+ if (!Array.isArray(rawTasks)) {
59
+ return { tasks: [] };
60
+ }
61
+ const tasks = [];
62
+ for (const rawTask of rawTasks.slice(0, TASK_PLAN_MAX_TASKS)) {
63
+ if (!rawTask || typeof rawTask !== "object") continue;
64
+ const value = rawTask;
65
+ const label = sanitizeTaskPlanText(value.label, TASK_PLAN_LABEL_MAX_CHARS);
66
+ if (!label) continue;
67
+ const detail = sanitizeTaskPlanText(value.detail, TASK_PLAN_DETAIL_MAX_CHARS) ?? void 0;
68
+ const id = sanitizeTaskPlanId(value.id);
69
+ const toolName = normalizeTaskPlanToolName(value.toolName);
70
+ tasks.push({
71
+ ...id ? { id } : {},
72
+ label,
73
+ ...detail ? { detail } : {},
74
+ ...toolName ? { toolName } : {}
75
+ });
76
+ }
77
+ return { tasks };
78
+ }
79
+ export {
80
+ TASK_PLAN_DETAIL_MAX_CHARS,
81
+ TASK_PLAN_ID_MAX_CHARS,
82
+ TASK_PLAN_LABEL_MAX_CHARS,
83
+ TASK_PLAN_MAX_TASKS,
84
+ TASK_PLAN_RUNTIME_PROMPT_SECTION,
85
+ TASK_PLAN_TOOL_NAME,
86
+ TASK_PLAN_TOOL_NAME_MAX_CHARS,
87
+ TASK_PLAN_TOOL_NAME_SDK,
88
+ isTaskPlanToolName,
89
+ looksLikeHiddenReasoning,
90
+ normalizeTaskPlanToolName,
91
+ sanitizeAgentTaskPlanInput,
92
+ sanitizeTaskPlanId,
93
+ sanitizeTaskPlanText
94
+ };
95
+ //# sourceMappingURL=task-plan-labels.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/ai_assistant/lib/task-plan-labels.ts"],
4
+ "sourcesContent": ["/**\n * Safe, user-visible task-plan labels for AI chat.\n *\n * These helpers intentionally reject text that looks like private reasoning.\n * Task plans are UI copy for operators, not a channel for model scratchpads.\n */\n\nexport const TASK_PLAN_TOOL_NAME = 'meta.update_task_plan'\nexport const TASK_PLAN_TOOL_NAME_SDK = 'meta__update_task_plan'\nexport const TASK_PLAN_MAX_TASKS = 8\nexport const TASK_PLAN_LABEL_MAX_CHARS = 80\nexport const TASK_PLAN_DETAIL_MAX_CHARS = 160\nexport const TASK_PLAN_ID_MAX_CHARS = 80\nexport const TASK_PLAN_TOOL_NAME_MAX_CHARS = 160\n\nexport interface SanitizedAgentTaskPlanInputTask {\n id?: string\n label: string\n detail?: string\n toolName?: string\n}\n\nexport interface SanitizedAgentTaskPlanInput {\n tasks: SanitizedAgentTaskPlanInputTask[]\n}\n\nexport const TASK_PLAN_RUNTIME_PROMPT_SECTION = [\n 'TASK PLAN (RUNTIME)',\n 'For every tool-using turn, first call `meta.update_task_plan` with 2-5 concise user-visible steps. Then call the domain/search/attachment/mutation tools.',\n 'Task labels are visible progress UI. Never include hidden reasoning, chain-of-thought, scratchpad notes, or XML thinking tags.',\n 'When a planned step maps to a known tool, include `toolName` so the chat can advance that row from pending to running to done.',\n 'Skip `meta.update_task_plan` for pure capability, example-question, or how-can-you-help prompts where no data tool is needed.',\n].join('\\n')\n\nconst HIDDEN_REASONING_PATTERNS: RegExp[] = [\n /\\bchain[-\\s]?of[-\\s]?thought\\b/i,\n /\\binternal\\s+(?:reasoning|thoughts?)\\b/i,\n /\\bprivate\\s+(?:reasoning|thoughts?)\\b/i,\n /\\bhidden\\s+(?:reasoning|thoughts?)\\b/i,\n /\\bscratch\\s*pad\\b/i,\n /\\bscratchpad\\b/i,\n /\\b(?:my\\s+)?reasoning\\s*:/i,\n /<\\/?\\s*(?:thinking|thought|reasoning|scratchpad)\\b/i,\n]\n\nconst CONTROL_CHARS = /[\\u0000-\\u001f\\u007f]/g\nconst WHITESPACE = /\\s+/g\n\nexport function normalizeTaskPlanToolName(toolName: unknown): string | undefined {\n if (typeof toolName !== 'string') return undefined\n const trimmed = toolName.trim()\n if (!trimmed) return undefined\n const dotted = trimmed.replace(/__/g, '.')\n const safe = dotted.replace(/[^a-zA-Z0-9._:-]/g, '').slice(0, TASK_PLAN_TOOL_NAME_MAX_CHARS)\n return safe.length > 0 ? safe : undefined\n}\n\nexport function isTaskPlanToolName(toolName: unknown): boolean {\n return normalizeTaskPlanToolName(toolName) === TASK_PLAN_TOOL_NAME\n}\n\nexport function looksLikeHiddenReasoning(value: string): boolean {\n return HIDDEN_REASONING_PATTERNS.some((pattern) => pattern.test(value))\n}\n\nexport function sanitizeTaskPlanText(\n value: unknown,\n maxChars: number,\n): string | null {\n if (typeof value !== 'string') return null\n const normalized = value.replace(CONTROL_CHARS, ' ').replace(WHITESPACE, ' ').trim()\n if (!normalized) return null\n if (looksLikeHiddenReasoning(normalized)) return null\n return normalized.slice(0, maxChars)\n}\n\nexport function sanitizeTaskPlanId(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const normalized = value\n .trim()\n .replace(/\\s+/g, '-')\n .replace(/[^a-zA-Z0-9._:-]/g, '')\n .slice(0, TASK_PLAN_ID_MAX_CHARS)\n return normalized.length > 0 ? normalized : undefined\n}\n\nexport function sanitizeAgentTaskPlanInput(input: unknown): SanitizedAgentTaskPlanInput {\n if (!input || typeof input !== 'object') {\n return { tasks: [] }\n }\n const rawTasks = (input as { tasks?: unknown }).tasks\n if (!Array.isArray(rawTasks)) {\n return { tasks: [] }\n }\n const tasks: SanitizedAgentTaskPlanInputTask[] = []\n for (const rawTask of rawTasks.slice(0, TASK_PLAN_MAX_TASKS)) {\n if (!rawTask || typeof rawTask !== 'object') continue\n const value = rawTask as Record<string, unknown>\n const label = sanitizeTaskPlanText(value.label, TASK_PLAN_LABEL_MAX_CHARS)\n if (!label) continue\n const detail = sanitizeTaskPlanText(value.detail, TASK_PLAN_DETAIL_MAX_CHARS) ?? undefined\n const id = sanitizeTaskPlanId(value.id)\n const toolName = normalizeTaskPlanToolName(value.toolName)\n tasks.push({\n ...(id ? { id } : {}),\n label,\n ...(detail ? { detail } : {}),\n ...(toolName ? { toolName } : {}),\n })\n }\n return { tasks }\n}\n"],
5
+ "mappings": "AAOO,MAAM,sBAAsB;AAC5B,MAAM,0BAA0B;AAChC,MAAM,sBAAsB;AAC5B,MAAM,4BAA4B;AAClC,MAAM,6BAA6B;AACnC,MAAM,yBAAyB;AAC/B,MAAM,gCAAgC;AAatC,MAAM,mCAAmC;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,IAAI;AAEX,MAAM,4BAAsC;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,gBAAgB;AACtB,MAAM,aAAa;AAEZ,SAAS,0BAA0B,UAAuC;AAC/E,MAAI,OAAO,aAAa,SAAU,QAAO;AACzC,QAAM,UAAU,SAAS,KAAK;AAC9B,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,SAAS,QAAQ,QAAQ,OAAO,GAAG;AACzC,QAAM,OAAO,OAAO,QAAQ,qBAAqB,EAAE,EAAE,MAAM,GAAG,6BAA6B;AAC3F,SAAO,KAAK,SAAS,IAAI,OAAO;AAClC;AAEO,SAAS,mBAAmB,UAA4B;AAC7D,SAAO,0BAA0B,QAAQ,MAAM;AACjD;AAEO,SAAS,yBAAyB,OAAwB;AAC/D,SAAO,0BAA0B,KAAK,CAAC,YAAY,QAAQ,KAAK,KAAK,CAAC;AACxE;AAEO,SAAS,qBACd,OACA,UACe;AACf,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,aAAa,MAAM,QAAQ,eAAe,GAAG,EAAE,QAAQ,YAAY,GAAG,EAAE,KAAK;AACnF,MAAI,CAAC,WAAY,QAAO;AACxB,MAAI,yBAAyB,UAAU,EAAG,QAAO;AACjD,SAAO,WAAW,MAAM,GAAG,QAAQ;AACrC;AAEO,SAAS,mBAAmB,OAAoC;AACrE,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,aAAa,MAChB,KAAK,EACL,QAAQ,QAAQ,GAAG,EACnB,QAAQ,qBAAqB,EAAE,EAC/B,MAAM,GAAG,sBAAsB;AAClC,SAAO,WAAW,SAAS,IAAI,aAAa;AAC9C;AAEO,SAAS,2BAA2B,OAA6C;AACtF,MAAI,CAAC,SAAS,OAAO,UAAU,UAAU;AACvC,WAAO,EAAE,OAAO,CAAC,EAAE;AAAA,EACrB;AACA,QAAM,WAAY,MAA8B;AAChD,MAAI,CAAC,MAAM,QAAQ,QAAQ,GAAG;AAC5B,WAAO,EAAE,OAAO,CAAC,EAAE;AAAA,EACrB;AACA,QAAM,QAA2C,CAAC;AAClD,aAAW,WAAW,SAAS,MAAM,GAAG,mBAAmB,GAAG;AAC5D,QAAI,CAAC,WAAW,OAAO,YAAY,SAAU;AAC7C,UAAM,QAAQ;AACd,UAAM,QAAQ,qBAAqB,MAAM,OAAO,yBAAyB;AACzE,QAAI,CAAC,MAAO;AACZ,UAAM,SAAS,qBAAqB,MAAM,QAAQ,0BAA0B,KAAK;AACjF,UAAM,KAAK,mBAAmB,MAAM,EAAE;AACtC,UAAM,WAAW,0BAA0B,MAAM,QAAQ;AACzD,UAAM,KAAK;AAAA,MACT,GAAI,KAAK,EAAE,GAAG,IAAI,CAAC;AAAA,MACnB;AAAA,MACA,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,MAC3B,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,IACjC,CAAC;AAAA,EACH;AACA,SAAO,EAAE,MAAM;AACjB;",
6
+ "names": []
7
+ }