@schoolai/shipyard 3.34.1-rc.20260927.1 → 3.34.1

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.
@@ -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-OP4JVMPM.js");
74
+ const { serve } = await import("./serve-LJYQF75A.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-MOBEORLT.js");
140
+ const { startCommand } = await import("./start-E2YHOYBP.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-OP4JVMPM.js");
162
+ const { serve } = await import("./serve-LJYQF75A.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.");
@@ -25629,6 +25629,30 @@ function resolveUsageLimitResetsAtMs(rateLimitStore, capabilities, profileForSpa
25629
25629
  if (record2.info.isUsingOverage === true) return null;
25630
25630
  return pickSoonestSaturatedReset(record2.info, nowMs);
25631
25631
  }
25632
+ var USAGE_LIMIT_TRUST_WINDOW_MS = 10 * 6e4;
25633
+ var USAGE_LIMIT_PROBE_TIMEOUT_MS = 9e4;
25634
+ function resolveUsageLimitGate(rateLimitStore, capabilities, profileForSpawn = null, nowMs = Date.now(), mode = "claim") {
25635
+ const resetsAt = resolveUsageLimitResetsAtMs(
25636
+ rateLimitStore,
25637
+ capabilities,
25638
+ profileForSpawn,
25639
+ nowMs
25640
+ );
25641
+ const accountKey = resolveRateLimitAccountKey(capabilities, profileForSpawn);
25642
+ if (resetsAt === null || rateLimitStore === null || accountKey === null) {
25643
+ return { resetsAtMs: resetsAt, probe: null, accountKey };
25644
+ }
25645
+ if (rateLimitStore.isRecordTrusted(accountKey, nowMs, USAGE_LIMIT_TRUST_WINDOW_MS)) {
25646
+ return { resetsAtMs: resetsAt, probe: null, accountKey };
25647
+ }
25648
+ if (mode === "observe") {
25649
+ return { resetsAtMs: null, probe: null, accountKey };
25650
+ }
25651
+ if (rateLimitStore.claimUsageLimitProbe(accountKey, nowMs, USAGE_LIMIT_PROBE_TIMEOUT_MS)) {
25652
+ return { resetsAtMs: null, probe: "claimed", accountKey };
25653
+ }
25654
+ return { resetsAtMs: resetsAt, probe: "blocked", accountKey };
25655
+ }
25632
25656
  var SYNTHETIC_REJECTED_WINDOW_MS = 15e3;
25633
25657
  function pickSoonestSaturatedReset(info, nowMs) {
25634
25658
  let candidate = null;
@@ -82440,6 +82464,20 @@ function createClaudeAccountTracker(onSwitched) {
82440
82464
  if (plan.switched !== null) onSwitched(plan.switched);
82441
82465
  };
82442
82466
  }
82467
+ function createAccountKeySwitchTracker(onSwitched) {
82468
+ const lastKeys = /* @__PURE__ */ new Map();
82469
+ return (next) => {
82470
+ for (const profile of AGENT_IDS) {
82471
+ const key = resolveRateLimitAccountKey(next, profile);
82472
+ if (key === null) continue;
82473
+ const last = lastKeys.get(profile);
82474
+ lastKeys.set(profile, key);
82475
+ if (last !== void 0 && last !== key) {
82476
+ onSwitched({ profile, from: last, to: key });
82477
+ }
82478
+ }
82479
+ };
82480
+ }
82443
82481
  function applyClaudeAccountSwitch(deps, switched) {
82444
82482
  const clearedTaskIds = [...deps.lastUsageLimitBroadcastByTask.keys()];
82445
82483
  deps.lastUsageLimitBroadcastByTask.clear();
@@ -91221,6 +91259,8 @@ async function buildRateLimitStore(dataDir, opts) {
91221
91259
  const initial = await loadStoreFile(filePath, opts.logger);
91222
91260
  const records = { ...initial.records };
91223
91261
  const observed = /* @__PURE__ */ new Set();
91262
+ const switchedInUntrusted = /* @__PURE__ */ new Set();
91263
+ const probeInFlight = /* @__PURE__ */ new Map();
91224
91264
  let writeQueue = Promise.resolve();
91225
91265
  function enqueueWrite(accountKey) {
91226
91266
  const attempt = () => withFileLock(lockPath2, async () => {
@@ -91245,8 +91285,25 @@ async function buildRateLimitStore(dataDir, opts) {
91245
91285
  observedSinceLoad(accountKey) {
91246
91286
  return observed.has(accountKey);
91247
91287
  },
91288
+ isRecordTrusted(accountKey, nowMs, trustWindowMs) {
91289
+ const record2 = records[accountKey];
91290
+ if (!record2) return false;
91291
+ if (switchedInUntrusted.has(accountKey)) return false;
91292
+ return nowMs - record2.updatedAt < trustWindowMs;
91293
+ },
91294
+ markAccountSwitchedIn(accountKey) {
91295
+ switchedInUntrusted.add(accountKey);
91296
+ },
91297
+ claimUsageLimitProbe(accountKey, nowMs, probeTimeoutMs) {
91298
+ const started = probeInFlight.get(accountKey);
91299
+ if (started !== void 0 && nowMs - started < probeTimeoutMs) return false;
91300
+ probeInFlight.set(accountKey, nowMs);
91301
+ return true;
91302
+ },
91248
91303
  set(accountKey, info) {
91249
91304
  observed.add(accountKey);
91305
+ switchedInUntrusted.delete(accountKey);
91306
+ probeInFlight.delete(accountKey);
91250
91307
  const record2 = {
91251
91308
  info: normalizeRateLimitResetsToMs(info),
91252
91309
  updatedAt: Date.now()
@@ -91257,6 +91314,8 @@ async function buildRateLimitStore(dataDir, opts) {
91257
91314
  },
91258
91315
  applyEvent(accountKey, info, eventTime) {
91259
91316
  observed.add(accountKey);
91317
+ switchedInUntrusted.delete(accountKey);
91318
+ probeInFlight.delete(accountKey);
91260
91319
  const prev = records[accountKey]?.info;
91261
91320
  const nextInfo = mergeEvent(prev, normalizeRateLimitResetsToMs(info), eventTime);
91262
91321
  const record2 = { info: nextInfo, updatedAt: eventTime };
@@ -91284,6 +91343,8 @@ async function buildRateLimitStore(dataDir, opts) {
91284
91343
  clearForAccount(accountKey) {
91285
91344
  if (!(accountKey in records)) return false;
91286
91345
  observed.delete(accountKey);
91346
+ switchedInUntrusted.delete(accountKey);
91347
+ probeInFlight.delete(accountKey);
91287
91348
  delete records[accountKey];
91288
91349
  enqueueWrite(accountKey);
91289
91350
  return true;
@@ -91293,6 +91354,8 @@ async function buildRateLimitStore(dataDir, opts) {
91293
91354
  delete records[key];
91294
91355
  }
91295
91356
  observed.clear();
91357
+ switchedInUntrusted.clear();
91358
+ probeInFlight.clear();
91296
91359
  enqueueWrite();
91297
91360
  }
91298
91361
  };
@@ -116934,14 +116997,23 @@ async function createDaemon(deps) {
116934
116997
  current: (reason) => deps.log({ event: "claude_auth_recheck_before_wiring", reason })
116935
116998
  };
116936
116999
  let lastClaudeAccountSwitchAtMs = null;
116937
- const commitCapabilities = createCapabilityWriter(
116938
- capabilitiesRef,
116939
- createClaudeAccountTracker((switched) => {
116940
- lastClaudeAccountSwitchAtMs = Date.now();
116941
- claudeAccountSwitchRef.current(switched);
116942
- })
116943
- );
116944
117000
  const rateLimitStoreRef = { current: null };
117001
+ const claudeTracker = createClaudeAccountTracker((switched) => {
117002
+ lastClaudeAccountSwitchAtMs = Date.now();
117003
+ claudeAccountSwitchRef.current(switched);
117004
+ });
117005
+ const accountKeySwitchTracker = createAccountKeySwitchTracker((switched) => {
117006
+ rateLimitStoreRef.current?.markAccountSwitchedIn(switched.to);
117007
+ deps.log({
117008
+ event: "rate_limit_record_untrusted_on_switch_in",
117009
+ profile: switched.profile,
117010
+ accountKey: switched.to
117011
+ });
117012
+ });
117013
+ const commitCapabilities = createCapabilityWriter(capabilitiesRef, (next) => {
117014
+ claudeTracker(next);
117015
+ accountKeySwitchTracker(next);
117016
+ });
116945
117017
  const gitCheckpoint = createGitCheckpointService();
116946
117018
  const skillResolution = buildSkillResolutionService({
116947
117019
  shipyardHome: deps.shipyardHome,
@@ -117658,6 +117730,32 @@ async function createDaemon(deps) {
117658
117730
  return subprocess;
117659
117731
  }
117660
117732
  const lastUsageLimitBroadcastByTask = /* @__PURE__ */ new Map();
117733
+ const usageLimitGateForSpawn = (taskId, profileId) => {
117734
+ const gate = resolveUsageLimitGate(
117735
+ rateLimitStoreRef.current,
117736
+ capabilitiesRef.current,
117737
+ profileId
117738
+ );
117739
+ if (gate.probe === "claimed") {
117740
+ const record2 = gate.accountKey === null ? void 0 : rateLimitStoreRef.current?.get(gate.accountKey);
117741
+ deps.log({
117742
+ event: "usage_limit_probe_spawn",
117743
+ taskId,
117744
+ accountKey: gate.accountKey,
117745
+ profile: profileId,
117746
+ recordAgeMs: record2 ? Date.now() - record2.updatedAt : null
117747
+ });
117748
+ } else if (gate.probe === "blocked") {
117749
+ deps.log({
117750
+ event: "usage_limit_probe_blocked",
117751
+ taskId,
117752
+ accountKey: gate.accountKey,
117753
+ profile: profileId,
117754
+ resetsAtMs: gate.resetsAtMs
117755
+ });
117756
+ }
117757
+ return gate.resetsAtMs;
117758
+ };
117661
117759
  const spawnSubprocessFn = (reason, initialContent, unattributedOnEvent, canUseTool, settings, cwd, taskId, additionalSystemPrompt, disallowedTools, mode, onCwdChanged, initialMessageKind) => {
117662
117760
  const resolvedTaskId = taskId ?? "unknown";
117663
117761
  const resolvedCwd = cwd ?? deps.workspaceRoot;
@@ -117716,11 +117814,7 @@ async function createDaemon(deps) {
117716
117814
  profileForModel?.id ?? null,
117717
117815
  capabilitiesRef.current
117718
117816
  ),
117719
- usageLimitResetsAtMs: resolveUsageLimitResetsAtMs(
117720
- rateLimitStoreRef.current,
117721
- capabilitiesRef.current,
117722
- profileForModel?.id ?? null
117723
- ),
117817
+ usageLimitResetsAtMs: usageLimitGateForSpawn(resolvedTaskId, profileForModel?.id ?? null),
117724
117818
  resolvedCwd,
117725
117819
  /** Sync: `SpawnSubprocessFn` contract requires synchronous check; policy details live on `isPathPresentSync`. */
117726
117820
  cwdExists: isPathPresentSync(resolvedCwd)
@@ -118406,10 +118500,23 @@ async function createDaemon(deps) {
118406
118500
  * no-op (returns null), which matches the legacy behavior of "spawn
118407
118501
  * unconditionally during early boot."
118408
118502
  */
118409
- checkUsageLimit: () => resolveUsageLimitResetsAtMs(
118410
- rateLimitStoreRef.current,
118411
- capabilitiesRef.current,
118412
- "claude-code"
118503
+ checkUsageLimit: () => (
118504
+ /**
118505
+ * 'observe' — a warm lane is idle until adopted (`initialContent: []`,
118506
+ * no API call), so it can never produce probe evidence; claiming would
118507
+ * only starve real task spawns behind USAGE_LIMIT_PROBE_TIMEOUT_MS.
118508
+ * The task spawn that claims the probe adopts this warm lane, and the
118509
+ * adopting turn's first `rate_limit` event IS the evidence (#5469).
118510
+ * This call also runs in the failure-backoff path — a bookkeeping read
118511
+ * that must not have side effects.
118512
+ */
118513
+ resolveUsageLimitGate(
118514
+ rateLimitStoreRef.current,
118515
+ capabilitiesRef.current,
118516
+ "claude-code",
118517
+ Date.now(),
118518
+ "observe"
118519
+ ).resetsAtMs
118413
118520
  ),
118414
118521
  /** Block the spawn only on an explicit `unauthenticated` flip (unknown/authenticated still spawn). */
118415
118522
  checkIsAuthenticated: () => capabilitiesRef.current?.anthropicAuth?.status !== "unauthenticated"
@@ -122647,4 +122754,4 @@ export {
122647
122754
  decideWorkspaceScope,
122648
122755
  serve
122649
122756
  };
122650
- //# sourceMappingURL=serve-OP4JVMPM.js.map
122757
+ //# sourceMappingURL=serve-LJYQF75A.js.map