@sayknow-cli/agent-core 0.3.16 → 0.4.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 +23 -0
- package/package.json +7 -8
- package/src/agent-loop.ts +826 -40
- package/src/agent.ts +260 -22
- package/src/compaction/compaction.ts +89 -95
- package/src/compaction/entries.ts +19 -0
- package/src/compaction/openai.ts +24 -30
- package/src/compaction/prompts/handoff-document.md +7 -0
- package/src/compaction/pruning.ts +144 -11
- package/src/proxy.ts +82 -2
- package/src/types.ts +106 -3
- package/dist/types/agent-loop.d.ts +0 -56
- package/dist/types/agent.d.ts +0 -403
- package/dist/types/append-only-context.d.ts +0 -137
- package/dist/types/compaction/branch-summarization.d.ts +0 -103
- package/dist/types/compaction/compaction.d.ts +0 -298
- package/dist/types/compaction/entries.d.ts +0 -109
- package/dist/types/compaction/errors.d.ts +0 -26
- package/dist/types/compaction/index.d.ts +0 -11
- package/dist/types/compaction/messages.d.ts +0 -61
- package/dist/types/compaction/openai.d.ts +0 -63
- package/dist/types/compaction/pruning.d.ts +0 -69
- package/dist/types/compaction/utils.d.ts +0 -32
- package/dist/types/compaction.d.ts +0 -1
- package/dist/types/harmony-leak.d.ts +0 -100
- package/dist/types/image-placeholder-guard.d.ts +0 -4
- package/dist/types/index.d.ts +0 -11
- package/dist/types/proxy.d.ts +0 -84
- package/dist/types/run-collector.d.ts +0 -196
- package/dist/types/telemetry.d.ts +0 -596
- package/dist/types/thinking.d.ts +0 -18
- package/dist/types/types.d.ts +0 -430
package/src/agent.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type ToolChoice,
|
|
21
21
|
type ToolResultMessage,
|
|
22
22
|
} from "@sayknow-cli/ai";
|
|
23
|
+
import { extractHttpStatusFromError } from "@sayknow-cli/utils";
|
|
23
24
|
import { agentLoop, agentLoopContinue } from "./agent-loop";
|
|
24
25
|
import type { AppendOnlyContextManager } from "./append-only-context";
|
|
25
26
|
import type { HarmonyAuditEvent } from "./harmony-leak";
|
|
@@ -32,6 +33,12 @@ import type {
|
|
|
32
33
|
AgentState,
|
|
33
34
|
AgentTool,
|
|
34
35
|
AgentToolContext,
|
|
36
|
+
ManagedAttemptContinuation,
|
|
37
|
+
ManagedAttemptContinuationOwnership,
|
|
38
|
+
ManagedAttemptDecision,
|
|
39
|
+
ManagedAttemptOutcome,
|
|
40
|
+
ManagedLogicalRunId,
|
|
41
|
+
RunTerminalRequest,
|
|
35
42
|
StreamFn,
|
|
36
43
|
ToolCallContext,
|
|
37
44
|
} from "./types";
|
|
@@ -88,6 +95,13 @@ function refreshToolChoiceForActiveTools(
|
|
|
88
95
|
return tools.some(tool => tool.name === toolName) ? toolChoice : undefined;
|
|
89
96
|
}
|
|
90
97
|
|
|
98
|
+
export class ManagedCursorInvariantError extends Error {
|
|
99
|
+
constructor(message: string = "Managed Cursor attempt received a provider-side tool result") {
|
|
100
|
+
super(message);
|
|
101
|
+
this.name = "ManagedCursorInvariantError";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
91
105
|
export class AgentBusyError extends Error {
|
|
92
106
|
constructor(
|
|
93
107
|
message: string = "Agent is already processing. Use steer() or followUp() to queue messages, or wait for completion.",
|
|
@@ -276,6 +290,16 @@ export interface AgentOptions {
|
|
|
276
290
|
|
|
277
291
|
export interface AgentPromptOptions {
|
|
278
292
|
toolChoice?: ToolChoice;
|
|
293
|
+
/** Disable transport replay; fallback accounting is owned by the caller. */
|
|
294
|
+
fallbackManaged?: boolean;
|
|
295
|
+
/** Called synchronously after this invocation claims the agent run, before asynchronous provider work. */
|
|
296
|
+
onRunAccepted?: () => void;
|
|
297
|
+
/** Called once immediately before every managed upstream request. */
|
|
298
|
+
nextFallbackAttempt?: AgentLoopConfig["nextFallbackAttempt"];
|
|
299
|
+
/** Called after a managed upstream request is accepted and committed. */
|
|
300
|
+
onManagedAttemptAccepted?: AgentLoopConfig["onManagedAttemptAccepted"];
|
|
301
|
+
/** Receives a discarded managed attempt without exposing assistant lifecycle events. */
|
|
302
|
+
onManagedAttemptOutcome?: AgentLoopConfig["onManagedAttemptOutcome"];
|
|
279
303
|
}
|
|
280
304
|
|
|
281
305
|
/** Buffered Cursor tool result with text position at time of call */
|
|
@@ -284,6 +308,11 @@ interface CursorToolResultEntry {
|
|
|
284
308
|
textLengthAtCall: number;
|
|
285
309
|
}
|
|
286
310
|
|
|
311
|
+
export type AgentQueueSnapshot = {
|
|
312
|
+
steering: AgentMessage[];
|
|
313
|
+
followUp: AgentMessage[];
|
|
314
|
+
};
|
|
315
|
+
|
|
287
316
|
export class Agent {
|
|
288
317
|
#state: AgentState = {
|
|
289
318
|
systemPrompt: [],
|
|
@@ -332,6 +361,8 @@ export class Agent {
|
|
|
332
361
|
#resolveRunningPrompt?: () => void;
|
|
333
362
|
#runSequence = 0;
|
|
334
363
|
#activeRunId?: number;
|
|
364
|
+
#continuationGeneration = 0;
|
|
365
|
+
#activeFallbackManaged = false;
|
|
335
366
|
#kimiApiFormat?: "openai" | "anthropic";
|
|
336
367
|
#preferWebsockets?: boolean;
|
|
337
368
|
#transformToolCallArguments?: (args: Record<string, unknown>, toolName: string) => Record<string, unknown>;
|
|
@@ -345,6 +376,7 @@ export class Agent {
|
|
|
345
376
|
#onHarmonyLeak?: (event: HarmonyAuditEvent) => void | Promise<void>;
|
|
346
377
|
#onBeforeYield?: () => Promise<void> | void;
|
|
347
378
|
#shouldPause?: AgentLoopConfig["shouldPause"];
|
|
379
|
+
#maintainContext?: AgentLoopConfig["maintainContext"];
|
|
348
380
|
#telemetry?: AgentLoopConfig["telemetry"];
|
|
349
381
|
#appendOnlyContext?: AppendOnlyContextManager;
|
|
350
382
|
|
|
@@ -354,6 +386,8 @@ export class Agent {
|
|
|
354
386
|
|
|
355
387
|
/** Buffered Cursor tool results with text length at time of call (for correct ordering) */
|
|
356
388
|
#cursorToolResultBuffer: CursorToolResultEntry[] = [];
|
|
389
|
+
#terminalizedLogicalRunIds = new Set<ManagedLogicalRunId>();
|
|
390
|
+
#managedLogicalRunOwner?: ManagedLogicalRunId;
|
|
357
391
|
|
|
358
392
|
streamFn: StreamFn;
|
|
359
393
|
getApiKey?: (provider: string) => Promise<string | undefined> | string | undefined;
|
|
@@ -680,6 +714,10 @@ export class Agent {
|
|
|
680
714
|
this.#shouldPause = fn;
|
|
681
715
|
}
|
|
682
716
|
|
|
717
|
+
setMaintainContext(fn: AgentLoopConfig["maintainContext"] | undefined): void {
|
|
718
|
+
this.#maintainContext = fn;
|
|
719
|
+
}
|
|
720
|
+
|
|
683
721
|
emitExternalEvent(event: AgentEvent) {
|
|
684
722
|
switch (event.type) {
|
|
685
723
|
case "message_start":
|
|
@@ -830,7 +868,7 @@ export class Agent {
|
|
|
830
868
|
this.#contextRevision++;
|
|
831
869
|
}
|
|
832
870
|
|
|
833
|
-
setModel(m: Model) {
|
|
871
|
+
setModel(m: Model | undefined) {
|
|
834
872
|
this.#state.model = m;
|
|
835
873
|
this.#contextRevision++;
|
|
836
874
|
}
|
|
@@ -976,6 +1014,20 @@ export class Agent {
|
|
|
976
1014
|
this.#followUpQueue = [...messages, ...this.#followUpQueue];
|
|
977
1015
|
}
|
|
978
1016
|
|
|
1017
|
+
/** Snapshot both executable queues as one atomic session-level view. */
|
|
1018
|
+
snapshotQueues(): AgentQueueSnapshot {
|
|
1019
|
+
return {
|
|
1020
|
+
steering: this.#steeringQueue.slice(),
|
|
1021
|
+
followUp: this.#followUpQueue.slice(),
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
/** Replace both executable queues with a prior snapshot. */
|
|
1026
|
+
restoreQueues(snapshot: AgentQueueSnapshot): void {
|
|
1027
|
+
this.#steeringQueue = snapshot.steering.slice();
|
|
1028
|
+
this.#followUpQueue = snapshot.followUp.slice();
|
|
1029
|
+
}
|
|
1030
|
+
|
|
979
1031
|
#dequeueSteeringMessages(): AgentMessage[] {
|
|
980
1032
|
if (this.#steeringMode === "one-at-a-time") {
|
|
981
1033
|
if (this.#steeringQueue.length > 0) {
|
|
@@ -1088,23 +1140,30 @@ export class Agent {
|
|
|
1088
1140
|
* #runLoop guards every state mutation with a run id.
|
|
1089
1141
|
*/
|
|
1090
1142
|
forceAbort(reason = "Force aborted"): boolean {
|
|
1091
|
-
const
|
|
1143
|
+
const runId = this.#activeRunId;
|
|
1144
|
+
const managedLogicalRunId = this.#managedLogicalRunOwner;
|
|
1145
|
+
const hadActiveRun = runId !== undefined && (this.#runningPrompt !== undefined || this.#state.isStreaming);
|
|
1092
1146
|
if (!hadActiveRun) return false;
|
|
1093
1147
|
|
|
1094
1148
|
this.#abortController?.abort(reason);
|
|
1095
|
-
this.#
|
|
1149
|
+
this.#continuationGeneration++;
|
|
1096
1150
|
this.#state.isStreaming = false;
|
|
1097
1151
|
this.#state.streamMessage = null;
|
|
1098
1152
|
this.#state.pendingToolCalls = new Set<string>();
|
|
1099
1153
|
this.#abortController = undefined;
|
|
1100
1154
|
this.#cursorToolResultBuffer = [];
|
|
1155
|
+
this.#managedLogicalRunOwner = undefined;
|
|
1101
1156
|
|
|
1102
1157
|
const resolve = this.#resolveRunningPrompt;
|
|
1103
1158
|
this.#runningPrompt = undefined;
|
|
1104
1159
|
this.#resolveRunningPrompt = undefined;
|
|
1160
|
+
this.#activeRunId = undefined;
|
|
1105
1161
|
resolve?.();
|
|
1106
|
-
|
|
1107
|
-
|
|
1162
|
+
if (this.#activeFallbackManaged) {
|
|
1163
|
+
this.requestRunTerminal(managedLogicalRunId ?? runId, { stopReason: "cancelled" });
|
|
1164
|
+
} else {
|
|
1165
|
+
this.#finalizeRun(runId, { type: "agent_end", messages: [] });
|
|
1166
|
+
}
|
|
1108
1167
|
return true;
|
|
1109
1168
|
}
|
|
1110
1169
|
|
|
@@ -1112,12 +1171,56 @@ export class Agent {
|
|
|
1112
1171
|
return this.#runningPrompt ?? Promise.resolve();
|
|
1113
1172
|
}
|
|
1114
1173
|
|
|
1174
|
+
/** The active per-attempt run identifier. */
|
|
1175
|
+
get activeRunId(): number | undefined {
|
|
1176
|
+
return this.#activeRunId;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/**
|
|
1180
|
+
* Stable identifier for the active managed logical run, shared by every retry
|
|
1181
|
+
* attempt. Pass this value to requestRunTerminal(); never retain activeRunId
|
|
1182
|
+
* for managed terminal completion.
|
|
1183
|
+
*/
|
|
1184
|
+
get currentManagedLogicalRunId(): ManagedLogicalRunId | undefined {
|
|
1185
|
+
return this.#managedLogicalRunOwner;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* Request terminal completion through the single logical-run keyed finalizer.
|
|
1190
|
+
*
|
|
1191
|
+
* For managed runs, logicalRunId must be currentManagedLogicalRunId from any
|
|
1192
|
+
* attempt in the retry chain. Non-managed runs use their activeRunId. Terminal
|
|
1193
|
+
* requests with messages emit a committed message_start/message_end lifecycle
|
|
1194
|
+
* for each diagnostic before agent_end. Requests without messages (such as
|
|
1195
|
+
* cancellation) emit only agent_end.
|
|
1196
|
+
*/
|
|
1197
|
+
requestRunTerminal(logicalRunId: ManagedLogicalRunId, request: RunTerminalRequest): boolean {
|
|
1198
|
+
if (this.#terminalizedLogicalRunIds.has(logicalRunId)) return false;
|
|
1199
|
+
this.#finalizeRun(
|
|
1200
|
+
logicalRunId,
|
|
1201
|
+
{
|
|
1202
|
+
type: "agent_end",
|
|
1203
|
+
messages: request.messages ?? [],
|
|
1204
|
+
...(request.stopReason === "cancelled" ? { stopReason: "cancelled" as const } : {}),
|
|
1205
|
+
},
|
|
1206
|
+
() => {
|
|
1207
|
+
for (const message of request.messages ?? []) {
|
|
1208
|
+
this.#emit({ type: "message_start", message });
|
|
1209
|
+
this.appendMessage(message);
|
|
1210
|
+
this.#emit({ type: "message_end", message });
|
|
1211
|
+
}
|
|
1212
|
+
},
|
|
1213
|
+
);
|
|
1214
|
+
return true;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1115
1217
|
reset() {
|
|
1116
1218
|
this.#state.messages = [];
|
|
1117
1219
|
this.#contextRevision++;
|
|
1118
1220
|
this.#state.isStreaming = false;
|
|
1119
1221
|
this.#state.streamMessage = null;
|
|
1120
1222
|
this.#state.pendingToolCalls = new Set<string>();
|
|
1223
|
+
this.#managedLogicalRunOwner = undefined;
|
|
1121
1224
|
this.#state.error = undefined;
|
|
1122
1225
|
this.#steeringQueue = [];
|
|
1123
1226
|
this.#followUpQueue = [];
|
|
@@ -1170,6 +1273,10 @@ export class Agent {
|
|
|
1170
1273
|
}
|
|
1171
1274
|
|
|
1172
1275
|
assertUserImagePlaceholdersHavePayload(msgs);
|
|
1276
|
+
if (this.#managedLogicalRunOwner !== undefined) {
|
|
1277
|
+
this.requestRunTerminal(this.#managedLogicalRunOwner, { stopReason: "cancelled" });
|
|
1278
|
+
this.#managedLogicalRunOwner = undefined;
|
|
1279
|
+
}
|
|
1173
1280
|
|
|
1174
1281
|
await this.#runLoop(msgs, promptOptions);
|
|
1175
1282
|
}
|
|
@@ -1177,7 +1284,7 @@ export class Agent {
|
|
|
1177
1284
|
/**
|
|
1178
1285
|
* Continue from current context (used for retries and resuming queued messages).
|
|
1179
1286
|
*/
|
|
1180
|
-
async continue() {
|
|
1287
|
+
async continue(options?: AgentPromptOptions) {
|
|
1181
1288
|
if (this.#state.isStreaming) {
|
|
1182
1289
|
throw new AgentBusyError();
|
|
1183
1290
|
}
|
|
@@ -1189,13 +1296,13 @@ export class Agent {
|
|
|
1189
1296
|
if (messages[messages.length - 1].role === "assistant") {
|
|
1190
1297
|
const queuedSteering = this.#dequeueSteeringMessages();
|
|
1191
1298
|
if (queuedSteering.length > 0) {
|
|
1192
|
-
await this.#runLoop(queuedSteering, { skipInitialSteeringPoll: true });
|
|
1299
|
+
await this.#runLoop(queuedSteering, { ...options, skipInitialSteeringPoll: true });
|
|
1193
1300
|
return;
|
|
1194
1301
|
}
|
|
1195
1302
|
|
|
1196
1303
|
const queuedFollowUp = this.#dequeueFollowUpMessages();
|
|
1197
1304
|
if (queuedFollowUp.length > 0) {
|
|
1198
|
-
await this.#runLoop(queuedFollowUp);
|
|
1305
|
+
await this.#runLoop(queuedFollowUp, options);
|
|
1199
1306
|
return;
|
|
1200
1307
|
}
|
|
1201
1308
|
|
|
@@ -1206,7 +1313,7 @@ export class Agent {
|
|
|
1206
1313
|
throw new Error("No messages to continue from");
|
|
1207
1314
|
}
|
|
1208
1315
|
|
|
1209
|
-
await this.#runLoop(undefined);
|
|
1316
|
+
await this.#runLoop(undefined, options);
|
|
1210
1317
|
}
|
|
1211
1318
|
|
|
1212
1319
|
/**
|
|
@@ -1225,18 +1332,41 @@ export class Agent {
|
|
|
1225
1332
|
this.#resolveRunningPrompt = resolve;
|
|
1226
1333
|
|
|
1227
1334
|
const runId = ++this.#runSequence;
|
|
1335
|
+
const continuationGeneration = ++this.#continuationGeneration;
|
|
1228
1336
|
this.#activeRunId = runId;
|
|
1229
1337
|
const abortController = new AbortController();
|
|
1230
1338
|
this.#abortController = abortController;
|
|
1231
1339
|
this.#state.isStreaming = true;
|
|
1232
1340
|
this.#state.streamMessage = null;
|
|
1233
1341
|
this.#state.error = undefined;
|
|
1234
|
-
|
|
1235
|
-
|
|
1342
|
+
options?.onRunAccepted?.();
|
|
1343
|
+
|
|
1344
|
+
const fallbackManaged = options?.fallbackManaged === true;
|
|
1345
|
+
const managedLogicalRunOwner = fallbackManaged ? (this.#managedLogicalRunOwner ?? runId) : undefined;
|
|
1346
|
+
const startsManagedLogicalRun = fallbackManaged && this.#managedLogicalRunOwner === undefined;
|
|
1347
|
+
if (startsManagedLogicalRun) {
|
|
1348
|
+
this.#managedLogicalRunOwner = managedLogicalRunOwner;
|
|
1349
|
+
this.#emit({ type: "agent_start" });
|
|
1350
|
+
}
|
|
1351
|
+
if (fallbackManaged && this.#cursorToolResultBuffer.length > 0) {
|
|
1352
|
+
const error = new ManagedCursorInvariantError(
|
|
1353
|
+
"Managed Cursor attempt started with buffered provider-side tool results",
|
|
1354
|
+
);
|
|
1355
|
+
this.#state.isStreaming = false;
|
|
1356
|
+
this.#abortController = undefined;
|
|
1357
|
+
this.#activeRunId = undefined;
|
|
1358
|
+
this.#runningPrompt = undefined;
|
|
1359
|
+
this.#resolveRunningPrompt = undefined;
|
|
1360
|
+
resolve();
|
|
1361
|
+
this.requestRunTerminal(managedLogicalRunOwner ?? runId, { stopReason: "error" });
|
|
1362
|
+
this.#managedLogicalRunOwner = undefined;
|
|
1363
|
+
throw error;
|
|
1364
|
+
}
|
|
1365
|
+
// Each run gets a fresh buffer only after managed stale-state validation.
|
|
1236
1366
|
this.#cursorToolResultBuffer = [];
|
|
1367
|
+
this.#activeFallbackManaged = fallbackManaged;
|
|
1237
1368
|
|
|
1238
1369
|
const reasoning = this.#state.thinkingLevel;
|
|
1239
|
-
|
|
1240
1370
|
const context: AgentContext = {
|
|
1241
1371
|
systemPrompt: this.#state.systemPrompt,
|
|
1242
1372
|
messages: this.#state.messages.slice(),
|
|
@@ -1244,7 +1374,7 @@ export class Agent {
|
|
|
1244
1374
|
};
|
|
1245
1375
|
|
|
1246
1376
|
const cursorOnToolResult =
|
|
1247
|
-
this.#cursorExecHandlers || this.#cursorOnToolResult
|
|
1377
|
+
!fallbackManaged && (this.#cursorExecHandlers || this.#cursorOnToolResult)
|
|
1248
1378
|
? async (message: ToolResultMessage) => {
|
|
1249
1379
|
let finalMessage = message;
|
|
1250
1380
|
if (this.#activeRunId !== runId) {
|
|
@@ -1261,7 +1391,6 @@ export class Agent {
|
|
|
1261
1391
|
}
|
|
1262
1392
|
} catch {}
|
|
1263
1393
|
}
|
|
1264
|
-
// Buffer tool result with current text length for correct ordering later.
|
|
1265
1394
|
// Cursor executes tools server-side during streaming, so the assistant message
|
|
1266
1395
|
// already incorporates results. We buffer here and emit in correct order
|
|
1267
1396
|
// when the assistant message ends.
|
|
@@ -1273,7 +1402,10 @@ export class Agent {
|
|
|
1273
1402
|
|
|
1274
1403
|
const getToolChoice = () =>
|
|
1275
1404
|
this.#getToolChoice?.() ?? refreshToolChoiceForActiveTools(options?.toolChoice, this.#state.tools);
|
|
1276
|
-
const cursorExecHandlers = this.#cursorExecHandlersForRun(runId);
|
|
1405
|
+
const cursorExecHandlers = fallbackManaged ? undefined : this.#cursorExecHandlersForRun(runId);
|
|
1406
|
+
let managedDecision: ManagedAttemptDecision | undefined;
|
|
1407
|
+
let managedOutcome: ManagedAttemptOutcome | undefined;
|
|
1408
|
+
let maintenanceInterrupted = false;
|
|
1277
1409
|
|
|
1278
1410
|
const config: AgentLoopConfig = {
|
|
1279
1411
|
model,
|
|
@@ -1296,6 +1428,21 @@ export class Agent {
|
|
|
1296
1428
|
maxRetryDelayMs: this.#maxRetryDelayMs,
|
|
1297
1429
|
requestMaxRetries: this.#requestMaxRetries,
|
|
1298
1430
|
streamMaxRetries: this.#streamMaxRetries,
|
|
1431
|
+
...(fallbackManaged
|
|
1432
|
+
? {
|
|
1433
|
+
fallbackManaged: true,
|
|
1434
|
+
nextFallbackAttempt: options?.nextFallbackAttempt,
|
|
1435
|
+
onManagedAttemptAccepted: options?.onManagedAttemptAccepted,
|
|
1436
|
+
onManagedAttemptOutcome: async outcome => {
|
|
1437
|
+
managedOutcome = outcome;
|
|
1438
|
+
managedDecision = (await options?.onManagedAttemptOutcome?.(outcome)) ?? {
|
|
1439
|
+
type: "terminal",
|
|
1440
|
+
terminal: { stopReason: outcome.type === "run_terminal" ? outcome.reason : "error" },
|
|
1441
|
+
};
|
|
1442
|
+
return managedDecision;
|
|
1443
|
+
},
|
|
1444
|
+
}
|
|
1445
|
+
: {}),
|
|
1299
1446
|
kimiApiFormat: this.#kimiApiFormat,
|
|
1300
1447
|
preferWebsockets: this.#preferWebsockets,
|
|
1301
1448
|
convertToLlm: this.#convertToLlm,
|
|
@@ -1314,8 +1461,8 @@ export class Agent {
|
|
|
1314
1461
|
context.systemPrompt = this.#state.systemPrompt;
|
|
1315
1462
|
context.tools = this.#state.tools;
|
|
1316
1463
|
},
|
|
1317
|
-
cursorExecHandlers,
|
|
1318
|
-
cursorOnToolResult,
|
|
1464
|
+
...(cursorExecHandlers ? { cursorExecHandlers } : {}),
|
|
1465
|
+
...(cursorOnToolResult ? { cursorOnToolResult } : {}),
|
|
1319
1466
|
transformToolCallArguments: this.#transformToolCallArguments,
|
|
1320
1467
|
intentTracing: this.#intentTracing,
|
|
1321
1468
|
appendOnlyContext: this.#appendOnlyContext,
|
|
@@ -1384,6 +1531,12 @@ export class Agent {
|
|
|
1384
1531
|
if (this.#activeRunId !== runId) return false;
|
|
1385
1532
|
return this.#shouldPause?.() === true;
|
|
1386
1533
|
},
|
|
1534
|
+
maintainContext: this.#maintainContext
|
|
1535
|
+
? async (context, lifecycle) => {
|
|
1536
|
+
if (this.#activeRunId !== runId) return "not-needed";
|
|
1537
|
+
return (await this.#maintainContext?.(context, lifecycle)) ?? "not-needed";
|
|
1538
|
+
}
|
|
1539
|
+
: undefined,
|
|
1387
1540
|
telemetry: this.#telemetry,
|
|
1388
1541
|
};
|
|
1389
1542
|
|
|
@@ -1391,8 +1544,8 @@ export class Agent {
|
|
|
1391
1544
|
|
|
1392
1545
|
try {
|
|
1393
1546
|
const stream = messages
|
|
1394
|
-
? agentLoop(messages, context, config, abortController.signal, this.streamFn)
|
|
1395
|
-
: agentLoopContinue(context, config, abortController.signal, this.streamFn);
|
|
1547
|
+
? agentLoop(messages, context, config, abortController.signal, this.streamFn, !fallbackManaged)
|
|
1548
|
+
: agentLoopContinue(context, config, abortController.signal, this.streamFn, !fallbackManaged);
|
|
1396
1549
|
|
|
1397
1550
|
for await (const event of stream) {
|
|
1398
1551
|
if (this.#activeRunId !== runId) {
|
|
@@ -1412,6 +1565,9 @@ export class Agent {
|
|
|
1412
1565
|
break;
|
|
1413
1566
|
|
|
1414
1567
|
case "message_end":
|
|
1568
|
+
if (fallbackManaged && this.#cursorToolResultBuffer.length > 0) {
|
|
1569
|
+
throw new ManagedCursorInvariantError();
|
|
1570
|
+
}
|
|
1415
1571
|
partial = null;
|
|
1416
1572
|
// Check if this is an assistant message with buffered Cursor tool results.
|
|
1417
1573
|
// If so, split the message to emit tool results at the correct position.
|
|
@@ -1444,9 +1600,18 @@ export class Agent {
|
|
|
1444
1600
|
break;
|
|
1445
1601
|
|
|
1446
1602
|
case "agent_end":
|
|
1603
|
+
if (fallbackManaged && managedOutcome) {
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1447
1606
|
this.#state.isStreaming = false;
|
|
1448
1607
|
this.#state.streamMessage = null;
|
|
1449
|
-
|
|
1608
|
+
if (event.stopReason === "maintenance") {
|
|
1609
|
+
maintenanceInterrupted = true;
|
|
1610
|
+
this.#emit(event);
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
this.#finalizeRun(managedLogicalRunOwner ?? runId, event);
|
|
1614
|
+
continue;
|
|
1450
1615
|
}
|
|
1451
1616
|
|
|
1452
1617
|
// Emit to listeners
|
|
@@ -1456,6 +1621,15 @@ export class Agent {
|
|
|
1456
1621
|
if (this.#activeRunId !== runId) {
|
|
1457
1622
|
return;
|
|
1458
1623
|
}
|
|
1624
|
+
if (managedOutcome) {
|
|
1625
|
+
if (managedDecision?.type === "terminal") {
|
|
1626
|
+
this.requestRunTerminal(managedLogicalRunOwner ?? runId, managedDecision.terminal);
|
|
1627
|
+
} else if (managedOutcome.type === "run_terminal") {
|
|
1628
|
+
this.requestRunTerminal(managedLogicalRunOwner ?? runId, { stopReason: managedOutcome.reason });
|
|
1629
|
+
} else if (managedDecision?.type !== "retry" && managedDecision?.type !== "maintenance") {
|
|
1630
|
+
this.#finalizeRun(managedLogicalRunOwner ?? runId);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1459
1633
|
|
|
1460
1634
|
// Handle any remaining partial message
|
|
1461
1635
|
if (partial && partial.role === "assistant" && Array.isArray(partial.content) && partial.content.length > 0) {
|
|
@@ -1494,23 +1668,73 @@ export class Agent {
|
|
|
1494
1668
|
},
|
|
1495
1669
|
stopReason: abortController.signal.aborted ? "aborted" : "error",
|
|
1496
1670
|
errorMessage: err?.message || String(err),
|
|
1671
|
+
errorStatus: extractHttpStatusFromError({ status: err?.errorStatus }) ?? extractHttpStatusFromError(err),
|
|
1497
1672
|
timestamp: Date.now(),
|
|
1498
1673
|
} as AgentMessage;
|
|
1499
1674
|
|
|
1500
|
-
this.appendMessage(errorMsg);
|
|
1501
1675
|
this.#state.error = err?.message || String(err);
|
|
1502
|
-
this
|
|
1676
|
+
this.requestRunTerminal(managedLogicalRunOwner ?? runId, {
|
|
1677
|
+
stopReason: abortController.signal.aborted ? "cancelled" : "error",
|
|
1678
|
+
messages: [errorMsg],
|
|
1679
|
+
});
|
|
1503
1680
|
} finally {
|
|
1681
|
+
let continuation: ManagedAttemptContinuation | undefined;
|
|
1682
|
+
if (
|
|
1683
|
+
managedOutcome?.type !== "run_terminal" &&
|
|
1684
|
+
(managedDecision?.type === "retry" || managedDecision?.type === "maintenance")
|
|
1685
|
+
) {
|
|
1686
|
+
continuation = managedDecision.continuation;
|
|
1687
|
+
}
|
|
1688
|
+
const ownership: ManagedAttemptContinuationOwnership = {
|
|
1689
|
+
runId,
|
|
1690
|
+
logicalRunId: managedLogicalRunOwner ?? runId,
|
|
1691
|
+
generation: continuationGeneration,
|
|
1692
|
+
isCurrent: () => this.#continuationGeneration === continuationGeneration && this.#activeRunId === undefined,
|
|
1693
|
+
};
|
|
1504
1694
|
if (this.#activeRunId === runId) {
|
|
1505
1695
|
this.#state.isStreaming = false;
|
|
1506
1696
|
this.#state.streamMessage = null;
|
|
1507
1697
|
this.#state.pendingToolCalls = new Set<string>();
|
|
1508
1698
|
this.#abortController = undefined;
|
|
1509
1699
|
this.#activeRunId = undefined;
|
|
1700
|
+
this.#activeFallbackManaged = false;
|
|
1510
1701
|
this.#resolveRunningPrompt?.();
|
|
1511
1702
|
this.#runningPrompt = undefined;
|
|
1512
1703
|
this.#resolveRunningPrompt = undefined;
|
|
1513
1704
|
}
|
|
1705
|
+
if (
|
|
1706
|
+
fallbackManaged &&
|
|
1707
|
+
!continuation &&
|
|
1708
|
+
!maintenanceInterrupted &&
|
|
1709
|
+
this.#managedLogicalRunOwner === managedLogicalRunOwner
|
|
1710
|
+
) {
|
|
1711
|
+
this.#managedLogicalRunOwner = undefined;
|
|
1712
|
+
}
|
|
1713
|
+
if (continuation && ownership.isCurrent()) {
|
|
1714
|
+
try {
|
|
1715
|
+
await continuation(ownership);
|
|
1716
|
+
if (
|
|
1717
|
+
managedDecision?.type === "maintenance" &&
|
|
1718
|
+
this.#terminalizedLogicalRunIds.has(managedLogicalRunOwner ?? runId) &&
|
|
1719
|
+
this.#managedLogicalRunOwner === managedLogicalRunOwner
|
|
1720
|
+
) {
|
|
1721
|
+
this.#managedLogicalRunOwner = undefined;
|
|
1722
|
+
}
|
|
1723
|
+
if (
|
|
1724
|
+
managedDecision?.type !== "maintenance" &&
|
|
1725
|
+
this.#activeRunId === undefined &&
|
|
1726
|
+
this.#managedLogicalRunOwner === managedLogicalRunOwner
|
|
1727
|
+
) {
|
|
1728
|
+
this.#managedLogicalRunOwner = undefined;
|
|
1729
|
+
}
|
|
1730
|
+
} catch (err) {
|
|
1731
|
+
if (ownership.isCurrent()) {
|
|
1732
|
+
this.#state.error = err instanceof Error ? err.message : String(err);
|
|
1733
|
+
this.requestRunTerminal(managedLogicalRunOwner ?? runId, { stopReason: "error" });
|
|
1734
|
+
if (this.#managedLogicalRunOwner === managedLogicalRunOwner) this.#managedLogicalRunOwner = undefined;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1514
1738
|
}
|
|
1515
1739
|
}
|
|
1516
1740
|
|
|
@@ -1521,6 +1745,20 @@ export class Agent {
|
|
|
1521
1745
|
}
|
|
1522
1746
|
|
|
1523
1747
|
/** Calculate total text length from an assistant message's content blocks */
|
|
1748
|
+
#finalizeRun(
|
|
1749
|
+
logicalRunId: ManagedLogicalRunId,
|
|
1750
|
+
event?: Extract<AgentEvent, { type: "agent_end" }>,
|
|
1751
|
+
beforeEvent?: () => void,
|
|
1752
|
+
): void {
|
|
1753
|
+
if (this.#terminalizedLogicalRunIds.has(logicalRunId)) return;
|
|
1754
|
+
this.#terminalizedLogicalRunIds.add(logicalRunId);
|
|
1755
|
+
if (this.#terminalizedLogicalRunIds.size > 256) {
|
|
1756
|
+
this.#terminalizedLogicalRunIds.delete(this.#terminalizedLogicalRunIds.values().next().value!);
|
|
1757
|
+
}
|
|
1758
|
+
beforeEvent?.();
|
|
1759
|
+
if (event) this.#emit(event);
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1524
1762
|
#getAssistantTextLength(message: AgentMessage | null): number {
|
|
1525
1763
|
if (message?.role !== "assistant" || !Array.isArray(message.content)) {
|
|
1526
1764
|
return 0;
|