@turns/dev 0.0.12 → 0.0.14

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.
@@ -595,6 +595,7 @@ class CodexAppServerTransport extends LineTransport {
595
595
  : 'workspace-write',
596
596
  approvalPolicy: this.anchor.permissionMode === 'bypassPermissions' ? 'never' : 'on-request',
597
597
  };
598
+ const resumePath = !createSession && fs.existsSync(this.anchor.transcriptPath) ? this.anchor.transcriptPath : '';
598
599
  const opened = createSession
599
600
  ? await this.request('thread/start', {
600
601
  ...configuration,
@@ -606,6 +607,13 @@ class CodexAppServerTransport extends LineTransport {
606
607
  }, 90_000)
607
608
  : await this.request('thread/resume', {
608
609
  threadId: this.threadId,
610
+ // Codex's thread registry may not include a still-existing rollout
611
+ // after an upgrade. Supplying the descriptor path lets Codex resume
612
+ // the exact on-disk history instead of rejecting every later
613
+ // queued turn as "thread not found". Do not send Turns' temporary
614
+ // placeholder for a newly bound session: a non-empty path takes
615
+ // precedence over a valid thread id in the app-server protocol.
616
+ ...(resumePath ? { path: resumePath } : {}),
609
617
  ...configuration,
610
618
  ...(this.anchor.model && this.anchor.model !== 'auto' ? { model: this.anchor.model } : {}),
611
619
  ...(this.anchor.thinkingLevel
@@ -1,7 +1,7 @@
1
1
  /** Machine-local Codex CLI self-update, callable by the Turns daemon. */
2
2
  import { spawn } from 'node:child_process';
3
3
  import os from 'node:os';
4
- import { resolveHarnessCommand } from "./harness-registry.js";
4
+ import { codexCommandIsAppManaged, resolveHarnessCommand } from "./harness-registry.js";
5
5
  const VERSION_TIMEOUT_MS = 15_000;
6
6
  const UPDATE_TIMEOUT_MS = 5 * 60_000;
7
7
  const OUTPUT_LIMIT = 64 * 1024;
@@ -58,20 +58,28 @@ function commandFailure(result, fallback) {
58
58
  }
59
59
  export function createCodexUpdateManager(dependencies = {}) {
60
60
  const resolveCommand = dependencies.resolveCommand || (() => resolveHarnessCommand('codex'));
61
+ const isAppManaged = dependencies.isAppManaged || codexCommandIsAppManaged;
61
62
  const run = dependencies.run || runCommand;
62
63
  let active = null;
64
+ const updateCommand = () => {
65
+ const command = resolveCommand();
66
+ if (isAppManaged(command)) {
67
+ throw new Error('Codex is managed by the macOS app. Update the Codex app to update its bundled CLI.');
68
+ }
69
+ return command;
70
+ };
63
71
  const perform = async () => {
64
- const beforeRun = await run(resolveCommand(), ['--version'], VERSION_TIMEOUT_MS);
72
+ const beforeRun = await run(updateCommand(), ['--version'], VERSION_TIMEOUT_MS);
65
73
  if (beforeRun.failed || beforeRun.timedOut || beforeRun.exitCode !== 0) {
66
74
  throw commandFailure(beforeRun, 'Codex is not installed or its version could not be read.');
67
75
  }
68
76
  const previousVersion = parseCodexVersion(beforeRun.output);
69
- const updateRun = await run(resolveCommand(), ['update'], UPDATE_TIMEOUT_MS);
77
+ const updateRun = await run(updateCommand(), ['update'], UPDATE_TIMEOUT_MS);
70
78
  if (updateRun.failed || updateRun.timedOut || updateRun.exitCode !== 0) {
71
79
  throw commandFailure(updateRun, 'Codex could not update itself. Run `codex update` in a terminal on the machine.');
72
80
  }
73
81
  // Resolve again because the updater may replace a shim or install path.
74
- const afterRun = await run(resolveCommand(), ['--version'], VERSION_TIMEOUT_MS);
82
+ const afterRun = await run(updateCommand(), ['--version'], VERSION_TIMEOUT_MS);
75
83
  if (afterRun.failed || afterRun.timedOut || afterRun.exitCode !== 0) {
76
84
  throw commandFailure(afterRun, 'Codex updated, but its new version could not be read.');
77
85
  }
@@ -83,6 +91,15 @@ export function createCodexUpdateManager(dependencies = {}) {
83
91
  };
84
92
  };
85
93
  return {
94
+ /** App-bundled Codex is updated with the app, not by its CLI command. */
95
+ available() {
96
+ try {
97
+ return !isAppManaged(resolveCommand());
98
+ }
99
+ catch {
100
+ return false;
101
+ }
102
+ },
86
103
  /** Concurrent clicks share one installer process instead of racing writes. */
87
104
  update() {
88
105
  if (!active)
@@ -269,30 +269,55 @@ function executable(candidate) {
269
269
  return '';
270
270
  }
271
271
  }
272
- export function resolveHarnessCommand(harness, profile = {}) {
272
+ export const MACOS_BUNDLED_CODEX_PATH = '/Applications/Codex.app/Contents/Resources/codex';
273
+ /** The app owns this executable and updates it as part of the app bundle. */
274
+ export function codexCommandIsAppManaged(command) {
275
+ let candidate = path.resolve(command);
276
+ try {
277
+ candidate = fs.realpathSync(candidate);
278
+ }
279
+ catch { }
280
+ return /\/Codex\.app\/Contents\/Resources\/codex$/u.test(candidate.replaceAll('\\', '/'));
281
+ }
282
+ export function resolveHarnessCommand(harness, profile = {}, dependencies = {}) {
283
+ const env = dependencies.env || process.env;
284
+ const home = dependencies.home || HOME;
273
285
  const envName = `TURNS_${String(harness).toUpperCase().replaceAll('-', '_')}_BIN`;
274
- const configured = String(process.env[envName] || '').trim();
286
+ const configured = String(env[envName] || '').trim();
275
287
  if (configured) {
276
288
  const found = executable(path.resolve(configured));
277
289
  if (found)
278
290
  return found;
279
291
  throw new Error(`${envName} is not executable: ${configured}`);
280
292
  }
293
+ // The desktop app and its bundled CLI are released together. On macOS,
294
+ // prefer that known-compatible binary over independently installed npm or
295
+ // Homebrew copies while preserving TURNS_CODEX_BIN as the explicit escape
296
+ // hatch above.
297
+ if (harness === 'codex' && (dependencies.platform || process.platform) === 'darwin') {
298
+ const candidates = dependencies.bundledCodexPaths ||
299
+ [MACOS_BUNDLED_CODEX_PATH, path.join(home, 'Applications', 'Codex.app', 'Contents', 'Resources', 'codex')];
300
+ for (const candidate of candidates) {
301
+ const found = executable(candidate);
302
+ if (found)
303
+ return found;
304
+ }
305
+ }
281
306
  const command = profile.command || ({ cursor: 'cursor-agent', antigravity: 'agy' }[harness] ?? harness);
282
- for (const directory of String(process.env.PATH || '').split(path.delimiter)) {
307
+ for (const directory of String(env.PATH || '').split(path.delimiter)) {
283
308
  const found = executable(path.join(directory, command));
284
309
  if (found)
285
310
  return found;
286
311
  }
287
312
  for (const segments of profile.homePaths || []) {
288
- const found = executable(path.join(HOME, ...segments));
313
+ const found = executable(path.join(home, ...segments));
289
314
  if (found)
290
315
  return found;
291
316
  }
292
317
  if (harness === 'cursor') {
293
318
  for (const candidate of [
294
- path.join(HOME, '.local', 'bin', 'cursor-agent'),
295
- path.join(HOME, '.local', 'bin', 'agent'),
319
+ path.join(home, '.local', 'bin', 'cursor-agent'),
320
+ path.join(home, '.local', 'bin', 'agent'),
296
321
  ]) {
297
322
  const found = executable(candidate);
298
323
  if (found)
@@ -1372,7 +1372,13 @@ export async function watchQueuedPrompts({ rpc, options, machine, getDescriptors
1372
1372
  type: 'machine.register',
1373
1373
  machineId: machine,
1374
1374
  machineHostname: os.hostname(),
1375
- capabilities: { fileSearch: true, git: true, fileRanges: true, harnessAuth: true, codexUpdate: true },
1375
+ capabilities: {
1376
+ fileSearch: true,
1377
+ git: true,
1378
+ fileRanges: true,
1379
+ harnessAuth: true,
1380
+ ...(codexUpdate.available() ? { codexUpdate: true } : {}),
1381
+ },
1376
1382
  workerId,
1377
1383
  workerInstanceId,
1378
1384
  anchorSessionIds: [...anchors.keys()],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turns/dev",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "description": "Upload local AI agent sessions to turns.dev",
5
5
  "license": "MIT",
6
6
  "type": "module",