@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,182 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { FleetError } from '../application/errors.js';
|
|
4
|
+
import { CodexAppServer, profileEnv } from './cli.js';
|
|
5
|
+
export const LOGIN_TTL_MS = 10 * 60_000;
|
|
6
|
+
export const LOGIN_URL_ALLOWLIST = {
|
|
7
|
+
claude: ['claude.com', 'claude.ai', 'platform.claude.com', 'console.anthropic.com'],
|
|
8
|
+
codex: ['auth.openai.com'],
|
|
9
|
+
};
|
|
10
|
+
export function allowedLoginUrl(provider, raw) {
|
|
11
|
+
let url;
|
|
12
|
+
try {
|
|
13
|
+
url = new URL(raw);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
if (url.protocol !== 'https:' || url.username || url.password || url.port)
|
|
19
|
+
return undefined;
|
|
20
|
+
return LOGIN_URL_ALLOWLIST[provider].includes(url.hostname) ? url.toString() : undefined;
|
|
21
|
+
}
|
|
22
|
+
/** Claude paste codes are opaque `code#state` strings; refuse anything else. */
|
|
23
|
+
const PASTE_CODE_RE = /^[A-Za-z0-9._~#-]{8,512}$/;
|
|
24
|
+
const URL_RE = /https:\/\/[^\s\x07\x1b"'<>\\]+/;
|
|
25
|
+
class BaseLogin {
|
|
26
|
+
provider;
|
|
27
|
+
owner;
|
|
28
|
+
kind;
|
|
29
|
+
loginId = randomBytes(16).toString('hex');
|
|
30
|
+
expiresAt;
|
|
31
|
+
state = 'starting';
|
|
32
|
+
url;
|
|
33
|
+
userCode;
|
|
34
|
+
error;
|
|
35
|
+
done;
|
|
36
|
+
finish;
|
|
37
|
+
timer;
|
|
38
|
+
constructor(provider, owner, kind, now = Date.now()) {
|
|
39
|
+
this.provider = provider;
|
|
40
|
+
this.owner = owner;
|
|
41
|
+
this.kind = kind;
|
|
42
|
+
this.expiresAt = now + LOGIN_TTL_MS;
|
|
43
|
+
this.done = new Promise(resolve => { this.finish = resolve; });
|
|
44
|
+
this.timer = setTimeout(() => this.end('expired', 'login timed out'), LOGIN_TTL_MS);
|
|
45
|
+
this.timer.unref?.();
|
|
46
|
+
}
|
|
47
|
+
view() {
|
|
48
|
+
const live = this.state === 'starting' || this.state === 'awaiting_user';
|
|
49
|
+
return {
|
|
50
|
+
loginId: this.loginId, provider: this.provider, kind: this.kind, state: this.state,
|
|
51
|
+
url: live ? this.url : undefined,
|
|
52
|
+
userCode: this.state === 'awaiting_user' ? this.userCode : undefined,
|
|
53
|
+
expiresAt: new Date(this.expiresAt).toISOString(),
|
|
54
|
+
error: this.error,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
cancel() { this.end('cancelled'); }
|
|
58
|
+
end(state, error) {
|
|
59
|
+
if (['succeeded', 'failed', 'cancelled', 'expired'].includes(this.state))
|
|
60
|
+
return;
|
|
61
|
+
this.state = state;
|
|
62
|
+
this.error = error?.slice(0, 200);
|
|
63
|
+
this.userCode = undefined;
|
|
64
|
+
clearTimeout(this.timer);
|
|
65
|
+
this.stop();
|
|
66
|
+
this.finish(state === 'succeeded');
|
|
67
|
+
}
|
|
68
|
+
/** Called by the service after verifying the new home. */
|
|
69
|
+
complete(ok, error) { this.end(ok ? 'succeeded' : 'failed', error); }
|
|
70
|
+
}
|
|
71
|
+
/** `claude auth login` inside script(1): prints an authorize URL, then reads a paste code. */
|
|
72
|
+
export class ClaudeLogin extends BaseLogin {
|
|
73
|
+
child;
|
|
74
|
+
output = '';
|
|
75
|
+
submitted = false;
|
|
76
|
+
/** Resolves with the CLI exit status once it exits. */
|
|
77
|
+
exited;
|
|
78
|
+
constructor(owner, bins, home, profileId) {
|
|
79
|
+
super('claude', owner, 'paste_code');
|
|
80
|
+
const child = spawn(bins.script, ['-qefc', `${shellQuote(bins.claude)} auth login`, '/dev/null'], {
|
|
81
|
+
env: profileEnv('CLAUDE_CONFIG_DIR', profileId, home), stdio: ['pipe', 'pipe', 'pipe'],
|
|
82
|
+
});
|
|
83
|
+
this.child = child;
|
|
84
|
+
child.stdin.on('error', () => { });
|
|
85
|
+
const onData = (chunk) => {
|
|
86
|
+
if (this.output.length < 64 * 1024)
|
|
87
|
+
this.output += chunk.toString('utf8');
|
|
88
|
+
if (!this.url) {
|
|
89
|
+
const match = URL_RE.exec(this.output);
|
|
90
|
+
if (match) {
|
|
91
|
+
const url = allowedLoginUrl('claude', match[0]);
|
|
92
|
+
if (!url) {
|
|
93
|
+
this.end('failed', 'the CLI returned an unexpected sign-in address');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.url = url;
|
|
97
|
+
this.state = 'awaiting_user';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Claude asks for Enter after success; the answer carries no secret.
|
|
101
|
+
if (this.submitted && /press enter/i.test(this.output))
|
|
102
|
+
child.stdin.write('\r');
|
|
103
|
+
};
|
|
104
|
+
child.stdout.on('data', onData);
|
|
105
|
+
child.stderr.on('data', onData);
|
|
106
|
+
this.exited = new Promise(resolve => {
|
|
107
|
+
child.once('error', () => { this.end('failed', 'could not start the sign-in'); resolve(null); });
|
|
108
|
+
child.once('close', code => resolve(code));
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
submitCode(code) {
|
|
112
|
+
if (this.state !== 'awaiting_user' || this.submitted)
|
|
113
|
+
throw new FleetError('conflict', 'this login is not waiting for a code');
|
|
114
|
+
if (!PASTE_CODE_RE.test(code))
|
|
115
|
+
throw new FleetError('invalid_request', 'that does not look like a sign-in code');
|
|
116
|
+
this.submitted = true;
|
|
117
|
+
this.state = 'verifying';
|
|
118
|
+
this.output = '';
|
|
119
|
+
this.child?.stdin.write(`${code}\r`);
|
|
120
|
+
}
|
|
121
|
+
stop() { this.child?.kill('SIGTERM'); }
|
|
122
|
+
}
|
|
123
|
+
/** Codex device-code login through the app-server's supported account API. */
|
|
124
|
+
export class CodexDeviceLogin extends BaseLogin {
|
|
125
|
+
server;
|
|
126
|
+
codexLoginId;
|
|
127
|
+
/** Resolves true when the app-server reports a successful login. */
|
|
128
|
+
completed;
|
|
129
|
+
constructor(owner, bins, home, profileId) {
|
|
130
|
+
super('codex', owner, 'device_code');
|
|
131
|
+
this.completed = (async () => {
|
|
132
|
+
try {
|
|
133
|
+
this.server = await CodexAppServer.start(bins.codex, profileEnv('CODEX_HOME', profileId, home));
|
|
134
|
+
if (this.state !== 'starting') {
|
|
135
|
+
this.server.close();
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
const completion = new Promise(resolve => {
|
|
139
|
+
this.server.onNotification((method, params) => {
|
|
140
|
+
const p = params;
|
|
141
|
+
if (method === 'account/login/completed' && (!p.loginId || p.loginId === this.codexLoginId)) {
|
|
142
|
+
if (!p.success)
|
|
143
|
+
this.error = 'sign-in was not completed';
|
|
144
|
+
resolve(Boolean(p.success));
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
const started = await this.server.call('account/login/start', { type: 'chatgptDeviceCode' });
|
|
149
|
+
if (this.state !== 'starting') {
|
|
150
|
+
this.server.close();
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
const url = started.verificationUrl && allowedLoginUrl('codex', started.verificationUrl);
|
|
154
|
+
if (!url || !started.userCode) {
|
|
155
|
+
this.end('failed', 'the CLI returned an unexpected sign-in address');
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
this.codexLoginId = started.loginId;
|
|
159
|
+
this.url = url;
|
|
160
|
+
this.userCode = String(started.userCode).slice(0, 32);
|
|
161
|
+
this.state = 'awaiting_user';
|
|
162
|
+
const ok = await Promise.race([completion, this.done.then(() => false)]);
|
|
163
|
+
if (this.state === 'awaiting_user')
|
|
164
|
+
this.state = 'verifying';
|
|
165
|
+
return ok;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
this.end('failed', 'could not start the sign-in');
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
})();
|
|
172
|
+
}
|
|
173
|
+
submitCode() { throw new FleetError('invalid_request', 'device-code logins need no code from Fleet'); }
|
|
174
|
+
stop() {
|
|
175
|
+
if (this.server && this.codexLoginId && this.state === 'cancelled')
|
|
176
|
+
void this.server.call('account/login/cancel', { loginId: this.codexLoginId }, 3_000).catch(() => { });
|
|
177
|
+
setTimeout(() => this.server?.close(), 500).unref?.();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function shellQuote(value) {
|
|
181
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
182
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persist the Claude subscription rate-limit snapshot an agent session just
|
|
3
|
+
* received (SDK `rate_limit_event`, forwarded by claude-agent-acp as
|
|
4
|
+
* `usage_update._meta["_claude/rateLimit"]`). Only numbers and window names
|
|
5
|
+
* are kept. The web backend attributes it to the agent's pinned profile.
|
|
6
|
+
*/
|
|
7
|
+
export declare function recordClaudeRateLimit(stateDir: string, info: unknown, now?: Date): boolean;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { replaceFileAtomically } from '../atomic-file.js';
|
|
3
|
+
import { OBSERVED_USAGE_FILE } from './store.js';
|
|
4
|
+
const WINDOW_LABELS = {
|
|
5
|
+
five_hour: { label: '5h', mins: 300 },
|
|
6
|
+
seven_day: { label: 'weekly', mins: 10_080 },
|
|
7
|
+
seven_day_opus: { label: 'weekly · Opus', mins: 10_080 },
|
|
8
|
+
seven_day_sonnet: { label: 'weekly · Sonnet', mins: 10_080 },
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Persist the Claude subscription rate-limit snapshot an agent session just
|
|
12
|
+
* received (SDK `rate_limit_event`, forwarded by claude-agent-acp as
|
|
13
|
+
* `usage_update._meta["_claude/rateLimit"]`). Only numbers and window names
|
|
14
|
+
* are kept. The web backend attributes it to the agent's pinned profile.
|
|
15
|
+
*/
|
|
16
|
+
export function recordClaudeRateLimit(stateDir, info, now = new Date()) {
|
|
17
|
+
if (!info || typeof info !== 'object')
|
|
18
|
+
return false;
|
|
19
|
+
const raw = info;
|
|
20
|
+
const entries = raw.unifiedWindows && typeof raw.unifiedWindows === 'object'
|
|
21
|
+
? Object.entries(raw.unifiedWindows)
|
|
22
|
+
: typeof raw.rateLimitType === 'string' ? [[raw.rateLimitType, raw]] : [];
|
|
23
|
+
const windows = [];
|
|
24
|
+
for (const [id, w] of entries) {
|
|
25
|
+
if (!/^[a-z0-9_]{1,40}$/.test(id) || !w || typeof w.utilization !== 'number' || !Number.isFinite(w.utilization))
|
|
26
|
+
continue;
|
|
27
|
+
const known = WINDOW_LABELS[id];
|
|
28
|
+
windows.push({
|
|
29
|
+
id, label: known?.label ?? id.replace(/_/g, ' '),
|
|
30
|
+
usedPercent: Math.round(Math.max(0, w.utilization) * 1000) / 10,
|
|
31
|
+
windowMins: known?.mins,
|
|
32
|
+
resetsAt: typeof w.resetsAt === 'number' ? new Date(w.resetsAt * 1000).toISOString() : undefined,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (!windows.length)
|
|
36
|
+
return false;
|
|
37
|
+
try {
|
|
38
|
+
replaceFileAtomically(join(stateDir, OBSERVED_USAGE_FILE), `${JSON.stringify({ windows, observedAt: now.toISOString(), source: 'agent' })}\n`, 0o600);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface ClaudeProbeResult {
|
|
2
|
+
rateLimit?: unknown;
|
|
3
|
+
exhausted: boolean;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Ask Haiku for one short reply in the selected Claude home. The JSONL stream
|
|
7
|
+
* can contain a rate_limit_event; prompts, replies and CLI stderr are discarded.
|
|
8
|
+
* This runs only after an explicit UI action and has a time and spend bound.
|
|
9
|
+
*/
|
|
10
|
+
export declare function probeClaudeRateLimit(bin: string, profileId: string, timeoutMs?: number): Promise<ClaudeProbeResult | undefined>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { mkdirSync } from 'node:fs';
|
|
3
|
+
import { profileEnv } from './cli.js';
|
|
4
|
+
import { profileHome, subscriptionsRoot } from './store.js';
|
|
5
|
+
const LIMIT_ERROR = /\b(?:hit (?:your |the )?limit|reached (?:your |the )?(?:usage )?limit|usage limits? (?:reached|exceeded)|rate limits? (?:reached|exceeded)|rate_limit_error|quota exceeded|out of (?:usage|credits)|too many requests|429)\b/i;
|
|
6
|
+
/**
|
|
7
|
+
* Ask Haiku for one short reply in the selected Claude home. The JSONL stream
|
|
8
|
+
* can contain a rate_limit_event; prompts, replies and CLI stderr are discarded.
|
|
9
|
+
* This runs only after an explicit UI action and has a time and spend bound.
|
|
10
|
+
*/
|
|
11
|
+
export function probeClaudeRateLimit(bin, profileId, timeoutMs = 75_000) {
|
|
12
|
+
const cwd = subscriptionsRoot();
|
|
13
|
+
mkdirSync(cwd, { recursive: true, mode: 0o700 });
|
|
14
|
+
return new Promise(resolve => {
|
|
15
|
+
const child = spawn(bin, [
|
|
16
|
+
'-p', 'Reply OK.', '--model', 'haiku', '--output-format', 'stream-json', '--verbose',
|
|
17
|
+
'--no-session-persistence', '--strict-mcp-config', '--tools', '',
|
|
18
|
+
'--system-prompt', 'Reply OK.', '--max-budget-usd', '0.05',
|
|
19
|
+
], {
|
|
20
|
+
cwd, env: profileEnv('CLAUDE_CONFIG_DIR', profileId, profileHome('claude', profileId)),
|
|
21
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
22
|
+
});
|
|
23
|
+
let pending = '';
|
|
24
|
+
let rateLimit;
|
|
25
|
+
let exhausted = false;
|
|
26
|
+
let done = false;
|
|
27
|
+
const finish = (value) => {
|
|
28
|
+
if (done)
|
|
29
|
+
return;
|
|
30
|
+
done = true;
|
|
31
|
+
clearTimeout(timer);
|
|
32
|
+
resolve(value);
|
|
33
|
+
};
|
|
34
|
+
const timer = setTimeout(() => { child.kill('SIGKILL'); finish(); }, timeoutMs);
|
|
35
|
+
child.stdout.on('data', chunk => {
|
|
36
|
+
pending += String(chunk);
|
|
37
|
+
if (pending.length > 64 * 1024 && !pending.includes('\n')) {
|
|
38
|
+
child.kill('SIGKILL');
|
|
39
|
+
finish();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
for (let end = pending.indexOf('\n'); end >= 0; end = pending.indexOf('\n')) {
|
|
43
|
+
const line = pending.slice(0, end);
|
|
44
|
+
pending = pending.slice(end + 1);
|
|
45
|
+
if (line.length > 64 * 1024)
|
|
46
|
+
continue;
|
|
47
|
+
try {
|
|
48
|
+
const event = JSON.parse(line);
|
|
49
|
+
if (event.type === 'rate_limit_event')
|
|
50
|
+
rateLimit = event.rate_limit_info ?? event.rateLimitInfo ?? event.rate_limit ?? event.rateLimit ?? event;
|
|
51
|
+
if ((event.type === 'result' && event.is_error === true) || event.type === 'error') {
|
|
52
|
+
const error = event.error && typeof event.error === 'object' ? event.error : undefined;
|
|
53
|
+
const message = typeof event.result === 'string' ? event.result
|
|
54
|
+
: typeof event.error === 'string' ? event.error : error?.message;
|
|
55
|
+
if (typeof message === 'string' && LIMIT_ERROR.test(message.slice(0, 4000)))
|
|
56
|
+
exhausted = true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch { /* Other CLI output is never retained. */ }
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
// Some CLI failures are reported only on stderr. Inspect a small rolling
|
|
63
|
+
// fragment for a limit signal; never retain or return the raw text.
|
|
64
|
+
let stderrTail = '';
|
|
65
|
+
child.stderr.on('data', chunk => {
|
|
66
|
+
stderrTail = (stderrTail + String(chunk)).slice(-4000);
|
|
67
|
+
if (LIMIT_ERROR.test(stderrTail))
|
|
68
|
+
exhausted = true;
|
|
69
|
+
});
|
|
70
|
+
child.once('error', () => finish());
|
|
71
|
+
child.once('close', () => finish(rateLimit !== undefined || exhausted ? { rateLimit, exhausted } : undefined));
|
|
72
|
+
});
|
|
73
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { type SubscriptionBinaries, type UsageWindow } from './cli.js';
|
|
2
|
+
import { type LoginHandle, type LoginView } from './login.js';
|
|
3
|
+
import { type SubscriptionProvider } from './store.js';
|
|
4
|
+
export type HealthState = 'ok' | 'expiring' | 'expired' | 'signed_out' | 'error' | 'unknown';
|
|
5
|
+
export interface ProfileHealth {
|
|
6
|
+
state: HealthState;
|
|
7
|
+
checkedAt?: string;
|
|
8
|
+
detail?: string;
|
|
9
|
+
/** When the stored login stops refreshing (Claude, observed field). */
|
|
10
|
+
loginExpiresAt?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ProfileUsage {
|
|
13
|
+
windows: UsageWindow[];
|
|
14
|
+
observedAt?: string;
|
|
15
|
+
/** `pull`: asked the provider. `agent`: last value an agent session reported. */
|
|
16
|
+
source: 'pull' | 'agent' | 'probe' | 'none';
|
|
17
|
+
/** A probe received an explicit limit error, even if no percentages were sent. */
|
|
18
|
+
exhausted?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface ProfileView {
|
|
21
|
+
id: string;
|
|
22
|
+
label: string;
|
|
23
|
+
active: boolean;
|
|
24
|
+
account?: {
|
|
25
|
+
email?: string;
|
|
26
|
+
plan?: string;
|
|
27
|
+
org?: string;
|
|
28
|
+
};
|
|
29
|
+
health: ProfileHealth;
|
|
30
|
+
usage: ProfileUsage;
|
|
31
|
+
/** Running agents whose session is pinned to this profile. */
|
|
32
|
+
agents: string[];
|
|
33
|
+
}
|
|
34
|
+
export interface ProviderView {
|
|
35
|
+
provider: SubscriptionProvider;
|
|
36
|
+
activeProfileId: string;
|
|
37
|
+
profiles: ProfileView[];
|
|
38
|
+
/** Running agents still on a profile other than the active one: restart to switch. */
|
|
39
|
+
staleAgents: Array<{
|
|
40
|
+
roleId: string;
|
|
41
|
+
profileId: string;
|
|
42
|
+
}>;
|
|
43
|
+
/** Running agents whose credentials come from env, not a Fleet profile. */
|
|
44
|
+
unmanagedAgents: Array<{
|
|
45
|
+
roleId: string;
|
|
46
|
+
via: string[];
|
|
47
|
+
}>;
|
|
48
|
+
}
|
|
49
|
+
export interface AgentRef {
|
|
50
|
+
roleId: string;
|
|
51
|
+
stateDir?: string;
|
|
52
|
+
running: boolean;
|
|
53
|
+
}
|
|
54
|
+
export interface SubscriptionServiceOptions {
|
|
55
|
+
agents(): Promise<AgentRef[]>;
|
|
56
|
+
binaries?: SubscriptionBinaries;
|
|
57
|
+
now?: () => number;
|
|
58
|
+
onChange?: () => void;
|
|
59
|
+
}
|
|
60
|
+
interface CheckResult {
|
|
61
|
+
health: ProfileHealth;
|
|
62
|
+
account?: ProfileView['account'];
|
|
63
|
+
usage?: ProfileUsage;
|
|
64
|
+
}
|
|
65
|
+
export declare class SubscriptionService {
|
|
66
|
+
private readonly options;
|
|
67
|
+
private readonly bins;
|
|
68
|
+
private readonly now;
|
|
69
|
+
private readonly checks;
|
|
70
|
+
private readonly running;
|
|
71
|
+
private readonly logins;
|
|
72
|
+
private readonly probes;
|
|
73
|
+
constructor(options: SubscriptionServiceOptions);
|
|
74
|
+
list(): Promise<ProviderView[]>;
|
|
75
|
+
/** Health + account + (Codex) usage for one profile. Concurrent calls share one run. */
|
|
76
|
+
check(provider: SubscriptionProvider, profileId: string): Promise<CheckResult>;
|
|
77
|
+
private checkClaude;
|
|
78
|
+
private checkCodex;
|
|
79
|
+
startLogin(provider: SubscriptionProvider, owner: string): Promise<LoginView>;
|
|
80
|
+
private completeLogin;
|
|
81
|
+
/** Query the official CLI for each existing profile, including the adopted default. */
|
|
82
|
+
private accountAlreadyAdded;
|
|
83
|
+
login(loginId: string, owner: string): LoginHandle;
|
|
84
|
+
setActive(provider: SubscriptionProvider, profileId: string): Promise<ProviderView>;
|
|
85
|
+
rename(provider: SubscriptionProvider, profileId: string, label: string): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Remove a profile and its login. Refused while it is active or while any
|
|
88
|
+
* running agent is still pinned to it; the adopted default is never removed.
|
|
89
|
+
*/
|
|
90
|
+
remove(provider: SubscriptionProvider, profileId: string): Promise<void>;
|
|
91
|
+
private profile;
|
|
92
|
+
/** An explicit, bounded Haiku request can refresh Claude usage for an idle profile. */
|
|
93
|
+
probeClaudeUsage(profileId: string): Promise<ProfileUsage>;
|
|
94
|
+
private probeUsageDir;
|
|
95
|
+
private probedUsage;
|
|
96
|
+
private pins;
|
|
97
|
+
/** Latest Claude rate-limit snapshot any agent on this profile reported. */
|
|
98
|
+
private observedUsage;
|
|
99
|
+
}
|
|
100
|
+
/** Read what `recordClaudeRateLimit` stored, dropping windows that already reset. */
|
|
101
|
+
export declare function readObservedClaudeUsage(stateDir: string, now?: number): ProfileUsage | undefined;
|
|
102
|
+
export {};
|