@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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { socketPath as privateSocketPath } from '../socket-path.js';
|
|
1
2
|
import { randomBytes, randomUUID } from 'node:crypto';
|
|
2
3
|
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
4
|
import { join } from 'node:path';
|
|
@@ -11,19 +12,31 @@ import { atomicPrivateWrite, binderKey } from './state.js';
|
|
|
11
12
|
import { AgentOursRuntime } from './runtime.js';
|
|
12
13
|
import { startMcpEndpoint } from './mcp-endpoint.js';
|
|
13
14
|
export const privateRuntimeRoot = () => join(stateRoot(), 'private-ours');
|
|
15
|
+
function roomSecretPath(role) {
|
|
16
|
+
const startup = role.roomMemberStartup;
|
|
17
|
+
return join(privateRuntimeRoot(), 'room-inputs', binderKey(startup.room_identity_cid, JSON.stringify([role.name, startup.invite_id])) + '.json');
|
|
18
|
+
}
|
|
19
|
+
function matchesRoomSecret(secret, role) {
|
|
20
|
+
const startup = role.roomMemberStartup;
|
|
21
|
+
return secret.room_id === startup.room_id && secret.room_identity_cid === startup.room_identity_cid
|
|
22
|
+
&& secret.invite_id === startup.invite_id && secret.identity_name === role.identity
|
|
23
|
+
&& secret.role === startup.role;
|
|
24
|
+
}
|
|
14
25
|
/** Only trusted launch orchestration may write this descriptor, never the child. */
|
|
15
26
|
export function storeRoomSecret(role) {
|
|
16
27
|
const startup = role.roomMemberStartup;
|
|
17
28
|
if (!startup?.invite)
|
|
18
29
|
return;
|
|
30
|
+
if (startup.identity_name !== role.identity)
|
|
31
|
+
throw Error('ROOM_SECRET_MISMATCH');
|
|
19
32
|
const root = join(privateRuntimeRoot(), 'room-inputs');
|
|
20
33
|
mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
21
|
-
|
|
34
|
+
// Failed attempts retain private evidence. A new invite must never overwrite
|
|
35
|
+
// or consume a previous attempt's descriptor.
|
|
36
|
+
const path = roomSecretPath(role);
|
|
22
37
|
if (existsSync(path)) {
|
|
23
38
|
const old = JSON.parse(readFileSync(path, 'utf8'));
|
|
24
|
-
if (old.
|
|
25
|
-
old.invite_id !== startup.invite_id ||
|
|
26
|
-
old.identity_name !== startup.identity_name)
|
|
39
|
+
if (!matchesRoomSecret(old, role) || old.invite !== startup.invite)
|
|
27
40
|
throw Error('ROOM_SECRET_COLLISION');
|
|
28
41
|
return;
|
|
29
42
|
}
|
|
@@ -144,7 +157,9 @@ export async function prepareManagedAgent(role, stateDir, temporary) {
|
|
|
144
157
|
let room;
|
|
145
158
|
if (role.roomMemberStartup) {
|
|
146
159
|
const startup = role.roomMemberStartup;
|
|
147
|
-
const
|
|
160
|
+
const legacyPath = join(root, 'room-inputs', binderKey(startup.room_identity_cid, role.name) + '.json');
|
|
161
|
+
const currentPath = roomSecretPath(role);
|
|
162
|
+
const path = existsSync(currentPath) ? currentPath : legacyPath;
|
|
148
163
|
const cowork = createCoworkAdapter();
|
|
149
164
|
room = {
|
|
150
165
|
id: startup.room_id,
|
|
@@ -153,9 +168,7 @@ export async function prepareManagedAgent(role, stateDir, temporary) {
|
|
|
153
168
|
action: startup.invite_id,
|
|
154
169
|
redeem: async (attached) => {
|
|
155
170
|
const secret = JSON.parse(readFileSync(path, 'utf8'));
|
|
156
|
-
if (secret
|
|
157
|
-
secret.invite_id !== startup.invite_id ||
|
|
158
|
-
secret.identity_name !== role.identity)
|
|
171
|
+
if (!matchesRoomSecret(secret, role))
|
|
159
172
|
throw Error('ROOM_SECRET_MISMATCH');
|
|
160
173
|
return attached.addContact({ invite: secret.invite });
|
|
161
174
|
},
|
|
@@ -195,7 +208,7 @@ export async function prepareManagedAgent(role, stateDir, temporary) {
|
|
|
195
208
|
await identities.add(role.identity);
|
|
196
209
|
const bridgeDir = join(stateDir, '.ours-bridge');
|
|
197
210
|
mkdirSync(bridgeDir, { recursive: true, mode: 0o700 });
|
|
198
|
-
const capability = randomBytes(32).toString('hex'), socket = join(bridgeDir, `g${generation}.sock`);
|
|
211
|
+
const capability = randomBytes(32).toString('hex'), socket = privateSocketPath(join(bridgeDir, `g${generation}.sock`));
|
|
199
212
|
const endpoint = await startMcpEndpoint({
|
|
200
213
|
socket,
|
|
201
214
|
capability,
|
|
@@ -207,7 +220,7 @@ export async function prepareManagedAgent(role, stateDir, temporary) {
|
|
|
207
220
|
});
|
|
208
221
|
partialEndpoint = endpoint;
|
|
209
222
|
const descriptor = join(bridgeDir, 'descriptor.json');
|
|
210
|
-
atomicPrivateWrite(descriptor, { socket, capability, generation });
|
|
223
|
+
atomicPrivateWrite(descriptor, { socket, capability, generation, role: role.name, identity: role.identity, cid: runtime.snapshot.cid });
|
|
211
224
|
return {
|
|
212
225
|
runtime,
|
|
213
226
|
descriptor,
|
|
@@ -255,8 +268,10 @@ export async function preparePermanentAssignment(role) {
|
|
|
255
268
|
endpoint: profile.endpoint,
|
|
256
269
|
expectedInstanceId: profile.expectedInstanceId,
|
|
257
270
|
credentialPath: profile.credentialPath,
|
|
258
|
-
sessionMode: '
|
|
259
|
-
|
|
271
|
+
sessionMode: 'external',
|
|
272
|
+
leaseToken: randomUUID(),
|
|
273
|
+
requiredCapabilities: ['external-sessions-v1', 'root-first-identities-v1'],
|
|
274
|
+
env: {},
|
|
260
275
|
});
|
|
261
276
|
try {
|
|
262
277
|
const rows = await client.listIdentities(), row = rows.find((r) => r.name === role.identity);
|
|
@@ -283,7 +298,12 @@ export async function preparePermanentAssignment(role) {
|
|
|
283
298
|
return 'verified';
|
|
284
299
|
}
|
|
285
300
|
finally {
|
|
286
|
-
|
|
301
|
+
try {
|
|
302
|
+
await client.releaseLease();
|
|
303
|
+
}
|
|
304
|
+
finally {
|
|
305
|
+
await client.close();
|
|
306
|
+
}
|
|
287
307
|
}
|
|
288
308
|
}
|
|
289
309
|
export function readRoomReadiness(roomCid, name) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type OfferedModels } from '../subscriptions/cli.js';
|
|
2
|
+
export interface CodexModelDiscoveryDeps {
|
|
3
|
+
/** The Codex executable ACP sessions will run, and its version. */
|
|
4
|
+
runtime(): Promise<{
|
|
5
|
+
executable: string;
|
|
6
|
+
version: string;
|
|
7
|
+
}>;
|
|
8
|
+
ask(executable: string, env: NodeJS.ProcessEnv): Promise<OfferedModels>;
|
|
9
|
+
now(): number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The models offered to the active Codex account by the runtime that sessions will actually use.
|
|
13
|
+
* An answer is reused only for the same executable, version, profile and sign-in.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createCodexModelDiscovery(overrides?: Partial<CodexModelDiscoveryDeps>): () => Promise<OfferedModels>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { resolveBundledAcpAgent } from '../harness/acp-agent.js';
|
|
4
|
+
import { probeCodexRuntime } from '../harness/codex-runtime.js';
|
|
5
|
+
import { codexOfferedModels, profileEnv } from '../subscriptions/cli.js';
|
|
6
|
+
import { profileHome, readSubscriptionState } from '../subscriptions/store.js';
|
|
7
|
+
const KNOWN_FOR_MS = 5 * 60_000;
|
|
8
|
+
const UNKNOWN_FOR_MS = 10_000;
|
|
9
|
+
/** When the profile's sign-in last changed; a new sign-in must not reuse another account's answer. */
|
|
10
|
+
function signInStamp(home) {
|
|
11
|
+
try {
|
|
12
|
+
return statSync(join(home, 'auth.json')).mtimeMs;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The models offered to the active Codex account by the runtime that sessions will actually use.
|
|
20
|
+
* An answer is reused only for the same executable, version, profile and sign-in.
|
|
21
|
+
*/
|
|
22
|
+
export function createCodexModelDiscovery(overrides = {}) {
|
|
23
|
+
const deps = {
|
|
24
|
+
runtime: () => probeCodexRuntime(resolveBundledAcpAgent('@agentclientprotocol/codex-acp', 'codex-acp', 'codex-acp'), process.env),
|
|
25
|
+
ask: codexOfferedModels,
|
|
26
|
+
now: Date.now,
|
|
27
|
+
...overrides,
|
|
28
|
+
};
|
|
29
|
+
let cached;
|
|
30
|
+
let pending;
|
|
31
|
+
return async () => {
|
|
32
|
+
let key, executable, env;
|
|
33
|
+
try {
|
|
34
|
+
const profileId = readSubscriptionState().providers.codex.activeProfileId;
|
|
35
|
+
const profile = profileHome('codex', profileId);
|
|
36
|
+
const runtime = await deps.runtime();
|
|
37
|
+
executable = runtime.executable;
|
|
38
|
+
env = profileEnv('CODEX_HOME', profileId, profile);
|
|
39
|
+
key = [runtime.executable, runtime.version, profileId, signInStamp(profile)].join('\n');
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
return { state: 'unknown', reason: error instanceof Error ? error.message : String(error) };
|
|
43
|
+
}
|
|
44
|
+
if (cached && cached.key === key && cached.until > deps.now())
|
|
45
|
+
return cached.value;
|
|
46
|
+
if (pending?.key === key)
|
|
47
|
+
return pending.answer;
|
|
48
|
+
const answer = deps.ask(executable, env).then(value => {
|
|
49
|
+
cached = { key, until: deps.now() + (value.state === 'known' ? KNOWN_FOR_MS : UNKNOWN_FOR_MS), value };
|
|
50
|
+
return value;
|
|
51
|
+
}).finally(() => { if (pending?.key === key)
|
|
52
|
+
pending = undefined; });
|
|
53
|
+
pending = { key, answer };
|
|
54
|
+
return answer;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ConversationEventV1 } from '../session/conversation-types.js';
|
|
2
|
+
import type { ConversationPageView } from './session-control.js';
|
|
3
|
+
/** Whether a ledger event belongs in the conversation: the live generation, or history of the resumed ACP session. */
|
|
4
|
+
export declare function conversationEventVisible(event: ConversationEventV1, sessionGeneration: string, resumedSessionId?: string): boolean;
|
|
5
|
+
export interface ConversationTailPage extends ConversationPageView {
|
|
6
|
+
/** Pass as `before` to read the next older page. */
|
|
7
|
+
olderCursor?: string;
|
|
8
|
+
hasOlder: boolean;
|
|
9
|
+
/** Latest page only: older events the view still needs, the current session's latest capabilities and still-pending permission requests. */
|
|
10
|
+
context: ConversationEventV1[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The newest `limit` visible events before `before` (or the newest overall),
|
|
14
|
+
* read backwards from the ledger so opening a long session costs one page.
|
|
15
|
+
* `nextCursor` is the newest ledger position seen, for forward `after` paging.
|
|
16
|
+
*/
|
|
17
|
+
export declare function conversationTailPage(stateDir: string, request: {
|
|
18
|
+
before?: string;
|
|
19
|
+
limit?: number;
|
|
20
|
+
}, snapshot: ConversationPageView['snapshot'], resumedSessionId?: string): Promise<ConversationTailPage>;
|
|
21
|
+
/** Read the existing ACP session ledger without opening its writer/recovery store. */
|
|
22
|
+
export declare function resumedConversationPage(stateDir: string, request: {
|
|
23
|
+
after?: string;
|
|
24
|
+
limit?: number;
|
|
25
|
+
}, live: ConversationPageView, expectedSessionId: string): Promise<ConversationPageView>;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { createReadStream } from 'node:fs';
|
|
2
|
+
import { open, readFile, readdir } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { createInterface } from 'node:readline';
|
|
5
|
+
const HISTORICAL_KINDS = ['fleet.task_created', 'prompt.admitted', 'message.chunk', 'message.replace', 'tool.upsert', 'tool.content_chunk', 'turn.completed'];
|
|
6
|
+
/** Whether a ledger event belongs in the conversation: the live generation, or history of the resumed ACP session. */
|
|
7
|
+
export function conversationEventVisible(event, sessionGeneration, resumedSessionId) {
|
|
8
|
+
return event.source !== 'agent_replay' && (event.sessionGeneration === sessionGeneration || (!!resumedSessionId && HISTORICAL_KINDS.includes(event.kind) && event.acpSessionId === resumedSessionId));
|
|
9
|
+
}
|
|
10
|
+
/** Newest-first lines of a file, read in chunks from its end; `visit` returns false to stop. */
|
|
11
|
+
async function readLinesBackward(path, visit) {
|
|
12
|
+
const file = await open(path, 'r');
|
|
13
|
+
try {
|
|
14
|
+
let position = (await file.stat()).size;
|
|
15
|
+
let carry = Buffer.alloc(0);
|
|
16
|
+
while (position > 0) {
|
|
17
|
+
const size = Math.min(256 * 1024, position);
|
|
18
|
+
position -= size;
|
|
19
|
+
const chunk = Buffer.alloc(size);
|
|
20
|
+
await file.read(chunk, 0, size, position);
|
|
21
|
+
let buffer = Buffer.concat([chunk, carry]);
|
|
22
|
+
let end = buffer.length;
|
|
23
|
+
for (let i = buffer.length - 1; i >= 0; i--) {
|
|
24
|
+
if (buffer[i] !== 10)
|
|
25
|
+
continue;
|
|
26
|
+
if (end > i + 1 && !visit(buffer.toString('utf8', i + 1, end)))
|
|
27
|
+
return false;
|
|
28
|
+
end = i;
|
|
29
|
+
}
|
|
30
|
+
carry = buffer.subarray(0, end);
|
|
31
|
+
}
|
|
32
|
+
if (carry.length && !visit(carry.toString('utf8')))
|
|
33
|
+
return false;
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
await file.close();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** The last event cursor in a segment, read from its tail; undefined when none is readable. */
|
|
41
|
+
async function lastSeq(path) {
|
|
42
|
+
let seq;
|
|
43
|
+
await readLinesBackward(path, line => { try {
|
|
44
|
+
const value = Number(JSON.parse(line).seq);
|
|
45
|
+
if (Number.isSafeInteger(value)) {
|
|
46
|
+
seq = value;
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch { } return true; });
|
|
51
|
+
return seq;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The newest `limit` visible events before `before` (or the newest overall),
|
|
55
|
+
* read backwards from the ledger so opening a long session costs one page.
|
|
56
|
+
* `nextCursor` is the newest ledger position seen, for forward `after` paging.
|
|
57
|
+
*/
|
|
58
|
+
export async function conversationTailPage(stateDir, request, snapshot, resumedSessionId) {
|
|
59
|
+
const before = request.before === undefined ? Infinity : Number(request.before);
|
|
60
|
+
if (before !== Infinity && (!Number.isSafeInteger(before) || before < 0))
|
|
61
|
+
throw new Error('before must be a ledger cursor');
|
|
62
|
+
const limit = Math.min(1000, Math.max(1, Number.isFinite(request.limit) ? Math.floor(request.limit) : 200));
|
|
63
|
+
let files = [];
|
|
64
|
+
try {
|
|
65
|
+
files = (await readdir(join(stateDir, '.conversation'))).filter(n => /^events-\d{6}\.jsonl$/.test(n)).sort().reverse();
|
|
66
|
+
}
|
|
67
|
+
catch { }
|
|
68
|
+
const events = [];
|
|
69
|
+
let newest;
|
|
70
|
+
let hasOlder = false;
|
|
71
|
+
const pending = new Set(snapshot.pendingPermissionIds);
|
|
72
|
+
let capabilities;
|
|
73
|
+
const context = [];
|
|
74
|
+
// Context only accompanies the newest page; older pages stop as soon as they are full.
|
|
75
|
+
const wantsContext = () => before === Infinity && (!capabilities || pending.size > 0);
|
|
76
|
+
const generation = `"sessionGeneration":${JSON.stringify(snapshot.sessionGeneration)}`;
|
|
77
|
+
for (const file of files) {
|
|
78
|
+
const more = await readLinesBackward(join(stateDir, '.conversation', file), line => {
|
|
79
|
+
if (hasOlder) {
|
|
80
|
+
// The page is full; only the current generation's context kinds matter. Generations are
|
|
81
|
+
// appended in order, so the first line of an older one ends the search.
|
|
82
|
+
if (!line.includes(generation))
|
|
83
|
+
return !line.includes('"sessionGeneration":') && wantsContext();
|
|
84
|
+
if (!line.includes('"capabilities.updated"') && !line.includes('"permission.requested"'))
|
|
85
|
+
return wantsContext();
|
|
86
|
+
}
|
|
87
|
+
let event;
|
|
88
|
+
try {
|
|
89
|
+
event = JSON.parse(line);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
const seq = Number(event.seq);
|
|
95
|
+
if (!Number.isSafeInteger(seq))
|
|
96
|
+
return true;
|
|
97
|
+
newest ??= seq;
|
|
98
|
+
if (seq < before && conversationEventVisible(event, snapshot.sessionGeneration, resumedSessionId)) {
|
|
99
|
+
const inPage = !hasOlder && events.length < limit;
|
|
100
|
+
if (event.sessionGeneration === snapshot.sessionGeneration) {
|
|
101
|
+
if (event.kind === 'capabilities.updated' && !capabilities) {
|
|
102
|
+
capabilities = event;
|
|
103
|
+
if (!inPage)
|
|
104
|
+
context.push(event);
|
|
105
|
+
}
|
|
106
|
+
if (event.kind === 'permission.requested' && event.permissionId && pending.delete(event.permissionId) && !inPage)
|
|
107
|
+
context.push(event);
|
|
108
|
+
}
|
|
109
|
+
if (inPage)
|
|
110
|
+
events.push(event);
|
|
111
|
+
else
|
|
112
|
+
hasOlder = true;
|
|
113
|
+
}
|
|
114
|
+
return !hasOlder || wantsContext();
|
|
115
|
+
});
|
|
116
|
+
if (!more)
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
events.reverse();
|
|
120
|
+
context.reverse();
|
|
121
|
+
return { events, snapshot, hasOlder, context, hasMore: false,
|
|
122
|
+
...(events.length ? { olderCursor: String(events[0].seq) } : {}),
|
|
123
|
+
...(before === Infinity && newest !== undefined ? { nextCursor: String(newest) } : {}) };
|
|
124
|
+
}
|
|
125
|
+
/** Read the existing ACP session ledger without opening its writer/recovery store. */
|
|
126
|
+
export async function resumedConversationPage(stateDir, request, live, expectedSessionId) {
|
|
127
|
+
let sessionId;
|
|
128
|
+
let files;
|
|
129
|
+
try {
|
|
130
|
+
sessionId = (await readFile(join(stateDir, '.acp-session-id'), 'utf8')).trim();
|
|
131
|
+
files = (await readdir(join(stateDir, '.conversation'))).filter(n => /^events-\d{6}\.jsonl$/.test(n)).sort();
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return live;
|
|
135
|
+
}
|
|
136
|
+
if (!sessionId || sessionId !== expectedSessionId || !files.length)
|
|
137
|
+
return live;
|
|
138
|
+
const after = Number(request.after ?? 0);
|
|
139
|
+
if (!Number.isSafeInteger(after) || after < 0)
|
|
140
|
+
return live;
|
|
141
|
+
const limit = Math.min(1000, Math.max(1, Number.isFinite(request.limit) ? Math.floor(request.limit) : 200));
|
|
142
|
+
const events = [];
|
|
143
|
+
let next = String(after);
|
|
144
|
+
let first;
|
|
145
|
+
let hasMore = false;
|
|
146
|
+
scan: for (const file of files) {
|
|
147
|
+
// Forward polling asks for the newest few events: skip whole segments that end at or before the cursor.
|
|
148
|
+
const end = await lastSeq(join(stateDir, '.conversation', file));
|
|
149
|
+
if (end !== undefined && end <= after)
|
|
150
|
+
continue;
|
|
151
|
+
const input = createReadStream(join(stateDir, '.conversation', file), { encoding: 'utf8' });
|
|
152
|
+
const lines = createInterface({ input, crlfDelay: Infinity });
|
|
153
|
+
try {
|
|
154
|
+
for await (const line of lines) {
|
|
155
|
+
let event;
|
|
156
|
+
try {
|
|
157
|
+
event = JSON.parse(line);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const cursor = Number(event.seq);
|
|
163
|
+
if (!Number.isSafeInteger(cursor) || cursor <= after)
|
|
164
|
+
continue;
|
|
165
|
+
const historical = ['fleet.task_created', 'prompt.admitted', 'message.chunk', 'message.replace', 'tool.upsert', 'tool.content_chunk', 'turn.completed'].includes(event.kind);
|
|
166
|
+
const visible = event.source !== 'agent_replay' && (event.sessionGeneration === live.snapshot.sessionGeneration || (historical && event.acpSessionId === sessionId));
|
|
167
|
+
if (!visible) {
|
|
168
|
+
next = String(cursor);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (events.length === limit) {
|
|
172
|
+
hasMore = true;
|
|
173
|
+
break scan;
|
|
174
|
+
}
|
|
175
|
+
first ??= String(cursor);
|
|
176
|
+
events.push(event);
|
|
177
|
+
next = String(cursor);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
lines.close();
|
|
182
|
+
input.destroy();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// If a fresh/resumed session changed during the read, use the live page only.
|
|
186
|
+
if ((await readFile(join(stateDir, '.acp-session-id'), 'utf8')).trim() !== sessionId)
|
|
187
|
+
return live;
|
|
188
|
+
return { ...live, events, firstAvailableCursor: first, nextCursor: next, hasMore };
|
|
189
|
+
}
|
|
@@ -24,7 +24,7 @@ export declare class FleetQueryService {
|
|
|
24
24
|
private readonly options;
|
|
25
25
|
private readonly control;
|
|
26
26
|
constructor(options: FleetQueryOptions);
|
|
27
|
-
list(): Promise<Array<{
|
|
27
|
+
list(includeTemporary?: boolean): Promise<Array<{
|
|
28
28
|
role: RoleRecord;
|
|
29
29
|
status: RoleStatus;
|
|
30
30
|
capabilities: RoleCapabilities;
|
|
@@ -75,9 +75,9 @@ export class FleetQueryService {
|
|
|
75
75
|
this.options = options;
|
|
76
76
|
this.control = options.control ?? controlRequest;
|
|
77
77
|
}
|
|
78
|
-
async list() {
|
|
78
|
+
async list(includeTemporary = false) {
|
|
79
79
|
const roles = (await this.options.repository.list())
|
|
80
|
-
.filter(role => role.configured && role.lifetime === 'permanent');
|
|
80
|
+
.filter(role => (role.configured && role.lifetime === 'permanent') || (includeTemporary && role.lifetime === 'temporary'));
|
|
81
81
|
return Promise.all(roles.map(async (role) => {
|
|
82
82
|
const status = await this.status(role);
|
|
83
83
|
return { role, status, capabilities: roleCapabilities(role, status, this.options.capabilityContext) };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Read-only compatibility proof for supervisors predating descriptor identity metadata. */
|
|
2
|
+
export declare function legacySupervisorIdentity(role: string, name: string, temporary: boolean, generation: number): {
|
|
3
|
+
name: string;
|
|
4
|
+
cid: string;
|
|
5
|
+
generation: number;
|
|
6
|
+
proof: string;
|
|
7
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { stateRoot } from '../paths.js';
|
|
5
|
+
import { binderKey } from '../agent-ours/state.js';
|
|
6
|
+
import { FleetError } from './errors.js';
|
|
7
|
+
/** Read-only compatibility proof for supervisors predating descriptor identity metadata. */
|
|
8
|
+
export function legacySupervisorIdentity(role, name, temporary, generation) {
|
|
9
|
+
const root = join(stateRoot(), 'private-ours');
|
|
10
|
+
try {
|
|
11
|
+
const matches = [];
|
|
12
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
13
|
+
if (!entry.isDirectory() || !/^[a-f0-9]{64}$/.test(entry.name))
|
|
14
|
+
continue;
|
|
15
|
+
const dir = join(root, entry.name);
|
|
16
|
+
const instance = JSON.parse(readFileSync(join(dir, 'instance.json'), 'utf8'));
|
|
17
|
+
if (instance.role !== role)
|
|
18
|
+
continue;
|
|
19
|
+
const state = JSON.parse(readFileSync(join(dir, 'state.json'), 'utf8'));
|
|
20
|
+
const pin = JSON.parse(readFileSync(join(dir, 'identity-pin.json'), 'utf8'));
|
|
21
|
+
if (!Number.isSafeInteger(generation) || generation < 1
|
|
22
|
+
|| typeof state.action !== 'string' || !state.action
|
|
23
|
+
|| instance.temporary !== temporary || typeof instance.instance !== 'string' || !instance.instance
|
|
24
|
+
|| state.version !== 1 || state.instance !== instance.instance
|
|
25
|
+
|| state.name !== name || state.lifetime !== (temporary ? 'temporary' : 'permanent')
|
|
26
|
+
|| state.generation !== generation || !['READY', 'SERVING'].includes(state.phase)
|
|
27
|
+
|| typeof state.daemon !== 'string' || !state.daemon || binderKey(state.daemon, name) !== entry.name
|
|
28
|
+
|| !/^[a-f0-9]{64}$/i.test(state.cid ?? '')
|
|
29
|
+
|| pin.daemon !== state.daemon || pin.name !== name || pin.cid !== state.cid)
|
|
30
|
+
throw new Error('mismatched legacy ownership proof');
|
|
31
|
+
matches.push({ name, cid: state.cid, generation, proof: createHash('sha256').update(JSON.stringify([
|
|
32
|
+
instance.instance, state.action, state.daemon, name, state.cid, state.lifetime, generation,
|
|
33
|
+
])).digest('hex') });
|
|
34
|
+
}
|
|
35
|
+
if (matches.length !== 1)
|
|
36
|
+
throw new Error('missing or ambiguous legacy ownership proof');
|
|
37
|
+
return matches[0];
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// The private directory also contains ownership material: never echo parse errors.
|
|
41
|
+
throw new FleetError('capability_unavailable', 'legacy supervisor ownership proof is missing, ambiguous, or mismatched');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -14,7 +14,7 @@ export interface HarnessModelCatalog {
|
|
|
14
14
|
export declare function codexModelCatalog(path?: string): HarnessModelCatalog;
|
|
15
15
|
/**
|
|
16
16
|
* Claude Code 2.1 has no typed model-list endpoint. These exact IDs are the
|
|
17
|
-
* versioned adapter contract
|
|
17
|
+
* versioned adapter contract verified with CLI 2.1.281, not aliases and
|
|
18
18
|
* not a claim about account entitlement (which Claude validates at launch).
|
|
19
19
|
*/
|
|
20
20
|
export declare function claudeModelCatalog(): HarnessModelCatalog;
|
|
@@ -41,17 +41,20 @@ export function codexModelCatalog(path = join(homedir(), '.codex', 'models_cache
|
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
43
|
* Claude Code 2.1 has no typed model-list endpoint. These exact IDs are the
|
|
44
|
-
* versioned adapter contract
|
|
44
|
+
* versioned adapter contract verified with CLI 2.1.281, not aliases and
|
|
45
45
|
* not a claim about account entitlement (which Claude validates at launch).
|
|
46
46
|
*/
|
|
47
47
|
export function claudeModelCatalog() {
|
|
48
48
|
const reasoningEfforts = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
49
49
|
return {
|
|
50
50
|
models: [
|
|
51
|
+
{ id: 'claude-fable-5-1', label: 'Claude Fable 5.1', reasoningEfforts, source: 'claude-adapter-2.1' },
|
|
52
|
+
{ id: 'claude-opus-5-5', label: 'Claude Opus 5.5', reasoningEfforts, source: 'claude-adapter-2.1' },
|
|
53
|
+
{ id: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5', reasoningEfforts: [], source: 'claude-adapter-2.1' },
|
|
51
54
|
{ id: 'claude-fable-5', label: 'Claude Fable 5', reasoningEfforts, source: 'claude-adapter-2.1' },
|
|
52
55
|
{ id: 'claude-opus-5', label: 'Claude Opus 5', reasoningEfforts, source: 'claude-adapter-2.1' },
|
|
53
56
|
{ id: 'claude-sonnet-5', label: 'Claude Sonnet 5', reasoningEfforts, source: 'claude-adapter-2.1' },
|
|
54
57
|
],
|
|
55
|
-
warnings: ['Claude Code exposes no typed model-list endpoint; choices
|
|
58
|
+
warnings: ['Claude Code exposes no typed model-list endpoint; choices require a current Claude runtime (verified with 2.1.281); older bundled SDKs need CLAUDE_CODE_EXECUTABLE. Entitlement is validated at launch.'],
|
|
56
59
|
};
|
|
57
60
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const PROVENANCE_KINDS: {
|
|
2
|
+
readonly role: "roles";
|
|
3
|
+
readonly brain: "brains";
|
|
4
|
+
readonly template: "agent_templates";
|
|
5
|
+
readonly layout: "room_layouts";
|
|
6
|
+
};
|
|
7
|
+
export type ProvenanceKind = keyof typeof PROVENANCE_KINDS;
|
|
8
|
+
export type ProvenanceStatus = 'predefined' | 'changed' | 'custom';
|
|
9
|
+
export interface ProvenanceEntry {
|
|
10
|
+
status: ProvenanceStatus;
|
|
11
|
+
preset?: string;
|
|
12
|
+
}
|
|
13
|
+
export type Provenance = Record<ProvenanceKind, Record<string, ProvenanceEntry>>;
|
|
14
|
+
export declare class PresetProvenance {
|
|
15
|
+
private configPath?;
|
|
16
|
+
private presetRoot;
|
|
17
|
+
constructor(configPath?: string | undefined, presetRoot?: string);
|
|
18
|
+
read(): Provenance;
|
|
19
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** Where each configured definition came from, compared with the packaged presets.
|
|
2
|
+
* A definition is `predefined` when it has the same value as the shipped preset of
|
|
3
|
+
* the same name, `changed` when a preset of that name exists but the value differs,
|
|
4
|
+
* and `custom` otherwise. Values are compared after parsing, so the formatting the
|
|
5
|
+
* configuration editor writes does not count as a change. Preset text is returned
|
|
6
|
+
* so users can see exactly what ships; it contains no host data.
|
|
7
|
+
*/
|
|
8
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
9
|
+
import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
|
|
10
|
+
import { basename, extname, join } from 'node:path';
|
|
11
|
+
import { parse } from 'yaml';
|
|
12
|
+
import { defaultConfigPath } from '../paths.js';
|
|
13
|
+
import { splitRootFor } from '../config.js';
|
|
14
|
+
import { packagedPresetRoot } from '../preset-bootstrap.js';
|
|
15
|
+
export const PROVENANCE_KINDS = { role: 'roles', brain: 'brains', template: 'agent_templates', layout: 'room_layouts' };
|
|
16
|
+
const MAX_BYTES = 256 * 1024;
|
|
17
|
+
/** Regular, bounded YAML files of one directory, by definition name. Anything else is skipped. */
|
|
18
|
+
function yamlFiles(dir) {
|
|
19
|
+
const files = new Map();
|
|
20
|
+
if (!existsSync(dir) || !lstatSync(dir).isDirectory())
|
|
21
|
+
return files;
|
|
22
|
+
for (const file of readdirSync(dir).sort()) {
|
|
23
|
+
if (!/\.ya?ml$/.test(file))
|
|
24
|
+
continue;
|
|
25
|
+
const path = join(dir, file), stat = lstatSync(path);
|
|
26
|
+
if (!stat.isFile() || stat.size > MAX_BYTES)
|
|
27
|
+
continue;
|
|
28
|
+
files.set(basename(file, extname(file)), readFileSync(path, 'utf8'));
|
|
29
|
+
}
|
|
30
|
+
return files;
|
|
31
|
+
}
|
|
32
|
+
const parsed = (text) => { try {
|
|
33
|
+
return parse(text);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return Symbol('unparseable');
|
|
37
|
+
} };
|
|
38
|
+
export class PresetProvenance {
|
|
39
|
+
configPath;
|
|
40
|
+
presetRoot;
|
|
41
|
+
constructor(configPath, presetRoot = join(packagedPresetRoot(), 'fleet')) {
|
|
42
|
+
this.configPath = configPath;
|
|
43
|
+
this.presetRoot = presetRoot;
|
|
44
|
+
}
|
|
45
|
+
read() {
|
|
46
|
+
const split = splitRootFor(this.configPath ?? defaultConfigPath());
|
|
47
|
+
const result = {};
|
|
48
|
+
for (const [kind, dir] of Object.entries(PROVENANCE_KINDS)) {
|
|
49
|
+
const presets = yamlFiles(join(this.presetRoot, dir)), installed = yamlFiles(join(split, dir));
|
|
50
|
+
result[kind] = Object.fromEntries([...installed].map(([name, text]) => {
|
|
51
|
+
const preset = presets.get(name);
|
|
52
|
+
if (preset === undefined)
|
|
53
|
+
return [name, { status: 'custom' }];
|
|
54
|
+
return [name, { status: isDeepStrictEqual(parsed(text), parsed(preset)) ? 'predefined' : 'changed', preset }];
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -138,7 +138,7 @@ export class RoleCreationService {
|
|
|
138
138
|
reasoningEffort: resolved.effort,
|
|
139
139
|
cwd, lifetime: request.lifetime,
|
|
140
140
|
permissions: resolvePermissions(defaults.permissions, request.permissions),
|
|
141
|
-
monitor: resolveMonitorConfig(defaults.monitor, request.monitor),
|
|
141
|
+
monitor: resolveMonitorConfig(defaults.monitor, request.monitor, { harness: resolved.harness }),
|
|
142
142
|
};
|
|
143
143
|
const warnings = [];
|
|
144
144
|
if (request.permissions.approval === 'allow')
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { retireNotificationTarget } from '../notifications/target-cleanup.js';
|
|
1
2
|
import { cpSync, existsSync, mkdirSync, readdirSync, renameSync } from 'node:fs';
|
|
2
3
|
import { basename, dirname, join } from 'node:path';
|
|
3
4
|
import { loadConfig, ROLE_NAME_RE } from '../config.js';
|
|
@@ -87,6 +88,7 @@ export class RoleRemovalService {
|
|
|
87
88
|
await this.options.ops.backend.uninstall(preview.role);
|
|
88
89
|
if (existsSync(sourceState))
|
|
89
90
|
renameSync(sourceState, join(recoveryPath, 'orphan-state'));
|
|
91
|
+
await retireNotificationTarget(`/fleet/chats?chat=${encodeURIComponent(preview.role)}`).catch(() => { });
|
|
90
92
|
this.options.ops.log(`removed orphaned backend/state '${preview.role}'`);
|
|
91
93
|
}
|
|
92
94
|
return { ...preview, removed: true, recoveryPath };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
|
|
2
2
|
import { join, relative } from 'node:path';
|
|
3
3
|
import { parse } from 'yaml';
|
|
4
|
-
import { loadConfig,
|
|
4
|
+
import { loadConfig, resolveSnapshotMonitor, resolvePermissions, ROLE_NAME_RE, } from '../config.js';
|
|
5
5
|
import { agentsRoot, tmpRoot } from '../paths.js';
|
|
6
6
|
import { readSpawnLineage } from '../web/topology.js';
|
|
7
7
|
const MAX_SNAPSHOT_BYTES = 256 * 1024;
|
|
@@ -54,7 +54,7 @@ function snapshot(path, name) {
|
|
|
54
54
|
session, identity: typeof raw.identity === 'string' ? raw.identity : name,
|
|
55
55
|
permissions: resolvePermissions(undefined, raw.permissions),
|
|
56
56
|
permissionsDeclared: raw.permissions !== undefined,
|
|
57
|
-
monitor:
|
|
57
|
+
monitor: resolveSnapshotMonitor(raw.monitor),
|
|
58
58
|
sourceFile: '(temp)',
|
|
59
59
|
},
|
|
60
60
|
};
|