grok-dev 1.0.0-rc2 → 1.0.0-rc3

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.
Files changed (39) hide show
  1. package/.cursor/rules/project-overview.mdc +2 -2
  2. package/README.md +12 -3
  3. package/dist/agent/agent.d.ts +42 -3
  4. package/dist/agent/agent.js +276 -137
  5. package/dist/agent/agent.js.map +1 -1
  6. package/dist/agent/compaction.d.ts +35 -0
  7. package/dist/agent/compaction.js +364 -0
  8. package/dist/agent/compaction.js.map +1 -0
  9. package/dist/agent/compaction.test.d.ts +1 -0
  10. package/dist/agent/compaction.test.js.map +1 -0
  11. package/dist/headless/output.d.ts +67 -0
  12. package/dist/headless/output.js +176 -0
  13. package/dist/headless/output.js.map +1 -0
  14. package/dist/headless/output.test.d.ts +1 -0
  15. package/dist/headless/output.test.js.map +1 -0
  16. package/dist/index.js +38 -33
  17. package/dist/index.js.map +1 -1
  18. package/dist/mcp/parse-headers.test.d.ts +1 -0
  19. package/dist/mcp/parse-headers.test.js.map +1 -0
  20. package/dist/storage/index.d.ts +2 -1
  21. package/dist/storage/index.js +2 -1
  22. package/dist/storage/index.js.map +1 -1
  23. package/dist/storage/migrations.js +31 -1
  24. package/dist/storage/migrations.js.map +1 -1
  25. package/dist/storage/transcript-view.d.ts +14 -0
  26. package/dist/storage/transcript-view.js +23 -0
  27. package/dist/storage/transcript-view.js.map +1 -0
  28. package/dist/storage/transcript.d.ts +8 -2
  29. package/dist/storage/transcript.js +72 -18
  30. package/dist/storage/transcript.js.map +1 -1
  31. package/dist/telegram/limits.test.d.ts +1 -0
  32. package/dist/telegram/limits.test.js.map +1 -0
  33. package/dist/ui/app.js +39 -10
  34. package/dist/ui/app.js.map +1 -1
  35. package/dist/utils/instructions.js +0 -8
  36. package/dist/utils/instructions.js.map +1 -1
  37. package/dist/utils/instructions.test.d.ts +1 -0
  38. package/dist/utils/instructions.test.js.map +1 -0
  39. package/package.json +5 -2
@@ -37,7 +37,7 @@ src/
37
37
  ├── utils/
38
38
  │ ├── settings.ts # User and project settings
39
39
  │ ├── git-root.ts # Resolve git repository root for AGENTS.md discovery
40
- │ └── instructions.ts # AGENTS.md (ecosystem) + .grok/GROK.md
40
+ │ └── instructions.ts # AGENTS.md (ecosystem) custom instructions
41
41
  └── types/
42
42
  └── index.ts # Shared TypeScript types
43
43
  ```
@@ -48,7 +48,7 @@ src/
48
48
  - **Bash-only tools**: The agent uses bash for everything (file editing, searching, git, builds, etc.).
49
49
  - **X Search & Web Search**: Integrated via the xAI Responses API for real-time information.
50
50
  - **Settings hierarchy**: Environment variables → User-level (`~/.grok/user-settings.json`) → Project-level (`.grok/settings.json`).
51
- - **Custom instructions**: `~/.grok/AGENTS.md`, then `AGENTS.override.md` / `AGENTS.md` per directory from git root through the workspace cwd (Codex-style merge), then `.grok/GROK.md` or `~/.grok/GROK.md` last.
51
+ - **Custom instructions**: `~/.grok/AGENTS.md`, then `AGENTS.override.md` / `AGENTS.md` per directory from git root through the workspace cwd (Codex-style merge).
52
52
  - **ESM only**: The project uses `"type": "module"` — all imports use `.js` extensions for compiled output.
53
53
 
54
54
  ## Latest Grok Models
package/README.md CHANGED
@@ -46,6 +46,7 @@ grok -d /path/to/your/repo
46
46
  grok --prompt "run the test suite and summarize failures"
47
47
  grok -p "show me package.json" --directory /path/to/project
48
48
  grok --prompt "refactor X" --max-tool-rounds 30
49
+ grok --prompt "summarize the repo state" --format json
49
50
  ```
50
51
 
51
52
  **Continue a saved session:**
@@ -57,6 +58,15 @@ grok -s <session-id>
57
58
 
58
59
  Works in interactive mode too—same flag.
59
60
 
61
+ **Structured headless output:**
62
+
63
+ ```bash
64
+ grok --prompt "summarize the repo state" --format json
65
+ ```
66
+
67
+ `--format json` emits newline-delimited `StreamChunk` objects instead of the
68
+ default human-readable text stream.
69
+
60
70
  **List Grok models and pricing hints:**
61
71
 
62
72
  ```bash
@@ -136,13 +146,12 @@ Treat the bot token like a password.
136
146
  ## Instructions & project brain
137
147
 
138
148
  - **`AGENTS.md`** — merged from git root down to your cwd (Codex-style; see repo docs). **`AGENTS.override.md`** wins per directory when present.
139
- - **`.grok/GROK.md`** or **`~/.grok/GROK.md`** — Grok-only rules, applied last.
140
149
 
141
150
  ---
142
151
 
143
- ## MCP & project settings
152
+ ## Project settings
144
153
 
145
- Project file: **`.grok/settings.json`** — e.g. current model, **`mcpServers`** for stdio/http/sse MCP servers. Manage interactively with **`/mcps`** in the TUI.
154
+ Project file: **`.grok/settings.json`** — e.g. the current model for this project.
146
155
 
147
156
  ---
148
157
 
@@ -1,8 +1,44 @@
1
- import type { AgentMode, ChatEntry, SessionInfo, SessionSnapshot, StreamChunk, SubagentStatus, TaskRequest, ToolResult } from "../types/index";
1
+ import type { AgentMode, ChatEntry, SessionInfo, SessionSnapshot, StreamChunk, SubagentStatus, TaskRequest, ToolCall, ToolResult } from "../types/index";
2
2
  interface AgentOptions {
3
3
  persistSession?: boolean;
4
4
  session?: string;
5
5
  }
6
+ type ProcessMessageFinishReason = "stop" | "length" | "content-filter" | "tool-calls" | "error" | "other";
7
+ export interface ProcessMessageUsage {
8
+ inputTokens?: number;
9
+ outputTokens?: number;
10
+ totalTokens?: number;
11
+ }
12
+ export interface ProcessMessageStepStart {
13
+ stepNumber: number;
14
+ timestamp: number;
15
+ }
16
+ export interface ProcessMessageStepFinish {
17
+ stepNumber: number;
18
+ timestamp: number;
19
+ finishReason: ProcessMessageFinishReason;
20
+ usage: ProcessMessageUsage;
21
+ }
22
+ export interface ProcessMessageToolStart {
23
+ toolCall: ToolCall;
24
+ timestamp: number;
25
+ }
26
+ export interface ProcessMessageToolFinish {
27
+ toolCall: ToolCall;
28
+ toolResult: ToolResult;
29
+ timestamp: number;
30
+ }
31
+ export interface ProcessMessageError {
32
+ message: string;
33
+ timestamp: number;
34
+ }
35
+ export interface ProcessMessageObserver {
36
+ onStepStart?(info: ProcessMessageStepStart): void;
37
+ onStepFinish?(info: ProcessMessageStepFinish): void;
38
+ onToolStart?(info: ProcessMessageToolStart): void;
39
+ onToolFinish?(info: ProcessMessageToolFinish): void;
40
+ onError?(info: ProcessMessageError): void;
41
+ }
6
42
  export declare class Agent {
7
43
  private provider;
8
44
  private apiKey;
@@ -13,7 +49,7 @@ export declare class Agent {
13
49
  private workspace;
14
50
  private session;
15
51
  private messages;
16
- private recordedTokens;
52
+ private messageSeqs;
17
53
  private abortController;
18
54
  private maxToolRounds;
19
55
  private mode;
@@ -56,7 +92,10 @@ export declare class Agent {
56
92
  private runDelegation;
57
93
  private readDelegation;
58
94
  private listDelegations;
59
- processMessage(userMessage: string): AsyncGenerator<StreamChunk, void, unknown>;
95
+ private getCompactionSettings;
96
+ private compactForContext;
97
+ private appendCompletedTurn;
98
+ processMessage(userMessage: string, observer?: ProcessMessageObserver): AsyncGenerator<StreamChunk, void, unknown>;
60
99
  private requireProvider;
61
100
  }
62
101
  export {};
@@ -1,12 +1,14 @@
1
1
  import { stepCountIs, streamText } from "ai";
2
2
  import { createProvider, generateTitle as genTitle } from "../grok/client";
3
+ import { getModelInfo } from "../grok/models";
3
4
  import { createTools } from "../grok/tools";
4
5
  import { buildMcpToolSet } from "../mcp/runtime";
5
- import { appendMessages, appendSystemMessage, buildChatEntries, getSessionTotalTokens, loadTranscript, recordUsageEvent, SessionStore, } from "../storage/index";
6
+ import { appendCompaction, appendMessages, appendSystemMessage, buildChatEntries, getNextMessageSequence, getSessionTotalTokens, loadTranscript, loadTranscriptState, recordUsageEvent, SessionStore, } from "../storage/index";
6
7
  import { BashTool } from "../tools/bash";
7
8
  import { loadCustomInstructions } from "../utils/instructions";
8
9
  import { loadMcpServers } from "../utils/settings";
9
10
  import { discoverSkills, formatSkillsForPrompt } from "../utils/skills";
11
+ import { createCompactionSummaryMessage, DEFAULT_KEEP_RECENT_TOKENS, DEFAULT_RESERVE_TOKENS, estimateConversationTokens, generateCompactionSummary, prepareCompaction, relaxCompactionSettings, shouldCompactContext, } from "./compaction";
10
12
  import { DelegationManager } from "./delegations";
11
13
  const MAX_TOOL_ROUNDS = 400;
12
14
  const ENVIRONMENT = `ENVIRONMENT:
@@ -156,7 +158,7 @@ export class Agent {
156
158
  workspace = null;
157
159
  session = null;
158
160
  messages = [];
159
- recordedTokens = 0;
161
+ messageSeqs = [];
160
162
  abortController = null;
161
163
  maxToolRounds;
162
164
  mode = "agent";
@@ -180,8 +182,9 @@ export class Agent {
180
182
  this.workspace = this.sessionStore.getWorkspace();
181
183
  this.session = this.sessionStore.openSession(options.session, this.modelId, this.mode, this.bash.getCwd());
182
184
  this.mode = this.session.mode;
183
- this.messages = loadTranscript(this.session.id);
184
- this.recordedTokens = getSessionTotalTokens(this.session.id);
185
+ const transcript = loadTranscriptState(this.session.id);
186
+ this.messages = transcript.messages;
187
+ this.messageSeqs = transcript.seqs;
185
188
  this.sessionStore.setModel(this.session.id, this.modelId);
186
189
  }
187
190
  }
@@ -223,8 +226,7 @@ export class Agent {
223
226
  }
224
227
  getContextStats(contextWindow, inFlightText = "") {
225
228
  const system = buildSystemPrompt(this.bash.getCwd(), this.mode, this.planContext);
226
- const estimatedTokens = estimateTokens(`${system}\n${JSON.stringify(this.messages)}\n${inFlightText}`);
227
- const usedTokens = Math.min(contextWindow, Math.max(estimatedTokens, this.recordedTokens));
229
+ const usedTokens = Math.min(contextWindow, estimateConversationTokens(system, this.messages, inFlightText));
228
230
  const remainingTokens = Math.max(0, contextWindow - usedTokens);
229
231
  return {
230
232
  contextWindow,
@@ -257,14 +259,14 @@ export class Agent {
257
259
  startNewSession() {
258
260
  if (!this.sessionStore) {
259
261
  this.messages = [];
260
- this.recordedTokens = 0;
262
+ this.messageSeqs = [];
261
263
  return null;
262
264
  }
263
265
  this.sessionStore = new SessionStore(this.bash.getCwd());
264
266
  this.workspace = this.sessionStore.getWorkspace();
265
267
  this.session = this.sessionStore.createSession(this.modelId, this.mode, this.bash.getCwd());
266
268
  this.messages = [];
267
- this.recordedTokens = 0;
269
+ this.messageSeqs = [];
268
270
  return this.getSessionSnapshot();
269
271
  }
270
272
  getSessionInfo() {
@@ -307,15 +309,12 @@ export class Agent {
307
309
  const idx = this.messages.lastIndexOf(userMessage);
308
310
  if (idx >= 0) {
309
311
  this.messages.splice(idx, 1);
312
+ this.messageSeqs.splice(idx, 1);
310
313
  }
311
314
  }
312
315
  recordUsage(usage, source = "message", model = this.modelId) {
313
316
  if (!usage)
314
317
  return;
315
- const total = usage.totalTokens ?? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
316
- if (Number.isFinite(total) && total > 0) {
317
- this.recordedTokens += total;
318
- }
319
318
  if (this.session) {
320
319
  recordUsageEvent(this.session.id, source, model, usage);
321
320
  }
@@ -325,9 +324,11 @@ export class Agent {
325
324
  const notifications = await this.delegations.consumeNotifications();
326
325
  for (const notification of notifications) {
327
326
  this.messages.push({ role: "system", content: notification.message });
327
+ let seq = null;
328
328
  if (this.session) {
329
- appendSystemMessage(this.session.id, notification.message);
329
+ seq = appendSystemMessage(this.session.id, notification.message);
330
330
  }
331
+ this.messageSeqs.push(seq);
331
332
  }
332
333
  return notifications.map((notification) => notification.message);
333
334
  }
@@ -498,143 +499,234 @@ export class Agent {
498
499
  };
499
500
  }
500
501
  }
501
- async *processMessage(userMessage) {
502
+ getCompactionSettings() {
503
+ return {
504
+ reserveTokens: Math.max(this.maxTokens, DEFAULT_RESERVE_TOKENS),
505
+ keepRecentTokens: DEFAULT_KEEP_RECENT_TOKENS,
506
+ };
507
+ }
508
+ async compactForContext(provider, system, contextWindow, signal, settings = this.getCompactionSettings(), force = false) {
509
+ if (!this.session)
510
+ return false;
511
+ const preparation = prepareCompaction(this.messages, system, settings);
512
+ if (!preparation)
513
+ return false;
514
+ if (!force && !shouldCompactContext(preparation.tokensBefore, contextWindow, settings)) {
515
+ return false;
516
+ }
517
+ const keptSeqs = this.messageSeqs.slice(preparation.firstKeptIndex);
518
+ const firstKeptSeq = keptSeqs.find((seq) => seq !== null) ?? getNextMessageSequence(this.session.id);
519
+ const summary = await generateCompactionSummary(provider, this.modelId, preparation, undefined, signal);
520
+ appendCompaction(this.session.id, firstKeptSeq, summary, preparation.tokensBefore);
521
+ this.messages = [createCompactionSummaryMessage(summary), ...preparation.keptMessages];
522
+ this.messageSeqs = [null, ...keptSeqs];
523
+ return true;
524
+ }
525
+ appendCompletedTurn(userMessage, newMessages) {
526
+ if (newMessages.length === 0)
527
+ return;
528
+ const userIndex = this.messages.lastIndexOf(userMessage);
529
+ if (!this.sessionStore || !this.session) {
530
+ if (userIndex >= 0 && this.messageSeqs[userIndex] == null) {
531
+ this.messageSeqs[userIndex] = null;
532
+ }
533
+ this.messages.push(...newMessages);
534
+ this.messageSeqs.push(...newMessages.map(() => null));
535
+ return;
536
+ }
537
+ const insertedSeqs = appendMessages(this.session.id, [userMessage, ...newMessages]);
538
+ if (userIndex >= 0) {
539
+ this.messageSeqs[userIndex] = insertedSeqs[0] ?? this.messageSeqs[userIndex];
540
+ }
541
+ this.messages.push(...newMessages);
542
+ this.messageSeqs.push(...insertedSeqs.slice(1));
543
+ this.sessionStore.touchSession(this.session.id, this.bash.getCwd());
544
+ this.session = this.sessionStore.getRequiredSession(this.session.id);
545
+ }
546
+ async *processMessage(userMessage, observer) {
502
547
  this.abortController = new AbortController();
503
548
  const signal = this.abortController.signal;
504
549
  this.emitSubagentStatus(null);
505
550
  await this.consumeBackgroundNotifications();
506
551
  const userModelMessage = { role: "user", content: userMessage };
507
552
  this.messages.push(userModelMessage);
508
- let assistantText = "";
509
- let streamOk = false;
510
- let closeMcp;
553
+ this.messageSeqs.push(null);
554
+ const provider = this.requireProvider();
555
+ const system = buildSystemPrompt(this.bash.getCwd(), this.mode, this.planContext);
556
+ const modelInfo = getModelInfo(this.modelId);
557
+ this.planContext = null;
558
+ let attemptedOverflowRecovery = false;
511
559
  try {
512
- const provider = this.requireProvider();
513
- const baseTools = createTools(this.bash, provider, this.mode, {
514
- runTask: (request, abortSignal) => this.runTask(request, combineAbortSignals(signal, abortSignal)),
515
- runDelegation: (request, abortSignal) => this.runDelegation(request, combineAbortSignals(signal, abortSignal)),
516
- readDelegation: (id) => this.readDelegation(id),
517
- listDelegations: () => this.listDelegations(),
518
- });
519
- let tools = baseTools;
520
- if (this.mode === "agent") {
521
- const mcpBundle = await buildMcpToolSet(loadMcpServers());
522
- closeMcp = mcpBundle.close;
523
- tools = { ...baseTools, ...mcpBundle.tools };
524
- if (mcpBundle.errors.length > 0) {
525
- yield { type: "content", content: `MCP unavailable: ${mcpBundle.errors.join(" | ")}\n\n` };
526
- }
527
- }
528
- const system = buildSystemPrompt(this.bash.getCwd(), this.mode, this.planContext);
529
- this.planContext = null;
530
- const result = streamText({
531
- model: provider(this.modelId),
532
- system,
533
- messages: this.messages,
534
- tools,
535
- stopWhen: stepCountIs(this.maxToolRounds),
536
- maxRetries: 0,
537
- abortSignal: signal,
538
- temperature: 0.7,
539
- maxOutputTokens: this.maxTokens,
540
- onFinish: ({ totalUsage }) => {
541
- this.recordUsage(totalUsage, "message");
542
- },
543
- });
544
- for await (const part of result.fullStream) {
545
- if (signal.aborted) {
546
- yield { type: "content", content: "\n\n[Cancelled]" };
547
- break;
548
- }
549
- switch (part.type) {
550
- case "text-delta":
551
- assistantText += part.text;
552
- yield { type: "content", content: part.text };
553
- break;
554
- case "reasoning-delta":
555
- yield { type: "reasoning", content: part.text };
556
- break;
557
- case "tool-call": {
558
- const tc = toToolCall(part);
559
- yield { type: "tool_calls", toolCalls: [tc] };
560
- break;
560
+ while (true) {
561
+ let assistantText = "";
562
+ let streamOk = false;
563
+ let closeMcp;
564
+ let stepNumber = -1;
565
+ try {
566
+ const settings = attemptedOverflowRecovery
567
+ ? relaxCompactionSettings(this.getCompactionSettings())
568
+ : this.getCompactionSettings();
569
+ if (modelInfo) {
570
+ await this.compactForContext(provider, system, modelInfo.contextWindow, signal, settings, attemptedOverflowRecovery);
561
571
  }
562
- case "tool-result": {
563
- const tc = {
564
- id: part.toolCallId,
565
- type: "function",
566
- function: { name: part.toolName, arguments: JSON.stringify(part.input ?? {}) },
567
- };
568
- const tr = toToolResult(part.output);
569
- yield { type: "tool_result", toolCall: tc, toolResult: tr };
570
- break;
572
+ const baseTools = createTools(this.bash, provider, this.mode, {
573
+ runTask: (request, abortSignal) => this.runTask(request, combineAbortSignals(signal, abortSignal)),
574
+ runDelegation: (request, abortSignal) => this.runDelegation(request, combineAbortSignals(signal, abortSignal)),
575
+ readDelegation: (id) => this.readDelegation(id),
576
+ listDelegations: () => this.listDelegations(),
577
+ });
578
+ let tools = baseTools;
579
+ if (this.mode === "agent") {
580
+ const mcpBundle = await buildMcpToolSet(loadMcpServers());
581
+ closeMcp = mcpBundle.close;
582
+ tools = { ...baseTools, ...mcpBundle.tools };
583
+ if (mcpBundle.errors.length > 0) {
584
+ yield { type: "content", content: `MCP unavailable: ${mcpBundle.errors.join(" | ")}\n\n` };
585
+ }
571
586
  }
572
- case "error":
573
- yield { type: "error", content: String(part.error) };
574
- break;
575
- case "abort":
576
- yield { type: "content", content: "\n\n[Cancelled]" };
577
- break;
578
- }
579
- }
580
- if (signal.aborted) {
581
- this.discardAbortedTurn(userModelMessage);
582
- yield { type: "done" };
583
- return;
584
- }
585
- try {
586
- const response = await result.response;
587
- if (!signal.aborted) {
588
- this.messages.push(...response.messages);
589
- if (this.sessionStore && this.session) {
590
- appendMessages(this.session.id, [userModelMessage, ...response.messages]);
591
- this.sessionStore.touchSession(this.session.id, this.bash.getCwd());
592
- this.session = this.sessionStore.getRequiredSession(this.session.id);
587
+ const result = streamText({
588
+ model: provider(this.modelId),
589
+ system,
590
+ messages: this.messages,
591
+ tools,
592
+ stopWhen: stepCountIs(this.maxToolRounds),
593
+ maxRetries: 0,
594
+ abortSignal: signal,
595
+ temperature: 0.7,
596
+ maxOutputTokens: this.maxTokens,
597
+ experimental_onStepStart: (event) => {
598
+ stepNumber = getStepNumber(event, stepNumber + 1);
599
+ notifyObserver(observer?.onStepStart, {
600
+ stepNumber,
601
+ timestamp: Date.now(),
602
+ });
603
+ },
604
+ onStepFinish: (event) => {
605
+ const currentStep = getStepNumber(event, Math.max(stepNumber, 0));
606
+ stepNumber = Math.max(stepNumber, currentStep);
607
+ notifyObserver(observer?.onStepFinish, {
608
+ stepNumber: currentStep,
609
+ timestamp: Date.now(),
610
+ finishReason: getFinishReason(event),
611
+ usage: getUsage(event),
612
+ });
613
+ },
614
+ onFinish: ({ totalUsage }) => {
615
+ this.recordUsage(totalUsage, "message");
616
+ },
617
+ });
618
+ for await (const part of result.fullStream) {
619
+ if (signal.aborted) {
620
+ yield { type: "content", content: "\n\n[Cancelled]" };
621
+ break;
622
+ }
623
+ switch (part.type) {
624
+ case "text-delta":
625
+ assistantText += part.text;
626
+ yield { type: "content", content: part.text };
627
+ break;
628
+ case "reasoning-delta":
629
+ yield { type: "reasoning", content: part.text };
630
+ break;
631
+ case "tool-call": {
632
+ const tc = toToolCall(part);
633
+ notifyObserver(observer?.onToolStart, {
634
+ toolCall: tc,
635
+ timestamp: Date.now(),
636
+ });
637
+ yield { type: "tool_calls", toolCalls: [tc] };
638
+ break;
639
+ }
640
+ case "tool-result": {
641
+ const tc = {
642
+ id: part.toolCallId,
643
+ type: "function",
644
+ function: { name: part.toolName, arguments: JSON.stringify(part.input ?? {}) },
645
+ };
646
+ const tr = toToolResult(part.output);
647
+ notifyObserver(observer?.onToolFinish, {
648
+ toolCall: tc,
649
+ toolResult: tr,
650
+ timestamp: Date.now(),
651
+ });
652
+ yield { type: "tool_result", toolCall: tc, toolResult: tr };
653
+ break;
654
+ }
655
+ case "error": {
656
+ const message = String(part.error);
657
+ notifyObserver(observer?.onError, {
658
+ message,
659
+ timestamp: Date.now(),
660
+ });
661
+ yield { type: "error", content: message };
662
+ break;
663
+ }
664
+ case "abort":
665
+ yield { type: "content", content: "\n\n[Cancelled]" };
666
+ break;
667
+ }
668
+ }
669
+ if (signal.aborted) {
670
+ this.discardAbortedTurn(userModelMessage);
671
+ yield { type: "done" };
672
+ return;
673
+ }
674
+ try {
675
+ const response = await result.response;
676
+ if (!signal.aborted) {
677
+ this.appendCompletedTurn(userModelMessage, response.messages);
678
+ streamOk = true;
679
+ }
593
680
  }
594
- streamOk = true;
681
+ catch (responseError) {
682
+ if (!attemptedOverflowRecovery &&
683
+ !assistantText.trim() &&
684
+ modelInfo &&
685
+ isContextLimitError(responseError)) {
686
+ attemptedOverflowRecovery = true;
687
+ continue;
688
+ }
689
+ }
690
+ if (signal.aborted) {
691
+ this.discardAbortedTurn(userModelMessage);
692
+ yield { type: "done" };
693
+ return;
694
+ }
695
+ if (!streamOk && assistantText.trim()) {
696
+ this.appendCompletedTurn(userModelMessage, [{ role: "assistant", content: assistantText }]);
697
+ }
698
+ yield { type: "done" };
699
+ return;
595
700
  }
596
- }
597
- catch {
598
- // response promise can fail after stream errors — fall back to manual message
599
- }
600
- if (signal.aborted) {
601
- this.discardAbortedTurn(userModelMessage);
602
- yield { type: "done" };
603
- return;
604
- }
605
- if (!streamOk && assistantText.trim()) {
606
- const fallbackMessage = { role: "assistant", content: assistantText };
607
- this.messages.push(fallbackMessage);
608
- if (this.sessionStore && this.session) {
609
- appendMessages(this.session.id, [userModelMessage, fallbackMessage]);
610
- this.sessionStore.touchSession(this.session.id, this.bash.getCwd());
611
- this.session = this.sessionStore.getRequiredSession(this.session.id);
701
+ catch (err) {
702
+ if (signal.aborted) {
703
+ this.discardAbortedTurn(userModelMessage);
704
+ yield { type: "content", content: "\n\n[Cancelled]" };
705
+ yield { type: "done" };
706
+ return;
707
+ }
708
+ if (!attemptedOverflowRecovery && !assistantText.trim() && modelInfo && isContextLimitError(err)) {
709
+ attemptedOverflowRecovery = true;
710
+ continue;
711
+ }
712
+ const msg = err instanceof Error ? err.message : String(err);
713
+ notifyObserver(observer?.onError, {
714
+ message: `Error: ${msg}`,
715
+ timestamp: Date.now(),
716
+ });
717
+ yield { type: "error", content: `Error: ${msg}` };
718
+ if (assistantText.trim()) {
719
+ this.appendCompletedTurn(userModelMessage, [{ role: "assistant", content: assistantText }]);
720
+ }
721
+ yield { type: "done" };
722
+ return;
612
723
  }
613
- }
614
- yield { type: "done" };
615
- }
616
- catch (err) {
617
- if (signal.aborted) {
618
- this.discardAbortedTurn(userModelMessage);
619
- yield { type: "content", content: "\n\n[Cancelled]" };
620
- }
621
- else {
622
- const msg = err instanceof Error ? err.message : String(err);
623
- yield { type: "error", content: `Error: ${msg}` };
624
- }
625
- if (!signal.aborted && assistantText.trim()) {
626
- const fallbackMessage = { role: "assistant", content: assistantText };
627
- this.messages.push(fallbackMessage);
628
- if (this.sessionStore && this.session) {
629
- appendMessages(this.session.id, [userModelMessage, fallbackMessage]);
630
- this.sessionStore.touchSession(this.session.id, this.bash.getCwd());
631
- this.session = this.sessionStore.getRequiredSession(this.session.id);
724
+ finally {
725
+ await closeMcp?.().catch(() => { });
632
726
  }
633
727
  }
634
- yield { type: "done" };
635
728
  }
636
729
  finally {
637
- await closeMcp?.().catch(() => { });
638
730
  if (this.abortController?.signal === signal) {
639
731
  this.abortController = null;
640
732
  }
@@ -657,6 +749,52 @@ function toToolCall(part) {
657
749
  },
658
750
  };
659
751
  }
752
+ function notifyObserver(listener, payload) {
753
+ if (!listener) {
754
+ return;
755
+ }
756
+ try {
757
+ listener(payload);
758
+ }
759
+ catch {
760
+ // Observer failures should never break generation.
761
+ }
762
+ }
763
+ function getStepNumber(event, fallback) {
764
+ if (event && typeof event === "object" && "stepNumber" in event && typeof event.stepNumber === "number") {
765
+ return event.stepNumber;
766
+ }
767
+ return fallback;
768
+ }
769
+ function getFinishReason(event) {
770
+ if (event && typeof event === "object" && "finishReason" in event) {
771
+ switch (event.finishReason) {
772
+ case "stop":
773
+ case "length":
774
+ case "content-filter":
775
+ case "tool-calls":
776
+ case "error":
777
+ case "other":
778
+ return event.finishReason;
779
+ }
780
+ }
781
+ return "other";
782
+ }
783
+ function getUsage(event) {
784
+ if (!(event && typeof event === "object" && "usage" in event)) {
785
+ return {};
786
+ }
787
+ const usage = event.usage;
788
+ if (!usage || typeof usage !== "object") {
789
+ return {};
790
+ }
791
+ const u = usage;
792
+ return {
793
+ inputTokens: typeof u.inputTokens === "number" ? u.inputTokens : undefined,
794
+ outputTokens: typeof u.outputTokens === "number" ? u.outputTokens : undefined,
795
+ totalTokens: typeof u.totalTokens === "number" ? u.totalTokens : undefined,
796
+ };
797
+ }
660
798
  function toToolResult(output) {
661
799
  if (output && typeof output === "object" && "success" in output) {
662
800
  const r = output;
@@ -704,9 +842,6 @@ function firstLine(text) {
704
842
  function truncate(text, max) {
705
843
  return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
706
844
  }
707
- function estimateTokens(text) {
708
- return Math.ceil(text.length / 4);
709
- }
710
845
  function combineAbortSignals(...signals) {
711
846
  const activeSignals = signals.filter((signal) => Boolean(signal));
712
847
  if (activeSignals.length === 0)
@@ -726,4 +861,8 @@ function combineAbortSignals(...signals) {
726
861
  }
727
862
  return controller.signal;
728
863
  }
864
+ function isContextLimitError(error) {
865
+ const message = error instanceof Error ? error.message : String(error);
866
+ return /(context|token|prompt).*(limit|length|large|window|overflow)|too many tokens|maximum context/i.test(message);
867
+ }
729
868
  //# sourceMappingURL=agent.js.map