@zq-silk/yui 0.8.1 → 0.8.3
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/ARCHITECTURE.md +27 -28
- package/README.md +57 -46
- package/dist/cli/commandCatalog.js +57 -17
- package/dist/cli/interactionPolicy.js +4 -10
- package/dist/cli/invocationRouter.js +2 -1
- package/dist/cli.js +106 -27
- package/dist/commands/taskCommands.js +458 -77
- package/dist/commands/taskCompletionGate.js +152 -0
- package/dist/context/runContextPack.js +19 -4
- package/dist/context/sessionBootstrapManifest.js +1 -1
- package/dist/controller/fileSchedulerStoreAdapter.js +389 -70
- package/dist/controller/resourceInventory.js +9 -5
- package/dist/controller/runtime.js +80 -7
- package/dist/controller/runtimeLaunchCoordinator.js +18 -78
- package/dist/controller/structuredProviderObservation.js +273 -0
- package/dist/executor/agentAdapter.js +40 -0
- package/dist/executor/agentExecutor.js +31 -7
- package/dist/executor/executorRegistry.js +11 -49
- package/dist/executor/fileRoleLaunchPlanner.js +115 -37
- package/dist/lifecycle/canonicalLifecycleEvent.js +5 -2
- package/dist/repository/gitWorkspace.js +7 -4
- package/dist/repository/taskBaseFreshness.js +4 -2
- package/dist/run/agentRun.js +4 -4
- package/dist/runtime/agentHost.js +767 -158
- package/dist/runtime/builtinAgentDrivers.js +1 -5
- package/dist/runtime/codexAppServerRuntime.js +67 -60
- package/dist/runtime/exactControlPlane.js +7 -2
- package/dist/runtime/index.js +6 -2
- package/dist/runtime/launchBroker.js +30 -8
- package/dist/runtime/providerAuthorityFence.js +24 -0
- package/dist/runtime/providerControl.js +63 -0
- package/dist/runtime/providerRecoveryDecision.js +55 -0
- package/dist/runtime/providerRuntimeIdentity.js +269 -19
- package/dist/runtime/runtimeBinding.js +20 -11
- package/dist/runtime/structuredProviderHost.js +476 -0
- package/dist/runtime/tmuxAdapters.js +143 -42
- package/dist/scheduler/activeRoleRunDelivery.js +206 -120
- package/dist/scheduler/leaderWakeupProcessor.js +141 -16
- package/dist/scheduler/wakeReason.js +1 -0
- package/dist/storage/migration/productionRegistry.js +111 -0
- package/dist/storage/sqliteStore.js +2 -0
- package/dist/storage/taskStore.js +3 -1
- package/dist/task/completionReadiness.js +43 -0
- package/dist/task/nextAction.js +6 -4
- package/dist/task/publicationReference.js +1 -0
- package/dist/tmux/tmuxManager.js +1 -1
- package/dist/workItem/workItem.js +12 -0
- package/dist/workspace/workItemChangeSetManager.js +2 -1
- package/i18n/README.zh-CN.md +11 -8
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +8 -3
- package/skills/yui-runtime/SKILL.md +7 -2
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { CodexAppServerRequestError, CodexAppServerRuntime, codexAppServerErrorIsMissing } from "./codexAppServerRuntime.js";
|
|
4
|
+
import { YUI_VERSION } from "../version.js";
|
|
5
|
+
const PROVIDER_MESSAGE_MAX_BYTES = 16 * 1024 * 1024;
|
|
6
|
+
const PROVIDER_ACCEPT_TIMEOUT_MS = 30_000;
|
|
7
|
+
export class ProviderDeliveryUnknownError extends Error {
|
|
8
|
+
attemptId;
|
|
9
|
+
name = "ProviderDeliveryUnknownError";
|
|
10
|
+
constructor(message, attemptId) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.attemptId = attemptId;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class ProviderTurnRejectedError extends Error {
|
|
16
|
+
attemptId;
|
|
17
|
+
name = "ProviderTurnRejectedError";
|
|
18
|
+
constructor(message, attemptId) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.attemptId = attemptId;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class ProviderConversationMissingError extends Error {
|
|
24
|
+
conversationId;
|
|
25
|
+
name = "ProviderConversationMissingError";
|
|
26
|
+
constructor(conversationId, message) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.conversationId = conversationId;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export async function startStructuredProviderSession(payload, input = {}) {
|
|
32
|
+
const control = payload.providerControl;
|
|
33
|
+
if (control === undefined) {
|
|
34
|
+
throw new Error("Managed Agent Host launch requires Provider control metadata.");
|
|
35
|
+
}
|
|
36
|
+
const child = spawn(payload.command, [...payload.args], {
|
|
37
|
+
cwd: payload.cwd,
|
|
38
|
+
env: { ...payload.environment },
|
|
39
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
40
|
+
detached: true
|
|
41
|
+
});
|
|
42
|
+
const processInstanceId = randomUUID();
|
|
43
|
+
const mirror = input.mirrorOutput ?? defaultMirrorOutput;
|
|
44
|
+
child.stderr.setEncoding("utf8");
|
|
45
|
+
child.stderr.on("data", (chunk) => mirror("stderr", chunk));
|
|
46
|
+
const exit = childExit(child, processInstanceId);
|
|
47
|
+
try {
|
|
48
|
+
const session = control.adapterId === "codex"
|
|
49
|
+
? await CodexStructuredProviderSession.open(child, exit, processInstanceId, payload, control, input.onTerminal, mirror)
|
|
50
|
+
: await ClaudeStructuredProviderSession.open(child, exit, processInstanceId, control, input.onTerminal, mirror);
|
|
51
|
+
return Object.freeze({ session });
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
terminateProcessGroup(child, "SIGTERM");
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
class JsonLineChannel {
|
|
59
|
+
child;
|
|
60
|
+
mirror;
|
|
61
|
+
#pending = new Map();
|
|
62
|
+
#listeners = new Set();
|
|
63
|
+
#buffer = "";
|
|
64
|
+
#nextId = 1;
|
|
65
|
+
#closedError;
|
|
66
|
+
constructor(child, mirror) {
|
|
67
|
+
this.child = child;
|
|
68
|
+
this.mirror = mirror;
|
|
69
|
+
child.stdout.setEncoding("utf8");
|
|
70
|
+
child.stdout.on("data", (chunk) => this.#receive(chunk));
|
|
71
|
+
child.once("error", (error) => this.#close(error));
|
|
72
|
+
child.once("close", (code, signal) => this.#close(new Error(`Provider process exited before replying (code=${code ?? "none"}, signal=${signal ?? "none"}).`)));
|
|
73
|
+
}
|
|
74
|
+
onMessage(listener) {
|
|
75
|
+
this.#listeners.add(listener);
|
|
76
|
+
return () => this.#listeners.delete(listener);
|
|
77
|
+
}
|
|
78
|
+
async request(method, params) {
|
|
79
|
+
if (this.#closedError !== undefined)
|
|
80
|
+
throw this.#closedError;
|
|
81
|
+
const id = String(this.#nextId++);
|
|
82
|
+
const response = new Promise((resolvePromise, reject) => {
|
|
83
|
+
const timer = setTimeout(() => {
|
|
84
|
+
this.#pending.delete(id);
|
|
85
|
+
reject(new Error(`Provider request timed out: ${method}.`));
|
|
86
|
+
}, PROVIDER_ACCEPT_TIMEOUT_MS);
|
|
87
|
+
this.#pending.set(id, { resolve: resolvePromise, reject, timer });
|
|
88
|
+
});
|
|
89
|
+
try {
|
|
90
|
+
await this.send({ id, method, params });
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
const pending = this.#pending.get(id);
|
|
94
|
+
if (pending !== undefined) {
|
|
95
|
+
clearTimeout(pending.timer);
|
|
96
|
+
this.#pending.delete(id);
|
|
97
|
+
pending.reject(error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return await response;
|
|
101
|
+
}
|
|
102
|
+
async notify(method, params) {
|
|
103
|
+
await this.send({ method, ...(params === undefined ? {} : { params }) });
|
|
104
|
+
}
|
|
105
|
+
async send(message) {
|
|
106
|
+
if (this.#closedError !== undefined)
|
|
107
|
+
throw this.#closedError;
|
|
108
|
+
const line = `${JSON.stringify(message)}\n`;
|
|
109
|
+
if (Buffer.byteLength(line, "utf8") > PROVIDER_MESSAGE_MAX_BYTES) {
|
|
110
|
+
throw new Error("Provider request exceeds its message bound.");
|
|
111
|
+
}
|
|
112
|
+
await new Promise((resolvePromise, reject) => {
|
|
113
|
+
this.child.stdin.write(line, "utf8", (error) => {
|
|
114
|
+
if (error === null || error === undefined)
|
|
115
|
+
resolvePromise();
|
|
116
|
+
else
|
|
117
|
+
reject(error);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
#receive(chunk) {
|
|
122
|
+
this.mirror("stdout", chunk);
|
|
123
|
+
this.#buffer += chunk;
|
|
124
|
+
if (Buffer.byteLength(this.#buffer, "utf8") > PROVIDER_MESSAGE_MAX_BYTES) {
|
|
125
|
+
this.#close(new Error("Provider response line exceeds its message bound."));
|
|
126
|
+
terminateProcessGroup(this.child, "SIGTERM");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
for (;;) {
|
|
130
|
+
const newline = this.#buffer.indexOf("\n");
|
|
131
|
+
if (newline < 0)
|
|
132
|
+
return;
|
|
133
|
+
const line = this.#buffer.slice(0, newline).trim();
|
|
134
|
+
this.#buffer = this.#buffer.slice(newline + 1);
|
|
135
|
+
if (line.length === 0)
|
|
136
|
+
continue;
|
|
137
|
+
let message;
|
|
138
|
+
try {
|
|
139
|
+
const parsed = JSON.parse(line);
|
|
140
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
141
|
+
continue;
|
|
142
|
+
message = parsed;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const id = requestId(message.id);
|
|
148
|
+
const pending = id === undefined ? undefined : this.#pending.get(id);
|
|
149
|
+
if (pending !== undefined) {
|
|
150
|
+
clearTimeout(pending.timer);
|
|
151
|
+
this.#pending.delete(id);
|
|
152
|
+
const error = object(message.error);
|
|
153
|
+
if (error !== null) {
|
|
154
|
+
pending.reject(new CodexAppServerRequestError(typeof error.code === "number" || typeof error.code === "string"
|
|
155
|
+
? error.code
|
|
156
|
+
: "UNKNOWN", typeof error.message === "string" ? error.message : "Provider request failed.", error.data));
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
pending.resolve(object(message.result) ?? {});
|
|
160
|
+
}
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
for (const listener of this.#listeners)
|
|
164
|
+
listener(message);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
#close(error) {
|
|
168
|
+
if (this.#closedError !== undefined)
|
|
169
|
+
return;
|
|
170
|
+
this.#closedError = error;
|
|
171
|
+
for (const pending of this.#pending.values()) {
|
|
172
|
+
clearTimeout(pending.timer);
|
|
173
|
+
pending.reject(error);
|
|
174
|
+
}
|
|
175
|
+
this.#pending.clear();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
class CodexStructuredProviderSession {
|
|
179
|
+
child;
|
|
180
|
+
exit;
|
|
181
|
+
processInstanceId;
|
|
182
|
+
conversationId;
|
|
183
|
+
runtime;
|
|
184
|
+
adapterId = "codex";
|
|
185
|
+
#activeTurnId;
|
|
186
|
+
constructor(child, exit, processInstanceId, conversationId, runtime) {
|
|
187
|
+
this.child = child;
|
|
188
|
+
this.exit = exit;
|
|
189
|
+
this.processInstanceId = processInstanceId;
|
|
190
|
+
this.conversationId = conversationId;
|
|
191
|
+
this.runtime = runtime;
|
|
192
|
+
}
|
|
193
|
+
static async open(child, exit, processInstanceId, payload, control, onTerminal, mirror) {
|
|
194
|
+
const channel = new JsonLineChannel(child, mirror);
|
|
195
|
+
await channel.request("initialize", {
|
|
196
|
+
clientInfo: { name: "yui", title: "Yui", version: YUI_VERSION },
|
|
197
|
+
capabilities: {
|
|
198
|
+
experimentalApi: true,
|
|
199
|
+
requestAttestation: false
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
await channel.notify("initialized");
|
|
203
|
+
const runtime = new CodexAppServerRuntime(channel);
|
|
204
|
+
let conversationId;
|
|
205
|
+
let resumedActiveTurnId;
|
|
206
|
+
if (control.mode === "new") {
|
|
207
|
+
conversationId = (await runtime.openConversation({ cwd: payload.cwd })).conversationId;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
try {
|
|
211
|
+
const resumed = await runtime.resumeConversation(control.nativeSessionId);
|
|
212
|
+
conversationId = resumed.threadId;
|
|
213
|
+
resumedActiveTurnId = resumed.activeTurnId;
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
if (codexAppServerErrorIsMissing(error)) {
|
|
217
|
+
throw new ProviderConversationMissingError(control.nativeSessionId, `Codex Conversation is exactly missing: ${control.nativeSessionId}.`);
|
|
218
|
+
}
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const session = new CodexStructuredProviderSession(child, exit, processInstanceId, conversationId, runtime);
|
|
223
|
+
session.#activeTurnId = resumedActiveTurnId;
|
|
224
|
+
channel.onMessage((message) => {
|
|
225
|
+
const method = typeof message.method === "string" ? message.method : "";
|
|
226
|
+
const params = object(message.params) ?? {};
|
|
227
|
+
if (optionalId(params.threadId) !== conversationId)
|
|
228
|
+
return;
|
|
229
|
+
if (method === "turn/started") {
|
|
230
|
+
const turnId = nestedId(params, "turn") ?? optionalId(params.turnId);
|
|
231
|
+
if (turnId !== undefined)
|
|
232
|
+
session.#activeTurnId = turnId;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (method !== "turn/completed")
|
|
236
|
+
return;
|
|
237
|
+
const turn = object(params.turn) ?? {};
|
|
238
|
+
const nativeTurnId = optionalId(turn.id) ?? optionalId(params.turnId);
|
|
239
|
+
if (nativeTurnId === undefined)
|
|
240
|
+
return;
|
|
241
|
+
session.#activeTurnId = undefined;
|
|
242
|
+
const status = turn.status === "failed"
|
|
243
|
+
? "failed"
|
|
244
|
+
: turn.status === "interrupted" ? "cancelled" : "completed";
|
|
245
|
+
onTerminal?.({
|
|
246
|
+
conversationId,
|
|
247
|
+
nativeSessionId: conversationId,
|
|
248
|
+
nativeTurnId,
|
|
249
|
+
status,
|
|
250
|
+
observedAt: new Date().toISOString(),
|
|
251
|
+
...(status !== "failed" ? {} : { error: providerErrorText(turn.error) })
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
return session;
|
|
255
|
+
}
|
|
256
|
+
get nativeSessionId() {
|
|
257
|
+
return this.conversationId;
|
|
258
|
+
}
|
|
259
|
+
get activeTurnId() {
|
|
260
|
+
return this.#activeTurnId;
|
|
261
|
+
}
|
|
262
|
+
async submitTurn(turn) {
|
|
263
|
+
if (this.#activeTurnId !== undefined) {
|
|
264
|
+
throw new ProviderTurnRejectedError(`Provider Conversation already has active Turn ${this.#activeTurnId}.`, turn.attemptId);
|
|
265
|
+
}
|
|
266
|
+
const acceptance = await this.runtime.submitTurn({
|
|
267
|
+
conversationId: this.conversationId,
|
|
268
|
+
attemptId: turn.attemptId,
|
|
269
|
+
text: turn.boundedText,
|
|
270
|
+
expectedNoActiveTurn: true
|
|
271
|
+
});
|
|
272
|
+
if (acceptance.status === "unknown") {
|
|
273
|
+
throw new ProviderDeliveryUnknownError(acceptance.reason, turn.attemptId);
|
|
274
|
+
}
|
|
275
|
+
if (acceptance.status === "not-accepted") {
|
|
276
|
+
throw new ProviderTurnRejectedError(acceptance.reason, turn.attemptId);
|
|
277
|
+
}
|
|
278
|
+
this.#activeTurnId = acceptance.turnId;
|
|
279
|
+
return Object.freeze({
|
|
280
|
+
attemptId: turn.attemptId,
|
|
281
|
+
conversationId: this.conversationId,
|
|
282
|
+
nativeSessionId: this.conversationId,
|
|
283
|
+
nativeTurnId: acceptance.turnId,
|
|
284
|
+
acceptedAt: new Date().toISOString()
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
waitForExit() {
|
|
288
|
+
return this.exit;
|
|
289
|
+
}
|
|
290
|
+
terminate(signal) {
|
|
291
|
+
terminateProcessGroup(this.child, signal);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
class ClaudeStructuredProviderSession {
|
|
295
|
+
child;
|
|
296
|
+
exit;
|
|
297
|
+
processInstanceId;
|
|
298
|
+
conversationId;
|
|
299
|
+
channel;
|
|
300
|
+
adapterId = "claude";
|
|
301
|
+
#activeTurnId;
|
|
302
|
+
#waiter;
|
|
303
|
+
constructor(child, exit, processInstanceId, conversationId, channel) {
|
|
304
|
+
this.child = child;
|
|
305
|
+
this.exit = exit;
|
|
306
|
+
this.processInstanceId = processInstanceId;
|
|
307
|
+
this.conversationId = conversationId;
|
|
308
|
+
this.channel = channel;
|
|
309
|
+
}
|
|
310
|
+
static async open(child, exit, processInstanceId, control, onTerminal, mirror) {
|
|
311
|
+
const channel = new JsonLineChannel(child, mirror);
|
|
312
|
+
const nativeSessionId = control.nativeSessionId;
|
|
313
|
+
if (nativeSessionId === undefined) {
|
|
314
|
+
throw new Error("Managed Claude launch requires a preallocated native Session id.");
|
|
315
|
+
}
|
|
316
|
+
const session = new ClaudeStructuredProviderSession(child, exit, processInstanceId, nativeSessionId, channel);
|
|
317
|
+
channel.onMessage((message) => session.#receive(message, onTerminal));
|
|
318
|
+
exit.then(() => {
|
|
319
|
+
const waiter = session.#waiter;
|
|
320
|
+
if (waiter === undefined)
|
|
321
|
+
return;
|
|
322
|
+
clearTimeout(waiter.timer);
|
|
323
|
+
session.#waiter = undefined;
|
|
324
|
+
waiter.reject(new ProviderDeliveryUnknownError("Claude process exited before replaying the submitted user message.", waiter.attemptId));
|
|
325
|
+
}).catch(() => { });
|
|
326
|
+
return session;
|
|
327
|
+
}
|
|
328
|
+
get nativeSessionId() {
|
|
329
|
+
return this.conversationId;
|
|
330
|
+
}
|
|
331
|
+
get activeTurnId() {
|
|
332
|
+
return this.#activeTurnId;
|
|
333
|
+
}
|
|
334
|
+
async submitTurn(turn) {
|
|
335
|
+
if (this.#activeTurnId !== undefined || this.#waiter !== undefined) {
|
|
336
|
+
throw new ProviderTurnRejectedError("Provider Conversation already has an unsettled Turn.", turn.attemptId);
|
|
337
|
+
}
|
|
338
|
+
let deliveryTimer;
|
|
339
|
+
const receipt = new Promise((resolvePromise, reject) => {
|
|
340
|
+
deliveryTimer = setTimeout(() => {
|
|
341
|
+
if (this.#waiter?.attemptId === turn.attemptId)
|
|
342
|
+
this.#waiter = undefined;
|
|
343
|
+
reject(new ProviderDeliveryUnknownError("Claude did not replay the submitted user message before the acknowledgement deadline.", turn.attemptId));
|
|
344
|
+
}, PROVIDER_ACCEPT_TIMEOUT_MS);
|
|
345
|
+
this.#waiter = {
|
|
346
|
+
attemptId: turn.attemptId,
|
|
347
|
+
text: turn.boundedText,
|
|
348
|
+
resolve: resolvePromise,
|
|
349
|
+
reject,
|
|
350
|
+
timer: deliveryTimer
|
|
351
|
+
};
|
|
352
|
+
});
|
|
353
|
+
try {
|
|
354
|
+
await this.channel.send({
|
|
355
|
+
type: "user",
|
|
356
|
+
message: {
|
|
357
|
+
role: "user",
|
|
358
|
+
content: [{ type: "text", text: turn.boundedText }]
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
if (deliveryTimer !== undefined)
|
|
364
|
+
clearTimeout(deliveryTimer);
|
|
365
|
+
this.#waiter = undefined;
|
|
366
|
+
throw new ProviderDeliveryUnknownError(`Claude input write did not produce an exact replay acknowledgement: ${error instanceof Error ? error.message : String(error)}`, turn.attemptId);
|
|
367
|
+
}
|
|
368
|
+
return await receipt;
|
|
369
|
+
}
|
|
370
|
+
waitForExit() {
|
|
371
|
+
return this.exit;
|
|
372
|
+
}
|
|
373
|
+
terminate(signal) {
|
|
374
|
+
terminateProcessGroup(this.child, signal);
|
|
375
|
+
}
|
|
376
|
+
#receive(message, onTerminal) {
|
|
377
|
+
if (message.type === "user") {
|
|
378
|
+
const waiter = this.#waiter;
|
|
379
|
+
if (waiter === undefined || claudeUserText(message) !== waiter.text)
|
|
380
|
+
return;
|
|
381
|
+
const observedSessionId = optionalId(message.session_id);
|
|
382
|
+
if (observedSessionId !== this.conversationId) {
|
|
383
|
+
clearTimeout(waiter.timer);
|
|
384
|
+
this.#waiter = undefined;
|
|
385
|
+
waiter.reject(new ProviderDeliveryUnknownError("Claude replayed input for a different native Session.", waiter.attemptId));
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const nativeTurnId = `claude-input:${waiter.attemptId}`;
|
|
389
|
+
this.#activeTurnId = nativeTurnId;
|
|
390
|
+
clearTimeout(waiter.timer);
|
|
391
|
+
this.#waiter = undefined;
|
|
392
|
+
waiter.resolve(Object.freeze({
|
|
393
|
+
attemptId: waiter.attemptId,
|
|
394
|
+
conversationId: this.conversationId,
|
|
395
|
+
nativeSessionId: this.conversationId,
|
|
396
|
+
nativeTurnId,
|
|
397
|
+
acceptedAt: new Date().toISOString()
|
|
398
|
+
}));
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (message.type !== "result" || this.#activeTurnId === undefined
|
|
402
|
+
|| optionalId(message.session_id) !== this.conversationId)
|
|
403
|
+
return;
|
|
404
|
+
const nativeTurnId = this.#activeTurnId;
|
|
405
|
+
this.#activeTurnId = undefined;
|
|
406
|
+
const failed = message.is_error === true || message.subtype === "error_during_execution";
|
|
407
|
+
onTerminal?.({
|
|
408
|
+
conversationId: this.conversationId,
|
|
409
|
+
nativeSessionId: this.conversationId,
|
|
410
|
+
nativeTurnId,
|
|
411
|
+
status: failed ? "failed" : "completed",
|
|
412
|
+
observedAt: new Date().toISOString(),
|
|
413
|
+
...(typeof message.result === "string" && message.result.length > 0
|
|
414
|
+
? failed ? { error: message.result } : { summary: message.result }
|
|
415
|
+
: {})
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function childExit(child, processInstanceId) {
|
|
420
|
+
return new Promise((resolvePromise, reject) => {
|
|
421
|
+
child.once("error", reject);
|
|
422
|
+
child.once("close", (code, signal) => resolvePromise(Object.freeze({
|
|
423
|
+
code,
|
|
424
|
+
signal,
|
|
425
|
+
processInstanceId
|
|
426
|
+
})));
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
function terminateProcessGroup(child, signal) {
|
|
430
|
+
if (child.pid === undefined)
|
|
431
|
+
return;
|
|
432
|
+
// Managed Providers are spawned detached and therefore own a process group.
|
|
433
|
+
// Kill that exact group so a CLI helper cannot outlive the Agent Host. The
|
|
434
|
+
// direct-child fallback covers embedded runtimes that cannot create setsid.
|
|
435
|
+
try {
|
|
436
|
+
process.kill(-child.pid, signal);
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
if (error.code !== "ESRCH")
|
|
440
|
+
throw error;
|
|
441
|
+
child.kill(signal);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function defaultMirrorOutput(stream, text) {
|
|
445
|
+
(stream === "stdout" ? process.stdout : process.stderr).write(text);
|
|
446
|
+
}
|
|
447
|
+
function requestId(value) {
|
|
448
|
+
return typeof value === "string" || typeof value === "number" ? String(value) : undefined;
|
|
449
|
+
}
|
|
450
|
+
function object(value) {
|
|
451
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
452
|
+
? value
|
|
453
|
+
: null;
|
|
454
|
+
}
|
|
455
|
+
function optionalId(value) {
|
|
456
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
457
|
+
}
|
|
458
|
+
function nestedId(value, key) {
|
|
459
|
+
return optionalId(object(value[key])?.id);
|
|
460
|
+
}
|
|
461
|
+
function providerErrorText(value) {
|
|
462
|
+
const error = object(value);
|
|
463
|
+
if (typeof error?.message === "string" && error.message.length > 0)
|
|
464
|
+
return error.message;
|
|
465
|
+
return "Provider Turn failed.";
|
|
466
|
+
}
|
|
467
|
+
function claudeUserText(message) {
|
|
468
|
+
const content = object(message.message)?.content;
|
|
469
|
+
if (!Array.isArray(content))
|
|
470
|
+
return undefined;
|
|
471
|
+
const text = content.flatMap((entry) => {
|
|
472
|
+
const block = object(entry);
|
|
473
|
+
return block?.type === "text" && typeof block.text === "string" ? [block.text] : [];
|
|
474
|
+
});
|
|
475
|
+
return text.length === 1 ? text[0] : text.join("");
|
|
476
|
+
}
|