@cluesmith/codev 3.3.5 → 3.3.6

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 (231) 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-record.d.ts +6 -1
  49. package/dist/agent-farm/lib/ide-record.d.ts.map +1 -1
  50. package/dist/agent-farm/lib/ide-record.js +8 -1
  51. package/dist/agent-farm/lib/ide-record.js.map +1 -1
  52. package/dist/agent-farm/lib/ide-version.d.ts +97 -0
  53. package/dist/agent-farm/lib/ide-version.d.ts.map +1 -0
  54. package/dist/agent-farm/lib/ide-version.js +224 -0
  55. package/dist/agent-farm/lib/ide-version.js.map +1 -0
  56. package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
  57. package/dist/agent-farm/lib/tunnel-client.js +7 -3
  58. package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
  59. package/dist/agent-farm/servers/agent-state/broadcast.d.ts +19 -0
  60. package/dist/agent-farm/servers/agent-state/broadcast.d.ts.map +1 -0
  61. package/dist/agent-farm/servers/agent-state/broadcast.js +30 -0
  62. package/dist/agent-farm/servers/agent-state/broadcast.js.map +1 -0
  63. package/dist/agent-farm/servers/agent-state/identity.d.ts +50 -0
  64. package/dist/agent-farm/servers/agent-state/identity.d.ts.map +1 -0
  65. package/dist/agent-farm/servers/agent-state/identity.js +120 -0
  66. package/dist/agent-farm/servers/agent-state/identity.js.map +1 -0
  67. package/dist/agent-farm/servers/agent-state/liveness.d.ts +107 -0
  68. package/dist/agent-farm/servers/agent-state/liveness.d.ts.map +1 -0
  69. package/dist/agent-farm/servers/agent-state/liveness.js +224 -0
  70. package/dist/agent-farm/servers/agent-state/liveness.js.map +1 -0
  71. package/dist/agent-farm/servers/agent-state/route.d.ts +38 -0
  72. package/dist/agent-farm/servers/agent-state/route.d.ts.map +1 -0
  73. package/dist/agent-farm/servers/agent-state/route.js +170 -0
  74. package/dist/agent-farm/servers/agent-state/route.js.map +1 -0
  75. package/dist/agent-farm/servers/agent-state/serve.d.ts +31 -0
  76. package/dist/agent-farm/servers/agent-state/serve.d.ts.map +1 -0
  77. package/dist/agent-farm/servers/agent-state/serve.js +49 -0
  78. package/dist/agent-farm/servers/agent-state/serve.js.map +1 -0
  79. package/dist/agent-farm/servers/agent-state/store.d.ts +76 -0
  80. package/dist/agent-farm/servers/agent-state/store.d.ts.map +1 -0
  81. package/dist/agent-farm/servers/agent-state/store.js +157 -0
  82. package/dist/agent-farm/servers/agent-state/store.js.map +1 -0
  83. package/dist/agent-farm/servers/decisions/parse.d.ts.map +1 -1
  84. package/dist/agent-farm/servers/decisions/parse.js +45 -15
  85. package/dist/agent-farm/servers/decisions/parse.js.map +1 -1
  86. package/dist/agent-farm/servers/decisions/stamp.d.ts +6 -2
  87. package/dist/agent-farm/servers/decisions/stamp.d.ts.map +1 -1
  88. package/dist/agent-farm/servers/decisions/stamp.js +53 -29
  89. package/dist/agent-farm/servers/decisions/stamp.js.map +1 -1
  90. package/dist/agent-farm/servers/decisions/template.d.ts +2 -0
  91. package/dist/agent-farm/servers/decisions/template.d.ts.map +1 -0
  92. package/dist/agent-farm/servers/decisions/template.js +15 -0
  93. package/dist/agent-farm/servers/decisions/template.js.map +1 -0
  94. package/dist/agent-farm/servers/ide-server.d.ts +28 -8
  95. package/dist/agent-farm/servers/ide-server.d.ts.map +1 -1
  96. package/dist/agent-farm/servers/ide-server.js +147 -50
  97. package/dist/agent-farm/servers/ide-server.js.map +1 -1
  98. package/dist/agent-farm/servers/overview.d.ts +27 -1
  99. package/dist/agent-farm/servers/overview.d.ts.map +1 -1
  100. package/dist/agent-farm/servers/overview.js +184 -31
  101. package/dist/agent-farm/servers/overview.js.map +1 -1
  102. package/dist/agent-farm/servers/tower-instances.d.ts +14 -1
  103. package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
  104. package/dist/agent-farm/servers/tower-instances.js +51 -9
  105. package/dist/agent-farm/servers/tower-instances.js.map +1 -1
  106. package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
  107. package/dist/agent-farm/servers/tower-routes.js +50 -9
  108. package/dist/agent-farm/servers/tower-routes.js.map +1 -1
  109. package/dist/agent-farm/servers/tower-server.js +35 -1
  110. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  111. package/dist/agent-farm/servers/tower-terminals.d.ts +25 -0
  112. package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
  113. package/dist/agent-farm/servers/tower-terminals.js +128 -4
  114. package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
  115. package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
  116. package/dist/agent-farm/servers/tower-utils.js +18 -5
  117. package/dist/agent-farm/servers/tower-utils.js.map +1 -1
  118. package/dist/agent-farm/utils/builder-worktree-identity.d.ts +39 -0
  119. package/dist/agent-farm/utils/builder-worktree-identity.d.ts.map +1 -0
  120. package/dist/agent-farm/utils/builder-worktree-identity.js +42 -0
  121. package/dist/agent-farm/utils/builder-worktree-identity.js.map +1 -0
  122. package/dist/agent-farm/utils/harness.d.ts +71 -0
  123. package/dist/agent-farm/utils/harness.d.ts.map +1 -1
  124. package/dist/agent-farm/utils/harness.js +153 -0
  125. package/dist/agent-farm/utils/harness.js.map +1 -1
  126. package/dist/agent-farm/utils/lifecycle-reporter.d.ts +79 -0
  127. package/dist/agent-farm/utils/lifecycle-reporter.d.ts.map +1 -0
  128. package/dist/agent-farm/utils/lifecycle-reporter.js +347 -0
  129. package/dist/agent-farm/utils/lifecycle-reporter.js.map +1 -0
  130. package/dist/cli.d.ts.map +1 -1
  131. package/dist/cli.js +17 -0
  132. package/dist/cli.js.map +1 -1
  133. package/dist/commands/adopt.d.ts +16 -2
  134. package/dist/commands/adopt.d.ts.map +1 -1
  135. package/dist/commands/adopt.js +67 -23
  136. package/dist/commands/adopt.js.map +1 -1
  137. package/dist/commands/bootstrap-docs.d.ts +132 -0
  138. package/dist/commands/bootstrap-docs.d.ts.map +1 -0
  139. package/dist/commands/bootstrap-docs.js +725 -0
  140. package/dist/commands/bootstrap-docs.js.map +1 -0
  141. package/dist/commands/consult/index.d.ts.map +1 -1
  142. package/dist/commands/consult/index.js +14 -1
  143. package/dist/commands/consult/index.js.map +1 -1
  144. package/dist/commands/consult/tree-tripwire.d.ts +2 -75
  145. package/dist/commands/consult/tree-tripwire.d.ts.map +1 -1
  146. package/dist/commands/consult/tree-tripwire.js +7 -184
  147. package/dist/commands/consult/tree-tripwire.js.map +1 -1
  148. package/dist/commands/doctor.d.ts.map +1 -1
  149. package/dist/commands/doctor.js +36 -4
  150. package/dist/commands/doctor.js.map +1 -1
  151. package/dist/commands/porch/state.d.ts +0 -4
  152. package/dist/commands/porch/state.d.ts.map +1 -1
  153. package/dist/commands/porch/state.js +23 -10
  154. package/dist/commands/porch/state.js.map +1 -1
  155. package/dist/commands/update.d.ts +5 -1
  156. package/dist/commands/update.d.ts.map +1 -1
  157. package/dist/commands/update.js +73 -18
  158. package/dist/commands/update.js.map +1 -1
  159. package/dist/lib/agent-env.d.ts +22 -1
  160. package/dist/lib/agent-env.d.ts.map +1 -1
  161. package/dist/lib/agent-env.js +27 -1
  162. package/dist/lib/agent-env.js.map +1 -1
  163. package/dist/lib/agent-process.d.ts +23 -0
  164. package/dist/lib/agent-process.d.ts.map +1 -0
  165. package/dist/lib/agent-process.js +24 -0
  166. package/dist/lib/agent-process.js.map +1 -0
  167. package/dist/lib/forge-contracts.d.ts +9 -3
  168. package/dist/lib/forge-contracts.d.ts.map +1 -1
  169. package/dist/lib/forge-contracts.js.map +1 -1
  170. package/dist/lib/forge.d.ts +8 -5
  171. package/dist/lib/forge.d.ts.map +1 -1
  172. package/dist/lib/forge.js +57 -1
  173. package/dist/lib/forge.js.map +1 -1
  174. package/dist/lib/github.d.ts.map +1 -1
  175. package/dist/lib/github.js +38 -0
  176. package/dist/lib/github.js.map +1 -1
  177. package/dist/lib/governance-bootstrap.d.ts +155 -0
  178. package/dist/lib/governance-bootstrap.d.ts.map +1 -0
  179. package/dist/lib/governance-bootstrap.js +469 -0
  180. package/dist/lib/governance-bootstrap.js.map +1 -0
  181. package/dist/lib/issue-id.d.ts +34 -0
  182. package/dist/lib/issue-id.d.ts.map +1 -0
  183. package/dist/lib/issue-id.js +61 -0
  184. package/dist/lib/issue-id.js.map +1 -0
  185. package/dist/lib/scaffold.d.ts +4 -0
  186. package/dist/lib/scaffold.d.ts.map +1 -1
  187. package/dist/lib/scaffold.js +29 -5
  188. package/dist/lib/scaffold.js.map +1 -1
  189. package/dist/lib/shipped-skill-hashes.d.ts +10 -0
  190. package/dist/lib/shipped-skill-hashes.d.ts.map +1 -0
  191. package/dist/lib/shipped-skill-hashes.js +197 -0
  192. package/dist/lib/shipped-skill-hashes.js.map +1 -0
  193. package/dist/lib/skill-refresh.d.ts +90 -0
  194. package/dist/lib/skill-refresh.d.ts.map +1 -0
  195. package/dist/lib/skill-refresh.js +267 -0
  196. package/dist/lib/skill-refresh.js.map +1 -0
  197. package/dist/lib/tree-snapshot.d.ts +172 -0
  198. package/dist/lib/tree-snapshot.d.ts.map +1 -0
  199. package/dist/lib/tree-snapshot.js +393 -0
  200. package/dist/lib/tree-snapshot.js.map +1 -0
  201. package/dist/terminal/pty-session.d.ts +5 -0
  202. package/dist/terminal/pty-session.d.ts.map +1 -1
  203. package/dist/terminal/pty-session.js +7 -0
  204. package/dist/terminal/pty-session.js.map +1 -1
  205. package/ide-extension/codev-vscode.vsix +0 -0
  206. package/ide-extension/manifest.json +7 -0
  207. package/package.json +8 -6
  208. package/scripts/forge/jira/_lib.sh +113 -0
  209. package/scripts/forge/jira/auth-status.sh +29 -0
  210. package/scripts/forge/jira/issue-comment.sh +23 -0
  211. package/scripts/forge/jira/issue-list.sh +32 -0
  212. package/scripts/forge/jira/issue-search.sh +40 -0
  213. package/scripts/forge/jira/issue-view.sh +41 -0
  214. package/scripts/forge/jira/recently-closed.sh +81 -0
  215. package/scripts/forge/jira/user-identity.sh +18 -0
  216. package/skeleton/.claude/skills/afx/SKILL.md +36 -0
  217. package/skeleton/.claude/skills/codev/SKILL.md +18 -2
  218. package/skeleton/.claude/skills/update-arch-docs/SKILL.md +28 -2
  219. package/skeleton/.codex/skills/afx/SKILL.md +36 -0
  220. package/skeleton/.codex/skills/codev/SKILL.md +18 -2
  221. package/skeleton/.codex/skills/update-arch-docs/SKILL.md +28 -2
  222. package/skeleton/resources/commands/agent-farm.md +112 -0
  223. package/skeleton/resources/commands/codev.md +47 -0
  224. package/skeleton/resources/commands/overview.md +1 -0
  225. package/skeleton/resources/decision-protocol.md +215 -0
  226. package/skeleton/resources/workflow-reference.md +12 -0
  227. package/skeleton/roles/architect.md +6 -0
  228. package/skeleton/templates/bootstrap-docs-prompt.md +16 -0
  229. package/skeleton/templates/bootstrap-docs-review-prompt.md +19 -0
  230. package/skeleton/templates/cheatsheet.md +1 -0
  231. package/dashboard-dist/assets/index-BuflRUi6.js.map +0 -1
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Agent liveness: the one place Tower decides whether an agent's process is
3
+ * running (Spec 1595, SC7, Constraint 14).
4
+ *
5
+ * A sweep every `LIVENESS_SWEEP_INTERVAL_MS` takes one process snapshot through
6
+ * the shared `listProcessCensus()` (#1227) and walks each registered agent's
7
+ * process tree from its session's pid:
8
+ *
9
+ * - shellper-backed builder: shellper → launch loop → agent. Alive while some
10
+ * launch loop has a child. The loop's relaunch-pause `sleep` is a child, so it
11
+ * counts as alive; its `read -r` relaunch prompt is not, so a loop waiting
12
+ * there reads as not alive although the loop itself still runs.
13
+ * - direct builder: `session.pid` is the launch loop; same child test.
14
+ * - shellper-backed architect: shellper → agent. Alive while the shellper has a
15
+ * child.
16
+ * - direct architect: `session.pid` is the agent; alive while it is running.
17
+ * - any agent: not alive with no session, an exited session, or (architects) a
18
+ * session in restart wait.
19
+ *
20
+ * A transition to not alive is published only after `NOT_ALIVE_CONFIRMATIONS`
21
+ * consecutive not-alive sweeps, so a transient gap never flashes `dead`. A
22
+ * failed snapshot (`[]`) publishes `alive: null` (unknown) for every agent, and
23
+ * an unrecognized process shape for its agent; neither advances nor resets the
24
+ * confirmation, so a not-alive observation before an unknown sweep still counts.
25
+ * A published not alive's `since` is the confirming sweep, up to one interval
26
+ * after the process went. Lifecycle reports never feed liveness.
27
+ *
28
+ * The sweep also drops the store entries of agents no longer registered (SC11)
29
+ * and raises `AGENT_STATE_CHANGED` whenever a published value changes. The
30
+ * snapshot's command lines can carry secrets, so only pid and ppid are read and
31
+ * nothing from it is logged.
32
+ */
33
+ import { EventEmitter } from 'node:events';
34
+ import { type ProcessCensusEntry } from '../process-census.js';
35
+ import { type AgentStateStore } from './store.js';
36
+ import type { WorkspaceTerminals } from '../tower-types.js';
37
+ /** How often liveness is swept. */
38
+ export declare const LIVENESS_SWEEP_INTERVAL_MS = 5000;
39
+ /** Consecutive not-alive sweeps before not alive is published. */
40
+ export declare const NOT_ALIVE_CONFIRMATIONS = 2;
41
+ /** The session facts liveness reads (a `PtySession` satisfies it). */
42
+ export interface LivenessSession {
43
+ /** The shellper's pid for a shellper-backed session, else the PTY child's. */
44
+ pid: number;
45
+ shellperBacked: boolean;
46
+ status: 'running' | 'exited';
47
+ awaitingRestart: boolean;
48
+ }
49
+ /** One agent registered with Tower, with its session when it has one. */
50
+ export interface RegisteredAgent {
51
+ /** Normalized workspace path. */
52
+ workspacePath: string;
53
+ /** Canonical id: a builder's lowercased role id, or an architect's name. */
54
+ agentId: string;
55
+ kind: 'builder' | 'architect';
56
+ session: LivenessSession | undefined;
57
+ }
58
+ /** An agent's published liveness. */
59
+ export interface AgentLiveness {
60
+ /** `null` while liveness cannot be determined. */
61
+ alive: boolean | null;
62
+ /** When `alive` took its current value (epoch ms). */
63
+ since: number;
64
+ }
65
+ export interface AgentLivenessOptions {
66
+ /** The registered agents, read once per sweep. */
67
+ agents: () => RegisteredAgent[];
68
+ /** The process snapshot. Defaults to `listProcessCensus`. */
69
+ census?: () => Promise<ProcessCensusEntry[]>;
70
+ /** The store whose entries follow registration. Defaults to Tower's store. */
71
+ store?: AgentStateStore;
72
+ now?: () => number;
73
+ log?: (message: string) => void;
74
+ }
75
+ export declare class AgentLivenessTracker extends EventEmitter {
76
+ private readonly options;
77
+ private readonly records;
78
+ private readonly census;
79
+ private readonly store;
80
+ private readonly now;
81
+ private timer;
82
+ private sweeping;
83
+ constructor(options: AgentLivenessOptions);
84
+ /** An agent's published liveness, or `undefined` when it is not registered. */
85
+ get(workspacePath: string, agentId: string): AgentLiveness | undefined;
86
+ /** Run one sweep: observe every registered agent, publish, and drop the removed. */
87
+ sweep(): Promise<void>;
88
+ /** Sweep now and then every `intervalMs`, until `stop`. */
89
+ start(intervalMs?: number): void;
90
+ stop(): void;
91
+ /** A timer-driven sweep: never overlaps another, never throws. */
92
+ private runSweep;
93
+ private publish;
94
+ }
95
+ /**
96
+ * The agents Tower's terminal registry holds: every builder and architect in
97
+ * each workspace's entry, keyed as the store keys them, with its session when
98
+ * the terminal manager still has one.
99
+ */
100
+ export declare function registeredAgents(workspaceTerminals: Map<string, WorkspaceTerminals>, getSession: (terminalId: string) => LivenessSession | undefined, normalizeWorkspace: (workspacePath: string) => string): RegisteredAgent[];
101
+ /** Start Tower's one liveness sweep; returns the tracker so boot can subscribe to it. */
102
+ export declare function startAgentLiveness(options: AgentLivenessOptions): AgentLivenessTracker;
103
+ /** Stop Tower's liveness sweep (shutdown). */
104
+ export declare function stopAgentLiveness(): void;
105
+ /** Tower's published liveness for an agent; `undefined` before the sweep has seen it. */
106
+ export declare function getAgentLiveness(workspacePath: string, agentId: string): AgentLiveness | undefined;
107
+ //# sourceMappingURL=liveness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liveness.d.ts","sourceRoot":"","sources":["../../../../src/agent-farm/servers/agent-state/liveness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Agent liveness: the one place Tower decides whether an agent's process is
3
+ * running (Spec 1595, SC7, Constraint 14).
4
+ *
5
+ * A sweep every `LIVENESS_SWEEP_INTERVAL_MS` takes one process snapshot through
6
+ * the shared `listProcessCensus()` (#1227) and walks each registered agent's
7
+ * process tree from its session's pid:
8
+ *
9
+ * - shellper-backed builder: shellper → launch loop → agent. Alive while some
10
+ * launch loop has a child. The loop's relaunch-pause `sleep` is a child, so it
11
+ * counts as alive; its `read -r` relaunch prompt is not, so a loop waiting
12
+ * there reads as not alive although the loop itself still runs.
13
+ * - direct builder: `session.pid` is the launch loop; same child test.
14
+ * - shellper-backed architect: shellper → agent. Alive while the shellper has a
15
+ * child.
16
+ * - direct architect: `session.pid` is the agent; alive while it is running.
17
+ * - any agent: not alive with no session, an exited session, or (architects) a
18
+ * session in restart wait.
19
+ *
20
+ * A transition to not alive is published only after `NOT_ALIVE_CONFIRMATIONS`
21
+ * consecutive not-alive sweeps, so a transient gap never flashes `dead`. A
22
+ * failed snapshot (`[]`) publishes `alive: null` (unknown) for every agent, and
23
+ * an unrecognized process shape for its agent; neither advances nor resets the
24
+ * confirmation, so a not-alive observation before an unknown sweep still counts.
25
+ * A published not alive's `since` is the confirming sweep, up to one interval
26
+ * after the process went. Lifecycle reports never feed liveness.
27
+ *
28
+ * The sweep also drops the store entries of agents no longer registered (SC11)
29
+ * and raises `AGENT_STATE_CHANGED` whenever a published value changes. The
30
+ * snapshot's command lines can carry secrets, so only pid and ppid are read and
31
+ * nothing from it is logged.
32
+ */
33
+ import { EventEmitter } from 'node:events';
34
+ import { listProcessCensus } from '../process-census.js';
35
+ import { AGENT_STATE_CHANGED, agentStateStore } from './store.js';
36
+ /** How often liveness is swept. */
37
+ export const LIVENESS_SWEEP_INTERVAL_MS = 5_000;
38
+ /** Consecutive not-alive sweeps before not alive is published. */
39
+ export const NOT_ALIVE_CONFIRMATIONS = 2;
40
+ function agentKey(workspacePath, agentId) {
41
+ return `${workspacePath}\0${agentId}`;
42
+ }
43
+ function processTree(census) {
44
+ const running = new Set();
45
+ const children = new Map();
46
+ for (const entry of census) {
47
+ running.add(entry.pid);
48
+ const siblings = children.get(entry.ppid);
49
+ if (siblings === undefined) {
50
+ children.set(entry.ppid, [entry.pid]);
51
+ }
52
+ else {
53
+ siblings.push(entry.pid);
54
+ }
55
+ }
56
+ return { running, children };
57
+ }
58
+ function hasChild(tree, pid) {
59
+ return tree.children.has(pid);
60
+ }
61
+ /** Whether a launch loop at `pid` is running an agent. */
62
+ function launchLoopRunsAgent(tree, pid) {
63
+ return tree.running.has(pid) && hasChild(tree, pid);
64
+ }
65
+ /**
66
+ * This sweep's observation for one agent; `null` when it cannot be told. A
67
+ * failed snapshot makes every observation unknown, session facts included, so a
68
+ * failing `ps` never advances any agent toward not alive.
69
+ */
70
+ function observe(agent, tree) {
71
+ if (tree === null)
72
+ return null;
73
+ const session = agent.session;
74
+ if (session === undefined || session.status === 'exited')
75
+ return false;
76
+ if (agent.kind === 'architect' && session.awaitingRestart)
77
+ return false;
78
+ if (!Number.isInteger(session.pid) || session.pid <= 0)
79
+ return null;
80
+ if (agent.kind === 'builder') {
81
+ if (!session.shellperBacked)
82
+ return launchLoopRunsAgent(tree, session.pid);
83
+ const loops = tree.children.get(session.pid) ?? [];
84
+ return loops.some(loop => hasChild(tree, loop));
85
+ }
86
+ if (session.shellperBacked)
87
+ return hasChild(tree, session.pid);
88
+ return tree.running.has(session.pid);
89
+ }
90
+ export class AgentLivenessTracker extends EventEmitter {
91
+ options;
92
+ records = new Map();
93
+ census;
94
+ store;
95
+ now;
96
+ timer = null;
97
+ sweeping = false;
98
+ constructor(options) {
99
+ super();
100
+ this.options = options;
101
+ this.census = options.census ?? listProcessCensus;
102
+ this.store = options.store ?? agentStateStore;
103
+ this.now = options.now ?? Date.now;
104
+ }
105
+ /** An agent's published liveness, or `undefined` when it is not registered. */
106
+ get(workspacePath, agentId) {
107
+ return this.records.get(agentKey(workspacePath, agentId))?.published;
108
+ }
109
+ /** Run one sweep: observe every registered agent, publish, and drop the removed. */
110
+ async sweep() {
111
+ const agents = this.options.agents();
112
+ let tree = null;
113
+ if (agents.length > 0) {
114
+ const census = await this.census();
115
+ if (census.length > 0)
116
+ tree = processTree(census);
117
+ }
118
+ const registered = new Set();
119
+ for (const agent of agents) {
120
+ const key = agentKey(agent.workspacePath, agent.agentId);
121
+ if (registered.has(key))
122
+ continue;
123
+ registered.add(key);
124
+ this.publish(agent, key, observe(agent, tree));
125
+ }
126
+ for (const key of this.records.keys()) {
127
+ if (!registered.has(key))
128
+ this.records.delete(key);
129
+ }
130
+ for (const held of this.store.agents()) {
131
+ if (!registered.has(agentKey(held.workspacePath, held.agentId))) {
132
+ this.store.drop(held.workspacePath, held.agentId);
133
+ }
134
+ }
135
+ }
136
+ /** Sweep now and then every `intervalMs`, until `stop`. */
137
+ start(intervalMs = LIVENESS_SWEEP_INTERVAL_MS) {
138
+ if (this.timer !== null)
139
+ return;
140
+ this.runSweep();
141
+ this.timer = setInterval(() => this.runSweep(), intervalMs);
142
+ this.timer.unref();
143
+ }
144
+ stop() {
145
+ if (this.timer === null)
146
+ return;
147
+ clearInterval(this.timer);
148
+ this.timer = null;
149
+ }
150
+ /** A timer-driven sweep: never overlaps another, never throws. */
151
+ runSweep() {
152
+ if (this.sweeping)
153
+ return;
154
+ this.sweeping = true;
155
+ this.sweep()
156
+ .catch((err) => {
157
+ this.options.log?.(`Agent liveness sweep failed: ${err.message}`);
158
+ })
159
+ .finally(() => {
160
+ this.sweeping = false;
161
+ });
162
+ }
163
+ publish(agent, key, observed) {
164
+ let record = this.records.get(key);
165
+ if (record === undefined) {
166
+ record = { published: { alive: null, since: this.now() }, notAliveSweeps: 0 };
167
+ this.records.set(key, record);
168
+ }
169
+ let next = record.published.alive;
170
+ if (observed === null) {
171
+ next = null;
172
+ }
173
+ else if (observed) {
174
+ record.notAliveSweeps = 0;
175
+ next = true;
176
+ }
177
+ else {
178
+ record.notAliveSweeps += 1;
179
+ if (record.notAliveSweeps >= NOT_ALIVE_CONFIRMATIONS)
180
+ next = false;
181
+ }
182
+ if (next === record.published.alive)
183
+ return;
184
+ record.published = { alive: next, since: this.now() };
185
+ const change = { workspacePath: agent.workspacePath, agentId: agent.agentId };
186
+ this.emit(AGENT_STATE_CHANGED, change);
187
+ }
188
+ }
189
+ /**
190
+ * The agents Tower's terminal registry holds: every builder and architect in
191
+ * each workspace's entry, keyed as the store keys them, with its session when
192
+ * the terminal manager still has one.
193
+ */
194
+ export function registeredAgents(workspaceTerminals, getSession, normalizeWorkspace) {
195
+ const agents = [];
196
+ for (const [rawWorkspacePath, entry] of workspaceTerminals) {
197
+ const workspacePath = normalizeWorkspace(rawWorkspacePath);
198
+ for (const [roleId, terminalId] of entry.builders) {
199
+ agents.push({ workspacePath, agentId: roleId.toLowerCase(), kind: 'builder', session: getSession(terminalId) });
200
+ }
201
+ for (const [name, terminalId] of entry.architects) {
202
+ agents.push({ workspacePath, agentId: name, kind: 'architect', session: getSession(terminalId) });
203
+ }
204
+ }
205
+ return agents;
206
+ }
207
+ let towerLiveness = null;
208
+ /** Start Tower's one liveness sweep; returns the tracker so boot can subscribe to it. */
209
+ export function startAgentLiveness(options) {
210
+ towerLiveness?.stop();
211
+ towerLiveness = new AgentLivenessTracker(options);
212
+ towerLiveness.start();
213
+ return towerLiveness;
214
+ }
215
+ /** Stop Tower's liveness sweep (shutdown). */
216
+ export function stopAgentLiveness() {
217
+ towerLiveness?.stop();
218
+ towerLiveness = null;
219
+ }
220
+ /** Tower's published liveness for an agent; `undefined` before the sweep has seen it. */
221
+ export function getAgentLiveness(workspacePath, agentId) {
222
+ return towerLiveness?.get(workspacePath, agentId);
223
+ }
224
+ //# sourceMappingURL=liveness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liveness.js","sourceRoot":"","sources":["../../../../src/agent-farm/servers/agent-state/liveness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
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 type http from 'node:http';
16
+ import type { AgentLaunchIdentity, AgentLifecycleEvent } from '@cluesmith/codev-types';
17
+ import type { AgentStateStore } from './store.js';
18
+ import type { ResolvedAgent } from './identity.js';
19
+ /** Request body cap, in bytes. */
20
+ export declare const MAX_LIFECYCLE_BODY_BYTES = 8192;
21
+ export declare const MAX_PATH_LENGTH = 4096;
22
+ export declare const MAX_NAME_LENGTH = 128;
23
+ export declare const MAX_SESSION_ID_LENGTH = 128;
24
+ export declare const MAX_TOOL_LENGTH = 64;
25
+ export declare const MAX_SUMMARY_LENGTH = 200;
26
+ export interface AgentLifecycleDeps {
27
+ store: AgentStateStore;
28
+ resolve: (identity: AgentLaunchIdentity) => ResolvedAgent | null;
29
+ }
30
+ /**
31
+ * Validate a parsed body against the `AgentLifecycleEvent` contract, or `null`.
32
+ * Strict: unknown keys anywhere are rejected, `needsInput` is accepted only on a
33
+ * `needs-input` event and `backgroundWork` only on `turn-ended`.
34
+ */
35
+ export declare function parseLifecycleEvent(value: unknown): AgentLifecycleEvent | null;
36
+ /** Handle one lifecycle report. Auth has already passed in `handleRequest`. */
37
+ export declare function handleAgentLifecycle(req: http.IncomingMessage, res: http.ServerResponse, deps: AgentLifecycleDeps): Promise<void>;
38
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../src/agent-farm/servers/agent-state/route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EAGpB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,kCAAkC;AAClC,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C,eAAO,MAAM,eAAe,OAAO,CAAC;AACpC,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAkBtC,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,EAAE,CAAC,QAAQ,EAAE,mBAAmB,KAAK,aAAa,GAAG,IAAI,CAAC;CAClE;AAmDD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,GAAG,IAAI,CAyB9E;AAOD,+EAA+E;AAC/E,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CAwCf"}
@@ -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