clisbot 0.1.36 → 0.1.38
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/main.js +392 -89
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -54623,6 +54623,12 @@ function parseCliArgs(argv) {
|
|
|
54623
54623
|
args: args.slice(1)
|
|
54624
54624
|
};
|
|
54625
54625
|
}
|
|
54626
|
+
if (command === "runner") {
|
|
54627
|
+
return {
|
|
54628
|
+
name: "runner",
|
|
54629
|
+
args: args.slice(1)
|
|
54630
|
+
};
|
|
54631
|
+
}
|
|
54626
54632
|
if (command === "pairing") {
|
|
54627
54633
|
return {
|
|
54628
54634
|
name: "pairing",
|
|
@@ -54685,6 +54691,7 @@ function renderCliHelp() {
|
|
|
54685
54691
|
" clisbot message <subcommand>",
|
|
54686
54692
|
" clisbot agents <subcommand>",
|
|
54687
54693
|
" clisbot auth <subcommand>",
|
|
54694
|
+
" clisbot runner <subcommand>",
|
|
54688
54695
|
" clisbot pairing <subcommand>",
|
|
54689
54696
|
" clisbot init [--cli <codex|claude|gemini>] [--bot-type <personal|team>] [--persist]",
|
|
54690
54697
|
" [--slack-account <id> --slack-app-token <ENV_NAME|${ENV_NAME}|literal> --slack-bot-token <ENV_NAME|${ENV_NAME}|literal>]...",
|
|
@@ -54728,6 +54735,8 @@ function renderCliHelp() {
|
|
|
54728
54735
|
" agents Manage configured agents and top-level bindings.",
|
|
54729
54736
|
" See `clisbot agents --help` for focused add/bootstrap/binding help.",
|
|
54730
54737
|
" auth Manage app and agent auth roles, principals, and permissions in config. See `clisbot auth --help`.",
|
|
54738
|
+
" runner Validate and inspect runner-facing operator contracts such as `runner smoke`.",
|
|
54739
|
+
" See `clisbot runner --help` for the current smoke surface.",
|
|
54731
54740
|
" pairing Run the pairing control CLI. See `clisbot pairing --help`.",
|
|
54732
54741
|
` init Seed ${configPath} and optionally create the first agent without starting clisbot.`,
|
|
54733
54742
|
" See `clisbot init --help` for bootstrap-focused flags and examples.",
|
|
@@ -64255,6 +64264,8 @@ function resolveAgentTargetInternal(loadedConfig, target) {
|
|
|
64255
64264
|
}
|
|
64256
64265
|
|
|
64257
64266
|
// src/agents/job-queue.ts
|
|
64267
|
+
var QUEUE_PENDING_POLL_INTERVAL_MS = 25;
|
|
64268
|
+
|
|
64258
64269
|
class ClearedQueuedTaskError extends Error {
|
|
64259
64270
|
constructor() {
|
|
64260
64271
|
super("Queued task was cleared before execution.");
|
|
@@ -64282,6 +64293,7 @@ class AgentJobQueue {
|
|
|
64282
64293
|
text: options.text,
|
|
64283
64294
|
createdAt: Date.now(),
|
|
64284
64295
|
status: "pending",
|
|
64296
|
+
canStart: options.canStart,
|
|
64285
64297
|
task,
|
|
64286
64298
|
resolve,
|
|
64287
64299
|
reject,
|
|
@@ -64349,6 +64361,10 @@ class AgentJobQueue {
|
|
|
64349
64361
|
if (!nextEntry) {
|
|
64350
64362
|
break;
|
|
64351
64363
|
}
|
|
64364
|
+
if (nextEntry.canStart && !await nextEntry.canStart()) {
|
|
64365
|
+
await sleep(QUEUE_PENDING_POLL_INTERVAL_MS);
|
|
64366
|
+
continue;
|
|
64367
|
+
}
|
|
64352
64368
|
nextEntry.status = "running";
|
|
64353
64369
|
try {
|
|
64354
64370
|
nextEntry.resolve(await nextEntry.task());
|
|
@@ -64609,12 +64625,22 @@ function isInterruptStatusLine(line) {
|
|
|
64609
64625
|
}
|
|
64610
64626
|
return CODEX_WORKING_STATUS_PATTERN.test(trimmed) || CODEX_INTERRUPT_FOOTER_PATTERN.test(trimmed);
|
|
64611
64627
|
}
|
|
64628
|
+
function isActiveTimerStatusLine(line) {
|
|
64629
|
+
const trimmed = line.trim();
|
|
64630
|
+
if (!trimmed) {
|
|
64631
|
+
return false;
|
|
64632
|
+
}
|
|
64633
|
+
return isInterruptStatusLine(trimmed) || GEMINI_THINKING_STATUS_PATTERN.test(trimmed) || CLAUDE_TIMER_FOOTER_PATTERN.test(trimmed);
|
|
64634
|
+
}
|
|
64612
64635
|
function isTimerDrivenStatusLine(line) {
|
|
64613
64636
|
const trimmed = line.trim();
|
|
64614
64637
|
if (!trimmed) {
|
|
64615
64638
|
return false;
|
|
64616
64639
|
}
|
|
64617
|
-
return
|
|
64640
|
+
return isActiveTimerStatusLine(trimmed) || CLAUDE_WORKED_STATUS_PATTERN.test(trimmed);
|
|
64641
|
+
}
|
|
64642
|
+
function hasActiveTimerStatus(snapshot) {
|
|
64643
|
+
return splitNormalizedLines(snapshot).some((line) => isActiveTimerStatusLine(line));
|
|
64618
64644
|
}
|
|
64619
64645
|
function shouldDropCodexChromeLine(line) {
|
|
64620
64646
|
const trimmed = line.trim();
|
|
@@ -66145,8 +66171,10 @@ var FIRST_OUTPUT_POLL_INTERVAL_MS = 250;
|
|
|
66145
66171
|
async function monitorTmuxRun(params) {
|
|
66146
66172
|
let previousSnapshot = params.initialSnapshot;
|
|
66147
66173
|
let previousRunningSnapshot = "";
|
|
66148
|
-
let
|
|
66174
|
+
let lastPaneChangeAt = params.startedAt;
|
|
66149
66175
|
let sawActivity = false;
|
|
66176
|
+
let sawPaneChange = false;
|
|
66177
|
+
let sawPromptSubmission = Boolean(params.prompt);
|
|
66150
66178
|
let detachedNotified = params.detachedAlready;
|
|
66151
66179
|
let firstMeaningfulDeltaLogged = false;
|
|
66152
66180
|
let noOutputThresholdLogged = false;
|
|
@@ -66162,6 +66190,8 @@ async function monitorTmuxRun(params) {
|
|
|
66162
66190
|
promptSubmitDelayMs: params.promptSubmitDelayMs,
|
|
66163
66191
|
timingContext: params.timingContext
|
|
66164
66192
|
});
|
|
66193
|
+
sawPromptSubmission = true;
|
|
66194
|
+
lastPaneChangeAt = Date.now();
|
|
66165
66195
|
await params.onPromptSubmitted?.();
|
|
66166
66196
|
logLatencyDebug("tmux-submit-complete", params.timingContext, {
|
|
66167
66197
|
sessionName: params.sessionName,
|
|
@@ -66173,11 +66203,16 @@ async function monitorTmuxRun(params) {
|
|
|
66173
66203
|
await sleep(sawActivity ? params.updateIntervalMs : Math.min(params.updateIntervalMs, FIRST_OUTPUT_POLL_INTERVAL_MS));
|
|
66174
66204
|
const snapshot = normalizePaneText(await params.tmux.capturePane(params.sessionName, params.captureLines));
|
|
66175
66205
|
const now = Date.now();
|
|
66206
|
+
const paneChanged = snapshot !== previousSnapshot;
|
|
66207
|
+
if (paneChanged) {
|
|
66208
|
+
lastPaneChangeAt = now;
|
|
66209
|
+
sawPaneChange = true;
|
|
66210
|
+
}
|
|
66211
|
+
const hasActiveTimer = hasActiveTimerStatus(snapshot);
|
|
66176
66212
|
const runningSnapshot = params.initialSnapshot ? deriveRunningInteractionText(params.initialSnapshot, snapshot) : deriveRunningInteractionSnapshot(snapshot);
|
|
66177
66213
|
previousSnapshot = snapshot;
|
|
66178
66214
|
if (runningSnapshot && runningSnapshot !== previousRunningSnapshot) {
|
|
66179
66215
|
previousRunningSnapshot = runningSnapshot;
|
|
66180
|
-
lastActivityAt = now;
|
|
66181
66216
|
sawActivity = true;
|
|
66182
66217
|
if (!firstMeaningfulDeltaLogged) {
|
|
66183
66218
|
firstMeaningfulDeltaLogged = true;
|
|
@@ -66200,7 +66235,7 @@ async function monitorTmuxRun(params) {
|
|
|
66200
66235
|
initialSnapshot: params.initialSnapshot
|
|
66201
66236
|
});
|
|
66202
66237
|
}
|
|
66203
|
-
if (sawActivity && now -
|
|
66238
|
+
if (!hasActiveTimer && (sawActivity || sawPaneChange || sawPromptSubmission) && now - lastPaneChangeAt >= params.idleTimeoutMs) {
|
|
66204
66239
|
await params.onCompleted({
|
|
66205
66240
|
snapshot: deriveInteractionText(params.initialSnapshot, previousSnapshot),
|
|
66206
66241
|
fullSnapshot: previousSnapshot,
|
|
@@ -66221,9 +66256,20 @@ async function monitorTmuxRun(params) {
|
|
|
66221
66256
|
// src/agents/session-service.ts
|
|
66222
66257
|
var OBSERVER_RETRYABLE_FAILURE_LIMIT = 3;
|
|
66223
66258
|
var DETACHED_OBSERVER_INTERVAL_MS = 5 * 60000;
|
|
66259
|
+
var TMUX_MISSING_SESSION_PATTERN3 = /(?:can't find session:|no server running on )/i;
|
|
66260
|
+
var TMUX_SERVER_UNAVAILABLE_PATTERN3 = /(?:No such file or directory|error connecting to|failed to connect to server)/i;
|
|
66224
66261
|
function formatObserverError(error) {
|
|
66225
66262
|
return error instanceof Error ? error.stack ?? error.message : String(error);
|
|
66226
66263
|
}
|
|
66264
|
+
function isMissingTmuxSessionError2(error) {
|
|
66265
|
+
return error instanceof Error && TMUX_MISSING_SESSION_PATTERN3.test(error.message);
|
|
66266
|
+
}
|
|
66267
|
+
function isTmuxServerUnavailableError2(error) {
|
|
66268
|
+
return error instanceof Error && TMUX_SERVER_UNAVAILABLE_PATTERN3.test(error.message);
|
|
66269
|
+
}
|
|
66270
|
+
function isBootstrapSessionLostError2(error) {
|
|
66271
|
+
return error instanceof Error && /tmux session disappeared before startup finished|tmux server became unavailable before startup finished/i.test(error.message);
|
|
66272
|
+
}
|
|
66227
66273
|
function listObserverErrorCodes(error) {
|
|
66228
66274
|
const codes = new Set;
|
|
66229
66275
|
const visit = (value) => {
|
|
@@ -66295,6 +66341,7 @@ class SessionService {
|
|
|
66295
66341
|
runnerSessions;
|
|
66296
66342
|
resolveTarget;
|
|
66297
66343
|
activeRuns = new Map;
|
|
66344
|
+
nextRunId = 1;
|
|
66298
66345
|
stopping = false;
|
|
66299
66346
|
constructor(tmux, sessionState, runnerSessions, resolveTarget) {
|
|
66300
66347
|
this.tmux = tmux;
|
|
@@ -66308,42 +66355,10 @@ class SessionService {
|
|
|
66308
66355
|
if (!entry.runtime || entry.runtime.state === "idle") {
|
|
66309
66356
|
continue;
|
|
66310
66357
|
}
|
|
66311
|
-
|
|
66358
|
+
await this.reconcilePersistedActiveRun({
|
|
66312
66359
|
agentId: entry.agentId,
|
|
66313
66360
|
sessionKey: entry.sessionKey
|
|
66314
66361
|
});
|
|
66315
|
-
if (!await this.tmux.hasSession(resolved.sessionName)) {
|
|
66316
|
-
await this.sessionState.setSessionRuntime(resolved, {
|
|
66317
|
-
state: "idle"
|
|
66318
|
-
});
|
|
66319
|
-
continue;
|
|
66320
|
-
}
|
|
66321
|
-
const fullSnapshot = normalizePaneText(await this.tmux.capturePane(resolved.sessionName, resolved.stream.captureLines));
|
|
66322
|
-
const initialResult = createDeferred();
|
|
66323
|
-
const update = this.createRunUpdate({
|
|
66324
|
-
resolved,
|
|
66325
|
-
status: entry.runtime.state === "detached" ? "detached" : "running",
|
|
66326
|
-
snapshot: deriveInteractionText("", fullSnapshot),
|
|
66327
|
-
fullSnapshot,
|
|
66328
|
-
initialSnapshot: "",
|
|
66329
|
-
note: entry.runtime.state === "detached" ? this.buildDetachedNote(resolved) : undefined
|
|
66330
|
-
});
|
|
66331
|
-
this.activeRuns.set(resolved.sessionKey, {
|
|
66332
|
-
resolved,
|
|
66333
|
-
observers: new Map,
|
|
66334
|
-
observerFailures: new Map,
|
|
66335
|
-
initialResult,
|
|
66336
|
-
latestUpdate: update,
|
|
66337
|
-
steeringReady: true,
|
|
66338
|
-
startedAt: entry.runtime.startedAt ?? Date.now()
|
|
66339
|
-
});
|
|
66340
|
-
this.startRunMonitor(resolved.sessionKey, {
|
|
66341
|
-
prompt: undefined,
|
|
66342
|
-
initialSnapshot: fullSnapshot,
|
|
66343
|
-
startedAt: entry.runtime.startedAt ?? Date.now(),
|
|
66344
|
-
detachedAlready: entry.runtime.state === "detached",
|
|
66345
|
-
timingContext: undefined
|
|
66346
|
-
});
|
|
66347
66362
|
}
|
|
66348
66363
|
}
|
|
66349
66364
|
async executePrompt(target, prompt, observer, options = {}) {
|
|
@@ -66354,21 +66369,15 @@ class SessionService {
|
|
|
66354
66369
|
if (existingActiveRun) {
|
|
66355
66370
|
throw new ActiveRunInProgressError(existingActiveRun.latestUpdate);
|
|
66356
66371
|
}
|
|
66357
|
-
const
|
|
66358
|
-
if (
|
|
66359
|
-
|
|
66360
|
-
throw new ActiveRunInProgressError(this.createRunUpdate({
|
|
66361
|
-
resolved: resolvedExisting,
|
|
66362
|
-
status: existingEntry.runtime.state === "detached" ? "detached" : "running",
|
|
66363
|
-
snapshot: "",
|
|
66364
|
-
fullSnapshot: "",
|
|
66365
|
-
initialSnapshot: "",
|
|
66366
|
-
note: existingEntry.runtime.state === "detached" ? this.buildDetachedNote(resolvedExisting) : "This session already has an active run. Use `/attach`, `/watch every 30s`, or `/stop` before sending a new prompt."
|
|
66367
|
-
}));
|
|
66372
|
+
const reconciledRun = await this.reconcilePersistedActiveRun(target);
|
|
66373
|
+
if (reconciledRun) {
|
|
66374
|
+
throw new ActiveRunInProgressError(reconciledRun.latestUpdate);
|
|
66368
66375
|
}
|
|
66369
66376
|
const initialResult = createDeferred();
|
|
66370
66377
|
const provisionalResolved = this.resolveTarget(target);
|
|
66378
|
+
const runId = this.allocateRunId();
|
|
66371
66379
|
this.activeRuns.set(provisionalResolved.sessionKey, {
|
|
66380
|
+
runId,
|
|
66372
66381
|
resolved: provisionalResolved,
|
|
66373
66382
|
observers: new Map([[observer.id, { ...observer }]]),
|
|
66374
66383
|
observerFailures: new Map,
|
|
@@ -66416,6 +66425,7 @@ class SessionService {
|
|
|
66416
66425
|
startedAt
|
|
66417
66426
|
});
|
|
66418
66427
|
this.startRunMonitor(resolved.sessionKey, {
|
|
66428
|
+
runId,
|
|
66419
66429
|
prompt,
|
|
66420
66430
|
initialSnapshot,
|
|
66421
66431
|
startedAt,
|
|
@@ -66424,12 +66434,12 @@ class SessionService {
|
|
|
66424
66434
|
});
|
|
66425
66435
|
return initialResult.promise;
|
|
66426
66436
|
} catch (error) {
|
|
66427
|
-
await this.failActiveRun(provisionalResolved.sessionKey, error);
|
|
66437
|
+
await this.failActiveRun(provisionalResolved.sessionKey, runId, error);
|
|
66428
66438
|
throw error;
|
|
66429
66439
|
}
|
|
66430
66440
|
}
|
|
66431
66441
|
async observeRun(target, observer) {
|
|
66432
|
-
const existingRun = this.activeRuns.get(target.sessionKey);
|
|
66442
|
+
const existingRun = this.activeRuns.get(target.sessionKey) ?? await this.reconcilePersistedActiveRun(target);
|
|
66433
66443
|
if (existingRun) {
|
|
66434
66444
|
existingRun.observers.set(observer.id, {
|
|
66435
66445
|
...observer
|
|
@@ -66592,8 +66602,8 @@ class SessionService {
|
|
|
66592
66602
|
}
|
|
66593
66603
|
}
|
|
66594
66604
|
}
|
|
66595
|
-
async finishActiveRun(sessionKey, update) {
|
|
66596
|
-
const run = this.
|
|
66605
|
+
async finishActiveRun(sessionKey, runId, update) {
|
|
66606
|
+
const run = this.getRun(sessionKey, runId);
|
|
66597
66607
|
if (!run) {
|
|
66598
66608
|
return;
|
|
66599
66609
|
}
|
|
@@ -66604,8 +66614,8 @@ class SessionService {
|
|
|
66604
66614
|
run.initialResult.resolve(update);
|
|
66605
66615
|
this.activeRuns.delete(run.resolved.sessionKey);
|
|
66606
66616
|
}
|
|
66607
|
-
async failActiveRun(sessionKey, error) {
|
|
66608
|
-
const run = this.
|
|
66617
|
+
async failActiveRun(sessionKey, runId, error) {
|
|
66618
|
+
const run = this.getRun(sessionKey, runId);
|
|
66609
66619
|
if (!run) {
|
|
66610
66620
|
return;
|
|
66611
66621
|
}
|
|
@@ -66631,7 +66641,7 @@ class SessionService {
|
|
|
66631
66641
|
if (!this.runnerSessions.canRecoverMidRun(error)) {
|
|
66632
66642
|
return false;
|
|
66633
66643
|
}
|
|
66634
|
-
const run = this.
|
|
66644
|
+
const run = this.getRun(sessionKey, params.runId);
|
|
66635
66645
|
if (!run) {
|
|
66636
66646
|
return true;
|
|
66637
66647
|
}
|
|
@@ -66648,7 +66658,7 @@ class SessionService {
|
|
|
66648
66658
|
}));
|
|
66649
66659
|
try {
|
|
66650
66660
|
const recovered = await this.runnerSessions.reopenRunContext(target, params.timingContext);
|
|
66651
|
-
const currentRun = this.
|
|
66661
|
+
const currentRun = this.getRun(sessionKey, params.runId);
|
|
66652
66662
|
if (!currentRun) {
|
|
66653
66663
|
return true;
|
|
66654
66664
|
}
|
|
@@ -66664,6 +66674,7 @@ class SessionService {
|
|
|
66664
66674
|
});
|
|
66665
66675
|
await this.notifyRunObservers(currentRun, currentRun.latestUpdate);
|
|
66666
66676
|
this.startRunMonitor(sessionKey, {
|
|
66677
|
+
runId: currentRun.runId,
|
|
66667
66678
|
prompt: MID_RUN_RECOVERY_CONTINUE_PROMPT,
|
|
66668
66679
|
initialSnapshot: recovered.initialSnapshot,
|
|
66669
66680
|
startedAt: currentRun.startedAt,
|
|
@@ -66676,11 +66687,12 @@ class SessionService {
|
|
|
66676
66687
|
} catch (reopenError) {
|
|
66677
66688
|
if (recoveryAttempt < MID_RUN_RECOVERY_MAX_ATTEMPTS && this.runnerSessions.canRecoverMidRun(reopenError)) {
|
|
66678
66689
|
return await this.recoverLostMidRun(sessionKey, {
|
|
66690
|
+
runId: params.runId,
|
|
66679
66691
|
timingContext: params.timingContext,
|
|
66680
66692
|
recoveryAttempt: recoveryAttempt + 1
|
|
66681
66693
|
}, reopenError);
|
|
66682
66694
|
}
|
|
66683
|
-
const currentRun = this.
|
|
66695
|
+
const currentRun = this.getRun(sessionKey, params.runId);
|
|
66684
66696
|
if (!currentRun) {
|
|
66685
66697
|
return true;
|
|
66686
66698
|
}
|
|
@@ -66688,15 +66700,15 @@ class SessionService {
|
|
|
66688
66700
|
try {
|
|
66689
66701
|
await this.runnerSessions.startFreshSession(target, params.timingContext);
|
|
66690
66702
|
} catch (freshError) {
|
|
66691
|
-
await this.failActiveRun(sessionKey, await this.runnerSessions.mapRunError(freshError, currentRun.resolved.sessionName, currentRun.latestUpdate.fullSnapshot));
|
|
66703
|
+
await this.failActiveRun(sessionKey, currentRun.runId, await this.runnerSessions.mapRunError(freshError, currentRun.resolved.sessionName, currentRun.latestUpdate.fullSnapshot));
|
|
66692
66704
|
return true;
|
|
66693
66705
|
}
|
|
66694
|
-
await this.failActiveRun(sessionKey, new Error(buildRunRecoveryNote("fresh-required")));
|
|
66706
|
+
await this.failActiveRun(sessionKey, currentRun.runId, new Error(buildRunRecoveryNote("fresh-required")));
|
|
66695
66707
|
return true;
|
|
66696
66708
|
}
|
|
66697
66709
|
}
|
|
66698
66710
|
startRunMonitor(sessionKey, params) {
|
|
66699
|
-
const run = this.
|
|
66711
|
+
const run = this.getRun(sessionKey, params.runId);
|
|
66700
66712
|
if (!run) {
|
|
66701
66713
|
return;
|
|
66702
66714
|
}
|
|
@@ -66720,14 +66732,14 @@ class SessionService {
|
|
|
66720
66732
|
detachedAlready: params.detachedAlready,
|
|
66721
66733
|
timingContext: params.timingContext,
|
|
66722
66734
|
onPromptSubmitted: async () => {
|
|
66723
|
-
const currentRun = this.
|
|
66735
|
+
const currentRun = this.getRun(sessionKey, params.runId);
|
|
66724
66736
|
if (!currentRun) {
|
|
66725
66737
|
return;
|
|
66726
66738
|
}
|
|
66727
66739
|
currentRun.steeringReady = true;
|
|
66728
66740
|
},
|
|
66729
66741
|
onRunning: async (update) => {
|
|
66730
|
-
const currentRun = this.
|
|
66742
|
+
const currentRun = this.getRun(sessionKey, params.runId);
|
|
66731
66743
|
if (!currentRun) {
|
|
66732
66744
|
return;
|
|
66733
66745
|
}
|
|
@@ -66743,7 +66755,7 @@ class SessionService {
|
|
|
66743
66755
|
}));
|
|
66744
66756
|
},
|
|
66745
66757
|
onDetached: async (update) => {
|
|
66746
|
-
const currentRun = this.
|
|
66758
|
+
const currentRun = this.getRun(sessionKey, params.runId);
|
|
66747
66759
|
if (!currentRun) {
|
|
66748
66760
|
return;
|
|
66749
66761
|
}
|
|
@@ -66765,27 +66777,111 @@ class SessionService {
|
|
|
66765
66777
|
currentRun.initialResult.resolve(detachedUpdate);
|
|
66766
66778
|
},
|
|
66767
66779
|
onCompleted: async (update) => {
|
|
66780
|
+
const currentRun = this.getRun(sessionKey, params.runId);
|
|
66781
|
+
if (!currentRun) {
|
|
66782
|
+
return;
|
|
66783
|
+
}
|
|
66768
66784
|
const runUpdate = this.createRunUpdate({
|
|
66769
|
-
resolved:
|
|
66785
|
+
resolved: currentRun.resolved,
|
|
66770
66786
|
status: "completed",
|
|
66771
66787
|
snapshot: mergeRunSnapshot(params.snapshotPrefix ?? "", update.snapshot),
|
|
66772
66788
|
fullSnapshot: update.fullSnapshot,
|
|
66773
66789
|
initialSnapshot: update.initialSnapshot
|
|
66774
66790
|
});
|
|
66775
|
-
await this.finishActiveRun(sessionKey, runUpdate);
|
|
66791
|
+
await this.finishActiveRun(sessionKey, params.runId, runUpdate);
|
|
66776
66792
|
}
|
|
66777
66793
|
});
|
|
66778
66794
|
} catch (error) {
|
|
66779
66795
|
if (await this.recoverLostMidRun(sessionKey, {
|
|
66796
|
+
runId: params.runId,
|
|
66780
66797
|
timingContext: params.timingContext,
|
|
66781
66798
|
recoveryAttempt: (params.recoveryAttempt ?? 0) + 1
|
|
66782
66799
|
}, error)) {
|
|
66783
66800
|
return;
|
|
66784
66801
|
}
|
|
66785
|
-
await this.failActiveRun(sessionKey, await this.runnerSessions.mapRunError(error, run.resolved.sessionName, run.latestUpdate.fullSnapshot));
|
|
66802
|
+
await this.failActiveRun(sessionKey, params.runId, await this.runnerSessions.mapRunError(error, run.resolved.sessionName, run.latestUpdate.fullSnapshot));
|
|
66786
66803
|
}
|
|
66787
66804
|
})();
|
|
66788
66805
|
}
|
|
66806
|
+
allocateRunId() {
|
|
66807
|
+
return String(this.nextRunId++);
|
|
66808
|
+
}
|
|
66809
|
+
async reconcilePersistedActiveRun(target) {
|
|
66810
|
+
const activeRun = this.activeRuns.get(target.sessionKey);
|
|
66811
|
+
if (activeRun) {
|
|
66812
|
+
return activeRun;
|
|
66813
|
+
}
|
|
66814
|
+
const entry = await this.sessionState.getEntry(target.sessionKey);
|
|
66815
|
+
if (!entry?.runtime || entry.runtime.state === "idle") {
|
|
66816
|
+
return null;
|
|
66817
|
+
}
|
|
66818
|
+
const resolved = this.resolveTarget(target);
|
|
66819
|
+
if (!await this.tmux.hasSession(resolved.sessionName)) {
|
|
66820
|
+
await this.sessionState.setSessionRuntime(resolved, {
|
|
66821
|
+
state: "idle"
|
|
66822
|
+
});
|
|
66823
|
+
return null;
|
|
66824
|
+
}
|
|
66825
|
+
try {
|
|
66826
|
+
return await this.rehydratePersistedActiveRun(resolved, {
|
|
66827
|
+
runtimeState: entry.runtime.state,
|
|
66828
|
+
startedAt: entry.runtime.startedAt
|
|
66829
|
+
});
|
|
66830
|
+
} catch (error) {
|
|
66831
|
+
if (isMissingTmuxSessionError2(error) || isTmuxServerUnavailableError2(error) || isBootstrapSessionLostError2(error)) {
|
|
66832
|
+
await this.sessionState.setSessionRuntime(resolved, {
|
|
66833
|
+
state: "idle"
|
|
66834
|
+
});
|
|
66835
|
+
return null;
|
|
66836
|
+
}
|
|
66837
|
+
throw error;
|
|
66838
|
+
}
|
|
66839
|
+
}
|
|
66840
|
+
async rehydratePersistedActiveRun(resolved, params) {
|
|
66841
|
+
const existingRun = this.activeRuns.get(resolved.sessionKey);
|
|
66842
|
+
if (existingRun) {
|
|
66843
|
+
return existingRun;
|
|
66844
|
+
}
|
|
66845
|
+
const fullSnapshot = normalizePaneText(await this.tmux.capturePane(resolved.sessionName, resolved.stream.captureLines));
|
|
66846
|
+
const startedAt = params.startedAt ?? Date.now();
|
|
66847
|
+
const runId = this.allocateRunId();
|
|
66848
|
+
const initialResult = createDeferred();
|
|
66849
|
+
const update = this.createRunUpdate({
|
|
66850
|
+
resolved,
|
|
66851
|
+
status: params.runtimeState === "detached" ? "detached" : "running",
|
|
66852
|
+
snapshot: deriveInteractionText("", fullSnapshot),
|
|
66853
|
+
fullSnapshot,
|
|
66854
|
+
initialSnapshot: "",
|
|
66855
|
+
note: params.runtimeState === "detached" ? this.buildDetachedNote(resolved) : undefined
|
|
66856
|
+
});
|
|
66857
|
+
const run = {
|
|
66858
|
+
runId,
|
|
66859
|
+
resolved,
|
|
66860
|
+
observers: new Map,
|
|
66861
|
+
observerFailures: new Map,
|
|
66862
|
+
initialResult,
|
|
66863
|
+
latestUpdate: update,
|
|
66864
|
+
steeringReady: true,
|
|
66865
|
+
startedAt
|
|
66866
|
+
};
|
|
66867
|
+
this.activeRuns.set(resolved.sessionKey, run);
|
|
66868
|
+
this.startRunMonitor(resolved.sessionKey, {
|
|
66869
|
+
runId,
|
|
66870
|
+
prompt: undefined,
|
|
66871
|
+
initialSnapshot: fullSnapshot,
|
|
66872
|
+
startedAt,
|
|
66873
|
+
detachedAlready: params.runtimeState === "detached",
|
|
66874
|
+
timingContext: undefined
|
|
66875
|
+
});
|
|
66876
|
+
return run;
|
|
66877
|
+
}
|
|
66878
|
+
getRun(sessionKey, runId) {
|
|
66879
|
+
const run = this.activeRuns.get(sessionKey);
|
|
66880
|
+
if (!run || run.runId !== runId) {
|
|
66881
|
+
return null;
|
|
66882
|
+
}
|
|
66883
|
+
return run;
|
|
66884
|
+
}
|
|
66789
66885
|
}
|
|
66790
66886
|
|
|
66791
66887
|
// src/agents/agent-service.ts
|
|
@@ -67133,7 +67229,8 @@ class AgentService {
|
|
|
67133
67229
|
timingContext: callbacks.timingContext,
|
|
67134
67230
|
onUpdate: callbacks.onUpdate
|
|
67135
67231
|
}), {
|
|
67136
|
-
text: prompt
|
|
67232
|
+
text: prompt,
|
|
67233
|
+
canStart: async () => !this.activeRuns.hasActiveRun(target)
|
|
67137
67234
|
});
|
|
67138
67235
|
}
|
|
67139
67236
|
getMaxMessageChars(agentId) {
|
|
@@ -69465,6 +69562,7 @@ async function clearSlackAssistantThreadStatus(client, target) {
|
|
|
69465
69562
|
}
|
|
69466
69563
|
|
|
69467
69564
|
// src/channels/processing-indicator.ts
|
|
69565
|
+
var ACTIVE_RUN_WAIT_POLL_INTERVAL_MS = 250;
|
|
69468
69566
|
function shouldResolveIndicatorWait(update) {
|
|
69469
69567
|
return isTerminalRunStatus(update.status);
|
|
69470
69568
|
}
|
|
@@ -69491,6 +69589,18 @@ async function waitForProcessingIndicatorLifecycle(params) {
|
|
|
69491
69589
|
settled = true;
|
|
69492
69590
|
resolve();
|
|
69493
69591
|
};
|
|
69592
|
+
(async () => {
|
|
69593
|
+
while (!settled) {
|
|
69594
|
+
await sleep(ACTIVE_RUN_WAIT_POLL_INTERVAL_MS);
|
|
69595
|
+
if (settled) {
|
|
69596
|
+
return;
|
|
69597
|
+
}
|
|
69598
|
+
if (!params.agentService.hasActiveRun(params.sessionTarget)) {
|
|
69599
|
+
resolveOnce();
|
|
69600
|
+
return;
|
|
69601
|
+
}
|
|
69602
|
+
}
|
|
69603
|
+
})();
|
|
69494
69604
|
try {
|
|
69495
69605
|
const observation = await params.agentService.observeRun(params.sessionTarget, {
|
|
69496
69606
|
id: params.observerId,
|
|
@@ -76277,6 +76387,216 @@ async function runMessageCli(args, dependencies = defaultMessageCliDependencies)
|
|
|
76277
76387
|
dependencies.print(JSON.stringify(execution.result, null, 2));
|
|
76278
76388
|
}
|
|
76279
76389
|
|
|
76390
|
+
// src/control/runtime-cli-shared.ts
|
|
76391
|
+
class CliCommandError extends Error {
|
|
76392
|
+
exitCode;
|
|
76393
|
+
constructor(message, exitCode = 1) {
|
|
76394
|
+
super(message);
|
|
76395
|
+
this.exitCode = exitCode;
|
|
76396
|
+
}
|
|
76397
|
+
}
|
|
76398
|
+
function printCommandOutcomeBanner(outcome) {
|
|
76399
|
+
console.log("");
|
|
76400
|
+
console.log("+---------+");
|
|
76401
|
+
console.log(outcome === "success" ? "| SUCCESS |" : "| FAILED |");
|
|
76402
|
+
console.log("+---------+");
|
|
76403
|
+
console.log("");
|
|
76404
|
+
}
|
|
76405
|
+
function printCommandOutcomeFooter(outcome) {
|
|
76406
|
+
printCommandOutcomeBanner(outcome);
|
|
76407
|
+
}
|
|
76408
|
+
function assertSupportedPlatform(command) {
|
|
76409
|
+
if (process.platform !== "win32") {
|
|
76410
|
+
return;
|
|
76411
|
+
}
|
|
76412
|
+
if (command.name === "help" || command.name === "version") {
|
|
76413
|
+
return;
|
|
76414
|
+
}
|
|
76415
|
+
throw new Error("Native Windows is not supported yet. Run clisbot from WSL2 or use Linux/macOS instead.");
|
|
76416
|
+
}
|
|
76417
|
+
function getCliErrorExitCode(error) {
|
|
76418
|
+
return error instanceof CliCommandError ? error.exitCode : 1;
|
|
76419
|
+
}
|
|
76420
|
+
|
|
76421
|
+
// src/control/runner-cli.ts
|
|
76422
|
+
var SMOKE_BACKENDS = ["codex", "claude", "gemini", "all"];
|
|
76423
|
+
var SMOKE_SCENARIOS = [
|
|
76424
|
+
"startup_ready",
|
|
76425
|
+
"first_prompt_roundtrip",
|
|
76426
|
+
"session_id_roundtrip",
|
|
76427
|
+
"interrupt_during_run",
|
|
76428
|
+
"recover_after_runner_loss"
|
|
76429
|
+
];
|
|
76430
|
+
var SMOKE_SUITES = ["launch-trio"];
|
|
76431
|
+
function parseRepeatedOption4(args, name) {
|
|
76432
|
+
const values = [];
|
|
76433
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
76434
|
+
if (args[index] !== name) {
|
|
76435
|
+
continue;
|
|
76436
|
+
}
|
|
76437
|
+
const value = args[index + 1]?.trim();
|
|
76438
|
+
if (!value) {
|
|
76439
|
+
throw new CliCommandError(`Missing value for ${name}`, 2);
|
|
76440
|
+
}
|
|
76441
|
+
values.push(value);
|
|
76442
|
+
}
|
|
76443
|
+
return values;
|
|
76444
|
+
}
|
|
76445
|
+
function parseSingleOption3(args, name) {
|
|
76446
|
+
const values = parseRepeatedOption4(args, name);
|
|
76447
|
+
if (values.length === 0) {
|
|
76448
|
+
return;
|
|
76449
|
+
}
|
|
76450
|
+
return values[values.length - 1];
|
|
76451
|
+
}
|
|
76452
|
+
function hasFlag5(args, name) {
|
|
76453
|
+
return args.includes(name);
|
|
76454
|
+
}
|
|
76455
|
+
function isOneOf(value, allowed) {
|
|
76456
|
+
return allowed.includes(value);
|
|
76457
|
+
}
|
|
76458
|
+
function parseTimeoutMs(raw) {
|
|
76459
|
+
if (!raw) {
|
|
76460
|
+
return;
|
|
76461
|
+
}
|
|
76462
|
+
const parsed = Number.parseInt(raw, 10);
|
|
76463
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
76464
|
+
throw new CliCommandError("Invalid value for --timeout-ms", 2);
|
|
76465
|
+
}
|
|
76466
|
+
return parsed;
|
|
76467
|
+
}
|
|
76468
|
+
function renderRunnerHelp() {
|
|
76469
|
+
return [
|
|
76470
|
+
"clisbot runner",
|
|
76471
|
+
"",
|
|
76472
|
+
"Usage:",
|
|
76473
|
+
" clisbot runner",
|
|
76474
|
+
" clisbot runner --help",
|
|
76475
|
+
" clisbot runner smoke --backend <codex|claude|gemini> --scenario <name> [--workspace <path>] [--agent <id>] [--artifact-dir <path>] [--timeout-ms <n>] [--keep-session] [--json]",
|
|
76476
|
+
" clisbot runner smoke --backend all --suite launch-trio [--workspace <path>] [--agent <id>] [--artifact-dir <path>] [--timeout-ms <n>] [--keep-session] [--json]",
|
|
76477
|
+
"",
|
|
76478
|
+
"Smoke scenarios:",
|
|
76479
|
+
" - startup_ready",
|
|
76480
|
+
" - first_prompt_roundtrip",
|
|
76481
|
+
" - session_id_roundtrip",
|
|
76482
|
+
" - interrupt_during_run",
|
|
76483
|
+
" - recover_after_runner_loss",
|
|
76484
|
+
"",
|
|
76485
|
+
"Smoke suites:",
|
|
76486
|
+
" - launch-trio",
|
|
76487
|
+
"",
|
|
76488
|
+
"Current status:",
|
|
76489
|
+
" - the `runner` CLI surface now validates the smoke command contract",
|
|
76490
|
+
" - real smoke execution is the next implementation batch"
|
|
76491
|
+
].join(`
|
|
76492
|
+
`);
|
|
76493
|
+
}
|
|
76494
|
+
function parseSmokeCommand(args) {
|
|
76495
|
+
const backend = parseSingleOption3(args, "--backend");
|
|
76496
|
+
if (!backend) {
|
|
76497
|
+
throw new CliCommandError(`Usage: clisbot runner smoke --backend <codex|claude|gemini> --scenario <name> [--json]
|
|
76498
|
+
clisbot runner smoke --backend all --suite launch-trio [--json]`, 2);
|
|
76499
|
+
}
|
|
76500
|
+
if (!isOneOf(backend, SMOKE_BACKENDS)) {
|
|
76501
|
+
throw new CliCommandError(`Unsupported --backend value: ${backend}`, 2);
|
|
76502
|
+
}
|
|
76503
|
+
const rawScenario = parseSingleOption3(args, "--scenario");
|
|
76504
|
+
const rawSuite = parseSingleOption3(args, "--suite");
|
|
76505
|
+
if (rawScenario && rawSuite) {
|
|
76506
|
+
throw new CliCommandError("--scenario and --suite are mutually exclusive", 2);
|
|
76507
|
+
}
|
|
76508
|
+
if (rawScenario && !isOneOf(rawScenario, SMOKE_SCENARIOS)) {
|
|
76509
|
+
throw new CliCommandError(`Unsupported --scenario value: ${rawScenario}`, 2);
|
|
76510
|
+
}
|
|
76511
|
+
if (rawSuite && !isOneOf(rawSuite, SMOKE_SUITES)) {
|
|
76512
|
+
throw new CliCommandError(`Unsupported --suite value: ${rawSuite}`, 2);
|
|
76513
|
+
}
|
|
76514
|
+
const scenario = rawScenario;
|
|
76515
|
+
const suite = rawSuite;
|
|
76516
|
+
if (backend === "all") {
|
|
76517
|
+
if (scenario) {
|
|
76518
|
+
throw new CliCommandError("--backend all is only valid with --suite", 2);
|
|
76519
|
+
}
|
|
76520
|
+
if (!suite) {
|
|
76521
|
+
throw new CliCommandError("--backend all requires --suite launch-trio", 2);
|
|
76522
|
+
}
|
|
76523
|
+
} else {
|
|
76524
|
+
if (suite) {
|
|
76525
|
+
throw new CliCommandError(`--suite is only valid with --backend all`, 2);
|
|
76526
|
+
}
|
|
76527
|
+
if (!scenario) {
|
|
76528
|
+
throw new CliCommandError(`--backend ${backend} requires --scenario`, 2);
|
|
76529
|
+
}
|
|
76530
|
+
}
|
|
76531
|
+
return {
|
|
76532
|
+
backend,
|
|
76533
|
+
scenario,
|
|
76534
|
+
suite,
|
|
76535
|
+
workspace: parseSingleOption3(args, "--workspace"),
|
|
76536
|
+
agent: parseSingleOption3(args, "--agent"),
|
|
76537
|
+
artifactDir: parseSingleOption3(args, "--artifact-dir"),
|
|
76538
|
+
timeoutMs: parseTimeoutMs(parseSingleOption3(args, "--timeout-ms")),
|
|
76539
|
+
keepSession: hasFlag5(args, "--keep-session"),
|
|
76540
|
+
json: hasFlag5(args, "--json")
|
|
76541
|
+
};
|
|
76542
|
+
}
|
|
76543
|
+
function renderSmokeNotImplementedResult(options) {
|
|
76544
|
+
return {
|
|
76545
|
+
kind: "runner-smoke-framework-error",
|
|
76546
|
+
version: "v0",
|
|
76547
|
+
ok: false,
|
|
76548
|
+
backendId: options.backend,
|
|
76549
|
+
scenario: options.scenario ?? null,
|
|
76550
|
+
suite: options.suite ?? null,
|
|
76551
|
+
error: {
|
|
76552
|
+
code: "NOT_IMPLEMENTED",
|
|
76553
|
+
message: "clisbot runner smoke is not implemented yet. The command surface and contract validation are ready; the real execution batch is next."
|
|
76554
|
+
},
|
|
76555
|
+
options: {
|
|
76556
|
+
workspace: options.workspace ?? null,
|
|
76557
|
+
agent: options.agent ?? null,
|
|
76558
|
+
artifactDir: options.artifactDir ?? null,
|
|
76559
|
+
timeoutMs: options.timeoutMs ?? null,
|
|
76560
|
+
keepSession: options.keepSession,
|
|
76561
|
+
json: options.json
|
|
76562
|
+
}
|
|
76563
|
+
};
|
|
76564
|
+
}
|
|
76565
|
+
async function runSmokeCli(args) {
|
|
76566
|
+
if (args.length === 0 || hasFlag5(args, "--help") || hasFlag5(args, "-h")) {
|
|
76567
|
+
console.log(renderRunnerHelp());
|
|
76568
|
+
return;
|
|
76569
|
+
}
|
|
76570
|
+
const options = parseSmokeCommand(args);
|
|
76571
|
+
const result = renderSmokeNotImplementedResult(options);
|
|
76572
|
+
if (options.json) {
|
|
76573
|
+
console.log(JSON.stringify(result, null, 2));
|
|
76574
|
+
} else {
|
|
76575
|
+
console.log([
|
|
76576
|
+
"clisbot runner smoke",
|
|
76577
|
+
"",
|
|
76578
|
+
`backend: ${options.backend}`,
|
|
76579
|
+
options.scenario ? `scenario: ${options.scenario}` : `suite: ${options.suite}`,
|
|
76580
|
+
"status: not implemented yet",
|
|
76581
|
+
"note: the smoke contract is validated, but real CLI execution is the next batch"
|
|
76582
|
+
].join(`
|
|
76583
|
+
`));
|
|
76584
|
+
}
|
|
76585
|
+
process.exitCode = 3;
|
|
76586
|
+
}
|
|
76587
|
+
async function runRunnerCli(args) {
|
|
76588
|
+
const subcommand = args[0];
|
|
76589
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
|
|
76590
|
+
console.log(renderRunnerHelp());
|
|
76591
|
+
return;
|
|
76592
|
+
}
|
|
76593
|
+
if (subcommand === "smoke") {
|
|
76594
|
+
await runSmokeCli(args.slice(1));
|
|
76595
|
+
return;
|
|
76596
|
+
}
|
|
76597
|
+
throw new CliCommandError(renderRunnerHelp(), 2);
|
|
76598
|
+
}
|
|
76599
|
+
|
|
76280
76600
|
// src/control/activity-store.ts
|
|
76281
76601
|
import { dirname as dirname14 } from "node:path";
|
|
76282
76602
|
class ActivityStore {
|
|
@@ -76812,27 +77132,6 @@ function renderOperatorErrorWithHelpLines(error) {
|
|
|
76812
77132
|
];
|
|
76813
77133
|
}
|
|
76814
77134
|
|
|
76815
|
-
// src/control/runtime-cli-shared.ts
|
|
76816
|
-
function printCommandOutcomeBanner(outcome) {
|
|
76817
|
-
console.log("");
|
|
76818
|
-
console.log("+---------+");
|
|
76819
|
-
console.log(outcome === "success" ? "| SUCCESS |" : "| FAILED |");
|
|
76820
|
-
console.log("+---------+");
|
|
76821
|
-
console.log("");
|
|
76822
|
-
}
|
|
76823
|
-
function printCommandOutcomeFooter(outcome) {
|
|
76824
|
-
printCommandOutcomeBanner(outcome);
|
|
76825
|
-
}
|
|
76826
|
-
function assertSupportedPlatform(command) {
|
|
76827
|
-
if (process.platform !== "win32") {
|
|
76828
|
-
return;
|
|
76829
|
-
}
|
|
76830
|
-
if (command.name === "help" || command.name === "version") {
|
|
76831
|
-
return;
|
|
76832
|
-
}
|
|
76833
|
-
throw new Error("Native Windows is not supported yet. Run clisbot from WSL2 or use Linux/macOS instead.");
|
|
76834
|
-
}
|
|
76835
|
-
|
|
76836
77135
|
// src/control/runtime-bootstrap-cli.ts
|
|
76837
77136
|
function hasHelpFlag(args) {
|
|
76838
77137
|
return args.includes("--help") || args.includes("-h") || args.includes("help");
|
|
@@ -77922,6 +78221,10 @@ async function runControlCommand(command) {
|
|
|
77922
78221
|
await runAuthCli(command.args);
|
|
77923
78222
|
return true;
|
|
77924
78223
|
}
|
|
78224
|
+
if (command.name === "runner") {
|
|
78225
|
+
await runRunnerCli(command.args);
|
|
78226
|
+
return true;
|
|
78227
|
+
}
|
|
77925
78228
|
if (command.name === "pairing") {
|
|
77926
78229
|
await runPairingCli(command.args);
|
|
77927
78230
|
return true;
|
|
@@ -77943,5 +78246,5 @@ try {
|
|
|
77943
78246
|
printCommandOutcomeBanner("failure");
|
|
77944
78247
|
}
|
|
77945
78248
|
await printCliError(error);
|
|
77946
|
-
process.exit(
|
|
78249
|
+
process.exit(getCliErrorExitCode(error));
|
|
77947
78250
|
}
|