@ours.network/fleet 1.2.0-nightly.5 → 1.2.0-nightly.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +263 -53
- package/dist/account-origin.d.ts +2 -0
- package/dist/account-origin.js +6 -0
- package/dist/agent-ours/bridge.js +9 -0
- package/dist/agent-ours/correspondence.d.ts +10 -0
- package/dist/agent-ours/correspondence.js +1 -0
- package/dist/agent-ours/harness.js +8 -0
- package/dist/agent-ours/runtime.d.ts +6 -0
- package/dist/agent-ours/runtime.js +36 -0
- package/dist/agent-ours/service.js +33 -13
- package/dist/application/codex-models.d.ts +15 -0
- package/dist/application/codex-models.js +56 -0
- package/dist/application/conversation-history.d.ts +25 -0
- package/dist/application/conversation-history.js +189 -0
- package/dist/application/fleet-query-service.d.ts +1 -1
- package/dist/application/fleet-query-service.js +2 -2
- package/dist/application/legacy-supervisor-identity.d.ts +7 -0
- package/dist/application/legacy-supervisor-identity.js +43 -0
- package/dist/application/model-catalog.d.ts +1 -1
- package/dist/application/model-catalog.js +5 -2
- package/dist/application/preset-provenance.d.ts +19 -0
- package/dist/application/preset-provenance.js +59 -0
- package/dist/application/role-creation-service.js +1 -1
- package/dist/application/role-removal-service.js +2 -0
- package/dist/application/role-repository.js +2 -2
- package/dist/application/room-layout-definitions.d.ts +45 -0
- package/dist/application/room-layout-definitions.js +130 -0
- package/dist/application/session-control.d.ts +30 -0
- package/dist/application/session-control.js +72 -3
- package/dist/application/supervisor-ours-tools.d.ts +159 -0
- package/dist/application/supervisor-ours-tools.js +113 -0
- package/dist/application/task-layouts.d.ts +91 -0
- package/dist/application/task-layouts.js +249 -0
- package/dist/application/task-room-service.d.ts +99 -3
- package/dist/application/task-room-service.js +296 -31
- package/dist/atomic-file.d.ts +5 -0
- package/dist/atomic-file.js +62 -1
- package/dist/briefing.d.ts +2 -0
- package/dist/briefing.js +8 -1
- package/dist/build-info.json +7 -4
- package/dist/capabilities.d.ts +6 -1
- package/dist/capabilities.js +8 -0
- package/dist/cli.js +376 -34
- package/dist/client-profile.d.ts +5 -17
- package/dist/client-profile.js +4 -127
- package/dist/config.d.ts +28 -0
- package/dist/config.js +41 -6
- package/dist/creation.js +2 -2
- package/dist/daemon-recovery.d.ts +1 -8
- package/dist/daemon-recovery.js +8 -113
- package/dist/docs.d.ts +3 -3
- package/dist/docs.js +126 -22
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +55 -60
- package/dist/erased-resources.d.ts +9 -0
- package/dist/erased-resources.js +87 -0
- package/dist/fleet-command-audit.d.ts +7 -0
- package/dist/fleet-command-audit.js +26 -9
- package/dist/harness/claude-code.js +9 -1
- package/dist/harness/claude-runtime.d.ts +12 -0
- package/dist/harness/claude-runtime.js +43 -0
- package/dist/harness/codex-app-server-proxy.d.ts +4 -1
- package/dist/harness/codex-app-server-proxy.js +41 -5
- package/dist/harness/codex-reasoning.d.ts +5 -0
- package/dist/harness/codex-reasoning.js +12 -0
- package/dist/harness/codex-runtime.d.ts +11 -1
- package/dist/harness/codex-runtime.js +31 -4
- package/dist/harness/codex.d.ts +3 -2
- package/dist/harness/codex.js +20 -11
- package/dist/init-wizard.d.ts +2 -0
- package/dist/init-wizard.js +38 -19
- package/dist/isolation/policy.js +2 -0
- package/dist/isolation/types.d.ts +5 -0
- package/dist/managed-cli.d.ts +253 -0
- package/dist/managed-cli.js +923 -0
- package/dist/minimal-setup.d.ts +2 -0
- package/dist/minimal-setup.js +44 -0
- package/dist/monitor.d.ts +1 -34
- package/dist/monitor.js +24 -151
- package/dist/notifications/fleet-producer.d.ts +19 -0
- package/dist/notifications/fleet-producer.js +208 -0
- package/dist/notifications/outbox.d.ts +46 -0
- package/dist/notifications/outbox.js +158 -0
- package/dist/notifications/target-cleanup.d.ts +8 -0
- package/dist/notifications/target-cleanup.js +41 -0
- package/dist/ops.js +5 -1
- package/dist/owner-channel/channel.d.ts +3 -0
- package/dist/owner-channel/channel.js +34 -6
- package/dist/owner-channel/commands.d.ts +4 -0
- package/dist/owner-channel/commands.js +8 -0
- package/dist/owner-channel/lifecycle-outbox.js +4 -3
- package/dist/owner-channel/ours-client.js +2 -17
- package/dist/paths.d.ts +2 -0
- package/dist/paths.js +2 -0
- package/dist/preset-migration.js +25 -0
- package/dist/resolved-plan.js +1 -0
- package/dist/rooms-tasks/add-member.d.ts +48 -0
- package/dist/rooms-tasks/add-member.js +83 -0
- package/dist/rooms-tasks/archived-absence.d.ts +8 -0
- package/dist/rooms-tasks/archived-absence.js +48 -0
- package/dist/rooms-tasks/cli.js +34 -5
- package/dist/rooms-tasks/close.d.ts +5 -2
- package/dist/rooms-tasks/close.js +208 -28
- package/dist/rooms-tasks/config.d.ts +1 -1
- package/dist/rooms-tasks/config.js +11 -4
- package/dist/rooms-tasks/cowork-adapter.d.ts +6 -4
- package/dist/rooms-tasks/cowork-adapter.js +8 -121
- package/dist/rooms-tasks/deletion.d.ts +1 -1
- package/dist/rooms-tasks/deletion.js +74 -5
- package/dist/rooms-tasks/erasure.d.ts +6 -0
- package/dist/rooms-tasks/erasure.js +169 -0
- package/dist/rooms-tasks/external-worker.d.ts +1 -1
- package/dist/rooms-tasks/external-worker.js +3 -4
- package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
- package/dist/rooms-tasks/layout-binding-client.js +56 -0
- package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
- package/dist/rooms-tasks/layout-binding-grants.js +128 -0
- package/dist/rooms-tasks/layout-cli.d.ts +4 -0
- package/dist/rooms-tasks/layout-cli.js +107 -0
- package/dist/rooms-tasks/layout-config.d.ts +11 -0
- package/dist/rooms-tasks/layout-config.js +89 -0
- package/dist/rooms-tasks/layout-control.d.ts +21 -0
- package/dist/rooms-tasks/layout-control.js +52 -0
- package/dist/rooms-tasks/layout-service.d.ts +42 -0
- package/dist/rooms-tasks/layout-service.js +185 -0
- package/dist/rooms-tasks/layout.d.ts +114 -0
- package/dist/rooms-tasks/layout.js +250 -0
- package/dist/rooms-tasks/live-readiness.d.ts +18 -0
- package/dist/rooms-tasks/live-readiness.js +85 -0
- package/dist/rooms-tasks/member-startup.d.ts +1 -0
- package/dist/rooms-tasks/member-startup.js +2 -0
- package/dist/rooms-tasks/provision.d.ts +14 -0
- package/dist/rooms-tasks/provision.js +173 -46
- package/dist/rooms-tasks/room-state.d.ts +4 -2
- package/dist/rooms-tasks/room-state.js +53 -8
- package/dist/rooms-tasks/task-state.d.ts +42 -7
- package/dist/rooms-tasks/task-state.js +153 -23
- package/dist/rooms-tasks/types.d.ts +28 -0
- package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
- package/dist/rooms-tasks/workspace-artifacts.js +93 -0
- package/dist/rooms-tasks/workspace.d.ts +26 -0
- package/dist/rooms-tasks/workspace.js +195 -0
- package/dist/runner.d.ts +19 -3
- package/dist/runner.js +128 -45
- package/dist/session/acp.d.ts +9 -1
- package/dist/session/acp.js +31 -6
- package/dist/session/arbiter.d.ts +5 -0
- package/dist/session/arbiter.js +28 -2
- package/dist/session/codex-app-server.d.ts +5 -0
- package/dist/session/codex-app-server.js +36 -3
- package/dist/session/control.d.ts +12 -1
- package/dist/session/control.js +68 -3
- package/dist/session/conversation-normalizer.d.ts +2 -1
- package/dist/session/conversation-normalizer.js +1 -1
- package/dist/session/conversation-store.d.ts +1 -1
- package/dist/session/conversation-store.js +7 -11
- package/dist/session/conversation-types.d.ts +7 -3
- package/dist/session/task-notice.d.ts +14 -0
- package/dist/session/task-notice.js +13 -0
- package/dist/session/types.d.ts +6 -0
- package/dist/setup-tunnel-preflight.d.ts +34 -0
- package/dist/setup-tunnel-preflight.js +62 -0
- package/dist/socket-path.d.ts +2 -0
- package/dist/socket-path.js +22 -0
- package/dist/spawn.js +8 -1
- package/dist/startup-readiness.d.ts +13 -0
- package/dist/startup-readiness.js +38 -0
- package/dist/subscriptions/cli.d.ts +91 -0
- package/dist/subscriptions/cli.js +223 -0
- package/dist/subscriptions/launch.d.ts +37 -0
- package/dist/subscriptions/launch.js +93 -0
- package/dist/subscriptions/login.d.ts +75 -0
- package/dist/subscriptions/login.js +182 -0
- package/dist/subscriptions/observed.d.ts +7 -0
- package/dist/subscriptions/observed.js +44 -0
- package/dist/subscriptions/probe.d.ts +10 -0
- package/dist/subscriptions/probe.js +73 -0
- package/dist/subscriptions/service.d.ts +102 -0
- package/dist/subscriptions/service.js +364 -0
- package/dist/subscriptions/store.d.ts +71 -0
- package/dist/subscriptions/store.js +140 -0
- package/dist/supervisor/launchd.js +2 -1
- package/dist/supervisor/systemd.js +9 -23
- package/dist/temp-idle.d.ts +17 -0
- package/dist/temp-idle.js +42 -0
- package/dist/temp-lifecycle.d.ts +6 -1
- package/dist/temp-lifecycle.js +29 -13
- package/dist/tunnel-setup-completion.d.ts +7 -0
- package/dist/tunnel-setup-completion.js +13 -0
- package/dist/tunnel-setup-input.d.ts +19 -0
- package/dist/tunnel-setup-input.js +124 -0
- package/dist/web/agent-attachments.d.ts +29 -0
- package/dist/web/agent-attachments.js +162 -0
- package/dist/web/auth.d.ts +29 -1
- package/dist/web/auth.js +82 -7
- package/dist/web/available-port.d.ts +2 -0
- package/dist/web/available-port.js +23 -0
- package/dist/web/control.d.ts +3 -3
- package/dist/web/control.js +10 -8
- package/dist/web/fleet-config-service.d.ts +4 -1
- package/dist/web/fleet-config-service.js +46 -15
- package/dist/web/prefix-gateway.d.ts +32 -0
- package/dist/web/prefix-gateway.js +234 -0
- package/dist/web/runtime.d.ts +3 -1
- package/dist/web/runtime.js +116 -31
- package/dist/web/server.d.ts +16 -0
- package/dist/web/server.js +588 -53
- package/dist/web/service.d.ts +11 -2
- package/dist/web/service.js +30 -4
- package/dist/web/workspace-devices.d.ts +36 -0
- package/dist/web/workspace-devices.js +101 -0
- package/dist/web/workspace-tunnel.d.ts +1 -0
- package/dist/web/workspace-tunnel.js +34 -0
- package/dist/workspace-enrollment.d.ts +82 -0
- package/dist/workspace-enrollment.js +566 -0
- package/dist/workspace-replacement.d.ts +66 -0
- package/dist/workspace-replacement.js +220 -0
- package/docs/live-voice.md +13 -0
- package/docs/room-layouts.md +227 -0
- package/docs/task-workspaces.md +130 -0
- package/docs/validation/managed-cli-permissions.md +421 -0
- package/examples/room-layouts/fresh.yaml +27 -0
- package/examples/room-layouts/scoped.yaml +21 -0
- package/examples/room-layouts/shared.yaml +19 -0
- package/package.json +15 -18
- package/presets/README.md +78 -0
- package/presets/brain-catalog.json +8 -2
- package/presets/fleet/agent_templates/Critic.yaml +1 -1
- package/presets/fleet/agent_templates/Developer.yaml +1 -1
- package/presets/fleet/agent_templates/Engineer.yaml +5 -0
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +1 -1
- package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
- package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +34 -11
- package/presets/fleet/roles/Engineer.yaml +40 -0
- package/presets/fleet/room_layouts/engineering.yaml +25 -0
- package/presets/fleet/room_layouts/pair.yaml +24 -0
- package/presets/fleet/room_layouts/single.yaml +17 -0
- package/presets/fleet/room_layouts/team.yaml +30 -0
- package/presets/fleet/room_templates/engineering.yaml +19 -0
- package/presets/manifest.json +2 -2
- package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
- package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
- package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
- package/dist/web-app/icons/ours-fleet.svg +0 -4
- package/dist/web-app/index.html +0 -17
- package/dist/web-app/manifest.webmanifest +0 -15
- package/dist/web-app/offline.html +0 -18
- package/dist/web-app/sw.js +0 -51
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { readPrivateFile } from '@ours.network/sdk/connector';
|
|
4
|
+
import { readClientProfile } from '../client-profile.js';
|
|
5
|
+
function defaultProfile() {
|
|
6
|
+
try {
|
|
7
|
+
return readClientProfile();
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** The installer delivers the producer credential beside the profile credential, bound to that server installation. */
|
|
14
|
+
export const PRODUCER_CREDENTIAL_FILE = 'notifications-producer.json';
|
|
15
|
+
const loopback = (url) => ['127.0.0.1', '[::1]', 'localhost'].includes(url.hostname);
|
|
16
|
+
export function producerConfig(env = process.env, profile = defaultProfile, warn = () => { }) {
|
|
17
|
+
const origin = env.OURS_NOTIFICATIONS_ORIGIN, token = env.OURS_NOTIFICATIONS_PRODUCER_TOKEN;
|
|
18
|
+
if (origin || token) {
|
|
19
|
+
if (!origin || !token || token.length < 32)
|
|
20
|
+
throw new Error('notifications producer requires origin and scoped token');
|
|
21
|
+
const url = new URL(origin);
|
|
22
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.origin !== origin || url.username || url.password)
|
|
23
|
+
throw new Error('invalid notification service origin');
|
|
24
|
+
if (url.protocol === 'http:' && !loopback(url))
|
|
25
|
+
throw new Error('notification credentials require HTTPS or loopback');
|
|
26
|
+
return { origin, token };
|
|
27
|
+
}
|
|
28
|
+
const selected = profile();
|
|
29
|
+
if (!selected)
|
|
30
|
+
return;
|
|
31
|
+
const file = join(dirname(selected.credentialPath), PRODUCER_CREDENTIAL_FILE);
|
|
32
|
+
if (!existsSync(file))
|
|
33
|
+
return;
|
|
34
|
+
// Reasons are fixed text: parser and file errors can quote the secret-bearing contents.
|
|
35
|
+
let reason = 'it is not a private owner-only file';
|
|
36
|
+
try {
|
|
37
|
+
const text = readPrivateFile(file, 4096).toString('utf8');
|
|
38
|
+
reason = 'it is not valid producer JSON';
|
|
39
|
+
const bound = JSON.parse(text);
|
|
40
|
+
reason = 'it belongs to another server installation';
|
|
41
|
+
if (!bound || bound.schema !== 1 || bound.serverUrl !== selected.serverUrl || bound.expectedInstanceId !== selected.expectedInstanceId)
|
|
42
|
+
throw new Error();
|
|
43
|
+
reason = 'its credential is invalid';
|
|
44
|
+
if (typeof bound.token !== 'string' || bound.token.length < 32)
|
|
45
|
+
throw new Error();
|
|
46
|
+
reason = 'the server requires HTTPS or loopback';
|
|
47
|
+
const server = new URL(selected.serverUrl);
|
|
48
|
+
if (server.protocol !== 'https:' && !(server.protocol === 'http:' && loopback(server)))
|
|
49
|
+
throw new Error();
|
|
50
|
+
reason = 'the client credential is unreadable';
|
|
51
|
+
const gatewayCredential = readPrivateFile(selected.credentialPath, 4096).toString('utf8').trim();
|
|
52
|
+
return { origin: selected.serverUrl.replace(/\/$/, '') + '/notifications', token: bound.token, gatewayCredential };
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
warn(`Notifications are not produced: ${file} is unusable (${reason}). Re-run the installer to repair it.`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Durable producer queue. Service dedupe makes crash-after-acceptance replay safe. */
|
|
60
|
+
export class NotificationOutbox {
|
|
61
|
+
file;
|
|
62
|
+
config;
|
|
63
|
+
project;
|
|
64
|
+
warn;
|
|
65
|
+
entries;
|
|
66
|
+
cursor;
|
|
67
|
+
checkpoint;
|
|
68
|
+
running;
|
|
69
|
+
retired = false;
|
|
70
|
+
timer;
|
|
71
|
+
constructor(file, config, project, warn = () => { }) {
|
|
72
|
+
this.file = file;
|
|
73
|
+
this.config = config;
|
|
74
|
+
this.project = project;
|
|
75
|
+
this.warn = warn;
|
|
76
|
+
mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
|
|
77
|
+
const saved = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : { entries: [] };
|
|
78
|
+
this.entries = Array.isArray(saved) ? saved : saved.entries;
|
|
79
|
+
this.cursor = saved.cursor;
|
|
80
|
+
this.checkpoint = saved.checkpoint;
|
|
81
|
+
if (!Array.isArray(this.entries))
|
|
82
|
+
throw new Error('invalid notification outbox');
|
|
83
|
+
this.timer = setInterval(() => { void this.drain(); }, 3000);
|
|
84
|
+
this.timer.unref();
|
|
85
|
+
}
|
|
86
|
+
persist(entries, cursor = this.cursor, checkpoint = this.checkpoint) {
|
|
87
|
+
const tmp = this.file + '.tmp', fd = openSync(tmp, 'w', 0o600);
|
|
88
|
+
try {
|
|
89
|
+
writeFileSync(fd, JSON.stringify({ entries, cursor, checkpoint }));
|
|
90
|
+
fsyncSync(fd);
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
closeSync(fd);
|
|
94
|
+
}
|
|
95
|
+
renameSync(tmp, this.file);
|
|
96
|
+
const dir = openSync(dirname(this.file), 'r');
|
|
97
|
+
try {
|
|
98
|
+
fsyncSync(dir);
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
closeSync(dir);
|
|
102
|
+
}
|
|
103
|
+
this.entries = entries;
|
|
104
|
+
this.cursor = cursor;
|
|
105
|
+
this.checkpoint = checkpoint;
|
|
106
|
+
}
|
|
107
|
+
advance(cursor) { this.persist(this.entries, cursor); }
|
|
108
|
+
saveCheckpoint(checkpoint) { this.persist(this.entries, this.cursor, checkpoint); }
|
|
109
|
+
enqueue(id, value, cursor = this.cursor) {
|
|
110
|
+
if (this.retired)
|
|
111
|
+
return false;
|
|
112
|
+
if (this.entries.some(e => e.id === id)) {
|
|
113
|
+
if (cursor !== this.cursor)
|
|
114
|
+
this.advance(cursor);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
if (this.entries.length >= 2000)
|
|
118
|
+
return false;
|
|
119
|
+
this.persist([...this.entries, { id, value }], cursor);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
drain() {
|
|
123
|
+
if (this.retired)
|
|
124
|
+
return Promise.resolve();
|
|
125
|
+
return this.running ??= this.drainOnce().finally(() => { this.running = undefined; });
|
|
126
|
+
}
|
|
127
|
+
async drainOnce() {
|
|
128
|
+
for (const entry of [...this.entries].slice(0, 32)) {
|
|
129
|
+
if (this.retired)
|
|
130
|
+
break;
|
|
131
|
+
try {
|
|
132
|
+
const value = entry.payload ?? await this.project(entry.value, entry.id);
|
|
133
|
+
// Freeze the canonical projection before any network attempt. A crash
|
|
134
|
+
// after service acceptance must replay the identical dedupe payload.
|
|
135
|
+
if (!entry.payload)
|
|
136
|
+
this.persist(this.entries.map(e => e.id === entry.id ? { ...e, payload: value } : e));
|
|
137
|
+
const response = await fetch(this.config.origin + '/api/v1/send', {
|
|
138
|
+
method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10_000),
|
|
139
|
+
headers: { ...(this.config.gatewayCredential
|
|
140
|
+
? { 'X-Ours-Api-Token': this.config.gatewayCredential, 'X-Ours-Notifications-Producer': this.config.token }
|
|
141
|
+
: { Authorization: `Bearer ${this.config.token}` }), 'Content-Type': 'application/json' },
|
|
142
|
+
body: JSON.stringify(value),
|
|
143
|
+
});
|
|
144
|
+
await response.body?.cancel();
|
|
145
|
+
if (!response.ok)
|
|
146
|
+
throw new Error('notification service rejected event');
|
|
147
|
+
this.persist(this.entries.filter(e => e.id !== entry.id));
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
this.warn('Notification producer delivery pending; retrying from durable outbox');
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/** Wait for the current send before clearing queued sends and retiring its target. */
|
|
156
|
+
async retire() { this.retired = true; clearInterval(this.timer); await this.running; this.persist([]); }
|
|
157
|
+
async close() { clearInterval(this.timer); await this.running; }
|
|
158
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ProducerConfig } from './outbox.js';
|
|
2
|
+
/** Queue only after the owning lifecycle has established deletion, never on a failed inventory read. */
|
|
3
|
+
export declare function retireNotificationTarget(url: string, config?: ProducerConfig | undefined, dir?: string, request?: typeof fetch): Promise<void>;
|
|
4
|
+
/** Persisted retries also run from the ordinary web notification producer after restart. */
|
|
5
|
+
export declare function drainNotificationTargetCleanup(config: ProducerConfig, dir?: string, request?: typeof fetch, options?: {
|
|
6
|
+
retain?: boolean;
|
|
7
|
+
beforeDelete?: (url: string) => Promise<boolean>;
|
|
8
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { replaceFileAtomically } from '../atomic-file.js';
|
|
5
|
+
import { stateRoot } from '../paths.js';
|
|
6
|
+
import { producerConfig } from './outbox.js';
|
|
7
|
+
const cleanupDir = () => join(stateRoot(), 'notifications', 'removed-targets');
|
|
8
|
+
/** Queue only after the owning lifecycle has established deletion, never on a failed inventory read. */
|
|
9
|
+
export async function retireNotificationTarget(url, config = producerConfig(), dir = cleanupDir(), request = fetch) {
|
|
10
|
+
if (!config)
|
|
11
|
+
return;
|
|
12
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
13
|
+
replaceFileAtomically(join(dir, createHash('sha256').update(url).digest('hex') + '.json'), JSON.stringify({ url }) + '\n', 0o600);
|
|
14
|
+
await drainNotificationTargetCleanup(config, dir, request, { retain: true });
|
|
15
|
+
}
|
|
16
|
+
/** Persisted retries also run from the ordinary web notification producer after restart. */
|
|
17
|
+
export async function drainNotificationTargetCleanup(config, dir = cleanupDir(), request = fetch, options = {}) {
|
|
18
|
+
if (!existsSync(dir))
|
|
19
|
+
return;
|
|
20
|
+
for (const file of readdirSync(dir).filter(file => /^[a-f0-9]{64}\.json$/.test(file)).slice(0, 32)) {
|
|
21
|
+
try {
|
|
22
|
+
const path = join(dir, file), value = JSON.parse(readFileSync(path, 'utf8'));
|
|
23
|
+
if (typeof value.url !== 'string' || !value.url.startsWith('/fleet/chats?'))
|
|
24
|
+
continue;
|
|
25
|
+
if (options.beforeDelete && !(await options.beforeDelete(value.url))) {
|
|
26
|
+
rmSync(path, { force: true });
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const response = await request(config.origin + '/api/v1/delete-target', { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10000),
|
|
30
|
+
headers: { ...(config.gatewayCredential ? { 'X-Ours-Api-Token': config.gatewayCredential, 'X-Ours-Notifications-Producer': config.token } : { Authorization: `Bearer ${config.token}` }), 'Content-Type': 'application/json' }, body: JSON.stringify(value) });
|
|
31
|
+
await response.body?.cancel();
|
|
32
|
+
if (!response.ok)
|
|
33
|
+
break;
|
|
34
|
+
if (!options.retain)
|
|
35
|
+
rmSync(path, { force: true });
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
break; /* No credentials or response bodies are included in logs. */
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
package/dist/ops.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { retireNotificationTarget } from './notifications/target-cleanup.js';
|
|
1
2
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, rmSync, unlinkSync, } from 'node:fs';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { randomUUID, createHash } from 'node:crypto';
|
|
@@ -5,6 +6,7 @@ import { agentDir, watchdogsRoot } from './paths.js';
|
|
|
5
6
|
import { findRole } from './config.js';
|
|
6
7
|
import { getAdapter } from './harness/registry.js';
|
|
7
8
|
import { generateBriefing } from './briefing.js';
|
|
9
|
+
import { managedCliBriefing } from './managed-cli.js';
|
|
8
10
|
import { resetRestartLedger } from './runner.js';
|
|
9
11
|
import { archiveTempState, stopTempSupervisor, tempSupervisorLiveness, } from './temp-lifecycle.js';
|
|
10
12
|
import { realExec } from './exec.js';
|
|
@@ -28,7 +30,7 @@ export function applyRole(role, opts = {}) {
|
|
|
28
30
|
// brought up from so the supervised process can reload the SAME file instead of
|
|
29
31
|
// silently falling back to the default ~/fleet.yaml. Temp roles snapshot their
|
|
30
32
|
// whole resolved role into role.yaml instead and don't need this.
|
|
31
|
-
if (!opts.temp)
|
|
33
|
+
if (!opts.temp || opts.configPath)
|
|
32
34
|
writeFileSync(join(dir, '.config-path'), (opts.configPath ?? '') + '\n');
|
|
33
35
|
writeFileSync(join(dir, '.identity'), role.identity + '\n');
|
|
34
36
|
if (role.cwd)
|
|
@@ -43,6 +45,7 @@ export function applyRole(role, opts = {}) {
|
|
|
43
45
|
routinesPath: join(dir, 'ROUTINES.md'), briefingBody,
|
|
44
46
|
identityGuarantee: opts.identityGuarantee,
|
|
45
47
|
temporaryIdentity: opts.temp === true,
|
|
48
|
+
managedCli: managedCliBriefing(role, opts.configPath, opts.temp === true),
|
|
46
49
|
}));
|
|
47
50
|
if (opts.fresh)
|
|
48
51
|
for (const f of ['.booted', '.session-id', '.exit-status'])
|
|
@@ -255,6 +258,7 @@ export async function rmRole(cfg, name, deps) {
|
|
|
255
258
|
rmSync(agentDir(name), { recursive: true, force: true });
|
|
256
259
|
if (generatedSource && existsSync(role.sourceFile)) {
|
|
257
260
|
unlinkSync(role.sourceFile);
|
|
261
|
+
await retireNotificationTarget(`/fleet/chats?chat=${encodeURIComponent(name)}`).catch(() => { });
|
|
258
262
|
deps.log(`removed ${role.sourceFile}`);
|
|
259
263
|
}
|
|
260
264
|
deps.log(`removed '${name}' (its ours identity is left intact)`);
|
|
@@ -14,6 +14,8 @@ export interface OwnerChannelOptions {
|
|
|
14
14
|
harness: string;
|
|
15
15
|
config: OwnerChannelConfig;
|
|
16
16
|
session: AgentSession;
|
|
17
|
+
/** Ordinary owner input must not cancel the initial briefing turn. */
|
|
18
|
+
startupPending?: () => boolean;
|
|
17
19
|
stateDir: string;
|
|
18
20
|
env?: Record<string, string>;
|
|
19
21
|
log(line: string): void;
|
|
@@ -269,6 +271,7 @@ export declare class OwnerChannel implements OwnerChannelHandle {
|
|
|
269
271
|
private attachmentGroups;
|
|
270
272
|
private handleAttachmentGroup;
|
|
271
273
|
private handle;
|
|
274
|
+
private ownerPromptPolicy;
|
|
272
275
|
/**
|
|
273
276
|
* Deterministic command path: the message never becomes an agent prompt.
|
|
274
277
|
* Authorization already happened — the managed-agent relay branch and the
|
|
@@ -883,8 +883,15 @@ export class OwnerChannel {
|
|
|
883
883
|
const recovered = [];
|
|
884
884
|
for (const claim of this.messageRecovery.list()) {
|
|
885
885
|
const item = await this.client.getHistoryItem(claim.wireId);
|
|
886
|
-
if (!item)
|
|
887
|
-
|
|
886
|
+
if (!item) {
|
|
887
|
+
// The daemon has definitively lost this body. Retrying the claim would
|
|
888
|
+
// block every later owner message, including after a supervisor restart.
|
|
889
|
+
// Keep transient lookup errors retryable; only an explicit absence skips.
|
|
890
|
+
this.messageRecovery.pruneHandled(wireId => wireId === claim.wireId);
|
|
891
|
+
this.options.log(`[${this.options.role}] skipped journaled owner message ${claim.wireId} `
|
|
892
|
+
+ `(seq=${claim.seq}): missing from persistent history`);
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
888
895
|
recovered.push(this.historyMessage(item, claim));
|
|
889
896
|
}
|
|
890
897
|
const listed = await this.client.listIncomingMessages();
|
|
@@ -1123,6 +1130,13 @@ export class OwnerChannel {
|
|
|
1123
1130
|
const retrieved = unread.length
|
|
1124
1131
|
? parseRetrievedAttachments(await this.client.getFiles(unread.map(file => file.wireId)), unread)
|
|
1125
1132
|
: [];
|
|
1133
|
+
// getFiles returns daemon-local paths, which need not exist in Fleet's
|
|
1134
|
+
// filesystem (for example with an HTTP daemon in a container). Fetch
|
|
1135
|
+
// through the bound client and keep the daemon's integrity metadata for
|
|
1136
|
+
// admission below; never trust or remap the returned filesystem path.
|
|
1137
|
+
for (const file of retrieved) {
|
|
1138
|
+
file.path = await writeRecoveredAttachment(requestDir, file.wireId, await this.client.fetchFile(file.wireId));
|
|
1139
|
+
}
|
|
1126
1140
|
for (const file of historyRecovered) {
|
|
1127
1141
|
if (!group.recovery)
|
|
1128
1142
|
throw new Error('unexpected read attachment without recovery route');
|
|
@@ -1136,8 +1150,7 @@ export class OwnerChannel {
|
|
|
1136
1150
|
await mkdir(outbox, { recursive: true, mode: 0o700 });
|
|
1137
1151
|
const activityCursor = this.latestEventSeq(this.options.session.eventsSince(0));
|
|
1138
1152
|
const queued = await queueSessionPrompt(this.options.session, this.ownerAttachmentPrompt(sender, originWireId, requestId, admitted, group.caption), {
|
|
1139
|
-
|
|
1140
|
-
...(this.options.config.interrupt ? { interruptSource: 'owner' } : {}),
|
|
1153
|
+
...this.ownerPromptPolicy(),
|
|
1141
1154
|
origin: { kind: 'owner', requestId,
|
|
1142
1155
|
...(group.caption ? { displayText: String(group.caption.text ?? '') } : {}) },
|
|
1143
1156
|
});
|
|
@@ -1263,8 +1276,7 @@ export class OwnerChannel {
|
|
|
1263
1276
|
const activityCursor = this.latestEventSeq(this.options.session.eventsSince(0));
|
|
1264
1277
|
try {
|
|
1265
1278
|
queued = await queueSessionPrompt(this.options.session, this.ownerPrompt(sender, text, wireId), {
|
|
1266
|
-
|
|
1267
|
-
...(this.options.config.interrupt ? { interruptSource: 'owner' } : {}),
|
|
1279
|
+
...this.ownerPromptPolicy(),
|
|
1268
1280
|
origin: { kind: 'owner', requestId, displayText: text },
|
|
1269
1281
|
});
|
|
1270
1282
|
}
|
|
@@ -1309,6 +1321,14 @@ export class OwnerChannel {
|
|
|
1309
1321
|
this.completionTasks.add(task);
|
|
1310
1322
|
return true;
|
|
1311
1323
|
}
|
|
1324
|
+
ownerPromptPolicy() {
|
|
1325
|
+
if (this.options.startupPending?.())
|
|
1326
|
+
return { interrupt: false, steer: true };
|
|
1327
|
+
return {
|
|
1328
|
+
interrupt: this.options.config.interrupt,
|
|
1329
|
+
...(this.options.config.interrupt ? { interruptSource: 'owner' } : {}),
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1312
1332
|
/**
|
|
1313
1333
|
* Deterministic command path: the message never becomes an agent prompt.
|
|
1314
1334
|
* Authorization already happened — the managed-agent relay branch and the
|
|
@@ -1378,6 +1398,7 @@ export class OwnerChannel {
|
|
|
1378
1398
|
await this.fleetOps.provisionTask(task.task_id);
|
|
1379
1399
|
return outcome;
|
|
1380
1400
|
}),
|
|
1401
|
+
observeTaskReadiness: taskId => new TaskRoomApplicationService(this.options.configPath).observeTaskReadiness(taskId),
|
|
1381
1402
|
taskProvisioningOutcome: taskId => new TaskRoomApplicationService(this.options.configPath).taskProvisioningOutcome(taskId),
|
|
1382
1403
|
listTasks: filter => new TaskRoomApplicationService(this.options.configPath).listTasks(filter),
|
|
1383
1404
|
groupedTasks: filter => new TaskRoomApplicationService(this.options.configPath).groupedTasks(filter),
|
|
@@ -1762,6 +1783,13 @@ export class OwnerChannel {
|
|
|
1762
1783
|
const retrieved = unread.length
|
|
1763
1784
|
? parseRetrievedAttachments(await this.client.getFiles(unread.map(file => file.wireId)), unread)
|
|
1764
1785
|
: [];
|
|
1786
|
+
// getFiles returns daemon-local paths, which need not exist in Fleet's
|
|
1787
|
+
// filesystem (for example with an HTTP daemon in a container). Fetch
|
|
1788
|
+
// through the bound client and keep the daemon's integrity metadata for
|
|
1789
|
+
// admission below; never trust or remap the returned filesystem path.
|
|
1790
|
+
for (const file of retrieved) {
|
|
1791
|
+
file.path = await writeRecoveredAttachment(requestDir, file.wireId, await this.client.fetchFile(file.wireId));
|
|
1792
|
+
}
|
|
1765
1793
|
for (const file of historyRecovered) {
|
|
1766
1794
|
if (!group.recovery)
|
|
1767
1795
|
throw new Error('unexpected read attachment without recovery route');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TaskReadinessIssue } from '../rooms-tasks/live-readiness.js';
|
|
1
2
|
import type { InterruptOutcome, SessionEvent, SessionSnapshot } from '../session/types.js';
|
|
2
3
|
import type { RoomOrchestrationRecord, TaskOutcome, TaskRecord, TaskTerminalIntent, TaskListRecord } from '../rooms-tasks/types.js';
|
|
3
4
|
import { type OwnerCommentsState } from './notices.js';
|
|
@@ -58,6 +59,9 @@ export interface OwnerCommandContext {
|
|
|
58
59
|
terminalTask(taskId: string, kind: TaskTerminalIntent['kind'], outcome?: TaskOutcome): Promise<void>;
|
|
59
60
|
createTask(input: Omit<CreateTaskRequest, 'actor'>): Promise<TaskRecord>;
|
|
60
61
|
startTask(taskId: string): Promise<TaskProvisioningOutcome | TaskRecord>;
|
|
62
|
+
observeTaskReadiness?(taskId: string): Promise<{
|
|
63
|
+
state: 'ready';
|
|
64
|
+
} | TaskReadinessIssue>;
|
|
61
65
|
taskProvisioningOutcome?(taskId: string): TaskProvisioningOutcome;
|
|
62
66
|
listTasks(filter?: {
|
|
63
67
|
state?: TaskState | TaskState[];
|
|
@@ -47,6 +47,11 @@ function isKnownOwnerRoomState(message) {
|
|
|
47
47
|
].some(pattern => pattern.test(message));
|
|
48
48
|
}
|
|
49
49
|
function ownerTaskFailure(error) {
|
|
50
|
+
if (error instanceof TaskRoomApplicationError && error.code === 'task_not_ready')
|
|
51
|
+
return {
|
|
52
|
+
kind: 'state', detail: error.message,
|
|
53
|
+
action: 'Ask Fleet Coordinator to inspect room and member status and choose supported recovery.',
|
|
54
|
+
};
|
|
50
55
|
if (error instanceof TaskListError)
|
|
51
56
|
return {
|
|
52
57
|
kind: error.code === 'list_not_found' ? 'not_found'
|
|
@@ -301,6 +306,7 @@ export const ownerCommands = [
|
|
|
301
306
|
const trailingLines = argLines.slice(1).join('\n').trim() || undefined;
|
|
302
307
|
const showTask = async (id) => {
|
|
303
308
|
const { task: t } = ctx.getTask(id);
|
|
309
|
+
const readiness = t.state === 'active' ? await ctx.observeTaskReadiness?.(id) : undefined;
|
|
304
310
|
await ctx.reply(renderMarkdownResult({
|
|
305
311
|
icon: '📋', title: 'Task details',
|
|
306
312
|
fields: [
|
|
@@ -308,6 +314,8 @@ export const ownerCommands = [
|
|
|
308
314
|
{ label: 'Title', value: t.title },
|
|
309
315
|
{ label: 'Status', value: taskStatus(t.state), kind: 'markdown' },
|
|
310
316
|
{ label: 'List', value: t.list_name ?? 'default', kind: 'code' },
|
|
317
|
+
...(readiness ? [{ label: 'Readiness', value: readiness.state },
|
|
318
|
+
...('reason' in readiness ? [{ label: 'Reason', value: readiness.reason }] : [])] : []),
|
|
311
319
|
...(t.blocked ? [{ label: 'Blocked', value: t.blocked.reason }] : []),
|
|
312
320
|
...(t.template ? [{ label: 'Template', value: `${t.template.name}@${t.template.version}`, kind: 'code' }] : []),
|
|
313
321
|
...(t.room_id ? [{ label: 'Room', value: t.room_id, kind: 'code' }] : []),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { redactErasedContent, writePrivacyFilteredLedger } from '../erased-resources.js';
|
|
1
2
|
import { createHash } from 'node:crypto';
|
|
2
3
|
import { chmodSync, existsSync, readFileSync } from 'node:fs';
|
|
3
|
-
import { replaceFileAtomically } from '../atomic-file.js';
|
|
4
4
|
import { lifecycleEventDigestBasis, validateFleetAuditPresentations, } from '../fleet-command-audit.js';
|
|
5
5
|
const DIGEST = /^[a-f0-9]{64}$/u;
|
|
6
6
|
const LIMIT = 5_000;
|
|
@@ -63,7 +63,7 @@ export class FleetLifecycleOutbox {
|
|
|
63
63
|
}
|
|
64
64
|
pending() {
|
|
65
65
|
this.assertHealthy();
|
|
66
|
-
return this.entries.filter(entry => entry.delivery === 'pending').map(entry => structuredClone(entry));
|
|
66
|
+
return redactErasedContent(this.entries).filter(entry => entry.delivery === 'pending').map(entry => structuredClone(entry));
|
|
67
67
|
}
|
|
68
68
|
finish(digest, delivery) {
|
|
69
69
|
this.assertHealthy();
|
|
@@ -82,6 +82,7 @@ export class FleetLifecycleOutbox {
|
|
|
82
82
|
throw new Error(this.corruptReason);
|
|
83
83
|
}
|
|
84
84
|
persist() {
|
|
85
|
-
|
|
85
|
+
this.entries = redactErasedContent(this.entries);
|
|
86
|
+
writePrivacyFilteredLedger(this.path, { version: 1, entries: this.entries });
|
|
86
87
|
}
|
|
87
88
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
|
-
import { extname
|
|
4
|
-
import { readDaemonConfig, resolveEndpoint } from '../monitor.js';
|
|
3
|
+
import { extname } from 'node:path';
|
|
5
4
|
import { readClientProfile } from '../client-profile.js';
|
|
6
5
|
import { OursError, attachOursClient, } from '@ours.network/sdk/client';
|
|
7
6
|
/** Any failure of a daemon operation. Never carries a message body or a token. */
|
|
@@ -131,24 +130,10 @@ export class OursSdkClient {
|
|
|
131
130
|
return;
|
|
132
131
|
const environment = { ...process.env, ...this.env };
|
|
133
132
|
const profile = readClientProfile(environment);
|
|
134
|
-
|
|
135
|
-
// daemon capability before reading the client's protected token delivery file.
|
|
136
|
-
const endpoint = !profile && environment.OURS_DAEMON_ID
|
|
137
|
-
? resolveEndpoint(environment, false) : undefined;
|
|
138
|
-
const token = endpoint ? environment.OURS_API_TOKEN?.trim() || readDaemonConfig(environment).apiToken : undefined;
|
|
139
|
-
const options = profile ? {
|
|
133
|
+
const options = {
|
|
140
134
|
endpoint: profile.endpoint, expectedInstanceId: profile.expectedInstanceId,
|
|
141
135
|
credentialPath: profile.credentialPath,
|
|
142
136
|
sessionMode: 'external', leaseToken: this.leaseToken, env: {},
|
|
143
|
-
} : endpoint ? {
|
|
144
|
-
endpoint: endpoint.origin, expectedInstanceId: environment.OURS_DAEMON_ID,
|
|
145
|
-
sessionMode: 'external', leaseToken: this.leaseToken,
|
|
146
|
-
...(token ? { token } : { credentialPath: join(endpoint.stateDir, 'daemon-token') }),
|
|
147
|
-
} : {
|
|
148
|
-
env: environment,
|
|
149
|
-
leaseToken: this.leaseToken,
|
|
150
|
-
clientPid: process.pid,
|
|
151
|
-
fetch: notificationDeadlineFetch(this.deps.fetch ?? globalThis.fetch, this.deps.notificationRequestDeadlineMs ?? NOTIFICATION_REQUEST_DEADLINE_MS),
|
|
152
137
|
};
|
|
153
138
|
this.client = await (this.deps.attachClient?.(options) ?? attachOursClient(options));
|
|
154
139
|
this.terminalReleasePending = true;
|
package/dist/paths.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export declare const home: () => string;
|
|
|
11
11
|
*/
|
|
12
12
|
export declare function deriveXdgRuntimeDir(env?: NodeJS.ProcessEnv, uid?: number | undefined, exists?: (p: string) => boolean): string | undefined;
|
|
13
13
|
export declare const stateRoot: () => string;
|
|
14
|
+
/** The Owner invitation file that tunnel setup writes itself after enrollment. */
|
|
15
|
+
export declare const workspaceOwnerInvite: () => string;
|
|
14
16
|
export declare const agentsRoot: () => string;
|
|
15
17
|
export declare const tmpRoot: () => string;
|
|
16
18
|
export declare const logsRoot: () => string;
|
package/dist/paths.js
CHANGED
|
@@ -21,6 +21,8 @@ export function deriveXdgRuntimeDir(env = process.env, uid = process.getuid?.(),
|
|
|
21
21
|
return env.XDG_RUNTIME_DIR;
|
|
22
22
|
}
|
|
23
23
|
export const stateRoot = () => join(home(), '.ours-fleet');
|
|
24
|
+
/** The Owner invitation file that tunnel setup writes itself after enrollment. */
|
|
25
|
+
export const workspaceOwnerInvite = () => join(stateRoot(), 'workspace', 'owner.invite');
|
|
24
26
|
export const agentsRoot = () => join(stateRoot(), 'agents');
|
|
25
27
|
export const tmpRoot = () => join(stateRoot(), 'tmp');
|
|
26
28
|
export const logsRoot = () => join(stateRoot(), 'logs');
|
package/dist/preset-migration.js
CHANGED
|
@@ -67,10 +67,21 @@ const V5_GENERATED_ROLE_DEFAULT_FINGERPRINTS = Object.freeze({
|
|
|
67
67
|
'agent_templates/Developer.yaml': 'fdd953aab72d042e62780dd273598745aae59521b39edf4876d7676bb7189c83',
|
|
68
68
|
'agent_templates/Critic.yaml': '3e98a28f17876061cfadf984351aa59fdbe19629e514eff52c74546545c64a91',
|
|
69
69
|
});
|
|
70
|
+
// The packaged Coordinator role of revisions 8 and 9 addressed a named owner channel that a
|
|
71
|
+
// fresh installation does not have. Untouched copies adopt the current role; edited ones stay.
|
|
72
|
+
const OWNER_CHANNEL_COORDINATOR_FINGERPRINTS = Object.freeze({
|
|
73
|
+
'roles/Coordinator.yaml': [
|
|
74
|
+
'8c8ad28d9bb9ddf9dba8c5c9c7859f25edd8fa10ebf833e2340124a6e49583ec',
|
|
75
|
+
'f0d8b169ad0a18051b57e5280b93cf01e05faa5c12f4e12bdf89973edf2ef58a',
|
|
76
|
+
],
|
|
77
|
+
});
|
|
70
78
|
const CURRENT_ROLE_DEFAULTS = new Set([
|
|
71
79
|
'roles/Coordinator.yaml', 'roles/LocalCoordinator.yaml', 'roles/Developer.yaml', 'roles/Critic.yaml',
|
|
80
|
+
'roles/Engineer.yaml',
|
|
72
81
|
'agent_templates/LocalCoordinator.yaml', 'agent_templates/Developer.yaml', 'agent_templates/Critic.yaml',
|
|
82
|
+
'agent_templates/Engineer.yaml',
|
|
73
83
|
'room_templates/single.yaml', 'room_templates/pair.yaml', 'room_templates/team.yaml',
|
|
84
|
+
'room_templates/engineering.yaml',
|
|
74
85
|
'agents/FleetCoordinator.yaml',
|
|
75
86
|
]);
|
|
76
87
|
export const legacyStarterMigrationManifest = Object.freeze({
|
|
@@ -193,6 +204,7 @@ export function migratePackagedRoleDefaults(configuration, options = {}, hooks =
|
|
|
193
204
|
...Object.keys(V3_ROLE_DEFAULT_FINGERPRINTS),
|
|
194
205
|
...Object.keys(V4_ROLE_DEFAULT_FINGERPRINTS),
|
|
195
206
|
...Object.keys(V5_ROLE_DEFAULT_FINGERPRINTS),
|
|
207
|
+
...Object.keys(OWNER_CHANNEL_COORDINATOR_FINGERPRINTS),
|
|
196
208
|
]);
|
|
197
209
|
for (const relative of known) {
|
|
198
210
|
const path = join(root, relative);
|
|
@@ -206,6 +218,7 @@ export function migratePackagedRoleDefaults(configuration, options = {}, hooks =
|
|
|
206
218
|
V4_GENERATED_ROLE_DEFAULT_FINGERPRINTS[relative],
|
|
207
219
|
V5_ROLE_DEFAULT_FINGERPRINTS[relative],
|
|
208
220
|
V5_GENERATED_ROLE_DEFAULT_FINGERPRINTS[relative],
|
|
221
|
+
...(OWNER_CHANNEL_COORDINATOR_FINGERPRINTS[relative] ?? []),
|
|
209
222
|
].some(expected => expected !== undefined && actual === expected);
|
|
210
223
|
if (recognized)
|
|
211
224
|
(CURRENT_ROLE_DEFAULTS.has(relative) ? replacements : removals).push(path);
|
|
@@ -258,6 +271,18 @@ export function migratePackagedRoleDefaults(configuration, options = {}, hooks =
|
|
|
258
271
|
unlinkSync(target);
|
|
259
272
|
copyFileSync(join(source, relative), target, constants.COPYFILE_EXCL);
|
|
260
273
|
chmodSync(target, 0o600);
|
|
274
|
+
// New Engineer seats use the existing Developer's selected brain. This
|
|
275
|
+
// preserves wizard/custom provider choices without copying permissions.
|
|
276
|
+
if (relative === 'agent_templates/Engineer.yaml' && classified.additions.includes(path)) {
|
|
277
|
+
const developer = join(root, 'agent_templates/Developer.yaml');
|
|
278
|
+
if (existsSync(developer)) {
|
|
279
|
+
const value = parseFleetDocument(developer, readFileSync(developer, 'utf8'), 'strict').value;
|
|
280
|
+
if (value.brain !== undefined && JSON.stringify(value.brain) !== JSON.stringify({ ref: 'claude-default' })) {
|
|
281
|
+
const bytes = readFileSync(target, 'utf8').replace('brain: { ref: claude-default }', () => `brain: ${JSON.stringify(value.brain)}`);
|
|
282
|
+
writeFileSync(target, bytes, { mode: 0o600 });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
261
286
|
fsyncPath(target);
|
|
262
287
|
}
|
|
263
288
|
for (const kind of ['roles', 'agent_templates', 'room_templates', 'agents'])
|
package/dist/resolved-plan.js
CHANGED
|
@@ -103,6 +103,7 @@ export function resolvedRolePlan(role, manifestDir = dirname(role.sourceFile)) {
|
|
|
103
103
|
isolation: role.isolation ?? null,
|
|
104
104
|
worklog: role.worklog ?? null,
|
|
105
105
|
authProxy: role.auth_proxy ?? null,
|
|
106
|
+
managedCli: role.managed_cli ?? [],
|
|
106
107
|
oversee: role.oversee ?? [],
|
|
107
108
|
harnessOptions: redactSensitive(role.harness_options ?? null),
|
|
108
109
|
env: {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { FleetConfig } from '../config.js';
|
|
2
|
+
import { TaskStateError } from './task-state.js';
|
|
3
|
+
import { provisionMembers } from './provision.js';
|
|
4
|
+
import type { CoworkAdapter } from './cowork-adapter.js';
|
|
5
|
+
export declare class MemberAdditionRejected extends TaskStateError {
|
|
6
|
+
readonly accepted = false;
|
|
7
|
+
}
|
|
8
|
+
export interface AddMemberRequest {
|
|
9
|
+
requestId: string;
|
|
10
|
+
slot: string;
|
|
11
|
+
role: string;
|
|
12
|
+
brain: string;
|
|
13
|
+
agentTemplate: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function memberAddition(taskId: string, requestId: string): {
|
|
16
|
+
requestId: string;
|
|
17
|
+
taskId: string;
|
|
18
|
+
roleId: string;
|
|
19
|
+
state: "running" | "succeeded" | "attention" | "failed";
|
|
20
|
+
error?: string;
|
|
21
|
+
} | {
|
|
22
|
+
state: string;
|
|
23
|
+
error: string;
|
|
24
|
+
requestId: string;
|
|
25
|
+
taskId: string;
|
|
26
|
+
roleId: string;
|
|
27
|
+
};
|
|
28
|
+
export declare function requestMemberAddition(input: {
|
|
29
|
+
taskId: string;
|
|
30
|
+
request: AddMemberRequest;
|
|
31
|
+
cfg: FleetConfig;
|
|
32
|
+
cowork: CoworkAdapter;
|
|
33
|
+
configPath?: string;
|
|
34
|
+
binPath: string;
|
|
35
|
+
provision?: typeof provisionMembers;
|
|
36
|
+
}): Promise<{
|
|
37
|
+
requestId: string;
|
|
38
|
+
taskId: string;
|
|
39
|
+
roleId: string;
|
|
40
|
+
state: "running" | "succeeded" | "attention" | "failed";
|
|
41
|
+
error?: string;
|
|
42
|
+
} | {
|
|
43
|
+
state: string;
|
|
44
|
+
error: string;
|
|
45
|
+
requestId: string;
|
|
46
|
+
taskId: string;
|
|
47
|
+
roleId: string;
|
|
48
|
+
}>;
|