harnery 0.31.6 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +5 -1
- package/dist/commands/agents.d.ts +0 -9
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +280 -94
- package/dist/commands/browse-session.d.ts +21 -0
- package/dist/commands/browse-session.d.ts.map +1 -0
- package/dist/commands/browse-session.js +157 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +205 -37
- package/dist/commands/checkpoint.js +1 -1
- package/dist/commands/claude-desktop.d.ts +2 -2
- package/dist/commands/claude-desktop.d.ts.map +1 -1
- package/dist/commands/claude-desktop.js +74 -3
- package/dist/commands/deinit.d.ts.map +1 -1
- package/dist/commands/deinit.js +4 -0
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +40 -0
- package/dist/commands/doctor.d.ts +4 -2
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +141 -19
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +26 -5
- package/dist/commands/instructions.d.ts +4 -0
- package/dist/commands/instructions.d.ts.map +1 -0
- package/dist/commands/instructions.js +100 -0
- package/dist/commands/presence.d.ts.map +1 -1
- package/dist/commands/presence.js +4 -1
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +2 -3
- package/dist/core/agents/canonical-emit.d.ts +2 -3
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +6 -5
- package/dist/core/agents/cli.js +128 -89
- package/dist/core/agents/coord-client.d.ts +18 -3
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +5 -4
- package/dist/core/agents/finalization.d.ts +68 -0
- package/dist/core/agents/finalization.d.ts.map +1 -0
- package/dist/core/agents/finalization.js +443 -0
- package/dist/core/agents/git-hook.d.ts +51 -0
- package/dist/core/agents/git-hook.d.ts.map +1 -0
- package/dist/core/agents/git-hook.js +118 -0
- package/dist/core/agents/index.d.ts +1 -0
- package/dist/core/agents/index.d.ts.map +1 -1
- package/dist/core/agents/index.js +1 -0
- package/dist/core/agents/render/prompt-context.d.ts +12 -5
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +70 -16
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +23 -4
- package/dist/core/agents/rules/claim-conflict.js +3 -3
- package/dist/core/agents/rules/commit-conflict.d.ts +15 -6
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/commit-conflict.js +21 -5
- package/dist/core/agents/rules/stop-hook.d.ts +3 -0
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
- package/dist/core/agents/rules/stop-hook.js +59 -23
- package/dist/core/agents/session-events.d.ts +8 -16
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +12 -26
- package/dist/core/agents/state/heartbeat-projector.d.ts +14 -0
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-projector.js +98 -1
- package/dist/core/agents/state/heartbeat-writer.d.ts +47 -5
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +86 -17
- package/dist/core/agents/state/names.d.ts +22 -1
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +40 -2
- package/dist/core/agents/state/session-state.d.ts +44 -0
- package/dist/core/agents/state/session-state.d.ts.map +1 -0
- package/dist/core/agents/state/session-state.js +81 -0
- package/dist/core/config.d.ts +30 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +74 -11
- package/dist/core/governor/planning.d.ts.map +1 -1
- package/dist/core/governor/planning.js +12 -13
- package/dist/core/hooks/adapter/detect.d.ts +2 -4
- package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
- package/dist/core/hooks/adapter/detect.js +4 -14
- package/dist/core/hooks/adapter/events.d.ts.map +1 -1
- package/dist/core/hooks/adapter/events.js +1 -0
- package/dist/core/hooks/adapter/output.d.ts +1 -1
- package/dist/core/hooks/adapter/output.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.js +5 -4
- package/dist/core/hooks/adapter/parse.d.ts +1 -1
- package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
- package/dist/core/hooks/adapter/parse.js +2 -0
- package/dist/core/hooks/adapter/wiring.d.ts +23 -0
- package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
- package/dist/core/hooks/adapter/wiring.js +32 -0
- package/dist/core/hooks/cli.d.ts +1 -2
- package/dist/core/hooks/cli.d.ts.map +1 -1
- package/dist/core/hooks/cli.js +258 -32
- package/dist/core/hooks/codex-authorization.d.ts +30 -0
- package/dist/core/hooks/codex-authorization.d.ts.map +1 -0
- package/dist/core/hooks/codex-authorization.js +199 -0
- package/dist/core/hooks/codex-wsl-bridge.d.ts +46 -0
- package/dist/core/hooks/codex-wsl-bridge.d.ts.map +1 -0
- package/dist/core/hooks/codex-wsl-bridge.js +137 -0
- package/dist/core/hooks/effects/index.d.ts +1 -1
- package/dist/core/hooks/effects/index.js +3 -3
- package/dist/core/hooks/events/schema.d.ts +57 -1
- package/dist/core/hooks/events/schema.d.ts.map +1 -1
- package/dist/core/hooks/resolve/owner.d.ts +7 -1
- package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
- package/dist/core/hooks/resolve/owner.js +15 -0
- package/dist/core/hooks/resolve/transcript.d.ts +44 -0
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +176 -1
- package/dist/core/hooks/session-name-presence.d.ts +30 -0
- package/dist/core/hooks/session-name-presence.d.ts.map +1 -0
- package/dist/core/hooks/session-name-presence.js +43 -0
- package/dist/core/hooks/unsafe-cross-shell.d.ts +13 -0
- package/dist/core/hooks/unsafe-cross-shell.d.ts.map +1 -0
- package/dist/core/hooks/unsafe-cross-shell.js +151 -0
- package/dist/core/presence/blob.d.ts +4 -0
- package/dist/core/presence/blob.d.ts.map +1 -1
- package/dist/core/presence/blob.js +12 -0
- package/dist/core/presence/index.js +15 -1
- package/dist/core/work/state.d.ts +4 -5
- package/dist/core/work/state.d.ts.map +1 -1
- package/dist/core/work/state.js +5 -8
- package/dist/core/workflow/index.d.ts +1 -1
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/proof.d.ts +2 -2
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +2 -2
- package/dist/core/workflow/run-state.d.ts +2 -2
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +2 -2
- package/dist/core/workflow/types.d.ts +3 -3
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/execution.d.ts +2 -2
- package/dist/core/workflow/workspaces/execution.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/execution.js +5 -0
- package/dist/core/workflow/workspaces/index.d.ts +1 -1
- package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.js +1 -2
- package/dist/core/workflow/workspaces/paths.d.ts +2 -2
- package/dist/core/workflow/workspaces/types.d.ts +0 -6
- package/dist/core/workflow/workspaces/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/validate.d.ts +0 -2
- package/dist/core/workflow/workspaces/validate.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/validate.js +0 -2
- package/dist/lib/browser/client.d.ts +118 -1
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +435 -6
- package/dist/lib/browser/geometry.d.ts.map +1 -1
- package/dist/lib/browser/geometry.js +180 -37
- package/dist/lib/browser/index.d.ts +5 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +5 -1
- package/dist/lib/browser/netscape-cookies.d.ts +6 -0
- package/dist/lib/browser/netscape-cookies.d.ts.map +1 -0
- package/dist/lib/browser/netscape-cookies.js +31 -0
- package/dist/lib/browser/proxy.d.ts +24 -0
- package/dist/lib/browser/proxy.d.ts.map +1 -0
- package/dist/lib/browser/proxy.js +84 -0
- package/dist/lib/browser/runts.d.ts +6 -0
- package/dist/lib/browser/runts.d.ts.map +1 -1
- package/dist/lib/browser/runts.js +21 -4
- package/dist/lib/browser/session-control.d.ts +112 -0
- package/dist/lib/browser/session-control.d.ts.map +1 -0
- package/dist/lib/browser/session-control.js +670 -0
- package/dist/lib/claude-desktop.d.ts +47 -0
- package/dist/lib/claude-desktop.d.ts.map +1 -1
- package/dist/lib/claude-desktop.js +169 -1
- package/dist/lib/docs-links.d.ts +108 -0
- package/dist/lib/docs-links.d.ts.map +1 -0
- package/dist/lib/docs-links.js +555 -0
- package/dist/lib/exec.d.ts +1 -1
- package/dist/lib/exec.js +1 -1
- package/dist/lib/identities/assume.d.ts +4 -2
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +15 -2
- package/dist/lib/instructions/bundle.d.ts +33 -0
- package/dist/lib/instructions/bundle.d.ts.map +1 -0
- package/dist/lib/instructions/bundle.js +149 -0
- package/dist/lib/instructions/git-hooks.d.ts +75 -0
- package/dist/lib/instructions/git-hooks.d.ts.map +1 -0
- package/dist/lib/instructions/git-hooks.js +238 -0
- package/dist/lib/instructions/splice.d.ts +10 -4
- package/dist/lib/instructions/splice.d.ts.map +1 -1
- package/dist/lib/instructions/splice.js +19 -11
- package/package.json +2 -2
- package/schemas/config.schema.json +32 -0
- package/src/commander.ts +5 -1
- package/src/commands/agents.ts +341 -113
- package/src/commands/browse-session.ts +245 -0
- package/src/commands/browse.ts +287 -44
- package/src/commands/checkpoint.ts +1 -1
- package/src/commands/claude-desktop.ts +106 -5
- package/src/commands/deinit.ts +7 -1
- package/src/commands/docs.ts +56 -0
- package/src/commands/doctor.ts +155 -21
- package/src/commands/init.ts +28 -9
- package/src/commands/instructions.ts +129 -0
- package/src/commands/presence.ts +7 -1
- package/src/commands/tunnel.ts +2 -5
- package/src/core/agents/canonical-emit.ts +7 -6
- package/src/core/agents/cli.ts +143 -89
- package/src/core/agents/coord-client.ts +21 -5
- package/src/core/agents/finalization.ts +595 -0
- package/src/core/agents/git-hook.ts +126 -0
- package/src/core/agents/index.ts +1 -0
- package/src/core/agents/render/prompt-context.ts +96 -16
- package/src/core/agents/render/session-context.ts +29 -4
- package/src/core/agents/rules/claim-conflict.ts +3 -3
- package/src/core/agents/rules/commit-conflict.ts +35 -8
- package/src/core/agents/rules/stop-hook.ts +71 -23
- package/src/core/agents/session-events.ts +14 -41
- package/src/core/agents/state/heartbeat-projector.ts +108 -1
- package/src/core/agents/state/heartbeat-writer.ts +129 -16
- package/src/core/agents/state/names.ts +54 -2
- package/src/core/agents/state/session-state.ts +131 -0
- package/src/core/config.ts +92 -12
- package/src/core/governor/planning.ts +17 -12
- package/src/core/hooks/adapter/detect.ts +4 -13
- package/src/core/hooks/adapter/events.ts +1 -0
- package/src/core/hooks/adapter/output.ts +9 -4
- package/src/core/hooks/adapter/parse.ts +3 -0
- package/src/core/hooks/adapter/wiring.ts +43 -0
- package/src/core/hooks/cli.ts +296 -27
- package/src/core/hooks/codex-authorization.ts +253 -0
- package/src/core/hooks/codex-wsl-bridge.ts +188 -0
- package/src/core/hooks/effects/index.ts +5 -5
- package/src/core/hooks/events/schema.ts +71 -0
- package/src/core/hooks/resolve/owner.ts +22 -1
- package/src/core/hooks/resolve/transcript.ts +164 -1
- package/src/core/hooks/session-name-presence.ts +52 -0
- package/src/core/hooks/unsafe-cross-shell.ts +160 -0
- package/src/core/presence/blob.ts +18 -0
- package/src/core/presence/index.ts +18 -1
- package/src/core/work/state.ts +8 -12
- package/src/core/workflow/engine.ts +2 -2
- package/src/core/workflow/index.ts +0 -1
- package/src/core/workflow/proof.ts +4 -4
- package/src/core/workflow/run-state.ts +5 -5
- package/src/core/workflow/types.ts +3 -3
- package/src/core/workflow/workspaces/execution.ts +11 -4
- package/src/core/workflow/workspaces/index.ts +0 -3
- package/src/core/workflow/workspaces/local-git.ts +1 -2
- package/src/core/workflow/workspaces/paths.ts +2 -2
- package/src/core/workflow/workspaces/types.ts +0 -9
- package/src/core/workflow/workspaces/validate.ts +1 -5
- package/src/lib/browser/client.ts +528 -6
- package/src/lib/browser/geometry.ts +197 -37
- package/src/lib/browser/index.ts +39 -0
- package/src/lib/browser/netscape-cookies.ts +39 -0
- package/src/lib/browser/proxy.ts +105 -0
- package/src/lib/browser/runts.ts +27 -3
- package/src/lib/browser/session-control.ts +892 -0
- package/src/lib/claude-desktop.ts +232 -1
- package/src/lib/docs-links.ts +674 -0
- package/src/lib/exec.ts +1 -1
- package/src/lib/identities/assume.ts +22 -1
- package/src/lib/instructions/bundle.ts +199 -0
- package/src/lib/instructions/git-hooks.ts +259 -0
- package/src/lib/instructions/splice.ts +41 -11
package/src/core/hooks/cli.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `agent-hook` CLI entry point
|
|
3
|
-
* alongside the legacy stream.
|
|
2
|
+
* `agent-hook` CLI entry point for canonical event emission.
|
|
4
3
|
*
|
|
5
4
|
* Flow:
|
|
6
5
|
* 1. Parse argv → event-name + adapter.
|
|
@@ -22,13 +21,22 @@ import { spawnSync } from "node:child_process";
|
|
|
22
21
|
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
23
22
|
import { dirname, join } from "node:path";
|
|
24
23
|
import { coordEnv } from "../../lib/env.ts";
|
|
24
|
+
import { buildInstructionBundle } from "../../lib/instructions/bundle.ts";
|
|
25
25
|
import { replayCodexJsonl } from "../agents/codex-replay.ts";
|
|
26
26
|
import { coordBinPath } from "../agents/coord-bin.ts";
|
|
27
27
|
import { consumeSince, writeCursor } from "../agents/events/consume.ts";
|
|
28
|
+
import {
|
|
29
|
+
type ClaimFinalizationDecision,
|
|
30
|
+
type ClaimFinalizationDescriptor,
|
|
31
|
+
classifyWriteClaimFinalization,
|
|
32
|
+
formatWriteClaimFinalizationDenial,
|
|
33
|
+
} from "../agents/finalization.ts";
|
|
28
34
|
import { evaluateStopHook } from "../agents/rules/stop-hook.ts";
|
|
29
35
|
import { projectHeartbeats } from "../agents/state/heartbeat-projector.ts";
|
|
36
|
+
import { stampSessionStateEvent } from "../agents/state/heartbeat-writer.ts";
|
|
30
37
|
import { writePidmapRow } from "../agents/state/pidmap.ts";
|
|
31
38
|
import { shellMutationPaths } from "../agents/state/shell-mutation.ts";
|
|
39
|
+
import { agentsRequireGitFinalization, resolveBinName } from "../config.ts";
|
|
32
40
|
import {
|
|
33
41
|
checkpointContext,
|
|
34
42
|
completeContextRecovery,
|
|
@@ -49,6 +57,12 @@ import {
|
|
|
49
57
|
type ParsedPayload,
|
|
50
58
|
parsePayload,
|
|
51
59
|
} from "./adapter/parse.ts";
|
|
60
|
+
import {
|
|
61
|
+
codexWslFileLinkTelemetry,
|
|
62
|
+
inspectCodexWslBridge,
|
|
63
|
+
isWslUncPath,
|
|
64
|
+
renderCodexWslFileLinkContext,
|
|
65
|
+
} from "./codex-wsl-bridge.ts";
|
|
52
66
|
import {
|
|
53
67
|
captureImages,
|
|
54
68
|
detectPresence,
|
|
@@ -69,7 +83,14 @@ import { adapterPidFromEnv, parsePsChainLine, selectAnchorPid } from "./resolve/
|
|
|
69
83
|
import { findCoordRoot } from "./resolve/coord-root.ts";
|
|
70
84
|
import { extractIntentComment, resolveIntent } from "./resolve/intent.ts";
|
|
71
85
|
import { resolveOwner } from "./resolve/owner.ts";
|
|
72
|
-
import {
|
|
86
|
+
import {
|
|
87
|
+
detectForkParent,
|
|
88
|
+
scanAssistantTextIncludes,
|
|
89
|
+
scanStatusBoxPresent,
|
|
90
|
+
scanTranscriptModel,
|
|
91
|
+
} from "./resolve/transcript.ts";
|
|
92
|
+
import { sessionNamePresence } from "./session-name-presence.ts";
|
|
93
|
+
import { unsafeCrossShellReason } from "./unsafe-cross-shell.ts";
|
|
73
94
|
|
|
74
95
|
interface Argv {
|
|
75
96
|
eventName: string | null;
|
|
@@ -80,11 +101,11 @@ function parseArgv(argv: string[]): Argv {
|
|
|
80
101
|
const out: Argv = { eventName: null, extra: [] };
|
|
81
102
|
for (let i = 0; i < argv.length; i++) {
|
|
82
103
|
const arg = argv[i]!;
|
|
83
|
-
if (arg === "--adapter"
|
|
104
|
+
if (arg === "--adapter") {
|
|
84
105
|
i++; // detectAdapter will re-parse; just consume the value here.
|
|
85
106
|
continue;
|
|
86
107
|
}
|
|
87
|
-
if (arg.startsWith("--adapter=")
|
|
108
|
+
if (arg.startsWith("--adapter=")) continue;
|
|
88
109
|
if (!out.eventName && !arg.startsWith("--")) {
|
|
89
110
|
out.eventName = arg;
|
|
90
111
|
} else {
|
|
@@ -160,11 +181,14 @@ function assignNameViaAgentCoord(
|
|
|
160
181
|
coordRoot: string,
|
|
161
182
|
instanceId: string,
|
|
162
183
|
kind: "session" | "subagent" | "transient",
|
|
184
|
+
forkedFrom?: string,
|
|
163
185
|
): { name: string; kind: string } | null {
|
|
164
186
|
const binary = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
165
187
|
if (!existsSync(binary)) return null;
|
|
166
188
|
try {
|
|
167
|
-
const
|
|
189
|
+
const args = ["assign-name", instanceId, kind];
|
|
190
|
+
if (forkedFrom) args.push("--forked-from", forkedFrom);
|
|
191
|
+
const result = spawnSync(binary, args, {
|
|
168
192
|
encoding: "utf8",
|
|
169
193
|
timeout: 2000,
|
|
170
194
|
env: childEnv(coordRoot),
|
|
@@ -238,13 +262,26 @@ function buildEventData(
|
|
|
238
262
|
case "session.start": {
|
|
239
263
|
const adapterPlatform =
|
|
240
264
|
ctx.adapter === "claude-code"
|
|
241
|
-
? "
|
|
265
|
+
? "claude-code"
|
|
242
266
|
: ctx.adapter === "cursor"
|
|
243
267
|
? "cursor"
|
|
244
268
|
: "codex";
|
|
269
|
+
// Recorded fork lineage is NOT detected here. On claude-code a fork
|
|
270
|
+
// never fires its own session.start — SessionStart fires under the
|
|
271
|
+
// PARENT's session id (source=resume) before the fork id is minted
|
|
272
|
+
// (verified 2026-08-05) — so detection at this point can only mislabel
|
|
273
|
+
// the resumed parent. The fork's new instance is caught by the
|
|
274
|
+
// tool.pre_use heal path instead. The forkedFrom plumbing below stays
|
|
275
|
+
// for adapters that DO report a parent at session start.
|
|
276
|
+
const forkedFrom: string | undefined = undefined;
|
|
245
277
|
// Assign (or recover) name + kind via agent-coord. Idempotent: resume
|
|
246
278
|
// returns the original name; new owner consumes a counter slot.
|
|
247
|
-
const assigned = assignNameViaAgentCoord(
|
|
279
|
+
const assigned = assignNameViaAgentCoord(
|
|
280
|
+
ctx.coordRoot,
|
|
281
|
+
ctx.instanceId,
|
|
282
|
+
"session",
|
|
283
|
+
forkedFrom,
|
|
284
|
+
);
|
|
248
285
|
// Write the adapter pid-map row so `harn agents whoami` ppid-walks find
|
|
249
286
|
// this owner. Prefer the payload pid (the actual claude binary), then the
|
|
250
287
|
// anchor walk (the `node` ancestor for Cursor, which has no payload pid),
|
|
@@ -255,6 +292,7 @@ function buildEventData(
|
|
|
255
292
|
if (adapterPid) {
|
|
256
293
|
writePidmapViaAgentCoord(ctx.coordRoot, adapterPid, ctx.instanceId, adapterPlatform);
|
|
257
294
|
}
|
|
295
|
+
const bundle = safeInstructionBundle(ctx.coordRoot, p?.cwd ?? process.cwd(), ctx.adapter);
|
|
258
296
|
return {
|
|
259
297
|
started_at: new Date().toISOString(),
|
|
260
298
|
cwd: p?.cwd ?? process.cwd(),
|
|
@@ -268,6 +306,18 @@ function buildEventData(
|
|
|
268
306
|
name: assigned?.name,
|
|
269
307
|
kind: "session",
|
|
270
308
|
agent_id: ctx.instanceId,
|
|
309
|
+
...(bundle
|
|
310
|
+
? {
|
|
311
|
+
instruction_bundle_id: bundle.instruction_bundle_id,
|
|
312
|
+
instruction_source_id: bundle.canonical_source_id,
|
|
313
|
+
instruction_profile_root: bundle.profile_root,
|
|
314
|
+
instruction_component_count: bundle.components.length,
|
|
315
|
+
}
|
|
316
|
+
: {}),
|
|
317
|
+
// Recorded fork lineage rides the canonical event too, so full replay
|
|
318
|
+
// and derived readers converge on the same parent linkage that
|
|
319
|
+
// .name-history carries (the ADR 0017 dual-write pattern).
|
|
320
|
+
...(forkedFrom ? { forked_from: forkedFrom } : {}),
|
|
271
321
|
// Workflow-child linkage: `workflow run` children carry the run id in
|
|
272
322
|
// env (spawn adapters set it via buildChildEnv), so their sessions and
|
|
273
323
|
// heartbeats join back to the run transcript + /workflows web view.
|
|
@@ -298,6 +348,11 @@ function buildEventData(
|
|
|
298
348
|
}
|
|
299
349
|
|
|
300
350
|
case "turn.stop": {
|
|
351
|
+
const lastAssistantMessage = (p?.raw.last_assistant_message as string | undefined) ?? "";
|
|
352
|
+
const fileLinkTelemetry =
|
|
353
|
+
ctx.adapter === "codex"
|
|
354
|
+
? codexWslFileLinkTelemetry(ctx.coordRoot, p?.cwd, lastAssistantMessage)
|
|
355
|
+
: null;
|
|
301
356
|
return {
|
|
302
357
|
// Backfill the model for adapters that omit it at session.start
|
|
303
358
|
// (Claude Code). The transcript is populated with assistant turns by
|
|
@@ -316,8 +371,27 @@ function buildEventData(
|
|
|
316
371
|
// the verdict now sees because agent-hook emits this turn.stop itself
|
|
317
372
|
// (the previous path passed those via the no-history fail-open).
|
|
318
373
|
status_box_present:
|
|
319
|
-
scanStatusBoxPresent(p?.transcript_path) ||
|
|
320
|
-
|
|
374
|
+
scanStatusBoxPresent(p?.transcript_path) || lastAssistantMessage.includes("┌─ agent-"),
|
|
375
|
+
// Shadow measurement for the rule-2/3 detector: the loose scan above
|
|
376
|
+
// matches the box prefix ANYWHERE in the transcript tail, including
|
|
377
|
+
// the tool_result row the status command itself writes, so it can
|
|
378
|
+
// report a paste that never happened. The strict variant counts
|
|
379
|
+
// assistant text only (same window, same scanner as the session-name
|
|
380
|
+
// detection). Telemetry-only: the Stop verdict still reads
|
|
381
|
+
// status_box_present. Once the divergence rate between the two fields
|
|
382
|
+
// is known, the gate either flips to strict or the rule gets
|
|
383
|
+
// reconsidered; either way this field decides it with data.
|
|
384
|
+
status_box_present_strict:
|
|
385
|
+
scanAssistantTextIncludes(p?.transcript_path, "┌─ agent-") ||
|
|
386
|
+
lastAssistantMessage.includes("┌─ agent-"),
|
|
387
|
+
// Whether the session's suggested name is satisfied as of this turn,
|
|
388
|
+
// and which name that refers to. Reported on every stop while a name
|
|
389
|
+
// exists (the stop-hook.session_name verdict reads it per turn); the
|
|
390
|
+
// transcript scan itself stops once the name has been sighted.
|
|
391
|
+
...sessionNamePresence(ctx.coordRoot, ctx.instanceId, lastAssistantMessage, (name) =>
|
|
392
|
+
scanAssistantTextIncludes(p?.transcript_path, name),
|
|
393
|
+
),
|
|
394
|
+
...(fileLinkTelemetry ?? {}),
|
|
321
395
|
stop_hook_active: p?.stop_hook_active,
|
|
322
396
|
};
|
|
323
397
|
}
|
|
@@ -372,6 +446,16 @@ function buildEventData(
|
|
|
372
446
|
};
|
|
373
447
|
}
|
|
374
448
|
|
|
449
|
+
case "interaction.input_requested": {
|
|
450
|
+
const description = extractToolDescription(p?.tool_input);
|
|
451
|
+
const reason = description ? clampString(description, 500).value : undefined;
|
|
452
|
+
return {
|
|
453
|
+
request_kind: "permission",
|
|
454
|
+
tool_name: p?.tool_name,
|
|
455
|
+
...(reason ? { reason } : {}),
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
375
459
|
case "tool.post_use": {
|
|
376
460
|
const toolName = p?.tool_name ?? "unknown";
|
|
377
461
|
const summary = summarizeOutput(p?.tool_response);
|
|
@@ -425,6 +509,15 @@ function buildEventData(
|
|
|
425
509
|
}
|
|
426
510
|
}
|
|
427
511
|
|
|
512
|
+
function safeInstructionBundle(coordRoot: string, cwd: string, adapter: Adapter) {
|
|
513
|
+
try {
|
|
514
|
+
return buildInstructionBundle({ coordRoot, cwd, adapter });
|
|
515
|
+
} catch (err) {
|
|
516
|
+
logError(coordRoot, err, { phase: "instruction-bundle-identity" });
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
428
521
|
function objectRecord(value: unknown): Record<string, unknown> | undefined {
|
|
429
522
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
430
523
|
? (value as Record<string, unknown>)
|
|
@@ -539,6 +632,21 @@ async function main(): Promise<number> {
|
|
|
539
632
|
event_id: envelope.event_id,
|
|
540
633
|
});
|
|
541
634
|
|
|
635
|
+
// Activity is only useful while it is current. Stamp prompt and permission
|
|
636
|
+
// evidence directly from this envelope rather than draining the global
|
|
637
|
+
// projector: a drain here could apply a tool event before its authorization
|
|
638
|
+
// verdict. Tool progress is stamped later, after the guard allows it.
|
|
639
|
+
if (
|
|
640
|
+
norm.event_type === "user_prompt.submit" ||
|
|
641
|
+
norm.event_type === "interaction.input_requested"
|
|
642
|
+
) {
|
|
643
|
+
try {
|
|
644
|
+
stampSessionStateEvent(coordRoot, owner.instance_id, envelope);
|
|
645
|
+
} catch (err) {
|
|
646
|
+
logError(coordRoot, err, { phase: "activity-projection" });
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
542
650
|
// Context telemetry is opportunistic and truthful: only persist a sample
|
|
543
651
|
// when the adapter payload actually exposes usage/window data. Identical
|
|
544
652
|
// measurements are de-duplicated before they reach the canonical stream.
|
|
@@ -805,6 +913,7 @@ async function main(): Promise<number> {
|
|
|
805
913
|
owner.instance_id,
|
|
806
914
|
sessionId,
|
|
807
915
|
adapter,
|
|
916
|
+
payload?.cwd,
|
|
808
917
|
recovery?.briefing ?? "",
|
|
809
918
|
);
|
|
810
919
|
if (injected && recovery) {
|
|
@@ -891,13 +1000,30 @@ async function main(): Promise<number> {
|
|
|
891
1000
|
bypass: coordEnv("AGENT_COORD_BYPASS_STOP") === "1",
|
|
892
1001
|
workflow_child: coordEnv("WORKFLOW_CHILD") === "1",
|
|
893
1002
|
});
|
|
1003
|
+
const enforcementMode = stopEnforcementMode(verdict.rule);
|
|
1004
|
+
emit(coordRoot, {
|
|
1005
|
+
event_type: "stop.verdict",
|
|
1006
|
+
instance_id: owner.instance_id,
|
|
1007
|
+
session_id: sessionId,
|
|
1008
|
+
turn_id: payload?.turn_id,
|
|
1009
|
+
parent_turn_id: payload?.parent_turn_id,
|
|
1010
|
+
adapter,
|
|
1011
|
+
data: {
|
|
1012
|
+
allow: verdict.allow,
|
|
1013
|
+
rule: verdict.rule,
|
|
1014
|
+
...(verdict.reason ? { reason: verdict.reason } : {}),
|
|
1015
|
+
enforcement_mode: enforcementMode,
|
|
1016
|
+
eligible: enforcementMode === "enforced",
|
|
1017
|
+
nag_delivered: enforcementMode === "enforced" && !verdict.allow,
|
|
1018
|
+
},
|
|
1019
|
+
});
|
|
894
1020
|
if (!verdict.allow) {
|
|
895
1021
|
// Adapter-aware enforcement channel: Claude Code / Codex honor exit-2 +
|
|
896
1022
|
// stderr as a turn block; Cursor ignores exit codes (fail-open) and
|
|
897
1023
|
// re-prompts only via a `followup_message` it auto-submits. emitStopBlock
|
|
898
1024
|
// writes the right shape and returns the exit code to use.
|
|
899
1025
|
const { emitStopBlock } = await import("./adapter/output.ts");
|
|
900
|
-
return emitStopBlock(adapter, verdict);
|
|
1026
|
+
return emitStopBlock(adapter, verdict, coordRoot);
|
|
901
1027
|
}
|
|
902
1028
|
}
|
|
903
1029
|
|
|
@@ -912,7 +1038,21 @@ async function main(): Promise<number> {
|
|
|
912
1038
|
// call was restored here afterward.
|
|
913
1039
|
if (norm.event_type === "tool.pre_use") {
|
|
914
1040
|
try {
|
|
915
|
-
|
|
1041
|
+
// Recorded fork lineage, heal-path flavor. A forked CC conversation
|
|
1042
|
+
// never fires its own session.start (SessionStart fires under the
|
|
1043
|
+
// PARENT's session id with source=resume, before the fork id is
|
|
1044
|
+
// minted; verified 2026-08-05), so the fork's new instance first
|
|
1045
|
+
// materializes right here. Gate detection on "no heartbeat yet" so the
|
|
1046
|
+
// transcript scan runs once per instance lifetime, not per tool call.
|
|
1047
|
+
let forkedFrom: string | undefined;
|
|
1048
|
+
if (
|
|
1049
|
+
adapter === "claude-code" &&
|
|
1050
|
+
payload?.transcript_path &&
|
|
1051
|
+
!existsSync(join(coordRoot, ".harnery", "active", `${owner.instance_id}.json`))
|
|
1052
|
+
) {
|
|
1053
|
+
forkedFrom = detectForkParent(payload.transcript_path, owner.instance_id);
|
|
1054
|
+
}
|
|
1055
|
+
healHeartbeatViaCli(coordRoot, owner.instance_id, sessionId, adapter, forkedFrom);
|
|
916
1056
|
refreshPidmap(coordRoot, owner.instance_id, adapter, payload?.pid);
|
|
917
1057
|
} catch (err) {
|
|
918
1058
|
logError(coordRoot, err, { phase: "pre-tool-use-heal" });
|
|
@@ -933,6 +1073,29 @@ async function main(): Promise<number> {
|
|
|
933
1073
|
logError(coordRoot, err, { phase: "pre-tool-use-image-capture" });
|
|
934
1074
|
}
|
|
935
1075
|
|
|
1076
|
+
// Windows-native Codex + WSL UNC only: block the one cross-shell shape
|
|
1077
|
+
// proven to corrupt argument boundaries. Normal WSL argv calls, literal
|
|
1078
|
+
// bash -s scripts, native Linux/macOS sessions, and every other adapter
|
|
1079
|
+
// pass through. The host instructions name its concrete safe bridge.
|
|
1080
|
+
const unsafeShellReason = unsafeCrossShellReason({
|
|
1081
|
+
adapter,
|
|
1082
|
+
cwd: payload?.cwd,
|
|
1083
|
+
toolName: payload?.tool_name,
|
|
1084
|
+
toolInput: payload?.tool_input,
|
|
1085
|
+
});
|
|
1086
|
+
if (unsafeShellReason) {
|
|
1087
|
+
emit(coordRoot, {
|
|
1088
|
+
event_type: "decision.block",
|
|
1089
|
+
instance_id: owner.instance_id,
|
|
1090
|
+
session_id: sessionId,
|
|
1091
|
+
adapter,
|
|
1092
|
+
data: { rule: "unsafe_cross_shell", reason: unsafeShellReason },
|
|
1093
|
+
});
|
|
1094
|
+
const { emitDeny } = await import("./adapter/output.ts");
|
|
1095
|
+
emitDeny(adapter, unsafeShellReason);
|
|
1096
|
+
return 0;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
936
1099
|
// G-guard for ALL adapters. Claude Code previously ran this via a
|
|
937
1100
|
// pre-tool-use bash adapter (which called `agent-coord verdict --rule=claim`);
|
|
938
1101
|
// that adapter is now deleted, so agent-hook owns the deny for every adapter.
|
|
@@ -941,12 +1104,27 @@ async function main(): Promise<number> {
|
|
|
941
1104
|
// apply_patch (codex) parses paths from the patch body and runs verdict
|
|
942
1105
|
// per-path; Edit/Write/NotebookEdit resolve a single target. Non-write tools
|
|
943
1106
|
// (incl. Agent) yield no targets and pass through with no deny.
|
|
1107
|
+
let guardAllowed = true;
|
|
944
1108
|
try {
|
|
945
|
-
await runPreToolUseGuard(
|
|
1109
|
+
guardAllowed = await runPreToolUseGuard(
|
|
1110
|
+
coordRoot,
|
|
1111
|
+
owner.instance_id,
|
|
1112
|
+
sessionId,
|
|
1113
|
+
data,
|
|
1114
|
+
adapter,
|
|
1115
|
+
);
|
|
946
1116
|
} catch (err) {
|
|
947
1117
|
logError(coordRoot, err, { phase: "pre-tool-use-guard" });
|
|
948
1118
|
}
|
|
949
1119
|
|
|
1120
|
+
if (guardAllowed) {
|
|
1121
|
+
try {
|
|
1122
|
+
stampSessionStateEvent(coordRoot, owner.instance_id, envelope);
|
|
1123
|
+
} catch (err) {
|
|
1124
|
+
logError(coordRoot, err, { phase: "activity-projection" });
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
950
1128
|
// Shell-mutation warn (warn-only, never blocks). Was the cursor
|
|
951
1129
|
// beforeShellExecution + codex preToolUse-Bash shell-mutation-claim-log in
|
|
952
1130
|
// the per-adapter shell adapters. Cursor sends the command at payload.command;
|
|
@@ -1025,21 +1203,60 @@ async function main(): Promise<number> {
|
|
|
1025
1203
|
return 0;
|
|
1026
1204
|
}
|
|
1027
1205
|
|
|
1206
|
+
function stopEnforcementMode(rule: string): "enforced" | "observe_only" | "exempt" | "fail_open" {
|
|
1207
|
+
if (rule === "stop-hook.codex_observe_only") return "observe_only";
|
|
1208
|
+
if (rule === "stop-hook.fail_open" || rule === "stop-hook.no_history") return "fail_open";
|
|
1209
|
+
if (rule === "stop-hook.bypass" || rule === "stop-hook.workflow_child") return "exempt";
|
|
1210
|
+
return "enforced";
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1028
1213
|
async function runPreToolUseGuard(
|
|
1029
1214
|
coordRoot: string,
|
|
1030
1215
|
instanceId: string,
|
|
1031
1216
|
sessionId: string,
|
|
1032
1217
|
data: Record<string, unknown>,
|
|
1033
1218
|
adapter: Adapter,
|
|
1034
|
-
): Promise<
|
|
1219
|
+
): Promise<boolean> {
|
|
1035
1220
|
const toolName = (data.tool_name as string | undefined) ?? "";
|
|
1036
|
-
const
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1221
|
+
const rawTargets = collectGuardTargets(toolName, data);
|
|
1222
|
+
if (rawTargets.length === 0) return true;
|
|
1223
|
+
|
|
1224
|
+
let targets: Array<{ path: string; finalization?: ClaimFinalizationDescriptor }>;
|
|
1225
|
+
if (agentsRequireGitFinalization(coordRoot)) {
|
|
1226
|
+
const decisions = rawTargets.map((path) => classifyWriteClaimFinalization(coordRoot, path));
|
|
1227
|
+
const denied = decisions.find((decision) => !decision.allow);
|
|
1228
|
+
if (denied && !denied.allow) {
|
|
1229
|
+
const { emitDeny } = await import("./adapter/output.ts");
|
|
1230
|
+
emitDeny(adapter, formatWriteClaimFinalizationDenial(denied, resolveBinName(coordRoot)));
|
|
1231
|
+
return false;
|
|
1232
|
+
}
|
|
1233
|
+
const allowed = decisions.filter(
|
|
1234
|
+
(decision): decision is Extract<ClaimFinalizationDecision, { allow: true }> => decision.allow,
|
|
1235
|
+
);
|
|
1236
|
+
targets = allowed.map((decision) => ({
|
|
1237
|
+
path: decision.path,
|
|
1238
|
+
finalization: decision.descriptor,
|
|
1239
|
+
}));
|
|
1240
|
+
} else {
|
|
1241
|
+
targets = rawTargets
|
|
1242
|
+
.map((path) => canonicalize(coordRoot, path))
|
|
1243
|
+
.filter((path): path is string => path !== null)
|
|
1244
|
+
.map((path) => ({ path }));
|
|
1245
|
+
}
|
|
1246
|
+
if (targets.length === 0) return true;
|
|
1040
1247
|
|
|
1041
1248
|
const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
1042
|
-
if (!existsSync(agentCoordBin))
|
|
1249
|
+
if (!existsSync(agentCoordBin)) {
|
|
1250
|
+
if (agentsRequireGitFinalization(coordRoot)) {
|
|
1251
|
+
const { emitDeny } = await import("./adapter/output.ts");
|
|
1252
|
+
emitDeny(
|
|
1253
|
+
adapter,
|
|
1254
|
+
"Harnery denied the write before mutation because the claim coordinator is unavailable; repair the project's Harnery installation and retry.",
|
|
1255
|
+
);
|
|
1256
|
+
return false;
|
|
1257
|
+
}
|
|
1258
|
+
return true;
|
|
1259
|
+
}
|
|
1043
1260
|
|
|
1044
1261
|
// For apply_patch (multi-file), collect siblings so the deny reason names
|
|
1045
1262
|
// them. For single-file tools the array has one entry.
|
|
@@ -1048,7 +1265,7 @@ async function runPreToolUseGuard(
|
|
|
1048
1265
|
rule: "claim",
|
|
1049
1266
|
instance_id: instanceId,
|
|
1050
1267
|
session_id: sessionId,
|
|
1051
|
-
path: target,
|
|
1268
|
+
path: target.path,
|
|
1052
1269
|
});
|
|
1053
1270
|
const result = spawnSync(agentCoordBin, ["verdict"], {
|
|
1054
1271
|
input: verdictReq,
|
|
@@ -1064,10 +1281,12 @@ async function runPreToolUseGuard(
|
|
|
1064
1281
|
continue;
|
|
1065
1282
|
}
|
|
1066
1283
|
if (parsed.allow === false) {
|
|
1067
|
-
let reason =
|
|
1284
|
+
let reason =
|
|
1285
|
+
parsed.reason ?? `Path ${target.path} is currently being edited by another agent.`;
|
|
1068
1286
|
if (targets.length > 1) {
|
|
1069
1287
|
const siblings = targets
|
|
1070
|
-
.filter((
|
|
1288
|
+
.filter((candidate) => candidate !== target)
|
|
1289
|
+
.map((candidate) => candidate.path)
|
|
1071
1290
|
.slice(0, 3)
|
|
1072
1291
|
.join(", ");
|
|
1073
1292
|
if (siblings) {
|
|
@@ -1076,9 +1295,28 @@ async function runPreToolUseGuard(
|
|
|
1076
1295
|
}
|
|
1077
1296
|
const { emitDeny } = await import("./adapter/output.ts");
|
|
1078
1297
|
emitDeny(adapter, reason);
|
|
1079
|
-
return;
|
|
1298
|
+
return false;
|
|
1080
1299
|
}
|
|
1300
|
+
|
|
1301
|
+
// The verdict mutates the live heartbeat immediately, but that file is a
|
|
1302
|
+
// projection and can be rebuilt at any later hook. Persist the acquisition
|
|
1303
|
+
// in the canonical stream too. This matters most for Codex apply_patch:
|
|
1304
|
+
// one tool call can claim several files, and tool.pre_use's clamped payload
|
|
1305
|
+
// is not a durable ownership representation for those targets.
|
|
1306
|
+
emit(coordRoot, {
|
|
1307
|
+
event_type: "claim.acquire",
|
|
1308
|
+
instance_id: instanceId,
|
|
1309
|
+
session_id: sessionId,
|
|
1310
|
+
adapter,
|
|
1311
|
+
source: "agent-hooks",
|
|
1312
|
+
data: {
|
|
1313
|
+
path: target.path,
|
|
1314
|
+
mode: "write",
|
|
1315
|
+
...(target.finalization ? { finalization: target.finalization } : {}),
|
|
1316
|
+
},
|
|
1317
|
+
});
|
|
1081
1318
|
}
|
|
1319
|
+
return true;
|
|
1082
1320
|
}
|
|
1083
1321
|
|
|
1084
1322
|
/** Canonicalize a path to monorepo-relative form. Absolute paths under
|
|
@@ -1142,14 +1380,18 @@ function healHeartbeatViaCli(
|
|
|
1142
1380
|
instanceId: string,
|
|
1143
1381
|
sessionId: string,
|
|
1144
1382
|
adapter: string,
|
|
1383
|
+
forkedFrom?: string,
|
|
1145
1384
|
): void {
|
|
1146
1385
|
const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
1147
1386
|
if (!existsSync(agentCoordBin)) return;
|
|
1148
1387
|
// Pass the detected adapter so a pruned Cursor/Codex heartbeat is recreated
|
|
1149
1388
|
// with the correct platform; without it, healHeartbeat defaults to
|
|
1150
|
-
//
|
|
1151
|
-
// heartbeat-writer.healHeartbeat.
|
|
1152
|
-
|
|
1389
|
+
// claude-code and the dashboard mislabels the agent. See
|
|
1390
|
+
// heartbeat-writer.healHeartbeat. --forked-from carries detected fork
|
|
1391
|
+
// lineage for an instance that never session.started (the CC fork flow).
|
|
1392
|
+
const args = ["heal-heartbeat", instanceId, sessionId, `--adapter=${adapter}`];
|
|
1393
|
+
if (forkedFrom) args.push(`--forked-from=${forkedFrom}`);
|
|
1394
|
+
spawnSync(agentCoordBin, args, {
|
|
1153
1395
|
encoding: "utf8",
|
|
1154
1396
|
timeout: 2000,
|
|
1155
1397
|
env: childEnv(coordRoot),
|
|
@@ -1384,6 +1626,7 @@ async function emitUserPromptSubmitSystemMessage(
|
|
|
1384
1626
|
instanceId: string,
|
|
1385
1627
|
sessionId: string,
|
|
1386
1628
|
adapter: Adapter,
|
|
1629
|
+
workspaceCwd?: string,
|
|
1387
1630
|
recoveryBriefing = "",
|
|
1388
1631
|
): Promise<boolean> {
|
|
1389
1632
|
const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
@@ -1410,12 +1653,23 @@ async function emitUserPromptSubmitSystemMessage(
|
|
|
1410
1653
|
// staleness nudges; Claude Code enforces those via its Stop transcript.
|
|
1411
1654
|
// Codex gets a fresh status-footer reminder on every prompt. Its Stop hook
|
|
1412
1655
|
// stays observe-only, so missing the footer never replaces the answer.
|
|
1656
|
+
// Claude Code + Cursor (the Stop-ENFORCING adapters) get a fresh per-turn
|
|
1657
|
+
// ritual reminder: satisfying the contract up front is far cheaper than
|
|
1658
|
+
// the bounce-and-retry the Stop hook otherwise forces.
|
|
1413
1659
|
args.push("--session-name-nudge");
|
|
1414
1660
|
if (adapter === "cursor" || adapter === "codex") args.push("--task-nudge");
|
|
1415
1661
|
if (adapter === "codex") args.push("--status-footer-nudge");
|
|
1662
|
+
if (adapter === "claude-code" || adapter === "cursor")
|
|
1663
|
+
args.push("--turn-ritual-nudge", adapter);
|
|
1416
1664
|
const result = spawnSync(agentCoordBin, args, { encoding: "utf8", timeout: 3000 });
|
|
1417
1665
|
if (result.status === 0 && result.stdout) additionalContext = result.stdout.trim();
|
|
1418
1666
|
}
|
|
1667
|
+
if (adapter === "codex") {
|
|
1668
|
+
const fileLinkContext = renderCodexWslFileLinkContext(coordRoot, workspaceCwd);
|
|
1669
|
+
if (fileLinkContext) {
|
|
1670
|
+
additionalContext = [additionalContext, fileLinkContext].filter(Boolean).join("\n\n");
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1419
1673
|
if (recoveryBriefing) {
|
|
1420
1674
|
additionalContext = [additionalContext, recoveryBriefing].filter(Boolean).join("\n\n");
|
|
1421
1675
|
}
|
|
@@ -1466,7 +1720,7 @@ function emitSubagentStartContext(
|
|
|
1466
1720
|
}
|
|
1467
1721
|
|
|
1468
1722
|
function adapterPlatform(adapter: Adapter): string {
|
|
1469
|
-
if (adapter === "claude-code") return "
|
|
1723
|
+
if (adapter === "claude-code") return "claude-code";
|
|
1470
1724
|
return adapter;
|
|
1471
1725
|
}
|
|
1472
1726
|
|
|
@@ -1527,6 +1781,21 @@ async function emitSessionStartSystemMessage(
|
|
|
1527
1781
|
if (recoveryBriefing) {
|
|
1528
1782
|
additionalContext = [additionalContext, recoveryBriefing].filter(Boolean).join("\n\n");
|
|
1529
1783
|
}
|
|
1784
|
+
if (adapter === "codex" && isWslUncPath(emittedData.cwd)) {
|
|
1785
|
+
const fileLinkContext = renderCodexWslFileLinkContext(coordRoot, emittedData.cwd);
|
|
1786
|
+
if (fileLinkContext) {
|
|
1787
|
+
additionalContext = [additionalContext, fileLinkContext].filter(Boolean).join("\n\n");
|
|
1788
|
+
}
|
|
1789
|
+
const bridge = inspectCodexWslBridge(process.env, { expected: true });
|
|
1790
|
+
if (bridge && !bridge.ok) {
|
|
1791
|
+
additionalContext = [
|
|
1792
|
+
additionalContext,
|
|
1793
|
+
`Harnery hybrid warning: ${bridge.detail}. Run \`${resolveBinName(coordRoot)} doctor\` for the repair hint.`,
|
|
1794
|
+
]
|
|
1795
|
+
.filter(Boolean)
|
|
1796
|
+
.join("\n\n");
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1530
1799
|
if (!additionalContext) return false;
|
|
1531
1800
|
|
|
1532
1801
|
const { emitContext } = await import("./adapter/output.ts");
|