@sideboard-ai/core 0.1.44 → 0.1.46
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 +11 -4
- package/dist/agents/cursor-runner.js +12 -5
- package/dist/{agents-PP3URTSF.js → agents-6L6VENDT.js} +25 -5
- package/dist/{app-settings-ZKVZHJPQ.js → app-settings-LZP632KI.js} +5 -1
- package/dist/{chunk-JM2TVGNW.js → chunk-6NAPN2N5.js} +4 -1
- package/dist/{chunk-V3S4NF5F.js → chunk-7DTJFP4D.js} +406 -30
- package/dist/{chunk-7PCTK4WO.js → chunk-FV6FN6V5.js} +1 -1
- package/dist/chunk-GI7E5733.js +30 -0
- package/dist/{chunk-PU27NUO4.js → chunk-J5JTEJ5O.js} +7 -2
- package/dist/{chunk-ENSD62HW.js → chunk-O6W3P7V3.js} +3 -1
- package/dist/{chunk-I3PKMLFW.js → chunk-SPCU3MFY.js} +8 -4
- package/dist/{chunk-TXFJEXFB.js → chunk-SZAKJ4TR.js} +2 -2
- package/dist/{chunk-FSIK442J.js → chunk-T5QQVXK3.js} +24 -0
- package/dist/{chunk-XX26NCB6.js → chunk-TQK2OYPU.js} +246 -6
- package/dist/{coordinator-prompt-I5YNS27B.js → coordinator-prompt-2XFYG3C5.js} +3 -3
- package/dist/{global-workspace-FU6UIPDY.js → global-workspace-XFOXEQCP.js} +5 -4
- package/dist/index.cjs +747 -18
- package/dist/index.d.cts +96 -2
- package/dist/index.d.ts +96 -2
- package/dist/index.js +33 -9
- package/dist/mcp/run-stdio.cjs +737 -28
- package/dist/mcp/run-stdio.js +10 -9
- package/dist/{thread-store-EHROA3VZ.js → thread-store-OV2X6PYO.js} +1 -1
- package/dist/{workspaces-RSEDTBGJ.js → workspaces-SRL2HZTB.js} +6 -5
- package/dist/{worktree-N4PRV4V3.js → worktree-TEDAJ57S.js} +2 -2
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
ensureWorkspace,
|
|
4
4
|
removeWorkspace,
|
|
5
5
|
syncWorkspacesFromThreads
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SZAKJ4TR.js";
|
|
7
7
|
import {
|
|
8
8
|
GLOBAL_WORKSPACE_ID,
|
|
9
9
|
createGlobalChat,
|
|
@@ -13,34 +13,44 @@ import {
|
|
|
13
13
|
isGlobalThread,
|
|
14
14
|
isOrchestratorThread,
|
|
15
15
|
orchestratorSessionPoisonedByBuiltins
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-SPCU3MFY.js";
|
|
17
17
|
import {
|
|
18
18
|
coordinatorSystemPrompt,
|
|
19
19
|
coordinatorTurnReminder,
|
|
20
20
|
enrichWorkspacesWithGithub,
|
|
21
21
|
ensureGlobalCoordinatorCwd
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-6NAPN2N5.js";
|
|
23
23
|
import {
|
|
24
24
|
PLAN_MODE_INSTRUCTION,
|
|
25
25
|
allAdapters,
|
|
26
26
|
getAdapter,
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
isSessionQuotaLimit,
|
|
28
|
+
listModelsForAgent,
|
|
29
|
+
parseBrightsyCliLine,
|
|
30
|
+
parseSessionQuotaResetAt,
|
|
31
|
+
resolveQuotaFallbackAgent
|
|
32
|
+
} from "./chunk-TQK2OYPU.js";
|
|
33
|
+
import {
|
|
34
|
+
assertOrchestratorCapableAgent
|
|
35
|
+
} from "./chunk-GI7E5733.js";
|
|
29
36
|
import {
|
|
30
37
|
fallbackTurnFailDetail,
|
|
31
38
|
formatTurnExitError,
|
|
39
|
+
humanizeAgentFailDetail,
|
|
32
40
|
looksLikeAgentFailureMessage,
|
|
33
41
|
pushTurnStderr,
|
|
34
42
|
summarizeTurnStderr
|
|
35
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-J5JTEJ5O.js";
|
|
36
44
|
import {
|
|
37
45
|
childEnvWithAppSettings,
|
|
38
46
|
getLinearApiKey,
|
|
39
47
|
isLinearConnected,
|
|
40
48
|
loadAppSettings,
|
|
49
|
+
orchestrationQuotaFallbackAgent,
|
|
50
|
+
orchestrationQuotaOnLimit,
|
|
41
51
|
resolveEffectiveIssueSource,
|
|
42
52
|
updateAdvancedSettings
|
|
43
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-T5QQVXK3.js";
|
|
44
54
|
import {
|
|
45
55
|
allocateTeamName,
|
|
46
56
|
allocateTeamSlug,
|
|
@@ -71,7 +81,7 @@ import {
|
|
|
71
81
|
takenSlugsFromThread,
|
|
72
82
|
threadDisplayLabel,
|
|
73
83
|
worktreeNameFromPath
|
|
74
|
-
} from "./chunk-
|
|
84
|
+
} from "./chunk-FV6FN6V5.js";
|
|
75
85
|
import {
|
|
76
86
|
appendMessage,
|
|
77
87
|
createEmptyThread,
|
|
@@ -83,7 +93,7 @@ import {
|
|
|
83
93
|
updateThread,
|
|
84
94
|
withThreadLock,
|
|
85
95
|
writeThread
|
|
86
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-O6W3P7V3.js";
|
|
87
97
|
import {
|
|
88
98
|
loadRepoSettings,
|
|
89
99
|
loadWorkspaceSettings,
|
|
@@ -459,11 +469,14 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
459
469
|
`Cannot spawn ${thread.agent}: thread ${thread.id} has no worktreePath`
|
|
460
470
|
);
|
|
461
471
|
}
|
|
462
|
-
const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-
|
|
472
|
+
const { isGlobalThread: isGlobalThread2 } = await import("./global-workspace-XFOXEQCP.js");
|
|
463
473
|
if (isGlobalThread2(thread)) {
|
|
464
|
-
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-
|
|
474
|
+
const { ensureGlobalCoordinatorCwd: ensureGlobalCoordinatorCwd2 } = await import("./coordinator-prompt-2XFYG3C5.js");
|
|
465
475
|
ensureGlobalCoordinatorCwd2();
|
|
466
476
|
}
|
|
477
|
+
if (isOrchestratorThread(thread)) {
|
|
478
|
+
assertOrchestratorCapableAgent(thread.agent);
|
|
479
|
+
}
|
|
467
480
|
const adapter = getAdapter(thread.agent);
|
|
468
481
|
const cmd = await adapter.buildTurn(thread, input);
|
|
469
482
|
if (cmd.cwd !== thread.worktreePath) {
|
|
@@ -2640,7 +2653,7 @@ async function createThread(input, onSetupLine) {
|
|
|
2640
2653
|
return readThread(thread.id) ?? thread;
|
|
2641
2654
|
}
|
|
2642
2655
|
async function listLinearIssues(agent, repoPath) {
|
|
2643
|
-
const { getAdapter: getAdapter2 } = await import("./agents-
|
|
2656
|
+
const { getAdapter: getAdapter2 } = await import("./agents-6L6VENDT.js");
|
|
2644
2657
|
await requireAgent(agent, { requireLinear: true });
|
|
2645
2658
|
const adapter = getAdapter2(agent);
|
|
2646
2659
|
if (!adapter.listLinearIssues) {
|
|
@@ -2712,6 +2725,10 @@ function createChatTab(input) {
|
|
|
2712
2725
|
const binding = worktreeBindingFrom(from);
|
|
2713
2726
|
const explicitTitle = input.title?.trim();
|
|
2714
2727
|
const title = explicitTitle || allocateTeamName(takenTeamSlugsForChatTab(binding.worktreePath)).name;
|
|
2728
|
+
const nextAgent = input.agent ?? from.agent;
|
|
2729
|
+
if (isOrchestratorThread(from) || binding.sourceType === "orchestration") {
|
|
2730
|
+
assertOrchestratorCapableAgent(nextAgent);
|
|
2731
|
+
}
|
|
2715
2732
|
const thread = createEmptyThread({
|
|
2716
2733
|
title,
|
|
2717
2734
|
// Chat-tab nicknames (soccer team or explicit) must stick. Post-turn
|
|
@@ -2719,7 +2736,7 @@ function createChatTab(input) {
|
|
|
2719
2736
|
// shared worktree folder name (e.g. fork "Arsenal" → "Monaco").
|
|
2720
2737
|
userSetTitle: true,
|
|
2721
2738
|
...binding,
|
|
2722
|
-
agent:
|
|
2739
|
+
agent: nextAgent,
|
|
2723
2740
|
model: input.model !== void 0 ? input.model : input.agent && input.agent !== from.agent ? null : from.model,
|
|
2724
2741
|
effort: input.effort !== void 0 ? input.effort : from.effort,
|
|
2725
2742
|
fast: input.fast !== void 0 ? Boolean(input.fast) : from.fast,
|
|
@@ -2735,12 +2752,19 @@ function forkChatTab(input) {
|
|
|
2735
2752
|
const from = requireThread(input.threadId);
|
|
2736
2753
|
const slice = forkMessageSlice(from, input.throughIndex);
|
|
2737
2754
|
const attachment = buildForkTranscriptAttachment(from.title || "Chat", slice);
|
|
2738
|
-
|
|
2755
|
+
const tab = createChatTab({
|
|
2739
2756
|
fromThreadId: input.threadId,
|
|
2740
2757
|
agent: input.agent ?? from.agent,
|
|
2758
|
+
model: input.model,
|
|
2741
2759
|
title: input.title?.trim() || void 0,
|
|
2742
2760
|
attachments: [attachment]
|
|
2743
2761
|
});
|
|
2762
|
+
if (isOrchestratorThread(from) && tab.parentThreadId !== from.id) {
|
|
2763
|
+
const next = { ...tab, parentThreadId: from.id };
|
|
2764
|
+
writeThread(next);
|
|
2765
|
+
return next;
|
|
2766
|
+
}
|
|
2767
|
+
return tab;
|
|
2744
2768
|
}
|
|
2745
2769
|
|
|
2746
2770
|
// src/threads/fork-worktree.ts
|
|
@@ -2751,16 +2775,28 @@ function requireThread2(idOrRef) {
|
|
|
2751
2775
|
}
|
|
2752
2776
|
async function forkThreadWorktree(input, onSetupLine) {
|
|
2753
2777
|
const from = requireThread2(input.threadId);
|
|
2778
|
+
if (isOrchestratorThread(from)) {
|
|
2779
|
+
throw new Error(
|
|
2780
|
+
"fork_worktree targets a worktree agent thread (not the orchestrator). Pass a child/worktree thread ref."
|
|
2781
|
+
);
|
|
2782
|
+
}
|
|
2783
|
+
if (!from.branchName?.trim() || !from.repoPath?.trim()) {
|
|
2784
|
+
throw new Error(
|
|
2785
|
+
`Cannot fork worktree: thread ${from.id} has no branch/repo (need a real worktree chat).`
|
|
2786
|
+
);
|
|
2787
|
+
}
|
|
2754
2788
|
const slice = forkMessageSlice(from, input.throughIndex);
|
|
2755
2789
|
const attachment = buildForkTranscriptAttachment(from.title || "Chat", slice);
|
|
2790
|
+
const nextAgent = input.agent ?? from.agent;
|
|
2791
|
+
const nextModel = input.model !== void 0 ? input.model : input.agent && input.agent !== from.agent ? null : from.model;
|
|
2756
2792
|
const thread = await createThread(
|
|
2757
2793
|
{
|
|
2758
2794
|
sourceType: "branch",
|
|
2759
2795
|
sourceRef: from.branchName,
|
|
2760
2796
|
repoPath: from.repoPath,
|
|
2761
|
-
agent:
|
|
2797
|
+
agent: nextAgent,
|
|
2762
2798
|
autonomy: from.autonomy,
|
|
2763
|
-
model:
|
|
2799
|
+
model: nextModel,
|
|
2764
2800
|
effort: from.effort,
|
|
2765
2801
|
fast: from.fast,
|
|
2766
2802
|
planMode: from.planMode,
|
|
@@ -2862,7 +2898,7 @@ async function adoptThread(input) {
|
|
|
2862
2898
|
messages: input.messages ?? []
|
|
2863
2899
|
});
|
|
2864
2900
|
writeThread(thread);
|
|
2865
|
-
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-
|
|
2901
|
+
const { ensureWorkspace: ensureWorkspace2 } = await import("./workspaces-SRL2HZTB.js");
|
|
2866
2902
|
await ensureWorkspace2(repoPath);
|
|
2867
2903
|
return thread;
|
|
2868
2904
|
}
|
|
@@ -3343,6 +3379,111 @@ async function requestReview(threadRef, send) {
|
|
|
3343
3379
|
import { EventEmitter } from "events";
|
|
3344
3380
|
import { existsSync as existsSync12 } from "fs";
|
|
3345
3381
|
|
|
3382
|
+
// src/orchestrator/quota-failover.ts
|
|
3383
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
3384
|
+
function planOrchestrationQuotaFailover(thread, limitText, opts) {
|
|
3385
|
+
if (!isOrchestratorThread(thread)) return null;
|
|
3386
|
+
if (!isSessionQuotaLimit(limitText)) return null;
|
|
3387
|
+
const onLimit = opts?.onLimit ?? orchestrationQuotaOnLimit();
|
|
3388
|
+
const resumeAt = parseSessionQuotaResetAt(limitText, opts?.now);
|
|
3389
|
+
if (thread.quotaContinuedFromId) {
|
|
3390
|
+
if (resumeAt) {
|
|
3391
|
+
return {
|
|
3392
|
+
action: "wait_reset",
|
|
3393
|
+
reason: "Already continued once; waiting for quota reset instead.",
|
|
3394
|
+
limitText,
|
|
3395
|
+
resumeAt
|
|
3396
|
+
};
|
|
3397
|
+
}
|
|
3398
|
+
return {
|
|
3399
|
+
action: "none",
|
|
3400
|
+
reason: "Already continued once; no parseable reset time.",
|
|
3401
|
+
limitText
|
|
3402
|
+
};
|
|
3403
|
+
}
|
|
3404
|
+
if (onLimit === "wait_reset") {
|
|
3405
|
+
if (!resumeAt) {
|
|
3406
|
+
return {
|
|
3407
|
+
action: "none",
|
|
3408
|
+
reason: "wait_reset configured but reset time could not be parsed.",
|
|
3409
|
+
limitText
|
|
3410
|
+
};
|
|
3411
|
+
}
|
|
3412
|
+
return {
|
|
3413
|
+
action: "wait_reset",
|
|
3414
|
+
reason: "Settings: wait for quota reset.",
|
|
3415
|
+
limitText,
|
|
3416
|
+
resumeAt
|
|
3417
|
+
};
|
|
3418
|
+
}
|
|
3419
|
+
const preferred = opts?.fallbackAgent ?? orchestrationQuotaFallbackAgent();
|
|
3420
|
+
const fallbackAgent = resolveQuotaFallbackAgent(thread.agent, preferred);
|
|
3421
|
+
return {
|
|
3422
|
+
action: "switch_agent",
|
|
3423
|
+
reason: `Continue on ${fallbackAgent} (Auto) after ${thread.agent} session limit.`,
|
|
3424
|
+
limitText,
|
|
3425
|
+
fallbackAgent
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
3428
|
+
function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
|
|
3429
|
+
const children = listThreads({ includeArchived: false }).filter((t) => t.parentThreadId === from.id && t.status !== "archived").slice(0, 40).map(
|
|
3430
|
+
(t) => `- ${t.title} \xB7 ${t.status} \xB7 ${t.agent} \xB7 sideboard://thread/${t.id}`
|
|
3431
|
+
);
|
|
3432
|
+
const recent = from.messages.slice(-8).map((m) => {
|
|
3433
|
+
const role = m.role === "user" ? "User" : m.role === "agent" ? "Agent" : "Summary";
|
|
3434
|
+
const text = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
|
|
3435
|
+
return text ? `- ${role}: ${text}` : null;
|
|
3436
|
+
}).filter(Boolean);
|
|
3437
|
+
const body = [
|
|
3438
|
+
`# Orchestration handoff`,
|
|
3439
|
+
"",
|
|
3440
|
+
`Previous chat: ${from.title} (\`${from.id}\`) on **${from.agent}** hit a session/usage limit.`,
|
|
3441
|
+
`Limit: ${limitText.trim()}`,
|
|
3442
|
+
`Continuing on **${fallbackAgent}** with Auto model.`,
|
|
3443
|
+
"",
|
|
3444
|
+
`## Goal`,
|
|
3445
|
+
from.sourceRef?.trim() || "(none)",
|
|
3446
|
+
"",
|
|
3447
|
+
`## Child threads`,
|
|
3448
|
+
children.length ? children.join("\n") : "(none listed \u2014 call list_threads)",
|
|
3449
|
+
"",
|
|
3450
|
+
`## Recent turns (truncated)`,
|
|
3451
|
+
recent.length ? recent.join("\n") : "(none)",
|
|
3452
|
+
"",
|
|
3453
|
+
`## Instructions`,
|
|
3454
|
+
`- Continue fleet orchestration from this handoff.`,
|
|
3455
|
+
`- Prefer Sideboard MCP (list_threads, get_thread, send_to_thread, \u2026) for live status.`,
|
|
3456
|
+
`- Leave model Auto unless there is a specific reason to pin one.`,
|
|
3457
|
+
`- Do not wait on the limited ${from.agent} account; keep going on ${fallbackAgent}.`
|
|
3458
|
+
].join("\n");
|
|
3459
|
+
return {
|
|
3460
|
+
id: randomUUID4(),
|
|
3461
|
+
name: "Orchestration quota handoff.md",
|
|
3462
|
+
kind: "transcript",
|
|
3463
|
+
content: body
|
|
3464
|
+
};
|
|
3465
|
+
}
|
|
3466
|
+
var QUOTA_CONTINUE_PROMPT = (fromAgent, fallback) => [
|
|
3467
|
+
`${fromAgent} hit a session/usage limit. Continue this orchestration on ${fallback} using the attached handoff.`,
|
|
3468
|
+
"Call list_threads for live fleet status, then proceed with the goal. Leave model Auto unless needed."
|
|
3469
|
+
].join(" ");
|
|
3470
|
+
var QUOTA_RESUME_PROMPT = "Session/usage limit window should have reset. Continue the orchestration from where you left off. Use list_threads for fleet status.";
|
|
3471
|
+
function createQuotaFailoverChat(from, fallbackAgent, limitText) {
|
|
3472
|
+
const handoff = buildQuotaHandoffAttachment(from, limitText, fallbackAgent);
|
|
3473
|
+
const tab = createChatTab({
|
|
3474
|
+
fromThreadId: from.id,
|
|
3475
|
+
agent: fallbackAgent,
|
|
3476
|
+
model: null,
|
|
3477
|
+
attachments: [handoff]
|
|
3478
|
+
});
|
|
3479
|
+
return updateThread(tab.id, {
|
|
3480
|
+
parentThreadId: from.id,
|
|
3481
|
+
quotaContinuedFromId: from.id,
|
|
3482
|
+
sourceRef: from.sourceRef,
|
|
3483
|
+
sourceType: "orchestration"
|
|
3484
|
+
});
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3346
3487
|
// src/threads/sync-branch.ts
|
|
3347
3488
|
async function syncThreadBranchFromGit(threadId) {
|
|
3348
3489
|
const thread = readThread(threadId);
|
|
@@ -3403,6 +3544,8 @@ var Orchestrator = class {
|
|
|
3403
3544
|
haltDrain = /* @__PURE__ */ new Set();
|
|
3404
3545
|
/** WIP snapshot SHA at the start of the latest agent turn (per thread). */
|
|
3405
3546
|
turnBaselines = /* @__PURE__ */ new Map();
|
|
3547
|
+
/** Timers for orchestration session-quota auto-resume. */
|
|
3548
|
+
quotaResumeTimers = /* @__PURE__ */ new Map();
|
|
3406
3549
|
maxConcurrent;
|
|
3407
3550
|
runningCount = 0;
|
|
3408
3551
|
constructor(opts) {
|
|
@@ -3471,7 +3614,7 @@ var Orchestrator = class {
|
|
|
3471
3614
|
orphans: orphans.map((o) => ({ path: o.path, repoPath: o.repoPath }))
|
|
3472
3615
|
});
|
|
3473
3616
|
}
|
|
3474
|
-
const { autoCleanupOrphansEnabled } = await import("./app-settings-
|
|
3617
|
+
const { autoCleanupOrphansEnabled } = await import("./app-settings-LZP632KI.js");
|
|
3475
3618
|
if (autoCleanupOrphansEnabled() && shouldRunWorktreeCleanup() && orphans.length > 0) {
|
|
3476
3619
|
await cleanupOrphanWorktrees({ repoPaths });
|
|
3477
3620
|
}
|
|
@@ -3482,6 +3625,113 @@ var Orchestrator = class {
|
|
|
3482
3625
|
void this.drainQueue(thread.id);
|
|
3483
3626
|
}
|
|
3484
3627
|
}
|
|
3628
|
+
this.schedulePendingQuotaResumes();
|
|
3629
|
+
}
|
|
3630
|
+
clearQuotaResumeTimer(threadId) {
|
|
3631
|
+
const timer = this.quotaResumeTimers.get(threadId);
|
|
3632
|
+
if (timer) clearTimeout(timer);
|
|
3633
|
+
this.quotaResumeTimers.delete(threadId);
|
|
3634
|
+
}
|
|
3635
|
+
/** Schedule (or fire) auto-retry after a provider session/usage limit reset. */
|
|
3636
|
+
scheduleQuotaResume(threadId, resumeAt) {
|
|
3637
|
+
this.clearQuotaResumeTimer(threadId);
|
|
3638
|
+
updateThread(threadId, { quotaResumeAt: resumeAt.toISOString() });
|
|
3639
|
+
const delay = Math.max(5e3, resumeAt.getTime() - Date.now());
|
|
3640
|
+
const capped = Math.min(delay, 2147483647);
|
|
3641
|
+
const timer = setTimeout(() => {
|
|
3642
|
+
this.quotaResumeTimers.delete(threadId);
|
|
3643
|
+
void this.resumeAfterQuotaWait(threadId);
|
|
3644
|
+
}, capped);
|
|
3645
|
+
this.quotaResumeTimers.set(threadId, timer);
|
|
3646
|
+
}
|
|
3647
|
+
schedulePendingQuotaResumes() {
|
|
3648
|
+
for (const thread of listThreads({ includeArchived: false })) {
|
|
3649
|
+
if (!thread.quotaResumeAt) continue;
|
|
3650
|
+
const at = new Date(thread.quotaResumeAt);
|
|
3651
|
+
if (Number.isNaN(at.getTime())) continue;
|
|
3652
|
+
if (at.getTime() <= Date.now()) {
|
|
3653
|
+
void this.resumeAfterQuotaWait(thread.id);
|
|
3654
|
+
} else if (!this.quotaResumeTimers.has(thread.id)) {
|
|
3655
|
+
this.scheduleQuotaResume(thread.id, at);
|
|
3656
|
+
}
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
async resumeAfterQuotaWait(threadId) {
|
|
3660
|
+
const thread = findThreadByRef(threadId);
|
|
3661
|
+
if (!thread || thread.status === "archived") return;
|
|
3662
|
+
this.clearQuotaResumeTimer(threadId);
|
|
3663
|
+
try {
|
|
3664
|
+
updateThread(threadId, { quotaResumeAt: null });
|
|
3665
|
+
} catch {
|
|
3666
|
+
return;
|
|
3667
|
+
}
|
|
3668
|
+
if (thread.status === "running" || this.activeTurns.has(threadId) || this.startingTurns.has(threadId)) {
|
|
3669
|
+
return;
|
|
3670
|
+
}
|
|
3671
|
+
await this.send(threadId, QUOTA_RESUME_PROMPT);
|
|
3672
|
+
}
|
|
3673
|
+
/**
|
|
3674
|
+
* Host-side continue when an orchestration chat hits a provider session/usage
|
|
3675
|
+
* limit (not context size): switch agent (Auto) or wait until reset.
|
|
3676
|
+
*/
|
|
3677
|
+
async maybeHandleOrchestrationQuotaFailover(threadId, limitText) {
|
|
3678
|
+
const thread = findThreadByRef(threadId);
|
|
3679
|
+
if (!thread) return;
|
|
3680
|
+
const plan = planOrchestrationQuotaFailover(thread, limitText);
|
|
3681
|
+
if (!plan || plan.action === "none") return;
|
|
3682
|
+
if (plan.action === "wait_reset" && plan.resumeAt) {
|
|
3683
|
+
this.haltDrain.add(threadId);
|
|
3684
|
+
this.scheduleQuotaResume(threadId, plan.resumeAt);
|
|
3685
|
+
setStatus(threadId, "idle", null);
|
|
3686
|
+
appendMessage(threadId, {
|
|
3687
|
+
role: "agent",
|
|
3688
|
+
text: `Sideboard will auto-retry this orchestration around ${plan.resumeAt.toLocaleString()} when the session limit resets.`,
|
|
3689
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
3690
|
+
});
|
|
3691
|
+
this.emit({
|
|
3692
|
+
type: "quota_failover",
|
|
3693
|
+
threadId,
|
|
3694
|
+
action: "wait_reset",
|
|
3695
|
+
message: plan.reason,
|
|
3696
|
+
resumeAt: plan.resumeAt.toISOString()
|
|
3697
|
+
});
|
|
3698
|
+
this.emit({ type: "status_changed", threadId, status: "idle" });
|
|
3699
|
+
return;
|
|
3700
|
+
}
|
|
3701
|
+
if (plan.action === "switch_agent" && plan.fallbackAgent) {
|
|
3702
|
+
this.haltDrain.add(threadId);
|
|
3703
|
+
const next = createQuotaFailoverChat(
|
|
3704
|
+
thread,
|
|
3705
|
+
plan.fallbackAgent,
|
|
3706
|
+
plan.limitText
|
|
3707
|
+
);
|
|
3708
|
+
this.clearQuotaResumeTimer(threadId);
|
|
3709
|
+
try {
|
|
3710
|
+
updateThread(threadId, { quotaResumeAt: null });
|
|
3711
|
+
} catch {
|
|
3712
|
+
}
|
|
3713
|
+
appendMessage(threadId, {
|
|
3714
|
+
role: "agent",
|
|
3715
|
+
text: `Session limit on ${thread.agent}. Sideboard continued on ${plan.fallbackAgent} (Auto) in [${next.title}](sideboard://thread/${next.id}).`,
|
|
3716
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
3717
|
+
});
|
|
3718
|
+
this.emit({
|
|
3719
|
+
type: "quota_failover",
|
|
3720
|
+
threadId,
|
|
3721
|
+
action: "switch_agent",
|
|
3722
|
+
toThreadId: next.id,
|
|
3723
|
+
message: plan.reason
|
|
3724
|
+
});
|
|
3725
|
+
this.emit({
|
|
3726
|
+
type: "status_changed",
|
|
3727
|
+
threadId: next.id,
|
|
3728
|
+
status: next.status
|
|
3729
|
+
});
|
|
3730
|
+
await this.send(
|
|
3731
|
+
next.id,
|
|
3732
|
+
QUOTA_CONTINUE_PROMPT(thread.agent, plan.fallbackAgent)
|
|
3733
|
+
);
|
|
3734
|
+
}
|
|
3485
3735
|
}
|
|
3486
3736
|
getThreads(includeArchived = false) {
|
|
3487
3737
|
return listThreads({ includeArchived });
|
|
@@ -3498,7 +3748,7 @@ var Orchestrator = class {
|
|
|
3498
3748
|
});
|
|
3499
3749
|
});
|
|
3500
3750
|
this.emit({ type: "status_changed", threadId: thread.id, status: thread.status });
|
|
3501
|
-
const { autoRunAfterSetupEnabled } = await import("./app-settings-
|
|
3751
|
+
const { autoRunAfterSetupEnabled } = await import("./app-settings-LZP632KI.js");
|
|
3502
3752
|
if (autoRunAfterSetupEnabled()) {
|
|
3503
3753
|
try {
|
|
3504
3754
|
await this.startDev(thread.id);
|
|
@@ -3744,7 +3994,7 @@ var Orchestrator = class {
|
|
|
3744
3994
|
});
|
|
3745
3995
|
const artifactDirective = isBrightsy ? null : formatArtifactDirective();
|
|
3746
3996
|
const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
|
|
3747
|
-
const { autoRenameBranchEnabled } = await import("./app-settings-
|
|
3997
|
+
const { autoRenameBranchEnabled } = await import("./app-settings-LZP632KI.js");
|
|
3748
3998
|
const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled() ? formatRenameBranchDirective(fresh, {
|
|
3749
3999
|
customPrompt: settings?.prompts?.renameBranch
|
|
3750
4000
|
}) : null;
|
|
@@ -3840,11 +4090,16 @@ var Orchestrator = class {
|
|
|
3840
4090
|
}
|
|
3841
4091
|
}
|
|
3842
4092
|
}
|
|
3843
|
-
const
|
|
3844
|
-
|
|
4093
|
+
const lastStderr = summarizeTurnStderr(stderrTail);
|
|
4094
|
+
const detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
|
|
4095
|
+
let chatText = assistantText;
|
|
4096
|
+
if (exitCode !== 0 && !chatText && looksLikeAgentFailureMessage(detail)) {
|
|
4097
|
+
chatText = humanizeAgentFailDetail(detail);
|
|
4098
|
+
}
|
|
4099
|
+
if (chatText || parts.length > 0) {
|
|
3845
4100
|
appendMessage(threadId, {
|
|
3846
4101
|
role: "agent",
|
|
3847
|
-
text:
|
|
4102
|
+
text: chatText,
|
|
3848
4103
|
parts: parts.length > 0 ? parts : void 0,
|
|
3849
4104
|
durationMs: Math.max(0, Date.now() - turnStartedAt),
|
|
3850
4105
|
usage,
|
|
@@ -3863,13 +4118,12 @@ var Orchestrator = class {
|
|
|
3863
4118
|
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
3864
4119
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
3865
4120
|
} else {
|
|
3866
|
-
const lastStderr = summarizeTurnStderr(stderrTail);
|
|
3867
|
-
const detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
|
|
3868
4121
|
const failDetail = formatTurnExitError(exitCode, detail);
|
|
4122
|
+
const explainedInChat = exitCode !== 0 && Boolean(chatText) && (looksLikeAgentFailureMessage(chatText) || failDetail && chatText.includes(failDetail.replace(/^exit\s*\d+:\s*/i, "").trim()));
|
|
3869
4123
|
setStatus(
|
|
3870
4124
|
threadId,
|
|
3871
4125
|
exitCode === 0 ? "idle" : "error",
|
|
3872
|
-
exitCode === 0 ? null : failDetail
|
|
4126
|
+
exitCode === 0 || explainedInChat ? null : failDetail
|
|
3873
4127
|
);
|
|
3874
4128
|
this.emit({
|
|
3875
4129
|
type: "status_changed",
|
|
@@ -3877,6 +4131,10 @@ var Orchestrator = class {
|
|
|
3877
4131
|
status: exitCode === 0 ? "idle" : "error"
|
|
3878
4132
|
});
|
|
3879
4133
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
4134
|
+
if (exitCode !== 0) {
|
|
4135
|
+
const blob = [chatText, detail].filter(Boolean).join("\n");
|
|
4136
|
+
void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
|
|
4137
|
+
}
|
|
3880
4138
|
}
|
|
3881
4139
|
} catch (err) {
|
|
3882
4140
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -3890,6 +4148,7 @@ var Orchestrator = class {
|
|
|
3890
4148
|
this.emit({ type: "error", threadId, message });
|
|
3891
4149
|
this.emit({ type: "status_changed", threadId, status: "error" });
|
|
3892
4150
|
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
4151
|
+
void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
|
|
3893
4152
|
}
|
|
3894
4153
|
} finally {
|
|
3895
4154
|
this.startingTurns.delete(threadId);
|
|
@@ -4374,6 +4633,9 @@ var Orchestrator = class {
|
|
|
4374
4633
|
`Cannot switch agent provider mid-chat (${thread.agent} \u2192 ${patch.agent}). Start a new chat tab instead.`
|
|
4375
4634
|
);
|
|
4376
4635
|
}
|
|
4636
|
+
if (isOrchestratorThread(thread)) {
|
|
4637
|
+
assertOrchestratorCapableAgent(patch.agent);
|
|
4638
|
+
}
|
|
4377
4639
|
next.agent = patch.agent;
|
|
4378
4640
|
if (patch.agent !== "claude" && patch.model === void 0) next.model = null;
|
|
4379
4641
|
next.sessionId = null;
|
|
@@ -4465,7 +4727,7 @@ var Orchestrator = class {
|
|
|
4465
4727
|
await runArchiveScript(thread.repoPath, thread.worktreePath);
|
|
4466
4728
|
} catch {
|
|
4467
4729
|
}
|
|
4468
|
-
const { deleteBranchOnPurgeEnabled } = await import("./app-settings-
|
|
4730
|
+
const { deleteBranchOnPurgeEnabled } = await import("./app-settings-LZP632KI.js");
|
|
4469
4731
|
const deleteBranch = opts?.deleteBranch ?? deleteBranchOnPurgeEnabled();
|
|
4470
4732
|
await removeWorktree(thread.repoPath, thread.worktreePath, {
|
|
4471
4733
|
deleteBranch: deleteBranch ? thread.branchName : void 0
|
|
@@ -4484,7 +4746,7 @@ var Orchestrator = class {
|
|
|
4484
4746
|
return setStatus(thread.id, "idle");
|
|
4485
4747
|
}
|
|
4486
4748
|
if (!existsSync12(thread.worktreePath)) {
|
|
4487
|
-
const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-
|
|
4749
|
+
const { createThreadWorktree: createThreadWorktree2 } = await import("./worktree-TEDAJ57S.js");
|
|
4488
4750
|
const { execa: execa6 } = await import("execa");
|
|
4489
4751
|
const slug = thread.worktreePath.split("/").pop();
|
|
4490
4752
|
const dest = thread.worktreePath;
|
|
@@ -4569,7 +4831,7 @@ async function startOrchestration(opts) {
|
|
|
4569
4831
|
sourceRef: "default",
|
|
4570
4832
|
...createOpts
|
|
4571
4833
|
}).catch(async () => {
|
|
4572
|
-
const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-
|
|
4834
|
+
const { resolveDefaultBranch: resolveDefaultBranch2, resolveRepoRoot: resolveRepoRoot2 } = await import("./worktree-TEDAJ57S.js");
|
|
4573
4835
|
const repo = await resolveRepoRoot2(repoPath);
|
|
4574
4836
|
const def = await resolveDefaultBranch2(repo);
|
|
4575
4837
|
return createThread({
|
|
@@ -4579,7 +4841,7 @@ async function startOrchestration(opts) {
|
|
|
4579
4841
|
repoPath: repo
|
|
4580
4842
|
});
|
|
4581
4843
|
});
|
|
4582
|
-
const { updateThread: upd } = await import("./thread-store-
|
|
4844
|
+
const { updateThread: upd } = await import("./thread-store-OV2X6PYO.js");
|
|
4583
4845
|
const updated = upd(thread.id, {
|
|
4584
4846
|
sourceType: "orchestration",
|
|
4585
4847
|
sourceRef: goal
|
|
@@ -5010,6 +5272,120 @@ async function startMcpServer() {
|
|
|
5010
5272
|
}
|
|
5011
5273
|
}
|
|
5012
5274
|
);
|
|
5275
|
+
const agentEnum = z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
5276
|
+
server.tool(
|
|
5277
|
+
"list_models",
|
|
5278
|
+
"List models for an agent. Prefer Auto: do not call this unless you have a reason to pin a specific model (user request, cost/latency, capability). Omit agent to list all.",
|
|
5279
|
+
{
|
|
5280
|
+
agent: agentEnum.optional().describe("Limit to one agent; omit for all")
|
|
5281
|
+
},
|
|
5282
|
+
async ({ agent }) => {
|
|
5283
|
+
try {
|
|
5284
|
+
const catalogs = await listModelsForAgent(agent);
|
|
5285
|
+
return {
|
|
5286
|
+
content: [{ type: "text", text: JSON.stringify(catalogs, null, 2) }]
|
|
5287
|
+
};
|
|
5288
|
+
} catch (err) {
|
|
5289
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5290
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
5291
|
+
}
|
|
5292
|
+
}
|
|
5293
|
+
);
|
|
5294
|
+
server.tool(
|
|
5295
|
+
"fork_worktree",
|
|
5296
|
+
"Fork a worktree agent chat into a NEW git worktree + chat (desktop \u201CFork to new workspace\u201D). Seeds a transcript (through through_index, default all). Optional agent override. Leave model unset for Auto (default) \u2014 only pass model when you have a reason. Not for the orchestrator. Then send_to_thread / wait_for_turn on the returned id.",
|
|
5297
|
+
{
|
|
5298
|
+
ref: z.string().describe("Worktree thread id/ref to fork"),
|
|
5299
|
+
through_index: z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
5300
|
+
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
5301
|
+
model: z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
5302
|
+
title: z.string().optional()
|
|
5303
|
+
},
|
|
5304
|
+
async ({ ref, through_index, agent, model, title }) => {
|
|
5305
|
+
try {
|
|
5306
|
+
const source = orch.getThread(ref);
|
|
5307
|
+
if (source) await orch.reconcile(source.repoPath);
|
|
5308
|
+
const thread = await orch.forkThreadWorktree({
|
|
5309
|
+
threadId: ref,
|
|
5310
|
+
throughIndex: through_index,
|
|
5311
|
+
agent,
|
|
5312
|
+
model,
|
|
5313
|
+
title
|
|
5314
|
+
});
|
|
5315
|
+
return {
|
|
5316
|
+
content: [
|
|
5317
|
+
{
|
|
5318
|
+
type: "text",
|
|
5319
|
+
text: JSON.stringify({
|
|
5320
|
+
id: thread.id,
|
|
5321
|
+
title: thread.title,
|
|
5322
|
+
status: thread.status,
|
|
5323
|
+
agent: thread.agent,
|
|
5324
|
+
model: thread.model,
|
|
5325
|
+
branchName: thread.branchName,
|
|
5326
|
+
worktreePath: thread.worktreePath,
|
|
5327
|
+
fromThreadId: source?.id ?? ref,
|
|
5328
|
+
link: `sideboard://thread/${thread.id}`
|
|
5329
|
+
})
|
|
5330
|
+
}
|
|
5331
|
+
]
|
|
5332
|
+
};
|
|
5333
|
+
} catch (err) {
|
|
5334
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5335
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
5336
|
+
}
|
|
5337
|
+
}
|
|
5338
|
+
);
|
|
5339
|
+
server.tool(
|
|
5340
|
+
"fork_chat",
|
|
5341
|
+
"Fork a chat into a NEW tab on the SAME workspace: worktree agent \u2192 same worktree tab; Global orchestration chat \u2192 new orchestration chat (same synthetic home). Seeds a transcript; optional agent override. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Remote coordinators use this to continue an orchestration chat on another agent after session limits. Then send_to_thread / wait_for_turn on the returned id. Use fork_worktree only for worktree agents that need a new git worktree.",
|
|
5342
|
+
{
|
|
5343
|
+
ref: z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
5344
|
+
through_index: z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
5345
|
+
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
5346
|
+
model: z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
5347
|
+
title: z.string().optional()
|
|
5348
|
+
},
|
|
5349
|
+
async ({ ref, through_index, agent, model, title }) => {
|
|
5350
|
+
try {
|
|
5351
|
+
const source = orch.getThread(ref);
|
|
5352
|
+
if (!source) {
|
|
5353
|
+
return {
|
|
5354
|
+
content: [{ type: "text", text: `Thread not found: ${ref}` }],
|
|
5355
|
+
isError: true
|
|
5356
|
+
};
|
|
5357
|
+
}
|
|
5358
|
+
const tab = orch.forkChatTab({
|
|
5359
|
+
threadId: source.id,
|
|
5360
|
+
throughIndex: through_index,
|
|
5361
|
+
agent,
|
|
5362
|
+
model,
|
|
5363
|
+
title
|
|
5364
|
+
});
|
|
5365
|
+
return {
|
|
5366
|
+
content: [
|
|
5367
|
+
{
|
|
5368
|
+
type: "text",
|
|
5369
|
+
text: JSON.stringify({
|
|
5370
|
+
id: tab.id,
|
|
5371
|
+
title: tab.title,
|
|
5372
|
+
status: tab.status,
|
|
5373
|
+
agent: tab.agent,
|
|
5374
|
+
model: tab.model,
|
|
5375
|
+
sourceType: tab.sourceType,
|
|
5376
|
+
worktreePath: tab.worktreePath,
|
|
5377
|
+
fromThreadId: source.id,
|
|
5378
|
+
link: `sideboard://thread/${tab.id}`
|
|
5379
|
+
})
|
|
5380
|
+
}
|
|
5381
|
+
]
|
|
5382
|
+
};
|
|
5383
|
+
} catch (err) {
|
|
5384
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5385
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
5386
|
+
}
|
|
5387
|
+
}
|
|
5388
|
+
);
|
|
5013
5389
|
server.tool(
|
|
5014
5390
|
"run_dev_script",
|
|
5015
5391
|
"Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
|