@rallycry/conveyor-agent 10.13.13 → 10.13.15

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.
@@ -52,4 +52,4 @@ export {
52
52
  DEFAULT_WORKBENCH_PORT,
53
53
  timingSafeTokenEqual
54
54
  };
55
- //# sourceMappingURL=chunk-IE7EOKMD.js.map
55
+ //# sourceMappingURL=chunk-VKZ5W2VO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/workbench/protocol.ts"],"sourcesContent":["/**\n * Wire protocol for the workbench launcher — the loopback control channel\n * between the protected agent container and the restartable workbench\n * container (see docs/superpowers/specs/2026-07-16-workbench-container-split-design.md).\n *\n * Newline-delimited JSON frames over a plain TCP socket. Every connection\n * carries exactly ONE operation: the first client frame is a WorkbenchRequest\n * (with the shared token); everything after is a WorkbenchFrame in either\n * direction. Binary payloads (pty bytes, file contents) ride base64 `d`\n * fields — intra-pod loopback makes the 33% inflation cheap,\n * and NDJSON keeps the framing trivially robust across partial reads.\n */\n\nimport crypto from \"node:crypto\";\nimport type { Socket } from \"node:net\";\n\n/** First frame of every connection (client → server). */\nexport type WorkbenchRequest =\n | { op: \"ping\"; token: string }\n | {\n op: \"exec\";\n token: string;\n /** Shell form — spawned as `sh -c command` (setup/start commands). */\n command?: string;\n /** Argv form — spawned directly, no shell (git and friends). */\n argv?: string[];\n cwd: string;\n env?: Record<string, string>;\n }\n | {\n op: \"pty\";\n token: string;\n file: string;\n args: string[];\n cwd: string;\n env: Record<string, string>;\n cols: number;\n rows: number;\n }\n | { op: \"readFile\"; token: string; path: string }\n | { op: \"stat\"; token: string; path: string }\n | { op: \"readdir\"; token: string; path: string };\n\n/** Mid-stream frames (either direction after the opening request). */\nexport type WorkbenchFrame =\n | { t: \"out\"; s: \"stdout\" | \"stderr\"; d: string }\n | { t: \"exit\"; code: number | null; signal: string | null }\n | { t: \"data\"; d: string }\n | { t: \"end\" }\n | { t: \"input\"; d: string }\n | { t: \"resize\"; cols: number; rows: number }\n | { t: \"kill\"; sig?: string }\n | { t: \"signal\"; mode: \"term-group\" }\n | {\n t: \"stat\";\n exists: boolean;\n isFile: boolean;\n isDirectory: boolean;\n size: number;\n mtimeMs: number;\n }\n | { t: \"entries\"; names: string[] }\n | { t: \"pong\"; version: string }\n | { t: \"error\"; message: string; code?: string };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/** Parse one NDJSON line into a frame-shaped record, or null when malformed. */\nexport function parseLine(line: string): Record<string, unknown> | null {\n if (!line) return null;\n try {\n const parsed: unknown = JSON.parse(line);\n return isRecord(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Incremental NDJSON reader. Feed raw socket chunks; complete lines are\n * parsed and handed to the callback (malformed lines are dropped — the same\n * contract as the hook socket's envelope parsing).\n */\nexport class FrameReader {\n private buffer = \"\";\n\n constructor(private readonly onFrame: (frame: Record<string, unknown>) => void) {}\n\n push(chunk: Buffer | string): void {\n this.buffer += chunk.toString();\n let index = this.buffer.indexOf(\"\\n\");\n while (index >= 0) {\n const line = this.buffer.slice(0, index);\n this.buffer = this.buffer.slice(index + 1);\n const frame = parseLine(line);\n if (frame) this.onFrame(frame);\n index = this.buffer.indexOf(\"\\n\");\n }\n }\n}\n\n/** Serialize + write one frame. Write errors are the socket's problem —\n * callers handle teardown via the socket's own error/close events. */\nexport function writeFrame(\n socket: Pick<Socket, \"write\">,\n frame: WorkbenchRequest | WorkbenchFrame,\n): void {\n try {\n socket.write(`${JSON.stringify(frame)}\\n`);\n } catch {\n /* socket already destroyed — close handling owns cleanup */\n }\n}\n\nexport const DEFAULT_WORKBENCH_PORT = 7411;\n\n/** Matches the timingSafeEqualStr helper used for every other shared-secret\n * comparison in the codebase (preview-resolve.ts, workspace-attach-token.ts,\n * card-image-link.ts) — constant-time so the loopback auth check can't leak\n * the token via response-time. */\nexport function timingSafeTokenEqual(a: string, b: string): boolean {\n const ab = Buffer.from(a);\n const bb = Buffer.from(b);\n if (ab.length !== bb.length) return false;\n return crypto.timingSafeEqual(ab, bb);\n}\n"],"mappings":";AAaA,OAAO,YAAY;AAoDnB,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU;AAChD;AAGO,SAAS,UAAU,MAA8C;AACtE,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACF,UAAM,SAAkB,KAAK,MAAM,IAAI;AACvC,WAAO,SAAS,MAAM,IAAI,SAAS;AAAA,EACrC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOO,IAAM,cAAN,MAAkB;AAAA,EAGvB,YAA6B,SAAmD;AAAnD;AAAA,EAAoD;AAAA,EAApD;AAAA,EAFrB,SAAS;AAAA,EAIjB,KAAK,OAA8B;AACjC,SAAK,UAAU,MAAM,SAAS;AAC9B,QAAI,QAAQ,KAAK,OAAO,QAAQ,IAAI;AACpC,WAAO,SAAS,GAAG;AACjB,YAAM,OAAO,KAAK,OAAO,MAAM,GAAG,KAAK;AACvC,WAAK,SAAS,KAAK,OAAO,MAAM,QAAQ,CAAC;AACzC,YAAM,QAAQ,UAAU,IAAI;AAC5B,UAAI,MAAO,MAAK,QAAQ,KAAK;AAC7B,cAAQ,KAAK,OAAO,QAAQ,IAAI;AAAA,IAClC;AAAA,EACF;AACF;AAIO,SAAS,WACd,QACA,OACM;AACN,MAAI;AACF,WAAO,MAAM,GAAG,KAAK,UAAU,KAAK,CAAC;AAAA,CAAI;AAAA,EAC3C,QAAQ;AAAA,EAER;AACF;AAEO,IAAM,yBAAyB;AAM/B,SAAS,qBAAqB,GAAW,GAAoB;AAClE,QAAM,KAAK,OAAO,KAAK,CAAC;AACxB,QAAM,KAAK,OAAO,KAAK,CAAC;AACxB,MAAI,GAAG,WAAW,GAAG,OAAQ,QAAO;AACpC,SAAO,OAAO,gBAAgB,IAAI,EAAE;AACtC;","names":[]}
package/dist/cli.js CHANGED
@@ -28,11 +28,11 @@ import {
28
28
  runUsageProbe,
29
29
  sampleKeyUsage,
30
30
  textResult
31
- } from "./chunk-TB5SQIGX.js";
31
+ } from "./chunk-SAHOFQBQ.js";
32
32
  import "./chunk-7TQO4ZF4.js";
33
33
  import {
34
34
  getWorkbenchClient
35
- } from "./chunk-YRC57EYG.js";
35
+ } from "./chunk-LZM5OKAC.js";
36
36
  import {
37
37
  workbenchEnabled
38
38
  } from "./chunk-4VUQ2NPF.js";
@@ -40,21 +40,18 @@ import {
40
40
  buildPromptBytes,
41
41
  inheritedEnv,
42
42
  loadPtySpawn,
43
- runSetupCommand,
44
43
  runStartCommand,
45
44
  terminateProcessGroup
46
- } from "./chunk-2K6RRTQC.js";
47
- import "./chunk-IE7EOKMD.js";
45
+ } from "./chunk-RSIW2UCR.js";
46
+ import "./chunk-VKZ5W2VO.js";
48
47
 
49
48
  // src/cli.ts
50
49
  import { readFileSync } from "fs";
51
- import { join as join3, dirname as dirname3 } from "path";
50
+ import { join as join3, dirname as dirname2 } from "path";
52
51
  import { fileURLToPath } from "url";
53
52
 
54
53
  // src/setup/sidecars.ts
55
54
  import net from "net";
56
- import { mkdir, writeFile } from "fs/promises";
57
- import { dirname } from "path";
58
55
  var POSTGRES_TIMEOUT_MS = 12e4;
59
56
  var FIREBASE_TIMEOUT_MS = 6e4;
60
57
  var FALLBACK_TIMEOUT_MS = 3e4;
@@ -62,22 +59,6 @@ var DEFAULT_SIDECAR_POLL_INTERVAL_MS = 1e3;
62
59
  var DEFAULT_PROBE_TIMEOUT_MS = 2e3;
63
60
  var POSTGRES_DEFAULT_PORT = 5432;
64
61
  var FIREBASE_DEFAULT_PORT = 9099;
65
- async function startLazySidecars(env, onLog, signal) {
66
- throwIfAborted(signal);
67
- const markerPath = env.CONVEYOR_SIDECAR_START_FILE;
68
- if (!markerPath) return;
69
- try {
70
- await mkdir(dirname(markerPath), { recursive: true });
71
- throwIfAborted(signal);
72
- await writeFile(markerPath, "start\n", "utf8");
73
- throwIfAborted(signal);
74
- onLog("Started lazy sidecars");
75
- } catch (err) {
76
- if (signal?.aborted) throw abortError();
77
- const message = err instanceof Error ? err.message : String(err);
78
- onLog(`WARNING: failed to start lazy sidecars: ${message}`);
79
- }
80
- }
81
62
  function parseHostPort(value, defaultPort) {
82
63
  const trimmed = value.trim().replace(/^[a-z]+:\/\//i, "");
83
64
  if (!trimmed) return null;
@@ -204,12 +185,8 @@ async function waitForSidecars(opts = {}) {
204
185
  timeoutMs,
205
186
  pollIntervalMs = DEFAULT_SIDECAR_POLL_INTERVAL_MS,
206
187
  probe = defaultProbe,
207
- startLazy = true,
208
188
  signal
209
189
  } = opts;
210
- if (startLazy) {
211
- await startLazySidecars(env, onLog, signal);
212
- }
213
190
  throwIfAborted(signal);
214
191
  const targets = resolveSidecarTargets(env);
215
192
  if (targets.length === 0) return;
@@ -253,14 +230,10 @@ function defaultCommandExecutors() {
253
230
  if (workbenchEnabled()) {
254
231
  const client = getWorkbenchClient();
255
232
  return {
256
- runSetupCommand: (command, cwd, onOutput, signal) => client.runSetupCommand(command, cwd, onOutput, signal),
257
233
  runStartCommand: (cmd, cwd, onOutput) => client.runStartCommand(cmd, cwd, onOutput)
258
234
  };
259
235
  }
260
- return {
261
- runSetupCommand: (command, cwd, onOutput, signal) => runSetupCommand(command, cwd, onOutput, signal),
262
- runStartCommand
263
- };
236
+ return { runStartCommand };
264
237
  }
265
238
  var defaultWriteOutput = (stream, data) => {
266
239
  (stream === "stderr" ? process.stderr : process.stdout).write(data);
@@ -283,9 +256,7 @@ var WorkspaceCommandSupervisor = class {
283
256
  connection;
284
257
  env;
285
258
  awaitGitReadyFn;
286
- startLazySidecarsFn;
287
259
  waitForSidecarsFn;
288
- runSetupCommandFn;
289
260
  runStartCommandFn;
290
261
  loadForwardPortsFn;
291
262
  writeOutput;
@@ -302,6 +273,14 @@ var WorkspaceCommandSupervisor = class {
302
273
  startCommandLaunchRequested = false;
303
274
  started = false;
304
275
  stopped = false;
276
+ /** Resolved by notifyLoopReady() once the runner's core loop (the PTY) is
277
+ * live, or by stop() if shutdown arrives first — either way unblocks the
278
+ * awaiter in runSetupAndStart so a supervisor stopped before the loop ever
279
+ * signals doesn't hang its background task (and therefore stop()) forever. */
280
+ resolveLoopReady;
281
+ loopReady = new Promise((resolve) => {
282
+ this.resolveLoopReady = resolve;
283
+ });
305
284
  constructor(options) {
306
285
  this.config = options.config;
307
286
  this.workspaceDir = options.workspaceDir;
@@ -311,10 +290,8 @@ var WorkspaceCommandSupervisor = class {
311
290
  onLog: opts.onLog,
312
291
  signal: opts.signal
313
292
  }));
314
- this.startLazySidecarsFn = options.startLazySidecars ?? ((env, onLog, signal) => startLazySidecars(env, onLog, signal));
315
- this.waitForSidecarsFn = options.waitForSidecars ?? ((opts) => waitForSidecars({ onLog: opts.onLog, startLazy: opts.startLazy, signal: opts.signal }));
293
+ this.waitForSidecarsFn = options.waitForSidecars ?? ((opts) => waitForSidecars({ onLog: opts.onLog, signal: opts.signal }));
316
294
  const executors = defaultCommandExecutors();
317
- this.runSetupCommandFn = options.runSetupCommand ?? executors.runSetupCommand;
318
295
  this.runStartCommandFn = options.runStartCommand ?? executors.runStartCommand;
319
296
  this.loadForwardPortsFn = options.loadForwardPorts ?? loadForwardPorts;
320
297
  this.writeOutput = options.writeOutput ?? defaultWriteOutput;
@@ -327,23 +304,23 @@ var WorkspaceCommandSupervisor = class {
327
304
  if (this.started || this.stopped) return;
328
305
  this.started = true;
329
306
  this.connection.onRunStartCommand(() => this.restartStartCommand());
330
- this.trackBackgroundTask(
331
- this.startLazySidecarsFn(
332
- this.env,
333
- (message) => this.forwardSetupOutput("stdout", `[sidecars] ${message}
334
- `),
335
- this.abortController.signal
336
- ).catch((error) => this.reportUnexpectedError(error))
337
- );
338
307
  if (this.config) {
339
308
  this.trackBackgroundTask(
340
309
  this.runSetupAndStart().catch((error) => this.reportUnexpectedError(error))
341
310
  );
342
311
  }
343
312
  }
313
+ /** Release the start-command launch — called once the runner's core loop
314
+ * (the PTY) is live. Idempotent; resolving an already-settled promise is a
315
+ * no-op, so a late/duplicate call (or one after stop() already resolved
316
+ * it) is harmless. */
317
+ notifyLoopReady() {
318
+ this.resolveLoopReady();
319
+ }
344
320
  stop() {
345
321
  if (this.shutdownPromise) return this.shutdownPromise;
346
322
  this.stopped = true;
323
+ this.resolveLoopReady();
347
324
  this.abortController.abort();
348
325
  const backgroundTasks = [...this.backgroundTasks];
349
326
  const termination = this.terminateAllStartCommands();
@@ -371,16 +348,15 @@ var WorkspaceCommandSupervisor = class {
371
348
  await this.waitForSidecarsFn({
372
349
  onLog: (message) => this.forwardSetupOutput("stdout", `[sidecars] ${message}
373
350
  `),
374
- startLazy: false,
375
351
  signal: this.abortController.signal
376
352
  });
377
353
  if (this.stopped) return;
378
354
  this.reportBootMilestoneFn("sidecars_ready");
379
- await this.runConfiguredSetup();
355
+ await this.loopReady;
380
356
  if (this.stopped) return;
381
- this.reportBootMilestoneFn("setup_complete");
382
357
  const startCommandRunning = this.config?.startCommand ? await this.ensureStartCommandLaunched(this.config.startCommand) : false;
383
358
  if (this.stopped) return;
359
+ this.reportBootMilestoneFn("start_command_launched");
384
360
  const forwardPorts = await this.loadForwardPortsFn(this.workspaceDir);
385
361
  if (this.stopped) return;
386
362
  const previewPorts = buildSessionPreviewPorts(forwardPorts);
@@ -390,24 +366,6 @@ var WorkspaceCommandSupervisor = class {
390
366
  ...previewPorts.length > 0 ? { previewPorts } : {}
391
367
  });
392
368
  }
393
- async runConfiguredSetup() {
394
- const command = this.config?.setupCommand;
395
- if (!command) return;
396
- try {
397
- await this.runSetupCommandFn(
398
- command,
399
- this.workspaceDir,
400
- (stream, data) => this.forwardSetupOutput(stream, data),
401
- this.abortController.signal
402
- );
403
- } catch (error) {
404
- if (this.stopped) return;
405
- this.connection.sendEvent({
406
- type: "setup_error",
407
- message: error instanceof Error ? error.message : "Setup command failed"
408
- });
409
- }
410
- }
411
369
  restartStartCommand() {
412
370
  if (this.stopped || !this.config?.startCommand) return;
413
371
  this.connection.sendEvent({
@@ -729,7 +687,7 @@ var ProjectSessionRunner = class {
729
687
  };
730
688
 
731
689
  // src/usage/multi-key-probe.ts
732
- import { mkdtemp, writeFile as writeFile2, copyFile, rm } from "fs/promises";
690
+ import { mkdtemp, writeFile, copyFile, rm } from "fs/promises";
733
691
  import { tmpdir } from "os";
734
692
  import { join } from "path";
735
693
  var logger = createServiceLogger("multi-key-probe");
@@ -760,7 +718,7 @@ async function isolatedProbe(token, now) {
760
718
  let dir = null;
761
719
  try {
762
720
  dir = await mkdtemp(join(tmpdir(), "conveyor-usage-"));
763
- await writeFile2(join(dir, ".credentials.json"), buildSynthesizedCredentials(token, now), {
721
+ await writeFile(join(dir, ".credentials.json"), buildSynthesizedCredentials(token, now), {
764
722
  encoding: "utf8",
765
723
  mode: 384
766
724
  });
@@ -801,7 +759,7 @@ async function probeKeysUsage(keys, deps = {}) {
801
759
 
802
760
  // src/harness/pty/adapters/opencode-auth.ts
803
761
  import { promises as fs } from "fs";
804
- import { dirname as dirname2, join as join2 } from "path";
762
+ import { dirname, join as join2 } from "path";
805
763
  import { homedir } from "os";
806
764
  var logger2 = createServiceLogger("opencode-auth");
807
765
  var OPENCODE_CODEX_PLUGIN = "opencode-openai-codex-auth@4.4.0";
@@ -842,7 +800,7 @@ async function readJsonFile(path) {
842
800
  }
843
801
  }
844
802
  async function writeJsonFile(path, value) {
845
- await fs.mkdir(dirname2(path), { recursive: true });
803
+ await fs.mkdir(dirname(path), { recursive: true });
846
804
  await fs.writeFile(path, `${JSON.stringify(value, null, 2)}
847
805
  `, { mode: 384 });
848
806
  }
@@ -1349,6 +1307,7 @@ var AdhocSessionRunner = class {
1349
1307
  return;
1350
1308
  }
1351
1309
  this.commandSupervisor.start();
1310
+ this.commandSupervisor.notifyLoopReady?.();
1352
1311
  await this.connection.emitStatus("running");
1353
1312
  this.callbacks.onEvent?.({ type: "adhoc_runner_started", projectId: this.config.projectId });
1354
1313
  this.lifecycle.startIdleTimer();
@@ -1931,7 +1890,7 @@ function createSpawnedChildRunner(inputs) {
1931
1890
 
1932
1891
  // src/cli.ts
1933
1892
  if (process.argv.includes("--version")) {
1934
- const __dirname = dirname3(fileURLToPath(import.meta.url));
1893
+ const __dirname = dirname2(fileURLToPath(import.meta.url));
1935
1894
  const pkgPath = join3(__dirname, "..", "package.json");
1936
1895
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
1937
1896
  process.stdout.write(pkg.version + "\n");
@@ -2013,15 +1972,15 @@ process.on("unhandledRejection", (reason) => {
2013
1972
  process.exit(1);
2014
1973
  });
2015
1974
  if (process.env.CONVEYOR_MODE === "workbench") {
2016
- const { startWorkbenchServer } = await import("./server-UGE67KIZ.js");
2017
- const { DEFAULT_WORKBENCH_PORT } = await import("./protocol-QJRHTTMQ.js");
1975
+ const { startWorkbenchServer } = await import("./server-TVIXWCAI.js");
1976
+ const { DEFAULT_WORKBENCH_PORT } = await import("./protocol-IHRTO5C4.js");
2018
1977
  const port = Number(process.env.CONVEYOR_WORKBENCH_PORT) || DEFAULT_WORKBENCH_PORT;
2019
1978
  const token = process.env.CONVEYOR_WORKBENCH_TOKEN ?? process.env.POD_BOOTSTRAP_TOKEN ?? "";
2020
1979
  if (!token) {
2021
1980
  logger6.error("workbench mode requires POD_BOOTSTRAP_TOKEN (or CONVEYOR_WORKBENCH_TOKEN)");
2022
1981
  process.exit(1);
2023
1982
  }
2024
- const pkgDir = dirname3(fileURLToPath(import.meta.url));
1983
+ const pkgDir = dirname2(fileURLToPath(import.meta.url));
2025
1984
  const pkg = JSON.parse(readFileSync(join3(pkgDir, "..", "package.json"), "utf-8"));
2026
1985
  const handle = await startWorkbenchServer({
2027
1986
  port,
@@ -2266,6 +2225,7 @@ if (!workspaceCommandSupervisor) {
2266
2225
  });
2267
2226
  process.exit(0);
2268
2227
  }
2228
+ runner.setWorkspaceCommands(workspaceCommandSupervisor);
2269
2229
  void checkSessionTaskIdentity({
2270
2230
  sessionId: process.env.CONVEYOR_SESSION_ID,
2271
2231
  taskId: CONVEYOR_TASK_ID,