@ours.network/fleet 1.2.0-nightly.2 → 1.2.0-nightly.21
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 +152 -29
- package/dist/agent-ours/bridge.d.ts +3 -0
- package/dist/agent-ours/bridge.js +178 -0
- package/dist/agent-ours/controller.d.ts +14 -0
- package/dist/agent-ours/controller.js +60 -0
- package/dist/agent-ours/correspondence.d.ts +10 -0
- package/dist/agent-ours/correspondence.js +1 -0
- package/dist/agent-ours/file-rpc.d.ts +23 -0
- package/dist/agent-ours/file-rpc.js +123 -0
- package/dist/agent-ours/harness.d.ts +14 -0
- package/dist/agent-ours/harness.js +72 -0
- package/dist/agent-ours/mcp-endpoint.d.ts +15 -0
- package/dist/agent-ours/mcp-endpoint.js +111 -0
- package/dist/agent-ours/runtime.d.ts +59 -0
- package/dist/agent-ours/runtime.js +283 -0
- package/dist/agent-ours/service.d.ts +23 -0
- package/dist/agent-ours/service.js +372 -0
- package/dist/agent-ours/state.d.ts +45 -0
- package/dist/agent-ours/state.js +95 -0
- package/dist/agent-ours/wire.d.ts +14 -0
- package/dist/agent-ours/wire.js +55 -0
- package/dist/agent-recovery-gate.js +1 -1
- package/dist/application/conversation-history.d.ts +6 -0
- package/dist/application/conversation-history.js +65 -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/room-layout-definitions.d.ts +45 -0
- package/dist/application/room-layout-definitions.js +130 -0
- package/dist/application/session-control.d.ts +8 -0
- package/dist/application/session-control.js +21 -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 +89 -0
- package/dist/application/task-layouts.js +248 -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.js +10 -59
- package/dist/build-info.json +5 -4
- package/dist/capabilities.d.ts +2 -1
- package/dist/capabilities.js +2 -0
- package/dist/cli.js +65 -2
- package/dist/client-profile.d.ts +5 -16
- package/dist/client-profile.js +4 -112
- package/dist/config.d.ts +4 -0
- package/dist/config.js +7 -0
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +5 -16
- 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 +43 -23
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +46 -59
- package/dist/erased-resources.d.ts +9 -0
- package/dist/erased-resources.js +87 -0
- package/dist/fleet-command-audit.d.ts +1 -0
- package/dist/fleet-command-audit.js +12 -7
- package/dist/harness/agent-session.d.ts +2 -0
- package/dist/harness/claude-code-session.js +4 -2
- 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-session.js +9 -2
- package/dist/harness/codex.d.ts +3 -2
- package/dist/harness/codex.js +4 -3
- package/dist/harness/hermes-session.js +7 -1
- package/dist/init-wizard.js +20 -9
- package/dist/monitor.d.ts +1 -34
- package/dist/monitor.js +24 -151
- 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/preset-migration.js +15 -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.js +3 -1
- package/dist/rooms-tasks/cowork-adapter.d.ts +6 -4
- package/dist/rooms-tasks/cowork-adapter.js +11 -117
- package/dist/rooms-tasks/cowork-http.d.ts +3 -0
- package/dist/rooms-tasks/cowork-http.js +70 -0
- 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-cli.d.ts +2 -0
- package/dist/rooms-tasks/layout-cli.js +81 -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 +39 -0
- package/dist/rooms-tasks/layout-service.js +173 -0
- package/dist/rooms-tasks/layout.d.ts +102 -0
- package/dist/rooms-tasks/layout.js +227 -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 +182 -48
- 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 +23 -3
- package/dist/runner.js +574 -494
- package/dist/session/acp.d.ts +3 -0
- package/dist/session/acp.js +7 -0
- package/dist/session/arbiter.d.ts +3 -0
- package/dist/session/arbiter.js +20 -2
- package/dist/session/codex-app-server-transport.js +1 -1
- package/dist/session/codex-app-server.d.ts +3 -0
- package/dist/session/codex-app-server.js +25 -1
- package/dist/session/control.d.ts +10 -1
- package/dist/session/control.js +36 -2
- package/dist/session/conversation-normalizer.d.ts +2 -1
- package/dist/session/conversation-normalizer.js +1 -1
- package/dist/session/conversation-types.d.ts +5 -3
- package/dist/session/task-notice.d.ts +14 -0
- package/dist/session/task-notice.js +13 -0
- package/dist/session/types.d.ts +3 -0
- package/dist/socket-path.d.ts +2 -0
- package/dist/socket-path.js +22 -0
- package/dist/spawn.js +19 -16
- package/dist/startup-readiness.d.ts +13 -0
- package/dist/startup-readiness.js +38 -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/temp-supervisor-recovery.d.ts +3 -0
- package/dist/temp-supervisor-recovery.js +46 -0
- package/dist/watchdog/briefing.js +3 -16
- package/dist/watchdog/run.js +19 -5
- package/dist/web/agent-attachments.d.ts +29 -0
- package/dist/web/agent-attachments.js +162 -0
- package/dist/web/auth.d.ts +4 -1
- package/dist/web/auth.js +23 -2
- package/dist/web/control.js +2 -1
- 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 +20 -0
- package/dist/web/prefix-gateway.js +150 -0
- package/dist/web/runtime.d.ts +4 -0
- package/dist/web/runtime.js +19 -14
- package/dist/web/server.d.ts +7 -0
- package/dist/web/server.js +293 -25
- package/docs/room-layouts.md +92 -0
- package/docs/task-workspaces.md +130 -0
- package/examples/room-layouts/fresh.yaml +27 -0
- package/examples/room-layouts/shared.yaml +19 -0
- package/package.json +17 -13
- package/presets/README.md +78 -0
- package/presets/brain-catalog.json +8 -2
- package/presets/fleet/agent_templates/Engineer.yaml +5 -0
- 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 +22 -0
- package/presets/fleet/roles/Engineer.yaml +40 -0
- package/presets/fleet/room_layouts/engineering.yaml +25 -0
- package/presets/fleet/room_layouts/pair.yaml +24 -0
- package/presets/fleet/room_layouts/single.yaml +17 -0
- package/presets/fleet/room_layouts/team.yaml +30 -0
- package/presets/fleet/room_templates/engineering.yaml +19 -0
- package/presets/manifest.json +2 -2
- package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
- package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
- package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
- package/dist/web-app/icons/ours-fleet.svg +0 -4
- package/dist/web-app/index.html +0 -17
- package/dist/web-app/manifest.webmanifest +0 -15
- package/dist/web-app/offline.html +0 -18
- package/dist/web-app/sw.js +0 -51
package/dist/session/acp.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type TaskNoticeBinding, type TaskCreatedNotice } from './task-notice.js';
|
|
1
2
|
import * as acp from '@agentclientprotocol/sdk';
|
|
2
3
|
import type { CommonPermissions } from '../config.js';
|
|
3
4
|
import type { AcpMcpServer } from '../harness/types.js';
|
|
@@ -340,6 +341,8 @@ export declare class AcpSession implements AgentSession {
|
|
|
340
341
|
private codexMessagePhase;
|
|
341
342
|
/** Normalize every ACP update losslessly into the durable ledger. */
|
|
342
343
|
private recordConversationUpdate;
|
|
344
|
+
taskNoticeBinding(): TaskNoticeBinding | undefined;
|
|
345
|
+
recordTaskCreated(binding: TaskNoticeBinding, notice: TaskCreatedNotice): void;
|
|
343
346
|
conversationPage(request?: {
|
|
344
347
|
after?: string;
|
|
345
348
|
limit?: number;
|
package/dist/session/acp.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { appendTaskCreated } from './task-notice.js';
|
|
1
2
|
import { spawn } from 'node:child_process';
|
|
2
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
4
|
import { existsSync, lstatSync, readFileSync, readlinkSync, realpathSync, writeFileSync, } from 'node:fs';
|
|
@@ -760,6 +761,8 @@ export class AcpSession {
|
|
|
760
761
|
const existing = this.conversation.receiptFor(command.commandId, bodyDigest);
|
|
761
762
|
if (existing)
|
|
762
763
|
return existing;
|
|
764
|
+
if (command.expectedSessionGeneration !== undefined && command.expectedSessionGeneration !== this.conversationSnapshot().sessionGeneration)
|
|
765
|
+
throw new Error('session_changed: Agent session changed. Record a new voice message.');
|
|
763
766
|
const queued = await this.queuePrompt(command.text, {
|
|
764
767
|
origin: { kind: 'owner-admin-console', commandId: command.commandId },
|
|
765
768
|
actor: { browserSession: command.actorBrowserSession },
|
|
@@ -1805,6 +1808,10 @@ export class AcpSession {
|
|
|
1805
1808
|
});
|
|
1806
1809
|
}
|
|
1807
1810
|
// ── conversation ledger access (AgentSession) ─────────────────────────────
|
|
1811
|
+
taskNoticeBinding() {
|
|
1812
|
+
return this.activeTurn && this.sessionId ? { sessionGeneration: this.sessionGeneration, acpSessionId: this.sessionId, promptId: this.activeTurn.id } : undefined;
|
|
1813
|
+
}
|
|
1814
|
+
recordTaskCreated(binding, notice) { appendTaskCreated(this.conversation, binding, notice); }
|
|
1808
1815
|
conversationPage(request = {}) {
|
|
1809
1816
|
const floor = Number(this.conversationStartCursor ?? 0);
|
|
1810
1817
|
const requested = Number(request.after ?? 0);
|
|
@@ -23,6 +23,7 @@ export declare class RoleTurnArbiter implements AgentSession {
|
|
|
23
23
|
private tail;
|
|
24
24
|
private unsettled;
|
|
25
25
|
private stopping;
|
|
26
|
+
private retiring;
|
|
26
27
|
/** Bumped by `retireStalledAdmission`; every claim carries the one it was made under. */
|
|
27
28
|
private generation;
|
|
28
29
|
/** Resolves the instant the CURRENT generation is retired. */
|
|
@@ -62,6 +63,8 @@ export declare class RoleTurnArbiter implements AgentSession {
|
|
|
62
63
|
tryScheduled(text: string, origin: Extract<PromptOrigin, {
|
|
63
64
|
kind: 'scheduled-loop';
|
|
64
65
|
}>, beforeQueue?: () => void | Promise<void>): Promise<ScheduledAttempt>;
|
|
66
|
+
/** Retire only at the same admission boundary used by all prompt producers. */
|
|
67
|
+
retireIfIdle(expired: (snapshot: SessionSnapshot, queueDepth: number) => boolean): Promise<boolean>;
|
|
65
68
|
stopScheduledAdmission(): void;
|
|
66
69
|
isAlive(): boolean;
|
|
67
70
|
snapshot(): SessionSnapshot;
|
package/dist/session/arbiter.js
CHANGED
|
@@ -29,6 +29,7 @@ export class RoleTurnArbiter {
|
|
|
29
29
|
tail = Promise.resolve();
|
|
30
30
|
unsettled = 0;
|
|
31
31
|
stopping = false;
|
|
32
|
+
retiring = false;
|
|
32
33
|
/** Bumped by `retireStalledAdmission`; every claim carries the one it was made under. */
|
|
33
34
|
generation = 0;
|
|
34
35
|
/** Resolves the instant the CURRENT generation is retired. */
|
|
@@ -89,7 +90,8 @@ export class RoleTurnArbiter {
|
|
|
89
90
|
retired.retire();
|
|
90
91
|
}
|
|
91
92
|
queuePrompt(text, options = {}) {
|
|
92
|
-
return this.exclusive(async () =>
|
|
93
|
+
return this.exclusive(async () => { if (this.retiring)
|
|
94
|
+
throw new Error('Chat is closing'); return this.track(await this.session.queuePrompt(text, options)); });
|
|
93
95
|
}
|
|
94
96
|
async submitPrompt(text, options = {}) {
|
|
95
97
|
return (await this.queuePrompt(text, options)).completion;
|
|
@@ -99,6 +101,8 @@ export class RoleTurnArbiter {
|
|
|
99
101
|
* answers and explicit interrupts must remain able to pass immediately.
|
|
100
102
|
*/
|
|
101
103
|
submitPromptAfterTool(text, options = {}) {
|
|
104
|
+
if (this.retiring)
|
|
105
|
+
return Promise.reject(new Error('Chat is closing'));
|
|
102
106
|
return this.session.submitPromptAfterTool?.(text, options)
|
|
103
107
|
?? this.session.submitPrompt(text, { ...options, interrupt: false, steer: false });
|
|
104
108
|
}
|
|
@@ -136,6 +140,19 @@ export class RoleTurnArbiter {
|
|
|
136
140
|
throw error;
|
|
137
141
|
});
|
|
138
142
|
}
|
|
143
|
+
/** Retire only at the same admission boundary used by all prompt producers. */
|
|
144
|
+
retireIfIdle(expired) {
|
|
145
|
+
return this.exclusive(async () => {
|
|
146
|
+
const snapshot = this.session.snapshot();
|
|
147
|
+
const queue = this.session.conversationSnapshot?.().queueDepth ?? -1;
|
|
148
|
+
if (this.retiring || this.unsettled > 0 || snapshot.readiness !== 'idle' || !expired(snapshot, queue))
|
|
149
|
+
return false;
|
|
150
|
+
this.retiring = true;
|
|
151
|
+
this.stopping = true;
|
|
152
|
+
await this.session.close();
|
|
153
|
+
return true;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
139
156
|
stopScheduledAdmission() { this.stopping = true; }
|
|
140
157
|
isAlive() { return this.session.isAlive(); }
|
|
141
158
|
snapshot() { return this.session.snapshot(); }
|
|
@@ -157,7 +174,8 @@ export class RoleTurnArbiter {
|
|
|
157
174
|
submitPromptBrowser(command) {
|
|
158
175
|
if (!this.session.submitPromptBrowser)
|
|
159
176
|
return Promise.reject(new Error('browser prompt admission is unavailable'));
|
|
160
|
-
return this.exclusive(() => this.
|
|
177
|
+
return this.exclusive(() => { if (this.retiring)
|
|
178
|
+
throw new Error('Chat is closing'); return this.session.submitPromptBrowser(command); });
|
|
161
179
|
}
|
|
162
180
|
interrupt(source = 'local-console') {
|
|
163
181
|
return this.exclusive(async () => interruptOutcome(await this.session.interrupt(source)));
|
|
@@ -55,7 +55,7 @@ export class CodexAppServerTransport {
|
|
|
55
55
|
throw new Error('Codex app-server command is empty');
|
|
56
56
|
const child = spawn(options.argv[0], options.argv.slice(1), {
|
|
57
57
|
cwd: options.cwd,
|
|
58
|
-
env: { ...process.env, ...options.env },
|
|
58
|
+
env: { ...(options.env?.FLEET_OURS_MANAGED === '1' ? {} : process.env), ...options.env },
|
|
59
59
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
60
60
|
});
|
|
61
61
|
// `pid` is assigned synchronously on a successful local spawn. Checking it
|
|
@@ -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';
|
|
@@ -76,6 +77,8 @@ export declare class CodexAppServerSession implements AgentSession {
|
|
|
76
77
|
setControllerAttached(attached: boolean): void;
|
|
77
78
|
exitResult(): ExitRecord | null;
|
|
78
79
|
close(): Promise<void>;
|
|
80
|
+
taskNoticeBinding(): TaskNoticeBinding | undefined;
|
|
81
|
+
recordTaskCreated(binding: TaskNoticeBinding, notice: TaskCreatedNotice): void;
|
|
79
82
|
conversationPage(request?: {
|
|
80
83
|
after?: string;
|
|
81
84
|
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';
|
|
@@ -228,8 +230,10 @@ export class CodexAppServerSession {
|
|
|
228
230
|
}
|
|
229
231
|
async interrupt(source = 'local-console') {
|
|
230
232
|
const active = this.activeTurn;
|
|
231
|
-
if (!active || !this.threadId
|
|
233
|
+
if (!active || !this.threadId)
|
|
232
234
|
return { state: 'settled' };
|
|
235
|
+
if (!active.nativeTurnId)
|
|
236
|
+
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
237
|
active.cancellationSource ??= source;
|
|
234
238
|
this.events.emit('state', { turnId: active.promptId, status: 'running', origin: active.origin });
|
|
235
239
|
this.conversation.appendSafe({
|
|
@@ -335,6 +339,10 @@ export class CodexAppServerSession {
|
|
|
335
339
|
this.conversation.close();
|
|
336
340
|
await this.transport.close();
|
|
337
341
|
}
|
|
342
|
+
taskNoticeBinding() {
|
|
343
|
+
return this.activeTurn && this.threadId ? { sessionGeneration: this.sessionGeneration, acpSessionId: this.threadId, promptId: this.activeTurn.promptId } : undefined;
|
|
344
|
+
}
|
|
345
|
+
recordTaskCreated(binding, notice) { appendTaskCreated(this.conversation, binding, notice); }
|
|
338
346
|
conversationPage(request = {}) {
|
|
339
347
|
const floor = Number(this.conversationStartCursor ?? 0);
|
|
340
348
|
const requested = Number(request.after ?? 0);
|
|
@@ -367,6 +375,8 @@ export class CodexAppServerSession {
|
|
|
367
375
|
const existing = this.conversation.receiptFor(command.commandId, bodyDigest);
|
|
368
376
|
if (existing)
|
|
369
377
|
return existing;
|
|
378
|
+
if (command.expectedSessionGeneration !== undefined && command.expectedSessionGeneration !== this.conversationSnapshot().sessionGeneration)
|
|
379
|
+
throw new Error('session_changed: Agent session changed. Record a new voice message.');
|
|
370
380
|
const queued = await this.queuePrompt(command.text, {
|
|
371
381
|
origin: { kind: 'owner-admin-console', commandId: command.commandId },
|
|
372
382
|
actor: { browserSession: command.actorBrowserSession },
|
|
@@ -415,6 +425,10 @@ export class CodexAppServerSession {
|
|
|
415
425
|
this.reasoningEffort = { value: effort };
|
|
416
426
|
this.readiness = 'idle';
|
|
417
427
|
this.events.emit('state', { status: 'idle', text: `Codex thread ${this.threadId}` });
|
|
428
|
+
this.conversation.appendSafe({
|
|
429
|
+
kind: 'capabilities.updated', sessionGeneration: this.sessionGeneration,
|
|
430
|
+
payload: { commands: [{ name: 'compact', description: textBlock('Compact session context') }] },
|
|
431
|
+
});
|
|
418
432
|
this.conversation.appendSafe({
|
|
419
433
|
kind: 'session.state', sessionGeneration: this.sessionGeneration,
|
|
420
434
|
payload: { status: 'idle' },
|
|
@@ -486,6 +500,12 @@ export class CodexAppServerSession {
|
|
|
486
500
|
promptId, turnId: promptId, source: sourceFor(origin).source, payload: {},
|
|
487
501
|
});
|
|
488
502
|
try {
|
|
503
|
+
if (text.trim() === '/compact') {
|
|
504
|
+
// The acknowledgement is not completion. Standard turn notifications
|
|
505
|
+
// settle the same queued prompt and durable browser receipt.
|
|
506
|
+
await this.transport.request('thread/compact/start', { threadId: this.threadId });
|
|
507
|
+
return await settled;
|
|
508
|
+
}
|
|
489
509
|
const response = await this.transport.request('turn/start', {
|
|
490
510
|
threadId: this.threadId,
|
|
491
511
|
input: [{ type: 'text', text, text_elements: [] }],
|
|
@@ -746,6 +766,10 @@ export class CodexAppServerSession {
|
|
|
746
766
|
toolCallId: scheduled ? 'scheduled-loop-tool' : id, snapshot,
|
|
747
767
|
title: scheduled ? 'scheduled-loop tool' : tool.title, status: tool.status,
|
|
748
768
|
kind: string(item.type),
|
|
769
|
+
...(!scheduled && item.arguments !== undefined ? { rawInput: boundedJson(item.arguments) } : {}),
|
|
770
|
+
...(!scheduled && item.type === 'commandExecution' ? { rawInput: boundedJson({ command: item.command, cwd: item.cwd }), ...(item.aggregatedOutput !== undefined ? { rawOutput: boundedJson({ output: item.aggregatedOutput, exitCode: item.exitCode }) } : {}) } : {}),
|
|
771
|
+
...(!scheduled && item.type === 'fileChange' && item.changes !== undefined ? { rawOutput: boundedJson(item.changes) } : {}),
|
|
772
|
+
...(!scheduled && (item.result !== undefined || item.error !== undefined) ? { rawOutput: boundedJson({ result: item.result, error: item.error }) } : {}),
|
|
749
773
|
},
|
|
750
774
|
});
|
|
751
775
|
}
|
|
@@ -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,11 @@ 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' | '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;
|
|
14
19
|
permissionId?: string;
|
|
15
20
|
optionId?: string;
|
|
16
21
|
since?: number;
|
|
@@ -112,9 +117,13 @@ export declare class RoleControlServer {
|
|
|
112
117
|
private readonly sockets;
|
|
113
118
|
/** Interrupt idempotency: same command id returns the first receipt. */
|
|
114
119
|
private readonly interruptCommands;
|
|
120
|
+
private correspondence?;
|
|
121
|
+
setCorrespondence(value: SupervisorCorrespondence): void;
|
|
115
122
|
private ownerChannel?;
|
|
116
123
|
private loopManager?;
|
|
117
124
|
private reloadConfig?;
|
|
125
|
+
private layoutControl?;
|
|
126
|
+
setLayoutControl(handler: (request: LayoutControlRequest) => Promise<unknown>): void;
|
|
118
127
|
private fleetSpawner?;
|
|
119
128
|
private fleetAuditor?;
|
|
120
129
|
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',
|
|
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,6 +240,7 @@ 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'] : []),
|
|
239
245
|
...(this.session.capabilities?.steering ? ['steering'] : []),
|
|
240
246
|
...(this.session.capabilities?.permissions ? ['permissions'] : []),
|
|
@@ -308,6 +314,13 @@ export class RoleControlServer {
|
|
|
308
314
|
});
|
|
309
315
|
return;
|
|
310
316
|
}
|
|
317
|
+
case 'layout_control': {
|
|
318
|
+
if (request.version !== 2 || !request.layout || !this.layoutControl)
|
|
319
|
+
throw new SessionControlError('rejected', 'layout control unavailable');
|
|
320
|
+
const result = await this.layoutControl(request.layout);
|
|
321
|
+
this.write(socket, { version: 1, id: request.id, ok: true, result });
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
311
324
|
case 'fleet_spawn': {
|
|
312
325
|
if (request.version !== 2 || !request.spawn || typeof request.spawn.name !== 'string')
|
|
313
326
|
throw new SessionControlError('rejected', 'version 2 and typed spawn options are required');
|
|
@@ -362,6 +375,23 @@ export class RoleControlServer {
|
|
|
362
375
|
this.write(socket, { version: 1, id: request.id, ok: true, result: { delivered: true } });
|
|
363
376
|
return;
|
|
364
377
|
}
|
|
378
|
+
case 'agent_contacts':
|
|
379
|
+
case 'agent_history': {
|
|
380
|
+
if (!this.correspondence)
|
|
381
|
+
throw new SessionControlError('rejected', 'Supervisor correspondence API is unavailable; this session needs an updated supervisor.');
|
|
382
|
+
const query = request.agentHistory;
|
|
383
|
+
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))))
|
|
384
|
+
throw new SessionControlError('rejected', 'Valid peer_cid, limit (1–200) and before_seq are required.');
|
|
385
|
+
let result;
|
|
386
|
+
try {
|
|
387
|
+
result = request.command === 'agent_contacts' ? await this.correspondence.contacts() : await this.correspondence.history(query);
|
|
388
|
+
}
|
|
389
|
+
catch {
|
|
390
|
+
throw new SessionControlError('rejected', 'Supervisor correspondence read failed; identity or transport is unavailable.');
|
|
391
|
+
}
|
|
392
|
+
this.write(socket, { version: 1, id: request.id, ok: true, result });
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
365
395
|
case 'owner_channel_manage': {
|
|
366
396
|
if (!request.ownerChannel || typeof request.ownerChannel.action !== 'string')
|
|
367
397
|
throw new SessionControlError('rejected', 'owner-channel management action is required');
|
|
@@ -421,7 +451,10 @@ export class RoleControlServer {
|
|
|
421
451
|
socket.write(JSON.stringify({ version: 1, conversationEvent: event }) + '\n');
|
|
422
452
|
}) };
|
|
423
453
|
}
|
|
454
|
+
case 'submit_voice_prompt':
|
|
424
455
|
case 'submit_prompt_v2': {
|
|
456
|
+
if (request.command === 'submit_voice_prompt' && !request.expectedSessionGeneration?.trim())
|
|
457
|
+
throw new SessionControlError('rejected', 'expectedSessionGeneration is required');
|
|
425
458
|
this.requireConversation(request);
|
|
426
459
|
if (!request.commandId?.trim() || !request.text?.trim() || !request.actor?.trim()
|
|
427
460
|
|| request.source !== 'owner_admin_console')
|
|
@@ -432,6 +465,7 @@ export class RoleControlServer {
|
|
|
432
465
|
const receipt = await this.session.submitPromptBrowser({
|
|
433
466
|
commandId: request.commandId, text: request.text,
|
|
434
467
|
source: 'owner_admin_console', actorBrowserSession: request.actor,
|
|
468
|
+
expectedSessionGeneration: request.expectedSessionGeneration,
|
|
435
469
|
});
|
|
436
470
|
this.write(socket, { version: 1, id: request.id, ok: true, result: receipt });
|
|
437
471
|
}
|
|
@@ -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;
|
|
@@ -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,7 @@ export interface ConversationEventV1 {
|
|
|
262
263
|
adapterMeta?: AdapterMeta[];
|
|
263
264
|
}
|
|
264
265
|
export interface SubmitPromptCommand {
|
|
266
|
+
expectedSessionGeneration?: string;
|
|
265
267
|
/** Idempotency-Key / clientRequestId. Reuse with a different body is a conflict. */
|
|
266
268
|
commandId: string;
|
|
267
269
|
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;
|
|
@@ -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
|
+
}
|
package/dist/spawn.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { storeRoomSecret, storeTemporaryLaunch, preparePermanentAssignment } from './agent-ours/service.js';
|
|
3
|
+
import { ensureWorkspace } from './rooms-tasks/workspace.js';
|
|
1
4
|
import { spawn as spawnChild } from 'node:child_process';
|
|
2
5
|
import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
6
|
import { join } from 'node:path';
|
|
@@ -7,7 +10,7 @@ import { validateIsolationConfig } from './isolation/policy.js';
|
|
|
7
10
|
import { loadConfig, findRole, splitRootFor, validateMonitorConfig, } from './config.js';
|
|
8
11
|
import { applyRole, up } from './ops.js';
|
|
9
12
|
import { START_STAGGER_FILE } from './runner.js';
|
|
10
|
-
import { buildProvenance,
|
|
13
|
+
import { buildProvenance, provenanceOf, withCreationTransaction, writeProvenance, writeRoleFile, } from './creation.js';
|
|
11
14
|
import { VERSION } from './version.js';
|
|
12
15
|
import { recordGeneratedAgentSource } from './generated-agent-source.js';
|
|
13
16
|
import './harness/claude-code.js';
|
|
@@ -239,22 +242,12 @@ export async function spawnPermanent(o, deps, creation = {}) {
|
|
|
239
242
|
return withCreationTransaction({ role: o.name, identity: effectiveIdentity(o) }, async (tx) => {
|
|
240
243
|
assertNameFree(o);
|
|
241
244
|
const cfg = loadConfig(o.configPath);
|
|
242
|
-
//
|
|
243
|
-
//
|
|
245
|
+
// Pin an existing assignment without binding; the supervisor provisions
|
|
246
|
+
// a missing role identity before starting its harness.
|
|
244
247
|
creation.onStage?.('checking_identity');
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
});
|
|
249
|
-
if (guarantee.state === 'created')
|
|
250
|
-
// We minted it; a failed creation must not leave an orphan identity
|
|
251
|
-
// behind. Only ever removes an identity THIS transaction created.
|
|
252
|
-
tx.record({
|
|
253
|
-
stage: `ours identity ${effectiveIdentity(o)}`,
|
|
254
|
-
undo: async () => {
|
|
255
|
-
await creation.identityProvisioner?.remove?.(effectiveIdentity(o));
|
|
256
|
-
},
|
|
257
|
-
});
|
|
248
|
+
const assignment = await preparePermanentAssignment(prepared.role);
|
|
249
|
+
const guarantee = { state: assignment, evidence: assignment === 'verified' ? 'verified' : 'missing' };
|
|
250
|
+
creation.onStage?.('checking_identity', { result: guarantee.evidence, guarantee: guarantee.state });
|
|
258
251
|
const agentRoot = join(splitRootFor(o.configPath ?? defaultConfigPath()), 'agents');
|
|
259
252
|
mkdirSync(agentRoot, { recursive: true });
|
|
260
253
|
creation.onStage?.('writing_role');
|
|
@@ -329,6 +322,12 @@ export async function spawnTemp(o, binPath, launch = independentSupervisor, crea
|
|
|
329
322
|
if (o.isolationFile)
|
|
330
323
|
readIsolationFile(o.isolationFile); // fail before reserving
|
|
331
324
|
const prepared = resolvedSpawn(o); // canonical validation before mutation
|
|
325
|
+
if (o.roomMemberStartup?.workspace) {
|
|
326
|
+
const workspace = o.roomMemberStartup.workspace;
|
|
327
|
+
if (prepared.role.cwd !== workspace.path)
|
|
328
|
+
throw new Error('room member cwd must equal its recorded owned workspace');
|
|
329
|
+
ensureWorkspace(workspace);
|
|
330
|
+
}
|
|
332
331
|
// Retire only supervisors whose recorded owner is definitively stopped. This
|
|
333
332
|
// bounded pass keeps the active roster clean without deleting old evidence.
|
|
334
333
|
await reclaimStaleTempState();
|
|
@@ -354,6 +353,10 @@ async function spawnTempInner(o, preparedRole, binPath, launch, tx, onStage) {
|
|
|
354
353
|
temporaryLoopSource: o.loopSource ?? (temporaryLoops?.length ? 'agent-template' : 'omitted'),
|
|
355
354
|
roomMemberStartup: o.roomMemberStartup,
|
|
356
355
|
};
|
|
356
|
+
storeTemporaryLaunch(role, o.creationActionId ?? randomUUID());
|
|
357
|
+
storeRoomSecret(role);
|
|
358
|
+
if (role.roomMemberStartup)
|
|
359
|
+
role.roomMemberStartup = { ...role.roomMemberStartup, invite: '' };
|
|
357
360
|
onStage?.('writing_role');
|
|
358
361
|
const dir = applyRole(role, { temp: true, identityGuarantee: 'unverified' });
|
|
359
362
|
const provenance = buildProvenance({
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type DaemonGenerationProbe } from './daemon-recovery.js';
|
|
2
|
+
export declare const STARTUP_WAIT_MS = 240000;
|
|
3
|
+
export declare const STARTUP_RETRY_MS = 5000;
|
|
4
|
+
interface ReadinessDeps {
|
|
5
|
+
probe(env: NodeJS.ProcessEnv): Promise<DaemonGenerationProbe>;
|
|
6
|
+
now(): number;
|
|
7
|
+
sleep(ms: number): Promise<void>;
|
|
8
|
+
log(message: string): void;
|
|
9
|
+
env: NodeJS.ProcessEnv;
|
|
10
|
+
}
|
|
11
|
+
/** Read-only preflight: never binds identities, starts a harness or edits its ledger. */
|
|
12
|
+
export declare function waitForRoleDaemon(name: string, configPath?: string, partial?: Partial<ReadinessDeps>): Promise<void>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { agentDir } from './paths.js';
|
|
2
|
+
import { findRole, loadConfig } from './config.js';
|
|
3
|
+
import { resolveConfigPath } from './runner.js';
|
|
4
|
+
import { probeDaemonGeneration } from './daemon-recovery.js';
|
|
5
|
+
export const STARTUP_WAIT_MS = 240_000;
|
|
6
|
+
export const STARTUP_RETRY_MS = 5_000;
|
|
7
|
+
/** Read-only preflight: never binds identities, starts a harness or edits its ledger. */
|
|
8
|
+
export async function waitForRoleDaemon(name, configPath, partial = {}) {
|
|
9
|
+
const deps = {
|
|
10
|
+
probe: env => probeDaemonGeneration(((url, init) => globalThis.fetch(url, {
|
|
11
|
+
...init, signal: AbortSignal.timeout(10_000),
|
|
12
|
+
})), env),
|
|
13
|
+
now: Date.now,
|
|
14
|
+
sleep: ms => new Promise(resolve => setTimeout(resolve, ms)),
|
|
15
|
+
log: message => process.stderr.write(message + '\n'),
|
|
16
|
+
env: process.env,
|
|
17
|
+
...partial,
|
|
18
|
+
};
|
|
19
|
+
const path = resolveConfigPath(agentDir(name), configPath);
|
|
20
|
+
const role = findRole(loadConfig(path), name);
|
|
21
|
+
const env = { ...deps.env, ...role.env };
|
|
22
|
+
const deadline = deps.now() + STARTUP_WAIT_MS;
|
|
23
|
+
let lastReason = '';
|
|
24
|
+
while (deps.now() < deadline) {
|
|
25
|
+
const result = await deps.probe(env);
|
|
26
|
+
if (result.state === 'ready') {
|
|
27
|
+
deps.log(`[${name}] daemon ready; starting supervised agent`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// Only structured diagnostic codes, never endpoint responses or credentials.
|
|
31
|
+
const reason = /^[A-Z][A-Z0-9_]*$/.test(result.reason) ? result.reason : 'DAEMON_UNAVAILABLE';
|
|
32
|
+
if (reason !== lastReason)
|
|
33
|
+
deps.log(`[${name}] waiting for configured daemon: ${reason}`);
|
|
34
|
+
lastReason = reason;
|
|
35
|
+
await deps.sleep(Math.min(STARTUP_RETRY_MS, Math.max(0, deadline - deps.now())));
|
|
36
|
+
}
|
|
37
|
+
throw new Error(`Daemon not ready (${lastReason}); service manager will retry startup`);
|
|
38
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { clientConfigPath } from '../client-profile.js';
|
|
1
2
|
import { existsSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { home, logsRoot } from '../paths.js';
|
|
@@ -22,7 +23,7 @@ const xml = (value) => value
|
|
|
22
23
|
.replaceAll("'", ''');
|
|
23
24
|
function plist(name, binPath) {
|
|
24
25
|
const log = join(logsRoot(), `${name}.log`);
|
|
25
|
-
const config = process.env
|
|
26
|
+
const config = clientConfigPath(process.env);
|
|
26
27
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
27
28
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
28
29
|
<plist version="1.0">
|