@zaip-ai/cli 1.3.5 → 1.3.7
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/README.md +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +7 -0
- package/dist/commands/init.js.map +1 -1
- 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/sqlite-bootstrap.d.ts +29 -0
- package/dist/services/sqlite-bootstrap.d.ts.map +1 -0
- package/dist/services/sqlite-bootstrap.js +141 -0
- package/dist/services/sqlite-bootstrap.js.map +1 -0
- 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 +2 -2
- package/templates/.agents/architecture/.gitkeep +0 -0
- package/templates/.agents/branding/.gitkeep +0 -0
- package/templates/.agents/dashboard/.zaip-managed.json +51 -39
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-D1mpyYwh.js → CostCalculationGuidePage-DwVZOmMV.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-CKO47D5s.js → DashboardHelpDialog--czwAlcH.js} +723 -723
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-BuSlCbyZ.js → PricingSettingsDialog-CyjMXkm8.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-CUHFkAhZ.js → ProjectSettingsPage-B_Oj6Dlf.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-BmASq2gW.js → TraceabilityWorkspace-CAqYVin1.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{cost-monitor-Csj6LiAt.js → cost-monitor-C09KmlSw.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-VyTTl_ph.js → icons-vendor-DXBOtWNI.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-CblPpexd.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-CTC6vtln.js → react-vendor-DaNXQPyk.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-9Mtvbwrg.js → traceability-I4AKqVDX.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +5 -5
- package/templates/.agents/dashboard/apps/frontend/src/App.tsx +17 -8
- package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +8 -3
- package/templates/.agents/dashboard/apps/frontend/src/operations-period.test.ts +19 -1
- package/templates/.agents/dashboard/apps/frontend/src/operations-period.ts +8 -0
- 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.tsx +2 -133
- 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 +46 -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-period-transition.spec.ts +37 -17
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard-performance-matrix.spec.ts +5 -3
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +12 -5
- 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 +84 -85
- 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/index-DIwDvCu5.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/OperationsFilteredStatusDialog.tsx
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { DashboardTaskProjection, MacroProjection } from "../contracts.js";
|
|
2
|
+
import type { OverviewStatusKind, OverviewStatusPage } from "./operations-page-contract.js";
|
|
3
|
+
import { type OperationsLocale, riskLabel } from "./status-labels.js";
|
|
4
|
+
|
|
5
|
+
export function OperationsFilteredStatusDialog({
|
|
6
|
+
eyebrow,
|
|
7
|
+
label,
|
|
8
|
+
locale,
|
|
9
|
+
onClose,
|
|
10
|
+
onOpenTask,
|
|
11
|
+
onOpenDocument,
|
|
12
|
+
items,
|
|
13
|
+
state,
|
|
14
|
+
kind = "task",
|
|
15
|
+
onLoadMore,
|
|
16
|
+
page,
|
|
17
|
+
}: {
|
|
18
|
+
eyebrow: string;
|
|
19
|
+
label: string;
|
|
20
|
+
locale: OperationsLocale;
|
|
21
|
+
onClose: () => void;
|
|
22
|
+
onOpenTask: (taskId: string) => void;
|
|
23
|
+
onOpenDocument: (kind: "prd" | "spec", id: string) => void;
|
|
24
|
+
items: readonly (DashboardTaskProjection | MacroProjection)[];
|
|
25
|
+
state?: { loading: boolean; error: string | null } | undefined;
|
|
26
|
+
kind?: OverviewStatusKind;
|
|
27
|
+
onLoadMore?: ((offset: number) => void) | undefined;
|
|
28
|
+
page?: OverviewStatusPage | undefined;
|
|
29
|
+
}) {
|
|
30
|
+
const total = page?.total ?? items.length;
|
|
31
|
+
const taskLabel = statusTypeLabel(kind, total, locale);
|
|
32
|
+
return (
|
|
33
|
+
<dialog
|
|
34
|
+
className="operations-status-dialog-backdrop"
|
|
35
|
+
onMouseDown={(event) => {
|
|
36
|
+
if (event.currentTarget === event.target) onClose();
|
|
37
|
+
}}
|
|
38
|
+
open
|
|
39
|
+
>
|
|
40
|
+
<section
|
|
41
|
+
aria-label={`${label} — ${total} ${taskLabel}`}
|
|
42
|
+
aria-modal="true"
|
|
43
|
+
className="operations-status-dialog"
|
|
44
|
+
role="dialog"
|
|
45
|
+
>
|
|
46
|
+
<header>
|
|
47
|
+
<div>
|
|
48
|
+
<span>{eyebrow}</span>
|
|
49
|
+
<h2>{label}</h2>
|
|
50
|
+
<p>
|
|
51
|
+
{total} {taskLabel}
|
|
52
|
+
</p>
|
|
53
|
+
</div>
|
|
54
|
+
<button
|
|
55
|
+
aria-label={locale === "pt-BR" ? "Fechar" : "Close"}
|
|
56
|
+
onClick={onClose}
|
|
57
|
+
type="button"
|
|
58
|
+
>
|
|
59
|
+
×
|
|
60
|
+
</button>
|
|
61
|
+
</header>
|
|
62
|
+
<div className="operations-status-task-list">
|
|
63
|
+
{(page?.loading ?? state?.loading) && items.length === 0 ? (
|
|
64
|
+
<p aria-busy="true" aria-live="polite" className="operations-empty" role="status">
|
|
65
|
+
{locale === "pt-BR" ? "Carregando itens\u2026" : "Loading items\u2026"}
|
|
66
|
+
</p>
|
|
67
|
+
) : null}
|
|
68
|
+
{(page?.error ?? state?.error) ? (
|
|
69
|
+
<p aria-live="assertive" className="operations-empty" role="alert">
|
|
70
|
+
{page?.error ?? state?.error}
|
|
71
|
+
</p>
|
|
72
|
+
) : null}
|
|
73
|
+
{items.map((item) => (
|
|
74
|
+
<button
|
|
75
|
+
key={item.id}
|
|
76
|
+
onClick={() =>
|
|
77
|
+
"change_id" in item ? onOpenTask(item.id) : onOpenDocument(item.kind, item.id)
|
|
78
|
+
}
|
|
79
|
+
type="button"
|
|
80
|
+
>
|
|
81
|
+
<span className="operations-status-task-main">
|
|
82
|
+
<strong>{item.title}</strong>
|
|
83
|
+
<small>{item.id}</small>
|
|
84
|
+
</span>
|
|
85
|
+
<span
|
|
86
|
+
className={
|
|
87
|
+
"change_id" in item
|
|
88
|
+
? `operations-risk is-${item.risk}`
|
|
89
|
+
: "operations-work-kind is-macro"
|
|
90
|
+
}
|
|
91
|
+
>
|
|
92
|
+
{"change_id" in item ? riskLabel(item.risk, locale) : item.kind.toUpperCase()}
|
|
93
|
+
</span>
|
|
94
|
+
<span className="operations-status-task-action">
|
|
95
|
+
{locale === "pt-BR" ? "Ver detalhes" : "View details"} →
|
|
96
|
+
</span>
|
|
97
|
+
</button>
|
|
98
|
+
))}
|
|
99
|
+
{items.length === 0 && !(page?.loading ?? state?.loading) ? (
|
|
100
|
+
<p className="operations-empty">
|
|
101
|
+
{locale === "pt-BR"
|
|
102
|
+
? `Nenhum${kind === "task" ? "a" : ""} ${statusTypeLabel(kind, 1, locale)} neste status.`
|
|
103
|
+
: `No ${statusTypeLabel(kind, 2, locale)} in this status.`}
|
|
104
|
+
</p>
|
|
105
|
+
) : null}
|
|
106
|
+
{onLoadMore && items.length < (page?.total ?? 0) ? (
|
|
107
|
+
<button disabled={page?.loading} onClick={() => onLoadMore(items.length)} type="button">
|
|
108
|
+
{page?.loading
|
|
109
|
+
? locale === "pt-BR"
|
|
110
|
+
? "Carregando…"
|
|
111
|
+
: "Loading…"
|
|
112
|
+
: locale === "pt-BR"
|
|
113
|
+
? "Carregar mais"
|
|
114
|
+
: "Load more"}
|
|
115
|
+
</button>
|
|
116
|
+
) : null}
|
|
117
|
+
</div>
|
|
118
|
+
</section>
|
|
119
|
+
</dialog>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function statusTypeLabel(
|
|
124
|
+
kind: OverviewStatusKind,
|
|
125
|
+
count: number,
|
|
126
|
+
locale: OperationsLocale,
|
|
127
|
+
): string {
|
|
128
|
+
if (kind === "prd") return count === 1 ? "PRD" : "PRDs";
|
|
129
|
+
if (kind === "spec") return count === 1 ? "SPEC" : "SPECs";
|
|
130
|
+
return locale === "pt-BR" ? (count === 1 ? "tarefa" : "tarefas") : count === 1 ? "task" : "tasks";
|
|
131
|
+
}
|
|
@@ -6,12 +6,9 @@ import {
|
|
|
6
6
|
formatPercent,
|
|
7
7
|
} from "@zaip-ai/dashboard-ui";
|
|
8
8
|
import { type ReactNode, useState } from "react";
|
|
9
|
-
import type {
|
|
10
|
-
DashboardTaskProjection,
|
|
11
|
-
MacroProjection,
|
|
12
|
-
OperationsProjection,
|
|
13
|
-
} from "../contracts.js";
|
|
9
|
+
import type { MacroProjection, OperationsProjection } from "../contracts.js";
|
|
14
10
|
import { allPhaseMetricTotals, executionMetricTotals } from "./execution-metrics.js";
|
|
11
|
+
import { OperationsFilteredStatusDialog as FilteredStatusDialog } from "./OperationsFilteredStatusDialog.js";
|
|
15
12
|
import type { EntityMetric } from "./OperationsOverview.helpers.js";
|
|
16
13
|
import {
|
|
17
14
|
aggregateMacroMetrics,
|
|
@@ -915,134 +912,6 @@ export function OperationsOverview({
|
|
|
915
912
|
);
|
|
916
913
|
}
|
|
917
914
|
|
|
918
|
-
function FilteredStatusDialog({
|
|
919
|
-
eyebrow,
|
|
920
|
-
label,
|
|
921
|
-
locale,
|
|
922
|
-
onClose,
|
|
923
|
-
onOpenTask,
|
|
924
|
-
onOpenDocument,
|
|
925
|
-
items,
|
|
926
|
-
state,
|
|
927
|
-
kind = "task",
|
|
928
|
-
onLoadMore,
|
|
929
|
-
page,
|
|
930
|
-
}: {
|
|
931
|
-
eyebrow: string;
|
|
932
|
-
label: string;
|
|
933
|
-
locale: OperationsLocale;
|
|
934
|
-
onClose: () => void;
|
|
935
|
-
onOpenTask: (taskId: string) => void;
|
|
936
|
-
onOpenDocument: (kind: "prd" | "spec", id: string) => void;
|
|
937
|
-
items: readonly (DashboardTaskProjection | MacroProjection)[];
|
|
938
|
-
state?: { loading: boolean; error: string | null } | undefined;
|
|
939
|
-
kind?: OverviewStatusKind;
|
|
940
|
-
onLoadMore?: ((offset: number) => void) | undefined;
|
|
941
|
-
page?: OverviewStatusPage | undefined;
|
|
942
|
-
}) {
|
|
943
|
-
const total = page?.total ?? items.length;
|
|
944
|
-
const taskLabel = statusTypeLabel(kind, total, locale);
|
|
945
|
-
return (
|
|
946
|
-
<dialog
|
|
947
|
-
className="operations-status-dialog-backdrop"
|
|
948
|
-
onMouseDown={(event) => {
|
|
949
|
-
if (event.currentTarget === event.target) onClose();
|
|
950
|
-
}}
|
|
951
|
-
open
|
|
952
|
-
>
|
|
953
|
-
<section
|
|
954
|
-
aria-label={`${label} — ${total} ${taskLabel}`}
|
|
955
|
-
aria-modal="true"
|
|
956
|
-
className="operations-status-dialog"
|
|
957
|
-
role="dialog"
|
|
958
|
-
>
|
|
959
|
-
<header>
|
|
960
|
-
<div>
|
|
961
|
-
<span>{eyebrow}</span>
|
|
962
|
-
<h2>{label}</h2>
|
|
963
|
-
<p>
|
|
964
|
-
{total} {taskLabel}
|
|
965
|
-
</p>
|
|
966
|
-
</div>
|
|
967
|
-
<button
|
|
968
|
-
aria-label={locale === "pt-BR" ? "Fechar" : "Close"}
|
|
969
|
-
onClick={onClose}
|
|
970
|
-
type="button"
|
|
971
|
-
>
|
|
972
|
-
×
|
|
973
|
-
</button>
|
|
974
|
-
</header>
|
|
975
|
-
<div className="operations-status-task-list">
|
|
976
|
-
{(page?.loading ?? state?.loading) && items.length === 0 ? (
|
|
977
|
-
<p aria-busy="true" aria-live="polite" className="operations-empty" role="status">
|
|
978
|
-
{locale === "pt-BR" ? "Carregando itens\u2026" : "Loading items\u2026"}
|
|
979
|
-
</p>
|
|
980
|
-
) : null}
|
|
981
|
-
{(page?.error ?? state?.error) ? (
|
|
982
|
-
<p aria-live="assertive" className="operations-empty" role="alert">
|
|
983
|
-
{page?.error ?? state?.error}
|
|
984
|
-
</p>
|
|
985
|
-
) : null}
|
|
986
|
-
{items.map((item) => (
|
|
987
|
-
<button
|
|
988
|
-
key={item.id}
|
|
989
|
-
onClick={() =>
|
|
990
|
-
"change_id" in item ? onOpenTask(item.id) : onOpenDocument(item.kind, item.id)
|
|
991
|
-
}
|
|
992
|
-
type="button"
|
|
993
|
-
>
|
|
994
|
-
<span className="operations-status-task-main">
|
|
995
|
-
<strong>{item.title}</strong>
|
|
996
|
-
<small>{item.id}</small>
|
|
997
|
-
</span>
|
|
998
|
-
<span
|
|
999
|
-
className={
|
|
1000
|
-
"change_id" in item
|
|
1001
|
-
? `operations-risk is-${item.risk}`
|
|
1002
|
-
: "operations-work-kind is-macro"
|
|
1003
|
-
}
|
|
1004
|
-
>
|
|
1005
|
-
{"change_id" in item ? riskLabel(item.risk, locale) : item.kind.toUpperCase()}
|
|
1006
|
-
</span>
|
|
1007
|
-
<span className="operations-status-task-action">
|
|
1008
|
-
{locale === "pt-BR" ? "Ver detalhes" : "View details"} →
|
|
1009
|
-
</span>
|
|
1010
|
-
</button>
|
|
1011
|
-
))}
|
|
1012
|
-
{items.length === 0 && !(page?.loading ?? state?.loading) ? (
|
|
1013
|
-
<p className="operations-empty">
|
|
1014
|
-
{locale === "pt-BR"
|
|
1015
|
-
? `Nenhum${kind === "task" ? "a" : ""} ${statusTypeLabel(kind, 1, locale)} neste status.`
|
|
1016
|
-
: `No ${statusTypeLabel(kind, 2, locale)} in this status.`}
|
|
1017
|
-
</p>
|
|
1018
|
-
) : null}
|
|
1019
|
-
{onLoadMore && items.length < (page?.total ?? 0) ? (
|
|
1020
|
-
<button disabled={page?.loading} onClick={() => onLoadMore(items.length)} type="button">
|
|
1021
|
-
{page?.loading
|
|
1022
|
-
? locale === "pt-BR"
|
|
1023
|
-
? "Carregando…"
|
|
1024
|
-
: "Loading…"
|
|
1025
|
-
: locale === "pt-BR"
|
|
1026
|
-
? "Carregar mais"
|
|
1027
|
-
: "Load more"}
|
|
1028
|
-
</button>
|
|
1029
|
-
) : null}
|
|
1030
|
-
</div>
|
|
1031
|
-
</section>
|
|
1032
|
-
</dialog>
|
|
1033
|
-
);
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
function statusTypeLabel(
|
|
1037
|
-
kind: OverviewStatusKind,
|
|
1038
|
-
count: number,
|
|
1039
|
-
locale: OperationsLocale,
|
|
1040
|
-
): string {
|
|
1041
|
-
if (kind === "prd") return count === 1 ? "PRD" : "PRDs";
|
|
1042
|
-
if (kind === "spec") return count === 1 ? "SPEC" : "SPECs";
|
|
1043
|
-
return locale === "pt-BR" ? (count === 1 ? "tarefa" : "tarefas") : count === 1 ? "task" : "tasks";
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
915
|
function PanelHeader({ title, hint, action }: { title: string; hint: string; action?: ReactNode }) {
|
|
1047
916
|
return (
|
|
1048
917
|
<header className="operations-panel-header">
|
|
@@ -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, minor] = process.versions.node.split(".").map(Number);
|
|
23
|
+
if (!Number.isInteger(major) || major < 22 || (major === 22 && minor < 16))
|
|
24
|
+
throw new Error(
|
|
25
|
+
`O Dashboard SQLite requer Node.js 22.16.0 ou superior; a versão encontrada foi ${process.versions.node}. Atualize o Node.js 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 o Node.js 22.16.0 ou superior com suporte a SQLite.",
|
|
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 npx @zaip-ai/cli@latest init na raiz do projeto e tente novamente.`,
|
|
39
|
+
);
|
|
40
|
+
}
|
package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-prerequisites.test.mjs
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
|
|
39
|
+
it("directs incomplete consumers to init instead of a development install", async () => {
|
|
40
|
+
const root = await mkdtemp(join(tmpdir(), "zaip-dashboard-missing-config-"));
|
|
41
|
+
temporaryDirectories.push(root);
|
|
42
|
+
await expect(assertDashboardPrerequisites(root)).rejects.toThrow(
|
|
43
|
+
"npx @zaip-ai/cli@latest init",
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -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").getByLabel("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) {
|