@tea-agent/loop-agent 0.26.2 → 0.26.4
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/CHANGELOG.md +43 -0
- package/dist/executors/dag-pi-executor.js +182 -10
- package/dist/executors/pi-executor.js +233 -147
- package/dist/executors/pi-sdk-executor.js +140 -81
- package/dist/executors/pi-writer-tool-policy.js +266 -0
- package/dist/executors/shell-executor.js +355 -52
- package/dist/executors/shell-write-guard.js +145 -12
- package/dist/governance/document-index-closure.js +164 -0
- package/dist/worker/observe/node-input.js +8 -11
- package/dist/workflows/dag/convergence/controller.js +100 -3
- package/dist/workflows/dag/frontend-repair.js +29 -29
- package/dist/workflows/dag/frontend-verification-trace.js +12 -2
- package/dist/workflows/dag/governance-profile.js +1 -1
- package/dist/workflows/dag/init-hybrid.js +70 -16
- package/dist/workflows/dag/repair-artifact.js +100 -4
- package/dist/workflows/dag/rerun-plan.js +14 -7
- package/dist/workflows/dag/types.js +7 -1
- package/dist/workflows/dag/workspace-checkpoint.js +42 -0
- package/docs/templates/init-managed-agents.md +3 -3
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/loop-agent/references/orchestrator-and-interventions.md +13 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { appendFile, mkdir } from
|
|
2
|
-
import path from
|
|
3
|
-
import { BoundedTextPreview, classifyPiFailure, createPiJsonlStreamCollector, DEFAULT_ABORT_GRACE_MS, DEFAULT_STALL_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, extractAssistantTextFromPiJson, } from
|
|
4
|
-
import { serializeSessionEvent } from
|
|
1
|
+
import { appendFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { BoundedTextPreview, classifyPiFailure, createPiJsonlStreamCollector, DEFAULT_ABORT_GRACE_MS, DEFAULT_STALL_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, extractAssistantTextFromPiJson, } from "./pi-executor.js";
|
|
4
|
+
import { serializeSessionEvent } from "./pi-event-serializer.js";
|
|
5
5
|
let sdkSessionFactoryOverride;
|
|
6
6
|
let sdkImportOverrideForTests;
|
|
7
7
|
let sdkModuleOverrideForTests;
|
|
@@ -54,7 +54,7 @@ export function setPiSdkModuleOverrideForTests(fn) {
|
|
|
54
54
|
/** Check whether the Pi SDK optional dependency satisfies the 0.80.10 runtime contract. */
|
|
55
55
|
export async function checkPiSdkAvailability(_repoRoot) {
|
|
56
56
|
if (sdkSessionFactoryOverride) {
|
|
57
|
-
return { ok: true, detail:
|
|
57
|
+
return { ok: true, detail: "pi SDK session factory override active" };
|
|
58
58
|
}
|
|
59
59
|
try {
|
|
60
60
|
const imported = sdkImportOverrideForTests
|
|
@@ -62,15 +62,15 @@ export async function checkPiSdkAvailability(_repoRoot) {
|
|
|
62
62
|
: await loadPiSdkModule();
|
|
63
63
|
const sdk = imported;
|
|
64
64
|
const ModelRuntime = sdk.ModelRuntime;
|
|
65
|
-
if (typeof sdk.createAgentSession !==
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
if (typeof sdk.createAgentSession !== "function" ||
|
|
66
|
+
typeof sdk.getAgentDir !== "function" ||
|
|
67
|
+
typeof ModelRuntime?.create !== "function") {
|
|
68
68
|
return {
|
|
69
69
|
ok: false,
|
|
70
|
-
detail:
|
|
70
|
+
detail: "pi SDK incompatible: requires createAgentSession, getAgentDir, and ModelRuntime.create (0.80.10 contract)",
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
|
-
return { ok: true, detail:
|
|
73
|
+
return { ok: true, detail: "pi SDK 0.80.10 contract available" };
|
|
74
74
|
}
|
|
75
75
|
catch (error) {
|
|
76
76
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -82,7 +82,7 @@ async function resolveModel(modelRuntime, provider, modelId) {
|
|
|
82
82
|
if (fromRuntime)
|
|
83
83
|
return fromRuntime;
|
|
84
84
|
try {
|
|
85
|
-
const piAi = await import(
|
|
85
|
+
const piAi = (await import("@earendil-works/pi-ai/compat"));
|
|
86
86
|
return piAi.getModel?.(provider, modelId);
|
|
87
87
|
}
|
|
88
88
|
catch {
|
|
@@ -93,7 +93,7 @@ async function loadPiSdkModule() {
|
|
|
93
93
|
if (sdkModuleOverrideForTests) {
|
|
94
94
|
return sdkModuleOverrideForTests();
|
|
95
95
|
}
|
|
96
|
-
return await import(
|
|
96
|
+
return (await import("@earendil-works/pi-coding-agent"));
|
|
97
97
|
}
|
|
98
98
|
async function getOrCreateSharedResources(state) {
|
|
99
99
|
if (state.resources)
|
|
@@ -101,13 +101,13 @@ async function getOrCreateSharedResources(state) {
|
|
|
101
101
|
const sdk = await loadPiSdkModule();
|
|
102
102
|
const getAgentDir = sdk.getAgentDir;
|
|
103
103
|
const ModelRuntime = sdk.ModelRuntime;
|
|
104
|
-
if (typeof ModelRuntime?.create !==
|
|
105
|
-
throw new Error(
|
|
104
|
+
if (typeof ModelRuntime?.create !== "function") {
|
|
105
|
+
throw new Error("incompatible pi SDK: ModelRuntime.create is unavailable");
|
|
106
106
|
}
|
|
107
107
|
const agentDir = getAgentDir();
|
|
108
108
|
const modelRuntime = await ModelRuntime.create({
|
|
109
|
-
authPath: path.join(agentDir,
|
|
110
|
-
modelsPath: path.join(agentDir,
|
|
109
|
+
authPath: path.join(agentDir, "auth.json"),
|
|
110
|
+
modelsPath: path.join(agentDir, "models.json"),
|
|
111
111
|
});
|
|
112
112
|
state.resources = { agentDir, modelRuntime };
|
|
113
113
|
return state.resources;
|
|
@@ -119,13 +119,14 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
119
119
|
const getAgentDir = sdk.getAgentDir;
|
|
120
120
|
const ModelRuntime = sdk.ModelRuntime;
|
|
121
121
|
const agentDir = shared?.agentDir ?? getAgentDir();
|
|
122
|
-
if (!shared && typeof ModelRuntime?.create !==
|
|
123
|
-
throw new Error(
|
|
122
|
+
if (!shared && typeof ModelRuntime?.create !== "function") {
|
|
123
|
+
throw new Error("incompatible pi SDK: ModelRuntime.create is unavailable");
|
|
124
124
|
}
|
|
125
|
-
const modelRuntime = shared?.modelRuntime ??
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
const modelRuntime = shared?.modelRuntime ??
|
|
126
|
+
(await ModelRuntime.create({
|
|
127
|
+
authPath: path.join(agentDir, "auth.json"),
|
|
128
|
+
modelsPath: path.join(agentDir, "models.json"),
|
|
129
|
+
}));
|
|
129
130
|
const model = input.provider && input.model
|
|
130
131
|
? await resolveModel(modelRuntime, input.provider, input.model)
|
|
131
132
|
: undefined;
|
|
@@ -134,9 +135,18 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
134
135
|
agentDir,
|
|
135
136
|
noContextFiles: true,
|
|
136
137
|
noSkills: true,
|
|
137
|
-
|
|
138
|
+
noExtensions: true,
|
|
139
|
+
appendSystemPromptOverride: (base) => [
|
|
140
|
+
...base,
|
|
141
|
+
input.appendSystemPrompt,
|
|
142
|
+
],
|
|
138
143
|
});
|
|
139
144
|
await loader.reload();
|
|
145
|
+
if (input.requireWriterCustomTools) {
|
|
146
|
+
if (!Array.isArray(input.customTools) || input.customTools.length === 0) {
|
|
147
|
+
throw new Error("pi writer tool policy missing customTools; refusing to create uncontrolled writer session");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
140
150
|
const created = await createAgentSession({
|
|
141
151
|
cwd: input.cwd,
|
|
142
152
|
sessionManager: SessionManager.inMemory(input.cwd),
|
|
@@ -145,6 +155,9 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
145
155
|
modelRuntime,
|
|
146
156
|
...(model ? { model } : {}),
|
|
147
157
|
...(input.thinking ? { thinkingLevel: input.thinking } : {}),
|
|
158
|
+
...(Array.isArray(input.customTools) && input.customTools.length > 0
|
|
159
|
+
? { customTools: input.customTools }
|
|
160
|
+
: {}),
|
|
148
161
|
});
|
|
149
162
|
return created.session;
|
|
150
163
|
}
|
|
@@ -154,35 +167,41 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
154
167
|
* Skips thinking_delta and message_update token floods.
|
|
155
168
|
*/
|
|
156
169
|
export function shouldPersistSessionEvent(event) {
|
|
157
|
-
const type = typeof event.type ===
|
|
158
|
-
if (type ===
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
170
|
+
const type = typeof event.type === "string" ? event.type : "";
|
|
171
|
+
if (type === "tool_start" ||
|
|
172
|
+
type === "tool_end" ||
|
|
173
|
+
type === "tool_execution_start" ||
|
|
174
|
+
type === "tool_execution_end" ||
|
|
175
|
+
type === "turn_end" ||
|
|
176
|
+
type === "agent_end" ||
|
|
177
|
+
type === "assistant_message" ||
|
|
178
|
+
type === "message_end") {
|
|
162
179
|
return true;
|
|
163
180
|
}
|
|
164
|
-
if (type ===
|
|
181
|
+
if (type === "thinking_delta" || type === "message_update") {
|
|
165
182
|
return false;
|
|
166
183
|
}
|
|
167
|
-
if (type.includes(
|
|
184
|
+
if (type.includes("error") || event.isError === true) {
|
|
168
185
|
return true;
|
|
169
186
|
}
|
|
170
187
|
return true;
|
|
171
188
|
}
|
|
172
189
|
function classifySdkActivityKind(event) {
|
|
173
|
-
if (!event || typeof event !==
|
|
174
|
-
return
|
|
175
|
-
const type = typeof event.type ===
|
|
190
|
+
if (!event || typeof event !== "object")
|
|
191
|
+
return "provider";
|
|
192
|
+
const type = typeof event.type === "string"
|
|
176
193
|
? String(event.type)
|
|
177
|
-
:
|
|
178
|
-
if (type ===
|
|
179
|
-
|
|
180
|
-
|
|
194
|
+
: "";
|
|
195
|
+
if (type === "tool_start" ||
|
|
196
|
+
type === "tool_end" ||
|
|
197
|
+
type === "tool_execution_start" ||
|
|
198
|
+
type === "tool_execution_end") {
|
|
199
|
+
return "tool";
|
|
181
200
|
}
|
|
182
|
-
if (type ===
|
|
201
|
+
if (type === "thinking_delta" || type === "message_update") {
|
|
183
202
|
return null;
|
|
184
203
|
}
|
|
185
|
-
return
|
|
204
|
+
return "provider";
|
|
186
205
|
}
|
|
187
206
|
function createSessionEventAppender(filePath, onSessionEvent) {
|
|
188
207
|
let chain = Promise.resolve();
|
|
@@ -195,7 +214,7 @@ function createSessionEventAppender(filePath, onSessionEvent) {
|
|
|
195
214
|
await mkdir(path.dirname(filePath), { recursive: true });
|
|
196
215
|
dirEnsured = true;
|
|
197
216
|
}
|
|
198
|
-
await appendFile(filePath, `${line}\n`,
|
|
217
|
+
await appendFile(filePath, `${line}\n`, "utf-8");
|
|
199
218
|
// Only after successful persistence: surface the persisted session event.
|
|
200
219
|
// Transport activity is reported synchronously by the subscription so
|
|
201
220
|
// filtered deltas and slow disk writes cannot trip the stall watchdog.
|
|
@@ -216,17 +235,19 @@ async function resolveSdkSessionFactory(reuseScope) {
|
|
|
216
235
|
return sdkSessionFactoryOverride;
|
|
217
236
|
const sdk = await loadPiSdkModule();
|
|
218
237
|
return async (input) => {
|
|
219
|
-
const shared = reuseScope
|
|
238
|
+
const shared = reuseScope
|
|
239
|
+
? await reuseScope.getOrCreateResources()
|
|
240
|
+
: undefined;
|
|
220
241
|
return createSdkSession(sdk, input, shared);
|
|
221
242
|
};
|
|
222
243
|
}
|
|
223
244
|
function isRecord(value) {
|
|
224
|
-
return typeof value ===
|
|
245
|
+
return typeof value === "object" && value !== null;
|
|
225
246
|
}
|
|
226
247
|
function readUsageNumber(record, keys) {
|
|
227
248
|
for (const key of keys) {
|
|
228
249
|
const value = record[key];
|
|
229
|
-
if (typeof value ===
|
|
250
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
|
|
230
251
|
return Math.trunc(value);
|
|
231
252
|
}
|
|
232
253
|
}
|
|
@@ -239,9 +260,19 @@ function extractSdkUsageSample(event) {
|
|
|
239
260
|
for (const candidate of usageCandidates) {
|
|
240
261
|
if (!isRecord(candidate))
|
|
241
262
|
continue;
|
|
242
|
-
const input = readUsageNumber(candidate, [
|
|
243
|
-
|
|
244
|
-
|
|
263
|
+
const input = readUsageNumber(candidate, [
|
|
264
|
+
"input_tokens",
|
|
265
|
+
"inputTokens",
|
|
266
|
+
"prompt_tokens",
|
|
267
|
+
"promptTokens",
|
|
268
|
+
]);
|
|
269
|
+
const output = readUsageNumber(candidate, [
|
|
270
|
+
"output_tokens",
|
|
271
|
+
"outputTokens",
|
|
272
|
+
"completion_tokens",
|
|
273
|
+
"completionTokens",
|
|
274
|
+
]);
|
|
275
|
+
const total = readUsageNumber(candidate, ["total_tokens", "totalTokens"]);
|
|
245
276
|
if (input !== undefined && output !== undefined) {
|
|
246
277
|
tokens = input + output;
|
|
247
278
|
break;
|
|
@@ -259,7 +290,7 @@ function extractSdkUsageSample(event) {
|
|
|
259
290
|
message?.responseId,
|
|
260
291
|
message?.id,
|
|
261
292
|
];
|
|
262
|
-
const responseKey = responseKeyCandidates.find((value) => typeof value ===
|
|
293
|
+
const responseKey = responseKeyCandidates.find((value) => typeof value === "string" && value.length > 0);
|
|
263
294
|
return responseKey ? { responseKey, tokens } : { tokens };
|
|
264
295
|
}
|
|
265
296
|
function aggregateSdkTokenUsage(samples) {
|
|
@@ -275,7 +306,8 @@ function aggregateSdkTokenUsage(samples) {
|
|
|
275
306
|
anonymousMaximum = Math.max(anonymousMaximum, sample.tokens);
|
|
276
307
|
}
|
|
277
308
|
}
|
|
278
|
-
return anonymousMaximum +
|
|
309
|
+
return (anonymousMaximum +
|
|
310
|
+
Array.from(identified.values()).reduce((sum, tokens) => sum + tokens, 0));
|
|
279
311
|
}
|
|
280
312
|
/**
|
|
281
313
|
* Execute a single Pi step via the SDK.
|
|
@@ -287,24 +319,28 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
287
319
|
const modelConfig = options.modelConfig;
|
|
288
320
|
const modelDisplay = modelConfig.provider && modelConfig.model
|
|
289
321
|
? `${modelConfig.provider}/${modelConfig.model}`
|
|
290
|
-
: modelConfig.model ??
|
|
322
|
+
: (modelConfig.model ?? "default");
|
|
291
323
|
const timeoutMs = options.timeoutMs ?? modelConfig.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
292
324
|
const stallTimeoutMs = options.stallTimeoutMs ?? DEFAULT_STALL_TIMEOUT_MS;
|
|
293
325
|
const abortGraceMs = options.abortGraceMs ?? DEFAULT_ABORT_GRACE_MS;
|
|
294
326
|
const piSdkArgs = [
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
327
|
+
"--provider",
|
|
328
|
+
modelConfig.provider ?? "(default)",
|
|
329
|
+
"--model",
|
|
330
|
+
modelConfig.model ?? "(default)",
|
|
331
|
+
...(modelConfig.thinking ? ["--thinking", modelConfig.thinking] : []),
|
|
332
|
+
"--tools",
|
|
333
|
+
options.toolNames.join(","),
|
|
334
|
+
"--append-system-prompt",
|
|
335
|
+
"<in-memory-system-prompt>",
|
|
300
336
|
...options.attachedFiles.map((file) => `@${file}`),
|
|
301
337
|
options.userMessage,
|
|
302
338
|
];
|
|
303
339
|
const startedAt = Date.now();
|
|
304
340
|
let timedOut = false;
|
|
305
341
|
let terminationConfirmed = true;
|
|
306
|
-
let stderr =
|
|
307
|
-
const stdoutPreview = new BoundedTextPreview(
|
|
342
|
+
let stderr = "";
|
|
343
|
+
const stdoutPreview = new BoundedTextPreview("stdout");
|
|
308
344
|
const stdoutCollector = createPiJsonlStreamCollector();
|
|
309
345
|
const usageSamples = [];
|
|
310
346
|
let session;
|
|
@@ -333,17 +369,25 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
333
369
|
clearTimeout(graceHandle);
|
|
334
370
|
if (outcome.ok)
|
|
335
371
|
return true;
|
|
336
|
-
if (
|
|
372
|
+
if ("timedOut" in outcome) {
|
|
337
373
|
appendStderr(`pi SDK ${label} was not confirmed within ${abortGraceMs}ms`);
|
|
338
374
|
}
|
|
339
375
|
else {
|
|
340
|
-
const message = outcome.error instanceof Error
|
|
376
|
+
const message = outcome.error instanceof Error
|
|
377
|
+
? outcome.error.message
|
|
378
|
+
: String(outcome.error);
|
|
341
379
|
appendStderr(`pi SDK ${label} failed: ${message}`);
|
|
342
380
|
}
|
|
343
381
|
return false;
|
|
344
382
|
};
|
|
345
383
|
try {
|
|
346
384
|
const createSession = await resolveSdkSessionFactory(options.reuseScope);
|
|
385
|
+
const writerCustomTools = options.writerToolPolicy?.customTools;
|
|
386
|
+
const requireWriterCustomTools = options.writerToolPolicy?.requireSdk === true;
|
|
387
|
+
if (requireWriterCustomTools &&
|
|
388
|
+
(!Array.isArray(writerCustomTools) || writerCustomTools.length === 0)) {
|
|
389
|
+
throw new Error("pi writer tool policy missing customTools; refusing to create uncontrolled writer session");
|
|
390
|
+
}
|
|
347
391
|
session = await createSession({
|
|
348
392
|
cwd: options.repoRoot,
|
|
349
393
|
toolNames: options.toolNames,
|
|
@@ -351,6 +395,10 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
351
395
|
provider: modelConfig.provider,
|
|
352
396
|
model: modelConfig.model,
|
|
353
397
|
thinking: modelConfig.thinking,
|
|
398
|
+
...(Array.isArray(writerCustomTools) && writerCustomTools.length > 0
|
|
399
|
+
? { customTools: writerCustomTools }
|
|
400
|
+
: {}),
|
|
401
|
+
...(requireWriterCustomTools ? { requireWriterCustomTools: true } : {}),
|
|
354
402
|
});
|
|
355
403
|
let resolveStall;
|
|
356
404
|
const stallPromise = stallTimeoutMs > 0
|
|
@@ -363,7 +411,7 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
363
411
|
return;
|
|
364
412
|
if (stallHandle)
|
|
365
413
|
clearTimeout(stallHandle);
|
|
366
|
-
stallHandle = setTimeout(() => resolveStall?.(
|
|
414
|
+
stallHandle = setTimeout(() => resolveStall?.("stall"), stallTimeoutMs);
|
|
367
415
|
};
|
|
368
416
|
unsubscribe = session.subscribe((event) => {
|
|
369
417
|
// Every real SDK event proves transport activity, including noisy deltas
|
|
@@ -372,7 +420,10 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
372
420
|
const activityKind = classifySdkActivityKind(event);
|
|
373
421
|
if (activityKind) {
|
|
374
422
|
try {
|
|
375
|
-
options.onActivity?.({
|
|
423
|
+
options.onActivity?.({
|
|
424
|
+
kind: activityKind,
|
|
425
|
+
at: new Date().toISOString(),
|
|
426
|
+
});
|
|
376
427
|
}
|
|
377
428
|
catch {
|
|
378
429
|
// best-effort: activity must never change Pi result
|
|
@@ -388,7 +439,9 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
388
439
|
stdoutCollector.append(`${line}\n`);
|
|
389
440
|
sessionEventAppender?.append(line, event);
|
|
390
441
|
});
|
|
391
|
-
const filePrefix = options.attachedFiles
|
|
442
|
+
const filePrefix = options.attachedFiles
|
|
443
|
+
.map((file) => `@${file}`)
|
|
444
|
+
.join(" ");
|
|
392
445
|
const promptMessage = filePrefix
|
|
393
446
|
? `${filePrefix}\n${options.userMessage}`
|
|
394
447
|
: options.userMessage;
|
|
@@ -397,23 +450,23 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
397
450
|
const timeoutPromise = timeoutMs > 0
|
|
398
451
|
? new Promise((resolve) => {
|
|
399
452
|
timeoutHandle = setTimeout(() => {
|
|
400
|
-
resolve(
|
|
453
|
+
resolve("absolute-timeout");
|
|
401
454
|
}, timeoutMs);
|
|
402
455
|
})
|
|
403
456
|
: null;
|
|
404
457
|
const raced = await Promise.race([
|
|
405
|
-
promptPromise.then(() =>
|
|
458
|
+
promptPromise.then(() => "done"),
|
|
406
459
|
...(timeoutPromise ? [timeoutPromise] : []),
|
|
407
460
|
...(stallPromise ? [stallPromise] : []),
|
|
408
461
|
]);
|
|
409
|
-
if (raced !==
|
|
462
|
+
if (raced !== "done") {
|
|
410
463
|
timedOut = true;
|
|
411
|
-
appendStderr(raced ===
|
|
464
|
+
appendStderr(raced === "stall"
|
|
412
465
|
? `pi SDK step stalled after ${stallTimeoutMs}ms with no provider activity`
|
|
413
466
|
: `pi SDK step timed out after ${timeoutMs}ms`);
|
|
414
|
-
const abortConfirmed = await runSessionActionWithGrace(
|
|
467
|
+
const abortConfirmed = await runSessionActionWithGrace("abort", () => session.abort());
|
|
415
468
|
disposeAttempted = true;
|
|
416
|
-
const disposeConfirmed = await runSessionActionWithGrace(
|
|
469
|
+
const disposeConfirmed = await runSessionActionWithGrace("dispose", () => session.dispose());
|
|
417
470
|
terminationConfirmed = abortConfirmed && disposeConfirmed;
|
|
418
471
|
}
|
|
419
472
|
}
|
|
@@ -429,7 +482,7 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
429
482
|
unsubscribe?.();
|
|
430
483
|
if (session && !disposeAttempted) {
|
|
431
484
|
disposeAttempted = true;
|
|
432
|
-
const disposeConfirmed = await runSessionActionWithGrace(
|
|
485
|
+
const disposeConfirmed = await runSessionActionWithGrace("dispose", () => session.dispose());
|
|
433
486
|
terminationConfirmed = terminationConfirmed && disposeConfirmed;
|
|
434
487
|
}
|
|
435
488
|
if (sessionEventAppender) {
|
|
@@ -449,28 +502,34 @@ export async function executeSingleSdkAttempt(options) {
|
|
|
449
502
|
: extractAssistantTextFromPiJson(stdout);
|
|
450
503
|
const assistantText = collected.parsedEvents > 0
|
|
451
504
|
? collected.assistantText
|
|
452
|
-
: fallbackParsed?.assistantText ??
|
|
505
|
+
: (fallbackParsed?.assistantText ?? "");
|
|
453
506
|
const parsedEvents = collected.parsedEvents > 0
|
|
454
507
|
? collected.parsedEvents
|
|
455
|
-
: fallbackParsed?.parsedEvents ?? 0;
|
|
508
|
+
: (fallbackParsed?.parsedEvents ?? 0);
|
|
456
509
|
const tokensUsed = aggregateSdkTokenUsage(usageSamples);
|
|
457
|
-
const failureCategory = terminationConfirmed
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
510
|
+
const failureCategory = terminationConfirmed
|
|
511
|
+
? classifyPiFailure({
|
|
512
|
+
assistantText,
|
|
513
|
+
exitCode: timedOut ? 1 : stderr ? 1 : 0,
|
|
514
|
+
outputTooLarge: collected.outputTooLarge,
|
|
515
|
+
stderr,
|
|
516
|
+
stdout,
|
|
517
|
+
timedOut,
|
|
518
|
+
})
|
|
519
|
+
: "termination-unconfirmed";
|
|
465
520
|
return {
|
|
466
521
|
assistantText,
|
|
467
|
-
backend:
|
|
468
|
-
command: [
|
|
522
|
+
backend: "sdk",
|
|
523
|
+
command: ["pi-sdk", ...piSdkArgs],
|
|
469
524
|
durationMs,
|
|
470
525
|
exitCode: timedOut || stderr ? 1 : 0,
|
|
471
526
|
failureCategory,
|
|
472
527
|
modelDisplay,
|
|
473
|
-
ok: terminationConfirmed &&
|
|
528
|
+
ok: terminationConfirmed &&
|
|
529
|
+
!timedOut &&
|
|
530
|
+
!stderr &&
|
|
531
|
+
assistantText.length > 0 &&
|
|
532
|
+
!collected.outputTooLarge,
|
|
474
533
|
parsedEvents,
|
|
475
534
|
stderr,
|
|
476
535
|
stdout,
|