@reticlehq/server 3.7.0 → 3.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +59 -68
- package/bin/reticle.js +9 -2
- package/dist/command/cli/adhoc-suite.js +1 -1
- package/dist/command/cli/adhoc-verdict.d.ts +12 -0
- package/dist/command/cli/adhoc-verdict.js +62 -4
- package/dist/command/cli/answers/gate-hook-message.d.ts +2 -0
- package/dist/command/cli/answers/gate-hook-message.js +5 -1
- package/dist/command/cli/auth/cloud-session.d.ts +11 -19
- package/dist/command/cli/auth/cloud-session.js +23 -0
- package/dist/command/cli/auth/device-flow.d.ts +3 -37
- package/dist/command/cli/cli-doctor.js +66 -21
- package/dist/command/cli/cli-flow-commands.js +53 -11
- package/dist/command/cli/cli-kill.d.ts +8 -0
- package/dist/command/cli/cli-kill.js +43 -3
- package/dist/command/cli/cli-parse-feedback.d.ts +4 -0
- package/dist/command/cli/cli-parse-feedback.js +13 -1
- package/dist/command/cli/cli-parse-grammar.d.ts +1 -2
- package/dist/command/cli/cli-parse-grammar.js +1 -6
- package/dist/command/cli/cli-parse-serve.d.ts +24 -0
- package/dist/command/cli/cli-parse-serve.js +124 -0
- package/dist/command/cli/cli-parse.d.ts +12 -11
- package/dist/command/cli/cli-parse.js +100 -123
- package/dist/command/cli/cli-usage.d.ts +0 -11
- package/dist/command/cli/cli-usage.js +43 -16
- package/dist/command/cli/cli-verify.d.ts +1 -1
- package/dist/command/cli/cli-verify.js +29 -14
- package/dist/command/cli/cloud-cli.js +27 -9
- package/dist/command/cli/cloud-kit.js +3 -3
- package/dist/command/cli/cloud-link.d.ts +1 -20
- package/dist/command/cli/cloud-link.js +2 -0
- package/dist/command/cli/cloud-login.d.ts +6 -2
- package/dist/command/cli/cloud-login.js +13 -41
- package/dist/command/cli/config/checkout.d.ts +10 -0
- package/dist/command/cli/config/checkout.js +78 -0
- package/dist/command/cli/doctor/doctor-agent-link.d.ts +23 -0
- package/dist/command/cli/doctor/doctor-agent-link.js +28 -0
- package/dist/command/cli/doctor/doctor-coverage-line.js +1 -7
- package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
- package/dist/command/cli/doctor/doctor-rows.js +4 -0
- package/dist/command/cli/drive/drive-attach.d.ts +5 -0
- package/dist/command/cli/drive/drive-attach.js +21 -1
- package/dist/command/cli/hook/codex-transcript.d.ts +20 -0
- package/dist/command/cli/hook/codex-transcript.js +224 -0
- package/dist/command/cli/hook/episode-structure.d.ts +24 -0
- package/dist/command/cli/hook/episode-structure.js +138 -0
- package/dist/command/cli/hook/hook-command.d.ts +9 -0
- package/dist/command/cli/hook/hook-command.js +211 -0
- package/dist/command/cli/hook/hook-fast.d.ts +31 -0
- package/dist/command/cli/hook/hook-fast.js +136 -0
- package/dist/command/cli/hook/prompt-hook.d.ts +30 -0
- package/dist/command/cli/hook/prompt-hook.js +199 -0
- package/dist/command/cli/hook/tool-hook.d.ts +21 -0
- package/dist/command/cli/hook/tool-hook.js +259 -0
- package/dist/command/cli/launch/cli-launch.d.ts +2 -0
- package/dist/command/cli/launch/cli-launch.js +22 -2
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +28 -2
- package/dist/command/cli/mcp-command.d.ts +9 -0
- package/dist/command/cli/mcp-command.js +127 -41
- package/dist/command/cli/open-navigate.d.ts +43 -0
- package/dist/command/cli/open-navigate.js +85 -0
- package/dist/command/cli/ports/port-holder.d.ts +16 -4
- package/dist/command/cli/ports/port-holder.js +58 -12
- package/dist/command/cli/ports/resolve/cli-port.js +18 -2
- package/dist/command/cli/project-name.d.ts +1 -7
- package/dist/command/cli/project-name.js +1 -0
- package/dist/command/cli/report-command.d.ts +8 -2
- package/dist/command/cli/report-command.js +95 -18
- package/dist/command/cli/status-fields.d.ts +9 -2
- package/dist/command/cli/status-fields.js +148 -4
- package/dist/command/cli/try-command.d.ts +3 -4
- package/dist/command/cli/try-command.js +6 -11
- package/dist/command/cli/verify-results.d.ts +4 -1
- package/dist/command/cli/verify-results.js +3 -1
- package/dist/command/cli.d.ts +1 -7
- package/dist/command/cli.js +79 -160
- package/dist/command/daemon/daemon-resilience.d.ts +6 -1
- package/dist/command/daemon/daemon-resilience.js +31 -18
- package/dist/command/daemon/daemon.d.ts +11 -0
- package/dist/command/daemon/daemon.js +70 -2
- package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +2 -0
- package/dist/command/daemon/lifetime/daemon-usefulness.js +13 -2
- package/dist/command/daemon/wake-decision.js +4 -2
- package/dist/command/daemon/win32-escape.d.ts +32 -0
- package/dist/command/daemon/win32-escape.js +119 -0
- package/dist/command/setup/agent-configs.d.ts +9 -0
- package/dist/command/setup/agent-configs.js +9 -0
- package/dist/command/setup/auto-approve.d.ts +10 -3
- package/dist/command/setup/auto-approve.js +51 -3
- package/dist/command/setup/bringup/dev-server-files.d.ts +1 -9
- package/dist/command/setup/bringup/dev-server-wait.js +5 -1
- package/dist/command/setup/bringup/ensure-daemon.js +4 -7
- package/dist/command/setup/init/init-host.js +6 -0
- package/dist/command/setup/init/init-port.js +5 -4
- package/dist/command/setup/init/init-runtime.js +7 -1
- package/dist/command/setup/node-effects.d.ts +8 -0
- package/dist/command/setup/node-effects.js +36 -4
- package/dist/command/setup/probe/dev-port.d.ts +26 -0
- package/dist/command/setup/probe/dev-port.js +117 -0
- package/dist/command/setup/probe/page-probe.d.ts +1 -1
- package/dist/command/setup/probe/page-probe.js +11 -3
- package/dist/command/setup/remaining-steps.d.ts +0 -7
- package/dist/command/setup/remaining-steps.js +3 -2
- package/dist/command/setup/run-setup.d.ts +11 -0
- package/dist/command/setup/run-setup.js +33 -5
- package/dist/command/setup/setup-command.d.ts +2 -0
- package/dist/command/setup/setup-command.js +22 -4
- package/dist/command/update/updater.d.ts +1 -6
- package/dist/command/update/updater.js +4 -5
- package/dist/command/update/version-seen.d.ts +24 -0
- package/dist/command/update/version-seen.js +65 -0
- package/dist/command/version/peer-announce.d.ts +9 -1
- package/dist/command/version/peer-announce.js +25 -2
- package/dist/command/version/sdk-fix.d.ts +5 -6
- package/dist/command/version/sdk-fix.js +17 -32
- package/dist/command/version/version-skew.d.ts +16 -1
- package/dist/command/version/version-skew.js +84 -10
- package/dist/features/crawl/crawl-tools.js +2 -2
- package/dist/features/crawl/crawl.js +2 -0
- package/dist/features/exhaust/ledger.d.ts +15 -96
- package/dist/features/exhaust/ledger.js +3 -3
- package/dist/features/harness/drive-runs.d.ts +28 -4
- package/dist/features/harness/drive-runs.js +99 -7
- package/dist/features/harness/drivers.d.ts +1 -1
- package/dist/features/harness/drivers.js +1 -1
- package/dist/features/harness/harness.d.ts +5 -0
- package/dist/features/harness/harness.js +24 -12
- package/dist/features/harness/platform/platform-drives.d.ts +7 -1
- package/dist/features/harness/platform/platform-drives.js +12 -7
- package/dist/features/harness/platform/refusal.d.ts +1 -1
- package/dist/features/harness/platform/refusal.js +4 -4
- package/dist/features/harness/platform/remote-drive.js +70 -28
- package/dist/features/harness/platform/server-driver.js +22 -1
- package/dist/features/harness/platform-config.d.ts +1 -10
- package/dist/features/harness/platform-config.js +0 -20
- package/dist/features/license/license.d.ts +2 -14
- package/dist/features/visual/step-shots.d.ts +33 -1
- package/dist/features/visual/step-shots.js +155 -1
- package/dist/features/visual/visual-tools.js +12 -4
- package/dist/hooks/coding-agents.d.ts +6 -0
- package/dist/hooks/coding-agents.js +24 -3
- package/dist/hooks/episode-stamp.d.ts +32 -0
- package/dist/hooks/episode-stamp.js +61 -0
- package/dist/hooks/hook-commands.js +19 -9
- package/dist/index.d.ts +5 -1
- package/dist/index.js +66 -24
- package/dist/judgement/app-graph/journal-steps.d.ts +8 -0
- package/dist/judgement/app-graph/journal-steps.js +92 -0
- package/dist/judgement/capsule/capsule-store.d.ts +2 -39
- package/dist/judgement/domain/domain-model.js +1 -10
- package/dist/judgement/domain/domain-tools.js +4 -2
- package/dist/judgement/runs/artifact/run-store.d.ts +28 -8
- package/dist/judgement/runs/artifact/run-store.js +153 -28
- package/dist/judgement/runs/default-run-id.d.ts +3 -1
- package/dist/judgement/runs/default-run-id.js +15 -3
- package/dist/judgement/runs/drive-run.d.ts +3 -3
- package/dist/judgement/runs/drive-run.js +11 -7
- package/dist/judgement/runs/run-ledger.d.ts +21 -0
- package/dist/judgement/runs/run-ledger.js +143 -0
- package/dist/judgement/runs/runner-port.d.ts +1 -1
- package/dist/judgement/runs/runner-port.js +8 -2
- package/dist/judgement/runs/verification-sync.d.ts +4 -3
- package/dist/judgement/runs/verification-sync.js +27 -5
- package/dist/language/flows/affected-tools.js +2 -1
- package/dist/language/flows/annotate-notes/annotate.js +8 -3
- package/dist/language/flows/annotate-tools.js +1 -1
- package/dist/language/flows/change/affected.d.ts +8 -0
- package/dist/language/flows/change/affected.js +20 -7
- package/dist/language/flows/decision.js +19 -4
- package/dist/language/flows/fields/flow-secret-field.d.ts +9 -0
- package/dist/language/flows/fields/flow-secret-field.js +67 -5
- package/dist/language/flows/flow-anchor.d.ts +5 -0
- package/dist/language/flows/flow-anchor.js +4 -0
- package/dist/language/flows/flow-cross-step.d.ts +7 -1
- package/dist/language/flows/flow-cross-step.js +9 -0
- package/dist/language/flows/flow-expect-grammar.js +15 -10
- package/dist/language/flows/flow-intent.js +6 -1
- package/dist/language/flows/flow-journey.d.ts +8 -0
- package/dist/language/flows/flow-journey.js +13 -0
- package/dist/language/flows/flow-lease.d.ts +27 -0
- package/dist/language/flows/flow-lease.js +59 -0
- package/dist/language/flows/flow-replay-run.d.ts +1 -87
- package/dist/language/flows/flow-replay-run.js +111 -310
- package/dist/language/flows/flow-replay.js +21 -19
- package/dist/language/flows/flow-replayable.d.ts +38 -0
- package/dist/language/flows/flow-replayable.js +179 -0
- package/dist/language/flows/flow-start-path.d.ts +108 -0
- package/dist/language/flows/flow-start-path.js +313 -0
- package/dist/language/flows/flow-step-runners.d.ts +23 -2
- package/dist/language/flows/flow-step-runners.js +191 -32
- package/dist/language/flows/flow-store-for-session.d.ts +8 -1
- package/dist/language/flows/flow-store-for-session.js +6 -0
- package/dist/language/flows/flow-tools.d.ts +2 -23
- package/dist/language/flows/flow-tools.js +63 -113
- package/dist/language/flows/flows.d.ts +4 -6
- package/dist/language/flows/flows.js +90 -15
- package/dist/language/flows/heal-run.d.ts +10 -4
- package/dist/language/flows/heal-run.js +26 -9
- package/dist/language/flows/recording/flake.d.ts +3 -27
- package/dist/language/flows/recording/flake.js +1 -1
- package/dist/language/flows/recording/recording-backtrack.js +1 -4
- package/dist/language/flows/recording/tape/recordings.d.ts +11 -1
- package/dist/language/flows/recording/tape/recordings.js +9 -3
- package/dist/language/flows/replay.d.ts +25 -5
- package/dist/language/flows/replay.js +83 -16
- package/dist/language/flows/stores/assertion-tiers-store.js +1 -1
- package/dist/language/flows/suite/coverage.d.ts +9 -0
- package/dist/language/flows/suite/coverage.js +12 -1
- package/dist/language/flows/suite-selection.d.ts +25 -0
- package/dist/language/flows/suite-selection.js +41 -9
- package/dist/machine/git-tracks.d.ts +6 -0
- package/dist/machine/git-tracks.js +36 -0
- package/dist/memory/cloud/background-sync.d.ts +25 -0
- package/dist/memory/cloud/background-sync.js +79 -0
- package/dist/memory/cloud/cloud-config.d.ts +12 -1
- package/dist/memory/cloud/cloud-config.js +39 -13
- package/dist/memory/cloud/cloud-sync.d.ts +3 -24
- package/dist/memory/cloud/cloud-sync.js +5 -2
- package/dist/memory/cloud/harness-config.d.ts +0 -7
- package/dist/memory/cloud/memory-scope.d.ts +19 -0
- package/dist/memory/cloud/memory-scope.js +48 -0
- package/dist/memory/cloud/sync-answer.d.ts +43 -0
- package/dist/memory/cloud/sync-answer.js +42 -0
- package/dist/memory/cloud/sync-cycle.d.ts +40 -10
- package/dist/memory/cloud/sync-cycle.js +97 -144
- package/dist/memory/cloud/sync-daemon.d.ts +50 -3
- package/dist/memory/cloud/sync-daemon.js +473 -83
- package/dist/memory/cloud/sync-describe.d.ts +28 -0
- package/dist/memory/cloud/sync-describe.js +92 -0
- package/dist/memory/cloud/sync-disk.d.ts +9 -1
- package/dist/memory/cloud/sync-disk.js +137 -33
- package/dist/memory/cloud/sync-flow-versions.d.ts +26 -0
- package/dist/memory/cloud/sync-flow-versions.js +42 -0
- package/dist/memory/impact/impact-store.js +32 -15
- package/dist/memory/intent/intent-shard.d.ts +41 -266
- package/dist/memory/intent/intent-tools.js +20 -61
- package/dist/memory/intent/record-request.d.ts +27 -0
- package/dist/memory/intent/record-request.js +88 -0
- package/dist/memory/intent/settled-words.js +9 -1
- package/dist/memory/journal/drive-flow.d.ts +3 -0
- package/dist/memory/journal/drive-flow.js +11 -41
- package/dist/memory/journal/envelope-store.js +1 -1
- package/dist/memory/journal/envelope.d.ts +6 -154
- package/dist/memory/journal/episode-log.d.ts +107 -0
- package/dist/memory/journal/episode-log.js +302 -0
- package/dist/memory/journal/journal-recorder.d.ts +7 -0
- package/dist/memory/journal/journal-recorder.js +22 -9
- package/dist/memory/journal/on-disk/ambient-file.d.ts +1 -7
- package/dist/memory/journal/on-disk/ambient-file.js +1 -1
- package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +24 -3
- package/dist/memory/journal/on-disk/workspace-gitignore.js +38 -7
- package/dist/memory/journal/on-disk/workspace-tiers.js +7 -0
- package/dist/memory/journal/self-observation.js +3 -1
- package/dist/memory/journal/session-end.js +23 -9
- package/dist/memory/journal/session-journal.js +39 -20
- package/dist/memory/project/artifact-root-resolver.d.ts +14 -1
- package/dist/memory/project/artifact-root-resolver.js +68 -23
- package/dist/memory/project/artifact-root.d.ts +17 -3
- package/dist/memory/project/artifact-root.js +47 -9
- package/dist/memory/project/fs/fs-port.d.ts +5 -0
- package/dist/memory/project/fs/fs-port.js +13 -0
- package/dist/memory/project/learned-routes.d.ts +3 -4
- package/dist/memory/project/learned-routes.js +3 -11
- package/dist/memory/project/link-directory.js +5 -2
- package/dist/memory/project/root-claims.d.ts +3 -1
- package/dist/memory/project/root-claims.js +41 -7
- package/dist/memory/project/session-root.d.ts +1 -1
- package/dist/memory/project/session-root.js +1 -15
- package/dist/memory/project/sync-status.js +5 -6
- package/dist/memory/recall/memory-tools.js +2 -2
- package/dist/memory/recall/platform-memory.d.ts +3 -1
- package/dist/memory/recall/platform-memory.js +9 -1
- package/dist/memory/recall/project-memory.js +15 -19
- package/dist/portal/bridge/auth-failure-reason.d.ts +13 -0
- package/dist/portal/bridge/auth-failure-reason.js +19 -0
- package/dist/portal/bridge/bridge.d.ts +19 -4
- package/dist/portal/bridge/bridge.js +150 -177
- package/dist/portal/bridge/harness-request.d.ts +15 -0
- package/dist/portal/bridge/harness-request.js +19 -0
- package/dist/portal/bridge/origin/origin-follow.d.ts +46 -0
- package/dist/portal/bridge/origin/origin-follow.js +110 -0
- package/dist/portal/bridge/pairing-token.d.ts +6 -0
- package/dist/portal/bridge/pairing-token.js +47 -16
- package/dist/portal/bridge/protocol-skew.d.ts +27 -2
- package/dist/portal/bridge/protocol-skew.js +66 -2
- package/dist/portal/bridge/session-nonces.d.ts +46 -0
- package/dist/portal/bridge/session-nonces.js +72 -0
- package/dist/portal/input/viewport-tools.d.ts +31 -0
- package/dist/portal/input/viewport-tools.js +106 -15
- package/dist/portal/pool/bounded-close.d.ts +23 -0
- package/dist/portal/pool/bounded-close.js +38 -0
- package/dist/portal/pool/browser-pool.d.ts +44 -39
- package/dist/portal/pool/browser-pool.js +247 -95
- package/dist/portal/pool/context-permissions.d.ts +9 -1
- package/dist/portal/pool/context-permissions.js +26 -0
- package/dist/portal/pool/lease-emulation.d.ts +37 -0
- package/dist/portal/pool/lease-emulation.js +29 -0
- package/dist/portal/pool/playwright-launcher.d.ts +6 -0
- package/dist/portal/pool/playwright-launcher.js +74 -7
- package/dist/portal/pool/pool-contract.d.ts +23 -1
- package/dist/portal/pool/pool-lease.d.ts +128 -0
- package/dist/portal/pool/pool-lease.js +49 -0
- package/dist/portal/pool/zero-install.d.ts +8 -1
- package/dist/portal/pool/zero-install.js +24 -2
- package/dist/portal/production/production-activation.d.ts +136 -0
- package/dist/portal/production/production-activation.js +173 -0
- package/dist/portal/production/production-drive.d.ts +52 -0
- package/dist/portal/production/production-drive.js +301 -0
- package/dist/portal/production/production-hello.d.ts +23 -0
- package/dist/portal/production/production-hello.js +23 -0
- package/dist/portal/realm/web-realm.js +42 -15
- package/dist/portal/session/control-envelope.d.ts +1 -1
- package/dist/portal/session/dev-server/served-document.d.ts +0 -5
- package/dist/portal/session/dev-server/served-document.js +6 -8
- package/dist/portal/session/facts/handshake-facts.d.ts +8 -1
- package/dist/portal/session/first-run-wiring.d.ts +3 -36
- package/dist/portal/session/first-run-wiring.js +10 -103
- package/dist/portal/session/live-control-tools.js +61 -19
- package/dist/portal/session/no-session-next-action.d.ts +11 -0
- package/dist/portal/session/no-session-next-action.js +40 -15
- package/dist/portal/session/no-session-watch.d.ts +7 -0
- package/dist/portal/session/no-session-watch.js +67 -23
- package/dist/portal/session/output-budget.d.ts +10 -0
- package/dist/portal/session/output-budget.js +16 -0
- package/dist/portal/session/presence/session-lease.d.ts +2 -0
- package/dist/portal/session/presence/session-lease.js +8 -0
- package/dist/portal/session/recorded-gaps.d.ts +15 -11
- package/dist/portal/session/recorded-gaps.js +39 -18
- package/dist/portal/session/serving-directory.d.ts +24 -2
- package/dist/portal/session/serving-directory.js +93 -5
- package/dist/portal/session/session-info.d.ts +26 -0
- package/dist/portal/session/session-info.js +20 -0
- package/dist/portal/session/session-manager.d.ts +35 -3
- package/dist/portal/session/session-manager.js +69 -14
- package/dist/portal/session/session-successor.js +1 -9
- package/dist/portal/session/session-tools.js +22 -4
- package/dist/portal/session/session.d.ts +10 -10
- package/dist/portal/session/session.js +37 -29
- package/dist/portal/session/zero-install-first-call.d.ts +7 -2
- package/dist/portal/session/zero-install-first-call.js +43 -20
- package/dist/start-wiring.d.ts +2 -2
- package/dist/start-wiring.js +17 -3
- package/dist/status-payload.d.ts +11 -1
- package/dist/status-payload.js +7 -1
- package/dist/surface/agent-presence-grace.d.ts +11 -0
- package/dist/surface/agent-presence-grace.js +34 -0
- package/dist/surface/http-server.d.ts +12 -1
- package/dist/surface/http-server.js +64 -15
- package/dist/surface/mcp/faults/mcp-outage.d.ts +12 -2
- package/dist/surface/mcp/faults/mcp-outage.js +19 -1
- package/dist/surface/mcp/mcp-proxy.d.ts +17 -74
- package/dist/surface/mcp/mcp-proxy.js +194 -267
- package/dist/surface/mcp/mcp.d.ts +20 -3
- package/dist/surface/mcp/mcp.js +88 -25
- package/dist/surface/mcp/proxy/drive-calls.d.ts +44 -0
- package/dist/surface/mcp/proxy/drive-calls.js +114 -5
- package/dist/surface/mcp/proxy/proxy-boot.d.ts +11 -0
- package/dist/surface/mcp/proxy/proxy-boot.js +1 -0
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +6 -0
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +14 -1
- package/dist/surface/mcp/proxy/proxy-handshake.d.ts +34 -2
- package/dist/surface/mcp/proxy/proxy-handshake.js +76 -8
- package/dist/surface/mcp/server-instructions.js +35 -16
- package/dist/surface/mcp/shared-params.d.ts +8 -0
- package/dist/surface/mcp/shared-params.js +15 -1
- package/dist/surface/mcp/sse-frame-parser.d.ts +11 -0
- package/dist/surface/mcp/sse-frame-parser.js +18 -0
- package/dist/surface/mcp/surface-vocabulary.d.ts +2 -0
- package/dist/surface/mcp/surface-vocabulary.js +13 -0
- package/dist/surface/mcp/tool-catalog-cache.d.ts +2 -0
- package/dist/surface/mcp/tool-catalog-cache.js +14 -4
- package/dist/surface/tools/account-tool.d.ts +4 -3
- package/dist/surface/tools/account-tool.js +20 -31
- package/dist/surface/tools/act/act-danger.d.ts +2 -0
- package/dist/surface/tools/act/act-danger.js +41 -14
- package/dist/surface/tools/act/act-observation.d.ts +11 -0
- package/dist/surface/tools/act/act-observation.js +33 -0
- package/dist/surface/tools/act/act-preflight.js +10 -3
- package/dist/surface/tools/act/act-sequence-retry.js +2 -1
- package/dist/surface/tools/act/act-target.js +19 -5
- package/dist/surface/tools/act/durable.js +1 -1
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +9 -7
- package/dist/surface/tools/act/navigation/navigate-arrival.js +11 -14
- package/dist/surface/tools/act/unsettled-find.d.ts +36 -0
- package/dist/surface/tools/act/unsettled-find.js +71 -0
- package/dist/surface/tools/act-sequence-tool.js +53 -24
- package/dist/surface/tools/act-tools.d.ts +2 -3
- package/dist/surface/tools/act-tools.js +49 -47
- package/dist/surface/tools/agent/compact-envelope.d.ts +2 -2
- package/dist/surface/tools/agent/for-the-agent.js +41 -3
- package/dist/surface/tools/assert/assert-grade.js +8 -3
- package/dist/surface/tools/assert/assert-verdict.js +4 -2
- package/dist/surface/tools/browser-tools.js +7 -4
- package/dist/surface/tools/chat-drives.js +50 -25
- package/dist/surface/tools/coverage-tools.js +1 -1
- package/dist/surface/tools/dynamic-tools.d.ts +8 -0
- package/dist/surface/tools/dynamic-tools.js +89 -34
- package/dist/surface/tools/empty-session-list.d.ts +18 -25
- package/dist/surface/tools/empty-session-list.js +67 -38
- package/dist/surface/tools/error-recovery.d.ts +17 -7
- package/dist/surface/tools/error-recovery.js +53 -20
- package/dist/surface/tools/explore-tools.d.ts +10 -0
- package/dist/surface/tools/explore-tools.js +72 -19
- package/dist/surface/tools/feedback-tools.js +23 -2
- package/dist/surface/tools/gap-novelty.d.ts +6 -0
- package/dist/surface/tools/gap-novelty.js +9 -0
- package/dist/surface/tools/gaps/capability-absences.js +4 -3
- package/dist/surface/tools/harness-explore.d.ts +1 -1
- package/dist/surface/tools/harness-explore.js +6 -4
- package/dist/surface/tools/harness-gate.d.ts +43 -21
- package/dist/surface/tools/harness-gate.js +55 -81
- package/dist/surface/tools/harness-script.d.ts +2 -0
- package/dist/surface/tools/harness-script.js +11 -6
- package/dist/surface/tools/harness-toolset.js +3 -4
- package/dist/surface/tools/invoke-tool.d.ts +0 -8
- package/dist/surface/tools/invoke-tool.js +164 -178
- package/dist/surface/tools/lease-hint.d.ts +9 -0
- package/dist/surface/tools/lease-hint.js +22 -3
- package/dist/surface/tools/lease-permissions.d.ts +1 -1
- package/dist/surface/tools/lease-readiness.d.ts +55 -0
- package/dist/surface/tools/lease-readiness.js +150 -2
- package/dist/surface/tools/lease-tools.d.ts +3 -75
- package/dist/surface/tools/lease-tools.js +124 -188
- package/dist/surface/tools/map-tools.d.ts +20 -0
- package/dist/surface/tools/map-tools.js +145 -0
- package/dist/surface/tools/merge-tools.js +11 -7
- package/dist/surface/tools/next-step.js +6 -3
- package/dist/surface/tools/observe-tools.js +46 -35
- package/dist/surface/tools/platform-moment.d.ts +3 -12
- package/dist/surface/tools/platform-moment.js +12 -24
- package/dist/surface/tools/read/complete-snapshot.d.ts +7 -1
- package/dist/surface/tools/read/complete-snapshot.js +13 -2
- package/dist/surface/tools/read/query-paginate.js +6 -0
- package/dist/surface/tools/read/query-shape.d.ts +9 -0
- package/dist/surface/tools/read/query-shape.js +15 -0
- package/dist/surface/tools/read/snapshot-unmounted-note.d.ts +1 -0
- package/dist/surface/tools/read/snapshot-unmounted-note.js +73 -0
- package/dist/surface/tools/read-tools.js +5 -5
- package/dist/surface/tools/real-input-attempt.d.ts +11 -0
- package/dist/surface/tools/real-input-attempt.js +206 -38
- package/dist/surface/tools/reconcile-tools.js +21 -8
- package/dist/surface/tools/reticle-result.d.ts +3 -0
- package/dist/surface/tools/reticle-result.js +8 -1
- package/dist/surface/tools/run-report.d.ts +22 -0
- package/dist/surface/tools/run-report.js +141 -0
- package/dist/surface/tools/session-ports.d.ts +57 -0
- package/dist/surface/tools/session-ports.js +102 -0
- package/dist/surface/tools/tool-kit.d.ts +58 -4
- package/dist/surface/tools/tool-kit.js +140 -6
- package/dist/surface/tools/tool-surface.d.ts +2 -0
- package/dist/surface/tools/tool-surface.js +45 -0
- package/dist/surface/tools/tools.js +49 -87
- package/dist/telemetry/automation-hint.d.ts +5 -0
- package/dist/telemetry/automation-hint.js +7 -0
- package/dist/telemetry/cli-telemetry.d.ts +22 -0
- package/dist/telemetry/cli-telemetry.js +80 -11
- package/dist/telemetry/daemon-telemetry.d.ts +3 -1
- package/dist/telemetry/daemon-telemetry.js +9 -2
- package/dist/telemetry/feedback-cli.d.ts +1 -1
- package/dist/telemetry/feedback-cli.js +8 -3
- package/dist/telemetry/feedback.d.ts +1 -1
- package/dist/telemetry/feedback.js +51 -4
- package/dist/telemetry/harness-run.d.ts +3 -3
- package/dist/telemetry/harness-run.js +18 -7
- package/dist/telemetry/hud-events.d.ts +1 -1
- package/dist/telemetry/hud-metrics.d.ts +6 -3
- package/dist/telemetry/hud-metrics.js +20 -8
- package/dist/telemetry/install-source.js +1 -1
- package/dist/telemetry/mcp-connection.d.ts +1 -1
- package/dist/telemetry/mcp-connection.js +21 -2
- package/dist/telemetry/onboarding-firsts.d.ts +6 -1
- package/dist/telemetry/onboarding-firsts.js +41 -1
- package/dist/telemetry/proxy-telemetry.d.ts +42 -0
- package/dist/telemetry/proxy-telemetry.js +70 -1
- package/dist/telemetry/session-metrics.d.ts +1 -3
- package/dist/telemetry/session-metrics.js +16 -12
- package/dist/telemetry/telemetry.d.ts +9 -1
- package/dist/telemetry/telemetry.js +16 -1
- package/dist/telemetry/verification-of.d.ts +3 -1
- package/dist/telemetry/verification-of.js +7 -1
- package/dist/wire-journal.js +12 -2
- package/docs/README.md +4 -2
- package/docs/actions.mdx +7 -6
- package/docs/adapters.md +2 -1
- package/docs/agent-cheatsheet.md +19 -5
- package/docs/architecture.md +13 -10
- package/docs/autodrive.md +6 -5
- package/docs/benchmarks.md +2 -2
- package/docs/capabilities.mdx +1 -1
- package/docs/cli/affected.mdx +1 -1
- package/docs/cli/cloud.mdx +6 -6
- package/docs/cli/doctor.mdx +3 -1
- package/docs/cli/feedback.mdx +11 -7
- package/docs/cli/gate.mdx +1 -1
- package/docs/cli/help.mdx +1 -1
- package/docs/cli/init.mdx +20 -16
- package/docs/cli/mcp.mdx +5 -2
- package/docs/cli/open.mdx +5 -2
- package/docs/cli/report.mdx +1 -1
- package/docs/cli/restart.mdx +1 -1
- package/docs/cli/rollback.mdx +4 -4
- package/docs/cli/serve.mdx +7 -5
- package/docs/cli/setup.mdx +1 -1
- package/docs/cli/status.mdx +6 -2
- package/docs/cli/stop.mdx +3 -3
- package/docs/cli/telemetry.mdx +5 -1
- package/docs/cli/try.mdx +8 -8
- package/docs/cli/update.mdx +8 -5
- package/docs/cli/verify.mdx +12 -5
- package/docs/cli/version.mdx +1 -1
- package/docs/cli.mdx +10 -6
- package/docs/debugging.md +1 -1
- package/docs/deploy-checks.md +8 -6
- package/docs/desktop.mdx +14 -18
- package/docs/enterprise.md +6 -6
- package/docs/environment.mdx +5 -3
- package/docs/faq.mdx +9 -6
- package/docs/first-drive.md +19 -9
- package/docs/flows.md +3 -4
- package/docs/for-agents.md +1 -1
- package/docs/frameworks.mdx +34 -11
- package/docs/getting-started.md +46 -29
- package/docs/http-transport.md +2 -2
- package/docs/human-control.md +8 -8
- package/docs/index.mdx +2 -2
- package/docs/install-agentic.mdx +18 -19
- package/docs/install-manual.mdx +8 -4
- package/docs/integration-patterns.md +5 -5
- package/docs/multi-agent-testing.md +7 -5
- package/docs/packages/browser.mdx +34 -2
- package/docs/packages/core.mdx +2 -1
- package/docs/packages/electron.mdx +15 -3
- package/docs/packages/eslint-plugin.mdx +1 -1
- package/docs/packages/next.mdx +17 -5
- package/docs/packages/react.mdx +8 -1
- package/docs/packages/server.mdx +3 -3
- package/docs/packages/tauri.mdx +3 -3
- package/docs/packages/vite-plugin.mdx +1 -1
- package/docs/packages.mdx +19 -6
- package/docs/platform-integration.md +12 -4
- package/docs/predicates.mdx +18 -7
- package/docs/production-mode.md +108 -0
- package/docs/prompt-capture.md +99 -0
- package/docs/quickstart.mdx +14 -12
- package/docs/recipes.mdx +9 -8
- package/docs/required-checks.md +3 -2
- package/docs/skill-file.mdx +4 -3
- package/docs/skill-setup.md +2 -2
- package/docs/skill.md +62 -70
- package/docs/state-files.md +1 -0
- package/docs/state-management.mdx +11 -9
- package/docs/system-map.md +14 -13
- package/docs/telemetry.md +32 -24
- package/docs/testing.md +9 -5
- package/docs/tools/act-and-wait.mdx +17 -13
- package/docs/tools/act-sequence.mdx +40 -66
- package/docs/tools/act.mdx +7 -5
- package/docs/tools/assert.mdx +1 -1
- package/docs/tools/look.mdx +1 -1
- package/docs/tools/navigate.mdx +1 -1
- package/docs/tools/network.mdx +13 -11
- package/docs/tools/overview.mdx +3 -3
- package/docs/tools/query.mdx +3 -3
- package/docs/tools/session-and-feedback.mdx +9 -6
- package/docs/tools/sessions.mdx +7 -0
- package/docs/tools/snapshot.mdx +9 -7
- package/docs/tools/tools-and-run.mdx +11 -10
- package/docs/tools/verify.mdx +1 -1
- package/docs/troubleshooting.mdx +21 -9
- package/docs/usage.md +58 -43
- package/docs/use-cases.mdx +2 -2
- package/docs/vs-chrome-devtools-mcp.mdx +5 -5
- package/docs/vs-playwright-mcp.mdx +5 -4
- package/docs/what-is-recorded.md +12 -7
- package/docs/what-is-reticle.mdx +2 -2
- package/docs/why-reticle.mdx +1 -1
- package/package.json +9 -12
- package/dist/command/dev/stale-issue-guard.d.ts +0 -34
- package/dist/command/dev/stale-issue-guard.js +0 -69
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reticle hook install|uninstall|status|prompt`: the prompt-capture hook, outside `init`.
|
|
3
|
+
*
|
|
4
|
+
* `init` installs the hook for the agents it finds. These are the same steps on their own: add it
|
|
5
|
+
* to a project wired before the hook existed, remove exactly what was added, or say per agent
|
|
6
|
+
* whether prompts are captured. `prompt` is what the agent hook itself runs.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { dirname, join } from 'node:path';
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
import { AGENT_FLAG, MCP_FALLBACK_AGENTS, PROMPT_CAPTURE_NOTICE, PROMPT_HOOK_AGENTS, PromptHookAgent, PromptHookStatus, claudeAvailableProbe, installPromptHook, promptHookInstalled, promptHookNpxOnly, promptHookTargets, uninstallPromptHook, } from '@reticlehq/init';
|
|
13
|
+
import { ReticleDir, ReticleEnv } from '@reticlehq/core';
|
|
14
|
+
import { discoverProjectConfigs } from '../config/config-discovery.js';
|
|
15
|
+
import { agentCaptureOff, agentCaptureOffIn, capturePrompt, hookAck } from './prompt-hook.js';
|
|
16
|
+
import { captureToolCall, toolHookAck } from './tool-hook.js';
|
|
17
|
+
import { AT_FLAG, PROMPT_WRITE_ACTION, TOOL_WRITE_ACTION, hookBinFor } from './hook-fast.js';
|
|
18
|
+
import { exportStructure } from './episode-structure.js';
|
|
19
|
+
const HookAction = {
|
|
20
|
+
PROMPT: 'prompt',
|
|
21
|
+
/** What the agent's post-tool hook runs: one of the agent's own tool calls. */
|
|
22
|
+
TOOL: 'tool',
|
|
23
|
+
/** The episodes, for a dataset: `--structure` is the text-free page structure per step. */
|
|
24
|
+
EXPORT: 'export',
|
|
25
|
+
INSTALL: 'install',
|
|
26
|
+
UNINSTALL: 'uninstall',
|
|
27
|
+
STATUS: 'status',
|
|
28
|
+
};
|
|
29
|
+
const STRUCTURE_FLAG = '--structure';
|
|
30
|
+
/** An agent hook that names none is Claude Code's: the stand-in this command replaces was. */
|
|
31
|
+
const DEFAULT_AGENT = PromptHookAgent.CLAUDE_CODE;
|
|
32
|
+
/** The payload is a few kilobytes. More than this is not a prompt payload, and is not read. */
|
|
33
|
+
const MAX_STDIN_BYTES = 1024 * 1024;
|
|
34
|
+
/** A hook whose agent never closes stdin must still end, and let the prompt through. */
|
|
35
|
+
const STDIN_WAIT_MS = 3_000;
|
|
36
|
+
const readText = (path) => {
|
|
37
|
+
try {
|
|
38
|
+
return readFileSync(path, 'utf8');
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const fileIo = { exists: existsSync, readFile: readText };
|
|
45
|
+
/** The project folder `cwd` belongs to: the nearest `.reticle.json` above it, else `cwd`. */
|
|
46
|
+
export function projectDirOf(cwd) {
|
|
47
|
+
return discoverProjectConfigs(cwd).found[0]?.directory ?? cwd;
|
|
48
|
+
}
|
|
49
|
+
export function claudeCliHere() {
|
|
50
|
+
const probe = claudeAvailableProbe();
|
|
51
|
+
return 0 === spawnSync(probe.command, probe.args, { stdio: 'ignore', timeout: 5_000 }).status;
|
|
52
|
+
}
|
|
53
|
+
/** The payload on stdin, bounded in size and in time. Never rejects. */
|
|
54
|
+
function readStdin() {
|
|
55
|
+
return new Promise((resolve) => {
|
|
56
|
+
let data = '';
|
|
57
|
+
const done = () => {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
resolve(data);
|
|
60
|
+
};
|
|
61
|
+
const timer = setTimeout(done, STDIN_WAIT_MS);
|
|
62
|
+
timer.unref();
|
|
63
|
+
process.stdin.setEncoding('utf8');
|
|
64
|
+
process.stdin.on('data', (chunk) => {
|
|
65
|
+
data += chunk;
|
|
66
|
+
if (MAX_STDIN_BYTES < data.length) {
|
|
67
|
+
data = '';
|
|
68
|
+
done();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
process.stdin.on('end', done);
|
|
72
|
+
process.stdin.on('error', done);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* One row per agent: does it capture prompts here, through its hook or the MCP instructions, or is
|
|
77
|
+
* capture off. `reticle doctor` prints the same rows.
|
|
78
|
+
*/
|
|
79
|
+
export function promptCaptureRows(projectDir, env, claudeCli, home = homedir()) {
|
|
80
|
+
if (agentCaptureOff(env)) {
|
|
81
|
+
return [`off on this machine (${ReticleEnv.PROMPT_CAPTURE})`];
|
|
82
|
+
}
|
|
83
|
+
if (agentCaptureOffIn(projectDir)) {
|
|
84
|
+
return ['off in this project ("promptCapture": false in .reticle.json)'];
|
|
85
|
+
}
|
|
86
|
+
// The project only, as `install` reads it: a row says what `install` would do here.
|
|
87
|
+
const targets = promptHookTargets(fileIo, projectDir, undefined, claudeCli, join);
|
|
88
|
+
// Where the bin can run directly (not Windows), an npx-only install is slower than it needs to be.
|
|
89
|
+
const direct = hookBinFor(home, process.platform) !== undefined;
|
|
90
|
+
const rows = targets.map((t) => {
|
|
91
|
+
if (!promptHookInstalled(t.spec, t.existing)) {
|
|
92
|
+
return `✗ ${t.spec.label}: no hook; run \`reticle hook install\``;
|
|
93
|
+
}
|
|
94
|
+
const slow = direct && promptHookNpxOnly(t.spec, t.existing)
|
|
95
|
+
? `, through npx (~400 ms slower per call); run \`reticle hook install\` to run it directly`
|
|
96
|
+
: '';
|
|
97
|
+
return `✓ ${t.spec.label}: hook installed (${t.path})${slow}${t.spec.activation === undefined ? '' : `. ${t.spec.activation}`}`;
|
|
98
|
+
});
|
|
99
|
+
rows.push(`MCP instructions only: ${MCP_FALLBACK_AGENTS.join(', ')}`);
|
|
100
|
+
return rows;
|
|
101
|
+
}
|
|
102
|
+
function install(cwd, print) {
|
|
103
|
+
const projectDir = projectDirOf(cwd);
|
|
104
|
+
// The project's own agents only. An agent installed on this machine is not one this repo uses.
|
|
105
|
+
const targets = promptHookTargets(fileIo, projectDir, undefined, claudeCliHere(), join);
|
|
106
|
+
if (0 === targets.length) {
|
|
107
|
+
print('No agent with a prompt hook is used here. The MCP instructions cover the others.');
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
let failed = 0;
|
|
111
|
+
for (const target of targets) {
|
|
112
|
+
const merged = installPromptHook(target.spec, target.existing, hookBinFor(homedir(), process.platform));
|
|
113
|
+
if (PromptHookStatus.ALREADY === merged.status) {
|
|
114
|
+
print(`✓ ${target.spec.label}: already installed (${target.path})`);
|
|
115
|
+
}
|
|
116
|
+
else if (PromptHookStatus.MANUAL === merged.status) {
|
|
117
|
+
failed++;
|
|
118
|
+
print(`✗ ${target.spec.label}: ${target.path} is not plain JSON, so it was left alone`);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
mkdirSync(dirname(target.path), { recursive: true });
|
|
122
|
+
writeFileSync(target.path, merged.content);
|
|
123
|
+
print(`✓ ${target.spec.label}: installed (${target.path})`);
|
|
124
|
+
}
|
|
125
|
+
if (target.spec.activation !== undefined) {
|
|
126
|
+
print(` ${target.spec.activation}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
print(PROMPT_CAPTURE_NOTICE);
|
|
130
|
+
return 0 === failed ? 0 : 1;
|
|
131
|
+
}
|
|
132
|
+
/** Remove our entry from every config any agent reads here. Other entries stay. */
|
|
133
|
+
function uninstall(cwd, print) {
|
|
134
|
+
const projectDir = projectDirOf(cwd);
|
|
135
|
+
let removed = 0;
|
|
136
|
+
for (const spec of PROMPT_HOOK_AGENTS) {
|
|
137
|
+
for (const rel of spec.paths) {
|
|
138
|
+
const path = join(projectDir, rel);
|
|
139
|
+
const out = uninstallPromptHook(spec, readText(path));
|
|
140
|
+
if (!out.changed) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
removed++;
|
|
144
|
+
if (null === out.content) {
|
|
145
|
+
rmSync(path, { force: true });
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
writeFileSync(path, out.content);
|
|
149
|
+
}
|
|
150
|
+
print(`✓ ${spec.label}: removed (${path})`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (0 === removed) {
|
|
154
|
+
print('No prompt-capture hook was installed here.');
|
|
155
|
+
}
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
export async function handleHook(argv, print = (line) => process.stdout.write(`${line}\n`)) {
|
|
159
|
+
const action = argv[0] ?? HookAction.STATUS;
|
|
160
|
+
const cwd = process.cwd();
|
|
161
|
+
// `prompt` normally never reaches here either: the fast path answers it and runs `prompt-write`.
|
|
162
|
+
if (HookAction.PROMPT === action || PROMPT_WRITE_ACTION === action) {
|
|
163
|
+
const at = argv.indexOf(AGENT_FLAG);
|
|
164
|
+
const agent = (-1 === at ? undefined : argv[at + 1]) ?? DEFAULT_AGENT;
|
|
165
|
+
// The fast path's writer is told when the prompt arrived; run directly, it is now.
|
|
166
|
+
const given = Number(argv[argv.indexOf(AT_FLAG) + 1]);
|
|
167
|
+
const arrived = -1 !== argv.indexOf(AT_FLAG) && Number.isFinite(given) ? given : Date.now();
|
|
168
|
+
capturePrompt(agent, await readStdin(), { cwd, env: process.env, now: () => arrived });
|
|
169
|
+
if (HookAction.PROMPT === action) {
|
|
170
|
+
process.stdout.write(hookAck(agent));
|
|
171
|
+
}
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
// `tool` normally never reaches here: `bin/reticle.js` answers it on the fast path and runs
|
|
175
|
+
// `tool-write`, the capture itself, in a detached child. Both write the same record.
|
|
176
|
+
if (HookAction.TOOL === action || TOOL_WRITE_ACTION === action) {
|
|
177
|
+
const at = argv.indexOf(AGENT_FLAG);
|
|
178
|
+
const agent = (-1 === at ? undefined : argv[at + 1]) ?? DEFAULT_AGENT;
|
|
179
|
+
const payload = await readStdin();
|
|
180
|
+
captureToolCall(agent, payload, { cwd, env: process.env, now: () => Date.now() });
|
|
181
|
+
if (HookAction.TOOL === action) {
|
|
182
|
+
process.stdout.write(toolHookAck(agent, payload));
|
|
183
|
+
}
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
if (HookAction.EXPORT === action) {
|
|
187
|
+
if (!argv.includes(STRUCTURE_FLAG)) {
|
|
188
|
+
print(`hook export needs ${STRUCTURE_FLAG}: the text-free page structure is the only export.`);
|
|
189
|
+
return 1;
|
|
190
|
+
}
|
|
191
|
+
const root = join(projectDirOf(cwd), ReticleDir.ROOT);
|
|
192
|
+
const summary = exportStructure(root, (line) => process.stdout.write(`${line}\n`));
|
|
193
|
+
process.stderr.write(`${String(summary.steps)} snapshot steps exported; ${String(summary.skippedCut)} cut short and skipped\n`);
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
if (HookAction.INSTALL === action) {
|
|
197
|
+
return install(cwd, print);
|
|
198
|
+
}
|
|
199
|
+
if (HookAction.UNINSTALL === action) {
|
|
200
|
+
return uninstall(cwd, print);
|
|
201
|
+
}
|
|
202
|
+
if (HookAction.STATUS === action) {
|
|
203
|
+
const rows = promptCaptureRows(projectDirOf(cwd), process.env, claudeCliHere());
|
|
204
|
+
for (const row of rows) {
|
|
205
|
+
print(row);
|
|
206
|
+
}
|
|
207
|
+
return 0;
|
|
208
|
+
}
|
|
209
|
+
print(`unknown hook action: ${action}. Use install, uninstall, status, prompt --agent <id>, tool --agent <id> or export --structure.`);
|
|
210
|
+
return 1;
|
|
211
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** The actions the detached writers run. Not for people: `hook tool` and `hook prompt` are. */
|
|
2
|
+
export declare const TOOL_WRITE_ACTION = "tool-write";
|
|
3
|
+
export declare const PROMPT_WRITE_ACTION = "prompt-write";
|
|
4
|
+
/** The prompt's arrival time, handed to its writer: a writer that finishes late is still ordered. */
|
|
5
|
+
export declare const AT_FLAG = "--at";
|
|
6
|
+
/**
|
|
7
|
+
* This CLI's bin as an agent hook should name it: `$HOME/...` when under the home folder, so no
|
|
8
|
+
* user name lands in a committed file; undefined on Windows, whose shells do not all read `||`
|
|
9
|
+
* and `$HOME`, so the hooks there stay on npx alone. See `directHookCommand` in `@reticlehq/init`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function hookBinFor(home: string, platform: string, bin?: string): string | undefined;
|
|
12
|
+
/** What the hook prints. Cursor and Gemini CLI read stdout as JSON; the rest need nothing. */
|
|
13
|
+
export declare function toolHookAck(agent: string, payload?: string): string;
|
|
14
|
+
/** What the prompt hook prints. Cursor reads `{ continue }`, Gemini CLI any JSON; the rest nothing. */
|
|
15
|
+
export declare function promptHookAck(agent: string): string;
|
|
16
|
+
interface FastIo {
|
|
17
|
+
readStdin: () => Promise<string>;
|
|
18
|
+
write: (text: string) => void;
|
|
19
|
+
/** Start the writer, detached, with `input` on its stdin. Resolves once the input is handed over. */
|
|
20
|
+
spawnWriter: (args: readonly string[], input: string) => Promise<void>;
|
|
21
|
+
now?: () => number;
|
|
22
|
+
}
|
|
23
|
+
export declare function runToolHookFast(argv: readonly string[], io: FastIo): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* The prompt hook: let the prompt through at once, keep it in a detached writer. The writer gets
|
|
26
|
+
* the arrival time, so an older prompt never replaces a newer one however the writers finish.
|
|
27
|
+
*/
|
|
28
|
+
export declare function runPromptHookFast(argv: readonly string[], io: FastIo): Promise<void>;
|
|
29
|
+
/** The process entry `bin/reticle.js` calls for `hook tool` or `hook prompt`. Always exits 0. */
|
|
30
|
+
export declare function mainHookFast(action: string, argv: readonly string[]): Promise<void>;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The post-tool hook's fast path: answer at once, write in a detached child.
|
|
3
|
+
*
|
|
4
|
+
* Cursor and Gemini CLI wait for a hook to exit, and document no background option. Through the
|
|
5
|
+
* full CLI the hook took p50 ~300 ms per call (100 synthetic payloads each), almost all of it
|
|
6
|
+
* loading modules the hook never uses. `bin/reticle.js` sends `hook tool` here before the CLI
|
|
7
|
+
* loads. This module imports only Node built-ins: it reads the payload, prints the agent's answer,
|
|
8
|
+
* pipes the payload to a detached `hook tool-write` (the real capture, `tool-hook.ts`), and returns.
|
|
9
|
+
*
|
|
10
|
+
* Kept free of `@reticlehq/*` imports on purpose: every import here is paid on every tool call.
|
|
11
|
+
*/
|
|
12
|
+
import { spawn } from 'node:child_process';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
/** The actions the detached writers run. Not for people: `hook tool` and `hook prompt` are. */
|
|
15
|
+
export const TOOL_WRITE_ACTION = 'tool-write';
|
|
16
|
+
export const PROMPT_WRITE_ACTION = 'prompt-write';
|
|
17
|
+
/** The prompt's arrival time, handed to its writer: a writer that finishes late is still ordered. */
|
|
18
|
+
export const AT_FLAG = '--at';
|
|
19
|
+
const HOOK_COMMAND = 'hook';
|
|
20
|
+
const PROMPT_ACTION = 'prompt';
|
|
21
|
+
const WINDOWS = 'win32';
|
|
22
|
+
const CURSOR = 'cursor';
|
|
23
|
+
const GEMINI = 'gemini';
|
|
24
|
+
const CODEX = 'codex';
|
|
25
|
+
/** Read without parsing: the fast path pays for every byte of work on every call. */
|
|
26
|
+
const CODEX_STOP = /"hook_event_name"\s*:\s*"Stop"/;
|
|
27
|
+
/** The payload is a few kilobytes. More than this is not a hook payload, and is not read. */
|
|
28
|
+
const MAX_STDIN_BYTES = 1024 * 1024;
|
|
29
|
+
/** An agent that never closes stdin must still get its answer. */
|
|
30
|
+
const STDIN_WAIT_MS = 3_000;
|
|
31
|
+
/** `bin/reticle.js`, from `dist/command/cli/hook/hook-fast.js`. */
|
|
32
|
+
const BIN = fileURLToPath(new URL('../../../../bin/reticle.js', import.meta.url));
|
|
33
|
+
/**
|
|
34
|
+
* This CLI's bin as an agent hook should name it: `$HOME/...` when under the home folder, so no
|
|
35
|
+
* user name lands in a committed file; undefined on Windows, whose shells do not all read `||`
|
|
36
|
+
* and `$HOME`, so the hooks there stay on npx alone. See `directHookCommand` in `@reticlehq/init`.
|
|
37
|
+
*/
|
|
38
|
+
export function hookBinFor(home, platform, bin = BIN) {
|
|
39
|
+
if (WINDOWS === platform) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
const under = 0 < home.length && bin.startsWith(`${home}/`);
|
|
43
|
+
return under ? `$HOME${bin.slice(home.length)}` : bin;
|
|
44
|
+
}
|
|
45
|
+
/** What the hook prints. Cursor and Gemini CLI read stdout as JSON; the rest need nothing. */
|
|
46
|
+
export function toolHookAck(agent, payload = '') {
|
|
47
|
+
if (CURSOR === agent || GEMINI === agent) {
|
|
48
|
+
return '{}\n';
|
|
49
|
+
}
|
|
50
|
+
// Codex's Stop rejects plain text and wants JSON when it exits 0; its other hooks need nothing.
|
|
51
|
+
return CODEX === agent && CODEX_STOP.test(payload) ? '{}\n' : '';
|
|
52
|
+
}
|
|
53
|
+
/** What the prompt hook prints. Cursor reads `{ continue }`, Gemini CLI any JSON; the rest nothing. */
|
|
54
|
+
export function promptHookAck(agent) {
|
|
55
|
+
if (CURSOR === agent) {
|
|
56
|
+
return `${JSON.stringify({ continue: true })}\n`;
|
|
57
|
+
}
|
|
58
|
+
return GEMINI === agent ? '{}\n' : '';
|
|
59
|
+
}
|
|
60
|
+
const agentOf = (argv) => {
|
|
61
|
+
const at = argv.indexOf('--agent');
|
|
62
|
+
return (-1 === at ? undefined : argv[at + 1]) ?? 'claude-code';
|
|
63
|
+
};
|
|
64
|
+
export async function runToolHookFast(argv, io) {
|
|
65
|
+
const agent = agentOf(argv);
|
|
66
|
+
const payload = await io.readStdin();
|
|
67
|
+
io.write(toolHookAck(agent, payload));
|
|
68
|
+
try {
|
|
69
|
+
await io.spawnWriter([HOOK_COMMAND, TOOL_WRITE_ACTION, ...argv], payload);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// A lost record is better than an agent told its tool failed.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The prompt hook: let the prompt through at once, keep it in a detached writer. The writer gets
|
|
77
|
+
* the arrival time, so an older prompt never replaces a newer one however the writers finish.
|
|
78
|
+
*/
|
|
79
|
+
export async function runPromptHookFast(argv, io) {
|
|
80
|
+
const arrived = (io.now ?? Date.now)();
|
|
81
|
+
const payload = await io.readStdin();
|
|
82
|
+
io.write(promptHookAck(agentOf(argv)));
|
|
83
|
+
try {
|
|
84
|
+
await io.spawnWriter([HOOK_COMMAND, PROMPT_WRITE_ACTION, ...argv, AT_FLAG, String(arrived)], payload);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// A prompt that is not kept must still go through.
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function readStdin() {
|
|
91
|
+
return new Promise((resolve) => {
|
|
92
|
+
let data = '';
|
|
93
|
+
const done = () => {
|
|
94
|
+
clearTimeout(timer);
|
|
95
|
+
resolve(data);
|
|
96
|
+
};
|
|
97
|
+
const timer = setTimeout(done, STDIN_WAIT_MS);
|
|
98
|
+
timer.unref();
|
|
99
|
+
process.stdin.setEncoding('utf8');
|
|
100
|
+
process.stdin.on('data', (chunk) => {
|
|
101
|
+
data += chunk;
|
|
102
|
+
if (MAX_STDIN_BYTES < data.length) {
|
|
103
|
+
data = '';
|
|
104
|
+
done();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
process.stdin.on('end', done);
|
|
108
|
+
process.stdin.on('error', done);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function spawnWriter(args, input) {
|
|
112
|
+
return new Promise((resolve, reject) => {
|
|
113
|
+
const child = spawn(process.execPath, [BIN, ...args], {
|
|
114
|
+
detached: true,
|
|
115
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
116
|
+
windowsHide: true,
|
|
117
|
+
});
|
|
118
|
+
child.on('error', reject);
|
|
119
|
+
child.stdin.end(input, () => {
|
|
120
|
+
child.unref();
|
|
121
|
+
resolve();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/** The process entry `bin/reticle.js` calls for `hook tool` or `hook prompt`. Always exits 0. */
|
|
126
|
+
export async function mainHookFast(action, argv) {
|
|
127
|
+
const io = { readStdin, write: (text) => process.stdout.write(text), spawnWriter };
|
|
128
|
+
if (PROMPT_ACTION === action) {
|
|
129
|
+
await runPromptHookFast(argv, io);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
await runToolHookFast(argv, io);
|
|
133
|
+
}
|
|
134
|
+
// Exit only once the answer is flushed: an exit can drop a pipe write still in flight.
|
|
135
|
+
process.stdout.write('', () => process.exit(0));
|
|
136
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export { agentCaptureOff, agentCaptureOffIn } from '../../../memory/intent/record-request.js';
|
|
2
|
+
/**
|
|
3
|
+
* The prompt and the folder it was sent from, read from one agent's payload.
|
|
4
|
+
*
|
|
5
|
+
* Every agent sends `prompt` except Windsurf (`tool_info.user_prompt`). Every agent sends `cwd`
|
|
6
|
+
* except Cursor (`workspace_roots`) and Windsurf (none: its hook runs in the workspace root).
|
|
7
|
+
*/
|
|
8
|
+
export declare function promptFromPayload(agent: string, payload: unknown, fallbackCwd: string, env: NodeJS.ProcessEnv): {
|
|
9
|
+
prompt: string | undefined;
|
|
10
|
+
cwd: string;
|
|
11
|
+
};
|
|
12
|
+
/** What the prompt hook prints; one table with the fast path's. */
|
|
13
|
+
export { promptHookAck as hookAck } from './hook-fast.js';
|
|
14
|
+
/**
|
|
15
|
+
* The `.reticle` folders a prompt sent from `cwd` belongs in.
|
|
16
|
+
*
|
|
17
|
+
* The nearest project above `cwd`, and every app below it that declares the same project id. In a
|
|
18
|
+
* monorepo `init` writes the app's config at the root too, and the app's runs read the request
|
|
19
|
+
* from the APP's `.reticle`, so a prompt kept only at the root is a prompt no run ever carries.
|
|
20
|
+
*/
|
|
21
|
+
export declare function captureRoots(cwd: string): string[];
|
|
22
|
+
interface CaptureIo {
|
|
23
|
+
readonly cwd: string;
|
|
24
|
+
readonly env: NodeJS.ProcessEnv;
|
|
25
|
+
readonly now: () => number;
|
|
26
|
+
}
|
|
27
|
+
/** Keep one prompt. Never throws: the folders it wrote to, or none. */
|
|
28
|
+
export declare function capturePrompt(agent: string, raw: string, io: CaptureIo): {
|
|
29
|
+
written: string[];
|
|
30
|
+
};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reticle hook prompt --agent <id>`: what an agent's prompt-submit hook runs.
|
|
3
|
+
*
|
|
4
|
+
* The user's request is what a verdict is checked against, and an agent relays it only when it
|
|
5
|
+
* remembers to. So the agent's own hook hands it over, before the model reads it. This writes, in
|
|
6
|
+
* the project's `.reticle/`:
|
|
7
|
+
*
|
|
8
|
+
* request.json the newest prompt, in the shape `reticle_intent { declare, request }` writes;
|
|
9
|
+
* a run written soon after carries it, so it reaches the user's own linked
|
|
10
|
+
* workspace with that run when the project shares requests;
|
|
11
|
+
* requests.jsonl every prompt, one line each, oldest first.
|
|
12
|
+
*
|
|
13
|
+
* Secrets are redacted before anything is written. It stays offline, finishes fast, writes nothing
|
|
14
|
+
* outside `.reticle/`, and never blocks the prompt: every failure is exit 0 and nothing written.
|
|
15
|
+
* Nothing here reaches telemetry: `cli.ts` runs this before it reports the command.
|
|
16
|
+
*/
|
|
17
|
+
import { closeSync, constants, existsSync, fstatSync, ftruncateSync, mkdirSync, openSync, readFileSync, renameSync, writeFileSync, writeSync, } from 'node:fs';
|
|
18
|
+
import { dirname, isAbsolute, join, relative } from 'node:path';
|
|
19
|
+
import { ReticleDir } from '@reticlehq/core';
|
|
20
|
+
import { PromptHookAgent } from '@reticlehq/init';
|
|
21
|
+
import { PromptContextSchema } from '@reticlehq/core/artifacts';
|
|
22
|
+
import { discoverProjectConfigs } from '../config/config-discovery.js';
|
|
23
|
+
import { MAX_REQUEST, agentCaptureOff, agentCaptureOffIn, promptContextFor, redactSecrets, sharesRequests, } from '../../../memory/intent/record-request.js';
|
|
24
|
+
export { agentCaptureOff, agentCaptureOffIn } from '../../../memory/intent/record-request.js';
|
|
25
|
+
import { readTextOrUndefined } from '../../../memory/project/fs/fs-port.js';
|
|
26
|
+
import { WORKSPACE_GITIGNORE, workspaceGitignore, } from '../../../memory/journal/on-disk/workspace-gitignore.js';
|
|
27
|
+
/** A hook fired twice for one prompt (VS Code and Copilot CLI read one file) keeps one copy. */
|
|
28
|
+
const DUPLICATE_WINDOW_MS = 10_000;
|
|
29
|
+
/** The history moves aside once it passes this size, so `.reticle/` cannot grow without end. */
|
|
30
|
+
const MAX_HISTORY_BYTES = 2 * 1024 * 1024;
|
|
31
|
+
/**
|
|
32
|
+
* The tags an agent harness wraps around a prompt it wrote for itself: a background task that
|
|
33
|
+
* finished, a slash command, a shell escape, a reminder. Only these names. A user prompt that starts
|
|
34
|
+
* with a tag of their own (`<Button> must be disabled`) is a real request, and is kept.
|
|
35
|
+
*/
|
|
36
|
+
const HARNESS_TAGS = [
|
|
37
|
+
'system-reminder',
|
|
38
|
+
'task-notification',
|
|
39
|
+
'agent-message',
|
|
40
|
+
'cross-session-message',
|
|
41
|
+
'teammate-message',
|
|
42
|
+
'command-name',
|
|
43
|
+
'command-message',
|
|
44
|
+
'command-args',
|
|
45
|
+
'local-command-stdout',
|
|
46
|
+
'local-command-stderr',
|
|
47
|
+
'local-command-caveat',
|
|
48
|
+
'bash-input',
|
|
49
|
+
'bash-stdout',
|
|
50
|
+
'bash-stderr',
|
|
51
|
+
'persisted-output',
|
|
52
|
+
'user-prompt-submit-hook',
|
|
53
|
+
];
|
|
54
|
+
const HARNESS_PROMPT = new RegExp(`^<(?:${HARNESS_TAGS.join('|')})[\\s>]`);
|
|
55
|
+
/** Where each agent names its project when the payload does not. */
|
|
56
|
+
const PROJECT_DIR_ENV = ['CLAUDE_PROJECT_DIR', 'CURSOR_PROJECT_DIR', 'GEMINI_PROJECT_DIR'];
|
|
57
|
+
const JSON_INDENT = 2;
|
|
58
|
+
const isObject = (value) => 'object' === typeof value && null !== value && !Array.isArray(value);
|
|
59
|
+
const text = (value) => 'string' === typeof value && 0 < value.trim().length ? value : undefined;
|
|
60
|
+
/**
|
|
61
|
+
* The prompt and the folder it was sent from, read from one agent's payload.
|
|
62
|
+
*
|
|
63
|
+
* Every agent sends `prompt` except Windsurf (`tool_info.user_prompt`). Every agent sends `cwd`
|
|
64
|
+
* except Cursor (`workspace_roots`) and Windsurf (none: its hook runs in the workspace root).
|
|
65
|
+
*/
|
|
66
|
+
export function promptFromPayload(agent, payload, fallbackCwd, env) {
|
|
67
|
+
const body = isObject(payload) ? payload : {};
|
|
68
|
+
const toolInfo = isObject(body['tool_info']) ? body['tool_info'] : {};
|
|
69
|
+
const prompt = PromptHookAgent.WINDSURF === agent
|
|
70
|
+
? (text(toolInfo['user_prompt']) ?? text(body['prompt']))
|
|
71
|
+
: (text(body['prompt']) ?? text(toolInfo['user_prompt']));
|
|
72
|
+
const roots = Array.isArray(body['workspace_roots']) ? body['workspace_roots'] : [];
|
|
73
|
+
const cwd = text(body['cwd']) ??
|
|
74
|
+
text(roots[0]) ??
|
|
75
|
+
PROJECT_DIR_ENV.map((name) => text(env[name])).find((dir) => dir !== undefined) ??
|
|
76
|
+
fallbackCwd;
|
|
77
|
+
return { prompt, cwd };
|
|
78
|
+
}
|
|
79
|
+
/** What the prompt hook prints; one table with the fast path's. */
|
|
80
|
+
export { promptHookAck as hookAck } from './hook-fast.js';
|
|
81
|
+
/**
|
|
82
|
+
* The `.reticle` folders a prompt sent from `cwd` belongs in.
|
|
83
|
+
*
|
|
84
|
+
* The nearest project above `cwd`, and every app below it that declares the same project id. In a
|
|
85
|
+
* monorepo `init` writes the app's config at the root too, and the app's runs read the request
|
|
86
|
+
* from the APP's `.reticle`, so a prompt kept only at the root is a prompt no run ever carries.
|
|
87
|
+
*/
|
|
88
|
+
export function captureRoots(cwd) {
|
|
89
|
+
if (!existsSync(cwd)) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
const { found } = discoverProjectConfigs(cwd);
|
|
93
|
+
const nearest = found.find((config) => isWithin(config.directory, cwd));
|
|
94
|
+
if (nearest === undefined) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
const apps = found.filter((config) => config.directory !== nearest.directory &&
|
|
98
|
+
config.projectId !== undefined &&
|
|
99
|
+
config.projectId === nearest.projectId &&
|
|
100
|
+
isWithin(nearest.directory, config.directory));
|
|
101
|
+
return [nearest, ...apps].map((config) => join(config.directory, ReticleDir.ROOT));
|
|
102
|
+
}
|
|
103
|
+
/** Is `inner` the folder `outer` or inside it? */
|
|
104
|
+
function isWithin(outer, inner) {
|
|
105
|
+
const rel = relative(outer, inner);
|
|
106
|
+
return '' === rel || (!rel.startsWith('..') && !isAbsolute(rel));
|
|
107
|
+
}
|
|
108
|
+
/** Keep one prompt. Never throws: the folders it wrote to, or none. */
|
|
109
|
+
export function capturePrompt(agent, raw, io) {
|
|
110
|
+
try {
|
|
111
|
+
if (agentCaptureOff(io.env)) {
|
|
112
|
+
return { written: [] };
|
|
113
|
+
}
|
|
114
|
+
const { prompt, cwd } = promptFromPayload(agent, JSON.parse(raw), io.cwd, io.env);
|
|
115
|
+
if (prompt === undefined || HARNESS_PROMPT.test(prompt.trim())) {
|
|
116
|
+
return { written: [] };
|
|
117
|
+
}
|
|
118
|
+
const written = [];
|
|
119
|
+
for (const root of captureRoots(cwd)) {
|
|
120
|
+
if (keepIn(root, agent, prompt.trim(), io.now())) {
|
|
121
|
+
written.push(root);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { written };
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return { written: [] };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Write one prompt into one `.reticle`. False when the project opted out, or it is a repeat. */
|
|
131
|
+
function keepIn(root, agent, prompt, at) {
|
|
132
|
+
const projectDir = dirname(root);
|
|
133
|
+
if (agentCaptureOffIn(projectDir)) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
const requestPath = join(root, ReticleDir.REQUEST_FILE);
|
|
137
|
+
const request = redactSecrets(prompt, MAX_REQUEST);
|
|
138
|
+
if (repeats(requestPath, request, at)) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
const context = promptContextFor(prompt, [], at, sharesRequests(projectDir, root));
|
|
142
|
+
mkdirSync(root, { recursive: true });
|
|
143
|
+
// The hook can run before any daemon has written `.reticle/.gitignore`: write it, or add the
|
|
144
|
+
// prompt files to an older one, before a prompt lands where `git add -A` would take it.
|
|
145
|
+
const ignorePath = join(root, WORKSPACE_GITIGNORE);
|
|
146
|
+
const ignore = workspaceGitignore(readTextOrUndefined(ignorePath));
|
|
147
|
+
if (ignore !== undefined) {
|
|
148
|
+
writeFileSync(ignorePath, ignore);
|
|
149
|
+
}
|
|
150
|
+
// Writers run detached and can finish out of order: the request is the NEWEST prompt, so an
|
|
151
|
+
// older one that lands late goes to the history only.
|
|
152
|
+
if (!newerHeld(requestPath, at)) {
|
|
153
|
+
const tmp = `${requestPath}.${String(process.pid)}.tmp`;
|
|
154
|
+
writeFileSync(tmp, `${JSON.stringify(context, null, JSON_INDENT)}\n`);
|
|
155
|
+
renameSync(tmp, requestPath);
|
|
156
|
+
}
|
|
157
|
+
const historyPath = join(root, ReticleDir.REQUESTS_LOG_FILE);
|
|
158
|
+
// ponytail: one rotated file; older history is dropped once a second one fills.
|
|
159
|
+
// Every step goes through one open file, so the path cannot change between a check and a write.
|
|
160
|
+
// It is not opened to append: Windows cannot truncate a file that is open to append. Two hooks
|
|
161
|
+
// that write in the same instant can lose one line of history.
|
|
162
|
+
const fd = openSync(historyPath, constants.O_RDWR | constants.O_CREAT);
|
|
163
|
+
try {
|
|
164
|
+
let end = fstatSync(fd).size;
|
|
165
|
+
if (MAX_HISTORY_BYTES < end) {
|
|
166
|
+
writeFileSync(join(root, ReticleDir.REQUESTS_LOG_ROTATED_FILE), readFileSync(fd));
|
|
167
|
+
ftruncateSync(fd, 0);
|
|
168
|
+
end = 0;
|
|
169
|
+
}
|
|
170
|
+
writeSync(fd, `${JSON.stringify({ at, agent, request: context.request })}\n`, end);
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
closeSync(fd);
|
|
174
|
+
}
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
/** The request file already holds a prompt that arrived after `at`. */
|
|
178
|
+
function newerHeld(requestPath, at) {
|
|
179
|
+
try {
|
|
180
|
+
const held = PromptContextSchema.safeParse(JSON.parse(readFileSync(requestPath, 'utf8')));
|
|
181
|
+
return held.success && held.data.at !== undefined && held.data.at > at;
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/** The same prompt was kept a moment ago: a second hook for the same submit. */
|
|
188
|
+
function repeats(requestPath, request, at) {
|
|
189
|
+
try {
|
|
190
|
+
const held = PromptContextSchema.safeParse(JSON.parse(readFileSync(requestPath, 'utf8')));
|
|
191
|
+
if (!held.success || held.data.at === undefined) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
return held.data.request === request && at - held.data.at < DUPLICATE_WINDOW_MS;
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** The `kind` of a record this hook writes, beside the daemon's `start` and `call`. */
|
|
2
|
+
export declare const AGENT_TOOL_RECORD: "agent_tool";
|
|
3
|
+
/** The `kind` of a record that only names the agent's model (Claude Code's session start). */
|
|
4
|
+
export declare const AGENT_MODEL_RECORD: "agent_model";
|
|
5
|
+
/** The `kind` of the agent's closing message at the end of a turn. */
|
|
6
|
+
export declare const AGENT_STOP_RECORD: "agent_stop";
|
|
7
|
+
/** A result keeps this many characters; its full size is kept beside it. */
|
|
8
|
+
export declare const MAX_TOOL_TEXT = 4000;
|
|
9
|
+
type Json = Record<string, unknown>;
|
|
10
|
+
/** One record from a post-tool payload, or from a payload that only names the model. */
|
|
11
|
+
export declare function toolRecordFrom(agent: string, payload: unknown, now: number, base: string): Json | undefined;
|
|
12
|
+
export { toolHookAck } from './hook-fast.js';
|
|
13
|
+
interface CaptureIo {
|
|
14
|
+
readonly cwd: string;
|
|
15
|
+
readonly env: NodeJS.ProcessEnv;
|
|
16
|
+
readonly now: () => number;
|
|
17
|
+
}
|
|
18
|
+
/** Keep one tool call. Never throws: the `.reticle` roots it wrote to, or none. */
|
|
19
|
+
export declare function captureToolCall(agent: string, raw: string, io: CaptureIo): {
|
|
20
|
+
written: string[];
|
|
21
|
+
};
|