@zaip-ai/cli 4.5.0 → 4.6.0

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 (211) hide show
  1. package/README.md +2 -0
  2. package/dist/services/adapters.d.ts +1 -1
  3. package/dist/services/adapters.js +4 -4
  4. package/dist/services/adapters.js.map +1 -1
  5. package/dist/services/codex-native-tool-authority-template.d.ts.map +1 -1
  6. package/dist/services/codex-native-tool-authority-template.js +188 -9
  7. package/dist/services/codex-native-tool-authority-template.js.map +1 -1
  8. package/dist/services/codex-pre-tool-hook-template.d.ts.map +1 -1
  9. package/dist/services/codex-pre-tool-hook-template.js +50 -128
  10. package/dist/services/codex-pre-tool-hook-template.js.map +1 -1
  11. package/dist/services/codex-pre-tool-hook-template.test.js +4 -0
  12. package/dist/services/codex-pre-tool-hook-template.test.js.map +1 -1
  13. package/dist/services/codex-pre-tool-support-template.d.ts.map +1 -1
  14. package/dist/services/codex-pre-tool-support-template.js +1 -4
  15. package/dist/services/codex-pre-tool-support-template.js.map +1 -1
  16. package/dist/services/codex-stale-git-lock-template.d.ts +2 -0
  17. package/dist/services/codex-stale-git-lock-template.d.ts.map +1 -0
  18. package/dist/services/codex-stale-git-lock-template.js +64 -0
  19. package/dist/services/codex-stale-git-lock-template.js.map +1 -0
  20. package/dist/services/global-codex-config-migration.test.js +1 -4
  21. package/dist/services/global-codex-config-migration.test.js.map +1 -1
  22. package/dist/services/install-template-policy.d.ts.map +1 -1
  23. package/dist/services/install-template-policy.js +2 -0
  24. package/dist/services/install-template-policy.js.map +1 -1
  25. package/dist/services/logger.js +2 -1
  26. package/dist/services/logger.js.map +1 -1
  27. package/dist/services/logger.test.js +71 -0
  28. package/dist/services/logger.test.js.map +1 -1
  29. package/dist/services/runtime-template-test-support.d.ts +3 -0
  30. package/dist/services/runtime-template-test-support.d.ts.map +1 -0
  31. package/dist/services/runtime-template-test-support.js +32 -0
  32. package/dist/services/runtime-template-test-support.js.map +1 -0
  33. package/dist/services/runtime-template.test.js +23 -31
  34. package/dist/services/runtime-template.test.js.map +1 -1
  35. package/package.json +1 -1
  36. package/templates/.agents/README.md +8 -0
  37. package/templates/.agents/config/agents/discovery.json +1 -1
  38. package/templates/.agents/config/deploy-policy.json +5 -0
  39. package/templates/.agents/dashboard/.zaip-managed.json +211 -75
  40. package/templates/.agents/dashboard/README.md +2 -0
  41. package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-f4ZGJRg3.js → CostCalculationGuidePage-BRtiPijr.js} +1 -1
  42. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-CkQ_y8kg.js → DashboardHelpDialog-BR4ZeYpE.js} +99 -10
  43. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-v9zGRTlk.js → PricingSettingsDialog-Dr4QDFmn.js} +1 -1
  44. package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-doIMhei7.js → ProjectSettingsPage-BG0N5dw_.js} +1 -1
  45. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-C_4ftjyB.js → TraceabilityWorkspace-BvoM-YQ0.js} +1 -1
  46. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-fTQhSL85.js +68 -0
  47. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-Cv3WBFIF.js → icons-vendor-Bc6vux5t.js} +1 -1
  48. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-BMfzKaAR.js +2 -0
  49. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DZhppOIk.css +1 -0
  50. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-mHgsmxb6.js → react-vendor-DNJQFov3.js} +1 -1
  51. package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-B0CPyEVq.js → traceability-B7tjIB7W.js} +1 -1
  52. package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
  53. package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
  54. package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +6 -34
  55. package/templates/.agents/dashboard/apps/frontend/src/ReconciliationFeedback.dom.test.ts +130 -0
  56. package/templates/.agents/dashboard/apps/frontend/src/ReconciliationFeedback.tsx +226 -0
  57. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api-sqlite-view.ts +45 -0
  58. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +15 -0
  59. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +17 -46
  60. package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-fixtures.ts +23 -0
  61. package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-model.test.ts +69 -0
  62. package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-model.ts +59 -0
  63. package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback.css +131 -0
  64. package/templates/.agents/dashboard/apps/frontend/src/styles.css +0 -32
  65. package/templates/.agents/dashboard/apps/frontend/src/use-dashboard-app-model.tsx +2 -2
  66. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  67. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +2 -2
  68. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +1 -1
  69. package/templates/.agents/dashboard/modules/operations/package.json +1 -1
  70. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +1 -4
  71. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.navigation.test.ts +236 -0
  72. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +1 -24
  73. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +3 -2
  74. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  75. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  76. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  77. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  78. package/templates/.agents/dashboard/packages/local-runtime/DIAGNOSTICS.md +42 -0
  79. package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.d.mts +10 -0
  80. package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.mjs +101 -0
  81. package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.test.mjs +58 -0
  82. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +232 -217
  83. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +201 -188
  84. package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
  85. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-dashboard-startup.mjs +233 -0
  86. package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-provider-nonzero.mjs +45 -0
  87. package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-dashboard-executable-startup.mjs +107 -0
  88. package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-dashboard-reconciliation-executable.mjs +271 -0
  89. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +232 -217
  90. package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.test.ts +78 -12
  91. package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.ts +36 -56
  92. package/templates/.agents/dashboard/packages/local-runtime/src/canonical-source-revision-cache.test.ts +125 -0
  93. package/templates/.agents/dashboard/packages/local-runtime/src/canonical-source-revision-cache.ts +168 -0
  94. package/templates/.agents/dashboard/packages/local-runtime/src/dashboard-diagnostics.test.ts +57 -0
  95. package/templates/.agents/dashboard/packages/local-runtime/src/server-agent-catalog-routes.ts +76 -0
  96. package/templates/.agents/dashboard/packages/local-runtime/src/server-dual-reads-manifest-summaries-without-losing.test.ts +5 -0
  97. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-diagnostics.test.ts +118 -0
  98. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-result.test.ts +35 -6
  99. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-route.ts +105 -0
  100. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.test.ts +64 -9
  101. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts +19 -27
  102. package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-command.test.ts +111 -0
  103. package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.test.ts +59 -0
  104. package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.ts +44 -0
  105. package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.test.ts +20 -0
  106. package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.ts +12 -7
  107. package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +48 -6
  108. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +2 -2
  109. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +126 -134
  110. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.ts +14 -2
  111. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-dashboard-startup.test.ts +163 -0
  112. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-dashboard-startup.ts +78 -0
  113. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-header-timestamp.test.ts +31 -0
  114. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-provider-nonzero.test.ts +271 -0
  115. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-provider-visibility.ts +32 -0
  116. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-operations-duration.ts +58 -0
  117. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-provider-dimension.ts +12 -3
  118. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-view-page.ts +9 -58
  119. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-rebuild.ts +9 -35
  120. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-schema.ts +10 -8
  121. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +43 -45
  122. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +5 -9
  123. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +5 -1
  124. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-startup-seed.test.ts +87 -0
  125. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-logging.mjs +6 -2
  126. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-server.mjs +7 -1
  127. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +4 -5
  128. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  129. package/templates/.agents/dashboard/packages/ui/src/DashboardHeader.test.mjs +33 -4
  130. package/templates/.agents/dashboard/packages/ui/src/DashboardHeader.tsx +8 -7
  131. package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +46 -3
  132. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  133. package/templates/.agents/dashboard/tests/e2e/qualify-kanban-popup.mjs +56 -0
  134. package/templates/.agents/dashboard/tests/e2e/specs/dashboard-update-label.spec.ts +49 -0
  135. package/templates/.agents/design/.zaip-managed.json +17 -5
  136. package/templates/.agents/design/apps/catalog/dist/assets/index-D3uhpycC.js +72 -0
  137. package/templates/.agents/design/apps/catalog/dist/index.html +1 -1
  138. package/templates/.agents/design/apps/catalog/package.json +1 -1
  139. package/templates/.agents/design/apps/catalog/src/pages/ComponentsPage.tsx +13 -1
  140. package/templates/.agents/design/apps/catalog/src/specimens/ReconciliationSpecimen.stories.tsx +6 -0
  141. package/templates/.agents/design/apps/catalog/src/specimens/ReconciliationSpecimen.tsx +101 -0
  142. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  143. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  144. package/templates/.agents/requirements/cloud-hostinger-autonomy.md +1 -1
  145. package/templates/.agents/requirements/comparative-audit.md +1 -1
  146. package/templates/.agents/requirements/context-engine.md +1 -1
  147. package/templates/.agents/requirements/cost-observability.md +1 -1
  148. package/templates/.agents/requirements/dashboard-launch.md +1 -1
  149. package/templates/.agents/requirements/data-export.md +1 -1
  150. package/templates/.agents/requirements/design-system.md +1 -1
  151. package/templates/.agents/requirements/execution-engine.md +1 -1
  152. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  153. package/templates/.agents/requirements/model-orchestration.md +21 -6
  154. package/templates/.agents/requirements/public-distribution.md +4 -4
  155. package/templates/.agents/requirements/software-architecture.md +1 -1
  156. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  157. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  158. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  159. package/templates/.agents/rules/core.md +3 -1
  160. package/templates/.agents/rules/workflow.md +3 -2
  161. package/templates/.agents/runtime/VERSION +1 -1
  162. package/templates/.agents/runtime/bin/zaip.mjs +366 -196
  163. package/templates/.agents/runtime/checksums.sha256 +5 -5
  164. package/templates/.agents/runtime/manifest.json +1 -1
  165. package/templates/.agents/runtime/provenance.json +4 -4
  166. package/templates/.agents/runtime/sbom.cdx.json +1 -1
  167. package/templates/.agents/skills/zaip-skill-config/SKILL.md +25 -3
  168. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +4 -0
  169. package/templates/.agents/skills/zaip-skill-execution/references/autonomous-continuation.md +4 -0
  170. package/templates/.agents/skills/zaip-skill-execution/references/candidate-workflow.md +1 -1
  171. package/templates/.agents/skills/zaip-skill-execution/references/runtime-governance.md +2 -2
  172. package/templates/.agents/skills/zaip-skill-general-operations/SKILL.md +1 -0
  173. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +36 -48
  174. package/templates/.agents/skills/zaip-skill-github-deploy/agents/openai.yaml +2 -2
  175. package/templates/.agents/skills/zaip-skill-github-deploy/references/deployment-workflow.md +67 -99
  176. package/templates/.agents/skills/zaip-skill-github-deploy/references/release-identity.md +33 -30
  177. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.mjs +303 -0
  178. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.test.mjs +334 -0
  179. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +41 -28
  180. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/guarded-commits.mjs +212 -0
  181. package/templates/.agents/skills/zaip-skill-lifecycle-closure/SKILL.md +4 -0
  182. package/templates/.agents/skills/zaip-skill-prd/SKILL.md +4 -0
  183. package/templates/.agents/skills/zaip-skill-release-qualification/SKILL.md +44 -68
  184. package/templates/.agents/skills/zaip-skill-release-qualification/references/local-preparation.md +91 -44
  185. package/templates/.agents/skills/zaip-skill-send-email/SKILL.md +15 -0
  186. package/templates/.agents/skills/zaip-skill-send-email/agents/openai.yaml +4 -0
  187. package/templates/.agents/skills/zaip-skill-setup/SKILL.md +5 -2
  188. package/templates/.agents/skills/zaip-skill-spec/SKILL.md +4 -0
  189. package/templates/.agents/skills/zaip-skill-task/SKILL.md +4 -0
  190. package/templates/.agents/skills/zaip-skill-testing-strategy/SKILL.md +19 -0
  191. package/templates/.agents/skills/zaip-skill-tool-setup/SKILL.md +2 -0
  192. package/templates/.agents/skills/zaip-skill-use-connectors/SKILL.md +15 -0
  193. package/templates/.agents/skills/zaip-skill-use-connectors/agents/openai.yaml +4 -0
  194. package/templates/.agents/skills/zaip-skill-use-connectors/scripts/connector-guidance.test.mjs +29 -0
  195. package/templates/.agents/skills/zaip-skill-use-connectors/scripts/verify-connector-lock.mjs +30 -0
  196. package/templates/.agents/tools/cloud-runner.integration.test.mjs +3 -0
  197. package/templates/.agents/zaip-aios/VERSION +1 -1
  198. package/templates/.agents/zaip-aios/guides/03-tasks.md +6 -0
  199. package/templates/.agents/zaip-aios/guides/04-specs.md +6 -0
  200. package/templates/.agents/zaip-aios/guides/05-prds.md +4 -0
  201. package/templates/.agents/zaip-aios/guides/06-execucao.md +6 -0
  202. package/templates/.agents/zaip-aios/guides/16-lifecycle-e-reconciliacao.md +6 -0
  203. package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +4 -0
  204. package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +6 -0
  205. package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +36 -4
  206. package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +15 -4
  207. package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +8 -0
  208. package/templates/template-manifest.json +330 -142
  209. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-D7PrDZy9.js +0 -68
  210. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-OBJXZOwe.css +0 -1
  211. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Rbmpz4UO.js +0 -2
@@ -0,0 +1,130 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { fileURLToPath } from "node:url";
4
+ import { describe, expect, it } from "vitest";
5
+
6
+ describe("ReconciliationFeedback browser interactions", () => {
7
+ it("bounds large results, preserves all entries, traps/restores focus, and supports empty/English states", async () => {
8
+ const browserRequire = createRequire(
9
+ fileURLToPath(new URL("../../../tests/e2e/package.json", import.meta.url)),
10
+ );
11
+ const esbuild = browserRequire("esbuild");
12
+ const { chromium, expect: browserExpect } = browserRequire("@playwright/test");
13
+ const harness = await esbuild.build({
14
+ bundle: true,
15
+ loader: { ".css": "empty" },
16
+ format: "iife",
17
+ platform: "browser",
18
+ write: false,
19
+ stdin: {
20
+ contents: `
21
+ import React, { useState } from "react";
22
+ import { createRoot } from "react-dom/client";
23
+ import { ReconciliationFeedback } from "./ReconciliationFeedback.js";
24
+ import { reconciliationFeedbackFixtures as fixtures } from "./reconciliation-feedback-fixtures.js";
25
+ function Harness() {
26
+ const [mode, setMode] = useState("large");
27
+ const [visible, setVisible] = useState(true);
28
+ const [locale, setLocale] = useState("pt-BR");
29
+ return <><button onClick={() => { setMode("mixed"); setVisible(true); }}>Mixed</button>
30
+ <button onClick={() => { setMode("empty"); setLocale("en-US"); setVisible(true); }}>Empty English</button>
31
+ {visible && <ReconciliationFeedback result={fixtures[mode]} locale={locale} onDismiss={() => setVisible(false)} />}</>;
32
+ }
33
+ createRoot(document.getElementById("root")).render(<Harness />);
34
+ `,
35
+ loader: "tsx",
36
+ resolveDir: fileURLToPath(new URL(".", import.meta.url)),
37
+ sourcefile: "reconciliation-harness.tsx",
38
+ },
39
+ });
40
+ const browser = await chromium.launch({ headless: true });
41
+ try {
42
+ const page = await browser.newPage({ viewport: { width: 360, height: 320 } });
43
+ const errors: string[] = [];
44
+ page.on("pageerror", (error: Error) => errors.push(error.message));
45
+ await page.setContent('<main id="root"></main>');
46
+ await page.addStyleTag({
47
+ content: readFileSync(new URL("./styles.css", import.meta.url), "utf8"),
48
+ });
49
+ await page.addStyleTag({
50
+ content: readFileSync(new URL("./reconciliation-feedback.css", import.meta.url), "utf8"),
51
+ });
52
+ await page.addScriptTag({ content: harness.outputFiles[0].text });
53
+ const status = page.getByRole("status");
54
+ await browserExpect(status).toContainText("Outros estados registrados: 120");
55
+ await browserExpect(status).not.toContainText("LONG-ID");
56
+ const summaryBounds = await page.locator(".app-reconciliation-result").boundingBox();
57
+ if (!summaryBounds) throw new Error("Summary must have visible bounds");
58
+ expect(summaryBounds.y).toBeGreaterThanOrEqual(0);
59
+ expect(summaryBounds.y + summaryBounds.height).toBeLessThanOrEqual(320);
60
+ await page.getByRole("button", { name: "Veja mais" }).click();
61
+ const dialog = page.getByRole("dialog", { name: "Resultado da reconciliação manual" });
62
+ await browserExpect(dialog).toBeVisible();
63
+ await browserExpect(dialog.locator("li")).toHaveCount(122);
64
+ expect(await dialog.boundingBox()).toEqual({ x: 0, y: 0, width: 360, height: 320 });
65
+ expect(await page.evaluate(() => document.body.style.overflow)).toBe("hidden");
66
+ expect(
67
+ await dialog.evaluate((element: HTMLElement) => element.scrollWidth <= element.clientWidth),
68
+ ).toBe(true);
69
+ const scroll = dialog.getByRole("region", { name: "Itens da reconciliação", exact: true });
70
+ expect(
71
+ await scroll.evaluate(
72
+ (element: HTMLElement) =>
73
+ element.scrollWidth <= element.clientWidth &&
74
+ element.scrollHeight > element.clientHeight,
75
+ ),
76
+ ).toBe(true);
77
+ await page.keyboard.press("Shift+Tab");
78
+ expect(await page.evaluate(() => document.activeElement?.closest("dialog") !== null)).toBe(
79
+ true,
80
+ );
81
+ for (let index = 0; index < 5; index++) {
82
+ await page.keyboard.press("Tab");
83
+ expect(await page.evaluate(() => document.activeElement?.closest("dialog") !== null)).toBe(
84
+ true,
85
+ );
86
+ }
87
+ await scroll.evaluate((element: HTMLElement) => {
88
+ element.scrollTop = element.scrollHeight;
89
+ });
90
+ await browserExpect(
91
+ dialog.getByText("A invocação ainda está em execução.", { exact: false }),
92
+ ).toBeVisible();
93
+ await page.keyboard.press("Escape");
94
+ await browserExpect(dialog).toHaveCount(0);
95
+ await browserExpect(page.getByRole("button", { name: "Veja mais" })).toBeFocused();
96
+ expect(await page.evaluate(() => document.body.style.overflow)).toBe("");
97
+ await page.getByRole("button", { name: "Mixed", exact: true }).click();
98
+ await page.getByRole("button", { name: "Veja mais" }).click();
99
+ await browserExpect(dialog).toContainText("Desfechos bloqueados aparecem somente");
100
+ await browserExpect(dialog.locator("code").filter({ hasText: "TASK-BLOCKED" })).toHaveCount(
101
+ 1,
102
+ );
103
+ await dialog.getByText("Motivo original", { exact: true }).first().click();
104
+ await browserExpect(
105
+ dialog.getByText("Host terminated without delivery evidence.", { exact: true }),
106
+ ).toBeVisible();
107
+ await browserExpect(dialog).toContainText("Motivo não informado.");
108
+ await browserExpect(dialog).toContainText("An unrecognized reason stays intact.");
109
+ await page.getByRole("button", { name: "Fechar detalhes" }).click();
110
+ await browserExpect(page.getByRole("button", { name: "Veja mais" })).toBeFocused();
111
+ await page.getByRole("button", { name: "Fechar", exact: true }).click();
112
+ await browserExpect(status).toHaveCount(0);
113
+ await page.getByRole("button", { name: "Empty English" }).click();
114
+ await browserExpect(status).toContainText(
115
+ "Other recorded outcomes: 0 · Blocked outcomes: 0 · Not reconciled: 0",
116
+ );
117
+ await page.getByRole("button", { name: "View details" }).click();
118
+ await browserExpect(page.getByRole("dialog")).toContainText("Blocked outcomes appear only");
119
+ await browserExpect(
120
+ page.getByText("No items in this category.", { exact: true }),
121
+ ).toHaveCount(3);
122
+ await page.getByRole("button", { name: "Close details" }).click();
123
+ await page.getByRole("button", { name: "Dismiss" }).click();
124
+ await browserExpect(status).toHaveCount(0);
125
+ expect(errors).toEqual([]);
126
+ } finally {
127
+ await browser.close();
128
+ }
129
+ }, 60_000);
130
+ });
@@ -0,0 +1,226 @@
1
+ import { useEffect, useId, useRef, useState } from "react";
2
+ import type { ReconciliationResult } from "./dashboard-api.js";
3
+ import {
4
+ type ReconciliationLocale,
5
+ reconciliationCounts,
6
+ reconciliationReason,
7
+ reconciliationRecordedItems,
8
+ reconciliationStatus,
9
+ } from "./reconciliation-feedback-model.js";
10
+ import "./reconciliation-feedback.css";
11
+
12
+ export function ReconciliationFeedback({
13
+ result,
14
+ locale,
15
+ onDismiss,
16
+ }: {
17
+ result: ReconciliationResult;
18
+ locale: ReconciliationLocale;
19
+ onDismiss: () => void;
20
+ }) {
21
+ const [detailsOpen, setDetailsOpen] = useState(false);
22
+ const triggerRef = useRef<HTMLButtonElement>(null);
23
+ const pt = locale === "pt-BR";
24
+ const counts = reconciliationCounts(result);
25
+ const title = pt ? "Resultado da reconciliação manual" : "Manual reconciliation result";
26
+ const labels = {
27
+ reconciled: pt ? "Outros estados registrados" : "Other recorded outcomes",
28
+ blocked: pt ? "Desfechos bloqueados" : "Blocked outcomes",
29
+ notReconciled: pt ? "Não reconciliados" : "Not reconciled",
30
+ };
31
+ const summary = `${labels.reconciled}: ${counts.reconciled} · ${labels.blocked}: ${counts.blocked} · ${labels.notReconciled}: ${counts.notReconciled}`;
32
+
33
+ return (
34
+ <>
35
+ <section className="app-reconciliation-result" aria-label={title}>
36
+ <div role="status" aria-live="polite" aria-atomic="true">
37
+ <strong>{title}</strong>
38
+ <p>{summary}</p>
39
+ </div>
40
+ <div className="app-reconciliation-actions">
41
+ <button
42
+ ref={triggerRef}
43
+ type="button"
44
+ aria-haspopup="dialog"
45
+ onClick={() => setDetailsOpen(true)}
46
+ >
47
+ {pt ? "Veja mais" : "View details"}
48
+ </button>
49
+ <button type="button" onClick={onDismiss}>
50
+ {pt ? "Fechar" : "Dismiss"}
51
+ </button>
52
+ </div>
53
+ </section>
54
+ {detailsOpen ? (
55
+ <ReconciliationDetails
56
+ result={result}
57
+ locale={locale}
58
+ title={title}
59
+ summary={summary}
60
+ labels={labels}
61
+ onClose={() => setDetailsOpen(false)}
62
+ restoreFocus={() => triggerRef.current?.focus({ preventScroll: true })}
63
+ />
64
+ ) : null}
65
+ </>
66
+ );
67
+ }
68
+
69
+ function ReconciliationDetails({
70
+ result,
71
+ locale,
72
+ title,
73
+ summary,
74
+ labels,
75
+ onClose,
76
+ restoreFocus,
77
+ }: {
78
+ result: ReconciliationResult;
79
+ locale: ReconciliationLocale;
80
+ title: string;
81
+ summary: string;
82
+ labels: { reconciled: string; blocked: string; notReconciled: string };
83
+ onClose: () => void;
84
+ restoreFocus: () => void;
85
+ }) {
86
+ const dialogRef = useRef<HTMLDialogElement>(null);
87
+ const restoreFocusRef = useRef(restoreFocus);
88
+ const titleId = useId();
89
+ const pt = locale === "pt-BR";
90
+ // Native modal dialogs make background content inert and contain keyboard focus.
91
+ useEffect(() => {
92
+ const dialog = dialogRef.current;
93
+ if (!dialog) return;
94
+ const previousOverflow = document.body.style.overflow;
95
+ dialog.showModal();
96
+ document.body.style.overflow = "hidden";
97
+ return () => {
98
+ dialog.close();
99
+ document.body.style.overflow = previousOverflow;
100
+ restoreFocusRef.current();
101
+ };
102
+ // The opener is stable for this dialog's lifetime; locale/result changes
103
+ // update its content without reopening it or changing focus.
104
+ }, []);
105
+
106
+ const groups = [
107
+ {
108
+ label: labels.reconciled,
109
+ items: reconciliationRecordedItems(result),
110
+ reasons: true,
111
+ },
112
+ { label: labels.blocked, items: result.blocked, reasons: true },
113
+ { label: labels.notReconciled, items: result.not_reconciled, reasons: true },
114
+ ];
115
+ return (
116
+ <dialog
117
+ ref={dialogRef}
118
+ className="app-reconciliation-dialog"
119
+ aria-labelledby={titleId}
120
+ aria-modal="true"
121
+ onCancel={(event) => {
122
+ event.preventDefault();
123
+ onClose();
124
+ }}
125
+ onKeyDown={(event) => {
126
+ if (event.key !== "Tab") return;
127
+ const focusable = Array.from(
128
+ event.currentTarget.querySelectorAll<HTMLElement>(
129
+ 'button:not(:disabled), summary, [tabindex="0"]',
130
+ ),
131
+ ).filter((element) => element.getClientRects().length > 0);
132
+ const first = focusable[0];
133
+ const last = focusable[focusable.length - 1];
134
+ if (event.shiftKey && document.activeElement === first) {
135
+ event.preventDefault();
136
+ last?.focus();
137
+ } else if (!event.shiftKey && document.activeElement === last) {
138
+ event.preventDefault();
139
+ first?.focus();
140
+ }
141
+ }}
142
+ >
143
+ <header>
144
+ <h2 id={titleId}>{title}</h2>
145
+ <button type="button" onClick={onClose}>
146
+ {pt ? "Fechar detalhes" : "Close details"}
147
+ </button>
148
+ </header>
149
+ <section
150
+ className="app-reconciliation-details"
151
+ // biome-ignore lint/a11y/noNoninteractiveTabindex: Scrollable content must support keyboard scrolling.
152
+ tabIndex={0}
153
+ aria-label={pt ? "Itens da reconciliação" : "Reconciliation items"}
154
+ >
155
+ <p>{summary}</p>
156
+ <p>
157
+ {pt
158
+ ? "A reconciliação registra o estado real de cada item. Desfechos bloqueados aparecem somente na categoria de bloqueios; um estado registrado não significa entrega concluída."
159
+ : "Reconciliation records each item's actual state. Blocked outcomes appear only in the blocked category; a recorded state does not imply completed delivery."}
160
+ </p>
161
+ <p>
162
+ {pt
163
+ ? "Última telemetria indica a coleta de tokens medidos. Registrar estados ou atualizar o snapshot sem novos tokens não altera esse horário."
164
+ : "Last telemetry means measured token collection. Recording states or refreshing the snapshot without new tokens does not change that time."}
165
+ </p>
166
+ {result.diagnostics ? (
167
+ <p>
168
+ {pt ? "Reconciliação executada em" : "Reconciliation executed at"}{" "}
169
+ {new Intl.DateTimeFormat(locale, { dateStyle: "short", timeStyle: "medium" }).format(
170
+ new Date(result.diagnostics.completed_at),
171
+ )}
172
+ {" · Job: "}
173
+ <code>{result.diagnostics.job_id}</code>
174
+ {" · "}
175
+ {pt ? "Logs" : "Logs"}: <code>{result.diagnostics.log_directory}</code>
176
+ </p>
177
+ ) : null}
178
+ <p>
179
+ {pt
180
+ ? "Invocações ainda em execução são preservadas intencionalmente. Permanecer não reconciliado por esse motivo não indica falha do comando."
181
+ : "Invocations that are still running are intentionally preserved. Remaining unreconciled for this reason does not indicate a command failure."}
182
+ </p>
183
+ {groups.map((group) => (
184
+ <section key={group.label} aria-label={group.label}>
185
+ <h3>
186
+ {group.label} ({group.items.length})
187
+ </h3>
188
+ {group.items.length === 0 ? (
189
+ <p>{pt ? "Nenhum item nesta categoria." : "No items in this category."}</p>
190
+ ) : (
191
+ <ul>
192
+ {keyedReconciliationItems(group.items).map(({ item, key }) => (
193
+ <li key={key}>
194
+ <code>{item.id}</code>
195
+ {"status" in item && typeof item.status === "string" ? (
196
+ <p>{reconciliationStatus(item.status, locale)}</p>
197
+ ) : null}
198
+ {group.reasons ? <p>{reconciliationReason(item.reason, locale)}</p> : null}
199
+ {group.reasons &&
200
+ item.reason !== null &&
201
+ reconciliationReason(item.reason, locale) !== item.reason ? (
202
+ <details>
203
+ <summary>{pt ? "Motivo original" : "Original reason"}</summary>
204
+ <p>{item.reason}</p>
205
+ </details>
206
+ ) : null}
207
+ </li>
208
+ ))}
209
+ </ul>
210
+ )}
211
+ </section>
212
+ ))}
213
+ </section>
214
+ </dialog>
215
+ );
216
+ }
217
+
218
+ function keyedReconciliationItems(items: Array<{ id: string; reason: string | null }>) {
219
+ const occurrences = new Map<string, number>();
220
+ return items.map((item) => {
221
+ const identity = JSON.stringify([item.id, item.reason]);
222
+ const occurrence = (occurrences.get(identity) ?? 0) + 1;
223
+ occurrences.set(identity, occurrence);
224
+ return { item, key: `${identity}:${occurrence}` };
225
+ });
226
+ }
@@ -0,0 +1,45 @@
1
+ import type { CostPeriodAverages } from "@zaip-ai/dashboard-cost-monitor/contracts";
2
+
3
+ export type SqliteViewName =
4
+ | "cost_monitor_daily"
5
+ | "cost_monitor_monthly"
6
+ | "cost_monitor_annual"
7
+ | "cost_monitor_overview_view"
8
+ | "cost_monitor_executions"
9
+ | "cost_monitor_agent_invocations"
10
+ | "cost_monitor_zaip_agents"
11
+ | "cost_monitor_models"
12
+ | "cost_monitor_providers"
13
+ | "cost_monitor_tasks"
14
+ | "cost_monitor_specs"
15
+ | "cost_monitor_prds"
16
+ | "cost_monitor_conversations"
17
+ | "operations_work_list"
18
+ | "operations_work_kanban"
19
+ | "operations_executions"
20
+ | "operations_agent_invocations"
21
+ | "operations_conversations"
22
+ | "operations_decisions"
23
+ | "operations_changes"
24
+ | "operations_daily"
25
+ | "operations_monthly"
26
+ | "operations_annual"
27
+ | "first_fold_overview"
28
+ | "first_fold_task_status"
29
+ | "first_fold_task_risk"
30
+ | "first_fold_top_tasks"
31
+ | "first_fold_task"
32
+ | "first_fold_spec"
33
+ | "first_fold_prd";
34
+
35
+ export interface SqliteViewPage {
36
+ data_source: "sqlite_read_model";
37
+ view: SqliteViewName;
38
+ offset: number;
39
+ limit: number;
40
+ total_count: number;
41
+ has_more: boolean;
42
+ totals?: Record<string, unknown>;
43
+ period_averages?: Record<"BRL" | "USD", CostPeriodAverages>;
44
+ rows: Array<Record<string, unknown>>;
45
+ }
@@ -139,6 +139,21 @@ describe("DashboardApiClient readiness", () => {
139
139
  vi.unstubAllGlobals();
140
140
  });
141
141
 
142
+ it("requests recent telemetry with an authenticated command when the browser opens", async () => {
143
+ vi.stubGlobal("window", globalThis);
144
+ const fetchMock = vi.fn(
145
+ async () => new Response(JSON.stringify({ snapshot_state: "updating" }), { status: 202 }),
146
+ );
147
+ vi.stubGlobal("fetch", fetchMock);
148
+ await expect(new DashboardApiClient("session").captureTelemetry()).resolves.toEqual({
149
+ snapshot_state: "updating",
150
+ });
151
+ expect(fetchMock).toHaveBeenCalledWith(
152
+ "/api/v2/capture-telemetry",
153
+ expect.objectContaining({ method: "POST", headers: { "x-zaip-session": "session" } }),
154
+ );
155
+ });
156
+
142
157
  it("reads a committed snapshot revision through the authenticated GET endpoint", async () => {
143
158
  vi.stubGlobal("window", globalThis);
144
159
  const fetchMock = vi.fn(
@@ -3,10 +3,7 @@ import type { ProjectOverview } from "./dashboard-project-overview.js";
3
3
  export type { ProjectOverview } from "./dashboard-project-overview.js";
4
4
 
5
5
  import { type ApiErrorPayload, dashboardApiPaths } from "@zaip-ai/dashboard-contracts";
6
- import type {
7
- CostMonitorProjection,
8
- CostPeriodAverages,
9
- } from "@zaip-ai/dashboard-cost-monitor/contracts";
6
+ import type { CostMonitorProjection } from "@zaip-ai/dashboard-cost-monitor/contracts";
10
7
  import type {
11
8
  ChangeProjection,
12
9
  DashboardTaskProjection,
@@ -25,53 +22,14 @@ import type {
25
22
  PricingCatalog,
26
23
  ProjectConfigSettings,
27
24
  } from "./dashboard-api-settings.js";
25
+ import type { SqliteViewName, SqliteViewPage } from "./dashboard-api-sqlite-view.js";
28
26
  import type { DashboardIdentityResponse } from "./dashboard-identity.js";
27
+
28
+ export type { SqliteViewName, SqliteViewPage } from "./dashboard-api-sqlite-view.js";
29
29
  export interface ReconciliationBlockedItem {
30
30
  id: string;
31
31
  reason: string | null;
32
32
  }
33
- export type SqliteViewName =
34
- | "cost_monitor_daily"
35
- | "cost_monitor_monthly"
36
- | "cost_monitor_annual"
37
- | "cost_monitor_overview_view"
38
- | "cost_monitor_executions"
39
- | "cost_monitor_agent_invocations"
40
- | "cost_monitor_zaip_agents"
41
- | "cost_monitor_models"
42
- | "cost_monitor_providers"
43
- | "cost_monitor_tasks"
44
- | "cost_monitor_specs"
45
- | "cost_monitor_prds"
46
- | "cost_monitor_conversations"
47
- | "operations_work_list"
48
- | "operations_work_kanban"
49
- | "operations_executions"
50
- | "operations_agent_invocations"
51
- | "operations_conversations"
52
- | "operations_decisions"
53
- | "operations_changes"
54
- | "operations_daily"
55
- | "operations_monthly"
56
- | "operations_annual"
57
- | "first_fold_overview"
58
- | "first_fold_task_status"
59
- | "first_fold_task_risk"
60
- | "first_fold_top_tasks"
61
- | "first_fold_task"
62
- | "first_fold_spec"
63
- | "first_fold_prd";
64
- export interface SqliteViewPage {
65
- data_source: "sqlite_read_model";
66
- view: SqliteViewName;
67
- offset: number;
68
- limit: number;
69
- total_count: number;
70
- has_more: boolean;
71
- totals?: Record<string, unknown>;
72
- period_averages?: Record<"BRL" | "USD", CostPeriodAverages>;
73
- rows: Array<Record<string, unknown>>;
74
- }
75
33
  export type OperationsScope = "quick" | "overview" | "detail";
76
34
  export type WorkBoardStage =
77
35
  | "definition"
@@ -224,6 +182,15 @@ export interface ReconciliationResult {
224
182
  reconciled_ids: string[];
225
183
  blocked: ReconciliationBlockedItem[];
226
184
  not_reconciled: ReconciliationBlockedItem[];
185
+ outcomes?: Array<ReconciliationBlockedItem & { status: string }>;
186
+ diagnostics?: {
187
+ job_id: string;
188
+ started_at: string;
189
+ completed_at: string;
190
+ log_directory: string;
191
+ projection_updated_at: string | null;
192
+ latest_token_collected_at: string | null;
193
+ };
227
194
  }
228
195
 
229
196
  export interface CostMonitorFilters {
@@ -245,6 +212,10 @@ export class DashboardApiClient {
245
212
  return this.get("/api/v2/ready");
246
213
  }
247
214
 
215
+ captureTelemetry(): Promise<{ snapshot_state: "current" | "updating" }> {
216
+ return this.post("/api/v2/capture-telemetry");
217
+ }
218
+
248
219
  /**
249
220
  * Lightweight, workspace-level metadata. This deliberately stays outside
250
221
  * the aggregate snapshot so the first Operations paint remains compact.
@@ -0,0 +1,23 @@
1
+ import type { ReconciliationResult } from "./dashboard-api.js";
2
+
3
+ /** Stable specimens shared by catalog and regression tests, with no API calls. */
4
+ export const reconciliationFeedbackFixtures = {
5
+ empty: { reconciled_ids: [], blocked: [], not_reconciled: [] },
6
+ mixed: {
7
+ reconciled_ids: ["TASK-RECONCILED", "TASK-BLOCKED"],
8
+ blocked: [{ id: "TASK-BLOCKED", reason: "Host terminated without delivery evidence." }],
9
+ not_reconciled: [
10
+ { id: "RUN-ACTIVE", reason: "Invocation is still running." },
11
+ { id: "RUN-UNKNOWN", reason: null },
12
+ { id: "RUN-UNRECOGNIZED", reason: "An unrecognized reason stays intact." },
13
+ ],
14
+ },
15
+ large: {
16
+ reconciled_ids: Array.from(
17
+ { length: 120 },
18
+ (_, index) => `TASK-${index}-${"LONG-ID-".repeat(24)}`,
19
+ ),
20
+ blocked: [{ id: "TASK-LONG-BLOCKED", reason: "unbroken-original-reason".repeat(100) }],
21
+ not_reconciled: [{ id: "RUN-ACTIVE", reason: "Invocation is still running." }],
22
+ },
23
+ } satisfies Record<string, ReconciliationResult>;
@@ -0,0 +1,69 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { reconciliationFeedbackFixtures as fixtures } from "./reconciliation-feedback-fixtures.js";
3
+ import {
4
+ reconciliationCounts,
5
+ reconciliationReason,
6
+ reconciliationRecordedItems,
7
+ reconciliationStatus,
8
+ } from "./reconciliation-feedback-model.js";
9
+
10
+ describe("reconciliation feedback semantics", () => {
11
+ it("displays each persisted outcome once without inferring success", () => {
12
+ expect(reconciliationCounts(fixtures.mixed)).toEqual({
13
+ reconciled: 1,
14
+ blocked: 1,
15
+ notReconciled: 3,
16
+ });
17
+ expect(fixtures.mixed.reconciled_ids).toContain(fixtures.mixed.blocked[0]?.id);
18
+ expect(reconciliationCounts(fixtures.empty)).toEqual({
19
+ reconciled: 0,
20
+ blocked: 0,
21
+ notReconciled: 0,
22
+ });
23
+ });
24
+ it("reports the screenshot's failed Run and three blocked owners as distinct outcomes", () => {
25
+ const blocked = ["TASK-X", "CHG-X", "EXEC-X"].map((id) => ({ id, reason: "repair required" }));
26
+ const result = {
27
+ reconciled_ids: ["RUN-X", ...blocked.map((item) => item.id)],
28
+ blocked,
29
+ not_reconciled: [],
30
+ outcomes: [{ id: "RUN-X", status: "failed", reason: "orphaned" }],
31
+ };
32
+ expect(reconciliationCounts(result)).toEqual({ reconciled: 1, blocked: 3, notReconciled: 0 });
33
+ expect(reconciliationRecordedItems(result)).toEqual([
34
+ { id: "RUN-X", status: "failed", reason: "orphaned" },
35
+ ]);
36
+ expect(reconciliationStatus("failed", "pt-BR")).toBe("Falha técnica");
37
+ expect(
38
+ reconciliationReason(
39
+ "Explicit orphan reconciliation found no active host invocation or terminal work outcome; repair is required before resuming (Run RUN-X).",
40
+ "pt-BR",
41
+ ),
42
+ ).toContain("exige reparo e validação");
43
+ });
44
+ it("explains intentional running preservation in Portuguese and keeps unknown/null truthful", () => {
45
+ expect(reconciliationReason("Invocation is still running.", "pt-BR")).toContain(
46
+ "preservada intencionalmente",
47
+ );
48
+ expect(reconciliationReason("Invocation is still running", "pt-BR")).toContain(
49
+ "não indica falha",
50
+ );
51
+ expect(reconciliationReason("Invocation is still running.", "en-US")).toBe(
52
+ "Invocation is still running.",
53
+ );
54
+ expect(reconciliationReason("unrecognized", "pt-BR")).toBe("unrecognized");
55
+ expect(reconciliationReason(null, "pt-BR")).toBe("Motivo não informado.");
56
+ expect(reconciliationReason(null, "en-US")).toBe("No reason provided.");
57
+ });
58
+ it("translates exact orphaned execution outcomes while preserving the execution ID", () => {
59
+ expect(
60
+ reconciliationReason(
61
+ "Execution EXEC-LONG has no active orchestrator agent call after the reconciliation grace period.",
62
+ "pt-BR",
63
+ ),
64
+ ).toContain("A execução EXEC-LONG não tem uma chamada");
65
+ expect(
66
+ reconciliationReason("Execution orphaned after the reconciliation grace period.", "pt-BR"),
67
+ ).toContain("período de tolerância");
68
+ });
69
+ });
@@ -0,0 +1,59 @@
1
+ import type { ReconciliationResult } from "./dashboard-api.js";
2
+
3
+ export type ReconciliationLocale = "pt-BR" | "en-US";
4
+
5
+ export function reconciliationCounts(result: ReconciliationResult) {
6
+ const blockedIds = new Set(result.blocked.map((item) => item.id));
7
+ return {
8
+ reconciled: result.reconciled_ids.filter((id) => !blockedIds.has(id)).length,
9
+ blocked: result.blocked.length,
10
+ notReconciled: result.not_reconciled.length,
11
+ };
12
+ }
13
+
14
+ export function reconciliationRecordedItems(result: ReconciliationResult) {
15
+ const blockedIds = new Set(result.blocked.map((item) => item.id));
16
+ return result.reconciled_ids
17
+ .filter((id) => !blockedIds.has(id))
18
+ .map((id) => {
19
+ const outcome = result.outcomes?.find((item) => item.id === id);
20
+ return { id, reason: outcome?.reason ?? null, status: outcome?.status ?? null };
21
+ });
22
+ }
23
+
24
+ export function reconciliationStatus(status: string | null, locale: ReconciliationLocale) {
25
+ const statuses: Record<string, string> = {
26
+ completed: "Concluído",
27
+ delivered: "Entregue",
28
+ failed: "Falha técnica",
29
+ blocked: "Bloqueado",
30
+ cancelled: "Cancelado",
31
+ };
32
+ return status === null ? "" : locale === "pt-BR" ? (statuses[status] ?? status) : status;
33
+ }
34
+
35
+ const portugueseReasons: Record<string, string> = {
36
+ "Invocation is still running":
37
+ "A invocação ainda está em execução. Foi preservada intencionalmente; isso não indica falha do comando.",
38
+ "Host terminated without delivery evidence": "O host encerrou sem evidências de entrega.",
39
+ "Host interrupted": "O host foi interrompido.",
40
+ "Child attempt stopped": "A tentativa do subagente foi encerrada.",
41
+ "Execution orphaned after the reconciliation grace period":
42
+ "A execução ficou sem orquestrador ativo após o período de tolerância da reconciliação.",
43
+ };
44
+
45
+ export function reconciliationReason(reason: string | null, locale: ReconciliationLocale): string {
46
+ if (reason === null) return locale === "pt-BR" ? "Motivo não informado." : "No reason provided.";
47
+ if (locale === "en-US") return reason;
48
+ const orphan = reason.match(
49
+ /^Explicit orphan reconciliation found no active host invocation or terminal work outcome; repair is required before resuming \(Run (\S+)\)\.$/,
50
+ );
51
+ if (orphan)
52
+ return `A Run ${orphan[1]} ficou sem invocação ativa do host e sem evidência de um desfecho de entrega. A reconciliação registrou a interrupção; a entrega exige reparo e validação antes de retomar.`;
53
+ const orphanedExecution = reason.match(
54
+ /^Execution (\S+) has no active orchestrator agent call after the reconciliation grace period\.?$/,
55
+ );
56
+ if (orphanedExecution)
57
+ return `A execução ${orphanedExecution[1]} não tem uma chamada de agente orquestrador ativa após o período de tolerância da reconciliação.`;
58
+ return portugueseReasons[reason.trim().replace(/\.$/, "")] ?? reason;
59
+ }