@themoltnet/pi-extension 0.32.0 → 0.32.1
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/dist/index.js +272 -140
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1965,7 +1965,7 @@ var findLatestRuntimeSlotForAttempt = (options) => (options.client ?? client).ge
|
|
|
1965
1965
|
...options
|
|
1966
1966
|
});
|
|
1967
1967
|
/**
|
|
1968
|
-
*
|
|
1968
|
+
* Queue asynchronous deletion of terminal tasks in bulk. By default, live, unauthorized, missing, and protected tasks are skipped. Set force: true with a reason to delete protected terminal tasks.
|
|
1969
1969
|
*/
|
|
1970
1970
|
var batchDeleteTasks = (options) => (options.client ?? client).delete({
|
|
1971
1971
|
security: [
|
|
@@ -26409,14 +26409,10 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
26409
26409
|
});
|
|
26410
26410
|
return makeFailedOutput("session_setup_failed", message);
|
|
26411
26411
|
}
|
|
26412
|
-
|
|
26413
|
-
let llmErrorMessage = null;
|
|
26414
|
-
let assistantText = "";
|
|
26412
|
+
const turnState = createSessionTurnState();
|
|
26415
26413
|
let reporterError = null;
|
|
26416
26414
|
const usage = finalUsage;
|
|
26417
26415
|
let capAbort = null;
|
|
26418
|
-
let toolUseTurnCount = 0;
|
|
26419
|
-
let bashTimeoutCount = 0;
|
|
26420
26416
|
const maxTurns = opts.maxTurns ?? 0;
|
|
26421
26417
|
const maxBashTimeouts = opts.maxBashTimeouts ?? 3;
|
|
26422
26418
|
cancelListener = wireSessionAbort(reporter.cancelSignal, session);
|
|
@@ -26451,49 +26447,16 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
26451
26447
|
message
|
|
26452
26448
|
}));
|
|
26453
26449
|
};
|
|
26454
|
-
session.subscribe((
|
|
26455
|
-
|
|
26456
|
-
|
|
26457
|
-
|
|
26458
|
-
|
|
26459
|
-
|
|
26460
|
-
|
|
26461
|
-
|
|
26462
|
-
|
|
26463
|
-
|
|
26464
|
-
tool_name: event.toolName,
|
|
26465
|
-
is_error: event.isError,
|
|
26466
|
-
result: event.isError ? truncateForWire(event.result) : void 0
|
|
26467
|
-
}));
|
|
26468
|
-
if (shouldEmitToolCallError(event)) track(emitError("tool_call_error", describeToolErrorMessage(event.result), {
|
|
26469
|
-
tool: event.toolName,
|
|
26470
|
-
result: truncateForWire(event.result)
|
|
26471
|
-
}));
|
|
26472
|
-
if (maxBashTimeouts > 0 && event.toolName === "bash" && event.isError && isBashTimeoutResult(event.result)) {
|
|
26473
|
-
bashTimeoutCount += 1;
|
|
26474
|
-
if (bashTimeoutCount >= maxBashTimeouts) triggerCapAbort("max_bash_timeouts_exceeded", `Aborted after ${bashTimeoutCount} bash timeouts in this attempt (cap ${maxBashTimeouts}).`);
|
|
26475
|
-
}
|
|
26476
|
-
} else if (event.type === "turn_end") {
|
|
26477
|
-
const msg = event.message;
|
|
26478
|
-
if (msg?.role === "assistant" && msg.usage) {
|
|
26479
|
-
usage.inputTokens += Math.max(0, msg.usage.input ?? 0);
|
|
26480
|
-
usage.outputTokens += Math.max(0, msg.usage.output ?? 0);
|
|
26481
|
-
const cr = Math.max(0, msg.usage.cacheRead ?? 0);
|
|
26482
|
-
const cw = Math.max(0, msg.usage.cacheWrite ?? 0);
|
|
26483
|
-
if (cr) usage.cacheReadTokens = (usage.cacheReadTokens ?? 0) + cr;
|
|
26484
|
-
if (cw) usage.cacheWriteTokens = (usage.cacheWriteTokens ?? 0) + cw;
|
|
26485
|
-
}
|
|
26486
|
-
const stopReason = msg?.stopReason ?? "end_turn";
|
|
26487
|
-
track(emit("turn_end", { stop_reason: stopReason }));
|
|
26488
|
-
if (maxTurns > 0 && stopReason !== "end_turn" && stopReason !== "aborted" && stopReason !== "error") {
|
|
26489
|
-
toolUseTurnCount += 1;
|
|
26490
|
-
if (toolUseTurnCount >= maxTurns) triggerCapAbort("max_turns_exceeded", `Aborted after ${toolUseTurnCount} tool-use turns (cap ${maxTurns}).`);
|
|
26491
|
-
}
|
|
26492
|
-
llmAbort = msg?.stopReason === "error";
|
|
26493
|
-
if (msg?.stopReason === "error") llmErrorMessage = typeof msg.errorMessage === "string" && msg.errorMessage.length > 0 ? msg.errorMessage : null;
|
|
26494
|
-
else llmErrorMessage = null;
|
|
26495
|
-
}
|
|
26496
|
-
});
|
|
26450
|
+
session.subscribe(makeSessionEventHandler({
|
|
26451
|
+
state: turnState,
|
|
26452
|
+
usage,
|
|
26453
|
+
maxTurns,
|
|
26454
|
+
maxBashTimeouts,
|
|
26455
|
+
emit,
|
|
26456
|
+
emitError,
|
|
26457
|
+
track,
|
|
26458
|
+
triggerCapAbort
|
|
26459
|
+
}));
|
|
26497
26460
|
let runError = null;
|
|
26498
26461
|
const runPrompt = (promptText) => promptWithProviderErrorRetries({
|
|
26499
26462
|
session: liveSession,
|
|
@@ -26501,8 +26464,8 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
26501
26464
|
cancelSignal: reporter.cancelSignal,
|
|
26502
26465
|
isCapAborted: () => capAbort !== null,
|
|
26503
26466
|
getProviderErrorState: () => ({
|
|
26504
|
-
llmAbort,
|
|
26505
|
-
llmErrorMessage
|
|
26467
|
+
llmAbort: turnState.llmAbort,
|
|
26468
|
+
llmErrorMessage: turnState.llmErrorMessage
|
|
26506
26469
|
}),
|
|
26507
26470
|
maxRetries: opts.maxProviderErrorRetries ?? 2,
|
|
26508
26471
|
baseDelayMs: opts.providerErrorRetryBaseDelayMs ?? 2e3,
|
|
@@ -26531,7 +26494,7 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
26531
26494
|
isStopped: () => submitRepromptStopped({
|
|
26532
26495
|
cancelled: reporter.cancelSignal.aborted,
|
|
26533
26496
|
capAborted: capAbort !== null,
|
|
26534
|
-
llmAbort
|
|
26497
|
+
llmAbort: turnState.llmAbort
|
|
26535
26498
|
}),
|
|
26536
26499
|
onSubmitReprompt: async (event) => {
|
|
26537
26500
|
await emit("info", event);
|
|
@@ -26552,62 +26515,18 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
26552
26515
|
let parsedOutput = null;
|
|
26553
26516
|
let parsedOutputCid = null;
|
|
26554
26517
|
let parseError = null;
|
|
26555
|
-
if (!runError && !llmAbort && !cancelled && !capAbort) {
|
|
26556
|
-
const captured =
|
|
26557
|
-
|
|
26558
|
-
|
|
26559
|
-
|
|
26560
|
-
|
|
26561
|
-
|
|
26562
|
-
|
|
26563
|
-
|
|
26564
|
-
|
|
26565
|
-
|
|
26566
|
-
|
|
26567
|
-
parsedOutput = null;
|
|
26568
|
-
parsedOutputCid = null;
|
|
26569
|
-
parseError = {
|
|
26570
|
-
code: "output_cid_compute_failed",
|
|
26571
|
-
message: `Captured submit-tool output could not be canonicalized: ${message}`
|
|
26572
|
-
};
|
|
26573
|
-
recordTaskOutputParseResult({
|
|
26574
|
-
taskType: task.taskType,
|
|
26575
|
-
model: opts.model,
|
|
26576
|
-
code: "output_cid_compute_failed"
|
|
26577
|
-
});
|
|
26578
|
-
await emit("error", {
|
|
26579
|
-
message: parseError.message,
|
|
26580
|
-
phase: "output_validation"
|
|
26581
|
-
});
|
|
26582
|
-
}
|
|
26583
|
-
else if (submitToolHandle) {
|
|
26584
|
-
const exhausted = submitToolHandle.getExhaustedValidationFailure();
|
|
26585
|
-
parseError = exhausted ?? {
|
|
26586
|
-
code: "submit_output_missing",
|
|
26587
|
-
message: "Agent did not satisfy the promised submit-output criterion: no valid task submit tool call was captured before the session ended."
|
|
26588
|
-
};
|
|
26589
|
-
if (!exhausted) recordTaskOutputParseResult({
|
|
26590
|
-
taskType: task.taskType,
|
|
26591
|
-
model: opts.model,
|
|
26592
|
-
code: "output_missing"
|
|
26593
|
-
});
|
|
26594
|
-
await emit("error", {
|
|
26595
|
-
message: parseError.message,
|
|
26596
|
-
phase: "output_validation"
|
|
26597
|
-
});
|
|
26598
|
-
} else {
|
|
26599
|
-
const parsed = await parseStructuredTaskOutput(assistantText, task.taskType, {
|
|
26600
|
-
model: opts.model,
|
|
26601
|
-
input: task.input
|
|
26602
|
-
});
|
|
26603
|
-
parsedOutput = parsed.output;
|
|
26604
|
-
parsedOutputCid = parsed.outputCid;
|
|
26605
|
-
parseError = parsed.error;
|
|
26606
|
-
if (parseError) await emit("error", {
|
|
26607
|
-
message: parseError.message,
|
|
26608
|
-
phase: "output_validation"
|
|
26609
|
-
});
|
|
26610
|
-
}
|
|
26518
|
+
if (!runError && !turnState.llmAbort && !cancelled && !capAbort) {
|
|
26519
|
+
const captured = await captureAttemptOutput({
|
|
26520
|
+
taskType: task.taskType,
|
|
26521
|
+
model: opts.model,
|
|
26522
|
+
input: task.input,
|
|
26523
|
+
assistantText: turnState.assistantText,
|
|
26524
|
+
submitToolHandle,
|
|
26525
|
+
emit
|
|
26526
|
+
});
|
|
26527
|
+
parsedOutput = captured.output;
|
|
26528
|
+
parsedOutputCid = captured.outputCid;
|
|
26529
|
+
parseError = captured.error;
|
|
26611
26530
|
}
|
|
26612
26531
|
if (cancelled) return {
|
|
26613
26532
|
taskId: task.id,
|
|
@@ -26638,53 +26557,266 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
26638
26557
|
retryable: false
|
|
26639
26558
|
}
|
|
26640
26559
|
};
|
|
26641
|
-
|
|
26642
|
-
const status = runError || llmAbort || parseError || reporterErrorSnapshot ? "failed" : "completed";
|
|
26643
|
-
const errorCode = runError?.code ?? parseError?.code ?? reporterErrorSnapshot?.code ?? (llmAbort ? "llm_api_error" : void 0);
|
|
26644
|
-
const errorMessage = runError?.message ?? parseError?.message ?? reporterErrorSnapshot?.message ?? (llmAbort ? llmErrorMessage ?? "LLM API error during turn" : void 0);
|
|
26645
|
-
const errorRetryable = reporterErrorSnapshot && errorCode === reporterErrorSnapshot.code && errorMessage === reporterErrorSnapshot.message ? reporterErrorSnapshot.retryable ?? false : false;
|
|
26646
|
-
return {
|
|
26560
|
+
return buildAttemptResult({
|
|
26647
26561
|
taskId: task.id,
|
|
26648
26562
|
attemptN,
|
|
26649
|
-
status,
|
|
26650
26563
|
output: parsedOutput,
|
|
26651
26564
|
outputCid: parsedOutputCid,
|
|
26652
26565
|
usage,
|
|
26653
26566
|
durationMs: Date.now() - startTime,
|
|
26654
|
-
|
|
26655
|
-
|
|
26656
|
-
|
|
26657
|
-
|
|
26658
|
-
|
|
26659
|
-
};
|
|
26567
|
+
runError,
|
|
26568
|
+
parseError,
|
|
26569
|
+
reporterError,
|
|
26570
|
+
llmAbort: turnState.llmAbort,
|
|
26571
|
+
llmErrorMessage: turnState.llmErrorMessage
|
|
26572
|
+
});
|
|
26660
26573
|
} catch (err) {
|
|
26661
26574
|
return makeFailedOutput("executor_unexpected_error", err instanceof Error ? err.message : String(err));
|
|
26662
26575
|
} finally {
|
|
26663
|
-
|
|
26664
|
-
|
|
26665
|
-
|
|
26666
|
-
|
|
26667
|
-
|
|
26668
|
-
|
|
26669
|
-
|
|
26670
|
-
|
|
26671
|
-
|
|
26672
|
-
|
|
26576
|
+
await cleanupAttempt({
|
|
26577
|
+
cancelSignal: reporter.cancelSignal,
|
|
26578
|
+
cancelListener,
|
|
26579
|
+
session,
|
|
26580
|
+
reporterOpen,
|
|
26581
|
+
reporter,
|
|
26582
|
+
finalUsage,
|
|
26583
|
+
managed,
|
|
26584
|
+
workspace,
|
|
26585
|
+
taskId: task.id,
|
|
26586
|
+
attemptN
|
|
26587
|
+
});
|
|
26588
|
+
}
|
|
26589
|
+
}
|
|
26590
|
+
function createSessionTurnState() {
|
|
26591
|
+
return {
|
|
26592
|
+
assistantText: "",
|
|
26593
|
+
llmAbort: false,
|
|
26594
|
+
llmErrorMessage: null,
|
|
26595
|
+
toolUseTurnCount: 0,
|
|
26596
|
+
bashTimeoutCount: 0
|
|
26597
|
+
};
|
|
26598
|
+
}
|
|
26599
|
+
/**
|
|
26600
|
+
* Build the `AgentSession.subscribe` handler for one attempt: bridges pi
|
|
26601
|
+
* events to the reporter, accumulates token usage and assistant text, and
|
|
26602
|
+
* enforces the bash-timeout and tool-use-turn caps. Extracted from
|
|
26603
|
+
* `executePiTask` so this dense, branch-heavy logic is unit-tested against a
|
|
26604
|
+
* scripted event stream instead of only through a booted VM.
|
|
26605
|
+
*
|
|
26606
|
+
* The handler mutates `deps.state` and `deps.usage` in place; the caller reads
|
|
26607
|
+
* them after `session.prompt()` resolves (by which point `state.llmAbort`
|
|
26608
|
+
* holds the terminal turn's outcome — see the "last-turn wins" note below).
|
|
26609
|
+
*
|
|
26610
|
+
* @internal Exported for unit testing; not part of the package's public API.
|
|
26611
|
+
*/
|
|
26612
|
+
function makeSessionEventHandler(deps) {
|
|
26613
|
+
const { state, usage, maxTurns, maxBashTimeouts, emit, emitError, track, triggerCapAbort } = deps;
|
|
26614
|
+
return (event) => {
|
|
26615
|
+
if (event.type === "message_update") {
|
|
26616
|
+
const ae = event.assistantMessageEvent;
|
|
26617
|
+
if (ae.type === "text_delta") {
|
|
26618
|
+
state.assistantText += ae.delta;
|
|
26619
|
+
track(emit("text_delta", { delta: ae.delta }));
|
|
26673
26620
|
}
|
|
26674
|
-
|
|
26675
|
-
|
|
26676
|
-
|
|
26677
|
-
|
|
26678
|
-
|
|
26621
|
+
} else if (event.type === "tool_execution_start") track(emit("tool_call_start", { tool_name: event.toolName }));
|
|
26622
|
+
else if (event.type === "tool_execution_end") {
|
|
26623
|
+
track(emit("tool_call_end", {
|
|
26624
|
+
tool_name: event.toolName,
|
|
26625
|
+
is_error: event.isError,
|
|
26626
|
+
result: event.isError ? truncateForWire(event.result) : void 0
|
|
26627
|
+
}));
|
|
26628
|
+
if (shouldEmitToolCallError(event)) track(emitError("tool_call_error", describeToolErrorMessage(event.result), {
|
|
26629
|
+
tool: event.toolName,
|
|
26630
|
+
result: truncateForWire(event.result)
|
|
26631
|
+
}));
|
|
26632
|
+
if (maxBashTimeouts > 0 && event.toolName === "bash" && event.isError && isBashTimeoutResult(event.result)) {
|
|
26633
|
+
state.bashTimeoutCount += 1;
|
|
26634
|
+
if (state.bashTimeoutCount >= maxBashTimeouts) triggerCapAbort("max_bash_timeouts_exceeded", `Aborted after ${state.bashTimeoutCount} bash timeouts in this attempt (cap ${maxBashTimeouts}).`);
|
|
26679
26635
|
}
|
|
26636
|
+
} else if (event.type === "turn_end") {
|
|
26637
|
+
const msg = event.message;
|
|
26638
|
+
if (msg?.role === "assistant" && msg.usage) {
|
|
26639
|
+
usage.inputTokens += Math.max(0, msg.usage.input ?? 0);
|
|
26640
|
+
usage.outputTokens += Math.max(0, msg.usage.output ?? 0);
|
|
26641
|
+
const cr = Math.max(0, msg.usage.cacheRead ?? 0);
|
|
26642
|
+
const cw = Math.max(0, msg.usage.cacheWrite ?? 0);
|
|
26643
|
+
if (cr) usage.cacheReadTokens = (usage.cacheReadTokens ?? 0) + cr;
|
|
26644
|
+
if (cw) usage.cacheWriteTokens = (usage.cacheWriteTokens ?? 0) + cw;
|
|
26645
|
+
}
|
|
26646
|
+
const stopReason = msg?.stopReason ?? "end_turn";
|
|
26647
|
+
track(emit("turn_end", { stop_reason: stopReason }));
|
|
26648
|
+
if (maxTurns > 0 && stopReason !== "end_turn" && stopReason !== "aborted" && stopReason !== "error") {
|
|
26649
|
+
state.toolUseTurnCount += 1;
|
|
26650
|
+
if (state.toolUseTurnCount >= maxTurns) triggerCapAbort("max_turns_exceeded", `Aborted after ${state.toolUseTurnCount} tool-use turns (cap ${maxTurns}).`);
|
|
26651
|
+
}
|
|
26652
|
+
state.llmAbort = msg?.stopReason === "error";
|
|
26653
|
+
if (msg?.stopReason === "error") state.llmErrorMessage = typeof msg.errorMessage === "string" && msg.errorMessage.length > 0 ? msg.errorMessage : null;
|
|
26654
|
+
else state.llmErrorMessage = null;
|
|
26680
26655
|
}
|
|
26681
|
-
|
|
26682
|
-
|
|
26683
|
-
|
|
26656
|
+
};
|
|
26657
|
+
}
|
|
26658
|
+
/**
|
|
26659
|
+
* Resolve the attempt's structured output once the session has finished
|
|
26660
|
+
* cleanly (no run error / provider abort / cancel / cap). Three mutually
|
|
26661
|
+
* exclusive paths, in precedence order:
|
|
26662
|
+
*
|
|
26663
|
+
* 1. Submit tool captured a payload → trust it, compute its CID. A
|
|
26664
|
+
* canonicalization failure becomes `output_cid_compute_failed`.
|
|
26665
|
+
* 2. Submit tool registered but nothing captured → the exhausted-validation
|
|
26666
|
+
* failure wins if present, else `submit_output_missing` (recording the
|
|
26667
|
+
* `output_missing` counter so the never-called path is observable).
|
|
26668
|
+
* 3. No submit tool (legacy task type) → parse the trailing assistant text.
|
|
26669
|
+
*
|
|
26670
|
+
* Extracted from `executePiTask` so this precedence — the part a refactor is
|
|
26671
|
+
* most likely to silently reorder — is unit-tested directly. The caller
|
|
26672
|
+
* still owns the guard deciding whether output capture runs at all.
|
|
26673
|
+
*
|
|
26674
|
+
* @internal Exported for unit testing; not part of the package's public API.
|
|
26675
|
+
*/
|
|
26676
|
+
async function captureAttemptOutput(deps) {
|
|
26677
|
+
const { taskType, model, input, assistantText, submitToolHandle, emit } = deps;
|
|
26678
|
+
const captured = submitToolHandle?.getCaptured() ?? null;
|
|
26679
|
+
if (captured) try {
|
|
26680
|
+
const outputCid = await computeJsonCid(captured);
|
|
26681
|
+
recordTaskOutputParseResult({
|
|
26682
|
+
taskType,
|
|
26683
|
+
model,
|
|
26684
|
+
code: "captured_via_tool"
|
|
26685
|
+
});
|
|
26686
|
+
return {
|
|
26687
|
+
output: captured,
|
|
26688
|
+
outputCid,
|
|
26689
|
+
error: null
|
|
26690
|
+
};
|
|
26691
|
+
} catch (err) {
|
|
26692
|
+
const error = {
|
|
26693
|
+
code: "output_cid_compute_failed",
|
|
26694
|
+
message: `Captured submit-tool output could not be canonicalized: ${err instanceof Error ? err.message : String(err)}`
|
|
26695
|
+
};
|
|
26696
|
+
recordTaskOutputParseResult({
|
|
26697
|
+
taskType,
|
|
26698
|
+
model,
|
|
26699
|
+
code: "output_cid_compute_failed"
|
|
26700
|
+
});
|
|
26701
|
+
await emit("error", {
|
|
26702
|
+
message: error.message,
|
|
26703
|
+
phase: "output_validation"
|
|
26704
|
+
});
|
|
26705
|
+
return {
|
|
26706
|
+
output: null,
|
|
26707
|
+
outputCid: null,
|
|
26708
|
+
error
|
|
26709
|
+
};
|
|
26710
|
+
}
|
|
26711
|
+
if (submitToolHandle) {
|
|
26712
|
+
const exhausted = submitToolHandle.getExhaustedValidationFailure();
|
|
26713
|
+
const error = exhausted ?? {
|
|
26714
|
+
code: "submit_output_missing",
|
|
26715
|
+
message: "Agent did not satisfy the promised submit-output criterion: no valid task submit tool call was captured before the session ended."
|
|
26716
|
+
};
|
|
26717
|
+
if (!exhausted) recordTaskOutputParseResult({
|
|
26718
|
+
taskType,
|
|
26719
|
+
model,
|
|
26720
|
+
code: "output_missing"
|
|
26721
|
+
});
|
|
26722
|
+
await emit("error", {
|
|
26723
|
+
message: error.message,
|
|
26724
|
+
phase: "output_validation"
|
|
26725
|
+
});
|
|
26726
|
+
return {
|
|
26727
|
+
output: null,
|
|
26728
|
+
outputCid: null,
|
|
26729
|
+
error
|
|
26730
|
+
};
|
|
26731
|
+
}
|
|
26732
|
+
const parsed = await parseStructuredTaskOutput(assistantText, taskType, {
|
|
26733
|
+
model,
|
|
26734
|
+
input
|
|
26735
|
+
});
|
|
26736
|
+
if (parsed.error) await emit("error", {
|
|
26737
|
+
message: parsed.error.message,
|
|
26738
|
+
phase: "output_validation"
|
|
26739
|
+
});
|
|
26740
|
+
return {
|
|
26741
|
+
output: parsed.output,
|
|
26742
|
+
outputCid: parsed.outputCid,
|
|
26743
|
+
error: parsed.error
|
|
26744
|
+
};
|
|
26745
|
+
}
|
|
26746
|
+
/**
|
|
26747
|
+
* Assemble the terminal `TaskOutput` for a clean-or-failed finish (cancel and
|
|
26748
|
+
* cap aborts are handled by the caller's earlier returns). Encapsulates the
|
|
26749
|
+
* failure-precedence ladder — runError → parseError → reporterError →
|
|
26750
|
+
* provider abort — so the ordering is unit-tested rather than buried in the
|
|
26751
|
+
* orchestrator. A provider abort with no captured diagnostic falls back to a
|
|
26752
|
+
* generic message.
|
|
26753
|
+
*
|
|
26754
|
+
* Errors are non-retryable EXCEPT a reporterError that both wins the ladder
|
|
26755
|
+
* and set `retryable: true` (a transient reporter failure, #1538).
|
|
26756
|
+
*
|
|
26757
|
+
* @internal Exported for unit testing; not part of the package's public API.
|
|
26758
|
+
*/
|
|
26759
|
+
function buildAttemptResult(args) {
|
|
26760
|
+
const status = args.runError || args.llmAbort || args.parseError || args.reporterError ? "failed" : "completed";
|
|
26761
|
+
const errorCode = args.runError?.code ?? args.parseError?.code ?? args.reporterError?.code ?? (args.llmAbort ? "llm_api_error" : void 0);
|
|
26762
|
+
const errorMessage = args.runError?.message ?? args.parseError?.message ?? args.reporterError?.message ?? (args.llmAbort ? args.llmErrorMessage ?? "LLM API error during turn" : void 0);
|
|
26763
|
+
const errorRetryable = args.reporterError && errorCode === args.reporterError.code && errorMessage === args.reporterError.message ? args.reporterError.retryable ?? false : false;
|
|
26764
|
+
return {
|
|
26765
|
+
taskId: args.taskId,
|
|
26766
|
+
attemptN: args.attemptN,
|
|
26767
|
+
status,
|
|
26768
|
+
output: args.output,
|
|
26769
|
+
outputCid: args.outputCid,
|
|
26770
|
+
usage: args.usage,
|
|
26771
|
+
durationMs: args.durationMs,
|
|
26772
|
+
...errorCode && errorMessage ? { error: {
|
|
26773
|
+
code: errorCode,
|
|
26774
|
+
message: errorMessage,
|
|
26775
|
+
retryable: errorRetryable
|
|
26776
|
+
} } : {}
|
|
26777
|
+
};
|
|
26778
|
+
}
|
|
26779
|
+
/**
|
|
26780
|
+
* Tear down one attempt's resources, in order: detach the cancel listener →
|
|
26781
|
+
* dispose the pi session → finalize+close the reporter → close the VM →
|
|
26782
|
+
* clean the workspace. Extracted from `executePiTask`'s `finally` so the
|
|
26783
|
+
* swallow-vs-log-vs-propagate policy is pinned by tests.
|
|
26784
|
+
*
|
|
26785
|
+
* Failure handling is deliberately asymmetric and preserved exactly:
|
|
26786
|
+
* `session.dispose()` throws are silently swallowed; reporter finalize/close
|
|
26787
|
+
* and workspace cleanup failures are logged but non-fatal (the task is about
|
|
26788
|
+
* to be reported anyway). `vm.close()` is the one teardown error allowed to
|
|
26789
|
+
* propagate: a leaked VM means a live microVM the host never reclaims, so its
|
|
26790
|
+
* failure must surface loudly rather than be logged and forgotten.
|
|
26791
|
+
*
|
|
26792
|
+
* @internal Exported for unit testing; not part of the package's public API.
|
|
26793
|
+
*/
|
|
26794
|
+
async function cleanupAttempt(deps) {
|
|
26795
|
+
const log = deps.logError ?? ((m) => console.error(m));
|
|
26796
|
+
if (deps.cancelListener) deps.cancelSignal.removeEventListener("abort", deps.cancelListener);
|
|
26797
|
+
if (deps.session) try {
|
|
26798
|
+
deps.session.dispose();
|
|
26799
|
+
} catch {}
|
|
26800
|
+
if (deps.reporterOpen) {
|
|
26801
|
+
try {
|
|
26802
|
+
await deps.reporter.finalize(deps.finalUsage);
|
|
26684
26803
|
} catch (err) {
|
|
26685
26804
|
const detail = err instanceof Error ? err.message : String(err);
|
|
26686
|
-
|
|
26805
|
+
log(`executePiTask: reporter.finalize() failed for task ${deps.taskId} attempt ${deps.attemptN}: ${detail}`);
|
|
26687
26806
|
}
|
|
26807
|
+
try {
|
|
26808
|
+
await deps.reporter.close();
|
|
26809
|
+
} catch (err) {
|
|
26810
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
26811
|
+
log(`executePiTask: reporter.close() failed for task ${deps.taskId} attempt ${deps.attemptN}: ${detail}`);
|
|
26812
|
+
}
|
|
26813
|
+
}
|
|
26814
|
+
if (deps.managed) await deps.managed.vm.close();
|
|
26815
|
+
if (deps.workspace) try {
|
|
26816
|
+
deps.workspace.cleanup();
|
|
26817
|
+
} catch (err) {
|
|
26818
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
26819
|
+
log(`executePiTask: workspace cleanup failed for task ${deps.taskId} attempt ${deps.attemptN}: ${detail}`);
|
|
26688
26820
|
}
|
|
26689
26821
|
}
|
|
26690
26822
|
function applyExecutionPlanSandboxOverrides(sandboxConfig, executionPlan) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/pi-extension",
|
|
3
|
-
"version": "0.32.
|
|
3
|
+
"version": "0.32.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MoltNet pi extension — sandboxed tool execution in Gondolin VMs with MoltNet identity and persistent memory",
|
|
6
6
|
"keywords": [
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"@earendil-works/gondolin": "^0.9.1",
|
|
37
37
|
"@opentelemetry/api": "^1.9.0",
|
|
38
38
|
"typebox": "^1.2.8",
|
|
39
|
-
"@themoltnet/
|
|
40
|
-
"@themoltnet/
|
|
39
|
+
"@themoltnet/sdk": "0.119.0",
|
|
40
|
+
"@themoltnet/agent-runtime": "0.34.1"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@earendil-works/pi-coding-agent": ">=0.74.0",
|