@ours.network/fleet 1.2.0-nightly.3 → 1.2.0-nightly.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +158 -28
- package/dist/account-origin.d.ts +2 -0
- package/dist/account-origin.js +6 -0
- package/dist/agent-ours/bridge.d.ts +3 -0
- package/dist/agent-ours/bridge.js +178 -0
- package/dist/agent-ours/controller.d.ts +14 -0
- package/dist/agent-ours/controller.js +60 -0
- package/dist/agent-ours/correspondence.d.ts +10 -0
- package/dist/agent-ours/correspondence.js +1 -0
- package/dist/agent-ours/file-rpc.d.ts +23 -0
- package/dist/agent-ours/file-rpc.js +123 -0
- package/dist/agent-ours/harness.d.ts +14 -0
- package/dist/agent-ours/harness.js +72 -0
- package/dist/agent-ours/mcp-endpoint.d.ts +15 -0
- package/dist/agent-ours/mcp-endpoint.js +111 -0
- package/dist/agent-ours/runtime.d.ts +59 -0
- package/dist/agent-ours/runtime.js +283 -0
- package/dist/agent-ours/service.d.ts +23 -0
- package/dist/agent-ours/service.js +372 -0
- package/dist/agent-ours/state.d.ts +45 -0
- package/dist/agent-ours/state.js +95 -0
- package/dist/agent-ours/wire.d.ts +14 -0
- package/dist/agent-ours/wire.js +55 -0
- package/dist/application/conversation-history.d.ts +25 -0
- package/dist/application/conversation-history.js +189 -0
- package/dist/application/fleet-query-service.d.ts +1 -1
- package/dist/application/fleet-query-service.js +2 -2
- package/dist/application/legacy-supervisor-identity.d.ts +7 -0
- package/dist/application/legacy-supervisor-identity.js +43 -0
- package/dist/application/model-catalog.d.ts +1 -1
- package/dist/application/model-catalog.js +5 -2
- package/dist/application/preset-provenance.d.ts +19 -0
- package/dist/application/preset-provenance.js +59 -0
- package/dist/application/room-layout-definitions.d.ts +45 -0
- package/dist/application/room-layout-definitions.js +130 -0
- package/dist/application/session-control.d.ts +18 -0
- package/dist/application/session-control.js +54 -3
- package/dist/application/supervisor-ours-tools.d.ts +159 -0
- package/dist/application/supervisor-ours-tools.js +113 -0
- package/dist/application/task-layouts.d.ts +91 -0
- package/dist/application/task-layouts.js +249 -0
- package/dist/application/task-room-service.d.ts +99 -3
- package/dist/application/task-room-service.js +296 -31
- package/dist/atomic-file.d.ts +5 -0
- package/dist/atomic-file.js +62 -1
- package/dist/briefing.js +10 -59
- package/dist/build-info.json +6 -4
- package/dist/capabilities.d.ts +3 -1
- package/dist/capabilities.js +4 -0
- package/dist/cli.js +131 -5
- package/dist/client-profile.d.ts +5 -16
- package/dist/client-profile.js +4 -112
- package/dist/config.d.ts +4 -0
- package/dist/config.js +7 -0
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +5 -16
- package/dist/daemon-recovery.d.ts +1 -8
- package/dist/daemon-recovery.js +8 -113
- package/dist/docs.d.ts +3 -3
- package/dist/docs.js +43 -23
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +46 -59
- package/dist/erased-resources.d.ts +9 -0
- package/dist/erased-resources.js +87 -0
- package/dist/fleet-command-audit.d.ts +1 -0
- package/dist/fleet-command-audit.js +12 -7
- package/dist/harness/agent-session.d.ts +2 -0
- package/dist/harness/claude-code-session.js +4 -2
- package/dist/harness/codex-app-server-proxy.d.ts +4 -1
- package/dist/harness/codex-app-server-proxy.js +41 -5
- package/dist/harness/codex-session.js +9 -2
- package/dist/harness/codex.d.ts +3 -2
- package/dist/harness/codex.js +4 -3
- package/dist/harness/hermes-session.js +7 -1
- package/dist/init-wizard.js +20 -9
- package/dist/isolation/policy.js +2 -0
- package/dist/isolation/types.d.ts +5 -0
- package/dist/monitor.d.ts +1 -34
- package/dist/monitor.js +24 -151
- package/dist/notifications/fleet-producer.d.ts +18 -0
- package/dist/notifications/fleet-producer.js +129 -0
- package/dist/notifications/outbox.d.ts +31 -0
- package/dist/notifications/outbox.js +104 -0
- package/dist/owner-channel/channel.d.ts +3 -0
- package/dist/owner-channel/channel.js +34 -6
- package/dist/owner-channel/commands.d.ts +4 -0
- package/dist/owner-channel/commands.js +8 -0
- package/dist/owner-channel/lifecycle-outbox.js +4 -3
- package/dist/owner-channel/ours-client.js +2 -17
- package/dist/preset-migration.js +15 -0
- package/dist/rooms-tasks/add-member.d.ts +48 -0
- package/dist/rooms-tasks/add-member.js +83 -0
- package/dist/rooms-tasks/archived-absence.d.ts +8 -0
- package/dist/rooms-tasks/archived-absence.js +48 -0
- package/dist/rooms-tasks/cli.js +34 -5
- package/dist/rooms-tasks/close.d.ts +5 -2
- package/dist/rooms-tasks/close.js +208 -28
- package/dist/rooms-tasks/config.js +3 -1
- package/dist/rooms-tasks/cowork-adapter.d.ts +6 -4
- package/dist/rooms-tasks/cowork-adapter.js +11 -117
- package/dist/rooms-tasks/cowork-http.d.ts +3 -0
- package/dist/rooms-tasks/cowork-http.js +70 -0
- package/dist/rooms-tasks/deletion.d.ts +1 -1
- package/dist/rooms-tasks/deletion.js +74 -5
- package/dist/rooms-tasks/erasure.d.ts +6 -0
- package/dist/rooms-tasks/erasure.js +169 -0
- package/dist/rooms-tasks/external-worker.d.ts +1 -1
- package/dist/rooms-tasks/external-worker.js +3 -4
- package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
- package/dist/rooms-tasks/layout-binding-client.js +56 -0
- package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
- package/dist/rooms-tasks/layout-binding-grants.js +128 -0
- package/dist/rooms-tasks/layout-cli.d.ts +4 -0
- package/dist/rooms-tasks/layout-cli.js +107 -0
- package/dist/rooms-tasks/layout-config.d.ts +11 -0
- package/dist/rooms-tasks/layout-config.js +89 -0
- package/dist/rooms-tasks/layout-control.d.ts +21 -0
- package/dist/rooms-tasks/layout-control.js +52 -0
- package/dist/rooms-tasks/layout-service.d.ts +42 -0
- package/dist/rooms-tasks/layout-service.js +185 -0
- package/dist/rooms-tasks/layout.d.ts +114 -0
- package/dist/rooms-tasks/layout.js +250 -0
- package/dist/rooms-tasks/live-readiness.d.ts +18 -0
- package/dist/rooms-tasks/live-readiness.js +85 -0
- package/dist/rooms-tasks/member-startup.d.ts +1 -0
- package/dist/rooms-tasks/member-startup.js +2 -0
- package/dist/rooms-tasks/provision.d.ts +14 -0
- package/dist/rooms-tasks/provision.js +182 -48
- package/dist/rooms-tasks/room-state.d.ts +4 -2
- package/dist/rooms-tasks/room-state.js +53 -8
- package/dist/rooms-tasks/task-state.d.ts +42 -7
- package/dist/rooms-tasks/task-state.js +153 -23
- package/dist/rooms-tasks/types.d.ts +28 -0
- package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
- package/dist/rooms-tasks/workspace-artifacts.js +93 -0
- package/dist/rooms-tasks/workspace.d.ts +26 -0
- package/dist/rooms-tasks/workspace.js +195 -0
- package/dist/runner.d.ts +23 -3
- package/dist/runner.js +581 -494
- package/dist/session/acp.d.ts +3 -0
- package/dist/session/acp.js +14 -0
- package/dist/session/arbiter.d.ts +3 -0
- package/dist/session/arbiter.js +20 -2
- package/dist/session/codex-app-server-transport.js +1 -1
- package/dist/session/codex-app-server.d.ts +3 -0
- package/dist/session/codex-app-server.js +25 -1
- package/dist/session/control.d.ts +10 -1
- package/dist/session/control.js +37 -2
- package/dist/session/conversation-normalizer.d.ts +2 -1
- package/dist/session/conversation-normalizer.js +1 -1
- package/dist/session/conversation-types.d.ts +5 -3
- package/dist/session/task-notice.d.ts +14 -0
- package/dist/session/task-notice.js +13 -0
- package/dist/session/types.d.ts +3 -0
- package/dist/socket-path.d.ts +2 -0
- package/dist/socket-path.js +22 -0
- package/dist/spawn.js +19 -16
- package/dist/startup-readiness.d.ts +13 -0
- package/dist/startup-readiness.js +38 -0
- package/dist/subscriptions/cli.d.ts +78 -0
- package/dist/subscriptions/cli.js +186 -0
- package/dist/subscriptions/launch.d.ts +37 -0
- package/dist/subscriptions/launch.js +93 -0
- package/dist/subscriptions/login.d.ts +75 -0
- package/dist/subscriptions/login.js +182 -0
- package/dist/subscriptions/observed.d.ts +7 -0
- package/dist/subscriptions/observed.js +44 -0
- package/dist/subscriptions/probe.d.ts +10 -0
- package/dist/subscriptions/probe.js +73 -0
- package/dist/subscriptions/service.d.ts +102 -0
- package/dist/subscriptions/service.js +364 -0
- package/dist/subscriptions/store.d.ts +71 -0
- package/dist/subscriptions/store.js +140 -0
- package/dist/supervisor/launchd.js +2 -1
- package/dist/supervisor/systemd.js +9 -23
- package/dist/temp-idle.d.ts +17 -0
- package/dist/temp-idle.js +42 -0
- package/dist/temp-lifecycle.d.ts +6 -1
- package/dist/temp-lifecycle.js +29 -13
- package/dist/temp-supervisor-recovery.d.ts +3 -0
- package/dist/temp-supervisor-recovery.js +46 -0
- package/dist/watchdog/briefing.js +3 -16
- package/dist/watchdog/run.js +19 -5
- package/dist/web/agent-attachments.d.ts +29 -0
- package/dist/web/agent-attachments.js +162 -0
- package/dist/web/auth.d.ts +28 -2
- package/dist/web/auth.js +81 -8
- package/dist/web/available-port.d.ts +2 -0
- package/dist/web/available-port.js +23 -0
- package/dist/web/control.d.ts +3 -2
- package/dist/web/control.js +11 -7
- package/dist/web/fleet-config-service.d.ts +4 -1
- package/dist/web/fleet-config-service.js +46 -15
- package/dist/web/prefix-gateway.d.ts +23 -0
- package/dist/web/prefix-gateway.js +156 -0
- package/dist/web/runtime.d.ts +4 -0
- package/dist/web/runtime.js +106 -20
- package/dist/web/server.d.ts +11 -0
- package/dist/web/server.js +445 -31
- package/dist/web/service.d.ts +11 -2
- package/dist/web/service.js +30 -4
- package/dist/web/workspace-devices.d.ts +29 -0
- package/dist/web/workspace-devices.js +84 -0
- package/dist/web/workspace-tunnel.d.ts +1 -0
- package/dist/web/workspace-tunnel.js +32 -0
- package/dist/web-app/assets/FleetApp-D58F4xje.css +1 -0
- package/dist/web-app/assets/FleetApp-DP5cBY0o.js +302 -0
- package/dist/web-app/assets/FleetApp-DP5cBY0o.js.map +1 -0
- package/dist/web-app/assets/abnfDiagram-VCTEODGH-BvJIz7wN.js +2 -0
- package/dist/web-app/assets/abnfDiagram-VCTEODGH-BvJIz7wN.js.map +1 -0
- package/dist/web-app/assets/arc-AT_BEfpe.js +2 -0
- package/dist/web-app/assets/arc-AT_BEfpe.js.map +1 -0
- package/dist/web-app/assets/architectureDiagram-5GKGNRK7-vSgJa9Xb.js +37 -0
- package/dist/web-app/assets/architectureDiagram-5GKGNRK7-vSgJa9Xb.js.map +1 -0
- package/dist/web-app/assets/blockDiagram-I7D4REHJ-DCFk_4Kv.js +130 -0
- package/dist/web-app/assets/blockDiagram-I7D4REHJ-DCFk_4Kv.js.map +1 -0
- package/dist/web-app/assets/browser-CMIwJ_8L.js +9 -0
- package/dist/web-app/assets/browser-CMIwJ_8L.js.map +1 -0
- package/dist/web-app/assets/c4Diagram-7LVT6UL2-8J_dVUtF.js +39 -0
- package/dist/web-app/assets/c4Diagram-7LVT6UL2-8J_dVUtF.js.map +1 -0
- package/dist/web-app/assets/channel-BHCBlfND.js +2 -0
- package/dist/web-app/assets/channel-BHCBlfND.js.map +1 -0
- package/dist/web-app/assets/chunk-2Q5K7J3B-31RX-jnL.js +2 -0
- package/dist/web-app/assets/chunk-2Q5K7J3B-31RX-jnL.js.map +1 -0
- package/dist/web-app/assets/chunk-5VM5RSS4-CFF8_ZJH.js +16 -0
- package/dist/web-app/assets/chunk-5VM5RSS4-CFF8_ZJH.js.map +1 -0
- package/dist/web-app/assets/chunk-F27PBJKO-Dx1l5wLI.js +2 -0
- package/dist/web-app/assets/chunk-F27PBJKO-Dx1l5wLI.js.map +1 -0
- package/dist/web-app/assets/chunk-IMKFNOWR-Ba2_9bl_.js +232 -0
- package/dist/web-app/assets/chunk-IMKFNOWR-Ba2_9bl_.js.map +1 -0
- package/dist/web-app/assets/chunk-JWPE2WC7-_mnRB2OH.js +2 -0
- package/dist/web-app/assets/chunk-JWPE2WC7-_mnRB2OH.js.map +1 -0
- package/dist/web-app/assets/chunk-POPQ4Y6H-B8jQTxQO.js +2 -0
- package/dist/web-app/assets/chunk-POPQ4Y6H-B8jQTxQO.js.map +1 -0
- package/dist/web-app/assets/chunk-SVP7TREG-BO_Jnb_c.js +89 -0
- package/dist/web-app/assets/chunk-SVP7TREG-BO_Jnb_c.js.map +1 -0
- package/dist/web-app/assets/chunk-TICWLB2K-ZYZ9hFu5.js +207 -0
- package/dist/web-app/assets/chunk-TICWLB2K-ZYZ9hFu5.js.map +1 -0
- package/dist/web-app/assets/chunk-XXDRQBXY-ByUAYXI3.js +2 -0
- package/dist/web-app/assets/chunk-XXDRQBXY-ByUAYXI3.js.map +1 -0
- package/dist/web-app/assets/classDiagram-ZZMXUADV-Ddx7RAuT.js +2 -0
- package/dist/web-app/assets/classDiagram-ZZMXUADV-Ddx7RAuT.js.map +1 -0
- package/dist/web-app/assets/classDiagram-v2-VYDZK3BY-Ddx7RAuT.js +2 -0
- package/dist/web-app/assets/classDiagram-v2-VYDZK3BY-Ddx7RAuT.js.map +1 -0
- package/dist/web-app/assets/cose-bilkent-JH36ORCC-BuOGJgHG.js +2 -0
- package/dist/web-app/assets/cose-bilkent-JH36ORCC-BuOGJgHG.js.map +1 -0
- package/dist/web-app/assets/cynefin-OW5HDTMX-DLksGPh8.js +179 -0
- package/dist/web-app/assets/cynefin-OW5HDTMX-DLksGPh8.js.map +1 -0
- package/dist/web-app/assets/cynefinDiagram-5FMLGOSQ-nJQb5qZR.js +63 -0
- package/dist/web-app/assets/cynefinDiagram-5FMLGOSQ-nJQb5qZR.js.map +1 -0
- package/dist/web-app/assets/cytoscape.esm-CtNdOvlO.js +332 -0
- package/dist/web-app/assets/cytoscape.esm-CtNdOvlO.js.map +1 -0
- package/dist/web-app/assets/dagre-GXQ25YYZ-CCF5O_UD.js +5 -0
- package/dist/web-app/assets/dagre-GXQ25YYZ-CCF5O_UD.js.map +1 -0
- package/dist/web-app/assets/defaultLocale-DX6XiGOO.js +2 -0
- package/dist/web-app/assets/defaultLocale-DX6XiGOO.js.map +1 -0
- package/dist/web-app/assets/diagram-S7CK7UJ4-C88au4NC.js +31 -0
- package/dist/web-app/assets/diagram-S7CK7UJ4-C88au4NC.js.map +1 -0
- package/dist/web-app/assets/diagram-UQ7AKVKN-B4QnPs7U.js +42 -0
- package/dist/web-app/assets/diagram-UQ7AKVKN-B4QnPs7U.js.map +1 -0
- package/dist/web-app/assets/diagram-VSXAHHWV-DeANdVWA.js +4 -0
- package/dist/web-app/assets/diagram-VSXAHHWV-DeANdVWA.js.map +1 -0
- package/dist/web-app/assets/diagram-VX7I27RA-YzlB_2AY.js +25 -0
- package/dist/web-app/assets/diagram-VX7I27RA-YzlB_2AY.js.map +1 -0
- package/dist/web-app/assets/diagram-Z3DM3KII-YY9NFaz_.js +25 -0
- package/dist/web-app/assets/diagram-Z3DM3KII-YY9NFaz_.js.map +1 -0
- package/dist/web-app/assets/ebnfDiagram-PWID7BFC-Cqu9kHUC.js +2 -0
- package/dist/web-app/assets/ebnfDiagram-PWID7BFC-Cqu9kHUC.js.map +1 -0
- package/dist/web-app/assets/erDiagram-RLTQ6QDP-IHi93MdG.js +100 -0
- package/dist/web-app/assets/erDiagram-RLTQ6QDP-IHi93MdG.js.map +1 -0
- package/dist/web-app/assets/flowDiagram-HODETNUW-19bm7W5P.js +169 -0
- package/dist/web-app/assets/flowDiagram-HODETNUW-19bm7W5P.js.map +1 -0
- package/dist/web-app/assets/ganttDiagram-EL5Y4UJY-BvCqvyiB.js +293 -0
- package/dist/web-app/assets/ganttDiagram-EL5Y4UJY-BvCqvyiB.js.map +1 -0
- package/dist/web-app/assets/gitGraphDiagram-WWUBYQGX-G4Ubl8Fs.js +107 -0
- package/dist/web-app/assets/gitGraphDiagram-WWUBYQGX-G4Ubl8Fs.js.map +1 -0
- package/dist/web-app/assets/index-C4BdeFB9.css +1 -0
- package/dist/web-app/assets/index-Dtn3ZY6J.js +430 -0
- package/dist/web-app/assets/index-Dtn3ZY6J.js.map +1 -0
- package/dist/web-app/assets/infoDiagram-27XIBGKW-B6rtfbuC.js +3 -0
- package/dist/web-app/assets/infoDiagram-27XIBGKW-B6rtfbuC.js.map +1 -0
- package/dist/web-app/assets/init-Gi6I4Gst.js +2 -0
- package/dist/web-app/assets/init-Gi6I4Gst.js.map +1 -0
- package/dist/web-app/assets/ishikawaDiagram-5VMMS53U-Lp8q1D21.js +71 -0
- package/dist/web-app/assets/ishikawaDiagram-5VMMS53U-Lp8q1D21.js.map +1 -0
- package/dist/web-app/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/web-app/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/web-app/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/web-app/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/web-app/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/web-app/assets/journeyDiagram-3NMN7TZE-Av03B9l3.js +140 -0
- package/dist/web-app/assets/journeyDiagram-3NMN7TZE-Av03B9l3.js.map +1 -0
- package/dist/web-app/assets/kanban-definition-UXKFOSKX-Ddd3_ejb.js +90 -0
- package/dist/web-app/assets/kanban-definition-UXKFOSKX-Ddd3_ejb.js.map +1 -0
- package/dist/web-app/assets/katex-HP8lGamR.js +258 -0
- package/dist/web-app/assets/katex-HP8lGamR.js.map +1 -0
- package/dist/web-app/assets/layout-CNjBeOlp.js +2 -0
- package/dist/web-app/assets/layout-CNjBeOlp.js.map +1 -0
- package/dist/web-app/assets/linear-DDONU9Ir.js +2 -0
- package/dist/web-app/assets/linear-DDONU9Ir.js.map +1 -0
- package/dist/web-app/assets/mermaid.core-B9X3C61k.js +313 -0
- package/dist/web-app/assets/mermaid.core-B9X3C61k.js.map +1 -0
- package/dist/web-app/assets/mindmap-definition-YA3MSWOX-I2UME2Ce.js +97 -0
- package/dist/web-app/assets/mindmap-definition-YA3MSWOX-I2UME2Ce.js.map +1 -0
- package/dist/web-app/assets/ordinal-Cboi1Yqb.js +2 -0
- package/dist/web-app/assets/ordinal-Cboi1Yqb.js.map +1 -0
- package/dist/web-app/assets/pegDiagram-XKGWAZYB-BER8lNbi.js +2 -0
- package/dist/web-app/assets/pegDiagram-XKGWAZYB-BER8lNbi.js.map +1 -0
- package/dist/web-app/assets/pieDiagram-E7YTZNPT-FVWVg8rH.js +40 -0
- package/dist/web-app/assets/pieDiagram-E7YTZNPT-FVWVg8rH.js.map +1 -0
- package/dist/web-app/assets/quadrantDiagram-AXDQQJYC-YlvT1u77.js +8 -0
- package/dist/web-app/assets/quadrantDiagram-AXDQQJYC-YlvT1u77.js.map +1 -0
- package/dist/web-app/assets/railroadDiagram-O6MQD6OU-CvxT6dMW.js +2 -0
- package/dist/web-app/assets/railroadDiagram-O6MQD6OU-CvxT6dMW.js.map +1 -0
- package/dist/web-app/assets/requirementDiagram-BXWQKSXE-Caav4EkP.js +85 -0
- package/dist/web-app/assets/requirementDiagram-BXWQKSXE-Caav4EkP.js.map +1 -0
- package/dist/web-app/assets/sankeyDiagram-P5KCCOFB-B5Uw70Gn.js +41 -0
- package/dist/web-app/assets/sankeyDiagram-P5KCCOFB-B5Uw70Gn.js.map +1 -0
- package/dist/web-app/assets/sequenceDiagram-WJ2MYXX4-D2SLKVJl.js +163 -0
- package/dist/web-app/assets/sequenceDiagram-WJ2MYXX4-D2SLKVJl.js.map +1 -0
- package/dist/web-app/assets/sizeCapture-INFHLROL-BVaBfbwa.js +2 -0
- package/dist/web-app/assets/sizeCapture-INFHLROL-BVaBfbwa.js.map +1 -0
- package/dist/web-app/assets/stateDiagram-D77RDMKH-BlV9y2tf.js +2 -0
- package/dist/web-app/assets/stateDiagram-D77RDMKH-BlV9y2tf.js.map +1 -0
- package/dist/web-app/assets/stateDiagram-v2-MP3YSRHH-BU4Tu2pp.js +2 -0
- package/dist/web-app/assets/stateDiagram-v2-MP3YSRHH-BU4Tu2pp.js.map +1 -0
- package/dist/web-app/assets/swimlanes-42K2YHIH-Pv3Wv8c7.js +2 -0
- package/dist/web-app/assets/swimlanes-42K2YHIH-Pv3Wv8c7.js.map +1 -0
- package/dist/web-app/assets/swimlanesDiagram-VR7AAH4N-BNKjOvPH.js +9 -0
- package/dist/web-app/assets/swimlanesDiagram-VR7AAH4N-BNKjOvPH.js.map +1 -0
- package/dist/web-app/assets/timeline-definition-24CTP7MA-C9TOilaR.js +121 -0
- package/dist/web-app/assets/timeline-definition-24CTP7MA-C9TOilaR.js.map +1 -0
- package/dist/web-app/assets/vennDiagram-4TSXK5OY-DeSDprN4.js +35 -0
- package/dist/web-app/assets/vennDiagram-4TSXK5OY-DeSDprN4.js.map +1 -0
- package/dist/web-app/assets/wardleyDiagram-VM6X3IG4-SulZn-nm.js +79 -0
- package/dist/web-app/assets/wardleyDiagram-VM6X3IG4-SulZn-nm.js.map +1 -0
- package/dist/web-app/assets/xychartDiagram-S5SC5T6Z-CC6MrQ0h.js +8 -0
- package/dist/web-app/assets/xychartDiagram-S5SC5T6Z-CC6MrQ0h.js.map +1 -0
- package/dist/web-app/fleet-index.html +19 -0
- package/dist/web-app/fleet.webmanifest +37 -0
- package/dist/web-app/icons/apple-touch-icon.png +0 -0
- package/dist/web-app/icons/favicon-16.png +0 -0
- package/dist/web-app/icons/favicon-32.png +0 -0
- package/dist/web-app/icons/icon-192-maskable.png +0 -0
- package/dist/web-app/icons/icon-192.png +0 -0
- package/dist/web-app/icons/icon-512-maskable.png +0 -0
- package/dist/web-app/icons/icon-512.png +0 -0
- package/dist/web-app/index.html +9 -7
- package/dist/web-app/manifest.webmanifest +10 -8
- package/dist/web-app/sw.js +210 -40
- package/dist/web-app/version.json +1 -0
- package/dist/web-app/workspace-install.sh +148 -0
- package/dist/workspace-enrollment.d.ts +28 -0
- package/dist/workspace-enrollment.js +129 -0
- package/docs/room-layouts.md +227 -0
- package/docs/task-workspaces.md +130 -0
- package/examples/room-layouts/fresh.yaml +27 -0
- package/examples/room-layouts/scoped.yaml +21 -0
- package/examples/room-layouts/shared.yaml +19 -0
- package/package.json +14 -17
- package/presets/README.md +78 -0
- package/presets/brain-catalog.json +8 -2
- package/presets/fleet/agent_templates/Engineer.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
- package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +22 -0
- package/presets/fleet/roles/Engineer.yaml +40 -0
- package/presets/fleet/room_layouts/engineering.yaml +25 -0
- package/presets/fleet/room_layouts/pair.yaml +24 -0
- package/presets/fleet/room_layouts/single.yaml +17 -0
- package/presets/fleet/room_layouts/team.yaml +30 -0
- package/presets/fleet/room_templates/engineering.yaml +19 -0
- package/presets/manifest.json +2 -2
- package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
- package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
- package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
- package/dist/web-app/icons/ours-fleet.svg +0 -4
- package/dist/web-app/offline.html +0 -18
package/dist/web/server.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { packagedPresetRoot } from '../preset-bootstrap.js';
|
|
3
|
+
import { validateCatalog } from '../init-wizard.js';
|
|
4
|
+
import { getAdapter } from '../harness/registry.js';
|
|
5
|
+
import { storeAgentAttachment, attachmentPrompt, MAX_ATTACHMENT_BODY, readAgentAttachment, prepareAttachmentPresentation, presentAttachmentEvent } from './agent-attachments.js';
|
|
6
|
+
import { readChatIdle } from '../temp-idle.js';
|
|
7
|
+
import { agentDir } from '../paths.js';
|
|
8
|
+
import { SupervisorOursTools } from '../application/supervisor-ours-tools.js';
|
|
1
9
|
import { existsSync } from 'node:fs';
|
|
2
10
|
import { dirname, join } from 'node:path';
|
|
3
11
|
import { fileURLToPath } from 'node:url';
|
|
@@ -13,6 +21,7 @@ import { FleetEventBus } from './events.js';
|
|
|
13
21
|
import { ROLE_NAME_RE } from '../config.js';
|
|
14
22
|
import { TaskListError } from '../rooms-tasks/task-lists.js';
|
|
15
23
|
import { TaskStateError } from '../rooms-tasks/task-state.js';
|
|
24
|
+
import { isProvider } from '../subscriptions/store.js';
|
|
16
25
|
const statusFor = (code) => ({
|
|
17
26
|
role_not_found: 404, resource_not_found: 404, unauthorized: 401, forbidden: 403, conflict: 409,
|
|
18
27
|
idempotency_conflict: 409, stale_state: 409, rate_limited: 429,
|
|
@@ -33,15 +42,24 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
33
42
|
app.addHook('onRequest', async (request, reply) => {
|
|
34
43
|
reply.header('X-Content-Type-Options', 'nosniff');
|
|
35
44
|
reply.header('Referrer-Policy', 'no-referrer');
|
|
36
|
-
reply.header('Permissions-Policy', 'camera=(), microphone=(), geolocation=(), payment=()');
|
|
37
|
-
|
|
38
|
-
reply.header('Content-Security-Policy', `default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; ` +
|
|
45
|
+
reply.header('Permissions-Policy', 'camera=(), microphone=(self), geolocation=(), payment=()');
|
|
46
|
+
// Only the configured account launcher may embed workspace pages.
|
|
47
|
+
reply.header('Content-Security-Policy', `default-src 'self'; script-src 'self'; style-src 'self'; style-src-attr 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob:; frame-src 'self' blob:; ` +
|
|
39
48
|
`connect-src 'self' ${auth.secureCookies ? 'wss' : 'ws'}://${auth.host}; object-src 'none'; base-uri 'none'; ` +
|
|
40
|
-
`frame-ancestors '
|
|
49
|
+
`frame-ancestors 'self' ${auth.appOrigin}; form-action 'self'; manifest-src 'self'; worker-src 'self'`);
|
|
41
50
|
if (request.url.startsWith('/api/'))
|
|
42
51
|
reply.header('Cache-Control', 'no-store');
|
|
52
|
+
if (request.headers.origin === auth.appOrigin && request.url.startsWith('/api/')) {
|
|
53
|
+
reply.header('Access-Control-Allow-Origin', auth.appOrigin).header('Vary', 'Origin');
|
|
54
|
+
reply.header('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
|
|
55
|
+
reply.header('Access-Control-Allow-Headers', 'Authorization,Content-Type,X-CSRF-Token,Idempotency-Key');
|
|
56
|
+
if (request.method === 'OPTIONS') {
|
|
57
|
+
auth.validateBoundary(request, false);
|
|
58
|
+
return reply.code(204).send();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
43
61
|
try {
|
|
44
|
-
auth.validateBoundary(request, false);
|
|
62
|
+
auth.validateBoundary(request, false, !request.url.startsWith('/api/'));
|
|
45
63
|
}
|
|
46
64
|
catch (error) {
|
|
47
65
|
if (request.url.startsWith('/api/'))
|
|
@@ -50,8 +68,24 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
50
68
|
return reply.code(421).type('text/html').send(`<!doctype html><html><head><title>Fleet console address</title></head><body><main><h1>This fleet-console address is not configured</h1><p>${escapeHtml(message)}</p><p>For nginx or VPS access, configure an explicit public origin. The console does not guess proxy hosts.</p></main></body></html>`);
|
|
51
69
|
}
|
|
52
70
|
});
|
|
71
|
+
app.post('/api/v1/layout-bindings/:id/control', async (request) => {
|
|
72
|
+
// Initial support is direct same-host Fleet-to-Fleet transport.
|
|
73
|
+
const forwarded = Object.keys(request.headers).some(name => name === 'forwarded' || name.startsWith('x-forwarded-') || name === 'x-real-ip' || name === 'via');
|
|
74
|
+
if (!['127.0.0.1', '::1', '::ffff:127.0.0.1'].includes(request.ip) || forwarded)
|
|
75
|
+
throw new FleetError('forbidden', 'layout bindings require direct loopback access');
|
|
76
|
+
// Machine credentials cannot authenticate browser or lifecycle routes.
|
|
77
|
+
if (request.headers.origin)
|
|
78
|
+
auth.validateBoundary(request, true);
|
|
79
|
+
if (!services.layoutBindings)
|
|
80
|
+
throw new FleetError('capability_unavailable', 'layout bindings unavailable');
|
|
81
|
+
return services.layoutBindings.control(request.params.id, request.headers.authorization, request.body);
|
|
82
|
+
});
|
|
53
83
|
app.setErrorHandler(async (error, request, reply) => {
|
|
54
|
-
const
|
|
84
|
+
const privateToolParseError = ['/api/v1/roles/:id/ours/call', '/api/v1/layout-bindings/:id/control'].includes(request.routeOptions.url ?? '')
|
|
85
|
+
&& error instanceof Error && 'code' in error
|
|
86
|
+
&& typeof error.code === 'string' && error.code.startsWith('FST_ERR_CTP_');
|
|
87
|
+
const fleetError = normalizeError(privateToolParseError
|
|
88
|
+
? new FleetError('invalid_request', 'expected a valid JSON tool request') : error, request.id);
|
|
55
89
|
await audit.record({
|
|
56
90
|
requestId: request.id, action: `${request.method} ${request.routeOptions.url ?? request.url}`,
|
|
57
91
|
result: 'rejected', errorCode: fleetError.code,
|
|
@@ -96,6 +130,20 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
96
130
|
const session = auth.authenticate(request);
|
|
97
131
|
return { csrfToken: session.csrf, expiresAt: new Date(session.absoluteExpiresAt).toISOString() };
|
|
98
132
|
});
|
|
133
|
+
app.get('/api/v1/workspace/identity', async () => auth.workspaceIdentity());
|
|
134
|
+
app.post('/api/v1/devices/enroll', async (request, reply) => {
|
|
135
|
+
reply.header('Cache-Control', 'no-store');
|
|
136
|
+
const input = request.body;
|
|
137
|
+
if (!input)
|
|
138
|
+
throw new FleetError('invalid_request', 'device enrollment body required');
|
|
139
|
+
return auth.enrollWorkspace(request, input);
|
|
140
|
+
});
|
|
141
|
+
app.post('/api/v1/devices/link', async (request) => {
|
|
142
|
+
const session = auth.authenticate(request, true);
|
|
143
|
+
return { version: 1, origin: auth.origin, ...auth.workspaceEnrollment(session.id) };
|
|
144
|
+
});
|
|
145
|
+
app.get('/api/v1/devices', async (request) => { auth.authenticate(request); return { devices: auth.listWorkspaceDevices() }; });
|
|
146
|
+
app.delete('/api/v1/devices/:id', async (request) => { auth.authenticate(request, true); auth.revokeWorkspaceDevice(request.params.id); return { revoked: true }; });
|
|
99
147
|
app.get('/api/v1/meta', async (request) => {
|
|
100
148
|
auth.authenticate(request);
|
|
101
149
|
return {
|
|
@@ -106,6 +154,19 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
106
154
|
auditDegraded: audit.degraded,
|
|
107
155
|
};
|
|
108
156
|
});
|
|
157
|
+
app.get('/api/v1/onboarding', async (request) => {
|
|
158
|
+
auth.authenticate(request);
|
|
159
|
+
const harnesses = await Promise.all(['codex', 'claude-code'].map(async (harness) => {
|
|
160
|
+
try {
|
|
161
|
+
return { harness, ...await getAdapter(harness).checkPrereqs() };
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return { harness, ok: false, checks: [{ name: 'availability', ok: false, detail: 'Harness checks unavailable' }] };
|
|
165
|
+
}
|
|
166
|
+
}));
|
|
167
|
+
const catalog = validateCatalog(JSON.parse(readFileSync(join(packagedPresetRoot(), 'brain-catalog.json'), 'utf8')));
|
|
168
|
+
return { harnesses, catalog: catalog.models, providers: services.subscriptions ? await services.subscriptions.list() : [], coordinator: 'FleetCoordinator' };
|
|
169
|
+
});
|
|
109
170
|
app.get('/api/v1/creation-capabilities', async (request) => {
|
|
110
171
|
auth.authenticate(request);
|
|
111
172
|
return services.creation.capabilities();
|
|
@@ -128,7 +189,8 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
128
189
|
});
|
|
129
190
|
app.get('/api/v1/roles', async (request) => {
|
|
130
191
|
auth.authenticate(request);
|
|
131
|
-
|
|
192
|
+
const query = request.query;
|
|
193
|
+
return { roles: await services.query.list(query.includeTemporary === 'true') };
|
|
132
194
|
});
|
|
133
195
|
const taskApi = async (fn) => {
|
|
134
196
|
try {
|
|
@@ -141,6 +203,8 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
141
203
|
? 'conflict' : 'invalid_request';
|
|
142
204
|
throw new FleetError(code, error.message);
|
|
143
205
|
}
|
|
206
|
+
if (error instanceof TaskStateError && 'accepted' in error && error.accepted === false)
|
|
207
|
+
return Promise.reject(Object.assign(new FleetError('invalid_request', error.message), { memberNotAccepted: true }));
|
|
144
208
|
if (error instanceof TaskStateError) {
|
|
145
209
|
throw new FleetError(error.message.startsWith('task not found:') ? 'resource_not_found' : 'conflict', error.message);
|
|
146
210
|
}
|
|
@@ -188,9 +252,10 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
188
252
|
const state = query.state && query.state !== 'all' ? query.state : undefined;
|
|
189
253
|
const filter = { ...(state ? { state } : {}), ...(query.list ? { list: query.list } : {}),
|
|
190
254
|
...(query.includeDeleting === 'true' ? { includeDeleting: true } : {}) };
|
|
255
|
+
const api = requireTaskRooms();
|
|
191
256
|
return taskApi(() => query.groupByList === 'true'
|
|
192
|
-
? { groups:
|
|
193
|
-
: { tasks:
|
|
257
|
+
? { groups: api.groupedTasks(filter).map(group => ({ ...group, tasks: group.tasks.map(task => api.withLayoutRooms(task)) })) }
|
|
258
|
+
: { tasks: api.listTasks(filter).map(task => api.withLayoutRooms(task)) });
|
|
194
259
|
});
|
|
195
260
|
app.delete('/api/v1/tasks/:id', async (request, reply) => {
|
|
196
261
|
auth.authenticate(request, true);
|
|
@@ -221,12 +286,17 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
221
286
|
const body = request.body;
|
|
222
287
|
if (typeof body?.title !== 'string' || !body.title)
|
|
223
288
|
throw new FleetError('invalid_request', 'title is required');
|
|
289
|
+
// Every task runs a saved Room Layout; rooms are never defined inline or from room templates here.
|
|
290
|
+
if (typeof body.layout !== 'string' || !body.layout)
|
|
291
|
+
throw new FleetError('invalid_request', 'layout is required: choose a room layout from Settings › Room layouts');
|
|
292
|
+
if (body.template !== undefined || body.noRoom !== undefined)
|
|
293
|
+
throw new FleetError('invalid_request', 'tasks use room layouts; template and noRoom are not accepted');
|
|
224
294
|
const task = await taskApi(() => requireTaskRooms().createTask({
|
|
225
295
|
actor: { kind: 'local_control', surface: 'web' }, title: body.title,
|
|
226
296
|
brief: typeof body.brief === 'string' ? body.brief : undefined,
|
|
227
|
-
|
|
228
|
-
backlog: body.backlog === true, noRoom: body.noRoom === true,
|
|
297
|
+
backlog: body.backlog === true,
|
|
229
298
|
list: typeof body.list === 'string' ? body.list : undefined,
|
|
299
|
+
layout: body.layout,
|
|
230
300
|
idempotencyKey: typeof request.headers['idempotency-key'] === 'string'
|
|
231
301
|
? request.headers['idempotency-key'] : undefined,
|
|
232
302
|
origin: { type: 'web' },
|
|
@@ -240,11 +310,162 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
240
310
|
const list = String(request.body?.list ?? '');
|
|
241
311
|
return { task: await taskApi(() => requireTaskRooms().moveTask({ actor: { kind: 'local_control', surface: 'web' }, taskId, list })) };
|
|
242
312
|
});
|
|
313
|
+
app.patch('/api/v1/tasks/:id/description', async (request) => {
|
|
314
|
+
auth.authenticate(request, true);
|
|
315
|
+
const body = request.body;
|
|
316
|
+
if (typeof body?.brief !== 'string' || typeof body.expectedBrief !== 'string' || body.brief.length > 100_000)
|
|
317
|
+
throw new FleetError('invalid_request', 'brief and expectedBrief must be strings; brief maximum is 100000 characters');
|
|
318
|
+
return { task: await taskApi(() => requireTaskRooms().editTaskDescription({
|
|
319
|
+
actor: { kind: 'local_control', surface: 'web' }, taskId: request.params.id,
|
|
320
|
+
brief: body.brief, expectedBrief: body.expectedBrief,
|
|
321
|
+
})) };
|
|
322
|
+
});
|
|
323
|
+
app.get('/api/v1/tasks/:id', async (request) => {
|
|
324
|
+
auth.authenticate(request);
|
|
325
|
+
return taskApi(() => requireTaskRooms().getTask(request.params.id));
|
|
326
|
+
});
|
|
327
|
+
app.post('/api/v1/tasks/:id/members', async (request, reply) => {
|
|
328
|
+
auth.authenticate(request, true);
|
|
329
|
+
const body = request.body;
|
|
330
|
+
try {
|
|
331
|
+
const result = await taskApi(() => requireTaskRooms().addMember(request.params.id, body));
|
|
332
|
+
reply.code(result.state === 'running' ? 202 : 200);
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
if (error instanceof Error && 'memberNotAccepted' in error)
|
|
337
|
+
return reply.code(409).send({ error: { code: 'member_not_accepted', message: error.message }, accepted: false });
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
app.get('/api/v1/tasks/:id/member-additions/:requestId', async (request) => {
|
|
342
|
+
auth.authenticate(request);
|
|
343
|
+
const { id, requestId } = request.params;
|
|
344
|
+
return taskApi(() => requireTaskRooms().memberAddition(id, requestId));
|
|
345
|
+
});
|
|
346
|
+
app.patch('/api/v1/tasks/:id/layout', async (request) => {
|
|
347
|
+
const session = auth.authenticate(request, true);
|
|
348
|
+
const body = request.body;
|
|
349
|
+
const name = (v) => typeof v === 'string' && v.length > 0;
|
|
350
|
+
if (!name(body?.layout) || !(body.expectedLayout === null || name(body.expectedLayout)))
|
|
351
|
+
throw new FleetError('invalid_request', 'layout must be a layout name; expectedLayout a layout name or null');
|
|
352
|
+
const task = await taskApi(() => requireTaskRooms().setTaskLayout({ actor: { kind: 'local_control', surface: 'web' },
|
|
353
|
+
taskId: request.params.id, layout: body.layout, expectedLayout: body.expectedLayout }));
|
|
354
|
+
await audit.record({ requestId: request.id, browser: session.id, action: 'task.layout.set', result: 'succeeded' });
|
|
355
|
+
return { task: requireTaskRooms().withLayoutRooms(task) };
|
|
356
|
+
});
|
|
357
|
+
app.get('/api/v1/tasks/:id/layout', async (request) => {
|
|
358
|
+
auth.authenticate(request);
|
|
359
|
+
return { layout: await taskApi(async () => requireTaskRooms().taskLayout(request.params.id)) };
|
|
360
|
+
});
|
|
361
|
+
for (const operation of ['open', 'close']) {
|
|
362
|
+
app.post(`/api/v1/tasks/:id/layout/rooms/:room/${operation}`, async (request, reply) => {
|
|
363
|
+
const session = auth.authenticate(request, true);
|
|
364
|
+
const { id, room } = request.params;
|
|
365
|
+
const accepted = await taskApi(() => requireTaskRooms().launchTaskLayoutOperation({
|
|
366
|
+
actor: { kind: 'local_control', surface: 'web' }, taskId: id, room,
|
|
367
|
+
operation: operation === 'open' ? 'open' : 'close-room',
|
|
368
|
+
}));
|
|
369
|
+
await audit.record({ requestId: request.id, browser: session.id, action: `task.layout.${operation}`, result: 'succeeded' });
|
|
370
|
+
reply.code(202);
|
|
371
|
+
return { operation: accepted };
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
app.post('/api/v1/tasks/:id/layout/cleanup', async (request, reply) => {
|
|
375
|
+
auth.authenticate(request, true);
|
|
376
|
+
const accepted = await taskApi(() => requireTaskRooms().retryTaskLayoutCleanup({
|
|
377
|
+
actor: { kind: 'local_control', surface: 'web' }, taskId: request.params.id,
|
|
378
|
+
}));
|
|
379
|
+
reply.code(202);
|
|
380
|
+
return { operation: accepted };
|
|
381
|
+
});
|
|
382
|
+
app.get('/api/v1/task-templates', async (request) => {
|
|
383
|
+
auth.authenticate(request);
|
|
384
|
+
return { templates: requireTaskRooms().listTemplates() };
|
|
385
|
+
});
|
|
386
|
+
const layouts = () => {
|
|
387
|
+
if (!services.roomLayouts)
|
|
388
|
+
throw new FleetError('capability_unavailable', 'room layout editing is unavailable');
|
|
389
|
+
return services.roomLayouts;
|
|
390
|
+
};
|
|
391
|
+
app.get('/api/v1/room-layouts', async (request) => {
|
|
392
|
+
auth.authenticate(request);
|
|
393
|
+
const list = layouts().list(), defaultLayout = layouts().defaultLayout(list);
|
|
394
|
+
return { layouts: list, ...(defaultLayout ? { default_layout: defaultLayout } : {}) };
|
|
395
|
+
});
|
|
396
|
+
app.post('/api/v1/room-layouts/validate', async (request) => {
|
|
397
|
+
auth.authenticate(request, true);
|
|
398
|
+
return layouts().validate(request.body?.definition);
|
|
399
|
+
});
|
|
400
|
+
app.put('/api/v1/room-layouts/:name', async (request) => {
|
|
401
|
+
const session = auth.authenticate(request, true);
|
|
402
|
+
const body = request.body;
|
|
403
|
+
if (typeof body?.revision !== 'string')
|
|
404
|
+
throw new FleetError('invalid_request', 'revision is required');
|
|
405
|
+
const result = await layouts().save(request.params.name, body.revision, body.definition);
|
|
406
|
+
events.publish('room_layouts.changed', { name: result.name, revision: result.revision });
|
|
407
|
+
await audit.record({ requestId: request.id, browser: session.id, action: 'room_layout.save', result: 'succeeded' });
|
|
408
|
+
return result;
|
|
409
|
+
});
|
|
410
|
+
app.delete('/api/v1/room-layouts/:name', async (request) => {
|
|
411
|
+
const session = auth.authenticate(request, true);
|
|
412
|
+
const revision = request.query.revision;
|
|
413
|
+
if (typeof revision !== 'string' || !revision)
|
|
414
|
+
throw new FleetError('invalid_request', 'revision is required');
|
|
415
|
+
const result = await layouts().remove(request.params.name, revision);
|
|
416
|
+
events.publish('room_layouts.changed', { name: result.name });
|
|
417
|
+
await audit.record({ requestId: request.id, browser: session.id, action: 'room_layout.delete', result: 'succeeded' });
|
|
418
|
+
return result;
|
|
419
|
+
});
|
|
420
|
+
for (const action of ['start', 'block', 'unblock', 'review', 'finish', 'cancel']) {
|
|
421
|
+
app.post(`/api/v1/tasks/:id/${action}`, async (request, reply) => {
|
|
422
|
+
auth.authenticate(request, true);
|
|
423
|
+
const taskId = request.params.id;
|
|
424
|
+
const body = (request.body ?? {});
|
|
425
|
+
const input = { actor: { kind: 'local_control', surface: 'web' }, taskId };
|
|
426
|
+
const api = requireTaskRooms();
|
|
427
|
+
return taskApi(async () => {
|
|
428
|
+
switch (action) {
|
|
429
|
+
case 'start': {
|
|
430
|
+
// A task's layout can be chosen or changed but never removed, so this check cannot go stale.
|
|
431
|
+
if (!api.getTask(taskId).task.layout)
|
|
432
|
+
throw new FleetError('conflict', 'choose a room layout for this task before starting it');
|
|
433
|
+
const task = await api.startTask(input);
|
|
434
|
+
const provisioning = await api.launchTaskProvisioning(taskId);
|
|
435
|
+
if (provisioning.kind !== 'ready')
|
|
436
|
+
reply.code(202);
|
|
437
|
+
return { task, provisioning };
|
|
438
|
+
}
|
|
439
|
+
case 'block':
|
|
440
|
+
if (typeof body.reason !== 'string' || !body.reason.trim())
|
|
441
|
+
throw new FleetError('invalid_request', 'reason is required');
|
|
442
|
+
return { task: api.blockTask({ ...input, reason: body.reason }) };
|
|
443
|
+
case 'unblock': return { task: api.unblockTask(input) };
|
|
444
|
+
case 'review': return { task: api.reviewTask(input) };
|
|
445
|
+
case 'finish':
|
|
446
|
+
case 'cancel': {
|
|
447
|
+
const plan = action === 'finish' ? await api.finishTask(input) : await api.cancelTask(input);
|
|
448
|
+
const task = plan.settlementRequired ? await api.launchTaskSettlement(taskId) : plan.task;
|
|
449
|
+
const pending = task.terminal_intent?.status === 'pending';
|
|
450
|
+
if (pending)
|
|
451
|
+
reply.code(202);
|
|
452
|
+
return { task, pending };
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
app.get('/api/v1/configuration/provenance', async (request) => {
|
|
459
|
+
auth.authenticate(request);
|
|
460
|
+
if (!services.presetProvenance)
|
|
461
|
+
throw new FleetError('capability_unavailable', 'preset provenance is unavailable');
|
|
462
|
+
return { provenance: services.presetProvenance.read() };
|
|
463
|
+
});
|
|
243
464
|
app.get('/api/v1/configuration', async (request) => {
|
|
244
465
|
auth.authenticate(request);
|
|
245
466
|
if (!services.configuration)
|
|
246
467
|
throw new FleetError('capability_unavailable', 'fleet configuration editing is unavailable');
|
|
247
|
-
return services.configuration.read();
|
|
468
|
+
return services.configuration.read(request.query.includeDefinitions === 'true');
|
|
248
469
|
});
|
|
249
470
|
app.post('/api/v1/configuration/preview', async (request) => {
|
|
250
471
|
auth.authenticate(request, true);
|
|
@@ -265,6 +486,86 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
265
486
|
});
|
|
266
487
|
return result;
|
|
267
488
|
});
|
|
489
|
+
// Claude Code / Codex subscription profiles. Responses carry account metadata
|
|
490
|
+
// only; token values never leave the CLI-owned credential files.
|
|
491
|
+
const subscriptions = () => {
|
|
492
|
+
if (!services.subscriptions)
|
|
493
|
+
throw new FleetError('capability_unavailable', 'subscription management is unavailable');
|
|
494
|
+
return services.subscriptions;
|
|
495
|
+
};
|
|
496
|
+
const provider = (raw) => {
|
|
497
|
+
if (!isProvider(raw))
|
|
498
|
+
throw new FleetError('invalid_request', 'unknown subscription provider');
|
|
499
|
+
return raw;
|
|
500
|
+
};
|
|
501
|
+
const subscriptionsChanged = () => events.publish('subscription.changed', {});
|
|
502
|
+
app.get('/api/v1/subscriptions', async (request) => {
|
|
503
|
+
auth.authenticate(request);
|
|
504
|
+
return { providers: await subscriptions().list() };
|
|
505
|
+
});
|
|
506
|
+
app.post('/api/v1/subscriptions/:provider/logins', async (request, reply) => {
|
|
507
|
+
const session = auth.authenticate(request, true);
|
|
508
|
+
const login = await subscriptions().startLogin(provider(request.params.provider), session.id);
|
|
509
|
+
await audit.record({ requestId: request.id, browser: session.id, action: `subscription.login.${request.params.provider}`, result: login.state });
|
|
510
|
+
reply.code(201);
|
|
511
|
+
return login;
|
|
512
|
+
});
|
|
513
|
+
app.get('/api/v1/subscriptions/:provider/logins/:loginId', async (request) => {
|
|
514
|
+
const session = auth.authenticate(request);
|
|
515
|
+
return subscriptions().login(request.params.loginId, session.id).view();
|
|
516
|
+
});
|
|
517
|
+
app.post('/api/v1/subscriptions/:provider/logins/:loginId/code', async (request) => {
|
|
518
|
+
const session = auth.authenticate(request, true);
|
|
519
|
+
const code = request.body?.code;
|
|
520
|
+
if (typeof code !== 'string')
|
|
521
|
+
throw new FleetError('invalid_request', 'code is required');
|
|
522
|
+
const login = subscriptions().login(request.params.loginId, session.id);
|
|
523
|
+
login.submitCode(code.trim());
|
|
524
|
+
await audit.record({ requestId: request.id, browser: session.id, action: `subscription.login.code.${request.params.provider}`, result: 'submitted' });
|
|
525
|
+
return login.view();
|
|
526
|
+
});
|
|
527
|
+
app.delete('/api/v1/subscriptions/:provider/logins/:loginId', async (request) => {
|
|
528
|
+
const session = auth.authenticate(request, true);
|
|
529
|
+
const login = subscriptions().login(request.params.loginId, session.id);
|
|
530
|
+
login.cancel();
|
|
531
|
+
return login.view();
|
|
532
|
+
});
|
|
533
|
+
app.post('/api/v1/subscriptions/:provider/active', async (request) => {
|
|
534
|
+
const session = auth.authenticate(request, true);
|
|
535
|
+
const profileId = request.body?.profileId;
|
|
536
|
+
if (typeof profileId !== 'string')
|
|
537
|
+
throw new FleetError('invalid_request', 'profileId is required');
|
|
538
|
+
const view = await subscriptions().setActive(provider(request.params.provider), profileId);
|
|
539
|
+
subscriptionsChanged();
|
|
540
|
+
await audit.record({ requestId: request.id, browser: session.id, action: `subscription.activate.${request.params.provider}`, result: 'succeeded' });
|
|
541
|
+
return view;
|
|
542
|
+
});
|
|
543
|
+
app.post('/api/v1/subscriptions/:provider/profiles/:profileId/check', async (request) => {
|
|
544
|
+
auth.authenticate(request, true);
|
|
545
|
+
return subscriptions().check(provider(request.params.provider), request.params.profileId);
|
|
546
|
+
});
|
|
547
|
+
app.post('/api/v1/subscriptions/claude/profiles/:profileId/probe', async (request) => {
|
|
548
|
+
const session = auth.authenticate(request, true);
|
|
549
|
+
const usage = await subscriptions().probeClaudeUsage(request.params.profileId);
|
|
550
|
+
await audit.record({ requestId: request.id, browser: session.id, action: 'subscription.usage.probe.claude', result: usage.source });
|
|
551
|
+
return { usage };
|
|
552
|
+
});
|
|
553
|
+
app.patch('/api/v1/subscriptions/:provider/profiles/:profileId', async (request) => {
|
|
554
|
+
auth.authenticate(request, true);
|
|
555
|
+
const label = request.body?.label;
|
|
556
|
+
if (typeof label !== 'string')
|
|
557
|
+
throw new FleetError('invalid_request', 'label is required');
|
|
558
|
+
await subscriptions().rename(provider(request.params.provider), request.params.profileId, label);
|
|
559
|
+
subscriptionsChanged();
|
|
560
|
+
return { ok: true };
|
|
561
|
+
});
|
|
562
|
+
app.delete('/api/v1/subscriptions/:provider/profiles/:profileId', async (request) => {
|
|
563
|
+
const session = auth.authenticate(request, true);
|
|
564
|
+
await subscriptions().remove(provider(request.params.provider), request.params.profileId);
|
|
565
|
+
subscriptionsChanged();
|
|
566
|
+
await audit.record({ requestId: request.id, browser: session.id, action: `subscription.remove.${request.params.provider}`, result: 'succeeded' });
|
|
567
|
+
return { ok: true };
|
|
568
|
+
});
|
|
268
569
|
app.get('/api/v1/topology', async (request) => {
|
|
269
570
|
auth.authenticate(request);
|
|
270
571
|
if (!services.topology)
|
|
@@ -311,9 +612,36 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
311
612
|
});
|
|
312
613
|
return result;
|
|
313
614
|
});
|
|
615
|
+
const oursTools = services.oursTools ?? new SupervisorOursTools();
|
|
616
|
+
app.get('/api/v1/roles/:id/contacts', async (request) => {
|
|
617
|
+
auth.authenticate(request);
|
|
618
|
+
const control = await services.session(request.params.id);
|
|
619
|
+
if (!control.agentContacts)
|
|
620
|
+
throw new FleetError('capability_unavailable', 'Supervisor contacts API is unavailable');
|
|
621
|
+
return control.agentContacts();
|
|
622
|
+
});
|
|
623
|
+
app.get('/api/v1/roles/:id/messages', async (request) => {
|
|
624
|
+
auth.authenticate(request);
|
|
625
|
+
const control = await services.session(request.params.id);
|
|
626
|
+
if (!control.agentHistory)
|
|
627
|
+
throw new FleetError('capability_unavailable', 'Supervisor history API is unavailable');
|
|
628
|
+
return control.agentHistory({ peer_cid: request.query.peer_cid, ...(request.query.limit !== undefined ? { limit: Number(request.query.limit) } : {}), ...(request.query.before_seq !== undefined ? { before_seq: Number(request.query.before_seq) } : {}) });
|
|
629
|
+
});
|
|
630
|
+
app.get('/api/v1/roles/:id/ours/tools', async (request) => {
|
|
631
|
+
auth.authenticate(request);
|
|
632
|
+
return oursTools.list(request.params.id);
|
|
633
|
+
});
|
|
634
|
+
app.post('/api/v1/roles/:id/ours/call', async (request) => {
|
|
635
|
+
const session = auth.authenticate(request, true);
|
|
636
|
+
const result = await oursTools.call(request.params.id, request.body);
|
|
637
|
+
await audit.record({ requestId: request.id, browser: session.id,
|
|
638
|
+
action: 'ours.call', result: result.result.isError === true ? 'tool_error' : 'succeeded' });
|
|
639
|
+
return result;
|
|
640
|
+
});
|
|
314
641
|
app.get('/api/v1/roles/:id', async (request) => {
|
|
315
642
|
auth.authenticate(request);
|
|
316
|
-
|
|
643
|
+
const detail = await services.query.detail(request.params.id);
|
|
644
|
+
return { ...detail, chatIdle: detail.role.lifetime === 'temporary' ? readChatIdle(agentDir(request.params.id, true)) ?? null : null };
|
|
317
645
|
});
|
|
318
646
|
app.get('/api/v1/roles/:id/removal-preview', async (request) => {
|
|
319
647
|
auth.authenticate(request);
|
|
@@ -348,15 +676,79 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
348
676
|
const control = await services.session(request.params.id);
|
|
349
677
|
if (!control.conversationPage)
|
|
350
678
|
throw new FleetError('capability_unavailable', 'this role has no conversation ledger');
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
679
|
+
const limit = request.query.limit ? Number(request.query.limit) : undefined;
|
|
680
|
+
// `latest` / `before` page backwards from the newest event; `after` pages forward for live updates.
|
|
681
|
+
const backward = request.query.latest === '1' || request.query.before !== undefined;
|
|
682
|
+
if (backward && request.query.after !== undefined)
|
|
683
|
+
throw new FleetError('invalid_request', 'after cannot be combined with latest or before');
|
|
684
|
+
if (backward && !control.conversationTail)
|
|
685
|
+
throw new FleetError('capability_unavailable', 'this role cannot page its conversation backwards');
|
|
686
|
+
const role = await services.repository.get(request.params.id);
|
|
687
|
+
const stateDir = role ? services.repository.stateDir(role) : undefined;
|
|
688
|
+
const present = (event) => presentAttachmentEvent(stateDir, request.params.id, event);
|
|
689
|
+
if (backward) {
|
|
690
|
+
const page = await control.conversationTail({ before: request.query.before, limit });
|
|
691
|
+
return { ...page, events: page.events.map(present), context: page.context.map(present) };
|
|
692
|
+
}
|
|
693
|
+
const page = await control.conversationPage({ after: request.query.after, limit });
|
|
694
|
+
return { ...page, events: page.events.map(present) };
|
|
695
|
+
});
|
|
696
|
+
app.get('/api/v1/roles/:id/attachments/:attachmentId', async (request, reply) => {
|
|
697
|
+
auth.authenticate(request);
|
|
698
|
+
const role = await services.repository.get(request.params.id);
|
|
699
|
+
if (!role)
|
|
700
|
+
throw new FleetError('role_not_found', 'Agent not found');
|
|
701
|
+
const stateDir = services.repository.stateDir(role);
|
|
702
|
+
if (!stateDir)
|
|
703
|
+
throw new FleetError('capability_unavailable', 'Agent file storage is unavailable');
|
|
704
|
+
const { record, bytes } = readAgentAttachment(stateDir, request.params.attachmentId);
|
|
705
|
+
const image = /^image\/(png|jpeg|gif|webp|avif)$/.test(record.mimeType);
|
|
706
|
+
reply.header('Cache-Control', 'private, no-store');
|
|
707
|
+
reply.header('X-Content-Type-Options', 'nosniff');
|
|
708
|
+
reply.header('Content-Security-Policy', "default-src 'none'; sandbox");
|
|
709
|
+
reply.header('Content-Disposition', `${image ? 'inline' : 'attachment'}; filename*=UTF-8''${encodeURIComponent(record.name).replace(/'/g, '%27')}`);
|
|
710
|
+
return reply.type(image ? record.mimeType : 'application/octet-stream').send(bytes);
|
|
711
|
+
});
|
|
712
|
+
app.post('/api/v1/roles/:id/attachments', { bodyLimit: MAX_ATTACHMENT_BODY }, async (request) => {
|
|
713
|
+
auth.authenticate(request, true);
|
|
714
|
+
const role = await services.repository.get(request.params.id);
|
|
715
|
+
if (!role)
|
|
716
|
+
throw new FleetError('role_not_found', 'Agent not found');
|
|
717
|
+
const stateDir = services.repository.stateDir(role);
|
|
718
|
+
if (!stateDir)
|
|
719
|
+
throw new FleetError('capability_unavailable', 'Agent file storage is unavailable');
|
|
720
|
+
const body = request.body;
|
|
721
|
+
if (typeof body?.expectedSessionGeneration !== 'string' || !body.expectedSessionGeneration.trim())
|
|
722
|
+
throw new FleetError('invalid_request', 'expectedSessionGeneration is required for attachments');
|
|
723
|
+
const control = await services.session(request.params.id);
|
|
724
|
+
if (!control.conversationPage || !control.submitPromptV2)
|
|
725
|
+
throw new FleetError('capability_unavailable', 'Session-bound attachments are unavailable');
|
|
726
|
+
const page = await control.conversationPage({ limit: 1 });
|
|
727
|
+
if (page.snapshot.sessionGeneration !== body.expectedSessionGeneration)
|
|
728
|
+
throw new FleetError('stale_state', 'Agent session changed. Select the files again.');
|
|
729
|
+
return storeAgentAttachment(stateDir, body.expectedSessionGeneration, request.body);
|
|
355
730
|
});
|
|
356
731
|
app.post('/api/v1/roles/:id/input', async (request, reply) => {
|
|
357
732
|
const session = auth.authenticate(request, true);
|
|
358
733
|
const body = request.body;
|
|
359
|
-
|
|
734
|
+
if (body?.expectedSessionGeneration !== undefined && (typeof body.expectedSessionGeneration !== 'string' || !body.expectedSessionGeneration.trim()))
|
|
735
|
+
throw new FleetError('invalid_request', 'expectedSessionGeneration must be a nonempty string');
|
|
736
|
+
let text = String(body?.text ?? '');
|
|
737
|
+
if (body?.attachments !== undefined) {
|
|
738
|
+
if (typeof body.commandId !== 'string' || !body.commandId.trim())
|
|
739
|
+
throw new FleetError('invalid_request', 'commandId is required for attachments');
|
|
740
|
+
if (typeof body.expectedSessionGeneration !== 'string' || !body.expectedSessionGeneration.trim())
|
|
741
|
+
throw new FleetError('invalid_request', 'expectedSessionGeneration is required for attachments');
|
|
742
|
+
const role = await services.repository.get(request.params.id);
|
|
743
|
+
if (!role)
|
|
744
|
+
throw new FleetError('role_not_found', 'Agent not found');
|
|
745
|
+
const stateDir = services.repository.stateDir(role);
|
|
746
|
+
if (!stateDir)
|
|
747
|
+
throw new FleetError('capability_unavailable', 'Agent file storage is unavailable');
|
|
748
|
+
const originalText = text;
|
|
749
|
+
text = attachmentPrompt(stateDir, body.expectedSessionGeneration, body.attachments, text);
|
|
750
|
+
prepareAttachmentPresentation(stateDir, body.expectedSessionGeneration, body.commandId, body.attachments, originalText, text);
|
|
751
|
+
}
|
|
360
752
|
const commandId = typeof body?.commandId === 'string' && body.commandId.trim()
|
|
361
753
|
? body.commandId : undefined;
|
|
362
754
|
const control = await services.session(request.params.id);
|
|
@@ -367,6 +759,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
367
759
|
const admittedCommandId = commandId ?? randomBytes(16).toString('hex');
|
|
368
760
|
const receipt = await control.submitPromptV2({
|
|
369
761
|
commandId: admittedCommandId, text, source: 'owner_admin_console',
|
|
762
|
+
expectedSessionGeneration: body.expectedSessionGeneration,
|
|
370
763
|
actorBrowserSession: createHmac('sha256', digestKey).update(session.id).digest('hex').slice(0, 24),
|
|
371
764
|
});
|
|
372
765
|
await audit.record({
|
|
@@ -378,6 +771,8 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
378
771
|
reply.code(202);
|
|
379
772
|
return receipt;
|
|
380
773
|
}
|
|
774
|
+
if (body.expectedSessionGeneration !== undefined)
|
|
775
|
+
throw new FleetError('capability_unavailable', 'session-bound voice input is unavailable for this role');
|
|
381
776
|
const receipt = await control.sendText(text);
|
|
382
777
|
await audit.record({
|
|
383
778
|
requestId: request.id, browser: session.id, roleId: request.params.id,
|
|
@@ -443,10 +838,22 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
443
838
|
const body = request.body;
|
|
444
839
|
if (!body.action || !['start', 'stop', 'restart_resume', 'restart_fresh'].includes(body.action))
|
|
445
840
|
throw new FleetError('invalid_request', 'invalid lifecycle action');
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
841
|
+
let receipt;
|
|
842
|
+
try {
|
|
843
|
+
receipt = await services.commands.execute({
|
|
844
|
+
roleId: request.params.id, action: body.action,
|
|
845
|
+
actionId: body.actionId, confirmation: body.confirmation,
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
catch (error) {
|
|
849
|
+
// An existing receipt always wins: a conflicting/repeated action is not a
|
|
850
|
+
// proof that the original operation was never admitted.
|
|
851
|
+
if (typeof body.actionId === 'string' && !services.commands.get(body.actionId)) {
|
|
852
|
+
const normalized = normalizeError(error);
|
|
853
|
+
return reply.code(409).send({ error: { code: 'action_not_accepted', message: normalized.message }, accepted: false });
|
|
854
|
+
}
|
|
855
|
+
throw error;
|
|
856
|
+
}
|
|
450
857
|
events.publish('action.changed', receipt, request.params.id);
|
|
451
858
|
reply.code(202);
|
|
452
859
|
return receipt;
|
|
@@ -534,6 +941,9 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
534
941
|
}
|
|
535
942
|
socket.send(JSON.stringify(payload));
|
|
536
943
|
};
|
|
944
|
+
const role = await services.repository.get(request.params.id);
|
|
945
|
+
const stateDir = role ? services.repository.stateDir(role) : undefined;
|
|
946
|
+
const present = (event) => presentAttachmentEvent(stateDir, request.params.id, event);
|
|
537
947
|
const follow = await control.followConversation({
|
|
538
948
|
after,
|
|
539
949
|
onPage: page => {
|
|
@@ -544,11 +954,11 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
544
954
|
lastCursor: page.nextCursor ?? after,
|
|
545
955
|
});
|
|
546
956
|
if (page.events.length)
|
|
547
|
-
guardedSend({ type: 'events', events: page.events });
|
|
957
|
+
guardedSend({ type: 'events', events: page.events.map(present) });
|
|
548
958
|
if (page.hasMore)
|
|
549
959
|
guardedSend({ type: 'resync.required' });
|
|
550
960
|
},
|
|
551
|
-
onEvent: event => guardedSend({ type: 'events', events: [event] }),
|
|
961
|
+
onEvent: event => guardedSend({ type: 'events', events: [present(event)] }),
|
|
552
962
|
onClose: reason => {
|
|
553
963
|
if (socket.readyState === socket.OPEN)
|
|
554
964
|
socket.close(4409, (reason ?? 'conversation stream ended').slice(0, 120));
|
|
@@ -561,13 +971,17 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
561
971
|
socket.on('close', () => { clearInterval(heartbeat); follow.close(); });
|
|
562
972
|
});
|
|
563
973
|
});
|
|
564
|
-
const staticRoot = join(dirname(fileURLToPath(import.meta.url)), '..', 'web-app');
|
|
974
|
+
const staticRoot = options.staticRoot ?? join(dirname(fileURLToPath(import.meta.url)), '..', 'web-app');
|
|
565
975
|
if (existsSync(staticRoot)) {
|
|
566
976
|
await app.register(fastifyStatic, { root: staticRoot, prefix: '/' });
|
|
567
977
|
app.setNotFoundHandler((request, reply) => {
|
|
568
978
|
if (request.url.startsWith('/api/'))
|
|
569
979
|
return reply.code(404).send({ error: 'not found' });
|
|
570
|
-
|
|
980
|
+
const path = request.url.split('?')[0];
|
|
981
|
+
if ((path === '/chats' || path.startsWith('/chats/')) && existsSync(join(staticRoot, 'fleet-index.html')))
|
|
982
|
+
return reply.redirect((request.url.includes('?') ? '/fleet/chats' : '/fleet') + (request.url.includes('?') ? request.url.slice(request.url.indexOf('?')) : ''));
|
|
983
|
+
const fleetEntry = (path === '/fleet' || path.startsWith('/fleet/')) && existsSync(join(staticRoot, 'fleet-index.html'));
|
|
984
|
+
return reply.sendFile(fleetEntry ? 'fleet-index.html' : 'index.html');
|
|
571
985
|
});
|
|
572
986
|
}
|
|
573
987
|
return {
|
|
@@ -582,17 +996,17 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
582
996
|
function setAuthCookies(reply, session, device, secure = false) {
|
|
583
997
|
const suffix = secure ? '; Secure' : '';
|
|
584
998
|
reply.header('Set-Cookie', [
|
|
585
|
-
`ofs_session=${encodeURIComponent(session)}; HttpOnly; SameSite=Strict; Path
|
|
586
|
-
`ofs_device=${encodeURIComponent(device)}; HttpOnly; SameSite=Strict; Path
|
|
999
|
+
`ofs_session=${encodeURIComponent(session)}; HttpOnly; SameSite=Strict; Path=/; Max-Age=28800${suffix}`,
|
|
1000
|
+
`ofs_device=${encodeURIComponent(device)}; HttpOnly; SameSite=Strict; Path=/; Max-Age=2592000${suffix}`,
|
|
587
1001
|
]);
|
|
588
1002
|
}
|
|
589
1003
|
function setSessionCookie(reply, session, secure = false) {
|
|
590
|
-
reply.header('Set-Cookie', `ofs_session=${encodeURIComponent(session)}; HttpOnly; SameSite=Strict; Path
|
|
1004
|
+
reply.header('Set-Cookie', `ofs_session=${encodeURIComponent(session)}; HttpOnly; SameSite=Strict; Path=/; Max-Age=28800${secure ? '; Secure' : ''}`);
|
|
591
1005
|
}
|
|
592
1006
|
function clearAuthCookies(reply) {
|
|
593
1007
|
reply.header('Set-Cookie', [
|
|
594
|
-
'ofs_session=; HttpOnly; SameSite=Strict; Path
|
|
595
|
-
'ofs_device=; HttpOnly; SameSite=Strict; Path
|
|
1008
|
+
'ofs_session=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0',
|
|
1009
|
+
'ofs_device=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0',
|
|
596
1010
|
]);
|
|
597
1011
|
}
|
|
598
1012
|
function promoteRequest(body) {
|