orizu 0.5.48 → 0.5.50

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 (94) hide show
  1. package/dist/account-directory.d.ts +2 -2
  2. package/dist/account-directory.d.ts.map +1 -1
  3. package/dist/account-directory.js +7 -32
  4. package/dist/auth-commands.d.ts.map +1 -1
  5. package/dist/auth-commands.js +22 -30
  6. package/dist/credentials.d.ts +5 -0
  7. package/dist/credentials.d.ts.map +1 -1
  8. package/dist/credentials.js +84 -3
  9. package/dist/dataset-transfer-upload.d.ts.map +1 -1
  10. package/dist/dataset-transfer-upload.js +6 -1
  11. package/dist/directory-list-read.d.ts +37 -0
  12. package/dist/directory-list-read.d.ts.map +1 -0
  13. package/dist/directory-list-read.js +87 -0
  14. package/dist/hosted-boot.d.ts.map +1 -1
  15. package/dist/hosted-boot.js +59 -41
  16. package/dist/hosted-harness-opencode.d.ts +31 -0
  17. package/dist/hosted-harness-opencode.d.ts.map +1 -1
  18. package/dist/hosted-harness-opencode.js +45 -7
  19. package/dist/hosted-loop.d.ts +39 -2
  20. package/dist/hosted-loop.d.ts.map +1 -1
  21. package/dist/hosted-loop.js +127 -18
  22. package/dist/hosted-optimization.d.ts.map +1 -1
  23. package/dist/hosted-optimization.js +40 -21
  24. package/dist/hosted-session-seed.d.ts +2 -0
  25. package/dist/hosted-session-seed.d.ts.map +1 -1
  26. package/dist/hosted-session-seed.js +27 -10
  27. package/dist/http.d.ts.map +1 -1
  28. package/dist/http.js +39 -234
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +83 -68
  31. package/dist/keyboard-select.d.ts.map +1 -1
  32. package/dist/keyboard-select.js +23 -45
  33. package/dist/project-create-workflow.d.ts +18 -0
  34. package/dist/project-create-workflow.d.ts.map +1 -0
  35. package/dist/project-create-workflow.js +121 -0
  36. package/dist/runner-folders.d.ts +61 -0
  37. package/dist/runner-folders.d.ts.map +1 -0
  38. package/dist/runner-folders.js +222 -0
  39. package/dist/runner-version-materialization.d.ts +1 -1
  40. package/dist/runner-version-materialization.d.ts.map +1 -1
  41. package/dist/runner-version-materialization.js +20 -38
  42. package/dist/self-update-command.d.ts +8 -0
  43. package/dist/self-update-command.d.ts.map +1 -0
  44. package/dist/self-update-command.js +390 -0
  45. package/dist/self-update.d.ts +29 -16
  46. package/dist/self-update.d.ts.map +1 -1
  47. package/dist/self-update.js +55 -244
  48. package/dist/setup-account.d.ts.map +1 -1
  49. package/dist/setup-account.js +4 -1
  50. package/dist/setup-and-skills-commands.d.ts.map +1 -1
  51. package/dist/setup-and-skills-commands.js +88 -102
  52. package/dist/skill-install-workflow.d.ts +23 -0
  53. package/dist/skill-install-workflow.d.ts.map +1 -0
  54. package/dist/skill-install-workflow.js +184 -0
  55. package/dist/skill-installer.d.ts +8 -3
  56. package/dist/skill-installer.d.ts.map +1 -1
  57. package/dist/skill-installer.js +26 -33
  58. package/dist/skilled-proposer-launch.d.ts +11 -1
  59. package/dist/skilled-proposer-launch.d.ts.map +1 -1
  60. package/dist/skilled-proposer-launch.js +35 -4
  61. package/dist/stored-session-requests.d.ts +27 -0
  62. package/dist/stored-session-requests.d.ts.map +1 -0
  63. package/dist/stored-session-requests.js +264 -0
  64. package/dist/terminal-key-session.d.ts +23 -0
  65. package/dist/terminal-key-session.d.ts.map +1 -0
  66. package/dist/terminal-key-session.js +74 -0
  67. package/package.json +1 -1
  68. package/scripts/build-cli-dist.mjs +46 -8
  69. package/scripts/vendor-orizu-skill.mjs +52 -7
  70. package/src/account-directory.ts +8 -42
  71. package/src/auth-commands.ts +20 -29
  72. package/src/credentials.ts +83 -3
  73. package/src/dataset-transfer-upload.ts +7 -1
  74. package/src/directory-list-read.ts +123 -0
  75. package/src/hosted-boot.ts +65 -43
  76. package/src/hosted-harness-opencode.ts +65 -9
  77. package/src/hosted-loop.ts +152 -18
  78. package/src/hosted-optimization.ts +49 -31
  79. package/src/hosted-session-seed.ts +25 -8
  80. package/src/http.ts +39 -317
  81. package/src/index.ts +79 -87
  82. package/src/keyboard-select.ts +18 -55
  83. package/src/project-create-workflow.ts +139 -0
  84. package/src/runner-folders.ts +223 -0
  85. package/src/runner-version-materialization.ts +21 -34
  86. package/src/self-update-command.ts +419 -0
  87. package/src/self-update.ts +70 -266
  88. package/src/setup-account.ts +4 -1
  89. package/src/setup-and-skills-commands.ts +89 -121
  90. package/src/skill-install-workflow.ts +215 -0
  91. package/src/skill-installer.ts +31 -38
  92. package/src/skilled-proposer-launch.ts +41 -3
  93. package/src/stored-session-requests.ts +316 -0
  94. package/src/terminal-key-session.ts +88 -0
@@ -35,7 +35,7 @@ import { closeSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync,
35
35
  import { isValidCloudflareArtifactsGitRemote } from './cloudflare-artifacts-git-remote.js';
36
36
  import { cloneTeamIntoWorkTree, seedSessionCopy } from './hosted-session-seed.js';
37
37
  import { AGENT_GIT_IDENTITY, BEARER_BASENAME, BOOT_CONTEXT_BASENAME, DEFAULT_CACHE_REFRESH_BUFFER_MS, HELPER_SCRIPT_BASENAME, REPO_CRED_CACHE_BASENAME, renderCredentialHelperScript, serializeBootContext, } from './hosted-runtime-assets.js';
38
- import { INJECTED_ENV_VARS_ENV, closeHostedLoopSession, runHostedLoop, runHostedLoopTurn, startHostedLoopSession, } from './hosted-loop.js';
38
+ import { INJECTED_ENV_VARS_ENV, closeHostedLoopSession, hostedSessionOpenCodeLogPath, prestartHostedOpenCode, runHostedLoop, runHostedLoopTurn, startHostedLoopSession, } from './hosted-loop.js';
39
39
  import { DEFAULT_EGRESS_CANARY_HOST, DEFAULT_HOSTED_MODEL } from './hosted-loop-lifecycle.js';
40
40
  import { composeHostedAnswerPrompt } from './hosted-question.js';
41
41
  import { resumeRunEventSink } from './hosted-run-event-sink.js';
@@ -675,16 +675,20 @@ export async function runHostedBoot(opts) {
675
675
  // work and closed however the boot ends.
676
676
  const liveChannels = [];
677
677
  const idleWait = new AbortController();
678
+ // ORI-2512: stops anything the boot started in the background.
679
+ const bootCleanups = [];
678
680
  try {
679
- return await runHostedBootWithLive(opts, liveChannels, idleWait.signal);
681
+ return await runHostedBootWithLive(opts, liveChannels, idleWait.signal, bootCleanups);
680
682
  }
681
683
  finally {
682
684
  idleWait.abort();
683
685
  for (const channel of liveChannels)
684
686
  channel.close();
687
+ for (const cleanup of bootCleanups)
688
+ cleanup();
685
689
  }
686
690
  }
687
- async function runHostedBootWithLive(opts, liveChannels, idleWaitSignal) {
691
+ async function runHostedBootWithLive(opts, liveChannels, idleWaitSignal, bootCleanups) {
688
692
  const env = opts.env;
689
693
  const processEnv = opts.processEnv ?? process.env;
690
694
  const root = opts.root ?? process.cwd();
@@ -913,6 +917,43 @@ async function runHostedBootWithLive(opts, liveChannels, idleWaitSignal) {
913
917
  };
914
918
  writeFile(bootContextAbs, serializeBootContext(bootContext));
915
919
  const helperValue = `!node ${helperScriptAbs} ${bootContextAbs}`;
920
+ // The loop's context (step 6), built before the clone so the background
921
+ // start below uses the same settings. The bearer PROVIDER keeps the event
922
+ // sink's bearer fresh from the same pull-mode source; the DO-provisioned
923
+ // sandbox always runs the pre-baked image under an enforced egress policy, so
924
+ // the loop's startup egress canary + deferred setup hook are both armed.
925
+ const model = session.model ?? DEFAULT_HOSTED_MODEL;
926
+ const configuredOpenCodePort = resolveTestPositiveInteger(processEnv, 'ORIZU_HOSTED_TEST_OPENCODE_PORT', 65_535) ?? (processEnv.ORIZU_OPENCODE_PORT
927
+ ? Number(processEnv.ORIZU_OPENCODE_PORT)
928
+ : undefined);
929
+ if (configuredOpenCodePort !== undefined && (!Number.isInteger(configuredOpenCodePort) || configuredOpenCodePort < 1 || configuredOpenCodePort > 65_535)) {
930
+ throw new Error('ORIZU_OPENCODE_PORT must be a valid TCP port');
931
+ }
932
+ const loopContext = {
933
+ apiBaseUrl: env.baseUrl,
934
+ runId,
935
+ bearerFile: bearerFileAbs,
936
+ taskFile: taskFileAbs,
937
+ workspaceDir,
938
+ sessionBranch: session.repoBranch,
939
+ repositoryRemote: repo.cloneUrl,
940
+ repositoryCredentialHelper: helperValue,
941
+ model,
942
+ reasoningEffort: session.reasoningEffort ?? undefined,
943
+ sessionOrigin: session.surface === 'hosted-web' ? 'hosted-web' : 'cli',
944
+ messageId: `${runId}:task`,
945
+ resumeAgentSessionId: session.agentSessionId ?? undefined,
946
+ author: AGENT_GIT_IDENTITY,
947
+ anthropicDummyKey: env.anthropicDummyKey ?? ANTHROPIC_DUMMY_KEY,
948
+ // Derive the per-prompt max-duration cap from the session duration so a long
949
+ // run is not killed at the hard-coded 90-min prompt cap (ALI-1061). Unset →
950
+ // the harness default (5400s) floor holds.
951
+ sandboxBudgetMs: session.durationMinutes != null ? session.durationMinutes * 60 * 1000 : undefined,
952
+ prebaked: true,
953
+ egressCanaryHost: (processEnv.NODE_ENV === 'test' ? processEnv.ORIZU_HOSTED_TEST_EGRESS_CANARY_HOST : undefined) ?? DEFAULT_EGRESS_CANARY_HOST,
954
+ runSetupHook: true,
955
+ opencodePort: configuredOpenCodePort,
956
+ };
916
957
  // 4b — ORI-2118: a hosted session's Artifacts copy is created empty (forks
917
958
  // of a busy team copy cannot be cloned). Fill it from the server-selected
918
959
  // origin before cloning. Selected saved history is checked on retries too.
@@ -921,6 +962,18 @@ async function runHostedBootWithLive(opts, liveChannels, idleWaitSignal) {
921
962
  }
922
963
  // ORI-2520: a woken sandbox boots on the disk it slept with (see step 5).
923
964
  const wokenDisk = existsSync(workspaceDir) && readdirSync(workspaceDir).length > 0;
965
+ // ORI-2512 cut 3: start the egress canary and then OpenCode while the
966
+ // repository clones. OpenCode runs in the (empty) working folder, which the
967
+ // clone fills in place, and is asked nothing until after the clone. The
968
+ // one-shot test seam (opts.runLoop) starts nothing early, but makes the
969
+ // folder the same way, so it takes production's fill.
970
+ mkdirp(workspaceDir);
971
+ const prestart = opts.runLoop
972
+ ? null
973
+ : prestartHostedOpenCode({ ...loopContext, projectId: session.projectId }, { logPath: hostedSessionOpenCodeLogPath({ sessionDir: sessionDirAbs, sessionId: env.sessionId }) });
974
+ // However the boot ends, nothing it started outlives it.
975
+ if (prestart)
976
+ bootCleanups.push(prestart.stop);
924
977
  // Set when the working folder came from the team copy (ORI-2512); settles
925
978
  // when the background fill of the session copy ends, and never rejects.
926
979
  let sessionCopyFilled = null;
@@ -1053,7 +1106,8 @@ const c = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
1053
1106
  log(`woken disk: reusing the repo (checked out: ${checkedOut})`);
1054
1107
  }
1055
1108
  else {
1056
- const clone = exec('git', [
1109
+ // Not blocking, so the background start above keeps running (ORI-2512).
1110
+ const clone = await execAsync('git', [
1057
1111
  'clone',
1058
1112
  '--depth',
1059
1113
  '1',
@@ -1126,42 +1180,6 @@ const c = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
1126
1180
  fetchImpl,
1127
1181
  log,
1128
1182
  });
1129
- // 6 — Launch the hosted loop, reused in-process. The bearer PROVIDER keeps the
1130
- // event sink's bearer fresh from the same pull-mode source; the DO-provisioned
1131
- // sandbox always runs the pre-baked image under an enforced egress policy, so
1132
- // the loop's startup egress canary + deferred setup hook are both armed.
1133
- const model = session.model ?? DEFAULT_HOSTED_MODEL;
1134
- const configuredOpenCodePort = resolveTestPositiveInteger(processEnv, 'ORIZU_HOSTED_TEST_OPENCODE_PORT', 65_535) ?? (processEnv.ORIZU_OPENCODE_PORT
1135
- ? Number(processEnv.ORIZU_OPENCODE_PORT)
1136
- : undefined);
1137
- if (configuredOpenCodePort !== undefined && (!Number.isInteger(configuredOpenCodePort) || configuredOpenCodePort < 1 || configuredOpenCodePort > 65_535)) {
1138
- throw new Error('ORIZU_OPENCODE_PORT must be a valid TCP port');
1139
- }
1140
- const loopContext = {
1141
- apiBaseUrl: env.baseUrl,
1142
- runId,
1143
- bearerFile: bearerFileAbs,
1144
- taskFile: taskFileAbs,
1145
- workspaceDir,
1146
- sessionBranch: session.repoBranch,
1147
- repositoryRemote: repo.cloneUrl,
1148
- repositoryCredentialHelper: helperValue,
1149
- model,
1150
- reasoningEffort: session.reasoningEffort ?? undefined,
1151
- sessionOrigin: session.surface === 'hosted-web' ? 'hosted-web' : 'cli',
1152
- messageId: `${runId}:task`,
1153
- resumeAgentSessionId: session.agentSessionId ?? undefined,
1154
- author: AGENT_GIT_IDENTITY,
1155
- anthropicDummyKey: env.anthropicDummyKey ?? ANTHROPIC_DUMMY_KEY,
1156
- // Derive the per-prompt max-duration cap from the session duration so a long
1157
- // run is not killed at the hard-coded 90-min prompt cap (ALI-1061). Unset →
1158
- // the harness default (5400s) floor holds.
1159
- sandboxBudgetMs: session.durationMinutes != null ? session.durationMinutes * 60 * 1000 : undefined,
1160
- prebaked: true,
1161
- egressCanaryHost: DEFAULT_EGRESS_CANARY_HOST,
1162
- runSetupHook: true,
1163
- opencodePort: configuredOpenCodePort,
1164
- };
1165
1183
  const runLoop = opts.runLoop ??
1166
1184
  ((input) => runHostedLoop({
1167
1185
  context: input.context,
@@ -1207,7 +1225,7 @@ const c = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
1207
1225
  projectId: session.projectId,
1208
1226
  sessionDir: sessionDirAbs,
1209
1227
  };
1210
- const hostedLoopSession = await startHostedLoopSession(sessionContext);
1228
+ const hostedLoopSession = await startHostedLoopSession(sessionContext, { prestart });
1211
1229
  let current = session.pendingTurn?.runId === runId
1212
1230
  ? { runId, ordinal: session.pendingTurn.ordinal, body: promptForPendingHostedTurn(session.pendingTurn) }
1213
1231
  : { runId, ordinal: 1, body: session.task };
@@ -195,6 +195,8 @@ export interface SpawnOpenCodeOptions extends OpenCodeConfigOptions {
195
195
  logPath?: string;
196
196
  }) => {
197
197
  kill: () => void;
198
+ hasExited?: () => boolean;
199
+ exitReason?: () => string | null;
198
200
  };
199
201
  /** File the spawner should append opencode's stdout/stderr to. On readiness
200
202
  * timeout its tail is included in the thrown error — the bare "fetch failed"
@@ -230,4 +232,33 @@ export interface SpawnedOpenCode {
230
232
  * (see the SSE-fragility note on that constant).
231
233
  */
232
234
  export declare function spawnOpenCode(opts: SpawnOpenCodeOptions): Promise<SpawnedOpenCode>;
235
+ /** A started `opencode serve` that has not been asked anything yet. */
236
+ export interface StartedOpenCode {
237
+ baseUrl: string;
238
+ port: number;
239
+ stop: () => void;
240
+ /** True once the process has exited (when the spawner can tell). */
241
+ hasExited: () => boolean;
242
+ /** Why it could not be started (for example ENOENT), when known. */
243
+ exitReason: () => string | null;
244
+ startedAt: number;
245
+ }
246
+ /** `waitOpenCodeReady` found the process already gone; nothing answered. */
247
+ export declare class OpenCodeExitedError extends Error {
248
+ }
249
+ /**
250
+ * The first half of `spawnOpenCode`: refresh the model catalog and start the
251
+ * process, without asking it anything. ORI-2512: the hosted boot calls this
252
+ * while the repository is still cloning. OpenCode ties its folder to a project
253
+ * on the first request it gets, so nothing may ask it anything until the
254
+ * folder holds the repository (measured on 1.18.35: a request into an empty
255
+ * folder binds it to the `global` project for good).
256
+ */
257
+ export declare function startOpenCodeProcess(opts: SpawnOpenCodeOptions): Promise<StartedOpenCode>;
258
+ /**
259
+ * The second half of `spawnOpenCode`: poll until the started server answers.
260
+ * The budget counts from this call, not from the start, so a long clone
261
+ * before it cannot use the budget up. `readyAfterMs` is from the start.
262
+ */
263
+ export declare function waitOpenCodeReady(started: StartedOpenCode, opts: Pick<SpawnOpenCodeOptions, 'readyTimeoutMs' | 'readyPollMs' | 'signal' | 'fetchImpl' | 'logPath'>): Promise<SpawnedOpenCode>;
233
264
  //# sourceMappingURL=hosted-harness-opencode.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hosted-harness-opencode.d.ts","sourceRoot":"","sources":["../src/hosted-harness-opencode.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hosted-harness-opencode.d.ts","sourceRoot":"","sources":["../src/hosted-harness-opencode.ts"],"names":[],"mappings":"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"}
@@ -1035,6 +1035,20 @@ function readLogTail(logPath) {
1035
1035
  * (see the SSE-fragility note on that constant).
1036
1036
  */
1037
1037
  export async function spawnOpenCode(opts) {
1038
+ return waitOpenCodeReady(await startOpenCodeProcess(opts), opts);
1039
+ }
1040
+ /** `waitOpenCodeReady` found the process already gone; nothing answered. */
1041
+ export class OpenCodeExitedError extends Error {
1042
+ }
1043
+ /**
1044
+ * The first half of `spawnOpenCode`: refresh the model catalog and start the
1045
+ * process, without asking it anything. ORI-2512: the hosted boot calls this
1046
+ * while the repository is still cloning. OpenCode ties its folder to a project
1047
+ * on the first request it gets, so nothing may ask it anything until the
1048
+ * folder holds the repository (measured on 1.18.35: a request into an empty
1049
+ * folder binds it to the `global` project for good).
1050
+ */
1051
+ export async function startOpenCodeProcess(opts) {
1038
1052
  const port = opts.port ?? DEFAULT_OPENCODE_PORT;
1039
1053
  const env = {
1040
1054
  OPENCODE_CONFIG_CONTENT: buildOpenCodeConfigContent(opts),
@@ -1059,14 +1073,29 @@ export async function spawnOpenCode(opts) {
1059
1073
  });
1060
1074
  });
1061
1075
  const child = spawnImpl(cmd, { cwd: opts.cwd, env, logPath: opts.logPath });
1062
- const baseUrl = `http://localhost:${port}`;
1076
+ return {
1077
+ baseUrl: `http://localhost:${port}`,
1078
+ port,
1079
+ stop: () => child.kill(),
1080
+ hasExited: () => child.hasExited?.() ?? false,
1081
+ exitReason: () => child.exitReason?.() ?? null,
1082
+ startedAt: Date.now(),
1083
+ };
1084
+ }
1085
+ /**
1086
+ * The second half of `spawnOpenCode`: poll until the started server answers.
1087
+ * The budget counts from this call, not from the start, so a long clone
1088
+ * before it cannot use the budget up. `readyAfterMs` is from the start.
1089
+ */
1090
+ export async function waitOpenCodeReady(started, opts) {
1091
+ const { baseUrl, port } = started;
1063
1092
  const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
1064
1093
  const timeoutMs = opts.readyTimeoutMs ?? OPENCODE_READY_TIMEOUT_MS;
1065
1094
  const pollMs = opts.readyPollMs ?? OPENCODE_READY_POLL_MS;
1066
- const startedAt = Date.now();
1095
+ const waitStartedAt = Date.now();
1067
1096
  for (;;) {
1068
1097
  if (opts.signal?.aborted) {
1069
- child.kill();
1098
+ started.stop();
1070
1099
  throw new Error('opencode serve readiness wait aborted');
1071
1100
  }
1072
1101
  try {
@@ -1086,17 +1115,26 @@ export async function spawnOpenCode(opts) {
1086
1115
  }
1087
1116
  }
1088
1117
  catch {
1089
- // not listening yet — fall through to the timeout check + sleep
1118
+ // not listening yet — fall through to the exit and timeout checks + sleep
1119
+ }
1120
+ // A process that already exited will never answer: say so now rather
1121
+ // than at the end of the budget.
1122
+ if (started.hasExited()) {
1123
+ const tail = readLogTail(opts.logPath);
1124
+ const reason = started.exitReason();
1125
+ throw new OpenCodeExitedError(`opencode serve exited before it answered on ${baseUrl}` +
1126
+ (reason ? `: ${reason}` : '') +
1127
+ (tail ? ` — log tail: ${tail}` : ' (no log output captured)'));
1090
1128
  }
1091
1129
  // Soft budget: checked between probes, so the total wait can overrun by up
1092
1130
  // to one probe timeout + poll interval. Negligible against the default.
1093
- if (Date.now() - startedAt >= timeoutMs) {
1094
- child.kill();
1131
+ if (Date.now() - waitStartedAt >= timeoutMs) {
1132
+ started.stop();
1095
1133
  const tail = readLogTail(opts.logPath);
1096
1134
  throw new Error(`opencode serve did not become ready on ${baseUrl} within ${timeoutMs}ms` +
1097
1135
  (tail ? ` — log tail: ${tail}` : ' (no log output captured)'));
1098
1136
  }
1099
1137
  await new Promise(resolve => setTimeout(resolve, pollMs));
1100
1138
  }
1101
- return { baseUrl, port, stop: () => child.kill(), readyAfterMs: Date.now() - startedAt };
1139
+ return { baseUrl, port, stop: started.stop, readyAfterMs: Date.now() - started.startedAt };
1102
1140
  }
@@ -36,7 +36,7 @@
36
36
  */
37
37
  import { type AgentHarness } from './hosted-harness.js';
38
38
  import { type HarvestExec } from './hosted-harvest.js';
39
- import { type ModelValidationOutcome, type SpawnOpenCodeOptions, type SpawnedOpenCode } from './hosted-harness-opencode.js';
39
+ import { type ModelValidationOutcome, type SpawnOpenCodeOptions, type SpawnedOpenCode, type StartedOpenCode } from './hosted-harness-opencode.js';
40
40
  import { type HostedFetch, type LiveTextSnapshot, type TerminalStatus } from './hosted-run-event-sink.js';
41
41
  import { type EgressProbeResult, type HostedLoopContext, type SetupHookOutcome } from './hosted-loop-lifecycle.js';
42
42
  export { DEFAULT_EGRESS_CANARY_HOST, DEFAULT_HOSTED_MODEL, buildOpenCodeSpawnEnv, egressCanaryAllowedHost, runEgressCanary, } from './hosted-loop-lifecycle.js';
@@ -61,6 +61,28 @@ interface HostedLoopSharedRuntime {
61
61
  startupComplete: boolean;
62
62
  spawnLogPath: string;
63
63
  closed: boolean;
64
+ /** ORI-2512: the boot's background start, until the first turn takes it. */
65
+ prestart?: HostedPrestart | null;
66
+ }
67
+ /**
68
+ * ORI-2512 cut 3: the egress canary's probes and the OpenCode start, begun by
69
+ * the boot while the repository clones. The first turn records the canary's
70
+ * events from the stored answers (same decision, same events) and waits for
71
+ * the started server, which is asked nothing before then.
72
+ */
73
+ export interface HostedPrestart {
74
+ /** The probes' answers by host, and the canary's verdict. Null when a
75
+ * probe threw: the turn then runs the canary itself. Never rejects. */
76
+ canary: Promise<{
77
+ results: Map<string, EgressProbeResult>;
78
+ checkedAt: number;
79
+ proceed: boolean;
80
+ } | null>;
81
+ /** The started process; null when the canary said no or the start was
82
+ * stopped. Rejects when the background start failed. */
83
+ opencode: Promise<StartedOpenCode | null>;
84
+ /** Stop the process, now or as soon as it starts. */
85
+ stop: () => void;
64
86
  }
65
87
  export interface HostedLoopSessionContext extends Omit<HostedLoopContext, 'runId' | 'bearerFile' | 'taskFile' | 'messageId'> {
66
88
  sessionId: string;
@@ -206,8 +228,23 @@ export { INJECTED_ENV_VARS_ENV } from './hosted-environment.js';
206
228
  * allows that one script and the result must print the pinned version
207
229
  * (ORI-2523). `env` exists for tests. */
208
230
  export declare function installOpenCodePinned(version: string, env?: NodeJS.ProcessEnv): InstallResult;
231
+ /**
232
+ * ORI-2512 cut 3: start the egress canary and then OpenCode in the
233
+ * background, for the boot to call before it clones. The order is the
234
+ * loop's: the canary first, fail-closed, before the models.dev refresh or
235
+ * OpenCode. Nothing here records events; the first turn does. Only for the
236
+ * pre-baked OpenCode runtime under an enforced egress policy; otherwise null
237
+ * and the turn starts everything in order.
238
+ */
239
+ export declare function prestartHostedOpenCode(context: HostedLoopContext, opts: {
240
+ logPath: string;
241
+ }): HostedPrestart | null;
209
242
  export declare function runHostedLoop(opts: RunHostedLoopOptions): Promise<HostedLoopResult>;
210
- export declare function startHostedLoopSession(context: HostedLoopSessionContext): Promise<HostedLoopSessionHandle>;
243
+ export declare function startHostedLoopSession(context: HostedLoopSessionContext, options?: {
244
+ prestart?: HostedPrestart | null;
245
+ }): Promise<HostedLoopSessionHandle>;
246
+ /** Where a hosted session's `opencode serve` writes its output. */
247
+ export declare function hostedSessionOpenCodeLogPath(context: Pick<HostedLoopSessionContext, 'sessionDir' | 'sessionId'>): string;
211
248
  export declare function runHostedLoopTurn(session: HostedLoopSessionHandle, turn: HostedLoopTurnContext, options: Omit<RunHostedLoopOptions, 'context' | 'taskPrompt' | 'sharedRuntime'> & {
212
249
  taskPrompt: string;
213
250
  }): Promise<HostedLoopResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"hosted-loop.d.ts","sourceRoot":"","sources":["../src/hosted-loop.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hosted-loop.d.ts","sourceRoot":"","sources":["../src/hosted-loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAOH,OAAO,EAAyB,KAAK,YAAY,EAAyC,MAAM,qBAAqB,CAAA;AAQrH,OAAO,EAAoB,KAAK,WAAW,EAAuB,MAAM,qBAAqB,CAAA;AAE7F,OAAO,EAUL,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,eAAe,EACrB,MAAM,8BAA8B,CAAA;AAIrC,OAAO,EAML,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACpB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAOL,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACtB,MAAM,4BAA4B,CAAA;AAKnC,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,EACvB,eAAe,GAChB,MAAM,4BAA4B,CAAA;AACnC,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,4BAA4B,CAAA;AAOnC,eAAO,MAAM,wBAAwB,QAAgB,CAAA;AAErD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAKrF;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;CACf;AAED,UAAU,uBAAuB;IAC/B,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;IAC5B,OAAO,EAAE,eAAe,GAAG,IAAI,CAAA;IAC/B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,SAAS,EAAE,OAAO,CAAA;IAClB,eAAe,EAAE,OAAO,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,OAAO,CAAA;IACf,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,cAAc,GAAG,IAAI,CAAA;CACjC;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B;4EACwE;IACxE,MAAM,EAAE,OAAO,CAAC;QAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;IACxG;6DACyD;IACzD,QAAQ,EAAE,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAA;IACzC,qDAAqD;IACrD,IAAI,EAAE,MAAM,IAAI,CAAA;CACjB;AAED,MAAM,WAAW,wBAAyB,SAAQ,IAAI,CAAC,iBAAiB,EAAE,OAAO,GAAG,YAAY,GAAG,UAAU,GAAG,WAAW,CAAC;IAC1H,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,wBAAwB,CAAA;IACjC,OAAO,EAAE,uBAAuB,CAAA;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,iBAAiB,CAAA;IAC1B,0EAA0E;IAC1E,aAAa,CAAC,EAAE,uBAAuB,CAAA;IACvC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;yEACqE;IACrE,cAAc,CAAC,EAAE,MAAM,MAAM,CAAA;IAC7B,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,WAAW,CAAA;IACvB;2FACuF;IACvF,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,YAAY,CAAA;IAC7F;gFAC4E;IAC5E,mBAAmB,CAAC,EAAE,MAAM,YAAY,CAAA;IACxC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;IAC1F,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,CAAA;IAC9C;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAA;IAC9F,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB;sDACkD;IAClD,eAAe,CAAC,EAAE,WAAW,CAAA;IAC7B,iFAAiF;IACjF,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACrC,wGAAwG;IACxG,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,OAAO,GAAG,IAAI,CAAA;IAC3D;;kFAE8E;IAC9E,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAC1D;;oDAEgD;IAChD,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAA;IAChG;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB;;;iCAG6B;IAC7B,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAChC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;+DAC2D;IAC3D,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACzC;iFAC6E;IAC7E,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACjC;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,cAAc,CAAA;IACtB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAeD;;;;;iDAKiD;AACjD,eAAO,MAAM,6BAA6B,QAAiB,CAAA;AAa3D;;;;;;;GAOG;AACH,eAAO,MAAM,iCAAiC,EAAE,SAAS,MAAM,EAAmB,CAAA;AAKlF;;;;;;GAMG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAiC/D;;;;;0CAK0C;AAC1C,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,aAAa,CAW7F;AA6DD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,iBAAiB,EAC1B,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GACxB,cAAc,GAAG,IAAI,CAqCvB;AAqBD,wBAAsB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAslBzF;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,wBAAwB,EACjC,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,IAAI,CAAA;CAAO,GACjD,OAAO,CAAC,uBAAuB,CAAC,CAclC;AAED,mEAAmE;AACnE,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,IAAI,CAAC,wBAAwB,EAAE,YAAY,GAAG,WAAW,CAAC,GAAG,MAAM,CAExH;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,uBAAuB,EAChC,IAAI,EAAE,qBAAqB,EAC3B,OAAO,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,GAAG,YAAY,GAAG,eAAe,CAAC,GAAG;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GACvG,OAAO,CAAC,gBAAgB,CAAC,CAe3B;AAED,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU5F;AAID,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AA0CD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,mBAAmB,GACtB,OAAO,CAAC,MAAM,CAAC,CA8BjB"}
@@ -42,7 +42,7 @@ import { isHarnessTerminalKind } from './hosted-harness.js';
42
42
  import { SETUP_HOOK_RELATIVE_PATH, composeHostedTaskPrompt, parsePrebakedMarker, resolveHostedTaskPreamble, resolvePrebakedMarkerPath, } from './hosted-runtime-assets.js';
43
43
  import { harvestWorkspace } from './hosted-harvest.js';
44
44
  import { annotateHeadlessQuestions, withIdleWatchdog } from './hosted-headless.js';
45
- import { DEFAULT_PROMPT_MAX_DURATION_MS, OPENCODE_PINNED_VERSION, awaitOpenCodeModelResolvable, createOpenCodeHarness, permissionForOrigin, spawnOpenCode, } from './hosted-harness-opencode.js';
45
+ import { DEFAULT_PROMPT_MAX_DURATION_MS, OPENCODE_PINNED_VERSION, OpenCodeExitedError, awaitOpenCodeModelResolvable, createOpenCodeHarness, permissionForOrigin, spawnOpenCode, startOpenCodeProcess, waitOpenCodeReady, } from './hosted-harness-opencode.js';
46
46
  import { createClaudeAgentHarness } from './hosted-harness-claude.js';
47
47
  import { INJECTED_ENV_VARS_ENV } from './hosted-environment.js';
48
48
  import { interceptHostedQuestions } from './hosted-question.js';
@@ -199,7 +199,78 @@ function nodeChildSpawner(cmd, opts) {
199
199
  // already closed
200
200
  }
201
201
  }
202
- return { kill: () => child.kill() };
202
+ // ORI-2512: lets the readiness wait stop at once for a process that has
203
+ // gone. The error listener also keeps a failed spawn from crashing the boot.
204
+ let exited = false;
205
+ let spawnError = null;
206
+ child.once('exit', () => { exited = true; });
207
+ child.once('error', error => {
208
+ exited = true;
209
+ spawnError = error instanceof Error ? error.message : String(error);
210
+ });
211
+ return { kill: () => child.kill(), hasExited: () => exited, exitReason: () => spawnError };
212
+ }
213
+ /** The one place the hosted `opencode serve` options come from, for both the
214
+ * boot's background start and the turn's start in order. */
215
+ function openCodeSpawnOptions(context, logPath) {
216
+ return {
217
+ model: context.model,
218
+ permission: permissionForOrigin(context.sessionOrigin),
219
+ projectId: context.projectId,
220
+ cwd: context.workspaceDir,
221
+ port: context.opencodePort,
222
+ env: buildOpenCodeSpawnEnv(context),
223
+ spawn: nodeChildSpawner,
224
+ logPath,
225
+ };
226
+ }
227
+ /**
228
+ * ORI-2512 cut 3: start the egress canary and then OpenCode in the
229
+ * background, for the boot to call before it clones. The order is the
230
+ * loop's: the canary first, fail-closed, before the models.dev refresh or
231
+ * OpenCode. Nothing here records events; the first turn does. Only for the
232
+ * pre-baked OpenCode runtime under an enforced egress policy; otherwise null
233
+ * and the turn starts everything in order.
234
+ */
235
+ export function prestartHostedOpenCode(context, opts) {
236
+ if ((context.harness ?? 'opencode') !== 'opencode' || !context.egressCanaryHost || context.prebaked !== true)
237
+ return null;
238
+ const targets = {
239
+ allowedHost: egressCanaryAllowedHost(context.apiBaseUrl),
240
+ deniedHost: context.egressCanaryHost,
241
+ };
242
+ const results = new Map();
243
+ let stopped = false;
244
+ const canary = (async () => {
245
+ let checkedAt = Date.now();
246
+ // The loop's own decision, with nothing recorded yet.
247
+ const decision = await runEgressCanary(targets, { append: async () => { } }, async (host) => {
248
+ const result = await defaultProbeEgress(host);
249
+ results.set(host, result);
250
+ return result;
251
+ }, () => (checkedAt = Date.now()));
252
+ return { results, checkedAt, proceed: decision.proceed };
253
+ })().catch(() => null);
254
+ const opencode = canary.then(async (stored) => {
255
+ if (!stored?.proceed || stopped)
256
+ return null;
257
+ const started = await startOpenCodeProcess(openCodeSpawnOptions(context, opts.logPath));
258
+ if (stopped) {
259
+ started.stop();
260
+ return null;
261
+ }
262
+ return started;
263
+ });
264
+ // The turn reads a failure; until then it must not count as unhandled.
265
+ opencode.catch(() => { });
266
+ return {
267
+ canary,
268
+ opencode,
269
+ stop: () => {
270
+ stopped = true;
271
+ void opencode.then(started => started?.stop(), () => { });
272
+ },
273
+ };
203
274
  }
204
275
  /** ORI-2512: the longest the end-of-turn save waits for the boot's
205
276
  * background fill of the session copy before saving anyway. */
@@ -260,15 +331,22 @@ export async function runHostedLoop(opts) {
260
331
  // unreachable → network broken / indistinguishable from a block) emits
261
332
  // `egress_allowed` and finishes the run FAILED rather than touch real customer
262
333
  // data. A canary whose own event append fails is likewise fail-closed.
334
+ // ORI-2512: on the first turn, the boot may already have probed in the
335
+ // background; the same decision is then made from its stored answers.
336
+ const prestart = opts.sharedRuntime && !opts.sharedRuntime.startupComplete ? opts.sharedRuntime.prestart : null;
263
337
  if (context.egressCanaryHost && !opts.sharedRuntime?.startupComplete) {
264
- const probe = opts.probeEgress ?? defaultProbeEgress;
338
+ const liveProbe = opts.probeEgress ?? defaultProbeEgress;
339
+ const stored = prestart ? await prestart.canary : null;
340
+ const probe = stored
341
+ ? async (host) => stored.results.get(host) ?? liveProbe(host)
342
+ : liveProbe;
265
343
  const targets = {
266
344
  allowedHost: egressCanaryAllowedHost(context.apiBaseUrl),
267
345
  deniedHost: context.egressCanaryHost,
268
346
  };
269
347
  let canary;
270
348
  try {
271
- canary = await runEgressCanary(targets, sink, probe, opts.now);
349
+ canary = await runEgressCanary(targets, sink, probe, stored ? () => stored.checkedAt : opts.now);
272
350
  }
273
351
  catch (error) {
274
352
  // The canary's own event append failed — treat as fail-closed: we cannot
@@ -490,26 +568,52 @@ export async function runHostedLoop(opts) {
490
568
  payload: { step: 'opencode_install', ok: install.ok, detail: install.detail },
491
569
  });
492
570
  }
571
+ const logPath = opts.sharedRuntime?.spawnLogPath ?? join(tmpdir(), `opencode-serve-${context.runId}.log`);
572
+ // ORI-2512: the first turn takes the server the boot started during the
573
+ // clone and asks it something only now, after the clone. A background
574
+ // start that failed, or a process that already exited, is started again
575
+ // here in order.
576
+ const waitStartedAt = Date.now();
577
+ let prestarted = null;
578
+ if (prestart && opts.sharedRuntime) {
579
+ opts.sharedRuntime.prestart = null;
580
+ let started = null;
581
+ try {
582
+ started = await prestart.opencode;
583
+ }
584
+ catch {
585
+ diagnose('hosted OpenCode background start failed; starting it in order');
586
+ }
587
+ if (started) {
588
+ try {
589
+ prestarted = await waitOpenCodeReady(started, { logPath, signal });
590
+ }
591
+ catch (error) {
592
+ if (!(error instanceof OpenCodeExitedError))
593
+ throw error;
594
+ diagnose('hosted OpenCode background start exited; starting it in order');
595
+ }
596
+ }
597
+ }
493
598
  const spawnImpl = opts.spawnOpenCode ?? spawnOpenCode;
494
599
  // The default spawnOpenCode WAITS for the server to answer HTTP before
495
600
  // returning — OpenCode's first boot runs a one-time sqlite migration, and
496
601
  // starting the harness before the server listens raced straight into a
497
602
  // connection-refused "fetch failed" (ALI-1034).
498
- spawned = await spawnImpl({
499
- model: context.model,
500
- permission: permissionForOrigin(context.sessionOrigin),
501
- projectId: context.projectId,
502
- cwd: context.workspaceDir,
503
- port: context.opencodePort,
504
- env: buildOpenCodeSpawnEnv(context),
505
- spawn: nodeChildSpawner,
506
- logPath: opts.sharedRuntime?.spawnLogPath ?? join(tmpdir(), `opencode-serve-${context.runId}.log`),
507
- signal,
508
- });
603
+ spawned = prestarted ?? await spawnImpl({ ...openCodeSpawnOptions(context, logPath), signal });
509
604
  if (typeof spawned.readyAfterMs === 'number') {
510
605
  await sink.append({
511
606
  kind: 'artifact',
512
- payload: { step: 'opencode_ready', ok: true, waitedMs: spawned.readyAfterMs },
607
+ payload: {
608
+ step: 'opencode_ready',
609
+ ok: true,
610
+ // How long this turn waited for OpenCode. When the boot started it
611
+ // during the clone, that is the wait after the clone; the time
612
+ // since it was started (clone included) is kept apart.
613
+ ...(prestarted
614
+ ? { waitedMs: Date.now() - waitStartedAt, prestarted: true, sinceStartMs: spawned.readyAfterMs }
615
+ : { waitedMs: spawned.readyAfterMs }),
616
+ },
513
617
  });
514
618
  }
515
619
  // Fail-fast model validation (ALI-1086): ask the RUNNING opencode whether
@@ -804,7 +908,7 @@ export async function runHostedLoop(opts) {
804
908
  }
805
909
  }
806
910
  }
807
- export async function startHostedLoopSession(context) {
911
+ export async function startHostedLoopSession(context, options = {}) {
808
912
  return {
809
913
  context,
810
914
  runtime: {
@@ -813,11 +917,16 @@ export async function startHostedLoopSession(context) {
813
917
  agentSessionId: null,
814
918
  installOk: false,
815
919
  startupComplete: false,
816
- spawnLogPath: join(context.sessionDir, `opencode-serve-${context.sessionId}.log`),
920
+ spawnLogPath: hostedSessionOpenCodeLogPath(context),
817
921
  closed: false,
922
+ prestart: options.prestart ?? null,
818
923
  },
819
924
  };
820
925
  }
926
+ /** Where a hosted session's `opencode serve` writes its output. */
927
+ export function hostedSessionOpenCodeLogPath(context) {
928
+ return join(context.sessionDir, `opencode-serve-${context.sessionId}.log`);
929
+ }
821
930
  export async function runHostedLoopTurn(session, turn, options) {
822
931
  if (session.runtime.closed)
823
932
  throw new Error('hosted loop session is closed');
@@ -1 +1 @@
1
- {"version":3,"file":"hosted-optimization.d.ts","sourceRoot":"","sources":["../src/hosted-optimization.ts"],"names":[],"mappings":"AAeA,UAAU,oBAAoB;IAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE;AAsZrF,wBAAsB,yBAAyB,CAAC,EAAE,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CA2FzF"}
1
+ {"version":3,"file":"hosted-optimization.d.ts","sourceRoot":"","sources":["../src/hosted-optimization.ts"],"names":[],"mappings":"AAcA,UAAU,oBAAoB;IAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE;AAgZrF,wBAAsB,yBAAyB,CAAC,EAAE,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoHzF"}