@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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type TaskNoticeBinding, type TaskCreatedNotice } from './task-notice.js';
|
|
1
2
|
import type { CommonPermissions } from '../config.js';
|
|
2
3
|
import type { ConversationEventV1, ConversationSnapshot, PromptOrigin, PromptReceipt, SubmitPromptCommand } from './conversation-types.js';
|
|
3
4
|
import type { CodexAppServerTransportFactory } from './codex-app-server-transport.js';
|
|
@@ -68,6 +69,8 @@ export declare class CodexAppServerSession implements AgentSession {
|
|
|
68
69
|
snapshot(): SessionSnapshot;
|
|
69
70
|
queuePrompt(text: string, options?: SubmitPromptOptions): Promise<QueuedPrompt>;
|
|
70
71
|
submitPrompt(text: string, options?: SubmitPromptOptions): Promise<TurnResult>;
|
|
72
|
+
readonly liveVoiceSupported = true;
|
|
73
|
+
interruptPrompt(generation: string, promptId: string): Promise<InterruptOutcome>;
|
|
71
74
|
interrupt(source?: TurnCancellationSource): Promise<InterruptOutcome>;
|
|
72
75
|
respondPermission(permissionId: string, optionId: string): boolean;
|
|
73
76
|
respondPermissionV2(permissionId: string, optionId: string, sessionGeneration: string): 'accepted' | 'stale';
|
|
@@ -76,6 +79,8 @@ export declare class CodexAppServerSession implements AgentSession {
|
|
|
76
79
|
setControllerAttached(attached: boolean): void;
|
|
77
80
|
exitResult(): ExitRecord | null;
|
|
78
81
|
close(): Promise<void>;
|
|
82
|
+
taskNoticeBinding(): TaskNoticeBinding | undefined;
|
|
83
|
+
recordTaskCreated(binding: TaskNoticeBinding, notice: TaskCreatedNotice): void;
|
|
79
84
|
conversationPage(request?: {
|
|
80
85
|
after?: string;
|
|
81
86
|
limit?: number;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { appendTaskCreated } from './task-notice.js';
|
|
1
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
3
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
4
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
5
|
+
import { boundedJson } from './conversation-normalizer.js';
|
|
4
6
|
import { ConversationEventStore } from './conversation-store.js';
|
|
5
7
|
import { SessionEvents } from './events.js';
|
|
6
8
|
import { CodexAppServerTransport } from './codex-app-server-transport.js';
|
|
@@ -12,7 +14,6 @@ const INTERRUPT_TERMINATE_GRACE_MS = 5_000;
|
|
|
12
14
|
const TERMINAL_RECONCILE_QUIET_MS = 2_000;
|
|
13
15
|
const TERMINAL_RECONCILE_REQUEST_TIMEOUT_MS = 5_000;
|
|
14
16
|
const SCHEDULED_REDACTION = '[scheduled-loop content redacted]';
|
|
15
|
-
const COMMENTARY_REDACTION = '[assistant commentary redacted]';
|
|
16
17
|
const OWNER_ADMIN_CONTEXT_KEY = 'ours-fleet://prompt-provenance?source=owner_admin_console';
|
|
17
18
|
const OWNER_CHANNEL_CONTEXT_KEY = 'ours-fleet://prompt-provenance?source=owner_channel';
|
|
18
19
|
const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -226,10 +227,20 @@ export class CodexAppServerSession {
|
|
|
226
227
|
throw error;
|
|
227
228
|
}
|
|
228
229
|
}
|
|
230
|
+
liveVoiceSupported = true;
|
|
231
|
+
async interruptPrompt(generation, promptId) {
|
|
232
|
+
if (generation !== this.sessionGeneration)
|
|
233
|
+
throw new Error('session_changed: Agent session changed.');
|
|
234
|
+
if (this.activeTurn?.promptId !== promptId)
|
|
235
|
+
return { state: 'settled' };
|
|
236
|
+
return this.interrupt('local-console');
|
|
237
|
+
}
|
|
229
238
|
async interrupt(source = 'local-console') {
|
|
230
239
|
const active = this.activeTurn;
|
|
231
|
-
if (!active || !this.threadId
|
|
240
|
+
if (!active || !this.threadId)
|
|
232
241
|
return { state: 'settled' };
|
|
242
|
+
if (!active.nativeTurnId)
|
|
243
|
+
throw new SessionControlError('backend', 'The operation is still pending without a native turn ID. Cancellation is not confirmed; inspect or restart the agent if it does not progress.');
|
|
233
244
|
active.cancellationSource ??= source;
|
|
234
245
|
this.events.emit('state', { turnId: active.promptId, status: 'running', origin: active.origin });
|
|
235
246
|
this.conversation.appendSafe({
|
|
@@ -335,6 +346,10 @@ export class CodexAppServerSession {
|
|
|
335
346
|
this.conversation.close();
|
|
336
347
|
await this.transport.close();
|
|
337
348
|
}
|
|
349
|
+
taskNoticeBinding() {
|
|
350
|
+
return this.activeTurn && this.threadId ? { sessionGeneration: this.sessionGeneration, acpSessionId: this.threadId, promptId: this.activeTurn.promptId } : undefined;
|
|
351
|
+
}
|
|
352
|
+
recordTaskCreated(binding, notice) { appendTaskCreated(this.conversation, binding, notice); }
|
|
338
353
|
conversationPage(request = {}) {
|
|
339
354
|
const floor = Number(this.conversationStartCursor ?? 0);
|
|
340
355
|
const requested = Number(request.after ?? 0);
|
|
@@ -367,6 +382,10 @@ export class CodexAppServerSession {
|
|
|
367
382
|
const existing = this.conversation.receiptFor(command.commandId, bodyDigest);
|
|
368
383
|
if (existing)
|
|
369
384
|
return existing;
|
|
385
|
+
if (command.expectedSessionGeneration !== undefined && command.expectedSessionGeneration !== this.conversationSnapshot().sessionGeneration)
|
|
386
|
+
throw new Error('session_changed: Agent session changed. Record a new voice message.');
|
|
387
|
+
if (command.requireIdle && (this.queueDepth > 0 || this.conversationSnapshot().readiness !== 'idle'))
|
|
388
|
+
throw new Error('voice_busy: Agent is already working. Wait for the current turn.');
|
|
370
389
|
const queued = await this.queuePrompt(command.text, {
|
|
371
390
|
origin: { kind: 'owner-admin-console', commandId: command.commandId },
|
|
372
391
|
actor: { browserSession: command.actorBrowserSession },
|
|
@@ -415,6 +434,10 @@ export class CodexAppServerSession {
|
|
|
415
434
|
this.reasoningEffort = { value: effort };
|
|
416
435
|
this.readiness = 'idle';
|
|
417
436
|
this.events.emit('state', { status: 'idle', text: `Codex thread ${this.threadId}` });
|
|
437
|
+
this.conversation.appendSafe({
|
|
438
|
+
kind: 'capabilities.updated', sessionGeneration: this.sessionGeneration,
|
|
439
|
+
payload: { commands: [{ name: 'compact', description: textBlock('Compact session context') }] },
|
|
440
|
+
});
|
|
418
441
|
this.conversation.appendSafe({
|
|
419
442
|
kind: 'session.state', sessionGeneration: this.sessionGeneration,
|
|
420
443
|
payload: { status: 'idle' },
|
|
@@ -486,6 +509,12 @@ export class CodexAppServerSession {
|
|
|
486
509
|
promptId, turnId: promptId, source: sourceFor(origin).source, payload: {},
|
|
487
510
|
});
|
|
488
511
|
try {
|
|
512
|
+
if (text.trim() === '/compact') {
|
|
513
|
+
// The acknowledgement is not completion. Standard turn notifications
|
|
514
|
+
// settle the same queued prompt and durable browser receipt.
|
|
515
|
+
await this.transport.request('thread/compact/start', { threadId: this.threadId });
|
|
516
|
+
return await settled;
|
|
517
|
+
}
|
|
489
518
|
const response = await this.transport.request('turn/start', {
|
|
490
519
|
threadId: this.threadId,
|
|
491
520
|
input: [{ type: 'text', text, text_elements: [] }],
|
|
@@ -699,7 +728,7 @@ export class CodexAppServerSession {
|
|
|
699
728
|
promptId: this.activeTurn?.promptId, turnId: this.activeTurn?.promptId,
|
|
700
729
|
messageId: id, source: 'agent',
|
|
701
730
|
payload: {
|
|
702
|
-
role: 'assistant', content: textBlock(delta, scheduled ? SCHEDULED_REDACTION :
|
|
731
|
+
role: 'assistant', content: textBlock(delta, scheduled ? SCHEDULED_REDACTION : undefined),
|
|
703
732
|
},
|
|
704
733
|
});
|
|
705
734
|
}
|
|
@@ -746,6 +775,10 @@ export class CodexAppServerSession {
|
|
|
746
775
|
toolCallId: scheduled ? 'scheduled-loop-tool' : id, snapshot,
|
|
747
776
|
title: scheduled ? 'scheduled-loop tool' : tool.title, status: tool.status,
|
|
748
777
|
kind: string(item.type),
|
|
778
|
+
...(!scheduled && item.arguments !== undefined ? { rawInput: boundedJson(item.arguments) } : {}),
|
|
779
|
+
...(!scheduled && item.type === 'commandExecution' ? { rawInput: boundedJson({ command: item.command, cwd: item.cwd }), ...(item.aggregatedOutput !== undefined ? { rawOutput: boundedJson({ output: item.aggregatedOutput, exitCode: item.exitCode }) } : {}) } : {}),
|
|
780
|
+
...(!scheduled && item.type === 'fileChange' && item.changes !== undefined ? { rawOutput: boundedJson(item.changes) } : {}),
|
|
781
|
+
...(!scheduled && (item.result !== undefined || item.error !== undefined) ? { rawOutput: boundedJson({ result: item.result, error: item.error }) } : {}),
|
|
749
782
|
},
|
|
750
783
|
});
|
|
751
784
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { LayoutControlRequest } from '../rooms-tasks/layout-control.js';
|
|
2
|
+
import type { SupervisorCorrespondence, AgentHistoryQuery } from '../agent-ours/correspondence.js';
|
|
1
3
|
import { type Socket } from 'node:net';
|
|
2
4
|
import type { AgentSession, ControlFailureKind, SessionEvent, SessionSnapshot } from './types.js';
|
|
3
5
|
import type { OwnerChannelHandle, OwnerChannelManagementRequest } from '../owner-channel/channel.js';
|
|
@@ -9,8 +11,13 @@ export interface ControlRequest {
|
|
|
9
11
|
version: 1 | 2 | 3;
|
|
10
12
|
id: string;
|
|
11
13
|
token: string;
|
|
12
|
-
command: 'status' | 'snapshot' | 'submit_prompt' | 'respond_permission' | 'interrupt' | 'follow' | 'events_since' | 'owner_channel_manage' | 'loop_status' | 'loop_run_now' | 'loop_disable' | 'loop_enable' | 'reload_config' | 'conversation_page' | 'conversation_follow' | 'submit_prompt_v2' | 'interrupt_v2' | 'respond_permission_v2' | 'fleet_spawn' | 'fleet_audit_begin' | 'fleet_audit_present' | 'fleet_audit_finish';
|
|
14
|
+
command: 'status' | 'snapshot' | 'submit_prompt' | 'respond_permission' | 'interrupt' | 'follow' | 'events_since' | 'owner_channel_manage' | 'loop_status' | 'loop_run_now' | 'loop_disable' | 'loop_enable' | 'reload_config' | 'conversation_page' | 'conversation_follow' | 'submit_prompt_v2' | 'submit_voice_prompt' | 'interrupt_v2' | 'interrupt_voice_v2' | 'layout_control' | 'agent_contacts' | 'agent_history' | 'respond_permission_v2' | 'fleet_spawn' | 'fleet_audit_begin' | 'fleet_audit_present' | 'fleet_audit_finish';
|
|
15
|
+
layout?: LayoutControlRequest;
|
|
16
|
+
agentHistory?: AgentHistoryQuery;
|
|
13
17
|
text?: string;
|
|
18
|
+
expectedSessionGeneration?: string;
|
|
19
|
+
requireIdle?: boolean;
|
|
20
|
+
promptId?: string;
|
|
14
21
|
permissionId?: string;
|
|
15
22
|
optionId?: string;
|
|
16
23
|
since?: number;
|
|
@@ -112,9 +119,13 @@ export declare class RoleControlServer {
|
|
|
112
119
|
private readonly sockets;
|
|
113
120
|
/** Interrupt idempotency: same command id returns the first receipt. */
|
|
114
121
|
private readonly interruptCommands;
|
|
122
|
+
private correspondence?;
|
|
123
|
+
setCorrespondence(value: SupervisorCorrespondence): void;
|
|
115
124
|
private ownerChannel?;
|
|
116
125
|
private loopManager?;
|
|
117
126
|
private reloadConfig?;
|
|
127
|
+
private layoutControl?;
|
|
128
|
+
setLayoutControl(handler: (request: LayoutControlRequest) => Promise<unknown>): void;
|
|
118
129
|
private fleetSpawner?;
|
|
119
130
|
private fleetAuditor?;
|
|
120
131
|
constructor(stateDir: string, session: AgentSession, log: (line: string) => void);
|
package/dist/session/control.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { socketPath as privateSocketPath } from '../socket-path.js';
|
|
1
2
|
import { randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
2
3
|
import { chmodSync, existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
4
|
import { createConnection, createServer } from 'node:net';
|
|
@@ -8,7 +9,7 @@ import { interruptSession, queueSessionPrompt, respondSessionPermission, respond
|
|
|
8
9
|
const MAX_LINE_BYTES = 64 * 1024;
|
|
9
10
|
/** Commands that require protocol version 3. */
|
|
10
11
|
const V3_COMMANDS = new Set([
|
|
11
|
-
'conversation_page', 'conversation_follow', 'submit_prompt_v2', 'interrupt_v2',
|
|
12
|
+
'conversation_page', 'conversation_follow', 'submit_prompt_v2', 'submit_voice_prompt', 'interrupt_v2', 'interrupt_voice_v2',
|
|
12
13
|
'respond_permission_v2',
|
|
13
14
|
]);
|
|
14
15
|
/** The one retained-range projection shared by polling and live-follow admission. */
|
|
@@ -99,7 +100,7 @@ export function oversightTaxonomy(name = '<Name>') {
|
|
|
99
100
|
export function oversightTaxonomyLines(name = '<Name>') {
|
|
100
101
|
return oversightTaxonomy(name).map(r => `- **${r.result}** — ${r.meaning} → ${r.action}`);
|
|
101
102
|
}
|
|
102
|
-
export const controlSocketPath = (stateDir) => join(stateDir, '.control.sock');
|
|
103
|
+
export const controlSocketPath = (stateDir) => privateSocketPath(join(stateDir, '.control.sock'));
|
|
103
104
|
export const controlTokenPath = (stateDir) => join(stateDir, '.control-token');
|
|
104
105
|
function sameToken(actual, supplied) {
|
|
105
106
|
const a = Buffer.from(actual);
|
|
@@ -116,9 +117,13 @@ export class RoleControlServer {
|
|
|
116
117
|
sockets = new Set();
|
|
117
118
|
/** Interrupt idempotency: same command id returns the first receipt. */
|
|
118
119
|
interruptCommands = new Map();
|
|
120
|
+
correspondence;
|
|
121
|
+
setCorrespondence(value) { this.correspondence = value; }
|
|
119
122
|
ownerChannel;
|
|
120
123
|
loopManager;
|
|
121
124
|
reloadConfig;
|
|
125
|
+
layoutControl;
|
|
126
|
+
setLayoutControl(handler) { this.layoutControl = handler; }
|
|
122
127
|
fleetSpawner;
|
|
123
128
|
fleetAuditor;
|
|
124
129
|
constructor(stateDir, session, log) {
|
|
@@ -235,7 +240,10 @@ export class RoleControlServer {
|
|
|
235
240
|
protocolVersion: this.session.conversationPage ? 3 : 2,
|
|
236
241
|
features: [
|
|
237
242
|
'events_since', 'observer_follow', 'retained_range',
|
|
243
|
+
...(this.correspondence ? ['agent_correspondence'] : []),
|
|
238
244
|
...(this.session.conversationPage ? ['conversation_v3'] : []),
|
|
245
|
+
...(this.session.submitPromptBrowser ? ['generation_bound_prompts'] : []),
|
|
246
|
+
...(this.session.liveVoiceSupported ? ['idle_bound_prompts', 'targeted_voice_interrupt'] : []),
|
|
239
247
|
...(this.session.capabilities?.steering ? ['steering'] : []),
|
|
240
248
|
...(this.session.capabilities?.permissions ? ['permissions'] : []),
|
|
241
249
|
...(this.session.capabilities?.messagePhases ? ['message_phases'] : []),
|
|
@@ -308,6 +316,13 @@ export class RoleControlServer {
|
|
|
308
316
|
});
|
|
309
317
|
return;
|
|
310
318
|
}
|
|
319
|
+
case 'layout_control': {
|
|
320
|
+
if (request.version !== 2 || !request.layout || !this.layoutControl)
|
|
321
|
+
throw new SessionControlError('rejected', 'layout control unavailable');
|
|
322
|
+
const result = await this.layoutControl(request.layout);
|
|
323
|
+
this.write(socket, { version: 1, id: request.id, ok: true, result });
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
311
326
|
case 'fleet_spawn': {
|
|
312
327
|
if (request.version !== 2 || !request.spawn || typeof request.spawn.name !== 'string')
|
|
313
328
|
throw new SessionControlError('rejected', 'version 2 and typed spawn options are required');
|
|
@@ -362,6 +377,23 @@ export class RoleControlServer {
|
|
|
362
377
|
this.write(socket, { version: 1, id: request.id, ok: true, result: { delivered: true } });
|
|
363
378
|
return;
|
|
364
379
|
}
|
|
380
|
+
case 'agent_contacts':
|
|
381
|
+
case 'agent_history': {
|
|
382
|
+
if (!this.correspondence)
|
|
383
|
+
throw new SessionControlError('rejected', 'Supervisor correspondence API is unavailable; this session needs an updated supervisor.');
|
|
384
|
+
const query = request.agentHistory;
|
|
385
|
+
if (request.command === 'agent_history' && (!query || !/^[a-f0-9]{64}$/i.test(query.peer_cid) || (query.limit !== undefined && (!Number.isInteger(query.limit) || query.limit < 1 || query.limit > 200)) || (query.before_seq !== undefined && (!Number.isSafeInteger(query.before_seq) || query.before_seq < 1))))
|
|
386
|
+
throw new SessionControlError('rejected', 'Valid peer_cid, limit (1–200) and before_seq are required.');
|
|
387
|
+
let result;
|
|
388
|
+
try {
|
|
389
|
+
result = request.command === 'agent_contacts' ? await this.correspondence.contacts() : await this.correspondence.history(query);
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
throw new SessionControlError('rejected', 'Supervisor correspondence read failed; identity or transport is unavailable.');
|
|
393
|
+
}
|
|
394
|
+
this.write(socket, { version: 1, id: request.id, ok: true, result });
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
365
397
|
case 'owner_channel_manage': {
|
|
366
398
|
if (!request.ownerChannel || typeof request.ownerChannel.action !== 'string')
|
|
367
399
|
throw new SessionControlError('rejected', 'owner-channel management action is required');
|
|
@@ -421,7 +453,10 @@ export class RoleControlServer {
|
|
|
421
453
|
socket.write(JSON.stringify({ version: 1, conversationEvent: event }) + '\n');
|
|
422
454
|
}) };
|
|
423
455
|
}
|
|
456
|
+
case 'submit_voice_prompt':
|
|
424
457
|
case 'submit_prompt_v2': {
|
|
458
|
+
if (request.command === 'submit_voice_prompt' && !request.expectedSessionGeneration?.trim())
|
|
459
|
+
throw new SessionControlError('rejected', 'expectedSessionGeneration is required');
|
|
425
460
|
this.requireConversation(request);
|
|
426
461
|
if (!request.commandId?.trim() || !request.text?.trim() || !request.actor?.trim()
|
|
427
462
|
|| request.source !== 'owner_admin_console')
|
|
@@ -432,6 +467,7 @@ export class RoleControlServer {
|
|
|
432
467
|
const receipt = await this.session.submitPromptBrowser({
|
|
433
468
|
commandId: request.commandId, text: request.text,
|
|
434
469
|
source: 'owner_admin_console', actorBrowserSession: request.actor,
|
|
470
|
+
expectedSessionGeneration: request.expectedSessionGeneration, requireIdle: request.requireIdle === true,
|
|
435
471
|
});
|
|
436
472
|
this.write(socket, { version: 1, id: request.id, ok: true, result: receipt });
|
|
437
473
|
}
|
|
@@ -442,6 +478,29 @@ export class RoleControlServer {
|
|
|
442
478
|
}
|
|
443
479
|
return;
|
|
444
480
|
}
|
|
481
|
+
case 'interrupt_voice_v2': {
|
|
482
|
+
this.requireConversation(request);
|
|
483
|
+
if (!request.commandId?.trim() || !request.expectedSessionGeneration?.trim() || !request.promptId?.trim())
|
|
484
|
+
throw new SessionControlError('rejected', 'commandId, expectedSessionGeneration and promptId are required');
|
|
485
|
+
if (!this.session.interruptPrompt)
|
|
486
|
+
throw new SessionControlError('rejected', 'targeted voice interrupt unavailable');
|
|
487
|
+
const key = 'voice:' + request.commandId;
|
|
488
|
+
const signature = JSON.stringify([request.expectedSessionGeneration, request.promptId]);
|
|
489
|
+
const existing = this.interruptCommands.get(key);
|
|
490
|
+
if (existing) {
|
|
491
|
+
if (existing.signature !== signature)
|
|
492
|
+
throw new SessionControlError('rejected', 'idempotency_conflict: interrupt target changed');
|
|
493
|
+
this.write(socket, { version: 1, id: request.id, ok: true, result: existing.receipt });
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const outcome = await this.session.interruptPrompt(request.expectedSessionGeneration, request.promptId);
|
|
497
|
+
const receipt = { accepted: true, commandId: request.commandId, ...outcome };
|
|
498
|
+
this.interruptCommands.set(key, { signature, receipt });
|
|
499
|
+
if (this.interruptCommands.size > 200)
|
|
500
|
+
this.interruptCommands.delete(this.interruptCommands.keys().next().value);
|
|
501
|
+
this.write(socket, { version: 1, id: request.id, ok: true, result: receipt });
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
445
504
|
case 'interrupt_v2': {
|
|
446
505
|
this.requireConversation(request);
|
|
447
506
|
if (!request.commandId?.trim())
|
|
@@ -532,7 +591,13 @@ export async function controlRequest(stateDir, request, timeoutMs = 120_000) {
|
|
|
532
591
|
socket.once('error', error => {
|
|
533
592
|
clearTimeout(timer);
|
|
534
593
|
const code = error.code;
|
|
535
|
-
|
|
594
|
+
const permissionHint = code === 'EPERM' || code === 'EACCES'
|
|
595
|
+
? `; the operating system denied access before '${request.command}' reached the supervisor. `
|
|
596
|
+
+ 'Check socket permissions and the command execution sandbox. '
|
|
597
|
+
+ 'An existing socket does not prove this process can connect; do not bypass Fleet audit. '
|
|
598
|
+
+ 'For managed workspace sessions, see docs/validation/managed-cli-permissions.md.'
|
|
599
|
+
: '';
|
|
600
|
+
reject(new SessionControlError(code === 'ENOENT' || code === 'ECONNREFUSED' ? 'control-unavailable' : 'backend', `role control socket: ${error.message}${permissionHint}`));
|
|
536
601
|
});
|
|
537
602
|
socket.on('data', chunk => {
|
|
538
603
|
buffer += chunk;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type * as acp from '@agentclientprotocol/sdk';
|
|
2
|
-
import type { AdapterMeta, ConversationEventKind, ConversationPayload } from './conversation-types.js';
|
|
2
|
+
import type { AdapterMeta, BoundedJson, ConversationEventKind, ConversationPayload } from './conversation-types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Reduce one ACP v1 `session/update` into one conversation-domain event draft.
|
|
5
5
|
*
|
|
@@ -43,4 +43,5 @@ export interface NormalizedUpdate {
|
|
|
43
43
|
toolCallId?: string;
|
|
44
44
|
adapterMeta?: AdapterMeta[];
|
|
45
45
|
}
|
|
46
|
+
export declare function boundedJson(value: unknown): BoundedJson;
|
|
46
47
|
export declare function normalizeSessionUpdate(update: acp.SessionUpdate, options?: NormalizeOptions): NormalizedUpdate;
|
|
@@ -29,6 +29,7 @@ type EventDraft = Omit<ConversationEventV1, 'schemaVersion' | 'roleId' | 'eventI
|
|
|
29
29
|
export declare class ConversationEventStore {
|
|
30
30
|
private readonly dir;
|
|
31
31
|
private nextSeq;
|
|
32
|
+
private firstAvailableSeq?;
|
|
32
33
|
private segments;
|
|
33
34
|
private tail;
|
|
34
35
|
private readonly listeners;
|
|
@@ -81,7 +82,6 @@ export declare class ConversationEventStore {
|
|
|
81
82
|
private trackPromptState;
|
|
82
83
|
private segmentFd;
|
|
83
84
|
private writeManifest;
|
|
84
|
-
private firstStoredSeq;
|
|
85
85
|
private eventsAfter;
|
|
86
86
|
private markDegraded;
|
|
87
87
|
}
|
|
@@ -24,6 +24,7 @@ export class IdempotencyConflictError extends Error {
|
|
|
24
24
|
export class ConversationEventStore {
|
|
25
25
|
dir;
|
|
26
26
|
nextSeq = 1;
|
|
27
|
+
firstAvailableSeq;
|
|
27
28
|
segments = [];
|
|
28
29
|
tail = [];
|
|
29
30
|
listeners = new Set();
|
|
@@ -72,6 +73,7 @@ export class ConversationEventStore {
|
|
|
72
73
|
const fd = this.segmentFd(Buffer.byteLength(line));
|
|
73
74
|
writeSync(fd, line);
|
|
74
75
|
fsyncSync(fd);
|
|
76
|
+
this.firstAvailableSeq ??= event.seq;
|
|
75
77
|
this.nextSeq++;
|
|
76
78
|
this.activeBytes += Buffer.byteLength(line);
|
|
77
79
|
this.tail.push(event);
|
|
@@ -101,7 +103,10 @@ export class ConversationEventStore {
|
|
|
101
103
|
const events = this.eventsAfter(after, limit + 1);
|
|
102
104
|
const hasMore = events.length > limit;
|
|
103
105
|
const pageEvents = hasMore ? events.slice(0, limit) : events;
|
|
104
|
-
|
|
106
|
+
// This single-writer store never removes retained segments. Recovery and
|
|
107
|
+
// append already know the first readable sequence; paging the memory tail
|
|
108
|
+
// must not parse the first segment again, especially while skipping replay.
|
|
109
|
+
const firstStored = this.firstAvailableSeq;
|
|
105
110
|
return {
|
|
106
111
|
events: pageEvents,
|
|
107
112
|
...(firstStored !== undefined ? { firstAvailableCursor: String(firstStored) } : {}),
|
|
@@ -159,6 +164,7 @@ export class ConversationEventStore {
|
|
|
159
164
|
let maxSeq = 0;
|
|
160
165
|
for (const segment of this.segments) {
|
|
161
166
|
for (const event of this.readSegment(segment)) {
|
|
167
|
+
this.firstAvailableSeq ??= event.seq;
|
|
162
168
|
maxSeq = Math.max(maxSeq, event.seq);
|
|
163
169
|
this.tail.push(event);
|
|
164
170
|
if (this.tail.length > TAIL_EVENTS)
|
|
@@ -308,16 +314,6 @@ export class ConversationEventStore {
|
|
|
308
314
|
}
|
|
309
315
|
}
|
|
310
316
|
// ── reads ──────────────────────────────────────────────────────────────────
|
|
311
|
-
firstStoredSeq() {
|
|
312
|
-
if (this.tail.length && this.tail[0].seq === 1)
|
|
313
|
-
return 1;
|
|
314
|
-
for (const segment of this.segments) {
|
|
315
|
-
const events = this.readSegment(segment);
|
|
316
|
-
if (events.length)
|
|
317
|
-
return events[0].seq;
|
|
318
|
-
}
|
|
319
|
-
return this.tail[0]?.seq;
|
|
320
|
-
}
|
|
321
317
|
eventsAfter(after, limit) {
|
|
322
318
|
// Serve from the in-memory tail whenever the range allows it.
|
|
323
319
|
if (this.tail.length && after >= this.tail[0].seq - 1)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TaskCreatedNotice } from './task-notice.js';
|
|
1
2
|
import type { PromptOrigin, TurnCancellationSource, TurnOutcome } from './types.js';
|
|
2
3
|
/**
|
|
3
4
|
* Durable conversation domain schema for managed-session consoles.
|
|
@@ -7,11 +8,11 @@ import type { PromptOrigin, TurnCancellationSource, TurnOutcome } from './types.
|
|
|
7
8
|
* file touches the wire: transport updates are reduced into these shapes by a
|
|
8
9
|
* session adapter, and the store assigns `seq`/`eventId`/timestamps.
|
|
9
10
|
*/
|
|
10
|
-
export type ConversationEventKind = 'prompt.admitted' | 'prompt.started' | 'prompt.interrupt_requested' | 'message.chunk' | 'message.replace' | 'thought.chunk' | 'thought.replace' | 'plan.replace' | 'tool.upsert' | 'tool.content_chunk' | 'permission.requested' | 'permission.resolved' | 'monitor.delivery' | 'usage.updated' | 'turn.state' | 'turn.completed' | 'session.state' | 'session.info' | 'capabilities.updated' | 'error'
|
|
11
|
+
export type ConversationEventKind = 'prompt.admitted' | 'prompt.started' | 'prompt.interrupt_requested' | 'message.chunk' | 'message.replace' | 'thought.chunk' | 'thought.replace' | 'plan.replace' | 'tool.upsert' | 'tool.content_chunk' | 'permission.requested' | 'permission.resolved' | 'fleet.task_created' | 'monitor.delivery' | 'usage.updated' | 'turn.state' | 'turn.completed' | 'session.state' | 'session.info' | 'capabilities.updated' | 'error'
|
|
11
12
|
/** A well-formed transport update this version cannot represent. Bounded, never a crash. */
|
|
12
13
|
| 'unsupported';
|
|
13
14
|
/** Where a conversation record came from. Typed provenance, never prompt text. */
|
|
14
|
-
export type ConversationSource = 'owner_admin_console' | 'owner_channel' | 'fleet_monitor' | 'scheduled_loop' | 'startup' | 'local_console' | 'agent' | 'agent_replay';
|
|
15
|
+
export type ConversationSource = 'owner_admin_console' | 'owner_channel' | 'fleet_monitor' | 'scheduled_loop' | 'startup' | 'local_console' | 'agent' | 'agent_replay' | 'fleet_lifecycle';
|
|
15
16
|
export interface NormalizedText {
|
|
16
17
|
type: 'text';
|
|
17
18
|
text: string;
|
|
@@ -227,7 +228,7 @@ export interface BoundedJson {
|
|
|
227
228
|
digest?: string;
|
|
228
229
|
redacted?: true;
|
|
229
230
|
}
|
|
230
|
-
export type ConversationPayload = MessageChunkPayload | ThoughtChunkPayload | PlanReplacePayload | ToolUpsertPayload | UsageUpdatedPayload | SessionStatePayload | SessionInfoPayload | CapabilitiesUpdatedPayload | UnsupportedPayload | PromptAdmittedPayload | PromptStartedPayload | PromptInterruptRequestedPayload | PermissionRequestedPayload | PermissionResolvedPayload | TurnStatePayload | TurnCompletedPayload | MonitorDeliveryPayload | SessionLifecyclePayload | ErrorPayload;
|
|
231
|
+
export type ConversationPayload = MessageChunkPayload | ThoughtChunkPayload | PlanReplacePayload | ToolUpsertPayload | UsageUpdatedPayload | SessionStatePayload | SessionInfoPayload | CapabilitiesUpdatedPayload | UnsupportedPayload | PromptAdmittedPayload | PromptStartedPayload | PromptInterruptRequestedPayload | PermissionRequestedPayload | PermissionResolvedPayload | TurnStatePayload | TurnCompletedPayload | MonitorDeliveryPayload | SessionLifecyclePayload | ErrorPayload | TaskCreatedNotice;
|
|
231
232
|
export interface ConversationEventV1 {
|
|
232
233
|
schemaVersion: 1;
|
|
233
234
|
roleId: string;
|
|
@@ -262,6 +263,9 @@ export interface ConversationEventV1 {
|
|
|
262
263
|
adapterMeta?: AdapterMeta[];
|
|
263
264
|
}
|
|
264
265
|
export interface SubmitPromptCommand {
|
|
266
|
+
expectedSessionGeneration?: string;
|
|
267
|
+
/** Live speech must not silently queue behind an unrelated turn. */
|
|
268
|
+
requireIdle?: boolean;
|
|
265
269
|
/** Idempotency-Key / clientRequestId. Reuse with a different body is a conflict. */
|
|
266
270
|
commandId: string;
|
|
267
271
|
text: string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ConversationEventStore } from './conversation-store.js';
|
|
2
|
+
export interface TaskNoticeBinding {
|
|
3
|
+
sessionGeneration: string;
|
|
4
|
+
acpSessionId: string;
|
|
5
|
+
promptId: string;
|
|
6
|
+
}
|
|
7
|
+
export interface TaskCreatedNotice {
|
|
8
|
+
operationId: string;
|
|
9
|
+
taskId: string;
|
|
10
|
+
title: string;
|
|
11
|
+
state: string;
|
|
12
|
+
}
|
|
13
|
+
/** Only the trusted Fleet audit completion calls this, never model output. */
|
|
14
|
+
export declare function appendTaskCreated(store: ConversationEventStore, binding: TaskNoticeBinding, notice: TaskCreatedNotice): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Only the trusted Fleet audit completion calls this, never model output. */
|
|
2
|
+
export function appendTaskCreated(store, binding, notice) {
|
|
3
|
+
let after;
|
|
4
|
+
do {
|
|
5
|
+
const page = store.page({ after, limit: 1000 });
|
|
6
|
+
if (page.events.some(e => e.kind === 'fleet.task_created' && e.payload.operationId === notice.operationId))
|
|
7
|
+
return;
|
|
8
|
+
if (!page.hasMore)
|
|
9
|
+
break;
|
|
10
|
+
after = page.nextCursor;
|
|
11
|
+
} while (after);
|
|
12
|
+
store.append({ kind: 'fleet.task_created', source: 'fleet_lifecycle', ...binding, turnId: binding.promptId, payload: notice });
|
|
13
|
+
}
|
package/dist/session/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TaskCreatedNotice, TaskNoticeBinding } from './task-notice.js';
|
|
1
2
|
import type { SessionBackendId } from '../config.js';
|
|
2
3
|
import type { ConversationEventV1, ConversationSnapshot, PromptReceipt, SubmitPromptCommand } from './conversation-types.js';
|
|
3
4
|
/**
|
|
@@ -289,6 +290,8 @@ export interface AgentSession {
|
|
|
289
290
|
readonly capabilities?: AgentSessionCapabilities;
|
|
290
291
|
isAlive(): boolean;
|
|
291
292
|
snapshot(): SessionSnapshot;
|
|
293
|
+
taskNoticeBinding?(): TaskNoticeBinding | undefined;
|
|
294
|
+
recordTaskCreated?(binding: TaskNoticeBinding, notice: TaskCreatedNotice): void;
|
|
292
295
|
conversationPage?(request: {
|
|
293
296
|
after?: string;
|
|
294
297
|
limit?: number;
|
|
@@ -316,6 +319,9 @@ export interface AgentSession {
|
|
|
316
319
|
* keep compiling. Read it through `interruptOutcome`, never directly.
|
|
317
320
|
*/
|
|
318
321
|
interrupt(source?: TurnCancellationSource): Promise<InterruptResult>;
|
|
322
|
+
/** Atomically bind cancellation to one voice-owned turn. */
|
|
323
|
+
readonly liveVoiceSupported?: boolean;
|
|
324
|
+
interruptPrompt?(generation: string, promptId: string): Promise<InterruptResult>;
|
|
319
325
|
respondPermission(permissionId: string, optionId: string): boolean;
|
|
320
326
|
/** Generation-bound browser decision; stale/settled/invalid all fail closed. */
|
|
321
327
|
respondPermissionV2?(permissionId: string, optionId: string, sessionGeneration: string): 'accepted' | 'stale';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type Exec } from './exec.js';
|
|
2
|
+
import type { WorkspacePayload } from './workspace-enrollment.js';
|
|
3
|
+
/**
|
|
4
|
+
* Check what the steps after the one-time root proof depend on, before that
|
|
5
|
+
* proof is sent: a failure afterwards cannot be retried with a fresh command.
|
|
6
|
+
*/
|
|
7
|
+
export declare function setupTunnelPreflight({ exec, platform, node }?: {
|
|
8
|
+
exec?: Exec;
|
|
9
|
+
platform?: NodeJS.Platform;
|
|
10
|
+
node?: string;
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
/** What the steps after the proof need, kept so they can be finished without a new command. */
|
|
13
|
+
export interface PendingTunnelSetup {
|
|
14
|
+
appOrigin: string;
|
|
15
|
+
origin: string;
|
|
16
|
+
hostWorkspaceId: string;
|
|
17
|
+
rootCid: string;
|
|
18
|
+
challenge: WorkspacePayload['challenge'];
|
|
19
|
+
/** Fleet configuration the setup started with; a resume must use the same one. */
|
|
20
|
+
configuration: string;
|
|
21
|
+
/** Every port sent to the account as the tunnel target. Without confirmedPort, the remote target may be any of these or the default. */
|
|
22
|
+
requestedPorts?: number[];
|
|
23
|
+
/** Last positively confirmed target; cleared before any new retarget request. */
|
|
24
|
+
confirmedPort?: number;
|
|
25
|
+
}
|
|
26
|
+
export declare function savePendingTunnelSetup(pending: PendingTunnelSetup): void;
|
|
27
|
+
export declare function clearPendingTunnelSetup(): void;
|
|
28
|
+
export declare function readPendingTunnelSetup(): PendingTunnelSetup;
|
|
29
|
+
export declare const DEFAULT_TUNNEL_PORT = 49271;
|
|
30
|
+
/**
|
|
31
|
+
* The port the web service must bind when the account can no longer retarget
|
|
32
|
+
* the tunnel. Unknown when a different port was requested and never confirmed.
|
|
33
|
+
*/
|
|
34
|
+
export declare function requiredPortAfterExpiry(pending: PendingTunnelSetup): number;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { existsSync, lstatSync, readFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import { userInfo } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { replaceFileAtomically } from './atomic-file.js';
|
|
5
|
+
import { realExec } from './exec.js';
|
|
6
|
+
import { stateRoot } from './paths.js';
|
|
7
|
+
const UNCHANGED = 'Nothing was changed and this host was not bound.';
|
|
8
|
+
/**
|
|
9
|
+
* Check what the steps after the one-time root proof depend on, before that
|
|
10
|
+
* proof is sent: a failure afterwards cannot be retried with a fresh command.
|
|
11
|
+
*/
|
|
12
|
+
export async function setupTunnelPreflight({ exec = realExec, platform = process.platform, node = process.versions.node } = {}) {
|
|
13
|
+
const [major, minor] = node.split('.').map(Number);
|
|
14
|
+
if (major < 22 || (major === 22 && minor < 13))
|
|
15
|
+
throw Error(`Tunnel setup requires Node.js 22.13 or newer; this is ${node}. Update Node.js, then run a fresh tunnel setup command. ${UNCHANGED}`);
|
|
16
|
+
if (platform === 'linux') {
|
|
17
|
+
const manager = await exec('systemctl', ['--user', 'show-environment'], { timeout: 10_000 });
|
|
18
|
+
if (manager.code !== 0)
|
|
19
|
+
throw Error(`The user service manager is not reachable from this shell (${manager.stderr.trim().split('\n')[0] || `systemctl --user exited ${manager.code}`}). `
|
|
20
|
+
+ `Log in directly as ${userInfo().username} (not through su or sudo), or run: sudo loginctl enable-linger ${userInfo().username} && export XDG_RUNTIME_DIR=/run/user/${process.getuid?.()} `
|
|
21
|
+
+ `Then run a fresh tunnel setup command. ${UNCHANGED}`);
|
|
22
|
+
}
|
|
23
|
+
const help = await exec('cloudflared', ['tunnel', 'run', '--help'], { timeout: 10_000 });
|
|
24
|
+
if (help.code === 127)
|
|
25
|
+
throw Error(`cloudflared is not installed or not on PATH. Install it, then run a fresh tunnel setup command. ${UNCHANGED}`);
|
|
26
|
+
if (help.code !== 0 || !`${help.stdout}\n${help.stderr}`.includes('--token-file'))
|
|
27
|
+
throw Error(`This cloudflared is too old for tunnel setup (no --token-file support). Update cloudflared, then run a fresh tunnel setup command. ${UNCHANGED}`);
|
|
28
|
+
}
|
|
29
|
+
const pendingPath = () => join(stateRoot(), 'workspace', 'pending-setup.json');
|
|
30
|
+
export function savePendingTunnelSetup(pending) {
|
|
31
|
+
replaceFileAtomically(pendingPath(), JSON.stringify(pending) + '\n', 0o600);
|
|
32
|
+
}
|
|
33
|
+
export function clearPendingTunnelSetup() { rmSync(pendingPath(), { force: true }); }
|
|
34
|
+
export function readPendingTunnelSetup() {
|
|
35
|
+
const path = pendingPath();
|
|
36
|
+
if (!existsSync(path))
|
|
37
|
+
throw Error('No unfinished tunnel setup on this host. Run the tunnel setup command from the App.');
|
|
38
|
+
const stat = lstatSync(path);
|
|
39
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.uid !== process.getuid?.() || (stat.mode & 0o077) !== 0)
|
|
40
|
+
throw Error('Unfinished tunnel setup record must be an owned private file');
|
|
41
|
+
const pending = JSON.parse(readFileSync(path, 'utf8'));
|
|
42
|
+
if (typeof pending?.appOrigin !== 'string' || typeof pending.origin !== 'string' || typeof pending.hostWorkspaceId !== 'string'
|
|
43
|
+
|| typeof pending.rootCid !== 'string' || !pending.challenge || !Number.isSafeInteger(pending.challenge.expiresAt) || typeof pending.configuration !== 'string'
|
|
44
|
+
|| (pending.requestedPorts !== undefined && (!Array.isArray(pending.requestedPorts) || !pending.requestedPorts.every(Number.isInteger))))
|
|
45
|
+
throw Error('Unfinished tunnel setup record is invalid');
|
|
46
|
+
if (pending.confirmedPort !== undefined && (!Number.isInteger(pending.confirmedPort) || pending.confirmedPort < 1024 || pending.confirmedPort > 65535))
|
|
47
|
+
throw Error('Unfinished tunnel setup record is invalid');
|
|
48
|
+
return pending;
|
|
49
|
+
}
|
|
50
|
+
export const DEFAULT_TUNNEL_PORT = 49_271;
|
|
51
|
+
/**
|
|
52
|
+
* The port the web service must bind when the account can no longer retarget
|
|
53
|
+
* the tunnel. Unknown when a different port was requested and never confirmed.
|
|
54
|
+
*/
|
|
55
|
+
export function requiredPortAfterExpiry(pending) {
|
|
56
|
+
if (pending.confirmedPort !== undefined)
|
|
57
|
+
return pending.confirmedPort;
|
|
58
|
+
const other = [...new Set(pending.requestedPorts ?? [])].filter(port => port !== DEFAULT_TUNNEL_PORT);
|
|
59
|
+
if (!other.length)
|
|
60
|
+
return DEFAULT_TUNNEL_PORT;
|
|
61
|
+
throw Error(`The setup window has expired and it is unknown whether the tunnel points at port ${[...other, DEFAULT_TUNNEL_PORT].join(' or ')}: the account's answer to a port request was not received. This setup cannot be finished automatically`);
|
|
62
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { lstatSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
4
|
+
/** Optional private socket directory for deeply nested, isolated Fleet homes. */
|
|
5
|
+
export function socketPath(logicalPath) {
|
|
6
|
+
const root = process.env.OURS_FLEET_SOCKET_ROOT;
|
|
7
|
+
if (!root) {
|
|
8
|
+
if (Buffer.byteLength(logicalPath) > 103)
|
|
9
|
+
throw new Error('Unix socket path is too long; configure a short private OURS_FLEET_SOCKET_ROOT');
|
|
10
|
+
return logicalPath;
|
|
11
|
+
}
|
|
12
|
+
if (!isAbsolute(root) || resolve(root) !== root)
|
|
13
|
+
throw new Error('OURS_FLEET_SOCKET_ROOT must be an absolute normalized path');
|
|
14
|
+
mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
15
|
+
const stat = lstatSync(root);
|
|
16
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || stat.uid !== process.getuid?.() || (stat.mode & 0o777) !== 0o700)
|
|
17
|
+
throw new Error('OURS_FLEET_SOCKET_ROOT must be an owner-only 0700 directory');
|
|
18
|
+
const path = join(root, createHash('sha256').update(resolve(logicalPath)).digest('hex').slice(0, 24) + '.sock');
|
|
19
|
+
if (Buffer.byteLength(path) > 103)
|
|
20
|
+
throw new Error('OURS_FLEET_SOCKET_ROOT is too long');
|
|
21
|
+
return path;
|
|
22
|
+
}
|