@ours.network/fleet 1.2.0-nightly.5 → 1.2.0-nightly.51
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 +592 -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
package/dist/client-profile.js
CHANGED
|
@@ -1,130 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const PROFILE_FIELDS = ['endpoint', 'expectedInstanceId', 'credentialPath'];
|
|
6
|
-
const LOWERCASE_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
7
|
-
const LEGACY_CONFIG_FIELDS = ['apiToken', 'stateDir', 'port'];
|
|
8
|
-
const LEGACY_ENV_FIELDS = ['OURS_API_TOKEN', 'OURS_STATE_DIR', 'OURS_PORT', 'OURS_DAEMON_ID'];
|
|
9
|
-
export class ClientProfileError extends Error {
|
|
10
|
-
constructor(message) {
|
|
11
|
-
super(message);
|
|
12
|
-
this.name = 'ClientProfileError';
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
export function clientConfigPath(env) {
|
|
16
|
-
if (env.OURS_CONFIG !== undefined)
|
|
17
|
-
return env.OURS_CONFIG;
|
|
18
|
-
const managedPath = join(env.HOME || homedir(), '.ours-client', 'profile.json');
|
|
19
|
-
try {
|
|
20
|
-
lstatSync(managedPath);
|
|
21
|
-
return managedPath;
|
|
22
|
-
}
|
|
23
|
-
catch (error) {
|
|
24
|
-
if (error.code !== 'ENOENT')
|
|
25
|
-
throw invalid(managedPath, 'could not be read');
|
|
26
|
-
return join(homedir(), '.ours', 'config.json');
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
function invalid(path, detail) {
|
|
30
|
-
return new ClientProfileError(`ours client profile ${JSON.stringify(path)} ${detail}`);
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Read Fleet's selected host-client profile without opening its credential.
|
|
34
|
-
* A legacy daemon config remains legacy only when none of the profile fields is
|
|
35
|
-
* present. Explicit and managed selections fail closed on invalid input.
|
|
36
|
-
*/
|
|
37
|
-
export function readClientProfile(env) {
|
|
38
|
-
const configPath = clientConfigPath(env);
|
|
39
|
-
const managed = configPath === join(env.HOME || homedir(), '.ours-client', 'profile.json');
|
|
40
|
-
const named = env.OURS_CONFIG !== undefined || managed;
|
|
41
|
-
let text;
|
|
42
|
-
try {
|
|
43
|
-
text = readFileSync(configPath, 'utf8');
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
if (named)
|
|
47
|
-
throw invalid(configPath, 'could not be read');
|
|
48
|
-
return undefined;
|
|
49
|
-
}
|
|
50
|
-
let parsed;
|
|
51
|
-
try {
|
|
52
|
-
parsed = JSON.parse(text);
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
if (named)
|
|
56
|
-
throw invalid(configPath, 'is not valid JSON');
|
|
57
|
-
return undefined;
|
|
58
|
-
}
|
|
59
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
60
|
-
if (named)
|
|
61
|
-
throw invalid(configPath, 'must contain a JSON object');
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
64
|
-
const row = parsed;
|
|
65
|
-
if (!PROFILE_FIELDS.some(field => Object.prototype.hasOwnProperty.call(row, field))) {
|
|
66
|
-
if (managed)
|
|
67
|
-
throw invalid(configPath, 'must contain a complete client profile');
|
|
68
|
-
return undefined;
|
|
69
|
-
}
|
|
70
|
-
let metadata;
|
|
71
|
-
try {
|
|
72
|
-
metadata = statSync(configPath);
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
throw invalid(configPath, 'could not be inspected');
|
|
76
|
-
}
|
|
77
|
-
if (!metadata.isFile())
|
|
78
|
-
throw invalid(configPath, 'must be a regular file');
|
|
79
|
-
const getuid = process.getuid;
|
|
80
|
-
if (getuid && metadata.uid !== getuid())
|
|
81
|
-
throw invalid(configPath, 'must be owned by this user');
|
|
82
|
-
if ((metadata.mode & 0o077) !== 0)
|
|
83
|
-
throw invalid(configPath, 'must not be accessible by group or other users');
|
|
84
|
-
for (const field of PROFILE_FIELDS)
|
|
85
|
-
if (typeof row[field] !== 'string' || !row[field].trim())
|
|
86
|
-
throw invalid(configPath, `has an invalid or missing ${field}`);
|
|
87
|
-
for (const field of LEGACY_CONFIG_FIELDS)
|
|
88
|
-
if (Object.prototype.hasOwnProperty.call(row, field))
|
|
89
|
-
throw invalid(configPath, `cannot combine ${field} with explicit profile selection`);
|
|
90
|
-
for (const field of LEGACY_ENV_FIELDS)
|
|
91
|
-
if (env[field]?.trim())
|
|
92
|
-
throw invalid(configPath, `cannot combine ${field} with explicit profile selection`);
|
|
93
|
-
const endpoint = row.endpoint.trim();
|
|
94
|
-
let url;
|
|
95
|
-
try {
|
|
96
|
-
url = new URL(endpoint);
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
throw invalid(configPath, 'has an invalid endpoint');
|
|
100
|
-
}
|
|
101
|
-
if ((url.protocol !== 'http:' && url.protocol !== 'https:') || url.username || url.password
|
|
102
|
-
|| url.search || url.hash)
|
|
103
|
-
throw invalid(configPath, 'endpoint must be an HTTP or HTTPS base URL');
|
|
104
|
-
const expectedInstanceId = row.expectedInstanceId.trim();
|
|
105
|
-
if (!LOWERCASE_UUID.test(expectedInstanceId))
|
|
106
|
-
throw invalid(configPath, 'expectedInstanceId must be a lowercase UUID');
|
|
107
|
-
const credentialPath = row.credentialPath.trim();
|
|
108
|
-
if (!isAbsolute(credentialPath))
|
|
109
|
-
throw invalid(configPath, 'credentialPath must be absolute');
|
|
110
|
-
let serverUrl;
|
|
111
|
-
if (Object.hasOwn(row, 'serverUrl')) {
|
|
112
|
-
if (typeof row.serverUrl !== 'string' || /[\s\\?#]/.test(row.serverUrl))
|
|
113
|
-
throw invalid(configPath, 'serverUrl must be a safe HTTP or HTTPS base URL');
|
|
114
|
-
try {
|
|
115
|
-
serverUrl = normalizeDaemonEndpoint(row.serverUrl);
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
throw invalid(configPath, 'serverUrl must be a safe HTTP or HTTPS base URL');
|
|
119
|
-
}
|
|
120
|
-
if (normalizeDaemonEndpoint(endpoint) !== serverUrl + '/daemon')
|
|
121
|
-
throw invalid(configPath, 'endpoint must select serverUrl/daemon');
|
|
122
|
-
}
|
|
123
|
-
return Object.freeze({
|
|
124
|
-
...(serverUrl === undefined ? {} : { serverUrl }),
|
|
125
|
-
endpoint: normalizeDaemonEndpoint(endpoint), expectedInstanceId, credentialPath, configPath,
|
|
126
|
-
});
|
|
127
|
-
}
|
|
1
|
+
import { clientProfilePath, readGatewayClientProfile } from '@ours.network/sdk/client';
|
|
2
|
+
export { ClientProfileError } from '@ours.network/sdk/client';
|
|
3
|
+
export const clientConfigPath = clientProfilePath;
|
|
4
|
+
export const readClientProfile = readGatewayClientProfile;
|
|
128
5
|
export function clientProfileKey(profile) {
|
|
129
6
|
return `${profile.endpoint}#${profile.expectedInstanceId}`;
|
|
130
7
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type ConfigDiagnostic, type YamlMode } from './config-yaml.js';
|
|
|
2
2
|
import type { IsolationConfig, WrapContext } from './isolation/types.js';
|
|
3
3
|
import type { ResolvedWatchdog } from './watchdog/config.js';
|
|
4
4
|
import type { AgentLoopsConfig, ResolvedLoop, ResolvedRoleLoop } from './loops/config.js';
|
|
5
|
+
import { validateRoomsConfig } from './rooms-tasks/config.js';
|
|
5
6
|
export interface OverseeEntry {
|
|
6
7
|
agent: string;
|
|
7
8
|
interval: string;
|
|
@@ -35,6 +36,12 @@ export type FilesystemMode = 'read-only' | 'workspace' | 'unrestricted';
|
|
|
35
36
|
export type UnattendedMode = 'deny' | 'wait';
|
|
36
37
|
/** Monitor wake policy: preserve legacy booleans and add one explicit safe boundary. */
|
|
37
38
|
export type MonitorInterrupt = boolean | 'after_tool';
|
|
39
|
+
/**
|
|
40
|
+
* Workflows Fleet prepares native harness execution policy for. Declared per
|
|
41
|
+
* Agent; never inferred from a role's name or persona, and never inherited.
|
|
42
|
+
*/
|
|
43
|
+
export declare const MANAGED_CLI_WORKFLOW_IDS: readonly ["task-workflow"];
|
|
44
|
+
export type ManagedCliWorkflowId = (typeof MANAGED_CLI_WORKFLOW_IDS)[number];
|
|
38
45
|
export interface CommonPermissions {
|
|
39
46
|
approval: ApprovalMode;
|
|
40
47
|
filesystem: FilesystemMode;
|
|
@@ -145,6 +152,8 @@ export interface RoleConfig {
|
|
|
145
152
|
owner_channel?: OwnerChannelConfigInput;
|
|
146
153
|
worklog?: WorklogPolicyInput;
|
|
147
154
|
auth_proxy?: Partial<AuthProxyConfig>;
|
|
155
|
+
/** Fleet-prepared CLI workflows this role may run outside its command sandbox. */
|
|
156
|
+
managed_cli?: ManagedCliWorkflowId[];
|
|
148
157
|
}
|
|
149
158
|
export type AgentSelection<T extends Record<string, unknown> = Record<string, unknown>> = {
|
|
150
159
|
ref: string;
|
|
@@ -166,6 +175,8 @@ export interface AgentDefinition {
|
|
|
166
175
|
owner_channel?: OwnerChannelConfigInput;
|
|
167
176
|
worklog?: WorklogPolicyInput;
|
|
168
177
|
auth_proxy?: Partial<AuthProxyConfig>;
|
|
178
|
+
/** Fleet-prepared CLI workflows this Agent may run outside its command sandbox. */
|
|
179
|
+
managed_cli?: ManagedCliWorkflowId[];
|
|
169
180
|
/** Scheduled turns scoped only to temporary launches; persistent Agents reject this field. */
|
|
170
181
|
loops?: AgentLoopsConfig;
|
|
171
182
|
}
|
|
@@ -178,6 +189,7 @@ export interface AgentTemplateInstance {
|
|
|
178
189
|
}
|
|
179
190
|
/** Internal first-boot payload for a Fleet-provisioned Cowork room member. */
|
|
180
191
|
export interface RoomMemberStartup {
|
|
192
|
+
workspace?: import('./rooms-tasks/workspace.js').OwnedWorkspace;
|
|
181
193
|
room_id: string;
|
|
182
194
|
room_identity_cid: string;
|
|
183
195
|
identity_name: string;
|
|
@@ -298,6 +310,8 @@ export declare function validateBrainValue(value: BarePreset, file: string, poin
|
|
|
298
310
|
export declare function validateEffectiveAgentTemplate(definition: AgentTemplateDefinition, id?: string): AgentTemplateDefinition;
|
|
299
311
|
export declare function assertBareKeys(value: unknown, allowed: string[], label: string): BarePreset;
|
|
300
312
|
export declare function splitRootFor(base: string): string;
|
|
313
|
+
/** Read only room connection/owner settings for an existing layout. */
|
|
314
|
+
export declare function loadRoomsConfig(configPath?: string): ReturnType<typeof validateRoomsConfig>;
|
|
301
315
|
/** Load a v2 manifest and bare Agent/Role/Brain documents from its stem directory. */
|
|
302
316
|
export declare function loadConfig(configPath?: string, options?: {
|
|
303
317
|
yamlMode?: YamlMode;
|
|
@@ -307,6 +321,8 @@ export declare function loadConfig(configPath?: string, options?: {
|
|
|
307
321
|
temporary?: boolean;
|
|
308
322
|
};
|
|
309
323
|
skipWatchdogs?: boolean;
|
|
324
|
+
/** The one Owner invitation file that tunnel setup writes itself and that may therefore still be absent. */
|
|
325
|
+
deferredOwnerInviteFile?: string;
|
|
310
326
|
}): FleetConfig;
|
|
311
327
|
/** Full read-only Agent resolution used by launch dry-runs and room plan validation. */
|
|
312
328
|
export declare function resolveAgentDefinitionDryRun(configPath: string, id: string, definition: AgentDefinition): ResolvedRole;
|
|
@@ -331,10 +347,22 @@ export declare function resolvePermissions(defaults: unknown, role: Partial<Comm
|
|
|
331
347
|
* malformed block so a typo fails loudly rather than silently disarming a monitor.
|
|
332
348
|
* Exported so temp-spawn (which builds a ResolvedRole by hand) resolves identically.
|
|
333
349
|
*/
|
|
350
|
+
/**
|
|
351
|
+
* A monitor without an explicit `interrupt` steers wakes at the next tool
|
|
352
|
+
* boundary. Hermes cannot, so its default stays non-interrupting; sessions
|
|
353
|
+
* without tool-boundary steering fall back to non-cancelling delivery.
|
|
354
|
+
*/
|
|
355
|
+
export declare function defaultMonitorInterrupt(harness: string | undefined): MonitorInterrupt;
|
|
356
|
+
/**
|
|
357
|
+
* Resolve a persisted temporary-role monitor. A snapshot written before
|
|
358
|
+
* monitor.interrupt existed keeps the non-interrupting delivery it ran with.
|
|
359
|
+
*/
|
|
360
|
+
export declare function resolveSnapshotMonitor(monitor: unknown): MonitorConfig;
|
|
334
361
|
export declare function resolveMonitorConfig(defMonitor: unknown, roleMonitor?: Partial<MonitorConfig>, labels?: {
|
|
335
362
|
base?: string;
|
|
336
363
|
file?: string;
|
|
337
364
|
name?: string;
|
|
365
|
+
harness?: string;
|
|
338
366
|
}): MonitorConfig;
|
|
339
367
|
export declare function findRole(cfg: FleetConfig, name: string): ResolvedRole;
|
|
340
368
|
export {};
|
package/dist/config.js
CHANGED
|
@@ -24,6 +24,11 @@ export const NOTIFY_EVENT_TYPES = [
|
|
|
24
24
|
'message_received', 'file_received', 'sibling_contact_added', 'local_contact_request',
|
|
25
25
|
'pending_message', 'contact_restored', 'inbound_error', 'state_import_failed',
|
|
26
26
|
];
|
|
27
|
+
/**
|
|
28
|
+
* Workflows Fleet prepares native harness execution policy for. Declared per
|
|
29
|
+
* Agent; never inferred from a role's name or persona, and never inherited.
|
|
30
|
+
*/
|
|
31
|
+
export const MANAGED_CLI_WORKFLOW_IDS = ['task-workflow'];
|
|
27
32
|
/** Default wake sources when a role does not list its own. */
|
|
28
33
|
export const DEFAULT_WAKE_SOURCES = ['message_received', 'file_received', 'local_contact_request', 'pending_message'];
|
|
29
34
|
const MONITOR_KEYS = [
|
|
@@ -139,7 +144,7 @@ export const ROLE_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
|
139
144
|
const ROLE_KEYS = [
|
|
140
145
|
'harness', 'session', 'session_options', 'permissions', 'identity', 'cwd', 'coordinator', 'mission', 'persona', 'bio',
|
|
141
146
|
'briefing_file', 'model', 'effort', 'model_chain', 'max_tokens', 'autocompact_pct', 'env', 'oversee', 'harness_options',
|
|
142
|
-
'isolation', 'monitor', 'owner_channel', 'worklog', 'auth_proxy',
|
|
147
|
+
'isolation', 'monitor', 'owner_channel', 'worklog', 'auth_proxy', 'managed_cli',
|
|
143
148
|
];
|
|
144
149
|
export const ROLE_PRESET_KEYS = ['mission', 'persona', 'bio', 'briefing_file'];
|
|
145
150
|
export const BRAIN_PRESET_KEYS = [
|
|
@@ -148,7 +153,7 @@ export const BRAIN_PRESET_KEYS = [
|
|
|
148
153
|
];
|
|
149
154
|
export const AGENT_KEYS = [
|
|
150
155
|
'role', 'brain', 'permissions', 'identity', 'cwd', 'coordinator', 'env', 'oversee',
|
|
151
|
-
'isolation', 'monitor', 'owner_channel', 'worklog', 'auth_proxy', 'loops',
|
|
156
|
+
'isolation', 'monitor', 'owner_channel', 'worklog', 'auth_proxy', 'loops', 'managed_cli',
|
|
152
157
|
];
|
|
153
158
|
const AGENT_INSTANCE_KEYS = ['template', 'overrides'];
|
|
154
159
|
const TEMPLATE_FORBIDDEN_KEYS = ['identity'];
|
|
@@ -236,6 +241,12 @@ function validateAgentScalars(value, file, id) {
|
|
|
236
241
|
if (current !== undefined && (typeof current !== 'string' || !current.trim()))
|
|
237
242
|
schemaError(file, `/agents/${id}/${key}`, 'must be a non-blank string');
|
|
238
243
|
}
|
|
244
|
+
if (value.managed_cli !== undefined) {
|
|
245
|
+
const ids = value.managed_cli;
|
|
246
|
+
if (!Array.isArray(ids) || ids.some(entry => !MANAGED_CLI_WORKFLOW_IDS.includes(entry))
|
|
247
|
+
|| new Set(ids).size !== ids.length)
|
|
248
|
+
schemaError(file, `/agents/${id}/managed_cli`, `must be a list of distinct workflow IDs; supported: ${MANAGED_CLI_WORKFLOW_IDS.join(', ')}`);
|
|
249
|
+
}
|
|
239
250
|
if (value.coordinator !== undefined
|
|
240
251
|
&& (typeof value.coordinator !== 'string' || !ROLE_NAME_RE.test(value.coordinator)))
|
|
241
252
|
schemaError(file, `/agents/${id}/coordinator`, 'must be a valid Agent ID');
|
|
@@ -575,6 +586,13 @@ function deepSub(v, vars) {
|
|
|
575
586
|
return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, deepSub(x, vars)]));
|
|
576
587
|
return v;
|
|
577
588
|
}
|
|
589
|
+
/** Read only room connection/owner settings for an existing layout. */
|
|
590
|
+
export function loadRoomsConfig(configPath = defaultConfigPath()) {
|
|
591
|
+
assertTrustedPath(configPath, 'file');
|
|
592
|
+
const doc = parseFleetDocument(configPath, readFileSync(configPath, 'utf8')).value;
|
|
593
|
+
const vars = (doc.vars ?? {});
|
|
594
|
+
return validateRoomsConfig(deepSub(doc.rooms, vars), vars, configPath);
|
|
595
|
+
}
|
|
578
596
|
/** Load a v2 manifest and bare Agent/Role/Brain documents from its stem directory. */
|
|
579
597
|
export function loadConfig(configPath, options = {}) {
|
|
580
598
|
const base = configPath ?? defaultConfigPath();
|
|
@@ -649,11 +667,11 @@ export function loadConfig(configPath, options = {}) {
|
|
|
649
667
|
if (problems.length)
|
|
650
668
|
throw new ConfigError(`${file}: role '${name}' ${problems.join('; ')}`);
|
|
651
669
|
}
|
|
652
|
-
const
|
|
670
|
+
const harness = r.harness ?? defaults.harness ?? 'claude-code';
|
|
671
|
+
const monitor = resolveMonitorConfig(defaults.monitor, r.monitor, { base, file, name, harness });
|
|
653
672
|
const ownerChannel = resolveOwnerChannelConfig(defaults.owner_channel, r.owner_channel, session, file, name);
|
|
654
673
|
const worklog = resolveWorklogPolicy(defaults.worklog, r.worklog, file, name);
|
|
655
674
|
const authProxy = resolveAuthProxy(defaults.auth_proxy, r.auth_proxy, file, name);
|
|
656
|
-
const harness = r.harness ?? defaults.harness ?? 'claude-code';
|
|
657
675
|
if (session === 'codex-app-server' && harness !== 'codex')
|
|
658
676
|
throw new ConfigError(`${file}: role '${name}' session: codex-app-server requires harness: codex`);
|
|
659
677
|
const defaultHarness = defaults.harness ?? 'claude-code';
|
|
@@ -757,7 +775,7 @@ export function loadConfig(configPath, options = {}) {
|
|
|
757
775
|
if (doc.rooms !== undefined) {
|
|
758
776
|
if (rooms)
|
|
759
777
|
throw new ConfigError(`rooms: defined in multiple files; last: ${file}`);
|
|
760
|
-
const validated = validateRoomsConfig(deepSub(doc.rooms, vars), vars, file);
|
|
778
|
+
const validated = validateRoomsConfig(deepSub(doc.rooms, vars), vars, file, options.deferredOwnerInviteFile);
|
|
761
779
|
ownerInviteFingerprint = validated._invite?.fingerprint;
|
|
762
780
|
ownerInvite = validated._invite?.value;
|
|
763
781
|
const { _invite: _, ...clean } = validated;
|
|
@@ -1135,6 +1153,23 @@ function resolveStartStaggerMs(raw, base) {
|
|
|
1135
1153
|
* malformed block so a typo fails loudly rather than silently disarming a monitor.
|
|
1136
1154
|
* Exported so temp-spawn (which builds a ResolvedRole by hand) resolves identically.
|
|
1137
1155
|
*/
|
|
1156
|
+
/**
|
|
1157
|
+
* A monitor without an explicit `interrupt` steers wakes at the next tool
|
|
1158
|
+
* boundary. Hermes cannot, so its default stays non-interrupting; sessions
|
|
1159
|
+
* without tool-boundary steering fall back to non-cancelling delivery.
|
|
1160
|
+
*/
|
|
1161
|
+
export function defaultMonitorInterrupt(harness) {
|
|
1162
|
+
return harness === 'hermes' ? false : 'after_tool';
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Resolve a persisted temporary-role monitor. A snapshot written before
|
|
1166
|
+
* monitor.interrupt existed keeps the non-interrupting delivery it ran with.
|
|
1167
|
+
*/
|
|
1168
|
+
export function resolveSnapshotMonitor(monitor) {
|
|
1169
|
+
if (!isPlainObject(monitor))
|
|
1170
|
+
return resolveMonitorConfig(undefined, monitor);
|
|
1171
|
+
return resolveMonitorConfig(undefined, { interrupt: false, ...monitor });
|
|
1172
|
+
}
|
|
1138
1173
|
export function resolveMonitorConfig(defMonitor, roleMonitor, labels = {}) {
|
|
1139
1174
|
const where = labels.file && labels.name ? `${labels.file}: role '${labels.name}' ` : '';
|
|
1140
1175
|
if (defMonitor !== undefined && !isPlainObject(defMonitor))
|
|
@@ -1169,7 +1204,7 @@ export function resolveMonitorConfig(defMonitor, roleMonitor, labels = {}) {
|
|
|
1169
1204
|
wake_sources: merged.wake_sources ?? [...DEFAULT_WAKE_SOURCES],
|
|
1170
1205
|
batch_ms: merged.batch_ms ?? MONITOR_DEFAULT_BATCH_MS,
|
|
1171
1206
|
inject: merged.inject ?? 'notification',
|
|
1172
|
-
interrupt: merged.interrupt ??
|
|
1207
|
+
interrupt: merged.interrupt ?? defaultMonitorInterrupt(labels.harness),
|
|
1173
1208
|
...(merged.stall_recovery !== undefined ? { stall_recovery: merged.stall_recovery } : {}),
|
|
1174
1209
|
...(merged.stall_timeout_ms !== undefined ? { stall_timeout_ms: merged.stall_timeout_ms } : {}),
|
|
1175
1210
|
turn_fail_threshold: merged.turn_fail_threshold ?? MONITOR_DEFAULT_TURN_FAIL_THRESHOLD,
|
package/dist/creation.js
CHANGED
|
@@ -213,10 +213,10 @@ export async function ensureIdentity(name, profile, provisioner, log = () => { }
|
|
|
213
213
|
export function daemonIdentityProvisioner(env = process.env, attachClient = attachOursClient, options = {}) {
|
|
214
214
|
const connect = (leaseToken) => {
|
|
215
215
|
const profile = readClientProfile(env);
|
|
216
|
-
return attachClient(
|
|
216
|
+
return attachClient({
|
|
217
217
|
endpoint: profile.endpoint, expectedInstanceId: profile.expectedInstanceId,
|
|
218
218
|
credentialPath: profile.credentialPath, sessionMode: 'external', leaseToken, env: {},
|
|
219
|
-
}
|
|
219
|
+
});
|
|
220
220
|
};
|
|
221
221
|
const provisioner = {
|
|
222
222
|
async exists(name) {
|
|
@@ -34,14 +34,7 @@ interface GenerationProbeDeps {
|
|
|
34
34
|
canonicalize?(path: string): string;
|
|
35
35
|
attachClient?(options: AttachOursClientOptions): Pick<OursClient, 'version' | 'identities' | 'close'> | Promise<Pick<OursClient, 'version' | 'identities' | 'close'>>;
|
|
36
36
|
}
|
|
37
|
-
/**
|
|
38
|
-
* Corroborate the loopback daemon's unauthenticated `/info`, its credentialed
|
|
39
|
-
* identity-index readiness route, and its local boot-generation record. The
|
|
40
|
-
* index enforces auth when the daemon visibility requires it; open visibility
|
|
41
|
-
* deliberately does not. No source is sufficient
|
|
42
|
-
* alone: `/info` has no boot id, the identity index has no generation, and a
|
|
43
|
-
* stale `ready` file can outlive the process that wrote it.
|
|
44
|
-
*/
|
|
37
|
+
/** Read generation and readiness through the selected gateway. Server paths are opaque. */
|
|
45
38
|
export declare function probeDaemonGeneration(fetch: FetchLike, env: NodeJS.ProcessEnv, deps?: GenerationProbeDeps): Promise<DaemonGenerationProbe>;
|
|
46
39
|
export declare class DaemonGenerationObserver {
|
|
47
40
|
private current?;
|
package/dist/daemon-recovery.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { readFileSync
|
|
2
|
-
import { join
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
4
4
|
import { attachOursClient, } from '@ours.network/sdk/client';
|
|
5
|
-
import { readDaemonConfig, resolveEndpoint } from './monitor.js';
|
|
6
5
|
import { readClientProfile } from './client-profile.js';
|
|
7
6
|
import { replaceFileAtomically } from './atomic-file.js';
|
|
8
7
|
export const DAEMON_RECOVERY_MAX_ATTEMPTS = 6;
|
|
@@ -39,129 +38,25 @@ function startupProgress(value) {
|
|
|
39
38
|
return undefined;
|
|
40
39
|
return row;
|
|
41
40
|
}
|
|
42
|
-
|
|
43
|
-
const absolute = resolve(path);
|
|
44
|
-
return deps.canonicalize?.(absolute) ?? realpathSync.native(absolute);
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Corroborate the loopback daemon's unauthenticated `/info`, its credentialed
|
|
48
|
-
* identity-index readiness route, and its local boot-generation record. The
|
|
49
|
-
* index enforces auth when the daemon visibility requires it; open visibility
|
|
50
|
-
* deliberately does not. No source is sufficient
|
|
51
|
-
* alone: `/info` has no boot id, the identity index has no generation, and a
|
|
52
|
-
* stale `ready` file can outlive the process that wrote it.
|
|
53
|
-
*/
|
|
41
|
+
/** Read generation and readiness through the selected gateway. Server paths are opaque. */
|
|
54
42
|
export async function probeDaemonGeneration(fetch, env, deps = {}) {
|
|
55
|
-
const profile = readClientProfile(env);
|
|
56
|
-
if (profile || env.OURS_DAEMON_ID)
|
|
57
|
-
return probeSelectedDaemon(env, deps, profile);
|
|
58
|
-
const endpoint = resolveEndpoint(env);
|
|
59
|
-
let response;
|
|
60
|
-
try {
|
|
61
|
-
response = await fetch(`${endpoint.origin}/info`, { headers: endpoint.headers });
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
return { state: 'unavailable', reason: 'DAEMON_INFO_UNREACHABLE' };
|
|
65
|
-
}
|
|
66
|
-
if (!response.ok)
|
|
67
|
-
return {
|
|
68
|
-
state: 'unavailable',
|
|
69
|
-
reason: response.status === 401 ? 'DAEMON_INFO_UNAUTHORIZED' : 'DAEMON_INFO_HTTP_ERROR',
|
|
70
|
-
};
|
|
71
|
-
let info;
|
|
72
|
-
try {
|
|
73
|
-
const value = await response.json();
|
|
74
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
75
|
-
throw new Error('shape');
|
|
76
|
-
info = value;
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
return { state: 'unavailable', reason: 'DAEMON_INFO_INVALID' };
|
|
80
|
-
}
|
|
81
|
-
if (info.name !== 'ours' || !positiveInteger(info.pid)
|
|
82
|
-
|| typeof info.stateDir !== 'string' || info.stateDir.length === 0)
|
|
83
|
-
return { state: 'unavailable', reason: 'DAEMON_INFO_INVALID' };
|
|
84
|
-
let reportedStateDir;
|
|
85
|
-
let expectedStateDir;
|
|
86
|
-
try {
|
|
87
|
-
reportedStateDir = canonical(info.stateDir, deps);
|
|
88
|
-
expectedStateDir = canonical(endpoint.stateDir, deps);
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
return { state: 'unavailable', reason: 'DAEMON_STATE_DIR_UNAVAILABLE' };
|
|
92
|
-
}
|
|
93
|
-
if (reportedStateDir !== expectedStateDir)
|
|
94
|
-
return { state: 'unavailable', reason: 'DAEMON_STATE_DIR_MISMATCH' };
|
|
95
|
-
// `/identities` is the daemon's credential-capable readiness route. A
|
|
96
|
-
// valid-but-empty index is a documented intermediate startup state, never
|
|
97
|
-
// proof of readiness.
|
|
98
|
-
let identitiesResponse;
|
|
99
|
-
try {
|
|
100
|
-
identitiesResponse = await fetch(`${endpoint.origin}/identities`, { headers: endpoint.headers });
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
return { state: 'unavailable', reason: 'DAEMON_IDENTITIES_UNREACHABLE' };
|
|
104
|
-
}
|
|
105
|
-
if (!identitiesResponse.ok)
|
|
106
|
-
return {
|
|
107
|
-
state: 'unavailable',
|
|
108
|
-
reason: identitiesResponse.status === 401
|
|
109
|
-
? 'DAEMON_IDENTITIES_UNAUTHORIZED' : 'DAEMON_IDENTITIES_HTTP_ERROR',
|
|
110
|
-
};
|
|
111
|
-
try {
|
|
112
|
-
const body = await identitiesResponse.json();
|
|
113
|
-
if (!body || typeof body !== 'object' || Array.isArray(body)
|
|
114
|
-
|| !Array.isArray(body.identities))
|
|
115
|
-
return { state: 'unavailable', reason: 'DAEMON_IDENTITIES_INVALID' };
|
|
116
|
-
if (body.identities.length === 0)
|
|
117
|
-
return { state: 'unavailable', reason: 'DAEMON_IDENTITIES_NOT_READY' };
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
return { state: 'unavailable', reason: 'DAEMON_IDENTITIES_INVALID' };
|
|
121
|
-
}
|
|
122
|
-
let progress;
|
|
123
43
|
try {
|
|
124
|
-
|
|
125
|
-
?? readFileSync(join(reportedStateDir, 'startup-progress.json'), 'utf8');
|
|
126
|
-
progress = startupProgress(JSON.parse(text));
|
|
44
|
+
return await probeSelectedDaemon(deps, readClientProfile(env));
|
|
127
45
|
}
|
|
128
46
|
catch {
|
|
129
|
-
return { state: 'unavailable', reason: '
|
|
47
|
+
return { state: 'unavailable', reason: 'CLIENT_GATEWAY_PROFILE_INVALID' };
|
|
130
48
|
}
|
|
131
|
-
if (!progress)
|
|
132
|
-
return { state: 'unavailable', reason: 'DAEMON_PROGRESS_INVALID' };
|
|
133
|
-
if (progress.phase !== 'ready')
|
|
134
|
-
return { state: 'unavailable', reason: 'DAEMON_PROGRESS_NOT_READY' };
|
|
135
|
-
if (progress.pid !== info.pid)
|
|
136
|
-
return { state: 'unavailable', reason: 'DAEMON_GENERATION_MISMATCH' };
|
|
137
|
-
return {
|
|
138
|
-
state: 'ready',
|
|
139
|
-
generation: {
|
|
140
|
-
// Keep bootId opaque. Its current writer uses `${pid}-${startedAt}`, but
|
|
141
|
-
// equality—not its formatting—is the generation contract Fleet needs.
|
|
142
|
-
bootId: progress.bootId,
|
|
143
|
-
pid: progress.pid,
|
|
144
|
-
startedAt: progress.startedAt,
|
|
145
|
-
stateDir: reportedStateDir,
|
|
146
|
-
},
|
|
147
|
-
};
|
|
148
49
|
}
|
|
149
50
|
/** Container selection uses public SDK reads; daemon paths/PIDs remain opaque metadata. */
|
|
150
|
-
async function probeSelectedDaemon(
|
|
51
|
+
async function probeSelectedDaemon(deps, profile) {
|
|
151
52
|
// Defer credential-file access to SDK selection, which checks the configured
|
|
152
53
|
// daemon identity/capability before it reads or sends the credential.
|
|
153
|
-
const endpoint = profile ? undefined : resolveEndpoint(env, false);
|
|
154
|
-
const token = profile ? undefined : env.OURS_API_TOKEN?.trim() || readDaemonConfig(env).apiToken;
|
|
155
54
|
let client;
|
|
156
55
|
try {
|
|
157
|
-
const options =
|
|
56
|
+
const options = {
|
|
158
57
|
endpoint: profile.endpoint, expectedInstanceId: profile.expectedInstanceId,
|
|
159
58
|
credentialPath: profile.credentialPath,
|
|
160
59
|
sessionMode: 'external', leaseToken: randomUUID(), env: {},
|
|
161
|
-
} : {
|
|
162
|
-
endpoint: endpoint.origin, expectedInstanceId: env.OURS_DAEMON_ID,
|
|
163
|
-
sessionMode: 'external', leaseToken: randomUUID(),
|
|
164
|
-
...(token ? { token } : { credentialPath: join(endpoint.stateDir, 'daemon-token') }),
|
|
165
60
|
};
|
|
166
61
|
client = await (deps.attachClient?.(options) ?? attachOursClient(options));
|
|
167
62
|
const info = await client.version({ startup: true });
|
|
@@ -189,7 +84,7 @@ async function probeSelectedDaemon(env, deps, profile) {
|
|
|
189
84
|
return { state: 'unavailable', reason: 'DAEMON_SELECTED_PROBE_UNAVAILABLE' };
|
|
190
85
|
}
|
|
191
86
|
finally {
|
|
192
|
-
await client?.close();
|
|
87
|
+
await client?.close().catch(() => undefined);
|
|
193
88
|
}
|
|
194
89
|
}
|
|
195
90
|
export class DaemonGenerationObserver {
|