@ours.network/fleet 1.1.0-nightly.8 → 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 +9 -2
- 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 +579 -173
- package/dist/briefing.js +85 -28
- 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 +130 -55
- 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 +193 -55
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +40 -4
- package/dist/fleet-command-audit.d.ts +59 -9
- package/dist/fleet-command-audit.js +426 -63
- 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 +21 -5
- package/dist/owner-channel/channel.js +312 -138
- 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/owner-channel/state.d.ts +2 -0
- package/dist/owner-channel/state.js +8 -2
- 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 +434 -279
- 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 -56
- 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 +92 -33
- 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 +2 -2
- package/dist/session/control.js +18 -2
- 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
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. */
|
|
@@ -187,6 +187,12 @@ export function runtimeSelector(options, category) {
|
|
|
187
187
|
const selected = choices.find(choice => choice.value === option.currentValue);
|
|
188
188
|
return { value: option.currentValue, ...(selected?.name ? { label: selected.name } : {}) };
|
|
189
189
|
}
|
|
190
|
+
function reasoningFromModelId(modelId) {
|
|
191
|
+
if (typeof modelId !== 'string')
|
|
192
|
+
return undefined;
|
|
193
|
+
const match = modelId.match(/\[([^\]]+)\]$/u);
|
|
194
|
+
return match?.[1] ? { value: match[1] } : undefined;
|
|
195
|
+
}
|
|
190
196
|
/**
|
|
191
197
|
* Classify an ACP `stopReason` into a terminal outcome. A refusal and a
|
|
192
198
|
* cancellation are the two ways a delivered prompt ends without being carried
|
|
@@ -206,6 +212,7 @@ export function classifyStopReason(stopReason) {
|
|
|
206
212
|
export class AcpSession {
|
|
207
213
|
options;
|
|
208
214
|
backend = 'acp';
|
|
215
|
+
capabilities;
|
|
209
216
|
pid;
|
|
210
217
|
child;
|
|
211
218
|
events;
|
|
@@ -232,7 +239,7 @@ export class AcpSession {
|
|
|
232
239
|
queueDepth = 0;
|
|
233
240
|
exit = null;
|
|
234
241
|
steeringSupported = false;
|
|
235
|
-
|
|
242
|
+
agentCapabilities;
|
|
236
243
|
runtimeModel;
|
|
237
244
|
reasoningEffort;
|
|
238
245
|
controllerCount = 0;
|
|
@@ -265,6 +272,7 @@ export class AcpSession {
|
|
|
265
272
|
this.child = child;
|
|
266
273
|
this.connection = connection;
|
|
267
274
|
this.pid = child.pid ?? -1;
|
|
275
|
+
this.capabilities = sessionBackendCapabilities('acp', options.harness);
|
|
268
276
|
this.events = new SessionEvents(join(options.stateDir, '.session-events.jsonl'));
|
|
269
277
|
this.conversation = new ConversationEventStore(join(options.stateDir, '.conversation'), {
|
|
270
278
|
roleId: options.name, log: line => options.log(`[${options.name}] ${line}`),
|
|
@@ -994,7 +1002,7 @@ export class AcpSession {
|
|
|
994
1002
|
for (const [permissionId, pending] of [...this.pendingPermissions])
|
|
995
1003
|
this.settlePendingAutomatically(permissionId, pending, 'cancelled', undefined, 'the session closed while this request was pending');
|
|
996
1004
|
this.releaseAllTools();
|
|
997
|
-
if (this.sessionId && this.
|
|
1005
|
+
if (this.sessionId && this.agentCapabilities?.sessionCapabilities?.close != null) {
|
|
998
1006
|
await this.connection.agent.request(acp.methods.agent.session.close, { sessionId: this.sessionId }).catch(() => undefined);
|
|
999
1007
|
}
|
|
1000
1008
|
this.connection.close();
|
|
@@ -1020,7 +1028,7 @@ export class AcpSession {
|
|
|
1020
1028
|
});
|
|
1021
1029
|
if (initialized.protocolVersion !== acp.PROTOCOL_VERSION)
|
|
1022
1030
|
throw new Error(`ACP protocol mismatch: agent selected ${initialized.protocolVersion}, client supports ${acp.PROTOCOL_VERSION}`);
|
|
1023
|
-
this.
|
|
1031
|
+
this.agentCapabilities = initialized.agentCapabilities;
|
|
1024
1032
|
const steering = initialized._meta?.steering;
|
|
1025
1033
|
this.steeringSupported = steering !== null && typeof steering === 'object'
|
|
1026
1034
|
&& steering.supported === true;
|
|
@@ -1028,17 +1036,19 @@ export class AcpSession {
|
|
|
1028
1036
|
? readFileSync(this.sessionFile, 'utf8').trim()
|
|
1029
1037
|
: '';
|
|
1030
1038
|
let advertisedConfigOptions;
|
|
1031
|
-
|
|
1039
|
+
let advertisedModelId;
|
|
1040
|
+
if (persisted && this.agentCapabilities?.sessionCapabilities?.resume != null) {
|
|
1032
1041
|
const resumed = await this.connection.agent.request(acp.methods.agent.session.resume, {
|
|
1033
1042
|
sessionId: persisted,
|
|
1034
1043
|
cwd: this.options.cwd,
|
|
1035
1044
|
mcpServers: this.declaredMcpServers(),
|
|
1036
1045
|
});
|
|
1037
1046
|
advertisedConfigOptions = resumed.configOptions;
|
|
1038
|
-
|
|
1047
|
+
advertisedModelId = resumed.models?.currentModelId;
|
|
1048
|
+
this.captureRuntimeMetadata(advertisedConfigOptions, advertisedModelId);
|
|
1039
1049
|
this.sessionId = persisted;
|
|
1040
1050
|
}
|
|
1041
|
-
else if (persisted && this.
|
|
1051
|
+
else if (persisted && this.agentCapabilities?.loadSession) {
|
|
1042
1052
|
// `session/load` replays prior history as ordinary updates before the
|
|
1043
1053
|
// response; those records carry `agent_replay` provenance, never `agent`.
|
|
1044
1054
|
this.replaying = true;
|
|
@@ -1049,7 +1059,8 @@ export class AcpSession {
|
|
|
1049
1059
|
mcpServers: this.declaredMcpServers(),
|
|
1050
1060
|
});
|
|
1051
1061
|
advertisedConfigOptions = loaded.configOptions;
|
|
1052
|
-
|
|
1062
|
+
advertisedModelId = loaded.models?.currentModelId;
|
|
1063
|
+
this.captureRuntimeMetadata(advertisedConfigOptions, advertisedModelId);
|
|
1053
1064
|
}
|
|
1054
1065
|
finally {
|
|
1055
1066
|
this.replaying = false;
|
|
@@ -1064,11 +1075,16 @@ export class AcpSession {
|
|
|
1064
1075
|
});
|
|
1065
1076
|
this.sessionId = created.sessionId;
|
|
1066
1077
|
advertisedConfigOptions = created.configOptions;
|
|
1067
|
-
|
|
1078
|
+
advertisedModelId = created.models?.currentModelId;
|
|
1079
|
+
this.captureRuntimeMetadata(advertisedConfigOptions, advertisedModelId);
|
|
1068
1080
|
}
|
|
1069
1081
|
for (const selection of this.options.configSelections ?? []) {
|
|
1070
|
-
if (!advertisedConfigOptions?.some(option => option.id === selection.configId))
|
|
1082
|
+
if (!advertisedConfigOptions?.some(option => option.id === selection.configId)) {
|
|
1083
|
+
if (selection.configId === 'reasoning_effort'
|
|
1084
|
+
&& reasoningFromModelId(advertisedModelId)?.value === selection.value)
|
|
1085
|
+
continue;
|
|
1071
1086
|
throw new Error(`ACP agent did not advertise required session config option '${selection.configId}'`);
|
|
1087
|
+
}
|
|
1072
1088
|
let configured;
|
|
1073
1089
|
try {
|
|
1074
1090
|
configured = await this.connection.agent.request(acp.methods.agent.session.setConfigOption, { sessionId: this.sessionId, configId: selection.configId, value: selection.value });
|
|
@@ -1078,7 +1094,7 @@ export class AcpSession {
|
|
|
1078
1094
|
+ (error instanceof Error ? error.message : String(error)));
|
|
1079
1095
|
}
|
|
1080
1096
|
advertisedConfigOptions = configured.configOptions;
|
|
1081
|
-
this.captureRuntimeMetadata(advertisedConfigOptions);
|
|
1097
|
+
this.captureRuntimeMetadata(advertisedConfigOptions, advertisedModelId);
|
|
1082
1098
|
const applied = advertisedConfigOptions.find(option => option.id === selection.configId);
|
|
1083
1099
|
if (applied?.currentValue !== selection.value)
|
|
1084
1100
|
throw new Error(`ACP agent did not apply required session config option '${selection.configId}' value '${selection.value}'`);
|
|
@@ -1110,9 +1126,9 @@ export class AcpSession {
|
|
|
1110
1126
|
acpSessionId: this.sessionId, payload: { status: 'idle' },
|
|
1111
1127
|
});
|
|
1112
1128
|
}
|
|
1113
|
-
captureRuntimeMetadata(options) {
|
|
1129
|
+
captureRuntimeMetadata(options, modelId) {
|
|
1114
1130
|
this.runtimeModel = runtimeSelector(options, 'model');
|
|
1115
|
-
this.reasoningEffort = runtimeSelector(options, 'thought_level');
|
|
1131
|
+
this.reasoningEffort = runtimeSelector(options, 'thought_level') ?? reasoningFromModelId(modelId);
|
|
1116
1132
|
}
|
|
1117
1133
|
async runPrompt(text, turnId = randomUUID(), origin) {
|
|
1118
1134
|
if (!this.sessionId || !this.isAlive())
|
|
@@ -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 {};
|