@nowcrew/daemon 0.6.7 → 0.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/codex-startup-stage.js +54 -0
- package/dist/config.js +1 -1
- package/dist/execution-supervisor.js +15 -6
- package/dist/local-executor.js +31 -3
- package/dist/main.js +0 -0
- package/dist/runtimes/codex-app-server-runner.js +1 -1
- package/dist/serve.js +1 -1
- package/dist/skills.js +36 -15
- package/package.json +10 -9
package/README.md
CHANGED
|
@@ -378,7 +378,7 @@ CREW_EXECUTION_MAX_QUEUED_TOTAL=128
|
|
|
378
378
|
CREW_EXECUTION_MAX_STARTING_TOTAL=1
|
|
379
379
|
CREW_EXECUTION_MAX_STARTING_PER_RUNTIME=1
|
|
380
380
|
CREW_EXECUTION_START_GAP_MS=3000
|
|
381
|
-
CREW_EXECUTION_STARTUP_TIMEOUT_MS=
|
|
381
|
+
CREW_EXECUTION_STARTUP_TIMEOUT_MS=120000
|
|
382
382
|
```
|
|
383
383
|
|
|
384
384
|
`MAX_PARALLEL_TOTAL` is the machine-wide process cap shared by protocol-v1 and legacy work.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const STAGE_LINE_PREFIX_CAP = 512;
|
|
2
|
+
const STAGE_PATTERN = /^\[codex-app-server\] stage=(spawn|initialize|thread_start|thread_resume|model_init) status=(start|ok|error|timeout) attempt=(\d+) elapsed_ms=(\d+)(?:\s|$)/;
|
|
3
|
+
function parseBoundedStageLine(line) {
|
|
4
|
+
const match = STAGE_PATTERN.exec(line);
|
|
5
|
+
if (match === null)
|
|
6
|
+
return null;
|
|
7
|
+
const [, rawStage, status, rawAttempt, rawStageMs] = match;
|
|
8
|
+
const attempt = Number(rawAttempt);
|
|
9
|
+
const stageMs = Number(rawStageMs);
|
|
10
|
+
if (!Number.isSafeInteger(attempt) || attempt < 1
|
|
11
|
+
|| !Number.isSafeInteger(stageMs) || stageMs < 0)
|
|
12
|
+
return null;
|
|
13
|
+
if (rawStage === "thread_resume") {
|
|
14
|
+
return { stage: "thread_start", status: status, attempt, stageMs, resumed: true };
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
stage: rawStage,
|
|
18
|
+
status: status,
|
|
19
|
+
attempt,
|
|
20
|
+
stageMs,
|
|
21
|
+
...(rawStage === "thread_start" ? { resumed: false } : {}),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export class CodexStartupStageParser {
|
|
25
|
+
onStage;
|
|
26
|
+
pendingPrefix = "";
|
|
27
|
+
constructor(onStage) {
|
|
28
|
+
this.onStage = onStage;
|
|
29
|
+
}
|
|
30
|
+
push(chunk) {
|
|
31
|
+
let remaining = chunk;
|
|
32
|
+
while (remaining.length > 0) {
|
|
33
|
+
const newline = remaining.indexOf("\n");
|
|
34
|
+
const segment = newline < 0 ? remaining : remaining.slice(0, newline);
|
|
35
|
+
const available = STAGE_LINE_PREFIX_CAP - this.pendingPrefix.length;
|
|
36
|
+
if (available > 0)
|
|
37
|
+
this.pendingPrefix += segment.slice(0, available);
|
|
38
|
+
if (newline < 0)
|
|
39
|
+
return;
|
|
40
|
+
this.emitPending();
|
|
41
|
+
remaining = remaining.slice(newline + 1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
finish() {
|
|
45
|
+
if (this.pendingPrefix.length > 0)
|
|
46
|
+
this.emitPending();
|
|
47
|
+
}
|
|
48
|
+
emitPending() {
|
|
49
|
+
const stage = parseBoundedStageLine(this.pendingPrefix.replace(/\r$/, ""));
|
|
50
|
+
this.pendingPrefix = "";
|
|
51
|
+
if (stage !== null)
|
|
52
|
+
this.onStage(stage);
|
|
53
|
+
}
|
|
54
|
+
}
|
package/dist/config.js
CHANGED
|
@@ -17,7 +17,7 @@ export const DEFAULT_EXECUTION_LIMITS = Object.freeze({
|
|
|
17
17
|
maxStartingTotal: 1,
|
|
18
18
|
maxStartingPerRuntime: 1,
|
|
19
19
|
startupGapMs: 3_000,
|
|
20
|
-
startupTimeoutMs:
|
|
20
|
+
startupTimeoutMs: 120_000,
|
|
21
21
|
});
|
|
22
22
|
// Fits the largest mandatory v1 lifecycle envelope (UUID + timestamps + outcome facts) with margin.
|
|
23
23
|
export const MIN_EXECUTION_EVENT_BYTES = 512;
|
|
@@ -2,7 +2,8 @@ import { fork } from "node:child_process";
|
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { executionBackendCapability, ownsRuntimeViaJobObject } from "./execution-backend.js";
|
|
4
4
|
const DEFAULT_ABORT_TIMEOUT_MS = 5_000;
|
|
5
|
-
const
|
|
5
|
+
const DEFAULT_READY_HANDSHAKE_TIMEOUT_MS = 20_000;
|
|
6
|
+
const DEFAULT_RELEASE_HANDSHAKE_TIMEOUT_MS = 5_000;
|
|
6
7
|
const DEFAULT_TASKKILL_TIMEOUT_MS = 5_000;
|
|
7
8
|
const PROCESS_GROUP_POLL_MS = 10;
|
|
8
9
|
function messageError(error) {
|
|
@@ -153,9 +154,17 @@ export async function startDormantSupervisor(launch, options = {}) {
|
|
|
153
154
|
const childEntry = options.childEntry
|
|
154
155
|
?? fileURLToPath(new URL("./execution-supervisor-child.js", import.meta.url));
|
|
155
156
|
const abortTimeoutMs = options.abortTimeoutMs ?? DEFAULT_ABORT_TIMEOUT_MS;
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
const readyHandshakeTimeoutMs = options.readyHandshakeTimeoutMs
|
|
158
|
+
?? options.handshakeTimeoutMs
|
|
159
|
+
?? DEFAULT_READY_HANDSHAKE_TIMEOUT_MS;
|
|
160
|
+
const releaseHandshakeTimeoutMs = options.releaseHandshakeTimeoutMs
|
|
161
|
+
?? options.handshakeTimeoutMs
|
|
162
|
+
?? DEFAULT_RELEASE_HANDSHAKE_TIMEOUT_MS;
|
|
163
|
+
if (!Number.isFinite(readyHandshakeTimeoutMs) || readyHandshakeTimeoutMs <= 0) {
|
|
164
|
+
throw new RangeError("readyHandshakeTimeoutMs must be a positive finite number");
|
|
165
|
+
}
|
|
166
|
+
if (!Number.isFinite(releaseHandshakeTimeoutMs) || releaseHandshakeTimeoutMs <= 0) {
|
|
167
|
+
throw new RangeError("releaseHandshakeTimeoutMs must be a positive finite number");
|
|
159
168
|
}
|
|
160
169
|
const env = Object.fromEntries(Object.entries(launch.env).filter((entry) => entry[1] !== undefined));
|
|
161
170
|
const child = fork(childEntry, [], {
|
|
@@ -272,7 +281,7 @@ export async function startDormantSupervisor(launch, options = {}) {
|
|
|
272
281
|
reject(error);
|
|
273
282
|
});
|
|
274
283
|
});
|
|
275
|
-
await withTimeout(ready,
|
|
284
|
+
await withTimeout(ready, readyHandshakeTimeoutMs, "ready handshake");
|
|
276
285
|
}
|
|
277
286
|
catch (error) {
|
|
278
287
|
await abort().catch((abortError) => {
|
|
@@ -298,7 +307,7 @@ export async function startDormantSupervisor(launch, options = {}) {
|
|
|
298
307
|
});
|
|
299
308
|
});
|
|
300
309
|
try {
|
|
301
|
-
await withTimeout(acknowledgement,
|
|
310
|
+
await withTimeout(acknowledgement, releaseHandshakeTimeoutMs, "release handshake");
|
|
302
311
|
}
|
|
303
312
|
catch (error) {
|
|
304
313
|
await abort().catch((abortError) => {
|
package/dist/local-executor.js
CHANGED
|
@@ -20,6 +20,7 @@ import { isRuntimeReadyEvent, } from "./runtime-startup-gate.js";
|
|
|
20
20
|
import { dslog } from "./slog.js";
|
|
21
21
|
import { evaluateMemoryPrunePostcondition, inspectMemoryPruneFilesWithinDeadline, parseMemoryPruneTraceId, } from "./memory-prune-diagnostics.js";
|
|
22
22
|
import { createLocalMemoryTelemetry, logLocalMemoryContextPrepareFailure, logLocalMemoryDiagnosticsFailure, } from "./local-memory-telemetry.js";
|
|
23
|
+
import { CodexStartupStageParser } from "./codex-startup-stage.js";
|
|
23
24
|
function memoryPruneSnapshotFields(snapshot) {
|
|
24
25
|
const fields = {};
|
|
25
26
|
for (const [label, fact] of Object.entries(snapshot)) {
|
|
@@ -615,10 +616,30 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
615
616
|
callbacks.onConsole?.(chunk);
|
|
616
617
|
});
|
|
617
618
|
let stderrTail = "";
|
|
619
|
+
const codexStartupStageState = { last: null };
|
|
620
|
+
const codexStartupStageParser = runtime.name === "codex"
|
|
621
|
+
? new CodexStartupStageParser((stage) => {
|
|
622
|
+
codexStartupStageState.last = stage;
|
|
623
|
+
dslog("runtime.start_stage", "Codex runtime 启动阶段更新", {
|
|
624
|
+
level: stage.status === "error" || stage.status === "timeout" ? "ERROR" : "INFO",
|
|
625
|
+
execution_id: input.executionId,
|
|
626
|
+
runtime: runtime.name,
|
|
627
|
+
stage: stage.stage,
|
|
628
|
+
status: stage.status,
|
|
629
|
+
attempt: stage.attempt,
|
|
630
|
+
stage_ms: stage.stageMs,
|
|
631
|
+
startup_ms: Date.now() - runtimeLaunchAt,
|
|
632
|
+
...(stage.stage === "thread_start" ? { resumed: stage.resumed } : {}),
|
|
633
|
+
});
|
|
634
|
+
})
|
|
635
|
+
: null;
|
|
618
636
|
child.stderr.on("data", (data) => {
|
|
619
637
|
process.stderr.write(data);
|
|
620
|
-
|
|
638
|
+
const text = String(data);
|
|
639
|
+
stderrTail = (stderrTail + text).slice(-STDERR_TAIL_CAP);
|
|
640
|
+
codexStartupStageParser?.push(text);
|
|
621
641
|
});
|
|
642
|
+
child.stderr.on("end", () => codexStartupStageParser?.finish());
|
|
622
643
|
let runtimeExit;
|
|
623
644
|
try {
|
|
624
645
|
if (startupReservation !== null) {
|
|
@@ -629,7 +650,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
629
650
|
runtimeReadySignal.then(() => ({ kind: "ready" })),
|
|
630
651
|
child.exit.then((exit) => ({ kind: "exit", exit })),
|
|
631
652
|
new Promise((resolve) => {
|
|
632
|
-
startupTimer = setTimeout(() => resolve({ kind: "timeout" }), dependencies.startupTimeoutMs ??
|
|
653
|
+
startupTimer = setTimeout(() => resolve({ kind: "timeout" }), dependencies.startupTimeoutMs ?? 120_000);
|
|
633
654
|
}),
|
|
634
655
|
]), dependencies.cancellation);
|
|
635
656
|
}
|
|
@@ -639,11 +660,18 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
639
660
|
}
|
|
640
661
|
if (startupOutcome.kind !== "ready") {
|
|
641
662
|
if (startupOutcome.kind === "timeout") {
|
|
663
|
+
const lastCodexStartupStage = codexStartupStageState.last;
|
|
642
664
|
dslog("runtime.start_timeout", "runtime 启动超时", {
|
|
643
665
|
level: "ERROR",
|
|
644
666
|
execution_id: input.executionId,
|
|
645
667
|
runtime: runtime.name,
|
|
646
|
-
startup_timeout_ms: dependencies.startupTimeoutMs ??
|
|
668
|
+
startup_timeout_ms: dependencies.startupTimeoutMs ?? 120_000,
|
|
669
|
+
...(lastCodexStartupStage === null ? {} : {
|
|
670
|
+
last_stage: lastCodexStartupStage.stage,
|
|
671
|
+
last_stage_status: lastCodexStartupStage.status,
|
|
672
|
+
last_stage_ms: lastCodexStartupStage.stageMs,
|
|
673
|
+
attempt: lastCodexStartupStage.attempt,
|
|
674
|
+
}),
|
|
647
675
|
});
|
|
648
676
|
await child.cancel?.();
|
|
649
677
|
}
|
package/dist/main.js
CHANGED
|
File without changes
|
|
@@ -468,7 +468,7 @@ async function runCodexAppServerAttempt(bin, input, attempt, initializeTimeoutMs
|
|
|
468
468
|
completionReject(new Error("Codex produced no semantic progress within the startup window"));
|
|
469
469
|
void cancel();
|
|
470
470
|
});
|
|
471
|
-
activeStage = "
|
|
471
|
+
activeStage = "model_init";
|
|
472
472
|
stageStartedAt = Date.now();
|
|
473
473
|
const started = await rpc.request("turn/start", {
|
|
474
474
|
threadId,
|
package/dist/serve.js
CHANGED
|
@@ -741,7 +741,7 @@ export function serve(config, opts = {}) {
|
|
|
741
741
|
try {
|
|
742
742
|
const data2 = req.type === "fs:list" ? await listWorkspace(root, req.path)
|
|
743
743
|
: req.type === "fs:read" ? await readWorkspaceFile(root, req.path)
|
|
744
|
-
: req.type === "skills:list" ? await listSkills(
|
|
744
|
+
: req.type === "skills:list" ? await listSkills(req.runtime)
|
|
745
745
|
: { models: await listRuntimeModels(req.type === "probe-models" ? (req.runtime ?? "") : "") };
|
|
746
746
|
reply({ ok: true, data: data2 });
|
|
747
747
|
}
|
package/dist/skills.js
CHANGED
|
@@ -1,17 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 枚举某 agent
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* 枚举某 agent 可用的全局 skill,供 Profile 的 SKILLS 区展示。
|
|
3
|
+
*
|
|
4
|
+
* 目录由 runtime 决定——各 runtime 的原生发现路径互不相同:
|
|
5
|
+
* - codex: $CODEX_HOME/skills (未设时 ~/.codex/skills) + ~/.agents/skills
|
|
6
|
+
* - claude: ~/.claude/skills
|
|
7
|
+
* runtime 缺省(老 server 不下发)时按 claude 回落,与历史行为一致。
|
|
8
|
+
* CREW_GLOBAL_SKILLS_DIR 若设置则覆盖以上全部,只扫它。
|
|
5
9
|
*
|
|
6
10
|
* 每个 skill = 一个目录,内含 SKILL.md;从其 YAML frontmatter 取 name/description,
|
|
7
11
|
* 缺失则回退到目录名。只读、容错(目录不存在/无 frontmatter 都不报错)。
|
|
12
|
+
*
|
|
13
|
+
* 不扫 agent 工作区:项目技能由 project-skills/reconciler 投影到
|
|
14
|
+
* <agentRoot>/.agents/skills 与 <agentRoot>/.crew/claude-skills/.claude/skills,
|
|
15
|
+
* 那两处是 reconciler 独占(每次整目录原子替换),且已由「项目技能」区单独展示。
|
|
8
16
|
*/
|
|
9
17
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
10
18
|
import { join } from "node:path";
|
|
11
19
|
import { homedir } from "node:os";
|
|
12
20
|
import { parseSkillFrontmatter } from "./skill-frontmatter.js";
|
|
13
|
-
|
|
14
|
-
|
|
21
|
+
/** 该 runtime 的原生全局 skill 目录;顺序即优先级(重名时靠前的胜出)。 */
|
|
22
|
+
function globalSkillsDirs(runtime) {
|
|
23
|
+
const override = process.env.CREW_GLOBAL_SKILLS_DIR;
|
|
24
|
+
if (override)
|
|
25
|
+
return [override];
|
|
26
|
+
const home = homedir();
|
|
27
|
+
if (runtime === "codex") {
|
|
28
|
+
return [join(process.env.CODEX_HOME ?? join(home, ".codex"), "skills"), join(home, ".agents", "skills")];
|
|
29
|
+
}
|
|
30
|
+
return [join(home, ".claude", "skills")];
|
|
31
|
+
}
|
|
32
|
+
async function readSkillsFrom(dir) {
|
|
15
33
|
const names = await readdir(dir).catch(() => []);
|
|
16
34
|
const skills = [];
|
|
17
35
|
for (const name of names) {
|
|
@@ -23,17 +41,20 @@ async function readSkillsFrom(dir, scope) {
|
|
|
23
41
|
continue;
|
|
24
42
|
const md = await readFile(skillMd, "utf8").catch(() => "");
|
|
25
43
|
const fm = parseSkillFrontmatter(md);
|
|
26
|
-
skills.push({ scope, name: fm.name || name, description: fm.description ?? "" });
|
|
44
|
+
skills.push({ scope: "global", name: fm.name || name, description: fm.description ?? "" });
|
|
27
45
|
}
|
|
28
46
|
return skills;
|
|
29
47
|
}
|
|
30
|
-
/**
|
|
31
|
-
export async function listSkills(
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
48
|
+
/** 列出该 runtime 的全局 skill,按 name 去重后排序。 */
|
|
49
|
+
export async function listSkills(runtime) {
|
|
50
|
+
const perDir = await Promise.all(globalSkillsDirs(runtime).map((dir) => readSkillsFrom(dir)));
|
|
51
|
+
// 同名 skill 可能同时存在于多个 root(如 ~/.codex/skills 与 ~/.agents/skills),
|
|
52
|
+
// 取先命中的:既符合 runtime 的目录优先级,也避免前端 key 冲突。
|
|
53
|
+
const byName = new Map();
|
|
54
|
+
for (const skills of perDir) {
|
|
55
|
+
for (const skill of skills)
|
|
56
|
+
if (!byName.has(skill.name))
|
|
57
|
+
byName.set(skill.name, skill);
|
|
58
|
+
}
|
|
59
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
39
60
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nowcrew/daemon",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -16,9 +16,16 @@
|
|
|
16
16
|
"publishConfig": {
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
|
|
21
|
+
"build": "tsc -p tsconfig.json",
|
|
22
|
+
"prepublishOnly": "pnpm build && node ../scripts/daemon-release-artifact.mjs --strict-registry",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"typecheck": "tsc --noEmit"
|
|
25
|
+
},
|
|
19
26
|
"dependencies": {
|
|
20
27
|
"@agentclientprotocol/sdk": "1.2.1",
|
|
21
|
-
"@nowcrew/cli": "^0.4.
|
|
28
|
+
"@nowcrew/cli": "^0.4.14",
|
|
22
29
|
"cross-spawn": "^7.0.6",
|
|
23
30
|
"ws": "^8",
|
|
24
31
|
"yaml": "^2.8.1",
|
|
@@ -34,11 +41,5 @@
|
|
|
34
41
|
"tsx": "^4.19.0",
|
|
35
42
|
"typescript": "^5.6.0",
|
|
36
43
|
"vitest": "^2.1.0"
|
|
37
|
-
},
|
|
38
|
-
"scripts": {
|
|
39
|
-
"daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
|
|
40
|
-
"build": "tsc -p tsconfig.json",
|
|
41
|
-
"test": "vitest run",
|
|
42
|
-
"typecheck": "tsc --noEmit"
|
|
43
44
|
}
|
|
44
|
-
}
|
|
45
|
+
}
|