@nowcrew/daemon 0.5.11 → 0.5.13
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 +77 -0
- package/dist/config.js +38 -0
- package/dist/execution-event-limit.js +59 -0
- package/dist/execution-journal-lock.js +262 -0
- package/dist/execution-journal.js +678 -0
- package/dist/execution-protocol.js +310 -0
- package/dist/execution-runner.js +637 -0
- package/dist/execution-supervisor-child.js +185 -0
- package/dist/execution-supervisor.js +209 -0
- package/dist/local-executor.js +326 -0
- package/dist/machine-info.js +13 -4
- package/dist/origin-decision.js +42 -0
- package/dist/prompt.js +23 -1
- package/dist/runner.js +146 -340
- package/dist/runtimes/claude.js +9 -1
- package/dist/runtimes/codex.js +21 -5
- package/dist/runtimes/kimi.js +3 -0
- package/dist/scheduled-run-report.js +15 -7
- package/dist/serve.js +412 -57
- package/dist/token.js +5 -2
- package/dist/workspace.js +39 -11
- package/package.json +3 -2
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { DaemonToServerExecutionFrameSchema, ExecutionCompletedSchema, ExecutionRejectedSchema, ExecutionStartSchema, } from "./execution-protocol.js";
|
|
3
|
+
import { JournalConflictError } from "./execution-journal.js";
|
|
4
|
+
import { boundExecutionFrame } from "./execution-event-limit.js";
|
|
5
|
+
import { mintAgentToken } from "./token.js";
|
|
6
|
+
import { executeLocal, withLocalExecutionFacts, } from "./local-executor.js";
|
|
7
|
+
import { startDormantSupervisor, } from "./execution-supervisor.js";
|
|
8
|
+
import { buildClaudeArgs, CLAUDE_EFFORT_LEVELS } from "./runtimes/claude.js";
|
|
9
|
+
import { buildCodexArgs, CODEX_EFFORT_LEVELS } from "./runtimes/codex.js";
|
|
10
|
+
import { buildKimiArgs, KIMI_EFFORT_LEVELS } from "./runtimes/kimi.js";
|
|
11
|
+
const ACTIVITY_KIND = {
|
|
12
|
+
init: "working",
|
|
13
|
+
text: "thinking",
|
|
14
|
+
reading: "reading",
|
|
15
|
+
sending: "sending",
|
|
16
|
+
checking: "checking",
|
|
17
|
+
claiming: "claiming",
|
|
18
|
+
crew: "working",
|
|
19
|
+
tool: "working",
|
|
20
|
+
tool_result: "working",
|
|
21
|
+
done: "done",
|
|
22
|
+
error: "error",
|
|
23
|
+
};
|
|
24
|
+
function canonical(value) {
|
|
25
|
+
if (Array.isArray(value))
|
|
26
|
+
return value.map(canonical);
|
|
27
|
+
if (value !== null && typeof value === "object") {
|
|
28
|
+
return Object.fromEntries(Object.entries(value)
|
|
29
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
30
|
+
.map(([key, nested]) => [key, canonical(nested)]));
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
export function hashExecutionSpec(spec) {
|
|
35
|
+
return createHash("sha256").update(JSON.stringify(canonical(spec))).digest("hex");
|
|
36
|
+
}
|
|
37
|
+
async function reportBestEffort(report, frame, timeoutMs) {
|
|
38
|
+
let timeout;
|
|
39
|
+
try {
|
|
40
|
+
return await Promise.race([
|
|
41
|
+
Promise.resolve(report(frame)).then(() => true, () => true),
|
|
42
|
+
new Promise((resolve) => {
|
|
43
|
+
timeout = setTimeout(() => resolve(false), timeoutMs);
|
|
44
|
+
}),
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
if (timeout !== undefined)
|
|
52
|
+
clearTimeout(timeout);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const DEFAULT_TELEMETRY_DRAIN_TIMEOUT_MS = 2_000;
|
|
56
|
+
const DEFAULT_TELEMETRY_MAX_PENDING_FRAMES = 256;
|
|
57
|
+
const DEFAULT_TELEMETRY_MAX_PENDING_BYTES = 1_048_576;
|
|
58
|
+
const MAX_TIMER_MS = 2_147_483_647;
|
|
59
|
+
class TelemetryQueue {
|
|
60
|
+
report;
|
|
61
|
+
timeoutMs;
|
|
62
|
+
maxPendingFrames;
|
|
63
|
+
maxPendingBytes;
|
|
64
|
+
pending = [];
|
|
65
|
+
idleWaiters = [];
|
|
66
|
+
pendingBytes = 0;
|
|
67
|
+
running = false;
|
|
68
|
+
closed = false;
|
|
69
|
+
constructor(report, timeoutMs, maxPendingFrames, maxPendingBytes) {
|
|
70
|
+
this.report = report;
|
|
71
|
+
this.timeoutMs = timeoutMs;
|
|
72
|
+
this.maxPendingFrames = maxPendingFrames;
|
|
73
|
+
this.maxPendingBytes = maxPendingBytes;
|
|
74
|
+
}
|
|
75
|
+
enqueue(frame) {
|
|
76
|
+
if (this.closed)
|
|
77
|
+
return;
|
|
78
|
+
const bytes = Buffer.byteLength(JSON.stringify(frame), "utf8");
|
|
79
|
+
if (this.pending.length >= this.maxPendingFrames
|
|
80
|
+
|| this.pendingBytes + bytes > this.maxPendingBytes)
|
|
81
|
+
return;
|
|
82
|
+
this.pending.push({ frame, bytes });
|
|
83
|
+
this.pendingBytes += bytes;
|
|
84
|
+
void this.pump();
|
|
85
|
+
}
|
|
86
|
+
async closeAndDrain() {
|
|
87
|
+
this.closed = true;
|
|
88
|
+
if (!this.running && this.pending.length === 0)
|
|
89
|
+
return;
|
|
90
|
+
let timeout;
|
|
91
|
+
let resolveIdle;
|
|
92
|
+
const idleWaiter = () => resolveIdle();
|
|
93
|
+
const drained = await Promise.race([
|
|
94
|
+
new Promise((resolve) => {
|
|
95
|
+
resolveIdle = () => resolve(true);
|
|
96
|
+
this.idleWaiters.push(idleWaiter);
|
|
97
|
+
}),
|
|
98
|
+
new Promise((resolve) => {
|
|
99
|
+
timeout = setTimeout(() => resolve(false), this.timeoutMs);
|
|
100
|
+
}),
|
|
101
|
+
]);
|
|
102
|
+
if (timeout !== undefined)
|
|
103
|
+
clearTimeout(timeout);
|
|
104
|
+
if (!drained) {
|
|
105
|
+
const waiterIndex = this.idleWaiters.indexOf(idleWaiter);
|
|
106
|
+
if (waiterIndex >= 0)
|
|
107
|
+
this.idleWaiters.splice(waiterIndex, 1);
|
|
108
|
+
this.fuse();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async pump() {
|
|
112
|
+
if (this.running)
|
|
113
|
+
return;
|
|
114
|
+
this.running = true;
|
|
115
|
+
while (this.pending.length > 0) {
|
|
116
|
+
const pending = this.pending.shift();
|
|
117
|
+
if (pending !== undefined) {
|
|
118
|
+
this.pendingBytes -= pending.bytes;
|
|
119
|
+
await this.reportBounded(pending.frame);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
this.running = false;
|
|
123
|
+
for (const resolve of this.idleWaiters.splice(0))
|
|
124
|
+
resolve();
|
|
125
|
+
}
|
|
126
|
+
async reportBounded(frame) {
|
|
127
|
+
const settled = await reportBestEffort(this.report, frame, this.timeoutMs);
|
|
128
|
+
if (!settled)
|
|
129
|
+
this.fuse();
|
|
130
|
+
}
|
|
131
|
+
fuse() {
|
|
132
|
+
this.closed = true;
|
|
133
|
+
this.pending.length = 0;
|
|
134
|
+
this.pendingBytes = 0;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function telemetryDrainTimeout(value) {
|
|
138
|
+
if (value === undefined || !Number.isFinite(value))
|
|
139
|
+
return DEFAULT_TELEMETRY_DRAIN_TIMEOUT_MS;
|
|
140
|
+
return Math.min(MAX_TIMER_MS, Math.max(1, Math.floor(value)));
|
|
141
|
+
}
|
|
142
|
+
function positiveTelemetryLimit(value, fallback) {
|
|
143
|
+
if (value === undefined || !Number.isFinite(value))
|
|
144
|
+
return fallback;
|
|
145
|
+
return Math.max(1, Math.floor(value));
|
|
146
|
+
}
|
|
147
|
+
function effectivePermission(spec, config) {
|
|
148
|
+
const requested = spec.permissions.requested === "local_default"
|
|
149
|
+
? (config.dangerous ? "full_access" : "workspace_write")
|
|
150
|
+
: spec.permissions.requested;
|
|
151
|
+
if (!config.dangerous && requested === "full_access")
|
|
152
|
+
return "workspace_write";
|
|
153
|
+
return requested;
|
|
154
|
+
}
|
|
155
|
+
function validReasoning(spec) {
|
|
156
|
+
const reasoning = spec.runtime.reasoning;
|
|
157
|
+
if (reasoning === undefined || reasoning === "default")
|
|
158
|
+
return true;
|
|
159
|
+
if (spec.runtime.name === "claude") {
|
|
160
|
+
return CLAUDE_EFFORT_LEVELS.includes(reasoning);
|
|
161
|
+
}
|
|
162
|
+
if (spec.runtime.name === "codex") {
|
|
163
|
+
return CODEX_EFFORT_LEVELS.includes(reasoning);
|
|
164
|
+
}
|
|
165
|
+
return KIMI_EFFORT_LEVELS.includes(reasoning);
|
|
166
|
+
}
|
|
167
|
+
function launchProviderConfig(config) {
|
|
168
|
+
if (config === undefined)
|
|
169
|
+
return {};
|
|
170
|
+
return {
|
|
171
|
+
...(config.provider === undefined ? {} : { provider: config.provider }),
|
|
172
|
+
...(config.providerBaseUrl === undefined ? {} : { providerBaseUrl: config.providerBaseUrl }),
|
|
173
|
+
...(config.providerApiKey === undefined ? {} : { providerApiKey: config.providerApiKey }),
|
|
174
|
+
...(config.providerAuthMode === undefined ? {} : { providerAuthMode: config.providerAuthMode }),
|
|
175
|
+
...(config.providerSmallFastModel === undefined
|
|
176
|
+
? {}
|
|
177
|
+
: { providerSmallFastModel: config.providerSmallFastModel }),
|
|
178
|
+
...(config.envVars === undefined ? {} : { envVars: config.envVars }),
|
|
179
|
+
...(config.description === undefined ? {} : { description: config.description }),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function supervisorLaunch(request) {
|
|
183
|
+
const common = {
|
|
184
|
+
wakePrompt: request.wakePrompt,
|
|
185
|
+
dangerous: request.effectivePermission === "full_access",
|
|
186
|
+
effectivePermission: request.effectivePermission,
|
|
187
|
+
...(request.model === undefined ? {} : { model: request.model }),
|
|
188
|
+
...(request.reasoning === undefined ? {} : { reasoning: request.reasoning }),
|
|
189
|
+
};
|
|
190
|
+
if (request.runtime === "claude") {
|
|
191
|
+
return {
|
|
192
|
+
command: request.bin,
|
|
193
|
+
args: buildClaudeArgs({
|
|
194
|
+
...common,
|
|
195
|
+
bin: request.bin,
|
|
196
|
+
cwd: request.cwd,
|
|
197
|
+
env: request.env,
|
|
198
|
+
systemPromptPath: request.systemPromptPath,
|
|
199
|
+
...(request.sessionId === undefined ? {} : {
|
|
200
|
+
sessionId: request.sessionId,
|
|
201
|
+
resume: request.resume,
|
|
202
|
+
}),
|
|
203
|
+
}),
|
|
204
|
+
cwd: request.cwd,
|
|
205
|
+
env: request.env,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
if (request.runtime === "codex") {
|
|
209
|
+
return {
|
|
210
|
+
command: request.bin,
|
|
211
|
+
args: buildCodexArgs(common),
|
|
212
|
+
cwd: request.cwd,
|
|
213
|
+
env: request.env,
|
|
214
|
+
stdinText: `${request.systemPrompt}\n\n${request.wakePrompt}`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
command: request.bin,
|
|
219
|
+
args: buildKimiArgs({
|
|
220
|
+
wakePrompt: `${request.systemPrompt}\n\n${request.wakePrompt}`,
|
|
221
|
+
effectivePermission: request.effectivePermission,
|
|
222
|
+
...(request.model === undefined ? {} : { model: request.model }),
|
|
223
|
+
}),
|
|
224
|
+
cwd: request.cwd,
|
|
225
|
+
env: request.env,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function rejection(executionId, reason, message, at) {
|
|
229
|
+
return ExecutionRejectedSchema.parse({
|
|
230
|
+
type: "execution:rejected",
|
|
231
|
+
protocolVersion: 1,
|
|
232
|
+
executionId,
|
|
233
|
+
reason,
|
|
234
|
+
message,
|
|
235
|
+
at,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
function admission(spec, config, dependencies, at) {
|
|
239
|
+
if (dependencies.platform === "win32" || (dependencies.platform === undefined && process.platform === "win32")) {
|
|
240
|
+
return { rejected: rejection(spec.executionId, "capability_missing", "Execution supervisor is unavailable on Windows", at) };
|
|
241
|
+
}
|
|
242
|
+
const promptBytes = Buffer.byteLength(spec.instructions.systemPrompt, "utf8")
|
|
243
|
+
+ Buffer.byteLength(spec.instructions.wakePrompt, "utf8");
|
|
244
|
+
if (promptBytes > config.executionLimits.maxPromptBytes) {
|
|
245
|
+
return { rejected: rejection(spec.executionId, "resource_limit", "Prompt exceeds the local byte limit", at) };
|
|
246
|
+
}
|
|
247
|
+
if (spec.runtime.timeoutMs !== undefined
|
|
248
|
+
&& spec.runtime.timeoutMs > config.executionLimits.maxTimeoutMs) {
|
|
249
|
+
return { rejected: rejection(spec.executionId, "resource_limit", "Timeout exceeds the local limit", at) };
|
|
250
|
+
}
|
|
251
|
+
if (!dependencies.facts.availableRuntimes.includes(spec.runtime.name)) {
|
|
252
|
+
return { rejected: rejection(spec.executionId, "runtime_unavailable", "Requested runtime is unavailable", at) };
|
|
253
|
+
}
|
|
254
|
+
if (!validReasoning(spec)) {
|
|
255
|
+
return { rejected: rejection(spec.executionId, "invalid_spec", "Reasoning mode is unsupported by the runtime", at) };
|
|
256
|
+
}
|
|
257
|
+
if (!Number.isInteger(dependencies.facts.activeForAgent)
|
|
258
|
+
|| !Number.isInteger(dependencies.facts.queuedForAgent)
|
|
259
|
+
|| dependencies.facts.activeForAgent < 0
|
|
260
|
+
|| dependencies.facts.queuedForAgent < 0) {
|
|
261
|
+
return { rejected: rejection(spec.executionId, "invalid_spec", "Invalid local resource facts", at) };
|
|
262
|
+
}
|
|
263
|
+
const slotInvalid = dependencies.slot?.state === "ready"
|
|
264
|
+
? dependencies.facts.activeForAgent >= config.executionLimits.maxParallelPerAgent
|
|
265
|
+
: dependencies.slot?.state === "queued"
|
|
266
|
+
? dependencies.facts.activeForAgent < config.executionLimits.maxParallelPerAgent
|
|
267
|
+
|| dependencies.facts.queuedForAgent >= config.executionLimits.maxQueuedPerAgent
|
|
268
|
+
: false;
|
|
269
|
+
if (slotInvalid || (dependencies.slot === undefined && (dependencies.facts.activeForAgent >= config.executionLimits.maxParallelPerAgent
|
|
270
|
+
|| dependencies.facts.queuedForAgent >= config.executionLimits.maxQueuedPerAgent))) {
|
|
271
|
+
return { rejected: rejection(spec.executionId, "resource_limit", "Local execution capacity is exhausted", at) };
|
|
272
|
+
}
|
|
273
|
+
const permission = effectivePermission(spec, config);
|
|
274
|
+
if (spec.runtime.name === "kimi" && permission !== "full_access") {
|
|
275
|
+
return { rejected: rejection(spec.executionId, "local_policy_denied", `Kimi cannot enforce ${permission} permission`, at) };
|
|
276
|
+
}
|
|
277
|
+
return { spec, permission };
|
|
278
|
+
}
|
|
279
|
+
class ExecutionCancelledError extends Error {
|
|
280
|
+
constructor() {
|
|
281
|
+
super("Execution cancelled before completion");
|
|
282
|
+
this.name = "ExecutionCancelledError";
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
async function cancellable(promise, cancellation) {
|
|
286
|
+
if (cancellation === undefined)
|
|
287
|
+
return promise;
|
|
288
|
+
if (cancellation.isRequested())
|
|
289
|
+
throw new ExecutionCancelledError();
|
|
290
|
+
return Promise.race([
|
|
291
|
+
promise,
|
|
292
|
+
cancellation.requested.then(() => { throw new ExecutionCancelledError(); }),
|
|
293
|
+
]);
|
|
294
|
+
}
|
|
295
|
+
function failedCompletion(spec, error, startedAt, finishedAt) {
|
|
296
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
297
|
+
return ExecutionCompletedSchema.parse({
|
|
298
|
+
type: "execution:completed",
|
|
299
|
+
protocolVersion: 1,
|
|
300
|
+
executionId: spec.executionId,
|
|
301
|
+
outcome: "failed",
|
|
302
|
+
errorCode: "local_execution_failed",
|
|
303
|
+
errorMessage: message || "Unknown local execution failure",
|
|
304
|
+
runtime: spec.runtime.name,
|
|
305
|
+
...(spec.runtime.model === undefined ? {} : { model: spec.runtime.model }),
|
|
306
|
+
resumed: false,
|
|
307
|
+
startedAt,
|
|
308
|
+
finishedAt,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
export async function runExecution(config, input, dependencies) {
|
|
312
|
+
const now = dependencies.now ?? (() => new Date());
|
|
313
|
+
const initialAt = now().toISOString();
|
|
314
|
+
const parsed = ExecutionStartSchema.safeParse(input);
|
|
315
|
+
if (!parsed.success) {
|
|
316
|
+
const executionId = typeof input === "object" && input !== null && "executionId" in input
|
|
317
|
+
&& typeof input.executionId === "string"
|
|
318
|
+
? input.executionId
|
|
319
|
+
: null;
|
|
320
|
+
if (executionId === null)
|
|
321
|
+
throw new TypeError("Invalid execution spec without an executionId");
|
|
322
|
+
const frame = ExecutionRejectedSchema.parse(boundExecutionFrame(rejection(executionId, "invalid_spec", parsed.error.message, initialAt), config.executionLimits.maxEventBytes));
|
|
323
|
+
await dependencies.report(frame);
|
|
324
|
+
return { kind: "rejected", frame };
|
|
325
|
+
}
|
|
326
|
+
const spec = parsed.data;
|
|
327
|
+
const bestEffortTimeoutMs = telemetryDrainTimeout(dependencies.telemetryDrainTimeoutMs);
|
|
328
|
+
const specHash = hashExecutionSpec(spec);
|
|
329
|
+
const replay = await dependencies.journal.get(spec.executionId);
|
|
330
|
+
if (replay !== null) {
|
|
331
|
+
if (replay.specHash !== specHash) {
|
|
332
|
+
throw new JournalConflictError(`Execution ${spec.executionId} already has a different specHash`);
|
|
333
|
+
}
|
|
334
|
+
const replayPermission = replay.effectivePermission ?? effectivePermission(spec, config);
|
|
335
|
+
if ((replay.state === "completed" || replay.state === "interrupted")
|
|
336
|
+
&& replay.completion !== null) {
|
|
337
|
+
await dependencies.report(boundExecutionFrame(replay.completion, config.executionLimits.maxEventBytes));
|
|
338
|
+
return { kind: "existing", entry: replay };
|
|
339
|
+
}
|
|
340
|
+
await reportBestEffort(dependencies.report, boundExecutionFrame(DaemonToServerExecutionFrameSchema.parse({
|
|
341
|
+
type: "execution:accepted",
|
|
342
|
+
protocolVersion: 1,
|
|
343
|
+
executionId: spec.executionId,
|
|
344
|
+
state: dependencies.slot?.state ?? "ready",
|
|
345
|
+
effectivePermission: replayPermission,
|
|
346
|
+
at: replay.acceptedAt,
|
|
347
|
+
}), config.executionLimits.maxEventBytes), bestEffortTimeoutMs);
|
|
348
|
+
if (replay.state === "running" && replay.processStartedAt !== null) {
|
|
349
|
+
await reportBestEffort(dependencies.report, boundExecutionFrame(DaemonToServerExecutionFrameSchema.parse({
|
|
350
|
+
type: "execution:started",
|
|
351
|
+
protocolVersion: 1,
|
|
352
|
+
executionId: spec.executionId,
|
|
353
|
+
at: replay.processStartedAt,
|
|
354
|
+
}), config.executionLimits.maxEventBytes), bestEffortTimeoutMs);
|
|
355
|
+
}
|
|
356
|
+
return { kind: "existing", entry: replay };
|
|
357
|
+
}
|
|
358
|
+
const checked = admission(spec, config, dependencies, initialAt);
|
|
359
|
+
if ("rejected" in checked) {
|
|
360
|
+
const frame = ExecutionRejectedSchema.parse(boundExecutionFrame(checked.rejected, config.executionLimits.maxEventBytes));
|
|
361
|
+
await dependencies.report(frame);
|
|
362
|
+
return { kind: "rejected", frame };
|
|
363
|
+
}
|
|
364
|
+
const { permission } = checked;
|
|
365
|
+
const effectiveTimeoutMs = spec.runtime.timeoutMs ?? config.executionLimits.maxTimeoutMs;
|
|
366
|
+
const accepted = await dependencies.journal.accept(spec.executionId, specHash, {
|
|
367
|
+
runtime: spec.runtime.name,
|
|
368
|
+
...(spec.runtime.model === undefined ? {} : { model: spec.runtime.model }),
|
|
369
|
+
resumed: false,
|
|
370
|
+
effectivePermission: permission,
|
|
371
|
+
});
|
|
372
|
+
if (accepted.kind === "existing") {
|
|
373
|
+
if ((accepted.state === "completed" || accepted.state === "interrupted")
|
|
374
|
+
&& accepted.completion !== null) {
|
|
375
|
+
await dependencies.report(boundExecutionFrame(accepted.completion, config.executionLimits.maxEventBytes));
|
|
376
|
+
return { kind: "existing", entry: accepted };
|
|
377
|
+
}
|
|
378
|
+
await reportBestEffort(dependencies.report, boundExecutionFrame(DaemonToServerExecutionFrameSchema.parse({
|
|
379
|
+
type: "execution:accepted",
|
|
380
|
+
protocolVersion: 1,
|
|
381
|
+
executionId: spec.executionId,
|
|
382
|
+
state: dependencies.slot?.state ?? "ready",
|
|
383
|
+
effectivePermission: accepted.effectivePermission ?? permission,
|
|
384
|
+
at: accepted.acceptedAt,
|
|
385
|
+
}), config.executionLimits.maxEventBytes), bestEffortTimeoutMs);
|
|
386
|
+
if (accepted.state === "running" && accepted.processStartedAt !== null) {
|
|
387
|
+
await reportBestEffort(dependencies.report, boundExecutionFrame(DaemonToServerExecutionFrameSchema.parse({
|
|
388
|
+
type: "execution:started",
|
|
389
|
+
protocolVersion: 1,
|
|
390
|
+
executionId: spec.executionId,
|
|
391
|
+
at: accepted.processStartedAt,
|
|
392
|
+
}), config.executionLimits.maxEventBytes), bestEffortTimeoutMs);
|
|
393
|
+
}
|
|
394
|
+
return { kind: "existing", entry: accepted };
|
|
395
|
+
}
|
|
396
|
+
await reportBestEffort(dependencies.report, boundExecutionFrame(DaemonToServerExecutionFrameSchema.parse({
|
|
397
|
+
type: "execution:accepted",
|
|
398
|
+
protocolVersion: 1,
|
|
399
|
+
executionId: spec.executionId,
|
|
400
|
+
state: dependencies.slot?.state ?? "ready",
|
|
401
|
+
effectivePermission: permission,
|
|
402
|
+
at: accepted.acceptedAt,
|
|
403
|
+
}), config.executionLimits.maxEventBytes), bestEffortTimeoutMs);
|
|
404
|
+
const mint = dependencies.mintAgentToken ?? mintAgentToken;
|
|
405
|
+
const execute = dependencies.executeLocal ?? executeLocal;
|
|
406
|
+
const startSupervisor = dependencies.startSupervisor ?? startDormantSupervisor;
|
|
407
|
+
const telemetry = new TelemetryQueue(dependencies.report, bestEffortTimeoutMs, positiveTelemetryLimit(dependencies.telemetryMaxPendingFrames, DEFAULT_TELEMETRY_MAX_PENDING_FRAMES), Math.max(config.executionLimits.maxEventBytes, positiveTelemetryLimit(dependencies.telemetryMaxPendingBytes, DEFAULT_TELEMETRY_MAX_PENDING_BYTES)));
|
|
408
|
+
const supervisorState = { active: null, abortOnce: null };
|
|
409
|
+
let launchClosed = false;
|
|
410
|
+
const launchAttempts = new Set();
|
|
411
|
+
const closeLaunchGate = async () => {
|
|
412
|
+
launchClosed = true;
|
|
413
|
+
await Promise.all([...launchAttempts]);
|
|
414
|
+
};
|
|
415
|
+
let startedAt = accepted.acceptedAt;
|
|
416
|
+
let timeout;
|
|
417
|
+
let timedOut = false;
|
|
418
|
+
let completion;
|
|
419
|
+
let rejectCancellationFailure;
|
|
420
|
+
const cancellationFailure = new Promise((_resolve, reject) => {
|
|
421
|
+
rejectCancellationFailure = reject;
|
|
422
|
+
});
|
|
423
|
+
try {
|
|
424
|
+
if (dependencies.slot !== undefined) {
|
|
425
|
+
await cancellable(dependencies.slot.ready, dependencies.cancellation);
|
|
426
|
+
}
|
|
427
|
+
const credential = await cancellable(mint(config.serverUrl, config.machineToken, spec.agent.handle, undefined, { executionId: spec.executionId }), dependencies.cancellation);
|
|
428
|
+
const providerConfig = launchProviderConfig(credential.config);
|
|
429
|
+
let activitySequence = 0;
|
|
430
|
+
let consoleSequence = 0;
|
|
431
|
+
const callbacks = {
|
|
432
|
+
...(spec.reporting.streamActivity ? {
|
|
433
|
+
onActivity: (activity) => {
|
|
434
|
+
const frame = DaemonToServerExecutionFrameSchema.parse({
|
|
435
|
+
type: "execution:activity",
|
|
436
|
+
protocolVersion: 1,
|
|
437
|
+
executionId: spec.executionId,
|
|
438
|
+
activity: ACTIVITY_KIND[activity.kind] ?? "working",
|
|
439
|
+
detail: activity.detail ?? activity.label,
|
|
440
|
+
seq: activitySequence++,
|
|
441
|
+
at: now().toISOString(),
|
|
442
|
+
});
|
|
443
|
+
try {
|
|
444
|
+
const bounded = boundExecutionFrame(frame, config.executionLimits.maxEventBytes);
|
|
445
|
+
telemetry.enqueue(bounded);
|
|
446
|
+
}
|
|
447
|
+
catch { /* best-effort activity omitted when its envelope cannot fit */ }
|
|
448
|
+
},
|
|
449
|
+
} : {}),
|
|
450
|
+
...(spec.reporting.streamConsole ? {
|
|
451
|
+
onConsole: (chunk) => {
|
|
452
|
+
const frame = DaemonToServerExecutionFrameSchema.parse({
|
|
453
|
+
type: "execution:console",
|
|
454
|
+
protocolVersion: 1,
|
|
455
|
+
executionId: spec.executionId,
|
|
456
|
+
stream: chunk.stream,
|
|
457
|
+
text: chunk.text,
|
|
458
|
+
seq: consoleSequence++,
|
|
459
|
+
at: now().toISOString(),
|
|
460
|
+
});
|
|
461
|
+
try {
|
|
462
|
+
const bounded = boundExecutionFrame(frame, config.executionLimits.maxEventBytes);
|
|
463
|
+
telemetry.enqueue(bounded);
|
|
464
|
+
}
|
|
465
|
+
catch { /* best-effort console omitted when its envelope cannot fit */ }
|
|
466
|
+
},
|
|
467
|
+
} : {}),
|
|
468
|
+
};
|
|
469
|
+
const localDependencies = {
|
|
470
|
+
launchRuntime: async (request) => {
|
|
471
|
+
if (launchClosed || dependencies.cancellation?.isRequested())
|
|
472
|
+
throw new ExecutionCancelledError();
|
|
473
|
+
let settleLaunch;
|
|
474
|
+
const launchSettled = new Promise((resolve) => { settleLaunch = resolve; });
|
|
475
|
+
launchAttempts.add(launchSettled);
|
|
476
|
+
try {
|
|
477
|
+
const processStartedAt = now().toISOString();
|
|
478
|
+
const guarded = await dependencies.journal.startGuarded(spec.executionId, processStartedAt, () => startSupervisor(supervisorLaunch(request)));
|
|
479
|
+
if (guarded.kind !== "started") {
|
|
480
|
+
throw new Error(`Execution became ${guarded.entry.state} before local launch`);
|
|
481
|
+
}
|
|
482
|
+
supervisorState.active = guarded.handle;
|
|
483
|
+
let stopPromise = null;
|
|
484
|
+
const stopOnce = (operation) => {
|
|
485
|
+
if (stopPromise === null) {
|
|
486
|
+
try {
|
|
487
|
+
stopPromise = Promise.resolve(operation());
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
stopPromise = Promise.reject(error);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return stopPromise;
|
|
494
|
+
};
|
|
495
|
+
const cancelOnce = () => stopOnce(guarded.handle.cancel);
|
|
496
|
+
supervisorState.abortOnce = () => stopOnce(guarded.handle.abort);
|
|
497
|
+
dependencies.cancellation?.register(cancelOnce);
|
|
498
|
+
startedAt = guarded.entry.processStartedAt ?? processStartedAt;
|
|
499
|
+
await reportBestEffort(dependencies.report, boundExecutionFrame(DaemonToServerExecutionFrameSchema.parse({
|
|
500
|
+
type: "execution:started",
|
|
501
|
+
protocolVersion: 1,
|
|
502
|
+
executionId: spec.executionId,
|
|
503
|
+
at: startedAt,
|
|
504
|
+
}), config.executionLimits.maxEventBytes), bestEffortTimeoutMs);
|
|
505
|
+
if (!dependencies.cancellation?.isRequested()) {
|
|
506
|
+
timeout = setTimeout(() => {
|
|
507
|
+
timedOut = true;
|
|
508
|
+
try {
|
|
509
|
+
void cancelOnce().catch(rejectCancellationFailure);
|
|
510
|
+
}
|
|
511
|
+
catch (error) {
|
|
512
|
+
rejectCancellationFailure(error);
|
|
513
|
+
}
|
|
514
|
+
}, effectiveTimeoutMs);
|
|
515
|
+
}
|
|
516
|
+
return guarded.handle;
|
|
517
|
+
}
|
|
518
|
+
finally {
|
|
519
|
+
launchAttempts.delete(launchSettled);
|
|
520
|
+
settleLaunch();
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
};
|
|
524
|
+
const localInput = {
|
|
525
|
+
executionId: spec.executionId,
|
|
526
|
+
handle: spec.agent.handle,
|
|
527
|
+
channelId: spec.context.channelId,
|
|
528
|
+
keyMode: "opaque",
|
|
529
|
+
taskKey: spec.workspace.taskKey,
|
|
530
|
+
...(spec.workspace.resumeKey === undefined ? {} : { resumeKey: spec.workspace.resumeKey }),
|
|
531
|
+
...(spec.context.wakeMessageId === undefined ? {} : { wakeMessageId: spec.context.wakeMessageId }),
|
|
532
|
+
systemPrompt: withLocalExecutionFacts(spec.instructions.systemPrompt, config.executionLimits.maxPromptBytes
|
|
533
|
+
- Buffer.byteLength(spec.instructions.wakePrompt, "utf8")),
|
|
534
|
+
wakePrompt: spec.instructions.wakePrompt,
|
|
535
|
+
runtime: {
|
|
536
|
+
name: spec.runtime.name,
|
|
537
|
+
...(spec.runtime.model === undefined ? {} : { model: spec.runtime.model }),
|
|
538
|
+
...(spec.runtime.reasoning === undefined ? {} : { reasoning: spec.runtime.reasoning }),
|
|
539
|
+
},
|
|
540
|
+
effectivePermission: permission,
|
|
541
|
+
captureFinal: spec.reporting.captureFinal,
|
|
542
|
+
launch: {
|
|
543
|
+
serverUrl: config.serverUrl,
|
|
544
|
+
token: credential.token,
|
|
545
|
+
agentId: credential.agentId,
|
|
546
|
+
agentsRoot: config.agentsRoot,
|
|
547
|
+
cliPath: config.cliPath,
|
|
548
|
+
providerConfig,
|
|
549
|
+
...(providerConfig.description ? { description: providerConfig.description } : {}),
|
|
550
|
+
},
|
|
551
|
+
session: {
|
|
552
|
+
enabled: config.resume,
|
|
553
|
+
warmMs: config.resumeWarmMs,
|
|
554
|
+
budgetTokens: config.sessionBudgetTokens,
|
|
555
|
+
softTokens: config.sessionSoftTokens,
|
|
556
|
+
maxTurns: config.sessionMaxTurns,
|
|
557
|
+
},
|
|
558
|
+
};
|
|
559
|
+
const result = await cancellable(Promise.race([
|
|
560
|
+
execute(localInput, callbacks, localDependencies),
|
|
561
|
+
cancellationFailure,
|
|
562
|
+
]), dependencies.cancellation);
|
|
563
|
+
if (timeout !== undefined)
|
|
564
|
+
clearTimeout(timeout);
|
|
565
|
+
const finishedAt = now().toISOString();
|
|
566
|
+
completion = ExecutionCompletedSchema.parse(timedOut ? {
|
|
567
|
+
type: "execution:completed",
|
|
568
|
+
protocolVersion: 1,
|
|
569
|
+
executionId: spec.executionId,
|
|
570
|
+
outcome: "cancelled",
|
|
571
|
+
errorCode: "timeout",
|
|
572
|
+
errorMessage: "Execution exceeded its local timeout",
|
|
573
|
+
runtime: result.runtime,
|
|
574
|
+
...(result.model === null ? {} : { model: result.model }),
|
|
575
|
+
resumed: result.resumed,
|
|
576
|
+
startedAt,
|
|
577
|
+
finishedAt,
|
|
578
|
+
} : {
|
|
579
|
+
type: "execution:completed",
|
|
580
|
+
protocolVersion: 1,
|
|
581
|
+
executionId: spec.executionId,
|
|
582
|
+
outcome: result.exitCode === 0 ? "succeeded" : "failed",
|
|
583
|
+
exitCode: result.exitCode,
|
|
584
|
+
...(result.terminationSignal === undefined ? {} : { terminationSignal: result.terminationSignal }),
|
|
585
|
+
...(result.exitCode === 0 ? {} : {
|
|
586
|
+
errorCode: "runtime_failed",
|
|
587
|
+
...(result.errorMessage ? { errorMessage: result.errorMessage } : {}),
|
|
588
|
+
}),
|
|
589
|
+
runtime: result.runtime,
|
|
590
|
+
...(result.model === null ? {} : { model: result.model }),
|
|
591
|
+
resumed: result.resumed,
|
|
592
|
+
...(!spec.reporting.captureFinal || result.finalText === null
|
|
593
|
+
? {}
|
|
594
|
+
: { finalText: result.finalText }),
|
|
595
|
+
...(result.usage === undefined ? {} : { usage: result.usage }),
|
|
596
|
+
startedAt,
|
|
597
|
+
finishedAt,
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
if (timeout !== undefined)
|
|
602
|
+
clearTimeout(timeout);
|
|
603
|
+
if (error instanceof ExecutionCancelledError) {
|
|
604
|
+
await closeLaunchGate();
|
|
605
|
+
await dependencies.cancellation?.waitForStop();
|
|
606
|
+
}
|
|
607
|
+
else if (supervisorState.active !== null && supervisorState.abortOnce !== null) {
|
|
608
|
+
try {
|
|
609
|
+
await supervisorState.abortOnce();
|
|
610
|
+
}
|
|
611
|
+
catch (abortError) {
|
|
612
|
+
const detail = abortError instanceof Error ? abortError.message : String(abortError);
|
|
613
|
+
throw new AggregateError([error, abortError], `Failed to stop the execution supervisor: ${detail}`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
completion = error instanceof ExecutionCancelledError
|
|
617
|
+
? ExecutionCompletedSchema.parse({
|
|
618
|
+
type: "execution:completed",
|
|
619
|
+
protocolVersion: 1,
|
|
620
|
+
executionId: spec.executionId,
|
|
621
|
+
outcome: "cancelled",
|
|
622
|
+
errorCode: "cancelled",
|
|
623
|
+
errorMessage: "Execution cancelled by server request",
|
|
624
|
+
runtime: spec.runtime.name,
|
|
625
|
+
...(spec.runtime.model === undefined ? {} : { model: spec.runtime.model }),
|
|
626
|
+
resumed: false,
|
|
627
|
+
startedAt,
|
|
628
|
+
finishedAt: now().toISOString(),
|
|
629
|
+
})
|
|
630
|
+
: failedCompletion(spec, error, startedAt, now().toISOString());
|
|
631
|
+
}
|
|
632
|
+
completion = boundExecutionFrame(completion, config.executionLimits.maxEventBytes);
|
|
633
|
+
await telemetry.closeAndDrain();
|
|
634
|
+
await dependencies.journal.complete(spec.executionId, completion);
|
|
635
|
+
await dependencies.report(completion);
|
|
636
|
+
return { kind: "completed", frame: completion };
|
|
637
|
+
}
|