harnery 0.34.1 → 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 +89 -175
- 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 +1 -0
- package/dist/core/agents/session-name-display.d.ts.map +1 -1
- package/dist/core/agents/session-name-display.js +8 -1
- 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 +111 -36
- 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 +22 -10
- 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 +102 -167
- 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 +9 -1
- 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 +123 -39
- package/src/core/hooks/remediation-cap.ts +77 -0
- package/src/core/hooks/resolve/transcript.ts +37 -11
- 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,27 +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
|
-
|
|
1862
|
-
|
|
1863
|
-
//
|
|
1864
|
-
//
|
|
1865
|
-
//
|
|
1866
|
-
|
|
1867
|
-
// display was seen; it only gives the next exact block new mint evidence.
|
|
1868
|
-
const retriedSuggestedName =
|
|
1869
|
-
!firstSuggestedName &&
|
|
1870
|
-
priorHb?.suggested_session_name &&
|
|
1871
|
-
priorHb.session_name_seen_for !== priorHb.suggested_session_name &&
|
|
1872
|
-
hb?.suggested_session_name === priorHb.suggested_session_name
|
|
1873
|
-
? hb.suggested_session_name
|
|
1874
|
-
: null;
|
|
1875
|
-
const suggestedName = firstSuggestedName ?? retriedSuggestedName;
|
|
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;
|
|
1876
1858
|
const firstOfSession = firstSuggestedName !== null;
|
|
1877
|
-
const sessionNameRetry =
|
|
1859
|
+
const sessionNameRetry = false;
|
|
1878
1860
|
|
|
1879
1861
|
emit.data({
|
|
1880
1862
|
instance_id: myOwner,
|
|
@@ -2110,12 +2092,16 @@ function runSuggestName(
|
|
|
2110
2092
|
|
|
2111
2093
|
const hb = readCurrentCoordinationRow(myOwner);
|
|
2112
2094
|
const agentName = hb?.name || "unknown";
|
|
2113
|
-
//
|
|
2114
|
-
//
|
|
2115
|
-
//
|
|
2116
|
-
//
|
|
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);
|
|
2117
2100
|
const parts = descriptionParts.length > 0 ? descriptionParts : hb?.task ? [hb.task] : [];
|
|
2118
|
-
const built =
|
|
2101
|
+
const built =
|
|
2102
|
+
descriptionParts.length === 0 && pendingName
|
|
2103
|
+
? { suggestedName: pendingName, description: hb?.task ?? pendingName }
|
|
2104
|
+
: buildSuggestedName(agentName, parts);
|
|
2119
2105
|
if (!built) {
|
|
2120
2106
|
emit.error({
|
|
2121
2107
|
code: "no_description",
|
|
@@ -2132,6 +2118,7 @@ function runSuggestName(
|
|
|
2132
2118
|
emit.data({
|
|
2133
2119
|
name: displayName,
|
|
2134
2120
|
suggested_session_name: suggestedName,
|
|
2121
|
+
session_name_retry: pendingName === suggestedName,
|
|
2135
2122
|
agent_name: agentName,
|
|
2136
2123
|
description,
|
|
2137
2124
|
});
|
|
@@ -2536,9 +2523,9 @@ function readContextUsage(
|
|
|
2536
2523
|
// Cursor doesn't surface token counts in its transcript JSONL but DOES
|
|
2537
2524
|
// store `contextUsagePercent` per composer in workspaceStorage's state.vscdb;
|
|
2538
2525
|
// readCursorContextUsage reads that via bun:sqlite (percent-only).
|
|
2539
|
-
if (platform === "codex") return
|
|
2526
|
+
if (platform === "codex") return readRuntimeContextUsage("codex", sessionId);
|
|
2540
2527
|
if (platform === "cursor") return readCursorContextUsage(sessionId);
|
|
2541
|
-
return
|
|
2528
|
+
return readRuntimeContextUsage("claude-code", sessionId);
|
|
2542
2529
|
}
|
|
2543
2530
|
|
|
2544
2531
|
function readCursorContextUsage(
|
|
@@ -2609,126 +2596,6 @@ function readCursorContextUsage(
|
|
|
2609
2596
|
return null;
|
|
2610
2597
|
}
|
|
2611
2598
|
|
|
2612
|
-
function readClaudeContextUsage(sessionId: string): { used: number; window: number } | null {
|
|
2613
|
-
const root = monorepoRoot();
|
|
2614
|
-
if (!root) return null;
|
|
2615
|
-
// Claude Code's project dir scheme: prepend "-", replace "/" → "-".
|
|
2616
|
-
const encoded = `-${root.replace(/^\//, "").replace(/\//g, "-")}`;
|
|
2617
|
-
const transcriptPath = resolve(homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
|
|
2618
|
-
if (!existsSync(transcriptPath)) return null;
|
|
2619
|
-
let raw: string;
|
|
2620
|
-
try {
|
|
2621
|
-
raw = readFileSync(transcriptPath, "utf8");
|
|
2622
|
-
} catch {
|
|
2623
|
-
return null;
|
|
2624
|
-
}
|
|
2625
|
-
const lines = raw.split("\n");
|
|
2626
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2627
|
-
const line = lines[i].trim();
|
|
2628
|
-
if (!line) continue;
|
|
2629
|
-
try {
|
|
2630
|
-
const entry = JSON.parse(line);
|
|
2631
|
-
const usage = entry?.message?.usage;
|
|
2632
|
-
if (entry?.type === "assistant" && usage) {
|
|
2633
|
-
const used =
|
|
2634
|
-
(usage.input_tokens ?? 0) +
|
|
2635
|
-
(usage.cache_creation_input_tokens ?? 0) +
|
|
2636
|
-
(usage.cache_read_input_tokens ?? 0);
|
|
2637
|
-
// Window: hardcode 1M for Opus 4.7 1M (current default). Refine to
|
|
2638
|
-
// model-aware lookup when other models become routine.
|
|
2639
|
-
return { used, window: 1000000 };
|
|
2640
|
-
}
|
|
2641
|
-
} catch {
|
|
2642
|
-
// skip malformed line
|
|
2643
|
-
}
|
|
2644
|
-
}
|
|
2645
|
-
return null;
|
|
2646
|
-
}
|
|
2647
|
-
|
|
2648
|
-
function readCodexContextUsage(sessionId: string): { used: number; window: number } | null {
|
|
2649
|
-
// Codex transcripts: ~/.codex/sessions/YYYY/MM/DD/rollout-<TS>-<sessionId>.jsonl
|
|
2650
|
-
// On WSL the active install often lives on the Windows side under
|
|
2651
|
-
// /mnt/c/Users/<user>/.codex/sessions/; both are searched.
|
|
2652
|
-
const path = findCodexTranscript(sessionId);
|
|
2653
|
-
if (!path) return null;
|
|
2654
|
-
let raw: string;
|
|
2655
|
-
try {
|
|
2656
|
-
raw = readFileSync(path, "utf8");
|
|
2657
|
-
} catch {
|
|
2658
|
-
return null;
|
|
2659
|
-
}
|
|
2660
|
-
const lines = raw.split("\n");
|
|
2661
|
-
// Walk backwards: most recent token_count event wins.
|
|
2662
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2663
|
-
const line = lines[i].trim();
|
|
2664
|
-
if (!line) continue;
|
|
2665
|
-
try {
|
|
2666
|
-
const entry = JSON.parse(line);
|
|
2667
|
-
if (entry?.type === "event_msg" && entry?.payload?.type === "token_count") {
|
|
2668
|
-
const info = entry.payload.info;
|
|
2669
|
-
const used = info?.last_token_usage?.input_tokens;
|
|
2670
|
-
const window = info?.model_context_window;
|
|
2671
|
-
if (typeof used === "number" && typeof window === "number" && window > 0) {
|
|
2672
|
-
return { used, window };
|
|
2673
|
-
}
|
|
2674
|
-
}
|
|
2675
|
-
} catch {
|
|
2676
|
-
// skip malformed line
|
|
2677
|
-
}
|
|
2678
|
-
}
|
|
2679
|
-
return null;
|
|
2680
|
-
}
|
|
2681
|
-
|
|
2682
|
-
function findCodexTranscript(sessionId: string): string | null {
|
|
2683
|
-
// Candidate roots in priority order.
|
|
2684
|
-
const homeRoot = resolve(homedir(), ".codex", "sessions");
|
|
2685
|
-
const wslRoots: string[] = [];
|
|
2686
|
-
try {
|
|
2687
|
-
if (existsSync("/mnt/c/Users")) {
|
|
2688
|
-
for (const entry of readdirSync("/mnt/c/Users")) {
|
|
2689
|
-
wslRoots.push(`/mnt/c/Users/${entry}/.codex/sessions`);
|
|
2690
|
-
}
|
|
2691
|
-
}
|
|
2692
|
-
} catch {
|
|
2693
|
-
// ignore
|
|
2694
|
-
}
|
|
2695
|
-
const roots = [homeRoot, ...wslRoots];
|
|
2696
|
-
const suffix = `-${sessionId}.jsonl`;
|
|
2697
|
-
for (const root of roots) {
|
|
2698
|
-
if (!existsSync(root)) continue;
|
|
2699
|
-
try {
|
|
2700
|
-
// Recursive scan: sessions are partitioned by YYYY/MM/DD/, so depth
|
|
2701
|
-
// is bounded at 3 + one file per session. Cheap enough at status time.
|
|
2702
|
-
const stack: string[] = [root];
|
|
2703
|
-
while (stack.length) {
|
|
2704
|
-
const dir = stack.pop()!;
|
|
2705
|
-
let entries: string[];
|
|
2706
|
-
try {
|
|
2707
|
-
entries = readdirSync(dir);
|
|
2708
|
-
} catch {
|
|
2709
|
-
continue;
|
|
2710
|
-
}
|
|
2711
|
-
for (const name of entries) {
|
|
2712
|
-
const full = `${dir}/${name}`;
|
|
2713
|
-
try {
|
|
2714
|
-
const stat = statSync(full);
|
|
2715
|
-
if (stat.isDirectory()) {
|
|
2716
|
-
stack.push(full);
|
|
2717
|
-
} else if (name.endsWith(suffix)) {
|
|
2718
|
-
return full;
|
|
2719
|
-
}
|
|
2720
|
-
} catch {
|
|
2721
|
-
// ignore
|
|
2722
|
-
}
|
|
2723
|
-
}
|
|
2724
|
-
}
|
|
2725
|
-
} catch {
|
|
2726
|
-
// ignore root scan failures
|
|
2727
|
-
}
|
|
2728
|
-
}
|
|
2729
|
-
return null;
|
|
2730
|
-
}
|
|
2731
|
-
|
|
2732
2599
|
function formatLocalTime(d: Date): string {
|
|
2733
2600
|
// "Sat, May 9, 2026, 3:48 AM CDT", rendered in the Chicago timezone.
|
|
2734
2601
|
return new Intl.DateTimeFormat("en-US", {
|
|
@@ -3136,6 +3003,15 @@ interface HealthReport {
|
|
|
3136
3003
|
// V3 event-ledger producer health: open tool spans, pending finalization
|
|
3137
3004
|
// requests, intake/diagnostics spool depth, span-count pressure.
|
|
3138
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
|
+
};
|
|
3139
3015
|
anomalies: string[];
|
|
3140
3016
|
}
|
|
3141
3017
|
|
|
@@ -3342,6 +3218,50 @@ export function collectEventLedgerHealthV3(root: string, nowMs = Date.now()): Ev
|
|
|
3342
3218
|
};
|
|
3343
3219
|
}
|
|
3344
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
|
+
|
|
3345
3265
|
/** Tally agent-hook failures (.harnery/debug/agent-hook.errors.ndjson) in the
|
|
3346
3266
|
* window by exact error and phase, while separating current-hour failures from
|
|
3347
3267
|
* historical rows. Each line is {ts, error, phase, ...}. */
|
|
@@ -3902,6 +3822,7 @@ function runHealth(opts: { since: string; json?: boolean }): void {
|
|
|
3902
3822
|
}
|
|
3903
3823
|
|
|
3904
3824
|
const hookErrors = readHookErrors(root, cutoffMs, nowMs);
|
|
3825
|
+
const stopRemediation = readStopRemediationExhaustions(root, cutoffMs);
|
|
3905
3826
|
const stream = readStreamStats(root);
|
|
3906
3827
|
const eventLedger = collectEventLedgerHealthV3(root);
|
|
3907
3828
|
|
|
@@ -4021,6 +3942,15 @@ function runHealth(opts: { since: string; json?: boolean }): void {
|
|
|
4021
3942
|
`${zombieCount} zombie heartbeat(s) in active/ (${zombieSamples.join(", ")}); broken files the sweep isn't reaping`,
|
|
4022
3943
|
);
|
|
4023
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
|
+
}
|
|
4024
3954
|
if (eventLedger.state === "live") {
|
|
4025
3955
|
// Open spans whose generation has no open turn: the turn ended without its
|
|
4026
3956
|
// tool spans closing, so an explicit end can only queue, never finalize.
|
|
@@ -4089,6 +4019,11 @@ function runHealth(opts: { since: string; json?: boolean }): void {
|
|
|
4089
4019
|
stream,
|
|
4090
4020
|
zombies: { count: zombieCount, samples: zombieSamples },
|
|
4091
4021
|
event_ledger: eventLedger,
|
|
4022
|
+
stop_remediation: {
|
|
4023
|
+
total: stopRemediation.total,
|
|
4024
|
+
latest_at: stopRemediation.latestAt,
|
|
4025
|
+
sessions: stopRemediation.sessions,
|
|
4026
|
+
},
|
|
4092
4027
|
anomalies,
|
|
4093
4028
|
};
|
|
4094
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",
|