@schoolai/shipyard 3.0.0-rc.20260413.2 → 3.0.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.
package/dist/index.js CHANGED
@@ -106,7 +106,7 @@ async function handleSubcommand() {
106
106
  return true;
107
107
  }
108
108
  if (subcommand === "start") {
109
- const { startCommand } = await import("./start-MJUFHZ3L.js");
109
+ const { startCommand } = await import("./start-G6D7XOCQ.js");
110
110
  await startCommand();
111
111
  return true;
112
112
  }
@@ -118,7 +118,7 @@ async function main() {
118
118
  const args = parseCliArgs();
119
119
  if (args.serve) {
120
120
  await loadAuthFromConfig(env);
121
- const { serve } = await import("./serve-IGUWRAXT.js");
121
+ const { serve } = await import("./serve-LL6LUMZI.js");
122
122
  return serve({ isDev: env.SHIPYARD_DEV });
123
123
  }
124
124
  logger.error("Use `shipyard start` to run the daemon. Use --help for usage.");
@@ -25302,6 +25302,7 @@ var TaskRecordSchema = external_exports.object({
25302
25302
  currentActivity: external_exports.string().nullable(),
25303
25303
  cwd: external_exports.string(),
25304
25304
  acknowledgedAt: external_exports.number().nullable().optional(),
25305
+ attentionAt: external_exports.number().nullable().optional(),
25305
25306
  pinned: external_exports.boolean().optional(),
25306
25307
  scheduleId: external_exports.string().optional(),
25307
25308
  scheduleName: external_exports.string().optional(),
@@ -29569,7 +29570,7 @@ var IFRAME_BLOCKING_HEADERS = /* @__PURE__ */ new Set([
29569
29570
  "content-security-policy",
29570
29571
  "content-security-policy-report-only"
29571
29572
  ]);
29572
- var HISTORY_GUARD = `<script>(function(){var p=history.pushState,r=history.replaceState;history.pushState=function(){try{p.apply(this,arguments)}catch(e){if(!(e instanceof DOMException&&e.name==="SecurityError"))throw e}};history.replaceState=function(){try{r.apply(this,arguments)}catch(e){if(!(e instanceof DOMException&&e.name==="SecurityError"))throw e}}})();</script>`;
29573
+ var HISTORY_GUARD = `<script>(function(){var p=history.pushState,r=history.replaceState;history.pushState=function(){try{p.apply(this,arguments)}catch(e){if(!(e instanceof DOMException&&e.name==="SecurityError"))throw e}};history.replaceState=function(){try{r.apply(this,arguments)}catch(e){if(!(e instanceof DOMException&&e.name==="SecurityError"))throw e}};var la=Location.prototype.assign,lr=Location.prototype.replace;function patchLoc(orig,hFn){return function(url){if(typeof url==="string"&&url.charAt(0)==="#"){try{hFn.call(history,null,"",location.origin+location.pathname+location.search+url)}catch(e){location.hash=url}return}try{return orig.call(this,url)}catch(e){if(!(e instanceof DOMException&&e.name==="SecurityError"))throw e}}}try{Location.prototype.assign=patchLoc(la,p);Location.prototype.replace=patchLoc(lr,r)}catch(e){}})();</script>`;
29573
29574
  function injectBaseHref(html, origin) {
29574
29575
  const baseTag = `<base href="${origin}/">`;
29575
29576
  const injection = HISTORY_GUARD + baseTag + ANNOTATION_BRIDGE_TAG;
@@ -30236,6 +30237,12 @@ async function resolveLinearToken(deps) {
30236
30237
  }
30237
30238
  return null;
30238
30239
  }
30240
+ var LinearAuthError = class extends Error {
30241
+ constructor(message) {
30242
+ super(message);
30243
+ this.name = "LinearAuthError";
30244
+ }
30245
+ };
30239
30246
  async function graphql(token, query3, variables) {
30240
30247
  const res = await fetch(LINEAR_API_URL, {
30241
30248
  method: "POST",
@@ -30247,7 +30254,7 @@ async function graphql(token, query3, variables) {
30247
30254
  });
30248
30255
  if (!res.ok) {
30249
30256
  if (res.status === 401) {
30250
- throw new Error("Linear authentication expired");
30257
+ throw new LinearAuthError("Linear authentication expired");
30251
30258
  }
30252
30259
  throw new Error(`Linear API error: ${res.status} ${res.statusText}`);
30253
30260
  }
@@ -30480,6 +30487,9 @@ async function handleLinearPluginRequest(method, params, deps) {
30480
30487
  } catch (err) {
30481
30488
  const message = err instanceof Error ? err.message : String(err);
30482
30489
  deps.log({ event: "linear_plugin_error", method, error: message });
30490
+ if (err instanceof LinearAuthError) {
30491
+ return { authError: message };
30492
+ }
30483
30493
  return { error: message };
30484
30494
  }
30485
30495
  }
@@ -30723,7 +30733,28 @@ function handlePluginDataRequest(requestId, pluginId, method, params, sendContro
30723
30733
  });
30724
30734
  }
30725
30735
  };
30726
- plugin.onPluginDataRequest(method, params, pluginDeps).then(respond).catch((err) => {
30736
+ plugin.onPluginDataRequest(method, params, pluginDeps).then((result) => {
30737
+ if ("authError" in result && result.authError) {
30738
+ const oauthConfig = plugin.oauthConfig;
30739
+ if (oauthConfig && deps.tokenStore) {
30740
+ const vaultKey = oauthConfig.vaultTokenKey;
30741
+ const baseName = vaultKey.replace(/-oauth$/, "");
30742
+ void deps.tokenStore.deleteToken(vaultKey);
30743
+ if (baseName !== vaultKey) {
30744
+ void deps.tokenStore.deleteToken(baseName);
30745
+ }
30746
+ }
30747
+ sendControl({
30748
+ type: "plugin_auth_status",
30749
+ pluginId,
30750
+ status: "error",
30751
+ error: result.authError
30752
+ });
30753
+ respond({ error: result.authError });
30754
+ } else {
30755
+ respond(result);
30756
+ }
30757
+ }).catch((err) => {
30727
30758
  respond({ error: err instanceof Error ? err.message : String(err) });
30728
30759
  });
30729
30760
  }
@@ -31583,6 +31614,28 @@ var McpProxyServer = class {
31583
31614
  return { name, status: "pending" };
31584
31615
  });
31585
31616
  }
31617
+ /** Whether a server is tracked (connected or failed). */
31618
+ hasConfigured(name) {
31619
+ return this.#allConfigured.has(name);
31620
+ }
31621
+ /**
31622
+ * Dynamically add and connect a server after initialization.
31623
+ * Used when an HTTP/SSE server is toggled on after daemon start.
31624
+ */
31625
+ async addServer(name, config2) {
31626
+ this.#allConfigured.set(name, config2);
31627
+ try {
31628
+ await this.connectServer(name, config2);
31629
+ } catch (err) {
31630
+ if (!this.#connectErrors.has(name)) this.#connectErrors.set(name, "transport");
31631
+ this.#deps.log({
31632
+ event: "mcp_proxy_connect_failed",
31633
+ serverName: name,
31634
+ url: config2.url,
31635
+ error: err instanceof Error ? err.message : String(err)
31636
+ });
31637
+ }
31638
+ }
31586
31639
  /** Disconnect a single server. */
31587
31640
  async disconnectServer(name) {
31588
31641
  const server = this.#servers.get(name);
@@ -31751,15 +31804,6 @@ async function sweepStaleTasks(taskStateStore, taskManager, log) {
31751
31804
  }
31752
31805
  }
31753
31806
  }
31754
- async function resealAcknowledgements(taskStateStore, log) {
31755
- const tasks = await taskStateStore.listTasks();
31756
- for (const [taskId, task] of Object.entries(tasks)) {
31757
- if (task.acknowledgedAt != null && task.updatedAt > task.acknowledgedAt) {
31758
- await taskStateStore.acknowledge(taskId);
31759
- log({ event: "acknowledgement_resealed", taskId });
31760
- }
31761
- }
31762
- }
31763
31807
 
31764
31808
  // src/services/credentials/credentials-sync.ts
31765
31809
  import {
@@ -65293,10 +65337,12 @@ var PreWarmManager = class {
65293
65337
  serverCount: statuses.length,
65294
65338
  servers: statuses.map((s2) => ({ name: s2.name, status: s2.status, error: s2.error }))
65295
65339
  });
65296
- const proxyStatuses = this.#deps.getProxyStatuses();
65297
- const proxyNames = new Set(proxyStatuses.map((s2) => s2.name));
65298
- const merged = [...statuses.filter((s2) => !proxyNames.has(s2.name)), ...proxyStatuses];
65299
- this.#deps.broadcastControl({ type: "mcp_server_status", servers: merged });
65340
+ if (!this.#deps.hasLiveSubprocesses()) {
65341
+ const proxyStatuses = this.#deps.getProxyStatuses();
65342
+ const proxyNames = new Set(proxyStatuses.map((s2) => s2.name));
65343
+ const merged = [...statuses.filter((s2) => !proxyNames.has(s2.name)), ...proxyStatuses];
65344
+ this.#deps.broadcastControl({ type: "mcp_server_status", servers: merged });
65345
+ }
65300
65346
  this.#disableNativePluginsForProxy(statuses);
65301
65347
  }).catch((err) => {
65302
65348
  const msg = err instanceof Error ? err.message : String(err);
@@ -75475,7 +75521,7 @@ var AgentSessionManager = class {
75475
75521
  var DEFAULTS = {
75476
75522
  assistantHeadChars: 400,
75477
75523
  assistantTailChars: 400,
75478
- toolResultMaxChars: 100,
75524
+ errorResultMaxChars: 200,
75479
75525
  maxTotalChars: 5e4
75480
75526
  };
75481
75527
  function serializeConversationContext(messages, options) {
@@ -75504,7 +75550,7 @@ function partitionBlockLines(blockLines) {
75504
75550
  const textLines = [];
75505
75551
  const toolLines = [];
75506
75552
  for (const line of blockLines) {
75507
- if (line.startsWith(" [Tool:") || line.startsWith(" \u2192")) {
75553
+ if (line.startsWith(" [") || line.startsWith(" \u2192")) {
75508
75554
  toolLines.push(line);
75509
75555
  } else {
75510
75556
  textLines.push(line);
@@ -75525,22 +75571,39 @@ function serializeBlock(block2, senderKind, opts) {
75525
75571
  case "resource":
75526
75572
  return serializeEmbeddedResource(block2);
75527
75573
  case "thinking":
75528
- return " [thinking]";
75574
+ return null;
75529
75575
  case "compaction_boundary":
75530
75576
  return ` [compaction boundary: ${block2.messageCount} messages compacted]`;
75531
75577
  case "diff_review_feedback":
75532
- case "preview_annotation_feedback":
75578
+ return serializeDiffReviewFeedback(block2);
75533
75579
  case "annotation_feedback":
75580
+ return serializeAnnotationFeedback(block2);
75581
+ case "preview_annotation_feedback":
75582
+ return serializePreviewAnnotationFeedback(block2);
75534
75583
  case "task_feedback":
75584
+ return ` [task feedback: ${block2.changes.map((c) => `${c.subject} (${c.changeType})`).join(", ")}]`;
75535
75585
  case "review_comment":
75586
+ return ` [review: ${block2.body}]`;
75536
75587
  case "review_decision":
75537
- return ` [${block2.type}]`;
75588
+ return ` [review decision: ${block2.decision}${block2.feedback ? ` \u2014 ${block2.feedback}` : ""}]`;
75538
75589
  default: {
75539
75590
  block2;
75540
75591
  return null;
75541
75592
  }
75542
75593
  }
75543
75594
  }
75595
+ function serializeDiffReviewFeedback(block2) {
75596
+ const comments = block2.comments.map((c) => `${c.filePath}:${c.lineNumber ?? ""} \u2014 ${c.body}`).join("; ");
75597
+ return ` [diff feedback: ${comments}]`;
75598
+ }
75599
+ function serializeAnnotationFeedback(block2) {
75600
+ const bodies = block2.annotations.map((a) => a.body).join("; ");
75601
+ return ` [annotation feedback: ${bodies}]`;
75602
+ }
75603
+ function serializePreviewAnnotationFeedback(block2) {
75604
+ const comments = block2.comments.map((c) => `${c.url}: ${c.body}`).join("; ");
75605
+ return ` [preview feedback: ${comments}]`;
75606
+ }
75544
75607
  function serializeBlocks(blocks, senderKind, opts) {
75545
75608
  const result = [];
75546
75609
  for (const block2 of blocks) {
@@ -75559,26 +75622,50 @@ function serializeTextBlock(text2, senderKind, opts) {
75559
75622
  return `${head} [truncated ${truncatedChars} chars] ${tail}`;
75560
75623
  }
75561
75624
  function serializeToolUse(block2) {
75562
- const inputSummary = summarizeToolInput(block2.input);
75563
- return ` [Tool: ${block2.toolName} ${inputSummary}]`;
75564
- }
75565
- function summarizeToolInput(input) {
75566
- const parts = [];
75567
- for (const [, value] of Object.entries(input)) {
75568
- if (typeof value === "string") {
75569
- parts.push(value.length > 60 ? value.slice(0, 60) : value);
75570
- } else if (typeof value === "number" || typeof value === "boolean") {
75571
- parts.push(String(value));
75625
+ const name = shortToolName(block2.toolName);
75626
+ const key = extractKeyParam(block2.toolName, block2.input);
75627
+ return key ? ` [${name}: ${key}]` : ` [${name}]`;
75628
+ }
75629
+ function shortToolName(toolName) {
75630
+ if (toolName.startsWith("mcp__")) {
75631
+ const parts = toolName.split("__");
75632
+ return parts[parts.length - 1] ?? toolName;
75633
+ }
75634
+ return toolName;
75635
+ }
75636
+ function extractKeyParam(toolName, input) {
75637
+ const baseName = shortToolName(toolName);
75638
+ switch (baseName) {
75639
+ case "Read":
75640
+ case "Edit":
75641
+ case "Write":
75642
+ return truncParam(input.file_path);
75643
+ case "Bash":
75644
+ return truncParam(input.command, 80);
75645
+ case "Grep":
75646
+ return truncParam(input.pattern);
75647
+ case "Glob":
75648
+ return truncParam(input.pattern);
75649
+ case "Agent":
75650
+ return truncParam(input.prompt, 80);
75651
+ case "WebSearch":
75652
+ return truncParam(input.query);
75653
+ case "WebFetch":
75654
+ return truncParam(input.url);
75655
+ default: {
75656
+ const firstStr = Object.values(input).find((v2) => typeof v2 === "string");
75657
+ return typeof firstStr === "string" ? truncParam(firstStr, 60) : null;
75572
75658
  }
75573
75659
  }
75574
- const summary = parts.join(" ").slice(0, 60);
75575
- return summary || Object.keys(input).join(", ");
75660
+ }
75661
+ function truncParam(value, max = 120) {
75662
+ if (typeof value !== "string") return null;
75663
+ return value.length > max ? value.slice(0, max) : value;
75576
75664
  }
75577
75665
  function serializeToolResult(block2, opts) {
75578
- const maxChars = block2.isError ? opts.toolResultMaxChars * 2 : opts.toolResultMaxChars;
75579
- const status = block2.isError ? "error" : "success";
75580
- const content = block2.content.length > maxChars ? block2.content.slice(0, maxChars) : block2.content;
75581
- return ` \u2192 ${status}: ${content}`;
75666
+ if (!block2.isError) return null;
75667
+ const content = block2.content.length > opts.errorResultMaxChars ? block2.content.slice(0, opts.errorResultMaxChars) : block2.content;
75668
+ return ` \u2192 error: ${content}`;
75582
75669
  }
75583
75670
  function serializeEmbeddedResource(block2) {
75584
75671
  const uri = block2.resource.uri ?? "unknown";
@@ -76866,6 +76953,8 @@ var PlanHandler = class {
76866
76953
  #processedPlanToolUseIds = /* @__PURE__ */ new Set();
76867
76954
  /** Tracks whether we have already published the plan (first ExitPlanMode). */
76868
76955
  #planPublished = false;
76956
+ /** Plan files this task's agent created (from Write tool results). */
76957
+ #createdPlanFiles = /* @__PURE__ */ new Set();
76869
76958
  constructor(deps) {
76870
76959
  this.#deps = deps;
76871
76960
  }
@@ -76876,30 +76965,18 @@ var PlanHandler = class {
76876
76965
  return this.#lastPlanDetection;
76877
76966
  }
76878
76967
  /**
76879
- * One-shot scan for an existing plan file on task init or reconnect.
76880
- * If a plan file exists and the bridge isn't already running, attaches
76881
- * the bridge and sends plan_detected so the browser shows the TipTap editor.
76968
+ * Record a plan file path created by this task's agent.
76969
+ * Called when a Write tool result mentions a file in ~/.claude/plans/.
76882
76970
  */
76883
- tryAttachExistingPlan() {
76884
- if (this.#planFileBridge) return;
76971
+ trackCreatedFile(filePath) {
76885
76972
  const plansDir = join28(homedir4(), ".claude", "plans");
76886
- if (!existsSync4(plansDir)) return;
76887
- try {
76888
- const files = readdirSync(plansDir).filter((f2) => f2.endsWith(".md")).map((f2) => ({
76889
- name: f2,
76890
- path: join28(plansDir, f2),
76891
- mtime: statSync2(join28(plansDir, f2)).mtimeMs
76892
- })).sort((a, b2) => b2.mtime - a.mtime);
76893
- const recent = files[0];
76894
- if (!recent) return;
76895
- const syntheticToolUseId = `plan-reattach-${Date.now()}`;
76973
+ if (filePath.startsWith(plansDir) && filePath.endsWith(".md")) {
76974
+ this.#createdPlanFiles.add(filePath);
76896
76975
  this.#deps.log({
76897
- event: "plan_existing_file_found",
76976
+ event: "plan_file_tracked",
76898
76977
  taskId: this.#deps.taskId,
76899
- filePath: recent.path
76978
+ filePath
76900
76979
  });
76901
- this.#handlePlanDetected(syntheticToolUseId, recent.path);
76902
- } catch {
76903
76980
  }
76904
76981
  }
76905
76982
  /**
@@ -76997,22 +77074,16 @@ var PlanHandler = class {
76997
77074
  taskId: this.#deps.taskId,
76998
77075
  toolUseId: block2.toolUseId
76999
77076
  });
77000
- const plansDir = join28(homedir4(), ".claude", "plans");
77001
- if (!existsSync4(plansDir)) continue;
77002
- const files = readdirSync(plansDir).filter((f2) => f2.endsWith(".md")).map((f2) => ({
77003
- name: f2,
77004
- path: join28(plansDir, f2),
77005
- mtime: statSync2(join28(plansDir, f2)).mtimeMs
77006
- })).sort((a, b2) => b2.mtime - a.mtime);
77007
- const recent = files[0];
77008
- if (recent && Date.now() - recent.mtime < 3e4) {
77077
+ const match2 = this.#findTaskPlanFile();
77078
+ if (match2) {
77009
77079
  this.#deps.log({
77010
77080
  event: "plan_file_found_from_message",
77011
77081
  taskId: this.#deps.taskId,
77012
77082
  toolUseId: block2.toolUseId,
77013
- filePath: recent.path
77083
+ filePath: match2,
77084
+ source: this.#createdPlanFiles.size > 0 ? "tracked" : "mtime_fallback"
77014
77085
  });
77015
- this.#handlePlanDetected(block2.toolUseId, recent.path);
77086
+ this.#handlePlanDetected(block2.toolUseId, match2);
77016
77087
  }
77017
77088
  }
77018
77089
  }
@@ -77045,6 +77116,7 @@ var PlanHandler = class {
77045
77116
  this.#lastPlanDetection = null;
77046
77117
  this.#processedPlanToolUseIds.clear();
77047
77118
  this.#pendingPlanAllowedPrompts.clear();
77119
+ this.#createdPlanFiles.clear();
77048
77120
  this.#planFileBridge?.dispose();
77049
77121
  this.#planFileBridge = null;
77050
77122
  }
@@ -77059,37 +77131,23 @@ var PlanHandler = class {
77059
77131
  this.#planFileBridge = null;
77060
77132
  this.#processedPlanToolUseIds.clear();
77061
77133
  this.#pendingPlanAllowedPrompts.clear();
77134
+ this.#createdPlanFiles.clear();
77062
77135
  }
77063
77136
  #pollForPlanFile(toolUseId) {
77064
- const plansDir = join28(homedir4(), ".claude", "plans");
77065
- if (!existsSync4(plansDir)) {
77066
- this.#deps.log({ event: "plan_dir_not_found", taskId: this.#deps.taskId, plansDir });
77067
- return;
77068
- }
77069
- const startTime = Date.now();
77070
- const lookbackMs = 3e4;
77071
77137
  let attempts = 0;
77072
77138
  const checkForPlanFile = () => {
77073
- try {
77074
- const files = readdirSync(plansDir);
77075
- for (const file of files) {
77076
- if (!file.endsWith(".md")) continue;
77077
- const filePath = join28(plansDir, file);
77078
- const { mtimeMs } = statSync2(filePath);
77079
- if (mtimeMs > startTime - lookbackMs) {
77080
- this.#deps.log({
77081
- event: "plan_file_found",
77082
- taskId: this.#deps.taskId,
77083
- toolUseId,
77084
- filePath,
77085
- attempts
77086
- });
77087
- this.#handlePlanDetected(toolUseId, filePath);
77088
- return true;
77089
- }
77090
- }
77091
- } catch {
77092
- return false;
77139
+ const match2 = this.#findTaskPlanFile();
77140
+ if (match2) {
77141
+ this.#deps.log({
77142
+ event: "plan_file_found",
77143
+ taskId: this.#deps.taskId,
77144
+ toolUseId,
77145
+ filePath: match2,
77146
+ attempts,
77147
+ source: this.#createdPlanFiles.size > 0 ? "tracked" : "mtime_fallback"
77148
+ });
77149
+ this.#handlePlanDetected(toolUseId, match2);
77150
+ return true;
77093
77151
  }
77094
77152
  return false;
77095
77153
  };
@@ -77114,6 +77172,35 @@ var PlanHandler = class {
77114
77172
  }, PLAN_POLL_INTERVAL_MS);
77115
77173
  this.#pollInterval = interval;
77116
77174
  }
77175
+ /**
77176
+ * Find a plan file for this task. Prefers files tracked from Write tool
77177
+ * results (task-scoped). Falls back to global mtime scan for backwards
77178
+ * compat with sessions that started before tracking was added.
77179
+ */
77180
+ #findTaskPlanFile() {
77181
+ if (this.#createdPlanFiles.size > 0) {
77182
+ const newest = [...this.#createdPlanFiles].map((f2) => {
77183
+ try {
77184
+ return { path: f2, mtime: statSync2(f2).mtimeMs };
77185
+ } catch {
77186
+ return null;
77187
+ }
77188
+ }).filter((f2) => f2 !== null).sort((a, b2) => b2.mtime - a.mtime)[0];
77189
+ return newest?.path ?? null;
77190
+ }
77191
+ const plansDir = join28(homedir4(), ".claude", "plans");
77192
+ if (!existsSync4(plansDir)) return null;
77193
+ try {
77194
+ const files = readdirSync(plansDir).filter((f2) => f2.endsWith(".md")).map((f2) => ({
77195
+ path: join28(plansDir, f2),
77196
+ mtime: statSync2(join28(plansDir, f2)).mtimeMs
77197
+ })).sort((a, b2) => b2.mtime - a.mtime);
77198
+ const recent = files[0];
77199
+ if (recent && Date.now() - recent.mtime < 3e4) return recent.path;
77200
+ } catch {
77201
+ }
77202
+ return null;
77203
+ }
77117
77204
  #handlePlanDetected(toolUseId, filePath) {
77118
77205
  if (this.#processedPlanToolUseIds.has(toolUseId)) {
77119
77206
  this.#deps.log({
@@ -79251,6 +79338,7 @@ var McpPoller = class {
79251
79338
  start() {
79252
79339
  this.stop();
79253
79340
  this.#deps.log({ event: "mcp_poll_start", taskId: this.#deps.taskId });
79341
+ this.pollOnce();
79254
79342
  this.#schedulePoll(MCP_POLL_IDLE_MS);
79255
79343
  }
79256
79344
  enterFastMode() {
@@ -81603,6 +81691,7 @@ Use this context to maintain continuity. You have already done this work \u2014
81603
81691
  this.#deps.updateTodoProgress(todoProgress);
81604
81692
  }
81605
81693
  this.#structuredTaskTracker.processStructuredTaskEvents(event.content);
81694
+ this.#trackPlanFileFromToolUse(event.content);
81606
81695
  this.#planHandler.detectPlanEvents(event.content);
81607
81696
  this.#trackWorktreeToolUse(event.content);
81608
81697
  this.#subagentManager.trackAgentToolUse(event.content);
@@ -81614,6 +81703,7 @@ Use this context to maintain continuity. You have already done this work \u2014
81614
81703
  this.#structuredTaskTracker.resolveStructuredTaskCreates(event.content);
81615
81704
  this.#handleWorktreeToolResults(event.content);
81616
81705
  this.#subagentManager.handleSubagentToolResults(event.content);
81706
+ this.#trackPlanFileCreation(event.content);
81617
81707
  break;
81618
81708
  case "api_usage_snapshot":
81619
81709
  this.#lastApiUsage = {
@@ -81840,6 +81930,26 @@ Use this context to maintain continuity. You have already done this work \u2014
81840
81930
  this.#deps.onCwdChanged(this.#deps.taskId, newCwd);
81841
81931
  }
81842
81932
  }
81933
+ #trackPlanFileFromToolUse(content) {
81934
+ for (const block2 of content) {
81935
+ if (block2.type !== "tool_use") continue;
81936
+ if (block2.toolName !== "Write") continue;
81937
+ const filePath = block2.input?.file_path;
81938
+ if (typeof filePath === "string") {
81939
+ this.#planHandler.trackCreatedFile(filePath);
81940
+ }
81941
+ }
81942
+ }
81943
+ #trackPlanFileCreation(content) {
81944
+ const writeResultPattern = /File created successfully at: (\/[^\s"')]+\.claude\/plans\/[^\s"')]+\.md)\b/;
81945
+ for (const block2 of content) {
81946
+ if (block2.type !== "tool_result" || block2.isError) continue;
81947
+ const match2 = block2.content.match(writeResultPattern);
81948
+ if (match2?.[1]) {
81949
+ this.#planHandler.trackCreatedFile(match2[1]);
81950
+ }
81951
+ }
81952
+ }
81843
81953
  applyOverlay(overlay) {
81844
81954
  this.#structuredTaskTracker.applyOverlay(overlay);
81845
81955
  }
@@ -82939,13 +83049,10 @@ function applyStatusTransition(task, status, now) {
82939
83049
  ...task,
82940
83050
  status,
82941
83051
  updatedAt: now,
82942
- taskStartedAt: status === "in_progress" ? now : null
83052
+ taskStartedAt: status === "in_progress" ? now : null,
83053
+ attentionAt: status === "input_required" ? now : null
82943
83054
  };
82944
83055
  }
82945
- function hasTodoProgressChanged(task, todoProgress) {
82946
- if (!todoProgress) return false;
82947
- return task.todoCompleted !== todoProgress.todoCompleted || task.todoTotal !== todoProgress.todoTotal || task.currentActivity !== todoProgress.currentActivity;
82948
- }
82949
83056
  function buildTaskStateStore(dataDir) {
82950
83057
  const store = buildJsonDocumentStore({
82951
83058
  filePath: join35(dataDir, "tasks.json"),
@@ -83042,9 +83149,6 @@ function buildTaskStateStore(dataDir) {
83042
83149
  async updateStructuredTasks(taskId, tasks, todoProgress) {
83043
83150
  const task = await store.get(taskId);
83044
83151
  if (!task) return;
83045
- const progressChanged = hasTodoProgressChanged(task, todoProgress);
83046
- const tasksChanged = JSON.stringify(task.structuredTasks ?? {}) !== JSON.stringify(tasks);
83047
- if (!progressChanged && !tasksChanged) return;
83048
83152
  await store.set(taskId, {
83049
83153
  ...task,
83050
83154
  structuredTasks: tasks,
@@ -83059,7 +83163,6 @@ function buildTaskStateStore(dataDir) {
83059
83163
  async updateTaskOverlay(taskId, overlay) {
83060
83164
  const task = await store.get(taskId);
83061
83165
  if (!task) return;
83062
- if (JSON.stringify(task.taskOverlay ?? {}) === JSON.stringify(overlay)) return;
83063
83166
  await store.set(taskId, {
83064
83167
  ...task,
83065
83168
  taskOverlay: overlay,
@@ -83737,7 +83840,8 @@ async function createDaemon(deps) {
83737
83840
  broadcastControl: (msg) => taskManager.broadcastControl(msg),
83738
83841
  resolveMcpServers,
83739
83842
  getProxyStatuses: () => proxyRef.current?.getServerStatuses() ?? [],
83740
- getProxyManagedNames: () => proxyRef.current?.getAllManagedNames() ?? []
83843
+ getProxyManagedNames: () => proxyRef.current?.getAllManagedNames() ?? [],
83844
+ hasLiveSubprocesses: () => taskManager.hasLiveSubprocesses
83741
83845
  });
83742
83846
  taskManager.setPreWarmManager(preWarmManager);
83743
83847
  const scheduleStore = buildScheduleStore(deps.dataDir);
@@ -83858,7 +83962,6 @@ async function createDaemon(deps) {
83858
83962
  }
83859
83963
  await rehydrateFromPersistence(sessionPersistence, taskManager, deps.log, taskStateStore);
83860
83964
  await sweepStaleTasks(taskStateStore, taskManager, deps.log);
83861
- await resealAcknowledgements(taskStateStore, deps.log);
83862
83965
  const taskStoreUnsub = taskStateStore.subscribe((event) => {
83863
83966
  switch (event.kind) {
83864
83967
  case "set": {
@@ -85791,6 +85894,28 @@ function applyMcpServerToggles(daemon, prevDisabled, nextDisabled, log) {
85791
85894
  if (!nextDisabled.has(name)) {
85792
85895
  log({ event: "mcp_server_enabling", serverName: name });
85793
85896
  daemon.taskManager.toggleMcpServer(name, true);
85897
+ if (daemon.proxyServer && !daemon.proxyServer.hasConfigured(name)) {
85898
+ const proxyServer = daemon.proxyServer;
85899
+ const server = daemon.capabilities.mcpServers?.find(
85900
+ (s2) => s2.name === name && (s2.type === "http" || s2.type === "sse") && s2.url
85901
+ );
85902
+ if (server?.url) {
85903
+ log({ event: "mcp_proxy_dynamic_add", serverName: name, url: server.url });
85904
+ proxyServer.addServer(name, { url: server.url, callbackPort: server.oauth?.callbackPort }).then(() => {
85905
+ daemon.taskManager.triggerFastMcpPolling();
85906
+ daemon.taskManager.broadcastControl({
85907
+ type: "mcp_server_status",
85908
+ servers: proxyServer.getServerStatuses()
85909
+ });
85910
+ }).catch((err) => {
85911
+ log({
85912
+ event: "mcp_proxy_dynamic_add_failed",
85913
+ serverName: name,
85914
+ error: err instanceof Error ? err.message : String(err)
85915
+ });
85916
+ });
85917
+ }
85918
+ }
85794
85919
  }
85795
85920
  }
85796
85921
  const resolved = daemon.resolveMcpServers() ?? {};
@@ -86148,11 +86273,17 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
86148
86273
  )
86149
86274
  };
86150
86275
  handler.sendControl(buildCapabilitiesMessage());
86151
- if (daemon.proxyServer) {
86152
- handler.sendControl({
86153
- type: "mcp_server_status",
86154
- servers: daemon.proxyServer.getServerStatuses()
86155
- });
86276
+ {
86277
+ const subprocessStatuses = daemon.preWarmManager.getLastMcpStatuses() ?? [];
86278
+ const proxyStatuses = daemon.proxyServer?.getServerStatuses() ?? [];
86279
+ const proxyNames = new Set(proxyStatuses.map((s2) => s2.name));
86280
+ const merged = [
86281
+ ...subprocessStatuses.filter((s2) => !proxyNames.has(s2.name)),
86282
+ ...proxyStatuses
86283
+ ];
86284
+ if (merged.length > 0) {
86285
+ handler.sendControl({ type: "mcp_server_status", servers: merged });
86286
+ }
86156
86287
  }
86157
86288
  const resolved = daemon.resolveMcpServers();
86158
86289
  if (resolved) {
@@ -86534,6 +86665,9 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
86534
86665
  const merged = [...subprocessStatuses.filter((s2) => !proxyNames.has(s2.name)), ...proxyStatuses];
86535
86666
  if (merged.length > 0) {
86536
86667
  handler.sendControl({ type: "mcp_server_status", servers: merged });
86668
+ if (subprocessStatuses.length === 0 && retriesLeft > 0) {
86669
+ setTimeout(() => pushMcpStatus(retriesLeft - 1), 3e3);
86670
+ }
86537
86671
  return;
86538
86672
  }
86539
86673
  if (retriesLeft > 0) {
@@ -90568,4 +90702,4 @@ export {
90568
90702
  classifyLogLevel,
90569
90703
  serve
90570
90704
  };
90571
- //# sourceMappingURL=serve-IGUWRAXT.js.map
90705
+ //# sourceMappingURL=serve-LL6LUMZI.js.map