@ours.network/fleet 1.1.0-nightly.2 → 1.1.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 +204 -55
- package/dist/agent-recovery-gate.d.ts +17 -0
- package/dist/agent-recovery-gate.js +135 -0
- package/dist/application/capabilities.js +18 -6
- package/dist/application/fleet-query-service.js +20 -5
- package/dist/application/role-command-service.js +9 -0
- package/dist/application/role-creation-service.d.ts +6 -25
- package/dist/application/role-creation-service.js +34 -72
- 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 +58 -6
- package/dist/application/task-room-service.js +435 -79
- package/dist/briefing.js +74 -24
- package/dist/build-info.json +4 -4
- package/dist/canonical-json.d.ts +2 -0
- package/dist/canonical-json.js +10 -0
- package/dist/cli.js +241 -76
- package/dist/config-yaml.d.ts +1 -1
- package/dist/config.d.ts +75 -3
- package/dist/config.js +274 -34
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +3 -2
- package/dist/daemon-recovery.d.ts +93 -0
- package/dist/daemon-recovery.js +328 -0
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +192 -66
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +54 -4
- package/dist/fleet-command-audit.d.ts +165 -0
- package/dist/fleet-command-audit.js +555 -0
- package/dist/fleet-proxy.d.ts +7 -2
- package/dist/fleet-proxy.js +30 -8
- package/dist/harness/agent-session.d.ts +5 -0
- package/dist/harness/claude-code-session.d.ts +2 -0
- package/dist/harness/claude-code-session.js +6 -1
- package/dist/harness/claude-code.js +2 -0
- 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 +11 -2
- package/dist/harness/codex-session.js +22 -2
- package/dist/harness/codex.d.ts +5 -1
- package/dist/harness/codex.js +110 -10
- package/dist/harness/types.d.ts +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +4 -3
- package/dist/init-guidance.d.ts +1 -1
- package/dist/init-guidance.js +4 -3
- package/dist/init-wizard.d.ts +98 -0
- package/dist/init-wizard.js +629 -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-env.d.ts +2 -2
- package/dist/model-env.js +1 -1
- package/dist/model-recovery.d.ts +1 -1
- package/dist/ops.js +2 -17
- package/dist/owner-channel/channel.d.ts +58 -0
- package/dist/owner-channel/channel.js +321 -42
- package/dist/owner-channel/commands.d.ts +6 -1
- package/dist/owner-channel/commands.js +34 -19
- package/dist/owner-channel/state.d.ts +2 -0
- package/dist/owner-channel/state.js +8 -2
- package/dist/permissions.js +1 -1
- package/dist/preset-bootstrap.d.ts +10 -0
- package/dist/preset-bootstrap.js +85 -0
- package/dist/preset-migration.d.ts +42 -0
- package/dist/preset-migration.js +414 -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 +514 -75
- package/dist/rooms-tasks/close.d.ts +9 -1
- package/dist/rooms-tasks/close.js +20 -4
- package/dist/rooms-tasks/config.js +16 -14
- package/dist/rooms-tasks/cowork-adapter.d.ts +1 -0
- package/dist/rooms-tasks/cowork-adapter.js +6 -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 +1 -0
- package/dist/rooms-tasks/external-worker.js +14 -9
- 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/provision.js +184 -49
- package/dist/rooms-tasks/room-state.d.ts +1 -0
- package/dist/rooms-tasks/room-state.js +12 -1
- 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 -2
- package/dist/rooms-tasks/templates.js +42 -78
- package/dist/rooms-tasks/terminal.d.ts +3 -0
- package/dist/rooms-tasks/terminal.js +7 -3
- package/dist/rooms-tasks/types.d.ts +96 -17
- package/dist/rooms-tasks/types.js +14 -4
- package/dist/runner.d.ts +12 -2
- package/dist/runner.js +124 -43
- package/dist/session/acp.d.ts +9 -1
- package/dist/session/acp.js +35 -9
- 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 +14 -1
- package/dist/session/control.js +38 -0
- 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 +15 -31
- package/dist/spawn.js +117 -237
- package/dist/temp-lifecycle.d.ts +1 -1
- package/dist/watchdog/config.d.ts +10 -7
- package/dist/watchdog/config.js +29 -19
- package/dist/watchdog/run.js +4 -13
- package/dist/web/fleet-config-service.d.ts +1 -0
- package/dist/web/fleet-config-service.js +27 -8
- package/dist/web/runtime.js +11 -6
- package/dist/web/server.js +28 -1
- package/dist/web/topology-promote.js +8 -1
- package/dist/web-app/assets/index-C0h3ALvs.js +10 -0
- package/dist/web-app/index.html +1 -1
- package/package.json +3 -2
- package/presets/brain-catalog.json +16 -0
- package/presets/fleet/agent_templates/Critic.yaml +4 -0
- package/presets/fleet/agent_templates/Developer.yaml +4 -0
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +4 -0
- package/presets/fleet/agents/FleetCoordinator.yaml +3 -0
- package/presets/fleet/brains/claude-default.yaml +5 -0
- 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/roles/Coordinator.yaml +30 -0
- package/presets/fleet/roles/Critic.yaml +19 -0
- package/presets/fleet/roles/Developer.yaml +19 -0
- package/presets/fleet/roles/LocalCoordinator.yaml +25 -0
- package/presets/fleet/room_templates/pair.yaml +10 -0
- package/presets/fleet/room_templates/single.yaml +7 -0
- package/presets/fleet/room_templates/team.yaml +12 -0
- package/presets/fleet.yaml +7 -0
- package/presets/manifest.json +6 -0
- package/dist/web-app/assets/index-BbE9EX6n.js +0 -10
package/dist/session/acp.d.ts
CHANGED
|
@@ -28,6 +28,8 @@ export declare function promptContentBlocks(text: string, origin?: PromptOrigin)
|
|
|
28
28
|
export declare function runtimeSelector(options: acp.SessionConfigOption[] | null | undefined, category: string): RuntimeSelectorMetadata | undefined;
|
|
29
29
|
export interface AcpSessionOptions {
|
|
30
30
|
name: string;
|
|
31
|
+
/** Harness identity used only for honest optional capability reporting. */
|
|
32
|
+
harness?: string;
|
|
31
33
|
argv: string[];
|
|
32
34
|
cwd: string;
|
|
33
35
|
env: Record<string, string>;
|
|
@@ -36,6 +38,11 @@ export interface AcpSessionOptions {
|
|
|
36
38
|
permissions: CommonPermissions;
|
|
37
39
|
/** Native permission-mode id to request via session/set_mode; undefined keeps the agent default. */
|
|
38
40
|
modeId?: string;
|
|
41
|
+
/** Ordered explicit Brain choices that must be applied before readiness. */
|
|
42
|
+
configSelections?: Array<{
|
|
43
|
+
configId: string;
|
|
44
|
+
value: string;
|
|
45
|
+
}>;
|
|
39
46
|
/** Adapter-resolved live permission policy; separate from ACP agent-specific session modes. */
|
|
40
47
|
permissionMode?: NonNullable<SessionSnapshot['permissionMode']>;
|
|
41
48
|
/** Adapter-authenticated request-metadata vocabulary; never inferred from ACP `_meta`. */
|
|
@@ -84,6 +91,7 @@ export declare function classifyStopReason(stopReason: string | undefined): Turn
|
|
|
84
91
|
export declare class AcpSession implements AgentSession {
|
|
85
92
|
private readonly options;
|
|
86
93
|
readonly backend: "acp";
|
|
94
|
+
readonly capabilities: import("./types.js").AgentSessionCapabilities;
|
|
87
95
|
readonly pid: number;
|
|
88
96
|
private readonly child;
|
|
89
97
|
private readonly events;
|
|
@@ -110,7 +118,7 @@ export declare class AcpSession implements AgentSession {
|
|
|
110
118
|
private queueDepth;
|
|
111
119
|
private exit;
|
|
112
120
|
private steeringSupported;
|
|
113
|
-
private
|
|
121
|
+
private agentCapabilities?;
|
|
114
122
|
private runtimeModel?;
|
|
115
123
|
private reasoningEffort?;
|
|
116
124
|
private controllerCount;
|
package/dist/session/acp.js
CHANGED
|
@@ -7,7 +7,7 @@ import * as acp from '@agentclientprotocol/sdk';
|
|
|
7
7
|
import { normalizeSessionUpdate } from './conversation-normalizer.js';
|
|
8
8
|
import { ConversationEventStore } from './conversation-store.js';
|
|
9
9
|
import { SessionEvents } from './events.js';
|
|
10
|
-
import { ACP_CANCEL_DEADLINE_EXCEEDED, SessionControlError, classifyChildExit, turnResult, } from './types.js';
|
|
10
|
+
import { ACP_CANCEL_DEADLINE_EXCEEDED, SessionControlError, classifyChildExit, sessionBackendCapabilities, turnResult, } from './types.js';
|
|
11
11
|
const CANCEL_SETTLE_GRACE_MS = 15_000;
|
|
12
12
|
const CANCEL_TERMINATE_GRACE_MS = 5_000;
|
|
13
13
|
/** A permission no human answered is eventually a decision nobody made. */
|
|
@@ -206,6 +206,7 @@ export function classifyStopReason(stopReason) {
|
|
|
206
206
|
export class AcpSession {
|
|
207
207
|
options;
|
|
208
208
|
backend = 'acp';
|
|
209
|
+
capabilities;
|
|
209
210
|
pid;
|
|
210
211
|
child;
|
|
211
212
|
events;
|
|
@@ -232,7 +233,7 @@ export class AcpSession {
|
|
|
232
233
|
queueDepth = 0;
|
|
233
234
|
exit = null;
|
|
234
235
|
steeringSupported = false;
|
|
235
|
-
|
|
236
|
+
agentCapabilities;
|
|
236
237
|
runtimeModel;
|
|
237
238
|
reasoningEffort;
|
|
238
239
|
controllerCount = 0;
|
|
@@ -265,6 +266,7 @@ export class AcpSession {
|
|
|
265
266
|
this.child = child;
|
|
266
267
|
this.connection = connection;
|
|
267
268
|
this.pid = child.pid ?? -1;
|
|
269
|
+
this.capabilities = sessionBackendCapabilities('acp', options.harness);
|
|
268
270
|
this.events = new SessionEvents(join(options.stateDir, '.session-events.jsonl'));
|
|
269
271
|
this.conversation = new ConversationEventStore(join(options.stateDir, '.conversation'), {
|
|
270
272
|
roleId: options.name, log: line => options.log(`[${options.name}] ${line}`),
|
|
@@ -994,7 +996,7 @@ export class AcpSession {
|
|
|
994
996
|
for (const [permissionId, pending] of [...this.pendingPermissions])
|
|
995
997
|
this.settlePendingAutomatically(permissionId, pending, 'cancelled', undefined, 'the session closed while this request was pending');
|
|
996
998
|
this.releaseAllTools();
|
|
997
|
-
if (this.sessionId && this.
|
|
999
|
+
if (this.sessionId && this.agentCapabilities?.sessionCapabilities?.close != null) {
|
|
998
1000
|
await this.connection.agent.request(acp.methods.agent.session.close, { sessionId: this.sessionId }).catch(() => undefined);
|
|
999
1001
|
}
|
|
1000
1002
|
this.connection.close();
|
|
@@ -1020,23 +1022,25 @@ export class AcpSession {
|
|
|
1020
1022
|
});
|
|
1021
1023
|
if (initialized.protocolVersion !== acp.PROTOCOL_VERSION)
|
|
1022
1024
|
throw new Error(`ACP protocol mismatch: agent selected ${initialized.protocolVersion}, client supports ${acp.PROTOCOL_VERSION}`);
|
|
1023
|
-
this.
|
|
1025
|
+
this.agentCapabilities = initialized.agentCapabilities;
|
|
1024
1026
|
const steering = initialized._meta?.steering;
|
|
1025
1027
|
this.steeringSupported = steering !== null && typeof steering === 'object'
|
|
1026
1028
|
&& steering.supported === true;
|
|
1027
1029
|
const persisted = this.options.mode === 'resume' && existsSync(this.sessionFile)
|
|
1028
1030
|
? readFileSync(this.sessionFile, 'utf8').trim()
|
|
1029
1031
|
: '';
|
|
1030
|
-
|
|
1032
|
+
let advertisedConfigOptions;
|
|
1033
|
+
if (persisted && this.agentCapabilities?.sessionCapabilities?.resume != null) {
|
|
1031
1034
|
const resumed = await this.connection.agent.request(acp.methods.agent.session.resume, {
|
|
1032
1035
|
sessionId: persisted,
|
|
1033
1036
|
cwd: this.options.cwd,
|
|
1034
1037
|
mcpServers: this.declaredMcpServers(),
|
|
1035
1038
|
});
|
|
1036
|
-
|
|
1039
|
+
advertisedConfigOptions = resumed.configOptions;
|
|
1040
|
+
this.captureRuntimeMetadata(advertisedConfigOptions);
|
|
1037
1041
|
this.sessionId = persisted;
|
|
1038
1042
|
}
|
|
1039
|
-
else if (persisted && this.
|
|
1043
|
+
else if (persisted && this.agentCapabilities?.loadSession) {
|
|
1040
1044
|
// `session/load` replays prior history as ordinary updates before the
|
|
1041
1045
|
// response; those records carry `agent_replay` provenance, never `agent`.
|
|
1042
1046
|
this.replaying = true;
|
|
@@ -1046,7 +1050,8 @@ export class AcpSession {
|
|
|
1046
1050
|
cwd: this.options.cwd,
|
|
1047
1051
|
mcpServers: this.declaredMcpServers(),
|
|
1048
1052
|
});
|
|
1049
|
-
|
|
1053
|
+
advertisedConfigOptions = loaded.configOptions;
|
|
1054
|
+
this.captureRuntimeMetadata(advertisedConfigOptions);
|
|
1050
1055
|
}
|
|
1051
1056
|
finally {
|
|
1052
1057
|
this.replaying = false;
|
|
@@ -1060,8 +1065,29 @@ export class AcpSession {
|
|
|
1060
1065
|
...(this.options.sessionMeta ? { _meta: this.options.sessionMeta } : {}),
|
|
1061
1066
|
});
|
|
1062
1067
|
this.sessionId = created.sessionId;
|
|
1063
|
-
|
|
1068
|
+
advertisedConfigOptions = created.configOptions;
|
|
1069
|
+
this.captureRuntimeMetadata(advertisedConfigOptions);
|
|
1070
|
+
}
|
|
1071
|
+
for (const selection of this.options.configSelections ?? []) {
|
|
1072
|
+
if (!advertisedConfigOptions?.some(option => option.id === selection.configId))
|
|
1073
|
+
throw new Error(`ACP agent did not advertise required session config option '${selection.configId}'`);
|
|
1074
|
+
let configured;
|
|
1075
|
+
try {
|
|
1076
|
+
configured = await this.connection.agent.request(acp.methods.agent.session.setConfigOption, { sessionId: this.sessionId, configId: selection.configId, value: selection.value });
|
|
1077
|
+
}
|
|
1078
|
+
catch (error) {
|
|
1079
|
+
throw new Error(`ACP agent refused required session config option '${selection.configId}' value '${selection.value}': `
|
|
1080
|
+
+ (error instanceof Error ? error.message : String(error)));
|
|
1081
|
+
}
|
|
1082
|
+
advertisedConfigOptions = configured.configOptions;
|
|
1083
|
+
this.captureRuntimeMetadata(advertisedConfigOptions);
|
|
1084
|
+
const applied = advertisedConfigOptions.find(option => option.id === selection.configId);
|
|
1085
|
+
if (applied?.currentValue !== selection.value)
|
|
1086
|
+
throw new Error(`ACP agent did not apply required session config option '${selection.configId}' value '${selection.value}'`);
|
|
1064
1087
|
}
|
|
1088
|
+
// Do not persist a session until every required Brain choice is live. A
|
|
1089
|
+
// failed startup closes the ACP session; persisting its id first would make
|
|
1090
|
+
// a later resume repeatedly target that invalid session.
|
|
1065
1091
|
writeFileSync(this.sessionFile, this.sessionId + '\n', { mode: 0o600 });
|
|
1066
1092
|
// Deliver the configured permission mode whichever way the session came up
|
|
1067
1093
|
// (new, resume or load) — the launch flag never reaches an ACP agent. A
|
|
@@ -17,8 +17,9 @@ export type ScheduledAttempt = {
|
|
|
17
17
|
*/
|
|
18
18
|
export declare class RoleTurnArbiter implements AgentSession {
|
|
19
19
|
private readonly session;
|
|
20
|
-
readonly backend: "
|
|
20
|
+
readonly backend: import("../config.js").SessionBackendId;
|
|
21
21
|
readonly pid: number;
|
|
22
|
+
readonly capabilities: import("./types.js").AgentSessionCapabilities | undefined;
|
|
22
23
|
private tail;
|
|
23
24
|
private unsettled;
|
|
24
25
|
private stopping;
|
package/dist/session/arbiter.js
CHANGED
|
@@ -25,6 +25,7 @@ export class RoleTurnArbiter {
|
|
|
25
25
|
session;
|
|
26
26
|
backend;
|
|
27
27
|
pid;
|
|
28
|
+
capabilities;
|
|
28
29
|
tail = Promise.resolve();
|
|
29
30
|
unsettled = 0;
|
|
30
31
|
stopping = false;
|
|
@@ -36,6 +37,7 @@ export class RoleTurnArbiter {
|
|
|
36
37
|
this.session = session;
|
|
37
38
|
this.backend = session.backend;
|
|
38
39
|
this.pid = session.pid;
|
|
40
|
+
this.capabilities = session.capabilities;
|
|
39
41
|
}
|
|
40
42
|
/**
|
|
41
43
|
* Waiters race the tail against their own generation's retirement, so a
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
2
|
+
import type { ExitRecord } from './types.js';
|
|
3
|
+
type JsonObject = Record<string, unknown>;
|
|
4
|
+
type RequestId = string | number;
|
|
5
|
+
export interface CodexAppServerTransportOptions {
|
|
6
|
+
name: string;
|
|
7
|
+
argv: string[];
|
|
8
|
+
cwd: string;
|
|
9
|
+
env: Record<string, string>;
|
|
10
|
+
log(line: string): void;
|
|
11
|
+
requestTimeoutMs?: number;
|
|
12
|
+
onNotification?(method: string, params: JsonObject): void;
|
|
13
|
+
onRequest?(method: string, id: RequestId, params: JsonObject): void;
|
|
14
|
+
onExit?(exit: ExitRecord): void;
|
|
15
|
+
}
|
|
16
|
+
export interface CodexAppServerConnection {
|
|
17
|
+
readonly pid: number;
|
|
18
|
+
readonly child: Pick<ChildProcessWithoutNullStreams, 'kill'>;
|
|
19
|
+
isAlive(): boolean;
|
|
20
|
+
exitResult(): ExitRecord | null;
|
|
21
|
+
request<T = unknown>(method: string, params?: JsonObject, timeoutMs?: number): Promise<T>;
|
|
22
|
+
notify(method: string, params?: JsonObject): void;
|
|
23
|
+
respond(id: RequestId, result: unknown): void;
|
|
24
|
+
respondError(id: RequestId, code: number, message: string): void;
|
|
25
|
+
close(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export type CodexAppServerTransportFactory = (options: CodexAppServerTransportOptions) => Promise<CodexAppServerConnection>;
|
|
28
|
+
/** Minimal, version-tolerant JSONL client for Codex app-server's stdio transport. */
|
|
29
|
+
export declare class CodexAppServerTransport implements CodexAppServerConnection {
|
|
30
|
+
private readonly options;
|
|
31
|
+
readonly child: ChildProcessWithoutNullStreams;
|
|
32
|
+
readonly pid: number;
|
|
33
|
+
private nextId;
|
|
34
|
+
private readonly pending;
|
|
35
|
+
private readonly requestTimeoutMs;
|
|
36
|
+
private readonly exited;
|
|
37
|
+
private resolveExited;
|
|
38
|
+
private closed;
|
|
39
|
+
private exit;
|
|
40
|
+
private constructor();
|
|
41
|
+
static start(options: CodexAppServerTransportOptions): Promise<CodexAppServerTransport>;
|
|
42
|
+
isAlive(): boolean;
|
|
43
|
+
exitResult(): ExitRecord | null;
|
|
44
|
+
request<T = unknown>(method: string, params?: JsonObject, timeoutMs?: number): Promise<T>;
|
|
45
|
+
notify(method: string, params?: JsonObject): void;
|
|
46
|
+
respond(id: RequestId, result: unknown): void;
|
|
47
|
+
respondError(id: RequestId, code: number, message: string): void;
|
|
48
|
+
close(): Promise<void>;
|
|
49
|
+
private waitForExit;
|
|
50
|
+
private write;
|
|
51
|
+
private receive;
|
|
52
|
+
}
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createInterface } from 'node:readline';
|
|
3
|
+
import { SessionControlError, classifyChildExit } from './types.js';
|
|
4
|
+
const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
5
|
+
function responseError(value) {
|
|
6
|
+
if (!isObject(value))
|
|
7
|
+
return new Error('Codex app-server returned an unknown error');
|
|
8
|
+
const message = typeof value.message === 'string' ? value.message : JSON.stringify(value);
|
|
9
|
+
const error = new Error(message);
|
|
10
|
+
error.name = 'CodexAppServerError';
|
|
11
|
+
return error;
|
|
12
|
+
}
|
|
13
|
+
/** Minimal, version-tolerant JSONL client for Codex app-server's stdio transport. */
|
|
14
|
+
export class CodexAppServerTransport {
|
|
15
|
+
options;
|
|
16
|
+
child;
|
|
17
|
+
pid;
|
|
18
|
+
nextId = 1;
|
|
19
|
+
pending = new Map();
|
|
20
|
+
requestTimeoutMs;
|
|
21
|
+
exited;
|
|
22
|
+
resolveExited;
|
|
23
|
+
closed = false;
|
|
24
|
+
exit = null;
|
|
25
|
+
constructor(options, child) {
|
|
26
|
+
this.options = options;
|
|
27
|
+
this.child = child;
|
|
28
|
+
this.pid = child.pid ?? -1;
|
|
29
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? 90_000;
|
|
30
|
+
this.exited = new Promise(resolve => { this.resolveExited = resolve; });
|
|
31
|
+
child.stderr.on('data', chunk => {
|
|
32
|
+
const text = String(chunk).trimEnd();
|
|
33
|
+
if (text)
|
|
34
|
+
options.log(`[${options.name}] codex app-server: ${text}`);
|
|
35
|
+
});
|
|
36
|
+
child.once('error', error => {
|
|
37
|
+
this.options.log(`[${options.name}] codex app-server process error: ${error.message}`);
|
|
38
|
+
});
|
|
39
|
+
const lines = createInterface({ input: child.stdout });
|
|
40
|
+
lines.on('line', line => this.receive(line));
|
|
41
|
+
child.once('exit', (code, signal) => {
|
|
42
|
+
this.exit = classifyChildExit(code, signal);
|
|
43
|
+
const error = new SessionControlError('offline', `Codex app-server ${this.exit.detail}`);
|
|
44
|
+
for (const pending of this.pending.values()) {
|
|
45
|
+
clearTimeout(pending.timer);
|
|
46
|
+
pending.reject(error);
|
|
47
|
+
}
|
|
48
|
+
this.pending.clear();
|
|
49
|
+
options.onExit?.(this.exit);
|
|
50
|
+
this.resolveExited();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
static async start(options) {
|
|
54
|
+
if (!options.argv.length)
|
|
55
|
+
throw new Error('Codex app-server command is empty');
|
|
56
|
+
const child = spawn(options.argv[0], options.argv.slice(1), {
|
|
57
|
+
cwd: options.cwd,
|
|
58
|
+
env: { ...process.env, ...options.env },
|
|
59
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
60
|
+
});
|
|
61
|
+
// `pid` is assigned synchronously on a successful local spawn. Checking it
|
|
62
|
+
// first avoids missing an exceptionally fast `spawn` event before the
|
|
63
|
+
// listeners below are attached (observable with tiny test/app-server shims).
|
|
64
|
+
if (child.pid === undefined)
|
|
65
|
+
await new Promise((resolve, reject) => {
|
|
66
|
+
let settled = false;
|
|
67
|
+
const finish = (error) => {
|
|
68
|
+
if (settled)
|
|
69
|
+
return;
|
|
70
|
+
settled = true;
|
|
71
|
+
child.off('spawn', spawned);
|
|
72
|
+
child.off('error', failed);
|
|
73
|
+
error ? reject(error) : resolve();
|
|
74
|
+
};
|
|
75
|
+
const spawned = () => finish();
|
|
76
|
+
const failed = (error) => finish(error);
|
|
77
|
+
child.once('spawn', spawned);
|
|
78
|
+
child.once('error', failed);
|
|
79
|
+
setImmediate(() => { if (child.pid !== undefined)
|
|
80
|
+
finish(); });
|
|
81
|
+
});
|
|
82
|
+
return new CodexAppServerTransport(options, child);
|
|
83
|
+
}
|
|
84
|
+
isAlive() {
|
|
85
|
+
return this.child.exitCode === null && (this.child.signalCode ?? null) === null;
|
|
86
|
+
}
|
|
87
|
+
exitResult() { return this.exit; }
|
|
88
|
+
request(method, params = {}, timeoutMs) {
|
|
89
|
+
if (this.closed || !this.isAlive())
|
|
90
|
+
return Promise.reject(new SessionControlError('offline', 'Codex app-server is offline'));
|
|
91
|
+
const id = this.nextId++;
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
const timer = setTimeout(() => {
|
|
94
|
+
this.pending.delete(id);
|
|
95
|
+
reject(new SessionControlError('timeout', `Codex app-server ${method} timed out after ${timeoutMs ?? this.requestTimeoutMs}ms`));
|
|
96
|
+
}, timeoutMs ?? this.requestTimeoutMs);
|
|
97
|
+
timer.unref?.();
|
|
98
|
+
this.pending.set(id, {
|
|
99
|
+
method, timer,
|
|
100
|
+
resolve: value => resolve(value), reject,
|
|
101
|
+
});
|
|
102
|
+
try {
|
|
103
|
+
this.write({ id, method, params });
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
this.pending.delete(id);
|
|
108
|
+
reject(error);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
notify(method, params) {
|
|
113
|
+
this.write(params === undefined ? { method } : { method, params });
|
|
114
|
+
}
|
|
115
|
+
respond(id, result) { this.write({ id, result }); }
|
|
116
|
+
respondError(id, code, message) {
|
|
117
|
+
this.write({ id, error: { code, message } });
|
|
118
|
+
}
|
|
119
|
+
async close() {
|
|
120
|
+
if (this.closed)
|
|
121
|
+
return;
|
|
122
|
+
this.closed = true;
|
|
123
|
+
if (!this.isAlive())
|
|
124
|
+
return;
|
|
125
|
+
this.child.kill('SIGTERM');
|
|
126
|
+
if (await this.waitForExit(2_500))
|
|
127
|
+
return;
|
|
128
|
+
this.child.kill('SIGKILL');
|
|
129
|
+
await this.waitForExit(2_500);
|
|
130
|
+
}
|
|
131
|
+
async waitForExit(timeoutMs) {
|
|
132
|
+
let timer;
|
|
133
|
+
const timedOut = new Promise(resolve => {
|
|
134
|
+
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
135
|
+
timer.unref?.();
|
|
136
|
+
});
|
|
137
|
+
const exited = await Promise.race([this.exited.then(() => true), timedOut]);
|
|
138
|
+
if (timer)
|
|
139
|
+
clearTimeout(timer);
|
|
140
|
+
return exited;
|
|
141
|
+
}
|
|
142
|
+
write(value) {
|
|
143
|
+
if (this.closed || this.child.stdin.destroyed)
|
|
144
|
+
throw new SessionControlError('offline', 'Codex app-server input is closed');
|
|
145
|
+
this.child.stdin.write(JSON.stringify(value) + '\n', error => {
|
|
146
|
+
if (error)
|
|
147
|
+
this.options.log(`[${this.options.name}] codex app-server write failed: ${error.message}`);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
receive(line) {
|
|
151
|
+
let value;
|
|
152
|
+
try {
|
|
153
|
+
const parsed = JSON.parse(line);
|
|
154
|
+
if (!isObject(parsed))
|
|
155
|
+
throw new Error('message is not an object');
|
|
156
|
+
value = parsed;
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
this.options.log(`[${this.options.name}] codex app-server: ignored malformed JSONL (${error.message})`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const method = typeof value.method === 'string' ? value.method : undefined;
|
|
163
|
+
const hasId = typeof value.id === 'string' || typeof value.id === 'number';
|
|
164
|
+
if (method && hasId) {
|
|
165
|
+
this.options.onRequest?.(method, value.id, isObject(value.params) ? value.params : {});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (method) {
|
|
169
|
+
this.options.onNotification?.(method, isObject(value.params) ? value.params : {});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (!hasId)
|
|
173
|
+
return;
|
|
174
|
+
const pending = this.pending.get(value.id);
|
|
175
|
+
if (!pending)
|
|
176
|
+
return;
|
|
177
|
+
this.pending.delete(value.id);
|
|
178
|
+
clearTimeout(pending.timer);
|
|
179
|
+
if (value.error !== undefined)
|
|
180
|
+
pending.reject(responseError(value.error));
|
|
181
|
+
else
|
|
182
|
+
pending.resolve(value.result);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { CommonPermissions } from '../config.js';
|
|
2
|
+
import type { ConversationEventV1, ConversationSnapshot, PromptOrigin, PromptReceipt, SubmitPromptCommand } from './conversation-types.js';
|
|
3
|
+
import type { CodexAppServerTransportFactory } from './codex-app-server-transport.js';
|
|
4
|
+
import type { AgentSession, ConversationHandlePage, ExitRecord, InterruptOutcome, QueuedPrompt, SessionEvent, SessionSnapshot, SubmitPromptOptions, TurnCancellationSource, TurnResult } from './types.js';
|
|
5
|
+
type JsonObject = Record<string, unknown>;
|
|
6
|
+
export interface CodexAppServerSessionOptions {
|
|
7
|
+
name: string;
|
|
8
|
+
argv: string[];
|
|
9
|
+
cwd: string;
|
|
10
|
+
env: Record<string, string>;
|
|
11
|
+
stateDir: string;
|
|
12
|
+
mode: 'fresh' | 'resume';
|
|
13
|
+
permissions: CommonPermissions;
|
|
14
|
+
permissionMode: NonNullable<SessionSnapshot['permissionMode']>;
|
|
15
|
+
model?: string | null;
|
|
16
|
+
effort?: string;
|
|
17
|
+
approvalPolicy: string;
|
|
18
|
+
sandbox: string;
|
|
19
|
+
config?: Record<string, unknown>;
|
|
20
|
+
addDirs?: string[];
|
|
21
|
+
log(line: string): void;
|
|
22
|
+
permissionTimeoutMs?: number;
|
|
23
|
+
controllerGraceMs?: number;
|
|
24
|
+
interruptSettleMs?: number;
|
|
25
|
+
interruptTerminateGraceMs?: number;
|
|
26
|
+
terminalReconcileQuietMs?: number;
|
|
27
|
+
terminalReconcileRequestTimeoutMs?: number;
|
|
28
|
+
}
|
|
29
|
+
/** Server-generated application provenance for authenticated browser prompts only. */
|
|
30
|
+
export declare function nativePromptAdditionalContext(origin: PromptOrigin | undefined): JsonObject | undefined;
|
|
31
|
+
/** Native Codex session implementing Fleet's provider-neutral live-session contract. */
|
|
32
|
+
export declare class CodexAppServerSession implements AgentSession {
|
|
33
|
+
private readonly options;
|
|
34
|
+
readonly backend: "codex-app-server";
|
|
35
|
+
readonly capabilities: import("./types.js").AgentSessionCapabilities;
|
|
36
|
+
readonly pid: number;
|
|
37
|
+
private readonly events;
|
|
38
|
+
private readonly conversation;
|
|
39
|
+
private readonly conversationStartCursor?;
|
|
40
|
+
private readonly sessionGeneration;
|
|
41
|
+
private readonly threadFile;
|
|
42
|
+
private readonly pendingPermissions;
|
|
43
|
+
private readonly activeTools;
|
|
44
|
+
private readonly activeItems;
|
|
45
|
+
private readonly itemPhases;
|
|
46
|
+
private readonly itemText;
|
|
47
|
+
private transport;
|
|
48
|
+
private threadId?;
|
|
49
|
+
private readiness;
|
|
50
|
+
private runtimeModel?;
|
|
51
|
+
private reasoningEffort?;
|
|
52
|
+
private lastUpdateAt?;
|
|
53
|
+
private lastError?;
|
|
54
|
+
private exit;
|
|
55
|
+
private queueDepth;
|
|
56
|
+
private promptTail;
|
|
57
|
+
private activeTurn?;
|
|
58
|
+
private controllerCount;
|
|
59
|
+
private controllerGrace?;
|
|
60
|
+
private interruptForceKill?;
|
|
61
|
+
private terminalReconcile?;
|
|
62
|
+
private terminalReconcileInFlight;
|
|
63
|
+
private finalAnswerObserved;
|
|
64
|
+
private closing;
|
|
65
|
+
private constructor();
|
|
66
|
+
static start(options: CodexAppServerSessionOptions, transportFactory?: CodexAppServerTransportFactory): Promise<CodexAppServerSession>;
|
|
67
|
+
isAlive(): boolean;
|
|
68
|
+
snapshot(): SessionSnapshot;
|
|
69
|
+
queuePrompt(text: string, options?: SubmitPromptOptions): Promise<QueuedPrompt>;
|
|
70
|
+
submitPrompt(text: string, options?: SubmitPromptOptions): Promise<TurnResult>;
|
|
71
|
+
interrupt(source?: TurnCancellationSource): Promise<InterruptOutcome>;
|
|
72
|
+
respondPermission(permissionId: string, optionId: string): boolean;
|
|
73
|
+
respondPermissionV2(permissionId: string, optionId: string, sessionGeneration: string): 'accepted' | 'stale';
|
|
74
|
+
eventsSince(seq: number): SessionEvent[];
|
|
75
|
+
subscribe(listener: (event: SessionEvent) => void): () => void;
|
|
76
|
+
setControllerAttached(attached: boolean): void;
|
|
77
|
+
exitResult(): ExitRecord | null;
|
|
78
|
+
close(): Promise<void>;
|
|
79
|
+
conversationPage(request?: {
|
|
80
|
+
after?: string;
|
|
81
|
+
limit?: number;
|
|
82
|
+
}): ConversationHandlePage;
|
|
83
|
+
conversationSnapshot(): ConversationSnapshot;
|
|
84
|
+
subscribeConversation(listener: (event: ConversationEventV1) => void): () => void;
|
|
85
|
+
submitPromptBrowser(command: SubmitPromptCommand): Promise<PromptReceipt>;
|
|
86
|
+
private initialize;
|
|
87
|
+
private recoverOpenPrompts;
|
|
88
|
+
private admit;
|
|
89
|
+
private runPrompt;
|
|
90
|
+
private steer;
|
|
91
|
+
private notification;
|
|
92
|
+
private itemStarted;
|
|
93
|
+
private itemCompleted;
|
|
94
|
+
private messageDelta;
|
|
95
|
+
private emitMessage;
|
|
96
|
+
private thoughtDelta;
|
|
97
|
+
private planUpdated;
|
|
98
|
+
private recordTool;
|
|
99
|
+
private finishTurn;
|
|
100
|
+
/**
|
|
101
|
+
* Some supported Codex versions can persist a completed turn without
|
|
102
|
+
* emitting `turn/completed`. Reconcile only an exact, quiescent final-answer
|
|
103
|
+
* candidate against the app-server's authoritative thread snapshot.
|
|
104
|
+
*/
|
|
105
|
+
private scheduleTerminalReconciliation;
|
|
106
|
+
private terminalCandidate;
|
|
107
|
+
private reconcileTerminal;
|
|
108
|
+
private cancelTerminalReconciliation;
|
|
109
|
+
private clearInterruptForceKill;
|
|
110
|
+
private terminateFailedTransport;
|
|
111
|
+
private serverRequest;
|
|
112
|
+
private requestPermission;
|
|
113
|
+
private serverRequestResolved;
|
|
114
|
+
private autoResolvePermission;
|
|
115
|
+
private resolvePermission;
|
|
116
|
+
private currentEvent;
|
|
117
|
+
private isActiveNotification;
|
|
118
|
+
private permissionResponse;
|
|
119
|
+
private transportExited;
|
|
120
|
+
private fail;
|
|
121
|
+
}
|
|
122
|
+
export {};
|