clay-server 3.4.0-beta.2 → 3.4.0-beta.21
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/lib/daemon-projects.js +3 -3
- package/lib/daemon.js +59 -59
- package/lib/project-connection.js +35 -7
- package/lib/project-debate.js +4 -0
- package/lib/project-mate-interaction.js +21 -1
- package/lib/project-memory.js +3 -0
- package/lib/project-models.js +220 -0
- package/lib/project-session-handoff.js +162 -0
- package/lib/project-session-pair.js +14 -7
- package/lib/project-session-spawn.js +1 -1
- package/lib/project-sessions.js +13 -45
- package/lib/project-worker-proposal.js +51 -14
- package/lib/project.js +38 -80
- package/lib/public/antigravity-avatar.png +0 -0
- package/lib/public/app.js +29 -0
- package/lib/public/copilot-avatar.svg +5 -0
- package/lib/public/css/command-palette.css +22 -2
- package/lib/public/css/icon-strip.css +29 -13
- package/lib/public/css/input.css +56 -0
- package/lib/public/css/mates.css +6 -0
- package/lib/public/css/menus.css +38 -25
- package/lib/public/css/messages.css +9 -0
- package/lib/public/css/pane.css +16 -3
- package/lib/public/css/pwa-mobile.css +17 -0
- package/lib/public/css/session-actions.css +98 -0
- package/lib/public/css/title-bar.css +19 -0
- package/lib/public/grok-avatar.svg +4 -0
- package/lib/public/index.html +29 -7
- package/lib/public/junie-avatar.svg +11 -0
- package/lib/public/kimi-avatar.svg +4 -0
- package/lib/public/modules/agent-config-selects.js +69 -0
- package/lib/public/modules/app-header.js +4 -22
- package/lib/public/modules/app-messages.js +54 -11
- package/lib/public/modules/app-panels.js +36 -83
- package/lib/public/modules/app-projects.js +3 -1
- package/lib/public/modules/app-rendering.js +19 -4
- package/lib/public/modules/background-tasks-ui.js +55 -0
- package/lib/public/modules/mate-sidebar.js +11 -3
- package/lib/public/modules/model-picker.js +263 -0
- package/lib/public/modules/notifications.js +7 -1
- package/lib/public/modules/pane-bridge.js +11 -0
- package/lib/public/modules/pane-links.js +23 -0
- package/lib/public/modules/project-switcher.js +7 -6
- package/lib/public/modules/session-actions.js +294 -0
- package/lib/public/modules/sidebar-mates.js +1 -1
- package/lib/public/modules/sidebar-mobile.js +2 -1
- package/lib/public/modules/sidebar-projects.js +34 -43
- package/lib/public/modules/sidebar-sessions.js +20 -6
- package/lib/public/modules/split-pair-ui.js +16 -77
- package/lib/public/modules/split-view.js +3 -0
- package/lib/public/modules/tools.js +6 -1
- package/lib/public/modules/vendor-priority.js +14 -0
- package/lib/public/modules/vendor-selection.js +20 -0
- package/lib/public/modules/worker-proposal.js +6 -1
- package/lib/public/modules/worktree-location.js +17 -0
- package/lib/public/qwen-avatar.svg +4 -0
- package/lib/public/style.css +4 -2
- package/lib/sdk-bridge.js +103 -56
- package/lib/sdk-message-processor.js +26 -4
- package/lib/session-handoff-context.js +110 -0
- package/lib/session-notes-mcp-server.js +8 -1
- package/lib/sessions.js +4 -0
- package/lib/worktree.js +9 -4
- package/lib/ws-schema.js +9 -1
- package/lib/yoke/acp-agent-profiles.js +64 -14
- package/lib/yoke/adapters/antigravity.js +417 -0
- package/lib/yoke/adapters/claude.js +34 -0
- package/lib/yoke/adapters/codex.js +101 -5
- package/lib/yoke/adapters/copilot.js +7 -0
- package/lib/yoke/adapters/grok.js +7 -0
- package/lib/yoke/adapters/junie.js +7 -0
- package/lib/yoke/adapters/kimi.js +7 -0
- package/lib/yoke/adapters/kiro.js +2 -2
- package/lib/yoke/adapters/qwen.js +7 -0
- package/lib/yoke/codex-app-server.js +25 -8
- package/lib/yoke/index.js +67 -28
- package/lib/yoke/instructions.js +3 -0
- package/lib/yoke/interface.js +12 -0
- package/lib/yoke/vendor-registry.js +61 -6
- package/package.json +1 -1
- package/lib/public/gemini-avatar.svg +0 -11
- package/lib/yoke/adapters/gemini.js +0 -7
package/lib/sdk-bridge.js
CHANGED
|
@@ -110,6 +110,7 @@ function createSDKBridge(opts) {
|
|
|
110
110
|
var adapters = opts.adapters || {};
|
|
111
111
|
var mateDisplayName = opts.mateDisplayName || "";
|
|
112
112
|
var isMate = opts.isMate || (slug.indexOf("mate-") === 0);
|
|
113
|
+
var osUsers = !!opts.osUsers;
|
|
113
114
|
var dangerouslySkipPermissions = opts.dangerouslySkipPermissions || false;
|
|
114
115
|
// mcpServers may be either a static object or a getter function. The
|
|
115
116
|
// getter form lets callers gate individual servers at call time (e.g.
|
|
@@ -244,17 +245,24 @@ function createSDKBridge(opts) {
|
|
|
244
245
|
return null;
|
|
245
246
|
}
|
|
246
247
|
|
|
247
|
-
function
|
|
248
|
+
function defaultModelForVendor(vendor) {
|
|
249
|
+
return (sm.defaultModelByVendor && sm.defaultModelByVendor[vendor]) || "";
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function sendModelInfoForVendor(session, vendor, model) {
|
|
248
253
|
var resolvedVendor = vendor || (adapter && adapter.vendor) || "claude";
|
|
249
|
-
|
|
254
|
+
var message = {
|
|
250
255
|
type: "model_info",
|
|
251
256
|
model: model || "",
|
|
252
257
|
models: getModelsForVendor(resolvedVendor),
|
|
253
258
|
vendor: resolvedVendor,
|
|
259
|
+
sessionId: session ? session.localId : null,
|
|
254
260
|
capabilities: (sm.capabilitiesByVendor && sm.capabilitiesByVendor[resolvedVendor]) || {},
|
|
255
261
|
availableVendors: sm.availableVendors || [],
|
|
256
262
|
installedVendors: sm.installedVendors || [],
|
|
257
|
-
}
|
|
263
|
+
};
|
|
264
|
+
if (session) sendToSession(session, message);
|
|
265
|
+
else send(message);
|
|
258
266
|
}
|
|
259
267
|
|
|
260
268
|
function rememberAdapterReady(vendor, result) {
|
|
@@ -323,6 +331,14 @@ function createSDKBridge(opts) {
|
|
|
323
331
|
var mergeSkills = skills.mergeSkills;
|
|
324
332
|
|
|
325
333
|
// --- Message processing (extracted to sdk-message-processor.js) ---
|
|
334
|
+
function getSessionLinuxUser(session) {
|
|
335
|
+
if (!osUsers || !session) return null;
|
|
336
|
+
if (session.lastLinuxUser) return session.lastLinuxUser;
|
|
337
|
+
if (!session.ownerId) return null;
|
|
338
|
+
var owner = usersModule.findUserById(session.ownerId);
|
|
339
|
+
return owner && owner.linuxUser ? owner.linuxUser : null;
|
|
340
|
+
}
|
|
341
|
+
|
|
326
342
|
// Auto-generate a session title via YOKE adapter.generateTitle().
|
|
327
343
|
// Triggered by sdk-message-processor after AUTO_TITLE_TURN_THRESHOLD turns.
|
|
328
344
|
function autoGenerateTitle(session) {
|
|
@@ -345,7 +361,10 @@ function createSDKBridge(opts) {
|
|
|
345
361
|
}
|
|
346
362
|
console.log("[auto-title] Calling adapter.generateTitle with " + userMessages.length + " messages for session " + session.localId);
|
|
347
363
|
|
|
348
|
-
sessionAdapter.generateTitle(userMessages, {
|
|
364
|
+
sessionAdapter.generateTitle(userMessages, {
|
|
365
|
+
cwd: cwd,
|
|
366
|
+
linuxUser: getSessionLinuxUser(session) || undefined,
|
|
367
|
+
}).then(function(title) {
|
|
349
368
|
if (!title || title.length < 2) return;
|
|
350
369
|
title = title.substring(0, 100);
|
|
351
370
|
if (!session.titleManuallySet) {
|
|
@@ -837,19 +856,20 @@ function createSDKBridge(opts) {
|
|
|
837
856
|
case "model_changed":
|
|
838
857
|
session.model = metaData.model;
|
|
839
858
|
sm.saveSessionFile(session);
|
|
840
|
-
sm.
|
|
841
|
-
|
|
842
|
-
|
|
859
|
+
sm.defaultModelByVendor = sm.defaultModelByVendor || {};
|
|
860
|
+
sm.defaultModelByVendor[session.vendor || "claude"] = metaData.model;
|
|
861
|
+
sendModelInfoForVendor(session, session.vendor || (adapter && adapter.vendor) || "claude", metaData.model);
|
|
862
|
+
sendToSession(session, { type: "config_state", model: session.model || defaultModelForVendor(session.vendor || "claude"), vendor: session.vendor || "claude", sessionId: session.localId, mode: session.permissionMode || sm.currentPermissionMode || "default", effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
|
|
843
863
|
break;
|
|
844
864
|
case "effort_changed":
|
|
845
865
|
session.effort = metaData.effort;
|
|
846
866
|
sm.saveSessionFile(session);
|
|
847
867
|
sm.currentEffort = metaData.effort;
|
|
848
|
-
sendToSession(session, { type: "config_state", model: session.model ||
|
|
868
|
+
sendToSession(session, { type: "config_state", model: session.model || defaultModelForVendor(session.vendor || "claude"), vendor: session.vendor || "claude", sessionId: session.localId, mode: session.permissionMode || sm.currentPermissionMode || "default", effort: session.effort, betas: sm.currentBetas || [] });
|
|
849
869
|
break;
|
|
850
870
|
case "permission_mode_changed":
|
|
851
871
|
sm.currentPermissionMode = metaData.mode;
|
|
852
|
-
sendToSession(session, { type: "config_state", model: session.model ||
|
|
872
|
+
sendToSession(session, { type: "config_state", model: session.model || defaultModelForVendor(session.vendor || "claude"), vendor: session.vendor || "claude", sessionId: session.localId, mode: session.permissionMode || sm.currentPermissionMode, effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
|
|
853
873
|
break;
|
|
854
874
|
case "worker_error":
|
|
855
875
|
send({ type: "error", text: metaData.error });
|
|
@@ -865,7 +885,10 @@ function createSDKBridge(opts) {
|
|
|
865
885
|
console.log("[sdk-bridge] processQueryStream ended: isProcessing=" + session.isProcessing + " taskStopRequested=" + session.taskStopRequested);
|
|
866
886
|
var stillOwnsRuntime = session.queryInstance === myQueryInstance;
|
|
867
887
|
if (session.isProcessing && session.taskStopRequested && stillOwnsRuntime) {
|
|
888
|
+
session._lastTurnInterrupted = true;
|
|
868
889
|
session.isProcessing = false;
|
|
890
|
+
session._awaitingTurnResult = false;
|
|
891
|
+
session._queuedTurnCount = 0;
|
|
869
892
|
onProcessingChanged();
|
|
870
893
|
send({ type: "status", processing: false });
|
|
871
894
|
sendAndRecord(session, { type: "thinking_stop" });
|
|
@@ -880,6 +903,8 @@ function createSDKBridge(opts) {
|
|
|
880
903
|
// iterator without a result event. Treat any result-less stream end as
|
|
881
904
|
// a failed turn so the UI cannot remain on processing forever.
|
|
882
905
|
session.isProcessing = false;
|
|
906
|
+
session._awaitingTurnResult = false;
|
|
907
|
+
session._queuedTurnCount = 0;
|
|
883
908
|
onProcessingChanged();
|
|
884
909
|
if (!session._lastAdapterError) {
|
|
885
910
|
sendAndRecord(session, {
|
|
@@ -894,8 +919,11 @@ function createSDKBridge(opts) {
|
|
|
894
919
|
} catch (err) {
|
|
895
920
|
if (session.isProcessing && session.queryInstance === myQueryInstance) {
|
|
896
921
|
session.isProcessing = false;
|
|
922
|
+
session._awaitingTurnResult = false;
|
|
923
|
+
session._queuedTurnCount = 0;
|
|
897
924
|
onProcessingChanged();
|
|
898
925
|
if (err.name === "AbortError" || (myAbortController && myAbortController.signal.aborted) || session.taskStopRequested) {
|
|
926
|
+
session._lastTurnInterrupted = true;
|
|
899
927
|
if (!session.destroying) {
|
|
900
928
|
sendAndRecord(session, { type: "thinking_stop" });
|
|
901
929
|
var interruptMsg2 = (session.vendor && session.vendor !== "claude")
|
|
@@ -1131,7 +1159,9 @@ function createSDKBridge(opts) {
|
|
|
1131
1159
|
async function rollbackConversation(session, numTurns) {
|
|
1132
1160
|
var sessionAdapter = getAdapterForSession(session);
|
|
1133
1161
|
if (sessionAdapter && typeof sessionAdapter.rollbackThread === "function") {
|
|
1134
|
-
await sessionAdapter.rollbackThread(session.cliSessionId, numTurns
|
|
1162
|
+
await sessionAdapter.rollbackThread(session.cliSessionId, numTurns, {
|
|
1163
|
+
linuxUser: getSessionLinuxUser(session) || undefined,
|
|
1164
|
+
});
|
|
1135
1165
|
}
|
|
1136
1166
|
// Claude: conversation rollback is handled by rewindFiles + local history trim
|
|
1137
1167
|
}
|
|
@@ -1143,7 +1173,11 @@ function createSDKBridge(opts) {
|
|
|
1143
1173
|
|
|
1144
1174
|
async function forkSessionUnified(session, uuid) {
|
|
1145
1175
|
var sessionAdapter = getAdapterForSession(session);
|
|
1146
|
-
var result = await sessionAdapter.forkSession(session.cliSessionId, {
|
|
1176
|
+
var result = await sessionAdapter.forkSession(session.cliSessionId, {
|
|
1177
|
+
upToMessageId: uuid,
|
|
1178
|
+
dir: cwd,
|
|
1179
|
+
linuxUser: getSessionLinuxUser(session) || undefined,
|
|
1180
|
+
});
|
|
1147
1181
|
if (!result || !result.sessionId) throw new Error("Fork returned no session id");
|
|
1148
1182
|
|
|
1149
1183
|
// Adapters with rollbackThread (e.g. Codex) use local history copy
|
|
@@ -1157,6 +1191,7 @@ function createSDKBridge(opts) {
|
|
|
1157
1191
|
// Wrapper: marks the boot window so pushMessage can buffer instead of
|
|
1158
1192
|
// dropping messages that arrive while createQuery is still awaiting.
|
|
1159
1193
|
async function startQuery(session, text, images, linuxUser) {
|
|
1194
|
+
delete session._lastTurnInterrupted;
|
|
1160
1195
|
session._queryStarting = true;
|
|
1161
1196
|
try {
|
|
1162
1197
|
return await startQueryInner(session, text, images, linuxUser);
|
|
@@ -1208,10 +1243,10 @@ function createSDKBridge(opts) {
|
|
|
1208
1243
|
sm.modelsByVendor[vendor] = await vendorAdapter.supportedModels();
|
|
1209
1244
|
}
|
|
1210
1245
|
var vendorModels = getModelsForVendor(vendor);
|
|
1211
|
-
var modelForVendor = resolveModelInList(vendorModels,
|
|
1246
|
+
var modelForVendor = resolveModelInList(vendorModels, defaultModelForVendor(vendor))
|
|
1212
1247
|
|| modelEntryValue(vendorModels[0])
|
|
1213
1248
|
|| "";
|
|
1214
|
-
sendModelInfoForVendor(vendor, modelForVendor);
|
|
1249
|
+
sendModelInfoForVendor(session, vendor, modelForVendor);
|
|
1215
1250
|
}
|
|
1216
1251
|
return vendorAdapter;
|
|
1217
1252
|
}
|
|
@@ -1414,14 +1449,9 @@ function createSDKBridge(opts) {
|
|
|
1414
1449
|
}
|
|
1415
1450
|
}
|
|
1416
1451
|
|
|
1417
|
-
// Pick a model
|
|
1418
|
-
//
|
|
1419
|
-
|
|
1420
|
-
// project (or in another session that switches vendor to claude) and
|
|
1421
|
-
// Claude would reject the unknown model. We validate against the
|
|
1422
|
-
// session vendor's model list regardless of which vendor happens to be
|
|
1423
|
-
// the project's default adapter.
|
|
1424
|
-
var queryModel = (ls.model && ls.model !== "default" ? ls.model : null) || session.model || sm.currentModel || undefined;
|
|
1452
|
+
// Pick a model from the session or its vendor-specific default, then
|
|
1453
|
+
// validate it against the session vendor's known model list.
|
|
1454
|
+
var queryModel = (ls.model && ls.model !== "default" ? ls.model : null) || session.model || defaultModelForVendor(session.vendor || "claude") || undefined;
|
|
1425
1455
|
var sessionVendor = session.vendor || (adapter && adapter.vendor) || null;
|
|
1426
1456
|
if (sessionVendor) {
|
|
1427
1457
|
var vendorModels = (sm.modelsByVendor && sm.modelsByVendor[sessionVendor]) || [];
|
|
@@ -1437,8 +1467,7 @@ function createSDKBridge(opts) {
|
|
|
1437
1467
|
}
|
|
1438
1468
|
|
|
1439
1469
|
// Bind the resolved model to the session so it survives a daemon
|
|
1440
|
-
// restart.
|
|
1441
|
-
// in-memory value) came back after restart on the adapter default.
|
|
1470
|
+
// restart. This preserves a session-specific selection across restarts.
|
|
1442
1471
|
if (queryModel && session.model !== queryModel) {
|
|
1443
1472
|
session.model = queryModel;
|
|
1444
1473
|
sm.saveSessionFile(session);
|
|
@@ -1471,6 +1500,7 @@ function createSDKBridge(opts) {
|
|
|
1471
1500
|
|
|
1472
1501
|
var queryOpts = {
|
|
1473
1502
|
cwd: cwd,
|
|
1503
|
+
linuxUser: linuxUser || undefined,
|
|
1474
1504
|
// appendSystemPrompt, NOT systemPrompt: a plain-string systemPrompt
|
|
1475
1505
|
// REPLACES the entire Claude Code system prompt (sdk.d.ts:2096), which
|
|
1476
1506
|
// would strip the driver session down to the two-sentence pair preset.
|
|
@@ -1528,6 +1558,10 @@ function createSDKBridge(opts) {
|
|
|
1528
1558
|
ACP: {
|
|
1529
1559
|
mcpServers: getAcpMcpServers(session),
|
|
1530
1560
|
},
|
|
1561
|
+
ANTIGRAVITY: {
|
|
1562
|
+
dangerouslySkipPermissions: dangerouslySkipPermissions
|
|
1563
|
+
|| session.permissionMode === "bypassPermissions",
|
|
1564
|
+
},
|
|
1531
1565
|
KIRO: {
|
|
1532
1566
|
engine: kiroConfig.engine,
|
|
1533
1567
|
mode: kiroConfig.mode,
|
|
@@ -1561,6 +1595,8 @@ function createSDKBridge(opts) {
|
|
|
1561
1595
|
console.error("[sdk-bridge] cliSessionId:", session.cliSessionId, "resume:", !!session.cliSessionId);
|
|
1562
1596
|
console.error("[sdk-bridge] Stack:", e.stack || "(no stack)");
|
|
1563
1597
|
session.isProcessing = false;
|
|
1598
|
+
session._awaitingTurnResult = false;
|
|
1599
|
+
session._queuedTurnCount = 0;
|
|
1564
1600
|
onProcessingChanged();
|
|
1565
1601
|
session.queryInstance = null;
|
|
1566
1602
|
session.messageQueue = null;
|
|
@@ -1602,6 +1638,8 @@ function createSDKBridge(opts) {
|
|
|
1602
1638
|
session.messageQueue = null;
|
|
1603
1639
|
session.abortController = null;
|
|
1604
1640
|
session.isProcessing = false;
|
|
1641
|
+
session._awaitingTurnResult = false;
|
|
1642
|
+
session._queuedTurnCount = 0;
|
|
1605
1643
|
onProcessingChanged();
|
|
1606
1644
|
sendAndRecord(session, { type: "error", text: "The agent connection closed before it received your message. Please send it again." });
|
|
1607
1645
|
sendAndRecord(session, { type: "done", code: 1 });
|
|
@@ -1672,6 +1710,7 @@ function createSDKBridge(opts) {
|
|
|
1672
1710
|
console.error("[sdk-bridge] QueryHandle rejected message for session " + session.localId + ":", e.message || e);
|
|
1673
1711
|
}
|
|
1674
1712
|
if (delivered) {
|
|
1713
|
+
delete session._lastTurnInterrupted;
|
|
1675
1714
|
if (session._awaitingTurnResult) {
|
|
1676
1715
|
session._queuedTurnCount = (session._queuedTurnCount || 0) + 1;
|
|
1677
1716
|
} else {
|
|
@@ -1790,16 +1829,20 @@ function createSDKBridge(opts) {
|
|
|
1790
1829
|
} catch (e) {}
|
|
1791
1830
|
if ((codexBin && fs.existsSync(codexBin)) || tryLookup(yoke.getVendorInfo("codex").binaryName)) result.push("codex");
|
|
1792
1831
|
|
|
1793
|
-
var
|
|
1832
|
+
var subprocessVendorKeys = ["antigravity", "opencode", "kimi", "grok", "copilot", "qwen", "junie"];
|
|
1794
1833
|
var acpProfiles = require("./yoke/acp-agent-profiles");
|
|
1795
|
-
for (var
|
|
1796
|
-
var
|
|
1797
|
-
var
|
|
1798
|
-
if (!linuxUser ||
|
|
1799
|
-
|
|
1800
|
-
|
|
1834
|
+
for (var subprocessVendorIndex = 0; subprocessVendorIndex < subprocessVendorKeys.length; subprocessVendorIndex++) {
|
|
1835
|
+
var subprocessVendor = subprocessVendorKeys[subprocessVendorIndex];
|
|
1836
|
+
var subprocessInfo = yoke.getVendorInfo(subprocessVendor);
|
|
1837
|
+
if (!linuxUser || subprocessInfo.osUserIsolation) {
|
|
1838
|
+
if (subprocessVendor === "antigravity") {
|
|
1839
|
+
if (tryLookup(subprocessInfo.binaryName)) result.push(subprocessVendor);
|
|
1840
|
+
} else {
|
|
1841
|
+
var acpProfile = acpProfiles.getAcpAgentProfile(subprocessVendor);
|
|
1842
|
+
if (acpProfiles.findAcpAgentPath(acpProfile)) result.push(subprocessVendor);
|
|
1843
|
+
}
|
|
1801
1844
|
} else {
|
|
1802
|
-
console.log("[sdk-bridge] " +
|
|
1845
|
+
console.log("[sdk-bridge] " + subprocessInfo.displayName + " hidden for OS-isolated user " + linuxUser + ": per-user spawning is not implemented");
|
|
1803
1846
|
}
|
|
1804
1847
|
}
|
|
1805
1848
|
|
|
@@ -1832,6 +1875,13 @@ function createSDKBridge(opts) {
|
|
|
1832
1875
|
var defaultVendor = adapter ? adapter.vendor : "claude";
|
|
1833
1876
|
sm.defaultVendor = defaultVendor;
|
|
1834
1877
|
|
|
1878
|
+
// Installation detection is independent of adapter initialization. Publish
|
|
1879
|
+
// it before warmup so a new project does not render the initial empty state
|
|
1880
|
+
// as if every CLI were missing while the default adapter starts up.
|
|
1881
|
+
sm.installedVendors = detectInstalledVendors(linuxUser);
|
|
1882
|
+
sm.availableVendors = getAvailableVendors(linuxUser);
|
|
1883
|
+
sendModelInfoForVendor(null, defaultVendor, defaultModelForVendor(defaultVendor));
|
|
1884
|
+
|
|
1835
1885
|
// Initialize default adapter first (provides skills, slash commands, etc.)
|
|
1836
1886
|
if (adapter) {
|
|
1837
1887
|
try {
|
|
@@ -1862,7 +1912,8 @@ function createSDKBridge(opts) {
|
|
|
1862
1912
|
send({ type: "slash_commands", commands: combined, vendor: defaultVendor });
|
|
1863
1913
|
}
|
|
1864
1914
|
if (result.defaultModel) {
|
|
1865
|
-
sm.
|
|
1915
|
+
sm.defaultModelByVendor = sm.defaultModelByVendor || {};
|
|
1916
|
+
sm.defaultModelByVendor[defaultVendor] = sm.defaultModelByVendor[defaultVendor] || result.defaultModel;
|
|
1866
1917
|
}
|
|
1867
1918
|
sm.availableModels = result.models || [];
|
|
1868
1919
|
// Store per-vendor models and capabilities
|
|
@@ -1883,19 +1934,8 @@ function createSDKBridge(opts) {
|
|
|
1883
1934
|
// actually issues a query with it.
|
|
1884
1935
|
sm.modelsByVendor = sm.modelsByVendor || {};
|
|
1885
1936
|
|
|
1886
|
-
//
|
|
1887
|
-
|
|
1888
|
-
sm.availableVendors = getAvailableVendors(linuxUser);
|
|
1889
|
-
|
|
1890
|
-
// Send initial state to client
|
|
1891
|
-
send({
|
|
1892
|
-
type: "model_info",
|
|
1893
|
-
model: sm.currentModel || "",
|
|
1894
|
-
models: getModelsForVendor(defaultVendor),
|
|
1895
|
-
vendor: defaultVendor,
|
|
1896
|
-
availableVendors: sm.availableVendors,
|
|
1897
|
-
installedVendors: sm.installedVendors,
|
|
1898
|
-
});
|
|
1937
|
+
// Send the fully initialized state to the client.
|
|
1938
|
+
sendModelInfoForVendor(null, defaultVendor, defaultModelForVendor(defaultVendor));
|
|
1899
1939
|
}
|
|
1900
1940
|
|
|
1901
1941
|
async function setModel(session, model) {
|
|
@@ -1907,22 +1947,28 @@ function createSDKBridge(opts) {
|
|
|
1907
1947
|
// No active query — just store the model for next startQuery
|
|
1908
1948
|
session.model = model;
|
|
1909
1949
|
sm.saveSessionFile(session);
|
|
1910
|
-
sm.
|
|
1911
|
-
//
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1950
|
+
sm.defaultModelByVendor = sm.defaultModelByVendor || {};
|
|
1951
|
+
// Confirm against the session vendor so a non-default vendor does not
|
|
1952
|
+
// receive model metadata labeled as the project's default adapter.
|
|
1953
|
+
var pendingVendor = session.vendor || (adapter && adapter.vendor) || "claude";
|
|
1954
|
+
sm.defaultModelByVendor[pendingVendor] = model;
|
|
1955
|
+
sendModelInfoForVendor(session, pendingVendor, model);
|
|
1956
|
+
sendToSession(session, { type: "config_state", model: model, vendor: pendingVendor, sessionId: session.localId, mode: session.permissionMode || sm.currentPermissionMode || "default", effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
|
|
1957
|
+
return { ok: true, model: model };
|
|
1915
1958
|
}
|
|
1916
1959
|
try {
|
|
1917
1960
|
await session.queryInstance.setModel(model);
|
|
1918
1961
|
session.model = model;
|
|
1919
1962
|
sm.saveSessionFile(session);
|
|
1920
|
-
sm.
|
|
1963
|
+
sm.defaultModelByVendor = sm.defaultModelByVendor || {};
|
|
1921
1964
|
var sessionVendor = session.vendor || (adapter && adapter.vendor) || "claude";
|
|
1922
|
-
|
|
1923
|
-
|
|
1965
|
+
sm.defaultModelByVendor[sessionVendor] = model;
|
|
1966
|
+
sendModelInfoForVendor(session, sessionVendor, model);
|
|
1967
|
+
sendToSession(session, { type: "config_state", model: model, vendor: sessionVendor, sessionId: session.localId, mode: session.permissionMode || sm.currentPermissionMode || "default", effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
|
|
1968
|
+
return { ok: true, model: model };
|
|
1924
1969
|
} catch (e) {
|
|
1925
1970
|
send({ type: "error", text: "Failed to switch model: " + (e.message || e) });
|
|
1971
|
+
return { ok: false, model: session.model || "", error: e.message || String(e) };
|
|
1926
1972
|
}
|
|
1927
1973
|
}
|
|
1928
1974
|
|
|
@@ -1951,14 +1997,14 @@ function createSDKBridge(opts) {
|
|
|
1951
1997
|
if (!session.queryInstance) {
|
|
1952
1998
|
// No active query — just store the mode for next startQuery
|
|
1953
1999
|
sm.currentPermissionMode = mode;
|
|
1954
|
-
sendToSession(session, { type: "config_state", model: session.model ||
|
|
2000
|
+
sendToSession(session, { type: "config_state", model: session.model || defaultModelForVendor(session.vendor || "claude"), vendor: session.vendor || "claude", sessionId: session.localId, mode: sm.currentPermissionMode, effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
|
|
1955
2001
|
return;
|
|
1956
2002
|
}
|
|
1957
2003
|
try {
|
|
1958
2004
|
// Route through QueryHandle (works for both in-process and worker paths)
|
|
1959
2005
|
await session.queryInstance.setPermissionMode(mode);
|
|
1960
2006
|
sm.currentPermissionMode = mode;
|
|
1961
|
-
sendToSession(session, { type: "config_state", model: session.model ||
|
|
2007
|
+
sendToSession(session, { type: "config_state", model: session.model || defaultModelForVendor(session.vendor || "claude"), vendor: session.vendor || "claude", sessionId: session.localId, mode: sm.currentPermissionMode, effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
|
|
1962
2008
|
} catch (e) {
|
|
1963
2009
|
sendToSession(session, { type: "error", text: "Failed to set permission mode: " + (e.message || e) });
|
|
1964
2010
|
}
|
|
@@ -2009,7 +2055,7 @@ function createSDKBridge(opts) {
|
|
|
2009
2055
|
// Creates a mention session that can be reused across multiple mentions
|
|
2010
2056
|
// within a conversation flow (session continuity).
|
|
2011
2057
|
async function createMentionSession(opts) {
|
|
2012
|
-
// opts: { vendor, claudeMd, initialContext, initialMessage, onDelta, onDone, onError, onActivity }
|
|
2058
|
+
// opts: { vendor, linuxUser, claudeMd, initialContext, initialMessage, onDelta, onDone, onError, onActivity }
|
|
2013
2059
|
var abortController = new AbortController();
|
|
2014
2060
|
|
|
2015
2061
|
// Current response callbacks (swapped on each pushMessage)
|
|
@@ -2029,6 +2075,7 @@ function createSDKBridge(opts) {
|
|
|
2029
2075
|
try {
|
|
2030
2076
|
handle = await mentionAdapter.createQuery({
|
|
2031
2077
|
cwd: cwd,
|
|
2078
|
+
linuxUser: opts.linuxUser || undefined,
|
|
2032
2079
|
systemPrompt: opts.claudeMd,
|
|
2033
2080
|
model: opts.model || undefined,
|
|
2034
2081
|
toolServers: opts.includeMcpServers ? (mergeMcpServers(getMcpServers(), getRemoteMcpServers) || undefined) : undefined,
|
|
@@ -175,6 +175,10 @@ function attachMessageProcessor(ctx) {
|
|
|
175
175
|
|
|
176
176
|
// Cache slash_commands and model from CLI init message
|
|
177
177
|
if (parsed.yokeType === "init") {
|
|
178
|
+
var previousBackgroundTaskCount = (session.activeBackgroundTasks || []).length;
|
|
179
|
+
session.activeBackgroundTasks = [];
|
|
180
|
+
sendToSession(session, { type: "active_background_tasks", tasks: [] });
|
|
181
|
+
if (previousBackgroundTaskCount !== 0) sm.broadcastSessionList();
|
|
178
182
|
var fsSkills = discoverSkillDirs();
|
|
179
183
|
sm.skillNames = mergeSkills(parsed.skills, fsSkills);
|
|
180
184
|
if (parsed.slashCommands) {
|
|
@@ -192,13 +196,20 @@ function attachMessageProcessor(ctx) {
|
|
|
192
196
|
send({ type: "slash_commands", commands: sm.slashCommands });
|
|
193
197
|
}
|
|
194
198
|
if (parsed.model) {
|
|
195
|
-
sm.currentModel = sm.currentModel || sm._savedDefaultModel || parsed.model;
|
|
196
199
|
var initVendor = session.vendor || (adapter && adapter.vendor) || "claude";
|
|
197
|
-
|
|
200
|
+
sm.defaultModelByVendor = sm.defaultModelByVendor || {};
|
|
201
|
+
sm.defaultModelByVendor[initVendor] = sm.defaultModelByVendor[initVendor] || parsed.model;
|
|
202
|
+
var initModels = getModelsForVendor(initVendor);
|
|
203
|
+
var initModel = session.model || sm.defaultModelByVendor[initVendor] || "";
|
|
204
|
+
if (initModels.length > 0 && !initModels.some(function(entry) {
|
|
205
|
+
return entry === initModel || (entry && (entry.value === initModel || entry.id === initModel || entry.resolvedModel === initModel));
|
|
206
|
+
})) initModel = typeof initModels[0] === "string" ? initModels[0] : (initModels[0].value || initModels[0].id || "");
|
|
207
|
+
sendToSession(session, {
|
|
198
208
|
type: "model_info",
|
|
199
|
-
model:
|
|
200
|
-
models:
|
|
209
|
+
model: initModel,
|
|
210
|
+
models: initModels,
|
|
201
211
|
vendor: initVendor,
|
|
212
|
+
sessionId: session.localId,
|
|
202
213
|
availableVendors: sm.availableVendors || [],
|
|
203
214
|
installedVendors: sm.installedVendors || [],
|
|
204
215
|
});
|
|
@@ -612,6 +623,15 @@ function attachMessageProcessor(ctx) {
|
|
|
612
623
|
});
|
|
613
624
|
}
|
|
614
625
|
|
|
626
|
+
} else if (parsed.yokeType === "background_tasks_changed") {
|
|
627
|
+
var previousBackgroundTaskCount = (session.activeBackgroundTasks || []).length;
|
|
628
|
+
var activeBackgroundTasks = Array.isArray(parsed.tasks) ? parsed.tasks : [];
|
|
629
|
+
session.activeBackgroundTasks = activeBackgroundTasks;
|
|
630
|
+
sendToSession(session, { type: "active_background_tasks", tasks: activeBackgroundTasks });
|
|
631
|
+
if (previousBackgroundTaskCount !== activeBackgroundTasks.length) {
|
|
632
|
+
sm.broadcastSessionList();
|
|
633
|
+
}
|
|
634
|
+
|
|
615
635
|
} else if (parsed.yokeType === "tool_progress") {
|
|
616
636
|
// Sub-agent tool_progress: forward as activity update
|
|
617
637
|
var parentId = parsed.parentToolId;
|
|
@@ -783,6 +803,8 @@ function attachMessageProcessor(ctx) {
|
|
|
783
803
|
// app-server returned an unauthorized/token-revoked error). Trigger the
|
|
784
804
|
// same login flow as the Claude login-prompt path.
|
|
785
805
|
session.isProcessing = false;
|
|
806
|
+
session._awaitingTurnResult = false;
|
|
807
|
+
session._queuedTurnCount = 0;
|
|
786
808
|
onProcessingChanged();
|
|
787
809
|
emitAuthRequired(session);
|
|
788
810
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
var spawnSync = require("child_process").spawnSync;
|
|
2
|
+
var yoke = require("./yoke");
|
|
3
|
+
|
|
4
|
+
var MAX_CONTEXT_CHARS = 36000;
|
|
5
|
+
var MAX_TURNS = 12;
|
|
6
|
+
var MAX_USER_CHARS = 5000;
|
|
7
|
+
var MAX_ASSISTANT_CHARS = 9000;
|
|
8
|
+
var MAX_GIT_CHARS = 5000;
|
|
9
|
+
var MAX_TRANSCRIPT_CHARS = 24000;
|
|
10
|
+
|
|
11
|
+
function trimText(value, limit) {
|
|
12
|
+
var text = typeof value === "string" ? value.trim() : "";
|
|
13
|
+
if (text.length <= limit) return text;
|
|
14
|
+
return text.slice(0, limit) + "\n[truncated]";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function recentTurns(history) {
|
|
18
|
+
var turns = [];
|
|
19
|
+
var current = null;
|
|
20
|
+
history = Array.isArray(history) ? history : [];
|
|
21
|
+
for (var i = 0; i < history.length; i++) {
|
|
22
|
+
var entry = history[i];
|
|
23
|
+
if (!entry) continue;
|
|
24
|
+
if (entry.type === "user_message" || (entry.type === "handoff_context" && entry.request)) {
|
|
25
|
+
current = { user: trimText(entry.text || entry.request, MAX_USER_CHARS), assistant: "" };
|
|
26
|
+
turns.push(current);
|
|
27
|
+
} else if (entry.type === "delta" && entry.text) {
|
|
28
|
+
if (!current) {
|
|
29
|
+
current = { user: "", assistant: "" };
|
|
30
|
+
turns.push(current);
|
|
31
|
+
}
|
|
32
|
+
current.assistant += entry.text;
|
|
33
|
+
if (current.assistant.length > MAX_ASSISTANT_CHARS) {
|
|
34
|
+
current.assistant = current.assistant.slice(-MAX_ASSISTANT_CHARS);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return turns.slice(-MAX_TURNS);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function latestUserRequest(history) {
|
|
42
|
+
var turns = recentTurns(history);
|
|
43
|
+
for (var i = turns.length - 1; i >= 0; i--) {
|
|
44
|
+
if (turns[i].user) return turns[i].user;
|
|
45
|
+
}
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function gitCommand(cwd, args) {
|
|
50
|
+
var result = spawnSync("git", args, {
|
|
51
|
+
cwd: cwd,
|
|
52
|
+
encoding: "utf8",
|
|
53
|
+
timeout: 5000,
|
|
54
|
+
maxBuffer: 1024 * 1024,
|
|
55
|
+
});
|
|
56
|
+
if (result.error || result.status !== 0) return "";
|
|
57
|
+
return trimText(result.stdout, MAX_GIT_CHARS);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function repositoryState(cwd) {
|
|
61
|
+
var status = gitCommand(cwd, ["status", "--short", "--branch"]);
|
|
62
|
+
var lines = [];
|
|
63
|
+
lines.push("Working tree:\n" + (status || "clean"));
|
|
64
|
+
return lines.join("\n\n");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function transcriptText(turns) {
|
|
68
|
+
var sections = [];
|
|
69
|
+
for (var i = 0; i < turns.length; i++) {
|
|
70
|
+
var turn = turns[i];
|
|
71
|
+
var parts = ["Turn " + (i + 1)];
|
|
72
|
+
if (turn.user) parts.push("USER:\n" + turn.user);
|
|
73
|
+
if (turn.assistant) parts.push("ASSISTANT:\n" + trimText(turn.assistant, MAX_ASSISTANT_CHARS));
|
|
74
|
+
sections.push(parts.join("\n\n"));
|
|
75
|
+
}
|
|
76
|
+
while (sections.length > 1 && sections.join("\n\n---\n\n").length > MAX_TRANSCRIPT_CHARS) {
|
|
77
|
+
sections.shift();
|
|
78
|
+
}
|
|
79
|
+
return sections.join("\n\n---\n\n");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildHandoffContext(options) {
|
|
83
|
+
var source = options.source;
|
|
84
|
+
var targetVendor = options.targetVendor;
|
|
85
|
+
var sourceVendor = source.vendor || "claude";
|
|
86
|
+
var sourceName = (yoke.getVendorInfo(sourceVendor) || {}).displayName || sourceVendor;
|
|
87
|
+
var targetName = (yoke.getVendorInfo(targetVendor) || {}).displayName || targetVendor;
|
|
88
|
+
var turns = recentTurns(source.history);
|
|
89
|
+
var latestUser = latestUserRequest(source.history);
|
|
90
|
+
var parts = [
|
|
91
|
+
"[Clay session handoff]",
|
|
92
|
+
"You are continuing work from another Clay coding-agent session. This is a snapshot, not a native conversation resume. Verify the current filesystem state before acting.",
|
|
93
|
+
"Source agent: " + sourceName,
|
|
94
|
+
"Target agent: " + targetName,
|
|
95
|
+
"Source session: " + (source.title || "Untitled session") + " (#" + source.localId + ")",
|
|
96
|
+
];
|
|
97
|
+
if (latestUser) parts.push("Current user request, verbatim:\n" + latestUser);
|
|
98
|
+
parts.push("Repository state at handoff:\n" + repositoryState(options.cwd));
|
|
99
|
+
parts.push("Recent conversation:\n" + transcriptText(turns));
|
|
100
|
+
parts.push("Continue from the unresolved work above. Preserve the user's decisions and constraints, inspect the actual files before making assumptions, and proceed without asking the user to repeat context.");
|
|
101
|
+
return trimText(parts.join("\n\n"), MAX_CONTEXT_CHARS);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
MAX_CONTEXT_CHARS: MAX_CONTEXT_CHARS,
|
|
106
|
+
buildHandoffContext: buildHandoffContext,
|
|
107
|
+
latestUserRequest: latestUserRequest,
|
|
108
|
+
recentTurns: recentTurns,
|
|
109
|
+
repositoryState: repositoryState,
|
|
110
|
+
};
|
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
var buildShape = require("./session-spawn-mcp-server").buildShape;
|
|
4
4
|
|
|
5
|
-
var MEMORY_CONTRACT =
|
|
5
|
+
var MEMORY_CONTRACT =
|
|
6
|
+
"The sticky-note board persists across sessions and is a user-facing artifact shared with people and Clay agents, not private agent scratch space. " +
|
|
7
|
+
"Default to not writing. Create a note proactively only when the user explicitly asks to remember or track something, or when all of these are true: it will remain useful after the current task and session, it is not already adequately recorded in the repository or another note, and the user would likely be glad to find it on the board a week later. " +
|
|
8
|
+
"Good notes capture an unresolved commitment, durable product decision, user preference, constraint, or handoff that will materially change future work. " +
|
|
9
|
+
"Important exception for deferred defects: while doing code or technical work, actively create a sticky note when you discover a concrete defect, regression risk, security issue, or data-loss risk that is outside the current session goal and will remain unfixed when the turn ends. Do not wait for the user to ask. Include the observable evidence, affected component, likely impact, and a clear next action. Check active notes first when a duplicate is plausible. Do not create defect notes for speculation, general cleanup ideas, or problems you fixed in the current session. " +
|
|
10
|
+
"Important exception for deferred proposals: when you propose work (a fix, follow-up, improvement, or next step) and the user defers it rather than declining it (\"let's do that later\", \"next time\", \"after this\"), actively create a sticky note before the topic moves on. Do not wait to be asked. Capture what was proposed, why it mattered, and the agreed timing if any. Deferred agreements scroll out of chat history quickly and are hard to track; the board is where they survive. If the user declines the idea outright, do not write a note. " +
|
|
11
|
+
"Never create a note merely because work is important, lengthy, spans agents or restarts, or might help another agent. Do not record completed work, implementation details, test results, investigation logs, transient blockers, conversation summaries, or announcements of your own activity. When uncertain, do not write. " +
|
|
12
|
+
"Updates are visible too: update only when durable state materially changes, and remove a note created by your session when it stops being useful instead of turning it into a completion log. Put a concise plain-text title on the first line, stay focused on one topic, and include only the context needed for future action.";
|
|
6
13
|
|
|
7
14
|
function getToolDefs(handlers) {
|
|
8
15
|
return [
|
package/lib/sessions.js
CHANGED
|
@@ -169,6 +169,7 @@ function createSessionManager(opts) {
|
|
|
169
169
|
if (session.lastRewindUuid) metaObj.lastRewindUuid = session.lastRewindUuid;
|
|
170
170
|
if (session.loop) metaObj.loop = session.loop;
|
|
171
171
|
if (session.spawn) metaObj.spawn = session.spawn;
|
|
172
|
+
if (session.handoff) metaObj.handoff = session.handoff;
|
|
172
173
|
if (session.debateState) metaObj.debateState = session.debateState;
|
|
173
174
|
if (session.debateSetupMode) metaObj.debateSetupMode = true;
|
|
174
175
|
var meta = JSON.stringify(metaObj);
|
|
@@ -281,6 +282,7 @@ function createSessionManager(opts) {
|
|
|
281
282
|
session.effort = m.effort || null;
|
|
282
283
|
if (m.loop) session.loop = m.loop;
|
|
283
284
|
if (m.spawn) session.spawn = m.spawn;
|
|
285
|
+
if (m.handoff) session.handoff = m.handoff;
|
|
284
286
|
if (m.debateState) session.debateState = m.debateState;
|
|
285
287
|
if (m.debateSetupMode) session.debateSetupMode = true;
|
|
286
288
|
if (m.ownerId) session.ownerId = m.ownerId;
|
|
@@ -493,6 +495,7 @@ function createSessionManager(opts) {
|
|
|
493
495
|
title: s.title || "New Session",
|
|
494
496
|
active: isActive,
|
|
495
497
|
isProcessing: s.isProcessing,
|
|
498
|
+
backgroundTaskCount: (s.activeBackgroundTasks || []).length,
|
|
496
499
|
lastActivity: s.lastActivity || s.createdAt || 0,
|
|
497
500
|
loop: loop,
|
|
498
501
|
spawn: s.spawn || null,
|
|
@@ -715,6 +718,7 @@ function createSessionManager(opts) {
|
|
|
715
718
|
if (session.isProcessing) {
|
|
716
719
|
_send({ type: "status", status: "processing" });
|
|
717
720
|
}
|
|
721
|
+
_send({ type: "active_background_tasks", tasks: session.activeBackgroundTasks || [] });
|
|
718
722
|
|
|
719
723
|
// Re-send any pending permission requests
|
|
720
724
|
var pendingIds = Object.keys(session.pendingPermissions);
|
package/lib/worktree.js
CHANGED
|
@@ -43,9 +43,14 @@ function isWorktree(projectPath) {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function isPathInside(parentPath, candidatePath) {
|
|
47
|
+
var relative = path.relative(path.resolve(parentPath), path.resolve(candidatePath));
|
|
48
|
+
return relative !== "" && relative !== ".." && relative.indexOf(".." + path.sep) !== 0 && !path.isAbsolute(relative);
|
|
49
|
+
}
|
|
50
|
+
|
|
46
51
|
// Scan worktrees for a given project path
|
|
47
|
-
// Returns array of { path, branch, bare, detached,
|
|
48
|
-
//
|
|
52
|
+
// Returns array of { path, branch, bare, detached, external }
|
|
53
|
+
// external = true when Git registered the worktree outside the main project folder
|
|
49
54
|
function scanWorktrees(projectPath) {
|
|
50
55
|
var resolvedParent = path.resolve(projectPath);
|
|
51
56
|
try {
|
|
@@ -63,7 +68,7 @@ function scanWorktrees(projectPath) {
|
|
|
63
68
|
if (wt.bare) continue;
|
|
64
69
|
var resolvedWt = path.resolve(wt.path);
|
|
65
70
|
if (resolvedWt === resolvedParent) continue;
|
|
66
|
-
wt.
|
|
71
|
+
wt.external = !isPathInside(resolvedParent, resolvedWt);
|
|
67
72
|
wt.dirName = path.basename(wt.path);
|
|
68
73
|
results.push(wt);
|
|
69
74
|
}
|
|
@@ -131,4 +136,4 @@ function removeWorktree(projectPath, worktreeDirName) {
|
|
|
131
136
|
}
|
|
132
137
|
}
|
|
133
138
|
|
|
134
|
-
module.exports = { scanWorktrees: scanWorktrees, createWorktree: createWorktree, removeWorktree: removeWorktree, isWorktree: isWorktree };
|
|
139
|
+
module.exports = { scanWorktrees: scanWorktrees, createWorktree: createWorktree, removeWorktree: removeWorktree, isWorktree: isWorktree, isPathInside: isPathInside };
|