harnery 0.31.6 → 0.32.0
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/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +2 -0
- package/dist/commands/agents.d.ts +0 -9
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +70 -95
- package/dist/commands/browse-session.d.ts +21 -0
- package/dist/commands/browse-session.d.ts.map +1 -0
- package/dist/commands/browse-session.js +157 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +205 -37
- package/dist/commands/checkpoint.js +1 -1
- package/dist/commands/deinit.d.ts.map +1 -1
- package/dist/commands/deinit.js +4 -0
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +40 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +100 -17
- package/dist/commands/init.d.ts +1 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +16 -5
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +2 -3
- package/dist/core/agents/canonical-emit.d.ts +1 -2
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +1 -2
- package/dist/core/agents/cli.js +123 -88
- package/dist/core/agents/coord-client.d.ts +7 -3
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +5 -4
- package/dist/core/agents/finalization.d.ts +68 -0
- package/dist/core/agents/finalization.d.ts.map +1 -0
- package/dist/core/agents/finalization.js +443 -0
- package/dist/core/agents/git-hook.d.ts +51 -0
- package/dist/core/agents/git-hook.d.ts.map +1 -0
- package/dist/core/agents/git-hook.js +118 -0
- package/dist/core/agents/render/prompt-context.d.ts +6 -5
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +26 -13
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +15 -2
- package/dist/core/agents/rules/claim-conflict.js +3 -3
- package/dist/core/agents/rules/commit-conflict.d.ts +15 -6
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/commit-conflict.js +21 -5
- package/dist/core/agents/rules/stop-hook.d.ts +3 -0
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
- package/dist/core/agents/rules/stop-hook.js +59 -23
- package/dist/core/agents/session-events.d.ts +8 -16
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +12 -26
- package/dist/core/agents/state/heartbeat-projector.d.ts +3 -0
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-projector.js +44 -1
- package/dist/core/agents/state/heartbeat-writer.d.ts +32 -5
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +54 -17
- package/dist/core/agents/state/names.d.ts +22 -1
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +40 -2
- package/dist/core/config.d.ts +30 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +74 -11
- package/dist/core/governor/planning.d.ts.map +1 -1
- package/dist/core/governor/planning.js +12 -13
- package/dist/core/hooks/adapter/detect.d.ts +2 -4
- package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
- package/dist/core/hooks/adapter/detect.js +4 -14
- package/dist/core/hooks/adapter/output.d.ts +1 -1
- package/dist/core/hooks/adapter/output.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.js +5 -4
- package/dist/core/hooks/adapter/wiring.d.ts +23 -0
- package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
- package/dist/core/hooks/adapter/wiring.js +32 -0
- package/dist/core/hooks/cli.d.ts +1 -2
- package/dist/core/hooks/cli.d.ts.map +1 -1
- package/dist/core/hooks/cli.js +169 -27
- package/dist/core/hooks/codex-wsl-bridge.d.ts +46 -0
- package/dist/core/hooks/codex-wsl-bridge.d.ts.map +1 -0
- package/dist/core/hooks/codex-wsl-bridge.js +137 -0
- package/dist/core/hooks/effects/index.d.ts +1 -1
- package/dist/core/hooks/effects/index.js +3 -3
- package/dist/core/hooks/events/schema.d.ts +27 -0
- package/dist/core/hooks/events/schema.d.ts.map +1 -1
- package/dist/core/hooks/resolve/owner.d.ts +7 -1
- package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
- package/dist/core/hooks/resolve/owner.js +15 -0
- package/dist/core/hooks/resolve/transcript.d.ts +44 -0
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +176 -1
- package/dist/core/hooks/session-name-presence.d.ts +30 -0
- package/dist/core/hooks/session-name-presence.d.ts.map +1 -0
- package/dist/core/hooks/session-name-presence.js +43 -0
- package/dist/core/hooks/unsafe-cross-shell.d.ts +13 -0
- package/dist/core/hooks/unsafe-cross-shell.d.ts.map +1 -0
- package/dist/core/hooks/unsafe-cross-shell.js +151 -0
- package/dist/core/work/state.d.ts +4 -5
- package/dist/core/work/state.d.ts.map +1 -1
- package/dist/core/work/state.js +5 -8
- package/dist/core/workflow/index.d.ts +1 -1
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/proof.d.ts +2 -2
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +2 -2
- package/dist/core/workflow/run-state.d.ts +2 -2
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +2 -2
- package/dist/core/workflow/types.d.ts +3 -3
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/execution.d.ts +2 -2
- package/dist/core/workflow/workspaces/execution.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/execution.js +5 -0
- package/dist/core/workflow/workspaces/index.d.ts +1 -1
- package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.js +1 -2
- package/dist/core/workflow/workspaces/paths.d.ts +2 -2
- package/dist/core/workflow/workspaces/types.d.ts +0 -6
- package/dist/core/workflow/workspaces/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/validate.d.ts +0 -2
- package/dist/core/workflow/workspaces/validate.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/validate.js +0 -2
- package/dist/lib/browser/client.d.ts +118 -1
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +435 -6
- package/dist/lib/browser/geometry.d.ts.map +1 -1
- package/dist/lib/browser/geometry.js +180 -37
- package/dist/lib/browser/index.d.ts +5 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +5 -1
- package/dist/lib/browser/netscape-cookies.d.ts +6 -0
- package/dist/lib/browser/netscape-cookies.d.ts.map +1 -0
- package/dist/lib/browser/netscape-cookies.js +31 -0
- package/dist/lib/browser/proxy.d.ts +24 -0
- package/dist/lib/browser/proxy.d.ts.map +1 -0
- package/dist/lib/browser/proxy.js +84 -0
- package/dist/lib/browser/runts.d.ts +6 -0
- package/dist/lib/browser/runts.d.ts.map +1 -1
- package/dist/lib/browser/runts.js +21 -4
- package/dist/lib/browser/session-control.d.ts +112 -0
- package/dist/lib/browser/session-control.d.ts.map +1 -0
- package/dist/lib/browser/session-control.js +670 -0
- package/dist/lib/docs-links.d.ts +108 -0
- package/dist/lib/docs-links.d.ts.map +1 -0
- package/dist/lib/docs-links.js +555 -0
- package/dist/lib/exec.d.ts +1 -1
- package/dist/lib/exec.js +1 -1
- package/dist/lib/identities/assume.d.ts +4 -2
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +15 -2
- package/dist/lib/instructions/git-hooks.d.ts +75 -0
- package/dist/lib/instructions/git-hooks.d.ts.map +1 -0
- package/dist/lib/instructions/git-hooks.js +238 -0
- package/dist/lib/instructions/splice.d.ts +10 -4
- package/dist/lib/instructions/splice.d.ts.map +1 -1
- package/dist/lib/instructions/splice.js +19 -11
- package/package.json +1 -1
- package/schemas/config.schema.json +32 -0
- package/src/commander.ts +2 -0
- package/src/commands/agents.ts +96 -115
- package/src/commands/browse-session.ts +245 -0
- package/src/commands/browse.ts +287 -44
- package/src/commands/checkpoint.ts +1 -1
- package/src/commands/deinit.ts +7 -1
- package/src/commands/docs.ts +56 -0
- package/src/commands/doctor.ts +100 -18
- package/src/commands/init.ts +17 -9
- package/src/commands/tunnel.ts +2 -5
- package/src/core/agents/canonical-emit.ts +1 -2
- package/src/core/agents/cli.ts +137 -88
- package/src/core/agents/coord-client.ts +10 -5
- package/src/core/agents/finalization.ts +595 -0
- package/src/core/agents/git-hook.ts +126 -0
- package/src/core/agents/render/prompt-context.ts +28 -13
- package/src/core/agents/render/session-context.ts +15 -2
- package/src/core/agents/rules/claim-conflict.ts +3 -3
- package/src/core/agents/rules/commit-conflict.ts +35 -8
- package/src/core/agents/rules/stop-hook.ts +71 -23
- package/src/core/agents/session-events.ts +14 -41
- package/src/core/agents/state/heartbeat-projector.ts +45 -1
- package/src/core/agents/state/heartbeat-writer.ts +78 -16
- package/src/core/agents/state/names.ts +54 -2
- package/src/core/config.ts +92 -12
- package/src/core/governor/planning.ts +17 -12
- package/src/core/hooks/adapter/detect.ts +4 -13
- package/src/core/hooks/adapter/output.ts +9 -4
- package/src/core/hooks/adapter/wiring.ts +43 -0
- package/src/core/hooks/cli.ts +201 -23
- package/src/core/hooks/codex-wsl-bridge.ts +188 -0
- package/src/core/hooks/effects/index.ts +5 -5
- package/src/core/hooks/events/schema.ts +27 -0
- package/src/core/hooks/resolve/owner.ts +22 -1
- package/src/core/hooks/resolve/transcript.ts +164 -1
- package/src/core/hooks/session-name-presence.ts +52 -0
- package/src/core/hooks/unsafe-cross-shell.ts +160 -0
- package/src/core/work/state.ts +8 -12
- package/src/core/workflow/engine.ts +2 -2
- package/src/core/workflow/index.ts +0 -1
- package/src/core/workflow/proof.ts +4 -4
- package/src/core/workflow/run-state.ts +5 -5
- package/src/core/workflow/types.ts +3 -3
- package/src/core/workflow/workspaces/execution.ts +11 -4
- package/src/core/workflow/workspaces/index.ts +0 -3
- package/src/core/workflow/workspaces/local-git.ts +1 -2
- package/src/core/workflow/workspaces/paths.ts +2 -2
- package/src/core/workflow/workspaces/types.ts +0 -9
- package/src/core/workflow/workspaces/validate.ts +1 -5
- package/src/lib/browser/client.ts +528 -6
- package/src/lib/browser/geometry.ts +197 -37
- package/src/lib/browser/index.ts +39 -0
- package/src/lib/browser/netscape-cookies.ts +39 -0
- package/src/lib/browser/proxy.ts +105 -0
- package/src/lib/browser/runts.ts +27 -3
- package/src/lib/browser/session-control.ts +892 -0
- package/src/lib/docs-links.ts +674 -0
- package/src/lib/exec.ts +1 -1
- package/src/lib/identities/assume.ts +22 -1
- package/src/lib/instructions/git-hooks.ts +259 -0
- package/src/lib/instructions/splice.ts +41 -11
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Command/narration event emitter for the coordination layer.
|
|
3
3
|
*
|
|
4
|
-
* `writeSessionEvent` emits command
|
|
4
|
+
* `writeSessionEvent` emits command and narration events straight to the
|
|
5
5
|
* **canonical** `.harnery/events.ndjson` (alongside the hook events), which the
|
|
6
|
-
* `/live` web viewer reads.
|
|
7
|
-
* `newCmdId`, `clampField`, `readLastIntent`) is stable so the session-tee
|
|
8
|
-
* middleware callers need no edits.
|
|
6
|
+
* `/live` web viewer reads.
|
|
9
7
|
*/
|
|
10
|
-
/** Event types accepted by `writeSessionEvent`.
|
|
11
|
-
|
|
12
|
-
* call-site compatibility but are no-ops (the agents CLI emits those itself). */
|
|
13
|
-
export type SessionEventType = "command_start" | "output" | "command_end" | "end_of_turn" | "hook_event" | "set_task" | "file_claim" | "file_release" | "peer_change" | "narration";
|
|
8
|
+
/** Event types accepted by `writeSessionEvent`. */
|
|
9
|
+
export type SessionEventType = "command_start" | "output" | "command_end" | "narration";
|
|
14
10
|
/**
|
|
15
11
|
* Resolved path of the ndjson sidecar file. Lives inside `.harnery/` so a
|
|
16
12
|
* containerized reader can pick it up through a single bind mount.
|
|
17
13
|
*/
|
|
18
|
-
export declare function
|
|
14
|
+
export declare function canonicalEventsPath(): string;
|
|
19
15
|
/** Random 8-char hex id for grouping output lines under a single command. */
|
|
20
16
|
export declare function newCmdId(): string;
|
|
21
17
|
/**
|
|
@@ -28,14 +24,10 @@ export declare function newCmdId(): string;
|
|
|
28
24
|
*/
|
|
29
25
|
export declare function readLastIntent(instanceId?: string): string | null;
|
|
30
26
|
/**
|
|
31
|
-
* Emit a
|
|
32
|
-
*
|
|
33
|
-
* are accepted for call-site compatibility but are no-ops here (the agents CLI
|
|
34
|
-
* emits those itself). Best-effort, never throws into the caller; a command
|
|
35
|
-
* must never break or slow on telemetry. The `agentName` arg is retained for
|
|
36
|
-
* the stable call signature (canonical events key on instance_id, not name).
|
|
27
|
+
* Emit a command or narration event to `.harnery/events.ndjson`. Best-effort,
|
|
28
|
+
* never throws into the caller; telemetry must not break or slow a command.
|
|
37
29
|
*/
|
|
38
|
-
export declare function writeSessionEvent(type: SessionEventType,
|
|
30
|
+
export declare function writeSessionEvent(type: SessionEventType, fields?: Record<string, unknown>): void;
|
|
39
31
|
/** Trim long values to keep individual events small. */
|
|
40
32
|
export declare function clampField(v: string, max?: number): string;
|
|
41
33
|
//# sourceMappingURL=session-events.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-events.d.ts","sourceRoot":"","sources":["../../../src/core/agents/session-events.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"session-events.d.ts","sourceRoot":"","sources":["../../../src/core/agents/session-events.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,mDAAmD;AACnD,MAAM,MAAM,gBAAgB,GAAG,eAAe,GAAG,QAAQ,GAAG,aAAa,GAAG,WAAW,CAAC;AAExF;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAa5C;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBjE;AA8FD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,gBAAgB,EACtB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,IAAI,CAEN;AAED,wDAAwD;AACxD,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,SAAO,GAAG,MAAM,CAGxD"}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Command/narration event emitter for the coordination layer.
|
|
3
3
|
*
|
|
4
|
-
* `writeSessionEvent` emits command
|
|
4
|
+
* `writeSessionEvent` emits command and narration events straight to the
|
|
5
5
|
* **canonical** `.harnery/events.ndjson` (alongside the hook events), which the
|
|
6
|
-
* `/live` web viewer reads.
|
|
7
|
-
* `newCmdId`, `clampField`, `readLastIntent`) is stable so the session-tee
|
|
8
|
-
* middleware callers need no edits.
|
|
6
|
+
* `/live` web viewer reads.
|
|
9
7
|
*/
|
|
10
8
|
import { randomBytes } from "node:crypto";
|
|
11
9
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -17,9 +15,9 @@ import { emit } from "./events/emit.js";
|
|
|
17
15
|
* Resolved path of the ndjson sidecar file. Lives inside `.harnery/` so a
|
|
18
16
|
* containerized reader can pick it up through a single bind mount.
|
|
19
17
|
*/
|
|
20
|
-
export function
|
|
18
|
+
export function canonicalEventsPath() {
|
|
21
19
|
// Explicit override (tests + non-monorepo invocations).
|
|
22
|
-
const explicit = process.env.
|
|
20
|
+
const explicit = process.env.HARNERY_EVENTS_PATH;
|
|
23
21
|
if (explicit)
|
|
24
22
|
return explicit;
|
|
25
23
|
// Superproject-aware root resolution (git first, cwd walk fallback) via
|
|
@@ -48,7 +46,7 @@ export function newCmdId() {
|
|
|
48
46
|
export function readLastIntent(instanceId) {
|
|
49
47
|
if (!instanceId)
|
|
50
48
|
return null;
|
|
51
|
-
// Same superproject-aware root resolution as
|
|
49
|
+
// Same superproject-aware root resolution as canonicalEventsPath(): the
|
|
52
50
|
// intent stamp is written by the PreToolUse hook into the SUPERPROJECT's
|
|
53
51
|
// .harnery/, so a nested-`.harnery/` cwd must not redirect the read.
|
|
54
52
|
const root = resolveEmitRoot();
|
|
@@ -68,14 +66,6 @@ export function readLastIntent(instanceId) {
|
|
|
68
66
|
return null;
|
|
69
67
|
}
|
|
70
68
|
}
|
|
71
|
-
/**
|
|
72
|
-
* Dual-write: mirror command/narration session-events
|
|
73
|
-
* into the canonical `.harnery/events.ndjson` stream so the legacy
|
|
74
|
-
* session-events.ndjson writer + its web consumers can be retired.
|
|
75
|
-
* Only the command-stream + narration types migrate; the coord/state types
|
|
76
|
-
* (`set_task`, `file_claim`, `peer_change`, …) are already emitted canonically
|
|
77
|
-
* by the agents CLI, so re-emitting them here would double-count.
|
|
78
|
-
*/
|
|
79
69
|
const CANONICAL_TYPE = {
|
|
80
70
|
command_start: "command.start",
|
|
81
71
|
output: "command.output",
|
|
@@ -110,7 +100,7 @@ function enrichFromHeartbeat(coordRoot, instanceId) {
|
|
|
110
100
|
return null;
|
|
111
101
|
}
|
|
112
102
|
}
|
|
113
|
-
/** Project the flat
|
|
103
|
+
/** Project the flat middleware `fields` into the canonical event's `data` shape.
|
|
114
104
|
* Unknown types never reach here, guarded by CANONICAL_TYPE. */
|
|
115
105
|
function canonicalData(type, fields) {
|
|
116
106
|
switch (type) {
|
|
@@ -133,7 +123,7 @@ function canonicalData(type, fields) {
|
|
|
133
123
|
}
|
|
134
124
|
/** Emit a command/narration event to the canonical stream. Swallows every
|
|
135
125
|
* error and skips when identity can't be resolved: telemetry must never break
|
|
136
|
-
*
|
|
126
|
+
* or slow down a command. */
|
|
137
127
|
function emitCanonicalCommand(type, fields) {
|
|
138
128
|
const eventType = CANONICAL_TYPE[type];
|
|
139
129
|
if (!eventType)
|
|
@@ -142,8 +132,8 @@ function emitCanonicalCommand(type, fields) {
|
|
|
142
132
|
if (!instanceId)
|
|
143
133
|
return;
|
|
144
134
|
try {
|
|
145
|
-
// coordRoot = the dir containing `.harnery/`;
|
|
146
|
-
const coordRoot = dirname(dirname(
|
|
135
|
+
// coordRoot = the dir containing `.harnery/`; canonicalEventsPath() anchors it.
|
|
136
|
+
const coordRoot = dirname(dirname(canonicalEventsPath()));
|
|
147
137
|
const enrich = enrichFromHeartbeat(coordRoot, instanceId);
|
|
148
138
|
if (!enrich)
|
|
149
139
|
return;
|
|
@@ -160,14 +150,10 @@ function emitCanonicalCommand(type, fields) {
|
|
|
160
150
|
}
|
|
161
151
|
}
|
|
162
152
|
/**
|
|
163
|
-
* Emit a
|
|
164
|
-
*
|
|
165
|
-
* are accepted for call-site compatibility but are no-ops here (the agents CLI
|
|
166
|
-
* emits those itself). Best-effort, never throws into the caller; a command
|
|
167
|
-
* must never break or slow on telemetry. The `agentName` arg is retained for
|
|
168
|
-
* the stable call signature (canonical events key on instance_id, not name).
|
|
153
|
+
* Emit a command or narration event to `.harnery/events.ndjson`. Best-effort,
|
|
154
|
+
* never throws into the caller; telemetry must not break or slow a command.
|
|
169
155
|
*/
|
|
170
|
-
export function writeSessionEvent(type,
|
|
156
|
+
export function writeSessionEvent(type, fields = {}) {
|
|
171
157
|
emitCanonicalCommand(type, fields);
|
|
172
158
|
}
|
|
173
159
|
/** Trim long values to keep individual events small. */
|
|
@@ -30,6 +30,9 @@ export interface V2Heartbeat {
|
|
|
30
30
|
last_tool_at?: string;
|
|
31
31
|
task?: string;
|
|
32
32
|
task_updated_at?: string;
|
|
33
|
+
suggested_session_name?: string;
|
|
34
|
+
session_name_seen_at?: string;
|
|
35
|
+
session_name_seen_for?: string;
|
|
33
36
|
last_status_at?: string;
|
|
34
37
|
presence?: "mobile" | "office";
|
|
35
38
|
last_intent?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"heartbeat-projector.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/heartbeat-projector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kFAAkF;IAClF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,yEAAyE;IACzE,aAAa,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,cAAc,EAAE,MAAM,CAAC;IACvB,oCAAoC;IACpC,OAAO,EAAE;QACP,cAAc,EAAE,CAAC,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,SAAS,cAAc,EAAE,GAChC;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;CAAE,CAoD9D;
|
|
1
|
+
{"version":3,"file":"heartbeat-projector.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/heartbeat-projector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kFAAkF;IAClF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,yEAAyE;IACzE,aAAa,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,cAAc,EAAE,MAAM,CAAC;IACvB,oCAAoC;IACpC,OAAO,EAAE;QACP,cAAc,EAAE,CAAC,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,SAAS,cAAc,EAAE,GAChC;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;CAAE,CAoD9D;AAsbD,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3E"}
|
|
@@ -197,6 +197,18 @@ function apply(hb, ev, coordRoot) {
|
|
|
197
197
|
case "turn.stop":
|
|
198
198
|
hb.last_turn_stop_at = ev.ts;
|
|
199
199
|
hb.last_turn_status_box_present = pickBool(d, "status_box_present");
|
|
200
|
+
// Rebuild fidelity for the naming ritual: the live stamp is written by
|
|
201
|
+
// the Stop hook (stampSessionNameSeen); reproduce it from the event so a
|
|
202
|
+
// full projector rebuild doesn't re-open a satisfied naming window.
|
|
203
|
+
if (d.session_name_present === true) {
|
|
204
|
+
hb.session_name_seen_at ??= ev.ts;
|
|
205
|
+
// Attribute the sighting to the name the scan actually covered. Reading
|
|
206
|
+
// the name current at this point in the replay instead re-attributed an
|
|
207
|
+
// old sighting to a re-minted name, which faked "already seen" for a
|
|
208
|
+
// name no reply had shown. An event without the field predates it, so
|
|
209
|
+
// leave the attribution unset and let the next stop re-scan.
|
|
210
|
+
hb.session_name_seen_for = pickStr(d, "session_name_present_for");
|
|
211
|
+
}
|
|
200
212
|
{
|
|
201
213
|
const summary = pickStr(d, "turn_summary");
|
|
202
214
|
if (summary) {
|
|
@@ -254,6 +266,11 @@ function apply(hb, ev, coordRoot) {
|
|
|
254
266
|
hb.task = task;
|
|
255
267
|
}
|
|
256
268
|
hb.task_updated_at = ev.ts;
|
|
269
|
+
// Rebuild fidelity: the naming call carries the name it produced.
|
|
270
|
+
const suggested = pickStr(d, "suggested_session_name");
|
|
271
|
+
if (suggested && !hb.suggested_session_name) {
|
|
272
|
+
hb.suggested_session_name = suggested;
|
|
273
|
+
}
|
|
257
274
|
break;
|
|
258
275
|
}
|
|
259
276
|
case "state.status_checked":
|
|
@@ -274,6 +291,20 @@ function apply(hb, ev, coordRoot) {
|
|
|
274
291
|
hb.presence = to;
|
|
275
292
|
break;
|
|
276
293
|
}
|
|
294
|
+
case "claim.acquire": {
|
|
295
|
+
const path = pickStr(d, "path");
|
|
296
|
+
const mode = pickStr(d, "mode");
|
|
297
|
+
if (path && mode === "write") {
|
|
298
|
+
const canonical = path.startsWith(`${coordRoot}/`)
|
|
299
|
+
? path.slice(coordRoot.length + 1)
|
|
300
|
+
: path;
|
|
301
|
+
if (!hb.files_touched)
|
|
302
|
+
hb.files_touched = [];
|
|
303
|
+
if (!hb.files_touched.includes(canonical))
|
|
304
|
+
hb.files_touched.push(canonical);
|
|
305
|
+
}
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
277
308
|
case "claim.release": {
|
|
278
309
|
const path = pickStr(d, "path");
|
|
279
310
|
if (path && hb.files_touched) {
|
|
@@ -292,7 +323,7 @@ function apply(hb, ev, coordRoot) {
|
|
|
292
323
|
}
|
|
293
324
|
function adapterToPlatform(adapter) {
|
|
294
325
|
if (adapter === "claude-code")
|
|
295
|
-
return "
|
|
326
|
+
return "claude-code";
|
|
296
327
|
if (adapter === "cursor")
|
|
297
328
|
return "cursor";
|
|
298
329
|
if (adapter === "codex")
|
|
@@ -446,6 +477,18 @@ function writeHeartbeat(coordRoot, instanceId, hb) {
|
|
|
446
477
|
setIfDefined(merged, "last_tool_at", hb.last_tool_at);
|
|
447
478
|
setIfDefined(merged, "task", hb.task);
|
|
448
479
|
setIfDefined(merged, "task_updated_at", hb.task_updated_at);
|
|
480
|
+
setIfDefined(merged, "suggested_session_name", hb.suggested_session_name);
|
|
481
|
+
setIfDefined(merged, "session_name_seen_at", hb.session_name_seen_at);
|
|
482
|
+
if (hb.session_name_seen_for) {
|
|
483
|
+
merged.session_name_seen_for = hb.session_name_seen_for;
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
// Unlike most projected fields, an absent attribution is meaningful: a
|
|
487
|
+
// legacy sighting cannot prove which suggested name it covered. Remove a
|
|
488
|
+
// stale value inherited through the additive merge so the next stop
|
|
489
|
+
// scans the current name instead of treating it as already satisfied.
|
|
490
|
+
delete merged.session_name_seen_for;
|
|
491
|
+
}
|
|
449
492
|
setIfDefined(merged, "last_status_at", hb.last_status_at);
|
|
450
493
|
setIfDefined(merged, "turn_summary", hb.turn_summary);
|
|
451
494
|
setIfDefined(merged, "turn_summary_updated_at", hb.turn_summary_updated_at);
|
|
@@ -27,6 +27,17 @@ export interface Heartbeat {
|
|
|
27
27
|
files_touched: string[];
|
|
28
28
|
task?: string;
|
|
29
29
|
task_updated_at?: string | null;
|
|
30
|
+
/** Session name built on the first non-empty set-task (never rebuilt). Its
|
|
31
|
+
* presence is the "this session has been named" signal the prompt-context
|
|
32
|
+
* nudge and the Stop-hook naming rule key on. */
|
|
33
|
+
suggested_session_name?: string;
|
|
34
|
+
/** Stamped by turn.stop once the suggested name is seen in assistant reply
|
|
35
|
+
* text, ending the per-turn transcript scan. */
|
|
36
|
+
session_name_seen_at?: string;
|
|
37
|
+
/** WHICH name that sighting was for. The scan is skipped only while this
|
|
38
|
+
* matches the current suggested name, so a re-minted name is detectable
|
|
39
|
+
* again rather than being suppressed by the earlier sighting. */
|
|
40
|
+
session_name_seen_for?: string;
|
|
30
41
|
last_status_at?: string;
|
|
31
42
|
turn_summary?: string | null;
|
|
32
43
|
turn_summary_updated_at?: string | null;
|
|
@@ -35,11 +46,26 @@ export interface Heartbeat {
|
|
|
35
46
|
last_tool_at?: string;
|
|
36
47
|
current_turn_id?: string;
|
|
37
48
|
parent_instance_id?: string;
|
|
49
|
+
workflow_run_id?: string;
|
|
38
50
|
[extra: string]: unknown;
|
|
39
51
|
}
|
|
40
52
|
export declare function readHeartbeat(coordRoot: string, instanceId: string): Heartbeat | null;
|
|
41
53
|
export declare function setTask(coordRoot: string, instanceId: string, task: string): Heartbeat | null;
|
|
42
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Build the copy-pasteable session name from the coord identity + the agent's
|
|
56
|
+
* description parts. Pure (no coord-state reads) so it's unit-testable; collapses
|
|
57
|
+
* internal whitespace and trims. Returns null when the description is empty.
|
|
58
|
+
*/
|
|
59
|
+
export declare function buildSuggestedName(agentName: string, descriptionParts: string[]): {
|
|
60
|
+
suggestedName: string;
|
|
61
|
+
description: string;
|
|
62
|
+
} | null;
|
|
63
|
+
/** Stamp the sighting once the suggested name has been observed in assistant
|
|
64
|
+
* reply text; later turns skip the transcript scan. Records WHICH name was seen,
|
|
65
|
+
* because the skip is only valid for that one: if a later set-task mints a
|
|
66
|
+
* different suggested name, a bare "already seen" stamp would suppress the scan
|
|
67
|
+
* forever and leave the Stop-hook naming rule permanently unsatisfiable. */
|
|
68
|
+
export declare function stampSessionNameSeen(coordRoot: string, instanceId: string, name?: string): Heartbeat | null;
|
|
43
69
|
export declare function setTurnSummary(coordRoot: string, instanceId: string, summary: string): Heartbeat | null;
|
|
44
70
|
export declare function releaseClaim(coordRoot: string, instanceId: string, path: string): Heartbeat | null;
|
|
45
71
|
/** A heartbeat that actually dropped a path during a group unclaim. */
|
|
@@ -57,9 +83,8 @@ export interface GroupUnclaimHit {
|
|
|
57
83
|
* durable `claim.release` events — a file-only prune is silently reverted by
|
|
58
84
|
* the next projector replay.
|
|
59
85
|
*
|
|
60
|
-
*
|
|
61
|
-
* repo-relative entries
|
|
62
|
-
* so both sides are normalized before comparing — an exact-string match
|
|
86
|
+
* Tool payloads and release calls can supply either absolute-under-coordRoot or
|
|
87
|
+
* canonical repo-relative entries, so both sides are normalized before comparing. An exact-string match
|
|
63
88
|
* silently no-ops on the mixed-form case and the claim never releases.
|
|
64
89
|
*
|
|
65
90
|
* This is the Option B fix for post-commit's pid-map attribution hole: a
|
|
@@ -69,7 +94,9 @@ export interface GroupUnclaimHit {
|
|
|
69
94
|
export declare function groupUnclaim(coordRoot: string, groupId: string, path: string): GroupUnclaimHit[];
|
|
70
95
|
export declare function killHeartbeat(coordRoot: string, instanceId: string): boolean;
|
|
71
96
|
export declare function healPidmap(coordRoot: string, instanceId: string, pid: number): void;
|
|
72
|
-
export declare function healHeartbeat(coordRoot: string, instanceId: string, sessionId?: string, model?: string, adapter?: string
|
|
97
|
+
export declare function healHeartbeat(coordRoot: string, instanceId: string, sessionId?: string, model?: string, adapter?: string, opts?: {
|
|
98
|
+
forkedFrom?: string;
|
|
99
|
+
}): Heartbeat | null;
|
|
73
100
|
/**
|
|
74
101
|
* Register a workflow child in the coordination layer from the ENGINE side.
|
|
75
102
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"heartbeat-writer.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/heartbeat-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"heartbeat-writer.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/heartbeat-writer.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,16 +24,15 @@ function adapterOf(platform) {
|
|
|
24
24
|
return "codex";
|
|
25
25
|
return "claude-code";
|
|
26
26
|
}
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* the same platform value projection would have written. */
|
|
27
|
+
/** Preserve the canonical adapter id in heartbeat state. Mirrors
|
|
28
|
+
* heartbeat-projector.adapterToPlatform so a healed heartbeat carries the same
|
|
29
|
+
* platform value projection would have written. */
|
|
31
30
|
function adapterToPlatform(adapter) {
|
|
32
31
|
if (adapter === "cursor")
|
|
33
32
|
return "cursor";
|
|
34
33
|
if (adapter === "codex")
|
|
35
34
|
return "codex";
|
|
36
|
-
return "
|
|
35
|
+
return "claude-code";
|
|
37
36
|
}
|
|
38
37
|
/**
|
|
39
38
|
* Emit a canonical `health.*` event for an actual self-heal write. This is the
|
|
@@ -93,17 +92,44 @@ function mutate(coordRoot, instanceId, fn) {
|
|
|
93
92
|
export function setTask(coordRoot, instanceId, task) {
|
|
94
93
|
return mutate(coordRoot, instanceId, (hb) => {
|
|
95
94
|
const cleared = !task || task.length === 0;
|
|
95
|
+
// Name the session on its first NON-EMPTY declaration. Keyed on the
|
|
96
|
+
// suggested_session_name stamp, not task_updated_at, so a bare clear as
|
|
97
|
+
// the first call never burns the naming window. Subagents and workflow
|
|
98
|
+
// children have no human-owned tab to rename, so they are never named.
|
|
99
|
+
const humanFacing = hb.kind !== "subagent" && hb.kind !== "transient" && !hb.workflow_run_id;
|
|
100
|
+
const built = !cleared && !hb.suggested_session_name && humanFacing
|
|
101
|
+
? buildSuggestedName(hb.name ?? "unknown", [task])
|
|
102
|
+
: null;
|
|
96
103
|
return {
|
|
97
104
|
...hb,
|
|
98
105
|
task: cleared ? undefined : task,
|
|
99
106
|
task_updated_at: nowIsoSeconds(),
|
|
107
|
+
...(built ? { suggested_session_name: built.suggestedName } : {}),
|
|
100
108
|
};
|
|
101
109
|
});
|
|
102
110
|
}
|
|
103
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Build the copy-pasteable session name from the coord identity + the agent's
|
|
113
|
+
* description parts. Pure (no coord-state reads) so it's unit-testable; collapses
|
|
114
|
+
* internal whitespace and trims. Returns null when the description is empty.
|
|
115
|
+
*/
|
|
116
|
+
export function buildSuggestedName(agentName, descriptionParts) {
|
|
117
|
+
const description = descriptionParts.join(" ").replace(/\s+/g, " ").trim();
|
|
118
|
+
if (!description)
|
|
119
|
+
return null;
|
|
120
|
+
const name = agentName?.trim() || "unknown";
|
|
121
|
+
return { suggestedName: `Agent ${name} - ${description}`, description };
|
|
122
|
+
}
|
|
123
|
+
/** Stamp the sighting once the suggested name has been observed in assistant
|
|
124
|
+
* reply text; later turns skip the transcript scan. Records WHICH name was seen,
|
|
125
|
+
* because the skip is only valid for that one: if a later set-task mints a
|
|
126
|
+
* different suggested name, a bare "already seen" stamp would suppress the scan
|
|
127
|
+
* forever and leave the Stop-hook naming rule permanently unsatisfiable. */
|
|
128
|
+
export function stampSessionNameSeen(coordRoot, instanceId, name) {
|
|
104
129
|
return mutate(coordRoot, instanceId, (hb) => ({
|
|
105
130
|
...hb,
|
|
106
|
-
|
|
131
|
+
session_name_seen_at: hb.session_name_seen_at ?? nowIsoSeconds(),
|
|
132
|
+
...(name ? { session_name_seen_for: name } : {}),
|
|
107
133
|
}));
|
|
108
134
|
}
|
|
109
135
|
export function setTurnSummary(coordRoot, instanceId, summary) {
|
|
@@ -134,9 +160,8 @@ export function releaseClaim(coordRoot, instanceId, path) {
|
|
|
134
160
|
* durable `claim.release` events — a file-only prune is silently reverted by
|
|
135
161
|
* the next projector replay.
|
|
136
162
|
*
|
|
137
|
-
*
|
|
138
|
-
* repo-relative entries
|
|
139
|
-
* so both sides are normalized before comparing — an exact-string match
|
|
163
|
+
* Tool payloads and release calls can supply either absolute-under-coordRoot or
|
|
164
|
+
* canonical repo-relative entries, so both sides are normalized before comparing. An exact-string match
|
|
140
165
|
* silently no-ops on the mixed-form case and the claim never releases.
|
|
141
166
|
*
|
|
142
167
|
* This is the Option B fix for post-commit's pid-map attribution hole: a
|
|
@@ -203,7 +228,7 @@ export function healPidmap(coordRoot, instanceId, pid) {
|
|
|
203
228
|
const dir = join(coordRoot, ".harnery", "pid-map");
|
|
204
229
|
mkdirSync(dir, { recursive: true });
|
|
205
230
|
const hb = readHeartbeat(coordRoot, instanceId);
|
|
206
|
-
const platform = hb?.platform ?? "
|
|
231
|
+
const platform = hb?.platform ?? "claude-code";
|
|
207
232
|
const pmPath = join(dir, String(pid));
|
|
208
233
|
// Drift guard: only write + emit telemetry when the entry is missing or
|
|
209
234
|
// points at a different owner. Without this, a per-tool-call heal would
|
|
@@ -226,7 +251,7 @@ export function healPidmap(coordRoot, instanceId, pid) {
|
|
|
226
251
|
...(existingOwner ? { prior: existingOwner.slice(0, 8) } : {}),
|
|
227
252
|
});
|
|
228
253
|
}
|
|
229
|
-
export function healHeartbeat(coordRoot, instanceId, sessionId, model, adapter) {
|
|
254
|
+
export function healHeartbeat(coordRoot, instanceId, sessionId, model, adapter, opts) {
|
|
230
255
|
const path = heartbeatPath(coordRoot, instanceId);
|
|
231
256
|
if (existsSync(path)) {
|
|
232
257
|
// Already alive, return it.
|
|
@@ -240,8 +265,8 @@ export function healHeartbeat(coordRoot, instanceId, sessionId, model, adapter)
|
|
|
240
265
|
let agentId = "";
|
|
241
266
|
try {
|
|
242
267
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
243
|
-
const
|
|
244
|
-
const resolved = resolveName(coordRoot, instanceId, sessionId);
|
|
268
|
+
const names = require("./names.ts");
|
|
269
|
+
const resolved = names.resolveName(coordRoot, instanceId, sessionId);
|
|
245
270
|
if (resolved) {
|
|
246
271
|
name = resolved.name;
|
|
247
272
|
kind = resolved.kind;
|
|
@@ -249,6 +274,16 @@ export function healHeartbeat(coordRoot, instanceId, sessionId, model, adapter)
|
|
|
249
274
|
// name-history. Native subagents continue to use instance_id.
|
|
250
275
|
agentId = resolved.agent_id ?? (resolved.kind === "subagent" ? instanceId : "");
|
|
251
276
|
}
|
|
277
|
+
else if (!sessionId || sessionId === instanceId) {
|
|
278
|
+
// A main session with NO history at all never fired session.start —
|
|
279
|
+
// the CC fork flow (SessionStart fires under the parent's id before
|
|
280
|
+
// the fork id is minted), or a partially-wired adapter. Mint its pool
|
|
281
|
+
// name here instead of leaving a nameless heartbeat, and stamp the
|
|
282
|
+
// detected fork lineage while we're at it. A sessionId that differs
|
|
283
|
+
// from instanceId is left alone: that shape is a subagent whose parent
|
|
284
|
+
// is also unknown, and guessing "session" would be wrong.
|
|
285
|
+
name = names.assignName(coordRoot, instanceId, "session", opts?.forkedFrom ? { forkedFrom: opts.forkedFrom } : undefined);
|
|
286
|
+
}
|
|
252
287
|
}
|
|
253
288
|
catch {
|
|
254
289
|
/* names module unavailable, fall back to empty */
|
|
@@ -264,19 +299,21 @@ export function healHeartbeat(coordRoot, instanceId, sessionId, model, adapter)
|
|
|
264
299
|
started_at: now,
|
|
265
300
|
last_heartbeat: now,
|
|
266
301
|
files_touched: [],
|
|
267
|
-
// Default to
|
|
302
|
+
// Default to claude-code only when the caller can't tell us the adapter
|
|
268
303
|
// (e.g. manual `harn agents heal`). The live tool.pre_use heal threads the
|
|
269
304
|
// detected adapter so a pruned Cursor/Codex heartbeat is recreated with
|
|
270
|
-
// the correct platform instead of being mislabeled
|
|
305
|
+
// the correct platform instead of being mislabeled claude-code.
|
|
271
306
|
platform: adapterToPlatform(adapter),
|
|
272
307
|
};
|
|
273
308
|
atomicWrite(path, JSON.stringify(hb, null, 2));
|
|
274
309
|
// Write-only telemetry: only the actual-recreate branch reaches here (the
|
|
275
310
|
// already-alive case returned above), so this records exactly the heals that
|
|
276
|
-
// happened.
|
|
311
|
+
// happened. Recorded fork lineage rides the event too, so derived readers
|
|
312
|
+
// rebuilding from the ledger converge with .name-history.
|
|
277
313
|
emitHealthHeal(coordRoot, "health.heartbeat_heal", instanceId, hb, {
|
|
278
314
|
reason: "missing",
|
|
279
315
|
kind: "heartbeat",
|
|
316
|
+
...(opts?.forkedFrom ? { forked_from: opts.forkedFrom } : {}),
|
|
280
317
|
});
|
|
281
318
|
return hb;
|
|
282
319
|
}
|
|
@@ -31,6 +31,10 @@ export interface NameHistoryRow {
|
|
|
31
31
|
/** Audit marker distinguishing an explicit role adoption from pool assignment. */
|
|
32
32
|
source?: "pool" | "identity.assume";
|
|
33
33
|
previous_name?: string;
|
|
34
|
+
/** Instance this session was forked/branched from (recorded fork lineage).
|
|
35
|
+
* Stamped only on the row that first assigns this instance, when the adapter
|
|
36
|
+
* layer detected or supplied a parent conversation. */
|
|
37
|
+
forked_from?: string;
|
|
34
38
|
}
|
|
35
39
|
/**
|
|
36
40
|
* Resolve (name, kind) for an existing owner without consuming a counter
|
|
@@ -66,5 +70,22 @@ export declare function recordNameAssumption(coordRoot: string, instanceId: stri
|
|
|
66
70
|
* Assign a name to <instanceId> with the given <kind>. Counter-consuming when
|
|
67
71
|
* the owner is new. Idempotent: returns existing name on resume.
|
|
68
72
|
*/
|
|
69
|
-
export declare function assignName(coordRoot: string, instanceId: string, kind: NameKind
|
|
73
|
+
export declare function assignName(coordRoot: string, instanceId: string, kind: NameKind, opts?: {
|
|
74
|
+
forkedFrom?: string;
|
|
75
|
+
}): string;
|
|
76
|
+
/** One step of recorded fork lineage: the latest row for <instanceId> that
|
|
77
|
+
* carries `forked_from` (latest-row-wins, matching resolveName). */
|
|
78
|
+
export declare function readForkParent(coordRoot: string, instanceId: string): {
|
|
79
|
+
instance_id: string;
|
|
80
|
+
name: string | null;
|
|
81
|
+
} | null;
|
|
82
|
+
/**
|
|
83
|
+
* Full recorded fork ancestry for <instanceId>, nearest ancestor first, each
|
|
84
|
+
* with its latest resolved name. Depth-capped and cycle-guarded: lineage is
|
|
85
|
+
* append-only operational data, not something to trust unboundedly.
|
|
86
|
+
*/
|
|
87
|
+
export declare function resolveForkAncestry(coordRoot: string, instanceId: string): Array<{
|
|
88
|
+
instance_id: string;
|
|
89
|
+
name: string | null;
|
|
90
|
+
}>;
|
|
70
91
|
//# sourceMappingURL=names.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/names.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,qBAAqB;AACrB,eAAO,MAAM,WAAW,y5EA+Qd,CAAC;AAMX,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;AAE5D,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/names.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,qBAAqB;AACrB,eAAO,MAAM,WAAW,y5EA+Qd,CAAC;AAMX,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;AAE5D,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;2DAEuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAsCD;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA4B5D;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACrE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,QAAoB,GACzB,oBAAoB,CAoBtB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,QAAQ,EACd,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7B,MAAM,CA0BR;AAED;oEACoE;AACpE,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAUrD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,KAAK,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAYrD"}
|
|
@@ -389,8 +389,10 @@ export function recordNameAssumption(coordRoot, instanceId, name, agentId, kind
|
|
|
389
389
|
* Assign a name to <instanceId> with the given <kind>. Counter-consuming when
|
|
390
390
|
* the owner is new. Idempotent: returns existing name on resume.
|
|
391
391
|
*/
|
|
392
|
-
export function assignName(coordRoot, instanceId, kind) {
|
|
393
|
-
// Check 1: existing history row → original name.
|
|
392
|
+
export function assignName(coordRoot, instanceId, kind, opts) {
|
|
393
|
+
// Check 1: existing history row → original name. A resume re-enters here,
|
|
394
|
+
// which also makes fork stamping naturally idempotent: lineage lands only on
|
|
395
|
+
// the row that first assigns the instance.
|
|
394
396
|
const existing = resolveName(coordRoot, instanceId);
|
|
395
397
|
if (existing)
|
|
396
398
|
return existing.name;
|
|
@@ -404,12 +406,48 @@ export function assignName(coordRoot, instanceId, kind) {
|
|
|
404
406
|
}
|
|
405
407
|
const name = COORD_NAMES[counter % 260];
|
|
406
408
|
atomicWrite(cPath, String(counter + 1));
|
|
409
|
+
const forkedFrom = opts?.forkedFrom;
|
|
407
410
|
appendHistory(coordRoot, {
|
|
408
411
|
instance_id: instanceId,
|
|
409
412
|
name,
|
|
410
413
|
kind,
|
|
411
414
|
source: "pool",
|
|
415
|
+
...(forkedFrom && forkedFrom !== instanceId ? { forked_from: forkedFrom } : {}),
|
|
412
416
|
ts: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
413
417
|
});
|
|
414
418
|
return name;
|
|
415
419
|
}
|
|
420
|
+
/** One step of recorded fork lineage: the latest row for <instanceId> that
|
|
421
|
+
* carries `forked_from` (latest-row-wins, matching resolveName). */
|
|
422
|
+
export function readForkParent(coordRoot, instanceId) {
|
|
423
|
+
const history = readHistory(coordRoot);
|
|
424
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
425
|
+
const row = history[i];
|
|
426
|
+
if (row.instance_id !== instanceId)
|
|
427
|
+
continue;
|
|
428
|
+
if (!row.forked_from)
|
|
429
|
+
return null;
|
|
430
|
+
const parent = resolveName(coordRoot, row.forked_from);
|
|
431
|
+
return { instance_id: row.forked_from, name: parent?.name ?? null };
|
|
432
|
+
}
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Full recorded fork ancestry for <instanceId>, nearest ancestor first, each
|
|
437
|
+
* with its latest resolved name. Depth-capped and cycle-guarded: lineage is
|
|
438
|
+
* append-only operational data, not something to trust unboundedly.
|
|
439
|
+
*/
|
|
440
|
+
export function resolveForkAncestry(coordRoot, instanceId) {
|
|
441
|
+
const out = [];
|
|
442
|
+
const seen = new Set([instanceId]);
|
|
443
|
+
let cursor = instanceId;
|
|
444
|
+
for (let depth = 0; depth < 20; depth++) {
|
|
445
|
+
const parent = readForkParent(coordRoot, cursor);
|
|
446
|
+
if (!parent || seen.has(parent.instance_id))
|
|
447
|
+
break;
|
|
448
|
+
out.push(parent);
|
|
449
|
+
seen.add(parent.instance_id);
|
|
450
|
+
cursor = parent.instance_id;
|
|
451
|
+
}
|
|
452
|
+
return out;
|
|
453
|
+
}
|