harnery 0.31.6 → 0.33.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/README.md +2 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +5 -1
- package/dist/commands/agents.d.ts +0 -9
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +280 -94
- 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/claude-desktop.d.ts +2 -2
- package/dist/commands/claude-desktop.d.ts.map +1 -1
- package/dist/commands/claude-desktop.js +74 -3
- 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 +4 -2
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +141 -19
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +26 -5
- package/dist/commands/instructions.d.ts +4 -0
- package/dist/commands/instructions.d.ts.map +1 -0
- package/dist/commands/instructions.js +100 -0
- package/dist/commands/presence.d.ts.map +1 -1
- package/dist/commands/presence.js +4 -1
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +2 -3
- package/dist/core/agents/canonical-emit.d.ts +2 -3
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +6 -5
- package/dist/core/agents/cli.js +128 -89
- package/dist/core/agents/coord-client.d.ts +18 -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/index.d.ts +1 -0
- package/dist/core/agents/index.d.ts.map +1 -1
- package/dist/core/agents/index.js +1 -0
- package/dist/core/agents/render/prompt-context.d.ts +12 -5
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +70 -16
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +23 -4
- 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 +14 -0
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-projector.js +98 -1
- package/dist/core/agents/state/heartbeat-writer.d.ts +47 -5
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +86 -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/agents/state/session-state.d.ts +44 -0
- package/dist/core/agents/state/session-state.d.ts.map +1 -0
- package/dist/core/agents/state/session-state.js +81 -0
- 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/events.d.ts.map +1 -1
- package/dist/core/hooks/adapter/events.js +1 -0
- 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/parse.d.ts +1 -1
- package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
- package/dist/core/hooks/adapter/parse.js +2 -0
- 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 +258 -32
- package/dist/core/hooks/codex-authorization.d.ts +30 -0
- package/dist/core/hooks/codex-authorization.d.ts.map +1 -0
- package/dist/core/hooks/codex-authorization.js +199 -0
- 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 +57 -1
- 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/presence/blob.d.ts +4 -0
- package/dist/core/presence/blob.d.ts.map +1 -1
- package/dist/core/presence/blob.js +12 -0
- package/dist/core/presence/index.js +15 -1
- 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/claude-desktop.d.ts +47 -0
- package/dist/lib/claude-desktop.d.ts.map +1 -1
- package/dist/lib/claude-desktop.js +169 -1
- 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/bundle.d.ts +33 -0
- package/dist/lib/instructions/bundle.d.ts.map +1 -0
- package/dist/lib/instructions/bundle.js +149 -0
- 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 +2 -2
- package/schemas/config.schema.json +32 -0
- package/src/commander.ts +5 -1
- package/src/commands/agents.ts +341 -113
- 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/claude-desktop.ts +106 -5
- package/src/commands/deinit.ts +7 -1
- package/src/commands/docs.ts +56 -0
- package/src/commands/doctor.ts +155 -21
- package/src/commands/init.ts +28 -9
- package/src/commands/instructions.ts +129 -0
- package/src/commands/presence.ts +7 -1
- package/src/commands/tunnel.ts +2 -5
- package/src/core/agents/canonical-emit.ts +7 -6
- package/src/core/agents/cli.ts +143 -89
- package/src/core/agents/coord-client.ts +21 -5
- package/src/core/agents/finalization.ts +595 -0
- package/src/core/agents/git-hook.ts +126 -0
- package/src/core/agents/index.ts +1 -0
- package/src/core/agents/render/prompt-context.ts +96 -16
- package/src/core/agents/render/session-context.ts +29 -4
- 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 +108 -1
- package/src/core/agents/state/heartbeat-writer.ts +129 -16
- package/src/core/agents/state/names.ts +54 -2
- package/src/core/agents/state/session-state.ts +131 -0
- 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/events.ts +1 -0
- package/src/core/hooks/adapter/output.ts +9 -4
- package/src/core/hooks/adapter/parse.ts +3 -0
- package/src/core/hooks/adapter/wiring.ts +43 -0
- package/src/core/hooks/cli.ts +296 -27
- package/src/core/hooks/codex-authorization.ts +253 -0
- 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 +71 -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/presence/blob.ts +18 -0
- package/src/core/presence/index.ts +18 -1
- 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/claude-desktop.ts +232 -1
- 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/bundle.ts +199 -0
- package/src/lib/instructions/git-hooks.ts +259 -0
- package/src/lib/instructions/splice.ts +41 -11
|
@@ -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. */
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* through the canonical event stream) survive each projector run.
|
|
10
10
|
*/
|
|
11
11
|
import type { CanonicalEvent } from "../events/consume.js";
|
|
12
|
+
import { type AgentActivity, type TaskState } from "./session-state.js";
|
|
12
13
|
export interface V2Heartbeat {
|
|
13
14
|
instance_id: string;
|
|
14
15
|
session_id: string;
|
|
@@ -17,6 +18,10 @@ export interface V2Heartbeat {
|
|
|
17
18
|
name?: string;
|
|
18
19
|
kind?: "session" | "subagent" | "transient";
|
|
19
20
|
model?: string;
|
|
21
|
+
instruction_bundle_id?: string;
|
|
22
|
+
instruction_source_id?: string;
|
|
23
|
+
instruction_profile_root?: string;
|
|
24
|
+
instruction_component_count?: number;
|
|
20
25
|
platform?: string;
|
|
21
26
|
subagent_call_id?: string;
|
|
22
27
|
parent_session_id?: string;
|
|
@@ -30,6 +35,15 @@ export interface V2Heartbeat {
|
|
|
30
35
|
last_tool_at?: string;
|
|
31
36
|
task?: string;
|
|
32
37
|
task_updated_at?: string;
|
|
38
|
+
activity?: AgentActivity;
|
|
39
|
+
activity_updated_at?: string;
|
|
40
|
+
activity_source?: string;
|
|
41
|
+
task_state?: TaskState;
|
|
42
|
+
task_state_updated_at?: string;
|
|
43
|
+
task_state_reason?: string;
|
|
44
|
+
suggested_session_name?: string;
|
|
45
|
+
session_name_seen_at?: string;
|
|
46
|
+
session_name_seen_for?: string;
|
|
33
47
|
last_status_at?: string;
|
|
34
48
|
presence?: "mobile" | "office";
|
|
35
49
|
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;
|
|
1
|
+
{"version":3,"file":"heartbeat-projector.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/state/heartbeat-projector.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { dirname, join } from "node:path";
|
|
13
|
+
import { applySessionStateEvent } from "./session-state.js";
|
|
13
14
|
export function projectHeartbeats(coordRoot, events) {
|
|
14
15
|
const perOwner = {};
|
|
15
16
|
// Events that must NOT seed a heartbeat for an owner with no live file.
|
|
@@ -122,6 +123,13 @@ function apply(hb, ev, coordRoot) {
|
|
|
122
123
|
hb.v2_meta.last_projected = new Date().toISOString();
|
|
123
124
|
if (ev.turn_id)
|
|
124
125
|
hb.last_turn_id = ev.turn_id;
|
|
126
|
+
const sessionState = applySessionStateEvent(hb, ev);
|
|
127
|
+
hb.activity = sessionState.activity;
|
|
128
|
+
hb.activity_updated_at = sessionState.activity_updated_at;
|
|
129
|
+
hb.activity_source = sessionState.activity_source;
|
|
130
|
+
hb.task_state = sessionState.task_state;
|
|
131
|
+
hb.task_state_updated_at = sessionState.task_state_updated_at;
|
|
132
|
+
hb.task_state_reason = sessionState.task_state_reason;
|
|
125
133
|
const d = ev.data;
|
|
126
134
|
switch (ev.event_type) {
|
|
127
135
|
case "session.start":
|
|
@@ -131,6 +139,19 @@ function apply(hb, ev, coordRoot) {
|
|
|
131
139
|
const model = pickStr(d, "model");
|
|
132
140
|
if (model)
|
|
133
141
|
hb.model = model;
|
|
142
|
+
const instructionBundleId = pickStr(d, "instruction_bundle_id");
|
|
143
|
+
if (instructionBundleId)
|
|
144
|
+
hb.instruction_bundle_id = instructionBundleId;
|
|
145
|
+
const instructionSourceId = pickStr(d, "instruction_source_id");
|
|
146
|
+
if (instructionSourceId)
|
|
147
|
+
hb.instruction_source_id = instructionSourceId;
|
|
148
|
+
const instructionProfileRoot = pickStr(d, "instruction_profile_root");
|
|
149
|
+
if (instructionProfileRoot)
|
|
150
|
+
hb.instruction_profile_root = instructionProfileRoot;
|
|
151
|
+
const instructionComponentCount = pickNum(d, "instruction_component_count");
|
|
152
|
+
if (instructionComponentCount !== undefined) {
|
|
153
|
+
hb.instruction_component_count = instructionComponentCount;
|
|
154
|
+
}
|
|
134
155
|
const platform = pickStr(d, "platform") ?? adapterToPlatform(ev.adapter);
|
|
135
156
|
hb.platform = platform;
|
|
136
157
|
const name = pickStr(d, "name");
|
|
@@ -197,6 +218,18 @@ function apply(hb, ev, coordRoot) {
|
|
|
197
218
|
case "turn.stop":
|
|
198
219
|
hb.last_turn_stop_at = ev.ts;
|
|
199
220
|
hb.last_turn_status_box_present = pickBool(d, "status_box_present");
|
|
221
|
+
// Rebuild fidelity for the naming ritual: the live stamp is written by
|
|
222
|
+
// the Stop hook (stampSessionNameSeen); reproduce it from the event so a
|
|
223
|
+
// full projector rebuild doesn't re-open a satisfied naming window.
|
|
224
|
+
if (d.session_name_present === true) {
|
|
225
|
+
hb.session_name_seen_at ??= ev.ts;
|
|
226
|
+
// Attribute the sighting to the name the scan actually covered. Reading
|
|
227
|
+
// the name current at this point in the replay instead re-attributed an
|
|
228
|
+
// old sighting to a re-minted name, which faked "already seen" for a
|
|
229
|
+
// name no reply had shown. An event without the field predates it, so
|
|
230
|
+
// leave the attribution unset and let the next stop re-scan.
|
|
231
|
+
hb.session_name_seen_for = pickStr(d, "session_name_present_for");
|
|
232
|
+
}
|
|
200
233
|
{
|
|
201
234
|
const summary = pickStr(d, "turn_summary");
|
|
202
235
|
if (summary) {
|
|
@@ -254,6 +287,25 @@ function apply(hb, ev, coordRoot) {
|
|
|
254
287
|
hb.task = task;
|
|
255
288
|
}
|
|
256
289
|
hb.task_updated_at = ev.ts;
|
|
290
|
+
// Rebuild fidelity: the naming call carries the name it produced.
|
|
291
|
+
const suggested = pickStr(d, "suggested_session_name");
|
|
292
|
+
if (suggested && !hb.suggested_session_name) {
|
|
293
|
+
hb.suggested_session_name = suggested;
|
|
294
|
+
}
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
case "state.task_state": {
|
|
298
|
+
const suggested = pickStr(d, "suggested_session_name");
|
|
299
|
+
if (suggested && suggested !== hb.suggested_session_name) {
|
|
300
|
+
// Older heartbeats predate session_name_seen_for. A lifecycle re-mint
|
|
301
|
+
// still needs a mismatch the prompt and Stop paths can enforce, so
|
|
302
|
+
// attribute the prior title before replacing it. Ordinary legacy
|
|
303
|
+
// heartbeats remain quiet until a real lifecycle event changes them.
|
|
304
|
+
if (!hb.session_name_seen_for && hb.suggested_session_name) {
|
|
305
|
+
hb.session_name_seen_for = hb.suggested_session_name;
|
|
306
|
+
}
|
|
307
|
+
hb.suggested_session_name = suggested;
|
|
308
|
+
}
|
|
257
309
|
break;
|
|
258
310
|
}
|
|
259
311
|
case "state.status_checked":
|
|
@@ -274,6 +326,20 @@ function apply(hb, ev, coordRoot) {
|
|
|
274
326
|
hb.presence = to;
|
|
275
327
|
break;
|
|
276
328
|
}
|
|
329
|
+
case "claim.acquire": {
|
|
330
|
+
const path = pickStr(d, "path");
|
|
331
|
+
const mode = pickStr(d, "mode");
|
|
332
|
+
if (path && mode === "write") {
|
|
333
|
+
const canonical = path.startsWith(`${coordRoot}/`)
|
|
334
|
+
? path.slice(coordRoot.length + 1)
|
|
335
|
+
: path;
|
|
336
|
+
if (!hb.files_touched)
|
|
337
|
+
hb.files_touched = [];
|
|
338
|
+
if (!hb.files_touched.includes(canonical))
|
|
339
|
+
hb.files_touched.push(canonical);
|
|
340
|
+
}
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
277
343
|
case "claim.release": {
|
|
278
344
|
const path = pickStr(d, "path");
|
|
279
345
|
if (path && hb.files_touched) {
|
|
@@ -292,7 +358,7 @@ function apply(hb, ev, coordRoot) {
|
|
|
292
358
|
}
|
|
293
359
|
function adapterToPlatform(adapter) {
|
|
294
360
|
if (adapter === "claude-code")
|
|
295
|
-
return "
|
|
361
|
+
return "claude-code";
|
|
296
362
|
if (adapter === "cursor")
|
|
297
363
|
return "cursor";
|
|
298
364
|
if (adapter === "codex")
|
|
@@ -433,6 +499,10 @@ function writeHeartbeat(coordRoot, instanceId, hb) {
|
|
|
433
499
|
setIfDefined(merged, "workflow_run_id", hb.workflow_run_id);
|
|
434
500
|
setIfDefined(merged, "workflow_agent_id", hb.workflow_agent_id);
|
|
435
501
|
setIfDefined(merged, "model", hb.model);
|
|
502
|
+
setIfDefined(merged, "instruction_bundle_id", hb.instruction_bundle_id);
|
|
503
|
+
setIfDefined(merged, "instruction_source_id", hb.instruction_source_id);
|
|
504
|
+
setIfDefined(merged, "instruction_profile_root", hb.instruction_profile_root);
|
|
505
|
+
setIfDefined(merged, "instruction_component_count", hb.instruction_component_count);
|
|
436
506
|
setIfDefined(merged, "platform", hb.platform);
|
|
437
507
|
setIfDefined(merged, "started_at", hb.started_at);
|
|
438
508
|
// files_touched is a required-array invariant for every reader
|
|
@@ -446,6 +516,29 @@ function writeHeartbeat(coordRoot, instanceId, hb) {
|
|
|
446
516
|
setIfDefined(merged, "last_tool_at", hb.last_tool_at);
|
|
447
517
|
setIfDefined(merged, "task", hb.task);
|
|
448
518
|
setIfDefined(merged, "task_updated_at", hb.task_updated_at);
|
|
519
|
+
setIfDefined(merged, "activity", hb.activity);
|
|
520
|
+
setIfDefined(merged, "activity_updated_at", hb.activity_updated_at);
|
|
521
|
+
setIfDefined(merged, "activity_source", hb.activity_source);
|
|
522
|
+
setIfDefined(merged, "task_state", hb.task_state);
|
|
523
|
+
setIfDefined(merged, "task_state_updated_at", hb.task_state_updated_at);
|
|
524
|
+
if (hb.task_state_reason) {
|
|
525
|
+
merged.task_state_reason = hb.task_state_reason;
|
|
526
|
+
}
|
|
527
|
+
else if (hb.task_state) {
|
|
528
|
+
delete merged.task_state_reason;
|
|
529
|
+
}
|
|
530
|
+
setIfDefined(merged, "suggested_session_name", hb.suggested_session_name);
|
|
531
|
+
setIfDefined(merged, "session_name_seen_at", hb.session_name_seen_at);
|
|
532
|
+
if (hb.session_name_seen_for) {
|
|
533
|
+
merged.session_name_seen_for = hb.session_name_seen_for;
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
// Unlike most projected fields, an absent attribution is meaningful: a
|
|
537
|
+
// legacy sighting cannot prove which suggested name it covered. Remove a
|
|
538
|
+
// stale value inherited through the additive merge so the next stop
|
|
539
|
+
// scans the current name instead of treating it as already satisfied.
|
|
540
|
+
delete merged.session_name_seen_for;
|
|
541
|
+
}
|
|
449
542
|
setIfDefined(merged, "last_status_at", hb.last_status_at);
|
|
450
543
|
setIfDefined(merged, "turn_summary", hb.turn_summary);
|
|
451
544
|
setIfDefined(merged, "turn_summary_updated_at", hb.turn_summary_updated_at);
|
|
@@ -482,3 +575,7 @@ function pickBool(o, k) {
|
|
|
482
575
|
const v = o[k];
|
|
483
576
|
return typeof v === "boolean" ? v : undefined;
|
|
484
577
|
}
|
|
578
|
+
function pickNum(o, k) {
|
|
579
|
+
const v = o[k];
|
|
580
|
+
return typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
|
581
|
+
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* Owner identity invariant: every action operates on `.harnery/active/<instance_id>.json`
|
|
13
13
|
* (the file IS the heartbeat). No mutations happen elsewhere.
|
|
14
14
|
*/
|
|
15
|
+
import { type AgentActivity, type SessionStateEvidenceEvent, type TaskState } from "./session-state.js";
|
|
15
16
|
export interface Heartbeat {
|
|
16
17
|
schema_version?: number;
|
|
17
18
|
instance_id: string;
|
|
@@ -27,6 +28,23 @@ export interface Heartbeat {
|
|
|
27
28
|
files_touched: string[];
|
|
28
29
|
task?: string;
|
|
29
30
|
task_updated_at?: string | null;
|
|
31
|
+
activity?: AgentActivity;
|
|
32
|
+
activity_updated_at?: string;
|
|
33
|
+
activity_source?: string;
|
|
34
|
+
task_state?: TaskState;
|
|
35
|
+
task_state_updated_at?: string;
|
|
36
|
+
task_state_reason?: string;
|
|
37
|
+
/** Session name built on the first non-empty set-task (never rebuilt). Its
|
|
38
|
+
* presence is the "this session has been named" signal the prompt-context
|
|
39
|
+
* nudge and the Stop-hook naming rule key on. */
|
|
40
|
+
suggested_session_name?: string;
|
|
41
|
+
/** Stamped by turn.stop once the suggested name is seen in assistant reply
|
|
42
|
+
* text, ending the per-turn transcript scan. */
|
|
43
|
+
session_name_seen_at?: string;
|
|
44
|
+
/** WHICH name that sighting was for. The scan is skipped only while this
|
|
45
|
+
* matches the current suggested name, so a re-minted name is detectable
|
|
46
|
+
* again rather than being suppressed by the earlier sighting. */
|
|
47
|
+
session_name_seen_for?: string;
|
|
30
48
|
last_status_at?: string;
|
|
31
49
|
turn_summary?: string | null;
|
|
32
50
|
turn_summary_updated_at?: string | null;
|
|
@@ -35,11 +53,34 @@ export interface Heartbeat {
|
|
|
35
53
|
last_tool_at?: string;
|
|
36
54
|
current_turn_id?: string;
|
|
37
55
|
parent_instance_id?: string;
|
|
56
|
+
workflow_run_id?: string;
|
|
38
57
|
[extra: string]: unknown;
|
|
39
58
|
}
|
|
40
59
|
export declare function readHeartbeat(coordRoot: string, instanceId: string): Heartbeat | null;
|
|
60
|
+
/** Apply one evidence event to the live state fields without draining the
|
|
61
|
+
* global event stream. This keeps permission prompts current while avoiding
|
|
62
|
+
* unrelated projector side effects before a tool authorization verdict. */
|
|
63
|
+
export declare function stampSessionStateEvent(coordRoot: string, instanceId: string, event: Omit<SessionStateEvidenceEvent, "data"> & {
|
|
64
|
+
data: unknown;
|
|
65
|
+
}): Heartbeat | null;
|
|
41
66
|
export declare function setTask(coordRoot: string, instanceId: string, task: string): Heartbeat | null;
|
|
42
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Build the copy-pasteable session name from the coord identity + the agent's
|
|
69
|
+
* description parts. Pure (no coord-state reads) so it's unit-testable; collapses
|
|
70
|
+
* internal whitespace and trims. Returns null when the description is empty.
|
|
71
|
+
*/
|
|
72
|
+
export declare function buildSuggestedName(agentName: string, descriptionParts: string[]): {
|
|
73
|
+
suggestedName: string;
|
|
74
|
+
description: string;
|
|
75
|
+
} | null;
|
|
76
|
+
/** Build the operator-facing title projected from an explicit task lifecycle. */
|
|
77
|
+
export declare function buildLifecycleSuggestedName(agentName: string, task: string | undefined, state: TaskState): string | null;
|
|
78
|
+
/** Stamp the sighting once the suggested name has been observed in assistant
|
|
79
|
+
* reply text; later turns skip the transcript scan. Records WHICH name was seen,
|
|
80
|
+
* because the skip is only valid for that one: if a later set-task mints a
|
|
81
|
+
* different suggested name, a bare "already seen" stamp would suppress the scan
|
|
82
|
+
* forever and leave the Stop-hook naming rule permanently unsatisfiable. */
|
|
83
|
+
export declare function stampSessionNameSeen(coordRoot: string, instanceId: string, name?: string): Heartbeat | null;
|
|
43
84
|
export declare function setTurnSummary(coordRoot: string, instanceId: string, summary: string): Heartbeat | null;
|
|
44
85
|
export declare function releaseClaim(coordRoot: string, instanceId: string, path: string): Heartbeat | null;
|
|
45
86
|
/** A heartbeat that actually dropped a path during a group unclaim. */
|
|
@@ -57,9 +98,8 @@ export interface GroupUnclaimHit {
|
|
|
57
98
|
* durable `claim.release` events — a file-only prune is silently reverted by
|
|
58
99
|
* the next projector replay.
|
|
59
100
|
*
|
|
60
|
-
*
|
|
61
|
-
* repo-relative entries
|
|
62
|
-
* so both sides are normalized before comparing — an exact-string match
|
|
101
|
+
* Tool payloads and release calls can supply either absolute-under-coordRoot or
|
|
102
|
+
* canonical repo-relative entries, so both sides are normalized before comparing. An exact-string match
|
|
63
103
|
* silently no-ops on the mixed-form case and the claim never releases.
|
|
64
104
|
*
|
|
65
105
|
* This is the Option B fix for post-commit's pid-map attribution hole: a
|
|
@@ -69,7 +109,9 @@ export interface GroupUnclaimHit {
|
|
|
69
109
|
export declare function groupUnclaim(coordRoot: string, groupId: string, path: string): GroupUnclaimHit[];
|
|
70
110
|
export declare function killHeartbeat(coordRoot: string, instanceId: string): boolean;
|
|
71
111
|
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
|
|
112
|
+
export declare function healHeartbeat(coordRoot: string, instanceId: string, sessionId?: string, model?: string, adapter?: string, opts?: {
|
|
113
|
+
forkedFrom?: string;
|
|
114
|
+
}): Heartbeat | null;
|
|
73
115
|
/**
|
|
74
116
|
* Register a workflow child in the coordination layer from the ENGINE side.
|
|
75
117
|
*
|
|
@@ -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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"}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
16
16
|
import { dirname, join } from "node:path";
|
|
17
17
|
import { emit } from "../events/emit.js";
|
|
18
|
+
import { applySessionStateEvent, } from "./session-state.js";
|
|
18
19
|
/** Inline adapter normalizer (mirrors canonical-emit.normalizeAdapter), kept
|
|
19
20
|
* local so the writer's only cross-module dep is the in-process emit(). */
|
|
20
21
|
function adapterOf(platform) {
|
|
@@ -24,16 +25,15 @@ function adapterOf(platform) {
|
|
|
24
25
|
return "codex";
|
|
25
26
|
return "claude-code";
|
|
26
27
|
}
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* the same platform value projection would have written. */
|
|
28
|
+
/** Preserve the canonical adapter id in heartbeat state. Mirrors
|
|
29
|
+
* heartbeat-projector.adapterToPlatform so a healed heartbeat carries the same
|
|
30
|
+
* platform value projection would have written. */
|
|
31
31
|
function adapterToPlatform(adapter) {
|
|
32
32
|
if (adapter === "cursor")
|
|
33
33
|
return "cursor";
|
|
34
34
|
if (adapter === "codex")
|
|
35
35
|
return "codex";
|
|
36
|
-
return "
|
|
36
|
+
return "claude-code";
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
39
|
* Emit a canonical `health.*` event for an actual self-heal write. This is the
|
|
@@ -81,6 +81,26 @@ export function readHeartbeat(coordRoot, instanceId) {
|
|
|
81
81
|
return null;
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
+
/** Apply one evidence event to the live state fields without draining the
|
|
85
|
+
* global event stream. This keeps permission prompts current while avoiding
|
|
86
|
+
* unrelated projector side effects before a tool authorization verdict. */
|
|
87
|
+
export function stampSessionStateEvent(coordRoot, instanceId, event) {
|
|
88
|
+
return mutate(coordRoot, instanceId, (heartbeat) => {
|
|
89
|
+
const data = typeof event.data === "object" && event.data !== null && !Array.isArray(event.data)
|
|
90
|
+
? event.data
|
|
91
|
+
: {};
|
|
92
|
+
const state = applySessionStateEvent(heartbeat, { ...event, data });
|
|
93
|
+
return {
|
|
94
|
+
...heartbeat,
|
|
95
|
+
activity: state.activity,
|
|
96
|
+
activity_updated_at: state.activity_updated_at,
|
|
97
|
+
activity_source: state.activity_source,
|
|
98
|
+
task_state: state.task_state,
|
|
99
|
+
task_state_updated_at: state.task_state_updated_at,
|
|
100
|
+
task_state_reason: state.task_state_reason,
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
}
|
|
84
104
|
function mutate(coordRoot, instanceId, fn) {
|
|
85
105
|
const hb = readHeartbeat(coordRoot, instanceId);
|
|
86
106
|
if (!hb)
|
|
@@ -93,17 +113,55 @@ function mutate(coordRoot, instanceId, fn) {
|
|
|
93
113
|
export function setTask(coordRoot, instanceId, task) {
|
|
94
114
|
return mutate(coordRoot, instanceId, (hb) => {
|
|
95
115
|
const cleared = !task || task.length === 0;
|
|
116
|
+
// Name the session on its first NON-EMPTY declaration. Keyed on the
|
|
117
|
+
// suggested_session_name stamp, not task_updated_at, so a bare clear as
|
|
118
|
+
// the first call never burns the naming window. Subagents and workflow
|
|
119
|
+
// children have no human-owned tab to rename, so they are never named.
|
|
120
|
+
const humanFacing = hb.kind !== "subagent" && hb.kind !== "transient" && !hb.workflow_run_id;
|
|
121
|
+
const built = !cleared && !hb.suggested_session_name && humanFacing
|
|
122
|
+
? buildSuggestedName(hb.name ?? "unknown", [task])
|
|
123
|
+
: null;
|
|
96
124
|
return {
|
|
97
125
|
...hb,
|
|
98
126
|
task: cleared ? undefined : task,
|
|
99
127
|
task_updated_at: nowIsoSeconds(),
|
|
128
|
+
...(built ? { suggested_session_name: built.suggestedName } : {}),
|
|
100
129
|
};
|
|
101
130
|
});
|
|
102
131
|
}
|
|
103
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Build the copy-pasteable session name from the coord identity + the agent's
|
|
134
|
+
* description parts. Pure (no coord-state reads) so it's unit-testable; collapses
|
|
135
|
+
* internal whitespace and trims. Returns null when the description is empty.
|
|
136
|
+
*/
|
|
137
|
+
export function buildSuggestedName(agentName, descriptionParts) {
|
|
138
|
+
const description = descriptionParts.join(" ").replace(/\s+/g, " ").trim();
|
|
139
|
+
if (!description)
|
|
140
|
+
return null;
|
|
141
|
+
const name = agentName?.trim() || "unknown";
|
|
142
|
+
return { suggestedName: `Agent ${name} - ${description}`, description };
|
|
143
|
+
}
|
|
144
|
+
/** Build the operator-facing title projected from an explicit task lifecycle. */
|
|
145
|
+
export function buildLifecycleSuggestedName(agentName, task, state) {
|
|
146
|
+
const built = buildSuggestedName(agentName, task ? [task] : []);
|
|
147
|
+
if (!built)
|
|
148
|
+
return null;
|
|
149
|
+
if (state === "blocked")
|
|
150
|
+
return `[BLOCKED] - ${built.suggestedName}`;
|
|
151
|
+
if (state === "done")
|
|
152
|
+
return `[DONE] - ${built.suggestedName}`;
|
|
153
|
+
return built.suggestedName;
|
|
154
|
+
}
|
|
155
|
+
/** Stamp the sighting once the suggested name has been observed in assistant
|
|
156
|
+
* reply text; later turns skip the transcript scan. Records WHICH name was seen,
|
|
157
|
+
* because the skip is only valid for that one: if a later set-task mints a
|
|
158
|
+
* different suggested name, a bare "already seen" stamp would suppress the scan
|
|
159
|
+
* forever and leave the Stop-hook naming rule permanently unsatisfiable. */
|
|
160
|
+
export function stampSessionNameSeen(coordRoot, instanceId, name) {
|
|
104
161
|
return mutate(coordRoot, instanceId, (hb) => ({
|
|
105
162
|
...hb,
|
|
106
|
-
|
|
163
|
+
session_name_seen_at: hb.session_name_seen_at ?? nowIsoSeconds(),
|
|
164
|
+
...(name ? { session_name_seen_for: name } : {}),
|
|
107
165
|
}));
|
|
108
166
|
}
|
|
109
167
|
export function setTurnSummary(coordRoot, instanceId, summary) {
|
|
@@ -134,9 +192,8 @@ export function releaseClaim(coordRoot, instanceId, path) {
|
|
|
134
192
|
* durable `claim.release` events — a file-only prune is silently reverted by
|
|
135
193
|
* the next projector replay.
|
|
136
194
|
*
|
|
137
|
-
*
|
|
138
|
-
* repo-relative entries
|
|
139
|
-
* so both sides are normalized before comparing — an exact-string match
|
|
195
|
+
* Tool payloads and release calls can supply either absolute-under-coordRoot or
|
|
196
|
+
* canonical repo-relative entries, so both sides are normalized before comparing. An exact-string match
|
|
140
197
|
* silently no-ops on the mixed-form case and the claim never releases.
|
|
141
198
|
*
|
|
142
199
|
* This is the Option B fix for post-commit's pid-map attribution hole: a
|
|
@@ -203,7 +260,7 @@ export function healPidmap(coordRoot, instanceId, pid) {
|
|
|
203
260
|
const dir = join(coordRoot, ".harnery", "pid-map");
|
|
204
261
|
mkdirSync(dir, { recursive: true });
|
|
205
262
|
const hb = readHeartbeat(coordRoot, instanceId);
|
|
206
|
-
const platform = hb?.platform ?? "
|
|
263
|
+
const platform = hb?.platform ?? "claude-code";
|
|
207
264
|
const pmPath = join(dir, String(pid));
|
|
208
265
|
// Drift guard: only write + emit telemetry when the entry is missing or
|
|
209
266
|
// points at a different owner. Without this, a per-tool-call heal would
|
|
@@ -226,7 +283,7 @@ export function healPidmap(coordRoot, instanceId, pid) {
|
|
|
226
283
|
...(existingOwner ? { prior: existingOwner.slice(0, 8) } : {}),
|
|
227
284
|
});
|
|
228
285
|
}
|
|
229
|
-
export function healHeartbeat(coordRoot, instanceId, sessionId, model, adapter) {
|
|
286
|
+
export function healHeartbeat(coordRoot, instanceId, sessionId, model, adapter, opts) {
|
|
230
287
|
const path = heartbeatPath(coordRoot, instanceId);
|
|
231
288
|
if (existsSync(path)) {
|
|
232
289
|
// Already alive, return it.
|
|
@@ -240,8 +297,8 @@ export function healHeartbeat(coordRoot, instanceId, sessionId, model, adapter)
|
|
|
240
297
|
let agentId = "";
|
|
241
298
|
try {
|
|
242
299
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
243
|
-
const
|
|
244
|
-
const resolved = resolveName(coordRoot, instanceId, sessionId);
|
|
300
|
+
const names = require("./names.ts");
|
|
301
|
+
const resolved = names.resolveName(coordRoot, instanceId, sessionId);
|
|
245
302
|
if (resolved) {
|
|
246
303
|
name = resolved.name;
|
|
247
304
|
kind = resolved.kind;
|
|
@@ -249,6 +306,16 @@ export function healHeartbeat(coordRoot, instanceId, sessionId, model, adapter)
|
|
|
249
306
|
// name-history. Native subagents continue to use instance_id.
|
|
250
307
|
agentId = resolved.agent_id ?? (resolved.kind === "subagent" ? instanceId : "");
|
|
251
308
|
}
|
|
309
|
+
else if (!sessionId || sessionId === instanceId) {
|
|
310
|
+
// A main session with NO history at all never fired session.start —
|
|
311
|
+
// the CC fork flow (SessionStart fires under the parent's id before
|
|
312
|
+
// the fork id is minted), or a partially-wired adapter. Mint its pool
|
|
313
|
+
// name here instead of leaving a nameless heartbeat, and stamp the
|
|
314
|
+
// detected fork lineage while we're at it. A sessionId that differs
|
|
315
|
+
// from instanceId is left alone: that shape is a subagent whose parent
|
|
316
|
+
// is also unknown, and guessing "session" would be wrong.
|
|
317
|
+
name = names.assignName(coordRoot, instanceId, "session", opts?.forkedFrom ? { forkedFrom: opts.forkedFrom } : undefined);
|
|
318
|
+
}
|
|
252
319
|
}
|
|
253
320
|
catch {
|
|
254
321
|
/* names module unavailable, fall back to empty */
|
|
@@ -264,19 +331,21 @@ export function healHeartbeat(coordRoot, instanceId, sessionId, model, adapter)
|
|
|
264
331
|
started_at: now,
|
|
265
332
|
last_heartbeat: now,
|
|
266
333
|
files_touched: [],
|
|
267
|
-
// Default to
|
|
334
|
+
// Default to claude-code only when the caller can't tell us the adapter
|
|
268
335
|
// (e.g. manual `harn agents heal`). The live tool.pre_use heal threads the
|
|
269
336
|
// detected adapter so a pruned Cursor/Codex heartbeat is recreated with
|
|
270
|
-
// the correct platform instead of being mislabeled
|
|
337
|
+
// the correct platform instead of being mislabeled claude-code.
|
|
271
338
|
platform: adapterToPlatform(adapter),
|
|
272
339
|
};
|
|
273
340
|
atomicWrite(path, JSON.stringify(hb, null, 2));
|
|
274
341
|
// Write-only telemetry: only the actual-recreate branch reaches here (the
|
|
275
342
|
// already-alive case returned above), so this records exactly the heals that
|
|
276
|
-
// happened.
|
|
343
|
+
// happened. Recorded fork lineage rides the event too, so derived readers
|
|
344
|
+
// rebuilding from the ledger converge with .name-history.
|
|
277
345
|
emitHealthHeal(coordRoot, "health.heartbeat_heal", instanceId, hb, {
|
|
278
346
|
reason: "missing",
|
|
279
347
|
kind: "heartbeat",
|
|
348
|
+
...(opts?.forkedFrom ? { forked_from: opts.forkedFrom } : {}),
|
|
280
349
|
});
|
|
281
350
|
return hb;
|
|
282
351
|
}
|
|
@@ -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"}
|