@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 +160 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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({
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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:
|
|
44284
|
+
allowDangerouslySkipPermissions: true
|
|
44140
44285
|
});
|
|
44141
44286
|
}
|
|
44142
44287
|
|