@overdeck/desktop 0.45.23 → 0.46.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/drizzle/overdeck/0000_overdeck_init.sql +34 -0
- package/package.json +1 -1
- package/server/{acceptance-criteria-DOA0KgXx.js → acceptance-criteria-CCwFGICj.js} +2 -2
- package/server/activity-logger-CMthG6Vd.js +3 -0
- package/server/{activity-logger-DDlOIAML.js → activity-logger-CyteR4JO.js} +24 -13
- package/server/{advancing-selfheal-BBu6DSNm.js → advancing-selfheal-De1L-Sb4.js} +6 -6
- package/server/{agent-gc-BV1q_2EV.js → agent-gc-CBULruzm.js} +5 -5
- package/server/agent-state-VQk8fY9F.js +3 -0
- package/server/{agent-state-service-HoK8U4jW.js → agent-state-service-BlnSkRFA.js} +5 -5
- package/server/{agent-state-service-CnzFKSAQ.js → agent-state-service-hZpxIw1E.js} +1 -1
- package/server/agents-BKKkxKgK.js +3 -0
- package/server/{agents-B4Bjbkfv.js → agents-C5rWYhb6.js} +4 -4
- package/server/archive-planning-0FJ6Xd3b.js +2 -0
- package/server/{archive-planning-DYrYojxl.js → archive-planning-C0lAbNB_.js} +16 -24
- package/server/{artifact-index-jobs-C-xy7Z5v.js → artifact-index-jobs-ug6ZxIP5.js} +2 -2
- package/server/assemble-DnffNpOo.js +339 -0
- package/server/{backlog-auto-trigger-GtG71X8H.js → backlog-auto-trigger-Dh-i9PQf.js} +3 -3
- package/server/{behavior-B_-4EKF3.js → behavior-CfIg6pKp.js} +2 -2
- package/server/bridge-pool-patrol-CUFmjVDN.js +136 -0
- package/server/{briefing-freshness-DWdW7vUm.js → briefing-freshness-DHmCSLar.js} +2 -2
- package/server/{build-info-DgTqZkL5.js → build-info-BP160gU7.js} +1 -1
- package/server/{build-info-BT096lEl.js → build-info-CuFfxZxp.js} +5 -3
- package/server/{checkpoint-manager-DDqcGyu8.js → checkpoint-manager-DEWD82qK.js} +2 -2
- package/server/{checkpoint-manager-B7Bt29J0.js → checkpoint-manager-Dxh0FYsv.js} +1 -1
- package/server/checkpoint-worker.js +1 -1
- package/server/{checkpoints-C-AXLfYT.js → checkpoints-CZvbtLnr.js} +2 -2
- package/server/{checkpoints-CpgCjAlN.js → checkpoints-DMTgXuLG.js} +1 -1
- package/server/{claude-hooks-provision-C8wdRnN1.js → claude-hooks-provision-CN-xo-do.js} +1 -1
- package/server/{claude-hooks-provision-but-_qGY.js → claude-hooks-provision-fvy4RD2b.js} +13 -2
- package/server/{claude-permissions-H_5wHQZQ.js → claude-permissions-CN0cSCPp.js} +2 -2
- package/server/{claude-settings-overlay-CbSz0Dvb.js → claude-settings-overlay-CJn35FHQ.js} +2 -2
- package/server/{claude-settings-overlay-BTENVmYf.js → claude-settings-overlay-Dabi9slj.js} +1 -1
- package/server/cli-DmXETsw-.js +3 -0
- package/server/{cli-vBeXbPrq.js → cli-oCWYA1pE.js} +9 -9
- package/server/{cliproxy-Bgb2XCqi.js → cliproxy-C8mB3KuB.js} +2 -2
- package/server/{cliproxy-DW0V86F8.js → cliproxy-PGwTX_o8.js} +1 -1
- package/server/{close-issue-Dn_4c4G8.js → close-issue-D5MF5GqF.js} +5 -5
- package/server/{closed-issue-reaper-CWpY2uBw.js → closed-issue-reaper-CJ24u0Zt.js} +1 -1
- package/server/{closed-issue-reaper-hANY_ehE.js → closed-issue-reaper-Cltx86x5.js} +37 -16
- package/server/{codex-Dg7Is5yb.js → codex-BdEsUFWh.js} +1 -1
- package/server/{codex-TmSi-yTv.js → codex-Drlf5Xhf.js} +37 -8
- package/server/{codex-parser-C4gSH-IR.js → codex-parser-DpB89Vqa.js} +2 -2
- package/server/compaction-continuation-K2AxQX4H.js +200 -0
- package/server/{config-DjLOdbgT.js → config-B2IMcBxx.js} +4 -3
- package/server/{config-DUIhMN_T.js → config-DV-grfQc.js} +1 -1
- package/server/{config-CCvjQf27.js → config-Dr3pW6w6.js} +2 -2
- package/server/{config-yaml-DvyMuOle.js → config-yaml-CmOPzmBb.js} +290 -22
- package/server/{conflict-gate-CmrYpa47.js → conflict-gate-B9cN3SBi.js} +4 -4
- package/server/{conflict-gate-BSJkNTdp.js → conflict-gate-Dh754uyA.js} +1 -1
- package/server/context-layers-D6LZo3l1.js +4 -0
- package/server/context-layers-dR5YxHYB.js +37 -0
- package/server/{control-settings-aP1ANIlI.js → control-settings-C7DUehE1.js} +49 -19
- package/server/{conversation-compaction-CNbTLB86.js → conversation-compaction-BoqdDXWF.js} +8 -8
- package/server/{conversation-compaction-DiG5zWQq.js → conversation-compaction-Dj4FAKoZ.js} +1 -1
- package/server/{conversation-service-Du-MTNpJ.js → conversation-service-5aCnq1zR.js} +331 -20
- package/server/conversation-service-BCxl4f8Z.js +3 -0
- package/server/{conversations-DbOs1e9Q.js → conversations-BUpYsOZg.js} +5 -5
- package/server/{conversations-6mPANqvL.js → conversations-C34dyIb9.js} +1 -1
- package/server/{cost-Ca0YTWDw.js → cost-Bgcub5kW.js} +12 -2
- package/server/{cost-CW4WSe72.js → cost-CvxRrucd.js} +3 -3
- package/server/{cost-sync-C0VdGme1.js → cost-sync-Dv3LUaNW.js} +3 -3
- package/server/{dag-CdOjJVDT.js → dag-ombYYt4s.js} +2 -2
- package/server/{dangerous-git-ops-CT-IypG3.js → dangerous-git-ops-BcJPC5Rr.js} +1 -1
- package/server/dashboard-db-worker.js +7 -7
- package/server/deacon-DrXqT9LQ.js +4 -0
- package/server/{deacon-pRnyD5w9.js → deacon-hi9KLPLT.js} +2998 -2659
- package/server/{deacon-nudge-log-Bjg8U4Y9.js → deacon-nudge-log-BNBebzeL.js} +3 -3
- package/server/deacon-pending-lifecycle-Cpk0k7Hi.js +33 -0
- package/server/{deacon-swarm-DftYRDo8.js → deacon-swarm-DBxUaO-A.js} +13 -13
- package/server/deacon-swarm-I2JfX5Tg.js +2 -0
- package/server/deacon.js +14 -19
- package/server/dead-end-trip-CQcP70ND.js +13 -0
- package/server/delivery-Da0zEMY4.js +3 -0
- package/server/{deploy-patrol-Bjsho_ha.js → deploy-patrol-Df-Tmaq0.js} +69 -18
- package/server/deploy-queue-9SrhZR63.js +3 -0
- package/server/deploy-queue-UjKGYsDe.js +169 -0
- package/server/deploy-window-jOiPRHL0.js +3 -0
- package/server/{deploy-window-DeP_OIRk.js → deploy-window-rpqpWqHA.js} +40 -16
- package/server/{dev-supervisor-BpMoHlkR.js → dev-supervisor-CC46Ig8q.js} +2 -2
- package/server/{devcontainer-renderer-CtUgrVKW.js → devcontainer-renderer-68CSCRnu.js} +3 -3
- package/server/{dist-DyRx4cI7.js → dist-CJy72uEg.js} +1 -1
- package/server/dist-DSlxIU1H.js +3 -0
- package/server/{dist-src-BSoS7mkd.js → dist-src-Bik83hFi.js} +1 -1
- package/server/docker-BMVTSFL5.js +3 -0
- package/server/{docker-BK7HIcBj.js → docker-Dam7mbIB.js} +2 -2
- package/server/{dod-BrSfUHm0.js → dod-Dar-yheg.js} +8 -4
- package/server/durable-review-pipeline-CGZ5QBxh.js +17 -0
- package/server/durable-review-pipeline-jpgCJZFp.js +3 -0
- package/server/{ensure-devcontainer-DseivYEy.js → ensure-devcontainer-B057YA81.js} +4 -4
- package/server/{ensure-devcontainer-DRqaAkBR.js → ensure-devcontainer-C98V8yW-.js} +1 -1
- package/server/{esm-shims-CbI3BVkl.js → esm-shims-Br9BPdkR.js} +1 -1
- package/server/{event-store-Cr5PQwFC.js → event-store-ELqkVOn0.js} +1 -1
- package/server/{event-store-tdiY5eDZ.js → event-store-zY5MJHgF.js} +23 -4
- package/server/{events-CvvGK6LT.js → events-D6pyqhzM.js} +1 -1
- package/server/{events-CC1dgUpx.js → events-D9PR1IC0.js} +3 -3
- package/server/{factory-CynerjXO.js → factory-CRoOpGsF.js} +5 -5
- package/server/{feature-registry-population-B0pmAz0a.js → feature-registry-population--YUXU999.js} +4 -4
- package/server/{feature-registry-storage-_wUBRD3J.js → feature-registry-storage-BORBTo0y.js} +2 -2
- package/server/{features-Dhxz-wLc.js → features--nFkJY8v.js} +2 -2
- package/server/{feedback-target-iygZwmMc.js → feedback-target-C0ZzyHvU.js} +1 -1
- package/server/{feedback-target-CPS15Vte.js → feedback-target-Dt9BsapX.js} +8 -8
- package/server/{feedback-writer-C9b6ccN1.js → feedback-writer-BG0RUZiu.js} +5 -5
- package/server/{feedback-writer-qmiMbqTZ.js → feedback-writer-D1fm-5Z8.js} +1 -1
- package/server/flywheel-BekukH4i.js +2 -0
- package/server/flywheel-BiT3odRb.js +1092 -0
- package/server/flywheel-CLVB55Be.js +478 -0
- package/server/flywheel-merge-order-BJv1DZz1.js +462 -0
- package/server/flywheel-merge-order-BKw9o6ku.js +2 -0
- package/server/forge-CZbSg6Ds.js +383 -0
- package/server/forge-DK3blQb_.js +3 -0
- package/server/{from-CJ_aH5jD.js → from-D_4_dk7i.js} +1 -1
- package/server/{fts-db-CKgg0VKg.js → fts-db-BeOomFa-.js} +2 -2
- package/server/{fts-operations-BrL_VTRr.js → fts-operations-BdewJ_g9.js} +2 -2
- package/server/git-utils-CI6cwyyC.js +3 -0
- package/server/{git-utils-BVa07a4a.js → git-utils-ameCgDdy.js} +93 -3
- package/server/{github-app-CMBdIFBQ.js → github-app-CQ1Lo6LA.js} +19 -8
- package/server/{github-app-CPfoFAdV.js → github-app-DtAsvwzK.js} +1 -1
- package/server/{github-client--XVlDpwM.js → github-client-BfyDxuRN.js} +2 -2
- package/server/{github-client-B4QsdE-X.js → github-client-Cj0yl_07.js} +1 -1
- package/server/github-pr-lookup-BDq6Wznz.js +58 -0
- package/server/{harness-binary-CVio2m-u.js → harness-binary-DqkftwTb.js} +32 -6
- package/server/{harness-resolve-qHJBhy3Z.js → harness-resolve-CqMqM1Mb.js} +18 -9
- package/server/{health-events-C9B8y1Hm.js → health-events-CazRxJUS.js} +2 -2
- package/server/{health-events-CpfvuVmm.js → health-events-_xfTaOUs.js} +1 -1
- package/server/{hooks-DUXw8A0-.js → hooks-BO3QT1sn.js} +2 -2
- package/server/{hooks-ltecuFqZ.js → hooks-BnJsxlSq.js} +1 -1
- package/server/{hume-BEq0bUDg.js → hume-CDYJxqx-.js} +2 -2
- package/server/hume-Dq--7rOQ.js +3 -0
- package/server/{idle-stack-reaper-D7w9t3fE.js → idle-stack-reaper-BYqERdkE.js} +1 -1
- package/server/{idle-stack-reaper-w1ISSlhs.js → idle-stack-reaper-CnGx_PO_.js} +99 -6
- package/server/in-flight-guard-Bq3epG58.js +45 -0
- package/server/{infra-CzaSOgul.js → infra-BjuxvjdS.js} +80 -5
- package/server/{injection-oNZeoDhL.js → injection-DZxlV2al.js} +1 -1
- package/server/{injection-DZ0PfYa4.js → injection-UCf8EsjX.js} +191 -183
- package/server/{inspect-agent-DJKLWcVT.js → inspect-agent-6kylZnrm.js} +139 -82
- package/server/inspect-agent-BVwDrVrz.js +2 -0
- package/server/{internal-token-Bb4XeV2x.js → internal-token-DvMTQSOS.js} +2 -2
- package/server/{internal-token-DXdfprGr.js → internal-token-M9omeXM1.js} +1 -1
- package/server/{io-B3_AHDfr.js → io-VHvc0Ssp.js} +43 -26
- package/server/{issue-closed-zhdvQicO.js → issue-closed-B6KJkmHZ.js} +7 -7
- package/server/{issue-lifecycle-Dy4vd0Oa.js → issue-lifecycle-9gk3fnXN.js} +1 -1
- package/server/{issue-lifecycle-CreaCkjW.js → issue-lifecycle-C4Zj0WmE.js} +34 -28
- package/server/{issue-service-singleton-B5ushCvE.js → issue-service-singleton-DmCh7EHj.js} +10 -10
- package/server/{issue-service-singleton-DabvBWUp.js → issue-service-singleton-PMx8F1jr.js} +1 -1
- package/server/{issues-B6ToV6x1.js → issues-CgnQ1OGQ.js} +2 -2
- package/server/{jsonl-parser-Dhflo1SH.js → jsonl-parser-DJOH_O-w.js} +5 -4
- package/server/{label-cleanup-BKPJm4Hv.js → label-cleanup-Dv7AV_R1.js} +3 -3
- package/server/{label-reconciler-DdWbHEBu.js → label-reconciler--F85rufp.js} +4 -4
- package/server/{lifecycle-DfeyBDwA.js → lifecycle-CTdOWYMi.js} +2 -2
- package/server/{lifecycle-sycO16hj.js → lifecycle-DDN8ApVY.js} +6 -7
- package/server/{lifecycle-io-D8iNBcux.js → lifecycle-io-D-lGdzK3.js} +5 -5
- package/server/{lifecycle-io-Bsnr2K4F.js → lifecycle-io-D_zTLvP1.js} +1 -1
- package/server/{linear-client-D4zqMnX7.js → linear-client-BQU36QqE.js} +2 -2
- package/server/{linear-client-DqhDQO9k.js → linear-client-CY4O_Tu3.js} +1 -1
- package/server/linear-mcp-auth-BnU4TYfY.js +280 -0
- package/server/linear-mcp-auth-NYBq_oHZ.js +2 -0
- package/server/memory-fts-worker.js +1 -1
- package/server/{merge-C_ZEhbqI.js → merge-Bpx4jjht.js} +13 -11
- package/server/{merge-agent-XrB80vfT.js → merge-agent-DdMVSV2Y.js} +233 -83
- package/server/{merge-agent-BQM3a8z5.js → merge-agent-DrXa1CpW.js} +1 -1
- package/server/{merge-batch-Bwoyr8Je.js → merge-batch-C5q1c1GX.js} +1 -1
- package/server/merge-completeness-BMm8tMyZ.js +277 -0
- package/server/merge-completeness-D0ljXbk3.js +3 -0
- package/server/{merge-DK23JEdS.js → merge-idOG--29.js} +1 -1
- package/server/merge-ops-DdpTiEMt.js +2 -0
- package/server/merge-rebase-DGQpYJU3.js +2 -0
- package/server/{merge-rebase-C1KIVr0s.js → merge-rebase-xLnMlw5h.js} +3 -3
- package/server/merge-set-DH7ot3yz.js +89 -0
- package/server/merge-set-DzsOTBg5.js +3 -0
- package/server/{merge-sync-DZ9dojJn.js → merge-sync-CQ-LgZ1_.js} +406 -153
- package/server/merge-sync-Ca7F5JnQ.js +3 -0
- package/server/{merge-train-DxDfBTjr.js → merge-train-DTEMyIu4.js} +18 -8
- package/server/{merge-train-deps-CyYQUksx.js → merge-train-deps-CNPJzepw.js} +4 -4
- package/server/merge-verification-x3JO8Dtz.js +220 -0
- package/server/merged-docker-cleanup-worker-CTpC27Bs.js +107 -0
- package/server/merged-docker-cleanup-worker-Cdi9apm-.js +3 -0
- package/server/{multipart-parser-Dt8YPN5S.js → multipart-parser-IMSBfePV.js} +2 -2
- package/server/{no-resume-mode-Cx3K0hHi.js → no-resume-mode-C5ULAXwO.js} +1 -1
- package/server/node_modules/.package-lock.json +27 -27
- package/server/node_modules/@effect/platform-node/node_modules/@effect/platform-node-shared/package.json +3 -3
- package/server/node_modules/@google-cloud/common/package.json +2 -2
- package/server/node_modules/gaxios/build/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/server/node_modules/gaxios/build/esm/tsconfig.tsbuildinfo +1 -1
- package/server/node_modules/gaxios/package.json +1 -1
- package/server/node_modules/google-auth-library/build/src/auth/googleauth.js +24 -19
- package/server/node_modules/google-auth-library/package.json +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/auth.proto +2 -1
- package/server/node_modules/google-gax/build/protos/google/api/backend.proto +12 -1
- package/server/node_modules/google-gax/build/protos/google/api/billing.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/client.proto +165 -53
- package/server/node_modules/google-gax/build/protos/google/api/cloudquotas/v1/resources.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/cloudquotas/v1beta/quota_adjuster_settings.proto +7 -3
- package/server/node_modules/google-gax/build/protos/google/api/cloudquotas/v1beta/resources.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/config_change.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/context.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/control.proto +3 -3
- package/server/node_modules/google-gax/build/protos/google/api/distribution.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/documentation.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/endpoint.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/error_reason.proto +186 -1
- package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/checked.proto +31 -3
- package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/eval.proto +8 -9
- package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/explain.proto +1 -2
- package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/syntax.proto +20 -22
- package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/value.proto +2 -3
- package/server/node_modules/google-gax/build/protos/google/api/field_behavior.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/field_info.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/http.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/httpbody.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/label.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/launch_stage.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/log.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/logging.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/metric.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/monitored_resource.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/monitoring.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/policy.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/quota.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/resource.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/routing.proto +8 -4
- package/server/node_modules/google-gax/build/protos/google/api/service.proto +2 -3
- package/server/node_modules/google-gax/build/protos/google/api/source_info.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/system_parameter.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/usage.proto +7 -27
- package/server/node_modules/google-gax/build/protos/google/api/visibility.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/longrunning/operations.proto +20 -1
- package/server/node_modules/google-gax/build/protos/google/protobuf/any.proto +52 -108
- package/server/node_modules/google-gax/build/protos/google/protobuf/compiler/ruby/ruby_generated_code.proto +2 -0
- package/server/node_modules/google-gax/build/protos/google/protobuf/cpp_features.proto +21 -1
- package/server/node_modules/google-gax/build/protos/google/protobuf/cpp_file_options.proto +24 -0
- package/server/node_modules/google-gax/build/protos/google/protobuf/cpp_options.proto +15 -0
- package/server/node_modules/google-gax/build/protos/google/protobuf/descriptor.proto +118 -30
- package/server/node_modules/google-gax/build/protos/google/protobuf/field_mask.proto +16 -18
- package/server/node_modules/google-gax/build/protos/google/protobuf/internal_options.proto +8 -1
- package/server/node_modules/google-gax/build/protos/google/protobuf/json/json_enumval_custom_string.proto +26 -0
- package/server/node_modules/google-gax/build/protos/google/protobuf/json_enumvalue_options.proto +29 -0
- package/server/node_modules/google-gax/build/protos/google/protobuf/json_options.proto +14 -0
- package/server/node_modules/google-gax/build/protos/google/protobuf/struct.proto +38 -22
- package/server/node_modules/google-gax/build/protos/google/protobuf/test_protos/forced_micro_string.proto +3 -0
- package/server/node_modules/google-gax/build/protos/google/protobuf/test_protos/repeated_field_proxy_import_message.proto +14 -0
- package/server/node_modules/google-gax/build/protos/google/protobuf/timestamp.proto +9 -8
- package/server/node_modules/google-gax/build/protos/google/protobuf/util/json_format_proto3.proto +18 -0
- package/server/node_modules/google-gax/build/protos/google/rpc/code.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/rpc/context/attribute_context.proto +7 -2
- package/server/node_modules/google-gax/build/protos/google/rpc/context/audit_context.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/rpc/error_details.proto +5 -5
- package/server/node_modules/google-gax/build/protos/google/rpc/http.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/rpc/status.proto +1 -2
- package/server/node_modules/google-gax/build/protos/google/type/calendar_period.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/type/color.proto +9 -10
- package/server/node_modules/google-gax/build/protos/google/type/date.proto +11 -9
- package/server/node_modules/google-gax/build/protos/google/type/datetime.proto +17 -17
- package/server/node_modules/google-gax/build/protos/google/type/dayofweek.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/type/decimal.proto +11 -13
- package/server/node_modules/google-gax/build/protos/google/type/expr.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/type/fraction.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/type/interval.proto +1 -2
- package/server/node_modules/google-gax/build/protos/google/type/latlng.proto +4 -5
- package/server/node_modules/google-gax/build/protos/google/type/localized_text.proto +3 -3
- package/server/node_modules/google-gax/build/protos/google/type/money.proto +1 -2
- package/server/node_modules/google-gax/build/protos/google/type/month.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/type/phone_number.proto +20 -15
- package/server/node_modules/google-gax/build/protos/google/type/postal_address.proto +39 -39
- package/server/node_modules/google-gax/build/protos/google/type/quaternion.proto +4 -4
- package/server/node_modules/google-gax/build/protos/google/type/timeofday.proto +11 -8
- package/server/node_modules/google-gax/package.json +5 -5
- package/server/node_modules/retry-request/package.json +4 -4
- package/server/node_modules/teeny-request/package.json +5 -5
- package/server/node_modules/undici/README.md +5 -0
- package/server/node_modules/undici/docs/docs/api/Client.md +4 -0
- package/server/node_modules/undici/docs/docs/api/Dispatcher.md +6 -0
- package/server/node_modules/undici/docs/docs/api/Interceptors.md +59 -0
- package/server/node_modules/undici/docs/docs/api/MockAgent.md +27 -0
- package/server/node_modules/undici/docs/docs/api/MockPool.md +28 -1
- package/server/node_modules/undici/docs/docs/index.md +5 -0
- package/server/node_modules/undici/lib/cache/memory-cache-store.js +54 -9
- package/server/node_modules/undici/lib/cache/sqlite-cache-store.js +7 -1
- package/server/node_modules/undici/lib/core/request.js +13 -2
- package/server/node_modules/undici/lib/core/util.js +6 -1
- package/server/node_modules/undici/lib/dispatcher/agent.js +3 -3
- package/server/node_modules/undici/lib/dispatcher/client-h1.js +42 -8
- package/server/node_modules/undici/lib/dispatcher/client-h2.js +34 -43
- package/server/node_modules/undici/lib/global.js +36 -16
- package/server/node_modules/undici/lib/handler/cache-handler.js +269 -61
- package/server/node_modules/undici/lib/handler/cache-revalidation-handler.js +13 -3
- package/server/node_modules/undici/lib/handler/redirect-handler.js +5 -2
- package/server/node_modules/undici/lib/handler/retry-handler.js +30 -4
- package/server/node_modules/undici/lib/interceptor/cache.js +159 -52
- package/server/node_modules/undici/lib/interceptor/decompress.js +9 -2
- package/server/node_modules/undici/lib/mock/mock-interceptor.js +25 -7
- package/server/node_modules/undici/lib/mock/mock-utils.js +39 -10
- package/server/node_modules/undici/lib/util/cache.js +372 -68
- package/server/node_modules/undici/lib/util/date.js +23 -6
- package/server/node_modules/undici/lib/web/cookies/util.js +78 -8
- package/server/node_modules/undici/lib/web/fetch/body.js +2 -1
- package/server/node_modules/undici/lib/web/fetch/response.js +1 -3
- package/server/node_modules/undici/package.json +2 -2
- package/server/node_modules/undici/types/client.d.ts +2 -2
- package/server/node_modules/undici/types/dispatcher.d.ts +3 -3
- package/server/node_modules/undici/types/mock-interceptor.d.ts +5 -1
- package/server/{orphan-proposed-reconciler-BTkDrl4R.js → orphan-proposed-reconciler-CWmOdM9-.js} +1 -1
- package/server/{orphan-proposed-reconciler-neK8dHQk.js → orphan-proposed-reconciler-Dg6U4ADZ.js} +24 -15
- package/server/package-lock.json +28 -28
- package/server/package.json +1 -1
- package/server/{pan-dir-DeVUgwA7.js → pan-dir-ubraCiUg.js} +24 -10
- package/server/{paths-_zEts6wc.js → paths-cXmOwSbh.js} +2 -2
- package/server/pending-lifecycle-claim-DEteYdS9.js +110 -0
- package/server/{pickup-Dd0Znxxy.js → pickup-D8xtG1J2.js} +17 -16
- package/server/pipeline-membership-BGSvkT2M.js +3 -0
- package/server/pipeline-membership-CrAPLYtf.js +57 -0
- package/server/{pipeline-membership-0HaEGDlH.js → pipeline-membership-gather-CET40frr.js} +177 -109
- package/server/pipeline-membership-gather-DTfcgxLl.js +3 -0
- package/server/{pipeline-notifier-D0ykk59O.js → pipeline-notifier-BuQ0e4hF.js} +2 -2
- package/server/{pipeline-notifier-6iCJplFZ.js → pipeline-notifier-ImNXknf0.js} +1 -1
- package/server/post-merge-lifecycle-worker-DbO10pum.js +50 -0
- package/server/{project-pipelines-2sK1apsO.js → project-pipelines-Bim6bThI.js} +2 -2
- package/server/project-repos-C0nuoBGv.js +98 -0
- package/server/project-repos-CRE6Ykow.js +3 -0
- package/server/projects-B03Wr-xc.js +3 -0
- package/server/{prompts-Dp4Tpk39.js → prompts-BEx08AIu.js} +2 -2
- package/server/{providers-DKkBXhxU.js → providers-D7IluJe0.js} +1 -1
- package/server/{providers-DV7ec8l7.js → providers-evXhiQeg.js} +7 -7
- package/server/public/assets/{chunk-BO2N2NFS-CltymLRg.js → chunk-BO2N2NFS-D1s4-Sqp.js} +4 -4
- package/server/public/assets/{diagram-OG6HWLK6-D2f6pjgA.js → diagram-OG6HWLK6-1gdMoSF7.js} +1 -1
- package/server/public/assets/{dist-BWR4qFiV.js → dist-CxAOHbil.js} +1 -1
- package/server/public/assets/{dist-DEmlBBNQ.js → dist-DqDRYW6P.js} +3 -3
- package/server/public/assets/{ganttDiagram-6RSMTGT7-DnfiViPK.js → ganttDiagram-6RSMTGT7-BYex-Ec7.js} +1 -1
- package/server/public/assets/{highlighted-body-OFNGDK62-B8Wrmj9H.js → highlighted-body-OFNGDK62-DlVkiG0S.js} +1 -1
- package/server/public/assets/index-Cc7cnFC4.css +1 -0
- package/server/public/assets/index-K0hAYCgV.js +807 -0
- package/server/public/assets/{linear-C9zFQpkX.js → linear-B4cuEFmO.js} +1 -1
- package/server/public/assets/mermaid-GHXKKRXX-DyDHFXHV.js +1 -0
- package/server/public/assets/{pieDiagram-4H26LBE5-lR8H7Yjd.js → pieDiagram-4H26LBE5-CBHNBIJQ.js} +1 -1
- package/server/public/assets/{prod-B1jCQihw.js → prod-CN2nHBvX.js} +4 -4
- package/server/public/assets/{quadrantDiagram-W4KKPZXB-CVQ2guem.js → quadrantDiagram-W4KKPZXB-Bom87VOQ.js} +1 -1
- package/server/public/assets/{sankeyDiagram-5OEKKPKP-eD1RB4gm.js → sankeyDiagram-5OEKKPKP-UrBeyLxk.js} +1 -1
- package/server/public/assets/{xychartDiagram-2RQKCTM6-ChEC6dQn.js → xychartDiagram-2RQKCTM6-DhkbrIxR.js} +1 -1
- package/server/public/index.html +2 -2
- package/server/{pull-requests--Ib2jTgy.js → pull-requests-Cv7LxzUN.js} +1 -1
- package/server/{pull-requests-CXIhdCYD.js → pull-requests-DHWiKxo7.js} +19 -10
- package/server/{queries-6d7Cdtm7.js → queries-DHJm7bTd.js} +1134 -203
- package/server/{rally-B708-nZn.js → rally-B71M9f4o.js} +3 -3
- package/server/rally-YDjU513q.js +3 -0
- package/server/{rally-api-yjQ0TfHd.js → rally-api-D5uXN08-.js} +1 -1
- package/server/{rally-api-nY0ZANw3.js → rally-api-Dkqmok65.js} +2 -2
- package/server/{rally-client-B1EJhfV0.js → rally-client-Bfqm6XI4.js} +4 -4
- package/server/{rally-client-CULK2XAY.js → rally-client-CxsA6ExK.js} +1 -1
- package/server/{reap-terminal-sessions-MWnqdLCJ.js → reap-terminal-sessions-BQ23nriz.js} +2 -2
- package/server/{reap-terminal-sessions-N3BTkmX_.js → reap-terminal-sessions-CvmwaqKv.js} +2 -2
- package/server/{rebuild-stack-Dp4eLkt4.js → rebuild-stack-0EEYReVZ.js} +36 -36
- package/server/rebuild-stack-qeRqHZxH.js +4 -0
- package/server/{reconstruct-cache-uWbZfjms.js → reconstruct-cache-BbYFsqLa.js} +7 -6
- package/server/record-CtroO946.js +3 -0
- package/server/record-update-DH6dLJID.js +3 -0
- package/server/{record-update-Dq0t2uBG.js → record-update-Z0QCxmMp.js} +124 -34
- package/server/{recovery-trip-BJtIQuag.js → recovery-trip-eJe26h-f.js} +2 -2
- package/server/{release-engine-DZpmGGzb.js → release-engine-lPUp-MTd.js} +5 -5
- package/server/{release-set-hnXd_kga.js → release-set-BwGeyZKX.js} +3 -3
- package/server/{remote-Br-ZWVIP.js → remote-BCd02Es1.js} +3 -3
- package/server/{remote-4tiENDaI.js → remote-CVGUhXMV.js} +3 -3
- package/server/{remote-agents-CRCg1L4H.js → remote-agents-C1bssi41.js} +2 -2
- package/server/{remote-agents-nmty1mLu.js → remote-agents-C_VxssLU.js} +300 -46
- package/server/{remote-completion-BbWNd10K.js → remote-completion-DAB0P706.js} +12 -12
- package/server/resume-u9byntrq.js +3 -0
- package/server/{review-agent-DJRO8EG8.js → review-agent-CD4RCI0Y.js} +1 -1
- package/server/{review-agent-sKkE1FEL.js → review-agent-CHhI66UP.js} +61 -44
- package/server/{review-artifacts-BLbSC4ke.js → review-artifacts-DSMRIfuJ.js} +7 -6
- package/server/{review-context-BgX3lcCx.js → review-context-oFuBbiEE.js} +69 -25
- package/server/{in-flight-guard-aQ1IxeI3.js → review-convoy-liveness-CpzrcJ5F.js} +3 -44
- package/server/review-status-BZH8h-6W.js +3 -0
- package/server/review-status-emit-CUExtHd5.js +50 -0
- package/server/{review-status-enrichment-tbOn_A_u.js → review-status-enrichment-QV_hQkgS.js} +4 -4
- package/server/{review-status-source-CxuEjLGg.js → review-status-source-CpvJnvnQ.js} +24 -3
- package/server/review-status-source-ylw5QZUR.js +3 -0
- package/server/{review-status-sync-BhSQ0d0l.js → review-status-sync-483sujRo.js} +10 -5
- package/server/{review-status-sync-To3-iS-N.js → review-status-sync-NaWvI9Xf.js} +1 -1
- package/server/review-status-yljtTd-q.js +7521 -0
- package/server/{review-verdict-feedback-C1Kg8n7t.js → review-verdict-feedback-BPDn4li_.js} +1 -1
- package/server/{review-verdict-feedback-CTRNtzXO.js → review-verdict-feedback-CELeUinh.js} +48 -8
- package/server/{rollup-WA0VD-Rr.js → rollup-DTNvelz9.js} +8 -8
- package/server/{rollup-B53Y81xK.js → rollup-DYZfq1c4.js} +1 -1
- package/server/{scanner-D2743cp8.js → scanner-CV35oSn4.js} +42 -14
- package/server/{sequence-io-Cg0zYj_E.js → sequence-io-DF_8iuhV.js} +4 -4
- package/server/{sequencer-agent-BGY8gjg-.js → sequencer-agent-B2WeCtGi.js} +5 -5
- package/server/server.js +3899 -672
- package/server/{service-DCpB_SGd.js → service-Br9rpBNA.js} +83 -65
- package/server/{session-fork-BQ8_6H32.js → session-fork-D0LY152N.js} +2 -2
- package/server/{session-history-CVZDpS3c.js → session-history-BUyK29eq.js} +4 -4
- package/server/{settings-BZ5m1UCl.js → settings-Bse1Pdcj.js} +3 -3
- package/server/settings-CdvIF84R.js +2 -0
- package/server/{settings-ylxwqwQG.js → settings-CvVGqNEs.js} +3 -2
- package/server/{shadow-engineering-BlxG6Xye.js → shadow-engineering-BWLa8ODz.js} +2 -2
- package/server/{shadow-state-Dh-HzF2J.js → shadow-state-B4fpKxOO.js} +2 -2
- package/server/{shadow-state-C2_epgx2.js → shadow-state-FV6PHfEW.js} +1 -1
- package/server/{shadow-utils-4bvjn9b3.js → shadow-utils-D7asqxOZ.js} +2 -2
- package/server/{ship-log-ClSMIzZX.js → ship-log-xplJ8t75.js} +1 -1
- package/server/{smart-compaction-CHIxLr_d.js → smart-compaction-CPp2Ykf6.js} +1 -1
- package/server/{smart-compaction-r4pHQBIr.js → smart-compaction-D3U_79f-.js} +8 -7
- package/server/{spawn-planning-session-KC_UduxD.js → spawn-planning-session-1nFi3gfk.js} +1 -1
- package/server/{spawn-planning-session-DWAorh2B.js → spawn-planning-session-DGfZ5B8D.js} +30 -23
- package/server/{specialist-completion-BXhav38U.js → specialist-completion-Br48eiDP.js} +1 -1
- package/server/{specialist-context-DtrYvGJ-.js → specialist-context-cVbGnTa3.js} +5 -5
- package/server/{specialist-handoff-logger-C45ouoZ6.js → specialist-handoff-logger-DK-GTz4s.js} +2 -2
- package/server/{specialist-logs-DJ3RzKxW.js → specialist-logs-Cl5-hqT8.js} +3 -3
- package/server/{specialists-JVO00bs7.js → specialists-CNfYO3G2.js} +1634 -457
- package/server/{specialists-DyFeedjn.js → specialists-Cicieuzu.js} +21 -19
- package/server/{specialists-D5fRZXmN.js → specialists-JHsdU3pA.js} +1 -1
- package/server/{specialists-DVj0hvaL.js → specialists-Tdezg_6N.js} +1 -1
- package/server/specs-Bt7WJDoC.js +3 -0
- package/server/src-CRmCHlma.js +11408 -0
- package/server/{src-CG9mCkP6.js → src-Ck028hjO.js} +6 -6
- package/server/{src-CMTQGyCK.js → src-UWcGe0Ix.js} +5 -5
- package/server/stack-health-CJLMrLBl.js +3 -0
- package/server/{stack-health-puygdaMd.js → stack-health-DqSqx0zv.js} +68 -12
- package/server/{stale-task-claims-BbFwCTb0.js → stale-task-claims-CYFOUNlT.js} +4 -4
- package/server/{staleness-Cbl_w4Bw.js → staleness-BQ-0gMYK.js} +1 -1
- package/server/{staleness-Bpy41ngS.js → staleness-D47CUJok.js} +1 -1
- package/server/{state-home-CIGjwNfJ.js → state-home-BYV7qZTW.js} +2 -2
- package/server/{state-mapping-BL_-3Yt8.js → state-mapping-BVldLhBf.js} +1 -1
- package/server/{state-migrate-Dkzaxvny.js → state-migrate-CvvBtS0Y.js} +4 -4
- package/server/{stuck-remediation-sY_MHHgQ.js → stuck-remediation-Wt0UVvu1.js} +1 -1
- package/server/{stuck-remediation-CzEov_fq.js → stuck-remediation-mkLC6G_t.js} +360 -26
- package/server/{substrate-bug-weights-service-BH66lhm7.js → substrate-bug-weights-service-XfLAKnmK.js} +3 -3
- package/server/{summary-fork-D8bXjCr7.js → summary-fork-CD7GOts0.js} +2 -2
- package/server/{summary-fork-DHhJIsAL.js → summary-fork-xiXBnWG9.js} +78 -12
- package/server/supervisor/{injection-budget-C1iPirFS.js → injection-budget-B5SbvDx2.js} +2 -41
- package/server/supervisor/{paths-DehGY2Fj.js → paths-K3noE4N_.js} +1 -1
- package/server/supervisor/pty-supervisor.js +49 -2
- package/server/supervisor/pty-token-B7lp0ElT.js +43 -0
- package/server/{supervisor-BO-Zk_h-.js → supervisor-DL788vos.js} +3 -3
- package/server/supervisor-UWiQNK-y.js +2 -0
- package/server/{swarm-readiness-CQ_fwUWF.js → swarm-readiness-BV14DF5N.js} +3 -3
- package/server/{system-prerequisites-Bza0ffAh.js → system-prerequisites-pdWG6ZQs.js} +56 -29
- package/server/{teardown-workspace-CEgauF4U.js → teardown-workspace-l2F4_zim.js} +15 -16
- package/server/{test-agent-queue-DXDt0Xa-.js → test-agent-queue-qakFIIGz.js} +6 -6
- package/server/{test-verdict-CmUBK5cT.js → test-verdict-BJYDggMm.js} +1 -1
- package/server/{test-verdict-DFjjIHR5.js → test-verdict-Gw-btEfb.js} +1 -1
- package/server/{tldr-daemon-DL566HbH.js → tldr-daemon-BKOYB616.js} +1 -1
- package/server/{tldr-daemon-BDtLOIlO.js → tldr-daemon-CBaWkV9k.js} +2 -2
- package/server/{tmux-ftABRAmc.js → tmux-B1Wbxz5o.js} +3 -3
- package/server/{tmux-DoThH6aj.js → tmux-DQ6jB76i.js} +1 -1
- package/server/tmux-dedup-DbN6MGzM.js +892 -0
- package/server/{token-CsOhvw42.js → token-Cxbk023d.js} +2 -2
- package/server/{token-util-CTmczIfW.js → token-util-CLq2MmQt.js} +1 -1
- package/server/token-util-zlz9U0ki.js +3 -0
- package/server/{tracker-config-B4itFzDo.js → tracker-config-DZ8Zn2nQ.js} +2 -2
- package/server/{tracker-utils-tnxxqROg.js → tracker-utils-L0FKtjq2.js} +3 -3
- package/server/{traefik-connect-m73vGebA.js → traefik-connect-bNX6jKkb.js} +1 -1
- package/server/{tunnel-B2vxjx_b.js → tunnel-BAm1XgjG.js} +3 -3
- package/server/{tunnel-Cgj-IGIl.js → tunnel-CjSnHdTc.js} +1 -1
- package/server/{types-BBNjPcqU.js → types-q67TYYg5.js} +1 -1
- package/server/uat-promote-verification-CKB9ZrKC.js +92 -0
- package/server/uat-train-Cu-4nvbf.js +2 -0
- package/server/uat-train-D5UjcyAa.js +0 -0
- package/server/validate-Cxsx5Uz1.js +179 -0
- package/server/{validation-CIi6pnI2.js → validation-DEaTZafj.js} +1 -1
- package/server/{validation-B5JxoPUS.js → validation-VZgJxmqc.js} +4 -4
- package/server/{verification-artifact-C-lIHfUa.js → verification-artifact-C85cRTc_.js} +1 -1
- package/server/{verification-artifact-BcnIbigL.js → verification-artifact-g3yuxQDL.js} +1 -1
- package/server/{verification-runner-CDj4lD4t.js → verification-runner-DlZI8yhw.js} +1 -1
- package/server/{verification-runner-Br0S_Ous.js → verification-runner-KDZIsBj4.js} +114 -107
- package/server/{vtt-parser-DKpNNW5i.js → vtt-parser-Jr94BF3Y.js} +1 -1
- package/server/{work-agent-prompt-Yb2bd3Fq.js → work-agent-prompt-C-vuP3_J.js} +20 -9
- package/server/workflows-CHrx_hcW.js +2 -0
- package/server/{workflows-C9WmILql.js → workflows-D0oip4eV.js} +161 -78
- package/server/{workspace-DuUdndHe.js → workspace-BqFg__wH.js} +1 -1
- package/server/{workspace-BVl4rihN.js → workspace-C0fH3aek.js} +3 -3
- package/server/workspace-anchor-drift-B4gMRHl4.js +3 -0
- package/server/workspace-anchor-drift-CZKofakh.js +224 -0
- package/server/{workspace-config-cEGcO6UT.js → workspace-config-B123aZl-.js} +1 -1
- package/server/workspace-manager-CDTKjjdU.js +3 -0
- package/server/{workspace-manager-hLUv3ADC.js → workspace-manager-CwO-Hek7.js} +206 -116
- package/server/{workspace-metadata-CskwzzhL.js → workspace-metadata-CQv00QwK.js} +2 -2
- package/server/{workspace-metadata-DNpMixRq.js → workspace-metadata-D4M8BM4W.js} +1 -1
- package/server/{workspaces-hWvy9eKs.js → workspaces-j1e_6aAT.js} +736 -290
- package/sync-sources/dev-skills/pan-dashboard-restart/SKILL.md +34 -10
- package/sync-sources/hooks/linear-mcp-auth-hook +97 -0
- package/sync-sources/hooks/record-cost-event.js +6 -4
- package/sync-sources/rules/ask-user-question-self-contained.md +9 -0
- package/sync-sources/skills/okf/README.md +11 -3
- package/sync-sources/skills/okf/SKILL.md +12 -1
- package/sync-sources/skills/okf/docs/USAGE.md +15 -0
- package/sync-sources/skills/okf/references/overdeck.md +28 -0
- package/sync-sources/skills/pan-close/SKILL.md +5 -3
- package/sync-sources/skills/pan-flywheel/SKILL.md +18 -1
- package/sync-sources/skills/pan-knowledge/SKILL.md +10 -0
- package/sync-sources/skills/pan-orders/SKILL.md +33 -0
- package/sync-sources/skills/pan-peer-review/SKILL.md +195 -0
- package/sync-sources/skills/pan-reload/SKILL.md +3 -3
- package/sync-sources/skills/pan-restart/SKILL.md +5 -4
- package/sync-sources/skills/pan-review/SKILL.md +7 -1
- package/sync-sources/skills/pan-start/SKILL.md +4 -0
- package/sync-sources/skills/pan-sync-main/SKILL.md +9 -8
- package/sync-sources/skills/pipeline-status/SKILL.md +10 -1
- package/sync-sources/skills/workspace-add-repo/SKILL.md +16 -1
- package/sync-sources/templates/traefik/dynamic/overdeck.yml.template +10 -0
- package/server/activity-logger-OEUBdfZ8.js +0 -3
- package/server/agent-state-Bg0lhqwj.js +0 -3
- package/server/agents-FlIgQ3ql.js +0 -3
- package/server/archive-planning-zkJb3BeM.js +0 -3
- package/server/cli-BsVIHwzC.js +0 -3
- package/server/context-layers-B3Nt58jl.js +0 -4
- package/server/context-layers-ZYFcWiyc.js +0 -165
- package/server/conversation-service-Up7FEzIC.js +0 -3
- package/server/deacon-DYSEJb48.js +0 -4
- package/server/deacon-swarm-9jRLS5Ke.js +0 -2
- package/server/deploy-window-CM6W1-c6.js +0 -3
- package/server/dist-Bd3vn0Hw.js +0 -3
- package/server/flywheel-DMCUzDWg.js +0 -2
- package/server/flywheel-DSSpWmqF.js +0 -522
- package/server/flywheel-DcBkQvgE.js +0 -207
- package/server/flywheel-merge-order-C-6zRLvM.js +0 -2
- package/server/flywheel-merge-order-DxbQKE1d.js +0 -244
- package/server/forge-B-ZWyTRm.js +0 -2
- package/server/forge-Ch9gCFod.js +0 -270
- package/server/git-utils-Dry_Ka_D.js +0 -3
- package/server/github-pr-lookup-BRbDW-qk.js +0 -44
- package/server/hume-B_DbflNM.js +0 -3
- package/server/inspect-agent-ch-F-5Ci.js +0 -2
- package/server/launcher-generator-CHVm7bGT.js +0 -379
- package/server/layers-DzxSTGNA.js +0 -93
- package/server/merge-blockers-BsFjGrQ8.js +0 -41
- package/server/merge-ops-DlB1IM0Y.js +0 -2
- package/server/merge-set-DLz0MkGT.js +0 -139
- package/server/merge-set-DnSYdBEp.js +0 -3
- package/server/merge-sync-SSv3E-pl.js +0 -3
- package/server/merge-verification-CSizH3HY.js +0 -124
- package/server/projects-9SkRpES9.js +0 -3
- package/server/public/assets/index-CUr1xv8w.css +0 -1
- package/server/public/assets/index-CsSuZQBI.js +0 -802
- package/server/public/assets/mermaid-GHXKKRXX-BUAB8hqt.js +0 -1
- package/server/rally-njs_mJrt.js +0 -3
- package/server/rebuild-stack-kA7XngID.js +0 -3
- package/server/record-t6V-3Dcj.js +0 -3
- package/server/record-update-DACtKh6_.js +0 -3
- package/server/resume-CcKqnTvE.js +0 -3
- package/server/review-status-A7quS5KJ.js +0 -1454
- package/server/review-status-C6n6-9f_.js +0 -3
- package/server/settings-D0OSwos5.js +0 -2
- package/server/specs-nN45DG2X.js +0 -3
- package/server/src-U7_kTPEE.js +0 -4733
- package/server/stack-health-Df0YEZKa.js +0 -3
- package/server/supervisor-BPy-o1A8.js +0 -2
- package/server/token-util-DrHg8abn.js +0 -3
- package/server/tracker-utils-BePq_oj-.js +0 -3
- package/server/uat-train-CuEn0t9M.js +0 -2152
- package/server/uat-train-cKfM-_WW.js +0 -2
- package/server/workflows-D90rMOya.js +0 -2
- package/server/workspace-manager-ZTdJAy9M.js +0 -3
|
@@ -1,45 +1,47 @@
|
|
|
1
1
|
import { a as __toCommonJS } from "./rolldown-runtime-DNCzzKnI.js";
|
|
2
|
-
import { A as canUseHarnessSync,
|
|
3
|
-
import {
|
|
4
|
-
import { d as getOverdeckDatabaseSync, f as init_infra } from "./infra-
|
|
5
|
-
import {
|
|
6
|
-
import { a as init_event_store, n as getEventStore } from "./event-store-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { a as EventStoreService, c as ReadModelService, d as init_read_model, i as init_issue_lifecycle, s as init_domain_services, t as IssueLifecycle
|
|
10
|
-
import { d as isTerminalIssueStage, o as init_agents, u as getIssueStageSync } from "./agents-
|
|
11
|
-
import { n as ensureSessionContextBriefingFile, r as init_briefing_freshness } from "./briefing-freshness-
|
|
12
|
-
import { o as getPricingSync, s as init_cost, t as calculateCostSync } from "./cost-
|
|
13
|
-
import {
|
|
14
|
-
import { a as performHandoff, i as checkAllTriggers, s as getAgentHealth, t as getCloisterService } from "./service-
|
|
15
|
-
import { o as init_persistent_logger, s as logAgentLifecycleSync } from "./session-history-
|
|
16
|
-
import { t as init_behavior } from "./behavior-
|
|
17
|
-
import { a as init_tracker_config, t as getGitHubConfig } from "./tracker-config-
|
|
18
|
-
import { n as init_issue_service_singleton, r as issue_service_singleton_exports } from "./issue-service-singleton-
|
|
19
|
-
import { o as getInternalTokenSync, s as init_internal_token } from "./internal-token-
|
|
2
|
+
import { A as canUseHarnessSync, Cn as resolveStateReadHomeSync, D as resolveTieredExecutionEnabled, Fr as init_errors, Gn as init_projects, H as init_providers, I as UnknownModelError, M as init_harness_policy, Mn as findProjectByPathSync, O as resolveTieredExecutionEnabledForIssue, Sn as init_state_read_home, T as init_tier_table, Tr as FsError, U as piProviderForModel, V as getProviderForModelSync, Yn as listProjectsSync, ar as resolveProjectFromIssueSync, c as isClaudeCodeChannelsEnabled, ci as init_paths, di as resolveOhmypiExtensionPath, ei as encodeClaudeProjectDir, f as loadConfigSync, gr as init_issue_id, h as jsYaml, hn as init_state_plane, hr as extractPrefixSync, m as init_js_yaml, si as getOverdeckHome, t as init_config_yaml, tt as resolveModel, ui as packageRoot, vn as parsePorcelainStatusPaths, vr as parseIssueIdSync, wn as shouldCommitLegacyWorkspaceArtifacts, zn as getProjectSync } from "./config-yaml-CmOPzmBb.js";
|
|
3
|
+
import { n as emitActivityEntryDurable, o as init_activity_logger, r as emitActivityEntrySync } from "./activity-logger-CyteR4JO.js";
|
|
4
|
+
import { d as getOverdeckDatabaseSync, f as init_infra } from "./infra-BjuxvjdS.js";
|
|
5
|
+
import { B as sendKeysAsync, D as killSession, H as sessionExists, M as listSessionNames, N as listSessions, Q as BLANKED_PROVIDER_ENV, T as isHarnessProcessAlive, U as sessionExistsSync, V as sendRawKeystroke, W as setOption, et as buildChildEnvWithoutTmuxSync, g as findManagedServerPidSync, i as capturePane, m as exactPaneTarget, o as capturePaneText, s as createSession, tt as init_child_env, w as init_tmux } from "./tmux-B1Wbxz5o.js";
|
|
6
|
+
import { a as init_event_store, n as getEventStore } from "./event-store-zY5MJHgF.js";
|
|
7
|
+
import { N as normalizeChannelReplyPayload, T as DomainEvent, n as COMPOSER_COMMAND_MANIFEST, s as getHarnessBehavior, t as init_src } from "./src-CRmCHlma.js";
|
|
8
|
+
import { K as stateToOverdeckParamsForDb, S as setReviewStatusSync, U as init_agent_state_sync, V as AGENT_COLUMNS_FOR_DB, f as init_review_status, m as loadReviewStatuses, o as clearWorkspaceStuck, u as getReviewStatusSync } from "./review-status-yljtTd-q.js";
|
|
9
|
+
import { a as EventStoreService, c as ReadModelService, d as getClosedIssueIdsForReadSource, f as init_read_model, i as init_issue_lifecycle, s as init_domain_services, t as IssueLifecycle } from "./issue-lifecycle-C4Zj0WmE.js";
|
|
10
|
+
import { d as isTerminalIssueStage, o as init_agents, u as getIssueStageSync } from "./agents-C5rWYhb6.js";
|
|
11
|
+
import { n as ensureSessionContextBriefingFile, r as init_briefing_freshness } from "./briefing-freshness-DHmCSLar.js";
|
|
12
|
+
import { o as getPricingSync, s as init_cost, t as calculateCostSync } from "./cost-Bgcub5kW.js";
|
|
13
|
+
import { $ as operatorInterventionEvent, $n as getAgentDir, An as getProviderExportsForModel, Bn as dismissDevChannelsDialog, Dr as init_pty_supervisor_locate, Er as writePtyToken, F as init_tier_escalation, Fr as getAgentRuntimeState, G as resumeAgent, Gt as assertWorkspaceStackHealthyForSpawn, H as restartAgent, Hr as checkCodexAuthStatus, Ht as BRIDGE_TOKEN_HEADER, Ir as getAgentRuntimeStateSync, Jt as init_spawn_prep, Mn as init_provider_health, Mt as deliverAgentMessage, Nn as validateProviderHealth, Nt as deliverAgentPermissionDecision, On as determineModel, Or as resolvePtySupervisorScriptPath, P as decideEscalation, Pr as waitForReadySignal, Q as init_operator_interventions, Qt as init_staffing, Rr as saveAgentRuntimeState, Rt as setAgentDeliveryMethod, Sr as writeAgentStateJsonSync, Tr as init_pty_token, Un as writeChannelsBridgeMcpConfig, Ur as init_codex_auth, Ut as init_bridge_token, V as recoverAgent, Wt as writeBridgeTokenSync, X as messageAgent, Xt as transitionIssueToInProgress, Yn as clearAgentTroubled, _r as saveAgentStateSync, b as init_agents$1, bn as init_ohmypi_fifo, cn as getAgentRuntimeBaseCommand, dn as getProviderAuthMode, gn as waitForAcpHostReady, gr as saveAgentState, i as listRunningAgents, it as getSessionId, jr as normalizeAgentId, kr as clearReadySignal, nn as resolveStaffing, nt as getLatestSessionId, ot as saveSessionId, pn as init_runtime_command, pt as stopAgent, qn as clearAgentPaused, r as listAgentStates, rr as getAgentStateSync, rt as getLatestSessionIdSync, sn as getAcpLauncherFields, tn as resolveIssueWorkModel, tr as getAgentState, tt as getActivity, ur as markAgentStoppedState, vn as init_pi_fifo, vr as setAgentPaused, xn as ohmypiFifoPaths, xr as wipeAgentStateDirs, yn as piFifoPaths } from "./queries-DHJm7bTd.js";
|
|
14
|
+
import { a as performHandoff, i as checkAllTriggers, s as getAgentHealth, t as getCloisterService } from "./service-Br9rpBNA.js";
|
|
15
|
+
import { o as init_persistent_logger, s as logAgentLifecycleSync } from "./session-history-BUyK29eq.js";
|
|
16
|
+
import { t as init_behavior } from "./behavior-CfIg6pKp.js";
|
|
17
|
+
import { a as init_tracker_config, t as getGitHubConfig } from "./tracker-config-DZ8Zn2nQ.js";
|
|
18
|
+
import { n as init_issue_service_singleton, r as issue_service_singleton_exports } from "./issue-service-singleton-DmCh7EHj.js";
|
|
19
|
+
import { o as getInternalTokenSync, s as init_internal_token } from "./internal-token-DvMTQSOS.js";
|
|
20
20
|
import { a as PAN_DIRNAME, i as PAN_CONTINUE_FILENAME, p as init_types } from "./types-hraoaQIp.js";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import { _ as
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import { a as
|
|
42
|
-
import {
|
|
21
|
+
import { l as resolveWorkspaceRepoRootsSync, o as resolvePrimaryWorkspaceRepoDirSync, r as init_project_repos } from "./project-repos-C0nuoBGv.js";
|
|
22
|
+
import { a as generateXBriefFilename, o as init_lifecycle } from "./lifecycle-CTdOWYMi.js";
|
|
23
|
+
import { f as readTierOverrides, g as serializeXBriefDocument, h as recordTierPromotion, m as readWorkspacePlanSync, o as init_io, r as findPlan, u as readPlan } from "./io-VHvc0Ssp.js";
|
|
24
|
+
import { B as removeFavorite, E as hasOtherActiveConversationOnTmuxSession, F as markConversationEnded, G as setConversationHarness, K as setConversationModel, M as listConversations, P as markConversationActive, Q as updateConversationDeliveryMethod, S as getConversationByName, W as setConversationEffort, g as backfillConversationModel, h as archiveConversation, k as init_conversations, nt as updateLastAttached, rt as updateSpawnError, x as getConversationById, y as createConversation } from "./conversations-BUpYsOZg.js";
|
|
25
|
+
import { n as recordFeatureRegistryLifecycle, t as init_feature_registry_population } from "./feature-registry-population--YUXU999.js";
|
|
26
|
+
import { d as generateLauncherScriptSync, f as init_launcher_generator, g as requireModelOverrideSync, h as normalizeModelOverrideSync, m as init_model_validation, p as MODEL_ID_PATTERN } from "./tmux-dedup-DbN6MGzM.js";
|
|
27
|
+
import { a as init_claude_permissions, o as resolvePermissionModeSync, r as getClaudePermissionFlagsStringSync, t as BYPASS_PERMISSION_MODE } from "./claude-permissions-CN0cSCPp.js";
|
|
28
|
+
import { E as init_layers, I as workspaceContextFile, k as piGlobalContextFile } from "./assemble-DnffNpOo.js";
|
|
29
|
+
import { i as prepareHarnessLaunch, r as init_harness_binary } from "./harness-binary-DqkftwTb.js";
|
|
30
|
+
import { a as getWorkspaceStackHealth, s as init_stack_health } from "./stack-health-DqSqx0zv.js";
|
|
31
|
+
import { l as init_dag, o as getDispatchableItems } from "./dag-ombYYt4s.js";
|
|
32
|
+
import { n as resolveHarness, t as init_harness_resolve } from "./harness-resolve-CqMqM1Mb.js";
|
|
33
|
+
import { a as loadCloisterConfigSync, r as init_config } from "./config-B2IMcBxx.js";
|
|
34
|
+
import { c as isStateMigrated, o as init_state_home } from "./state-home-BYV7qZTW.js";
|
|
35
|
+
import { a as init_lifecycle_io, c as updatePlanStatus, s as transitionXBriefOnMain } from "./lifecycle-io-D-lGdzK3.js";
|
|
36
|
+
import { _ as init_codex_conversation_parser, b as parseAcpConversationMessages, c as parseEntireConversation, d as init_pi_conversation_parser, g as parseOhmypiConversationMessages, m as init_ohmypi_conversation_parser, p as parsePiConversationMessages, t as init_conversation_service, v as parseCodexConversationMessages, y as init_acp_conversation_parser } from "./conversation-service-5aCnq1zR.js";
|
|
37
|
+
import { _ as normalizeAwaitingInputPrompt, d as getAgentPendingQuestions$1, f as getAgentWorkspace$1, h as detectAwaitingInputForAgent, m as scanPendingInputsPromise, o as getRuntimeForAgent, p as isBlockedOnPendingInput, s as computeAgentEnrichment, u as getAgentJsonlPath$1, v as parseCodexApprovalPrompt } from "./stuck-remediation-mkLC6G_t.js";
|
|
38
|
+
import { B as getBook, H as liveOrderIssueLookup, T as resolveLiveFlywheelRunId, w as readFlywheelLaunchMetadata, y as getFlywheelRunDir, z as computeBookProgress } from "./flywheel-BiT3odRb.js";
|
|
39
|
+
import { Ft as getResourceConfig, Lt as getSystemHealthSnapshot, Ot as classifyMemoryPressure } from "./deacon-hi9KLPLT.js";
|
|
40
|
+
import { o as stopWorkspaceDocker, r as init_workspace_manager } from "./workspace-manager-CwO-Hek7.js";
|
|
41
|
+
import { a as init_jsonl_parser, o as normalizeModelName } from "./jsonl-parser-DJOH_O-w.js";
|
|
42
|
+
import { r as init_in_flight_guard, t as createInFlightGuard } from "./in-flight-guard-Bq3epG58.js";
|
|
43
|
+
import { a as loadWorkspaceMetadataSync } from "./workspace-metadata-CQv00QwK.js";
|
|
44
|
+
import { basename, delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
43
45
|
import { Cause, Data, Effect, Layer, Option, Schema, Stream } from "effect";
|
|
44
46
|
import { join as join$1 } from "path";
|
|
45
47
|
import { homedir, networkInterfaces } from "node:os";
|
|
@@ -51,9 +53,9 @@ import { mkdir as mkdir$1, writeFile as writeFile$1 } from "fs/promises";
|
|
|
51
53
|
import { exec, execFile, spawn } from "node:child_process";
|
|
52
54
|
import { promisify } from "node:util";
|
|
53
55
|
import { createHash as createHash$1, createHmac, randomBytes, randomUUID as randomUUID$1, timingSafeEqual } from "node:crypto";
|
|
56
|
+
import { createInterface } from "node:readline";
|
|
54
57
|
import { pathToFileURL } from "node:url";
|
|
55
58
|
import { request } from "node:http";
|
|
56
|
-
import { createInterface } from "node:readline";
|
|
57
59
|
import * as Result$1 from "effect/Result";
|
|
58
60
|
//#region http-helpers.ts
|
|
59
61
|
init_paths();
|
|
@@ -85,6 +87,7 @@ init_projects();
|
|
|
85
87
|
init_providers();
|
|
86
88
|
init_harness_policy();
|
|
87
89
|
init_tier_table();
|
|
90
|
+
init_js_yaml();
|
|
88
91
|
init_config_yaml();
|
|
89
92
|
init_child_env();
|
|
90
93
|
init_tmux();
|
|
@@ -100,6 +103,7 @@ init_cost();
|
|
|
100
103
|
init_behavior();
|
|
101
104
|
init_persistent_logger();
|
|
102
105
|
init_tracker_config();
|
|
106
|
+
init_project_repos();
|
|
103
107
|
init_lifecycle();
|
|
104
108
|
init_review_status();
|
|
105
109
|
init_io();
|
|
@@ -200,10 +204,12 @@ init_layers();
|
|
|
200
204
|
init_harness_binary();
|
|
201
205
|
init_ohmypi_fifo();
|
|
202
206
|
init_pi_fifo();
|
|
207
|
+
init_runtime_command();
|
|
203
208
|
init_stack_health();
|
|
204
209
|
init_launcher_generator();
|
|
205
210
|
init_dag();
|
|
206
211
|
init_staffing();
|
|
212
|
+
init_spawn_prep();
|
|
207
213
|
init_harness_resolve();
|
|
208
214
|
init_bridge_token();
|
|
209
215
|
init_operator_interventions();
|
|
@@ -215,6 +221,27 @@ function addTrustedOrigin(origins, raw) {
|
|
|
215
221
|
const normalized = normalizeOrigin(raw.trim());
|
|
216
222
|
if (normalized) origins.add(normalized);
|
|
217
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Last-resort origin config for launches that carry no origin env at all
|
|
226
|
+
* (manual `node dist/...`, ad-hoc re-exec). `pan up` builds its launch env from
|
|
227
|
+
* this same config.yaml, so reading it here keeps every launch path trusting the
|
|
228
|
+
* same Traefik domain instead of 403ing /ws/* into a "Reconnecting" loop.
|
|
229
|
+
*/
|
|
230
|
+
function readTraefikConfigFromYaml() {
|
|
231
|
+
try {
|
|
232
|
+
const configPath = join(process.env["OVERDECK_HOME"] || join(homedir(), ".overdeck"), "config.yaml");
|
|
233
|
+
if (!existsSync(configPath)) return null;
|
|
234
|
+
const traefik = jsYaml.load(readFileSync(configPath, "utf8"))?.traefik;
|
|
235
|
+
if (!traefik || typeof traefik !== "object") return null;
|
|
236
|
+
if (typeof traefik.domain !== "string" || traefik.domain.trim() === "") return null;
|
|
237
|
+
return {
|
|
238
|
+
enabled: traefik.enabled === true,
|
|
239
|
+
domain: traefik.domain.trim()
|
|
240
|
+
};
|
|
241
|
+
} catch {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
218
245
|
function getTrustedOrigins() {
|
|
219
246
|
if (cachedTrustedOrigins !== void 0) return cachedTrustedOrigins;
|
|
220
247
|
const port = parseInt(process.env["API_PORT"] ?? process.env["PORT"] ?? "3011", 10);
|
|
@@ -229,6 +256,10 @@ function getTrustedOrigins() {
|
|
|
229
256
|
for (const origin of trustedOrigins?.split(",") ?? []) addTrustedOrigin(origins, origin);
|
|
230
257
|
const traefikDomain = process.env["OVERDECK_TRAEFIK_DOMAIN"] ?? process.env["TRAEFIK_DOMAIN"];
|
|
231
258
|
if (process.env["OVERDECK_TRAEFIK_ENABLED"] === "1" && traefikDomain) addTrustedOrigin(origins, `https://${traefikDomain}`);
|
|
259
|
+
if (!(process.env["OVERDECK_TRAEFIK_ENABLED"] !== void 0 || traefikDomain !== void 0 || process.env["OVERDECK_TRUSTED_ORIGINS"] !== void 0)) {
|
|
260
|
+
const fromYaml = readTraefikConfigFromYaml();
|
|
261
|
+
if (fromYaml?.enabled) addTrustedOrigin(origins, `https://${fromYaml.domain}`);
|
|
262
|
+
}
|
|
232
263
|
if (process.env["NODE_ENV"] === "development") {
|
|
233
264
|
addTrustedOrigin(origins, "http://localhost:3000");
|
|
234
265
|
addTrustedOrigin(origins, "http://127.0.0.1:3000");
|
|
@@ -364,7 +395,215 @@ async function waitForSessionRespawn(sessionName, timeoutMs) {
|
|
|
364
395
|
return Effect.runPromise(sessionExists(sessionName));
|
|
365
396
|
}
|
|
366
397
|
//#endregion
|
|
398
|
+
//#region routes/dashboard-auth.ts
|
|
399
|
+
init_state_read_home();
|
|
400
|
+
init_state_plane();
|
|
401
|
+
init_types();
|
|
402
|
+
init_issue_lifecycle();
|
|
403
|
+
init_codex_auth();
|
|
404
|
+
init_config();
|
|
405
|
+
init_state_home();
|
|
406
|
+
init_internal_token();
|
|
407
|
+
const DASHBOARD_SESSION_COOKIE = "overdeck_session";
|
|
408
|
+
const DASHBOARD_CSRF_HEADER = "x-overdeck-csrf-token";
|
|
409
|
+
const DASHBOARD_SESSION_MAX_AGE_SECONDS = 3600 * 24 * 30;
|
|
410
|
+
let browserSessionToken;
|
|
411
|
+
let browserCsrfToken;
|
|
412
|
+
function getDashboardSessionToken() {
|
|
413
|
+
if (browserSessionToken) return browserSessionToken;
|
|
414
|
+
const override = process.env["OVERDECK_DASHBOARD_SESSION_TOKEN"];
|
|
415
|
+
if (override) {
|
|
416
|
+
browserSessionToken = override;
|
|
417
|
+
return browserSessionToken;
|
|
418
|
+
}
|
|
419
|
+
const internal = getInternalTokenSync();
|
|
420
|
+
browserSessionToken = internal ? createHmac("sha256", internal).update("overdeck-dashboard-session-v1").digest("base64url") : randomBytes(32).toString("base64url");
|
|
421
|
+
return browserSessionToken;
|
|
422
|
+
}
|
|
423
|
+
function dashboardCsrfToken() {
|
|
424
|
+
if (browserCsrfToken) return browserCsrfToken;
|
|
425
|
+
const override = process.env["OVERDECK_DASHBOARD_CSRF_TOKEN"];
|
|
426
|
+
if (override) {
|
|
427
|
+
browserCsrfToken = override;
|
|
428
|
+
return browserCsrfToken;
|
|
429
|
+
}
|
|
430
|
+
const internal = getInternalTokenSync();
|
|
431
|
+
browserCsrfToken = internal ? createHmac("sha256", internal).update("overdeck-dashboard-csrf-v1").digest("base64url") : randomBytes(32).toString("base64url");
|
|
432
|
+
return browserCsrfToken;
|
|
433
|
+
}
|
|
434
|
+
function constantTimeTokenEqual$1(provided, expected) {
|
|
435
|
+
if (!provided) return false;
|
|
436
|
+
return timingSafeEqual(createHash$1("sha256").update(provided).digest(), createHash$1("sha256").update(expected).digest());
|
|
437
|
+
}
|
|
438
|
+
function cookieValue(cookieHeader, name) {
|
|
439
|
+
if (!cookieHeader) return void 0;
|
|
440
|
+
for (const part of cookieHeader.split(";")) {
|
|
441
|
+
const [rawKey, ...rawValue] = part.trim().split("=");
|
|
442
|
+
if (rawKey !== name) continue;
|
|
443
|
+
try {
|
|
444
|
+
return decodeURIComponent(rawValue.join("="));
|
|
445
|
+
} catch {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
function dashboardSessionCookieHeader(options = {}) {
|
|
451
|
+
const secure = options.secure ? "; Secure" : "";
|
|
452
|
+
return `${DASHBOARD_SESSION_COOKIE}=${encodeURIComponent(getDashboardSessionToken())}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${DASHBOARD_SESSION_MAX_AGE_SECONDS}${secure}`;
|
|
453
|
+
}
|
|
454
|
+
function hasDashboardInternalTokenHeaders(headers) {
|
|
455
|
+
const expected = getInternalTokenSync();
|
|
456
|
+
if (!expected) return false;
|
|
457
|
+
if (constantTimeTokenEqual$1(getHeaderFromMap(headers, "x-overdeck-internal-token"), expected)) return true;
|
|
458
|
+
const authorization = getHeaderFromMap(headers, "authorization");
|
|
459
|
+
if (authorization) {
|
|
460
|
+
const [scheme, token] = authorization.split(/\s+/);
|
|
461
|
+
if (scheme?.toLowerCase() === "bearer" && constantTimeTokenEqual$1(token, expected)) return true;
|
|
462
|
+
}
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
function hasDashboardInternalToken(request) {
|
|
466
|
+
return hasDashboardInternalTokenHeaders(request.headers);
|
|
467
|
+
}
|
|
468
|
+
function hasDashboardAuthHeaders(headers) {
|
|
469
|
+
return hasDashboardInternalTokenHeaders(headers) || constantTimeTokenEqual$1(cookieValue(getHeaderFromMap(headers, "cookie"), "overdeck_session"), getDashboardSessionToken());
|
|
470
|
+
}
|
|
471
|
+
function hasDashboardAuth(request) {
|
|
472
|
+
return hasDashboardAuthHeaders(request.headers);
|
|
473
|
+
}
|
|
474
|
+
function isJsonContentType(headers) {
|
|
475
|
+
const contentType = getHeaderFromMap(headers, "content-type");
|
|
476
|
+
if (!contentType) return false;
|
|
477
|
+
const [mime] = contentType.toLowerCase().split(";");
|
|
478
|
+
return mime.trim() === "application/json";
|
|
479
|
+
}
|
|
480
|
+
function hasTrustedExactOrigin(headers) {
|
|
481
|
+
const origin = getHeaderFromMap(headers, "origin");
|
|
482
|
+
if (!origin) return false;
|
|
483
|
+
const normalized = normalizeOrigin(origin);
|
|
484
|
+
return !!normalized && getTrustedOrigins().includes(normalized);
|
|
485
|
+
}
|
|
486
|
+
function hasValidCsrfToken(headers) {
|
|
487
|
+
return constantTimeTokenEqual$1(getHeaderFromMap(headers, DASHBOARD_CSRF_HEADER), dashboardCsrfToken());
|
|
488
|
+
}
|
|
489
|
+
function rejectUnsafeDashboardMutationRequest(request) {
|
|
490
|
+
const authError = rejectUnauthorizedDashboardRequest(request);
|
|
491
|
+
if (authError) return authError;
|
|
492
|
+
const headers = request.headers;
|
|
493
|
+
if (!isJsonContentType(headers)) return jsonResponse({ error: "Content-Type must be application/json" }, { status: 400 });
|
|
494
|
+
if (hasDashboardInternalToken(request)) return null;
|
|
495
|
+
if (getHeaderFromMap(headers, "origin") && !hasTrustedExactOrigin(headers)) return jsonResponse({ error: "Invalid origin" }, { status: 403 });
|
|
496
|
+
if (!hasValidCsrfToken(headers)) return jsonResponse({ error: "Invalid CSRF token" }, { status: 403 });
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
function ipv4ToInt(value) {
|
|
500
|
+
const parts = value.split(".");
|
|
501
|
+
if (parts.length !== 4) return null;
|
|
502
|
+
let result = 0;
|
|
503
|
+
for (const part of parts) {
|
|
504
|
+
const octet = Number(part);
|
|
505
|
+
if (!Number.isInteger(octet) || octet < 0 || octet > 255) return null;
|
|
506
|
+
result = (result << 8 | octet) >>> 0;
|
|
507
|
+
}
|
|
508
|
+
return result;
|
|
509
|
+
}
|
|
510
|
+
/** RFC1918 private ranges — Docker bridges always fall inside one of these. */
|
|
511
|
+
function isRfc1918(ip) {
|
|
512
|
+
return (ip & 4278190080) >>> 0 === 167772160 || (ip & 4293918720) >>> 0 === 2886729728 || (ip & 4294901760) >>> 0 === 3232235520;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* True when `addr` is the IP of a container on one of THIS host's Docker bridge
|
|
516
|
+
* interfaces (docker0 / br-*). Such a peer can only be a process running on this
|
|
517
|
+
* machine — our own host-local Traefik fronting overdeck.localhost, or another
|
|
518
|
+
* Overdeck container — never a LAN client (a direct LAN hit to the 0.0.0.0
|
|
519
|
+
* API port arrives with a LAN peer that is not inside any Docker bridge subnet).
|
|
520
|
+
*
|
|
521
|
+
* Pure for testability: the interface map is injected. We only ever trust
|
|
522
|
+
* RFC1918 bridge subnets, so a misconfigured public-range br-* cannot widen
|
|
523
|
+
* trust beyond the local Docker fabric.
|
|
524
|
+
*/
|
|
525
|
+
function peerIsHostLocalDockerBridge(addr, interfaces) {
|
|
526
|
+
const peer = ipv4ToInt(addr);
|
|
527
|
+
if (peer === null) return false;
|
|
528
|
+
for (const [name, addrs] of Object.entries(interfaces)) {
|
|
529
|
+
if (name !== "docker0" && !name.startsWith("br-")) continue;
|
|
530
|
+
for (const info of addrs ?? []) {
|
|
531
|
+
if (info.family !== "IPv4") continue;
|
|
532
|
+
const ifaceIp = ipv4ToInt(info.address);
|
|
533
|
+
const mask = ipv4ToInt(info.netmask);
|
|
534
|
+
if (ifaceIp === null || mask === null) continue;
|
|
535
|
+
const network = (ifaceIp & mask) >>> 0;
|
|
536
|
+
if (!isRfc1918(network)) continue;
|
|
537
|
+
if ((peer & mask) >>> 0 === network) return true;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* True when `addr` shares an RFC1918 subnet with one of THIS process's own
|
|
544
|
+
* network interfaces. This is the in-container counterpart of
|
|
545
|
+
* peerIsHostLocalDockerBridge: inside a container the Docker bridge is
|
|
546
|
+
* invisible — the container sees only its veth endpoints (eth0/eth1), so the
|
|
547
|
+
* docker0/br-* name filter never matches and the host-local Traefik fronting
|
|
548
|
+
* the stack 401'd every session mint (first hit live on the PAN-1737 UAT
|
|
549
|
+
* stack). A peer inside one of the container's attached Docker-network
|
|
550
|
+
* subnets can only be another container on those networks — the server
|
|
551
|
+
* publishes no host port (Traefik labels only), so no LAN client can ever
|
|
552
|
+
* present such an address. Only consulted when running inside a container.
|
|
553
|
+
*/
|
|
554
|
+
function peerIsLocalContainerNetwork(addr, interfaces) {
|
|
555
|
+
const peer = ipv4ToInt(addr);
|
|
556
|
+
if (peer === null) return false;
|
|
557
|
+
for (const addrs of Object.values(interfaces)) for (const info of addrs ?? []) {
|
|
558
|
+
if (info.family !== "IPv4" || info.internal) continue;
|
|
559
|
+
const ifaceIp = ipv4ToInt(info.address);
|
|
560
|
+
const mask = ipv4ToInt(info.netmask);
|
|
561
|
+
if (ifaceIp === null || mask === null) continue;
|
|
562
|
+
const network = (ifaceIp & mask) >>> 0;
|
|
563
|
+
if (!isRfc1918(network)) continue;
|
|
564
|
+
if ((peer & mask) >>> 0 === network) return true;
|
|
565
|
+
}
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
/** Docker creates /.dockerenv in every container — stable in-container signal. */
|
|
569
|
+
const runningInContainer = existsSync("/.dockerenv");
|
|
570
|
+
/**
|
|
571
|
+
* A request we trust to mint a session because it originated from this machine —
|
|
572
|
+
* the local browser hitting the dashboard directly (127.0.0.1), or the
|
|
573
|
+
* host-local Traefik that fronts overdeck.localhost. In Docker, that Traefik reaches
|
|
574
|
+
* the host server from a Docker-bridge IP (e.g. 172.18.0.2), NOT 127.0.0.1, so
|
|
575
|
+
* a literal-loopback check alone silently breaks the zero-step bootstrap for
|
|
576
|
+
* every overdeck.localhost user — their session mint 401s and no cookie is ever set.
|
|
577
|
+
*
|
|
578
|
+
* This is the auto-bootstrap that removes the manual one-time #overdeck_token
|
|
579
|
+
* step: any browser on overdeck.localhost gets a session with no user action. It is
|
|
580
|
+
* NOT a security downgrade for the LAN — the raw API ports bind 0.0.0.0, but a
|
|
581
|
+
* direct LAN hit has a non-loopback, non-Docker-bridge peer and is rejected. We
|
|
582
|
+
* read ONLY the real TCP peer (request.remoteAddress), never X-Forwarded-For,
|
|
583
|
+
* which a caller could spoof.
|
|
584
|
+
*/
|
|
585
|
+
function isLoopbackPeer(request) {
|
|
586
|
+
const remoteAddress = request.remoteAddress;
|
|
587
|
+
const raw = remoteAddress && typeof remoteAddress === "object" && "_tag" in remoteAddress ? Option.getOrElse(remoteAddress, () => "") : typeof remoteAddress === "string" ? remoteAddress : "";
|
|
588
|
+
if (!raw) return false;
|
|
589
|
+
const addr = raw.startsWith("::ffff:") ? raw.slice(7) : raw;
|
|
590
|
+
if (addr === "127.0.0.1" || addr === "::1") return true;
|
|
591
|
+
if (peerIsHostLocalDockerBridge(addr, networkInterfaces())) return true;
|
|
592
|
+
return runningInContainer && peerIsLocalContainerNetwork(addr, networkInterfaces());
|
|
593
|
+
}
|
|
594
|
+
function rejectUnauthorizedDashboardSessionMintRequest(request) {
|
|
595
|
+
if (!getInternalTokenSync()) return jsonResponse({ error: "dashboard session token not configured" }, { status: 503 });
|
|
596
|
+
if (!hasDashboardInternalToken(request) && !isLoopbackPeer(request)) return jsonResponse({ error: "unauthorized" }, { status: 401 });
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
function rejectUnauthorizedDashboardRequest(request) {
|
|
600
|
+
if (!getInternalTokenSync()) return jsonResponse({ error: "dashboard session token not configured" }, { status: 503 });
|
|
601
|
+
if (!hasDashboardAuth(request)) return jsonResponse({ error: "unauthorized" }, { status: 401 });
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
//#endregion
|
|
367
605
|
//#region routes/jsonl-resolver.ts
|
|
606
|
+
init_acp_conversation_parser();
|
|
368
607
|
init_codex_conversation_parser();
|
|
369
608
|
init_ohmypi_conversation_parser();
|
|
370
609
|
init_pi_conversation_parser();
|
|
@@ -390,6 +629,8 @@ init_conversation_service();
|
|
|
390
629
|
* order no longer reflects time-recency. We disambiguate
|
|
391
630
|
* across all listed IDs by JSONL mtime.
|
|
392
631
|
* 3. runtime state — in-process mirror, populated by hooks
|
|
632
|
+
* 4. agents registry — the DB row's session_id recorded at spawn; survives
|
|
633
|
+
* janitor removal of the agent directory
|
|
393
634
|
*
|
|
394
635
|
* Async-only (fs/promises) because this code path runs inside the dashboard
|
|
395
636
|
* server's event loop.
|
|
@@ -475,6 +716,10 @@ async function resolveClaudeSessionId(agentId, opts = {}) {
|
|
|
475
716
|
const runtimeState = await (opts.getRuntimeStateAsync ?? ((id) => Effect.runPromise(getAgentRuntimeState(id))))(agentId);
|
|
476
717
|
if (runtimeState?.claudeSessionId) return runtimeState.claudeSessionId;
|
|
477
718
|
} catch {}
|
|
719
|
+
if (!opts.agentsDirOverride) {
|
|
720
|
+
const registrySessionId = getAgentStateSync(agentId)?.sessionId?.trim();
|
|
721
|
+
if (registrySessionId) return registrySessionId;
|
|
722
|
+
}
|
|
478
723
|
return null;
|
|
479
724
|
}
|
|
480
725
|
/**
|
|
@@ -532,7 +777,7 @@ async function readRecordedState(agentId, opts) {
|
|
|
532
777
|
* Resolve the harness for transcript routing. Reads state.json, then
|
|
533
778
|
* self-corrects when the recorded value is stale.
|
|
534
779
|
*
|
|
535
|
-
* Trust an explicit non-default harness (codex / pi / ohmypi) — it was chosen
|
|
780
|
+
* Trust an explicit non-default harness (codex / pi / ohmypi / acp) — it was chosen
|
|
536
781
|
* by resolveHarness at spawn time and is not the generic fallback. The
|
|
537
782
|
* 'claude-code' default, however, goes stale in one observed case: a
|
|
538
783
|
* wipe-and-respawn that changed the provider-default harness left state.json
|
|
@@ -550,8 +795,9 @@ async function readRecordedState(agentId, opts) {
|
|
|
550
795
|
*/
|
|
551
796
|
async function resolveAgentHarness(agentId, opts = {}) {
|
|
552
797
|
const recorded = (await readRecordedState(agentId, opts)).harness;
|
|
553
|
-
if (recorded === "codex" || recorded === "pi" || recorded === "ohmypi") return recorded;
|
|
798
|
+
if (recorded === "codex" || recorded === "pi" || recorded === "ohmypi" || recorded === "acp") return recorded;
|
|
554
799
|
if (await agentHasClaudeTranscript(agentId, opts)) return recorded ?? "claude-code";
|
|
800
|
+
if (await resolveAcpTranscriptPath(agentId, opts)) return "acp";
|
|
555
801
|
if (await resolveCodexRolloutPath(agentId, opts)) return "codex";
|
|
556
802
|
if (await resolvePiSessionPath(agentId, opts)) return "ohmypi";
|
|
557
803
|
return recorded;
|
|
@@ -581,7 +827,7 @@ async function resolveCodexRolloutPath(agentId, opts = {}) {
|
|
|
581
827
|
const agentDir = join(opts.agentsDirOverride ?? join(getOverdeckHome(), "agents"), agentId);
|
|
582
828
|
const codexHome = join(agentDir, "codex-home");
|
|
583
829
|
if (!await pathExists$1(codexHome)) return null;
|
|
584
|
-
const { findRolloutPath, findLatestRollout } = await import("./codex-
|
|
830
|
+
const { findRolloutPath, findLatestRollout } = await import("./codex-BdEsUFWh.js");
|
|
585
831
|
const threadId = (await readOptional(join(agentDir, "codex-thread-id")))?.trim();
|
|
586
832
|
if (threadId) {
|
|
587
833
|
const rollout = findRolloutPath(codexHome, threadId);
|
|
@@ -589,6 +835,11 @@ async function resolveCodexRolloutPath(agentId, opts = {}) {
|
|
|
589
835
|
}
|
|
590
836
|
return findLatestRollout(codexHome);
|
|
591
837
|
}
|
|
838
|
+
/** Resolve the normalized transcript written by the persistent ACP host. */
|
|
839
|
+
async function resolveAcpTranscriptPath(agentId, opts = {}) {
|
|
840
|
+
const transcriptPath = join(opts.agentsDirOverride ?? join(getOverdeckHome(), "agents"), agentId, "acp-session.jsonl");
|
|
841
|
+
return await pathExists$1(transcriptPath) ? transcriptPath : null;
|
|
842
|
+
}
|
|
592
843
|
/**
|
|
593
844
|
* Resolve the pi/kimi session JSONL (PAN-1908). Pi writes its session transcript
|
|
594
845
|
* as `<iso-ts>_<session-id>.jsonl` either in the agent dir's `sessions/` subdir
|
|
@@ -599,6 +850,7 @@ async function resolveCodexRolloutPath(agentId, opts = {}) {
|
|
|
599
850
|
async function resolvePiSessionPath(agentId, opts = {}) {
|
|
600
851
|
const agentDir = join(opts.agentsDirOverride ?? join(getOverdeckHome(), "agents"), agentId);
|
|
601
852
|
const NON_TRANSCRIPT = new Set([
|
|
853
|
+
"acp-session.jsonl",
|
|
602
854
|
"cost-events.jsonl",
|
|
603
855
|
"activity.jsonl",
|
|
604
856
|
"pending-events.jsonl"
|
|
@@ -637,6 +889,7 @@ async function resolveJsonlPath(agentId, workspacePath, opts = {}) {
|
|
|
637
889
|
const behavior = behaviorForHarness(harness);
|
|
638
890
|
if (behavior.transcriptKind === "codex-rollout-jsonl") return resolveCodexRolloutPath(agentId, opts);
|
|
639
891
|
if (behavior.transcriptKind === "ohmypi-jsonl") return resolvePiSessionPath(agentId, opts);
|
|
892
|
+
if (behavior.transcriptKind === "acp-jsonl") return resolveAcpTranscriptPath(agentId, opts);
|
|
640
893
|
const claudeSessionId = await resolveClaudeSessionId(agentId, opts);
|
|
641
894
|
if (!claudeSessionId) {
|
|
642
895
|
logTranscriptResolution(agentId, `missing-session-id:${harness ?? "unknown"}`, `failed harness=${harness ?? "unknown"} reason=no-session-id checked=session.id,sessions.json,runtime-state`, opts);
|
|
@@ -808,7 +1061,7 @@ async function getWorkAgentLifecycleStateSnapshot(agentOrIssueId) {
|
|
|
808
1061
|
runtimeState: runtime,
|
|
809
1062
|
agentStatus,
|
|
810
1063
|
canStartFresh: (!hasLiveTmuxSession || hasLiveTmuxSession && isStopped) && (!requiresSessionResetBeforeFreshStart || isOrphaned),
|
|
811
|
-
canResumeSession: !isRunning && hasResumableBackingState && (isStopped || isCrashed),
|
|
1064
|
+
canResumeSession: !isRunning && hasSavedSession && hasResumableBackingState && (isStopped || isCrashed),
|
|
812
1065
|
canRestartWithContext: hasAgentState && hasWorkspace,
|
|
813
1066
|
canResetSession: hasSavedSession && hasResumableBackingState,
|
|
814
1067
|
requiresSessionResetBeforeFreshStart,
|
|
@@ -960,41 +1213,130 @@ async function resolveAgentGitInfo(workspacePath, expectedBranch) {
|
|
|
960
1213
|
};
|
|
961
1214
|
}
|
|
962
1215
|
//#endregion
|
|
963
|
-
//#region
|
|
1216
|
+
//#region ../../lib/composer-commands/executors.ts
|
|
1217
|
+
const CAPTURED_COMMAND_MAX_OUTPUT_BYTES = 64 * 1024;
|
|
1218
|
+
const TRUNCATION_MESSAGE = "\n\nOutput was truncated after 65,536 bytes.";
|
|
1219
|
+
function runCapturedCommand(argv, dependencies = {}) {
|
|
1220
|
+
const command = `/pan ${argv.join(" ")}`;
|
|
1221
|
+
const spawnCommand = dependencies.spawnPanCli ?? spawnPanCli;
|
|
1222
|
+
const timeoutMs = dependencies.timeoutMs ?? 3e4;
|
|
1223
|
+
const child = spawnCommand(argv, { stdio: [
|
|
1224
|
+
"ignore",
|
|
1225
|
+
"pipe",
|
|
1226
|
+
"pipe"
|
|
1227
|
+
] });
|
|
1228
|
+
return new Promise((resolve) => {
|
|
1229
|
+
const chunks = [];
|
|
1230
|
+
let bufferedBytes = 0;
|
|
1231
|
+
let settled = false;
|
|
1232
|
+
let timeout;
|
|
1233
|
+
let truncated = false;
|
|
1234
|
+
const appendOutput = (data) => {
|
|
1235
|
+
if (truncated) return;
|
|
1236
|
+
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
1237
|
+
const remaining = CAPTURED_COMMAND_MAX_OUTPUT_BYTES - Buffer.byteLength(TRUNCATION_MESSAGE) - bufferedBytes;
|
|
1238
|
+
if (buffer.length <= remaining) {
|
|
1239
|
+
chunks.push(buffer);
|
|
1240
|
+
bufferedBytes += buffer.length;
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
if (remaining > 0) {
|
|
1244
|
+
chunks.push(buffer.subarray(0, remaining));
|
|
1245
|
+
bufferedBytes += remaining;
|
|
1246
|
+
}
|
|
1247
|
+
truncated = true;
|
|
1248
|
+
};
|
|
1249
|
+
const output = () => {
|
|
1250
|
+
const captured = Buffer.concat(chunks, bufferedBytes).toString("utf8");
|
|
1251
|
+
return truncated ? `${captured}${TRUNCATION_MESSAGE}` : captured;
|
|
1252
|
+
};
|
|
1253
|
+
const finish = (result) => {
|
|
1254
|
+
if (settled) return;
|
|
1255
|
+
settled = true;
|
|
1256
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
1257
|
+
resolve(result);
|
|
1258
|
+
};
|
|
1259
|
+
child.stdout?.on("data", appendOutput);
|
|
1260
|
+
child.stderr?.on("data", appendOutput);
|
|
1261
|
+
child.once("error", (error) => {
|
|
1262
|
+
finish({
|
|
1263
|
+
kind: "captured",
|
|
1264
|
+
status: "failed",
|
|
1265
|
+
command,
|
|
1266
|
+
output: `Command ${command} could not start: ${error.message}`,
|
|
1267
|
+
truncated: false
|
|
1268
|
+
});
|
|
1269
|
+
});
|
|
1270
|
+
child.once("close", (code) => {
|
|
1271
|
+
const captured = output();
|
|
1272
|
+
finish({
|
|
1273
|
+
kind: "captured",
|
|
1274
|
+
status: code === 0 ? "completed" : "failed",
|
|
1275
|
+
command,
|
|
1276
|
+
output: captured || `Command ${command} ${code === 0 ? "completed without output." : `failed with exit code ${code ?? "unknown"}.`}`,
|
|
1277
|
+
truncated
|
|
1278
|
+
});
|
|
1279
|
+
});
|
|
1280
|
+
timeout = setTimeout(() => {
|
|
1281
|
+
stopChild(child);
|
|
1282
|
+
finish({
|
|
1283
|
+
kind: "captured",
|
|
1284
|
+
status: "failed",
|
|
1285
|
+
command,
|
|
1286
|
+
output: `Command ${command} timed out after ${Math.round(timeoutMs / 1e3)} seconds and was stopped.`,
|
|
1287
|
+
truncated: false
|
|
1288
|
+
});
|
|
1289
|
+
}, timeoutMs);
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
function stopChild(child) {
|
|
1293
|
+
child.kill("SIGTERM");
|
|
1294
|
+
}
|
|
1295
|
+
//#endregion
|
|
1296
|
+
//#region ../../lib/composer-commands/detached.ts
|
|
964
1297
|
init_paths();
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1298
|
+
const DETACHED_TRUNCATION_MESSAGE = "\n\nOutput was truncated after 65,536 bytes.";
|
|
1299
|
+
function resolveAgentDirectory(overdeckHome, agentId) {
|
|
1300
|
+
if (agentId.length === 0 || agentId === "." || agentId === ".." || agentId.includes("/") || agentId.includes("\\") || isAbsolute(agentId)) throw new Error("Agent ID must be a single filesystem-safe path segment.");
|
|
1301
|
+
return join(resolve(overdeckHome, "agents"), agentId);
|
|
1302
|
+
}
|
|
1303
|
+
async function readBoundedSpawnOutput(spawnLogPath, startOffset) {
|
|
1304
|
+
const maximumPrefixBytes = CAPTURED_COMMAND_MAX_OUTPUT_BYTES - Buffer.byteLength(DETACHED_TRUNCATION_MESSAGE);
|
|
1305
|
+
const handle = await open(spawnLogPath, "r");
|
|
1306
|
+
try {
|
|
1307
|
+
const stats = await handle.stat();
|
|
1308
|
+
const outputBytes = Math.max(0, stats.size - startOffset);
|
|
1309
|
+
if (outputBytes === 0) return "";
|
|
1310
|
+
const bytesToRead = Math.min(outputBytes, maximumPrefixBytes);
|
|
1311
|
+
const buffer = Buffer.alloc(bytesToRead);
|
|
1312
|
+
const { bytesRead } = await handle.read(buffer, 0, bytesToRead, startOffset);
|
|
1313
|
+
const output = buffer.subarray(0, bytesRead).toString("utf8");
|
|
1314
|
+
return outputBytes > maximumPrefixBytes ? `${output}${DETACHED_TRUNCATION_MESSAGE}` : output;
|
|
1315
|
+
} finally {
|
|
1316
|
+
await handle.close().catch(() => void 0);
|
|
1317
|
+
}
|
|
979
1318
|
}
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1319
|
+
async function appendAgentLifecycleLog(agentId, event, details = {}, overdeckHome = getOverdeckHome()) {
|
|
1320
|
+
const agentDir = resolveAgentDirectory(overdeckHome, agentId);
|
|
1321
|
+
await mkdir(agentDir, { recursive: true });
|
|
1322
|
+
const logLine = JSON.stringify({
|
|
1323
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1324
|
+
event,
|
|
1325
|
+
...details
|
|
1326
|
+
});
|
|
1327
|
+
await appendFile(join(agentDir, "lifecycle.log"), `${logLine}\n`);
|
|
1328
|
+
}
|
|
1329
|
+
async function launchPanCommandDetached(input, dependencies = {}) {
|
|
989
1330
|
const { agentSessionName, issueId, role, workspacePath, args } = input;
|
|
990
1331
|
const cwd = input.cwd ?? workspacePath;
|
|
991
|
-
const
|
|
992
|
-
const
|
|
1332
|
+
const overdeckHome = dependencies.overdeckHome ?? getOverdeckHome();
|
|
1333
|
+
const activityId = input.activityId ?? `activity-${(dependencies.now ?? Date.now)()}`;
|
|
1334
|
+
const agentDir = resolveAgentDirectory(overdeckHome, agentSessionName);
|
|
993
1335
|
await mkdir(agentDir, { recursive: true });
|
|
994
1336
|
const spawnLogPath = join(agentDir, "spawn.log");
|
|
995
1337
|
const spawnLogHandle = await open(spawnLogPath, "a");
|
|
996
|
-
const
|
|
997
|
-
const child =
|
|
1338
|
+
const spawnLogStart = (await spawnLogHandle.stat()).size;
|
|
1339
|
+
const child = (dependencies.spawnPanCli ?? spawnPanCli)(args, {
|
|
998
1340
|
cwd,
|
|
999
1341
|
detached: true,
|
|
1000
1342
|
stdio: [
|
|
@@ -1003,67 +1345,200 @@ async function spawnPanCommandDetached(input) {
|
|
|
1003
1345
|
spawnLogHandle.fd
|
|
1004
1346
|
]
|
|
1005
1347
|
});
|
|
1348
|
+
let spawned = false;
|
|
1349
|
+
let settleSpawn;
|
|
1350
|
+
let rejectSpawn;
|
|
1351
|
+
const spawnPromise = new Promise((resolve, reject) => {
|
|
1352
|
+
settleSpawn = resolve;
|
|
1353
|
+
rejectSpawn = reject;
|
|
1354
|
+
});
|
|
1355
|
+
const completion = commandCompletion(child, {
|
|
1356
|
+
agentSessionName,
|
|
1357
|
+
issueId,
|
|
1358
|
+
role,
|
|
1359
|
+
workspacePath,
|
|
1360
|
+
activityId,
|
|
1361
|
+
args,
|
|
1362
|
+
cwd,
|
|
1363
|
+
spawnLogPath,
|
|
1364
|
+
spawnLogStart,
|
|
1365
|
+
spawnLogHandle,
|
|
1366
|
+
overdeckHome,
|
|
1367
|
+
onSpawn: () => {
|
|
1368
|
+
spawned = true;
|
|
1369
|
+
settleSpawn?.();
|
|
1370
|
+
},
|
|
1371
|
+
onError: (error) => {
|
|
1372
|
+
if (!spawned) rejectSpawn?.(error);
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
completion.catch(() => void 0);
|
|
1376
|
+
await spawnPromise;
|
|
1377
|
+
child.unref();
|
|
1378
|
+
return {
|
|
1379
|
+
activityId,
|
|
1380
|
+
spawnLogPath,
|
|
1381
|
+
spawnLogStart,
|
|
1382
|
+
completion
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
function commandCompletion(child, context) {
|
|
1006
1386
|
child.once("spawn", () => {
|
|
1007
|
-
appendAgentLifecycleLog(agentSessionName, "agent.work_spawn_process_spawned", {
|
|
1008
|
-
issueId,
|
|
1009
|
-
role,
|
|
1010
|
-
workspacePath,
|
|
1011
|
-
activityId,
|
|
1387
|
+
appendAgentLifecycleLog(context.agentSessionName, "agent.work_spawn_process_spawned", {
|
|
1388
|
+
issueId: context.issueId,
|
|
1389
|
+
role: context.role,
|
|
1390
|
+
workspacePath: context.workspacePath,
|
|
1391
|
+
activityId: context.activityId,
|
|
1012
1392
|
pid: child.pid,
|
|
1013
|
-
args,
|
|
1014
|
-
cwd,
|
|
1015
|
-
spawnLogPath
|
|
1016
|
-
}).catch(() => void 0);
|
|
1393
|
+
args: context.args,
|
|
1394
|
+
cwd: context.cwd,
|
|
1395
|
+
spawnLogPath: context.spawnLogPath
|
|
1396
|
+
}, context.overdeckHome).catch(() => void 0).finally(context.onSpawn);
|
|
1017
1397
|
});
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1398
|
+
return new Promise((resolve, reject) => {
|
|
1399
|
+
let settled = false;
|
|
1400
|
+
const finish = async (error) => {
|
|
1401
|
+
if (settled) return;
|
|
1402
|
+
settled = true;
|
|
1403
|
+
await context.spawnLogHandle.close().catch(() => void 0);
|
|
1404
|
+
if (error) reject(error);
|
|
1405
|
+
else resolve();
|
|
1406
|
+
};
|
|
1407
|
+
child.once("error", (error) => {
|
|
1408
|
+
(async () => {
|
|
1409
|
+
await appendAgentLifecycleLog(context.agentSessionName, "agent.work_spawn_process_error", {
|
|
1410
|
+
issueId: context.issueId,
|
|
1411
|
+
role: context.role,
|
|
1412
|
+
workspacePath: context.workspacePath,
|
|
1413
|
+
activityId: context.activityId,
|
|
1026
1414
|
error: error.message,
|
|
1027
|
-
args,
|
|
1028
|
-
cwd,
|
|
1029
|
-
spawnLogPath
|
|
1030
|
-
}).catch(() => void 0);
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1415
|
+
args: context.args,
|
|
1416
|
+
cwd: context.cwd,
|
|
1417
|
+
spawnLogPath: context.spawnLogPath
|
|
1418
|
+
}, context.overdeckHome).catch(() => void 0);
|
|
1419
|
+
context.onError(error);
|
|
1420
|
+
await finish(error);
|
|
1421
|
+
})();
|
|
1422
|
+
});
|
|
1423
|
+
child.once("close", (code, signal) => {
|
|
1424
|
+
(async () => {
|
|
1425
|
+
await appendAgentLifecycleLog(context.agentSessionName, "agent.work_spawn_process_closed", {
|
|
1426
|
+
issueId: context.issueId,
|
|
1427
|
+
role: context.role,
|
|
1428
|
+
workspacePath: context.workspacePath,
|
|
1429
|
+
activityId: context.activityId,
|
|
1039
1430
|
code,
|
|
1040
1431
|
signal,
|
|
1041
|
-
args,
|
|
1042
|
-
cwd,
|
|
1043
|
-
spawnLogPath
|
|
1044
|
-
}).catch(() => void 0);
|
|
1045
|
-
|
|
1432
|
+
args: context.args,
|
|
1433
|
+
cwd: context.cwd,
|
|
1434
|
+
spawnLogPath: context.spawnLogPath
|
|
1435
|
+
}, context.overdeckHome).catch(() => void 0);
|
|
1436
|
+
if (code === 0) {
|
|
1437
|
+
await finish();
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
const output = await readBoundedSpawnOutput(context.spawnLogPath, context.spawnLogStart).catch(() => "");
|
|
1441
|
+
const error = new Error(output.trim() || `pan ${context.args.join(" ")} exited with code ${code ?? "null"}`);
|
|
1442
|
+
Object.assign(error, {
|
|
1443
|
+
activityId: context.activityId,
|
|
1444
|
+
spawnLogPath: context.spawnLogPath,
|
|
1046
1445
|
code,
|
|
1047
|
-
signal
|
|
1446
|
+
signal,
|
|
1447
|
+
output
|
|
1048
1448
|
});
|
|
1049
|
-
|
|
1449
|
+
await finish(error);
|
|
1450
|
+
})();
|
|
1050
1451
|
});
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
async function runDetachedCommand(argv, dependencies = {}) {
|
|
1455
|
+
const issueId = argv[1] ?? "";
|
|
1456
|
+
const parsedIssueId = parseIssueIdSync(issueId);
|
|
1457
|
+
if (!parsedIssueId) return {
|
|
1458
|
+
kind: "terminal-only",
|
|
1459
|
+
status: "rejected",
|
|
1460
|
+
message: `/pan ${argv[0] ?? "command"} requires a canonical issue ID such as PAN-1525.`
|
|
1461
|
+
};
|
|
1462
|
+
const role = argv[0] === "plan" ? "plan" : "work";
|
|
1463
|
+
const agentSessionName = `${role === "plan" ? "planning" : "agent"}-${parsedIssueId.normalized}`;
|
|
1464
|
+
const command = `/pan ${argv.join(" ")}`;
|
|
1465
|
+
const activityId = `activity-${(dependencies.now ?? Date.now)()}`;
|
|
1466
|
+
const emitActivity = dependencies.emitActivity ?? emitActivityEntryDurable;
|
|
1467
|
+
const source = role === "plan" ? "planning-agent" : "work-agent";
|
|
1468
|
+
const emitTransition = (status, level, message, output) => emitActivity({
|
|
1469
|
+
id: activityId,
|
|
1470
|
+
source,
|
|
1471
|
+
level,
|
|
1472
|
+
status,
|
|
1473
|
+
command,
|
|
1474
|
+
message,
|
|
1475
|
+
details: output,
|
|
1476
|
+
output,
|
|
1477
|
+
issueId
|
|
1478
|
+
});
|
|
1479
|
+
await emitTransition("accepted", "info", `Accepted ${command} for ${issueId} as activity ${activityId}.`);
|
|
1480
|
+
let launch;
|
|
1481
|
+
try {
|
|
1482
|
+
launch = await launchPanCommandDetached({
|
|
1483
|
+
agentSessionName,
|
|
1484
|
+
issueId,
|
|
1485
|
+
role,
|
|
1486
|
+
workspacePath: process.cwd(),
|
|
1487
|
+
args: argv,
|
|
1488
|
+
activityId
|
|
1489
|
+
}, dependencies);
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
const output = error instanceof Error ? error.message : String(error);
|
|
1492
|
+
await emitTransition("failed", "error", `Activity ${activityId} failed to start ${command} for ${issueId}.`, output).catch(() => void 0);
|
|
1493
|
+
throw error;
|
|
1066
1494
|
}
|
|
1495
|
+
await emitTransition("running", "info", `Activity ${activityId} is running ${command} for ${issueId}.`).catch(() => void 0);
|
|
1496
|
+
launch.completion.then(async () => {
|
|
1497
|
+
const output = await readBoundedSpawnOutput(launch.spawnLogPath, launch.spawnLogStart).catch(() => "");
|
|
1498
|
+
await emitTransition("completed", "success", `Activity ${activityId} completed ${command} for ${issueId}.`, output || `Command ${command} completed without output.`);
|
|
1499
|
+
}, async (error) => {
|
|
1500
|
+
const activityError = error;
|
|
1501
|
+
const output = activityError.output?.trim() || activityError.message;
|
|
1502
|
+
await emitTransition("failed", "error", `Activity ${activityId} failed ${command} for ${issueId}.`, output);
|
|
1503
|
+
}).catch(() => void 0);
|
|
1504
|
+
return {
|
|
1505
|
+
kind: "activity",
|
|
1506
|
+
status: "accepted",
|
|
1507
|
+
command,
|
|
1508
|
+
activityId: launch.activityId,
|
|
1509
|
+
message: `Started ${command} for ${issueId}. Watch activity ${launch.activityId} or the ${issueId} issue view for progress.`
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
//#endregion
|
|
1513
|
+
//#region routes/agents/shared.ts
|
|
1514
|
+
init_paths();
|
|
1515
|
+
init_agents$1();
|
|
1516
|
+
init_activity_logger();
|
|
1517
|
+
init_tmux();
|
|
1518
|
+
const execAsync$2 = promisify(exec);
|
|
1519
|
+
const execFileAsync$2 = promisify(execFile);
|
|
1520
|
+
function buildPanStartArgs(input) {
|
|
1521
|
+
return [
|
|
1522
|
+
"start",
|
|
1523
|
+
input.issueId,
|
|
1524
|
+
"--local",
|
|
1525
|
+
"--model",
|
|
1526
|
+
input.model,
|
|
1527
|
+
...input.harness ? ["--harness", input.harness] : [],
|
|
1528
|
+
...input.allowHost ? ["--host", "--yes"] : [],
|
|
1529
|
+
...input.offBook ? ["--off-book"] : []
|
|
1530
|
+
];
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* PAN-1985: detached-spawn helper for `pan <args>`, shared between the
|
|
1534
|
+
* standard work-spawn route and the restart-fresh route. The neutral launcher
|
|
1535
|
+
* owns process/log lifecycle; this route-compatible wrapper preserves the
|
|
1536
|
+
* original behavior by waiting for a successful child exit.
|
|
1537
|
+
*/
|
|
1538
|
+
async function spawnPanCommandDetached(input, dependencies = {}) {
|
|
1539
|
+
const launch = await launchPanCommandDetached(input, dependencies);
|
|
1540
|
+
await launch.completion;
|
|
1541
|
+
return launch.activityId;
|
|
1067
1542
|
}
|
|
1068
1543
|
function emitStartAgentPhase(issueId, phase, status, reason, details = {}) {
|
|
1069
1544
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -1083,7 +1558,7 @@ function emitStartAgentPhase(issueId, phase, status, reason, details = {}) {
|
|
|
1083
1558
|
});
|
|
1084
1559
|
}
|
|
1085
1560
|
const INTERNAL_TOKEN_HEADER = "x-overdeck-internal-token";
|
|
1086
|
-
function constantTimeTokenEqual
|
|
1561
|
+
function constantTimeTokenEqual(provided, expected) {
|
|
1087
1562
|
if (!provided) return false;
|
|
1088
1563
|
const providedBuffer = Buffer.from(provided, "utf8");
|
|
1089
1564
|
const expectedBuffer = Buffer.from(expected, "utf8");
|
|
@@ -1109,7 +1584,7 @@ async function validateAgentRuntimeEventAuth(request) {
|
|
|
1109
1584
|
error: "internal token not configured"
|
|
1110
1585
|
}, { status: 503 })
|
|
1111
1586
|
};
|
|
1112
|
-
if (constantTimeTokenEqual
|
|
1587
|
+
if (constantTimeTokenEqual(getHeaderFromMap(request.headers, INTERNAL_TOKEN_HEADER), expected)) return { ok: true };
|
|
1113
1588
|
return {
|
|
1114
1589
|
ok: false,
|
|
1115
1590
|
response: jsonResponse({
|
|
@@ -1118,16 +1593,6 @@ async function validateAgentRuntimeEventAuth(request) {
|
|
|
1118
1593
|
}, { status: 403 })
|
|
1119
1594
|
};
|
|
1120
1595
|
}
|
|
1121
|
-
async function appendAgentLifecycleLog(agentId, event, details = {}) {
|
|
1122
|
-
const agentDir = join(homedir(), ".overdeck", "agents", agentId);
|
|
1123
|
-
await mkdir(agentDir, { recursive: true });
|
|
1124
|
-
const logLine = JSON.stringify({
|
|
1125
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1126
|
-
event,
|
|
1127
|
-
...details
|
|
1128
|
-
});
|
|
1129
|
-
await appendFile(join(agentDir, "lifecycle.log"), logLine + "\n");
|
|
1130
|
-
}
|
|
1131
1596
|
function updateRegistryForAgentStart(issueId, workspacePath, agentId) {
|
|
1132
1597
|
recordFeatureRegistryLifecycle({
|
|
1133
1598
|
issueId,
|
|
@@ -1311,13 +1776,14 @@ async function getWorkspaceLocation(issueId) {
|
|
|
1311
1776
|
}
|
|
1312
1777
|
} catch {}
|
|
1313
1778
|
}
|
|
1314
|
-
async function getGitStatusAsync(workspacePath) {
|
|
1779
|
+
async function getGitStatusAsync(issueId, workspacePath) {
|
|
1315
1780
|
try {
|
|
1316
1781
|
if (!existsSync(workspacePath)) return null;
|
|
1782
|
+
const repoDir = resolvePrimaryWorkspaceRepoDirSync(issueId, workspacePath);
|
|
1317
1783
|
const [branchResult, uncommittedResult, commitResult] = await Promise.all([
|
|
1318
|
-
execAsync$2("git rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"\"", { cwd:
|
|
1319
|
-
execAsync$2("git status --porcelain 2>/dev/null | wc -l", { cwd:
|
|
1320
|
-
execAsync$2("git log -1 --pretty=format:\"%s\" 2>/dev/null || echo \"\"", { cwd:
|
|
1784
|
+
execAsync$2("git rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"\"", { cwd: repoDir }),
|
|
1785
|
+
execAsync$2("git status --porcelain 2>/dev/null | wc -l", { cwd: repoDir }),
|
|
1786
|
+
execAsync$2("git log -1 --pretty=format:\"%s\" 2>/dev/null || echo \"\"", { cwd: repoDir })
|
|
1321
1787
|
]);
|
|
1322
1788
|
const branch = branchResult.stdout.trim();
|
|
1323
1789
|
const uncommitted = uncommittedResult.stdout.trim();
|
|
@@ -1474,6 +1940,22 @@ const getAgentPendingQuestions = getAgentPendingQuestions$1;
|
|
|
1474
1940
|
//#region routes/agents/listing.ts
|
|
1475
1941
|
init_agents$1();
|
|
1476
1942
|
init_tmux();
|
|
1943
|
+
/**
|
|
1944
|
+
* PAN-3070 — the health status a live agent row reports.
|
|
1945
|
+
*
|
|
1946
|
+
* A live tmux session used to mean `healthy` unconditionally, so an agent frozen
|
|
1947
|
+
* for hours on an unanswered tool-permission prompt was reported
|
|
1948
|
+
* `status: healthy` / `resolution: working` while the Decisions surface
|
|
1949
|
+
* simultaneously showed it as needing the operator. Two surfaces answering "is
|
|
1950
|
+
* this agent stuck?" from different evidence, and this one stale.
|
|
1951
|
+
*
|
|
1952
|
+
* `warning` is the value `src/lib/health.ts` already reports for an agent
|
|
1953
|
+
* waiting on a human, and it stays inside the fleet-view status set, so a
|
|
1954
|
+
* blocked agent is flagged without disappearing from any existing list.
|
|
1955
|
+
*/
|
|
1956
|
+
function liveAgentHealthStatus(enrichment) {
|
|
1957
|
+
return isBlockedOnPendingInput(enrichment) ? "warning" : "healthy";
|
|
1958
|
+
}
|
|
1477
1959
|
const getAgentsRoute = HttpRouter.add("GET", "/api/agents", httpHandler(Effect.gen(function* () {
|
|
1478
1960
|
const now = Date.now();
|
|
1479
1961
|
if (agentsCache.data && now - agentsCache.timestamp < 5e3) return jsonResponse(agentsCache.data);
|
|
@@ -1607,7 +2089,7 @@ const getAgentsRoute = HttpRouter.add("GET", "/api/agents", httpHandler(Effect.g
|
|
|
1607
2089
|
const enrichment = await Effect.runPromise(computeAgentEnrichment(name, startedAt, hasActiveSpecialist));
|
|
1608
2090
|
const workspaceLocation = isRemote ? "remote" : await getWorkspaceLocation(issueId);
|
|
1609
2091
|
const workspace = isRemote && remoteState.vmName ? `/workspace (${String(remoteState.vmName)})` : state.workspace || null;
|
|
1610
|
-
const gitStatus = workspace && !isRemote ? await getGitStatusAsync(workspace) : null;
|
|
2092
|
+
const gitStatus = workspace && !isRemote ? await getGitStatusAsync(issueId, workspace) : null;
|
|
1611
2093
|
let contextPercent = null;
|
|
1612
2094
|
let initialContextPercent = null;
|
|
1613
2095
|
const agentCtxDir = join(homedir(), ".overdeck", "agents", name);
|
|
@@ -1617,12 +2099,13 @@ const getAgentsRoute = HttpRouter.add("GET", "/api/agents", httpHandler(Effect.g
|
|
|
1617
2099
|
const initCtxFile = join(agentCtxDir, "initial-context-pct");
|
|
1618
2100
|
initialContextPercent = parseInt((await readFile(initCtxFile, "utf-8").catch(() => "")).trim(), 10) || null;
|
|
1619
2101
|
} catch {}
|
|
2102
|
+
const blockedOnPendingInput = isBlockedOnPendingInput(enrichment);
|
|
1620
2103
|
return {
|
|
1621
2104
|
id: name,
|
|
1622
2105
|
issueId,
|
|
1623
2106
|
runtime: state.harness ?? "claude-code",
|
|
1624
2107
|
model: state.model || (isPlanning ? "opus" : "sonnet"),
|
|
1625
|
-
status:
|
|
2108
|
+
status: liveAgentHealthStatus(enrichment),
|
|
1626
2109
|
startedAt,
|
|
1627
2110
|
...buildAgentGateFailureSnapshot(state),
|
|
1628
2111
|
killCount: health.killCount || 0,
|
|
@@ -1639,7 +2122,7 @@ const getAgentsRoute = HttpRouter.add("GET", "/api/agents", httpHandler(Effect.g
|
|
|
1639
2122
|
pendingInputCount: enrichment.pendingInputCount,
|
|
1640
2123
|
pendingInputKinds: enrichment.pendingInputKinds,
|
|
1641
2124
|
pendingAskUserQuestion: enrichment.pendingAskUserQuestion,
|
|
1642
|
-
resolution: runtimeData?.resolution || enrichment.resolution || "working",
|
|
2125
|
+
resolution: blockedOnPendingInput ? "needs_input" : runtimeData?.resolution || enrichment.resolution || "working",
|
|
1643
2126
|
resolutionCount: runtimeData?.resolutionCount || enrichment.resolutionCount || 0,
|
|
1644
2127
|
contextPercent,
|
|
1645
2128
|
initialContextPercent,
|
|
@@ -1725,7 +2208,7 @@ const getAgentOutputRoute = HttpRouter.add("GET", "/api/agents/:id/output", http
|
|
|
1725
2208
|
} catch {}
|
|
1726
2209
|
let stdout;
|
|
1727
2210
|
if (isRemote && vmName) {
|
|
1728
|
-
const { getRemoteAgentOutput } = await import("./remote-agents-
|
|
2211
|
+
const { getRemoteAgentOutput } = await import("./remote-agents-C1bssi41.js");
|
|
1729
2212
|
stdout = await getRemoteAgentOutput(id, vmName, parseInt(String(lines), 10) || 100);
|
|
1730
2213
|
} else stdout = await Effect.runPromise(capturePane(id, parseInt(String(lines), 10) || 100));
|
|
1731
2214
|
if (!stdout || stdout.trim() === "" || stdout.trim() === "Session not found") {
|
|
@@ -1790,6 +2273,20 @@ async function buildConversationResponse(id) {
|
|
|
1790
2273
|
streaming: false
|
|
1791
2274
|
};
|
|
1792
2275
|
}
|
|
2276
|
+
if (harness === "acp") {
|
|
2277
|
+
const sessionFile = await resolveAcpTranscriptPath(id);
|
|
2278
|
+
if (!sessionFile || !existsSync(sessionFile)) return EMPTY_CONVERSATION;
|
|
2279
|
+
const result = await parseAcpConversationMessages(sessionFile);
|
|
2280
|
+
return {
|
|
2281
|
+
...result,
|
|
2282
|
+
messages: result.messages.map((message) => message.role === "assistant" ? {
|
|
2283
|
+
...message,
|
|
2284
|
+
completedAt: message.completedAt ?? message.createdAt,
|
|
2285
|
+
streaming: false
|
|
2286
|
+
} : message),
|
|
2287
|
+
streaming: false
|
|
2288
|
+
};
|
|
2289
|
+
}
|
|
1793
2290
|
let jsonlPath = null;
|
|
1794
2291
|
const pinnedSessionId = await readLauncherPinnedSessionId(id);
|
|
1795
2292
|
if (pinnedSessionId) {
|
|
@@ -1864,6 +2361,352 @@ const getAgentTimelineRoute = HttpRouter.add("GET", "/api/agents/:id/timeline",
|
|
|
1864
2361
|
return jsonResponse({ timeline: events.slice(0, limit) });
|
|
1865
2362
|
})));
|
|
1866
2363
|
//#endregion
|
|
2364
|
+
//#region ../../lib/composer-commands/confirmations.ts
|
|
2365
|
+
const COMPOSER_CONFIRMATION_TTL_MS = 300 * 1e3;
|
|
2366
|
+
function composerCommandConfirmationFromBody(body) {
|
|
2367
|
+
if (!("confirmationNonce" in body) && !("confirmationText" in body)) return void 0;
|
|
2368
|
+
return {
|
|
2369
|
+
nonce: body["confirmationNonce"],
|
|
2370
|
+
typedText: body["confirmationText"]
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
var ComposerCommandConfirmationError = class extends Error {
|
|
2374
|
+
code = "invalid-confirmation";
|
|
2375
|
+
constructor(message) {
|
|
2376
|
+
super(message);
|
|
2377
|
+
this.name = "ComposerCommandConfirmationError";
|
|
2378
|
+
}
|
|
2379
|
+
};
|
|
2380
|
+
function createComposerConfirmationStore(dependencies = {}) {
|
|
2381
|
+
const entries = /* @__PURE__ */ new Map();
|
|
2382
|
+
const now = dependencies.now ?? Date.now;
|
|
2383
|
+
const createNonce = dependencies.createNonce ?? (() => randomBytes(24).toString("base64url"));
|
|
2384
|
+
function pruneExpired() {
|
|
2385
|
+
const currentTime = now();
|
|
2386
|
+
for (const [nonce, entry] of entries) if (entry.expiresAt <= currentTime) entries.delete(nonce);
|
|
2387
|
+
}
|
|
2388
|
+
return {
|
|
2389
|
+
issue({ target, argv, policy, display }) {
|
|
2390
|
+
if (policy.safety === "destructive" && !policy.typedConfirmation) throw new Error(`Destructive composer policy for ${display} must define typedConfirmation.`);
|
|
2391
|
+
pruneExpired();
|
|
2392
|
+
const nonce = createNonce();
|
|
2393
|
+
entries.set(nonce, {
|
|
2394
|
+
targetKind: target.kind,
|
|
2395
|
+
targetId: target.id,
|
|
2396
|
+
argv: [...argv],
|
|
2397
|
+
safety: policy.safety,
|
|
2398
|
+
typedText: policy.typedConfirmation,
|
|
2399
|
+
expiresAt: now() + COMPOSER_CONFIRMATION_TTL_MS
|
|
2400
|
+
});
|
|
2401
|
+
return {
|
|
2402
|
+
kind: "confirmation",
|
|
2403
|
+
status: "confirmation_required",
|
|
2404
|
+
nonce,
|
|
2405
|
+
consequence: policy.confirmationText ?? `Running ${display} requires confirmation before Overdeck continues.`,
|
|
2406
|
+
...policy.typedConfirmation ? { typedText: policy.typedConfirmation } : {}
|
|
2407
|
+
};
|
|
2408
|
+
},
|
|
2409
|
+
consume({ target, argv, policy, confirmation }) {
|
|
2410
|
+
if (typeof confirmation.nonce !== "string") throw new ComposerCommandConfirmationError("A valid confirmation nonce is required before this command can run.");
|
|
2411
|
+
const entry = entries.get(confirmation.nonce);
|
|
2412
|
+
entries.delete(confirmation.nonce);
|
|
2413
|
+
if (!entry) throw new ComposerCommandConfirmationError("This command confirmation is missing, invalid, or has already been used. Request a new confirmation and try again.");
|
|
2414
|
+
if (entry.expiresAt <= now()) throw new ComposerCommandConfirmationError("This command confirmation has expired. Request a new confirmation and try again.");
|
|
2415
|
+
if (entry.targetKind !== target.kind || entry.targetId !== target.id || entry.safety !== policy.safety || !sameArgv(entry.argv, argv)) throw new ComposerCommandConfirmationError("This command confirmation does not match the current target or command. Request a new confirmation and try again.");
|
|
2416
|
+
if (policy.safety === "destructive" && (typeof confirmation.typedText !== "string" || confirmation.typedText !== entry.typedText)) throw new ComposerCommandConfirmationError(`Type "${entry.typedText ?? ""}" exactly to confirm this destructive command.`);
|
|
2417
|
+
}
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
function sameArgv(left, right) {
|
|
2421
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
2422
|
+
}
|
|
2423
|
+
const composerConfirmationStore = createComposerConfirmationStore();
|
|
2424
|
+
//#endregion
|
|
2425
|
+
//#region ../../lib/composer-commands/parser.ts
|
|
2426
|
+
var ComposerCommandParseError = class extends Error {
|
|
2427
|
+
constructor(code, message, token, expected) {
|
|
2428
|
+
super(message);
|
|
2429
|
+
this.code = code;
|
|
2430
|
+
this.token = token;
|
|
2431
|
+
this.expected = expected;
|
|
2432
|
+
this.name = "ComposerCommandParseError";
|
|
2433
|
+
}
|
|
2434
|
+
};
|
|
2435
|
+
const FORBIDDEN_TOKEN = /\$\(|[;|&<>`]/;
|
|
2436
|
+
function expectedSyntax(entry) {
|
|
2437
|
+
const args = entry.args.map((argument) => {
|
|
2438
|
+
const name = argument.variadic ? `${argument.name}...` : argument.name;
|
|
2439
|
+
return argument.required ? `<${name}>` : `[${name}]`;
|
|
2440
|
+
});
|
|
2441
|
+
const options = entry.options.length > 0 ? ["[options]"] : [];
|
|
2442
|
+
return [
|
|
2443
|
+
entry.display,
|
|
2444
|
+
...args,
|
|
2445
|
+
...options
|
|
2446
|
+
].join(" ");
|
|
2447
|
+
}
|
|
2448
|
+
function tokenize(input) {
|
|
2449
|
+
const tokens = [];
|
|
2450
|
+
let current = "";
|
|
2451
|
+
let quote = null;
|
|
2452
|
+
let escaped = false;
|
|
2453
|
+
const pushCurrent = () => {
|
|
2454
|
+
if (current.length === 0) return;
|
|
2455
|
+
tokens.push(current);
|
|
2456
|
+
current = "";
|
|
2457
|
+
};
|
|
2458
|
+
for (const character of input) {
|
|
2459
|
+
if (escaped) {
|
|
2460
|
+
current += character;
|
|
2461
|
+
escaped = false;
|
|
2462
|
+
continue;
|
|
2463
|
+
}
|
|
2464
|
+
if (character === "\\") {
|
|
2465
|
+
escaped = true;
|
|
2466
|
+
continue;
|
|
2467
|
+
}
|
|
2468
|
+
if (quote !== null) {
|
|
2469
|
+
if (character === quote) quote = null;
|
|
2470
|
+
else current += character;
|
|
2471
|
+
continue;
|
|
2472
|
+
}
|
|
2473
|
+
if (character === "\"" || character === "'") {
|
|
2474
|
+
quote = character;
|
|
2475
|
+
continue;
|
|
2476
|
+
}
|
|
2477
|
+
if (/\s/.test(character)) {
|
|
2478
|
+
pushCurrent();
|
|
2479
|
+
continue;
|
|
2480
|
+
}
|
|
2481
|
+
current += character;
|
|
2482
|
+
}
|
|
2483
|
+
if (escaped) current += "\\";
|
|
2484
|
+
if (quote !== null) throw new ComposerCommandParseError("missing-argument", `Unclosed ${quote} quote. Close the quoted value before submitting the command.`, quote, `a closing ${quote}`);
|
|
2485
|
+
pushCurrent();
|
|
2486
|
+
return tokens;
|
|
2487
|
+
}
|
|
2488
|
+
function aliasesForPath(entry) {
|
|
2489
|
+
let candidates = [[]];
|
|
2490
|
+
for (let index = 0; index < entry.path.length; index += 1) {
|
|
2491
|
+
const prefix = entry.path.slice(0, index + 1);
|
|
2492
|
+
const prefixEntry = COMPOSER_COMMAND_MANIFEST.find((candidate) => candidate.path.length === prefix.length && candidate.path.every((part, partIndex) => part === prefix[partIndex]));
|
|
2493
|
+
const names = [entry.path[index], ...prefixEntry?.aliases ?? []];
|
|
2494
|
+
candidates = candidates.flatMap((candidate) => names.map((name) => [...candidate, name]));
|
|
2495
|
+
}
|
|
2496
|
+
return candidates;
|
|
2497
|
+
}
|
|
2498
|
+
function resolveEntry(tokens) {
|
|
2499
|
+
const match = COMPOSER_COMMAND_MANIFEST.flatMap((entry) => aliasesForPath(entry).filter((path) => path.every((part, index) => tokens[index] === part)).map((path) => ({
|
|
2500
|
+
entry,
|
|
2501
|
+
pathLength: path.length
|
|
2502
|
+
}))).sort((left, right) => right.pathLength - left.pathLength)[0];
|
|
2503
|
+
if (match) return match;
|
|
2504
|
+
const token = tokens[0] ?? "";
|
|
2505
|
+
throw new ComposerCommandParseError("unknown-command", token ? `Unknown Overdeck command "${token}". Type /pan to list available commands.` : "Missing an Overdeck command after /pan. Type /pan to list available commands.", token, "/pan <command>");
|
|
2506
|
+
}
|
|
2507
|
+
function optionDefinitions(entry) {
|
|
2508
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
2509
|
+
for (const option of entry.options) {
|
|
2510
|
+
const flags = option.flags.split(/[ ,|]+/).filter((part) => part.startsWith("-"));
|
|
2511
|
+
const canonicalFlag = flags.find((flag) => flag.startsWith("--")) ?? flags[0];
|
|
2512
|
+
for (const flag of flags) definitions.set(flag, {
|
|
2513
|
+
option,
|
|
2514
|
+
canonicalFlag
|
|
2515
|
+
});
|
|
2516
|
+
}
|
|
2517
|
+
return definitions;
|
|
2518
|
+
}
|
|
2519
|
+
function splitFlag(token) {
|
|
2520
|
+
const separator = token.indexOf("=");
|
|
2521
|
+
if (separator === -1) return { flag: token };
|
|
2522
|
+
return {
|
|
2523
|
+
flag: token.slice(0, separator),
|
|
2524
|
+
inlineValue: token.slice(separator + 1)
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2527
|
+
function parseOverdeckComposerCommand(message) {
|
|
2528
|
+
if (!/^\/pan(?:\s|$)/.test(message)) return null;
|
|
2529
|
+
const forbidden = message.match(FORBIDDEN_TOKEN)?.[0];
|
|
2530
|
+
if (forbidden !== void 0) throw new ComposerCommandParseError("forbidden-token", `The token "${forbidden}" is not allowed in /pan commands. Run compound shell syntax in a terminal instead.`, forbidden, "plain arguments without shell operators");
|
|
2531
|
+
const tokens = tokenize(message.slice(4).trim());
|
|
2532
|
+
const { entry, pathLength } = resolveEntry(tokens);
|
|
2533
|
+
const remaining = tokens.slice(pathLength);
|
|
2534
|
+
const definitions = optionDefinitions(entry);
|
|
2535
|
+
const positionals = [];
|
|
2536
|
+
const flags = [];
|
|
2537
|
+
for (let index = 0; index < remaining.length; index += 1) {
|
|
2538
|
+
const token = remaining[index];
|
|
2539
|
+
if (!token.startsWith("-")) {
|
|
2540
|
+
positionals.push(token);
|
|
2541
|
+
continue;
|
|
2542
|
+
}
|
|
2543
|
+
const { flag, inlineValue } = splitFlag(token);
|
|
2544
|
+
const definition = definitions.get(flag);
|
|
2545
|
+
if (definition === void 0) throw new ComposerCommandParseError("unknown-flag", `Unknown flag "${flag}" for ${entry.display}. Expected syntax: ${expectedSyntax(entry)}.`, flag, expectedSyntax(entry));
|
|
2546
|
+
flags.push(definition.canonicalFlag);
|
|
2547
|
+
if (definition.option.valueHint === null) {
|
|
2548
|
+
if (inlineValue !== void 0) throw new ComposerCommandParseError("unknown-flag", `Flag "${flag}" does not accept a value. Expected syntax: ${expectedSyntax(entry)}.`, token, expectedSyntax(entry));
|
|
2549
|
+
continue;
|
|
2550
|
+
}
|
|
2551
|
+
if (inlineValue !== void 0) {
|
|
2552
|
+
if (inlineValue.length === 0 && definition.option.required) throw new ComposerCommandParseError("missing-argument", `Flag "${flag}" requires <${definition.option.valueHint}>. Expected syntax: ${expectedSyntax(entry)}.`, flag, expectedSyntax(entry));
|
|
2553
|
+
if (inlineValue.length > 0) flags.push(inlineValue);
|
|
2554
|
+
continue;
|
|
2555
|
+
}
|
|
2556
|
+
const next = remaining[index + 1];
|
|
2557
|
+
const nextIsFlag = next?.startsWith("-") === true;
|
|
2558
|
+
if (definition.option.required && (next === void 0 || nextIsFlag)) throw new ComposerCommandParseError("missing-argument", `Flag "${flag}" requires <${definition.option.valueHint}>. Expected syntax: ${expectedSyntax(entry)}.`, flag, expectedSyntax(entry));
|
|
2559
|
+
if (next !== void 0 && !nextIsFlag) {
|
|
2560
|
+
flags.push(next);
|
|
2561
|
+
index += 1;
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
if (!(entry.args.at(-1)?.variadic === true) && positionals.length > entry.args.length) {
|
|
2565
|
+
const token = positionals[entry.args.length];
|
|
2566
|
+
throw new ComposerCommandParseError("unknown-command", `Unexpected token "${token}" for ${entry.display}. Expected syntax: ${expectedSyntax(entry)}.`, token, expectedSyntax(entry));
|
|
2567
|
+
}
|
|
2568
|
+
const missing = entry.args.find((argument, index) => argument.required && positionals[index] === void 0);
|
|
2569
|
+
if (missing !== void 0) throw new ComposerCommandParseError("missing-argument", `Missing required argument <${missing.name}> for ${entry.display}. Expected syntax: ${expectedSyntax(entry)}.`, missing.name, expectedSyntax(entry));
|
|
2570
|
+
return {
|
|
2571
|
+
entry,
|
|
2572
|
+
argv: [
|
|
2573
|
+
...entry.path,
|
|
2574
|
+
...positionals,
|
|
2575
|
+
...flags
|
|
2576
|
+
]
|
|
2577
|
+
};
|
|
2578
|
+
}
|
|
2579
|
+
//#endregion
|
|
2580
|
+
//#region ../../lib/composer-commands/policy.ts
|
|
2581
|
+
init_src();
|
|
2582
|
+
const COMPOSER_COMMAND_POLICIES = {
|
|
2583
|
+
start: {
|
|
2584
|
+
mode: "detached",
|
|
2585
|
+
safety: "safe"
|
|
2586
|
+
},
|
|
2587
|
+
plan: {
|
|
2588
|
+
mode: "detached",
|
|
2589
|
+
safety: "safe"
|
|
2590
|
+
},
|
|
2591
|
+
show: {
|
|
2592
|
+
mode: "captured",
|
|
2593
|
+
safety: "safe"
|
|
2594
|
+
},
|
|
2595
|
+
status: {
|
|
2596
|
+
mode: "captured",
|
|
2597
|
+
safety: "safe"
|
|
2598
|
+
},
|
|
2599
|
+
tell: {
|
|
2600
|
+
mode: "captured",
|
|
2601
|
+
safety: "safe"
|
|
2602
|
+
},
|
|
2603
|
+
handoff: {
|
|
2604
|
+
mode: "ui",
|
|
2605
|
+
safety: "dialog",
|
|
2606
|
+
uiAction: "handoff",
|
|
2607
|
+
confirmationText: "This opens the handoff dialog for the selected conversation. Review the destination and focus before continuing."
|
|
2608
|
+
},
|
|
2609
|
+
fork: {
|
|
2610
|
+
mode: "ui",
|
|
2611
|
+
safety: "dialog",
|
|
2612
|
+
uiAction: "fork",
|
|
2613
|
+
confirmationText: "This opens the fork dialog for the selected conversation. Review the fork mode and destination before continuing."
|
|
2614
|
+
}
|
|
2615
|
+
};
|
|
2616
|
+
const DEFAULT_POLICY = {
|
|
2617
|
+
mode: "terminal-only",
|
|
2618
|
+
safety: "safe"
|
|
2619
|
+
};
|
|
2620
|
+
function assertPolicyOverlayIntegrity(policies = COMPOSER_COMMAND_POLICIES, manifest = COMPOSER_COMMAND_MANIFEST) {
|
|
2621
|
+
for (const path of Object.keys(policies)) {
|
|
2622
|
+
const matches = manifest.filter((entry) => entry.path.join(" ") === path);
|
|
2623
|
+
if (matches.length !== 1) throw new Error(`Composer command policy path "${path}" must match exactly one manifest entry; found ${matches.length}.`);
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
function resolvePolicy(path) {
|
|
2627
|
+
return COMPOSER_COMMAND_POLICIES[path.join(" ")] ?? DEFAULT_POLICY;
|
|
2628
|
+
}
|
|
2629
|
+
assertPolicyOverlayIntegrity();
|
|
2630
|
+
//#endregion
|
|
2631
|
+
//#region ../../lib/composer-commands/router.ts
|
|
2632
|
+
const UI_COMMAND_PATTERNS = [{
|
|
2633
|
+
action: "handoff",
|
|
2634
|
+
pattern: /^\/(?:handoff|pan-handoff|pan\s+handoff)(?:\s+([\s\S]+))?$/i
|
|
2635
|
+
}, {
|
|
2636
|
+
action: "fork",
|
|
2637
|
+
pattern: /^\/pan\s+fork(?:\s+([\s\S]+))?$/i
|
|
2638
|
+
}];
|
|
2639
|
+
function isComposerCommandMessage(message) {
|
|
2640
|
+
return UI_COMMAND_PATTERNS.some(({ pattern }) => pattern.test(message)) || /^\/pan(?:\s|$)/.test(message);
|
|
2641
|
+
}
|
|
2642
|
+
async function handleComposerCommandMessage({ message, target, confirmation }, dependencies = {}) {
|
|
2643
|
+
for (const { action, pattern } of UI_COMMAND_PATTERNS) {
|
|
2644
|
+
const match = message.match(pattern);
|
|
2645
|
+
if (match) {
|
|
2646
|
+
const parsed = parseOverdeckComposerCommand(`/pan ${action}`);
|
|
2647
|
+
const focus = match[1]?.trim();
|
|
2648
|
+
if (focus) parsed.argv.push(focus);
|
|
2649
|
+
return handleComposerCommand({
|
|
2650
|
+
parsed,
|
|
2651
|
+
target,
|
|
2652
|
+
confirmation
|
|
2653
|
+
}, dependencies);
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
const parsed = parseOverdeckComposerCommand(message);
|
|
2657
|
+
if (parsed === null) return null;
|
|
2658
|
+
return handleComposerCommand({
|
|
2659
|
+
parsed,
|
|
2660
|
+
target,
|
|
2661
|
+
confirmation
|
|
2662
|
+
}, dependencies);
|
|
2663
|
+
}
|
|
2664
|
+
async function handleComposerCommand({ parsed, target, confirmation }, dependencies = {}) {
|
|
2665
|
+
const policy = (dependencies.resolvePolicy ?? resolvePolicy)(parsed.entry.path);
|
|
2666
|
+
if (policy.safety !== "safe") {
|
|
2667
|
+
const confirmationStore = dependencies.confirmationStore ?? composerConfirmationStore;
|
|
2668
|
+
if (confirmation === void 0) return confirmationStore.issue({
|
|
2669
|
+
target,
|
|
2670
|
+
argv: parsed.argv,
|
|
2671
|
+
policy,
|
|
2672
|
+
display: parsed.entry.display
|
|
2673
|
+
});
|
|
2674
|
+
confirmationStore.consume({
|
|
2675
|
+
target,
|
|
2676
|
+
argv: parsed.argv,
|
|
2677
|
+
policy,
|
|
2678
|
+
confirmation
|
|
2679
|
+
});
|
|
2680
|
+
}
|
|
2681
|
+
if (policy.mode === "captured") return runCapturedCommand(parsed.argv);
|
|
2682
|
+
if (policy.mode === "detached") return runDetachedCommand(parsed.argv);
|
|
2683
|
+
if (policy.mode === "ui" && policy.uiAction) return uiCommandResult(policy.uiAction, parsed.argv.slice(parsed.entry.path.length).join(" ") || void 0);
|
|
2684
|
+
if (policy.mode === "terminal-only") return {
|
|
2685
|
+
kind: "terminal-only",
|
|
2686
|
+
status: "rejected",
|
|
2687
|
+
message: `${parsed.entry.display} must run in a terminal. Composer execution has not been enabled for this command.`
|
|
2688
|
+
};
|
|
2689
|
+
return missingExecutorResult(parsed.entry.display, policy);
|
|
2690
|
+
}
|
|
2691
|
+
function uiCommandResult(action, focus) {
|
|
2692
|
+
return {
|
|
2693
|
+
kind: "ui",
|
|
2694
|
+
status: "requires_ui",
|
|
2695
|
+
action,
|
|
2696
|
+
args: focus ? { focus } : {}
|
|
2697
|
+
};
|
|
2698
|
+
}
|
|
2699
|
+
function missingExecutorResult(display, policy) {
|
|
2700
|
+
return {
|
|
2701
|
+
kind: "terminal-only",
|
|
2702
|
+
status: "rejected",
|
|
2703
|
+
message: `${display} is recognized as a ${policy.mode} command, but its composer executor is not registered yet. Run it in a terminal for now.`
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2706
|
+
function composerCommandResultHttpStatus(result) {
|
|
2707
|
+
return result.kind === "terminal-only" ? 422 : 200;
|
|
2708
|
+
}
|
|
2709
|
+
//#endregion
|
|
1867
2710
|
//#region routes/agents/messaging.ts
|
|
1868
2711
|
init_agents$1();
|
|
1869
2712
|
async function sendAgentMessage(id, message) {
|
|
@@ -1879,6 +2722,55 @@ async function sendAgentMessage(id, message) {
|
|
|
1879
2722
|
remote: true
|
|
1880
2723
|
} : { success: true };
|
|
1881
2724
|
}
|
|
2725
|
+
async function handleAgentMessage(id, message, confirmation) {
|
|
2726
|
+
try {
|
|
2727
|
+
if (isComposerCommandMessage(message)) {
|
|
2728
|
+
let agentState;
|
|
2729
|
+
try {
|
|
2730
|
+
agentState = await Effect.runPromise(getAgentState(id));
|
|
2731
|
+
} catch {
|
|
2732
|
+
return jsonResponse({
|
|
2733
|
+
error: `Failed to resolve agent target: ${id}`,
|
|
2734
|
+
code: "agent-resolution-failed"
|
|
2735
|
+
}, { status: 503 });
|
|
2736
|
+
}
|
|
2737
|
+
if (!agentState) return jsonResponse({
|
|
2738
|
+
error: `Agent not found: ${id}`,
|
|
2739
|
+
code: "agent-not-found"
|
|
2740
|
+
}, { status: 404 });
|
|
2741
|
+
if (!agentState.harness) return jsonResponse({
|
|
2742
|
+
error: `Agent harness could not be resolved: ${id}`,
|
|
2743
|
+
code: "agent-harness-unresolved"
|
|
2744
|
+
}, { status: 503 });
|
|
2745
|
+
const result = await handleComposerCommandMessage({
|
|
2746
|
+
message,
|
|
2747
|
+
confirmation,
|
|
2748
|
+
target: {
|
|
2749
|
+
kind: "agent",
|
|
2750
|
+
id,
|
|
2751
|
+
harness: agentState.harness,
|
|
2752
|
+
cwd: agentState.workspace,
|
|
2753
|
+
issueId: agentState.issueId
|
|
2754
|
+
}
|
|
2755
|
+
});
|
|
2756
|
+
if (result !== null) return jsonResponse(result, { status: composerCommandResultHttpStatus(result) });
|
|
2757
|
+
}
|
|
2758
|
+
} catch (error) {
|
|
2759
|
+
if (error instanceof ComposerCommandConfirmationError) return jsonResponse({
|
|
2760
|
+
error: error.message,
|
|
2761
|
+
code: error.code
|
|
2762
|
+
}, { status: 422 });
|
|
2763
|
+
if (!(error instanceof ComposerCommandParseError)) throw error;
|
|
2764
|
+
const status = error.code === "unknown-command" ? 404 : error.code === "unknown-flag" ? 422 : 400;
|
|
2765
|
+
return jsonResponse({
|
|
2766
|
+
error: error.message,
|
|
2767
|
+
code: error.code,
|
|
2768
|
+
token: error.token,
|
|
2769
|
+
expected: error.expected
|
|
2770
|
+
}, { status });
|
|
2771
|
+
}
|
|
2772
|
+
return jsonResponse(await sendAgentMessage(id, message));
|
|
2773
|
+
}
|
|
1882
2774
|
function validateAgentMessageOrigin(request) {
|
|
1883
2775
|
const originCheck = validateOrigin(request);
|
|
1884
2776
|
if (!originCheck.ok) return {
|
|
@@ -1895,9 +2787,10 @@ function postAgentMessageLikeRoute(path) {
|
|
|
1895
2787
|
const originCheck = validateAgentMessageOrigin(yield* HttpServerRequest.HttpServerRequest);
|
|
1896
2788
|
if (!originCheck.ok) return originCheck.response;
|
|
1897
2789
|
const id = (yield* HttpRouter.params)["id"] ?? "";
|
|
1898
|
-
const
|
|
2790
|
+
const body = yield* readJsonBody$1;
|
|
2791
|
+
const { message } = body;
|
|
1899
2792
|
if (!message) return jsonResponse({ error: "Message required" }, { status: 400 });
|
|
1900
|
-
return yield* Effect.promise(() =>
|
|
2793
|
+
return yield* Effect.promise(() => handleAgentMessage(id, message, composerCommandConfirmationFromBody(body)));
|
|
1901
2794
|
})));
|
|
1902
2795
|
}
|
|
1903
2796
|
const postAgentMessageRoute = postAgentMessageLikeRoute("/api/agents/:id/message");
|
|
@@ -2146,7 +3039,7 @@ async function cleanupConversationAttachments(name) {
|
|
|
2146
3039
|
* sessions that the lifecycle poller missed. */
|
|
2147
3040
|
async function cleanupOrphanedConversationAttachments() {
|
|
2148
3041
|
try {
|
|
2149
|
-
const { listConversations } = await import("./conversations-
|
|
3042
|
+
const { listConversations } = await import("./conversations-C34dyIb9.js");
|
|
2150
3043
|
const convNames = new Set(listConversations().map((c) => c.name));
|
|
2151
3044
|
const root = getConversationAttachmentsRoot();
|
|
2152
3045
|
await runInBatches((await readdir(root, { withFileTypes: true })).filter((entry) => entry.isDirectory() && !convNames.has(entry.name)).map((entry) => join(root, entry.name)), 10, (path) => rm(path, {
|
|
@@ -2424,7 +3317,7 @@ async function stopConversationRuntime(conv, name) {
|
|
|
2424
3317
|
function shellQuote(str) {
|
|
2425
3318
|
return "'" + str.replace(/'/g, "'\"'\"'") + "'";
|
|
2426
3319
|
}
|
|
2427
|
-
const SAFE_MODEL_PATTERN =
|
|
3320
|
+
const SAFE_MODEL_PATTERN = MODEL_ID_PATTERN;
|
|
2428
3321
|
const SAFE_EFFORT_PATTERN = /^(low|medium|high)$/;
|
|
2429
3322
|
const SAFE_ISSUE_ID_PATTERN = /^[A-Z0-9]+-[0-9]+$/;
|
|
2430
3323
|
const PI_CONVERSATION_SOURCE_CONTRACT = [
|
|
@@ -2436,7 +3329,7 @@ async function resolveAllowedHarness(requested, model) {
|
|
|
2436
3329
|
if (!model) return "claude-code";
|
|
2437
3330
|
return resolveHarness({
|
|
2438
3331
|
model,
|
|
2439
|
-
explicit: requested === "ohmypi" || requested === "claude-code" || requested === "codex" ? requested : void 0
|
|
3332
|
+
explicit: requested === "ohmypi" || requested === "claude-code" || requested === "codex" || requested === "acp" ? requested : void 0
|
|
2440
3333
|
});
|
|
2441
3334
|
}
|
|
2442
3335
|
async function isInsideGitWorkTree(dir) {
|
|
@@ -2518,6 +3411,10 @@ function shouldUseSupervisorForConversation(harness, options = {}) {
|
|
|
2518
3411
|
return getHarnessBehavior(harness).supportsPtySupervisor && process.env.OVERDECK_DOCKER_WORKSPACE !== "1" && process.env.PAN_DOCKER !== "1";
|
|
2519
3412
|
}
|
|
2520
3413
|
async function waitForConversationRuntimeReady(tmuxSession, harness, mode) {
|
|
3414
|
+
if (harness === "acp") {
|
|
3415
|
+
await waitForAcpHostReady(tmuxSession);
|
|
3416
|
+
return;
|
|
3417
|
+
}
|
|
2521
3418
|
const transcriptKind = getHarnessBehavior(harness).transcriptKind;
|
|
2522
3419
|
if (transcriptKind === "ohmypi-jsonl") {
|
|
2523
3420
|
if (!await waitForPiTuiReady(tmuxSession)) throw new Error(`Pi (ohmypi) did not become interactive in ${tmuxSession} within the startup window — MCP server connections are likely still in progress. Retry once MCP servers finish connecting, or check ~/.claude/mcp.json for a slow/hanging server.`);
|
|
@@ -2533,7 +3430,7 @@ async function handleConversationSwitchModel(name, body) {
|
|
|
2533
3430
|
const currentHarness = conv.harness ?? "claude-code";
|
|
2534
3431
|
const requestedHarness = body["harness"];
|
|
2535
3432
|
let harness = currentHarness;
|
|
2536
|
-
if (requestedHarness === "ohmypi" || requestedHarness === "pi" || requestedHarness === "claude-code" || requestedHarness === "codex") {
|
|
3433
|
+
if (requestedHarness === "ohmypi" || requestedHarness === "pi" || requestedHarness === "claude-code" || requestedHarness === "codex" || requestedHarness === "acp") {
|
|
2537
3434
|
const requestedRuntime = requestedHarness === "pi" ? "ohmypi" : requestedHarness;
|
|
2538
3435
|
if (requestedRuntime !== currentHarness) {
|
|
2539
3436
|
const policyModel = model ?? conv.model ?? "";
|
|
@@ -2567,6 +3464,21 @@ async function handleConversationSwitchModel(name, body) {
|
|
|
2567
3464
|
function getPtySupervisorSocketPath(agentId) {
|
|
2568
3465
|
return join(getOverdeckHome(), "sockets", `pty-${agentId}.sock`);
|
|
2569
3466
|
}
|
|
3467
|
+
/**
|
|
3468
|
+
* Pull the supervisor's own failure out of the tmux pane it died in.
|
|
3469
|
+
*
|
|
3470
|
+
* A supervisor that cannot load its native node-pty exits instantly with an
|
|
3471
|
+
* ERR_MODULE_NOT_FOUND on stderr; all the spawn path sees is the socket that
|
|
3472
|
+
* never appeared. Reporting only the timeout hides the actual cause and sent
|
|
3473
|
+
* an operator hunting the memory governor instead (PAN-3172).
|
|
3474
|
+
*/
|
|
3475
|
+
function extractSupervisorFailure(paneText) {
|
|
3476
|
+
const lines = paneText.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
3477
|
+
const errors = lines.filter((line) => /error|cannot find|ERR_[A-Z_]+|pty-supervisor:/i.test(line));
|
|
3478
|
+
const chosen = (errors.length > 0 ? errors : lines).slice(-3);
|
|
3479
|
+
if (chosen.length === 0) return null;
|
|
3480
|
+
return chosen.join(" | ").slice(0, 500);
|
|
3481
|
+
}
|
|
2570
3482
|
async function waitForPtySupervisorSocket(agentId, timeoutMs = PTY_SUPERVISOR_SOCKET_WAIT_MS) {
|
|
2571
3483
|
const socketPath = getPtySupervisorSocketPath(agentId);
|
|
2572
3484
|
const start = Date.now();
|
|
@@ -2576,7 +3488,8 @@ async function waitForPtySupervisorSocket(agentId, timeoutMs = PTY_SUPERVISOR_SO
|
|
|
2576
3488
|
} catch {}
|
|
2577
3489
|
await new Promise((r) => setTimeout(r, 250));
|
|
2578
3490
|
}
|
|
2579
|
-
|
|
3491
|
+
const failure = extractSupervisorFailure(await capturePaneText(agentId, 40));
|
|
3492
|
+
throw new Error(`Timed out waiting for PTY supervisor socket ${socketPath}` + (failure ? ` — supervisor output: ${failure}` : ""));
|
|
2580
3493
|
}
|
|
2581
3494
|
async function extractModelFromSessionFile(sessionFile) {
|
|
2582
3495
|
try {
|
|
@@ -2686,14 +3599,25 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
|
|
|
2686
3599
|
let providerExportsStr = "";
|
|
2687
3600
|
let piFields;
|
|
2688
3601
|
let codexFields;
|
|
3602
|
+
let acpFields;
|
|
2689
3603
|
let codexTransport;
|
|
2690
|
-
if (
|
|
3604
|
+
if (behavior.launchCommandKind === "acp-host") {
|
|
3605
|
+
if (!model) throw new Error("ACP conversation requires a model");
|
|
3606
|
+
if (!SAFE_MODEL_PATTERN.test(model)) throw new Error("Invalid model name");
|
|
3607
|
+
const sessionIdPath = join(getOverdeckHome(), "agents", tmuxSession, "acp-session-id");
|
|
3608
|
+
const resumeSessionId = resume ? await readFile(sessionIdPath, "utf-8").then((value) => value.trim() || void 0).catch(() => void 0) : void 0;
|
|
3609
|
+
await rm(sessionIdPath, { force: true });
|
|
3610
|
+
acpFields = {
|
|
3611
|
+
...getAcpLauncherFields(tmuxSession, model, cwd, harnessLaunch.binaryPath, "work"),
|
|
3612
|
+
resumeSessionId
|
|
3613
|
+
};
|
|
3614
|
+
runtimeCommand = "acp-host";
|
|
3615
|
+
} else if (model) {
|
|
2691
3616
|
if (!SAFE_MODEL_PATTERN.test(model)) throw new Error("Invalid model name");
|
|
2692
3617
|
runtimeCommand = await getAgentRuntimeBaseCommand(model, void 0, void 0, harness);
|
|
2693
3618
|
const mode = resolvePermissionModeSync();
|
|
2694
3619
|
if (!runtimeCommand.includes("--permission-mode")) runtimeCommand = `${runtimeCommand} --permission-mode ${mode === "auto" ? "auto" : BYPASS_PERMISSION_MODE}`;
|
|
2695
3620
|
providerExportsStr = (await getProviderExportsForModel(model)).trim();
|
|
2696
|
-
await getProviderEnvForModel(model);
|
|
2697
3621
|
if (behavior.transcriptKind === "ohmypi-jsonl") {
|
|
2698
3622
|
if (getProviderForModelSync(model).name === "openai") {
|
|
2699
3623
|
const auth = await getOhmypiCodexAuthStatus({ refreshIfExpired: true });
|
|
@@ -2725,7 +3649,7 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
|
|
|
2725
3649
|
const codexApprovalPolicy = codexPermMode === "full-access" ? "never" : "on-request";
|
|
2726
3650
|
const codexSandboxMode = codexPermMode === "full-access" ? "danger-full-access" : codexPermMode === "read-only" ? "read-only" : "workspace-write";
|
|
2727
3651
|
const codexApprovalsReviewer = codexPermMode === "auto-review" ? "auto_review" : void 0;
|
|
2728
|
-
const { initCodexHome, extractThreadIdFromRollout } = await import("./codex-
|
|
3652
|
+
const { initCodexHome, extractThreadIdFromRollout } = await import("./codex-BdEsUFWh.js");
|
|
2729
3653
|
initCodexHome(codexHome, {
|
|
2730
3654
|
trustedDir: cwd,
|
|
2731
3655
|
approvalPolicy: codexApprovalPolicy,
|
|
@@ -2755,7 +3679,7 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
|
|
|
2755
3679
|
await writePtyToken(tmuxSession);
|
|
2756
3680
|
}
|
|
2757
3681
|
let channelsBridgeMcpConfig;
|
|
2758
|
-
if (!piFields && !codexFields && !plainFork && isClaudeCodeChannelsEnabled() && (!model || getProviderForModelSync(model).name === "anthropic") && process.env.CLAUDE_CODE_USE_BEDROCK !== "1" && process.env.CLAUDE_CODE_USE_VERTEX !== "1" && process.env.CLAUDE_CODE_USE_FOUNDRY !== "1" && process.env.OVERDECK_DOCKER_WORKSPACE !== "1" && process.env.PAN_DOCKER !== "1") {
|
|
3682
|
+
if (!piFields && !codexFields && !acpFields && !plainFork && isClaudeCodeChannelsEnabled() && (!model || getProviderForModelSync(model).name === "anthropic") && process.env.CLAUDE_CODE_USE_BEDROCK !== "1" && process.env.CLAUDE_CODE_USE_VERTEX !== "1" && process.env.CLAUDE_CODE_USE_FOUNDRY !== "1" && process.env.OVERDECK_DOCKER_WORKSPACE !== "1" && process.env.PAN_DOCKER !== "1") {
|
|
2759
3683
|
channelsBridgeMcpConfig = join(stateDir, "agent-mcp.json");
|
|
2760
3684
|
writeBridgeTokenSync(tmuxSession);
|
|
2761
3685
|
await writeChannelsBridgeMcpConfig(channelsBridgeMcpConfig, tmuxSession);
|
|
@@ -2769,19 +3693,19 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
|
|
|
2769
3693
|
unsetProviderEnv: true,
|
|
2770
3694
|
overdeckEnv: {
|
|
2771
3695
|
...issueId ? { issueId } : {},
|
|
2772
|
-
...piFields || codexFields || useSupervisor ? { agentId: tmuxSession } : {}
|
|
3696
|
+
...piFields || codexFields || acpFields || useSupervisor ? { agentId: tmuxSession } : {}
|
|
2773
3697
|
},
|
|
2774
3698
|
extraEnvExports: [harnessLaunch.pathExport, `export OVERDECK_DASHBOARD_URL="http://127.0.0.1:${process.env["API_PORT"] ?? process.env["PORT"] ?? "3011"}"`],
|
|
2775
3699
|
providerExports: providerExportsStr || void 0,
|
|
2776
3700
|
trapHup: true,
|
|
2777
3701
|
baseCommand: runtimeCommand,
|
|
2778
|
-
appendSystemPromptFiles: piFields ? await piConversationSystemPromptFiles(cwd) : codexFields ? [] : await claudeConversationSystemPromptFiles(cwd),
|
|
3702
|
+
appendSystemPromptFiles: piFields ? await piConversationSystemPromptFiles(cwd) : codexFields || acpFields ? [] : await claudeConversationSystemPromptFiles(cwd),
|
|
2779
3703
|
model: launcherModel,
|
|
2780
|
-
...piFields ?? codexFields ?? {
|
|
3704
|
+
...piFields ?? codexFields ?? acpFields ?? {
|
|
2781
3705
|
resumeSessionId: resume ? claudeSessionId : void 0,
|
|
2782
3706
|
sessionId: resume ? void 0 : claudeSessionId
|
|
2783
3707
|
},
|
|
2784
|
-
extraArgs: !piFields && effort ? `--effort "${effort}"` : void 0,
|
|
3708
|
+
extraArgs: !piFields && !acpFields && effort ? `--effort "${effort}"` : void 0,
|
|
2785
3709
|
keepAlive: true,
|
|
2786
3710
|
fileMode: 448,
|
|
2787
3711
|
channelsBridgeMcpConfig,
|
|
@@ -2794,7 +3718,7 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
|
|
|
2794
3718
|
} catch {}
|
|
2795
3719
|
console.log(`[claude-invoke] purpose=conversation-session | model=${model || "default"} | source=conversations.ts:spawnConversationSession | session=${tmuxSession} | resume=${resume} | command="${runtimeCommand}"`);
|
|
2796
3720
|
try {
|
|
2797
|
-
const { preTrustDirectory } = await import("./workspace-manager-
|
|
3721
|
+
const { preTrustDirectory } = await import("./workspace-manager-CDTKjjdU.js");
|
|
2798
3722
|
preTrustDirectory(cwd);
|
|
2799
3723
|
} catch {}
|
|
2800
3724
|
try {
|
|
@@ -2811,7 +3735,7 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
|
|
|
2811
3735
|
const codexHomeDir = codexFields.codexHome;
|
|
2812
3736
|
(async () => {
|
|
2813
3737
|
try {
|
|
2814
|
-
const { waitForCodexRollout, extractThreadIdFromRollout, writeThreadId } = await import("./codex-
|
|
3738
|
+
const { waitForCodexRollout, extractThreadIdFromRollout, writeThreadId } = await import("./codex-BdEsUFWh.js");
|
|
2815
3739
|
const rollout = await waitForCodexRollout(codexHomeDir, 12e4);
|
|
2816
3740
|
if (rollout) {
|
|
2817
3741
|
const threadId = extractThreadIdFromRollout(rollout);
|
|
@@ -2887,10 +3811,14 @@ async function handleConversationCreate(body, deps) {
|
|
|
2887
3811
|
await spawnConversationSession(tmuxSession, cwd, claudeSessionId, model, effort, issueId, false, harness);
|
|
2888
3812
|
console.log(`[conversations] tmux session ${tmuxSession} spawned, sessionId: ${claudeSessionId}`);
|
|
2889
3813
|
await waitForConversationRuntimeReady(tmuxSession, harness, "spawn");
|
|
2890
|
-
if (message)
|
|
3814
|
+
if (message) {
|
|
3815
|
+
const delivery = await deliverAgentMessage(tmuxSession, message, "conversation-message", resolveConversationDeliveryMethod(conv));
|
|
3816
|
+
if (harness === "acp" && !delivery.ok) throw new Error(`ACP initial prompt did not land: ${delivery.failure ?? "unknown failure"}`);
|
|
3817
|
+
}
|
|
2891
3818
|
} catch (spawnErr) {
|
|
2892
3819
|
const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
2893
3820
|
console.error(`[conversations] background spawn failed for ${tmuxSession}: ${msg}`);
|
|
3821
|
+
if (harness === "acp") await stopConversationRuntime(conv, name);
|
|
2894
3822
|
updateSpawnError(name, msg);
|
|
2895
3823
|
getEventStore().emitOnly({
|
|
2896
3824
|
type: "conversation.created",
|
|
@@ -2976,6 +3904,9 @@ async function handleConversationResume(name, body, deps) {
|
|
|
2976
3904
|
reattached: false,
|
|
2977
3905
|
sessionAlive: true
|
|
2978
3906
|
});
|
|
3907
|
+
} catch (error) {
|
|
3908
|
+
if (harness === "acp") await stopConversationRuntime(conv, name);
|
|
3909
|
+
throw error;
|
|
2979
3910
|
} finally {
|
|
2980
3911
|
respawn.done();
|
|
2981
3912
|
}
|
|
@@ -3010,13 +3941,16 @@ async function handleConversationRestartAll(deps) {
|
|
|
3010
3941
|
const results = [];
|
|
3011
3942
|
for (const conv of convs) {
|
|
3012
3943
|
const respawn = markRespawnPending(conv.tmuxSession);
|
|
3944
|
+
let attemptedHarness = conv.harness ?? "claude-code";
|
|
3013
3945
|
try {
|
|
3014
3946
|
await Effect.runPromise(killSession(conv.tmuxSession).pipe(Effect.catch(() => Effect.succeed(void 0))));
|
|
3015
3947
|
const oldSessionId = conv.claudeSessionId;
|
|
3016
3948
|
const sessionFileForResume = await deps.resolveSessionFile(conv);
|
|
3017
3949
|
const canResume = !!oldSessionId && !!sessionFileForResume && existsSync(sessionFileForResume);
|
|
3018
3950
|
const harness = await resolveAllowedHarness(conv.harness, conv.model);
|
|
3951
|
+
attemptedHarness = harness;
|
|
3019
3952
|
await spawnConversationSession(conv.tmuxSession, conv.cwd, oldSessionId ?? randomUUID$1(), conv.model ?? void 0, conv.effort ?? void 0, conv.issueId ?? void 0, canResume, harness);
|
|
3953
|
+
if (harness === "acp") await waitForConversationRuntimeReady(conv.tmuxSession, harness, "respawn");
|
|
3020
3954
|
setConversationHarness(conv.name, harness);
|
|
3021
3955
|
markConversationActive(conv.name);
|
|
3022
3956
|
results.push({
|
|
@@ -3027,6 +3961,7 @@ async function handleConversationRestartAll(deps) {
|
|
|
3027
3961
|
} catch (err) {
|
|
3028
3962
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3029
3963
|
console.error(`[conversations] Failed to restart ${conv.name}:`, msg);
|
|
3964
|
+
if (attemptedHarness === "acp") await stopConversationRuntime(conv, conv.name);
|
|
3030
3965
|
results.push({
|
|
3031
3966
|
name: conv.name,
|
|
3032
3967
|
model: conv.model,
|
|
@@ -3230,6 +4165,7 @@ function pickDeliverAs(bodyDeliverAs) {
|
|
|
3230
4165
|
}
|
|
3231
4166
|
function resolveConversationDeliveryMethod(conv) {
|
|
3232
4167
|
const harness = conv.harness ?? "claude-code";
|
|
4168
|
+
if (harness === "acp") return "auto";
|
|
3233
4169
|
if (isPiControlChannelHarness(harness)) return "auto";
|
|
3234
4170
|
if (harness === "codex" && loadConfigSync().config.codex?.transport !== "tui") return "auto";
|
|
3235
4171
|
return conv.deliveryMethod ?? (getHarnessBehavior(harness).deliveryKind === "rpc-fifo" ? "tmux" : "auto");
|
|
@@ -3690,14 +4626,14 @@ const postInternalAgentPermissionRequestRoute = HttpRouter.add("POST", "/api/int
|
|
|
3690
4626
|
error: "missing agent id"
|
|
3691
4627
|
}, { status: 400 });
|
|
3692
4628
|
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
3693
|
-
const { INTERNAL_TOKEN_HEADER, getInternalTokenSync } = yield* Effect.promise(() => import("./internal-token-
|
|
4629
|
+
const { INTERNAL_TOKEN_HEADER, getInternalTokenSync } = yield* Effect.promise(() => import("./internal-token-M9omeXM1.js"));
|
|
3694
4630
|
const expected = getInternalTokenSync();
|
|
3695
4631
|
if (!expected) return jsonResponse({
|
|
3696
4632
|
ok: false,
|
|
3697
4633
|
error: "internal token not configured"
|
|
3698
4634
|
}, { status: 503 });
|
|
3699
4635
|
const rawHeader = request.headers[INTERNAL_TOKEN_HEADER];
|
|
3700
|
-
if (!constantTimeTokenEqual
|
|
4636
|
+
if (!constantTimeTokenEqual(Array.isArray(rawHeader) ? rawHeader[0] : rawHeader, expected)) return jsonResponse({
|
|
3701
4637
|
ok: false,
|
|
3702
4638
|
error: "forbidden"
|
|
3703
4639
|
}, { status: 403 });
|
|
@@ -3826,6 +4762,7 @@ const postAgentPermissionResponseRoute = HttpRouter.add("POST", "/api/agents/:id
|
|
|
3826
4762
|
* pollers (PAN-1134). Extracted from routes/agents.ts so Pi event polling
|
|
3827
4763
|
* can reuse bodyToEvent without importing a heavy route module.
|
|
3828
4764
|
*/
|
|
4765
|
+
init_src();
|
|
3829
4766
|
/**
|
|
3830
4767
|
* Translate a decoded body into an unsigned DomainEvent (no sequence yet).
|
|
3831
4768
|
* Returns null if the body doesn't map to a runtime event.
|
|
@@ -3946,6 +4883,25 @@ const bodyToEvent = (agentId, body, timestamp) => {
|
|
|
3946
4883
|
contextSaturatedAt: source["contextSaturatedAt"]
|
|
3947
4884
|
}
|
|
3948
4885
|
};
|
|
4886
|
+
case "linear_mcp_auth_required": return {
|
|
4887
|
+
type: "linear_mcp_auth.required",
|
|
4888
|
+
timestamp,
|
|
4889
|
+
payload: {
|
|
4890
|
+
agentId,
|
|
4891
|
+
issueId: source["issueId"] ?? null,
|
|
4892
|
+
authUrl: source["authUrl"] ?? null,
|
|
4893
|
+
expiresAt: source["expiresAt"] ?? null
|
|
4894
|
+
}
|
|
4895
|
+
};
|
|
4896
|
+
case "linear_mcp_auth_healthy": return {
|
|
4897
|
+
type: "linear_mcp_auth.healthy",
|
|
4898
|
+
timestamp,
|
|
4899
|
+
payload: {
|
|
4900
|
+
agentId,
|
|
4901
|
+
issueId: source["issueId"] ?? null,
|
|
4902
|
+
source: "hook"
|
|
4903
|
+
}
|
|
4904
|
+
};
|
|
3949
4905
|
default: return null;
|
|
3950
4906
|
}
|
|
3951
4907
|
};
|
|
@@ -3957,7 +4913,7 @@ const getAgentHealthHistoryRoute = HttpRouter.add("GET", "/api/agents/:id/health
|
|
|
3957
4913
|
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
3958
4914
|
const urlOpt = HttpServerRequest.toURL(request);
|
|
3959
4915
|
const hours = Option.isSome(urlOpt) ? urlOpt.value.searchParams.get("hours") ?? "24" : "24";
|
|
3960
|
-
const { getHealthHistory } = yield* Effect.promise(() => import("./health-events-
|
|
4916
|
+
const { getHealthHistory } = yield* Effect.promise(() => import("./health-events-_xfTaOUs.js"));
|
|
3961
4917
|
const endTime = /* @__PURE__ */ new Date();
|
|
3962
4918
|
const startTime = /* @__PURE__ */ new Date(endTime.getTime() - parseInt(hours) * 60 * 60 * 1e3);
|
|
3963
4919
|
const events = getHealthHistory(id, startTime.toISOString(), endTime.toISOString());
|
|
@@ -3970,9 +4926,18 @@ const getAgentHealthHistoryRoute = HttpRouter.add("GET", "/api/agents/:id/health
|
|
|
3970
4926
|
})));
|
|
3971
4927
|
function emitAgentRuntimeEvent(id, body, timestamp) {
|
|
3972
4928
|
return Effect.gen(function* () {
|
|
4929
|
+
let eventBody = body;
|
|
4930
|
+
if (body["kind"] === "linear_mcp_auth_required" || body["kind"] === "linear_mcp_auth_healthy") {
|
|
4931
|
+
const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-hZpxIw1E.js"));
|
|
4932
|
+
const snapshot = yield* (yield* AgentStateService).get(id);
|
|
4933
|
+
eventBody = {
|
|
4934
|
+
...body,
|
|
4935
|
+
issueId: snapshot?.currentIssue ?? null
|
|
4936
|
+
};
|
|
4937
|
+
}
|
|
3973
4938
|
let raw;
|
|
3974
4939
|
try {
|
|
3975
|
-
raw = bodyToEvent(id,
|
|
4940
|
+
raw = bodyToEvent(id, eventBody, timestamp);
|
|
3976
4941
|
} catch (error) {
|
|
3977
4942
|
return {
|
|
3978
4943
|
ok: false,
|
|
@@ -3998,7 +4963,7 @@ function emitAgentRuntimeEvent(id, body, timestamp) {
|
|
|
3998
4963
|
detail: String(decoded.failure)
|
|
3999
4964
|
}, { status: 400 })
|
|
4000
4965
|
};
|
|
4001
|
-
const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-
|
|
4966
|
+
const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-hZpxIw1E.js"));
|
|
4002
4967
|
yield* (yield* AgentStateService).emit(decoded.success);
|
|
4003
4968
|
return {
|
|
4004
4969
|
ok: true,
|
|
@@ -4080,7 +5045,7 @@ const getAgentRuntimeRoute = HttpRouter.add("GET", "/api/agents/:id/runtime", ht
|
|
|
4080
5045
|
success: false,
|
|
4081
5046
|
error: "missing agent id"
|
|
4082
5047
|
}, { status: 400 });
|
|
4083
|
-
const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-
|
|
5048
|
+
const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-hZpxIw1E.js"));
|
|
4084
5049
|
const snapshot = yield* (yield* AgentStateService).get(id);
|
|
4085
5050
|
if (!snapshot) return jsonResponse({
|
|
4086
5051
|
success: false,
|
|
@@ -4265,8 +5230,8 @@ function createAgentStopHandler(lifecycleEvent) {
|
|
|
4265
5230
|
yield* stopAgent(id);
|
|
4266
5231
|
if (stateBeforeStop?.issueId) yield* Effect.promise(async () => {
|
|
4267
5232
|
try {
|
|
4268
|
-
const { resolveProjectFromIssueSync } = await import("./projects-
|
|
4269
|
-
const { findWorkspacePath } = await import("./archive-planning-
|
|
5233
|
+
const { resolveProjectFromIssueSync } = await import("./projects-B03Wr-xc.js");
|
|
5234
|
+
const { findWorkspacePath } = await import("./archive-planning-0FJ6Xd3b.js");
|
|
4270
5235
|
const issueLower = stateBeforeStop.issueId.toLowerCase();
|
|
4271
5236
|
const workspacePath = findWorkspacePath(resolveProjectFromIssueSync(stateBeforeStop.issueId)?.projectPath ?? process.cwd(), issueLower);
|
|
4272
5237
|
if (workspacePath) {
|
|
@@ -4402,9 +5367,18 @@ const postAgentUnpauseRoute = HttpRouter.add("POST", "/api/agents/:id/unpause",
|
|
|
4402
5367
|
payload: buildAgentControlEventPayload(updatedState, toAgentStatusPayload(stateBeforeUnpause.status), hasLiveTmuxSession)
|
|
4403
5368
|
});
|
|
4404
5369
|
invalidateAgentsCache();
|
|
5370
|
+
let resumeTriggered = false;
|
|
5371
|
+
if ((yield* getWorkAgentLifecycleState(id)).canResumeSession && updatedState.troubled !== true) {
|
|
5372
|
+
resumeTriggered = true;
|
|
5373
|
+
yield* Effect.promise(() => appendAgentLifecycleLog(id, "agent.unpause_immediate_resume_triggered"));
|
|
5374
|
+
import("./resume-u9byntrq.js").then(({ resumeAgent }) => resumeAgent(id)).then((result) => {
|
|
5375
|
+
if (!result.success) console.warn(`[agents] immediate resume after unpause failed for ${id}: ${result.error}`);
|
|
5376
|
+
}).catch((err) => console.warn(`[agents] immediate resume after unpause errored for ${id}:`, err));
|
|
5377
|
+
}
|
|
4405
5378
|
return jsonResponse({
|
|
4406
5379
|
success: true,
|
|
4407
|
-
agent: updatedState
|
|
5380
|
+
agent: updatedState,
|
|
5381
|
+
resumeTriggered
|
|
4408
5382
|
});
|
|
4409
5383
|
})));
|
|
4410
5384
|
const postAgentUntroubledRoute = HttpRouter.add("POST", "/api/agents/:id/untroubled", httpHandler(Effect.gen(function* () {
|
|
@@ -4442,6 +5416,7 @@ const postAgentUntroubledRoute = HttpRouter.add("POST", "/api/agents/:id/untroub
|
|
|
4442
5416
|
init_config_yaml();
|
|
4443
5417
|
init_agents$1();
|
|
4444
5418
|
init_harness_policy();
|
|
5419
|
+
init_model_validation();
|
|
4445
5420
|
init_operator_interventions();
|
|
4446
5421
|
init_projects();
|
|
4447
5422
|
init_tmux();
|
|
@@ -5090,7 +6065,6 @@ const postAgentsRestartWithConfigRoute = HttpRouter.add("POST", "/api/agents/res
|
|
|
5090
6065
|
})));
|
|
5091
6066
|
//#endregion
|
|
5092
6067
|
//#region routes/agents/control.ts
|
|
5093
|
-
init_config();
|
|
5094
6068
|
init_jsonl_parser();
|
|
5095
6069
|
init_model_validation();
|
|
5096
6070
|
init_paths();
|
|
@@ -5244,12 +6218,328 @@ const postAgentSwitchModelRoute = HttpRouter.add("POST", "/api/agents/:id/switch
|
|
|
5244
6218
|
return jsonResponse({ error: `Agent ${(yield* HttpRouter.params)["id"] ?? ""} model is locked once the agent is spawned` }, { status: 409 });
|
|
5245
6219
|
})));
|
|
5246
6220
|
//#endregion
|
|
6221
|
+
//#region ../../lib/orders/eligibility.ts
|
|
6222
|
+
function evaluateOrderDispatchEligibility(input) {
|
|
6223
|
+
const issueId = input.issueId.toUpperCase();
|
|
6224
|
+
const item = input.book.items.find((candidate) => candidate.issue.toUpperCase() === issueId);
|
|
6225
|
+
if (!item) {
|
|
6226
|
+
const overrideUsed = input.offBook === true;
|
|
6227
|
+
return {
|
|
6228
|
+
eligible: overrideUsed,
|
|
6229
|
+
overrideUsed,
|
|
6230
|
+
...overrideUsed ? {} : {
|
|
6231
|
+
code: "off-book",
|
|
6232
|
+
message: `Issue ${issueId} is not in active order book ${input.book.id}; work-agent dispatch is blocked. Retry with --off-book only for an intentional exception.`
|
|
6233
|
+
},
|
|
6234
|
+
conditions: [{
|
|
6235
|
+
key: "book-membership",
|
|
6236
|
+
met: overrideUsed,
|
|
6237
|
+
detail: overrideUsed ? `Issue ${issueId} is outside the book and the operator supplied --off-book.` : `Issue ${issueId} is not a member of ${input.book.id}.`
|
|
6238
|
+
}]
|
|
6239
|
+
};
|
|
6240
|
+
}
|
|
6241
|
+
const terminalByIssue = new Map(input.progress.items.map((progress) => [progress.issue.toUpperCase(), progress.terminal]));
|
|
6242
|
+
for (const [issue, terminal] of input.prerequisiteTerminal ?? []) if (!terminalByIssue.has(issue.toUpperCase())) terminalByIssue.set(issue.toUpperCase(), terminal);
|
|
6243
|
+
const unmetPrereq = item.prereqs.find((prereq) => terminalByIssue.get(prereq.toUpperCase()) !== true);
|
|
6244
|
+
const prereqsMet = unmetPrereq === void 0;
|
|
6245
|
+
const otherInFlight = input.book.items.filter((candidate) => candidate.issue.toUpperCase() !== issueId && input.inFlightIssues.has(candidate.issue.toUpperCase()));
|
|
6246
|
+
const laneBBlocker = item.lane === "B" ? otherInFlight.find((candidate) => candidate.lane === "B") : void 0;
|
|
6247
|
+
const laneAInFlight = otherInFlight.filter((candidate) => candidate.lane === "A").length;
|
|
6248
|
+
const laneSlotFree = item.lane === "B" ? laneBBlocker === void 0 : laneAInFlight < input.book.settings.laneAConcurrency;
|
|
6249
|
+
const postureOpen = input.book.settings.posture === "open";
|
|
6250
|
+
const prdReverified = !item.reVerify;
|
|
6251
|
+
const conditions = [
|
|
6252
|
+
{
|
|
6253
|
+
key: "book-membership",
|
|
6254
|
+
met: true,
|
|
6255
|
+
detail: `${issueId} is a member of ${input.book.id}.`
|
|
6256
|
+
},
|
|
6257
|
+
{
|
|
6258
|
+
key: "pickup-posture",
|
|
6259
|
+
met: postureOpen,
|
|
6260
|
+
detail: postureOpen ? "Order-book pickup posture is OPEN." : "Order-book pickup posture is DRAIN."
|
|
6261
|
+
},
|
|
6262
|
+
{
|
|
6263
|
+
key: "lane-slot",
|
|
6264
|
+
met: laneSlotFree,
|
|
6265
|
+
detail: item.lane === "B" ? laneBBlocker ? `Lane B is occupied by ${laneBBlocker.issue}.` : "Lane B has no issue in flight." : `${laneAInFlight}/${input.book.settings.laneAConcurrency} Lane A slots are in flight.`
|
|
6266
|
+
},
|
|
6267
|
+
{
|
|
6268
|
+
key: "prerequisites",
|
|
6269
|
+
met: prereqsMet,
|
|
6270
|
+
detail: unmetPrereq ? `Prerequisite ${unmetPrereq.toUpperCase()} is not terminal.` : "Every prerequisite is landed or parked."
|
|
6271
|
+
},
|
|
6272
|
+
{
|
|
6273
|
+
key: "prd-reverified",
|
|
6274
|
+
met: prdReverified,
|
|
6275
|
+
detail: prdReverified ? "No PRD re-verification is pending." : `${issueId} requires PRD re-verification before dispatch.`
|
|
6276
|
+
}
|
|
6277
|
+
];
|
|
6278
|
+
if (!postureOpen) return {
|
|
6279
|
+
eligible: false,
|
|
6280
|
+
overrideUsed: false,
|
|
6281
|
+
code: "book-draining",
|
|
6282
|
+
message: `Issue ${issueId} cannot start because order book ${input.book.id} is in DRAIN posture; no new book items may dispatch.`,
|
|
6283
|
+
conditions
|
|
6284
|
+
};
|
|
6285
|
+
if (unmetPrereq) return {
|
|
6286
|
+
eligible: false,
|
|
6287
|
+
overrideUsed: false,
|
|
6288
|
+
code: "prerequisite-unmet",
|
|
6289
|
+
message: `Issue ${issueId} cannot start because prerequisite ${unmetPrereq.toUpperCase()} has not landed or been parked.`,
|
|
6290
|
+
conditions
|
|
6291
|
+
};
|
|
6292
|
+
if (!prdReverified) return {
|
|
6293
|
+
eligible: false,
|
|
6294
|
+
overrideUsed: false,
|
|
6295
|
+
code: "reverify-required",
|
|
6296
|
+
message: `Issue ${issueId} cannot start until its PRD has been re-verified against current main.`,
|
|
6297
|
+
conditions
|
|
6298
|
+
};
|
|
6299
|
+
if (laneBBlocker) return {
|
|
6300
|
+
eligible: false,
|
|
6301
|
+
overrideUsed: false,
|
|
6302
|
+
code: "lane-b-busy",
|
|
6303
|
+
message: `Issue ${issueId} cannot start in Lane B because ${laneBBlocker.issue} is still in flight; Lane B allows one active issue at a time.`,
|
|
6304
|
+
conditions
|
|
6305
|
+
};
|
|
6306
|
+
if (item.lane === "A" && !laneSlotFree) return {
|
|
6307
|
+
eligible: false,
|
|
6308
|
+
overrideUsed: false,
|
|
6309
|
+
code: "lane-a-full",
|
|
6310
|
+
message: `Issue ${issueId} cannot start in Lane A because ${laneAInFlight} issues are already in flight and laneAConcurrency is ${input.book.settings.laneAConcurrency}.`,
|
|
6311
|
+
conditions
|
|
6312
|
+
};
|
|
6313
|
+
return {
|
|
6314
|
+
eligible: true,
|
|
6315
|
+
overrideUsed: false,
|
|
6316
|
+
conditions
|
|
6317
|
+
};
|
|
6318
|
+
}
|
|
6319
|
+
//#endregion
|
|
6320
|
+
//#region ../../lib/orders/dispatch-gate.ts
|
|
6321
|
+
init_review_status();
|
|
6322
|
+
init_projects();
|
|
6323
|
+
function stateRootFor(projectRoot) {
|
|
6324
|
+
const project = findProjectByPathSync(projectRoot);
|
|
6325
|
+
if (!project) throw new Error(`No configured project contains ${projectRoot}`);
|
|
6326
|
+
return resolveStateReadHomeSync(project).root;
|
|
6327
|
+
}
|
|
6328
|
+
function defaultInFlightIssues(_bookId, issueIds) {
|
|
6329
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
6330
|
+
for (const issueId of issueIds) {
|
|
6331
|
+
const lifecycle = getWorkAgentLifecycleStateSync(issueId);
|
|
6332
|
+
const review = getReviewStatusSync(issueId);
|
|
6333
|
+
if (lifecycle.isRunning || review !== null && review.reviewStatus !== "pending") inFlight.add(issueId.toUpperCase());
|
|
6334
|
+
}
|
|
6335
|
+
return inFlight;
|
|
6336
|
+
}
|
|
6337
|
+
const booklessDecision = {
|
|
6338
|
+
eligible: true,
|
|
6339
|
+
overrideUsed: false,
|
|
6340
|
+
conditions: []
|
|
6341
|
+
};
|
|
6342
|
+
async function checkActiveOrderDispatch(projectRoot, issueId, options = {}, deps = {}) {
|
|
6343
|
+
const runId = await (deps.resolveRunId ?? resolveLiveFlywheelRunId)();
|
|
6344
|
+
if (!runId) return {
|
|
6345
|
+
ordersBound: false,
|
|
6346
|
+
decision: booklessDecision
|
|
6347
|
+
};
|
|
6348
|
+
const launch = await (deps.readLaunch ?? readFlywheelLaunchMetadata)(runId);
|
|
6349
|
+
const bookId = launch?.orders?.bookId;
|
|
6350
|
+
if (!bookId) return {
|
|
6351
|
+
ordersBound: false,
|
|
6352
|
+
runId,
|
|
6353
|
+
decision: booklessDecision
|
|
6354
|
+
};
|
|
6355
|
+
const stateRoot = (deps.stateRoot ?? stateRootFor)(launch?.workspace ?? projectRoot);
|
|
6356
|
+
const book = (deps.getOrderBook ?? getBook)(stateRoot, bookId);
|
|
6357
|
+
if (!book) throw new Error(`Active Flywheel run ${runId} references missing order book ${bookId}`);
|
|
6358
|
+
const progress = (deps.computeProgress ?? computeBookProgress)(book);
|
|
6359
|
+
const prerequisiteIds = [...new Set(book.items.flatMap((item) => item.prereqs.map((prereq) => prereq.toUpperCase())))];
|
|
6360
|
+
const prerequisiteState = (deps.issueLookup ?? liveOrderIssueLookup)(prerequisiteIds);
|
|
6361
|
+
const prerequisiteTerminal = new Map(prerequisiteIds.map((prereq) => {
|
|
6362
|
+
const state = prerequisiteState.get(prereq);
|
|
6363
|
+
return [prereq, state ? !state.open || state.parked : false];
|
|
6364
|
+
}));
|
|
6365
|
+
const inFlightIssues = new Set((deps.inFlightIssues ?? defaultInFlightIssues)(book.id, book.items.filter((item) => !progress.items.find((entry) => entry.issue === item.issue)?.terminal).map((item) => item.issue)));
|
|
6366
|
+
for (const reserved of deps.reservedIssues ?? []) inFlightIssues.add(reserved.toUpperCase());
|
|
6367
|
+
return {
|
|
6368
|
+
ordersBound: true,
|
|
6369
|
+
runId,
|
|
6370
|
+
bookId,
|
|
6371
|
+
decision: evaluateOrderDispatchEligibility({
|
|
6372
|
+
book,
|
|
6373
|
+
progress,
|
|
6374
|
+
issueId,
|
|
6375
|
+
inFlightIssues,
|
|
6376
|
+
prerequisiteTerminal,
|
|
6377
|
+
offBook: options.offBook
|
|
6378
|
+
})
|
|
6379
|
+
};
|
|
6380
|
+
}
|
|
6381
|
+
async function recordOffBookOverride(runId, bookId, issueId, actor = process.env["OVERDECK_AGENT_ID"] ?? "operator") {
|
|
6382
|
+
const runDir = getFlywheelRunDir(runId);
|
|
6383
|
+
const path = join(runDir, "orders-overrides.jsonl");
|
|
6384
|
+
await mkdir(runDir, { recursive: true });
|
|
6385
|
+
await appendFile(path, `${JSON.stringify({
|
|
6386
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6387
|
+
runId,
|
|
6388
|
+
bookId,
|
|
6389
|
+
issueId: issueId.toUpperCase(),
|
|
6390
|
+
actor,
|
|
6391
|
+
override: "off-book"
|
|
6392
|
+
})}\n`, "utf8");
|
|
6393
|
+
return path;
|
|
6394
|
+
}
|
|
6395
|
+
//#endregion
|
|
6396
|
+
//#region ../../lib/orders/dispatch-reservation.ts
|
|
6397
|
+
var OrderDispatchReservationError = class extends Error {
|
|
6398
|
+
constructor(check) {
|
|
6399
|
+
super(check.decision.message ?? "Order-book dispatch is blocked.");
|
|
6400
|
+
this.check = check;
|
|
6401
|
+
this.name = "OrderDispatchReservationError";
|
|
6402
|
+
}
|
|
6403
|
+
};
|
|
6404
|
+
const DEFAULT_STALE_MS = 900 * 1e3;
|
|
6405
|
+
const processLocks = /* @__PURE__ */ new Map();
|
|
6406
|
+
let reservationSequence = 0;
|
|
6407
|
+
async function withProcessLock(key, action) {
|
|
6408
|
+
const previous = processLocks.get(key) ?? Promise.resolve();
|
|
6409
|
+
let release;
|
|
6410
|
+
const current = new Promise((resolve) => {
|
|
6411
|
+
release = resolve;
|
|
6412
|
+
});
|
|
6413
|
+
const tail = previous.then(() => current);
|
|
6414
|
+
processLocks.set(key, tail);
|
|
6415
|
+
await previous;
|
|
6416
|
+
try {
|
|
6417
|
+
return await action();
|
|
6418
|
+
} finally {
|
|
6419
|
+
release();
|
|
6420
|
+
if (processLocks.get(key) === tail) processLocks.delete(key);
|
|
6421
|
+
}
|
|
6422
|
+
}
|
|
6423
|
+
function paths(root) {
|
|
6424
|
+
return {
|
|
6425
|
+
state: join(root, "orders-dispatch-reservations.json"),
|
|
6426
|
+
temp: join(root, `orders-dispatch-reservations.${process.pid}.tmp`),
|
|
6427
|
+
lock: join(root, "orders-dispatch-reservations.lock")
|
|
6428
|
+
};
|
|
6429
|
+
}
|
|
6430
|
+
async function acquireFileLock(path, staleMs, now) {
|
|
6431
|
+
try {
|
|
6432
|
+
await mkdir(path);
|
|
6433
|
+
return;
|
|
6434
|
+
} catch (error) {
|
|
6435
|
+
if ((typeof error === "object" && error !== null && "code" in error ? error.code : void 0) !== "EEXIST") throw error;
|
|
6436
|
+
}
|
|
6437
|
+
if (await stat$1(path).then((value) => now.getTime() - value.mtimeMs).catch(() => 0) <= staleMs) throw new Error("Another order-book dispatch admission is in progress; retry after it finishes.");
|
|
6438
|
+
await rm(path, {
|
|
6439
|
+
recursive: true,
|
|
6440
|
+
force: true
|
|
6441
|
+
});
|
|
6442
|
+
await mkdir(path);
|
|
6443
|
+
}
|
|
6444
|
+
async function readReservations(path) {
|
|
6445
|
+
try {
|
|
6446
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
6447
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
6448
|
+
} catch (error) {
|
|
6449
|
+
if ((typeof error === "object" && error !== null && "code" in error ? error.code : void 0) === "ENOENT") return [];
|
|
6450
|
+
throw error;
|
|
6451
|
+
}
|
|
6452
|
+
}
|
|
6453
|
+
async function writeReservations(path, tempPath, reservations) {
|
|
6454
|
+
await writeFile(tempPath, `${JSON.stringify(reservations, null, 2)}\n`, {
|
|
6455
|
+
encoding: "utf8",
|
|
6456
|
+
mode: 384
|
|
6457
|
+
});
|
|
6458
|
+
await rename(tempPath, path);
|
|
6459
|
+
}
|
|
6460
|
+
function freshReservations(reservations, now, staleMs) {
|
|
6461
|
+
return reservations.filter((reservation) => {
|
|
6462
|
+
const reservedAt = Date.parse(reservation.reservedAt);
|
|
6463
|
+
return Number.isFinite(reservedAt) && now.getTime() - reservedAt <= staleMs;
|
|
6464
|
+
});
|
|
6465
|
+
}
|
|
6466
|
+
async function reserveActiveOrderDispatch(projectRoot, issueId, options = {}, deps = {}) {
|
|
6467
|
+
const initial = await checkActiveOrderDispatch(projectRoot, issueId, options, deps);
|
|
6468
|
+
if (!initial.ordersBound || !initial.runId || !initial.bookId || !initial.decision.eligible) return {
|
|
6469
|
+
check: initial,
|
|
6470
|
+
release: async () => {}
|
|
6471
|
+
};
|
|
6472
|
+
const root = (deps.reservationRoot ?? getFlywheelRunDir)(initial.runId);
|
|
6473
|
+
await mkdir(root, { recursive: true });
|
|
6474
|
+
const reservationPaths = paths(root);
|
|
6475
|
+
const staleMs = deps.staleMs ?? DEFAULT_STALE_MS;
|
|
6476
|
+
const now = (deps.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
6477
|
+
const reservationId = `${process.pid}-${now.getTime()}-${reservationSequence++}`;
|
|
6478
|
+
let check = initial;
|
|
6479
|
+
await withProcessLock(reservationPaths.lock, async () => {
|
|
6480
|
+
await acquireFileLock(reservationPaths.lock, staleMs, now);
|
|
6481
|
+
try {
|
|
6482
|
+
const current = freshReservations(await readReservations(reservationPaths.state), now, staleMs);
|
|
6483
|
+
check = await checkActiveOrderDispatch(projectRoot, issueId, options, {
|
|
6484
|
+
...deps,
|
|
6485
|
+
reservedIssues: new Set(current.filter((reservation) => reservation.bookId === initial.bookId).map((reservation) => reservation.issueId))
|
|
6486
|
+
});
|
|
6487
|
+
if (!check.decision.eligible) return;
|
|
6488
|
+
current.push({
|
|
6489
|
+
id: reservationId,
|
|
6490
|
+
bookId: initial.bookId,
|
|
6491
|
+
issueId: issueId.toUpperCase(),
|
|
6492
|
+
reservedAt: now.toISOString()
|
|
6493
|
+
});
|
|
6494
|
+
await writeReservations(reservationPaths.state, reservationPaths.temp, current);
|
|
6495
|
+
} finally {
|
|
6496
|
+
await rm(reservationPaths.lock, {
|
|
6497
|
+
recursive: true,
|
|
6498
|
+
force: true
|
|
6499
|
+
});
|
|
6500
|
+
}
|
|
6501
|
+
});
|
|
6502
|
+
if (!check.decision.eligible) return {
|
|
6503
|
+
check,
|
|
6504
|
+
release: async () => {}
|
|
6505
|
+
};
|
|
6506
|
+
if (options.recordOverride && check.decision.overrideUsed) await (deps.recordOverride ?? recordOffBookOverride)(initial.runId, initial.bookId, issueId);
|
|
6507
|
+
let released = false;
|
|
6508
|
+
return {
|
|
6509
|
+
check,
|
|
6510
|
+
release: async () => {
|
|
6511
|
+
if (released) return;
|
|
6512
|
+
released = true;
|
|
6513
|
+
const releasedAt = (deps.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
6514
|
+
await withProcessLock(reservationPaths.lock, async () => {
|
|
6515
|
+
await acquireFileLock(reservationPaths.lock, staleMs, releasedAt);
|
|
6516
|
+
try {
|
|
6517
|
+
const current = freshReservations(await readReservations(reservationPaths.state), releasedAt, staleMs).filter((reservation) => reservation.id !== reservationId);
|
|
6518
|
+
await writeReservations(reservationPaths.state, reservationPaths.temp, current);
|
|
6519
|
+
} finally {
|
|
6520
|
+
await rm(reservationPaths.lock, {
|
|
6521
|
+
recursive: true,
|
|
6522
|
+
force: true
|
|
6523
|
+
});
|
|
6524
|
+
}
|
|
6525
|
+
});
|
|
6526
|
+
}
|
|
6527
|
+
};
|
|
6528
|
+
}
|
|
6529
|
+
async function withActiveOrderDispatchReservation(projectRoot, issueId, options, action, deps = {}) {
|
|
6530
|
+
const reservation = await reserveActiveOrderDispatch(projectRoot, issueId, options, deps);
|
|
6531
|
+
if (!reservation.check.decision.eligible) return { check: reservation.check };
|
|
6532
|
+
try {
|
|
6533
|
+
return {
|
|
6534
|
+
check: reservation.check,
|
|
6535
|
+
result: await action()
|
|
6536
|
+
};
|
|
6537
|
+
} finally {
|
|
6538
|
+
await reservation.release();
|
|
6539
|
+
}
|
|
6540
|
+
}
|
|
6541
|
+
//#endregion
|
|
5247
6542
|
//#region ../../lib/xbrief/auto-synthesize.ts
|
|
5248
|
-
init_state_read_home();
|
|
5249
|
-
init_state_plane();
|
|
5250
|
-
init_types();
|
|
5251
|
-
init_codex_auth();
|
|
5252
|
-
init_state_home();
|
|
5253
6543
|
init_io();
|
|
5254
6544
|
function cleanMarkdownLine(line) {
|
|
5255
6545
|
return line.replace(/^[-*+]\s+\[[ xX]\]\s+/, "").replace(/^[-*+]\s+/, "").replace(/^#+\s+/, "").replace(/`([^`]+)`/g, "$1").trim();
|
|
@@ -5365,222 +6655,21 @@ const writeAutoStartXBrief = (projectRoot, workspacePath, issue) => Effect.tryPr
|
|
|
5365
6655
|
})
|
|
5366
6656
|
});
|
|
5367
6657
|
//#endregion
|
|
5368
|
-
//#region routes/dashboard-auth.ts
|
|
5369
|
-
init_issue_lifecycle();
|
|
5370
|
-
init_internal_token();
|
|
5371
|
-
const DASHBOARD_SESSION_COOKIE = "overdeck_session";
|
|
5372
|
-
const DASHBOARD_CSRF_HEADER = "x-overdeck-csrf-token";
|
|
5373
|
-
const DASHBOARD_SESSION_MAX_AGE_SECONDS = 3600 * 24 * 30;
|
|
5374
|
-
let browserSessionToken;
|
|
5375
|
-
let browserCsrfToken;
|
|
5376
|
-
function getDashboardSessionToken() {
|
|
5377
|
-
if (browserSessionToken) return browserSessionToken;
|
|
5378
|
-
const override = process.env["OVERDECK_DASHBOARD_SESSION_TOKEN"];
|
|
5379
|
-
if (override) {
|
|
5380
|
-
browserSessionToken = override;
|
|
5381
|
-
return browserSessionToken;
|
|
5382
|
-
}
|
|
5383
|
-
const internal = getInternalTokenSync();
|
|
5384
|
-
browserSessionToken = internal ? createHmac("sha256", internal).update("overdeck-dashboard-session-v1").digest("base64url") : randomBytes(32).toString("base64url");
|
|
5385
|
-
return browserSessionToken;
|
|
5386
|
-
}
|
|
5387
|
-
function dashboardCsrfToken() {
|
|
5388
|
-
if (browserCsrfToken) return browserCsrfToken;
|
|
5389
|
-
const override = process.env["OVERDECK_DASHBOARD_CSRF_TOKEN"];
|
|
5390
|
-
if (override) {
|
|
5391
|
-
browserCsrfToken = override;
|
|
5392
|
-
return browserCsrfToken;
|
|
5393
|
-
}
|
|
5394
|
-
const internal = getInternalTokenSync();
|
|
5395
|
-
browserCsrfToken = internal ? createHmac("sha256", internal).update("overdeck-dashboard-csrf-v1").digest("base64url") : randomBytes(32).toString("base64url");
|
|
5396
|
-
return browserCsrfToken;
|
|
5397
|
-
}
|
|
5398
|
-
function constantTimeTokenEqual(provided, expected) {
|
|
5399
|
-
if (!provided) return false;
|
|
5400
|
-
return timingSafeEqual(createHash$1("sha256").update(provided).digest(), createHash$1("sha256").update(expected).digest());
|
|
5401
|
-
}
|
|
5402
|
-
function cookieValue(cookieHeader, name) {
|
|
5403
|
-
if (!cookieHeader) return void 0;
|
|
5404
|
-
for (const part of cookieHeader.split(";")) {
|
|
5405
|
-
const [rawKey, ...rawValue] = part.trim().split("=");
|
|
5406
|
-
if (rawKey !== name) continue;
|
|
5407
|
-
try {
|
|
5408
|
-
return decodeURIComponent(rawValue.join("="));
|
|
5409
|
-
} catch {
|
|
5410
|
-
return;
|
|
5411
|
-
}
|
|
5412
|
-
}
|
|
5413
|
-
}
|
|
5414
|
-
function dashboardSessionCookieHeader(options = {}) {
|
|
5415
|
-
const secure = options.secure ? "; Secure" : "";
|
|
5416
|
-
return `${DASHBOARD_SESSION_COOKIE}=${encodeURIComponent(getDashboardSessionToken())}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${DASHBOARD_SESSION_MAX_AGE_SECONDS}${secure}`;
|
|
5417
|
-
}
|
|
5418
|
-
function hasDashboardInternalTokenHeaders(headers) {
|
|
5419
|
-
const expected = getInternalTokenSync();
|
|
5420
|
-
if (!expected) return false;
|
|
5421
|
-
if (constantTimeTokenEqual(getHeaderFromMap(headers, "x-overdeck-internal-token"), expected)) return true;
|
|
5422
|
-
const authorization = getHeaderFromMap(headers, "authorization");
|
|
5423
|
-
if (authorization) {
|
|
5424
|
-
const [scheme, token] = authorization.split(/\s+/);
|
|
5425
|
-
if (scheme?.toLowerCase() === "bearer" && constantTimeTokenEqual(token, expected)) return true;
|
|
5426
|
-
}
|
|
5427
|
-
return false;
|
|
5428
|
-
}
|
|
5429
|
-
function hasDashboardInternalToken(request) {
|
|
5430
|
-
return hasDashboardInternalTokenHeaders(request.headers);
|
|
5431
|
-
}
|
|
5432
|
-
function hasDashboardAuthHeaders(headers) {
|
|
5433
|
-
return hasDashboardInternalTokenHeaders(headers) || constantTimeTokenEqual(cookieValue(getHeaderFromMap(headers, "cookie"), "overdeck_session"), getDashboardSessionToken());
|
|
5434
|
-
}
|
|
5435
|
-
function hasDashboardAuth(request) {
|
|
5436
|
-
return hasDashboardAuthHeaders(request.headers);
|
|
5437
|
-
}
|
|
5438
|
-
function isJsonContentType(headers) {
|
|
5439
|
-
const contentType = getHeaderFromMap(headers, "content-type");
|
|
5440
|
-
if (!contentType) return false;
|
|
5441
|
-
const [mime] = contentType.toLowerCase().split(";");
|
|
5442
|
-
return mime.trim() === "application/json";
|
|
5443
|
-
}
|
|
5444
|
-
function hasTrustedExactOrigin(headers) {
|
|
5445
|
-
const origin = getHeaderFromMap(headers, "origin");
|
|
5446
|
-
if (!origin) return false;
|
|
5447
|
-
const normalized = normalizeOrigin(origin);
|
|
5448
|
-
return !!normalized && getTrustedOrigins().includes(normalized);
|
|
5449
|
-
}
|
|
5450
|
-
function hasValidCsrfToken(headers) {
|
|
5451
|
-
return constantTimeTokenEqual(getHeaderFromMap(headers, DASHBOARD_CSRF_HEADER), dashboardCsrfToken());
|
|
5452
|
-
}
|
|
5453
|
-
function rejectUnsafeDashboardMutationRequest(request) {
|
|
5454
|
-
const authError = rejectUnauthorizedDashboardRequest(request);
|
|
5455
|
-
if (authError) return authError;
|
|
5456
|
-
const headers = request.headers;
|
|
5457
|
-
if (!isJsonContentType(headers)) return jsonResponse({ error: "Content-Type must be application/json" }, { status: 400 });
|
|
5458
|
-
if (hasDashboardInternalToken(request)) return null;
|
|
5459
|
-
if (getHeaderFromMap(headers, "origin") && !hasTrustedExactOrigin(headers)) return jsonResponse({ error: "Invalid origin" }, { status: 403 });
|
|
5460
|
-
if (!hasValidCsrfToken(headers)) return jsonResponse({ error: "Invalid CSRF token" }, { status: 403 });
|
|
5461
|
-
return null;
|
|
5462
|
-
}
|
|
5463
|
-
function ipv4ToInt(value) {
|
|
5464
|
-
const parts = value.split(".");
|
|
5465
|
-
if (parts.length !== 4) return null;
|
|
5466
|
-
let result = 0;
|
|
5467
|
-
for (const part of parts) {
|
|
5468
|
-
const octet = Number(part);
|
|
5469
|
-
if (!Number.isInteger(octet) || octet < 0 || octet > 255) return null;
|
|
5470
|
-
result = (result << 8 | octet) >>> 0;
|
|
5471
|
-
}
|
|
5472
|
-
return result;
|
|
5473
|
-
}
|
|
5474
|
-
/** RFC1918 private ranges — Docker bridges always fall inside one of these. */
|
|
5475
|
-
function isRfc1918(ip) {
|
|
5476
|
-
return (ip & 4278190080) >>> 0 === 167772160 || (ip & 4293918720) >>> 0 === 2886729728 || (ip & 4294901760) >>> 0 === 3232235520;
|
|
5477
|
-
}
|
|
5478
|
-
/**
|
|
5479
|
-
* True when `addr` is the IP of a container on one of THIS host's Docker bridge
|
|
5480
|
-
* interfaces (docker0 / br-*). Such a peer can only be a process running on this
|
|
5481
|
-
* machine — our own host-local Traefik fronting overdeck.localhost, or another
|
|
5482
|
-
* Overdeck container — never a LAN client (a direct LAN hit to the 0.0.0.0
|
|
5483
|
-
* API port arrives with a LAN peer that is not inside any Docker bridge subnet).
|
|
5484
|
-
*
|
|
5485
|
-
* Pure for testability: the interface map is injected. We only ever trust
|
|
5486
|
-
* RFC1918 bridge subnets, so a misconfigured public-range br-* cannot widen
|
|
5487
|
-
* trust beyond the local Docker fabric.
|
|
5488
|
-
*/
|
|
5489
|
-
function peerIsHostLocalDockerBridge(addr, interfaces) {
|
|
5490
|
-
const peer = ipv4ToInt(addr);
|
|
5491
|
-
if (peer === null) return false;
|
|
5492
|
-
for (const [name, addrs] of Object.entries(interfaces)) {
|
|
5493
|
-
if (name !== "docker0" && !name.startsWith("br-")) continue;
|
|
5494
|
-
for (const info of addrs ?? []) {
|
|
5495
|
-
if (info.family !== "IPv4") continue;
|
|
5496
|
-
const ifaceIp = ipv4ToInt(info.address);
|
|
5497
|
-
const mask = ipv4ToInt(info.netmask);
|
|
5498
|
-
if (ifaceIp === null || mask === null) continue;
|
|
5499
|
-
const network = (ifaceIp & mask) >>> 0;
|
|
5500
|
-
if (!isRfc1918(network)) continue;
|
|
5501
|
-
if ((peer & mask) >>> 0 === network) return true;
|
|
5502
|
-
}
|
|
5503
|
-
}
|
|
5504
|
-
return false;
|
|
5505
|
-
}
|
|
5506
|
-
/**
|
|
5507
|
-
* True when `addr` shares an RFC1918 subnet with one of THIS process's own
|
|
5508
|
-
* network interfaces. This is the in-container counterpart of
|
|
5509
|
-
* peerIsHostLocalDockerBridge: inside a container the Docker bridge is
|
|
5510
|
-
* invisible — the container sees only its veth endpoints (eth0/eth1), so the
|
|
5511
|
-
* docker0/br-* name filter never matches and the host-local Traefik fronting
|
|
5512
|
-
* the stack 401'd every session mint (first hit live on the PAN-1737 UAT
|
|
5513
|
-
* stack). A peer inside one of the container's attached Docker-network
|
|
5514
|
-
* subnets can only be another container on those networks — the server
|
|
5515
|
-
* publishes no host port (Traefik labels only), so no LAN client can ever
|
|
5516
|
-
* present such an address. Only consulted when running inside a container.
|
|
5517
|
-
*/
|
|
5518
|
-
function peerIsLocalContainerNetwork(addr, interfaces) {
|
|
5519
|
-
const peer = ipv4ToInt(addr);
|
|
5520
|
-
if (peer === null) return false;
|
|
5521
|
-
for (const addrs of Object.values(interfaces)) for (const info of addrs ?? []) {
|
|
5522
|
-
if (info.family !== "IPv4" || info.internal) continue;
|
|
5523
|
-
const ifaceIp = ipv4ToInt(info.address);
|
|
5524
|
-
const mask = ipv4ToInt(info.netmask);
|
|
5525
|
-
if (ifaceIp === null || mask === null) continue;
|
|
5526
|
-
const network = (ifaceIp & mask) >>> 0;
|
|
5527
|
-
if (!isRfc1918(network)) continue;
|
|
5528
|
-
if ((peer & mask) >>> 0 === network) return true;
|
|
5529
|
-
}
|
|
5530
|
-
return false;
|
|
5531
|
-
}
|
|
5532
|
-
/** Docker creates /.dockerenv in every container — stable in-container signal. */
|
|
5533
|
-
const runningInContainer = existsSync("/.dockerenv");
|
|
5534
|
-
/**
|
|
5535
|
-
* A request we trust to mint a session because it originated from this machine —
|
|
5536
|
-
* the local browser hitting the dashboard directly (127.0.0.1), or the
|
|
5537
|
-
* host-local Traefik that fronts overdeck.localhost. In Docker, that Traefik reaches
|
|
5538
|
-
* the host server from a Docker-bridge IP (e.g. 172.18.0.2), NOT 127.0.0.1, so
|
|
5539
|
-
* a literal-loopback check alone silently breaks the zero-step bootstrap for
|
|
5540
|
-
* every overdeck.localhost user — their session mint 401s and no cookie is ever set.
|
|
5541
|
-
*
|
|
5542
|
-
* This is the auto-bootstrap that removes the manual one-time #overdeck_token
|
|
5543
|
-
* step: any browser on overdeck.localhost gets a session with no user action. It is
|
|
5544
|
-
* NOT a security downgrade for the LAN — the raw API ports bind 0.0.0.0, but a
|
|
5545
|
-
* direct LAN hit has a non-loopback, non-Docker-bridge peer and is rejected. We
|
|
5546
|
-
* read ONLY the real TCP peer (request.remoteAddress), never X-Forwarded-For,
|
|
5547
|
-
* which a caller could spoof.
|
|
5548
|
-
*/
|
|
5549
|
-
function isLoopbackPeer(request) {
|
|
5550
|
-
const remoteAddress = request.remoteAddress;
|
|
5551
|
-
const raw = remoteAddress && typeof remoteAddress === "object" && "_tag" in remoteAddress ? Option.getOrElse(remoteAddress, () => "") : typeof remoteAddress === "string" ? remoteAddress : "";
|
|
5552
|
-
if (!raw) return false;
|
|
5553
|
-
const addr = raw.startsWith("::ffff:") ? raw.slice(7) : raw;
|
|
5554
|
-
if (addr === "127.0.0.1" || addr === "::1") return true;
|
|
5555
|
-
if (peerIsHostLocalDockerBridge(addr, networkInterfaces())) return true;
|
|
5556
|
-
return runningInContainer && peerIsLocalContainerNetwork(addr, networkInterfaces());
|
|
5557
|
-
}
|
|
5558
|
-
function rejectUnauthorizedDashboardSessionMintRequest(request) {
|
|
5559
|
-
if (!getInternalTokenSync()) return jsonResponse({ error: "dashboard session token not configured" }, { status: 503 });
|
|
5560
|
-
if (!hasDashboardInternalToken(request) && !isLoopbackPeer(request)) return jsonResponse({ error: "unauthorized" }, { status: 401 });
|
|
5561
|
-
return null;
|
|
5562
|
-
}
|
|
5563
|
-
function rejectUnauthorizedDashboardRequest(request) {
|
|
5564
|
-
if (!getInternalTokenSync()) return jsonResponse({ error: "dashboard session token not configured" }, { status: 503 });
|
|
5565
|
-
if (!hasDashboardAuth(request)) return jsonResponse({ error: "unauthorized" }, { status: 401 });
|
|
5566
|
-
return null;
|
|
5567
|
-
}
|
|
5568
|
-
//#endregion
|
|
5569
6658
|
//#region routes/agents/spawn-helpers.ts
|
|
5570
6659
|
init_child_env();
|
|
5571
6660
|
init_agents$1();
|
|
5572
6661
|
function handleRemoteAgentSpawn(input) {
|
|
5573
6662
|
return Effect.gen(function* () {
|
|
5574
6663
|
const { issueId, workspacePath, workspaceMetadata, spawnModel, projectPath, spawnGuardrails, lifecycle } = input;
|
|
5575
|
-
const { spawnRemoteAgent, checkRemoteSpendCap } = yield* Effect.promise(() => import("./remote-agents-
|
|
5576
|
-
const { createFlyProviderFromConfig } = yield* Effect.promise(() => import("./remote-
|
|
5577
|
-
const { loadConfigSync: loadPanConfig } = yield* Effect.promise(() => import("./config-
|
|
6664
|
+
const { spawnRemoteAgent, checkRemoteSpendCap } = yield* Effect.promise(() => import("./remote-agents-C1bssi41.js"));
|
|
6665
|
+
const { createFlyProviderFromConfig } = yield* Effect.promise(() => import("./remote-CVGUhXMV.js"));
|
|
6666
|
+
const { loadConfigSync: loadPanConfig } = yield* Effect.promise(() => import("./config-DV-grfQc.js"));
|
|
5578
6667
|
const panConfig = loadPanConfig();
|
|
5579
6668
|
const spendCap = checkRemoteSpendCap(panConfig);
|
|
5580
6669
|
if (!spendCap.allowed) return jsonResponse({ error: spendCap.message }, { status: 429 });
|
|
5581
6670
|
const fly = createFlyProviderFromConfig(panConfig.remote);
|
|
5582
6671
|
yield* Effect.promise(() => fly.syncAllCredentials(workspaceMetadata.vmName));
|
|
5583
|
-
const { buildWorkAgentPrompt, getTrackerContext } = yield* Effect.promise(() => import("./work-agent-prompt-
|
|
6672
|
+
const { buildWorkAgentPrompt, getTrackerContext } = yield* Effect.promise(() => import("./work-agent-prompt-C-vuP3_J.js"));
|
|
5584
6673
|
const trackerContext = yield* Effect.promise(() => getTrackerContext(issueId, workspacePath));
|
|
5585
6674
|
const agentPrompt = yield* Effect.promise(() => buildWorkAgentPrompt({
|
|
5586
6675
|
issueId,
|
|
@@ -5896,16 +6985,30 @@ function handleContainerOrchestration(input) {
|
|
|
5896
6985
|
//#endregion
|
|
5897
6986
|
//#region routes/agents/spawn.ts
|
|
5898
6987
|
init_agents$1();
|
|
6988
|
+
init_staffing();
|
|
5899
6989
|
init_operator_interventions();
|
|
5900
6990
|
init_child_env();
|
|
5901
6991
|
init_harness_policy();
|
|
5902
6992
|
init_activity_logger();
|
|
6993
|
+
init_issue_id();
|
|
5903
6994
|
init_projects();
|
|
5904
6995
|
init_review_status();
|
|
6996
|
+
init_state_read_home();
|
|
5905
6997
|
init_io();
|
|
5906
6998
|
init_read_model();
|
|
5907
6999
|
init_domain_services();
|
|
5908
7000
|
init_tmux();
|
|
7001
|
+
function orderDispatchConflict(decision) {
|
|
7002
|
+
if (decision.eligible) return null;
|
|
7003
|
+
return {
|
|
7004
|
+
status: 409,
|
|
7005
|
+
body: {
|
|
7006
|
+
error: decision.message ?? "Order-book dispatch is blocked.",
|
|
7007
|
+
code: decision.code ?? "order-dispatch-blocked",
|
|
7008
|
+
conditions: decision.conditions
|
|
7009
|
+
}
|
|
7010
|
+
};
|
|
7011
|
+
}
|
|
5909
7012
|
/**
|
|
5910
7013
|
* PAN-2386: emit a dashboard activity event when start-agent refuses to spawn
|
|
5911
7014
|
* because the workspace is dirty. Kept as a pure function so the refusal path
|
|
@@ -5925,20 +7028,17 @@ function emitDirtyWorkspaceRefusalActivity(issueId, porcelain) {
|
|
|
5925
7028
|
});
|
|
5926
7029
|
} catch {}
|
|
5927
7030
|
}
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
]);
|
|
7031
|
+
/**
|
|
7032
|
+
* Both workspace runtime directories are Overdeck-owned in full, so both get a
|
|
7033
|
+
* blanket prefix match. The legacy `.pan/` allowlist only matched the collapsed
|
|
7034
|
+
* `?? .pan/` porcelain form plus a closed set of files — but this gate always
|
|
7035
|
+
* runs with `--untracked-files=all`, which expands untracked directories into
|
|
7036
|
+
* individual paths. A pipeline-authored `.pan/drafts/<ISSUE>.md` therefore read
|
|
7037
|
+
* as operator dirt and 409'd the planning→work auto-handoff (PAN-3042).
|
|
7038
|
+
*/
|
|
5937
7039
|
function isOverdeckWorkspaceRuntimePath(path) {
|
|
5938
7040
|
if (path === ".overdeck" || path.startsWith(".overdeck/")) return true;
|
|
5939
|
-
|
|
5940
|
-
if (LEGACY_WORKSPACE_RUNTIME_FILES.has(path)) return true;
|
|
5941
|
-
return path.startsWith(".pan/feedback/") || path.startsWith(".pan/review/") || path.startsWith(".pan/test/") || path.startsWith(".pan/handoff-");
|
|
7041
|
+
return path === ".pan" || path === ".pan/" || path.startsWith(".pan/");
|
|
5942
7042
|
}
|
|
5943
7043
|
/** True when git porcelain contains only Overdeck-owned workspace runtime files. */
|
|
5944
7044
|
function isOnlyOverdeckRuntimeWorkspaceChanges(porcelain) {
|
|
@@ -5974,6 +7074,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
|
|
|
5974
7074
|
const { issueId, projectId } = body;
|
|
5975
7075
|
const autoStart = body.auto === true;
|
|
5976
7076
|
const guardrailAcknowledged = body.guardrailAcknowledged === true;
|
|
7077
|
+
const offBook = body.offBook === true;
|
|
5977
7078
|
const requestedHostOverride = body.host === true || body.allowHost === true;
|
|
5978
7079
|
if (!issueId) return jsonResponse({ error: "issueId required" }, { status: 400 });
|
|
5979
7080
|
const legacyFields = [
|
|
@@ -6029,6 +7130,8 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
|
|
|
6029
7130
|
const resolvedProject = resolveProjectFromIssueSync(String(issueId));
|
|
6030
7131
|
const projectConfig = resolvedProject ? getProjectSync(resolvedProject.projectKey) : null;
|
|
6031
7132
|
const projectPath = projectConfig?.path ?? getProjectPath(projectId, issuePrefix);
|
|
7133
|
+
const orderConflict = orderDispatchConflict((yield* Effect.promise(() => checkActiveOrderDispatch(projectPath, issueId, { offBook }))).decision);
|
|
7134
|
+
if (orderConflict) return jsonResponse(orderConflict.body, { status: orderConflict.status });
|
|
6032
7135
|
const workspacePath = join(projectPath, "workspaces", `feature-${issueLower}`);
|
|
6033
7136
|
if (!existsSync(workspacePath)) try {
|
|
6034
7137
|
const nodeDir = dirname(process.execPath);
|
|
@@ -6128,7 +7231,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
|
|
|
6128
7231
|
let spawnModel;
|
|
6129
7232
|
try {
|
|
6130
7233
|
spawnModel = determineModel({
|
|
6131
|
-
model: body.model,
|
|
7234
|
+
model: body.model ?? resolveIssueWorkModel(issueId),
|
|
6132
7235
|
role,
|
|
6133
7236
|
spawnKey: `${role}:${issueId}`
|
|
6134
7237
|
});
|
|
@@ -6173,10 +7276,18 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
|
|
|
6173
7276
|
}
|
|
6174
7277
|
if (!isRemote) {
|
|
6175
7278
|
emitStartAgentPhase(issueId, "stackHealthGate", "start", "checking workspace docker stack health", { workspacePath });
|
|
6176
|
-
|
|
7279
|
+
let stackHealth = yield* getWorkspaceStackHealth(issueId, {
|
|
6177
7280
|
projectConfig,
|
|
6178
7281
|
workspacePath
|
|
6179
7282
|
});
|
|
7283
|
+
if (!stackHealth.healthy && !allowHost) {
|
|
7284
|
+
emitStartAgentPhase(issueId, "stackHealthGate", "start", "stack unhealthy — attempting workspace stack rebuild", { workspacePath });
|
|
7285
|
+
yield* Effect.promise(() => assertWorkspaceStackHealthyForSpawn(issueId, "work", false, workspacePath).catch(() => void 0));
|
|
7286
|
+
stackHealth = yield* getWorkspaceStackHealth(issueId, {
|
|
7287
|
+
projectConfig,
|
|
7288
|
+
workspacePath
|
|
7289
|
+
});
|
|
7290
|
+
}
|
|
6180
7291
|
if (!stackHealth.healthy) {
|
|
6181
7292
|
yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, "agent.start_blocked_stack_unhealthy", {
|
|
6182
7293
|
issueId,
|
|
@@ -6278,7 +7389,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
|
|
|
6278
7389
|
console.warn(`[start-agent] Failed to set plan.status=running (non-fatal): ${planStatusErr?.message ?? planStatusErr}`);
|
|
6279
7390
|
}
|
|
6280
7391
|
try {
|
|
6281
|
-
const { appendSessionEntry, getProjectConfigFromWorkspacePath, resolveProjectForIssue } = await import("./record-
|
|
7392
|
+
const { appendSessionEntry, getProjectConfigFromWorkspacePath, resolveProjectForIssue } = await import("./record-CtroO946.js");
|
|
6282
7393
|
await appendSessionEntry(resolveProjectForIssue(issueId) ?? getProjectConfigFromWorkspacePath(workspacePath), issueId, {
|
|
6283
7394
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6284
7395
|
reason: "start",
|
|
@@ -6291,7 +7402,10 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
|
|
|
6291
7402
|
if (getReviewStatusSync(issueId)?.stuckReason === "planning_auto_handoff_failed") clearWorkspaceStuck(issueId);
|
|
6292
7403
|
};
|
|
6293
7404
|
if (isRemote && workspaceMetadata) {
|
|
6294
|
-
const
|
|
7405
|
+
const admitted = yield* Effect.promise(() => withActiveOrderDispatchReservation(projectPath, issueId, {
|
|
7406
|
+
offBook,
|
|
7407
|
+
recordOverride: true
|
|
7408
|
+
}, () => spawnAfterClearingStartGates({
|
|
6295
7409
|
agentSessionName,
|
|
6296
7410
|
gate: startGateBlock,
|
|
6297
7411
|
initialState: initialAgentState,
|
|
@@ -6305,7 +7419,10 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
|
|
|
6305
7419
|
lifecycle
|
|
6306
7420
|
})),
|
|
6307
7421
|
isSuccessful: (remoteResponse) => remoteResponse.status >= 200 && remoteResponse.status < 300
|
|
6308
|
-
}));
|
|
7422
|
+
})));
|
|
7423
|
+
const admittedConflict = orderDispatchConflict(admitted.check.decision);
|
|
7424
|
+
if (admittedConflict) return jsonResponse(admittedConflict.body, { status: admittedConflict.status });
|
|
7425
|
+
const response = admitted.result;
|
|
6309
7426
|
if (response.status < 200 || response.status >= 300) return response;
|
|
6310
7427
|
yield* Effect.promise(commitClearedGates);
|
|
6311
7428
|
yield* Effect.promise(markWorkStartAccepted);
|
|
@@ -6373,11 +7490,14 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
|
|
|
6373
7490
|
console.warn(`[start-agent] Failed to check workspace status for ${issueId}: ${message}`);
|
|
6374
7491
|
}
|
|
6375
7492
|
const bodyHarness = body.harness;
|
|
6376
|
-
const userPickedHarness = bodyHarness === "ohmypi" || bodyHarness === "claude-code" || bodyHarness === "codex" ? bodyHarness : null;
|
|
7493
|
+
const userPickedHarness = bodyHarness === "ohmypi" || bodyHarness === "claude-code" || bodyHarness === "codex" || bodyHarness === "acp" ? bodyHarness : null;
|
|
6377
7494
|
let effectiveHarness = null;
|
|
6378
7495
|
if (userPickedHarness !== null) effectiveHarness = (yield* Effect.promise(async () => canUseHarnessSync(userPickedHarness, spawnModel, await getProviderAuthMode(spawnModel)))).allowed ? userPickedHarness : "claude-code";
|
|
6379
7496
|
const spawnPanCommand = async (args, cwd) => {
|
|
6380
|
-
const
|
|
7497
|
+
const admitted = await withActiveOrderDispatchReservation(projectPath, issueId, {
|
|
7498
|
+
offBook,
|
|
7499
|
+
recordOverride: false
|
|
7500
|
+
}, () => spawnAfterClearingStartGates({
|
|
6381
7501
|
agentSessionName,
|
|
6382
7502
|
gate: gatesCommitted ? null : startGateBlock,
|
|
6383
7503
|
initialState: initialAgentState,
|
|
@@ -6389,9 +7509,10 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
|
|
|
6389
7509
|
args,
|
|
6390
7510
|
cwd
|
|
6391
7511
|
})
|
|
6392
|
-
});
|
|
7512
|
+
}));
|
|
7513
|
+
if (!admitted.check.decision.eligible || !admitted.result) throw new OrderDispatchReservationError(admitted.check);
|
|
6393
7514
|
await commitClearedGates();
|
|
6394
|
-
return
|
|
7515
|
+
return admitted.result;
|
|
6395
7516
|
};
|
|
6396
7517
|
const updateIssueStatus = async () => {
|
|
6397
7518
|
await Effect.runPromise(lifecycle.transitionTo(issueId, "in_progress").pipe(Effect.catch(() => Effect.void)));
|
|
@@ -6475,7 +7596,8 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
|
|
|
6475
7596
|
issueId,
|
|
6476
7597
|
model: spawnModel,
|
|
6477
7598
|
harness: effectiveHarness,
|
|
6478
|
-
allowHost
|
|
7599
|
+
allowHost,
|
|
7600
|
+
offBook
|
|
6479
7601
|
}), workspacePath));
|
|
6480
7602
|
yield* Effect.promise(markWorkStartAccepted);
|
|
6481
7603
|
emitStartAgentPhase(issueId, "spawn", "success", "local work agent spawn requested", {
|
|
@@ -6506,6 +7628,10 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
|
|
|
6506
7628
|
fallbackStatus: fallbackState.status
|
|
6507
7629
|
}));
|
|
6508
7630
|
invalidateAgentsCache();
|
|
7631
|
+
if (error instanceof OrderDispatchReservationError) {
|
|
7632
|
+
const conflict = orderDispatchConflict(error.check.decision);
|
|
7633
|
+
return jsonResponse(conflict.body, { status: conflict.status });
|
|
7634
|
+
}
|
|
6509
7635
|
const output = String(error?.output ?? error?.message ?? "");
|
|
6510
7636
|
if (output.includes(`Workspace docker stack for ${issueId}`) && output.includes("is not healthy")) {
|
|
6511
7637
|
const failedStackHealth = yield* getWorkspaceStackHealth(issueId, {
|
|
@@ -6713,16 +7839,26 @@ const readJsonBody = Effect.gen(function* () {
|
|
|
6713
7839
|
const postMergeGuard = createInFlightGuard();
|
|
6714
7840
|
const _serverManagedMerges = /* @__PURE__ */ new Set();
|
|
6715
7841
|
function firePostMergeLifecycle(issueId, options) {
|
|
6716
|
-
const
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
7842
|
+
const parsedIssueId = parseIssueIdSync(issueId.trim());
|
|
7843
|
+
if (!parsedIssueId) {
|
|
7844
|
+
console.error(`[merge] Refusing post-merge lifecycle for invalid issue ID: ${issueId}`);
|
|
7845
|
+
return false;
|
|
7846
|
+
}
|
|
7847
|
+
const normalizedIssueId = parsedIssueId.normalized.toUpperCase();
|
|
7848
|
+
const started = postMergeGuard.run(normalizedIssueId, async () => {
|
|
7849
|
+
const projectPath = getProjectPathForIssue(parsedIssueId.prefix);
|
|
7850
|
+
const { postMergeLifecycle } = await import("./merge-agent-DrXa1CpW.js");
|
|
7851
|
+
const lifecycleOptions = {
|
|
7852
|
+
...options?.verifiedMergedRef ? { verifiedMergedRef: options.verifiedMergedRef } : {},
|
|
7853
|
+
...options?.verifiedMergedRepos?.length ? { verifiedMergedRepos: options.verifiedMergedRepos } : {}
|
|
7854
|
+
};
|
|
7855
|
+
await postMergeLifecycle(normalizedIssueId, projectPath, options?.sourceBranch, Object.keys(lifecycleOptions).length > 0 ? lifecycleOptions : void 0);
|
|
7856
|
+
console.log(`[merge] post-merge lifecycle completed for ${normalizedIssueId}`);
|
|
7857
|
+
const { runMergeTrainReconcile } = await import("./merge-train-DTEMyIu4.js");
|
|
7858
|
+
const outcomes = await runMergeTrainReconcile(normalizedIssueId);
|
|
7859
|
+
if (outcomes.length > 0) console.log(`[merge-train] reconciled ${outcomes.length} sibling(s) after ${normalizedIssueId}: ` + outcomes.map((o) => `${o.issueId}=${o.result}`).join(", "));
|
|
7860
|
+
}, (err) => console.error(`[merge] post-merge lifecycle failed for ${normalizedIssueId}:`, err));
|
|
7861
|
+
if (!started) console.log(`[merge] firePostMergeLifecycle: skipping ${normalizedIssueId} — already in flight`);
|
|
6726
7862
|
return started;
|
|
6727
7863
|
}
|
|
6728
7864
|
function getProjectPathForIssue(issuePrefix) {
|
|
@@ -6739,15 +7875,15 @@ init_io();
|
|
|
6739
7875
|
init_domain_services();
|
|
6740
7876
|
init_tmux();
|
|
6741
7877
|
const getSpecialistsRoute = HttpRouter.add("GET", "/api/specialists", httpHandler(Effect.gen(function* () {
|
|
6742
|
-
const { getAllSpecialistStatus, getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-
|
|
7878
|
+
const { getAllSpecialistStatus, getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
|
|
6743
7879
|
return jsonResponse({
|
|
6744
7880
|
specialists: yield* Effect.promise(() => getAllSpecialistStatus()),
|
|
6745
7881
|
projects: yield* Effect.promise(() => getAllProjectSpecialistStatuses())
|
|
6746
7882
|
});
|
|
6747
7883
|
})));
|
|
6748
7884
|
const postSpecialistsResetAllRoute = HttpRouter.add("POST", "/api/specialists/reset-all", httpHandler(Effect.gen(function* () {
|
|
6749
|
-
const { getAllSpecialists, isRunning, getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-
|
|
6750
|
-
const { clearHookSync } = yield* Effect.promise(() => import("./hooks-
|
|
7885
|
+
const { getAllSpecialists, isRunning, getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
|
|
7886
|
+
const { clearHookSync } = yield* Effect.promise(() => import("./hooks-BnJsxlSq.js"));
|
|
6751
7887
|
const specialists = getAllSpecialists();
|
|
6752
7888
|
const results = [];
|
|
6753
7889
|
for (const specialist of specialists) {
|
|
@@ -6782,7 +7918,7 @@ const postSpecialistsResetAllRoute = HttpRouter.add("POST", "/api/specialists/re
|
|
|
6782
7918
|
const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done", httpHandler(Effect.gen(function* () {
|
|
6783
7919
|
const body = yield* readJsonBody;
|
|
6784
7920
|
const eventStore = yield* EventStoreService;
|
|
6785
|
-
const { specialist, issueId, status, notes, itemId } = body;
|
|
7921
|
+
const { specialist, issueId, status, notes, itemId, runId } = body;
|
|
6786
7922
|
const validSpecialists = [
|
|
6787
7923
|
"review",
|
|
6788
7924
|
"test",
|
|
@@ -6803,7 +7939,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
|
|
|
6803
7939
|
}
|
|
6804
7940
|
console.log(`[specialists/done] ${specialist} signaling ${status} for ${normalizedIssueId}`);
|
|
6805
7941
|
if (specialist === "merge") {
|
|
6806
|
-
const { reportSpecialistCompletion } = yield* Effect.promise(() => import("./specialist-completion-
|
|
7942
|
+
const { reportSpecialistCompletion } = yield* Effect.promise(() => import("./specialist-completion-Br48eiDP.js"));
|
|
6807
7943
|
if (yield* reportSpecialistCompletion(normalizedIssueId, {
|
|
6808
7944
|
status,
|
|
6809
7945
|
notes
|
|
@@ -6847,10 +7983,27 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
|
|
|
6847
7983
|
break;
|
|
6848
7984
|
}
|
|
6849
7985
|
const updatedStatus = setReviewStatusSync(normalizedIssueId, update);
|
|
6850
|
-
if (specialist === "inspect" && status === "failed")
|
|
7986
|
+
if (specialist === "inspect" && status === "failed") {
|
|
7987
|
+
yield* Effect.promise(() => reportTieredInspectFailureEscalation(normalizedIssueId, notes));
|
|
7988
|
+
yield* Effect.promise(async () => {
|
|
7989
|
+
try {
|
|
7990
|
+
const failedItemId = itemId ?? getReviewStatusSync(normalizedIssueId)?.inspectBeadId ?? void 0;
|
|
7991
|
+
const project = resolveProjectFromIssueSync(normalizedIssueId);
|
|
7992
|
+
if (project && failedItemId) {
|
|
7993
|
+
const workspacePath = join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`);
|
|
7994
|
+
if (existsSync(workspacePath)) {
|
|
7995
|
+
const { onInspectComplete } = await import("./inspect-agent-BVwDrVrz.js");
|
|
7996
|
+
await Effect.runPromise(onInspectComplete(project.projectKey, normalizedIssueId, failedItemId, "failed", workspacePath, notes));
|
|
7997
|
+
}
|
|
7998
|
+
}
|
|
7999
|
+
} catch (err) {
|
|
8000
|
+
console.error(`[specialists/done] Error delivering failed inspect verdict for ${normalizedIssueId}:`, err);
|
|
8001
|
+
}
|
|
8002
|
+
});
|
|
8003
|
+
}
|
|
6851
8004
|
yield* Effect.promise(async () => {
|
|
6852
8005
|
try {
|
|
6853
|
-
const { getTmuxSessionName, updateRunMetadata, makeSpecialistRegistryKey } = await import("./specialists-
|
|
8006
|
+
const { getTmuxSessionName, updateRunMetadata, makeSpecialistRegistryKey } = await import("./specialists-Tdezg_6N.js");
|
|
6854
8007
|
const projectKey = resolveProjectFromIssueSync(normalizedIssueId)?.projectKey;
|
|
6855
8008
|
const tmuxSession = projectKey ? getTmuxSessionName(`${specialist}-agent`, projectKey, normalizedIssueId) : getTmuxSessionName(`${specialist}-agent`);
|
|
6856
8009
|
saveAgentRuntimeState(tmuxSession, {
|
|
@@ -6869,7 +8022,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
|
|
|
6869
8022
|
});
|
|
6870
8023
|
console.log(`[specialists/done] Cleared registry lock for ${registryKey} (${projectKey})`);
|
|
6871
8024
|
}
|
|
6872
|
-
const { updateSpecialistHandoffStatus } = await import("./specialist-handoff-logger-
|
|
8025
|
+
const { updateSpecialistHandoffStatus } = await import("./specialist-handoff-logger-DK-GTz4s.js");
|
|
6873
8026
|
if (await Effect.runPromise(updateSpecialistHandoffStatus(normalizedIssueId, `${specialist}-agent`, status === "passed" ? "completed" : "failed", status === "passed" ? "success" : "failure"))) console.log(`[specialists/done] Updated handoff log: ${specialist}-agent ${normalizedIssueId} → ${status}`);
|
|
6874
8027
|
} catch (err) {
|
|
6875
8028
|
console.error(`[specialists/done] Error managing specialist state:`, err);
|
|
@@ -6881,10 +8034,12 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
|
|
|
6881
8034
|
if (project) {
|
|
6882
8035
|
const workspacePath = join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`);
|
|
6883
8036
|
if (existsSync(workspacePath)) {
|
|
6884
|
-
const {
|
|
6885
|
-
const
|
|
6886
|
-
|
|
6887
|
-
|
|
8037
|
+
const { formatAnchorShort, snapshotWorkspaceHeadsPromise } = await import("./git-utils-CI6cwyyC.js");
|
|
8038
|
+
const headAnchor = await snapshotWorkspaceHeadsPromise(normalizedIssueId, workspacePath);
|
|
8039
|
+
if (headAnchor) {
|
|
8040
|
+
setReviewStatusSync(normalizedIssueId, { reviewedAtCommit: headAnchor });
|
|
8041
|
+
console.log(`[specialists/done] Snapshotted reviewedAtCommit=${formatAnchorShort(headAnchor)} for ${normalizedIssueId}`);
|
|
8042
|
+
}
|
|
6888
8043
|
}
|
|
6889
8044
|
}
|
|
6890
8045
|
} catch (err) {
|
|
@@ -6893,11 +8048,11 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
|
|
|
6893
8048
|
});
|
|
6894
8049
|
if (specialist === "inspect" && status === "passed") yield* Effect.promise(async () => {
|
|
6895
8050
|
try {
|
|
6896
|
-
const { onInspectComplete } = await import("./inspect-agent-
|
|
8051
|
+
const { onInspectComplete } = await import("./inspect-agent-BVwDrVrz.js");
|
|
6897
8052
|
const project = resolveProjectFromIssueSync(normalizedIssueId);
|
|
6898
8053
|
if (project) {
|
|
6899
8054
|
const workspacePath = join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`);
|
|
6900
|
-
if (existsSync(workspacePath)) await Effect.runPromise(onInspectComplete(project.projectKey, normalizedIssueId, itemId, "passed", workspacePath));
|
|
8055
|
+
if (existsSync(workspacePath)) await Effect.runPromise(onInspectComplete(project.projectKey, normalizedIssueId, itemId, "passed", workspacePath, notes));
|
|
6901
8056
|
}
|
|
6902
8057
|
} catch (err) {
|
|
6903
8058
|
console.error(`[specialists/done] Error saving inspect checkpoint:`, err);
|
|
@@ -6909,7 +8064,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
|
|
|
6909
8064
|
if (project) {
|
|
6910
8065
|
if (existsSync(join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`))) {
|
|
6911
8066
|
setReviewStatusSync(normalizedIssueId, { testStatus: "passed" });
|
|
6912
|
-
const { initEventStore } = await import("./event-store-
|
|
8067
|
+
const { initEventStore } = await import("./event-store-ELqkVOn0.js");
|
|
6913
8068
|
await (await initEventStore()).appendAsync({
|
|
6914
8069
|
type: "test.passed",
|
|
6915
8070
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -6962,8 +8117,8 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
|
|
|
6962
8117
|
if (notes?.toLowerCase().includes("conflict")) yield* Effect.promise(async () => {
|
|
6963
8118
|
try {
|
|
6964
8119
|
const workAgentId = `agent-${normalizedIssueId.toLowerCase()}`;
|
|
6965
|
-
const { sessionExists } = await import("./tmux-
|
|
6966
|
-
const { messageAgent, spawnAgent, getAgentStateSync } = await import("./agents-
|
|
8120
|
+
const { sessionExists } = await import("./tmux-DQ6jB76i.js");
|
|
8121
|
+
const { messageAgent, spawnAgent, getAgentStateSync } = await import("./agents-BKKkxKgK.js");
|
|
6967
8122
|
if (await Effect.runPromise(sessionExists(workAgentId))) {
|
|
6968
8123
|
await messageAgent(workAgentId, `MERGE CONFLICT: The merge-agent could not rebase your branch onto main due to conflicts. Please fix this now:\n\n1. git fetch origin main\n2. git rebase origin/main\n3. Resolve any conflicts (git add <file> && git rebase --continue)\n4. git push --force-with-lease\n5. Resubmit: curl -s -X POST http://localhost:3011/api/review/${normalizedIssueId}/request -H "Content-Type: application/json" -d "{}"\n\nConflict details: ${notes}`);
|
|
6969
8124
|
console.log(`[specialists/done] Sent rebase instructions to ${workAgentId}`);
|
|
@@ -6977,19 +8132,38 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
|
|
|
6977
8132
|
try {
|
|
6978
8133
|
const project = resolveProjectFromIssueSync(normalizedIssueId);
|
|
6979
8134
|
const workspacePath = project ? join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`) : void 0;
|
|
6980
|
-
const { deliverReviewVerdictFeedback } = await import("./review-verdict-feedback-
|
|
8135
|
+
const { deliverReviewVerdictFeedback } = await import("./review-verdict-feedback-BPDn4li_.js");
|
|
6981
8136
|
const result = await Effect.runPromise(deliverReviewVerdictFeedback({
|
|
6982
8137
|
issueId: normalizedIssueId,
|
|
6983
8138
|
verdict: status === "failed" ? "failed" : "blocked",
|
|
6984
8139
|
notes,
|
|
6985
8140
|
workspacePath,
|
|
6986
|
-
prUrl: updatedStatus.prUrl
|
|
8141
|
+
prUrl: updatedStatus.prUrl,
|
|
8142
|
+
...runId ? { runId } : {}
|
|
6987
8143
|
}));
|
|
6988
8144
|
console.log(`[specialists/done] Delivered review verdict feedback for ${normalizedIssueId} (feedback=${result.feedbackPath ?? "none"}, synthesis=${result.synthesisPath ?? "none"}, prComment=${result.prCommentPosted})`);
|
|
6989
8145
|
} catch (err) {
|
|
6990
8146
|
console.warn(`[specialists/done] Failed to deliver review verdict feedback: ${err.message}`);
|
|
6991
8147
|
}
|
|
6992
8148
|
});
|
|
8149
|
+
if (specialist === "review" && status === "failed") yield* Effect.promise(async () => {
|
|
8150
|
+
try {
|
|
8151
|
+
const project = resolveProjectFromIssueSync(normalizedIssueId);
|
|
8152
|
+
if (project) {
|
|
8153
|
+
const workspacePath = join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`);
|
|
8154
|
+
if (existsSync(workspacePath)) {
|
|
8155
|
+
const { formatAnchorShort, snapshotWorkspaceHeadsPromise } = await import("./git-utils-CI6cwyyC.js");
|
|
8156
|
+
const reviewedAtCommit = await snapshotWorkspaceHeadsPromise(normalizedIssueId, workspacePath);
|
|
8157
|
+
if (reviewedAtCommit) {
|
|
8158
|
+
setReviewStatusSync(normalizedIssueId, { reviewedAtCommit });
|
|
8159
|
+
console.log(`[specialists/done] Snapshotted blocked reviewedAtCommit=${formatAnchorShort(reviewedAtCommit)} for ${normalizedIssueId}`);
|
|
8160
|
+
}
|
|
8161
|
+
}
|
|
8162
|
+
}
|
|
8163
|
+
} catch (err) {
|
|
8164
|
+
console.error(`[specialists/done] Failed to snapshot blocked reviewedAtCommit for ${normalizedIssueId}:`, err);
|
|
8165
|
+
}
|
|
8166
|
+
});
|
|
6993
8167
|
const eventRole = specialistEventRole(specialist);
|
|
6994
8168
|
if (eventRole) if (status === "passed") yield* eventStore.append({
|
|
6995
8169
|
type: "specialist.completed",
|
|
@@ -7018,7 +8192,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
|
|
|
7018
8192
|
});
|
|
7019
8193
|
})));
|
|
7020
8194
|
const postSpecialistsLogsCleanupAllRoute = HttpRouter.add("POST", "/api/specialists/logs/cleanup-all", httpHandler(Effect.gen(function* () {
|
|
7021
|
-
const { cleanupAllLogsSync } = yield* Effect.promise(() => import("./specialist-logs-
|
|
8195
|
+
const { cleanupAllLogsSync } = yield* Effect.promise(() => import("./specialist-logs-Cl5-hqT8.js"));
|
|
7022
8196
|
const results = cleanupAllLogsSync();
|
|
7023
8197
|
return jsonResponse({
|
|
7024
8198
|
success: true,
|
|
@@ -7028,7 +8202,7 @@ const postSpecialistsLogsCleanupAllRoute = HttpRouter.add("POST", "/api/speciali
|
|
|
7028
8202
|
});
|
|
7029
8203
|
})));
|
|
7030
8204
|
const getSpecialistsProjectsRoute = HttpRouter.add("GET", "/api/specialists/projects", httpHandler(Effect.gen(function* () {
|
|
7031
|
-
const { getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-
|
|
8205
|
+
const { getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
|
|
7032
8206
|
return jsonResponse(yield* Effect.promise(() => getAllProjectSpecialistStatuses()));
|
|
7033
8207
|
})));
|
|
7034
8208
|
const postSpecialistWakeRoute = HttpRouter.add("POST", "/api/specialists/:name/wake", httpHandler(Effect.gen(function* () {
|
|
@@ -7072,7 +8246,7 @@ const postSpecialistReportStatusRoute = HttpRouter.add("POST", "/api/specialists
|
|
|
7072
8246
|
"blocked",
|
|
7073
8247
|
"failed"
|
|
7074
8248
|
].includes(status)) {
|
|
7075
|
-
const { getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-
|
|
8249
|
+
const { getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
|
|
7076
8250
|
saveAgentRuntimeState(getTmuxSessionName(name), {
|
|
7077
8251
|
state: "idle",
|
|
7078
8252
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -7171,6 +8345,7 @@ const specialistsLegacyRouteLayer = Layer.mergeAll(getSpecialistsRoute, getSpeci
|
|
|
7171
8345
|
//#endregion
|
|
7172
8346
|
//#region routes/specialists/project-routes.ts
|
|
7173
8347
|
init_config_yaml();
|
|
8348
|
+
init_project_repos();
|
|
7174
8349
|
init_projects();
|
|
7175
8350
|
init_review_status();
|
|
7176
8351
|
init_tmux();
|
|
@@ -7193,7 +8368,7 @@ const postProjectSpecialistKillRoute = HttpRouter.add("POST", "/api/specialists/
|
|
|
7193
8368
|
const issueId = params["issueId"];
|
|
7194
8369
|
const type = params["type"];
|
|
7195
8370
|
if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
|
|
7196
|
-
const { getTmuxSessionName, makeSpecialistRegistryKey, getRunMetadata } = yield* Effect.promise(() => import("./specialists-
|
|
8371
|
+
const { getTmuxSessionName, makeSpecialistRegistryKey, getRunMetadata } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
|
|
7197
8372
|
const tmuxSession = getRunMetadata(project, makeSpecialistRegistryKey(type, issueId)).tmuxSession ?? getTmuxSessionName(type, project, issueId);
|
|
7198
8373
|
yield* Effect.promise(() => Effect.runPromise(killSession(tmuxSession)).catch(() => {}));
|
|
7199
8374
|
saveAgentRuntimeState(tmuxSession, {
|
|
@@ -7220,7 +8395,7 @@ const getProjectSpecialistRunsRoute = HttpRouter.add("GET", "/api/specialists/:p
|
|
|
7220
8395
|
if (limitParam) limit = parseInt(limitParam, 10);
|
|
7221
8396
|
if (offsetParam) offset = parseInt(offsetParam, 10);
|
|
7222
8397
|
}
|
|
7223
|
-
const { listRunLogsSync } = yield* Effect.promise(() => import("./specialist-logs-
|
|
8398
|
+
const { listRunLogsSync } = yield* Effect.promise(() => import("./specialist-logs-Cl5-hqT8.js"));
|
|
7224
8399
|
return jsonResponse(listRunLogsSync(project, type, {
|
|
7225
8400
|
limit,
|
|
7226
8401
|
offset
|
|
@@ -7231,7 +8406,7 @@ const getProjectSpecialistRunStreamRoute = HttpRouter.add("GET", "/api/specialis
|
|
|
7231
8406
|
const project = params["project"];
|
|
7232
8407
|
const type = params["type"];
|
|
7233
8408
|
const runId = params["runId"];
|
|
7234
|
-
const { getRunLogPath, isRunLogActive } = yield* Effect.promise(() => import("./specialist-logs-
|
|
8409
|
+
const { getRunLogPath, isRunLogActive } = yield* Effect.promise(() => import("./specialist-logs-Cl5-hqT8.js"));
|
|
7235
8410
|
const logPath = getRunLogPath(project, type, runId);
|
|
7236
8411
|
if (!existsSync(logPath)) return jsonResponse({ error: "Run log not found" }, { status: 404 });
|
|
7237
8412
|
const encoder = new TextEncoder();
|
|
@@ -7290,7 +8465,7 @@ const getProjectSpecialistRunRoute = HttpRouter.add("GET", "/api/specialists/:pr
|
|
|
7290
8465
|
const project = params["project"];
|
|
7291
8466
|
const type = params["type"];
|
|
7292
8467
|
const runId = params["runId"];
|
|
7293
|
-
const { getRunLogSync, parseLogMetadata } = yield* Effect.promise(() => import("./specialist-logs-
|
|
8468
|
+
const { getRunLogSync, parseLogMetadata } = yield* Effect.promise(() => import("./specialist-logs-Cl5-hqT8.js"));
|
|
7294
8469
|
const content = getRunLogSync(project, type, runId);
|
|
7295
8470
|
if (!content) return jsonResponse({ error: "Run log not found" }, { status: 404 });
|
|
7296
8471
|
return jsonResponse({
|
|
@@ -7304,7 +8479,7 @@ const postProjectSpecialistRunTerminateRoute = HttpRouter.add("POST", "/api/spec
|
|
|
7304
8479
|
const project = params["project"];
|
|
7305
8480
|
const type = params["type"];
|
|
7306
8481
|
if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
|
|
7307
|
-
const { terminateSpecialist } = yield* Effect.promise(() => import("./specialists-
|
|
8482
|
+
const { terminateSpecialist } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
|
|
7308
8483
|
yield* Effect.promise(() => terminateSpecialist(project, type));
|
|
7309
8484
|
return jsonResponse({
|
|
7310
8485
|
success: true,
|
|
@@ -7316,7 +8491,7 @@ const postProjectSpecialistGracePauseRoute = HttpRouter.add("POST", "/api/specia
|
|
|
7316
8491
|
const project = params["project"];
|
|
7317
8492
|
const type = params["type"];
|
|
7318
8493
|
if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
|
|
7319
|
-
const { pauseGracePeriod } = yield* Effect.promise(() => import("./specialists-
|
|
8494
|
+
const { pauseGracePeriod } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
|
|
7320
8495
|
if (pauseGracePeriod(project, type)) return jsonResponse({
|
|
7321
8496
|
success: true,
|
|
7322
8497
|
message: "Grace period paused"
|
|
@@ -7328,7 +8503,7 @@ const postProjectSpecialistGraceResumeRoute = HttpRouter.add("POST", "/api/speci
|
|
|
7328
8503
|
const project = params["project"];
|
|
7329
8504
|
const type = params["type"];
|
|
7330
8505
|
if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
|
|
7331
|
-
const { resumeGracePeriod } = yield* Effect.promise(() => import("./specialists-
|
|
8506
|
+
const { resumeGracePeriod } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
|
|
7332
8507
|
if (resumeGracePeriod(project, type)) return jsonResponse({
|
|
7333
8508
|
success: true,
|
|
7334
8509
|
message: "Grace period resumed"
|
|
@@ -7340,7 +8515,7 @@ const postProjectSpecialistGraceExitRoute = HttpRouter.add("POST", "/api/special
|
|
|
7340
8515
|
const project = params["project"];
|
|
7341
8516
|
const type = params["type"];
|
|
7342
8517
|
if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
|
|
7343
|
-
const { exitGracePeriod } = yield* Effect.promise(() => import("./specialists-
|
|
8518
|
+
const { exitGracePeriod } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
|
|
7344
8519
|
exitGracePeriod(project, type);
|
|
7345
8520
|
return jsonResponse({
|
|
7346
8521
|
success: true,
|
|
@@ -7352,7 +8527,7 @@ const getProjectSpecialistGraceRoute = HttpRouter.add("GET", "/api/specialists/:
|
|
|
7352
8527
|
const project = params["project"];
|
|
7353
8528
|
const type = params["type"];
|
|
7354
8529
|
if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
|
|
7355
|
-
const { getGracePeriodState } = yield* Effect.promise(() => import("./specialists-
|
|
8530
|
+
const { getGracePeriodState } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
|
|
7356
8531
|
const state = getGracePeriodState(project, type);
|
|
7357
8532
|
if (state) return jsonResponse(state);
|
|
7358
8533
|
else return jsonResponse({ error: "No active grace period" }, { status: 404 });
|
|
@@ -7361,7 +8536,7 @@ const getProjectSpecialistContextRoute = HttpRouter.add("GET", "/api/specialists
|
|
|
7361
8536
|
const params = yield* HttpRouter.params;
|
|
7362
8537
|
const project = params["project"];
|
|
7363
8538
|
const type = params["type"];
|
|
7364
|
-
const { loadContextDigest } = yield* Effect.promise(() => import("./specialist-context-
|
|
8539
|
+
const { loadContextDigest } = yield* Effect.promise(() => import("./specialist-context-cVbGnTa3.js"));
|
|
7365
8540
|
const digest = loadContextDigest(project, type);
|
|
7366
8541
|
if (digest) return jsonResponse({ digest });
|
|
7367
8542
|
else return jsonResponse({ error: "No context digest found" }, { status: 404 });
|
|
@@ -7370,7 +8545,7 @@ const postProjectSpecialistContextRegenerateRoute = HttpRouter.add("POST", "/api
|
|
|
7370
8545
|
const params = yield* HttpRouter.params;
|
|
7371
8546
|
const project = params["project"];
|
|
7372
8547
|
const type = params["type"];
|
|
7373
|
-
const { regenerateContextDigest } = yield* Effect.promise(() => import("./specialist-context-
|
|
8548
|
+
const { regenerateContextDigest } = yield* Effect.promise(() => import("./specialist-context-cVbGnTa3.js"));
|
|
7374
8549
|
const digest = yield* Effect.promise(() => regenerateContextDigest(project, type));
|
|
7375
8550
|
if (digest) return jsonResponse({
|
|
7376
8551
|
digest,
|
|
@@ -7389,7 +8564,7 @@ const postProjectSpecialistCompleteRoute = HttpRouter.add("POST", "/api/speciali
|
|
|
7389
8564
|
"blocked"
|
|
7390
8565
|
].includes(status)) return jsonResponse({ error: "Valid status (passed/failed/blocked) is required" }, { status: 400 });
|
|
7391
8566
|
if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
|
|
7392
|
-
const { signalSpecialistCompletion } = yield* Effect.promise(() => import("./specialists-
|
|
8567
|
+
const { signalSpecialistCompletion } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
|
|
7393
8568
|
signalSpecialistCompletion(project, type, {
|
|
7394
8569
|
status,
|
|
7395
8570
|
notes
|
|
@@ -7423,8 +8598,8 @@ const postProjectSpecialistLogsCleanupRoute = HttpRouter.add("POST", "/api/speci
|
|
|
7423
8598
|
const params = yield* HttpRouter.params;
|
|
7424
8599
|
const project = params["project"];
|
|
7425
8600
|
const type = params["type"];
|
|
7426
|
-
const { cleanupOldLogsSync } = yield* Effect.promise(() => import("./specialist-logs-
|
|
7427
|
-
const { getSpecialistRetention } = yield* Effect.promise(() => import("./projects-
|
|
8601
|
+
const { cleanupOldLogsSync } = yield* Effect.promise(() => import("./specialist-logs-Cl5-hqT8.js"));
|
|
8602
|
+
const { getSpecialistRetention } = yield* Effect.promise(() => import("./projects-B03Wr-xc.js"));
|
|
7428
8603
|
const retention = getSpecialistRetention(project);
|
|
7429
8604
|
const deleted = cleanupOldLogsSync(project, type, {
|
|
7430
8605
|
maxDays: retention.max_days,
|
|
@@ -7445,21 +8620,23 @@ const postProjectReviewRestartRoute = HttpRouter.add("POST", "/api/specialists/:
|
|
|
7445
8620
|
const project = params["project"];
|
|
7446
8621
|
const issueId = params["issueId"];
|
|
7447
8622
|
const { model, harness } = yield* readJsonBody;
|
|
7448
|
-
const { killAllReviewerSessions } = yield* Effect.promise(() => import("./review-agent-
|
|
8623
|
+
const { killAllReviewerSessions } = yield* Effect.promise(() => import("./review-agent-CD4RCI0Y.js"));
|
|
7449
8624
|
const killResult = yield* killAllReviewerSessions(project, issueId);
|
|
7450
8625
|
const projectConfig = resolveProjectFromIssueSync(issueId);
|
|
7451
8626
|
if (!projectConfig) return jsonResponse({ error: `Cannot resolve project for ${issueId}` }, { status: 404 });
|
|
7452
8627
|
const workspacePath = join(projectConfig.projectPath, "workspaces", `feature-${issueId.toLowerCase()}`);
|
|
7453
8628
|
if (!existsSync(workspacePath)) return jsonResponse({ error: `Workspace not found: ${workspacePath}` }, { status: 404 });
|
|
7454
|
-
|
|
8629
|
+
const primaryRepo = resolveWorkspaceRepoRootsSync(issueId, workspacePath)[0];
|
|
8630
|
+
let branch = primaryRepo.sourceBranch;
|
|
7455
8631
|
try {
|
|
7456
|
-
const { stdout } = yield* Effect.promise(() => execAsync(
|
|
8632
|
+
const { stdout } = yield* Effect.promise(() => execAsync("git branch --show-current", {
|
|
8633
|
+
cwd: resolvePrimaryWorkspaceRepoDirSync(issueId, workspacePath),
|
|
7457
8634
|
encoding: "utf-8",
|
|
7458
8635
|
timeout: 5e3
|
|
7459
8636
|
}));
|
|
7460
|
-
branch = stdout.trim() ||
|
|
8637
|
+
branch = stdout.trim() || primaryRepo.sourceBranch;
|
|
7461
8638
|
} catch {}
|
|
7462
|
-
const { spawnReviewRoleForIssue } = yield* Effect.promise(() => import("./review-agent-
|
|
8639
|
+
const { spawnReviewRoleForIssue } = yield* Effect.promise(() => import("./review-agent-CD4RCI0Y.js"));
|
|
7463
8640
|
const prUrl = getReviewStatusSync(issueId)?.prUrl;
|
|
7464
8641
|
const result = yield* spawnReviewRoleForIssue({
|
|
7465
8642
|
issueId,
|
|
@@ -7608,6 +8785,6 @@ const specialistsProjectRouteLayer = Layer.mergeAll(getProjectSpecialistStatusRo
|
|
|
7608
8785
|
*/
|
|
7609
8786
|
const specialistsRouteLayer = Layer.mergeAll(specialistsLegacyRouteLayer, specialistsProjectRouteLayer);
|
|
7610
8787
|
//#endregion
|
|
7611
|
-
export {
|
|
8788
|
+
export { composerCommandConfirmationFromBody as $, resolveAllowedHarness as A, cleanupUnreferencedConversationAttachments as B, handleConversationCreate as C, isTrustedOriginForHost as Ct, handleConversationStop as D, getDefaultCwd as Dt, handleConversationResume as E, httpHandler as Et, tmuxSessionExists as F, isManagedConversationAttachmentPath as G, extractConversationAttachmentPaths as H, waitForPiTuiReady as I, composerCommandResultHttpStatus as J, removeConversationAttachment as K, waitForTmuxSession as L, spawnConversationSession as M, startConversationModelBackfill as N, handleConversationSwitchModel as O, jsonResponse as Ot, stopConversationRuntime as P, ComposerCommandConfirmationError as Q, cleanupConversationAttachments as R, conversationSessionAliveFromState as S, getHeaderFromMap as St, handleConversationRestartAll as T, validateOriginHeaders as Tt, getConversationAttachmentsRoot as U, ensureConversationAttachmentDir as V, hasConversationAttachment as W, resolvePolicy as X, handleComposerCommandMessage as Y, ComposerCommandParseError as Z, isPiControlChannelHarness as _, rejectUnauthorizedDashboardRequest as _t, agentsRouteLayer as a, panCliInvocation as at, validateConversationControlAckOrigin as b, isRespawnPending as bt, codexConversationPendingInput as c, resolveAcpTranscriptPath as ct, handleConversationCodexApproval as d, resolveCodexRolloutPath as dt, evaluateSpawnGuardrails as et, handleConversationCompact as f, resolveJsonlPath as ft, handleConversationThinkingLevel as g, hasDashboardInternalToken as gt, handleConversationPlanAction as h, dashboardSessionCookieHeader as ht, validateSpecialistAutoCompleteMetadata as i, getWorkAgentLifecycleStateSync as it, shouldReportUnresolvedLiveSession as j, isInsideGitWorkTree as k, jsonStringResponse as kt, deliverConversationViaControlChannel as l, resolveAgentHarness as lt, handleConversationDeliveryMethod as m, dashboardCsrfToken as mt, _serverManagedMerges as n, resolveAgentGitInfo as nt, saveAgentStateAndEmitEvent as o, spawnPanCli as ot, handleConversationControlAck as p, resolvePiSessionPath as pt, runInBatches as q, firePostMergeLifecycle as r, resolveConversationGitInfo as rt, saveAgentStateAndEmitEventProgram as s, readLauncherPinnedSessionId as st, specialistsRouteLayer as t, invalidateAgentsCache as tt, handleConversationAbort as u, resolveClaudeSessionId as ut, pickDeliverAs as v, rejectUnauthorizedDashboardSessionMintRequest as vt, handleConversationDelete as w, validateOrigin as wt, conversationNeedsRunningRepair as x, waitForSessionRespawn as xt, resolveConversationDeliveryMethod as y, rejectUnsafeDashboardMutationRequest as yt, cleanupOrphanedConversationAttachments as z };
|
|
7612
8789
|
|
|
7613
|
-
//# sourceMappingURL=specialists-
|
|
8790
|
+
//# sourceMappingURL=specialists-CNfYO3G2.js.map
|