clay-server 3.2.2-beta.3 → 3.3.0-beta.2
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/git-cli.js +370 -0
- package/lib/git-session-attribution.js +219 -0
- package/lib/project-connection.js +1 -15
- package/lib/project-email.js +0 -61
- package/lib/project-http.js +104 -1
- package/lib/project-session-pair.js +129 -43
- package/lib/project-sessions.js +1 -11
- package/lib/project-user-message.js +2 -0
- package/lib/project-worker-proposal.js +317 -0
- package/lib/project.js +23 -8
- package/lib/public/app.js +14 -2
- package/lib/public/css/filebrowser.css +320 -0
- package/lib/public/css/git-panel.css +424 -0
- package/lib/public/css/input.css +0 -3
- package/lib/public/css/worker-proposal.css +127 -0
- package/lib/public/index.html +12 -15
- package/lib/public/modules/app-messages.js +11 -6
- package/lib/public/modules/context-sources.js +0 -90
- package/lib/public/modules/filebrowser.js +135 -21
- package/lib/public/modules/git-panel.js +456 -0
- package/lib/public/modules/input.js +8 -0
- package/lib/public/modules/markdown-slides.js +292 -0
- package/lib/public/modules/mate-sidebar.js +0 -7
- package/lib/public/modules/sidebar.js +42 -0
- package/lib/public/modules/tool-palette.js +1 -2
- package/lib/public/modules/worker-proposal.js +239 -0
- package/lib/public/style.css +2 -0
- package/lib/sdk-bridge.js +47 -6
- package/lib/sdk-message-processor.js +26 -2
- package/lib/session-pair-mcp-server.js +2 -2
- package/lib/sessions.js +1 -1
- package/lib/ws-schema.js +3 -0
- package/lib/yoke/adapters/claude.js +43 -17
- package/lib/yoke/index.js +19 -0
- package/package.json +1 -1
package/lib/sdk-bridge.js
CHANGED
|
@@ -559,7 +559,7 @@ function createSDKBridge(opts) {
|
|
|
559
559
|
// split partner, and a prompt on every delegation hop defeats the
|
|
560
560
|
// driver/worker workflow. spawn_sessions is NOT here: it creates new
|
|
561
561
|
// sessions and keeps its prompt.
|
|
562
|
-
var pairPartnerTools = { send_to_partner: true, read_partner: true };
|
|
562
|
+
var pairPartnerTools = { send_to_partner: true, read_partner: true, propose_worker: true };
|
|
563
563
|
if (toolName.indexOf("mcp__clay-sessions__") === 0) {
|
|
564
564
|
var sessionsToolName = toolName.substring(toolName.lastIndexOf("__") + 2);
|
|
565
565
|
if (pairPartnerTools[sessionsToolName]) {
|
|
@@ -863,7 +863,8 @@ function createSDKBridge(opts) {
|
|
|
863
863
|
// Stream ended normally after a task stop — no "result" message was sent,
|
|
864
864
|
// so the session is still marked as processing. Send interrupted feedback.
|
|
865
865
|
console.log("[sdk-bridge] processQueryStream ended: isProcessing=" + session.isProcessing + " taskStopRequested=" + session.taskStopRequested);
|
|
866
|
-
|
|
866
|
+
var stillOwnsRuntime = session.queryInstance === myQueryInstance;
|
|
867
|
+
if (session.isProcessing && session.taskStopRequested && stillOwnsRuntime) {
|
|
867
868
|
session.isProcessing = false;
|
|
868
869
|
onProcessingChanged();
|
|
869
870
|
send({ type: "status", processing: false });
|
|
@@ -874,9 +875,24 @@ function createSDKBridge(opts) {
|
|
|
874
875
|
sendAndRecord(session, { type: "info", text: interruptMsg });
|
|
875
876
|
sendAndRecord(session, { type: "done", code: 0 });
|
|
876
877
|
sm.broadcastSessionList();
|
|
878
|
+
} else if (session.isProcessing && stillOwnsRuntime) {
|
|
879
|
+
// Codex and Kiro can report a protocol error and then close their
|
|
880
|
+
// iterator without a result event. Treat any result-less stream end as
|
|
881
|
+
// a failed turn so the UI cannot remain on processing forever.
|
|
882
|
+
session.isProcessing = false;
|
|
883
|
+
onProcessingChanged();
|
|
884
|
+
if (!session._lastAdapterError) {
|
|
885
|
+
sendAndRecord(session, {
|
|
886
|
+
type: "error",
|
|
887
|
+
text: "The agent connection ended before completing the response.",
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
session._lastAdapterError = null;
|
|
891
|
+
sendAndRecord(session, { type: "done", code: 1 });
|
|
892
|
+
sm.broadcastSessionList();
|
|
877
893
|
}
|
|
878
894
|
} catch (err) {
|
|
879
|
-
if (session.isProcessing) {
|
|
895
|
+
if (session.isProcessing && session.queryInstance === myQueryInstance) {
|
|
880
896
|
session.isProcessing = false;
|
|
881
897
|
onProcessingChanged();
|
|
882
898
|
if (err.name === "AbortError" || (myAbortController && myAbortController.signal.aborted) || session.taskStopRequested) {
|
|
@@ -1310,6 +1326,7 @@ function createSDKBridge(opts) {
|
|
|
1310
1326
|
session.pendingElicitations = {};
|
|
1311
1327
|
session.streamedText = false;
|
|
1312
1328
|
session.responsePreview = "";
|
|
1329
|
+
session._lastAdapterError = null;
|
|
1313
1330
|
|
|
1314
1331
|
// For in-process path, create AbortController. For worker path, the adapter
|
|
1315
1332
|
// handles abort internally and exposes it via handle.abort().
|
|
@@ -1574,7 +1591,22 @@ function createSDKBridge(opts) {
|
|
|
1574
1591
|
|
|
1575
1592
|
// Push initial user message through the QueryHandle
|
|
1576
1593
|
console.log("[sdk-bridge] pushing initial message via handle.pushMessage...");
|
|
1577
|
-
handle.pushMessage(text, images);
|
|
1594
|
+
var initialMessageAccepted = handle.pushMessage(text, images) !== false;
|
|
1595
|
+
if (!initialMessageAccepted) {
|
|
1596
|
+
console.error("[sdk-bridge] Query rejected initial message for session " + session.localId);
|
|
1597
|
+
try { handle.close(); } catch (e) {}
|
|
1598
|
+
if (session.queryInstance === handle) session.queryInstance = null;
|
|
1599
|
+
session.messageQueue = null;
|
|
1600
|
+
session.abortController = null;
|
|
1601
|
+
session.isProcessing = false;
|
|
1602
|
+
onProcessingChanged();
|
|
1603
|
+
sendAndRecord(session, { type: "error", text: "The agent connection closed before it received your message. Please send it again." });
|
|
1604
|
+
sendAndRecord(session, { type: "done", code: 1 });
|
|
1605
|
+
sm.broadcastSessionList();
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
session._awaitingTurnResult = true;
|
|
1609
|
+
session._queuedTurnCount = 0;
|
|
1578
1610
|
console.log("[sdk-bridge] pushMessage done, starting processQueryStream...");
|
|
1579
1611
|
|
|
1580
1612
|
// Flush messages that arrived while createQuery was still awaiting
|
|
@@ -1585,7 +1617,9 @@ function createSDKBridge(opts) {
|
|
|
1585
1617
|
session.pendingPush = [];
|
|
1586
1618
|
console.log("[sdk-bridge] flushing " + backlog.length + " buffered message(s) into new query");
|
|
1587
1619
|
for (var bi = 0; bi < backlog.length; bi++) {
|
|
1588
|
-
handle.pushMessage(backlog[bi].text, backlog[bi].images)
|
|
1620
|
+
if (handle.pushMessage(backlog[bi].text, backlog[bi].images) !== false) {
|
|
1621
|
+
session._queuedTurnCount++;
|
|
1622
|
+
}
|
|
1589
1623
|
}
|
|
1590
1624
|
}
|
|
1591
1625
|
|
|
@@ -1634,7 +1668,14 @@ function createSDKBridge(opts) {
|
|
|
1634
1668
|
} catch (e) {
|
|
1635
1669
|
console.error("[sdk-bridge] QueryHandle rejected message for session " + session.localId + ":", e.message || e);
|
|
1636
1670
|
}
|
|
1637
|
-
if (delivered)
|
|
1671
|
+
if (delivered) {
|
|
1672
|
+
if (session._awaitingTurnResult) {
|
|
1673
|
+
session._queuedTurnCount = (session._queuedTurnCount || 0) + 1;
|
|
1674
|
+
} else {
|
|
1675
|
+
session._awaitingTurnResult = true;
|
|
1676
|
+
}
|
|
1677
|
+
return true;
|
|
1678
|
+
}
|
|
1638
1679
|
|
|
1639
1680
|
// The handle object can outlive its underlying input queue or worker.
|
|
1640
1681
|
// Retire it before the caller starts a resumed replacement query; the
|
|
@@ -451,6 +451,8 @@ function attachMessageProcessor(ctx) {
|
|
|
451
451
|
if (parsed.terminalReason) execError += " (reason: " + parsed.terminalReason + ")";
|
|
452
452
|
console.error("[sdk-bridge] Execution error for session " + session.localId + ": " + execError);
|
|
453
453
|
session.isProcessing = false;
|
|
454
|
+
session._awaitingTurnResult = false;
|
|
455
|
+
session._queuedTurnCount = 0;
|
|
454
456
|
onProcessingChanged();
|
|
455
457
|
sendAndRecord(session, { type: "error", text: "Claude error: " + execError });
|
|
456
458
|
sendAndRecord(session, { type: "done", code: 1 });
|
|
@@ -458,8 +460,17 @@ function attachMessageProcessor(ctx) {
|
|
|
458
460
|
return;
|
|
459
461
|
}
|
|
460
462
|
|
|
461
|
-
session.
|
|
462
|
-
|
|
463
|
+
session._lastAdapterError = null;
|
|
464
|
+
var hasQueuedTurn = (session._queuedTurnCount || 0) > 0;
|
|
465
|
+
if (hasQueuedTurn) {
|
|
466
|
+
session._queuedTurnCount--;
|
|
467
|
+
session._awaitingTurnResult = true;
|
|
468
|
+
} else {
|
|
469
|
+
session.isProcessing = false;
|
|
470
|
+
session._awaitingTurnResult = false;
|
|
471
|
+
session._queuedTurnCount = 0;
|
|
472
|
+
onProcessingChanged();
|
|
473
|
+
}
|
|
463
474
|
// Detect "Not logged in" scenario early for the check below
|
|
464
475
|
var previewTrimmed = (session.responsePreview || "").trim();
|
|
465
476
|
var isZeroCost = !parsed.cost || parsed.cost === 0;
|
|
@@ -494,6 +505,11 @@ function attachMessageProcessor(ctx) {
|
|
|
494
505
|
emitAuthRequired(session);
|
|
495
506
|
}
|
|
496
507
|
sendAndRecord(session, { type: "done", code: 0 });
|
|
508
|
+
if (hasQueuedTurn) {
|
|
509
|
+
// The adapter immediately continues with the already queued user turn.
|
|
510
|
+
// Restore the client stop state after the previous turn's done event.
|
|
511
|
+
sendToSession(session, { type: "status", status: "processing" });
|
|
512
|
+
}
|
|
497
513
|
var _donePreviewText = (session.responsePreview || "").replace(/\s+/g, " ").trim();
|
|
498
514
|
if (_donePreviewText.length > 140) _donePreviewText = _donePreviewText.substring(0, 140) + "...";
|
|
499
515
|
var _doneTitle = mateDisplayName ? (mateDisplayName + " responded") : (session.title || "Claude");
|
|
@@ -770,6 +786,14 @@ function attachMessageProcessor(ctx) {
|
|
|
770
786
|
onProcessingChanged();
|
|
771
787
|
emitAuthRequired(session);
|
|
772
788
|
|
|
789
|
+
} else if (parsed.yokeType === "error") {
|
|
790
|
+
// Adapters use a neutral error event for turn failures that arrive as
|
|
791
|
+
// protocol messages instead of thrown iterator errors. Remember it so
|
|
792
|
+
// processQueryStream can finish the turn when the iterator closes.
|
|
793
|
+
var adapterErrorText = parsed.text || parsed.message || parsed.error || "Agent runtime error";
|
|
794
|
+
session._lastAdapterError = adapterErrorText;
|
|
795
|
+
sendAndRecord(session, { type: "error", text: adapterErrorText });
|
|
796
|
+
|
|
773
797
|
} else if (parsed.yokeType === "model_refusal") {
|
|
774
798
|
// Model declined the request. "fallback" => the CLI retried on another
|
|
775
799
|
// model; "no_fallback" => the turn ended with a refusal.
|
|
@@ -6,7 +6,7 @@ function getToolDefs(handlers) {
|
|
|
6
6
|
return [
|
|
7
7
|
{
|
|
8
8
|
name: "send_to_partner",
|
|
9
|
-
description: "Delegate one concrete task to the other session in this split. The partner works visibly in its own pane. A delegated turn cannot delegate back, so keep orchestration one hop deep.",
|
|
9
|
+
description: "Delegate one concrete task to the other session in this split. The partner works visibly in its own pane. Detached completions are pushed back automatically. A delegated turn cannot delegate back, so keep orchestration one hop deep.",
|
|
10
10
|
inputSchema: buildShape({
|
|
11
11
|
message: { type: "string", description: "The complete task or question for the partner." },
|
|
12
12
|
wait: { type: "boolean", description: "Wait for the partner's turn to finish. Defaults to true." },
|
|
@@ -16,7 +16,7 @@ function getToolDefs(handlers) {
|
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
name: "read_partner",
|
|
19
|
-
description: "Read the partner's current status and recent conversation turns. Use this after a non-waiting delegation or timeout.",
|
|
19
|
+
description: "Read the partner's current status and recent conversation turns. Use this for an interim check after a non-waiting delegation or timeout; completed results are pushed automatically.",
|
|
20
20
|
inputSchema: buildShape({
|
|
21
21
|
lastTurns: { type: "number", description: "Number of recent user-message-delimited turns, from 1 to 5. Defaults to 1." },
|
|
22
22
|
}),
|
package/lib/sessions.js
CHANGED
|
@@ -926,7 +926,7 @@ function createSessionManager(opts) {
|
|
|
926
926
|
}
|
|
927
927
|
}
|
|
928
928
|
// Notify server for cross-project unread tracking
|
|
929
|
-
if (obj.type === "done") onSessionDone();
|
|
929
|
+
if (obj.type === "done") onSessionDone(session);
|
|
930
930
|
}
|
|
931
931
|
|
|
932
932
|
function resumeSession(cliSessionId, opts, targetWs) {
|
package/lib/ws-schema.js
CHANGED
|
@@ -38,6 +38,7 @@ var schema = {
|
|
|
38
38
|
"split_group_set_pair": { direction: "c2s", handler: "lib/session-split-groups.js", description: "Assign Driver/Worker roles on a split group (driverId null clears them)" },
|
|
39
39
|
"pair_session_options": { direction: "c2s", handler: "lib/project-session-pair.js", description: "Request vendors/models for the pair-session dialog (response reuses the same type)" },
|
|
40
40
|
"pair_session_create": { direction: "c2s", handler: "lib/project-session-pair.js", description: "Create a Driver/Worker session pair as a split group" },
|
|
41
|
+
"worker_proposal_response": { direction: "c2s", handler: "lib/project-worker-proposal.js", description: "Accept or decline a Fable Worker suggestion with the selected runtime" },
|
|
41
42
|
|
|
42
43
|
"session_list": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Full list of sessions for the sidebar" },
|
|
43
44
|
"session_switched": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Confirmation that active session changed" },
|
|
@@ -53,6 +54,8 @@ var schema = {
|
|
|
53
54
|
"split_groups": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Full persistent split-group list for the current user" },
|
|
54
55
|
"split_group_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Result of a split-group mutation" },
|
|
55
56
|
"pair_session_created": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Result of pair_session_create; carries the new group" },
|
|
57
|
+
"worker_proposal": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Inline Fable suggestion to delegate execution to a Worker" },
|
|
58
|
+
"worker_proposal_update": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Worker suggestion lifecycle update" },
|
|
56
59
|
"split_delegation": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "A pair delegation started or ended (drives the split-view flow indicator)" },
|
|
57
60
|
|
|
58
61
|
// -----------------------------------------------------------------------
|
|
@@ -701,12 +701,7 @@ function spawnWorker(linuxUser, workerScriptPath, cwd) {
|
|
|
701
701
|
});
|
|
702
702
|
|
|
703
703
|
worker.send = function(msg) {
|
|
704
|
-
|
|
705
|
-
try {
|
|
706
|
-
worker.connection.write(JSON.stringify(serializeWorkerValue(msg)) + "\n");
|
|
707
|
-
} catch (e) {
|
|
708
|
-
console.error("[yoke/claude] Failed to send to worker:", e.message);
|
|
709
|
-
}
|
|
704
|
+
return sendWorkerMessage(worker, msg);
|
|
710
705
|
};
|
|
711
706
|
|
|
712
707
|
worker.onMessage = function(handler) {
|
|
@@ -732,6 +727,36 @@ function spawnWorker(linuxUser, workerScriptPath, cwd) {
|
|
|
732
727
|
return worker;
|
|
733
728
|
}
|
|
734
729
|
|
|
730
|
+
function hasWritableWorkerConnection(worker) {
|
|
731
|
+
var connection = worker && worker.connection;
|
|
732
|
+
return !!connection
|
|
733
|
+
&& !connection.destroyed
|
|
734
|
+
&& !connection.writableEnded
|
|
735
|
+
&& connection.writable !== false;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function sendWorkerMessage(worker, msg) {
|
|
739
|
+
if (!hasWritableWorkerConnection(worker)) return false;
|
|
740
|
+
try {
|
|
741
|
+
// A false return from socket.write means backpressure, not rejection. The
|
|
742
|
+
// bytes are still queued, so delivery is accepted unless write throws.
|
|
743
|
+
worker.connection.write(JSON.stringify(serializeWorkerValue(msg)) + "\n");
|
|
744
|
+
return true;
|
|
745
|
+
} catch (e) {
|
|
746
|
+
console.error("[yoke/claude] Failed to send to worker:", e.message);
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function canReuseWorker(worker) {
|
|
752
|
+
return !!worker
|
|
753
|
+
&& worker.ready
|
|
754
|
+
&& worker.process
|
|
755
|
+
&& !worker.process.killed
|
|
756
|
+
&& worker.process.exitCode == null
|
|
757
|
+
&& hasWritableWorkerConnection(worker);
|
|
758
|
+
}
|
|
759
|
+
|
|
735
760
|
function cleanupWorker(worker) {
|
|
736
761
|
console.log("[yoke/claude] cleanupWorker() called, pid=" + (worker.process ? worker.process.pid : "?") + " stack=" + new Error().stack.split("\n").slice(1, 4).join(" | "));
|
|
737
762
|
if (worker._abortTimeout) { clearTimeout(worker._abortTimeout); worker._abortTimeout = null; }
|
|
@@ -1009,12 +1034,7 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
|
|
|
1009
1034
|
}
|
|
1010
1035
|
if (text) content.push({ type: "text", text: text });
|
|
1011
1036
|
var userMsg = { type: "user", message: { role: "user", content: content } };
|
|
1012
|
-
|
|
1013
|
-
worker.send({ type: "push_message", content: userMsg });
|
|
1014
|
-
return true;
|
|
1015
|
-
} catch (e) {
|
|
1016
|
-
return false;
|
|
1017
|
-
}
|
|
1037
|
+
return worker.send({ type: "push_message", content: userMsg }) === true;
|
|
1018
1038
|
},
|
|
1019
1039
|
|
|
1020
1040
|
setModel: function(model) {
|
|
@@ -1498,7 +1518,8 @@ function createClaudeAdapter(opts) {
|
|
|
1498
1518
|
var reusingWorker = false;
|
|
1499
1519
|
|
|
1500
1520
|
// Wait for previous worker exit if needed
|
|
1501
|
-
if (workerState && workerState.exitPromise
|
|
1521
|
+
if (workerState && workerState.exitPromise
|
|
1522
|
+
&& (!workerState.worker || !canReuseWorker(workerState.worker))) {
|
|
1502
1523
|
await Promise.race([
|
|
1503
1524
|
workerState.exitPromise,
|
|
1504
1525
|
new Promise(function(resolve) { setTimeout(resolve, 3000); }),
|
|
@@ -1506,8 +1527,7 @@ function createClaudeAdapter(opts) {
|
|
|
1506
1527
|
}
|
|
1507
1528
|
|
|
1508
1529
|
// Reuse existing worker if alive
|
|
1509
|
-
if (workerState && workerState.worker
|
|
1510
|
-
workerState.worker.process && !workerState.worker.process.killed) {
|
|
1530
|
+
if (workerState && canReuseWorker(workerState.worker)) {
|
|
1511
1531
|
worker = workerState.worker;
|
|
1512
1532
|
reusingWorker = true;
|
|
1513
1533
|
// Clear old message handlers so they don't fire for the new query
|
|
@@ -1564,7 +1584,7 @@ function createClaudeAdapter(opts) {
|
|
|
1564
1584
|
// will push the initial message and the worker receives it via push_message.
|
|
1565
1585
|
// Instead, we send query_start with no prompt; the worker starts a query with
|
|
1566
1586
|
// the message queue, and the first push_message will arrive.
|
|
1567
|
-
worker.send({
|
|
1587
|
+
if (!worker.send({
|
|
1568
1588
|
type: "query_start",
|
|
1569
1589
|
prompt: null,
|
|
1570
1590
|
options: queryOptions,
|
|
@@ -1572,7 +1592,9 @@ function createClaudeAdapter(opts) {
|
|
|
1572
1592
|
originalHome: claudeOpts.originalHome || null,
|
|
1573
1593
|
projectPath: claudeOpts.projectPath || null,
|
|
1574
1594
|
_perfT0: claudeOpts._perfT0 || Date.now(),
|
|
1575
|
-
})
|
|
1595
|
+
})) {
|
|
1596
|
+
throw new Error("Claude worker IPC connection closed before query start");
|
|
1597
|
+
}
|
|
1576
1598
|
|
|
1577
1599
|
return handle;
|
|
1578
1600
|
}
|
|
@@ -1808,6 +1830,10 @@ module.exports = {
|
|
|
1808
1830
|
contractTestKit: {
|
|
1809
1831
|
createMessageQueue: createMessageQueue,
|
|
1810
1832
|
createQueryHandle: createQueryHandle,
|
|
1833
|
+
createWorkerQueryHandle: createWorkerQueryHandle,
|
|
1834
|
+
hasWritableWorkerConnection: hasWritableWorkerConnection,
|
|
1835
|
+
sendWorkerMessage: sendWorkerMessage,
|
|
1836
|
+
canReuseWorker: canReuseWorker,
|
|
1811
1837
|
normalizeEvent: flattenEvent,
|
|
1812
1838
|
},
|
|
1813
1839
|
};
|
package/lib/yoke/index.js
CHANGED
|
@@ -8,6 +8,23 @@ var createClaudeAdapter = require("./adapters/claude").createClaudeAdapter;
|
|
|
8
8
|
var createCodexAdapter = require("./adapters/codex").createCodexAdapter;
|
|
9
9
|
var createKiroAdapter = require("./adapters/kiro").createKiroAdapter;
|
|
10
10
|
|
|
11
|
+
// Keep the first session in a new project predictable. This order is also
|
|
12
|
+
// used by the UI when a project has no remembered vendor yet.
|
|
13
|
+
var DEFAULT_VENDOR_ORDER = ["claude", "codex", "kiro"];
|
|
14
|
+
|
|
15
|
+
function resolveDefaultVendor(availableVendors) {
|
|
16
|
+
availableVendors = availableVendors || {};
|
|
17
|
+
for (var i = 0; i < DEFAULT_VENDOR_ORDER.length; i++) {
|
|
18
|
+
var vendor = DEFAULT_VENDOR_ORDER[i];
|
|
19
|
+
if (Array.isArray(availableVendors)) {
|
|
20
|
+
if (availableVendors.indexOf(vendor) !== -1) return vendor;
|
|
21
|
+
} else if (availableVendors[vendor]) {
|
|
22
|
+
return vendor;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return "claude";
|
|
26
|
+
}
|
|
27
|
+
|
|
11
28
|
/**
|
|
12
29
|
* Wrap adapter.createQuery to inject cross-vendor project instructions.
|
|
13
30
|
*
|
|
@@ -371,6 +388,8 @@ async function lazyCreateAdapter(adapters, vendor, opts) {
|
|
|
371
388
|
module.exports = {
|
|
372
389
|
createAdapter: createAdapter,
|
|
373
390
|
createAdapters: createAdapters,
|
|
391
|
+
resolveDefaultVendor: resolveDefaultVendor,
|
|
392
|
+
DEFAULT_VENDOR_ORDER: DEFAULT_VENDOR_ORDER,
|
|
374
393
|
lazyCreateAdapter: lazyCreateAdapter,
|
|
375
394
|
checkAuth: checkAuth,
|
|
376
395
|
checkInstalled: checkInstalled,
|
package/package.json
CHANGED