@sideboard-ai/core 0.1.95 → 0.1.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +87 -43
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +87 -43
- package/dist/mcp/run-stdio.cjs +77 -38
- package/dist/mcp/run-stdio.js +77 -38
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -11694,23 +11694,24 @@ async function countUnpushedCommits(worktreePath) {
|
|
|
11694
11694
|
reject: false
|
|
11695
11695
|
});
|
|
11696
11696
|
const branch = head.stdout.trim();
|
|
11697
|
+
if (branch && branch !== "HEAD") {
|
|
11698
|
+
const remote = await git(
|
|
11699
|
+
["rev-list", "--count", `origin/${branch}..HEAD`],
|
|
11700
|
+
worktreePath,
|
|
11701
|
+
{ reject: false }
|
|
11702
|
+
);
|
|
11703
|
+
if (remote.exitCode === 0) {
|
|
11704
|
+
const n2 = Number(remote.stdout.trim());
|
|
11705
|
+
return Number.isFinite(n2) ? n2 : 0;
|
|
11706
|
+
}
|
|
11707
|
+
}
|
|
11697
11708
|
const upstream = await git(
|
|
11698
11709
|
["rev-list", "--count", "@{upstream}..HEAD"],
|
|
11699
11710
|
worktreePath,
|
|
11700
11711
|
{ reject: false }
|
|
11701
11712
|
);
|
|
11702
|
-
if (upstream.exitCode
|
|
11703
|
-
|
|
11704
|
-
return Number.isFinite(n2) ? n2 : 0;
|
|
11705
|
-
}
|
|
11706
|
-
if (!branch || branch === "HEAD") return 0;
|
|
11707
|
-
const remote = await git(
|
|
11708
|
-
["rev-list", "--count", `origin/${branch}..HEAD`],
|
|
11709
|
-
worktreePath,
|
|
11710
|
-
{ reject: false }
|
|
11711
|
-
);
|
|
11712
|
-
if (remote.exitCode !== 0) return 0;
|
|
11713
|
-
const n = Number(remote.stdout.trim());
|
|
11713
|
+
if (upstream.exitCode !== 0) return 0;
|
|
11714
|
+
const n = Number(upstream.stdout.trim());
|
|
11714
11715
|
return Number.isFinite(n) ? n : 0;
|
|
11715
11716
|
}
|
|
11716
11717
|
function splitCombinedDiff(stdout) {
|
|
@@ -15418,6 +15419,11 @@ function isPidAlive(pid) {
|
|
|
15418
15419
|
return false;
|
|
15419
15420
|
}
|
|
15420
15421
|
}
|
|
15422
|
+
function writeLiveStatus(threadId, status, lastError) {
|
|
15423
|
+
const latest = readThread(threadId);
|
|
15424
|
+
if (!latest || latest.status === "archived") return latest;
|
|
15425
|
+
return setStatus(threadId, status, lastError);
|
|
15426
|
+
}
|
|
15421
15427
|
var Orchestrator = class {
|
|
15422
15428
|
events = new import_node_events.EventEmitter();
|
|
15423
15429
|
processes = /* @__PURE__ */ new Map();
|
|
@@ -15723,6 +15729,9 @@ var Orchestrator = class {
|
|
|
15723
15729
|
}
|
|
15724
15730
|
async send(threadRef, prompt) {
|
|
15725
15731
|
const thread = this.requireThread(threadRef);
|
|
15732
|
+
if (thread.status === "archived") {
|
|
15733
|
+
throw new Error(`Thread is archived: ${thread.id}`);
|
|
15734
|
+
}
|
|
15726
15735
|
return withThreadLock(thread.id, async () => {
|
|
15727
15736
|
const current = this.requireThread(thread.id);
|
|
15728
15737
|
const queue = [...current.queue, prompt];
|
|
@@ -15816,7 +15825,7 @@ var Orchestrator = class {
|
|
|
15816
15825
|
break;
|
|
15817
15826
|
}
|
|
15818
15827
|
const thread = readThread(threadId);
|
|
15819
|
-
if (!thread || thread.queue.length === 0) {
|
|
15828
|
+
if (!thread || thread.status === "archived" || thread.queue.length === 0) {
|
|
15820
15829
|
if (thread && thread.status === "queued") {
|
|
15821
15830
|
setStatus(threadId, "idle");
|
|
15822
15831
|
this.emit({ type: "status_changed", threadId, status: "idle" });
|
|
@@ -15847,14 +15856,17 @@ var Orchestrator = class {
|
|
|
15847
15856
|
}
|
|
15848
15857
|
}
|
|
15849
15858
|
async runTurn(threadId, prompt) {
|
|
15850
|
-
|
|
15859
|
+
const existing = readThread(threadId);
|
|
15860
|
+
if (!existing || existing.status === "archived") return;
|
|
15861
|
+
let thread = existing;
|
|
15851
15862
|
if (isGlobalThread(thread) && thread.sessionId && orchestratorSessionPoisonedByBuiltins(thread)) {
|
|
15852
15863
|
thread = updateThread(threadId, { sessionId: null });
|
|
15853
15864
|
}
|
|
15865
|
+
const running = writeLiveStatus(threadId, "running");
|
|
15866
|
+
if (!running || running.status !== "running") return;
|
|
15854
15867
|
this.runningCount += 1;
|
|
15855
15868
|
const turnStartedAt = Date.now();
|
|
15856
15869
|
this.startingTurns.add(threadId);
|
|
15857
|
-
setStatus(threadId, "running");
|
|
15858
15870
|
this.emit({ type: "status_changed", threadId, status: "running" });
|
|
15859
15871
|
this.emit({ type: "turn_started", threadId, prompt });
|
|
15860
15872
|
try {
|
|
@@ -16125,8 +16137,8 @@ var Orchestrator = class {
|
|
|
16125
16137
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
16126
16138
|
});
|
|
16127
16139
|
}
|
|
16128
|
-
const afterTurn =
|
|
16129
|
-
if (afterTurn.planMode && afterTurn.worktreePath?.trim()) {
|
|
16140
|
+
const afterTurn = readThread(threadId);
|
|
16141
|
+
if (afterTurn && afterTurn.status !== "archived" && afterTurn.planMode && afterTurn.worktreePath?.trim()) {
|
|
16130
16142
|
const presented = extractPresentedPlan(parts);
|
|
16131
16143
|
const exited = parts.some(
|
|
16132
16144
|
(p) => p.type === "tool" && /exitplanmode/i.test(p.name)
|
|
@@ -16139,29 +16151,34 @@ var Orchestrator = class {
|
|
|
16139
16151
|
}
|
|
16140
16152
|
}
|
|
16141
16153
|
}
|
|
16142
|
-
if (afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
|
|
16154
|
+
if (afterTurn && afterTurn.status !== "archived" && afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
|
|
16143
16155
|
(p) => p.type === "tool" && /exitplanmode/i.test(p.name)
|
|
16144
16156
|
)) {
|
|
16145
16157
|
updateThread(threadId, { sessionId: null });
|
|
16146
16158
|
}
|
|
16147
16159
|
await syncThreadBranchFromGit(threadId);
|
|
16148
16160
|
if (this.stoppedTurns.has(threadId)) {
|
|
16149
|
-
|
|
16150
|
-
|
|
16161
|
+
const stopped = writeLiveStatus(threadId, "stopped");
|
|
16162
|
+
if (stopped?.status === "stopped") {
|
|
16163
|
+
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
16164
|
+
}
|
|
16151
16165
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
16152
16166
|
} else {
|
|
16153
16167
|
const failDetail = formatTurnExitError(exitCode, detail);
|
|
16154
16168
|
const explainedInChat = exitCode !== 0 && Boolean(chatText) && (looksLikeAgentFailureMessage(chatText) || failDetail && chatText.includes(failDetail.replace(/^exit\s*\d+:\s*/i, "").trim()));
|
|
16155
|
-
|
|
16169
|
+
const nextStatus = exitCode === 0 ? "idle" : "error";
|
|
16170
|
+
const written = writeLiveStatus(
|
|
16156
16171
|
threadId,
|
|
16157
|
-
|
|
16172
|
+
nextStatus,
|
|
16158
16173
|
exitCode === 0 || explainedInChat ? null : failDetail
|
|
16159
16174
|
);
|
|
16160
|
-
|
|
16161
|
-
|
|
16162
|
-
|
|
16163
|
-
|
|
16164
|
-
|
|
16175
|
+
if (written && written.status !== "archived") {
|
|
16176
|
+
this.emit({
|
|
16177
|
+
type: "status_changed",
|
|
16178
|
+
threadId,
|
|
16179
|
+
status: written.status
|
|
16180
|
+
});
|
|
16181
|
+
}
|
|
16165
16182
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
16166
16183
|
if (exitCode !== 0) {
|
|
16167
16184
|
const blob = [chatText, detail].filter(Boolean).join("\n");
|
|
@@ -16172,13 +16189,17 @@ var Orchestrator = class {
|
|
|
16172
16189
|
const message = err instanceof Error ? err.message : String(err);
|
|
16173
16190
|
await syncThreadBranchFromGit(threadId).catch(() => void 0);
|
|
16174
16191
|
if (this.stoppedTurns.has(threadId)) {
|
|
16175
|
-
|
|
16176
|
-
|
|
16192
|
+
const stopped = writeLiveStatus(threadId, "stopped");
|
|
16193
|
+
if (stopped?.status === "stopped") {
|
|
16194
|
+
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
16195
|
+
}
|
|
16177
16196
|
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
16178
16197
|
} else {
|
|
16179
|
-
|
|
16180
|
-
|
|
16181
|
-
|
|
16198
|
+
const written = writeLiveStatus(threadId, "error", message);
|
|
16199
|
+
if (written?.status === "error") {
|
|
16200
|
+
this.emit({ type: "error", threadId, message });
|
|
16201
|
+
this.emit({ type: "status_changed", threadId, status: "error" });
|
|
16202
|
+
}
|
|
16182
16203
|
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
16183
16204
|
void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
|
|
16184
16205
|
}
|
|
@@ -16225,9 +16246,11 @@ var Orchestrator = class {
|
|
|
16225
16246
|
if (handle) handle.kill();
|
|
16226
16247
|
const proc = this.processes.get(`${thread.id}:agent`);
|
|
16227
16248
|
if (proc) proc.kill();
|
|
16228
|
-
|
|
16229
|
-
|
|
16230
|
-
|
|
16249
|
+
const stopped = writeLiveStatus(thread.id, "stopped") ?? readThread(thread.id) ?? thread;
|
|
16250
|
+
if (stopped.status === "stopped") {
|
|
16251
|
+
this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
|
|
16252
|
+
}
|
|
16253
|
+
return stopped;
|
|
16231
16254
|
}
|
|
16232
16255
|
async startDev(threadRef, scriptName) {
|
|
16233
16256
|
const thread = this.requireThread(threadRef);
|
|
@@ -16463,11 +16486,21 @@ var Orchestrator = class {
|
|
|
16463
16486
|
const off = this.on((event) => {
|
|
16464
16487
|
if (event.type === "turn_finished" && event.threadId === thread.id) {
|
|
16465
16488
|
off();
|
|
16466
|
-
|
|
16489
|
+
const latest = readThread(thread.id);
|
|
16490
|
+
if (!latest) {
|
|
16491
|
+
reject(new Error(`Thread not found: ${thread.id}`));
|
|
16492
|
+
return;
|
|
16493
|
+
}
|
|
16494
|
+
resolve(latest);
|
|
16467
16495
|
}
|
|
16468
16496
|
if (event.type === "error" && event.threadId === thread.id) {
|
|
16469
16497
|
off();
|
|
16470
|
-
|
|
16498
|
+
const latest = readThread(thread.id);
|
|
16499
|
+
if (!latest) {
|
|
16500
|
+
reject(new Error(`Thread not found: ${thread.id}`));
|
|
16501
|
+
return;
|
|
16502
|
+
}
|
|
16503
|
+
resolve(latest);
|
|
16471
16504
|
}
|
|
16472
16505
|
});
|
|
16473
16506
|
const timer = setInterval(() => {
|
|
@@ -16477,7 +16510,13 @@ var Orchestrator = class {
|
|
|
16477
16510
|
reject(new Error("wait_for_turn timed out"));
|
|
16478
16511
|
}
|
|
16479
16512
|
const current = readThread(thread.id);
|
|
16480
|
-
if (
|
|
16513
|
+
if (!current) {
|
|
16514
|
+
clearInterval(timer);
|
|
16515
|
+
off();
|
|
16516
|
+
reject(new Error(`Thread not found: ${thread.id}`));
|
|
16517
|
+
return;
|
|
16518
|
+
}
|
|
16519
|
+
if (!["running", "queued"].includes(current.status)) {
|
|
16481
16520
|
clearInterval(timer);
|
|
16482
16521
|
off();
|
|
16483
16522
|
resolve(current);
|
|
@@ -20094,10 +20133,10 @@ function slackInboundSuperseded(opts) {
|
|
|
20094
20133
|
function interruptSlackCoordinatorForInbound(msg, agent, log = () => void 0) {
|
|
20095
20134
|
const userId = msg.userId?.trim();
|
|
20096
20135
|
if (!userId) return false;
|
|
20097
|
-
const coordinator = ensureSlackCoordinator(msg.teamId, userId, agent);
|
|
20098
|
-
const fresh = readThread(coordinator.id) ?? coordinator;
|
|
20099
|
-
if (fresh.status !== "running" && fresh.status !== "queued") return false;
|
|
20100
20136
|
try {
|
|
20137
|
+
const coordinator = ensureSlackCoordinator(msg.teamId, userId, agent);
|
|
20138
|
+
const fresh = readThread(coordinator.id) ?? coordinator;
|
|
20139
|
+
if (fresh.status !== "running" && fresh.status !== "queued") return false;
|
|
20101
20140
|
getOrchestrator().stop(fresh.id, { clearQueue: true });
|
|
20102
20141
|
log(
|
|
20103
20142
|
`interrupt ${msg.kind} ${msg.ts} \u2192 coordinator ${fresh.id.slice(0, 8)} (${fresh.status})`
|
|
@@ -20137,7 +20176,7 @@ function slackReplyThreadTs(msg) {
|
|
|
20137
20176
|
if (msg.kind === "mention") return msg.ts;
|
|
20138
20177
|
return void 0;
|
|
20139
20178
|
}
|
|
20140
|
-
var SLACK_SEEN_REACTION = "
|
|
20179
|
+
var SLACK_SEEN_REACTION = "eyes";
|
|
20141
20180
|
function writeTokenForTeam(teamId) {
|
|
20142
20181
|
const workspaces = listSlackWorkspacesRaw();
|
|
20143
20182
|
const ws = workspaces.find((w) => w.team_id === teamId);
|
|
@@ -20303,7 +20342,7 @@ async function handleSlackInbound(msg, opts) {
|
|
|
20303
20342
|
return;
|
|
20304
20343
|
}
|
|
20305
20344
|
const after = readThread(fresh.id);
|
|
20306
|
-
if (after
|
|
20345
|
+
if (!after || after.status === "stopped" || after.status === "archived") {
|
|
20307
20346
|
log(`turn finished ${msg.ts} (interrupted, skip post)`);
|
|
20308
20347
|
return;
|
|
20309
20348
|
}
|
|
@@ -20314,6 +20353,11 @@ async function handleSlackInbound(msg, opts) {
|
|
|
20314
20353
|
return;
|
|
20315
20354
|
}
|
|
20316
20355
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
20356
|
+
const gone = /thread not found/i.test(errMsg);
|
|
20357
|
+
if (gone) {
|
|
20358
|
+
log(`turn finished ${msg.ts} (thread gone, skip post)`);
|
|
20359
|
+
return;
|
|
20360
|
+
}
|
|
20317
20361
|
const timedOut = /timed out|timeout/i.test(errMsg);
|
|
20318
20362
|
const errorReply = timedOut ? SLACK_LISTEN_TIMEOUT_REPLY : `Sideboard coordinator error: ${errMsg}`;
|
|
20319
20363
|
await postSlackReply(msg, errorReply, opts).catch((postErr) => {
|
package/dist/index.d.cts
CHANGED
|
@@ -4262,7 +4262,7 @@ interface SlackListenOptions {
|
|
|
4262
4262
|
*/
|
|
4263
4263
|
inboundGeneration?: number;
|
|
4264
4264
|
currentInboundGeneration?: () => number;
|
|
4265
|
-
/** Listen already acked; skip the
|
|
4265
|
+
/** Listen already acked; skip the eyes reaction in handleSlackInbound. */
|
|
4266
4266
|
skipAck?: boolean;
|
|
4267
4267
|
}
|
|
4268
4268
|
/**
|
|
@@ -4276,9 +4276,9 @@ declare function formatSlackInboundPrompt(msg: SlackInboundMessage): string;
|
|
|
4276
4276
|
* more than one device can see who answered and address follow-ups the same way.
|
|
4277
4277
|
*/
|
|
4278
4278
|
declare function formatSlackSignedReply(deviceLabel: string, text: string): string;
|
|
4279
|
-
/** Slack emoji short name for “seen /
|
|
4280
|
-
declare const SLACK_SEEN_REACTION = "
|
|
4281
|
-
/**
|
|
4279
|
+
/** Slack emoji short name for “seen / looking at this”. */
|
|
4280
|
+
declare const SLACK_SEEN_REACTION = "eyes";
|
|
4281
|
+
/** Eyes-react the inbound message so Slack shows the bot saw it. */
|
|
4282
4282
|
declare function ackSlackInboundSeen(msg: SlackInboundMessage, opts: Pick<SlackListenOptions, 'addReaction' | 'fetchImpl' | 'onLog'>): Promise<void>;
|
|
4283
4283
|
declare function handleSlackInbound(msg: SlackInboundMessage, opts: SlackListenOptions): Promise<void>;
|
|
4284
4284
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -4262,7 +4262,7 @@ interface SlackListenOptions {
|
|
|
4262
4262
|
*/
|
|
4263
4263
|
inboundGeneration?: number;
|
|
4264
4264
|
currentInboundGeneration?: () => number;
|
|
4265
|
-
/** Listen already acked; skip the
|
|
4265
|
+
/** Listen already acked; skip the eyes reaction in handleSlackInbound. */
|
|
4266
4266
|
skipAck?: boolean;
|
|
4267
4267
|
}
|
|
4268
4268
|
/**
|
|
@@ -4276,9 +4276,9 @@ declare function formatSlackInboundPrompt(msg: SlackInboundMessage): string;
|
|
|
4276
4276
|
* more than one device can see who answered and address follow-ups the same way.
|
|
4277
4277
|
*/
|
|
4278
4278
|
declare function formatSlackSignedReply(deviceLabel: string, text: string): string;
|
|
4279
|
-
/** Slack emoji short name for “seen /
|
|
4280
|
-
declare const SLACK_SEEN_REACTION = "
|
|
4281
|
-
/**
|
|
4279
|
+
/** Slack emoji short name for “seen / looking at this”. */
|
|
4280
|
+
declare const SLACK_SEEN_REACTION = "eyes";
|
|
4281
|
+
/** Eyes-react the inbound message so Slack shows the bot saw it. */
|
|
4282
4282
|
declare function ackSlackInboundSeen(msg: SlackInboundMessage, opts: Pick<SlackListenOptions, 'addReaction' | 'fetchImpl' | 'onLog'>): Promise<void>;
|
|
4283
4283
|
declare function handleSlackInbound(msg: SlackInboundMessage, opts: SlackListenOptions): Promise<void>;
|
|
4284
4284
|
/**
|
package/dist/index.js
CHANGED
|
@@ -2151,23 +2151,24 @@ async function countUnpushedCommits(worktreePath) {
|
|
|
2151
2151
|
reject: false
|
|
2152
2152
|
});
|
|
2153
2153
|
const branch = head.stdout.trim();
|
|
2154
|
+
if (branch && branch !== "HEAD") {
|
|
2155
|
+
const remote = await git(
|
|
2156
|
+
["rev-list", "--count", `origin/${branch}..HEAD`],
|
|
2157
|
+
worktreePath,
|
|
2158
|
+
{ reject: false }
|
|
2159
|
+
);
|
|
2160
|
+
if (remote.exitCode === 0) {
|
|
2161
|
+
const n2 = Number(remote.stdout.trim());
|
|
2162
|
+
return Number.isFinite(n2) ? n2 : 0;
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2154
2165
|
const upstream = await git(
|
|
2155
2166
|
["rev-list", "--count", "@{upstream}..HEAD"],
|
|
2156
2167
|
worktreePath,
|
|
2157
2168
|
{ reject: false }
|
|
2158
2169
|
);
|
|
2159
|
-
if (upstream.exitCode
|
|
2160
|
-
|
|
2161
|
-
return Number.isFinite(n2) ? n2 : 0;
|
|
2162
|
-
}
|
|
2163
|
-
if (!branch || branch === "HEAD") return 0;
|
|
2164
|
-
const remote = await git(
|
|
2165
|
-
["rev-list", "--count", `origin/${branch}..HEAD`],
|
|
2166
|
-
worktreePath,
|
|
2167
|
-
{ reject: false }
|
|
2168
|
-
);
|
|
2169
|
-
if (remote.exitCode !== 0) return 0;
|
|
2170
|
-
const n = Number(remote.stdout.trim());
|
|
2170
|
+
if (upstream.exitCode !== 0) return 0;
|
|
2171
|
+
const n = Number(upstream.stdout.trim());
|
|
2171
2172
|
return Number.isFinite(n) ? n : 0;
|
|
2172
2173
|
}
|
|
2173
2174
|
function splitCombinedDiff(stdout) {
|
|
@@ -5810,6 +5811,11 @@ function isPidAlive(pid) {
|
|
|
5810
5811
|
return false;
|
|
5811
5812
|
}
|
|
5812
5813
|
}
|
|
5814
|
+
function writeLiveStatus(threadId, status, lastError) {
|
|
5815
|
+
const latest = readThread(threadId);
|
|
5816
|
+
if (!latest || latest.status === "archived") return latest;
|
|
5817
|
+
return setStatus(threadId, status, lastError);
|
|
5818
|
+
}
|
|
5813
5819
|
var Orchestrator = class {
|
|
5814
5820
|
events = new EventEmitter();
|
|
5815
5821
|
processes = /* @__PURE__ */ new Map();
|
|
@@ -6115,6 +6121,9 @@ var Orchestrator = class {
|
|
|
6115
6121
|
}
|
|
6116
6122
|
async send(threadRef, prompt) {
|
|
6117
6123
|
const thread = this.requireThread(threadRef);
|
|
6124
|
+
if (thread.status === "archived") {
|
|
6125
|
+
throw new Error(`Thread is archived: ${thread.id}`);
|
|
6126
|
+
}
|
|
6118
6127
|
return withThreadLock(thread.id, async () => {
|
|
6119
6128
|
const current = this.requireThread(thread.id);
|
|
6120
6129
|
const queue = [...current.queue, prompt];
|
|
@@ -6208,7 +6217,7 @@ var Orchestrator = class {
|
|
|
6208
6217
|
break;
|
|
6209
6218
|
}
|
|
6210
6219
|
const thread = readThread(threadId);
|
|
6211
|
-
if (!thread || thread.queue.length === 0) {
|
|
6220
|
+
if (!thread || thread.status === "archived" || thread.queue.length === 0) {
|
|
6212
6221
|
if (thread && thread.status === "queued") {
|
|
6213
6222
|
setStatus(threadId, "idle");
|
|
6214
6223
|
this.emit({ type: "status_changed", threadId, status: "idle" });
|
|
@@ -6239,14 +6248,17 @@ var Orchestrator = class {
|
|
|
6239
6248
|
}
|
|
6240
6249
|
}
|
|
6241
6250
|
async runTurn(threadId, prompt) {
|
|
6242
|
-
|
|
6251
|
+
const existing = readThread(threadId);
|
|
6252
|
+
if (!existing || existing.status === "archived") return;
|
|
6253
|
+
let thread = existing;
|
|
6243
6254
|
if (isGlobalThread(thread) && thread.sessionId && orchestratorSessionPoisonedByBuiltins(thread)) {
|
|
6244
6255
|
thread = updateThread(threadId, { sessionId: null });
|
|
6245
6256
|
}
|
|
6257
|
+
const running = writeLiveStatus(threadId, "running");
|
|
6258
|
+
if (!running || running.status !== "running") return;
|
|
6246
6259
|
this.runningCount += 1;
|
|
6247
6260
|
const turnStartedAt = Date.now();
|
|
6248
6261
|
this.startingTurns.add(threadId);
|
|
6249
|
-
setStatus(threadId, "running");
|
|
6250
6262
|
this.emit({ type: "status_changed", threadId, status: "running" });
|
|
6251
6263
|
this.emit({ type: "turn_started", threadId, prompt });
|
|
6252
6264
|
try {
|
|
@@ -6517,8 +6529,8 @@ var Orchestrator = class {
|
|
|
6517
6529
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
6518
6530
|
});
|
|
6519
6531
|
}
|
|
6520
|
-
const afterTurn =
|
|
6521
|
-
if (afterTurn.planMode && afterTurn.worktreePath?.trim()) {
|
|
6532
|
+
const afterTurn = readThread(threadId);
|
|
6533
|
+
if (afterTurn && afterTurn.status !== "archived" && afterTurn.planMode && afterTurn.worktreePath?.trim()) {
|
|
6522
6534
|
const presented = extractPresentedPlan(parts);
|
|
6523
6535
|
const exited = parts.some(
|
|
6524
6536
|
(p) => p.type === "tool" && /exitplanmode/i.test(p.name)
|
|
@@ -6531,29 +6543,34 @@ var Orchestrator = class {
|
|
|
6531
6543
|
}
|
|
6532
6544
|
}
|
|
6533
6545
|
}
|
|
6534
|
-
if (afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
|
|
6546
|
+
if (afterTurn && afterTurn.status !== "archived" && afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
|
|
6535
6547
|
(p) => p.type === "tool" && /exitplanmode/i.test(p.name)
|
|
6536
6548
|
)) {
|
|
6537
6549
|
updateThread(threadId, { sessionId: null });
|
|
6538
6550
|
}
|
|
6539
6551
|
await syncThreadBranchFromGit(threadId);
|
|
6540
6552
|
if (this.stoppedTurns.has(threadId)) {
|
|
6541
|
-
|
|
6542
|
-
|
|
6553
|
+
const stopped = writeLiveStatus(threadId, "stopped");
|
|
6554
|
+
if (stopped?.status === "stopped") {
|
|
6555
|
+
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
6556
|
+
}
|
|
6543
6557
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
6544
6558
|
} else {
|
|
6545
6559
|
const failDetail = formatTurnExitError(exitCode, detail);
|
|
6546
6560
|
const explainedInChat = exitCode !== 0 && Boolean(chatText) && (looksLikeAgentFailureMessage(chatText) || failDetail && chatText.includes(failDetail.replace(/^exit\s*\d+:\s*/i, "").trim()));
|
|
6547
|
-
|
|
6561
|
+
const nextStatus = exitCode === 0 ? "idle" : "error";
|
|
6562
|
+
const written = writeLiveStatus(
|
|
6548
6563
|
threadId,
|
|
6549
|
-
|
|
6564
|
+
nextStatus,
|
|
6550
6565
|
exitCode === 0 || explainedInChat ? null : failDetail
|
|
6551
6566
|
);
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6567
|
+
if (written && written.status !== "archived") {
|
|
6568
|
+
this.emit({
|
|
6569
|
+
type: "status_changed",
|
|
6570
|
+
threadId,
|
|
6571
|
+
status: written.status
|
|
6572
|
+
});
|
|
6573
|
+
}
|
|
6557
6574
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
6558
6575
|
if (exitCode !== 0) {
|
|
6559
6576
|
const blob = [chatText, detail].filter(Boolean).join("\n");
|
|
@@ -6564,13 +6581,17 @@ var Orchestrator = class {
|
|
|
6564
6581
|
const message = err instanceof Error ? err.message : String(err);
|
|
6565
6582
|
await syncThreadBranchFromGit(threadId).catch(() => void 0);
|
|
6566
6583
|
if (this.stoppedTurns.has(threadId)) {
|
|
6567
|
-
|
|
6568
|
-
|
|
6584
|
+
const stopped = writeLiveStatus(threadId, "stopped");
|
|
6585
|
+
if (stopped?.status === "stopped") {
|
|
6586
|
+
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
6587
|
+
}
|
|
6569
6588
|
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
6570
6589
|
} else {
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6590
|
+
const written = writeLiveStatus(threadId, "error", message);
|
|
6591
|
+
if (written?.status === "error") {
|
|
6592
|
+
this.emit({ type: "error", threadId, message });
|
|
6593
|
+
this.emit({ type: "status_changed", threadId, status: "error" });
|
|
6594
|
+
}
|
|
6574
6595
|
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
6575
6596
|
void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
|
|
6576
6597
|
}
|
|
@@ -6617,9 +6638,11 @@ var Orchestrator = class {
|
|
|
6617
6638
|
if (handle) handle.kill();
|
|
6618
6639
|
const proc = this.processes.get(`${thread.id}:agent`);
|
|
6619
6640
|
if (proc) proc.kill();
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6641
|
+
const stopped = writeLiveStatus(thread.id, "stopped") ?? readThread(thread.id) ?? thread;
|
|
6642
|
+
if (stopped.status === "stopped") {
|
|
6643
|
+
this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
|
|
6644
|
+
}
|
|
6645
|
+
return stopped;
|
|
6623
6646
|
}
|
|
6624
6647
|
async startDev(threadRef, scriptName) {
|
|
6625
6648
|
const thread = this.requireThread(threadRef);
|
|
@@ -6855,11 +6878,21 @@ var Orchestrator = class {
|
|
|
6855
6878
|
const off = this.on((event) => {
|
|
6856
6879
|
if (event.type === "turn_finished" && event.threadId === thread.id) {
|
|
6857
6880
|
off();
|
|
6858
|
-
|
|
6881
|
+
const latest = readThread(thread.id);
|
|
6882
|
+
if (!latest) {
|
|
6883
|
+
reject(new Error(`Thread not found: ${thread.id}`));
|
|
6884
|
+
return;
|
|
6885
|
+
}
|
|
6886
|
+
resolve(latest);
|
|
6859
6887
|
}
|
|
6860
6888
|
if (event.type === "error" && event.threadId === thread.id) {
|
|
6861
6889
|
off();
|
|
6862
|
-
|
|
6890
|
+
const latest = readThread(thread.id);
|
|
6891
|
+
if (!latest) {
|
|
6892
|
+
reject(new Error(`Thread not found: ${thread.id}`));
|
|
6893
|
+
return;
|
|
6894
|
+
}
|
|
6895
|
+
resolve(latest);
|
|
6863
6896
|
}
|
|
6864
6897
|
});
|
|
6865
6898
|
const timer = setInterval(() => {
|
|
@@ -6869,7 +6902,13 @@ var Orchestrator = class {
|
|
|
6869
6902
|
reject(new Error("wait_for_turn timed out"));
|
|
6870
6903
|
}
|
|
6871
6904
|
const current = readThread(thread.id);
|
|
6872
|
-
if (
|
|
6905
|
+
if (!current) {
|
|
6906
|
+
clearInterval(timer);
|
|
6907
|
+
off();
|
|
6908
|
+
reject(new Error(`Thread not found: ${thread.id}`));
|
|
6909
|
+
return;
|
|
6910
|
+
}
|
|
6911
|
+
if (!["running", "queued"].includes(current.status)) {
|
|
6873
6912
|
clearInterval(timer);
|
|
6874
6913
|
off();
|
|
6875
6914
|
resolve(current);
|
|
@@ -10444,10 +10483,10 @@ function slackInboundSuperseded(opts) {
|
|
|
10444
10483
|
function interruptSlackCoordinatorForInbound(msg, agent, log = () => void 0) {
|
|
10445
10484
|
const userId = msg.userId?.trim();
|
|
10446
10485
|
if (!userId) return false;
|
|
10447
|
-
const coordinator = ensureSlackCoordinator(msg.teamId, userId, agent);
|
|
10448
|
-
const fresh = readThread(coordinator.id) ?? coordinator;
|
|
10449
|
-
if (fresh.status !== "running" && fresh.status !== "queued") return false;
|
|
10450
10486
|
try {
|
|
10487
|
+
const coordinator = ensureSlackCoordinator(msg.teamId, userId, agent);
|
|
10488
|
+
const fresh = readThread(coordinator.id) ?? coordinator;
|
|
10489
|
+
if (fresh.status !== "running" && fresh.status !== "queued") return false;
|
|
10451
10490
|
getOrchestrator().stop(fresh.id, { clearQueue: true });
|
|
10452
10491
|
log(
|
|
10453
10492
|
`interrupt ${msg.kind} ${msg.ts} \u2192 coordinator ${fresh.id.slice(0, 8)} (${fresh.status})`
|
|
@@ -10487,7 +10526,7 @@ function slackReplyThreadTs(msg) {
|
|
|
10487
10526
|
if (msg.kind === "mention") return msg.ts;
|
|
10488
10527
|
return void 0;
|
|
10489
10528
|
}
|
|
10490
|
-
var SLACK_SEEN_REACTION = "
|
|
10529
|
+
var SLACK_SEEN_REACTION = "eyes";
|
|
10491
10530
|
function writeTokenForTeam(teamId) {
|
|
10492
10531
|
const workspaces = listSlackWorkspacesRaw();
|
|
10493
10532
|
const ws = workspaces.find((w) => w.team_id === teamId);
|
|
@@ -10653,7 +10692,7 @@ async function handleSlackInbound(msg, opts) {
|
|
|
10653
10692
|
return;
|
|
10654
10693
|
}
|
|
10655
10694
|
const after = readThread(fresh.id);
|
|
10656
|
-
if (after
|
|
10695
|
+
if (!after || after.status === "stopped" || after.status === "archived") {
|
|
10657
10696
|
log(`turn finished ${msg.ts} (interrupted, skip post)`);
|
|
10658
10697
|
return;
|
|
10659
10698
|
}
|
|
@@ -10664,6 +10703,11 @@ async function handleSlackInbound(msg, opts) {
|
|
|
10664
10703
|
return;
|
|
10665
10704
|
}
|
|
10666
10705
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
10706
|
+
const gone = /thread not found/i.test(errMsg);
|
|
10707
|
+
if (gone) {
|
|
10708
|
+
log(`turn finished ${msg.ts} (thread gone, skip post)`);
|
|
10709
|
+
return;
|
|
10710
|
+
}
|
|
10667
10711
|
const timedOut = /timed out|timeout/i.test(errMsg);
|
|
10668
10712
|
const errorReply = timedOut ? SLACK_LISTEN_TIMEOUT_REPLY : `Sideboard coordinator error: ${errMsg}`;
|
|
10669
10713
|
await postSlackReply(msg, errorReply, opts).catch((postErr) => {
|
package/dist/mcp/run-stdio.cjs
CHANGED
|
@@ -12124,23 +12124,24 @@ async function countUnpushedCommits(worktreePath) {
|
|
|
12124
12124
|
reject: false
|
|
12125
12125
|
});
|
|
12126
12126
|
const branch = head.stdout.trim();
|
|
12127
|
+
if (branch && branch !== "HEAD") {
|
|
12128
|
+
const remote = await git(
|
|
12129
|
+
["rev-list", "--count", `origin/${branch}..HEAD`],
|
|
12130
|
+
worktreePath,
|
|
12131
|
+
{ reject: false }
|
|
12132
|
+
);
|
|
12133
|
+
if (remote.exitCode === 0) {
|
|
12134
|
+
const n2 = Number(remote.stdout.trim());
|
|
12135
|
+
return Number.isFinite(n2) ? n2 : 0;
|
|
12136
|
+
}
|
|
12137
|
+
}
|
|
12127
12138
|
const upstream = await git(
|
|
12128
12139
|
["rev-list", "--count", "@{upstream}..HEAD"],
|
|
12129
12140
|
worktreePath,
|
|
12130
12141
|
{ reject: false }
|
|
12131
12142
|
);
|
|
12132
|
-
if (upstream.exitCode
|
|
12133
|
-
|
|
12134
|
-
return Number.isFinite(n2) ? n2 : 0;
|
|
12135
|
-
}
|
|
12136
|
-
if (!branch || branch === "HEAD") return 0;
|
|
12137
|
-
const remote = await git(
|
|
12138
|
-
["rev-list", "--count", `origin/${branch}..HEAD`],
|
|
12139
|
-
worktreePath,
|
|
12140
|
-
{ reject: false }
|
|
12141
|
-
);
|
|
12142
|
-
if (remote.exitCode !== 0) return 0;
|
|
12143
|
-
const n = Number(remote.stdout.trim());
|
|
12143
|
+
if (upstream.exitCode !== 0) return 0;
|
|
12144
|
+
const n = Number(upstream.stdout.trim());
|
|
12144
12145
|
return Number.isFinite(n) ? n : 0;
|
|
12145
12146
|
}
|
|
12146
12147
|
function splitCombinedDiff(stdout) {
|
|
@@ -13466,6 +13467,11 @@ function isPidAlive(pid) {
|
|
|
13466
13467
|
return false;
|
|
13467
13468
|
}
|
|
13468
13469
|
}
|
|
13470
|
+
function writeLiveStatus(threadId, status, lastError) {
|
|
13471
|
+
const latest = readThread(threadId);
|
|
13472
|
+
if (!latest || latest.status === "archived") return latest;
|
|
13473
|
+
return setStatus(threadId, status, lastError);
|
|
13474
|
+
}
|
|
13469
13475
|
var Orchestrator = class {
|
|
13470
13476
|
events = new import_node_events.EventEmitter();
|
|
13471
13477
|
processes = /* @__PURE__ */ new Map();
|
|
@@ -13771,6 +13777,9 @@ var Orchestrator = class {
|
|
|
13771
13777
|
}
|
|
13772
13778
|
async send(threadRef, prompt) {
|
|
13773
13779
|
const thread = this.requireThread(threadRef);
|
|
13780
|
+
if (thread.status === "archived") {
|
|
13781
|
+
throw new Error(`Thread is archived: ${thread.id}`);
|
|
13782
|
+
}
|
|
13774
13783
|
return withThreadLock(thread.id, async () => {
|
|
13775
13784
|
const current = this.requireThread(thread.id);
|
|
13776
13785
|
const queue = [...current.queue, prompt];
|
|
@@ -13864,7 +13873,7 @@ var Orchestrator = class {
|
|
|
13864
13873
|
break;
|
|
13865
13874
|
}
|
|
13866
13875
|
const thread = readThread(threadId);
|
|
13867
|
-
if (!thread || thread.queue.length === 0) {
|
|
13876
|
+
if (!thread || thread.status === "archived" || thread.queue.length === 0) {
|
|
13868
13877
|
if (thread && thread.status === "queued") {
|
|
13869
13878
|
setStatus(threadId, "idle");
|
|
13870
13879
|
this.emit({ type: "status_changed", threadId, status: "idle" });
|
|
@@ -13895,14 +13904,17 @@ var Orchestrator = class {
|
|
|
13895
13904
|
}
|
|
13896
13905
|
}
|
|
13897
13906
|
async runTurn(threadId, prompt) {
|
|
13898
|
-
|
|
13907
|
+
const existing = readThread(threadId);
|
|
13908
|
+
if (!existing || existing.status === "archived") return;
|
|
13909
|
+
let thread = existing;
|
|
13899
13910
|
if (isGlobalThread(thread) && thread.sessionId && orchestratorSessionPoisonedByBuiltins(thread)) {
|
|
13900
13911
|
thread = updateThread(threadId, { sessionId: null });
|
|
13901
13912
|
}
|
|
13913
|
+
const running = writeLiveStatus(threadId, "running");
|
|
13914
|
+
if (!running || running.status !== "running") return;
|
|
13902
13915
|
this.runningCount += 1;
|
|
13903
13916
|
const turnStartedAt = Date.now();
|
|
13904
13917
|
this.startingTurns.add(threadId);
|
|
13905
|
-
setStatus(threadId, "running");
|
|
13906
13918
|
this.emit({ type: "status_changed", threadId, status: "running" });
|
|
13907
13919
|
this.emit({ type: "turn_started", threadId, prompt });
|
|
13908
13920
|
try {
|
|
@@ -14173,8 +14185,8 @@ var Orchestrator = class {
|
|
|
14173
14185
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
14174
14186
|
});
|
|
14175
14187
|
}
|
|
14176
|
-
const afterTurn =
|
|
14177
|
-
if (afterTurn.planMode && afterTurn.worktreePath?.trim()) {
|
|
14188
|
+
const afterTurn = readThread(threadId);
|
|
14189
|
+
if (afterTurn && afterTurn.status !== "archived" && afterTurn.planMode && afterTurn.worktreePath?.trim()) {
|
|
14178
14190
|
const presented = extractPresentedPlan(parts);
|
|
14179
14191
|
const exited = parts.some(
|
|
14180
14192
|
(p) => p.type === "tool" && /exitplanmode/i.test(p.name)
|
|
@@ -14187,29 +14199,34 @@ var Orchestrator = class {
|
|
|
14187
14199
|
}
|
|
14188
14200
|
}
|
|
14189
14201
|
}
|
|
14190
|
-
if (afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
|
|
14202
|
+
if (afterTurn && afterTurn.status !== "archived" && afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
|
|
14191
14203
|
(p) => p.type === "tool" && /exitplanmode/i.test(p.name)
|
|
14192
14204
|
)) {
|
|
14193
14205
|
updateThread(threadId, { sessionId: null });
|
|
14194
14206
|
}
|
|
14195
14207
|
await syncThreadBranchFromGit(threadId);
|
|
14196
14208
|
if (this.stoppedTurns.has(threadId)) {
|
|
14197
|
-
|
|
14198
|
-
|
|
14209
|
+
const stopped = writeLiveStatus(threadId, "stopped");
|
|
14210
|
+
if (stopped?.status === "stopped") {
|
|
14211
|
+
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
14212
|
+
}
|
|
14199
14213
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
14200
14214
|
} else {
|
|
14201
14215
|
const failDetail = formatTurnExitError(exitCode, detail);
|
|
14202
14216
|
const explainedInChat = exitCode !== 0 && Boolean(chatText) && (looksLikeAgentFailureMessage(chatText) || failDetail && chatText.includes(failDetail.replace(/^exit\s*\d+:\s*/i, "").trim()));
|
|
14203
|
-
|
|
14217
|
+
const nextStatus = exitCode === 0 ? "idle" : "error";
|
|
14218
|
+
const written = writeLiveStatus(
|
|
14204
14219
|
threadId,
|
|
14205
|
-
|
|
14220
|
+
nextStatus,
|
|
14206
14221
|
exitCode === 0 || explainedInChat ? null : failDetail
|
|
14207
14222
|
);
|
|
14208
|
-
|
|
14209
|
-
|
|
14210
|
-
|
|
14211
|
-
|
|
14212
|
-
|
|
14223
|
+
if (written && written.status !== "archived") {
|
|
14224
|
+
this.emit({
|
|
14225
|
+
type: "status_changed",
|
|
14226
|
+
threadId,
|
|
14227
|
+
status: written.status
|
|
14228
|
+
});
|
|
14229
|
+
}
|
|
14213
14230
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
14214
14231
|
if (exitCode !== 0) {
|
|
14215
14232
|
const blob = [chatText, detail].filter(Boolean).join("\n");
|
|
@@ -14220,13 +14237,17 @@ var Orchestrator = class {
|
|
|
14220
14237
|
const message = err instanceof Error ? err.message : String(err);
|
|
14221
14238
|
await syncThreadBranchFromGit(threadId).catch(() => void 0);
|
|
14222
14239
|
if (this.stoppedTurns.has(threadId)) {
|
|
14223
|
-
|
|
14224
|
-
|
|
14240
|
+
const stopped = writeLiveStatus(threadId, "stopped");
|
|
14241
|
+
if (stopped?.status === "stopped") {
|
|
14242
|
+
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
14243
|
+
}
|
|
14225
14244
|
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
14226
14245
|
} else {
|
|
14227
|
-
|
|
14228
|
-
|
|
14229
|
-
|
|
14246
|
+
const written = writeLiveStatus(threadId, "error", message);
|
|
14247
|
+
if (written?.status === "error") {
|
|
14248
|
+
this.emit({ type: "error", threadId, message });
|
|
14249
|
+
this.emit({ type: "status_changed", threadId, status: "error" });
|
|
14250
|
+
}
|
|
14230
14251
|
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
14231
14252
|
void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
|
|
14232
14253
|
}
|
|
@@ -14273,9 +14294,11 @@ var Orchestrator = class {
|
|
|
14273
14294
|
if (handle) handle.kill();
|
|
14274
14295
|
const proc = this.processes.get(`${thread.id}:agent`);
|
|
14275
14296
|
if (proc) proc.kill();
|
|
14276
|
-
|
|
14277
|
-
|
|
14278
|
-
|
|
14297
|
+
const stopped = writeLiveStatus(thread.id, "stopped") ?? readThread(thread.id) ?? thread;
|
|
14298
|
+
if (stopped.status === "stopped") {
|
|
14299
|
+
this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
|
|
14300
|
+
}
|
|
14301
|
+
return stopped;
|
|
14279
14302
|
}
|
|
14280
14303
|
async startDev(threadRef, scriptName) {
|
|
14281
14304
|
const thread = this.requireThread(threadRef);
|
|
@@ -14511,11 +14534,21 @@ var Orchestrator = class {
|
|
|
14511
14534
|
const off = this.on((event) => {
|
|
14512
14535
|
if (event.type === "turn_finished" && event.threadId === thread.id) {
|
|
14513
14536
|
off();
|
|
14514
|
-
|
|
14537
|
+
const latest = readThread(thread.id);
|
|
14538
|
+
if (!latest) {
|
|
14539
|
+
reject(new Error(`Thread not found: ${thread.id}`));
|
|
14540
|
+
return;
|
|
14541
|
+
}
|
|
14542
|
+
resolve(latest);
|
|
14515
14543
|
}
|
|
14516
14544
|
if (event.type === "error" && event.threadId === thread.id) {
|
|
14517
14545
|
off();
|
|
14518
|
-
|
|
14546
|
+
const latest = readThread(thread.id);
|
|
14547
|
+
if (!latest) {
|
|
14548
|
+
reject(new Error(`Thread not found: ${thread.id}`));
|
|
14549
|
+
return;
|
|
14550
|
+
}
|
|
14551
|
+
resolve(latest);
|
|
14519
14552
|
}
|
|
14520
14553
|
});
|
|
14521
14554
|
const timer = setInterval(() => {
|
|
@@ -14525,7 +14558,13 @@ var Orchestrator = class {
|
|
|
14525
14558
|
reject(new Error("wait_for_turn timed out"));
|
|
14526
14559
|
}
|
|
14527
14560
|
const current = readThread(thread.id);
|
|
14528
|
-
if (
|
|
14561
|
+
if (!current) {
|
|
14562
|
+
clearInterval(timer);
|
|
14563
|
+
off();
|
|
14564
|
+
reject(new Error(`Thread not found: ${thread.id}`));
|
|
14565
|
+
return;
|
|
14566
|
+
}
|
|
14567
|
+
if (!["running", "queued"].includes(current.status)) {
|
|
14529
14568
|
clearInterval(timer);
|
|
14530
14569
|
off();
|
|
14531
14570
|
resolve(current);
|
package/dist/mcp/run-stdio.js
CHANGED
|
@@ -3300,23 +3300,24 @@ async function countUnpushedCommits(worktreePath) {
|
|
|
3300
3300
|
reject: false
|
|
3301
3301
|
});
|
|
3302
3302
|
const branch = head.stdout.trim();
|
|
3303
|
+
if (branch && branch !== "HEAD") {
|
|
3304
|
+
const remote = await git(
|
|
3305
|
+
["rev-list", "--count", `origin/${branch}..HEAD`],
|
|
3306
|
+
worktreePath,
|
|
3307
|
+
{ reject: false }
|
|
3308
|
+
);
|
|
3309
|
+
if (remote.exitCode === 0) {
|
|
3310
|
+
const n2 = Number(remote.stdout.trim());
|
|
3311
|
+
return Number.isFinite(n2) ? n2 : 0;
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3303
3314
|
const upstream = await git(
|
|
3304
3315
|
["rev-list", "--count", "@{upstream}..HEAD"],
|
|
3305
3316
|
worktreePath,
|
|
3306
3317
|
{ reject: false }
|
|
3307
3318
|
);
|
|
3308
|
-
if (upstream.exitCode
|
|
3309
|
-
|
|
3310
|
-
return Number.isFinite(n2) ? n2 : 0;
|
|
3311
|
-
}
|
|
3312
|
-
if (!branch || branch === "HEAD") return 0;
|
|
3313
|
-
const remote = await git(
|
|
3314
|
-
["rev-list", "--count", `origin/${branch}..HEAD`],
|
|
3315
|
-
worktreePath,
|
|
3316
|
-
{ reject: false }
|
|
3317
|
-
);
|
|
3318
|
-
if (remote.exitCode !== 0) return 0;
|
|
3319
|
-
const n = Number(remote.stdout.trim());
|
|
3319
|
+
if (upstream.exitCode !== 0) return 0;
|
|
3320
|
+
const n = Number(upstream.stdout.trim());
|
|
3320
3321
|
return Number.isFinite(n) ? n : 0;
|
|
3321
3322
|
}
|
|
3322
3323
|
function splitCombinedDiff(stdout) {
|
|
@@ -4626,6 +4627,11 @@ function isPidAlive(pid) {
|
|
|
4626
4627
|
return false;
|
|
4627
4628
|
}
|
|
4628
4629
|
}
|
|
4630
|
+
function writeLiveStatus(threadId, status, lastError) {
|
|
4631
|
+
const latest = readThread(threadId);
|
|
4632
|
+
if (!latest || latest.status === "archived") return latest;
|
|
4633
|
+
return setStatus(threadId, status, lastError);
|
|
4634
|
+
}
|
|
4629
4635
|
var Orchestrator = class {
|
|
4630
4636
|
events = new EventEmitter();
|
|
4631
4637
|
processes = /* @__PURE__ */ new Map();
|
|
@@ -4931,6 +4937,9 @@ var Orchestrator = class {
|
|
|
4931
4937
|
}
|
|
4932
4938
|
async send(threadRef, prompt) {
|
|
4933
4939
|
const thread = this.requireThread(threadRef);
|
|
4940
|
+
if (thread.status === "archived") {
|
|
4941
|
+
throw new Error(`Thread is archived: ${thread.id}`);
|
|
4942
|
+
}
|
|
4934
4943
|
return withThreadLock(thread.id, async () => {
|
|
4935
4944
|
const current = this.requireThread(thread.id);
|
|
4936
4945
|
const queue = [...current.queue, prompt];
|
|
@@ -5024,7 +5033,7 @@ var Orchestrator = class {
|
|
|
5024
5033
|
break;
|
|
5025
5034
|
}
|
|
5026
5035
|
const thread = readThread(threadId);
|
|
5027
|
-
if (!thread || thread.queue.length === 0) {
|
|
5036
|
+
if (!thread || thread.status === "archived" || thread.queue.length === 0) {
|
|
5028
5037
|
if (thread && thread.status === "queued") {
|
|
5029
5038
|
setStatus(threadId, "idle");
|
|
5030
5039
|
this.emit({ type: "status_changed", threadId, status: "idle" });
|
|
@@ -5055,14 +5064,17 @@ var Orchestrator = class {
|
|
|
5055
5064
|
}
|
|
5056
5065
|
}
|
|
5057
5066
|
async runTurn(threadId, prompt) {
|
|
5058
|
-
|
|
5067
|
+
const existing = readThread(threadId);
|
|
5068
|
+
if (!existing || existing.status === "archived") return;
|
|
5069
|
+
let thread = existing;
|
|
5059
5070
|
if (isGlobalThread(thread) && thread.sessionId && orchestratorSessionPoisonedByBuiltins(thread)) {
|
|
5060
5071
|
thread = updateThread(threadId, { sessionId: null });
|
|
5061
5072
|
}
|
|
5073
|
+
const running = writeLiveStatus(threadId, "running");
|
|
5074
|
+
if (!running || running.status !== "running") return;
|
|
5062
5075
|
this.runningCount += 1;
|
|
5063
5076
|
const turnStartedAt = Date.now();
|
|
5064
5077
|
this.startingTurns.add(threadId);
|
|
5065
|
-
setStatus(threadId, "running");
|
|
5066
5078
|
this.emit({ type: "status_changed", threadId, status: "running" });
|
|
5067
5079
|
this.emit({ type: "turn_started", threadId, prompt });
|
|
5068
5080
|
try {
|
|
@@ -5333,8 +5345,8 @@ var Orchestrator = class {
|
|
|
5333
5345
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
5334
5346
|
});
|
|
5335
5347
|
}
|
|
5336
|
-
const afterTurn =
|
|
5337
|
-
if (afterTurn.planMode && afterTurn.worktreePath?.trim()) {
|
|
5348
|
+
const afterTurn = readThread(threadId);
|
|
5349
|
+
if (afterTurn && afterTurn.status !== "archived" && afterTurn.planMode && afterTurn.worktreePath?.trim()) {
|
|
5338
5350
|
const presented = extractPresentedPlan(parts);
|
|
5339
5351
|
const exited = parts.some(
|
|
5340
5352
|
(p) => p.type === "tool" && /exitplanmode/i.test(p.name)
|
|
@@ -5347,29 +5359,34 @@ var Orchestrator = class {
|
|
|
5347
5359
|
}
|
|
5348
5360
|
}
|
|
5349
5361
|
}
|
|
5350
|
-
if (afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
|
|
5362
|
+
if (afterTurn && afterTurn.status !== "archived" && afterTurn.planMode && afterTurn.agent === "claude" && parts.some(
|
|
5351
5363
|
(p) => p.type === "tool" && /exitplanmode/i.test(p.name)
|
|
5352
5364
|
)) {
|
|
5353
5365
|
updateThread(threadId, { sessionId: null });
|
|
5354
5366
|
}
|
|
5355
5367
|
await syncThreadBranchFromGit(threadId);
|
|
5356
5368
|
if (this.stoppedTurns.has(threadId)) {
|
|
5357
|
-
|
|
5358
|
-
|
|
5369
|
+
const stopped = writeLiveStatus(threadId, "stopped");
|
|
5370
|
+
if (stopped?.status === "stopped") {
|
|
5371
|
+
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
5372
|
+
}
|
|
5359
5373
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
5360
5374
|
} else {
|
|
5361
5375
|
const failDetail = formatTurnExitError(exitCode, detail);
|
|
5362
5376
|
const explainedInChat = exitCode !== 0 && Boolean(chatText) && (looksLikeAgentFailureMessage(chatText) || failDetail && chatText.includes(failDetail.replace(/^exit\s*\d+:\s*/i, "").trim()));
|
|
5363
|
-
|
|
5377
|
+
const nextStatus = exitCode === 0 ? "idle" : "error";
|
|
5378
|
+
const written = writeLiveStatus(
|
|
5364
5379
|
threadId,
|
|
5365
|
-
|
|
5380
|
+
nextStatus,
|
|
5366
5381
|
exitCode === 0 || explainedInChat ? null : failDetail
|
|
5367
5382
|
);
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5383
|
+
if (written && written.status !== "archived") {
|
|
5384
|
+
this.emit({
|
|
5385
|
+
type: "status_changed",
|
|
5386
|
+
threadId,
|
|
5387
|
+
status: written.status
|
|
5388
|
+
});
|
|
5389
|
+
}
|
|
5373
5390
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
5374
5391
|
if (exitCode !== 0) {
|
|
5375
5392
|
const blob = [chatText, detail].filter(Boolean).join("\n");
|
|
@@ -5380,13 +5397,17 @@ var Orchestrator = class {
|
|
|
5380
5397
|
const message = err instanceof Error ? err.message : String(err);
|
|
5381
5398
|
await syncThreadBranchFromGit(threadId).catch(() => void 0);
|
|
5382
5399
|
if (this.stoppedTurns.has(threadId)) {
|
|
5383
|
-
|
|
5384
|
-
|
|
5400
|
+
const stopped = writeLiveStatus(threadId, "stopped");
|
|
5401
|
+
if (stopped?.status === "stopped") {
|
|
5402
|
+
this.emit({ type: "status_changed", threadId, status: "stopped" });
|
|
5403
|
+
}
|
|
5385
5404
|
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
5386
5405
|
} else {
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5406
|
+
const written = writeLiveStatus(threadId, "error", message);
|
|
5407
|
+
if (written?.status === "error") {
|
|
5408
|
+
this.emit({ type: "error", threadId, message });
|
|
5409
|
+
this.emit({ type: "status_changed", threadId, status: "error" });
|
|
5410
|
+
}
|
|
5390
5411
|
this.emit({ type: "turn_finished", threadId, exitCode: 1 });
|
|
5391
5412
|
void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
|
|
5392
5413
|
}
|
|
@@ -5433,9 +5454,11 @@ var Orchestrator = class {
|
|
|
5433
5454
|
if (handle) handle.kill();
|
|
5434
5455
|
const proc = this.processes.get(`${thread.id}:agent`);
|
|
5435
5456
|
if (proc) proc.kill();
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5457
|
+
const stopped = writeLiveStatus(thread.id, "stopped") ?? readThread(thread.id) ?? thread;
|
|
5458
|
+
if (stopped.status === "stopped") {
|
|
5459
|
+
this.emit({ type: "status_changed", threadId: thread.id, status: "stopped" });
|
|
5460
|
+
}
|
|
5461
|
+
return stopped;
|
|
5439
5462
|
}
|
|
5440
5463
|
async startDev(threadRef, scriptName) {
|
|
5441
5464
|
const thread = this.requireThread(threadRef);
|
|
@@ -5671,11 +5694,21 @@ var Orchestrator = class {
|
|
|
5671
5694
|
const off = this.on((event) => {
|
|
5672
5695
|
if (event.type === "turn_finished" && event.threadId === thread.id) {
|
|
5673
5696
|
off();
|
|
5674
|
-
|
|
5697
|
+
const latest = readThread(thread.id);
|
|
5698
|
+
if (!latest) {
|
|
5699
|
+
reject(new Error(`Thread not found: ${thread.id}`));
|
|
5700
|
+
return;
|
|
5701
|
+
}
|
|
5702
|
+
resolve(latest);
|
|
5675
5703
|
}
|
|
5676
5704
|
if (event.type === "error" && event.threadId === thread.id) {
|
|
5677
5705
|
off();
|
|
5678
|
-
|
|
5706
|
+
const latest = readThread(thread.id);
|
|
5707
|
+
if (!latest) {
|
|
5708
|
+
reject(new Error(`Thread not found: ${thread.id}`));
|
|
5709
|
+
return;
|
|
5710
|
+
}
|
|
5711
|
+
resolve(latest);
|
|
5679
5712
|
}
|
|
5680
5713
|
});
|
|
5681
5714
|
const timer = setInterval(() => {
|
|
@@ -5685,7 +5718,13 @@ var Orchestrator = class {
|
|
|
5685
5718
|
reject(new Error("wait_for_turn timed out"));
|
|
5686
5719
|
}
|
|
5687
5720
|
const current = readThread(thread.id);
|
|
5688
|
-
if (
|
|
5721
|
+
if (!current) {
|
|
5722
|
+
clearInterval(timer);
|
|
5723
|
+
off();
|
|
5724
|
+
reject(new Error(`Thread not found: ${thread.id}`));
|
|
5725
|
+
return;
|
|
5726
|
+
}
|
|
5727
|
+
if (!["running", "queued"].includes(current.status)) {
|
|
5689
5728
|
clearInterval(timer);
|
|
5690
5729
|
off();
|
|
5691
5730
|
resolve(current);
|