@zaip-ai/cli 1.3.4 → 1.3.6
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/services/install-agents-template.d.ts.map +1 -1
- package/dist/services/install-agents-template.js +18 -2
- package/dist/services/install-agents-template.js.map +1 -1
- package/dist/services/template-manifest.d.ts.map +1 -1
- package/dist/services/template-manifest.js +2 -0
- package/dist/services/template-manifest.js.map +1 -1
- package/package.json +1 -1
- package/templates/.agents/architecture/.gitkeep +0 -0
- package/templates/.agents/branding/.gitkeep +0 -0
- package/templates/.agents/dashboard/.zaip-managed.json +63 -51
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-BZWjXQB-.js → CostCalculationGuidePage-DwVZOmMV.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-D8qqeIPE.js → DashboardHelpDialog-3Lrfq645.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-Cm12_A0v.js → PricingSettingsDialog-CyjMXkm8.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-DxO9F8B4.js → ProjectSettingsPage-B_Oj6Dlf.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-_x0twOcS.js → TraceabilityWorkspace-diq9g1L8.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-C09KmlSw.js +68 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-CR4Fbf3g.js → icons-vendor-DXBOtWNI.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Deo_W81f.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-hPPfVGQv.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-DRJQbKnp.js → react-vendor-DaNXQPyk.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-CMrDYqjR.js → traceability-I4AKqVDX.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
- package/templates/.agents/dashboard/apps/frontend/src/App.tsx +43 -15
- package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +18 -3
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-capability-nav.css +11 -0
- package/templates/.agents/dashboard/apps/frontend/src/operations-period-contract.test.ts +14 -0
- package/templates/.agents/dashboard/apps/frontend/src/operations-period.test.ts +22 -7
- package/templates/.agents/dashboard/apps/frontend/src/operations-period.ts +11 -2
- package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +7 -4
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsFilteredStatusDialog.tsx +131 -0
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.helpers.tsx +2 -1
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +12 -4
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +69 -157
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +19 -4
- package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.test.ts +25 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.ts +29 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operations-coverage-toast.test.ts +3 -2
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +12 -2
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.status-cards.test.ts +13 -7
- package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-prerequisites.mjs +40 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-prerequisites.test.mjs +38 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +33 -26
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +5 -8
- package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-loading.spec.ts +4 -6
- package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-pagination.spec.ts +4 -0
- package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-period-transition.spec.ts +43 -17
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard-performance-matrix.spec.ts +9 -3
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +51 -22
- package/templates/.agents/growth/.gitkeep +0 -0
- package/templates/.agents/marketing/.gitkeep +0 -0
- package/templates/.agents/requirements/autonomous-assurance.md +1 -1
- package/templates/.agents/requirements/comparative-audit.md +1 -1
- package/templates/.agents/requirements/context-engine.md +1 -1
- package/templates/.agents/requirements/cost-observability.md +1 -1
- package/templates/.agents/requirements/dashboard-launch.md +1 -1
- package/templates/.agents/requirements/data-export.md +1 -1
- package/templates/.agents/requirements/design-system.md +1 -1
- package/templates/.agents/requirements/execution-engine.md +1 -1
- package/templates/.agents/requirements/kanban-dashboard.md +1 -1
- package/templates/.agents/requirements/model-orchestration.md +1 -1
- package/templates/.agents/requirements/public-distribution.md +1 -1
- package/templates/.agents/requirements/software-architecture.md +1 -1
- package/templates/.agents/requirements/strategic-improvements.md +1 -1
- package/templates/.agents/requirements/work-lifecycle.md +1 -1
- package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
- package/templates/.agents/sales/.gitkeep +0 -0
- package/templates/README.md +722 -0
- package/templates/template-manifest.json +96 -97
- package/templates/.agents/architecture/software-architecture.md +0 -131
- package/templates/.agents/branding/brandbook.docx +0 -0
- package/templates/.agents/branding/brandbook.md +0 -60
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-Cpp8xFij.js +0 -68
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-B31GMG5O.css +0 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-ncC1rxap.js +0 -2
- package/templates/.agents/growth/estrategia-bootstrap-90-dias.md +0 -169
- package/templates/.agents/growth/strategy.md +0 -27
- package/templates/.agents/marketing/estrategia-marketing-zaip-90-dias.md +0 -197
- package/templates/.agents/marketing/strategy.md +0 -29
- package/templates/.agents/sales/playbook.md +0 -32
- package/templates/.agents/sales/roteiro-comercial-zaip.md +0 -252
package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.test.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import {
|
|
3
3
|
dateRangeForPreset,
|
|
4
|
+
factualCostDateRange,
|
|
4
5
|
initialOperationsDateRange,
|
|
5
6
|
initialTaskDateRange,
|
|
6
7
|
isDateInOperationPreset,
|
|
@@ -34,6 +35,30 @@ describe("operation date ranges", () => {
|
|
|
34
35
|
});
|
|
35
36
|
});
|
|
36
37
|
|
|
38
|
+
it("uses the first factual Cost Monitor day for All Time while ending today", () => {
|
|
39
|
+
expect(
|
|
40
|
+
factualCostDateRange(
|
|
41
|
+
[
|
|
42
|
+
{ key: "2026-06-01", estimated_brl: null, estimated_usd: null },
|
|
43
|
+
{ key: "2026-06-02", estimated_brl: 0, estimated_usd: 0 },
|
|
44
|
+
{ key: "2026-06-03", estimated_brl: 1.25, estimated_usd: 0.24 },
|
|
45
|
+
],
|
|
46
|
+
{ start: "2026-07-10", end: "2026-07-14" },
|
|
47
|
+
today,
|
|
48
|
+
),
|
|
49
|
+
).toEqual({ start: "2026-06-02", end: "2026-07-14" });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("keeps the Operations range when no factual Cost Monitor day is available", () => {
|
|
53
|
+
expect(
|
|
54
|
+
factualCostDateRange(
|
|
55
|
+
[{ key: "2026-06-01", estimated_brl: null, estimated_usd: null }],
|
|
56
|
+
{ start: "2026-07-10", end: "2026-07-14" },
|
|
57
|
+
today,
|
|
58
|
+
),
|
|
59
|
+
).toEqual({ start: "2026-07-10", end: "2026-07-14" });
|
|
60
|
+
});
|
|
61
|
+
|
|
37
62
|
it.each([
|
|
38
63
|
["today", "2026-07-14", "2026-07-14"],
|
|
39
64
|
["yesterday", "2026-07-13", "2026-07-13"],
|
|
@@ -17,6 +17,35 @@ export interface OperationDateRange {
|
|
|
17
17
|
end: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** A daily Cost Monitor group with a factual monetary measurement. */
|
|
21
|
+
export interface FactualCostDay {
|
|
22
|
+
key: string;
|
|
23
|
+
estimated_brl: number | null;
|
|
24
|
+
estimated_usd: number | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* All Time is unbounded for requests, but its disabled date controls should
|
|
29
|
+
* describe the actual Cost Monitor coverage rather than an Operations record.
|
|
30
|
+
*/
|
|
31
|
+
export function factualCostDateRange(
|
|
32
|
+
days: readonly FactualCostDay[] | undefined,
|
|
33
|
+
fallback: OperationDateRange,
|
|
34
|
+
today = new Date(),
|
|
35
|
+
): OperationDateRange {
|
|
36
|
+
const firstMeasuredDay = days
|
|
37
|
+
?.map((day) => day.key)
|
|
38
|
+
.filter(
|
|
39
|
+
(key, index, keys) =>
|
|
40
|
+
/^\d{4}-\d{2}-\d{2}$/.test(key) &&
|
|
41
|
+
(days[index]!.estimated_brl !== null || days[index]!.estimated_usd !== null),
|
|
42
|
+
)
|
|
43
|
+
.sort()[0];
|
|
44
|
+
return firstMeasuredDay === undefined
|
|
45
|
+
? fallback
|
|
46
|
+
: { start: firstMeasuredDay, end: toDateInputValue(today) };
|
|
47
|
+
}
|
|
48
|
+
|
|
20
49
|
export function initialTaskDateRange(
|
|
21
50
|
tasks: readonly { created_at: string }[],
|
|
22
51
|
today = new Date(),
|
package/templates/.agents/dashboard/modules/operations/src/react/operations-coverage-toast.test.ts
CHANGED
|
@@ -36,7 +36,7 @@ describe("Operations persisted aggregate display", () => {
|
|
|
36
36
|
expect(totals).toMatchObject({ duration_ms: null, tokens: 0, cost_brl: 0 });
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
it("preserves unavailable aggregate totals
|
|
39
|
+
it("preserves unavailable aggregate totals and renders zero only for an empty population", () => {
|
|
40
40
|
const overview = readFileSync(new URL("OperationsOverview.tsx", import.meta.url), "utf8");
|
|
41
41
|
|
|
42
42
|
expect(overview).toContain("overviewMetrics?.duration_ms ??");
|
|
@@ -48,7 +48,8 @@ describe("Operations persisted aggregate display", () => {
|
|
|
48
48
|
expect(overview).toContain(
|
|
49
49
|
"durationMs: totalDurationMetric, tokens: totalTokensMetric, costBrl: totalCostMetric",
|
|
50
50
|
);
|
|
51
|
-
expect(overview).toContain("
|
|
51
|
+
expect(overview).toContain("formatSummaryMetricValue(\n averageDuration,");
|
|
52
|
+
expect(overview).toContain('populationCount === 0 ? "0"');
|
|
52
53
|
expect(overview).not.toContain("allPhaseMetrics.tokens ?? 0");
|
|
53
54
|
expect(overview).not.toContain("executionMetrics.durationMs ?? 0");
|
|
54
55
|
});
|
|
@@ -50,6 +50,15 @@ describe("overview period filter", () => {
|
|
|
50
50
|
expect(shouldRenderOperationsContent(true)).toBe(false);
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
+
it("synchronizes the controlled All Time display after factual cost coverage arrives", () => {
|
|
54
|
+
const source = readFileSync(new URL("OperationsPage.tsx", import.meta.url), "utf8");
|
|
55
|
+
|
|
56
|
+
expect(source).toContain('datePreset !== "all"');
|
|
57
|
+
expect(source).toContain("dateRange.start === defaultDateRange.start");
|
|
58
|
+
expect(source).toContain('updatePeriod("all", defaultDateRange);');
|
|
59
|
+
expect(source).toContain("requestPeriodForOperations still omits both bounds");
|
|
60
|
+
});
|
|
61
|
+
|
|
53
62
|
it("derives all big-number KPIs from the period-filtered global aggregate", () => {
|
|
54
63
|
const source = readFileSync(new URL("OperationsOverview.tsx", import.meta.url), "utf8");
|
|
55
64
|
const helpers = readFileSync(
|
|
@@ -81,8 +90,9 @@ describe("overview period filter", () => {
|
|
|
81
90
|
|
|
82
91
|
expect(source).toContain('currency: "BRL" | "USD"');
|
|
83
92
|
expect(source).toContain("const selectedCurrencyCost =");
|
|
84
|
-
expect(source).toContain("formatCurrency(
|
|
85
|
-
expect(source).toContain("
|
|
93
|
+
expect(source).toContain("formatCurrency(value, currency, locale)");
|
|
94
|
+
expect(source).toContain("formatSummaryMetricValue(\n totalCostMetric,");
|
|
95
|
+
expect(source).toContain("formatRoundedCurrency(value, currency, locale)");
|
|
86
96
|
expect(source).toContain("const topEntitiesCost =");
|
|
87
97
|
expect(topPanel).toContain("formatCurrency(topCost, currency, locale)");
|
|
88
98
|
expect(periods).toContain("resolveRunCost?: ((runId: string) => number | null)");
|
|
@@ -2,6 +2,10 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
3
|
|
|
4
4
|
const source = readFileSync(new URL("./OperationsOverview.tsx", import.meta.url), "utf8");
|
|
5
|
+
const dialogSource = readFileSync(
|
|
6
|
+
new URL("./OperationsFilteredStatusDialog.tsx", import.meta.url),
|
|
7
|
+
"utf8",
|
|
8
|
+
);
|
|
5
9
|
|
|
6
10
|
describe("OperationsOverview status cards", () => {
|
|
7
11
|
it("opens a filtered dialog for Tasks, SPECs, and PRDs", () => {
|
|
@@ -12,16 +16,18 @@ describe("OperationsOverview status cards", () => {
|
|
|
12
16
|
});
|
|
13
17
|
|
|
14
18
|
it("uses selected-type labels, closes before document navigation, and paginates", () => {
|
|
15
|
-
expect(
|
|
19
|
+
expect(dialogSource).toContain("onOpenDocument(item.kind, item.id)");
|
|
16
20
|
expect(source).toContain("setSelectedStatus(null);");
|
|
17
21
|
expect(source).toContain('"Status das tarefas"');
|
|
18
22
|
expect(source).toContain('"Status das SPECs"');
|
|
19
23
|
expect(source).toContain('"Status dos PRDs"');
|
|
20
|
-
expect(
|
|
21
|
-
expect(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
expect(
|
|
25
|
-
expect(
|
|
24
|
+
expect(dialogSource).toContain("const total = page?.total ?? items.length;");
|
|
25
|
+
expect(dialogSource).toContain(
|
|
26
|
+
"aria-label={`" + "$" + "{label} — $" + "{total} $" + "{taskLabel}`}",
|
|
27
|
+
);
|
|
28
|
+
expect(dialogSource).toContain("onLoadMore(items.length)");
|
|
29
|
+
expect(dialogSource).toContain('aria-busy="true"');
|
|
30
|
+
expect(dialogSource).toContain('role="alert"');
|
|
31
|
+
expect(dialogSource).toContain("Nenhum$" + '{kind === "task" ? "a" : ""}');
|
|
26
32
|
});
|
|
27
33
|
});
|
|
@@ -582,7 +582,7 @@ describe("dashboard backend", () => {
|
|
|
582
582
|
tasks: expect.any(Array),
|
|
583
583
|
runs: expect.any(Array),
|
|
584
584
|
});
|
|
585
|
-
});
|
|
585
|
+
}, 60_000);
|
|
586
586
|
it("serves only allowlisted canonical agent portraits", async () => {
|
|
587
587
|
const app = createDashboardApp(workspaceRoot, "test-session");
|
|
588
588
|
const agentIds = [
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { access, mkdir } from "node:fs/promises";
|
|
2
|
+
import { relative, resolve } from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
|
|
5
|
+
async function exists(path) {
|
|
6
|
+
return access(path).then(
|
|
7
|
+
() => true,
|
|
8
|
+
() => false,
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A fresh npm installation has no lifecycle history yet. The SQLite read
|
|
14
|
+
* model supports that empty state, so its canonical source root is created
|
|
15
|
+
* instead of requiring the development workspace to be installed again.
|
|
16
|
+
*/
|
|
17
|
+
export async function ensureDashboardRecordsDirectory(root) {
|
|
18
|
+
await mkdir(resolve(root, ".agents/records"), { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function assertDashboardPrerequisites(workspaceRoot) {
|
|
22
|
+
const major = Number.parseInt(process.versions.node.split(".")[0] ?? "", 10);
|
|
23
|
+
if (!Number.isInteger(major) || major < 22)
|
|
24
|
+
throw new Error(
|
|
25
|
+
`O Dashboard SQLite requer Node.js 22 ou superior; a versão encontrada foi ${process.versions.node}. Instale o Node.js 22 e tente novamente.`,
|
|
26
|
+
);
|
|
27
|
+
try {
|
|
28
|
+
await import("node:sqlite");
|
|
29
|
+
} catch {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"O Node.js instalado não disponibiliza node:sqlite. Reinstale uma versão suportada do Node.js 22 ou superior.",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
await ensureDashboardRecordsDirectory(workspaceRoot);
|
|
35
|
+
const required = resolve(workspaceRoot, ".agents/config");
|
|
36
|
+
if (!(await exists(required)))
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Pré-requisito do Dashboard SQLite ausente: ${relative(workspaceRoot, required) || required}. Execute corepack pnpm install na raiz do ZAIP-AIOS e tente novamente.`,
|
|
39
|
+
);
|
|
40
|
+
}
|
package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-prerequisites.test.mjs
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { access, mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
import {
|
|
6
|
+
assertDashboardPrerequisites,
|
|
7
|
+
ensureDashboardRecordsDirectory,
|
|
8
|
+
} from "./start-dashboard-prerequisites.mjs";
|
|
9
|
+
|
|
10
|
+
const temporaryDirectories = [];
|
|
11
|
+
|
|
12
|
+
afterEach(async () => {
|
|
13
|
+
await Promise.all(
|
|
14
|
+
temporaryDirectories.splice(0).map((path) => rm(path, { recursive: true, force: true })),
|
|
15
|
+
);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("dashboard SQLite prerequisites", () => {
|
|
19
|
+
it("creates the empty records root required by a fresh npm installation", async () => {
|
|
20
|
+
const root = await mkdtemp(join(tmpdir(), "zaip-dashboard-empty-records-"));
|
|
21
|
+
temporaryDirectories.push(root);
|
|
22
|
+
|
|
23
|
+
await expect(access(join(root, ".agents", "records"))).rejects.toMatchObject({
|
|
24
|
+
code: "ENOENT",
|
|
25
|
+
});
|
|
26
|
+
await ensureDashboardRecordsDirectory(root);
|
|
27
|
+
await expect(access(join(root, ".agents", "records"))).resolves.toBeUndefined();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("accepts a zeroed workspace after creating its records root", async () => {
|
|
31
|
+
const root = await mkdtemp(join(tmpdir(), "zaip-dashboard-prerequisites-"));
|
|
32
|
+
temporaryDirectories.push(root);
|
|
33
|
+
await mkdir(join(root, ".agents", "config"), { recursive: true });
|
|
34
|
+
|
|
35
|
+
await expect(assertDashboardPrerequisites(root)).resolves.toBeUndefined();
|
|
36
|
+
await expect(access(join(root, ".agents", "records"))).resolves.toBeUndefined();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
presentLaunchFailure,
|
|
26
26
|
writeLauncherStatus,
|
|
27
27
|
} from "./start-dashboard-lifecycle.mjs";
|
|
28
|
+
import { assertDashboardPrerequisites } from "./start-dashboard-prerequisites.mjs";
|
|
28
29
|
import {
|
|
29
30
|
isProcessAlive,
|
|
30
31
|
stopOwnedDashboardProcess,
|
|
@@ -171,30 +172,7 @@ export function formatLauncherError(value, maximumLength = 3_600) {
|
|
|
171
172
|
const lastHalf = maximumLength - firstHalf;
|
|
172
173
|
return `${message.slice(0, firstHalf)}\n\n… detalhes reduzidos; consulte o log temporário …\n\n${message.slice(-lastHalf)}`;
|
|
173
174
|
}
|
|
174
|
-
|
|
175
|
-
const major = Number.parseInt(process.versions.node.split(".")[0] ?? "", 10);
|
|
176
|
-
if (!Number.isInteger(major) || major < 22)
|
|
177
|
-
throw new Error(
|
|
178
|
-
`O Dashboard SQLite requer Node.js 22 ou superior; a versão encontrada foi ${process.versions.node}. Instale o Node.js 22 e tente novamente.`,
|
|
179
|
-
);
|
|
180
|
-
try {
|
|
181
|
-
await import("node:sqlite");
|
|
182
|
-
} catch {
|
|
183
|
-
throw new Error(
|
|
184
|
-
"O Node.js instalado não disponibiliza node:sqlite. Reinstale uma versão suportada do Node.js 22 ou superior.",
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
for (const required of [
|
|
188
|
-
resolve(workspaceRoot, ".agents/records"),
|
|
189
|
-
resolve(workspaceRoot, ".agents/config"),
|
|
190
|
-
resolve(workspaceRoot, "node_modules/esbuild"),
|
|
191
|
-
]) {
|
|
192
|
-
if (!(await exists(required)))
|
|
193
|
-
throw new Error(
|
|
194
|
-
`Pré-requisito do Dashboard SQLite ausente: ${relative(workspaceRoot, required) || required}. Execute corepack pnpm install na raiz do ZAIP-AIOS e tente novamente.`,
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
175
|
+
|
|
198
176
|
export async function readDashboardSession(path = sessionPath) {
|
|
199
177
|
try {
|
|
200
178
|
return JSON.parse(await readFile(path, "utf8"));
|
|
@@ -457,6 +435,32 @@ export async function refreshDashboardBuildTargets({
|
|
|
457
435
|
return { artifacts: Object.values(targets).flatMap((target) => target.artifacts), builtTargets };
|
|
458
436
|
}
|
|
459
437
|
async function ensureFreshDashboardBuild(onProgress = () => {}) {
|
|
438
|
+
// npm consumers receive a ready-to-run server and frontend bundle. They do
|
|
439
|
+
// not receive development-only esbuild, so never turn the first dashboard
|
|
440
|
+
// click into a pnpm-install requirement when that packaged runtime exists.
|
|
441
|
+
const buildTool = resolve(workspaceRoot, "node_modules/esbuild");
|
|
442
|
+
if (!(await exists(buildTool))) {
|
|
443
|
+
const packagedArtifacts = [installedServer, resolve(frontendRoot, "index.html")];
|
|
444
|
+
if (
|
|
445
|
+
await Promise.all(packagedArtifacts.map((path) => exists(path))).then((entries) =>
|
|
446
|
+
entries.every(Boolean),
|
|
447
|
+
)
|
|
448
|
+
) {
|
|
449
|
+
buildProgress(
|
|
450
|
+
onProgress,
|
|
451
|
+
39,
|
|
452
|
+
"Usando runtime empacotado",
|
|
453
|
+
"O pacote npm já inclui o dashboard pronto; nenhuma instalação de desenvolvimento é necessária.",
|
|
454
|
+
);
|
|
455
|
+
const signature = await Promise.all(
|
|
456
|
+
[...packagedArtifacts, resolve(packageRoot, "start-dashboard.mjs")].map(async (path) => {
|
|
457
|
+
const details = await stat(path);
|
|
458
|
+
return `${path}:${details.size}:${details.mtimeMs}`;
|
|
459
|
+
}),
|
|
460
|
+
);
|
|
461
|
+
return createHash("sha256").update(signature.join("|")).digest("hex");
|
|
462
|
+
}
|
|
463
|
+
}
|
|
460
464
|
const { artifacts } = await refreshDashboardBuildTargets({ onProgress });
|
|
461
465
|
const artifactFingerprint = await Promise.all(
|
|
462
466
|
[...artifacts, resolve(packageRoot, "start-dashboard.mjs")].map(async (path) => {
|
|
@@ -626,7 +630,10 @@ async function redirectLaunchPage(launchPage, target) {
|
|
|
626
630
|
launchPage.closeAfterHandoffGrace();
|
|
627
631
|
}
|
|
628
632
|
async function assertRuntimeReady() {
|
|
629
|
-
const serverPath =
|
|
633
|
+
const serverPath =
|
|
634
|
+
(await exists(resolve(workspaceRoot, "node_modules/esbuild"))) && (await exists(sourceServer))
|
|
635
|
+
? sourceServer
|
|
636
|
+
: installedServer;
|
|
630
637
|
if (!(await exists(serverPath))) {
|
|
631
638
|
throw new Error(
|
|
632
639
|
"Dashboard server bundle is missing; run the ZAIP release build or reinstall ZAIP-AIOS",
|
|
@@ -854,7 +861,7 @@ async function main() {
|
|
|
854
861
|
detail:
|
|
855
862
|
"Verificando Node.js 22 e o suporte a node:sqlite; isto não instala um banco de dados.",
|
|
856
863
|
});
|
|
857
|
-
await assertDashboardPrerequisites();
|
|
864
|
+
await assertDashboardPrerequisites(workspaceRoot);
|
|
858
865
|
launchPage.setProgress({
|
|
859
866
|
percent: 18,
|
|
860
867
|
phase: "Preparando a sessão do dashboard",
|
|
@@ -5,7 +5,6 @@ import { tmpdir } from "node:os";
|
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { afterEach, describe, expect, it } from "vitest";
|
|
7
7
|
import {
|
|
8
|
-
assertDashboardPrerequisites,
|
|
9
8
|
browserLaunchCommands,
|
|
10
9
|
findAvailablePort,
|
|
11
10
|
findAvailablePortWithFallback,
|
|
@@ -56,17 +55,15 @@ describe("dashboard executable session", () => {
|
|
|
56
55
|
expect(stripTerminalControlCodes(colored)).toBe("Missing export");
|
|
57
56
|
});
|
|
58
57
|
|
|
59
|
-
it("shows preparation before validating
|
|
58
|
+
it("shows preparation before validating dashboard prerequisites", async () => {
|
|
60
59
|
const source = await readFile(new URL("./start-dashboard.mjs", import.meta.url), "utf8");
|
|
61
60
|
|
|
62
|
-
expect(source).toContain(
|
|
63
|
-
expect(source).toContain(
|
|
64
|
-
expect(source).
|
|
65
|
-
expect(source).toContain("Node.js 22 ou superior");
|
|
61
|
+
expect(source).toContain('from "./start-dashboard-prerequisites.mjs";');
|
|
62
|
+
expect(source).toContain("await assertDashboardPrerequisites(workspaceRoot);");
|
|
63
|
+
expect(source).toContain("Usando runtime empacotado");
|
|
66
64
|
expect(source.indexOf("await openBrowser(dashboardUrl(launchPort, token))")).toBeLessThan(
|
|
67
|
-
source.indexOf("await assertDashboardPrerequisites();"),
|
|
65
|
+
source.indexOf("await assertDashboardPrerequisites(workspaceRoot);"),
|
|
68
66
|
);
|
|
69
|
-
await expect(assertDashboardPrerequisites()).resolves.toBeUndefined();
|
|
70
67
|
});
|
|
71
68
|
|
|
72
69
|
it("keeps the beginning and end of an oversized launcher error", () => {
|
|
@@ -50,7 +50,7 @@ test("releases an aborted Cost group load and keeps Operation reachable", async
|
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
await openDashboard(page);
|
|
53
|
-
await
|
|
53
|
+
await expectGlobalAllTimePreset(page);
|
|
54
54
|
const capabilityNav = page.getByRole("navigation", { name: "Áreas do dashboard" });
|
|
55
55
|
await capabilityNav.getByRole("button", { name: /Monitor de Custos/ }).click();
|
|
56
56
|
const costTabs = page.locator(".cost-tabs");
|
|
@@ -87,7 +87,7 @@ test("loads every Cost Monitor tab before returning to Operation", async ({ page
|
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
await openDashboard(page);
|
|
90
|
-
await
|
|
90
|
+
await expectGlobalAllTimePreset(page);
|
|
91
91
|
const capabilityNav = page.getByRole("navigation", { name: "Áreas do dashboard" });
|
|
92
92
|
await capabilityNav.getByRole("button", { name: /Monitor de Custos/ }).click();
|
|
93
93
|
const costTabs = page.locator(".cost-tabs");
|
|
@@ -124,10 +124,8 @@ test("loads every Cost Monitor tab before returning to Operation", async ({ page
|
|
|
124
124
|
expect(failedApiResponses).toEqual([]);
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
-
async function
|
|
128
|
-
await expect(
|
|
129
|
-
page.locator(".dashboard-global-controls").getByRole("combobox", { name: "Período" }),
|
|
130
|
-
).toHaveValue("this-month");
|
|
127
|
+
async function expectGlobalAllTimePreset(page: import("@playwright/test").Page) {
|
|
128
|
+
await expect(page.locator(".dashboard-global-controls").getByLabel("Período")).toHaveValue("all");
|
|
131
129
|
}
|
|
132
130
|
|
|
133
131
|
async function openDashboard(page: import("@playwright/test").Page) {
|
|
@@ -156,6 +156,10 @@ async function openAllTimeCostMonitor(page: Page) {
|
|
|
156
156
|
);
|
|
157
157
|
await page.goto("/?token=e2e-token");
|
|
158
158
|
await operations;
|
|
159
|
+
await page
|
|
160
|
+
.getByRole("navigation", { name: "Áreas do dashboard" })
|
|
161
|
+
.getByRole("button", { name: /Operação/ })
|
|
162
|
+
.click();
|
|
159
163
|
const period = page
|
|
160
164
|
.locator(".dashboard-global-controls")
|
|
161
165
|
.getByRole("combobox", { name: "Período" });
|
|
@@ -8,12 +8,13 @@ test("refreshes Cost Monitor within five seconds when Todo o Período is selecte
|
|
|
8
8
|
page,
|
|
9
9
|
}) => {
|
|
10
10
|
await openDashboard(page);
|
|
11
|
+
await selectThisMonth(page);
|
|
11
12
|
await openCostMonitor(page);
|
|
12
13
|
|
|
13
|
-
const
|
|
14
|
+
const summaryRequest = waitForCostSummaryRequest(page, "all");
|
|
14
15
|
const startedAt = Date.now();
|
|
15
16
|
await periodControl(page).selectOption("all");
|
|
16
|
-
await
|
|
17
|
+
await summaryRequest;
|
|
17
18
|
await expectCostMonitorReady(page);
|
|
18
19
|
|
|
19
20
|
expect(Date.now() - startedAt).toBeLessThan(fiveSeconds);
|
|
@@ -25,22 +26,29 @@ test("loads Cost Monitor within five seconds when the user enters after choosing
|
|
|
25
26
|
}) => {
|
|
26
27
|
await openDashboard(page);
|
|
27
28
|
|
|
29
|
+
await page
|
|
30
|
+
.getByRole("navigation", { name: "Áreas do dashboard" })
|
|
31
|
+
.getByRole("button", { name: /Operação/ })
|
|
32
|
+
.click();
|
|
33
|
+
await expect(periodControl(page)).toBeVisible();
|
|
34
|
+
|
|
35
|
+
await selectThisMonth(page);
|
|
36
|
+
const summary = waitForCostSummary(page, "all");
|
|
28
37
|
await periodControl(page).selectOption("all");
|
|
38
|
+
await summary;
|
|
29
39
|
await expect(page.locator(".period-transition-overlay")).toHaveCount(0, { timeout: 60_000 });
|
|
30
40
|
|
|
31
|
-
const summary = waitForCostSummary(page, "all");
|
|
32
41
|
const startedAt = Date.now();
|
|
33
42
|
await page
|
|
34
43
|
.getByRole("navigation", { name: "Áreas do dashboard" })
|
|
35
44
|
.getByRole("button", { name: /Monitor de Custos/ })
|
|
36
45
|
.click();
|
|
37
|
-
await summary;
|
|
38
46
|
await expectCostMonitorReady(page);
|
|
39
47
|
|
|
40
48
|
expect(Date.now() - startedAt).toBeLessThan(fiveSeconds);
|
|
41
49
|
});
|
|
42
50
|
|
|
43
|
-
test("
|
|
51
|
+
test("retries a summary 503 once when Cost Monitor opens and does not loop", async ({ page }) => {
|
|
44
52
|
let summaries = 0;
|
|
45
53
|
await page.route("**/api/v2/cost-monitor**", async (route) => {
|
|
46
54
|
const url = new URL(route.request().url());
|
|
@@ -56,16 +64,10 @@ test("shows one retry after a summary 503 and does not retry in a loop", async (
|
|
|
56
64
|
});
|
|
57
65
|
|
|
58
66
|
await openDashboard(page);
|
|
59
|
-
await openCostMonitor(page
|
|
60
|
-
const retry = page.locator(".app-loading-panel-error").getByRole("button", {
|
|
61
|
-
name: "Tentar novamente",
|
|
62
|
-
});
|
|
63
|
-
await expect(retry).toBeVisible();
|
|
64
|
-
|
|
65
|
-
await retry.click();
|
|
66
|
-
await expectCostMonitorReady(page);
|
|
67
|
+
await openCostMonitor(page);
|
|
67
68
|
await page.waitForTimeout(750);
|
|
68
69
|
expect(summaries).toBe(2);
|
|
70
|
+
await expect(page.locator(".app-error")).toHaveCount(0);
|
|
69
71
|
});
|
|
70
72
|
|
|
71
73
|
test("does not let a late all-time summary overwrite the newer period", async ({ page }) => {
|
|
@@ -130,6 +132,7 @@ test("does not let a late all-time summary overwrite the newer period", async ({
|
|
|
130
132
|
});
|
|
131
133
|
|
|
132
134
|
await openDashboard(page);
|
|
135
|
+
await selectThisMonth(page);
|
|
133
136
|
await openCostMonitor(page);
|
|
134
137
|
await expect.poll(() => priorPeriodBody).not.toBeNull();
|
|
135
138
|
const staleBody = withStaleSummarySentinel(priorPeriodBody!);
|
|
@@ -176,17 +179,26 @@ async function openDashboard(page: Page) {
|
|
|
176
179
|
await operations;
|
|
177
180
|
}
|
|
178
181
|
|
|
179
|
-
async function openCostMonitor(page: Page
|
|
180
|
-
const summary = expectReady ? waitForCostSummary(page, "this-month") : null;
|
|
182
|
+
async function openCostMonitor(page: Page) {
|
|
181
183
|
await page
|
|
182
184
|
.getByRole("navigation", { name: "Áreas do dashboard" })
|
|
183
185
|
.getByRole("button", { name: /Monitor de Custos/ })
|
|
184
186
|
.click();
|
|
185
|
-
if (!expectReady) return;
|
|
186
|
-
await summary;
|
|
187
187
|
await expectCostMonitorReady(page);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
async function selectThisMonth(page: Page) {
|
|
191
|
+
await page
|
|
192
|
+
.getByRole("navigation", { name: "Áreas do dashboard" })
|
|
193
|
+
.getByRole("button", { name: /Operação/ })
|
|
194
|
+
.click();
|
|
195
|
+
const summary =
|
|
196
|
+
(await page.locator(".cost-tabs").count()) > 0 ? waitForCostSummary(page, "this-month") : null;
|
|
197
|
+
await periodControl(page).selectOption("this-month");
|
|
198
|
+
await summary;
|
|
199
|
+
await expect(page.locator(".period-transition-overlay")).toHaveCount(0, { timeout: 60_000 });
|
|
200
|
+
}
|
|
201
|
+
|
|
190
202
|
function periodControl(page: Page) {
|
|
191
203
|
return page.locator(".dashboard-global-controls").getByRole("combobox", { name: "Período" });
|
|
192
204
|
}
|
|
@@ -203,6 +215,20 @@ function waitForCostSummary(page: Page, preset: "all" | "this-month") {
|
|
|
203
215
|
});
|
|
204
216
|
}
|
|
205
217
|
|
|
218
|
+
function waitForCostSummaryRequest(page: Page, preset: "all" | "this-month") {
|
|
219
|
+
return page.waitForRequest(
|
|
220
|
+
(request) => {
|
|
221
|
+
const url = new URL(request.url());
|
|
222
|
+
if (url.pathname !== COST_MONITOR_PATH || url.searchParams.get("scope") !== "summary")
|
|
223
|
+
return false;
|
|
224
|
+
return preset === "all"
|
|
225
|
+
? !url.searchParams.has("from") && !url.searchParams.has("to")
|
|
226
|
+
: url.searchParams.has("from") && url.searchParams.has("to");
|
|
227
|
+
},
|
|
228
|
+
{ timeout: 10_000 },
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
206
232
|
async function expectCostMonitorReady(page: Page) {
|
|
207
233
|
await expect(page.locator(".cost-tabs")).toBeVisible({ timeout: fiveSeconds });
|
|
208
234
|
const dailyTable = page.locator(".cost-content-daily table");
|
|
@@ -174,6 +174,10 @@ async function openDashboard(page: Page) {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
async function selectPeriod(page: Page, period: "this-month" | "all") {
|
|
177
|
+
await page
|
|
178
|
+
.getByRole("navigation", { name: "Áreas do dashboard" })
|
|
179
|
+
.getByRole("button", { name: /Operação/ })
|
|
180
|
+
.click();
|
|
177
181
|
const control = page
|
|
178
182
|
.locator(".dashboard-global-controls")
|
|
179
183
|
.getByRole("combobox", { name: "Período" });
|
|
@@ -290,9 +294,11 @@ async function exerciseOperations(
|
|
|
290
294
|
},
|
|
291
295
|
successfulResponses,
|
|
292
296
|
content === ".operations-work-board"
|
|
293
|
-
?
|
|
294
|
-
|
|
295
|
-
)
|
|
297
|
+
? kind === "Tudo"
|
|
298
|
+
? undefined
|
|
299
|
+
: boardStages.map((stage) =>
|
|
300
|
+
operationSelectionResponseMatcher(content, kind, expectedPeriod, stage),
|
|
301
|
+
)
|
|
296
302
|
: [selectionResponse],
|
|
297
303
|
);
|
|
298
304
|
}
|