@ours.network/fleet 1.2.0-nightly.5 → 1.2.0-nightly.50
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 +263 -53
- package/dist/account-origin.d.ts +2 -0
- package/dist/account-origin.js +6 -0
- package/dist/agent-ours/bridge.js +9 -0
- package/dist/agent-ours/correspondence.d.ts +10 -0
- package/dist/agent-ours/correspondence.js +1 -0
- package/dist/agent-ours/harness.js +8 -0
- package/dist/agent-ours/runtime.d.ts +6 -0
- package/dist/agent-ours/runtime.js +36 -0
- package/dist/agent-ours/service.js +33 -13
- package/dist/application/codex-models.d.ts +15 -0
- package/dist/application/codex-models.js +56 -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/role-creation-service.js +1 -1
- package/dist/application/role-removal-service.js +2 -0
- package/dist/application/role-repository.js +2 -2
- 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 +30 -0
- package/dist/application/session-control.js +72 -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.d.ts +2 -0
- package/dist/briefing.js +8 -1
- package/dist/build-info.json +7 -4
- package/dist/capabilities.d.ts +6 -1
- package/dist/capabilities.js +8 -0
- package/dist/cli.js +376 -34
- package/dist/client-profile.d.ts +5 -17
- package/dist/client-profile.js +4 -127
- package/dist/config.d.ts +28 -0
- package/dist/config.js +41 -6
- package/dist/creation.js +2 -2
- 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 +126 -22
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +55 -60
- package/dist/erased-resources.d.ts +9 -0
- package/dist/erased-resources.js +87 -0
- package/dist/fleet-command-audit.d.ts +7 -0
- package/dist/fleet-command-audit.js +26 -9
- package/dist/harness/claude-code.js +9 -1
- package/dist/harness/claude-runtime.d.ts +12 -0
- package/dist/harness/claude-runtime.js +43 -0
- 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-reasoning.d.ts +5 -0
- package/dist/harness/codex-reasoning.js +12 -0
- package/dist/harness/codex-runtime.d.ts +11 -1
- package/dist/harness/codex-runtime.js +31 -4
- package/dist/harness/codex.d.ts +3 -2
- package/dist/harness/codex.js +20 -11
- package/dist/init-wizard.d.ts +2 -0
- package/dist/init-wizard.js +38 -19
- package/dist/isolation/policy.js +2 -0
- package/dist/isolation/types.d.ts +5 -0
- package/dist/managed-cli.d.ts +253 -0
- package/dist/managed-cli.js +923 -0
- package/dist/minimal-setup.d.ts +2 -0
- package/dist/minimal-setup.js +44 -0
- package/dist/monitor.d.ts +1 -34
- package/dist/monitor.js +24 -151
- package/dist/notifications/fleet-producer.d.ts +19 -0
- package/dist/notifications/fleet-producer.js +208 -0
- package/dist/notifications/outbox.d.ts +46 -0
- package/dist/notifications/outbox.js +158 -0
- package/dist/notifications/target-cleanup.d.ts +8 -0
- package/dist/notifications/target-cleanup.js +41 -0
- package/dist/ops.js +5 -1
- 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/paths.d.ts +2 -0
- package/dist/paths.js +2 -0
- package/dist/preset-migration.js +25 -0
- package/dist/resolved-plan.js +1 -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.d.ts +1 -1
- package/dist/rooms-tasks/config.js +11 -4
- package/dist/rooms-tasks/cowork-adapter.d.ts +6 -4
- package/dist/rooms-tasks/cowork-adapter.js +8 -121
- 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 +173 -46
- 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 +19 -3
- package/dist/runner.js +128 -45
- package/dist/session/acp.d.ts +9 -1
- package/dist/session/acp.js +31 -6
- package/dist/session/arbiter.d.ts +5 -0
- package/dist/session/arbiter.js +28 -2
- package/dist/session/codex-app-server.d.ts +5 -0
- package/dist/session/codex-app-server.js +36 -3
- package/dist/session/control.d.ts +12 -1
- package/dist/session/control.js +68 -3
- package/dist/session/conversation-normalizer.d.ts +2 -1
- package/dist/session/conversation-normalizer.js +1 -1
- package/dist/session/conversation-store.d.ts +1 -1
- package/dist/session/conversation-store.js +7 -11
- package/dist/session/conversation-types.d.ts +7 -3
- package/dist/session/task-notice.d.ts +14 -0
- package/dist/session/task-notice.js +13 -0
- package/dist/session/types.d.ts +6 -0
- package/dist/setup-tunnel-preflight.d.ts +34 -0
- package/dist/setup-tunnel-preflight.js +62 -0
- package/dist/socket-path.d.ts +2 -0
- package/dist/socket-path.js +22 -0
- package/dist/spawn.js +8 -1
- package/dist/startup-readiness.d.ts +13 -0
- package/dist/startup-readiness.js +38 -0
- package/dist/subscriptions/cli.d.ts +91 -0
- package/dist/subscriptions/cli.js +223 -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/tunnel-setup-completion.d.ts +7 -0
- package/dist/tunnel-setup-completion.js +13 -0
- package/dist/tunnel-setup-input.d.ts +19 -0
- package/dist/tunnel-setup-input.js +124 -0
- package/dist/web/agent-attachments.d.ts +29 -0
- package/dist/web/agent-attachments.js +162 -0
- package/dist/web/auth.d.ts +29 -1
- package/dist/web/auth.js +82 -7
- package/dist/web/available-port.d.ts +2 -0
- package/dist/web/available-port.js +23 -0
- package/dist/web/control.d.ts +3 -3
- package/dist/web/control.js +10 -8
- 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 +32 -0
- package/dist/web/prefix-gateway.js +234 -0
- package/dist/web/runtime.d.ts +3 -1
- package/dist/web/runtime.js +116 -31
- package/dist/web/server.d.ts +16 -0
- package/dist/web/server.js +588 -53
- package/dist/web/service.d.ts +11 -2
- package/dist/web/service.js +30 -4
- package/dist/web/workspace-devices.d.ts +36 -0
- package/dist/web/workspace-devices.js +101 -0
- package/dist/web/workspace-tunnel.d.ts +1 -0
- package/dist/web/workspace-tunnel.js +34 -0
- package/dist/workspace-enrollment.d.ts +82 -0
- package/dist/workspace-enrollment.js +566 -0
- package/dist/workspace-replacement.d.ts +66 -0
- package/dist/workspace-replacement.js +220 -0
- package/docs/live-voice.md +13 -0
- package/docs/room-layouts.md +227 -0
- package/docs/task-workspaces.md +130 -0
- package/docs/validation/managed-cli-permissions.md +421 -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 +15 -18
- package/presets/README.md +78 -0
- package/presets/brain-catalog.json +8 -2
- package/presets/fleet/agent_templates/Critic.yaml +1 -1
- package/presets/fleet/agent_templates/Developer.yaml +1 -1
- package/presets/fleet/agent_templates/Engineer.yaml +5 -0
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +1 -1
- package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
- 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 +34 -11
- 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/index.html +0 -17
- package/dist/web-app/manifest.webmanifest +0 -15
- package/dist/web-app/offline.html +0 -18
- package/dist/web-app/sw.js +0 -51
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { loadConfig } from '../config.js';
|
|
2
|
+
import { type RoomLayoutDefinition } from '../rooms-tasks/layout-config.js';
|
|
3
|
+
export declare const ABSENT_REVISION = "absent";
|
|
4
|
+
export type LayoutDefinitionBody = Omit<RoomLayoutDefinition, 'sourceFile'>;
|
|
5
|
+
export type LayoutListEntry = {
|
|
6
|
+
name: string;
|
|
7
|
+
revision: string;
|
|
8
|
+
file: string;
|
|
9
|
+
definition: LayoutDefinitionBody;
|
|
10
|
+
issues: string[];
|
|
11
|
+
definition_hash: string;
|
|
12
|
+
} | {
|
|
13
|
+
name: string;
|
|
14
|
+
revision: string;
|
|
15
|
+
file: string;
|
|
16
|
+
error: string;
|
|
17
|
+
};
|
|
18
|
+
export declare class RoomLayoutDefinitions {
|
|
19
|
+
private configPath?;
|
|
20
|
+
private deps;
|
|
21
|
+
constructor(configPath?: string | undefined, deps?: {
|
|
22
|
+
loadConfiguration?: typeof loadConfig;
|
|
23
|
+
});
|
|
24
|
+
private root;
|
|
25
|
+
private pathFor;
|
|
26
|
+
private currentRevision;
|
|
27
|
+
/** Agent-template references that do not resolve in the current Fleet configuration. */
|
|
28
|
+
private templateIssues;
|
|
29
|
+
/** The configured default (`rooms.defaults.template`, else `tasks.default_room_template`) when it names a usable layout. */
|
|
30
|
+
defaultLayout(entries?: LayoutListEntry[]): string | undefined;
|
|
31
|
+
list(): LayoutListEntry[];
|
|
32
|
+
validate(raw: unknown): {
|
|
33
|
+
definition?: LayoutDefinitionBody;
|
|
34
|
+
issues: string[];
|
|
35
|
+
};
|
|
36
|
+
save(name: string, expectedRevision: string, raw: unknown): Promise<{
|
|
37
|
+
name: string;
|
|
38
|
+
revision: string;
|
|
39
|
+
definition: LayoutDefinitionBody;
|
|
40
|
+
}>;
|
|
41
|
+
remove(name: string, expectedRevision: string): Promise<{
|
|
42
|
+
name: string;
|
|
43
|
+
deleted: boolean;
|
|
44
|
+
}>;
|
|
45
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/** Web authoring of `fleet/room_layouts/<name>.yaml`.
|
|
2
|
+
* Writes are revision-guarded (sha256 of the file bytes) and validated with the
|
|
3
|
+
* same parser the CLI uses. Retained layout runs keep their own snapshots, so
|
|
4
|
+
* editing or deleting a definition never changes an existing run.
|
|
5
|
+
*/
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync } from 'node:fs';
|
|
8
|
+
import { basename, extname, join } from 'node:path';
|
|
9
|
+
import { stringify } from 'yaml';
|
|
10
|
+
import { replaceFileAtomically, withFileLock } from '../atomic-file.js';
|
|
11
|
+
import { parseFleetDocument } from '../config-yaml.js';
|
|
12
|
+
import { defaultConfigPath } from '../paths.js';
|
|
13
|
+
import { loadConfig, splitRootFor } from '../config.js';
|
|
14
|
+
import { assertLayoutFile, validLayoutKey, validateRoomLayout } from '../rooms-tasks/layout-config.js';
|
|
15
|
+
import { FleetError } from './errors.js';
|
|
16
|
+
import { layoutDefinitionHash } from './task-layouts.js';
|
|
17
|
+
export const ABSENT_REVISION = 'absent';
|
|
18
|
+
const revisionOf = (bytes) => createHash('sha256').update(bytes).digest('hex');
|
|
19
|
+
export class RoomLayoutDefinitions {
|
|
20
|
+
configPath;
|
|
21
|
+
deps;
|
|
22
|
+
constructor(configPath, deps = {}) {
|
|
23
|
+
this.configPath = configPath;
|
|
24
|
+
this.deps = deps;
|
|
25
|
+
}
|
|
26
|
+
root() { return join(splitRootFor(this.configPath ?? defaultConfigPath()), 'room_layouts'); }
|
|
27
|
+
pathFor(name) {
|
|
28
|
+
if (!validLayoutKey(name))
|
|
29
|
+
throw new FleetError('invalid_request', 'layout name must start with a letter and use letters, digits, - or _');
|
|
30
|
+
return join(this.root(), `${name}.yaml`);
|
|
31
|
+
}
|
|
32
|
+
currentRevision(path) {
|
|
33
|
+
if (!existsSync(path))
|
|
34
|
+
return ABSENT_REVISION;
|
|
35
|
+
assertLayoutFile(path);
|
|
36
|
+
return revisionOf(readFileSync(path));
|
|
37
|
+
}
|
|
38
|
+
/** Agent-template references that do not resolve in the current Fleet configuration. */
|
|
39
|
+
templateIssues(definition) {
|
|
40
|
+
let templates = {};
|
|
41
|
+
try {
|
|
42
|
+
templates = (this.deps.loadConfiguration ?? loadConfig)(this.configPath).agentTemplates ?? {};
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
return [`fleet configuration unavailable: ${error.message}`];
|
|
46
|
+
}
|
|
47
|
+
return Object.entries(definition.participants).flatMap(([key, participant]) => participant.agent_template && !Object.hasOwn(templates, participant.agent_template)
|
|
48
|
+
? [`participant ${key}: agent template not found: ${participant.agent_template}`] : []);
|
|
49
|
+
}
|
|
50
|
+
/** The configured default (`rooms.defaults.template`, else `tasks.default_room_template`) when it names a usable layout. */
|
|
51
|
+
defaultLayout(entries = this.list()) {
|
|
52
|
+
let name;
|
|
53
|
+
try {
|
|
54
|
+
const config = (this.deps.loadConfiguration ?? loadConfig)(this.configPath);
|
|
55
|
+
name = config.rooms?.defaults?.template ?? config.tasks?.default_room_template;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const entry = entries.find(e => e.name === name);
|
|
61
|
+
return entry && !('error' in entry) && !entry.issues.length ? entry.name : undefined;
|
|
62
|
+
}
|
|
63
|
+
list() {
|
|
64
|
+
const root = this.root();
|
|
65
|
+
if (!existsSync(root))
|
|
66
|
+
return [];
|
|
67
|
+
assertLayoutFile(root, true);
|
|
68
|
+
return readdirSync(root).filter(file => /\.ya?ml$/.test(file)).sort().map(file => {
|
|
69
|
+
const name = basename(file, extname(file)), path = join(root, file);
|
|
70
|
+
try {
|
|
71
|
+
if (!validLayoutKey(name))
|
|
72
|
+
throw Error(`invalid room layout id: ${name}`);
|
|
73
|
+
assertLayoutFile(path);
|
|
74
|
+
const bytes = readFileSync(path);
|
|
75
|
+
const definition = validateRoomLayout(parseFleetDocument(path, bytes.toString('utf8'), 'strict').value, file);
|
|
76
|
+
return { name, file, revision: revisionOf(bytes), definition, issues: this.templateIssues(definition), definition_hash: layoutDefinitionHash(definition) };
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
// Only a trusted regular file may be read to offer an overwrite revision.
|
|
80
|
+
let revision = ABSENT_REVISION;
|
|
81
|
+
try {
|
|
82
|
+
assertLayoutFile(path);
|
|
83
|
+
revision = revisionOf(readFileSync(path));
|
|
84
|
+
}
|
|
85
|
+
catch { /* not trusted: never read */ }
|
|
86
|
+
return { name, file, revision, error: error.message };
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
validate(raw) {
|
|
91
|
+
let definition;
|
|
92
|
+
try {
|
|
93
|
+
definition = validateRoomLayout(raw, 'room layout');
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
return { issues: [error.message] };
|
|
97
|
+
}
|
|
98
|
+
return { definition, issues: this.templateIssues(definition) };
|
|
99
|
+
}
|
|
100
|
+
async save(name, expectedRevision, raw) {
|
|
101
|
+
const path = this.pathFor(name);
|
|
102
|
+
const { definition, issues } = this.validate(raw);
|
|
103
|
+
if (!definition || issues.length)
|
|
104
|
+
throw new FleetError('invalid_request', issues.join('; '));
|
|
105
|
+
const root = this.root();
|
|
106
|
+
mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
107
|
+
assertLayoutFile(root, true);
|
|
108
|
+
return withFileLock(`${path}.lock`, async () => {
|
|
109
|
+
const current = this.currentRevision(path);
|
|
110
|
+
if (current !== expectedRevision)
|
|
111
|
+
throw new FleetError('stale_state', 'room layout changed since it was loaded; reload before saving');
|
|
112
|
+
const { description, participants, rooms } = definition;
|
|
113
|
+
const text = stringify({ version: 1, ...(description ? { description } : {}), participants, rooms });
|
|
114
|
+
replaceFileAtomically(path, text, 0o600);
|
|
115
|
+
return { name, revision: revisionOf(text), definition };
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
async remove(name, expectedRevision) {
|
|
119
|
+
const path = this.pathFor(name);
|
|
120
|
+
return withFileLock(`${path}.lock`, async () => {
|
|
121
|
+
const current = this.currentRevision(path);
|
|
122
|
+
if (current === ABSENT_REVISION)
|
|
123
|
+
return { name, deleted: false };
|
|
124
|
+
if (current !== expectedRevision)
|
|
125
|
+
throw new FleetError('stale_state', 'room layout changed since it was loaded; reload before deleting');
|
|
126
|
+
unlinkSync(path);
|
|
127
|
+
return { name, deleted: true };
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type ConversationTailPage } from './conversation-history.js';
|
|
2
|
+
import type { AgentHistoryQuery } from '../agent-ours/correspondence.js';
|
|
1
3
|
import { controlRequest } from '../session/control.js';
|
|
2
4
|
import type { ConversationEventV1, ConversationSnapshot, PromptReceipt } from '../session/conversation-types.js';
|
|
3
5
|
import type { SessionSnapshot } from '../session/types.js';
|
|
@@ -23,6 +25,8 @@ export interface InterruptReceipt {
|
|
|
23
25
|
reasonCode?: string;
|
|
24
26
|
}
|
|
25
27
|
export interface RoleSessionControl {
|
|
28
|
+
agentContacts?(): Promise<unknown>;
|
|
29
|
+
agentHistory?(request: AgentHistoryQuery): Promise<unknown>;
|
|
26
30
|
describe(): Promise<SessionDescriptor>;
|
|
27
31
|
snapshot(): Promise<SessionSnapshot>;
|
|
28
32
|
recentOutput(request?: {
|
|
@@ -41,12 +45,24 @@ export interface RoleSessionControl {
|
|
|
41
45
|
after?: string;
|
|
42
46
|
limit?: number;
|
|
43
47
|
}): Promise<ConversationPageView>;
|
|
48
|
+
/** The newest visible events (or those before `before`), read backwards from the ledger. */
|
|
49
|
+
conversationTail?(request: {
|
|
50
|
+
before?: string;
|
|
51
|
+
limit?: number;
|
|
52
|
+
}): Promise<ConversationTailPage>;
|
|
44
53
|
submitPromptV2?(request: {
|
|
45
54
|
commandId: string;
|
|
46
55
|
text: string;
|
|
47
56
|
actorBrowserSession: string;
|
|
57
|
+
expectedSessionGeneration?: string;
|
|
58
|
+
requireIdle?: boolean;
|
|
48
59
|
source: 'owner_admin_console';
|
|
49
60
|
}): Promise<PromptReceipt>;
|
|
61
|
+
interruptPromptV2?(request: {
|
|
62
|
+
commandId: string;
|
|
63
|
+
expectedSessionGeneration: string;
|
|
64
|
+
promptId: string;
|
|
65
|
+
}): Promise<InterruptReceipt>;
|
|
50
66
|
interruptV2?(commandId: string): Promise<InterruptReceipt & {
|
|
51
67
|
commandId: string;
|
|
52
68
|
}>;
|
|
@@ -75,6 +91,9 @@ export declare class RoleSessionControlAdapter implements RoleSessionControl {
|
|
|
75
91
|
private readonly stateDir;
|
|
76
92
|
private readonly request;
|
|
77
93
|
constructor(stateDir: string, request?: typeof controlRequest);
|
|
94
|
+
agentContacts(): Promise<unknown>;
|
|
95
|
+
agentHistory(request: AgentHistoryQuery): Promise<unknown>;
|
|
96
|
+
private requireCorrespondence;
|
|
78
97
|
describe(): Promise<SessionDescriptor>;
|
|
79
98
|
snapshot(): Promise<SessionSnapshot>;
|
|
80
99
|
recentOutput(request?: {
|
|
@@ -87,12 +106,23 @@ export declare class RoleSessionControlAdapter implements RoleSessionControl {
|
|
|
87
106
|
after?: string;
|
|
88
107
|
limit?: number;
|
|
89
108
|
}): Promise<ConversationPageView>;
|
|
109
|
+
conversationTail(request?: {
|
|
110
|
+
before?: string;
|
|
111
|
+
limit?: number;
|
|
112
|
+
}): Promise<ConversationTailPage>;
|
|
90
113
|
submitPromptV2(request: {
|
|
91
114
|
commandId: string;
|
|
92
115
|
text: string;
|
|
93
116
|
actorBrowserSession: string;
|
|
117
|
+
expectedSessionGeneration?: string;
|
|
118
|
+
requireIdle?: boolean;
|
|
94
119
|
source: 'owner_admin_console';
|
|
95
120
|
}): Promise<PromptReceipt>;
|
|
121
|
+
interruptPromptV2(request: {
|
|
122
|
+
commandId: string;
|
|
123
|
+
expectedSessionGeneration: string;
|
|
124
|
+
promptId: string;
|
|
125
|
+
}): Promise<InterruptReceipt>;
|
|
96
126
|
interruptV2(commandId: string): Promise<InterruptReceipt & {
|
|
97
127
|
commandId: string;
|
|
98
128
|
}>;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { conversationTailPage, resumedConversationPage } from './conversation-history.js';
|
|
1
4
|
import { controlRequest, followConversation } from '../session/control.js';
|
|
2
5
|
import { FleetError, normalizeError } from './errors.js';
|
|
3
6
|
const visibleEvents = (events) => events.filter(event => event.kind !== 'thought');
|
|
@@ -8,6 +11,10 @@ export class RoleSessionControlAdapter {
|
|
|
8
11
|
this.stateDir = stateDir;
|
|
9
12
|
this.request = request;
|
|
10
13
|
}
|
|
14
|
+
async agentContacts() { await this.requireCorrespondence(); return this.call('agent_contacts'); }
|
|
15
|
+
async agentHistory(request) { await this.requireCorrespondence(); return this.call('agent_history', { agentHistory: request }); }
|
|
16
|
+
async requireCorrespondence() { if (!(await this.describe()).features.includes('agent_correspondence'))
|
|
17
|
+
throw new FleetError('capability_unavailable', 'This session needs an updated supervisor to show contacts and history.'); }
|
|
11
18
|
async describe() {
|
|
12
19
|
const response = await this.call('snapshot');
|
|
13
20
|
const result = response;
|
|
@@ -58,28 +65,90 @@ export class RoleSessionControlAdapter {
|
|
|
58
65
|
};
|
|
59
66
|
}
|
|
60
67
|
async conversationPage(request = {}) {
|
|
61
|
-
|
|
68
|
+
const live = await this.call('conversation_page', {
|
|
62
69
|
after: request.after, limit: request.limit,
|
|
63
70
|
});
|
|
71
|
+
const snapshot = await this.snapshot();
|
|
72
|
+
if (snapshot.backend !== 'acp' || !snapshot.sessionId)
|
|
73
|
+
return live;
|
|
74
|
+
try {
|
|
75
|
+
const history = await resumedConversationPage(this.stateDir, request, live, snapshot.sessionId);
|
|
76
|
+
const check = await this.call('conversation_page', { limit: 1 });
|
|
77
|
+
return check.snapshot.sessionGeneration === live.snapshot.sessionGeneration ? history : await this.call('conversation_page', request);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return live;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async conversationTail(request = {}) {
|
|
84
|
+
const live = await this.call('conversation_page', { limit: 1 });
|
|
85
|
+
const snapshot = await this.snapshot();
|
|
86
|
+
let resumed;
|
|
87
|
+
if (snapshot.backend === 'acp' && snapshot.sessionId) {
|
|
88
|
+
try {
|
|
89
|
+
if ((await readFile(join(this.stateDir, '.acp-session-id'), 'utf8')).trim() === snapshot.sessionId)
|
|
90
|
+
resumed = snapshot.sessionId;
|
|
91
|
+
}
|
|
92
|
+
catch { /* no resumed history: live generation only */ }
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
return await conversationTailPage(this.stateDir, request, live.snapshot, resumed);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
throw new FleetError('invalid_request', error.message);
|
|
99
|
+
}
|
|
64
100
|
}
|
|
65
101
|
async submitPromptV2(request) {
|
|
66
102
|
if (!request.text.trim())
|
|
67
103
|
throw new FleetError('invalid_request', 'text is required');
|
|
68
104
|
if (Buffer.byteLength(request.text) > 32 * 1024)
|
|
69
105
|
throw new FleetError('invalid_request', 'text exceeds 32 KiB');
|
|
106
|
+
// A prompt bound to a session generation (voice or attachments) is checked atomically by supervisors that
|
|
107
|
+
// support it. Older supervisors do not know that command and never answer, so check the generation here
|
|
108
|
+
// and submit an ordinary prompt; its command id keeps retries idempotent either way.
|
|
109
|
+
const features = request.requireIdle ? (await this.describe()).features : undefined;
|
|
110
|
+
if (request.requireIdle && (!request.expectedSessionGeneration || !features?.includes('idle_bound_prompts')))
|
|
111
|
+
throw new FleetError('capability_unavailable', 'Live voice requires an updated supervisor.');
|
|
112
|
+
let command = 'submit_prompt_v2';
|
|
113
|
+
if (request.expectedSessionGeneration) {
|
|
114
|
+
if ((await this.describe()).features.includes('generation_bound_prompts'))
|
|
115
|
+
command = 'submit_voice_prompt';
|
|
116
|
+
else {
|
|
117
|
+
const live = await this.call('conversation_page', { limit: 1 });
|
|
118
|
+
if (live.snapshot?.sessionGeneration !== request.expectedSessionGeneration)
|
|
119
|
+
throw new FleetError('stale_state', 'Agent session changed. Record a new voice message.');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
70
122
|
try {
|
|
71
|
-
return await this.call(
|
|
123
|
+
return await this.call(command, {
|
|
72
124
|
commandId: request.commandId, text: request.text, actor: request.actorBrowserSession,
|
|
73
|
-
source: request.source,
|
|
125
|
+
source: request.source, expectedSessionGeneration: request.expectedSessionGeneration, requireIdle: request.requireIdle,
|
|
74
126
|
});
|
|
75
127
|
}
|
|
76
128
|
catch (error) {
|
|
77
129
|
const fleetError = normalizeError(error);
|
|
130
|
+
if (fleetError.message.includes('session_changed:'))
|
|
131
|
+
throw new FleetError('stale_state', 'Agent session changed. Record a new voice message.');
|
|
132
|
+
if (fleetError.message.includes('voice_busy:'))
|
|
133
|
+
throw new FleetError('conflict', 'Agent is already working. Wait for the current turn.');
|
|
78
134
|
if (fleetError.message.includes('idempotency_conflict'))
|
|
79
135
|
throw new FleetError('idempotency_conflict', 'this command id was already used with a different prompt body');
|
|
80
136
|
throw fleetError;
|
|
81
137
|
}
|
|
82
138
|
}
|
|
139
|
+
async interruptPromptV2(request) {
|
|
140
|
+
if (!(await this.describe()).features.includes('targeted_voice_interrupt'))
|
|
141
|
+
throw new FleetError('capability_unavailable', 'Live voice requires an updated supervisor.');
|
|
142
|
+
try {
|
|
143
|
+
return await this.call('interrupt_voice_v2', request);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
const failure = normalizeError(error);
|
|
147
|
+
if (failure.message.includes('session_changed:'))
|
|
148
|
+
throw new FleetError('stale_state', 'Agent session changed.');
|
|
149
|
+
throw failure;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
83
152
|
async interruptV2(commandId) {
|
|
84
153
|
const receipt = await this.call('interrupt_v2', { commandId });
|
|
85
154
|
return {
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
export interface SupervisorToolRequest {
|
|
2
|
+
tool: string;
|
|
3
|
+
arguments?: Record<string, unknown>;
|
|
4
|
+
}
|
|
5
|
+
/** CLI and REST use the same fixed-identity MCP server as the agent harness. */
|
|
6
|
+
export declare class SupervisorOursTools {
|
|
7
|
+
private withClient;
|
|
8
|
+
list(role: string): Promise<{
|
|
9
|
+
tools: {
|
|
10
|
+
inputSchema: {
|
|
11
|
+
[x: string]: unknown;
|
|
12
|
+
type: "object";
|
|
13
|
+
properties?: Record<string, object> | undefined;
|
|
14
|
+
required?: string[] | undefined;
|
|
15
|
+
};
|
|
16
|
+
name: string;
|
|
17
|
+
description?: string | undefined;
|
|
18
|
+
outputSchema?: {
|
|
19
|
+
[x: string]: unknown;
|
|
20
|
+
type: "object";
|
|
21
|
+
properties?: Record<string, object> | undefined;
|
|
22
|
+
required?: string[] | undefined;
|
|
23
|
+
} | undefined;
|
|
24
|
+
annotations?: {
|
|
25
|
+
title?: string | undefined;
|
|
26
|
+
readOnlyHint?: boolean | undefined;
|
|
27
|
+
destructiveHint?: boolean | undefined;
|
|
28
|
+
idempotentHint?: boolean | undefined;
|
|
29
|
+
openWorldHint?: boolean | undefined;
|
|
30
|
+
} | undefined;
|
|
31
|
+
execution?: {
|
|
32
|
+
taskSupport?: "optional" | "required" | "forbidden" | undefined;
|
|
33
|
+
} | undefined;
|
|
34
|
+
_meta?: Record<string, unknown> | undefined;
|
|
35
|
+
icons?: {
|
|
36
|
+
src: string;
|
|
37
|
+
mimeType?: string | undefined;
|
|
38
|
+
sizes?: string[] | undefined;
|
|
39
|
+
theme?: "light" | "dark" | undefined;
|
|
40
|
+
}[] | undefined;
|
|
41
|
+
title?: string | undefined;
|
|
42
|
+
}[];
|
|
43
|
+
_meta?: {
|
|
44
|
+
[x: string]: unknown;
|
|
45
|
+
progressToken?: string | number | undefined;
|
|
46
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
47
|
+
taskId: string;
|
|
48
|
+
} | undefined;
|
|
49
|
+
} | undefined;
|
|
50
|
+
nextCursor?: string | undefined;
|
|
51
|
+
agent: string;
|
|
52
|
+
identity: {
|
|
53
|
+
name: string;
|
|
54
|
+
cid: string;
|
|
55
|
+
generation: number;
|
|
56
|
+
};
|
|
57
|
+
}>;
|
|
58
|
+
call(role: string, request: SupervisorToolRequest): Promise<{
|
|
59
|
+
agent: string;
|
|
60
|
+
identity: {
|
|
61
|
+
name: string;
|
|
62
|
+
cid: string;
|
|
63
|
+
generation: number;
|
|
64
|
+
};
|
|
65
|
+
result: {
|
|
66
|
+
[x: string]: unknown;
|
|
67
|
+
content: ({
|
|
68
|
+
type: "text";
|
|
69
|
+
text: string;
|
|
70
|
+
annotations?: {
|
|
71
|
+
audience?: ("user" | "assistant")[] | undefined;
|
|
72
|
+
priority?: number | undefined;
|
|
73
|
+
lastModified?: string | undefined;
|
|
74
|
+
} | undefined;
|
|
75
|
+
_meta?: Record<string, unknown> | undefined;
|
|
76
|
+
} | {
|
|
77
|
+
type: "image";
|
|
78
|
+
data: string;
|
|
79
|
+
mimeType: string;
|
|
80
|
+
annotations?: {
|
|
81
|
+
audience?: ("user" | "assistant")[] | undefined;
|
|
82
|
+
priority?: number | undefined;
|
|
83
|
+
lastModified?: string | undefined;
|
|
84
|
+
} | undefined;
|
|
85
|
+
_meta?: Record<string, unknown> | undefined;
|
|
86
|
+
} | {
|
|
87
|
+
type: "audio";
|
|
88
|
+
data: string;
|
|
89
|
+
mimeType: string;
|
|
90
|
+
annotations?: {
|
|
91
|
+
audience?: ("user" | "assistant")[] | undefined;
|
|
92
|
+
priority?: number | undefined;
|
|
93
|
+
lastModified?: string | undefined;
|
|
94
|
+
} | undefined;
|
|
95
|
+
_meta?: Record<string, unknown> | undefined;
|
|
96
|
+
} | {
|
|
97
|
+
type: "resource";
|
|
98
|
+
resource: {
|
|
99
|
+
uri: string;
|
|
100
|
+
text: string;
|
|
101
|
+
mimeType?: string | undefined;
|
|
102
|
+
_meta?: Record<string, unknown> | undefined;
|
|
103
|
+
} | {
|
|
104
|
+
uri: string;
|
|
105
|
+
blob: string;
|
|
106
|
+
mimeType?: string | undefined;
|
|
107
|
+
_meta?: Record<string, unknown> | undefined;
|
|
108
|
+
};
|
|
109
|
+
annotations?: {
|
|
110
|
+
audience?: ("user" | "assistant")[] | undefined;
|
|
111
|
+
priority?: number | undefined;
|
|
112
|
+
lastModified?: string | undefined;
|
|
113
|
+
} | undefined;
|
|
114
|
+
_meta?: Record<string, unknown> | undefined;
|
|
115
|
+
} | {
|
|
116
|
+
uri: string;
|
|
117
|
+
name: string;
|
|
118
|
+
type: "resource_link";
|
|
119
|
+
description?: string | undefined;
|
|
120
|
+
mimeType?: string | undefined;
|
|
121
|
+
size?: number | undefined;
|
|
122
|
+
annotations?: {
|
|
123
|
+
audience?: ("user" | "assistant")[] | undefined;
|
|
124
|
+
priority?: number | undefined;
|
|
125
|
+
lastModified?: string | undefined;
|
|
126
|
+
} | undefined;
|
|
127
|
+
_meta?: {
|
|
128
|
+
[x: string]: unknown;
|
|
129
|
+
} | undefined;
|
|
130
|
+
icons?: {
|
|
131
|
+
src: string;
|
|
132
|
+
mimeType?: string | undefined;
|
|
133
|
+
sizes?: string[] | undefined;
|
|
134
|
+
theme?: "light" | "dark" | undefined;
|
|
135
|
+
}[] | undefined;
|
|
136
|
+
title?: string | undefined;
|
|
137
|
+
})[];
|
|
138
|
+
_meta?: {
|
|
139
|
+
[x: string]: unknown;
|
|
140
|
+
progressToken?: string | number | undefined;
|
|
141
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
142
|
+
taskId: string;
|
|
143
|
+
} | undefined;
|
|
144
|
+
} | undefined;
|
|
145
|
+
structuredContent?: Record<string, unknown> | undefined;
|
|
146
|
+
isError?: boolean | undefined;
|
|
147
|
+
} | {
|
|
148
|
+
[x: string]: unknown;
|
|
149
|
+
toolResult: unknown;
|
|
150
|
+
_meta?: {
|
|
151
|
+
[x: string]: unknown;
|
|
152
|
+
progressToken?: string | number | undefined;
|
|
153
|
+
"io.modelcontextprotocol/related-task"?: {
|
|
154
|
+
taskId: string;
|
|
155
|
+
} | undefined;
|
|
156
|
+
} | undefined;
|
|
157
|
+
};
|
|
158
|
+
}>;
|
|
159
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
6
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
7
|
+
import { agentDir } from '../paths.js';
|
|
8
|
+
import { ROLE_NAME_RE } from '../config.js';
|
|
9
|
+
import { legacySupervisorIdentity } from './legacy-supervisor-identity.js';
|
|
10
|
+
import { FleetError } from './errors.js';
|
|
11
|
+
/** CLI and REST use the same fixed-identity MCP server as the agent harness. */
|
|
12
|
+
export class SupervisorOursTools {
|
|
13
|
+
async withClient(role, work) {
|
|
14
|
+
if (!ROLE_NAME_RE.test(role))
|
|
15
|
+
throw new FleetError('invalid_request', 'invalid agent name');
|
|
16
|
+
const findCandidates = () => [false, true].map(temporary => ({ temporary, dir: agentDir(role, temporary) }))
|
|
17
|
+
.map(row => ({ ...row, path: join(row.dir, '.ours-bridge', 'descriptor.json') }))
|
|
18
|
+
.filter(row => existsSync(row.path));
|
|
19
|
+
const candidates = findCandidates();
|
|
20
|
+
if (candidates.length !== 1)
|
|
21
|
+
throw new FleetError('capability_unavailable', 'agent supervisor endpoint is missing or ambiguous');
|
|
22
|
+
const selected = candidates[0];
|
|
23
|
+
let descriptor;
|
|
24
|
+
let identityName;
|
|
25
|
+
try {
|
|
26
|
+
descriptor = JSON.parse(readFileSync(selected.path, 'utf8'));
|
|
27
|
+
identityName = readFileSync(join(selected.dir, '.identity'), 'utf8').trim();
|
|
28
|
+
if (!descriptor || typeof descriptor !== 'object')
|
|
29
|
+
throw new Error();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
throw new FleetError('capability_unavailable', 'supervisor identity metadata cannot be read');
|
|
33
|
+
}
|
|
34
|
+
const legacy = ['role', 'identity', 'cid'].every(key => descriptor[key] === undefined);
|
|
35
|
+
const identity = legacy
|
|
36
|
+
? legacySupervisorIdentity(role, identityName, selected.temporary, descriptor.generation)
|
|
37
|
+
: { name: identityName, cid: descriptor.cid, generation: descriptor.generation };
|
|
38
|
+
if (legacy && descriptor.socket !== join(selected.dir, '.ours-bridge', `g${descriptor.generation}.sock`))
|
|
39
|
+
throw new FleetError('capability_unavailable', 'legacy supervisor socket does not match the selected agent');
|
|
40
|
+
if ((!legacy && (descriptor.role !== role || descriptor.identity !== identityName))
|
|
41
|
+
|| !/^[a-f0-9]{64}$/i.test(identity.cid ?? '') || !Number.isSafeInteger(descriptor.generation) || descriptor.generation < 1)
|
|
42
|
+
throw new FleetError('capability_unavailable', 'supervisor identity metadata is unavailable or mismatched');
|
|
43
|
+
const verifySelection = () => {
|
|
44
|
+
try {
|
|
45
|
+
const current = findCandidates();
|
|
46
|
+
const freshDescriptor = JSON.parse(readFileSync(selected.path, 'utf8'));
|
|
47
|
+
if (current.length !== 1 || current[0].path !== selected.path
|
|
48
|
+
|| readFileSync(join(selected.dir, '.identity'), 'utf8').trim() !== identityName
|
|
49
|
+
|| ['role', 'identity', 'cid', 'socket', 'capability', 'generation']
|
|
50
|
+
.some(key => freshDescriptor[key] !== descriptor[key]))
|
|
51
|
+
throw new Error();
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new FleetError('capability_unavailable', 'selected supervisor assignment changed');
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const client = new Client({ name: 'ours-fleet-supervisor-tools', version: '1' });
|
|
58
|
+
const transport = new StdioClientTransport({
|
|
59
|
+
command: process.execPath,
|
|
60
|
+
args: [fileURLToPath(new URL('../agent-ours/bridge.js', import.meta.url))],
|
|
61
|
+
env: { ...Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== undefined)),
|
|
62
|
+
FLEET_OURS_BRIDGE_DESCRIPTOR: selected.path,
|
|
63
|
+
FLEET_OURS_BRIDGE_EXPECTED: JSON.stringify({ role: descriptor.role, identity: descriptor.identity, cid: descriptor.cid, generation: descriptor.generation,
|
|
64
|
+
transportDigest: createHash('sha256').update(JSON.stringify([descriptor.socket, descriptor.capability, descriptor.generation])).digest('hex') }) },
|
|
65
|
+
stderr: 'pipe',
|
|
66
|
+
});
|
|
67
|
+
try {
|
|
68
|
+
await client.connect(transport);
|
|
69
|
+
const verify = async () => {
|
|
70
|
+
if (!legacy) {
|
|
71
|
+
verifySelection();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// The supervisor owns the binding. The authenticated bridge and pinned local
|
|
75
|
+
// assignment are authoritative; current_identity is display text, not a protocol.
|
|
76
|
+
const fresh = legacySupervisorIdentity(role, identityName, selected.temporary, descriptor.generation);
|
|
77
|
+
if (!('proof' in identity) || fresh.proof !== identity.proof)
|
|
78
|
+
throw new FleetError('capability_unavailable', 'legacy supervisor identity changed');
|
|
79
|
+
verifySelection();
|
|
80
|
+
};
|
|
81
|
+
await verify();
|
|
82
|
+
return await work(client, { name: identity.name, cid: identity.cid, generation: identity.generation }, verify);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (error instanceof FleetError)
|
|
86
|
+
throw error;
|
|
87
|
+
// Do not leak arguments, invites, capabilities, or raw transport errors.
|
|
88
|
+
throw new FleetError('control_unavailable', 'supervisor tool request failed; its outcome may be unknown, do not retry a mutation automatically', { retryable: false });
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
await client.close().catch(() => { });
|
|
92
|
+
await transport.close().catch(() => { });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
list(role) {
|
|
96
|
+
return this.withClient(role, async (client, identity) => ({
|
|
97
|
+
agent: role, identity, ...(await client.listTools()),
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
call(role, request) {
|
|
101
|
+
if (!request || typeof request.tool !== 'string'
|
|
102
|
+
|| (request.arguments !== undefined && (!request.arguments || typeof request.arguments !== 'object' || Array.isArray(request.arguments))))
|
|
103
|
+
throw new FleetError('invalid_request', 'tool and object arguments are required');
|
|
104
|
+
return this.withClient(role, async (client, identity, verify) => {
|
|
105
|
+
const { tools } = await client.listTools();
|
|
106
|
+
if (!tools.some(tool => tool.name === request.tool))
|
|
107
|
+
throw new FleetError('forbidden', 'tool is not exposed by the fixed-identity supervisor');
|
|
108
|
+
await verify();
|
|
109
|
+
const result = await client.callTool({ name: request.tool, arguments: request.arguments ?? {} });
|
|
110
|
+
return { agent: role, identity, result };
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|