@kenkaiiii/ggcoder 5.74.2 → 5.75.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/README.md +1 -1
  2. package/assets/skills/bulletproof/SKILL.md +1 -1
  3. package/assets/skills/clarify/SKILL.md +1 -1
  4. package/assets/skills/code-review/SKILL.md +1 -1
  5. package/assets/skills/compliance-guard/SKILL.md +1 -1
  6. package/assets/skills/durable/SKILL.md +1 -1
  7. package/assets/skills/evidence-led-ui/SKILL.md +1 -1
  8. package/assets/skills/lean/SKILL.md +1 -1
  9. package/assets/skills/refactoring/SKILL.md +1 -1
  10. package/assets/skills/root-cause/SKILL.md +1 -1
  11. package/assets/skills/shared-language/SKILL.md +1 -1
  12. package/assets/skills/tdd/SKILL.md +1 -1
  13. package/dist/app-sidecar/app-settings.d.ts +65 -0
  14. package/dist/app-sidecar/app-settings.js +147 -0
  15. package/dist/app-sidecar/app-settings.js.map +1 -0
  16. package/dist/app-sidecar/attachments.d.ts +11 -0
  17. package/dist/app-sidecar/attachments.js +47 -0
  18. package/dist/app-sidecar/attachments.js.map +1 -0
  19. package/dist/app-sidecar/auth-routes.d.ts +3 -0
  20. package/dist/app-sidecar/auth-routes.js +290 -0
  21. package/dist/app-sidecar/auth-routes.js.map +1 -0
  22. package/dist/app-sidecar/autopilot-runner.d.ts +61 -0
  23. package/dist/app-sidecar/autopilot-runner.js +367 -0
  24. package/dist/app-sidecar/autopilot-runner.js.map +1 -0
  25. package/dist/app-sidecar/file-search.d.ts +15 -0
  26. package/dist/app-sidecar/file-search.js +103 -0
  27. package/dist/app-sidecar/file-search.js.map +1 -0
  28. package/dist/app-sidecar/hf-pull-manager.d.ts +20 -0
  29. package/dist/app-sidecar/hf-pull-manager.js +224 -0
  30. package/dist/app-sidecar/hf-pull-manager.js.map +1 -0
  31. package/dist/app-sidecar/history-routes.d.ts +3 -0
  32. package/dist/app-sidecar/history-routes.js +375 -0
  33. package/dist/app-sidecar/history-routes.js.map +1 -0
  34. package/dist/app-sidecar/history.d.ts +67 -0
  35. package/dist/app-sidecar/history.js +23 -0
  36. package/dist/app-sidecar/history.js.map +1 -0
  37. package/dist/app-sidecar/http.d.ts +5 -0
  38. package/dist/app-sidecar/http.js +27 -0
  39. package/dist/app-sidecar/http.js.map +1 -0
  40. package/dist/app-sidecar/integration-routes.d.ts +3 -0
  41. package/dist/app-sidecar/integration-routes.js +286 -0
  42. package/dist/app-sidecar/integration-routes.js.map +1 -0
  43. package/dist/app-sidecar/json-mode.d.ts +10 -0
  44. package/dist/app-sidecar/json-mode.js +73 -0
  45. package/dist/app-sidecar/json-mode.js.map +1 -0
  46. package/dist/app-sidecar/keep-awake.d.ts +4 -0
  47. package/dist/app-sidecar/keep-awake.js +5 -0
  48. package/dist/app-sidecar/keep-awake.js.map +1 -0
  49. package/dist/app-sidecar/ken-context.d.ts +20 -0
  50. package/dist/app-sidecar/ken-context.js +55 -0
  51. package/dist/app-sidecar/ken-context.js.map +1 -0
  52. package/dist/app-sidecar/mcp-routes.d.ts +3 -0
  53. package/dist/app-sidecar/mcp-routes.js +185 -0
  54. package/dist/app-sidecar/mcp-routes.js.map +1 -0
  55. package/dist/app-sidecar/mcp-rows.d.ts +31 -0
  56. package/dist/app-sidecar/mcp-rows.js +77 -0
  57. package/dist/app-sidecar/mcp-rows.js.map +1 -0
  58. package/dist/app-sidecar/progress-manager.d.ts +10 -0
  59. package/dist/app-sidecar/progress-manager.js +116 -0
  60. package/dist/app-sidecar/progress-manager.js.map +1 -0
  61. package/dist/app-sidecar/prompt-routes.d.ts +3 -0
  62. package/dist/app-sidecar/prompt-routes.js +354 -0
  63. package/dist/app-sidecar/prompt-routes.js.map +1 -0
  64. package/dist/app-sidecar/route-context.d.ts +145 -0
  65. package/dist/app-sidecar/route-context.js +2 -0
  66. package/dist/app-sidecar/route-context.js.map +1 -0
  67. package/dist/app-sidecar/run-control-routes.d.ts +3 -0
  68. package/dist/app-sidecar/run-control-routes.js +430 -0
  69. package/dist/app-sidecar/run-control-routes.js.map +1 -0
  70. package/dist/app-sidecar/session-routes.d.ts +3 -0
  71. package/dist/app-sidecar/session-routes.js +115 -0
  72. package/dist/app-sidecar/session-routes.js.map +1 -0
  73. package/dist/app-sidecar/session-types.d.ts +39 -0
  74. package/dist/app-sidecar/session-types.js +5 -0
  75. package/dist/app-sidecar/session-types.js.map +1 -0
  76. package/dist/app-sidecar/task-routes.d.ts +3 -0
  77. package/dist/app-sidecar/task-routes.js +149 -0
  78. package/dist/app-sidecar/task-routes.js.map +1 -0
  79. package/dist/app-sidecar/usage.d.ts +19 -0
  80. package/dist/app-sidecar/usage.js +153 -0
  81. package/dist/app-sidecar/usage.js.map +1 -0
  82. package/dist/app-sidecar/workspace-routes.d.ts +3 -0
  83. package/dist/app-sidecar/workspace-routes.js +244 -0
  84. package/dist/app-sidecar/workspace-routes.js.map +1 -0
  85. package/dist/app-sidecar.js +482 -3695
  86. package/dist/app-sidecar.js.map +1 -1
  87. package/dist/cli.js +2 -2
  88. package/dist/cli.js.map +1 -1
  89. package/dist/config.js +1 -1
  90. package/dist/core/acceptance-checks.js +3 -9
  91. package/dist/core/acceptance-checks.js.map +1 -1
  92. package/dist/core/agent-session/helpers.d.ts +26 -0
  93. package/dist/core/agent-session/helpers.js +47 -0
  94. package/dist/core/agent-session/helpers.js.map +1 -0
  95. package/dist/core/agent-session/mcp-tools.d.ts +68 -0
  96. package/dist/core/agent-session/mcp-tools.js +195 -0
  97. package/dist/core/agent-session/mcp-tools.js.map +1 -0
  98. package/dist/core/agent-session/run-loop-monitor.d.ts +63 -0
  99. package/dist/core/agent-session/run-loop-monitor.js +164 -0
  100. package/dist/core/agent-session/run-loop-monitor.js.map +1 -0
  101. package/dist/core/agent-session/transcript-markers.d.ts +57 -0
  102. package/dist/core/agent-session/transcript-markers.js +166 -0
  103. package/dist/core/agent-session/transcript-markers.js.map +1 -0
  104. package/dist/core/agent-session/verification-tracker.d.ts +46 -0
  105. package/dist/core/agent-session/verification-tracker.js +246 -0
  106. package/dist/core/agent-session/verification-tracker.js.map +1 -0
  107. package/dist/core/agent-session.d.ts +27 -87
  108. package/dist/core/agent-session.js +166 -709
  109. package/dist/core/agent-session.js.map +1 -1
  110. package/dist/core/auth-providers.js +3 -3
  111. package/dist/core/auth-providers.js.map +1 -1
  112. package/dist/core/autopilot-cycle.d.ts +6 -2
  113. package/dist/core/autopilot-cycle.js +1 -1
  114. package/dist/core/autopilot-cycle.js.map +1 -1
  115. package/dist/core/bundled-agents.js +6 -6
  116. package/dist/core/bundled-agents.js.map +1 -1
  117. package/dist/core/checklist-git.d.ts +19 -0
  118. package/dist/core/checklist-git.js +54 -0
  119. package/dist/core/checklist-git.js.map +1 -0
  120. package/dist/core/checklist-items.d.ts +10 -1
  121. package/dist/core/checklist-items.js +22 -0
  122. package/dist/core/checklist-items.js.map +1 -1
  123. package/dist/core/checklist-prompt.d.ts +5 -1
  124. package/dist/core/checklist-prompt.js +16 -5
  125. package/dist/core/checklist-prompt.js.map +1 -1
  126. package/dist/core/checklist-snapshot.d.ts +5 -1
  127. package/dist/core/checklist-snapshot.js +19 -2
  128. package/dist/core/checklist-snapshot.js.map +1 -1
  129. package/dist/core/context-limits.js +1 -1
  130. package/dist/core/context-limits.js.map +1 -1
  131. package/dist/core/plan-handoff.d.ts +30 -0
  132. package/dist/core/plan-handoff.js +10 -0
  133. package/dist/core/plan-handoff.js.map +1 -0
  134. package/dist/core/plan-submission-gate.d.ts +28 -0
  135. package/dist/core/plan-submission-gate.js +27 -0
  136. package/dist/core/plan-submission-gate.js.map +1 -0
  137. package/dist/core/skills.js +6 -9
  138. package/dist/core/skills.js.map +1 -1
  139. package/dist/core/style-packs/index.js +2 -11
  140. package/dist/core/style-packs/index.js.map +1 -1
  141. package/dist/core/style-packs/packs.d.ts +3 -12
  142. package/dist/core/style-packs/packs.js +132 -173
  143. package/dist/core/style-packs/packs.js.map +1 -1
  144. package/dist/system-prompt.js +55 -78
  145. package/dist/system-prompt.js.map +1 -1
  146. package/dist/tools/ask-user.js +13 -74
  147. package/dist/tools/ask-user.js.map +1 -1
  148. package/dist/tools/bash.js +13 -78
  149. package/dist/tools/bash.js.map +1 -1
  150. package/dist/tools/checklist.d.ts +2 -10
  151. package/dist/tools/checklist.js +7 -39
  152. package/dist/tools/checklist.js.map +1 -1
  153. package/dist/tools/code-nav.js +7 -29
  154. package/dist/tools/code-nav.js.map +1 -1
  155. package/dist/tools/edit.d.ts +3 -9
  156. package/dist/tools/edit.js +66 -65
  157. package/dist/tools/edit.js.map +1 -1
  158. package/dist/tools/enter-plan.js +2 -7
  159. package/dist/tools/enter-plan.js.map +1 -1
  160. package/dist/tools/exit-plan.js +2 -3
  161. package/dist/tools/exit-plan.js.map +1 -1
  162. package/dist/tools/find.js +3 -4
  163. package/dist/tools/find.js.map +1 -1
  164. package/dist/tools/grep.js +6 -18
  165. package/dist/tools/grep.js.map +1 -1
  166. package/dist/tools/ls.js +3 -3
  167. package/dist/tools/ls.js.map +1 -1
  168. package/dist/tools/prompt-hints.js +34 -47
  169. package/dist/tools/prompt-hints.js.map +1 -1
  170. package/dist/tools/read.js +8 -20
  171. package/dist/tools/read.js.map +1 -1
  172. package/dist/tools/search-code.js +4 -12
  173. package/dist/tools/search-code.js.map +1 -1
  174. package/dist/tools/skill.js +13 -7
  175. package/dist/tools/skill.js.map +1 -1
  176. package/dist/tools/subagent-control.js +7 -22
  177. package/dist/tools/subagent-control.js.map +1 -1
  178. package/dist/tools/subagent.js +4 -7
  179. package/dist/tools/subagent.js.map +1 -1
  180. package/dist/tools/task-output.js +4 -15
  181. package/dist/tools/task-output.js.map +1 -1
  182. package/dist/tools/task-send.js +5 -17
  183. package/dist/tools/task-send.js.map +1 -1
  184. package/dist/tools/task-stop.js +2 -2
  185. package/dist/tools/task-stop.js.map +1 -1
  186. package/dist/tools/tool-search.js +2 -7
  187. package/dist/tools/tool-search.js.map +1 -1
  188. package/dist/tools/tool-tiers.d.ts +17 -4
  189. package/dist/tools/tool-tiers.js +38 -15
  190. package/dist/tools/tool-tiers.js.map +1 -1
  191. package/dist/tools/web-fetch.js +7 -33
  192. package/dist/tools/web-fetch.js.map +1 -1
  193. package/dist/tools/write.js +3 -4
  194. package/dist/tools/write.js.map +1 -1
  195. package/dist/ui/components/Footer.js +1 -2
  196. package/dist/ui/components/Footer.js.map +1 -1
  197. package/package.json +7 -4
  198. package/dist/core/api-benchmark.d.ts +0 -63
  199. package/dist/core/api-benchmark.js +0 -381
  200. package/dist/core/api-benchmark.js.map +0 -1
  201. package/dist/core/bash-spawn-benchmark.d.ts +0 -1
  202. package/dist/core/bash-spawn-benchmark.js +0 -139
  203. package/dist/core/bash-spawn-benchmark.js.map +0 -1
  204. package/dist/core/cache-warm-benchmark.d.ts +0 -21
  205. package/dist/core/cache-warm-benchmark.js +0 -170
  206. package/dist/core/cache-warm-benchmark.js.map +0 -1
  207. package/dist/core/fast-apply-benchmark.d.ts +0 -25
  208. package/dist/core/fast-apply-benchmark.js +0 -284
  209. package/dist/core/fast-apply-benchmark.js.map +0 -1
  210. package/dist/core/hashline-edit-benchmark.d.ts +0 -88
  211. package/dist/core/hashline-edit-benchmark.js +0 -518
  212. package/dist/core/hashline-edit-benchmark.js.map +0 -1
  213. package/dist/core/semantic-search-benchmark.d.ts +0 -36
  214. package/dist/core/semantic-search-benchmark.js +0 -211
  215. package/dist/core/semantic-search-benchmark.js.map +0 -1
  216. package/dist/core/speed-benchmark.d.ts +0 -132
  217. package/dist/core/speed-benchmark.js +0 -410
  218. package/dist/core/speed-benchmark.js.map +0 -1
@@ -0,0 +1,195 @@
1
+ import { z } from "zod";
2
+ import { log } from "../logger.js";
3
+ import { MCPClientManager } from "../mcp/index.js";
4
+ import { clampMcpToolDescription, DeferredToolCatalog } from "../mcp/deferred-catalog.js";
5
+ import { McpCatalogCache } from "../mcp/catalog-cache.js";
6
+ import { createToolSearchTool } from "../../tools/tool-search.js";
7
+ /**
8
+ * MCP tool wiring for one session: the client manager, the deferred
9
+ * `tool_search` catalog (shared with deferred built-ins), cached-catalog stubs
10
+ * and the live/cached reconcile maps.
11
+ *
12
+ * Never owns the tool array — it reads the session's CURRENT array through
13
+ * `getTools` (the session may filter-replace it) and only appends to it or
14
+ * swaps same-named entries in place, so the cached tool prefix stays stable.
15
+ */
16
+ export class McpToolRegistry {
17
+ getTools;
18
+ getContextLimits;
19
+ manager;
20
+ /** Deferred MCP tools awaiting discovery via tool_search. */
21
+ catalog;
22
+ /** Live (connected) MCP tools by name — the reconcile target for cached stubs. */
23
+ liveTools = new Map();
24
+ /** Server name for each cached-only tool, so a stub knows what to wait on. */
25
+ cachedToolServers = new Map();
26
+ catalogCache = new McpCatalogCache();
27
+ constructor(getTools, getContextLimits) {
28
+ this.getTools = getTools;
29
+ this.getContextLimits = getContextLimits;
30
+ }
31
+ createManager(options) {
32
+ this.manager = new MCPClientManager({
33
+ catalogCache: this.catalogCache,
34
+ modernProtocol: options.modernProtocol,
35
+ onElicit: options.onElicit,
36
+ });
37
+ }
38
+ ensureCatalog() {
39
+ this.catalog ??= new DeferredToolCatalog(this.getContextLimits());
40
+ return this.catalog;
41
+ }
42
+ /** Hold tools in the tool_search catalog and make sure tool_search exists. */
43
+ addDeferred(tools) {
44
+ this.ensureCatalog().add(tools);
45
+ this.ensureToolSearchTool();
46
+ }
47
+ /** Pull named entries out of the catalog (empty when no catalog exists). */
48
+ promote(names) {
49
+ return this.catalog?.promote(names) ?? [];
50
+ }
51
+ /** Forget every MCP tool before a provider-switch reconnect re-adds them. */
52
+ clearMcpTools() {
53
+ this.catalog?.removeWhere((name) => name.startsWith("mcp__"));
54
+ this.liveTools.clear();
55
+ this.cachedToolServers.clear();
56
+ }
57
+ /**
58
+ * Route freshly connected MCP tools: deferred into the tool_search catalog
59
+ * (default — keeps ~8k tokens of schema out of every cache-miss turn) or
60
+ * pushed eagerly when `defer` is false.
61
+ * Promotion pushes onto the live tools array the running agent loop
62
+ * re-reads every turn, so promoted tools are callable on the next step.
63
+ */
64
+ addMcpTools(mcpTools, defer) {
65
+ if (mcpTools.length === 0)
66
+ return;
67
+ for (const tool of mcpTools) {
68
+ this.liveTools.set(tool.name, tool);
69
+ this.cachedToolServers.delete(tool.name);
70
+ }
71
+ if (!defer) {
72
+ // Eager path bypasses the catalog, so budget descriptions here too.
73
+ this.replaceOrPushTools(mcpTools.map((tool) => clampMcpToolDescription(tool, this.getContextLimits())));
74
+ return;
75
+ }
76
+ // `add` is name-keyed, so live definitions replace cached stubs in place.
77
+ this.ensureCatalog().add(mcpTools);
78
+ // A stub the model already promoted lives in the tools array; swap it for
79
+ // the live tool so later calls dispatch directly instead of through the stub.
80
+ this.replaceLivePromotedTools(mcpTools);
81
+ this.ensureToolSearchTool();
82
+ }
83
+ /**
84
+ * Register `tool_search` once. Promotion of a cached-only entry waits for its
85
+ * server so the model is told immediately when that capability turns out to
86
+ * be unreachable, instead of promoting a tool that fails on first call.
87
+ *
88
+ * The catalog is created on demand rather than required up front: deferred
89
+ * built-in tools populate it with zero MCP servers connected, so gating
90
+ * registration on an existing catalog would leave those tools unreachable.
91
+ */
92
+ ensureToolSearchTool() {
93
+ const catalog = this.ensureCatalog();
94
+ if (this.getTools().some((t) => t.name === "tool_search"))
95
+ return;
96
+ this.getTools().push(createToolSearchTool(catalog, (promoted) => {
97
+ this.getTools().push(...promoted);
98
+ }, async (toolName) => {
99
+ if (this.liveTools.has(toolName))
100
+ return undefined;
101
+ const serverName = this.cachedToolServers.get(toolName);
102
+ if (!serverName)
103
+ return undefined;
104
+ const outcome = (await this.manager?.whenConnected(serverName)) ?? {
105
+ ok: false,
106
+ error: "MCP is disabled for this session",
107
+ };
108
+ return outcome.ok
109
+ ? { serverName, ok: true }
110
+ : { serverName, ok: false, error: outcome.error };
111
+ }, this.getContextLimits()));
112
+ }
113
+ /** Append tools, replacing any same-named entry (cached stub → live tool). */
114
+ replaceOrPushTools(tools) {
115
+ const live = this.getTools();
116
+ for (const tool of tools) {
117
+ const index = live.findIndex((t) => t.name === tool.name);
118
+ if (index >= 0)
119
+ live[index] = tool;
120
+ else
121
+ live.push(tool);
122
+ }
123
+ }
124
+ /** Swap already-promoted cached stubs for their live equivalents, in place. */
125
+ replaceLivePromotedTools(tools) {
126
+ const live = this.getTools();
127
+ for (const tool of tools) {
128
+ const index = live.findIndex((t) => t.name === tool.name);
129
+ if (index >= 0)
130
+ live[index] = tool;
131
+ }
132
+ }
133
+ /**
134
+ * Publish cached tool definitions into the deferred catalog so `tool_search`
135
+ * answers correctly on turn 1. A cached stub carries the real name, one-line
136
+ * description and input schema; calling it waits for the live connection and
137
+ * then dispatches against the real client, or returns a clear error when that
138
+ * server ultimately failed. Live tools replace stubs on connect.
139
+ */
140
+ async seedFromCache(servers) {
141
+ let entries;
142
+ try {
143
+ entries = await this.catalogCache.entriesFor(servers);
144
+ }
145
+ catch {
146
+ return;
147
+ }
148
+ const stubs = [];
149
+ for (const [serverName, entry] of entries) {
150
+ for (const cached of entry.tools) {
151
+ if (this.liveTools.has(cached.name))
152
+ continue;
153
+ this.cachedToolServers.set(cached.name, serverName);
154
+ stubs.push(this.buildCachedMcpTool(serverName, cached));
155
+ }
156
+ }
157
+ if (stubs.length === 0)
158
+ return;
159
+ log("INFO", "mcp", "Seeded deferred tool catalog from cache", {
160
+ tools: String(stubs.length),
161
+ servers: String(entries.size),
162
+ });
163
+ // Catalog-only registration for cached stubs — never marks them live.
164
+ this.addDeferred(stubs);
165
+ }
166
+ buildCachedMcpTool(serverName, cached) {
167
+ return {
168
+ name: cached.name,
169
+ description: cached.description,
170
+ parameters: z.record(z.string(), z.unknown()),
171
+ rawInputSchema: cached.rawInputSchema,
172
+ execute: async (args, context) => {
173
+ const live = this.liveTools.get(cached.name);
174
+ if (live)
175
+ return live.execute(args, context);
176
+ const outcome = (await this.manager?.whenConnected(serverName)) ?? {
177
+ ok: false,
178
+ error: "MCP is disabled for this session",
179
+ };
180
+ if (!outcome.ok) {
181
+ return (`MCP tool ${cached.name} is unavailable: server "${serverName}" did not connect ` +
182
+ `(${outcome.error}). This tool was offered from a cached catalog. ` +
183
+ `Use a different approach or ask the user to check their MCP configuration.`);
184
+ }
185
+ const connected = this.liveTools.get(cached.name);
186
+ if (!connected) {
187
+ return (`MCP tool ${cached.name} no longer exists: server "${serverName}" connected but ` +
188
+ `does not expose it. The cached catalog entry was stale.`);
189
+ }
190
+ return connected.execute(args, context);
191
+ },
192
+ };
193
+ }
194
+ }
195
+ //# sourceMappingURL=mcp-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-tools.js","sourceRoot":"","sources":["../../../src/core/agent-session/mcp-tools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import { CycleDetector, ToolCallProgressTracker, type CycleDetection, type LoopBreakDecision } from "../loop-breaker.js";
2
+ import { type SemanticCallDigest, type SemanticLoopVerdict } from "../semantic-loop-check.js";
3
+ /** LLM-judged loop detection state for one run. */
4
+ export interface SemanticLoopRunState {
5
+ checksUsed: number;
6
+ lastCheckTurn: number;
7
+ pending: boolean;
8
+ verdict: SemanticLoopVerdict | null;
9
+ injected: boolean;
10
+ }
11
+ /**
12
+ * Per-run stuck-detection state (mirrors the TUI's useAgentLoop refs): tool
13
+ * stats, streamed text, progress/cycle trackers, the two-stage loop-breaker and
14
+ * the semantic loop judge's budget. Reset at the start of every run; observed
15
+ * from the event stream. Owns state and decisions only — logging, events and
16
+ * the judge call stay with the session.
17
+ */
18
+ export declare class RunLoopMonitor {
19
+ stats: {
20
+ toolCalls: number;
21
+ toolFailures: number;
22
+ turns: number;
23
+ };
24
+ text: string;
25
+ consecutiveFailures: number;
26
+ repeatedNoProgressCalls: number;
27
+ readonly progressTracker: ToolCallProgressTracker;
28
+ readonly cycleDetector: CycleDetector;
29
+ cyclicPattern: CycleDetection | null;
30
+ /** 0 = none; 1 = first nudge sent; 2 = final stop-and-report injected. */
31
+ loopBreakInjected: 0 | 1 | 2;
32
+ /** Recent tool-call digests for the semantic loop judge — bounded ring. */
33
+ recentCalls: SemanticCallDigest[];
34
+ /** LLM-judged loop detection state. `verdict` holds a LOOP verdict awaiting
35
+ * injection at the next steering poll; judge failures fail open (no
36
+ * injection) and still consume budget + cooldown. Replaced (never mutated)
37
+ * on reset so a late judge from an earlier run cannot write into it. */
38
+ semanticLoop: SemanticLoopRunState;
39
+ /** Clean slate for a new run. */
40
+ reset(): void;
41
+ recordText(text: string): void;
42
+ recordToolEnd(name: string, args: Record<string, unknown> | undefined, result: string, isError: boolean): void;
43
+ recordTurn(turn: number): void;
44
+ /** Deterministic stuck verdict from the current signals. */
45
+ evaluateLoopBreak(): LoopBreakDecision;
46
+ /**
47
+ * Two-stage loop-breaker: stage 1 nudges; a FRESH detection after that
48
+ * escalates to stage 2. Returns the stage to inject (signals reset so stage 2
49
+ * only fires on new evidence), or null when nothing should be injected.
50
+ */
51
+ takeLoopBreakStage(decision: LoopBreakDecision): 1 | 2 | null;
52
+ /** Consume a pending semantic LOOP verdict, exactly once per run. */
53
+ takeSemanticVerdict(): SemanticLoopVerdict | null;
54
+ shouldRunSemanticCheck(deterministicBreak: boolean): boolean;
55
+ /** Mark a judge call in flight; the returned run state is its stale-guard token. */
56
+ beginSemanticCheck(): SemanticLoopRunState;
57
+ /** Publish a judge verdict only if its run is still the current one. */
58
+ publishSemanticVerdict(runState: SemanticLoopRunState, verdict: SemanticLoopVerdict): void;
59
+ /** Settle a judge call: consume budget + cooldown, unless its run is stale. */
60
+ settleSemanticCheck(runState: SemanticLoopRunState): void;
61
+ /** Reasons to refuse a turn-budget extension; empty means grant. */
62
+ turnExtensionRefusals(): string[];
63
+ }
@@ -0,0 +1,164 @@
1
+ import { evaluateLoopBreak, CycleDetector, ToolCallProgressTracker, detectTextRepetition, } from "../loop-breaker.js";
2
+ import { MAX_SEMANTIC_LOOP_CALLS, shouldRunSemanticLoopCheck, } from "../semantic-loop-check.js";
3
+ import { TURN_EXTENSION_MAX_FAILURE_RATIO } from "./helpers.js";
4
+ function freshSemanticLoopState() {
5
+ return { checksUsed: 0, lastCheckTurn: 0, pending: false, verdict: null, injected: false };
6
+ }
7
+ /**
8
+ * Per-run stuck-detection state (mirrors the TUI's useAgentLoop refs): tool
9
+ * stats, streamed text, progress/cycle trackers, the two-stage loop-breaker and
10
+ * the semantic loop judge's budget. Reset at the start of every run; observed
11
+ * from the event stream. Owns state and decisions only — logging, events and
12
+ * the judge call stay with the session.
13
+ */
14
+ export class RunLoopMonitor {
15
+ stats = { toolCalls: 0, toolFailures: 0, turns: 0 };
16
+ text = "";
17
+ consecutiveFailures = 0;
18
+ repeatedNoProgressCalls = 0;
19
+ progressTracker = new ToolCallProgressTracker();
20
+ cycleDetector = new CycleDetector();
21
+ cyclicPattern = null;
22
+ /** 0 = none; 1 = first nudge sent; 2 = final stop-and-report injected. */
23
+ loopBreakInjected = 0;
24
+ /** Recent tool-call digests for the semantic loop judge — bounded ring. */
25
+ recentCalls = [];
26
+ /** LLM-judged loop detection state. `verdict` holds a LOOP verdict awaiting
27
+ * injection at the next steering poll; judge failures fail open (no
28
+ * injection) and still consume budget + cooldown. Replaced (never mutated)
29
+ * on reset so a late judge from an earlier run cannot write into it. */
30
+ semanticLoop = freshSemanticLoopState();
31
+ /** Clean slate for a new run. */
32
+ reset() {
33
+ this.stats = { toolCalls: 0, toolFailures: 0, turns: 0 };
34
+ this.text = "";
35
+ this.consecutiveFailures = 0;
36
+ this.repeatedNoProgressCalls = 0;
37
+ this.progressTracker.reset();
38
+ this.cycleDetector.reset();
39
+ this.cyclicPattern = null;
40
+ this.loopBreakInjected = 0;
41
+ this.recentCalls = [];
42
+ this.semanticLoop = freshSemanticLoopState();
43
+ }
44
+ recordText(text) {
45
+ this.text += text;
46
+ }
47
+ recordToolEnd(name, args, result, isError) {
48
+ this.stats.toolCalls += 1;
49
+ if (isError)
50
+ this.stats.toolFailures += 1;
51
+ this.consecutiveFailures = isError ? this.consecutiveFailures + 1 : 0;
52
+ this.repeatedNoProgressCalls = this.progressTracker.record(name, args, result, isError);
53
+ this.cyclicPattern = this.cycleDetector.record(name, args, result, isError);
54
+ // Semantic-loop judge input: a bounded digest of WHAT was attempted and
55
+ // HOW it came out. Args/results are sliced AT RECORD TIME — a write with
56
+ // a 50 KiB payload or a bash dump must never inflate the ring, and the
57
+ // judge needs shapes, not payloads.
58
+ this.recentCalls.push({
59
+ tool: name,
60
+ args: args === undefined ? "" : JSON.stringify(args).slice(0, 300),
61
+ ok: !isError,
62
+ result: result.slice(0, 400),
63
+ });
64
+ if (this.recentCalls.length > MAX_SEMANTIC_LOOP_CALLS) {
65
+ this.recentCalls.splice(0, this.recentCalls.length - MAX_SEMANTIC_LOOP_CALLS);
66
+ }
67
+ }
68
+ recordTurn(turn) {
69
+ this.stats.turns = turn;
70
+ }
71
+ /** Deterministic stuck verdict from the current signals. */
72
+ evaluateLoopBreak() {
73
+ return evaluateLoopBreak({
74
+ consecutiveFailures: this.consecutiveFailures,
75
+ repeatedNoProgressCalls: this.repeatedNoProgressCalls,
76
+ textRepetitionDetected: detectTextRepetition(this.text),
77
+ ...(this.cyclicPattern ? { cyclicPattern: this.cyclicPattern } : {}),
78
+ });
79
+ }
80
+ /**
81
+ * Two-stage loop-breaker: stage 1 nudges; a FRESH detection after that
82
+ * escalates to stage 2. Returns the stage to inject (signals reset so stage 2
83
+ * only fires on new evidence), or null when nothing should be injected.
84
+ */
85
+ takeLoopBreakStage(decision) {
86
+ if (this.loopBreakInjected >= 2)
87
+ return null;
88
+ if (!decision.shouldBreak)
89
+ return null;
90
+ const stage = this.loopBreakInjected === 0 ? 1 : 2;
91
+ this.loopBreakInjected = stage;
92
+ this.progressTracker.reset();
93
+ this.cycleDetector.reset();
94
+ this.cyclicPattern = null;
95
+ this.consecutiveFailures = 0;
96
+ this.repeatedNoProgressCalls = 0;
97
+ // The deterministic breaker owns this burst — a semantic verdict from
98
+ // the same burst must not double-correct on the next poll.
99
+ this.semanticLoop.verdict = null;
100
+ // Clear the text buffer too — otherwise a stage-1 text-repetition
101
+ // trigger still sees the same repeated tail on the next check and
102
+ // escalates to stage 2 on stale evidence.
103
+ this.text = "";
104
+ return stage;
105
+ }
106
+ /** Consume a pending semantic LOOP verdict, exactly once per run. */
107
+ takeSemanticVerdict() {
108
+ if (!this.semanticLoop.verdict || this.semanticLoop.injected)
109
+ return null;
110
+ const verdict = this.semanticLoop.verdict;
111
+ this.semanticLoop.injected = true;
112
+ this.semanticLoop.verdict = null;
113
+ // The judged burst has been addressed; a fresh burst must re-accumulate.
114
+ this.consecutiveFailures = 0;
115
+ return verdict;
116
+ }
117
+ shouldRunSemanticCheck(deterministicBreak) {
118
+ return shouldRunSemanticLoopCheck({
119
+ consecutiveFailures: this.consecutiveFailures,
120
+ totalFailures: this.stats.toolFailures,
121
+ turns: this.stats.turns,
122
+ lastCheckTurn: this.semanticLoop.lastCheckTurn,
123
+ checksUsed: this.semanticLoop.checksUsed,
124
+ checkPending: this.semanticLoop.pending,
125
+ deterministicBreak,
126
+ });
127
+ }
128
+ /** Mark a judge call in flight; the returned run state is its stale-guard token. */
129
+ beginSemanticCheck() {
130
+ const runState = this.semanticLoop;
131
+ runState.pending = true;
132
+ return runState;
133
+ }
134
+ /** Publish a judge verdict only if its run is still the current one. */
135
+ publishSemanticVerdict(runState, verdict) {
136
+ if (this.semanticLoop === runState)
137
+ runState.verdict = verdict;
138
+ }
139
+ /** Settle a judge call: consume budget + cooldown, unless its run is stale. */
140
+ settleSemanticCheck(runState) {
141
+ if (this.semanticLoop === runState) {
142
+ runState.pending = false;
143
+ runState.checksUsed += 1;
144
+ runState.lastCheckTurn = this.stats.turns;
145
+ }
146
+ }
147
+ /** Reasons to refuse a turn-budget extension; empty means grant. */
148
+ turnExtensionRefusals() {
149
+ const refusals = [];
150
+ const stuck = this.evaluateLoopBreak();
151
+ if (stuck.shouldBreak)
152
+ refusals.push(...stuck.reasons);
153
+ // Stage 2 means the loop-breaker already detected spinning twice and told
154
+ // the agent to stop and report. Do not overrule that with more turns.
155
+ if (this.loopBreakInjected >= 2)
156
+ refusals.push("loop-breaker already escalated");
157
+ const { toolCalls, toolFailures } = this.stats;
158
+ if (toolCalls > 0 && toolFailures / toolCalls > TURN_EXTENSION_MAX_FAILURE_RATIO) {
159
+ refusals.push(`${toolFailures}/${toolCalls} tool calls failed`);
160
+ }
161
+ return refusals;
162
+ }
163
+ }
164
+ //# sourceMappingURL=run-loop-monitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-loop-monitor.js","sourceRoot":"","sources":["../../../src/core/agent-session/run-loop-monitor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import { type KenTurnPayload, type AutopilotMarkerPayload, type AppMarkerPayload, type SessionEntry, type SessionManager, type TurnMetricPayload } from "../session-manager.js";
2
+ import type { CompactionAnchorRemap } from "../compaction/compactor.js";
3
+ /**
4
+ * Display-only transcript history recorded against a session file: Ken Kai
5
+ * turns, autopilot verdicts, app markers and per-turn metrics. NEVER part of
6
+ * the LLM message history — persisted as `custom` entries (parentId null, so
7
+ * never on the message DAG) and reloaded on resume so the transcript renders
8
+ * the same rows the live run showed. Persistence is a no-op while the session
9
+ * has no file (transient sessions keep markers in memory only).
10
+ */
11
+ export declare class TranscriptMarkers {
12
+ private readonly getSessionManager;
13
+ private readonly getSessionPath;
14
+ private kenTurns;
15
+ private autopilotMarkers;
16
+ private appMarkers;
17
+ private turnMetrics;
18
+ constructor(getSessionManager: () => SessionManager, getSessionPath: () => string);
19
+ getKenTurns(): KenTurnPayload[];
20
+ getAutopilotMarkers(): AutopilotMarkerPayload[];
21
+ getAppMarkers(): AppMarkerPayload[];
22
+ getTurnMetrics(): TurnMetricPayload[];
23
+ /** Summed cost when every recorded turn has a known price, else undefined. */
24
+ knownCostUsd(): number | undefined;
25
+ /** Drop everything — display-only history belongs to the OLD session. */
26
+ clear(): void;
27
+ /**
28
+ * Restore from a loaded session file. The leaf is passed so each marker also
29
+ * carries its FILE-order position, the fallback used when a legacy anchor is
30
+ * out of range (see RecordedPosition).
31
+ */
32
+ restore(entries: SessionEntry[], leafId: string | null | undefined): void;
33
+ /**
34
+ * Rebase every transcript anchor (Ken turns, autopilot verdicts, app markers)
35
+ * onto a freshly compacted message list.
36
+ */
37
+ remapAnchors(remap: CompactionAnchorRemap | undefined): void;
38
+ recordKenTurn(question: string, reply: string, afterMessageCount: number): Promise<void>;
39
+ recordAutopilotMarker(phase: AutopilotMarkerPayload["phase"], extra: {
40
+ reason?: string;
41
+ body?: string;
42
+ } | undefined, afterMessageCount: number): Promise<void>;
43
+ recordAppMarker(kind: AppMarkerPayload["kind"], data: Record<string, unknown>, afterMessageCount: number): Promise<void>;
44
+ /** Keep a turn metric in memory; {@link persistTurnMetric} writes it. */
45
+ addTurnMetric(payload: TurnMetricPayload): void;
46
+ persistTurnMetric(payload: TurnMetricPayload): Promise<void>;
47
+ /**
48
+ * Re-append everything to the current session file. Called after a
49
+ * continuation/compaction file is created so display-only history isn't lost
50
+ * when the session is rewritten (those rewrites only re-persist messages).
51
+ * Each marker keeps its original `afterMessageCount` anchor.
52
+ */
53
+ rePersistAll(): Promise<void>;
54
+ private rePersistTurnMetrics;
55
+ private rePersist;
56
+ private append;
57
+ }
@@ -0,0 +1,166 @@
1
+ import crypto from "node:crypto";
2
+ import { KEN_TURN_CUSTOM_KIND, AUTOPILOT_MARKER_CUSTOM_KIND, APP_MARKER_CUSTOM_KIND, } from "../session-manager.js";
3
+ import { remapAnchorForCompaction, stripRecordedPosition } from "../session-history.js";
4
+ /**
5
+ * Display-only transcript history recorded against a session file: Ken Kai
6
+ * turns, autopilot verdicts, app markers and per-turn metrics. NEVER part of
7
+ * the LLM message history — persisted as `custom` entries (parentId null, so
8
+ * never on the message DAG) and reloaded on resume so the transcript renders
9
+ * the same rows the live run showed. Persistence is a no-op while the session
10
+ * has no file (transient sessions keep markers in memory only).
11
+ */
12
+ export class TranscriptMarkers {
13
+ getSessionManager;
14
+ getSessionPath;
15
+ // Ken Kai (mentor agent) turns. Each carries the non-system message count at
16
+ // record time so the webview can interleave them chronologically.
17
+ kenTurns = [];
18
+ // Autopilot Ken (auto-reviewer) verdict markers (prompted / done / human /
19
+ // capped), so a resumed session shows the identical Ken bubble the live run
20
+ // showed instead of dropping it or replaying a raw verdict.
21
+ autopilotMarkers = [];
22
+ // Generic app transcript markers (plan-mode banner, task header, error rows,
23
+ // user-bubble display hints).
24
+ appMarkers = [];
25
+ turnMetrics = [];
26
+ constructor(getSessionManager, getSessionPath) {
27
+ this.getSessionManager = getSessionManager;
28
+ this.getSessionPath = getSessionPath;
29
+ }
30
+ getKenTurns() {
31
+ return this.kenTurns;
32
+ }
33
+ getAutopilotMarkers() {
34
+ return this.autopilotMarkers;
35
+ }
36
+ getAppMarkers() {
37
+ return this.appMarkers;
38
+ }
39
+ getTurnMetrics() {
40
+ return this.turnMetrics.map((metric) => ({
41
+ ...metric,
42
+ usage: { ...metric.usage },
43
+ timing: { ...metric.timing },
44
+ cost: { ...metric.cost },
45
+ }));
46
+ }
47
+ /** Summed cost when every recorded turn has a known price, else undefined. */
48
+ knownCostUsd() {
49
+ return this.turnMetrics.length > 0 && this.turnMetrics.every((m) => m.cost.status === "known")
50
+ ? this.turnMetrics.reduce((sum, m) => sum + (m.cost.status === "known" ? m.cost.usd : 0), 0)
51
+ : undefined;
52
+ }
53
+ /** Drop everything — display-only history belongs to the OLD session. */
54
+ clear() {
55
+ this.kenTurns = [];
56
+ this.autopilotMarkers = [];
57
+ this.appMarkers = [];
58
+ this.turnMetrics = [];
59
+ }
60
+ /**
61
+ * Restore from a loaded session file. The leaf is passed so each marker also
62
+ * carries its FILE-order position, the fallback used when a legacy anchor is
63
+ * out of range (see RecordedPosition).
64
+ */
65
+ restore(entries, leafId) {
66
+ const sessionManager = this.getSessionManager();
67
+ this.kenTurns = sessionManager.getKenTurns(entries, leafId);
68
+ this.autopilotMarkers = sessionManager.getAutopilotMarkers(entries, leafId);
69
+ this.appMarkers = sessionManager.getAppMarkers(entries, leafId);
70
+ this.turnMetrics = sessionManager.getTurnMetrics(entries);
71
+ }
72
+ /**
73
+ * Rebase every transcript anchor (Ken turns, autopilot verdicts, app markers)
74
+ * onto a freshly compacted message list.
75
+ */
76
+ remapAnchors(remap) {
77
+ if (!remap)
78
+ return;
79
+ const move = (payload) => ({
80
+ ...payload,
81
+ afterMessageCount: remapAnchorForCompaction(payload.afterMessageCount, remap),
82
+ });
83
+ this.kenTurns = this.kenTurns.map(move);
84
+ this.autopilotMarkers = this.autopilotMarkers.map(move);
85
+ this.appMarkers = this.appMarkers.map(move);
86
+ }
87
+ async recordKenTurn(question, reply, afterMessageCount) {
88
+ const payload = { version: 1, question, reply, afterMessageCount };
89
+ this.kenTurns.push(payload);
90
+ await this.append(KEN_TURN_CUSTOM_KIND, payload);
91
+ }
92
+ async recordAutopilotMarker(phase, extra, afterMessageCount) {
93
+ const payload = {
94
+ version: 1,
95
+ phase,
96
+ afterMessageCount,
97
+ ...(extra?.reason !== undefined ? { reason: extra.reason } : {}),
98
+ ...(extra?.body !== undefined ? { body: extra.body } : {}),
99
+ };
100
+ this.autopilotMarkers.push(payload);
101
+ await this.append(AUTOPILOT_MARKER_CUSTOM_KIND, payload);
102
+ }
103
+ async recordAppMarker(kind, data, afterMessageCount) {
104
+ const payload = { version: 1, kind, afterMessageCount, data };
105
+ this.appMarkers.push(payload);
106
+ await this.append(APP_MARKER_CUSTOM_KIND, payload);
107
+ }
108
+ /** Keep a turn metric in memory; {@link persistTurnMetric} writes it. */
109
+ addTurnMetric(payload) {
110
+ this.turnMetrics.push(payload);
111
+ }
112
+ async persistTurnMetric(payload) {
113
+ const sessionPath = this.getSessionPath();
114
+ if (sessionPath)
115
+ await this.getSessionManager().appendTurnMetric(sessionPath, payload);
116
+ }
117
+ /**
118
+ * Re-append everything to the current session file. Called after a
119
+ * continuation/compaction file is created so display-only history isn't lost
120
+ * when the session is rewritten (those rewrites only re-persist messages).
121
+ * Each marker keeps its original `afterMessageCount` anchor.
122
+ */
123
+ async rePersistAll() {
124
+ await this.rePersistTurnMetrics();
125
+ await this.rePersist(KEN_TURN_CUSTOM_KIND, this.kenTurns);
126
+ await this.rePersist(AUTOPILOT_MARKER_CUSTOM_KIND, this.autopilotMarkers);
127
+ await this.rePersist(APP_MARKER_CUSTOM_KIND, this.appMarkers);
128
+ }
129
+ async rePersistTurnMetrics() {
130
+ if (!this.getSessionPath())
131
+ return;
132
+ for (const metric of this.turnMetrics) {
133
+ await this.getSessionManager().appendTurnMetric(this.getSessionPath(), metric);
134
+ }
135
+ }
136
+ async rePersist(kind, payloads) {
137
+ if (!this.getSessionPath())
138
+ return;
139
+ for (const payload of payloads) {
140
+ const entry = {
141
+ type: "custom",
142
+ kind,
143
+ id: crypto.randomUUID(),
144
+ parentId: null,
145
+ timestamp: new Date().toISOString(),
146
+ data: stripRecordedPosition(payload),
147
+ };
148
+ await this.getSessionManager().appendEntry(this.getSessionPath(), entry);
149
+ }
150
+ }
151
+ async append(kind, payload) {
152
+ const sessionPath = this.getSessionPath();
153
+ if (!sessionPath)
154
+ return;
155
+ const entry = {
156
+ type: "custom",
157
+ kind,
158
+ id: crypto.randomUUID(),
159
+ parentId: null,
160
+ timestamp: new Date().toISOString(),
161
+ data: payload,
162
+ };
163
+ await this.getSessionManager().appendEntry(sessionPath, entry);
164
+ }
165
+ }
166
+ //# sourceMappingURL=transcript-markers.js.map