@ours.network/fleet 1.1.0-nightly.9 → 1.1.1
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 +285 -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 +42 -3
- package/dist/config.js +202 -29
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +7 -4
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +195 -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 +5 -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 +21 -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 +97 -47
- package/dist/session/acp.d.ts +27 -1
- package/dist/session/acp.js +307 -17
- 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/stall-watchdog.d.ts +60 -0
- package/dist/session/stall-watchdog.js +210 -0
- package/dist/session/types.d.ts +29 -8
- 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';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export declare const DEFAULT_STALL_TIMEOUT_MS: number;
|
|
2
|
+
export declare const STALL_RECOVERY_PROMPT: string;
|
|
3
|
+
export type StallStatus = 'interrupt_requested' | 'recovery_started' | 'progress_resumed' | 'recovery_completed' | 'blocked_cancel' | 'blocked_recovery' | 'blocked_restall' | 'blocked_persistence' | 'blocked_previous_attempt' | 'blocked_evidence' | 'superseded';
|
|
4
|
+
export interface StallDiagnostic {
|
|
5
|
+
version: 1;
|
|
6
|
+
kind: 'stall_recovery';
|
|
7
|
+
eventId: string;
|
|
8
|
+
session: string;
|
|
9
|
+
turn: string;
|
|
10
|
+
status: StallStatus;
|
|
11
|
+
evidence: 'adapter_transport' | 'no_progress';
|
|
12
|
+
idleMs: number;
|
|
13
|
+
}
|
|
14
|
+
export interface StallObservation {
|
|
15
|
+
sessionId: string;
|
|
16
|
+
generation: string;
|
|
17
|
+
turnId: string;
|
|
18
|
+
startedAt: number;
|
|
19
|
+
lastProgressAt: number;
|
|
20
|
+
progressCount: number;
|
|
21
|
+
transportFailures: number;
|
|
22
|
+
safe: boolean;
|
|
23
|
+
boundaryEvidenceAvailable?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Presence, including an incomplete claim, restores conservative mail policy. */
|
|
26
|
+
export declare function hasStallRecoveryClaim(stateDir: string, sessionId: string): boolean;
|
|
27
|
+
/** ACP has no turn IDs on tool updates. Reuse across turns is ambiguous. */
|
|
28
|
+
export declare class StallToolHistory {
|
|
29
|
+
private readonly turns;
|
|
30
|
+
private healthy;
|
|
31
|
+
private readonly directory;
|
|
32
|
+
private readonly path;
|
|
33
|
+
private readonly session;
|
|
34
|
+
constructor(stateDir: string, sessionId: string, resume: boolean);
|
|
35
|
+
available(): boolean;
|
|
36
|
+
/** Record before relying on a tool event. False means cancellation is unsafe. */
|
|
37
|
+
observe(toolId: string, turnId: string): boolean;
|
|
38
|
+
private persist;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* One durable attempt per ACP session, deliberately stricter than one per turn.
|
|
42
|
+
* A restarted supervisor never guesses whether cancellation or recovery ran.
|
|
43
|
+
* Claim files are never reclaimed automatically, including malformed/empty ones.
|
|
44
|
+
*/
|
|
45
|
+
export declare class StallWatchdog {
|
|
46
|
+
private readonly options;
|
|
47
|
+
private checking;
|
|
48
|
+
private disabled;
|
|
49
|
+
private unavailableTurn?;
|
|
50
|
+
constructor(options: {
|
|
51
|
+
stateDir: string;
|
|
52
|
+
timeoutMs: number;
|
|
53
|
+
previouslyClaimed?: boolean;
|
|
54
|
+
now(): number;
|
|
55
|
+
observe(): StallObservation | undefined;
|
|
56
|
+
recover(observed: StallObservation, report: (status: StallStatus) => void): Promise<void>;
|
|
57
|
+
diagnostic(event: StallDiagnostic): void;
|
|
58
|
+
});
|
|
59
|
+
tick(): Promise<void>;
|
|
60
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { closeSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { replaceFileAtomically } from '../atomic-file.js';
|
|
5
|
+
export const DEFAULT_STALL_TIMEOUT_MS = 15 * 60_000;
|
|
6
|
+
export const STALL_RECOVERY_PROMPT = 'This is a diagnostic interruption. The previous turn showed no progress. '
|
|
7
|
+
+ 'Inspect recorded terminal events and re-check completed actions before continuing. '
|
|
8
|
+
+ 'Never assume an issued side effect failed: do not replay ambiguous or already-completed mutations. '
|
|
9
|
+
+ 'Continue the previous task if safe; otherwise report an actionable blocker.';
|
|
10
|
+
const digest = (text) => createHash('sha256').update(text).digest('hex');
|
|
11
|
+
/** Presence, including an incomplete claim, restores conservative mail policy. */
|
|
12
|
+
export function hasStallRecoveryClaim(stateDir, sessionId) {
|
|
13
|
+
try {
|
|
14
|
+
lstatSync(join(stateDir, '.stall-recovery', `${digest(sessionId)}.claim`));
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
return error.code !== 'ENOENT';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** ACP has no turn IDs on tool updates. Reuse across turns is ambiguous. */
|
|
22
|
+
export class StallToolHistory {
|
|
23
|
+
turns = new Map();
|
|
24
|
+
healthy = true;
|
|
25
|
+
directory;
|
|
26
|
+
path;
|
|
27
|
+
session;
|
|
28
|
+
constructor(stateDir, sessionId, resume) {
|
|
29
|
+
this.session = digest(sessionId);
|
|
30
|
+
this.directory = join(stateDir, '.stall-recovery');
|
|
31
|
+
this.path = join(this.directory, `${this.session}.tools.json`);
|
|
32
|
+
try {
|
|
33
|
+
const stored = JSON.parse(readFileSync(this.path, 'utf8'));
|
|
34
|
+
if (stored.version !== 1 || stored.session !== this.session || !Array.isArray(stored.tools)
|
|
35
|
+
|| stored.tools.length > 4096 || !stored.tools.every((id) => typeof id === 'string' && /^[a-f0-9]{64}$/.test(id)))
|
|
36
|
+
throw new Error('invalid history');
|
|
37
|
+
for (const id of stored.tools)
|
|
38
|
+
this.turns.set(id, 'previous-generation');
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error.code !== 'ENOENT' || resume)
|
|
42
|
+
this.healthy = false;
|
|
43
|
+
else {
|
|
44
|
+
try {
|
|
45
|
+
mkdirSync(this.directory, { recursive: true, mode: 0o700 });
|
|
46
|
+
this.persist();
|
|
47
|
+
const fd = openSync(stateDir, 'r');
|
|
48
|
+
try {
|
|
49
|
+
fsyncSync(fd);
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
closeSync(fd);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
this.healthy = false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
available() { return this.healthy; }
|
|
62
|
+
/** Record before relying on a tool event. False means cancellation is unsafe. */
|
|
63
|
+
observe(toolId, turnId) {
|
|
64
|
+
if (!this.healthy || !toolId)
|
|
65
|
+
return false;
|
|
66
|
+
const id = digest(toolId);
|
|
67
|
+
const previous = this.turns.get(id);
|
|
68
|
+
if (previous !== undefined)
|
|
69
|
+
return previous === turnId;
|
|
70
|
+
if (this.turns.size >= 4096) {
|
|
71
|
+
this.healthy = false;
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
this.turns.set(id, turnId);
|
|
75
|
+
try {
|
|
76
|
+
this.persist();
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
this.healthy = false;
|
|
80
|
+
}
|
|
81
|
+
return this.healthy;
|
|
82
|
+
}
|
|
83
|
+
persist() {
|
|
84
|
+
replaceFileAtomically(this.path, JSON.stringify({ version: 1, session: this.session,
|
|
85
|
+
tools: [...this.turns.keys()] }) + '\n');
|
|
86
|
+
const fd = openSync(this.directory, 'r');
|
|
87
|
+
try {
|
|
88
|
+
fsyncSync(fd);
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
closeSync(fd);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* One durable attempt per ACP session, deliberately stricter than one per turn.
|
|
97
|
+
* A restarted supervisor never guesses whether cancellation or recovery ran.
|
|
98
|
+
* Claim files are never reclaimed automatically, including malformed/empty ones.
|
|
99
|
+
*/
|
|
100
|
+
export class StallWatchdog {
|
|
101
|
+
options;
|
|
102
|
+
checking = false;
|
|
103
|
+
disabled = false;
|
|
104
|
+
unavailableTurn;
|
|
105
|
+
constructor(options) {
|
|
106
|
+
this.options = options;
|
|
107
|
+
}
|
|
108
|
+
async tick() {
|
|
109
|
+
if (this.checking || this.disabled)
|
|
110
|
+
return;
|
|
111
|
+
const observed = this.options.observe();
|
|
112
|
+
if (!observed)
|
|
113
|
+
return;
|
|
114
|
+
const missingBoundary = observed.boundaryEvidenceAvailable === false;
|
|
115
|
+
if (!observed.safe && !missingBoundary && !this.options.previouslyClaimed)
|
|
116
|
+
return;
|
|
117
|
+
const missingEvidence = observed.progressCount === 0 || missingBoundary;
|
|
118
|
+
// Turn age can only produce an informational blocker, never cancellation.
|
|
119
|
+
const idleMs = this.options.now() - (observed.progressCount === 0 ? observed.startedAt : observed.lastProgressAt);
|
|
120
|
+
// Generic silence needs two full windows. Authenticated repeated transport
|
|
121
|
+
// failures strengthen evidence, but never bypass protected-operation checks.
|
|
122
|
+
const threshold = this.options.timeoutMs * (!missingEvidence && observed.transportFailures >= 2 ? 1 : 2);
|
|
123
|
+
if (!Number.isFinite(idleMs) || (idleMs < threshold && !this.options.previouslyClaimed))
|
|
124
|
+
return;
|
|
125
|
+
this.checking = true;
|
|
126
|
+
const session = digest(observed.sessionId);
|
|
127
|
+
const turn = digest(observed.generation + '\0' + observed.turnId);
|
|
128
|
+
const directory = join(this.options.stateDir, '.stall-recovery');
|
|
129
|
+
const report = (status) => {
|
|
130
|
+
const event = {
|
|
131
|
+
version: 1, kind: 'stall_recovery', eventId: `${turn}:${status}`,
|
|
132
|
+
session, turn, status, idleMs: Math.floor(idleMs),
|
|
133
|
+
evidence: observed.transportFailures >= 2 ? 'adapter_transport' : 'no_progress',
|
|
134
|
+
};
|
|
135
|
+
const fd = openSync(join(directory, 'audit.jsonl'), 'a', 0o600);
|
|
136
|
+
try {
|
|
137
|
+
writeFileSync(fd, JSON.stringify(event) + '\n');
|
|
138
|
+
fsyncSync(fd);
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
closeSync(fd);
|
|
142
|
+
}
|
|
143
|
+
this.options.diagnostic(event);
|
|
144
|
+
};
|
|
145
|
+
try {
|
|
146
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
147
|
+
const parentFd = openSync(this.options.stateDir, 'r');
|
|
148
|
+
try {
|
|
149
|
+
fsyncSync(parentFd);
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
closeSync(parentFd);
|
|
153
|
+
}
|
|
154
|
+
if (this.options.previouslyClaimed) {
|
|
155
|
+
this.disabled = true;
|
|
156
|
+
report('blocked_previous_attempt');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (missingEvidence) {
|
|
160
|
+
if (this.unavailableTurn !== turn) {
|
|
161
|
+
this.unavailableTurn = turn;
|
|
162
|
+
report('blocked_evidence');
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
let fd;
|
|
167
|
+
try {
|
|
168
|
+
fd = openSync(join(directory, `${session}.claim`), 'wx', 0o600);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
this.disabled = true;
|
|
172
|
+
if (error.code === 'EEXIST') {
|
|
173
|
+
report('blocked_previous_attempt');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
// Even a crash before this write leaves a permanent conservative fence.
|
|
179
|
+
try {
|
|
180
|
+
writeFileSync(fd, JSON.stringify({ version: 1, session, turn }) + '\n');
|
|
181
|
+
fsyncSync(fd);
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
closeSync(fd);
|
|
185
|
+
}
|
|
186
|
+
const dirFd = openSync(directory, 'r');
|
|
187
|
+
try {
|
|
188
|
+
fsyncSync(dirFd);
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
closeSync(dirFd);
|
|
192
|
+
}
|
|
193
|
+
this.disabled = true;
|
|
194
|
+
report('interrupt_requested');
|
|
195
|
+
// No await between the durable claim and the adapter's final atomic
|
|
196
|
+
// observation check. Recovery itself must re-check before session/cancel.
|
|
197
|
+
await this.options.recover(observed, report);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
this.disabled = true;
|
|
201
|
+
// Never include an exception string: it may contain paths or wire data.
|
|
202
|
+
this.options.diagnostic({ version: 1, kind: 'stall_recovery',
|
|
203
|
+
eventId: `${turn}:blocked_persistence`, session, turn,
|
|
204
|
+
status: 'blocked_persistence', evidence: 'no_progress', idleMs: Math.floor(idleMs) });
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
this.checking = false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
package/dist/session/types.d.ts
CHANGED
|
@@ -13,9 +13,11 @@ import type { ConversationEventV1, ConversationSnapshot, PromptReceipt, SubmitPr
|
|
|
13
13
|
*/
|
|
14
14
|
export type SessionReadiness = 'starting' | 'idle' | 'running' | 'awaiting_permission' | 'failed';
|
|
15
15
|
export type TurnOutcome = 'completed' | 'refused' | 'cancelled' | 'failed' | 'inconclusive';
|
|
16
|
-
export type TurnCancellationSource = 'owner' | 'local-console' | 'fleet-monitor' | 'scheduled-loop' | 'shutdown';
|
|
16
|
+
export type TurnCancellationSource = 'owner' | 'local-console' | 'fleet-monitor' | 'scheduled-loop' | 'shutdown' | 'stall-watchdog';
|
|
17
17
|
export type PromptOrigin = {
|
|
18
18
|
kind: 'startup';
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'stall-watchdog';
|
|
19
21
|
} | {
|
|
20
22
|
kind: 'local-console';
|
|
21
23
|
} | {
|
|
@@ -33,7 +35,7 @@ export type PromptOrigin = {
|
|
|
33
35
|
commandId: string;
|
|
34
36
|
};
|
|
35
37
|
export interface RuntimeSelectorMetadata {
|
|
36
|
-
/** Exact provider/model identifier reported by the live
|
|
38
|
+
/** Exact provider/model identifier reported by the live managed session. */
|
|
37
39
|
value: string;
|
|
38
40
|
/** Complete provider-supplied label for that exact value, when available. */
|
|
39
41
|
label?: string;
|
|
@@ -75,6 +77,8 @@ export interface TurnResult {
|
|
|
75
77
|
export type ControlFailureKind = 'offline' | 'control-unavailable' | 'timeout' | 'rejected' | 'backend';
|
|
76
78
|
/** Stable body-free reason shared by ACP recovery and durable ingress. */
|
|
77
79
|
export declare const ACP_CANCEL_DEADLINE_EXCEEDED = "ACP_CANCEL_DEADLINE_EXCEEDED";
|
|
80
|
+
/** Native Codex equivalent; kept distinct so diagnostics name the failing transport. */
|
|
81
|
+
export declare const CODEX_APP_SERVER_CANCEL_DEADLINE_EXCEEDED = "CODEX_APP_SERVER_CANCEL_DEADLINE_EXCEEDED";
|
|
78
82
|
export declare class SessionControlError extends Error {
|
|
79
83
|
readonly kind: ControlFailureKind;
|
|
80
84
|
/** Stable body-free machine reason for recovery/audit decisions. */
|
|
@@ -170,7 +174,7 @@ export interface SubmitPromptOptions {
|
|
|
170
174
|
interruptSource?: TurnCancellationSource;
|
|
171
175
|
/** Typed in-process provenance. Text markers never grant this origin. */
|
|
172
176
|
origin?: PromptOrigin;
|
|
173
|
-
/**
|
|
177
|
+
/** Inject into active work when the selected backend supports steering. */
|
|
174
178
|
steer?: boolean;
|
|
175
179
|
/** Audit-grade actor detail persisted with the conversation admission record. */
|
|
176
180
|
actor?: {
|
|
@@ -200,12 +204,26 @@ export interface SessionSnapshot {
|
|
|
200
204
|
activity?: SessionActivity;
|
|
201
205
|
}
|
|
202
206
|
export interface SessionActivity {
|
|
203
|
-
/**
|
|
207
|
+
/** Tool calls currently reserved (lifecycle open or permission pending). */
|
|
204
208
|
activeToolCalls: number;
|
|
205
209
|
/** When the agent last sent ANY session update, replay excluded. */
|
|
206
210
|
lastUpdateAt?: string;
|
|
207
211
|
}
|
|
208
|
-
|
|
212
|
+
/** Provider-neutral behavior exposed by a session transport. */
|
|
213
|
+
export interface AgentSessionCapabilities {
|
|
214
|
+
streaming: boolean;
|
|
215
|
+
durableConversation: boolean;
|
|
216
|
+
promptInput: boolean;
|
|
217
|
+
interrupt: boolean;
|
|
218
|
+
steering: boolean;
|
|
219
|
+
permissions: boolean;
|
|
220
|
+
toolBoundaryDelivery: boolean;
|
|
221
|
+
messagePhases: boolean;
|
|
222
|
+
resume: boolean;
|
|
223
|
+
}
|
|
224
|
+
/** Conservative static capabilities used before a live session is reachable. */
|
|
225
|
+
export declare function sessionBackendCapabilities(backend: SessionBackendId, harness?: string): AgentSessionCapabilities;
|
|
226
|
+
export type SessionEventKind = 'state' | 'agent_text' | 'thought' | 'tool_call' | 'tool_update' | 'permission' | 'stall_recovery' | 'monitor_delivery' | 'turn_stop' | 'error';
|
|
209
227
|
/** What a settled permission request resolved to. */
|
|
210
228
|
export type PermissionDecision = 'allowed' | 'denied' | 'cancelled';
|
|
211
229
|
export interface SessionEvent {
|
|
@@ -219,7 +237,7 @@ export interface SessionEvent {
|
|
|
219
237
|
text?: string;
|
|
220
238
|
/**
|
|
221
239
|
* Adapter-authenticated presentation phase for assistant text. Only the
|
|
222
|
-
* exact Codex
|
|
240
|
+
* exact Codex phase marker is promoted; absence/unknown values stay unset.
|
|
223
241
|
*/
|
|
224
242
|
messagePhase?: 'commentary' | 'final_answer';
|
|
225
243
|
/** Stable adapter message/item id when supplied. */
|
|
@@ -247,6 +265,7 @@ export interface SessionEvent {
|
|
|
247
265
|
/** The option actually selected, when one was. */
|
|
248
266
|
optionId?: string;
|
|
249
267
|
/** Body-free evidence for monitor safe-boundary delivery. */
|
|
268
|
+
stallDiagnostic?: import('./stall-watchdog.js').StallDiagnostic;
|
|
250
269
|
monitorPolicy?: 'after_tool';
|
|
251
270
|
activeToolCount?: number;
|
|
252
271
|
waitedMs?: number;
|
|
@@ -261,11 +280,13 @@ export interface ConversationHandlePage {
|
|
|
261
280
|
/**
|
|
262
281
|
* The live, harness-neutral contract between Fleet and one agent session.
|
|
263
282
|
* Harness adapters construct this handle; orchestration code must not depend
|
|
264
|
-
* on
|
|
283
|
+
* on any concrete transport which implements it.
|
|
265
284
|
*/
|
|
266
285
|
export interface AgentSession {
|
|
267
286
|
readonly backend: SessionBackendId;
|
|
268
287
|
readonly pid: number;
|
|
288
|
+
/** Live capabilities; optional only for source compatibility with external adapters. */
|
|
289
|
+
readonly capabilities?: AgentSessionCapabilities;
|
|
269
290
|
isAlive(): boolean;
|
|
270
291
|
snapshot(): SessionSnapshot;
|
|
271
292
|
conversationPage?(request: {
|
|
@@ -283,7 +304,7 @@ export interface AgentSession {
|
|
|
283
304
|
queuePrompt(text: string, options?: SubmitPromptOptions): Promise<QueuedPrompt>;
|
|
284
305
|
/** Queue a prompt and wait for its terminal result. */
|
|
285
306
|
submitPrompt(text: string, options?: SubmitPromptOptions): Promise<TurnResult>;
|
|
286
|
-
/** Monitor-only
|
|
307
|
+
/** Monitor-only safe-boundary delivery. Never implies human/control cancellation. */
|
|
287
308
|
submitPromptAfterTool?(text: string, options?: SubmitPromptOptions): Promise<TurnResult>;
|
|
288
309
|
/**
|
|
289
310
|
* 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;
|