harnery 0.45.2 → 0.46.1
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 +13 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commands/admission.d.ts.map +1 -1
- package/dist/commands/admission.js +214 -27
- package/dist/commands/agents.d.ts +4 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +102 -25
- package/dist/commands/browse.d.ts +3 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +172 -8
- package/dist/commands/fetch.js +1 -0
- package/dist/commands/ledger-v3.d.ts.map +1 -1
- package/dist/commands/ledger-v3.js +8 -2
- package/dist/commands/presence.d.ts.map +1 -1
- package/dist/commands/presence.js +40 -0
- package/dist/commands/qa-run.d.ts.map +1 -1
- package/dist/commands/qa-run.js +4 -3
- package/dist/commands/review-pack.d.ts.map +1 -1
- package/dist/commands/review-pack.js +33 -6
- package/dist/core/agents/index.d.ts +1 -0
- package/dist/core/agents/index.d.ts.map +1 -1
- package/dist/core/agents/index.js +1 -0
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +11 -4
- package/dist/core/agents/render/session-context.d.ts +0 -6
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +1 -6
- package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/claim-conflict.js +36 -16
- package/dist/core/agents/rules/stop-hook.d.ts +6 -0
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
- package/dist/core/agents/rules/stop-hook.js +20 -1
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +12 -4
- package/dist/core/agents/state/heartbeat-reader.d.ts +3 -0
- package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.d.ts +3 -1
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +6 -2
- package/dist/core/agents/state/parent-reference-contract.d.ts +16 -0
- package/dist/core/agents/state/parent-reference-contract.d.ts.map +1 -0
- package/dist/core/agents/state/parent-reference-contract.js +1 -0
- package/dist/core/agents/state/parent-reference.d.ts +18 -0
- package/dist/core/agents/state/parent-reference.d.ts.map +1 -0
- package/dist/core/agents/state/parent-reference.js +119 -0
- package/dist/core/config.d.ts +39 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +63 -0
- package/dist/core/diagnostics/path-identity.d.ts +1 -1
- package/dist/core/diagnostics/path-identity.d.ts.map +1 -1
- package/dist/core/diagnostics/path-identity.js +51 -6
- package/dist/core/events/v3/canonical.js +43 -14
- package/dist/core/events/v3/index.d.ts +1 -1
- package/dist/core/events/v3/index.d.ts.map +1 -1
- package/dist/core/events/v3/index.js +1 -1
- package/dist/core/events/v3/producers/recorder.d.ts +34 -0
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +117 -13
- package/dist/core/events/v3/reader.d.ts.map +1 -1
- package/dist/core/events/v3/reader.js +53 -13
- package/dist/core/events/v3/validate.d.ts.map +1 -1
- package/dist/core/events/v3/validate.js +36 -1
- package/dist/core/hooks/adapter/native-parent.d.ts +23 -0
- package/dist/core/hooks/adapter/native-parent.d.ts.map +1 -0
- package/dist/core/hooks/adapter/native-parent.js +91 -0
- package/dist/core/hooks/cli.js +103 -5
- package/dist/core/hooks/resolve/owner.d.ts +0 -11
- package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
- package/dist/core/hooks/resolve/owner.js +0 -23
- package/dist/core/hooks/resolve/transcript.d.ts +12 -0
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +54 -0
- package/dist/core/hooks/shell-admission.d.ts +84 -0
- package/dist/core/hooks/shell-admission.d.ts.map +1 -0
- package/dist/core/hooks/shell-admission.js +622 -0
- package/dist/core/hooks/shell-commit-tree.d.ts +2 -0
- package/dist/core/hooks/shell-commit-tree.d.ts.map +1 -0
- package/dist/core/hooks/shell-commit-tree.js +104 -0
- package/dist/core/inbox/contract.d.ts +12 -0
- package/dist/core/inbox/contract.d.ts.map +1 -1
- package/dist/core/inbox/contract.js +16 -0
- package/dist/core/inbox/peer-messages.d.ts +41 -16
- package/dist/core/inbox/peer-messages.d.ts.map +1 -1
- package/dist/core/inbox/peer-messages.js +372 -15
- package/dist/core/inbox/service.d.ts +8 -1
- package/dist/core/inbox/service.d.ts.map +1 -1
- package/dist/core/inbox/service.js +83 -3
- package/dist/core/inbox/targets.d.ts +17 -3
- package/dist/core/inbox/targets.d.ts.map +1 -1
- package/dist/core/inbox/targets.js +77 -2
- package/dist/core/presence/git.d.ts +18 -12
- package/dist/core/presence/git.d.ts.map +1 -1
- package/dist/core/presence/git.js +32 -38
- package/dist/core/presence/index.d.ts +2 -2
- package/dist/core/presence/index.d.ts.map +1 -1
- package/dist/core/presence/index.js +8 -6
- package/dist/core/presence/mail.d.ts +133 -0
- package/dist/core/presence/mail.d.ts.map +1 -0
- package/dist/core/presence/mail.js +464 -0
- package/dist/core/semantic/contract.d.ts +5 -5
- package/dist/core/storage/modes.d.ts +4 -0
- package/dist/core/storage/modes.d.ts.map +1 -1
- package/dist/core/storage/modes.js +4 -0
- package/dist/core/storage/segments.d.ts.map +1 -1
- package/dist/core/storage/segments.js +5 -4
- package/dist/lib/admission-history.d.ts +96 -0
- package/dist/lib/admission-history.d.ts.map +1 -0
- package/dist/lib/admission-history.js +196 -0
- package/dist/lib/admission.d.ts +56 -7
- package/dist/lib/admission.d.ts.map +1 -1
- package/dist/lib/admission.js +161 -19
- package/dist/lib/browser/client.d.ts +18 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +100 -12
- package/dist/lib/browser/content-checks.d.ts +3 -1
- package/dist/lib/browser/content-checks.d.ts.map +1 -1
- package/dist/lib/browser/content-checks.js +10 -2
- package/dist/lib/browser/critique.d.ts +32 -0
- package/dist/lib/browser/critique.d.ts.map +1 -1
- package/dist/lib/browser/critique.js +72 -3
- package/dist/lib/browser/geometry.d.ts +2 -0
- package/dist/lib/browser/geometry.d.ts.map +1 -1
- package/dist/lib/browser/geometry.js +61 -15
- package/dist/lib/browser/index.d.ts +1 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +1 -1
- package/dist/lib/browser/page-review-contracts.d.ts +6 -1
- package/dist/lib/browser/page-review-contracts.d.ts.map +1 -1
- package/dist/lib/browser/page-review-contracts.js +6 -1
- package/dist/lib/browser/page-review-judge.d.ts +3 -1
- package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
- package/dist/lib/browser/page-review-judge.js +41 -11
- package/dist/lib/browser/page-review-pack.d.ts +3 -0
- package/dist/lib/browser/page-review-pack.d.ts.map +1 -1
- package/dist/lib/browser/page-review-pack.js +5 -2
- package/dist/lib/browser/qa-run-contracts.d.ts +11 -0
- package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
- package/dist/lib/browser/qa-run-contracts.js +11 -4
- package/dist/lib/browser/qa-run.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.js +73 -16
- package/dist/lib/browser/runts.d.ts +4 -2
- package/dist/lib/browser/runts.d.ts.map +1 -1
- package/dist/lib/browser/runts.js +84 -33
- package/dist/lib/browser/windows-chrome.d.ts +14 -0
- package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
- package/dist/lib/browser/windows-chrome.js +42 -0
- package/dist/lib/browser/windows-login-sync.d.ts +110 -0
- package/dist/lib/browser/windows-login-sync.d.ts.map +1 -0
- package/dist/lib/browser/windows-login-sync.js +171 -0
- package/dist/lib/cookies/client.d.ts +30 -5
- package/dist/lib/cookies/client.d.ts.map +1 -1
- package/dist/lib/cookies/client.js +55 -10
- package/dist/lib/cookies/index.d.ts +1 -1
- package/dist/lib/cookies/index.d.ts.map +1 -1
- package/dist/lib/cookies/index.js +1 -1
- package/dist/lib/docs-metadata-sync.d.ts.map +1 -1
- package/dist/lib/docs-metadata-sync.js +41 -2
- package/dist/lib/durable-job.d.ts +8 -0
- package/dist/lib/durable-job.d.ts.map +1 -1
- package/dist/lib/durable-job.js +10 -0
- package/dist/lib/http/client.d.ts +9 -8
- package/dist/lib/http/client.d.ts.map +1 -1
- package/dist/lib/http/client.js +44 -2
- package/dist/lib/instructions/templates.js +4 -4
- package/package.json +2 -2
- package/src/commander.ts +13 -0
- package/src/commands/admission.ts +272 -29
- package/src/commands/agents.ts +132 -27
- package/src/commands/browse.ts +208 -6
- package/src/commands/fetch.ts +1 -0
- package/src/commands/ledger-v3.ts +8 -2
- package/src/commands/presence.ts +49 -0
- package/src/commands/qa-run.ts +4 -3
- package/src/commands/review-pack.ts +41 -5
- package/src/core/agents/index.ts +6 -0
- package/src/core/agents/live-authority-v3.ts +11 -4
- package/src/core/agents/render/session-context.ts +1 -7
- package/src/core/agents/rules/claim-conflict.ts +50 -18
- package/src/core/agents/rules/stop-hook.ts +28 -1
- package/src/core/agents/session-events.ts +18 -3
- package/src/core/agents/state/heartbeat-reader.ts +3 -0
- package/src/core/agents/state/live-coordination-view.ts +13 -6
- package/src/core/agents/state/parent-reference-contract.ts +14 -0
- package/src/core/agents/state/parent-reference.ts +179 -0
- package/src/core/config.ts +85 -0
- package/src/core/diagnostics/path-identity.ts +52 -6
- package/src/core/events/v3/canonical.ts +46 -13
- package/src/core/events/v3/index.ts +3 -0
- package/src/core/events/v3/producers/recorder.ts +151 -13
- package/src/core/events/v3/reader.ts +55 -11
- package/src/core/events/v3/validate.ts +42 -1
- package/src/core/hooks/adapter/native-parent.ts +109 -0
- package/src/core/hooks/cli.ts +110 -4
- package/src/core/hooks/resolve/owner.ts +0 -23
- package/src/core/hooks/resolve/transcript.ts +61 -0
- package/src/core/hooks/shell-admission.ts +634 -0
- package/src/core/hooks/shell-commit-tree.ts +102 -0
- package/src/core/inbox/contract.ts +18 -0
- package/src/core/inbox/peer-messages.ts +501 -15
- package/src/core/inbox/service.ts +108 -3
- package/src/core/inbox/targets.ts +94 -3
- package/src/core/presence/git.ts +45 -37
- package/src/core/presence/index.ts +16 -8
- package/src/core/presence/mail.ts +620 -0
- package/src/core/storage/modes.ts +6 -0
- package/src/core/storage/segments.ts +5 -4
- package/src/lib/admission-history.ts +267 -0
- package/src/lib/admission.ts +217 -19
- package/src/lib/browser/client.ts +147 -35
- package/src/lib/browser/content-checks.ts +14 -3
- package/src/lib/browser/critique.ts +113 -3
- package/src/lib/browser/geometry.ts +63 -14
- package/src/lib/browser/index.ts +5 -0
- package/src/lib/browser/page-review-contracts.ts +6 -1
- package/src/lib/browser/page-review-judge.ts +58 -13
- package/src/lib/browser/page-review-pack.ts +13 -2
- package/src/lib/browser/qa-run-contracts.ts +30 -4
- package/src/lib/browser/qa-run.ts +78 -17
- package/src/lib/browser/runts.ts +85 -37
- package/src/lib/browser/windows-chrome.ts +50 -0
- package/src/lib/browser/windows-login-sync.ts +275 -0
- package/src/lib/cookies/client.ts +62 -10
- package/src/lib/cookies/index.ts +3 -0
- package/src/lib/docs-metadata-sync.ts +44 -2
- package/src/lib/durable-job.ts +15 -0
- package/src/lib/http/client.ts +63 -1
- package/src/lib/instructions/templates.ts +4 -4
package/src/core/hooks/cli.ts
CHANGED
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
agentsRequireGitFinalization,
|
|
70
70
|
coordFreshnessSeconds,
|
|
71
71
|
hostPromptContextConfig,
|
|
72
|
+
readAdmissionConfig,
|
|
72
73
|
resolveBinName,
|
|
73
74
|
} from "../config.ts";
|
|
74
75
|
import {
|
|
@@ -92,6 +93,7 @@ import {
|
|
|
92
93
|
import type { TurnRitualEvidenceV3 } from "../events/v3/producers/hook-base.ts";
|
|
93
94
|
import { captureSpanClockV3 } from "../events/v3/span-state.ts";
|
|
94
95
|
import { ensureRelayDaemon, fetchPresence, publishPresence } from "../presence/index.ts";
|
|
96
|
+
import { importMail, publishMail } from "../presence/mail.ts";
|
|
95
97
|
import { closeProcessLoggers, legacyLogFields, processLogger } from "../storage/logger.ts";
|
|
96
98
|
import { stableScopeId } from "../workflow/scope-id.ts";
|
|
97
99
|
import { type AdapterBehavior, adapterBehavior } from "./adapter/behaviors/index.ts";
|
|
@@ -137,11 +139,13 @@ import type { SessionNameDisplayInspection } from "./resolve/transcript.ts";
|
|
|
137
139
|
import {
|
|
138
140
|
inspectSessionNameDisplayImmediately,
|
|
139
141
|
scanAssistantStatusBoxPresent,
|
|
142
|
+
scanPendingBackgroundHandoffs,
|
|
140
143
|
scanStatusBoxPresent,
|
|
141
144
|
scanTranscriptRuntime,
|
|
142
145
|
} from "./resolve/transcript.ts";
|
|
143
146
|
import { clearHookRunMarker, writeHookRunMarker } from "./run-markers.ts";
|
|
144
147
|
import { sessionNamePresence } from "./session-name-presence.ts";
|
|
148
|
+
import { shellCommitTreeReason } from "./shell-commit-tree.ts";
|
|
145
149
|
import { shellRmReason } from "./shell-rm.ts";
|
|
146
150
|
import { shellWaiterReason } from "./shell-waiter.ts";
|
|
147
151
|
|
|
@@ -343,8 +347,17 @@ function ensureSessionDisplayName(
|
|
|
343
347
|
payload: ParsedPayload | null,
|
|
344
348
|
): void {
|
|
345
349
|
if (coordEnv("WORKFLOW_CHILD") === "1") return;
|
|
346
|
-
|
|
347
|
-
|
|
350
|
+
// This runs on every tool call. The heartbeat cache is the surface this
|
|
351
|
+
// function stamps, so a cache that already carries a name, or marks a
|
|
352
|
+
// session that never takes one, means there is nothing to do. Only a cache
|
|
353
|
+
// that cannot answer pays for the authoritative read, which re-validates the
|
|
354
|
+
// whole active ledger and cost about half a second per call at 22 MB.
|
|
355
|
+
const cached = readHeartbeat(coordRoot, instanceId);
|
|
356
|
+
if (cached?.kind === "subagent" || cached?.kind === "transient" || cached?.workflow_run_id) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (cached?.name?.trim()) return;
|
|
360
|
+
const row = readLiveCoordinationRow(coordRoot, instanceId) ?? cached;
|
|
348
361
|
if (row?.kind === "subagent" || row?.kind === "transient" || row?.workflow_run_id) return;
|
|
349
362
|
if (row?.name?.trim()) return;
|
|
350
363
|
const assigned = assignSessionNameInProcess(coordRoot, instanceId, payload, adapter);
|
|
@@ -1276,8 +1289,23 @@ function recordContextTelemetry(run: HookRun): void {
|
|
|
1276
1289
|
}
|
|
1277
1290
|
}
|
|
1278
1291
|
|
|
1292
|
+
/** Cross-machine peer messages (ADR 0222) ride the presence fetch: import
|
|
1293
|
+
* whatever mail refs are already local. Reads local refs only, at most once
|
|
1294
|
+
* per import interval; never blocks on the network. */
|
|
1295
|
+
async function receiveRemoteMail(coordRoot: string): Promise<void> {
|
|
1296
|
+
const { peerInbox } = await import("../inbox/peer-messages.ts");
|
|
1297
|
+
importMail(coordRoot, peerInbox(coordRoot), { throttle: true });
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1279
1300
|
async function handleSessionStarted(run: HookRun): Promise<void> {
|
|
1280
1301
|
const { coordRoot, adapter, payload, owner, sessionId, data } = run;
|
|
1302
|
+
// Import mail from refs fetched earlier, so a message sent from another
|
|
1303
|
+
// machine while this task was away is in the inbox this context renders.
|
|
1304
|
+
try {
|
|
1305
|
+
await receiveRemoteMail(coordRoot);
|
|
1306
|
+
} catch (err) {
|
|
1307
|
+
logError(coordRoot, err, { phase: "session-start-mail" });
|
|
1308
|
+
}
|
|
1281
1309
|
// A configured backup runs out of process and is freshness-gated per host.
|
|
1282
1310
|
// Startup never waits for restic or a remote provider; the child owns no
|
|
1283
1311
|
// hook stdio, so the harness is not held open until it finishes. A failed
|
|
@@ -1402,6 +1430,7 @@ async function handleTurnStarted(run: HookRun): Promise<void> {
|
|
|
1402
1430
|
// interval; the systemMessage below reads whatever is locally known).
|
|
1403
1431
|
try {
|
|
1404
1432
|
fetchPresence(coordRoot);
|
|
1433
|
+
await receiveRemoteMail(coordRoot);
|
|
1405
1434
|
} catch (err) {
|
|
1406
1435
|
logError(coordRoot, err, { phase: "user-prompt-submit-presence" });
|
|
1407
1436
|
}
|
|
@@ -1472,11 +1501,24 @@ async function handleStop(run: HookRun): Promise<number | undefined> {
|
|
|
1472
1501
|
// its daemon intentionally exits when a machine has no live sessions.
|
|
1473
1502
|
try {
|
|
1474
1503
|
publishPresence(coordRoot);
|
|
1504
|
+
publishMail(coordRoot);
|
|
1475
1505
|
ensureRelayDaemon(coordRoot);
|
|
1476
1506
|
} catch (err) {
|
|
1477
1507
|
logError(coordRoot, err, { phase: "stop-presence" });
|
|
1478
1508
|
}
|
|
1479
1509
|
|
|
1510
|
+
// Claude Code may move a running foreground command (often the agent's own
|
|
1511
|
+
// commit and push) into the background to deliver a user message. Until it
|
|
1512
|
+
// reports back, finalization cannot pass, so the verdict defers the status
|
|
1513
|
+
// demands instead of bouncing the agent in a loop.
|
|
1514
|
+
const pendingBackgroundHandoffs =
|
|
1515
|
+
adapter === "claude-code"
|
|
1516
|
+
? scanPendingBackgroundHandoffs(
|
|
1517
|
+
payload?.transcript_path ??
|
|
1518
|
+
behavior.discoverTranscript?.(payload?.session_id ?? sessionId),
|
|
1519
|
+
)
|
|
1520
|
+
: [];
|
|
1521
|
+
|
|
1480
1522
|
// Stop verdict (V3 ritual + task/status gate). Direct in-process call: the
|
|
1481
1523
|
// rule lives in Harnery. agent-hook already emitted this turn.completed
|
|
1482
1524
|
// with privacy-safe ritual observations, so the evidence is in the stream.
|
|
@@ -1490,7 +1532,15 @@ async function handleStop(run: HookRun): Promise<number | undefined> {
|
|
|
1490
1532
|
session_name_observation_unavailable: typeof data.session_name_unavailable_reason === "string",
|
|
1491
1533
|
bypass: coordEnv("AGENT_COORD_BYPASS_STOP") === "1",
|
|
1492
1534
|
workflow_child: coordEnv("WORKFLOW_CHILD") === "1",
|
|
1535
|
+
pending_background_handoff: pendingBackgroundHandoffs.length > 0,
|
|
1493
1536
|
});
|
|
1537
|
+
if (verdict.rule === "stop-hook.background_handoff_deferred") {
|
|
1538
|
+
appendDebug(coordRoot, {
|
|
1539
|
+
...debugBase,
|
|
1540
|
+
effect: "stop-deferred-background-handoff",
|
|
1541
|
+
background_task_ids: pendingBackgroundHandoffs,
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1494
1544
|
if (!verdict.allow) {
|
|
1495
1545
|
// Backstop for sessions whose evidence can never land (e.g. a headless
|
|
1496
1546
|
// child whose status command resolves to a different owner): after
|
|
@@ -1524,6 +1574,12 @@ async function handleStop(run: HookRun): Promise<number | undefined> {
|
|
|
1524
1574
|
// until the adapter has committed turn.completed above. Reconcile only
|
|
1525
1575
|
// when such a request exists; any later real work cancels it in the
|
|
1526
1576
|
// finalizer instead of being terminated underneath the agent.
|
|
1577
|
+
// A deferred stop proves nothing about finalization; an explicit end must
|
|
1578
|
+
// wait for the turn that reports the background task's result.
|
|
1579
|
+
if (verdict.rule === "stop-hook.background_handoff_deferred") {
|
|
1580
|
+
if (behavior.runtimeContextRetry) scheduleRuntimeContextRetry(coordRoot, adapter, sessionId);
|
|
1581
|
+
return undefined;
|
|
1582
|
+
}
|
|
1527
1583
|
try {
|
|
1528
1584
|
const { hasPendingExplicitSessionEndV3, reconcileSessionFinalizationV3 } = await import(
|
|
1529
1585
|
"../agents/session-finalizer-v3.ts"
|
|
@@ -1609,6 +1665,17 @@ async function handleToolRequested(run: HookRun): Promise<number | undefined> {
|
|
|
1609
1665
|
return 0;
|
|
1610
1666
|
}
|
|
1611
1667
|
|
|
1668
|
+
const commitTreeReason = shellCommitTreeReason(
|
|
1669
|
+
payload?.tool_name,
|
|
1670
|
+
payload?.tool_input,
|
|
1671
|
+
resolveBinName(coordRoot),
|
|
1672
|
+
);
|
|
1673
|
+
if (commitTreeReason) {
|
|
1674
|
+
const { emitDeny } = await import("./adapter/output.ts");
|
|
1675
|
+
emitDeny(adapter, commitTreeReason);
|
|
1676
|
+
return 0;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1612
1679
|
// G-guard for ALL adapters. Claude Code previously ran this via a
|
|
1613
1680
|
// pre-tool-use bash adapter (which called `agent-coord verdict --rule=claim`);
|
|
1614
1681
|
// that adapter is now deleted, so agent-hook owns the deny for every adapter.
|
|
@@ -1624,9 +1691,41 @@ async function handleToolRequested(run: HookRun): Promise<number | undefined> {
|
|
|
1624
1691
|
logError(coordRoot, err, { phase: "pre-tool-use-guard" });
|
|
1625
1692
|
}
|
|
1626
1693
|
if (!guardAllowed) return 0;
|
|
1694
|
+
|
|
1695
|
+
// Heavy shell checks queue for a machine-wide admission slot when the
|
|
1696
|
+
// project configures one. Fails open: any error runs the command as written.
|
|
1697
|
+
try {
|
|
1698
|
+
if (await emitAdmissionRewrite(coordRoot, adapter, payload)) return 0;
|
|
1699
|
+
} catch (err) {
|
|
1700
|
+
logError(coordRoot, err, { phase: "pre-tool-use-admission" });
|
|
1701
|
+
}
|
|
1627
1702
|
return undefined;
|
|
1628
1703
|
}
|
|
1629
1704
|
|
|
1705
|
+
async function emitAdmissionRewrite(
|
|
1706
|
+
coordRoot: string,
|
|
1707
|
+
adapter: Adapter,
|
|
1708
|
+
payload: HookRun["payload"],
|
|
1709
|
+
): Promise<boolean> {
|
|
1710
|
+
const resource = readAdmissionConfig(coordRoot)?.guard?.resource;
|
|
1711
|
+
if (!resource) return false;
|
|
1712
|
+
const { admissionHookOutput } = await import("./shell-admission.ts");
|
|
1713
|
+
const output = admissionHookOutput({
|
|
1714
|
+
adapter,
|
|
1715
|
+
resource,
|
|
1716
|
+
bin: resolveBinName(coordRoot),
|
|
1717
|
+
projectRoot: coordRoot,
|
|
1718
|
+
toolName: payload?.tool_name,
|
|
1719
|
+
toolInput: payload?.tool_input,
|
|
1720
|
+
cwd: payload?.cwd,
|
|
1721
|
+
skip: coordEnv("ADMISSION_SKIP") === "1" || Boolean(coordEnv("ADMISSION_HELD")),
|
|
1722
|
+
platform: process.platform,
|
|
1723
|
+
});
|
|
1724
|
+
if (!output) return false;
|
|
1725
|
+
process.stdout.write(`${JSON.stringify(output)}\n`);
|
|
1726
|
+
return true;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1630
1729
|
async function handlePostToolUse(run: HookRun): Promise<void> {
|
|
1631
1730
|
const { coordRoot, adapter, eventName, payload, owner } = run;
|
|
1632
1731
|
// Inject the full instruction only at the successful tool boundary that
|
|
@@ -1646,9 +1745,16 @@ async function handlePostToolUse(run: HookRun): Promise<void> {
|
|
|
1646
1745
|
// retry response already carries the full instruction and an end-turn
|
|
1647
1746
|
// status is the wrong place to ask.
|
|
1648
1747
|
try {
|
|
1649
|
-
const row = readLiveCoordinationRow(coordRoot, owner.instance_id);
|
|
1650
1748
|
// A workflow child has no user-visible title to set.
|
|
1651
|
-
|
|
1749
|
+
if (coordEnv("WORKFLOW_CHILD") === "1") return;
|
|
1750
|
+
// The live row copies every name-display field from a current heartbeat
|
|
1751
|
+
// cache and leaves them unset otherwise, so a raw cache with nothing
|
|
1752
|
+
// pending proves the row has nothing pending either. That skips a full
|
|
1753
|
+
// ledger read on almost every tool call; only a pending title pays for
|
|
1754
|
+
// the authoritative check below.
|
|
1755
|
+
if (!sessionNameDisplayPending(readHeartbeat(coordRoot, owner.instance_id))) return;
|
|
1756
|
+
const row = readLiveCoordinationRow(coordRoot, owner.instance_id);
|
|
1757
|
+
const name = sessionNameDisplayPending(row);
|
|
1652
1758
|
if (name && toolResponseMintedSessionName(payload?.tool_response, name)) {
|
|
1653
1759
|
// Record the title before asking for it. The suggestion can still
|
|
1654
1760
|
// change afterwards (an assigned-name rewrite, a lifecycle re-mint, a
|
|
@@ -144,29 +144,6 @@ function readPpid(pid: number): number | null {
|
|
|
144
144
|
return null;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
/**
|
|
148
|
-
* Find the parent owner for a subagent invocation. The per-shell marker at
|
|
149
|
-
* `.harnery/shells/<pid>` is set by `sub-agent-start` and removed by
|
|
150
|
-
* `sub-agent-stop`.
|
|
151
|
-
*
|
|
152
|
-
* Phase 2 stub: the marker file isn't written yet, so we return null in most
|
|
153
|
-
* cases. Phase 2 callers can pass through.
|
|
154
|
-
*/
|
|
155
|
-
export function readShellMarker(coordRoot: string, pid: number): string | null {
|
|
156
|
-
const path = join(coordRoot, ".harnery", "shells", String(pid));
|
|
157
|
-
if (!existsSync(path)) return null;
|
|
158
|
-
try {
|
|
159
|
-
return readFileSync(path, "utf8").trim() || null;
|
|
160
|
-
} catch {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/** Read the V3-projected `agent_id` for an owner if it exists. */
|
|
166
|
-
export function readAgentIdForOwner(coordRoot: string, instanceId: string): string | null {
|
|
167
|
-
return readLiveCoordinationRow(coordRoot, instanceId)?.agent_id ?? null;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
147
|
/** Diagnostic: list of pid-map entries (for debugging). */
|
|
171
148
|
export function listPidmap(coordRoot: string): Array<{ pid: number; owner: string }> {
|
|
172
149
|
const dir = join(coordRoot, ".harnery", "pid-map");
|
|
@@ -94,6 +94,67 @@ export function scanAssistantStatusBoxPresent(
|
|
|
94
94
|
);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/** A handoff older than this no longer defers Stop, so a lost notification cannot disable the ritual. */
|
|
98
|
+
export const BACKGROUND_HANDOFF_MAX_AGE_MS = 30 * 60 * 1000;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Claude Code interleaves request-capture rows of several hundred KB each, so
|
|
102
|
+
* the 256KB status-box window can lose a handoff written a minute earlier.
|
|
103
|
+
*/
|
|
104
|
+
const BACKGROUND_HANDOFF_TAIL_BYTES = 8 * 1024 * 1024;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Claude Code background tasks it moved off the foreground on its own (to
|
|
108
|
+
* deliver a mid-command user message) that have not yet posted a
|
|
109
|
+
* `<task-notification>`. Only the structured `toolUseResult` fields written by
|
|
110
|
+
* Claude Code open a handoff; the agent cannot forge them through tool output.
|
|
111
|
+
* Any later notification text naming the task closes it, so a spoofed
|
|
112
|
+
* notification can only make Stop stricter. A command the agent chose to
|
|
113
|
+
* background (`run_in_background`) or a Monitor opens nothing.
|
|
114
|
+
*/
|
|
115
|
+
export function scanPendingBackgroundHandoffs(
|
|
116
|
+
transcriptPath: string | undefined,
|
|
117
|
+
nowMs: number = Date.now(),
|
|
118
|
+
): string[] {
|
|
119
|
+
const readablePath = resolveTranscriptPath(transcriptPath);
|
|
120
|
+
if (!readablePath) return [];
|
|
121
|
+
let size: number;
|
|
122
|
+
try {
|
|
123
|
+
size = statSync(readablePath).size;
|
|
124
|
+
} catch {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
const text = readRange(readablePath, Math.max(0, size - BACKGROUND_HANDOFF_TAIL_BYTES), size);
|
|
128
|
+
if (!text) return [];
|
|
129
|
+
const opened = new Map<string, number>();
|
|
130
|
+
for (const line of text.split("\n")) {
|
|
131
|
+
// Request-capture rows echo the same text; the row-type check below keeps
|
|
132
|
+
// only Claude Code's own top-level tool-result rows.
|
|
133
|
+
if (line.includes("backgroundedToDeliverMessage")) {
|
|
134
|
+
const row = parseTranscriptRow(line);
|
|
135
|
+
const result = row?.toolUseResult as
|
|
136
|
+
| { backgroundTaskId?: unknown; backgroundedToDeliverMessage?: unknown }
|
|
137
|
+
| undefined;
|
|
138
|
+
if (
|
|
139
|
+
row?.type === "user" &&
|
|
140
|
+
result?.backgroundedToDeliverMessage === true &&
|
|
141
|
+
typeof result.backgroundTaskId === "string" &&
|
|
142
|
+
result.backgroundTaskId
|
|
143
|
+
) {
|
|
144
|
+
opened.set(result.backgroundTaskId, Date.parse(String(row.timestamp)));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (line.includes("task-notification")) {
|
|
148
|
+
for (const match of line.matchAll(/<task-id>([^<]+)<\/task-id>/g)) {
|
|
149
|
+
opened.delete(match[1]!);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return [...opened]
|
|
154
|
+
.filter(([, at]) => Number.isFinite(at) && nowMs - at <= BACKGROUND_HANDOFF_MAX_AGE_MS)
|
|
155
|
+
.map(([id]) => id);
|
|
156
|
+
}
|
|
157
|
+
|
|
97
158
|
/** Most recent user-visible assistant text across Claude Code and Codex JSONL. */
|
|
98
159
|
export function scanLatestAssistantText(transcriptPath: string | undefined): string | undefined {
|
|
99
160
|
const readablePath = resolveTranscriptPath(transcriptPath);
|