@fusengine/harness 0.1.89 → 0.1.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/claude/index.d.mts +1 -1
- package/dist/adapters/claude/index.mjs +1 -1
- package/dist/adapters/cline/index.mjs +1 -1
- package/dist/adapters/codex/index.d.mts +2 -2
- package/dist/adapters/codex/index.mjs +5 -4
- package/dist/adapters/cursor/index.d.mts +15 -28
- package/dist/adapters/cursor/index.mjs +35 -46
- package/dist/adapters/gemini/index.mjs +1 -1
- package/dist/adapters/hermes/index.d.mts +1 -1
- package/dist/adapters/hermes/index.mjs +1 -1
- package/dist/adapters/kimi/index.d.mts +1 -1
- package/dist/adapters/kimi/index.mjs +1 -1
- package/dist/{claude-5bC8TDAz.mjs → claude-Ckv2_TgP.mjs} +3 -43
- package/dist/cli/bin.mjs +429 -16
- package/dist/cli/index.mjs +1 -1
- package/dist/command-string-CALMTnwN.mjs +42 -0
- package/dist/{evaluate-C1VSI5pb.mjs → evaluate-CMiIqHeH.mjs} +261 -13
- package/dist/{handle-DHhukRdh.mjs → handle-C43gA-Pr.mjs} +737 -150
- package/dist/{hermes-kP6NUQlG.mjs → hermes-B9-p_3IF.mjs} +3 -2
- package/dist/{index-DBgnIZn9.d.mts → index-B8PG82su.d.mts} +1 -1
- package/dist/{index-D4C2-IIc.d.mts → index-SmKYkk2N.d.mts} +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/init/index.d.mts +1 -1
- package/dist/init/index.mjs +1 -1
- package/dist/{kimi-D6X7b8wF.mjs → kimi-G2wcSh5-.mjs} +3 -2
- package/dist/normalize-BjG6unTj.mjs +317 -0
- package/dist/permission-mode-B-SFyR0X.mjs +226 -0
- package/dist/policy/index.d.mts +1 -1
- package/dist/policy/index.mjs +2 -2
- package/dist/prompt/index.d.mts +1 -1
- package/dist/{run-CdIlUtjv.mjs → run-DkrzC0gb.mjs} +1 -1
- package/dist/{run-TAXNRSpD.mjs → run-GHAMqcOn.mjs} +26 -3
- package/dist/runtime/index.d.mts +27 -16
- package/dist/runtime/index.mjs +2 -2
- package/dist/{types-C0-igRKr.d.mts → types-Bh0jEF3_.d.mts} +2 -0
- package/dist/{validate-DZFtAbLP.mjs → validate-KjZ1X9tH.mjs} +1 -1
- package/package.json +3 -2
- package/src/adapters/claude/index.ts +92 -0
- package/src/adapters/cline/index.ts +36 -0
- package/src/adapters/codex/apply-patch.ts +94 -0
- package/src/adapters/codex/index.ts +84 -0
- package/src/adapters/codex/permission-mode.ts +16 -0
- package/src/adapters/cursor/context.ts +46 -0
- package/src/adapters/cursor/events.ts +38 -0
- package/src/adapters/cursor/index.ts +43 -0
- package/src/adapters/cursor/interfaces/types.ts +88 -0
- package/src/adapters/cursor/native-response.ts +158 -0
- package/src/adapters/cursor/normalize.ts +123 -0
- package/src/adapters/cursor/respond.ts +123 -0
- package/src/adapters/gemini/index.ts +36 -0
- package/src/adapters/hermes/index.ts +55 -0
- package/src/adapters/hermes/interfaces/types.ts +26 -0
- package/src/adapters/kimi/index.ts +96 -0
- package/src/adapters/kimi/interfaces/types.ts +55 -0
- package/src/cache/compact.ts +55 -0
- package/src/cache/index.ts +5 -0
- package/src/cache/io.ts +38 -0
- package/src/cache/mcp-response.ts +39 -0
- package/src/cache/mcp-store.ts +70 -0
- package/src/cache/store.ts +93 -0
- package/src/changelog/fetch.ts +79 -0
- package/src/cli/bin.ts +100 -0
- package/src/cli/cursor-event-scanner.ts +175 -0
- package/src/cli/cursor-stdin-reader.ts +65 -0
- package/src/cli/doctor.ts +96 -0
- package/src/cli/hook-io.ts +130 -0
- package/src/cli/hook-sound.ts +49 -0
- package/src/cli/index.ts +1 -0
- package/src/cli/json-primitive-scanner.ts +83 -0
- package/src/cli/run.ts +36 -0
- package/src/cli/scope.ts +31 -0
- package/src/codex-rules/build.ts +63 -0
- package/src/codex-rules/index.ts +8 -0
- package/src/codex-rules/mapping.ts +42 -0
- package/src/codex-rules/rules/absolute-paths.ts +52 -0
- package/src/codex-rules/rules/ask.ts +44 -0
- package/src/codex-rules/rules/chmod-ask.ts +57 -0
- package/src/codex-rules/rules/dd.ts +23 -0
- package/src/codex-rules/rules/diskutil.ts +33 -0
- package/src/codex-rules/rules/mkfs.ts +42 -0
- package/src/codex-rules/rules/privilege.ts +49 -0
- package/src/codex-rules/rules/remote-fetch.ts +31 -0
- package/src/codex-rules/rules/rm-variants.ts +58 -0
- package/src/codex-rules/rules/single-commands.ts +57 -0
- package/src/codex-rules/skip-list.ts +25 -0
- package/src/codex-rules/starlark.ts +46 -0
- package/src/codex-rules/types.ts +36 -0
- package/src/config/dotenv.ts +55 -0
- package/src/config/env.ts +10 -0
- package/src/config/home-dir.ts +36 -0
- package/src/config/index.ts +5 -0
- package/src/config/layout.ts +52 -0
- package/src/config/limits.ts +56 -0
- package/src/config/ttl.ts +30 -0
- package/src/detect/harness.ts +73 -0
- package/src/detect/index.ts +1 -0
- package/src/detect/interfaces/types.ts +18 -0
- package/src/freshness/agent-evidence-record.ts +95 -0
- package/src/freshness/agent-evidence.ts +95 -0
- package/src/freshness/codex-spawn-evidence.ts +91 -0
- package/src/freshness/doc-helpers.ts +90 -0
- package/src/freshness/evidence-harvest-io.ts +72 -0
- package/src/freshness/evidence-harvest.ts +61 -0
- package/src/freshness/explore-tools.ts +95 -0
- package/src/freshness/index.ts +2 -0
- package/src/freshness/query-framework.ts +73 -0
- package/src/freshness/ref-evidence.ts +49 -0
- package/src/freshness/ref-journal.ts +98 -0
- package/src/freshness/trivial-edit-counter.ts +30 -0
- package/src/index.ts +11 -0
- package/src/init/index.ts +2 -0
- package/src/init/run.ts +32 -0
- package/src/init/templates.ts +75 -0
- package/src/memory/gitignore.ts +14 -0
- package/src/memory/index.ts +3 -0
- package/src/memory/registry.ts +39 -0
- package/src/memory/session-roots.ts +99 -0
- package/src/memory/state.ts +58 -0
- package/src/policy/apex-authorization.ts +100 -0
- package/src/policy/apex-gates.ts +91 -0
- package/src/policy/apex-target-steps.ts +73 -0
- package/src/policy/apex-target.ts +49 -0
- package/src/policy/apex-task-context.ts +81 -0
- package/src/policy/apex.ts +94 -0
- package/src/policy/cartographer/build-tree.ts +69 -0
- package/src/policy/cartographer/describe.ts +53 -0
- package/src/policy/cartographer/entry.ts +38 -0
- package/src/policy/cartographer/frontmatter.ts +48 -0
- package/src/policy/cartographer/indicators.ts +34 -0
- package/src/policy/claude-md-context.ts +66 -0
- package/src/policy/conventions/index.ts +11 -0
- package/src/policy/conventions/interfaces.ts +71 -0
- package/src/policy/conventions/langs.ts +53 -0
- package/src/policy/conventions/query.ts +49 -0
- package/src/policy/conventions/react-hooks.ts +18 -0
- package/src/policy/conventions/store-or-query.ts +21 -0
- package/src/policy/conventions/stores.ts +56 -0
- package/src/policy/conventions/strip.ts +87 -0
- package/src/policy/conventions/verdict.ts +38 -0
- package/src/policy/creation-intent.ts +11 -0
- package/src/policy/deny-loop.ts +88 -0
- package/src/policy/design/allowed-write.ts +28 -0
- package/src/policy/design/content-checks.ts +45 -0
- package/src/policy/design/corpus-fs.ts +21 -0
- package/src/policy/design/corpus-resolve-cache.ts +49 -0
- package/src/policy/design/corpus-resolve.ts +99 -0
- package/src/policy/design/corpus.ts +92 -0
- package/src/policy/design/design-system-rules.ts +45 -0
- package/src/policy/design/flag.ts +30 -0
- package/src/policy/design/gates-pipeline.ts +105 -0
- package/src/policy/design/gates.ts +87 -0
- package/src/policy/design/screenshot-tools.ts +24 -0
- package/src/policy/design/skill-evidence.ts +34 -0
- package/src/policy/design/skill-gate.ts +81 -0
- package/src/policy/design/skill-triggers.ts +51 -0
- package/src/policy/design/state.ts +92 -0
- package/src/policy/design/template-urls.ts +54 -0
- package/src/policy/design/transitions.ts +68 -0
- package/src/policy/detect-claude-md-project-type.ts +32 -0
- package/src/policy/detect-framework.ts +99 -0
- package/src/policy/detect-primitive-checks.ts +100 -0
- package/src/policy/detect-primitive-lib.ts +55 -0
- package/src/policy/detect-project.ts +99 -0
- package/src/policy/edit-outcome.ts +53 -0
- package/src/policy/evaluate.ts +81 -0
- package/src/policy/expert-agents.ts +60 -0
- package/src/policy/file-size-scope.ts +32 -0
- package/src/policy/file-size.ts +99 -0
- package/src/policy/framework-solid-exclude.ts +52 -0
- package/src/policy/framework-solid-extended.ts +93 -0
- package/src/policy/framework-solid-gates-systems.ts +99 -0
- package/src/policy/framework-solid-gates.ts +98 -0
- package/src/policy/framework-solid.ts +88 -0
- package/src/policy/gemini-mcp-gate.ts +83 -0
- package/src/policy/git-gates.ts +40 -0
- package/src/policy/guards/bash-command-anchor.ts +40 -0
- package/src/policy/guards/bash-write-patterns.ts +140 -0
- package/src/policy/guards/bash-write-redirects.ts +201 -0
- package/src/policy/guards/bash-write-safe-paths.ts +62 -0
- package/src/policy/guards/bash-write.ts +76 -0
- package/src/policy/guards/context.ts +12 -0
- package/src/policy/guards/index.ts +61 -0
- package/src/policy/guards/install.ts +22 -0
- package/src/policy/guards/interface-separation-ext.ts +41 -0
- package/src/policy/guards/interface-separation.ts +100 -0
- package/src/policy/guards/protected-path.ts +100 -0
- package/src/policy/guards/security.ts +97 -0
- package/src/policy/index.ts +19 -0
- package/src/policy/interfaces/types.ts +46 -0
- package/src/policy/js-gate-route.ts +51 -0
- package/src/policy/lessons/lesson-gate.ts +0 -0
- package/src/policy/lessons/trigger-index.ts +84 -0
- package/src/policy/lessons/types.ts +31 -0
- package/src/policy/module-layout.ts +31 -0
- package/src/policy/nearest-manifest.ts +75 -0
- package/src/policy/never-approval.ts +95 -0
- package/src/policy/patterns.ts +80 -0
- package/src/policy/seo/validate.ts +51 -0
- package/src/policy/shadcn-project.ts +22 -0
- package/src/policy/shadcn-skill-gate.ts +97 -0
- package/src/policy/shell-read-refs.ts +96 -0
- package/src/policy/skill-path.ts +46 -0
- package/src/policy/skill-patterns/laravel.ts +40 -0
- package/src/policy/skill-patterns/nextjs.ts +29 -0
- package/src/policy/skill-patterns/react.ts +27 -0
- package/src/policy/skill-patterns/shadcn-subskills.ts +33 -0
- package/src/policy/skill-patterns/shadcn.ts +66 -0
- package/src/policy/skill-patterns/swift.ts +30 -0
- package/src/policy/skill-patterns/tailwind.ts +40 -0
- package/src/policy/skill-source.ts +52 -0
- package/src/policy/skill-trigger-patterns.ts +33 -0
- package/src/policy/skill-triggers.ts +108 -0
- package/src/policy/tailwind-skill-gate.ts +63 -0
- package/src/policy/trivial-edits.ts +52 -0
- package/src/policy/verbosity.ts +39 -0
- package/src/prompt/index.ts +1 -0
- package/src/prompt/types.ts +36 -0
- package/src/refs/discover.ts +69 -0
- package/src/refs/frontmatter.ts +24 -0
- package/src/refs/index.ts +4 -0
- package/src/refs/loader.ts +59 -0
- package/src/refs/ref-key.ts +41 -0
- package/src/refs/router.ts +63 -0
- package/src/refs/types.ts +25 -0
- package/src/runtime/activity.ts +99 -0
- package/src/runtime/apply-patch-gate.ts +41 -0
- package/src/runtime/burst-window.ts +21 -0
- package/src/runtime/codex-shell-tool.ts +37 -0
- package/src/runtime/command-string.ts +37 -0
- package/src/runtime/confirm/codex-confirm.ts +126 -0
- package/src/runtime/confirm/codex-prompt-origin.ts +16 -0
- package/src/runtime/confirm/confirm-code.ts +33 -0
- package/src/runtime/confirm/confirm-gate.ts +65 -0
- package/src/runtime/confirm/confirm-irreversible.ts +32 -0
- package/src/runtime/confirm/confirm-pending.ts +34 -0
- package/src/runtime/confirm/confirm-state.ts +56 -0
- package/src/runtime/confirm/confirm-subagent.ts +112 -0
- package/src/runtime/confirm/confirm-submit.ts +38 -0
- package/src/runtime/core-guards-active.ts +84 -0
- package/src/runtime/core-guards-wired.ts +47 -0
- package/src/runtime/deny-loop-store.ts +94 -0
- package/src/runtime/deny-notice.ts +52 -0
- package/src/runtime/design-cache-resolve.ts +51 -0
- package/src/runtime/design-content-gate.ts +95 -0
- package/src/runtime/design-files-gate.ts +114 -0
- package/src/runtime/design-helpers.ts +93 -0
- package/src/runtime/design-lifecycle.ts +39 -0
- package/src/runtime/design-read-credit.ts +28 -0
- package/src/runtime/design.ts +116 -0
- package/src/runtime/dev-context.ts +43 -0
- package/src/runtime/dry-patterns.ts +32 -0
- package/src/runtime/dry.ts +96 -0
- package/src/runtime/fragment-registry.ts +33 -0
- package/src/runtime/framework-skill-gate.ts +79 -0
- package/src/runtime/fs-cleanup.ts +71 -0
- package/src/runtime/gate-apex.ts +81 -0
- package/src/runtime/gate-helpers.ts +52 -0
- package/src/runtime/gate-input.ts +26 -0
- package/src/runtime/gate.ts +128 -0
- package/src/runtime/handle-post.ts +104 -0
- package/src/runtime/handle-pre.ts +137 -0
- package/src/runtime/handle-scope-async.ts +29 -0
- package/src/runtime/handle-types.ts +27 -0
- package/src/runtime/handle.ts +116 -0
- package/src/runtime/home-state.ts +55 -0
- package/src/runtime/index.ts +18 -0
- package/src/runtime/inform.ts +28 -0
- package/src/runtime/inject-budget-recap.ts +47 -0
- package/src/runtime/inject-budget.ts +81 -0
- package/src/runtime/inject-context.ts +85 -0
- package/src/runtime/inject-dedup-exclusive.ts +98 -0
- package/src/runtime/inject-dedup.ts +89 -0
- package/src/runtime/is-agent-tool.ts +19 -0
- package/src/runtime/lifecycle/agent-files.ts +49 -0
- package/src/runtime/lifecycle/agent-memory.ts +82 -0
- package/src/runtime/lifecycle/agent-transcript.ts +80 -0
- package/src/runtime/lifecycle/aipilot/analytics.ts +99 -0
- package/src/runtime/lifecycle/aipilot/apex-task-store.ts +61 -0
- package/src/runtime/lifecycle/aipilot/cache-base.ts +48 -0
- package/src/runtime/lifecycle/aipilot/cache-doc.ts +100 -0
- package/src/runtime/lifecycle/aipilot/cache-lessons.ts +58 -0
- package/src/runtime/lifecycle/aipilot/cache-test.ts +70 -0
- package/src/runtime/lifecycle/aipilot/curate-lessons.ts +64 -0
- package/src/runtime/lifecycle/aipilot/dispatch-aipilot.ts +100 -0
- package/src/runtime/lifecycle/aipilot/doc-cache-gate.ts +69 -0
- package/src/runtime/lifecycle/aipilot/inject-apex.ts +98 -0
- package/src/runtime/lifecycle/aipilot/inject-doc.ts +62 -0
- package/src/runtime/lifecycle/aipilot/inject-explore.ts +64 -0
- package/src/runtime/lifecycle/aipilot/inject-lessons.ts +61 -0
- package/src/runtime/lifecycle/aipilot/inject-test.ts +48 -0
- package/src/runtime/lifecycle/aipilot/lesson-archive.ts +60 -0
- package/src/runtime/lifecycle/aipilot/lesson-inject.ts +98 -0
- package/src/runtime/lifecycle/aipilot/lesson-parse.ts +74 -0
- package/src/runtime/lifecycle/aipilot/lessons.ts +72 -0
- package/src/runtime/lifecycle/aipilot/promote-global-lessons.ts +92 -0
- package/src/runtime/lifecycle/aipilot/solid-compliance.ts +69 -0
- package/src/runtime/lifecycle/aipilot/solid-notice.ts +47 -0
- package/src/runtime/lifecycle/aipilot/solid-transcript.ts +60 -0
- package/src/runtime/lifecycle/aipilot/source-scan.ts +56 -0
- package/src/runtime/lifecycle/aipilot/sync-task.ts +75 -0
- package/src/runtime/lifecycle/aipilot/transcript.ts +88 -0
- package/src/runtime/lifecycle/aipilot/types.ts +72 -0
- package/src/runtime/lifecycle/auto-document-reads.ts +73 -0
- package/src/runtime/lifecycle/cartographer/detect.ts +57 -0
- package/src/runtime/lifecycle/cartographer/ecosystem-map.ts +56 -0
- package/src/runtime/lifecycle/cartographer/fs-util.ts +64 -0
- package/src/runtime/lifecycle/cartographer/merge.ts +51 -0
- package/src/runtime/lifecycle/cartographer/project-map.ts +49 -0
- package/src/runtime/lifecycle/cartographer/scan-hooks.ts +48 -0
- package/src/runtime/lifecycle/cartographer/scan.ts +81 -0
- package/src/runtime/lifecycle/cartographer/session-start.ts +33 -0
- package/src/runtime/lifecycle/cartographer/track-enrichment.ts +45 -0
- package/src/runtime/lifecycle/cartographer/write-plugin-map.ts +43 -0
- package/src/runtime/lifecycle/cartographer/write-tree.ts +44 -0
- package/src/runtime/lifecycle/changelog-research.ts +43 -0
- package/src/runtime/lifecycle/check-file-size.ts +61 -0
- package/src/runtime/lifecycle/codex-resync/fingerprint.ts +63 -0
- package/src/runtime/lifecycle/codex-resync/lock.ts +46 -0
- package/src/runtime/lifecycle/codex-resync/managed-destination.ts +39 -0
- package/src/runtime/lifecycle/codex-resync/materialize-agents.ts +57 -0
- package/src/runtime/lifecycle/codex-resync/plugin-files.ts +65 -0
- package/src/runtime/lifecycle/codex-resync/plugin-roots.ts +62 -0
- package/src/runtime/lifecycle/codex-resync/resync.ts +45 -0
- package/src/runtime/lifecycle/codex-resync/symlink-commands.ts +25 -0
- package/src/runtime/lifecycle/dispatch.ts +109 -0
- package/src/runtime/lifecycle/failure-lesson.ts +51 -0
- package/src/runtime/lifecycle/index.ts +34 -0
- package/src/runtime/lifecycle/inject-rules.ts +50 -0
- package/src/runtime/lifecycle/instructions-loaded.ts +22 -0
- package/src/runtime/lifecycle/kimi-rules-native.ts +44 -0
- package/src/runtime/lifecycle/lessons/dispatch.ts +88 -0
- package/src/runtime/lifecycle/lessons/reminder.ts +89 -0
- package/src/runtime/lifecycle/lessons/state.ts +23 -0
- package/src/runtime/lifecycle/memory/agent-lesson.ts +35 -0
- package/src/runtime/lifecycle/memory/capture-error.ts +41 -0
- package/src/runtime/lifecycle/memory/client.ts +65 -0
- package/src/runtime/lifecycle/memory/dispatch.ts +42 -0
- package/src/runtime/lifecycle/memory/recall.ts +30 -0
- package/src/runtime/lifecycle/memory/salience.ts +31 -0
- package/src/runtime/lifecycle/memory/state.ts +49 -0
- package/src/runtime/lifecycle/memory/track-ops.ts +15 -0
- package/src/runtime/lifecycle/post-compact.ts +38 -0
- package/src/runtime/lifecycle/post-edit-ts.ts +49 -0
- package/src/runtime/lifecycle/post-tracking.ts +40 -0
- package/src/runtime/lifecycle/pre-compact.ts +38 -0
- package/src/runtime/lifecycle/rules-root.ts +83 -0
- package/src/runtime/lifecycle/security/check-skill.ts +63 -0
- package/src/runtime/lifecycle/security/scan-patterns.ts +53 -0
- package/src/runtime/lifecycle/security/scan.ts +76 -0
- package/src/runtime/lifecycle/security/skill-state.ts +47 -0
- package/src/runtime/lifecycle/security/track-mcp.ts +29 -0
- package/src/runtime/lifecycle/security/track-skill-read.ts +31 -0
- package/src/runtime/lifecycle/seo/post-tool-use.ts +45 -0
- package/src/runtime/lifecycle/session-end.ts +17 -0
- package/src/runtime/lifecycle/session-start.ts +57 -0
- package/src/runtime/lifecycle/snapshot/board.ts +27 -0
- package/src/runtime/lifecycle/snapshot/format.ts +60 -0
- package/src/runtime/lifecycle/snapshot/git.ts +71 -0
- package/src/runtime/lifecycle/snapshot/index.ts +52 -0
- package/src/runtime/lifecycle/snapshot/version.ts +34 -0
- package/src/runtime/lifecycle/solid-detect.ts +56 -0
- package/src/runtime/lifecycle/stop-core.ts +35 -0
- package/src/runtime/lifecycle/subagent-cache.ts +61 -0
- package/src/runtime/lifecycle/task-completed.ts +94 -0
- package/src/runtime/lifecycle/teammate-idle-check.ts +63 -0
- package/src/runtime/lifecycle/teammate-idle.ts +28 -0
- package/src/runtime/lifecycle/tool-failure.ts +24 -0
- package/src/runtime/lifecycle/track-changes.ts +61 -0
- package/src/runtime/lifecycle/validate-solid.ts +70 -0
- package/src/runtime/lifecycle/validate-tailwind.ts +80 -0
- package/src/runtime/lifecycle-bridge.ts +74 -0
- package/src/runtime/mcp-key.ts +44 -0
- package/src/runtime/mcp-tool-name.ts +92 -0
- package/src/runtime/mcp.ts +91 -0
- package/src/runtime/modular.ts +77 -0
- package/src/runtime/normalize.ts +107 -0
- package/src/runtime/notices.ts +91 -0
- package/src/runtime/notification-sound.ts +49 -0
- package/src/runtime/notifications.ts +77 -0
- package/src/runtime/paths.ts +59 -0
- package/src/runtime/post-fanout.ts +41 -0
- package/src/runtime/post-outcome.ts +61 -0
- package/src/runtime/pre-allow.ts +97 -0
- package/src/runtime/precommit.ts +60 -0
- package/src/runtime/prompt-text.ts +38 -0
- package/src/runtime/receipt-capture.ts +34 -0
- package/src/runtime/record.ts +26 -0
- package/src/runtime/respond.ts +92 -0
- package/src/runtime/solid-file-size-gate.ts +58 -0
- package/src/runtime/solid-pre.ts +53 -0
- package/src/runtime/storage.ts +9 -0
- package/src/statusline/colors.ts +31 -0
- package/src/statusline/constants.ts +17 -0
- package/src/statusline/formatters.ts +54 -0
- package/src/statusline/index.ts +4 -0
- package/src/statusline/progress-bar.ts +46 -0
- package/src/tracking/codex-post-failure.ts +73 -0
- package/src/tracking/index.ts +2 -0
- package/src/tracking/integrity.ts +99 -0
- package/src/tracking/one-shot-dedup.ts +34 -0
- package/src/tracking/one-shot-failure.ts +42 -0
- package/src/tracking/one-shot-store.ts +95 -0
- package/src/tracking/one-shot.ts +93 -0
- package/src/tracking/receipts.ts +95 -0
- package/src/tracking/session-state.ts +94 -0
- package/src/tracking/store.ts +96 -0
- package/src/tracking/track-compact.ts +100 -0
- package/src/tracking/track-diff.ts +50 -0
- package/src/tracking/track-journal.ts +97 -0
- package/src/tracking/track-lock-sync.ts +83 -0
- package/src/tracking/track-lock.ts +67 -0
- package/src/util/compact-json.ts +23 -0
- package/src/util/index.ts +2 -0
- package/src/util/json-io.ts +47 -0
- package/src/util/project-root.ts +41 -0
- package/src/util/runtime-io.ts +85 -0
- package/src/util/semver.ts +30 -0
- package/dist/permission-mode-B0MUl7FT.mjs +0 -136
|
@@ -3,21 +3,23 @@ import { a as harnessHome, c as resolveTtlSec } from "./dotenv-BLBkBTww.mjs";
|
|
|
3
3
|
import { r as projectLayout } from "./layout-KWoE_Mqn.mjs";
|
|
4
4
|
import { i as walkUpFor, n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "./project-root-3kk7gCOp.mjs";
|
|
5
5
|
import { t as detectHarness } from "./harness-BMuLJ9lm.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { D as isVendorPath, G as computeEditResultLines, K as isFileSizeScoped, V as shellOutputRedirects, Y as GIT_BLOCKED, b as interfaceDeclLevel, ct as detectProjectType$1, et as matchPatterns, g as conventionsMode, it as maskCommentsAndStrings, n as FAIL_CLOSED, q as resolveSolidRefFramework, st as detectModularArchitecture, t as evaluate, tt as detectFramework, ut as requiredArchSkill } from "./evaluate-CMiIqHeH.mjs";
|
|
7
7
|
import { a as sanitizeSessionId, c as sessionsDir, d as countFrameworkCodeLines, f as countLines, i as loadSessionState, l as PLUGINS_DIR, n as fuseHarnessHome, o as saveSessionState, p as evaluateFileSize, r as fusengineCache, t as claudeHome } from "./home-state-oUGFB4ds.mjs";
|
|
8
8
|
import { a as writeJsonFile, i as readJsonFile, r as hashText, t as atomicWrite } from "./json-io-DisYd2fb.mjs";
|
|
9
9
|
import { r as isDocConsulted } from "./doc-helpers-CWZegVdR.mjs";
|
|
10
|
-
import { D as frameworkSolidGate, F as isExcludedSwiftPath, G as evaluateApex, H as PRE_AUTH_GATES, P as isExcludedJsPath, R as capVerbosity, S as parseField, T as usesTailwindUtilities, V as POST_AUTH_GATES, W as docConsultedGate, b as scanPlugin, c as EXCLUDE_DIRS$1, d as buildApexTaskInjection, g as buildClaudeMdContext, l as PROJECT_INDICATORS, n as missingSeoElements, o as parseEnrichment, p as apexAgentSteps, r as descFromText, s as parseEntry, t as isHtmlLike, v as apexDocName, w as skillTriggerGate, y as harnessHomeSegment, z as detectCreationIntent } from "./validate-
|
|
10
|
+
import { D as frameworkSolidGate, F as isExcludedSwiftPath, G as evaluateApex, H as PRE_AUTH_GATES, P as isExcludedJsPath, R as capVerbosity, S as parseField, T as usesTailwindUtilities, V as POST_AUTH_GATES, W as docConsultedGate, b as scanPlugin, c as EXCLUDE_DIRS$1, d as buildApexTaskInjection, g as buildClaudeMdContext, l as PROJECT_INDICATORS, n as missingSeoElements, o as parseEnrichment, p as apexAgentSteps, r as descFromText, s as parseEntry, t as isHtmlLike, v as apexDocName, w as skillTriggerGate, y as harnessHomeSegment, z as detectCreationIntent } from "./validate-KjZ1X9tH.mjs";
|
|
11
11
|
import { n as findMarketplacePlugins, r as readPluginMeta, t as resolveSkillPath } from "./skill-path-DVML3zfp.mjs";
|
|
12
12
|
import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
|
|
13
13
|
import { a as nowStamp, l as throttleMs, n as readRoots, o as readState, s as setStateField, t as addRoot } from "./registry-IhHk2KlT.mjs";
|
|
14
14
|
import { d as loadIndex, i as cacheLookupMeta, n as webfetchCacheWrite, o as cacheLookupSubstringMeta, t as mcpCacheWrite, u as extractText } from "./mcp-store-BkBDmuxN.mjs";
|
|
15
15
|
import { t as loadRefs } from "./loader-AGz4nK7d.mjs";
|
|
16
16
|
import { C as apexAuthorizationGate, S as trivialCount, _ as recordBrainstormRequired, a as journalLogPath, b as recordTarget, c as appendEvent, d as signTrack, f as verifyTrack, g as recordAgent, h as emptyTrack, i as withTrack, l as withTrackLockSync, m as agentsFresh, o as readTrackSync, p as writeLastNonce, r as trackJournalEnabled, s as diffTrackEvents, t as loadTrack, u as LOCK_FAILED, v as recordDoc, x as recordTrivialEdit, y as recordRefRead } from "./store-5-ZPKb0u.mjs";
|
|
17
|
-
import { n as
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import { r as
|
|
17
|
+
import { i as parseApplyPatch, n as canonicalizeMcpToolName, r as canonicalizeCodexShellTool, t as isBypassPermissions } from "./permission-mode-B-SFyR0X.mjs";
|
|
18
|
+
import { t as commandToString } from "./command-string-CALMTnwN.mjs";
|
|
19
|
+
import { n as cursorProjectCwd, r as cursorEventContract, t as extractCursorEvent } from "./normalize-BjG6unTj.mjs";
|
|
20
|
+
import { d as collectFiles, f as pathExists, g as writeText, h as spawnCapture, i as denyResponse, l as systemMessage, m as sleep, n as blockResponse, p as readText$1, r as contextResponse, s as informResponse, t as attachSystemMessage } from "./claude-Ckv2_TgP.mjs";
|
|
21
|
+
import { i as toKimiResponse, n as kimiDenyResponse } from "./kimi-G2wcSh5-.mjs";
|
|
22
|
+
import { r as toHermesResponse } from "./hermes-B9-p_3IF.mjs";
|
|
21
23
|
import { basename, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
|
|
22
24
|
import { appendFileSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, renameSync, rmSync, rmdirSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
23
25
|
import { homedir } from "node:os";
|
|
@@ -69,9 +71,7 @@ function str(v) {
|
|
|
69
71
|
return typeof v === "string" ? v : void 0;
|
|
70
72
|
}
|
|
71
73
|
/**
|
|
72
|
-
* Normalize a
|
|
73
|
-
* `preToolUse`/`postToolUse` shape and the top-level `tool_name`/`tool_input`
|
|
74
|
-
* shape used by Claude, Codex, Gemini, and Cursor.
|
|
74
|
+
* Normalize a hook payload, including Cline nesting and native Cursor events.
|
|
75
75
|
*/
|
|
76
76
|
function normalizeEvent(id, payload) {
|
|
77
77
|
if (id === "cline") {
|
|
@@ -88,16 +88,24 @@ function normalizeEvent(id, payload) {
|
|
|
88
88
|
command: str(params.command)
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
|
+
if (id === "cursor") return {
|
|
92
|
+
...extractCursorEvent(payload),
|
|
93
|
+
sessionId: str(payload.session_id) ?? str(payload.conversation_id) ?? "",
|
|
94
|
+
agentType: str(payload.agent_type),
|
|
95
|
+
permissionMode: str(payload.permission_mode)
|
|
96
|
+
};
|
|
91
97
|
const event = str(payload.hook_event_name) ?? "";
|
|
92
98
|
const input = payload.tool_input ?? payload;
|
|
93
|
-
const tool = canonicalizeCodexShellTool(id, str(payload.tool_name) ?? "");
|
|
99
|
+
const tool = canonicalizeCodexShellTool(id, canonicalizeMcpToolName(id, str(payload.tool_name) ?? ""));
|
|
94
100
|
const base = {
|
|
95
101
|
phase: /post|after/i.test(event) ? "post" : "pre",
|
|
96
102
|
tool,
|
|
97
103
|
input,
|
|
98
104
|
sessionId: str(payload.session_id) ?? str(payload.conversation_id) ?? "",
|
|
99
105
|
agentType: str(payload.agent_type) ?? str(input.subagent_type),
|
|
100
|
-
permissionMode: str(payload.permission_mode)
|
|
106
|
+
permissionMode: str(payload.permission_mode),
|
|
107
|
+
toolUseId: str(payload.tool_use_id),
|
|
108
|
+
cwd: str(payload.cwd)
|
|
101
109
|
};
|
|
102
110
|
if (tool === "apply_patch") {
|
|
103
111
|
const files = parseApplyPatch(str(input.command) ?? str(payload.command) ?? "").map((f) => ({
|
|
@@ -3064,7 +3072,7 @@ function lessonsStateFileFor(root) {
|
|
|
3064
3072
|
//#endregion
|
|
3065
3073
|
//#region src/tracking/one-shot-store.ts
|
|
3066
3074
|
/** A fresh, empty state — always spread (`{ ...EMPTY }`) so the const is never shared. */
|
|
3067
|
-
const EMPTY = {
|
|
3075
|
+
const EMPTY$1 = {
|
|
3068
3076
|
gates: {},
|
|
3069
3077
|
firstTry: 0,
|
|
3070
3078
|
corrected: 0,
|
|
@@ -3076,7 +3084,7 @@ const EMPTY = {
|
|
|
3076
3084
|
* gates/pending older than `windowMs`. Keeps the "7d" window honest, bounds size.
|
|
3077
3085
|
*/
|
|
3078
3086
|
function pruneState(s, now, windowMs) {
|
|
3079
|
-
if (now - s.updatedAt >= windowMs) return { ...EMPTY };
|
|
3087
|
+
if (now - s.updatedAt >= windowMs) return { ...EMPTY$1 };
|
|
3080
3088
|
const gates = {};
|
|
3081
3089
|
for (const [k, g] of Object.entries(s.gates)) if (now - g.lastTs < windowMs) gates[k] = g;
|
|
3082
3090
|
const pending = {};
|
|
@@ -3309,16 +3317,16 @@ const SIDECAR$1 = "one-shot.json";
|
|
|
3309
3317
|
/** Retention window: 7 days. Aggregates and pending denies older than this are pruned. */
|
|
3310
3318
|
const WINDOW_MS = 10080 * 60 * 1e3;
|
|
3311
3319
|
/** Load the state, or a fresh copy when missing/corrupt. */
|
|
3312
|
-
function loadState(path) {
|
|
3320
|
+
function loadState$1(path) {
|
|
3313
3321
|
try {
|
|
3314
|
-
if (!existsSync(path)) return { ...EMPTY };
|
|
3322
|
+
if (!existsSync(path)) return { ...EMPTY$1 };
|
|
3315
3323
|
const d = JSON.parse(readFileSync(path, "utf8"));
|
|
3316
3324
|
return d && typeof d === "object" && !Array.isArray(d) ? {
|
|
3317
|
-
...EMPTY,
|
|
3325
|
+
...EMPTY$1,
|
|
3318
3326
|
...d
|
|
3319
|
-
} : { ...EMPTY };
|
|
3327
|
+
} : { ...EMPTY$1 };
|
|
3320
3328
|
} catch {
|
|
3321
|
-
return { ...EMPTY };
|
|
3329
|
+
return { ...EMPTY$1 };
|
|
3322
3330
|
}
|
|
3323
3331
|
}
|
|
3324
3332
|
/**
|
|
@@ -3341,7 +3349,7 @@ function recordOneShot(prompt, input, opts) {
|
|
|
3341
3349
|
});
|
|
3342
3350
|
if (!burstFirst(op, prompt ? `deny:${prompt.title}` : "allow", opts)) return;
|
|
3343
3351
|
const path = join(opts.dir, SIDECAR$1);
|
|
3344
|
-
let s = pruneState(loadState(path), opts.now, WINDOW_MS);
|
|
3352
|
+
let s = pruneState(loadState$1(path), opts.now, WINDOW_MS);
|
|
3345
3353
|
s = prompt ? applyDeny(s, prompt.title, op, opts.now) : applyAllow(s, op, opts.now, input.content != null || input.command != null);
|
|
3346
3354
|
atomicWrite(path, JSON.stringify(s));
|
|
3347
3355
|
} catch {}
|
|
@@ -3356,7 +3364,7 @@ function recordOneShot(prompt, input, opts) {
|
|
|
3356
3364
|
*/
|
|
3357
3365
|
function oneShotSummary(cwd) {
|
|
3358
3366
|
try {
|
|
3359
|
-
return formatSummary(pruneState(loadState(join(defaultStateDir(cwd), SIDECAR$1)), Date.now(), WINDOW_MS));
|
|
3367
|
+
return formatSummary(pruneState(loadState$1(join(defaultStateDir(cwd), SIDECAR$1)), Date.now(), WINDOW_MS));
|
|
3360
3368
|
} catch {
|
|
3361
3369
|
return "";
|
|
3362
3370
|
}
|
|
@@ -3401,7 +3409,7 @@ function recordFailure(tool, opts) {
|
|
|
3401
3409
|
try {
|
|
3402
3410
|
if (!burstFirst(`fail:${tool}`, "failure", opts)) return;
|
|
3403
3411
|
const path = join(opts.dir, SIDECAR$1);
|
|
3404
|
-
const next = applyFailure(pruneState(loadState(path), opts.now, WINDOW_MS), tool, opts.now);
|
|
3412
|
+
const next = applyFailure(pruneState(loadState$1(path), opts.now, WINDOW_MS), tool, opts.now);
|
|
3405
3413
|
atomicWrite(path, JSON.stringify(next));
|
|
3406
3414
|
} catch {}
|
|
3407
3415
|
}
|
|
@@ -5326,8 +5334,13 @@ const RETRY_DELAYS = [
|
|
|
5326
5334
|
1e3,
|
|
5327
5335
|
2e3
|
|
5328
5336
|
];
|
|
5329
|
-
/**
|
|
5330
|
-
|
|
5337
|
+
/**
|
|
5338
|
+
* Extract the longest assistant synthesis + queried library ids from a transcript.
|
|
5339
|
+
* @param id - Harness adapter id, used to canonicalize `block.name` (Codex-mangled
|
|
5340
|
+
* underscore tool names) before the {@link TOOL_PATTERN} match — see
|
|
5341
|
+
* {@link cacheDocFromTranscript} for why this is a defensive, unconfirmed-case cover.
|
|
5342
|
+
*/
|
|
5343
|
+
async function extractSynthesis(path, id) {
|
|
5331
5344
|
const lines = readText$1(path).split("\n").filter(Boolean);
|
|
5332
5345
|
const libraries = [];
|
|
5333
5346
|
let synthesis = "";
|
|
@@ -5337,7 +5350,7 @@ async function extractSynthesis(path) {
|
|
|
5337
5350
|
const contents = entry?.message?.content;
|
|
5338
5351
|
if (!Array.isArray(contents)) continue;
|
|
5339
5352
|
for (const block of contents) {
|
|
5340
|
-
if (block.type === "tool_use" && TOOL_PATTERN.test(block.name ?? "")) {
|
|
5353
|
+
if (block.type === "tool_use" && TOOL_PATTERN.test(canonicalizeMcpToolName(id, block.name ?? ""))) {
|
|
5341
5354
|
const lib = block.input?.libraryId ?? block.input?.query ?? "";
|
|
5342
5355
|
if (lib && !libraries.includes(lib)) libraries.push(lib);
|
|
5343
5356
|
}
|
|
@@ -5354,17 +5367,28 @@ async function extractSynthesis(path) {
|
|
|
5354
5367
|
* @param transcript - Path to the agent JSONL transcript.
|
|
5355
5368
|
* @param cwd - Fallback project root.
|
|
5356
5369
|
* @param home - Home dir (defaults to `~`).
|
|
5357
|
-
|
|
5358
|
-
|
|
5370
|
+
* @param id - Harness adapter id (defaults to "claude-code"). On `"codex"`,
|
|
5371
|
+
* every `tool_use.name` read from the transcript is canonicalized before the
|
|
5372
|
+
* {@link TOOL_PATTERN} match, defensively covering the SAME Codex-mangled
|
|
5373
|
+
* underscore form as `doc-cache-gate.ts`'s `docCacheGate`. UNLIKE that gate,
|
|
5374
|
+
* this is NOT a confirmed-reproduced bug: the proof that Codex emits the
|
|
5375
|
+
* underscore form (`sanitize_responses_api_tool_name()`, openai/codex#14605,
|
|
5376
|
+
* `SCENARIO_DASH_TOOL` in #18385) is about the live PreToolUse HOOK payload,
|
|
5377
|
+
* never observed here against transcript `tool_use.name` content. The
|
|
5378
|
+
* canonicalization is a no-op when the transcript already carries the dash
|
|
5379
|
+
* form (idempotence proven in `test/mcp-tool-name.test.ts`, test 2), so this
|
|
5380
|
+
* costs nothing if the hypothesis turns out false.
|
|
5381
|
+
*/
|
|
5382
|
+
async function cacheDocFromTranscript(transcript, cwd, home = homedir(), id = "claude-code") {
|
|
5359
5383
|
if (!transcript || !pathExists(transcript)) return;
|
|
5360
5384
|
const projPath = projectRootFromPaths(await transcriptFilePaths(transcript)) ?? process.env.CLAUDE_PROJECT_DIR ?? cwd;
|
|
5361
5385
|
const cacheDir = cacheDirFor("doc", projPath, home);
|
|
5362
5386
|
const docsDir = join(cacheDir, "docs");
|
|
5363
|
-
let result = await extractSynthesis(transcript);
|
|
5387
|
+
let result = await extractSynthesis(transcript, id);
|
|
5364
5388
|
for (const delay of RETRY_DELAYS) {
|
|
5365
5389
|
if (result.text.length >= MIN_TEXT_SIZE && result.libraries.length > 0) break;
|
|
5366
5390
|
await sleep(delay);
|
|
5367
|
-
result = await extractSynthesis(transcript);
|
|
5391
|
+
result = await extractSynthesis(transcript, id);
|
|
5368
5392
|
}
|
|
5369
5393
|
const { text, libraries } = result;
|
|
5370
5394
|
if (text.length < MIN_TEXT_SIZE || libraries.length === 0) return;
|
|
@@ -5762,10 +5786,14 @@ function libraryOf(payload) {
|
|
|
5762
5786
|
* @param cwd - Fallback project root (uses `CLAUDE_PROJECT_DIR` first).
|
|
5763
5787
|
* @param now - Clock (defaults to `Date.now()`).
|
|
5764
5788
|
* @param home - Home dir (defaults to `~`).
|
|
5789
|
+
* @param id - Harness adapter id (defaults to "claude-code"); on `"codex"`,
|
|
5790
|
+
* `payload.tool_name` is canonicalized before the {@link GATED_TOOLS} match
|
|
5791
|
+
* so the Codex-mangled underscore form (`mcp__context7__query_docs`) gates
|
|
5792
|
+
* exactly like the dash form does on every other harness.
|
|
5765
5793
|
* @returns A native deny response, or `null` to fall through to the live call.
|
|
5766
5794
|
*/
|
|
5767
|
-
async function docCacheGate(payload, cwd, now = Date.now(), home = homedir()) {
|
|
5768
|
-
const tool = String(payload.tool_name ?? "");
|
|
5795
|
+
async function docCacheGate(payload, cwd, now = Date.now(), home = homedir(), id = "claude-code") {
|
|
5796
|
+
const tool = canonicalizeMcpToolName(id, String(payload.tool_name ?? ""));
|
|
5769
5797
|
if (!GATED_TOOLS.test(tool)) return null;
|
|
5770
5798
|
const library = libraryOf(payload);
|
|
5771
5799
|
if (!library) return null;
|
|
@@ -5981,11 +6009,11 @@ async function onSubagentStart(payload, cwd, now, home, id) {
|
|
|
5981
6009
|
const agent = agentTypeOf(payload);
|
|
5982
6010
|
return combineContext(await injectApexSubagentContext(cwd, home, id), await injectLessonsCache(cwd, home, now), await typeSpecificCache(agent, cwd, now, home));
|
|
5983
6011
|
}
|
|
5984
|
-
/** SubagentStop routing: transcript-driven cache writers, then the universal SOLID check. */
|
|
5985
|
-
async function onSubagentStop(payload, cwd, home) {
|
|
6012
|
+
/** SubagentStop routing: transcript-driven cache writers, then the universal SOLID check. `id` selects the harness target (defaults to "claude-code"), threaded into `cacheDocFromTranscript`. */
|
|
6013
|
+
async function onSubagentStop(payload, cwd, home, id) {
|
|
5986
6014
|
const agent = agentTypeOf(payload);
|
|
5987
6015
|
const transcript = transcriptOf(payload);
|
|
5988
|
-
if (agent.includes("research-expert")) await cacheDocFromTranscript(transcript, cwd, home);
|
|
6016
|
+
if (agent.includes("research-expert")) await cacheDocFromTranscript(transcript, cwd, home, id);
|
|
5989
6017
|
if (agent.includes("sniper")) {
|
|
5990
6018
|
await cacheSniperLessons(transcript, cwd, home);
|
|
5991
6019
|
await cacheTestResults(transcript, cwd, home);
|
|
@@ -5999,12 +6027,12 @@ async function onSubagentStop(payload, cwd, home) {
|
|
|
5999
6027
|
*/
|
|
6000
6028
|
async function dispatchAipilot(event, payload, cwd, now, home = homedir(), id = "claude-code") {
|
|
6001
6029
|
if (event === "SubagentStart") return onSubagentStart(payload, cwd, now, home, id);
|
|
6002
|
-
if (event === "SubagentStop") return onSubagentStop(payload, cwd, home);
|
|
6030
|
+
if (event === "SubagentStop") return onSubagentStop(payload, cwd, home, id);
|
|
6003
6031
|
if (event === "SessionEnd" || event === "Stop") {
|
|
6004
6032
|
await cacheAnalyticsSave(home, now);
|
|
6005
6033
|
return "";
|
|
6006
6034
|
}
|
|
6007
|
-
if (event === "PreToolUse") return docCacheGate(payload, cwd, now, home);
|
|
6035
|
+
if (event === "PreToolUse") return docCacheGate(payload, cwd, now, home, id);
|
|
6008
6036
|
return null;
|
|
6009
6037
|
}
|
|
6010
6038
|
/** PostToolUse (Write/Edit SOLID check, else TaskCreate/TaskUpdate sync) for the ai-pilot scope; `id` selects the harness target (defaults to "claude-code"). */
|
|
@@ -6626,13 +6654,281 @@ async function autoDocumentRead(filePath, now = Date.now(), id = "claude-code")
|
|
|
6626
6654
|
return systemMessage(`📖 [${docType}] ${fname} logged`);
|
|
6627
6655
|
}
|
|
6628
6656
|
//#endregion
|
|
6657
|
+
//#region src/adapters/cursor/native-response.ts
|
|
6658
|
+
const stringValue = (value) => typeof value === "string";
|
|
6659
|
+
const booleanValue = (value) => typeof value === "boolean";
|
|
6660
|
+
const plainRecord = (value) => {
|
|
6661
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
6662
|
+
try {
|
|
6663
|
+
const prototype = Object.getPrototypeOf(value);
|
|
6664
|
+
return prototype === Object.prototype || prototype === null;
|
|
6665
|
+
} catch {
|
|
6666
|
+
return false;
|
|
6667
|
+
}
|
|
6668
|
+
};
|
|
6669
|
+
function jsonChildren(value) {
|
|
6670
|
+
const keys = Reflect.ownKeys(value);
|
|
6671
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
6672
|
+
if (Array.isArray(value)) {
|
|
6673
|
+
if (Object.getPrototypeOf(value) !== Array.prototype) return null;
|
|
6674
|
+
const length = descriptors.length;
|
|
6675
|
+
if (!length || !("value" in length) || !Number.isSafeInteger(length.value) || length.value < 0) return null;
|
|
6676
|
+
if (keys.length !== length.value + 1 || keys.some((key) => typeof key === "symbol")) return null;
|
|
6677
|
+
const children = [];
|
|
6678
|
+
for (let index = 0; index < length.value; index += 1) {
|
|
6679
|
+
const descriptor = descriptors[String(index)];
|
|
6680
|
+
if (!descriptor?.enumerable || !("value" in descriptor)) return null;
|
|
6681
|
+
children.push(descriptor.value);
|
|
6682
|
+
}
|
|
6683
|
+
return children;
|
|
6684
|
+
}
|
|
6685
|
+
if (!plainRecord(value) || keys.some((key) => typeof key === "symbol")) return null;
|
|
6686
|
+
const children = [];
|
|
6687
|
+
for (const key of keys) {
|
|
6688
|
+
const descriptor = descriptors[key];
|
|
6689
|
+
if (!descriptor?.enumerable || !("value" in descriptor)) return null;
|
|
6690
|
+
children.push(descriptor.value);
|
|
6691
|
+
}
|
|
6692
|
+
return children;
|
|
6693
|
+
}
|
|
6694
|
+
function jsonValue(root) {
|
|
6695
|
+
const active = /* @__PURE__ */ new WeakSet();
|
|
6696
|
+
const stack = [{ value: root }];
|
|
6697
|
+
while (stack.length > 0) {
|
|
6698
|
+
const frame = stack.pop();
|
|
6699
|
+
if (frame.leave) {
|
|
6700
|
+
active.delete(frame.value);
|
|
6701
|
+
continue;
|
|
6702
|
+
}
|
|
6703
|
+
const { value } = frame;
|
|
6704
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") continue;
|
|
6705
|
+
if (typeof value === "number") {
|
|
6706
|
+
if (!Number.isFinite(value)) return false;
|
|
6707
|
+
continue;
|
|
6708
|
+
}
|
|
6709
|
+
if (typeof value !== "object" || active.has(value)) return false;
|
|
6710
|
+
let children;
|
|
6711
|
+
try {
|
|
6712
|
+
children = jsonChildren(value);
|
|
6713
|
+
} catch {
|
|
6714
|
+
return false;
|
|
6715
|
+
}
|
|
6716
|
+
if (!children) return false;
|
|
6717
|
+
active.add(value);
|
|
6718
|
+
stack.push({
|
|
6719
|
+
value,
|
|
6720
|
+
leave: true
|
|
6721
|
+
});
|
|
6722
|
+
for (let index = children.length - 1; index >= 0; index -= 1) stack.push({ value: children[index] });
|
|
6723
|
+
}
|
|
6724
|
+
return true;
|
|
6725
|
+
}
|
|
6726
|
+
const recordValue = (value) => plainRecord(value) && jsonValue(value);
|
|
6727
|
+
const stringRecord = (value) => {
|
|
6728
|
+
if (!recordValue(value)) return false;
|
|
6729
|
+
try {
|
|
6730
|
+
return Object.values(Object.getOwnPropertyDescriptors(value)).every((descriptor) => "value" in descriptor && typeof descriptor.value === "string");
|
|
6731
|
+
} catch {
|
|
6732
|
+
return false;
|
|
6733
|
+
}
|
|
6734
|
+
};
|
|
6735
|
+
const stringArray = (value) => Array.isArray(value) && value.every(stringValue);
|
|
6736
|
+
const permission = (...values) => (value) => typeof value === "string" && values.includes(value);
|
|
6737
|
+
const EMPTY = { fields: {} };
|
|
6738
|
+
const FOLLOWUP = { fields: { followup_message: stringValue } };
|
|
6739
|
+
const PERMISSION_ASK = {
|
|
6740
|
+
fields: {
|
|
6741
|
+
permission: permission("allow", "deny", "ask"),
|
|
6742
|
+
user_message: stringValue,
|
|
6743
|
+
agent_message: stringValue
|
|
6744
|
+
},
|
|
6745
|
+
required: ["permission"]
|
|
6746
|
+
};
|
|
6747
|
+
const NATIVE_SCHEMAS = {
|
|
6748
|
+
sessionStart: { fields: {
|
|
6749
|
+
env: stringRecord,
|
|
6750
|
+
additional_context: stringValue,
|
|
6751
|
+
continue: booleanValue,
|
|
6752
|
+
user_message: stringValue
|
|
6753
|
+
} },
|
|
6754
|
+
sessionEnd: EMPTY,
|
|
6755
|
+
beforeSubmitPrompt: {
|
|
6756
|
+
fields: {
|
|
6757
|
+
continue: booleanValue,
|
|
6758
|
+
user_message: stringValue
|
|
6759
|
+
},
|
|
6760
|
+
required: ["continue"]
|
|
6761
|
+
},
|
|
6762
|
+
preCompact: { fields: { user_message: stringValue } },
|
|
6763
|
+
subagentStart: {
|
|
6764
|
+
fields: {
|
|
6765
|
+
permission: permission("allow", "deny"),
|
|
6766
|
+
user_message: stringValue
|
|
6767
|
+
},
|
|
6768
|
+
required: ["permission"]
|
|
6769
|
+
},
|
|
6770
|
+
subagentStop: FOLLOWUP,
|
|
6771
|
+
preToolUse: {
|
|
6772
|
+
fields: {
|
|
6773
|
+
...PERMISSION_ASK.fields,
|
|
6774
|
+
updated_input: recordValue
|
|
6775
|
+
},
|
|
6776
|
+
required: ["permission"]
|
|
6777
|
+
},
|
|
6778
|
+
postToolUse: { fields: {
|
|
6779
|
+
updated_mcp_tool_output: recordValue,
|
|
6780
|
+
additional_context: stringValue
|
|
6781
|
+
} },
|
|
6782
|
+
postToolUseFailure: EMPTY,
|
|
6783
|
+
beforeShellExecution: PERMISSION_ASK,
|
|
6784
|
+
afterShellExecution: EMPTY,
|
|
6785
|
+
beforeMCPExecution: PERMISSION_ASK,
|
|
6786
|
+
afterMCPExecution: EMPTY,
|
|
6787
|
+
beforeReadFile: {
|
|
6788
|
+
fields: {
|
|
6789
|
+
permission: permission("allow", "deny"),
|
|
6790
|
+
user_message: stringValue
|
|
6791
|
+
},
|
|
6792
|
+
required: ["permission"]
|
|
6793
|
+
},
|
|
6794
|
+
afterFileEdit: EMPTY,
|
|
6795
|
+
beforeTabFileRead: {
|
|
6796
|
+
fields: { permission: permission("allow", "deny") },
|
|
6797
|
+
required: ["permission"]
|
|
6798
|
+
},
|
|
6799
|
+
afterTabFileEdit: EMPTY,
|
|
6800
|
+
afterAgentResponse: EMPTY,
|
|
6801
|
+
afterAgentThought: EMPTY,
|
|
6802
|
+
stop: FOLLOWUP,
|
|
6803
|
+
workspaceOpen: { fields: { pluginPaths: stringArray } }
|
|
6804
|
+
};
|
|
6805
|
+
function isNativeCursorResponse(value, eventName) {
|
|
6806
|
+
try {
|
|
6807
|
+
if (!recordValue(value)) return false;
|
|
6808
|
+
const schema = NATIVE_SCHEMAS[eventName];
|
|
6809
|
+
if (!schema) return false;
|
|
6810
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
6811
|
+
if (schema.required?.some((field) => !Object.hasOwn(descriptors, field))) return false;
|
|
6812
|
+
return Reflect.ownKeys(descriptors).every((field) => {
|
|
6813
|
+
if (typeof field !== "string") return false;
|
|
6814
|
+
const descriptor = descriptors[field];
|
|
6815
|
+
if (!Object.hasOwn(schema.fields, field)) return false;
|
|
6816
|
+
const validate = schema.fields[field];
|
|
6817
|
+
return descriptor?.enumerable === true && "value" in descriptor && validate !== void 0 && validate(descriptor.value);
|
|
6818
|
+
});
|
|
6819
|
+
} catch {
|
|
6820
|
+
return false;
|
|
6821
|
+
}
|
|
6822
|
+
}
|
|
6823
|
+
/** Preserve raw stdout only when JSON.parse proves a documented native Cursor response. */
|
|
6824
|
+
function parseNativeCursorStdout(stdout, eventName) {
|
|
6825
|
+
if (typeof stdout !== "string") return null;
|
|
6826
|
+
try {
|
|
6827
|
+
return isNativeCursorResponse(JSON.parse(stdout), eventName) ? stdout : null;
|
|
6828
|
+
} catch {
|
|
6829
|
+
return null;
|
|
6830
|
+
}
|
|
6831
|
+
}
|
|
6832
|
+
//#endregion
|
|
6833
|
+
//#region src/adapters/cursor/respond.ts
|
|
6834
|
+
const AGENT_MESSAGE_EVENTS = /* @__PURE__ */ new Set([
|
|
6835
|
+
"preToolUse",
|
|
6836
|
+
"beforeShellExecution",
|
|
6837
|
+
"beforeMCPExecution"
|
|
6838
|
+
]);
|
|
6839
|
+
const USER_MESSAGE_EVENTS = /* @__PURE__ */ new Set([
|
|
6840
|
+
"preToolUse",
|
|
6841
|
+
"beforeShellExecution",
|
|
6842
|
+
"beforeMCPExecution",
|
|
6843
|
+
"beforeReadFile",
|
|
6844
|
+
"subagentStart"
|
|
6845
|
+
]);
|
|
6846
|
+
function permissionMessages(eventName, userMessage, agentMessage) {
|
|
6847
|
+
return {
|
|
6848
|
+
...userMessage && USER_MESSAGE_EVENTS.has(eventName) ? { user_message: userMessage } : {},
|
|
6849
|
+
...agentMessage && AGENT_MESSAGE_EVENTS.has(eventName) ? { agent_message: agentMessage } : {}
|
|
6850
|
+
};
|
|
6851
|
+
}
|
|
6852
|
+
function joinMessages(...values) {
|
|
6853
|
+
return values.filter((value) => typeof value === "string" && value.length > 0).join("\n");
|
|
6854
|
+
}
|
|
6855
|
+
function isRecord(value) {
|
|
6856
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6857
|
+
}
|
|
6858
|
+
/** Render a portable policy prompt using the native Cursor event contract. */
|
|
6859
|
+
function toCursorResponse(prompt, eventName) {
|
|
6860
|
+
const contract = cursorEventContract(eventName);
|
|
6861
|
+
const message = formatPrompt(prompt);
|
|
6862
|
+
if (!contract.known || contract.response === "neutral" || contract.response === "plugin-paths") return "{}";
|
|
6863
|
+
if (contract.response === "post-context" || contract.response === "session-context") return JSON.stringify({ additional_context: message });
|
|
6864
|
+
if (contract.response === "followup") return JSON.stringify({ followup_message: message });
|
|
6865
|
+
if (contract.response === "compact-notice") return JSON.stringify({ user_message: prompt.userMessage ?? message });
|
|
6866
|
+
if (contract.response === "submit-control") return JSON.stringify({
|
|
6867
|
+
continue: prompt.kind !== "block",
|
|
6868
|
+
user_message: prompt.userMessage ?? message
|
|
6869
|
+
});
|
|
6870
|
+
if (prompt.kind === "inform") return JSON.stringify({
|
|
6871
|
+
permission: "allow",
|
|
6872
|
+
...permissionMessages(eventName, prompt.userMessage, prompt.reason ? message : void 0)
|
|
6873
|
+
});
|
|
6874
|
+
const userMessage = prompt.kind === "ask" ? `[downgraded from ask — Cursor does not enforce approval for this event]\n${message}` : message;
|
|
6875
|
+
return JSON.stringify({
|
|
6876
|
+
permission: "deny",
|
|
6877
|
+
...permissionMessages(eventName, userMessage, userMessage)
|
|
6878
|
+
});
|
|
6879
|
+
}
|
|
6880
|
+
/** Convert a shared lifecycle handler's output to the native Cursor envelope. */
|
|
6881
|
+
function toCursorLifecycleResponse(stdout, eventName) {
|
|
6882
|
+
const contract = cursorEventContract(eventName);
|
|
6883
|
+
const native = parseNativeCursorStdout(stdout, eventName);
|
|
6884
|
+
if (native !== null) return native;
|
|
6885
|
+
let text = stdout;
|
|
6886
|
+
let decision;
|
|
6887
|
+
let userMessage = "";
|
|
6888
|
+
let agentMessage = "";
|
|
6889
|
+
let decisionMessage = "";
|
|
6890
|
+
let structured = false;
|
|
6891
|
+
try {
|
|
6892
|
+
const parsed = JSON.parse(stdout);
|
|
6893
|
+
const subagentAsk = eventName === "subagentStart" && isRecord(parsed) && Object.hasOwn(parsed, "permission") && parsed.permission === "ask";
|
|
6894
|
+
const shared = isRecord(parsed) ? parsed : {};
|
|
6895
|
+
structured = true;
|
|
6896
|
+
decision = shared.hookSpecificOutput?.permissionDecision ?? (subagentAsk ? "ask" : void 0);
|
|
6897
|
+
userMessage = shared.systemMessage ?? (subagentAsk && Object.hasOwn(shared, "user_message") && typeof shared.user_message === "string" ? shared.user_message : "");
|
|
6898
|
+
decisionMessage = joinMessages(shared.hookSpecificOutput?.permissionDecisionReason, shared.reason);
|
|
6899
|
+
agentMessage = joinMessages(shared.hookSpecificOutput?.additionalContext, decisionMessage);
|
|
6900
|
+
text = joinMessages(agentMessage, userMessage, shared.followup_message);
|
|
6901
|
+
} catch {
|
|
6902
|
+
text = stdout;
|
|
6903
|
+
}
|
|
6904
|
+
if (contract.response === "neutral") return "{}";
|
|
6905
|
+
if (!text) return contract.response === "permission" ? "{\"permission\":\"allow\"}" : "{}";
|
|
6906
|
+
if (contract.response === "session-context" || contract.response === "post-context") return JSON.stringify({ additional_context: text });
|
|
6907
|
+
if (contract.response === "permission") {
|
|
6908
|
+
const permission = decision === "deny" || decision === "ask" ? "deny" : "allow";
|
|
6909
|
+
const denied = permission === "deny";
|
|
6910
|
+
if (eventName === "subagentStart" && !denied) return "{\"permission\":\"allow\"}";
|
|
6911
|
+
return JSON.stringify({
|
|
6912
|
+
permission,
|
|
6913
|
+
...permissionMessages(eventName, userMessage || (denied ? decisionMessage || agentMessage : ""), agentMessage || (denied ? decisionMessage || userMessage : structured ? "" : text))
|
|
6914
|
+
});
|
|
6915
|
+
}
|
|
6916
|
+
if (contract.response === "followup") return JSON.stringify({ followup_message: text });
|
|
6917
|
+
if (contract.response === "compact-notice") return JSON.stringify({ user_message: text });
|
|
6918
|
+
if (contract.response === "submit-control") return JSON.stringify({
|
|
6919
|
+
continue: true,
|
|
6920
|
+
user_message: text
|
|
6921
|
+
});
|
|
6922
|
+
return "{}";
|
|
6923
|
+
}
|
|
6924
|
+
//#endregion
|
|
6629
6925
|
//#region src/runtime/lifecycle-bridge.ts
|
|
6630
6926
|
/** Raw event name from a payload (Cline lacks one; lifecycle is Claude-only). */
|
|
6631
6927
|
function rawEvent(payload) {
|
|
6632
6928
|
return typeof payload.hook_event_name === "string" ? payload.hook_event_name : "";
|
|
6633
6929
|
}
|
|
6634
6930
|
/** Extract the `additionalContext` body from a `contextResponse(...)` stdout string ("" when empty/unparseable). */
|
|
6635
|
-
function additionalContextOf(stdout) {
|
|
6931
|
+
function additionalContextOf$1(stdout) {
|
|
6636
6932
|
if (!stdout) return "";
|
|
6637
6933
|
try {
|
|
6638
6934
|
return JSON.parse(stdout).hookSpecificOutput?.additionalContext ?? "";
|
|
@@ -6653,14 +6949,30 @@ function additionalContextOf(stdout) {
|
|
|
6653
6949
|
* @returns The native stdout, or `null` when unhandled.
|
|
6654
6950
|
*/
|
|
6655
6951
|
function lifecycleStdout(payload, cwd, scope, now, id = "claude-code") {
|
|
6656
|
-
|
|
6657
|
-
|
|
6952
|
+
const wireEvent = rawEvent(payload);
|
|
6953
|
+
if (id !== "cursor") return dispatchLifecycle({
|
|
6954
|
+
event: wireEvent,
|
|
6658
6955
|
payload,
|
|
6659
6956
|
cwd,
|
|
6660
6957
|
scope,
|
|
6661
6958
|
now,
|
|
6662
6959
|
id
|
|
6663
6960
|
});
|
|
6961
|
+
const lifecycleEvent = cursorEventContract(wireEvent).lifecycle;
|
|
6962
|
+
if (lifecycleEvent === null) return null;
|
|
6963
|
+
const sessionId = typeof payload.session_id === "string" ? payload.session_id : payload.conversation_id;
|
|
6964
|
+
const stdout = dispatchLifecycle({
|
|
6965
|
+
event: lifecycleEvent,
|
|
6966
|
+
payload: {
|
|
6967
|
+
...payload,
|
|
6968
|
+
session_id: sessionId
|
|
6969
|
+
},
|
|
6970
|
+
cwd,
|
|
6971
|
+
scope,
|
|
6972
|
+
now,
|
|
6973
|
+
id
|
|
6974
|
+
});
|
|
6975
|
+
return stdout === null ? null : toCursorLifecycleResponse(stdout, wireEvent);
|
|
6664
6976
|
}
|
|
6665
6977
|
/**
|
|
6666
6978
|
* Post-edit additions for core-scope PostToolUse: auto-document a Read of a
|
|
@@ -6682,7 +6994,7 @@ async function postEditContext(scope, event, now, id = "claude-code") {
|
|
|
6682
6994
|
const sniper = trackSessionChanges(event.sessionId, event.filePath, void 0, now);
|
|
6683
6995
|
const lint = postEditTypescript(event.filePath);
|
|
6684
6996
|
if (!sniper || !lint) return sniper || lint;
|
|
6685
|
-
return contextResponse("PostToolUse", `${additionalContextOf(sniper)}\n\n${additionalContextOf(lint)}`);
|
|
6997
|
+
return contextResponse("PostToolUse", `${additionalContextOf$1(sniper)}\n\n${additionalContextOf$1(lint)}`);
|
|
6686
6998
|
}
|
|
6687
6999
|
//#endregion
|
|
6688
7000
|
//#region src/policy/trivial-edits.ts
|
|
@@ -7674,11 +7986,37 @@ const TRIVIAL_BUDGET = 4;
|
|
|
7674
7986
|
* {@link withDenyLoop} rewrite an identical retried deny (decision unchanged).
|
|
7675
7987
|
*/
|
|
7676
7988
|
async function gate(input) {
|
|
7677
|
-
|
|
7989
|
+
return finalizeGate(input, await runGates(input), input.command);
|
|
7990
|
+
}
|
|
7991
|
+
/**
|
|
7992
|
+
* Gate independent Cursor command candidates without merging their shell syntax.
|
|
7993
|
+
* Stateful outcome bookkeeping runs once, against the command that decided the
|
|
7994
|
+
* aggregate result. A single distinct candidate follows {@link gate} verbatim.
|
|
7995
|
+
*/
|
|
7996
|
+
async function gateCommandCandidates(input, candidates) {
|
|
7997
|
+
const distinct = [...new Set(candidates)];
|
|
7998
|
+
if (distinct.length <= 1) return gate(input);
|
|
7999
|
+
let decisive;
|
|
8000
|
+
for (const command of distinct) {
|
|
8001
|
+
const prompt = await runGates({
|
|
8002
|
+
...input,
|
|
8003
|
+
command
|
|
8004
|
+
});
|
|
8005
|
+
if (!prompt) continue;
|
|
8006
|
+
if (!decisive || promptRank(prompt) > promptRank(decisive.prompt)) decisive = {
|
|
8007
|
+
prompt,
|
|
8008
|
+
command
|
|
8009
|
+
};
|
|
8010
|
+
if (prompt.kind === "block") break;
|
|
8011
|
+
}
|
|
8012
|
+
return finalizeGate(input, decisive?.prompt ?? null, decisive?.command ?? input.command);
|
|
8013
|
+
}
|
|
8014
|
+
/** Persist one final gate outcome and apply deny-loop enrichment exactly once. */
|
|
8015
|
+
function finalizeGate(input, prompt, command) {
|
|
7678
8016
|
const op = {
|
|
7679
8017
|
filePath: input.filePath,
|
|
7680
8018
|
content: input.content,
|
|
7681
|
-
command
|
|
8019
|
+
command
|
|
7682
8020
|
};
|
|
7683
8021
|
const dir = dirname(input.trackFile);
|
|
7684
8022
|
recordOneShot(prompt, op, {
|
|
@@ -7693,6 +8031,10 @@ async function gate(input) {
|
|
|
7693
8031
|
sessionId: input.sessionId
|
|
7694
8032
|
});
|
|
7695
8033
|
}
|
|
8034
|
+
/** Higher numeric values dominate while equal values preserve wire order. */
|
|
8035
|
+
function promptRank(prompt) {
|
|
8036
|
+
return prompt.kind === "block" ? 3 : prompt.kind === "ask" ? 2 : 1;
|
|
8037
|
+
}
|
|
7696
8038
|
/** Stateless guards, then the trivial fast path, then the stateful APEX gates. */
|
|
7697
8039
|
async function runGates(input) {
|
|
7698
8040
|
const precommit = preCommitGate(input.tool, input.command, input.cwd);
|
|
@@ -7880,8 +8222,8 @@ async function recordActivity(file, activity) {
|
|
|
7880
8222
|
* - gemini-cli/cline: their real hook schemas have no interactive "ask"
|
|
7881
8223
|
* state (deny is the only blocking outcome), so `ask` and `inform` already
|
|
7882
8224
|
* both resolve to non-blocking context injection — unchanged.
|
|
7883
|
-
* - cursor: `ask`
|
|
7884
|
-
*
|
|
8225
|
+
* - cursor: `ask` degrades to a native `permission:"deny"` because Cursor does
|
|
8226
|
+
* not reliably apply `ask` on `preToolUse`; `inform` stays non-blocking.
|
|
7885
8227
|
* - hermes: delegated to the adapter's `toHermesResponse` — `block` ->
|
|
7886
8228
|
* `{decision:"block",reason}`; `ask`/`inform` degrade to non-blocking
|
|
7887
8229
|
* `{context}` (Hermes has no interactive "ask" state).
|
|
@@ -7935,25 +8277,7 @@ function respond(id, prompt, event = "PreToolUse") {
|
|
|
7935
8277
|
systemMessage: userMessage
|
|
7936
8278
|
});
|
|
7937
8279
|
return JSON.stringify({ hookSpecificOutput: { additionalContext: message } });
|
|
7938
|
-
case "cursor":
|
|
7939
|
-
if (kind === "inform") {
|
|
7940
|
-
if (userMessage) return JSON.stringify({
|
|
7941
|
-
permission: "allow",
|
|
7942
|
-
user_message: userMessage,
|
|
7943
|
-
...reason ? { agent_message: message } : {}
|
|
7944
|
-
});
|
|
7945
|
-
return JSON.stringify({
|
|
7946
|
-
permission: "allow",
|
|
7947
|
-
user_message: message,
|
|
7948
|
-
agent_message: message
|
|
7949
|
-
});
|
|
7950
|
-
}
|
|
7951
|
-
return JSON.stringify({
|
|
7952
|
-
permission: kind === "block" ? "deny" : "ask",
|
|
7953
|
-
continue: false,
|
|
7954
|
-
user_message: message,
|
|
7955
|
-
agent_message: message
|
|
7956
|
-
});
|
|
8280
|
+
case "cursor": return toCursorResponse(prompt, event === "PreToolUse" ? "preToolUse" : event);
|
|
7957
8281
|
case "hermes": return toHermesResponse(prompt);
|
|
7958
8282
|
case "kimi": return toKimiResponse(prompt);
|
|
7959
8283
|
case "cline":
|
|
@@ -8638,8 +8962,8 @@ function segments(command) {
|
|
|
8638
8962
|
}
|
|
8639
8963
|
/** Strip a redirection (`>`, `>>`, `<`, `2>`, `&>`, …) and everything after — its target is WRITTEN, not read. */
|
|
8640
8964
|
function beforeRedirect(segment) {
|
|
8641
|
-
const
|
|
8642
|
-
return
|
|
8965
|
+
const first = shellOutputRedirects(segment)[0];
|
|
8966
|
+
return first ? segment.slice(0, first.start) : segment;
|
|
8643
8967
|
}
|
|
8644
8968
|
/** Naive shell tokenizer: whitespace-split, stripping one matching layer of quotes per token. */
|
|
8645
8969
|
function tokenize(segment) {
|
|
@@ -9135,7 +9459,7 @@ async function allowOutcome(id, event, payload, mcpDir, cwd, evidence, corpusRoo
|
|
|
9135
9459
|
stdout: respond(id, userMessage ? {
|
|
9136
9460
|
...lesson,
|
|
9137
9461
|
userMessage
|
|
9138
|
-
} : lesson),
|
|
9462
|
+
} : lesson, event.eventName ?? "PreToolUse"),
|
|
9139
9463
|
exit: 0
|
|
9140
9464
|
};
|
|
9141
9465
|
}
|
|
@@ -9150,12 +9474,12 @@ async function allowOutcome(id, event, payload, mcpDir, cwd, evidence, corpusRoo
|
|
|
9150
9474
|
title: "APEX freshness",
|
|
9151
9475
|
reason: "",
|
|
9152
9476
|
userMessage
|
|
9153
|
-
}),
|
|
9477
|
+
}, event.eventName ?? "PreToolUse"),
|
|
9154
9478
|
exit: 0
|
|
9155
9479
|
};
|
|
9156
9480
|
}
|
|
9157
9481
|
return {
|
|
9158
|
-
stdout: notice ? respond(id, notice) : "",
|
|
9482
|
+
stdout: notice ? respond(id, notice, event.eventName ?? "PreToolUse") : "",
|
|
9159
9483
|
exit: 0
|
|
9160
9484
|
};
|
|
9161
9485
|
}
|
|
@@ -9296,7 +9620,7 @@ function getPendingDeny(sessionIdRaw, home = homedir()) {
|
|
|
9296
9620
|
//#endregion
|
|
9297
9621
|
//#region src/runtime/confirm/confirm-state.ts
|
|
9298
9622
|
/** Confirmation freshness window (G2) — 5 minutes, matches the validated prototype. */
|
|
9299
|
-
const TTL_MS = 300 * 1e3;
|
|
9623
|
+
const TTL_MS$1 = 300 * 1e3;
|
|
9300
9624
|
/**
|
|
9301
9625
|
* Pose a confirmation token for this session's pending action. No-op while
|
|
9302
9626
|
* {@link isSubagentActive} (G0, confirm-subagent.ts) — the sole gate that
|
|
@@ -9336,7 +9660,7 @@ function consumeConfirmToken(sessionIdRaw, hash, now, home = homedir()) {
|
|
|
9336
9660
|
if (!sid) return false;
|
|
9337
9661
|
const tok = loadSessionState(sid, home).confirmToken;
|
|
9338
9662
|
if (!tok || tok.hash !== hash) return false;
|
|
9339
|
-
if (now - tok.ts > TTL_MS) {
|
|
9663
|
+
if (now - tok.ts > TTL_MS$1) {
|
|
9340
9664
|
dropConfirmToken(sid, home);
|
|
9341
9665
|
return false;
|
|
9342
9666
|
}
|
|
@@ -9344,6 +9668,141 @@ function consumeConfirmToken(sessionIdRaw, hash, now, home = homedir()) {
|
|
|
9344
9668
|
return true;
|
|
9345
9669
|
}
|
|
9346
9670
|
//#endregion
|
|
9671
|
+
//#region src/runtime/confirm/codex-confirm.ts
|
|
9672
|
+
const TTL_MS = 300 * 1e3;
|
|
9673
|
+
const STATE_VERSION = 1;
|
|
9674
|
+
function statePath(sid, home) {
|
|
9675
|
+
return join(sessionsDir(home), `codex-confirm-${sid}.json`);
|
|
9676
|
+
}
|
|
9677
|
+
function lockDir(sid, home) {
|
|
9678
|
+
return join(sessionsDir(home), ".confirm-locks", sid);
|
|
9679
|
+
}
|
|
9680
|
+
function loadState(sid, home) {
|
|
9681
|
+
const path = statePath(sid, home);
|
|
9682
|
+
if (!existsSync(path)) return {};
|
|
9683
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
9684
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
|
|
9685
|
+
}
|
|
9686
|
+
function saveState(sid, state, home) {
|
|
9687
|
+
mkdirSync(sessionsDir(home), {
|
|
9688
|
+
recursive: true,
|
|
9689
|
+
mode: 448
|
|
9690
|
+
});
|
|
9691
|
+
atomicWrite(statePath(sid, home), JSON.stringify(state, null, 2));
|
|
9692
|
+
}
|
|
9693
|
+
/** Canonical identity of one Codex shell action and its display token. */
|
|
9694
|
+
function codexAction(tool, cwd, command, now) {
|
|
9695
|
+
const canonicalCommand = commandToString(command);
|
|
9696
|
+
if (!canonicalCommand) return null;
|
|
9697
|
+
const identity = JSON.stringify({
|
|
9698
|
+
version: STATE_VERSION,
|
|
9699
|
+
harness: "codex",
|
|
9700
|
+
tool,
|
|
9701
|
+
cwd: resolve(cwd),
|
|
9702
|
+
command: canonicalCommand
|
|
9703
|
+
});
|
|
9704
|
+
return {
|
|
9705
|
+
hash: hashForAction(identity),
|
|
9706
|
+
code: displayCodeForAction(identity),
|
|
9707
|
+
command: canonicalCommand,
|
|
9708
|
+
ts: now
|
|
9709
|
+
};
|
|
9710
|
+
}
|
|
9711
|
+
/** Atomically authorize or reject a Codex action, recording the current denial when needed. */
|
|
9712
|
+
function authorizeCodexAction(sessionIdRaw, action, toolUseId, now, home = homedir()) {
|
|
9713
|
+
const sid = sanitizeSessionId(sessionIdRaw);
|
|
9714
|
+
if (!sid) return {
|
|
9715
|
+
allow: false,
|
|
9716
|
+
reason: "state-io"
|
|
9717
|
+
};
|
|
9718
|
+
try {
|
|
9719
|
+
const result = withTrackLockSync(lockDir(sid, home), () => {
|
|
9720
|
+
const state = loadState(sid, home);
|
|
9721
|
+
const token = state.codexConfirmToken;
|
|
9722
|
+
const receipt = state.codexConfirmReceipt;
|
|
9723
|
+
const receiptExpired = receipt !== void 0 && now - receipt.ts > TTL_MS;
|
|
9724
|
+
if (!receiptExpired && receipt?.hash === action.hash && receipt.toolUseId === toolUseId && toolUseId) return { allow: true };
|
|
9725
|
+
let reason = "no-token";
|
|
9726
|
+
if (token) if (token.hash !== action.hash) reason = "mismatch";
|
|
9727
|
+
else if (now - token.ts > TTL_MS) reason = "expired";
|
|
9728
|
+
else if (!toolUseId) reason = "missing-tool-use-id";
|
|
9729
|
+
else {
|
|
9730
|
+
const { codexConfirmToken: _token, codexConfirmPending: _pending, ...rest } = state;
|
|
9731
|
+
saveState(sid, {
|
|
9732
|
+
...rest,
|
|
9733
|
+
codexConfirmReceipt: {
|
|
9734
|
+
...action,
|
|
9735
|
+
toolUseId
|
|
9736
|
+
}
|
|
9737
|
+
}, home);
|
|
9738
|
+
return { allow: true };
|
|
9739
|
+
}
|
|
9740
|
+
else if (receipt?.hash === action.hash) reason = receiptExpired ? "expired" : "already-consumed";
|
|
9741
|
+
if (reason === "missing-tool-use-id") return {
|
|
9742
|
+
allow: false,
|
|
9743
|
+
reason
|
|
9744
|
+
};
|
|
9745
|
+
const pending = state.codexConfirmPending;
|
|
9746
|
+
const stablePending = pending?.hash === action.hash ? pending : action;
|
|
9747
|
+
const { codexConfirmToken: _token, codexConfirmReceipt: _receipt, ...withoutReceipt } = state;
|
|
9748
|
+
const rest = receiptExpired ? withoutReceipt : {
|
|
9749
|
+
...withoutReceipt,
|
|
9750
|
+
codexConfirmReceipt: receipt
|
|
9751
|
+
};
|
|
9752
|
+
saveState(sid, {
|
|
9753
|
+
...rest,
|
|
9754
|
+
codexConfirmPending: stablePending
|
|
9755
|
+
}, home);
|
|
9756
|
+
return {
|
|
9757
|
+
allow: false,
|
|
9758
|
+
reason
|
|
9759
|
+
};
|
|
9760
|
+
});
|
|
9761
|
+
return result === LOCK_FAILED ? {
|
|
9762
|
+
allow: false,
|
|
9763
|
+
reason: "state-io"
|
|
9764
|
+
} : result;
|
|
9765
|
+
} catch {
|
|
9766
|
+
return {
|
|
9767
|
+
allow: false,
|
|
9768
|
+
reason: "state-io"
|
|
9769
|
+
};
|
|
9770
|
+
}
|
|
9771
|
+
}
|
|
9772
|
+
/**
|
|
9773
|
+
* Atomically arm the last Codex denial, consuming its pending record exactly once.
|
|
9774
|
+
* A classified root prompt may bypass G0; classified subagent/unknown prompts fail closed.
|
|
9775
|
+
*/
|
|
9776
|
+
function submitCodexConfirmation(sessionIdRaw, text, now, home = homedir(), origin) {
|
|
9777
|
+
const sid = sanitizeSessionId(sessionIdRaw);
|
|
9778
|
+
if (!sid) return;
|
|
9779
|
+
const refusal = /\b(non|no|stop|annule|cancel|abort|nope|laisse tomber|pas maintenant)\b/i.test(text);
|
|
9780
|
+
const typedCode = text.trim().match(/^confirm[ _-]*([0-9a-f]{4})$/i)?.[1];
|
|
9781
|
+
try {
|
|
9782
|
+
withTrackLockSync(lockDir(sid, home), () => {
|
|
9783
|
+
const state = loadState(sid, home);
|
|
9784
|
+
if (origin !== void 0 && origin !== "root") return;
|
|
9785
|
+
if (refusal) {
|
|
9786
|
+
const { codexConfirmPending: _pending, codexConfirmToken: _token, ...rest } = state;
|
|
9787
|
+
saveState(sid, rest, home);
|
|
9788
|
+
return;
|
|
9789
|
+
}
|
|
9790
|
+
if (!typedCode) return;
|
|
9791
|
+
if (origin === void 0 && isSubagentActive(sid, now, home)) return;
|
|
9792
|
+
const pending = state.codexConfirmPending;
|
|
9793
|
+
if (!pending || pending.code.toLowerCase() !== typedCode.toLowerCase()) return;
|
|
9794
|
+
const { codexConfirmPending: _pending, ...rest } = state;
|
|
9795
|
+
saveState(sid, {
|
|
9796
|
+
...rest,
|
|
9797
|
+
codexConfirmToken: {
|
|
9798
|
+
...pending,
|
|
9799
|
+
ts: now
|
|
9800
|
+
}
|
|
9801
|
+
}, home);
|
|
9802
|
+
});
|
|
9803
|
+
} catch {}
|
|
9804
|
+
}
|
|
9805
|
+
//#endregion
|
|
9347
9806
|
//#region src/runtime/confirm/confirm-gate.ts
|
|
9348
9807
|
/**
|
|
9349
9808
|
* Harnesses where `respond.ts` silently downgrades `kind: "ask"` to a hard
|
|
@@ -9367,9 +9826,23 @@ const DEGRADES_ASK_TO_DENY = /* @__PURE__ */ new Set(["codex", "kimi"]);
|
|
|
9367
9826
|
* @param now - Epoch ms.
|
|
9368
9827
|
* @param home - Test-only OS home override.
|
|
9369
9828
|
*/
|
|
9370
|
-
function confirmGate(id, prompt, command, sessionId, now, home) {
|
|
9829
|
+
function confirmGate(id, prompt, command, sessionId, now, home, codex) {
|
|
9371
9830
|
if (prompt.kind !== "ask" || !DEGRADES_ASK_TO_DENY.has(id) || !command || isIrreversible(command)) return null;
|
|
9372
9831
|
try {
|
|
9832
|
+
if (id === "codex" && codex) {
|
|
9833
|
+
const action = codexAction(codex.tool, codex.cwd, command, now);
|
|
9834
|
+
if (!action) return null;
|
|
9835
|
+
const verdict = authorizeCodexAction(sessionId, action, codex.toolUseId, now, home);
|
|
9836
|
+
if (verdict.allow) return verdict;
|
|
9837
|
+
const diagnostic = `rule ID: ${prompt.ruleId ?? "policy:ask"}\ncanonical command: ${action.command}\nexpected token: CONFIRM ${action.code}\nrejection: ${verdict.reason}`;
|
|
9838
|
+
return {
|
|
9839
|
+
allow: false,
|
|
9840
|
+
prompt: {
|
|
9841
|
+
...prompt,
|
|
9842
|
+
reason: `${prompt.reason}\nPour autoriser, réponds : CONFIRM ${action.code}\n${diagnostic}`
|
|
9843
|
+
}
|
|
9844
|
+
};
|
|
9845
|
+
}
|
|
9373
9846
|
const hash = hashForAction(command);
|
|
9374
9847
|
if (consumeConfirmToken(sessionId, hash, now, home)) return { allow: true };
|
|
9375
9848
|
const code = displayCodeForAction(command);
|
|
@@ -9410,9 +9883,19 @@ async function handlePre(ctx) {
|
|
|
9410
9883
|
}
|
|
9411
9884
|
const designBlock = designGate(payload, event, designCacheDir, opts.cwd, opts.corpusRoot);
|
|
9412
9885
|
if (designBlock) return {
|
|
9413
|
-
stdout: withDenyNotice(id, respond(id, designBlock), designBlock, event.sessionId, dirname(file), opts.now),
|
|
9886
|
+
stdout: withDenyNotice(id, respond(id, designBlock, event.eventName ?? "PreToolUse"), designBlock, event.sessionId, dirname(file), opts.now),
|
|
9414
9887
|
exit: 0
|
|
9415
9888
|
};
|
|
9889
|
+
if (id === "cursor" && (event.eventName === "beforeReadFile" || event.eventName === "beforeTabFileRead")) {
|
|
9890
|
+
const readPolicy = evaluate({
|
|
9891
|
+
tool: event.tool,
|
|
9892
|
+
filePath: event.filePath
|
|
9893
|
+
});
|
|
9894
|
+
return {
|
|
9895
|
+
stdout: readPolicy.prompt ? respond(id, readPolicy.prompt, event.eventName) : JSON.stringify({ permission: "allow" }),
|
|
9896
|
+
exit: 0
|
|
9897
|
+
};
|
|
9898
|
+
}
|
|
9416
9899
|
if (opts.scope === "security") return {
|
|
9417
9900
|
stdout: event.files?.length ? securityAdvisoryForPatch(event.files, opts.now) : securityAdvisory(event.tool, event.filePath ?? "", opts.now),
|
|
9418
9901
|
exit: 0
|
|
@@ -9428,11 +9911,12 @@ async function handlePre(ctx) {
|
|
|
9428
9911
|
if (event.files && event.files.length > 0) {
|
|
9429
9912
|
const patchPrompt = applyPatchGate(event.files, opts.cwd);
|
|
9430
9913
|
if (patchPrompt) return {
|
|
9431
|
-
stdout: withDenyNotice(id, respond(id, patchPrompt), patchPrompt, event.sessionId, dirname(file), opts.now),
|
|
9914
|
+
stdout: withDenyNotice(id, respond(id, patchPrompt, event.eventName ?? "PreToolUse"), patchPrompt, event.sessionId, dirname(file), opts.now),
|
|
9432
9915
|
exit: 0
|
|
9433
9916
|
};
|
|
9434
9917
|
}
|
|
9435
|
-
const
|
|
9918
|
+
const refs = opts.refsDir ? await loadRefs(opts.refsDir) : void 0;
|
|
9919
|
+
const gateInput = {
|
|
9436
9920
|
sessionId: event.sessionId,
|
|
9437
9921
|
framework,
|
|
9438
9922
|
tool: event.tool,
|
|
@@ -9440,7 +9924,7 @@ async function handlePre(ctx) {
|
|
|
9440
9924
|
content: event.content,
|
|
9441
9925
|
command: event.command,
|
|
9442
9926
|
cwd: opts.cwd,
|
|
9443
|
-
refs
|
|
9927
|
+
refs,
|
|
9444
9928
|
isReplaceAll: event.input.replace_all === true,
|
|
9445
9929
|
oldString: event.oldString,
|
|
9446
9930
|
agentType: event.agentType,
|
|
@@ -9450,9 +9934,14 @@ async function handlePre(ctx) {
|
|
|
9450
9934
|
trackFile: file,
|
|
9451
9935
|
transcriptPath: typeof payload.transcript_path === "string" ? payload.transcript_path : void 0,
|
|
9452
9936
|
neverApproval: id === "codex" && isBypassPermissions(event.permissionMode)
|
|
9453
|
-
}
|
|
9937
|
+
};
|
|
9938
|
+
const prompt = id === "cursor" && (event.commandCandidates?.length ?? 0) > 1 ? await gateCommandCandidates(gateInput, event.commandCandidates) : await gate(gateInput);
|
|
9454
9939
|
if (prompt) {
|
|
9455
|
-
const confirm = confirmGate(id, prompt, event.command, event.sessionId, opts.now, opts.home
|
|
9940
|
+
const confirm = confirmGate(id, prompt, event.command, event.sessionId, opts.now, opts.home, id === "codex" ? {
|
|
9941
|
+
tool: event.tool,
|
|
9942
|
+
cwd: event.cwd ?? opts.cwd,
|
|
9943
|
+
toolUseId: event.toolUseId
|
|
9944
|
+
} : void 0);
|
|
9456
9945
|
if (confirm?.allow) return allowOutcome(id, event, payload, designCacheDir, opts.cwd, {
|
|
9457
9946
|
trackFile: file,
|
|
9458
9947
|
windowMs: opts.windowMs,
|
|
@@ -9460,7 +9949,7 @@ async function handlePre(ctx) {
|
|
|
9460
9949
|
}, opts.corpusRoot);
|
|
9461
9950
|
const finalPrompt = confirm ? confirm.prompt : prompt;
|
|
9462
9951
|
return {
|
|
9463
|
-
stdout: withDenyNotice(id, respond(id, finalPrompt), finalPrompt, event.sessionId, dirname(file), opts.now),
|
|
9952
|
+
stdout: withDenyNotice(id, respond(id, finalPrompt, event.eventName ?? "PreToolUse"), finalPrompt, event.sessionId, dirname(file), opts.now),
|
|
9464
9953
|
exit: 0
|
|
9465
9954
|
};
|
|
9466
9955
|
}
|
|
@@ -9827,14 +10316,14 @@ function recordCodexPostFailure(tool, result, opts) {
|
|
|
9827
10316
|
//#endregion
|
|
9828
10317
|
//#region src/runtime/post-fanout.ts
|
|
9829
10318
|
/**
|
|
9830
|
-
* Fan
|
|
10319
|
+
* Fan Codex `apply_patch` or Cursor `afterFileEdit` `event.files` into one
|
|
9831
10320
|
* per-file event per touched file, so the per-file PostToolUse gates (SOLID
|
|
9832
10321
|
* size, Tailwind, tracking, post-edit context) see EACH file instead of the
|
|
9833
10322
|
* whole patch (whose own `filePath`/`content` are always undefined). `add`
|
|
9834
10323
|
* maps to `Write`, `update` to `Edit` (mirrors the Pre-phase `applyPatchGate`'s
|
|
9835
10324
|
* tool mapping); `delete`/`move` map to a tool none of those Write|Edit-gated
|
|
9836
10325
|
* checks recognize, so they no-op on it without special-casing each gate.
|
|
9837
|
-
* Returns `[event]` unchanged
|
|
10326
|
+
* Returns `[event]` unchanged when no per-file changes are present.
|
|
9838
10327
|
* @param event - The normalized PostToolUse event.
|
|
9839
10328
|
* @returns One event per touched file, or the original event.
|
|
9840
10329
|
*/
|
|
@@ -9844,7 +10333,8 @@ function fanOutFiles(event) {
|
|
|
9844
10333
|
...event,
|
|
9845
10334
|
tool: f.op === "add" ? "Write" : f.op === "update" ? "Edit" : "apply_patch:delete",
|
|
9846
10335
|
filePath: f.filePath,
|
|
9847
|
-
content: f.op === "delete" ? void 0 : f.content
|
|
10336
|
+
content: f.op === "delete" ? void 0 : f.content,
|
|
10337
|
+
oldString: f.oldString
|
|
9848
10338
|
}));
|
|
9849
10339
|
}
|
|
9850
10340
|
/**
|
|
@@ -9864,7 +10354,106 @@ function firstFileMatch(files, check) {
|
|
|
9864
10354
|
return "";
|
|
9865
10355
|
}
|
|
9866
10356
|
//#endregion
|
|
10357
|
+
//#region src/runtime/post-outcome.ts
|
|
10358
|
+
/** Extract the additional-context body from a Claude-shaped post response. */
|
|
10359
|
+
function additionalContextOf(stdout) {
|
|
10360
|
+
if (!stdout) return "";
|
|
10361
|
+
try {
|
|
10362
|
+
return JSON.parse(stdout).hookSpecificOutput?.additionalContext ?? "";
|
|
10363
|
+
} catch {
|
|
10364
|
+
return "";
|
|
10365
|
+
}
|
|
10366
|
+
}
|
|
10367
|
+
function cursorPostToolOutput(eventName, designWarn, userMessage, extra) {
|
|
10368
|
+
if (!/^postToolUse$/i.test(eventName)) return "";
|
|
10369
|
+
const context = [
|
|
10370
|
+
designWarn ? formatPrompt(designWarn) : "",
|
|
10371
|
+
designWarn?.userMessage,
|
|
10372
|
+
userMessage,
|
|
10373
|
+
additionalContextOf(extra)
|
|
10374
|
+
].filter(Boolean).join("\n");
|
|
10375
|
+
return context ? JSON.stringify({ additional_context: context }) : "";
|
|
10376
|
+
}
|
|
10377
|
+
/**
|
|
10378
|
+
* Assemble the final PostToolUse response after all side effects and deny paths.
|
|
10379
|
+
* @param input - Final assembly values captured at their original evaluation times.
|
|
10380
|
+
* @returns The native hook outcome.
|
|
10381
|
+
*/
|
|
10382
|
+
function postOutcome(input) {
|
|
10383
|
+
const { id, agentId, sessionId, now, cwd, activities, files, designCacheDir, designWarn, extra, cursorAfterFileEdit, cursorEventName } = input;
|
|
10384
|
+
const noticeLines = [];
|
|
10385
|
+
for (const f of files) {
|
|
10386
|
+
const notice = designPassNotice({
|
|
10387
|
+
agentId,
|
|
10388
|
+
tool: f.tool,
|
|
10389
|
+
filePath: f.filePath ?? "",
|
|
10390
|
+
content: f.content ?? "",
|
|
10391
|
+
url: "",
|
|
10392
|
+
phase: "post"
|
|
10393
|
+
}, designCacheDir);
|
|
10394
|
+
if (notice?.userMessage) noticeLines.push(notice.userMessage);
|
|
10395
|
+
}
|
|
10396
|
+
const notice = noticeLines.length ? {
|
|
10397
|
+
kind: "inform",
|
|
10398
|
+
title: "Design pipeline",
|
|
10399
|
+
reason: "",
|
|
10400
|
+
userMessage: noticeLines.join("\n")
|
|
10401
|
+
} : null;
|
|
10402
|
+
const refNotice = refCreditNoticeFor(activities, sessionId, now, defaultStateDir(cwd));
|
|
10403
|
+
const userMessage = [notice?.userMessage, refNotice].filter(Boolean).join("\n") || void 0;
|
|
10404
|
+
if (cursorAfterFileEdit) return {
|
|
10405
|
+
stdout: "{}",
|
|
10406
|
+
exit: 0
|
|
10407
|
+
};
|
|
10408
|
+
if (id === "cursor") return {
|
|
10409
|
+
stdout: cursorPostToolOutput(cursorEventName, designWarn, userMessage, extra),
|
|
10410
|
+
exit: 0
|
|
10411
|
+
};
|
|
10412
|
+
if (designWarn) return {
|
|
10413
|
+
stdout: respond(id, userMessage ? {
|
|
10414
|
+
...designWarn,
|
|
10415
|
+
userMessage
|
|
10416
|
+
} : designWarn, "PostToolUse"),
|
|
10417
|
+
exit: 0
|
|
10418
|
+
};
|
|
10419
|
+
if (!userMessage) return {
|
|
10420
|
+
stdout: extra,
|
|
10421
|
+
exit: 0
|
|
10422
|
+
};
|
|
10423
|
+
const withUserMessage = notice ? {
|
|
10424
|
+
...notice,
|
|
10425
|
+
userMessage
|
|
10426
|
+
} : {
|
|
10427
|
+
kind: "inform",
|
|
10428
|
+
title: "Compliance",
|
|
10429
|
+
reason: "",
|
|
10430
|
+
userMessage
|
|
10431
|
+
};
|
|
10432
|
+
if (!extra) return {
|
|
10433
|
+
stdout: respond(id, withUserMessage, "PostToolUse"),
|
|
10434
|
+
exit: 0
|
|
10435
|
+
};
|
|
10436
|
+
if (id === "claude-code" || id === "codex") return {
|
|
10437
|
+
stdout: attachSystemMessage(extra, userMessage),
|
|
10438
|
+
exit: 0
|
|
10439
|
+
};
|
|
10440
|
+
if (id === "kimi") return {
|
|
10441
|
+
stdout: respond(id, withUserMessage, "PostToolUse"),
|
|
10442
|
+
exit: 0
|
|
10443
|
+
};
|
|
10444
|
+
return {
|
|
10445
|
+
stdout: respond(id, withUserMessage, "PostToolUse") || extra,
|
|
10446
|
+
exit: 0
|
|
10447
|
+
};
|
|
10448
|
+
}
|
|
10449
|
+
//#endregion
|
|
9867
10450
|
//#region src/runtime/handle-post.ts
|
|
10451
|
+
function isCursorAfterFileEdit(id, payload) {
|
|
10452
|
+
if (id !== "cursor") return false;
|
|
10453
|
+
const hookEvent = typeof payload.hook_event_name === "string" ? payload.hook_event_name : "";
|
|
10454
|
+
if (hookEvent) return /^afterFileEdit$/i.test(hookEvent);
|
|
10455
|
+
return typeof payload.file_path === "string" && Array.isArray(payload.edits);
|
|
10456
|
+
}
|
|
9868
10457
|
/**
|
|
9869
10458
|
* Run the PostToolUse pipeline: store the MCP response, emit a design warning,
|
|
9870
10459
|
* record the activity into the session track, apply per-scope side-effects (SEO
|
|
@@ -9887,8 +10476,14 @@ function firstFileMatch(files, check) {
|
|
|
9887
10476
|
*/
|
|
9888
10477
|
async function handlePost(ctx) {
|
|
9889
10478
|
const { id, payload, event, framework, mcpDir, file, opts } = ctx;
|
|
10479
|
+
const cursorAfterFileEdit = isCursorAfterFileEdit(id, payload);
|
|
9890
10480
|
const designCacheDir = ctx.designCacheDir ?? mcpDir;
|
|
9891
|
-
|
|
10481
|
+
let response = payload.tool_response ?? payload.tool_output;
|
|
10482
|
+
if (id === "cursor" && /^afterMCPExecution$/i.test(event.eventName ?? "")) try {
|
|
10483
|
+
response = typeof payload.result_json === "string" ? JSON.parse(payload.result_json) : payload.result_json;
|
|
10484
|
+
} catch {
|
|
10485
|
+
response = void 0;
|
|
10486
|
+
}
|
|
9892
10487
|
mcpPostStore(event.tool, event.input, response, mcpDir);
|
|
9893
10488
|
const designWarn = designGate(payload, event, designCacheDir, opts.cwd, opts.corpusRoot);
|
|
9894
10489
|
const activities = activityFor({
|
|
@@ -9912,27 +10507,27 @@ async function handlePost(ctx) {
|
|
|
9912
10507
|
const files = fanOutFiles(event);
|
|
9913
10508
|
for (const f of files) postTrackingSideEffects(opts.scope ?? "core", f, f.input, opts.now, payload, opts.cwd);
|
|
9914
10509
|
const seoDeny = opts.scope === "seo" ? seoPostToolUseResponse(payload) : null;
|
|
9915
|
-
if (seoDeny) return {
|
|
10510
|
+
if (seoDeny && !cursorAfterFileEdit) return {
|
|
9916
10511
|
stdout: seoDeny,
|
|
9917
10512
|
exit: 0
|
|
9918
10513
|
};
|
|
9919
10514
|
if (opts.scope === "solid") {
|
|
9920
10515
|
const solidWarn = firstFileMatch(files, checkFileSize);
|
|
9921
|
-
if (solidWarn) return {
|
|
10516
|
+
if (solidWarn && !cursorAfterFileEdit) return {
|
|
9922
10517
|
stdout: solidWarn,
|
|
9923
10518
|
exit: 0
|
|
9924
10519
|
};
|
|
9925
10520
|
}
|
|
9926
10521
|
if (opts.scope === "tailwindcss") {
|
|
9927
10522
|
const tailwindWarn = firstFileMatch(files, validateTailwind);
|
|
9928
|
-
if (tailwindWarn) return {
|
|
10523
|
+
if (tailwindWarn && !cursorAfterFileEdit) return {
|
|
9929
10524
|
stdout: tailwindWarn,
|
|
9930
10525
|
exit: 0
|
|
9931
10526
|
};
|
|
9932
10527
|
}
|
|
9933
10528
|
if (opts.scope === "aipilot" && (event.tool === "TaskCreate" || event.tool === "TaskUpdate" || event.tool === "Write" || event.tool === "Edit")) {
|
|
9934
10529
|
const out = await aipilotPostToolUse(payload, opts.cwd, id);
|
|
9935
|
-
if (out) return {
|
|
10530
|
+
if (out && !cursorAfterFileEdit) return {
|
|
9936
10531
|
stdout: out,
|
|
9937
10532
|
exit: 0
|
|
9938
10533
|
};
|
|
@@ -9942,63 +10537,20 @@ async function handlePost(ctx) {
|
|
|
9942
10537
|
extra = await postEditContext(opts.scope ?? "core", f, opts.now, id);
|
|
9943
10538
|
if (extra) break;
|
|
9944
10539
|
}
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
|
|
9948
|
-
|
|
9949
|
-
|
|
9950
|
-
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
kind: "inform",
|
|
9960
|
-
title: "Design pipeline",
|
|
9961
|
-
reason: "",
|
|
9962
|
-
userMessage: noticeLines.join("\n")
|
|
9963
|
-
} : null;
|
|
9964
|
-
const refNotice = refCreditNoticeFor(activities, event.sessionId, opts.now, defaultStateDir(opts.cwd));
|
|
9965
|
-
const userMessage = [notice?.userMessage, refNotice].filter(Boolean).join("\n") || void 0;
|
|
9966
|
-
if (designWarn) return {
|
|
9967
|
-
stdout: respond(id, userMessage ? {
|
|
9968
|
-
...designWarn,
|
|
9969
|
-
userMessage
|
|
9970
|
-
} : designWarn, "PostToolUse"),
|
|
9971
|
-
exit: 0
|
|
9972
|
-
};
|
|
9973
|
-
if (!userMessage) return {
|
|
9974
|
-
stdout: extra,
|
|
9975
|
-
exit: 0
|
|
9976
|
-
};
|
|
9977
|
-
const withUserMessage = notice ? {
|
|
9978
|
-
...notice,
|
|
9979
|
-
userMessage
|
|
9980
|
-
} : {
|
|
9981
|
-
kind: "inform",
|
|
9982
|
-
title: "Compliance",
|
|
9983
|
-
reason: "",
|
|
9984
|
-
userMessage
|
|
9985
|
-
};
|
|
9986
|
-
if (!extra) return {
|
|
9987
|
-
stdout: respond(id, withUserMessage, "PostToolUse"),
|
|
9988
|
-
exit: 0
|
|
9989
|
-
};
|
|
9990
|
-
if (id === "claude-code" || id === "codex") return {
|
|
9991
|
-
stdout: attachSystemMessage(extra, userMessage),
|
|
9992
|
-
exit: 0
|
|
9993
|
-
};
|
|
9994
|
-
if (id === "kimi") return {
|
|
9995
|
-
stdout: respond(id, withUserMessage, "PostToolUse"),
|
|
9996
|
-
exit: 0
|
|
9997
|
-
};
|
|
9998
|
-
return {
|
|
9999
|
-
stdout: respond(id, withUserMessage, "PostToolUse") || extra,
|
|
10000
|
-
exit: 0
|
|
10001
|
-
};
|
|
10540
|
+
return postOutcome({
|
|
10541
|
+
id,
|
|
10542
|
+
agentId: typeof payload.agent_id === "string" ? payload.agent_id : "",
|
|
10543
|
+
sessionId: event.sessionId,
|
|
10544
|
+
now: opts.now,
|
|
10545
|
+
cwd: opts.cwd,
|
|
10546
|
+
activities,
|
|
10547
|
+
files,
|
|
10548
|
+
designCacheDir,
|
|
10549
|
+
designWarn,
|
|
10550
|
+
extra,
|
|
10551
|
+
cursorAfterFileEdit,
|
|
10552
|
+
cursorEventName: typeof payload.hook_event_name === "string" ? payload.hook_event_name : ""
|
|
10553
|
+
});
|
|
10002
10554
|
}
|
|
10003
10555
|
//#endregion
|
|
10004
10556
|
//#region src/runtime/handle-scope-async.ts
|
|
@@ -10018,9 +10570,12 @@ async function handlePost(ctx) {
|
|
|
10018
10570
|
* @returns The native stdout when intercepted, or `null` to fall through.
|
|
10019
10571
|
*/
|
|
10020
10572
|
async function asyncScopeStdout(scope, event, payload, cwd, now, id = "claude-code") {
|
|
10021
|
-
|
|
10022
|
-
if (
|
|
10023
|
-
|
|
10573
|
+
const dispatchedEvent = id === "cursor" ? cursorEventContract(event).lifecycle : event;
|
|
10574
|
+
if (dispatchedEvent === null) return null;
|
|
10575
|
+
let stdout = null;
|
|
10576
|
+
if (scope === "aipilot") stdout = await dispatchAipilot(dispatchedEvent, payload, cwd, now, void 0, id);
|
|
10577
|
+
if (scope === "memory") stdout = await dispatchMemory(dispatchedEvent, payload, cwd, now);
|
|
10578
|
+
return id === "cursor" && stdout !== null ? toCursorLifecycleResponse(stdout, event) : stdout;
|
|
10024
10579
|
}
|
|
10025
10580
|
//#endregion
|
|
10026
10581
|
//#region src/runtime/design-cache-resolve.ts
|
|
@@ -10489,6 +11044,18 @@ function handleConfirmSubmit(sessionId, text, now, home) {
|
|
|
10489
11044
|
} catch {}
|
|
10490
11045
|
}
|
|
10491
11046
|
//#endregion
|
|
11047
|
+
//#region src/runtime/confirm/codex-prompt-origin.ts
|
|
11048
|
+
const AGENT_FIELDS = ["agent_id", "agent_type"];
|
|
11049
|
+
/**
|
|
11050
|
+
* Classify Codex prompt provenance from its optional agent metadata.
|
|
11051
|
+
* Missing metadata is the observed root shape; malformed metadata fails closed.
|
|
11052
|
+
*/
|
|
11053
|
+
function codexPromptOrigin(payload) {
|
|
11054
|
+
const present = AGENT_FIELDS.filter((field) => Object.hasOwn(payload, field));
|
|
11055
|
+
if (present.length === 0) return "root";
|
|
11056
|
+
return present.every((field) => typeof payload[field] === "string" && payload[field].trim().length > 0) ? "subagent" : "unknown";
|
|
11057
|
+
}
|
|
11058
|
+
//#endregion
|
|
10492
11059
|
//#region src/runtime/handle.ts
|
|
10493
11060
|
/** Raw Claude hook event name from a payload (empty when absent). */
|
|
10494
11061
|
function rawEventName(payload) {
|
|
@@ -10500,8 +11067,18 @@ function rawEventName(payload) {
|
|
|
10500
11067
|
* POST event it records the activity into the track. The loop that makes the
|
|
10501
11068
|
* package behave like the Claude plugin, on any harness.
|
|
10502
11069
|
*/
|
|
10503
|
-
async function
|
|
11070
|
+
async function handleHookCore(id, payload, opts) {
|
|
10504
11071
|
const event = normalizeEvent(id, payload);
|
|
11072
|
+
if (id === "cursor") {
|
|
11073
|
+
const cursorCwd = cursorProjectCwd(event.cwd, event.workspaceRoots ?? [], event.filePath, opts.cwd);
|
|
11074
|
+
if (cursorCwd !== opts.cwd) opts = {
|
|
11075
|
+
...opts,
|
|
11076
|
+
cwd: cursorCwd
|
|
11077
|
+
};
|
|
11078
|
+
}
|
|
11079
|
+
const rawPrompt = payload.prompt;
|
|
11080
|
+
const userPrompt = typeof rawPrompt === "string" || Array.isArray(rawPrompt) ? promptText(rawPrompt) : void 0;
|
|
11081
|
+
if (id === "codex" && rawEventName(payload) === "UserPromptSubmit" && userPrompt !== void 0) submitCodexConfirmation(event.sessionId, userPrompt, opts.now, opts.home, codexPromptOrigin(payload));
|
|
10505
11082
|
resetFragmentRegistry();
|
|
10506
11083
|
const layout = projectLayout(opts.cwd);
|
|
10507
11084
|
const file = trackFile(event.sessionId, defaultStateDir(opts.cwd));
|
|
@@ -10523,10 +11100,8 @@ async function handleHook(id, payload, opts) {
|
|
|
10523
11100
|
stdout: id === "claude-code" ? attachBudgetRecap(life, rawEventName(payload), event.sessionId, opts.cwd, opts.now) : life,
|
|
10524
11101
|
exit: 0
|
|
10525
11102
|
};
|
|
10526
|
-
const rawPrompt = payload.prompt;
|
|
10527
|
-
const userPrompt = typeof rawPrompt === "string" || Array.isArray(rawPrompt) ? promptText(rawPrompt) : void 0;
|
|
10528
11103
|
if (userPrompt !== void 0) {
|
|
10529
|
-
handleConfirmSubmit(event.sessionId, userPrompt, opts.now, opts.home);
|
|
11104
|
+
if (id !== "codex") handleConfirmSubmit(event.sessionId, userPrompt, opts.now, opts.home);
|
|
10530
11105
|
await withTrack(file, (track) => recordBrainstormRequired(track, detectCreationIntent(userPrompt)));
|
|
10531
11106
|
return {
|
|
10532
11107
|
stdout: promptSubmitContext(userPrompt, opts.cwd, id),
|
|
@@ -10554,5 +11129,17 @@ async function handleHook(id, payload, opts) {
|
|
|
10554
11129
|
opts
|
|
10555
11130
|
});
|
|
10556
11131
|
}
|
|
11132
|
+
/**
|
|
11133
|
+
* Run one hook and adapt every Cursor scope outcome at the common runtime exit.
|
|
11134
|
+
* Other harnesses retain the core handler's stdout and exit status unchanged.
|
|
11135
|
+
*/
|
|
11136
|
+
async function handleHook(id, payload, opts) {
|
|
11137
|
+
const outcome = await handleHookCore(id, payload, opts);
|
|
11138
|
+
if (id !== "cursor") return outcome;
|
|
11139
|
+
return {
|
|
11140
|
+
...outcome,
|
|
11141
|
+
stdout: toCursorLifecycleResponse(outcome.stdout, rawEventName(payload))
|
|
11142
|
+
};
|
|
11143
|
+
}
|
|
10557
11144
|
//#endregion
|
|
10558
|
-
export {
|
|
11145
|
+
export { runDoctor as $, trackWatchResearch as A, taskContext as At, writePluginMap as B, todayUtc as Bt, postEditContext as C, removeOldFiles as Ct, securityAdvisory as D, projectContext as Dt, dispatchMemory as E, gitContext as Et, aipilotPostToolUse as F, isoUtc as Ft, mergeLines as G, isProject as H, dispatchAipilot as I, loadSecurityState as It, listChildren as J, countFiles as K, dispatchLessons as L, saveSecurityState as Lt, trackSkillRead as M, projectHash$1 as Mt, trackEnrichment as N, trackFile as Nt, securityAdvisoryForPatch as O, claudeMdKey as Ot, dispatchLifecycle as P, normalizeEvent as Pt, notify as Q, cartoSessionStart as R, securityStateDir as Rt, lifecycleStdout as S, purgeTtlTree as St, seoPostToolUseResponse as T, devContext as Tt, writeTree as U, generateProjectMap as V, loadEnriched as W, lessonsFileFor as X, lessonsArchiveFileFor as Y, lessonsStateFileFor as Z, gateCommandCandidates as _, injectRules as _t, recordActivity as a, cleanupSession as at, dryGate as b, sessionStartCore as bt, MCP_TTL_MS as c, validateTeammateOutput as ct, isMcpTool as d, validateTailwind as dt, runningVersion as et, queryOf as f, validateSolidGate as ft, gate as g, solidDetectStart as gt, TRIVIAL_BUDGET as h, detectSolidProfile as ht, respond as i, validateRulesLoaded as it, trackMcpResearch as j, defaultStateDir as jt, postTrackingSideEffects as k, promptSubmitContext as kt, WEBFETCH_TTL_MS as l, trackAgentMemory as lt, REQUIRED_AGENTS as m, countLoc as mt, activityFor as n, postEditTypescript as nt, mcpPostStore as o, saveApexState as ot, DEFAULT_WINDOW_MS as p, checkFileSize as pt, getFileDesc as q, handlePre as r, trackSessionChanges as rt, mcpPreIntercept as s, logToolFailure as st, handleHook as t, versionBanner as tt, cacheQueryOf as u, subagentCacheContext as ut, preCommitGate as v, readRules as vt, seoPostToolUse as w, trimLogFile as wt, extractSymbols as x, pruneEmptyDirs as xt, detectDuplication as y, runSessionStartCleanups as yt, generateEcosystemMap as z, securityStatePath as zt };
|