@zaip-ai/cli 4.8.0 → 5.0.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.
- package/dist/services/adapter-hook-sources.d.ts +3 -0
- package/dist/services/adapter-hook-sources.d.ts.map +1 -1
- package/dist/services/adapter-hook-sources.js +3 -0
- package/dist/services/adapter-hook-sources.js.map +1 -1
- package/dist/services/adapters.d.ts +1 -1
- package/dist/services/adapters.d.ts.map +1 -1
- package/dist/services/adapters.js +5 -2
- package/dist/services/adapters.js.map +1 -1
- package/dist/services/adapters.test.js +1 -1
- package/dist/services/adapters.test.js.map +1 -1
- package/dist/services/codex-conversation-lifecycle-hook.d.ts.map +1 -1
- package/dist/services/codex-conversation-lifecycle-hook.js +16 -6
- package/dist/services/codex-conversation-lifecycle-hook.js.map +1 -1
- package/dist/services/codex-hook-activation.d.ts +1 -1
- package/dist/services/codex-hook-activation.d.ts.map +1 -1
- package/dist/services/codex-hook-activation.js +116 -10
- package/dist/services/codex-hook-activation.js.map +1 -1
- package/dist/services/codex-pre-tool-cli-capture-template.d.ts +2 -0
- package/dist/services/codex-pre-tool-cli-capture-template.d.ts.map +1 -0
- package/dist/services/codex-pre-tool-cli-capture-template.js +104 -0
- package/dist/services/codex-pre-tool-cli-capture-template.js.map +1 -0
- package/dist/services/codex-pre-tool-cloud-template.d.ts +2 -0
- package/dist/services/codex-pre-tool-cloud-template.d.ts.map +1 -0
- package/dist/services/codex-pre-tool-cloud-template.js +190 -0
- package/dist/services/codex-pre-tool-cloud-template.js.map +1 -0
- package/dist/services/codex-pre-tool-dependencies.test-support.d.ts +2 -0
- package/dist/services/codex-pre-tool-dependencies.test-support.d.ts.map +1 -0
- package/dist/services/codex-pre-tool-dependencies.test-support.js +9 -0
- package/dist/services/codex-pre-tool-dependencies.test-support.js.map +1 -0
- package/dist/services/codex-pre-tool-hook-fixture.test-support.d.ts +5 -0
- package/dist/services/codex-pre-tool-hook-fixture.test-support.d.ts.map +1 -0
- package/dist/services/codex-pre-tool-hook-fixture.test-support.js +19 -0
- package/dist/services/codex-pre-tool-hook-fixture.test-support.js.map +1 -0
- package/dist/services/codex-pre-tool-hook-template-bootstrap.test.d.ts +2 -0
- package/dist/services/codex-pre-tool-hook-template-bootstrap.test.d.ts.map +1 -0
- package/dist/services/codex-pre-tool-hook-template-bootstrap.test.js +107 -0
- package/dist/services/codex-pre-tool-hook-template-bootstrap.test.js.map +1 -0
- package/dist/services/codex-pre-tool-hook-template.d.ts.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.js +152 -190
- package/dist/services/codex-pre-tool-hook-template.js.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.test.js +63 -75
- package/dist/services/codex-pre-tool-hook-template.test.js.map +1 -1
- package/dist/services/codex-pre-tool-host-context-template.d.ts +2 -0
- package/dist/services/codex-pre-tool-host-context-template.d.ts.map +1 -0
- package/dist/services/codex-pre-tool-host-context-template.js +139 -0
- package/dist/services/codex-pre-tool-host-context-template.js.map +1 -0
- package/dist/services/codex-pre-tool-support-template.d.ts.map +1 -1
- package/dist/services/codex-pre-tool-support-template.js +77 -100
- package/dist/services/codex-pre-tool-support-template.js.map +1 -1
- package/dist/services/codex-subagent-hook-template.d.ts +1 -1
- package/dist/services/codex-subagent-hook-template.d.ts.map +1 -1
- package/dist/services/codex-subagent-hook-template.js +59 -15
- package/dist/services/codex-subagent-hook-template.js.map +1 -1
- package/dist/services/codex-validation-runtime-template.d.ts.map +1 -1
- package/dist/services/codex-validation-runtime-template.js +37 -10
- package/dist/services/codex-validation-runtime-template.js.map +1 -1
- package/dist/services/runtime-template-test-support.d.ts +1 -0
- package/dist/services/runtime-template-test-support.d.ts.map +1 -1
- package/dist/services/runtime-template-test-support.js +23 -2
- package/dist/services/runtime-template-test-support.js.map +1 -1
- package/dist/services/runtime-template.test.js +7 -24
- package/dist/services/runtime-template.test.js.map +1 -1
- package/package.json +1 -1
- package/templates/.agents/README-ENG.md +49 -7
- package/templates/.agents/README.md +41 -10
- package/templates/.agents/config/agents/discovery.json +1 -1
- package/templates/.agents/dashboard/.zaip-managed.json +125 -73
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-BW7Vy1cp.js → CostCalculationGuidePage-w6pjt4OM.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-BN8KQPrr.js → DashboardHelpDialog-DhlntQcR.js} +117 -17
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-Prcs-ydy.js → PricingSettingsDialog-BHoZT2an.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-HqGUImIG.js → ProjectSettingsPage-IcncIZ9Y.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-CNpmQMO4.js → TraceabilityWorkspace-C6rORjpr.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-uPnOBShc.js +68 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{dashboard-localization-CF7B1Hjn.js → dashboard-localization-DWXjEIgc.js} +49 -7
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-DOhy6D2-.js → icons-vendor-Hc9fp_gs.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{index-DX65Ufuu.js → index-BqeOEZW5.js} +2 -2
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-CkPC-yMz.js → react-vendor-C9ta_O10.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-BEdGoIDm.js → traceability-IcefoTOv.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +5 -5
- package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
- package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/contracts.ts +10 -3
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.run-page.test.ts +5 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostGroupsTable.period-values.test.tsx +2 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables-keeps-legacy-codex-identity-fields-blank.test.ts +5 -5
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +36 -43
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-loaders.ts +1 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-table-formatters.ts +6 -4
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/aggregate.ts +2 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.ts +1 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +11 -25
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/cost-summary-defaults.ts +26 -0
- package/templates/.agents/dashboard/modules/operations/package.json +1 -1
- package/templates/.agents/dashboard/modules/operations/src/contracts.ts +11 -16
- package/templates/.agents/dashboard/modules/operations/src/planning-contracts.ts +14 -0
- package/templates/.agents/dashboard/modules/operations/src/react/DecisionTable.tsx +65 -0
- package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.tsx +5 -71
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +10 -36
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +7 -2
- package/templates/.agents/dashboard/modules/operations/src/react/use-operations-filter-sync.ts +66 -0
- package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
- package/templates/.agents/dashboard/packages/branding/package.json +1 -1
- package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
- package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +229 -222
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +167 -167
- package/templates/.agents/dashboard/packages/local-runtime/package.json +3 -3
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +229 -222
- package/templates/.agents/dashboard/packages/local-runtime/src/active-capture-failure.test.ts +38 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/active-capture-failure.ts +35 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.test.ts +69 -6
- package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.ts +62 -15
- package/templates/.agents/dashboard/packages/local-runtime/src/manifest-runs.ts +8 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/server-capture-projection-order.test.ts +118 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-cost-run-search.test.ts +90 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-cost-run-search.ts +86 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-routes.ts +8 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/server-projection-loaders.ts +13 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-projections.ts +30 -74
- package/templates/.agents/dashboard/packages/local-runtime/src/server-route-handlers.ts +42 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-routes.test.ts +60 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-run-projections.test.ts +12 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-command.test.ts +29 -4
- package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.ts +7 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +51 -54
- package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +37 -48
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-filter-matrix.test.ts +5 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-page.test.ts +49 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-page.ts +44 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-cached-cost-page.ts +11 -10
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-summary.ts +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-work-item-index.test.ts +18 -1
- package/templates/.agents/dashboard/packages/local-runtime/vitest.config.ts +12 -0
- package/templates/.agents/dashboard/packages/ui/package.json +1 -1
- package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
- package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-pagination.spec.ts +13 -6
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +1 -1
- package/templates/.agents/design/.zaip-managed.json +3 -3
- package/templates/.agents/design/apps/catalog/package.json +1 -1
- package/templates/.agents/design/packages/local-runtime/package.json +1 -1
- package/templates/.agents/requirements/autonomous-assurance.md +39 -3
- package/templates/.agents/requirements/cloud-hostinger-autonomy.md +1 -1
- package/templates/.agents/requirements/comparative-audit.md +1 -1
- package/templates/.agents/requirements/context-engine.md +3 -3
- 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 +11 -2
- 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 +2 -1
- package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
- package/templates/.agents/rules/cloud-hostinger-autonomy.md +1 -1
- package/templates/.agents/rules/production-readiness.md +24 -0
- package/templates/.agents/rules/run-lifecycle-closure.md +2 -1
- package/templates/.agents/rules/security.md +2 -2
- package/templates/.agents/rules/workflow.md +4 -3
- package/templates/.agents/runtime/VERSION +1 -1
- package/templates/.agents/runtime/bin/zaip.mjs +1185 -610
- package/templates/.agents/runtime/checksums.sha256 +6 -6
- package/templates/.agents/runtime/manifest.json +1 -1
- package/templates/.agents/runtime/pnpm-lock.sha256 +1 -1
- package/templates/.agents/runtime/provenance.json +5 -5
- package/templates/.agents/runtime/sbom.cdx.json +71 -71
- package/templates/.agents/skills/zaip-skill-execution/SKILL.md +4 -2
- package/templates/.agents/skills/zaip-skill-execution/references/controlled-release-recovery.md +44 -0
- package/templates/.agents/skills/zaip-skill-execution/references/runtime-governance.md +1 -1
- package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +7 -1
- package/templates/.agents/skills/zaip-skill-github-deploy/references/release-gate-matrix.md +15 -20
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.mjs +38 -2
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.test.mjs +62 -4
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +8 -6
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/guarded-commits.mjs +5 -3
- package/templates/.agents/skills/zaip-skill-lifecycle-closure/SKILL.md +7 -0
- package/templates/.agents/skills/zaip-skill-maintenance-enter/SKILL.md +23 -0
- package/templates/.agents/skills/zaip-skill-maintenance-enter/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-maintenance-enter/scripts/maintenance.mjs +213 -0
- package/templates/.agents/skills/zaip-skill-maintenance-enter/scripts/maintenance.test.mjs +207 -0
- package/templates/.agents/skills/zaip-skill-maintenance-exit/SKILL.md +21 -0
- package/templates/.agents/skills/zaip-skill-maintenance-exit/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-prd/SKILL.md +6 -0
- package/templates/.agents/skills/zaip-skill-release-qualification/SKILL.md +26 -0
- package/templates/.agents/skills/zaip-skill-release-qualification/references/local-preparation.md +46 -11
- package/templates/.agents/skills/zaip-skill-spec/SKILL.md +9 -0
- package/templates/.agents/skills/zaip-skill-testing-strategy/SKILL.md +15 -0
- package/templates/.agents/zaip-aios/VERSION +1 -1
- package/templates/.agents/zaip-aios/guides/11-observabilidade-e-custos.md +22 -0
- package/templates/.agents/zaip-aios/guides/14-troubleshooting.md +1 -1
- package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +52 -5
- package/templates/template-manifest.json +210 -126
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-Co2Jiuzh.js +0 -68
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
export const CODEX_PRE_TOOL_SUPPORT_HOOK = String.raw `import {
|
|
2
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
-
import { link, lstat, mkdir, readFile, realpath, unlink, writeFile } from "node:fs/promises";
|
|
1
|
+
export const CODEX_PRE_TOOL_SUPPORT_HOOK = String.raw `import { lstat, readFile, realpath } from "node:fs/promises";
|
|
4
2
|
import { dirname, isAbsolute, relative, resolve, toNamespacedPath } from "node:path";
|
|
5
3
|
import { homedir } from "node:os";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
const execute = promisify(execFile);
|
|
4
|
+
import { createHostContext } from "./zaip-pre-tool-host-context.mjs";
|
|
5
|
+
import { createCliTurnCapture } from "./zaip-pre-tool-cli-capture.mjs";
|
|
9
6
|
export { readAuthorityRunRecords, checkDestructivePatterns, configuredDeploymentBranches } from "./zaip-native-tool-authority.mjs";
|
|
10
7
|
const pathIdentity = value => process.platform === "win32" ? toNamespacedPath(resolve(value)).toLowerCase() : resolve(value);
|
|
11
8
|
export const inside = (path, root) => {
|
|
@@ -13,51 +10,27 @@ export const inside = (path, root) => {
|
|
|
13
10
|
return scoped === "" || (!scoped.startsWith("..") && !isAbsolute(scoped));
|
|
14
11
|
};
|
|
15
12
|
|
|
16
|
-
export async function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (inside(cwd, installedRoot)) {
|
|
24
|
-
let cursor = resolve(cwd);
|
|
25
|
-
while (cursor.toLowerCase() !== installedRoot.toLowerCase() &&
|
|
26
|
-
!(await lstat(resolve(cursor, ".git")).catch(() => null))) cursor = dirname(cursor);
|
|
27
|
-
if (cursor.toLowerCase() === installedRoot.toLowerCase()) return installedRoot;
|
|
28
|
-
}
|
|
29
|
-
try {
|
|
30
|
-
const git = async (path, ...args) => (await execute("git", ["-C", path, ...args], {
|
|
31
|
-
encoding: "utf8", windowsHide: true, timeout: 5000, maxBuffer: 1048576,
|
|
32
|
-
})).stdout.trim();
|
|
33
|
-
const primary = await git(installedRoot, "rev-parse", "--show-toplevel");
|
|
34
|
-
if ((await realpath(primary)).toLowerCase() !== installedRoot.toLowerCase()) throw new Error("primary_not_root");
|
|
35
|
-
const worktrees = await git(installedRoot, "worktree", "list", "--porcelain", "-z");
|
|
36
|
-
const registered = worktrees.split("\0").filter(line => line.startsWith("worktree ")).map(line => line.slice(9));
|
|
37
|
-
const candidate = await realpath(await git(cwd, "rev-parse", "--show-toplevel"));
|
|
38
|
-
if (!registered.some(path => resolve(path).toLowerCase() === candidate.toLowerCase()) || !inside(cwd, candidate)) {
|
|
39
|
-
throw new Error("worktree_not_registered");
|
|
40
|
-
}
|
|
41
|
-
const common = async path => (await realpath(await git(path, "rev-parse", "--path-format=absolute", "--git-common-dir"))).toLowerCase();
|
|
42
|
-
if (await common(installedRoot) !== await common(candidate)) throw new Error("common_git_dir_mismatch");
|
|
43
|
-
return candidate;
|
|
44
|
-
} catch {
|
|
45
|
-
throw new Error("Tool workspace could not be verified as a registered worktree.");
|
|
46
|
-
}
|
|
13
|
+
export async function readBoundedCanonicalText(path, maximumBytes, authorityRoot) {
|
|
14
|
+
const info = await lstat(path);
|
|
15
|
+
if (!info.isFile() || info.isSymbolicLink() || info.size > maximumBytes) throw new Error("Unsafe Run lookup file");
|
|
16
|
+
const physical = await realpath(path);
|
|
17
|
+
const identity = value => process.platform === "win32" ? value.toLowerCase() : value;
|
|
18
|
+
if (!inside(physical, authorityRoot) || identity(physical) !== identity(resolve(path))) throw new Error("Run lookup must not follow links");
|
|
19
|
+
return readFile(path, "utf8");
|
|
47
20
|
}
|
|
48
21
|
|
|
49
|
-
export
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const workdir = ["exec_command", "shell_command", "Bash"].includes(event.tool_name) ? event.tool_input?.workdir : undefined;
|
|
53
|
-
if (workdir !== undefined && (typeof workdir !== "string" || !isAbsolute(workdir)))
|
|
54
|
-
throw new Error("Tool workdir must be an absolute literal workspace.");
|
|
55
|
-
const cwd = workdir ?? event.cwd;
|
|
56
|
-
return { cwd, repositoryRoot: await selectHookRepositoryRoot(installedRoot, cwd) };
|
|
22
|
+
export function parseHookEvent(raw, deny) {
|
|
23
|
+
try { return JSON.parse(raw); }
|
|
24
|
+
catch { deny("Malformed hook input was rejected."); }
|
|
57
25
|
}
|
|
58
26
|
|
|
27
|
+
export const { configuredPermissionProfile, selectHookRepositoryRoot, selectHookExecutionWorkspace } = createHostContext({ inside, readBoundedCanonicalText });
|
|
28
|
+
|
|
59
29
|
export function tokenizeLiteralCommand(command) {
|
|
60
30
|
if (command.includes("\r") || command.includes("\n")) return null;
|
|
31
|
+
// PowerShell's call operator invokes one literal executable; it grants no authority.
|
|
32
|
+
// All interpolation, composition and subsequent operators still fail below.
|
|
33
|
+
if (process.platform === "win32") command = command.replace(/^\s*&\s+/, "");
|
|
61
34
|
const tokens = [];
|
|
62
35
|
let token = "", quote = null, started = false;
|
|
63
36
|
for (let index = 0; index < command.length; index++) {
|
|
@@ -83,7 +56,7 @@ export function tokenizeLiteralCommand(command) {
|
|
|
83
56
|
}
|
|
84
57
|
if (quote !== null) return null;
|
|
85
58
|
if (started) tokens.push(token);
|
|
86
|
-
return tokens;
|
|
59
|
+
return tokens[0] === "" ? null : tokens;
|
|
87
60
|
}
|
|
88
61
|
|
|
89
62
|
export function shellSyntax(command) {
|
|
@@ -111,9 +84,12 @@ export function requiresShellMutationRun(command, tokens, cwd, repositoryRoot) {
|
|
|
111
84
|
const syntax = shellSyntax(command);
|
|
112
85
|
if (!syntax.interpolation && !syntax.redirection && !syntax.grouping && isLiteralReadSequence(command)) return false;
|
|
113
86
|
const executable = tokens?.[0] ?? "";
|
|
114
|
-
const canonicalCli = /^(?:node|node\.exe)$/i.test(executable)
|
|
87
|
+
const canonicalCli = /^(?:node|node\.exe)$/i.test(executable) || /[\\/]node(?:\.exe)?$/i.test(executable);
|
|
88
|
+
const canonicalRuntime = canonicalCli && tokens?.[1] &&
|
|
115
89
|
resolve(cwd, tokens[1]).toLowerCase() === resolve(repositoryRoot, ".agents/runtime/bin/zaip.mjs").toLowerCase();
|
|
116
|
-
|
|
90
|
+
if (canonicalRuntime && tokens?.[2] === "execution" && tokens?.[3] === "native-next" &&
|
|
91
|
+
tokens.includes("--reserve-causal")) return true;
|
|
92
|
+
const dataCommand = canonicalRuntime || /^(?:Write-Output|echo|printf)$/i.test(executable);
|
|
117
93
|
if (tokens && !syntax.interpolation && !syntax.grouping && dataCommand) {
|
|
118
94
|
return syntax.redirection;
|
|
119
95
|
}
|
|
@@ -124,6 +100,8 @@ export function requiresShellMutationRun(command, tokens, cwd, repositoryRoot) {
|
|
|
124
100
|
// Recognize only literal read-only pipelines/sequences. Quoted search text is data,
|
|
125
101
|
// not a PowerShell alias (for example the AC prefix in acceptance-criterion IDs).
|
|
126
102
|
export function isLiteralReadSequence(command) {
|
|
103
|
+
const syntax = shellSyntax(command);
|
|
104
|
+
if (syntax.interpolation || syntax.redirection || syntax.grouping) return false;
|
|
127
105
|
const parts = [];
|
|
128
106
|
let start = 0, quote = null;
|
|
129
107
|
for (let index = 0; index < command.length; index++) {
|
|
@@ -141,6 +119,20 @@ export function isLiteralReadSequence(command) {
|
|
|
141
119
|
return parts.every(part => {
|
|
142
120
|
const values = tokenizeLiteralCommand(part);
|
|
143
121
|
if (!values?.length) return false;
|
|
122
|
+
if (/^Get-Item$/i.test(values[0])) {
|
|
123
|
+
const args = values.slice(1);
|
|
124
|
+
const path = args[0] === "-LiteralPath" ? args[1] : args[0];
|
|
125
|
+
return args.length === (args[0] === "-LiteralPath" ? 2 : 1) &&
|
|
126
|
+
!!path && !/^(?:[a-z][\w-]*:)(?![\/])/i.test(path) && !/[*?]/.test(path);
|
|
127
|
+
}
|
|
128
|
+
if (/^Select-Object$/i.test(values[0])) {
|
|
129
|
+
const args = values.slice(1);
|
|
130
|
+
const metadata = /^(?:Name|FullName|Length|LastWriteTime|LastWriteTimeUtc|CreationTime|Attributes|IsReadOnly|LinkType|Target)(?:,(?:Name|FullName|Length|LastWriteTime|LastWriteTimeUtc|CreationTime|Attributes|IsReadOnly|LinkType|Target))*$/i;
|
|
131
|
+
return (args.length === 2 && args[0] === "-First" && /^\d+$/.test(args[1])) ||
|
|
132
|
+
(args.length === 2 && args[0] === "-ExpandProperty" && args[1] === "Target") ||
|
|
133
|
+
(args.length === 2 && args[0] === "-Property" && metadata.test(args[1])) ||
|
|
134
|
+
(args.length === 1 && metadata.test(args[0]));
|
|
135
|
+
}
|
|
144
136
|
if (/^(?:Get-Content|Measure-Object)$/i.test(values[0])) return true;
|
|
145
137
|
if (!/^(?:rg|rg\.exe)$/i.test(values[0])) return false;
|
|
146
138
|
return !values.slice(1).some(value => /^--(?:pre|hostname-bin)(?:=|$)/i.test(value));
|
|
@@ -294,7 +286,7 @@ export async function reviewedReleasePaths(command, cwd, repositoryRoot, credent
|
|
|
294
286
|
}
|
|
295
287
|
|
|
296
288
|
/** Narrow read capability, never a general external-path or executable allowlist. */
|
|
297
|
-
export async function reviewedContextReadPaths(command, credentialPattern) {
|
|
289
|
+
export async function reviewedContextReadPaths(command, credentialPattern, repositoryRoot) {
|
|
298
290
|
const tokens = tokenizeLiteralCommand(command);
|
|
299
291
|
if (!tokens || tokens.length < 2 || credentialPattern.test(command)) return [];
|
|
300
292
|
const executable = tokens[0].toLowerCase();
|
|
@@ -305,10 +297,28 @@ export async function reviewedContextReadPaths(command, credentialPattern) {
|
|
|
305
297
|
// -f means a pattern file in ripgrep, not fixed strings: do not permit it.
|
|
306
298
|
if (tokens.slice(1).some(token => token.startsWith("-") && (!flags.has(token.toLowerCase()) || token === "-f"))) return [];
|
|
307
299
|
const roots = ["memories", "skills", "plugins/cache"].map(part => resolve(homedir(), ".codex", part));
|
|
300
|
+
// Project-owned policy records explicitly authorized files, never executable
|
|
301
|
+
// commands or a blanket permission for the user's home/attachments directory.
|
|
302
|
+
let authorizedFiles = [];
|
|
303
|
+
if (repositoryRoot) {
|
|
304
|
+
const policyPath = resolve(repositoryRoot, ".agents/config/external-read-policy.json");
|
|
305
|
+
try {
|
|
306
|
+
const info = await lstat(policyPath);
|
|
307
|
+
if (!info.isFile() || info.isSymbolicLink() || info.size > 65536 ||
|
|
308
|
+
pathIdentity(await realpath(policyPath)) !== pathIdentity(policyPath)) return [];
|
|
309
|
+
const policy = JSON.parse(await readFile(policyPath, "utf8"));
|
|
310
|
+
if (policy?.schema_version !== 1 || !Array.isArray(policy.files) || policy.files.length > 128 ||
|
|
311
|
+
Object.keys(policy).sort().join(",") !== "files,schema_version" ||
|
|
312
|
+
policy.files.some(path => typeof path !== "string" || !isAbsolute(path) || hasTraversalSegment(path))) return [];
|
|
313
|
+
authorizedFiles = policy.files.map(pathIdentity);
|
|
314
|
+
} catch (error) { if (error.code !== "ENOENT") return []; }
|
|
315
|
+
}
|
|
308
316
|
const paths = tokens.slice(1).filter(value => isAbsolute(value));
|
|
309
317
|
if (!paths.length) return [];
|
|
310
318
|
for (const path of paths) {
|
|
311
|
-
if (
|
|
319
|
+
if (hasTraversalSegment(path) ||
|
|
320
|
+
(!roots.some(root => inside(path, root)) && !authorizedFiles.includes(pathIdentity(path))) ||
|
|
321
|
+
!/\.(?:md|txt|json|jsonl|yaml|yml|toml)$/i.test(path)) return [];
|
|
312
322
|
try {
|
|
313
323
|
const info = await lstat(path);
|
|
314
324
|
if (!info.isFile() || info.isSymbolicLink() || pathIdentity(await realpath(path)) !== pathIdentity(path)) return [];
|
|
@@ -317,62 +327,26 @@ export async function reviewedContextReadPaths(command, credentialPattern) {
|
|
|
317
327
|
return paths;
|
|
318
328
|
}
|
|
319
329
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (!tokens || tokens.length < 4 || !/^(?:node|node\.exe)$/i.test(tokens[0])) return;
|
|
323
|
-
const script = resolve(event.cwd, tokens[1]);
|
|
324
|
-
if (script.toLowerCase() !== resolve(repositoryRoot, ".agents/runtime/bin/zaip.mjs").toLowerCase()) return;
|
|
325
|
-
if (!((tokens[2] === "conversation" || tokens[2] === "run") && tokens[3] === "start")) return;
|
|
326
|
-
if (tokens.includes("--help") || tokens.includes("-h")) return;
|
|
327
|
-
if (tokens.some(token => token.startsWith("--codex-hook-"))) throw new Error("Codex hook identity cannot be supplied by the caller.");
|
|
328
|
-
const threadId = event.session_id, turnId = event.turn_id, toolUseId = event.tool_use_id;
|
|
329
|
-
if (typeof threadId !== "string" || !/^[A-Za-z0-9_-]{1,256}$/.test(threadId) ||
|
|
330
|
-
typeof turnId !== "string" || !turnId || turnId.length > 256 ||
|
|
331
|
-
typeof toolUseId !== "string" || !toolUseId || toolUseId.length > 256 ||
|
|
332
|
-
(process.env.CODEX_THREAD_ID && process.env.CODEX_THREAD_ID !== threadId) ||
|
|
333
|
-
(process.env.CODEX_TURN_ID && process.env.CODEX_TURN_ID !== turnId)) {
|
|
334
|
-
throw new Error("Codex thread, turn and tool identity could not be captured for the ZAIP CLI.");
|
|
335
|
-
}
|
|
336
|
-
const directory = resolve(repositoryRoot, ".local/codex-hook-turn/v1");
|
|
337
|
-
const argvSha256 = createHash("sha256").update(JSON.stringify(tokens.slice(2))).digest("hex");
|
|
338
|
-
const digest = createHash("sha256").update(JSON.stringify([threadId, toolUseId])).digest("hex");
|
|
339
|
-
const target = resolve(directory, digest + ".json");
|
|
340
|
-
const temporary = resolve(directory, digest + "." + randomUUID() + ".tmp");
|
|
341
|
-
try {
|
|
342
|
-
let parent = repositoryRoot;
|
|
343
|
-
for (const part of [".local", "codex-hook-turn", "v1"]) {
|
|
344
|
-
parent = resolve(parent, part);
|
|
345
|
-
try { await mkdir(parent); } catch (error) { if (error?.code !== "EEXIST") throw error; }
|
|
346
|
-
const info = await lstat(parent), physical = await realpath(parent);
|
|
347
|
-
if (!info.isDirectory() || info.isSymbolicLink() || !inside(physical, repositoryRoot)) throw new Error("Unsafe turn receipt directory");
|
|
348
|
-
}
|
|
349
|
-
const value = {
|
|
350
|
-
schema_version: 1, thread_id: threadId, turn_id: turnId,
|
|
351
|
-
tool_use_sha256: digest, argv_sha256: argvSha256, observed_at_ms: Date.now(),
|
|
352
|
-
};
|
|
353
|
-
await writeFile(temporary, JSON.stringify(value) + "\n", { flag: "wx", mode: 0o600 });
|
|
354
|
-
try { await link(temporary, target); }
|
|
355
|
-
catch (error) {
|
|
356
|
-
if (error.code !== "EEXIST") throw error;
|
|
357
|
-
const info = await lstat(target);
|
|
358
|
-
if (!info.isFile() || info.isSymbolicLink() || info.size > 4096 ||
|
|
359
|
-
pathIdentity(await realpath(target)) !== pathIdentity(target)) throw new Error("Unsafe existing turn receipt");
|
|
360
|
-
const prior = JSON.parse(await readFile(target, "utf8"));
|
|
361
|
-
for (const key of ["schema_version", "thread_id", "turn_id", "tool_use_sha256", "argv_sha256"])
|
|
362
|
-
if (prior[key] !== value[key]) throw new Error("Conflicting existing turn receipt");
|
|
363
|
-
if (!Number.isSafeInteger(prior.observed_at_ms) || prior.observed_at_ms > Date.now()) throw new Error("Invalid turn receipt time");
|
|
364
|
-
}
|
|
365
|
-
} catch {
|
|
366
|
-
throw new Error("Codex turn identity could not be persisted for the ZAIP CLI.");
|
|
367
|
-
} finally { await unlink(temporary).catch(() => {}); }
|
|
368
|
-
return command + " --codex-hook-id=" + digest + " --codex-hook-thread=" + threadId;
|
|
330
|
+
function hasTraversalSegment(path) {
|
|
331
|
+
return path.split(/[\\/]/).includes("..");
|
|
369
332
|
}
|
|
370
333
|
|
|
334
|
+
export const captureCliTurn = createCliTurnCapture({ inside, tokenizeLiteralCommand });
|
|
335
|
+
|
|
371
336
|
export function validateInlineExecution(command, deny) {
|
|
372
337
|
if (shellSyntax(command).interpolation) {
|
|
373
338
|
deny("Nested shell execution is blocked; use a reviewed repository script.");
|
|
374
339
|
}
|
|
375
340
|
if (/(?:^|[;&|\r\n])\s*&\s*\(/.test(command)) deny("Dynamic shell invocation is blocked; use a literal command.");
|
|
341
|
+
const tokens = tokenizeLiteralCommand(command);
|
|
342
|
+
const executable = tokens?.[0]?.split(/[\\/]/).at(-1) ?? "";
|
|
343
|
+
// Quoting and absolute paths do not change Node's inline-execution semantics.
|
|
344
|
+
// Stop at --, which makes subsequent arguments positional rather than options.
|
|
345
|
+
const argumentsBeforeDelimiter = tokens?.slice(1, tokens.includes("--") ? tokens.indexOf("--") : undefined) ?? [];
|
|
346
|
+
if (/^node(?:\.exe)?$/i.test(executable) &&
|
|
347
|
+
argumentsBeforeDelimiter.some(argument => /^(?:--(?:eval|print)(?:=|$)|-[ep](?:[ep]|$))/.test(argument))) {
|
|
348
|
+
deny("Inline or piped interpreter execution is blocked; use a reviewed repository script.");
|
|
349
|
+
}
|
|
376
350
|
const inlinePatterns = [
|
|
377
351
|
/\b(?:node|deno)\s+(?:-e|--eval)\b/i,
|
|
378
352
|
/\b(?:python(?:3)?|ruby|perl|php)\s+(?:-c|-e)\b/i,
|
|
@@ -383,7 +357,10 @@ export function validateInlineExecution(command, deny) {
|
|
|
383
357
|
/\|\s*(?:node|deno|python(?:3)?|ruby|perl|php|powershell|pwsh|cmd)\b/i,
|
|
384
358
|
/\b(?:Invoke-Expression|iex)\b/i,
|
|
385
359
|
];
|
|
386
|
-
|
|
360
|
+
// A quoted rg expression can contain a literal pipe followed by "node".
|
|
361
|
+
// Only the parsed read command may exempt that data; a real pipe to an
|
|
362
|
+
// interpreter remains outside isLiteralReadSequence and is denied.
|
|
363
|
+
if (!isLiteralReadSequence(command) && inlinePatterns.some((pattern) => pattern.test(command))) {
|
|
387
364
|
deny("Inline or piped interpreter execution is blocked; use a reviewed repository script.");
|
|
388
365
|
}
|
|
389
366
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex-pre-tool-support-template.js","sourceRoot":"","sources":["../../src/services/codex-pre-tool-support-template.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,2BAA2B,GAAG,MAAM,CAAC,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"codex-pre-tool-support-template.js","sourceRoot":"","sources":["../../src/services/codex-pre-tool-support-template.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,2BAA2B,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqXpD,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CODEX_SUBAGENT_LIFECYCLE_HOOK = "import { existsSync, realpathSync } from \"node:fs\";\nimport { promises as fs } from \"node:fs\";\nimport { execFileSync, spawn } from \"node:child_process\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { dirname, isAbsolute, relative, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst hookPath = fileURLToPath(import.meta.url);\nconst activation = await import(\"./zaip-hook-activation.mjs\").catch(() => null);\nconst installedRoot = resolve(dirname(hookPath), \"..\", \"..\");\nlet repositoryRoot = installedRoot;\nlet diagnosticPath = resolve(\n repositoryRoot,\n \".local\",\n \"hooks\",\n \"subagent-lifecycle.ndjson\",\n);\nlet pendingRoot = resolve(repositoryRoot, \".local\", \"hooks\", \"subagent-pending\");\n\nfunction inside(path, root) {\n const scoped = relative(root, resolve(path));\n return scoped === \"\" || (!scoped.startsWith(\"..\") && !isAbsolute(scoped));\n}\nfunction selectRoot(cwd) {\n if (typeof cwd !== \"string\") return false;\n try {\n if (realpathSync(cwd).toLowerCase() !== resolve(cwd).toLowerCase()) return false;\n let nestedGit = false;\n if (inside(cwd, installedRoot)) {\n for (let cursor = resolve(cwd); cursor.toLowerCase() !== installedRoot.toLowerCase(); cursor = dirname(cursor)) {\n if (existsSync(resolve(cursor, \".git\"))) { nestedGit = true; break; }\n }\n }\n if (!inside(cwd, installedRoot) || nestedGit) {\n const git = (root, ...args) => execFileSync(\"git\", [\"-C\", root, ...args], {\n encoding: \"utf8\", windowsHide: true, timeout: 5000,\n }).trim();\n if (realpathSync(git(installedRoot, \"rev-parse\", \"--show-toplevel\")).toLowerCase() !== installedRoot.toLowerCase()) return false;\n const registered = git(installedRoot, \"worktree\", \"list\", \"--porcelain\", \"-z\")\n .split(\"\\0\").filter(line => line.startsWith(\"worktree \")).map(line => resolve(line.slice(9)).toLowerCase());\n const candidate = realpathSync(git(cwd, \"rev-parse\", \"--show-toplevel\"));\n if (!registered.includes(candidate.toLowerCase()) || !inside(cwd, candidate)) return false;\n const common = root => realpathSync(git(root, \"rev-parse\", \"--path-format=absolute\", \"--git-common-dir\")).toLowerCase();\n if (common(installedRoot) !== common(candidate)) return false;\n repositoryRoot = candidate;\n }\n diagnosticPath = resolve(repositoryRoot, \".local\", \"hooks\", \"subagent-lifecycle.ndjson\");\n pendingRoot = resolve(repositoryRoot, \".local\", \"hooks\", \"subagent-pending\");\n return true;\n } catch { return false; }\n}\n\nif (process.argv[2] === \"--capture-runner\") {\n if (!selectRoot(process.cwd())) process.exit(0);\n await runCaptureRunner(process.argv[3]);\n process.exit(0);\n}\nif (process.argv[2] === \"--replay-pending\") {\n if (!selectRoot(process.cwd())) process.exit(0);\n const files = await fs.readdir(pendingRoot).catch(() => []);\n let replayed = 0;\n for (const file of files.filter((name) => /^[a-f0-9]{64}\\.json$/.test(name)).slice(0, 256)) {\n const path = resolve(pendingRoot, file);\n const receipt = await fs.readFile(path, \"utf8\").then(JSON.parse).catch(() => null);\n if (receipt?.state === \"pending\" && Array.isArray(receipt.capture_arguments)) { await runCaptureRunner(path, true); if (++replayed >= 3) break; }\n }\n process.exit(0);\n}\n\nlet raw = \"\";\nfor await (const chunk of process.stdin) raw += chunk;\n\ntry {\n const event = JSON.parse(raw);\n const cwd = text(event.cwd);\n if (cwd === null || !selectRoot(cwd)) process.exit(0);\n const eventName = text(event.hook_event_name);\n activation?.recordActivation(repositoryRoot, event);\n const lifecycleEvent =\n eventName === \"SubagentStart\" ? \"start\" : eventName === \"SubagentStop\" ? \"stop\" : null;\n const session = text(event.session_id);\n const turn = text(event.turn_id);\n const agentId = text(event.agent_id);\n const agentType = text(event.agent_type);\n const model = text(event.model);\n const reasoningEffort = reasoningEffortValue(\n event.model_reasoning_effort ?? event.reasoning_effort,\n );\n const parentAgentId = text(event.parent_agent_id ?? event.parent_id);\n const parentRunId = text(event.parent_run_id ?? event.root_run_id);\n const status = terminalStatus(event.status);\n const required = [lifecycleEvent, session, turn, agentId, agentType];\n if (lifecycleEvent === null) {\n await diagnostic(\"ignored_unrecognized_event\", { eventName });\n process.exit(0);\n }\n const terminalFallback =\n lifecycleEvent === \"stop\" &&\n status !== null &&\n agentId !== null &&\n parentAgentId !== null &&\n parentRunId !== null &&\n (session === null || turn === null);\n if (\n (!terminalFallback && required.some((value) => value === null)) ||\n (lifecycleEvent === \"start\" && model === null) ||\n (lifecycleEvent === \"stop\" && status === null)\n ) {\n await fs.mkdir(pendingRoot, { recursive: true });\n const identity = JSON.stringify({ eventName, session, turn, agentId, agentType, parentAgentId, parentRunId });\n const pending = resolve(pendingRoot, createHash(\"sha256\").update(identity).digest(\"hex\") + \".json\");\n await atomicSave(pending, { schema_version: 1, state: \"pending\", last_error: \"incomplete_host_metadata\", identity: JSON.parse(identity), recovery: \"Capture observed native metadata manually; never infer completed status.\" });\n await diagnostic(\"ignored_incomplete_event\", {\n eventName,\n session,\n turn,\n agentId,\n agentType,\n missing_model: lifecycleEvent === \"start\" && model === null,\n });\n process.exit(0);\n }\n const candidates = [\n resolve(repositoryRoot, \".agents\", \"runtime\", \"bin\", \"zaip.mjs\"),\n resolve(repositoryRoot, \"packages\", \"cli\", \"dist\", \"index.js\"),\n ];\n const cli = candidates.find(existsSync);\n if (cli === undefined) {\n await diagnostic(\"cli_not_found\", { eventName, candidates });\n process.exit(0);\n }\n const captureArguments = terminalFallback\n ? [\n \"subagent\",\n \"capture-terminal\",\n \"--parent-agent-id\",\n parentAgentId,\n \"--parent-run-id\",\n parentRunId,\n \"--agent-id\",\n agentId,\n \"--agent-type\",\n agentType,\n \"--status\",\n status,\n \"--json\",\n ]\n : [\n \"subagent\",\n \"capture\",\n \"--event\",\n lifecycleEvent,\n \"--session\",\n session,\n \"--turn\",\n turn,\n \"--agent-id\",\n agentId,\n \"--agent-type\",\n agentType,\n ...(model === null ? [] : [\"--model\", model]),\n ...(reasoningEffort === null ? [] : [\"--reasoning-effort\", reasoningEffort]),\n ...(parentAgentId === null ? [] : [\"--parent-agent-id\", parentAgentId]),\n ...(lifecycleEvent !== \"stop\" || status === null ? [] : [\"--status\", status]),\n \"--json\",\n ];\n const pending = await persistPending(cli, captureArguments);\n const receipt = await fs.readFile(pending, \"utf8\").then(JSON.parse);\n if (receipt.state !== \"acknowledged\") {\n const runner = spawn(\n process.execPath,\n [hookPath, \"--capture-runner\", pending],\n {\n cwd: repositoryRoot,\n detached: true,\n windowsHide: true,\n stdio: \"ignore\",\n },\n );\n runner.once(\"error\", (error) =>\n diagnostic(\"runner_spawn_failed\", { eventName, error: error.message }),\n );\n runner.unref();\n }\n if (lifecycleEvent === \"start\") {\n process.stdout.write(JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"SubagentStart\",\n additionalContext:\n \"ZAIP Codex delegation policy: a Sub-Run is only a native collaboration subagent. Do not use create_thread or fork_thread as a Sub-Run, fallback, retry, or delegation proof; those create independent chats and require an explicit user request. If native delegation is unavailable, report the limitation rather than create an independent chat. A catalog name, task_name, title, or prompt is not native ancestry. Before retrying the same bounded dispatch, await or reuse its pending native child; capture only ancestry observed from native host spawn/terminal results or SubagentStart/SubagentStop events, and work only as a cataloged zaip-agent-NOME identity. Delegation is recursive; do not use default, worker, explorer, or ad-hoc agent types.\",\n },\n }));\n } else {\n // Codex expects one JSON object on stdout for SubagentStop as well.\n process.stdout.write(JSON.stringify({}));\n }\n} catch (error) {\n await diagnostic(\"hook_exception\", {\n error: error instanceof Error ? error.message : String(error),\n });\n}\n\nasync function atomicSave(path, value) {\n const temporary = path + \".tmp-\" + randomUUID();\n await fs.writeFile(temporary, JSON.stringify(value), { flag: \"wx\", mode: 0o600 });\n for (let retry = 0; ; retry++) {\n try { await fs.rename(temporary, path); break; }\n catch (error) { if (![\"EPERM\", \"EACCES\", \"EBUSY\"].includes(error.code) || retry >= 5) throw error; await new Promise(done => setTimeout(done, 20 * (retry + 1))); }\n }\n}\n\nasync function runCaptureRunner(receiptPath, replay = false) {\n if (typeof receiptPath !== \"string\" || dirname(resolve(receiptPath)) !== pendingRoot) return;\n const lockPath = receiptPath + \".lock\";\n let lease;\n try {\n lease = await fs.open(lockPath, \"wx\");\n await lease.writeFile(JSON.stringify({ pid: process.pid }));\n } catch (error) {\n if (error.code !== \"EEXIST\") return;\n const owner = await fs.readFile(lockPath, \"utf8\").then(JSON.parse).catch(() => null);\n if (!Number.isSafeInteger(owner?.pid) || owner.pid <= 0) return;\n try { process.kill(owner.pid, 0); return; } catch (failure) { if (failure.code !== \"ESRCH\") return; }\n // Serialize stale lock recovery so two recovering workers cannot remove a new lease.\n let recovery;\n try { recovery = await fs.open(lockPath + \".recover\", \"wx\"); } catch { return; }\n try {\n const current = await fs.readFile(lockPath, \"utf8\").then(JSON.parse).catch(() => null);\n if (current?.pid !== owner.pid) return;\n await fs.unlink(lockPath);\n lease = await fs.open(lockPath, \"wx\");\n await lease.writeFile(JSON.stringify({ pid: process.pid }));\n } finally { await recovery.close(); await fs.unlink(lockPath + \".recover\").catch(() => {}); }\n if (!lease) return;\n }\n try {\n const receipt = await fs.readFile(receiptPath, \"utf8\").then(JSON.parse);\n if (receipt.schema_version !== 1 || receipt.state === \"acknowledged\" || (!replay && receipt.attempts >= 3)) return;\n const cli = receipt.cli, args = receipt.capture_arguments;\n const allowedCli = [resolve(repositoryRoot, \".agents/runtime/bin/zaip.mjs\"), resolve(repositoryRoot, \"packages/cli/dist/index.js\")];\n if (!allowedCli.includes(cli) || !Array.isArray(args) || !args.every(x => typeof x === \"string\") || args[0] !== \"subagent\" || ![\"capture\", \"capture-terminal\"].includes(args[1])) return;\n const digest = createHash(\"sha256\").update(JSON.stringify({ cli, arguments: args })).digest(\"hex\");\n if (resolve(pendingRoot, digest + \".json\") !== resolve(receiptPath)) return;\n const value = flag => args[args.indexOf(flag) + 1];\n const stop = args[1] === \"capture-terminal\" || value(\"--event\") === \"stop\";\n if (stop && ![\"completed\", \"failed\", \"cancelled\"].includes(value(\"--status\"))) return;\n for (let retry = 0; retry < 3; retry++) {\n receipt.attempts = (receipt.attempts ?? 0) + 1;\n receipt.updated_at = new Date().toISOString();\n await atomicSave(receiptPath, receipt);\n const outcome = await capture(cli, args);\n let result;\n try { result = JSON.parse(outcome.stdout); } catch { result = null; }\n const runId = result?.run?.run_id ?? result?.run?.id;\n const status = result?.status ?? result?.run?.status;\n if (outcome.code === 0 && result?.captured === true && typeof runId === \"string\" && runId.startsWith(\"RUN-\") && (!stop || status === value(\"--status\"))) {\n receipt.state = \"acknowledged\";\n receipt.run_id = runId;\n receipt.acknowledged_at = new Date().toISOString();\n delete receipt.last_error;\n await atomicSave(receiptPath, receipt);\n return;\n }\n receipt.last_error = outcome.error ?? (outcome.code === 0 ? \"capture_not_confirmed\" : \"capture_exit_nonzero\");\n await atomicSave(receiptPath, receipt);\n if (retry < 2) await new Promise(done => setTimeout(done, 250 * (retry + 1)));\n }\n await persistDiagnostic(\"capture_pending\", { receipt: receiptPath, attempts: receipt.attempts, error: receipt.last_error });\n } catch (error) {\n await persistDiagnostic(\"runner_exception\", { error: error instanceof Error ? error.message : String(error) });\n } finally {\n await lease?.close();\n await fs.unlink(lockPath).catch(() => {});\n }\n}\n\nasync function capture(cli, args) {\n const child = spawn(process.execPath, [cli, ...args], { cwd: repositoryRoot, windowsHide: true, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n let stdout = \"\", overflow = false;\n child.stdout.on(\"data\", chunk => { stdout += String(chunk); if (stdout.length > 262144) { overflow = true; stdout = \"\"; child.kill(); } });\n child.stderr.resume();\n return await new Promise(done => {\n let settled = false;\n const finish = result => { if (settled) return; settled = true; clearTimeout(timer); done({ ...result, stdout: overflow ? \"\" : stdout }); };\n const timer = setTimeout(() => { child.kill(); finish({ error: \"capture_timeout\" }); }, 15000);\n child.once(\"error\", () => finish({ error: \"capture_spawn_failed\" }));\n child.once(\"close\", code => finish({ code }));\n });\n}\n\nasync function persistPending(cli, captureArguments) {\n const identity = JSON.stringify({ cli, arguments: captureArguments });\n const digest = createHash(\"sha256\").update(identity).digest(\"hex\");\n const path = resolve(pendingRoot, digest + \".json\");\n await fs.mkdir(pendingRoot, { recursive: true });\n const receipt = { schema_version: 1, state: \"pending\", cli, capture_arguments: captureArguments, attempts: 0, created_at: new Date().toISOString() };\n // Publish a complete immutable initial receipt without overwriting another worker's acknowledgement.\n const temporary = path + \".tmp-\" + randomUUID();\n await fs.writeFile(temporary, JSON.stringify(receipt), { flag: \"wx\", mode: 0o600 });\n try { await fs.link(temporary, path); } catch (error) { if (error.code !== \"EEXIST\") throw error; }\n finally { await fs.unlink(temporary); }\n return path;\n}\n\nasync function persistDiagnostic(code, detail) {\n const line = `${JSON.stringify({\n source: \"zaip-subagent-lifecycle-runner\",\n code,\n occurred_at: new Date().toISOString(),\n detail,\n })}\\n`;\n await fs.mkdir(dirname(diagnosticPath), { recursive: true });\n const size = await fs.stat(diagnosticPath).then((entry) => entry.size).catch(() => 0);\n if (size + Buffer.byteLength(line) > 262_144) {\n await fs.writeFile(diagnosticPath, \"\", \"utf8\");\n }\n await fs.appendFile(diagnosticPath, line, \"utf8\");\n}\n\nasync function diagnostic(code, detail) {\n await persistDiagnostic(code, detail);\n process.stderr.write(\n `${JSON.stringify({ source: \"zaip-subagent-lifecycle\", code, detail })}\\n`,\n );\n}\n\nfunction text(value) {\n if (typeof value !== \"string\") return null;\n const normalized = value.trim();\n return normalized.length > 0 && normalized.length <= 200 ? normalized : null;\n}\n\nfunction reasoningEffortValue(value) {\n const normalized = text(value);\n return [\"low\", \"medium\", \"high\", \"xhigh\"].includes(normalized) ? normalized : null;\n}\n\nfunction terminalStatus(value) {\n const normalized = text(value);\n return [\"completed\", \"failed\", \"cancelled\"].includes(normalized) ? normalized : null;\n}\n";
|
|
1
|
+
export declare const CODEX_SUBAGENT_LIFECYCLE_HOOK = "import { existsSync, realpathSync } from \"node:fs\";\nimport { promises as fs } from \"node:fs\";\nimport { execFileSync, spawn } from \"node:child_process\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { dirname, isAbsolute, relative, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst hookPath = fileURLToPath(import.meta.url);\nconst activation = await import(\"./zaip-hook-activation.mjs\").catch(() => null);\nconst installedRoot = resolve(dirname(hookPath), \"..\", \"..\");\nlet repositoryRoot = installedRoot;\nlet diagnosticPath = resolve(\n repositoryRoot,\n \".local\",\n \"hooks\",\n \"subagent-lifecycle.ndjson\",\n);\nlet pendingRoot = resolve(repositoryRoot, \".local\", \"hooks\", \"subagent-pending\");\n\nfunction inside(path, root) {\n const scoped = relative(root, resolve(path));\n return scoped === \"\" || (!scoped.startsWith(\"..\") && !isAbsolute(scoped));\n}\nfunction selectRoot(cwd) {\n if (typeof cwd !== \"string\") return false;\n try {\n if (realpathSync(cwd).toLowerCase() !== resolve(cwd).toLowerCase()) return false;\n let nestedGit = false;\n if (inside(cwd, installedRoot)) {\n for (let cursor = resolve(cwd); cursor.toLowerCase() !== installedRoot.toLowerCase(); cursor = dirname(cursor)) {\n if (existsSync(resolve(cursor, \".git\"))) { nestedGit = true; break; }\n }\n }\n if (!inside(cwd, installedRoot) || nestedGit) {\n const git = (root, ...args) => execFileSync(\"git\", [\"-C\", root, ...args], {\n encoding: \"utf8\", windowsHide: true, timeout: 5000,\n }).trim();\n if (realpathSync(git(installedRoot, \"rev-parse\", \"--show-toplevel\")).toLowerCase() !== installedRoot.toLowerCase()) return false;\n const registered = git(installedRoot, \"worktree\", \"list\", \"--porcelain\", \"-z\")\n .split(\"\\0\").filter(line => line.startsWith(\"worktree \")).map(line => resolve(line.slice(9)).toLowerCase());\n const candidate = realpathSync(git(cwd, \"rev-parse\", \"--show-toplevel\"));\n if (!registered.includes(candidate.toLowerCase()) || !inside(cwd, candidate)) return false;\n const common = root => realpathSync(git(root, \"rev-parse\", \"--path-format=absolute\", \"--git-common-dir\")).toLowerCase();\n if (common(installedRoot) !== common(candidate)) return false;\n repositoryRoot = candidate;\n }\n diagnosticPath = resolve(repositoryRoot, \".local\", \"hooks\", \"subagent-lifecycle.ndjson\");\n pendingRoot = resolve(repositoryRoot, \".local\", \"hooks\", \"subagent-pending\");\n return true;\n } catch { return false; }\n}\n\nif (process.argv[2] === \"--capture-runner\") {\n if (!selectRoot(process.cwd())) process.exit(0);\n if (!(await activation?.hookPolicies?.(repositoryRoot))?.governance) process.exit(0);\n await runCaptureRunner(process.argv[3]);\n process.exit(0);\n}\nif (process.argv[2] === \"--replay-pending\") {\n if (!selectRoot(process.cwd())) process.exit(0);\n const policy = await activation?.hookPolicies?.(repositoryRoot);\n if (policy?.tracking) activation?.replayHookObservations?.(repositoryRoot);\n if (!policy?.governance) { process.stdout.write(JSON.stringify({ attempted: 0, deferred: 0, pending: 0, disabled: true })); process.exit(0); }\n const files = (await fs.readdir(pendingRoot).catch(() => []))\n .filter((name) => /^[a-f0-9]{64}\\.json$/.test(name)).sort();\n const cursorPath = resolve(pendingRoot, \".replay-cursor\");\n const cursor = await fs.readFile(cursorPath, \"utf8\").then(JSON.parse).catch(() => null);\n const start = (files.indexOf(cursor?.last) + 1) % (files.length || 1);\n const selected = [...files.slice(start), ...files.slice(0, start)].slice(0, 256);\n let attempted = 0, deferred = 0, pending = 0;\n let lastAttempted = null;\n for (const file of selected) {\n const path = resolve(pendingRoot, file);\n const receipt = await fs.readFile(path, \"utf8\").then(JSON.parse).catch(() => null);\n if (receipt?.state !== \"pending\" || !Array.isArray(receipt.capture_arguments)) continue;\n if (attempted < 3) {\n attempted++; lastAttempted = file;\n if (await replayPendingReceipt(path) === \"deferred\") deferred++;\n }\n if ((await fs.readFile(path, \"utf8\").then(JSON.parse).catch(() => null))?.state === \"pending\") pending++;\n }\n if (selected.length) await atomicSave(cursorPath, { last: lastAttempted ?? selected.at(-1) });\n process.stdout.write(JSON.stringify({ attempted, deferred, pending, uninspected: files.length - selected.length }));\n process.exit(0);\n}\n\nlet raw = \"\";\nfor await (const chunk of process.stdin) raw += chunk;\n\ntry {\n const event = JSON.parse(raw);\n const cwd = text(event.cwd);\n if (cwd === null || !selectRoot(cwd)) process.exit(0);\n const policy = await activation?.hookPolicies?.(repositoryRoot) ?? { governance: false, tracking: false, diagnostic: \"policy_module_unavailable\" };\n activation?.recordPolicyDiagnostic?.(repositoryRoot, policy);\n if (policy.tracking) activation?.recordHookObservation?.(repositoryRoot, event, policy.governance);\n if (policy.tracking && event.hook_event_name === \"SubagentStop\") activation?.replayHookObservations?.(repositoryRoot);\n if (!policy.governance) { process.stdout.write(JSON.stringify({})); process.exit(0); }\n const eventName = text(event.hook_event_name);\n activation?.recordActivation(repositoryRoot, event);\n const lifecycleEvent =\n eventName === \"SubagentStart\" ? \"start\" : eventName === \"SubagentStop\" ? \"stop\" : null;\n const session = text(event.session_id);\n const turn = text(event.turn_id);\n const agentId = text(event.agent_id);\n const agentType = text(event.agent_type);\n const model = text(event.model);\n const reasoningEffort = reasoningEffortValue(\n event.model_reasoning_effort ?? event.reasoning_effort,\n );\n const parentAgentId = text(event.parent_agent_id ?? event.parent_id);\n const parentRunId = text(event.parent_run_id ?? event.root_run_id);\n const status = terminalStatus(event.status);\n const required = [lifecycleEvent, session, turn, agentId, agentType];\n if (lifecycleEvent === null) {\n await diagnostic(\"ignored_unrecognized_event\", { eventName });\n process.exit(0);\n }\n const terminalFallback =\n lifecycleEvent === \"stop\" &&\n status !== null &&\n agentId !== null &&\n parentAgentId !== null &&\n parentRunId !== null &&\n (session === null || turn === null);\n if (\n (!terminalFallback && required.some((value) => value === null)) ||\n (lifecycleEvent === \"start\" && model === null) ||\n (lifecycleEvent === \"stop\" && status === null)\n ) {\n await fs.mkdir(pendingRoot, { recursive: true });\n const identity = JSON.stringify({ eventName, session, turn, agentId, agentType, parentAgentId, parentRunId });\n const pending = resolve(pendingRoot, createHash(\"sha256\").update(identity).digest(\"hex\") + \".json\");\n await atomicSave(pending, { schema_version: 1, state: \"pending\", last_error: \"incomplete_host_metadata\", identity: JSON.parse(identity), recovery: \"Capture observed native metadata manually; never infer completed status.\" });\n await diagnostic(\"ignored_incomplete_event\", {\n eventName,\n session,\n turn,\n agentId,\n agentType,\n missing_model: lifecycleEvent === \"start\" && model === null,\n });\n process.exit(0);\n }\n const candidates = [\n resolve(repositoryRoot, \".agents\", \"runtime\", \"bin\", \"zaip.mjs\"),\n resolve(repositoryRoot, \"packages\", \"cli\", \"dist\", \"index.js\"),\n ];\n const cli = candidates.find(existsSync);\n if (cli === undefined) {\n await diagnostic(\"cli_not_found\", { eventName, candidates });\n process.exit(0);\n }\n const captureArguments = terminalFallback\n ? [\n \"subagent\",\n \"capture-terminal\",\n \"--parent-agent-id\",\n parentAgentId,\n \"--parent-run-id\",\n parentRunId,\n \"--agent-id\",\n agentId,\n \"--agent-type\",\n agentType,\n \"--status\",\n status,\n \"--json\",\n ]\n : [\n \"subagent\",\n \"capture\",\n \"--event\",\n lifecycleEvent,\n \"--session\",\n session,\n \"--turn\",\n turn,\n \"--agent-id\",\n agentId,\n \"--agent-type\",\n agentType,\n ...(model === null ? [] : [\"--model\", model]),\n ...(reasoningEffort === null ? [] : [\"--reasoning-effort\", reasoningEffort]),\n ...(parentAgentId === null ? [] : [\"--parent-agent-id\", parentAgentId]),\n ...(lifecycleEvent !== \"stop\" || status === null ? [] : [\"--status\", status]),\n \"--json\",\n ];\n const pending = await persistPending(cli, captureArguments);\n const receipt = await fs.readFile(pending, \"utf8\").then(JSON.parse);\n if (receipt.state !== \"acknowledged\") {\n const runner = spawn(\n process.execPath,\n [hookPath, \"--capture-runner\", pending],\n {\n cwd: repositoryRoot,\n detached: true,\n windowsHide: true,\n stdio: \"ignore\",\n },\n );\n runner.once(\"error\", (error) =>\n diagnostic(\"runner_spawn_failed\", { eventName, error: error.message }),\n );\n runner.unref();\n }\n if (lifecycleEvent === \"start\") {\n process.stdout.write(JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"SubagentStart\",\n additionalContext:\n \"ZAIP Codex delegation policy: a Sub-Run is only a native collaboration subagent. Do not use create_thread or fork_thread as a Sub-Run, fallback, retry, or delegation proof; those create independent chats and require an explicit user request. If native delegation is unavailable, report the limitation rather than create an independent chat. A catalog name, task_name, title, or prompt is not native ancestry. Before retrying the same bounded dispatch, await or reuse its pending native child; capture only ancestry observed from native host spawn/terminal results or SubagentStart/SubagentStop events, and work only as a cataloged zaip-agent-NOME identity. Delegation is recursive; do not use default, worker, explorer, or ad-hoc agent types.\",\n },\n }));\n } else {\n // Codex expects one JSON object on stdout for SubagentStop as well.\n process.stdout.write(JSON.stringify({}));\n }\n} catch (error) {\n await diagnostic(\"hook_exception\", {\n error: error instanceof Error ? error.message : String(error),\n });\n}\n\nasync function atomicSave(path, value) {\n const temporary = path + \".tmp-\" + randomUUID();\n const delays = [25, 50, 75, 100, 150, 225, 300, 400, 500];\n try {\n await fs.writeFile(temporary, JSON.stringify(value), { flag: \"wx\", mode: 0o600 });\n for (let retry = 0; ; retry++) {\n try { await fs.rename(temporary, path); return; }\n catch (error) {\n if (![\"EPERM\", \"EACCES\", \"EBUSY\"].includes(error.code) || retry >= delays.length) throw error;\n await new Promise(done => setTimeout(done, delays[retry]));\n }\n }\n } finally {\n await fs.unlink(temporary).catch(() => {});\n }\n}\n\nasync function replayPendingReceipt(path) {\n const deadline = Date.now() + 2000;\n for (;;) {\n if (await runCaptureRunner(path, true) !== \"lease_busy\") return \"attempted\";\n if (Date.now() >= deadline) {\n await persistDiagnostic(\"replay_deferred_active_lease\", { receipt: path });\n return \"deferred\";\n }\n await new Promise(done => setTimeout(done, 50));\n }\n}\n\nasync function runCaptureRunner(receiptPath, replay = false) {\n if (typeof receiptPath !== \"string\" || dirname(resolve(receiptPath)) !== pendingRoot) return;\n const lockPath = receiptPath + \".lock\";\n let lease;\n try {\n lease = await fs.open(lockPath, \"wx\");\n await lease.writeFile(JSON.stringify({ pid: process.pid }));\n } catch (error) {\n if (error.code !== \"EEXIST\") return;\n const owner = await fs.readFile(lockPath, \"utf8\").then(JSON.parse).catch(() => null);\n if (!Number.isSafeInteger(owner?.pid) || owner.pid <= 0) return \"lease_busy\";\n try { process.kill(owner.pid, 0); return \"lease_busy\"; } catch (failure) { if (failure.code !== \"ESRCH\") return \"lease_busy\"; }\n // Serialize stale lock recovery so two recovering workers cannot remove a new lease.\n let recovery;\n try { recovery = await fs.open(lockPath + \".recover\", \"wx\"); } catch { return \"lease_busy\"; }\n try {\n const current = await fs.readFile(lockPath, \"utf8\").then(JSON.parse).catch(() => null);\n if (current?.pid !== owner.pid) return \"lease_busy\";\n await fs.unlink(lockPath);\n lease = await fs.open(lockPath, \"wx\");\n await lease.writeFile(JSON.stringify({ pid: process.pid }));\n } finally { await recovery.close(); await fs.unlink(lockPath + \".recover\").catch(() => {}); }\n if (!lease) return \"lease_busy\";\n }\n try {\n const receipt = await fs.readFile(receiptPath, \"utf8\").then(JSON.parse);\n if (receipt.schema_version !== 1 || receipt.state === \"acknowledged\" || (!replay && receipt.attempts >= 3)) return;\n const cli = receipt.cli, args = receipt.capture_arguments;\n const allowedCli = [resolve(repositoryRoot, \".agents/runtime/bin/zaip.mjs\"), resolve(repositoryRoot, \"packages/cli/dist/index.js\")];\n if (!allowedCli.includes(cli) || !Array.isArray(args) || !args.every(x => typeof x === \"string\") || args[0] !== \"subagent\" || ![\"capture\", \"capture-terminal\"].includes(args[1])) return;\n const digest = createHash(\"sha256\").update(JSON.stringify({ cli, arguments: args })).digest(\"hex\");\n if (resolve(pendingRoot, digest + \".json\") !== resolve(receiptPath)) return;\n const value = flag => args[args.indexOf(flag) + 1];\n const stop = args[1] === \"capture-terminal\" || value(\"--event\") === \"stop\";\n if (stop && ![\"completed\", \"failed\", \"cancelled\"].includes(value(\"--status\"))) return;\n for (let retry = 0; retry < 3; retry++) {\n receipt.attempts = (receipt.attempts ?? 0) + 1;\n receipt.updated_at = new Date().toISOString();\n await atomicSave(receiptPath, receipt);\n const outcome = await capture(cli, args);\n let result;\n try { result = JSON.parse(outcome.stdout); } catch { result = null; }\n const runId = result?.run?.run_id ?? result?.run?.id;\n const status = result?.status ?? result?.run?.status;\n if (outcome.code === 0 && result?.captured === true && typeof runId === \"string\" && runId.startsWith(\"RUN-\") && (!stop || status === value(\"--status\"))) {\n receipt.state = \"acknowledged\";\n receipt.run_id = runId;\n receipt.acknowledged_at = new Date().toISOString();\n delete receipt.last_error;\n await atomicSave(receiptPath, receipt);\n return;\n }\n receipt.last_error = outcome.error ?? (outcome.code === 0 ? \"capture_not_confirmed\" : \"capture_exit_nonzero\");\n await atomicSave(receiptPath, receipt);\n if (retry < 2) await new Promise(done => setTimeout(done, 250 * (retry + 1)));\n }\n await persistDiagnostic(\"capture_pending\", { receipt: receiptPath, attempts: receipt.attempts, error: receipt.last_error });\n } catch (error) {\n await persistDiagnostic(\"runner_exception\", { error: error instanceof Error ? error.message : String(error) });\n } finally {\n await lease?.close();\n await fs.unlink(lockPath).catch(() => {});\n }\n}\n\nasync function capture(cli, args) {\n const policy = await activation?.hookPolicies?.(repositoryRoot);\n const child = spawn(process.execPath, [cli, ...args], { cwd: repositoryRoot, windowsHide: true,\n env: { ...process.env, ZAIP_HOOK_TRACKING_ENABLED: policy?.tracking ? \"true\" : \"false\" }, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n let stdout = \"\", overflow = false;\n child.stdout.on(\"data\", chunk => { stdout += String(chunk); if (stdout.length > 262144) { overflow = true; stdout = \"\"; child.kill(); } });\n child.stderr.resume();\n return await new Promise(done => {\n let settled = false;\n const finish = result => { if (settled) return; settled = true; clearTimeout(timer); done({ ...result, stdout: overflow ? \"\" : stdout }); };\n const timer = setTimeout(() => { child.kill(); finish({ error: \"capture_timeout\" }); }, 15000);\n child.once(\"error\", () => finish({ error: \"capture_spawn_failed\" }));\n child.once(\"close\", code => finish({ code }));\n });\n}\n\nasync function persistPending(cli, captureArguments) {\n const identity = JSON.stringify({ cli, arguments: captureArguments });\n const digest = createHash(\"sha256\").update(identity).digest(\"hex\");\n const path = resolve(pendingRoot, digest + \".json\");\n await fs.mkdir(pendingRoot, { recursive: true });\n const receipt = { schema_version: 1, state: \"pending\", cli, capture_arguments: captureArguments, attempts: 0, created_at: new Date().toISOString() };\n // Publish a complete immutable initial receipt without overwriting another worker's acknowledgement.\n const temporary = path + \".tmp-\" + randomUUID();\n await fs.writeFile(temporary, JSON.stringify(receipt), { flag: \"wx\", mode: 0o600 });\n try { await fs.link(temporary, path); } catch (error) { if (error.code !== \"EEXIST\") throw error; }\n finally { await fs.unlink(temporary); }\n return path;\n}\n\nasync function persistDiagnostic(code, detail) {\n const line = `${JSON.stringify({\n source: \"zaip-subagent-lifecycle-runner\",\n code,\n occurred_at: new Date().toISOString(),\n detail,\n })}\\n`;\n await fs.mkdir(dirname(diagnosticPath), { recursive: true });\n const size = await fs.stat(diagnosticPath).then((entry) => entry.size).catch(() => 0);\n if (size + Buffer.byteLength(line) > 262_144) {\n await fs.writeFile(diagnosticPath, \"\", \"utf8\");\n }\n await fs.appendFile(diagnosticPath, line, \"utf8\");\n}\n\nasync function diagnostic(code, detail) {\n await persistDiagnostic(code, detail);\n process.stderr.write(\n `${JSON.stringify({ source: \"zaip-subagent-lifecycle\", code, detail })}\\n`,\n );\n}\n\nfunction text(value) {\n if (typeof value !== \"string\") return null;\n const normalized = value.trim();\n return normalized.length > 0 && normalized.length <= 200 ? normalized : null;\n}\n\nfunction reasoningEffortValue(value) {\n const normalized = text(value);\n return [\"low\", \"medium\", \"high\", \"xhigh\"].includes(normalized) ? normalized : null;\n}\n\nfunction terminalStatus(value) {\n const normalized = text(value);\n return [\"completed\", \"failed\", \"cancelled\"].includes(normalized) ? normalized : null;\n}\n";
|
|
2
2
|
//# sourceMappingURL=codex-subagent-hook-template.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex-subagent-hook-template.d.ts","sourceRoot":"","sources":["../../src/services/codex-subagent-hook-template.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,6BAA6B,
|
|
1
|
+
{"version":3,"file":"codex-subagent-hook-template.d.ts","sourceRoot":"","sources":["../../src/services/codex-subagent-hook-template.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,6BAA6B,i1kBAiYzC,CAAC"}
|
|
@@ -52,18 +52,35 @@ function selectRoot(cwd) {
|
|
|
52
52
|
|
|
53
53
|
if (process.argv[2] === "--capture-runner") {
|
|
54
54
|
if (!selectRoot(process.cwd())) process.exit(0);
|
|
55
|
+
if (!(await activation?.hookPolicies?.(repositoryRoot))?.governance) process.exit(0);
|
|
55
56
|
await runCaptureRunner(process.argv[3]);
|
|
56
57
|
process.exit(0);
|
|
57
58
|
}
|
|
58
59
|
if (process.argv[2] === "--replay-pending") {
|
|
59
60
|
if (!selectRoot(process.cwd())) process.exit(0);
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
const policy = await activation?.hookPolicies?.(repositoryRoot);
|
|
62
|
+
if (policy?.tracking) activation?.replayHookObservations?.(repositoryRoot);
|
|
63
|
+
if (!policy?.governance) { process.stdout.write(JSON.stringify({ attempted: 0, deferred: 0, pending: 0, disabled: true })); process.exit(0); }
|
|
64
|
+
const files = (await fs.readdir(pendingRoot).catch(() => []))
|
|
65
|
+
.filter((name) => /^[a-f0-9]{64}\\.json$/.test(name)).sort();
|
|
66
|
+
const cursorPath = resolve(pendingRoot, ".replay-cursor");
|
|
67
|
+
const cursor = await fs.readFile(cursorPath, "utf8").then(JSON.parse).catch(() => null);
|
|
68
|
+
const start = (files.indexOf(cursor?.last) + 1) % (files.length || 1);
|
|
69
|
+
const selected = [...files.slice(start), ...files.slice(0, start)].slice(0, 256);
|
|
70
|
+
let attempted = 0, deferred = 0, pending = 0;
|
|
71
|
+
let lastAttempted = null;
|
|
72
|
+
for (const file of selected) {
|
|
63
73
|
const path = resolve(pendingRoot, file);
|
|
64
74
|
const receipt = await fs.readFile(path, "utf8").then(JSON.parse).catch(() => null);
|
|
65
|
-
if (receipt?.state
|
|
75
|
+
if (receipt?.state !== "pending" || !Array.isArray(receipt.capture_arguments)) continue;
|
|
76
|
+
if (attempted < 3) {
|
|
77
|
+
attempted++; lastAttempted = file;
|
|
78
|
+
if (await replayPendingReceipt(path) === "deferred") deferred++;
|
|
79
|
+
}
|
|
80
|
+
if ((await fs.readFile(path, "utf8").then(JSON.parse).catch(() => null))?.state === "pending") pending++;
|
|
66
81
|
}
|
|
82
|
+
if (selected.length) await atomicSave(cursorPath, { last: lastAttempted ?? selected.at(-1) });
|
|
83
|
+
process.stdout.write(JSON.stringify({ attempted, deferred, pending, uninspected: files.length - selected.length }));
|
|
67
84
|
process.exit(0);
|
|
68
85
|
}
|
|
69
86
|
|
|
@@ -74,6 +91,11 @@ try {
|
|
|
74
91
|
const event = JSON.parse(raw);
|
|
75
92
|
const cwd = text(event.cwd);
|
|
76
93
|
if (cwd === null || !selectRoot(cwd)) process.exit(0);
|
|
94
|
+
const policy = await activation?.hookPolicies?.(repositoryRoot) ?? { governance: false, tracking: false, diagnostic: "policy_module_unavailable" };
|
|
95
|
+
activation?.recordPolicyDiagnostic?.(repositoryRoot, policy);
|
|
96
|
+
if (policy.tracking) activation?.recordHookObservation?.(repositoryRoot, event, policy.governance);
|
|
97
|
+
if (policy.tracking && event.hook_event_name === "SubagentStop") activation?.replayHookObservations?.(repositoryRoot);
|
|
98
|
+
if (!policy.governance) { process.stdout.write(JSON.stringify({})); process.exit(0); }
|
|
77
99
|
const eventName = text(event.hook_event_name);
|
|
78
100
|
activation?.recordActivation(repositoryRoot, event);
|
|
79
101
|
const lifecycleEvent =
|
|
@@ -202,10 +224,30 @@ try {
|
|
|
202
224
|
|
|
203
225
|
async function atomicSave(path, value) {
|
|
204
226
|
const temporary = path + ".tmp-" + randomUUID();
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
227
|
+
const delays = [25, 50, 75, 100, 150, 225, 300, 400, 500];
|
|
228
|
+
try {
|
|
229
|
+
await fs.writeFile(temporary, JSON.stringify(value), { flag: "wx", mode: 0o600 });
|
|
230
|
+
for (let retry = 0; ; retry++) {
|
|
231
|
+
try { await fs.rename(temporary, path); return; }
|
|
232
|
+
catch (error) {
|
|
233
|
+
if (!["EPERM", "EACCES", "EBUSY"].includes(error.code) || retry >= delays.length) throw error;
|
|
234
|
+
await new Promise(done => setTimeout(done, delays[retry]));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} finally {
|
|
238
|
+
await fs.unlink(temporary).catch(() => {});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function replayPendingReceipt(path) {
|
|
243
|
+
const deadline = Date.now() + 2000;
|
|
244
|
+
for (;;) {
|
|
245
|
+
if (await runCaptureRunner(path, true) !== "lease_busy") return "attempted";
|
|
246
|
+
if (Date.now() >= deadline) {
|
|
247
|
+
await persistDiagnostic("replay_deferred_active_lease", { receipt: path });
|
|
248
|
+
return "deferred";
|
|
249
|
+
}
|
|
250
|
+
await new Promise(done => setTimeout(done, 50));
|
|
209
251
|
}
|
|
210
252
|
}
|
|
211
253
|
|
|
@@ -219,19 +261,19 @@ async function runCaptureRunner(receiptPath, replay = false) {
|
|
|
219
261
|
} catch (error) {
|
|
220
262
|
if (error.code !== "EEXIST") return;
|
|
221
263
|
const owner = await fs.readFile(lockPath, "utf8").then(JSON.parse).catch(() => null);
|
|
222
|
-
if (!Number.isSafeInteger(owner?.pid) || owner.pid <= 0) return;
|
|
223
|
-
try { process.kill(owner.pid, 0); return; } catch (failure) { if (failure.code !== "ESRCH") return; }
|
|
264
|
+
if (!Number.isSafeInteger(owner?.pid) || owner.pid <= 0) return "lease_busy";
|
|
265
|
+
try { process.kill(owner.pid, 0); return "lease_busy"; } catch (failure) { if (failure.code !== "ESRCH") return "lease_busy"; }
|
|
224
266
|
// Serialize stale lock recovery so two recovering workers cannot remove a new lease.
|
|
225
267
|
let recovery;
|
|
226
|
-
try { recovery = await fs.open(lockPath + ".recover", "wx"); } catch { return; }
|
|
268
|
+
try { recovery = await fs.open(lockPath + ".recover", "wx"); } catch { return "lease_busy"; }
|
|
227
269
|
try {
|
|
228
270
|
const current = await fs.readFile(lockPath, "utf8").then(JSON.parse).catch(() => null);
|
|
229
|
-
if (current?.pid !== owner.pid) return;
|
|
271
|
+
if (current?.pid !== owner.pid) return "lease_busy";
|
|
230
272
|
await fs.unlink(lockPath);
|
|
231
273
|
lease = await fs.open(lockPath, "wx");
|
|
232
274
|
await lease.writeFile(JSON.stringify({ pid: process.pid }));
|
|
233
275
|
} finally { await recovery.close(); await fs.unlink(lockPath + ".recover").catch(() => {}); }
|
|
234
|
-
if (!lease) return;
|
|
276
|
+
if (!lease) return "lease_busy";
|
|
235
277
|
}
|
|
236
278
|
try {
|
|
237
279
|
const receipt = await fs.readFile(receiptPath, "utf8").then(JSON.parse);
|
|
@@ -248,7 +290,7 @@ async function runCaptureRunner(receiptPath, replay = false) {
|
|
|
248
290
|
receipt.attempts = (receipt.attempts ?? 0) + 1;
|
|
249
291
|
receipt.updated_at = new Date().toISOString();
|
|
250
292
|
await atomicSave(receiptPath, receipt);
|
|
251
|
-
|
|
293
|
+
const outcome = await capture(cli, args);
|
|
252
294
|
let result;
|
|
253
295
|
try { result = JSON.parse(outcome.stdout); } catch { result = null; }
|
|
254
296
|
const runId = result?.run?.run_id ?? result?.run?.id;
|
|
@@ -275,7 +317,9 @@ async function runCaptureRunner(receiptPath, replay = false) {
|
|
|
275
317
|
}
|
|
276
318
|
|
|
277
319
|
async function capture(cli, args) {
|
|
278
|
-
const
|
|
320
|
+
const policy = await activation?.hookPolicies?.(repositoryRoot);
|
|
321
|
+
const child = spawn(process.execPath, [cli, ...args], { cwd: repositoryRoot, windowsHide: true,
|
|
322
|
+
env: { ...process.env, ZAIP_HOOK_TRACKING_ENABLED: policy?.tracking ? "true" : "false" }, stdio: ["ignore", "pipe", "pipe"] });
|
|
279
323
|
let stdout = "", overflow = false;
|
|
280
324
|
child.stdout.on("data", chunk => { stdout += String(chunk); if (stdout.length > 262144) { overflow = true; stdout = ""; child.kill(); } });
|
|
281
325
|
child.stderr.resume();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex-subagent-hook-template.js","sourceRoot":"","sources":["../../src/services/codex-subagent-hook-template.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,6BAA6B,GAAG
|
|
1
|
+
{"version":3,"file":"codex-subagent-hook-template.js","sourceRoot":"","sources":["../../src/services/codex-subagent-hook-template.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,6BAA6B,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiY5C,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex-validation-runtime-template.d.ts","sourceRoot":"","sources":["../../src/services/codex-validation-runtime-template.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,eAAO,MAAM,gCAAgC,
|
|
1
|
+
{"version":3,"file":"codex-validation-runtime-template.d.ts","sourceRoot":"","sources":["../../src/services/codex-validation-runtime-template.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,eAAO,MAAM,gCAAgC,QA0C5C,CAAC"}
|
|
@@ -1,18 +1,45 @@
|
|
|
1
1
|
/** Appended to native authority; receives the shared literal parser. */
|
|
2
2
|
export const CODEX_VALIDATION_RUNTIME_SUPPORT = String.raw `
|
|
3
|
-
/**
|
|
4
|
-
export async function reviewedValidationRuntimePaths(command, tokenizeLiteralCommand) {
|
|
3
|
+
/** A local opt-in may name only the Codex-owned Node under this user's profile. */
|
|
4
|
+
export async function reviewedValidationRuntimePaths(command, tokenizeLiteralCommand, repositoryRoot) {
|
|
5
5
|
const tokens = tokenizeLiteralCommand(command);
|
|
6
|
-
if (!tokens
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
if (
|
|
6
|
+
if (!tokens) return [];
|
|
7
|
+
const validation = (/^(?:node|node\.exe)$/i.test(tokens[0]) || isAbsolute(tokens[0])) &&
|
|
8
|
+
/^\.agents[\\/]runtime[\\/]bin[\\/]zaip\.mjs$/.test(tokens[1] ?? "") &&
|
|
9
|
+
tokens[2] === "validation" && ["batch", "execute"].includes(tokens[3]);
|
|
10
|
+
const positions = validation ? tokens.flatMap((value, index) => value === "--executable" ? [index] : []) : [];
|
|
11
|
+
const validationPath = positions.length === 1 ? tokens[positions[0] + 1] : null;
|
|
12
|
+
if (validationPath && isAbsolute(validationPath) && command.split(validationPath).length === 2) {
|
|
13
|
+
try { if (await realpath(validationPath) === await realpath(process.execPath)) return [validationPath]; }
|
|
14
|
+
catch { /* A missing executable has no exemption. */ }
|
|
15
|
+
}
|
|
16
|
+
if (!repositoryRoot || !process.env.USERPROFILE) return [];
|
|
17
|
+
let configured;
|
|
13
18
|
try {
|
|
14
|
-
|
|
19
|
+
configured = JSON.parse(await readFile(resolve(repositoryRoot, ".local/validation-runtime.json"), "utf8"));
|
|
15
20
|
} catch { return []; }
|
|
21
|
+
if (!configured || configured.schema_version !== 1 ||
|
|
22
|
+
Object.keys(configured).sort().join(",") !== "executable,schema_version" ||
|
|
23
|
+
typeof configured.executable !== "string" || !isAbsolute(configured.executable)) return [];
|
|
24
|
+
const expected = resolve(process.env.USERPROFILE, ".cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node.exe");
|
|
25
|
+
if (resolve(configured.executable).toLowerCase() !== expected.toLowerCase()) return [];
|
|
26
|
+
// Windows accepts either separator spelling of the same configured literal.
|
|
27
|
+
// Do not admit aliases, traversal or another executable by realpath alone.
|
|
28
|
+
const configuredLiteral = value => typeof value === "string" &&
|
|
29
|
+
value.replaceAll("/", "\\").toLowerCase() === configured.executable.replaceAll("/", "\\").toLowerCase();
|
|
30
|
+
const directVersion = tokens.length === 2 && tokens[0] === configured.executable && tokens[1] === "--version";
|
|
31
|
+
const directPreflight = tokens.length === 3 && tokens[0] === configured.executable &&
|
|
32
|
+
/^packages[\\/]specs[\\/]qualify-proportional-v1\.mjs$/.test(tokens[1]) &&
|
|
33
|
+
tokens[2] === "preflight";
|
|
34
|
+
const configuredLauncher = tokens[0] === configured.executable;
|
|
35
|
+
const validationExecutable = configuredLiteral(validationPath) &&
|
|
36
|
+
(tokens[3] === "execute"
|
|
37
|
+
? tokens[0] === validationPath
|
|
38
|
+
: configuredLauncher || /^(?:node|node\.exe)$/i.test(tokens[0])) &&
|
|
39
|
+
command.split(validationPath).length === (tokens[0] === validationPath ? 3 : 2);
|
|
40
|
+
if (!directVersion && !directPreflight && !validationExecutable) return [];
|
|
41
|
+
try { return await realpath(configured.executable) === await realpath(expected) ? [validationExecutable ? validationPath : configured.executable] : []; }
|
|
42
|
+
catch { return []; }
|
|
16
43
|
}
|
|
17
44
|
`;
|
|
18
45
|
//# sourceMappingURL=codex-validation-runtime-template.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex-validation-runtime-template.js","sourceRoot":"","sources":["../../src/services/codex-validation-runtime-template.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,MAAM,CAAC,MAAM,gCAAgC,GAAG,MAAM,CAAC,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"codex-validation-runtime-template.js","sourceRoot":"","sources":["../../src/services/codex-validation-runtime-template.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,MAAM,CAAC,MAAM,gCAAgC,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0CzD,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export declare function run(runtime: string, cwd: string, arguments_: readonly string[]): Record<string, unknown>;
|
|
2
2
|
export declare function snapshotTree(root: string): Promise<Record<string, string>>;
|
|
3
|
+
export declare function testTemplate(roots: string[], name: string, files: readonly (readonly [path: string, content: string])[]): Promise<string>;
|
|
3
4
|
//# sourceMappingURL=runtime-template-test-support.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-template-test-support.d.ts","sourceRoot":"","sources":["../../src/services/runtime-template-test-support.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runtime-template-test-support.d.ts","sourceRoot":"","sources":["../../src/services/runtime-template-test-support.ts"],"names":[],"mappings":"AAQA,wBAAgB,GAAG,CACjB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAezB;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAchF;AACD,wBAAsB,YAAY,CAChC,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,EAAE,GAC3D,OAAO,CAAC,MAAM,CAAC,CAwBjB"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { readdir, readFile } from "node:fs/promises";
|
|
3
|
-
import {
|
|
2
|
+
import { mkdir, mkdtemp, readdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
5
|
+
import { digestRuntimeFiles, sha256 } from "./template-manifest.js";
|
|
4
6
|
let turnSequence = 0;
|
|
5
7
|
export function run(runtime, cwd, arguments_) {
|
|
6
8
|
const result = spawnSync(process.execPath, [runtime, ...arguments_], {
|
|
@@ -29,4 +31,23 @@ export async function snapshotTree(root) {
|
|
|
29
31
|
(await readFile(path)).toString("base64"),
|
|
30
32
|
])));
|
|
31
33
|
}
|
|
34
|
+
export async function testTemplate(roots, name, files) {
|
|
35
|
+
const root = await mkdtemp(join(tmpdir(), `zaip-${name}-template-`));
|
|
36
|
+
roots.push(root);
|
|
37
|
+
const records = await Promise.all(files.map(async ([path, content]) => {
|
|
38
|
+
await mkdir(dirname(resolve(root, path)), { recursive: true });
|
|
39
|
+
await writeFile(resolve(root, path), content, "utf8");
|
|
40
|
+
return { path, sha256: sha256(content) };
|
|
41
|
+
}));
|
|
42
|
+
await writeFile(resolve(root, "template-manifest.json"), `${JSON.stringify({
|
|
43
|
+
schemaVersion: 1,
|
|
44
|
+
templateVersion: "2.2.0",
|
|
45
|
+
minimumCliVersion: "2.2.0",
|
|
46
|
+
source: "test",
|
|
47
|
+
sourceFilesSha256: sha256("test"),
|
|
48
|
+
runtimeFilesSha256: digestRuntimeFiles([]),
|
|
49
|
+
files: records,
|
|
50
|
+
})}\n`, "utf8");
|
|
51
|
+
return root;
|
|
52
|
+
}
|
|
32
53
|
//# sourceMappingURL=runtime-template-test-support.js.map
|