@sideboard-ai/core 0.1.32 → 0.1.34
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/agents/cursor-runner.cjs +53 -2
- package/dist/agents/cursor-runner.js +6 -3
- package/dist/{agents-HWIPJLC3.js → agents-2OX5XXYP.js} +2 -2
- package/dist/{chunk-OY47OEY2.js → chunk-AL2GL5HJ.js} +175 -28
- package/dist/chunk-BSZX63TV.js +212 -0
- package/dist/{chunk-4YLTMPEO.js → chunk-MULWZLDI.js} +83 -18
- package/dist/index.cjs +371 -44
- package/dist/index.d.cts +29 -3
- package/dist/index.d.ts +29 -3
- package/dist/index.js +3 -3
- package/dist/mcp/run-stdio.cjs +356 -39
- package/dist/mcp/run-stdio.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-3DKGI32Q.js +0 -92
|
@@ -27,6 +27,50 @@ function appDataDir() {
|
|
|
27
27
|
return base;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
// src/agents/error-detail.ts
|
|
31
|
+
function formatUnknownDetail(err) {
|
|
32
|
+
if (err == null) return "";
|
|
33
|
+
if (typeof err === "string") return err.trim();
|
|
34
|
+
if (err instanceof Error) {
|
|
35
|
+
const base = err.message.trim() || err.name;
|
|
36
|
+
const code = "code" in err && typeof err.code === "string" ? err.code.trim() : "";
|
|
37
|
+
return code && !base.includes(code) ? `${base} (${code})` : base;
|
|
38
|
+
}
|
|
39
|
+
if (typeof err === "object") {
|
|
40
|
+
const o = err;
|
|
41
|
+
const nested = o.error != null && typeof o.error === "object" ? formatUnknownDetail(o.error) : "";
|
|
42
|
+
const message = typeof o.message === "string" ? o.message.trim() : typeof o.error === "string" ? o.error.trim() : typeof o.result === "string" ? o.result.trim() : nested;
|
|
43
|
+
const code = typeof o.code === "string" ? o.code.trim() : "";
|
|
44
|
+
if (message) return code && !message.includes(code) ? `${message} (${code})` : message;
|
|
45
|
+
try {
|
|
46
|
+
const json = JSON.stringify(err);
|
|
47
|
+
if (json && json !== "{}" && json !== "null") return json;
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const fallback = String(err);
|
|
52
|
+
return fallback === "[object Object]" ? "" : fallback;
|
|
53
|
+
}
|
|
54
|
+
function extractJsonErrorMessage(obj) {
|
|
55
|
+
const nested = obj.error != null && typeof obj.error === "object" ? obj.error : null;
|
|
56
|
+
const candidates = [
|
|
57
|
+
typeof obj.message === "string" ? obj.message : null,
|
|
58
|
+
typeof obj.error === "string" ? obj.error : null,
|
|
59
|
+
nested && typeof nested.message === "string" ? nested.message : null,
|
|
60
|
+
typeof obj.result === "string" ? obj.result : null,
|
|
61
|
+
typeof obj.detail === "string" ? obj.detail : null
|
|
62
|
+
];
|
|
63
|
+
for (const c of candidates) {
|
|
64
|
+
const t = c?.trim();
|
|
65
|
+
if (t) return t;
|
|
66
|
+
}
|
|
67
|
+
if (Array.isArray(obj.errors)) {
|
|
68
|
+
const parts = obj.errors.map((e) => formatUnknownDetail(e)).map((s) => s.trim()).filter(Boolean);
|
|
69
|
+
if (parts.length) return parts.join("; ");
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
30
74
|
// src/agents/cursor-events.ts
|
|
31
75
|
function usageFromCursor(usage) {
|
|
32
76
|
if (!usage) return null;
|
|
@@ -92,7 +136,12 @@ function cursorSdkMessageToEvents(msg) {
|
|
|
92
136
|
if (usage) return [{ type: "usage", data: usage }];
|
|
93
137
|
}
|
|
94
138
|
if (msg.type === "status" && msg.status === "ERROR") {
|
|
95
|
-
const
|
|
139
|
+
const rawMessage = msg.message;
|
|
140
|
+
const detail = (typeof rawMessage === "string" ? rawMessage.trim() : "") || extractJsonErrorMessage(msg) || formatUnknownDetail(msg.error) || msg.text || "Cursor run entered ERROR status";
|
|
141
|
+
return [{ type: "stderr", data: detail }];
|
|
142
|
+
}
|
|
143
|
+
if (msg.type === "error") {
|
|
144
|
+
const detail = extractJsonErrorMessage(msg) || formatUnknownDetail(msg.error) || msg.text || "Cursor run error";
|
|
96
145
|
return [{ type: "stderr", data: detail }];
|
|
97
146
|
}
|
|
98
147
|
return [];
|
|
@@ -171,12 +220,14 @@ async function main() {
|
|
|
171
220
|
}
|
|
172
221
|
const result = await run.wait();
|
|
173
222
|
if (result.status === "error") {
|
|
223
|
+
const detail = formatUnknownDetail(result.error);
|
|
174
224
|
emit({
|
|
175
225
|
type: "stderr",
|
|
176
|
-
data: `Cursor run failed (${result.id})${
|
|
226
|
+
data: detail ? `Cursor run failed (${result.id}): ${detail}` : `Cursor run failed (${result.id})`
|
|
177
227
|
});
|
|
178
228
|
return 2;
|
|
179
229
|
}
|
|
230
|
+
if (result.status === "cancelled") return 0;
|
|
180
231
|
return 0;
|
|
181
232
|
} finally {
|
|
182
233
|
await agent[Symbol.asyncDispose]();
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
cursorSdkMessageToEvents
|
|
4
|
-
|
|
3
|
+
cursorSdkMessageToEvents,
|
|
4
|
+
formatUnknownDetail
|
|
5
|
+
} from "../chunk-BSZX63TV.js";
|
|
5
6
|
import {
|
|
6
7
|
appDataDir
|
|
7
8
|
} from "../chunk-M37RITA6.js";
|
|
@@ -83,12 +84,14 @@ async function main() {
|
|
|
83
84
|
}
|
|
84
85
|
const result = await run.wait();
|
|
85
86
|
if (result.status === "error") {
|
|
87
|
+
const detail = formatUnknownDetail(result.error);
|
|
86
88
|
emit({
|
|
87
89
|
type: "stderr",
|
|
88
|
-
data: `Cursor run failed (${result.id})${
|
|
90
|
+
data: detail ? `Cursor run failed (${result.id}): ${detail}` : `Cursor run failed (${result.id})`
|
|
89
91
|
});
|
|
90
92
|
return 2;
|
|
91
93
|
}
|
|
94
|
+
if (result.status === "cancelled") return 0;
|
|
92
95
|
return 0;
|
|
93
96
|
} finally {
|
|
94
97
|
await agent[Symbol.asyncDispose]();
|
|
@@ -21,12 +21,12 @@ import {
|
|
|
21
21
|
opencodeAdapter,
|
|
22
22
|
permissionMode,
|
|
23
23
|
resolveCursorModelId
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-MULWZLDI.js";
|
|
25
25
|
import "./chunk-ILQK4P5R.js";
|
|
26
26
|
import {
|
|
27
27
|
cursorSdkMessageToEvents,
|
|
28
28
|
parseCursorRunnerLine
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-BSZX63TV.js";
|
|
30
30
|
import "./chunk-3WF3X46L.js";
|
|
31
31
|
import "./chunk-M37RITA6.js";
|
|
32
32
|
import {
|
|
@@ -25,7 +25,14 @@ import {
|
|
|
25
25
|
allAdapters,
|
|
26
26
|
getAdapter,
|
|
27
27
|
parseBrightsyCliLine
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-MULWZLDI.js";
|
|
29
|
+
import {
|
|
30
|
+
fallbackTurnFailDetail,
|
|
31
|
+
formatTurnExitError,
|
|
32
|
+
looksLikeAgentFailureMessage,
|
|
33
|
+
pushTurnStderr,
|
|
34
|
+
summarizeTurnStderr
|
|
35
|
+
} from "./chunk-BSZX63TV.js";
|
|
29
36
|
import {
|
|
30
37
|
childEnvWithAppSettings,
|
|
31
38
|
getLinearApiKey,
|
|
@@ -846,6 +853,49 @@ function pipeLines(stream, onLine) {
|
|
|
846
853
|
const rl = createInterface2({ input: stream });
|
|
847
854
|
rl.on("line", onLine);
|
|
848
855
|
}
|
|
856
|
+
function killScriptTree(child, ports = []) {
|
|
857
|
+
const pid = child.pid;
|
|
858
|
+
if (pid) {
|
|
859
|
+
try {
|
|
860
|
+
if (process.platform === "win32") {
|
|
861
|
+
void execa2("taskkill", ["/pid", String(pid), "/T", "/F"], { reject: false });
|
|
862
|
+
} else {
|
|
863
|
+
process.kill(-pid, "SIGTERM");
|
|
864
|
+
setTimeout(() => {
|
|
865
|
+
try {
|
|
866
|
+
process.kill(-pid, "SIGKILL");
|
|
867
|
+
} catch {
|
|
868
|
+
}
|
|
869
|
+
}, 2500).unref?.();
|
|
870
|
+
}
|
|
871
|
+
} catch {
|
|
872
|
+
try {
|
|
873
|
+
child.kill("SIGTERM");
|
|
874
|
+
} catch {
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
for (const port of ports) {
|
|
879
|
+
if (!Number.isFinite(port) || port <= 0) continue;
|
|
880
|
+
if (process.platform === "win32") {
|
|
881
|
+
void execa2(
|
|
882
|
+
"powershell",
|
|
883
|
+
[
|
|
884
|
+
"-NoProfile",
|
|
885
|
+
"-Command",
|
|
886
|
+
`Get-NetTCPConnection -LocalPort ${port} -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }`
|
|
887
|
+
],
|
|
888
|
+
{ reject: false }
|
|
889
|
+
);
|
|
890
|
+
} else {
|
|
891
|
+
void execa2(
|
|
892
|
+
"zsh",
|
|
893
|
+
["-lc", `pids=$(lsof -tiTCP:${port} -sTCP:LISTEN 2>/dev/null); [ -n "$pids" ] && kill -TERM $pids 2>/dev/null; true`],
|
|
894
|
+
{ reject: false }
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
849
899
|
async function spawnWorkspaceScript(command, opts) {
|
|
850
900
|
const loginEnv = await captureLoginEnv();
|
|
851
901
|
const env = buildWorkspaceScriptEnv(
|
|
@@ -862,18 +912,17 @@ async function spawnWorkspaceScript(command, opts) {
|
|
|
862
912
|
const child = execa2(shell, ["-lc", command], {
|
|
863
913
|
cwd: opts.worktreePath,
|
|
864
914
|
reject: false,
|
|
865
|
-
env
|
|
915
|
+
env,
|
|
916
|
+
// Own process group so Stop can tear down the whole tree (not just the shell).
|
|
917
|
+
// There is no settings.toml `stop=` / teardown hook for run scripts.
|
|
918
|
+
...process.platform === "win32" ? {} : { detached: true }
|
|
866
919
|
});
|
|
867
920
|
pipeLines(child.stdout, opts.onLine);
|
|
868
921
|
pipeLines(child.stderr, opts.onLine);
|
|
922
|
+
const ports = opts.ports ?? [];
|
|
869
923
|
return {
|
|
870
924
|
pid: child.pid,
|
|
871
|
-
kill: () =>
|
|
872
|
-
try {
|
|
873
|
-
child.kill("SIGTERM");
|
|
874
|
-
} catch {
|
|
875
|
-
}
|
|
876
|
-
},
|
|
925
|
+
kill: () => killScriptTree(child, ports),
|
|
877
926
|
done: child.then((r) => r.exitCode ?? null),
|
|
878
927
|
child
|
|
879
928
|
};
|
|
@@ -2557,7 +2606,7 @@ async function createThread(input, onSetupLine) {
|
|
|
2557
2606
|
return readThread(thread.id) ?? thread;
|
|
2558
2607
|
}
|
|
2559
2608
|
async function listLinearIssues(agent, repoPath) {
|
|
2560
|
-
const { getAdapter: getAdapter2 } = await import("./agents-
|
|
2609
|
+
const { getAdapter: getAdapter2 } = await import("./agents-2OX5XXYP.js");
|
|
2561
2610
|
await requireAgent(agent, { requireLinear: true });
|
|
2562
2611
|
const adapter = getAdapter2(agent);
|
|
2563
2612
|
if (!adapter.listLinearIssues) {
|
|
@@ -3252,6 +3301,11 @@ var Orchestrator = class {
|
|
|
3252
3301
|
* the killed turn's handle.done resolves.
|
|
3253
3302
|
*/
|
|
3254
3303
|
stoppedTurns = /* @__PURE__ */ new Set();
|
|
3304
|
+
/**
|
|
3305
|
+
* Pause drainQueue after the in-flight turn unwinds (Stop with a preserved
|
|
3306
|
+
* queue). Cleared when the user sends or promotes a queued message again.
|
|
3307
|
+
*/
|
|
3308
|
+
haltDrain = /* @__PURE__ */ new Set();
|
|
3255
3309
|
/** WIP snapshot SHA at the start of the latest agent turn (per thread). */
|
|
3256
3310
|
turnBaselines = /* @__PURE__ */ new Map();
|
|
3257
3311
|
maxConcurrent;
|
|
@@ -3319,7 +3373,7 @@ var Orchestrator = class {
|
|
|
3319
3373
|
} catch {
|
|
3320
3374
|
}
|
|
3321
3375
|
for (const thread of listThreads()) {
|
|
3322
|
-
if (thread.queue.length > 0) {
|
|
3376
|
+
if (thread.queue.length > 0 && thread.status !== "stopped") {
|
|
3323
3377
|
void this.drainQueue(thread.id);
|
|
3324
3378
|
}
|
|
3325
3379
|
}
|
|
@@ -3380,6 +3434,7 @@ var Orchestrator = class {
|
|
|
3380
3434
|
return withThreadLock(thread.id, async () => {
|
|
3381
3435
|
const current = this.requireThread(thread.id);
|
|
3382
3436
|
const queue = [...current.queue, prompt];
|
|
3437
|
+
this.haltDrain.delete(thread.id);
|
|
3383
3438
|
updateThread(thread.id, { queue, status: "queued" });
|
|
3384
3439
|
this.emit({ type: "queue_changed", threadId: thread.id, queue });
|
|
3385
3440
|
this.emit({ type: "status_changed", threadId: thread.id, status: "queued" });
|
|
@@ -3394,11 +3449,75 @@ var Orchestrator = class {
|
|
|
3394
3449
|
}
|
|
3395
3450
|
return results;
|
|
3396
3451
|
}
|
|
3452
|
+
/** Edit the text of a not-yet-started queued message. */
|
|
3453
|
+
async editQueuedMessage(threadRef, index, text) {
|
|
3454
|
+
const thread = this.requireThread(threadRef);
|
|
3455
|
+
return withThreadLock(thread.id, async () => {
|
|
3456
|
+
const current = this.requireThread(thread.id);
|
|
3457
|
+
const trimmed = text.trim();
|
|
3458
|
+
if (!trimmed || index < 0 || index >= current.queue.length) {
|
|
3459
|
+
return current;
|
|
3460
|
+
}
|
|
3461
|
+
const queue = current.queue.map((p, i) => i === index ? trimmed : p);
|
|
3462
|
+
updateThread(thread.id, { queue });
|
|
3463
|
+
this.emit({ type: "queue_changed", threadId: thread.id, queue });
|
|
3464
|
+
return this.requireThread(thread.id);
|
|
3465
|
+
});
|
|
3466
|
+
}
|
|
3467
|
+
/** Remove a not-yet-started queued message. */
|
|
3468
|
+
async removeQueuedMessage(threadRef, index) {
|
|
3469
|
+
const thread = this.requireThread(threadRef);
|
|
3470
|
+
return withThreadLock(thread.id, async () => {
|
|
3471
|
+
const current = this.requireThread(thread.id);
|
|
3472
|
+
if (index < 0 || index >= current.queue.length) return current;
|
|
3473
|
+
const queue = current.queue.filter((_, i) => i !== index);
|
|
3474
|
+
const stillQueued = queue.length > 0;
|
|
3475
|
+
const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id);
|
|
3476
|
+
updateThread(thread.id, {
|
|
3477
|
+
queue,
|
|
3478
|
+
status: !stillQueued && !inFlight && current.status === "queued" ? "idle" : current.status
|
|
3479
|
+
});
|
|
3480
|
+
this.emit({ type: "queue_changed", threadId: thread.id, queue });
|
|
3481
|
+
const next = this.requireThread(thread.id);
|
|
3482
|
+
this.emit({ type: "status_changed", threadId: thread.id, status: next.status });
|
|
3483
|
+
return next;
|
|
3484
|
+
});
|
|
3485
|
+
}
|
|
3486
|
+
/**
|
|
3487
|
+
* Promote a queued message to run next, interrupting the in-flight turn (if any).
|
|
3488
|
+
* The current turn is stopped without clearing the rest of the queue — drainQueue
|
|
3489
|
+
* picks the promoted message up as soon as the interrupted turn unwinds.
|
|
3490
|
+
*/
|
|
3491
|
+
async sendQueuedMessageNow(threadRef, index) {
|
|
3492
|
+
const thread = this.requireThread(threadRef);
|
|
3493
|
+
const promoted = await withThreadLock(thread.id, async () => {
|
|
3494
|
+
const current = this.requireThread(thread.id);
|
|
3495
|
+
if (index < 0 || index >= current.queue.length) return false;
|
|
3496
|
+
const item = current.queue[index];
|
|
3497
|
+
const rest = current.queue.filter((_, i) => i !== index);
|
|
3498
|
+
const queue = [item, ...rest];
|
|
3499
|
+
this.haltDrain.delete(thread.id);
|
|
3500
|
+
updateThread(thread.id, { queue });
|
|
3501
|
+
this.emit({ type: "queue_changed", threadId: thread.id, queue });
|
|
3502
|
+
return true;
|
|
3503
|
+
});
|
|
3504
|
+
if (!promoted) return this.requireThread(thread.id);
|
|
3505
|
+
const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id);
|
|
3506
|
+
if (inFlight) {
|
|
3507
|
+
this.stop(thread.id, { clearQueue: false, continueQueue: true });
|
|
3508
|
+
} else {
|
|
3509
|
+
void this.drainQueue(thread.id);
|
|
3510
|
+
}
|
|
3511
|
+
return this.requireThread(thread.id);
|
|
3512
|
+
}
|
|
3397
3513
|
async drainQueue(threadId) {
|
|
3398
3514
|
if (this.draining.has(threadId)) return;
|
|
3399
3515
|
this.draining.add(threadId);
|
|
3400
3516
|
try {
|
|
3401
3517
|
while (true) {
|
|
3518
|
+
if (this.haltDrain.has(threadId)) {
|
|
3519
|
+
break;
|
|
3520
|
+
}
|
|
3402
3521
|
const thread = readThread(threadId);
|
|
3403
3522
|
if (!thread || thread.queue.length === 0) {
|
|
3404
3523
|
if (thread && thread.status === "queued") {
|
|
@@ -3411,7 +3530,7 @@ var Orchestrator = class {
|
|
|
3411
3530
|
await new Promise((r) => setTimeout(r, 250));
|
|
3412
3531
|
continue;
|
|
3413
3532
|
}
|
|
3414
|
-
if (this.activeTurns.has(threadId)) {
|
|
3533
|
+
if (this.activeTurns.has(threadId) || this.startingTurns.has(threadId)) {
|
|
3415
3534
|
await new Promise((r) => setTimeout(r, 100));
|
|
3416
3535
|
continue;
|
|
3417
3536
|
}
|
|
@@ -3546,7 +3665,7 @@ var Orchestrator = class {
|
|
|
3546
3665
|
...fresh.agent === "claude" && fresh.sessionId ? [] : [instructions, seed]
|
|
3547
3666
|
].filter(Boolean).join("\n\n---\n\n");
|
|
3548
3667
|
try {
|
|
3549
|
-
|
|
3668
|
+
const stderrTail = [];
|
|
3550
3669
|
const handle = await spawnAgentTurn(
|
|
3551
3670
|
fresh,
|
|
3552
3671
|
{ cachedPrefix, prompt: agentPrompt },
|
|
@@ -3555,8 +3674,8 @@ var Orchestrator = class {
|
|
|
3555
3674
|
if (event.type === "session_id") {
|
|
3556
3675
|
updateThread(threadId, { sessionId: event.data });
|
|
3557
3676
|
}
|
|
3558
|
-
if (event.type === "stderr" && typeof event.data === "string"
|
|
3559
|
-
|
|
3677
|
+
if (event.type === "stderr" && typeof event.data === "string") {
|
|
3678
|
+
pushTurnStderr(stderrTail, event.data);
|
|
3560
3679
|
}
|
|
3561
3680
|
}
|
|
3562
3681
|
);
|
|
@@ -3581,10 +3700,12 @@ var Orchestrator = class {
|
|
|
3581
3700
|
if (result.sessionId) {
|
|
3582
3701
|
updateThread(threadId, { sessionId: result.sessionId });
|
|
3583
3702
|
}
|
|
3584
|
-
|
|
3703
|
+
const assistantText = result.assistantText.trim();
|
|
3704
|
+
const failureOnlyMessage = result.exitCode !== 0 && looksLikeAgentFailureMessage(assistantText) && !result.parts.some((p) => p.type === "tool" || p.type === "thinking");
|
|
3705
|
+
if (!failureOnlyMessage && (assistantText || result.parts.length > 0)) {
|
|
3585
3706
|
appendMessage(threadId, {
|
|
3586
3707
|
role: "agent",
|
|
3587
|
-
text:
|
|
3708
|
+
text: assistantText,
|
|
3588
3709
|
parts: result.parts.length > 0 ? result.parts : void 0,
|
|
3589
3710
|
durationMs: Math.max(0, Date.now() - turnStartedAt),
|
|
3590
3711
|
usage: result.usage ?? void 0,
|
|
@@ -3603,7 +3724,9 @@ var Orchestrator = class {
|
|
|
3603
3724
|
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
3604
3725
|
this.emit({ type: "turn_finished", threadId, exitCode: result.exitCode });
|
|
3605
3726
|
} else {
|
|
3606
|
-
const
|
|
3727
|
+
const lastStderr = summarizeTurnStderr(stderrTail);
|
|
3728
|
+
const detail = lastStderr || (result.exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
|
|
3729
|
+
const failDetail = formatTurnExitError(result.exitCode, detail);
|
|
3607
3730
|
setStatus(
|
|
3608
3731
|
threadId,
|
|
3609
3732
|
result.exitCode === 0 ? "idle" : "error",
|
|
@@ -3639,20 +3762,30 @@ var Orchestrator = class {
|
|
|
3639
3762
|
}
|
|
3640
3763
|
/**
|
|
3641
3764
|
* Stop an in-flight agent turn.
|
|
3642
|
-
*
|
|
3643
|
-
*
|
|
3644
|
-
*
|
|
3765
|
+
*
|
|
3766
|
+
* - Default `clearQueue: true` (force-stop): empties queued prompts so nothing
|
|
3767
|
+
* resumes. Used by MCP force-stop, archive, and cloud-connect.
|
|
3768
|
+
* - Desktop Stop uses `{ clearQueue: false }` so follow-ups stay editable.
|
|
3769
|
+
* - `continueQueue: true` (Send now): keep the queue and let drainQueue resume
|
|
3770
|
+
* after the interrupted turn unwinds. Without it, drain pauses until send /
|
|
3771
|
+
* promote.
|
|
3645
3772
|
*/
|
|
3646
3773
|
stop(threadRef, opts) {
|
|
3647
3774
|
const clearQueue = opts?.clearQueue !== false;
|
|
3775
|
+
const continueQueue = opts?.continueQueue === true;
|
|
3648
3776
|
const thread = this.requireThread(threadRef);
|
|
3649
3777
|
const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id);
|
|
3650
3778
|
if (inFlight) {
|
|
3651
3779
|
this.stoppedTurns.add(thread.id);
|
|
3652
3780
|
}
|
|
3653
3781
|
if (clearQueue && thread.queue.length > 0) {
|
|
3782
|
+
this.haltDrain.delete(thread.id);
|
|
3654
3783
|
updateThread(thread.id, { queue: [] });
|
|
3655
3784
|
this.emit({ type: "queue_changed", threadId: thread.id, queue: [] });
|
|
3785
|
+
} else if (!clearQueue && !continueQueue) {
|
|
3786
|
+
this.haltDrain.add(thread.id);
|
|
3787
|
+
} else if (continueQueue) {
|
|
3788
|
+
this.haltDrain.delete(thread.id);
|
|
3656
3789
|
}
|
|
3657
3790
|
const handle = this.activeTurns.get(thread.id);
|
|
3658
3791
|
if (handle) handle.kill();
|
|
@@ -4247,9 +4380,11 @@ function getOrchestrator() {
|
|
|
4247
4380
|
}
|
|
4248
4381
|
async function startOrchestration(opts) {
|
|
4249
4382
|
const repoPath = opts.repoPath?.trim();
|
|
4383
|
+
const goal = opts.goal.trim();
|
|
4384
|
+
const orch = getOrchestrator();
|
|
4250
4385
|
if (!repoPath || isGlobalRepoPath(repoPath)) {
|
|
4251
|
-
|
|
4252
|
-
sourceRef:
|
|
4386
|
+
const thread2 = createGlobalChat({
|
|
4387
|
+
sourceRef: goal,
|
|
4253
4388
|
agent: opts.agent,
|
|
4254
4389
|
autonomy: opts.autonomy,
|
|
4255
4390
|
model: opts.model,
|
|
@@ -4257,9 +4392,13 @@ async function startOrchestration(opts) {
|
|
|
4257
4392
|
planMode: opts.planMode,
|
|
4258
4393
|
attachments: opts.attachments
|
|
4259
4394
|
});
|
|
4395
|
+
if (goal) {
|
|
4396
|
+
return orch.send(thread2.id, goal);
|
|
4397
|
+
}
|
|
4398
|
+
return thread2;
|
|
4260
4399
|
}
|
|
4261
4400
|
const { titleFromPrompt } = await import("./title-4A2ATYNY.js");
|
|
4262
|
-
const title = titleFromPrompt(
|
|
4401
|
+
const title = titleFromPrompt(goal) || "Orchestration";
|
|
4263
4402
|
const createOpts = {
|
|
4264
4403
|
agent: opts.agent,
|
|
4265
4404
|
repoPath,
|
|
@@ -4286,10 +4425,14 @@ async function startOrchestration(opts) {
|
|
|
4286
4425
|
});
|
|
4287
4426
|
});
|
|
4288
4427
|
const { updateThread: upd } = await import("./thread-store-UNPZNIFW.js");
|
|
4289
|
-
|
|
4428
|
+
const updated = upd(thread.id, {
|
|
4290
4429
|
sourceType: "orchestration",
|
|
4291
|
-
sourceRef:
|
|
4430
|
+
sourceRef: goal
|
|
4292
4431
|
});
|
|
4432
|
+
if (goal) {
|
|
4433
|
+
return orch.send(updated.id, goal);
|
|
4434
|
+
}
|
|
4435
|
+
return updated;
|
|
4293
4436
|
}
|
|
4294
4437
|
|
|
4295
4438
|
// src/mcp/server.ts
|
|
@@ -4379,11 +4522,15 @@ async function startMcpServer() {
|
|
|
4379
4522
|
);
|
|
4380
4523
|
server.tool(
|
|
4381
4524
|
"present_artifact",
|
|
4382
|
-
"Show an HTML, SVG, or
|
|
4525
|
+
"Show an HTML, SVG, markdown, or React document in Sideboard\u2019s Claude-style side column (desktop). Use instead of claude.ai\u2019s artifact tool \u2014 pass the full document content. Prefer type=html for interactive pages. For type=react, pass a single component module that `export default`s a component (JSX/TSX ok) \u2014 Sideboard bootstraps React/ReactDOM/Babel and renders it; only `react`/`react-dom` imports are available, no other npm packages.",
|
|
4383
4526
|
{
|
|
4384
4527
|
title: z.string().describe("Short title shown in the artifact pane header"),
|
|
4385
|
-
type: z.enum(["html", "svg", "markdown"]).describe(
|
|
4386
|
-
|
|
4528
|
+
type: z.enum(["html", "svg", "markdown", "react"]).describe(
|
|
4529
|
+
"Artifact kind \u2014 html opens an iframe preview; react transpiles and renders a default-exported component in a sandboxed iframe"
|
|
4530
|
+
),
|
|
4531
|
+
content: z.string().describe(
|
|
4532
|
+
"Full document body (complete HTML page, SVG markup, markdown, or a React component module with a default export)"
|
|
4533
|
+
),
|
|
4387
4534
|
artifact_id: z.string().optional().describe("Stable id when updating the same artifact across turns")
|
|
4388
4535
|
},
|
|
4389
4536
|
async ({ title, type, content, artifact_id }) => {
|