harnery 0.32.0 → 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 +3 -1
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +212 -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/doctor.d.ts +4 -2
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +45 -6
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +10 -0
- 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/core/agents/canonical-emit.d.ts +1 -1
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +5 -3
- package/dist/core/agents/cli.js +5 -1
- package/dist/core/agents/coord-client.d.ts +11 -0
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- 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 +6 -0
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +46 -5
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +8 -2
- package/dist/core/agents/rules/stop-hook.js +4 -4
- package/dist/core/agents/state/heartbeat-projector.d.ts +11 -0
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-projector.js +54 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts +15 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +32 -0
- 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/hooks/adapter/events.d.ts.map +1 -1
- package/dist/core/hooks/adapter/events.js +1 -0
- 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/cli.js +90 -6
- 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/events/schema.d.ts +30 -1
- package/dist/core/hooks/events/schema.d.ts.map +1 -1
- 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/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/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/package.json +2 -2
- package/src/commander.ts +3 -1
- package/src/commands/agents.ts +248 -1
- package/src/commands/claude-desktop.ts +106 -5
- package/src/commands/doctor.ts +59 -7
- package/src/commands/init.ts +11 -0
- package/src/commands/instructions.ts +129 -0
- package/src/commands/presence.ts +7 -1
- package/src/core/agents/canonical-emit.ts +6 -4
- package/src/core/agents/cli.ts +6 -1
- package/src/core/agents/coord-client.ts +11 -0
- package/src/core/agents/index.ts +1 -0
- package/src/core/agents/render/prompt-context.ts +71 -6
- package/src/core/agents/render/session-context.ts +14 -2
- package/src/core/agents/rules/stop-hook.ts +5 -5
- package/src/core/agents/state/heartbeat-projector.ts +63 -0
- package/src/core/agents/state/heartbeat-writer.ts +51 -0
- package/src/core/agents/state/session-state.ts +131 -0
- package/src/core/hooks/adapter/events.ts +1 -0
- package/src/core/hooks/adapter/parse.ts +3 -0
- package/src/core/hooks/cli.ts +98 -7
- package/src/core/hooks/codex-authorization.ts +253 -0
- package/src/core/hooks/events/schema.ts +44 -0
- package/src/core/presence/blob.ts +18 -0
- package/src/core/presence/index.ts +18 -1
- package/src/lib/claude-desktop.ts +232 -1
- package/src/lib/instructions/bundle.ts +199 -0
package/dist/commands/init.js
CHANGED
|
@@ -141,6 +141,9 @@ export function registerInitCommand(program, emit, binName) {
|
|
|
141
141
|
actions.push(`+ wired ${wired} hook(s), upgraded ${upgraded} stale command(s), and removed ` +
|
|
142
142
|
`${removed} legacy hook(s) in ${rel(projectRoot, settingsPath)} (${already} already present)`);
|
|
143
143
|
}
|
|
144
|
+
const authorizationReview = codexHookReviewAction(adapter);
|
|
145
|
+
if (authorizationReview)
|
|
146
|
+
actions.push(authorizationReview);
|
|
144
147
|
// ── 3. agent-facing instructions block + skills ────────────────────────
|
|
145
148
|
// A misconfigured host addendum aborts here rather than half-writing: the
|
|
146
149
|
// apply step validates the configured source before it touches a file, so
|
|
@@ -166,6 +169,13 @@ export function registerInitCommand(program, emit, binName) {
|
|
|
166
169
|
emit.text(render(projectRoot, dryRun, actions, [...applied.warnings, ...gitHooks.warnings]));
|
|
167
170
|
});
|
|
168
171
|
}
|
|
172
|
+
export function codexHookReviewAction(adapter) {
|
|
173
|
+
if (adapter !== "codex")
|
|
174
|
+
return null;
|
|
175
|
+
return ("! Codex authorizes hook commands separately. Before starting work, review them in " +
|
|
176
|
+
"the terminal UI with `/hooks` or in Codex Desktop under Settings > Hooks. " +
|
|
177
|
+
"After approval, start a fresh task so SessionStart can run.");
|
|
178
|
+
}
|
|
169
179
|
/**
|
|
170
180
|
* Merge agent-hook entries into a adapter settings object in place, idempotently.
|
|
171
181
|
* Preserves every existing hook; rewrites events already wired to
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instructions.d.ts","sourceRoot":"","sources":["../../src/commands/instructions.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAcnD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAmDrF"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { countTokens } from "gpt-tokenizer/model/gpt-4o";
|
|
5
|
+
import { monorepoRoot } from "../core/agents/index.js";
|
|
6
|
+
import { buildInstructionBundle, } from "../lib/instructions/bundle.js";
|
|
7
|
+
export function registerInstructionsCommand(program, emit) {
|
|
8
|
+
const command = program
|
|
9
|
+
.command("instructions")
|
|
10
|
+
.description("Inspect and version the repo-authored instruction bundle loaded by each adapter.");
|
|
11
|
+
command
|
|
12
|
+
.command("manifest [paths...]")
|
|
13
|
+
.description("Emit a machine-readable bundle manifest for one or more working paths")
|
|
14
|
+
.option("--adapter <adapter>", "claude-code, cursor, codex, or all", "all")
|
|
15
|
+
.option("--format <type>", "Output format: json or table", "json")
|
|
16
|
+
.option("-o, --output <file>", "Write the complete JSON manifest to a file")
|
|
17
|
+
.action((paths, options) => {
|
|
18
|
+
if (options.format === "json")
|
|
19
|
+
emit.config({ format: "json" });
|
|
20
|
+
const coordRoot = monorepoRoot() ?? process.cwd();
|
|
21
|
+
const adapters = parseAdapters(options.adapter);
|
|
22
|
+
const profiles = (paths.length > 0 ? paths : [process.cwd()]).flatMap((path) => adapters.map((adapter) => enrichBundle(buildInstructionBundle({ coordRoot, cwd: resolve(path), adapter }), coordRoot)));
|
|
23
|
+
const manifest = {
|
|
24
|
+
ok: true,
|
|
25
|
+
schema_version: 1,
|
|
26
|
+
generated_at: new Date().toISOString(),
|
|
27
|
+
source_commit: gitHead(coordRoot),
|
|
28
|
+
tokenizer: "o200k_base",
|
|
29
|
+
coord_root: ".",
|
|
30
|
+
profile_count: profiles.length,
|
|
31
|
+
profiles,
|
|
32
|
+
};
|
|
33
|
+
if (options.output) {
|
|
34
|
+
const output = resolve(options.output);
|
|
35
|
+
writeFileSync(output, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
36
|
+
emit.data({
|
|
37
|
+
ok: true,
|
|
38
|
+
output,
|
|
39
|
+
schema_version: manifest.schema_version,
|
|
40
|
+
generated_at: manifest.generated_at,
|
|
41
|
+
source_commit: manifest.source_commit,
|
|
42
|
+
profile_count: manifest.profile_count,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
emit.data(manifest);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function parseAdapters(value) {
|
|
51
|
+
if (value === "all")
|
|
52
|
+
return ["claude-code", "cursor", "codex"];
|
|
53
|
+
if (value === "claude-code" || value === "cursor" || value === "codex")
|
|
54
|
+
return [value];
|
|
55
|
+
throw new Error(`unsupported adapter '${value}' (expected claude-code, cursor, codex, or all)`);
|
|
56
|
+
}
|
|
57
|
+
function enrichBundle(bundle, coordRoot) {
|
|
58
|
+
const components = bundle.components.map((component) => enrichComponent(component, coordRoot));
|
|
59
|
+
const canonicalSources = bundle.canonical_sources.map((component) => enrichComponent(component, coordRoot));
|
|
60
|
+
return {
|
|
61
|
+
...bundle,
|
|
62
|
+
coord_root: ".",
|
|
63
|
+
components,
|
|
64
|
+
canonical_sources: canonicalSources,
|
|
65
|
+
totals: totals(components),
|
|
66
|
+
canonical_source_totals: totals(canonicalSources),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function enrichComponent(component, coordRoot) {
|
|
70
|
+
const bytes = readFileSync(resolve(coordRoot, component.path));
|
|
71
|
+
const text = bytes.includes(0) ? null : bytes.toString("utf8");
|
|
72
|
+
return {
|
|
73
|
+
...component,
|
|
74
|
+
chars: text?.length ?? null,
|
|
75
|
+
tokens: text === null ? null : countTokens(text),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function totals(components) {
|
|
79
|
+
const roles = ["always_loaded", "just_in_time", "runtime_config"];
|
|
80
|
+
return {
|
|
81
|
+
files: components.length,
|
|
82
|
+
bytes: components.reduce((sum, component) => sum + component.bytes, 0),
|
|
83
|
+
tokens: components.reduce((sum, component) => sum + (component.tokens ?? 0), 0),
|
|
84
|
+
by_role: Object.fromEntries(roles.map((role) => {
|
|
85
|
+
const selected = components.filter((component) => component.role === role);
|
|
86
|
+
return [
|
|
87
|
+
role,
|
|
88
|
+
{
|
|
89
|
+
files: selected.length,
|
|
90
|
+
bytes: selected.reduce((sum, component) => sum + component.bytes, 0),
|
|
91
|
+
tokens: selected.reduce((sum, component) => sum + (component.tokens ?? 0), 0),
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
})),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function gitHead(root) {
|
|
98
|
+
const result = spawnSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" });
|
|
99
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
100
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../src/commands/presence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AA0BnD;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../src/commands/presence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AA0BnD;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA8KjF"}
|
|
@@ -133,7 +133,10 @@ export function registerPresenceCommand(program, emit) {
|
|
|
133
133
|
for (const a of m.agents) {
|
|
134
134
|
const task = a.task ? ` "${a.task.slice(0, 70)}"` : "";
|
|
135
135
|
const files = a.files_touched?.length ? ` [${a.files_touched.length} files]` : "";
|
|
136
|
-
|
|
136
|
+
const lifecycle = a.task_state === "blocked" && a.task_state_reason
|
|
137
|
+
? `${a.task_state}: ${a.task_state_reason}`
|
|
138
|
+
: a.task_state;
|
|
139
|
+
emit.text(` agent-${a.name ?? a.instance_id.slice(0, 8)}${task}${files} activity=${a.activity} lifecycle=${lifecycle}\n`);
|
|
137
140
|
}
|
|
138
141
|
}
|
|
139
142
|
});
|
|
@@ -28,7 +28,7 @@ export interface CanonicalEmitInput {
|
|
|
28
28
|
* `agents status`, and no sequence of CLI commands can satisfy it.
|
|
29
29
|
*/
|
|
30
30
|
export declare function resolveEmitRoot(start?: string): string | null;
|
|
31
|
-
export declare function emitCanonical(input: CanonicalEmitInput):
|
|
31
|
+
export declare function emitCanonical(input: CanonicalEmitInput): boolean;
|
|
32
32
|
/**
|
|
33
33
|
* Normalize the heartbeat's `platform` field to the canonical Adapter type.
|
|
34
34
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canonical-emit.d.ts","sourceRoot":"","sources":["../../../src/core/agents/canonical-emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAE5E;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,
|
|
1
|
+
{"version":3,"file":"canonical-emit.d.ts","sourceRoot":"","sources":["../../../src/core/agents/canonical-emit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAE5E;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAwChE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,QAAQ,GAAG,OAAO,CAIjG"}
|
|
@@ -26,10 +26,10 @@ export function resolveEmitRoot(start = process.cwd()) {
|
|
|
26
26
|
export function emitCanonical(input) {
|
|
27
27
|
const root = resolveEmitRoot();
|
|
28
28
|
if (!root)
|
|
29
|
-
return;
|
|
29
|
+
return false;
|
|
30
30
|
const binary = coordBinPath("agent-coord", root);
|
|
31
31
|
if (!binary)
|
|
32
|
-
return;
|
|
32
|
+
return false;
|
|
33
33
|
try {
|
|
34
34
|
const args = [
|
|
35
35
|
"emit-event",
|
|
@@ -62,10 +62,12 @@ export function emitCanonical(input) {
|
|
|
62
62
|
if (result.error || result.status !== 0) {
|
|
63
63
|
const why = result.error ? result.error.message : `exit ${result.status}`;
|
|
64
64
|
process.stderr.write(`emitCanonical: ${input.type} emit failed (${why})\n`); // lint-ok-emission: soft-fail diagnostic promised by the module doc; silent drops cost blocked turns
|
|
65
|
+
return false;
|
|
65
66
|
}
|
|
67
|
+
return true;
|
|
66
68
|
}
|
|
67
69
|
catch {
|
|
68
|
-
|
|
70
|
+
return false;
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
/**
|
package/dist/core/agents/cli.js
CHANGED
|
@@ -564,8 +564,11 @@ async function handlePromptContext(root, rest) {
|
|
|
564
564
|
const sessionNameNudge = args["session-name-nudge"] === "true";
|
|
565
565
|
const taskNudge = args["task-nudge"] === "true";
|
|
566
566
|
const statusFooterNudge = args["status-footer-nudge"] === "true";
|
|
567
|
+
// Value is the adapter id; a bare flag (parsed as "true") still enables the
|
|
568
|
+
// reminder with the adapter-neutral wording.
|
|
569
|
+
const turnRitualNudge = args["turn-ritual-nudge"] === "true" ? "generic" : args["turn-ritual-nudge"];
|
|
567
570
|
if (!instanceId) {
|
|
568
|
-
process.stderr.write("agent-coord prompt-context --instance <id> [--session <id>] [--name <agent-name>] [--session-name-nudge] [--task-nudge] [--status-footer-nudge]\n");
|
|
571
|
+
process.stderr.write("agent-coord prompt-context --instance <id> [--session <id>] [--name <agent-name>] [--session-name-nudge] [--task-nudge] [--status-footer-nudge] [--turn-ritual-nudge <adapter>]\n");
|
|
569
572
|
return 2;
|
|
570
573
|
}
|
|
571
574
|
const { renderPromptContext } = await import("./render/prompt-context.js");
|
|
@@ -577,6 +580,7 @@ async function handlePromptContext(root, rest) {
|
|
|
577
580
|
sessionNameNudge,
|
|
578
581
|
taskNudge,
|
|
579
582
|
statusFooterNudge,
|
|
583
|
+
turnRitualNudge,
|
|
580
584
|
});
|
|
581
585
|
process.stdout.write(text);
|
|
582
586
|
return 0;
|
|
@@ -40,16 +40,27 @@ export interface Heartbeat {
|
|
|
40
40
|
task?: string;
|
|
41
41
|
/** UTC ISO-8601 timestamp when task was last set/cleared. Used by adapters without Stop enforcement to compute staleness. 2026-05-24. */
|
|
42
42
|
task_updated_at?: string | null;
|
|
43
|
+
/** Current evidence-derived activity axis. */
|
|
44
|
+
activity?: "unknown" | "working" | "needs_input" | "idle";
|
|
45
|
+
activity_updated_at?: string;
|
|
46
|
+
activity_source?: string;
|
|
47
|
+
/** Explicit task lifecycle axis. */
|
|
48
|
+
task_state?: "active" | "blocked" | "done";
|
|
49
|
+
task_state_updated_at?: string;
|
|
50
|
+
task_state_reason?: string;
|
|
43
51
|
/** Session name built on the first non-empty set-task; its presence means "this session has been named". 2026-08-09. */
|
|
44
52
|
suggested_session_name?: string;
|
|
45
53
|
/** Stamped by turn.stop once the suggested name is seen in assistant reply text. 2026-08-09. */
|
|
46
54
|
session_name_seen_at?: string;
|
|
55
|
+
/** Which suggested name the latest sighting covered. */
|
|
56
|
+
session_name_seen_for?: string;
|
|
47
57
|
/** Auto-generated per-turn summary written by the Stop hook via Haiku. 2026-05-23. */
|
|
48
58
|
turn_summary?: string | null;
|
|
49
59
|
/** UTC ISO-8601 timestamp when turn_summary was last refreshed. */
|
|
50
60
|
turn_summary_updated_at?: string | null;
|
|
51
61
|
/** Hook client: `claude-code` (default) or `cursor`. Cursor Phase 1. */
|
|
52
62
|
platform?: string;
|
|
63
|
+
workflow_run_id?: string;
|
|
53
64
|
}
|
|
54
65
|
/**
|
|
55
66
|
* Resolve the monorepo root for coord-state purposes.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coord-client.d.ts","sourceRoot":"","sources":["../../../src/core/agents/coord-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAWhE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM,CAI5E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOhF;AAED,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oGAAoG;IACpG,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yIAAyI;IACzI,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,wHAAwH;IACxH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gGAAgG;IAChG,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,mEAAmE;IACnE,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"coord-client.d.ts","sourceRoot":"","sources":["../../../src/core/agents/coord-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/agents/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/agents/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC"}
|
|
@@ -43,6 +43,12 @@ export interface PromptContextOpts {
|
|
|
43
43
|
/** When true, append the non-deduplicated Codex status-footer reminder.
|
|
44
44
|
* The caller enables this only for human-facing Codex sessions. */
|
|
45
45
|
statusFooterNudge?: boolean;
|
|
46
|
+
/** Adapter id whose Stop hook ENFORCES the end-of-turn ritual (task_set +
|
|
47
|
+
* status check). When set, append a fresh per-prompt reminder of that
|
|
48
|
+
* contract so the model satisfies it before ending the turn instead of
|
|
49
|
+
* being bounced into a retry. Not hash-deduped, same rationale as the
|
|
50
|
+
* status footer. */
|
|
51
|
+
turnRitualNudge?: string;
|
|
46
52
|
}
|
|
47
53
|
/**
|
|
48
54
|
* Build the combined UserPromptSubmit additionalContext string. Returns "" when
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-context.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/render/prompt-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;
|
|
1
|
+
{"version":3,"file":"prompt-context.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/render/prompt-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AA0CH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;8EAC0E;IAC1E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;kFAC8E;IAC9E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;uEACmE;IACnE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;wBAIoB;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAqDnE"}
|
|
@@ -45,7 +45,7 @@ import { formatPendingCouncils } from "./session-context.js";
|
|
|
45
45
|
* councils are pending, matching the bash behavior).
|
|
46
46
|
*/
|
|
47
47
|
export function renderPromptContext(opts) {
|
|
48
|
-
const { coordRoot, instanceId, sessionId, agentName, sessionNameNudge, taskNudge, statusFooterNudge, } = opts;
|
|
48
|
+
const { coordRoot, instanceId, sessionId, agentName, sessionNameNudge, taskNudge, statusFooterNudge, turnRitualNudge, } = opts;
|
|
49
49
|
const sections = [];
|
|
50
50
|
// 1. Peer table with hash dedup.
|
|
51
51
|
const peerTable = computePeerTableIfChanged(coordRoot, instanceId, sessionId);
|
|
@@ -76,8 +76,32 @@ export function renderPromptContext(opts) {
|
|
|
76
76
|
if (statusMsg)
|
|
77
77
|
sections.push(statusMsg);
|
|
78
78
|
}
|
|
79
|
+
// 5. Adapters whose Stop hook ENFORCES the turn ritual get a fresh reminder
|
|
80
|
+
// on every prompt. Reminding before the reply is drafted is far cheaper than
|
|
81
|
+
// the bounce-and-retry the Stop hook otherwise forces: enforcement-only
|
|
82
|
+
// compliance was measured missing on a double-digit share of turns. Not
|
|
83
|
+
// hash-deduped for the same reason as the Codex footer.
|
|
84
|
+
if (turnRitualNudge) {
|
|
85
|
+
const ritualMsg = renderTurnRitualReminder(coordRoot, instanceId, turnRitualNudge);
|
|
86
|
+
if (ritualMsg)
|
|
87
|
+
sections.push(ritualMsg);
|
|
88
|
+
}
|
|
79
89
|
return sections.join("\n\n");
|
|
80
90
|
}
|
|
91
|
+
function renderTurnRitualReminder(coordRoot, selfInstanceId, adapter) {
|
|
92
|
+
const hb = readHeartbeat(join(coordRoot, ".harnery", "active", `${selfInstanceId}.json`));
|
|
93
|
+
if (!hb || hb.kind === "subagent" || hb.kind === "transient" || hb.workflow_run_id)
|
|
94
|
+
return "";
|
|
95
|
+
const bin = resolveBinName(coordRoot);
|
|
96
|
+
const statusCommand = endOfTurnStatusCommand(coordRoot);
|
|
97
|
+
// Claude Code renders the status box only if the reply carries it; Cursor
|
|
98
|
+
// shows the box inline on its own, so it is never asked for a second copy.
|
|
99
|
+
const paste = adapter === "claude-code"
|
|
100
|
+
? " and paste its output verbatim in a fenced code block at the end of your reply"
|
|
101
|
+
: "";
|
|
102
|
+
return (`Turn ritual (Stop-enforced): on any turn that uses tools, run \`${bin} agents set-task "<short focus>"\` at some point during the turn (required every turn, even when the focus is unchanged; pass an empty string when there is no meaningful focus), then make \`${statusCommand}\` your final tool call${paste}. ` +
|
|
103
|
+
"A turn that ends without these is bounced by the Stop hook and costs a retry.");
|
|
104
|
+
}
|
|
81
105
|
function renderStatusFooterReminder(coordRoot, selfInstanceId) {
|
|
82
106
|
const hb = readHeartbeat(join(coordRoot, ".harnery", "active", `${selfInstanceId}.json`));
|
|
83
107
|
if (!hb || hb.kind === "subagent" || hb.kind === "transient" || hb.workflow_run_id)
|
|
@@ -116,13 +140,17 @@ function computeFocusNudgeIfChanged(coordRoot, selfInstanceId, opts) {
|
|
|
116
140
|
let needsNudge = false;
|
|
117
141
|
let message = "";
|
|
118
142
|
let nudgeKind = "";
|
|
119
|
-
if (opts.sessionNameNudge &&
|
|
143
|
+
if (opts.sessionNameNudge &&
|
|
144
|
+
(!hb.suggested_session_name ||
|
|
145
|
+
(hb.session_name_seen_for !== undefined &&
|
|
146
|
+
hb.suggested_session_name !== hb.session_name_seen_for))) {
|
|
120
147
|
// Keyed on "a name was ever produced", not task_updated_at: a bare clear
|
|
121
148
|
// as the first declaration must not end the naming window.
|
|
122
149
|
needsNudge = true;
|
|
123
150
|
nudgeKind = "session-name";
|
|
124
|
-
message =
|
|
125
|
-
`This session
|
|
151
|
+
message = hb.suggested_session_name
|
|
152
|
+
? `This session's lifecycle changed its suggested name. Reproduce this value by itself inside a fenced code block at the very top of your reply so the operator can one-click-copy it as the session/tab title: ${hb.suggested_session_name}`
|
|
153
|
+
: `This session has no name yet: run \`${bin} agents set-task "<2-5 word session topic>"\` as your first tool call. ` +
|
|
126
154
|
"When it returns `first_of_session: true`, reproduce its `suggested_session_name` value by itself inside a fenced code block at the very top of your reply so the operator can one-click-copy it as the session/tab title. " +
|
|
127
155
|
"Then continue with the task; that `set-task` also satisfies this turn's focus declaration.";
|
|
128
156
|
}
|
|
@@ -204,6 +232,9 @@ function computePeerTableIfChanged(coordRoot, selfInstanceId, selfSessionIdFallb
|
|
|
204
232
|
started_at: p.started_at ?? null,
|
|
205
233
|
files_touched: Array.from(p.files_touched ?? []).sort(),
|
|
206
234
|
platform: p.platform ?? null,
|
|
235
|
+
activity: p.activity ?? "unknown",
|
|
236
|
+
task_state: p.task_state ?? "active",
|
|
237
|
+
task_state_reason: p.task_state_reason ?? null,
|
|
207
238
|
}))
|
|
208
239
|
.sort((a, b) => (a.instance_id ?? "").localeCompare(b.instance_id ?? ""));
|
|
209
240
|
// Remote basis: machine + per-agent identity/task/files (published_at is
|
|
@@ -214,6 +245,9 @@ function computePeerTableIfChanged(coordRoot, selfInstanceId, selfSessionIdFallb
|
|
|
214
245
|
instance_id: a.instance_id,
|
|
215
246
|
name: a.name ?? null,
|
|
216
247
|
task: a.task ?? null,
|
|
248
|
+
activity: a.activity,
|
|
249
|
+
task_state: a.task_state,
|
|
250
|
+
task_state_reason: a.task_state_reason ?? null,
|
|
217
251
|
files_touched: Array.from(a.files_touched ?? []).sort(),
|
|
218
252
|
})),
|
|
219
253
|
}));
|
|
@@ -393,6 +427,7 @@ function renderSubtable(rows, header, nowSec) {
|
|
|
393
427
|
}
|
|
394
428
|
function formatRow(r, nowSec) {
|
|
395
429
|
const taskPart = r.task ? ` "${r.task.slice(0, 60)}"` : "";
|
|
430
|
+
const statePart = formatState(r);
|
|
396
431
|
// Fall back started_at → last_heartbeat; if neither is a valid timestamp,
|
|
397
432
|
// show "age unknown" rather than the epoch-derived "20608d ago" ghost.
|
|
398
433
|
const startedSec = parseIsoSec(r.started_at) ?? parseIsoSec(r.last_heartbeat);
|
|
@@ -403,7 +438,13 @@ function formatRow(r, nowSec) {
|
|
|
403
438
|
// Prefer a short instance_id over a bare "unknown" so an incomplete row is
|
|
404
439
|
// still identifiable.
|
|
405
440
|
const label = r.name ?? (r.instance_id ? r.instance_id.slice(0, 8) : "unknown");
|
|
406
|
-
return ` - agent-${label}${taskPart} (${ageFrom}, ${filesPart}${lastActivity})${turnSummary}`;
|
|
441
|
+
return ` - agent-${label}${taskPart} (${statePart}, ${ageFrom}, ${filesPart}${lastActivity})${turnSummary}`;
|
|
442
|
+
}
|
|
443
|
+
function formatState(r) {
|
|
444
|
+
const activity = r.activity ?? "unknown";
|
|
445
|
+
const lifecycle = r.task_state ?? "active";
|
|
446
|
+
const reason = lifecycle === "blocked" && r.task_state_reason ? `: ${r.task_state_reason.slice(0, 80)}` : "";
|
|
447
|
+
return `activity=${activity}, lifecycle=${lifecycle}${reason}`;
|
|
407
448
|
}
|
|
408
449
|
function fmtFiles(files) {
|
|
409
450
|
if (files.length === 0)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-context.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/render/session-context.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAyC,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"session-context.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/render/session-context.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAyC,QAAQ,EAAE,MAAM,SAAS,CAAC;AA0B1E,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;+BAC2B;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CA6E7D;AAuJD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAwBvD;AAwDD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAiDlF;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,iBAAW,CAAC"}
|
|
@@ -101,7 +101,10 @@ function formatRemoteMachines(coordRoot) {
|
|
|
101
101
|
const files = a.files_touched?.length
|
|
102
102
|
? `holds: ${a.files_touched.slice(0, 3).join(", ")}${a.files_touched.length > 3 ? `, +${a.files_touched.length - 3} more` : ""}`
|
|
103
103
|
: "nothing held";
|
|
104
|
-
|
|
104
|
+
const reason = a.task_state === "blocked" && a.task_state_reason
|
|
105
|
+
? `: ${a.task_state_reason.slice(0, 80)}`
|
|
106
|
+
: "";
|
|
107
|
+
lines.push(` - agent-${a.name ?? a.instance_id.slice(0, 8)} @${m.machine}${task} (activity=${a.activity}, lifecycle=${a.task_state}${reason}, ${files})`);
|
|
105
108
|
}
|
|
106
109
|
}
|
|
107
110
|
return `Sessions on other machines (advisory, via presence refs):\n${lines.join("\n")}`;
|
|
@@ -179,11 +182,14 @@ function renderSubtable(rows, header, nowSec) {
|
|
|
179
182
|
}
|
|
180
183
|
function formatRow(r, nowSec) {
|
|
181
184
|
const taskPart = r.task ? ` "${r.task.slice(0, 60)}"` : "";
|
|
185
|
+
const activity = r.activity ?? "unknown";
|
|
186
|
+
const lifecycle = r.task_state ?? "active";
|
|
187
|
+
const reason = lifecycle === "blocked" && r.task_state_reason ? `: ${r.task_state_reason.slice(0, 80)}` : "";
|
|
182
188
|
const ageFrom = fmtAge(nowSec - parseIsoSec(r.started_at));
|
|
183
189
|
const filesPart = fmtFiles(r.display_files);
|
|
184
190
|
const lastActivity = fmtLastActivity(r, nowSec);
|
|
185
191
|
const turnSummary = r.turn_summary ? `\n last turn: ${r.turn_summary.slice(0, 80)}` : "";
|
|
186
|
-
return ` - agent-${r.name ?? "unknown"}${taskPart} (${ageFrom}, ${filesPart}${lastActivity})${turnSummary}`;
|
|
192
|
+
return ` - agent-${r.name ?? "unknown"}${taskPart} (activity=${activity}, lifecycle=${lifecycle}${reason}, ${ageFrom}, ${filesPart}${lastActivity})${turnSummary}`;
|
|
187
193
|
}
|
|
188
194
|
function fmtFiles(files) {
|
|
189
195
|
if (files.length === 0)
|
|
@@ -222,18 +222,18 @@ export function evaluateStopHook(coordRoot, req) {
|
|
|
222
222
|
// `session_name_present` (assistant-text-only transcript scan), mirroring
|
|
223
223
|
// how rule 2/3 rides `status_box_present`.
|
|
224
224
|
if (ackSignal === "status_box_present") {
|
|
225
|
-
const
|
|
226
|
-
e.data.
|
|
225
|
+
const namingEvent = inTurn.find((e) => ((e.event_type === "state.task_set" && e.data.first_of_session === true) ||
|
|
226
|
+
(e.event_type === "state.task_state" && e.data.name_reminted === true)) &&
|
|
227
227
|
typeof e.data.suggested_session_name === "string" &&
|
|
228
228
|
e.data.suggested_session_name.length > 0);
|
|
229
|
-
if (
|
|
229
|
+
if (namingEvent) {
|
|
230
230
|
// ANY in-window turn.stop with the flag satisfies the rule, not just the
|
|
231
231
|
// latest: once seen, `session_name_seen_at` is stamped and later
|
|
232
232
|
// turn.stops OMIT the field, so a later block on a different rule (whose
|
|
233
233
|
// repair fires a fresh turn.stop) must not un-satisfy this one.
|
|
234
234
|
const namePresent = inTurn.some((e) => e.event_type === "turn.stop" && e.data.session_name_present === true);
|
|
235
235
|
if (!namePresent) {
|
|
236
|
-
return sessionNameBlock(String(
|
|
236
|
+
return sessionNameBlock(String(namingEvent.data.suggested_session_name));
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
239
|
}
|
|
@@ -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,12 @@ 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;
|
|
33
44
|
suggested_session_name?: string;
|
|
34
45
|
session_name_seen_at?: string;
|
|
35
46
|
session_name_seen_for?: 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");
|
|
@@ -273,6 +294,20 @@ function apply(hb, ev, coordRoot) {
|
|
|
273
294
|
}
|
|
274
295
|
break;
|
|
275
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
|
+
}
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
276
311
|
case "state.status_checked":
|
|
277
312
|
hb.last_status_at = ev.ts;
|
|
278
313
|
break;
|
|
@@ -464,6 +499,10 @@ function writeHeartbeat(coordRoot, instanceId, hb) {
|
|
|
464
499
|
setIfDefined(merged, "workflow_run_id", hb.workflow_run_id);
|
|
465
500
|
setIfDefined(merged, "workflow_agent_id", hb.workflow_agent_id);
|
|
466
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);
|
|
467
506
|
setIfDefined(merged, "platform", hb.platform);
|
|
468
507
|
setIfDefined(merged, "started_at", hb.started_at);
|
|
469
508
|
// files_touched is a required-array invariant for every reader
|
|
@@ -477,6 +516,17 @@ function writeHeartbeat(coordRoot, instanceId, hb) {
|
|
|
477
516
|
setIfDefined(merged, "last_tool_at", hb.last_tool_at);
|
|
478
517
|
setIfDefined(merged, "task", hb.task);
|
|
479
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
|
+
}
|
|
480
530
|
setIfDefined(merged, "suggested_session_name", hb.suggested_session_name);
|
|
481
531
|
setIfDefined(merged, "session_name_seen_at", hb.session_name_seen_at);
|
|
482
532
|
if (hb.session_name_seen_for) {
|
|
@@ -525,3 +575,7 @@ function pickBool(o, k) {
|
|
|
525
575
|
const v = o[k];
|
|
526
576
|
return typeof v === "boolean" ? v : undefined;
|
|
527
577
|
}
|
|
578
|
+
function pickNum(o, k) {
|
|
579
|
+
const v = o[k];
|
|
580
|
+
return typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
|
581
|
+
}
|