@ours.network/fleet 1.1.0-nightly.9 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/README.md +231 -46
  2. package/dist/application/capabilities.js +18 -6
  3. package/dist/application/fleet-query-service.js +7 -4
  4. package/dist/application/role-command-service.js +9 -0
  5. package/dist/application/role-creation-service.d.ts +4 -4
  6. package/dist/application/role-creation-service.js +8 -1
  7. package/dist/application/role-repository.d.ts +4 -2
  8. package/dist/application/role-repository.js +6 -3
  9. package/dist/application/session-control.d.ts +3 -1
  10. package/dist/application/session-control.js +4 -2
  11. package/dist/application/task-room-service.d.ts +92 -39
  12. package/dist/application/task-room-service.js +562 -289
  13. package/dist/briefing.js +79 -22
  14. package/dist/build-info.json +5 -5
  15. package/dist/canonical-json.d.ts +2 -0
  16. package/dist/canonical-json.js +10 -0
  17. package/dist/cli.js +129 -47
  18. package/dist/config.d.ts +38 -3
  19. package/dist/config.js +193 -28
  20. package/dist/creation.d.ts +1 -1
  21. package/dist/creation.js +7 -4
  22. package/dist/docs.d.ts +1 -1
  23. package/dist/docs.js +181 -41
  24. package/dist/doctor.d.ts +2 -0
  25. package/dist/doctor.js +40 -4
  26. package/dist/fleet-command-audit.d.ts +22 -1
  27. package/dist/fleet-command-audit.js +355 -50
  28. package/dist/fleet-proxy.d.ts +5 -2
  29. package/dist/harness/claude-code-session.js +2 -1
  30. package/dist/harness/codex-app-server-proxy.d.ts +40 -0
  31. package/dist/harness/codex-app-server-proxy.js +348 -14
  32. package/dist/harness/codex-session.d.ts +9 -2
  33. package/dist/harness/codex-session.js +18 -2
  34. package/dist/harness/codex.d.ts +5 -1
  35. package/dist/harness/codex.js +104 -10
  36. package/dist/harness/types.d.ts +1 -1
  37. package/dist/index.d.ts +4 -3
  38. package/dist/index.js +3 -2
  39. package/dist/init-guidance.d.ts +1 -1
  40. package/dist/init-guidance.js +1 -1
  41. package/dist/init-wizard.d.ts +98 -0
  42. package/dist/init-wizard.js +631 -0
  43. package/dist/lifecycle-summary.d.ts +134 -0
  44. package/dist/lifecycle-summary.js +179 -0
  45. package/dist/loops/config.d.ts +10 -1
  46. package/dist/loops/config.js +38 -5
  47. package/dist/model-recovery.d.ts +1 -1
  48. package/dist/ops.js +2 -17
  49. package/dist/owner-channel/channel.d.ts +19 -3
  50. package/dist/owner-channel/channel.js +301 -120
  51. package/dist/owner-channel/commands.d.ts +28 -6
  52. package/dist/owner-channel/commands.js +109 -27
  53. package/dist/owner-channel/lifecycle-outbox.d.ts +24 -0
  54. package/dist/owner-channel/lifecycle-outbox.js +87 -0
  55. package/dist/owner-channel/ours-client.d.ts +19 -0
  56. package/dist/owner-channel/ours-client.js +3 -0
  57. package/dist/permissions.js +1 -1
  58. package/dist/preset-migration.d.ts +42 -0
  59. package/dist/preset-migration.js +450 -0
  60. package/dist/reports/artifact.d.ts +10 -0
  61. package/dist/reports/artifact.js +32 -0
  62. package/dist/reports/index.d.ts +5 -0
  63. package/dist/reports/index.js +5 -0
  64. package/dist/reports/render.d.ts +6 -0
  65. package/dist/reports/render.js +240 -0
  66. package/dist/reports/service.d.ts +42 -0
  67. package/dist/reports/service.js +57 -0
  68. package/dist/reports/tasks.d.ts +52 -0
  69. package/dist/reports/tasks.js +177 -0
  70. package/dist/reports/types.d.ts +315 -0
  71. package/dist/reports/types.js +6 -0
  72. package/dist/resolved-plan.js +13 -1
  73. package/dist/rooms-tasks/cli.d.ts +2 -0
  74. package/dist/rooms-tasks/cli.js +361 -285
  75. package/dist/rooms-tasks/close.d.ts +9 -1
  76. package/dist/rooms-tasks/close.js +23 -7
  77. package/dist/rooms-tasks/config.js +16 -25
  78. package/dist/rooms-tasks/cowork-adapter.d.ts +5 -0
  79. package/dist/rooms-tasks/cowork-adapter.js +13 -0
  80. package/dist/rooms-tasks/deletion.d.ts +51 -0
  81. package/dist/rooms-tasks/deletion.js +217 -0
  82. package/dist/rooms-tasks/external-worker.d.ts +8 -0
  83. package/dist/rooms-tasks/external-worker.js +38 -0
  84. package/dist/rooms-tasks/launch-snapshot.d.ts +19 -0
  85. package/dist/rooms-tasks/launch-snapshot.js +181 -0
  86. package/dist/rooms-tasks/member-overrides.d.ts +30 -0
  87. package/dist/rooms-tasks/member-overrides.js +204 -0
  88. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  89. package/dist/rooms-tasks/member-startup.js +9 -2
  90. package/dist/rooms-tasks/provision.js +146 -83
  91. package/dist/rooms-tasks/room-state.d.ts +1 -0
  92. package/dist/rooms-tasks/room-state.js +12 -1
  93. package/dist/rooms-tasks/task-room-name.d.ts +12 -0
  94. package/dist/rooms-tasks/task-room-name.js +61 -0
  95. package/dist/rooms-tasks/task-state.d.ts +115 -3
  96. package/dist/rooms-tasks/task-state.js +361 -11
  97. package/dist/rooms-tasks/templates.d.ts +4 -1
  98. package/dist/rooms-tasks/templates.js +34 -3
  99. package/dist/rooms-tasks/terminal.d.ts +3 -0
  100. package/dist/rooms-tasks/terminal.js +10 -4
  101. package/dist/rooms-tasks/types.d.ts +92 -7
  102. package/dist/rooms-tasks/types.js +14 -1
  103. package/dist/runner.d.ts +3 -0
  104. package/dist/runner.js +91 -44
  105. package/dist/session/acp.d.ts +4 -1
  106. package/dist/session/acp.js +29 -13
  107. package/dist/session/arbiter.d.ts +2 -1
  108. package/dist/session/arbiter.js +2 -0
  109. package/dist/session/codex-app-server-transport.d.ts +53 -0
  110. package/dist/session/codex-app-server-transport.js +184 -0
  111. package/dist/session/codex-app-server.d.ts +122 -0
  112. package/dist/session/codex-app-server.js +1031 -0
  113. package/dist/session/control.d.ts +1 -1
  114. package/dist/session/control.js +17 -1
  115. package/dist/session/conversation-types.d.ts +4 -4
  116. package/dist/session/types.d.ts +24 -6
  117. package/dist/session/types.js +28 -0
  118. package/dist/spawn.d.ts +9 -0
  119. package/dist/spawn.js +77 -9
  120. package/dist/web/fleet-config-service.d.ts +1 -0
  121. package/dist/web/fleet-config-service.js +25 -7
  122. package/dist/web/runtime.js +11 -6
  123. package/dist/web/server.js +28 -1
  124. package/dist/web-app/assets/{index-BbTc5KtZ.js → index-C0h3ALvs.js} +8 -8
  125. package/dist/web-app/index.html +1 -1
  126. package/package.json +4 -4
  127. package/presets/brain-catalog.json +17 -0
  128. package/presets/fleet/{agents → agent_templates}/Critic.yaml +2 -0
  129. package/presets/fleet/{agents/Agent.yaml → agent_templates/Developer.yaml} +3 -1
  130. package/presets/fleet/agent_templates/LocalCoordinator.yaml +50 -0
  131. package/presets/fleet/agents/{Developer.yaml → FleetCoordinator.yaml} +1 -1
  132. package/presets/fleet/brains/claude-fable-5-high.yaml +5 -0
  133. package/presets/fleet/brains/claude-fable-5-low.yaml +5 -0
  134. package/presets/fleet/brains/claude-fable-5-max.yaml +5 -0
  135. package/presets/fleet/brains/claude-fable-5-medium.yaml +5 -0
  136. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +5 -0
  137. package/presets/fleet/brains/claude-opus-5-high.yaml +5 -0
  138. package/presets/fleet/brains/claude-opus-5-low.yaml +5 -0
  139. package/presets/fleet/brains/claude-opus-5-max.yaml +5 -0
  140. package/presets/fleet/brains/claude-opus-5-medium.yaml +5 -0
  141. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +5 -0
  142. package/presets/fleet/brains/claude-sonnet-5-high.yaml +5 -0
  143. package/presets/fleet/brains/claude-sonnet-5-low.yaml +5 -0
  144. package/presets/fleet/brains/claude-sonnet-5-max.yaml +5 -0
  145. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +5 -0
  146. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +5 -0
  147. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +5 -0
  148. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +5 -0
  149. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +5 -0
  150. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +5 -0
  151. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +5 -0
  152. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +5 -0
  153. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +5 -0
  154. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +5 -0
  155. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +5 -0
  156. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +5 -0
  157. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +5 -0
  158. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +5 -0
  159. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +5 -0
  160. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +5 -0
  161. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +5 -0
  162. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +5 -0
  163. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +5 -0
  164. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +5 -0
  165. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +5 -0
  166. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +5 -0
  167. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +5 -0
  168. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +5 -0
  169. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +5 -0
  170. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +5 -0
  171. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +5 -0
  172. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +5 -0
  173. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +5 -0
  174. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +5 -0
  175. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +5 -0
  176. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +5 -0
  177. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +5 -0
  178. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +5 -0
  179. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +5 -0
  180. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +5 -0
  181. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +5 -0
  182. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +5 -0
  183. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +5 -0
  184. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +5 -0
  185. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +5 -0
  186. package/presets/fleet/roles/Coordinator.yaml +37 -0
  187. package/presets/fleet/roles/Critic.yaml +23 -1
  188. package/presets/fleet/roles/Developer.yaml +27 -5
  189. package/presets/fleet/roles/LocalCoordinator.yaml +35 -0
  190. package/presets/fleet/room_templates/pair.yaml +9 -7
  191. package/presets/fleet/room_templates/single.yaml +5 -4
  192. package/presets/fleet/room_templates/team.yaml +12 -8
  193. package/presets/manifest.json +2 -1
  194. package/presets/fleet/agents/Architect.yaml +0 -3
  195. package/presets/fleet/agents/Secretary.yaml +0 -3
  196. package/presets/fleet/agents/Tester.yaml +0 -3
  197. package/presets/fleet/roles/Agent.yaml +0 -6
  198. package/presets/fleet/roles/Architect.yaml +0 -6
  199. package/presets/fleet/roles/Secretary.yaml +0 -6
  200. package/presets/fleet/roles/Tester.yaml +0 -6
@@ -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
- capabilities;
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.capabilities?.sessionCapabilities?.close != null) {
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.capabilities = initialized.agentCapabilities;
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
- if (persisted && this.capabilities?.sessionCapabilities?.resume != null) {
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
- this.captureRuntimeMetadata(advertisedConfigOptions);
1047
+ advertisedModelId = resumed.models?.currentModelId;
1048
+ this.captureRuntimeMetadata(advertisedConfigOptions, advertisedModelId);
1039
1049
  this.sessionId = persisted;
1040
1050
  }
1041
- else if (persisted && this.capabilities?.loadSession) {
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
- this.captureRuntimeMetadata(advertisedConfigOptions);
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
- this.captureRuntimeMetadata(advertisedConfigOptions);
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: "acp";
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;
@@ -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 {};