@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.
- package/dashboard-dist/assets/{index-r3bZzLmX.css → index-BjcqV9Vv.css} +1 -1
- package/dashboard-dist/assets/{index-BuflRUi6.js → index-b7GnZV2i.js} +55 -55
- package/dashboard-dist/assets/index-b7GnZV2i.js.map +1 -0
- package/dashboard-dist/index.html +2 -2
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +50 -17
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/architect.d.ts.map +1 -1
- package/dist/agent-farm/commands/architect.js +5 -1
- package/dist/agent-farm/commands/architect.js.map +1 -1
- package/dist/agent-farm/commands/attach.d.ts +1 -1
- package/dist/agent-farm/commands/attach.d.ts.map +1 -1
- package/dist/agent-farm/commands/cleanup.d.ts +1 -1
- package/dist/agent-farm/commands/cleanup.d.ts.map +1 -1
- package/dist/agent-farm/commands/cleanup.js +2 -1
- package/dist/agent-farm/commands/cleanup.js.map +1 -1
- package/dist/agent-farm/commands/decisions.d.ts +25 -0
- package/dist/agent-farm/commands/decisions.d.ts.map +1 -0
- package/dist/agent-farm/commands/decisions.js +54 -0
- package/dist/agent-farm/commands/decisions.js.map +1 -0
- package/dist/agent-farm/commands/ide.d.ts +32 -3
- package/dist/agent-farm/commands/ide.d.ts.map +1 -1
- package/dist/agent-farm/commands/ide.js +180 -19
- package/dist/agent-farm/commands/ide.js.map +1 -1
- package/dist/agent-farm/commands/reset/context.d.ts +4 -2
- package/dist/agent-farm/commands/reset/context.d.ts.map +1 -1
- package/dist/agent-farm/commands/reset/context.js +12 -4
- package/dist/agent-farm/commands/reset/context.js.map +1 -1
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +13 -18
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.d.ts +7 -0
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.js +51 -18
- package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts +17 -0
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +41 -19
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/lib/builder-lookup.d.ts +3 -2
- package/dist/agent-farm/lib/builder-lookup.d.ts.map +1 -1
- package/dist/agent-farm/lib/builder-lookup.js +24 -9
- package/dist/agent-farm/lib/builder-lookup.js.map +1 -1
- package/dist/agent-farm/lib/ide-extensions.d.ts +82 -0
- package/dist/agent-farm/lib/ide-extensions.d.ts.map +1 -0
- package/dist/agent-farm/lib/ide-extensions.js +179 -0
- package/dist/agent-farm/lib/ide-extensions.js.map +1 -0
- package/dist/agent-farm/lib/ide-prefix.d.ts +10 -0
- package/dist/agent-farm/lib/ide-prefix.d.ts.map +1 -0
- package/dist/agent-farm/lib/ide-prefix.js +13 -0
- package/dist/agent-farm/lib/ide-prefix.js.map +1 -0
- package/dist/agent-farm/lib/ide-record.d.ts +20 -3
- package/dist/agent-farm/lib/ide-record.d.ts.map +1 -1
- package/dist/agent-farm/lib/ide-record.js +34 -3
- package/dist/agent-farm/lib/ide-record.js.map +1 -1
- package/dist/agent-farm/lib/ide-version.d.ts +97 -0
- package/dist/agent-farm/lib/ide-version.d.ts.map +1 -0
- package/dist/agent-farm/lib/ide-version.js +224 -0
- package/dist/agent-farm/lib/ide-version.js.map +1 -0
- package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tunnel-client.js +7 -3
- package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
- package/dist/agent-farm/servers/agent-state/broadcast.d.ts +19 -0
- package/dist/agent-farm/servers/agent-state/broadcast.d.ts.map +1 -0
- package/dist/agent-farm/servers/agent-state/broadcast.js +30 -0
- package/dist/agent-farm/servers/agent-state/broadcast.js.map +1 -0
- package/dist/agent-farm/servers/agent-state/identity.d.ts +50 -0
- package/dist/agent-farm/servers/agent-state/identity.d.ts.map +1 -0
- package/dist/agent-farm/servers/agent-state/identity.js +120 -0
- package/dist/agent-farm/servers/agent-state/identity.js.map +1 -0
- package/dist/agent-farm/servers/agent-state/liveness.d.ts +107 -0
- package/dist/agent-farm/servers/agent-state/liveness.d.ts.map +1 -0
- package/dist/agent-farm/servers/agent-state/liveness.js +224 -0
- package/dist/agent-farm/servers/agent-state/liveness.js.map +1 -0
- package/dist/agent-farm/servers/agent-state/route.d.ts +38 -0
- package/dist/agent-farm/servers/agent-state/route.d.ts.map +1 -0
- package/dist/agent-farm/servers/agent-state/route.js +170 -0
- package/dist/agent-farm/servers/agent-state/route.js.map +1 -0
- package/dist/agent-farm/servers/agent-state/serve.d.ts +31 -0
- package/dist/agent-farm/servers/agent-state/serve.d.ts.map +1 -0
- package/dist/agent-farm/servers/agent-state/serve.js +49 -0
- package/dist/agent-farm/servers/agent-state/serve.js.map +1 -0
- package/dist/agent-farm/servers/agent-state/store.d.ts +76 -0
- package/dist/agent-farm/servers/agent-state/store.d.ts.map +1 -0
- package/dist/agent-farm/servers/agent-state/store.js +157 -0
- package/dist/agent-farm/servers/agent-state/store.js.map +1 -0
- package/dist/agent-farm/servers/decisions/parse.d.ts.map +1 -1
- package/dist/agent-farm/servers/decisions/parse.js +45 -15
- package/dist/agent-farm/servers/decisions/parse.js.map +1 -1
- package/dist/agent-farm/servers/decisions/stamp.d.ts +6 -2
- package/dist/agent-farm/servers/decisions/stamp.d.ts.map +1 -1
- package/dist/agent-farm/servers/decisions/stamp.js +53 -29
- package/dist/agent-farm/servers/decisions/stamp.js.map +1 -1
- package/dist/agent-farm/servers/decisions/template.d.ts +2 -0
- package/dist/agent-farm/servers/decisions/template.d.ts.map +1 -0
- package/dist/agent-farm/servers/decisions/template.js +15 -0
- package/dist/agent-farm/servers/decisions/template.js.map +1 -0
- package/dist/agent-farm/servers/ide-forward.d.ts +57 -5
- package/dist/agent-farm/servers/ide-forward.d.ts.map +1 -1
- package/dist/agent-farm/servers/ide-forward.js +263 -31
- package/dist/agent-farm/servers/ide-forward.js.map +1 -1
- package/dist/agent-farm/servers/ide-server.d.ts +28 -8
- package/dist/agent-farm/servers/ide-server.d.ts.map +1 -1
- package/dist/agent-farm/servers/ide-server.js +151 -52
- package/dist/agent-farm/servers/ide-server.js.map +1 -1
- package/dist/agent-farm/servers/overview.d.ts +27 -1
- package/dist/agent-farm/servers/overview.d.ts.map +1 -1
- package/dist/agent-farm/servers/overview.js +184 -31
- package/dist/agent-farm/servers/overview.js.map +1 -1
- package/dist/agent-farm/servers/tower-instances.d.ts +14 -1
- package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-instances.js +51 -9
- package/dist/agent-farm/servers/tower-instances.js.map +1 -1
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-routes.js +149 -15
- package/dist/agent-farm/servers/tower-routes.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +35 -1
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.d.ts +25 -0
- package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.js +128 -4
- package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
- package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-utils.js +18 -5
- package/dist/agent-farm/servers/tower-utils.js.map +1 -1
- package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-websocket.js +11 -7
- package/dist/agent-farm/servers/tower-websocket.js.map +1 -1
- package/dist/agent-farm/utils/builder-worktree-identity.d.ts +39 -0
- package/dist/agent-farm/utils/builder-worktree-identity.d.ts.map +1 -0
- package/dist/agent-farm/utils/builder-worktree-identity.js +42 -0
- package/dist/agent-farm/utils/builder-worktree-identity.js.map +1 -0
- package/dist/agent-farm/utils/harness.d.ts +71 -0
- package/dist/agent-farm/utils/harness.d.ts.map +1 -1
- package/dist/agent-farm/utils/harness.js +153 -0
- package/dist/agent-farm/utils/harness.js.map +1 -1
- package/dist/agent-farm/utils/lifecycle-reporter.d.ts +79 -0
- package/dist/agent-farm/utils/lifecycle-reporter.d.ts.map +1 -0
- package/dist/agent-farm/utils/lifecycle-reporter.js +347 -0
- package/dist/agent-farm/utils/lifecycle-reporter.js.map +1 -0
- package/dist/agent-farm/utils/server-utils.d.ts +54 -19
- package/dist/agent-farm/utils/server-utils.d.ts.map +1 -1
- package/dist/agent-farm/utils/server-utils.js +144 -27
- package/dist/agent-farm/utils/server-utils.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +17 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt.d.ts +16 -2
- package/dist/commands/adopt.d.ts.map +1 -1
- package/dist/commands/adopt.js +67 -23
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/bootstrap-docs.d.ts +132 -0
- package/dist/commands/bootstrap-docs.d.ts.map +1 -0
- package/dist/commands/bootstrap-docs.js +725 -0
- package/dist/commands/bootstrap-docs.js.map +1 -0
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +14 -1
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/consult/tree-tripwire.d.ts +2 -75
- package/dist/commands/consult/tree-tripwire.d.ts.map +1 -1
- package/dist/commands/consult/tree-tripwire.js +7 -184
- package/dist/commands/consult/tree-tripwire.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +36 -4
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/porch/state.d.ts +0 -4
- package/dist/commands/porch/state.d.ts.map +1 -1
- package/dist/commands/porch/state.js +23 -10
- package/dist/commands/porch/state.js.map +1 -1
- package/dist/commands/update.d.ts +5 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +73 -18
- package/dist/commands/update.js.map +1 -1
- package/dist/lib/agent-env.d.ts +22 -1
- package/dist/lib/agent-env.d.ts.map +1 -1
- package/dist/lib/agent-env.js +27 -1
- package/dist/lib/agent-env.js.map +1 -1
- package/dist/lib/agent-process.d.ts +23 -0
- package/dist/lib/agent-process.d.ts.map +1 -0
- package/dist/lib/agent-process.js +24 -0
- package/dist/lib/agent-process.js.map +1 -0
- package/dist/lib/forge-contracts.d.ts +9 -3
- package/dist/lib/forge-contracts.d.ts.map +1 -1
- package/dist/lib/forge-contracts.js.map +1 -1
- package/dist/lib/forge.d.ts +8 -5
- package/dist/lib/forge.d.ts.map +1 -1
- package/dist/lib/forge.js +57 -1
- package/dist/lib/forge.js.map +1 -1
- package/dist/lib/github.d.ts.map +1 -1
- package/dist/lib/github.js +38 -0
- package/dist/lib/github.js.map +1 -1
- package/dist/lib/governance-bootstrap.d.ts +155 -0
- package/dist/lib/governance-bootstrap.d.ts.map +1 -0
- package/dist/lib/governance-bootstrap.js +469 -0
- package/dist/lib/governance-bootstrap.js.map +1 -0
- package/dist/lib/issue-id.d.ts +34 -0
- package/dist/lib/issue-id.d.ts.map +1 -0
- package/dist/lib/issue-id.js +61 -0
- package/dist/lib/issue-id.js.map +1 -0
- package/dist/lib/scaffold.d.ts +4 -0
- package/dist/lib/scaffold.d.ts.map +1 -1
- package/dist/lib/scaffold.js +29 -5
- package/dist/lib/scaffold.js.map +1 -1
- package/dist/lib/shipped-skill-hashes.d.ts +10 -0
- package/dist/lib/shipped-skill-hashes.d.ts.map +1 -0
- package/dist/lib/shipped-skill-hashes.js +197 -0
- package/dist/lib/shipped-skill-hashes.js.map +1 -0
- package/dist/lib/skill-refresh.d.ts +90 -0
- package/dist/lib/skill-refresh.d.ts.map +1 -0
- package/dist/lib/skill-refresh.js +267 -0
- package/dist/lib/skill-refresh.js.map +1 -0
- package/dist/lib/tree-snapshot.d.ts +172 -0
- package/dist/lib/tree-snapshot.d.ts.map +1 -0
- package/dist/lib/tree-snapshot.js +393 -0
- package/dist/lib/tree-snapshot.js.map +1 -0
- package/dist/terminal/pty-session.d.ts +5 -0
- package/dist/terminal/pty-session.d.ts.map +1 -1
- package/dist/terminal/pty-session.js +7 -0
- package/dist/terminal/pty-session.js.map +1 -1
- package/ide-extension/codev-vscode.vsix +0 -0
- package/ide-extension/manifest.json +7 -0
- package/package.json +8 -6
- package/scripts/forge/jira/_lib.sh +113 -0
- package/scripts/forge/jira/auth-status.sh +29 -0
- package/scripts/forge/jira/issue-comment.sh +23 -0
- package/scripts/forge/jira/issue-list.sh +32 -0
- package/scripts/forge/jira/issue-search.sh +40 -0
- package/scripts/forge/jira/issue-view.sh +41 -0
- package/scripts/forge/jira/recently-closed.sh +81 -0
- package/scripts/forge/jira/user-identity.sh +18 -0
- package/skeleton/.claude/skills/afx/SKILL.md +36 -0
- package/skeleton/.claude/skills/codev/SKILL.md +18 -2
- package/skeleton/.claude/skills/update-arch-docs/SKILL.md +28 -2
- package/skeleton/.codex/skills/afx/SKILL.md +36 -0
- package/skeleton/.codex/skills/codev/SKILL.md +18 -2
- package/skeleton/.codex/skills/update-arch-docs/SKILL.md +28 -2
- package/skeleton/resources/commands/agent-farm.md +139 -0
- package/skeleton/resources/commands/codev.md +47 -0
- package/skeleton/resources/commands/overview.md +1 -0
- package/skeleton/resources/decision-protocol.md +215 -0
- package/skeleton/resources/workflow-reference.md +12 -0
- package/skeleton/roles/architect.md +6 -0
- package/skeleton/templates/bootstrap-docs-prompt.md +16 -0
- package/skeleton/templates/bootstrap-docs-review-prompt.md +19 -0
- package/skeleton/templates/cheatsheet.md +1 -0
- package/templates/tower.html +78 -3
- 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":"
|
|
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
|
|
4
|
-
// the only state: this runs on every read, nothing is cached.
|
|
5
|
-
//
|
|
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
|
-
*
|
|
46
|
-
*
|
|
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
|
|
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,
|
|
54
|
-
const
|
|
55
|
-
|
|
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,
|
|
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
|
|
301
|
+
stamp,
|
|
272
302
|
question,
|
|
273
303
|
bodyMarkdown: joinTrimmed(rest),
|
|
274
304
|
options,
|