@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,120 @@
1
+ /**
2
+ * Resolve a lifecycle report's launch identity to the agent Tower knows
3
+ * (Spec 1595, Constraint 15).
4
+ *
5
+ * The reporter names its agent with the identity codev injected at launch: a
6
+ * builder by its worktree path, an architect by name plus workspace path. Tower
7
+ * normalizes every path before the registry lookup, so a differently spelled path
8
+ * to the same agent (a symlink, a trailing slash, a relative segment; #1809)
9
+ * resolves to the same row, and the id it returns is always its own canonical one,
10
+ * never the raw reported identity:
11
+ *
12
+ * - a builder: the shared worktree row matcher (`afx send`'s lookup), then the
13
+ * lowercased role id the overview derives for that row (`builderRoleId`);
14
+ * - an architect: its row in the workspace's registered architects.
15
+ *
16
+ * It never throws. An unknown agent, an unreadable registry, or a path outside
17
+ * any builder worktree all resolve to `null`, which the route rejects.
18
+ *
19
+ * Ingestion fires per tool call, so lookups use Tower's own `global.db` handle
20
+ * behind a memo of successful resolutions. A memoized resolution is reused only
21
+ * while the registry is unchanged: any write to `global.db`, by Tower or by
22
+ * another process such as `afx spawn` or `afx cleanup`, moves SQLite's
23
+ * `data_version` or this connection's `total_changes()` and forces a fresh
24
+ * lookup, and `IDENTITY_MEMO_TTL_MS` bounds reuse regardless. Misses are never
25
+ * memoized, so a newly spawned agent resolves on its first report.
26
+ */
27
+ import { getGlobalDb } from '../../db/index.js';
28
+ import { normalizeWorkspacePath } from '../../utils/workspace-path.js';
29
+ import { matchBuilderRow, parseBuilderWorktreePath, } from '../../utils/builder-worktree-identity.js';
30
+ import { builderRoleId } from '../overview.js';
31
+ /** The longest a successful resolution is reused, even with the registry unchanged. */
32
+ export const IDENTITY_MEMO_TTL_MS = 5_000;
33
+ const memo = new Map();
34
+ /** Drop every memoized resolution. */
35
+ export function invalidateAgentIdentityMemo() {
36
+ memo.clear();
37
+ }
38
+ /**
39
+ * A token that changes whenever `global.db` is written: `data_version` moves on
40
+ * another connection's commit, `total_changes()` on this connection's own writes.
41
+ */
42
+ function registryVersion(db) {
43
+ const dataVersion = db.pragma('data_version', { simple: true });
44
+ const changes = db.prepare('SELECT total_changes() AS n').get();
45
+ return `${dataVersion}:${changes.n}`;
46
+ }
47
+ /** Whether a stored `workspace_path` names `workspacePath` (already normalized). */
48
+ function sameWorkspace(stored, workspacePath) {
49
+ return stored === workspacePath || normalizeWorkspacePath(stored) === workspacePath;
50
+ }
51
+ function resolveBuilder(db, worktreePath) {
52
+ const ref = parseBuilderWorktreePath(normalizeWorkspacePath(worktreePath));
53
+ if (!ref)
54
+ return null;
55
+ const workspacePath = normalizeWorkspacePath(ref.workspacePath);
56
+ // Both sides normalized: a row recorded under another spelling of the
57
+ // workspace path still belongs to this workspace. Rows stored in the
58
+ // canonical spelling are tried first, so the common case realpaths nothing
59
+ // beyond this workspace's own rows.
60
+ const rows = db
61
+ .prepare('SELECT id, worktree, workspace_path FROM builders WHERE worktree IS NOT NULL')
62
+ .all();
63
+ const row = matchBuilderRow(rows.filter(r => r.workspace_path === workspacePath), ref)
64
+ ?? matchBuilderRow(rows.filter(r => r.workspace_path !== workspacePath && sameWorkspace(r.workspace_path, workspacePath)), ref);
65
+ if (!row)
66
+ return null;
67
+ const agentId = builderRoleId(row.id, ref.worktreeDirName);
68
+ if (agentId === null)
69
+ return null;
70
+ return { workspacePath, agentId };
71
+ }
72
+ function resolveArchitect(db, workspacePath, name) {
73
+ const normalized = normalizeWorkspacePath(workspacePath);
74
+ const rows = db
75
+ .prepare('SELECT id, workspace_path FROM architect WHERE id = ?')
76
+ .all(name);
77
+ const row = rows.find(r => sameWorkspace(r.workspace_path, normalized));
78
+ if (!row)
79
+ return null;
80
+ return { workspacePath: normalized, agentId: row.id };
81
+ }
82
+ /**
83
+ * The agent a launch identity names, or `null` when Tower does not know it.
84
+ * Never throws.
85
+ */
86
+ export function resolveAgentIdentity(identity, options = {}) {
87
+ const now = options.now ?? Date.now;
88
+ try {
89
+ let memoKey;
90
+ if (identity.kind === 'builder') {
91
+ memoKey = `builder\0${normalizeWorkspacePath(identity.worktreePath)}`;
92
+ }
93
+ else {
94
+ memoKey = `architect\0${normalizeWorkspacePath(identity.workspacePath)}\0${identity.name}`;
95
+ }
96
+ const db = (options.db ?? getGlobalDb)();
97
+ const version = registryVersion(db);
98
+ const cached = memo.get(memoKey);
99
+ if (cached !== undefined && cached.registryVersion === version && cached.expiresAt > now()) {
100
+ return cached.agent;
101
+ }
102
+ let agent;
103
+ if (identity.kind === 'builder') {
104
+ agent = resolveBuilder(db, identity.worktreePath);
105
+ }
106
+ else {
107
+ agent = resolveArchitect(db, identity.workspacePath, identity.name);
108
+ }
109
+ if (agent === null) {
110
+ memo.delete(memoKey);
111
+ return null;
112
+ }
113
+ memo.set(memoKey, { agent, registryVersion: version, expiresAt: now() + IDENTITY_MEMO_TTL_MS });
114
+ return agent;
115
+ }
116
+ catch {
117
+ return null;
118
+ }
119
+ }
120
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../../../../src/agent-farm/servers/agent-state/identity.ts"],"names":[],"mappings":"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"}
@@ -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"}