@nowcrew/daemon 0.6.88-beta.2 → 0.6.88-beta.21
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 +43 -13
- package/dist/agent-memory/policy.js +3 -1
- package/dist/computer-profile.js +9 -4
- package/dist/config.js +0 -9
- package/dist/daemon-update-eligibility.js +1 -1
- package/dist/execution-journal.js +9 -6
- package/dist/execution-protocol.js +2 -1
- package/dist/execution-runner.js +5 -4
- package/dist/list-models.js +2 -0
- package/dist/local-execution-lease.js +22 -0
- package/dist/local-executor.js +58 -45
- package/dist/local-memory/candidate.js +43 -0
- package/dist/local-memory/index-budget.js +8 -0
- package/dist/local-memory/inventory-cli.js +61 -0
- package/dist/local-memory/inventory-preview.js +154 -0
- package/dist/local-memory/inventory-source.js +159 -0
- package/dist/local-memory/pending-match.js +29 -0
- package/dist/local-memory/pending-query.js +100 -0
- package/dist/local-memory/pending-schema.js +29 -0
- package/dist/local-memory/pending-source.js +126 -0
- package/dist/local-memory/pending.js +34 -84
- package/dist/local-memory/prompt.js +14 -11
- package/dist/local-memory/read-cli.js +97 -12
- package/dist/local-memory/runtime.js +24 -17
- package/dist/local-memory/work-log-execution.js +31 -0
- package/dist/local-memory/work-log.js +58 -0
- package/dist/local-memory/write.js +27 -58
- package/dist/machine-info.js +54 -17
- package/dist/main.js +4 -69
- package/dist/native-thread-context.js +59 -0
- package/dist/pre-admission-rejection.js +64 -0
- package/dist/project-skills/execution-adapter.js +3 -2
- package/dist/project-skills/runtime-launch.js +14 -0
- package/dist/prompt.js +15 -15
- package/dist/provider-env.js +3 -6
- package/dist/runner.js +14 -16
- package/dist/runtime-probe.js +34 -5
- package/dist/runtimes/acp-probe-transport.js +69 -0
- package/dist/runtimes/claude-session-start-hook.js +24 -0
- package/dist/runtimes/claude-thread-runner.js +266 -0
- package/dist/runtimes/claude.js +23 -4
- package/dist/runtimes/codex-app-server-runner.js +43 -4
- package/dist/runtimes/deepseek-harness-profile.js +92 -0
- package/dist/runtimes/deepseek-harness-runner.js +224 -0
- package/dist/runtimes/deepseek-harness.js +64 -11
- package/dist/runtimes/harness-probe-retry.js +42 -0
- package/dist/runtimes/kimi-acp-runner.js +63 -10
- package/dist/runtimes/opencode-runner.js +7 -1
- package/dist/serve.js +59 -101
- package/dist/session.js +4 -0
- package/dist/storage-identity.js +0 -3
- package/dist/storage-serve-adapter.js +6 -72
- package/dist/supervised-runtime.js +28 -18
- package/dist/thread-input-delivery.js +217 -0
- package/package.json +2 -3
- package/dist/codex-home-launch.js +0 -7
- package/dist/codex-home-mode-controller.js +0 -68
- package/dist/daemon-migration-controller.js +0 -211
- package/dist/daemon-migration-wiring.js +0 -22
- package/dist/runtimes/codex-home-migration-cli.js +0 -26
- package/dist/runtimes/codex-home-migration.js +0 -112
- package/dist/runtimes/codex-home.js +0 -236
- package/dist/storage-control.js +0 -212
- package/dist/storage-copy.js +0 -628
- package/dist/storage-credential-guard.js +0 -21
- package/dist/storage-doctor.js +0 -228
- package/dist/storage-manifest.js +0 -1027
- package/dist/storage-migration-controller.js +0 -440
- package/dist/storage-migration-manifest.js +0 -105
- package/dist/storage-migration-policy.js +0 -100
- package/dist/storage-runtime-controller.js +0 -381
- package/dist/storage-runtime-readiness.js +0 -240
- package/dist/storage-safe-fs.js +0 -302
- package/dist/storage-status-delivery.js +0 -224
- package/dist/storage-status-replay.js +0 -140
- package/dist/storage-target-confirmation.js +0 -129
- package/dist/storage-worker-command.js +0 -37
- package/dist/storage-worker-executor.js +0 -449
- package/dist/storage-worker-handoff.js +0 -151
- package/dist/storage-worker-operation.js +0 -294
- package/dist/storage-worker-status.js +0 -259
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { AcpProbeTransport } from "./acp-probe-transport.js";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { dslog } from "../slog.js";
|
|
1
4
|
import { once } from "node:events";
|
|
2
5
|
import { isAbsolute } from "node:path";
|
|
3
6
|
import { parseArgs } from "node:util";
|
|
@@ -6,6 +9,7 @@ import { pathToFileURL } from "node:url";
|
|
|
6
9
|
import spawn from "cross-spawn";
|
|
7
10
|
import { PROTOCOL_VERSION, RequestError, client, methods, ndJsonStream, } from "@agentclientprotocol/sdk";
|
|
8
11
|
import { augmentedPath } from "../runtime-path.js";
|
|
12
|
+
import { prepareThreadDelivery } from "../thread-input-delivery.js";
|
|
9
13
|
import { startFirstProgressWatchdog } from "./progress-watchdog.js";
|
|
10
14
|
import { assertKimiLegacyPromptFits, buildKimiArgs } from "./kimi.js";
|
|
11
15
|
const ERROR_MESSAGE_CAP = 2_000;
|
|
@@ -193,18 +197,38 @@ async function stopChild(child) {
|
|
|
193
197
|
await closed;
|
|
194
198
|
}
|
|
195
199
|
}
|
|
200
|
+
export class AcpProbeCleanupError extends Error {
|
|
201
|
+
constructor() { super("ACP probe process cleanup unconfirmed"); this.name = "AcpProbeCleanupError"; }
|
|
202
|
+
}
|
|
196
203
|
/** Probe the ACP transport without starting a session or forcing an optional interactive login flow. */
|
|
197
204
|
export async function probeKimiAcp(options, spawnProcess = spawn) {
|
|
198
205
|
const provider = options.provider ?? "kimi";
|
|
199
206
|
const permission = options.effectivePermission ?? "sandboxed";
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
207
|
+
const probeId = options.probeId ?? randomUUID();
|
|
208
|
+
const started = performance.now();
|
|
209
|
+
const elapsed = () => Math.round(performance.now() - started);
|
|
210
|
+
const transport = new AcpProbeTransport(elapsed);
|
|
211
|
+
let spawnConfirmedMs;
|
|
212
|
+
let reason = "initializing", protocolVersion;
|
|
213
|
+
let stderrSeen = false;
|
|
214
|
+
const fields = () => ({ probe_id: probeId, runtime: provider, duration_ms: elapsed(),
|
|
215
|
+
...transport.fields(), spawn_confirmed_ms: spawnConfirmedMs, child_pid: child.pid,
|
|
216
|
+
reason, protocol_version: protocolVersion, stderr_seen: stderrSeen, aborted: options.signal?.aborted ?? false });
|
|
217
|
+
dslog("execution.acp_probe_started", "ACP 探测开始", { probe_id: probeId, runtime: provider, node_version: process.version });
|
|
218
|
+
const launchEnv = options.launchEnv ?? providerEnv(provider, permission);
|
|
219
|
+
const child = spawnProcess(options.bin, options.launchArgs ?? providerArgs(provider, options.configPath), {
|
|
220
|
+
cwd: options.launchCwd ?? process.cwd(),
|
|
221
|
+
// 显式 launchEnv 也决定 PATH/HOME;不能把它与父进程 PATH 混用。
|
|
222
|
+
env: { ...launchEnv, PATH: augmentedPath(launchEnv) },
|
|
204
223
|
stdio: ["pipe", "pipe", "pipe"],
|
|
205
224
|
});
|
|
225
|
+
child.once("spawn", () => {
|
|
226
|
+
spawnConfirmedMs = elapsed();
|
|
227
|
+
dslog("execution.acp_probe_spawned", "ACP 探测子进程已启动", fields());
|
|
228
|
+
});
|
|
206
229
|
if (child.stdin === null || child.stdout === null || child.stderr === null)
|
|
207
230
|
return false;
|
|
231
|
+
child.stderr.on("data", () => { stderrSeen = true; });
|
|
208
232
|
child.stderr.resume();
|
|
209
233
|
const app = client({ name: `nowcrew-daemon-${provider}-probe` })
|
|
210
234
|
.onRequest(methods.client.session.requestPermission, () => ({
|
|
@@ -214,25 +238,36 @@ export async function probeKimiAcp(options, spawnProcess = spawn) {
|
|
|
214
238
|
let timeout;
|
|
215
239
|
let onAbort;
|
|
216
240
|
try {
|
|
217
|
-
const stream = ndJsonStream(
|
|
241
|
+
const stream = ndJsonStream(transport.writable(child.stdin), transport.readable(child.stdout));
|
|
218
242
|
const connected = app.connectWith(stream, async (context) => {
|
|
219
|
-
|
|
243
|
+
transport.initializeRequested();
|
|
244
|
+
const initialized = await context.request(methods.agent.initialize, {
|
|
220
245
|
protocolVersion: PROTOCOL_VERSION,
|
|
221
246
|
clientCapabilities: {},
|
|
222
247
|
clientInfo: { name: "nowcrew-daemon", version: "1" },
|
|
223
248
|
});
|
|
224
|
-
|
|
249
|
+
transport.initializeResponse();
|
|
250
|
+
protocolVersion = initialized.protocolVersion;
|
|
251
|
+
const ready = protocolVersion === PROTOCOL_VERSION;
|
|
252
|
+
if (reason === "initializing")
|
|
253
|
+
reason = ready ? "ready" : "protocol_mismatch";
|
|
254
|
+
dslog("execution.acp_probe_handshake", "ACP 握手结果", { ...fields(), ready });
|
|
255
|
+
return ready;
|
|
225
256
|
});
|
|
226
257
|
const result = await Promise.race([
|
|
227
258
|
connected,
|
|
228
259
|
new Promise((resolve) => {
|
|
229
260
|
timeout = setTimeout(() => {
|
|
261
|
+
if (reason === "initializing")
|
|
262
|
+
reason = "timeout";
|
|
230
263
|
void stopChild(child);
|
|
231
264
|
resolve(false);
|
|
232
265
|
}, PROBE_TIMEOUT_MS);
|
|
233
266
|
}),
|
|
234
267
|
new Promise((resolve) => {
|
|
235
268
|
onAbort = () => {
|
|
269
|
+
if (reason === "initializing")
|
|
270
|
+
reason = "aborted";
|
|
236
271
|
void stopChild(child);
|
|
237
272
|
resolve(false);
|
|
238
273
|
};
|
|
@@ -245,6 +280,8 @@ export async function probeKimiAcp(options, spawnProcess = spawn) {
|
|
|
245
280
|
return result;
|
|
246
281
|
}
|
|
247
282
|
catch {
|
|
283
|
+
if (reason === "initializing")
|
|
284
|
+
reason = options.signal?.aborted ? "aborted" : "transport_error";
|
|
248
285
|
return false;
|
|
249
286
|
}
|
|
250
287
|
finally {
|
|
@@ -252,17 +289,33 @@ export async function probeKimiAcp(options, spawnProcess = spawn) {
|
|
|
252
289
|
clearTimeout(timeout);
|
|
253
290
|
if (onAbort !== undefined)
|
|
254
291
|
options.signal?.removeEventListener("abort", onAbort);
|
|
255
|
-
|
|
292
|
+
dslog("execution.acp_probe_cleanup_started", "ACP 探测清理开始", fields());
|
|
293
|
+
let cleanupFinished = false;
|
|
294
|
+
try {
|
|
295
|
+
await stopChild(child);
|
|
296
|
+
cleanupFinished = true;
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
throw new AcpProbeCleanupError();
|
|
300
|
+
}
|
|
301
|
+
finally {
|
|
302
|
+
dslog("execution.acp_probe_finished", "ACP 探测结束", { ...fields(),
|
|
303
|
+
cleanup_finished: cleanupFinished, exit_code: child.exitCode, exit_signal: child.signalCode,
|
|
304
|
+
level: reason === "ready" && cleanupFinished ? "INFO" : "WARN" });
|
|
305
|
+
}
|
|
256
306
|
}
|
|
257
307
|
}
|
|
258
|
-
export async function runKimiAcp(
|
|
308
|
+
export async function runKimiAcp(requestedOptions) {
|
|
309
|
+
// Until this adapter verifies native restoration, thread-bound runs are cold.
|
|
310
|
+
const options = process.env.CREW_THREAD_ROOT ? { ...requestedOptions, resume: false } : requestedOptions;
|
|
259
311
|
const provider = options.provider ?? "kimi";
|
|
260
312
|
const displayName = providerDisplayName(provider);
|
|
261
313
|
const permission = options.effectivePermission ?? "full_access";
|
|
262
314
|
if (provider === "deepseek-harness" && options.resume) {
|
|
263
315
|
throw new Error("DeepSeek Harness ACP does not support session resume");
|
|
264
316
|
}
|
|
265
|
-
const prompt = await
|
|
317
|
+
const prompt = (await prepareThreadDelivery({ env: process.env, resumed: false,
|
|
318
|
+
sessionId: "unverified-cold-acp", wakePrompt: await readPrompt() })).prompt;
|
|
266
319
|
const child = spawn(options.bin, providerArgs(provider, options.configPath), {
|
|
267
320
|
cwd: process.cwd(),
|
|
268
321
|
env: providerEnv(provider, permission),
|
|
@@ -4,6 +4,7 @@ import { parseArgs } from "node:util";
|
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import spawn from "cross-spawn";
|
|
6
6
|
import { augmentedPath } from "../runtime-path.js";
|
|
7
|
+
import { prepareThreadDelivery } from "../thread-input-delivery.js";
|
|
7
8
|
import { buildOpenCodeArgs, createOpenCodeEventDecoder } from "./opencode.js";
|
|
8
9
|
const ERROR_CAP = 2_000;
|
|
9
10
|
async function readPrompt() {
|
|
@@ -86,7 +87,12 @@ async function runAttempt(options, prompt) {
|
|
|
86
87
|
return { code: 0, staleSession: false };
|
|
87
88
|
}
|
|
88
89
|
export async function runOpenCode(options) {
|
|
89
|
-
const prompt = await
|
|
90
|
+
const prompt = (await prepareThreadDelivery({ env: process.env, resumed: false,
|
|
91
|
+
sessionId: "unverified-cold-opencode", wakePrompt: await readPrompt() })).prompt;
|
|
92
|
+
if (process.env.CREW_THREAD_ROOT) {
|
|
93
|
+
const { sessionId: _unverified, ...coldOptions } = options;
|
|
94
|
+
return (await runAttempt(coldOptions, prompt)).code;
|
|
95
|
+
}
|
|
90
96
|
const first = await runAttempt(options, prompt);
|
|
91
97
|
if (!first.staleSession)
|
|
92
98
|
return first.code;
|
package/dist/serve.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* 断线指数退避重连。一个 agent:start 进来就跑一次现有的 runAgent。
|
|
4
4
|
*/
|
|
5
5
|
import { WebSocket } from "ws";
|
|
6
|
+
import { persistPreAdmissionRejection, rejectQueuedAdmission } from "./pre-admission-rejection.js";
|
|
6
7
|
import { join } from "node:path";
|
|
7
8
|
import { randomUUID } from "node:crypto";
|
|
8
9
|
import { dslog, setSlogDefaults, drainSpool, flushSlog } from "./slog.js";
|
|
@@ -51,10 +52,8 @@ import { parseMemoryPruneTraceId } from "./memory-prune-diagnostics.js";
|
|
|
51
52
|
import { probeRuntimeHealth } from "./runtime-health.js";
|
|
52
53
|
import { handleRuntimeHealthProbeFrame } from "./runtime-health-handler.js";
|
|
53
54
|
import { buildControlPlaneUrl } from "./control-plane-url.js";
|
|
54
|
-
import { createServeMigrationController, handleMigrationControlMessage } from "./daemon-migration-wiring.js";
|
|
55
|
-
import { createCodexHomeModeController } from "./codex-home-mode-controller.js";
|
|
56
55
|
import { resolveDaemonLocalStateRootFromEnv } from "./daemon-local-home.js";
|
|
57
|
-
import {
|
|
56
|
+
import { bindStorageServerIdentity, createStorageIdentityAdmission, } from "./storage-serve-adapter.js";
|
|
58
57
|
export { buildControlPlaneUrl } from "./control-plane-url.js";
|
|
59
58
|
// normalize.ts 的活动种类 → activity 枚举
|
|
60
59
|
const ACTIVITY_MAP = {
|
|
@@ -127,28 +126,13 @@ export function serve(config, opts = {}) {
|
|
|
127
126
|
catch { /* reconnect/timeout reconciliation handles a lost status frame */ }
|
|
128
127
|
},
|
|
129
128
|
});
|
|
130
|
-
|
|
131
|
-
const migrationController = createServeMigrationController({
|
|
132
|
-
config,
|
|
133
|
-
...(opts.profileName === undefined ? {} : { profileName: opts.profileName }),
|
|
134
|
-
isBusy: () => executionRuns.size > 0 || legacyRuns.size > 0,
|
|
135
|
-
sendStatus: (frame) => { try {
|
|
136
|
-
if (ws?.readyState === WebSocket.OPEN)
|
|
137
|
-
ws.send(JSON.stringify(frame));
|
|
138
|
-
}
|
|
139
|
-
catch { /* reconnect */ } },
|
|
140
|
-
});
|
|
141
|
-
const codexHomeModeController = createCodexHomeModeController({ ...(opts.profileName === undefined ? {} : { profileName: opts.profileName }), scheduleRestart: opts.update?.scheduleRestart ?? scheduleServiceRestart, getServiceSpec: async () => { const eligibility = await updateEligibility(); return eligibility.eligible ? eligibility.serviceSpec : null; }, isBusy: () => executionRuns.size > 0 || legacyRuns.size > 0, sendStatus: (frame) => { try {
|
|
142
|
-
if (ws?.readyState === WebSocket.OPEN)
|
|
143
|
-
ws.send(JSON.stringify(frame));
|
|
144
|
-
}
|
|
145
|
-
catch { /* reconnect */ } } });
|
|
129
|
+
const storageIdentityAdmission = createStorageIdentityAdmission(opts.storageRuntime);
|
|
146
130
|
const projectionCoordinator = createAgentProjectionCoordinator();
|
|
147
131
|
const { projectRegistry, projectWorkspaceRegistry, projectDependencyProvisioner } = createProjectRegistryWiring(config.agentsRoot, opts.execution?.dependencies);
|
|
148
132
|
const agentAbilityRuntime = createAgentAbilityRuntime(config.agentsRoot, {
|
|
149
133
|
...opts.agentAbility,
|
|
150
134
|
coordinator: projectionCoordinator,
|
|
151
|
-
isAdmissionClosed:
|
|
135
|
+
isAdmissionClosed: storageIdentityAdmission.isAdmissionClosed,
|
|
152
136
|
});
|
|
153
137
|
let projectSkillsController;
|
|
154
138
|
const projectSkillsReconciler = opts.projectSkills?.reconciler ?? createProjectSkillsReconciler({
|
|
@@ -169,7 +153,7 @@ export function serve(config, opts = {}) {
|
|
|
169
153
|
}
|
|
170
154
|
catch { /* 下一次 ready 或项目操作会重新发送完整快照 */ }
|
|
171
155
|
},
|
|
172
|
-
isAdmissionClosed:
|
|
156
|
+
isAdmissionClosed: storageIdentityAdmission.isAdmissionClosed,
|
|
173
157
|
reconcile: (handle, bindings) => projectSkillsReconciler.reconcile(handle, bindings),
|
|
174
158
|
});
|
|
175
159
|
const logRegistryFailure = (error) => dslog("project_skills.registry_failed", "本机项目注册表读取失败,本次不上报项目清单(能力不受影响)", {
|
|
@@ -177,7 +161,7 @@ export function serve(config, opts = {}) {
|
|
|
177
161
|
});
|
|
178
162
|
const machineHelloPublisher = createMachineHelloPublisher({
|
|
179
163
|
effective: (hello) => {
|
|
180
|
-
const configuredIdentity = opts.
|
|
164
|
+
const configuredIdentity = opts.storageRuntime?.identity;
|
|
181
165
|
const currentIdentity = typeof configuredIdentity === "function"
|
|
182
166
|
? configuredIdentity()
|
|
183
167
|
: configuredIdentity;
|
|
@@ -187,23 +171,7 @@ export function serve(config, opts = {}) {
|
|
|
187
171
|
});
|
|
188
172
|
const initializedProjectSkills = initializedProjectSkillsReconciler(() => projectSkillsController.ensureScanned(), projectSkillsReconciler);
|
|
189
173
|
const configuredCapabilities = opts.machineInfo?.capabilities ?? daemonCapabilities(process.platform);
|
|
190
|
-
const
|
|
191
|
-
const storageStatusDelivery = createServeStorageStatusDelivery(opts.storageMigration, {
|
|
192
|
-
onPersistenceError: (error) => dslog("daemon.storage_status_outbox_failed", "存储迁移状态 outbox 持久化失败", { level: "ERROR", error_message: error.message }),
|
|
193
|
-
transmit: (frame) => { try {
|
|
194
|
-
if (ws?.readyState === WebSocket.OPEN)
|
|
195
|
-
ws.send(JSON.stringify(frame));
|
|
196
|
-
}
|
|
197
|
-
catch { /* retry */ } },
|
|
198
|
-
});
|
|
199
|
-
const storageMigrationRuntime = attachStorageMigrationRuntime(opts.storageMigration, {
|
|
200
|
-
isBusy: () => executionRuns.size > 0 || legacyRuns.size > 0,
|
|
201
|
-
waitForExecutionFrames: () => executionFrameQueue,
|
|
202
|
-
flushLocalState: flushSlog,
|
|
203
|
-
sendStatus: storageStatusDelivery.send,
|
|
204
|
-
});
|
|
205
|
-
storageMigrationAdmissionClosed = storageMigrationRuntime.isAdmissionClosed;
|
|
206
|
-
const prepareCapabilities = async () => daemonCapabilityBindings(process.platform, [...storageCapabilities, ...await managedDaemonCapabilities(updateEligibility)]);
|
|
174
|
+
const prepareCapabilities = async () => daemonCapabilityBindings(process.platform, [...configuredCapabilities, ...await managedDaemonCapabilities(updateEligibility)]);
|
|
207
175
|
const safeExecutionSend = (frame) => {
|
|
208
176
|
try {
|
|
209
177
|
if (ws?.readyState !== WebSocket.OPEN)
|
|
@@ -237,6 +205,11 @@ export function serve(config, opts = {}) {
|
|
|
237
205
|
return;
|
|
238
206
|
}
|
|
239
207
|
safeExecutionSend(frame);
|
|
208
|
+
if (frame.type === "execution:rejected" && frame.preAdmissionProof) {
|
|
209
|
+
const entry = await executionJournal.get(frame.executionId);
|
|
210
|
+
if (entry?.completion)
|
|
211
|
+
completionRetransmitter.track(entry.completion);
|
|
212
|
+
}
|
|
240
213
|
};
|
|
241
214
|
const replayExecutionTelemetry = async () => {
|
|
242
215
|
const frames = await executionTelemetry.replay();
|
|
@@ -316,57 +289,64 @@ export function serve(config, opts = {}) {
|
|
|
316
289
|
let connectionEpochToken;
|
|
317
290
|
ws.on("open", () => {
|
|
318
291
|
connectionEpochToken = projectSkillsController.beginConnectionEpoch();
|
|
319
|
-
|
|
292
|
+
storageIdentityAdmission.beginIdentityBinding();
|
|
320
293
|
const openedSocket = ws;
|
|
294
|
+
const connectionId = randomUUID();
|
|
321
295
|
backoff = 1000;
|
|
322
296
|
connectedAt = Date.now();
|
|
323
297
|
log(`🔌 已连接控制面 ${config.serverUrl}`);
|
|
324
|
-
dslog("daemon.ws_open", "已连接控制面", { server_url: config.serverUrl });
|
|
298
|
+
dslog("daemon.ws_open", "已连接控制面", { server_url: config.serverUrl, connection_id: connectionId });
|
|
325
299
|
// 连上了才有机会把离线期间(断连原因/退出前)落盘的日志补传上去
|
|
326
300
|
void drainSpool();
|
|
327
301
|
// 上报本机信息 (hostname/os/daemon 版本/已装 runtimes)
|
|
328
|
-
storageStatusDelivery.replayPending();
|
|
329
|
-
const storageReplay = replayStorageMigrationStatus(opts.storageMigration, openedSocket, () => ws === openedSocket, (error) => {
|
|
330
|
-
dslog("daemon.storage_status_replay_failed", "存储迁移状态重放失败", {
|
|
331
|
-
level: "ERROR", error_message: error.message,
|
|
332
|
-
});
|
|
333
|
-
}, storageStatusDelivery.send);
|
|
334
302
|
const { detectInstalled, detectExecutable = detectExecutionRuntimesWithSignal } = opts.machineInfo ?? {};
|
|
335
303
|
const helloPromise = (opts.machineInfo?.collect ?? collectMachineHello)(config.agentsRoot, config.executionLimits, process.platform, {
|
|
336
304
|
...(detectInstalled ? { detectInstalled } : {}),
|
|
337
305
|
...(opts.profileName === undefined ? {} : { profileName: opts.profileName }),
|
|
338
306
|
// First hello must not wait for third-party handshakes; optional transports arrive in the refresh.
|
|
339
307
|
detectExecutable: async (installed) => conservativeExecutionRuntimes(installed),
|
|
340
|
-
additionalCapabilities: async () =>
|
|
308
|
+
additionalCapabilities: async () => managedDaemonCapabilities(updateEligibility),
|
|
341
309
|
capabilities: capabilities.machineHello,
|
|
342
|
-
codexHomeMode: config.codexHomeMode,
|
|
343
310
|
});
|
|
344
311
|
runtimeFacts = helloPromise
|
|
345
312
|
.then(async (hello) => {
|
|
346
313
|
if (hello.executionProtocol === undefined) {
|
|
347
314
|
executionRuntimesKnown = true;
|
|
315
|
+
dslog("runtime_probe.hello_refresh", "runtime hello 刷新决策", {
|
|
316
|
+
connection_id: connectionId, reason: "protocol_unavailable",
|
|
317
|
+
current_socket: ws === openedSocket, socket_open: openedSocket.readyState === WebSocket.OPEN,
|
|
318
|
+
socket_ready_state: openedSocket.readyState, installed_runtimes: JSON.stringify(hello.runtimes),
|
|
319
|
+
execution_runtimes: JSON.stringify(hello.executionRuntimes),
|
|
320
|
+
});
|
|
348
321
|
return [];
|
|
349
322
|
}
|
|
350
|
-
const detected = await runtimeProbe.detect(hello.runtimes, detectExecutable);
|
|
323
|
+
const detected = await runtimeProbe.detect(hello.runtimes, detectExecutable, connectionId);
|
|
351
324
|
executionRuntimesKnown = true;
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
325
|
+
const currentSocket = ws === openedSocket;
|
|
326
|
+
const socketOpen = openedSocket.readyState === WebSocket.OPEN;
|
|
327
|
+
const hasNewRuntime = detected.some((runtime) => !hello.executionRuntimes.includes(runtime));
|
|
328
|
+
dslog("runtime_probe.hello_refresh", "runtime hello 刷新决策", {
|
|
329
|
+
connection_id: connectionId, current_socket: currentSocket, socket_open: socketOpen,
|
|
330
|
+
socket_ready_state: openedSocket.readyState, installed_runtimes: JSON.stringify(hello.runtimes),
|
|
331
|
+
initial_execution_runtimes: JSON.stringify(hello.executionRuntimes), execution_runtimes: JSON.stringify(detected),
|
|
332
|
+
reason: !currentSocket ? "stale_connection" : !socketOpen ? "socket_not_open" : !hasNewRuntime ? "unchanged" : "publish",
|
|
333
|
+
});
|
|
334
|
+
if (currentSocket && socketOpen && hasNewRuntime) {
|
|
355
335
|
detectedExecutionRuntimes = detected;
|
|
356
336
|
machineHelloPublisher.publish({ ...hello, executionRuntimes: [...detected] }, openedSocket);
|
|
357
337
|
}
|
|
358
338
|
return detected;
|
|
359
339
|
})
|
|
360
|
-
.catch((
|
|
340
|
+
.catch(() => {
|
|
361
341
|
executionRuntimesKnown = true;
|
|
362
342
|
dslog("execution.runtime_detection_failed", "runtime 探测失败", {
|
|
363
|
-
level: "ERROR",
|
|
343
|
+
level: "ERROR", reason: "detection_threw", connection_id: connectionId,
|
|
344
|
+
current_socket: ws === openedSocket, socket_open: openedSocket.readyState === WebSocket.OPEN,
|
|
364
345
|
});
|
|
365
346
|
return [];
|
|
366
347
|
});
|
|
367
348
|
void helloPromise
|
|
368
349
|
.then(async (hello) => {
|
|
369
|
-
await storageReplay;
|
|
370
350
|
if (ws !== openedSocket || openedSocket.readyState !== WebSocket.OPEN)
|
|
371
351
|
return;
|
|
372
352
|
detectedExecutionRuntimes = hello.executionRuntimes;
|
|
@@ -414,8 +394,6 @@ export function serve(config, opts = {}) {
|
|
|
414
394
|
if (typeof decoded !== "object" || decoded === null)
|
|
415
395
|
return;
|
|
416
396
|
const rawType = "type" in decoded && typeof decoded.type === "string" ? decoded.type : "";
|
|
417
|
-
if (storageStatusDelivery.acknowledge(decoded))
|
|
418
|
-
return;
|
|
419
397
|
if (rawType === "daemon:update" || rawType === "daemon:restart") {
|
|
420
398
|
void updateController.handle(decoded).catch((error) => {
|
|
421
399
|
dslog("daemon.control_failed", "daemon 控制操作处理失败", {
|
|
@@ -424,19 +402,6 @@ export function serve(config, opts = {}) {
|
|
|
424
402
|
});
|
|
425
403
|
return;
|
|
426
404
|
}
|
|
427
|
-
if (rawType === "daemon:migrate") {
|
|
428
|
-
void handleMigrationControlMessage(migrationController, decoded).catch((error) => dslog("daemon.migration_failed", "daemon 布局迁移失败", { level: "ERROR", error_message: error.message }));
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
if (rawType === "daemon:codex-home-mode") {
|
|
432
|
-
void codexHomeModeController.handle(decoded).catch((error) => dslog("daemon.codex_home_mode_failed", "Codex Home 模式切换失败", { level: "ERROR", error_message: error.message }));
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
if (handleStorageMigrationControl(opts.storageMigration, rawType, decoded, storageStatusDelivery.send, (kind, error) => {
|
|
436
|
-
dslog(kind === "migration" ? "daemon.storage_migration_failed" : "daemon.storage_migration_confirmation_failed", kind === "migration" ? "存储迁移控制操作失败" : "存储迁移 hello 确认处理失败", { level: "ERROR", error_message: error.message });
|
|
437
|
-
})) {
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
405
|
if (rawType.startsWith("execution:")) {
|
|
441
406
|
const parsedExecution = ServerToDaemonExecutionFrameSchema.safeParse(decoded);
|
|
442
407
|
if (!parsedExecution.success) {
|
|
@@ -498,10 +463,10 @@ export function serve(config, opts = {}) {
|
|
|
498
463
|
return;
|
|
499
464
|
}
|
|
500
465
|
const spec = frame;
|
|
501
|
-
if (
|
|
466
|
+
if (storageIdentityAdmission.isAdmissionClosed()) {
|
|
502
467
|
safeExecutionSend(ExecutionRejectedSchema.parse({
|
|
503
468
|
type: "execution:rejected", protocolVersion: 1, executionId: spec.executionId,
|
|
504
|
-
reason: "resource_limit", message: "Storage
|
|
469
|
+
reason: "resource_limit", message: "Storage identity binding is not complete",
|
|
505
470
|
at: new Date().toISOString(),
|
|
506
471
|
}));
|
|
507
472
|
return;
|
|
@@ -560,18 +525,8 @@ export function serve(config, opts = {}) {
|
|
|
560
525
|
: spec.context.scheduledRunId === undefined ? "normal" : "scheduled";
|
|
561
526
|
let reservation = sharedSlots.reserve(spec.agent.handle, "execution", executionClass);
|
|
562
527
|
if (!reservation.accepted) {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
execution_id: spec.executionId,
|
|
566
|
-
agent_handle: spec.agent.handle,
|
|
567
|
-
execution_class: executionClass,
|
|
568
|
-
...reservation.facts,
|
|
569
|
-
});
|
|
570
|
-
safeExecutionSend(ExecutionRejectedSchema.parse({
|
|
571
|
-
type: "execution:rejected", protocolVersion: 1, executionId: spec.executionId,
|
|
572
|
-
reason: "resource_limit", message: "Local machine execution queue is full",
|
|
573
|
-
at: new Date().toISOString(),
|
|
574
|
-
}));
|
|
528
|
+
await rejectQueuedAdmission({ journal: executionJournal, knownExecutionHashes, executionRuns,
|
|
529
|
+
report: reportExecutionFrame, replay: sendJournalStatus }, spec, executionClass, reservation.facts);
|
|
575
530
|
return;
|
|
576
531
|
}
|
|
577
532
|
knownExecutionHashes.set(spec.executionId, hash);
|
|
@@ -741,6 +696,13 @@ export function serve(config, opts = {}) {
|
|
|
741
696
|
...reservation.facts,
|
|
742
697
|
},
|
|
743
698
|
report: reportExecutionFrame,
|
|
699
|
+
confirmPreAdmission: async (rejection) => {
|
|
700
|
+
const prior = await executionJournal.get(spec.executionId);
|
|
701
|
+
if (prior !== null || stopped || knownExecutionHashes.get(spec.executionId) !== hash
|
|
702
|
+
|| executionRuns.get(spec.executionId) !== execution)
|
|
703
|
+
return false;
|
|
704
|
+
return await persistPreAdmissionRejection(executionJournal, spec, rejection) !== null;
|
|
705
|
+
},
|
|
744
706
|
startupGate: runtimeStartupGate,
|
|
745
707
|
startupTimeoutMs: config.executionLimits.startupTimeoutMs,
|
|
746
708
|
onRuntimePhase: (phase, runtime) => {
|
|
@@ -812,17 +774,17 @@ export function serve(config, opts = {}) {
|
|
|
812
774
|
// ready 帧带 server 视角的 machineId/workspaceId → 作为后续所有日志的默认关联键
|
|
813
775
|
const r = msg;
|
|
814
776
|
setSlogDefaults({ machine_id: r.machineId, workspace_id: r.workspaceId });
|
|
815
|
-
if (opts.
|
|
777
|
+
if (opts.storageRuntime !== undefined && typeof r.workspaceId === "string" && typeof r.machineId === "string") {
|
|
816
778
|
const bindingSocket = ws;
|
|
817
|
-
|
|
779
|
+
bindStorageServerIdentity(opts.storageRuntime, { workspaceId: r.workspaceId, machineId: r.machineId }, {
|
|
818
780
|
onBound: () => {
|
|
819
781
|
if (bindingSocket !== ws || bindingSocket?.readyState !== WebSocket.OPEN)
|
|
820
782
|
return;
|
|
821
|
-
|
|
783
|
+
storageIdentityAdmission.completeIdentityBinding();
|
|
822
784
|
machineHelloPublisher.republish();
|
|
823
785
|
},
|
|
824
786
|
onError: (error) => {
|
|
825
|
-
dslog("daemon.storage_identity_bind_failed", "
|
|
787
|
+
dslog("daemon.storage_identity_bind_failed", "存储 Workspace 身份绑定失败", { level: "ERROR", error_message: error.message });
|
|
826
788
|
if (bindingSocket === ws && bindingSocket?.readyState === WebSocket.OPEN)
|
|
827
789
|
bindingSocket.close(1011, "storage identity binding failed");
|
|
828
790
|
},
|
|
@@ -925,7 +887,7 @@ export function serve(config, opts = {}) {
|
|
|
925
887
|
}
|
|
926
888
|
if (msg.type !== "agent:start")
|
|
927
889
|
return; // ready/error 等忽略
|
|
928
|
-
if (
|
|
890
|
+
if (storageIdentityAdmission.isAdmissionClosed())
|
|
929
891
|
return;
|
|
930
892
|
// 任务键:scheduled run 用 runId(每次运行独立 cwd/work-log;overlap 由 server 端 skip_if_running 管,
|
|
931
893
|
// daemon 的 running 去重只兜底"同一 run 重复投递");普通唤醒仍是 线程锚点 ?? 频道。
|
|
@@ -1090,15 +1052,14 @@ export function serve(config, opts = {}) {
|
|
|
1090
1052
|
const ackHint = `\n**协作礼仪:决定接手后,先用 \`${sendCmd}\` 在该任务线程说一声你来跟进**——用日常语言带上你对问题的理解或打算先查什么,不要写"我接 task #N"这类内部编号;写不出实质内容就不单发,并进第一条实质进展,但不要长时间闷头干活把线程空着。`;
|
|
1091
1053
|
// 图片/文件附件:crew message read 会在消息下列出附件及其 id;图片需下载后用 Read 工具查看,才能真正"看到"内容。
|
|
1092
1054
|
const attHint = `\n若消息带图片/文件附件(read 会列出 id),用 \`crew attachment get <id>\` 下载到本地,图片再用 Read 工具打开查看后再处理。`;
|
|
1093
|
-
//
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
: `crew message read --channel ${msg.channelId}`;
|
|
1055
|
+
// 启动上下文由执行层交付;legacy 提示词也不能要求模型补读或退回频道历史。
|
|
1056
|
+
const contextHint = threadId
|
|
1057
|
+
? "直接依据程序交付的本线程上下文与新消息处理,无需启动补读历史。"
|
|
1058
|
+
: "没有线程锚点,只处理明确来信或请求补充当前任务线程,不读取频道历史猜测。";
|
|
1098
1059
|
const wakeText = scheduled
|
|
1099
1060
|
? buildScheduledPrompt(msg.channelId, msg.wake?.content ?? "", scheduled.outputPolicy, scheduled.externalNotificationPolicy)
|
|
1100
1061
|
: (msg.wake?.content
|
|
1101
|
-
? `你被唤醒(${msg.reason}): ${msg.wake.content}\n
|
|
1062
|
+
? `你被唤醒(${msg.reason}): ${msg.wake.content}\n${contextHint}${reasonHint}${ackHint}${threadHint}${attHint}`
|
|
1102
1063
|
: undefined);
|
|
1103
1064
|
const wakeOrigin = scheduled ? undefined : msg.wake?.origin;
|
|
1104
1065
|
const guarded = await runWithOriginDecisionGuard(wakeOrigin, async (attempt) => {
|
|
@@ -1124,8 +1085,8 @@ export function serve(config, opts = {}) {
|
|
|
1124
1085
|
},
|
|
1125
1086
|
} : {}),
|
|
1126
1087
|
...(wakeOrigin ? { wakeOrigin, originDecisionAttempt: attempt } : {}),
|
|
1127
|
-
//
|
|
1128
|
-
...(!scheduled && threadId ? { wakeMessageId: threadId } : {}),
|
|
1088
|
+
// legacy wake.threadId 已由 Server 归一到线程根;与普通触发消息 id 分开透传。
|
|
1089
|
+
...(!scheduled && threadId ? { threadId, wakeMessageId: threadId } : {}),
|
|
1129
1090
|
...(!scheduled && msg.wake?.seq !== undefined ? { wakeContextUpToSeq: msg.wake.seq } : {}),
|
|
1130
1091
|
...(attemptWake ? { wake: attemptWake } : {}),
|
|
1131
1092
|
}, reportActivity, reportConsole, {
|
|
@@ -1358,7 +1319,6 @@ export function serve(config, opts = {}) {
|
|
|
1358
1319
|
concurrencyTelemetry.stop();
|
|
1359
1320
|
runtimeProbe.stop();
|
|
1360
1321
|
completionRetransmitter.stop();
|
|
1361
|
-
storageStatusDelivery.stop();
|
|
1362
1322
|
const deadline = createShutdownDeadline(shutdownTimeoutMs);
|
|
1363
1323
|
if (reconnectTimer !== null) {
|
|
1364
1324
|
clearTimeout(reconnectTimer);
|
|
@@ -1368,8 +1328,6 @@ export function serve(config, opts = {}) {
|
|
|
1368
1328
|
const pending = [
|
|
1369
1329
|
webSocketClosed,
|
|
1370
1330
|
updateController.drain(),
|
|
1371
|
-
...(migrationController === null ? [] : [migrationController.drain()]),
|
|
1372
|
-
codexHomeModeController.drain(),
|
|
1373
1331
|
executionFrameQueue,
|
|
1374
1332
|
...executionRuns.values(),
|
|
1375
1333
|
...[...legacyRuns.values()].map((run) => run.done),
|
package/dist/session.js
CHANGED
|
@@ -24,6 +24,10 @@ export async function readSession(runDir) {
|
|
|
24
24
|
if (typeof o.sessionId !== "string" || !o.sessionId)
|
|
25
25
|
return null;
|
|
26
26
|
return {
|
|
27
|
+
...(typeof o.runtime === "string" ? { runtime: o.runtime } : {}),
|
|
28
|
+
...(typeof o.threadRoot === "string" ? { threadRoot: o.threadRoot } : {}),
|
|
29
|
+
...(Number.isSafeInteger(o.threadContextSeq) && o.threadContextSeq >= 0 ? { threadContextSeq: o.threadContextSeq } : {}),
|
|
30
|
+
...(typeof o.threadHistoryComplete === "boolean" ? { threadHistoryComplete: o.threadHistoryComplete } : {}),
|
|
27
31
|
sessionId: o.sessionId,
|
|
28
32
|
lastRunAt: typeof o.lastRunAt === "number" ? o.lastRunAt : 0,
|
|
29
33
|
turns: typeof o.turns === "number" ? o.turns : 0,
|
package/dist/storage-identity.js
CHANGED
|
@@ -39,8 +39,5 @@ export function storageHelloIdentity(input) {
|
|
|
39
39
|
daemonRootFingerprint: fingerprint("daemon-root", layout.daemonRoot),
|
|
40
40
|
agentRootFingerprint: fingerprint("agent-root", layout.agentItemsRoot),
|
|
41
41
|
...(ownership === undefined ? {} : { storageOwnership: ownership }),
|
|
42
|
-
...(installation.storageMigrationId === undefined
|
|
43
|
-
? {}
|
|
44
|
-
: { storageMigrationId: installation.storageMigrationId }),
|
|
45
42
|
});
|
|
46
43
|
}
|
|
@@ -1,55 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
if (options === undefined)
|
|
5
|
-
return configured;
|
|
6
|
-
return Object.freeze([...new Set([
|
|
7
|
-
...configured,
|
|
8
|
-
STORAGE_MIGRATION_STATUS_CAPABILITY,
|
|
9
|
-
...(options.ready ? [STORAGE_MIGRATION_CAPABILITY] : []),
|
|
10
|
-
])]);
|
|
1
|
+
export function bindStorageServerIdentity(options, identity, callbacks) {
|
|
2
|
+
void Promise.resolve().then(() => options.bindServerIdentity?.(identity))
|
|
3
|
+
.then(callbacks.onBound).catch(callbacks.onError);
|
|
11
4
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (bind === undefined) {
|
|
15
|
-
if (options?.ready === true)
|
|
16
|
-
callbacks.onBound();
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
void Promise.resolve().then(() => bind(identity)).then(callbacks.onBound).catch(callbacks.onError);
|
|
20
|
-
}
|
|
21
|
-
export function createServeStorageStatusDelivery(options, dependencies) {
|
|
22
|
-
return createStorageStatusDelivery({
|
|
23
|
-
...(options?.statusOutboxPath === undefined ? {} : { outboxPath: options.statusOutboxPath }),
|
|
24
|
-
...(options?.statusRetryMs === undefined ? {} : { retryMs: options.statusRetryMs }),
|
|
25
|
-
...dependencies,
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
export function attachStorageMigrationRuntime(options, dependencies) {
|
|
29
|
-
let migrationAdmissionClosed = false;
|
|
5
|
+
/** 新连接必须重新验证 Server 身份,不能沿用上一次连接的执行准入。 */
|
|
6
|
+
export function createStorageIdentityAdmission(options) {
|
|
30
7
|
let identityAdmissionClosed = options?.bindServerIdentity !== undefined;
|
|
31
|
-
if (options?.ready === true) {
|
|
32
|
-
options.attach?.({
|
|
33
|
-
gate: {
|
|
34
|
-
isBusy: dependencies.isBusy,
|
|
35
|
-
closeAndFlush: async () => {
|
|
36
|
-
migrationAdmissionClosed = true;
|
|
37
|
-
await dependencies.waitForExecutionFrames();
|
|
38
|
-
if (dependencies.isBusy()) {
|
|
39
|
-
migrationAdmissionClosed = false;
|
|
40
|
-
throw Object.assign(new Error("active execution entered before storage migration quiesce"), {
|
|
41
|
-
code: "runtime_busy",
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
await dependencies.flushLocalState();
|
|
45
|
-
},
|
|
46
|
-
reopen: () => { migrationAdmissionClosed = false; },
|
|
47
|
-
},
|
|
48
|
-
sendStatus: dependencies.sendStatus,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
8
|
return Object.freeze({
|
|
52
|
-
isAdmissionClosed: () =>
|
|
9
|
+
isAdmissionClosed: () => identityAdmissionClosed,
|
|
53
10
|
beginIdentityBinding: () => {
|
|
54
11
|
if (options?.bindServerIdentity !== undefined)
|
|
55
12
|
identityAdmissionClosed = true;
|
|
@@ -57,26 +14,3 @@ export function attachStorageMigrationRuntime(options, dependencies) {
|
|
|
57
14
|
completeIdentityBinding: () => { identityAdmissionClosed = false; },
|
|
58
15
|
});
|
|
59
16
|
}
|
|
60
|
-
export function replayStorageMigrationStatus(options, socket, isCurrent, onError, deliver) {
|
|
61
|
-
return options?.replayStatus?.((frame) => {
|
|
62
|
-
if (!isCurrent())
|
|
63
|
-
return;
|
|
64
|
-
if (deliver !== undefined)
|
|
65
|
-
deliver(frame);
|
|
66
|
-
else if (socket.readyState === socket.OPEN)
|
|
67
|
-
socket.send(JSON.stringify(frame));
|
|
68
|
-
}).catch(onError) ?? Promise.resolve();
|
|
69
|
-
}
|
|
70
|
-
export function handleStorageMigrationControl(options, rawType, raw, sendStatus, onError) {
|
|
71
|
-
if (rawType === "daemon:storage-doctor" || rawType === "daemon:storage-migrate") {
|
|
72
|
-
if (options?.ready === true)
|
|
73
|
-
void options.controller.handle(raw).catch((error) => onError("migration", error));
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
if (rawType !== "daemon:storage-migration-confirmed")
|
|
77
|
-
return false;
|
|
78
|
-
const confirm = options?.ready === false ? options.confirm : undefined;
|
|
79
|
-
if (confirm !== undefined)
|
|
80
|
-
void confirm(raw, sendStatus).catch((error) => onError("confirmation", error));
|
|
81
|
-
return true;
|
|
82
|
-
}
|