@neriros/ralphy 3.10.10 → 3.10.12

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.
package/dist/mcp/index.js CHANGED
@@ -24093,6 +24093,12 @@ var HistoryEntrySchema = exports_external.object({
24093
24093
  appVersion: exports_external.string().optional(),
24094
24094
  usage: IterationUsageSchema.partial().optional()
24095
24095
  });
24096
+ var RevisionSchema = exports_external.object({
24097
+ version: exports_external.number(),
24098
+ attachmentId: exports_external.string(),
24099
+ sha256: exports_external.string(),
24100
+ trigger: exports_external.string()
24101
+ });
24096
24102
  var StateSchema = exports_external.object({
24097
24103
  version: exports_external.literal("2"),
24098
24104
  name: exports_external.string(),
@@ -24141,12 +24147,16 @@ var StateSchema = exports_external.object({
24141
24147
  attachmentId: exports_external.string().nullable().default(null),
24142
24148
  sha256: exports_external.string().nullable().default(null)
24143
24149
  }).default({ attachmentId: null, sha256: null }),
24150
+ designRevisions: exports_external.array(RevisionSchema).default([]),
24151
+ designPdfRevisions: exports_external.array(RevisionSchema).default([]),
24144
24152
  legacyProposalPurged: exports_external.boolean().default(false)
24145
24153
  }).default({
24146
24154
  proposal: { attachmentId: null, sha256: null },
24147
24155
  design: { attachmentId: null, sha256: null },
24148
24156
  proposalPdf: { attachmentId: null, sha256: null },
24149
24157
  designPdf: { attachmentId: null, sha256: null },
24158
+ designRevisions: [],
24159
+ designPdfRevisions: [],
24150
24160
  legacyProposalPurged: false
24151
24161
  }),
24152
24162
  confirmation: exports_external.object({
@@ -18928,8 +18928,8 @@ import { readFileSync } from "fs";
18928
18928
  import { resolve } from "path";
18929
18929
  function getVersion() {
18930
18930
  try {
18931
- if ("3.10.10")
18932
- return "3.10.10";
18931
+ if ("3.10.12")
18932
+ return "3.10.12";
18933
18933
  } catch {}
18934
18934
  const dirsToTry = [];
18935
18935
  try {
@@ -89590,7 +89590,7 @@ var init_zod2 = __esm(() => {
89590
89590
  function markersOf(set3) {
89591
89591
  return Array.isArray(set3) ? set3 : [set3];
89592
89592
  }
89593
- var IterationUsageSchema, UsageSchema, HistoryEntrySchema, StateSchema, PhaseFrontmatterSchema;
89593
+ var IterationUsageSchema, UsageSchema, HistoryEntrySchema, RevisionSchema, StateSchema, PhaseFrontmatterSchema;
89594
89594
  var init_types2 = __esm(() => {
89595
89595
  init_zod2();
89596
89596
  IterationUsageSchema = exports_external2.object({
@@ -89623,6 +89623,12 @@ var init_types2 = __esm(() => {
89623
89623
  appVersion: exports_external2.string().optional(),
89624
89624
  usage: IterationUsageSchema.partial().optional()
89625
89625
  });
89626
+ RevisionSchema = exports_external2.object({
89627
+ version: exports_external2.number(),
89628
+ attachmentId: exports_external2.string(),
89629
+ sha256: exports_external2.string(),
89630
+ trigger: exports_external2.string()
89631
+ });
89626
89632
  StateSchema = exports_external2.object({
89627
89633
  version: exports_external2.literal("2"),
89628
89634
  name: exports_external2.string(),
@@ -89671,12 +89677,16 @@ var init_types2 = __esm(() => {
89671
89677
  attachmentId: exports_external2.string().nullable().default(null),
89672
89678
  sha256: exports_external2.string().nullable().default(null)
89673
89679
  }).default({ attachmentId: null, sha256: null }),
89680
+ designRevisions: exports_external2.array(RevisionSchema).default([]),
89681
+ designPdfRevisions: exports_external2.array(RevisionSchema).default([]),
89674
89682
  legacyProposalPurged: exports_external2.boolean().default(false)
89675
89683
  }).default({
89676
89684
  proposal: { attachmentId: null, sha256: null },
89677
89685
  design: { attachmentId: null, sha256: null },
89678
89686
  proposalPdf: { attachmentId: null, sha256: null },
89679
89687
  designPdf: { attachmentId: null, sha256: null },
89688
+ designRevisions: [],
89689
+ designPdfRevisions: [],
89680
89690
  legacyProposalPurged: false
89681
89691
  }),
89682
89692
  confirmation: exports_external2.object({
@@ -105470,6 +105480,7 @@ class AgentCoordinator {
105470
105480
  opts;
105471
105481
  workers = [];
105472
105482
  pendingIds = new Set;
105483
+ inFlight = new Set;
105473
105484
  queue = [];
105474
105485
  stopped = false;
105475
105486
  paused = null;
@@ -106036,7 +106047,31 @@ class AgentCoordinator {
106036
106047
  while (this.workers.length + this.pendingIds.size < this.opts.concurrency && this.queue.length > 0) {
106037
106048
  const next = this.queue.shift();
106038
106049
  this.pendingIds.add(next.issue.id);
106039
- this.launchWorker(next.issue, next.trigger, next.mention);
106050
+ this.track(this.launchWorker(next.issue, next.trigger, next.mention));
106051
+ }
106052
+ }
106053
+ track(p) {
106054
+ this.inFlight.add(p);
106055
+ p.finally(() => {
106056
+ this.inFlight.delete(p);
106057
+ });
106058
+ return p;
106059
+ }
106060
+ async whenSettled() {
106061
+ let consecutiveEmpty = 0;
106062
+ for (let guard = 0;guard < 1000; guard++) {
106063
+ if (this.inFlight.size > 0) {
106064
+ await Promise.allSettled(this.inFlight);
106065
+ consecutiveEmpty = 0;
106066
+ }
106067
+ await new Promise((r) => setTimeout(r, 0));
106068
+ if (this.inFlight.size === 0) {
106069
+ consecutiveEmpty += 1;
106070
+ if (consecutiveEmpty >= WHEN_SETTLED_STABLE_HOPS)
106071
+ return;
106072
+ } else {
106073
+ consecutiveEmpty = 0;
106074
+ }
106040
106075
  }
106041
106076
  }
106042
106077
  async launchWorker(issue2, trigger, mention) {
@@ -106115,6 +106150,7 @@ class AgentCoordinator {
106115
106150
  issueIdentifier: issue2.identifier,
106116
106151
  issue: issue2,
106117
106152
  trigger,
106153
+ ...prep.cwd ? { cwd: prep.cwd } : {},
106118
106154
  kill: handle.kill,
106119
106155
  lastReportedIteration: 0,
106120
106156
  lastSyncedIteration: 0,
@@ -106154,7 +106190,10 @@ class AgentCoordinator {
106154
106190
  this.deps.onLog(`! sync-tasks (launch) failed for ${issue2.identifier}: ${err.message}`, "yellow");
106155
106191
  }
106156
106192
  }
106157
- handle.exited.then(async (code) => {
106193
+ handle.exited.then((code) => this.track(this.finalizeWorkerExit(worker, issue2, prep, trigger, code)));
106194
+ }
106195
+ async finalizeWorkerExit(worker, issue2, prep, trigger, code) {
106196
+ {
106158
106197
  const idx = this.workers.indexOf(worker);
106159
106198
  if (idx >= 0)
106160
106199
  this.workers.splice(idx, 1);
@@ -106199,10 +106238,10 @@ class AgentCoordinator {
106199
106238
  exit_code: code,
106200
106239
  ok
106201
106240
  });
106202
- await this.notifyExited(issue2, prep.changeName, code, trigger);
106241
+ await this.notifyExited(issue2, prep.changeName, code, trigger, worker.cwd);
106203
106242
  this.deps.onWorkersChanged();
106204
106243
  this.spawnNext();
106205
- });
106244
+ }
106206
106245
  }
106207
106246
  async restartWorker(changeName) {
106208
106247
  if (this.stopped)
@@ -106259,7 +106298,7 @@ class AgentCoordinator {
106259
106298
  this.deps.onLog(`! onSteeringAppended failed for ${changeName}: ${err.message}`, "yellow");
106260
106299
  }
106261
106300
  }
106262
- async notifyExited(issue2, changeName, code, trigger) {
106301
+ async notifyExited(issue2, changeName, code, trigger, workerCwd) {
106263
106302
  const noChanges = code === NO_CHANGES_EXIT;
106264
106303
  const ok = code === 0 || noChanges;
106265
106304
  const changeDir = this.deps.getChangeDir?.(issue2) ?? undefined;
@@ -106279,6 +106318,7 @@ class AgentCoordinator {
106279
106318
  issueIdentifier: issue2.identifier,
106280
106319
  issue: issue2,
106281
106320
  trigger,
106321
+ ...workerCwd ? { cwd: workerCwd } : {},
106282
106322
  kill: () => {},
106283
106323
  lastReportedIteration: 0,
106284
106324
  lastSyncedIteration: 0,
@@ -106378,7 +106418,7 @@ var emptyPrStatus = () => ({
106378
106418
  conflicted: 0,
106379
106419
  ciFailed: 0,
106380
106420
  quarantined: 0
106381
- }), emptyPollResult = () => ({
106421
+ }), WHEN_SETTLED_STABLE_HOPS = 3, emptyPollResult = () => ({
106382
106422
  found: 0,
106383
106423
  added: 0,
106384
106424
  buckets: {
@@ -106891,7 +106931,9 @@ async function applyAwaitingMarkerOnce(issue2, statePath, state, deps) {
106891
106931
  }
106892
106932
  state.confirmation.awaitingMarkerAppliedAt = new Date().toISOString();
106893
106933
  try {
106894
- await writeConfirmationState(statePath, state.stateObj, state.confirmation);
106934
+ const fresh = await readConfirmationState(statePath);
106935
+ fresh.confirmation.awaitingMarkerAppliedAt = state.confirmation.awaitingMarkerAppliedAt;
106936
+ await writeConfirmationState(statePath, fresh.stateObj, fresh.confirmation);
106895
106937
  } catch (err) {
106896
106938
  deps.onLog(`! persist awaitingMarkerAppliedAt for ${issue2.identifier}: ${err.message}`, "yellow");
106897
106939
  }
@@ -106911,7 +106953,9 @@ async function openDraftPrOnce(issue2, statePath, changeName, cwd2, state, deps)
106911
106953
  }
106912
106954
  state.confirmation.earlyDraftPrAt = new Date().toISOString();
106913
106955
  try {
106914
- await writeConfirmationState(statePath, state.stateObj, state.confirmation);
106956
+ const fresh = await readConfirmationState(statePath);
106957
+ fresh.confirmation.earlyDraftPrAt = state.confirmation.earlyDraftPrAt;
106958
+ await writeConfirmationState(statePath, fresh.stateObj, fresh.confirmation);
106915
106959
  } catch (err) {
106916
106960
  deps.onLog(`! persist earlyDraftPrAt for ${issue2.identifier}: ${err.message}`, "yellow");
106917
106961
  }
@@ -107521,6 +107565,11 @@ var init_linear_resolvers = __esm(() => {
107521
107565
  // apps/agent/src/agent/wire/prepare.ts
107522
107566
  import { mkdir as mkdir8 } from "fs/promises";
107523
107567
  import { join as join27 } from "path";
107568
+ function composeAppendPrompt(promptArg, cfgAppendPrompt, workflowPrompt) {
107569
+ return [promptArg || cfgAppendPrompt || "", workflowPrompt].filter(Boolean).join(`
107570
+
107571
+ `);
107572
+ }
107524
107573
  function createPrepareHelpers(input) {
107525
107574
  const {
107526
107575
  args,
@@ -107617,9 +107666,7 @@ function createPrepareHelpers(input) {
107617
107666
  } catch (err) {
107618
107667
  diag("workflow", `! workflow render failed: ${err.message}`, "yellow");
107619
107668
  }
107620
- const appendPrompt = [args.prompt || cfg.appendPrompt || "", workflowPrompt].filter(Boolean).join(`
107621
-
107622
- `);
107669
+ const appendPrompt = composeAppendPrompt(args.prompt ?? "", cfg.appendPrompt ?? "", workflowPrompt);
107623
107670
  changeName = await scaffoldChangeForIssue(scaffoldTasksDir, scaffoldStatesDir, issue2, comments, appendPrompt, attachments);
107624
107671
  } else {
107625
107672
  changeName = derivedName;
@@ -107636,6 +107683,7 @@ function createPrepareHelpers(input) {
107636
107683
  }
107637
107684
  return {
107638
107685
  changeName,
107686
+ cwd: workerCwd,
107639
107687
  ...maps.prByChange.has(changeName) ? { prUrl: maps.prByChange.get(changeName) } : {}
107640
107688
  };
107641
107689
  }
@@ -108620,6 +108668,103 @@ function buildTicketDigest(issue2, comments) {
108620
108668
  function retroDepEntry(agentDebug, hook) {
108621
108669
  return agentDebug ? { runRetrospective: hook } : {};
108622
108670
  }
108671
+ function computeWantPr(wantPrBase, isAwaiting, isAwaitingConfirmation) {
108672
+ return wantPrBase && !isAwaiting && !isAwaitingConfirmation;
108673
+ }
108674
+ function computeWantValidateOnly(hasValidateSpec, wantPrBase) {
108675
+ return hasValidateSpec && !wantPrBase;
108676
+ }
108677
+ function releaseWorkerMaps(maps, changeName) {
108678
+ maps.cwdByChange.delete(changeName);
108679
+ maps.statesDirByChange.delete(changeName);
108680
+ maps.branchByChange.delete(changeName);
108681
+ maps.issueByChange.delete(changeName);
108682
+ }
108683
+ function buildTaskCmd(args, cfg, changeName) {
108684
+ const engine = args.engineSet ? args.engine : cfg.engine;
108685
+ const model = args.engineSet ? args.model : cfg.model;
108686
+ const c = [
108687
+ process.execPath,
108688
+ process.argv[1] ?? "",
108689
+ "loop",
108690
+ "task",
108691
+ "--name",
108692
+ changeName,
108693
+ "--" + engine,
108694
+ "--model",
108695
+ model
108696
+ ];
108697
+ const maxIter = args.maxIterations || cfg.maxIterationsPerTask;
108698
+ if (maxIter > 0)
108699
+ c.push("--max-iterations", String(maxIter));
108700
+ const maxCost = args.maxCostUsd || cfg.maxCostUsdPerTask;
108701
+ if (maxCost > 0)
108702
+ c.push("--max-cost", String(maxCost));
108703
+ const maxRuntime = args.maxRuntimeMinutes || cfg.maxRuntimeMinutesPerTask;
108704
+ if (maxRuntime > 0)
108705
+ c.push("--max-runtime", String(maxRuntime));
108706
+ const maxFailures = args.maxConsecutiveFailures !== 5 ? args.maxConsecutiveFailures : cfg.maxConsecutiveFailuresPerTask;
108707
+ if (maxFailures !== 5)
108708
+ c.push("--max-failures", String(maxFailures));
108709
+ const delay2 = args.delay || cfg.iterationDelaySeconds;
108710
+ if (delay2 > 0)
108711
+ c.push("--delay", String(delay2));
108712
+ if (args.log || cfg.logRawStream)
108713
+ c.push("--log");
108714
+ if (args.verbose || cfg.taskVerbose)
108715
+ c.push("--verbose");
108716
+ if (args.manualTest || cfg.enableManualTest)
108717
+ c.push("--manual-test");
108718
+ const rp = cfg.openspec.reviewPhase;
108719
+ if (rp.enabled) {
108720
+ c.push("--review-enabled");
108721
+ if (rp.maxRounds !== 1)
108722
+ c.push("--review-max-rounds", String(rp.maxRounds));
108723
+ if (rp.reviewerModel !== undefined)
108724
+ c.push("--review-model", rp.reviewerModel);
108725
+ if (rp.reviewerContextStrategy !== "fresh")
108726
+ c.push("--review-context-strategy", rp.reviewerContextStrategy);
108727
+ }
108728
+ c.push("--from-agent");
108729
+ return c;
108730
+ }
108731
+ function buildPostTaskInput(input) {
108732
+ const { args, cfg } = input;
108733
+ return {
108734
+ ...input.trigger ? { mode: input.trigger } : {},
108735
+ ...input.prUrl ? { prUrl: input.prUrl } : {},
108736
+ changeName: input.changeName,
108737
+ cwd: input.cwd,
108738
+ projectRoot: input.projectRoot,
108739
+ changeDir: input.changeDir,
108740
+ stateFilePath: input.stateFilePath,
108741
+ branch: input.branch,
108742
+ issue: input.issue,
108743
+ exitCode: input.exitCode,
108744
+ useWorktree: input.useWorktree,
108745
+ wantPr: input.wantPr,
108746
+ wantFixCi: input.wantFixCi,
108747
+ wantAutoMerge: input.wantAutoMerge,
108748
+ wantValidateOnly: input.wantValidateOnly,
108749
+ cfg: {
108750
+ teardownScript: cfg.teardownScript ?? null,
108751
+ prBaseBranch: cfg.prBaseBranch,
108752
+ autoMergeStrategy: cfg.autoMergeStrategy,
108753
+ maxCiFixAttempts: cfg.maxCiFixAttempts,
108754
+ ciPollIntervalSeconds: cfg.ciPollIntervalSeconds,
108755
+ cleanupWorktreeOnSuccess: cfg.cleanupWorktreeOnSuccess,
108756
+ ignoreCiChecks: cfg.ignoreCiChecks,
108757
+ stackPrsOnDependencies: args.stackPrs || cfg.stackPrsOnDependencies,
108758
+ neverTouch: cfg.boundaries.never_touch,
108759
+ metaOnlyFiles: cfg.boundaries.meta_only_files,
108760
+ finalizeNoOpAsDone: cfg.finalizeNoOpAsDone,
108761
+ manualMergeWhenAutoMergeDisabled: cfg.manualMergeWhenAutoMergeDisabled,
108762
+ prDraft: cfg.prDraft,
108763
+ validateCommands: [cfg.commands.test, cfg.commands.lint, cfg.commands.typecheck].filter((c) => Boolean(c))
108764
+ },
108765
+ respawnWorker: input.respawnWorker
108766
+ };
108767
+ }
108623
108768
  function createSpawnWorker(input) {
108624
108769
  const {
108625
108770
  args,
@@ -108652,54 +108797,8 @@ function createSpawnWorker(input) {
108652
108797
  onWorkerOutput,
108653
108798
  onWorkerCmd
108654
108799
  } = input;
108655
- function buildTaskCmdFor(changeName) {
108656
- const engine = args.engineSet ? args.engine : cfg.engine;
108657
- const model = args.engineSet ? args.model : cfg.model;
108658
- const c = [
108659
- process.execPath,
108660
- process.argv[1] ?? "",
108661
- "loop",
108662
- "task",
108663
- "--name",
108664
- changeName,
108665
- "--" + engine,
108666
- "--model",
108667
- model
108668
- ];
108669
- const maxIter = args.maxIterations || cfg.maxIterationsPerTask;
108670
- if (maxIter > 0)
108671
- c.push("--max-iterations", String(maxIter));
108672
- const maxCost = args.maxCostUsd || cfg.maxCostUsdPerTask;
108673
- if (maxCost > 0)
108674
- c.push("--max-cost", String(maxCost));
108675
- const maxRuntime = args.maxRuntimeMinutes || cfg.maxRuntimeMinutesPerTask;
108676
- if (maxRuntime > 0)
108677
- c.push("--max-runtime", String(maxRuntime));
108678
- const maxFailures = args.maxConsecutiveFailures !== 5 ? args.maxConsecutiveFailures : cfg.maxConsecutiveFailuresPerTask;
108679
- if (maxFailures !== 5)
108680
- c.push("--max-failures", String(maxFailures));
108681
- const delay2 = args.delay || cfg.iterationDelaySeconds;
108682
- if (delay2 > 0)
108683
- c.push("--delay", String(delay2));
108684
- if (args.log || cfg.logRawStream)
108685
- c.push("--log");
108686
- if (args.verbose || cfg.taskVerbose)
108687
- c.push("--verbose");
108688
- if (args.manualTest || cfg.enableManualTest)
108689
- c.push("--manual-test");
108690
- const rp = cfg.openspec.reviewPhase;
108691
- if (rp.enabled) {
108692
- c.push("--review-enabled");
108693
- if (rp.maxRounds !== 1)
108694
- c.push("--review-max-rounds", String(rp.maxRounds));
108695
- if (rp.reviewerModel !== undefined)
108696
- c.push("--review-model", rp.reviewerModel);
108697
- if (rp.reviewerContextStrategy !== "fresh")
108698
- c.push("--review-context-strategy", rp.reviewerContextStrategy);
108699
- }
108700
- c.push("--from-agent");
108701
- return c;
108702
- }
108800
+ const doPostTask = input.runners?.runPostTask ?? runPostTask;
108801
+ const buildTaskCmdFor = (changeName) => buildTaskCmd(args, cfg, changeName);
108703
108802
  const retroSeen = new Set;
108704
108803
  const runRetrospectiveHook = async (info) => {
108705
108804
  try {
@@ -108777,7 +108876,7 @@ function createSpawnWorker(input) {
108777
108876
  const workerLayout = projectLayout(cwd2);
108778
108877
  const validateSpecPath = join33(workerLayout.changeDir(changeName), "specs", "validate.md");
108779
108878
  const hasValidateSpec = await Bun.file(validateSpecPath).exists();
108780
- const wantValidateOnly = hasValidateSpec && !wantPrBase;
108879
+ const wantValidateOnly = computeWantValidateOnly(hasValidateSpec, wantPrBase);
108781
108880
  if (hasValidateSpec) {
108782
108881
  try {
108783
108882
  const stateFile = workerLayout.stateFile(changeName);
@@ -108821,10 +108920,10 @@ function createSpawnWorker(input) {
108821
108920
  } catch (err) {
108822
108921
  diag("tasks", `! tasks.md normalization failed: ${err.message}`, "yellow");
108823
108922
  }
108824
- const wantPr = wantPrBase && !awaitingChangeSet.has(changeName) && !(coordRef.current?.isAwaitingConfirmation(changeName) ?? false);
108825
- const effectiveCode = await runPostTask({
108826
- ...trigger ? { mode: trigger } : {},
108827
- ...prByChange?.get(changeName) ? { prUrl: prByChange.get(changeName) } : {},
108923
+ const wantPr = computeWantPr(wantPrBase, awaitingChangeSet.has(changeName), coordRef.current?.isAwaitingConfirmation(changeName) ?? false);
108924
+ const effectiveCode = await doPostTask(buildPostTaskInput({
108925
+ args,
108926
+ cfg,
108828
108927
  changeName,
108829
108928
  cwd: cwd2,
108830
108929
  projectRoot,
@@ -108838,24 +108937,10 @@ function createSpawnWorker(input) {
108838
108937
  wantFixCi,
108839
108938
  wantAutoMerge,
108840
108939
  wantValidateOnly,
108841
- cfg: {
108842
- teardownScript: cfg.teardownScript ?? null,
108843
- prBaseBranch: cfg.prBaseBranch,
108844
- autoMergeStrategy: cfg.autoMergeStrategy,
108845
- maxCiFixAttempts: cfg.maxCiFixAttempts,
108846
- ciPollIntervalSeconds: cfg.ciPollIntervalSeconds,
108847
- cleanupWorktreeOnSuccess: cfg.cleanupWorktreeOnSuccess,
108848
- ignoreCiChecks: cfg.ignoreCiChecks,
108849
- stackPrsOnDependencies: args.stackPrs || cfg.stackPrsOnDependencies,
108850
- neverTouch: cfg.boundaries.never_touch,
108851
- metaOnlyFiles: cfg.boundaries.meta_only_files,
108852
- finalizeNoOpAsDone: cfg.finalizeNoOpAsDone,
108853
- manualMergeWhenAutoMergeDisabled: cfg.manualMergeWhenAutoMergeDisabled,
108854
- prDraft: cfg.prDraft,
108855
- validateCommands: [cfg.commands.test, cfg.commands.lint, cfg.commands.typecheck].filter((c) => Boolean(c))
108856
- },
108940
+ ...trigger ? { trigger } : {},
108941
+ ...prByChange?.get(changeName) ? { prUrl: prByChange.get(changeName) } : {},
108857
108942
  respawnWorker: respawn
108858
- }, {
108943
+ }), {
108859
108944
  cmd: tracedCmd,
108860
108945
  git: gitRunner,
108861
108946
  log: onLog,
@@ -108894,10 +108979,7 @@ function createSpawnWorker(input) {
108894
108979
  },
108895
108980
  resolveDependencyBaseBranch: (issue2) => resolveDependencyBaseBranchImpl(issue2, tracedCmd, cwd2, { apiKey, onLog })
108896
108981
  });
108897
- cwdByChange.delete(changeName);
108898
- statesDirByChange.delete(changeName);
108899
- branchByChange.delete(changeName);
108900
- issueByChange.delete(changeName);
108982
+ releaseWorkerMaps({ cwdByChange, statesDirByChange, branchByChange, issueByChange }, changeName);
108901
108983
  onWorkerExited(changeName);
108902
108984
  return effectiveCode;
108903
108985
  });
@@ -261725,6 +261807,25 @@ async function readSpecAttachmentsSubtree(statePath) {
261725
261807
  const sidecar = await readSlotSidecar(dirname13(statePath), "specAttachments");
261726
261808
  return sidecar ?? await readInlineSpecAttachments(statePath);
261727
261809
  }
261810
+ function asRevisions(value) {
261811
+ if (!Array.isArray(value))
261812
+ return [];
261813
+ const out = [];
261814
+ for (const entry of value) {
261815
+ if (entry && typeof entry === "object" && !Array.isArray(entry)) {
261816
+ const e = entry;
261817
+ if (typeof e.version === "number" && typeof e.attachmentId === "string" && typeof e.sha256 === "string" && typeof e.trigger === "string") {
261818
+ out.push({
261819
+ version: e.version,
261820
+ attachmentId: e.attachmentId,
261821
+ sha256: e.sha256,
261822
+ trigger: e.trigger
261823
+ });
261824
+ }
261825
+ }
261826
+ }
261827
+ return out;
261828
+ }
261728
261829
  async function readSpecAttachments(statePath) {
261729
261830
  const sa = await readSpecAttachmentsSubtree(statePath);
261730
261831
  return {
@@ -261743,12 +261844,45 @@ async function readSpecAttachments(statePath) {
261743
261844
  designPdf: {
261744
261845
  attachmentId: sa.designPdf?.attachmentId ?? null,
261745
261846
  sha256: sa.designPdf?.sha256 ?? null
261746
- }
261847
+ },
261848
+ designRevisions: asRevisions(sa.designRevisions),
261849
+ designPdfRevisions: asRevisions(sa.designPdfRevisions)
261747
261850
  };
261748
261851
  }
261749
261852
  async function persistSlot(statePath, slot, value) {
261750
261853
  await writeField(stateDirOf(statePath), "linear-attachments", `specAttachments.${slot}`, value);
261751
261854
  }
261855
+ async function persistRevision(statePath, slot, revisions) {
261856
+ await writeField(stateDirOf(statePath), "linear-attachments", `specAttachments.${REVISIONS_KEY[slot]}`, revisions);
261857
+ }
261858
+ async function isDesignSealed(stateDir) {
261859
+ try {
261860
+ const pr2 = await readSlotSidecar(stateDir, "pr");
261861
+ const url2 = pr2?.url;
261862
+ if (typeof url2 === "string" && url2.length > 0)
261863
+ return true;
261864
+ } catch {}
261865
+ try {
261866
+ const confirmation = await readSlotSidecar(stateDir, "confirmation");
261867
+ if (confirmation?.earlyDraftPrAt != null)
261868
+ return true;
261869
+ } catch {}
261870
+ return false;
261871
+ }
261872
+ async function resolveTriggerLabel(stateDir) {
261873
+ try {
261874
+ const flow2 = await readSlotSidecar(stateDir, "flow");
261875
+ const snapshot = flow2?.actorSnapshot;
261876
+ const value = snapshot?.value;
261877
+ if (typeof value === "string" && TRIGGER_LABELS[value])
261878
+ return TRIGGER_LABELS[value];
261879
+ } catch {}
261880
+ return "revision";
261881
+ }
261882
+ function versionedTitle(slot, n, label) {
261883
+ const base2 = `Ralph design #${n} (${label})`;
261884
+ return slot === "designPdf" ? `${base2} (PDF)` : base2;
261885
+ }
261752
261886
  async function adopt(deps, slot) {
261753
261887
  const spec = SLOT_SPECS[slot];
261754
261888
  try {
@@ -261793,6 +261927,65 @@ function extractImplementationSection(tasksMarkdown) {
261793
261927
  return captured.join(`
261794
261928
  `).trim();
261795
261929
  }
261930
+ async function syncSlotSealed(deps, slot, sourceBytes, hash2, state) {
261931
+ const spec = SLOT_SPECS[slot];
261932
+ const revisions = state[REVISIONS_KEY[slot]];
261933
+ const v1Sha = state[slot]?.sha256 ?? null;
261934
+ if (hash2 === v1Sha || revisions.some((r) => r.sha256 === hash2)) {
261935
+ deps.log(` spec-attachments: ${spec.uploadFilename} unchanged (sealed), skipping`, "gray");
261936
+ return;
261937
+ }
261938
+ const n = 2 + revisions.length;
261939
+ const label = await resolveTriggerLabel(stateDirOf(deps.statePath));
261940
+ const title = versionedTitle(slot, n, label);
261941
+ let uploadBytes;
261942
+ try {
261943
+ uploadBytes = await spec.renderBytes(sourceBytes);
261944
+ } catch (err) {
261945
+ deps.log(`! spec-attachments: render ${spec.uploadFilename} (sealed) failed: ${err.message}`, "yellow");
261946
+ return;
261947
+ }
261948
+ let assetUrl;
261949
+ try {
261950
+ const uploaded = await deps.mutations.uploadFileToLinear(deps.apiKey, {
261951
+ filename: spec.uploadFilename,
261952
+ contentType: spec.contentType,
261953
+ bytes: uploadBytes
261954
+ });
261955
+ assetUrl = uploaded.assetUrl;
261956
+ } catch (err) {
261957
+ deps.log(`! spec-attachments: upload ${spec.uploadFilename} (sealed) failed: ${describeLinearError(err)}`, "yellow");
261958
+ return;
261959
+ }
261960
+ let attachmentId = null;
261961
+ try {
261962
+ attachmentId = await deps.mutations.findIssueAttachmentByTitle(deps.apiKey, deps.issueId, title);
261963
+ if (attachmentId) {
261964
+ deps.log(` spec-attachments: adopted existing ${title} attachment ${attachmentId}`, "gray");
261965
+ }
261966
+ } catch (err) {
261967
+ deps.log(`! spec-attachments: findIssueAttachmentByTitle ${title} failed (treating as no match): ${describeLinearError(err)}`, "yellow");
261968
+ attachmentId = null;
261969
+ }
261970
+ if (!attachmentId) {
261971
+ try {
261972
+ attachmentId = await deps.mutations.createAttachmentForUrl(deps.apiKey, {
261973
+ issueId: deps.issueId,
261974
+ url: assetUrl,
261975
+ title,
261976
+ subtitle: `iteration ${deps.iteration}`
261977
+ });
261978
+ } catch (err) {
261979
+ deps.log(`! spec-attachments: createAttachmentForUrl ${title} failed: ${describeLinearError(err)}`, "yellow");
261980
+ return;
261981
+ }
261982
+ deps.log(` spec-attachments: created ${title} attachment`, "gray");
261983
+ }
261984
+ await persistRevision(deps.statePath, slot, [
261985
+ ...revisions,
261986
+ { version: n, attachmentId, sha256: hash2, trigger: label }
261987
+ ]);
261988
+ }
261796
261989
  async function syncSlot(deps, slot) {
261797
261990
  const spec = SLOT_SPECS[slot];
261798
261991
  const [primaryName, ...trailingNames] = spec.sourceFiles;
@@ -261848,7 +262041,12 @@ ${body}
261848
262041
  offset += p.length;
261849
262042
  }
261850
262043
  const hash2 = sha256Hex(sourceBytes);
261851
- let current = (await readSpecAttachments(deps.statePath))[slot] ?? EMPTY_SLOT;
262044
+ const state = await readSpecAttachments(deps.statePath);
262045
+ if (await isDesignSealed(stateDirOf(deps.statePath))) {
262046
+ await syncSlotSealed(deps, slot, sourceBytes, hash2, state);
262047
+ return;
262048
+ }
262049
+ let current = state[slot] ?? EMPTY_SLOT;
261852
262050
  if (!current.attachmentId) {
261853
262051
  const { adoptedId } = await adopt(deps, slot);
261854
262052
  if (adoptedId) {
@@ -261950,7 +262148,7 @@ async function syncSpecAttachments(deps) {
261950
262148
  await syncSlot(deps, slot);
261951
262149
  }
261952
262150
  }
261953
- var identityRender = async (b2) => b2, pdfRender = (title) => async (b2) => renderMarkdownToPdf(new TextDecoder().decode(b2), title), SLOT_SPECS, LEGACY_SLOT_TITLES, EMPTY_SLOT;
262151
+ var identityRender = async (b2) => b2, pdfRender = (title) => async (b2) => renderMarkdownToPdf(new TextDecoder().decode(b2), title), SLOT_SPECS, LEGACY_SLOT_TITLES, REVISIONS_KEY, EMPTY_SLOT, TRIGGER_LABELS;
261954
262152
  var init_spec_attachments = __esm(() => {
261955
262153
  init_store();
261956
262154
  init_comment_sync();
@@ -261977,7 +262175,16 @@ var init_spec_attachments = __esm(() => {
261977
262175
  proposal: "Ralph proposal",
261978
262176
  proposalPdf: "Ralph proposal (PDF)"
261979
262177
  };
262178
+ REVISIONS_KEY = {
262179
+ design: "designRevisions",
262180
+ designPdf: "designPdfRevisions"
262181
+ };
261980
262182
  EMPTY_SLOT = { attachmentId: null, sha256: null };
262183
+ TRIGGER_LABELS = {
262184
+ review: "review follow-up",
262185
+ "ci-fix": "CI fix",
262186
+ "conflict-fix": "conflict fix"
262187
+ };
261981
262188
  });
261982
262189
 
261983
262190
  // apps/agent/src/agent/wire/comment-sync.ts
@@ -262002,7 +262209,7 @@ function createCommentSyncHooks(input) {
262002
262209
  return {
262003
262210
  enabled: enabled2,
262004
262211
  syncTasks: async (worker, iteration) => {
262005
- const root = cwdByChange.get(worker.changeName) ?? projectRoot;
262212
+ const root = worker.cwd ?? cwdByChange.get(worker.changeName) ?? projectRoot;
262006
262213
  const layout = projectLayout(root);
262007
262214
  const changeDir = layout.changeDir(worker.changeName);
262008
262215
  const statePath = layout.stateFile(worker.changeName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "3.10.10",
3
+ "version": "3.10.12",
4
4
  "description": "An iterative AI task execution framework. Orchestrates multi-phase autonomous work using Claude or Codex engines.",
5
5
  "keywords": [
6
6
  "agent",