@schoolai/shipyard 3.3.0 → 3.3.1-nightly.20260425.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.
@@ -53,7 +53,7 @@ import {
53
53
  VaultKeyPutRequestSchema,
54
54
  VaultKeyPutResponseSchema,
55
55
  classifyClaudeCodeCompatibility
56
- } from "./chunk-UWBX6UGC.js";
56
+ } from "./chunk-SDMIFWEZ.js";
57
57
  import "./chunk-EHQITHQX.js";
58
58
  import {
59
59
  loadAuthToken
@@ -14592,7 +14592,7 @@ function parseWorktreeListOutput(output, repoPath) {
14592
14592
  // src/shared/capabilities/index.ts
14593
14593
  import { readFile as readFile3 } from "fs/promises";
14594
14594
  import { homedir as homedir2 } from "os";
14595
- import { join as join7 } from "path";
14595
+ import { join as join6 } from "path";
14596
14596
 
14597
14597
  // src/shared/capabilities/agents.ts
14598
14598
  async function detectAgentProviders() {
@@ -14850,7 +14850,6 @@ async function detectMarketplacePlugins() {
14850
14850
  // src/shared/capabilities/effort-probe.ts
14851
14851
  import { existsSync as existsSync2, readFileSync } from "fs";
14852
14852
  import { createRequire } from "module";
14853
- import { dirname as dirname3, join as join6 } from "path";
14854
14853
 
14855
14854
  // ../../packages/loro-schema/dist/index.js
14856
14855
  import { LoroDoc as LoroDoc4 } from "loro-crdt";
@@ -28056,8 +28055,7 @@ var RequestPublishShape = external_exports.object({
28056
28055
  external_exports.object({
28057
28056
  kind: external_exports.literal("previewPort"),
28058
28057
  previewPort: external_exports.number().int().positive().max(65535),
28059
- canvasElementId: external_exports.string().optional(),
28060
- projectRoot: external_exports.string().optional()
28058
+ canvasElementId: external_exports.string().optional()
28061
28059
  })
28062
28060
  ]),
28063
28061
  ttl: external_exports.enum(["24h", "7d", "30d"]),
@@ -28106,6 +28104,20 @@ var PublishedArtifactsStateShape = external_exports.object({
28106
28104
  taskId: external_exports.string(),
28107
28105
  entries: external_exports.array(PublishedArtifactRecordShape)
28108
28106
  });
28107
+ var PreviewElementStateShape = external_exports.object({
28108
+ elementId: external_exports.string().min(1),
28109
+ ownerUserId: external_exports.string(),
28110
+ port: external_exports.number().int().positive().optional(),
28111
+ url: external_exports.string().optional(),
28112
+ proxyPort: external_exports.number().int().positive().optional(),
28113
+ initialPath: external_exports.string().optional(),
28114
+ projectRoot: external_exports.string().optional()
28115
+ });
28116
+ var PreviewElementsStateShape = external_exports.object({
28117
+ type: external_exports.literal("preview_elements_state"),
28118
+ taskId: external_exports.string(),
28119
+ entries: external_exports.array(PreviewElementStateShape)
28120
+ });
28109
28121
  var BrowserToDaemonControlMessageSchema = external_exports.discriminatedUnion("type", [
28110
28122
  external_exports.object({
28111
28123
  type: external_exports.literal("permission_response"),
@@ -28451,6 +28463,7 @@ var RateLimitInfoSchema = external_exports.object({
28451
28463
  "org_service_level_disabled",
28452
28464
  "org_service_zero_credit_limit",
28453
28465
  "no_limits_configured",
28466
+ "fetch_error",
28454
28467
  "unknown"
28455
28468
  ]).optional(),
28456
28469
  isUsingOverage: external_exports.boolean().optional(),
@@ -28938,7 +28951,8 @@ var DaemonToBrowserControlMessageSchema = external_exports.discriminatedUnion("t
28938
28951
  }),
28939
28952
  PublishProgressShape,
28940
28953
  PublishResultShape,
28941
- PublishedArtifactsStateShape
28954
+ PublishedArtifactsStateShape,
28955
+ PreviewElementsStateShape
28942
28956
  ]);
28943
28957
  var TASK_MESSAGES_PREFIX = "task-messages:";
28944
28958
  var DAEMON_CONTROL_LABEL = "daemon-control";
@@ -29319,8 +29333,7 @@ var PublishTargetSchema = external_exports.discriminatedUnion("kind", [
29319
29333
  external_exports.object({
29320
29334
  kind: external_exports.literal("previewPort"),
29321
29335
  previewPort: external_exports.number().int().positive().max(65535),
29322
- canvasElementId: external_exports.string().optional(),
29323
- projectRoot: external_exports.string().optional()
29336
+ canvasElementId: external_exports.string().optional()
29324
29337
  })
29325
29338
  ]);
29326
29339
  var PublishInputSchema = external_exports.object({
@@ -30319,21 +30332,27 @@ function isReasoningEffort(value) {
30319
30332
  }
30320
30333
  function probeCli() {
30321
30334
  try {
30322
- const req = createRequire(import.meta.url);
30323
- const sdkMain = req.resolve("@anthropic-ai/claude-agent-sdk");
30324
- const cliPath = join6(dirname3(sdkMain), "cli.js");
30325
- if (!existsSync2(cliPath)) {
30335
+ const binaryPath = resolveBundledClaudeBinary();
30336
+ if (!binaryPath || !existsSync2(binaryPath)) {
30337
+ logger.warn(
30338
+ { binaryPath },
30339
+ "bundled Claude Code binary not found; using conservative effort set"
30340
+ );
30341
+ return { supported: CONSERVATIVE_FALLBACK, source: "fallback" };
30342
+ }
30343
+ const slice2 = readEffortDescriptionFromBinary(binaryPath);
30344
+ if (slice2 === null) {
30326
30345
  logger.warn(
30327
- { cliPath },
30328
- "bundled Claude Code cli.js not found; using conservative effort set"
30346
+ { binaryPath },
30347
+ "effort description not found in bundled binary; using conservative effort set"
30329
30348
  );
30330
30349
  return { supported: CONSERVATIVE_FALLBACK, source: "fallback" };
30331
30350
  }
30332
- const parsed = parseEffortsFromCliText(readFileSync(cliPath, "utf-8"));
30351
+ const parsed = parseEffortsFromCliText(slice2);
30333
30352
  if (!parsed) {
30334
30353
  logger.warn(
30335
- { cliPath },
30336
- "could not parse effort tokens from bundled cli.js; using conservative effort set"
30354
+ { binaryPath },
30355
+ "could not parse effort tokens from bundled binary; using conservative effort set"
30337
30356
  );
30338
30357
  return { supported: CONSERVATIVE_FALLBACK, source: "fallback" };
30339
30358
  }
@@ -30343,6 +30362,32 @@ function probeCli() {
30343
30362
  return { supported: CONSERVATIVE_FALLBACK, source: "fallback" };
30344
30363
  }
30345
30364
  }
30365
+ function resolveBundledClaudeBinary() {
30366
+ const req = createRequire(import.meta.url);
30367
+ const sdkMain = req.resolve("@anthropic-ai/claude-agent-sdk");
30368
+ const sdkReq = createRequire(sdkMain);
30369
+ const ext2 = process.platform === "win32" ? ".exe" : "";
30370
+ const candidates = process.platform === "linux" ? [
30371
+ `@anthropic-ai/claude-agent-sdk-linux-${process.arch}-musl`,
30372
+ `@anthropic-ai/claude-agent-sdk-linux-${process.arch}`
30373
+ ] : [`@anthropic-ai/claude-agent-sdk-${process.platform}-${process.arch}`];
30374
+ for (const pkg of candidates) {
30375
+ try {
30376
+ return sdkReq.resolve(`${pkg}/claude${ext2}`);
30377
+ } catch (err) {
30378
+ logger.debug({ pkg, err }, "platform claude binary candidate not resolvable");
30379
+ }
30380
+ }
30381
+ return null;
30382
+ }
30383
+ function readEffortDescriptionFromBinary(binaryPath) {
30384
+ const buf = readFileSync(binaryPath);
30385
+ const needle = Buffer.from("Effort level for the current session");
30386
+ const idx = buf.indexOf(needle);
30387
+ if (idx === -1) return null;
30388
+ const end = Math.min(idx + 200, buf.length);
30389
+ return buf.toString("utf-8", idx, end);
30390
+ }
30346
30391
 
30347
30392
  // src/shared/capabilities/models.ts
30348
30393
  async function detectModels() {
@@ -31139,7 +31184,7 @@ var AutoModeConfigSchema = external_exports.object({
31139
31184
  }).passthrough();
31140
31185
  async function detectAutoModeEnabled() {
31141
31186
  try {
31142
- const claudeConfig = await readFile3(join7(homedir2(), ".claude.json"), "utf-8");
31187
+ const claudeConfig = await readFile3(join6(homedir2(), ".claude.json"), "utf-8");
31143
31188
  const result = AutoModeConfigSchema.safeParse(JSON.parse(claudeConfig));
31144
31189
  return result.success && result.data.cachedGrowthBookFeatures.tengu_auto_mode_config.enabled === "enabled";
31145
31190
  } catch {
@@ -31208,7 +31253,7 @@ async function detectCapabilitiesWithInitialRetry(tokenStore, methodHint, lastKn
31208
31253
 
31209
31254
  // src/shared/file-storage-adapter.ts
31210
31255
  import { access, mkdir as mkdir2, readdir as readdir3, readFile as readFile4, rename as rename2, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
31211
- import { dirname as dirname4, join as join8, sep } from "path";
31256
+ import { dirname as dirname3, join as join7, sep } from "path";
31212
31257
  var FileStorageAdapter = class _FileStorageAdapter extends StorageAdapter {
31213
31258
  #dataDir;
31214
31259
  constructor(dataDir) {
@@ -31226,7 +31271,7 @@ var FileStorageAdapter = class _FileStorageAdapter extends StorageAdapter {
31226
31271
  }
31227
31272
  async save(key, data) {
31228
31273
  const filePath = this.#keyToPath(key);
31229
- const dir = dirname4(filePath);
31274
+ const dir = dirname3(filePath);
31230
31275
  await mkdir2(dir, { recursive: true, mode: 448 });
31231
31276
  const tmpPath = `${filePath}.tmp`;
31232
31277
  await writeFile2(tmpPath, data, { mode: 384 });
@@ -31268,7 +31313,7 @@ var FileStorageAdapter = class _FileStorageAdapter extends StorageAdapter {
31268
31313
  * docId is the first key segment (e.g. "canvas:<taskId>:<epoch>").
31269
31314
  */
31270
31315
  async hasDoc(docId) {
31271
- const dirPath = join8(this.#dataDir, encodeURIComponent(docId));
31316
+ const dirPath = join7(this.#dataDir, encodeURIComponent(docId));
31272
31317
  try {
31273
31318
  await access(dirPath);
31274
31319
  return true;
@@ -31282,7 +31327,7 @@ var FileStorageAdapter = class _FileStorageAdapter extends StorageAdapter {
31282
31327
  }
31283
31328
  #keyToPath(key) {
31284
31329
  const sanitized = key.map((part) => encodeURIComponent(part));
31285
- return join8(this.#dataDir, ...sanitized);
31330
+ return join7(this.#dataDir, ...sanitized);
31286
31331
  }
31287
31332
  #pathToKey(filePath) {
31288
31333
  const relative5 = filePath.slice(this.#dataDir.length + 1);
@@ -31300,7 +31345,7 @@ var FileStorageAdapter = class _FileStorageAdapter extends StorageAdapter {
31300
31345
  return;
31301
31346
  }
31302
31347
  for (const entry of entries) {
31303
- const fullPath = join8(dir, entry.name);
31348
+ const fullPath = join7(dir, entry.name);
31304
31349
  if (entry.isDirectory()) {
31305
31350
  await this.#walkDir(fullPath, keyPrefix, results);
31306
31351
  } else if (entry.isFile() && !entry.name.endsWith(".tmp")) {
@@ -31478,7 +31523,7 @@ var KeepAwakeManager = class {
31478
31523
  // src/shared/mcp/token-store.ts
31479
31524
  import { readFileSync as readFileSync2, statSync } from "fs";
31480
31525
  import { readFile as readFile5, rename as rename3, stat as stat3, writeFile as writeFile3 } from "fs/promises";
31481
- import { join as join9 } from "path";
31526
+ import { join as join8 } from "path";
31482
31527
  var TOKEN_FILE = "mcp-tokens.json";
31483
31528
  var REFRESH_THRESHOLD_MS = 5 * 60 * 1e3;
31484
31529
  var MCPTokenStore = class {
@@ -31496,7 +31541,7 @@ var MCPTokenStore = class {
31496
31541
  this.#onChange = cb;
31497
31542
  }
31498
31543
  #filePath() {
31499
- return join9(this.#shipyardHome, TOKEN_FILE);
31544
+ return join8(this.#shipyardHome, TOKEN_FILE);
31500
31545
  }
31501
31546
  /** Read mtime from disk. Returns -1 if file does not exist. */
31502
31547
  #readMtimeMs() {
@@ -31608,7 +31653,7 @@ var MCPTokenStore = class {
31608
31653
  // src/services/bootstrap/lifecycle.ts
31609
31654
  import { unlinkSync } from "fs";
31610
31655
  import { readFile as readFile6, unlink as unlink3, writeFile as writeFile4 } from "fs/promises";
31611
- import { join as join10 } from "path";
31656
+ import { join as join9 } from "path";
31612
31657
 
31613
31658
  // src/services/bootstrap/classify-uncaught-error.ts
31614
31659
  function classifyUncaughtError(error2) {
@@ -31742,7 +31787,7 @@ var LifecycleManager = class {
31742
31787
  this.#removePidFileSync();
31743
31788
  }
31744
31789
  async acquirePidFile(shipyardHome) {
31745
- const pidFilePath = join10(shipyardHome, "daemon.pid");
31790
+ const pidFilePath = join9(shipyardHome, "daemon.pid");
31746
31791
  try {
31747
31792
  const existing = await readFile6(pidFilePath, "utf-8");
31748
31793
  const raw = existing.trim();
@@ -31939,10 +31984,10 @@ var LifecycleManager = class {
31939
31984
  // src/services/bootstrap/load-or-create-peer-id.ts
31940
31985
  import { randomUUID } from "crypto";
31941
31986
  import { mkdir as mkdir3, readFile as readFile7, rename as rename4, writeFile as writeFile5 } from "fs/promises";
31942
- import { dirname as dirname5, join as join11 } from "path";
31987
+ import { dirname as dirname4, join as join10 } from "path";
31943
31988
  var PEER_ID_FILE = "daemon-peer-id";
31944
31989
  async function loadOrCreateDaemonPeerId(shipyardDataDir) {
31945
- const target = join11(shipyardDataDir, PEER_ID_FILE);
31990
+ const target = join10(shipyardDataDir, PEER_ID_FILE);
31946
31991
  const existing = await readExistingPeerId(target);
31947
31992
  if (existing !== null) return existing;
31948
31993
  const peerId = generatePeerId();
@@ -31967,7 +32012,7 @@ async function readExistingPeerId(target) {
31967
32012
  return candidate;
31968
32013
  }
31969
32014
  async function writePeerIdAtomic(target, peerId) {
31970
- await mkdir3(dirname5(target), { recursive: true });
32015
+ await mkdir3(dirname4(target), { recursive: true });
31971
32016
  const tmp = `${target}.${randomUUID()}.tmp`;
31972
32017
  await writeFile5(tmp, peerId, "utf-8");
31973
32018
  await rename4(tmp, target);
@@ -31975,10 +32020,10 @@ async function writePeerIdAtomic(target, peerId) {
31975
32020
 
31976
32021
  // src/services/bootstrap/pid-tracking.ts
31977
32022
  import { readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
31978
- import { join as join12 } from "path";
32023
+ import { join as join11 } from "path";
31979
32024
  var PID_FILE = "children.pid";
31980
32025
  function buildPidTracker(shipyardHome) {
31981
- const filePath = join12(shipyardHome, PID_FILE);
32026
+ const filePath = join11(shipyardHome, PID_FILE);
31982
32027
  let queue = Promise.resolve();
31983
32028
  function enqueue(fn) {
31984
32029
  const next = queue.then(fn);
@@ -32061,7 +32106,7 @@ function buildReloadDocsFromStorage(repoRef, log) {
32061
32106
  // src/services/bootstrap/signaling.ts
32062
32107
  import { mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
32063
32108
  import { hostname } from "os";
32064
- import { dirname as dirname6, join as join13 } from "path";
32109
+ import { dirname as dirname5, join as join12 } from "path";
32065
32110
 
32066
32111
  // ../../node_modules/.pnpm/nanoid@5.1.6/node_modules/nanoid/index.js
32067
32112
  import { webcrypto as crypto2 } from "crypto";
@@ -32094,7 +32139,7 @@ function nanoid(size2 = 21) {
32094
32139
  }
32095
32140
 
32096
32141
  // src/services/bootstrap/signaling.ts
32097
- var DAEMON_NPM_VERSION = true ? "3.3.0" : "unknown";
32142
+ var DAEMON_NPM_VERSION = true ? "3.3.1" : "unknown";
32098
32143
  function createDaemonSignaling(config2) {
32099
32144
  const agentId = config2.agentId ?? nanoid();
32100
32145
  function send(msg) {
@@ -32123,7 +32168,7 @@ function createDaemonSignaling(config2) {
32123
32168
  };
32124
32169
  }
32125
32170
  function getOrCreateAgentId(shipyardHome) {
32126
- const filePath = join13(shipyardHome, "agent-id");
32171
+ const filePath = join12(shipyardHome, "agent-id");
32127
32172
  try {
32128
32173
  const existing = readFileSync3(filePath, "utf-8").trim();
32129
32174
  if (existing) return existing;
@@ -32131,7 +32176,7 @@ function getOrCreateAgentId(shipyardHome) {
32131
32176
  }
32132
32177
  const id = crypto.randomUUID();
32133
32178
  try {
32134
- mkdirSync(dirname6(filePath), { recursive: true });
32179
+ mkdirSync(dirname5(filePath), { recursive: true });
32135
32180
  writeFileSync(filePath, id, { mode: 384 });
32136
32181
  } catch {
32137
32182
  }
@@ -32339,7 +32384,7 @@ function createPeerRoleRegistry() {
32339
32384
 
32340
32385
  // src/services/epoch-pruning.ts
32341
32386
  import { readdir as readdir4, rm } from "fs/promises";
32342
- import { join as join14 } from "path";
32387
+ import { join as join13 } from "path";
32343
32388
  var LEGACY_PREFIXES = [
32344
32389
  "task-meta",
32345
32390
  "task-conv",
@@ -32379,7 +32424,7 @@ async function pruneOldEpochData(dataDir, currentEpoch, log) {
32379
32424
  }
32380
32425
  if (!shouldPrune(decoded, currentEpoch)) continue;
32381
32426
  removals.push(
32382
- rm(join14(dataDir, entry.name), { recursive: true }).then(() => {
32427
+ rm(join13(dataDir, entry.name), { recursive: true }).then(() => {
32383
32428
  pruned++;
32384
32429
  }).catch((err) => {
32385
32430
  log({
@@ -34385,7 +34430,7 @@ function onConnection(ws, deps, peers) {
34385
34430
 
34386
34431
  // src/services/local-direct/local-direct-token.ts
34387
34432
  import { chmod, mkdir as mkdir4, rename as rename5, rm as rm2, writeFile as writeFile7 } from "fs/promises";
34388
- import { dirname as dirname7, join as join15 } from "path";
34433
+ import { dirname as dirname6, join as join14 } from "path";
34389
34434
  var ADVERTISEMENT_FILE = "local-direct.json";
34390
34435
  var ADVERTISEMENT_MODE = 384;
34391
34436
  var ADVERTISEMENT_DIR_MODE = 448;
@@ -34394,11 +34439,11 @@ function generateLocalDirectToken() {
34394
34439
  return nanoid(TOKEN_LENGTH);
34395
34440
  }
34396
34441
  function advertisementPath(shipyardHome) {
34397
- return join15(shipyardHome, "data", ADVERTISEMENT_FILE);
34442
+ return join14(shipyardHome, "data", ADVERTISEMENT_FILE);
34398
34443
  }
34399
34444
  async function writeAdvertisement(shipyardHome, ad) {
34400
34445
  const target = advertisementPath(shipyardHome);
34401
- const dir = dirname7(target);
34446
+ const dir = dirname6(target);
34402
34447
  await mkdir4(dir, { recursive: true, mode: ADVERTISEMENT_DIR_MODE });
34403
34448
  try {
34404
34449
  await chmod(dir, ADVERTISEMENT_DIR_MODE);
@@ -34523,7 +34568,7 @@ function createMetricsCollector(workerUrl, authToken, telemetryEnabled) {
34523
34568
  // src/services/plugins/plugin-file-watcher.ts
34524
34569
  import { existsSync as existsSync3, watch } from "fs";
34525
34570
  import { readdir as readdir5, readFile as readFile9, stat as stat4 } from "fs/promises";
34526
- import { join as join16 } from "path";
34571
+ import { join as join15 } from "path";
34527
34572
  import { pathToFileURL } from "url";
34528
34573
 
34529
34574
  // src/shared/plugins/plugin-registry.ts
@@ -34624,7 +34669,7 @@ var PluginFileWatcher = class {
34624
34669
  }
34625
34670
  const loaded = [];
34626
34671
  for (const entry of entries) {
34627
- const pluginDir = join16(dir, entry);
34672
+ const pluginDir = join15(dir, entry);
34628
34673
  let stats;
34629
34674
  try {
34630
34675
  stats = await stat4(pluginDir);
@@ -34639,7 +34684,7 @@ var PluginFileWatcher = class {
34639
34684
  this.#reconcile(loaded);
34640
34685
  }
34641
34686
  async #loadPlugin(id, pluginDir) {
34642
- const manifestPath = join16(pluginDir, "plugin.json");
34687
+ const manifestPath = join15(pluginDir, "plugin.json");
34643
34688
  let manifestRaw;
34644
34689
  try {
34645
34690
  manifestRaw = await readFile9(manifestPath, "utf-8");
@@ -34666,7 +34711,7 @@ var PluginFileWatcher = class {
34666
34711
  }
34667
34712
  const manifest = parsed.data;
34668
34713
  let template = "";
34669
- const templatePath = join16(pluginDir, "template.html");
34714
+ const templatePath = join15(pluginDir, "template.html");
34670
34715
  try {
34671
34716
  template = await readFile9(templatePath, "utf-8");
34672
34717
  } catch {
@@ -34681,7 +34726,7 @@ var PluginFileWatcher = class {
34681
34726
  };
34682
34727
  }
34683
34728
  async #loadAndRegisterHandler(id, pluginDir, title) {
34684
- const handlerPath = join16(pluginDir, "handler.mjs");
34729
+ const handlerPath = join15(pluginDir, "handler.mjs");
34685
34730
  let handlerFn;
34686
34731
  try {
34687
34732
  const handlerStat = await stat4(handlerPath);
@@ -35348,7 +35393,7 @@ function handlePluginAuthRequest(pluginId, sendControl, deps, logAdapter) {
35348
35393
  // src/services/port-detection.ts
35349
35394
  import { execFile as execFile3 } from "child_process";
35350
35395
  import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
35351
- import { dirname as dirname8, join as join17 } from "path";
35396
+ import { dirname as dirname7, join as join16 } from "path";
35352
35397
  import { promisify as promisify2 } from "util";
35353
35398
  var execFileAsync = promisify2(execFile3);
35354
35399
  var EXEC_TIMEOUT_MS2 = 5e3;
@@ -35463,7 +35508,7 @@ function hasStringName(val) {
35463
35508
  function resolveProjectRoot(cwd) {
35464
35509
  let dir = cwd;
35465
35510
  while (true) {
35466
- const candidate = join17(dir, "package.json");
35511
+ const candidate = join16(dir, "package.json");
35467
35512
  if (existsSync4(candidate)) {
35468
35513
  let packageName;
35469
35514
  try {
@@ -35476,7 +35521,7 @@ function resolveProjectRoot(cwd) {
35476
35521
  }
35477
35522
  return { projectRoot: dir, packageName };
35478
35523
  }
35479
- const parent = dirname8(dir);
35524
+ const parent = dirname7(dir);
35480
35525
  if (parent === dir) {
35481
35526
  return {};
35482
35527
  }
@@ -35573,6 +35618,175 @@ async function getCwdByPid(pids) {
35573
35618
  }
35574
35619
  }
35575
35620
 
35621
+ // src/services/preview/preview-state-store.ts
35622
+ import { randomUUID as randomUUID2 } from "crypto";
35623
+ import {
35624
+ mkdirSync as mkdirSync2,
35625
+ readdirSync,
35626
+ readFileSync as readFileSync5,
35627
+ renameSync,
35628
+ unlinkSync as unlinkSync2,
35629
+ writeFileSync as writeFileSync2
35630
+ } from "fs";
35631
+ import { join as join17 } from "path";
35632
+ function isEnoent2(err) {
35633
+ return err instanceof Error && Reflect.get(err, "code") === "ENOENT";
35634
+ }
35635
+ function atomicWriteSync(filePath, content) {
35636
+ const tmpPath = `${filePath}.${randomUUID2()}.tmp`;
35637
+ writeFileSync2(tmpPath, content, "utf-8");
35638
+ renameSync(tmpPath, filePath);
35639
+ }
35640
+ function recordFilePath(rootDir, taskId, elementId) {
35641
+ return join17(rootDir, taskId, `${elementId}.json`);
35642
+ }
35643
+ function parseRecord(filePath, logger2) {
35644
+ let raw;
35645
+ try {
35646
+ raw = readFileSync5(filePath, "utf-8");
35647
+ } catch {
35648
+ return null;
35649
+ }
35650
+ let obj;
35651
+ try {
35652
+ obj = JSON.parse(raw);
35653
+ } catch {
35654
+ logger2.warn({ filePath }, "preview_state_store: invalid JSON, skipping");
35655
+ return null;
35656
+ }
35657
+ const result = PreviewElementStateShape.safeParse(obj);
35658
+ if (!result.success) {
35659
+ logger2.warn(
35660
+ { filePath, issues: result.error.issues },
35661
+ "preview_state_store: schema validation failed, skipping"
35662
+ );
35663
+ return null;
35664
+ }
35665
+ return result.data;
35666
+ }
35667
+ function createPreviewStateStore(opts) {
35668
+ const { rootDir, logger: logger2 } = opts;
35669
+ const registry = /* @__PURE__ */ new Map();
35670
+ let sendFn = null;
35671
+ mkdirSync2(rootDir, { recursive: true });
35672
+ function getTaskMap(taskId) {
35673
+ let taskMap = registry.get(taskId);
35674
+ if (!taskMap) {
35675
+ taskMap = /* @__PURE__ */ new Map();
35676
+ registry.set(taskId, taskMap);
35677
+ }
35678
+ return taskMap;
35679
+ }
35680
+ function scanTaskDir(taskId) {
35681
+ const taskPath = join17(rootDir, taskId);
35682
+ let files;
35683
+ try {
35684
+ files = readdirSync(taskPath);
35685
+ } catch {
35686
+ return;
35687
+ }
35688
+ for (const file of files) {
35689
+ if (!file.endsWith(".json")) continue;
35690
+ const record = parseRecord(join17(taskPath, file), logger2);
35691
+ if (record) getTaskMap(taskId).set(record.elementId, record);
35692
+ }
35693
+ }
35694
+ function scanRootDir() {
35695
+ let taskDirs;
35696
+ try {
35697
+ taskDirs = readdirSync(rootDir);
35698
+ } catch {
35699
+ return;
35700
+ }
35701
+ for (const taskId of taskDirs) {
35702
+ scanTaskDir(taskId);
35703
+ }
35704
+ }
35705
+ scanRootDir();
35706
+ function broadcastTask(taskId) {
35707
+ if (!sendFn) return;
35708
+ const taskMap = registry.get(taskId);
35709
+ const entries = taskMap ? [...taskMap.values()] : [];
35710
+ entries.sort((a, b2) => a.elementId < b2.elementId ? -1 : a.elementId > b2.elementId ? 1 : 0);
35711
+ sendFn({ type: "preview_elements_state", taskId, entries });
35712
+ }
35713
+ function writeToDisk(taskId, state) {
35714
+ const filePath = recordFilePath(rootDir, taskId, state.elementId);
35715
+ try {
35716
+ mkdirSync2(join17(rootDir, taskId), { recursive: true });
35717
+ atomicWriteSync(filePath, JSON.stringify(state, null, 2));
35718
+ } catch (err) {
35719
+ logger2.warn(
35720
+ {
35721
+ taskId,
35722
+ elementId: state.elementId,
35723
+ err: err instanceof Error ? err.message : String(err)
35724
+ },
35725
+ "preview_state_store: putState disk write failed"
35726
+ );
35727
+ }
35728
+ }
35729
+ function removeFromDisk(taskId, elementId) {
35730
+ const filePath = recordFilePath(rootDir, taskId, elementId);
35731
+ try {
35732
+ unlinkSync2(filePath);
35733
+ } catch (err) {
35734
+ if (!isEnoent2(err)) {
35735
+ logger2.warn(
35736
+ { taskId, elementId, err: err instanceof Error ? err.message : String(err) },
35737
+ "preview_state_store: deleteByElementId disk unlink failed"
35738
+ );
35739
+ }
35740
+ }
35741
+ }
35742
+ return {
35743
+ getByElementId(taskId, elementId) {
35744
+ return registry.get(taskId)?.get(elementId);
35745
+ },
35746
+ findByProjectRoot(taskId, projectRoot) {
35747
+ const taskMap = registry.get(taskId);
35748
+ if (!taskMap) return void 0;
35749
+ for (const state of taskMap.values()) {
35750
+ if (state.projectRoot === projectRoot) return state;
35751
+ }
35752
+ return void 0;
35753
+ },
35754
+ putState(taskId, state) {
35755
+ writeToDisk(taskId, state);
35756
+ const taskMap = getTaskMap(taskId);
35757
+ taskMap.set(state.elementId, state);
35758
+ broadcastTask(taskId);
35759
+ },
35760
+ deleteByElementId(taskId, elementId) {
35761
+ const taskMap = registry.get(taskId);
35762
+ if (!taskMap) return;
35763
+ const existed = taskMap.has(elementId);
35764
+ if (!existed) return;
35765
+ removeFromDisk(taskId, elementId);
35766
+ taskMap.delete(elementId);
35767
+ if (taskMap.size === 0) registry.delete(taskId);
35768
+ broadcastTask(taskId);
35769
+ },
35770
+ listByTask(taskId) {
35771
+ const taskMap = registry.get(taskId);
35772
+ if (!taskMap) return [];
35773
+ const entries = [...taskMap.values()];
35774
+ entries.sort((a, b2) => a.elementId < b2.elementId ? -1 : a.elementId > b2.elementId ? 1 : 0);
35775
+ return entries;
35776
+ },
35777
+ setSendControlMessage(fn) {
35778
+ sendFn = fn;
35779
+ },
35780
+ broadcast(taskId) {
35781
+ broadcastTask(taskId);
35782
+ },
35783
+ dispose() {
35784
+ registry.clear();
35785
+ sendFn = null;
35786
+ }
35787
+ };
35788
+ }
35789
+
35576
35790
  // src/services/preview-proxy.ts
35577
35791
  import http2 from "http";
35578
35792
 
@@ -36449,30 +36663,30 @@ function createPreviewProxy(config2) {
36449
36663
  }
36450
36664
 
36451
36665
  // src/services/publish/published-artifact-store.ts
36452
- import { createHash, randomUUID as randomUUID2 } from "crypto";
36453
- import { mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync5, watch as watch2 } from "fs";
36666
+ import { createHash, randomUUID as randomUUID3 } from "crypto";
36667
+ import { mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync6, watch as watch2 } from "fs";
36454
36668
  import { mkdir as mkdir5, readFile as readFile10, rename as rename6, unlink as unlink4, writeFile as writeFile8 } from "fs/promises";
36455
- import { dirname as dirname9, join as join18, relative as relative2 } from "path";
36669
+ import { dirname as dirname8, join as join18, relative as relative2 } from "path";
36456
36670
  var DEBOUNCE_MS2 = 200;
36457
36671
  function bindMapKey(bindKind, bindKey) {
36458
36672
  return `${bindKind}:${bindKey}`;
36459
36673
  }
36460
- function recordFilePath(rootDir, taskId, bindKind, bindKey) {
36674
+ function recordFilePath2(rootDir, taskId, bindKind, bindKey) {
36461
36675
  return join18(rootDir, taskId, bindKind, `${bindKey}.json`);
36462
36676
  }
36463
36677
  function sha256Hex(input) {
36464
36678
  return createHash("sha256").update(input, "utf-8").digest("hex");
36465
36679
  }
36466
36680
  async function atomicWrite(filePath, content) {
36467
- await mkdir5(dirname9(filePath), { recursive: true });
36468
- const tmpPath = `${filePath}.${randomUUID2()}.tmp`;
36681
+ await mkdir5(dirname8(filePath), { recursive: true });
36682
+ const tmpPath = `${filePath}.${randomUUID3()}.tmp`;
36469
36683
  await writeFile8(tmpPath, content, "utf-8");
36470
36684
  await rename6(tmpPath, filePath);
36471
36685
  }
36472
- function parseRecord(filePath, logger2) {
36686
+ function parseRecord2(filePath, logger2) {
36473
36687
  let raw;
36474
36688
  try {
36475
- raw = readFileSync5(filePath, "utf-8");
36689
+ raw = readFileSync6(filePath, "utf-8");
36476
36690
  } catch {
36477
36691
  return null;
36478
36692
  }
@@ -36493,7 +36707,7 @@ function parseRecord(filePath, logger2) {
36493
36707
  }
36494
36708
  return result.data;
36495
36709
  }
36496
- function isEnoent2(err) {
36710
+ function isEnoent3(err) {
36497
36711
  return err instanceof Error && Reflect.get(err, "code") === "ENOENT";
36498
36712
  }
36499
36713
  function createPublishedArtifactStore(opts) {
@@ -36504,17 +36718,17 @@ function createPublishedArtifactStore(opts) {
36504
36718
  const ownWrites = /* @__PURE__ */ new Set();
36505
36719
  let watcher = null;
36506
36720
  let disposed = false;
36507
- mkdirSync2(rootDir, { recursive: true });
36721
+ mkdirSync3(rootDir, { recursive: true });
36508
36722
  function scanKindDir(taskId, kindPath) {
36509
36723
  let files;
36510
36724
  try {
36511
- files = readdirSync(kindPath);
36725
+ files = readdirSync2(kindPath);
36512
36726
  } catch {
36513
36727
  return;
36514
36728
  }
36515
36729
  for (const file of files) {
36516
36730
  if (!file.endsWith(".json")) continue;
36517
- const record = parseRecord(join18(kindPath, file), logger2);
36731
+ const record = parseRecord2(join18(kindPath, file), logger2);
36518
36732
  if (record) inMemorySet(taskId, record);
36519
36733
  }
36520
36734
  }
@@ -36522,7 +36736,7 @@ function createPublishedArtifactStore(opts) {
36522
36736
  const taskPath = join18(rootDir, taskId);
36523
36737
  let kindDirs;
36524
36738
  try {
36525
- kindDirs = readdirSync(taskPath);
36739
+ kindDirs = readdirSync2(taskPath);
36526
36740
  } catch {
36527
36741
  return;
36528
36742
  }
@@ -36534,7 +36748,7 @@ function createPublishedArtifactStore(opts) {
36534
36748
  function scanRootDir() {
36535
36749
  let taskDirs;
36536
36750
  try {
36537
- taskDirs = readdirSync(rootDir);
36751
+ taskDirs = readdirSync2(rootDir);
36538
36752
  } catch {
36539
36753
  return;
36540
36754
  }
@@ -36658,7 +36872,7 @@ function createPublishedArtifactStore(opts) {
36658
36872
  return entries;
36659
36873
  },
36660
36874
  async putBinding(taskId, record) {
36661
- const filePath = recordFilePath(rootDir, taskId, record.bindKind, record.bindKey);
36875
+ const filePath = recordFilePath2(rootDir, taskId, record.bindKind, record.bindKey);
36662
36876
  ownWrites.add(filePath);
36663
36877
  try {
36664
36878
  await atomicWrite(filePath, JSON.stringify(record, null, 2));
@@ -36670,12 +36884,12 @@ function createPublishedArtifactStore(opts) {
36670
36884
  broadcastTask(taskId);
36671
36885
  },
36672
36886
  async deleteBinding(taskId, bindKind, bindKey) {
36673
- const filePath = recordFilePath(rootDir, taskId, bindKind, bindKey);
36887
+ const filePath = recordFilePath2(rootDir, taskId, bindKind, bindKey);
36674
36888
  ownWrites.add(filePath);
36675
36889
  try {
36676
36890
  await unlink4(filePath);
36677
36891
  } catch (err) {
36678
- if (!isEnoent2(err)) {
36892
+ if (!isEnoent3(err)) {
36679
36893
  ownWrites.delete(filePath);
36680
36894
  throw err;
36681
36895
  }
@@ -37878,44 +38092,51 @@ async function storePreviewArtifact(input, bindKey, resolvedProjectRoot, existin
37878
38092
  expiresAt: result.expiresAt,
37879
38093
  ttl: input.ttl,
37880
38094
  lastSize: files.reduce((sum, f2) => sum + f2.content.length, 0),
37881
- projectRoot: resolvedProjectRoot ?? void 0,
38095
+ projectRoot: resolvedProjectRoot,
37882
38096
  canvasElementId: input.target.kind === "previewPort" ? input.target.canvasElementId : void 0
37883
38097
  };
37884
38098
  await input.publishedArtifactStore.putBinding(input.taskId, newRecord);
37885
38099
  }
38100
+ function resolvePreviewProjectRoot(input, target) {
38101
+ const canvasElementId = target.canvasElementId;
38102
+ const storeEntry = canvasElementId && input.previewStateStore ? input.previewStateStore.getByElementId(input.taskId, canvasElementId) ?? null : null;
38103
+ const storeProjectRoot = storeEntry?.projectRoot ?? null;
38104
+ const detectedPortMatch = input.getDetectedPorts().find((p2) => p2.port === target.previewPort);
38105
+ const detectedProjectRoot = detectedPortMatch?.projectRoot ?? null;
38106
+ const resolvedProjectRoot = storeProjectRoot ?? detectedProjectRoot ?? null;
38107
+ const resolvedFrom = storeProjectRoot ? "store" : detectedProjectRoot ? "detected_ports" : "none";
38108
+ return { resolvedProjectRoot, resolvedFrom, storeProjectRoot, detectedProjectRoot };
38109
+ }
37886
38110
  async function runPublishPreviewPort(input, ctx) {
37887
38111
  if (input.target.kind !== "previewPort") {
37888
38112
  return { ok: false, phase: "failed", error: "Internal error: wrong target kind" };
37889
38113
  }
37890
- const agentHint = input.target.projectRoot;
37891
- const previewPortNumber = input.target.previewPort;
37892
- const detectedPortMatch = input.getDetectedPorts().find((p2) => p2.port === previewPortNumber);
37893
- const resolvedProjectRoot = agentHint ?? detectedPortMatch?.projectRoot ?? null;
37894
- const bindKey = resolvedProjectRoot !== null ? sha256Hex(resolvedProjectRoot) : null;
37895
- const existing = bindKey !== null ? input.publishedArtifactStore?.getBinding(input.taskId, "preview", bindKey) ?? null : null;
37896
- const buildRoot = resolvedProjectRoot ?? input.projectRoot;
38114
+ const target = input.target;
38115
+ const canvasElementId = target.canvasElementId;
38116
+ const resolution = resolvePreviewProjectRoot(input, target);
38117
+ const { resolvedProjectRoot } = resolution;
37897
38118
  ctx.log?.({
37898
38119
  event: "publish_preview_resolved",
37899
- agentHint: agentHint ?? null,
37900
- detectedPortMatch: detectedPortMatch?.projectRoot ?? null,
38120
+ canvasElementId: canvasElementId ?? null,
38121
+ previewPort: target.previewPort,
38122
+ storeProjectRoot: resolution.storeProjectRoot,
38123
+ detectedPortProjectRoot: resolution.detectedProjectRoot,
37901
38124
  resolvedProjectRoot,
37902
- buildRoot
38125
+ resolvedFrom: resolution.resolvedFrom
37903
38126
  });
37904
- const built = await detectAndBuild(buildRoot, ctx);
38127
+ if (!resolvedProjectRoot) {
38128
+ const error2 = canvasElementId ? `Preview element has no resolvable projectRoot \u2014 was \`present({projectRoot})\` called? (canvasElementId: ${canvasElementId})` : "Preview publish requires a canvasElementId. The browser must send the stable canvas element id so the daemon can resolve projectRoot from the preview state store.";
38129
+ ctx.onProgress("failed", error2);
38130
+ return { ok: false, phase: "detecting_framework", error: error2 };
38131
+ }
38132
+ const bindKey = sha256Hex(resolvedProjectRoot);
38133
+ const existing = input.publishedArtifactStore?.getBinding(input.taskId, "preview", bindKey) ?? null;
38134
+ const built = await detectAndBuild(resolvedProjectRoot, ctx);
37905
38135
  if (!built.ok) return built.outcome;
37906
38136
  ctx.onProgress("uploading");
37907
38137
  try {
37908
38138
  const result = await uploadPreviewFiles(input, built.framework, built.files, existing);
37909
- if (bindKey !== null) {
37910
- await storePreviewArtifact(
37911
- input,
37912
- bindKey,
37913
- resolvedProjectRoot,
37914
- existing,
37915
- result,
37916
- built.files
37917
- );
37918
- }
38139
+ await storePreviewArtifact(input, bindKey, resolvedProjectRoot, existing, result, built.files);
37919
38140
  ctx.onProgress("completed");
37920
38141
  return { ok: true, id: result.id, url: result.url, expiresAt: result.expiresAt };
37921
38142
  } catch (err) {
@@ -37966,6 +38187,7 @@ function createPublishTools(ctx) {
37966
38187
  taskId: ctx.taskId.current,
37967
38188
  vizWatcher: ctx.vizWatcher,
37968
38189
  publishedArtifactStore: ctx.publishedArtifactStore,
38190
+ previewStateStore: ctx.previewStateStore,
37969
38191
  getDetectedPorts: ctx.getDetectedPorts
37970
38192
  },
37971
38193
  { onProgress: () => {
@@ -38068,6 +38290,7 @@ async function handlePublishRequest(params) {
38068
38290
  taskId: params.taskId,
38069
38291
  vizWatcher: params.vizWatcher,
38070
38292
  publishedArtifactStore: params.publishedArtifactStore,
38293
+ previewStateStore: params.previewStateStore,
38071
38294
  getDetectedPorts: params.getDetectedPorts
38072
38295
  },
38073
38296
  { onProgress: emitProgress, ...params.log !== void 0 && { log: params.log } }
@@ -39178,38 +39401,22 @@ async function rehydrateCollabQueues(persistence, taskManager, log) {
39178
39401
  })
39179
39402
  );
39180
39403
  }
39181
- function isPreviewData(raw) {
39182
- return typeof raw === "object" && raw !== null && !Array.isArray(raw);
39183
- }
39184
- function collectOwnedPreviews(canvasRepo, taskId, selfUserId) {
39185
- try {
39186
- const elements = canvasRepo.getElements(taskId);
39187
- const ports = [];
39188
- for (const el of elements) {
39189
- if (el.data.type !== "preview") continue;
39190
- const data = isPreviewData(el.data.data) ? el.data.data : null;
39191
- if (!data || data.ownerUserId !== selfUserId) continue;
39192
- if (typeof data.port === "number") ports.push(data.port);
39193
- }
39194
- return ports;
39195
- } catch {
39196
- return [];
39197
- }
39404
+ function collectOwnedPreviews(_canvasRepo, _taskId, _selfUserId) {
39405
+ return [];
39198
39406
  }
39199
39407
  async function rehydrateCanvasPreviews(canvasRepo, previewProxy, selfUserId, taskStateStore, log) {
39200
39408
  const tasks = await taskStateStore.listTasks();
39201
39409
  const refs = [];
39202
39410
  for (const taskId of Object.keys(tasks)) {
39203
39411
  if (!await canvasRepo.hasLocalDoc(taskId)) continue;
39204
- for (const port of collectOwnedPreviews(canvasRepo, taskId, selfUserId)) {
39205
- refs.push({ taskId, port });
39412
+ for (const ref of collectOwnedPreviews(canvasRepo, taskId, selfUserId)) {
39413
+ refs.push(ref);
39206
39414
  }
39207
39415
  }
39208
39416
  const warmedTaskIds = /* @__PURE__ */ new Set();
39209
39417
  for (const { taskId, port } of refs) {
39210
39418
  try {
39211
- const newProxyPort = await previewProxy.acquire(port);
39212
- await reconcileProxyPortInCanvas(canvasRepo, taskId, port, newProxyPort);
39419
+ await previewProxy.acquire(port);
39213
39420
  warmedTaskIds.add(taskId);
39214
39421
  log({ event: "canvas_preview_warmed", taskId, port });
39215
39422
  } catch (err) {
@@ -39223,19 +39430,6 @@ async function rehydrateCanvasPreviews(canvasRepo, previewProxy, selfUserId, tas
39223
39430
  }
39224
39431
  return Array.from(warmedTaskIds);
39225
39432
  }
39226
- async function reconcileProxyPortInCanvas(canvasRepo, taskId, upstreamPort, newProxyPort) {
39227
- if (!await canvasRepo.hasLocalDoc(taskId)) return;
39228
- for (const el of canvasRepo.getElements(taskId)) {
39229
- if (el.data.type !== "preview") continue;
39230
- const rec = el.data.data;
39231
- if (!isPreviewData(rec)) continue;
39232
- if (rec.port !== upstreamPort) continue;
39233
- if (rec.proxyPort === newProxyPort) continue;
39234
- canvasRepo.updateElement(taskId, el.id, {
39235
- data: { ...rec, proxyPort: newProxyPort }
39236
- });
39237
- }
39238
- }
39239
39433
  async function migratePinnedToFavoriteTasks(taskStateStore, userSettingsStore, log) {
39240
39434
  const settings = await userSettingsStore.getSettings();
39241
39435
  if (settings.favoriteTasks.length > 0) return;
@@ -39245,6 +39439,77 @@ async function migratePinnedToFavoriteTasks(taskStateStore, userSettingsStore, l
39245
39439
  await userSettingsStore.updateSetting("favoriteTasks", pinnedIds);
39246
39440
  log({ event: "pinned_to_favorites_migrated", count: pinnedIds.length });
39247
39441
  }
39442
+ function isObjectRecord(v2) {
39443
+ return typeof v2 === "object" && v2 !== null && !Array.isArray(v2);
39444
+ }
39445
+ function extractLegacyPreviewState(elementId, rawData) {
39446
+ if (!isObjectRecord(rawData)) return null;
39447
+ const ownerUserId = typeof rawData.ownerUserId === "string" && rawData.ownerUserId.length > 0 ? rawData.ownerUserId : null;
39448
+ if (!ownerUserId) return null;
39449
+ const state = { elementId, ownerUserId };
39450
+ let hasLegacyField = false;
39451
+ if (typeof rawData.port === "number" && Number.isInteger(rawData.port) && rawData.port > 0) {
39452
+ state.port = rawData.port;
39453
+ hasLegacyField = true;
39454
+ }
39455
+ if (typeof rawData.url === "string" && rawData.url.length > 0) {
39456
+ state.url = rawData.url;
39457
+ hasLegacyField = true;
39458
+ }
39459
+ if (typeof rawData.proxyPort === "number" && Number.isInteger(rawData.proxyPort) && rawData.proxyPort > 0) {
39460
+ state.proxyPort = rawData.proxyPort;
39461
+ hasLegacyField = true;
39462
+ }
39463
+ if (typeof rawData.initialPath === "string" && rawData.initialPath.length > 0) {
39464
+ state.initialPath = rawData.initialPath;
39465
+ hasLegacyField = true;
39466
+ }
39467
+ if (typeof rawData.projectRoot === "string" && rawData.projectRoot.length > 0) {
39468
+ state.projectRoot = rawData.projectRoot;
39469
+ hasLegacyField = true;
39470
+ }
39471
+ return hasLegacyField ? state : null;
39472
+ }
39473
+ function migrateLegacyPreviewDataForTask(taskId, canvasRepo, store, log) {
39474
+ let seededCount = 0;
39475
+ const elements = canvasRepo.getElements(taskId);
39476
+ for (const el of elements) {
39477
+ if (el.data.type !== "preview") continue;
39478
+ const elementId = String(el.id);
39479
+ if (store.getByElementId(taskId, elementId)) continue;
39480
+ const legacy = extractLegacyPreviewState(elementId, el.data.data);
39481
+ if (!legacy) continue;
39482
+ store.putState(taskId, legacy);
39483
+ seededCount += 1;
39484
+ log({
39485
+ event: "preview_legacy_migrated",
39486
+ taskId,
39487
+ elementId,
39488
+ port: legacy.port,
39489
+ projectRoot: legacy.projectRoot
39490
+ });
39491
+ }
39492
+ return seededCount;
39493
+ }
39494
+ async function migrateLegacyPreviewData(canvasRepo, previewStateStore, taskStateStore, log) {
39495
+ const tasks = await taskStateStore.listTasks();
39496
+ let totalSeeded = 0;
39497
+ for (const taskId of Object.keys(tasks)) {
39498
+ if (!await canvasRepo.hasLocalDoc(taskId)) continue;
39499
+ try {
39500
+ totalSeeded += migrateLegacyPreviewDataForTask(taskId, canvasRepo, previewStateStore, log);
39501
+ } catch (err) {
39502
+ log({
39503
+ event: "preview_legacy_migrate_task_failed",
39504
+ taskId,
39505
+ error: err instanceof Error ? err.message : String(err)
39506
+ });
39507
+ }
39508
+ }
39509
+ if (totalSeeded > 0) {
39510
+ log({ event: "preview_legacy_migrate_complete", seededCount: totalSeeded });
39511
+ }
39512
+ }
39248
39513
  async function sweepStaleTasks(taskStateStore, taskManager, log) {
39249
39514
  const tasks = await taskStateStore.listTasks();
39250
39515
  const actions = planStaleSweep(tasks, (id) => taskManager.isRunning(id));
@@ -39372,7 +39637,7 @@ function assertNever3(value) {
39372
39637
 
39373
39638
  // src/services/bootstrap/self-update-lock.ts
39374
39639
  import { mkdir as mkdir7, readFile as readFile16, stat as stat5, unlink as unlink6, writeFile as writeFile11 } from "fs/promises";
39375
- import { dirname as dirname10, join as join24 } from "path";
39640
+ import { dirname as dirname9, join as join24 } from "path";
39376
39641
  var LOCK_FILENAME = ".lock";
39377
39642
  var STALE_LOCK_MS = 10 * 60 * 1e3;
39378
39643
  var LockFileSchema = external_exports.object({
@@ -39464,7 +39729,7 @@ async function resolveEexist(shipyardHome, path2, ownerPid, now, isProcessAlive2
39464
39729
  }
39465
39730
  async function tryAcquireLockExclusive(shipyardHome, pid, now, isProcessAlive2) {
39466
39731
  const path2 = lockPath(shipyardHome);
39467
- await mkdir7(dirname10(path2), { recursive: true });
39732
+ await mkdir7(dirname9(path2), { recursive: true });
39468
39733
  const body = JSON.stringify({ pid, startedAt: now() });
39469
39734
  while (true) {
39470
39735
  try {
@@ -40025,7 +40290,7 @@ function createCanvasResourceResolver(deps) {
40025
40290
 
40026
40291
  // src/services/credentials/vault-key-manager.ts
40027
40292
  import { mkdir as mkdir8, readFile as readFile17, writeFile as writeFile12 } from "fs/promises";
40028
- import { dirname as dirname11, join as join26 } from "path";
40293
+ import { dirname as dirname10, join as join26 } from "path";
40029
40294
 
40030
40295
  // src/services/credentials/vault-crypto.ts
40031
40296
  async function generateVaultKey() {
@@ -40111,12 +40376,12 @@ async function resolveMachineId(shipyardHome) {
40111
40376
  } catch {
40112
40377
  }
40113
40378
  const id = globalThis.crypto.randomUUID();
40114
- await mkdir8(dirname11(idPath), { recursive: true });
40379
+ await mkdir8(dirname10(idPath), { recursive: true });
40115
40380
  await writeFile12(idPath, id, { encoding: "utf-8", mode: 384 });
40116
40381
  return id;
40117
40382
  }
40118
40383
  async function saveKeyLocally(keyPath, key) {
40119
- await mkdir8(dirname11(keyPath), { recursive: true });
40384
+ await mkdir8(dirname10(keyPath), { recursive: true });
40120
40385
  await writeFile12(keyPath, key, { encoding: "utf-8", mode: 384 });
40121
40386
  }
40122
40387
 
@@ -40814,7 +41079,7 @@ function createGitCheckpointService() {
40814
41079
  import { createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
40815
41080
 
40816
41081
  // src/services/harness/comment-server.ts
40817
- import { randomUUID as randomUUID3 } from "crypto";
41082
+ import { randomUUID as randomUUID4 } from "crypto";
40818
41083
  import { access as access2, readFile as readFile20 } from "fs/promises";
40819
41084
  import { join as join29, relative as relative3 } from "path";
40820
41085
  var TOOL_DESCRIPTION = [
@@ -40933,7 +41198,7 @@ function resolveShortCommentId(annotations, input) {
40933
41198
  }
40934
41199
  function buildReplyAnnotation(parent, body) {
40935
41200
  const base3 = {
40936
- commentId: randomUUID3(),
41201
+ commentId: randomUUID4(),
40937
41202
  body,
40938
41203
  authorName: "Claude Code",
40939
41204
  authorKind: "agent",
@@ -41235,7 +41500,7 @@ async function handleReplyComment(ctx, parentCommentId, comment2) {
41235
41500
  async function handlePlanComment(ctx, anchorText, comment2) {
41236
41501
  const annotation = {
41237
41502
  annotationType: "plan-text",
41238
- commentId: randomUUID3(),
41503
+ commentId: randomUUID4(),
41239
41504
  anchorText,
41240
41505
  anchorContext: null,
41241
41506
  body: comment2,
@@ -41285,7 +41550,7 @@ async function handleDiffComment(ctx, filePath, lineNumber, comment2) {
41285
41550
  const lineContentHash = lineContent ? djb2Hash(lineContent) : "";
41286
41551
  const annotation = {
41287
41552
  annotationType: "diff-hunk",
41288
- commentId: randomUUID3(),
41553
+ commentId: randomUUID4(),
41289
41554
  filePath: normalizedPath,
41290
41555
  lineNumber,
41291
41556
  lineContent,
@@ -41977,7 +42242,7 @@ var PresentPortSchema = external_exports.object({
41977
42242
  width: external_exports.number().int().positive().optional().describe("Canvas only."),
41978
42243
  height: external_exports.number().int().positive().optional().describe("Canvas only."),
41979
42244
  projectRoot: external_exports.string().optional().describe(
41980
- "Absolute path to the dev server's package.json directory. Overrides cwd-based auto-detection. Provide this when you know the project root (e.g. the app you just spawned) \u2014 it makes the canvas publish URL stable across port changes. Use for Docker / remote / monorepo cases where lsof cwd detection would land on the wrong package.json."
42245
+ "Absolute path to the dev server's package.json directory. Binds this preview to a stable app identity. Restarting your dev server on a new port (or a fresh `present` call) with the same projectRoot will reuse the same canvas element \u2014 no duplicate frames, and the published URL is stable across port restarts. Without projectRoot, every port change creates a new element."
41981
42246
  )
41982
42247
  });
41983
42248
  var PresentUrlSchema = external_exports.object({
@@ -42006,7 +42271,7 @@ var PresentToolInputShape = {
42006
42271
  height: external_exports.number().int().positive().optional().describe("Canvas only; ignored elsewhere."),
42007
42272
  initialPath: external_exports.string().optional().describe('SPA route for source="port" on canvas (e.g. "/settings").'),
42008
42273
  projectRoot: external_exports.string().optional().describe(
42009
- `Absolute path to the dev server's package.json dir (source="port" only). Overrides lsof cwd auto-detect. Provide when you know it \u2014 makes the publish URL stable across port restarts.`
42274
+ `Absolute path to the dev server's package.json dir (source="port" only). Binds this preview to a stable app identity. Restarting your dev server on a new port with the same projectRoot will reuse the same canvas element \u2014 no duplicate frames, and the published URL is stable across port restarts. Without projectRoot, every port change creates a new element.`
42010
42275
  ),
42011
42276
  force: external_exports.boolean().optional().describe("Re-apply an already-placed slug to canvas after editing. Slug+canvas only.")
42012
42277
  };
@@ -51285,6 +51550,7 @@ function createHarnessMcpServer(ctx) {
51285
51550
  projectRoot: ctx.projectRoot,
51286
51551
  previewProxy: ctx.previewProxy,
51287
51552
  publishedArtifactStore: ctx.publishedArtifactStore,
51553
+ previewStateStore: ctx.previewStateStore,
51288
51554
  getDetectedPorts: ctx.getDetectedPorts
51289
51555
  };
51290
51556
  const server = createSdkMcpServer({
@@ -51307,37 +51573,53 @@ function createHarnessMcpServer(ctx) {
51307
51573
  }
51308
51574
 
51309
51575
  // src/services/harness/visualization-file-watcher.ts
51310
- import { randomUUID as randomUUID4 } from "crypto";
51576
+ import { randomUUID as randomUUID5 } from "crypto";
51311
51577
  import { watch as watch3 } from "fs";
51312
51578
  import { mkdir as mkdir10, readFile as readFile22, rename as rename9, writeFile as writeFile15 } from "fs/promises";
51313
- import { dirname as dirname12, join as join31 } from "path";
51579
+ import { dirname as dirname11, join as join31 } from "path";
51314
51580
  var PREVIEW_DEFAULT_W = 1200;
51315
51581
  var PREVIEW_DEFAULT_H = 800;
51316
51582
  function previewDataToLoroValue(data) {
51317
- const out = { ownerUserId: data.ownerUserId };
51318
- if (data.port !== void 0) out.port = data.port;
51319
- if (data.url !== void 0) out.url = data.url;
51320
- if (data.proxyPort !== void 0) out.proxyPort = data.proxyPort;
51321
- if (data.initialPath !== void 0) out.initialPath = data.initialPath;
51322
- return out;
51583
+ return { ownerUserId: data.ownerUserId };
51323
51584
  }
51324
- function isObjectRecord(v2) {
51585
+ function isObjectRecord2(v2) {
51325
51586
  return typeof v2 === "object" && v2 !== null && !Array.isArray(v2);
51326
51587
  }
51327
51588
  function extractPreviewData(raw) {
51328
- if (!isObjectRecord(raw)) return null;
51589
+ if (!isObjectRecord2(raw)) return null;
51329
51590
  const out = {};
51330
- if (typeof raw.port === "number") out.port = raw.port;
51331
- if (typeof raw.url === "string") out.url = raw.url;
51332
- if (typeof raw.proxyPort === "number") out.proxyPort = raw.proxyPort;
51333
51591
  if (typeof raw.ownerUserId === "string") out.ownerUserId = raw.ownerUserId;
51334
- if (typeof raw.initialPath === "string") out.initialPath = raw.initialPath;
51592
+ return out;
51593
+ }
51594
+ function buildPreviewState(elementId, params, ownerUserId, existing) {
51595
+ const state = { elementId, ownerUserId };
51596
+ const port = params.port ?? existing?.port;
51597
+ if (port !== void 0) state.port = port;
51598
+ const url = params.url ?? existing?.url;
51599
+ if (url !== void 0) state.url = url;
51600
+ const proxyPort = params.proxyPort ?? existing?.proxyPort;
51601
+ if (proxyPort !== void 0) state.proxyPort = proxyPort;
51602
+ const initialPath = params.initialPath ?? existing?.initialPath;
51603
+ if (initialPath !== void 0) state.initialPath = initialPath;
51604
+ const projectRoot = params.projectRoot ?? existing?.projectRoot;
51605
+ if (typeof projectRoot === "string" && projectRoot.length > 0) state.projectRoot = projectRoot;
51606
+ return state;
51607
+ }
51608
+ function mergedPreviewData(data, state) {
51609
+ const out = {
51610
+ ownerUserId: data.ownerUserId
51611
+ };
51612
+ if (state.port !== void 0) out.port = state.port;
51613
+ if (state.url !== void 0) out.url = state.url;
51614
+ if (state.proxyPort !== void 0) out.proxyPort = state.proxyPort;
51615
+ if (state.initialPath !== void 0) out.initialPath = state.initialPath;
51616
+ if (state.projectRoot !== void 0) out.projectRoot = state.projectRoot;
51335
51617
  return out;
51336
51618
  }
51337
51619
  var DEBOUNCE_MS3 = 200;
51338
51620
  async function atomicWrite2(filePath, content) {
51339
- await mkdir10(dirname12(filePath), { recursive: true });
51340
- const tmpPath = `${filePath}.${randomUUID4()}.tmp`;
51621
+ await mkdir10(dirname11(filePath), { recursive: true });
51622
+ const tmpPath = `${filePath}.${randomUUID5()}.tmp`;
51341
51623
  await writeFile15(tmpPath, content, "utf-8");
51342
51624
  await rename9(tmpPath, filePath);
51343
51625
  }
@@ -51506,7 +51788,7 @@ var VisualizationFileWatcher = class {
51506
51788
  if (this.#disposed) return;
51507
51789
  if (this.#watchedFiles.has(filePath)) return;
51508
51790
  this.#watchedFiles.add(filePath);
51509
- const dirPath = dirname12(filePath);
51791
+ const dirPath = dirname11(filePath);
51510
51792
  const existing = this.#dirWatchers.get(dirPath);
51511
51793
  if (existing) {
51512
51794
  existing.refCount += 1;
@@ -51546,7 +51828,7 @@ var VisualizationFileWatcher = class {
51546
51828
  }
51547
51829
  #stopFileWatch(filePath) {
51548
51830
  if (!this.#watchedFiles.delete(filePath)) return;
51549
- const dirPath = dirname12(filePath);
51831
+ const dirPath = dirname11(filePath);
51550
51832
  const entry = this.#dirWatchers.get(dirPath);
51551
51833
  if (entry) {
51552
51834
  entry.refCount -= 1;
@@ -51564,39 +51846,56 @@ var VisualizationFileWatcher = class {
51564
51846
  /**
51565
51847
  * Idempotent create-or-update of a preview canvas element.
51566
51848
  *
51567
- * The match key is `{taskId, port || url}`: if an element already exists
51568
- * with the same type='preview' and matching port (or url), we update its
51569
- * `data` in place and return its id. Otherwise we create a new element.
51849
+ * Dedupe order (pointer-only CRDT, PROTOCOL_VERSION 36):
51850
+ * 1. If params has `projectRoot` AND `previewStateStore` already has
51851
+ * an entry with a matching `projectRoot` update that entry's
51852
+ * port / url / proxyPort in place (same elementId, no new canvas
51853
+ * element). This is the port-swap upsert: Vite restarts on a new
51854
+ * port, daemon calls `present({projectRoot})` again, and the
51855
+ * existing element's iframe URL flips — no duplicates.
51856
+ * 2. Else if the CRDT has a `preview` element with matching
51857
+ * `ownerUserId` AND the store has an entry with matching port —
51858
+ * update that entry. This covers the legacy / no-projectRoot path
51859
+ * where dedupe keys off the live port alone.
51860
+ * 3. Else create a new canvas element (pointer-only) + a new store
51861
+ * entry carrying the mutable state.
51862
+ *
51863
+ * In every case, the store is written with the element's projectRoot
51864
+ * (if known) so the publish pipeline can look it up by elementId.
51570
51865
  *
51571
- * The check+write happens in the same tick — this function is synchronous
51572
- * with respect to the canvas repository, so a concurrent caller cannot
51573
- * race the lookup. (loro-crdt mutations are synchronous via `change()`.)
51866
+ * loro-crdt mutations are synchronous via `change()`, so the
51867
+ * check+write against the CRDT cannot race a concurrent caller.
51574
51868
  */
51575
51869
  createOrUpdatePreviewElement(params) {
51576
51870
  const data = this.registry.planPreviewElement(params);
51577
51871
  const loroData = previewDataToLoroValue(data);
51872
+ const store = this.#deps.previewStateStore ?? null;
51873
+ if (store && params.projectRoot !== void 0 && params.projectRoot.length > 0) {
51874
+ const existing = store.findByProjectRoot(params.taskId, params.projectRoot);
51875
+ if (existing) {
51876
+ const elementId2 = existing.elementId;
51877
+ const mergedState = buildPreviewState(elementId2, params, data.ownerUserId, existing);
51878
+ store.putState(params.taskId, mergedState);
51879
+ return {
51880
+ kind: "updated",
51881
+ elementId: elementId2,
51882
+ data: mergedPreviewData(data, mergedState)
51883
+ };
51884
+ }
51885
+ }
51578
51886
  const existingElements = this.#deps.canvasRepo.getElements(params.taskId);
51579
- const match2 = existingElements.find((el) => {
51580
- if (el.data.type !== "preview") return false;
51581
- const elData = extractPreviewData(el.data.data);
51582
- if (!elData) return false;
51583
- if (params.port !== void 0) return elData.port === params.port;
51584
- if (params.url !== void 0) return elData.url === params.url;
51585
- return false;
51586
- });
51587
- if (match2) {
51588
- const matchTreeId = match2.id;
51589
- const preservedData = (() => {
51590
- if (data.projectRoot !== void 0) return data;
51591
- const existing = extractPreviewData(match2.data.data);
51592
- if (existing?.projectRoot) {
51593
- return { ...data, projectRoot: existing.projectRoot };
51594
- }
51595
- return data;
51596
- })();
51597
- const preservedLoroData = previewDataToLoroValue(preservedData);
51598
- this.#deps.canvasRepo.updateElement(params.taskId, matchTreeId, { data: preservedLoroData });
51599
- return { kind: "updated", elementId: `${match2.id}`, data: preservedData };
51887
+ const legacyMatch = store ? this.#findLegacyPreviewMatch(existingElements, params, data.ownerUserId) : null;
51888
+ if (legacyMatch && store) {
51889
+ const matchTreeId = legacyMatch.id;
51890
+ const elementId2 = `${matchTreeId}`;
51891
+ const existingState = store.getByElementId(params.taskId, elementId2);
51892
+ const mergedState = buildPreviewState(elementId2, params, data.ownerUserId, existingState);
51893
+ store.putState(params.taskId, mergedState);
51894
+ return {
51895
+ kind: "updated",
51896
+ elementId: elementId2,
51897
+ data: mergedPreviewData(data, mergedState)
51898
+ };
51600
51899
  }
51601
51900
  const positionable = existingElements.map((el) => ({
51602
51901
  x: el.data.x,
@@ -51616,7 +51915,26 @@ var VisualizationFileWatcher = class {
51616
51915
  zIndex: position.zIndex,
51617
51916
  data: loroData
51618
51917
  });
51619
- return { kind: "created", elementId: `${nodeId}`, data };
51918
+ const elementId = `${nodeId}`;
51919
+ if (store) {
51920
+ const freshState = buildPreviewState(elementId, params, data.ownerUserId, void 0);
51921
+ store.putState(params.taskId, freshState);
51922
+ }
51923
+ return { kind: "created", elementId, data };
51924
+ }
51925
+ #findLegacyPreviewMatch(existingElements, params, ownerUserId) {
51926
+ const store = this.#deps.previewStateStore;
51927
+ if (!store) return null;
51928
+ for (const el of existingElements) {
51929
+ if (el.data.type !== "preview") continue;
51930
+ const elData = extractPreviewData(el.data.data);
51931
+ if (!elData || elData.ownerUserId !== ownerUserId) continue;
51932
+ const state = store.getByElementId(params.taskId, `${el.id}`);
51933
+ if (!state) continue;
51934
+ if (params.port !== void 0 && state.port === params.port) return el;
51935
+ if (params.url !== void 0 && state.url === params.url) return el;
51936
+ }
51937
+ return null;
51620
51938
  }
51621
51939
  async #createCanvasElement(effect) {
51622
51940
  const existingElements = this.#deps.canvasRepo.getElements(effect.taskId);
@@ -51757,17 +52075,7 @@ var VisualizationRegistry = class {
51757
52075
  * idempotency key derived from `{taskId, port || url}`.
51758
52076
  */
51759
52077
  planPreviewElement(params) {
51760
- const data = {
51761
- ownerUserId: params.ownerUserId
51762
- };
51763
- if (params.port !== void 0) data.port = params.port;
51764
- if (params.url !== void 0) data.url = params.url;
51765
- if (params.proxyPort !== void 0) data.proxyPort = params.proxyPort;
51766
- if (params.initialPath !== void 0) data.initialPath = params.initialPath;
51767
- if (typeof params.projectRoot === "string" && params.projectRoot.length > 0) {
51768
- data.projectRoot = params.projectRoot;
51769
- }
51770
- return data;
52078
+ return { ownerUserId: params.ownerUserId };
51771
52079
  }
51772
52080
  /**
51773
52081
  * Force-refresh the canvas element for a presented visualization, regardless
@@ -75398,7 +75706,7 @@ var ScheduleEvaluator = class {
75398
75706
  };
75399
75707
 
75400
75708
  // src/services/serve-factory-helpers.ts
75401
- import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
75709
+ import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
75402
75710
  import { homedir as homedir3 } from "os";
75403
75711
  import { join as join34 } from "path";
75404
75712
 
@@ -77978,7 +78286,7 @@ function rehydrateVizRegistry(registry, vizWatcher, resolvedTaskId, vizDir, log)
77978
78286
  const registryPath = join34(vizDir, resolvedTaskId, "registry.json");
77979
78287
  if (!existsSync6(registryPath)) return Promise.resolve();
77980
78288
  try {
77981
- const raw = readFileSync6(registryPath, "utf-8");
78289
+ const raw = readFileSync7(registryPath, "utf-8");
77982
78290
  const parsed = PersistedVisualizationRegistrySchema.safeParse(JSON.parse(raw));
77983
78291
  if (parsed.success) {
77984
78292
  const effects = registry.rehydrate(resolvedTaskId, parsed.data);
@@ -78003,7 +78311,7 @@ function rehydrateVizRegistry(registry, vizWatcher, resolvedTaskId, vizDir, log)
78003
78311
  }
78004
78312
  function prehydrateAllVizWatchers(vizDir, getOrCreateVizWatcher) {
78005
78313
  try {
78006
- for (const entry of readdirSync2(vizDir, { withFileTypes: true })) {
78314
+ for (const entry of readdirSync3(vizDir, { withFileTypes: true })) {
78007
78315
  if (entry.isDirectory() && existsSync6(join34(vizDir, entry.name, "registry.json"))) {
78008
78316
  getOrCreateVizWatcher(entry.name);
78009
78317
  }
@@ -78729,7 +79037,7 @@ function resolveModelFamily(model) {
78729
79037
  if (model.includes("haiku")) return "haiku";
78730
79038
  return "sonnet";
78731
79039
  }
78732
- var CC_VERSION = "2.1.111";
79040
+ var CC_VERSION = "2.1.120";
78733
79041
  var BILLING_HEADER_PREFIX = `x-anthropic-billing-header: cc_version=${CC_VERSION}; cc_entrypoint=sdk-ts;`;
78734
79042
  function buildConversationSystemPrompt(model) {
78735
79043
  const family = resolveModelFamily(model);
@@ -78825,7 +79133,7 @@ async function resolveDirectApiCredentials(method) {
78825
79133
  }
78826
79134
 
78827
79135
  // src/services/session/direct-api-subprocess.ts
78828
- import { randomUUID as randomUUID5 } from "crypto";
79136
+ import { randomUUID as randomUUID6 } from "crypto";
78829
79137
 
78830
79138
  // ../../node_modules/.pnpm/@anthropic-ai+sdk@0.90.0_zod@4.3.6/node_modules/@anthropic-ai/sdk/internal/tslib.mjs
78831
79139
  function __classPrivateFieldSet(receiver, state, value, kind, f2) {
@@ -84994,7 +85302,7 @@ var DirectApiSubprocess = class _DirectApiSubprocess {
84994
85302
  const allToolNames = [...SERVER_SIDE_TOOLS.map((t) => t.name), ...tools.map((t) => t.name)];
84995
85303
  onEvent({
84996
85304
  type: "init_received",
84997
- sessionId: `direct-api-${randomUUID5()}`,
85305
+ sessionId: `direct-api-${randomUUID6()}`,
84998
85306
  metadata: {
84999
85307
  tools: allToolNames,
85000
85308
  skills: [],
@@ -85347,8 +85655,7 @@ async function handlePublish(ctx, input) {
85347
85655
  previewPort: target.previewPort,
85348
85656
  ...target.canvasElementId !== void 0 && {
85349
85657
  canvasElementId: target.canvasElementId
85350
- },
85351
- ...target.projectRoot !== void 0 && { projectRoot: target.projectRoot }
85658
+ }
85352
85659
  } : { kind: "canvasElementId", canvasElementId: target.canvasElementId };
85353
85660
  const outcome = await runPublish(
85354
85661
  {
@@ -85361,6 +85668,7 @@ async function handlePublish(ctx, input) {
85361
85668
  taskId: ctx.taskId,
85362
85669
  vizWatcher: ctx.vizWatcher,
85363
85670
  publishedArtifactStore: ctx.publishedArtifactStore,
85671
+ previewStateStore: ctx.previewStateStore,
85364
85672
  getDetectedPorts: ctx.getDetectedPorts
85365
85673
  },
85366
85674
  { onProgress: () => {
@@ -85680,7 +85988,7 @@ function createShipyardResolver() {
85680
85988
 
85681
85989
  // src/services/storage/annotation-store.ts
85682
85990
  import { mkdir as mkdir11, readFile as readFile25, rename as rename10, writeFile as writeFile16 } from "fs/promises";
85683
- import { dirname as dirname13, join as join35 } from "path";
85991
+ import { dirname as dirname12, join as join35 } from "path";
85684
85992
  var LegacyBaseFields = external_exports.object({
85685
85993
  commentId: external_exports.string(),
85686
85994
  body: external_exports.string(),
@@ -85732,7 +86040,7 @@ function buildAnnotationStore(dataDir) {
85732
86040
  }
85733
86041
  async function atomicWrite4(taskId, data) {
85734
86042
  const fp = filePath(taskId);
85735
- await mkdir11(dirname13(fp), { recursive: true });
86043
+ await mkdir11(dirname12(fp), { recursive: true });
85736
86044
  const tmpPath = `${fp}.tmp`;
85737
86045
  await writeFile16(tmpPath, JSON.stringify(data, null, 2), "utf-8");
85738
86046
  await rename10(tmpPath, fp);
@@ -86102,7 +86410,7 @@ async function seedBuiltInTemplates(store, builtIns, dismissed) {
86102
86410
 
86103
86411
  // src/services/storage/credentials-vault-store.ts
86104
86412
  import { mkdir as mkdir13, readFile as readFile27, rename as rename12, writeFile as writeFile18 } from "fs/promises";
86105
- import { dirname as dirname14 } from "path";
86413
+ import { dirname as dirname13 } from "path";
86106
86414
  function buildCredentialsVaultStore(filePath) {
86107
86415
  let cache2 = null;
86108
86416
  const listeners = /* @__PURE__ */ new Set();
@@ -86116,7 +86424,7 @@ function buildCredentialsVaultStore(filePath) {
86116
86424
  }
86117
86425
  }
86118
86426
  async function ensureDir() {
86119
- await mkdir13(dirname14(filePath), { recursive: true });
86427
+ await mkdir13(dirname13(filePath), { recursive: true });
86120
86428
  }
86121
86429
  async function readStore() {
86122
86430
  if (cache2) return cache2;
@@ -86646,9 +86954,9 @@ function buildObservableConversationStore(inner) {
86646
86954
  }
86647
86955
 
86648
86956
  // src/services/storage/projects-store.ts
86649
- import { randomUUID as randomUUID6 } from "crypto";
86957
+ import { randomUUID as randomUUID7 } from "crypto";
86650
86958
  import { mkdir as mkdir15, readFile as readFile29, rename as rename14, writeFile as writeFile20 } from "fs/promises";
86651
- import { dirname as dirname15 } from "path";
86959
+ import { dirname as dirname14 } from "path";
86652
86960
  var ProjectsArraySchema = external_exports.array(ProjectSchema);
86653
86961
  function buildProjectsStore(filePath) {
86654
86962
  let cache2 = null;
@@ -86663,7 +86971,7 @@ function buildProjectsStore(filePath) {
86663
86971
  }
86664
86972
  }
86665
86973
  async function ensureDir() {
86666
- await mkdir15(dirname15(filePath), { recursive: true });
86974
+ await mkdir15(dirname14(filePath), { recursive: true });
86667
86975
  }
86668
86976
  async function readStore() {
86669
86977
  if (cache2) return cache2;
@@ -86723,7 +87031,7 @@ function buildProjectsStore(filePath) {
86723
87031
  const current2 = await readStore();
86724
87032
  const nextOrder = current2.length === 0 ? 0 : Math.max(...current2.map((p2) => p2.order)) + 1;
86725
87033
  const project = {
86726
- id: randomUUID6(),
87034
+ id: randomUUID7(),
86727
87035
  name,
86728
87036
  order: nextOrder,
86729
87037
  createdAt: Date.now(),
@@ -86787,7 +87095,7 @@ function buildProjectsStore(filePath) {
86787
87095
 
86788
87096
  // src/services/storage/rate-limit-store.ts
86789
87097
  import { mkdir as mkdir16, readFile as readFile30, rename as rename15, writeFile as writeFile21 } from "fs/promises";
86790
- import { dirname as dirname16, join as join38 } from "path";
87098
+ import { dirname as dirname15, join as join38 } from "path";
86791
87099
  var RATE_LIMIT_STORE_VERSION = 2;
86792
87100
  var RateLimitRecordSchema = external_exports.object({
86793
87101
  info: RateLimitInfoSchema,
@@ -86969,7 +87277,7 @@ function migrateV1toV2(v1) {
86969
87277
  return { schemaVersion: RATE_LIMIT_STORE_VERSION, records };
86970
87278
  }
86971
87279
  async function atomicWrite3(filePath, data) {
86972
- await mkdir16(dirname16(filePath), { recursive: true });
87280
+ await mkdir16(dirname15(filePath), { recursive: true });
86973
87281
  const tmpPath = `${filePath}.tmp`;
86974
87282
  await writeFile21(tmpPath, JSON.stringify(data, null, 2), "utf-8");
86975
87283
  await rename15(tmpPath, filePath);
@@ -86980,7 +87288,7 @@ import { join as join39 } from "path";
86980
87288
 
86981
87289
  // src/services/storage/json-document-store.ts
86982
87290
  import { mkdir as mkdir17, readFile as readFile31, rename as rename16, writeFile as writeFile22 } from "fs/promises";
86983
- import { dirname as dirname17 } from "path";
87291
+ import { dirname as dirname16 } from "path";
86984
87292
  function buildJsonDocumentStore(opts) {
86985
87293
  const { filePath, recordSchema, currentVersion, migrate } = opts;
86986
87294
  let cache2 = null;
@@ -86995,7 +87303,7 @@ function buildJsonDocumentStore(opts) {
86995
87303
  }
86996
87304
  }
86997
87305
  async function ensureDir() {
86998
- await mkdir17(dirname17(filePath), { recursive: true });
87306
+ await mkdir17(dirname16(filePath), { recursive: true });
86999
87307
  }
87000
87308
  async function readStore() {
87001
87309
  if (cache2) return cache2;
@@ -87231,7 +87539,7 @@ function buildTemplateStore(dataDir) {
87231
87539
 
87232
87540
  // src/services/storage/user-settings-store.ts
87233
87541
  import { mkdir as mkdir19, readFile as readFile33, rename as rename18, writeFile as writeFile24 } from "fs/promises";
87234
- import { dirname as dirname18 } from "path";
87542
+ import { dirname as dirname17 } from "path";
87235
87543
  function buildUserSettingsStore(filePath) {
87236
87544
  let cache2 = null;
87237
87545
  const listeners = /* @__PURE__ */ new Set();
@@ -87245,7 +87553,7 @@ function buildUserSettingsStore(filePath) {
87245
87553
  }
87246
87554
  }
87247
87555
  async function ensureDir() {
87248
- await mkdir19(dirname18(filePath), { recursive: true });
87556
+ await mkdir19(dirname17(filePath), { recursive: true });
87249
87557
  }
87250
87558
  async function readStore() {
87251
87559
  if (cache2) return cache2;
@@ -87336,7 +87644,7 @@ function buildUserSettingsStore(filePath) {
87336
87644
  }
87337
87645
 
87338
87646
  // src/services/task/orchestrator/task.ts
87339
- import { randomUUID as randomUUID9 } from "crypto";
87647
+ import { randomUUID as randomUUID10 } from "crypto";
87340
87648
  import { join as join50 } from "path";
87341
87649
 
87342
87650
  // src/services/event-batching.ts
@@ -90190,13 +90498,13 @@ function skipForMainChannel(content) {
90190
90498
  }
90191
90499
 
90192
90500
  // src/services/plan/plan-handler.ts
90193
- import { existsSync as existsSync8, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
90501
+ import { existsSync as existsSync8, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
90194
90502
  import { readFile as readFile35 } from "fs/promises";
90195
90503
  import { homedir as homedir5 } from "os";
90196
90504
  import { join as join44 } from "path";
90197
90505
 
90198
90506
  // src/services/plan/plan-file-bridge.ts
90199
- import { createHash as createHash3, randomUUID as randomUUID7 } from "crypto";
90507
+ import { createHash as createHash3, randomUUID as randomUUID8 } from "crypto";
90200
90508
  import { existsSync as existsSync7, watch as watch4 } from "fs";
90201
90509
  import { readFile as readFile34, rename as rename19, writeFile as writeFile25 } from "fs/promises";
90202
90510
  import { homedir as homedir4 } from "os";
@@ -90461,7 +90769,7 @@ var PlanFileBridge = class _PlanFileBridge {
90461
90769
  const content = this.#config.planRepo.getContent(this.#config.taskId);
90462
90770
  const hash = contentHash(content);
90463
90771
  if (hash === this.#lastWrittenHash) return;
90464
- const tmpPath = `${this.#filePath}.${randomUUID7()}.tmp`;
90772
+ const tmpPath = `${this.#filePath}.${randomUUID8()}.tmp`;
90465
90773
  await writeFile25(tmpPath, content, "utf-8");
90466
90774
  await rename19(tmpPath, this.#filePath);
90467
90775
  this.#lastWrittenHash = hash;
@@ -91214,7 +91522,7 @@ var PlanHandler = class {
91214
91522
  const plansDir = join44(homedir5(), ".claude", "plans");
91215
91523
  if (!existsSync8(plansDir)) return null;
91216
91524
  try {
91217
- const files = readdirSync3(plansDir).filter((f2) => f2.endsWith(".md")).map((f2) => ({
91525
+ const files = readdirSync4(plansDir).filter((f2) => f2.endsWith(".md")).map((f2) => ({
91218
91526
  path: join44(plansDir, f2),
91219
91527
  mtime: statSync2(join44(plansDir, f2)).mtimeMs
91220
91528
  })).sort((a, b2) => b2.mtime - a.mtime);
@@ -91833,14 +92141,14 @@ function planPostCompactPending(input) {
91833
92141
  }
91834
92142
 
91835
92143
  // src/services/task/compaction-snapshot.ts
91836
- import { randomUUID as randomUUID8 } from "crypto";
92144
+ import { randomUUID as randomUUID9 } from "crypto";
91837
92145
  import { homedir as homedir6 } from "os";
91838
92146
  import { join as pathJoin } from "path";
91839
92147
  function resolveArchiveAbsolutePath(channelId) {
91840
92148
  return pathJoin(homedir6(), ".shipyard", "data", "channels", `${channelId}.jsonl`);
91841
92149
  }
91842
92150
  function planCompactionSnapshot(input) {
91843
- const threadId = randomUUID8();
92151
+ const threadId = randomUUID9();
91844
92152
  const channelId = buildThreadChannelId(input.taskId, threadId);
91845
92153
  const compactionCount = input.messages.filter(
91846
92154
  (m2) => m2.content.some((b2) => b2.type === "compaction_boundary")
@@ -91877,7 +92185,7 @@ function planCompactionSnapshot(input) {
91877
92185
  stats
91878
92186
  };
91879
92187
  const boundaryMessage = {
91880
- messageId: randomUUID8(),
92188
+ messageId: randomUUID9(),
91881
92189
  channelId: input.channelId,
91882
92190
  participantId: input.humanParticipantId,
91883
92191
  senderKind: "human",
@@ -92757,7 +93065,7 @@ var RewindCheckpointHandler = class {
92757
93065
 
92758
93066
  // src/services/task/side-thread-registry.ts
92759
93067
  import { mkdir as mkdir20, readFile as readFile36, rename as rename20, writeFile as writeFile26 } from "fs/promises";
92760
- import { dirname as dirname19, join as join45 } from "path";
93068
+ import { dirname as dirname18, join as join45 } from "path";
92761
93069
  var ThreadFileSchema = external_exports.object({
92762
93070
  threads: external_exports.record(external_exports.string(), ThreadMetadataSchema)
92763
93071
  });
@@ -93118,7 +93426,7 @@ var SideThreadRegistry = class {
93118
93426
  threads[threadId] = entry.metadata;
93119
93427
  }
93120
93428
  const filePath = this.#filePath();
93121
- await mkdir20(dirname19(filePath), { recursive: true });
93429
+ await mkdir20(dirname18(filePath), { recursive: true });
93122
93430
  const tmpPath = `${filePath}.tmp`;
93123
93431
  await writeFile26(tmpPath, JSON.stringify({ threads }, null, 2), "utf-8");
93124
93432
  await rename20(tmpPath, filePath);
@@ -93533,7 +93841,7 @@ async function resolveResources(ctx, content, history2, excludeUris) {
93533
93841
  import { watch as watch5 } from "fs";
93534
93842
  import { readdir as readdir10, readFile as readFile37 } from "fs/promises";
93535
93843
  import { homedir as homedir7 } from "os";
93536
- import { basename as basename5, dirname as dirname20, join as join46 } from "path";
93844
+ import { basename as basename5, dirname as dirname19, join as join46 } from "path";
93537
93845
  var VALID_STATUSES2 = /* @__PURE__ */ new Set(["pending", "in_progress", "completed"]);
93538
93846
  function sanitize(id) {
93539
93847
  return id.replace(/[^a-zA-Z0-9_-]/g, "-");
@@ -93586,7 +93894,7 @@ function createCCTaskFileWatcher(listId, log) {
93586
93894
  let debounceTimer = null;
93587
93895
  const DEBOUNCE_MS4 = 200;
93588
93896
  const targetDirName = basename5(dir);
93589
- const parentDir = dirname20(dir);
93897
+ const parentDir = dirname19(dir);
93590
93898
  function scheduleRead() {
93591
93899
  if (debounceTimer) clearTimeout(debounceTimer);
93592
93900
  debounceTimer = setTimeout(() => {
@@ -96285,7 +96593,7 @@ var Task = class {
96285
96593
  instructions
96286
96594
  });
96287
96595
  const archiveMessages = plan.messagesToCopy.map((msg) => ({
96288
- messageId: randomUUID9(),
96596
+ messageId: randomUUID10(),
96289
96597
  channelId: plan.threadMetadata.channelId,
96290
96598
  participantId: msg.participantId,
96291
96599
  senderKind: msg.senderKind,
@@ -100444,7 +100752,7 @@ function buildThemeStore(dataDir) {
100444
100752
  // src/services/themes/vscode-scanner.ts
100445
100753
  import { readdir as readdir13, readFile as readFile39, stat as stat11 } from "fs/promises";
100446
100754
  import { homedir as homedir9 } from "os";
100447
- import { dirname as dirname21, join as join53, normalize as normalize5, resolve as resolve2 } from "path";
100755
+ import { dirname as dirname20, join as join53, normalize as normalize5, resolve as resolve2 } from "path";
100448
100756
  var VSCodeThemeEntrySchema2 = external_exports.object({
100449
100757
  extensionId: external_exports.string(),
100450
100758
  name: external_exports.string(),
@@ -100599,7 +100907,7 @@ async function readVSCodeThemeWithIncludes(absolutePath) {
100599
100907
  const obj = await readObject(canonical);
100600
100908
  const include = typeof obj.include === "string" ? obj.include : null;
100601
100909
  if (!include) return obj;
100602
- const basePath = resolve2(dirname21(canonical), include);
100910
+ const basePath = resolve2(dirname20(canonical), include);
100603
100911
  const base3 = await walk(basePath, depth + 1);
100604
100912
  return mergeThemeObjects(base3, obj);
100605
100913
  };
@@ -100912,7 +101220,8 @@ async function createDaemon(deps) {
100912
101220
  const watcher = new VisualizationFileWatcher(registry, {
100913
101221
  canvasRepo,
100914
101222
  vizDir,
100915
- log: deps.log
101223
+ log: deps.log,
101224
+ previewStateStore: deps.previewStateStore ?? null
100916
101225
  });
100917
101226
  watcher.setSendControlMessage((msg) => taskManager.broadcastControl(msg));
100918
101227
  watcher.setRehydrationPromise(
@@ -100937,26 +101246,25 @@ async function createDaemon(deps) {
100937
101246
  }
100938
101247
  const trackedPreviewPorts = /* @__PURE__ */ new Map();
100939
101248
  const previewLifecycleSubs = /* @__PURE__ */ new Map();
100940
- function readLocalPreviewPort(dataField, selfUserId) {
100941
- if (!isRecord4(dataField)) return void 0;
100942
- if (dataField.ownerUserId !== selfUserId) return void 0;
100943
- const port = dataField.port;
100944
- return typeof port === "number" ? port : void 0;
101249
+ function isLocalPreviewElement(dataField, selfUserId) {
101250
+ if (!isRecord4(dataField)) return false;
101251
+ return dataField.ownerUserId === selfUserId;
100945
101252
  }
100946
101253
  function collectLocalPreviewPorts(taskId) {
100947
101254
  const current2 = /* @__PURE__ */ new Map();
101255
+ const store2 = deps.previewStateStore ?? null;
101256
+ if (!store2) return current2;
100948
101257
  for (const el of canvasRepo.getElements(taskId)) {
100949
101258
  if (el.data.type !== "preview") continue;
100950
- const port = readLocalPreviewPort(el.data.data, deps.auth.userId);
100951
- if (port !== void 0) current2.set(String(el.id), port);
101259
+ if (!isLocalPreviewElement(el.data.data, deps.auth.userId)) continue;
101260
+ const state = store2.getByElementId(taskId, String(el.id));
101261
+ const port = state?.port;
101262
+ if (typeof port === "number") current2.set(String(el.id), port);
100952
101263
  }
100953
101264
  return current2;
100954
101265
  }
100955
- function reconcilePreviewRefs(taskId) {
100956
- const current2 = collectLocalPreviewPorts(taskId);
100957
- const tracked = trackedPreviewPorts.get(taskId) ?? /* @__PURE__ */ new Map();
100958
- trackedPreviewPorts.set(taskId, current2);
100959
- for (const [elementId, port] of tracked) {
101266
+ function releaseRemovedProxyRefs(previous, current2) {
101267
+ for (const [elementId, port] of previous) {
100960
101268
  const stillPresent = current2.get(elementId);
100961
101269
  if (stillPresent === void 0 || stillPresent !== port) {
100962
101270
  deps.previewProxy.release(port).catch(() => {
@@ -100964,6 +101272,22 @@ async function createDaemon(deps) {
100964
101272
  }
100965
101273
  }
100966
101274
  }
101275
+ function sweepOrphanedPreviewState(taskId, liveElementIds) {
101276
+ const store2 = deps.previewStateStore ?? null;
101277
+ if (!store2) return;
101278
+ for (const state of store2.listByTask(taskId)) {
101279
+ if (liveElementIds.has(state.elementId)) continue;
101280
+ const el = canvasRepo.getElements(taskId).find((e) => String(e.id) === state.elementId);
101281
+ if (!el) store2.deleteByElementId(taskId, state.elementId);
101282
+ }
101283
+ }
101284
+ function reconcilePreviewRefs(taskId) {
101285
+ const current2 = collectLocalPreviewPorts(taskId);
101286
+ const tracked = trackedPreviewPorts.get(taskId) ?? /* @__PURE__ */ new Map();
101287
+ trackedPreviewPorts.set(taskId, current2);
101288
+ releaseRemovedProxyRefs(tracked, current2);
101289
+ sweepOrphanedPreviewState(taskId, new Set(current2.keys()));
101290
+ }
100967
101291
  function ensurePreviewLifecycleTracking(taskId) {
100968
101292
  if (previewLifecycleSubs.has(taskId)) return;
100969
101293
  const unsub = canvasRepo.subscribe(
@@ -101003,6 +101327,7 @@ async function createDaemon(deps) {
101003
101327
  getAuthToken: () => deps.auth.token,
101004
101328
  projectRoot: cwd ?? deps.workspaceRoot,
101005
101329
  publishedArtifactStore: deps.publishedArtifactStore ?? null,
101330
+ previewStateStore: deps.previewStateStore ?? null,
101006
101331
  getDetectedPorts: deps.getDetectedPorts ?? (() => [])
101007
101332
  });
101008
101333
  const options = buildSpawnOptions({
@@ -101042,6 +101367,7 @@ async function createDaemon(deps) {
101042
101367
  getAuthToken: () => deps.auth.token,
101043
101368
  projectRoot,
101044
101369
  publishedArtifactStore: deps.publishedArtifactStore ?? null,
101370
+ previewStateStore: deps.previewStateStore ?? null,
101045
101371
  getDetectedPorts: deps.getDetectedPorts ?? (() => [])
101046
101372
  });
101047
101373
  deps.log({ event: "conversation_backend", backend: "direct-api", model, taskId });
@@ -101334,6 +101660,9 @@ async function createDaemon(deps) {
101334
101660
  await sweepStaleTasks(taskStateStore, taskManager, deps.log);
101335
101661
  await migratePinnedToFavoriteTasks(taskStateStore, userSettingsStore, deps.log);
101336
101662
  await rehydrateCollabQueues(collabQueuePersistence, taskManager, deps.log);
101663
+ if (deps.previewStateStore) {
101664
+ await migrateLegacyPreviewData(canvasRepo, deps.previewStateStore, taskStateStore, deps.log);
101665
+ }
101337
101666
  const warmedPreviewTasks = await rehydrateCanvasPreviews(
101338
101667
  canvasRepo,
101339
101668
  deps.previewProxy,
@@ -101931,6 +102260,14 @@ function filterOutboundForCollab(msg, collabTaskId, getTaskSync) {
101931
102260
  case "background_agent_update":
101932
102261
  case "viz_content":
101933
102262
  case "viz_content_batch":
102263
+ /**
102264
+ * published_artifacts_state and preview_elements_state are per-task
102265
+ * but visible to all collaborators so the chip + iframe on each
102266
+ * canvas element reflect the same reality for everyone. Same rule
102267
+ * as the other task-scoped messages: match taskId or drop.
102268
+ */
102269
+ case "published_artifacts_state":
102270
+ case "preview_elements_state":
101934
102271
  if ("taskId" in msg && msg.taskId !== collabTaskId) return null;
101935
102272
  return msg;
101936
102273
  /** Preview: collab peers need port detection and preview targets */
@@ -101956,13 +102293,6 @@ function filterOutboundForCollab(msg, collabTaskId, getTaskSync) {
101956
102293
  case "publish_progress":
101957
102294
  case "publish_result":
101958
102295
  return null;
101959
- /**
101960
- * published_artifacts_state is per-task but visible to all collaborators
101961
- * so the chip on each element reflects the same reality for everyone.
101962
- * Filter by taskId so a peer only sees state for tasks they participate in.
101963
- */
101964
- case "published_artifacts_state":
101965
- return msg.taskId === collabTaskId ? msg : null;
101966
102296
  /** Exhaustiveness check: compile error if a new type is unhandled */
101967
102297
  default: {
101968
102298
  const _exhaustive = msg;
@@ -102516,7 +102846,7 @@ import { readdir as readdir14 } from "fs/promises";
102516
102846
  import { execFile as execFile8, spawn as spawn5 } from "child_process";
102517
102847
  import { closeSync, openSync } from "fs";
102518
102848
  import { access as access3, chmod as chmod2, constants, mkdir as mkdir24, writeFile as writeFile30 } from "fs/promises";
102519
- import { dirname as dirname22, isAbsolute as isAbsolute2, join as join55 } from "path";
102849
+ import { dirname as dirname21, isAbsolute as isAbsolute2, join as join55 } from "path";
102520
102850
  var GIT_TIMEOUT_MS = 3e4;
102521
102851
  var MAX_BUFFER = 10 * 1024 * 1024;
102522
102852
  var BASE_REF_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9/_.-]*$/;
@@ -102779,7 +103109,7 @@ async function removeWorktree(worktreePath) {
102779
103109
  if (!worktreePath.includes("-wt/")) {
102780
103110
  throw new Error("worktreePath must be under a -wt/ parent directory");
102781
103111
  }
102782
- await runGit(["worktree", "remove", worktreePath], dirname22(worktreePath));
103112
+ await runGit(["worktree", "remove", worktreePath], dirname21(worktreePath));
102783
103113
  }
102784
103114
 
102785
103115
  // src/services/channels/control-channel-infra-handlers.ts
@@ -103274,7 +103604,7 @@ function runPluginOp(pluginName, marketplace, action, ctx) {
103274
103604
  }
103275
103605
 
103276
103606
  // src/services/channels/read-recent-logs.ts
103277
- import { createReadStream, readdirSync as readdirSync4 } from "fs";
103607
+ import { createReadStream, readdirSync as readdirSync5 } from "fs";
103278
103608
  import { join as join56 } from "path";
103279
103609
  import { createInterface } from "readline";
103280
103610
  var MAX_BYTES = 5e4;
@@ -103332,7 +103662,7 @@ async function readRecentLogs(logDir, windowMinutes, maxBytes = MAX_BYTES) {
103332
103662
  function discoverLogFiles(logDir) {
103333
103663
  let entries;
103334
103664
  try {
103335
- entries = readdirSync4(logDir);
103665
+ entries = readdirSync5(logDir);
103336
103666
  } catch {
103337
103667
  return [];
103338
103668
  }
@@ -104919,6 +105249,7 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
104919
105249
  }
104920
105250
  };
104921
105251
  const publishedArtifactStore = deps.publishedArtifactStore;
105252
+ const previewStateStore = deps.previewStateStore;
104922
105253
  const getDetectedPorts = deps.getDetectedPorts;
104923
105254
  handlePublishRequest({
104924
105255
  correlationId,
@@ -104931,6 +105262,7 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
104931
105262
  projectRoot: wsRoot,
104932
105263
  vizWatcher,
104933
105264
  publishedArtifactStore,
105265
+ previewStateStore,
104934
105266
  getDetectedPorts,
104935
105267
  sendControl: wrappedSendControl,
104936
105268
  log: (entry) => {
@@ -105150,6 +105482,24 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
105150
105482
  });
105151
105483
  });
105152
105484
  }
105485
+ if (deps?.previewStateStore) {
105486
+ const previewStore = deps.previewStateStore;
105487
+ daemon.taskStateStore.listTasks().then((tasks) => {
105488
+ for (const taskId of Object.keys(tasks)) {
105489
+ const entries = previewStore.listByTask(taskId);
105490
+ controlHandler.sendControl({
105491
+ type: "preview_elements_state",
105492
+ taskId,
105493
+ entries
105494
+ });
105495
+ }
105496
+ }).catch((err) => {
105497
+ logAdapter({
105498
+ event: "preview_elements_initial_push_failed",
105499
+ error: err instanceof Error ? err.message : String(err)
105500
+ });
105501
+ });
105502
+ }
105153
105503
  const userSettingsUnsub = daemon.userSettingsStore.subscribe((settings) => {
105154
105504
  controlHandler.sendControl({ type: "user_settings_updated", settings });
105155
105505
  });
@@ -105684,7 +106034,7 @@ import { promisify as promisify7 } from "util";
105684
106034
 
105685
106035
  // src/shared/file-io-path-safety.ts
105686
106036
  import { realpath } from "fs/promises";
105687
- import { basename as basename6, dirname as dirname23, join as join57, normalize as normalize6 } from "path";
106037
+ import { basename as basename6, dirname as dirname22, join as join57, normalize as normalize6 } from "path";
105688
106038
  async function safeAbsolutePath(userPath, isHidden2, allowedHiddenNames) {
105689
106039
  const normalized = prepareAbsolutePath(userPath, isHidden2, allowedHiddenNames);
105690
106040
  if (normalized === null) return null;
@@ -105706,13 +106056,13 @@ async function canonicalizeOrRecombine(path2) {
105706
106056
  try {
105707
106057
  return await realpath(path2);
105708
106058
  } catch (err) {
105709
- if (!isEnoent3(err)) return null;
105710
- const parentCanonical = await realpath(dirname23(path2)).catch(() => null);
106059
+ if (!isEnoent4(err)) return null;
106060
+ const parentCanonical = await realpath(dirname22(path2)).catch(() => null);
105711
106061
  if (parentCanonical === null) return null;
105712
106062
  return join57(parentCanonical, basename6(path2));
105713
106063
  }
105714
106064
  }
105715
- function isEnoent3(err) {
106065
+ function isEnoent4(err) {
105716
106066
  return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
105717
106067
  }
105718
106068
  function checkSegmentsAllowed(path2, isHidden2, allowedHiddenNames) {
@@ -107022,13 +107372,13 @@ function wireThreadErrorFallback(daemon, dc, taskId, threadId, channelId, log) {
107022
107372
  // src/shared/pty-manager.ts
107023
107373
  import { accessSync, chmodSync, constants as constants2 } from "fs";
107024
107374
  import { createRequire as createRequire4 } from "module";
107025
- import { dirname as dirname24, resolve as resolve4 } from "path";
107375
+ import { dirname as dirname23, resolve as resolve4 } from "path";
107026
107376
  import * as pty from "node-pty";
107027
107377
  function ensureSpawnHelperExecutable() {
107028
107378
  if (globalThis.process.platform === "win32") return;
107029
107379
  try {
107030
107380
  const req = createRequire4(import.meta.url);
107031
- const nodePtyDir = dirname24(req.resolve("node-pty/package.json"));
107381
+ const nodePtyDir = dirname23(req.resolve("node-pty/package.json"));
107032
107382
  const spawnHelper = resolve4(
107033
107383
  nodePtyDir,
107034
107384
  "prebuilds",
@@ -107487,8 +107837,9 @@ function buildCollabRoomManager(deps) {
107487
107837
  configStore: pluginConfigStore,
107488
107838
  previewProxy,
107489
107839
  presencePool: presencePoolRef,
107490
- /** Collab peers cannot publish — null store and empty ports enforce read-only access. */
107840
+ /** Collab peers cannot publish — null stores enforce read-only access. */
107491
107841
  publishedArtifactStore: null,
107842
+ previewStateStore: null,
107492
107843
  getDetectedPorts: () => []
107493
107844
  });
107494
107845
  if (loadedPluginsRef.current.length > 0) {
@@ -107770,7 +108121,7 @@ function buildCollabRoomManager(deps) {
107770
108121
  import { execSync } from "child_process";
107771
108122
  import { existsSync as existsSync9 } from "fs";
107772
108123
  import { createRequire as createRequire5 } from "module";
107773
- import { dirname as dirname25, join as join59 } from "path";
108124
+ import { dirname as dirname24, join as join59 } from "path";
107774
108125
 
107775
108126
  // src/services/bootstrap/self-update.ts
107776
108127
  import { execFile as execFile11, spawn as spawn9 } from "child_process";
@@ -108433,7 +108784,7 @@ function resolveClaudeCodePath(log) {
108433
108784
  try {
108434
108785
  const req = createRequire5(import.meta.url);
108435
108786
  const sdkMain = req.resolve("@anthropic-ai/claude-agent-sdk");
108436
- const p2 = join59(dirname25(sdkMain), "cli.js");
108787
+ const p2 = join59(dirname24(sdkMain), "cli.js");
108437
108788
  if (existsSync9(p2)) return ok("sdk_bundled", p2);
108438
108789
  } catch {
108439
108790
  }
@@ -108697,7 +109048,8 @@ function buildSharedChannelCallbacks(deps) {
108697
109048
  terminalPtys,
108698
109049
  signalingHandle,
108699
109050
  sessionServerUrl,
108700
- publishedArtifactStore
109051
+ publishedArtifactStore,
109052
+ previewStateStore
108701
109053
  } = deps;
108702
109054
  return {
108703
109055
  onPeerDataChannel: (machineId) => {
@@ -108720,6 +109072,7 @@ function buildSharedChannelCallbacks(deps) {
108720
109072
  sessionServerUrl,
108721
109073
  getAuthToken: () => auth3.token,
108722
109074
  publishedArtifactStore,
109075
+ previewStateStore,
108723
109076
  getDetectedPorts: () => detectedPortsRef.current
108724
109077
  });
108725
109078
  portDetectorRef.current?.resend();
@@ -109133,6 +109486,10 @@ async function serve(options = {}) {
109133
109486
  rootDir: join62(dataDir, "published"),
109134
109487
  logger: log
109135
109488
  });
109489
+ const previewStateStore = createPreviewStateStore({
109490
+ rootDir: join62(dataDir, "preview-state"),
109491
+ logger: log
109492
+ });
109136
109493
  const daemon = await createDaemon({
109137
109494
  shipyardHome,
109138
109495
  dataDir,
@@ -109152,10 +109509,12 @@ async function serve(options = {}) {
109152
109509
  previewProxy,
109153
109510
  sessionServerUrl,
109154
109511
  publishedArtifactStore,
109512
+ previewStateStore,
109155
109513
  getDetectedPorts: () => detectedPortsRef.current
109156
109514
  });
109157
109515
  daemon.healthMetrics.start();
109158
109516
  publishedArtifactStore.setSendControlMessage((msg) => daemon.taskManager.broadcastControl(msg));
109517
+ previewStateStore.setSendControlMessage((msg) => daemon.taskManager.broadcastControl(msg));
109159
109518
  const pluginsDir = join62(shipyardHome, "plugins");
109160
109519
  await mkdir28(pluginsDir, { recursive: true });
109161
109520
  let loadedPlugins = [];
@@ -109287,7 +109646,8 @@ async function serve(options = {}) {
109287
109646
  fileWatcherPool,
109288
109647
  terminalPtys,
109289
109648
  sessionServerUrl,
109290
- publishedArtifactStore
109649
+ publishedArtifactStore,
109650
+ previewStateStore
109291
109651
  } : null;
109292
109652
  const peerManager = peerSetupDeps ? buildPeerManager(peerSetupDeps) : null;
109293
109653
  if (peerSetupDeps && !localDirectRef.current) {
@@ -109400,6 +109760,7 @@ async function serve(options = {}) {
109400
109760
  log.info("Graceful shutdown initiated");
109401
109761
  portDetector.dispose();
109402
109762
  publishedArtifactStore.dispose();
109763
+ previewStateStore.dispose();
109403
109764
  await previewProxy.stop();
109404
109765
  pluginFileWatcher.dispose();
109405
109766
  await fileWatcherPool.dispose();
@@ -109436,4 +109797,4 @@ export {
109436
109797
  _testing,
109437
109798
  serve
109438
109799
  };
109439
- //# sourceMappingURL=serve-QLYUK5XN.js.map
109800
+ //# sourceMappingURL=serve-ULDOVKX7.js.map