@open-mercato/ai-assistant 0.9.1-develop.7532.1.8eac7e198f → 0.9.1-develop.7536.1.f9a5776a07
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.
- package/dist/modules/ai_assistant/i18n/de.json +2 -0
- package/dist/modules/ai_assistant/i18n/en.json +2 -0
- package/dist/modules/ai_assistant/i18n/es.json +2 -0
- package/dist/modules/ai_assistant/i18n/ko.json +2 -0
- package/dist/modules/ai_assistant/i18n/pl.json +2 -0
- package/dist/modules/ai_assistant/lib/pending-action-cancel.js +5 -1
- package/dist/modules/ai_assistant/lib/pending-action-cancel.js.map +2 -2
- package/package.json +6 -6
- package/src/modules/ai_assistant/api/ai/actions/[id]/cancel/__tests__/route.test.ts +3 -3
- package/src/modules/ai_assistant/i18n/de.json +2 -0
- package/src/modules/ai_assistant/i18n/en.json +2 -0
- package/src/modules/ai_assistant/i18n/es.json +2 -0
- package/src/modules/ai_assistant/i18n/ko.json +2 -0
- package/src/modules/ai_assistant/i18n/pl.json +2 -0
- package/src/modules/ai_assistant/lib/__tests__/pending-action-cancel.test.ts +2 -2
- package/src/modules/ai_assistant/lib/pending-action-cancel.ts +5 -1
|
@@ -229,6 +229,8 @@
|
|
|
229
229
|
"ai_assistant.chat.mutation_cards.preview.handlerError": "Der Mutations-Handler meldete einen Fehler. Prüfen Sie die Details und schlagen Sie die Aktion ggf. erneut vor.",
|
|
230
230
|
"ai_assistant.chat.mutation_cards.preview.reviewDetails": "Details ansehen",
|
|
231
231
|
"ai_assistant.chat.mutation_cards.preview.title": "Vorgeschlagene Änderungen prüfen",
|
|
232
|
+
"ai_assistant.chat.mutation_cards.result.cancelledBody": "Sie haben diese vorgeschlagene Änderung abgebrochen. Es wurden keine Änderungen vorgenommen.",
|
|
233
|
+
"ai_assistant.chat.mutation_cards.result.cancelledTitle": "Vorschlag abgebrochen",
|
|
232
234
|
"ai_assistant.chat.mutation_cards.result.failureBody": "Die Änderung konnte nicht angewendet werden.",
|
|
233
235
|
"ai_assistant.chat.mutation_cards.result.failureTitle": "Aktion fehlgeschlagen",
|
|
234
236
|
"ai_assistant.chat.mutation_cards.result.fixWithAi": "Mit KI korrigieren",
|
|
@@ -229,6 +229,8 @@
|
|
|
229
229
|
"ai_assistant.chat.mutation_cards.preview.handlerError": "The mutation handler reported an error. Review the details and re-propose if needed.",
|
|
230
230
|
"ai_assistant.chat.mutation_cards.preview.reviewDetails": "Review details",
|
|
231
231
|
"ai_assistant.chat.mutation_cards.preview.title": "Review proposed changes",
|
|
232
|
+
"ai_assistant.chat.mutation_cards.result.cancelledBody": "You cancelled this proposed change. No changes were made.",
|
|
233
|
+
"ai_assistant.chat.mutation_cards.result.cancelledTitle": "Proposal cancelled",
|
|
232
234
|
"ai_assistant.chat.mutation_cards.result.failureBody": "The mutation could not be applied.",
|
|
233
235
|
"ai_assistant.chat.mutation_cards.result.failureTitle": "Action failed",
|
|
234
236
|
"ai_assistant.chat.mutation_cards.result.fixWithAi": "Fix with AI",
|
|
@@ -229,6 +229,8 @@
|
|
|
229
229
|
"ai_assistant.chat.mutation_cards.preview.handlerError": "El gestor de mutaciones informó un error. Revisa los detalles y vuelve a proponer la acción si es necesario.",
|
|
230
230
|
"ai_assistant.chat.mutation_cards.preview.reviewDetails": "Ver detalles",
|
|
231
231
|
"ai_assistant.chat.mutation_cards.preview.title": "Revisar los cambios propuestos",
|
|
232
|
+
"ai_assistant.chat.mutation_cards.result.cancelledBody": "Cancelaste este cambio propuesto. No se realizó ningún cambio.",
|
|
233
|
+
"ai_assistant.chat.mutation_cards.result.cancelledTitle": "Propuesta cancelada",
|
|
232
234
|
"ai_assistant.chat.mutation_cards.result.failureBody": "No se pudo aplicar la mutación.",
|
|
233
235
|
"ai_assistant.chat.mutation_cards.result.failureTitle": "La acción falló",
|
|
234
236
|
"ai_assistant.chat.mutation_cards.result.fixWithAi": "Corregir con IA",
|
|
@@ -229,6 +229,8 @@
|
|
|
229
229
|
"ai_assistant.chat.mutation_cards.preview.handlerError": "뮤테이션 핸들러가 오류를 보고했습니다. 세부 정보를 검토하고 필요하면 다시 제안하세요.",
|
|
230
230
|
"ai_assistant.chat.mutation_cards.preview.reviewDetails": "세부 정보 검토",
|
|
231
231
|
"ai_assistant.chat.mutation_cards.preview.title": "제안된 변경 사항 검토",
|
|
232
|
+
"ai_assistant.chat.mutation_cards.result.cancelledBody": "이 제안된 변경을 취소했습니다. 변경 사항이 적용되지 않았습니다.",
|
|
233
|
+
"ai_assistant.chat.mutation_cards.result.cancelledTitle": "제안이 취소됨",
|
|
232
234
|
"ai_assistant.chat.mutation_cards.result.failureBody": "뮤테이션을 적용할 수 없습니다.",
|
|
233
235
|
"ai_assistant.chat.mutation_cards.result.failureTitle": "작업 실패",
|
|
234
236
|
"ai_assistant.chat.mutation_cards.result.fixWithAi": "AI로 수정",
|
|
@@ -229,6 +229,8 @@
|
|
|
229
229
|
"ai_assistant.chat.mutation_cards.preview.handlerError": "Handler mutacji zgłosił błąd. Sprawdź szczegóły i w razie potrzeby zaproponuj akcję ponownie.",
|
|
230
230
|
"ai_assistant.chat.mutation_cards.preview.reviewDetails": "Pokaż szczegóły",
|
|
231
231
|
"ai_assistant.chat.mutation_cards.preview.title": "Sprawdź proponowane zmiany",
|
|
232
|
+
"ai_assistant.chat.mutation_cards.result.cancelledBody": "Anulowano tę proponowaną zmianę. Nie wprowadzono żadnych zmian.",
|
|
233
|
+
"ai_assistant.chat.mutation_cards.result.cancelledTitle": "Propozycja anulowana",
|
|
232
234
|
"ai_assistant.chat.mutation_cards.result.failureBody": "Nie udało się zastosować zmiany.",
|
|
233
235
|
"ai_assistant.chat.mutation_cards.result.failureTitle": "Akcja nie powiodła się",
|
|
234
236
|
"ai_assistant.chat.mutation_cards.result.fixWithAi": "Popraw z AI",
|
|
@@ -54,7 +54,11 @@ async function executePendingActionCancel(input) {
|
|
|
54
54
|
const executionResult = {
|
|
55
55
|
error: {
|
|
56
56
|
code: "cancelled_by_user",
|
|
57
|
-
|
|
57
|
+
// Left empty when no reason is supplied: this is surfaced as a
|
|
58
|
+
// neutral "proposal cancelled" message in the client, which
|
|
59
|
+
// supplies its own localized default copy (see MutationResultCard)
|
|
60
|
+
// instead of a hardcoded English string.
|
|
61
|
+
message: trimmedReason
|
|
58
62
|
}
|
|
59
63
|
};
|
|
60
64
|
const cancelledRow = await repo.setStatus(action.id, "cancelled", scope, {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/lib/pending-action-cancel.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Pending-action cancel executor (spec \u00A79.4, Step 5.9).\n *\n * Flips an `AiPendingAction` from `pending \u2192 cancelled` and emits the\n * typed `ai.action.cancelled` event via `emitAiAssistantEvent`. Unlike\n * {@link executePendingActionConfirm} the tool handler is NEVER invoked \u2014\n * cancellation is a pure state-machine transition plus an event emission.\n * Any other status short-circuits: already-`cancelled` is idempotent (no\n * second emit, same row returned); `confirmed` / `executing` / `failed`\n * are treated as invariant violations and bubble up as 409 via the route.\n *\n * If the row's `expiresAt` is in the past at the time of this call we\n * flip it to `expired` (not `cancelled`) \u2014 the Step 5.12 cleanup worker\n * races with this code path and we want a single canonical terminal\n * status for a row that reached its TTL.\n *\n * Idempotency: the caller MUST handle the already-`cancelled` branch\n * BEFORE invoking this helper so the event is emitted exactly once per\n * cancellation.\n */\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { AiPendingActionRepository } from '../data/repositories/AiPendingActionRepository'\nimport type { AiPendingAction } from '../data/entities'\nimport { emitAiAssistantEvent } from '../events'\nimport type {\n AiActionCancelledPayload,\n AiActionExpiredPayload,\n AiAssistantEventId,\n} from '../events'\nimport type { AiPendingActionExecutionResult } from './pending-action-types'\n\nconst logger = createLogger('ai_assistant')\n\nexport interface PendingActionCancelContext {\n tenantId: string\n organizationId: string | null\n userId: string\n container: import('awilix').AwilixContainer\n}\n\nexport type CancelEmitter = (\n eventId: Extract<AiAssistantEventId, 'ai.action.cancelled' | 'ai.action.expired'>,\n payload: AiActionCancelledPayload | AiActionExpiredPayload,\n) => Promise<void>\n\nexport interface PendingActionCancelInput {\n action: AiPendingAction\n ctx: PendingActionCancelContext\n /** Optional, caller-supplied cancellation reason (already trimmed by the route). */\n reason?: string | null\n repo?: AiPendingActionRepository\n /**\n * Injection seam for unit tests. When omitted, emission is routed via\n * the typed `emitAiAssistantEvent` helper (the normal production path).\n */\n emitEvent?: CancelEmitter\n now?: Date\n}\n\nexport type PendingActionCancelStatus = 'cancelled' | 'expired'\n\nexport interface PendingActionCancelResult {\n row: AiPendingAction\n status: PendingActionCancelStatus\n}\n\nconst CANCELLED_EVENT_ID = 'ai.action.cancelled' as const\nconst EXPIRED_EVENT_ID = 'ai.action.expired' as const\n\nconst defaultCancelEmitter: CancelEmitter = async (eventId, payload) => {\n await emitAiAssistantEvent(eventId, payload as unknown as Record<string, unknown>, {\n persistent: true,\n })\n}\n\nasync function emitEventSafe(\n emitter: CancelEmitter,\n eventId: Parameters<CancelEmitter>[0],\n payload: Parameters<CancelEmitter>[1],\n): Promise<void> {\n try {\n await emitter(eventId, payload)\n } catch (error) {\n logger.warn('Failed to emit pending-action event', { eventId, err: error })\n }\n}\n\n/**\n * Atomic `pending \u2192 cancelled` transition with TTL-race safety.\n *\n * - If `action.status === 'cancelled'`, returns the current row WITHOUT\n * emitting a second event. Callers should typically short-circuit on\n * this branch BEFORE invoking the helper (see the Step 5.9 route).\n * - If `action.expiresAt <= now`, the row is flipped to `expired` and the\n * `ai.action.expired` event is emitted via the typed\n * `emitAiAssistantEvent` helper (see `../events`). Returns\n * `{ status: 'expired' }` so the route can translate to a 409\n * `expired` envelope.\n * - Otherwise flips `pending \u2192 cancelled`, writes `resolvedAt` + the\n * optional cancellation reason onto `executionResult.error`, and emits\n * `ai.action.cancelled`.\n *\n * Any status other than `pending` / `cancelled` is treated as an\n * invariant violation \u2014 the route returns 409 `invalid_status` before\n * reaching this helper. If the caller invokes the helper on such a row\n * it will throw via the repo's state-machine guard.\n */\nexport async function executePendingActionCancel(\n input: PendingActionCancelInput,\n): Promise<PendingActionCancelResult> {\n const { action, ctx, now } = input\n const repo = input.repo ?? new AiPendingActionRepository(ctx.container.resolve('em'))\n const scope = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n }\n const clock = now ?? new Date()\n const emitter: CancelEmitter = input.emitEvent ?? defaultCancelEmitter\n\n if (action.status === 'cancelled') {\n return { row: action, status: 'cancelled' }\n }\n\n const expiresAt =\n action.expiresAt instanceof Date ? action.expiresAt : new Date(action.expiresAt)\n if (expiresAt.getTime() <= clock.getTime()) {\n const expiredRow = await repo.setStatus(action.id, 'expired', scope, { now: clock })\n const resolvedAtIso =\n (expiredRow.resolvedAt ?? clock).toISOString?.() ?? new Date(clock).toISOString()\n const expiresAtIso =\n (expiresAt ?? clock).toISOString?.() ?? new Date(clock).toISOString()\n const expiredPayload: AiActionExpiredPayload = {\n pendingActionId: expiredRow.id,\n agentId: expiredRow.agentId,\n toolName: expiredRow.toolName,\n status: expiredRow.status,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? null,\n userId: ctx.userId,\n resolvedByUserId: null,\n resolvedAt: resolvedAtIso,\n expiresAt: expiresAtIso,\n expiredAt: resolvedAtIso,\n }\n await emitEventSafe(emitter, EXPIRED_EVENT_ID, expiredPayload)\n return { row: expiredRow, status: 'expired' }\n }\n\n const trimmedReason = typeof input.reason === 'string' ? input.reason.trim() : ''\n const executionResult: AiPendingActionExecutionResult = {\n error: {\n code: 'cancelled_by_user',\n
|
|
5
|
-
"mappings": "AAoBA,SAAS,oBAAoB;AAC7B,SAAS,iCAAiC;AAE1C,SAAS,4BAA4B;AAQrC,MAAM,SAAS,aAAa,cAAc;AAmC1C,MAAM,qBAAqB;AAC3B,MAAM,mBAAmB;AAEzB,MAAM,uBAAsC,OAAO,SAAS,YAAY;AACtE,QAAM,qBAAqB,SAAS,SAA+C;AAAA,IACjF,YAAY;AAAA,EACd,CAAC;AACH;AAEA,eAAe,cACb,SACA,SACA,SACe;AACf,MAAI;AACF,UAAM,QAAQ,SAAS,OAAO;AAAA,EAChC,SAAS,OAAO;AACd,WAAO,KAAK,uCAAuC,EAAE,SAAS,KAAK,MAAM,CAAC;AAAA,EAC5E;AACF;AAsBA,eAAsB,2BACpB,OACoC;AACpC,QAAM,EAAE,QAAQ,KAAK,IAAI,IAAI;AAC7B,QAAM,OAAO,MAAM,QAAQ,IAAI,0BAA0B,IAAI,UAAU,QAAQ,IAAI,CAAC;AACpF,QAAM,QAAQ;AAAA,IACZ,UAAU,IAAI;AAAA,IACd,gBAAgB,IAAI;AAAA,IACpB,QAAQ,IAAI;AAAA,EACd;AACA,QAAM,QAAQ,OAAO,oBAAI,KAAK;AAC9B,QAAM,UAAyB,MAAM,aAAa;AAElD,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,EAAE,KAAK,QAAQ,QAAQ,YAAY;AAAA,EAC5C;AAEA,QAAM,YACJ,OAAO,qBAAqB,OAAO,OAAO,YAAY,IAAI,KAAK,OAAO,SAAS;AACjF,MAAI,UAAU,QAAQ,KAAK,MAAM,QAAQ,GAAG;AAC1C,UAAM,aAAa,MAAM,KAAK,UAAU,OAAO,IAAI,WAAW,OAAO,EAAE,KAAK,MAAM,CAAC;AACnF,UAAM,iBACH,WAAW,cAAc,OAAO,cAAc,KAAK,IAAI,KAAK,KAAK,EAAE,YAAY;AAClF,UAAM,gBACH,aAAa,OAAO,cAAc,KAAK,IAAI,KAAK,KAAK,EAAE,YAAY;AACtE,UAAM,iBAAyC;AAAA,MAC7C,iBAAiB,WAAW;AAAA,MAC5B,SAAS,WAAW;AAAA,MACpB,UAAU,WAAW;AAAA,MACrB,QAAQ,WAAW;AAAA,MACnB,UAAU,IAAI;AAAA,MACd,gBAAgB,IAAI,kBAAkB;AAAA,MACtC,QAAQ,IAAI;AAAA,MACZ,kBAAkB;AAAA,MAClB,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,WAAW;AAAA,IACb;AACA,UAAM,cAAc,SAAS,kBAAkB,cAAc;AAC7D,WAAO,EAAE,KAAK,YAAY,QAAQ,UAAU;AAAA,EAC9C;AAEA,QAAM,gBAAgB,OAAO,MAAM,WAAW,WAAW,MAAM,OAAO,KAAK,IAAI;AAC/E,QAAM,kBAAkD;AAAA,IACtD,OAAO;AAAA,MACL,MAAM;AAAA,
|
|
4
|
+
"sourcesContent": ["/**\n * Pending-action cancel executor (spec \u00A79.4, Step 5.9).\n *\n * Flips an `AiPendingAction` from `pending \u2192 cancelled` and emits the\n * typed `ai.action.cancelled` event via `emitAiAssistantEvent`. Unlike\n * {@link executePendingActionConfirm} the tool handler is NEVER invoked \u2014\n * cancellation is a pure state-machine transition plus an event emission.\n * Any other status short-circuits: already-`cancelled` is idempotent (no\n * second emit, same row returned); `confirmed` / `executing` / `failed`\n * are treated as invariant violations and bubble up as 409 via the route.\n *\n * If the row's `expiresAt` is in the past at the time of this call we\n * flip it to `expired` (not `cancelled`) \u2014 the Step 5.12 cleanup worker\n * races with this code path and we want a single canonical terminal\n * status for a row that reached its TTL.\n *\n * Idempotency: the caller MUST handle the already-`cancelled` branch\n * BEFORE invoking this helper so the event is emitted exactly once per\n * cancellation.\n */\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { AiPendingActionRepository } from '../data/repositories/AiPendingActionRepository'\nimport type { AiPendingAction } from '../data/entities'\nimport { emitAiAssistantEvent } from '../events'\nimport type {\n AiActionCancelledPayload,\n AiActionExpiredPayload,\n AiAssistantEventId,\n} from '../events'\nimport type { AiPendingActionExecutionResult } from './pending-action-types'\n\nconst logger = createLogger('ai_assistant')\n\nexport interface PendingActionCancelContext {\n tenantId: string\n organizationId: string | null\n userId: string\n container: import('awilix').AwilixContainer\n}\n\nexport type CancelEmitter = (\n eventId: Extract<AiAssistantEventId, 'ai.action.cancelled' | 'ai.action.expired'>,\n payload: AiActionCancelledPayload | AiActionExpiredPayload,\n) => Promise<void>\n\nexport interface PendingActionCancelInput {\n action: AiPendingAction\n ctx: PendingActionCancelContext\n /** Optional, caller-supplied cancellation reason (already trimmed by the route). */\n reason?: string | null\n repo?: AiPendingActionRepository\n /**\n * Injection seam for unit tests. When omitted, emission is routed via\n * the typed `emitAiAssistantEvent` helper (the normal production path).\n */\n emitEvent?: CancelEmitter\n now?: Date\n}\n\nexport type PendingActionCancelStatus = 'cancelled' | 'expired'\n\nexport interface PendingActionCancelResult {\n row: AiPendingAction\n status: PendingActionCancelStatus\n}\n\nconst CANCELLED_EVENT_ID = 'ai.action.cancelled' as const\nconst EXPIRED_EVENT_ID = 'ai.action.expired' as const\n\nconst defaultCancelEmitter: CancelEmitter = async (eventId, payload) => {\n await emitAiAssistantEvent(eventId, payload as unknown as Record<string, unknown>, {\n persistent: true,\n })\n}\n\nasync function emitEventSafe(\n emitter: CancelEmitter,\n eventId: Parameters<CancelEmitter>[0],\n payload: Parameters<CancelEmitter>[1],\n): Promise<void> {\n try {\n await emitter(eventId, payload)\n } catch (error) {\n logger.warn('Failed to emit pending-action event', { eventId, err: error })\n }\n}\n\n/**\n * Atomic `pending \u2192 cancelled` transition with TTL-race safety.\n *\n * - If `action.status === 'cancelled'`, returns the current row WITHOUT\n * emitting a second event. Callers should typically short-circuit on\n * this branch BEFORE invoking the helper (see the Step 5.9 route).\n * - If `action.expiresAt <= now`, the row is flipped to `expired` and the\n * `ai.action.expired` event is emitted via the typed\n * `emitAiAssistantEvent` helper (see `../events`). Returns\n * `{ status: 'expired' }` so the route can translate to a 409\n * `expired` envelope.\n * - Otherwise flips `pending \u2192 cancelled`, writes `resolvedAt` + the\n * optional cancellation reason onto `executionResult.error`, and emits\n * `ai.action.cancelled`.\n *\n * Any status other than `pending` / `cancelled` is treated as an\n * invariant violation \u2014 the route returns 409 `invalid_status` before\n * reaching this helper. If the caller invokes the helper on such a row\n * it will throw via the repo's state-machine guard.\n */\nexport async function executePendingActionCancel(\n input: PendingActionCancelInput,\n): Promise<PendingActionCancelResult> {\n const { action, ctx, now } = input\n const repo = input.repo ?? new AiPendingActionRepository(ctx.container.resolve('em'))\n const scope = {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n userId: ctx.userId,\n }\n const clock = now ?? new Date()\n const emitter: CancelEmitter = input.emitEvent ?? defaultCancelEmitter\n\n if (action.status === 'cancelled') {\n return { row: action, status: 'cancelled' }\n }\n\n const expiresAt =\n action.expiresAt instanceof Date ? action.expiresAt : new Date(action.expiresAt)\n if (expiresAt.getTime() <= clock.getTime()) {\n const expiredRow = await repo.setStatus(action.id, 'expired', scope, { now: clock })\n const resolvedAtIso =\n (expiredRow.resolvedAt ?? clock).toISOString?.() ?? new Date(clock).toISOString()\n const expiresAtIso =\n (expiresAt ?? clock).toISOString?.() ?? new Date(clock).toISOString()\n const expiredPayload: AiActionExpiredPayload = {\n pendingActionId: expiredRow.id,\n agentId: expiredRow.agentId,\n toolName: expiredRow.toolName,\n status: expiredRow.status,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? null,\n userId: ctx.userId,\n resolvedByUserId: null,\n resolvedAt: resolvedAtIso,\n expiresAt: expiresAtIso,\n expiredAt: resolvedAtIso,\n }\n await emitEventSafe(emitter, EXPIRED_EVENT_ID, expiredPayload)\n return { row: expiredRow, status: 'expired' }\n }\n\n const trimmedReason = typeof input.reason === 'string' ? input.reason.trim() : ''\n const executionResult: AiPendingActionExecutionResult = {\n error: {\n code: 'cancelled_by_user',\n // Left empty when no reason is supplied: this is surfaced as a\n // neutral \"proposal cancelled\" message in the client, which\n // supplies its own localized default copy (see MutationResultCard)\n // instead of a hardcoded English string.\n message: trimmedReason,\n },\n }\n\n const cancelledRow = await repo.setStatus(action.id, 'cancelled', scope, {\n resolvedByUserId: ctx.userId,\n executionResult,\n now: clock,\n })\n const cancelledPayload: AiActionCancelledPayload = {\n pendingActionId: cancelledRow.id,\n agentId: cancelledRow.agentId,\n toolName: cancelledRow.toolName,\n status: cancelledRow.status,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? null,\n userId: ctx.userId,\n resolvedByUserId: ctx.userId,\n resolvedAt: (cancelledRow.resolvedAt ?? clock).toISOString?.() ?? new Date(clock).toISOString(),\n executionResult,\n ...(trimmedReason.length > 0 ? { reason: trimmedReason } : {}),\n }\n await emitEventSafe(emitter, CANCELLED_EVENT_ID, cancelledPayload)\n\n return { row: cancelledRow, status: 'cancelled' }\n}\n\nexport const PENDING_ACTION_CANCELLED_EVENT_ID = CANCELLED_EVENT_ID\nexport const PENDING_ACTION_EXPIRED_EVENT_ID = EXPIRED_EVENT_ID\n"],
|
|
5
|
+
"mappings": "AAoBA,SAAS,oBAAoB;AAC7B,SAAS,iCAAiC;AAE1C,SAAS,4BAA4B;AAQrC,MAAM,SAAS,aAAa,cAAc;AAmC1C,MAAM,qBAAqB;AAC3B,MAAM,mBAAmB;AAEzB,MAAM,uBAAsC,OAAO,SAAS,YAAY;AACtE,QAAM,qBAAqB,SAAS,SAA+C;AAAA,IACjF,YAAY;AAAA,EACd,CAAC;AACH;AAEA,eAAe,cACb,SACA,SACA,SACe;AACf,MAAI;AACF,UAAM,QAAQ,SAAS,OAAO;AAAA,EAChC,SAAS,OAAO;AACd,WAAO,KAAK,uCAAuC,EAAE,SAAS,KAAK,MAAM,CAAC;AAAA,EAC5E;AACF;AAsBA,eAAsB,2BACpB,OACoC;AACpC,QAAM,EAAE,QAAQ,KAAK,IAAI,IAAI;AAC7B,QAAM,OAAO,MAAM,QAAQ,IAAI,0BAA0B,IAAI,UAAU,QAAQ,IAAI,CAAC;AACpF,QAAM,QAAQ;AAAA,IACZ,UAAU,IAAI;AAAA,IACd,gBAAgB,IAAI;AAAA,IACpB,QAAQ,IAAI;AAAA,EACd;AACA,QAAM,QAAQ,OAAO,oBAAI,KAAK;AAC9B,QAAM,UAAyB,MAAM,aAAa;AAElD,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,EAAE,KAAK,QAAQ,QAAQ,YAAY;AAAA,EAC5C;AAEA,QAAM,YACJ,OAAO,qBAAqB,OAAO,OAAO,YAAY,IAAI,KAAK,OAAO,SAAS;AACjF,MAAI,UAAU,QAAQ,KAAK,MAAM,QAAQ,GAAG;AAC1C,UAAM,aAAa,MAAM,KAAK,UAAU,OAAO,IAAI,WAAW,OAAO,EAAE,KAAK,MAAM,CAAC;AACnF,UAAM,iBACH,WAAW,cAAc,OAAO,cAAc,KAAK,IAAI,KAAK,KAAK,EAAE,YAAY;AAClF,UAAM,gBACH,aAAa,OAAO,cAAc,KAAK,IAAI,KAAK,KAAK,EAAE,YAAY;AACtE,UAAM,iBAAyC;AAAA,MAC7C,iBAAiB,WAAW;AAAA,MAC5B,SAAS,WAAW;AAAA,MACpB,UAAU,WAAW;AAAA,MACrB,QAAQ,WAAW;AAAA,MACnB,UAAU,IAAI;AAAA,MACd,gBAAgB,IAAI,kBAAkB;AAAA,MACtC,QAAQ,IAAI;AAAA,MACZ,kBAAkB;AAAA,MAClB,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,WAAW;AAAA,IACb;AACA,UAAM,cAAc,SAAS,kBAAkB,cAAc;AAC7D,WAAO,EAAE,KAAK,YAAY,QAAQ,UAAU;AAAA,EAC9C;AAEA,QAAM,gBAAgB,OAAO,MAAM,WAAW,WAAW,MAAM,OAAO,KAAK,IAAI;AAC/E,QAAM,kBAAkD;AAAA,IACtD,OAAO;AAAA,MACL,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,MAKN,SAAS;AAAA,IACX;AAAA,EACF;AAEA,QAAM,eAAe,MAAM,KAAK,UAAU,OAAO,IAAI,aAAa,OAAO;AAAA,IACvE,kBAAkB,IAAI;AAAA,IACtB;AAAA,IACA,KAAK;AAAA,EACP,CAAC;AACD,QAAM,mBAA6C;AAAA,IACjD,iBAAiB,aAAa;AAAA,IAC9B,SAAS,aAAa;AAAA,IACtB,UAAU,aAAa;AAAA,IACvB,QAAQ,aAAa;AAAA,IACrB,UAAU,IAAI;AAAA,IACd,gBAAgB,IAAI,kBAAkB;AAAA,IACtC,QAAQ,IAAI;AAAA,IACZ,kBAAkB,IAAI;AAAA,IACtB,aAAa,aAAa,cAAc,OAAO,cAAc,KAAK,IAAI,KAAK,KAAK,EAAE,YAAY;AAAA,IAC9F;AAAA,IACA,GAAI,cAAc,SAAS,IAAI,EAAE,QAAQ,cAAc,IAAI,CAAC;AAAA,EAC9D;AACA,QAAM,cAAc,SAAS,oBAAoB,gBAAgB;AAEjE,SAAO,EAAE,KAAK,cAAc,QAAQ,YAAY;AAClD;AAEO,MAAM,oCAAoC;AAC1C,MAAM,kCAAkC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/ai-assistant",
|
|
3
|
-
"version": "0.9.1-develop.
|
|
3
|
+
"version": "0.9.1-develop.7536.1.f9a5776a07",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -100,16 +100,16 @@
|
|
|
100
100
|
"zod-to-json-schema": "^3.25.2"
|
|
101
101
|
},
|
|
102
102
|
"peerDependencies": {
|
|
103
|
-
"@open-mercato/shared": "0.9.1-develop.
|
|
104
|
-
"@open-mercato/ui": "0.9.1-develop.
|
|
103
|
+
"@open-mercato/shared": "0.9.1-develop.7536.1.f9a5776a07",
|
|
104
|
+
"@open-mercato/ui": "0.9.1-develop.7536.1.f9a5776a07",
|
|
105
105
|
"react": "^19.0.0",
|
|
106
106
|
"react-dom": "^19.0.0",
|
|
107
107
|
"zod": ">=3.23.0"
|
|
108
108
|
},
|
|
109
109
|
"devDependencies": {
|
|
110
|
-
"@open-mercato/cli": "0.9.1-develop.
|
|
111
|
-
"@open-mercato/shared": "0.9.1-develop.
|
|
112
|
-
"@open-mercato/ui": "0.9.1-develop.
|
|
110
|
+
"@open-mercato/cli": "0.9.1-develop.7536.1.f9a5776a07",
|
|
111
|
+
"@open-mercato/shared": "0.9.1-develop.7536.1.f9a5776a07",
|
|
112
|
+
"@open-mercato/ui": "0.9.1-develop.7536.1.f9a5776a07",
|
|
113
113
|
"@types/react": "^19.2.18",
|
|
114
114
|
"@types/react-dom": "^19.2.5",
|
|
115
115
|
"react": "19.2.8",
|
|
@@ -250,14 +250,14 @@ describe('POST /api/ai/actions/:id/cancel route (Step 5.9)', () => {
|
|
|
250
250
|
expect(repoGetByIdMock).not.toHaveBeenCalled()
|
|
251
251
|
})
|
|
252
252
|
|
|
253
|
-
it('whitespace-only reason becomes empty
|
|
253
|
+
it('whitespace-only reason becomes empty (no hardcoded English copy)', async () => {
|
|
254
254
|
repoGetByIdMock.mockResolvedValueOnce(makeRow())
|
|
255
255
|
|
|
256
256
|
const response = await POST(buildRequest({ reason: ' \t\n ' }) as any, buildContext(PENDING_ACTION_ID))
|
|
257
257
|
expect(response.status).toBe(200)
|
|
258
258
|
const body = await response.json()
|
|
259
259
|
expect(body.pendingAction.executionResult).toEqual({
|
|
260
|
-
error: { code: 'cancelled_by_user', message: '
|
|
260
|
+
error: { code: 'cancelled_by_user', message: '' },
|
|
261
261
|
})
|
|
262
262
|
})
|
|
263
263
|
|
|
@@ -288,7 +288,7 @@ describe('POST /api/ai/actions/:id/cancel route (Step 5.9)', () => {
|
|
|
288
288
|
const body = await response.json()
|
|
289
289
|
expect(body.ok).toBe(true)
|
|
290
290
|
expect(body.pendingAction.executionResult).toEqual({
|
|
291
|
-
error: { code: 'cancelled_by_user', message: '
|
|
291
|
+
error: { code: 'cancelled_by_user', message: '' },
|
|
292
292
|
})
|
|
293
293
|
})
|
|
294
294
|
|
|
@@ -229,6 +229,8 @@
|
|
|
229
229
|
"ai_assistant.chat.mutation_cards.preview.handlerError": "Der Mutations-Handler meldete einen Fehler. Prüfen Sie die Details und schlagen Sie die Aktion ggf. erneut vor.",
|
|
230
230
|
"ai_assistant.chat.mutation_cards.preview.reviewDetails": "Details ansehen",
|
|
231
231
|
"ai_assistant.chat.mutation_cards.preview.title": "Vorgeschlagene Änderungen prüfen",
|
|
232
|
+
"ai_assistant.chat.mutation_cards.result.cancelledBody": "Sie haben diese vorgeschlagene Änderung abgebrochen. Es wurden keine Änderungen vorgenommen.",
|
|
233
|
+
"ai_assistant.chat.mutation_cards.result.cancelledTitle": "Vorschlag abgebrochen",
|
|
232
234
|
"ai_assistant.chat.mutation_cards.result.failureBody": "Die Änderung konnte nicht angewendet werden.",
|
|
233
235
|
"ai_assistant.chat.mutation_cards.result.failureTitle": "Aktion fehlgeschlagen",
|
|
234
236
|
"ai_assistant.chat.mutation_cards.result.fixWithAi": "Mit KI korrigieren",
|
|
@@ -229,6 +229,8 @@
|
|
|
229
229
|
"ai_assistant.chat.mutation_cards.preview.handlerError": "The mutation handler reported an error. Review the details and re-propose if needed.",
|
|
230
230
|
"ai_assistant.chat.mutation_cards.preview.reviewDetails": "Review details",
|
|
231
231
|
"ai_assistant.chat.mutation_cards.preview.title": "Review proposed changes",
|
|
232
|
+
"ai_assistant.chat.mutation_cards.result.cancelledBody": "You cancelled this proposed change. No changes were made.",
|
|
233
|
+
"ai_assistant.chat.mutation_cards.result.cancelledTitle": "Proposal cancelled",
|
|
232
234
|
"ai_assistant.chat.mutation_cards.result.failureBody": "The mutation could not be applied.",
|
|
233
235
|
"ai_assistant.chat.mutation_cards.result.failureTitle": "Action failed",
|
|
234
236
|
"ai_assistant.chat.mutation_cards.result.fixWithAi": "Fix with AI",
|
|
@@ -229,6 +229,8 @@
|
|
|
229
229
|
"ai_assistant.chat.mutation_cards.preview.handlerError": "El gestor de mutaciones informó un error. Revisa los detalles y vuelve a proponer la acción si es necesario.",
|
|
230
230
|
"ai_assistant.chat.mutation_cards.preview.reviewDetails": "Ver detalles",
|
|
231
231
|
"ai_assistant.chat.mutation_cards.preview.title": "Revisar los cambios propuestos",
|
|
232
|
+
"ai_assistant.chat.mutation_cards.result.cancelledBody": "Cancelaste este cambio propuesto. No se realizó ningún cambio.",
|
|
233
|
+
"ai_assistant.chat.mutation_cards.result.cancelledTitle": "Propuesta cancelada",
|
|
232
234
|
"ai_assistant.chat.mutation_cards.result.failureBody": "No se pudo aplicar la mutación.",
|
|
233
235
|
"ai_assistant.chat.mutation_cards.result.failureTitle": "La acción falló",
|
|
234
236
|
"ai_assistant.chat.mutation_cards.result.fixWithAi": "Corregir con IA",
|
|
@@ -229,6 +229,8 @@
|
|
|
229
229
|
"ai_assistant.chat.mutation_cards.preview.handlerError": "뮤테이션 핸들러가 오류를 보고했습니다. 세부 정보를 검토하고 필요하면 다시 제안하세요.",
|
|
230
230
|
"ai_assistant.chat.mutation_cards.preview.reviewDetails": "세부 정보 검토",
|
|
231
231
|
"ai_assistant.chat.mutation_cards.preview.title": "제안된 변경 사항 검토",
|
|
232
|
+
"ai_assistant.chat.mutation_cards.result.cancelledBody": "이 제안된 변경을 취소했습니다. 변경 사항이 적용되지 않았습니다.",
|
|
233
|
+
"ai_assistant.chat.mutation_cards.result.cancelledTitle": "제안이 취소됨",
|
|
232
234
|
"ai_assistant.chat.mutation_cards.result.failureBody": "뮤테이션을 적용할 수 없습니다.",
|
|
233
235
|
"ai_assistant.chat.mutation_cards.result.failureTitle": "작업 실패",
|
|
234
236
|
"ai_assistant.chat.mutation_cards.result.fixWithAi": "AI로 수정",
|
|
@@ -229,6 +229,8 @@
|
|
|
229
229
|
"ai_assistant.chat.mutation_cards.preview.handlerError": "Handler mutacji zgłosił błąd. Sprawdź szczegóły i w razie potrzeby zaproponuj akcję ponownie.",
|
|
230
230
|
"ai_assistant.chat.mutation_cards.preview.reviewDetails": "Pokaż szczegóły",
|
|
231
231
|
"ai_assistant.chat.mutation_cards.preview.title": "Sprawdź proponowane zmiany",
|
|
232
|
+
"ai_assistant.chat.mutation_cards.result.cancelledBody": "Anulowano tę proponowaną zmianę. Nie wprowadzono żadnych zmian.",
|
|
233
|
+
"ai_assistant.chat.mutation_cards.result.cancelledTitle": "Propozycja anulowana",
|
|
232
234
|
"ai_assistant.chat.mutation_cards.result.failureBody": "Nie udało się zastosować zmiany.",
|
|
233
235
|
"ai_assistant.chat.mutation_cards.result.failureTitle": "Akcja nie powiodła się",
|
|
234
236
|
"ai_assistant.chat.mutation_cards.result.fixWithAi": "Popraw z AI",
|
|
@@ -147,7 +147,7 @@ describe('executePendingActionCancel', () => {
|
|
|
147
147
|
expect(typeof emittedPayload.resolvedAt).toBe('string')
|
|
148
148
|
})
|
|
149
149
|
|
|
150
|
-
it('
|
|
150
|
+
it('stores an empty message when no reason is supplied (no hardcoded English copy)', async () => {
|
|
151
151
|
const repo = makeRepoStub(makeAction())
|
|
152
152
|
const emitEvent = jest.fn().mockResolvedValue(undefined)
|
|
153
153
|
const clock = new Date('2026-04-18T10:05:00.000Z')
|
|
@@ -164,7 +164,7 @@ describe('executePendingActionCancel', () => {
|
|
|
164
164
|
const [, , , extra] = repo.setStatus.mock.calls[0]
|
|
165
165
|
expect(extra).toMatchObject({
|
|
166
166
|
executionResult: {
|
|
167
|
-
error: { code: 'cancelled_by_user', message: '
|
|
167
|
+
error: { code: 'cancelled_by_user', message: '' },
|
|
168
168
|
},
|
|
169
169
|
})
|
|
170
170
|
const [, payload] = emitEvent.mock.calls[0] as [string, AiActionCancelledPayload]
|
|
@@ -151,7 +151,11 @@ export async function executePendingActionCancel(
|
|
|
151
151
|
const executionResult: AiPendingActionExecutionResult = {
|
|
152
152
|
error: {
|
|
153
153
|
code: 'cancelled_by_user',
|
|
154
|
-
|
|
154
|
+
// Left empty when no reason is supplied: this is surfaced as a
|
|
155
|
+
// neutral "proposal cancelled" message in the client, which
|
|
156
|
+
// supplies its own localized default copy (see MutationResultCard)
|
|
157
|
+
// instead of a hardcoded English string.
|
|
158
|
+
message: trimmedReason,
|
|
155
159
|
},
|
|
156
160
|
}
|
|
157
161
|
|