@ponythewhite/base-context 1.0.0 → 1.0.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 (285) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/NOTICE +0 -5
  3. package/README.md +44 -27
  4. package/dist/NOTICE +0 -5
  5. package/dist/base-context-runtime/pyproject.toml +1 -1
  6. package/dist/base-context-runtime/src/rlm/__init__.py +7 -5
  7. package/dist/base-context-runtime/test/test_subagent_registry.py +28 -1
  8. package/dist/base-context-runtime/uv.lock +1 -1
  9. package/dist/build-info.json +1 -1
  10. package/dist/bundle/amazon-bedrock.js +21 -0
  11. package/dist/bundle/{anthropic-YY4S534V.js → anthropic-UV5GZDGU.js} +5 -5
  12. package/dist/bundle/{azure-openai-responses-JRP6MIM3.js → azure-openai-responses-WSNPXLYC.js} +6 -6
  13. package/dist/bundle/{bundled-modules-P5HARVZA.js → bundled-modules-TXUMJ2XQ.js} +71 -16
  14. package/dist/bundle/chunk-27QCJ2AZ.js +2578 -0
  15. package/dist/bundle/{chunk-MZHTNNVA.js → chunk-4GXPNXOJ.js} +8 -0
  16. package/dist/bundle/{chunk-SC5KG5R4.js → chunk-DGNX7HQ3.js} +1 -7
  17. package/dist/bundle/{chunk-A53O363M.js → chunk-GRJFXLBM.js} +0 -1
  18. package/dist/bundle/chunk-HKINIBP4.js +29 -0
  19. package/dist/bundle/{chunk-UDSW6FNG.js → chunk-KTNETTHI.js} +1 -1
  20. package/dist/bundle/{chunk-BYVP7LWV.js → chunk-S3INFPZZ.js} +1861 -4662
  21. package/dist/bundle/{chunk-73DCPU3D.js → chunk-V3L54UD6.js} +1019 -3427
  22. package/dist/bundle/{chunk-U7AUVG3B.js → chunk-VSIYFWYI.js} +24 -6
  23. package/dist/bundle/{chunk-IW77OXFS.js → chunk-W46VZFI6.js} +0 -2099
  24. package/dist/bundle/{cli-main-O4X7GFGY.js → cli-main-BYMSRPH6.js} +8 -6
  25. package/dist/bundle/cli.js +2 -2
  26. package/dist/bundle/{google-AUTVGDGZ.js → google-T32FMD4G.js} +6 -6
  27. package/dist/bundle/{google-vertex-Q75D7RKS.js → google-vertex-56KP6I3B.js} +3 -3
  28. package/dist/bundle/{main-XKOW2BV6.js → main-ODOYCEQC.js} +7 -5
  29. package/dist/bundle/{mistral-7ESB7QUD.js → mistral-WGBMYIVU.js} +5 -5
  30. package/dist/bundle/{openai-codex-responses-LV3QRPN4.js → openai-codex-responses-PIV2RQ3G.js} +6 -6
  31. package/dist/bundle/{openai-completions-OJECLPXJ.js → openai-completions-64C3JPR4.js} +11 -22
  32. package/dist/bundle/{openai-responses-FN6HZMBC.js → openai-responses-UI7RSTDV.js} +6 -6
  33. package/dist/cli/args.d.ts.map +1 -1
  34. package/dist/cli/args.js +108 -59
  35. package/dist/cli/args.js.map +1 -1
  36. package/dist/cli/daemon-ps.d.ts +4 -3
  37. package/dist/cli/daemon-ps.d.ts.map +1 -1
  38. package/dist/cli/daemon-ps.js +6 -1
  39. package/dist/cli/daemon-ps.js.map +1 -1
  40. package/dist/cli/product-doctor.d.ts +0 -1
  41. package/dist/cli/product-doctor.d.ts.map +1 -1
  42. package/dist/cli/product-doctor.js +6 -3
  43. package/dist/cli/product-doctor.js.map +1 -1
  44. package/dist/config.d.ts +0 -3
  45. package/dist/config.d.ts.map +1 -1
  46. package/dist/config.js +0 -8
  47. package/dist/config.js.map +1 -1
  48. package/dist/core/agent-session-config.d.ts +1 -2
  49. package/dist/core/agent-session-config.d.ts.map +1 -1
  50. package/dist/core/agent-session-config.js +0 -3
  51. package/dist/core/agent-session-config.js.map +1 -1
  52. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  53. package/dist/core/agent-session-runtime.js +5 -4
  54. package/dist/core/agent-session-runtime.js.map +1 -1
  55. package/dist/core/agent-session-services.d.ts +5 -3
  56. package/dist/core/agent-session-services.d.ts.map +1 -1
  57. package/dist/core/agent-session-services.js +2 -27
  58. package/dist/core/agent-session-services.js.map +1 -1
  59. package/dist/core/agent-session.d.ts +10 -6
  60. package/dist/core/agent-session.d.ts.map +1 -1
  61. package/dist/core/agent-session.js +107 -107
  62. package/dist/core/agent-session.js.map +1 -1
  63. package/dist/core/auth-storage.d.ts +8 -35
  64. package/dist/core/auth-storage.d.ts.map +1 -1
  65. package/dist/core/auth-storage.js +33 -243
  66. package/dist/core/auth-storage.js.map +1 -1
  67. package/dist/core/cron-jobs.d.ts.map +1 -1
  68. package/dist/core/cron-jobs.js +7 -1
  69. package/dist/core/cron-jobs.js.map +1 -1
  70. package/dist/core/inference-coordinator.d.ts +6 -1
  71. package/dist/core/inference-coordinator.d.ts.map +1 -1
  72. package/dist/core/inference-coordinator.js +18 -2
  73. package/dist/core/inference-coordinator.js.map +1 -1
  74. package/dist/core/kernel/bootstrap.d.ts.map +1 -1
  75. package/dist/core/kernel/bootstrap.js +2 -2
  76. package/dist/core/kernel/bootstrap.js.map +1 -1
  77. package/dist/core/kernel/repl-manager.d.ts +2 -0
  78. package/dist/core/kernel/repl-manager.d.ts.map +1 -1
  79. package/dist/core/kernel/repl-manager.js +44 -1
  80. package/dist/core/kernel/repl-manager.js.map +1 -1
  81. package/dist/core/logging.d.ts +4 -3
  82. package/dist/core/logging.d.ts.map +1 -1
  83. package/dist/core/logging.js +6 -6
  84. package/dist/core/logging.js.map +1 -1
  85. package/dist/core/mcp/mcp-manager.d.ts.map +1 -1
  86. package/dist/core/mcp/mcp-manager.js +3 -3
  87. package/dist/core/mcp/mcp-manager.js.map +1 -1
  88. package/dist/core/model-registry.d.ts +1 -16
  89. package/dist/core/model-registry.d.ts.map +1 -1
  90. package/dist/core/model-registry.js +88 -196
  91. package/dist/core/model-registry.js.map +1 -1
  92. package/dist/core/model-resolver.d.ts +2 -19
  93. package/dist/core/model-resolver.d.ts.map +1 -1
  94. package/dist/core/model-resolver.js +36 -266
  95. package/dist/core/model-resolver.js.map +1 -1
  96. package/dist/core/provider-contracts.d.ts +5 -6
  97. package/dist/core/provider-contracts.d.ts.map +1 -1
  98. package/dist/core/provider-contracts.js +25 -36
  99. package/dist/core/provider-contracts.js.map +1 -1
  100. package/dist/core/provider-display-names.d.ts.map +1 -1
  101. package/dist/core/provider-display-names.js +0 -2
  102. package/dist/core/provider-display-names.js.map +1 -1
  103. package/dist/core/rlm-max-subagents.d.ts +32 -0
  104. package/dist/core/rlm-max-subagents.d.ts.map +1 -0
  105. package/dist/core/rlm-max-subagents.js +50 -0
  106. package/dist/core/rlm-max-subagents.js.map +1 -0
  107. package/dist/core/rlm-runtime.d.ts +3 -3
  108. package/dist/core/rlm-runtime.d.ts.map +1 -1
  109. package/dist/core/rlm-runtime.js.map +1 -1
  110. package/dist/core/sdk.d.ts +4 -3
  111. package/dist/core/sdk.d.ts.map +1 -1
  112. package/dist/core/sdk.js +27 -14
  113. package/dist/core/sdk.js.map +1 -1
  114. package/dist/core/settings-manager.d.ts +3 -16
  115. package/dist/core/settings-manager.d.ts.map +1 -1
  116. package/dist/core/settings-manager.js +12 -44
  117. package/dist/core/settings-manager.js.map +1 -1
  118. package/dist/core/slash-commands.d.ts.map +1 -1
  119. package/dist/core/slash-commands.js +6 -6
  120. package/dist/core/slash-commands.js.map +1 -1
  121. package/dist/index.d.ts +1 -1
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/index.js.map +1 -1
  124. package/dist/installer.mjs +2 -3
  125. package/dist/main.d.ts.map +1 -1
  126. package/dist/main.js +39 -34
  127. package/dist/main.js.map +1 -1
  128. package/dist/modes/acp/acp-mode.d.ts.map +1 -1
  129. package/dist/modes/acp/acp-mode.js +1 -1
  130. package/dist/modes/acp/acp-mode.js.map +1 -1
  131. package/dist/modes/agent-connection/daemon-agent-connection.d.ts +6 -2
  132. package/dist/modes/agent-connection/daemon-agent-connection.d.ts.map +1 -1
  133. package/dist/modes/agent-connection/daemon-agent-connection.js +13 -2
  134. package/dist/modes/agent-connection/daemon-agent-connection.js.map +1 -1
  135. package/dist/modes/agent-connection/in-process-agent-connection.d.ts +2 -0
  136. package/dist/modes/agent-connection/in-process-agent-connection.d.ts.map +1 -1
  137. package/dist/modes/agent-connection/in-process-agent-connection.js +6 -0
  138. package/dist/modes/agent-connection/in-process-agent-connection.js.map +1 -1
  139. package/dist/modes/agent-connection/types.d.ts +3 -0
  140. package/dist/modes/agent-connection/types.d.ts.map +1 -1
  141. package/dist/modes/agent-connection/types.js.map +1 -1
  142. package/dist/modes/agents-view/agents-view-mode.d.ts.map +1 -1
  143. package/dist/modes/agents-view/agents-view-mode.js +2 -19
  144. package/dist/modes/agents-view/agents-view-mode.js.map +1 -1
  145. package/dist/modes/daemon/daemon-mode.d.ts +5 -0
  146. package/dist/modes/daemon/daemon-mode.d.ts.map +1 -1
  147. package/dist/modes/daemon/daemon-mode.js +107 -12
  148. package/dist/modes/daemon/daemon-mode.js.map +1 -1
  149. package/dist/modes/daemon/daemon-protocol.d.ts +194 -165
  150. package/dist/modes/daemon/daemon-protocol.d.ts.map +1 -1
  151. package/dist/modes/daemon/daemon-protocol.js +16 -11
  152. package/dist/modes/daemon/daemon-protocol.js.map +1 -1
  153. package/dist/modes/daemon/daemon-rlm-capacity-coordinator.d.ts +19 -0
  154. package/dist/modes/daemon/daemon-rlm-capacity-coordinator.d.ts.map +1 -0
  155. package/dist/modes/daemon/daemon-rlm-capacity-coordinator.js +74 -0
  156. package/dist/modes/daemon/daemon-rlm-capacity-coordinator.js.map +1 -0
  157. package/dist/modes/daemon/daemon-rlm-capacity.d.ts +19 -0
  158. package/dist/modes/daemon/daemon-rlm-capacity.d.ts.map +1 -0
  159. package/dist/modes/daemon/daemon-rlm-capacity.js +85 -0
  160. package/dist/modes/daemon/daemon-rlm-capacity.js.map +1 -0
  161. package/dist/modes/daemon/daemon-session-summarizer.d.ts +2 -3
  162. package/dist/modes/daemon/daemon-session-summarizer.d.ts.map +1 -1
  163. package/dist/modes/daemon/daemon-session-summarizer.js +32 -23
  164. package/dist/modes/daemon/daemon-session-summarizer.js.map +1 -1
  165. package/dist/modes/daemon/daemon-state-root.d.ts +15 -0
  166. package/dist/modes/daemon/daemon-state-root.d.ts.map +1 -0
  167. package/dist/modes/daemon/daemon-state-root.js +40 -0
  168. package/dist/modes/daemon/daemon-state-root.js.map +1 -0
  169. package/dist/modes/daemon/daemon-supervisor-ownership.d.ts +2 -0
  170. package/dist/modes/daemon/daemon-supervisor-ownership.d.ts.map +1 -1
  171. package/dist/modes/daemon/daemon-supervisor-ownership.js +27 -0
  172. package/dist/modes/daemon/daemon-supervisor-ownership.js.map +1 -1
  173. package/dist/modes/daemon/daemon-supervisor.d.ts +10 -1
  174. package/dist/modes/daemon/daemon-supervisor.d.ts.map +1 -1
  175. package/dist/modes/daemon/daemon-supervisor.js +213 -20
  176. package/dist/modes/daemon/daemon-supervisor.js.map +1 -1
  177. package/dist/modes/daemon/daemon-worker-protocol.d.ts +28 -1
  178. package/dist/modes/daemon/daemon-worker-protocol.d.ts.map +1 -1
  179. package/dist/modes/daemon/daemon-worker-protocol.js +16 -2
  180. package/dist/modes/daemon/daemon-worker-protocol.js.map +1 -1
  181. package/dist/modes/daemon/saved-session-catalog.d.ts.map +1 -1
  182. package/dist/modes/daemon/saved-session-catalog.js +2 -0
  183. package/dist/modes/daemon/saved-session-catalog.js.map +1 -1
  184. package/dist/modes/interactive/auth-flows.d.ts +0 -6
  185. package/dist/modes/interactive/auth-flows.d.ts.map +1 -1
  186. package/dist/modes/interactive/auth-flows.js +6 -126
  187. package/dist/modes/interactive/auth-flows.js.map +1 -1
  188. package/dist/modes/interactive/components/configuration-menu.d.ts +3 -0
  189. package/dist/modes/interactive/components/configuration-menu.d.ts.map +1 -1
  190. package/dist/modes/interactive/components/configuration-menu.js +13 -2
  191. package/dist/modes/interactive/components/configuration-menu.js.map +1 -1
  192. package/dist/modes/interactive/components/footer.d.ts +1 -1
  193. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  194. package/dist/modes/interactive/components/footer.js +2 -2
  195. package/dist/modes/interactive/components/footer.js.map +1 -1
  196. package/dist/modes/interactive/components/index.d.ts +0 -1
  197. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  198. package/dist/modes/interactive/components/index.js +0 -1
  199. package/dist/modes/interactive/components/index.js.map +1 -1
  200. package/dist/modes/interactive/components/login-dialog.d.ts +0 -1
  201. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  202. package/dist/modes/interactive/components/login-dialog.js +3 -40
  203. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  204. package/dist/modes/interactive/components/model-selector.d.ts +2 -0
  205. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  206. package/dist/modes/interactive/components/model-selector.js +13 -2
  207. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  208. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  209. package/dist/modes/interactive/components/oauth-selector.js +2 -11
  210. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  211. package/dist/modes/interactive/feature-hints.d.ts.map +1 -1
  212. package/dist/modes/interactive/feature-hints.js +0 -4
  213. package/dist/modes/interactive/feature-hints.js.map +1 -1
  214. package/dist/modes/interactive/interactive-mode.d.ts +3 -12
  215. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  216. package/dist/modes/interactive/interactive-mode.js +81 -507
  217. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  218. package/dist/modes/interactive/onboarding.d.ts +0 -3
  219. package/dist/modes/interactive/onboarding.d.ts.map +1 -1
  220. package/dist/modes/interactive/onboarding.js +0 -17
  221. package/dist/modes/interactive/onboarding.js.map +1 -1
  222. package/dist/modes/rpc/rpc-client.d.ts +2 -1
  223. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  224. package/dist/modes/rpc/rpc-client.js +24 -11
  225. package/dist/modes/rpc/rpc-client.js.map +1 -1
  226. package/dist/node/amazon-bedrock.d.ts +2 -0
  227. package/dist/node/amazon-bedrock.d.ts.map +1 -0
  228. package/dist/node/amazon-bedrock.js +6 -0
  229. package/dist/node/amazon-bedrock.js.map +1 -0
  230. package/dist/product-identity.d.ts +0 -1
  231. package/dist/product-identity.d.ts.map +1 -1
  232. package/dist/product-identity.js +0 -1
  233. package/dist/product-identity.js.map +1 -1
  234. package/docs/development.md +1 -1
  235. package/docs/fork-philosophy.md +4 -8
  236. package/docs/installation.md +83 -62
  237. package/docs/providers.md +23 -25
  238. package/docs/quickstart.md +19 -10
  239. package/docs/rlm-runtime.md +13 -5
  240. package/docs/rpc.md +4 -4
  241. package/docs/sdk.md +20 -11
  242. package/docs/sessions.md +0 -1
  243. package/docs/settings.md +9 -42
  244. package/docs/skills.md +0 -1
  245. package/docs/upstream-0.9.5.md +171 -0
  246. package/docs/usage.md +14 -10
  247. package/package.json +4 -4
  248. package/dist/core/agent-traces.d.ts +0 -149
  249. package/dist/core/agent-traces.d.ts.map +0 -1
  250. package/dist/core/agent-traces.js +0 -904
  251. package/dist/core/agent-traces.js.map +0 -1
  252. package/dist/core/prime-inference-auth.d.ts +0 -70
  253. package/dist/core/prime-inference-auth.d.ts.map +0 -1
  254. package/dist/core/prime-inference-auth.js +0 -352
  255. package/dist/core/prime-inference-auth.js.map +0 -1
  256. package/dist/core/prime-inference-model-selection.d.ts +0 -16
  257. package/dist/core/prime-inference-model-selection.d.ts.map +0 -1
  258. package/dist/core/prime-inference-model-selection.js +0 -16
  259. package/dist/core/prime-inference-model-selection.js.map +0 -1
  260. package/dist/core/prime-inference-models.d.ts +0 -6
  261. package/dist/core/prime-inference-models.d.ts.map +0 -1
  262. package/dist/core/prime-inference-models.js +0 -62
  263. package/dist/core/prime-inference-models.js.map +0 -1
  264. package/dist/core/telemetry.d.ts +0 -95
  265. package/dist/core/telemetry.d.ts.map +0 -1
  266. package/dist/core/telemetry.js +0 -637
  267. package/dist/core/telemetry.js.map +0 -1
  268. package/dist/modes/interactive/components/prime-onboarding-splash.d.ts +0 -39
  269. package/dist/modes/interactive/components/prime-onboarding-splash.d.ts.map +0 -1
  270. package/dist/modes/interactive/components/prime-onboarding-splash.js +0 -286
  271. package/dist/modes/interactive/components/prime-onboarding-splash.js.map +0 -1
  272. package/dist/modes/interactive/components/prime-team-selector.d.ts +0 -30
  273. package/dist/modes/interactive/components/prime-team-selector.d.ts.map +0 -1
  274. package/dist/modes/interactive/components/prime-team-selector.js +0 -166
  275. package/dist/modes/interactive/components/prime-team-selector.js.map +0 -1
  276. package/dist/skills/prime-intellect/SKILL.md +0 -87
  277. package/dist/skills/prime-intellect/references/compute.md +0 -45
  278. package/dist/skills/prime-intellect/references/environments.md +0 -73
  279. package/dist/skills/prime-intellect/references/inference.md +0 -47
  280. package/dist/skills/prime-intellect/references/sandboxes.md +0 -75
  281. package/skills/prime-intellect/SKILL.md +0 -87
  282. package/skills/prime-intellect/references/compute.md +0 -45
  283. package/skills/prime-intellect/references/environments.md +0 -73
  284. package/skills/prime-intellect/references/inference.md +0 -47
  285. package/skills/prime-intellect/references/sandboxes.md +0 -75
@@ -0,0 +1,85 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { assertRlmMaxSubagents, } from "../../core/rlm-max-subagents.js";
3
+ import { DaemonClient } from "./daemon-client.js";
4
+ import { deserializeDaemonError } from "./daemon-errors.js";
5
+ /** Worker-owned slots remain visible to a replacement supervisor until release is acknowledged. */
6
+ export class DaemonRlmCapacityClient {
7
+ options;
8
+ reservations = new Map();
9
+ constructor(options) {
10
+ this.options = options;
11
+ }
12
+ snapshot() {
13
+ return { reservations: [...this.reservations.values()].map((record) => ({ ...record })) };
14
+ }
15
+ forSession(origin) {
16
+ return {
17
+ getStatus: () => this.requestStatus({ op: "status", ...origin }),
18
+ setMaxSubagents: (maxSubagents) => {
19
+ assertRlmMaxSubagents(maxSubagents);
20
+ return this.requestStatus({ op: "set", ...origin, maxSubagents });
21
+ },
22
+ reserve: () => this.reserve(origin),
23
+ };
24
+ }
25
+ async reserveRoot(origin) {
26
+ return this.reserve(origin, true);
27
+ }
28
+ async reserve(origin, residentRoot) {
29
+ const reservationId = randomUUID();
30
+ const record = { ...origin, reservationId, ...(residentRoot ? { residentRoot } : {}) };
31
+ this.reservations.set(reservationId, record);
32
+ let released = false;
33
+ const release = async () => {
34
+ if (released)
35
+ return;
36
+ await this.request({ op: "release", reservationId });
37
+ released = true;
38
+ this.reservations.delete(reservationId);
39
+ };
40
+ try {
41
+ await this.request({ op: "reserve", ...record });
42
+ return { release };
43
+ }
44
+ catch (error) {
45
+ try {
46
+ await release();
47
+ }
48
+ catch (cleanupError) {
49
+ throw new AggregateError([error, cleanupError], "RLM capacity admission and cleanup failed");
50
+ }
51
+ throw error;
52
+ }
53
+ }
54
+ async requestStatus(operation) {
55
+ const data = await this.request(operation);
56
+ if (typeof data !== "object" ||
57
+ data === null ||
58
+ !("maxSubagents" in data) ||
59
+ typeof data.maxSubagents !== "number") {
60
+ throw new Error("Supervisor returned invalid RLM subagent capacity status");
61
+ }
62
+ assertRlmMaxSubagents(data.maxSubagents);
63
+ return { maxSubagents: data.maxSubagents };
64
+ }
65
+ async request(operation) {
66
+ const client = new DaemonClient(this.options.socketPath);
67
+ try {
68
+ await client.connect(1000);
69
+ await client.waitForHello(1000);
70
+ const response = await client.request({
71
+ type: "rlm_capacity",
72
+ workerToken: this.options.workerToken,
73
+ workerInstanceId: this.options.workerInstanceId,
74
+ operation,
75
+ }, 30_000, { recoverable: false });
76
+ if (!response.success)
77
+ throw deserializeDaemonError(response);
78
+ return response.data;
79
+ }
80
+ finally {
81
+ client.close();
82
+ }
83
+ }
84
+ }
85
+ //# sourceMappingURL=daemon-rlm-capacity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-rlm-capacity.js","sourceRoot":"","sources":["../../../src/modes/daemon/daemon-rlm-capacity.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport {\n\tassertRlmMaxSubagents,\n\ttype RlmMaxSubagentsStatus,\n\ttype RlmSubagentCapacity,\n\ttype RlmSubagentCapacityReservation,\n} from \"../../core/rlm-max-subagents.js\";\nimport { DaemonClient } from \"./daemon-client.js\";\nimport { deserializeDaemonError } from \"./daemon-errors.js\";\nimport type {\n\tDaemonRlmCapacityOperation,\n\tDaemonRlmCapacityOrigin,\n\tDaemonRlmCapacityReservation,\n\tDaemonRlmCapacitySnapshot,\n} from \"./daemon-worker-protocol.js\";\n\n/** Worker-owned slots remain visible to a replacement supervisor until release is acknowledged. */\nexport class DaemonRlmCapacityClient {\n\tprivate readonly reservations = new Map<string, DaemonRlmCapacityReservation>();\n\n\tconstructor(private readonly options: { socketPath: string; workerToken: string; workerInstanceId: string }) {}\n\n\tsnapshot(): DaemonRlmCapacitySnapshot {\n\t\treturn { reservations: [...this.reservations.values()].map((record) => ({ ...record })) };\n\t}\n\n\tforSession(origin: DaemonRlmCapacityOrigin): RlmSubagentCapacity {\n\t\treturn {\n\t\t\tgetStatus: () => this.requestStatus({ op: \"status\", ...origin }),\n\t\t\tsetMaxSubagents: (maxSubagents) => {\n\t\t\t\tassertRlmMaxSubagents(maxSubagents);\n\t\t\t\treturn this.requestStatus({ op: \"set\", ...origin, maxSubagents });\n\t\t\t},\n\t\t\treserve: () => this.reserve(origin),\n\t\t};\n\t}\n\n\tasync reserveRoot(origin: DaemonRlmCapacityOrigin): Promise<RlmSubagentCapacityReservation> {\n\t\treturn this.reserve(origin, true);\n\t}\n\n\tprivate async reserve(\n\t\torigin: DaemonRlmCapacityOrigin,\n\t\tresidentRoot?: true,\n\t): Promise<RlmSubagentCapacityReservation> {\n\t\tconst reservationId = randomUUID();\n\t\tconst record = { ...origin, reservationId, ...(residentRoot ? { residentRoot } : {}) };\n\t\tthis.reservations.set(reservationId, record);\n\t\tlet released = false;\n\t\tconst release = async () => {\n\t\t\tif (released) return;\n\t\t\tawait this.request({ op: \"release\", reservationId });\n\t\t\treleased = true;\n\t\t\tthis.reservations.delete(reservationId);\n\t\t};\n\t\ttry {\n\t\t\tawait this.request({ op: \"reserve\", ...record });\n\t\t\treturn { release };\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\tawait release();\n\t\t\t} catch (cleanupError) {\n\t\t\t\tthrow new AggregateError([error, cleanupError], \"RLM capacity admission and cleanup failed\");\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate async requestStatus(operation: DaemonRlmCapacityOperation): Promise<RlmMaxSubagentsStatus> {\n\t\tconst data = await this.request(operation);\n\t\tif (\n\t\t\ttypeof data !== \"object\" ||\n\t\t\tdata === null ||\n\t\t\t!(\"maxSubagents\" in data) ||\n\t\t\ttypeof data.maxSubagents !== \"number\"\n\t\t) {\n\t\t\tthrow new Error(\"Supervisor returned invalid RLM subagent capacity status\");\n\t\t}\n\t\tassertRlmMaxSubagents(data.maxSubagents);\n\t\treturn { maxSubagents: data.maxSubagents };\n\t}\n\n\tprivate async request(operation: DaemonRlmCapacityOperation): Promise<unknown> {\n\t\tconst client = new DaemonClient(this.options.socketPath);\n\t\ttry {\n\t\t\tawait client.connect(1000);\n\t\t\tawait client.waitForHello(1000);\n\t\t\tconst response = await client.request(\n\t\t\t\t{\n\t\t\t\t\ttype: \"rlm_capacity\",\n\t\t\t\t\tworkerToken: this.options.workerToken,\n\t\t\t\t\tworkerInstanceId: this.options.workerInstanceId,\n\t\t\t\t\toperation,\n\t\t\t\t},\n\t\t\t\t30_000,\n\t\t\t\t{ recoverable: false },\n\t\t\t);\n\t\t\tif (!response.success) throw deserializeDaemonError(response);\n\t\t\treturn response.data;\n\t\t} finally {\n\t\t\tclient.close();\n\t\t}\n\t}\n}\n"]}
@@ -9,20 +9,19 @@ export interface AgentStatusResult {
9
9
  summary: string;
10
10
  taskState?: AgentTaskState;
11
11
  }
12
- /** Resolve the cheap summary model, or undefined when it has no configured auth. */
13
- export declare function resolveSummaryModel(registry: ModelRegistry): Model<Api> | undefined;
14
12
  /** Serialize the trailing messages into a compact prompt body (tool calls by name only). */
15
13
  export declare function buildStatusContext(messages: readonly AgentMessage[], isWorking: boolean): string;
16
14
  /** Take the content of the last `<recap>` and `<status>` tags; idle verdicts default to needs_input. */
17
15
  export declare function parseAgentStatusResponse(text: string, isWorking: boolean): AgentStatusResult | undefined;
18
16
  export interface GenerateAgentStatusParams {
19
17
  registry: ModelRegistry;
18
+ model?: Model<Api>;
20
19
  messages: readonly AgentMessage[];
21
20
  isWorking: boolean;
22
21
  signal?: AbortSignal;
23
22
  requests?: InferenceCoordinator;
24
23
  }
25
- /** One cheap model call for a fresh status, or undefined if unavailable/empty/failed. */
24
+ /** Use the session's selected model, or keep the local status when unavailable. */
26
25
  export declare function generateAgentStatus(params: GenerateAgentStatusParams): Promise<AgentStatusResult | undefined>;
27
26
  /**
28
27
  * Background status summarization for daemon-hosted sessions, top-level and
@@ -1 +1 @@
1
- {"version":3,"file":"daemon-session-summarizer.d.ts","sourceRoot":"","sources":["../../../src/modes/daemon/daemon-session-summarizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAqB,KAAK,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AACnG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAcpE,eAAO,MAAM,0BAA0B,s5BAaV,CAAC;AAE9B,MAAM,WAAW,iBAAiB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,oFAAoF;AACpF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CAMnF;AAiCD,4FAA4F;AAC5F,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,CAkBhG;AAyBD,wGAAwG;AACxG,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,iBAAiB,GAAG,SAAS,CAgBxG;AAED,MAAM,WAAW,yBAAyB;IACzC,QAAQ,EAAE,aAAa,CAAC;IACxB,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,oBAAoB,CAAC;CAChC;AAED,yFAAyF;AACzF,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,yBAAyB,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CA4CnH;AAOD;;;;;GAKG;AACH,qBAAa,uBAAuB;IAYlC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;IAEjC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAGzB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAlBzB,OAAO,CAAC,QAAQ,CAA6C;IAC7D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAoD;IAEnF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsC;IAC/D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;IAC1D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;IAC/C,OAAO,CAAC,OAAO,CAAS;IAExB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IAEpD,YACkB,YAAY,EAAE,MAAM,SAAS,kBAAkB,EAAE,EACjD,eAAe,CAAC,mDAAqC,EAErD,QAAQ,GAAE,CAC1B,MAAM,EAAE,yBAAyB,KAC7B,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAuB,EAChD,WAAW,CAAC,wFAA6E,EACzF,OAAO,GAAE,CAAC,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,OAAO,KAAK,IACpD,EAClB;IAEJ,KAAK,IAAI,IAAI,CAWZ;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAe1B;IAED,0EAA0E;IACpE,MAAM,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUnD;IAED,8EAA8E;IAC9E,IAAI,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CASpC;IAED,+EAA+E;IAC/E,cAAc,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAa9C;IAED,OAAO,CAAC,SAAS;YAkBH,UAAU;CAoGxB","sourcesContent":["import type { AgentMessage } from \"@ponythewhite/base-context-agent\";\nimport type { Api, Model } from \"@ponythewhite/base-context-ai\";\nimport { completeInference, type InferenceCoordinator } from \"../../core/inference-coordinator.js\";\nimport type { ModelRegistry } from \"../../core/model-registry.js\";\nimport type { AgentStatus, AgentTaskState } from \"../../core/session-manager.js\";\nimport type { ActiveSessionState } from \"./active-session-state.js\";\n\nconst SWEEP_INTERVAL_MS = 25_000;\n// Collapse a tool-use loop's rapid turn_end bursts into one summarization.\nconst SETTLE_DEBOUNCE_MS = 2_000;\n\nconst SUMMARY_MODEL_PROVIDER = \"prime-inference\";\nconst SUMMARY_MODEL_ID = \"qwen/qwen3-30b-a3b-instruct-2507\";\n\nconst SUMMARY_CONTEXT_MESSAGES = 8;\nconst SUMMARY_MAX_CHARS_PER_MESSAGE = 600;\n// Generous so a chatty model still closes the tags before truncation.\nconst SUMMARY_MAX_TOKENS = 400;\n\nexport const AGENT_STATUS_SYSTEM_PROMPT = `You generate a status line for an AI coding agent dashboard. You are given the recent conversation between a user and the agent, plus whether the agent is currently working or idle.\n\nOutput ONLY these two tags, nothing before, between, or after. Do not think out loud, explain, or count words.\n<recap>a present-tense clause, at most 12 words, saying what the agent is doing or just did, no trailing period</recap>\n<status>one of NEEDS_INPUT, COMPLETED</status>\n\nSTATUS meaning:\n- COMPLETED: the agent finished its turn AND the user's request is fully done with nothing left.\n- NEEDS_INPUT: the agent finished its turn but the task is not fully done — it asked a question, hit a blocker, or needs more prompting.\nWhen you are unsure between COMPLETED and NEEDS_INPUT, choose NEEDS_INPUT.\n\nExample:\n<recap>Refactoring the auth middleware and updating its tests</recap>\n<status>NEEDS_INPUT</status>`;\n\nexport interface AgentStatusResult {\n\tsummary: string;\n\ttaskState?: AgentTaskState;\n}\n\n/** Resolve the cheap summary model, or undefined when it has no configured auth. */\nexport function resolveSummaryModel(registry: ModelRegistry): Model<Api> | undefined {\n\tconst model = registry.find(SUMMARY_MODEL_PROVIDER, SUMMARY_MODEL_ID);\n\tif (model && registry.hasConfiguredAuth(model)) {\n\t\treturn model;\n\t}\n\treturn undefined;\n}\n\nfunction messageText(content: unknown): { text: string; tools: string[] } {\n\tif (typeof content === \"string\") {\n\t\treturn { text: content, tools: [] };\n\t}\n\tif (!Array.isArray(content)) {\n\t\treturn { text: \"\", tools: [] };\n\t}\n\tconst parts: string[] = [];\n\tconst tools: string[] = [];\n\tfor (const block of content) {\n\t\tif (typeof block !== \"object\" || block === null) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst type = (block as { type?: unknown }).type;\n\t\tif (type === \"text\" && typeof (block as { text?: unknown }).text === \"string\") {\n\t\t\tparts.push((block as { text: string }).text);\n\t\t} else if (type === \"tool_use\" || type === \"toolUse\") {\n\t\t\tconst name = (block as { name?: unknown }).name;\n\t\t\tif (typeof name === \"string\") {\n\t\t\t\ttools.push(name);\n\t\t\t}\n\t\t}\n\t}\n\treturn { text: parts.join(\"\\n\"), tools };\n}\n\nfunction clamp(text: string, max: number): string {\n\tconst normalized = text.replace(/\\s+/g, \" \").trim();\n\treturn normalized.length > max ? `${normalized.slice(0, max)}…` : normalized;\n}\n\n/** Serialize the trailing messages into a compact prompt body (tool calls by name only). */\nexport function buildStatusContext(messages: readonly AgentMessage[], isWorking: boolean): string {\n\tconst recent = messages.slice(-SUMMARY_CONTEXT_MESSAGES);\n\tconst lines: string[] = [];\n\tfor (const message of recent) {\n\t\tconst role = message.role;\n\t\tif (role !== \"user\" && role !== \"assistant\" && role !== \"toolResult\" && role !== \"custom\") {\n\t\t\tcontinue;\n\t\t}\n\t\tconst { text, tools } = messageText(message.content);\n\t\tconst body = clamp(text, SUMMARY_MAX_CHARS_PER_MESSAGE);\n\t\tconst toolNote = tools.length > 0 ? `[tools: ${[...new Set(tools)].join(\", \")}]` : \"\";\n\t\tconst rendered = [body, toolNote].filter((part) => part.length > 0).join(\" \");\n\t\tif (rendered) {\n\t\t\tlines.push(`${role}: ${rendered}`);\n\t\t}\n\t}\n\tconst state = isWorking ? \"working\" : \"idle (finished its turn)\";\n\treturn `<agent-state>${state}</agent-state>\\n<conversation>\\n${lines.join(\"\\n\")}\\n</conversation>`;\n}\n\n// Cuts a word-counting trailer the model sometimes appends, e.g.\n// `Sending X. That's 5 words? Count: X(1)... = 6 words.`. Kept to structural\n// counting markers so plain words (\"Waiting for CI\") survive.\nconst REASONING_TRAILER = /\\s*(?:[\"”]\\s*)?(?:\\bthat['’]?s\\s+\\d+\\s*words?\\b|\\bcount\\s*:|\\(\\d+\\)|=\\s*\\d+\\s*words?\\b).*/i;\nconst COUNTING_ARTIFACT = /\\(\\d+\\)|=\\s*\\d+\\s*words?\\b/i;\nconst MAX_RECAP_WORDS = 16;\n\nfunction cleanRecap(raw: string): string | undefined {\n\tconst value = raw\n\t\t.trim()\n\t\t.replace(REASONING_TRAILER, \"\")\n\t\t.replace(/^[\"“']+|[\"”']+$/g, \"\")\n\t\t.replace(/[.\\s]+$/, \"\")\n\t\t.trim();\n\tif (!value || value.startsWith(\"<\") || /present-tense|12 words/i.test(value)) {\n\t\treturn undefined;\n\t}\n\tif (COUNTING_ARTIFACT.test(value) || value.split(/\\s+/).length > MAX_RECAP_WORDS) {\n\t\treturn undefined;\n\t}\n\treturn value;\n}\n\n/** Take the content of the last `<recap>` and `<status>` tags; idle verdicts default to needs_input. */\nexport function parseAgentStatusResponse(text: string, isWorking: boolean): AgentStatusResult | undefined {\n\t// Normalize unicode angle-bracket lookalikes (‹ › < >) so a tag written with them still parses.\n\tconst cleaned = text.replace(/[‹<]/g, \"<\").replace(/[›>]/g, \">\");\n\n\tconst recapMatch = [...cleaned.matchAll(/<recap>([\\s\\S]*?)<\\/recap>/gi)].at(-1);\n\tconst summary = recapMatch ? cleanRecap(recapMatch[1]!) : undefined;\n\tif (!summary) {\n\t\treturn undefined;\n\t}\n\tif (isWorking) {\n\t\treturn { summary };\n\t}\n\tconst statusMatch = [...cleaned.matchAll(/<status>\\s*([a-z_]+)\\s*<\\/status>/gi)].at(-1);\n\tconst status = statusMatch ? statusMatch[1]!.toUpperCase() : undefined;\n\tconst taskState: AgentTaskState = status === \"COMPLETED\" ? \"completed\" : \"needs_input\";\n\treturn { summary, taskState };\n}\n\nexport interface GenerateAgentStatusParams {\n\tregistry: ModelRegistry;\n\tmessages: readonly AgentMessage[];\n\tisWorking: boolean;\n\tsignal?: AbortSignal;\n\trequests?: InferenceCoordinator;\n}\n\n/** One cheap model call for a fresh status, or undefined if unavailable/empty/failed. */\nexport async function generateAgentStatus(params: GenerateAgentStatusParams): Promise<AgentStatusResult | undefined> {\n\tconst { registry, messages, isWorking, signal, requests } = params;\n\tif (messages.length === 0) {\n\t\treturn undefined;\n\t}\n\tconst selectedModel = resolveSummaryModel(registry);\n\tif (!selectedModel) {\n\t\treturn undefined;\n\t}\n\tconst model = { ...selectedModel, cost: { ...selectedModel.cost } };\n\tconst subject = buildStatusContext(messages, isWorking);\n\tconst timestamp = Date.now();\n\tconst auth = await registry.getApiKeyAndHeaders(model);\n\tif (!auth.ok || !auth.apiKey) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\tconst response = await completeInference(\n\t\t\trequests,\n\t\t\tmodel,\n\t\t\t{\n\t\t\t\tsystemPrompt: AGENT_STATUS_SYSTEM_PROMPT,\n\t\t\t\tmessages: [\n\t\t\t\t\t{\n\t\t\t\t\t\trole: \"user\" as const,\n\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: subject }],\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t\t{ maxTokens: SUMMARY_MAX_TOKENS, apiKey: auth.apiKey, headers: auth.headers, signal },\n\t\t\t{ purpose: \"native-control\", purposeDetail: \"daemon-status\" },\n\t\t);\n\t\tif (response.stopReason === \"error\") {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst textContent = response.content\n\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t.map((c) => c.text)\n\t\t\t.join(\"\\n\");\n\t\treturn parseAgentStatusResponse(textContent, isWorking);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction isSessionWorking(state: ActiveSessionState): boolean {\n\tconst session = state.runtime.session;\n\treturn session.isSessionActive;\n}\n\n/**\n * Background status summarization for daemon-hosted sessions, top-level and\n * subagents alike. A periodic sweep refreshes working sessions; debounced\n * turn-end activity drives the idle verdict. Status lives in memory; settled\n * idle verdicts are persisted.\n */\nexport class DaemonSessionSummarizer {\n\tprivate interval: ReturnType<typeof setInterval> | undefined;\n\tprivate readonly debounceTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\t// Controller per in-flight summary so a closing session can abort its write.\n\tprivate readonly inFlight = new Map<string, AbortController>();\n\tprivate readonly tasks = new Map<string, Promise<void>>();\n\tprivate readonly forgotten = new Set<string>();\n\tprivate stopped = false;\n\t// Sessions requested while one was running; get one more pass on completion.\n\tprivate readonly rerunRequested = new Set<string>();\n\n\tconstructor(\n\t\tprivate readonly listSessions: () => readonly ActiveSessionState[],\n\t\tprivate readonly onStatusChanged?: (state: ActiveSessionState) => void,\n\t\t// Injectable for tests.\n\t\tprivate readonly generate: (\n\t\t\tparams: GenerateAgentStatusParams,\n\t\t) => Promise<AgentStatusResult | undefined> = generateAgentStatus,\n\t\tprivate readonly getRequests?: (session: ActiveSessionState[\"runtime\"][\"session\"]) => InferenceCoordinator,\n\t\tprivate readonly onError: (state: ActiveSessionState, error: unknown) => void = (_state, error) =>\n\t\t\tconsole.error(error),\n\t) {}\n\n\tstart(): void {\n\t\tif (this.interval) {\n\t\t\treturn;\n\t\t}\n\t\tthis.stopped = false;\n\t\tthis.interval = setInterval(() => {\n\t\t\tfor (const state of this.listSessions()) {\n\t\t\t\tthis.summarize(state);\n\t\t\t}\n\t\t}, SWEEP_INTERVAL_MS);\n\t\tthis.interval.unref?.();\n\t}\n\n\tasync stop(): Promise<void> {\n\t\tthis.stopped = true;\n\t\tif (this.interval) {\n\t\t\tclearInterval(this.interval);\n\t\t\tthis.interval = undefined;\n\t\t}\n\t\tfor (const timer of this.debounceTimers.values()) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tthis.debounceTimers.clear();\n\t\tfor (const controller of this.inFlight.values()) {\n\t\t\tcontroller.abort();\n\t\t}\n\t\tthis.rerunRequested.clear();\n\t\tawait Promise.all(this.tasks.values());\n\t}\n\n\t/** Cancel new work and drain any admitted status write before closing. */\n\tasync forget(activeSessionId: string): Promise<void> {\n\t\tthis.forgotten.add(activeSessionId);\n\t\tconst timer = this.debounceTimers.get(activeSessionId);\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.debounceTimers.delete(activeSessionId);\n\t\t}\n\t\tthis.inFlight.get(activeSessionId)?.abort();\n\t\tthis.rerunRequested.delete(activeSessionId);\n\t\tawait this.tasks.get(activeSessionId);\n\t}\n\n\t/** Seed in-memory status from the persisted entry when a session is added. */\n\tseed(state: ActiveSessionState): void {\n\t\tthis.forgotten.delete(state.activeSessionId);\n\t\tif (state.summaryState) {\n\t\t\treturn;\n\t\t}\n\t\tconst persisted = state.runtime.session.sessionManager.getLatestAgentStatus();\n\t\tif (persisted) {\n\t\t\tstate.summaryState = persisted;\n\t\t}\n\t}\n\n\t/** Called when a session finishes a turn; debounce until the agent settles. */\n\tnotifyActivity(state: ActiveSessionState): void {\n\t\tconst id = state.activeSessionId;\n\t\tif (this.stopped || this.forgotten.has(id)) return;\n\t\tconst existing = this.debounceTimers.get(id);\n\t\tif (existing) {\n\t\t\tclearTimeout(existing);\n\t\t}\n\t\tconst timer = setTimeout(() => {\n\t\t\tthis.debounceTimers.delete(id);\n\t\t\tthis.summarize(state);\n\t\t}, SETTLE_DEBOUNCE_MS);\n\t\ttimer.unref?.();\n\t\tthis.debounceTimers.set(id, timer);\n\t}\n\n\tprivate summarize(state: ActiveSessionState): Promise<void> {\n\t\tconst id = state.activeSessionId;\n\t\tif (this.stopped || this.forgotten.has(id)) return Promise.resolve();\n\t\tconst existing = this.tasks.get(id);\n\t\tif (existing) {\n\t\t\tthis.rerunRequested.add(id);\n\t\t\treturn existing;\n\t\t}\n\t\tconst task = this.runSummary(state)\n\t\t\t.catch((error) => this.onError(state, error))\n\t\t\t.finally(() => {\n\t\t\t\tthis.tasks.delete(id);\n\t\t\t\tif (this.rerunRequested.delete(id)) this.notifyActivity(state);\n\t\t\t});\n\t\tthis.tasks.set(id, task);\n\t\treturn task;\n\t}\n\n\tprivate async runSummary(state: ActiveSessionState): Promise<void> {\n\t\tconst id = state.activeSessionId;\n\t\tconst session = state.runtime.session;\n\t\tconst sessionManager = session.sessionManager;\n\t\tconst sessionId = sessionManager.getSessionId();\n\t\tconst sessionFile = sessionManager.getSessionFile();\n\t\tconst messages = session.messages;\n\t\tif (messages.length === 0) {\n\t\t\treturn;\n\t\t}\n\t\tconst messageCount = messages.length;\n\t\tconst isWorking = isSessionWorking(state);\n\t\tconst previous = state.summaryState;\n\t\t// Idle sessions with a current verdict need no refresh; working sessions\n\t\t// always refresh so the recap keeps up with the in-progress turn.\n\t\tconst contentUnchanged = previous?.basedOnMessageCount === messageCount;\n\t\tconst owesIdleVerdict = !isWorking && previous?.taskState === undefined;\n\t\t// A blank recap means the model call hasn't succeeded yet (e.g. the\n\t\t// needs_input fallback fired on a transient failure); keep retrying until a\n\t\t// real summary lands so the recap isn't left permanently empty.\n\t\tconst owesSummary = !isWorking && !previous?.summary;\n\t\tif (contentUnchanged && !isWorking && !owesIdleVerdict && !owesSummary) {\n\t\t\treturn;\n\t\t}\n\t\t// Include the in-progress message so a long streaming turn gets a live recap.\n\t\tconst streaming = isWorking ? session.state.streamingMessage : undefined;\n\t\tconst contextMessages = streaming ? [...messages, streaming] : messages;\n\n\t\tconst controller = new AbortController();\n\t\tconst isCurrent = () =>\n\t\t\t!controller.signal.aborted &&\n\t\t\tstate.runtime.session === session &&\n\t\t\tsession.sessionManager === sessionManager &&\n\t\t\tsessionManager.getSessionId() === sessionId &&\n\t\t\tsessionManager.getSessionFile() === sessionFile &&\n\t\t\tisSessionWorking(state) === isWorking &&\n\t\t\tsession.messages.length === messageCount;\n\t\tthis.inFlight.set(id, controller);\n\t\tlet requests: InferenceCoordinator | undefined;\n\t\tlet status: AgentStatus | undefined;\n\t\tlet changed = false;\n\t\ttry {\n\t\t\trequests = this.getRequests?.(session);\n\t\t\tconst generated = await this.generate({\n\t\t\t\tregistry: session.modelRegistry,\n\t\t\t\tmessages: contextMessages,\n\t\t\t\tisWorking,\n\t\t\t\tsignal: controller.signal,\n\t\t\t\trequests,\n\t\t\t});\n\t\t\t// An unused capture otherwise counts this idle subject as working.\n\t\t\tawait requests?.dispose();\n\t\t\t// A failed classification on an idle session would spin at \"working\"\n\t\t\t// forever (the activity axis holds unjudged idle sessions there), so\n\t\t\t// settle it to needs_input.\n\t\t\tconst result =\n\t\t\t\tgenerated ??\n\t\t\t\t(!isWorking && (owesIdleVerdict || owesSummary)\n\t\t\t\t\t? { summary: previous?.summary ?? \"\", taskState: \"needs_input\" as const }\n\t\t\t\t\t: undefined);\n\t\t\tif (!result) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Discard if the session closed, was swapped, or moved to a new turn\n\t\t\t// during the async call — never write a verdict for stale state.\n\t\t\tif (!isCurrent()) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// A working refresh carries no verdict; keep the prior one at the same\n\t\t\t// message count so a still-valid needs_input isn't dropped.\n\t\t\tconst taskState =\n\t\t\t\tresult.taskState ?? (previous?.basedOnMessageCount === messageCount ? previous?.taskState : undefined);\n\t\t\tstatus = {\n\t\t\t\tsummary: result.summary,\n\t\t\t\ttaskState,\n\t\t\t\tbasedOnMessageCount: messageCount,\n\t\t\t};\n\t\t\t// An idle settle refreshes the verdict's currency, which drives the roster's\n\t\t\t// activity axis: it must publish even when the verdict text is unchanged.\n\t\t\tchanged =\n\t\t\t\tprevious?.summary !== status.summary ||\n\t\t\t\tprevious?.taskState !== status.taskState ||\n\t\t\t\t(!isWorking && previous?.basedOnMessageCount !== status.basedOnMessageCount);\n\t\t\t// Persist only settled idle verdicts, never mid-stream.\n\t\t\tif (!isWorking) {\n\t\t\t\tawait sessionManager.appendAgentStatus(status);\n\t\t\t\tif (!isCurrent()) return;\n\t\t\t}\n\t\t} finally {\n\t\t\ttry {\n\t\t\t\tawait requests?.dispose();\n\t\t\t} finally {\n\t\t\t\tthis.inFlight.delete(id);\n\t\t\t}\n\t\t}\n\t\tif (status && isCurrent()) {\n\t\t\tstate.summaryState = status;\n\t\t\tif (changed) this.onStatusChanged?.(state);\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"daemon-session-summarizer.d.ts","sourceRoot":"","sources":["../../../src/modes/daemon/daemon-session-summarizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAqB,KAAK,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AACnG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAWpE,eAAO,MAAM,0BAA0B,s5BAaV,CAAC;AAE9B,MAAM,WAAW,iBAAiB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,cAAc,CAAC;CAC3B;AAiCD,4FAA4F;AAC5F,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,CAkBhG;AAyBD,wGAAwG;AACxG,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,iBAAiB,GAAG,SAAS,CAgBxG;AAED,MAAM,WAAW,yBAAyB;IACzC,QAAQ,EAAE,aAAa,CAAC;IACxB,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,oBAAoB,CAAC;CAChC;AAED,mFAAmF;AACnF,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,yBAAyB,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CA2CnH;AAmBD;;;;;GAKG;AACH,qBAAa,uBAAuB;IAYlC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;IAEjC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAGzB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAlBzB,OAAO,CAAC,QAAQ,CAA6C;IAC7D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAoD;IAEnF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsC;IAC/D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;IAC1D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;IAC/C,OAAO,CAAC,OAAO,CAAS;IAExB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IAEpD,YACkB,YAAY,EAAE,MAAM,SAAS,kBAAkB,EAAE,EACjD,eAAe,CAAC,mDAAqC,EAErD,QAAQ,GAAE,CAC1B,MAAM,EAAE,yBAAyB,KAC7B,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAuB,EAChD,WAAW,CAAC,wFAA6E,EACzF,OAAO,GAAE,CAAC,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,OAAO,KAAK,IACpD,EAClB;IAEJ,KAAK,IAAI,IAAI,CAWZ;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAe1B;IAED,0EAA0E;IACpE,MAAM,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAUnD;IAED,8EAA8E;IAC9E,IAAI,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CASpC;IAED,+EAA+E;IAC/E,cAAc,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAa9C;IAED,OAAO,CAAC,SAAS;YAkBH,UAAU;CA4GxB","sourcesContent":["import type { AgentMessage } from \"@ponythewhite/base-context-agent\";\nimport type { Api, Model } from \"@ponythewhite/base-context-ai\";\nimport { completeInference, type InferenceCoordinator } from \"../../core/inference-coordinator.js\";\nimport type { ModelRegistry } from \"../../core/model-registry.js\";\nimport type { AgentStatus, AgentTaskState } from \"../../core/session-manager.js\";\nimport type { ActiveSessionState } from \"./active-session-state.js\";\n\nconst SWEEP_INTERVAL_MS = 25_000;\n// Collapse a tool-use loop's rapid turn_end bursts into one summarization.\nconst SETTLE_DEBOUNCE_MS = 2_000;\n\nconst SUMMARY_CONTEXT_MESSAGES = 8;\nconst SUMMARY_MAX_CHARS_PER_MESSAGE = 600;\n// Generous so a chatty model still closes the tags before truncation.\nconst SUMMARY_MAX_TOKENS = 400;\n\nexport const AGENT_STATUS_SYSTEM_PROMPT = `You generate a status line for an AI coding agent dashboard. You are given the recent conversation between a user and the agent, plus whether the agent is currently working or idle.\n\nOutput ONLY these two tags, nothing before, between, or after. Do not think out loud, explain, or count words.\n<recap>a present-tense clause, at most 12 words, saying what the agent is doing or just did, no trailing period</recap>\n<status>one of NEEDS_INPUT, COMPLETED</status>\n\nSTATUS meaning:\n- COMPLETED: the agent finished its turn AND the user's request is fully done with nothing left.\n- NEEDS_INPUT: the agent finished its turn but the task is not fully done — it asked a question, hit a blocker, or needs more prompting.\nWhen you are unsure between COMPLETED and NEEDS_INPUT, choose NEEDS_INPUT.\n\nExample:\n<recap>Refactoring the auth middleware and updating its tests</recap>\n<status>NEEDS_INPUT</status>`;\n\nexport interface AgentStatusResult {\n\tsummary: string;\n\ttaskState?: AgentTaskState;\n}\n\nfunction messageText(content: unknown): { text: string; tools: string[] } {\n\tif (typeof content === \"string\") {\n\t\treturn { text: content, tools: [] };\n\t}\n\tif (!Array.isArray(content)) {\n\t\treturn { text: \"\", tools: [] };\n\t}\n\tconst parts: string[] = [];\n\tconst tools: string[] = [];\n\tfor (const block of content) {\n\t\tif (typeof block !== \"object\" || block === null) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst type = (block as { type?: unknown }).type;\n\t\tif (type === \"text\" && typeof (block as { text?: unknown }).text === \"string\") {\n\t\t\tparts.push((block as { text: string }).text);\n\t\t} else if (type === \"tool_use\" || type === \"toolUse\") {\n\t\t\tconst name = (block as { name?: unknown }).name;\n\t\t\tif (typeof name === \"string\") {\n\t\t\t\ttools.push(name);\n\t\t\t}\n\t\t}\n\t}\n\treturn { text: parts.join(\"\\n\"), tools };\n}\n\nfunction clamp(text: string, max: number): string {\n\tconst normalized = text.replace(/\\s+/g, \" \").trim();\n\treturn normalized.length > max ? `${normalized.slice(0, max)}…` : normalized;\n}\n\n/** Serialize the trailing messages into a compact prompt body (tool calls by name only). */\nexport function buildStatusContext(messages: readonly AgentMessage[], isWorking: boolean): string {\n\tconst recent = messages.slice(-SUMMARY_CONTEXT_MESSAGES);\n\tconst lines: string[] = [];\n\tfor (const message of recent) {\n\t\tconst role = message.role;\n\t\tif (role !== \"user\" && role !== \"assistant\" && role !== \"toolResult\" && role !== \"custom\") {\n\t\t\tcontinue;\n\t\t}\n\t\tconst { text, tools } = messageText(message.content);\n\t\tconst body = clamp(text, SUMMARY_MAX_CHARS_PER_MESSAGE);\n\t\tconst toolNote = tools.length > 0 ? `[tools: ${[...new Set(tools)].join(\", \")}]` : \"\";\n\t\tconst rendered = [body, toolNote].filter((part) => part.length > 0).join(\" \");\n\t\tif (rendered) {\n\t\t\tlines.push(`${role}: ${rendered}`);\n\t\t}\n\t}\n\tconst state = isWorking ? \"working\" : \"idle (finished its turn)\";\n\treturn `<agent-state>${state}</agent-state>\\n<conversation>\\n${lines.join(\"\\n\")}\\n</conversation>`;\n}\n\n// Cuts a word-counting trailer the model sometimes appends, e.g.\n// `Sending X. That's 5 words? Count: X(1)... = 6 words.`. Kept to structural\n// counting markers so plain words (\"Waiting for CI\") survive.\nconst REASONING_TRAILER = /\\s*(?:[\"”]\\s*)?(?:\\bthat['’]?s\\s+\\d+\\s*words?\\b|\\bcount\\s*:|\\(\\d+\\)|=\\s*\\d+\\s*words?\\b).*/i;\nconst COUNTING_ARTIFACT = /\\(\\d+\\)|=\\s*\\d+\\s*words?\\b/i;\nconst MAX_RECAP_WORDS = 16;\n\nfunction cleanRecap(raw: string): string | undefined {\n\tconst value = raw\n\t\t.trim()\n\t\t.replace(REASONING_TRAILER, \"\")\n\t\t.replace(/^[\"“']+|[\"”']+$/g, \"\")\n\t\t.replace(/[.\\s]+$/, \"\")\n\t\t.trim();\n\tif (!value || value.startsWith(\"<\") || /present-tense|12 words/i.test(value)) {\n\t\treturn undefined;\n\t}\n\tif (COUNTING_ARTIFACT.test(value) || value.split(/\\s+/).length > MAX_RECAP_WORDS) {\n\t\treturn undefined;\n\t}\n\treturn value;\n}\n\n/** Take the content of the last `<recap>` and `<status>` tags; idle verdicts default to needs_input. */\nexport function parseAgentStatusResponse(text: string, isWorking: boolean): AgentStatusResult | undefined {\n\t// Normalize unicode angle-bracket lookalikes (‹ › < >) so a tag written with them still parses.\n\tconst cleaned = text.replace(/[‹<]/g, \"<\").replace(/[›>]/g, \">\");\n\n\tconst recapMatch = [...cleaned.matchAll(/<recap>([\\s\\S]*?)<\\/recap>/gi)].at(-1);\n\tconst summary = recapMatch ? cleanRecap(recapMatch[1]!) : undefined;\n\tif (!summary) {\n\t\treturn undefined;\n\t}\n\tif (isWorking) {\n\t\treturn { summary };\n\t}\n\tconst statusMatch = [...cleaned.matchAll(/<status>\\s*([a-z_]+)\\s*<\\/status>/gi)].at(-1);\n\tconst status = statusMatch ? statusMatch[1]!.toUpperCase() : undefined;\n\tconst taskState: AgentTaskState = status === \"COMPLETED\" ? \"completed\" : \"needs_input\";\n\treturn { summary, taskState };\n}\n\nexport interface GenerateAgentStatusParams {\n\tregistry: ModelRegistry;\n\tmodel?: Model<Api>;\n\tmessages: readonly AgentMessage[];\n\tisWorking: boolean;\n\tsignal?: AbortSignal;\n\trequests?: InferenceCoordinator;\n}\n\n/** Use the session's selected model, or keep the local status when unavailable. */\nexport async function generateAgentStatus(params: GenerateAgentStatusParams): Promise<AgentStatusResult | undefined> {\n\tconst { registry, messages, isWorking, signal, requests, model: selectedModel } = params;\n\tif (messages.length === 0) {\n\t\treturn undefined;\n\t}\n\tif (!selectedModel) {\n\t\treturn undefined;\n\t}\n\tconst model = { ...selectedModel, cost: { ...selectedModel.cost } };\n\tconst subject = buildStatusContext(messages, isWorking);\n\tconst timestamp = Date.now();\n\tconst auth = await registry.getApiKeyAndHeaders(model);\n\tif (!auth.ok || !auth.apiKey) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\tconst response = await completeInference(\n\t\t\trequests,\n\t\t\tmodel,\n\t\t\t{\n\t\t\t\tsystemPrompt: AGENT_STATUS_SYSTEM_PROMPT,\n\t\t\t\tmessages: [\n\t\t\t\t\t{\n\t\t\t\t\t\trole: \"user\" as const,\n\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: subject }],\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t\t{ maxTokens: SUMMARY_MAX_TOKENS, apiKey: auth.apiKey, headers: auth.headers, signal },\n\t\t\t{ purpose: \"native-control\", purposeDetail: \"daemon-status\" },\n\t\t);\n\t\tif (response.stopReason === \"error\") {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst textContent = response.content\n\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t.map((c) => c.text)\n\t\t\t.join(\"\\n\");\n\t\treturn parseAgentStatusResponse(textContent, isWorking);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction isSessionWorking(state: ActiveSessionState): boolean {\n\tconst session = state.runtime.session;\n\treturn session.isSessionActive;\n}\n\n/** Use the last assistant turn's real failure instead of asking a classifier to invent completed work. */\nfunction terminalTurnError(messages: readonly AgentMessage[]): string | undefined {\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst message = messages[i];\n\t\tif (message.role !== \"assistant\") continue;\n\t\tif (message.stopReason !== \"error\") return undefined;\n\t\tconst detail = message.errorMessage?.trim();\n\t\treturn detail ? `Model request failed: ${clamp(detail, 160)}` : \"Model request failed\";\n\t}\n\treturn undefined;\n}\n\n/**\n * Background status summarization for daemon-hosted sessions, top-level and\n * subagents alike. A periodic sweep refreshes working sessions; debounced\n * turn-end activity drives the idle verdict. Status lives in memory; settled\n * idle verdicts are persisted.\n */\nexport class DaemonSessionSummarizer {\n\tprivate interval: ReturnType<typeof setInterval> | undefined;\n\tprivate readonly debounceTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\t// Controller per in-flight summary so a closing session can abort its write.\n\tprivate readonly inFlight = new Map<string, AbortController>();\n\tprivate readonly tasks = new Map<string, Promise<void>>();\n\tprivate readonly forgotten = new Set<string>();\n\tprivate stopped = false;\n\t// Sessions requested while one was running; get one more pass on completion.\n\tprivate readonly rerunRequested = new Set<string>();\n\n\tconstructor(\n\t\tprivate readonly listSessions: () => readonly ActiveSessionState[],\n\t\tprivate readonly onStatusChanged?: (state: ActiveSessionState) => void,\n\t\t// Injectable for tests.\n\t\tprivate readonly generate: (\n\t\t\tparams: GenerateAgentStatusParams,\n\t\t) => Promise<AgentStatusResult | undefined> = generateAgentStatus,\n\t\tprivate readonly getRequests?: (session: ActiveSessionState[\"runtime\"][\"session\"]) => InferenceCoordinator,\n\t\tprivate readonly onError: (state: ActiveSessionState, error: unknown) => void = (_state, error) =>\n\t\t\tconsole.error(error),\n\t) {}\n\n\tstart(): void {\n\t\tif (this.interval) {\n\t\t\treturn;\n\t\t}\n\t\tthis.stopped = false;\n\t\tthis.interval = setInterval(() => {\n\t\t\tfor (const state of this.listSessions()) {\n\t\t\t\tthis.summarize(state);\n\t\t\t}\n\t\t}, SWEEP_INTERVAL_MS);\n\t\tthis.interval.unref?.();\n\t}\n\n\tasync stop(): Promise<void> {\n\t\tthis.stopped = true;\n\t\tif (this.interval) {\n\t\t\tclearInterval(this.interval);\n\t\t\tthis.interval = undefined;\n\t\t}\n\t\tfor (const timer of this.debounceTimers.values()) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tthis.debounceTimers.clear();\n\t\tfor (const controller of this.inFlight.values()) {\n\t\t\tcontroller.abort();\n\t\t}\n\t\tthis.rerunRequested.clear();\n\t\tawait Promise.all(this.tasks.values());\n\t}\n\n\t/** Cancel new work and drain any admitted status write before closing. */\n\tasync forget(activeSessionId: string): Promise<void> {\n\t\tthis.forgotten.add(activeSessionId);\n\t\tconst timer = this.debounceTimers.get(activeSessionId);\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.debounceTimers.delete(activeSessionId);\n\t\t}\n\t\tthis.inFlight.get(activeSessionId)?.abort();\n\t\tthis.rerunRequested.delete(activeSessionId);\n\t\tawait this.tasks.get(activeSessionId);\n\t}\n\n\t/** Seed in-memory status from the persisted entry when a session is added. */\n\tseed(state: ActiveSessionState): void {\n\t\tthis.forgotten.delete(state.activeSessionId);\n\t\tif (state.summaryState) {\n\t\t\treturn;\n\t\t}\n\t\tconst persisted = state.runtime.session.sessionManager.getLatestAgentStatus();\n\t\tif (persisted) {\n\t\t\tstate.summaryState = persisted;\n\t\t}\n\t}\n\n\t/** Called when a session finishes a turn; debounce until the agent settles. */\n\tnotifyActivity(state: ActiveSessionState): void {\n\t\tconst id = state.activeSessionId;\n\t\tif (this.stopped || this.forgotten.has(id)) return;\n\t\tconst existing = this.debounceTimers.get(id);\n\t\tif (existing) {\n\t\t\tclearTimeout(existing);\n\t\t}\n\t\tconst timer = setTimeout(() => {\n\t\t\tthis.debounceTimers.delete(id);\n\t\t\tthis.summarize(state);\n\t\t}, SETTLE_DEBOUNCE_MS);\n\t\ttimer.unref?.();\n\t\tthis.debounceTimers.set(id, timer);\n\t}\n\n\tprivate summarize(state: ActiveSessionState): Promise<void> {\n\t\tconst id = state.activeSessionId;\n\t\tif (this.stopped || this.forgotten.has(id)) return Promise.resolve();\n\t\tconst existing = this.tasks.get(id);\n\t\tif (existing) {\n\t\t\tthis.rerunRequested.add(id);\n\t\t\treturn existing;\n\t\t}\n\t\tconst task = this.runSummary(state)\n\t\t\t.catch((error) => this.onError(state, error))\n\t\t\t.finally(() => {\n\t\t\t\tthis.tasks.delete(id);\n\t\t\t\tif (this.rerunRequested.delete(id)) this.notifyActivity(state);\n\t\t\t});\n\t\tthis.tasks.set(id, task);\n\t\treturn task;\n\t}\n\n\tprivate async runSummary(state: ActiveSessionState): Promise<void> {\n\t\tconst id = state.activeSessionId;\n\t\tconst session = state.runtime.session;\n\t\tconst sessionManager = session.sessionManager;\n\t\tconst sessionId = sessionManager.getSessionId();\n\t\tconst sessionFile = sessionManager.getSessionFile();\n\t\tconst messages = session.messages;\n\t\tif (messages.length === 0) {\n\t\t\treturn;\n\t\t}\n\t\tconst messageCount = messages.length;\n\t\tconst isWorking = isSessionWorking(state);\n\t\tconst previous = state.summaryState;\n\t\t// Idle sessions with a current verdict need no refresh unless a terminal\n\t\t// error must replace a previously fabricated verdict. Working sessions\n\t\t// always refresh so the recap keeps up with the in-progress turn.\n\t\tconst contentUnchanged = previous?.basedOnMessageCount === messageCount;\n\t\tconst owesIdleVerdict = !isWorking && previous?.taskState === undefined;\n\t\t// A blank recap means the model call hasn't succeeded yet (e.g. the\n\t\t// needs_input fallback fired on a transient failure); keep retrying until a\n\t\t// real summary lands so the recap isn't left permanently empty.\n\t\tconst owesSummary = !isWorking && !previous?.summary;\n\t\tconst turnError = !isWorking ? terminalTurnError(messages) : undefined;\n\t\tconst owesErrorVerdict =\n\t\t\tturnError !== undefined && (previous?.taskState !== \"needs_input\" || previous?.summary !== turnError);\n\t\tif (contentUnchanged && !isWorking && !owesIdleVerdict && !owesSummary && !owesErrorVerdict) {\n\t\t\treturn;\n\t\t}\n\t\t// Include the in-progress message so a long streaming turn gets a live recap.\n\t\tconst streaming = isWorking ? session.state.streamingMessage : undefined;\n\t\tconst contextMessages = streaming ? [...messages, streaming] : messages;\n\n\t\tconst controller = new AbortController();\n\t\tconst isCurrent = () =>\n\t\t\t!controller.signal.aborted &&\n\t\t\tstate.runtime.session === session &&\n\t\t\tsession.sessionManager === sessionManager &&\n\t\t\tsessionManager.getSessionId() === sessionId &&\n\t\t\tsessionManager.getSessionFile() === sessionFile &&\n\t\t\tisSessionWorking(state) === isWorking &&\n\t\t\tsession.messages.length === messageCount;\n\t\tthis.inFlight.set(id, controller);\n\t\tlet requests: InferenceCoordinator | undefined;\n\t\tlet status: AgentStatus | undefined;\n\t\tlet changed = false;\n\t\ttry {\n\t\t\tif (turnError === undefined) requests = this.getRequests?.(session);\n\t\t\tconst generated: AgentStatusResult | undefined =\n\t\t\t\tturnError !== undefined\n\t\t\t\t\t? { summary: turnError, taskState: \"needs_input\" }\n\t\t\t\t\t: await this.generate({\n\t\t\t\t\t\t\tregistry: session.modelRegistry,\n\t\t\t\t\t\t\tmodel: session.model,\n\t\t\t\t\t\t\tmessages: contextMessages,\n\t\t\t\t\t\t\tisWorking,\n\t\t\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t\t\trequests,\n\t\t\t\t\t\t});\n\t\t\t// An unused capture otherwise counts this idle subject as working.\n\t\t\tawait requests?.dispose();\n\t\t\t// A failed classification on an idle session would spin at \"working\"\n\t\t\t// forever (the activity axis holds unjudged idle sessions there), so\n\t\t\t// settle it to needs_input.\n\t\t\tconst result =\n\t\t\t\tgenerated ??\n\t\t\t\t(!isWorking && (owesIdleVerdict || owesSummary)\n\t\t\t\t\t? { summary: previous?.summary ?? \"\", taskState: \"needs_input\" as const }\n\t\t\t\t\t: undefined);\n\t\t\tif (!result) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Discard if the session closed, was swapped, or moved to a new turn\n\t\t\t// during the async call — never write a verdict for stale state.\n\t\t\tif (!isCurrent()) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// A working refresh carries no verdict; keep the prior one at the same\n\t\t\t// message count so a still-valid needs_input isn't dropped.\n\t\t\tconst taskState =\n\t\t\t\tresult.taskState ?? (previous?.basedOnMessageCount === messageCount ? previous?.taskState : undefined);\n\t\t\tstatus = {\n\t\t\t\tsummary: result.summary,\n\t\t\t\ttaskState,\n\t\t\t\tbasedOnMessageCount: messageCount,\n\t\t\t};\n\t\t\t// An idle settle refreshes the verdict's currency, which drives the roster's\n\t\t\t// activity axis: it must publish even when the verdict text is unchanged.\n\t\t\tchanged =\n\t\t\t\tprevious?.summary !== status.summary ||\n\t\t\t\tprevious?.taskState !== status.taskState ||\n\t\t\t\t(!isWorking && previous?.basedOnMessageCount !== status.basedOnMessageCount);\n\t\t\t// Persist only settled idle verdicts, never mid-stream.\n\t\t\tif (!isWorking) {\n\t\t\t\tawait sessionManager.appendAgentStatus(status);\n\t\t\t\tif (!isCurrent()) return;\n\t\t\t}\n\t\t} finally {\n\t\t\ttry {\n\t\t\t\tawait requests?.dispose();\n\t\t\t} finally {\n\t\t\t\tthis.inFlight.delete(id);\n\t\t\t}\n\t\t}\n\t\tif (status && isCurrent()) {\n\t\t\tstate.summaryState = status;\n\t\t\tif (changed) this.onStatusChanged?.(state);\n\t\t}\n\t}\n}\n"]}
@@ -2,8 +2,6 @@ import { completeInference } from "../../core/inference-coordinator.js";
2
2
  const SWEEP_INTERVAL_MS = 25_000;
3
3
  // Collapse a tool-use loop's rapid turn_end bursts into one summarization.
4
4
  const SETTLE_DEBOUNCE_MS = 2_000;
5
- const SUMMARY_MODEL_PROVIDER = "prime-inference";
6
- const SUMMARY_MODEL_ID = "qwen/qwen3-30b-a3b-instruct-2507";
7
5
  const SUMMARY_CONTEXT_MESSAGES = 8;
8
6
  const SUMMARY_MAX_CHARS_PER_MESSAGE = 600;
9
7
  // Generous so a chatty model still closes the tags before truncation.
@@ -22,14 +20,6 @@ When you are unsure between COMPLETED and NEEDS_INPUT, choose NEEDS_INPUT.
22
20
  Example:
23
21
  <recap>Refactoring the auth middleware and updating its tests</recap>
24
22
  <status>NEEDS_INPUT</status>`;
25
- /** Resolve the cheap summary model, or undefined when it has no configured auth. */
26
- export function resolveSummaryModel(registry) {
27
- const model = registry.find(SUMMARY_MODEL_PROVIDER, SUMMARY_MODEL_ID);
28
- if (model && registry.hasConfiguredAuth(model)) {
29
- return model;
30
- }
31
- return undefined;
32
- }
33
23
  function messageText(content) {
34
24
  if (typeof content === "string") {
35
25
  return { text: content, tools: [] };
@@ -118,13 +108,12 @@ export function parseAgentStatusResponse(text, isWorking) {
118
108
  const taskState = status === "COMPLETED" ? "completed" : "needs_input";
119
109
  return { summary, taskState };
120
110
  }
121
- /** One cheap model call for a fresh status, or undefined if unavailable/empty/failed. */
111
+ /** Use the session's selected model, or keep the local status when unavailable. */
122
112
  export async function generateAgentStatus(params) {
123
- const { registry, messages, isWorking, signal, requests } = params;
113
+ const { registry, messages, isWorking, signal, requests, model: selectedModel } = params;
124
114
  if (messages.length === 0) {
125
115
  return undefined;
126
116
  }
127
- const selectedModel = resolveSummaryModel(registry);
128
117
  if (!selectedModel) {
129
118
  return undefined;
130
119
  }
@@ -163,6 +152,19 @@ function isSessionWorking(state) {
163
152
  const session = state.runtime.session;
164
153
  return session.isSessionActive;
165
154
  }
155
+ /** Use the last assistant turn's real failure instead of asking a classifier to invent completed work. */
156
+ function terminalTurnError(messages) {
157
+ for (let i = messages.length - 1; i >= 0; i--) {
158
+ const message = messages[i];
159
+ if (message.role !== "assistant")
160
+ continue;
161
+ if (message.stopReason !== "error")
162
+ return undefined;
163
+ const detail = message.errorMessage?.trim();
164
+ return detail ? `Model request failed: ${clamp(detail, 160)}` : "Model request failed";
165
+ }
166
+ return undefined;
167
+ }
166
168
  /**
167
169
  * Background status summarization for daemon-hosted sessions, top-level and
168
170
  * subagents alike. A periodic sweep refreshes working sessions; debounced
@@ -292,7 +294,8 @@ export class DaemonSessionSummarizer {
292
294
  const messageCount = messages.length;
293
295
  const isWorking = isSessionWorking(state);
294
296
  const previous = state.summaryState;
295
- // Idle sessions with a current verdict need no refresh; working sessions
297
+ // Idle sessions with a current verdict need no refresh unless a terminal
298
+ // error must replace a previously fabricated verdict. Working sessions
296
299
  // always refresh so the recap keeps up with the in-progress turn.
297
300
  const contentUnchanged = previous?.basedOnMessageCount === messageCount;
298
301
  const owesIdleVerdict = !isWorking && previous?.taskState === undefined;
@@ -300,7 +303,9 @@ export class DaemonSessionSummarizer {
300
303
  // needs_input fallback fired on a transient failure); keep retrying until a
301
304
  // real summary lands so the recap isn't left permanently empty.
302
305
  const owesSummary = !isWorking && !previous?.summary;
303
- if (contentUnchanged && !isWorking && !owesIdleVerdict && !owesSummary) {
306
+ const turnError = !isWorking ? terminalTurnError(messages) : undefined;
307
+ const owesErrorVerdict = turnError !== undefined && (previous?.taskState !== "needs_input" || previous?.summary !== turnError);
308
+ if (contentUnchanged && !isWorking && !owesIdleVerdict && !owesSummary && !owesErrorVerdict) {
304
309
  return;
305
310
  }
306
311
  // Include the in-progress message so a long streaming turn gets a live recap.
@@ -319,14 +324,18 @@ export class DaemonSessionSummarizer {
319
324
  let status;
320
325
  let changed = false;
321
326
  try {
322
- requests = this.getRequests?.(session);
323
- const generated = await this.generate({
324
- registry: session.modelRegistry,
325
- messages: contextMessages,
326
- isWorking,
327
- signal: controller.signal,
328
- requests,
329
- });
327
+ if (turnError === undefined)
328
+ requests = this.getRequests?.(session);
329
+ const generated = turnError !== undefined
330
+ ? { summary: turnError, taskState: "needs_input" }
331
+ : await this.generate({
332
+ registry: session.modelRegistry,
333
+ model: session.model,
334
+ messages: contextMessages,
335
+ isWorking,
336
+ signal: controller.signal,
337
+ requests,
338
+ });
330
339
  // An unused capture otherwise counts this idle subject as working.
331
340
  await requests?.dispose();
332
341
  // A failed classification on an idle session would spin at "working"
@@ -1 +1 @@
1
- {"version":3,"file":"daemon-session-summarizer.js","sourceRoot":"","sources":["../../../src/modes/daemon/daemon-session-summarizer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@ponythewhite/base-context-agent\";\nimport type { Api, Model } from \"@ponythewhite/base-context-ai\";\nimport { completeInference, type InferenceCoordinator } from \"../../core/inference-coordinator.js\";\nimport type { ModelRegistry } from \"../../core/model-registry.js\";\nimport type { AgentStatus, AgentTaskState } from \"../../core/session-manager.js\";\nimport type { ActiveSessionState } from \"./active-session-state.js\";\n\nconst SWEEP_INTERVAL_MS = 25_000;\n// Collapse a tool-use loop's rapid turn_end bursts into one summarization.\nconst SETTLE_DEBOUNCE_MS = 2_000;\n\nconst SUMMARY_MODEL_PROVIDER = \"prime-inference\";\nconst SUMMARY_MODEL_ID = \"qwen/qwen3-30b-a3b-instruct-2507\";\n\nconst SUMMARY_CONTEXT_MESSAGES = 8;\nconst SUMMARY_MAX_CHARS_PER_MESSAGE = 600;\n// Generous so a chatty model still closes the tags before truncation.\nconst SUMMARY_MAX_TOKENS = 400;\n\nexport const AGENT_STATUS_SYSTEM_PROMPT = `You generate a status line for an AI coding agent dashboard. You are given the recent conversation between a user and the agent, plus whether the agent is currently working or idle.\n\nOutput ONLY these two tags, nothing before, between, or after. Do not think out loud, explain, or count words.\n<recap>a present-tense clause, at most 12 words, saying what the agent is doing or just did, no trailing period</recap>\n<status>one of NEEDS_INPUT, COMPLETED</status>\n\nSTATUS meaning:\n- COMPLETED: the agent finished its turn AND the user's request is fully done with nothing left.\n- NEEDS_INPUT: the agent finished its turn but the task is not fully done — it asked a question, hit a blocker, or needs more prompting.\nWhen you are unsure between COMPLETED and NEEDS_INPUT, choose NEEDS_INPUT.\n\nExample:\n<recap>Refactoring the auth middleware and updating its tests</recap>\n<status>NEEDS_INPUT</status>`;\n\nexport interface AgentStatusResult {\n\tsummary: string;\n\ttaskState?: AgentTaskState;\n}\n\n/** Resolve the cheap summary model, or undefined when it has no configured auth. */\nexport function resolveSummaryModel(registry: ModelRegistry): Model<Api> | undefined {\n\tconst model = registry.find(SUMMARY_MODEL_PROVIDER, SUMMARY_MODEL_ID);\n\tif (model && registry.hasConfiguredAuth(model)) {\n\t\treturn model;\n\t}\n\treturn undefined;\n}\n\nfunction messageText(content: unknown): { text: string; tools: string[] } {\n\tif (typeof content === \"string\") {\n\t\treturn { text: content, tools: [] };\n\t}\n\tif (!Array.isArray(content)) {\n\t\treturn { text: \"\", tools: [] };\n\t}\n\tconst parts: string[] = [];\n\tconst tools: string[] = [];\n\tfor (const block of content) {\n\t\tif (typeof block !== \"object\" || block === null) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst type = (block as { type?: unknown }).type;\n\t\tif (type === \"text\" && typeof (block as { text?: unknown }).text === \"string\") {\n\t\t\tparts.push((block as { text: string }).text);\n\t\t} else if (type === \"tool_use\" || type === \"toolUse\") {\n\t\t\tconst name = (block as { name?: unknown }).name;\n\t\t\tif (typeof name === \"string\") {\n\t\t\t\ttools.push(name);\n\t\t\t}\n\t\t}\n\t}\n\treturn { text: parts.join(\"\\n\"), tools };\n}\n\nfunction clamp(text: string, max: number): string {\n\tconst normalized = text.replace(/\\s+/g, \" \").trim();\n\treturn normalized.length > max ? `${normalized.slice(0, max)}…` : normalized;\n}\n\n/** Serialize the trailing messages into a compact prompt body (tool calls by name only). */\nexport function buildStatusContext(messages: readonly AgentMessage[], isWorking: boolean): string {\n\tconst recent = messages.slice(-SUMMARY_CONTEXT_MESSAGES);\n\tconst lines: string[] = [];\n\tfor (const message of recent) {\n\t\tconst role = message.role;\n\t\tif (role !== \"user\" && role !== \"assistant\" && role !== \"toolResult\" && role !== \"custom\") {\n\t\t\tcontinue;\n\t\t}\n\t\tconst { text, tools } = messageText(message.content);\n\t\tconst body = clamp(text, SUMMARY_MAX_CHARS_PER_MESSAGE);\n\t\tconst toolNote = tools.length > 0 ? `[tools: ${[...new Set(tools)].join(\", \")}]` : \"\";\n\t\tconst rendered = [body, toolNote].filter((part) => part.length > 0).join(\" \");\n\t\tif (rendered) {\n\t\t\tlines.push(`${role}: ${rendered}`);\n\t\t}\n\t}\n\tconst state = isWorking ? \"working\" : \"idle (finished its turn)\";\n\treturn `<agent-state>${state}</agent-state>\\n<conversation>\\n${lines.join(\"\\n\")}\\n</conversation>`;\n}\n\n// Cuts a word-counting trailer the model sometimes appends, e.g.\n// `Sending X. That's 5 words? Count: X(1)... = 6 words.`. Kept to structural\n// counting markers so plain words (\"Waiting for CI\") survive.\nconst REASONING_TRAILER = /\\s*(?:[\"”]\\s*)?(?:\\bthat['’]?s\\s+\\d+\\s*words?\\b|\\bcount\\s*:|\\(\\d+\\)|=\\s*\\d+\\s*words?\\b).*/i;\nconst COUNTING_ARTIFACT = /\\(\\d+\\)|=\\s*\\d+\\s*words?\\b/i;\nconst MAX_RECAP_WORDS = 16;\n\nfunction cleanRecap(raw: string): string | undefined {\n\tconst value = raw\n\t\t.trim()\n\t\t.replace(REASONING_TRAILER, \"\")\n\t\t.replace(/^[\"“']+|[\"”']+$/g, \"\")\n\t\t.replace(/[.\\s]+$/, \"\")\n\t\t.trim();\n\tif (!value || value.startsWith(\"<\") || /present-tense|12 words/i.test(value)) {\n\t\treturn undefined;\n\t}\n\tif (COUNTING_ARTIFACT.test(value) || value.split(/\\s+/).length > MAX_RECAP_WORDS) {\n\t\treturn undefined;\n\t}\n\treturn value;\n}\n\n/** Take the content of the last `<recap>` and `<status>` tags; idle verdicts default to needs_input. */\nexport function parseAgentStatusResponse(text: string, isWorking: boolean): AgentStatusResult | undefined {\n\t// Normalize unicode angle-bracket lookalikes (‹ › < >) so a tag written with them still parses.\n\tconst cleaned = text.replace(/[‹<]/g, \"<\").replace(/[›>]/g, \">\");\n\n\tconst recapMatch = [...cleaned.matchAll(/<recap>([\\s\\S]*?)<\\/recap>/gi)].at(-1);\n\tconst summary = recapMatch ? cleanRecap(recapMatch[1]!) : undefined;\n\tif (!summary) {\n\t\treturn undefined;\n\t}\n\tif (isWorking) {\n\t\treturn { summary };\n\t}\n\tconst statusMatch = [...cleaned.matchAll(/<status>\\s*([a-z_]+)\\s*<\\/status>/gi)].at(-1);\n\tconst status = statusMatch ? statusMatch[1]!.toUpperCase() : undefined;\n\tconst taskState: AgentTaskState = status === \"COMPLETED\" ? \"completed\" : \"needs_input\";\n\treturn { summary, taskState };\n}\n\nexport interface GenerateAgentStatusParams {\n\tregistry: ModelRegistry;\n\tmessages: readonly AgentMessage[];\n\tisWorking: boolean;\n\tsignal?: AbortSignal;\n\trequests?: InferenceCoordinator;\n}\n\n/** One cheap model call for a fresh status, or undefined if unavailable/empty/failed. */\nexport async function generateAgentStatus(params: GenerateAgentStatusParams): Promise<AgentStatusResult | undefined> {\n\tconst { registry, messages, isWorking, signal, requests } = params;\n\tif (messages.length === 0) {\n\t\treturn undefined;\n\t}\n\tconst selectedModel = resolveSummaryModel(registry);\n\tif (!selectedModel) {\n\t\treturn undefined;\n\t}\n\tconst model = { ...selectedModel, cost: { ...selectedModel.cost } };\n\tconst subject = buildStatusContext(messages, isWorking);\n\tconst timestamp = Date.now();\n\tconst auth = await registry.getApiKeyAndHeaders(model);\n\tif (!auth.ok || !auth.apiKey) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\tconst response = await completeInference(\n\t\t\trequests,\n\t\t\tmodel,\n\t\t\t{\n\t\t\t\tsystemPrompt: AGENT_STATUS_SYSTEM_PROMPT,\n\t\t\t\tmessages: [\n\t\t\t\t\t{\n\t\t\t\t\t\trole: \"user\" as const,\n\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: subject }],\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t\t{ maxTokens: SUMMARY_MAX_TOKENS, apiKey: auth.apiKey, headers: auth.headers, signal },\n\t\t\t{ purpose: \"native-control\", purposeDetail: \"daemon-status\" },\n\t\t);\n\t\tif (response.stopReason === \"error\") {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst textContent = response.content\n\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t.map((c) => c.text)\n\t\t\t.join(\"\\n\");\n\t\treturn parseAgentStatusResponse(textContent, isWorking);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction isSessionWorking(state: ActiveSessionState): boolean {\n\tconst session = state.runtime.session;\n\treturn session.isSessionActive;\n}\n\n/**\n * Background status summarization for daemon-hosted sessions, top-level and\n * subagents alike. A periodic sweep refreshes working sessions; debounced\n * turn-end activity drives the idle verdict. Status lives in memory; settled\n * idle verdicts are persisted.\n */\nexport class DaemonSessionSummarizer {\n\tprivate interval: ReturnType<typeof setInterval> | undefined;\n\tprivate readonly debounceTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\t// Controller per in-flight summary so a closing session can abort its write.\n\tprivate readonly inFlight = new Map<string, AbortController>();\n\tprivate readonly tasks = new Map<string, Promise<void>>();\n\tprivate readonly forgotten = new Set<string>();\n\tprivate stopped = false;\n\t// Sessions requested while one was running; get one more pass on completion.\n\tprivate readonly rerunRequested = new Set<string>();\n\n\tconstructor(\n\t\tprivate readonly listSessions: () => readonly ActiveSessionState[],\n\t\tprivate readonly onStatusChanged?: (state: ActiveSessionState) => void,\n\t\t// Injectable for tests.\n\t\tprivate readonly generate: (\n\t\t\tparams: GenerateAgentStatusParams,\n\t\t) => Promise<AgentStatusResult | undefined> = generateAgentStatus,\n\t\tprivate readonly getRequests?: (session: ActiveSessionState[\"runtime\"][\"session\"]) => InferenceCoordinator,\n\t\tprivate readonly onError: (state: ActiveSessionState, error: unknown) => void = (_state, error) =>\n\t\t\tconsole.error(error),\n\t) {}\n\n\tstart(): void {\n\t\tif (this.interval) {\n\t\t\treturn;\n\t\t}\n\t\tthis.stopped = false;\n\t\tthis.interval = setInterval(() => {\n\t\t\tfor (const state of this.listSessions()) {\n\t\t\t\tthis.summarize(state);\n\t\t\t}\n\t\t}, SWEEP_INTERVAL_MS);\n\t\tthis.interval.unref?.();\n\t}\n\n\tasync stop(): Promise<void> {\n\t\tthis.stopped = true;\n\t\tif (this.interval) {\n\t\t\tclearInterval(this.interval);\n\t\t\tthis.interval = undefined;\n\t\t}\n\t\tfor (const timer of this.debounceTimers.values()) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tthis.debounceTimers.clear();\n\t\tfor (const controller of this.inFlight.values()) {\n\t\t\tcontroller.abort();\n\t\t}\n\t\tthis.rerunRequested.clear();\n\t\tawait Promise.all(this.tasks.values());\n\t}\n\n\t/** Cancel new work and drain any admitted status write before closing. */\n\tasync forget(activeSessionId: string): Promise<void> {\n\t\tthis.forgotten.add(activeSessionId);\n\t\tconst timer = this.debounceTimers.get(activeSessionId);\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.debounceTimers.delete(activeSessionId);\n\t\t}\n\t\tthis.inFlight.get(activeSessionId)?.abort();\n\t\tthis.rerunRequested.delete(activeSessionId);\n\t\tawait this.tasks.get(activeSessionId);\n\t}\n\n\t/** Seed in-memory status from the persisted entry when a session is added. */\n\tseed(state: ActiveSessionState): void {\n\t\tthis.forgotten.delete(state.activeSessionId);\n\t\tif (state.summaryState) {\n\t\t\treturn;\n\t\t}\n\t\tconst persisted = state.runtime.session.sessionManager.getLatestAgentStatus();\n\t\tif (persisted) {\n\t\t\tstate.summaryState = persisted;\n\t\t}\n\t}\n\n\t/** Called when a session finishes a turn; debounce until the agent settles. */\n\tnotifyActivity(state: ActiveSessionState): void {\n\t\tconst id = state.activeSessionId;\n\t\tif (this.stopped || this.forgotten.has(id)) return;\n\t\tconst existing = this.debounceTimers.get(id);\n\t\tif (existing) {\n\t\t\tclearTimeout(existing);\n\t\t}\n\t\tconst timer = setTimeout(() => {\n\t\t\tthis.debounceTimers.delete(id);\n\t\t\tthis.summarize(state);\n\t\t}, SETTLE_DEBOUNCE_MS);\n\t\ttimer.unref?.();\n\t\tthis.debounceTimers.set(id, timer);\n\t}\n\n\tprivate summarize(state: ActiveSessionState): Promise<void> {\n\t\tconst id = state.activeSessionId;\n\t\tif (this.stopped || this.forgotten.has(id)) return Promise.resolve();\n\t\tconst existing = this.tasks.get(id);\n\t\tif (existing) {\n\t\t\tthis.rerunRequested.add(id);\n\t\t\treturn existing;\n\t\t}\n\t\tconst task = this.runSummary(state)\n\t\t\t.catch((error) => this.onError(state, error))\n\t\t\t.finally(() => {\n\t\t\t\tthis.tasks.delete(id);\n\t\t\t\tif (this.rerunRequested.delete(id)) this.notifyActivity(state);\n\t\t\t});\n\t\tthis.tasks.set(id, task);\n\t\treturn task;\n\t}\n\n\tprivate async runSummary(state: ActiveSessionState): Promise<void> {\n\t\tconst id = state.activeSessionId;\n\t\tconst session = state.runtime.session;\n\t\tconst sessionManager = session.sessionManager;\n\t\tconst sessionId = sessionManager.getSessionId();\n\t\tconst sessionFile = sessionManager.getSessionFile();\n\t\tconst messages = session.messages;\n\t\tif (messages.length === 0) {\n\t\t\treturn;\n\t\t}\n\t\tconst messageCount = messages.length;\n\t\tconst isWorking = isSessionWorking(state);\n\t\tconst previous = state.summaryState;\n\t\t// Idle sessions with a current verdict need no refresh; working sessions\n\t\t// always refresh so the recap keeps up with the in-progress turn.\n\t\tconst contentUnchanged = previous?.basedOnMessageCount === messageCount;\n\t\tconst owesIdleVerdict = !isWorking && previous?.taskState === undefined;\n\t\t// A blank recap means the model call hasn't succeeded yet (e.g. the\n\t\t// needs_input fallback fired on a transient failure); keep retrying until a\n\t\t// real summary lands so the recap isn't left permanently empty.\n\t\tconst owesSummary = !isWorking && !previous?.summary;\n\t\tif (contentUnchanged && !isWorking && !owesIdleVerdict && !owesSummary) {\n\t\t\treturn;\n\t\t}\n\t\t// Include the in-progress message so a long streaming turn gets a live recap.\n\t\tconst streaming = isWorking ? session.state.streamingMessage : undefined;\n\t\tconst contextMessages = streaming ? [...messages, streaming] : messages;\n\n\t\tconst controller = new AbortController();\n\t\tconst isCurrent = () =>\n\t\t\t!controller.signal.aborted &&\n\t\t\tstate.runtime.session === session &&\n\t\t\tsession.sessionManager === sessionManager &&\n\t\t\tsessionManager.getSessionId() === sessionId &&\n\t\t\tsessionManager.getSessionFile() === sessionFile &&\n\t\t\tisSessionWorking(state) === isWorking &&\n\t\t\tsession.messages.length === messageCount;\n\t\tthis.inFlight.set(id, controller);\n\t\tlet requests: InferenceCoordinator | undefined;\n\t\tlet status: AgentStatus | undefined;\n\t\tlet changed = false;\n\t\ttry {\n\t\t\trequests = this.getRequests?.(session);\n\t\t\tconst generated = await this.generate({\n\t\t\t\tregistry: session.modelRegistry,\n\t\t\t\tmessages: contextMessages,\n\t\t\t\tisWorking,\n\t\t\t\tsignal: controller.signal,\n\t\t\t\trequests,\n\t\t\t});\n\t\t\t// An unused capture otherwise counts this idle subject as working.\n\t\t\tawait requests?.dispose();\n\t\t\t// A failed classification on an idle session would spin at \"working\"\n\t\t\t// forever (the activity axis holds unjudged idle sessions there), so\n\t\t\t// settle it to needs_input.\n\t\t\tconst result =\n\t\t\t\tgenerated ??\n\t\t\t\t(!isWorking && (owesIdleVerdict || owesSummary)\n\t\t\t\t\t? { summary: previous?.summary ?? \"\", taskState: \"needs_input\" as const }\n\t\t\t\t\t: undefined);\n\t\t\tif (!result) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Discard if the session closed, was swapped, or moved to a new turn\n\t\t\t// during the async call — never write a verdict for stale state.\n\t\t\tif (!isCurrent()) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// A working refresh carries no verdict; keep the prior one at the same\n\t\t\t// message count so a still-valid needs_input isn't dropped.\n\t\t\tconst taskState =\n\t\t\t\tresult.taskState ?? (previous?.basedOnMessageCount === messageCount ? previous?.taskState : undefined);\n\t\t\tstatus = {\n\t\t\t\tsummary: result.summary,\n\t\t\t\ttaskState,\n\t\t\t\tbasedOnMessageCount: messageCount,\n\t\t\t};\n\t\t\t// An idle settle refreshes the verdict's currency, which drives the roster's\n\t\t\t// activity axis: it must publish even when the verdict text is unchanged.\n\t\t\tchanged =\n\t\t\t\tprevious?.summary !== status.summary ||\n\t\t\t\tprevious?.taskState !== status.taskState ||\n\t\t\t\t(!isWorking && previous?.basedOnMessageCount !== status.basedOnMessageCount);\n\t\t\t// Persist only settled idle verdicts, never mid-stream.\n\t\t\tif (!isWorking) {\n\t\t\t\tawait sessionManager.appendAgentStatus(status);\n\t\t\t\tif (!isCurrent()) return;\n\t\t\t}\n\t\t} finally {\n\t\t\ttry {\n\t\t\t\tawait requests?.dispose();\n\t\t\t} finally {\n\t\t\t\tthis.inFlight.delete(id);\n\t\t\t}\n\t\t}\n\t\tif (status && isCurrent()) {\n\t\t\tstate.summaryState = status;\n\t\t\tif (changed) this.onStatusChanged?.(state);\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"daemon-session-summarizer.js","sourceRoot":"","sources":["../../../src/modes/daemon/daemon-session-summarizer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@ponythewhite/base-context-agent\";\nimport type { Api, Model } from \"@ponythewhite/base-context-ai\";\nimport { completeInference, type InferenceCoordinator } from \"../../core/inference-coordinator.js\";\nimport type { ModelRegistry } from \"../../core/model-registry.js\";\nimport type { AgentStatus, AgentTaskState } from \"../../core/session-manager.js\";\nimport type { ActiveSessionState } from \"./active-session-state.js\";\n\nconst SWEEP_INTERVAL_MS = 25_000;\n// Collapse a tool-use loop's rapid turn_end bursts into one summarization.\nconst SETTLE_DEBOUNCE_MS = 2_000;\n\nconst SUMMARY_CONTEXT_MESSAGES = 8;\nconst SUMMARY_MAX_CHARS_PER_MESSAGE = 600;\n// Generous so a chatty model still closes the tags before truncation.\nconst SUMMARY_MAX_TOKENS = 400;\n\nexport const AGENT_STATUS_SYSTEM_PROMPT = `You generate a status line for an AI coding agent dashboard. You are given the recent conversation between a user and the agent, plus whether the agent is currently working or idle.\n\nOutput ONLY these two tags, nothing before, between, or after. Do not think out loud, explain, or count words.\n<recap>a present-tense clause, at most 12 words, saying what the agent is doing or just did, no trailing period</recap>\n<status>one of NEEDS_INPUT, COMPLETED</status>\n\nSTATUS meaning:\n- COMPLETED: the agent finished its turn AND the user's request is fully done with nothing left.\n- NEEDS_INPUT: the agent finished its turn but the task is not fully done — it asked a question, hit a blocker, or needs more prompting.\nWhen you are unsure between COMPLETED and NEEDS_INPUT, choose NEEDS_INPUT.\n\nExample:\n<recap>Refactoring the auth middleware and updating its tests</recap>\n<status>NEEDS_INPUT</status>`;\n\nexport interface AgentStatusResult {\n\tsummary: string;\n\ttaskState?: AgentTaskState;\n}\n\nfunction messageText(content: unknown): { text: string; tools: string[] } {\n\tif (typeof content === \"string\") {\n\t\treturn { text: content, tools: [] };\n\t}\n\tif (!Array.isArray(content)) {\n\t\treturn { text: \"\", tools: [] };\n\t}\n\tconst parts: string[] = [];\n\tconst tools: string[] = [];\n\tfor (const block of content) {\n\t\tif (typeof block !== \"object\" || block === null) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst type = (block as { type?: unknown }).type;\n\t\tif (type === \"text\" && typeof (block as { text?: unknown }).text === \"string\") {\n\t\t\tparts.push((block as { text: string }).text);\n\t\t} else if (type === \"tool_use\" || type === \"toolUse\") {\n\t\t\tconst name = (block as { name?: unknown }).name;\n\t\t\tif (typeof name === \"string\") {\n\t\t\t\ttools.push(name);\n\t\t\t}\n\t\t}\n\t}\n\treturn { text: parts.join(\"\\n\"), tools };\n}\n\nfunction clamp(text: string, max: number): string {\n\tconst normalized = text.replace(/\\s+/g, \" \").trim();\n\treturn normalized.length > max ? `${normalized.slice(0, max)}…` : normalized;\n}\n\n/** Serialize the trailing messages into a compact prompt body (tool calls by name only). */\nexport function buildStatusContext(messages: readonly AgentMessage[], isWorking: boolean): string {\n\tconst recent = messages.slice(-SUMMARY_CONTEXT_MESSAGES);\n\tconst lines: string[] = [];\n\tfor (const message of recent) {\n\t\tconst role = message.role;\n\t\tif (role !== \"user\" && role !== \"assistant\" && role !== \"toolResult\" && role !== \"custom\") {\n\t\t\tcontinue;\n\t\t}\n\t\tconst { text, tools } = messageText(message.content);\n\t\tconst body = clamp(text, SUMMARY_MAX_CHARS_PER_MESSAGE);\n\t\tconst toolNote = tools.length > 0 ? `[tools: ${[...new Set(tools)].join(\", \")}]` : \"\";\n\t\tconst rendered = [body, toolNote].filter((part) => part.length > 0).join(\" \");\n\t\tif (rendered) {\n\t\t\tlines.push(`${role}: ${rendered}`);\n\t\t}\n\t}\n\tconst state = isWorking ? \"working\" : \"idle (finished its turn)\";\n\treturn `<agent-state>${state}</agent-state>\\n<conversation>\\n${lines.join(\"\\n\")}\\n</conversation>`;\n}\n\n// Cuts a word-counting trailer the model sometimes appends, e.g.\n// `Sending X. That's 5 words? Count: X(1)... = 6 words.`. Kept to structural\n// counting markers so plain words (\"Waiting for CI\") survive.\nconst REASONING_TRAILER = /\\s*(?:[\"”]\\s*)?(?:\\bthat['’]?s\\s+\\d+\\s*words?\\b|\\bcount\\s*:|\\(\\d+\\)|=\\s*\\d+\\s*words?\\b).*/i;\nconst COUNTING_ARTIFACT = /\\(\\d+\\)|=\\s*\\d+\\s*words?\\b/i;\nconst MAX_RECAP_WORDS = 16;\n\nfunction cleanRecap(raw: string): string | undefined {\n\tconst value = raw\n\t\t.trim()\n\t\t.replace(REASONING_TRAILER, \"\")\n\t\t.replace(/^[\"“']+|[\"”']+$/g, \"\")\n\t\t.replace(/[.\\s]+$/, \"\")\n\t\t.trim();\n\tif (!value || value.startsWith(\"<\") || /present-tense|12 words/i.test(value)) {\n\t\treturn undefined;\n\t}\n\tif (COUNTING_ARTIFACT.test(value) || value.split(/\\s+/).length > MAX_RECAP_WORDS) {\n\t\treturn undefined;\n\t}\n\treturn value;\n}\n\n/** Take the content of the last `<recap>` and `<status>` tags; idle verdicts default to needs_input. */\nexport function parseAgentStatusResponse(text: string, isWorking: boolean): AgentStatusResult | undefined {\n\t// Normalize unicode angle-bracket lookalikes (‹ › < >) so a tag written with them still parses.\n\tconst cleaned = text.replace(/[‹<]/g, \"<\").replace(/[›>]/g, \">\");\n\n\tconst recapMatch = [...cleaned.matchAll(/<recap>([\\s\\S]*?)<\\/recap>/gi)].at(-1);\n\tconst summary = recapMatch ? cleanRecap(recapMatch[1]!) : undefined;\n\tif (!summary) {\n\t\treturn undefined;\n\t}\n\tif (isWorking) {\n\t\treturn { summary };\n\t}\n\tconst statusMatch = [...cleaned.matchAll(/<status>\\s*([a-z_]+)\\s*<\\/status>/gi)].at(-1);\n\tconst status = statusMatch ? statusMatch[1]!.toUpperCase() : undefined;\n\tconst taskState: AgentTaskState = status === \"COMPLETED\" ? \"completed\" : \"needs_input\";\n\treturn { summary, taskState };\n}\n\nexport interface GenerateAgentStatusParams {\n\tregistry: ModelRegistry;\n\tmodel?: Model<Api>;\n\tmessages: readonly AgentMessage[];\n\tisWorking: boolean;\n\tsignal?: AbortSignal;\n\trequests?: InferenceCoordinator;\n}\n\n/** Use the session's selected model, or keep the local status when unavailable. */\nexport async function generateAgentStatus(params: GenerateAgentStatusParams): Promise<AgentStatusResult | undefined> {\n\tconst { registry, messages, isWorking, signal, requests, model: selectedModel } = params;\n\tif (messages.length === 0) {\n\t\treturn undefined;\n\t}\n\tif (!selectedModel) {\n\t\treturn undefined;\n\t}\n\tconst model = { ...selectedModel, cost: { ...selectedModel.cost } };\n\tconst subject = buildStatusContext(messages, isWorking);\n\tconst timestamp = Date.now();\n\tconst auth = await registry.getApiKeyAndHeaders(model);\n\tif (!auth.ok || !auth.apiKey) {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\tconst response = await completeInference(\n\t\t\trequests,\n\t\t\tmodel,\n\t\t\t{\n\t\t\t\tsystemPrompt: AGENT_STATUS_SYSTEM_PROMPT,\n\t\t\t\tmessages: [\n\t\t\t\t\t{\n\t\t\t\t\t\trole: \"user\" as const,\n\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: subject }],\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t\t{ maxTokens: SUMMARY_MAX_TOKENS, apiKey: auth.apiKey, headers: auth.headers, signal },\n\t\t\t{ purpose: \"native-control\", purposeDetail: \"daemon-status\" },\n\t\t);\n\t\tif (response.stopReason === \"error\") {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst textContent = response.content\n\t\t\t.filter((c): c is { type: \"text\"; text: string } => c.type === \"text\")\n\t\t\t.map((c) => c.text)\n\t\t\t.join(\"\\n\");\n\t\treturn parseAgentStatusResponse(textContent, isWorking);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction isSessionWorking(state: ActiveSessionState): boolean {\n\tconst session = state.runtime.session;\n\treturn session.isSessionActive;\n}\n\n/** Use the last assistant turn's real failure instead of asking a classifier to invent completed work. */\nfunction terminalTurnError(messages: readonly AgentMessage[]): string | undefined {\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst message = messages[i];\n\t\tif (message.role !== \"assistant\") continue;\n\t\tif (message.stopReason !== \"error\") return undefined;\n\t\tconst detail = message.errorMessage?.trim();\n\t\treturn detail ? `Model request failed: ${clamp(detail, 160)}` : \"Model request failed\";\n\t}\n\treturn undefined;\n}\n\n/**\n * Background status summarization for daemon-hosted sessions, top-level and\n * subagents alike. A periodic sweep refreshes working sessions; debounced\n * turn-end activity drives the idle verdict. Status lives in memory; settled\n * idle verdicts are persisted.\n */\nexport class DaemonSessionSummarizer {\n\tprivate interval: ReturnType<typeof setInterval> | undefined;\n\tprivate readonly debounceTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\t// Controller per in-flight summary so a closing session can abort its write.\n\tprivate readonly inFlight = new Map<string, AbortController>();\n\tprivate readonly tasks = new Map<string, Promise<void>>();\n\tprivate readonly forgotten = new Set<string>();\n\tprivate stopped = false;\n\t// Sessions requested while one was running; get one more pass on completion.\n\tprivate readonly rerunRequested = new Set<string>();\n\n\tconstructor(\n\t\tprivate readonly listSessions: () => readonly ActiveSessionState[],\n\t\tprivate readonly onStatusChanged?: (state: ActiveSessionState) => void,\n\t\t// Injectable for tests.\n\t\tprivate readonly generate: (\n\t\t\tparams: GenerateAgentStatusParams,\n\t\t) => Promise<AgentStatusResult | undefined> = generateAgentStatus,\n\t\tprivate readonly getRequests?: (session: ActiveSessionState[\"runtime\"][\"session\"]) => InferenceCoordinator,\n\t\tprivate readonly onError: (state: ActiveSessionState, error: unknown) => void = (_state, error) =>\n\t\t\tconsole.error(error),\n\t) {}\n\n\tstart(): void {\n\t\tif (this.interval) {\n\t\t\treturn;\n\t\t}\n\t\tthis.stopped = false;\n\t\tthis.interval = setInterval(() => {\n\t\t\tfor (const state of this.listSessions()) {\n\t\t\t\tthis.summarize(state);\n\t\t\t}\n\t\t}, SWEEP_INTERVAL_MS);\n\t\tthis.interval.unref?.();\n\t}\n\n\tasync stop(): Promise<void> {\n\t\tthis.stopped = true;\n\t\tif (this.interval) {\n\t\t\tclearInterval(this.interval);\n\t\t\tthis.interval = undefined;\n\t\t}\n\t\tfor (const timer of this.debounceTimers.values()) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tthis.debounceTimers.clear();\n\t\tfor (const controller of this.inFlight.values()) {\n\t\t\tcontroller.abort();\n\t\t}\n\t\tthis.rerunRequested.clear();\n\t\tawait Promise.all(this.tasks.values());\n\t}\n\n\t/** Cancel new work and drain any admitted status write before closing. */\n\tasync forget(activeSessionId: string): Promise<void> {\n\t\tthis.forgotten.add(activeSessionId);\n\t\tconst timer = this.debounceTimers.get(activeSessionId);\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.debounceTimers.delete(activeSessionId);\n\t\t}\n\t\tthis.inFlight.get(activeSessionId)?.abort();\n\t\tthis.rerunRequested.delete(activeSessionId);\n\t\tawait this.tasks.get(activeSessionId);\n\t}\n\n\t/** Seed in-memory status from the persisted entry when a session is added. */\n\tseed(state: ActiveSessionState): void {\n\t\tthis.forgotten.delete(state.activeSessionId);\n\t\tif (state.summaryState) {\n\t\t\treturn;\n\t\t}\n\t\tconst persisted = state.runtime.session.sessionManager.getLatestAgentStatus();\n\t\tif (persisted) {\n\t\t\tstate.summaryState = persisted;\n\t\t}\n\t}\n\n\t/** Called when a session finishes a turn; debounce until the agent settles. */\n\tnotifyActivity(state: ActiveSessionState): void {\n\t\tconst id = state.activeSessionId;\n\t\tif (this.stopped || this.forgotten.has(id)) return;\n\t\tconst existing = this.debounceTimers.get(id);\n\t\tif (existing) {\n\t\t\tclearTimeout(existing);\n\t\t}\n\t\tconst timer = setTimeout(() => {\n\t\t\tthis.debounceTimers.delete(id);\n\t\t\tthis.summarize(state);\n\t\t}, SETTLE_DEBOUNCE_MS);\n\t\ttimer.unref?.();\n\t\tthis.debounceTimers.set(id, timer);\n\t}\n\n\tprivate summarize(state: ActiveSessionState): Promise<void> {\n\t\tconst id = state.activeSessionId;\n\t\tif (this.stopped || this.forgotten.has(id)) return Promise.resolve();\n\t\tconst existing = this.tasks.get(id);\n\t\tif (existing) {\n\t\t\tthis.rerunRequested.add(id);\n\t\t\treturn existing;\n\t\t}\n\t\tconst task = this.runSummary(state)\n\t\t\t.catch((error) => this.onError(state, error))\n\t\t\t.finally(() => {\n\t\t\t\tthis.tasks.delete(id);\n\t\t\t\tif (this.rerunRequested.delete(id)) this.notifyActivity(state);\n\t\t\t});\n\t\tthis.tasks.set(id, task);\n\t\treturn task;\n\t}\n\n\tprivate async runSummary(state: ActiveSessionState): Promise<void> {\n\t\tconst id = state.activeSessionId;\n\t\tconst session = state.runtime.session;\n\t\tconst sessionManager = session.sessionManager;\n\t\tconst sessionId = sessionManager.getSessionId();\n\t\tconst sessionFile = sessionManager.getSessionFile();\n\t\tconst messages = session.messages;\n\t\tif (messages.length === 0) {\n\t\t\treturn;\n\t\t}\n\t\tconst messageCount = messages.length;\n\t\tconst isWorking = isSessionWorking(state);\n\t\tconst previous = state.summaryState;\n\t\t// Idle sessions with a current verdict need no refresh unless a terminal\n\t\t// error must replace a previously fabricated verdict. Working sessions\n\t\t// always refresh so the recap keeps up with the in-progress turn.\n\t\tconst contentUnchanged = previous?.basedOnMessageCount === messageCount;\n\t\tconst owesIdleVerdict = !isWorking && previous?.taskState === undefined;\n\t\t// A blank recap means the model call hasn't succeeded yet (e.g. the\n\t\t// needs_input fallback fired on a transient failure); keep retrying until a\n\t\t// real summary lands so the recap isn't left permanently empty.\n\t\tconst owesSummary = !isWorking && !previous?.summary;\n\t\tconst turnError = !isWorking ? terminalTurnError(messages) : undefined;\n\t\tconst owesErrorVerdict =\n\t\t\tturnError !== undefined && (previous?.taskState !== \"needs_input\" || previous?.summary !== turnError);\n\t\tif (contentUnchanged && !isWorking && !owesIdleVerdict && !owesSummary && !owesErrorVerdict) {\n\t\t\treturn;\n\t\t}\n\t\t// Include the in-progress message so a long streaming turn gets a live recap.\n\t\tconst streaming = isWorking ? session.state.streamingMessage : undefined;\n\t\tconst contextMessages = streaming ? [...messages, streaming] : messages;\n\n\t\tconst controller = new AbortController();\n\t\tconst isCurrent = () =>\n\t\t\t!controller.signal.aborted &&\n\t\t\tstate.runtime.session === session &&\n\t\t\tsession.sessionManager === sessionManager &&\n\t\t\tsessionManager.getSessionId() === sessionId &&\n\t\t\tsessionManager.getSessionFile() === sessionFile &&\n\t\t\tisSessionWorking(state) === isWorking &&\n\t\t\tsession.messages.length === messageCount;\n\t\tthis.inFlight.set(id, controller);\n\t\tlet requests: InferenceCoordinator | undefined;\n\t\tlet status: AgentStatus | undefined;\n\t\tlet changed = false;\n\t\ttry {\n\t\t\tif (turnError === undefined) requests = this.getRequests?.(session);\n\t\t\tconst generated: AgentStatusResult | undefined =\n\t\t\t\tturnError !== undefined\n\t\t\t\t\t? { summary: turnError, taskState: \"needs_input\" }\n\t\t\t\t\t: await this.generate({\n\t\t\t\t\t\t\tregistry: session.modelRegistry,\n\t\t\t\t\t\t\tmodel: session.model,\n\t\t\t\t\t\t\tmessages: contextMessages,\n\t\t\t\t\t\t\tisWorking,\n\t\t\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t\t\trequests,\n\t\t\t\t\t\t});\n\t\t\t// An unused capture otherwise counts this idle subject as working.\n\t\t\tawait requests?.dispose();\n\t\t\t// A failed classification on an idle session would spin at \"working\"\n\t\t\t// forever (the activity axis holds unjudged idle sessions there), so\n\t\t\t// settle it to needs_input.\n\t\t\tconst result =\n\t\t\t\tgenerated ??\n\t\t\t\t(!isWorking && (owesIdleVerdict || owesSummary)\n\t\t\t\t\t? { summary: previous?.summary ?? \"\", taskState: \"needs_input\" as const }\n\t\t\t\t\t: undefined);\n\t\t\tif (!result) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Discard if the session closed, was swapped, or moved to a new turn\n\t\t\t// during the async call — never write a verdict for stale state.\n\t\t\tif (!isCurrent()) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// A working refresh carries no verdict; keep the prior one at the same\n\t\t\t// message count so a still-valid needs_input isn't dropped.\n\t\t\tconst taskState =\n\t\t\t\tresult.taskState ?? (previous?.basedOnMessageCount === messageCount ? previous?.taskState : undefined);\n\t\t\tstatus = {\n\t\t\t\tsummary: result.summary,\n\t\t\t\ttaskState,\n\t\t\t\tbasedOnMessageCount: messageCount,\n\t\t\t};\n\t\t\t// An idle settle refreshes the verdict's currency, which drives the roster's\n\t\t\t// activity axis: it must publish even when the verdict text is unchanged.\n\t\t\tchanged =\n\t\t\t\tprevious?.summary !== status.summary ||\n\t\t\t\tprevious?.taskState !== status.taskState ||\n\t\t\t\t(!isWorking && previous?.basedOnMessageCount !== status.basedOnMessageCount);\n\t\t\t// Persist only settled idle verdicts, never mid-stream.\n\t\t\tif (!isWorking) {\n\t\t\t\tawait sessionManager.appendAgentStatus(status);\n\t\t\t\tif (!isCurrent()) return;\n\t\t\t}\n\t\t} finally {\n\t\t\ttry {\n\t\t\t\tawait requests?.dispose();\n\t\t\t} finally {\n\t\t\t\tthis.inFlight.delete(id);\n\t\t\t}\n\t\t}\n\t\tif (status && isCurrent()) {\n\t\t\tstate.summaryState = status;\n\t\t\tif (changed) this.onStatusChanged?.(state);\n\t\t}\n\t}\n}\n"]}
@@ -0,0 +1,15 @@
1
+ export interface DaemonStateRoot {
2
+ agentDir: string;
3
+ socketDir: string;
4
+ defaultSocketPath: string;
5
+ }
6
+ export declare function currentDaemonStateRoot(): DaemonStateRoot;
7
+ /**
8
+ * Limit OS-wide discovery to this product state root. The existing default
9
+ * socket namespace includes the agent directory and package scope. Custom
10
+ * sockets inside the agent directory also cover unregistered predecessors;
11
+ * custom sockets elsewhere must be named by this root's ownership registry.
12
+ * Each sweep gets a fresh matcher and a lazy, read-only registry snapshot.
13
+ */
14
+ export declare function createDaemonStateRootMatcher(root?: DaemonStateRoot): (socketPath: string) => boolean;
15
+ //# sourceMappingURL=daemon-state-root.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-state-root.d.ts","sourceRoot":"","sources":["../../../src/modes/daemon/daemon-state-root.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,sBAAsB,IAAI,eAAe,CAMxD;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC3C,IAAI,GAAE,eAA0C,GAC9C,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAmBjC","sourcesContent":["import { dirname, isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { getAgentDir } from \"../../config.js\";\nimport { defaultDaemonSocketDir, defaultDaemonSocketPath, normalizeSocketPath } from \"./daemon-socket.js\";\nimport { listDaemonSupervisorSocketPathsForAgentDir } from \"./daemon-supervisor-ownership.js\";\n\nexport interface DaemonStateRoot {\n\tagentDir: string;\n\tsocketDir: string;\n\tdefaultSocketPath: string;\n}\n\nexport function currentDaemonStateRoot(): DaemonStateRoot {\n\treturn {\n\t\tagentDir: getAgentDir(),\n\t\tsocketDir: defaultDaemonSocketDir(),\n\t\tdefaultSocketPath: normalizeSocketPath(defaultDaemonSocketPath()),\n\t};\n}\n\n/**\n * Limit OS-wide discovery to this product state root. The existing default\n * socket namespace includes the agent directory and package scope. Custom\n * sockets inside the agent directory also cover unregistered predecessors;\n * custom sockets elsewhere must be named by this root's ownership registry.\n * Each sweep gets a fresh matcher and a lazy, read-only registry snapshot.\n */\nexport function createDaemonStateRootMatcher(\n\troot: DaemonStateRoot = currentDaemonStateRoot(),\n): (socketPath: string) => boolean {\n\tconst socketDir = resolve(root.socketDir);\n\tconst agentDir = resolve(root.agentDir);\n\tconst defaultSocketPath = normalizeSocketPath(root.defaultSocketPath);\n\tlet registeredSocketPaths: Set<string> | undefined;\n\treturn (socketPath: string): boolean => {\n\t\tconst normalized = normalizeSocketPath(socketPath);\n\t\tif (normalized === defaultSocketPath) return true;\n\t\t// Named pipes have no filesystem parent that identifies a state root.\n\t\tif (process.platform !== \"win32\") {\n\t\t\tconst directory = resolve(dirname(normalized));\n\t\t\tconst offset = relative(agentDir, directory);\n\t\t\tif (directory === socketDir || (!isAbsolute(offset) && offset !== \"..\" && !offset.startsWith(`..${sep}`))) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\tregisteredSocketPaths ??= new Set(listDaemonSupervisorSocketPathsForAgentDir(root.agentDir));\n\t\treturn registeredSocketPaths.has(normalized);\n\t};\n}\n"]}
@@ -0,0 +1,40 @@
1
+ import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
2
+ import { getAgentDir } from "../../config.js";
3
+ import { defaultDaemonSocketDir, defaultDaemonSocketPath, normalizeSocketPath } from "./daemon-socket.js";
4
+ import { listDaemonSupervisorSocketPathsForAgentDir } from "./daemon-supervisor-ownership.js";
5
+ export function currentDaemonStateRoot() {
6
+ return {
7
+ agentDir: getAgentDir(),
8
+ socketDir: defaultDaemonSocketDir(),
9
+ defaultSocketPath: normalizeSocketPath(defaultDaemonSocketPath()),
10
+ };
11
+ }
12
+ /**
13
+ * Limit OS-wide discovery to this product state root. The existing default
14
+ * socket namespace includes the agent directory and package scope. Custom
15
+ * sockets inside the agent directory also cover unregistered predecessors;
16
+ * custom sockets elsewhere must be named by this root's ownership registry.
17
+ * Each sweep gets a fresh matcher and a lazy, read-only registry snapshot.
18
+ */
19
+ export function createDaemonStateRootMatcher(root = currentDaemonStateRoot()) {
20
+ const socketDir = resolve(root.socketDir);
21
+ const agentDir = resolve(root.agentDir);
22
+ const defaultSocketPath = normalizeSocketPath(root.defaultSocketPath);
23
+ let registeredSocketPaths;
24
+ return (socketPath) => {
25
+ const normalized = normalizeSocketPath(socketPath);
26
+ if (normalized === defaultSocketPath)
27
+ return true;
28
+ // Named pipes have no filesystem parent that identifies a state root.
29
+ if (process.platform !== "win32") {
30
+ const directory = resolve(dirname(normalized));
31
+ const offset = relative(agentDir, directory);
32
+ if (directory === socketDir || (!isAbsolute(offset) && offset !== ".." && !offset.startsWith(`..${sep}`))) {
33
+ return true;
34
+ }
35
+ }
36
+ registeredSocketPaths ??= new Set(listDaemonSupervisorSocketPathsForAgentDir(root.agentDir));
37
+ return registeredSocketPaths.has(normalized);
38
+ };
39
+ }
40
+ //# sourceMappingURL=daemon-state-root.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-state-root.js","sourceRoot":"","sources":["../../../src/modes/daemon/daemon-state-root.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC1G,OAAO,EAAE,0CAA0C,EAAE,MAAM,kCAAkC,CAAC;AAQ9F,MAAM,UAAU,sBAAsB,GAAoB;IACzD,OAAO;QACN,QAAQ,EAAE,WAAW,EAAE;QACvB,SAAS,EAAE,sBAAsB,EAAE;QACnC,iBAAiB,EAAE,mBAAmB,CAAC,uBAAuB,EAAE,CAAC;KACjE,CAAC;AAAA,CACF;AAED;;;;;;GAMG;AACH,MAAM,UAAU,4BAA4B,CAC3C,IAAI,GAAoB,sBAAsB,EAAE,EACd;IAClC,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxC,MAAM,iBAAiB,GAAG,mBAAmB,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACtE,IAAI,qBAA8C,CAAC;IACnD,OAAO,CAAC,UAAkB,EAAW,EAAE,CAAC;QACvC,MAAM,UAAU,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;QACnD,IAAI,UAAU,KAAK,iBAAiB;YAAE,OAAO,IAAI,CAAC;QAClD,sEAAsE;QACtE,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YAClC,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;YAC/C,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;YAC7C,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;gBAC3G,OAAO,IAAI,CAAC;YACb,CAAC;QACF,CAAC;QACD,qBAAqB,KAAK,IAAI,GAAG,CAAC,0CAA0C,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC7F,OAAO,qBAAqB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAAA,CAC7C,CAAC;AAAA,CACF","sourcesContent":["import { dirname, isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { getAgentDir } from \"../../config.js\";\nimport { defaultDaemonSocketDir, defaultDaemonSocketPath, normalizeSocketPath } from \"./daemon-socket.js\";\nimport { listDaemonSupervisorSocketPathsForAgentDir } from \"./daemon-supervisor-ownership.js\";\n\nexport interface DaemonStateRoot {\n\tagentDir: string;\n\tsocketDir: string;\n\tdefaultSocketPath: string;\n}\n\nexport function currentDaemonStateRoot(): DaemonStateRoot {\n\treturn {\n\t\tagentDir: getAgentDir(),\n\t\tsocketDir: defaultDaemonSocketDir(),\n\t\tdefaultSocketPath: normalizeSocketPath(defaultDaemonSocketPath()),\n\t};\n}\n\n/**\n * Limit OS-wide discovery to this product state root. The existing default\n * socket namespace includes the agent directory and package scope. Custom\n * sockets inside the agent directory also cover unregistered predecessors;\n * custom sockets elsewhere must be named by this root's ownership registry.\n * Each sweep gets a fresh matcher and a lazy, read-only registry snapshot.\n */\nexport function createDaemonStateRootMatcher(\n\troot: DaemonStateRoot = currentDaemonStateRoot(),\n): (socketPath: string) => boolean {\n\tconst socketDir = resolve(root.socketDir);\n\tconst agentDir = resolve(root.agentDir);\n\tconst defaultSocketPath = normalizeSocketPath(root.defaultSocketPath);\n\tlet registeredSocketPaths: Set<string> | undefined;\n\treturn (socketPath: string): boolean => {\n\t\tconst normalized = normalizeSocketPath(socketPath);\n\t\tif (normalized === defaultSocketPath) return true;\n\t\t// Named pipes have no filesystem parent that identifies a state root.\n\t\tif (process.platform !== \"win32\") {\n\t\t\tconst directory = resolve(dirname(normalized));\n\t\t\tconst offset = relative(agentDir, directory);\n\t\t\tif (directory === socketDir || (!isAbsolute(offset) && offset !== \"..\" && !offset.startsWith(`..${sep}`))) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\tregisteredSocketPaths ??= new Set(listDaemonSupervisorSocketPathsForAgentDir(root.agentDir));\n\t\treturn registeredSocketPaths.has(normalized);\n\t};\n}\n"]}
@@ -64,6 +64,8 @@ declare class DaemonShutdownAdmission {
64
64
  release(): Promise<void>;
65
65
  }
66
66
  export declare function acquireDaemonSupervisorOwnership(options: AcquireDaemonSupervisorOwnershipOptions): Promise<DaemonSupervisorOwnership>;
67
+ /** Read only: discovery must not acquire ownership or reclaim registry directories. */
68
+ export declare function listDaemonSupervisorSocketPathsForAgentDir(agentDir: string, registryDir?: string): string[];
67
69
  export declare function assertDaemonSupervisorOwnerCurrent(owner: {
68
70
  generation: string;
69
71
  pid: number;