@sema-agent/core 5.7.0 → 5.8.0
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 +37 -0
- package/dist/agents/cascade.d.ts +1 -1
- package/dist/agents/subagent.d.ts +4 -7
- package/dist/agents/subagent.js +10 -19
- package/dist/agents/teacher.js +3 -3
- package/dist/agents/team.d.ts +1 -1
- package/dist/agents/team.js +1 -1
- package/dist/agents/verify.js +1 -1
- package/dist/brain/anthropic.js +24 -35
- package/dist/brain/openai.js +22 -33
- package/dist/brain/stream-engine.d.ts +0 -2
- package/dist/brain/stream-engine.js +5 -47
- package/dist/brain/stream-shared.d.ts +0 -10
- package/dist/brain/stream-shared.js +0 -23
- package/dist/brain/terminal-cause.d.ts +0 -1
- package/dist/brain/terminal-cause.js +0 -3
- package/dist/brain/timeout.d.ts +4 -2
- package/dist/brain/timeout.js +4 -14
- package/dist/config/catalog.d.ts +3 -11
- package/dist/config/catalog.js +30 -69
- package/dist/config/defaults.d.ts +0 -3
- package/dist/config/defaults.js +0 -3
- package/dist/core/auto-compaction.d.ts +2 -11
- package/dist/core/auto-compaction.js +3 -67
- package/dist/core/checkpoint-store.d.ts +10 -8
- package/dist/core/checkpoint-store.js +7 -7
- package/dist/core/memory-engine/file-backend.js +20 -17
- package/dist/core/memory-engine/layout.d.ts +5 -0
- package/dist/core/memory-engine/layout.js +72 -47
- package/dist/core/runner/assemble-result.d.ts +2 -8
- package/dist/core/runner/assemble-result.js +15 -16
- package/dist/core/runner/prepare-task.d.ts +26 -6
- package/dist/core/runner/prepare-task.js +203 -141
- package/dist/core/runner/runtask.d.ts +0 -2
- package/dist/core/runner/runtask.js +200 -244
- package/dist/core/session-policy-store.d.ts +5 -0
- package/dist/core/session-policy-store.js +3 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
- package/dist/core/task-registry-agent.d.ts +2 -0
- package/dist/core/task-registry-agent.js +33 -1
- package/dist/core/task-registry.d.ts +2 -0
- package/dist/core/task-registry.js +9 -1
- package/dist/core/tool-errors.js +10 -2
- package/dist/core/trace.d.ts +1 -8
- package/dist/core/types.d.ts +24 -30
- package/dist/core/usage-window-store.d.ts +39 -0
- package/dist/core/usage-window-store.js +115 -0
- package/dist/engine/execution-env/node-execution-env.js +0 -22
- package/dist/engine/harness/agent-harness.d.ts +0 -7
- package/dist/engine/harness/agent-harness.js +13 -53
- package/dist/engine/harness/types.d.ts +2 -15
- package/dist/engine/llm/types.d.ts +1 -2
- package/dist/engine/loop/agent-loop.d.ts +1 -1
- package/dist/engine/loop/agent-loop.js +5 -171
- package/dist/engine/loop/types.d.ts +0 -18
- package/dist/index.d.ts +10 -5
- package/dist/index.js +8 -4
- package/dist/orchestration/goal.js +1 -5
- package/dist/orchestration/run-workflow-tool.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.js +1 -2
- package/dist/orchestration/workflow-governance.d.ts +6 -6
- package/dist/orchestration/workflow-governance.js +33 -47
- package/dist/prompt-assembly/event-registry.js +2 -3
- package/dist/stores/cc/lockfile.js +9 -11
- package/dist/stores/cc/mailbox-store.js +49 -38
- package/dist/stores/file/index.d.ts +3 -0
- package/dist/stores/file/index.js +4 -0
- package/dist/stores/file/usage-window-store.d.ts +9 -0
- package/dist/stores/file/usage-window-store.js +86 -0
- package/dist/tools/fs/fs-bash.d.ts +1 -10
- package/dist/tools/fs/fs-bash.js +20 -60
- package/dist/tools/fs/index.d.ts +0 -2
- package/dist/tools/fs/index.js +1 -2
- package/package.json +1 -4
- package/dist/bin/sema-tb.d.ts +0 -31
- package/dist/bin/sema-tb.js +0 -448
- package/dist/bin/tb-env.d.ts +0 -2
- package/dist/bin/tb-env.js +0 -17
- package/dist/brain/walltime.d.ts +0 -1
- package/dist/brain/walltime.js +0 -1
- package/dist/core/runner/call-cap.d.ts +0 -67
- package/dist/core/runner/call-cap.js +0 -145
- package/dist/core/runner/cut-kill.d.ts +0 -10
- package/dist/core/runner/cut-kill.js +0 -37
|
@@ -26,6 +26,7 @@ export interface SessionPolicyStore {
|
|
|
26
26
|
listBySession?(sessionId: string): Promise<SessionRulesRecord[]>;
|
|
27
27
|
deleteBySession?(sessionId: string): Promise<void>;
|
|
28
28
|
}
|
|
29
|
+
declare function lexWithin(root: string, p: string): boolean;
|
|
29
30
|
export declare function normalizeRules(rules: SessionPermissionRules): SessionPermissionRules;
|
|
30
31
|
export declare function loosenReasons(prior: SessionPermissionRules, next: SessionPermissionRules): string[];
|
|
31
32
|
export declare function stripRev(rules: SessionPermissionRules): SessionPermissionRules;
|
|
@@ -37,3 +38,7 @@ export declare class InMemorySessionPolicyStore implements SessionPolicyStore {
|
|
|
37
38
|
listBySession(sessionId: string): Promise<SessionRulesRecord[]>;
|
|
38
39
|
deleteBySession(sessionId: string): Promise<void>;
|
|
39
40
|
}
|
|
41
|
+
export declare const __testables: {
|
|
42
|
+
lexWithin: typeof lexWithin;
|
|
43
|
+
};
|
|
44
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { posix } from "node:path";
|
|
2
|
+
import { isWinFormPath } from "../tools/fs/safety.js";
|
|
2
3
|
export class SessionPolicyError extends Error {
|
|
3
4
|
code;
|
|
4
5
|
constructor(code, message) {
|
|
@@ -9,7 +10,7 @@ export class SessionPolicyError extends Error {
|
|
|
9
10
|
}
|
|
10
11
|
function lexWithin(root, p) {
|
|
11
12
|
const fold = (x) => {
|
|
12
|
-
let n = x.replace(/\\/g, "/");
|
|
13
|
+
let n = isWinFormPath(x) ? x.replace(/\\/g, "/") : x;
|
|
13
14
|
if (/^[A-Za-z]:(\/|$)/.test(n))
|
|
14
15
|
n = n[0].toLowerCase() + n.slice(1);
|
|
15
16
|
return n;
|
|
@@ -116,3 +117,4 @@ export class InMemorySessionPolicyStore {
|
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
}
|
|
120
|
+
export const __testables = { lexWithin };
|
|
@@ -157,8 +157,8 @@ export async function checkpointListByScopeSummaries(store) {
|
|
|
157
157
|
scope: "tenant-a",
|
|
158
158
|
sessionId: "out-of-budget",
|
|
159
159
|
createdAt: 1_700_000_000_001,
|
|
160
|
-
gate: { kind: "resource_limit", reason: "
|
|
161
|
-
pendingAction: { kind: "resource_limit", reason: "
|
|
160
|
+
gate: { kind: "resource_limit", reason: "budget_tokens" },
|
|
161
|
+
pendingAction: { kind: "resource_limit", reason: "budget_tokens" },
|
|
162
162
|
resourceLedger: { totalBudgetMicroUsd: 10_000_000, spentMicroUsd: 4_200_000, spentTokens: 9, spentTurns: 3, sliceCount: 2 },
|
|
163
163
|
});
|
|
164
164
|
const resolved = createCheckpointFixture({ token: "tok-resolved", scope: "tenant-a", sessionId: "done" });
|
|
@@ -106,6 +106,8 @@ export declare function settleRevivedAgentLane(core: DurableAgentCore, id: strin
|
|
|
106
106
|
retryable?: boolean;
|
|
107
107
|
errorKind?: string;
|
|
108
108
|
}): "completed" | "failed" | "killed" | undefined;
|
|
109
|
+
export declare function noteBackgroundAgentActivityLane(core: DurableAgentCore, id: string, now?: number): void;
|
|
110
|
+
export declare function reapStaleSessionBackgroundAgentsLane(core: DurableAgentCore, staleMs: number, now?: number, onTerminal?: (note: () => void) => void): number;
|
|
109
111
|
export declare function unmarkRetainedContinuationLane(core: DurableAgentCore, id: string): void;
|
|
110
112
|
export declare function attachAgentNotifyLane(core: DurableAgentCore, id: string, notify: NonNullable<BackgroundAgentTaskHandle["notify"]>, cycle?: number): void;
|
|
111
113
|
export declare function deliverToRunningAgentLane(core: DurableAgentCore, id: string, access: TaskAccess, notification: import("./task-notification.js").TaskNotificationPayload, opts?: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { uuidv7 } from "../internal/harness.js";
|
|
3
|
-
import { canAccessAgentRecord, BackgroundAgentStoreError, REVIVED_ROW_CLEARED_FIELDS, } from "./background-agent-store.js";
|
|
3
|
+
import { canAccessAgentRecord, BackgroundAgentStoreError, REVIVED_ROW_CLEARED_FIELDS, STALE_RUNNING_REAP_ATTRIBUTION, } from "./background-agent-store.js";
|
|
4
4
|
import { shutdownDebug } from "./shutdown-debug.js";
|
|
5
5
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
6
6
|
import { boundedRedactedSummary } from "./untrusted-egress.js";
|
|
@@ -870,6 +870,38 @@ export function reviveBackgroundAgentLane(core, id, access, abort) {
|
|
|
870
870
|
export function settleRevivedAgentLane(core, id, cycle, outcome) {
|
|
871
871
|
return settleBackgroundAgentLane(core, id, { ...outcome, cycle });
|
|
872
872
|
}
|
|
873
|
+
export function noteBackgroundAgentActivityLane(core, id, now = Date.now()) {
|
|
874
|
+
const handle = core.handles.get(id);
|
|
875
|
+
if (!handle || handle.type !== "background_agent" || handle.status !== "running")
|
|
876
|
+
return;
|
|
877
|
+
handle.updatedAt = now;
|
|
878
|
+
}
|
|
879
|
+
export function reapStaleSessionBackgroundAgentsLane(core, staleMs, now = Date.now(), onTerminal) {
|
|
880
|
+
if (!Number.isFinite(staleMs) || staleMs <= 0) {
|
|
881
|
+
throw new Error(`reapStaleSessionBackgroundAgents: staleMs must be a positive finite number of milliseconds (got ${String(staleMs)})`);
|
|
882
|
+
}
|
|
883
|
+
let reaped = 0;
|
|
884
|
+
for (const handle of core.handles.values()) {
|
|
885
|
+
if (handle.type !== "background_agent" || handle.status !== "running" || handle.sessionScoped !== true)
|
|
886
|
+
continue;
|
|
887
|
+
if (now - handle.updatedAt <= staleMs)
|
|
888
|
+
continue;
|
|
889
|
+
core.markStopSource(handle.id, "system");
|
|
890
|
+
const reapTerminalNote = handle.onReapTerminal;
|
|
891
|
+
handle.abort.abort();
|
|
892
|
+
settleBackgroundAgentLane(core, handle.id, {
|
|
893
|
+
status: "killed",
|
|
894
|
+
error: STALE_RUNNING_REAP_ATTRIBUTION,
|
|
895
|
+
cycle: handle.reviveCycle ?? 0,
|
|
896
|
+
});
|
|
897
|
+
if (reapTerminalNote !== undefined && handle.terminalNotified !== true) {
|
|
898
|
+
handle.terminalNotified = true;
|
|
899
|
+
onTerminal?.(reapTerminalNote);
|
|
900
|
+
}
|
|
901
|
+
reaped++;
|
|
902
|
+
}
|
|
903
|
+
return reaped;
|
|
904
|
+
}
|
|
873
905
|
export function unmarkRetainedContinuationLane(core, id) {
|
|
874
906
|
const handle = core.handles.get(id);
|
|
875
907
|
if (handle && handle.type === "background_agent")
|
|
@@ -145,6 +145,8 @@ export declare class TaskRegistry {
|
|
|
145
145
|
seq?: number;
|
|
146
146
|
cycle?: number;
|
|
147
147
|
}): "completed" | "failed" | "killed" | undefined;
|
|
148
|
+
noteBackgroundAgentActivity(id: string, now?: number): void;
|
|
149
|
+
reapStaleSessionBackgroundAgents(staleMs: number, now?: number): number;
|
|
148
150
|
abortBackgroundAgentsForOwner(access: TaskAccess, opts?: {
|
|
149
151
|
skipSessionScoped?: boolean;
|
|
150
152
|
sessionScopedOnly?: boolean;
|
|
@@ -10,7 +10,7 @@ import { registerWorkflowLane, pollWorkflowLane, stopWorkflowLane } from "./task
|
|
|
10
10
|
import { mintCompletionId, canAccessWorkflowRun, formatWorkflowRun, clipTaskOutput, assertOwnership, sleepPollStep, statusFromBackground, rollSpoolText, accountDroppedBytes, renderSpoolBody, spoolDropNote, droppedGapNote, alreadyTerminalStopNote, terminalTaskSummary, TASK_OUTPUT_MAX_CHARS, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE, canAccess, DURABLE_AGENT_HANDLE_RE, } from "./task-registry-shared.js";
|
|
11
11
|
export { normalizeAgentName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR } from "./task-registry-shared.js";
|
|
12
12
|
import { TASK_OUTPUT_TOOL_NAME, TASK_STOP_TOOL_NAME, TASK_OUTPUT_CONTRACT, TASK_STOP_CONTRACT, TASK_OUTPUT_MISSING_ID_MESSAGE, TASK_STOP_MISSING_ID_MESSAGE, TASK_STOP_PARAMS, resolveTaskIdArg, REGISTRY_TASK_TOOL_CAPS, composeTaskOutputDescription, composeTaskOutputParams, composeTaskStopDescription, } from "./task-tool-shape.js";
|
|
13
|
-
import { durableAgentArmedLane, durableAgentRowProbeLane, beginDurableClaimLane, endDurableClaimLane, reapDurableAgentsLane, releaseDurableTranscriptAnchorLane, bindBackgroundAgentSessionLane, registerBackgroundAgentLane, parkBackgroundAgentLane, reconcileParkedAgentsLane, claimParkedAgentLane, rollbackParkedClaimLane, consumeParkedFlipLane, finalizeParkedResumeLane, settleBackgroundAgentLane, abortBackgroundAgentsForOwnerLane, serveDurableAgentRowLane, resolveBackgroundAgentByNameLane, markRetainedContinuationLane, reviveBackgroundAgentLane, settleRevivedAgentLane, unmarkRetainedContinuationLane, attachAgentNotifyLane, deliverToRunningAgentLane, runningBackgroundAgentLabelsLane, runningAgentFooterLane, notFoundRunningAgentsTail, pollBackgroundAgentLane, stopBackgroundAgentLane, } from "./task-registry-agent.js";
|
|
13
|
+
import { durableAgentArmedLane, durableAgentRowProbeLane, beginDurableClaimLane, endDurableClaimLane, reapDurableAgentsLane, noteBackgroundAgentActivityLane, reapStaleSessionBackgroundAgentsLane, releaseDurableTranscriptAnchorLane, bindBackgroundAgentSessionLane, registerBackgroundAgentLane, parkBackgroundAgentLane, reconcileParkedAgentsLane, claimParkedAgentLane, rollbackParkedClaimLane, consumeParkedFlipLane, finalizeParkedResumeLane, settleBackgroundAgentLane, abortBackgroundAgentsForOwnerLane, serveDurableAgentRowLane, resolveBackgroundAgentByNameLane, markRetainedContinuationLane, reviveBackgroundAgentLane, settleRevivedAgentLane, unmarkRetainedContinuationLane, attachAgentNotifyLane, deliverToRunningAgentLane, runningBackgroundAgentLabelsLane, runningAgentFooterLane, notFoundRunningAgentsTail, pollBackgroundAgentLane, stopBackgroundAgentLane, } from "./task-registry-agent.js";
|
|
14
14
|
export { canAccessWorkflowRun, clipTaskOutput, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE };
|
|
15
15
|
const BLOCK_DEFAULT_TIMEOUT_MS = 30_000;
|
|
16
16
|
const BLOCK_MAX_TIMEOUT_MS = 600_000;
|
|
@@ -167,6 +167,14 @@ export class TaskRegistry {
|
|
|
167
167
|
settleBackgroundAgent(id, outcome) {
|
|
168
168
|
return settleBackgroundAgentLane(this.core, id, outcome);
|
|
169
169
|
}
|
|
170
|
+
noteBackgroundAgentActivity(id, now) {
|
|
171
|
+
return noteBackgroundAgentActivityLane(this.core, id, now);
|
|
172
|
+
}
|
|
173
|
+
reapStaleSessionBackgroundAgents(staleMs, now) {
|
|
174
|
+
return reapStaleSessionBackgroundAgentsLane(this.core, staleMs, now, (note) => {
|
|
175
|
+
this.notifier.notify(() => note(), "taskRegistry.reapTerminalNote");
|
|
176
|
+
});
|
|
177
|
+
}
|
|
170
178
|
abortBackgroundAgentsForOwner(access, opts) {
|
|
171
179
|
return abortBackgroundAgentsForOwnerLane(this.core, access, opts);
|
|
172
180
|
}
|
package/dist/core/tool-errors.js
CHANGED
|
@@ -65,9 +65,14 @@ const BRAIN_FLAT_CODES = new Set([
|
|
|
65
65
|
"refusal",
|
|
66
66
|
"length_empty",
|
|
67
67
|
]);
|
|
68
|
+
const EXACT_CODE_CLASS = new Map([
|
|
69
|
+
["limits.max_tokens_exceeded", "budget"],
|
|
70
|
+
["limits.max_cost_exceeded", "budget"],
|
|
71
|
+
["limits.max_turns_exceeded", "limit"],
|
|
72
|
+
["limits.max_walltime_exceeded", "limit"],
|
|
73
|
+
]);
|
|
68
74
|
const DOTTED_PREFIX_CLASS = [
|
|
69
|
-
["
|
|
70
|
-
["limit.", "limit"],
|
|
75
|
+
["limits.", "limit"],
|
|
71
76
|
["output.", "output"],
|
|
72
77
|
["suspend.", "suspend"],
|
|
73
78
|
["review.", "review"],
|
|
@@ -79,6 +84,9 @@ const DOTTED_PREFIX_CLASS = [
|
|
|
79
84
|
export function errorClassOf(errorCode) {
|
|
80
85
|
if (!errorCode)
|
|
81
86
|
return "unknown";
|
|
87
|
+
const exact = EXACT_CODE_CLASS.get(errorCode);
|
|
88
|
+
if (exact !== undefined)
|
|
89
|
+
return exact;
|
|
82
90
|
for (const [prefix, cls] of DOTTED_PREFIX_CLASS) {
|
|
83
91
|
if (errorCode.startsWith(prefix))
|
|
84
92
|
return cls;
|
package/dist/core/trace.d.ts
CHANGED
|
@@ -2,17 +2,10 @@ import type { TaskStatus, ToolEffect } from "./types.js";
|
|
|
2
2
|
import type { ThinkingLevel } from "../internal/harness-types.js";
|
|
3
3
|
import type { ToolManifestRow } from "../prompt-assembly/tool-catalog.js";
|
|
4
4
|
export interface TraceMechanismsSummary {
|
|
5
|
-
finalizeInjected?: true;
|
|
6
|
-
nudgesSent?: number;
|
|
7
|
-
capShrinks?: number;
|
|
8
|
-
callCutoffs?: number;
|
|
9
|
-
toolClamps?: number;
|
|
10
|
-
toolCuts?: number;
|
|
11
|
-
toolCutKills?: number;
|
|
12
|
-
pendingCutTools?: number;
|
|
13
5
|
finalVerifyInjected?: true;
|
|
14
6
|
finalVerifyInjections?: number;
|
|
15
7
|
attachmentsInjected?: number;
|
|
8
|
+
approachNoticesSent?: number;
|
|
16
9
|
repetitionCuts?: number;
|
|
17
10
|
repetitionSpared?: number;
|
|
18
11
|
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -244,6 +244,22 @@ export type ImageInput = {
|
|
|
244
244
|
} | {
|
|
245
245
|
url: string;
|
|
246
246
|
};
|
|
247
|
+
export interface TaskLimits {
|
|
248
|
+
maxTokens?: number;
|
|
249
|
+
maxCostUsd?: number;
|
|
250
|
+
maxTurns?: number;
|
|
251
|
+
maxWalltimeMs?: number;
|
|
252
|
+
maxOutputTokens?: number;
|
|
253
|
+
approachNotice?: false | {
|
|
254
|
+
at?: [number, number];
|
|
255
|
+
};
|
|
256
|
+
budgetStreamCancel?: boolean;
|
|
257
|
+
degrade?: {
|
|
258
|
+
to: ModelRef;
|
|
259
|
+
atCostFraction: number;
|
|
260
|
+
};
|
|
261
|
+
brainCallGuardrailMs?: import("../brain/timeout.js").BrainCallGuardrailKnob;
|
|
262
|
+
}
|
|
247
263
|
export interface TaskSpec {
|
|
248
264
|
taskId?: string;
|
|
249
265
|
objective: string;
|
|
@@ -293,7 +309,7 @@ export interface TaskSpec {
|
|
|
293
309
|
resourceSuspend?: {
|
|
294
310
|
scope: string;
|
|
295
311
|
totalBudgetUsd?: number;
|
|
296
|
-
|
|
312
|
+
totalTokens?: number;
|
|
297
313
|
maxSlices?: number;
|
|
298
314
|
ttlMs?: number;
|
|
299
315
|
};
|
|
@@ -324,17 +340,7 @@ export interface TaskSpec {
|
|
|
324
340
|
note?: string;
|
|
325
341
|
};
|
|
326
342
|
};
|
|
327
|
-
limits?:
|
|
328
|
-
maxTurns?: number;
|
|
329
|
-
timeoutSec?: number;
|
|
330
|
-
maxOutputTokens?: number;
|
|
331
|
-
deadlineNudge?: false | {
|
|
332
|
-
at?: [number, number];
|
|
333
|
-
};
|
|
334
|
-
callCapByDeadline?: false;
|
|
335
|
-
gracefulFinalize?: false;
|
|
336
|
-
brainCallGuardrailMs?: import("../brain/timeout.js").BrainCallGuardrailKnob;
|
|
337
|
-
};
|
|
343
|
+
limits?: TaskLimits;
|
|
338
344
|
configOverrides?: ReadonlyArray<import("../config/catalog.js").ConfigOverrideDeclaration>;
|
|
339
345
|
forwardSubagentEvents?: boolean;
|
|
340
346
|
retainSubagentSessions?: boolean | {
|
|
@@ -343,13 +349,6 @@ export interface TaskSpec {
|
|
|
343
349
|
};
|
|
344
350
|
retainBackgroundProcesses?: boolean;
|
|
345
351
|
streamingToolExecution?: boolean;
|
|
346
|
-
maxCostUsd?: number;
|
|
347
|
-
maxTokens?: number;
|
|
348
|
-
budgetStreamCancel?: boolean;
|
|
349
|
-
degrade?: {
|
|
350
|
-
to: ModelRef;
|
|
351
|
-
atCostFraction: number;
|
|
352
|
-
};
|
|
353
352
|
outputSchema?: TSchema;
|
|
354
353
|
outputRetries?: number;
|
|
355
354
|
tracer?: import("./trace.js").TracerHook;
|
|
@@ -389,7 +388,7 @@ export interface TaskSpec {
|
|
|
389
388
|
} | undefined>;
|
|
390
389
|
signal?: AbortSignal;
|
|
391
390
|
}
|
|
392
|
-
export type TaskStatus = "completed" | "blocked" | "failed" | "
|
|
391
|
+
export type TaskStatus = "completed" | "blocked" | "failed" | "suspended" | "needs_review";
|
|
393
392
|
export interface RemoteEnvFailureNote {
|
|
394
393
|
op: "suspendVM" | "resumeVM" | "postResumeInit";
|
|
395
394
|
code: import("./remote-env.js").RemoteExecutionErrorCode;
|
|
@@ -446,17 +445,10 @@ export interface TaskResult {
|
|
|
446
445
|
costMicroUsd: number;
|
|
447
446
|
};
|
|
448
447
|
mechanisms?: {
|
|
449
|
-
finalizeInjected?: true;
|
|
450
|
-
nudgesSent?: number;
|
|
451
|
-
capShrinks?: number;
|
|
452
|
-
callCutoffs?: number;
|
|
453
|
-
toolClamps?: number;
|
|
454
|
-
toolCuts?: number;
|
|
455
|
-
toolCutKills?: number;
|
|
456
|
-
pendingCutTools?: number;
|
|
457
448
|
finalVerifyInjected?: true;
|
|
458
449
|
finalVerifyInjections?: number;
|
|
459
450
|
attachmentsInjected?: number;
|
|
451
|
+
approachNoticesSent?: number;
|
|
460
452
|
repetitionCuts?: number;
|
|
461
453
|
repetitionSpared?: number;
|
|
462
454
|
repetitionEvents?: Array<{
|
|
@@ -568,7 +560,7 @@ export type TaskEvent = ({
|
|
|
568
560
|
modelFallback?: true;
|
|
569
561
|
fallbackReason?: "window";
|
|
570
562
|
clampedRatio?: number;
|
|
571
|
-
clampReason?: "budget" | "
|
|
563
|
+
clampReason?: "budget" | "tolerance";
|
|
572
564
|
phaseDurations?: import("./auto-compaction.js").CompactionPhaseDurations;
|
|
573
565
|
} & TaskEventIdentity) | ({
|
|
574
566
|
type: "compaction_outcome";
|
|
@@ -577,7 +569,7 @@ export type TaskEvent = ({
|
|
|
577
569
|
reason?: string;
|
|
578
570
|
} & TaskEventIdentity) | ({
|
|
579
571
|
type: "steering_injected";
|
|
580
|
-
source: "
|
|
572
|
+
source: "limit_approach" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
|
|
581
573
|
preview: string;
|
|
582
574
|
} & TaskEventIdentity) | ({
|
|
583
575
|
type: "diagnostics";
|
|
@@ -750,6 +742,8 @@ export interface RunnerDeps {
|
|
|
750
742
|
workflowRunStore?: import("./workflow-run-store.js").WorkflowRunStore;
|
|
751
743
|
backgroundAgentStore?: import("./background-agent-store.js").BackgroundAgentStore;
|
|
752
744
|
mailboxStore?: import("./mailbox-store.js").MailboxStore;
|
|
745
|
+
usageWindows?: readonly import("./usage-window-store.js").UsageWindow[];
|
|
746
|
+
usageWindowStore?: import("./usage-window-store.js").UsageWindowStore;
|
|
753
747
|
workflowJournalStore?: import("./workflow-journal-store.js").WorkflowJournalStore;
|
|
754
748
|
workflowScriptStore?: import("../orchestration/workflow-script-store.js").WorkflowScriptStore;
|
|
755
749
|
builtinWorkflows?: boolean;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface UsageWindow {
|
|
2
|
+
windowMs: number;
|
|
3
|
+
maxTokens: number;
|
|
4
|
+
anchor: "first-use" | "rolling";
|
|
5
|
+
}
|
|
6
|
+
export interface UsageSlot {
|
|
7
|
+
at: number;
|
|
8
|
+
tokens: number;
|
|
9
|
+
}
|
|
10
|
+
export interface UsageBucketRow {
|
|
11
|
+
windowMs: number;
|
|
12
|
+
openedAt: number;
|
|
13
|
+
tokens: number;
|
|
14
|
+
}
|
|
15
|
+
export interface UsageWindowRecord {
|
|
16
|
+
slots: readonly UsageSlot[];
|
|
17
|
+
buckets: readonly UsageBucketRow[];
|
|
18
|
+
}
|
|
19
|
+
export interface UsageWindowReading {
|
|
20
|
+
window: UsageWindow;
|
|
21
|
+
tokens: number;
|
|
22
|
+
exhausted: boolean;
|
|
23
|
+
retryAfterMs: number;
|
|
24
|
+
}
|
|
25
|
+
export interface UsageWindowStore {
|
|
26
|
+
charge(key: string, tokens: number, at: number, windows: readonly UsageWindow[]): Promise<void>;
|
|
27
|
+
read(key: string, windows: readonly UsageWindow[], now: number): Promise<readonly UsageWindowReading[]>;
|
|
28
|
+
}
|
|
29
|
+
export declare const GLOBAL_USAGE_KEY = "__usage_global__";
|
|
30
|
+
export declare const EMPTY_USAGE_WINDOW_RECORD: UsageWindowRecord;
|
|
31
|
+
export declare function resolveUsageWindows(windows: readonly UsageWindow[] | undefined): readonly UsageWindow[] | undefined;
|
|
32
|
+
export declare function chargeUsageRecord(record: UsageWindowRecord, tokens: number, at: number, windows: readonly UsageWindow[]): UsageWindowRecord;
|
|
33
|
+
export declare function readUsageRecord(record: UsageWindowRecord, windows: readonly UsageWindow[], now: number): UsageWindowReading[];
|
|
34
|
+
export declare function usageRetryAfterMs(readings: readonly UsageWindowReading[]): number | undefined;
|
|
35
|
+
export declare class InMemoryUsageWindowStore implements UsageWindowStore {
|
|
36
|
+
private readonly records;
|
|
37
|
+
charge(key: string, tokens: number, at: number, windows: readonly UsageWindow[]): Promise<void>;
|
|
38
|
+
read(key: string, windows: readonly UsageWindow[], now: number): Promise<readonly UsageWindowReading[]>;
|
|
39
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export const GLOBAL_USAGE_KEY = "__usage_global__";
|
|
2
|
+
export const EMPTY_USAGE_WINDOW_RECORD = Object.freeze({
|
|
3
|
+
slots: Object.freeze([]),
|
|
4
|
+
buckets: Object.freeze([]),
|
|
5
|
+
});
|
|
6
|
+
function usageConfigError(code, message) {
|
|
7
|
+
const e = new Error(message);
|
|
8
|
+
e.code = code;
|
|
9
|
+
return e;
|
|
10
|
+
}
|
|
11
|
+
export function resolveUsageWindows(windows) {
|
|
12
|
+
if (windows === undefined)
|
|
13
|
+
return undefined;
|
|
14
|
+
if (!Array.isArray(windows)) {
|
|
15
|
+
throw usageConfigError("config.usage_window_invalid", `RunnerDeps.usageWindows must be an array (got ${typeof windows})`);
|
|
16
|
+
}
|
|
17
|
+
for (const w of windows) {
|
|
18
|
+
if (w === null || typeof w !== "object") {
|
|
19
|
+
throw usageConfigError("config.usage_window_invalid", `RunnerDeps.usageWindows entries must be objects (got ${String(w)})`);
|
|
20
|
+
}
|
|
21
|
+
if (typeof w.windowMs !== "number" || !Number.isFinite(w.windowMs) || w.windowMs <= 0) {
|
|
22
|
+
throw usageConfigError("config.usage_window_invalid", `RunnerDeps.usageWindows[].windowMs must be a finite number of milliseconds > 0 (got ${String(w.windowMs)})`);
|
|
23
|
+
}
|
|
24
|
+
if (typeof w.maxTokens !== "number" || !Number.isFinite(w.maxTokens) || w.maxTokens < 0) {
|
|
25
|
+
throw usageConfigError("config.usage_window_invalid", `RunnerDeps.usageWindows[].maxTokens must be a finite, non-negative number (got ${String(w.maxTokens)})`);
|
|
26
|
+
}
|
|
27
|
+
if (w.anchor !== "first-use" && w.anchor !== "rolling") {
|
|
28
|
+
throw usageConfigError("config.usage_window_invalid", `RunnerDeps.usageWindows[].anchor must be "first-use" or "rolling" (got ${String(w.anchor)})`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return windows;
|
|
32
|
+
}
|
|
33
|
+
function rollingHorizonMs(windows) {
|
|
34
|
+
let widest;
|
|
35
|
+
for (const w of windows) {
|
|
36
|
+
if (w.anchor !== "rolling")
|
|
37
|
+
continue;
|
|
38
|
+
if (widest === undefined || w.windowMs > widest)
|
|
39
|
+
widest = w.windowMs;
|
|
40
|
+
}
|
|
41
|
+
return widest;
|
|
42
|
+
}
|
|
43
|
+
export function chargeUsageRecord(record, tokens, at, windows) {
|
|
44
|
+
const horizon = rollingHorizonMs(windows);
|
|
45
|
+
const keptSlots = horizon === undefined ? [] : record.slots.filter((s) => s.at > at - horizon).map((s) => ({ at: s.at, tokens: s.tokens }));
|
|
46
|
+
if (horizon !== undefined && tokens > 0)
|
|
47
|
+
keptSlots.push({ at, tokens });
|
|
48
|
+
const buckets = [];
|
|
49
|
+
for (const w of windows) {
|
|
50
|
+
if (w.anchor !== "first-use")
|
|
51
|
+
continue;
|
|
52
|
+
if (buckets.some((b) => b.windowMs === w.windowMs))
|
|
53
|
+
continue;
|
|
54
|
+
const prior = record.buckets.find((b) => b.windowMs === w.windowMs);
|
|
55
|
+
const lapsed = prior === undefined || at >= prior.openedAt + w.windowMs;
|
|
56
|
+
if (lapsed) {
|
|
57
|
+
if (tokens > 0)
|
|
58
|
+
buckets.push({ windowMs: w.windowMs, openedAt: at, tokens });
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
buckets.push({ windowMs: w.windowMs, openedAt: prior.openedAt, tokens: prior.tokens + tokens });
|
|
62
|
+
}
|
|
63
|
+
return { slots: keptSlots, buckets };
|
|
64
|
+
}
|
|
65
|
+
export function readUsageRecord(record, windows, now) {
|
|
66
|
+
const readings = [];
|
|
67
|
+
for (const w of windows) {
|
|
68
|
+
let tokens = 0;
|
|
69
|
+
let freesAt;
|
|
70
|
+
if (w.anchor === "rolling") {
|
|
71
|
+
const floor = now - w.windowMs;
|
|
72
|
+
let oldest;
|
|
73
|
+
for (const s of record.slots) {
|
|
74
|
+
if (s.at <= floor)
|
|
75
|
+
continue;
|
|
76
|
+
tokens += s.tokens;
|
|
77
|
+
if (oldest === undefined || s.at < oldest)
|
|
78
|
+
oldest = s.at;
|
|
79
|
+
}
|
|
80
|
+
if (oldest !== undefined)
|
|
81
|
+
freesAt = oldest + w.windowMs;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const row = record.buckets.find((b) => b.windowMs === w.windowMs);
|
|
85
|
+
if (row !== undefined && now < row.openedAt + w.windowMs) {
|
|
86
|
+
tokens = row.tokens;
|
|
87
|
+
freesAt = row.openedAt + w.windowMs;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const exhausted = tokens >= w.maxTokens;
|
|
91
|
+
const retryAfterMs = exhausted ? Math.max(1, freesAt === undefined ? w.windowMs : freesAt - now) : 0;
|
|
92
|
+
readings.push({ window: w, tokens, exhausted, retryAfterMs });
|
|
93
|
+
}
|
|
94
|
+
return readings;
|
|
95
|
+
}
|
|
96
|
+
export function usageRetryAfterMs(readings) {
|
|
97
|
+
let worst;
|
|
98
|
+
for (const r of readings) {
|
|
99
|
+
if (!r.exhausted)
|
|
100
|
+
continue;
|
|
101
|
+
if (worst === undefined || r.retryAfterMs > worst)
|
|
102
|
+
worst = r.retryAfterMs;
|
|
103
|
+
}
|
|
104
|
+
return worst;
|
|
105
|
+
}
|
|
106
|
+
export class InMemoryUsageWindowStore {
|
|
107
|
+
records = new Map();
|
|
108
|
+
async charge(key, tokens, at, windows) {
|
|
109
|
+
const prior = this.records.get(key) ?? EMPTY_USAGE_WINDOW_RECORD;
|
|
110
|
+
this.records.set(key, chargeUsageRecord(prior, tokens, at, windows));
|
|
111
|
+
}
|
|
112
|
+
async read(key, windows, now) {
|
|
113
|
+
return readUsageRecord(this.records.get(key) ?? EMPTY_USAGE_WINDOW_RECORD, windows, now);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -432,23 +432,6 @@ export class NodeExecutionEnv {
|
|
|
432
432
|
return;
|
|
433
433
|
}
|
|
434
434
|
closeSpoolFds();
|
|
435
|
-
if (options?.onSpawn && child.pid !== undefined) {
|
|
436
|
-
const spawnedPid = child.pid;
|
|
437
|
-
const spawnedChild = child;
|
|
438
|
-
try {
|
|
439
|
-
options.onSpawn({
|
|
440
|
-
pid: spawnedPid,
|
|
441
|
-
kill: () => {
|
|
442
|
-
abortKillInFlight = true;
|
|
443
|
-
killProcessTree(spawnedPid, {
|
|
444
|
-
stillRunning: () => spawnedChild.exitCode === null && spawnedChild.signalCode === null,
|
|
445
|
-
});
|
|
446
|
-
},
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
catch {
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
435
|
const timeoutMs = resolveExecTimeoutMs(options?.timeout);
|
|
453
436
|
timeoutRef.current =
|
|
454
437
|
timeoutMs === undefined
|
|
@@ -580,11 +563,6 @@ export class NodeExecutionEnv {
|
|
|
580
563
|
return false;
|
|
581
564
|
if (spoolPumpRef.current)
|
|
582
565
|
clearInterval(spoolPumpRef.current);
|
|
583
|
-
try {
|
|
584
|
-
options?.onDetachAdopted?.();
|
|
585
|
-
}
|
|
586
|
-
catch {
|
|
587
|
-
}
|
|
588
566
|
if (timeoutRef.current)
|
|
589
567
|
clearTimeout(timeoutRef.current);
|
|
590
568
|
const dso = stdoutTail.result();
|
|
@@ -17,11 +17,6 @@ export interface HarnessLoopRecovery {
|
|
|
17
17
|
detect: (message: AssistantMessage) => boolean;
|
|
18
18
|
maxContinues?: number;
|
|
19
19
|
};
|
|
20
|
-
walltimeCutoff?: {
|
|
21
|
-
detect: (message: AssistantMessage) => boolean;
|
|
22
|
-
maxContinues?: number;
|
|
23
|
-
hasLiveWriteoutWindow?: () => boolean;
|
|
24
|
-
};
|
|
25
20
|
promptTooLong?: {
|
|
26
21
|
recover: (attempt: number) => Promise<boolean>;
|
|
27
22
|
detect?: (message: AssistantMessage) => boolean;
|
|
@@ -54,9 +49,7 @@ export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate
|
|
|
54
49
|
private steeringQueueMode;
|
|
55
50
|
private maxOutputTokens?;
|
|
56
51
|
private maxOutputTokensPerCall?;
|
|
57
|
-
private callDeadlineMsPerCall?;
|
|
58
52
|
private stallTimeoutsPerCall?;
|
|
59
|
-
private toolDeadline?;
|
|
60
53
|
private loopTrace?;
|
|
61
54
|
private resilience?;
|
|
62
55
|
private maxToolConcurrency?;
|
|
@@ -185,9 +185,7 @@ export class AgentHarness {
|
|
|
185
185
|
steeringQueueMode;
|
|
186
186
|
maxOutputTokens;
|
|
187
187
|
maxOutputTokensPerCall;
|
|
188
|
-
callDeadlineMsPerCall;
|
|
189
188
|
stallTimeoutsPerCall;
|
|
190
|
-
toolDeadline;
|
|
191
189
|
loopTrace;
|
|
192
190
|
resilience;
|
|
193
191
|
maxToolConcurrency;
|
|
@@ -217,9 +215,7 @@ export class AgentHarness {
|
|
|
217
215
|
this.steeringQueueMode = options.steeringMode ?? "one-at-a-time";
|
|
218
216
|
this.maxOutputTokens = options.maxOutputTokens;
|
|
219
217
|
this.maxOutputTokensPerCall = options.maxOutputTokensPerCall;
|
|
220
|
-
this.callDeadlineMsPerCall = options.callDeadlineMsPerCall;
|
|
221
218
|
this.stallTimeoutsPerCall = options.stallTimeoutsPerCall;
|
|
222
|
-
this.toolDeadline = options.toolDeadline;
|
|
223
219
|
this.loopTrace = options.loopTrace;
|
|
224
220
|
this.resilience = options.resilience;
|
|
225
221
|
this.maxToolConcurrency = options.maxToolConcurrency;
|
|
@@ -407,7 +403,6 @@ export class AgentHarness {
|
|
|
407
403
|
},
|
|
408
404
|
reasoning: streamOptions?.reasoning,
|
|
409
405
|
maxTokens: streamOptions?.maxTokens,
|
|
410
|
-
callDeadlineMs: streamOptions?.callDeadlineMs,
|
|
411
406
|
stallTimeouts: streamOptions?.stallTimeouts,
|
|
412
407
|
maxTokensDynamic: streamOptions?.maxTokensDynamic,
|
|
413
408
|
staticReasoningCutDowngrade: streamOptions?.staticReasoningCutDowngrade,
|
|
@@ -442,12 +437,9 @@ export class AgentHarness {
|
|
|
442
437
|
return {
|
|
443
438
|
model: turnState.model,
|
|
444
439
|
reasoning: turnState.thinkingLevel,
|
|
445
|
-
liveThinkingLevel: () => self.thinkingLevel,
|
|
446
440
|
...(this.maxOutputTokens !== undefined ? { maxTokens: this.maxOutputTokens } : {}),
|
|
447
441
|
...(this.maxOutputTokensPerCall !== undefined ? { maxTokensPerCall: this.maxOutputTokensPerCall } : {}),
|
|
448
|
-
...(this.callDeadlineMsPerCall !== undefined ? { callDeadlineMsPerCall: this.callDeadlineMsPerCall } : {}),
|
|
449
442
|
...(this.stallTimeoutsPerCall !== undefined ? { stallTimeoutsPerCall: this.stallTimeoutsPerCall } : {}),
|
|
450
|
-
...(this.toolDeadline !== undefined ? { toolDeadline: this.toolDeadline } : {}),
|
|
451
443
|
...(this.resilience !== undefined ? { resilience: this.resilience } : {}),
|
|
452
444
|
...(this.maxToolConcurrency !== undefined ? { maxToolConcurrency: this.maxToolConcurrency } : {}),
|
|
453
445
|
...(this.streamingToolExecution === true && (this.getHandlers("tool_call")?.size ?? 0) === 0
|
|
@@ -511,9 +503,6 @@ export class AgentHarness {
|
|
|
511
503
|
get degenerateOutput() {
|
|
512
504
|
return self.loopRecovery?.degenerateOutput;
|
|
513
505
|
},
|
|
514
|
-
get walltimeCutoff() {
|
|
515
|
-
return self.loopRecovery?.walltimeCutoff;
|
|
516
|
-
},
|
|
517
506
|
get promptTooLong() {
|
|
518
507
|
const ptl = self.loopRecovery?.promptTooLong;
|
|
519
508
|
if (!ptl)
|
|
@@ -681,7 +670,7 @@ export class AgentHarness {
|
|
|
681
670
|
for (let i = newMessages.length - 1; i >= 0; i--) {
|
|
682
671
|
const message = newMessages[i];
|
|
683
672
|
if (message.role === "assistant") {
|
|
684
|
-
return mergeTruncatedOutputChain(newMessages, i
|
|
673
|
+
return mergeTruncatedOutputChain(newMessages, i);
|
|
685
674
|
}
|
|
686
675
|
}
|
|
687
676
|
throw new AgentHarnessError("invalid_state", "AgentHarness prompt completed without an assistant message");
|
|
@@ -931,54 +920,25 @@ function isEmptyFailureAssistant(message) {
|
|
|
931
920
|
return false;
|
|
932
921
|
return m.content.every((c) => c.type === "text" ? c.text.trim() === "" : c.type === "thinking" ? c.thinking.trim() === "" : false);
|
|
933
922
|
}
|
|
934
|
-
function mergeTruncatedOutputChain(messages, lastAssistantIndex
|
|
923
|
+
function mergeTruncatedOutputChain(messages, lastAssistantIndex) {
|
|
935
924
|
const last = messages[lastAssistantIndex];
|
|
936
925
|
const lastText = last.content.map((c) => (c.type === "text" ? c.text : "")).join("");
|
|
937
926
|
const parts = [];
|
|
938
927
|
let i = lastAssistantIndex;
|
|
939
928
|
while (i - 2 >= 0) {
|
|
940
929
|
const mid = messages[i - 1];
|
|
941
|
-
|
|
942
|
-
const walltimeGate = last.stopReason === "error" || downstreamText.trim() === "";
|
|
943
|
-
let head;
|
|
944
|
-
let walltimeStep = false;
|
|
945
|
-
let nextI = i - 2;
|
|
946
|
-
if (mid.role === "custom") {
|
|
947
|
-
const h = messages[i - 2];
|
|
948
|
-
if (h.role !== "assistant")
|
|
949
|
-
break;
|
|
950
|
-
const assistant = h;
|
|
951
|
-
const step = (mid.customType === "loop:truncated-output-continue" && assistant.stopReason === "length") ||
|
|
952
|
-
(mid.customType === "loop:midstream-partial-continue" && assistant.stopReason === "stop") ||
|
|
953
|
-
(mid.customType === "loop:walltime-cutoff-recovery" && assistant.stopReason === "error" && walltimeGate);
|
|
954
|
-
if (!step)
|
|
955
|
-
break;
|
|
956
|
-
head = assistant;
|
|
957
|
-
walltimeStep = mid.customType === "loop:walltime-cutoff-recovery";
|
|
958
|
-
}
|
|
959
|
-
else if (mid.role === "user" && isWalltimeCutoff) {
|
|
960
|
-
let j = i - 1;
|
|
961
|
-
while (j >= 0 && messages[j].role === "user")
|
|
962
|
-
j--;
|
|
963
|
-
if (j < 0 || j >= i - 1)
|
|
964
|
-
break;
|
|
965
|
-
const h = messages[j];
|
|
966
|
-
if (h.role !== "assistant")
|
|
967
|
-
break;
|
|
968
|
-
const assistant = h;
|
|
969
|
-
if (!(assistant.stopReason === "error" && isWalltimeCutoff(assistant) && walltimeGate))
|
|
970
|
-
break;
|
|
971
|
-
head = assistant;
|
|
972
|
-
walltimeStep = true;
|
|
973
|
-
nextI = j;
|
|
974
|
-
}
|
|
975
|
-
else {
|
|
930
|
+
if (mid.role !== "custom")
|
|
976
931
|
break;
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
932
|
+
const h = messages[i - 2];
|
|
933
|
+
if (h.role !== "assistant")
|
|
934
|
+
break;
|
|
935
|
+
const head = h;
|
|
936
|
+
const step = (mid.customType === "loop:truncated-output-continue" && head.stopReason === "length") ||
|
|
937
|
+
(mid.customType === "loop:midstream-partial-continue" && head.stopReason === "stop");
|
|
938
|
+
if (!step)
|
|
939
|
+
break;
|
|
940
|
+
parts.unshift(head.content.map((c) => (c.type === "text" ? c.text : "")).join(""));
|
|
941
|
+
i = i - 2;
|
|
982
942
|
}
|
|
983
943
|
if (parts.length === 0) {
|
|
984
944
|
return last;
|