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
|
@@ -84,6 +84,29 @@ export interface AdapterWiringStatus {
|
|
|
84
84
|
* with no settings file at all, or an unparseable one, is skipped.
|
|
85
85
|
*/
|
|
86
86
|
export declare function loadAdapterWiring(projectRoot: string): AdapterWiringStatus[];
|
|
87
|
+
export interface AdapterWiringSummary {
|
|
88
|
+
/** Adapters carrying at least one harnery hook. */
|
|
89
|
+
wired: AdapterId[];
|
|
90
|
+
/**
|
|
91
|
+
* Adapters carrying none: no settings file at all, or a settings file with
|
|
92
|
+
* none of ours in it. An unparseable file lands in neither list, since
|
|
93
|
+
* `loadAdapterWiring` already reports that as drift.
|
|
94
|
+
*/
|
|
95
|
+
unwired: AdapterId[];
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Classify every known adapter as wired or unwired, without judging whether an
|
|
99
|
+
* unwired one is a problem.
|
|
100
|
+
*
|
|
101
|
+
* `loadAdapterWiring` stays deliberately silent about an adapter with zero
|
|
102
|
+
* harnery hooks, because a bare settings file is not an opt-out signal and
|
|
103
|
+
* nagging about it would false-warn every session. That silence is right in
|
|
104
|
+
* isolation and wrong once you also know the adapter's CLI is installed: then
|
|
105
|
+
* an agent can start a session here and register nothing. Splitting the fact
|
|
106
|
+
* from the judgement lets a caller holding both (doctor) tell "not used here"
|
|
107
|
+
* from "never wired" without changing what counts as drift.
|
|
108
|
+
*/
|
|
109
|
+
export declare function summarizeAdapterWiring(projectRoot: string): AdapterWiringSummary;
|
|
87
110
|
/**
|
|
88
111
|
* Resolve the harnery package version for context in nudges/checks. Walks up
|
|
89
112
|
* from this module to the package root (works under Bun from `src/` and Node
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wiring.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/adapter/wiring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,aAAa,CAAC;AAErB,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC5C;AACD,0CAA0C;AAC1C,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,MAAM,SAAS,GAAG,eAAe,GAAG,eAAe,CAAC;AAE1D,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IACpC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAED,iDAAiD;AACjD,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAE3E;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,EAAE,CAMxD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAEnF;AAQD,MAAM,WAAW,UAAU;IACzB,kDAAkD;IAClD,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,iCAAiC;IACjC,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB;;;;OAIG;IACH,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,4EAA4E;IAC5E,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,yEAAyE;IACzE,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CA2ChF;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,SAAS,CAAC;IACnB,wDAAwD;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAwC5E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAkBvC"}
|
|
1
|
+
{"version":3,"file":"wiring.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/adapter/wiring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,aAAa,CAAC;AAErB,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC5C;AACD,0CAA0C;AAC1C,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,MAAM,SAAS,GAAG,eAAe,GAAG,eAAe,CAAC;AAE1D,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IACpC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAED,iDAAiD;AACjD,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAE3E;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,EAAE,CAMxD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAEnF;AAQD,MAAM,WAAW,UAAU;IACzB,kDAAkD;IAClD,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,iCAAiC;IACjC,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB;;;;OAIG;IACH,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,4EAA4E;IAC5E,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,yEAAyE;IACzE,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CA2ChF;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,SAAS,CAAC;IACnB,wDAAwD;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAwC5E;AAED,MAAM,WAAW,oBAAoB;IACnC,mDAAmD;IACnD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB;;;;OAIG;IACH,OAAO,EAAE,SAAS,EAAE,CAAC;CACtB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,oBAAoB,CAkBhF;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAkBvC"}
|
|
@@ -136,6 +136,38 @@ export function loadAdapterWiring(projectRoot) {
|
|
|
136
136
|
}
|
|
137
137
|
return out;
|
|
138
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Classify every known adapter as wired or unwired, without judging whether an
|
|
141
|
+
* unwired one is a problem.
|
|
142
|
+
*
|
|
143
|
+
* `loadAdapterWiring` stays deliberately silent about an adapter with zero
|
|
144
|
+
* harnery hooks, because a bare settings file is not an opt-out signal and
|
|
145
|
+
* nagging about it would false-warn every session. That silence is right in
|
|
146
|
+
* isolation and wrong once you also know the adapter's CLI is installed: then
|
|
147
|
+
* an agent can start a session here and register nothing. Splitting the fact
|
|
148
|
+
* from the judgement lets a caller holding both (doctor) tell "not used here"
|
|
149
|
+
* from "never wired" without changing what counts as drift.
|
|
150
|
+
*/
|
|
151
|
+
export function summarizeAdapterWiring(projectRoot) {
|
|
152
|
+
const wired = [];
|
|
153
|
+
const unwired = [];
|
|
154
|
+
for (const [id, spec] of Object.entries(ADAPTER_SPECS)) {
|
|
155
|
+
const settingsPath = resolve(projectRoot, spec.settingsFile);
|
|
156
|
+
if (!existsSync(settingsPath)) {
|
|
157
|
+
unwired.push(id);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
let settings;
|
|
161
|
+
try {
|
|
162
|
+
settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
continue; // unparseable → already surfaced by loadAdapterWiring
|
|
166
|
+
}
|
|
167
|
+
(diffWiring(settings, spec).present.length > 0 ? wired : unwired).push(id);
|
|
168
|
+
}
|
|
169
|
+
return { wired, unwired };
|
|
170
|
+
}
|
|
139
171
|
/**
|
|
140
172
|
* Resolve the harnery package version for context in nudges/checks. Walks up
|
|
141
173
|
* from this module to the package root (works under Bun from `src/` and Node
|
package/dist/core/hooks/cli.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/cli.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG"}
|
package/dist/core/hooks/cli.js
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.
|
|
@@ -21,17 +20,22 @@ import { spawnSync } from "node:child_process";
|
|
|
21
20
|
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
22
21
|
import { dirname, join } from "node:path";
|
|
23
22
|
import { coordEnv } from "../../lib/env.js";
|
|
23
|
+
import { buildInstructionBundle } from "../../lib/instructions/bundle.js";
|
|
24
24
|
import { replayCodexJsonl } from "../agents/codex-replay.js";
|
|
25
25
|
import { coordBinPath } from "../agents/coord-bin.js";
|
|
26
26
|
import { consumeSince, writeCursor } from "../agents/events/consume.js";
|
|
27
|
+
import { classifyWriteClaimFinalization, formatWriteClaimFinalizationDenial, } from "../agents/finalization.js";
|
|
27
28
|
import { evaluateStopHook } from "../agents/rules/stop-hook.js";
|
|
28
29
|
import { projectHeartbeats } from "../agents/state/heartbeat-projector.js";
|
|
30
|
+
import { stampSessionStateEvent } from "../agents/state/heartbeat-writer.js";
|
|
29
31
|
import { writePidmapRow } from "../agents/state/pidmap.js";
|
|
30
32
|
import { shellMutationPaths } from "../agents/state/shell-mutation.js";
|
|
33
|
+
import { agentsRequireGitFinalization, resolveBinName } from "../config.js";
|
|
31
34
|
import { checkpointContext, completeContextRecovery, extractContextSample, markContextCompactionCompleted, prepareContextRecovery, readContextState, recordContextSample, } from "../context/index.js";
|
|
32
35
|
import { ensureRelayDaemon, fetchPresence, publishPresence } from "../presence/index.js";
|
|
33
36
|
import { detectAdapter } from "./adapter/detect.js";
|
|
34
37
|
import { extractBashCommand, extractToolDescription, normalizeEventName, parsePayload, } from "./adapter/parse.js";
|
|
38
|
+
import { codexWslFileLinkTelemetry, inspectCodexWslBridge, isWslUncPath, renderCodexWslFileLinkContext, } from "./codex-wsl-bridge.js";
|
|
35
39
|
import { captureImages, detectPresence, imageJanitor, journalArchive, journalJanitor, journalRecoveryCue, playSound, resetSoundCounters, runSessionSyncExtension, runTurnSummary, soundForEvent, } from "./effects/index.js";
|
|
36
40
|
import { emit } from "./events/emit.js";
|
|
37
41
|
import { canonicalize } from "./guard-path.js";
|
|
@@ -39,16 +43,18 @@ import { adapterPidFromEnv, parsePsChainLine, selectAnchorPid } from "./resolve/
|
|
|
39
43
|
import { findCoordRoot } from "./resolve/coord-root.js";
|
|
40
44
|
import { extractIntentComment, resolveIntent } from "./resolve/intent.js";
|
|
41
45
|
import { resolveOwner } from "./resolve/owner.js";
|
|
42
|
-
import { scanStatusBoxPresent, scanTranscriptModel } from "./resolve/transcript.js";
|
|
46
|
+
import { detectForkParent, scanAssistantTextIncludes, scanStatusBoxPresent, scanTranscriptModel, } from "./resolve/transcript.js";
|
|
47
|
+
import { sessionNamePresence } from "./session-name-presence.js";
|
|
48
|
+
import { unsafeCrossShellReason } from "./unsafe-cross-shell.js";
|
|
43
49
|
function parseArgv(argv) {
|
|
44
50
|
const out = { eventName: null, extra: [] };
|
|
45
51
|
for (let i = 0; i < argv.length; i++) {
|
|
46
52
|
const arg = argv[i];
|
|
47
|
-
if (arg === "--adapter"
|
|
53
|
+
if (arg === "--adapter") {
|
|
48
54
|
i++; // detectAdapter will re-parse; just consume the value here.
|
|
49
55
|
continue;
|
|
50
56
|
}
|
|
51
|
-
if (arg.startsWith("--adapter=")
|
|
57
|
+
if (arg.startsWith("--adapter="))
|
|
52
58
|
continue;
|
|
53
59
|
if (!out.eventName && !arg.startsWith("--")) {
|
|
54
60
|
out.eventName = arg;
|
|
@@ -118,12 +124,15 @@ function logError(coordRoot, err, context) {
|
|
|
118
124
|
* Lives at agent-hooks side (not agent-coord) to keep emitter/consumer
|
|
119
125
|
* separation: we spawn rather than import.
|
|
120
126
|
*/
|
|
121
|
-
function assignNameViaAgentCoord(coordRoot, instanceId, kind) {
|
|
127
|
+
function assignNameViaAgentCoord(coordRoot, instanceId, kind, forkedFrom) {
|
|
122
128
|
const binary = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
123
129
|
if (!existsSync(binary))
|
|
124
130
|
return null;
|
|
125
131
|
try {
|
|
126
|
-
const
|
|
132
|
+
const args = ["assign-name", instanceId, kind];
|
|
133
|
+
if (forkedFrom)
|
|
134
|
+
args.push("--forked-from", forkedFrom);
|
|
135
|
+
const result = spawnSync(binary, args, {
|
|
127
136
|
encoding: "utf8",
|
|
128
137
|
timeout: 2000,
|
|
129
138
|
env: childEnv(coordRoot),
|
|
@@ -178,13 +187,21 @@ function buildEventData(eventType, ctx) {
|
|
|
178
187
|
switch (eventType) {
|
|
179
188
|
case "session.start": {
|
|
180
189
|
const adapterPlatform = ctx.adapter === "claude-code"
|
|
181
|
-
? "
|
|
190
|
+
? "claude-code"
|
|
182
191
|
: ctx.adapter === "cursor"
|
|
183
192
|
? "cursor"
|
|
184
193
|
: "codex";
|
|
194
|
+
// Recorded fork lineage is NOT detected here. On claude-code a fork
|
|
195
|
+
// never fires its own session.start — SessionStart fires under the
|
|
196
|
+
// PARENT's session id (source=resume) before the fork id is minted
|
|
197
|
+
// (verified 2026-08-05) — so detection at this point can only mislabel
|
|
198
|
+
// the resumed parent. The fork's new instance is caught by the
|
|
199
|
+
// tool.pre_use heal path instead. The forkedFrom plumbing below stays
|
|
200
|
+
// for adapters that DO report a parent at session start.
|
|
201
|
+
const forkedFrom = undefined;
|
|
185
202
|
// Assign (or recover) name + kind via agent-coord. Idempotent: resume
|
|
186
203
|
// returns the original name; new owner consumes a counter slot.
|
|
187
|
-
const assigned = assignNameViaAgentCoord(ctx.coordRoot, ctx.instanceId, "session");
|
|
204
|
+
const assigned = assignNameViaAgentCoord(ctx.coordRoot, ctx.instanceId, "session", forkedFrom);
|
|
188
205
|
// Write the adapter pid-map row so `harn agents whoami` ppid-walks find
|
|
189
206
|
// this owner. Prefer the payload pid (the actual claude binary), then the
|
|
190
207
|
// anchor walk (the `node` ancestor for Cursor, which has no payload pid),
|
|
@@ -195,6 +212,7 @@ function buildEventData(eventType, ctx) {
|
|
|
195
212
|
if (adapterPid) {
|
|
196
213
|
writePidmapViaAgentCoord(ctx.coordRoot, adapterPid, ctx.instanceId, adapterPlatform);
|
|
197
214
|
}
|
|
215
|
+
const bundle = safeInstructionBundle(ctx.coordRoot, p?.cwd ?? process.cwd(), ctx.adapter);
|
|
198
216
|
return {
|
|
199
217
|
started_at: new Date().toISOString(),
|
|
200
218
|
cwd: p?.cwd ?? process.cwd(),
|
|
@@ -208,6 +226,18 @@ function buildEventData(eventType, ctx) {
|
|
|
208
226
|
name: assigned?.name,
|
|
209
227
|
kind: "session",
|
|
210
228
|
agent_id: ctx.instanceId,
|
|
229
|
+
...(bundle
|
|
230
|
+
? {
|
|
231
|
+
instruction_bundle_id: bundle.instruction_bundle_id,
|
|
232
|
+
instruction_source_id: bundle.canonical_source_id,
|
|
233
|
+
instruction_profile_root: bundle.profile_root,
|
|
234
|
+
instruction_component_count: bundle.components.length,
|
|
235
|
+
}
|
|
236
|
+
: {}),
|
|
237
|
+
// Recorded fork lineage rides the canonical event too, so full replay
|
|
238
|
+
// and derived readers converge on the same parent linkage that
|
|
239
|
+
// .name-history carries (the ADR 0017 dual-write pattern).
|
|
240
|
+
...(forkedFrom ? { forked_from: forkedFrom } : {}),
|
|
211
241
|
// Workflow-child linkage: `workflow run` children carry the run id in
|
|
212
242
|
// env (spawn adapters set it via buildChildEnv), so their sessions and
|
|
213
243
|
// heartbeats join back to the run transcript + /workflows web view.
|
|
@@ -235,6 +265,10 @@ function buildEventData(eventType, ctx) {
|
|
|
235
265
|
return { prompt_text: value, ...(truncated ? { truncated: true } : {}) };
|
|
236
266
|
}
|
|
237
267
|
case "turn.stop": {
|
|
268
|
+
const lastAssistantMessage = p?.raw.last_assistant_message ?? "";
|
|
269
|
+
const fileLinkTelemetry = ctx.adapter === "codex"
|
|
270
|
+
? codexWslFileLinkTelemetry(ctx.coordRoot, p?.cwd, lastAssistantMessage)
|
|
271
|
+
: null;
|
|
238
272
|
return {
|
|
239
273
|
// Backfill the model for adapters that omit it at session.start
|
|
240
274
|
// (Claude Code). The transcript is populated with assistant turns by
|
|
@@ -252,8 +286,24 @@ function buildEventData(eventType, ctx) {
|
|
|
252
286
|
// text-only stop (box in last_assistant_message, no transcript), which
|
|
253
287
|
// the verdict now sees because agent-hook emits this turn.stop itself
|
|
254
288
|
// (the previous path passed those via the no-history fail-open).
|
|
255
|
-
status_box_present: scanStatusBoxPresent(p?.transcript_path) ||
|
|
256
|
-
|
|
289
|
+
status_box_present: scanStatusBoxPresent(p?.transcript_path) || lastAssistantMessage.includes("┌─ agent-"),
|
|
290
|
+
// Shadow measurement for the rule-2/3 detector: the loose scan above
|
|
291
|
+
// matches the box prefix ANYWHERE in the transcript tail, including
|
|
292
|
+
// the tool_result row the status command itself writes, so it can
|
|
293
|
+
// report a paste that never happened. The strict variant counts
|
|
294
|
+
// assistant text only (same window, same scanner as the session-name
|
|
295
|
+
// detection). Telemetry-only: the Stop verdict still reads
|
|
296
|
+
// status_box_present. Once the divergence rate between the two fields
|
|
297
|
+
// is known, the gate either flips to strict or the rule gets
|
|
298
|
+
// reconsidered; either way this field decides it with data.
|
|
299
|
+
status_box_present_strict: scanAssistantTextIncludes(p?.transcript_path, "┌─ agent-") ||
|
|
300
|
+
lastAssistantMessage.includes("┌─ agent-"),
|
|
301
|
+
// Whether the session's suggested name is satisfied as of this turn,
|
|
302
|
+
// and which name that refers to. Reported on every stop while a name
|
|
303
|
+
// exists (the stop-hook.session_name verdict reads it per turn); the
|
|
304
|
+
// transcript scan itself stops once the name has been sighted.
|
|
305
|
+
...sessionNamePresence(ctx.coordRoot, ctx.instanceId, lastAssistantMessage, (name) => scanAssistantTextIncludes(p?.transcript_path, name)),
|
|
306
|
+
...(fileLinkTelemetry ?? {}),
|
|
257
307
|
stop_hook_active: p?.stop_hook_active,
|
|
258
308
|
};
|
|
259
309
|
}
|
|
@@ -301,6 +351,15 @@ function buildEventData(eventType, ctx) {
|
|
|
301
351
|
...(clamped.truncated ? { truncated: true } : {}),
|
|
302
352
|
};
|
|
303
353
|
}
|
|
354
|
+
case "interaction.input_requested": {
|
|
355
|
+
const description = extractToolDescription(p?.tool_input);
|
|
356
|
+
const reason = description ? clampString(description, 500).value : undefined;
|
|
357
|
+
return {
|
|
358
|
+
request_kind: "permission",
|
|
359
|
+
tool_name: p?.tool_name,
|
|
360
|
+
...(reason ? { reason } : {}),
|
|
361
|
+
};
|
|
362
|
+
}
|
|
304
363
|
case "tool.post_use": {
|
|
305
364
|
const toolName = p?.tool_name ?? "unknown";
|
|
306
365
|
const summary = summarizeOutput(p?.tool_response);
|
|
@@ -347,6 +406,15 @@ function buildEventData(eventType, ctx) {
|
|
|
347
406
|
}
|
|
348
407
|
}
|
|
349
408
|
}
|
|
409
|
+
function safeInstructionBundle(coordRoot, cwd, adapter) {
|
|
410
|
+
try {
|
|
411
|
+
return buildInstructionBundle({ coordRoot, cwd, adapter });
|
|
412
|
+
}
|
|
413
|
+
catch (err) {
|
|
414
|
+
logError(coordRoot, err, { phase: "instruction-bundle-identity" });
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
350
418
|
function objectRecord(value) {
|
|
351
419
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
352
420
|
? value
|
|
@@ -449,6 +517,19 @@ async function main() {
|
|
|
449
517
|
owner_source: owner.source,
|
|
450
518
|
event_id: envelope.event_id,
|
|
451
519
|
});
|
|
520
|
+
// Activity is only useful while it is current. Stamp prompt and permission
|
|
521
|
+
// evidence directly from this envelope rather than draining the global
|
|
522
|
+
// projector: a drain here could apply a tool event before its authorization
|
|
523
|
+
// verdict. Tool progress is stamped later, after the guard allows it.
|
|
524
|
+
if (norm.event_type === "user_prompt.submit" ||
|
|
525
|
+
norm.event_type === "interaction.input_requested") {
|
|
526
|
+
try {
|
|
527
|
+
stampSessionStateEvent(coordRoot, owner.instance_id, envelope);
|
|
528
|
+
}
|
|
529
|
+
catch (err) {
|
|
530
|
+
logError(coordRoot, err, { phase: "activity-projection" });
|
|
531
|
+
}
|
|
532
|
+
}
|
|
452
533
|
// Context telemetry is opportunistic and truthful: only persist a sample
|
|
453
534
|
// when the adapter payload actually exposes usage/window data. Identical
|
|
454
535
|
// measurements are de-duplicated before they reach the canonical stream.
|
|
@@ -694,7 +775,7 @@ async function main() {
|
|
|
694
775
|
}
|
|
695
776
|
}
|
|
696
777
|
try {
|
|
697
|
-
const injected = await emitUserPromptSubmitSystemMessage(coordRoot, owner.instance_id, sessionId, adapter, recovery?.briefing ?? "");
|
|
778
|
+
const injected = await emitUserPromptSubmitSystemMessage(coordRoot, owner.instance_id, sessionId, adapter, payload?.cwd, recovery?.briefing ?? "");
|
|
698
779
|
if (injected && recovery) {
|
|
699
780
|
completeRecoveryInjection(coordRoot, owner.instance_id, sessionId, adapter, recovery, "UserPromptSubmit");
|
|
700
781
|
}
|
|
@@ -772,13 +853,30 @@ async function main() {
|
|
|
772
853
|
bypass: coordEnv("AGENT_COORD_BYPASS_STOP") === "1",
|
|
773
854
|
workflow_child: coordEnv("WORKFLOW_CHILD") === "1",
|
|
774
855
|
});
|
|
856
|
+
const enforcementMode = stopEnforcementMode(verdict.rule);
|
|
857
|
+
emit(coordRoot, {
|
|
858
|
+
event_type: "stop.verdict",
|
|
859
|
+
instance_id: owner.instance_id,
|
|
860
|
+
session_id: sessionId,
|
|
861
|
+
turn_id: payload?.turn_id,
|
|
862
|
+
parent_turn_id: payload?.parent_turn_id,
|
|
863
|
+
adapter,
|
|
864
|
+
data: {
|
|
865
|
+
allow: verdict.allow,
|
|
866
|
+
rule: verdict.rule,
|
|
867
|
+
...(verdict.reason ? { reason: verdict.reason } : {}),
|
|
868
|
+
enforcement_mode: enforcementMode,
|
|
869
|
+
eligible: enforcementMode === "enforced",
|
|
870
|
+
nag_delivered: enforcementMode === "enforced" && !verdict.allow,
|
|
871
|
+
},
|
|
872
|
+
});
|
|
775
873
|
if (!verdict.allow) {
|
|
776
874
|
// Adapter-aware enforcement channel: Claude Code / Codex honor exit-2 +
|
|
777
875
|
// stderr as a turn block; Cursor ignores exit codes (fail-open) and
|
|
778
876
|
// re-prompts only via a `followup_message` it auto-submits. emitStopBlock
|
|
779
877
|
// writes the right shape and returns the exit code to use.
|
|
780
878
|
const { emitStopBlock } = await import("./adapter/output.js");
|
|
781
|
-
return emitStopBlock(adapter, verdict);
|
|
879
|
+
return emitStopBlock(adapter, verdict, coordRoot);
|
|
782
880
|
}
|
|
783
881
|
}
|
|
784
882
|
// Phase 7: PreToolUse: heartbeat + pid-map self-heal on every tool call.
|
|
@@ -792,7 +890,19 @@ async function main() {
|
|
|
792
890
|
// call was restored here afterward.
|
|
793
891
|
if (norm.event_type === "tool.pre_use") {
|
|
794
892
|
try {
|
|
795
|
-
|
|
893
|
+
// Recorded fork lineage, heal-path flavor. A forked CC conversation
|
|
894
|
+
// never fires its own session.start (SessionStart fires under the
|
|
895
|
+
// PARENT's session id with source=resume, before the fork id is
|
|
896
|
+
// minted; verified 2026-08-05), so the fork's new instance first
|
|
897
|
+
// materializes right here. Gate detection on "no heartbeat yet" so the
|
|
898
|
+
// transcript scan runs once per instance lifetime, not per tool call.
|
|
899
|
+
let forkedFrom;
|
|
900
|
+
if (adapter === "claude-code" &&
|
|
901
|
+
payload?.transcript_path &&
|
|
902
|
+
!existsSync(join(coordRoot, ".harnery", "active", `${owner.instance_id}.json`))) {
|
|
903
|
+
forkedFrom = detectForkParent(payload.transcript_path, owner.instance_id);
|
|
904
|
+
}
|
|
905
|
+
healHeartbeatViaCli(coordRoot, owner.instance_id, sessionId, adapter, forkedFrom);
|
|
796
906
|
refreshPidmap(coordRoot, owner.instance_id, adapter, payload?.pid);
|
|
797
907
|
}
|
|
798
908
|
catch (err) {
|
|
@@ -813,6 +923,28 @@ async function main() {
|
|
|
813
923
|
catch (err) {
|
|
814
924
|
logError(coordRoot, err, { phase: "pre-tool-use-image-capture" });
|
|
815
925
|
}
|
|
926
|
+
// Windows-native Codex + WSL UNC only: block the one cross-shell shape
|
|
927
|
+
// proven to corrupt argument boundaries. Normal WSL argv calls, literal
|
|
928
|
+
// bash -s scripts, native Linux/macOS sessions, and every other adapter
|
|
929
|
+
// pass through. The host instructions name its concrete safe bridge.
|
|
930
|
+
const unsafeShellReason = unsafeCrossShellReason({
|
|
931
|
+
adapter,
|
|
932
|
+
cwd: payload?.cwd,
|
|
933
|
+
toolName: payload?.tool_name,
|
|
934
|
+
toolInput: payload?.tool_input,
|
|
935
|
+
});
|
|
936
|
+
if (unsafeShellReason) {
|
|
937
|
+
emit(coordRoot, {
|
|
938
|
+
event_type: "decision.block",
|
|
939
|
+
instance_id: owner.instance_id,
|
|
940
|
+
session_id: sessionId,
|
|
941
|
+
adapter,
|
|
942
|
+
data: { rule: "unsafe_cross_shell", reason: unsafeShellReason },
|
|
943
|
+
});
|
|
944
|
+
const { emitDeny } = await import("./adapter/output.js");
|
|
945
|
+
emitDeny(adapter, unsafeShellReason);
|
|
946
|
+
return 0;
|
|
947
|
+
}
|
|
816
948
|
// G-guard for ALL adapters. Claude Code previously ran this via a
|
|
817
949
|
// pre-tool-use bash adapter (which called `agent-coord verdict --rule=claim`);
|
|
818
950
|
// that adapter is now deleted, so agent-hook owns the deny for every adapter.
|
|
@@ -821,12 +953,21 @@ async function main() {
|
|
|
821
953
|
// apply_patch (codex) parses paths from the patch body and runs verdict
|
|
822
954
|
// per-path; Edit/Write/NotebookEdit resolve a single target. Non-write tools
|
|
823
955
|
// (incl. Agent) yield no targets and pass through with no deny.
|
|
956
|
+
let guardAllowed = true;
|
|
824
957
|
try {
|
|
825
|
-
await runPreToolUseGuard(coordRoot, owner.instance_id, sessionId, data, adapter);
|
|
958
|
+
guardAllowed = await runPreToolUseGuard(coordRoot, owner.instance_id, sessionId, data, adapter);
|
|
826
959
|
}
|
|
827
960
|
catch (err) {
|
|
828
961
|
logError(coordRoot, err, { phase: "pre-tool-use-guard" });
|
|
829
962
|
}
|
|
963
|
+
if (guardAllowed) {
|
|
964
|
+
try {
|
|
965
|
+
stampSessionStateEvent(coordRoot, owner.instance_id, envelope);
|
|
966
|
+
}
|
|
967
|
+
catch (err) {
|
|
968
|
+
logError(coordRoot, err, { phase: "activity-projection" });
|
|
969
|
+
}
|
|
970
|
+
}
|
|
830
971
|
// Shell-mutation warn (warn-only, never blocks). Was the cursor
|
|
831
972
|
// beforeShellExecution + codex preToolUse-Bash shell-mutation-claim-log in
|
|
832
973
|
// the per-adapter shell adapters. Cursor sends the command at payload.command;
|
|
@@ -901,16 +1042,52 @@ async function main() {
|
|
|
901
1042
|
}
|
|
902
1043
|
return 0;
|
|
903
1044
|
}
|
|
1045
|
+
function stopEnforcementMode(rule) {
|
|
1046
|
+
if (rule === "stop-hook.codex_observe_only")
|
|
1047
|
+
return "observe_only";
|
|
1048
|
+
if (rule === "stop-hook.fail_open" || rule === "stop-hook.no_history")
|
|
1049
|
+
return "fail_open";
|
|
1050
|
+
if (rule === "stop-hook.bypass" || rule === "stop-hook.workflow_child")
|
|
1051
|
+
return "exempt";
|
|
1052
|
+
return "enforced";
|
|
1053
|
+
}
|
|
904
1054
|
async function runPreToolUseGuard(coordRoot, instanceId, sessionId, data, adapter) {
|
|
905
1055
|
const toolName = data.tool_name ?? "";
|
|
906
|
-
const
|
|
907
|
-
|
|
908
|
-
|
|
1056
|
+
const rawTargets = collectGuardTargets(toolName, data);
|
|
1057
|
+
if (rawTargets.length === 0)
|
|
1058
|
+
return true;
|
|
1059
|
+
let targets;
|
|
1060
|
+
if (agentsRequireGitFinalization(coordRoot)) {
|
|
1061
|
+
const decisions = rawTargets.map((path) => classifyWriteClaimFinalization(coordRoot, path));
|
|
1062
|
+
const denied = decisions.find((decision) => !decision.allow);
|
|
1063
|
+
if (denied && !denied.allow) {
|
|
1064
|
+
const { emitDeny } = await import("./adapter/output.js");
|
|
1065
|
+
emitDeny(adapter, formatWriteClaimFinalizationDenial(denied, resolveBinName(coordRoot)));
|
|
1066
|
+
return false;
|
|
1067
|
+
}
|
|
1068
|
+
const allowed = decisions.filter((decision) => decision.allow);
|
|
1069
|
+
targets = allowed.map((decision) => ({
|
|
1070
|
+
path: decision.path,
|
|
1071
|
+
finalization: decision.descriptor,
|
|
1072
|
+
}));
|
|
1073
|
+
}
|
|
1074
|
+
else {
|
|
1075
|
+
targets = rawTargets
|
|
1076
|
+
.map((path) => canonicalize(coordRoot, path))
|
|
1077
|
+
.filter((path) => path !== null)
|
|
1078
|
+
.map((path) => ({ path }));
|
|
1079
|
+
}
|
|
909
1080
|
if (targets.length === 0)
|
|
910
|
-
return;
|
|
1081
|
+
return true;
|
|
911
1082
|
const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
912
|
-
if (!existsSync(agentCoordBin))
|
|
913
|
-
|
|
1083
|
+
if (!existsSync(agentCoordBin)) {
|
|
1084
|
+
if (agentsRequireGitFinalization(coordRoot)) {
|
|
1085
|
+
const { emitDeny } = await import("./adapter/output.js");
|
|
1086
|
+
emitDeny(adapter, "Harnery denied the write before mutation because the claim coordinator is unavailable; repair the project's Harnery installation and retry.");
|
|
1087
|
+
return false;
|
|
1088
|
+
}
|
|
1089
|
+
return true;
|
|
1090
|
+
}
|
|
914
1091
|
// For apply_patch (multi-file), collect siblings so the deny reason names
|
|
915
1092
|
// them. For single-file tools the array has one entry.
|
|
916
1093
|
for (const target of targets) {
|
|
@@ -918,7 +1095,7 @@ async function runPreToolUseGuard(coordRoot, instanceId, sessionId, data, adapte
|
|
|
918
1095
|
rule: "claim",
|
|
919
1096
|
instance_id: instanceId,
|
|
920
1097
|
session_id: sessionId,
|
|
921
|
-
path: target,
|
|
1098
|
+
path: target.path,
|
|
922
1099
|
});
|
|
923
1100
|
const result = spawnSync(agentCoordBin, ["verdict"], {
|
|
924
1101
|
input: verdictReq,
|
|
@@ -936,10 +1113,11 @@ async function runPreToolUseGuard(coordRoot, instanceId, sessionId, data, adapte
|
|
|
936
1113
|
continue;
|
|
937
1114
|
}
|
|
938
1115
|
if (parsed.allow === false) {
|
|
939
|
-
let reason = parsed.reason ?? `Path ${target} is currently being edited by another agent.`;
|
|
1116
|
+
let reason = parsed.reason ?? `Path ${target.path} is currently being edited by another agent.`;
|
|
940
1117
|
if (targets.length > 1) {
|
|
941
1118
|
const siblings = targets
|
|
942
|
-
.filter((
|
|
1119
|
+
.filter((candidate) => candidate !== target)
|
|
1120
|
+
.map((candidate) => candidate.path)
|
|
943
1121
|
.slice(0, 3)
|
|
944
1122
|
.join(", ");
|
|
945
1123
|
if (siblings) {
|
|
@@ -948,9 +1126,27 @@ async function runPreToolUseGuard(coordRoot, instanceId, sessionId, data, adapte
|
|
|
948
1126
|
}
|
|
949
1127
|
const { emitDeny } = await import("./adapter/output.js");
|
|
950
1128
|
emitDeny(adapter, reason);
|
|
951
|
-
return;
|
|
952
|
-
}
|
|
1129
|
+
return false;
|
|
1130
|
+
}
|
|
1131
|
+
// The verdict mutates the live heartbeat immediately, but that file is a
|
|
1132
|
+
// projection and can be rebuilt at any later hook. Persist the acquisition
|
|
1133
|
+
// in the canonical stream too. This matters most for Codex apply_patch:
|
|
1134
|
+
// one tool call can claim several files, and tool.pre_use's clamped payload
|
|
1135
|
+
// is not a durable ownership representation for those targets.
|
|
1136
|
+
emit(coordRoot, {
|
|
1137
|
+
event_type: "claim.acquire",
|
|
1138
|
+
instance_id: instanceId,
|
|
1139
|
+
session_id: sessionId,
|
|
1140
|
+
adapter,
|
|
1141
|
+
source: "agent-hooks",
|
|
1142
|
+
data: {
|
|
1143
|
+
path: target.path,
|
|
1144
|
+
mode: "write",
|
|
1145
|
+
...(target.finalization ? { finalization: target.finalization } : {}),
|
|
1146
|
+
},
|
|
1147
|
+
});
|
|
953
1148
|
}
|
|
1149
|
+
return true;
|
|
954
1150
|
}
|
|
955
1151
|
/** Canonicalize a path to monorepo-relative form. Absolute paths under
|
|
956
1152
|
* coordRoot get the prefix stripped; relative paths pass through (assumed
|
|
@@ -1008,15 +1204,19 @@ function parseApplyPatchPaths(data) {
|
|
|
1008
1204
|
}
|
|
1009
1205
|
return out;
|
|
1010
1206
|
}
|
|
1011
|
-
function healHeartbeatViaCli(coordRoot, instanceId, sessionId, adapter) {
|
|
1207
|
+
function healHeartbeatViaCli(coordRoot, instanceId, sessionId, adapter, forkedFrom) {
|
|
1012
1208
|
const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
1013
1209
|
if (!existsSync(agentCoordBin))
|
|
1014
1210
|
return;
|
|
1015
1211
|
// Pass the detected adapter so a pruned Cursor/Codex heartbeat is recreated
|
|
1016
1212
|
// with the correct platform; without it, healHeartbeat defaults to
|
|
1017
|
-
//
|
|
1018
|
-
// heartbeat-writer.healHeartbeat.
|
|
1019
|
-
|
|
1213
|
+
// claude-code and the dashboard mislabels the agent. See
|
|
1214
|
+
// heartbeat-writer.healHeartbeat. --forked-from carries detected fork
|
|
1215
|
+
// lineage for an instance that never session.started (the CC fork flow).
|
|
1216
|
+
const args = ["heal-heartbeat", instanceId, sessionId, `--adapter=${adapter}`];
|
|
1217
|
+
if (forkedFrom)
|
|
1218
|
+
args.push(`--forked-from=${forkedFrom}`);
|
|
1219
|
+
spawnSync(agentCoordBin, args, {
|
|
1020
1220
|
encoding: "utf8",
|
|
1021
1221
|
timeout: 2000,
|
|
1022
1222
|
env: childEnv(coordRoot),
|
|
@@ -1238,7 +1438,7 @@ function cleanupSessionEnd(coordRoot, instanceId, reason) {
|
|
|
1238
1438
|
spawnSync(agentCoordBin, ["log", `SESSION_END reason=${reason}`, "--instance", instanceId], { encoding: "utf8", timeout: 2000 });
|
|
1239
1439
|
}
|
|
1240
1440
|
}
|
|
1241
|
-
async function emitUserPromptSubmitSystemMessage(coordRoot, instanceId, sessionId, adapter, recoveryBriefing = "") {
|
|
1441
|
+
async function emitUserPromptSubmitSystemMessage(coordRoot, instanceId, sessionId, adapter, workspaceCwd, recoveryBriefing = "") {
|
|
1242
1442
|
const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
|
|
1243
1443
|
let additionalContext = "";
|
|
1244
1444
|
if (existsSync(agentCoordBin)) {
|
|
@@ -1263,15 +1463,26 @@ async function emitUserPromptSubmitSystemMessage(coordRoot, instanceId, sessionI
|
|
|
1263
1463
|
// staleness nudges; Claude Code enforces those via its Stop transcript.
|
|
1264
1464
|
// Codex gets a fresh status-footer reminder on every prompt. Its Stop hook
|
|
1265
1465
|
// stays observe-only, so missing the footer never replaces the answer.
|
|
1466
|
+
// Claude Code + Cursor (the Stop-ENFORCING adapters) get a fresh per-turn
|
|
1467
|
+
// ritual reminder: satisfying the contract up front is far cheaper than
|
|
1468
|
+
// the bounce-and-retry the Stop hook otherwise forces.
|
|
1266
1469
|
args.push("--session-name-nudge");
|
|
1267
1470
|
if (adapter === "cursor" || adapter === "codex")
|
|
1268
1471
|
args.push("--task-nudge");
|
|
1269
1472
|
if (adapter === "codex")
|
|
1270
1473
|
args.push("--status-footer-nudge");
|
|
1474
|
+
if (adapter === "claude-code" || adapter === "cursor")
|
|
1475
|
+
args.push("--turn-ritual-nudge", adapter);
|
|
1271
1476
|
const result = spawnSync(agentCoordBin, args, { encoding: "utf8", timeout: 3000 });
|
|
1272
1477
|
if (result.status === 0 && result.stdout)
|
|
1273
1478
|
additionalContext = result.stdout.trim();
|
|
1274
1479
|
}
|
|
1480
|
+
if (adapter === "codex") {
|
|
1481
|
+
const fileLinkContext = renderCodexWslFileLinkContext(coordRoot, workspaceCwd);
|
|
1482
|
+
if (fileLinkContext) {
|
|
1483
|
+
additionalContext = [additionalContext, fileLinkContext].filter(Boolean).join("\n\n");
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1275
1486
|
if (recoveryBriefing) {
|
|
1276
1487
|
additionalContext = [additionalContext, recoveryBriefing].filter(Boolean).join("\n\n");
|
|
1277
1488
|
}
|
|
@@ -1310,7 +1521,7 @@ function emitSubagentStartContext(coordRoot, instanceId, sessionId, data, adapte
|
|
|
1310
1521
|
}
|
|
1311
1522
|
function adapterPlatform(adapter) {
|
|
1312
1523
|
if (adapter === "claude-code")
|
|
1313
|
-
return "
|
|
1524
|
+
return "claude-code";
|
|
1314
1525
|
return adapter;
|
|
1315
1526
|
}
|
|
1316
1527
|
async function emitSessionStartSystemMessage(coordRoot, instanceId, sessionId, emittedData, adapter, recoveryBriefing = "") {
|
|
@@ -1359,6 +1570,21 @@ async function emitSessionStartSystemMessage(coordRoot, instanceId, sessionId, e
|
|
|
1359
1570
|
if (recoveryBriefing) {
|
|
1360
1571
|
additionalContext = [additionalContext, recoveryBriefing].filter(Boolean).join("\n\n");
|
|
1361
1572
|
}
|
|
1573
|
+
if (adapter === "codex" && isWslUncPath(emittedData.cwd)) {
|
|
1574
|
+
const fileLinkContext = renderCodexWslFileLinkContext(coordRoot, emittedData.cwd);
|
|
1575
|
+
if (fileLinkContext) {
|
|
1576
|
+
additionalContext = [additionalContext, fileLinkContext].filter(Boolean).join("\n\n");
|
|
1577
|
+
}
|
|
1578
|
+
const bridge = inspectCodexWslBridge(process.env, { expected: true });
|
|
1579
|
+
if (bridge && !bridge.ok) {
|
|
1580
|
+
additionalContext = [
|
|
1581
|
+
additionalContext,
|
|
1582
|
+
`Harnery hybrid warning: ${bridge.detail}. Run \`${resolveBinName(coordRoot)} doctor\` for the repair hint.`,
|
|
1583
|
+
]
|
|
1584
|
+
.filter(Boolean)
|
|
1585
|
+
.join("\n\n");
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1362
1588
|
if (!additionalContext)
|
|
1363
1589
|
return false;
|
|
1364
1590
|
const { emitContext } = await import("./adapter/output.js");
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type CodexHookTrustStatus = "managed" | "trusted" | "untrusted" | "modified";
|
|
2
|
+
export type CodexHookAuthorizationStatus = "runnable" | "review_required" | "disabled" | "not_wired" | "error";
|
|
3
|
+
export interface CodexHookAuthorizationResult {
|
|
4
|
+
status: CodexHookAuthorizationStatus;
|
|
5
|
+
detail: string;
|
|
6
|
+
hookCount: number;
|
|
7
|
+
disabledCount: number;
|
|
8
|
+
trustCounts: Partial<Record<CodexHookTrustStatus, number>>;
|
|
9
|
+
}
|
|
10
|
+
export interface CodexHookAuthorizationProbeOptions {
|
|
11
|
+
cwd: string;
|
|
12
|
+
codexBin?: string;
|
|
13
|
+
appServerArgs?: string[];
|
|
14
|
+
env?: NodeJS.ProcessEnv;
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Convert Codex app-server's `hooks/list` response into the one authorization
|
|
19
|
+
* state doctor needs. This is intentionally read-only: hashes are observed but
|
|
20
|
+
* never persisted, and only commands containing Harnery's `agent-hook` binary
|
|
21
|
+
* are included in the result.
|
|
22
|
+
*/
|
|
23
|
+
export declare function assessCodexHookList(response: unknown, cwd: string): CodexHookAuthorizationResult;
|
|
24
|
+
/**
|
|
25
|
+
* Ask the active Codex CLI for hook authorization through its app-server
|
|
26
|
+
* protocol. The child is closed as soon as `hooks/list` responds. Harnery never
|
|
27
|
+
* calls the trust-write method and never reads Codex's private config format.
|
|
28
|
+
*/
|
|
29
|
+
export declare function probeCodexHookAuthorization(options: CodexHookAuthorizationProbeOptions): Promise<CodexHookAuthorizationResult>;
|
|
30
|
+
//# sourceMappingURL=codex-authorization.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-authorization.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/codex-authorization.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;AAEpF,MAAM,MAAM,4BAA4B,GACpC,UAAU,GACV,iBAAiB,GACjB,UAAU,GACV,WAAW,GACX,OAAO,CAAC;AAEZ,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,4BAA4B,CAAC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,kCAAkC;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA0BD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,4BAA4B,CAqGhG;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,kCAAkC,GAC1C,OAAO,CAAC,4BAA4B,CAAC,CAoFvC"}
|