@schoolai/shipyard 0.7.0-rc.20260221 → 0.8.0-rc.20260221

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
@@ -11337,7 +11337,8 @@ var TaskDocumentSchema = Shape.doc({
11337
11337
  plans: Shape.list(PlanVersionShape),
11338
11338
  planEditorDocs: Shape.record(Shape.any()),
11339
11339
  diffComments: Shape.record(DiffCommentShape),
11340
- planComments: Shape.record(PlanCommentShape)
11340
+ planComments: Shape.record(PlanCommentShape),
11341
+ deliveredCommentIds: Shape.list(Shape.plain.string())
11341
11342
  });
11342
11343
  var TERMINAL_TASK_STATES = ["completed", "failed", "canceled"];
11343
11344
  var TOOL_RISK_LEVELS = ["low", "medium", "high"];
@@ -11394,7 +11395,10 @@ var WorktreeScriptShape = Shape.plain.struct({
11394
11395
  script: Shape.plain.string()
11395
11396
  });
11396
11397
  var UserSettingsShape = Shape.struct({
11397
- worktreeScripts: Shape.record(WorktreeScriptShape)
11398
+ worktreeScripts: Shape.record(WorktreeScriptShape),
11399
+ composerModel: Shape.plain.string().nullable(),
11400
+ composerReasoning: Shape.plain.string(...REASONING_EFFORTS).nullable(),
11401
+ composerPermission: Shape.plain.string(...PERMISSION_MODES).nullable()
11398
11402
  });
11399
11403
  var TaskIndexDocumentSchema = Shape.doc({
11400
11404
  taskIndex: Shape.record(TaskIndexEntryShape),
@@ -12761,13 +12765,17 @@ function recoverOrphanedTask(taskDoc, log) {
12761
12765
 
12762
12766
  // src/peer-manager.ts
12763
12767
  var ICE_SERVERS = [{ urls: "stun:stun.l.google.com:19302" }];
12768
+ var MAX_MESSAGE_SIZE = 16 * 1024 * 1024;
12764
12769
  function machineIdToPeerId(machineId) {
12765
12770
  return machineId;
12766
12771
  }
12767
12772
  async function loadDefaultFactory() {
12768
12773
  const { RTCPeerConnection } = await import("node-datachannel/polyfill");
12769
12774
  return () => {
12770
- const pc = new RTCPeerConnection({ iceServers: ICE_SERVERS });
12775
+ const pc = new RTCPeerConnection({
12776
+ iceServers: ICE_SERVERS,
12777
+ maxMessageSize: MAX_MESSAGE_SIZE
12778
+ });
12771
12779
  return pc;
12772
12780
  };
12773
12781
  }
@@ -12911,6 +12919,40 @@ function createPeerManager(config2) {
12911
12919
  };
12912
12920
  }
12913
12921
 
12922
+ // src/plan-editor/format-diff-feedback.ts
12923
+ function formatDiffFeedbackForClaudeCode(comments, generalFeedback) {
12924
+ const sections = [];
12925
+ if (generalFeedback) {
12926
+ sections.push("## General Feedback\n");
12927
+ sections.push(generalFeedback);
12928
+ sections.push("");
12929
+ }
12930
+ if (comments.length > 0) {
12931
+ appendCommentSection(sections, comments);
12932
+ }
12933
+ return sections.join("\n").trim();
12934
+ }
12935
+ function appendCommentSection(sections, comments) {
12936
+ sections.push("## Inline Comments on Code Changes\n");
12937
+ sections.push("The user left the following comments on the diff:\n");
12938
+ const byFile = /* @__PURE__ */ new Map();
12939
+ for (const c of comments) {
12940
+ const existing = byFile.get(c.filePath) ?? [];
12941
+ existing.push(c);
12942
+ byFile.set(c.filePath, existing);
12943
+ }
12944
+ for (const [filePath, fileComments] of byFile) {
12945
+ sections.push(`### ${filePath}
12946
+ `);
12947
+ const sorted = [...fileComments].sort((a, b) => a.lineNumber - b.lineNumber);
12948
+ for (const comment2 of sorted) {
12949
+ const sideLabel = comment2.side === "old" ? "before change" : "after change";
12950
+ sections.push(`> Line ${comment2.lineNumber} (${sideLabel}): ${comment2.body}`);
12951
+ }
12952
+ sections.push("");
12953
+ }
12954
+ }
12955
+
12914
12956
  // src/plan-editor/format-plan-feedback.ts
12915
12957
  function formatPlanFeedbackForClaudeCode(original, edited, comments, generalFeedback) {
12916
12958
  const sections = [];
@@ -12920,7 +12962,7 @@ function formatPlanFeedbackForClaudeCode(original, edited, comments, generalFeed
12920
12962
  sections.push("");
12921
12963
  }
12922
12964
  if (comments.length > 0) {
12923
- appendCommentSection(sections, comments, edited);
12965
+ appendCommentSection2(sections, comments, edited);
12924
12966
  }
12925
12967
  if (original !== edited) {
12926
12968
  sections.push("## Edits Made\n");
@@ -12932,7 +12974,7 @@ function formatPlanFeedbackForClaudeCode(original, edited, comments, generalFeed
12932
12974
  }
12933
12975
  return sections.join("\n").trim();
12934
12976
  }
12935
- function appendCommentSection(sections, comments, edited) {
12977
+ function appendCommentSection2(sections, comments, edited) {
12936
12978
  sections.push("## Inline Comments\n");
12937
12979
  sections.push("The user left the following comments on specific parts of the plan:\n");
12938
12980
  const editedLines = edited.split("\n");
@@ -41781,6 +41823,9 @@ var SessionManager = class {
41781
41823
  await this.#activeQuery?.setModel(model);
41782
41824
  this.#currentModel = model;
41783
41825
  }
41826
+ async setPermissionMode(mode) {
41827
+ await this.#activeQuery?.setPermissionMode(mode);
41828
+ }
41784
41829
  /**
41785
41830
  * Resume an existing Claude Code session using streaming input mode.
41786
41831
  * Looks up the agentSessionId from the task doc and passes it as `resume`.
@@ -43719,10 +43764,6 @@ function promotePendingFollowUps(taskHandle, activeTask, taskLog) {
43719
43764
  taskLog.debug("Task is being aborted, skipping pending follow-up promotion");
43720
43765
  return;
43721
43766
  }
43722
- if (!activeTask.sessionManager.isStreaming) {
43723
- taskLog.debug("Task not streaming, skipping pending follow-up promotion");
43724
- return;
43725
- }
43726
43767
  const json = taskHandle.doc.toJSON();
43727
43768
  const pending = json.pendingFollowUps ?? [];
43728
43769
  if (pending.length === 0) return;
@@ -43735,17 +43776,32 @@ function promotePendingFollowUps(taskHandle, activeTask, taskLog) {
43735
43776
  draft.pendingFollowUps.delete(0, draft.pendingFollowUps.length);
43736
43777
  }
43737
43778
  });
43779
+ taskLog.info({ pendingCount: pending.length }, "Promoted pending follow-ups to conversation");
43780
+ if (!activeTask.sessionManager.isStreaming) {
43781
+ taskLog.debug("Task not streaming, skipping follow-up dispatch");
43782
+ return;
43783
+ }
43738
43784
  const allContentBlocks = pending.flatMap(
43739
43785
  (msg) => msg.content.filter((block2) => block2.type === "text" || block2.type === "image")
43740
43786
  );
43741
- if (allContentBlocks.length > 0) {
43787
+ if (allContentBlocks.length === 0) return;
43788
+ const dispatchFollowUp = () => {
43742
43789
  try {
43743
- taskLog.info({ pendingCount: pending.length }, "Promoted pending follow-ups to conversation");
43744
43790
  activeTask.lastDispatchedConvLen = json.conversation.length + pending.length;
43745
43791
  activeTask.sessionManager.sendFollowUp(allContentBlocks);
43746
43792
  } catch (err) {
43747
43793
  taskLog.warn({ err }, "Failed to send promoted follow-up");
43748
43794
  }
43795
+ };
43796
+ const lastPending = pending[pending.length - 1];
43797
+ const mappedMode = lastPending?.permissionMode ? mapPermissionMode(lastPending.permissionMode) : void 0;
43798
+ if (mappedMode) {
43799
+ activeTask.sessionManager.setPermissionMode(mappedMode).then(dispatchFollowUp).catch((err) => {
43800
+ taskLog.warn({ err }, "Failed to update permission mode from queued message");
43801
+ dispatchFollowUp();
43802
+ });
43803
+ } else {
43804
+ dispatchFollowUp();
43749
43805
  }
43750
43806
  }
43751
43807
  function onTaskDocChanged(taskId, taskHandle, taskLog, ctx) {
@@ -43974,6 +44030,19 @@ function resolveExitPlanMode(taskHandle, taskLog, toolUseID, value) {
43974
44030
  "Updated plan reviewStatus in CRDT with rich feedback"
43975
44031
  );
43976
44032
  }
44033
+ function resolveAskUserQuestion(taskLog, toolUseID, input, value) {
44034
+ if (value.decision !== "approved" || !value.message) return input;
44035
+ try {
44036
+ const parsed = JSON.parse(value.message);
44037
+ if ("answers" in parsed && parsed.answers && typeof parsed.answers === "object" && !Array.isArray(parsed.answers)) {
44038
+ taskLog.info({ toolUseID }, "Merged AskUserQuestion answers into input");
44039
+ return { ...input, answers: parsed.answers };
44040
+ }
44041
+ } catch {
44042
+ taskLog.warn({ toolUseID }, "Failed to parse AskUserQuestion answers from message");
44043
+ }
44044
+ return input;
44045
+ }
43977
44046
  function resolvePermissionResponse(ctx) {
43978
44047
  const { taskHandle, roomDoc, taskId, taskLog, toolName, toolUseID, input, suggestions, value } = ctx;
43979
44048
  taskHandle.permReqs.delete(toolUseID);
@@ -43986,6 +44055,10 @@ function resolvePermissionResponse(ctx) {
43986
44055
  if (toolName === "ExitPlanMode") {
43987
44056
  resolveExitPlanMode(taskHandle, taskLog, toolUseID, value);
43988
44057
  }
44058
+ let resolvedInput = input;
44059
+ if (toolName === "AskUserQuestion") {
44060
+ resolvedInput = resolveAskUserQuestion(taskLog, toolUseID, input, value);
44061
+ }
43989
44062
  taskLog.info(
43990
44063
  {
43991
44064
  toolName,
@@ -43997,7 +44070,8 @@ function resolvePermissionResponse(ctx) {
43997
44070
  "Permission response received"
43998
44071
  );
43999
44072
  const decision = value.decision === "approved" ? "approved" : "denied";
44000
- return toPermissionResult(decision, input, suggestions, value.message);
44073
+ const resultMessage = toolName === "AskUserQuestion" && decision === "approved" ? null : value.message;
44074
+ return toPermissionResult(decision, resolvedInput, suggestions, resultMessage);
44001
44075
  }
44002
44076
  function buildCanUseTool(taskHandle, taskLog, roomDoc, taskId) {
44003
44077
  return async (toolName, input, options) => {
@@ -44078,6 +44152,76 @@ function buildCanUseTool(taskHandle, taskLog, roomDoc, taskId) {
44078
44152
  });
44079
44153
  };
44080
44154
  }
44155
+ function collectPlanFeedback(unresolvedPlan, plans, loroDoc) {
44156
+ const parts = [];
44157
+ const byPlanId = /* @__PURE__ */ new Map();
44158
+ for (const c of unresolvedPlan) {
44159
+ const existing = byPlanId.get(c.planId) ?? [];
44160
+ existing.push(c);
44161
+ byPlanId.set(c.planId, existing);
44162
+ }
44163
+ for (const [planId, comments] of byPlanId) {
44164
+ const plan = plans.find((p) => p.planId === planId);
44165
+ const originalMarkdown = plan?.markdown ?? "";
44166
+ const editedMarkdown = serializePlanEditorDoc(loroDoc, planId) || originalMarkdown;
44167
+ const feedback = formatPlanFeedbackForClaudeCode(
44168
+ originalMarkdown,
44169
+ editedMarkdown,
44170
+ comments,
44171
+ null
44172
+ );
44173
+ if (feedback) {
44174
+ parts.push(feedback);
44175
+ }
44176
+ }
44177
+ return parts;
44178
+ }
44179
+ function harvestUndeliveredComments(taskHandle, contentBlocks, log) {
44180
+ const json = taskHandle.doc.toJSON();
44181
+ const deliveredSet = new Set(json.deliveredCommentIds ?? []);
44182
+ const unresolvedDiff = Object.values(json.diffComments).filter(
44183
+ (c) => c.resolvedAt === null && !deliveredSet.has(c.commentId)
44184
+ );
44185
+ const unresolvedPlan = Object.values(json.planComments).filter(
44186
+ (c) => c.resolvedAt === null && !deliveredSet.has(c.commentId)
44187
+ );
44188
+ if (unresolvedDiff.length === 0 && unresolvedPlan.length === 0) {
44189
+ return;
44190
+ }
44191
+ const feedbackParts = [];
44192
+ if (unresolvedDiff.length > 0) {
44193
+ const diffFeedback = formatDiffFeedbackForClaudeCode(unresolvedDiff, null);
44194
+ if (diffFeedback) {
44195
+ feedbackParts.push(diffFeedback);
44196
+ }
44197
+ }
44198
+ if (unresolvedPlan.length > 0) {
44199
+ feedbackParts.push(...collectPlanFeedback(unresolvedPlan, json.plans, taskHandle.loroDoc));
44200
+ }
44201
+ if (feedbackParts.length === 0) {
44202
+ return;
44203
+ }
44204
+ const feedbackText = `
44205
+
44206
+ ---
44207
+ **User feedback on your changes (comments from code review):**
44208
+
44209
+ ${feedbackParts.join("\n\n")}`;
44210
+ contentBlocks.push({ type: "text", text: feedbackText });
44211
+ const harvestedIds = [
44212
+ ...unresolvedDiff.map((c) => c.commentId),
44213
+ ...unresolvedPlan.map((c) => c.commentId)
44214
+ ];
44215
+ change(taskHandle.doc, (draft) => {
44216
+ for (const id of harvestedIds) {
44217
+ draft.deliveredCommentIds.push(id);
44218
+ }
44219
+ });
44220
+ log.info(
44221
+ { diffCommentCount: unresolvedDiff.length, planCommentCount: unresolvedPlan.length },
44222
+ "Harvested undelivered comments as safety net"
44223
+ );
44224
+ }
44081
44225
  async function runTask(opts) {
44082
44226
  const {
44083
44227
  sessionManager: manager,
@@ -44096,13 +44240,14 @@ async function runTask(opts) {
44096
44240
  if (!contentBlocks || contentBlocks.length === 0) {
44097
44241
  throw new Error(`No user message found in task ${taskId}`);
44098
44242
  }
44243
+ harvestUndeliveredComments(taskHandle, contentBlocks, log);
44099
44244
  const textPreview = contentBlocks.filter((b) => b.type === "text").map((b) => b.text).join(" ").slice(0, 100);
44100
44245
  const imageCount = contentBlocks.filter((b) => b.type === "image").length;
44101
44246
  log.info(
44102
44247
  { prompt: textPreview || "(images only)", imageCount },
44103
44248
  "Running task with prompt from CRDT"
44104
44249
  );
44105
- const canUseTool = permissionMode === "bypassPermissions" ? void 0 : buildCanUseTool(taskHandle, log, roomDoc, taskId);
44250
+ const canUseTool = buildCanUseTool(taskHandle, log, roomDoc, taskId);
44106
44251
  const stderr = (data) => {
44107
44252
  const trimmed = data.trim();
44108
44253
  if (!trimmed) return;
@@ -44123,7 +44268,7 @@ async function runTask(opts) {
44123
44268
  effort,
44124
44269
  canUseTool,
44125
44270
  stderr,
44126
- allowDangerouslySkipPermissions: permissionMode === "bypassPermissions" ? true : void 0
44271
+ allowDangerouslySkipPermissions: true
44127
44272
  });
44128
44273
  }
44129
44274
  return manager.createSession({
@@ -44136,7 +44281,7 @@ async function runTask(opts) {
44136
44281
  abortController,
44137
44282
  canUseTool,
44138
44283
  stderr,
44139
- allowDangerouslySkipPermissions: permissionMode === "bypassPermissions" ? true : void 0
44284
+ allowDangerouslySkipPermissions: true
44140
44285
  });
44141
44286
  }
44142
44287