harnery 0.42.0 → 0.44.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 +3 -3
- package/dist/commander.d.ts +16 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +18 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +95 -52
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +38 -11
- package/dist/commands/diagnostics.d.ts.map +1 -1
- package/dist/commands/diagnostics.js +10 -0
- package/dist/commands/doctor.d.ts +12 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +22 -7
- package/dist/commands/governor.js +22 -10
- package/dist/commands/inbox.d.ts +1 -1
- package/dist/commands/inbox.d.ts.map +1 -1
- package/dist/commands/inbox.js +55 -2
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +28 -4
- package/dist/commands/servers.d.ts.map +1 -1
- package/dist/commands/servers.js +16 -1
- package/dist/commands/supervisor.d.ts.map +1 -1
- package/dist/commands/supervisor.js +7 -2
- package/dist/commands/toc.d.ts +14 -0
- package/dist/commands/toc.d.ts.map +1 -1
- package/dist/commands/toc.js +7 -4
- package/dist/commands/tunnel.d.ts +65 -0
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +290 -101
- package/dist/commands/web-fetch.d.ts +3 -2
- package/dist/commands/web-fetch.d.ts.map +1 -1
- package/dist/commands/web-fetch.js +25 -19
- package/dist/commands/web.d.ts +7 -1
- package/dist/commands/web.d.ts.map +1 -1
- package/dist/commands/web.js +9 -3
- package/dist/commands/workflow.js +24 -30
- package/dist/core/agents/live-authority-v3.d.ts +1 -0
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +11 -0
- package/dist/core/agents/live-lifecycle-v3.d.ts +4 -2
- package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
- package/dist/core/agents/live-lifecycle-v3.js +28 -7
- package/dist/core/agents/mailbox.d.ts +9 -8
- package/dist/core/agents/mailbox.d.ts.map +1 -1
- package/dist/core/agents/mailbox.js +24 -17
- package/dist/core/agents/render/prompt-context.d.ts +2 -0
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +19 -17
- package/dist/core/agents/render/session-context.d.ts +2 -8
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +64 -43
- package/dist/core/agents/rules/stop-hook.js +3 -3
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +25 -14
- package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-writer.js +5 -0
- package/dist/core/agents/state/names.d.ts +43 -3
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +87 -3
- package/dist/core/agents/state/pidmap.d.ts.map +1 -1
- package/dist/core/agents/state/pidmap.js +5 -0
- package/dist/core/agents/state/proc-start.d.ts +19 -0
- package/dist/core/agents/state/proc-start.d.ts.map +1 -1
- package/dist/core/agents/state/proc-start.js +41 -0
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +180 -70
- package/dist/core/agents/state/unchanged-file.d.ts +44 -0
- package/dist/core/agents/state/unchanged-file.d.ts.map +1 -0
- package/dist/core/agents/state/unchanged-file.js +390 -0
- package/dist/core/context/index.d.ts +22 -10
- package/dist/core/context/index.d.ts.map +1 -1
- package/dist/core/context/index.js +381 -119
- package/dist/core/context/observation.d.ts +26 -0
- package/dist/core/context/observation.d.ts.map +1 -0
- package/dist/core/context/observation.js +11 -0
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +25 -14
- package/dist/core/diagnostics/path-identity.d.ts +3 -0
- package/dist/core/diagnostics/path-identity.d.ts.map +1 -0
- package/dist/core/diagnostics/path-identity.js +12 -0
- package/dist/core/diagnostics/sanitize.d.ts.map +1 -1
- package/dist/core/diagnostics/sanitize.js +4 -3
- package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
- package/dist/core/events/v3/authority-outbox.js +29 -0
- package/dist/core/events/v3/generation-custody.d.ts +5 -0
- package/dist/core/events/v3/generation-custody.d.ts.map +1 -0
- package/dist/core/events/v3/generation-custody.js +50 -0
- package/dist/core/events/v3/producers/coordination-recorder.d.ts +1 -0
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/coordination-recorder.js +7 -0
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +77 -64
- package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -1
- package/dist/core/events/v3/support-storage/pack-reader.js +42 -16
- package/dist/core/governor/planning.d.ts.map +1 -1
- package/dist/core/governor/planning.js +14 -1
- package/dist/core/governor/service-read.d.ts +10 -2
- package/dist/core/governor/service-read.d.ts.map +1 -1
- package/dist/core/governor/service-read.js +105 -22
- package/dist/core/governor/service.d.ts +20 -2
- package/dist/core/governor/service.d.ts.map +1 -1
- package/dist/core/governor/service.js +72 -47
- package/dist/core/hooks/cli.js +120 -23
- package/dist/core/hooks/fork-lineage.d.ts +40 -0
- package/dist/core/hooks/fork-lineage.d.ts.map +1 -0
- package/dist/core/hooks/fork-lineage.js +62 -0
- package/dist/core/hooks/resolve/transcript.d.ts +16 -0
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +84 -0
- package/dist/core/hooks/shell-rm.d.ts +2 -0
- package/dist/core/hooks/shell-rm.d.ts.map +1 -0
- package/dist/core/hooks/shell-rm.js +105 -0
- package/dist/core/hooks/shell-tokens.d.ts +12 -0
- package/dist/core/hooks/shell-tokens.d.ts.map +1 -0
- package/dist/core/hooks/shell-tokens.js +97 -0
- package/dist/core/hooks/shell-waiter.d.ts.map +1 -1
- package/dist/core/hooks/shell-waiter.js +3 -93
- package/dist/core/inbox/contract.d.ts +2 -1
- package/dist/core/inbox/contract.d.ts.map +1 -1
- package/dist/core/inbox/index.d.ts +2 -0
- package/dist/core/inbox/index.d.ts.map +1 -1
- package/dist/core/inbox/index.js +2 -0
- package/dist/core/inbox/peer-messages.d.ts +37 -0
- package/dist/core/inbox/peer-messages.d.ts.map +1 -0
- package/dist/core/inbox/peer-messages.js +72 -0
- package/dist/core/inbox/service.d.ts +8 -1
- package/dist/core/inbox/service.d.ts.map +1 -1
- package/dist/core/inbox/service.js +67 -21
- package/dist/core/inbox/targets.d.ts +22 -0
- package/dist/core/inbox/targets.d.ts.map +1 -0
- package/dist/core/inbox/targets.js +108 -0
- package/dist/core/inbox/watcher.js +7 -3
- package/dist/core/journal/index.d.ts.map +1 -1
- package/dist/core/journal/index.js +17 -2
- package/dist/core/resources/contract.d.ts +3 -0
- package/dist/core/resources/contract.d.ts.map +1 -1
- package/dist/core/resources/service-status.d.ts +14 -1
- package/dist/core/resources/service-status.d.ts.map +1 -1
- package/dist/core/resources/service-status.js +23 -13
- package/dist/core/resources/service.d.ts +9 -1
- package/dist/core/resources/service.d.ts.map +1 -1
- package/dist/core/resources/service.js +47 -27
- package/dist/core/resources/status.d.ts +2 -5
- package/dist/core/resources/status.d.ts.map +1 -1
- package/dist/core/resources/status.js +36 -7
- package/dist/core/semantic/contract.d.ts +5 -5
- package/dist/core/servers/index.d.ts +24 -13
- package/dist/core/servers/index.d.ts.map +1 -1
- package/dist/core/servers/index.js +132 -92
- package/dist/core/servers/tunnels.d.ts +3 -5
- package/dist/core/servers/tunnels.d.ts.map +1 -1
- package/dist/core/servers/tunnels.js +3 -9
- package/dist/core/storage/process-log.d.ts +12 -0
- package/dist/core/storage/process-log.d.ts.map +1 -1
- package/dist/core/storage/process-log.js +10 -2
- package/dist/core/supervisor/contract.d.ts +12 -0
- package/dist/core/supervisor/contract.d.ts.map +1 -1
- package/dist/core/supervisor/explanations.d.ts.map +1 -1
- package/dist/core/supervisor/explanations.js +2 -1
- package/dist/core/supervisor/findings.d.ts.map +1 -1
- package/dist/core/supervisor/findings.js +54 -9
- package/dist/core/supervisor/service.d.ts +9 -1
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +59 -29
- package/dist/core/supervisor/services.d.ts.map +1 -1
- package/dist/core/supervisor/services.js +22 -1
- package/dist/core/supervisor/status.d.ts +17 -2
- package/dist/core/supervisor/status.d.ts.map +1 -1
- package/dist/core/supervisor/status.js +70 -12
- package/dist/core/supervisor/timeline.d.ts.map +1 -1
- package/dist/core/supervisor/timeline.js +2 -1
- package/dist/core/work/state.js +7 -7
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +105 -32
- package/dist/core/workflow/failure-evidence.d.ts +16 -0
- package/dist/core/workflow/failure-evidence.d.ts.map +1 -0
- package/dist/core/workflow/failure-evidence.js +114 -0
- package/dist/core/workflow/index.d.ts +1 -1
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/index.js +1 -1
- package/dist/core/workflow/live-session-v3.d.ts +6 -0
- package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
- package/dist/core/workflow/live-session-v3.js +126 -5
- package/dist/core/workflow/proof.d.ts +5 -5
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +115 -20
- package/dist/core/workflow/run-state.d.ts +3 -1
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +31 -27
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +19 -0
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +13 -0
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +19 -0
- package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
- package/dist/core/workflow/spawn-opencode.js +14 -0
- package/dist/core/workflow/types.d.ts +64 -17
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/index.d.ts +0 -1
- package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/index.js +0 -1
- package/dist/core/workflow/workspaces/leases.d.ts +3 -1
- package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.js +30 -16
- package/dist/core/workflow/workspaces/local-git.d.ts +6 -2
- package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.js +270 -6
- package/dist/core/workflow/workspaces/ownership.d.ts +11 -0
- package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -0
- package/dist/core/workflow/workspaces/ownership.js +194 -0
- package/dist/core/workflow/workspaces/reclaim.d.ts +35 -0
- package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/reclaim.js +14 -0
- package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/recovery.js +21 -13
- package/dist/core/workflow/workspaces/state.d.ts +1 -0
- package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.js +21 -1
- package/dist/lib/browser/windows-chrome.d.ts +100 -12
- package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
- package/dist/lib/browser/windows-chrome.js +262 -73
- package/dist/lib/docs-links.d.ts.map +1 -1
- package/dist/lib/docs-links.js +45 -2
- package/dist/lib/guarded-remove.d.ts +7 -0
- package/dist/lib/guarded-remove.d.ts.map +1 -1
- package/dist/lib/guarded-remove.js +106 -12
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +4 -0
- package/dist/lib/tunnel/allowlist.d.ts +44 -0
- package/dist/lib/tunnel/allowlist.d.ts.map +1 -0
- package/dist/lib/tunnel/allowlist.js +204 -0
- package/dist/lib/tunnel/current-address.d.ts +41 -0
- package/dist/lib/tunnel/current-address.d.ts.map +1 -0
- package/dist/lib/tunnel/current-address.js +123 -0
- package/dist/lib/tunnel/gate.js +13 -6
- package/dist/lib/tunnel/processes.d.ts +39 -0
- package/dist/lib/tunnel/processes.d.ts.map +1 -0
- package/dist/lib/tunnel/processes.js +121 -0
- package/dist/lib/tunnel/state.d.ts +28 -3
- package/dist/lib/tunnel/state.d.ts.map +1 -1
- package/dist/lib/tunnel/state.js +98 -27
- package/dist/lib/zip/index.d.ts +41 -0
- package/dist/lib/zip/index.d.ts.map +1 -0
- package/dist/lib/zip/index.js +398 -0
- package/package.json +8 -1
- package/src/commander.ts +32 -0
- package/src/commands/agents.ts +151 -74
- package/src/commands/browse.ts +50 -14
- package/src/commands/diagnostics.ts +10 -0
- package/src/commands/doctor.ts +26 -7
- package/src/commands/governor.ts +23 -10
- package/src/commands/inbox.ts +52 -2
- package/src/commands/rm.ts +43 -4
- package/src/commands/servers.ts +16 -4
- package/src/commands/supervisor.ts +7 -2
- package/src/commands/toc.ts +14 -6
- package/src/commands/tunnel.ts +394 -116
- package/src/commands/web-fetch.ts +30 -19
- package/src/commands/web.ts +9 -3
- package/src/commands/workflow.ts +30 -30
- package/src/core/agents/live-authority-v3.ts +17 -0
- package/src/core/agents/live-lifecycle-v3.ts +52 -14
- package/src/core/agents/mailbox.ts +26 -17
- package/src/core/agents/render/prompt-context.ts +35 -20
- package/src/core/agents/render/session-context.ts +92 -48
- package/src/core/agents/rules/stop-hook.ts +3 -3
- package/src/core/agents/state/heartbeat-writer.ts +29 -12
- package/src/core/agents/state/live-coordination-writer.ts +29 -0
- package/src/core/agents/state/names.ts +117 -6
- package/src/core/agents/state/pidmap.ts +13 -0
- package/src/core/agents/state/proc-start.ts +57 -0
- package/src/core/agents/state/stale-sweep.ts +247 -82
- package/src/core/agents/state/unchanged-file.ts +456 -0
- package/src/core/context/index.ts +446 -136
- package/src/core/context/observation.ts +29 -0
- package/src/core/diagnostics/bundle.ts +23 -18
- package/src/core/diagnostics/path-identity.ts +15 -0
- package/src/core/diagnostics/sanitize.ts +4 -3
- package/src/core/events/v3/authority-outbox.ts +56 -0
- package/src/core/events/v3/generation-custody.ts +59 -0
- package/src/core/events/v3/producers/coordination-recorder.ts +15 -0
- package/src/core/events/v3/producers/recorder.ts +94 -68
- package/src/core/events/v3/support-storage/pack-reader.ts +45 -19
- package/src/core/governor/planning.ts +20 -8
- package/src/core/governor/service-read.ts +128 -20
- package/src/core/governor/service.ts +119 -42
- package/src/core/hooks/cli.ts +139 -22
- package/src/core/hooks/fork-lineage.ts +82 -0
- package/src/core/hooks/resolve/transcript.ts +88 -0
- package/src/core/hooks/shell-rm.ts +103 -0
- package/src/core/hooks/shell-tokens.ts +96 -0
- package/src/core/hooks/shell-waiter.ts +4 -91
- package/src/core/inbox/contract.ts +2 -1
- package/src/core/inbox/index.ts +2 -0
- package/src/core/inbox/peer-messages.ts +94 -0
- package/src/core/inbox/service.ts +88 -22
- package/src/core/inbox/targets.ts +148 -0
- package/src/core/inbox/watcher.ts +8 -9
- package/src/core/journal/index.ts +19 -2
- package/src/core/resources/contract.ts +4 -0
- package/src/core/resources/service-status.ts +35 -12
- package/src/core/resources/service.ts +71 -24
- package/src/core/resources/status.ts +57 -8
- package/src/core/servers/index.ts +177 -91
- package/src/core/servers/tunnels.ts +3 -11
- package/src/core/storage/process-log.ts +17 -2
- package/src/core/supervisor/contract.ts +13 -0
- package/src/core/supervisor/explanations.ts +4 -1
- package/src/core/supervisor/findings.ts +68 -10
- package/src/core/supervisor/service.ts +88 -26
- package/src/core/supervisor/services.ts +26 -8
- package/src/core/supervisor/status.ts +89 -12
- package/src/core/supervisor/timeline.ts +2 -1
- package/src/core/work/state.ts +7 -7
- package/src/core/workflow/engine.ts +131 -49
- package/src/core/workflow/failure-evidence.ts +141 -0
- package/src/core/workflow/index.ts +0 -1
- package/src/core/workflow/live-session-v3.ts +146 -5
- package/src/core/workflow/proof.ts +134 -26
- package/src/core/workflow/run-state.ts +31 -29
- package/src/core/workflow/spawn-claude.ts +18 -0
- package/src/core/workflow/spawn-codex.ts +13 -0
- package/src/core/workflow/spawn-cursor.ts +18 -0
- package/src/core/workflow/spawn-opencode.ts +14 -0
- package/src/core/workflow/types.ts +51 -17
- package/src/core/workflow/workspaces/index.ts +0 -1
- package/src/core/workflow/workspaces/leases.ts +48 -17
- package/src/core/workflow/workspaces/local-git.ts +315 -5
- package/src/core/workflow/workspaces/ownership.ts +232 -0
- package/src/core/workflow/workspaces/reclaim.ts +58 -0
- package/src/core/workflow/workspaces/recovery.ts +25 -13
- package/src/core/workflow/workspaces/state.ts +20 -0
- package/src/lib/browser/windows-chrome.ts +346 -86
- package/src/lib/docs-links.ts +49 -2
- package/src/lib/guarded-remove.ts +132 -14
- package/src/lib/identities/assume.ts +4 -0
- package/src/lib/tunnel/allowlist.ts +208 -0
- package/src/lib/tunnel/current-address.ts +158 -0
- package/src/lib/tunnel/gate.ts +16 -8
- package/src/lib/tunnel/processes.ts +149 -0
- package/src/lib/tunnel/state.ts +123 -27
- package/src/lib/zip/index.ts +483 -0
package/dist/core/hooks/cli.js
CHANGED
|
@@ -30,7 +30,7 @@ import { assistantTextStartsWithSessionNameBlock, isSessionNameRemediationComman
|
|
|
30
30
|
import { readHeartbeat, setAssignedNameCache, stampSessionNameReminded, stampSessionNameRequested, stampSessionNameSeen, } from "../agents/state/heartbeat-writer.js";
|
|
31
31
|
import { readLiveCoordinationRow } from "../agents/state/live-coordination-view.js";
|
|
32
32
|
import { ensureLiveCoordinationHeartbeat } from "../agents/state/live-coordination-writer.js";
|
|
33
|
-
import { assignName } from "../agents/state/names.js";
|
|
33
|
+
import { assignName, readForkParent, readForkState, recordForkDetection, resolveName, } from "../agents/state/names.js";
|
|
34
34
|
import { writePidmapRow } from "../agents/state/pidmap.js";
|
|
35
35
|
import { agentsRequireGitFinalization, coordFreshnessSeconds, hostPromptContextConfig, resolveBinName, } from "../config.js";
|
|
36
36
|
import { checkpointContext, completeContextRecovery, extractContextSample, markContextCompactionCompleted, prepareContextRecovery, readContextState, recordContextSample, } from "../context/index.js";
|
|
@@ -48,6 +48,7 @@ import { hookTimeoutFor } from "./adapter/wiring.js";
|
|
|
48
48
|
import { scheduleBackupSnapshot } from "./backup-schedule.js";
|
|
49
49
|
import { captureImages, detectPresence, imageJanitor, journalArchive, journalJanitor, journalRecoveryCue, playSound, recordImageArtifactsV3, resetSoundCounters, runSessionSyncExtension, soundForEvent, } from "./effects/index.js";
|
|
50
50
|
import { toolInputHash, toolTargetHash } from "./events/input-hash.js";
|
|
51
|
+
import { detectClaudeCodeFork } from "./fork-lineage.js";
|
|
51
52
|
import { canonicalize } from "./guard-path.js";
|
|
52
53
|
import { beginHookHealth, observeHookDebug, observeHookError, writeHookHealthCompletion, } from "./health.js";
|
|
53
54
|
import { adapterPidFromEnv, parsePsChainLine, selectAnchorPid } from "./resolve/anchor.js";
|
|
@@ -56,6 +57,7 @@ import { resolveOwner } from "./resolve/owner.js";
|
|
|
56
57
|
import { inspectSessionNameDisplayImmediately, scanAssistantStatusBoxPresent, scanStatusBoxPresent, scanTranscriptRuntime, } from "./resolve/transcript.js";
|
|
57
58
|
import { clearHookRunMarker, writeHookRunMarker } from "./run-markers.js";
|
|
58
59
|
import { sessionNamePresence } from "./session-name-presence.js";
|
|
60
|
+
import { shellRmReason } from "./shell-rm.js";
|
|
59
61
|
import { shellWaiterReason } from "./shell-waiter.js";
|
|
60
62
|
let hookHealthState;
|
|
61
63
|
let hookRunMarkerPath = null;
|
|
@@ -155,11 +157,13 @@ function logError(coordRoot, err, context) {
|
|
|
155
157
|
* Mint or recover the hurricane-style name for this owner in-process. Returns
|
|
156
158
|
* null on any failure so session.started emission never breaks adapter flow.
|
|
157
159
|
*/
|
|
158
|
-
function assignNameInProcess(coordRoot, instanceId, kind,
|
|
160
|
+
function assignNameInProcess(coordRoot, instanceId, kind, fork) {
|
|
159
161
|
try {
|
|
160
162
|
const name = assignName(coordRoot, instanceId, kind, {
|
|
161
163
|
freshnessSecs: coordFreshnessSeconds(coordRoot),
|
|
162
|
-
...(forkedFrom ? { forkedFrom } : {}),
|
|
164
|
+
...(fork?.forkedFrom ? { forkedFrom: fork.forkedFrom } : {}),
|
|
165
|
+
...(fork?.fork ? { fork: true } : {}),
|
|
166
|
+
...(fork?.forkChecked ? { forkChecked: true } : {}),
|
|
163
167
|
});
|
|
164
168
|
return { name, kind };
|
|
165
169
|
}
|
|
@@ -167,9 +171,60 @@ function assignNameInProcess(coordRoot, instanceId, kind, forkedFrom) {
|
|
|
167
171
|
return null;
|
|
168
172
|
}
|
|
169
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Name a main session, recording fork lineage when the instance is new. Every
|
|
176
|
+
* main-session naming path goes through here: whichever hook first sees a new
|
|
177
|
+
* instance writes its history row, and lineage can only ride that first row
|
|
178
|
+
* (later detection appends a `fork.detected` row instead).
|
|
179
|
+
*/
|
|
180
|
+
function assignSessionNameInProcess(coordRoot, instanceId, payload, adapter) {
|
|
181
|
+
let fork;
|
|
182
|
+
try {
|
|
183
|
+
if (adapter === "claude-code" &&
|
|
184
|
+
payload?.session_id === instanceId &&
|
|
185
|
+
!resolveName(coordRoot, instanceId)) {
|
|
186
|
+
const detected = detectClaudeCodeFork({
|
|
187
|
+
coordRoot,
|
|
188
|
+
transcriptPath: payload.transcript_path,
|
|
189
|
+
sessionId: instanceId,
|
|
190
|
+
source: payload.source,
|
|
191
|
+
});
|
|
192
|
+
fork = {
|
|
193
|
+
...(detected.forkedFrom ? { forkedFrom: detected.forkedFrom } : {}),
|
|
194
|
+
fork: detected.fork,
|
|
195
|
+
forkChecked: detected.checked,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
fork = undefined;
|
|
201
|
+
}
|
|
202
|
+
return assignNameInProcess(coordRoot, instanceId, "session", fork);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Finish fork detection that session start could not: a CLI fork starts before
|
|
206
|
+
* its transcript exists, and a mid-flight instance may never see SessionStart.
|
|
207
|
+
* Runs once per instance; the appended `fork_checked` row stops later calls.
|
|
208
|
+
*/
|
|
209
|
+
function healForkLineage(coordRoot, instanceId, payload) {
|
|
210
|
+
if (!payload?.transcript_path || payload.session_id !== instanceId)
|
|
211
|
+
return;
|
|
212
|
+
const state = readForkState(coordRoot, instanceId);
|
|
213
|
+
if (!state || state.checked)
|
|
214
|
+
return;
|
|
215
|
+
const detected = detectClaudeCodeFork({
|
|
216
|
+
coordRoot,
|
|
217
|
+
transcriptPath: payload.transcript_path,
|
|
218
|
+
sessionId: instanceId,
|
|
219
|
+
knownFork: state.fork,
|
|
220
|
+
});
|
|
221
|
+
if (!detected.checked)
|
|
222
|
+
return;
|
|
223
|
+
recordForkDetection(coordRoot, instanceId, detected);
|
|
224
|
+
}
|
|
170
225
|
/** Mid-flight sessions can emit tools before SessionStart assigns a pool name.
|
|
171
226
|
* Stamp the disposable cache so Codec does not render the 8-character id. */
|
|
172
|
-
function ensureSessionDisplayName(coordRoot, instanceId, sessionId, adapter) {
|
|
227
|
+
function ensureSessionDisplayName(coordRoot, instanceId, sessionId, adapter, payload) {
|
|
173
228
|
if (coordEnv("WORKFLOW_CHILD") === "1")
|
|
174
229
|
return;
|
|
175
230
|
const row = readLiveCoordinationRow(coordRoot, instanceId) ?? readHeartbeat(coordRoot, instanceId);
|
|
@@ -177,7 +232,7 @@ function ensureSessionDisplayName(coordRoot, instanceId, sessionId, adapter) {
|
|
|
177
232
|
return;
|
|
178
233
|
if (row?.name?.trim())
|
|
179
234
|
return;
|
|
180
|
-
const assigned =
|
|
235
|
+
const assigned = assignSessionNameInProcess(coordRoot, instanceId, payload, adapter);
|
|
181
236
|
if (!assigned?.name)
|
|
182
237
|
return;
|
|
183
238
|
if (!readHeartbeat(coordRoot, instanceId)) {
|
|
@@ -230,21 +285,21 @@ function buildEventData(eventType, ctx) {
|
|
|
230
285
|
// The platform label is the adapter id itself; enumerating and defaulting
|
|
231
286
|
// to codex silently mislabels any adapter not in the list.
|
|
232
287
|
const adapterPlatform = ctx.adapter;
|
|
233
|
-
// Recorded fork lineage is NOT detected here. On claude-code a fork
|
|
234
|
-
// never fires its own session.started — SessionStart fires under the
|
|
235
|
-
// PARENT's session id (source=resume) before the fork id is minted
|
|
236
|
-
// (verified 2026-08-05) — so detection at this point can only mislabel
|
|
237
|
-
// the resumed parent. The fork's new instance is caught by the
|
|
238
|
-
// tool.requested heal path instead. The forkedFrom plumbing below stays
|
|
239
|
-
// for adapters that DO report a parent at session start.
|
|
240
|
-
const forkedFrom = undefined;
|
|
241
288
|
// Assign (or recover) name + kind in-process. Idempotent: resume
|
|
242
|
-
// returns the original name; new owner consumes a counter slot
|
|
289
|
+
// returns the original name; new owner consumes a counter slot and,
|
|
290
|
+
// on claude-code, runs fork detection (fork-lineage.ts). Since Claude
|
|
291
|
+
// Code 2.1.283 both fork flows fire SessionStart under the FORK's own
|
|
292
|
+
// id (verified 2026-10-07), superseding the 2026-08-05 finding that a
|
|
293
|
+
// fork never started on its own id. A resumed parent keeps its existing
|
|
294
|
+
// history row, so it is never relabeled here.
|
|
243
295
|
// Workflow children stay unnamed, as on the tool path: a pool name
|
|
244
296
|
// would make them the addressee of mail held for that name.
|
|
245
297
|
const assigned = coordEnv("WORKFLOW_CHILD") === "1"
|
|
246
298
|
? undefined
|
|
247
|
-
:
|
|
299
|
+
: assignSessionNameInProcess(ctx.coordRoot, ctx.instanceId, p, ctx.adapter);
|
|
300
|
+
const forkedFrom = assigned
|
|
301
|
+
? readForkParent(ctx.coordRoot, ctx.instanceId)?.instance_id
|
|
302
|
+
: undefined;
|
|
248
303
|
// Write the adapter pid-map row so `harn agents whoami` ppid-walks find
|
|
249
304
|
// this owner. Prefer the payload pid (the actual claude binary), then the
|
|
250
305
|
// anchor walk (the `node` ancestor for Cursor, which has no payload pid),
|
|
@@ -309,7 +364,7 @@ function buildEventData(eventType, ctx) {
|
|
|
309
364
|
// 8-character native id.
|
|
310
365
|
if (coordEnv("WORKFLOW_CHILD") !== "1" &&
|
|
311
366
|
!readLiveCoordinationRow(ctx.coordRoot, ctx.instanceId)?.name) {
|
|
312
|
-
|
|
367
|
+
assignSessionNameInProcess(ctx.coordRoot, ctx.instanceId, p, ctx.adapter);
|
|
313
368
|
}
|
|
314
369
|
const prompt = p?.prompt ?? "";
|
|
315
370
|
const { value, truncated } = clampString(prompt, 4000);
|
|
@@ -645,7 +700,7 @@ async function main() {
|
|
|
645
700
|
priorCoordination?.kind !== "transient" &&
|
|
646
701
|
!priorCoordination?.workflow_run_id &&
|
|
647
702
|
!priorCoordination?.name?.trim()) {
|
|
648
|
-
|
|
703
|
+
assignSessionNameInProcess(coordRoot, owner.instance_id, payload, adapter);
|
|
649
704
|
}
|
|
650
705
|
const data = buildEventData(norm.event_type, {
|
|
651
706
|
coordRoot,
|
|
@@ -820,12 +875,20 @@ function recordSignal(run) {
|
|
|
820
875
|
}
|
|
821
876
|
if (norm.event_type !== "session.ended") {
|
|
822
877
|
try {
|
|
823
|
-
ensureSessionDisplayName(coordRoot, owner.instance_id, sessionId, adapter);
|
|
878
|
+
ensureSessionDisplayName(coordRoot, owner.instance_id, sessionId, adapter, payload);
|
|
824
879
|
}
|
|
825
880
|
catch (error) {
|
|
826
881
|
logError(coordRoot, error, { phase: "session-display-name" });
|
|
827
882
|
}
|
|
828
883
|
}
|
|
884
|
+
if (norm.event_type === "tool.requested" && adapter === "claude-code" && run.topLevelSession) {
|
|
885
|
+
try {
|
|
886
|
+
healForkLineage(coordRoot, owner.instance_id, payload);
|
|
887
|
+
}
|
|
888
|
+
catch (error) {
|
|
889
|
+
logError(coordRoot, error, { phase: "fork-lineage-heal" });
|
|
890
|
+
}
|
|
891
|
+
}
|
|
829
892
|
const v3EventId = v3Result && "event" in v3Result
|
|
830
893
|
? v3Result.event.event_id
|
|
831
894
|
: v3Result && "event_id" in v3Result
|
|
@@ -1281,6 +1344,12 @@ async function handleToolRequested(run) {
|
|
|
1281
1344
|
emitDeny(adapter, waiterReason);
|
|
1282
1345
|
return 0;
|
|
1283
1346
|
}
|
|
1347
|
+
const rmReason = shellRmReason(payload?.tool_name, payload?.tool_input, resolveBinName(coordRoot));
|
|
1348
|
+
if (rmReason) {
|
|
1349
|
+
const { emitDeny } = await import("./adapter/output.js");
|
|
1350
|
+
emitDeny(adapter, rmReason);
|
|
1351
|
+
return 0;
|
|
1352
|
+
}
|
|
1284
1353
|
// G-guard for ALL adapters. Claude Code previously ran this via a
|
|
1285
1354
|
// pre-tool-use bash adapter (which called `agent-coord verdict --rule=claim`);
|
|
1286
1355
|
// that adapter is now deleted, so agent-hook owns the deny for every adapter.
|
|
@@ -1869,12 +1938,16 @@ async function emitUserPromptSubmitSystemMessage(coordRoot, instanceId, sessionI
|
|
|
1869
1938
|
return false;
|
|
1870
1939
|
const behavior = adapterBehavior(adapter);
|
|
1871
1940
|
const { renderPromptContext } = await import("../agents/render/prompt-context.js");
|
|
1941
|
+
let peerMessageIds = [];
|
|
1872
1942
|
let additionalContext = renderPromptContext({
|
|
1873
1943
|
coordRoot,
|
|
1874
1944
|
instanceId,
|
|
1875
1945
|
sessionId,
|
|
1876
1946
|
sessionNameNudge: true,
|
|
1877
1947
|
...behavior.promptContextNudges,
|
|
1948
|
+
onInboxPrepared: (ids) => {
|
|
1949
|
+
peerMessageIds = ids;
|
|
1950
|
+
},
|
|
1878
1951
|
}).trim();
|
|
1879
1952
|
const fileLinkContext = behavior.fileLinkContext?.(coordRoot, workspaceCwd) ?? "";
|
|
1880
1953
|
if (fileLinkContext) {
|
|
@@ -1889,7 +1962,12 @@ async function emitUserPromptSubmitSystemMessage(coordRoot, instanceId, sessionI
|
|
|
1889
1962
|
if (!additionalContext)
|
|
1890
1963
|
return false;
|
|
1891
1964
|
const { emitContext } = await import("./adapter/output.js");
|
|
1892
|
-
|
|
1965
|
+
const emitted = emitContext(adapter, "UserPromptSubmit", additionalContext);
|
|
1966
|
+
if (emitted) {
|
|
1967
|
+
const { acknowledgeEmittedPeerInbox } = await import("../inbox/peer-messages.js");
|
|
1968
|
+
await acknowledgeEmittedPeerInbox(coordRoot, instanceId, peerMessageIds);
|
|
1969
|
+
}
|
|
1970
|
+
return emitted;
|
|
1893
1971
|
}
|
|
1894
1972
|
async function emitSubagentStartContext(coordRoot, instanceId, sessionId, data, adapter) {
|
|
1895
1973
|
// Look up the subagent's assigned name (just-written by the in-process name
|
|
@@ -1905,19 +1983,26 @@ async function emitSubagentStartContext(coordRoot, instanceId, sessionId, data,
|
|
|
1905
1983
|
// is around. Reuse prompt-context (which dedups against the per-owner hash);
|
|
1906
1984
|
// first call will always emit.
|
|
1907
1985
|
let combined = message;
|
|
1986
|
+
let peerMessageIds = [];
|
|
1908
1987
|
const { renderPromptContext } = await import("../agents/render/prompt-context.js");
|
|
1909
1988
|
const context = renderPromptContext({
|
|
1910
1989
|
coordRoot,
|
|
1911
1990
|
instanceId,
|
|
1912
1991
|
sessionId,
|
|
1913
1992
|
agentName: subagentName,
|
|
1993
|
+
onInboxPrepared: (ids) => {
|
|
1994
|
+
peerMessageIds = ids;
|
|
1995
|
+
},
|
|
1914
1996
|
}).trim();
|
|
1915
1997
|
if (context)
|
|
1916
1998
|
combined = `${message}\n\n${context}`;
|
|
1917
1999
|
// Use SubagentStart event-name in CC's hookSpecificOutput shape; cursor's
|
|
1918
2000
|
// flat `additional_context` works the same way.
|
|
1919
2001
|
const { emitContext } = await import("./adapter/output.js");
|
|
1920
|
-
emitContext(adapter, "SubagentStart", combined)
|
|
2002
|
+
if (emitContext(adapter, "SubagentStart", combined)) {
|
|
2003
|
+
const { acknowledgeEmittedPeerInbox } = await import("../inbox/peer-messages.js");
|
|
2004
|
+
await acknowledgeEmittedPeerInbox(coordRoot, instanceId, peerMessageIds);
|
|
2005
|
+
}
|
|
1921
2006
|
}
|
|
1922
2007
|
function adapterPlatform(adapter) {
|
|
1923
2008
|
return adapter;
|
|
@@ -1926,6 +2011,7 @@ async function emitSessionStartSystemMessage(coordRoot, instanceId, sessionId, e
|
|
|
1926
2011
|
const behavior = adapterBehavior(adapter);
|
|
1927
2012
|
const workflowChild = coordEnv("WORKFLOW_CHILD") === "1";
|
|
1928
2013
|
let additionalContext = "";
|
|
2014
|
+
let peerMessageIds = [];
|
|
1929
2015
|
// Opportunistic reconciliation makes normal session starts a failsafe for
|
|
1930
2016
|
// archive, idle, cascade, and host lifecycle observations. It also runs the
|
|
1931
2017
|
// stale cache sweep through the shared ADR 0077 composition.
|
|
@@ -1944,6 +2030,9 @@ async function emitSessionStartSystemMessage(coordRoot, instanceId, sessionId, e
|
|
|
1944
2030
|
sessionId,
|
|
1945
2031
|
agentName: agentName || undefined,
|
|
1946
2032
|
platformLabel: behavior.sessionStartPlatformLabel,
|
|
2033
|
+
onInboxPrepared: (ids) => {
|
|
2034
|
+
peerMessageIds = ids;
|
|
2035
|
+
},
|
|
1947
2036
|
}).trim();
|
|
1948
2037
|
}
|
|
1949
2038
|
// Effect (journal-maintaining adapters): merge the journal recovery cue into
|
|
@@ -1972,11 +2061,19 @@ async function emitSessionStartSystemMessage(coordRoot, instanceId, sessionId, e
|
|
|
1972
2061
|
}
|
|
1973
2062
|
}
|
|
1974
2063
|
const { emitContext } = await import("./adapter/output.js");
|
|
1975
|
-
|
|
2064
|
+
const emitted = emitContext(adapter, "SessionStart", additionalContext);
|
|
2065
|
+
if (emitted) {
|
|
2066
|
+
const { acknowledgeEmittedPeerInbox } = await import("../inbox/peer-messages.js");
|
|
2067
|
+
await acknowledgeEmittedPeerInbox(coordRoot, instanceId, peerMessageIds);
|
|
2068
|
+
}
|
|
2069
|
+
return emitted;
|
|
1976
2070
|
}
|
|
1977
2071
|
function completeRecoveryInjection(coordRoot, instanceId, sessionId, recovery) {
|
|
1978
|
-
completeContextRecovery(coordRoot, {
|
|
1979
|
-
|
|
2072
|
+
completeContextRecovery(coordRoot, {
|
|
2073
|
+
sessionId,
|
|
2074
|
+
instanceId,
|
|
2075
|
+
checkpoint: recovery.checkpoint,
|
|
2076
|
+
});
|
|
1980
2077
|
}
|
|
1981
2078
|
main()
|
|
1982
2079
|
.then(async (code) => {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code fork detection: decide whether a new main-session instance is a
|
|
3
|
+
* branch of another session, and which one.
|
|
4
|
+
*
|
|
5
|
+
* Two shapes exist as of Claude Code 2.1.283 (verified 2026-10-07):
|
|
6
|
+
*
|
|
7
|
+
* - CLI `--resume <id> --fork-session` fires SessionStart under the FORK's id
|
|
8
|
+
* with `source: "fork"`, before its transcript exists, and names no parent.
|
|
9
|
+
* The transcript later holds the parent's message uuids with every row's
|
|
10
|
+
* `sessionId` rewritten, so the parent is found by uuid containment
|
|
11
|
+
* (`detectForkParent`) once the transcript is on disk.
|
|
12
|
+
* - The desktop app's "Fork from here" copies the parent's rows into the
|
|
13
|
+
* fork's transcript with their original `sessionId`, so inherited rows
|
|
14
|
+
* name the parent directly (`detectInheritedSessionParent`).
|
|
15
|
+
*
|
|
16
|
+
* Inherited rows alone do not prove a fork: a continuation of a finished
|
|
17
|
+
* session can carry them too. The parent's liveness separates the two. A
|
|
18
|
+
* parent with an unended generation is still running (or resumable) and owns
|
|
19
|
+
* its name, task, and claims, so the new instance is a fork. A finished
|
|
20
|
+
* parent makes it a continuation, which keeps the generic notice.
|
|
21
|
+
*
|
|
22
|
+
* Everything here fails open: an unreadable transcript or an unknown layout
|
|
23
|
+
* yields "not a fork", never an error.
|
|
24
|
+
*/
|
|
25
|
+
export interface ForkDetection {
|
|
26
|
+
fork: boolean;
|
|
27
|
+
forkedFrom?: string;
|
|
28
|
+
/** Detection had the evidence it needs; the tool-path heal can skip it. */
|
|
29
|
+
checked: boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare function detectClaudeCodeFork(input: {
|
|
32
|
+
coordRoot: string;
|
|
33
|
+
transcriptPath: string | undefined;
|
|
34
|
+
sessionId: string;
|
|
35
|
+
source?: string;
|
|
36
|
+
/** The instance was already recorded as a fork without a parent. */
|
|
37
|
+
knownFork?: boolean;
|
|
38
|
+
isLive?: (instanceId: string) => boolean;
|
|
39
|
+
}): ForkDetection;
|
|
40
|
+
//# sourceMappingURL=fork-lineage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fork-lineage.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/fork-lineage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,OAAO,EAAE,OAAO,CAAC;CAClB;AAKD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;CAC1C,GAAG,aAAa,CAkChB"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code fork detection: decide whether a new main-session instance is a
|
|
3
|
+
* branch of another session, and which one.
|
|
4
|
+
*
|
|
5
|
+
* Two shapes exist as of Claude Code 2.1.283 (verified 2026-10-07):
|
|
6
|
+
*
|
|
7
|
+
* - CLI `--resume <id> --fork-session` fires SessionStart under the FORK's id
|
|
8
|
+
* with `source: "fork"`, before its transcript exists, and names no parent.
|
|
9
|
+
* The transcript later holds the parent's message uuids with every row's
|
|
10
|
+
* `sessionId` rewritten, so the parent is found by uuid containment
|
|
11
|
+
* (`detectForkParent`) once the transcript is on disk.
|
|
12
|
+
* - The desktop app's "Fork from here" copies the parent's rows into the
|
|
13
|
+
* fork's transcript with their original `sessionId`, so inherited rows
|
|
14
|
+
* name the parent directly (`detectInheritedSessionParent`).
|
|
15
|
+
*
|
|
16
|
+
* Inherited rows alone do not prove a fork: a continuation of a finished
|
|
17
|
+
* session can carry them too. The parent's liveness separates the two. A
|
|
18
|
+
* parent with an unended generation is still running (or resumable) and owns
|
|
19
|
+
* its name, task, and claims, so the new instance is a fork. A finished
|
|
20
|
+
* parent makes it a continuation, which keeps the generic notice.
|
|
21
|
+
*
|
|
22
|
+
* Everything here fails open: an unreadable transcript or an unknown layout
|
|
23
|
+
* yields "not a fork", never an error.
|
|
24
|
+
*/
|
|
25
|
+
import { existsSync } from "node:fs";
|
|
26
|
+
import { readLiveCoordinationRow } from "../agents/state/live-coordination-view.js";
|
|
27
|
+
import { detectForkParent, detectInheritedSessionParent } from "./resolve/transcript.js";
|
|
28
|
+
/** Sources after which a brand-new instance cannot hold inherited history. */
|
|
29
|
+
const FRESH_SOURCES = new Set(["startup", "clear"]);
|
|
30
|
+
export function detectClaudeCodeFork(input) {
|
|
31
|
+
try {
|
|
32
|
+
const declared = input.source === "fork" || input.knownFork === true;
|
|
33
|
+
const transcriptReady = !!input.transcriptPath && existsSync(input.transcriptPath);
|
|
34
|
+
const isLive = input.isLive ?? ((id) => readLiveCoordinationRow(input.coordRoot, id) !== null);
|
|
35
|
+
const inherited = transcriptReady
|
|
36
|
+
? detectInheritedSessionParent(input.transcriptPath, input.sessionId)
|
|
37
|
+
: undefined;
|
|
38
|
+
if (inherited && inherited !== input.sessionId) {
|
|
39
|
+
if (declared || isLive(inherited)) {
|
|
40
|
+
return { fork: true, forkedFrom: inherited, checked: true };
|
|
41
|
+
}
|
|
42
|
+
return { fork: false, checked: true };
|
|
43
|
+
}
|
|
44
|
+
if (declared) {
|
|
45
|
+
if (!transcriptReady)
|
|
46
|
+
return { fork: true, checked: false };
|
|
47
|
+
const parent = detectForkParent(input.transcriptPath, input.sessionId);
|
|
48
|
+
return {
|
|
49
|
+
fork: true,
|
|
50
|
+
...(parent && parent !== input.sessionId ? { forkedFrom: parent } : {}),
|
|
51
|
+
checked: true,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
fork: false,
|
|
56
|
+
checked: transcriptReady || FRESH_SOURCES.has(input.source ?? ""),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return { fork: false, checked: true };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -92,6 +92,22 @@ export interface TranscriptRuntimeScan {
|
|
|
92
92
|
* SessionStart).
|
|
93
93
|
*/
|
|
94
94
|
export declare function scanTranscriptRuntime(transcriptPath: string | undefined): TranscriptRuntimeScan | undefined;
|
|
95
|
+
/**
|
|
96
|
+
* Detect inherited history from row session ids: the session whose rows were
|
|
97
|
+
* copied into this transcript, or undefined when every row is this session's.
|
|
98
|
+
*
|
|
99
|
+
* Empirical basis (verified 2026-10-07, Claude Code 2.1.283): the desktop
|
|
100
|
+
* app's "Fork from here" copies the parent's rows into the fork's transcript
|
|
101
|
+
* WITHOUT rewriting their `sessionId`, so the fork's file opens with thousands
|
|
102
|
+
* of rows stamped with the parent's id. The CLI `--fork-session` path rewrites
|
|
103
|
+
* the ids instead and is covered by {@link detectForkParent}.
|
|
104
|
+
*
|
|
105
|
+
* When the history is itself several generations deep, the LAST foreign row is
|
|
106
|
+
* the nearest ancestor, so the tail is scanned first. Whether the foreign
|
|
107
|
+
* session is a fork parent or a dead predecessor (a continuation) is the
|
|
108
|
+
* caller's call. Fail-soft: never throws, undefined on any read problem.
|
|
109
|
+
*/
|
|
110
|
+
export declare function detectInheritedSessionParent(transcriptPath: string | undefined, sessionId: string): string | undefined;
|
|
95
111
|
/**
|
|
96
112
|
* Detect the parent session of a forked CC conversation from its transcript.
|
|
97
113
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/resolve/transcript.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,2BAA2B,wBAAc,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAQhF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CACvC,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,MAAM,EAAE,MAAM,GACb,OAAO,CAiCT;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,oBAAoB,EAAE,MAAM,GAAG,SAAS,GACvC,OAAO,CAKT;AAED,kFAAkF;AAClF,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAgB9F;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CACjD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,GAC/D,OAAO,CAIT;AAED,MAAM,MAAM,4BAA4B,GACpC;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,GACpB;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE,GACnB;IACE,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,oBAAoB,GAAG,sBAAsB,GAAG,wBAAwB,CAAC;CAClF,CAAC;AAEN;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAClD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,GAC/D,4BAA4B,CAqD9B;AAgBD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAUrF;AAwDD;4CAC4C;AAC5C,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd;+EAC2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,MAAM,GAAG,SAAS,GACjC,qBAAqB,GAAG,SAAS,CA8BnC;
|
|
1
|
+
{"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/resolve/transcript.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,2BAA2B,wBAAc,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAQhF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CACvC,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,MAAM,EAAE,MAAM,GACb,OAAO,CAiCT;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,oBAAoB,EAAE,MAAM,GAAG,SAAS,GACvC,OAAO,CAKT;AAED,kFAAkF;AAClF,wBAAgB,uBAAuB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAgB9F;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CACjD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,GAC/D,OAAO,CAIT;AAED,MAAM,MAAM,4BAA4B,GACpC;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,GACpB;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE,GACnB;IACE,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,oBAAoB,GAAG,sBAAsB,GAAG,wBAAwB,CAAC;CAClF,CAAC;AAEN;;;;;GAKG;AACH,wBAAgB,oCAAoC,CAClD,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,GAC/D,4BAA4B,CAqD9B;AAgBD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAUrF;AAwDD;4CAC4C;AAC5C,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd;+EAC2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,MAAM,GAAG,SAAS,GACjC,qBAAqB,GAAG,SAAS,CA8BnC;AAgBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,4BAA4B,CAC1C,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,SAAS,CAiBpB;AA6CD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,gBAAgB,CAC9B,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,SAAS,CAkDpB"}
|
|
@@ -318,6 +318,90 @@ const SESSION_FILE_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-
|
|
|
318
318
|
const FORK_SCAN_MAX_BYTES = 128 * 1024 * 1024;
|
|
319
319
|
/** How many message uuids to sample across the fork's copied prefix. */
|
|
320
320
|
const FORK_SAMPLE_SIZE = 24;
|
|
321
|
+
/** Tail window scanned for inherited rows; a fork's own rows are few when the
|
|
322
|
+
* scan runs (session start or the first tool call), so the copied history ends
|
|
323
|
+
* well inside it. */
|
|
324
|
+
const INHERITED_TAIL_BYTES = 4 * 1024 * 1024;
|
|
325
|
+
const INHERITED_HEAD_BYTES = 64 * 1024;
|
|
326
|
+
const SESSION_ID_FIELD_RE = /"sessionId":"([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})"/gi;
|
|
327
|
+
/**
|
|
328
|
+
* Detect inherited history from row session ids: the session whose rows were
|
|
329
|
+
* copied into this transcript, or undefined when every row is this session's.
|
|
330
|
+
*
|
|
331
|
+
* Empirical basis (verified 2026-10-07, Claude Code 2.1.283): the desktop
|
|
332
|
+
* app's "Fork from here" copies the parent's rows into the fork's transcript
|
|
333
|
+
* WITHOUT rewriting their `sessionId`, so the fork's file opens with thousands
|
|
334
|
+
* of rows stamped with the parent's id. The CLI `--fork-session` path rewrites
|
|
335
|
+
* the ids instead and is covered by {@link detectForkParent}.
|
|
336
|
+
*
|
|
337
|
+
* When the history is itself several generations deep, the LAST foreign row is
|
|
338
|
+
* the nearest ancestor, so the tail is scanned first. Whether the foreign
|
|
339
|
+
* session is a fork parent or a dead predecessor (a continuation) is the
|
|
340
|
+
* caller's call. Fail-soft: never throws, undefined on any read problem.
|
|
341
|
+
*/
|
|
342
|
+
export function detectInheritedSessionParent(transcriptPath, sessionId) {
|
|
343
|
+
try {
|
|
344
|
+
const readablePath = resolveTranscriptPath(transcriptPath);
|
|
345
|
+
if (!readablePath || !sessionId)
|
|
346
|
+
return undefined;
|
|
347
|
+
const size = statSync(readablePath).size;
|
|
348
|
+
const fromTail = lastForeignSessionId(readRange(readablePath, Math.max(0, size - INHERITED_TAIL_BYTES), size), sessionId);
|
|
349
|
+
if (fromTail || size <= INHERITED_TAIL_BYTES)
|
|
350
|
+
return fromTail;
|
|
351
|
+
return lastForeignSessionId(readRange(readablePath, 0, Math.min(size, INHERITED_HEAD_BYTES)), sessionId);
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
return undefined;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function lastForeignSessionId(text, sessionId) {
|
|
358
|
+
if (!text)
|
|
359
|
+
return undefined;
|
|
360
|
+
const own = sessionId.toLowerCase();
|
|
361
|
+
const lines = text.split("\n");
|
|
362
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
363
|
+
const line = lines[i];
|
|
364
|
+
if (!line.includes('"sessionId":"'))
|
|
365
|
+
continue;
|
|
366
|
+
let foreign = false;
|
|
367
|
+
for (const m of line.matchAll(SESSION_ID_FIELD_RE)) {
|
|
368
|
+
if (m[1].toLowerCase() !== own)
|
|
369
|
+
foreign = true;
|
|
370
|
+
}
|
|
371
|
+
if (!foreign)
|
|
372
|
+
continue;
|
|
373
|
+
// Confirm on the parsed row: a nested object can carry another session's
|
|
374
|
+
// id, but only the row's own top-level field says whose row it is. A
|
|
375
|
+
// partial line at a window edge fails to parse and is skipped.
|
|
376
|
+
try {
|
|
377
|
+
const row = JSON.parse(line);
|
|
378
|
+
if (typeof row.sessionId === "string" && row.sessionId.toLowerCase() !== own) {
|
|
379
|
+
return row.sessionId;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
/* torn line */
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return undefined;
|
|
387
|
+
}
|
|
388
|
+
function readRange(path, start, end) {
|
|
389
|
+
try {
|
|
390
|
+
const fs = require("node:fs");
|
|
391
|
+
const fd = fs.openSync(path, "r");
|
|
392
|
+
try {
|
|
393
|
+
const buf = Buffer.alloc(Math.max(0, end - start));
|
|
394
|
+
fs.readSync(fd, buf, 0, buf.length, start);
|
|
395
|
+
return buf.toString("utf8");
|
|
396
|
+
}
|
|
397
|
+
finally {
|
|
398
|
+
fs.closeSync(fd);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
return undefined;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
321
405
|
/**
|
|
322
406
|
* Detect the parent session of a forked CC conversation from its transcript.
|
|
323
407
|
*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-rm.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/shell-rm.ts"],"names":[],"mappings":"AAsFA,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,OAAO,EACjB,SAAS,EAAE,OAAO,EAClB,OAAO,EAAE,MAAM,GACd,MAAM,GAAG,IAAI,CAYf"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { basename, shellCommandFromTool, tokenize } from "./shell-tokens.js";
|
|
2
|
+
/**
|
|
3
|
+
* Deny a shell `rm` and name the guarded replacement. The instructions have
|
|
4
|
+
* said "use <bin> rm instead of shell rm" since the command shipped, and
|
|
5
|
+
* agents still reached for bare rm several times a day, often on unquoted
|
|
6
|
+
* variable paths that Claude Code then stops on for a human prompt. A
|
|
7
|
+
* repeated, machine-detectable failure belongs in a gate, not more prose.
|
|
8
|
+
*
|
|
9
|
+
* This is a guardrail against the reflex, not a security boundary: it reads
|
|
10
|
+
* command positions only (so `git rm`, `docker rm`, and quoted examples pass),
|
|
11
|
+
* plus the `xargs rm`, `find -exec rm`, and `bash -c '...rm...'` shapes.
|
|
12
|
+
* HARNERY_ALLOW_RM=1 on the command is the deliberate override.
|
|
13
|
+
*/
|
|
14
|
+
const OVERRIDE = "HARNERY_ALLOW_RM=1";
|
|
15
|
+
const SEPARATORS = new Set([";", "|", "||", "&", "&&", "(", ")", "{", "}"]);
|
|
16
|
+
const KEYWORDS = new Set(["do", "if", "then", "elif", "else", "!", "while", "until", "time"]);
|
|
17
|
+
const WRAPPERS = new Set(["command", "exec", "builtin", "nohup", "sudo", "doas", "env", "nice"]);
|
|
18
|
+
// xargs options that consume the following token as their value.
|
|
19
|
+
const XARGS_VALUE_FLAGS = new Set(["-n", "-I", "-L", "-P", "-d", "-E", "-s", "-a"]);
|
|
20
|
+
function isRm(value, quoted) {
|
|
21
|
+
return !quoted && basename(value) === "rm";
|
|
22
|
+
}
|
|
23
|
+
function invokesRm(source, depth = 0) {
|
|
24
|
+
if (depth > 8)
|
|
25
|
+
return false;
|
|
26
|
+
const tokens = tokenize(source);
|
|
27
|
+
if (tokens.some((t) => !t.quoted && t.value === OVERRIDE))
|
|
28
|
+
return false;
|
|
29
|
+
let start = true;
|
|
30
|
+
let afterWrapper = false;
|
|
31
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
32
|
+
const token = tokens[i];
|
|
33
|
+
if (token.operator) {
|
|
34
|
+
if (SEPARATORS.has(token.value)) {
|
|
35
|
+
start = true;
|
|
36
|
+
afterWrapper = false;
|
|
37
|
+
}
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (!start)
|
|
41
|
+
continue;
|
|
42
|
+
if (!token.quoted) {
|
|
43
|
+
if (KEYWORDS.has(token.value))
|
|
44
|
+
continue;
|
|
45
|
+
if (/^[A-Za-z_][A-Za-z_0-9]*=/.test(token.value))
|
|
46
|
+
continue;
|
|
47
|
+
if (afterWrapper && token.value.startsWith("-"))
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const name = basename(token.value);
|
|
51
|
+
if (WRAPPERS.has(name)) {
|
|
52
|
+
afterWrapper = true;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
start = false;
|
|
56
|
+
afterWrapper = false;
|
|
57
|
+
if (isRm(token.value, token.quoted))
|
|
58
|
+
return true;
|
|
59
|
+
if (name === "xargs") {
|
|
60
|
+
for (let j = i + 1; j < tokens.length && !tokens[j].operator; j++) {
|
|
61
|
+
const arg = tokens[j];
|
|
62
|
+
if (arg.value.startsWith("-")) {
|
|
63
|
+
if (XARGS_VALUE_FLAGS.has(arg.value))
|
|
64
|
+
j++;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (isRm(arg.value, arg.quoted))
|
|
68
|
+
return true;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (name === "find") {
|
|
73
|
+
for (let j = i + 1; j < tokens.length && !tokens[j].operator; j++) {
|
|
74
|
+
if (!/^-(?:exec|execdir|ok|okdir)$/.test(tokens[j].value))
|
|
75
|
+
continue;
|
|
76
|
+
const next = tokens[j + 1];
|
|
77
|
+
if (next && !next.operator && isRm(next.value, next.quoted))
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (["bash", "sh", "dash", "zsh", "ksh"].includes(name)) {
|
|
82
|
+
for (let j = i + 1; j < tokens.length && !tokens[j].operator; j++) {
|
|
83
|
+
if (/^-[a-z]*c[a-z]*$/.test(tokens[j].value)) {
|
|
84
|
+
const script = tokens[j + 1];
|
|
85
|
+
if (script && !script.operator && invokesRm(script.value, depth + 1))
|
|
86
|
+
return true;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
export function shellRmReason(toolName, toolInput, binName) {
|
|
95
|
+
const command = shellCommandFromTool(toolName, toolInput);
|
|
96
|
+
if (command === null || !invokesRm(command))
|
|
97
|
+
return null;
|
|
98
|
+
return (`Shell rm blocked: it skips the resolved-path, Git-ownership, and peer-claim checks, and an ` +
|
|
99
|
+
`unquoted variable path can expand to the wrong target. Use the guarded paths instead: ` +
|
|
100
|
+
`\`${binName} rm --root <directory> <paths...>\` previews removal of untracked files (add ` +
|
|
101
|
+
`--yes to delete, -r for a directory); \`git rm\` removes tracked source; ` +
|
|
102
|
+
`\`${binName} artifacts discard <id>\` retires managed evidence. To replace a file, overwrite ` +
|
|
103
|
+
`it in place or \`mv\` the new file over the old one, so nothing needs deleting. If none of ` +
|
|
104
|
+
`these fit, prefix the command with ${OVERRIDE}.`);
|
|
105
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Shared, deliberately small shell lexer for PreToolUse command guards. */
|
|
2
|
+
export interface Token {
|
|
3
|
+
value: string;
|
|
4
|
+
quoted: boolean;
|
|
5
|
+
operator?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** Keep quoted arguments opaque, including examples and heredoc bodies. */
|
|
8
|
+
export declare function tokenize(source: string): Token[];
|
|
9
|
+
export declare function basename(value: string): string;
|
|
10
|
+
/** The command string of a Bash-style tool call, or null for any other tool. */
|
|
11
|
+
export declare function shellCommandFromTool(toolName: unknown, toolInput: unknown): string | null;
|
|
12
|
+
//# sourceMappingURL=shell-tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-tokens.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/shell-tokens.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAE5E,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,2EAA2E;AAC3E,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,EAAE,CA+DhD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,gFAAgF;AAChF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAgBzF"}
|