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/dist/core/hooks/cli.js
CHANGED
|
@@ -24,19 +24,20 @@ import { buildInstructionBundle } from "../../lib/instructions/bundle.js";
|
|
|
24
24
|
import { coordBinPath } from "../agents/coord-bin.js";
|
|
25
25
|
import { classifyWriteClaimFinalization, formatWriteClaimFinalizationDenial, } from "../agents/finalization.js";
|
|
26
26
|
import { evaluateStopHook, STOP_REMEDIATION_MARKER } from "../agents/rules/stop-hook.js";
|
|
27
|
-
import { assistantTextStartsWithSessionNameBlock, isSessionNameRemediationCommand, sessionNameDisplayInstruction, sessionNameDisplayPending, toolResponseMintedSessionName, } from "../agents/session-name-display.js";
|
|
27
|
+
import { assistantTextStartsWithSessionNameBlock, isSessionNameRemediationCommand, sessionNameDisplayInstruction, sessionNameDisplayPending, sessionNameDisplayRecoveryInstruction, toolResponseMintedSessionName, } from "../agents/session-name-display.js";
|
|
28
28
|
import { stampSessionNameSeen } from "../agents/state/heartbeat-writer.js";
|
|
29
|
-
import { readLiveCoordinationRow } from "../agents/state/live-coordination-view.js";
|
|
29
|
+
import { ensureLiveCoordinationHeartbeat, readLiveCoordinationRow, } from "../agents/state/live-coordination-view.js";
|
|
30
30
|
import { writePidmapRow } from "../agents/state/pidmap.js";
|
|
31
31
|
import { agentsRequireGitFinalization, resolveBinName } from "../config.js";
|
|
32
32
|
import { checkpointContext, completeContextRecovery, extractContextSample, markContextCompactionCompleted, prepareContextRecovery, readContextState, recordContextSample, } from "../context/index.js";
|
|
33
33
|
import { tryWriteLiveDisplayV3 } from "../events/v3/live-feed.js";
|
|
34
|
-
import { recordLiveHookSignalV3, resolveLiveEventLedgerRouteV3, } from "../events/v3/live-routing.js";
|
|
34
|
+
import { liveHookSignalDefersDrainV3, recordLiveDelegatedChildSessionV3, recordLiveHookSignalV3, resolveLiveEventLedgerRouteV3, } from "../events/v3/live-routing.js";
|
|
35
35
|
import { captureSpanClockV3 } from "../events/v3/span-state.js";
|
|
36
36
|
import { fetchPresence } from "../presence/index.js";
|
|
37
37
|
import { stableScopeId } from "../workflow/scope-id.js";
|
|
38
38
|
import { detectAdapter, shouldSkipHookAdapter } from "./adapter/detect.js";
|
|
39
39
|
import { extractBashCommand, extractToolDescription, normalizeEventName, parsePayload, } from "./adapter/parse.js";
|
|
40
|
+
import { discoverCodexSessionTranscript } from "./adapter/runtime-telemetry.js";
|
|
40
41
|
import { codexWslFileLinkTelemetry, inspectCodexWslBridge, isWslUncPath, renderCodexWslFileLinkContext, } from "./codex-wsl-bridge.js";
|
|
41
42
|
import { captureImages, detectPresence, imageJanitor, journalArchive, journalJanitor, journalRecoveryCue, playSound, recordImageArtifactsV3, resetSoundCounters, runSessionSyncExtension, soundForEvent, } from "./effects/index.js";
|
|
42
43
|
import { toolInputHash, toolTargetHash } from "./events/input-hash.js";
|
|
@@ -45,7 +46,7 @@ import { adapterPidFromEnv, parsePsChainLine, selectAnchorPid } from "./resolve/
|
|
|
45
46
|
import { findCoordRoot } from "./resolve/coord-root.js";
|
|
46
47
|
import { extractIntentComment, resolveIntent } from "./resolve/intent.js";
|
|
47
48
|
import { resolveOwner } from "./resolve/owner.js";
|
|
48
|
-
import { inspectSessionNameDisplayImmediately, scanAssistantTextIncludes, scanSessionNameDisplayedImmediately, scanStatusBoxPresent,
|
|
49
|
+
import { inspectSessionNameDisplayImmediately, scanAssistantTextIncludes, scanSessionNameDisplayedImmediately, scanStatusBoxPresent, scanTranscriptRuntime, } from "./resolve/transcript.js";
|
|
49
50
|
import { sessionNamePresence } from "./session-name-presence.js";
|
|
50
51
|
import { unsafeCrossShellReason } from "./unsafe-cross-shell.js";
|
|
51
52
|
function parseArgv(argv) {
|
|
@@ -221,7 +222,7 @@ function buildEventData(eventType, ctx) {
|
|
|
221
222
|
// Claude Code's SessionStart payload omits `model` (Codex + Cursor
|
|
222
223
|
// supply it). Fall back to the transcript, populated on `resume`, and
|
|
223
224
|
// backfilled later by `turn.completed` for a fresh startup session.
|
|
224
|
-
model: p?.model ??
|
|
225
|
+
model: p?.model ?? scanTranscriptRuntime(p?.transcript_path)?.model,
|
|
225
226
|
pid: adapterPid,
|
|
226
227
|
source: p?.source,
|
|
227
228
|
platform: adapterPlatform,
|
|
@@ -282,7 +283,7 @@ function buildEventData(eventType, ctx) {
|
|
|
282
283
|
// Backfill the model for adapters that omit it at session.started
|
|
283
284
|
// (Claude Code). The transcript is populated with assistant turns by
|
|
284
285
|
// Stop-hook time, so this resolves even for fresh `startup` sessions.
|
|
285
|
-
model: p?.model ??
|
|
286
|
+
model: p?.model ?? scanTranscriptRuntime(p?.transcript_path)?.model,
|
|
286
287
|
// Phase 2: tool_call_count + text_length aren't cheaply available
|
|
287
288
|
// from the Stop payload alone (they'd require a transcript scan that
|
|
288
289
|
// races with the JSONL flush). Emit `-1` / `0` sentinels and let
|
|
@@ -296,22 +297,26 @@ function buildEventData(eventType, ctx) {
|
|
|
296
297
|
// the verdict now sees because agent-hook emits this turn.completed itself
|
|
297
298
|
// (the previous path passed those via the no-history fail-open).
|
|
298
299
|
status_box_present: scanStatusBoxPresent(p?.transcript_path) || lastAssistantMessage.includes("┌─ agent-"),
|
|
299
|
-
//
|
|
300
|
+
// Assistant-text evidence for the rule-2/3 detector: the loose scan above
|
|
300
301
|
// matches the box prefix ANYWHERE in the transcript tail, including
|
|
301
302
|
// the tool_result row the status command itself writes, so it can
|
|
302
303
|
// report a paste that never happened. The strict variant counts
|
|
303
304
|
// assistant text only (same window, same scanner as the session-name
|
|
304
|
-
// detection).
|
|
305
|
-
//
|
|
306
|
-
// is known, the gate either flips to strict or the rule gets
|
|
307
|
-
// reconsidered; either way this field decides it with data.
|
|
305
|
+
// detection). The Stop verdict enforces this field; the loose field is
|
|
306
|
+
// retained as diagnostic telemetry for the measured detector divergence.
|
|
308
307
|
status_box_present_strict: scanAssistantTextIncludes(p?.transcript_path, "┌─ agent-") ||
|
|
309
308
|
lastAssistantMessage.includes("┌─ agent-"),
|
|
310
309
|
// Whether the session's suggested name is satisfied as of this turn,
|
|
311
310
|
// and which name that refers to. Reported on every stop while a name
|
|
312
311
|
// exists (the stop-hook.session_name verdict reads it per turn); the
|
|
313
312
|
// transcript scan itself stops once the name has been sighted.
|
|
314
|
-
...sessionNamePresence(ctx.coordRoot, ctx.instanceId, (name) => scanSessionNameDisplayedImmediately(
|
|
313
|
+
...sessionNamePresence(ctx.coordRoot, ctx.instanceId, (name) => scanSessionNameDisplayedImmediately(
|
|
314
|
+
// Codex stops carry no transcript_path; discover the rollout by
|
|
315
|
+
// session id so the naming ritual can verify and stamp there too.
|
|
316
|
+
p?.transcript_path ??
|
|
317
|
+
(ctx.adapter === "codex"
|
|
318
|
+
? discoverCodexSessionTranscript(p?.session_id ?? ctx.instanceId)
|
|
319
|
+
: undefined), name, assistantTextStartsWithSessionNameBlock)),
|
|
315
320
|
...(fileLinkTelemetry ?? {}),
|
|
316
321
|
stop_hook_active: p?.stop_hook_active,
|
|
317
322
|
};
|
|
@@ -321,7 +326,8 @@ function buildEventData(eventType, ctx) {
|
|
|
321
326
|
// Subagents inherit parent's name via the resolve-name session_id path
|
|
322
327
|
// (agent-coord/state/names.ts → kind=transient). Use the call ID as the
|
|
323
328
|
// instance_id input; assignName falls through to transient.
|
|
324
|
-
const
|
|
329
|
+
const childInstanceId = subagentCallId ?? ctx.instanceId;
|
|
330
|
+
const assigned = assignNameViaAgentCoord(ctx.coordRoot, childInstanceId, "subagent");
|
|
325
331
|
return {
|
|
326
332
|
agent_type: p?.raw.agent_type ??
|
|
327
333
|
p?.raw.subagent_type ??
|
|
@@ -329,7 +335,7 @@ function buildEventData(eventType, ctx) {
|
|
|
329
335
|
prompt_summary: p?.raw.prompt_summary,
|
|
330
336
|
name: assigned?.name,
|
|
331
337
|
kind: "subagent",
|
|
332
|
-
agent_id:
|
|
338
|
+
agent_id: childInstanceId,
|
|
333
339
|
subagent_call_id: subagentCallId,
|
|
334
340
|
parent_session_id: p?.parent_session_id,
|
|
335
341
|
};
|
|
@@ -475,14 +481,21 @@ async function main() {
|
|
|
475
481
|
const repoRoot = findCoordRoot(process.cwd());
|
|
476
482
|
if (repoRoot) {
|
|
477
483
|
let sid = "";
|
|
484
|
+
let stopContinuation = false;
|
|
478
485
|
try {
|
|
479
486
|
const j = JSON.parse(raw);
|
|
480
487
|
sid = j.session_id ?? j.conversation_id ?? "";
|
|
488
|
+
// A stop with stop_hook_active is a remediation continuation of a
|
|
489
|
+
// turn that already chimed; replaying the sound on every bounce
|
|
490
|
+
// turns a blocked-stop loop into a repeating alarm.
|
|
491
|
+
stopContinuation = eventName === "stop" && j.stop_hook_active === true;
|
|
481
492
|
}
|
|
482
493
|
catch {
|
|
483
494
|
// non-JSON payload: play unkeyed (rate-limit just won't dedup)
|
|
484
495
|
}
|
|
485
|
-
|
|
496
|
+
if (!stopContinuation) {
|
|
497
|
+
playSound(repoRoot, s.sound, sid, s.maxPlays);
|
|
498
|
+
}
|
|
486
499
|
}
|
|
487
500
|
}
|
|
488
501
|
}
|
|
@@ -571,6 +584,7 @@ async function main() {
|
|
|
571
584
|
logError(coordRoot, err, { phase: "image-capture" });
|
|
572
585
|
}
|
|
573
586
|
}
|
|
587
|
+
const deferV3Drain = liveHookSignalDefersDrainV3(eventName, coordEnv("EXPERIMENT_DEFER_V3_DRAIN"));
|
|
574
588
|
const v3Result = recordLiveHookSignalV3({
|
|
575
589
|
coordRoot,
|
|
576
590
|
route: ledgerRoute,
|
|
@@ -593,13 +607,39 @@ async function main() {
|
|
|
593
607
|
monotonic_ns: hookClock.monotonic_ns,
|
|
594
608
|
...(stopRemediation ? { stop_remediation: true } : {}),
|
|
595
609
|
...(turnRitual ? { turn_ritual: turnRitual } : {}),
|
|
596
|
-
...(
|
|
610
|
+
...(deferV3Drain ? { defer_drain: true } : {}),
|
|
597
611
|
});
|
|
598
612
|
const v3EventId = v3Result && "event" in v3Result
|
|
599
613
|
? v3Result.event.event_id
|
|
600
614
|
: v3Result && "event_id" in v3Result
|
|
601
615
|
? v3Result.event_id
|
|
602
616
|
: undefined;
|
|
617
|
+
if (norm.event_type === "agent.started" &&
|
|
618
|
+
payload &&
|
|
619
|
+
v3Result?.state === "recorded" &&
|
|
620
|
+
v3Result.event.event_type === "agent.started") {
|
|
621
|
+
try {
|
|
622
|
+
const childResult = recordLiveDelegatedChildSessionV3({
|
|
623
|
+
coordRoot,
|
|
624
|
+
route: ledgerRoute,
|
|
625
|
+
parentEvent: v3Result.event,
|
|
626
|
+
payload,
|
|
627
|
+
adapter,
|
|
628
|
+
...(adapter === "codex" && isWslUncPath(payload.cwd)
|
|
629
|
+
? { bridge: "codex-wsl" }
|
|
630
|
+
: {}),
|
|
631
|
+
monotonic_ns: hookClock.monotonic_ns,
|
|
632
|
+
});
|
|
633
|
+
const nativeChild = payload.subagent_id ?? payload.agent_id;
|
|
634
|
+
if (nativeChild &&
|
|
635
|
+
(childResult.state === "recorded" || childResult.state === "already_started")) {
|
|
636
|
+
ensureLiveCoordinationHeartbeat(coordRoot, nativeChild, sessionId, adapter, payload.model);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
catch (err) {
|
|
640
|
+
logError(coordRoot, err, { phase: "subagent-child-session-start" });
|
|
641
|
+
}
|
|
642
|
+
}
|
|
603
643
|
if (v3Result?.state === "recorded" && capturedImages.length > 0) {
|
|
604
644
|
try {
|
|
605
645
|
recordImageArtifactsV3(coordRoot, v3Result.event, capturedImages);
|
|
@@ -744,11 +784,12 @@ async function main() {
|
|
|
744
784
|
runSessionSyncExtension(coordRoot, true);
|
|
745
785
|
}
|
|
746
786
|
}
|
|
747
|
-
// Phase 8: SubagentStart:
|
|
748
|
-
//
|
|
749
|
-
//
|
|
787
|
+
// Phase 8: SubagentStart: the child generation was opened above from the
|
|
788
|
+
// native delegation identity. Refresh its disposable projection, log the
|
|
789
|
+
// lifecycle event, and emit a context message announcing the subagent.
|
|
750
790
|
if (norm.event_type === "agent.started") {
|
|
751
791
|
try {
|
|
792
|
+
const childInstanceId = data.agent_id ?? owner.instance_id;
|
|
752
793
|
const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
753
794
|
if (existsSync(agentCoordBin)) {
|
|
754
795
|
spawnSync(agentCoordBin, ["project"], {
|
|
@@ -758,12 +799,12 @@ async function main() {
|
|
|
758
799
|
});
|
|
759
800
|
spawnSync(agentCoordBin, [
|
|
760
801
|
"log",
|
|
761
|
-
`SUBAGENT_START agent_type=${data.agent_type ?? "unknown"} agent_id=${
|
|
802
|
+
`SUBAGENT_START agent_type=${data.agent_type ?? "unknown"} agent_id=${childInstanceId.slice(0, 8)} platform=${adapterPlatform(adapter)}`,
|
|
762
803
|
"--instance",
|
|
763
|
-
|
|
804
|
+
childInstanceId,
|
|
764
805
|
], { encoding: "utf8", timeout: 2000, env: childEnv(coordRoot) });
|
|
765
806
|
}
|
|
766
|
-
emitSubagentStartContext(coordRoot,
|
|
807
|
+
emitSubagentStartContext(coordRoot, childInstanceId, sessionId, data, adapter);
|
|
767
808
|
}
|
|
768
809
|
catch (err) {
|
|
769
810
|
logError(coordRoot, err, { phase: "subagent-start-project" });
|
|
@@ -772,16 +813,19 @@ async function main() {
|
|
|
772
813
|
// Phase 8: SubagentStop: delete subagent heartbeat + log.
|
|
773
814
|
if (norm.event_type === "agent.completed") {
|
|
774
815
|
try {
|
|
775
|
-
|
|
816
|
+
const childInstanceId = payload?.subagent_id ?? payload?.agent_id ?? owner.instance_id;
|
|
817
|
+
cleanupSessionEnd(coordRoot, childInstanceId, data.reason ?? "unknown");
|
|
776
818
|
const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
777
819
|
if (existsSync(agentCoordBin)) {
|
|
778
820
|
spawnSync(agentCoordBin, [
|
|
779
821
|
"log",
|
|
780
|
-
`SUBAGENT_STOP agent_id=${
|
|
822
|
+
`SUBAGENT_STOP agent_id=${childInstanceId.slice(0, 8)} platform=${adapterPlatform(adapter)}`,
|
|
781
823
|
"--instance",
|
|
782
|
-
|
|
824
|
+
childInstanceId,
|
|
783
825
|
], { encoding: "utf8", timeout: 2000, env: childEnv(coordRoot) });
|
|
784
826
|
}
|
|
827
|
+
const { reconcileSessionFinalizationV3 } = await import("../agents/session-finalizer-v3.js");
|
|
828
|
+
reconcileSessionFinalizationV3(coordRoot, { archive_observations: [] });
|
|
785
829
|
}
|
|
786
830
|
catch (err) {
|
|
787
831
|
logError(coordRoot, err, { phase: "subagent-stop-cleanup" });
|
|
@@ -853,12 +897,32 @@ async function main() {
|
|
|
853
897
|
workflow_child: coordEnv("WORKFLOW_CHILD") === "1",
|
|
854
898
|
});
|
|
855
899
|
if (!verdict.allow) {
|
|
856
|
-
//
|
|
857
|
-
//
|
|
858
|
-
// `
|
|
859
|
-
//
|
|
860
|
-
|
|
861
|
-
|
|
900
|
+
// Backstop for sessions whose evidence can never land (e.g. a headless
|
|
901
|
+
// child whose status command resolves to a different owner): after
|
|
902
|
+
// `cap` consecutive blocked stops in one cycle, allow the stop instead
|
|
903
|
+
// of bouncing the model forever. Cycle boundaries come from the
|
|
904
|
+
// adapter's continuation flag, so adapters without one are unaffected.
|
|
905
|
+
const { remediationCapExceeded } = await import("./remediation-cap.js");
|
|
906
|
+
if (remediationCapExceeded(sessionId, payload?.stop_hook_active === true)) {
|
|
907
|
+
appendDebug(coordRoot, {
|
|
908
|
+
...debugBase,
|
|
909
|
+
skipped: "stop-remediation-cap-exhausted",
|
|
910
|
+
blocked_rule: verdict.rule,
|
|
911
|
+
session_id: sessionId,
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
else {
|
|
915
|
+
// Adapter-aware enforcement channel: Claude Code honors exit-2 + stderr
|
|
916
|
+
// as a turn block; Cursor ignores exit codes and re-prompts only via a
|
|
917
|
+
// `followup_message` it auto-submits. Codex returned observe-only above.
|
|
918
|
+
// emitStopBlock writes the right shape and returns the exit code to use.
|
|
919
|
+
const { emitStopBlock } = await import("./adapter/output.js");
|
|
920
|
+
return emitStopBlock(adapter, verdict, coordRoot);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
else {
|
|
924
|
+
const { clearRemediationCount } = await import("./remediation-cap.js");
|
|
925
|
+
clearRemediationCount(sessionId);
|
|
862
926
|
}
|
|
863
927
|
// An explicit end requested from inside this turn cannot be authoritative
|
|
864
928
|
// until the adapter has committed turn.completed above. Reconcile only
|
|
@@ -944,8 +1008,9 @@ async function main() {
|
|
|
944
1008
|
return 0;
|
|
945
1009
|
}
|
|
946
1010
|
if (norm.event_type === "tool.completed" && eventName === "post-tool-use") {
|
|
947
|
-
// Inject only at the successful tool boundary that actually minted
|
|
948
|
-
// the first non-empty set-task
|
|
1011
|
+
// Inject only at the successful tool boundary that actually minted or
|
|
1012
|
+
// retried a name: the first non-empty set-task, a pending-name set-task
|
|
1013
|
+
// retry, or the transition to lifecycle done. The
|
|
949
1014
|
// coordination row intentionally remains pending until transcript evidence
|
|
950
1015
|
// catches up; reading that latch alone here would re-inject after every
|
|
951
1016
|
// later tool and make the agent print the same block repeatedly.
|
|
@@ -979,24 +1044,35 @@ async function enforcePendingSessionNameDisplay(coordRoot, instanceId, adapter,
|
|
|
979
1044
|
const command = extractBashCommand(payload?.tool_name, payload?.tool_input);
|
|
980
1045
|
if (isSessionNameRemediationCommand(command, resolveBinName(coordRoot)))
|
|
981
1046
|
return true;
|
|
982
|
-
const inspection = payload?.agent_message !== undefined
|
|
1047
|
+
const inspection = adapter === "cursor" && payload?.agent_message !== undefined
|
|
983
1048
|
? {
|
|
984
1049
|
state: assistantTextStartsWithSessionNameBlock(payload.agent_message, name)
|
|
985
1050
|
? "present"
|
|
986
1051
|
: "absent",
|
|
987
1052
|
}
|
|
988
|
-
: inspectSessionNameDisplayImmediately(
|
|
1053
|
+
: inspectSessionNameDisplayImmediately(
|
|
1054
|
+
// Codex hook payloads omit transcript_path on every event, which
|
|
1055
|
+
// left this inspection permanently unavailable and the latch never
|
|
1056
|
+
// stamped. Discover the rollout by session id; the scan only runs
|
|
1057
|
+
// while a name is pending, so the latch closes after one success.
|
|
1058
|
+
payload?.transcript_path ??
|
|
1059
|
+
(adapter === "codex"
|
|
1060
|
+
? discoverCodexSessionTranscript(payload?.session_id ?? instanceId)
|
|
1061
|
+
: undefined), name, assistantTextStartsWithSessionNameBlock);
|
|
989
1062
|
if (inspection.state === "present") {
|
|
990
1063
|
stampSessionNameSeen(coordRoot, instanceId, name);
|
|
991
1064
|
return true;
|
|
992
1065
|
}
|
|
993
1066
|
if (inspection.state === "unavailable") {
|
|
994
|
-
|
|
995
|
-
|
|
1067
|
+
// PostToolUse already delivered the exact block for this mint. An
|
|
1068
|
+
// unavailable transcript cannot become evidence, and repeating a generic
|
|
1069
|
+
// reminder on every tool creates an unbounded Codex loop. Keep the latch
|
|
1070
|
+
// honestly pending and fail open without another instruction. A later
|
|
1071
|
+
// readable transcript can still verify it.
|
|
996
1072
|
return true;
|
|
997
1073
|
}
|
|
998
1074
|
const { emitDeny } = await import("./adapter/output.js");
|
|
999
|
-
emitDeny(adapter,
|
|
1075
|
+
emitDeny(adapter, sessionNameDisplayRecoveryInstruction(name, resolveBinName(coordRoot)));
|
|
1000
1076
|
return false;
|
|
1001
1077
|
}
|
|
1002
1078
|
async function runPreToolUseGuard(coordRoot, instanceId, sessionId, data, adapter) {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session cap on consecutive blocked Stop remediations.
|
|
3
|
+
*
|
|
4
|
+
* The Stop verdict re-blocks until the ledger shows the end-of-turn evidence,
|
|
5
|
+
* which assumes the agent CAN produce it. A session whose evidence can never
|
|
6
|
+
* land (e.g. a headless child whose status command resolves to a different
|
|
7
|
+
* owner) bounces forever: every retry replays the stop cycle until the budget
|
|
8
|
+
* or the operator kills it. This counter is the backstop: once a single stop
|
|
9
|
+
* cycle has been blocked `cap` times in a row, the verdict is allowed through
|
|
10
|
+
* so the session can terminate.
|
|
11
|
+
*
|
|
12
|
+
* A "stop cycle" is delimited by the adapter's continuation flag (Claude
|
|
13
|
+
* Code's `stop_hook_active`): a stop without the flag starts a fresh cycle and
|
|
14
|
+
* resets the count. Adapters that never set the flag (Cursor) reset on every
|
|
15
|
+
* stop, so the cap never engages there and behavior is unchanged.
|
|
16
|
+
*
|
|
17
|
+
* State is a tmpdir counter file keyed by session id — deliberately outside
|
|
18
|
+
* the ledger, because remediation stops do not land fresh `turn.completed`
|
|
19
|
+
* events (the turn span is already closed), so ledger evidence cannot count
|
|
20
|
+
* them.
|
|
21
|
+
*/
|
|
22
|
+
export declare const DEFAULT_STOP_REMEDIATION_CAP = 5;
|
|
23
|
+
/**
|
|
24
|
+
* Record one blocked stop and report whether the cap is exhausted.
|
|
25
|
+
*
|
|
26
|
+
* Call only when the Stop verdict blocked. `continuation` is the adapter's
|
|
27
|
+
* "this stop follows a stop-hook block" flag; false starts a fresh cycle.
|
|
28
|
+
* Returns true when this cycle has already been blocked `cap` times, meaning
|
|
29
|
+
* the caller should allow the stop instead of blocking again.
|
|
30
|
+
*/
|
|
31
|
+
export declare function remediationCapExceeded(sessionId: string, continuation: boolean, cap?: number): boolean;
|
|
32
|
+
/** Clear the cycle counter after an allowed stop. */
|
|
33
|
+
export declare function clearRemediationCount(sessionId: string): void;
|
|
34
|
+
//# sourceMappingURL=remediation-cap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remediation-cap.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/remediation-cap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAO9C;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,OAAO,EACrB,GAAG,SAA+B,GACjC,OAAO,CAsBT;AAED,qDAAqD;AACrD,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAM7D"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session cap on consecutive blocked Stop remediations.
|
|
3
|
+
*
|
|
4
|
+
* The Stop verdict re-blocks until the ledger shows the end-of-turn evidence,
|
|
5
|
+
* which assumes the agent CAN produce it. A session whose evidence can never
|
|
6
|
+
* land (e.g. a headless child whose status command resolves to a different
|
|
7
|
+
* owner) bounces forever: every retry replays the stop cycle until the budget
|
|
8
|
+
* or the operator kills it. This counter is the backstop: once a single stop
|
|
9
|
+
* cycle has been blocked `cap` times in a row, the verdict is allowed through
|
|
10
|
+
* so the session can terminate.
|
|
11
|
+
*
|
|
12
|
+
* A "stop cycle" is delimited by the adapter's continuation flag (Claude
|
|
13
|
+
* Code's `stop_hook_active`): a stop without the flag starts a fresh cycle and
|
|
14
|
+
* resets the count. Adapters that never set the flag (Cursor) reset on every
|
|
15
|
+
* stop, so the cap never engages there and behavior is unchanged.
|
|
16
|
+
*
|
|
17
|
+
* State is a tmpdir counter file keyed by session id — deliberately outside
|
|
18
|
+
* the ledger, because remediation stops do not land fresh `turn.completed`
|
|
19
|
+
* events (the turn span is already closed), so ledger evidence cannot count
|
|
20
|
+
* them.
|
|
21
|
+
*/
|
|
22
|
+
import { readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
23
|
+
import os from "node:os";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
export const DEFAULT_STOP_REMEDIATION_CAP = 5;
|
|
26
|
+
function counterPath(sessionId) {
|
|
27
|
+
const key = (sessionId || "default").replace(/[^A-Za-z0-9_-]/g, "_");
|
|
28
|
+
return join(os.tmpdir(), `harnery-stop-remediation-${key}.count`);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Record one blocked stop and report whether the cap is exhausted.
|
|
32
|
+
*
|
|
33
|
+
* Call only when the Stop verdict blocked. `continuation` is the adapter's
|
|
34
|
+
* "this stop follows a stop-hook block" flag; false starts a fresh cycle.
|
|
35
|
+
* Returns true when this cycle has already been blocked `cap` times, meaning
|
|
36
|
+
* the caller should allow the stop instead of blocking again.
|
|
37
|
+
*/
|
|
38
|
+
export function remediationCapExceeded(sessionId, continuation, cap = DEFAULT_STOP_REMEDIATION_CAP) {
|
|
39
|
+
const path = counterPath(sessionId);
|
|
40
|
+
let count = 1;
|
|
41
|
+
if (continuation) {
|
|
42
|
+
let prior = 0;
|
|
43
|
+
try {
|
|
44
|
+
prior = Number.parseInt(readFileSync(path, "utf8"), 10);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// No counter yet (cycle started before the counter existed): treat the
|
|
48
|
+
// continuation itself as evidence of one earlier blocked stop, so an
|
|
49
|
+
// already-running loop still converges on the cap.
|
|
50
|
+
prior = 1;
|
|
51
|
+
}
|
|
52
|
+
count = Number.isFinite(prior) && prior >= 0 ? prior + 1 : 1;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
writeFileSync(path, String(count), "utf8");
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Counter IO must never turn into a block or a crash; fail open (no cap).
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return count > cap;
|
|
62
|
+
}
|
|
63
|
+
/** Clear the cycle counter after an allowed stop. */
|
|
64
|
+
export function clearRemediationCount(sessionId) {
|
|
65
|
+
try {
|
|
66
|
+
rmSync(counterPath(sessionId), { force: true });
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// best-effort
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -55,19 +55,35 @@ export declare function inspectSessionNameDisplayImmediately(transcriptPath: str
|
|
|
55
55
|
* file is mounted at `/mnt/c/...`.
|
|
56
56
|
*/
|
|
57
57
|
export declare function transcriptPathCandidates(transcriptPath: string | undefined): string[];
|
|
58
|
+
/** Runtime identity read from one transcript row: the model plus the tuning
|
|
59
|
+
* values that row reported alongside it. */
|
|
60
|
+
export interface TranscriptRuntimeScan {
|
|
61
|
+
model: string;
|
|
62
|
+
/** CC stamps the effective effort level on each assistant row; absent when
|
|
63
|
+
* the model has no effort dial (verified: haiku rows omit it entirely). */
|
|
64
|
+
effort?: string;
|
|
65
|
+
/** CC reports the response speed tier inside `message.usage`. */
|
|
66
|
+
speed?: string;
|
|
67
|
+
}
|
|
58
68
|
/**
|
|
59
|
-
* Resolve the agent's
|
|
60
|
-
* most-recent assistant
|
|
61
|
-
*
|
|
62
|
-
* fallback that lets `session.started` / `turn.completed` populate
|
|
63
|
-
* model
|
|
69
|
+
* Resolve the agent's runtime identity from a CC-style JSONL transcript by
|
|
70
|
+
* reading the most-recent assistant row. Claude Code's SessionStart payload
|
|
71
|
+
* omits `model` and `effort` (Codex + Cursor supply model directly), so this
|
|
72
|
+
* is the fallback that lets `session.started` / `turn.completed` populate
|
|
73
|
+
* model and tuning once the transcript has at least one assistant turn.
|
|
74
|
+
*
|
|
75
|
+
* Model, effort, and speed are read from the SAME row, never independently:
|
|
76
|
+
* effort varies with model inside one transcript (a mid-session model swap
|
|
77
|
+
* changes both), so pairing the newest model with a separately scanned newest
|
|
78
|
+
* effort can mislabel the session.
|
|
64
79
|
*
|
|
65
80
|
* Tail-reads the same 256KB window as the status-box scan and walks lines from
|
|
66
|
-
* the end, returning the first real model id
|
|
67
|
-
* (`<synthetic>`) and empty values are skipped. Returns undefined
|
|
68
|
-
* transcript is missing/empty (e.g. a fresh session's first
|
|
81
|
+
* the end, returning the first row with a real model id. Synthetic
|
|
82
|
+
* placeholders (`<synthetic>`) and empty values are skipped. Returns undefined
|
|
83
|
+
* when the transcript is missing/empty (e.g. a fresh session's first
|
|
84
|
+
* SessionStart).
|
|
69
85
|
*/
|
|
70
|
-
export declare function
|
|
86
|
+
export declare function scanTranscriptRuntime(transcriptPath: string | undefined): TranscriptRuntimeScan | undefined;
|
|
71
87
|
/**
|
|
72
88
|
* Detect the parent session of a forked CC conversation from its transcript.
|
|
73
89
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/resolve/transcript.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/resolve/transcript.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAQhF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CACvC,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,MAAM,EAAE,MAAM,GACb,OAAO,CAiCT;AAED,kFAAkF;AAClF,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAgB9F;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CACjD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,GAC/D,OAAO,CAIT;AAED,MAAM,MAAM,4BAA4B,GACpC;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,GACpB;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE,GACnB;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,oBAAoB,GAAG,sBAAsB,CAAA;CAAE,CAAC;AAEpF;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAClD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,GAC/D,4BAA4B,CA0B9B;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAUrF;AAwDD;4CAC4C;AAC5C,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd;+EAC2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,MAAM,GAAG,SAAS,GACjC,qBAAqB,GAAG,SAAS,CA8BnC;AAQD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,gBAAgB,CAC9B,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,SAAS,CAkDpB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join } from "node:path";
|
|
3
|
+
import { toolResponseMintedSessionName } from "../../agents/session-name-display.js";
|
|
3
4
|
/**
|
|
4
5
|
* Scan a CC-style JSONL transcript for the `┌─ agent-` status-box prefix in
|
|
5
6
|
* the most-recent assistant turn. Used by `turn.completed` events to populate
|
|
@@ -123,21 +124,26 @@ export function inspectSessionNameDisplayImmediately(transcriptPath, name, start
|
|
|
123
124
|
if (!text)
|
|
124
125
|
return { state: "unavailable", reason: "transcript_not_ready" };
|
|
125
126
|
let sawMintResult = false;
|
|
127
|
+
let awaitingReply = false;
|
|
126
128
|
for (const line of text.split("\n")) {
|
|
127
|
-
if (!sawMintResult) {
|
|
128
|
-
if (line.includes(name) && line.includes("suggested_session_name"))
|
|
129
|
-
sawMintResult = true;
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
129
|
const row = parseTranscriptRow(line);
|
|
133
130
|
if (!row)
|
|
134
131
|
continue;
|
|
132
|
+
if (toolResponseMintedSessionName(row, name)) {
|
|
133
|
+
sawMintResult = true;
|
|
134
|
+
awaitingReply = true;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (!awaitingReply)
|
|
138
|
+
continue;
|
|
135
139
|
const assistantText = assistantTextFromRow(row);
|
|
136
140
|
if (assistantText !== null) {
|
|
137
|
-
|
|
141
|
+
if (startsWithBlock(assistantText, name))
|
|
142
|
+
return { state: "present" };
|
|
143
|
+
awaitingReply = false;
|
|
138
144
|
}
|
|
139
145
|
}
|
|
140
|
-
return sawMintResult
|
|
146
|
+
return sawMintResult && awaitingReply
|
|
141
147
|
? { state: "unavailable", reason: "transcript_not_ready" }
|
|
142
148
|
: { state: "absent" };
|
|
143
149
|
}
|
|
@@ -210,18 +216,24 @@ function textFromContent(content) {
|
|
|
210
216
|
return parts.join("\n");
|
|
211
217
|
}
|
|
212
218
|
/**
|
|
213
|
-
* Resolve the agent's
|
|
214
|
-
* most-recent assistant
|
|
215
|
-
*
|
|
216
|
-
* fallback that lets `session.started` / `turn.completed` populate
|
|
217
|
-
* model
|
|
219
|
+
* Resolve the agent's runtime identity from a CC-style JSONL transcript by
|
|
220
|
+
* reading the most-recent assistant row. Claude Code's SessionStart payload
|
|
221
|
+
* omits `model` and `effort` (Codex + Cursor supply model directly), so this
|
|
222
|
+
* is the fallback that lets `session.started` / `turn.completed` populate
|
|
223
|
+
* model and tuning once the transcript has at least one assistant turn.
|
|
224
|
+
*
|
|
225
|
+
* Model, effort, and speed are read from the SAME row, never independently:
|
|
226
|
+
* effort varies with model inside one transcript (a mid-session model swap
|
|
227
|
+
* changes both), so pairing the newest model with a separately scanned newest
|
|
228
|
+
* effort can mislabel the session.
|
|
218
229
|
*
|
|
219
230
|
* Tail-reads the same 256KB window as the status-box scan and walks lines from
|
|
220
|
-
* the end, returning the first real model id
|
|
221
|
-
* (`<synthetic>`) and empty values are skipped. Returns undefined
|
|
222
|
-
* transcript is missing/empty (e.g. a fresh session's first
|
|
231
|
+
* the end, returning the first row with a real model id. Synthetic
|
|
232
|
+
* placeholders (`<synthetic>`) and empty values are skipped. Returns undefined
|
|
233
|
+
* when the transcript is missing/empty (e.g. a fresh session's first
|
|
234
|
+
* SessionStart).
|
|
223
235
|
*/
|
|
224
|
-
export function
|
|
236
|
+
export function scanTranscriptRuntime(transcriptPath) {
|
|
225
237
|
const readablePath = resolveTranscriptPath(transcriptPath);
|
|
226
238
|
if (!readablePath)
|
|
227
239
|
return undefined;
|
|
@@ -237,7 +249,13 @@ export function scanTranscriptModel(transcriptPath) {
|
|
|
237
249
|
const obj = JSON.parse(line);
|
|
238
250
|
const model = obj.message?.model ?? obj.model;
|
|
239
251
|
if (typeof model === "string" && model.length > 0 && !model.startsWith("<")) {
|
|
240
|
-
|
|
252
|
+
const effort = obj.effort;
|
|
253
|
+
const speed = obj.message?.usage?.speed;
|
|
254
|
+
return {
|
|
255
|
+
model,
|
|
256
|
+
...(typeof effort === "string" && effort.length > 0 ? { effort } : {}),
|
|
257
|
+
...(typeof speed === "string" && speed.length > 0 ? { speed } : {}),
|
|
258
|
+
};
|
|
241
259
|
}
|
|
242
260
|
}
|
|
243
261
|
catch {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { TSchema } from "@sinclair/typebox";
|
|
2
|
+
import { type SemanticConfiguredModel, type SemanticHarness } from "./contract.js";
|
|
3
|
+
import type { SemanticReaderResolution } from "./storage.js";
|
|
4
|
+
export interface SemanticReaderRoute {
|
|
5
|
+
harness: SemanticHarness;
|
|
6
|
+
binary: string;
|
|
7
|
+
configured_model: SemanticConfiguredModel;
|
|
8
|
+
invocation_model_id: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SemanticAdapterSuccess {
|
|
11
|
+
ok: true;
|
|
12
|
+
text: string;
|
|
13
|
+
resolved_model_id: string;
|
|
14
|
+
model_attestation: "verified" | "requested-only";
|
|
15
|
+
duration_ms: number;
|
|
16
|
+
output_bytes: number;
|
|
17
|
+
}
|
|
18
|
+
export interface SemanticAdapterFailure {
|
|
19
|
+
ok: false;
|
|
20
|
+
reason_code: "harness_unavailable" | "authentication_unavailable" | "model_unavailable" | "model_mismatch";
|
|
21
|
+
resolved_model_id?: string;
|
|
22
|
+
model_attestation?: "verified" | "requested-only";
|
|
23
|
+
duration_ms: number;
|
|
24
|
+
}
|
|
25
|
+
export type SemanticAdapterResult = SemanticAdapterSuccess | SemanticAdapterFailure;
|
|
26
|
+
export interface SemanticAdapter {
|
|
27
|
+
readonly route: SemanticReaderRoute;
|
|
28
|
+
discover(now?: () => Date): SemanticReaderResolution;
|
|
29
|
+
invoke(prompt: string, timeoutMs?: number, responseSchema?: TSchema): Promise<SemanticAdapterResult>;
|
|
30
|
+
}
|
|
31
|
+
export declare const SEMANTIC_READER_ROUTES: Record<SemanticHarness, SemanticReaderRoute>;
|
|
32
|
+
export declare function createSemanticAdapters(): Record<SemanticHarness, SemanticAdapter>;
|
|
33
|
+
export declare function discoverSemanticReaders(adapters?: Record<SemanticHarness, SemanticAdapter>): Record<SemanticHarness, SemanticReaderResolution>;
|
|
34
|
+
//# sourceMappingURL=adapters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../../../src/core/semantic/adapters.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,eAAe,EAErB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAM7D,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,eAAe,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,uBAAuB,CAAC;IAC1C,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,UAAU,GAAG,gBAAgB,CAAC;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,KAAK,CAAC;IACV,WAAW,EACP,qBAAqB,GACrB,4BAA4B,GAC5B,mBAAmB,GACnB,gBAAgB,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,UAAU,GAAG,gBAAgB,CAAC;IAClD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,qBAAqB,GAAG,sBAAsB,GAAG,sBAAsB,CAAC;AAEpF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,mBAAmB,CAAC;IACpC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,GAAG,wBAAwB,CAAC;IACrD,MAAM,CACJ,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,OAAO,GACvB,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACnC;AAED,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,eAAe,EAAE,mBAAmB,CAmB/E,CAAC;AAEF,wBAAgB,sBAAsB,IAAI,MAAM,CAAC,eAAe,EAAE,eAAe,CAAC,CAMjF;AAED,wBAAgB,uBAAuB,CACrC,QAAQ,GAAE,MAAM,CAAC,eAAe,EAAE,eAAe,CAA4B,GAC5E,MAAM,CAAC,eAAe,EAAE,wBAAwB,CAAC,CAMnD"}
|