@zaip-ai/cli 4.1.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/dist/commands/init.d.ts.map +1 -1
  2. package/dist/commands/init.js +3 -1
  3. package/dist/commands/init.js.map +1 -1
  4. package/dist/services/adapter-hook-sources.d.ts +1 -0
  5. package/dist/services/adapter-hook-sources.d.ts.map +1 -1
  6. package/dist/services/adapter-hook-sources.js +1 -0
  7. package/dist/services/adapter-hook-sources.js.map +1 -1
  8. package/dist/services/adapters.d.ts +2 -2
  9. package/dist/services/adapters.d.ts.map +1 -1
  10. package/dist/services/adapters.js +14 -73
  11. package/dist/services/adapters.js.map +1 -1
  12. package/dist/services/adapters.test.js +1 -0
  13. package/dist/services/adapters.test.js.map +1 -1
  14. package/dist/services/agent-adapters.d.ts.map +1 -1
  15. package/dist/services/agent-adapters.js +3 -7
  16. package/dist/services/agent-adapters.js.map +1 -1
  17. package/dist/services/agent-adapters.test.js +43 -11
  18. package/dist/services/agent-adapters.test.js.map +1 -1
  19. package/dist/services/codex-config-template.js +2 -2
  20. package/dist/services/codex-conversation-lifecycle-hook.d.ts.map +1 -1
  21. package/dist/services/codex-conversation-lifecycle-hook.js +38 -14
  22. package/dist/services/codex-conversation-lifecycle-hook.js.map +1 -1
  23. package/dist/services/codex-hook-activation.d.ts +4 -0
  24. package/dist/services/codex-hook-activation.d.ts.map +1 -0
  25. package/dist/services/codex-hook-activation.js +64 -0
  26. package/dist/services/codex-hook-activation.js.map +1 -0
  27. package/dist/services/codex-model-policy.d.ts +4 -0
  28. package/dist/services/codex-model-policy.d.ts.map +1 -0
  29. package/dist/services/codex-model-policy.js +24 -0
  30. package/dist/services/codex-model-policy.js.map +1 -0
  31. package/dist/services/codex-native-tool-authority-template.d.ts +2 -0
  32. package/dist/services/codex-native-tool-authority-template.d.ts.map +1 -0
  33. package/dist/services/codex-native-tool-authority-template.js +89 -0
  34. package/dist/services/codex-native-tool-authority-template.js.map +1 -0
  35. package/dist/services/codex-pre-tool-hook-template.d.ts.map +1 -1
  36. package/dist/services/codex-pre-tool-hook-template.js +68 -126
  37. package/dist/services/codex-pre-tool-hook-template.js.map +1 -1
  38. package/dist/services/codex-pre-tool-hook-template.test.js +262 -1
  39. package/dist/services/codex-pre-tool-hook-template.test.js.map +1 -1
  40. package/dist/services/codex-pre-tool-support-template.d.ts +2 -0
  41. package/dist/services/codex-pre-tool-support-template.d.ts.map +1 -0
  42. package/dist/services/codex-pre-tool-support-template.js +401 -0
  43. package/dist/services/codex-pre-tool-support-template.js.map +1 -0
  44. package/dist/services/codex-subagent-hook-template.d.ts +1 -1
  45. package/dist/services/codex-subagent-hook-template.d.ts.map +1 -1
  46. package/dist/services/codex-subagent-hook-template.js +46 -10
  47. package/dist/services/codex-subagent-hook-template.js.map +1 -1
  48. package/dist/services/codex-validation-runtime-template.d.ts +3 -0
  49. package/dist/services/codex-validation-runtime-template.d.ts.map +1 -0
  50. package/dist/services/codex-validation-runtime-template.js +18 -0
  51. package/dist/services/codex-validation-runtime-template.js.map +1 -0
  52. package/dist/services/codex-windows-hook-command.d.ts.map +1 -1
  53. package/dist/services/codex-windows-hook-command.js +4 -2
  54. package/dist/services/codex-windows-hook-command.js.map +1 -1
  55. package/dist/services/codex-windows-hook-command.test.js +22 -0
  56. package/dist/services/codex-windows-hook-command.test.js.map +1 -1
  57. package/dist/services/developer-identity-fixture.d.ts +3 -0
  58. package/dist/services/developer-identity-fixture.d.ts.map +1 -0
  59. package/dist/services/developer-identity-fixture.js +17 -0
  60. package/dist/services/developer-identity-fixture.js.map +1 -0
  61. package/dist/services/global-codex-config-fixtures.d.ts +12 -0
  62. package/dist/services/global-codex-config-fixtures.d.ts.map +1 -0
  63. package/dist/services/global-codex-config-fixtures.js +38 -0
  64. package/dist/services/global-codex-config-fixtures.js.map +1 -0
  65. package/dist/services/global-codex-config-migration.test.d.ts +2 -0
  66. package/dist/services/global-codex-config-migration.test.d.ts.map +1 -0
  67. package/dist/services/global-codex-config-migration.test.js +301 -0
  68. package/dist/services/global-codex-config-migration.test.js.map +1 -0
  69. package/dist/services/global-codex-config-transaction.d.ts +15 -0
  70. package/dist/services/global-codex-config-transaction.d.ts.map +1 -0
  71. package/dist/services/global-codex-config-transaction.js +89 -0
  72. package/dist/services/global-codex-config-transaction.js.map +1 -0
  73. package/dist/services/global-codex-config-transaction.test.d.ts +2 -0
  74. package/dist/services/global-codex-config-transaction.test.d.ts.map +1 -0
  75. package/dist/services/global-codex-config-transaction.test.js +145 -0
  76. package/dist/services/global-codex-config-transaction.test.js.map +1 -0
  77. package/dist/services/global-codex-config.d.ts +10 -11
  78. package/dist/services/global-codex-config.d.ts.map +1 -1
  79. package/dist/services/global-codex-config.js +226 -108
  80. package/dist/services/global-codex-config.js.map +1 -1
  81. package/dist/services/global-codex-config.test.js +6 -6
  82. package/dist/services/global-codex-config.test.js.map +1 -1
  83. package/dist/services/hook-readiness.d.ts +7 -0
  84. package/dist/services/hook-readiness.d.ts.map +1 -0
  85. package/dist/services/hook-readiness.js +17 -0
  86. package/dist/services/hook-readiness.js.map +1 -0
  87. package/dist/services/logger.d.ts +2 -0
  88. package/dist/services/logger.d.ts.map +1 -1
  89. package/dist/services/logger.js +15 -1
  90. package/dist/services/logger.js.map +1 -1
  91. package/dist/services/logger.test.js +3 -0
  92. package/dist/services/logger.test.js.map +1 -1
  93. package/dist/services/managed-adapter.d.ts +6 -0
  94. package/dist/services/managed-adapter.d.ts.map +1 -0
  95. package/dist/services/managed-adapter.js +63 -0
  96. package/dist/services/managed-adapter.js.map +1 -0
  97. package/dist/services/runtime-template.test.js +3 -0
  98. package/dist/services/runtime-template.test.js.map +1 -1
  99. package/dist/types.d.ts +2 -0
  100. package/dist/types.d.ts.map +1 -1
  101. package/package.json +1 -1
  102. package/templates/.agents/README.md +8 -0
  103. package/templates/.agents/config/agents/discovery.json +1 -1
  104. package/templates/.agents/config/clients/codex/agents/zaip-agent-backend.toml +1 -2
  105. package/templates/.agents/config/clients/codex/agents/zaip-agent-brand-strategist.toml +1 -2
  106. package/templates/.agents/config/clients/codex/agents/zaip-agent-builder.toml +1 -2
  107. package/templates/.agents/config/clients/codex/agents/zaip-agent-cloud-engineer.toml +1 -2
  108. package/templates/.agents/config/clients/codex/agents/zaip-agent-data-analyst.toml +1 -2
  109. package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +1 -2
  110. package/templates/.agents/config/clients/codex/agents/zaip-agent-frontend.toml +1 -2
  111. package/templates/.agents/config/clients/codex/agents/zaip-agent-general-operations.toml +1 -2
  112. package/templates/.agents/config/clients/codex/agents/zaip-agent-governance-compliance.toml +1 -2
  113. package/templates/.agents/config/clients/codex/agents/zaip-agent-growth-strategist.toml +1 -2
  114. package/templates/.agents/config/clients/codex/agents/zaip-agent-marketing-analyst.toml +1 -2
  115. package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +1 -2
  116. package/templates/.agents/config/clients/codex/agents/zaip-agent-refactoring-specialist.toml +1 -2
  117. package/templates/.agents/config/clients/codex/agents/zaip-agent-sales-specialist.toml +1 -2
  118. package/templates/.agents/config/clients/codex/agents/zaip-agent-task-executor.toml +1 -2
  119. package/templates/.agents/config/clients/codex/agents/zaip-agent-website-developer.toml +1 -2
  120. package/templates/.agents/config/clients/codex/agents/zaip-agent-writer.toml +1 -2
  121. package/templates/.agents/dashboard/.zaip-managed.json +40 -32
  122. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-DlN5EMmD.js → DashboardHelpDialog-CkQ_y8kg.js} +60 -1
  123. package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-BRdoeTWk.js → ProjectSettingsPage-doIMhei7.js} +1 -1
  124. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-B_5u4Xnp.js → TraceabilityWorkspace-C_4ftjyB.js} +1 -1
  125. package/templates/.agents/dashboard/apps/frontend/dist/assets/{index-C4rLXrz_.js → index-Rbmpz4UO.js} +2 -2
  126. package/templates/.agents/dashboard/apps/frontend/dist/index.html +1 -1
  127. package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
  128. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  129. package/templates/.agents/dashboard/modules/operations/package.json +1 -1
  130. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  131. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  132. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  133. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  134. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +176 -176
  135. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +135 -135
  136. package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
  137. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +176 -176
  138. package/templates/.agents/dashboard/packages/local-runtime/src/server-operation-detail.test.ts +106 -0
  139. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +0 -13
  140. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-counts-every-root-execution-day-returns.test.ts +17 -230
  141. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-counts-fixture.test-support.ts +239 -0
  142. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.test.mjs +28 -8
  143. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  144. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  145. package/templates/.agents/design/.zaip-managed.json +3 -3
  146. package/templates/.agents/design/apps/catalog/package.json +1 -1
  147. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  148. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  149. package/templates/.agents/requirements/cloud-hostinger-autonomy.md +1 -1
  150. package/templates/.agents/requirements/comparative-audit.md +1 -1
  151. package/templates/.agents/requirements/context-engine.md +1 -1
  152. package/templates/.agents/requirements/cost-observability.md +1 -1
  153. package/templates/.agents/requirements/dashboard-launch.md +1 -1
  154. package/templates/.agents/requirements/data-export.md +1 -1
  155. package/templates/.agents/requirements/design-system.md +1 -1
  156. package/templates/.agents/requirements/execution-engine.md +1 -1
  157. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  158. package/templates/.agents/requirements/model-orchestration.md +1 -1
  159. package/templates/.agents/requirements/public-distribution.md +1 -1
  160. package/templates/.agents/requirements/software-architecture.md +1 -1
  161. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  162. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  163. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  164. package/templates/.agents/rules/astra-diagnostic.md +1 -1
  165. package/templates/.agents/runtime/VERSION +1 -1
  166. package/templates/.agents/runtime/bin/zaip.mjs +769 -213
  167. package/templates/.agents/runtime/checksums.sha256 +5 -5
  168. package/templates/.agents/runtime/manifest.json +1 -1
  169. package/templates/.agents/runtime/provenance.json +4 -4
  170. package/templates/.agents/runtime/sbom.cdx.json +1 -1
  171. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +1 -1
  172. package/templates/.agents/skills/zaip-skill-execution/references/correction-cycle.md +26 -0
  173. package/templates/.agents/skills/zaip-skill-execution/references/runtime-governance.md +2 -0
  174. package/templates/.agents/skills/zaip-skill-release-qualification/references/local-preparation.md +31 -1
  175. package/templates/.agents/skills/zaip-skill-setup/SKILL.md +26 -1
  176. package/templates/.agents/skills/zaip-skill-task/references/commit-policy.md +8 -1
  177. package/templates/.agents/zaip-aios/VERSION +1 -1
  178. package/templates/.agents/zaip-aios/guides/14-troubleshooting.md +52 -1
  179. package/templates/template-manifest.json +95 -87
@@ -0,0 +1,401 @@
1
+ export const CODEX_PRE_TOOL_SUPPORT_HOOK = String.raw `import { execFile } from "node:child_process";
2
+ import { createHash, randomUUID } from "node:crypto";
3
+ import { link, lstat, mkdir, readFile, realpath, unlink, writeFile } from "node:fs/promises";
4
+ import { dirname, isAbsolute, relative, resolve, toNamespacedPath } from "node:path";
5
+ import { homedir } from "node:os";
6
+ import { promisify } from "node:util";
7
+
8
+ const execute = promisify(execFile);
9
+ const pathIdentity = value => process.platform === "win32" ? toNamespacedPath(resolve(value)).toLowerCase() : resolve(value);
10
+ export const inside = (path, root) => {
11
+ const scoped = relative(root, resolve(path));
12
+ return scoped === "" || (!scoped.startsWith("..") && !isAbsolute(scoped));
13
+ };
14
+
15
+ export async function selectHookRepositoryRoot(installedRoot, cwd) {
16
+ if (typeof cwd !== "string") throw new Error("Tool execution without a workspace is blocked.");
17
+ const physicalCwd = await realpath(cwd).catch(() => null);
18
+ if (!physicalCwd || pathIdentity(physicalCwd) !== pathIdentity(cwd)) {
19
+ throw new Error("Tool workspace could not be safely resolved.");
20
+ }
21
+ if (resolve(cwd).toLowerCase() === installedRoot.toLowerCase()) return installedRoot;
22
+ if (inside(cwd, installedRoot)) {
23
+ let cursor = resolve(cwd);
24
+ while (cursor.toLowerCase() !== installedRoot.toLowerCase() &&
25
+ !(await lstat(resolve(cursor, ".git")).catch(() => null))) cursor = dirname(cursor);
26
+ if (cursor.toLowerCase() === installedRoot.toLowerCase()) return installedRoot;
27
+ }
28
+ try {
29
+ const git = async (path, ...args) => (await execute("git", ["-C", path, ...args], {
30
+ encoding: "utf8", windowsHide: true, timeout: 5000, maxBuffer: 1048576,
31
+ })).stdout.trim();
32
+ const primary = await git(installedRoot, "rev-parse", "--show-toplevel");
33
+ if ((await realpath(primary)).toLowerCase() !== installedRoot.toLowerCase()) throw new Error("primary_not_root");
34
+ const worktrees = await git(installedRoot, "worktree", "list", "--porcelain", "-z");
35
+ const registered = worktrees.split("\0").filter(line => line.startsWith("worktree ")).map(line => line.slice(9));
36
+ const candidate = await realpath(await git(cwd, "rev-parse", "--show-toplevel"));
37
+ if (!registered.some(path => resolve(path).toLowerCase() === candidate.toLowerCase()) || !inside(cwd, candidate)) {
38
+ throw new Error("worktree_not_registered");
39
+ }
40
+ const common = async path => (await realpath(await git(path, "rev-parse", "--path-format=absolute", "--git-common-dir"))).toLowerCase();
41
+ if (await common(installedRoot) !== await common(candidate)) throw new Error("common_git_dir_mismatch");
42
+ return candidate;
43
+ } catch {
44
+ throw new Error("Tool workspace could not be verified as a registered worktree.");
45
+ }
46
+ }
47
+
48
+ export async function selectHookExecutionWorkspace(installedRoot, event) {
49
+ // Codex normalizes shell tools to Bash; preserve the host-supplied literal workdir.
50
+ await selectHookRepositoryRoot(installedRoot, event.cwd);
51
+ const workdir = ["exec_command", "shell_command", "Bash"].includes(event.tool_name) ? event.tool_input?.workdir : undefined;
52
+ if (workdir !== undefined && (typeof workdir !== "string" || !isAbsolute(workdir)))
53
+ throw new Error("Tool workdir must be an absolute literal workspace.");
54
+ const cwd = workdir ?? event.cwd;
55
+ return { cwd, repositoryRoot: await selectHookRepositoryRoot(installedRoot, cwd) };
56
+ }
57
+
58
+ export function tokenizeLiteralCommand(command) {
59
+ if (command.includes("\r") || command.includes("\n")) return null;
60
+ const tokens = [];
61
+ let token = "", quote = null, started = false;
62
+ for (let index = 0; index < command.length; index++) {
63
+ const character = command[index];
64
+ if (quote !== null) {
65
+ if (quote === '"' && (character === "$" || character === String.fromCharCode(96))) return null;
66
+ if (quote === "'" && character === "'" && command[index + 1] === "'" && process.platform === "win32") {
67
+ token += "'"; index++; continue;
68
+ }
69
+ if (process.platform !== "win32" && quote === '"' && character === "\\") return null;
70
+ if (character === quote) quote = null;
71
+ else token += character;
72
+ } else if (character === "'" || character === '"') {
73
+ quote = character;
74
+ started = true;
75
+ } else if (/\s/.test(character)) {
76
+ if (started) { tokens.push(token); token = ""; started = false; }
77
+ } else {
78
+ if (";|&<>(){}$%".includes(character) || character === String.fromCharCode(96)) return null;
79
+ token += character;
80
+ started = true;
81
+ }
82
+ }
83
+ if (quote !== null) return null;
84
+ if (started) tokens.push(token);
85
+ return tokens;
86
+ }
87
+
88
+ export function shellSyntax(command) {
89
+ let quote = null, interpolation = false, redirection = false, grouping = false;
90
+ for (let index = 0; index < command.length; index += 1) {
91
+ const character = command[index];
92
+ if (quote === "'") {
93
+ if (character === "'" && command[index + 1] === "'") index += 1;
94
+ else if (character === "'") quote = null;
95
+ continue;
96
+ }
97
+ if (character === "'" && quote === null) { quote = "'"; continue; }
98
+ if (character === '"') { quote = quote === '"' ? null : '"'; continue; }
99
+ if (character === "$" && command[index + 1] === "(") interpolation = true;
100
+ if (character === String.fromCharCode(96)) interpolation = true;
101
+ if (character === ">" && quote === null) redirection = true;
102
+ if (quote === null && /[(){}]/.test(character)) grouping = true;
103
+ }
104
+ return { interpolation, redirection, grouping };
105
+ }
106
+
107
+ const shellMutationPattern = /\b(?:Set-Content|Add-Content|Out-File|New-Item|Copy-Item|Move-Item|Remove-Item|sc|ac|ni|cp|mv|mi|ri|tee|sed\b[^\r\n;|]*(?:\s-[A-Za-z]*i[^\s;|]*|--in-place(?:=[^\s;|]*)?)(?=\s|$)|git\s+apply|npm\s+version|pnpm\s+version)\b/i;
108
+
109
+ export function requiresShellMutationRun(command, tokens, cwd, repositoryRoot) {
110
+ const syntax = shellSyntax(command);
111
+ if (!syntax.interpolation && !syntax.redirection && !syntax.grouping && isLiteralReadSequence(command)) return false;
112
+ const executable = tokens?.[0] ?? "";
113
+ const canonicalCli = /^(?:node|node\.exe)$/i.test(executable) && tokens?.[1] &&
114
+ resolve(cwd, tokens[1]).toLowerCase() === resolve(repositoryRoot, ".agents/runtime/bin/zaip.mjs").toLowerCase();
115
+ const dataCommand = canonicalCli || /^(?:Write-Output|echo|printf)$/i.test(executable);
116
+ if (tokens && !syntax.interpolation && !syntax.grouping && dataCommand) {
117
+ return syntax.redirection;
118
+ }
119
+ // Unknown executables, wrappers and assignments retain the original gate.
120
+ return (tokens && isSedMutation(tokens)) || shellMutationPattern.test(command) || syntax.redirection;
121
+ }
122
+
123
+ // Recognize only literal read-only pipelines/sequences. Quoted search text is data,
124
+ // not a PowerShell alias (for example the AC prefix in acceptance-criterion IDs).
125
+ export function isLiteralReadSequence(command) {
126
+ const parts = [];
127
+ let start = 0, quote = null;
128
+ for (let index = 0; index < command.length; index++) {
129
+ const char = command[index];
130
+ if (quote !== null) {
131
+ if (char === quote) {
132
+ if (quote === "'" && command[index + 1] === "'" && process.platform === "win32") index++;
133
+ else quote = null;
134
+ }
135
+ } else if (char === "'" || char === '"') quote = char;
136
+ else if (char === ";" || char === "|") { parts.push(command.slice(start, index)); start = index + 1; }
137
+ }
138
+ if (quote !== null) return false;
139
+ parts.push(command.slice(start));
140
+ return parts.every(part => {
141
+ const values = tokenizeLiteralCommand(part);
142
+ if (!values?.length) return false;
143
+ if (/^(?:Get-Content|Measure-Object)$/i.test(values[0])) return true;
144
+ if (!/^(?:rg|rg\.exe)$/i.test(values[0])) return false;
145
+ return !values.slice(1).some(value => /^--(?:pre|hostname-bin)(?:=|$)/i.test(value));
146
+ });
147
+ }
148
+
149
+ function isSedMutation(tokens) {
150
+ const executable = ((tokens[0] ?? "").split(/[\\/]/).pop() ?? "").replace(/\.(?:exe|cmd|bat)$/i, "");
151
+ return /^sed$/i.test(executable) && tokens.slice(1).some((token) =>
152
+ /^-[A-Za-z]*i/i.test(token) || /^--in-place(?:=|$)/i.test(token) ||
153
+ /^(?:-f|--file)(?:=|$)/i.test(token) || /(?:^|[;\/#@])w(?:\s+\S|[^\s\/;#@]+\/)/.test(token));
154
+ }
155
+
156
+ export function gitInvocation(tokens, deny) {
157
+ if (tokens === null || !/^(?:git|git\.exe)$/i.test(tokens[0] ?? "")) return null;
158
+ let index = 1;
159
+ while (index < tokens.length && tokens[index].startsWith("-")) {
160
+ const rawOption = tokens[index];
161
+ const option = rawOption.toLowerCase();
162
+ if (option === "--version" || option === "--help") return null;
163
+ if (option === "--no-pager" || option === "--literal-pathspecs") { index += 1; continue; }
164
+ if (rawOption === "-c" || rawOption.startsWith("-c") || option.startsWith("--config") ||
165
+ option === "--git-dir" || option === "--work-tree" || option === "--namespace") {
166
+ deny("Git global configuration and repository-routing overrides are blocked.");
167
+ }
168
+ if (rawOption === "-C" || rawOption.startsWith("-C")) {
169
+ const directory = rawOption === "-C" ? tokens[index + 1] : rawOption.slice(2);
170
+ if (directory !== ".") deny("Git working-directory overrides outside the repository root are blocked.");
171
+ index += rawOption === "-C" ? 2 : 1;
172
+ continue;
173
+ }
174
+ deny("Unknown Git global option is blocked.");
175
+ }
176
+ const subcommand = tokens[index]?.toLowerCase();
177
+ return subcommand === undefined ? null : { subcommand, arguments: tokens.slice(index + 1) };
178
+ }
179
+
180
+ export function checkDestructivePatterns(command, patterns, deny) {
181
+ if (/^(?:Write-Output|echo|printf)\s+'(?:[^']|'')*'\s*$/i.test(command)) return;
182
+ for (const entry of patterns) if (entry.pattern.test(command)) deny(entry.reason);
183
+ }
184
+
185
+ export async function reviewedReleasePaths(command, cwd, repositoryRoot, credentialReferencePattern, deny) {
186
+ const tokens = tokenizeLiteralCommand(command);
187
+ if (!tokens || tokens[0] !== "node" || ![
188
+ "scripts/local-release-preparation.mjs", "scripts/local-release-deploy.mjs",
189
+ ].includes(tokens[1])) return [];
190
+ if (resolve(cwd) !== repositoryRoot) return [];
191
+ const helper = resolve(repositoryRoot, tokens[1]);
192
+ try {
193
+ const info = await lstat(helper);
194
+ if (!info.isFile() || info.isSymbolicLink() || resolve(await realpath(helper)) !== helper) return [];
195
+ } catch { return []; }
196
+ const preparation = tokens[1] === "scripts/local-release-preparation.mjs";
197
+ const mode = preparation ? tokens[2] : "deploy";
198
+ const start = preparation && ["prepare", "verify"].includes(mode) ? 3 : !preparation ? 2 : -1;
199
+ if (start < 0) return [];
200
+ const values = new Map();
201
+ const switches = new Set(["--initial", "--dry-run", "--offline-dry-run", "--draft-only"]);
202
+ const allowed = new Set(mode === "verify"
203
+ ? ["--repository", "--expected-repository", "--receipt", "--tarball", "--policy", "--trust-store", "--sha", "--tree-sha256", "--channel", "--version", "--profile"]
204
+ : mode === "prepare"
205
+ ? ["--repository", "--expected-repository", "--sha", "--output-dir", "--policy", "--key-id", "--private-key", "--trust-store", "--channel", "--version", "--expires-at", "--profile"]
206
+ : ["--repository", "--expected-repository", "--branch", "--output-dir", "--policy", "--private-key", "--trust-store", "--key-id", "--sha", "--channel", "--version", "--expires-at", "--profile", "--notes-file", "--inventory-file", "--from", ...switches]);
207
+ for (let i = start; i < tokens.length; i += 1) {
208
+ const flag = tokens[i];
209
+ if (!allowed.has(flag) || values.has(flag)) return [];
210
+ if (switches.has(flag)) values.set(flag, true);
211
+ else {
212
+ const value = tokens[++i];
213
+ if (!value || value.startsWith("--")) return [];
214
+ values.set(flag, value);
215
+ }
216
+ }
217
+ const required = mode === "verify"
218
+ ? ["--repository", "--expected-repository", "--receipt", "--tarball", "--policy", "--trust-store", "--sha", "--tree-sha256", "--channel", "--version"]
219
+ : mode === "prepare"
220
+ ? ["--repository", "--expected-repository", "--sha", "--output-dir", "--policy", "--key-id", "--channel", "--version", "--expires-at"]
221
+ : ["--repository", "--expected-repository", "--branch", "--output-dir", "--policy", "--key-id", "--sha", "--channel", "--version", "--expires-at", "--notes-file", "--inventory-file"];
222
+ if (required.some(flag => !values.has(flag)) || values.get("--expected-repository") !== "zaip-ai/zaip-aios" ||
223
+ !/^[a-f0-9]{40}$/i.test(values.get("--sha")) ||
224
+ (mode === "verify" && !/^[a-f0-9]{64}$/i.test(values.get("--tree-sha256"))) ||
225
+ !["beta", "latest"].includes(values.get("--channel")) ||
226
+ !/^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/.test(values.get("--version")) ||
227
+ (values.has("--profile") && !["essential", "full"].includes(values.get("--profile"))) ||
228
+ (mode !== "verify" && (
229
+ !/^[a-z0-9][a-z0-9._-]{0,63}$/i.test(values.get("--key-id")) ||
230
+ !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?Z$/.test(values.get("--expires-at")))) ||
231
+ (mode === "deploy" && (values.get("--branch") !== "main" ||
232
+ (values.has("--from") && !/^v\d+\.\d+$/.test(values.get("--from"))) ||
233
+ (values.has("--offline-dry-run") && !values.has("--dry-run")) ||
234
+ (values.has("--from") === values.has("--initial"))))) return [];
235
+ const repository = values.get("--repository");
236
+ if (!isAbsolute(repository) && repository !== ".") return [];
237
+ const releaseRoot = resolve(cwd, repository);
238
+ // The host may expose only Bash/chat cwd. This exact reviewed helper targets its explicit repository internally.
239
+ if (releaseRoot !== repositoryRoot) {
240
+ try { if (await selectHookRepositoryRoot(repositoryRoot, releaseRoot) !== releaseRoot) return []; }
241
+ catch { return []; }
242
+ }
243
+ const keyRoot = resolve(homedir(), ".codex/release-keys/zaip-aios");
244
+ const candidateRoot = resolve(homedir(), ".codex/release-candidates/zaip-aios");
245
+ const profile = values.get("--profile") ?? "essential";
246
+ const candidateName = new RegExp("^v" + values.get("--version").replace(".", "\\.") + "-" + profile + "-" + values.get("--sha").slice(0, 8) + "-[0-9]{8}$", "i");
247
+ const external = releaseRoot !== repositoryRoot ? [repository] : [];
248
+ let selectedCandidate = null;
249
+ let selectedKey = values.get("--key-id") ?? null;
250
+ for (const flag of ["--receipt", "--tarball", "--policy", "--trust-store", "--private-key", "--output-dir", "--notes-file", "--inventory-file"]) {
251
+ const value = values.get(flag);
252
+ if (!value) continue;
253
+ if ((!isAbsolute(value) && !inside(resolve(cwd, value), repositoryRoot)) ||
254
+ /[\x00-\x1f*?{}\[\]$%<>]/.test(value) || value.split(/[\\/]/).includes("..")) return [];
255
+ const path = resolve(cwd, value);
256
+ let parent = path;
257
+ while (parent !== dirname(parent)) {
258
+ try { if ((await lstat(parent)).isSymbolicLink()) deny("Reviewed release paths must not traverse links."); }
259
+ catch (error) { if (error?.code !== "ENOENT") deny("Reviewed release paths could not be safely resolved."); }
260
+ parent = dirname(parent);
261
+ }
262
+ if (inside(path, repositoryRoot) || inside(path, releaseRoot)) deny("Reviewed release artifacts and credentials must stay outside the repository.");
263
+ if (path !== (process.platform === "win32" ? value.replaceAll("/", "\\") : value)) return [];
264
+ if (flag !== "--private-key" && credentialReferencePattern.test(value)) return [];
265
+ const keyRelative = relative(keyRoot, path).replaceAll("\\", "/");
266
+ const keyMatch = /^([a-z0-9][a-z0-9._-]{0,63})\/(.+)$/i.exec(keyRelative);
267
+ const keyId = keyMatch?.[1];
268
+ const keyFile = keyMatch?.[2];
269
+ const keyPath = flag === "--trust-store" && keyFile === "trust" ||
270
+ flag === "--private-key" && keyFile === "release-private.pem" ||
271
+ flag === "--policy" && keyFile === "policy-v" + values.get("--version") + "-" + profile + ".json";
272
+ if (keyPath) {
273
+ if (!keyId || selectedKey && selectedKey !== keyId) return [];
274
+ selectedKey = keyId;
275
+ }
276
+ const candidateRelative = relative(candidateRoot, path).replaceAll("\\", "/");
277
+ const candidateParts = candidateRelative.split("/");
278
+ const candidate = candidateParts[0];
279
+ const candidatePath = candidateName.test(candidate) &&
280
+ (flag === "--output-dir" && candidateParts.length === 1 ||
281
+ flag !== "--output-dir" && candidateParts.length === 2 && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(candidateParts[1]));
282
+ if (candidatePath) {
283
+ if (selectedCandidate && selectedCandidate !== candidate) return [];
284
+ selectedCandidate = candidate;
285
+ }
286
+ if (!keyPath && !candidatePath) return [];
287
+ if (["--receipt", "--tarball", "--output-dir", "--notes-file", "--inventory-file"].includes(flag) && !candidatePath ||
288
+ ["--trust-store", "--private-key", "--policy"].includes(flag) && !keyPath) return [];
289
+ if (flag === "--receipt" && !path.endsWith("local-release-receipt.json") ||
290
+ flag === "--tarball" && !path.endsWith(".tgz") ||
291
+ flag === "--policy" && !path.endsWith(".json") ||
292
+ flag === "--notes-file" && !path.endsWith(".md") ||
293
+ flag === "--inventory-file" && !path.endsWith(".json")) return [];
294
+ external.push(value);
295
+ }
296
+ return external;
297
+ }
298
+
299
+ /** Narrow read capability, never a general external-path or executable allowlist. */
300
+ export async function reviewedContextReadPaths(command, credentialPattern) {
301
+ const tokens = tokenizeLiteralCommand(command);
302
+ if (!tokens || tokens.length < 2 || credentialPattern.test(command)) return [];
303
+ const executable = tokens[0].toLowerCase();
304
+ if (!["get-content", "rg", "rg.exe"].includes(executable)) return [];
305
+ const flags = executable === "get-content"
306
+ ? new Set(["-literalpath", "-path", "-raw", "-totalcount", "-tail", "-encoding"])
307
+ : new Set(["-n", "--line-number", "-i", "--ignore-case", "-f", "--fixed-strings", "-e", "--regexp", "-m", "--max-count", "--"]);
308
+ // -f means a pattern file in ripgrep, not fixed strings: do not permit it.
309
+ if (tokens.slice(1).some(token => token.startsWith("-") && (!flags.has(token.toLowerCase()) || token === "-f"))) return [];
310
+ const roots = ["memories", "skills", "plugins/cache"].map(part => resolve(homedir(), ".codex", part));
311
+ const paths = tokens.slice(1).filter(value => isAbsolute(value));
312
+ if (!paths.length) return [];
313
+ for (const path of paths) {
314
+ if (!roots.some(root => inside(path, root)) || !/\.(?:md|txt|json|yaml|yml|toml)$/i.test(path)) return [];
315
+ try {
316
+ const info = await lstat(path);
317
+ if (!info.isFile() || info.isSymbolicLink() || pathIdentity(await realpath(path)) !== pathIdentity(path)) return [];
318
+ } catch { return []; }
319
+ }
320
+ return paths;
321
+ }
322
+
323
+ export async function captureCliTurn(repositoryRoot, event, command) {
324
+ const tokens = tokenizeLiteralCommand(command);
325
+ if (!tokens || tokens.length < 4 || !/^(?:node|node\.exe)$/i.test(tokens[0])) return;
326
+ const script = resolve(event.cwd, tokens[1]);
327
+ if (script.toLowerCase() !== resolve(repositoryRoot, ".agents/runtime/bin/zaip.mjs").toLowerCase()) return;
328
+ if (!((tokens[2] === "conversation" || tokens[2] === "run") && tokens[3] === "start")) return;
329
+ if (tokens.includes("--help") || tokens.includes("-h")) return;
330
+ if (tokens.some(token => token.startsWith("--codex-hook-"))) throw new Error("Codex hook identity cannot be supplied by the caller.");
331
+ const threadId = event.session_id, turnId = event.turn_id, toolUseId = event.tool_use_id;
332
+ if (typeof threadId !== "string" || !/^[A-Za-z0-9_-]{1,256}$/.test(threadId) ||
333
+ typeof turnId !== "string" || !turnId || turnId.length > 256 ||
334
+ typeof toolUseId !== "string" || !toolUseId || toolUseId.length > 256 ||
335
+ (process.env.CODEX_THREAD_ID && process.env.CODEX_THREAD_ID !== threadId) ||
336
+ (process.env.CODEX_TURN_ID && process.env.CODEX_TURN_ID !== turnId)) {
337
+ throw new Error("Codex thread, turn and tool identity could not be captured for the ZAIP CLI.");
338
+ }
339
+ const directory = resolve(repositoryRoot, ".local/codex-hook-turn/v1");
340
+ const argvSha256 = createHash("sha256").update(JSON.stringify(tokens.slice(2))).digest("hex");
341
+ const digest = createHash("sha256").update(JSON.stringify([threadId, toolUseId])).digest("hex");
342
+ const target = resolve(directory, digest + ".json");
343
+ const temporary = resolve(directory, digest + "." + randomUUID() + ".tmp");
344
+ try {
345
+ let parent = repositoryRoot;
346
+ for (const part of [".local", "codex-hook-turn", "v1"]) {
347
+ parent = resolve(parent, part);
348
+ try { await mkdir(parent); } catch (error) { if (error?.code !== "EEXIST") throw error; }
349
+ const info = await lstat(parent), physical = await realpath(parent);
350
+ if (!info.isDirectory() || info.isSymbolicLink() || !inside(physical, repositoryRoot)) throw new Error("Unsafe turn receipt directory");
351
+ }
352
+ const value = {
353
+ schema_version: 1, thread_id: threadId, turn_id: turnId,
354
+ tool_use_sha256: digest, argv_sha256: argvSha256, observed_at_ms: Date.now(),
355
+ };
356
+ await writeFile(temporary, JSON.stringify(value) + "\n", { flag: "wx", mode: 0o600 });
357
+ try { await link(temporary, target); }
358
+ catch (error) {
359
+ if (error.code !== "EEXIST") throw error;
360
+ const info = await lstat(target);
361
+ if (!info.isFile() || info.isSymbolicLink() || info.size > 4096 ||
362
+ pathIdentity(await realpath(target)) !== pathIdentity(target)) throw new Error("Unsafe existing turn receipt");
363
+ const prior = JSON.parse(await readFile(target, "utf8"));
364
+ for (const key of ["schema_version", "thread_id", "turn_id", "tool_use_sha256", "argv_sha256"])
365
+ if (prior[key] !== value[key]) throw new Error("Conflicting existing turn receipt");
366
+ if (!Number.isSafeInteger(prior.observed_at_ms) || prior.observed_at_ms > Date.now()) throw new Error("Invalid turn receipt time");
367
+ }
368
+ } catch {
369
+ throw new Error("Codex turn identity could not be persisted for the ZAIP CLI.");
370
+ } finally { await unlink(temporary).catch(() => {}); }
371
+ return command + " --codex-hook-id=" + digest + " --codex-hook-thread=" + threadId;
372
+ }
373
+
374
+ export function validateInlineExecution(command, deny) {
375
+ if (shellSyntax(command).interpolation) {
376
+ deny("Nested shell execution is blocked; use a reviewed repository script.");
377
+ }
378
+ if (/(?:^|[;&|\r\n])\s*&\s*\(/.test(command)) deny("Dynamic shell invocation is blocked; use a literal command.");
379
+ const inlinePatterns = [
380
+ /\b(?:node|deno)\s+(?:-e|--eval)\b/i,
381
+ /\b(?:python(?:3)?|ruby|perl|php)\s+(?:-c|-e)\b/i,
382
+ /\bdeno\s+eval\b/i,
383
+ /\b(?:powershell|pwsh)(?:\.exe)?\b[\s\S]*\s-(?:Command|EncodedCommand|enc)\b/i,
384
+ /\bcmd(?:\.exe)?\s+\/c\b/i,
385
+ /\b(?:bash|sh|zsh|dash|fish)(?:\.exe)?\b[^\r\n;|]*\s-[A-Za-z]*c(?=\s|$)/i,
386
+ /\|\s*(?:node|deno|python(?:3)?|ruby|perl|php|powershell|pwsh|cmd)\b/i,
387
+ /\b(?:Invoke-Expression|iex)\b/i,
388
+ ];
389
+ if (inlinePatterns.some((pattern) => pattern.test(command))) {
390
+ deny("Inline or piped interpreter execution is blocked; use a reviewed repository script.");
391
+ }
392
+ }
393
+
394
+ export function rewriteCliInput(toolInput, command, commandFromCmd) {
395
+ return { hookSpecificOutput: {
396
+ hookEventName: "PreToolUse", permissionDecision: "allow",
397
+ updatedInput: { ...toolInput, command, ...(commandFromCmd === undefined ? {} : { cmd: command }) },
398
+ } };
399
+ }
400
+ `;
401
+ //# sourceMappingURL=codex-pre-tool-support-template.js.map
@@ -0,0 +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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+YpD,CAAC"}
@@ -1,2 +1,2 @@
1
- export declare const CODEX_SUBAGENT_LIFECYCLE_HOOK = "import { existsSync } from \"node:fs\";\nimport { promises as fs } from \"node:fs\";\nimport { spawn } from \"node:child_process\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { dirname, relative, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst hookPath = fileURLToPath(import.meta.url);\nconst repositoryRoot = resolve(dirname(hookPath), \"..\", \"..\");\nconst diagnosticPath = resolve(\n repositoryRoot,\n \".agents\",\n \"local\",\n \"hooks\",\n \"subagent-lifecycle.ndjson\",\n);\nconst pendingRoot = resolve(repositoryRoot, \".local\", \"hooks\", \"subagent-pending\");\n\nif (process.argv[2] === \"--capture-runner\") {\n await runCaptureRunner(process.argv[3]);\n process.exit(0);\n}\nif (process.argv[2] === \"--replay-pending\") {\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 const scoped = cwd === null ? \"..\" : relative(repositoryRoot, resolve(cwd));\n if (cwd === null || scoped.startsWith(\"..\")) process.exit(0);\n const eventName = text(event.hook_event_name);\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 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";
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,2ybAiTzC,CAAC"}
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,80fAqVzC,CAAC"}
@@ -1,26 +1,62 @@
1
- export const CODEX_SUBAGENT_LIFECYCLE_HOOK = `import { existsSync } from "node:fs";
1
+ export const CODEX_SUBAGENT_LIFECYCLE_HOOK = `import { existsSync, realpathSync } from "node:fs";
2
2
  import { promises as fs } from "node:fs";
3
- import { spawn } from "node:child_process";
3
+ import { execFileSync, spawn } from "node:child_process";
4
4
  import { createHash, randomUUID } from "node:crypto";
5
- import { dirname, relative, resolve } from "node:path";
5
+ import { dirname, isAbsolute, relative, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
8
  const hookPath = fileURLToPath(import.meta.url);
9
- const repositoryRoot = resolve(dirname(hookPath), "..", "..");
10
- const diagnosticPath = resolve(
9
+ const activation = await import("./zaip-hook-activation.mjs").catch(() => null);
10
+ const installedRoot = resolve(dirname(hookPath), "..", "..");
11
+ let repositoryRoot = installedRoot;
12
+ let diagnosticPath = resolve(
11
13
  repositoryRoot,
12
- ".agents",
13
- "local",
14
+ ".local",
14
15
  "hooks",
15
16
  "subagent-lifecycle.ndjson",
16
17
  );
17
- const pendingRoot = resolve(repositoryRoot, ".local", "hooks", "subagent-pending");
18
+ let pendingRoot = resolve(repositoryRoot, ".local", "hooks", "subagent-pending");
19
+
20
+ function inside(path, root) {
21
+ const scoped = relative(root, resolve(path));
22
+ return scoped === "" || (!scoped.startsWith("..") && !isAbsolute(scoped));
23
+ }
24
+ function selectRoot(cwd) {
25
+ if (typeof cwd !== "string") return false;
26
+ try {
27
+ if (realpathSync(cwd).toLowerCase() !== resolve(cwd).toLowerCase()) return false;
28
+ let nestedGit = false;
29
+ if (inside(cwd, installedRoot)) {
30
+ for (let cursor = resolve(cwd); cursor.toLowerCase() !== installedRoot.toLowerCase(); cursor = dirname(cursor)) {
31
+ if (existsSync(resolve(cursor, ".git"))) { nestedGit = true; break; }
32
+ }
33
+ }
34
+ if (!inside(cwd, installedRoot) || nestedGit) {
35
+ const git = (root, ...args) => execFileSync("git", ["-C", root, ...args], {
36
+ encoding: "utf8", windowsHide: true, timeout: 5000,
37
+ }).trim();
38
+ if (realpathSync(git(installedRoot, "rev-parse", "--show-toplevel")).toLowerCase() !== installedRoot.toLowerCase()) return false;
39
+ const registered = git(installedRoot, "worktree", "list", "--porcelain", "-z")
40
+ .split("\\0").filter(line => line.startsWith("worktree ")).map(line => resolve(line.slice(9)).toLowerCase());
41
+ const candidate = realpathSync(git(cwd, "rev-parse", "--show-toplevel"));
42
+ if (!registered.includes(candidate.toLowerCase()) || !inside(cwd, candidate)) return false;
43
+ const common = root => realpathSync(git(root, "rev-parse", "--path-format=absolute", "--git-common-dir")).toLowerCase();
44
+ if (common(installedRoot) !== common(candidate)) return false;
45
+ repositoryRoot = candidate;
46
+ }
47
+ diagnosticPath = resolve(repositoryRoot, ".local", "hooks", "subagent-lifecycle.ndjson");
48
+ pendingRoot = resolve(repositoryRoot, ".local", "hooks", "subagent-pending");
49
+ return true;
50
+ } catch { return false; }
51
+ }
18
52
 
19
53
  if (process.argv[2] === "--capture-runner") {
54
+ if (!selectRoot(process.cwd())) process.exit(0);
20
55
  await runCaptureRunner(process.argv[3]);
21
56
  process.exit(0);
22
57
  }
23
58
  if (process.argv[2] === "--replay-pending") {
59
+ if (!selectRoot(process.cwd())) process.exit(0);
24
60
  const files = await fs.readdir(pendingRoot).catch(() => []);
25
61
  let replayed = 0;
26
62
  for (const file of files.filter((name) => /^[a-f0-9]{64}\\.json$/.test(name)).slice(0, 256)) {
@@ -37,9 +73,9 @@ for await (const chunk of process.stdin) raw += chunk;
37
73
  try {
38
74
  const event = JSON.parse(raw);
39
75
  const cwd = text(event.cwd);
40
- const scoped = cwd === null ? ".." : relative(repositoryRoot, resolve(cwd));
41
- if (cwd === null || scoped.startsWith("..")) process.exit(0);
76
+ if (cwd === null || !selectRoot(cwd)) process.exit(0);
42
77
  const eventName = text(event.hook_event_name);
78
+ activation?.recordActivation(repositoryRoot, event);
43
79
  const lifecycleEvent =
44
80
  eventName === "SubagentStart" ? "start" : eventName === "SubagentStop" ? "stop" : null;
45
81
  const session = text(event.session_id);
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiT5C,CAAC"}
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqV5C,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** Appended to native authority; receives the shared literal parser. */
2
+ export declare const CODEX_VALIDATION_RUNTIME_SUPPORT: string;
3
+ //# sourceMappingURL=codex-validation-runtime-template.d.ts.map
@@ -0,0 +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,QAe5C,CAAC"}
@@ -0,0 +1,18 @@
1
+ /** Appended to native authority; receives the shared literal parser. */
2
+ export const CODEX_VALIDATION_RUNTIME_SUPPORT = String.raw `
3
+ /** Candidate validation requires this exact host runtime, not arbitrary executables. */
4
+ export async function reviewedValidationRuntimePaths(command, tokenizeLiteralCommand) {
5
+ const tokens = tokenizeLiteralCommand(command);
6
+ if (!tokens || !/^(?:node|node\.exe)$/i.test(tokens[0]) ||
7
+ !/^\.agents[\\/]runtime[\\/]bin[\\/]zaip\.mjs$/.test(tokens[1] ?? "") ||
8
+ tokens[2] !== "validation" || !["batch", "execute"].includes(tokens[3])) return [];
9
+ const positions = tokens.flatMap((value, index) => value === "--executable" ? [index] : []);
10
+ if (positions.length !== 1) return [];
11
+ const path = tokens[positions[0] + 1];
12
+ if (!path || !isAbsolute(path) || command.split(path).length !== 2) return [];
13
+ try {
14
+ return await realpath(path) === await realpath(process.execPath) ? [path] : [];
15
+ } catch { return []; }
16
+ }
17
+ `;
18
+ //# sourceMappingURL=codex-validation-runtime-template.js.map
@@ -0,0 +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;;;;;;;;;;;;;;;CAezD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"codex-windows-hook-command.d.ts","sourceRoot":"","sources":["../../src/services/codex-windows-hook-command.ts"],"names":[],"mappings":"AAEA,wEAAwE;AACxE,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAKpF"}
1
+ {"version":3,"file":"codex-windows-hook-command.d.ts","sourceRoot":"","sources":["../../src/services/codex-windows-hook-command.ts"],"names":[],"mappings":"AAEA,wEAAwE;AACxE,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAQpF"}