@ours.network/fleet 1.1.0-nightly.9 → 1.1.0
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 +231 -46
- package/dist/application/capabilities.js +18 -6
- package/dist/application/fleet-query-service.js +7 -4
- package/dist/application/role-command-service.js +9 -0
- package/dist/application/role-creation-service.d.ts +4 -4
- package/dist/application/role-creation-service.js +8 -1
- package/dist/application/role-repository.d.ts +4 -2
- package/dist/application/role-repository.js +6 -3
- package/dist/application/session-control.d.ts +3 -1
- package/dist/application/session-control.js +4 -2
- package/dist/application/task-room-service.d.ts +92 -39
- package/dist/application/task-room-service.js +562 -289
- package/dist/briefing.js +79 -22
- package/dist/build-info.json +5 -5
- package/dist/canonical-json.d.ts +2 -0
- package/dist/canonical-json.js +10 -0
- package/dist/cli.js +129 -47
- package/dist/config.d.ts +38 -3
- package/dist/config.js +193 -28
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +7 -4
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +181 -41
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +40 -4
- package/dist/fleet-command-audit.d.ts +22 -1
- package/dist/fleet-command-audit.js +355 -50
- package/dist/fleet-proxy.d.ts +5 -2
- package/dist/harness/claude-code-session.js +2 -1
- package/dist/harness/codex-app-server-proxy.d.ts +40 -0
- package/dist/harness/codex-app-server-proxy.js +348 -14
- package/dist/harness/codex-session.d.ts +9 -2
- package/dist/harness/codex-session.js +18 -2
- package/dist/harness/codex.d.ts +5 -1
- package/dist/harness/codex.js +104 -10
- package/dist/harness/types.d.ts +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/init-guidance.d.ts +1 -1
- package/dist/init-guidance.js +1 -1
- package/dist/init-wizard.d.ts +98 -0
- package/dist/init-wizard.js +631 -0
- package/dist/lifecycle-summary.d.ts +134 -0
- package/dist/lifecycle-summary.js +179 -0
- package/dist/loops/config.d.ts +10 -1
- package/dist/loops/config.js +38 -5
- package/dist/model-recovery.d.ts +1 -1
- package/dist/ops.js +2 -17
- package/dist/owner-channel/channel.d.ts +19 -3
- package/dist/owner-channel/channel.js +301 -120
- package/dist/owner-channel/commands.d.ts +28 -6
- package/dist/owner-channel/commands.js +109 -27
- package/dist/owner-channel/lifecycle-outbox.d.ts +24 -0
- package/dist/owner-channel/lifecycle-outbox.js +87 -0
- package/dist/owner-channel/ours-client.d.ts +19 -0
- package/dist/owner-channel/ours-client.js +3 -0
- package/dist/permissions.js +1 -1
- package/dist/preset-migration.d.ts +42 -0
- package/dist/preset-migration.js +450 -0
- package/dist/reports/artifact.d.ts +10 -0
- package/dist/reports/artifact.js +32 -0
- package/dist/reports/index.d.ts +5 -0
- package/dist/reports/index.js +5 -0
- package/dist/reports/render.d.ts +6 -0
- package/dist/reports/render.js +240 -0
- package/dist/reports/service.d.ts +42 -0
- package/dist/reports/service.js +57 -0
- package/dist/reports/tasks.d.ts +52 -0
- package/dist/reports/tasks.js +177 -0
- package/dist/reports/types.d.ts +315 -0
- package/dist/reports/types.js +6 -0
- package/dist/resolved-plan.js +13 -1
- package/dist/rooms-tasks/cli.d.ts +2 -0
- package/dist/rooms-tasks/cli.js +361 -285
- package/dist/rooms-tasks/close.d.ts +9 -1
- package/dist/rooms-tasks/close.js +23 -7
- package/dist/rooms-tasks/config.js +16 -25
- package/dist/rooms-tasks/cowork-adapter.d.ts +5 -0
- package/dist/rooms-tasks/cowork-adapter.js +13 -0
- package/dist/rooms-tasks/deletion.d.ts +51 -0
- package/dist/rooms-tasks/deletion.js +217 -0
- package/dist/rooms-tasks/external-worker.d.ts +8 -0
- package/dist/rooms-tasks/external-worker.js +38 -0
- package/dist/rooms-tasks/launch-snapshot.d.ts +19 -0
- package/dist/rooms-tasks/launch-snapshot.js +181 -0
- package/dist/rooms-tasks/member-overrides.d.ts +30 -0
- package/dist/rooms-tasks/member-overrides.js +204 -0
- package/dist/rooms-tasks/member-startup.d.ts +1 -0
- package/dist/rooms-tasks/member-startup.js +9 -2
- package/dist/rooms-tasks/provision.js +146 -83
- package/dist/rooms-tasks/room-state.d.ts +1 -0
- package/dist/rooms-tasks/room-state.js +12 -1
- package/dist/rooms-tasks/task-room-name.d.ts +12 -0
- package/dist/rooms-tasks/task-room-name.js +61 -0
- package/dist/rooms-tasks/task-state.d.ts +115 -3
- package/dist/rooms-tasks/task-state.js +361 -11
- package/dist/rooms-tasks/templates.d.ts +4 -1
- package/dist/rooms-tasks/templates.js +34 -3
- package/dist/rooms-tasks/terminal.d.ts +3 -0
- package/dist/rooms-tasks/terminal.js +10 -4
- package/dist/rooms-tasks/types.d.ts +92 -7
- package/dist/rooms-tasks/types.js +14 -1
- package/dist/runner.d.ts +3 -0
- package/dist/runner.js +91 -44
- package/dist/session/acp.d.ts +4 -1
- package/dist/session/acp.js +29 -13
- package/dist/session/arbiter.d.ts +2 -1
- package/dist/session/arbiter.js +2 -0
- package/dist/session/codex-app-server-transport.d.ts +53 -0
- package/dist/session/codex-app-server-transport.js +184 -0
- package/dist/session/codex-app-server.d.ts +122 -0
- package/dist/session/codex-app-server.js +1031 -0
- package/dist/session/control.d.ts +1 -1
- package/dist/session/control.js +17 -1
- package/dist/session/conversation-types.d.ts +4 -4
- package/dist/session/types.d.ts +24 -6
- package/dist/session/types.js +28 -0
- package/dist/spawn.d.ts +9 -0
- package/dist/spawn.js +77 -9
- package/dist/web/fleet-config-service.d.ts +1 -0
- package/dist/web/fleet-config-service.js +25 -7
- package/dist/web/runtime.js +11 -6
- package/dist/web/server.js +28 -1
- package/dist/web-app/assets/{index-BbTc5KtZ.js → index-C0h3ALvs.js} +8 -8
- package/dist/web-app/index.html +1 -1
- package/package.json +4 -4
- package/presets/brain-catalog.json +17 -0
- package/presets/fleet/{agents → agent_templates}/Critic.yaml +2 -0
- package/presets/fleet/{agents/Agent.yaml → agent_templates/Developer.yaml} +3 -1
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +50 -0
- package/presets/fleet/agents/{Developer.yaml → FleetCoordinator.yaml} +1 -1
- package/presets/fleet/brains/claude-fable-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +37 -0
- package/presets/fleet/roles/Critic.yaml +23 -1
- package/presets/fleet/roles/Developer.yaml +27 -5
- package/presets/fleet/roles/LocalCoordinator.yaml +35 -0
- package/presets/fleet/room_templates/pair.yaml +9 -7
- package/presets/fleet/room_templates/single.yaml +5 -4
- package/presets/fleet/room_templates/team.yaml +12 -8
- package/presets/manifest.json +2 -1
- package/presets/fleet/agents/Architect.yaml +0 -3
- package/presets/fleet/agents/Secretary.yaml +0 -3
- package/presets/fleet/agents/Tester.yaml +0 -3
- package/presets/fleet/roles/Agent.yaml +0 -6
- package/presets/fleet/roles/Architect.yaml +0 -6
- package/presets/fleet/roles/Secretary.yaml +0 -6
- package/presets/fleet/roles/Tester.yaml +0 -6
|
@@ -9,7 +9,7 @@ export interface ControlRequest {
|
|
|
9
9
|
version: 1 | 2 | 3;
|
|
10
10
|
id: string;
|
|
11
11
|
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_finish';
|
|
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';
|
|
13
13
|
text?: string;
|
|
14
14
|
permissionId?: string;
|
|
15
15
|
optionId?: string;
|
package/dist/session/control.js
CHANGED
|
@@ -3,7 +3,7 @@ import { chmodSync, existsSync, readFileSync, rmSync, writeFileSync } from 'node
|
|
|
3
3
|
import { createConnection, createServer } from 'node:net';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { SessionControlError } from './types.js';
|
|
6
|
-
import { validateFleetAuditBegin, validateFleetAuditFinish, } from '../fleet-command-audit.js';
|
|
6
|
+
import { validateFleetAuditBegin, validateFleetAuditFinish, validateFleetAuditPresentations, } from '../fleet-command-audit.js';
|
|
7
7
|
import { interruptSession, queueSessionPrompt, respondSessionPermission, respondSessionPermissionV2, } from '../application/session-mutations.js';
|
|
8
8
|
const MAX_LINE_BYTES = 64 * 1024;
|
|
9
9
|
/** Commands that require protocol version 3. */
|
|
@@ -236,6 +236,9 @@ export class RoleControlServer {
|
|
|
236
236
|
features: [
|
|
237
237
|
'events_since', 'observer_follow', 'retained_range',
|
|
238
238
|
...(this.session.conversationPage ? ['conversation_v3'] : []),
|
|
239
|
+
...(this.session.capabilities?.steering ? ['steering'] : []),
|
|
240
|
+
...(this.session.capabilities?.permissions ? ['permissions'] : []),
|
|
241
|
+
...(this.session.capabilities?.messagePhases ? ['message_phases'] : []),
|
|
239
242
|
],
|
|
240
243
|
},
|
|
241
244
|
});
|
|
@@ -346,6 +349,19 @@ export class RoleControlServer {
|
|
|
346
349
|
this.write(socket, { version: 1, id: request.id, ok: true, result });
|
|
347
350
|
return;
|
|
348
351
|
}
|
|
352
|
+
case 'fleet_audit_present': {
|
|
353
|
+
if (!this.fleetAuditor)
|
|
354
|
+
throw new SessionControlError('rejected', 'fleet auditing is unavailable');
|
|
355
|
+
try {
|
|
356
|
+
validateFleetAuditPresentations(request.audit?.presentations);
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
throw new SessionControlError('rejected', 'valid Fleet lifecycle presentations are required');
|
|
360
|
+
}
|
|
361
|
+
await this.fleetAuditor.present(request.audit.presentations);
|
|
362
|
+
this.write(socket, { version: 1, id: request.id, ok: true, result: { delivered: true } });
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
349
365
|
case 'owner_channel_manage': {
|
|
350
366
|
if (!request.ownerChannel || typeof request.ownerChannel.action !== 'string')
|
|
351
367
|
throw new SessionControlError('rejected', 'owner-channel management action is required');
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { PromptOrigin, TurnCancellationSource, TurnOutcome } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Durable conversation domain schema for
|
|
3
|
+
* Durable conversation domain schema for managed-session consoles.
|
|
4
4
|
*
|
|
5
5
|
* `SessionEvent` in ./types.js remains the compact diagnostic projection; the
|
|
6
6
|
* types here describe the durable per-role conversation ledger. Nothing in this
|
|
7
|
-
* file touches the wire:
|
|
8
|
-
*
|
|
7
|
+
* file touches the wire: transport updates are reduced into these shapes by a
|
|
8
|
+
* session adapter, and the store assigns `seq`/`eventId`/timestamps.
|
|
9
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
|
-
/** A well-formed
|
|
11
|
+
/** A well-formed transport update this version cannot represent. Bounded, never a crash. */
|
|
12
12
|
| 'unsupported';
|
|
13
13
|
/** Where a conversation record came from. Typed provenance, never prompt text. */
|
|
14
14
|
export type ConversationSource = 'owner_admin_console' | 'owner_channel' | 'fleet_monitor' | 'scheduled_loop' | 'startup' | 'local_console' | 'agent' | 'agent_replay';
|
package/dist/session/types.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ export type PromptOrigin = {
|
|
|
33
33
|
commandId: string;
|
|
34
34
|
};
|
|
35
35
|
export interface RuntimeSelectorMetadata {
|
|
36
|
-
/** Exact provider/model identifier reported by the live
|
|
36
|
+
/** Exact provider/model identifier reported by the live managed session. */
|
|
37
37
|
value: string;
|
|
38
38
|
/** Complete provider-supplied label for that exact value, when available. */
|
|
39
39
|
label?: string;
|
|
@@ -75,6 +75,8 @@ export interface TurnResult {
|
|
|
75
75
|
export type ControlFailureKind = 'offline' | 'control-unavailable' | 'timeout' | 'rejected' | 'backend';
|
|
76
76
|
/** Stable body-free reason shared by ACP recovery and durable ingress. */
|
|
77
77
|
export declare const ACP_CANCEL_DEADLINE_EXCEEDED = "ACP_CANCEL_DEADLINE_EXCEEDED";
|
|
78
|
+
/** Native Codex equivalent; kept distinct so diagnostics name the failing transport. */
|
|
79
|
+
export declare const CODEX_APP_SERVER_CANCEL_DEADLINE_EXCEEDED = "CODEX_APP_SERVER_CANCEL_DEADLINE_EXCEEDED";
|
|
78
80
|
export declare class SessionControlError extends Error {
|
|
79
81
|
readonly kind: ControlFailureKind;
|
|
80
82
|
/** Stable body-free machine reason for recovery/audit decisions. */
|
|
@@ -170,7 +172,7 @@ export interface SubmitPromptOptions {
|
|
|
170
172
|
interruptSource?: TurnCancellationSource;
|
|
171
173
|
/** Typed in-process provenance. Text markers never grant this origin. */
|
|
172
174
|
origin?: PromptOrigin;
|
|
173
|
-
/**
|
|
175
|
+
/** Inject into active work when the selected backend supports steering. */
|
|
174
176
|
steer?: boolean;
|
|
175
177
|
/** Audit-grade actor detail persisted with the conversation admission record. */
|
|
176
178
|
actor?: {
|
|
@@ -200,11 +202,25 @@ export interface SessionSnapshot {
|
|
|
200
202
|
activity?: SessionActivity;
|
|
201
203
|
}
|
|
202
204
|
export interface SessionActivity {
|
|
203
|
-
/**
|
|
205
|
+
/** Tool calls currently reserved (lifecycle open or permission pending). */
|
|
204
206
|
activeToolCalls: number;
|
|
205
207
|
/** When the agent last sent ANY session update, replay excluded. */
|
|
206
208
|
lastUpdateAt?: string;
|
|
207
209
|
}
|
|
210
|
+
/** Provider-neutral behavior exposed by a session transport. */
|
|
211
|
+
export interface AgentSessionCapabilities {
|
|
212
|
+
streaming: boolean;
|
|
213
|
+
durableConversation: boolean;
|
|
214
|
+
promptInput: boolean;
|
|
215
|
+
interrupt: boolean;
|
|
216
|
+
steering: boolean;
|
|
217
|
+
permissions: boolean;
|
|
218
|
+
toolBoundaryDelivery: boolean;
|
|
219
|
+
messagePhases: boolean;
|
|
220
|
+
resume: boolean;
|
|
221
|
+
}
|
|
222
|
+
/** Conservative static capabilities used before a live session is reachable. */
|
|
223
|
+
export declare function sessionBackendCapabilities(backend: SessionBackendId, harness?: string): AgentSessionCapabilities;
|
|
208
224
|
export type SessionEventKind = 'state' | 'agent_text' | 'thought' | 'tool_call' | 'tool_update' | 'permission' | 'monitor_delivery' | 'turn_stop' | 'error';
|
|
209
225
|
/** What a settled permission request resolved to. */
|
|
210
226
|
export type PermissionDecision = 'allowed' | 'denied' | 'cancelled';
|
|
@@ -219,7 +235,7 @@ export interface SessionEvent {
|
|
|
219
235
|
text?: string;
|
|
220
236
|
/**
|
|
221
237
|
* Adapter-authenticated presentation phase for assistant text. Only the
|
|
222
|
-
* exact Codex
|
|
238
|
+
* exact Codex phase marker is promoted; absence/unknown values stay unset.
|
|
223
239
|
*/
|
|
224
240
|
messagePhase?: 'commentary' | 'final_answer';
|
|
225
241
|
/** Stable adapter message/item id when supplied. */
|
|
@@ -261,11 +277,13 @@ export interface ConversationHandlePage {
|
|
|
261
277
|
/**
|
|
262
278
|
* The live, harness-neutral contract between Fleet and one agent session.
|
|
263
279
|
* Harness adapters construct this handle; orchestration code must not depend
|
|
264
|
-
* on
|
|
280
|
+
* on any concrete transport which implements it.
|
|
265
281
|
*/
|
|
266
282
|
export interface AgentSession {
|
|
267
283
|
readonly backend: SessionBackendId;
|
|
268
284
|
readonly pid: number;
|
|
285
|
+
/** Live capabilities; optional only for source compatibility with external adapters. */
|
|
286
|
+
readonly capabilities?: AgentSessionCapabilities;
|
|
269
287
|
isAlive(): boolean;
|
|
270
288
|
snapshot(): SessionSnapshot;
|
|
271
289
|
conversationPage?(request: {
|
|
@@ -283,7 +301,7 @@ export interface AgentSession {
|
|
|
283
301
|
queuePrompt(text: string, options?: SubmitPromptOptions): Promise<QueuedPrompt>;
|
|
284
302
|
/** Queue a prompt and wait for its terminal result. */
|
|
285
303
|
submitPrompt(text: string, options?: SubmitPromptOptions): Promise<TurnResult>;
|
|
286
|
-
/** Monitor-only
|
|
304
|
+
/** Monitor-only safe-boundary delivery. Never implies human/control cancellation. */
|
|
287
305
|
submitPromptAfterTool?(text: string, options?: SubmitPromptOptions): Promise<TurnResult>;
|
|
288
306
|
/**
|
|
289
307
|
* Cancel the active turn. Resolves when the cancellation has taken effect —
|
package/dist/session/types.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Stable body-free reason shared by ACP recovery and durable ingress. */
|
|
2
2
|
export const ACP_CANCEL_DEADLINE_EXCEEDED = 'ACP_CANCEL_DEADLINE_EXCEEDED';
|
|
3
|
+
/** Native Codex equivalent; kept distinct so diagnostics name the failing transport. */
|
|
4
|
+
export const CODEX_APP_SERVER_CANCEL_DEADLINE_EXCEEDED = 'CODEX_APP_SERVER_CANCEL_DEADLINE_EXCEEDED';
|
|
3
5
|
export class SessionControlError extends Error {
|
|
4
6
|
kind;
|
|
5
7
|
reasonCode;
|
|
@@ -53,3 +55,29 @@ export function turnResult(accepted, outcome, detail, output, cancellationSource
|
|
|
53
55
|
...(outcome === 'cancelled' && cancellationSource ? { cancellationSource } : {}),
|
|
54
56
|
};
|
|
55
57
|
}
|
|
58
|
+
/** Conservative static capabilities used before a live session is reachable. */
|
|
59
|
+
export function sessionBackendCapabilities(backend, harness) {
|
|
60
|
+
if (backend === 'codex-app-server')
|
|
61
|
+
return {
|
|
62
|
+
streaming: true,
|
|
63
|
+
durableConversation: true,
|
|
64
|
+
promptInput: true,
|
|
65
|
+
interrupt: true,
|
|
66
|
+
steering: true,
|
|
67
|
+
permissions: true,
|
|
68
|
+
toolBoundaryDelivery: false,
|
|
69
|
+
messagePhases: true,
|
|
70
|
+
resume: true,
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
streaming: true,
|
|
74
|
+
durableConversation: true,
|
|
75
|
+
promptInput: true,
|
|
76
|
+
interrupt: true,
|
|
77
|
+
steering: false,
|
|
78
|
+
permissions: true,
|
|
79
|
+
toolBoundaryDelivery: true,
|
|
80
|
+
messagePhases: harness === 'codex',
|
|
81
|
+
resume: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
package/dist/spawn.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IsolationConfig } from './isolation/types.js';
|
|
2
|
+
import type { AgentLoopsConfig } from './loops/config.js';
|
|
2
3
|
import { type ApprovalMode, type FilesystemMode, type ResolvedRole, type MonitorConfig, type UnattendedMode, type RoomMemberStartup, type AgentDefinition, type AgentSelection } from './config.js';
|
|
3
4
|
import { type OpsDeps } from './ops.js';
|
|
4
5
|
import { type CreationDeps, type CreationProvenance } from './creation.js';
|
|
@@ -40,6 +41,12 @@ export interface SpawnOpts {
|
|
|
40
41
|
* input in this release.
|
|
41
42
|
*/
|
|
42
43
|
isolationFile?: string;
|
|
44
|
+
/** Trusted YAML containing exactly a top-level `loops:` map. Temporary launches only. */
|
|
45
|
+
loopsFile?: string;
|
|
46
|
+
/** Explicitly override any selected Agent Template loops with none. */
|
|
47
|
+
noLoops?: boolean;
|
|
48
|
+
/** Internal source label retained after room-member plan resolution. */
|
|
49
|
+
loopSource?: 'agent-template' | 'cli' | 'omitted';
|
|
43
50
|
overseeInterval?: string;
|
|
44
51
|
configPath?: string;
|
|
45
52
|
dryRun?: boolean;
|
|
@@ -56,6 +63,8 @@ export declare function agentDefinitionFromSpawn(o: SpawnOpts): AgentDefinition;
|
|
|
56
63
|
* must fail before any artifact exists.
|
|
57
64
|
*/
|
|
58
65
|
export declare function readIsolationFile(path: string): IsolationConfig;
|
|
66
|
+
/** Read a private canonical temporary-loop override before any creation side effect. */
|
|
67
|
+
export declare function readLoopsFile(path: string): AgentLoopsConfig;
|
|
59
68
|
export declare function validateSpawnOpts(o: SpawnOpts): void;
|
|
60
69
|
/** The ours identity a spawn will bind: explicit, else the role name. */
|
|
61
70
|
export declare const effectiveIdentity: (o: SpawnOpts) => string;
|
package/dist/spawn.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn as spawnChild } from 'node:child_process';
|
|
2
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { parse, stringify } from 'yaml';
|
|
5
5
|
import { agentDir, defaultConfigPath } from './paths.js';
|
|
@@ -22,9 +22,12 @@ export function agentDefinitionFromSpawn(o) {
|
|
|
22
22
|
if (o.agentDefinition) {
|
|
23
23
|
if (o.brain !== undefined || o.role !== undefined || o.cwd !== undefined
|
|
24
24
|
|| o.coordinator !== undefined || o.approval !== undefined || o.filesystem !== undefined
|
|
25
|
-
|| o.unattended !== undefined || o.isolationFile !== undefined || o.monitorConfig !== undefined
|
|
25
|
+
|| o.unattended !== undefined || o.isolationFile !== undefined || o.monitorConfig !== undefined
|
|
26
|
+
|| o.loopsFile !== undefined)
|
|
26
27
|
throw new Error('canonical agentDefinition conflicts with separate Agent fields');
|
|
27
28
|
const definition = structuredClone(o.agentDefinition);
|
|
29
|
+
if (o.noLoops === true && definition.loops !== undefined)
|
|
30
|
+
throw new Error('canonical agentDefinition loops conflict with explicit no-loops policy');
|
|
28
31
|
if (o.identity)
|
|
29
32
|
definition.identity = o.identity;
|
|
30
33
|
return definition;
|
|
@@ -46,12 +49,21 @@ export function agentDefinitionFromSpawn(o) {
|
|
|
46
49
|
...(permissions ? { permissions } : {}),
|
|
47
50
|
...(o.isolationFile ? { isolation: readIsolationFile(o.isolationFile) } : {}),
|
|
48
51
|
...(o.monitorConfig ? { monitor: structuredClone(o.monitorConfig) } : {}),
|
|
52
|
+
...(o.loopsFile ? { loops: readLoopsFile(o.loopsFile) } : {}),
|
|
49
53
|
};
|
|
50
54
|
}
|
|
51
55
|
function resolvedSpawn(o) {
|
|
52
56
|
const definition = agentDefinitionFromSpawn(o);
|
|
53
|
-
const cfg = loadConfig(o.configPath, {
|
|
54
|
-
|
|
57
|
+
const cfg = loadConfig(o.configPath, {
|
|
58
|
+
additionalAgent: { id: o.name, definition, temporary: o.temp === true },
|
|
59
|
+
});
|
|
60
|
+
const role = findRole(cfg, o.name);
|
|
61
|
+
if (o.temp)
|
|
62
|
+
role.temporaryLoopSource = o.loopSource
|
|
63
|
+
?? (role.temporaryLoops?.length ? 'agent-template' : 'omitted');
|
|
64
|
+
if (o.noLoops === true)
|
|
65
|
+
role.temporaryLoops = [];
|
|
66
|
+
return { definition, role };
|
|
55
67
|
}
|
|
56
68
|
/**
|
|
57
69
|
* Read and validate an `--isolation-file`. The file is the existing
|
|
@@ -78,7 +90,37 @@ export function readIsolationFile(path) {
|
|
|
78
90
|
throw new Error(`--isolation-file ${path}: ${problems.join('; ')}`);
|
|
79
91
|
return cfg;
|
|
80
92
|
}
|
|
93
|
+
/** Read a private canonical temporary-loop override before any creation side effect. */
|
|
94
|
+
export function readLoopsFile(path) {
|
|
95
|
+
const stat = lstatSync(path);
|
|
96
|
+
const uid = process.getuid?.();
|
|
97
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1_000_000
|
|
98
|
+
|| (uid !== undefined && stat.uid !== uid) || (stat.mode & 0o777) !== 0o600)
|
|
99
|
+
throw new Error(`${path}: loops file must be an owner-only regular file no larger than 1 MB`);
|
|
100
|
+
let raw;
|
|
101
|
+
try {
|
|
102
|
+
raw = parse(readFileSync(path, 'utf8'));
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
throw new Error(`--loops-file ${path}: ${error.message}`);
|
|
106
|
+
}
|
|
107
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)
|
|
108
|
+
|| !Object.hasOwn(raw, 'loops')
|
|
109
|
+
|| Object.keys(raw).some(key => key !== 'loops'))
|
|
110
|
+
throw new Error(`${path}: expected exactly a top-level loops: mapping`);
|
|
111
|
+
const loops = raw.loops;
|
|
112
|
+
if (!loops || typeof loops !== 'object' || Array.isArray(loops)
|
|
113
|
+
|| !Object.keys(loops).length)
|
|
114
|
+
throw new Error(`${path}: loops must be a non-empty mapping; use --no-loops to disable loops`);
|
|
115
|
+
return structuredClone(loops);
|
|
116
|
+
}
|
|
81
117
|
export function validateSpawnOpts(o) {
|
|
118
|
+
if (o.loopsFile && o.noLoops === true)
|
|
119
|
+
throw new Error('--loops-file and --no-loops are mutually exclusive');
|
|
120
|
+
if ((o.loopsFile || o.noLoops === true || o.agentDefinition?.loops !== undefined) && !o.temp)
|
|
121
|
+
throw new Error('temporary Agent loops require --temp');
|
|
122
|
+
if (o.loopsFile)
|
|
123
|
+
readLoopsFile(o.loopsFile);
|
|
82
124
|
if (o.approval && !['ask', 'auto', 'allow', 'deny'].includes(o.approval))
|
|
83
125
|
throw new Error(`invalid --approval '${o.approval}'; allowed: ask, auto, allow (deprecated alias: deny)`);
|
|
84
126
|
if (o.filesystem && !['read-only', 'workspace', 'unrestricted'].includes(o.filesystem))
|
|
@@ -102,7 +144,15 @@ export function validateSpawnOpts(o) {
|
|
|
102
144
|
* by the reservation rather than by this function.
|
|
103
145
|
*/
|
|
104
146
|
function assertNameFree(o) {
|
|
105
|
-
|
|
147
|
+
let cfg;
|
|
148
|
+
try {
|
|
149
|
+
cfg = loadConfig(o.configPath);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (error instanceof Error && error.message.includes(`resolve to duplicate identity '${effectiveIdentity(o)}'`))
|
|
153
|
+
throw new Error(`ours identity '${effectiveIdentity(o)}' is already taken or being created right now`);
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
106
156
|
if (cfg.roles.some(r => r.name === o.name))
|
|
107
157
|
throw new Error(`role '${o.name}' already exists (${cfg.roles.find(r => r.name === o.name).sourceFile})`);
|
|
108
158
|
if (existsSync(agentDir(o.name)) || existsSync(agentDir(o.name, true)))
|
|
@@ -141,7 +191,7 @@ export function spawnDryRun(o) {
|
|
|
141
191
|
* `env`, `bio`, `persona` and `harness_options` are deliberately absent: the
|
|
142
192
|
* record exists to be read, and must not become a place credentials collect.
|
|
143
193
|
*/
|
|
144
|
-
function provenanceSettings(o, defaults) {
|
|
194
|
+
function provenanceSettings(o, defaults, resolvedLoops = []) {
|
|
145
195
|
const perms = (defaults.permissions ?? {});
|
|
146
196
|
const callerDefaults = new Set(o.inheritedFromCaller ?? []);
|
|
147
197
|
const tagged = (key, entry) => callerDefaults.has(key) ? { ...entry, source: 'caller-role' } : entry;
|
|
@@ -161,6 +211,16 @@ function provenanceSettings(o, defaults) {
|
|
|
161
211
|
? { value: 'declared via --isolation-file', source: 'cli' }
|
|
162
212
|
: { value: defaults.isolation ? 'from fleet defaults' : undefined, source: defaults.isolation ? 'fleet-default' : 'built-in' },
|
|
163
213
|
monitor: tagged('monitorConfig', provenanceOf(o.monitorConfig, defaults.monitor, { mode: 'fleet' })),
|
|
214
|
+
loops: {
|
|
215
|
+
value: o.noLoops ? { enabled: false, loops: [] }
|
|
216
|
+
: resolvedLoops.length ? resolvedLoops.map(loop => ({
|
|
217
|
+
name: loop.name, enabled: loop.enabled, intervalMs: loop.intervalMs,
|
|
218
|
+
initialDelayMs: loop.initialDelayMs, jitterMs: loop.jitterMs,
|
|
219
|
+
prompt: { bytes: loop.promptBytes, sha256: loop.promptHash },
|
|
220
|
+
})) : 'omitted (legacy no temporary loops)',
|
|
221
|
+
source: o.loopSource === 'agent-template' ? 'agent-template'
|
|
222
|
+
: o.loopsFile || o.noLoops || o.loopSource === 'cli' ? 'cli' : 'built-in',
|
|
223
|
+
},
|
|
164
224
|
};
|
|
165
225
|
}
|
|
166
226
|
/** Permanent spawn: persist one bare Agent document under the selected v2 root. */
|
|
@@ -168,6 +228,9 @@ export async function spawnPermanent(o, deps, creation = {}) {
|
|
|
168
228
|
validateSpawnOpts(o);
|
|
169
229
|
if (o.isolationFile)
|
|
170
230
|
readIsolationFile(o.isolationFile); // fail before reserving
|
|
231
|
+
// Preserve the detailed owner/source diagnostic for an existing identity.
|
|
232
|
+
// The check is repeated inside the reservation boundary to close races.
|
|
233
|
+
assertNameFree(o);
|
|
171
234
|
const prepared = resolvedSpawn(o); // canonical validation before mutation
|
|
172
235
|
// Reserve name and identity together before anything is written or started.
|
|
173
236
|
// A loser of the race creates no config, state, or service.
|
|
@@ -221,7 +284,7 @@ export async function spawnPermanent(o, deps, creation = {}) {
|
|
|
221
284
|
// launch still records how it was asked for.
|
|
222
285
|
const provenance = buildProvenance({
|
|
223
286
|
role: o.name, lifetime: 'permanent', fleetVersion: VERSION,
|
|
224
|
-
settings: provenanceSettings(o, cfg.defaults),
|
|
287
|
+
settings: provenanceSettings(o, cfg.defaults, prepared.role.temporaryLoops),
|
|
225
288
|
surface: o.surface, creationActionId: o.creationActionId, callerRole: o.callerRole,
|
|
226
289
|
});
|
|
227
290
|
mkdirSync(agentDir(o.name), { recursive: true });
|
|
@@ -282,14 +345,19 @@ export async function spawnTemp(o, binPath, launch = independentSupervisor, crea
|
|
|
282
345
|
}
|
|
283
346
|
async function spawnTempInner(o, preparedRole, binPath, launch, tx, onStage) {
|
|
284
347
|
const cfg = loadConfig(o.configPath);
|
|
348
|
+
const { temporaryLoops, ...launchRole } = preparedRole;
|
|
285
349
|
const role = {
|
|
286
|
-
...
|
|
350
|
+
...launchRole, sourceFile: '(temp)',
|
|
351
|
+
...(o.noLoops === true ? { loops: [] }
|
|
352
|
+
: temporaryLoops?.length ? { loops: temporaryLoops } : { loops: undefined }),
|
|
353
|
+
temporaryLoopSource: o.loopSource ?? (temporaryLoops?.length ? 'agent-template' : 'omitted'),
|
|
354
|
+
roomMemberStartup: o.roomMemberStartup,
|
|
287
355
|
};
|
|
288
356
|
onStage?.('writing_role');
|
|
289
357
|
const dir = applyRole(role, { temp: true, identityGuarantee: 'unverified' });
|
|
290
358
|
const provenance = buildProvenance({
|
|
291
359
|
role: o.name, lifetime: 'temporary', fleetVersion: VERSION,
|
|
292
|
-
settings: provenanceSettings(o, cfg.defaults),
|
|
360
|
+
settings: provenanceSettings(o, cfg.defaults, preparedRole.temporaryLoops),
|
|
293
361
|
surface: o.surface, creationActionId: o.creationActionId, callerRole: o.callerRole,
|
|
294
362
|
});
|
|
295
363
|
writeProvenance(dir, provenance);
|
|
@@ -3,6 +3,7 @@ export declare const REDACTED_ENV_VALUE = "__OURS_FLEET_SECRET_REDACTED__";
|
|
|
3
3
|
export interface EditableFleetModel {
|
|
4
4
|
manifest: Record<string, unknown>;
|
|
5
5
|
agents: Record<string, Record<string, unknown>>;
|
|
6
|
+
agent_templates: Record<string, Record<string, unknown>>;
|
|
6
7
|
}
|
|
7
8
|
export interface ConfigReadResult {
|
|
8
9
|
path: string;
|
|
@@ -12,7 +12,7 @@ import { isSensitiveConfigKey } from '../sensitive-config.js';
|
|
|
12
12
|
export const REDACTED_ENV_VALUE = '__OURS_FLEET_SECRET_REDACTED__';
|
|
13
13
|
const MAX_CONFIG_BYTES = 256 * 1024;
|
|
14
14
|
const DIFF_CONTEXT_LINES = 3;
|
|
15
|
-
const KINDS = ['agents', 'roles', 'brains', 'room_templates'];
|
|
15
|
+
const KINDS = ['agents', 'agent_templates', 'roles', 'brains', 'room_templates'];
|
|
16
16
|
const emptyReport = () => ({ ok: true, checks: [] });
|
|
17
17
|
export class FleetConfigService {
|
|
18
18
|
path;
|
|
@@ -176,7 +176,7 @@ function readSources(manifest) {
|
|
|
176
176
|
}
|
|
177
177
|
trustedDirectory(dir);
|
|
178
178
|
for (const name of readdirSync(dir).sort()) {
|
|
179
|
-
if ((kind === 'agents' || kind === 'room_templates')
|
|
179
|
+
if ((kind === 'agents' || kind === 'agent_templates' || kind === 'room_templates')
|
|
180
180
|
&& !['.yaml', '.yml'].includes(extname(name).toLowerCase()))
|
|
181
181
|
continue;
|
|
182
182
|
documents.set(join(kind, name), trustedFile(join(dir, name)));
|
|
@@ -188,20 +188,26 @@ function modelFrom(sources) {
|
|
|
188
188
|
const manifestKey = basename(sources.manifest);
|
|
189
189
|
const manifest = parseFleetDocument(sources.manifest, sources.documents.get(manifestKey), 'strict').value;
|
|
190
190
|
const agents = {};
|
|
191
|
+
const agent_templates = {};
|
|
191
192
|
for (const [rel, source] of sources.documents)
|
|
192
193
|
if (rel.startsWith(`agents/`)) {
|
|
193
194
|
const id = basename(rel, extname(rel));
|
|
194
195
|
agents[id] = parseFleetDocument(absoluteFor(sources.manifest, rel), source, 'strict').value;
|
|
195
196
|
}
|
|
196
|
-
|
|
197
|
+
for (const [rel, source] of sources.documents)
|
|
198
|
+
if (rel.startsWith(`agent_templates/`)) {
|
|
199
|
+
const id = basename(rel, extname(rel));
|
|
200
|
+
agent_templates[id] = parseFleetDocument(absoluteFor(sources.manifest, rel), source, 'strict').value;
|
|
201
|
+
}
|
|
202
|
+
return { manifest, agents, agent_templates };
|
|
197
203
|
}
|
|
198
204
|
function assertModel(value) {
|
|
199
205
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
200
206
|
throw new FleetError('invalid_request', 'model must be an object');
|
|
201
207
|
const input = value;
|
|
202
|
-
if (Object.keys(input).sort().join(',') !== 'agents,manifest')
|
|
203
|
-
throw new FleetError('invalid_request', 'model allows exactly manifest and
|
|
204
|
-
for (const key of ['manifest', 'agents'])
|
|
208
|
+
if (Object.keys(input).sort().join(',') !== 'agent_templates,agents,manifest')
|
|
209
|
+
throw new FleetError('invalid_request', 'model allows exactly manifest, agents, and agent_templates');
|
|
210
|
+
for (const key of ['manifest', 'agents', 'agent_templates'])
|
|
205
211
|
if (!input[key] || typeof input[key] !== 'object' || Array.isArray(input[key]))
|
|
206
212
|
throw new FleetError('invalid_request', `${key} must be an object`);
|
|
207
213
|
for (const [id, doc] of Object.entries(input.agents)) {
|
|
@@ -210,6 +216,12 @@ function assertModel(value) {
|
|
|
210
216
|
if (!doc || typeof doc !== 'object' || Array.isArray(doc))
|
|
211
217
|
throw new FleetError('invalid_request', `Agent '${id}' must be an object`);
|
|
212
218
|
}
|
|
219
|
+
for (const [id, doc] of Object.entries(input.agent_templates)) {
|
|
220
|
+
if (!ROLE_NAME_RE.test(id))
|
|
221
|
+
throw new FleetError('invalid_request', `invalid Agent Template ID '${id}'`);
|
|
222
|
+
if (!doc || typeof doc !== 'object' || Array.isArray(doc))
|
|
223
|
+
throw new FleetError('invalid_request', `Agent Template '${id}' must be an object`);
|
|
224
|
+
}
|
|
213
225
|
return structuredClone(input);
|
|
214
226
|
}
|
|
215
227
|
function renderProposal(current, model) {
|
|
@@ -222,6 +234,12 @@ function renderProposal(current, model) {
|
|
|
222
234
|
next.set(oldRel ?? join('agents', `${id}.yaml`), oldRel
|
|
223
235
|
? render(current.documents.get(oldRel), doc) : stringify(doc));
|
|
224
236
|
}
|
|
237
|
+
for (const [id, doc] of Object.entries(model.agent_templates).sort(([a], [b]) => a.localeCompare(b))) {
|
|
238
|
+
const oldRel = [...current.documents.keys()].find(rel => rel.startsWith('agent_templates/')
|
|
239
|
+
&& basename(rel, extname(rel)) === id);
|
|
240
|
+
next.set(oldRel ?? join('agent_templates', `${id}.yaml`), oldRel
|
|
241
|
+
? render(current.documents.get(oldRel), doc) : stringify(doc));
|
|
242
|
+
}
|
|
225
243
|
for (const [rel, source] of current.documents)
|
|
226
244
|
if (rel.startsWith('roles/') || rel.startsWith('brains/') || rel.startsWith('room_templates/'))
|
|
227
245
|
next.set(rel, source);
|
|
@@ -425,7 +443,7 @@ function sourceSensitiveValues(sources) {
|
|
|
425
443
|
continue;
|
|
426
444
|
try {
|
|
427
445
|
const parsed = parse(source);
|
|
428
|
-
values.push(...sensitiveValues({ manifest: {}, agents: { source: parsed } }));
|
|
446
|
+
values.push(...sensitiveValues({ manifest: {}, agents: { source: parsed }, agent_templates: {} }));
|
|
429
447
|
}
|
|
430
448
|
catch { /* the authoritative parser reports malformed YAML */ }
|
|
431
449
|
}
|
package/dist/web/runtime.js
CHANGED
|
@@ -4,7 +4,7 @@ import { resolve } from 'node:path';
|
|
|
4
4
|
import { loadConfig } from '../config.js';
|
|
5
5
|
import { RoleRepository } from '../application/role-repository.js';
|
|
6
6
|
import { FleetQueryService } from '../application/fleet-query-service.js';
|
|
7
|
-
import {
|
|
7
|
+
import { RoleSessionControlAdapter } from '../application/session-control.js';
|
|
8
8
|
import { StructuredLogService } from '../application/log-service.js';
|
|
9
9
|
import { RoleCommandService } from '../application/role-command-service.js';
|
|
10
10
|
import { RoleCreationService } from '../application/role-creation-service.js';
|
|
@@ -64,18 +64,22 @@ export async function startWebConsole(options) {
|
|
|
64
64
|
const repository = new RoleRepository({
|
|
65
65
|
configPath: options.configPath,
|
|
66
66
|
probeBackend: async (name) => {
|
|
67
|
-
let
|
|
67
|
+
let backend;
|
|
68
68
|
const permanent = resolve(home(), '.ours-fleet', 'agents', name);
|
|
69
69
|
const temporary = resolve(home(), '.ours-fleet', 'tmp', name);
|
|
70
70
|
for (const dir of [permanent, temporary]) {
|
|
71
71
|
if (!existsSync(controlSocketPath(dir)))
|
|
72
72
|
continue;
|
|
73
73
|
try {
|
|
74
|
-
|
|
74
|
+
const response = await controlRequest(dir, { command: 'snapshot' }, 500);
|
|
75
|
+
const snapshot = response.result;
|
|
76
|
+
if (response.ok && (snapshot?.backend === 'acp'
|
|
77
|
+
|| snapshot?.backend === 'codex-app-server'))
|
|
78
|
+
backend = snapshot.backend;
|
|
75
79
|
}
|
|
76
80
|
catch { /* stale socket is evidence, not reachability */ }
|
|
77
81
|
}
|
|
78
|
-
return {
|
|
82
|
+
return { backend };
|
|
79
83
|
},
|
|
80
84
|
});
|
|
81
85
|
const events = new FleetEventBus();
|
|
@@ -161,11 +165,12 @@ export async function startWebConsole(options) {
|
|
|
161
165
|
const role = await repository.get(roleId);
|
|
162
166
|
if (!role)
|
|
163
167
|
throw new FleetError('role_not_found', `no such role '${roleId}'`);
|
|
164
|
-
if (role.configuredBackend === 'acp'
|
|
168
|
+
if (role.configuredBackend === 'acp'
|
|
169
|
+
|| role.configuredBackend === 'codex-app-server') {
|
|
165
170
|
const dir = repository.stateDir(role);
|
|
166
171
|
if (!dir)
|
|
167
172
|
throw new FleetError('control_unavailable', 'role state directory is unavailable');
|
|
168
|
-
return new
|
|
173
|
+
return new RoleSessionControlAdapter(dir);
|
|
169
174
|
}
|
|
170
175
|
throw new FleetError('capability_unavailable', 'role session backend is unavailable');
|
|
171
176
|
},
|
package/dist/web/server.js
CHANGED
|
@@ -183,12 +183,39 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
183
183
|
throw new FleetError('invalid_request', 'invalid task state filter');
|
|
184
184
|
if (query.groupByList !== undefined && !['true', 'false'].includes(query.groupByList))
|
|
185
185
|
throw new FleetError('invalid_request', 'groupByList must be true or false');
|
|
186
|
+
if (query.includeDeleting !== undefined && !['true', 'false'].includes(query.includeDeleting))
|
|
187
|
+
throw new FleetError('invalid_request', 'includeDeleting must be true or false');
|
|
186
188
|
const state = query.state && query.state !== 'all' ? query.state : undefined;
|
|
187
|
-
const filter = { ...(state ? { state } : {}), ...(query.list ? { list: query.list } : {})
|
|
189
|
+
const filter = { ...(state ? { state } : {}), ...(query.list ? { list: query.list } : {}),
|
|
190
|
+
...(query.includeDeleting === 'true' ? { includeDeleting: true } : {}) };
|
|
188
191
|
return taskApi(() => query.groupByList === 'true'
|
|
189
192
|
? { groups: requireTaskRooms().groupedTasks(filter) }
|
|
190
193
|
: { tasks: requireTaskRooms().listTasks(filter) });
|
|
191
194
|
});
|
|
195
|
+
app.delete('/api/v1/tasks/:id', async (request, reply) => {
|
|
196
|
+
auth.authenticate(request, true);
|
|
197
|
+
const taskId = request.params.id;
|
|
198
|
+
const confirm = request.query.confirm;
|
|
199
|
+
// Exact-ID-twice confirmation is validated before existence or idempotency.
|
|
200
|
+
if (confirm !== taskId)
|
|
201
|
+
throw new FleetError('invalid_request', 'confirm must exactly repeat the task id');
|
|
202
|
+
const accepted = await taskApi(() => requireTaskRooms().requestTaskDeletion({
|
|
203
|
+
actor: { kind: 'local_control', surface: 'web' }, taskId,
|
|
204
|
+
}));
|
|
205
|
+
if (accepted.status === 'already_absent')
|
|
206
|
+
return { task_id: taskId, deleted: false, already_absent: true };
|
|
207
|
+
// Cleanup runs in an external worker outside the request lifecycle; the
|
|
208
|
+
// request waits boundedly and reports pending — never fabricated success.
|
|
209
|
+
const outcome = await requireTaskRooms().launchTaskDeletionWorker({ taskId, waitMs: 5_000 });
|
|
210
|
+
if (outcome.deleted)
|
|
211
|
+
return { task_id: taskId, deleted: true };
|
|
212
|
+
reply.code(202);
|
|
213
|
+
return {
|
|
214
|
+
task_id: taskId, accepted: true, deletion: 'pending',
|
|
215
|
+
...(outcome.error ? { error: outcome.error } : {}),
|
|
216
|
+
recovery: `Repeat DELETE /api/v1/tasks/${taskId}?confirm=${taskId}.`,
|
|
217
|
+
};
|
|
218
|
+
});
|
|
192
219
|
app.post('/api/v1/tasks', async (request, reply) => {
|
|
193
220
|
auth.authenticate(request, true);
|
|
194
221
|
const body = request.body;
|