@kenkaiiii/ggcoder 4.11.3 → 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 +368 -53
  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 +57 -11
  11. package/dist/core/agent-session.d.ts.map +1 -1
  12. package/dist/core/agent-session.js +196 -38
  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 +4 -4
@@ -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?;
@@ -107,6 +134,16 @@ export declare class AgentSession {
107
134
  */
108
135
  private resolveMaxTokens;
109
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;
110
147
  /**
111
148
  * Process user input. Handles slash commands or runs agent loop.
112
149
  */
@@ -118,13 +155,13 @@ export declare class AgentSession {
118
155
  * agent can open them with its tools. Slash-command parsing is skipped —
119
156
  * attachments are always a direct conversational turn.
120
157
  */
121
- promptWithAttachments(text: string, attachments: Array<{
122
- kind: "image" | "video" | "file";
123
- mediaType: string;
124
- data: string;
125
- name: string;
126
- path?: string;
127
- }>): 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;
128
165
  /**
129
166
  * Reset per-run self-correction hook state. Mirrors the TUI's run_start
130
167
  * resets so each run evaluates the hooks from a clean slate. `originalRequest`
@@ -177,12 +214,14 @@ export declare class AgentSession {
177
214
  listBranches(): Promise<BranchInfo[]>;
178
215
  getState(): AgentSessionState;
179
216
  getPlanMode(): boolean;
180
- /** Queue a user message to be injected mid-run as steering. Returns the new
181
- * queue length. No-op semantics are the caller's concern. */
182
- 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;
183
221
  /** Number of messages currently queued. */
184
222
  getQueuedCount(): number;
185
- /** 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. */
186
225
  drainQueue(): string;
187
226
  /** Snapshot of background processes (bash run_in_background), newest-state. */
188
227
  listBackgroundProcesses(): BackgroundProcess[];
@@ -218,6 +257,13 @@ export declare class AgentSession {
218
257
  setThinkingLevel(level: ThinkingLevel | undefined): void;
219
258
  /** Replace the abort signal (e.g. after cancellation). */
220
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;
221
267
  private getPromptCacheKey;
222
268
  /** Stable cache-routing key for downstream sub-agent processes. */
223
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":"AACA,OAAO,EAGL,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,KAAK,aAAa,EAInB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EACL,oBAAoB,EAGrB,MAAM,qBAAqB,CAAC;AAO7B,OAAO,EAAqC,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAc1F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAwB9D;;gDAEgD;AAChD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,0DAA0D;IAC1D,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;OAQG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACpD;AAID,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAID,qBAAa,YAAY;IACvB,QAAQ,CAAC,QAAQ,WAAkB;IACnC,QAAQ,CAAC,aAAa,uBAA8B;IAEpD,OAAO,CAAC,eAAe,CAAmB;IAC1C,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,cAAc,CAAkB;IACxC,OAAO,CAAC,eAAe,CAAyB;IAEhD,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,KAAK,CAAmB;IAChC;;0EAEsE;IACtE,OAAO,CAAC,eAAe,CAA6C;IACpE,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,cAAc,CAAS;IAI/B,OAAO,CAAC,SAAS,CAQf;IACF,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,uBAAuB,CAAK;IACpC,OAAO,CAAC,uBAAuB,CAAK;IACpC,OAAO,CAAC,oBAAoB,CAAK;IACjC,OAAO,CAAC,mBAAmB,CAA6B;IACxD,OAAO,CAAC,kBAAkB,CAA6B;IACvD,OAAO,CAAC,aAAa,CAAsE;IAC3F,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,eAAe,CAAM;IAC7B;mEAC+D;IAC/D,OAAO,CAAC,cAAc,CAAS;IAK/B,OAAO,CAAC,SAAS,CAAiE;IAClF,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,UAAU,CAAC,CAAmB;IACtC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC,OAAO,CAAC,kBAAkB,CAAC,CAAS;IACpC,4EAA4E;IAC5E,OAAO,CAAC,WAAW,CAAsB;IACzC;;mFAE+E;IAC/E,OAAO,CAAC,gBAAgB,CAAC,CAAS;IAElC,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,kBAAkB,CAAK;IAC/B,wFAAwF;IACxF,OAAO,CAAC,aAAa,CAAuB;IAE5C,OAAO,CAAC,IAAI,CAAsB;gBAEtB,OAAO,EAAE,mBAAmB;IAWxC;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IAUlB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAiKjC;;;;;;;;OAQG;YACW,iBAAiB;IAoC/B;;OAEG;IACG,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2C5C;;;;;;OAMG;IACG,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,iBAAiB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAU1F;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAsD5B;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAwBtB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IA4CtB;;;;;OAKG;IACH,OAAO,CAAC,uBAAuB;IAsC/B;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAU/B,oFAAoF;YACtE,OAAO;IAkIf,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA8E3D,OAAO,CAAC,mBAAmB,CAAC,EAAE;QAClC,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,IAAI,CAAC;IAwCX,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB3B,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrD;;;;;;OAMG;IACG,MAAM,CAAC,SAAS,SAAI,GAAG,OAAO,CAAC;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IAmCpF;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAK3C,QAAQ,IAAI,iBAAiB;IAY7B,WAAW,IAAI,OAAO;IAItB;;mBAEe;IACf,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,GAAE,iBAAiB,EAAO,GAAG,MAAM;IAKzE,2CAA2C;IAC3C,cAAc,IAAI,MAAM;IAIxB;sFACkF;IAClF,UAAU,IAAI,MAAM;IAOpB,+EAA+E;IAC/E,uBAAuB,IAAI,iBAAiB,EAAE;IAI9C,+EAA+E;IACzE,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAKxD;;;;;OAKG;IACG,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAQjD;;;;;OAKG;IACG,eAAe,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1E,wEAAwE;YAC1D,0BAA0B;IAkBxC,WAAW,IAAI,OAAO,EAAE;IAIxB;;;;OAIG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA6B7C,2EAA2E;IAC3E,gBAAgB,IAAI,aAAa,GAAG,SAAS;IAI7C;gFAC4E;IAC5E,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,GAAG,IAAI;IAIxD,0DAA0D;IAC1D,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAIpC,wEAAwE;IACxE,OAAO,CAAC,gBAAgB;IAIxB;;;uEAGmE;YACrD,iBAAiB;IAkC/B,OAAO,CAAC,iBAAiB;IAMzB,mEAAmE;IACnE,kBAAkB,IAAI,MAAM,GAAG,SAAS;IAIlC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAYhB,gBAAgB;YAOhB,mBAAmB;YAmDnB,qBAAqB;YAIrB,cAAc;IAgB5B,OAAO,CAAC,yBAAyB;CA4ClC"}
@@ -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;
@@ -142,12 +150,13 @@ export class AgentSession {
142
150
  globalAgentsDir: paths.agentsDir,
143
151
  projectDir: this.cwd,
144
152
  });
145
- const { tools, processManager, lspManager } = createTools(this.cwd, {
153
+ const { tools, processManager, rebuildReadTool, lspManager } = await createTools(this.cwd, {
146
154
  agents,
147
155
  skills: this.skills,
148
156
  provider: this.provider,
149
157
  model: this.model,
150
158
  lspDiagnostics: this.settingsManager.get("lspDiagnostics"),
159
+ authStorage: this.authStorage,
151
160
  // Lazy — sessionId/model/provider can change after createTools() runs, so
152
161
  // sub-agent spawns read the current parent state at execution time.
153
162
  getProvider: () => this.provider,
@@ -164,26 +173,22 @@ export class AgentSession {
164
173
  : {}),
165
174
  });
166
175
  this.tools = tools;
176
+ this.rebuildReadTool = rebuildReadTool;
167
177
  this.processManager = processManager;
168
178
  this.lspManager = lspManager;
169
- // 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.
170
186
  this.mcpManager = new MCPClientManager();
171
- try {
172
- let apiKey;
173
- if (this.provider === "glm") {
174
- try {
175
- const glmCreds = await this.authStorage.resolveCredentials("glm");
176
- apiKey = glmCreds.accessToken;
177
- }
178
- catch {
179
- // GLM not configured — skip Z.AI MCP servers
180
- }
181
- }
182
- const mcpTools = await this.mcpManager.connectAll(getMCPServers(this.provider, apiKey));
183
- this.tools.push(...mcpTools);
187
+ if (this.opts.backgroundMcpConnect) {
188
+ void this.connectMcpServers();
184
189
  }
185
- catch (err) {
186
- log("WARN", "mcp", `MCP initialization failed: ${err instanceof Error ? err.message : String(err)}`);
190
+ else {
191
+ await this.connectMcpServers();
187
192
  }
188
193
  const basePrompt = this.customSystemPrompt ??
189
194
  (await buildSystemPrompt(this.cwd, this.skills, false, undefined, this.tools.map((tool) => tool.name), undefined, this.provider));
@@ -253,6 +258,47 @@ export class AgentSession {
253
258
  await this.extensionLoader.loadAll(paths.extensionsDir, extContext);
254
259
  this.eventBus.emit("session_start", { sessionId: this.sessionId });
255
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
+ }
256
302
  /**
257
303
  * Process user input. Handles slash commands or runs agent loop.
258
304
  */
@@ -302,14 +348,59 @@ export class AgentSession {
302
348
  * attachments are always a direct conversational turn.
303
349
  */
304
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) {
305
366
  const parts = [];
306
367
  const fileNotes = [];
368
+ const modelSupportsVideo = getModel(this.model)?.supportsVideo ?? false;
307
369
  for (const a of attachments) {
308
370
  if (a.kind === "image") {
309
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
+ }
310
375
  }
311
376
  else if (a.kind === "video") {
312
- 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
+ }
313
404
  }
314
405
  else if (a.path) {
315
406
  fileNotes.push(`- ${a.name} (saved at ${a.path})`);
@@ -323,13 +414,7 @@ export class AgentSession {
323
414
  }
324
415
  if (textParts.length > 0)
325
416
  parts.unshift({ type: "text", text: textParts.join("\n\n") });
326
- if (parts.length === 0)
327
- return;
328
- const userMessage = { role: "user", content: parts };
329
- this.messages.push(userMessage);
330
- await this.persistMessage(userMessage);
331
- this.lastPersistedIndex = this.messages.length;
332
- await this.runLoop();
417
+ return parts;
333
418
  }
334
419
  /**
335
420
  * Reset per-run self-correction hook state. Mirrors the TUI's run_start
@@ -423,8 +508,18 @@ export class AgentSession {
423
508
  // User steering wins: drain any messages queued during this run first so the
424
509
  // agent sees them mid-loop instead of after it stops.
425
510
  if (this.userQueue.length > 0) {
426
- const merged = this.userQueue.splice(0).join("\n\n");
427
- 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 }];
428
523
  }
429
524
  if (!this.settingsManager.get("idealReviewEnabled"))
430
525
  return null;
@@ -524,7 +619,9 @@ export class AgentSession {
524
619
  defaultHeaders: this.provider === "moonshot" && isKimiCodingEndpoint(effectiveBaseUrl)
525
620
  ? kimiCodingHeaders()
526
621
  : undefined,
527
- 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",
528
625
  promptCacheKey: this.getPromptCacheKey(),
529
626
  supportsImages: modelInfo?.supportsImages,
530
627
  supportsVideo: modelInfo?.supportsVideo,
@@ -543,6 +640,11 @@ export class AgentSession {
543
640
  }
544
641
  };
545
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);
546
648
  await runAgentLoop(creds.accessToken, creds.accountId, creds.projectId);
547
649
  }
548
650
  catch (err) {
@@ -587,7 +689,20 @@ export class AgentSession {
587
689
  // booted on e.g. Kimi (256K) keeps sending that cap after switching to a
588
690
  // smaller model (Opus 128K), which the provider rejects.
589
691
  this.maxTokens = this.resolveMaxTokens(model);
590
- this.eventBus.emit("model_change", { provider: this.provider, model: this.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
+ }
591
706
  // Update provider-specific tools when provider changes
592
707
  if (provider && provider !== prevProvider) {
593
708
  // Add/remove client-side web_search tool based on provider.
@@ -740,19 +855,24 @@ export class AgentSession {
740
855
  getPlanMode() {
741
856
  return this.planModeRef.current;
742
857
  }
743
- /** Queue a user message to be injected mid-run as steering. Returns the new
744
- * queue length. No-op semantics are the caller's concern. */
745
- queueMessage(text) {
746
- 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 });
747
863
  return this.userQueue.length;
748
864
  }
749
865
  /** Number of messages currently queued. */
750
866
  getQueuedCount() {
751
867
  return this.userQueue.length;
752
868
  }
753
- /** 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. */
754
871
  drainQueue() {
755
- return this.userQueue.splice(0).join("\n\n");
872
+ return this.userQueue
873
+ .splice(0)
874
+ .map((m) => m.text)
875
+ .join("\n\n");
756
876
  }
757
877
  /** Snapshot of background processes (bash run_in_background), newest-state. */
758
878
  listBackgroundProcesses() {
@@ -848,6 +968,44 @@ export class AgentSession {
848
968
  setSignal(signal) {
849
969
  this.opts = { ...this.opts, signal };
850
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
+ }
851
1009
  getPromptCacheKey() {
852
1010
  if (this.opts.promptCacheKey)
853
1011
  return this.opts.promptCacheKey;