@code-yeongyu/senpi 2026.8.21 → 2026.8.22
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 +48 -0
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +5 -4
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/extensions/builtin/permission-system/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/permission-system/index.js +7 -11
- package/dist/core/extensions/builtin/permission-system/index.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +2 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +79 -31
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +40 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/extension/session-manager.ts +8 -3
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-kernel.ts +15 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/stream-retry.d.ts +21 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/stream-retry.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/stream-retry.js +36 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/stream-retry.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/types.d.ts +10 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +6 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +166 -26
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/google.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
- package/dist/modes/interactive/split-trailing-assistant-text.d.ts +0 -7
- package/dist/modes/interactive/split-trailing-assistant-text.d.ts.map +0 -1
- package/dist/modes/interactive/split-trailing-assistant-text.js +0 -19
- package/dist/modes/interactive/split-trailing-assistant-text.js.map +0 -1
|
@@ -31,11 +31,18 @@ import { buildMcpStateResult, buildNeutralHookResult, buildPiBashError, buildPiB
|
|
|
31
31
|
import { AgentClientMessageSchema, AgentConversationTurnStructureSchema, AgentRunRequestSchema, AgentServerMessageSchema, AgentStoreConflictErrorSchema, AgentStoreConflictResultSchema, AssistantMessageSchema, BackgroundShellSpawnResultSchema, CanvasDiagnosticsErrorSchema, CanvasDiagnosticsResultSchema, ClientHeartbeatSchema, ComputerUseErrorSchema, ComputerUseResultSchema, ConversationActionSchema, ConversationSearchErrorSchema, ConversationSearchResultSchema, ConversationStateStructureSchema, ConversationStepSchema, ConversationTurnStructureSchema, DeleteErrorSchema, DeleteRejectedSchema, DeleteResultSchema, DeleteSuccessSchema, DiagnosticsErrorSchema, DiagnosticsRejectedSchema, DiagnosticsResultSchema, DiagnosticsSuccessSchema, ExecClientControlMessageSchema, ExecClientHeartbeatSchema, ExecClientMessageSchema, ExecClientStreamCloseSchema, ExecClientThrowSchema, FetchErrorSchema, FetchResultSchema, ForceBackgroundShellResultSchema, ForceBackgroundShellStatus, ForceBackgroundSubagentResultSchema, ForceBackgroundSubagentStatus, GetBlobResultSchema, GetUsableModelsRequestSchema, GetUsableModelsResponseSchema, GrepContentMatchSchema, GrepContentResultSchema, GrepCountResultSchema, GrepErrorSchema, GrepFileCountSchema, GrepFileMatchSchema, GrepFilesResultSchema, GrepResultSchema, GrepSuccessSchema, GrepUnionResultSchema, KvClientMessageSchema, ListMcpResourcesExecResultSchema, ListMcpResourcesSuccessSchema, LsDirectoryTreeNode_FileSchema, LsDirectoryTreeNodeSchema, LsErrorSchema, LsRejectedSchema, LsResultSchema, LsSuccessSchema, McpAllowlistPrecheckResultSchema, McpApprovedSchema, McpArgsSchema, McpErrorSchema, McpImageContentSchema, McpRejectedSchema, McpResultSchema, McpSuccessSchema, McpTextContentSchema, McpToolCallSchema, McpToolDefinitionSchema, McpToolErrorSchema, McpToolNotFoundSchema, McpToolResultContentItemSchema, McpToolResultSchema, ModelDetailsSchema, ReadErrorSchema, ReadMcpResourceExecResultSchema, ReadMcpResourceNotFoundSchema, ReadRejectedSchema, ReadResultSchema, ReadSuccessSchema, RecordScreenFailureSchema, RecordScreenResultSchema, RequestContextResultSchema, RequestContextSchema, RequestContextSuccessSchema, ResumeActionSchema, SelectedContextSchema, SelectedImageSchema, SetBlobResultSchema, ShellAllowlistPrecheckResultSchema, ShellFailureSchema, ShellRejectedSchema, ShellResultSchema, ShellStreamExitSchema, ShellStreamSchema, ShellStreamStartSchema, ShellStreamStderrSchema, ShellStreamStdoutSchema, ShellSuccessSchema, SmartModeClassifierErrorSchema, SmartModeClassifierResultSchema, SubagentAwaitNotFoundSchema, SubagentAwaitResultSchema, SubagentErrorSchema, SubagentResultSchema, ToolCallSchema, UserMessageActionSchema, UserMessageSchema, WebFetchAllowlistPrecheckResultSchema, WriteErrorSchema, WriteRejectedSchema, WriteResultSchema, WriteShellStdinErrorSchema, WriteShellStdinResultSchema, WriteSuccessSchema, } from "./cursor-agent/gen/agent_pb.js";
|
|
32
32
|
import { composeShellCommand, omitUndefinedArgs, piLimit, piLsPath, piReadArgs, piTimeout, } from "./cursor-agent/pi-args.js";
|
|
33
33
|
import { buildRequestedModel } from "./cursor-agent/reasoning-params.js";
|
|
34
|
+
import { CursorRetryableStreamError, cursorStreamRetryDelayMs, shouldRetryCursorStream, waitForCursorStreamRetry, } from "./cursor-agent/stream-retry.js";
|
|
34
35
|
import { CURSOR_CONVERSATION_POISONED_MESSAGE, createConversationRotationStore, isZeroTokenResourceExhausted, resolveConversationRotationPersistPath, } from "./cursor-conversation-rotation.js";
|
|
35
36
|
import { keepUsableCursorTaskArgs } from "./cursor-task-args.js";
|
|
36
37
|
export const CURSOR_API_URL = "https://api2.cursor.sh";
|
|
37
38
|
export const CURSOR_CLIENT_VERSION = "cli-2026.07.23-e383d2b";
|
|
38
39
|
const EXEC_HEARTBEAT_INTERVAL_MS = 3000;
|
|
40
|
+
/** Maximum inbound silence before a Cursor turn without turnEnded is failed. */
|
|
41
|
+
export const CURSOR_STREAM_HEALTH_FAIL_THRESHOLD_MS = 30_000;
|
|
42
|
+
/** @deprecated Heartbeats and checkpoints count as inbound liveness without a separate deadline. */
|
|
43
|
+
export const CURSOR_STREAM_HEALTH_HEARTBEAT_ONLY_THRESHOLD_MS = CURSOR_STREAM_HEALTH_FAIL_THRESHOLD_MS * 3;
|
|
44
|
+
/** Maximum time allowed to drain exec handlers after turnEnded. */
|
|
45
|
+
export const CURSOR_TURN_END_DRAIN_TIMEOUT_MS = 5000;
|
|
39
46
|
/**
|
|
40
47
|
* HTTP/1 connection-specific headers that HTTP/2 forbids. Node's
|
|
41
48
|
* `http2.request()` throws on these rather than dropping them.
|
|
@@ -221,8 +228,10 @@ export const stream = (model, context, options) => {
|
|
|
221
228
|
let h2Client = null;
|
|
222
229
|
let h2Request = null;
|
|
223
230
|
let heartbeatTimer = null;
|
|
231
|
+
let streamHealthTimer = null;
|
|
224
232
|
let h2Settled = false;
|
|
225
233
|
let sawTurnEnded = false;
|
|
234
|
+
let turnEndDrainTimedOut = false;
|
|
226
235
|
let endStreamError = null;
|
|
227
236
|
// Reachable from the catch: a stream that dies mid-turn must still close
|
|
228
237
|
// and pair the blocks it left open.
|
|
@@ -233,6 +242,10 @@ export const stream = (model, context, options) => {
|
|
|
233
242
|
if (h2Settled)
|
|
234
243
|
return;
|
|
235
244
|
h2Settled = true;
|
|
245
|
+
if (streamHealthTimer) {
|
|
246
|
+
clearTimeout(streamHealthTimer);
|
|
247
|
+
streamHealthTimer = null;
|
|
248
|
+
}
|
|
236
249
|
if (error !== undefined) {
|
|
237
250
|
rejectH2(error);
|
|
238
251
|
return;
|
|
@@ -252,19 +265,25 @@ export const stream = (model, context, options) => {
|
|
|
252
265
|
let baseConversationId;
|
|
253
266
|
let conversationId;
|
|
254
267
|
let usageState;
|
|
255
|
-
let
|
|
268
|
+
let retryAttempt = false;
|
|
256
269
|
let attempt = 0;
|
|
270
|
+
let streamRetries = 0;
|
|
271
|
+
let forceResumeAction = false;
|
|
272
|
+
let pinnedRequestedModel;
|
|
273
|
+
let pinnedModelDetails;
|
|
257
274
|
do {
|
|
258
|
-
|
|
275
|
+
retryAttempt = false;
|
|
259
276
|
attempt += 1;
|
|
260
277
|
h2Settled = false;
|
|
261
278
|
sawTurnEnded = false;
|
|
279
|
+
turnEndDrainTimedOut = false;
|
|
262
280
|
endStreamError = null;
|
|
263
281
|
openBlockState = undefined;
|
|
264
282
|
const h2Completion = new Promise((resolve, reject) => {
|
|
265
283
|
resolveH2 = resolve;
|
|
266
284
|
rejectH2 = reject;
|
|
267
285
|
});
|
|
286
|
+
let attemptSawCheckpoint = false;
|
|
268
287
|
try {
|
|
269
288
|
const apiKey = options?.apiKey;
|
|
270
289
|
if (!apiKey) {
|
|
@@ -275,11 +294,16 @@ export const stream = (model, context, options) => {
|
|
|
275
294
|
const blobStore = conversationBlobStores.get(conversationId) ?? new Map();
|
|
276
295
|
conversationBlobStores.set(conversationId, blobStore);
|
|
277
296
|
const cachedState = conversationStateCache.get(conversationId);
|
|
278
|
-
const { requestBytes, conversationState } = await buildGrpcRequest(model, context, options, {
|
|
297
|
+
const { requestBytes, conversationState, requestedModel, modelDetails } = await buildGrpcRequest(model, context, options, {
|
|
279
298
|
conversationId,
|
|
280
299
|
blobStore,
|
|
281
300
|
conversationState: cachedState,
|
|
301
|
+
forceResumeAction,
|
|
302
|
+
pinnedRequestedModel,
|
|
303
|
+
pinnedModelDetails,
|
|
282
304
|
});
|
|
305
|
+
pinnedRequestedModel ??= requestedModel;
|
|
306
|
+
pinnedModelDetails ??= modelDetails;
|
|
283
307
|
conversationStateCache.set(conversationId, conversationState);
|
|
284
308
|
const requestContextTools = buildMcpToolDefinitions(context.tools);
|
|
285
309
|
const baseUrl = model.baseUrl || CURSOR_API_URL;
|
|
@@ -300,9 +324,28 @@ export const stream = (model, context, options) => {
|
|
|
300
324
|
"x-cursor-client-type": "cli",
|
|
301
325
|
"x-request-id": randomUUID(),
|
|
302
326
|
};
|
|
303
|
-
|
|
304
|
-
h2Client
|
|
305
|
-
|
|
327
|
+
const attemptH2Client = http2.connect(baseUrl);
|
|
328
|
+
h2Client = attemptH2Client;
|
|
329
|
+
attemptH2Client.on("error", (error) => {
|
|
330
|
+
if (h2Client !== attemptH2Client)
|
|
331
|
+
return;
|
|
332
|
+
const mapped = mapH2TransportError(error, baseUrl);
|
|
333
|
+
settleH2(sawTurnEnded
|
|
334
|
+
? mapped
|
|
335
|
+
: new CursorRetryableStreamError(mapped instanceof Error ? mapped.message : String(mapped), "transport", { cause: mapped }));
|
|
336
|
+
});
|
|
337
|
+
attemptH2Client.on("goaway", () => {
|
|
338
|
+
if (h2Client === attemptH2Client && !h2Settled && !sawTurnEnded) {
|
|
339
|
+
settleH2(new CursorRetryableStreamError("Cursor HTTP/2 session received GOAWAY", "transport"));
|
|
340
|
+
h2Request?.close();
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
attemptH2Client.on("close", () => {
|
|
344
|
+
if (h2Client === attemptH2Client && !h2Settled && !sawTurnEnded && !endStreamError) {
|
|
345
|
+
settleH2(new CursorRetryableStreamError("Cursor HTTP/2 session closed", "transport"));
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
h2Request = attemptH2Client.request(requestHeaders);
|
|
306
349
|
if (attempt === 1) {
|
|
307
350
|
stream.push({ type: "start", partial: output });
|
|
308
351
|
}
|
|
@@ -337,8 +380,55 @@ export const stream = (model, context, options) => {
|
|
|
337
380
|
};
|
|
338
381
|
openBlockState = state;
|
|
339
382
|
const onConversationCheckpoint = (checkpoint) => {
|
|
383
|
+
attemptSawCheckpoint = true;
|
|
340
384
|
conversationStateCache.set(conversationId, checkpoint);
|
|
341
385
|
};
|
|
386
|
+
const healthFailThresholdMs = options?.streamHealthFailThresholdMs ?? CURSOR_STREAM_HEALTH_FAIL_THRESHOLD_MS;
|
|
387
|
+
let lastInboundFrameAt = Date.now();
|
|
388
|
+
let turnEndCompletionStarted = false;
|
|
389
|
+
const armStreamHealthTimer = () => {
|
|
390
|
+
if (streamHealthTimer)
|
|
391
|
+
clearTimeout(streamHealthTimer);
|
|
392
|
+
if (sawTurnEnded || h2Settled)
|
|
393
|
+
return;
|
|
394
|
+
const now = Date.now();
|
|
395
|
+
const deadline = lastInboundFrameAt + healthFailThresholdMs;
|
|
396
|
+
streamHealthTimer = setTimeout(() => {
|
|
397
|
+
streamHealthTimer = null;
|
|
398
|
+
if (sawTurnEnded || h2Settled)
|
|
399
|
+
return;
|
|
400
|
+
const stalledFor = Date.now() - lastInboundFrameAt;
|
|
401
|
+
if (stalledFor < healthFailThresholdMs) {
|
|
402
|
+
armStreamHealthTimer();
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
settleH2(new CursorRetryableStreamError("Cursor stream ended before turnEnded: inbound stream stalled", "stall"));
|
|
406
|
+
h2Request?.close();
|
|
407
|
+
}, Math.max(0, deadline - now));
|
|
408
|
+
};
|
|
409
|
+
const completeAfterTurnEnded = async () => {
|
|
410
|
+
const drainTimeoutMs = options?.turnEndDrainTimeoutMs ?? CURSOR_TURN_END_DRAIN_TIMEOUT_MS;
|
|
411
|
+
let timeout;
|
|
412
|
+
const drained = await Promise.race([
|
|
413
|
+
drainInFlightDispatches().then(() => true),
|
|
414
|
+
new Promise((resolve) => {
|
|
415
|
+
timeout = setTimeout(() => resolve(false), drainTimeoutMs);
|
|
416
|
+
}),
|
|
417
|
+
]);
|
|
418
|
+
if (timeout)
|
|
419
|
+
clearTimeout(timeout);
|
|
420
|
+
if (h2Settled)
|
|
421
|
+
return;
|
|
422
|
+
if (!drained) {
|
|
423
|
+
turnEndDrainTimedOut = true;
|
|
424
|
+
settleH2(new Error(`Cursor exec dispatches did not settle within ${drainTimeoutMs}ms after turnEnded`));
|
|
425
|
+
h2Request?.close();
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
h2Request?.close();
|
|
429
|
+
settleH2();
|
|
430
|
+
};
|
|
431
|
+
armStreamHealthTimer();
|
|
342
432
|
h2Request.on("data", (chunk) => {
|
|
343
433
|
// Steady state drains fully per chunk; alias the fresh h2 chunk
|
|
344
434
|
// instead of copying it through Buffer.concat.
|
|
@@ -360,8 +450,12 @@ export const stream = (model, context, options) => {
|
|
|
360
450
|
}
|
|
361
451
|
try {
|
|
362
452
|
const serverMessage = fromBinary(AgentServerMessageSchema, messageBytes);
|
|
363
|
-
const
|
|
364
|
-
serverMessage.message.value.message?.case
|
|
453
|
+
const interactionUpdateCase = serverMessage.message.case === "interactionUpdate"
|
|
454
|
+
? serverMessage.message.value.message?.case
|
|
455
|
+
: undefined;
|
|
456
|
+
const isTurnEnded = interactionUpdateCase === "turnEnded";
|
|
457
|
+
lastInboundFrameAt = Date.now();
|
|
458
|
+
armStreamHealthTimer();
|
|
365
459
|
// Dispatch is fire-and-forget so the socket keeps draining
|
|
366
460
|
// while a handler runs, but the promise is tracked: `done`
|
|
367
461
|
// must not be pushed while an exec handler is still resolving,
|
|
@@ -372,10 +466,13 @@ export const stream = (model, context, options) => {
|
|
|
372
466
|
});
|
|
373
467
|
inFlightDispatches.add(dispatch);
|
|
374
468
|
void dispatch.finally(() => inFlightDispatches.delete(dispatch));
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
|
|
469
|
+
// turnEnded is the definitive application completion signal. Drain
|
|
470
|
+
// every dispatch it follows, then close our side of the stream so a
|
|
471
|
+
// server that keeps HTTP/2 open cannot hold the turn hostage.
|
|
472
|
+
if (isTurnEnded && !turnEndCompletionStarted) {
|
|
378
473
|
sawTurnEnded = true;
|
|
474
|
+
turnEndCompletionStarted = true;
|
|
475
|
+
void completeAfterTurnEnded().catch((error) => settleH2(error));
|
|
379
476
|
}
|
|
380
477
|
}
|
|
381
478
|
catch (e) {
|
|
@@ -401,10 +498,17 @@ export const stream = (model, context, options) => {
|
|
|
401
498
|
}
|
|
402
499
|
});
|
|
403
500
|
h2Request.on("end", () => {
|
|
501
|
+
if (!sawTurnEnded && !endStreamError) {
|
|
502
|
+
settleH2(new CursorRetryableStreamError("Cursor stream ended before turnEnded", "clean-end"));
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
404
505
|
settleH2();
|
|
405
506
|
});
|
|
406
507
|
h2Request.on("error", (error) => {
|
|
407
|
-
|
|
508
|
+
const mapped = mapH2TransportError(error, baseUrl);
|
|
509
|
+
settleH2(sawTurnEnded
|
|
510
|
+
? mapped
|
|
511
|
+
: new CursorRetryableStreamError(mapped instanceof Error ? mapped.message : String(mapped), "transport", { cause: mapped }));
|
|
408
512
|
});
|
|
409
513
|
if (options?.signal) {
|
|
410
514
|
options.signal.addEventListener("abort", () => {
|
|
@@ -435,8 +539,43 @@ export const stream = (model, context, options) => {
|
|
|
435
539
|
// Same reason as the success path: a handler still running would land
|
|
436
540
|
// its real result after the turn finalized and be discarded — even
|
|
437
541
|
// though the tool may already have run side effects. On abort the
|
|
438
|
-
// drain returns immediately.
|
|
439
|
-
|
|
542
|
+
// drain returns immediately. A post-turn drain timeout is already the
|
|
543
|
+
// bound: do not wait forever a second time in the error path.
|
|
544
|
+
if (!turnEndDrainTimedOut)
|
|
545
|
+
await drainInFlightDispatches();
|
|
546
|
+
const shouldRetryStream = shouldRetryCursorStream({
|
|
547
|
+
error,
|
|
548
|
+
retries: streamRetries,
|
|
549
|
+
maxRetries: options?.streamStallMaxRetries ?? 10,
|
|
550
|
+
sawTurnEnded,
|
|
551
|
+
aborted: options?.signal?.aborted === true,
|
|
552
|
+
});
|
|
553
|
+
if (shouldRetryStream) {
|
|
554
|
+
if (openBlockState) {
|
|
555
|
+
// Resume responses continue from the server checkpoint. Close any
|
|
556
|
+
// locally open cards before resetting per-attempt bookkeeping; no
|
|
557
|
+
// speculative replay deduplication is attempted.
|
|
558
|
+
endCurrentTextBlock(output, stream, openBlockState);
|
|
559
|
+
endCurrentThinkingBlock(output, stream, openBlockState);
|
|
560
|
+
flushOpenToolCalls(output, stream, openBlockState);
|
|
561
|
+
}
|
|
562
|
+
forceResumeAction ||= attemptSawCheckpoint;
|
|
563
|
+
const retryDelayMs = cursorStreamRetryDelayMs({
|
|
564
|
+
attempt: streamRetries,
|
|
565
|
+
fixedDelayMs: options?.streamStallRetryDelayMs,
|
|
566
|
+
});
|
|
567
|
+
streamRetries += 1;
|
|
568
|
+
retryAttempt = true;
|
|
569
|
+
await waitForCursorStreamRetry(retryDelayMs, options?.signal);
|
|
570
|
+
if (options?.signal?.aborted) {
|
|
571
|
+
retryAttempt = false;
|
|
572
|
+
output.stopReason = "aborted";
|
|
573
|
+
output.errorMessage = "Request was aborted";
|
|
574
|
+
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
575
|
+
stream.end();
|
|
576
|
+
}
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
440
579
|
// A stream that dies mid-turn leaves blocks open. Closing them here
|
|
441
580
|
// settles their live cards and pairs the server-owned calls that
|
|
442
581
|
// nothing else answers — an unpaired call is stripped from every
|
|
@@ -485,14 +624,14 @@ export const stream = (model, context, options) => {
|
|
|
485
624
|
const blobs = conversationBlobStores.get(conversationId);
|
|
486
625
|
if (blobs)
|
|
487
626
|
conversationBlobStores.set(decision.wireId, blobs);
|
|
488
|
-
|
|
627
|
+
retryAttempt = true;
|
|
489
628
|
}
|
|
490
629
|
else {
|
|
491
630
|
message = CURSOR_CONVERSATION_POISONED_MESSAGE;
|
|
492
631
|
}
|
|
493
632
|
}
|
|
494
633
|
}
|
|
495
|
-
if (!
|
|
634
|
+
if (!retryAttempt) {
|
|
496
635
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
497
636
|
output.errorMessage = message;
|
|
498
637
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
@@ -509,7 +648,7 @@ export const stream = (model, context, options) => {
|
|
|
509
648
|
h2Request = null;
|
|
510
649
|
h2Client = null;
|
|
511
650
|
}
|
|
512
|
-
} while (
|
|
651
|
+
} while (retryAttempt);
|
|
513
652
|
})();
|
|
514
653
|
return stream;
|
|
515
654
|
};
|
|
@@ -3195,7 +3334,7 @@ async function buildGrpcRequest(model, context, options, state) {
|
|
|
3195
3334
|
}
|
|
3196
3335
|
}
|
|
3197
3336
|
const action = create(ConversationActionSchema, {
|
|
3198
|
-
action: userContent && (userText.trim().length > 0 || hasUserImages)
|
|
3337
|
+
action: !state.forceResumeAction && userContent && (userText.trim().length > 0 || hasUserImages)
|
|
3199
3338
|
? {
|
|
3200
3339
|
case: "userMessageAction",
|
|
3201
3340
|
value: create(UserMessageActionSchema, {
|
|
@@ -3244,15 +3383,16 @@ async function buildGrpcRequest(model, context, options, state) {
|
|
|
3244
3383
|
rootPromptMessagesJson,
|
|
3245
3384
|
turns,
|
|
3246
3385
|
});
|
|
3247
|
-
const requestedModel = buildRequestedModel(model, options?.thinkingSelection);
|
|
3386
|
+
const requestedModel = state.pinnedRequestedModel ?? buildRequestedModel(model, options?.thinkingSelection);
|
|
3248
3387
|
const wireModelId = requestedModel.modelId;
|
|
3249
3388
|
const cursorMaxMode = model.compat?.cursorMaxMode === true;
|
|
3250
|
-
const modelDetails =
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3389
|
+
const modelDetails = state.pinnedModelDetails ??
|
|
3390
|
+
create(ModelDetailsSchema, {
|
|
3391
|
+
modelId: wireModelId,
|
|
3392
|
+
displayModelId: model.id,
|
|
3393
|
+
displayName: model.name,
|
|
3394
|
+
...(cursorMaxMode ? { maxMode: true } : undefined),
|
|
3395
|
+
});
|
|
3256
3396
|
const runRequest = create(AgentRunRequestSchema, {
|
|
3257
3397
|
conversationState,
|
|
3258
3398
|
action,
|
|
@@ -3272,7 +3412,7 @@ async function buildGrpcRequest(model, context, options, state) {
|
|
|
3272
3412
|
bytes: requestBytes.length,
|
|
3273
3413
|
tools: context.tools?.length ?? 0,
|
|
3274
3414
|
});
|
|
3275
|
-
return { requestBytes, blobStore, conversationState };
|
|
3415
|
+
return { requestBytes, blobStore, conversationState, requestedModel, modelDetails };
|
|
3276
3416
|
}
|
|
3277
3417
|
function hasImages(content) {
|
|
3278
3418
|
return content.some((item) => item.type === "image");
|