@nowcrew/daemon 0.6.16 → 0.6.17

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 (42) hide show
  1. package/dist/agent-ability/runtime-context.js +7 -1
  2. package/dist/atomic-private-write.js +54 -1
  3. package/dist/automatic-install-target.js +40 -11
  4. package/dist/console.js +9 -0
  5. package/dist/control-plane-url.js +2 -2
  6. package/dist/daemon-migration-controller.js +198 -0
  7. package/dist/daemon-migration-wiring.js +22 -0
  8. package/dist/daemon-update-eligibility.js +1 -1
  9. package/dist/directory-projection-identity.js +32 -0
  10. package/dist/directory-projection.js +922 -0
  11. package/dist/execution-protocol.js +78 -11
  12. package/dist/execution-runner.js +50 -2
  13. package/dist/i18n.js +1 -0
  14. package/dist/local-execution-prompt.js +57 -0
  15. package/dist/local-executor.js +99 -40
  16. package/dist/machine-info.js +45 -9
  17. package/dist/normalize.js +5 -0
  18. package/dist/profile-layout.js +41 -0
  19. package/dist/project-skills/controller.js +74 -14
  20. package/dist/project-skills/execution-adapter.js +11 -0
  21. package/dist/project-skills/initialized-reconciler.js +20 -0
  22. package/dist/project-skills/projection-set-switch.js +419 -0
  23. package/dist/project-skills/projection-state-domain.js +153 -0
  24. package/dist/project-skills/projection-state-store.js +841 -0
  25. package/dist/project-skills/projection-state-transaction.js +318 -0
  26. package/dist/project-skills/projection-state.js +3 -0
  27. package/dist/project-skills/reconciler.js +299 -68
  28. package/dist/project-skills/runtime-warning.js +6 -0
  29. package/dist/project-skills/scanner.js +30 -1
  30. package/dist/project-skills/types.js +9 -0
  31. package/dist/project-workspaces/resolver.js +179 -0
  32. package/dist/project-workspaces/types.js +1 -0
  33. package/dist/prompt.js +40 -0
  34. package/dist/runtimes/claude.js +235 -4
  35. package/dist/runtimes/codex-app-server-runner.js +92 -23
  36. package/dist/runtimes/codex-contract.js +123 -0
  37. package/dist/runtimes/codex.js +2 -0
  38. package/dist/serve.js +31 -17
  39. package/dist/session.js +3 -0
  40. package/dist/supervised-runtime.js +12 -4
  41. package/dist/workspace.js +14 -5
  42. package/package.json +1 -1
@@ -0,0 +1,179 @@
1
+ import { constants } from "node:fs";
2
+ import { access, lstat, realpath, stat } from "node:fs/promises";
3
+ import { posix, win32 } from "node:path";
4
+ const UNAVAILABLE_CODE = "project_context_unavailable";
5
+ const WINDOWS_EXTENDED_PREFIX = "\\\\?\\";
6
+ const WINDOWS_EXTENDED_UNC_PREFIX = "\\\\?\\UNC\\";
7
+ const WINDOWS_DEVICE_PREFIX = "\\\\.\\";
8
+ const WINDOWS_DRIVE_ABSOLUTE = /^[A-Za-z]:\\/u;
9
+ export class ProjectContextUnavailableError extends Error {
10
+ projectId;
11
+ code = UNAVAILABLE_CODE;
12
+ constructor(projectId) {
13
+ super(UNAVAILABLE_CODE);
14
+ this.projectId = projectId;
15
+ Object.defineProperty(this, "name", {
16
+ configurable: true,
17
+ value: "ProjectContextUnavailableError",
18
+ });
19
+ }
20
+ toJSON() {
21
+ return { projectId: this.projectId, code: this.code };
22
+ }
23
+ }
24
+ const compareProjectIds = (left, right) => {
25
+ if (left < right)
26
+ return -1;
27
+ if (left > right)
28
+ return 1;
29
+ return 0;
30
+ };
31
+ const warningFor = (projectId) => Object.freeze({
32
+ projectId,
33
+ code: UNAVAILABLE_CODE,
34
+ });
35
+ const nodeFilesystem = { lstat, stat, access, realpath };
36
+ /**
37
+ * Shape/access preflight only. In particular, fs.access cannot prove effective Windows ACL access;
38
+ * process launch remains authoritative and Windows capability enablement is owned outside this module.
39
+ */
40
+ const preflightDirectory = async (root, filesystem) => {
41
+ try {
42
+ const entry = await filesystem.lstat(root);
43
+ if (!entry.isDirectory()) {
44
+ if (!entry.isSymbolicLink())
45
+ return false;
46
+ if (!(await filesystem.stat(root)).isDirectory())
47
+ return false;
48
+ }
49
+ await filesystem.access(root, constants.R_OK | constants.X_OK);
50
+ return true;
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ };
56
+ const registryByProjectId = (registrations) => new Map(registrations.map((registration) => [registration.projectId, registration]));
57
+ const hasUncServerAndShare = (root, prefixLength) => {
58
+ const [server, share] = root.slice(prefixLength).split("\\");
59
+ return server !== undefined && server.length > 0 && share !== undefined && share.length > 0;
60
+ };
61
+ const normalizeWindowsRoot = (root) => {
62
+ const normalized = win32.normalize(root);
63
+ const lower = normalized.toLowerCase();
64
+ const extendedPrefix = WINDOWS_EXTENDED_PREFIX.toLowerCase();
65
+ const extendedUncPrefix = WINDOWS_EXTENDED_UNC_PREFIX.toLowerCase();
66
+ if (normalized.startsWith(WINDOWS_DEVICE_PREFIX))
67
+ return null;
68
+ if (lower.startsWith(extendedUncPrefix)) {
69
+ return hasUncServerAndShare(normalized, WINDOWS_EXTENDED_UNC_PREFIX.length)
70
+ ? normalized
71
+ : null;
72
+ }
73
+ if (lower.startsWith(extendedPrefix)) {
74
+ return WINDOWS_DRIVE_ABSOLUTE.test(normalized.slice(WINDOWS_EXTENDED_PREFIX.length))
75
+ ? normalized
76
+ : null;
77
+ }
78
+ if (WINDOWS_DRIVE_ABSOLUTE.test(normalized))
79
+ return win32.resolve(normalized);
80
+ if (!normalized.startsWith("\\\\"))
81
+ return null;
82
+ return hasUncServerAndShare(normalized, 2) ? win32.resolve(normalized) : null;
83
+ };
84
+ const normalizeRegisteredRoot = (root, platform) => {
85
+ if (root.length === 0)
86
+ return null;
87
+ if (platform === "win32")
88
+ return normalizeWindowsRoot(root);
89
+ return posix.isAbsolute(root) ? posix.resolve(root) : null;
90
+ };
91
+ const rootIdentity = (root, platform) => {
92
+ if (platform !== "win32")
93
+ return root;
94
+ const lower = root.toLowerCase();
95
+ if (lower.startsWith(WINDOWS_EXTENDED_UNC_PREFIX.toLowerCase())) {
96
+ return win32.normalize(`\\\\${root.slice(WINDOWS_EXTENDED_UNC_PREFIX.length)}`).toLowerCase();
97
+ }
98
+ if (lower.startsWith(WINDOWS_EXTENDED_PREFIX.toLowerCase())) {
99
+ return win32.normalize(root.slice(WINDOWS_EXTENDED_PREFIX.length)).toLowerCase();
100
+ }
101
+ return root.toLowerCase();
102
+ };
103
+ export async function resolveProjectContext(snapshot, registry, options = {}) {
104
+ if (snapshot.projectIds.length === 0 && snapshot.primaryProjectId === undefined) {
105
+ return Object.freeze({
106
+ secondary: Object.freeze([]),
107
+ warnings: Object.freeze([]),
108
+ });
109
+ }
110
+ const platform = options.platform ?? process.platform;
111
+ const filesystem = options.filesystem ?? nodeFilesystem;
112
+ const sortedProjectIds = [...snapshot.projectIds].sort(compareProjectIds);
113
+ const primaryProjectId = snapshot.primaryProjectId;
114
+ let registrations;
115
+ try {
116
+ registrations = await registry.list();
117
+ }
118
+ catch {
119
+ registrations = Object.freeze([]);
120
+ }
121
+ const localProjects = registryByProjectId(registrations);
122
+ const seenRoots = new Set();
123
+ const resolveOne = async (projectId) => {
124
+ const registration = localProjects.get(projectId);
125
+ if (registration === undefined)
126
+ return { status: "unavailable" };
127
+ const root = normalizeRegisteredRoot(registration.root, platform);
128
+ if (root === null || !(await preflightDirectory(root, filesystem))) {
129
+ return { status: "unavailable" };
130
+ }
131
+ let physicalRoot;
132
+ try {
133
+ physicalRoot = await filesystem.realpath(root);
134
+ }
135
+ catch {
136
+ return { status: "unavailable" };
137
+ }
138
+ const normalizedPhysicalRoot = normalizeRegisteredRoot(physicalRoot, platform);
139
+ if (normalizedPhysicalRoot === null)
140
+ return { status: "unavailable" };
141
+ const identity = rootIdentity(normalizedPhysicalRoot, platform);
142
+ if (seenRoots.has(identity))
143
+ return { status: "duplicate" };
144
+ seenRoots.add(identity);
145
+ return {
146
+ status: "resolved",
147
+ project: Object.freeze({ projectId, root }),
148
+ };
149
+ };
150
+ let primary;
151
+ if (primaryProjectId !== undefined) {
152
+ if (!sortedProjectIds.includes(primaryProjectId)) {
153
+ throw new ProjectContextUnavailableError(primaryProjectId);
154
+ }
155
+ const resolvedPrimary = await resolveOne(primaryProjectId);
156
+ if (resolvedPrimary.status !== "resolved") {
157
+ throw new ProjectContextUnavailableError(primaryProjectId);
158
+ }
159
+ primary = resolvedPrimary.project;
160
+ }
161
+ const secondary = [];
162
+ const warnings = [];
163
+ for (const projectId of sortedProjectIds) {
164
+ if (projectId === primaryProjectId)
165
+ continue;
166
+ const resolved = await resolveOne(projectId);
167
+ if (resolved.status === "unavailable")
168
+ warnings.push(warningFor(projectId));
169
+ else if (resolved.status === "resolved")
170
+ secondary.push(resolved.project);
171
+ }
172
+ const base = {
173
+ secondary: Object.freeze(secondary),
174
+ warnings: Object.freeze(warnings),
175
+ };
176
+ return primary === undefined
177
+ ? Object.freeze(base)
178
+ : Object.freeze({ primary, ...base });
179
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/prompt.js CHANGED
@@ -20,6 +20,46 @@ const EXTERNAL_RESULT_READABILITY = `
20
20
  - 有足够重点时,只加粗三到五处真正决定性的数字、最终状态、风险、截止时间、负责人或行动项;不足三处时宁缺毋滥。加粗范围要短,不要整段加粗,不要把每个数字都加粗,不得强化未经验证的判断。
21
21
  - 需要颜色层级时,只在确有对应语义的短加粗重点前使用一个彩色圆点:🟢 绿色圆点表示已验证成功或健康,🟠 橙色圆点表示风险、临期或需要关注,🔴 红色圆点表示失败、阻塞或严重异常,🔵 蓝色圆点表示负责人、行动项或关键中性数据。每个短加粗重点前最多放一个;不要给普通段落、标题或每个要点都加标记。
22
22
  - 使用标题、列表、引用、彩色圆点和加粗形成在企微原生流式消息中稳定可见的层级;不得输出 \`<font>\` 或其它 HTML 标色标签。`;
23
+ const compareProjectIds = (left, right) => {
24
+ if (left < right)
25
+ return -1;
26
+ if (left > right)
27
+ return 1;
28
+ return 0;
29
+ };
30
+ const JSON_SINGLE_LINE_ESCAPE = /[\u007f-\u009f\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]/gu;
31
+ const escapeJsonSingleLineControls = (json) => json.replace(JSON_SINGLE_LINE_ESCAPE, (character) => `\\u${character.codePointAt(0).toString(16).padStart(4, "0")}`);
32
+ const canonicalJsonString = (value) => escapeJsonSingleLineControls(JSON.stringify(value));
33
+ const canonicalJsonRecord = (value) => escapeJsonSingleLineControls(JSON.stringify(value));
34
+ /**
35
+ * Resolved paths are daemon-local facts. JSON rows keep paths with spaces or Unicode as data,
36
+ * never as shell fragments; warning rows expose only logical IDs and stable codes, never roots.
37
+ */
38
+ export function buildRuntimeWorkspacePrompt(projectContext) {
39
+ if (projectContext?.primary === undefined)
40
+ return "";
41
+ const projectRoles = [
42
+ { role: "primary", ...projectContext.primary },
43
+ ...[...projectContext.secondary]
44
+ .sort((left, right) => compareProjectIds(left.projectId, right.projectId))
45
+ .map((project) => ({ role: "secondary", ...project })),
46
+ ].map(({ role, projectId, root }) => `- ${canonicalJsonRecord({ role, projectId, root })}`);
47
+ const projectWarnings = [...projectContext.warnings]
48
+ .sort((left, right) => compareProjectIds(left.projectId, right.projectId)
49
+ || compareProjectIds(left.code, right.code))
50
+ .map(({ projectId, code }) => `- ${canonicalJsonRecord({ projectId, code })}`);
51
+ const warningsBlock = projectWarnings.length === 0
52
+ ? ""
53
+ : `\n\n## Daemon-local project warnings\n${projectWarnings.join("\n")}`;
54
+ return `\n\n## Runtime workspace boundaries
55
+ Repository cwd: ${canonicalJsonString(projectContext.primary.root)}. Use it for source, builds, tests, and Git operations.
56
+ Task state directory: $CREW_TASK_DIR. Put downloads, diagnostics, drafts, reports, and temporary artifacts there.
57
+ Work log: $CREW_TASK_LOG. Do not treat the repository as NowWork task storage.
58
+ Runtime permission and sandbox rules still govern repository writes; project binding grants no additional write access.
59
+
60
+ ## Daemon-local project roles
61
+ ${projectRoles.join("\n")}${warningsBlock}`;
62
+ }
23
63
  export function capWorkLogForInject(workLog, cap = WORKLOG_INJECT_CAP) {
24
64
  if (workLog.length <= cap)
25
65
  return workLog;
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Claude Code runtime 适配:print + stream-json 模式,headless 驱动。
3
3
  */
4
+ import { realpath } from "node:fs/promises";
5
+ import { posix, win32 } from "node:path";
4
6
  // cross-spawn:win32 上 npm CLI 是 .cmd shim,node 原生 spawn 不带 shell 无法执行(ENOENT/EINVAL)
5
7
  import spawn from "cross-spawn";
6
8
  // Claude Code 原生 --effort 档位(claude 2.1.196 实测:--help 与非法值告警均枚举这五档)。
@@ -9,6 +11,237 @@ export const CLAUDE_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
9
11
  // 未配置/非法档位时的默认思考强度:medium 开启原生 thinking(终端透传要展示思考过程),
10
12
  // 又不至于 high/max 的 token 开销;agent 配置白名单档位可覆盖。
11
13
  export const CLAUDE_DEFAULT_EFFORT = "medium";
14
+ export const CLAUDE_ADDITIONAL_DIRECTORY_INSTRUCTIONS_MIN_VERSION = "2.1.237";
15
+ export const CLAUDE_ADDITIONAL_DIRECTORY_INSTRUCTIONS_ENV = "CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD";
16
+ const CLAUDE_VERSION_PROBE_TIMEOUT_MS = 1_000;
17
+ const CLAUDE_VERSION_PROBE_KILL_GRACE_MS = 250;
18
+ const CLAUDE_VERSION_PROBE_MAX_BUFFER = 8 * 1024;
19
+ const CLAUDE_VERSION_PROBE_SUCCESS_TTL_MS = 30_000;
20
+ const CLAUDE_REALPATH_TIMEOUT_MS = 250;
21
+ const ignoreTerminalProbeError = () => undefined;
22
+ const defaultVersionProbeSpawner = (file, args, options) => spawn(file, [...args], options);
23
+ const versionProbeCache = new Map();
24
+ const probeClaudeVersionOnce = (bin, env, start, dependencies) => {
25
+ // A .cmd launch creates an unmanaged cmd.exe process tree. Without a Job Object or another
26
+ // verifiable owner, a timeout cannot safely distinguish that tree from PID reuse. Fail closed:
27
+ // directory access remains available, but nested CLAUDE.md auto-loading stays unverified.
28
+ if ((dependencies.platform ?? process.platform) === "win32")
29
+ return Promise.resolve(null);
30
+ let child;
31
+ try {
32
+ child = start(bin, ["--version"], {
33
+ env,
34
+ stdio: ["ignore", "pipe", "pipe"],
35
+ windowsHide: true,
36
+ shell: false,
37
+ });
38
+ }
39
+ catch {
40
+ return Promise.resolve(null);
41
+ }
42
+ return new Promise((resolve) => {
43
+ let settled = false;
44
+ let timedOut = false;
45
+ let closed = false;
46
+ let output = Buffer.alloc(0);
47
+ let resolveClose;
48
+ const close = new Promise((closeResolve) => { resolveClose = closeResolve; });
49
+ const waitForClose = async () => {
50
+ let deadline;
51
+ try {
52
+ await Promise.race([
53
+ close,
54
+ new Promise((waitResolve) => {
55
+ deadline = setTimeout(waitResolve, CLAUDE_VERSION_PROBE_KILL_GRACE_MS);
56
+ }),
57
+ ]);
58
+ }
59
+ finally {
60
+ if (deadline !== undefined)
61
+ clearTimeout(deadline);
62
+ }
63
+ };
64
+ const append = (chunk) => {
65
+ if (output.length >= CLAUDE_VERSION_PROBE_MAX_BUFFER)
66
+ return;
67
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
68
+ const remaining = CLAUDE_VERSION_PROBE_MAX_BUFFER - output.length;
69
+ output = Buffer.concat([output, bytes.subarray(0, remaining)]);
70
+ };
71
+ child.stdout?.on("data", append);
72
+ child.stderr?.on("data", append);
73
+ const observeClose = () => {
74
+ if (closed)
75
+ return;
76
+ closed = true;
77
+ resolveClose();
78
+ };
79
+ const cleanup = () => {
80
+ child.stdout?.off("data", append);
81
+ child.stderr?.off("data", append);
82
+ child.off("error", onError);
83
+ child.off("close", onClose);
84
+ // ChildProcess can report a late error after its final close deadline. Retain one static sink:
85
+ // it captures no local state, creates no timer, and remains safe until the child is collected.
86
+ child.off("error", ignoreTerminalProbeError);
87
+ child.on("error", ignoreTerminalProbeError);
88
+ child.stdout?.destroy();
89
+ child.stderr?.destroy();
90
+ };
91
+ const settle = (value) => {
92
+ if (settled)
93
+ return;
94
+ settled = true;
95
+ clearTimeout(timeoutTimer);
96
+ cleanup();
97
+ resolve(value);
98
+ };
99
+ const onError = () => {
100
+ if (!timedOut)
101
+ settle(null);
102
+ };
103
+ const onClose = (code) => {
104
+ observeClose();
105
+ if (timedOut)
106
+ return;
107
+ const value = output.toString("utf8").trim();
108
+ settle(code === 0 && value !== "" ? value : null);
109
+ };
110
+ const timeoutTimer = setTimeout(() => {
111
+ timedOut = true;
112
+ void (async () => {
113
+ try {
114
+ child.kill("SIGTERM");
115
+ }
116
+ catch { /* process already exited */ }
117
+ await waitForClose();
118
+ if (!closed) {
119
+ try {
120
+ child.kill("SIGKILL");
121
+ }
122
+ catch { /* process already exited */ }
123
+ await waitForClose();
124
+ }
125
+ settle(null);
126
+ })();
127
+ }, CLAUDE_VERSION_PROBE_TIMEOUT_MS);
128
+ child.on("error", onError);
129
+ child.on("close", onClose);
130
+ });
131
+ };
132
+ /** Bounded local-only probe; errors and output never cross the execution protocol. */
133
+ export function probeClaudeVersion(bin, env, start = defaultVersionProbeSpawner, dependencies = {}) {
134
+ if (start !== defaultVersionProbeSpawner) {
135
+ return probeClaudeVersionOnce(bin, env, start, dependencies);
136
+ }
137
+ const key = JSON.stringify([dependencies.platform ?? process.platform, bin, env.PATH ?? ""]);
138
+ const now = Date.now();
139
+ const cached = versionProbeCache.get(key);
140
+ if (cached !== undefined && (cached.expiresAt === null || cached.expiresAt > now)) {
141
+ return cached.promise;
142
+ }
143
+ if (cached !== undefined)
144
+ versionProbeCache.delete(key);
145
+ const pending = probeClaudeVersionOnce(bin, env, start, dependencies);
146
+ const inFlight = { promise: pending, expiresAt: null };
147
+ versionProbeCache.set(key, inFlight);
148
+ void pending.then((version) => {
149
+ if (versionProbeCache.get(key) !== inFlight)
150
+ return;
151
+ if (version === null) {
152
+ versionProbeCache.delete(key);
153
+ return;
154
+ }
155
+ versionProbeCache.set(key, {
156
+ promise: Promise.resolve(version),
157
+ expiresAt: Date.now() + CLAUDE_VERSION_PROBE_SUCCESS_TTL_MS,
158
+ });
159
+ });
160
+ return pending;
161
+ }
162
+ const canonicalDirectoryIdentity = (directory, platform) => {
163
+ if (platform !== "win32")
164
+ return posix.normalize(directory);
165
+ let normalized = directory.replaceAll("/", "\\");
166
+ const lower = normalized.toLowerCase();
167
+ if (lower.startsWith("\\\\?\\unc\\"))
168
+ normalized = `\\\\${normalized.slice(8)}`;
169
+ else if (lower.startsWith("\\\\?\\"))
170
+ normalized = normalized.slice(4);
171
+ return win32.normalize(normalized).toLowerCase();
172
+ };
173
+ export function orderedUniqueClaudeDirectories(directories, excluded = [], platform = process.platform) {
174
+ const seen = new Set(excluded.map((directory) => canonicalDirectoryIdentity(directory, platform)));
175
+ const ordered = [];
176
+ for (const directory of directories) {
177
+ const identity = canonicalDirectoryIdentity(directory, platform);
178
+ if (seen.has(identity))
179
+ continue;
180
+ seen.add(identity);
181
+ ordered.push(directory);
182
+ }
183
+ return Object.freeze(ordered);
184
+ }
185
+ const canonicalizeWithinDeadline = async (directory, canonicalize) => {
186
+ let timeout;
187
+ try {
188
+ return await Promise.race([
189
+ Promise.resolve().then(() => canonicalize(directory)).catch(() => directory),
190
+ new Promise((resolve) => {
191
+ timeout = setTimeout(() => resolve(directory), CLAUDE_REALPATH_TIMEOUT_MS);
192
+ }),
193
+ ]);
194
+ }
195
+ finally {
196
+ if (timeout !== undefined)
197
+ clearTimeout(timeout);
198
+ }
199
+ };
200
+ /** Preserve caller order while collapsing physical aliases; inaccessible paths fall back to lexical identity. */
201
+ export async function resolveOrderedUniqueClaudeDirectories(directories, excluded = [], canonicalize = realpath, platform = process.platform) {
202
+ const identity = async (directory) => {
203
+ const physical = await canonicalizeWithinDeadline(directory, canonicalize);
204
+ return canonicalDirectoryIdentity(physical, platform);
205
+ };
206
+ const [excludedIdentities, identities] = await Promise.all([
207
+ Promise.all(excluded.map(identity)),
208
+ Promise.all(directories.map(identity)),
209
+ ]);
210
+ const seen = new Set(excludedIdentities);
211
+ const ordered = [];
212
+ for (let index = 0; index < directories.length; index += 1) {
213
+ const directoryIdentity = identities[index];
214
+ if (seen.has(directoryIdentity))
215
+ continue;
216
+ seen.add(directoryIdentity);
217
+ ordered.push(directories[index]);
218
+ }
219
+ return Object.freeze(ordered);
220
+ }
221
+ const parseVersion = (value) => {
222
+ const match = /(?:^|[^0-9])(\d+)\.(\d+)\.(\d+)(?:[^0-9]|$)/u.exec(value);
223
+ if (match === null)
224
+ return null;
225
+ const version = match.slice(1, 4).map(Number);
226
+ return version.every(Number.isSafeInteger)
227
+ ? [version[0], version[1], version[2]]
228
+ : null;
229
+ };
230
+ export function isClaudeAdditionalDirectoryInstructionsSupported(versionOutput) {
231
+ if (versionOutput === null)
232
+ return false;
233
+ const version = parseVersion(versionOutput);
234
+ const minimum = parseVersion(CLAUDE_ADDITIONAL_DIRECTORY_INSTRUCTIONS_MIN_VERSION);
235
+ if (version === null || minimum === null)
236
+ return false;
237
+ for (let index = 0; index < version.length; index += 1) {
238
+ if (version[index] > minimum[index])
239
+ return true;
240
+ if (version[index] < minimum[index])
241
+ return false;
242
+ }
243
+ return true;
244
+ }
12
245
  export function buildClaudeArgs(input) {
13
246
  const args = [
14
247
  "--print",
@@ -28,10 +261,8 @@ export function buildClaudeArgs(input) {
28
261
  if (input.sessionId) {
29
262
  args.push(input.resume ? "--resume" : "--session-id", input.sessionId);
30
263
  }
31
- if (input.projectSkillsDirectory)
32
- args.push("--add-dir", input.projectSkillsDirectory);
33
- if (input.agentRootDirectory)
34
- args.push("--add-dir", input.agentRootDirectory);
264
+ for (const directory of input.additionalDirectories ?? [])
265
+ args.push("--add-dir", directory);
35
266
  if (input.effectivePermission === undefined) {
36
267
  if (input.dangerous)
37
268
  args.push("--dangerously-skip-permissions");