@nowcrew/daemon 0.6.98-beta.4 → 0.6.98
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 -34
- package/dist/automatic-service-migration.js +6 -7
- package/dist/cloud-report-upload.js +10 -1
- package/dist/computer-service.js +42 -40
- package/dist/computer-setup-client.js +8 -22
- package/dist/computer-setup-orchestration.js +2 -13
- package/dist/computer-setup.js +6 -36
- package/dist/execution-journal.js +1 -3
- package/dist/execution-protocol.js +1 -1
- package/dist/execution-runner.js +29 -20
- package/dist/external-output.js +12 -1
- package/dist/i18n.js +0 -10
- package/dist/local-execution-prompt.js +3 -8
- package/dist/local-executor.js +52 -69
- package/dist/local-memory/index-budget.js +3 -9
- package/dist/local-memory/inventory-cli.js +8 -52
- package/dist/local-memory/inventory-source.js +8 -15
- package/dist/local-memory/pending-query.js +2 -5
- package/dist/local-memory/pending-schema.js +1 -1
- package/dist/local-memory/pending.js +2 -16
- package/dist/local-memory/prompt.js +5 -21
- package/dist/local-memory/read-cli.js +76 -98
- package/dist/local-memory/rollout.js +11 -79
- package/dist/local-memory/runtime.js +13 -181
- package/dist/local-memory/write.js +21 -189
- package/dist/local-memory-diagnostics.js +13 -94
- package/dist/local-memory-telemetry.js +2 -19
- package/dist/machine-info.js +3 -6
- package/dist/marketplace-controller.js +21 -8
- package/dist/marketplace-maintenance-bridge.js +79 -0
- package/dist/marketplace-maintenance-protocol.js +40 -0
- package/dist/marketplace-protocol.js +1 -1
- package/dist/marketplace-remote.js +1 -1
- package/dist/marketplace-runner.js +1 -1
- package/dist/marketplace-serve.js +4 -2
- package/dist/memory-prune-diagnostics.js +4 -60
- package/dist/message-file-preview.js +1 -11
- package/dist/npm-command.js +5 -5
- package/dist/project-skills/machine-hello-publisher.js +6 -95
- package/dist/prompt.js +2 -6
- package/dist/runner.js +1 -1
- package/dist/runtimes/claude-thread-runner.js +4 -25
- package/dist/runtimes/pi.js +85 -17
- package/dist/serve.js +10 -92
- package/dist/token.js +0 -1
- package/dist/windows-scheduled-task.js +3 -3
- package/dist/workspace-control-handler.js +3 -16
- package/dist/workspace.js +2 -13
- package/package.json +1 -1
- package/dist/computer-token-setup.js +0 -46
- package/dist/local-memory/commit-telemetry.js +0 -30
- package/dist/local-memory/execution-workspace.js +0 -62
- package/dist/local-memory/full-index-search.js +0 -47
- package/dist/local-memory/initialization-identity.js +0 -36
- package/dist/local-memory/inventory-v2.js +0 -362
- package/dist/local-memory/legacy-adapter.js +0 -233
- package/dist/local-memory/migration-access.js +0 -242
- package/dist/local-memory/migration-cli.js +0 -54
- package/dist/local-memory/migration-io.js +0 -199
- package/dist/local-memory/migration-pending.js +0 -9
- package/dist/local-memory/migration-plan.js +0 -432
- package/dist/local-memory/migration-schema.js +0 -55
- package/dist/local-memory/migration-state-invariants.js +0 -65
- package/dist/local-memory/migration-store.js +0 -414
- package/dist/local-memory/migration-tool-entry.js +0 -21
- package/dist/local-memory/source-path.js +0 -13
- package/dist/local-memory/storage-format.js +0 -152
- package/dist/local-memory/writer-mode.js +0 -13
- package/dist/memory-migration-controller.js +0 -89
- package/dist/memory-migration-inspect.js +0 -108
- package/dist/memory-migration-journal.js +0 -14
- package/dist/memory-migration-protocol.js +0 -14
- package/dist/memory-migration-runtime.js +0 -305
- package/dist/powershell-command.js +0 -12
- package/dist/windows-task-principal.js +0 -26
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MaintenanceRequestSchema, MaintenanceReportSchema } from "./marketplace-maintenance-protocol.js";
|
|
1
2
|
import { MarketplaceRequestSchema, marketplaceActive } from "./marketplace-protocol.js";
|
|
2
3
|
import { resolveMarketplaceScopes, runMarketplaceScope } from "./marketplace-runner.js";
|
|
3
4
|
export function createMarketplaceController(deps) {
|
|
@@ -11,22 +12,31 @@ export function createMarketplaceController(deps) {
|
|
|
11
12
|
const deadline = now() + 10 * 60_000;
|
|
12
13
|
let lease = null;
|
|
13
14
|
try {
|
|
14
|
-
const scopes = await (deps.resolve ?? resolveMarketplaceScopes)(request.targets, deps.agentsRoot);
|
|
15
|
+
const scopes = request.type === "marketplace:operation" ? await (deps.resolve ?? resolveMarketplaceScopes)(request.targets, deps.agentsRoot) : [];
|
|
15
16
|
// 查询不使用任务执行槽;只有写入运行时共用市场/插件目录时才等待独占保护。
|
|
16
|
-
if (request.mode === "update") {
|
|
17
|
+
if (request.type === "marketplace:maintenance" || request.mode === "update") {
|
|
17
18
|
while (!stopped && now() < deadline && lease === null) {
|
|
18
19
|
lease = await deps.acquire();
|
|
19
20
|
if (!lease)
|
|
20
21
|
await wait();
|
|
21
22
|
}
|
|
22
23
|
if (!lease || stopped) {
|
|
23
|
-
operations.set(id, {
|
|
24
|
+
operations.set(id, { ...operations.get(id), status: "failed", errorCode: "runtime_busy" });
|
|
24
25
|
return;
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
if (stopped || now() >= deadline)
|
|
28
29
|
throw new Error("operation_deadline");
|
|
29
|
-
operations.set(id, {
|
|
30
|
+
operations.set(id, { ...operations.get(id), status: "running" });
|
|
31
|
+
if (request.type === "marketplace:maintenance") {
|
|
32
|
+
if (!deps.maintain)
|
|
33
|
+
throw new Error("maintenance_unavailable");
|
|
34
|
+
const result = MaintenanceReportSchema.parse(await deps.maintain(request));
|
|
35
|
+
if (result.operationId !== id)
|
|
36
|
+
throw new Error("operation_mismatch");
|
|
37
|
+
operations.set(id, result);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
30
40
|
const results = [];
|
|
31
41
|
for (const scope of scopes) {
|
|
32
42
|
if (stopped || now() >= deadline)
|
|
@@ -41,7 +51,9 @@ export function createMarketplaceController(deps) {
|
|
|
41
51
|
}
|
|
42
52
|
catch {
|
|
43
53
|
const previous = operations.get(id);
|
|
44
|
-
operations.set(id,
|
|
54
|
+
operations.set(id, request.type === "marketplace:maintenance"
|
|
55
|
+
? { ...previous, status: "unknown", errorCode: "operation_unknown" }
|
|
56
|
+
: { ...previous, status: previous.results.some(x => !x.errorCode) ? "partial" : "failed", errorCode: "execution_failed" });
|
|
45
57
|
}
|
|
46
58
|
finally {
|
|
47
59
|
try {
|
|
@@ -55,22 +67,23 @@ export function createMarketplaceController(deps) {
|
|
|
55
67
|
};
|
|
56
68
|
return {
|
|
57
69
|
handle(raw) {
|
|
58
|
-
const parsed = MarketplaceRequestSchema.safeParse(raw);
|
|
70
|
+
const parsed = MarketplaceRequestSchema.or(MaintenanceRequestSchema).safeParse(raw);
|
|
59
71
|
if (!parsed.success || stopped)
|
|
60
72
|
return { ok: false, error: "invalid_request" };
|
|
61
73
|
const request = parsed.data;
|
|
74
|
+
const version = request.type === "marketplace:maintenance" ? { version: 2 } : {};
|
|
62
75
|
const existing = operations.get(request.operationId);
|
|
63
76
|
if (existing)
|
|
64
77
|
return { ok: true, data: existing };
|
|
65
78
|
if (request.action === "status")
|
|
66
|
-
return { ok: true, data: { operationId: request.operationId, status: "unknown", results: [], errorCode: "operation_unknown" } };
|
|
79
|
+
return { ok: true, data: { ...version, operationId: request.operationId, status: "unknown", results: [], errorCode: "operation_unknown" } };
|
|
67
80
|
if (process.platform !== "darwin" && process.platform !== "linux")
|
|
68
81
|
return { ok: false, error: "unsupported_platform" };
|
|
69
82
|
if (current !== null || [...operations.values()].some(x => marketplaceActive(x.status)))
|
|
70
83
|
return { ok: false, error: "runtime_busy" };
|
|
71
84
|
if (operations.size >= 100)
|
|
72
85
|
operations.delete(operations.keys().next().value);
|
|
73
|
-
const accepted = { operationId: request.operationId, status: "queued", results: [], errorCode: null };
|
|
86
|
+
const accepted = { ...version, operationId: request.operationId, status: "queued", results: [], errorCode: null };
|
|
74
87
|
operations.set(request.operationId, accepted);
|
|
75
88
|
current = execute(request).finally(() => { current = null; });
|
|
76
89
|
return { ok: true, data: accepted };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { augmentedPath } from "./runtime-path.js";
|
|
7
|
+
import { MaintenanceReportSchema } from "./marketplace-maintenance-protocol.js";
|
|
8
|
+
export function maintenanceEnvironment(env) {
|
|
9
|
+
// Keep the machine user's login/config, never forward control-plane credentials.
|
|
10
|
+
return Object.fromEntries(Object.entries(env).filter(([key]) => !/^(CREW_|NOWCREW_|NOWWORK_)/i.test(key)));
|
|
11
|
+
}
|
|
12
|
+
async function supervise(file, id, cwd, env) {
|
|
13
|
+
await new Promise((resolve, reject) => {
|
|
14
|
+
const child = spawn(process.execPath, [file, "--run", id], { cwd, env, detached: true, stdio: "ignore" });
|
|
15
|
+
let expired = false;
|
|
16
|
+
const kill = () => { if (child.pid) {
|
|
17
|
+
try {
|
|
18
|
+
process.kill(-child.pid, "SIGKILL");
|
|
19
|
+
}
|
|
20
|
+
catch { /* already gone */ }
|
|
21
|
+
} };
|
|
22
|
+
const timer = setTimeout(() => { expired = true; kill(); }, 18 * 60_000);
|
|
23
|
+
child.once("error", () => { clearTimeout(timer); kill(); reject(new Error("worker_failed")); });
|
|
24
|
+
child.once("close", code => {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
// Never release the machine lock while descendants can still write shared configuration.
|
|
27
|
+
kill();
|
|
28
|
+
if (expired || code !== 0)
|
|
29
|
+
reject(new Error("worker_failed"));
|
|
30
|
+
else
|
|
31
|
+
resolve();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export async function runMaintenanceBridge(config, request, acquireEnv) {
|
|
36
|
+
const fail = (errorCode) => ({ version: 2, operationId: request.operationId, status: "failed", results: [], errorCode });
|
|
37
|
+
let dir;
|
|
38
|
+
let started = false;
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetch(`${config.serverUrl.replace(/\/+$/, "")}/machine/marketplace-maintenance/${request.operationId}/runner/${request.plan.runner.releaseId}`, {
|
|
41
|
+
headers: { authorization: `Bearer ${config.machineToken}` }, redirect: "error", signal: AbortSignal.timeout(15_000),
|
|
42
|
+
});
|
|
43
|
+
if (!response.ok)
|
|
44
|
+
return fail("runner_unavailable");
|
|
45
|
+
// Bound even a malformed server response before buffering or executing it.
|
|
46
|
+
if (!response.body)
|
|
47
|
+
return fail("runner_unavailable");
|
|
48
|
+
const chunks = [];
|
|
49
|
+
let bytes = 0;
|
|
50
|
+
for await (const chunk of response.body) {
|
|
51
|
+
bytes += chunk.length;
|
|
52
|
+
if (bytes > 256_000)
|
|
53
|
+
return fail("runner_unavailable");
|
|
54
|
+
chunks.push(chunk);
|
|
55
|
+
}
|
|
56
|
+
const content = Buffer.concat(chunks);
|
|
57
|
+
if (createHash("sha256").update(content).digest("hex") !== request.plan.runner.sha256)
|
|
58
|
+
return fail("runner_unavailable");
|
|
59
|
+
const env = maintenanceEnvironment(await acquireEnv());
|
|
60
|
+
env.PATH = augmentedPath(env, {}, process.platform, "append");
|
|
61
|
+
dir = await mkdtemp(join(tmpdir(), "nowwork-maintenance-"));
|
|
62
|
+
const file = join(dir, "runner.mjs");
|
|
63
|
+
await writeFile(file, content, { mode: 0o600 });
|
|
64
|
+
started = true;
|
|
65
|
+
await supervise(file, request.operationId, dir, env);
|
|
66
|
+
const report = MaintenanceReportSchema.parse(JSON.parse(await readFile(join(dir, "report.json"), "utf8")));
|
|
67
|
+
if (report.operationId !== request.operationId)
|
|
68
|
+
throw new Error("operation_mismatch");
|
|
69
|
+
return report;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// A killed worker may have changed files: require readback/manual confirmation before another write.
|
|
73
|
+
return started ? { ...fail("operation_unknown"), status: "unknown" } : fail("runner_unavailable");
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
if (dir)
|
|
77
|
+
await rm(dir, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// 与 daemon 同名文件逐字一致;这是独立于旧单插件协议的版本化契约。
|
|
3
|
+
export const MAINTENANCE_CAPABILITY = "marketplace_codex_maintenance_v1";
|
|
4
|
+
export const MaintenancePlanSchema = z.object({
|
|
5
|
+
marketplace: z.literal("nowwork-pm"),
|
|
6
|
+
actions: z.tuple([z.literal("install-missing"), z.literal("update-installed")]),
|
|
7
|
+
hosts: z.tuple([z.literal("claude"), z.literal("codex"), z.literal("pi")]),
|
|
8
|
+
runner: z.object({ releaseId: z.string().regex(/^[a-f0-9]{64}$/), sha256: z.string().regex(/^[a-f0-9]{64}$/) }).strict(),
|
|
9
|
+
}).strict();
|
|
10
|
+
export const MaintenanceRequestSchema = z.discriminatedUnion("action", [
|
|
11
|
+
z.object({ type: z.literal("marketplace:maintenance"), action: z.literal("start"), operationId: z.string().uuid(), plan: MaintenancePlanSchema }).strict(),
|
|
12
|
+
z.object({ type: z.literal("marketplace:maintenance"), action: z.literal("status"), operationId: z.string().uuid() }).strict(),
|
|
13
|
+
]);
|
|
14
|
+
export const MaintenanceErrorSchema = z.enum(["runtime_busy", "execution_failed", "operation_unknown", "runner_unavailable",
|
|
15
|
+
"manager_missing", "cli_missing", "command_failed", "verification_failed", "source_conflict", "unsupported_platform"]);
|
|
16
|
+
const Version = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._+-]{0,79}$/).nullable();
|
|
17
|
+
export const MaintenanceResultSchema = z.object({
|
|
18
|
+
runtime: z.enum(["claude", "codex", "pi"]), plugin: z.string().regex(/^[a-z0-9][a-z0-9_-]{0,127}$/).nullable(),
|
|
19
|
+
status: z.enum(["installed", "updated", "unchanged", "empty", "missing_cli", "failed"]),
|
|
20
|
+
beforeVersion: Version, afterVersion: Version, targetVersion: Version,
|
|
21
|
+
activation: z.enum(["reload_required", "not_changed", "unverified"]), errorCode: MaintenanceErrorSchema.nullable(),
|
|
22
|
+
}).strict();
|
|
23
|
+
export const MaintenanceReportSchema = z.object({
|
|
24
|
+
version: z.literal(2), operationId: z.string().uuid(),
|
|
25
|
+
status: z.enum(["queued", "running", "completed", "partial", "failed", "unknown"]),
|
|
26
|
+
results: z.array(MaintenanceResultSchema).max(1500), errorCode: MaintenanceErrorSchema.nullable(),
|
|
27
|
+
}).strict().superRefine((r, ctx) => {
|
|
28
|
+
const keys = r.results.map(x => `${x.runtime}:${x.plugin}`);
|
|
29
|
+
if (new Set(keys).size !== keys.length)
|
|
30
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Duplicate plugin result" });
|
|
31
|
+
for (const x of r.results) {
|
|
32
|
+
if (["installed", "updated", "unchanged"].includes(x.status) && (!x.plugin || !x.targetVersion || x.targetVersion !== x.afterVersion || x.errorCode)) {
|
|
33
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Successful plugin result requires a verified target version" });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (r.status === "completed" && (r.errorCode || ["claude", "codex", "pi"].some(host => !r.results.some(x => x.runtime === host))
|
|
37
|
+
|| r.results.some(x => x.errorCode || x.status === "failed" || x.status === "missing_cli"))) {
|
|
38
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Completion requires verified coverage of every host" });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
// 与另一个进程中的 marketplace-protocol.ts 保持一致;跨进程契约测试校验摘要。
|
|
3
3
|
export const MARKETPLACE_CAPABILITY = "marketplace_update_v1";
|
|
4
4
|
export const MARKETPLACE = "nowwork-pm";
|
|
5
|
-
export const PLUGIN = "
|
|
5
|
+
export const PLUGIN = "AIinterview-plugin";
|
|
6
6
|
export const MarketplaceModeSchema = z.enum(["inspect", "refresh", "update"]);
|
|
7
7
|
export const MarketplaceStatusSchema = z.enum(["queued", "running", "completed", "partial", "failed", "unknown"]);
|
|
8
8
|
export const MarketplaceTargetSchema = z.object({
|
|
@@ -10,7 +10,7 @@ const Target = z.object({
|
|
|
10
10
|
version: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._+-]{0,79}$/),
|
|
11
11
|
source: z.object({ source: z.literal("git-subdir"),
|
|
12
12
|
url: z.literal("git@codeup.aliyun.com:6128d2e757e7cd986dfae5ab/pm/AIinterview-agent-skill.git"),
|
|
13
|
-
path: z.literal("plugins/
|
|
13
|
+
path: z.literal("plugins/AIinterview-plugin"), sha: z.string().regex(/^[a-f0-9]{40}$/) }),
|
|
14
14
|
});
|
|
15
15
|
export async function readRemoteMarketplace(runtime, registered, env, command) {
|
|
16
16
|
const source = validateMarketplaceSource(registered.url, registered.ref);
|
|
@@ -12,7 +12,7 @@ import { readMarketplaceSource } from "./marketplace-source.js";
|
|
|
12
12
|
export { MarketplaceFailure, runMarketplaceCommand } from "./marketplace-command.js";
|
|
13
13
|
const SELECTOR = `${PLUGIN}@${MARKETPLACE}`;
|
|
14
14
|
const SOURCE = "git@codeup.aliyun.com:6128d2e757e7cd986dfae5ab/pm/AIinterview-agent-skill.git";
|
|
15
|
-
const SOURCE_PATH = "plugins/
|
|
15
|
+
const SOURCE_PATH = "plugins/AIinterview-plugin";
|
|
16
16
|
const obj = (x) => x !== null && typeof x === "object" && !Array.isArray(x) ? x : {};
|
|
17
17
|
const parse = (text) => { try {
|
|
18
18
|
return JSON.parse(text);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { runMaintenanceBridge } from "./marketplace-maintenance-bridge.js";
|
|
1
2
|
import { createMarketplaceController } from "./marketplace-controller.js";
|
|
2
3
|
import { prepareDaemonRestart } from "./daemon-updater.js";
|
|
3
|
-
export function createServeMarketplace(config, localSlots, hostCoordinator) {
|
|
4
|
+
export function createServeMarketplace(config, localSlots, hostCoordinator, acquireEnv = async () => ({ ...process.env })) {
|
|
4
5
|
const controller = createMarketplaceController({ agentsRoot: config.agentsRoot,
|
|
6
|
+
maintain: request => runMaintenanceBridge(config, request, acquireEnv),
|
|
5
7
|
acquire: async () => {
|
|
6
8
|
// Marketplace updates can mutate shared Codex/Claude homes across profiles.
|
|
7
9
|
const local = await prepareDaemonRestart({ localSlots });
|
|
@@ -29,7 +31,7 @@ export function createServeMarketplace(config, localSlots, hostCoordinator) {
|
|
|
29
31
|
} });
|
|
30
32
|
return {
|
|
31
33
|
handle(raw, send) {
|
|
32
|
-
if (!raw || typeof raw !== "object" || !("type" in raw) || raw.type !== "marketplace:operation")
|
|
34
|
+
if (!raw || typeof raw !== "object" || !("type" in raw) || (raw.type !== "marketplace:operation" && raw.type !== "marketplace:maintenance"))
|
|
33
35
|
return false;
|
|
34
36
|
const { reqId, ...request } = raw;
|
|
35
37
|
if (typeof reqId === "string")
|
|
@@ -7,7 +7,6 @@ import { dslog } from "./slog.js";
|
|
|
7
7
|
const TRACE_PATTERN = /\[memory-prune trace_id=([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\]/i;
|
|
8
8
|
export const MEMORY_PRUNE_HASH_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
9
9
|
export const MEMORY_PRUNE_NOTES_MANIFEST_LIMIT = 128;
|
|
10
|
-
export const MEMORY_PRUNE_PENDING_MANIFEST_LIMIT = 128;
|
|
11
10
|
export const MEMORY_PRUNE_NOTE_CHANGE_LIMIT = 128;
|
|
12
11
|
function compareCodeUnits(left, right) {
|
|
13
12
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
@@ -193,50 +192,6 @@ export async function inspectMemoryPruneNotes(homeDir, signal) {
|
|
|
193
192
|
...(signal?.aborted ? { error: "ABORT_ERR" } : {}),
|
|
194
193
|
};
|
|
195
194
|
}
|
|
196
|
-
export async function inspectMemoryPrunePending(homeDir, signal) {
|
|
197
|
-
const pendingDir = join(homeDir, ".crew", "memory", "pending");
|
|
198
|
-
try {
|
|
199
|
-
const metadata = await lstat(pendingDir);
|
|
200
|
-
if (metadata.isSymbolicLink())
|
|
201
|
-
return { files: [], total_count: 0, omitted_count: 0, truncated: false, error: "symbolic_link" };
|
|
202
|
-
if (!metadata.isDirectory())
|
|
203
|
-
return { files: [], total_count: 0, omitted_count: 0, truncated: false, error: "ENOTDIR" };
|
|
204
|
-
}
|
|
205
|
-
catch (error) {
|
|
206
|
-
if (error.code === "ENOENT")
|
|
207
|
-
return { files: [], total_count: 0, omitted_count: 0, truncated: false };
|
|
208
|
-
return { files: [], total_count: 0, omitted_count: 0, truncated: false, error: diagnosticError(error) };
|
|
209
|
-
}
|
|
210
|
-
let names;
|
|
211
|
-
try {
|
|
212
|
-
names = (await readdir(pendingDir, { withFileTypes: true }))
|
|
213
|
-
.filter(entry => (entry.isFile() || entry.isSymbolicLink()) && entry.name.endsWith(".json"))
|
|
214
|
-
.map(entry => entry.name).sort(compareCodeUnits);
|
|
215
|
-
}
|
|
216
|
-
catch (error) {
|
|
217
|
-
return { files: [], total_count: 0, omitted_count: 0, truncated: false, error: diagnosticError(error) };
|
|
218
|
-
}
|
|
219
|
-
const selected = names.slice(0, MEMORY_PRUNE_PENDING_MANIFEST_LIMIT);
|
|
220
|
-
const files = [];
|
|
221
|
-
for (const name of selected) {
|
|
222
|
-
if (signal?.aborted)
|
|
223
|
-
break;
|
|
224
|
-
const path = join(pendingDir, name);
|
|
225
|
-
try {
|
|
226
|
-
if ((await lstat(path)).isSymbolicLink()) {
|
|
227
|
-
files.push({ relative_path: `.crew/memory/pending/${name}`, exists: false, error: "symbolic_link" });
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
catch (error) {
|
|
232
|
-
files.push({ relative_path: `.crew/memory/pending/${name}`, exists: false, error: diagnosticError(error) });
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
files.push({ relative_path: `.crew/memory/pending/${name}`, ...await inspectFile(path, false, undefined, signal) });
|
|
236
|
-
}
|
|
237
|
-
return { files, total_count: names.length, omitted_count: Math.max(0, names.length - selected.length),
|
|
238
|
-
truncated: names.length > selected.length, ...(signal?.aborted ? { error: "ABORT_ERR" } : {}) };
|
|
239
|
-
}
|
|
240
195
|
async function inspectMemoryPruneFixedFiles(homeDir, runDir, workLogPath, signal) {
|
|
241
196
|
const cwdIsHome = runDir === homeDir;
|
|
242
197
|
const [memory, lessons, cwdMemory, cwdLessons, workLog] = await Promise.all([
|
|
@@ -255,17 +210,15 @@ async function inspectMemoryPruneFixedFiles(homeDir, runDir, workLogPath, signal
|
|
|
255
210
|
};
|
|
256
211
|
}
|
|
257
212
|
export async function inspectMemoryPruneFiles(homeDir, runDir, workLogPath, signal) {
|
|
258
|
-
const [fixedFiles, notesManifest
|
|
213
|
+
const [fixedFiles, notesManifest] = await Promise.all([
|
|
259
214
|
inspectMemoryPruneFixedFiles(homeDir, runDir, workLogPath, signal),
|
|
260
215
|
inspectMemoryPruneNotes(homeDir, signal),
|
|
261
|
-
inspectMemoryPrunePending(homeDir, signal),
|
|
262
216
|
]);
|
|
263
|
-
return { ...fixedFiles, notes_manifest: notesManifest
|
|
217
|
+
return { ...fixedFiles, notes_manifest: notesManifest };
|
|
264
218
|
}
|
|
265
219
|
export async function inspectMemoryPruneFilesWithinDeadline(homeDir, runDir, workLogPath, timeoutMs) {
|
|
266
220
|
const fixedFiles = await withinMemoryPruneDiagnosticsDeadline((signal) => inspectMemoryPruneFixedFiles(homeDir, runDir, workLogPath, signal), timeoutMs);
|
|
267
221
|
let notesManifest;
|
|
268
|
-
let pendingManifest;
|
|
269
222
|
try {
|
|
270
223
|
notesManifest = await withinMemoryPruneDiagnosticsDeadline((signal) => inspectMemoryPruneNotes(homeDir, signal), timeoutMs);
|
|
271
224
|
}
|
|
@@ -278,13 +231,7 @@ export async function inspectMemoryPruneFilesWithinDeadline(homeDir, runDir, wor
|
|
|
278
231
|
error: diagnosticError(error),
|
|
279
232
|
};
|
|
280
233
|
}
|
|
281
|
-
|
|
282
|
-
pendingManifest = await withinMemoryPruneDiagnosticsDeadline(signal => inspectMemoryPrunePending(homeDir, signal), timeoutMs);
|
|
283
|
-
}
|
|
284
|
-
catch (error) {
|
|
285
|
-
pendingManifest = { files: [], total_count: 0, omitted_count: 0, truncated: false, error: diagnosticError(error) };
|
|
286
|
-
}
|
|
287
|
-
return { ...fixedFiles, notes_manifest: notesManifest, pending_manifest: pendingManifest };
|
|
234
|
+
return { ...fixedFiles, notes_manifest: notesManifest };
|
|
288
235
|
}
|
|
289
236
|
export async function withinMemoryPruneDiagnosticsDeadline(operation, timeoutMs) {
|
|
290
237
|
const controller = new AbortController();
|
|
@@ -414,7 +361,6 @@ export function evaluateMemoryPrunePostcondition(before, after, execution) {
|
|
|
414
361
|
&& (after.cwd_memory.lessons_reference_count ?? 0) > 0,
|
|
415
362
|
cwd_lessons_changed: !after.cwd_lessons.same_as_shared_path
|
|
416
363
|
&& fileChanged(before.cwd_lessons, after.cwd_lessons),
|
|
417
|
-
pending_changed: diffMemoryPruneNotes(before.pending_manifest ?? { files: [], total_count: 0, omitted_count: 0, truncated: false }, after.pending_manifest ?? { files: [], total_count: 0, omitted_count: 0, truncated: false }).change_count > 0,
|
|
418
364
|
};
|
|
419
365
|
let outcome;
|
|
420
366
|
if (!execution.executorCompleted)
|
|
@@ -442,8 +388,6 @@ export function evaluateMemoryPrunePostcondition(before, after, execution) {
|
|
|
442
388
|
outcome = "promoted_without_index_change";
|
|
443
389
|
else if (facts.shared_memory_changed)
|
|
444
390
|
outcome = "shared_memory_updated";
|
|
445
|
-
else if (facts.pending_changed)
|
|
446
|
-
outcome = "pending_updated";
|
|
447
391
|
else
|
|
448
392
|
outcome = "no_files_changed";
|
|
449
393
|
return { outcome, ...facts };
|
|
@@ -464,7 +408,7 @@ function memoryPruneFileFactFields(fileFacts) {
|
|
|
464
408
|
}
|
|
465
409
|
return fields;
|
|
466
410
|
}
|
|
467
|
-
export function memoryPruneSnapshotFields({ notes_manifest: _notesManifest,
|
|
411
|
+
export function memoryPruneSnapshotFields({ notes_manifest: _notesManifest, ...fileFacts }) {
|
|
468
412
|
return memoryPruneFileFactFields(fileFacts);
|
|
469
413
|
}
|
|
470
414
|
export function logMemoryPruneDiagnosticsFailure(input, traceId, phase, error) {
|
|
@@ -9,7 +9,6 @@ import { z } from "zod";
|
|
|
9
9
|
import { isInternalEnvironmentPath } from "./workspace-fs.js";
|
|
10
10
|
import { opaqueKey, safeKey } from "./workspace.js";
|
|
11
11
|
import { AgentHandleSchema } from "./execution-protocol.js";
|
|
12
|
-
import { withMemoryAccess } from "./local-memory/migration-access.js";
|
|
13
12
|
const MAX_BYTES = 256 * 1024;
|
|
14
13
|
const MAX_BINARY_BYTES = 8 * 1024 * 1024;
|
|
15
14
|
const MAX_CONCURRENT_READS = 4;
|
|
@@ -232,16 +231,7 @@ export async function readSharedMessageFile(agentsRoot, rawRequest, registry) {
|
|
|
232
231
|
throw new SharedFileError("file_preview_busy");
|
|
233
232
|
activeReads++;
|
|
234
233
|
try {
|
|
235
|
-
|
|
236
|
-
const agentRoot = await realpath(resolve(agentsRoot, parsed.data.handle)).catch((error) => {
|
|
237
|
-
if (error.code === "ENOENT")
|
|
238
|
-
return undefined;
|
|
239
|
-
throw error;
|
|
240
|
-
});
|
|
241
|
-
// 记忆保护只用于该Agent根内资料,独立项目文件仍按原执行授权读取。
|
|
242
|
-
return await (agentRoot && inside(agentRoot, candidate.path)
|
|
243
|
-
? withMemoryAccess(agentRoot, () => readCandidate(candidate))
|
|
244
|
-
: readCandidate(candidate));
|
|
234
|
+
return await readCandidate(await resolveCandidate(agentsRoot, parsed.data, registry));
|
|
245
235
|
}
|
|
246
236
|
catch (error) {
|
|
247
237
|
if (error instanceof SharedFileError)
|
package/dist/npm-command.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { posix, win32 } from "node:path";
|
|
2
|
-
/** Execute npm through its owning Node binary so PATH
|
|
2
|
+
/** Execute POSIX npm through its owning Node binary so service PATH is irrelevant. */
|
|
3
3
|
export function npmInvocation(platform, nodePath = process.execPath) {
|
|
4
4
|
const paths = platform === "win32" ? win32 : posix;
|
|
5
|
-
const npmPath = platform === "win32"
|
|
6
|
-
|
|
7
|
-
:
|
|
8
|
-
|
|
5
|
+
const npmPath = paths.join(paths.dirname(nodePath), platform === "win32" ? "npm.cmd" : "npm");
|
|
6
|
+
return platform === "win32"
|
|
7
|
+
? { command: npmPath, argsPrefix: [] }
|
|
8
|
+
: { command: nodePath, argsPrefix: [npmPath] };
|
|
9
9
|
}
|
|
@@ -1,109 +1,20 @@
|
|
|
1
1
|
import { WebSocket } from "ws";
|
|
2
|
-
const HELLO_RETRY_DELAYS_MS = [1_000, 2_000, 4_000];
|
|
3
2
|
/** 记住最近一次 hello 与 socket,便于存储身份变化后重发;Project Skills 状态恒为 ready。 */
|
|
4
3
|
export function createMachineHelloPublisher(input) {
|
|
5
4
|
let latest = null;
|
|
6
5
|
let latestSocket = null;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// 发送代次:每开一轮新发送(attach/publish/republish)自增,随帧发出并要求 ack 回显。
|
|
10
|
-
let generation = 0;
|
|
11
|
-
let retryTimer;
|
|
12
|
-
const clearRetry = () => {
|
|
13
|
-
if (retryTimer !== undefined) {
|
|
14
|
-
clearTimeout(retryTimer);
|
|
15
|
-
retryTimer = undefined;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
const scheduleRetry = () => {
|
|
19
|
-
clearRetry();
|
|
20
|
-
if (acknowledged || latest === null || latestSocket?.readyState !== WebSocket.OPEN)
|
|
21
|
-
return;
|
|
22
|
-
// 当前 server 不支持 ack → 不排重试(否则永远等不到确认,白跑满退避)。
|
|
23
|
-
if (input.ackSupported && !input.ackSupported())
|
|
24
|
-
return;
|
|
25
|
-
const delay = HELLO_RETRY_DELAYS_MS[attempts - 1];
|
|
26
|
-
if (delay === undefined) {
|
|
27
|
-
// 退避跑满仍未 ack:期待 ack(ackSupported 已声明支持)才升级为断连重连,避免旧行为下反复重连。
|
|
28
|
-
if (input.ackSupported !== undefined && latestSocket !== null)
|
|
29
|
-
input.onExhausted?.(latestSocket);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
retryTimer = setTimeout(() => {
|
|
33
|
-
retryTimer = undefined;
|
|
34
|
-
sendCurrent();
|
|
35
|
-
}, delay);
|
|
36
|
-
};
|
|
37
|
-
function sendCurrent() {
|
|
38
|
-
if (latest === null || latestSocket?.readyState !== WebSocket.OPEN || acknowledged)
|
|
6
|
+
const republish = () => {
|
|
7
|
+
if (latest === null || latestSocket?.readyState !== WebSocket.OPEN)
|
|
39
8
|
return;
|
|
40
|
-
|
|
41
|
-
const hello = input.effective({ ...latest, projectSkillsStatus: "ready", helloSeq: generation });
|
|
42
|
-
const attempt = attempts + 1;
|
|
43
|
-
attempts = attempt;
|
|
9
|
+
const hello = input.effective({ ...latest, projectSkillsStatus: "ready" });
|
|
44
10
|
try {
|
|
45
11
|
latestSocket.send(JSON.stringify(hello));
|
|
46
|
-
input.onSent(hello
|
|
47
|
-
}
|
|
48
|
-
catch (error) {
|
|
49
|
-
input.onSendFailed?.(hello, attempt, error);
|
|
12
|
+
input.onSent(hello);
|
|
50
13
|
}
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
const attach = (socket) => {
|
|
54
|
-
clearRetry();
|
|
55
|
-
latestSocket = socket;
|
|
56
|
-
acknowledged = false;
|
|
57
|
-
attempts = 0;
|
|
58
|
-
generation += 1;
|
|
59
|
-
sendCurrent();
|
|
60
|
-
};
|
|
61
|
-
const detach = (socket) => {
|
|
62
|
-
if (latestSocket !== socket)
|
|
63
|
-
return;
|
|
64
|
-
clearRetry();
|
|
65
|
-
latestSocket = null;
|
|
66
|
-
acknowledged = false;
|
|
67
|
-
attempts = 0;
|
|
68
|
-
};
|
|
69
|
-
const republish = () => {
|
|
70
|
-
if (latest === null || latestSocket === null)
|
|
71
|
-
return;
|
|
72
|
-
clearRetry();
|
|
73
|
-
acknowledged = false;
|
|
74
|
-
attempts = 0;
|
|
75
|
-
generation += 1;
|
|
76
|
-
sendCurrent();
|
|
77
|
-
};
|
|
78
|
-
const acknowledge = (socket, seq) => {
|
|
79
|
-
// 非当前 socket / 已确认 / 该 socket 已在关闭(close 后迟到的 ack)一律忽略:
|
|
80
|
-
// 后者会错误触发 onAck→清零强制重连计数,削弱重连上限。
|
|
81
|
-
if (latestSocket !== socket || latest === null || acknowledged || socket.readyState !== WebSocket.OPEN)
|
|
82
|
-
return;
|
|
83
|
-
// 代次校验:只接受回显当前代次的 ack,忽略上一代次迟到的 ack(否则会误停新代次的重试)。
|
|
84
|
-
// seq 缺省(老 server 不回显)时退回仅按 socket 匹配,保持兼容。
|
|
85
|
-
if (seq !== undefined && seq !== generation)
|
|
86
|
-
return;
|
|
87
|
-
acknowledged = true;
|
|
88
|
-
clearRetry();
|
|
89
|
-
input.onAck?.(input.effective({ ...latest, projectSkillsStatus: "ready" }));
|
|
14
|
+
catch { /* 非 OPEN,忽略 */ }
|
|
90
15
|
};
|
|
91
16
|
return Object.freeze({
|
|
92
|
-
publish: (hello, socket) => {
|
|
93
|
-
latest = hello;
|
|
94
|
-
if (latestSocket !== socket) {
|
|
95
|
-
attach(socket);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
acknowledged = false;
|
|
99
|
-
attempts = 0;
|
|
100
|
-
generation += 1;
|
|
101
|
-
clearRetry();
|
|
102
|
-
sendCurrent();
|
|
103
|
-
},
|
|
104
|
-
attach,
|
|
105
|
-
detach,
|
|
17
|
+
publish: (hello, socket) => { latest = hello; latestSocket = socket; republish(); },
|
|
106
18
|
republish,
|
|
107
|
-
acknowledge,
|
|
108
19
|
});
|
|
109
20
|
}
|
package/dist/prompt.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* claim-before-work、freshness/draft、"做完所有事再停"、inbox notice 语义、
|
|
6
6
|
* 分层记忆与压缩安全、协作礼仪。措辞为本项目原创。
|
|
7
7
|
*/
|
|
8
|
-
import { detectLocalMemoryStorageVersion, selectLocalMemoryPreview } from "./local-memory/storage-format.js";
|
|
9
8
|
/** work-log 注入上限(字符):防冷启动注入随文件膨胀。超限保头(目标/结论)+尾(最近进展),
|
|
10
9
|
* 截中段并提示读全文——全文仍在盘上,只限"默认推送量"不限"可获取量"。纯函数。 */
|
|
11
10
|
export const WORKLOG_INJECT_CAP = 8000;
|
|
@@ -68,10 +67,7 @@ export function capWorkLogForInject(workLog, cap = WORKLOG_INJECT_CAP) {
|
|
|
68
67
|
const tail = workLog.slice(-WORKLOG_TAIL);
|
|
69
68
|
return `${head}\n…(work-log 过长已截去中段,全文用 Read 读 $CREW_TASK_LOG)\n${tail}`;
|
|
70
69
|
}
|
|
71
|
-
export function capMemoryForInject(memory, cap = MEMORY_INJECT_CAP, maxBytes = Number.POSITIVE_INFINITY
|
|
72
|
-
if (detectLocalMemoryStorageVersion(memory) === 2) {
|
|
73
|
-
return selectLocalMemoryPreview(memory, query, { maxCodePoints: cap, maxBytes }).memory;
|
|
74
|
-
}
|
|
70
|
+
export function capMemoryForInject(memory, cap = MEMORY_INJECT_CAP, maxBytes = Number.POSITIVE_INFINITY) {
|
|
75
71
|
if (memory.length <= cap && Buffer.byteLength(memory, "utf8") <= maxBytes)
|
|
76
72
|
return memory;
|
|
77
73
|
const notice = "[MEMORY.md preview truncated; use crew-memory search/read for more.]\n";
|
|
@@ -346,7 +342,7 @@ ${interactiveTaskRules}${freshnessRule}${externalReplyRule}${collaborationEtique
|
|
|
346
342
|
|
|
347
343
|
## Workspace 与分层记忆(CRITICAL — 索引+按需,配合上面的并行规则)
|
|
348
344
|
你的持久记忆在 \`$CREW_HOME\`(跨你所有任务共享),按“常驻索引 + 功能明细 + 当前工作”分层:
|
|
349
|
-
1. **MEMORY.md = 常驻索引 +
|
|
345
|
+
1. **MEMORY.md = 常驻索引 + 角色**:提示词末尾可能只有它的有界预览,预算不足时也可能省略。预览未命中不等于没有记忆。索引条目包含 \`file/type/scope/summary/triggers/priority/updated\`,其中 file 是相对于 \`$CREW_HOME\` 的路径(例如 \`notes/testing.md\`);需要历史约束、经验或偏好时先检索完整索引,再按需分段读取。
|
|
350
346
|
2. **notes/<topic>.md = 功能明细**:按用户偏好、项目规则、测试、调试、Git、事故复盘等主题拆分。优先读取与当前任务匹配的文件;\`notes/lessons.md\` 只保留未分类经验和历史兼容内容,不是所有新经验的默认目的地。普通任务默认只读不写,但当前用户明确要求时可以按最小范围更新;任务结束后的专门收尾执行仍按收尾指令复核。
|
|
351
347
|
- 对旧版 MEMORY.md,如果条目没有完整结构化字段但明确指向 \`notes/<file>.md\`,把它视为 legacy 索引;仍需结合条目标题/链接和当前请求判断相关性,不要因为缺少 summary/triggers 就假定正文已注入。
|
|
352
348
|
3. **本任务 work-log = \`$CREW_TASK_LOG\`**:每任务一份(别的运行不会碰),记当前阶段/下一步/卡点/关键结论。系统已把它的当前内容注入末尾让你续上;有进展就更新它——这是你**唯一**该在任务中持续写的记忆文件。**work-log 是现状快照,不是流水账**:保持 ≤100 行,更新时重写全文替换过时内容,不要无限追加(过长会被注入截断)。
|
package/dist/runner.js
CHANGED
|
@@ -80,7 +80,7 @@ async function runAgentWithMachineEnvironment(config, input, onActivity = defaul
|
|
|
80
80
|
? { scheduledExternalNotificationPolicy: input.scheduled.externalNotificationPolicy }
|
|
81
81
|
: {}),
|
|
82
82
|
...(input.wakeOrigin ? { wakeOrigin: input.wakeOrigin } : {}),
|
|
83
|
-
memory: capMemoryForInject(workspace.memory
|
|
83
|
+
memory: capMemoryForInject(workspace.memory),
|
|
84
84
|
...(resuming ? {} : { workLog: capWorkLogForInject(workspace.workLog) }),
|
|
85
85
|
collaborationToolsEnabled: config.codexCollaborationTools && runtime === "codex",
|
|
86
86
|
localMemoryV2Enabled: workspace.localMemoryV2Enabled ?? localMemoryV2Enabled,
|
|
@@ -12,14 +12,6 @@ const InputSchema = z.object({ bin: z.string().min(1), args: z.array(z.string())
|
|
|
12
12
|
wakePrompt: z.string().min(1), sessionId: z.string().min(1).optional(), resume: z.boolean() }).strict();
|
|
13
13
|
class PreflightError extends Error {
|
|
14
14
|
}
|
|
15
|
-
// Claude waits for every merged SessionStart hook before acknowledging SDK
|
|
16
|
-
// initialization. Those hooks may legitimately outlive the old 30s adapter
|
|
17
|
-
// deadline. Cap this phase below the daemon's default 120s fail-closed startup
|
|
18
|
-
// window so wrapper failure propagation and supervisor-owned tree cleanup retain
|
|
19
|
-
// margin. The 100s cap leaves a nominal 20s outside this phase; this runner's
|
|
20
|
-
// local TERM/KILL cleanup is itself bounded to 2s.
|
|
21
|
-
// Resume and cold recovery share this one budget; hook activity never extends it.
|
|
22
|
-
export const CLAUDE_INITIALIZE_TIMEOUT_MS = 100_000;
|
|
23
15
|
function hookCommand(path, nonce) {
|
|
24
16
|
const source = import.meta.url.endsWith(".ts");
|
|
25
17
|
const hook = fileURLToPath(new URL(`./claude-session-start-hook.${source ? "ts" : "js"}`, import.meta.url));
|
|
@@ -60,9 +52,6 @@ async function bounded(promise, milliseconds, label) {
|
|
|
60
52
|
clearTimeout(timer);
|
|
61
53
|
}
|
|
62
54
|
}
|
|
63
|
-
export function waitForClaudeInitialize(initialized, timeoutMs = CLAUDE_INITIALIZE_TIMEOUT_MS) {
|
|
64
|
-
return bounded(initialized, timeoutMs, "claude_initialize_timeout");
|
|
65
|
-
}
|
|
66
55
|
async function attempt(input, sessionId, resume, options) {
|
|
67
56
|
const proofDir = await mkdtemp(join(tmpdir(), "nowwork-claude-session-"));
|
|
68
57
|
const proofPath = join(proofDir, "proof.json");
|
|
@@ -199,7 +188,7 @@ async function attempt(input, sessionId, resume, options) {
|
|
|
199
188
|
try {
|
|
200
189
|
try {
|
|
201
190
|
await send({ type: "control_request", request_id: initId, request: { subtype: "initialize" } });
|
|
202
|
-
await
|
|
191
|
+
await bounded(initialized.promise, options.initializeTimeoutMs ?? 30_000, "claude_initialize_timeout");
|
|
203
192
|
const proof = JSON.parse(await readFile(proofPath, "utf8"));
|
|
204
193
|
if (proof.nonce !== nonce || proof.sessionId !== sessionId || proof.source !== (resume ? "resume" : "startup")) {
|
|
205
194
|
throw new Error("claude_session_proof_mismatch");
|
|
@@ -249,25 +238,15 @@ async function attempt(input, sessionId, resume, options) {
|
|
|
249
238
|
}
|
|
250
239
|
export async function runClaudeThread(input, options = {}) {
|
|
251
240
|
const sessionId = input.sessionId ?? randomUUID();
|
|
252
|
-
const initializeBudgetMs = options.initializeTimeoutMs ?? CLAUDE_INITIALIZE_TIMEOUT_MS;
|
|
253
|
-
const now = options.now ?? (() => performance.now());
|
|
254
|
-
const initializeDeadline = now() + Math.max(0, initializeBudgetMs);
|
|
255
|
-
const runAttempt = (attemptSessionId, resume) => {
|
|
256
|
-
const remainingMs = Math.max(0, Math.ceil(initializeDeadline - now()));
|
|
257
|
-
if (remainingMs === 0)
|
|
258
|
-
return Promise.reject(new PreflightError("claude_native_session_unconfirmed"));
|
|
259
|
-
return attempt(input, attemptSessionId, resume, { ...options, initializeTimeoutMs: remainingMs });
|
|
260
|
-
};
|
|
261
241
|
try {
|
|
262
|
-
return await
|
|
242
|
+
return await attempt(input, sessionId, input.resume, options);
|
|
263
243
|
}
|
|
264
244
|
catch (error) {
|
|
265
245
|
// The failed attempt never received a business user turn or entered its
|
|
266
|
-
// model/tool loop. Native startup hooks may run again on cold recovery
|
|
267
|
-
// within the original aggregate initialize deadline.
|
|
246
|
+
// model/tool loop. Native startup hooks may run again on cold recovery.
|
|
268
247
|
if (!input.resume || !(error instanceof PreflightError || error instanceof ThreadDeliveryColdStartRequired))
|
|
269
248
|
throw error;
|
|
270
|
-
return
|
|
249
|
+
return attempt(input, randomUUID(), false, options);
|
|
271
250
|
}
|
|
272
251
|
}
|
|
273
252
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|