harnery 0.34.0 → 0.35.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 +15 -4
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +91 -162
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +26 -2
- package/dist/commands/decision.d.ts.map +1 -1
- package/dist/commands/decision.js +2 -2
- package/dist/commands/events.d.ts.map +1 -1
- package/dist/commands/events.js +18 -1
- package/dist/commands/presence.d.ts.map +1 -1
- package/dist/commands/presence.js +2 -2
- package/dist/commands/semantic.d.ts +4 -0
- package/dist/commands/semantic.d.ts.map +1 -0
- package/dist/commands/semantic.js +147 -0
- package/dist/commands/web.d.ts.map +1 -1
- package/dist/commands/web.js +17 -0
- package/dist/core/agents/canonical-emit.d.ts +3 -0
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +12 -0
- package/dist/core/agents/claim-path.d.ts +11 -0
- package/dist/core/agents/claim-path.d.ts.map +1 -0
- package/dist/core/agents/claim-path.js +22 -0
- package/dist/core/agents/coord-client.d.ts +29 -3
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +88 -9
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +1 -3
- package/dist/core/agents/live-lifecycle-v3.js +3 -3
- package/dist/core/agents/live-observation-v3.d.ts.map +1 -1
- package/dist/core/agents/live-observation-v3.js +2 -2
- package/dist/core/agents/render/prompt-context.js +12 -10
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
- package/dist/core/agents/rules/stop-hook.js +23 -11
- package/dist/core/agents/session-name-display.d.ts +2 -1
- package/dist/core/agents/session-name-display.d.ts.map +1 -1
- package/dist/core/agents/session-name-display.js +10 -3
- package/dist/core/agents/state/heartbeat-reader.d.ts +46 -0
- package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -0
- package/dist/core/agents/state/heartbeat-reader.js +25 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts +3 -48
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +16 -34
- package/dist/core/agents/state/live-coordination-view.d.ts +1 -1
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +25 -1
- package/dist/core/agents/state/names.d.ts +9 -1
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +190 -182
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +23 -5
- package/dist/core/artifacts/constants.d.ts +5 -0
- package/dist/core/artifacts/constants.d.ts.map +1 -0
- package/dist/core/artifacts/constants.js +4 -0
- package/dist/core/artifacts/index.d.ts +3 -3
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +50 -7
- package/dist/core/events/v3/base-contract.d.ts +795 -0
- package/dist/core/events/v3/base-contract.d.ts.map +1 -1
- package/dist/core/events/v3/base-contract.js +33 -0
- package/dist/core/events/v3/capabilities.d.ts +11 -0
- package/dist/core/events/v3/capabilities.d.ts.map +1 -1
- package/dist/core/events/v3/capabilities.js +23 -0
- package/dist/core/events/v3/generated.d.ts +1 -1
- package/dist/core/events/v3/generated.js +1 -1
- package/dist/core/events/v3/index.d.ts +4 -3
- package/dist/core/events/v3/index.d.ts.map +1 -1
- package/dist/core/events/v3/index.js +4 -3
- package/dist/core/events/v3/latency.d.ts +31 -1
- package/dist/core/events/v3/latency.d.ts.map +1 -1
- package/dist/core/events/v3/latency.js +127 -3
- package/dist/core/events/v3/live-routing.d.ts +30 -0
- package/dist/core/events/v3/live-routing.d.ts.map +1 -1
- package/dist/core/events/v3/live-routing.js +109 -1
- package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/command-recorder.js +10 -1
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/coordination-recorder.js +14 -3
- package/dist/core/events/v3/producers/hook-base.d.ts +17 -1
- package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook-base.js +82 -1
- package/dist/core/events/v3/producers/intake.d.ts +6 -0
- package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
- package/dist/core/events/v3/producers/intake.js +11 -0
- package/dist/core/events/v3/producers/recorder.d.ts +62 -0
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +654 -18
- package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts +51 -0
- package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts.map +1 -0
- package/dist/core/events/v3/runtime-telemetry-capabilities.js +54 -0
- package/dist/core/events/v3/turn-telemetry.d.ts +9 -0
- package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -1
- package/dist/core/hooks/adapter/parse.d.ts +3 -0
- package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
- package/dist/core/hooks/adapter/parse.js +14 -0
- package/dist/core/hooks/adapter/runtime-telemetry.d.ts +119 -0
- package/dist/core/hooks/adapter/runtime-telemetry.d.ts.map +1 -0
- package/dist/core/hooks/adapter/runtime-telemetry.js +568 -0
- package/dist/core/hooks/cli.js +114 -38
- package/dist/core/hooks/remediation-cap.d.ts +34 -0
- package/dist/core/hooks/remediation-cap.d.ts.map +1 -0
- package/dist/core/hooks/remediation-cap.js +71 -0
- package/dist/core/hooks/resolve/transcript.d.ts +25 -9
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +35 -17
- package/dist/core/semantic/adapters.d.ts +34 -0
- package/dist/core/semantic/adapters.d.ts.map +1 -0
- package/dist/core/semantic/adapters.js +222 -0
- package/dist/core/semantic/contract.d.ts +588 -0
- package/dist/core/semantic/contract.d.ts.map +1 -0
- package/dist/core/semantic/contract.js +230 -0
- package/dist/core/semantic/evidence.d.ts +11 -0
- package/dist/core/semantic/evidence.d.ts.map +1 -0
- package/dist/core/semantic/evidence.js +262 -0
- package/dist/core/semantic/index.d.ts +10 -0
- package/dist/core/semantic/index.d.ts.map +1 -0
- package/dist/core/semantic/index.js +9 -0
- package/dist/core/semantic/once.d.ts +33 -0
- package/dist/core/semantic/once.d.ts.map +1 -0
- package/dist/core/semantic/once.js +293 -0
- package/dist/core/semantic/prompt.d.ts +12 -0
- package/dist/core/semantic/prompt.d.ts.map +1 -0
- package/dist/core/semantic/prompt.js +70 -0
- package/dist/core/semantic/scheduler.d.ts +16 -0
- package/dist/core/semantic/scheduler.d.ts.map +1 -0
- package/dist/core/semantic/scheduler.js +67 -0
- package/dist/core/semantic/service-status.d.ts +31 -0
- package/dist/core/semantic/service-status.d.ts.map +1 -0
- package/dist/core/semantic/service-status.js +84 -0
- package/dist/core/semantic/service.d.ts +51 -0
- package/dist/core/semantic/service.d.ts.map +1 -0
- package/dist/core/semantic/service.js +470 -0
- package/dist/core/semantic/storage.d.ts +71 -0
- package/dist/core/semantic/storage.d.ts.map +1 -0
- package/dist/core/semantic/storage.js +179 -0
- package/dist/core/semantic/validate.d.ts +16 -0
- package/dist/core/semantic/validate.d.ts.map +1 -0
- package/dist/core/semantic/validate.js +174 -0
- package/dist/lib/browser/client.d.ts +11 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +13 -0
- package/dist/lib/browser/index.d.ts +1 -0
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/standalone-html.d.ts +25 -0
- package/dist/lib/browser/standalone-html.d.ts.map +1 -0
- package/dist/lib/browser/standalone-html.js +284 -0
- package/dist/lib/instructions/apply.d.ts.map +1 -1
- package/dist/lib/instructions/apply.js +1 -0
- package/dist/lib/instructions/templates.d.ts +2 -0
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +144 -4
- package/package.json +6 -1
- package/schemas/event-v3.schema.json +2758 -478
- package/src/commander.ts +2 -0
- package/src/commands/agents.ts +104 -152
- package/src/commands/browse.ts +25 -2
- package/src/commands/decision.ts +8 -2
- package/src/commands/events.ts +21 -1
- package/src/commands/presence.ts +7 -2
- package/src/commands/semantic.ts +179 -0
- package/src/commands/web.ts +15 -0
- package/src/core/agents/canonical-emit.ts +15 -0
- package/src/core/agents/claim-path.ts +23 -0
- package/src/core/agents/coord-client.ts +92 -8
- package/src/core/agents/live-authority-v3.ts +1 -4
- package/src/core/agents/live-lifecycle-v3.ts +8 -3
- package/src/core/agents/live-observation-v3.ts +7 -2
- package/src/core/agents/render/prompt-context.ts +13 -10
- package/src/core/agents/rules/stop-hook.ts +24 -11
- package/src/core/agents/session-name-display.ts +11 -3
- package/src/core/agents/state/heartbeat-reader.ts +69 -0
- package/src/core/agents/state/heartbeat-writer.ts +20 -84
- package/src/core/agents/state/live-coordination-view.ts +26 -1
- package/src/core/agents/state/names.ts +190 -182
- package/src/core/agents/state/stale-sweep.ts +25 -5
- package/src/core/artifacts/constants.ts +4 -0
- package/src/core/artifacts/index.ts +49 -7
- package/src/core/events/v3/base-contract.ts +39 -0
- package/src/core/events/v3/capabilities.ts +34 -0
- package/src/core/events/v3/generated.ts +1 -1
- package/src/core/events/v3/index.ts +21 -0
- package/src/core/events/v3/latency.ts +213 -2
- package/src/core/events/v3/live-routing.ts +120 -1
- package/src/core/events/v3/producers/command-recorder.ts +10 -1
- package/src/core/events/v3/producers/coordination-recorder.ts +17 -5
- package/src/core/events/v3/producers/hook-base.ts +115 -1
- package/src/core/events/v3/producers/intake.ts +18 -0
- package/src/core/events/v3/producers/recorder.ts +856 -21
- package/src/core/events/v3/runtime-telemetry-capabilities.ts +137 -0
- package/src/core/events/v3/turn-telemetry.ts +10 -0
- package/src/core/hooks/adapter/parse.ts +16 -0
- package/src/core/hooks/adapter/runtime-telemetry.ts +805 -0
- package/src/core/hooks/cli.ts +126 -41
- package/src/core/hooks/remediation-cap.ts +77 -0
- package/src/core/hooks/resolve/transcript.ts +49 -17
- package/src/core/semantic/adapters.ts +293 -0
- package/src/core/semantic/contract.ts +298 -0
- package/src/core/semantic/evidence.ts +336 -0
- package/src/core/semantic/index.ts +127 -0
- package/src/core/semantic/once.ts +410 -0
- package/src/core/semantic/prompt.ts +84 -0
- package/src/core/semantic/scheduler.ts +107 -0
- package/src/core/semantic/service-status.ts +118 -0
- package/src/core/semantic/service.ts +580 -0
- package/src/core/semantic/storage.ts +286 -0
- package/src/core/semantic/validate.ts +212 -0
- package/src/lib/browser/client.ts +21 -0
- package/src/lib/browser/index.ts +4 -0
- package/src/lib/browser/standalone-html.ts +312 -0
- package/src/lib/instructions/apply.ts +1 -0
- package/src/lib/instructions/templates.ts +149 -4
package/src/commander.ts
CHANGED
|
@@ -53,6 +53,7 @@ import { registerPolicyCommand } from "./commands/policy.ts";
|
|
|
53
53
|
import { registerPresenceCommand } from "./commands/presence.ts";
|
|
54
54
|
import { registerReadCommand } from "./commands/read.ts";
|
|
55
55
|
import { registerRelayCommand } from "./commands/relay.ts";
|
|
56
|
+
import { registerSemanticCommand } from "./commands/semantic.ts";
|
|
56
57
|
import { registerSyncCommand } from "./commands/sync.ts";
|
|
57
58
|
import { registerSectionCommand, registerTocCommand } from "./commands/toc.ts";
|
|
58
59
|
import { registerTokensCommand } from "./commands/tokens.ts";
|
|
@@ -290,6 +291,7 @@ export function createHarneryProgram(opts: HarneryContextOpts = {}): Command {
|
|
|
290
291
|
registerCheckpointCommand(program, emit, opts.context);
|
|
291
292
|
registerJournalCommand(program, emit);
|
|
292
293
|
registerLedgerV3Command(program, emit, opts.context);
|
|
294
|
+
registerSemanticCommand(program, emit, opts.context);
|
|
293
295
|
registerEventsCommand(program, emit, opts.context);
|
|
294
296
|
registerArtifactsCommand(program, emit, opts.context);
|
|
295
297
|
registerDecisionCommand(program, emit);
|
package/src/commands/agents.ts
CHANGED
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
readdirSync,
|
|
22
22
|
readFileSync,
|
|
23
23
|
rmSync,
|
|
24
|
-
statSync,
|
|
25
24
|
writeFileSync,
|
|
26
25
|
} from "node:fs";
|
|
27
26
|
import { homedir, tmpdir } from "node:os";
|
|
@@ -38,6 +37,7 @@ import {
|
|
|
38
37
|
import {
|
|
39
38
|
emitEventV3,
|
|
40
39
|
monorepoRoot,
|
|
40
|
+
nativeSessionIdentity,
|
|
41
41
|
normalizeAdapter,
|
|
42
42
|
resolveOwner,
|
|
43
43
|
resolveOwnerBySessionEnv,
|
|
@@ -52,7 +52,10 @@ import {
|
|
|
52
52
|
requestSessionEndExplicitV3,
|
|
53
53
|
type SessionFinalizationRequestV3,
|
|
54
54
|
} from "../core/agents/session-finalizer-v3.ts";
|
|
55
|
-
import {
|
|
55
|
+
import {
|
|
56
|
+
SESSION_NAME_DISPLAY_NOTE,
|
|
57
|
+
sessionNameDisplayPending,
|
|
58
|
+
} from "../core/agents/session-name-display.ts";
|
|
56
59
|
import {
|
|
57
60
|
buildLifecycleSuggestedName,
|
|
58
61
|
buildSuggestedName,
|
|
@@ -89,6 +92,7 @@ import { readLedgerV3 } from "../core/events/v3/reader.ts";
|
|
|
89
92
|
import { EVENT_V3_LEDGER_RELATIVE_ROOT } from "../core/events/v3/writer.ts";
|
|
90
93
|
import type { RunQualitySnapshot, RunQualityStatus } from "../core/guard/index.ts";
|
|
91
94
|
import { evaluateRunQualityIfDue } from "../core/guard/index.ts";
|
|
95
|
+
import { readRuntimeContextUsage } from "../core/hooks/adapter/runtime-telemetry.ts";
|
|
92
96
|
import { type RemoteMachine, readRemoteMachines } from "../core/presence/index.ts";
|
|
93
97
|
import { registerContextCommand } from "./context.ts";
|
|
94
98
|
|
|
@@ -152,9 +156,8 @@ function freshnessCutoffSecs(): number {
|
|
|
152
156
|
}
|
|
153
157
|
|
|
154
158
|
const SUBAGENT_NOTE =
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
-
"bridge (per-shell marker file at .harnery/shells/<pid>) is out of scope.";
|
|
159
|
+
"Native adapter subagents resolve to their own child generation after the hook bridge " +
|
|
160
|
+
"observes delegation. An unbridged subprocess shell still resolves through its parent.";
|
|
158
161
|
|
|
159
162
|
/**
|
|
160
163
|
* Spawn options for agent-coord child processes: pin the coord root the
|
|
@@ -248,19 +251,7 @@ function taskStateOf(hb: Pick<Heartbeat, "task_state">): TaskState {
|
|
|
248
251
|
return hb.task_state ?? "active";
|
|
249
252
|
}
|
|
250
253
|
|
|
251
|
-
|
|
252
|
-
export function nativeSessionIdentity(
|
|
253
|
-
row: Pick<Heartbeat, "native_session_id" | "session_id"> | null | undefined,
|
|
254
|
-
fallback: string,
|
|
255
|
-
): string {
|
|
256
|
-
if (row?.native_session_id) return row.native_session_id;
|
|
257
|
-
// A projection-only row carries the privacy-safe canonical session
|
|
258
|
-
// fingerprint, not the adapter's native session ID needed to join the hook
|
|
259
|
-
// producer. Until a disposable cache exists, the resolved owner is the only
|
|
260
|
-
// native identity available to command surfaces such as `agents status`.
|
|
261
|
-
if (row?.session_id && !/^sid_[a-f0-9]{64}$/.test(row.session_id)) return row.session_id;
|
|
262
|
-
return fallback;
|
|
263
|
-
}
|
|
254
|
+
export { nativeSessionIdentity };
|
|
264
255
|
|
|
265
256
|
function lifecycleLabel(hb: Pick<Heartbeat, "task_state" | "task_state_reason">): string {
|
|
266
257
|
const state = taskStateOf(hb);
|
|
@@ -329,9 +320,9 @@ export function registerAgentsCommand(
|
|
|
329
320
|
.command("suggest-name [description...]")
|
|
330
321
|
.description(
|
|
331
322
|
'Reprint a session name ("Agent <you> - <description>") for the operator to set as their ' +
|
|
332
|
-
"adapter tab title. With no arg, derives
|
|
323
|
+
"adapter tab title. With no arg, prefers the exact pending name, then derives from your current task. The primary naming path " +
|
|
333
324
|
"is set-task (it suggests a name on the first focus declaration); reach for this to reprint " +
|
|
334
|
-
"or
|
|
325
|
+
"or to create an explicit pending-display retry with --json. Read-only.",
|
|
335
326
|
)
|
|
336
327
|
.option("--json", "JSON output instead of the bare name")
|
|
337
328
|
.option(
|
|
@@ -1854,11 +1845,18 @@ function runSetTask(task: string, opts?: { sessionId?: string }): void {
|
|
|
1854
1845
|
// the first time. `first_of_session: true` means exactly "this call produced
|
|
1855
1846
|
// the session name" — it is never true with a null name (an empty first
|
|
1856
1847
|
// declaration or a `--session-id` relay both keep the window open instead).
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
const
|
|
1848
|
+
// A native generation restart may rebuild the current cache during the
|
|
1849
|
+
// mutation and carry an already-seen suggestion from the prior generation.
|
|
1850
|
+
// That title is continuity evidence, not a new display request.
|
|
1851
|
+
const pendingName = sessionNameDisplayPending(hb);
|
|
1852
|
+
const firstSuggestedName = !priorHb?.suggested_session_name && pendingName ? pendingName : null;
|
|
1853
|
+
|
|
1854
|
+
// Routine focus declarations stay title-silent. A pending display no longer
|
|
1855
|
+
// turns every repeated set-task into a fresh name instruction; explicit
|
|
1856
|
+
// recovery belongs to `agents suggest-name --json`.
|
|
1857
|
+
const suggestedName = firstSuggestedName;
|
|
1858
|
+
const firstOfSession = firstSuggestedName !== null;
|
|
1859
|
+
const sessionNameRetry = false;
|
|
1862
1860
|
|
|
1863
1861
|
emit.data({
|
|
1864
1862
|
instance_id: myOwner,
|
|
@@ -1866,6 +1864,7 @@ function runSetTask(task: string, opts?: { sessionId?: string }): void {
|
|
|
1866
1864
|
task: hb?.task ?? null,
|
|
1867
1865
|
cleared: !task || task.length === 0,
|
|
1868
1866
|
first_of_session: firstOfSession,
|
|
1867
|
+
session_name_retry: sessionNameRetry,
|
|
1869
1868
|
suggested_session_name: suggestedName,
|
|
1870
1869
|
...(lifecycleWarning ? { warning: lifecycleWarning } : {}),
|
|
1871
1870
|
// Right-time instruction: the UserPromptSubmit nudge fires before the name
|
|
@@ -2093,12 +2092,16 @@ function runSuggestName(
|
|
|
2093
2092
|
|
|
2094
2093
|
const hb = readCurrentCoordinationRow(myOwner);
|
|
2095
2094
|
const agentName = hb?.name || "unknown";
|
|
2096
|
-
//
|
|
2097
|
-
//
|
|
2098
|
-
//
|
|
2099
|
-
//
|
|
2095
|
+
// A bare suggest-name must prefer the exact pending suggestion over rebuilding
|
|
2096
|
+
// from the current task. The task may have changed since the title was minted;
|
|
2097
|
+
// reconstructing it would produce a block the display latch can never accept.
|
|
2098
|
+
// An explicit description keeps the read-only re-suggest behavior.
|
|
2099
|
+
const pendingName = sessionNameDisplayPending(hb);
|
|
2100
2100
|
const parts = descriptionParts.length > 0 ? descriptionParts : hb?.task ? [hb.task] : [];
|
|
2101
|
-
const built =
|
|
2101
|
+
const built =
|
|
2102
|
+
descriptionParts.length === 0 && pendingName
|
|
2103
|
+
? { suggestedName: pendingName, description: hb?.task ?? pendingName }
|
|
2104
|
+
: buildSuggestedName(agentName, parts);
|
|
2102
2105
|
if (!built) {
|
|
2103
2106
|
emit.error({
|
|
2104
2107
|
code: "no_description",
|
|
@@ -2115,6 +2118,7 @@ function runSuggestName(
|
|
|
2115
2118
|
emit.data({
|
|
2116
2119
|
name: displayName,
|
|
2117
2120
|
suggested_session_name: suggestedName,
|
|
2121
|
+
session_name_retry: pendingName === suggestedName,
|
|
2118
2122
|
agent_name: agentName,
|
|
2119
2123
|
description,
|
|
2120
2124
|
});
|
|
@@ -2519,9 +2523,9 @@ function readContextUsage(
|
|
|
2519
2523
|
// Cursor doesn't surface token counts in its transcript JSONL but DOES
|
|
2520
2524
|
// store `contextUsagePercent` per composer in workspaceStorage's state.vscdb;
|
|
2521
2525
|
// readCursorContextUsage reads that via bun:sqlite (percent-only).
|
|
2522
|
-
if (platform === "codex") return
|
|
2526
|
+
if (platform === "codex") return readRuntimeContextUsage("codex", sessionId);
|
|
2523
2527
|
if (platform === "cursor") return readCursorContextUsage(sessionId);
|
|
2524
|
-
return
|
|
2528
|
+
return readRuntimeContextUsage("claude-code", sessionId);
|
|
2525
2529
|
}
|
|
2526
2530
|
|
|
2527
2531
|
function readCursorContextUsage(
|
|
@@ -2592,126 +2596,6 @@ function readCursorContextUsage(
|
|
|
2592
2596
|
return null;
|
|
2593
2597
|
}
|
|
2594
2598
|
|
|
2595
|
-
function readClaudeContextUsage(sessionId: string): { used: number; window: number } | null {
|
|
2596
|
-
const root = monorepoRoot();
|
|
2597
|
-
if (!root) return null;
|
|
2598
|
-
// Claude Code's project dir scheme: prepend "-", replace "/" → "-".
|
|
2599
|
-
const encoded = `-${root.replace(/^\//, "").replace(/\//g, "-")}`;
|
|
2600
|
-
const transcriptPath = resolve(homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
|
|
2601
|
-
if (!existsSync(transcriptPath)) return null;
|
|
2602
|
-
let raw: string;
|
|
2603
|
-
try {
|
|
2604
|
-
raw = readFileSync(transcriptPath, "utf8");
|
|
2605
|
-
} catch {
|
|
2606
|
-
return null;
|
|
2607
|
-
}
|
|
2608
|
-
const lines = raw.split("\n");
|
|
2609
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2610
|
-
const line = lines[i].trim();
|
|
2611
|
-
if (!line) continue;
|
|
2612
|
-
try {
|
|
2613
|
-
const entry = JSON.parse(line);
|
|
2614
|
-
const usage = entry?.message?.usage;
|
|
2615
|
-
if (entry?.type === "assistant" && usage) {
|
|
2616
|
-
const used =
|
|
2617
|
-
(usage.input_tokens ?? 0) +
|
|
2618
|
-
(usage.cache_creation_input_tokens ?? 0) +
|
|
2619
|
-
(usage.cache_read_input_tokens ?? 0);
|
|
2620
|
-
// Window: hardcode 1M for Opus 4.7 1M (current default). Refine to
|
|
2621
|
-
// model-aware lookup when other models become routine.
|
|
2622
|
-
return { used, window: 1000000 };
|
|
2623
|
-
}
|
|
2624
|
-
} catch {
|
|
2625
|
-
// skip malformed line
|
|
2626
|
-
}
|
|
2627
|
-
}
|
|
2628
|
-
return null;
|
|
2629
|
-
}
|
|
2630
|
-
|
|
2631
|
-
function readCodexContextUsage(sessionId: string): { used: number; window: number } | null {
|
|
2632
|
-
// Codex transcripts: ~/.codex/sessions/YYYY/MM/DD/rollout-<TS>-<sessionId>.jsonl
|
|
2633
|
-
// On WSL the active install often lives on the Windows side under
|
|
2634
|
-
// /mnt/c/Users/<user>/.codex/sessions/; both are searched.
|
|
2635
|
-
const path = findCodexTranscript(sessionId);
|
|
2636
|
-
if (!path) return null;
|
|
2637
|
-
let raw: string;
|
|
2638
|
-
try {
|
|
2639
|
-
raw = readFileSync(path, "utf8");
|
|
2640
|
-
} catch {
|
|
2641
|
-
return null;
|
|
2642
|
-
}
|
|
2643
|
-
const lines = raw.split("\n");
|
|
2644
|
-
// Walk backwards: most recent token_count event wins.
|
|
2645
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2646
|
-
const line = lines[i].trim();
|
|
2647
|
-
if (!line) continue;
|
|
2648
|
-
try {
|
|
2649
|
-
const entry = JSON.parse(line);
|
|
2650
|
-
if (entry?.type === "event_msg" && entry?.payload?.type === "token_count") {
|
|
2651
|
-
const info = entry.payload.info;
|
|
2652
|
-
const used = info?.last_token_usage?.input_tokens;
|
|
2653
|
-
const window = info?.model_context_window;
|
|
2654
|
-
if (typeof used === "number" && typeof window === "number" && window > 0) {
|
|
2655
|
-
return { used, window };
|
|
2656
|
-
}
|
|
2657
|
-
}
|
|
2658
|
-
} catch {
|
|
2659
|
-
// skip malformed line
|
|
2660
|
-
}
|
|
2661
|
-
}
|
|
2662
|
-
return null;
|
|
2663
|
-
}
|
|
2664
|
-
|
|
2665
|
-
function findCodexTranscript(sessionId: string): string | null {
|
|
2666
|
-
// Candidate roots in priority order.
|
|
2667
|
-
const homeRoot = resolve(homedir(), ".codex", "sessions");
|
|
2668
|
-
const wslRoots: string[] = [];
|
|
2669
|
-
try {
|
|
2670
|
-
if (existsSync("/mnt/c/Users")) {
|
|
2671
|
-
for (const entry of readdirSync("/mnt/c/Users")) {
|
|
2672
|
-
wslRoots.push(`/mnt/c/Users/${entry}/.codex/sessions`);
|
|
2673
|
-
}
|
|
2674
|
-
}
|
|
2675
|
-
} catch {
|
|
2676
|
-
// ignore
|
|
2677
|
-
}
|
|
2678
|
-
const roots = [homeRoot, ...wslRoots];
|
|
2679
|
-
const suffix = `-${sessionId}.jsonl`;
|
|
2680
|
-
for (const root of roots) {
|
|
2681
|
-
if (!existsSync(root)) continue;
|
|
2682
|
-
try {
|
|
2683
|
-
// Recursive scan: sessions are partitioned by YYYY/MM/DD/, so depth
|
|
2684
|
-
// is bounded at 3 + one file per session. Cheap enough at status time.
|
|
2685
|
-
const stack: string[] = [root];
|
|
2686
|
-
while (stack.length) {
|
|
2687
|
-
const dir = stack.pop()!;
|
|
2688
|
-
let entries: string[];
|
|
2689
|
-
try {
|
|
2690
|
-
entries = readdirSync(dir);
|
|
2691
|
-
} catch {
|
|
2692
|
-
continue;
|
|
2693
|
-
}
|
|
2694
|
-
for (const name of entries) {
|
|
2695
|
-
const full = `${dir}/${name}`;
|
|
2696
|
-
try {
|
|
2697
|
-
const stat = statSync(full);
|
|
2698
|
-
if (stat.isDirectory()) {
|
|
2699
|
-
stack.push(full);
|
|
2700
|
-
} else if (name.endsWith(suffix)) {
|
|
2701
|
-
return full;
|
|
2702
|
-
}
|
|
2703
|
-
} catch {
|
|
2704
|
-
// ignore
|
|
2705
|
-
}
|
|
2706
|
-
}
|
|
2707
|
-
}
|
|
2708
|
-
} catch {
|
|
2709
|
-
// ignore root scan failures
|
|
2710
|
-
}
|
|
2711
|
-
}
|
|
2712
|
-
return null;
|
|
2713
|
-
}
|
|
2714
|
-
|
|
2715
2599
|
function formatLocalTime(d: Date): string {
|
|
2716
2600
|
// "Sat, May 9, 2026, 3:48 AM CDT", rendered in the Chicago timezone.
|
|
2717
2601
|
return new Intl.DateTimeFormat("en-US", {
|
|
@@ -3119,6 +3003,15 @@ interface HealthReport {
|
|
|
3119
3003
|
// V3 event-ledger producer health: open tool spans, pending finalization
|
|
3120
3004
|
// requests, intake/diagnostics spool depth, span-count pressure.
|
|
3121
3005
|
event_ledger: EventLedgerHealthV3;
|
|
3006
|
+
// Stop-hook remediation cap exhaustions in the window: sessions whose
|
|
3007
|
+
// end-of-turn evidence never landed, so the hook stopped bouncing them and
|
|
3008
|
+
// let the turn end unenforced. Each one is a session that could not comply —
|
|
3009
|
+
// the signal that used to be visible only as a repeating notification sound.
|
|
3010
|
+
stop_remediation: {
|
|
3011
|
+
total: number;
|
|
3012
|
+
latest_at: string | null;
|
|
3013
|
+
sessions: string[];
|
|
3014
|
+
};
|
|
3122
3015
|
anomalies: string[];
|
|
3123
3016
|
}
|
|
3124
3017
|
|
|
@@ -3325,6 +3218,50 @@ export function collectEventLedgerHealthV3(root: string, nowMs = Date.now()): Ev
|
|
|
3325
3218
|
};
|
|
3326
3219
|
}
|
|
3327
3220
|
|
|
3221
|
+
/**
|
|
3222
|
+
* Tally stop-hook remediation-cap exhaustions in the window from
|
|
3223
|
+
* `.harnery/debug/agent-hook.ndjson` (rows with
|
|
3224
|
+
* `skipped: "stop-remediation-cap-exhausted"`). Each one is a session whose
|
|
3225
|
+
* end-of-turn evidence never landed, so the Stop hook gave up bouncing it and
|
|
3226
|
+
* let the turn end unenforced — a compliance gap an operator should see
|
|
3227
|
+
* without grepping the debug ledger.
|
|
3228
|
+
*/
|
|
3229
|
+
export function readStopRemediationExhaustions(
|
|
3230
|
+
root: string,
|
|
3231
|
+
cutoffMs: number,
|
|
3232
|
+
): { total: number; latestAt: string | null; sessions: string[] } {
|
|
3233
|
+
const p = resolve(root, ".harnery", "debug", "agent-hook.ndjson");
|
|
3234
|
+
if (!existsSync(p)) return { total: 0, latestAt: null, sessions: [] };
|
|
3235
|
+
let raw: string;
|
|
3236
|
+
try {
|
|
3237
|
+
raw = readFileSync(p, "utf8");
|
|
3238
|
+
} catch {
|
|
3239
|
+
return { total: 0, latestAt: null, sessions: [] };
|
|
3240
|
+
}
|
|
3241
|
+
let total = 0;
|
|
3242
|
+
let latestMs = Number.NEGATIVE_INFINITY;
|
|
3243
|
+
const sessions = new Set<string>();
|
|
3244
|
+
for (const line of raw.split("\n")) {
|
|
3245
|
+
if (!line.includes("stop-remediation-cap-exhausted")) continue;
|
|
3246
|
+
try {
|
|
3247
|
+
const e = JSON.parse(line) as { ts?: string; skipped?: string; session_id?: string };
|
|
3248
|
+
if (e.skipped !== "stop-remediation-cap-exhausted") continue;
|
|
3249
|
+
const tsMs = e.ts ? Date.parse(e.ts) : Number.NaN;
|
|
3250
|
+
if (!Number.isFinite(tsMs) || tsMs < cutoffMs) continue;
|
|
3251
|
+
total++;
|
|
3252
|
+
latestMs = Math.max(latestMs, tsMs);
|
|
3253
|
+
if (e.session_id && sessions.size < 5) sessions.add(e.session_id);
|
|
3254
|
+
} catch {
|
|
3255
|
+
/* skip malformed */
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
return {
|
|
3259
|
+
total,
|
|
3260
|
+
latestAt: Number.isFinite(latestMs) ? new Date(latestMs).toISOString() : null,
|
|
3261
|
+
sessions: [...sessions],
|
|
3262
|
+
};
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3328
3265
|
/** Tally agent-hook failures (.harnery/debug/agent-hook.errors.ndjson) in the
|
|
3329
3266
|
* window by exact error and phase, while separating current-hour failures from
|
|
3330
3267
|
* historical rows. Each line is {ts, error, phase, ...}. */
|
|
@@ -3885,6 +3822,7 @@ function runHealth(opts: { since: string; json?: boolean }): void {
|
|
|
3885
3822
|
}
|
|
3886
3823
|
|
|
3887
3824
|
const hookErrors = readHookErrors(root, cutoffMs, nowMs);
|
|
3825
|
+
const stopRemediation = readStopRemediationExhaustions(root, cutoffMs);
|
|
3888
3826
|
const stream = readStreamStats(root);
|
|
3889
3827
|
const eventLedger = collectEventLedgerHealthV3(root);
|
|
3890
3828
|
|
|
@@ -4004,6 +3942,15 @@ function runHealth(opts: { since: string; json?: boolean }): void {
|
|
|
4004
3942
|
`${zombieCount} zombie heartbeat(s) in active/ (${zombieSamples.join(", ")}); broken files the sweep isn't reaping`,
|
|
4005
3943
|
);
|
|
4006
3944
|
}
|
|
3945
|
+
if (stopRemediation.total > 0) {
|
|
3946
|
+
const who =
|
|
3947
|
+
stopRemediation.sessions.length > 0
|
|
3948
|
+
? ` (sessions: ${stopRemediation.sessions.map((s) => s.slice(0, 8)).join(", ")})`
|
|
3949
|
+
: "";
|
|
3950
|
+
anomalies.push(
|
|
3951
|
+
`stop-hook remediation cap exhausted ${stopRemediation.total}x in ${opts.since}${who}; these sessions ended turns without the end-of-turn ritual — check why their evidence never lands`,
|
|
3952
|
+
);
|
|
3953
|
+
}
|
|
4007
3954
|
if (eventLedger.state === "live") {
|
|
4008
3955
|
// Open spans whose generation has no open turn: the turn ended without its
|
|
4009
3956
|
// tool spans closing, so an explicit end can only queue, never finalize.
|
|
@@ -4072,6 +4019,11 @@ function runHealth(opts: { since: string; json?: boolean }): void {
|
|
|
4072
4019
|
stream,
|
|
4073
4020
|
zombies: { count: zombieCount, samples: zombieSamples },
|
|
4074
4021
|
event_ledger: eventLedger,
|
|
4022
|
+
stop_remediation: {
|
|
4023
|
+
total: stopRemediation.total,
|
|
4024
|
+
latest_at: stopRemediation.latestAt,
|
|
4025
|
+
sessions: stopRemediation.sessions,
|
|
4026
|
+
},
|
|
4075
4027
|
anomalies,
|
|
4076
4028
|
};
|
|
4077
4029
|
|
package/src/commands/browse.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
parseAssertSpec,
|
|
25
25
|
type RuntsResult,
|
|
26
26
|
runCritique,
|
|
27
|
+
type StandaloneHtmlResult,
|
|
27
28
|
type TargetSizeProfile,
|
|
28
29
|
type TargetSizeResult,
|
|
29
30
|
tilesFromFullPage,
|
|
@@ -1529,9 +1530,22 @@ async function runTrioMode(
|
|
|
1529
1530
|
throw new Error("--capture-evaluate requires a screenshot; remove --no-screenshot.");
|
|
1530
1531
|
}
|
|
1531
1532
|
|
|
1533
|
+
// A trio `.html` is opened in a browser later — from the files origin, from
|
|
1534
|
+
// another machine, from disk — so it has to carry its own CSS and media. A
|
|
1535
|
+
// selector capture is a DOM fragment, not a document, and stays raw.
|
|
1532
1536
|
const htmlPath = `${prefix}.html`;
|
|
1533
|
-
|
|
1534
|
-
|
|
1537
|
+
let snapshot: StandaloneHtmlResult | undefined;
|
|
1538
|
+
if (opts.selector) {
|
|
1539
|
+
writeFileSync(htmlPath, await browser.htmlContent(opts.selector));
|
|
1540
|
+
} else {
|
|
1541
|
+
try {
|
|
1542
|
+
snapshot = await browser.standaloneHtml();
|
|
1543
|
+
writeFileSync(htmlPath, snapshot.html);
|
|
1544
|
+
} catch {
|
|
1545
|
+
// Inlining is best-effort; a raw serialization beats no snapshot at all.
|
|
1546
|
+
writeFileSync(htmlPath, await browser.htmlContent());
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1535
1549
|
written.push(htmlPath);
|
|
1536
1550
|
|
|
1537
1551
|
const diag = browser.diagnostics();
|
|
@@ -1548,6 +1562,15 @@ async function runTrioMode(
|
|
|
1548
1562
|
...summarizeDiagnostics(diag),
|
|
1549
1563
|
};
|
|
1550
1564
|
if (pngBytes !== undefined) envelope.screenshotBytes = pngBytes;
|
|
1565
|
+
if (snapshot) {
|
|
1566
|
+
envelope.htmlSnapshot = {
|
|
1567
|
+
stylesheetsInlined: snapshot.stylesheetsInlined,
|
|
1568
|
+
stylesheetsLinked: snapshot.stylesheetsLinked,
|
|
1569
|
+
resourcesInlined: snapshot.resourcesInlined,
|
|
1570
|
+
resourcesLinked: snapshot.resourcesLinked,
|
|
1571
|
+
inlinedBytes: snapshot.inlinedBytes,
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1551
1574
|
if (opts.evaluate) envelope.eval = evalResult;
|
|
1552
1575
|
if (opts.captureEvaluate) {
|
|
1553
1576
|
envelope.captureEval = captureEvalResult;
|
package/src/commands/decision.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import type { Command } from "commander";
|
|
3
3
|
import type { EmitContext } from "../commander.ts";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
emitEventV3,
|
|
6
|
+
monorepoRoot,
|
|
7
|
+
nativeSessionIdentity,
|
|
8
|
+
normalizeAdapter,
|
|
9
|
+
resolveOwner,
|
|
10
|
+
} from "../core/agents/index.ts";
|
|
5
11
|
import { readLiveCoordinationRow } from "../core/agents/state/live-coordination-view.ts";
|
|
6
12
|
import { resolveBinName } from "../core/config.ts";
|
|
7
13
|
import {
|
|
@@ -442,7 +448,7 @@ function emitDecisionEvent(
|
|
|
442
448
|
const hb = root ? readLiveCoordinationRow(root, owner) : null;
|
|
443
449
|
emitEventV3({
|
|
444
450
|
owner,
|
|
445
|
-
session: hb
|
|
451
|
+
session: nativeSessionIdentity(hb, owner),
|
|
446
452
|
adapter: normalizeAdapter(hb?.platform),
|
|
447
453
|
observation: {
|
|
448
454
|
event_type: "decision.state_changed",
|
package/src/commands/events.ts
CHANGED
|
@@ -25,6 +25,7 @@ interface GenerationLatencyRow {
|
|
|
25
25
|
tool_ms: LatencyMetricV3;
|
|
26
26
|
command_ms: LatencyMetricV3;
|
|
27
27
|
wait_ms: LatencyMetricV3;
|
|
28
|
+
agent_action_ms: LatencyMetricV3;
|
|
28
29
|
inference_ms: LatencyMetricV3;
|
|
29
30
|
harness_ms: LatencyMetricV3;
|
|
30
31
|
residual_ms: LatencyMetricV3;
|
|
@@ -112,6 +113,9 @@ function summarizeByGeneration(turns: TurnLatencyV3[]): GenerationLatencyRow[] {
|
|
|
112
113
|
tool_ms: sumMetrics(group.map(({ tool_ms }) => tool_ms)),
|
|
113
114
|
command_ms: sumMetrics(group.map(({ command_exclusive_ms }) => command_exclusive_ms)),
|
|
114
115
|
wait_ms: sumMetrics(group.map(({ wait_ms }) => wait_ms)),
|
|
116
|
+
agent_action_ms: sumMetrics(
|
|
117
|
+
group.map(({ response_latency }) => response_latency.agent_action_ms),
|
|
118
|
+
),
|
|
115
119
|
inference_ms: sumMetrics(group.map(({ inference_ms }) => inference_ms)),
|
|
116
120
|
harness_ms: sumMetrics(group.map(({ harness_ms }) => harness_ms)),
|
|
117
121
|
residual_ms: sumMetrics(group.map(({ residual_ms }) => residual_ms)),
|
|
@@ -180,6 +184,8 @@ function renderTurns(turns: TurnLatencyV3[]): string {
|
|
|
180
184
|
"TOOL",
|
|
181
185
|
"COMMAND",
|
|
182
186
|
"WAIT",
|
|
187
|
+
"ACTION",
|
|
188
|
+
"POST-TOOL",
|
|
183
189
|
"INFERENCE",
|
|
184
190
|
"HARNESS",
|
|
185
191
|
"RESIDUAL",
|
|
@@ -192,6 +198,8 @@ function renderTurns(turns: TurnLatencyV3[]): string {
|
|
|
192
198
|
formatMetric(turn.tool_ms),
|
|
193
199
|
formatMetric(turn.command_exclusive_ms),
|
|
194
200
|
formatMetric(turn.wait_ms),
|
|
201
|
+
formatMetric(turn.response_latency.agent_action_ms),
|
|
202
|
+
formatMetric(turn.response_latency.post_tool_response_ms),
|
|
195
203
|
formatMetric(turn.inference_ms),
|
|
196
204
|
formatMetric(turn.harness_ms),
|
|
197
205
|
formatMetric(turn.residual_ms),
|
|
@@ -202,7 +210,18 @@ function renderTurns(turns: TurnLatencyV3[]): string {
|
|
|
202
210
|
|
|
203
211
|
function renderGenerations(rows: GenerationLatencyRow[]): string {
|
|
204
212
|
return renderTable(
|
|
205
|
-
[
|
|
213
|
+
[
|
|
214
|
+
"GENERATION",
|
|
215
|
+
"TURNS",
|
|
216
|
+
"WALL",
|
|
217
|
+
"TOOL",
|
|
218
|
+
"COMMAND",
|
|
219
|
+
"WAIT",
|
|
220
|
+
"ACTION",
|
|
221
|
+
"INFERENCE",
|
|
222
|
+
"HARNESS",
|
|
223
|
+
"RESIDUAL",
|
|
224
|
+
],
|
|
206
225
|
rows.map((row) => [
|
|
207
226
|
row.generation_id,
|
|
208
227
|
String(row.turns),
|
|
@@ -210,6 +229,7 @@ function renderGenerations(rows: GenerationLatencyRow[]): string {
|
|
|
210
229
|
formatMetric(row.tool_ms),
|
|
211
230
|
formatMetric(row.command_ms),
|
|
212
231
|
formatMetric(row.wait_ms),
|
|
232
|
+
formatMetric(row.agent_action_ms),
|
|
213
233
|
formatMetric(row.inference_ms),
|
|
214
234
|
formatMetric(row.harness_ms),
|
|
215
235
|
formatMetric(row.residual_ms),
|
package/src/commands/presence.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
import type { EmitContext } from "../commander.ts";
|
|
3
3
|
import { monorepoRoot } from "../core/agents/coord-client.ts";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
emitEventV3,
|
|
6
|
+
nativeSessionIdentity,
|
|
7
|
+
normalizeAdapter,
|
|
8
|
+
resolveOwner,
|
|
9
|
+
} from "../core/agents/index.ts";
|
|
5
10
|
import { readLiveCoordinationRow } from "../core/agents/state/live-coordination-view.ts";
|
|
6
11
|
import {
|
|
7
12
|
fetchPresence,
|
|
@@ -253,7 +258,7 @@ function emitPresenceChange(
|
|
|
253
258
|
const hb = root ? readLiveCoordinationRow(root, owner) : null;
|
|
254
259
|
emitEventV3({
|
|
255
260
|
owner,
|
|
256
|
-
session: hb
|
|
261
|
+
session: nativeSessionIdentity(hb, owner),
|
|
257
262
|
adapter: normalizeAdapter(hb?.platform),
|
|
258
263
|
observation: {
|
|
259
264
|
event_type: "coord.presence_changed",
|