@cluesmith/codev 3.3.5 → 3.3.7

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 (247) hide show
  1. package/dashboard-dist/assets/{index-r3bZzLmX.css → index-BjcqV9Vv.css} +1 -1
  2. package/dashboard-dist/assets/{index-BuflRUi6.js → index-b7GnZV2i.js} +55 -55
  3. package/dashboard-dist/assets/index-b7GnZV2i.js.map +1 -0
  4. package/dashboard-dist/index.html +2 -2
  5. package/dist/agent-farm/cli.d.ts.map +1 -1
  6. package/dist/agent-farm/cli.js +50 -17
  7. package/dist/agent-farm/cli.js.map +1 -1
  8. package/dist/agent-farm/commands/architect.d.ts.map +1 -1
  9. package/dist/agent-farm/commands/architect.js +5 -1
  10. package/dist/agent-farm/commands/architect.js.map +1 -1
  11. package/dist/agent-farm/commands/attach.d.ts +1 -1
  12. package/dist/agent-farm/commands/attach.d.ts.map +1 -1
  13. package/dist/agent-farm/commands/cleanup.d.ts +1 -1
  14. package/dist/agent-farm/commands/cleanup.d.ts.map +1 -1
  15. package/dist/agent-farm/commands/cleanup.js +2 -1
  16. package/dist/agent-farm/commands/cleanup.js.map +1 -1
  17. package/dist/agent-farm/commands/decisions.d.ts +25 -0
  18. package/dist/agent-farm/commands/decisions.d.ts.map +1 -0
  19. package/dist/agent-farm/commands/decisions.js +54 -0
  20. package/dist/agent-farm/commands/decisions.js.map +1 -0
  21. package/dist/agent-farm/commands/ide.d.ts +32 -3
  22. package/dist/agent-farm/commands/ide.d.ts.map +1 -1
  23. package/dist/agent-farm/commands/ide.js +180 -19
  24. package/dist/agent-farm/commands/ide.js.map +1 -1
  25. package/dist/agent-farm/commands/reset/context.d.ts +4 -2
  26. package/dist/agent-farm/commands/reset/context.d.ts.map +1 -1
  27. package/dist/agent-farm/commands/reset/context.js +12 -4
  28. package/dist/agent-farm/commands/reset/context.js.map +1 -1
  29. package/dist/agent-farm/commands/send.d.ts.map +1 -1
  30. package/dist/agent-farm/commands/send.js +13 -18
  31. package/dist/agent-farm/commands/send.js.map +1 -1
  32. package/dist/agent-farm/commands/spawn-worktree.d.ts +7 -0
  33. package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
  34. package/dist/agent-farm/commands/spawn-worktree.js +51 -18
  35. package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
  36. package/dist/agent-farm/commands/spawn.d.ts +17 -0
  37. package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
  38. package/dist/agent-farm/commands/spawn.js +41 -19
  39. package/dist/agent-farm/commands/spawn.js.map +1 -1
  40. package/dist/agent-farm/lib/builder-lookup.d.ts +3 -2
  41. package/dist/agent-farm/lib/builder-lookup.d.ts.map +1 -1
  42. package/dist/agent-farm/lib/builder-lookup.js +24 -9
  43. package/dist/agent-farm/lib/builder-lookup.js.map +1 -1
  44. package/dist/agent-farm/lib/ide-extensions.d.ts +82 -0
  45. package/dist/agent-farm/lib/ide-extensions.d.ts.map +1 -0
  46. package/dist/agent-farm/lib/ide-extensions.js +179 -0
  47. package/dist/agent-farm/lib/ide-extensions.js.map +1 -0
  48. package/dist/agent-farm/lib/ide-prefix.d.ts +10 -0
  49. package/dist/agent-farm/lib/ide-prefix.d.ts.map +1 -0
  50. package/dist/agent-farm/lib/ide-prefix.js +13 -0
  51. package/dist/agent-farm/lib/ide-prefix.js.map +1 -0
  52. package/dist/agent-farm/lib/ide-record.d.ts +20 -3
  53. package/dist/agent-farm/lib/ide-record.d.ts.map +1 -1
  54. package/dist/agent-farm/lib/ide-record.js +34 -3
  55. package/dist/agent-farm/lib/ide-record.js.map +1 -1
  56. package/dist/agent-farm/lib/ide-version.d.ts +97 -0
  57. package/dist/agent-farm/lib/ide-version.d.ts.map +1 -0
  58. package/dist/agent-farm/lib/ide-version.js +224 -0
  59. package/dist/agent-farm/lib/ide-version.js.map +1 -0
  60. package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
  61. package/dist/agent-farm/lib/tunnel-client.js +7 -3
  62. package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
  63. package/dist/agent-farm/servers/agent-state/broadcast.d.ts +19 -0
  64. package/dist/agent-farm/servers/agent-state/broadcast.d.ts.map +1 -0
  65. package/dist/agent-farm/servers/agent-state/broadcast.js +30 -0
  66. package/dist/agent-farm/servers/agent-state/broadcast.js.map +1 -0
  67. package/dist/agent-farm/servers/agent-state/identity.d.ts +50 -0
  68. package/dist/agent-farm/servers/agent-state/identity.d.ts.map +1 -0
  69. package/dist/agent-farm/servers/agent-state/identity.js +120 -0
  70. package/dist/agent-farm/servers/agent-state/identity.js.map +1 -0
  71. package/dist/agent-farm/servers/agent-state/liveness.d.ts +107 -0
  72. package/dist/agent-farm/servers/agent-state/liveness.d.ts.map +1 -0
  73. package/dist/agent-farm/servers/agent-state/liveness.js +224 -0
  74. package/dist/agent-farm/servers/agent-state/liveness.js.map +1 -0
  75. package/dist/agent-farm/servers/agent-state/route.d.ts +38 -0
  76. package/dist/agent-farm/servers/agent-state/route.d.ts.map +1 -0
  77. package/dist/agent-farm/servers/agent-state/route.js +170 -0
  78. package/dist/agent-farm/servers/agent-state/route.js.map +1 -0
  79. package/dist/agent-farm/servers/agent-state/serve.d.ts +31 -0
  80. package/dist/agent-farm/servers/agent-state/serve.d.ts.map +1 -0
  81. package/dist/agent-farm/servers/agent-state/serve.js +49 -0
  82. package/dist/agent-farm/servers/agent-state/serve.js.map +1 -0
  83. package/dist/agent-farm/servers/agent-state/store.d.ts +76 -0
  84. package/dist/agent-farm/servers/agent-state/store.d.ts.map +1 -0
  85. package/dist/agent-farm/servers/agent-state/store.js +157 -0
  86. package/dist/agent-farm/servers/agent-state/store.js.map +1 -0
  87. package/dist/agent-farm/servers/decisions/parse.d.ts.map +1 -1
  88. package/dist/agent-farm/servers/decisions/parse.js +45 -15
  89. package/dist/agent-farm/servers/decisions/parse.js.map +1 -1
  90. package/dist/agent-farm/servers/decisions/stamp.d.ts +6 -2
  91. package/dist/agent-farm/servers/decisions/stamp.d.ts.map +1 -1
  92. package/dist/agent-farm/servers/decisions/stamp.js +53 -29
  93. package/dist/agent-farm/servers/decisions/stamp.js.map +1 -1
  94. package/dist/agent-farm/servers/decisions/template.d.ts +2 -0
  95. package/dist/agent-farm/servers/decisions/template.d.ts.map +1 -0
  96. package/dist/agent-farm/servers/decisions/template.js +15 -0
  97. package/dist/agent-farm/servers/decisions/template.js.map +1 -0
  98. package/dist/agent-farm/servers/ide-forward.d.ts +57 -5
  99. package/dist/agent-farm/servers/ide-forward.d.ts.map +1 -1
  100. package/dist/agent-farm/servers/ide-forward.js +263 -31
  101. package/dist/agent-farm/servers/ide-forward.js.map +1 -1
  102. package/dist/agent-farm/servers/ide-server.d.ts +28 -8
  103. package/dist/agent-farm/servers/ide-server.d.ts.map +1 -1
  104. package/dist/agent-farm/servers/ide-server.js +151 -52
  105. package/dist/agent-farm/servers/ide-server.js.map +1 -1
  106. package/dist/agent-farm/servers/overview.d.ts +27 -1
  107. package/dist/agent-farm/servers/overview.d.ts.map +1 -1
  108. package/dist/agent-farm/servers/overview.js +184 -31
  109. package/dist/agent-farm/servers/overview.js.map +1 -1
  110. package/dist/agent-farm/servers/tower-instances.d.ts +14 -1
  111. package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
  112. package/dist/agent-farm/servers/tower-instances.js +51 -9
  113. package/dist/agent-farm/servers/tower-instances.js.map +1 -1
  114. package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
  115. package/dist/agent-farm/servers/tower-routes.js +149 -15
  116. package/dist/agent-farm/servers/tower-routes.js.map +1 -1
  117. package/dist/agent-farm/servers/tower-server.js +35 -1
  118. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  119. package/dist/agent-farm/servers/tower-terminals.d.ts +25 -0
  120. package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
  121. package/dist/agent-farm/servers/tower-terminals.js +128 -4
  122. package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
  123. package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
  124. package/dist/agent-farm/servers/tower-utils.js +18 -5
  125. package/dist/agent-farm/servers/tower-utils.js.map +1 -1
  126. package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -1
  127. package/dist/agent-farm/servers/tower-websocket.js +11 -7
  128. package/dist/agent-farm/servers/tower-websocket.js.map +1 -1
  129. package/dist/agent-farm/utils/builder-worktree-identity.d.ts +39 -0
  130. package/dist/agent-farm/utils/builder-worktree-identity.d.ts.map +1 -0
  131. package/dist/agent-farm/utils/builder-worktree-identity.js +42 -0
  132. package/dist/agent-farm/utils/builder-worktree-identity.js.map +1 -0
  133. package/dist/agent-farm/utils/harness.d.ts +71 -0
  134. package/dist/agent-farm/utils/harness.d.ts.map +1 -1
  135. package/dist/agent-farm/utils/harness.js +153 -0
  136. package/dist/agent-farm/utils/harness.js.map +1 -1
  137. package/dist/agent-farm/utils/lifecycle-reporter.d.ts +79 -0
  138. package/dist/agent-farm/utils/lifecycle-reporter.d.ts.map +1 -0
  139. package/dist/agent-farm/utils/lifecycle-reporter.js +347 -0
  140. package/dist/agent-farm/utils/lifecycle-reporter.js.map +1 -0
  141. package/dist/agent-farm/utils/server-utils.d.ts +54 -19
  142. package/dist/agent-farm/utils/server-utils.d.ts.map +1 -1
  143. package/dist/agent-farm/utils/server-utils.js +144 -27
  144. package/dist/agent-farm/utils/server-utils.js.map +1 -1
  145. package/dist/cli.d.ts.map +1 -1
  146. package/dist/cli.js +17 -0
  147. package/dist/cli.js.map +1 -1
  148. package/dist/commands/adopt.d.ts +16 -2
  149. package/dist/commands/adopt.d.ts.map +1 -1
  150. package/dist/commands/adopt.js +67 -23
  151. package/dist/commands/adopt.js.map +1 -1
  152. package/dist/commands/bootstrap-docs.d.ts +132 -0
  153. package/dist/commands/bootstrap-docs.d.ts.map +1 -0
  154. package/dist/commands/bootstrap-docs.js +725 -0
  155. package/dist/commands/bootstrap-docs.js.map +1 -0
  156. package/dist/commands/consult/index.d.ts.map +1 -1
  157. package/dist/commands/consult/index.js +14 -1
  158. package/dist/commands/consult/index.js.map +1 -1
  159. package/dist/commands/consult/tree-tripwire.d.ts +2 -75
  160. package/dist/commands/consult/tree-tripwire.d.ts.map +1 -1
  161. package/dist/commands/consult/tree-tripwire.js +7 -184
  162. package/dist/commands/consult/tree-tripwire.js.map +1 -1
  163. package/dist/commands/doctor.d.ts.map +1 -1
  164. package/dist/commands/doctor.js +36 -4
  165. package/dist/commands/doctor.js.map +1 -1
  166. package/dist/commands/porch/state.d.ts +0 -4
  167. package/dist/commands/porch/state.d.ts.map +1 -1
  168. package/dist/commands/porch/state.js +23 -10
  169. package/dist/commands/porch/state.js.map +1 -1
  170. package/dist/commands/update.d.ts +5 -1
  171. package/dist/commands/update.d.ts.map +1 -1
  172. package/dist/commands/update.js +73 -18
  173. package/dist/commands/update.js.map +1 -1
  174. package/dist/lib/agent-env.d.ts +22 -1
  175. package/dist/lib/agent-env.d.ts.map +1 -1
  176. package/dist/lib/agent-env.js +27 -1
  177. package/dist/lib/agent-env.js.map +1 -1
  178. package/dist/lib/agent-process.d.ts +23 -0
  179. package/dist/lib/agent-process.d.ts.map +1 -0
  180. package/dist/lib/agent-process.js +24 -0
  181. package/dist/lib/agent-process.js.map +1 -0
  182. package/dist/lib/forge-contracts.d.ts +9 -3
  183. package/dist/lib/forge-contracts.d.ts.map +1 -1
  184. package/dist/lib/forge-contracts.js.map +1 -1
  185. package/dist/lib/forge.d.ts +8 -5
  186. package/dist/lib/forge.d.ts.map +1 -1
  187. package/dist/lib/forge.js +57 -1
  188. package/dist/lib/forge.js.map +1 -1
  189. package/dist/lib/github.d.ts.map +1 -1
  190. package/dist/lib/github.js +38 -0
  191. package/dist/lib/github.js.map +1 -1
  192. package/dist/lib/governance-bootstrap.d.ts +155 -0
  193. package/dist/lib/governance-bootstrap.d.ts.map +1 -0
  194. package/dist/lib/governance-bootstrap.js +469 -0
  195. package/dist/lib/governance-bootstrap.js.map +1 -0
  196. package/dist/lib/issue-id.d.ts +34 -0
  197. package/dist/lib/issue-id.d.ts.map +1 -0
  198. package/dist/lib/issue-id.js +61 -0
  199. package/dist/lib/issue-id.js.map +1 -0
  200. package/dist/lib/scaffold.d.ts +4 -0
  201. package/dist/lib/scaffold.d.ts.map +1 -1
  202. package/dist/lib/scaffold.js +29 -5
  203. package/dist/lib/scaffold.js.map +1 -1
  204. package/dist/lib/shipped-skill-hashes.d.ts +10 -0
  205. package/dist/lib/shipped-skill-hashes.d.ts.map +1 -0
  206. package/dist/lib/shipped-skill-hashes.js +197 -0
  207. package/dist/lib/shipped-skill-hashes.js.map +1 -0
  208. package/dist/lib/skill-refresh.d.ts +90 -0
  209. package/dist/lib/skill-refresh.d.ts.map +1 -0
  210. package/dist/lib/skill-refresh.js +267 -0
  211. package/dist/lib/skill-refresh.js.map +1 -0
  212. package/dist/lib/tree-snapshot.d.ts +172 -0
  213. package/dist/lib/tree-snapshot.d.ts.map +1 -0
  214. package/dist/lib/tree-snapshot.js +393 -0
  215. package/dist/lib/tree-snapshot.js.map +1 -0
  216. package/dist/terminal/pty-session.d.ts +5 -0
  217. package/dist/terminal/pty-session.d.ts.map +1 -1
  218. package/dist/terminal/pty-session.js +7 -0
  219. package/dist/terminal/pty-session.js.map +1 -1
  220. package/ide-extension/codev-vscode.vsix +0 -0
  221. package/ide-extension/manifest.json +7 -0
  222. package/package.json +8 -6
  223. package/scripts/forge/jira/_lib.sh +113 -0
  224. package/scripts/forge/jira/auth-status.sh +29 -0
  225. package/scripts/forge/jira/issue-comment.sh +23 -0
  226. package/scripts/forge/jira/issue-list.sh +32 -0
  227. package/scripts/forge/jira/issue-search.sh +40 -0
  228. package/scripts/forge/jira/issue-view.sh +41 -0
  229. package/scripts/forge/jira/recently-closed.sh +81 -0
  230. package/scripts/forge/jira/user-identity.sh +18 -0
  231. package/skeleton/.claude/skills/afx/SKILL.md +36 -0
  232. package/skeleton/.claude/skills/codev/SKILL.md +18 -2
  233. package/skeleton/.claude/skills/update-arch-docs/SKILL.md +28 -2
  234. package/skeleton/.codex/skills/afx/SKILL.md +36 -0
  235. package/skeleton/.codex/skills/codev/SKILL.md +18 -2
  236. package/skeleton/.codex/skills/update-arch-docs/SKILL.md +28 -2
  237. package/skeleton/resources/commands/agent-farm.md +139 -0
  238. package/skeleton/resources/commands/codev.md +47 -0
  239. package/skeleton/resources/commands/overview.md +1 -0
  240. package/skeleton/resources/decision-protocol.md +215 -0
  241. package/skeleton/resources/workflow-reference.md +12 -0
  242. package/skeleton/roles/architect.md +6 -0
  243. package/skeleton/templates/bootstrap-docs-prompt.md +16 -0
  244. package/skeleton/templates/bootstrap-docs-review-prompt.md +19 -0
  245. package/skeleton/templates/cheatsheet.md +1 -0
  246. package/templates/tower.html +78 -3
  247. package/dashboard-dist/assets/index-BuflRUi6.js.map +0 -1
@@ -0,0 +1,170 @@
1
+ /**
2
+ * `POST /api/agent-lifecycle`: the one write-only agent-state route (Spec 1595,
3
+ * Constraint 16).
4
+ *
5
+ * An agent's lifecycle reporter posts one `AgentLifecycleEvent` per harness hook.
6
+ * The route validates it strictly against the contract and its limits, resolves
7
+ * the launch identity to the agent Tower knows, applies it to the in-memory
8
+ * store, and answers an empty 204. It writes nothing else: no PTY, no mailbox,
9
+ * no database. There is no read route; state is served on the overview.
10
+ *
11
+ * Auth is the existing `isRequestAllowed` choke point in `handleRequest`; this
12
+ * route is not on the public allowlist. A rejection is a 4xx carrying only an
13
+ * error code. The reporter ignores every response.
14
+ */
15
+ import { isAbsolute } from 'node:path';
16
+ import { readBody } from '../../utils/server-utils.js';
17
+ /** Request body cap, in bytes. */
18
+ export const MAX_LIFECYCLE_BODY_BYTES = 8192;
19
+ export const MAX_PATH_LENGTH = 4096;
20
+ export const MAX_NAME_LENGTH = 128;
21
+ export const MAX_SESSION_ID_LENGTH = 128;
22
+ export const MAX_TOOL_LENGTH = 64;
23
+ export const MAX_SUMMARY_LENGTH = 200;
24
+ const EVENT_NAMES = new Set([
25
+ 'session-started',
26
+ 'turn-started',
27
+ 'tool-activity',
28
+ 'needs-input',
29
+ 'turn-ended',
30
+ ]);
31
+ const NEEDS_INPUT_KINDS = new Set(['permission', 'question', 'other']);
32
+ const EVENT_KEYS = new Set(['agent', 'sessionId', 'event', 'at', 'needsInput', 'backgroundWork']);
33
+ const BUILDER_KEYS = new Set(['kind', 'worktreePath']);
34
+ const ARCHITECT_KEYS = new Set(['kind', 'workspacePath', 'name']);
35
+ const DETAIL_KEYS = new Set(['kind', 'tool', 'summary']);
36
+ // eslint-disable-next-line no-control-regex
37
+ const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/;
38
+ function isRecord(value) {
39
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
40
+ }
41
+ function hasOnlyKeys(value, allowed) {
42
+ return Object.keys(value).every(key => allowed.has(key));
43
+ }
44
+ function isBoundedString(value, maxLength) {
45
+ return typeof value === 'string' && value.length > 0 && value.length <= maxLength;
46
+ }
47
+ function isPlainText(value, maxLength) {
48
+ return isBoundedString(value, maxLength) && !CONTROL_CHARACTERS.test(value);
49
+ }
50
+ function isAbsolutePath(value) {
51
+ return isPlainText(value, MAX_PATH_LENGTH) && isAbsolute(value);
52
+ }
53
+ function parseIdentity(value) {
54
+ if (!isRecord(value))
55
+ return null;
56
+ if (value.kind === 'builder') {
57
+ if (!hasOnlyKeys(value, BUILDER_KEYS) || !isAbsolutePath(value.worktreePath))
58
+ return null;
59
+ return { kind: 'builder', worktreePath: value.worktreePath };
60
+ }
61
+ if (value.kind === 'architect') {
62
+ if (!hasOnlyKeys(value, ARCHITECT_KEYS) || !isAbsolutePath(value.workspacePath))
63
+ return null;
64
+ if (!isPlainText(value.name, MAX_NAME_LENGTH))
65
+ return null;
66
+ return { kind: 'architect', workspacePath: value.workspacePath, name: value.name };
67
+ }
68
+ return null;
69
+ }
70
+ function parseDetail(value) {
71
+ if (!isRecord(value) || !hasOnlyKeys(value, DETAIL_KEYS))
72
+ return null;
73
+ if (typeof value.kind !== 'string' || !NEEDS_INPUT_KINDS.has(value.kind))
74
+ return null;
75
+ const detail = { kind: value.kind };
76
+ if (value.tool !== undefined) {
77
+ if (!isPlainText(value.tool, MAX_TOOL_LENGTH))
78
+ return null;
79
+ detail.tool = value.tool;
80
+ }
81
+ if (value.summary !== undefined) {
82
+ if (!isPlainText(value.summary, MAX_SUMMARY_LENGTH))
83
+ return null;
84
+ detail.summary = value.summary;
85
+ }
86
+ return detail;
87
+ }
88
+ /**
89
+ * Validate a parsed body against the `AgentLifecycleEvent` contract, or `null`.
90
+ * Strict: unknown keys anywhere are rejected, `needsInput` is accepted only on a
91
+ * `needs-input` event and `backgroundWork` only on `turn-ended`.
92
+ */
93
+ export function parseLifecycleEvent(value) {
94
+ if (!isRecord(value) || !hasOnlyKeys(value, EVENT_KEYS))
95
+ return null;
96
+ const agent = parseIdentity(value.agent);
97
+ if (agent === null)
98
+ return null;
99
+ if (!isPlainText(value.sessionId, MAX_SESSION_ID_LENGTH))
100
+ return null;
101
+ if (typeof value.event !== 'string' || !EVENT_NAMES.has(value.event))
102
+ return null;
103
+ if (typeof value.at !== 'number' || !Number.isFinite(value.at) || value.at <= 0)
104
+ return null;
105
+ const event = {
106
+ agent,
107
+ sessionId: value.sessionId,
108
+ event: value.event,
109
+ at: value.at,
110
+ };
111
+ if (value.needsInput !== undefined) {
112
+ if (event.event !== 'needs-input')
113
+ return null;
114
+ const detail = parseDetail(value.needsInput);
115
+ if (detail === null)
116
+ return null;
117
+ event.needsInput = detail;
118
+ }
119
+ if (value.backgroundWork !== undefined) {
120
+ if (event.event !== 'turn-ended' || typeof value.backgroundWork !== 'boolean')
121
+ return null;
122
+ event.backgroundWork = value.backgroundWork;
123
+ }
124
+ return event;
125
+ }
126
+ function reject(res, status, code) {
127
+ res.writeHead(status, { 'Content-Type': 'application/json' });
128
+ res.end(JSON.stringify({ error: code }));
129
+ }
130
+ /** Handle one lifecycle report. Auth has already passed in `handleRequest`. */
131
+ export async function handleAgentLifecycle(req, res, deps) {
132
+ let raw;
133
+ try {
134
+ raw = await readBody(req, MAX_LIFECYCLE_BODY_BYTES);
135
+ }
136
+ catch (err) {
137
+ if (err instanceof Error && err.message === 'Request body too large') {
138
+ reject(res, 413, 'BODY_TOO_LARGE');
139
+ }
140
+ else {
141
+ reject(res, 400, 'UNREADABLE_BODY');
142
+ }
143
+ return;
144
+ }
145
+ let parsed;
146
+ try {
147
+ parsed = JSON.parse(raw);
148
+ }
149
+ catch {
150
+ reject(res, 400, 'INVALID_JSON');
151
+ return;
152
+ }
153
+ const event = parseLifecycleEvent(parsed);
154
+ if (event === null) {
155
+ reject(res, 400, 'INVALID_EVENT');
156
+ return;
157
+ }
158
+ const agent = deps.resolve(event.agent);
159
+ if (agent === null) {
160
+ reject(res, 404, 'UNKNOWN_AGENT');
161
+ return;
162
+ }
163
+ if (deps.store.apply(agent.workspacePath, agent.agentId, event) === 'future') {
164
+ reject(res, 400, 'AT_IN_FUTURE');
165
+ return;
166
+ }
167
+ res.writeHead(204);
168
+ res.end();
169
+ }
170
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.js","sourceRoot":"","sources":["../../../../src/agent-farm/servers/agent-state/route.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Serve an agent's attention state on the overview (Spec 1595).
3
+ *
4
+ * Gathers Tower's facts about one agent (porch's `blocked`, the caller already
5
+ * holds; the published liveness; the stored reported activity) and hands them to
6
+ * the one projection policy in codev-core. Called per request: nothing here is
7
+ * cached, so a served row is never older than the store and the liveness sweep.
8
+ */
9
+ import type { ServedAgentState } from '@cluesmith/codev-types';
10
+ import { type AgentStateStore } from './store.js';
11
+ import { type AgentLiveness } from './liveness.js';
12
+ /** The porch facts the overview already computed for the agent. */
13
+ export interface AgentPorchFacts {
14
+ /** Whether porch reports a requested, pending gate (`detectBlocked` is non-null). */
15
+ blocked: boolean;
16
+ blockedSince: string | null;
17
+ /** The builder's display phase; `''` for an architect. */
18
+ phase: string;
19
+ }
20
+ export interface ServeAgentStateOptions {
21
+ store?: AgentStateStore;
22
+ liveness?: (workspacePath: string, agentId: string) => AgentLiveness | undefined;
23
+ now?: () => number;
24
+ }
25
+ /**
26
+ * The served state for the agent `(workspacePath, agentId)`, keyed as the store
27
+ * and liveness key it: the normalized workspace path and the canonical agent id
28
+ * (a builder's lowercased role id, an architect's name). `null` when unknown.
29
+ */
30
+ export declare function serveAgentState(workspacePath: string, agentId: string, porch: AgentPorchFacts, options?: ServeAgentStateOptions): ServedAgentState | null;
31
+ //# sourceMappingURL=serve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../../../src/agent-farm/servers/agent-state/serve.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAM/D,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AACnE,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAErE,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC9B,qFAAqF;IACrF,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,QAAQ,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,CAAC;IACjF,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAoBD;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,eAAe,EACtB,OAAO,GAAE,sBAA2B,GACnC,gBAAgB,GAAG,IAAI,CAYzB"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Serve an agent's attention state on the overview (Spec 1595).
3
+ *
4
+ * Gathers Tower's facts about one agent (porch's `blocked`, the caller already
5
+ * holds; the published liveness; the stored reported activity) and hands them to
6
+ * the one projection policy in codev-core. Called per request: nothing here is
7
+ * cached, so a served row is never older than the store and the liveness sweep.
8
+ */
9
+ import { projectAgentState, } from '@cluesmith/codev-core/agent-state';
10
+ import { agentStateStore } from './store.js';
11
+ import { getAgentLiveness } from './liveness.js';
12
+ function livenessFact(liveness) {
13
+ if (liveness === undefined)
14
+ return null;
15
+ return { alive: liveness.alive, since: new Date(liveness.since).toISOString() };
16
+ }
17
+ function reportedFact(store, workspacePath, agentId) {
18
+ const stored = store.get(workspacePath, agentId);
19
+ if (stored === undefined)
20
+ return null;
21
+ const fact = {
22
+ activity: stored.activity,
23
+ since: new Date(stored.activitySince).toISOString(),
24
+ at: new Date(stored.reportedAt).toISOString(),
25
+ backgroundWork: stored.backgroundWork,
26
+ };
27
+ if (stored.needsInput !== undefined)
28
+ fact.needsInput = stored.needsInput;
29
+ return fact;
30
+ }
31
+ /**
32
+ * The served state for the agent `(workspacePath, agentId)`, keyed as the store
33
+ * and liveness key it: the normalized workspace path and the canonical agent id
34
+ * (a builder's lowercased role id, an architect's name). `null` when unknown.
35
+ */
36
+ export function serveAgentState(workspacePath, agentId, porch, options = {}) {
37
+ const store = options.store ?? agentStateStore;
38
+ const liveness = options.liveness ?? getAgentLiveness;
39
+ const now = options.now ?? Date.now;
40
+ return projectAgentState({
41
+ blocked: porch.blocked,
42
+ blockedSince: porch.blockedSince,
43
+ phase: porch.phase,
44
+ liveness: livenessFact(liveness(workspacePath, agentId)),
45
+ reported: reportedFact(store, workspacePath, agentId),
46
+ now: new Date(now()).toISOString(),
47
+ });
48
+ }
49
+ //# sourceMappingURL=serve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.js","sourceRoot":"","sources":["../../../../src/agent-farm/servers/agent-state/serve.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EACL,iBAAiB,GAGlB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAwB,MAAM,YAAY,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAsB,MAAM,eAAe,CAAC;AAiBrE,SAAS,YAAY,CAAC,QAAmC;IACvD,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACxC,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;AAClF,CAAC;AAED,SAAS,YAAY,CAAC,KAAsB,EAAE,aAAqB,EAAE,OAAe;IAClF,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IACjD,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,IAAI,GAAyB;QACjC,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,KAAK,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,WAAW,EAAE;QACnD,EAAE,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,WAAW,EAAE;QAC7C,cAAc,EAAE,MAAM,CAAC,cAAc;KACtC,CAAC;IACF,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS;QAAE,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;IACzE,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,aAAqB,EACrB,OAAe,EACf,KAAsB,EACtB,UAAkC,EAAE;IAEpC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,eAAe,CAAC;IAC/C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,gBAAgB,CAAC;IACtD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACpC,OAAO,iBAAiB,CAAC;QACvB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,QAAQ,EAAE,YAAY,CAAC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QACxD,QAAQ,EAAE,YAAY,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,CAAC;QACrD,GAAG,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,WAAW,EAAE;KACnC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Tower's in-memory agent activity store (Spec 1595).
3
+ *
4
+ * Holds, per agent, the latest activity its harness reported through the
5
+ * lifecycle route: the harness session id, the activity and when it began, the
6
+ * hook time of the latest accepted report, whether background work is pending,
7
+ * and the needs-input detail while (and only while) the agent needs input.
8
+ *
9
+ * Memory only, by owner ruling (2026-10-21): nothing here is ever written to
10
+ * `global.db` or any other store, so no served state can outlive the Tower
11
+ * process that received its report. A fresh store holds nothing.
12
+ *
13
+ * Keys are `(normalized workspace path, canonical agent id)`; callers resolve
14
+ * both through `resolveAgentIdentity` before touching the store.
15
+ *
16
+ * Ordering (SC17): a report older than the stored hook time is ignored; a report
17
+ * from a different harness session supersedes the stored one only with a newer
18
+ * hook time; a hook time more than `MAX_FUTURE_SKEW_MS` ahead of Tower's clock is
19
+ * rejected.
20
+ *
21
+ * Background work (SC5): a turn end with `backgroundWork: true` holds the agent
22
+ * `working`. A later same-session turn end that makes no background-work claim
23
+ * (the field absent) does not release that hold; a new turn does.
24
+ */
25
+ import { EventEmitter } from 'node:events';
26
+ import type { AgentActivity, AgentLifecycleEvent, NeedsInputDetail } from '@cluesmith/codev-types';
27
+ /** How far ahead of Tower's clock a report's hook time may be. */
28
+ export declare const MAX_FUTURE_SKEW_MS = 60000;
29
+ /** Raised on every accepted report that changes a served field. */
30
+ export declare const AGENT_STATE_CHANGED = "agent-state-changed";
31
+ /** One agent's reported activity, as held in memory. */
32
+ export interface StoredAgentActivity {
33
+ /** The harness session the activity belongs to. */
34
+ sessionId: string;
35
+ activity: AgentActivity;
36
+ /** Hook time (epoch ms) at which `activity` began. */
37
+ activitySince: number;
38
+ /** Hook time (epoch ms) of the latest report that set the served state; the served `at`. */
39
+ reportedAt: number;
40
+ /**
41
+ * Hook time (epoch ms) of the latest accepted report, including one that
42
+ * changed nothing served. The ordering watermark (SC17): never behind `reportedAt`.
43
+ */
44
+ latestHookAt: number;
45
+ /** True when the agent ended its turn with its own background work running. */
46
+ backgroundWork: boolean;
47
+ /** Present only while `activity` is `needs-input`. */
48
+ needsInput?: NeedsInputDetail;
49
+ }
50
+ /** The payload of `AGENT_STATE_CHANGED`. */
51
+ export interface AgentStateChange {
52
+ workspacePath: string;
53
+ agentId: string;
54
+ }
55
+ /**
56
+ * What `apply` did with a report: `changed` (accepted, a served field moved),
57
+ * `unchanged` (accepted, nothing served moved), `stale` (ignored by ordering),
58
+ * or `future` (rejected: hook time implausibly far ahead).
59
+ */
60
+ export type ApplyOutcome = 'changed' | 'unchanged' | 'stale' | 'future';
61
+ export declare class AgentStateStore extends EventEmitter {
62
+ private readonly now;
63
+ private readonly entries;
64
+ constructor(now?: () => number);
65
+ /** The stored activity for an agent, or `undefined` when it has reported nothing. */
66
+ get(workspacePath: string, agentId: string): StoredAgentActivity | undefined;
67
+ /** Apply one validated report for a resolved agent. */
68
+ apply(workspacePath: string, agentId: string, event: AgentLifecycleEvent): ApplyOutcome;
69
+ /** Every agent the store holds state for. */
70
+ agents(): AgentStateChange[];
71
+ /** Forget an agent's reported state (it was removed). */
72
+ drop(workspacePath: string, agentId: string): void;
73
+ }
74
+ /** Tower's one store. Nothing subscribes to its change event except the broadcaster. */
75
+ export declare const agentStateStore: AgentStateStore;
76
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../../src/agent-farm/servers/agent-state/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAEnG,kEAAkE;AAClE,eAAO,MAAM,kBAAkB,QAAS,CAAC;AAEzC,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,wBAAwB,CAAC;AAEzD,wDAAwD;AACxD,MAAM,WAAW,mBAAmB;IAClC,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,aAAa,CAAC;IACxB,sDAAsD;IACtD,aAAa,EAAE,MAAM,CAAC;IACtB,4FAA4F;IAC5F,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,cAAc,EAAE,OAAO,CAAC;IACxB,sDAAsD;IACtD,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED,4CAA4C;AAC5C,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,QAAQ,CAAC;AAyDxE,qBAAa,eAAgB,SAAQ,YAAY;IAGnC,OAAO,CAAC,QAAQ,CAAC,GAAG;IAFhC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0C;gBAErC,GAAG,GAAE,MAAM,MAAiB;IAIzD,qFAAqF;IACrF,GAAG,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAI5E,uDAAuD;IACvD,KAAK,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,YAAY;IA8CvF,6CAA6C;IAC7C,MAAM,IAAI,gBAAgB,EAAE;IAS5B,yDAAyD;IACzD,IAAI,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;CAGnD;AAED,wFAAwF;AACxF,eAAO,MAAM,eAAe,iBAAwB,CAAC"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Tower's in-memory agent activity store (Spec 1595).
3
+ *
4
+ * Holds, per agent, the latest activity its harness reported through the
5
+ * lifecycle route: the harness session id, the activity and when it began, the
6
+ * hook time of the latest accepted report, whether background work is pending,
7
+ * and the needs-input detail while (and only while) the agent needs input.
8
+ *
9
+ * Memory only, by owner ruling (2026-10-21): nothing here is ever written to
10
+ * `global.db` or any other store, so no served state can outlive the Tower
11
+ * process that received its report. A fresh store holds nothing.
12
+ *
13
+ * Keys are `(normalized workspace path, canonical agent id)`; callers resolve
14
+ * both through `resolveAgentIdentity` before touching the store.
15
+ *
16
+ * Ordering (SC17): a report older than the stored hook time is ignored; a report
17
+ * from a different harness session supersedes the stored one only with a newer
18
+ * hook time; a hook time more than `MAX_FUTURE_SKEW_MS` ahead of Tower's clock is
19
+ * rejected.
20
+ *
21
+ * Background work (SC5): a turn end with `backgroundWork: true` holds the agent
22
+ * `working`. A later same-session turn end that makes no background-work claim
23
+ * (the field absent) does not release that hold; a new turn does.
24
+ */
25
+ import { EventEmitter } from 'node:events';
26
+ /** How far ahead of Tower's clock a report's hook time may be. */
27
+ export const MAX_FUTURE_SKEW_MS = 60_000;
28
+ /** Raised on every accepted report that changes a served field. */
29
+ export const AGENT_STATE_CHANGED = 'agent-state-changed';
30
+ function storeKey(workspacePath, agentId) {
31
+ return `${workspacePath}\0${agentId}`;
32
+ }
33
+ function sameDetail(a, b) {
34
+ if (a === undefined || b === undefined)
35
+ return a === b;
36
+ return a.kind === b.kind && a.tool === b.tool && a.summary === b.summary;
37
+ }
38
+ /** The activity a lifecycle event moves the agent into. */
39
+ function activityFor(event) {
40
+ switch (event.event) {
41
+ case 'session-started':
42
+ return 'idle';
43
+ case 'turn-started':
44
+ case 'tool-activity':
45
+ return 'working';
46
+ case 'needs-input':
47
+ return 'needs-input';
48
+ case 'turn-ended':
49
+ if (event.backgroundWork === true)
50
+ return 'working';
51
+ return 'idle';
52
+ }
53
+ }
54
+ /**
55
+ * True when `event` is a turn end that makes no background-work claim, arriving
56
+ * while the same session's turn end with background work is held. Such a report
57
+ * carries nothing new: Claude's 60 s `idle_prompt` (the second chance for a lost
58
+ * turn end) fires while the agent's own background task still runs, and must not
59
+ * end the background-work hold early. Real-session finding, Phase 8.
60
+ */
61
+ function repeatsBackgroundTurnEnd(event, previous) {
62
+ return event.event === 'turn-ended'
63
+ && event.backgroundWork === undefined
64
+ && previous !== undefined
65
+ && previous.sessionId === event.sessionId
66
+ && previous.backgroundWork;
67
+ }
68
+ /**
69
+ * The needs-input detail for an accepted `needs-input` report. A report without
70
+ * a tool (the redundant permission notification names none) keeps the detail
71
+ * already held for the same episode.
72
+ */
73
+ function detailFor(event, previous) {
74
+ const continuesEpisode = previous !== undefined
75
+ && previous.sessionId === event.sessionId
76
+ && previous.activity === 'needs-input';
77
+ if (continuesEpisode && previous.needsInput !== undefined && event.needsInput?.tool === undefined) {
78
+ return previous.needsInput;
79
+ }
80
+ return event.needsInput;
81
+ }
82
+ export class AgentStateStore extends EventEmitter {
83
+ now;
84
+ entries = new Map();
85
+ constructor(now = Date.now) {
86
+ super();
87
+ this.now = now;
88
+ }
89
+ /** The stored activity for an agent, or `undefined` when it has reported nothing. */
90
+ get(workspacePath, agentId) {
91
+ return this.entries.get(storeKey(workspacePath, agentId));
92
+ }
93
+ /** Apply one validated report for a resolved agent. */
94
+ apply(workspacePath, agentId, event) {
95
+ if (event.at > this.now() + MAX_FUTURE_SKEW_MS)
96
+ return 'future';
97
+ const key = storeKey(workspacePath, agentId);
98
+ const previous = this.entries.get(key);
99
+ if (previous !== undefined) {
100
+ if (event.at < previous.latestHookAt)
101
+ return 'stale';
102
+ if (event.sessionId !== previous.sessionId && event.at === previous.latestHookAt)
103
+ return 'stale';
104
+ }
105
+ if (previous !== undefined && repeatsBackgroundTurnEnd(event, previous)) {
106
+ // The hold and its served time stay; only the ordering watermark moves, so a
107
+ // delayed report older than this one is still ignored.
108
+ this.entries.set(key, { ...previous, latestHookAt: event.at });
109
+ return 'unchanged';
110
+ }
111
+ const activity = activityFor(event);
112
+ let activitySince = event.at;
113
+ if (previous !== undefined && previous.sessionId === event.sessionId && previous.activity === activity) {
114
+ activitySince = previous.activitySince;
115
+ }
116
+ const next = {
117
+ sessionId: event.sessionId,
118
+ activity,
119
+ activitySince,
120
+ reportedAt: event.at,
121
+ latestHookAt: event.at,
122
+ backgroundWork: event.event === 'turn-ended' && event.backgroundWork === true,
123
+ };
124
+ if (activity === 'needs-input') {
125
+ const detail = detailFor(event, previous);
126
+ if (detail !== undefined)
127
+ next.needsInput = detail;
128
+ }
129
+ this.entries.set(key, next);
130
+ const changed = previous === undefined
131
+ || previous.activity !== next.activity
132
+ || previous.reportedAt !== next.reportedAt
133
+ || previous.backgroundWork !== next.backgroundWork
134
+ || !sameDetail(previous.needsInput, next.needsInput);
135
+ if (!changed)
136
+ return 'unchanged';
137
+ const change = { workspacePath, agentId };
138
+ this.emit(AGENT_STATE_CHANGED, change);
139
+ return 'changed';
140
+ }
141
+ /** Every agent the store holds state for. */
142
+ agents() {
143
+ const agents = [];
144
+ for (const key of this.entries.keys()) {
145
+ const separator = key.indexOf('\0');
146
+ agents.push({ workspacePath: key.slice(0, separator), agentId: key.slice(separator + 1) });
147
+ }
148
+ return agents;
149
+ }
150
+ /** Forget an agent's reported state (it was removed). */
151
+ drop(workspacePath, agentId) {
152
+ this.entries.delete(storeKey(workspacePath, agentId));
153
+ }
154
+ }
155
+ /** Tower's one store. Nothing subscribes to its change event except the broadcaster. */
156
+ export const agentStateStore = new AgentStateStore();
157
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../../src/agent-farm/servers/agent-state/store.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../../../src/agent-farm/servers/decisions/parse.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACV,YAAY,EAEZ,cAAc,EAEd,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAoD,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAGjG,MAAM,WAAW,UAAW,SAAQ,YAAY;IAC9C,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,qDAAqD;IACrD,GAAG,EAAE,IAAI,GAAG,MAAM,CAAC;IACnB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,oBAAoB,EAAE,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;CACjB;AA0CD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,gBAAgB,EAAE,MAAM,GAAG,cAAc,EAAE,CAuBvE;AAmGD,2DAA2D;AAC3D,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAgD5D;AA8DD,wEAAwE;AACxE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,iBAAiB,CAmB5F"}
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../../../src/agent-farm/servers/decisions/parse.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EACV,YAAY,EAEZ,cAAc,EAGd,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAoD,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAGjG,MAAM,WAAW,UAAW,SAAQ,YAAY;IAC9C,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,qDAAqD;IACrD,GAAG,EAAE,IAAI,GAAG,MAAM,CAAC;IACnB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,oBAAoB,EAAE,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;CACjB;AA4DD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,gBAAgB,EAAE,MAAM,GAAG,cAAc,EAAE,CAuBvE;AAmGD,2DAA2D;AAC3D,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAiD5D;AA+DD,wEAAwE;AACxE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,iBAAiB,CAmB5F"}
@@ -1,11 +1,20 @@
1
1
  // Decisions file parser (Spec 1819).
2
2
  //
3
- // A pure function from the decisions file's text to its read model. The file is
4
- // the only state: this runs on every read, nothing is cached. Structure comes
5
- // from `grammar.ts`; this module adds the format's conventions:
3
+ // A function from the decisions file's text, and the clock, to its read model. The
4
+ // file is the only state: this runs on every read, nothing is cached. The clock
5
+ // enters only through time-only stamps, which are read as today in their zone
6
+ // (#1836), so the same text can parse to different instants on different days:
7
+ // - `version` (the client's cache key) hashes the text alone and does not cover
8
+ // that: a client keeps the instants it has until the file changes.
9
+ // - `splice.ts`'s `verify()` re-parses at a later moment than the parse it checks
10
+ // against; it is safe only because it compares no stamps.
11
+ // Structure comes from `grammar.ts`; this module adds the format's conventions:
6
12
  // - `## Open` / `## Ruled` sections; items are `###` headings inside `## Open`,
7
13
  // running to the next heading of level 1–3 (or end of file).
8
- // - item id = the heading's first token; ` · ` separates id, stamp and question.
14
+ // - item id = the heading's first token; ` · ` separates id, stamp and question. A first
15
+ // part that is not a stamp belongs to the question.
16
+ // - a `_Zone: <zone>_` line in the header (above the first `##`) is the zone of every
17
+ // stamp that names none.
9
18
  // - options = backticked words on the `**Your word:**` line, first recommended; an
10
19
  // option with no `= …` there takes its description from the body's `**Options:**`.
11
20
  // - current ruling = everything after the `**Your word:**` line, verbatim.
@@ -16,6 +25,7 @@ import { createHash } from 'node:crypto';
16
25
  import { YOUR_WORD_LABEL, classifyLines, detectLineEnding } from './grammar.js';
17
26
  import { resolveStamp } from './stamp.js';
18
27
  const SEPARATOR = ' · ';
28
+ const ZONE_LINE_RE = /^ {0,3}_Zone:[ \t]*(\S+?)\.?_[ \t]*$/;
19
29
  const ASKED_RE = /^ {0,3}- \*\*You asked \(([^)]*)\):\*\*[ \t]*(.*)$/;
20
30
  const ANSWER_RE = /^ {0,3}- \*\*(.+?)['’]s answer \(([^)]*)\):\*\*[ \t]*(.*)$/;
21
31
  const CONTINUATION_INDENT_RE = /^(?: {2}|\t)/;
@@ -42,17 +52,36 @@ function joinTrimmed(lines) {
42
52
  return lines.slice(from, to).map(l => l.text).join('\n');
43
53
  }
44
54
  /**
45
- * Split a heading's text into id, stamp text and question: the first token is the id;
46
- * after a ` · `, the first part is the stamp and the rest (re-joined) the question. A
47
- * heading without separators has a question and no stamp.
55
+ * The file's zone: the one `_Zone: <zone>_` line above the first `##` heading. None, or
56
+ * more than one, gives no file zone.
48
57
  */
49
- function splitHeading(headingText) {
58
+ function fileZone(lines) {
59
+ const zones = [];
60
+ for (const line of lines) {
61
+ if (line.headingLevel >= 2)
62
+ break;
63
+ const zone = line.fenced ? null : ZONE_LINE_RE.exec(line.text);
64
+ if (zone)
65
+ zones.push(zone[1]);
66
+ }
67
+ return zones.length === 1 ? zones[0] : null;
68
+ }
69
+ /**
70
+ * Split a heading's text into id, stamp and question: the first token is the id; after a
71
+ * ` · `, the first part is the stamp and the rest (re-joined) the question. A heading
72
+ * without separators, or whose first part is not a stamp, has a question and no stamp.
73
+ */
74
+ function splitHeading(headingText, zone) {
50
75
  const id = headingText.split(/\s+/)[0] ?? '';
51
76
  const rest = headingText.slice(id.length);
52
77
  if (!rest.startsWith(SEPARATOR))
53
- return { id, stampText: null, question: rest.trim() };
54
- const [stampText, ...questionParts] = rest.slice(SEPARATOR.length).split(SEPARATOR);
55
- return { id, stampText: stampText.trim(), question: questionParts.join(SEPARATOR).trim() };
78
+ return { id, stamp: null, question: rest.trim() };
79
+ const afterId = rest.slice(SEPARATOR.length);
80
+ const [stampText, ...questionParts] = afterId.split(SEPARATOR);
81
+ const stamp = resolveStamp(stampText.trim(), zone);
82
+ if (!stamp)
83
+ return { id, stamp: null, question: afterId.trim() };
84
+ return { id, stamp, question: questionParts.join(SEPARATOR).trim() };
56
85
  }
57
86
  /**
58
87
  * Options from a `**Your word:**` line. An option starts at a backticked span at the
@@ -202,6 +231,7 @@ export function parseDecisions(text) {
202
231
  if (ruledHeadings.length > 1)
203
232
  problems.push('ruled-multiple');
204
233
  const offsetOf = (lineIndex) => (lineIndex < lines.length ? lines[lineIndex].start : text.length);
234
+ const zone = fileZone(lines);
205
235
  const items = [];
206
236
  for (const open of openHeadings) {
207
237
  const openEnd = sectionEnd(open.index);
@@ -215,7 +245,7 @@ export function parseDecisions(text) {
215
245
  break;
216
246
  }
217
247
  }
218
- items.push(parseItem(text, lines, i, endLine, offsetOf));
248
+ items.push(parseItem(text, lines, i, endLine, offsetOf, zone));
219
249
  i = endLine - 1;
220
250
  }
221
251
  }
@@ -232,9 +262,9 @@ export function parseDecisions(text) {
232
262
  const ruledMarkdown = ruled ? joinTrimmed(lines.slice(ruled.index + 1, sectionEnd(ruled.index))) : '';
233
263
  return { lines, eol: detectLineEnding(lines), items, ruledMarkdown, problems, version: sha256(text) };
234
264
  }
235
- function parseItem(text, lines, headingLine, endLine, offsetOf) {
265
+ function parseItem(text, lines, headingLine, endLine, offsetOf, zone) {
236
266
  const heading = lines[headingLine];
237
- const { id, stampText, question } = splitHeading(heading.headingText);
267
+ const { id, stamp, question } = splitHeading(heading.headingText, zone);
238
268
  const start = heading.start;
239
269
  const end = offsetOf(endLine);
240
270
  const itemLines = lines.slice(headingLine + 1, endLine);
@@ -268,7 +298,7 @@ function parseItem(text, lines, headingLine, endLine, offsetOf) {
268
298
  }
269
299
  return {
270
300
  id,
271
- stamp: stampText === null ? null : resolveStamp(stampText),
301
+ stamp,
272
302
  question,
273
303
  bodyMarkdown: joinTrimmed(rest),
274
304
  options,