agent-relay 3.2.18 → 3.2.22

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 (76) hide show
  1. package/bin/agent-relay-broker-darwin-arm64 +0 -0
  2. package/bin/agent-relay-broker-darwin-x64 +0 -0
  3. package/bin/agent-relay-broker-linux-arm64 +0 -0
  4. package/bin/agent-relay-broker-linux-x64 +0 -0
  5. package/dist/index.cjs +233 -55
  6. package/dist/src/cli/commands/cloud.d.ts +1 -9
  7. package/dist/src/cli/commands/cloud.d.ts.map +1 -1
  8. package/dist/src/cli/commands/cloud.js +326 -323
  9. package/dist/src/cli/commands/cloud.js.map +1 -1
  10. package/dist/src/cli/commands/connect.d.ts.map +1 -1
  11. package/dist/src/cli/commands/connect.js +6 -10
  12. package/dist/src/cli/commands/connect.js.map +1 -1
  13. package/package.json +16 -10
  14. package/packages/acp-bridge/package.json +2 -2
  15. package/packages/brand/README.md +36 -0
  16. package/packages/brand/brand.css +226 -0
  17. package/packages/brand/package.json +20 -0
  18. package/packages/cloud/dist/api-client.d.ts +33 -0
  19. package/packages/cloud/dist/api-client.d.ts.map +1 -0
  20. package/packages/cloud/dist/api-client.js +123 -0
  21. package/packages/cloud/dist/api-client.js.map +1 -0
  22. package/packages/cloud/dist/auth.d.ts +13 -0
  23. package/packages/cloud/dist/auth.d.ts.map +1 -0
  24. package/packages/cloud/dist/auth.js +248 -0
  25. package/packages/cloud/dist/auth.js.map +1 -0
  26. package/packages/cloud/dist/index.d.ts +5 -0
  27. package/packages/cloud/dist/index.d.ts.map +1 -0
  28. package/packages/cloud/dist/index.js +5 -0
  29. package/packages/cloud/dist/index.js.map +1 -0
  30. package/packages/cloud/dist/types.d.ts +73 -0
  31. package/packages/cloud/dist/types.d.ts.map +1 -0
  32. package/packages/cloud/dist/types.js +19 -0
  33. package/packages/cloud/dist/types.js.map +1 -0
  34. package/packages/cloud/dist/workflows.d.ts +34 -0
  35. package/packages/cloud/dist/workflows.d.ts.map +1 -0
  36. package/packages/cloud/dist/workflows.js +389 -0
  37. package/packages/cloud/dist/workflows.js.map +1 -0
  38. package/packages/cloud/package.json +44 -0
  39. package/packages/cloud/src/api-client.ts +169 -0
  40. package/packages/cloud/src/auth.ts +314 -0
  41. package/packages/cloud/src/index.ts +41 -0
  42. package/packages/cloud/src/types.ts +97 -0
  43. package/packages/cloud/src/workflows.ts +539 -0
  44. package/packages/cloud/tsconfig.json +21 -0
  45. package/packages/config/package.json +1 -1
  46. package/packages/hooks/package.json +4 -4
  47. package/packages/memory/package.json +2 -2
  48. package/packages/openclaw/package.json +2 -2
  49. package/packages/policy/package.json +2 -2
  50. package/packages/sdk/dist/workflows/__tests__/e2big-and-verify.test.d.ts +2 -0
  51. package/packages/sdk/dist/workflows/__tests__/e2big-and-verify.test.d.ts.map +1 -0
  52. package/packages/sdk/dist/workflows/__tests__/e2big-and-verify.test.js +62 -0
  53. package/packages/sdk/dist/workflows/__tests__/e2big-and-verify.test.js.map +1 -0
  54. package/packages/sdk/dist/workflows/cli.js +46 -2
  55. package/packages/sdk/dist/workflows/cli.js.map +1 -1
  56. package/packages/sdk/dist/workflows/file-db.d.ts +2 -0
  57. package/packages/sdk/dist/workflows/file-db.d.ts.map +1 -1
  58. package/packages/sdk/dist/workflows/file-db.js +20 -3
  59. package/packages/sdk/dist/workflows/file-db.js.map +1 -1
  60. package/packages/sdk/dist/workflows/runner.d.ts +10 -1
  61. package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
  62. package/packages/sdk/dist/workflows/runner.js +233 -50
  63. package/packages/sdk/dist/workflows/runner.js.map +1 -1
  64. package/packages/sdk/package.json +2 -2
  65. package/packages/sdk/src/__tests__/resume-fallback.test.ts +415 -0
  66. package/packages/sdk/src/__tests__/workflow-runner.test.ts +73 -2
  67. package/packages/sdk/src/workflows/__tests__/e2big-and-verify.test.ts +117 -0
  68. package/packages/sdk/src/workflows/cli.ts +53 -2
  69. package/packages/sdk/src/workflows/file-db.ts +22 -3
  70. package/packages/sdk/src/workflows/runner.ts +283 -49
  71. package/packages/sdk-py/pyproject.toml +1 -1
  72. package/packages/sdk-swift/Sources/AgentRelaySDK/RelayObserver.swift +2 -0
  73. package/packages/telemetry/package.json +1 -1
  74. package/packages/trajectory/package.json +2 -2
  75. package/packages/user-directory/package.json +2 -2
  76. package/packages/utils/package.json +2 -2
Binary file
Binary file
Binary file
Binary file
package/dist/index.cjs CHANGED
@@ -9318,11 +9318,11 @@ __export(base_exports, {
9318
9318
  setCwd: () => setCwd,
9319
9319
  synchronizedOutput: () => synchronizedOutput
9320
9320
  });
9321
- var import_node_process, import_node_os8, ESC, OSC, BEL, SEP, isTerminalApp, isWindows2, isTmux, cwdFunction, wrapOsc, cursorTo, cursorMove, cursorUp, cursorDown, cursorForward, cursorBackward, cursorLeft, cursorSavePosition, cursorRestorePosition, cursorGetPosition, cursorNextLine, cursorPrevLine, cursorHide, cursorShow, eraseLines, eraseEndLine, eraseStartLine, eraseLine, eraseDown, eraseUp, eraseScreen, scrollUp, scrollDown, clearScreen, clearViewport, isOldWindows, clearTerminal, enterAlternativeScreen, exitAlternativeScreen, beginSynchronizedOutput, endSynchronizedOutput, synchronizedOutput, beep, link, image, iTerm, ConEmu, setCwd;
9321
+ var import_node_process, import_node_os9, ESC, OSC, BEL, SEP, isTerminalApp, isWindows2, isTmux, cwdFunction, wrapOsc, cursorTo, cursorMove, cursorUp, cursorDown, cursorForward, cursorBackward, cursorLeft, cursorSavePosition, cursorRestorePosition, cursorGetPosition, cursorNextLine, cursorPrevLine, cursorHide, cursorShow, eraseLines, eraseEndLine, eraseStartLine, eraseLine, eraseDown, eraseUp, eraseScreen, scrollUp, scrollDown, clearScreen, clearViewport, isOldWindows, clearTerminal, enterAlternativeScreen, exitAlternativeScreen, beginSynchronizedOutput, endSynchronizedOutput, synchronizedOutput, beep, link, image, iTerm, ConEmu, setCwd;
9322
9322
  var init_base = __esm({
9323
9323
  "node_modules/ansi-escapes/base.js"() {
9324
9324
  import_node_process = __toESM(require("node:process"), 1);
9325
- import_node_os8 = __toESM(require("node:os"), 1);
9325
+ import_node_os9 = __toESM(require("node:os"), 1);
9326
9326
  init_environment();
9327
9327
  ESC = "\x1B[";
9328
9328
  OSC = "\x1B]";
@@ -9402,7 +9402,7 @@ var init_base = __esm({
9402
9402
  if (isBrowser || !isWindows2) {
9403
9403
  return false;
9404
9404
  }
9405
- const parts = import_node_os8.default.release().split(".");
9405
+ const parts = import_node_os9.default.release().split(".");
9406
9406
  const major = Number(parts[0]);
9407
9407
  const build = Number(parts[2] ?? 0);
9408
9408
  if (major < 10) {
@@ -41321,6 +41321,7 @@ var import_node_child_process4 = require("node:child_process");
41321
41321
  var import_node_crypto5 = require("node:crypto");
41322
41322
  var import_node_fs9 = require("node:fs");
41323
41323
  var import_promises5 = require("node:fs/promises");
41324
+ var import_node_os8 = require("node:os");
41324
41325
  var import_node_path12 = __toESM(require("node:path"), 1);
41325
41326
  var import_chalk = __toESM(require_source(), 1);
41326
41327
  var import_yaml2 = __toESM(require_dist(), 1);
@@ -43465,6 +43466,8 @@ var WorkflowRunner = class _WorkflowRunner {
43465
43466
  activeReviewers = /* @__PURE__ */ new Map();
43466
43467
  /** Structured CLI session reports captured during the current run, keyed by step name. */
43467
43468
  agentReports = /* @__PURE__ */ new Map();
43469
+ static PTY_TASK_ARG_SIZE_LIMIT = 2 * 1024 * 1024;
43470
+ // 2 MB
43468
43471
  constructor(options = {}) {
43469
43472
  this.db = options.db ?? new InMemoryWorkflowDb();
43470
43473
  this.workspaceId = options.workspaceId ?? "local";
@@ -44708,33 +44711,45 @@ ${err.suggestion}`);
44708
44711
  });
44709
44712
  }
44710
44713
  /** Resume a previously paused or partially completed run. */
44711
- async resume(runId, vars) {
44714
+ async resume(runId, vars, config2) {
44712
44715
  this.abortController = new AbortController();
44713
44716
  this.paused = false;
44714
- const run = await this.db.getRun(runId);
44717
+ let run = await this.db.getRun(runId);
44718
+ let stepStates = /* @__PURE__ */ new Map();
44715
44719
  if (!run) {
44716
- throw new Error(`Run "${runId}" not found`);
44720
+ const reconstructed = this.reconstructRunFromCache(runId, config2);
44721
+ if (!reconstructed) {
44722
+ throw new Error(`Run "${runId}" not found (no database entry or cached step outputs)`);
44723
+ }
44724
+ this.log("[resume] Reconstructing run from cached step outputs (workflow-runs.jsonl missing)");
44725
+ run = reconstructed.run;
44726
+ stepStates = reconstructed.stepStates;
44727
+ await this.db.insertRun(run);
44728
+ for (const [, state] of stepStates) {
44729
+ await this.db.insertStep(state.row);
44730
+ }
44717
44731
  }
44718
44732
  this.persistRunIdHint(runId);
44719
44733
  if (run.status !== "running" && run.status !== "failed") {
44720
44734
  throw new Error(`Run "${runId}" is in status "${run.status}" and cannot be resumed`);
44721
44735
  }
44722
- const config2 = vars ? this.resolveVariables(run.config, vars) : run.config;
44723
- const pathResult = this.resolvePathDefinitions(config2.paths, this.cwd);
44736
+ const resolvedConfig = vars ? this.resolveVariables(run.config, vars) : run.config;
44737
+ const pathResult = this.resolvePathDefinitions(resolvedConfig.paths, this.cwd);
44724
44738
  if (pathResult.errors.length > 0) {
44725
44739
  throw new Error(`Path validation failed:
44726
44740
  ${pathResult.errors.join("\n ")}`);
44727
44741
  }
44728
44742
  this.resolvedPaths = pathResult.resolved;
44729
- const workflows = config2.workflows ?? [];
44743
+ const workflows = resolvedConfig.workflows ?? [];
44730
44744
  const workflow2 = workflows.find((w) => w.name === run.workflowName);
44731
44745
  if (!workflow2) {
44732
44746
  throw new Error(`Workflow "${run.workflowName}" not found in stored config`);
44733
44747
  }
44734
- const existingSteps = await this.db.getStepsByRunId(runId);
44735
- const stepStates = /* @__PURE__ */ new Map();
44736
- for (const stepRow of existingSteps) {
44737
- stepStates.set(stepRow.stepName, { row: stepRow });
44748
+ if (stepStates.size === 0) {
44749
+ const existingSteps = await this.db.getStepsByRunId(runId);
44750
+ for (const stepRow of existingSteps) {
44751
+ stepStates.set(stepRow.stepName, { row: stepRow });
44752
+ }
44738
44753
  }
44739
44754
  for (const [, state] of stepStates) {
44740
44755
  if (state.row.status === "failed") {
@@ -44752,7 +44767,7 @@ ${err.suggestion}`);
44752
44767
  return this.runWorkflowCore({
44753
44768
  run,
44754
44769
  workflow: workflow2,
44755
- config: config2,
44770
+ config: resolvedConfig,
44756
44771
  stepStates,
44757
44772
  isResume: true
44758
44773
  });
@@ -45887,6 +45902,7 @@ ${resolvedTask}`;
45887
45902
  let ownerOutput;
45888
45903
  let ownerElapsed;
45889
45904
  let completionReason;
45905
+ let promptTaskText;
45890
45906
  if (usesDedicatedOwner) {
45891
45907
  const result = await this.executeSupervisedAgentStep(step, { specialist: effectiveSpecialist, owner: effectiveOwner, reviewer: reviewDef }, resolvedTask, timeoutMs);
45892
45908
  specialistOutput = result.specialistOutput;
@@ -45919,13 +45935,14 @@ ${resolvedTask}`;
45919
45935
  } : void 0
45920
45936
  });
45921
45937
  const output = typeof spawnResult === "string" ? spawnResult : spawnResult.output;
45938
+ promptTaskText = typeof spawnResult === "string" ? effectiveOwner.interactive === false ? void 0 : ownerTask : spawnResult.promptTaskText ?? ownerTask;
45922
45939
  lastExitCode = typeof spawnResult === "string" ? void 0 : spawnResult.exitCode;
45923
45940
  lastExitSignal = typeof spawnResult === "string" ? void 0 : spawnResult.exitSignal;
45924
45941
  ownerElapsed = Date.now() - ownerStartTime;
45925
45942
  this.log(`[${step.name}] Owner "${effectiveOwner.name}" exited`);
45926
45943
  if (usesOwnerFlow) {
45927
45944
  try {
45928
- const completionDecision = this.resolveOwnerCompletionDecision(step, output, output, ownerTask, resolvedTask);
45945
+ const completionDecision = this.resolveOwnerCompletionDecision(step, output, output, promptTaskText ?? ownerTask, promptTaskText ?? ownerTask);
45929
45946
  completionReason = completionDecision.completionReason;
45930
45947
  } catch (error48) {
45931
45948
  const canUseVerificationFallback = !usesDedicatedOwner && step.verification && error48 instanceof WorkflowCompletionError && error48.completionReason === "failed_no_evidence";
@@ -45950,7 +45967,7 @@ ${resolvedTask}`;
45950
45967
  }
45951
45968
  }
45952
45969
  if (step.verification && (!usesOwnerFlow || !usesDedicatedOwner) && !completionReason) {
45953
- const verificationResult = this.runVerification(step.verification, specialistOutput, step.name, effectiveOwner.interactive === false ? void 0 : resolvedTask);
45970
+ const verificationResult = this.runVerification(step.verification, specialistOutput, step.name, promptTaskText);
45954
45971
  completionReason = verificationResult.completionReason;
45955
45972
  }
45956
45973
  if (completionReason === "retry_requested_by_owner") {
@@ -46186,7 +46203,7 @@ WORKER COMPLETION CONTRACT:
46186
46203
  detail: `Worker ${workerRuntimeName} exited`,
46187
46204
  raw: { worker: workerRuntimeName, exitCode: result.exitCode, exitSignal: result.exitSignal }
46188
46205
  });
46189
- if (step.verification?.type === "output_contains" && result.output.includes(step.verification.value)) {
46206
+ if (step.verification?.type === "output_contains" && this.outputContainsVerificationToken(result.output, step.verification.value, result.promptTaskText)) {
46190
46207
  this.log(`[${step.name}] Verification gate observed: output contains ${JSON.stringify(step.verification.value)}`);
46191
46208
  }
46192
46209
  }).catch((error48) => {
@@ -46228,8 +46245,9 @@ WORKER COMPLETION CONTRACT:
46228
46245
  const ownerElapsed = Date.now() - ownerStartTime;
46229
46246
  const ownerOutput = ownerResultObj.output;
46230
46247
  this.log(`[${step.name}] Owner "${supervised.owner.name}" exited`);
46231
- const specialistOutput = (await workerPromise).output;
46232
- const completionDecision = this.resolveOwnerCompletionDecision(step, ownerOutput, specialistOutput, supervisorTask, resolvedTask);
46248
+ const workerResultObj = await workerPromise;
46249
+ const specialistOutput = workerResultObj.output;
46250
+ const completionDecision = this.resolveOwnerCompletionDecision(step, ownerOutput, specialistOutput, ownerResultObj.promptTaskText ?? supervisorTask, workerResultObj.promptTaskText ?? specialistTask);
46233
46251
  return {
46234
46252
  specialistOutput,
46235
46253
  ownerOutput,
@@ -46417,6 +46435,10 @@ WORKER COMPLETION CONTRACT:
46417
46435
  }
46418
46436
  hasOwnerCompletionMarker(step, output, injectedTaskText) {
46419
46437
  const marker = `STEP_COMPLETE:${step.name}`;
46438
+ const strippedOutput = this.stripInjectedTaskEcho(output, injectedTaskText);
46439
+ if (strippedOutput.includes(marker)) {
46440
+ return true;
46441
+ }
46420
46442
  const taskHasMarker = injectedTaskText.includes(marker);
46421
46443
  const first = output.indexOf(marker);
46422
46444
  if (first === -1) {
@@ -46463,6 +46485,49 @@ WORKER COMPLETION CONTRACT:
46463
46485
  stripEchoedPromptLines(output, patterns) {
46464
46486
  return output.split("\n").map((line) => line.trim()).filter(Boolean).filter((line) => patterns.every((pattern) => !pattern.test(line))).join("\n");
46465
46487
  }
46488
+ stripInjectedTaskEcho(output, injectedTaskText) {
46489
+ if (!injectedTaskText) {
46490
+ return output;
46491
+ }
46492
+ const candidates = [
46493
+ injectedTaskText,
46494
+ injectedTaskText.replace(/\r\n/g, "\n"),
46495
+ injectedTaskText.replace(/\n/g, "\r\n")
46496
+ ].filter((candidate, index, all) => candidate.length > 0 && all.indexOf(candidate) === index);
46497
+ for (const candidate of candidates) {
46498
+ const start = output.indexOf(candidate);
46499
+ if (start !== -1) {
46500
+ return output.slice(0, start) + output.slice(start + candidate.length);
46501
+ }
46502
+ }
46503
+ return output;
46504
+ }
46505
+ outputContainsVerificationToken(output, token, injectedTaskText) {
46506
+ if (!token) {
46507
+ return false;
46508
+ }
46509
+ return this.stripInjectedTaskEcho(output, injectedTaskText).includes(token);
46510
+ }
46511
+ prepareInteractiveSpawnTask(agentName, taskText) {
46512
+ if (Buffer.byteLength(taskText, "utf8") <= _WorkflowRunner.PTY_TASK_ARG_SIZE_LIMIT) {
46513
+ return {
46514
+ spawnTaskText: taskText,
46515
+ promptTaskText: taskText
46516
+ };
46517
+ }
46518
+ const taskTmpDir = (0, import_node_fs9.mkdtempSync)(import_node_path12.default.join((0, import_node_os8.tmpdir)(), "relay-pty-task-"));
46519
+ const taskTmpFile = import_node_path12.default.join(taskTmpDir, `${agentName}-${Date.now()}.txt`);
46520
+ (0, import_node_fs9.writeFileSync)(taskTmpFile, taskText, { encoding: "utf8", mode: 384, flag: "wx" });
46521
+ const promptTaskText = `TASK_FILE:${taskTmpFile}
46522
+ Read that file completely before taking any action.
46523
+ Treat the file contents as the full workflow task and follow them exactly.
46524
+ Do not ask for the task again.`;
46525
+ return {
46526
+ spawnTaskText: promptTaskText,
46527
+ promptTaskText,
46528
+ taskTmpFile
46529
+ };
46530
+ }
46466
46531
  firstMeaningfulLine(output) {
46467
46532
  return output.split("\n").map((line) => line.trim()).find(Boolean);
46468
46533
  }
@@ -47000,6 +47065,7 @@ DO NOT:
47000
47065
  const delegationGuidance = isHub || !isHubPattern ? this.buildDelegationGuidance(agentDef.cli, timeoutMs) : "";
47001
47066
  const relayRegistrationNote = this.buildRelayRegistrationNote(agentDef.cli, agentName);
47002
47067
  const taskWithExit = step.task + (relayRegistrationNote ? "\n\n" + relayRegistrationNote : "") + (delegationGuidance ? "\n\n" + delegationGuidance + "\n" : "") + '\n\n---\nIMPORTANT: When you have fully completed this task, you MUST self-terminate by either: (a) calling remove_agent(name: "<your-agent-name>", reason: "task completed") \u2014 preferred, or (b) outputting the exact text "/exit" on its own line as a fallback. Do not wait for further input \u2014 terminate immediately after finishing. Do NOT spawn sub-agents unless the task explicitly requires it.';
47068
+ const preparedTask = this.prepareInteractiveSpawnTask(agentName, taskWithExit);
47003
47069
  this.ptyOutputBuffers.set(agentName, []);
47004
47070
  const logsDir = this.getWorkerLogsDir();
47005
47071
  const logStream = (0, import_node_fs9.createWriteStream)(import_node_path12.default.join(logsDir, `${agentName}.log`), { flags: "a" });
@@ -47029,7 +47095,7 @@ DO NOT:
47029
47095
  model: agentDef.constraints?.model,
47030
47096
  args: interactiveSpawnPolicy.args,
47031
47097
  channels: agentChannels,
47032
- task: taskWithExit,
47098
+ task: preparedTask.spawnTaskText,
47033
47099
  idleThresholdSecs: agentDef.constraints?.idleThresholdSecs,
47034
47100
  cwd: agentCwd
47035
47101
  });
@@ -47097,13 +47163,13 @@ DO NOT:
47097
47163
  }
47098
47164
  this.log(`[${step.name}] Assigned to ${agent.name}`);
47099
47165
  this.activeAgentHandles.set(agentName, agent);
47100
- exitResult = await this.waitForExitWithIdleNudging(agent, agentDef, step, timeoutMs, options.preserveOnIdle ?? this.shouldPreserveIdleSupervisor(agentDef, step, options.evidenceRole));
47166
+ exitResult = await this.waitForExitWithIdleNudging(agent, agentDef, step, timeoutMs, preparedTask.promptTaskText, options.preserveOnIdle ?? this.shouldPreserveIdleSupervisor(agentDef, step, options.evidenceRole));
47101
47167
  stopHeartbeat?.();
47102
47168
  if (exitResult === "timeout") {
47103
47169
  let timeoutRecovered = false;
47104
47170
  if (step.verification) {
47105
47171
  const ptyOutput = (this.ptyOutputBuffers.get(agentName) ?? []).join("");
47106
- const verificationResult = this.runVerification(step.verification, ptyOutput, step.name, void 0, { allowFailure: true });
47172
+ const verificationResult = this.runVerification(step.verification, ptyOutput, step.name, preparedTask.promptTaskText, { allowFailure: true });
47107
47173
  if (verificationResult.passed) {
47108
47174
  this.log(`[${step.name}] Agent timed out but verification passed \u2014 treating as complete`);
47109
47175
  this.postToChannel(`**[${step.name}]** Agent idle after completing work \u2014 verification passed, releasing`);
@@ -47147,6 +47213,9 @@ DO NOT:
47147
47213
  this.unregisterWorker(agentName);
47148
47214
  this.supervisedRuntimeAgents.delete(agentName);
47149
47215
  this.runtimeStepAgents.delete(agentName);
47216
+ if (preparedTask.taskTmpFile) {
47217
+ await (0, import_promises5.unlink)(preparedTask.taskTmpFile).catch(() => void 0);
47218
+ }
47150
47219
  }
47151
47220
  let output;
47152
47221
  if (ptyChunks.length > 0) {
@@ -47165,7 +47234,8 @@ DO NOT:
47165
47234
  return {
47166
47235
  output,
47167
47236
  exitCode: agent?.exitCode,
47168
- exitSignal: agent?.exitSignal
47237
+ exitSignal: agent?.exitSignal,
47238
+ promptTaskText: preparedTask.promptTaskText
47169
47239
  };
47170
47240
  }
47171
47241
  // ── Idle nudging ────────────────────────────────────────────────────────
@@ -47210,7 +47280,7 @@ DO NOT:
47210
47280
  * Wait for agent exit with idle detection and nudging.
47211
47281
  * If no idle nudge config is set, falls through to simple waitForExit.
47212
47282
  */
47213
- async waitForExitWithIdleNudging(agent, agentDef, step, timeoutMs, preserveIdleSupervisor = false) {
47283
+ async waitForExitWithIdleNudging(agent, agentDef, step, timeoutMs, promptTaskText, preserveIdleSupervisor = false) {
47214
47284
  const nudgeConfig = this.currentConfig?.swarm.idleNudge;
47215
47285
  if (!nudgeConfig) {
47216
47286
  if (preserveIdleSupervisor) {
@@ -47232,15 +47302,7 @@ DO NOT:
47232
47302
  if (step.verification && step.verification.type === "output_contains") {
47233
47303
  const token = step.verification.value;
47234
47304
  const ptyOutput = (this.ptyOutputBuffers.get(agent.name) ?? []).join("");
47235
- const taskText = step.task ?? "";
47236
- const taskHasToken = taskText.includes(token);
47237
- let verificationPassed = true;
47238
- if (taskHasToken) {
47239
- const first = ptyOutput.indexOf(token);
47240
- verificationPassed = first !== -1 && ptyOutput.includes(token, first + token.length);
47241
- } else {
47242
- verificationPassed = ptyOutput.includes(token);
47243
- }
47305
+ const verificationPassed = this.outputContainsVerificationToken(ptyOutput, token, promptTaskText);
47244
47306
  if (!verificationPassed) {
47245
47307
  this.log(`[${step.name}] Agent "${agent.name}" went idle but verification not yet passed \u2014 waiting for more output`);
47246
47308
  const idleGraceSecs = 15;
@@ -47389,14 +47451,7 @@ DO NOT:
47389
47451
  switch (check2.type) {
47390
47452
  case "output_contains": {
47391
47453
  const token = check2.value;
47392
- const taskHasToken = injectedTaskText ? injectedTaskText.includes(token) : false;
47393
- if (taskHasToken) {
47394
- const first = output.indexOf(token);
47395
- const hasSecond = first !== -1 && output.includes(token, first + token.length);
47396
- if (!hasSecond) {
47397
- return fail(`Verification failed for "${stepName}": output does not contain "${token}" (token found only in task injection \u2014 agent must output it explicitly)`);
47398
- }
47399
- } else if (!output.includes(token)) {
47454
+ if (!this.outputContainsVerificationToken(output, token, injectedTaskText)) {
47400
47455
  return fail(`Verification failed for "${stepName}": output does not contain "${token}"`);
47401
47456
  }
47402
47457
  break;
@@ -47899,8 +47954,15 @@ ${excerpt}` : "";
47899
47954
  sanitizeChannelName(name) {
47900
47955
  return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").slice(0, 32);
47901
47956
  }
47957
+ /** Validate that a runId is safe for use in file paths (no traversal). */
47958
+ validateRunId(runId) {
47959
+ if (/[/\\]|^\.\.?$/.test(runId) || runId.includes("..")) {
47960
+ throw new Error(`Invalid runId: "${runId}" contains path traversal characters`);
47961
+ }
47962
+ }
47902
47963
  /** Directory for persisted step outputs: .agent-relay/step-outputs/{runId}/ */
47903
47964
  getStepOutputDir(runId) {
47965
+ this.validateRunId(runId);
47904
47966
  return import_node_path12.default.join(this.cwd, ".agent-relay", "step-outputs", runId);
47905
47967
  }
47906
47968
  /** Persist step output to disk and post full output as a channel message. */
@@ -47978,6 +48040,109 @@ ${preview}
47978
48040
  return void 0;
47979
48041
  }
47980
48042
  }
48043
+ /** Match the best workflow from config given a set of cached step names. */
48044
+ matchWorkflowFromCache(workflows, cachedStepNames) {
48045
+ if (workflows.length === 1)
48046
+ return workflows[0];
48047
+ if (cachedStepNames.size === 0) {
48048
+ this.log("[resume] Multiple workflows in config with empty cache \u2014 cannot disambiguate");
48049
+ return null;
48050
+ }
48051
+ const scored = workflows.map((candidate) => ({
48052
+ workflow: candidate,
48053
+ matchedSteps: candidate.steps.filter((step) => cachedStepNames.has(step.name)).length,
48054
+ unknownSteps: [...cachedStepNames].filter((name) => !candidate.steps.some((step) => step.name === name)).length
48055
+ })).filter((candidate) => candidate.unknownSteps === 0).sort((a, b) => b.matchedSteps - a.matchedSteps);
48056
+ return scored[0]?.workflow ?? null;
48057
+ }
48058
+ reconstructRunFromCache(runId, config2) {
48059
+ const stepOutputDir = this.getStepOutputDir(runId);
48060
+ if (!(0, import_node_fs9.existsSync)(stepOutputDir))
48061
+ return null;
48062
+ let resumeConfig = config2 ?? this.currentConfig;
48063
+ if (!resumeConfig) {
48064
+ const yamlPath = import_node_path12.default.join(this.cwd, "relay.yaml");
48065
+ if ((0, import_node_fs9.existsSync)(yamlPath)) {
48066
+ try {
48067
+ const raw = (0, import_node_fs9.readFileSync)(yamlPath, "utf-8");
48068
+ resumeConfig = this.parseYamlString(raw, yamlPath);
48069
+ } catch {
48070
+ return null;
48071
+ }
48072
+ } else {
48073
+ return null;
48074
+ }
48075
+ }
48076
+ let entries;
48077
+ try {
48078
+ entries = (0, import_node_fs9.readdirSync)(stepOutputDir, { withFileTypes: true });
48079
+ } catch {
48080
+ return null;
48081
+ }
48082
+ const cachedStepNames = new Set(entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name.slice(0, -3)).filter(Boolean));
48083
+ const workflows = resumeConfig.workflows ?? [];
48084
+ if (workflows.length === 0)
48085
+ return null;
48086
+ const workflow2 = this.matchWorkflowFromCache(workflows, cachedStepNames);
48087
+ if (!workflow2)
48088
+ return null;
48089
+ const stepMtimes = /* @__PURE__ */ new Map();
48090
+ let earliestMtime = Date.now();
48091
+ for (const stepName of cachedStepNames) {
48092
+ try {
48093
+ const mdPath = import_node_path12.default.join(stepOutputDir, `${stepName}.md`);
48094
+ const reportPath = import_node_path12.default.join(stepOutputDir, `${stepName}.report.json`);
48095
+ const mdStat = (0, import_node_fs9.existsSync)(mdPath) ? (0, import_node_fs9.statSync)(mdPath) : null;
48096
+ const reportStat = (0, import_node_fs9.existsSync)(reportPath) ? (0, import_node_fs9.statSync)(reportPath) : null;
48097
+ const mtime = Math.max(mdStat?.mtimeMs ?? 0, reportStat?.mtimeMs ?? 0);
48098
+ if (mtime > 0) {
48099
+ stepMtimes.set(stepName, new Date(mtime).toISOString());
48100
+ if (mtime < earliestMtime)
48101
+ earliestMtime = mtime;
48102
+ }
48103
+ } catch {
48104
+ }
48105
+ }
48106
+ const fallbackTime = (/* @__PURE__ */ new Date()).toISOString();
48107
+ const completedSteps = new Set(workflow2.steps.filter((step) => cachedStepNames.has(step.name)).map((step) => step.name));
48108
+ const failedStepName = workflow2.steps.find((step) => !completedSteps.has(step.name) && (step.dependsOn ?? []).every((dep) => completedSteps.has(dep)))?.name;
48109
+ const runStartedAt = new Date(earliestMtime).toISOString();
48110
+ const run = {
48111
+ id: runId,
48112
+ workspaceId: this.workspaceId,
48113
+ workflowName: workflow2.name,
48114
+ pattern: resumeConfig.swarm.pattern,
48115
+ status: "failed",
48116
+ config: resumeConfig,
48117
+ startedAt: runStartedAt,
48118
+ createdAt: runStartedAt,
48119
+ updatedAt: fallbackTime
48120
+ };
48121
+ const stepStates = /* @__PURE__ */ new Map();
48122
+ for (const step of workflow2.steps) {
48123
+ const isNonAgent = step.type === "deterministic" || step.type === "worktree" || step.type === "integration";
48124
+ const cachedOutput = completedSteps.has(step.name) ? this.loadStepOutput(runId, step.name) : void 0;
48125
+ const status = completedSteps.has(step.name) ? "completed" : step.name === failedStepName ? "failed" : "pending";
48126
+ const stepRow = {
48127
+ id: this.generateId(),
48128
+ runId,
48129
+ stepName: step.name,
48130
+ agentName: isNonAgent ? null : step.agent ?? null,
48131
+ stepType: isNonAgent ? step.type : "agent",
48132
+ status,
48133
+ task: step.type === "deterministic" ? step.command ?? "" : step.type === "worktree" ? step.branch ?? "" : step.type === "integration" ? `${step.integration}.${step.action}` : step.task ?? "",
48134
+ dependsOn: step.dependsOn ?? [],
48135
+ output: cachedOutput,
48136
+ error: status === "failed" ? "Recovered from cached step outputs" : void 0,
48137
+ completedAt: status === "completed" ? stepMtimes.get(step.name) ?? fallbackTime : void 0,
48138
+ retryCount: 0,
48139
+ createdAt: stepMtimes.get(step.name) ?? fallbackTime,
48140
+ updatedAt: stepMtimes.get(step.name) ?? fallbackTime
48141
+ };
48142
+ stepStates.set(step.name, { row: stepRow });
48143
+ }
48144
+ return { run, stepStates };
48145
+ }
47981
48146
  /** Get or create the worker logs directory (.agent-relay/team/worker-logs) */
47982
48147
  getWorkerLogsDir() {
47983
48148
  const logsDir = import_node_path12.default.join(this.cwd, ".agent-relay", "team", "worker-logs");
@@ -48039,6 +48204,7 @@ var JsonFileWorkflowDb = class {
48039
48204
  filePath;
48040
48205
  /** Whether the storage directory is writable. False = silent no-op mode. */
48041
48206
  writable;
48207
+ appendFailedOnce = false;
48042
48208
  constructor(filePath) {
48043
48209
  this.filePath = filePath;
48044
48210
  let writable = false;
@@ -48053,13 +48219,25 @@ var JsonFileWorkflowDb = class {
48053
48219
  isWritable() {
48054
48220
  return this.writable;
48055
48221
  }
48222
+ hasStepOutputs(runId) {
48223
+ try {
48224
+ const dir = import_node_path13.default.join(import_node_path13.default.dirname(this.filePath), "step-outputs", runId);
48225
+ return (0, import_node_fs10.existsSync)(dir) && (0, import_node_fs10.readdirSync)(dir).length > 0;
48226
+ } catch {
48227
+ return false;
48228
+ }
48229
+ }
48056
48230
  // ── Private helpers ─────────────────────────────────────────────────────
48057
48231
  append(entry) {
48058
48232
  if (!this.writable)
48059
48233
  return;
48060
48234
  try {
48061
48235
  (0, import_node_fs10.appendFileSync)(this.filePath, JSON.stringify(entry) + "\n", "utf8");
48062
- } catch {
48236
+ } catch (err) {
48237
+ if (!this.appendFailedOnce) {
48238
+ this.appendFailedOnce = true;
48239
+ console.warn("[workflow] warning: failed to write run state to " + this.filePath + " \u2014 --resume will not be available for this run. Use --start-from instead. Error: " + (err instanceof Error ? err.message : String(err)));
48240
+ }
48063
48241
  }
48064
48242
  }
48065
48243
  /** Read all lines and build the latest snapshot for each ID. */
@@ -50487,13 +50665,13 @@ function getRepoFullNameFromPath(workingDirectory) {
50487
50665
  var import_node_fs14 = __toESM(require("node:fs"), 1);
50488
50666
  var import_node_path16 = __toESM(require("node:path"), 1);
50489
50667
  var import_node_https = __toESM(require("node:https"), 1);
50490
- var import_node_os9 = __toESM(require("node:os"), 1);
50668
+ var import_node_os10 = __toESM(require("node:os"), 1);
50491
50669
  var import_compare_versions = __toESM(require_umd(), 1);
50492
50670
  var PACKAGE_NAME = "agent-relay";
50493
50671
  var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
50494
50672
  var NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
50495
50673
  function getCachePath() {
50496
- const cacheDir = import_node_path16.default.join(import_node_os9.default.homedir(), ".agent-relay");
50674
+ const cacheDir = import_node_path16.default.join(import_node_os10.default.homedir(), ".agent-relay");
50497
50675
  return import_node_path16.default.join(cacheDir, "update-cache.json");
50498
50676
  }
50499
50677
  function readCache() {
@@ -50794,7 +50972,7 @@ function validateModelForCli(cli, model) {
50794
50972
 
50795
50973
  // packages/utils/dist/relay-pty-path.js
50796
50974
  var import_node_fs15 = __toESM(require("node:fs"), 1);
50797
- var import_node_os10 = __toESM(require("node:os"), 1);
50975
+ var import_node_os11 = __toESM(require("node:os"), 1);
50798
50976
  var import_node_path17 = __toESM(require("node:path"), 1);
50799
50977
  var SUPPORTED_PLATFORMS = {
50800
50978
  darwin: {
@@ -50807,13 +50985,13 @@ var SUPPORTED_PLATFORMS = {
50807
50985
  }
50808
50986
  };
50809
50987
  function getPlatformBinaryName() {
50810
- const platform2 = import_node_os10.default.platform();
50811
- const arch = import_node_os10.default.arch();
50988
+ const platform2 = import_node_os11.default.platform();
50989
+ const arch = import_node_os11.default.arch();
50812
50990
  return SUPPORTED_PLATFORMS[platform2]?.[arch] ?? null;
50813
50991
  }
50814
50992
  function isPlatformSupported() {
50815
- const platform2 = import_node_os10.default.platform();
50816
- const arch = import_node_os10.default.arch();
50993
+ const platform2 = import_node_os11.default.platform();
50994
+ const arch = import_node_os11.default.arch();
50817
50995
  return SUPPORTED_PLATFORMS[platform2]?.[arch] !== void 0;
50818
50996
  }
50819
50997
  function getSupportedPlatforms() {
@@ -50933,7 +51111,7 @@ function isPlatformCompatibleBinary(filePath) {
50933
51111
  return false;
50934
51112
  }
50935
51113
  const magic = header.readUInt32BE(0);
50936
- const platform2 = import_node_os10.default.platform();
51114
+ const platform2 = import_node_os11.default.platform();
50937
51115
  if (platform2 === "darwin") {
50938
51116
  return isMachOBinary(magic);
50939
51117
  }
@@ -51568,7 +51746,7 @@ var import_node_child_process7 = require("node:child_process");
51568
51746
  var import_node_crypto13 = __toESM(require("node:crypto"), 1);
51569
51747
  var import_node_path18 = __toESM(require("node:path"), 1);
51570
51748
  var import_node_fs16 = __toESM(require("node:fs"), 1);
51571
- var import_node_os11 = __toESM(require("node:os"), 1);
51749
+ var import_node_os12 = __toESM(require("node:os"), 1);
51572
51750
  function getGlobalBaseDir2() {
51573
51751
  if (process.env.AGENT_RELAY_DATA_DIR) {
51574
51752
  return process.env.AGENT_RELAY_DATA_DIR;
@@ -51577,7 +51755,7 @@ function getGlobalBaseDir2() {
51577
51755
  if (xdgDataHome) {
51578
51756
  return import_node_path18.default.join(xdgDataHome, "agent-relay");
51579
51757
  }
51580
- return import_node_path18.default.join(import_node_os11.default.homedir(), ".agent-relay");
51758
+ return import_node_path18.default.join(import_node_os12.default.homedir(), ".agent-relay");
51581
51759
  }
51582
51760
  var GLOBAL_BASE_DIR2 = getGlobalBaseDir2();
51583
51761
  var PROJECT_DATA_DIR2 = ".agent-relay";
@@ -51620,10 +51798,10 @@ function getProjectPaths2(projectRoot) {
51620
51798
  // packages/config/dist/trajectory-config.js
51621
51799
  var import_node_fs17 = require("node:fs");
51622
51800
  var import_node_path19 = require("node:path");
51623
- var import_node_os12 = require("node:os");
51801
+ var import_node_os13 = require("node:os");
51624
51802
  var import_node_crypto14 = require("node:crypto");
51625
51803
  function getAgentRelayConfigDir() {
51626
- return process.env.AGENT_RELAY_CONFIG_DIR ?? (0, import_node_path19.join)((0, import_node_os12.homedir)(), ".config", "agent-relay");
51804
+ return process.env.AGENT_RELAY_CONFIG_DIR ?? (0, import_node_path19.join)((0, import_node_os13.homedir)(), ".config", "agent-relay");
51627
51805
  }
51628
51806
  var configCache = null;
51629
51807
  function getRelayConfigPath(_projectRoot) {
@@ -51667,7 +51845,7 @@ function getProjectHash(projectRoot) {
51667
51845
  }
51668
51846
  function getUserTrajectoriesDir(projectRoot) {
51669
51847
  const projectHash = getProjectHash(projectRoot);
51670
- const configDir = process.env.XDG_CONFIG_HOME || (0, import_node_path19.join)((0, import_node_os12.homedir)(), ".config");
51848
+ const configDir = process.env.XDG_CONFIG_HOME || (0, import_node_path19.join)((0, import_node_os13.homedir)(), ".config");
51671
51849
  return (0, import_node_path19.join)(configDir, "agent-relay", "trajectories", projectHash);
51672
51850
  }
51673
51851
  function getRepoTrajectoriesDir(projectRoot) {
@@ -1,18 +1,10 @@
1
1
  import { Command } from 'commander';
2
- import { type CloudApiClient, type CloudAgent } from '../lib/cloud-client.js';
3
2
  type ExitFn = (code: number) => never;
4
- export type { CloudApiClient, CloudAgent };
5
3
  export interface CloudDependencies {
6
- createApiClient: () => CloudApiClient;
7
- getDataDir: () => string;
8
- getHostname: () => string;
9
- randomHex: (bytes: number) => string;
10
- now: () => Date;
11
- openExternal: (url: string) => Promise<void>;
12
- prompt: (question: string) => Promise<string>;
13
4
  log: (...args: unknown[]) => void;
14
5
  error: (...args: unknown[]) => void;
15
6
  exit: ExitFn;
16
7
  }
17
8
  export declare function registerCloudCommands(program: Command, overrides?: Partial<CloudDependencies>): void;
9
+ export {};
18
10
  //# sourceMappingURL=cloud.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/cloud.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,UAAU,EAChB,MAAM,wBAAwB,CAAC;AAEhC,KAAK,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;AAUtC,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC;AAE3C,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,MAAM,cAAc,CAAC;IACtC,UAAU,EAAE,MAAM,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,GAAG,EAAE,MAAM,IAAI,CAAC;IAChB,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAClC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;CACd;AA+FD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,GAAE,OAAO,CAAC,iBAAiB,CAAM,GACzC,IAAI,CAmWN"}
1
+ {"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/cloud.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAwB,MAAM,WAAW,CAAC;AAwB1D,KAAK,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;AAEtC,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAClC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;CACd;AAyFD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,GAAE,OAAO,CAAC,iBAAiB,CAAM,GACzC,IAAI,CAsYN"}