@schoolai/shipyard 3.34.0 → 3.34.1-rc.20260924.0

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getDaemonVersion
4
- } from "./chunk-O7PCPQ5T.js";
4
+ } from "./chunk-ZZHFRLKK.js";
5
5
  import {
6
6
  WorkerCommandCodec,
7
7
  WorkerReplyCodec
@@ -1116,4 +1116,4 @@ export {
1116
1116
  shutdownFileWatcherGuard,
1117
1117
  guardedSubscribe
1118
1118
  };
1119
- //# sourceMappingURL=chunk-ZGLXU6XJ.js.map
1119
+ //# sourceMappingURL=chunk-CRS3BMH4.js.map
@@ -18,8 +18,8 @@ function getDaemonVersion() {
18
18
  return cached;
19
19
  }
20
20
  function readDaemonVersion() {
21
- if ("3.34.0".length > 0) {
22
- return "3.34.0";
21
+ if ("3.34.1".length > 0) {
22
+ return "3.34.1";
23
23
  }
24
24
  try {
25
25
  const here = dirname(fileURLToPath(import.meta.url));
@@ -48,4 +48,4 @@ function readDaemonVersion() {
48
48
  export {
49
49
  getDaemonVersion
50
50
  };
51
- //# sourceMappingURL=chunk-O7PCPQ5T.js.map
51
+ //# sourceMappingURL=chunk-ZZHFRLKK.js.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getDaemonVersion
4
- } from "./chunk-O7PCPQ5T.js";
4
+ } from "./chunk-ZZHFRLKK.js";
5
5
  import {
6
6
  enableNativeCrashReports
7
7
  } from "./chunk-CTVKFKRG.js";
@@ -71,7 +71,7 @@ async function main() {
71
71
  await loadAuthFromConfig(env);
72
72
  crumb("post-auth-load");
73
73
  crumb("pre-serve-import");
74
- const { serve } = await import("./serve-NWLZH6MD.js");
74
+ const { serve } = await import("./serve-ZUI52LNF.js");
75
75
  crumb("post-serve-import");
76
76
  const portQueue = [];
77
77
  let acceptor = null;
package/dist/index.js CHANGED
@@ -137,7 +137,7 @@ async function handleSubcommand() {
137
137
  return true;
138
138
  }
139
139
  if (subcommand === "start") {
140
- const { startCommand } = await import("./start-DCG37IQJ.js");
140
+ const { startCommand } = await import("./start-2FKA6COR.js");
141
141
  await startCommand();
142
142
  return true;
143
143
  }
@@ -159,7 +159,7 @@ async function main() {
159
159
  const args = parseCliArgs();
160
160
  if (args.serve) {
161
161
  await loadAuthFromConfig(env);
162
- const { serve } = await import("./serve-NWLZH6MD.js");
162
+ const { serve } = await import("./serve-ZUI52LNF.js");
163
163
  return serve({ isDev: env.SHIPYARD_DEV });
164
164
  }
165
165
  logger.error("Use `shipyard start` to run the daemon. Use --help for usage.");
@@ -12,7 +12,7 @@ import {
12
12
  guardedSubscribe,
13
13
  makeInitialAttemptState,
14
14
  shutdownFileWatcherGuard
15
- } from "./chunk-ZGLXU6XJ.js";
15
+ } from "./chunk-CRS3BMH4.js";
16
16
  import {
17
17
  TRAILER_SCHEMA_VERSION,
18
18
  appendTrailerToMessage,
@@ -46,7 +46,7 @@ import {
46
46
  } from "./chunk-T5J6HZOQ.js";
47
47
  import {
48
48
  getDaemonVersion
49
- } from "./chunk-O7PCPQ5T.js";
49
+ } from "./chunk-ZZHFRLKK.js";
50
50
  import {
51
51
  collectPriorCrashDetail,
52
52
  getRecentWasmPanicMessages
@@ -6333,7 +6333,7 @@ function nanoid(size = 21) {
6333
6333
  }
6334
6334
 
6335
6335
  // src/services/bootstrap/signaling.ts
6336
- var DAEMON_NPM_VERSION = true ? "3.34.0" : "unknown";
6336
+ var DAEMON_NPM_VERSION = true ? "3.34.1" : "unknown";
6337
6337
  function createDaemonSignaling(config) {
6338
6338
  const agentId = config.agentId ?? nanoid();
6339
6339
  function send(msg) {
@@ -80980,14 +80980,41 @@ function buildDevinTaskSubprocess(options) {
80980
80980
  }
80981
80981
 
80982
80982
  // src/services/serve-factory/rate-limit-account-switch.ts
80983
- function attributeRateLimitEvents(onEvent, spawnAccountKey) {
80983
+ function attributeRateLimitEvents(onEvent, spawnAccountKey, ctx) {
80984
80984
  if (spawnAccountKey === null) return onEvent;
80985
+ const isStale = () => {
80986
+ if (ctx === void 0) return false;
80987
+ const current = ctx.getCurrentAccountKey();
80988
+ if (spawnAccountKey !== null && current !== null && spawnAccountKey !== current) {
80989
+ return true;
80990
+ }
80991
+ const lastSwitchAt = ctx.getLastAccountSwitchAtMs();
80992
+ return lastSwitchAt !== null && ctx.spawnedAtMs < lastSwitchAt;
80993
+ };
80985
80994
  return (event) => {
80986
- if (event.type !== "rate_limit" || event.accountKey !== void 0) {
80987
- onEvent(event);
80995
+ if (event.type === "rate_limit") {
80996
+ const stale = isStale();
80997
+ if (event.accountKey !== void 0 && !stale) {
80998
+ onEvent(event);
80999
+ return;
81000
+ }
81001
+ onEvent({
81002
+ ...event,
81003
+ accountKey: event.accountKey ?? spawnAccountKey,
81004
+ ...stale ? { spawnAccountStale: true } : {}
81005
+ });
80988
81006
  return;
80989
81007
  }
80990
- onEvent({ ...event, accountKey: spawnAccountKey });
81008
+ if (event.type === "rate_limit_error") {
81009
+ const stale = isStale();
81010
+ if (!stale) {
81011
+ onEvent(event);
81012
+ return;
81013
+ }
81014
+ onEvent({ ...event, spawnAccountStale: true });
81015
+ return;
81016
+ }
81017
+ onEvent(event);
80991
81018
  };
80992
81019
  }
80993
81020
  function planClaudeAccountTransition(lastKey, next) {
@@ -81020,12 +81047,15 @@ function applyClaudeAccountSwitch(deps, switched) {
81020
81047
  });
81021
81048
  }
81022
81049
  deps.rewarmUnderNewAccount();
81050
+ const retired = deps.retireStaleClaudeLanes();
81023
81051
  deps.log({
81024
81052
  event: "claude_account_switched",
81025
81053
  from: switched.from,
81026
81054
  to: switched.to,
81027
81055
  clearedTaskIds,
81028
- newAccountHasRateLimitRecord: record2 !== void 0
81056
+ newAccountHasRateLimitRecord: record2 !== void 0,
81057
+ retiredLanesStopped: retired.stopped,
81058
+ retiredLanesDeferred: retired.deferred
81029
81059
  });
81030
81060
  }
81031
81061
 
@@ -91955,6 +91985,18 @@ var AgentSessionManager = class {
91955
91985
  */
91956
91986
  #expectedShutdown = false;
91957
91987
  #configReloadPending = false;
91988
+ /**
91989
+ * One-shot intent: retire this lane's subprocess for a Claude account
91990
+ * switch as soon as it reaches quiescent warm_idle. Set only by
91991
+ * `prepareAccountReload()` when the lane is mid-turn — aborting a running
91992
+ * turn would lose work, so the retirement is deferred until the turn
91993
+ * completes (the stale-account respawn in RateLimitFlapController is the
91994
+ * backstop if the old account saturates first). Cleared wherever the
91995
+ * pending retirement becomes moot: `stop()`, `dispose()`, subprocess
91996
+ * death, and any fresh `spawn` effect (a new generation already
91997
+ * authenticates under the new account).
91998
+ */
91999
+ #accountReloadPending = false;
91958
92000
  /**
91959
92001
  * True while a Cursor tool call is in flight (between `tool_execution_started`
91960
92002
  * and `tool_execution_settled` FSM events). When true, `#chooseStallTimeoutMs`
@@ -92095,6 +92137,7 @@ var AgentSessionManager = class {
92095
92137
  stop() {
92096
92138
  if (this.#configReloadPending) this.#queue.length = 0;
92097
92139
  this.#configReloadPending = false;
92140
+ this.#accountReloadPending = false;
92098
92141
  this.#dispatch({ type: "stop_commanded" });
92099
92142
  }
92100
92143
  /** Preserve queued follow-up input across a configuration-only shutdown. */
@@ -92104,6 +92147,38 @@ var AgentSessionManager = class {
92104
92147
  this.#dispatch({ type: "stop_commanded" });
92105
92148
  return true;
92106
92149
  }
92150
+ /**
92151
+ * Retire the live subprocess because the canonical Claude account
92152
+ * changed underneath it. A `warm_idle` process is parked holding the OLD
92153
+ * account's token — stopping it now costs only a resume spawn on the
92154
+ * next user message, so it goes down immediately via the existing
92155
+ * config-reload teardown (queue preserved). A `running`/`spawning`/
92156
+ * `stopping` lane keeps its turn — aborting would lose work — and is
92157
+ * stopped on reaching quiescent warm_idle via `#accountReloadPending`.
92158
+ * No live process (`cold_idle`/`resumable_idle`) means nothing to do.
92159
+ */
92160
+ prepareAccountReload() {
92161
+ if (this.#state === "warm_idle" && this.#pendingInputs.size === 0) {
92162
+ const from = this.#state;
92163
+ this.prepareConfigReload();
92164
+ this.#config.log({
92165
+ event: "session_account_reload_stopped",
92166
+ taskId: this.#config.taskId,
92167
+ state: from
92168
+ });
92169
+ return "stopped";
92170
+ }
92171
+ if (this.#state === "running" || this.#state === "spawning" || this.#state === "stopping" || this.#state === "warm_idle") {
92172
+ this.#accountReloadPending = true;
92173
+ this.#config.log({
92174
+ event: "session_account_reload_deferred",
92175
+ taskId: this.#config.taskId,
92176
+ state: this.#state
92177
+ });
92178
+ return "deferred";
92179
+ }
92180
+ return "noop";
92181
+ }
92107
92182
  rewind(resumeAtMessageId) {
92108
92183
  this.#dispatch({ type: "rewind_requested", resumeAtMessageId });
92109
92184
  }
@@ -92184,6 +92259,7 @@ var AgentSessionManager = class {
92184
92259
  this.#queue = [];
92185
92260
  this.#pendingInputs.clear();
92186
92261
  this.#eventQueue = [];
92262
+ this.#accountReloadPending = false;
92187
92263
  }
92188
92264
  /**
92189
92265
  * Trace the rewind fork target's whole lifetime: SET when a rewind records
@@ -92269,6 +92345,17 @@ var AgentSessionManager = class {
92269
92345
  this.#configReloadPending = false;
92270
92346
  if (this.#queue.length > 0) this.#dispatch({ type: "user_message" });
92271
92347
  }
92348
+ if (this.#accountReloadPending && this.#state === "warm_idle" && this.#pendingInputs.size === 0) {
92349
+ this.#accountReloadPending = false;
92350
+ this.prepareConfigReload();
92351
+ this.#config.log({
92352
+ event: "session_account_reload_stopped",
92353
+ taskId: this.#config.taskId,
92354
+ state: "warm_idle",
92355
+ deferred: true
92356
+ });
92357
+ }
92358
+ if (event.type === "subprocess_died") this.#accountReloadPending = false;
92272
92359
  }
92273
92360
  #applyEffects(effects) {
92274
92361
  for (const effect of effects) {
@@ -92279,6 +92366,7 @@ var AgentSessionManager = class {
92279
92366
  this.#firstStepReceived = false;
92280
92367
  this.#lastStreamActivityAt = null;
92281
92368
  this.#toolExecutionInFlight = false;
92369
+ this.#accountReloadPending = false;
92282
92370
  this.#config.onSpawn(effect.reason, initial?.content ?? []);
92283
92371
  break;
92284
92372
  }
@@ -92833,6 +92921,8 @@ var CursorRecoveryController = class {
92833
92921
  // src/services/conversation/rate-limit-flap-controller.ts
92834
92922
  var MAX_RATE_LIMIT_FLAP_RETRIES = 1;
92835
92923
  var RATE_LIMIT_FLAP_RETRY_DELAY_MS = 8e3;
92924
+ var MAX_STALE_ACCOUNT_RESPAWNS = 1;
92925
+ var STALE_ACCOUNT_RESPAWN_DELAY_MS = 1e3;
92836
92926
  function isLikelyAnthropicFlap(info, nowMs = Date.now()) {
92837
92927
  if (info === void 0) return false;
92838
92928
  if (info.provider !== void 0 && info.provider !== "anthropic") return false;
@@ -92885,6 +92975,13 @@ var RateLimitFlapController = class {
92885
92975
  * sent something fresh, that turn deserves its own retry budget).
92886
92976
  */
92887
92977
  #flapRetries = 0;
92978
+ /**
92979
+ * Consecutive stale-account respawns this turn. Independent budget from
92980
+ * `#flapRetries`: the stale path answers "the emitting process predates
92981
+ * the current account", not "Anthropic returned an implausible
92982
+ * rejection". Reset alongside it.
92983
+ */
92984
+ #staleAccountRespawns = 0;
92888
92985
  /**
92889
92986
  * Cached most recent `rate_limit` info-event payload. The flap
92890
92987
  * discriminator runs at `rate_limit_error` time but the discriminating
@@ -92912,6 +93009,7 @@ var RateLimitFlapController = class {
92912
93009
  resetForNewTurn() {
92913
93010
  this.cancelPendingRetry();
92914
93011
  this.#flapRetries = 0;
93012
+ this.#staleAccountRespawns = 0;
92915
93013
  this.#lastInfo = void 0;
92916
93014
  }
92917
93015
  /**
@@ -92935,15 +93033,17 @@ var RateLimitFlapController = class {
92935
93033
  * exhausts into the surfaced banner, not a silent loop. #3932.
92936
93034
  */
92937
93035
  noteRecoveredOnTurnComplete(producedOutput) {
92938
- if (this.#flapRetries === 0) return;
93036
+ if (this.#flapRetries === 0 && this.#staleAccountRespawns === 0) return;
92939
93037
  if (!producedOutput) return;
92940
93038
  this.cancelPendingRetry();
92941
93039
  this.#deps.log({
92942
93040
  event: "thread_usage_limit_flap_recovered",
92943
93041
  threadId: this.#deps.threadId,
92944
- retries: this.#flapRetries
93042
+ retries: this.#flapRetries,
93043
+ staleAccountRespawns: this.#staleAccountRespawns
92945
93044
  });
92946
93045
  this.#flapRetries = 0;
93046
+ this.#staleAccountRespawns = 0;
92947
93047
  }
92948
93048
  handleEvent(event) {
92949
93049
  if (event.type === "rate_limit" && event.info.status === "rejected") {
@@ -92952,6 +93052,22 @@ var RateLimitFlapController = class {
92952
93052
  if (this.#timer !== null) return;
92953
93053
  const isBlocking = event.type === "rate_limit_error" || event.info.status === "rejected" && event.info.isUsingOverage !== true;
92954
93054
  if (!isBlocking) return;
93055
+ if (event.spawnAccountStale === true && this.#deps.getState() === "running" && this.#staleAccountRespawns < MAX_STALE_ACCOUNT_RESPAWNS) {
93056
+ this.#staleAccountRespawns++;
93057
+ this.#deps.log({
93058
+ event: "thread_usage_limit_stale_account_respawn",
93059
+ threadId: this.#deps.threadId,
93060
+ eventType: event.type,
93061
+ retries: this.#staleAccountRespawns
93062
+ });
93063
+ this.#deps.notifyAbortRetry();
93064
+ this.#timer = setTimeout(() => {
93065
+ this.#timer = null;
93066
+ if (this.#deps.isDisposed()) return;
93067
+ this.#deps.respawnForResume();
93068
+ }, STALE_ACCOUNT_RESPAWN_DELAY_MS);
93069
+ return;
93070
+ }
92955
93071
  const flapInfo = event.type === "rate_limit" ? event.info : this.#lastInfo;
92956
93072
  if (this.#deps.getState() === "running" && this.#flapRetries < MAX_RATE_LIMIT_FLAP_RETRIES && isLikelyAnthropicFlap(flapInfo)) {
92957
93073
  this.#flapRetries++;
@@ -96342,6 +96458,17 @@ var Thread = class {
96342
96458
  if (this.#agentSystem !== "devin") return false;
96343
96459
  return this.#manager.prepareConfigReload();
96344
96460
  }
96461
+ /**
96462
+ * Retire this lane's subprocess after a Claude account switch: a parked
96463
+ * warm_idle process holds the old account's token, so it is stopped now
96464
+ * (the next message respawns under the new credential); a running lane
96465
+ * keeps its turn and is retired on reaching warm_idle. Non-Claude lanes
96466
+ * are unaffected by a Claude credential swap.
96467
+ */
96468
+ retireSubprocessForAccountSwitch() {
96469
+ if (this.#agentSystem !== "claude-code") return "noop";
96470
+ return this.#manager.prepareAccountReload();
96471
+ }
96345
96472
  async reconnectMcpServer(serverName) {
96346
96473
  if (!this.#subprocess) return;
96347
96474
  await this.#subprocess.reconnectMcpServer(serverName);
@@ -102972,6 +103099,15 @@ function handleMainThreadEvent(ctx, event) {
102972
103099
  ctx.onAuthNotLoggedIn(ctx.getRuntimeId());
102973
103100
  break;
102974
103101
  case "rate_limit":
103102
+ if (event.spawnAccountStale === true) {
103103
+ ctx.log({
103104
+ event: "rate_limit_event_dropped_stale_account",
103105
+ taskId: ctx.taskId,
103106
+ spawnAccountKey: event.accountKey ?? null,
103107
+ status: event.info.status
103108
+ });
103109
+ break;
103110
+ }
102975
103111
  ctx.onRateLimitEvent(event.info, event.accountKey ?? null);
102976
103112
  break;
102977
103113
  case "billing_error":
@@ -108459,6 +108595,23 @@ var Task = class {
108459
108595
  get mainThread() {
108460
108596
  return this.#getPrimary();
108461
108597
  }
108598
+ /**
108599
+ * Retire every Claude lane in this task after a canonical-account switch:
108600
+ * the primary thread plus every side thread. Warm-idle lanes stop now
108601
+ * (`stopped`); mid-turn lanes defer until they reach warm_idle
108602
+ * (`deferred`). Non-Claude and process-free lanes are `noop`.
108603
+ */
108604
+ retireClaudeSubprocessesForAccountSwitch() {
108605
+ const tally = { stopped: 0, deferred: 0 };
108606
+ const apply = (thread) => {
108607
+ const result = thread.retireSubprocessForAccountSwitch();
108608
+ if (result === "stopped") tally.stopped++;
108609
+ else if (result === "deferred") tally.deferred++;
108610
+ };
108611
+ apply(this.#getPrimary());
108612
+ this.#propagateToSideThreads(apply, "retire_for_account_switch_error");
108613
+ return tally;
108614
+ }
108462
108615
  /**
108463
108616
  * Cancel the sliding-window timer for `uri` and trigger an immediate flush.
108464
108617
  * User-initiated path from `flush_resource_batch` control-channel message.
@@ -111660,6 +111813,20 @@ var TaskManager = class {
111660
111813
  }
111661
111814
  return result;
111662
111815
  }
111816
+ /**
111817
+ * Retire every Claude lane across all managed tasks after a canonical
111818
+ * account switch. Called from `applyClaudeAccountSwitch`; returns the
111819
+ * aggregate counts for the `claude_account_switched` log line.
111820
+ */
111821
+ retireClaudeSubprocessesForAccountSwitch() {
111822
+ const tally = { stopped: 0, deferred: 0 };
111823
+ for (const task of this.#tasks.values()) {
111824
+ const result = task.orchestrator.retireClaudeSubprocessesForAccountSwitch();
111825
+ tally.stopped += result.stopped;
111826
+ tally.deferred += result.deferred;
111827
+ }
111828
+ return tally;
111829
+ }
111663
111830
  /**
111664
111831
  * Browser-initiated flush for a specific resource URI (PROTOCOL_VERSION 48).
111665
111832
  * Resolves the URI to its owning task by extracting the taskId from the
@@ -113944,9 +114111,13 @@ async function createDaemon(deps) {
113944
114111
  const claudeAccountSwitchRef = {
113945
114112
  current: (switched) => deps.log({ event: "claude_account_switch_before_wiring", ...switched })
113946
114113
  };
114114
+ let lastClaudeAccountSwitchAtMs = null;
113947
114115
  const commitCapabilities = createCapabilityWriter(
113948
114116
  capabilitiesRef,
113949
- createClaudeAccountTracker((switched) => claudeAccountSwitchRef.current(switched))
114117
+ createClaudeAccountTracker((switched) => {
114118
+ lastClaudeAccountSwitchAtMs = Date.now();
114119
+ claudeAccountSwitchRef.current(switched);
114120
+ })
113950
114121
  );
113951
114122
  const rateLimitStoreRef = { current: null };
113952
114123
  const gitCheckpoint = createGitCheckpointService();
@@ -114680,9 +114851,24 @@ async function createDaemon(deps) {
114680
114851
  });
114681
114852
  const selectedModel = settings?.model ?? null;
114682
114853
  const profileForModel = selectedModel !== null ? profileRegistry.findByModelId(selectedModel) : null;
114854
+ const spawnProfileId = profileForModel?.id ?? "claude-code";
114683
114855
  const onEvent = attributeRateLimitEvents(
114684
114856
  unattributedOnEvent,
114685
- resolveRateLimitAccountKey(capabilitiesRef.current, profileForModel?.id ?? "claude-code")
114857
+ resolveRateLimitAccountKey(capabilitiesRef.current, spawnProfileId),
114858
+ {
114859
+ /**
114860
+ * Same profile as the spawn key: a non-Claude lane compares against
114861
+ * its own account so cross-profile key shapes never false-flag stale.
114862
+ */
114863
+ getCurrentAccountKey: () => resolveRateLimitAccountKey(capabilitiesRef.current, spawnProfileId),
114864
+ /**
114865
+ * `lastClaudeAccountSwitchAtMs` is Claude-only — gate it on the
114866
+ * spawn profile so a Codex/Cursor lane spawned before a Claude
114867
+ * switch isn't false-flagged stale by the timestamp rule.
114868
+ */
114869
+ getLastAccountSwitchAtMs: () => spawnProfileId === "claude-code" ? lastClaudeAccountSwitchAtMs : null,
114870
+ spawnedAtMs: Date.now()
114871
+ }
114686
114872
  );
114687
114873
  const decision = decideSpawnRoute({
114688
114874
  mode,
@@ -115394,6 +115580,7 @@ async function createDaemon(deps) {
115394
115580
  preWarmManager.kill();
115395
115581
  preWarmManager.resetAndRestart();
115396
115582
  },
115583
+ retireStaleClaudeLanes: () => taskManager.retireClaudeSubprocessesForAccountSwitch(),
115397
115584
  log: deps.log
115398
115585
  },
115399
115586
  switched
@@ -119525,4 +119712,4 @@ export {
119525
119712
  decideWorkspaceScope,
119526
119713
  serve
119527
119714
  };
119528
- //# sourceMappingURL=serve-NWLZH6MD.js.map
119715
+ //# sourceMappingURL=serve-ZUI52LNF.js.map