@schoolai/shipyard 3.9.1 → 3.10.0-rc.20260609.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.
Files changed (61) hide show
  1. package/dist/{auth-UF3MLB77.js → auth-GGM253LQ.js} +3 -3
  2. package/dist/capability-detector-worker.js +9 -9
  3. package/dist/{chunk-LBTLMT5Z.js → chunk-2EQOL57Z.js} +2 -2
  4. package/dist/{chunk-CVMNGYPR.js → chunk-3WEEGJJN.js} +2 -2
  5. package/dist/{chunk-DGX2QR6G.js → chunk-4PBXNWJV.js} +26 -3
  6. package/dist/chunk-4PBXNWJV.js.map +1 -0
  7. package/dist/{chunk-K3QG7S6V.js → chunk-4THTCNVI.js} +4 -4
  8. package/dist/{chunk-K3QG7S6V.js.map → chunk-4THTCNVI.js.map} +1 -1
  9. package/dist/{chunk-7OD3UJUP.js → chunk-6LINHACK.js} +29 -9
  10. package/dist/chunk-6LINHACK.js.map +1 -0
  11. package/dist/{chunk-APVDHUPT.js → chunk-AEUTFH76.js} +28 -4
  12. package/dist/chunk-AEUTFH76.js.map +1 -0
  13. package/dist/{chunk-7AB4NH6T.js → chunk-EI4HMJ54.js} +75 -30
  14. package/dist/chunk-EI4HMJ54.js.map +1 -0
  15. package/dist/{chunk-WYP4NTFE.js → chunk-GM6MH4CD.js} +2 -2
  16. package/dist/{chunk-G7W4GFUC.js → chunk-IJHF4OM4.js} +2 -2
  17. package/dist/{chunk-2I5XDMUD.js → chunk-KRX7OJER.js} +5 -5
  18. package/dist/{chunk-3SXIJEPM.js → chunk-QJP7JCIS.js} +31 -13
  19. package/dist/chunk-QJP7JCIS.js.map +1 -0
  20. package/dist/{chunk-MCDZOOAI.js → chunk-RW2OTTUA.js} +50 -15
  21. package/dist/chunk-RW2OTTUA.js.map +1 -0
  22. package/dist/{chunk-AVF7LE7Q.js → chunk-VKCGK333.js} +192 -25
  23. package/dist/chunk-VKCGK333.js.map +1 -0
  24. package/dist/{chunk-MLEGFDFW.js → chunk-Z37T5W6S.js} +13 -2
  25. package/dist/chunk-Z37T5W6S.js.map +1 -0
  26. package/dist/cursor-runner.js +4 -4
  27. package/dist/electron-utility.js +5 -5
  28. package/dist/{git-repo-723BKZIH.js → git-repo-QNGPCJLI.js} +6 -4
  29. package/dist/index.js +8 -8
  30. package/dist/{logger-KICM6IPJ.js → logger-2F3CBS3V.js} +7 -5
  31. package/dist/{login-OKFUEGZW.js → login-NZKH63H7.js} +7 -7
  32. package/dist/{logout-5RGCOHCI.js → logout-HY3MPOY5.js} +5 -5
  33. package/dist/{mcp-servers-CAUI2K5W.js → mcp-servers-ICHOWXZB.js} +4 -4
  34. package/dist/{roi-Q3BQLIO7.js → roi-YM5OOWHG.js} +3 -3
  35. package/dist/{serve-76X367VD.js → serve-HVLR5UQ7.js} +64218 -62733
  36. package/dist/{serve-76X367VD.js.map → serve-HVLR5UQ7.js.map} +1 -1
  37. package/dist/{skills-YEZJMAPT.js → skills-W2Y6TWHA.js} +2 -2
  38. package/dist/{start-QOGAKRUP.js → start-YA4H2XFQ.js} +11 -10
  39. package/dist/{start-QOGAKRUP.js.map → start-YA4H2XFQ.js.map} +1 -1
  40. package/package.json +2 -2
  41. package/dist/chunk-3SXIJEPM.js.map +0 -1
  42. package/dist/chunk-7AB4NH6T.js.map +0 -1
  43. package/dist/chunk-7OD3UJUP.js.map +0 -1
  44. package/dist/chunk-APVDHUPT.js.map +0 -1
  45. package/dist/chunk-AVF7LE7Q.js.map +0 -1
  46. package/dist/chunk-DGX2QR6G.js.map +0 -1
  47. package/dist/chunk-MCDZOOAI.js.map +0 -1
  48. package/dist/chunk-MLEGFDFW.js.map +0 -1
  49. /package/dist/{auth-UF3MLB77.js.map → auth-GGM253LQ.js.map} +0 -0
  50. /package/dist/{chunk-LBTLMT5Z.js.map → chunk-2EQOL57Z.js.map} +0 -0
  51. /package/dist/{chunk-CVMNGYPR.js.map → chunk-3WEEGJJN.js.map} +0 -0
  52. /package/dist/{chunk-WYP4NTFE.js.map → chunk-GM6MH4CD.js.map} +0 -0
  53. /package/dist/{chunk-G7W4GFUC.js.map → chunk-IJHF4OM4.js.map} +0 -0
  54. /package/dist/{chunk-2I5XDMUD.js.map → chunk-KRX7OJER.js.map} +0 -0
  55. /package/dist/{git-repo-723BKZIH.js.map → git-repo-QNGPCJLI.js.map} +0 -0
  56. /package/dist/{logger-KICM6IPJ.js.map → logger-2F3CBS3V.js.map} +0 -0
  57. /package/dist/{login-OKFUEGZW.js.map → login-NZKH63H7.js.map} +0 -0
  58. /package/dist/{logout-5RGCOHCI.js.map → logout-HY3MPOY5.js.map} +0 -0
  59. /package/dist/{mcp-servers-CAUI2K5W.js.map → mcp-servers-ICHOWXZB.js.map} +0 -0
  60. /package/dist/{roi-Q3BQLIO7.js.map → roi-YM5OOWHG.js.map} +0 -0
  61. /package/dist/{skills-YEZJMAPT.js.map → skills-W2Y6TWHA.js.map} +0 -0
@@ -2,13 +2,13 @@
2
2
  import {
3
3
  PUBLISHED_PREVIEW_KINDS,
4
4
  PUBLISH_TTL_CHOICES
5
- } from "./chunk-DGX2QR6G.js";
5
+ } from "./chunk-4PBXNWJV.js";
6
6
  import {
7
7
  assertNever
8
8
  } from "./chunk-X3MULCV5.js";
9
9
  import {
10
10
  logger
11
- } from "./chunk-3SXIJEPM.js";
11
+ } from "./chunk-QJP7JCIS.js";
12
12
  import {
13
13
  external_exports
14
14
  } from "./chunk-CNR7O5YH.js";
@@ -669,7 +669,14 @@ var ParticipantSchema = external_exports.object({
669
669
  });
670
670
  var TextBlockSchema = external_exports.object({
671
671
  type: external_exports.literal("text"),
672
- text: external_exports.string()
672
+ text: external_exports.string(),
673
+ /**
674
+ * Set when this block belongs to a sub-agent (Task tool) turn — the parent
675
+ * Task tool_use id from the SDK. Drives `skipForMainChannel` suppression so
676
+ * a sub-agent's prose does not leak into the main channel. Additive/optional:
677
+ * older persisted blocks and main-agent turns omit it (null/undefined).
678
+ */
679
+ parentToolUseId: external_exports.string().nullable().optional()
673
680
  });
674
681
  var ToolUseBlockSchema = external_exports.object({
675
682
  type: external_exports.literal("tool_use"),
@@ -693,7 +700,9 @@ var ThinkingBlockSchema = external_exports.object({
693
700
  * `SDKThinkingMessage.thinking_duration_ms`). Additive/optional — older
694
701
  * persisted blocks and runtimes that don't report duration omit it.
695
702
  */
696
- durationMs: external_exports.number().optional()
703
+ durationMs: external_exports.number().optional(),
704
+ /** See `TextBlockSchema.parentToolUseId` — sub-agent suppression key. */
705
+ parentToolUseId: external_exports.string().nullable().optional()
697
706
  });
698
707
  var diffCommentSides = ["original", "modified"];
699
708
  var DiffReviewCommentSchema = external_exports.object({
@@ -6417,6 +6426,14 @@ var CanvasRepository = class {
6417
6426
  #epoch;
6418
6427
  #storageAdapter;
6419
6428
  #canvasDocs = /* @__PURE__ */ new Map();
6429
+ /**
6430
+ * Live Loro-subscription refcount per task. `subscribe()` increments; the
6431
+ * returned unsubscribe decrements (idempotently). `invalidate()` is a no-op
6432
+ * while a task's count is > 0 because evicting a doc out from under a live
6433
+ * subscription orphans the subscriber's callback against a dropped LoroDoc —
6434
+ * a silent data-loss bug. The idle sweep only evicts zero-refcount docs.
6435
+ */
6436
+ #subscriberCounts = /* @__PURE__ */ new Map();
6420
6437
  constructor(repo, epoch, storageAdapter) {
6421
6438
  this.#repo = repo;
6422
6439
  this.#epoch = epoch;
@@ -6496,18 +6513,70 @@ var CanvasRepository = class {
6496
6513
  /** Subscribe to CRDT changes on a canvas document. Returns unsubscribe function. */
6497
6514
  subscribe(taskId, callback) {
6498
6515
  const doc2 = this.getOrCreateCanvasDoc(taskId);
6499
- return subscribe(doc2, callback);
6516
+ const unsubscribe = subscribe(doc2, callback);
6517
+ this.#subscriberCounts.set(taskId, (this.#subscriberCounts.get(taskId) ?? 0) + 1);
6518
+ let released = false;
6519
+ return () => {
6520
+ if (released) return;
6521
+ released = true;
6522
+ unsubscribe();
6523
+ this.#releaseSubscriber(taskId);
6524
+ };
6525
+ }
6526
+ #releaseSubscriber(taskId) {
6527
+ const next = (this.#subscriberCounts.get(taskId) ?? 0) - 1;
6528
+ if (next <= 0) this.#subscriberCounts.delete(taskId);
6529
+ else this.#subscriberCounts.set(taskId, next);
6530
+ }
6531
+ /** Number of live Loro subscriptions held on this task's canvas doc. */
6532
+ subscriberCount(taskId) {
6533
+ return this.#subscriberCounts.get(taskId) ?? 0;
6500
6534
  }
6501
6535
  /**
6502
6536
  * Drop the cached `Doc` reference for this task so the next
6503
- * `getOrCreateCanvasDoc(taskId)` call returns a fresh doc from the
6504
- * underlying repo. Called by the corruption recovery service after
6505
- * `repo.delete(docId)` evicts the poisoned doc from the synchronizer
6506
- * model without this, callers would keep getting back the dangling
6507
- * reference to the now-evicted (and still-poisoned) LoroDoc instance.
6537
+ * `getOrCreateCanvasDoc(taskId)` returns a fresh doc from the repo.
6538
+ *
6539
+ * Refcount-gated: a no-op (returns false) while a live Loro subscription
6540
+ * is held on the doc, because evicting it would orphan the subscriber's
6541
+ * callback against a dropped LoroDoc. The idle-sweep leak fix and
6542
+ * `removeTask` both route through here so neither can drop a doc a
6543
+ * resolver/viz-watcher is still reading. Returns true if the doc was
6544
+ * evicted (or was already absent).
6545
+ *
6546
+ * For the corruption-recovery path — where the doc is poisoned and its
6547
+ * subscription is already dead — use `forceInvalidate` instead.
6508
6548
  */
6509
6549
  invalidate(taskId) {
6550
+ if (this.subscriberCount(taskId) > 0) return false;
6551
+ this.#evict(taskId);
6552
+ return true;
6553
+ }
6554
+ /**
6555
+ * Unconditionally drop the cached `Doc` reference, ignoring the
6556
+ * subscription refcount. Used only by the corruption-recovery service:
6557
+ * a poisoned doc's wasm methods throw, so its subscription is already
6558
+ * dead and pinning the reference would re-hand the poisoned doc on the
6559
+ * next `repo.get`. The refcount gate on `invalidate` must NOT block that
6560
+ * recovery — hence this escape hatch.
6561
+ */
6562
+ forceInvalidate(taskId) {
6563
+ this.#evict(taskId);
6564
+ }
6565
+ /**
6566
+ * Eviction's remote-safety depends on every personal/canvas repo using
6567
+ * `deletion: () => false`: `repo.delete` fans out a `channel/delete-request`
6568
+ * that peers currently ignore, so dropping the local cache never propagates
6569
+ * as a remote delete. If any repo's `deletion` ever becomes permissive,
6570
+ * eviction here would tombstone the doc on peers — re-audit this call site.
6571
+ */
6572
+ #evict(taskId) {
6510
6573
  this.#canvasDocs.delete(taskId);
6574
+ this.#subscriberCounts.delete(taskId);
6575
+ const repoDelete = this.#repo.delete;
6576
+ if (repoDelete) {
6577
+ const docId = buildCanvasDocId(taskId, this.#epoch);
6578
+ void repoDelete.call(this.#repo, docId);
6579
+ }
6511
6580
  }
6512
6581
  /**
6513
6582
  * Number of cached canvas docs. Exposed for health metrics — see
@@ -6517,8 +6586,23 @@ var CanvasRepository = class {
6517
6586
  get cachedDocCount() {
6518
6587
  return this.#canvasDocs.size;
6519
6588
  }
6589
+ /** Task ids with a currently-cached canvas doc. Drives the idle sweep's candidate set. */
6590
+ cachedTaskIds() {
6591
+ return [...this.#canvasDocs.keys()];
6592
+ }
6593
+ /**
6594
+ * The repo-level docId this task's canvas maps to. Lets the daemon-side idle
6595
+ * sweep ask the personal repo's synchronizer "is any peer syncing this doc?"
6596
+ * (a browser viewer holds no daemon-side `subscribe()` refcount — it syncs
6597
+ * over WebRTC — so a doc a viewer is editing must be excluded from eviction
6598
+ * by its live peer subscription, not by the refcount).
6599
+ */
6600
+ docIdFor(taskId) {
6601
+ return buildCanvasDocId(taskId, this.#epoch);
6602
+ }
6520
6603
  dispose() {
6521
6604
  this.#canvasDocs.clear();
6605
+ this.#subscriberCounts.clear();
6522
6606
  }
6523
6607
  };
6524
6608
 
@@ -17195,6 +17279,14 @@ var PlanRepository = class {
17195
17279
  #epoch;
17196
17280
  #planDocs = /* @__PURE__ */ new Map();
17197
17281
  #contentBridge;
17282
+ /**
17283
+ * Live Loro-subscription refcount per task. `subscribe()` increments; the
17284
+ * returned unsubscribe decrements (idempotently). `invalidate()` is a no-op
17285
+ * while a task's count is > 0 because evicting a doc out from under a live
17286
+ * subscription orphans the subscriber's callback against a dropped LoroDoc —
17287
+ * a silent data-loss bug. The idle sweep only evicts zero-refcount docs.
17288
+ */
17289
+ #subscriberCounts = /* @__PURE__ */ new Map();
17198
17290
  constructor(repo, epoch, contentBridge) {
17199
17291
  this.#repo = repo;
17200
17292
  this.#epoch = epoch;
@@ -17223,7 +17315,7 @@ var PlanRepository = class {
17223
17315
  try {
17224
17316
  this.#contentBridge.writeMarkdown(doc2, markdown);
17225
17317
  } catch (err) {
17226
- this.invalidate(taskId);
17318
+ this.forceInvalidate(taskId);
17227
17319
  throw err;
17228
17320
  }
17229
17321
  }
@@ -17243,26 +17335,76 @@ var PlanRepository = class {
17243
17335
  /** Subscribe to CRDT changes on a plan document. Returns unsubscribe function. */
17244
17336
  subscribe(taskId, callback) {
17245
17337
  const doc2 = this.getOrCreatePlanDoc(taskId);
17246
- return subscribe(doc2, callback);
17338
+ const unsubscribe = subscribe(doc2, callback);
17339
+ this.#subscriberCounts.set(taskId, (this.#subscriberCounts.get(taskId) ?? 0) + 1);
17340
+ let released = false;
17341
+ return () => {
17342
+ if (released) return;
17343
+ released = true;
17344
+ unsubscribe();
17345
+ this.#releaseSubscriber(taskId);
17346
+ };
17347
+ }
17348
+ #releaseSubscriber(taskId) {
17349
+ const next = (this.#subscriberCounts.get(taskId) ?? 0) - 1;
17350
+ if (next <= 0) this.#subscriberCounts.delete(taskId);
17351
+ else this.#subscriberCounts.set(taskId, next);
17352
+ }
17353
+ /** Number of live Loro subscriptions held on this task's plan doc. */
17354
+ subscriberCount(taskId) {
17355
+ return this.#subscriberCounts.get(taskId) ?? 0;
17247
17356
  }
17248
17357
  /**
17249
17358
  * Drop the cached `Doc` reference for this task so the next
17250
- * `getOrCreatePlanDoc(taskId)` call returns a fresh doc.
17359
+ * `getOrCreatePlanDoc(taskId)` call returns a fresh doc from disk.
17251
17360
  *
17252
- * Local map clear is necessary but not sufficient: loro-repo maintains
17253
- * its own per-docId cache and `repo.get(docId, schema)` returns the
17254
- * same `RepoDoc` until it is also evicted there. When the repo
17255
- * implementation provides `delete`, fire it (no await kept sync to
17256
- * preserve the existing call contract; microtask ordering guarantees
17257
- * eviction completes before the next `get` reaches the synchronizer).
17361
+ * Refcount-gated: a no-op (returns false) while a live Loro subscription
17362
+ * is held on the doc, because evicting it would orphan the subscriber's
17363
+ * callback against a dropped LoroDoc. The idle-sweep leak fix and
17364
+ * `removeTask` both route through here so neither can drop a doc a
17365
+ * resolver/file-bridge is still reading. Returns true if the doc was
17366
+ * evicted (or was already absent).
17258
17367
  *
17259
- * Without the repo-side eviction, the wasm-panic recovery path in
17260
- * `updateContent` re-hands the poisoned doc on the next write,
17261
- * triggering the same panic again — the 47s detection wait reappears
17262
- * on every subsequent attempt.
17368
+ * For the corruption-recovery path — where the doc is poisoned and its
17369
+ * subscription is already dead use `forceInvalidate` instead.
17263
17370
  */
17264
17371
  invalidate(taskId) {
17372
+ if (this.subscriberCount(taskId) > 0) return false;
17373
+ this.#evict(taskId);
17374
+ return true;
17375
+ }
17376
+ /**
17377
+ * Unconditionally evict, ignoring the subscription refcount. Used by the
17378
+ * corruption-recovery service and the `updateContent` bridge-throw path:
17379
+ * a poisoned doc's wasm methods throw, so its subscription is already dead
17380
+ * and pinning the reference would re-hand the poisoned doc on the next
17381
+ * `repo.get` — re-triggering the panic. The refcount gate must NOT block
17382
+ * that recovery.
17383
+ */
17384
+ forceInvalidate(taskId) {
17385
+ this.#evict(taskId);
17386
+ }
17387
+ /**
17388
+ * Drop the local Map ref AND loro-repo's `#docCache` + synchronizer entry.
17389
+ *
17390
+ * Local map clear is necessary but not sufficient: loro-repo maintains its
17391
+ * own per-docId cache and `repo.get(docId, schema)` returns the same
17392
+ * `RepoDoc` until it is also evicted there — so without `repo.delete` the
17393
+ * `LoroDoc` is never released and its wasm linear-memory leaks. When the
17394
+ * repo implementation provides `delete`, fire it (no await — kept sync to
17395
+ * preserve the call contract; microtask ordering guarantees eviction
17396
+ * completes before the next `get` reaches the synchronizer). The persisted
17397
+ * on-disk chunks are NOT removed, so the next `get` rehydrates from disk.
17398
+ *
17399
+ * Remote-safety depends on every personal/plan repo using
17400
+ * `deletion: () => false`: `repo.delete` fans out a `channel/delete-request`
17401
+ * that peers currently ignore, so dropping the local cache never propagates
17402
+ * as a remote delete. If any repo's `deletion` ever becomes permissive,
17403
+ * eviction here would tombstone the doc on peers — re-audit this call site.
17404
+ */
17405
+ #evict(taskId) {
17265
17406
  this.#planDocs.delete(taskId);
17407
+ this.#subscriberCounts.delete(taskId);
17266
17408
  const repoDelete = this.#repo.delete;
17267
17409
  if (repoDelete) {
17268
17410
  const docId = buildPlanDocId(taskId, this.#epoch);
@@ -17278,8 +17420,23 @@ var PlanRepository = class {
17278
17420
  get cachedDocCount() {
17279
17421
  return this.#planDocs.size;
17280
17422
  }
17423
+ /** Task ids with a currently-cached plan doc. Drives the idle sweep's candidate set. */
17424
+ cachedTaskIds() {
17425
+ return [...this.#planDocs.keys()];
17426
+ }
17427
+ /**
17428
+ * The repo-level docId this task's plan maps to. Lets the daemon-side idle
17429
+ * sweep ask the personal repo's synchronizer "is any peer syncing this doc?"
17430
+ * (a browser viewer holds no daemon-side `subscribe()` refcount — it syncs
17431
+ * over WebRTC — so a doc a viewer is editing must be excluded from eviction
17432
+ * by its live peer subscription, not by the refcount).
17433
+ */
17434
+ docIdFor(taskId) {
17435
+ return buildPlanDocId(taskId, this.#epoch);
17436
+ }
17281
17437
  dispose() {
17282
17438
  this.#planDocs.clear();
17439
+ this.#subscriberCounts.clear();
17283
17440
  }
17284
17441
  };
17285
17442
 
@@ -21920,7 +22077,17 @@ var DaemonToBrowserControlMessageSchema = external_exports.discriminatedUnion("t
21920
22077
  connectionStatus: external_exports.enum(["connected", "failed", "needs-auth", "connecting", "disabled"]).nullable(),
21921
22078
  terminalReason: external_exports.literal("unsupported").optional(),
21922
22079
  error: external_exports.string().optional(),
21923
- toolCount: external_exports.number().optional()
22080
+ toolCount: external_exports.number().optional(),
22081
+ /**
22082
+ * PROTOCOL_VERSION 130 (additive): data-driven error surface. `errorType`
22083
+ * is the classified backend/gateway code (raw `error` still preserved,
22084
+ * unmasked per #4475); `remediation` is an optional generic CTA the picker
22085
+ * renders without per-connector branching; `sources` is the dedup
22086
+ * provenance set. All optional — pre-v130 browsers drop them, MIN stays 129.
22087
+ */
22088
+ errorType: external_exports.string().optional(),
22089
+ remediation: external_exports.object({ message: external_exports.string(), url: external_exports.string().optional() }).optional(),
22090
+ sources: external_exports.array(external_exports.string()).optional()
21924
22091
  })
21925
22092
  )
21926
22093
  }),
@@ -27761,4 +27928,4 @@ export {
27761
27928
  toRecord,
27762
27929
  buildCursorUserPrompt
27763
27930
  };
27764
- //# sourceMappingURL=chunk-AVF7LE7Q.js.map
27931
+ //# sourceMappingURL=chunk-VKCGK333.js.map