@kenkaiiii/ggcoder 4.11.2 → 4.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/app-sidecar.js +427 -62
  2. package/dist/app-sidecar.js.map +1 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +5 -3
  5. package/dist/cli.js.map +1 -1
  6. package/dist/config.d.ts +5 -0
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +3 -0
  9. package/dist/config.js.map +1 -1
  10. package/dist/core/agent-session.d.ts +66 -11
  11. package/dist/core/agent-session.d.ts.map +1 -1
  12. package/dist/core/agent-session.js +219 -39
  13. package/dist/core/agent-session.js.map +1 -1
  14. package/dist/core/api-benchmark.d.ts +64 -0
  15. package/dist/core/api-benchmark.d.ts.map +1 -0
  16. package/dist/core/api-benchmark.js +381 -0
  17. package/dist/core/api-benchmark.js.map +1 -0
  18. package/dist/core/event-bus.d.ts +1 -0
  19. package/dist/core/event-bus.d.ts.map +1 -1
  20. package/dist/core/mcp/client.d.ts +32 -0
  21. package/dist/core/mcp/client.d.ts.map +1 -1
  22. package/dist/core/mcp/client.js +232 -27
  23. package/dist/core/mcp/client.js.map +1 -1
  24. package/dist/core/mcp/index.d.ts +3 -1
  25. package/dist/core/mcp/index.d.ts.map +1 -1
  26. package/dist/core/mcp/index.js +2 -0
  27. package/dist/core/mcp/index.js.map +1 -1
  28. package/dist/core/mcp/loopback.d.ts +27 -0
  29. package/dist/core/mcp/loopback.d.ts.map +1 -0
  30. package/dist/core/mcp/loopback.js +66 -0
  31. package/dist/core/mcp/loopback.js.map +1 -0
  32. package/dist/core/mcp/loopback.test.d.ts +2 -0
  33. package/dist/core/mcp/loopback.test.d.ts.map +1 -0
  34. package/dist/core/mcp/loopback.test.js +87 -0
  35. package/dist/core/mcp/loopback.test.js.map +1 -0
  36. package/dist/core/mcp/oauth-provider.d.ts +51 -0
  37. package/dist/core/mcp/oauth-provider.d.ts.map +1 -0
  38. package/dist/core/mcp/oauth-provider.js +95 -0
  39. package/dist/core/mcp/oauth-provider.js.map +1 -0
  40. package/dist/core/mcp/oauth-store.d.ts +39 -0
  41. package/dist/core/mcp/oauth-store.d.ts.map +1 -0
  42. package/dist/core/mcp/oauth-store.js +63 -0
  43. package/dist/core/mcp/oauth-store.js.map +1 -0
  44. package/dist/core/mcp/oauth-store.test.d.ts +2 -0
  45. package/dist/core/mcp/oauth-store.test.d.ts.map +1 -0
  46. package/dist/core/mcp/oauth-store.test.js +94 -0
  47. package/dist/core/mcp/oauth-store.test.js.map +1 -0
  48. package/dist/core/mcp/parse-add-command.d.ts.map +1 -1
  49. package/dist/core/mcp/parse-add-command.js +1 -0
  50. package/dist/core/mcp/parse-add-command.js.map +1 -1
  51. package/dist/core/mcp/parse-add-command.test.js +8 -2
  52. package/dist/core/mcp/parse-add-command.test.js.map +1 -1
  53. package/dist/core/mcp/store.d.ts +4 -4
  54. package/dist/core/mcp/store.d.ts.map +1 -1
  55. package/dist/core/mcp/store.js +7 -1
  56. package/dist/core/mcp/store.js.map +1 -1
  57. package/dist/core/mcp/store.test.js +11 -2
  58. package/dist/core/mcp/store.test.js.map +1 -1
  59. package/dist/core/mcp/types.d.ts +5 -1
  60. package/dist/core/mcp/types.d.ts.map +1 -1
  61. package/dist/core/process-manager.d.ts.map +1 -1
  62. package/dist/core/process-manager.js +5 -1
  63. package/dist/core/process-manager.js.map +1 -1
  64. package/dist/core/settings-manager.d.ts +4 -0
  65. package/dist/core/settings-manager.d.ts.map +1 -1
  66. package/dist/core/settings-manager.js +5 -0
  67. package/dist/core/settings-manager.js.map +1 -1
  68. package/dist/core/shell.d.ts +51 -0
  69. package/dist/core/shell.d.ts.map +1 -0
  70. package/dist/core/shell.js +82 -0
  71. package/dist/core/shell.js.map +1 -0
  72. package/dist/core/shell.test.d.ts +2 -0
  73. package/dist/core/shell.test.d.ts.map +1 -0
  74. package/dist/core/shell.test.js +87 -0
  75. package/dist/core/shell.test.js.map +1 -0
  76. package/dist/core/speed-benchmark.d.ts +133 -0
  77. package/dist/core/speed-benchmark.d.ts.map +1 -0
  78. package/dist/core/speed-benchmark.js +410 -0
  79. package/dist/core/speed-benchmark.js.map +1 -0
  80. package/dist/core/speed-benchmark.test.d.ts +2 -0
  81. package/dist/core/speed-benchmark.test.d.ts.map +1 -0
  82. package/dist/core/speed-benchmark.test.js +97 -0
  83. package/dist/core/speed-benchmark.test.js.map +1 -0
  84. package/dist/interactive.d.ts.map +1 -1
  85. package/dist/interactive.js +4 -3
  86. package/dist/interactive.js.map +1 -1
  87. package/dist/tools/bash.d.ts.map +1 -1
  88. package/dist/tools/bash.js +17 -1
  89. package/dist/tools/bash.js.map +1 -1
  90. package/dist/tools/edit-diff.d.ts.map +1 -1
  91. package/dist/tools/edit-diff.js +25 -8
  92. package/dist/tools/edit-diff.js.map +1 -1
  93. package/dist/tools/generate-image.d.ts +39 -0
  94. package/dist/tools/generate-image.d.ts.map +1 -0
  95. package/dist/tools/generate-image.js +301 -0
  96. package/dist/tools/generate-image.js.map +1 -0
  97. package/dist/tools/generate-image.test.d.ts +2 -0
  98. package/dist/tools/generate-image.test.d.ts.map +1 -0
  99. package/dist/tools/generate-image.test.js +223 -0
  100. package/dist/tools/generate-image.test.js.map +1 -0
  101. package/dist/tools/index.d.ts +12 -1
  102. package/dist/tools/index.d.ts.map +1 -1
  103. package/dist/tools/index.js +16 -1
  104. package/dist/tools/index.js.map +1 -1
  105. package/dist/tools/ls.d.ts.map +1 -1
  106. package/dist/tools/ls.js +7 -4
  107. package/dist/tools/ls.js.map +1 -1
  108. package/dist/tools/plan-mode.test.js +5 -5
  109. package/dist/tools/plan-mode.test.js.map +1 -1
  110. package/dist/tools/prompt-hints.d.ts.map +1 -1
  111. package/dist/tools/prompt-hints.js +2 -0
  112. package/dist/tools/prompt-hints.js.map +1 -1
  113. package/dist/tools/safe-env.d.ts.map +1 -1
  114. package/dist/tools/safe-env.js +27 -0
  115. package/dist/tools/safe-env.js.map +1 -1
  116. package/dist/ui/App.d.ts +1 -1
  117. package/dist/ui/App.d.ts.map +1 -1
  118. package/dist/ui/hooks/usePixelFixFlow.d.ts +1 -1
  119. package/dist/ui/hooks/usePixelFixFlow.d.ts.map +1 -1
  120. package/dist/ui/hooks/usePixelFixFlow.js +1 -1
  121. package/dist/ui/hooks/usePixelFixFlow.js.map +1 -1
  122. package/dist/ui/render.d.ts +1 -1
  123. package/dist/ui/render.d.ts.map +1 -1
  124. package/package.json +5 -5
@@ -3,6 +3,16 @@ import { EventBus } from "./event-bus.js";
3
3
  import { SlashCommandRegistry } from "./slash-commands.js";
4
4
  import { type BranchInfo } from "./session-manager.js";
5
5
  import type { BackgroundProcess } from "./process-manager.js";
6
+ /** A chat attachment (image / video / other file) prepared for the model. The
7
+ * raw base64 `data` rides native blocks; `path` (when persisted to disk) lets
8
+ * the agent's tools open the file directly. */
9
+ export interface SessionAttachment {
10
+ kind: "image" | "video" | "file";
11
+ mediaType: string;
12
+ data: string;
13
+ name: string;
14
+ path?: string;
15
+ }
6
16
  export interface AgentSessionOptions {
7
17
  provider: Provider;
8
18
  model: string;
@@ -31,6 +41,16 @@ export interface AgentSessionOptions {
31
41
  * resumable identity.
32
42
  */
33
43
  transient?: boolean;
44
+ /**
45
+ * If true, `initialize()` returns WITHOUT waiting for MCP servers to connect —
46
+ * the connection runs in the background and tools are appended when ready.
47
+ * Hosts whose readiness is gated on `initialize()` (the gg-app sidecar, which
48
+ * can't emit its listening handshake until init resolves) set this so a slow
49
+ * or hanging stdio MCP server (e.g. a first-run `npx -y …` download) can't
50
+ * delay the session from becoming usable. Default (false) keeps the CLI's
51
+ * connect-before-ready behavior so MCP tools are present on the first turn.
52
+ */
53
+ backgroundMcpConnect?: boolean;
34
54
  /**
35
55
  * Plan-mode callbacks. When provided, the `enter_plan`/`exit_plan` tools are
36
56
  * registered and the session manages plan-mode restrictions + system-prompt
@@ -58,6 +78,10 @@ export declare class AgentSession {
58
78
  private extensionLoader;
59
79
  private messages;
60
80
  private tools;
81
+ /** Rebuilds the read tool for a new model (video byte cap is baked in at
82
+ * creation). Called from switchModel so video-capable models get the
83
+ * read-tool's native-video path after a mid-session model change. */
84
+ private rebuildReadTool;
61
85
  private skills;
62
86
  private cacheKeyLogged;
63
87
  private hookStats;
@@ -73,6 +97,9 @@ export declare class AgentSession {
73
97
  private regroundingInjected;
74
98
  private compactionOccurred;
75
99
  private originalRequest;
100
+ /** True after the cache has been pre-warmed for this session. Ensures we only
101
+ * fire the warm-up call once (before the first real turn). */
102
+ private cachePrewarmed;
76
103
  private userQueue;
77
104
  private processManager?;
78
105
  private lspManager?;
@@ -97,7 +124,26 @@ export declare class AgentSession {
97
124
  private currentLeafId;
98
125
  private opts;
99
126
  constructor(options: AgentSessionOptions);
127
+ /**
128
+ * Derive the output-token cap for a model. Follows the active model's
129
+ * `maxOutputTokens` so a session booted on a large-output model (e.g. Kimi's
130
+ * 256K) doesn't carry that cap to a smaller one (e.g. Opus's 128K) after a
131
+ * model switch — that mismatch surfaces from the provider as
132
+ * `max_tokens: 262144 > 128000, which is the maximum allowed …`. An explicit
133
+ * `maxTokens` override is honored but clamped to the model's ceiling.
134
+ */
135
+ private resolveMaxTokens;
100
136
  initialize(): Promise<void>;
137
+ /**
138
+ * Connect all configured MCP servers and append their tools to `this.tools`.
139
+ * Resolves the GLM api key first (Z.AI's bundled servers need it). Never
140
+ * throws — a failed connect is logged and skipped — so it is safe to either
141
+ * `await` (CLI: tools ready before the first turn) or fire-and-forget
142
+ * (sidecar: `backgroundMcpConnect`, so a slow stdio server can't stall
143
+ * startup). Tools are pushed onto the live array the agent loop reads each
144
+ * turn, so background-connected servers become available on the next prompt.
145
+ */
146
+ private connectMcpServers;
101
147
  /**
102
148
  * Process user input. Handles slash commands or runs agent loop.
103
149
  */
@@ -109,13 +155,13 @@ export declare class AgentSession {
109
155
  * agent can open them with its tools. Slash-command parsing is skipped —
110
156
  * attachments are always a direct conversational turn.
111
157
  */
112
- promptWithAttachments(text: string, attachments: Array<{
113
- kind: "image" | "video" | "file";
114
- mediaType: string;
115
- data: string;
116
- name: string;
117
- path?: string;
118
- }>): Promise<void>;
158
+ promptWithAttachments(text: string, attachments: SessionAttachment[]): Promise<void>;
159
+ /**
160
+ * Build the native content blocks (text + image/video notes + file notes) for
161
+ * a user message with attachments. Shared by {@link promptWithAttachments} and
162
+ * the mid-run steering drain so queued media is delivered identically.
163
+ */
164
+ private buildAttachmentParts;
119
165
  /**
120
166
  * Reset per-run self-correction hook state. Mirrors the TUI's run_start
121
167
  * resets so each run evaluates the hooks from a clean slate. `originalRequest`
@@ -168,12 +214,14 @@ export declare class AgentSession {
168
214
  listBranches(): Promise<BranchInfo[]>;
169
215
  getState(): AgentSessionState;
170
216
  getPlanMode(): boolean;
171
- /** Queue a user message to be injected mid-run as steering. Returns the new
172
- * queue length. No-op semantics are the caller's concern. */
173
- queueMessage(text: string): number;
217
+ /** Queue a user message (optionally with attachments) to be injected mid-run
218
+ * as steering. Returns the new queue length. No-op semantics are the caller's
219
+ * concern. */
220
+ queueMessage(text: string, attachments?: SessionAttachment[]): number;
174
221
  /** Number of messages currently queued. */
175
222
  getQueuedCount(): number;
176
- /** Clear the queue, returning the combined text (to restore to the composer). */
223
+ /** Clear the queue, returning the combined text (to restore to the composer).
224
+ * Queued attachments are dropped on cancel — the composer only restores text. */
177
225
  drainQueue(): string;
178
226
  /** Snapshot of background processes (bash run_in_background), newest-state. */
179
227
  listBackgroundProcesses(): BackgroundProcess[];
@@ -209,6 +257,13 @@ export declare class AgentSession {
209
257
  setThinkingLevel(level: ThinkingLevel | undefined): void;
210
258
  /** Replace the abort signal (e.g. after cancellation). */
211
259
  setSignal(signal: AbortSignal): void;
260
+ /** True when speedProfile is "optimized" (1-h cache TTL + pre-warm). */
261
+ private isSpeedOptimized;
262
+ /** Fire a cache pre-warm request for Anthropic so the first real turn is a
263
+ * cache read instead of a cold write. No-op for other providers and when
264
+ * speedProfile is not "optimized". Entirely best-effort — any failure is
265
+ * swallowed so prewarm never blocks or aborts the real prompt. */
266
+ private maybePrewarmCache;
212
267
  private getPromptCacheKey;
213
268
  /** Stable cache-routing key for downstream sub-agent processes. */
214
269
  getCurrentCacheKey(): string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"agent-session.d.ts","sourceRoot":"","sources":["../../src/core/agent-session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent-session.d.ts","sourceRoot":"","sources":["../../src/core/agent-session.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { agentLoop, isAbortError } from "@kenkaiiii/gg-agent";
2
- import { ProviderError, } from "@kenkaiiii/gg-ai";
2
+ import { ProviderError, prewarmAnthropicCache, } from "@kenkaiiii/gg-ai";
3
3
  import { EventBus } from "./event-bus.js";
4
4
  import { SlashCommandRegistry, createBuiltinCommands, } from "./slash-commands.js";
5
5
  import { PROMPT_COMMANDS, getPromptCommand } from "./prompt-commands.js";
@@ -16,7 +16,7 @@ import { discoverSkills } from "./skills.js";
16
16
  import { ensureAppDirs } from "../config.js";
17
17
  import { buildSystemPrompt } from "../system-prompt.js";
18
18
  import { createTools, createWebSearchTool, } from "../tools/index.js";
19
- import { MCPClientManager, getMCPServers, getAllMcpServers } from "./mcp/index.js";
19
+ import { MCPClientManager, getAllMcpServers } from "./mcp/index.js";
20
20
  import { log } from "./logger.js";
21
21
  import { setEstimatorModel } from "./compaction/token-estimator.js";
22
22
  import { discoverAgents } from "./agents.js";
@@ -37,6 +37,10 @@ export class AgentSession {
37
37
  extensionLoader = new ExtensionLoader();
38
38
  messages = [];
39
39
  tools = [];
40
+ /** Rebuilds the read tool for a new model (video byte cap is baked in at
41
+ * creation). Called from switchModel so video-capable models get the
42
+ * read-tool's native-video path after a mid-session model change. */
43
+ rebuildReadTool;
40
44
  skills = [];
41
45
  cacheKeyLogged = false;
42
46
  // ── Self-correction hook state (mirrors the TUI's useAgentLoop refs) ──
@@ -63,9 +67,13 @@ export class AgentSession {
63
67
  regroundingInjected = false;
64
68
  compactionOccurred = false;
65
69
  originalRequest = "";
70
+ /** True after the cache has been pre-warmed for this session. Ensures we only
71
+ * fire the warm-up call once (before the first real turn). */
72
+ cachePrewarmed = false;
66
73
  // Messages queued by the user while a run is in flight. Drained at the
67
74
  // mid-loop steering boundary (user steering wins over the hooks), mirroring
68
- // the TUI's getSteeringMessages.
75
+ // the TUI's getSteeringMessages. Each entry carries its own attachments so a
76
+ // user can queue media (images/video/files) mid-run, not just plain text.
69
77
  userQueue = [];
70
78
  processManager;
71
79
  lspManager;
@@ -95,10 +103,27 @@ export class AgentSession {
95
103
  this.model = options.model;
96
104
  this.cwd = options.cwd;
97
105
  this.baseUrl = options.baseUrl;
98
- this.maxTokens = options.maxTokens ?? getModel(options.model)?.maxOutputTokens ?? 16384;
106
+ this.maxTokens = this.resolveMaxTokens(options.model);
99
107
  this.thinkingLevel = options.thinkingLevel;
100
108
  this.customSystemPrompt = options.systemPrompt;
101
109
  }
110
+ /**
111
+ * Derive the output-token cap for a model. Follows the active model's
112
+ * `maxOutputTokens` so a session booted on a large-output model (e.g. Kimi's
113
+ * 256K) doesn't carry that cap to a smaller one (e.g. Opus's 128K) after a
114
+ * model switch — that mismatch surfaces from the provider as
115
+ * `max_tokens: 262144 > 128000, which is the maximum allowed …`. An explicit
116
+ * `maxTokens` override is honored but clamped to the model's ceiling.
117
+ */
118
+ resolveMaxTokens(modelId) {
119
+ const modelInfo = getModel(modelId);
120
+ if (this.opts.maxTokens) {
121
+ return modelInfo
122
+ ? Math.min(this.opts.maxTokens, modelInfo.maxOutputTokens)
123
+ : this.opts.maxTokens;
124
+ }
125
+ return modelInfo?.maxOutputTokens ?? 16384;
126
+ }
102
127
  async initialize() {
103
128
  // Set model for accurate token estimation
104
129
  setEstimatorModel(this.model);
@@ -125,12 +150,13 @@ export class AgentSession {
125
150
  globalAgentsDir: paths.agentsDir,
126
151
  projectDir: this.cwd,
127
152
  });
128
- const { tools, processManager, lspManager } = createTools(this.cwd, {
153
+ const { tools, processManager, rebuildReadTool, lspManager } = await createTools(this.cwd, {
129
154
  agents,
130
155
  skills: this.skills,
131
156
  provider: this.provider,
132
157
  model: this.model,
133
158
  lspDiagnostics: this.settingsManager.get("lspDiagnostics"),
159
+ authStorage: this.authStorage,
134
160
  // Lazy — sessionId/model/provider can change after createTools() runs, so
135
161
  // sub-agent spawns read the current parent state at execution time.
136
162
  getProvider: () => this.provider,
@@ -147,26 +173,22 @@ export class AgentSession {
147
173
  : {}),
148
174
  });
149
175
  this.tools = tools;
176
+ this.rebuildReadTool = rebuildReadTool;
150
177
  this.processManager = processManager;
151
178
  this.lspManager = lspManager;
152
- // Connect MCP servers (non-blocking failures are logged and skipped)
179
+ // Connect MCP servers. The connect attempt itself can block for up to the
180
+ // per-server connect timeout (~30s) — a slow stdio server such as a
181
+ // first-run `npx -y @playwright/mcp` download stalls here. When the host
182
+ // gates its own readiness on initialize() (the gg-app sidecar can't emit
183
+ // its listening handshake until this resolves), `backgroundMcpConnect`
184
+ // moves the connect off the critical path so the session becomes usable
185
+ // immediately and tools are appended whenever the servers come up.
153
186
  this.mcpManager = new MCPClientManager();
154
- try {
155
- let apiKey;
156
- if (this.provider === "glm") {
157
- try {
158
- const glmCreds = await this.authStorage.resolveCredentials("glm");
159
- apiKey = glmCreds.accessToken;
160
- }
161
- catch {
162
- // GLM not configured — skip Z.AI MCP servers
163
- }
164
- }
165
- const mcpTools = await this.mcpManager.connectAll(getMCPServers(this.provider, apiKey));
166
- this.tools.push(...mcpTools);
187
+ if (this.opts.backgroundMcpConnect) {
188
+ void this.connectMcpServers();
167
189
  }
168
- catch (err) {
169
- log("WARN", "mcp", `MCP initialization failed: ${err instanceof Error ? err.message : String(err)}`);
190
+ else {
191
+ await this.connectMcpServers();
170
192
  }
171
193
  const basePrompt = this.customSystemPrompt ??
172
194
  (await buildSystemPrompt(this.cwd, this.skills, false, undefined, this.tools.map((tool) => tool.name), undefined, this.provider));
@@ -236,6 +258,47 @@ export class AgentSession {
236
258
  await this.extensionLoader.loadAll(paths.extensionsDir, extContext);
237
259
  this.eventBus.emit("session_start", { sessionId: this.sessionId });
238
260
  }
261
+ /**
262
+ * Connect all configured MCP servers and append their tools to `this.tools`.
263
+ * Resolves the GLM api key first (Z.AI's bundled servers need it). Never
264
+ * throws — a failed connect is logged and skipped — so it is safe to either
265
+ * `await` (CLI: tools ready before the first turn) or fire-and-forget
266
+ * (sidecar: `backgroundMcpConnect`, so a slow stdio server can't stall
267
+ * startup). Tools are pushed onto the live array the agent loop reads each
268
+ * turn, so background-connected servers become available on the next prompt.
269
+ */
270
+ async connectMcpServers() {
271
+ if (!this.mcpManager)
272
+ return;
273
+ try {
274
+ let apiKey;
275
+ if (this.provider === "glm") {
276
+ try {
277
+ const glmCreds = await this.authStorage.resolveCredentials("glm");
278
+ apiKey = glmCreds.accessToken;
279
+ }
280
+ catch {
281
+ // GLM not configured — skip Z.AI MCP servers
282
+ }
283
+ }
284
+ const mcpTools = await this.mcpManager.connectAll(await getAllMcpServers(this.provider, apiKey, this.cwd));
285
+ this.tools.push(...mcpTools);
286
+ // Background connect resolves AFTER initialize() has already built the
287
+ // system prompt (the default path awaits this before buildSystemPrompt,
288
+ // so its prompt already lists the tools). Refresh messages[0] so the
289
+ // model is also told about the MCP tools by name on its next turn —
290
+ // mirrors the TUI's replaceSystemPrompt after connectInitialMcpTools.
291
+ // Safe ordering: this method's first await yields before initialize()
292
+ // sets `messages`, and connectAll (process spawn / network) always
293
+ // resolves long after the local-only remainder of init has finished.
294
+ if (this.opts.backgroundMcpConnect && mcpTools.length > 0) {
295
+ await this.rebuildSystemPromptInPlace();
296
+ }
297
+ }
298
+ catch (err) {
299
+ log("WARN", "mcp", `MCP initialization failed: ${err instanceof Error ? err.message : String(err)}`);
300
+ }
301
+ }
239
302
  /**
240
303
  * Process user input. Handles slash commands or runs agent loop.
241
304
  */
@@ -285,14 +348,59 @@ export class AgentSession {
285
348
  * attachments are always a direct conversational turn.
286
349
  */
287
350
  async promptWithAttachments(text, attachments) {
351
+ const parts = this.buildAttachmentParts(text, attachments);
352
+ if (parts.length === 0)
353
+ return;
354
+ const userMessage = { role: "user", content: parts };
355
+ this.messages.push(userMessage);
356
+ await this.persistMessage(userMessage);
357
+ this.lastPersistedIndex = this.messages.length;
358
+ await this.runLoop();
359
+ }
360
+ /**
361
+ * Build the native content blocks (text + image/video notes + file notes) for
362
+ * a user message with attachments. Shared by {@link promptWithAttachments} and
363
+ * the mid-run steering drain so queued media is delivered identically.
364
+ */
365
+ buildAttachmentParts(text, attachments) {
288
366
  const parts = [];
289
367
  const fileNotes = [];
368
+ const modelSupportsVideo = getModel(this.model)?.supportsVideo ?? false;
290
369
  for (const a of attachments) {
291
370
  if (a.kind === "image") {
292
371
  parts.push({ type: "image", mediaType: a.mediaType, data: a.data });
372
+ if (a.path) {
373
+ parts.push({ type: "text", text: `[Image saved at ${a.path}]` });
374
+ }
293
375
  }
294
376
  else if (a.kind === "video") {
295
- parts.push({ type: "video", mediaType: a.mediaType, data: a.data });
377
+ // Mirror the CLI's buildUserContentWithAttachments: never send inline
378
+ // VideoContent in the user message. Video-capable models (Kimi/Gemini/
379
+ // MiniMax) watch video via the read tool, which auto-compresses to the
380
+ // model's byte cap and delivers it in the provider's required shape.
381
+ // Non-video models get a plain note so they know to use ffmpeg. The file
382
+ // was already saved to disk by prepareAttachments in the sidecar.
383
+ if (modelSupportsVideo && a.path) {
384
+ parts.push({
385
+ type: "text",
386
+ text: `The user attached a video at ${a.path}. You CAN watch it: call the read tool ` +
387
+ `on this exact path now, then answer based on what you see. Do not say you ` +
388
+ `cannot watch video — reading the file lets you analyze it.`,
389
+ });
390
+ }
391
+ else if (a.path) {
392
+ parts.push({
393
+ type: "text",
394
+ text: `[User attached a video file at ${a.path}. You cannot watch video directly; ` +
395
+ `if needed, use ffmpeg to extract frames or audio.]`,
396
+ });
397
+ }
398
+ else {
399
+ parts.push({
400
+ type: "text",
401
+ text: `[User attached a video file but it could not be saved for analysis.]`,
402
+ });
403
+ }
296
404
  }
297
405
  else if (a.path) {
298
406
  fileNotes.push(`- ${a.name} (saved at ${a.path})`);
@@ -306,13 +414,7 @@ export class AgentSession {
306
414
  }
307
415
  if (textParts.length > 0)
308
416
  parts.unshift({ type: "text", text: textParts.join("\n\n") });
309
- if (parts.length === 0)
310
- return;
311
- const userMessage = { role: "user", content: parts };
312
- this.messages.push(userMessage);
313
- await this.persistMessage(userMessage);
314
- this.lastPersistedIndex = this.messages.length;
315
- await this.runLoop();
417
+ return parts;
316
418
  }
317
419
  /**
318
420
  * Reset per-run self-correction hook state. Mirrors the TUI's run_start
@@ -406,8 +508,18 @@ export class AgentSession {
406
508
  // User steering wins: drain any messages queued during this run first so the
407
509
  // agent sees them mid-loop instead of after it stops.
408
510
  if (this.userQueue.length > 0) {
409
- const merged = this.userQueue.splice(0).join("\n\n");
410
- return [{ role: "user", content: merged }];
511
+ const queued = this.userQueue.splice(0);
512
+ // Plain-text-only queue: keep the simple merged-string message.
513
+ if (queued.every((m) => m.attachments.length === 0)) {
514
+ const merged = queued.map((m) => m.text).join("\n\n");
515
+ return [{ role: "user", content: merged }];
516
+ }
517
+ // Any queued attachments → deliver one user message with text + media
518
+ // blocks built the same way as a non-queued attachment prompt.
519
+ const parts = [];
520
+ for (const m of queued)
521
+ parts.push(...this.buildAttachmentParts(m.text, m.attachments));
522
+ return [{ role: "user", content: parts }];
411
523
  }
412
524
  if (!this.settingsManager.get("idealReviewEnabled"))
413
525
  return null;
@@ -507,7 +619,9 @@ export class AgentSession {
507
619
  defaultHeaders: this.provider === "moonshot" && isKimiCodingEndpoint(effectiveBaseUrl)
508
620
  ? kimiCodingHeaders()
509
621
  : undefined,
510
- cacheRetention: "short",
622
+ // speedProfile "optimized": 1-h cache TTL (survives turns >5 min apart)
623
+ // + pre-warm before the first turn. "baseline": current 5-min default.
624
+ cacheRetention: this.isSpeedOptimized() ? "long" : "short",
511
625
  promptCacheKey: this.getPromptCacheKey(),
512
626
  supportsImages: modelInfo?.supportsImages,
513
627
  supportsVideo: modelInfo?.supportsVideo,
@@ -526,6 +640,11 @@ export class AgentSession {
526
640
  }
527
641
  };
528
642
  try {
643
+ // Fire cache pre-warm before the first turn (Anthropic + speedProfile optimized).
644
+ // Runs concurrently with nothing — it must complete before runAgentLoop so
645
+ // the cache is warm when the real request arrives. Best-effort: swallowed
646
+ // inside maybePrewarmCache/prewarmAnthropicCache.
647
+ await this.maybePrewarmCache(creds);
529
648
  await runAgentLoop(creds.accessToken, creds.accountId, creds.projectId);
530
649
  }
531
650
  catch (err) {
@@ -565,7 +684,25 @@ export class AgentSession {
565
684
  this.provider = provider;
566
685
  this.model = model;
567
686
  setEstimatorModel(model);
568
- this.eventBus.emit("model_change", { provider: this.provider, model: this.model });
687
+ // maxTokens must follow the active model — it was frozen at the boot
688
+ // model's `maxOutputTokens` in the constructor, so without this a session
689
+ // booted on e.g. Kimi (256K) keeps sending that cap after switching to a
690
+ // smaller model (Opus 128K), which the provider rejects.
691
+ this.maxTokens = this.resolveMaxTokens(model);
692
+ this.eventBus.emit("model_change", {
693
+ provider: this.provider,
694
+ model: this.model,
695
+ supportsVideo: getModel(this.model)?.supportsVideo ?? false,
696
+ });
697
+ // Rebuild the read tool for the new model's video byte cap. The tool's
698
+ // video capability (description + native-video execute path) is baked in
699
+ // at creation from the model's maxVideoBytes, so switching to/from a
700
+ // video-capable model mid-session needs a fresh tool object — mirrors
701
+ // the TUI's rebuildReadTool call on model switch.
702
+ if (this.rebuildReadTool) {
703
+ const newReadTool = this.rebuildReadTool(model);
704
+ this.tools = this.tools.map((t) => (t.name === "read" ? newReadTool : t));
705
+ }
569
706
  // Update provider-specific tools when provider changes
570
707
  if (provider && provider !== prevProvider) {
571
708
  // Add/remove client-side web_search tool based on provider.
@@ -718,19 +855,24 @@ export class AgentSession {
718
855
  getPlanMode() {
719
856
  return this.planModeRef.current;
720
857
  }
721
- /** Queue a user message to be injected mid-run as steering. Returns the new
722
- * queue length. No-op semantics are the caller's concern. */
723
- queueMessage(text) {
724
- this.userQueue.push(text);
858
+ /** Queue a user message (optionally with attachments) to be injected mid-run
859
+ * as steering. Returns the new queue length. No-op semantics are the caller's
860
+ * concern. */
861
+ queueMessage(text, attachments = []) {
862
+ this.userQueue.push({ text, attachments });
725
863
  return this.userQueue.length;
726
864
  }
727
865
  /** Number of messages currently queued. */
728
866
  getQueuedCount() {
729
867
  return this.userQueue.length;
730
868
  }
731
- /** Clear the queue, returning the combined text (to restore to the composer). */
869
+ /** Clear the queue, returning the combined text (to restore to the composer).
870
+ * Queued attachments are dropped on cancel — the composer only restores text. */
732
871
  drainQueue() {
733
- return this.userQueue.splice(0).join("\n\n");
872
+ return this.userQueue
873
+ .splice(0)
874
+ .map((m) => m.text)
875
+ .join("\n\n");
734
876
  }
735
877
  /** Snapshot of background processes (bash run_in_background), newest-state. */
736
878
  listBackgroundProcesses() {
@@ -826,6 +968,44 @@ export class AgentSession {
826
968
  setSignal(signal) {
827
969
  this.opts = { ...this.opts, signal };
828
970
  }
971
+ /** True when speedProfile is "optimized" (1-h cache TTL + pre-warm). */
972
+ isSpeedOptimized() {
973
+ return this.settingsManager?.get("speedProfile") === "optimized";
974
+ }
975
+ /** Fire a cache pre-warm request for Anthropic so the first real turn is a
976
+ * cache read instead of a cold write. No-op for other providers and when
977
+ * speedProfile is not "optimized". Entirely best-effort — any failure is
978
+ * swallowed so prewarm never blocks or aborts the real prompt. */
979
+ async maybePrewarmCache(creds) {
980
+ if (this.cachePrewarmed || !this.isSpeedOptimized() || this.provider !== "anthropic") {
981
+ return;
982
+ }
983
+ this.cachePrewarmed = true;
984
+ try {
985
+ const userAgent = await getClaudeCliUserAgent();
986
+ const systemText = typeof this.messages[0]?.content === "string" ? this.messages[0].content : "";
987
+ if (!systemText)
988
+ return;
989
+ await prewarmAnthropicCache({
990
+ apiKey: creds.accessToken,
991
+ model: this.model,
992
+ system: systemText,
993
+ tools: this.tools.map((t) => ({
994
+ name: t.name,
995
+ description: t.description,
996
+ parameters: t.parameters,
997
+ ...(t.rawInputSchema ? { rawInputSchema: t.rawInputSchema } : {}),
998
+ })),
999
+ baseUrl: this.baseUrl ?? creds.baseUrl,
1000
+ userAgent,
1001
+ cacheRetention: "long",
1002
+ signal: this.opts.signal,
1003
+ });
1004
+ }
1005
+ catch {
1006
+ // Best-effort — prewarm failure must never block the session.
1007
+ }
1008
+ }
829
1009
  getPromptCacheKey() {
830
1010
  if (this.opts.promptCacheKey)
831
1011
  return this.opts.promptCacheKey;