clinkx 0.2.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/LICENSE +10 -0
- package/README.md +113 -18
- package/clinkx-workflows/dist/allowed-roots.d.ts +1 -0
- package/clinkx-workflows/dist/allowed-roots.js +5 -0
- package/clinkx-workflows/dist/allowed-roots.js.map +1 -0
- package/clinkx-workflows/dist/artifacts.d.ts +20 -1
- package/clinkx-workflows/dist/artifacts.js +38 -9
- package/clinkx-workflows/dist/artifacts.js.map +1 -1
- package/clinkx-workflows/dist/backend.d.ts +4 -0
- package/clinkx-workflows/dist/citation-check.d.ts +40 -0
- package/clinkx-workflows/dist/citation-check.js +348 -0
- package/clinkx-workflows/dist/citation-check.js.map +1 -0
- package/clinkx-workflows/dist/clink-client.d.ts +2 -1
- package/clinkx-workflows/dist/clink-client.js +17 -4
- package/clinkx-workflows/dist/clink-client.js.map +1 -1
- package/clinkx-workflows/dist/config.d.ts +145 -16
- package/clinkx-workflows/dist/config.js +384 -26
- package/clinkx-workflows/dist/config.js.map +1 -1
- package/clinkx-workflows/dist/contained-file.d.ts +172 -0
- package/clinkx-workflows/dist/contained-file.js +483 -0
- package/clinkx-workflows/dist/contained-file.js.map +1 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-contract.d.ts +416 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-contract.js +328 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-contract.js.map +1 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-router.d.ts +24 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-router.js +39 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-router.js.map +1 -0
- package/clinkx-workflows/dist/context-cache-key.d.ts +53 -0
- package/clinkx-workflows/dist/context-cache-key.js +91 -0
- package/clinkx-workflows/dist/context-cache-key.js.map +1 -0
- package/clinkx-workflows/dist/context-mode-resolver.d.ts +27 -0
- package/clinkx-workflows/dist/context-mode-resolver.js +126 -0
- package/clinkx-workflows/dist/context-mode-resolver.js.map +1 -0
- package/clinkx-workflows/dist/context-preflight.d.ts +184 -0
- package/clinkx-workflows/dist/context-preflight.js +91 -0
- package/clinkx-workflows/dist/context-preflight.js.map +1 -0
- package/clinkx-workflows/dist/council-expander.d.ts +1 -1
- package/clinkx-workflows/dist/council-expander.js +2 -1
- package/clinkx-workflows/dist/council-expander.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +165 -0
- package/clinkx-workflows/dist/dashboard/activity-contract.js +105 -0
- package/clinkx-workflows/dist/dashboard/activity-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/activity-lease.d.ts +25 -0
- package/clinkx-workflows/dist/dashboard/activity-lease.js +34 -0
- package/clinkx-workflows/dist/dashboard/activity-lease.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/activity-reader.d.ts +86 -0
- package/clinkx-workflows/dist/dashboard/activity-reader.js +480 -0
- package/clinkx-workflows/dist/dashboard/activity-reader.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/api-routes.d.ts +36 -0
- package/clinkx-workflows/dist/dashboard/api-routes.js +1047 -0
- package/clinkx-workflows/dist/dashboard/api-routes.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/artifact-index.d.ts +37 -0
- package/clinkx-workflows/dist/dashboard/artifact-index.js +145 -0
- package/clinkx-workflows/dist/dashboard/artifact-index.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/challenge-store.d.ts +31 -0
- package/clinkx-workflows/dist/dashboard/auth/challenge-store.js +65 -0
- package/clinkx-workflows/dist/dashboard/auth/challenge-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/controller.d.ts +26 -0
- package/clinkx-workflows/dist/dashboard/auth/controller.js +596 -0
- package/clinkx-workflows/dist/dashboard/auth/controller.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/credential-store.d.ts +60 -0
- package/clinkx-workflows/dist/dashboard/auth/credential-store.js +776 -0
- package/clinkx-workflows/dist/dashboard/auth/credential-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.d.ts +9 -0
- package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.js +44 -0
- package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/session-store.d.ts +17 -0
- package/clinkx-workflows/dist/dashboard/auth/session-store.js +77 -0
- package/clinkx-workflows/dist/dashboard/auth/session-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/webauthn-service.d.ts +25 -0
- package/clinkx-workflows/dist/dashboard/auth/webauthn-service.js +71 -0
- package/clinkx-workflows/dist/dashboard/auth/webauthn-service.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/context-run-liveness.d.ts +37 -0
- package/clinkx-workflows/dist/dashboard/context-run-liveness.js +49 -0
- package/clinkx-workflows/dist/dashboard/context-run-liveness.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/context-run-reader.d.ts +222 -0
- package/clinkx-workflows/dist/dashboard/context-run-reader.js +1178 -0
- package/clinkx-workflows/dist/dashboard/context-run-reader.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/contracts.d.ts +8936 -0
- package/clinkx-workflows/dist/dashboard/contracts.js +1157 -0
- package/clinkx-workflows/dist/dashboard/contracts.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/generation.d.ts +14 -0
- package/clinkx-workflows/dist/dashboard/generation.js +41 -0
- package/clinkx-workflows/dist/dashboard/generation.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/index.d.ts +28 -0
- package/clinkx-workflows/dist/dashboard/index.js +16 -0
- package/clinkx-workflows/dist/dashboard/index.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/lazy-content.d.ts +101 -0
- package/clinkx-workflows/dist/dashboard/lazy-content.js +748 -0
- package/clinkx-workflows/dist/dashboard/lazy-content.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-contract.d.ts +303 -0
- package/clinkx-workflows/dist/dashboard/progress-report-contract.js +221 -0
- package/clinkx-workflows/dist/dashboard/progress-report-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-reader.d.ts +50 -0
- package/clinkx-workflows/dist/dashboard/progress-report-reader.js +581 -0
- package/clinkx-workflows/dist/dashboard/progress-report-reader.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.d.ts +89 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.js +71 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.d.ts +28 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.js +110 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/read-model.d.ts +86 -0
- package/clinkx-workflows/dist/dashboard/read-model.js +440 -0
- package/clinkx-workflows/dist/dashboard/read-model.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/request-admission.d.ts +31 -0
- package/clinkx-workflows/dist/dashboard/request-admission.js +126 -0
- package/clinkx-workflows/dist/dashboard/request-admission.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/scanner.d.ts +75 -0
- package/clinkx-workflows/dist/dashboard/scanner.js +1110 -0
- package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/server-factory.d.ts +50 -0
- package/clinkx-workflows/dist/dashboard/server-factory.js +431 -0
- package/clinkx-workflows/dist/dashboard/server-factory.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +2611 -0
- package/clinkx-workflows/dist/dashboard/session-log-contract.js +480 -0
- package/clinkx-workflows/dist/dashboard/session-log-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-identity.d.ts +21 -0
- package/clinkx-workflows/dist/dashboard/session-log-identity.js +101 -0
- package/clinkx-workflows/dist/dashboard/session-log-identity.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-journal.d.ts +43 -0
- package/clinkx-workflows/dist/dashboard/session-log-journal.js +522 -0
- package/clinkx-workflows/dist/dashboard/session-log-journal.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-reader.d.ts +61 -0
- package/clinkx-workflows/dist/dashboard/session-log-reader.js +378 -0
- package/clinkx-workflows/dist/dashboard/session-log-reader.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-redaction.d.ts +6 -0
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js +367 -0
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-retention.d.ts +23 -0
- package/clinkx-workflows/dist/dashboard/session-log-retention.js +212 -0
- package/clinkx-workflows/dist/dashboard/session-log-retention.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/static-responses.d.ts +10 -0
- package/clinkx-workflows/dist/dashboard/static-responses.js +94 -0
- package/clinkx-workflows/dist/dashboard/static-responses.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/worker-lock-observer.d.ts +28 -0
- package/clinkx-workflows/dist/dashboard/worker-lock-observer.js +241 -0
- package/clinkx-workflows/dist/dashboard/worker-lock-observer.js.map +1 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/index-DOkMXTfB.js +25 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/style-ChJsn0KD.css +1 -0
- package/clinkx-workflows/dist/dashboard-ui/contracts/council-plan-review-input-v1.json +158 -0
- package/clinkx-workflows/dist/dashboard-ui/index.html +15 -0
- package/clinkx-workflows/dist/dashboard-ui/licenses/IBM-Plex-Mono-OFL.txt +93 -0
- package/clinkx-workflows/dist/dashboard-ui/licenses/IBM-Plex-Sans-OFL.txt +93 -0
- package/clinkx-workflows/dist/dashboard-ui/licenses/JetBrains-Mono-OFL.txt +93 -0
- package/clinkx-workflows/dist/dashboard-ui/licenses/NOTICE.md +32 -0
- package/clinkx-workflows/dist/dashboard-ui/theme-init.js +8 -0
- package/clinkx-workflows/dist/definition-normalizer.d.ts +19 -0
- package/clinkx-workflows/dist/definition-normalizer.js.map +1 -1
- package/clinkx-workflows/dist/delegate-validate.d.ts +78 -0
- package/clinkx-workflows/dist/delegate-validate.js +97 -0
- package/clinkx-workflows/dist/delegate-validate.js.map +1 -0
- package/clinkx-workflows/dist/engine.d.ts +141 -0
- package/clinkx-workflows/dist/engine.js +882 -28
- package/clinkx-workflows/dist/engine.js.map +1 -1
- package/clinkx-workflows/dist/errors.d.ts +2 -0
- package/clinkx-workflows/dist/errors.js.map +1 -1
- package/clinkx-workflows/dist/fingerprint.d.ts +1 -1
- package/clinkx-workflows/dist/fingerprint.js +1 -1
- package/clinkx-workflows/dist/index.d.ts +19 -0
- package/clinkx-workflows/dist/index.js +15 -0
- package/clinkx-workflows/dist/index.js.map +1 -1
- package/clinkx-workflows/dist/loader.d.ts +9 -3
- package/clinkx-workflows/dist/loader.js +92 -21
- package/clinkx-workflows/dist/loader.js.map +1 -1
- package/clinkx-workflows/dist/plan-review/admission.d.ts +160 -0
- package/clinkx-workflows/dist/plan-review/admission.js +290 -0
- package/clinkx-workflows/dist/plan-review/admission.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/context-policy.d.ts +55 -0
- package/clinkx-workflows/dist/plan-review/context-policy.js +146 -0
- package/clinkx-workflows/dist/plan-review/context-policy.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/contract.d.ts +763 -0
- package/clinkx-workflows/dist/plan-review/contract.js +289 -0
- package/clinkx-workflows/dist/plan-review/contract.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/digest.d.ts +68 -0
- package/clinkx-workflows/dist/plan-review/digest.js +241 -0
- package/clinkx-workflows/dist/plan-review/digest.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/discovery.d.ts +45 -0
- package/clinkx-workflows/dist/plan-review/discovery.js +36 -0
- package/clinkx-workflows/dist/plan-review/discovery.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/execution-contract.d.ts +298 -0
- package/clinkx-workflows/dist/plan-review/execution-contract.js +194 -0
- package/clinkx-workflows/dist/plan-review/execution-contract.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/expander.d.ts +48 -0
- package/clinkx-workflows/dist/plan-review/expander.js +243 -0
- package/clinkx-workflows/dist/plan-review/expander.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/projector.d.ts +126 -0
- package/clinkx-workflows/dist/plan-review/projector.js +437 -0
- package/clinkx-workflows/dist/plan-review/projector.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/publication.d.ts +95 -0
- package/clinkx-workflows/dist/plan-review/publication.js +182 -0
- package/clinkx-workflows/dist/plan-review/publication.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/ranking-reducer.d.ts +34 -0
- package/clinkx-workflows/dist/plan-review/ranking-reducer.js +139 -0
- package/clinkx-workflows/dist/plan-review/ranking-reducer.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/resume.d.ts +38 -0
- package/clinkx-workflows/dist/plan-review/resume.js +122 -0
- package/clinkx-workflows/dist/plan-review/resume.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/rubric.d.ts +7 -0
- package/clinkx-workflows/dist/plan-review/rubric.js +24 -0
- package/clinkx-workflows/dist/plan-review/rubric.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/run-state.d.ts +398 -0
- package/clinkx-workflows/dist/plan-review/run-state.js +183 -0
- package/clinkx-workflows/dist/plan-review/run-state.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/runtime-policy.d.ts +74 -0
- package/clinkx-workflows/dist/plan-review/runtime-policy.js +186 -0
- package/clinkx-workflows/dist/plan-review/runtime-policy.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/snapshot.d.ts +81 -0
- package/clinkx-workflows/dist/plan-review/snapshot.js +170 -0
- package/clinkx-workflows/dist/plan-review/snapshot.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/stage-error.d.ts +19 -0
- package/clinkx-workflows/dist/plan-review/stage-error.js +20 -0
- package/clinkx-workflows/dist/plan-review/stage-error.js.map +1 -0
- package/clinkx-workflows/dist/post-processors/index.d.ts +20 -0
- package/clinkx-workflows/dist/post-processors/index.js +21 -0
- package/clinkx-workflows/dist/post-processors/index.js.map +1 -0
- package/clinkx-workflows/dist/prompt-budget.d.ts +7 -0
- package/clinkx-workflows/dist/prompt-budget.js +7 -1
- package/clinkx-workflows/dist/prompt-budget.js.map +1 -1
- package/clinkx-workflows/dist/reducers/index.js +2 -0
- package/clinkx-workflows/dist/reducers/index.js.map +1 -1
- package/clinkx-workflows/dist/run-lock-heartbeat.d.ts +2 -0
- package/clinkx-workflows/dist/run-lock-heartbeat.js +3 -0
- package/clinkx-workflows/dist/run-lock-heartbeat.js.map +1 -0
- package/clinkx-workflows/dist/run-summary/cards/council-plan-review.d.ts +34 -0
- package/clinkx-workflows/dist/run-summary/cards/council-plan-review.js +74 -0
- package/clinkx-workflows/dist/run-summary/cards/council-plan-review.js.map +1 -0
- package/clinkx-workflows/dist/run-summary/cards/index.js +5 -0
- package/clinkx-workflows/dist/run-summary/cards/index.js.map +1 -1
- package/clinkx-workflows/dist/run-summary.d.ts +13 -0
- package/clinkx-workflows/dist/run-summary.js +29 -1
- package/clinkx-workflows/dist/run-summary.js.map +1 -1
- package/clinkx-workflows/dist/schema.d.ts +76 -21
- package/clinkx-workflows/dist/schema.js +10 -1
- package/clinkx-workflows/dist/schema.js.map +1 -1
- package/clinkx-workflows/dist/seed-terms.d.ts +30 -0
- package/clinkx-workflows/dist/seed-terms.js +176 -0
- package/clinkx-workflows/dist/seed-terms.js.map +1 -0
- package/clinkx-workflows/dist/server.d.ts +5 -2
- package/clinkx-workflows/dist/server.js +82 -52
- package/clinkx-workflows/dist/server.js.map +1 -1
- package/clinkx-workflows/dist/shutdown.d.ts +18 -20
- package/clinkx-workflows/dist/shutdown.js +43 -61
- package/clinkx-workflows/dist/shutdown.js.map +1 -1
- package/clinkx-workflows/dist/state-schema.d.ts +38 -0
- package/clinkx-workflows/dist/state.d.ts +170 -3
- package/clinkx-workflows/dist/state.js +1275 -28
- package/clinkx-workflows/dist/state.js.map +1 -1
- package/clinkx-workflows/dist/transport.d.ts +15 -0
- package/clinkx-workflows/dist/transport.js +19 -10
- package/clinkx-workflows/dist/transport.js.map +1 -1
- package/clinkx-workflows/dist/types.d.ts +14 -2
- package/clinkx-workflows/dist/validators/council.d.ts +318 -85
- package/clinkx-workflows/dist/validators/council.js +52 -13
- package/clinkx-workflows/dist/validators/council.js.map +1 -1
- package/clinkx-workflows/dist/validators/index.d.ts +1 -0
- package/clinkx-workflows/dist/validators/index.js +3 -0
- package/clinkx-workflows/dist/validators/index.js.map +1 -1
- package/clinkx-workflows/dist/validators/plan-review.d.ts +398 -0
- package/clinkx-workflows/dist/validators/plan-review.js +316 -0
- package/clinkx-workflows/dist/validators/plan-review.js.map +1 -0
- package/clinkx-workflows/dist/workflow-receipt.js +188 -4
- package/clinkx-workflows/dist/workflow-receipt.js.map +1 -1
- package/clinkx-workflows/dist/workflow-tools.d.ts +48 -3
- package/clinkx-workflows/dist/workflow-tools.js +1402 -59
- package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
- package/clinkx-workflows/dist/workspace-signature.d.ts +16 -0
- package/clinkx-workflows/dist/workspace-signature.js +388 -0
- package/clinkx-workflows/dist/workspace-signature.js.map +1 -0
- package/clinkx-workflows/templates/council/answer-specialist.txt +2 -1
- package/clinkx-workflows/templates/council/code-review-chairman.txt +13 -0
- package/clinkx-workflows/templates/council/code-review-cross-reviewer.txt +4 -1
- package/clinkx-workflows/templates/council/code-review-reviewer.txt +19 -3
- package/clinkx-workflows/templates/council/debug-debugger.txt +2 -1
- package/clinkx-workflows/templates/council/discover-analyst.txt +2 -1
- package/clinkx-workflows/templates/council/member.txt +2 -1
- package/clinkx-workflows/templates/council/plan-review-chairman.txt +68 -0
- package/clinkx-workflows/templates/council/plan-review-cross-reviewer.txt +51 -0
- package/clinkx-workflows/templates/council/plan-review-reviewer.txt +60 -0
- package/clinkx-workflows/templates/plan-review/chairman.txt +48 -0
- package/clinkx-workflows/templates/plan-review/cross-review.txt +52 -0
- package/clinkx-workflows/templates/plan-review/ranking.txt +16 -0
- package/clinkx-workflows/templates/plan-review/reviewer.txt +57 -0
- package/clinkx-workflows/workflows/council-answer.yaml +97 -8
- package/clinkx-workflows/workflows/council-code-review.yaml +103 -8
- package/clinkx-workflows/workflows/council-debug.yaml +97 -8
- package/clinkx-workflows/workflows/council-default.yaml +97 -8
- package/clinkx-workflows/workflows/council-discover.yaml +97 -8
- package/clinkx-workflows/workflows/council-plan-review.yaml +250 -0
- package/clinkx-workflows/workflows/plan-review-receipts.yaml +230 -0
- package/conf/adapters/agy.json +49 -0
- package/conf/adapters/claude.json +5 -0
- package/conf/adapters/codex-app-server.json +44 -0
- package/conf/adapters/codex.json +7 -2
- package/conf/adapters/glm.json +5 -0
- package/conf/adapters/grok.json +60 -0
- package/conf/adapters/hapi/agy.json +52 -0
- package/conf/adapters/hapi/claude.json +5 -0
- package/conf/adapters/hapi/codex.json +7 -2
- package/conf/adapters/hapi/glm.json +5 -0
- package/conf/prompts/json-codereviewer.txt +1 -0
- package/conf/prompts/json.txt +1 -0
- package/dist/activity-journal.d.ts +42 -0
- package/dist/activity-journal.js +381 -0
- package/dist/activity-journal.js.map +1 -0
- package/dist/allowed-roots.d.ts +53 -0
- package/dist/allowed-roots.js +216 -0
- package/dist/allowed-roots.js.map +1 -0
- package/dist/codex/activity-session-log.d.ts +14 -0
- package/dist/codex/activity-session-log.js +206 -0
- package/dist/codex/activity-session-log.js.map +1 -0
- package/dist/codex/agent-tools.d.ts +23 -0
- package/dist/codex/agent-tools.js +1270 -0
- package/dist/codex/agent-tools.js.map +1 -0
- package/dist/codex/app-server-client.d.ts +76 -0
- package/dist/codex/app-server-client.js +582 -0
- package/dist/codex/app-server-client.js.map +1 -0
- package/dist/codex/app-server-registry.d.ts +389 -0
- package/dist/codex/app-server-registry.js +1407 -0
- package/dist/codex/app-server-registry.js.map +1 -0
- package/dist/codex/client-types.d.ts +109 -0
- package/dist/codex/client-types.js +2 -0
- package/dist/codex/client-types.js.map +1 -0
- package/dist/codex/protocol.d.ts +5375 -0
- package/dist/codex/protocol.js +614 -0
- package/dist/codex/protocol.js.map +1 -0
- package/dist/codex/session.d.ts +263 -0
- package/dist/codex/session.js +466 -0
- package/dist/codex/session.js.map +1 -0
- package/dist/config.d.ts +185 -19
- package/dist/config.js +82 -16
- package/dist/config.js.map +1 -1
- package/dist/context-builder/anchors.d.ts +12 -0
- package/dist/context-builder/anchors.js +22 -0
- package/dist/context-builder/anchors.js.map +1 -0
- package/dist/context-builder/bundle.d.ts +109 -0
- package/dist/context-builder/bundle.js +1498 -0
- package/dist/context-builder/bundle.js.map +1 -0
- package/dist/context-builder/codemap.d.ts +22 -0
- package/dist/context-builder/codemap.js +675 -0
- package/dist/context-builder/codemap.js.map +1 -0
- package/dist/context-builder/config.d.ts +61 -0
- package/dist/context-builder/config.js +216 -0
- package/dist/context-builder/config.js.map +1 -0
- package/dist/context-builder/context-run-catalog.d.ts +17 -0
- package/dist/context-builder/context-run-catalog.js +99 -0
- package/dist/context-builder/context-run-catalog.js.map +1 -0
- package/dist/context-builder/delegate-packet.d.ts +219 -0
- package/dist/context-builder/delegate-packet.js +1190 -0
- package/dist/context-builder/delegate-packet.js.map +1 -0
- package/dist/context-builder/delegate-reconstruction.d.ts +13 -0
- package/dist/context-builder/delegate-reconstruction.js +220 -0
- package/dist/context-builder/delegate-reconstruction.js.map +1 -0
- package/dist/context-builder/delegate-validation.d.ts +27 -0
- package/dist/context-builder/delegate-validation.js +479 -0
- package/dist/context-builder/delegate-validation.js.map +1 -0
- package/dist/context-builder/delegate.d.ts +26 -0
- package/dist/context-builder/delegate.js +134 -0
- package/dist/context-builder/delegate.js.map +1 -0
- package/dist/context-builder/diagnostics.d.ts +36 -0
- package/dist/context-builder/diagnostics.js +107 -0
- package/dist/context-builder/diagnostics.js.map +1 -0
- package/dist/context-builder/discovery.d.ts +146 -0
- package/dist/context-builder/discovery.js +2129 -0
- package/dist/context-builder/discovery.js.map +1 -0
- package/dist/context-builder/globs.d.ts +14 -0
- package/dist/context-builder/globs.js +175 -0
- package/dist/context-builder/globs.js.map +1 -0
- package/dist/context-builder/handler.d.ts +41 -0
- package/dist/context-builder/handler.js +2590 -0
- package/dist/context-builder/handler.js.map +1 -0
- package/dist/context-builder/lifecycle-contract.d.ts +424 -0
- package/dist/context-builder/lifecycle-contract.js +523 -0
- package/dist/context-builder/lifecycle-contract.js.map +1 -0
- package/dist/context-builder/lifecycle-journal.d.ts +62 -0
- package/dist/context-builder/lifecycle-journal.js +88 -0
- package/dist/context-builder/lifecycle-journal.js.map +1 -0
- package/dist/context-builder/lifecycle-observer.d.ts +102 -0
- package/dist/context-builder/lifecycle-observer.js +258 -0
- package/dist/context-builder/lifecycle-observer.js.map +1 -0
- package/dist/context-builder/lifecycle-reconcile.d.ts +32 -0
- package/dist/context-builder/lifecycle-reconcile.js +191 -0
- package/dist/context-builder/lifecycle-reconcile.js.map +1 -0
- package/dist/context-builder/lifecycle-recovery.d.ts +2 -0
- package/dist/context-builder/lifecycle-recovery.js +14 -0
- package/dist/context-builder/lifecycle-recovery.js.map +1 -0
- package/dist/context-builder/path-signals.d.ts +7 -0
- package/dist/context-builder/path-signals.js +70 -0
- package/dist/context-builder/path-signals.js.map +1 -0
- package/dist/context-builder/paths.d.ts +30 -0
- package/dist/context-builder/paths.js +153 -0
- package/dist/context-builder/paths.js.map +1 -0
- package/dist/context-builder/progress.d.ts +17 -0
- package/dist/context-builder/progress.js +93 -0
- package/dist/context-builder/progress.js.map +1 -0
- package/dist/context-builder/receipt.d.ts +91 -0
- package/dist/context-builder/receipt.js +180 -0
- package/dist/context-builder/receipt.js.map +1 -0
- package/dist/context-builder/refine-handler.d.ts +14 -0
- package/dist/context-builder/refine-handler.js +790 -0
- package/dist/context-builder/refine-handler.js.map +1 -0
- package/dist/context-builder/review-coverage.d.ts +84 -0
- package/dist/context-builder/review-coverage.js +437 -0
- package/dist/context-builder/review-coverage.js.map +1 -0
- package/dist/context-builder/run-id.d.ts +7 -0
- package/dist/context-builder/run-id.js +31 -0
- package/dist/context-builder/run-id.js.map +1 -0
- package/dist/context-builder/schema.d.ts +669 -0
- package/dist/context-builder/schema.js +399 -0
- package/dist/context-builder/schema.js.map +1 -0
- package/dist/context-builder/secrets.d.ts +41 -0
- package/dist/context-builder/secrets.js +1651 -0
- package/dist/context-builder/secrets.js.map +1 -0
- package/dist/context-builder/selection.d.ts +56 -0
- package/dist/context-builder/selection.js +1111 -0
- package/dist/context-builder/selection.js.map +1 -0
- package/dist/context-builder/session-log-identity.d.ts +1 -0
- package/dist/context-builder/session-log-identity.js +66 -0
- package/dist/context-builder/session-log-identity.js.map +1 -0
- package/dist/context-builder/slice-range-math.d.ts +16 -0
- package/dist/context-builder/slice-range-math.js +137 -0
- package/dist/context-builder/slice-range-math.js.map +1 -0
- package/dist/context-builder/status.d.ts +66 -0
- package/dist/context-builder/status.js +160 -0
- package/dist/context-builder/status.js.map +1 -0
- package/dist/context-builder/storage.d.ts +118 -0
- package/dist/context-builder/storage.js +1003 -0
- package/dist/context-builder/storage.js.map +1 -0
- package/dist/context-builder/token-estimate.d.ts +19 -0
- package/dist/context-builder/token-estimate.js +90 -0
- package/dist/context-builder/token-estimate.js.map +1 -0
- package/dist/context-builder/versions.d.ts +29 -0
- package/dist/context-builder/versions.js +30 -0
- package/dist/context-builder/versions.js.map +1 -0
- package/dist/context-builder/workflow-runner.d.ts +69 -0
- package/dist/context-builder/workflow-runner.js +1051 -0
- package/dist/context-builder/workflow-runner.js.map +1 -0
- package/dist/context-builder/write-watchdog.d.ts +14 -0
- package/dist/context-builder/write-watchdog.js +38 -0
- package/dist/context-builder/write-watchdog.js.map +1 -0
- package/dist/dashboard-adapter-catalog.d.ts +9 -0
- package/dist/dashboard-adapter-catalog.js +46 -0
- package/dist/dashboard-adapter-catalog.js.map +1 -0
- package/dist/dashboard-cli.d.ts +79 -0
- package/dist/dashboard-cli.js +585 -0
- package/dist/dashboard-cli.js.map +1 -0
- package/dist/dashboard-companion/client.d.ts +36 -0
- package/dist/dashboard-companion/client.js +210 -0
- package/dist/dashboard-companion/client.js.map +1 -0
- package/dist/dashboard-companion/config.d.ts +8 -0
- package/dist/dashboard-companion/config.js +52 -0
- package/dist/dashboard-companion/config.js.map +1 -0
- package/dist/dashboard-companion/http-proxy.d.ts +10 -0
- package/dist/dashboard-companion/http-proxy.js +234 -0
- package/dist/dashboard-companion/http-proxy.js.map +1 -0
- package/dist/dashboard-companion/protocol.d.ts +80 -0
- package/dist/dashboard-companion/protocol.js +455 -0
- package/dist/dashboard-companion/protocol.js.map +1 -0
- package/dist/dashboard-companion/standalone-assets.d.ts +12 -0
- package/dist/dashboard-companion/standalone-assets.js +72 -0
- package/dist/dashboard-companion/standalone-assets.js.map +1 -0
- package/dist/dashboard-companion/standalone-entry.d.ts +1 -0
- package/dist/dashboard-companion/standalone-entry.js +17 -0
- package/dist/dashboard-companion/standalone-entry.js.map +1 -0
- package/dist/dashboard-gateway/config.d.ts +9 -0
- package/dist/dashboard-gateway/config.js +44 -0
- package/dist/dashboard-gateway/config.js.map +1 -0
- package/dist/dashboard-gateway/index.d.ts +11 -0
- package/dist/dashboard-gateway/index.js +755 -0
- package/dist/dashboard-gateway/index.js.map +1 -0
- package/dist/dashboard-gateway/main.d.ts +1 -0
- package/dist/dashboard-gateway/main.js +25 -0
- package/dist/dashboard-gateway/main.js.map +1 -0
- package/dist/dashboard-pairing-token.d.ts +1 -0
- package/dist/dashboard-pairing-token.js +5 -0
- package/dist/dashboard-pairing-token.js.map +1 -0
- package/dist/elaris/client.d.ts +73 -0
- package/dist/elaris/client.js +825 -0
- package/dist/elaris/client.js.map +1 -0
- package/dist/elaris/config.d.ts +18 -0
- package/dist/elaris/config.js +75 -0
- package/dist/elaris/config.js.map +1 -0
- package/dist/elaris/errors.d.ts +41 -0
- package/dist/elaris/errors.js +205 -0
- package/dist/elaris/errors.js.map +1 -0
- package/dist/elaris/github-repo.d.ts +18 -0
- package/dist/elaris/github-repo.js +89 -0
- package/dist/elaris/github-repo.js.map +1 -0
- package/dist/elaris/index.d.ts +10 -0
- package/dist/elaris/index.js +11 -0
- package/dist/elaris/index.js.map +1 -0
- package/dist/elaris/redact.d.ts +89 -0
- package/dist/elaris/redact.js +423 -0
- package/dist/elaris/redact.js.map +1 -0
- package/dist/elaris/run-id.d.ts +5 -0
- package/dist/elaris/run-id.js +20 -0
- package/dist/elaris/run-id.js.map +1 -0
- package/dist/elaris/status.d.ts +45 -0
- package/dist/elaris/status.js +122 -0
- package/dist/elaris/status.js.map +1 -0
- package/dist/elaris/tool-schemas.d.ts +314 -0
- package/dist/elaris/tool-schemas.js +208 -0
- package/dist/elaris/tool-schemas.js.map +1 -0
- package/dist/elaris/tools.d.ts +73 -0
- package/dist/elaris/tools.js +1065 -0
- package/dist/elaris/tools.js.map +1 -0
- package/dist/elaris/types.d.ts +1541 -0
- package/dist/elaris/types.js +252 -0
- package/dist/elaris/types.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.js +19 -0
- package/dist/errors.js.map +1 -1
- package/dist/handler.d.ts +4 -0
- package/dist/handler.js +10 -1
- package/dist/handler.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +13 -3
- package/dist/index.js.map +1 -1
- package/dist/local-clink-backend.d.ts +3 -0
- package/dist/local-clink-backend.js +4 -0
- package/dist/local-clink-backend.js.map +1 -1
- package/dist/package-info.d.ts +2 -0
- package/dist/package-info.js +18 -0
- package/dist/package-info.js.map +1 -0
- package/dist/parsers/codex-jsonl.js +48 -3
- package/dist/parsers/codex-jsonl.js.map +1 -1
- package/dist/parsers/extract.d.ts +10 -1
- package/dist/parsers/extract.js +11 -3
- package/dist/parsers/extract.js.map +1 -1
- package/dist/paths.d.ts +8 -0
- package/dist/paths.js +31 -43
- package/dist/paths.js.map +1 -1
- package/dist/pipeline.d.ts +51 -3
- package/dist/pipeline.js +264 -47
- package/dist/pipeline.js.map +1 -1
- package/dist/run-dir.d.ts +24 -0
- package/dist/run-dir.js +201 -2
- package/dist/run-dir.js.map +1 -1
- package/dist/runner.d.ts +13 -0
- package/dist/runner.js +184 -15
- package/dist/runner.js.map +1 -1
- package/dist/safety.d.ts +4 -4
- package/dist/safety.js +4 -4
- package/dist/schema.d.ts +7 -7
- package/dist/server.d.ts +2 -0
- package/dist/server.js +396 -24
- package/dist/server.js.map +1 -1
- package/dist/session-log-stream.d.ts +18 -0
- package/dist/session-log-stream.js +319 -0
- package/dist/session-log-stream.js.map +1 -0
- package/package.json +47 -5
- package/conf/adapters/gemini.json +0 -44
- package/conf/adapters/hapi/gemini.json +0 -47
- package/dist/artifacts.d.ts +0 -9
- package/dist/artifacts.js +0 -24
- package/dist/artifacts.js.map +0 -1
- package/dist/parsers/gemini-json.d.ts +0 -2
- package/dist/parsers/gemini-json.js +0 -72
- package/dist/parsers/gemini-json.js.map +0 -1
|
@@ -0,0 +1,1270 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
4
|
+
import { AdapterRegistry } from "../registry.js";
|
|
5
|
+
import { assemblePrompt } from "../prompt.js";
|
|
6
|
+
import { ContinuationCache } from "../continuation.js";
|
|
7
|
+
import { appServerEnabled } from "../config.js";
|
|
8
|
+
import { CancellationError, InvalidParamsError } from "../errors.js";
|
|
9
|
+
import { logger } from "../logger.js";
|
|
10
|
+
import { limitOutput } from "../pipeline.js";
|
|
11
|
+
import { validateExistingDirectory } from "../paths.js";
|
|
12
|
+
import { createActivitySessionLogObserver } from "./activity-session-log.js";
|
|
13
|
+
import { getAppServerRegistry, peekAppServerRegistry, spawnRequestFor, } from "./app-server-registry.js";
|
|
14
|
+
import { sandboxPolicyFromMode } from "./app-server-client.js";
|
|
15
|
+
import { CODEX_APP_SERVER_PROTOCOL_VERSION, SUPPORTED_COMMAND_EXECUTION_APPROVAL_DECISIONS, SupportedCommandExecutionApprovalDecisionSchema, } from "./protocol.js";
|
|
16
|
+
const DEFAULT_MAX_RESPONSE_CHARS = 20_000;
|
|
17
|
+
const PENDING_APPROVAL_FIELD_CHARS = 2_000;
|
|
18
|
+
// Bounds for the model/list passthrough: a configurable/compromisable child
|
|
19
|
+
// app-server could return an oversized payload, so cap both the row count and
|
|
20
|
+
// every surfaced string field before it reaches the caller.
|
|
21
|
+
const MAX_LIST_MODELS = 100;
|
|
22
|
+
const MODEL_DESCRIPTION_CHARS = 500;
|
|
23
|
+
const MODEL_IDENTIFIER_CHARS = 128;
|
|
24
|
+
const MODEL_EFFORT_CHARS = 64;
|
|
25
|
+
const MAX_SUPPORTED_REASONING_EFFORTS = 16;
|
|
26
|
+
// Pagination cursors are opaque tokens that can legitimately be long; cap with a
|
|
27
|
+
// GENEROUS ceiling to bound a compromised child app-server without corrupting a
|
|
28
|
+
// valid cursor (which would break page-two fetches). NOT the tight identifier cap.
|
|
29
|
+
const MAX_CURSOR_CHARS = 4096;
|
|
30
|
+
const SandboxModeSchema = z.enum([
|
|
31
|
+
"read-only",
|
|
32
|
+
"workspace-write",
|
|
33
|
+
"danger-full-access",
|
|
34
|
+
]);
|
|
35
|
+
const ApprovalModeSchema = z.enum([
|
|
36
|
+
"untrusted",
|
|
37
|
+
"on-failure",
|
|
38
|
+
"on-request",
|
|
39
|
+
"granular",
|
|
40
|
+
"never",
|
|
41
|
+
]);
|
|
42
|
+
const SessionStatusSchema = z.enum([
|
|
43
|
+
"running",
|
|
44
|
+
"completed",
|
|
45
|
+
"interrupted",
|
|
46
|
+
"failed",
|
|
47
|
+
"timeout",
|
|
48
|
+
]);
|
|
49
|
+
const AgentRunInputSchema = z
|
|
50
|
+
.object({
|
|
51
|
+
prompt: z.string().min(1).describe("The task or question for the Codex session"),
|
|
52
|
+
cli_name: z.string().min(1).optional(),
|
|
53
|
+
role: z.string().default("default"),
|
|
54
|
+
absolute_file_paths: z.array(z.string()).optional(),
|
|
55
|
+
images: z.array(z.string()).optional(),
|
|
56
|
+
context: z.string().optional(),
|
|
57
|
+
cwd: z
|
|
58
|
+
.string()
|
|
59
|
+
.min(1)
|
|
60
|
+
.optional()
|
|
61
|
+
.describe("Optional working directory for the Codex child. Must be an existing directory inside CLINKX_ALLOWED_ROOTS; when supplied with workspace-write it is the sole explicit writable root."),
|
|
62
|
+
continuation_id: z
|
|
63
|
+
.string()
|
|
64
|
+
.min(1)
|
|
65
|
+
.optional()
|
|
66
|
+
.describe("Write/read-symmetric thread alias (process-local; lost on ClinkX restart). This run is tagged under this continuation_id, and passing the SAME continuation_id back on a later run resumes that thread (real model state). Its first use is never an error — it just starts a fresh thread. Alias resume precedence: resume_continuation_id > session_id > continuation_id."),
|
|
67
|
+
session_id: z
|
|
68
|
+
.string()
|
|
69
|
+
.min(1)
|
|
70
|
+
.optional()
|
|
71
|
+
.describe("Resume alias for a prior session: looked up to recover that session's thread (errors if unknown). Does NOT set the new session's id (always auto-generated). Mutually exclusive with resume_thread_id and resume_continuation_id. Takes precedence over continuation_id, but is overridden by resume_thread_id and resume_continuation_id."),
|
|
72
|
+
resume_continuation_id: z
|
|
73
|
+
.string()
|
|
74
|
+
.min(1)
|
|
75
|
+
.optional()
|
|
76
|
+
.describe("Resume an existing thread by its prior continuation_id — restores real model state (unlike clink's text-append), erroring if unknown. Mutually exclusive with resume_thread_id and session_id. Highest alias resume precedence (over session_id and continuation_id)."),
|
|
77
|
+
resume_thread_id: z
|
|
78
|
+
.string()
|
|
79
|
+
.min(1)
|
|
80
|
+
.optional()
|
|
81
|
+
.describe("Resume an existing Codex thread by its RAW thread_id (from a prior snapshot's thread_id). Unlike the process-local continuation_id/session_id aliases, a raw thread_id survives a ClinkX restart (the on-disk rollout persists). Highest resume precedence. Mutually exclusive with session_id and resume_continuation_id."),
|
|
82
|
+
wait: z
|
|
83
|
+
.boolean()
|
|
84
|
+
.default(true)
|
|
85
|
+
.describe("true: block until the turn reaches a terminal state and return the final text. false: return once the turn is RUNNING, then drive it with clink_agent_manage (poll/wait/steer/respond)."),
|
|
86
|
+
timeout_seconds: z.number().positive().optional(),
|
|
87
|
+
max_response_chars: z.number().int().positive().optional(),
|
|
88
|
+
model: z.string().min(1).optional(),
|
|
89
|
+
sandbox: SandboxModeSchema.optional().describe('Filesystem sandbox for this turn: "read-only" (safe default), "workspace-write" (write within the workspace), or "danger-full-access" (unrestricted; only when the adapter sets allow_danger_full_access:true). Per-call override of the adapter default.'),
|
|
90
|
+
approval: ApprovalModeSchema.optional().describe('Approval policy: "never" runs without prompting; "on-request"/"on-failure"/"granular" surface approval requests you answer via clink_agent_manage op:"respond". Use a non-"never" mode whenever the turn can write files or run commands.'),
|
|
91
|
+
output_schema: z
|
|
92
|
+
.record(z.unknown())
|
|
93
|
+
.optional()
|
|
94
|
+
.describe("JSON Schema object constraining the turn's final assistant message to that shape. When supplied and the turn ends with terminal text, the run response adds a best-effort parsed `final_json` (null if the text is not valid JSON)."),
|
|
95
|
+
debug: z.boolean().default(false),
|
|
96
|
+
})
|
|
97
|
+
.strict();
|
|
98
|
+
// Cap on batch poll/wait fan-out. Enforced in the handler (not via a schema
|
|
99
|
+
// .refine, which would turn these discriminatedUnion arms into ZodEffects and
|
|
100
|
+
// break union construction).
|
|
101
|
+
const MAX_BATCH_SESSION_IDS = 50;
|
|
102
|
+
const BatchSessionIdsSchema = z
|
|
103
|
+
.array(z.string().min(1))
|
|
104
|
+
.optional()
|
|
105
|
+
.describe("Batch form: address multiple sessions at once. Provide exactly one of session_id or session_ids (max 50). poll returns { snapshots }; wait races all ids and returns on the first ACTIONABLE session (terminal, or paused on a pending approval).");
|
|
106
|
+
const PollSchema = z.object({
|
|
107
|
+
op: z.literal("poll"),
|
|
108
|
+
session_id: z.string().min(1).optional(),
|
|
109
|
+
session_ids: BatchSessionIdsSchema,
|
|
110
|
+
}).strict();
|
|
111
|
+
const WaitSchema = z.object({
|
|
112
|
+
op: z.literal("wait"),
|
|
113
|
+
session_id: z.string().min(1).optional(),
|
|
114
|
+
session_ids: BatchSessionIdsSchema,
|
|
115
|
+
timeout_seconds: z.number().positive().optional(),
|
|
116
|
+
}).strict();
|
|
117
|
+
const SteerSchema = z.object({
|
|
118
|
+
op: z.literal("steer"),
|
|
119
|
+
session_id: z.string().min(1),
|
|
120
|
+
prompt: z.string().min(1),
|
|
121
|
+
}).strict();
|
|
122
|
+
const RespondSchema = z.object({
|
|
123
|
+
op: z.literal("respond"),
|
|
124
|
+
session_id: z.string().min(1),
|
|
125
|
+
// Approval (JSON-RPC request) ids are string-or-number per the protocol
|
|
126
|
+
// RequestId union; live Codex emits numeric ids. Pass through unchanged.
|
|
127
|
+
approval_id: z.union([z.string().min(1), z.number()]),
|
|
128
|
+
decision: SupportedCommandExecutionApprovalDecisionSchema,
|
|
129
|
+
}).strict();
|
|
130
|
+
const SessionOnlySchema = (op) => z.object({
|
|
131
|
+
op: z.literal(op),
|
|
132
|
+
session_id: z.string().min(1),
|
|
133
|
+
}).strict();
|
|
134
|
+
const CleanupSchema = z.object({
|
|
135
|
+
op: z.literal("cleanup"),
|
|
136
|
+
session_id: z.string().min(1),
|
|
137
|
+
force: z.boolean().default(false),
|
|
138
|
+
}).strict();
|
|
139
|
+
const HealthSchema = z.object({
|
|
140
|
+
op: z.literal("health"),
|
|
141
|
+
}).strict();
|
|
142
|
+
const ListModelsSchema = z.object({
|
|
143
|
+
op: z.literal("list_models"),
|
|
144
|
+
cursor: z.string().min(1).optional(),
|
|
145
|
+
include_hidden: z.boolean().optional(),
|
|
146
|
+
limit: z.number().int().nonnegative().optional(),
|
|
147
|
+
}).strict();
|
|
148
|
+
const GetLogSchema = z.object({
|
|
149
|
+
op: z.literal("get_log"),
|
|
150
|
+
session_id: z.string().min(1),
|
|
151
|
+
cursor: z.number().int().nonnegative().optional(),
|
|
152
|
+
limit: z.number().int().nonnegative().optional(),
|
|
153
|
+
}).strict();
|
|
154
|
+
const ListSchema = z.object({
|
|
155
|
+
op: z.literal("list"),
|
|
156
|
+
status: SessionStatusSchema.optional(),
|
|
157
|
+
offset: z.number().int().nonnegative().default(0),
|
|
158
|
+
limit: z.number().int().positive().optional(),
|
|
159
|
+
}).strict();
|
|
160
|
+
const AgentManageInputSchema = z.discriminatedUnion("op", [
|
|
161
|
+
PollSchema,
|
|
162
|
+
WaitSchema,
|
|
163
|
+
SteerSchema,
|
|
164
|
+
RespondSchema,
|
|
165
|
+
SessionOnlySchema("cancel"),
|
|
166
|
+
GetLogSchema,
|
|
167
|
+
ListSchema,
|
|
168
|
+
CleanupSchema,
|
|
169
|
+
HealthSchema,
|
|
170
|
+
ListModelsSchema,
|
|
171
|
+
]);
|
|
172
|
+
export const AGENT_TOOL_NAMES = new Set([
|
|
173
|
+
"clink_agent_run",
|
|
174
|
+
"clink_agent_manage",
|
|
175
|
+
]);
|
|
176
|
+
/**
|
|
177
|
+
* Bounded, insertion-order (oldest-first eviction) map. Keeps the process-global
|
|
178
|
+
* resume aliases from growing without limit in a long-running STDIO server.
|
|
179
|
+
*/
|
|
180
|
+
class BoundedAliasMap {
|
|
181
|
+
maxEntries;
|
|
182
|
+
map = new Map();
|
|
183
|
+
constructor(maxEntries) {
|
|
184
|
+
this.maxEntries = maxEntries;
|
|
185
|
+
}
|
|
186
|
+
set(key, value) {
|
|
187
|
+
this.map.delete(key);
|
|
188
|
+
if (this.map.size >= this.maxEntries) {
|
|
189
|
+
const oldest = this.map.keys().next();
|
|
190
|
+
if (!oldest.done)
|
|
191
|
+
this.map.delete(oldest.value);
|
|
192
|
+
}
|
|
193
|
+
this.map.set(key, value);
|
|
194
|
+
}
|
|
195
|
+
get(key) {
|
|
196
|
+
return this.map.get(key);
|
|
197
|
+
}
|
|
198
|
+
delete(key) {
|
|
199
|
+
this.map.delete(key);
|
|
200
|
+
}
|
|
201
|
+
get size() {
|
|
202
|
+
return this.map.size;
|
|
203
|
+
}
|
|
204
|
+
clear() {
|
|
205
|
+
this.map.clear();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const MAX_ASYNC_ALIASES = 100;
|
|
209
|
+
/**
|
|
210
|
+
* Async tool-local resume aliases. They intentionally live only in this MCP
|
|
211
|
+
* process: restarting ClinkX loses these continuation_id/session aliases even
|
|
212
|
+
* though the Codex rollout/thread may still exist on disk and can be recovered
|
|
213
|
+
* by other app-server mechanisms. Bounded (LRU-by-insertion) so the maps cannot
|
|
214
|
+
* grow without limit; `sessionContinuationKeys` lets cleanup prune the paired
|
|
215
|
+
* continuation alias rather than leaking a still-resumable thread.
|
|
216
|
+
*/
|
|
217
|
+
const asyncThreadIds = new BoundedAliasMap(MAX_ASYNC_ALIASES);
|
|
218
|
+
const sessionContinuationKeys = new BoundedAliasMap(MAX_ASYNC_ALIASES);
|
|
219
|
+
const emptyContinuationCache = new ContinuationCache(1);
|
|
220
|
+
export function getAgentToolDefinitions(appServerCliNames = []) {
|
|
221
|
+
const runSchema = zodToJsonSchema(AgentRunInputSchema, {
|
|
222
|
+
target: "jsonSchema7",
|
|
223
|
+
$refStrategy: "none",
|
|
224
|
+
});
|
|
225
|
+
// ADVERTISED schema for clink_agent_manage. AgentManageInputSchema is a
|
|
226
|
+
// z.discriminatedUnion("op", ...) which serializes to a root `anyOf` with no
|
|
227
|
+
// top-level `properties`; stricter MCP clients drop a tool whose root has no
|
|
228
|
+
// `properties`. We therefore hand-author a FLAT object schema (root
|
|
229
|
+
// properties, no root anyOf) as a permissive superset for advertisement.
|
|
230
|
+
// `op` is the only universally-required field; strict per-op validation is
|
|
231
|
+
// still enforced at runtime by AgentManageInputSchema (see handleAgentManage).
|
|
232
|
+
const manageSchema = {
|
|
233
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
234
|
+
type: "object",
|
|
235
|
+
additionalProperties: false,
|
|
236
|
+
required: ["op"],
|
|
237
|
+
properties: {
|
|
238
|
+
op: {
|
|
239
|
+
type: "string",
|
|
240
|
+
enum: [
|
|
241
|
+
"poll",
|
|
242
|
+
"wait",
|
|
243
|
+
"steer",
|
|
244
|
+
"respond",
|
|
245
|
+
"cancel",
|
|
246
|
+
"get_log",
|
|
247
|
+
"list",
|
|
248
|
+
"cleanup",
|
|
249
|
+
"health",
|
|
250
|
+
"list_models",
|
|
251
|
+
],
|
|
252
|
+
description: "Operation. poll: current snapshot; wait: block until terminality; " +
|
|
253
|
+
"steer: send a follow-up prompt into the active turn; respond: answer a " +
|
|
254
|
+
"pending approval (accept/acceptForSession/decline/cancel); cancel: interrupt the turn; " +
|
|
255
|
+
"get_log: event history; list: enumerate live in-process sessions; " +
|
|
256
|
+
"cleanup: drop a session; health: inspect app-server backend health; " +
|
|
257
|
+
"list_models: list available models via model/list.",
|
|
258
|
+
},
|
|
259
|
+
session_id: {
|
|
260
|
+
type: "string",
|
|
261
|
+
minLength: 1,
|
|
262
|
+
description: "Target session (poll/wait/steer/respond/cancel/get_log/cleanup; " +
|
|
263
|
+
"provide exactly one of session_id or session_ids for poll/wait).",
|
|
264
|
+
},
|
|
265
|
+
session_ids: {
|
|
266
|
+
type: "array",
|
|
267
|
+
items: { type: "string", minLength: 1 },
|
|
268
|
+
maxItems: 50,
|
|
269
|
+
description: "Batch form for poll/wait (max 50).",
|
|
270
|
+
},
|
|
271
|
+
prompt: {
|
|
272
|
+
type: "string",
|
|
273
|
+
minLength: 1,
|
|
274
|
+
description: "steer: the follow-up prompt.",
|
|
275
|
+
},
|
|
276
|
+
decision: {
|
|
277
|
+
type: "string",
|
|
278
|
+
enum: [...SUPPORTED_COMMAND_EXECUTION_APPROVAL_DECISIONS],
|
|
279
|
+
description: "respond: approval decision.",
|
|
280
|
+
},
|
|
281
|
+
approval_id: {
|
|
282
|
+
type: ["string", "number"],
|
|
283
|
+
minLength: 1,
|
|
284
|
+
description: "respond: JSON-RPC approval request id to answer (string or number; minLength applies only to the string form).",
|
|
285
|
+
},
|
|
286
|
+
cursor: {
|
|
287
|
+
type: ["integer", "string"],
|
|
288
|
+
minimum: 0,
|
|
289
|
+
description: "get_log: integer start offset into the event ring; " +
|
|
290
|
+
"list_models: opaque string pagination token returned as a prior call's next_cursor " +
|
|
291
|
+
"(minimum applies only to the integer get_log form and is ignored for the string form).",
|
|
292
|
+
},
|
|
293
|
+
include_hidden: {
|
|
294
|
+
type: "boolean",
|
|
295
|
+
description: "list_models: include hidden models in the result.",
|
|
296
|
+
},
|
|
297
|
+
limit: {
|
|
298
|
+
type: "integer",
|
|
299
|
+
minimum: 0,
|
|
300
|
+
description: "get_log/list/list_models: max items.",
|
|
301
|
+
},
|
|
302
|
+
status: {
|
|
303
|
+
type: "string",
|
|
304
|
+
enum: ["running", "completed", "interrupted", "failed", "timeout"],
|
|
305
|
+
description: "list: optional status filter.",
|
|
306
|
+
},
|
|
307
|
+
offset: {
|
|
308
|
+
type: "integer",
|
|
309
|
+
minimum: 0,
|
|
310
|
+
description: "list: pagination offset.",
|
|
311
|
+
},
|
|
312
|
+
timeout_seconds: {
|
|
313
|
+
type: "number",
|
|
314
|
+
exclusiveMinimum: 0,
|
|
315
|
+
description: "wait: max seconds to block.",
|
|
316
|
+
},
|
|
317
|
+
force: {
|
|
318
|
+
type: "boolean",
|
|
319
|
+
default: false,
|
|
320
|
+
description: "cleanup: when true, interrupt a running turn before eviction. Default false skips active sessions.",
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
const props = runSchema["properties"];
|
|
325
|
+
if (props?.["cli_name"] != null && appServerCliNames.length > 0) {
|
|
326
|
+
props["cli_name"]["enum"] = appServerCliNames;
|
|
327
|
+
props["cli_name"]["description"] =
|
|
328
|
+
`Codex app-server adapter to use (defaults to the sole configured one when omitted).` +
|
|
329
|
+
` Available: ${appServerCliNames.join(", ")}`;
|
|
330
|
+
}
|
|
331
|
+
return [
|
|
332
|
+
{
|
|
333
|
+
name: "clink_agent_run",
|
|
334
|
+
description: "Start a Codex app-server agent turn — the stateful, session-based path. " +
|
|
335
|
+
"Prefer this over clink for Codex whenever the work is multi-turn, needs an approval gate on file/command actions, " +
|
|
336
|
+
"needs per-call sandbox control, or should be resumable (resume_continuation_id restores real model state, not text re-paste). " +
|
|
337
|
+
"Set wait:false to start the turn and then drive it with clink_agent_manage; wait:true blocks for the final answer. " +
|
|
338
|
+
"For a single self-contained, stateless shot, plain clink is simpler.",
|
|
339
|
+
inputSchema: runSchema,
|
|
340
|
+
annotations: {
|
|
341
|
+
readOnlyHint: false,
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: "clink_agent_manage",
|
|
346
|
+
description: "Drive or inspect a running Codex app-server session started by clink_agent_run. Ops: " +
|
|
347
|
+
'poll (current snapshot), wait (block for terminality), steer (send a follow-up prompt into the active turn), ' +
|
|
348
|
+
'respond (answer a pending approval: accept/acceptForSession/decline/cancel), cancel (interrupt), get_log (event history), ' +
|
|
349
|
+
'list (enumerate live in-process sessions — not persisted Codex threads), cleanup (drop a session), ' +
|
|
350
|
+
'health (backend counters/version envelope), list_models (list available models via model/list). ' +
|
|
351
|
+
"Use respond when a turn started with a non-\"never\" approval mode pauses for an approval request.",
|
|
352
|
+
inputSchema: manageSchema,
|
|
353
|
+
annotations: {
|
|
354
|
+
readOnlyHint: false,
|
|
355
|
+
destructiveHint: true,
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
];
|
|
359
|
+
}
|
|
360
|
+
export async function handleAgentToolCall(name, args, options = {}) {
|
|
361
|
+
if (!appServerEnabled()) {
|
|
362
|
+
throw new InvalidParamsError("Codex app-server async tools are disabled. Set CLINKX_APP_SERVER_ENABLED=true to enable them.");
|
|
363
|
+
}
|
|
364
|
+
if (name === "clink_agent_run") {
|
|
365
|
+
return handleAgentRun(args, options);
|
|
366
|
+
}
|
|
367
|
+
if (name === "clink_agent_manage") {
|
|
368
|
+
return handleAgentManage(args, options);
|
|
369
|
+
}
|
|
370
|
+
throw new InvalidParamsError(`Unknown Codex agent tool: ${name}`);
|
|
371
|
+
}
|
|
372
|
+
async function handleAgentRun(args, options) {
|
|
373
|
+
const input = parseWithSchema(AgentRunInputSchema, args, "clink_agent_run");
|
|
374
|
+
const resumeSelectorCount = (input.resume_thread_id != null ? 1 : 0) +
|
|
375
|
+
(input.session_id != null ? 1 : 0) +
|
|
376
|
+
(input.resume_continuation_id != null ? 1 : 0);
|
|
377
|
+
if (resumeSelectorCount > 1) {
|
|
378
|
+
throw new InvalidParamsError("clink_agent_run accepts only one of resume_thread_id, session_id, or resume_continuation_id.");
|
|
379
|
+
}
|
|
380
|
+
throwIfAborted(options.signal);
|
|
381
|
+
const adapterRegistry = options.adapterRegistry ?? AdapterRegistry.load();
|
|
382
|
+
const adapter = resolveAppServerAdapter(adapterRegistry, input.cli_name);
|
|
383
|
+
// The app-server turn input is text-only. If the caller supplied images but the
|
|
384
|
+
// resolved adapter cannot carry them, reject rather than silently degrading them
|
|
385
|
+
// into manifest TEXT inside the prompt (which the model would treat as a path
|
|
386
|
+
// listing, not an image). Fail loud so the caller can drop them or pick an
|
|
387
|
+
// image-capable adapter (F5).
|
|
388
|
+
if (input.images != null &&
|
|
389
|
+
input.images.length > 0 &&
|
|
390
|
+
adapter.supports_images !== true) {
|
|
391
|
+
throw new InvalidParamsError(`Adapter "${adapter.name}" does not support images (supports_images:false); ` +
|
|
392
|
+
"remove the images field or use an image-capable adapter.");
|
|
393
|
+
}
|
|
394
|
+
const policy = resolvePolicies(adapter, input);
|
|
395
|
+
const registry = options.registry ?? getAppServerRegistry(spawnRequestFor(adapter));
|
|
396
|
+
const resumeThreadId = resolveResumeThreadId(input, registry);
|
|
397
|
+
const resumedFrom = effectiveResumeKey(input, resumeThreadId);
|
|
398
|
+
const sessionId = `clink-agent-${randomUUID()}`;
|
|
399
|
+
const prompt = buildAsyncPrompt(input, adapter);
|
|
400
|
+
const cwd = resolveAgentCwd(input.cwd);
|
|
401
|
+
const sandboxPolicy = sandboxPolicyFromMode(policy.sandbox, cwd != null ? { writableRoots: [cwd] } : undefined);
|
|
402
|
+
const activityObservation = await beginAgentActivity(options.activityJournal, {
|
|
403
|
+
activityId: sessionId,
|
|
404
|
+
kind: "clink_agent_run",
|
|
405
|
+
adapter: adapter.name,
|
|
406
|
+
role: input.role,
|
|
407
|
+
projectFolders: [cwd ?? process.cwd()],
|
|
408
|
+
});
|
|
409
|
+
const sessionObserver = options.sessionLogRootDir !== undefined && activityObservation?.identity !== undefined
|
|
410
|
+
? createActivitySessionLogObserver({
|
|
411
|
+
rootDir: options.sessionLogRootDir,
|
|
412
|
+
activityIdentity: activityObservation.identity,
|
|
413
|
+
})
|
|
414
|
+
: undefined;
|
|
415
|
+
let started;
|
|
416
|
+
try {
|
|
417
|
+
started = await registry.startSession({
|
|
418
|
+
sessionId,
|
|
419
|
+
input: [{ type: "text", text: prompt }],
|
|
420
|
+
sandbox: policy.sandbox,
|
|
421
|
+
approval: policy.approval,
|
|
422
|
+
approvalPolicy: policy.approval,
|
|
423
|
+
sandboxPolicy,
|
|
424
|
+
...(cwd != null ? { cwd } : {}),
|
|
425
|
+
...(policy.model != null ? { model: policy.model } : {}),
|
|
426
|
+
...(input.output_schema != null ? { outputSchema: input.output_schema } : {}),
|
|
427
|
+
...(resumeThreadId != null ? { resumeThreadId } : {}),
|
|
428
|
+
...(activityObservation != null ? { activityObservation } : {}),
|
|
429
|
+
...(sessionObserver != null ? { sessionObserver } : {}),
|
|
430
|
+
// Mirror the async wait timeout into the per-session turn timer so an
|
|
431
|
+
// explicit timeout_seconds drives both (review fix B). The async path
|
|
432
|
+
// derives the turn timeout ONLY from input.timeout_seconds — NOT from
|
|
433
|
+
// adapter.timeout_seconds — so without a per-call timeout an async turn
|
|
434
|
+
// falls back to the global CLINKX_TURN_TIMEOUT_SECONDS default. This is
|
|
435
|
+
// intentional: the async API is caller-driven (not a bug).
|
|
436
|
+
...(input.timeout_seconds != null
|
|
437
|
+
? { turnTimeoutMs: Math.ceil(input.timeout_seconds * 1000) }
|
|
438
|
+
: {}),
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
catch (err) {
|
|
442
|
+
await completeAgentActivity(activityObservation, "failed");
|
|
443
|
+
throw err;
|
|
444
|
+
}
|
|
445
|
+
rememberThread(input, started);
|
|
446
|
+
if (!input.wait) {
|
|
447
|
+
return jsonResult({
|
|
448
|
+
session_id: started.sessionId,
|
|
449
|
+
status: "running",
|
|
450
|
+
resumed_from: resumedFrom,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
const startedAt = Date.now();
|
|
454
|
+
const waited = await waitForRun(registry, started.sessionId, input, options.signal);
|
|
455
|
+
rememberThread(input, waited);
|
|
456
|
+
const response = snapshotResponse(waited, input.max_response_chars);
|
|
457
|
+
response["resumed_from"] = resumedFrom;
|
|
458
|
+
addWaitMetadata(response, waited, Date.now() - startedAt);
|
|
459
|
+
addPendingApprovalNextAction(response, waited);
|
|
460
|
+
if (input.output_schema != null) {
|
|
461
|
+
// Best-effort: parse the raw (un-truncated) final text against the requested
|
|
462
|
+
// schema's intent. Never throw — leave final_json null on parse failure.
|
|
463
|
+
response["final_json"] = parseFinalJson(waited.finalText);
|
|
464
|
+
}
|
|
465
|
+
return jsonResult(response, { isError: snapshotIsError(waited) });
|
|
466
|
+
}
|
|
467
|
+
async function beginAgentActivity(journal, input) {
|
|
468
|
+
if (journal == null)
|
|
469
|
+
return undefined;
|
|
470
|
+
try {
|
|
471
|
+
return await journal.begin(input);
|
|
472
|
+
}
|
|
473
|
+
catch (err) {
|
|
474
|
+
logger.debug({ err: err instanceof Error ? err.message : String(err) }, "clink_agent_run: dashboard activity admission failed open");
|
|
475
|
+
return undefined;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async function completeAgentActivity(observation, status) {
|
|
479
|
+
if (observation == null)
|
|
480
|
+
return;
|
|
481
|
+
try {
|
|
482
|
+
if (status === "completed") {
|
|
483
|
+
await observation.complete({
|
|
484
|
+
status: "succeeded",
|
|
485
|
+
outcomeKind: "success",
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
else if (status === "interrupted") {
|
|
489
|
+
await observation.complete({
|
|
490
|
+
status: "cancelled",
|
|
491
|
+
outcomeKind: "cancelled",
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
await observation.complete({
|
|
496
|
+
status: "failed",
|
|
497
|
+
outcomeKind: "error",
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
catch (err) {
|
|
502
|
+
logger.debug({ err: err instanceof Error ? err.message : String(err) }, "clink_agent_run: dashboard activity completion failed open");
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function resolveAgentCwd(cwd) {
|
|
506
|
+
if (cwd == null) {
|
|
507
|
+
logger.debug("clink_agent_run cwd omitted; Codex child will inherit the ClinkX launch directory");
|
|
508
|
+
return undefined;
|
|
509
|
+
}
|
|
510
|
+
return validateExistingDirectory(cwd);
|
|
511
|
+
}
|
|
512
|
+
async function handleAgentManage(args, options) {
|
|
513
|
+
const input = parseWithSchema(AgentManageInputSchema, args, "clink_agent_manage");
|
|
514
|
+
if (input.op === "list_models") {
|
|
515
|
+
return handleListModels(input, options);
|
|
516
|
+
}
|
|
517
|
+
// Use a NON-creating accessor: a manage op must never construct the singleton
|
|
518
|
+
// with the default spawn config, or a later run with a custom app_server
|
|
519
|
+
// command would spuriously conflict (review fix D). `list` degrades to empty;
|
|
520
|
+
// session-specific ops behave as "unknown session".
|
|
521
|
+
const registry = options.registry ?? peekAppServerRegistry();
|
|
522
|
+
if (registry == null) {
|
|
523
|
+
if (input.op === "list") {
|
|
524
|
+
return jsonResult({ sessions: [] });
|
|
525
|
+
}
|
|
526
|
+
if (input.op === "health") {
|
|
527
|
+
return jsonResult(emptyHealthResponse());
|
|
528
|
+
}
|
|
529
|
+
const unknown = input.session_id ??
|
|
530
|
+
input.session_ids?.join(", ");
|
|
531
|
+
throw new InvalidParamsError(`Unknown session: ${unknown}`);
|
|
532
|
+
}
|
|
533
|
+
switch (input.op) {
|
|
534
|
+
case "poll": {
|
|
535
|
+
throwIfAborted(options.signal);
|
|
536
|
+
const target = resolveSessionSelector(input);
|
|
537
|
+
if (target.kind === "single") {
|
|
538
|
+
const snapshot = registry.poll(target.id);
|
|
539
|
+
return jsonResult(snapshotResponse(snapshot), {
|
|
540
|
+
isError: snapshotIsError(snapshot),
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
return jsonResult({
|
|
544
|
+
snapshots: target.ids.map((id) => snapshotResponse(registry.poll(id))),
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
case "wait": {
|
|
548
|
+
const target = resolveSessionSelector(input);
|
|
549
|
+
if (target.kind === "single") {
|
|
550
|
+
const startedAt = Date.now();
|
|
551
|
+
const snapshot = await waitForManage(registry, target.id, input.timeout_seconds, options.signal);
|
|
552
|
+
const response = snapshotResponse(snapshot);
|
|
553
|
+
addWaitMetadata(response, snapshot, Date.now() - startedAt);
|
|
554
|
+
addPendingApprovalNextAction(response, snapshot);
|
|
555
|
+
return jsonResult(response, {
|
|
556
|
+
isError: snapshotIsError(snapshot),
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
const batch = await waitForManageBatch(registry, target.ids, input.timeout_seconds, options.signal);
|
|
560
|
+
return jsonResult(batch.value, { isError: batch.isError });
|
|
561
|
+
}
|
|
562
|
+
case "steer": {
|
|
563
|
+
const snapshot = await registry.steer(input.session_id, [
|
|
564
|
+
{ type: "text", text: input.prompt },
|
|
565
|
+
]);
|
|
566
|
+
return jsonResult(snapshotResponse(snapshot), {
|
|
567
|
+
isError: snapshotIsError(snapshot),
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
case "respond": {
|
|
571
|
+
const snapshot = await registry.respondApproval(input.session_id, input.approval_id, input.decision);
|
|
572
|
+
return jsonResult(snapshotResponse(snapshot), {
|
|
573
|
+
isError: snapshotIsError(snapshot),
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
case "cancel": {
|
|
577
|
+
const snapshot = await registry.interrupt(input.session_id);
|
|
578
|
+
return jsonResult(snapshotResponse(snapshot), {
|
|
579
|
+
isError: snapshotIsError(snapshot),
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
case "get_log": {
|
|
583
|
+
const page = registry.getLog(input.session_id, input.cursor, input.limit);
|
|
584
|
+
return jsonResult(logResponse(page));
|
|
585
|
+
}
|
|
586
|
+
case "list": {
|
|
587
|
+
const offset = input.offset ?? 0;
|
|
588
|
+
const snapshots = registry
|
|
589
|
+
.list(input.status != null ? { status: input.status } : undefined)
|
|
590
|
+
.slice(offset, input.limit != null ? offset + input.limit : undefined)
|
|
591
|
+
.map((snapshot) => snapshotResponse(snapshot));
|
|
592
|
+
return jsonResult({ sessions: snapshots });
|
|
593
|
+
}
|
|
594
|
+
case "health":
|
|
595
|
+
return jsonResult(healthResponse(registry.health()));
|
|
596
|
+
case "cleanup": {
|
|
597
|
+
const cleanup = await registry.cleanup(input.session_id, {
|
|
598
|
+
force: input.force,
|
|
599
|
+
});
|
|
600
|
+
if (cleanup.cleanedUp) {
|
|
601
|
+
const sKey = sessionKey(input.session_id);
|
|
602
|
+
const contKey = sessionContinuationKeys.get(sKey);
|
|
603
|
+
asyncThreadIds.delete(sKey);
|
|
604
|
+
if (contKey != null) {
|
|
605
|
+
asyncThreadIds.delete(contKey);
|
|
606
|
+
sessionContinuationKeys.delete(sKey);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return jsonResult({
|
|
610
|
+
session_id: cleanup.sessionId,
|
|
611
|
+
cleaned_up: cleanup.cleanedUp,
|
|
612
|
+
skipped_active: cleanup.skippedActive,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
async function handleListModels(input, options) {
|
|
618
|
+
let registry = options.registry ?? peekAppServerRegistry();
|
|
619
|
+
if (registry == null) {
|
|
620
|
+
const adapterRegistry = options.adapterRegistry ?? AdapterRegistry.load();
|
|
621
|
+
const adapter = resolveAppServerAdapter(adapterRegistry, undefined);
|
|
622
|
+
registry = getAppServerRegistry(spawnRequestFor(adapter));
|
|
623
|
+
}
|
|
624
|
+
// Map snake_case tool params → protocol/registry camelCase. Omit absent keys
|
|
625
|
+
// so the strict ModelListParamsSchema is not handed `undefined` values.
|
|
626
|
+
const params = {};
|
|
627
|
+
if (input.cursor !== undefined)
|
|
628
|
+
params.cursor = input.cursor;
|
|
629
|
+
if (input.include_hidden !== undefined)
|
|
630
|
+
params.includeHidden = input.include_hidden;
|
|
631
|
+
if (input.limit !== undefined)
|
|
632
|
+
params.limit = input.limit;
|
|
633
|
+
try {
|
|
634
|
+
const result = await registry.listModels(params);
|
|
635
|
+
return jsonResult(modelListResponse(result));
|
|
636
|
+
}
|
|
637
|
+
catch (err) {
|
|
638
|
+
return jsonResult(listModelsErrorResponse(err));
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
function parseWithSchema(schema, value, toolName) {
|
|
642
|
+
const result = schema.safeParse(value ?? {});
|
|
643
|
+
if (result.success)
|
|
644
|
+
return result.data;
|
|
645
|
+
const detail = result.error.issues
|
|
646
|
+
.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`)
|
|
647
|
+
.join("; ");
|
|
648
|
+
throw new InvalidParamsError(`Invalid ${toolName} input: ${detail}`);
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Enforce the "exactly one of session_id | session_ids" rule (and the batch cap)
|
|
652
|
+
* for poll/wait. Kept in handler code because a schema-level `.refine` would turn
|
|
653
|
+
* these discriminatedUnion arms into ZodEffects, which the union rejects.
|
|
654
|
+
*/
|
|
655
|
+
function resolveSessionSelector(input) {
|
|
656
|
+
const hasSingle = input.session_id != null;
|
|
657
|
+
const hasBatch = input.session_ids != null;
|
|
658
|
+
if (hasSingle === hasBatch) {
|
|
659
|
+
throw new InvalidParamsError("Provide exactly one of session_id or session_ids.");
|
|
660
|
+
}
|
|
661
|
+
if (hasBatch) {
|
|
662
|
+
const ids = input.session_ids;
|
|
663
|
+
if (ids.length === 0) {
|
|
664
|
+
throw new InvalidParamsError("session_ids must not be empty.");
|
|
665
|
+
}
|
|
666
|
+
if (ids.length > MAX_BATCH_SESSION_IDS) {
|
|
667
|
+
throw new InvalidParamsError(`session_ids exceeds the maximum of ${MAX_BATCH_SESSION_IDS}.`);
|
|
668
|
+
}
|
|
669
|
+
return { kind: "batch", ids };
|
|
670
|
+
}
|
|
671
|
+
return { kind: "single", id: input.session_id };
|
|
672
|
+
}
|
|
673
|
+
function parseFinalJson(finalText) {
|
|
674
|
+
if (finalText == null)
|
|
675
|
+
return null;
|
|
676
|
+
try {
|
|
677
|
+
return JSON.parse(finalText);
|
|
678
|
+
}
|
|
679
|
+
catch {
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
function resolveAppServerAdapter(registry, cliName) {
|
|
684
|
+
if (cliName != null) {
|
|
685
|
+
const adapter = registry.resolveAdapter(cliName);
|
|
686
|
+
if (adapter.backend !== "app_server") {
|
|
687
|
+
throw new InvalidParamsError(`Adapter "${adapter.name}" does not use the Codex app-server backend.`);
|
|
688
|
+
}
|
|
689
|
+
return adapter;
|
|
690
|
+
}
|
|
691
|
+
const appServerAdapters = registry
|
|
692
|
+
.getAdapterNames()
|
|
693
|
+
.map((name) => registry.resolveAdapter(name))
|
|
694
|
+
.filter((adapter) => adapter.backend === "app_server");
|
|
695
|
+
if (appServerAdapters.length === 0) {
|
|
696
|
+
throw new InvalidParamsError("No Codex app-server adapters configured.");
|
|
697
|
+
}
|
|
698
|
+
if (appServerAdapters.length > 1) {
|
|
699
|
+
throw new InvalidParamsError(`cli_name is required when multiple app-server adapters are configured. Available: ${appServerAdapters
|
|
700
|
+
.map((adapter) => adapter.name)
|
|
701
|
+
.sort()
|
|
702
|
+
.join(", ")}`);
|
|
703
|
+
}
|
|
704
|
+
return appServerAdapters[0];
|
|
705
|
+
}
|
|
706
|
+
function buildAsyncPrompt(input, adapter) {
|
|
707
|
+
const { prompt } = assemblePrompt({
|
|
708
|
+
input: {
|
|
709
|
+
prompt: input.prompt,
|
|
710
|
+
cli_name: input.cli_name,
|
|
711
|
+
role: input.role,
|
|
712
|
+
absolute_file_paths: input.absolute_file_paths,
|
|
713
|
+
images: input.images,
|
|
714
|
+
context: input.context,
|
|
715
|
+
debug: input.debug,
|
|
716
|
+
debug_keep_artifacts: false,
|
|
717
|
+
},
|
|
718
|
+
adapter,
|
|
719
|
+
continuationCache: emptyContinuationCache,
|
|
720
|
+
runDirPath: process.cwd(),
|
|
721
|
+
});
|
|
722
|
+
return prompt;
|
|
723
|
+
}
|
|
724
|
+
function resolvePolicies(adapter, input) {
|
|
725
|
+
const appConfig = adapter.app_server;
|
|
726
|
+
const defaultSandbox = appConfig?.default_sandbox ?? "read-only";
|
|
727
|
+
const defaultApproval = appConfig?.default_approval ?? "never";
|
|
728
|
+
const sandbox = input.sandbox ?? defaultSandbox;
|
|
729
|
+
const approval = input.approval ?? defaultApproval;
|
|
730
|
+
enforceSandboxClamp(defaultSandbox, sandbox, appConfig?.allow_danger_full_access === true);
|
|
731
|
+
enforceApprovalClamp(defaultApproval, approval);
|
|
732
|
+
return {
|
|
733
|
+
sandbox,
|
|
734
|
+
approval,
|
|
735
|
+
model: input.model ?? appConfig?.default_model,
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function enforceSandboxClamp(defaultSandbox, requested, allowDangerFullAccess) {
|
|
739
|
+
const rank = {
|
|
740
|
+
"read-only": 0,
|
|
741
|
+
"workspace-write": 1,
|
|
742
|
+
"danger-full-access": 2,
|
|
743
|
+
};
|
|
744
|
+
if (requested === "danger-full-access" && !allowDangerFullAccess) {
|
|
745
|
+
throw new InvalidParamsError('Per-call sandbox "danger-full-access" requires app_server.allow_danger_full_access: true.');
|
|
746
|
+
}
|
|
747
|
+
if (rank[requested] > rank[defaultSandbox]) {
|
|
748
|
+
throw new InvalidParamsError(`Per-call sandbox "${requested}" is more privileged than adapter default "${defaultSandbox}".`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
function enforceApprovalClamp(defaultApproval, requested) {
|
|
752
|
+
const rank = {
|
|
753
|
+
untrusted: 0,
|
|
754
|
+
"on-failure": 1,
|
|
755
|
+
"on-request": 2,
|
|
756
|
+
granular: 3,
|
|
757
|
+
never: 4,
|
|
758
|
+
};
|
|
759
|
+
if (rank[requested] > rank[defaultApproval]) {
|
|
760
|
+
throw new InvalidParamsError(`Per-call approval "${requested}" is more privileged than adapter default "${defaultApproval}".`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Resolve the prior thread to resume, honoring raw thread ids before the alias
|
|
765
|
+
* precedence resume_continuation_id > session_id > continuation_id.
|
|
766
|
+
*
|
|
767
|
+
* resume_continuation_id and session_id are EXPLICIT resume requests: an unknown
|
|
768
|
+
* alias is an error. continuation_id is a WRITE/READ-symmetric alias (F2): the
|
|
769
|
+
* SAME continuation_id passed back resumes the thread it tagged, but its FIRST
|
|
770
|
+
* use (no stored alias yet) is not an error — it simply starts a fresh thread
|
|
771
|
+
* that {@link rememberThread} then records under that continuation_id.
|
|
772
|
+
*/
|
|
773
|
+
function resolveResumeThreadId(input, registry) {
|
|
774
|
+
if (input.resume_thread_id != null) {
|
|
775
|
+
const threadId = input.resume_thread_id;
|
|
776
|
+
assertThreadNotRunning(registry, threadId);
|
|
777
|
+
return threadId;
|
|
778
|
+
}
|
|
779
|
+
let key;
|
|
780
|
+
let explicit = false;
|
|
781
|
+
if (input.resume_continuation_id != null) {
|
|
782
|
+
key = continuationKey(input.resume_continuation_id);
|
|
783
|
+
explicit = true;
|
|
784
|
+
}
|
|
785
|
+
else if (input.session_id != null) {
|
|
786
|
+
key = sessionKey(input.session_id);
|
|
787
|
+
explicit = true;
|
|
788
|
+
}
|
|
789
|
+
else if (input.continuation_id != null) {
|
|
790
|
+
key = continuationKey(input.continuation_id);
|
|
791
|
+
explicit = false;
|
|
792
|
+
}
|
|
793
|
+
if (key == null)
|
|
794
|
+
return undefined;
|
|
795
|
+
const threadId = asyncThreadIds.get(key);
|
|
796
|
+
if (threadId == null) {
|
|
797
|
+
if (!explicit)
|
|
798
|
+
return undefined; // first use of this continuation_id → fresh thread
|
|
799
|
+
throw new InvalidParamsError(input.resume_continuation_id != null
|
|
800
|
+
? `Unknown async continuation_id: ${input.resume_continuation_id}`
|
|
801
|
+
: `Unknown async session_id: ${input.session_id}`);
|
|
802
|
+
}
|
|
803
|
+
assertThreadNotRunning(registry, threadId);
|
|
804
|
+
return threadId;
|
|
805
|
+
}
|
|
806
|
+
function assertThreadNotRunning(registry, threadId) {
|
|
807
|
+
// Cheap, advisory fast-path for a friendlier early error: refuse to resume a
|
|
808
|
+
// thread a still-running session visibly holds. This is NOT the authoritative
|
|
809
|
+
// guard — startSession reserves the resumed thread SYNCHRONOUSLY before any
|
|
810
|
+
// await (review fix A), closing the deferred-insertion race two concurrent
|
|
811
|
+
// resumes could otherwise slip through here.
|
|
812
|
+
if (registry.list({ status: "running" }).some((s) => s.threadId === threadId)) {
|
|
813
|
+
throw new InvalidParamsError(`Cannot resume thread ${threadId}: a session is still running on it. ` +
|
|
814
|
+
"Wait for that turn to complete before resuming.");
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* The user-facing identifier that actually drove the resume (or null when the run
|
|
819
|
+
* started a fresh thread), echoed back as `resumed_from`. Mirrors the precedence
|
|
820
|
+
* in {@link resolveResumeThreadId}.
|
|
821
|
+
*/
|
|
822
|
+
function effectiveResumeKey(input, resumeThreadId) {
|
|
823
|
+
if (resumeThreadId == null)
|
|
824
|
+
return null;
|
|
825
|
+
if (input.resume_thread_id != null)
|
|
826
|
+
return input.resume_thread_id;
|
|
827
|
+
if (input.resume_continuation_id != null)
|
|
828
|
+
return input.resume_continuation_id;
|
|
829
|
+
if (input.session_id != null)
|
|
830
|
+
return input.session_id;
|
|
831
|
+
if (input.continuation_id != null)
|
|
832
|
+
return input.continuation_id;
|
|
833
|
+
return null;
|
|
834
|
+
}
|
|
835
|
+
function rememberThread(input, snapshot) {
|
|
836
|
+
asyncThreadIds.set(sessionKey(snapshot.sessionId), snapshot.threadId);
|
|
837
|
+
if (input.continuation_id != null) {
|
|
838
|
+
const contKey = continuationKey(input.continuation_id);
|
|
839
|
+
asyncThreadIds.set(contKey, snapshot.threadId);
|
|
840
|
+
// Track the paired alias so cleanup can prune it (otherwise the thread stays
|
|
841
|
+
// resumable via continuation_id after the session is cleaned up).
|
|
842
|
+
sessionContinuationKeys.set(sessionKey(snapshot.sessionId), contKey);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
async function waitForRun(registry, sessionId, input, signal) {
|
|
846
|
+
const opts = buildWaitOptions(input.timeout_seconds, signal);
|
|
847
|
+
if (signal == null) {
|
|
848
|
+
return registry.waitForTurn(sessionId, opts);
|
|
849
|
+
}
|
|
850
|
+
// Run-path cancellation INTERRUPTS the turn (and clears the underlying wait via
|
|
851
|
+
// the threaded signal), then surfaces CancellationError.
|
|
852
|
+
const outcome = await raceAbort(registry.waitForTurn(sessionId, opts), signal);
|
|
853
|
+
if (outcome.aborted) {
|
|
854
|
+
await registry.interrupt(sessionId).catch(() => undefined);
|
|
855
|
+
throw new CancellationError();
|
|
856
|
+
}
|
|
857
|
+
return outcome.value;
|
|
858
|
+
}
|
|
859
|
+
async function waitForManage(registry, sessionId, timeoutSeconds, signal) {
|
|
860
|
+
const opts = buildWaitOptions(timeoutSeconds, signal);
|
|
861
|
+
if (signal == null) {
|
|
862
|
+
return registry.waitForTurn(sessionId, opts);
|
|
863
|
+
}
|
|
864
|
+
// Manage-path cancellation only STOPS WAITING (the threaded signal clears the
|
|
865
|
+
// registry's race timer/listeners); it never interrupts the turn.
|
|
866
|
+
const outcome = await raceAbort(registry.waitForTurn(sessionId, opts), signal);
|
|
867
|
+
if (outcome.aborted) {
|
|
868
|
+
throw new CancellationError();
|
|
869
|
+
}
|
|
870
|
+
return outcome.value;
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Batch wait. Races all ids and returns on the FIRST actionable session rather
|
|
874
|
+
* than blocking on a barrier. A session is "actionable" when its snapshot is
|
|
875
|
+
* TERMINAL (completed/failed/timeout/interrupted) or PAUSED (running with a
|
|
876
|
+
* pending approval). On each wake we re-poll ALL sessions and pick the
|
|
877
|
+
* lowest-input-index actionable one as winner, preserving determinism when
|
|
878
|
+
* several settle together. A bare wait-timeout (still running, no approval) is
|
|
879
|
+
* NOT actionable, so we keep waiting on the rest.
|
|
880
|
+
*
|
|
881
|
+
* If the shared timeout elapses with no actionable session, winner is null and
|
|
882
|
+
* `timed_out` is true. `pending_session_ids` lists ONLY sessions still running
|
|
883
|
+
* with no pending approval — terminal and paused ids are excluded. `elapsed_ms`
|
|
884
|
+
* is wall-clock. The abort signal throws {@link CancellationError}.
|
|
885
|
+
*/
|
|
886
|
+
async function waitForManageBatch(registry, sessionIds, timeoutSeconds, signal) {
|
|
887
|
+
// Validate all ids up-front so an unknown session surfaces a deterministic
|
|
888
|
+
// InvalidParamsError instead of racing a rejection against valid sessions.
|
|
889
|
+
for (const id of sessionIds)
|
|
890
|
+
registry.poll(id);
|
|
891
|
+
const startedAtMs = Date.now();
|
|
892
|
+
const isActionable = (id) => {
|
|
893
|
+
const snapshot = registry.poll(id);
|
|
894
|
+
return (snapshot.status !== "running" ||
|
|
895
|
+
(snapshot.status === "running" && snapshot.pendingApprovals.length > 0));
|
|
896
|
+
};
|
|
897
|
+
// The first actionable session in INPUT order, or undefined. find() walks in
|
|
898
|
+
// order, so this is always the lowest input index.
|
|
899
|
+
const lowestActionable = () => sessionIds.find((id) => isActionable(id));
|
|
900
|
+
const buildResult = (winnerId) => {
|
|
901
|
+
const pendingSessionIds = sessionIds.filter((id) => !isActionable(id));
|
|
902
|
+
const elapsedMs = Date.now() - startedAtMs;
|
|
903
|
+
if (winnerId == null) {
|
|
904
|
+
return {
|
|
905
|
+
value: {
|
|
906
|
+
winner_session_id: null,
|
|
907
|
+
timed_out: true,
|
|
908
|
+
pending_session_ids: pendingSessionIds,
|
|
909
|
+
snapshot: null,
|
|
910
|
+
elapsed_ms: elapsedMs,
|
|
911
|
+
},
|
|
912
|
+
isError: false,
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
const winnerSnapshot = registry.poll(winnerId);
|
|
916
|
+
const response = snapshotResponse(winnerSnapshot);
|
|
917
|
+
addPendingApprovalNextAction(response, winnerSnapshot);
|
|
918
|
+
return {
|
|
919
|
+
value: {
|
|
920
|
+
winner_session_id: winnerId,
|
|
921
|
+
timed_out: false,
|
|
922
|
+
pending_session_ids: pendingSessionIds,
|
|
923
|
+
snapshot: response,
|
|
924
|
+
elapsed_ms: elapsedMs,
|
|
925
|
+
},
|
|
926
|
+
isError: snapshotIsError(winnerSnapshot),
|
|
927
|
+
};
|
|
928
|
+
};
|
|
929
|
+
// Fast path: if any session is already actionable, return immediately.
|
|
930
|
+
const alreadyActionable = lowestActionable();
|
|
931
|
+
if (alreadyActionable != null)
|
|
932
|
+
return buildResult(alreadyActionable);
|
|
933
|
+
// Cleanup controller: its signal is threaded into EVERY per-session
|
|
934
|
+
// waitForTurn so that on any early return (a winner is crowned) or throw
|
|
935
|
+
// (caller abort) we can abort the still-outstanding losing waits. Each such
|
|
936
|
+
// wait then hits its abort path inside raceTurnWait → its race timer and
|
|
937
|
+
// terminal/attention listeners are torn down and it settles, so the residual
|
|
938
|
+
// .then(done) continuations become guaranteed no-ops. This is distinct from
|
|
939
|
+
// the caller's signal: only the caller's signal produces CancellationError;
|
|
940
|
+
// the cleanup abort never does (it is not raced via raceAbort below).
|
|
941
|
+
const cleanupController = new AbortController();
|
|
942
|
+
// If the caller cancels, also tear down the per-session waits. (The reverse
|
|
943
|
+
// is unnecessary: aborting cleanup must not look like a caller cancellation.)
|
|
944
|
+
let onCallerAbort;
|
|
945
|
+
if (signal != null) {
|
|
946
|
+
if (signal.aborted) {
|
|
947
|
+
cleanupController.abort();
|
|
948
|
+
}
|
|
949
|
+
else {
|
|
950
|
+
onCallerAbort = () => cleanupController.abort();
|
|
951
|
+
signal.addEventListener("abort", onCallerAbort, { once: true });
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
try {
|
|
955
|
+
const opts = buildWaitOptions(timeoutSeconds, cleanupController.signal);
|
|
956
|
+
// Race the still-pending sessions' waits. Each wake re-polls ALL sessions; a
|
|
957
|
+
// wait that merely timed out (still running, no approval) is non-actionable,
|
|
958
|
+
// so we drop it and keep racing the rest. Map rejections to a resolved void so
|
|
959
|
+
// a single bad wait cannot reject the batch. Tag each promise with its id so a
|
|
960
|
+
// settled one is not re-awaited.
|
|
961
|
+
const outstanding = new Map();
|
|
962
|
+
for (const id of sessionIds) {
|
|
963
|
+
outstanding.set(id, registry.waitForTurn(id, opts).then(() => id, () => id));
|
|
964
|
+
}
|
|
965
|
+
while (outstanding.size > 0) {
|
|
966
|
+
const settledId = signal == null
|
|
967
|
+
? await Promise.race(outstanding.values())
|
|
968
|
+
: await (async () => {
|
|
969
|
+
const outcome = await raceAbort(Promise.race(outstanding.values()), signal);
|
|
970
|
+
if (outcome.aborted)
|
|
971
|
+
throw new CancellationError();
|
|
972
|
+
return outcome.value;
|
|
973
|
+
})();
|
|
974
|
+
outstanding.delete(settledId);
|
|
975
|
+
const winner = lowestActionable();
|
|
976
|
+
if (winner != null)
|
|
977
|
+
return buildResult(winner);
|
|
978
|
+
}
|
|
979
|
+
// All waits settled with no actionable session: shared timeout elapsed.
|
|
980
|
+
return buildResult(undefined);
|
|
981
|
+
}
|
|
982
|
+
finally {
|
|
983
|
+
// Covers normal return (a winner), all-timed-out return, and the
|
|
984
|
+
// caller-abort throw. Aborting forces every still-outstanding losing
|
|
985
|
+
// waitForTurn to settle and release its timer/listener.
|
|
986
|
+
cleanupController.abort();
|
|
987
|
+
if (onCallerAbort != null) {
|
|
988
|
+
signal?.removeEventListener("abort", onCallerAbort);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
function buildWaitOptions(timeoutSeconds, signal) {
|
|
993
|
+
const timeoutMs = timeoutSeconds != null ? Math.ceil(timeoutSeconds * 1000) : undefined;
|
|
994
|
+
if (timeoutMs == null && signal == null)
|
|
995
|
+
return undefined;
|
|
996
|
+
return {
|
|
997
|
+
...(timeoutMs != null ? { timeoutMs } : {}),
|
|
998
|
+
...(signal != null ? { signal } : {}),
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
function raceAbort(promise, signal) {
|
|
1002
|
+
if (signal.aborted)
|
|
1003
|
+
return Promise.resolve({ aborted: true });
|
|
1004
|
+
let onAbort;
|
|
1005
|
+
const abortPromise = new Promise((resolve) => {
|
|
1006
|
+
onAbort = () => resolve({ aborted: true });
|
|
1007
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1008
|
+
});
|
|
1009
|
+
return Promise.race([
|
|
1010
|
+
promise.then((value) => ({ aborted: false, value })),
|
|
1011
|
+
abortPromise,
|
|
1012
|
+
]).finally(() => {
|
|
1013
|
+
if (onAbort != null) {
|
|
1014
|
+
signal.removeEventListener("abort", onAbort);
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
function throwIfAborted(signal) {
|
|
1019
|
+
if (signal?.aborted) {
|
|
1020
|
+
throw new CancellationError();
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
function snapshotResponse(snapshot, maxResponseChars) {
|
|
1024
|
+
const finalText = snapshot.finalText != null
|
|
1025
|
+
? limitOutput(snapshot.finalText, maxResponseChars ?? DEFAULT_MAX_RESPONSE_CHARS)
|
|
1026
|
+
: null;
|
|
1027
|
+
return {
|
|
1028
|
+
session_id: snapshot.sessionId,
|
|
1029
|
+
thread_id: snapshot.threadId,
|
|
1030
|
+
active_turn_id: snapshot.activeTurnId,
|
|
1031
|
+
status: snapshot.status,
|
|
1032
|
+
cwd: snapshot.cwd,
|
|
1033
|
+
model: snapshot.model,
|
|
1034
|
+
sandbox: snapshot.sandbox,
|
|
1035
|
+
approval: snapshot.approval,
|
|
1036
|
+
origin: snapshot.origin,
|
|
1037
|
+
depth: snapshot.depth,
|
|
1038
|
+
pending_approvals: snapshot.pendingApprovals.map((approval) => pendingApprovalResponse(approval, maxResponseChars)),
|
|
1039
|
+
final_text: finalText,
|
|
1040
|
+
final_text_truncated: snapshot.finalTextTruncated,
|
|
1041
|
+
latest_assistant_preview: snapshot.latestAssistantPreview,
|
|
1042
|
+
latest_assistant_preview_truncated: snapshot.latestAssistantPreviewTruncated,
|
|
1043
|
+
latest_diff: snapshot.latestDiff,
|
|
1044
|
+
latest_plan: snapshot.latestPlan != null && snapshot.latestPlan.length > 0
|
|
1045
|
+
? snapshot.latestPlan.map((s) => ({ step: s.step, status: s.status }))
|
|
1046
|
+
: null,
|
|
1047
|
+
plan_explanation: snapshot.planExplanation,
|
|
1048
|
+
token_usage: tokenUsageResponse(snapshot.tokenUsage),
|
|
1049
|
+
failure_reason: snapshot.failureReason,
|
|
1050
|
+
event_count: snapshot.eventCount,
|
|
1051
|
+
created_at_ms: snapshot.createdAtMs,
|
|
1052
|
+
updated_at_ms: snapshot.updatedAtMs,
|
|
1053
|
+
last_activity_at_ms: snapshot.lastActivityAtMs,
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
function pendingApprovalResponse(approval, maxResponseChars) {
|
|
1057
|
+
const cap = Math.min(maxResponseChars ?? DEFAULT_MAX_RESPONSE_CHARS, PENDING_APPROVAL_FIELD_CHARS);
|
|
1058
|
+
const response = {
|
|
1059
|
+
id: approval.id,
|
|
1060
|
+
type: approval.type,
|
|
1061
|
+
allowed_decisions: allowedDecisionsForType(approval.type),
|
|
1062
|
+
reason: boundedApprovalString(approval.reason, cap),
|
|
1063
|
+
requested_at_ms: approval.requestedAtMs,
|
|
1064
|
+
};
|
|
1065
|
+
if (approval.command !== undefined) {
|
|
1066
|
+
response["command"] = boundedApprovalString(approval.command, cap);
|
|
1067
|
+
}
|
|
1068
|
+
if (approval.cwd !== undefined) {
|
|
1069
|
+
response["cwd"] = boundedApprovalString(approval.cwd, cap);
|
|
1070
|
+
}
|
|
1071
|
+
if (approval.grantRoot !== undefined) {
|
|
1072
|
+
response["grant_root"] = boundedApprovalString(approval.grantRoot, cap);
|
|
1073
|
+
}
|
|
1074
|
+
if (approval.networkApprovalContext !== undefined) {
|
|
1075
|
+
response["network_approval_context"] = boundedUnknown(approval.networkApprovalContext, cap);
|
|
1076
|
+
}
|
|
1077
|
+
return response;
|
|
1078
|
+
}
|
|
1079
|
+
function allowedDecisionsForType(type) {
|
|
1080
|
+
switch (type) {
|
|
1081
|
+
case "commandExecution":
|
|
1082
|
+
case "fileChange":
|
|
1083
|
+
return [...SUPPORTED_COMMAND_EXECUTION_APPROVAL_DECISIONS];
|
|
1084
|
+
case "permissions":
|
|
1085
|
+
return [];
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
function boundedApprovalString(value, maxChars) {
|
|
1089
|
+
if (value == null)
|
|
1090
|
+
return null;
|
|
1091
|
+
return limitOutput(redactSensitiveText(value), maxChars);
|
|
1092
|
+
}
|
|
1093
|
+
function boundedUnknown(value, maxChars) {
|
|
1094
|
+
if (value == null)
|
|
1095
|
+
return null;
|
|
1096
|
+
if (typeof value === "string")
|
|
1097
|
+
return boundedApprovalString(value, maxChars);
|
|
1098
|
+
if (typeof value === "number" ||
|
|
1099
|
+
typeof value === "boolean") {
|
|
1100
|
+
return value;
|
|
1101
|
+
}
|
|
1102
|
+
if (typeof value === "bigint")
|
|
1103
|
+
return value.toString();
|
|
1104
|
+
try {
|
|
1105
|
+
const json = JSON.stringify(value);
|
|
1106
|
+
const redactedJson = redactSensitiveText(json);
|
|
1107
|
+
if (redactedJson.length <= maxChars)
|
|
1108
|
+
return JSON.parse(redactedJson);
|
|
1109
|
+
return {
|
|
1110
|
+
summary: limitOutput(redactedJson, maxChars),
|
|
1111
|
+
truncated: true,
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
catch {
|
|
1115
|
+
return {
|
|
1116
|
+
summary: limitOutput(redactSensitiveText(String(value)), maxChars),
|
|
1117
|
+
truncated: true,
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
function redactSensitiveText(value) {
|
|
1122
|
+
return value
|
|
1123
|
+
.replace(/("?[A-Z0-9_]*(?:KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL)[A-Z0-9_]*"?\s*:\s*)"[^"]*"/gi, "$1\"[REDACTED]\"")
|
|
1124
|
+
.replace(/\b([A-Z0-9_]*(?:KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL)[A-Z0-9_]*)=([^\s]+)/gi, "$1=[REDACTED]")
|
|
1125
|
+
.replace(/\b(?:sk|pk|ghp|gho|ghu|ghs|github_pat)_[A-Za-z0-9_=-]{8,}\b/g, "[REDACTED]");
|
|
1126
|
+
}
|
|
1127
|
+
function healthResponse(health) {
|
|
1128
|
+
return {
|
|
1129
|
+
active_leases: health.activeLeases,
|
|
1130
|
+
retained_session_count: health.retainedSessionCount,
|
|
1131
|
+
app_server_protocol: {
|
|
1132
|
+
accepted_version: health.appServerProtocol.acceptedVersion,
|
|
1133
|
+
min_version: health.appServerProtocol.minVersion,
|
|
1134
|
+
max_version: health.appServerProtocol.maxVersion,
|
|
1135
|
+
strict_version: health.appServerProtocol.strictVersion,
|
|
1136
|
+
},
|
|
1137
|
+
child_respawn_count: health.childRespawnCount,
|
|
1138
|
+
recent_failures: {
|
|
1139
|
+
count: health.recentFailures.count,
|
|
1140
|
+
samples: health.recentFailures.samples,
|
|
1141
|
+
},
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
function emptyHealthResponse() {
|
|
1145
|
+
const minVersion = process.env["CLINKX_APP_SERVER_MIN_VERSION"] ??
|
|
1146
|
+
CODEX_APP_SERVER_PROTOCOL_VERSION;
|
|
1147
|
+
const maxVersion = process.env["CLINKX_APP_SERVER_MAX_VERSION"] ??
|
|
1148
|
+
CODEX_APP_SERVER_PROTOCOL_VERSION;
|
|
1149
|
+
return {
|
|
1150
|
+
active_leases: 0,
|
|
1151
|
+
retained_session_count: 0,
|
|
1152
|
+
app_server_protocol: {
|
|
1153
|
+
accepted_version: null,
|
|
1154
|
+
min_version: minVersion,
|
|
1155
|
+
max_version: maxVersion,
|
|
1156
|
+
strict_version: ["1", "true", "yes", "on"].includes(String(process.env["CLINKX_APP_SERVER_STRICT_VERSION"] ?? "").toLowerCase()),
|
|
1157
|
+
},
|
|
1158
|
+
child_respawn_count: 0,
|
|
1159
|
+
recent_failures: {
|
|
1160
|
+
count: 0,
|
|
1161
|
+
samples: [],
|
|
1162
|
+
},
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
/** Coerce an arbitrary (untrusted) value to a bounded string, or null. */
|
|
1166
|
+
function boundedUnknownString(value, maxChars) {
|
|
1167
|
+
if (value == null)
|
|
1168
|
+
return null;
|
|
1169
|
+
const text = typeof value === "string" ? value : String(value);
|
|
1170
|
+
return limitOutput(text, maxChars);
|
|
1171
|
+
}
|
|
1172
|
+
function modelListResponse(result) {
|
|
1173
|
+
const models = result.data.slice(0, MAX_LIST_MODELS).map((m) => ({
|
|
1174
|
+
id: limitOutput(m.id, MODEL_IDENTIFIER_CHARS),
|
|
1175
|
+
model: limitOutput(m.model, MODEL_IDENTIFIER_CHARS),
|
|
1176
|
+
display_name: m.displayName != null
|
|
1177
|
+
? limitOutput(m.displayName, MODEL_IDENTIFIER_CHARS)
|
|
1178
|
+
: null,
|
|
1179
|
+
description: m.description != null
|
|
1180
|
+
? limitOutput(m.description, MODEL_DESCRIPTION_CHARS)
|
|
1181
|
+
: null,
|
|
1182
|
+
is_default: m.isDefault ?? null,
|
|
1183
|
+
hidden: m.hidden ?? null,
|
|
1184
|
+
default_reasoning_effort: boundedUnknownString(m.defaultReasoningEffort, MODEL_EFFORT_CHARS),
|
|
1185
|
+
supported_reasoning_efforts: m.supportedReasoningEfforts != null
|
|
1186
|
+
? m.supportedReasoningEfforts
|
|
1187
|
+
.slice(0, MAX_SUPPORTED_REASONING_EFFORTS)
|
|
1188
|
+
.map((e) => boundedUnknownString(e, MODEL_EFFORT_CHARS))
|
|
1189
|
+
: null,
|
|
1190
|
+
}));
|
|
1191
|
+
return {
|
|
1192
|
+
supported: true,
|
|
1193
|
+
models,
|
|
1194
|
+
next_cursor: result.nextCursor != null
|
|
1195
|
+
? limitOutput(result.nextCursor, MAX_CURSOR_CHARS)
|
|
1196
|
+
: null,
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
function listModelsErrorResponse(err) {
|
|
1200
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1201
|
+
return {
|
|
1202
|
+
supported: false,
|
|
1203
|
+
reason: limitOutput(`Codex model/list RPC failed: ${message}`, 500),
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
function tokenUsageResponse(usage) {
|
|
1207
|
+
return {
|
|
1208
|
+
baseline_total: usage.baselineTotal,
|
|
1209
|
+
last_usage: usage.lastUsage,
|
|
1210
|
+
delta_usage: usage.deltaUsage,
|
|
1211
|
+
cumulative_total: usage.cumulativeTotal,
|
|
1212
|
+
incomplete: usage.incomplete,
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
function addPendingApprovalNextAction(response, snapshot) {
|
|
1216
|
+
if (snapshot.status !== "running" || snapshot.pendingApprovals.length === 0) {
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
response["next_action"] =
|
|
1220
|
+
'Use clink_agent_manage op:"respond" with this session_id, an approval_id from pending_approvals, and a decision.';
|
|
1221
|
+
}
|
|
1222
|
+
function logResponse(page) {
|
|
1223
|
+
return {
|
|
1224
|
+
events: page.events,
|
|
1225
|
+
next_cursor: page.nextCursor,
|
|
1226
|
+
truncated: page.truncated,
|
|
1227
|
+
floor: page.floor,
|
|
1228
|
+
capacity: page.capacity,
|
|
1229
|
+
persisted: false,
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
function addWaitMetadata(response, snapshot, elapsedMs) {
|
|
1233
|
+
const running = snapshot.status === "running";
|
|
1234
|
+
const paused = running && snapshot.pendingApprovals.length > 0;
|
|
1235
|
+
response["wake_reason"] = paused
|
|
1236
|
+
? "paused_on_approval"
|
|
1237
|
+
: running
|
|
1238
|
+
? "timeout"
|
|
1239
|
+
: "terminal";
|
|
1240
|
+
response["wait_result"] = running && !paused
|
|
1241
|
+
? "timed_out"
|
|
1242
|
+
: paused
|
|
1243
|
+
? "actionable"
|
|
1244
|
+
: "settled";
|
|
1245
|
+
response["elapsed_ms"] = elapsedMs;
|
|
1246
|
+
}
|
|
1247
|
+
function snapshotIsError(snapshot) {
|
|
1248
|
+
return (snapshot.status === "failed" ||
|
|
1249
|
+
snapshot.status === "timeout" ||
|
|
1250
|
+
snapshot.status === "interrupted");
|
|
1251
|
+
}
|
|
1252
|
+
function jsonResult(value, opts = {}) {
|
|
1253
|
+
const result = {
|
|
1254
|
+
content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
|
|
1255
|
+
};
|
|
1256
|
+
if (opts.isError === true)
|
|
1257
|
+
result.isError = true;
|
|
1258
|
+
return result;
|
|
1259
|
+
}
|
|
1260
|
+
function sessionKey(sessionId) {
|
|
1261
|
+
return `session:${sessionId}`;
|
|
1262
|
+
}
|
|
1263
|
+
function continuationKey(continuationId) {
|
|
1264
|
+
return `continuation:${continuationId}`;
|
|
1265
|
+
}
|
|
1266
|
+
export function resetAgentToolStateForTest() {
|
|
1267
|
+
asyncThreadIds.clear();
|
|
1268
|
+
sessionContinuationKeys.clear();
|
|
1269
|
+
}
|
|
1270
|
+
//# sourceMappingURL=agent-tools.js.map
|