clisbot 0.1.22 → 0.1.28
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
CHANGED
|
@@ -54694,6 +54694,7 @@ function renderCliHelp() {
|
|
|
54694
54694
|
"",
|
|
54695
54695
|
"Commands:",
|
|
54696
54696
|
` start Seed ${configPath} if missing, apply explicit channel-account bootstrap intent, and start clisbot in the background.`,
|
|
54697
|
+
" See `clisbot start --help` for bootstrap-focused flags and examples.",
|
|
54697
54698
|
" restart Stop the running clisbot process, then start it again.",
|
|
54698
54699
|
" stop Stop the running clisbot process.",
|
|
54699
54700
|
" stop --hard Stop clisbot and kill all tmux sessions on the configured clisbot socket.",
|
|
@@ -54710,20 +54711,26 @@ function renderCliHelp() {
|
|
|
54710
54711
|
" remove slack-group <groupId>",
|
|
54711
54712
|
" set-token <slack-app|slack-bot|telegram-bot> <value>",
|
|
54712
54713
|
" clear-token <slack-app|slack-bot|telegram-bot>",
|
|
54714
|
+
" See `clisbot channels --help` for route policy notes and defaults such as `requireMention`.",
|
|
54713
54715
|
" accounts Manage Slack and Telegram provider accounts plus persistence state.",
|
|
54714
54716
|
" add telegram --account <id> --token <ENV_NAME|${ENV_NAME}|literal> [--persist]",
|
|
54715
54717
|
" add slack --account <id> --app-token <ENV_NAME|${ENV_NAME}|literal> --bot-token <ENV_NAME|${ENV_NAME}|literal> [--persist]",
|
|
54716
54718
|
" persist --channel <slack|telegram> --account <id>",
|
|
54717
54719
|
" persist --all",
|
|
54720
|
+
" See `clisbot accounts --help` for env-vs-mem-vs-persist behavior.",
|
|
54718
54721
|
" loops Inspect or cancel managed recurring loops persisted by `/loop`.",
|
|
54719
54722
|
" list|status",
|
|
54720
54723
|
" cancel <id>",
|
|
54721
54724
|
" cancel --all",
|
|
54725
|
+
" See `clisbot loops --help` for behavior notes.",
|
|
54722
54726
|
" message Run provider message actions such as send, react, read, edit, delete, and pins.",
|
|
54727
|
+
" See `clisbot message --help` for channel-specific syntax.",
|
|
54723
54728
|
" agents Manage configured agents and top-level bindings.",
|
|
54729
|
+
" See `clisbot agents --help` for focused add/bootstrap/binding help.",
|
|
54724
54730
|
" auth Manage app and agent auth roles, principals, and permissions in config. See `clisbot auth --help`.",
|
|
54725
|
-
" pairing Run the pairing control CLI.",
|
|
54731
|
+
" pairing Run the pairing control CLI. See `clisbot pairing --help`.",
|
|
54726
54732
|
` init Seed ${configPath} and optionally create the first agent without starting clisbot.`,
|
|
54733
|
+
" See `clisbot init --help` for bootstrap-focused flags and examples.",
|
|
54727
54734
|
" --version, -v Show the installed clisbot version.",
|
|
54728
54735
|
" --help Show this help text.",
|
|
54729
54736
|
"",
|
|
@@ -54766,6 +54773,35 @@ function buildPairingReply(params) {
|
|
|
54766
54773
|
].join(`
|
|
54767
54774
|
`);
|
|
54768
54775
|
}
|
|
54776
|
+
function buildPairingQueueFullReply(params) {
|
|
54777
|
+
return [
|
|
54778
|
+
"clisbot: access not configured.",
|
|
54779
|
+
"",
|
|
54780
|
+
params.idLine,
|
|
54781
|
+
"",
|
|
54782
|
+
"Pairing queue is full right now.",
|
|
54783
|
+
"",
|
|
54784
|
+
"Ask the bot owner to inspect or clear pending requests with:",
|
|
54785
|
+
`clisbot pairing list ${params.channel}`,
|
|
54786
|
+
`clisbot pairing reject ${params.channel} <code>`,
|
|
54787
|
+
`clisbot pairing clear ${params.channel}`
|
|
54788
|
+
].join(`
|
|
54789
|
+
`);
|
|
54790
|
+
}
|
|
54791
|
+
function buildPairingReplyFromRequest(params) {
|
|
54792
|
+
const code = params.pairingRequest.code.trim();
|
|
54793
|
+
if (!code) {
|
|
54794
|
+
return buildPairingQueueFullReply({
|
|
54795
|
+
channel: params.channel,
|
|
54796
|
+
idLine: params.idLine
|
|
54797
|
+
});
|
|
54798
|
+
}
|
|
54799
|
+
return buildPairingReply({
|
|
54800
|
+
channel: params.channel,
|
|
54801
|
+
idLine: params.idLine,
|
|
54802
|
+
code
|
|
54803
|
+
});
|
|
54804
|
+
}
|
|
54769
54805
|
function renderPairingRequests(params) {
|
|
54770
54806
|
if (!params.requests.length) {
|
|
54771
54807
|
return `No pending ${params.channel} pairing requests.`;
|
|
@@ -54788,7 +54824,7 @@ import path from "node:path";
|
|
|
54788
54824
|
var PAIRING_CODE_LENGTH = 8;
|
|
54789
54825
|
var PAIRING_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
54790
54826
|
var PAIRING_PENDING_TTL_MS = 60 * 60 * 1000;
|
|
54791
|
-
var PAIRING_PENDING_MAX =
|
|
54827
|
+
var PAIRING_PENDING_MAX = 20;
|
|
54792
54828
|
var PAIRING_STORE_LOCK_OPTIONS = {
|
|
54793
54829
|
retries: {
|
|
54794
54830
|
retries: 10,
|
|
@@ -55067,8 +55103,63 @@ async function approveChannelPairingCode(params) {
|
|
|
55067
55103
|
return { id: entry.id, entry };
|
|
55068
55104
|
});
|
|
55069
55105
|
}
|
|
55106
|
+
async function rejectChannelPairingCode(params) {
|
|
55107
|
+
const code = params.code.trim().toUpperCase();
|
|
55108
|
+
if (!code) {
|
|
55109
|
+
return null;
|
|
55110
|
+
}
|
|
55111
|
+
const filePath = resolvePairingPath(params.channel, params.baseDir);
|
|
55112
|
+
return withFileLock(filePath, { version: 1, requests: [] }, async () => {
|
|
55113
|
+
const { value } = await readJsonFile(filePath, {
|
|
55114
|
+
version: 1,
|
|
55115
|
+
requests: []
|
|
55116
|
+
});
|
|
55117
|
+
const requests = Array.isArray(value.requests) ? value.requests : [];
|
|
55118
|
+
const { requests: pruned, removed } = pruneExpiredRequests(requests, Date.now());
|
|
55119
|
+
const matchIndex = pruned.findIndex((request) => String(request.code ?? "").trim().toUpperCase() === code);
|
|
55120
|
+
if (matchIndex < 0) {
|
|
55121
|
+
if (removed) {
|
|
55122
|
+
await writeJsonFile(filePath, {
|
|
55123
|
+
version: 1,
|
|
55124
|
+
requests: pruned
|
|
55125
|
+
});
|
|
55126
|
+
}
|
|
55127
|
+
return null;
|
|
55128
|
+
}
|
|
55129
|
+
const [rejected] = pruned.splice(matchIndex, 1);
|
|
55130
|
+
await writeJsonFile(filePath, {
|
|
55131
|
+
version: 1,
|
|
55132
|
+
requests: pruned
|
|
55133
|
+
});
|
|
55134
|
+
return rejected ?? null;
|
|
55135
|
+
});
|
|
55136
|
+
}
|
|
55137
|
+
async function clearChannelPairingRequests(params) {
|
|
55138
|
+
const filePath = resolvePairingPath(params.channel, params.baseDir);
|
|
55139
|
+
return withFileLock(filePath, { version: 1, requests: [] }, async () => {
|
|
55140
|
+
const { value } = await readJsonFile(filePath, {
|
|
55141
|
+
version: 1,
|
|
55142
|
+
requests: []
|
|
55143
|
+
});
|
|
55144
|
+
const requests = Array.isArray(value.requests) ? value.requests : [];
|
|
55145
|
+
const { requests: pruned } = pruneExpiredRequests(requests, Date.now());
|
|
55146
|
+
await writeJsonFile(filePath, {
|
|
55147
|
+
version: 1,
|
|
55148
|
+
requests: []
|
|
55149
|
+
});
|
|
55150
|
+
return { cleared: pruned.length };
|
|
55151
|
+
});
|
|
55152
|
+
}
|
|
55070
55153
|
|
|
55071
55154
|
// src/channels/pairing/cli.ts
|
|
55155
|
+
function resolvePairingBaseDir(env = process.env) {
|
|
55156
|
+
const configured = env.CLISBOT_PAIRING_DIR?.trim();
|
|
55157
|
+
if (configured) {
|
|
55158
|
+
return configured;
|
|
55159
|
+
}
|
|
55160
|
+
const legacy = env.TMUX_TALK_PAIRING_DIR?.trim();
|
|
55161
|
+
return legacy || undefined;
|
|
55162
|
+
}
|
|
55072
55163
|
function parseChannel(raw) {
|
|
55073
55164
|
const value = raw?.trim().toLowerCase();
|
|
55074
55165
|
if (value === "slack" || value === "telegram") {
|
|
@@ -55076,9 +55167,33 @@ function parseChannel(raw) {
|
|
|
55076
55167
|
}
|
|
55077
55168
|
throw new Error("Channel required: slack | telegram");
|
|
55078
55169
|
}
|
|
55170
|
+
function renderPairingCliHelp() {
|
|
55171
|
+
return [
|
|
55172
|
+
"clisbot pairing",
|
|
55173
|
+
"",
|
|
55174
|
+
"Usage:",
|
|
55175
|
+
" clisbot pairing --help",
|
|
55176
|
+
" clisbot pairing help",
|
|
55177
|
+
" clisbot pairing list <slack|telegram> [--json]",
|
|
55178
|
+
" clisbot pairing approve <slack|telegram> <code>",
|
|
55179
|
+
" clisbot pairing reject <slack|telegram> <code>",
|
|
55180
|
+
" clisbot pairing clear <slack|telegram>",
|
|
55181
|
+
"",
|
|
55182
|
+
"Notes:",
|
|
55183
|
+
" - `list` shows pending pairing requests for one channel only",
|
|
55184
|
+
" - `approve` moves that sender into the channel allowlist",
|
|
55185
|
+
" - `reject` removes one pending request without allowlisting the sender",
|
|
55186
|
+
" - `clear` drops every pending request for that channel when the queue needs a reset"
|
|
55187
|
+
].join(`
|
|
55188
|
+
`);
|
|
55189
|
+
}
|
|
55079
55190
|
async function runPairingCli(args, writer = console) {
|
|
55080
55191
|
const [command, ...rest] = args;
|
|
55081
|
-
const baseDir =
|
|
55192
|
+
const baseDir = resolvePairingBaseDir();
|
|
55193
|
+
if (!command || command === "--help" || command === "-h" || command === "help") {
|
|
55194
|
+
writer.log(renderPairingCliHelp());
|
|
55195
|
+
return;
|
|
55196
|
+
}
|
|
55082
55197
|
if (command === "list") {
|
|
55083
55198
|
const wantsJson = rest.includes("--json");
|
|
55084
55199
|
const channel = parseChannel(rest.find((value) => !value.startsWith("--")));
|
|
@@ -55103,7 +55218,34 @@ async function runPairingCli(args, writer = console) {
|
|
|
55103
55218
|
writer.log(`Approved ${channel} sender ${approved.id}.`);
|
|
55104
55219
|
return;
|
|
55105
55220
|
}
|
|
55106
|
-
|
|
55221
|
+
if (command === "reject") {
|
|
55222
|
+
const [channelArg, code] = rest;
|
|
55223
|
+
const channel = parseChannel(channelArg);
|
|
55224
|
+
if (!code?.trim()) {
|
|
55225
|
+
throw new Error("Usage: pairing reject <channel> <code>");
|
|
55226
|
+
}
|
|
55227
|
+
const rejected = await rejectChannelPairingCode({
|
|
55228
|
+
channel,
|
|
55229
|
+
code,
|
|
55230
|
+
baseDir
|
|
55231
|
+
});
|
|
55232
|
+
if (!rejected) {
|
|
55233
|
+
throw new Error(`No pending pairing request found for code: ${code}`);
|
|
55234
|
+
}
|
|
55235
|
+
writer.log(`Rejected ${channel} sender ${rejected.id}.`);
|
|
55236
|
+
return;
|
|
55237
|
+
}
|
|
55238
|
+
if (command === "clear") {
|
|
55239
|
+
const [channelArg] = rest;
|
|
55240
|
+
const channel = parseChannel(channelArg);
|
|
55241
|
+
const result = await clearChannelPairingRequests({
|
|
55242
|
+
channel,
|
|
55243
|
+
baseDir
|
|
55244
|
+
});
|
|
55245
|
+
writer.log(`Cleared ${result.cleared} pending ${channel} pairing request(s).`);
|
|
55246
|
+
return;
|
|
55247
|
+
}
|
|
55248
|
+
throw new Error(renderPairingCliHelp());
|
|
55107
55249
|
}
|
|
55108
55250
|
|
|
55109
55251
|
// src/config/agent-tool-presets.ts
|
|
@@ -59283,7 +59425,7 @@ function formatConfiguredRuntimeLimit(params) {
|
|
|
59283
59425
|
if (typeof params.maxRuntimeMin === "number" && Number.isFinite(params.maxRuntimeMin)) {
|
|
59284
59426
|
return `${params.maxRuntimeMin} minute${params.maxRuntimeMin === 1 ? "" : "s"}`;
|
|
59285
59427
|
}
|
|
59286
|
-
return "
|
|
59428
|
+
return "30 minutes";
|
|
59287
59429
|
}
|
|
59288
59430
|
function parseCommandDurationMs(raw) {
|
|
59289
59431
|
const match = raw.trim().match(/^(\d+)(ms|s|m|h)$/i);
|
|
@@ -59932,7 +60074,7 @@ var sessionDmScopeSchema = exports_external.enum([
|
|
|
59932
60074
|
]);
|
|
59933
60075
|
var sessionConfigSchema = exports_external.object({
|
|
59934
60076
|
mainKey: exports_external.string().min(1).default("main"),
|
|
59935
|
-
dmScope: sessionDmScopeSchema.default("
|
|
60077
|
+
dmScope: sessionDmScopeSchema.default("per-channel-peer"),
|
|
59936
60078
|
identityLinks: exports_external.record(exports_external.string(), exports_external.array(exports_external.string())).default({}),
|
|
59937
60079
|
storePath: exports_external.string().default("~/.clisbot/state/sessions.json")
|
|
59938
60080
|
});
|
|
@@ -59991,7 +60133,7 @@ var agentDefaultsSchema = exports_external.object({
|
|
|
59991
60133
|
updateIntervalMs: 2000,
|
|
59992
60134
|
idleTimeoutMs: 6000,
|
|
59993
60135
|
noOutputTimeoutMs: 20000,
|
|
59994
|
-
maxRuntimeMin:
|
|
60136
|
+
maxRuntimeMin: 30,
|
|
59995
60137
|
maxMessageChars: 3500
|
|
59996
60138
|
}),
|
|
59997
60139
|
session: sessionSchema.default({
|
|
@@ -60250,7 +60392,15 @@ var controlRuntimeMonitorRestartStageSchema = exports_external.object({
|
|
|
60250
60392
|
delayMinutes: exports_external.number().int().positive().default(15),
|
|
60251
60393
|
maxRestarts: exports_external.number().int().positive().default(4)
|
|
60252
60394
|
});
|
|
60395
|
+
var controlRuntimeMonitorFastRetrySchema = exports_external.object({
|
|
60396
|
+
delaySeconds: exports_external.number().int().positive().default(10),
|
|
60397
|
+
maxRestarts: exports_external.number().int().min(0).default(3)
|
|
60398
|
+
});
|
|
60253
60399
|
var controlRuntimeMonitorRestartBackoffSchema = exports_external.object({
|
|
60400
|
+
fastRetry: controlRuntimeMonitorFastRetrySchema.default({
|
|
60401
|
+
delaySeconds: 10,
|
|
60402
|
+
maxRestarts: 3
|
|
60403
|
+
}),
|
|
60254
60404
|
stages: exports_external.array(controlRuntimeMonitorRestartStageSchema).min(1).default([
|
|
60255
60405
|
{
|
|
60256
60406
|
delayMinutes: 15,
|
|
@@ -60268,6 +60418,10 @@ var controlRuntimeMonitorOwnerAlertsSchema = exports_external.object({
|
|
|
60268
60418
|
});
|
|
60269
60419
|
var controlRuntimeMonitorSchema = exports_external.object({
|
|
60270
60420
|
restartBackoff: controlRuntimeMonitorRestartBackoffSchema.default({
|
|
60421
|
+
fastRetry: {
|
|
60422
|
+
delaySeconds: 10,
|
|
60423
|
+
maxRestarts: 3
|
|
60424
|
+
},
|
|
60271
60425
|
stages: [
|
|
60272
60426
|
{
|
|
60273
60427
|
delayMinutes: 15,
|
|
@@ -60328,7 +60482,7 @@ var clisbotConfigSchema = exports_external.object({
|
|
|
60328
60482
|
}),
|
|
60329
60483
|
session: sessionConfigSchema.default({
|
|
60330
60484
|
mainKey: "main",
|
|
60331
|
-
dmScope: "
|
|
60485
|
+
dmScope: "per-channel-peer",
|
|
60332
60486
|
identityLinks: {},
|
|
60333
60487
|
storePath: "~/.clisbot/state/sessions.json"
|
|
60334
60488
|
}),
|
|
@@ -60367,6 +60521,10 @@ var clisbotConfigSchema = exports_external.object({
|
|
|
60367
60521
|
},
|
|
60368
60522
|
runtimeMonitor: {
|
|
60369
60523
|
restartBackoff: {
|
|
60524
|
+
fastRetry: {
|
|
60525
|
+
delaySeconds: 10,
|
|
60526
|
+
maxRestarts: 3
|
|
60527
|
+
},
|
|
60370
60528
|
stages: [
|
|
60371
60529
|
{
|
|
60372
60530
|
delayMinutes: 15,
|
|
@@ -60581,7 +60739,7 @@ function renderDefaultConfigTemplate(options = {}) {
|
|
|
60581
60739
|
},
|
|
60582
60740
|
session: {
|
|
60583
60741
|
mainKey: "main",
|
|
60584
|
-
dmScope: "
|
|
60742
|
+
dmScope: "per-channel-peer",
|
|
60585
60743
|
identityLinks: {},
|
|
60586
60744
|
storePath: sessionStorePath
|
|
60587
60745
|
},
|
|
@@ -60663,7 +60821,7 @@ function renderDefaultConfigTemplate(options = {}) {
|
|
|
60663
60821
|
updateIntervalMs: 2000,
|
|
60664
60822
|
idleTimeoutMs: 6000,
|
|
60665
60823
|
noOutputTimeoutMs: 20000,
|
|
60666
|
-
maxRuntimeMin:
|
|
60824
|
+
maxRuntimeMin: 30,
|
|
60667
60825
|
maxMessageChars: 3500
|
|
60668
60826
|
},
|
|
60669
60827
|
session: {
|
|
@@ -60691,6 +60849,10 @@ function renderDefaultConfigTemplate(options = {}) {
|
|
|
60691
60849
|
},
|
|
60692
60850
|
runtimeMonitor: {
|
|
60693
60851
|
restartBackoff: {
|
|
60852
|
+
fastRetry: {
|
|
60853
|
+
delaySeconds: 10,
|
|
60854
|
+
maxRestarts: 3
|
|
60855
|
+
},
|
|
60694
60856
|
stages: [
|
|
60695
60857
|
{
|
|
60696
60858
|
delayMinutes: 15,
|
|
@@ -61024,6 +61186,32 @@ function parseSingleOption(args, name) {
|
|
|
61024
61186
|
function hasFlag(args, name) {
|
|
61025
61187
|
return args.includes(name);
|
|
61026
61188
|
}
|
|
61189
|
+
function renderAgentsHelp() {
|
|
61190
|
+
return [
|
|
61191
|
+
"clisbot agents",
|
|
61192
|
+
"",
|
|
61193
|
+
"Usage:",
|
|
61194
|
+
" clisbot agents --help",
|
|
61195
|
+
" clisbot agents help",
|
|
61196
|
+
" clisbot agents list [--bindings] [--json]",
|
|
61197
|
+
" clisbot agents add <id> --cli <codex|claude|gemini> [--workspace <path>] [--startup-option <arg>]... [--bootstrap <personal-assistant|team-assistant>] [--bind <channel[:accountId]>]...",
|
|
61198
|
+
" clisbot agents bootstrap <id> --mode <personal-assistant|team-assistant> [--force]",
|
|
61199
|
+
" clisbot agents bindings [--agent <id>] [--json]",
|
|
61200
|
+
" clisbot agents bind --agent <id> --bind <channel[:accountId]>",
|
|
61201
|
+
" clisbot agents unbind --agent <id> [--bind <channel[:accountId]> | --all]",
|
|
61202
|
+
" clisbot agents response-mode <status|set|clear> --agent <id> [capture-pane|message-tool]",
|
|
61203
|
+
" clisbot agents additional-message-mode <status|set|clear> --agent <id> [queue|steer]",
|
|
61204
|
+
"",
|
|
61205
|
+
"Notes:",
|
|
61206
|
+
" - `agents add` is the lower-level manual surface; first-run `clisbot start` and `clisbot init` can bootstrap the first `default` agent for you",
|
|
61207
|
+
" - `--cli` is required on `agents add`; supported tools are `codex`, `claude`, and `gemini`",
|
|
61208
|
+
" - omit `--startup-option` to inherit the built-in startup args for the selected CLI tool",
|
|
61209
|
+
" - `--bind slack`, `--bind telegram`, or `--bind <channel>:<accountId>` creates top-level fallback bindings",
|
|
61210
|
+
" - explicit route `agentId` on Slack or Telegram still wins before these fallback bindings",
|
|
61211
|
+
" - `response-mode` and `additional-message-mode` mutate per-agent overrides under `agents.list[]`"
|
|
61212
|
+
].join(`
|
|
61213
|
+
`);
|
|
61214
|
+
}
|
|
61027
61215
|
function parseResponseMode(raw) {
|
|
61028
61216
|
if (raw === "capture-pane" || raw === "message-tool") {
|
|
61029
61217
|
return raw;
|
|
@@ -61353,7 +61541,11 @@ async function runAgentAdditionalMessageModeCli(args) {
|
|
|
61353
61541
|
async function runAgentsCli(args) {
|
|
61354
61542
|
const subcommand = args[0];
|
|
61355
61543
|
const rest = args.slice(1);
|
|
61356
|
-
if (!subcommand || subcommand === "
|
|
61544
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
|
|
61545
|
+
console.log(renderAgentsHelp());
|
|
61546
|
+
return;
|
|
61547
|
+
}
|
|
61548
|
+
if (subcommand === "list") {
|
|
61357
61549
|
await listAgents(rest);
|
|
61358
61550
|
return;
|
|
61359
61551
|
}
|
|
@@ -61385,7 +61577,7 @@ async function runAgentsCli(args) {
|
|
|
61385
61577
|
await unbindAgent(rest);
|
|
61386
61578
|
return;
|
|
61387
61579
|
}
|
|
61388
|
-
throw new Error(
|
|
61580
|
+
throw new Error(renderAgentsHelp());
|
|
61389
61581
|
}
|
|
61390
61582
|
|
|
61391
61583
|
// src/control/accounts-cli.ts
|
|
@@ -61731,8 +61923,8 @@ class RuntimeHealthStore {
|
|
|
61731
61923
|
|
|
61732
61924
|
// src/control/runtime-process.ts
|
|
61733
61925
|
import { execFileSync, spawn as spawn3 } from "node:child_process";
|
|
61734
|
-
import { closeSync, existsSync as existsSync7, openSync, readFileSync as readFileSync3, rmSync as rmSync3, statSync as
|
|
61735
|
-
import { dirname as dirname13 } from "node:path";
|
|
61926
|
+
import { closeSync, existsSync as existsSync7, openSync, readFileSync as readFileSync3, rmSync as rmSync3, statSync as statSync4 } from "node:fs";
|
|
61927
|
+
import { dirname as dirname13, join as join11 } from "node:path";
|
|
61736
61928
|
import { kill as kill2 } from "node:process";
|
|
61737
61929
|
|
|
61738
61930
|
// src/control/clisbot-wrapper.ts
|
|
@@ -62719,43 +62911,151 @@ function hasActiveRuntime(entry) {
|
|
|
62719
62911
|
}
|
|
62720
62912
|
|
|
62721
62913
|
// src/channels/agent-prompt.ts
|
|
62722
|
-
|
|
62723
|
-
|
|
62724
|
-
|
|
62725
|
-
|
|
62726
|
-
|
|
62727
|
-
|
|
62728
|
-
|
|
62914
|
+
var CONFIGURATION_GUIDANCE = "When the user asks to change clisbot configuration, use clisbot CLI commands; see `clisbot --help`, `clisbot channels --help`, or `clisbot auth --help` for details.";
|
|
62915
|
+
var BASE_TEMPLATE = `<system>
|
|
62916
|
+
[{{timestamp}}] {{identity_summary}}
|
|
62917
|
+
|
|
62918
|
+
You are operating inside clisbot.
|
|
62919
|
+
{{delivery_intro}}
|
|
62920
|
+
{{reply_command}}
|
|
62921
|
+
{{reply_rules}}
|
|
62922
|
+
${CONFIGURATION_GUIDANCE}{{protected_control_suffix}}
|
|
62923
|
+
</system>
|
|
62924
|
+
|
|
62925
|
+
<user>
|
|
62926
|
+
{{message_body}}
|
|
62927
|
+
</user>`;
|
|
62928
|
+
var STEERING_TEMPLATE = `<system>
|
|
62929
|
+
A new user message arrived while you were still working.
|
|
62930
|
+
Adjust your current work if needed and continue.{{protected_control_suffix}}
|
|
62729
62931
|
</system>
|
|
62730
62932
|
|
|
62731
62933
|
<user>
|
|
62732
|
-
|
|
62934
|
+
{{message_body}}
|
|
62733
62935
|
</user>`;
|
|
62936
|
+
var DELIVERY_INTRO = "To send a user-visible {{progress_phrase}}final reply, use the following CLI command:";
|
|
62937
|
+
var DELIVERY_INTRO_CAPTURE_PANE = "channel auto-delivery remains enabled for this conversation; do not send user-facing progress updates or the final response with clisbot message send";
|
|
62938
|
+
var REPLY_COMMAND = `{{reply_command_base}}
|
|
62939
|
+
--final{{progress_flag_suffix}} \\
|
|
62940
|
+
--message "$(cat <<\\__CLISBOT_MESSAGE__
|
|
62941
|
+
<user-facing reply>
|
|
62942
|
+
__CLISBOT_MESSAGE__
|
|
62943
|
+
)" \\
|
|
62944
|
+
[--media /absolute/path/to/file]`;
|
|
62945
|
+
var REPLY_RULES = `When replying to the user:
|
|
62946
|
+
- put the user-facing message inside the --message body of that command
|
|
62947
|
+
{{progress_rules_block}}- {{final_rule_line}}`;
|
|
62948
|
+
var PROGRESS_PHRASE = "progress update or ";
|
|
62949
|
+
var EMPTY_PROGRESS_PHRASE = "";
|
|
62950
|
+
var PROGRESS_FLAG_SUFFIX = "|progress";
|
|
62951
|
+
var EMPTY_PROGRESS_FLAG_SUFFIX = "";
|
|
62952
|
+
var PROGRESS_RULES_BLOCK = `- use that command to send progress updates and the final reply back to the conversation
|
|
62953
|
+
- send at most {{max_progress_messages}} progress updates
|
|
62954
|
+
- keep progress updates short and meaningful
|
|
62955
|
+
- do not send progress updates for trivial internal steps
|
|
62956
|
+
`;
|
|
62957
|
+
var FINAL_ONLY_RULES_BLOCK = `- use that command only for the final user-facing reply
|
|
62958
|
+
- do not send user-facing progress updates for this conversation
|
|
62959
|
+
`;
|
|
62960
|
+
var FINAL_RULE_REQUIRED = "send exactly 1 final user-facing response";
|
|
62961
|
+
var FINAL_RULE_OPTIONAL = "final response is optional";
|
|
62962
|
+
var EMPTY_REPLY_COMMAND = "";
|
|
62963
|
+
var EMPTY_REPLY_RULES = "";
|
|
62964
|
+
var SLACK_REPLY_COMMAND_BASE = `{{command}} message send \\
|
|
62965
|
+
--channel slack \\
|
|
62966
|
+
{{account_clause}} --target channel:{{channel_id}} \\
|
|
62967
|
+
{{thread_clause}}`;
|
|
62968
|
+
var TELEGRAM_REPLY_COMMAND_BASE = `{{command}} message send \\
|
|
62969
|
+
--channel telegram \\
|
|
62970
|
+
{{account_clause}} --target {{chat_id}} \\
|
|
62971
|
+
{{thread_clause}}`;
|
|
62972
|
+
var ACCOUNT_CLAUSE = " --account {{account_id}} \\\n";
|
|
62973
|
+
var EMPTY_ACCOUNT_CLAUSE = "";
|
|
62974
|
+
var SLACK_THREAD_CLAUSE = " --thread-id {{thread_ts}} \\\n";
|
|
62975
|
+
var TELEGRAM_THREAD_CLAUSE = " --thread-id {{topic_id}} \\\n";
|
|
62976
|
+
var EMPTY_THREAD_CLAUSE = "";
|
|
62977
|
+
function buildAgentPromptText(params) {
|
|
62978
|
+
return buildChannelPromptText({
|
|
62979
|
+
...params,
|
|
62980
|
+
mode: "message"
|
|
62981
|
+
});
|
|
62734
62982
|
}
|
|
62735
|
-
function
|
|
62736
|
-
|
|
62737
|
-
|
|
62738
|
-
|
|
62739
|
-
|
|
62740
|
-
|
|
62741
|
-
|
|
62742
|
-
|
|
62743
|
-
|
|
62744
|
-
|
|
62745
|
-
|
|
62746
|
-
|
|
62747
|
-
|
|
62983
|
+
function buildSteeringPromptText(params) {
|
|
62984
|
+
return buildChannelPromptText({
|
|
62985
|
+
text: params.text,
|
|
62986
|
+
mode: "steer",
|
|
62987
|
+
protectedControlMutationRule: params.protectedControlMutationRule
|
|
62988
|
+
});
|
|
62989
|
+
}
|
|
62990
|
+
function buildChannelPromptText(params) {
|
|
62991
|
+
if (params.mode === "message" && !params.config?.enabled) {
|
|
62992
|
+
return params.text;
|
|
62993
|
+
}
|
|
62994
|
+
if (params.mode === "steer") {
|
|
62995
|
+
return renderTemplate(STEERING_TEMPLATE, {
|
|
62996
|
+
message_body: params.text,
|
|
62997
|
+
protected_control_suffix: renderProtectedControlSuffix(params.protectedControlMutationRule)
|
|
62748
62998
|
});
|
|
62749
|
-
lines.push(replyCommand, "When replying to the user:", "- put the user-facing message inside the --message body of that command", progressAllowed ? "- use that command to send progress updates and the final reply back to the conversation" : "- use that command only for the final user-facing reply", ...progressAllowed ? [`- send at most ${params.config.maxProgressMessages} progress updates`] : ["- do not send user-facing progress updates for this conversation"], params.config.requireFinalResponse ? "- send exactly 1 final user-facing response" : "- final response is optional", ...progressAllowed ? [
|
|
62750
|
-
"- keep progress updates short and meaningful",
|
|
62751
|
-
"- do not send progress updates for trivial internal steps"
|
|
62752
|
-
] : []);
|
|
62753
62999
|
}
|
|
62754
|
-
|
|
62755
|
-
|
|
63000
|
+
const promptParts = renderMessagePromptParts({
|
|
63001
|
+
identity: params.identity,
|
|
63002
|
+
config: params.config,
|
|
63003
|
+
responseMode: params.responseMode,
|
|
63004
|
+
streaming: params.streaming
|
|
63005
|
+
});
|
|
63006
|
+
return renderTemplate(BASE_TEMPLATE, {
|
|
63007
|
+
timestamp: renderPromptTimestamp(),
|
|
63008
|
+
identity_summary: renderIdentitySummary(params.identity),
|
|
63009
|
+
delivery_intro: promptParts.deliveryIntro,
|
|
63010
|
+
reply_command: promptParts.replyCommand,
|
|
63011
|
+
reply_rules: promptParts.replyRules,
|
|
63012
|
+
protected_control_suffix: renderProtectedControlSuffix(params.protectedControlMutationRule),
|
|
63013
|
+
message_body: params.text
|
|
63014
|
+
});
|
|
63015
|
+
}
|
|
63016
|
+
function renderMessagePromptParts(params) {
|
|
63017
|
+
const messageToolMode = (params.responseMode ?? "message-tool") === "message-tool";
|
|
63018
|
+
if (!messageToolMode) {
|
|
63019
|
+
return {
|
|
63020
|
+
deliveryIntro: DELIVERY_INTRO_CAPTURE_PANE,
|
|
63021
|
+
replyCommand: EMPTY_REPLY_COMMAND,
|
|
63022
|
+
replyRules: EMPTY_REPLY_RULES
|
|
63023
|
+
};
|
|
62756
63024
|
}
|
|
62757
|
-
|
|
62758
|
-
|
|
63025
|
+
const allowProgress = (params.streaming ?? "off") === "off";
|
|
63026
|
+
const progressPhrase = allowProgress ? PROGRESS_PHRASE : EMPTY_PROGRESS_PHRASE;
|
|
63027
|
+
const progressFlagSuffix = allowProgress ? PROGRESS_FLAG_SUFFIX : EMPTY_PROGRESS_FLAG_SUFFIX;
|
|
63028
|
+
const progressRulesBlock = allowProgress ? PROGRESS_RULES_BLOCK : FINAL_ONLY_RULES_BLOCK;
|
|
63029
|
+
const finalRuleLine = params.config.requireFinalResponse ? FINAL_RULE_REQUIRED : FINAL_RULE_OPTIONAL;
|
|
63030
|
+
return {
|
|
63031
|
+
deliveryIntro: renderTemplate(DELIVERY_INTRO, {
|
|
63032
|
+
progress_phrase: progressPhrase
|
|
63033
|
+
}),
|
|
63034
|
+
replyCommand: renderTemplate(REPLY_COMMAND, {
|
|
63035
|
+
reply_command_base: buildReplyCommandBase({
|
|
63036
|
+
command: getClisbotPromptCommand(),
|
|
63037
|
+
identity: params.identity
|
|
63038
|
+
}).trimEnd(),
|
|
63039
|
+
progress_flag_suffix: progressFlagSuffix
|
|
63040
|
+
}),
|
|
63041
|
+
replyRules: renderTemplate(REPLY_RULES, {
|
|
63042
|
+
progress_rules_block: renderTemplate(progressRulesBlock, {
|
|
63043
|
+
max_progress_messages: String(params.config.maxProgressMessages)
|
|
63044
|
+
}),
|
|
63045
|
+
final_rule_line: finalRuleLine
|
|
63046
|
+
})
|
|
63047
|
+
};
|
|
63048
|
+
}
|
|
63049
|
+
function renderProtectedControlSuffix(rule) {
|
|
63050
|
+
if (!rule) {
|
|
63051
|
+
return "";
|
|
63052
|
+
}
|
|
63053
|
+
return `
|
|
63054
|
+
|
|
63055
|
+
${rule}`;
|
|
63056
|
+
}
|
|
63057
|
+
function renderTemplate(template, values) {
|
|
63058
|
+
return template.replaceAll(/\{\{([a-zA-Z0-9_]+)\}\}/g, (_, key) => values[key] ?? "");
|
|
62759
63059
|
}
|
|
62760
63060
|
function renderPromptTimestamp() {
|
|
62761
63061
|
const date = new Date;
|
|
@@ -62821,42 +63121,29 @@ function renderNamedValue(label, name, id) {
|
|
|
62821
63121
|
const value = renderLabeledTarget(name, id);
|
|
62822
63122
|
return value ? `${label} ${value}` : "";
|
|
62823
63123
|
}
|
|
62824
|
-
function
|
|
62825
|
-
const lines = [`${params.command} message send \\`];
|
|
63124
|
+
function buildReplyCommandBase(params) {
|
|
62826
63125
|
if (params.identity.platform === "slack") {
|
|
62827
|
-
|
|
62828
|
-
|
|
62829
|
-
|
|
62830
|
-
|
|
62831
|
-
|
|
62832
|
-
|
|
62833
|
-
|
|
62834
|
-
|
|
62835
|
-
|
|
62836
|
-
|
|
62837
|
-
lines.push("<user-facing reply>");
|
|
62838
|
-
lines.push("__CLISBOT_MESSAGE__");
|
|
62839
|
-
lines.push(')" \\');
|
|
62840
|
-
lines.push(" [--media /absolute/path/to/file]");
|
|
62841
|
-
return lines.join(`
|
|
62842
|
-
`);
|
|
62843
|
-
}
|
|
62844
|
-
lines.push(" --channel telegram \\");
|
|
62845
|
-
if (params.identity.accountId) {
|
|
62846
|
-
lines.push(` --account ${params.identity.accountId} \\`);
|
|
63126
|
+
return renderTemplate(SLACK_REPLY_COMMAND_BASE, {
|
|
63127
|
+
command: params.command,
|
|
63128
|
+
account_clause: params.identity.accountId ? renderTemplate(ACCOUNT_CLAUSE, {
|
|
63129
|
+
account_id: params.identity.accountId
|
|
63130
|
+
}) : EMPTY_ACCOUNT_CLAUSE,
|
|
63131
|
+
channel_id: params.identity.channelId ?? "",
|
|
63132
|
+
thread_clause: params.identity.threadTs ? renderTemplate(SLACK_THREAD_CLAUSE, {
|
|
63133
|
+
thread_ts: params.identity.threadTs
|
|
63134
|
+
}) : EMPTY_THREAD_CLAUSE
|
|
63135
|
+
});
|
|
62847
63136
|
}
|
|
62848
|
-
|
|
62849
|
-
|
|
62850
|
-
|
|
62851
|
-
|
|
62852
|
-
|
|
62853
|
-
|
|
62854
|
-
|
|
62855
|
-
|
|
62856
|
-
|
|
62857
|
-
|
|
62858
|
-
return lines.join(`
|
|
62859
|
-
`);
|
|
63137
|
+
return renderTemplate(TELEGRAM_REPLY_COMMAND_BASE, {
|
|
63138
|
+
command: params.command,
|
|
63139
|
+
account_clause: params.identity.accountId ? renderTemplate(ACCOUNT_CLAUSE, {
|
|
63140
|
+
account_id: params.identity.accountId
|
|
63141
|
+
}) : EMPTY_ACCOUNT_CLAUSE,
|
|
63142
|
+
chat_id: params.identity.chatId ?? "",
|
|
63143
|
+
thread_clause: params.identity.topicId ? renderTemplate(TELEGRAM_THREAD_CLAUSE, {
|
|
63144
|
+
topic_id: params.identity.topicId
|
|
63145
|
+
}) : EMPTY_THREAD_CLAUSE
|
|
63146
|
+
});
|
|
62860
63147
|
}
|
|
62861
63148
|
|
|
62862
63149
|
// src/channels/surface-notifications.ts
|
|
@@ -63390,6 +63677,12 @@ function collapseBlankLines(lines) {
|
|
|
63390
63677
|
}
|
|
63391
63678
|
return collapsed;
|
|
63392
63679
|
}
|
|
63680
|
+
var DURATION_STATUS_PATTERN = String.raw`(?:\d+(?:h|m|s))(?:\s+\d+(?:h|m|s)){0,2}`;
|
|
63681
|
+
var CODEX_WORKING_STATUS_PATTERN = new RegExp(String.raw`^(?:[•◦·]\s*)?Working(?:\s*\()?${DURATION_STATUS_PATTERN}\b.*(?:esc to interrupt|interrupt)\)?$`, "i");
|
|
63682
|
+
var CODEX_INTERRUPT_FOOTER_PATTERN = new RegExp(String.raw`^(?:[•◦·]\s*)?${DURATION_STATUS_PATTERN}\s*[•◦·]?\s*esc to interrupt\)?$`, "i");
|
|
63683
|
+
var GEMINI_THINKING_STATUS_PATTERN = new RegExp(String.raw`^Thinking\.\.\. \(esc to cancel,\s*${DURATION_STATUS_PATTERN}\)$`, "i");
|
|
63684
|
+
var CLAUDE_WORKED_STATUS_PATTERN = new RegExp(String.raw`^(?:[✻✽*]\s*)?(?:Worked|Cooked) for ${DURATION_STATUS_PATTERN}$`, "i");
|
|
63685
|
+
var CLAUDE_TIMER_FOOTER_PATTERN = new RegExp(String.raw`\|\s*claude\s*\|.*\|\s*${DURATION_STATUS_PATTERN}\s*$`, "i");
|
|
63393
63686
|
function looksLikeUrlContinuation(line) {
|
|
63394
63687
|
const trimmed = line.trim();
|
|
63395
63688
|
return trimmed.startsWith("(") || trimmed.startsWith("http://") || trimmed.startsWith("https://");
|
|
@@ -63587,7 +63880,14 @@ function isInterruptStatusLine(line) {
|
|
|
63587
63880
|
if (!trimmed) {
|
|
63588
63881
|
return false;
|
|
63589
63882
|
}
|
|
63590
|
-
return
|
|
63883
|
+
return CODEX_WORKING_STATUS_PATTERN.test(trimmed) || CODEX_INTERRUPT_FOOTER_PATTERN.test(trimmed);
|
|
63884
|
+
}
|
|
63885
|
+
function isTimerDrivenStatusLine(line) {
|
|
63886
|
+
const trimmed = line.trim();
|
|
63887
|
+
if (!trimmed) {
|
|
63888
|
+
return false;
|
|
63889
|
+
}
|
|
63890
|
+
return isInterruptStatusLine(trimmed) || GEMINI_THINKING_STATUS_PATTERN.test(trimmed) || CLAUDE_WORKED_STATUS_PATTERN.test(trimmed) || CLAUDE_TIMER_FOOTER_PATTERN.test(trimmed);
|
|
63591
63891
|
}
|
|
63592
63892
|
function shouldDropCodexChromeLine(line) {
|
|
63593
63893
|
const trimmed = line.trim();
|
|
@@ -63601,14 +63901,14 @@ function shouldDropClaudeChromeLine(line) {
|
|
|
63601
63901
|
if (!trimmed) {
|
|
63602
63902
|
return false;
|
|
63603
63903
|
}
|
|
63604
|
-
return trimmed.includes("Claude Code v") || trimmed.includes("Welcome back!") || trimmed.includes("Tips for getting started") || trimmed.includes("Ask Claude to create a new app or clone a repository") || trimmed.includes("Recent activity") || trimmed.includes("No recent activity") || trimmed.includes("API Usage Billing") || trimmed.includes("shift+tab to cycle") || trimmed.includes("ctrl+o to expand") || trimmed.includes("ctrl+b ctrl+b") || trimmed.includes("run in background") || /^~\/\.clisbot\/(?:workspace\/)?[a-z0-9._/-]+$/i.test(trimmed) || trimmed.includes("| claude |") ||
|
|
63904
|
+
return trimmed.includes("Claude Code v") || trimmed.includes("Welcome back!") || trimmed.includes("Tips for getting started") || trimmed.includes("Ask Claude to create a new app or clone a repository") || trimmed.includes("Recent activity") || trimmed.includes("No recent activity") || trimmed.includes("API Usage Billing") || trimmed.includes("shift+tab to cycle") || trimmed.includes("ctrl+o to expand") || trimmed.includes("ctrl+b ctrl+b") || trimmed.includes("run in background") || /^~\/\.clisbot\/(?:workspace\/)?[a-z0-9._/-]+$/i.test(trimmed) || trimmed.includes("| claude |") || isTimerDrivenStatusLine(trimmed) || trimmed.startsWith("⏵⏵") || trimmed.startsWith("❯") || isProgressLine(trimmed) || /^[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+$/.test(trimmed) || /^[╭╰│]/.test(trimmed) || /^─+$/.test(trimmed) || /^[▐▛▜▌▝▘ ]+$/.test(trimmed) || /^[▐▛▜▌▝▘ ]+.+$/.test(trimmed);
|
|
63605
63905
|
}
|
|
63606
63906
|
function shouldDropGeminiChromeLine(line) {
|
|
63607
63907
|
const trimmed = line.trim();
|
|
63608
63908
|
if (!trimmed) {
|
|
63609
63909
|
return false;
|
|
63610
63910
|
}
|
|
63611
|
-
return trimmed.includes("Gemini CLI v") || trimmed.includes("Signed in with Google") || trimmed.includes("Plan:") || /^[▝▜▄▗▟▀ ]+$/.test(trimmed) || trimmed.includes("We're making changes to Gemini CLI") || trimmed.includes("What's Changing:") || trimmed.includes("How it affects you:") || trimmed.includes("Read more: https://goo.gle/geminicli-updates") || trimmed.includes("Skipping project agents due to untrusted folder.") || trimmed.includes("Do you trust the files in this folder?") || trimmed.includes("Trusting a folder allows Gemini CLI to load its local configurations") || trimmed === "1. Trust folder (default)" || trimmed === "2. Trust parent folder (workspaces)" || trimmed === "3. Don't trust" || trimmed.includes("Tips for getting started") || /^Create GEMINI\.md files to customize your interactions$/i.test(trimmed) || /^\/help for more information$/i.test(trimmed) || /^Ask coding questions, edit code or run commands$/i.test(trimmed) || /^Be specific for the best results$/i.test(trimmed) || trimmed.includes("? for shortcuts") || trimmed.includes("YOLO Ctrl+Y") || trimmed.includes("Type your message or @path/to/file") || trimmed.includes("workspace (/directory)") || /^~\/.+\s+\S+\s+no sandbox\s+\S+/i.test(trimmed) ||
|
|
63911
|
+
return trimmed.includes("Gemini CLI v") || trimmed.includes("Signed in with Google") || trimmed.includes("Plan:") || /^[▝▜▄▗▟▀ ]+$/.test(trimmed) || trimmed.includes("We're making changes to Gemini CLI") || trimmed.includes("What's Changing:") || trimmed.includes("How it affects you:") || trimmed.includes("Read more: https://goo.gle/geminicli-updates") || trimmed.includes("Skipping project agents due to untrusted folder.") || trimmed.includes("Do you trust the files in this folder?") || trimmed.includes("Trusting a folder allows Gemini CLI to load its local configurations") || trimmed === "1. Trust folder (default)" || trimmed === "2. Trust parent folder (workspaces)" || trimmed === "3. Don't trust" || trimmed.includes("Tips for getting started") || /^Create GEMINI\.md files to customize your interactions$/i.test(trimmed) || /^\/help for more information$/i.test(trimmed) || /^Ask coding questions, edit code or run commands$/i.test(trimmed) || /^Be specific for the best results$/i.test(trimmed) || trimmed.includes("? for shortcuts") || trimmed.includes("YOLO Ctrl+Y") || trimmed.includes("Type your message or @path/to/file") || trimmed.includes("workspace (/directory)") || /^~\/.+\s+\S+\s+no sandbox\s+\S+/i.test(trimmed) || isTimerDrivenStatusLine(trimmed) || /^[╭╰│]/.test(trimmed) || /^[-▀▄]{10,}$/.test(trimmed) || /^─+$/.test(trimmed);
|
|
63612
63912
|
}
|
|
63613
63913
|
function normalizeBoundaryLine(line) {
|
|
63614
63914
|
return line.trim().replace(/^(?::eight_spoked_asterisk:|[-*•◦·✽✶])\s+/, "");
|
|
@@ -63642,16 +63942,21 @@ function collapseAdjacentDuplicateLines(raw) {
|
|
|
63642
63942
|
return collapseBlankLines(collapsed).join(`
|
|
63643
63943
|
`).trim();
|
|
63644
63944
|
}
|
|
63645
|
-
function
|
|
63945
|
+
function cleanInteractionSnapshotInternal(raw, options) {
|
|
63646
63946
|
const lines = splitNormalizedLines(raw);
|
|
63647
63947
|
const isCodex = looksLikeCodexSnapshot(lines);
|
|
63648
63948
|
const isClaude = looksLikeClaudeSnapshot(lines);
|
|
63649
63949
|
const isGemini = looksLikeGeminiSnapshot(lines);
|
|
63650
63950
|
const promptStripped = isCodex ? dropCodexPromptBlocks(lines) : isClaude ? dropClaudePromptBlocks(lines) : isGemini ? dropGeminiPromptBlocks(lines) : lines;
|
|
63951
|
+
const timerStatusLines = [];
|
|
63651
63952
|
const filtered = promptStripped.filter((line) => {
|
|
63652
63953
|
if (shouldDropDeliveryReportLine(line)) {
|
|
63653
63954
|
return false;
|
|
63654
63955
|
}
|
|
63956
|
+
if (options?.preserveTimerStatusLines && isTimerDrivenStatusLine(line)) {
|
|
63957
|
+
timerStatusLines.push(line.trim());
|
|
63958
|
+
return false;
|
|
63959
|
+
}
|
|
63655
63960
|
if (isCodex && shouldDropCodexChromeLine(line)) {
|
|
63656
63961
|
return false;
|
|
63657
63962
|
}
|
|
@@ -63665,8 +63970,21 @@ function cleanInteractionSnapshot(raw) {
|
|
|
63665
63970
|
});
|
|
63666
63971
|
const normalized = isCodex ? unwrapCodexMessageBlocks(filtered) : isClaude ? unwrapClaudeMessageBlocks(filtered) : isGemini ? filtered.map((line) => line.replace(/^\s*>\s*/, "")) : filtered;
|
|
63667
63972
|
const unwrapped = unwrapSoftWrappedLines(normalized);
|
|
63668
|
-
|
|
63973
|
+
const cleanedBody = collapseAdjacentDuplicateLines(collapseBlankLines(trimBlankLines(unwrapped)).join(`
|
|
63669
63974
|
`));
|
|
63975
|
+
const cleanedTimerStatus = options?.preserveTimerStatusLines ? collapseAdjacentDuplicateLines(collapseBlankLines(trimBlankLines(timerStatusLines)).join(`
|
|
63976
|
+
`)) : "";
|
|
63977
|
+
return [cleanedBody, cleanedTimerStatus].filter(Boolean).join(`
|
|
63978
|
+
|
|
63979
|
+
`).trim();
|
|
63980
|
+
}
|
|
63981
|
+
function cleanInteractionSnapshot(raw) {
|
|
63982
|
+
return cleanInteractionSnapshotInternal(raw);
|
|
63983
|
+
}
|
|
63984
|
+
function cleanRunningInteractionSnapshot(raw) {
|
|
63985
|
+
return cleanInteractionSnapshotInternal(raw, {
|
|
63986
|
+
preserveTimerStatusLines: true
|
|
63987
|
+
});
|
|
63670
63988
|
}
|
|
63671
63989
|
// src/shared/transcript-delta.ts
|
|
63672
63990
|
function diffText(previous, current) {
|
|
@@ -63727,16 +64045,8 @@ function extractScrolledAppend(previous, current) {
|
|
|
63727
64045
|
}
|
|
63728
64046
|
return "";
|
|
63729
64047
|
}
|
|
63730
|
-
function
|
|
63731
|
-
|
|
63732
|
-
const current = cleanInteractionSnapshot(currentSnapshot);
|
|
63733
|
-
if (!current || current === previous) {
|
|
63734
|
-
return "";
|
|
63735
|
-
}
|
|
63736
|
-
if (!previous) {
|
|
63737
|
-
return current;
|
|
63738
|
-
}
|
|
63739
|
-
return extractScrolledAppend(previous, current);
|
|
64048
|
+
function deriveRunningInteractionSnapshot(currentSnapshot) {
|
|
64049
|
+
return cleanRunningInteractionSnapshot(currentSnapshot);
|
|
63740
64050
|
}
|
|
63741
64051
|
function deriveInteractionText(initialSnapshot, currentSnapshot) {
|
|
63742
64052
|
const previous = cleanInteractionSnapshot(initialSnapshot);
|
|
@@ -63940,6 +64250,36 @@ function renderInteractionBody(params) {
|
|
|
63940
64250
|
}
|
|
63941
64251
|
return truncateTail(completedBody, params.maxChars);
|
|
63942
64252
|
}
|
|
64253
|
+
function normalizeLeadingStatusLine(line) {
|
|
64254
|
+
return line.trim().replace(/^[_*`\s]+|[_*`\s]+$/g, "");
|
|
64255
|
+
}
|
|
64256
|
+
function startsWithExplicitErrorLabel(body) {
|
|
64257
|
+
const firstLine = body.split(`
|
|
64258
|
+
`).map((line) => normalizeLeadingStatusLine(line)).find((line) => line.length > 0);
|
|
64259
|
+
if (!firstLine) {
|
|
64260
|
+
return false;
|
|
64261
|
+
}
|
|
64262
|
+
return /^(error|failed|failure|denied|forbidden)(?::|\.)/i.test(firstLine);
|
|
64263
|
+
}
|
|
64264
|
+
function shouldInlineErrorPrefix(body) {
|
|
64265
|
+
return !body.includes(`
|
|
64266
|
+
`) && !body.includes("```");
|
|
64267
|
+
}
|
|
64268
|
+
function renderErrorInteractionBody(body, footer) {
|
|
64269
|
+
const trimmedBody = body.trim();
|
|
64270
|
+
if (!trimmedBody) {
|
|
64271
|
+
return footer;
|
|
64272
|
+
}
|
|
64273
|
+
if (startsWithExplicitErrorLabel(trimmedBody)) {
|
|
64274
|
+
return trimmedBody;
|
|
64275
|
+
}
|
|
64276
|
+
if (shouldInlineErrorPrefix(trimmedBody)) {
|
|
64277
|
+
return `Error: ${trimmedBody}`;
|
|
64278
|
+
}
|
|
64279
|
+
return `${trimmedBody}
|
|
64280
|
+
|
|
64281
|
+
${footer}`;
|
|
64282
|
+
}
|
|
63943
64283
|
function renderSlackInteraction(params) {
|
|
63944
64284
|
const body = renderInteractionBody(params);
|
|
63945
64285
|
if (params.status === "queued") {
|
|
@@ -63967,9 +64307,7 @@ _Timed out waiting for more output._` : "_Timed out waiting for visible output._
|
|
|
63967
64307
|
_${note}_` : `_${note}_`;
|
|
63968
64308
|
}
|
|
63969
64309
|
if (params.status === "error") {
|
|
63970
|
-
return body
|
|
63971
|
-
|
|
63972
|
-
_Error._` : "_Error._";
|
|
64310
|
+
return renderErrorInteractionBody(body, "_Error._");
|
|
63973
64311
|
}
|
|
63974
64312
|
return body || "_Completed with no new visible output._";
|
|
63975
64313
|
}
|
|
@@ -64000,9 +64338,7 @@ Timed out waiting for more output.` : "Timed out waiting for visible output.";
|
|
|
64000
64338
|
${note}` : note;
|
|
64001
64339
|
}
|
|
64002
64340
|
if (params.status === "error") {
|
|
64003
|
-
return body
|
|
64004
|
-
|
|
64005
|
-
Error.` : "Error.";
|
|
64341
|
+
return renderErrorInteractionBody(body, "Error.");
|
|
64006
64342
|
}
|
|
64007
64343
|
return body || "Completed with no new visible output.";
|
|
64008
64344
|
}
|
|
@@ -64452,6 +64788,7 @@ var TMUX_DUPLICATE_SESSION_PATTERN = /duplicate session:/i;
|
|
|
64452
64788
|
var TMUX_TRANSIENT_TARGET_PATTERN = /(?:no current target|can't find pane|can't find window)/i;
|
|
64453
64789
|
var SESSION_READY_CAPTURE_RETRY_COUNT = 5;
|
|
64454
64790
|
var SESSION_READY_CAPTURE_RETRY_DELAY_MS = 100;
|
|
64791
|
+
var SESSION_ID_CAPTURE_FAILURE_COOLDOWN_MS = 15000;
|
|
64455
64792
|
function summarizeSnapshot(snapshot) {
|
|
64456
64793
|
const compact = snapshot.split(`
|
|
64457
64794
|
`).map((line) => line.trim()).filter(Boolean).join(" ").slice(0, 220);
|
|
@@ -64483,6 +64820,7 @@ class RunnerService {
|
|
|
64483
64820
|
sessionState;
|
|
64484
64821
|
resolveTarget;
|
|
64485
64822
|
cleanupInFlight = false;
|
|
64823
|
+
sessionIdentityCaptureRetryAt = new Map;
|
|
64486
64824
|
constructor(loadedConfig, tmux, sessionState, resolveTarget) {
|
|
64487
64825
|
this.loadedConfig = loadedConfig;
|
|
64488
64826
|
this.tmux = tmux;
|
|
@@ -64534,12 +64872,24 @@ class RunnerService {
|
|
|
64534
64872
|
async syncSessionIdentity(resolved) {
|
|
64535
64873
|
const existing = await this.sessionState.getEntry(resolved.sessionKey);
|
|
64536
64874
|
if (existing?.sessionId) {
|
|
64875
|
+
this.sessionIdentityCaptureRetryAt.delete(resolved.sessionKey);
|
|
64537
64876
|
return this.sessionState.touchSessionEntry(resolved, {
|
|
64538
64877
|
sessionId: existing.sessionId,
|
|
64539
64878
|
runnerCommand: resolved.runner.command
|
|
64540
64879
|
});
|
|
64541
64880
|
}
|
|
64881
|
+
const retryAt = this.sessionIdentityCaptureRetryAt.get(resolved.sessionKey) ?? 0;
|
|
64882
|
+
if (retryAt > Date.now()) {
|
|
64883
|
+
return this.sessionState.touchSessionEntry(resolved, {
|
|
64884
|
+
runnerCommand: resolved.runner.command
|
|
64885
|
+
});
|
|
64886
|
+
}
|
|
64542
64887
|
const sessionId = await this.captureSessionIdentity(resolved);
|
|
64888
|
+
if (sessionId) {
|
|
64889
|
+
this.sessionIdentityCaptureRetryAt.delete(resolved.sessionKey);
|
|
64890
|
+
} else {
|
|
64891
|
+
this.sessionIdentityCaptureRetryAt.set(resolved.sessionKey, Date.now() + SESSION_ID_CAPTURE_FAILURE_COOLDOWN_MS);
|
|
64892
|
+
}
|
|
64543
64893
|
return this.sessionState.touchSessionEntry(resolved, {
|
|
64544
64894
|
sessionId,
|
|
64545
64895
|
runnerCommand: resolved.runner.command
|
|
@@ -64694,6 +65044,7 @@ class RunnerService {
|
|
|
64694
65044
|
sessionName: resolved.sessionName,
|
|
64695
65045
|
stateDir: this.loadedConfig.stateDir
|
|
64696
65046
|
});
|
|
65047
|
+
this.sessionIdentityCaptureRetryAt.delete(resolved.sessionKey);
|
|
64697
65048
|
try {
|
|
64698
65049
|
await this.tmux.newSession({
|
|
64699
65050
|
sessionName: resolved.sessionName,
|
|
@@ -64936,15 +65287,19 @@ class RunnerService {
|
|
|
64936
65287
|
}
|
|
64937
65288
|
|
|
64938
65289
|
// src/agents/run-recovery.ts
|
|
65290
|
+
var MID_RUN_RECOVERY_MAX_ATTEMPTS = 2;
|
|
65291
|
+
var MID_RUN_RECOVERY_CONTINUE_PROMPT = "continue exactly where you left off";
|
|
64939
65292
|
function mergeRunSnapshot(snapshotPrefix, snapshot) {
|
|
64940
65293
|
return appendInteractionText(snapshotPrefix, snapshot);
|
|
64941
65294
|
}
|
|
64942
|
-
function buildRunRecoveryNote(kind) {
|
|
65295
|
+
function buildRunRecoveryNote(kind, params) {
|
|
64943
65296
|
if (kind === "resume-attempt") {
|
|
64944
|
-
|
|
65297
|
+
const attempt = params?.attempt ?? 1;
|
|
65298
|
+
const maxAttempts = params?.maxAttempts ?? MID_RUN_RECOVERY_MAX_ATTEMPTS;
|
|
65299
|
+
return `Runner session was lost. Attempting recovery ${attempt}/${maxAttempts} by reopening the same conversation context.`;
|
|
64945
65300
|
}
|
|
64946
65301
|
if (kind === "resume-success") {
|
|
64947
|
-
return "Recovery succeeded.
|
|
65302
|
+
return "Recovery succeeded. Asking the runner to continue exactly where it left off.";
|
|
64948
65303
|
}
|
|
64949
65304
|
if (kind === "fresh-attempt") {
|
|
64950
65305
|
return "The previous runner session could not be resumed. Opening a fresh runner session 2/2 without replaying your prompt.";
|
|
@@ -64956,11 +65311,12 @@ function buildRunRecoveryNote(kind) {
|
|
|
64956
65311
|
var FIRST_OUTPUT_POLL_INTERVAL_MS = 250;
|
|
64957
65312
|
async function monitorTmuxRun(params) {
|
|
64958
65313
|
let previousSnapshot = params.initialSnapshot;
|
|
64959
|
-
let
|
|
64960
|
-
let
|
|
64961
|
-
let
|
|
65314
|
+
let previousRunningSnapshot = "";
|
|
65315
|
+
let lastActivityAt = params.startedAt;
|
|
65316
|
+
let sawActivity = false;
|
|
64962
65317
|
let detachedNotified = params.detachedAlready;
|
|
64963
65318
|
let firstMeaningfulDeltaLogged = false;
|
|
65319
|
+
let noOutputThresholdLogged = false;
|
|
64964
65320
|
if (params.prompt) {
|
|
64965
65321
|
logLatencyDebug("tmux-submit-start", params.timingContext, {
|
|
64966
65322
|
sessionName: params.sessionName,
|
|
@@ -64981,61 +65337,57 @@ async function monitorTmuxRun(params) {
|
|
|
64981
65337
|
});
|
|
64982
65338
|
}
|
|
64983
65339
|
while (true) {
|
|
64984
|
-
await sleep(
|
|
65340
|
+
await sleep(sawActivity ? params.updateIntervalMs : Math.min(params.updateIntervalMs, FIRST_OUTPUT_POLL_INTERVAL_MS));
|
|
64985
65341
|
const snapshot = normalizePaneText(await params.tmux.capturePane(params.sessionName, params.captureLines));
|
|
64986
65342
|
const now = Date.now();
|
|
64987
|
-
|
|
64988
|
-
|
|
64989
|
-
|
|
64990
|
-
|
|
64991
|
-
|
|
64992
|
-
|
|
64993
|
-
if (
|
|
64994
|
-
|
|
64995
|
-
|
|
64996
|
-
|
|
64997
|
-
|
|
64998
|
-
logLatencyDebug("tmux-first-meaningful-delta", params.timingContext, {
|
|
64999
|
-
sessionName: params.sessionName,
|
|
65000
|
-
elapsedMs: now - params.startedAt
|
|
65001
|
-
});
|
|
65002
|
-
}
|
|
65003
|
-
await params.onRunning({
|
|
65004
|
-
snapshot: cumulativeInteractionSnapshot,
|
|
65005
|
-
fullSnapshot: snapshot,
|
|
65006
|
-
initialSnapshot: params.initialSnapshot
|
|
65343
|
+
const runningSnapshot = deriveRunningInteractionSnapshot(snapshot);
|
|
65344
|
+
previousSnapshot = snapshot;
|
|
65345
|
+
if (runningSnapshot && runningSnapshot !== previousRunningSnapshot) {
|
|
65346
|
+
previousRunningSnapshot = runningSnapshot;
|
|
65347
|
+
lastActivityAt = now;
|
|
65348
|
+
sawActivity = true;
|
|
65349
|
+
if (!firstMeaningfulDeltaLogged) {
|
|
65350
|
+
firstMeaningfulDeltaLogged = true;
|
|
65351
|
+
logLatencyDebug("tmux-first-meaningful-delta", params.timingContext, {
|
|
65352
|
+
sessionName: params.sessionName,
|
|
65353
|
+
elapsedMs: now - params.startedAt
|
|
65007
65354
|
});
|
|
65008
65355
|
}
|
|
65356
|
+
await params.onRunning({
|
|
65357
|
+
snapshot: runningSnapshot,
|
|
65358
|
+
fullSnapshot: snapshot,
|
|
65359
|
+
initialSnapshot: params.initialSnapshot
|
|
65360
|
+
});
|
|
65009
65361
|
}
|
|
65010
65362
|
if (!detachedNotified && now - params.startedAt >= params.maxRuntimeMs) {
|
|
65011
65363
|
detachedNotified = true;
|
|
65012
65364
|
await params.onDetached({
|
|
65013
|
-
snapshot:
|
|
65365
|
+
snapshot: previousRunningSnapshot || deriveInteractionText(params.initialSnapshot, snapshot),
|
|
65014
65366
|
fullSnapshot: previousSnapshot,
|
|
65015
65367
|
initialSnapshot: params.initialSnapshot
|
|
65016
65368
|
});
|
|
65017
65369
|
}
|
|
65018
|
-
if (
|
|
65370
|
+
if (sawActivity && now - lastActivityAt >= params.idleTimeoutMs) {
|
|
65019
65371
|
await params.onCompleted({
|
|
65020
|
-
snapshot:
|
|
65372
|
+
snapshot: deriveInteractionText(params.initialSnapshot, previousSnapshot),
|
|
65021
65373
|
fullSnapshot: previousSnapshot,
|
|
65022
65374
|
initialSnapshot: params.initialSnapshot
|
|
65023
65375
|
});
|
|
65024
65376
|
return;
|
|
65025
65377
|
}
|
|
65026
|
-
if (!
|
|
65027
|
-
|
|
65028
|
-
|
|
65029
|
-
|
|
65030
|
-
|
|
65378
|
+
if (!noOutputThresholdLogged && !sawActivity && now - params.startedAt >= params.noOutputTimeoutMs) {
|
|
65379
|
+
noOutputThresholdLogged = true;
|
|
65380
|
+
logLatencyDebug("tmux-no-output-threshold-crossed", params.timingContext, {
|
|
65381
|
+
sessionName: params.sessionName,
|
|
65382
|
+
elapsedMs: now - params.startedAt
|
|
65031
65383
|
});
|
|
65032
|
-
return;
|
|
65033
65384
|
}
|
|
65034
65385
|
}
|
|
65035
65386
|
}
|
|
65036
65387
|
|
|
65037
65388
|
// src/agents/session-service.ts
|
|
65038
65389
|
var OBSERVER_RETRYABLE_FAILURE_LIMIT = 3;
|
|
65390
|
+
var DETACHED_OBSERVER_INTERVAL_MS = 5 * 60000;
|
|
65039
65391
|
function formatObserverError(error) {
|
|
65040
65392
|
return error instanceof Error ? error.stack ?? error.message : String(error);
|
|
65041
65393
|
}
|
|
@@ -65283,7 +65635,10 @@ class SessionService {
|
|
|
65283
65635
|
detached: false
|
|
65284
65636
|
};
|
|
65285
65637
|
}
|
|
65286
|
-
observer.mode = "
|
|
65638
|
+
observer.mode = "poll";
|
|
65639
|
+
observer.intervalMs = DETACHED_OBSERVER_INTERVAL_MS;
|
|
65640
|
+
observer.expiresAt = undefined;
|
|
65641
|
+
observer.lastSentAt = Date.now();
|
|
65287
65642
|
run.observerFailures.delete(observerId);
|
|
65288
65643
|
return {
|
|
65289
65644
|
detached: true
|
|
@@ -65306,7 +65661,19 @@ class SessionService {
|
|
|
65306
65661
|
this.activeRuns.clear();
|
|
65307
65662
|
}
|
|
65308
65663
|
buildDetachedNote(resolved) {
|
|
65309
|
-
return `This session has been running for over ${resolved.stream.maxRuntimeLabel}. clisbot will keep monitoring it and
|
|
65664
|
+
return `This session has been running for over ${resolved.stream.maxRuntimeLabel}. clisbot will keep monitoring it, switch this thread to sparse progress updates, and post the final result here when it completes. Use \`/attach\` to resume live updates, \`/watch every 30s\` for interval updates, or \`/stop\` to interrupt it.`;
|
|
65665
|
+
}
|
|
65666
|
+
applyDetachedObserverPolicy(run) {
|
|
65667
|
+
const now = Date.now();
|
|
65668
|
+
for (const observer of run.observers.values()) {
|
|
65669
|
+
if (observer.mode !== "live") {
|
|
65670
|
+
continue;
|
|
65671
|
+
}
|
|
65672
|
+
observer.mode = "poll";
|
|
65673
|
+
observer.intervalMs = DETACHED_OBSERVER_INTERVAL_MS;
|
|
65674
|
+
observer.expiresAt = undefined;
|
|
65675
|
+
observer.lastSentAt = now;
|
|
65676
|
+
}
|
|
65310
65677
|
}
|
|
65311
65678
|
createRunUpdate(params) {
|
|
65312
65679
|
return {
|
|
@@ -65416,10 +65783,13 @@ class SessionService {
|
|
|
65416
65783
|
agentId: run.resolved.agentId,
|
|
65417
65784
|
sessionKey: run.resolved.sessionKey
|
|
65418
65785
|
};
|
|
65786
|
+
const recoveryAttempt = params.recoveryAttempt ?? 1;
|
|
65419
65787
|
const snapshotPrefix = run.latestUpdate.snapshot;
|
|
65420
|
-
const previousFullSnapshot = run.latestUpdate.fullSnapshot;
|
|
65421
65788
|
const detachedAlready = run.latestUpdate.status === "detached";
|
|
65422
|
-
await this.notifyRecoveryStep(run, buildRunRecoveryNote("resume-attempt"
|
|
65789
|
+
await this.notifyRecoveryStep(run, buildRunRecoveryNote("resume-attempt", {
|
|
65790
|
+
attempt: recoveryAttempt,
|
|
65791
|
+
maxAttempts: MID_RUN_RECOVERY_MAX_ATTEMPTS
|
|
65792
|
+
}));
|
|
65423
65793
|
try {
|
|
65424
65794
|
const recovered = await this.runnerSessions.reopenRunContext(target, params.timingContext);
|
|
65425
65795
|
const currentRun = this.activeRuns.get(sessionKey);
|
|
@@ -65438,15 +65808,22 @@ class SessionService {
|
|
|
65438
65808
|
});
|
|
65439
65809
|
await this.notifyRunObservers(currentRun, currentRun.latestUpdate);
|
|
65440
65810
|
this.startRunMonitor(sessionKey, {
|
|
65441
|
-
prompt:
|
|
65442
|
-
initialSnapshot:
|
|
65811
|
+
prompt: MID_RUN_RECOVERY_CONTINUE_PROMPT,
|
|
65812
|
+
initialSnapshot: recovered.initialSnapshot,
|
|
65443
65813
|
startedAt: currentRun.startedAt,
|
|
65444
65814
|
detachedAlready,
|
|
65445
65815
|
timingContext: params.timingContext,
|
|
65446
|
-
snapshotPrefix
|
|
65816
|
+
snapshotPrefix,
|
|
65817
|
+
recoveryAttempt
|
|
65447
65818
|
});
|
|
65448
65819
|
return true;
|
|
65449
|
-
} catch {
|
|
65820
|
+
} catch (reopenError) {
|
|
65821
|
+
if (recoveryAttempt < MID_RUN_RECOVERY_MAX_ATTEMPTS && this.runnerSessions.canRecoverMidRun(reopenError)) {
|
|
65822
|
+
return await this.recoverLostMidRun(sessionKey, {
|
|
65823
|
+
timingContext: params.timingContext,
|
|
65824
|
+
recoveryAttempt: recoveryAttempt + 1
|
|
65825
|
+
}, reopenError);
|
|
65826
|
+
}
|
|
65450
65827
|
const currentRun = this.activeRuns.get(sessionKey);
|
|
65451
65828
|
if (!currentRun) {
|
|
65452
65829
|
return true;
|
|
@@ -65527,7 +65904,8 @@ class SessionService {
|
|
|
65527
65904
|
startedAt: params.startedAt,
|
|
65528
65905
|
detachedAt: Date.now()
|
|
65529
65906
|
});
|
|
65530
|
-
currentRun
|
|
65907
|
+
await this.notifyRunObservers(currentRun, detachedUpdate);
|
|
65908
|
+
this.applyDetachedObserverPolicy(currentRun);
|
|
65531
65909
|
currentRun.initialResult.resolve(detachedUpdate);
|
|
65532
65910
|
},
|
|
65533
65911
|
onCompleted: async (update) => {
|
|
@@ -65539,20 +65917,13 @@ class SessionService {
|
|
|
65539
65917
|
initialSnapshot: update.initialSnapshot
|
|
65540
65918
|
});
|
|
65541
65919
|
await this.finishActiveRun(sessionKey, runUpdate);
|
|
65542
|
-
},
|
|
65543
|
-
onTimeout: async (update) => {
|
|
65544
|
-
const runUpdate = this.createRunUpdate({
|
|
65545
|
-
resolved: run.resolved,
|
|
65546
|
-
status: "timeout",
|
|
65547
|
-
snapshot: mergeRunSnapshot(params.snapshotPrefix ?? "", update.snapshot),
|
|
65548
|
-
fullSnapshot: update.fullSnapshot,
|
|
65549
|
-
initialSnapshot: update.initialSnapshot
|
|
65550
|
-
});
|
|
65551
|
-
await this.finishActiveRun(sessionKey, runUpdate);
|
|
65552
65920
|
}
|
|
65553
65921
|
});
|
|
65554
65922
|
} catch (error) {
|
|
65555
|
-
if (await this.recoverLostMidRun(sessionKey, {
|
|
65923
|
+
if (await this.recoverLostMidRun(sessionKey, {
|
|
65924
|
+
timingContext: params.timingContext,
|
|
65925
|
+
recoveryAttempt: (params.recoveryAttempt ?? 0) + 1
|
|
65926
|
+
}, error)) {
|
|
65556
65927
|
return;
|
|
65557
65928
|
}
|
|
65558
65929
|
await this.failActiveRun(sessionKey, await this.runnerSessions.mapRunError(error, run.resolved.sessionName, run.latestUpdate.fullSnapshot));
|
|
@@ -65562,6 +65933,28 @@ class SessionService {
|
|
|
65562
65933
|
}
|
|
65563
65934
|
|
|
65564
65935
|
// src/agents/agent-service.ts
|
|
65936
|
+
function shellQuote3(value) {
|
|
65937
|
+
if (/^[a-zA-Z0-9_./:@=-]+$/.test(value)) {
|
|
65938
|
+
return value;
|
|
65939
|
+
}
|
|
65940
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
65941
|
+
}
|
|
65942
|
+
function buildCommandString2(command, args) {
|
|
65943
|
+
return [command, ...args].map(shellQuote3).join(" ");
|
|
65944
|
+
}
|
|
65945
|
+
function stripWorkspaceArgs(args) {
|
|
65946
|
+
const filtered = [];
|
|
65947
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
65948
|
+
const current = args[index];
|
|
65949
|
+
if (current === "-C") {
|
|
65950
|
+
index += 1;
|
|
65951
|
+
continue;
|
|
65952
|
+
}
|
|
65953
|
+
filtered.push(current);
|
|
65954
|
+
}
|
|
65955
|
+
return filtered;
|
|
65956
|
+
}
|
|
65957
|
+
|
|
65565
65958
|
class AgentService {
|
|
65566
65959
|
loadedConfig;
|
|
65567
65960
|
tmuxClient;
|
|
@@ -65652,6 +66045,15 @@ class AgentService {
|
|
|
65652
66045
|
async getSessionRuntime(target) {
|
|
65653
66046
|
return this.sessionState.getSessionRuntime(target);
|
|
65654
66047
|
}
|
|
66048
|
+
async getSessionDiagnostics(target) {
|
|
66049
|
+
const resolved = this.resolveTarget(target);
|
|
66050
|
+
const entry = await this.sessionState.getEntry(target.sessionKey);
|
|
66051
|
+
const sessionId = entry?.sessionId?.trim() || undefined;
|
|
66052
|
+
return {
|
|
66053
|
+
sessionId,
|
|
66054
|
+
resumeCommand: this.buildResumeCommandPreview(resolved, sessionId)
|
|
66055
|
+
};
|
|
66056
|
+
}
|
|
65655
66057
|
async listActiveSessionRuntimes() {
|
|
65656
66058
|
return this.sessionState.listActiveSessionRuntimes();
|
|
65657
66059
|
}
|
|
@@ -65894,6 +66296,21 @@ class AgentService {
|
|
|
65894
66296
|
this.scheduleIntervalLoopTimer(persisted.id, Math.max(0, persisted.nextRunAt - Date.now()));
|
|
65895
66297
|
}
|
|
65896
66298
|
}
|
|
66299
|
+
buildResumeCommandPreview(resolved, sessionId) {
|
|
66300
|
+
if (!sessionId || resolved.runner.sessionId.resume.mode !== "command") {
|
|
66301
|
+
return;
|
|
66302
|
+
}
|
|
66303
|
+
const values = {
|
|
66304
|
+
agentId: resolved.agentId,
|
|
66305
|
+
workspace: resolved.workspacePath,
|
|
66306
|
+
sessionName: resolved.sessionName,
|
|
66307
|
+
sessionKey: resolved.sessionKey,
|
|
66308
|
+
sessionId
|
|
66309
|
+
};
|
|
66310
|
+
const command = resolved.runner.sessionId.resume.command ?? resolved.runner.command;
|
|
66311
|
+
const args = stripWorkspaceArgs(resolved.runner.sessionId.resume.args.map((value) => applyTemplate(value, values)));
|
|
66312
|
+
return buildCommandString2(command, args);
|
|
66313
|
+
}
|
|
65897
66314
|
async isManagedLoopPersisted(managed) {
|
|
65898
66315
|
const entry = await this.sessionState.getEntry(managed.target.sessionKey);
|
|
65899
66316
|
return (entry?.intervalLoops ?? []).some((loop) => loop.id === managed.loop.id);
|
|
@@ -66398,6 +66815,12 @@ function parseAgentCommand(text, options = {}) {
|
|
|
66398
66815
|
if (lowered === "loop") {
|
|
66399
66816
|
const loopText = withoutSlash.slice(command.length).trim();
|
|
66400
66817
|
const loweredLoopText = loopText.toLowerCase();
|
|
66818
|
+
if (!loweredLoopText || loweredLoopText === "help") {
|
|
66819
|
+
return {
|
|
66820
|
+
type: "control",
|
|
66821
|
+
name: "loop-help"
|
|
66822
|
+
};
|
|
66823
|
+
}
|
|
66401
66824
|
if (loweredLoopText === "status") {
|
|
66402
66825
|
return {
|
|
66403
66826
|
type: "loop-control",
|
|
@@ -66445,6 +66868,12 @@ function parseAgentCommand(text, options = {}) {
|
|
|
66445
66868
|
const queueText = withoutSlash.slice(command.length).trim();
|
|
66446
66869
|
const normalizedQueueText = queueText.toLowerCase();
|
|
66447
66870
|
if (lowered === "queue") {
|
|
66871
|
+
if (normalizedQueueText === "help") {
|
|
66872
|
+
return {
|
|
66873
|
+
type: "control",
|
|
66874
|
+
name: "queue-help"
|
|
66875
|
+
};
|
|
66876
|
+
}
|
|
66448
66877
|
if (normalizedQueueText === "list") {
|
|
66449
66878
|
return {
|
|
66450
66879
|
type: "control",
|
|
@@ -66511,7 +66940,7 @@ function renderAgentControlSlashHelp() {
|
|
|
66511
66940
|
"- `/whoami`: show the current platform, route, and sender identity details",
|
|
66512
66941
|
"- `/transcript`: show the current conversation session transcript when the route verbose policy allows it",
|
|
66513
66942
|
"- `/attach`: attach this thread to the active run and resume live updates when it is still processing",
|
|
66514
|
-
"- `/detach`: stop live updates for this thread
|
|
66943
|
+
"- `/detach`: stop live updates for this thread, switch to sparse progress updates, and still allow final settlement here",
|
|
66515
66944
|
"- `/watch every 30s [for 10m]`: post the latest state on an interval until the run settles or the watch window ends",
|
|
66516
66945
|
"- `/stop`: send Escape to interrupt the current conversation session",
|
|
66517
66946
|
"- `/nudge`: send one extra Enter to the current tmux session without resending the prompt text",
|
|
@@ -66528,9 +66957,11 @@ function renderAgentControlSlashHelp() {
|
|
|
66528
66957
|
"- `/additionalmessagemode steer`: send later user messages straight into the active session",
|
|
66529
66958
|
"- `/additionalmessagemode queue`: queue later user messages behind the active run for this surface",
|
|
66530
66959
|
"- `/queue <message>` or `\\q <message>`: enqueue a later message behind the active run and let clisbot deliver it in order",
|
|
66960
|
+
"- `/queue help`: show queue-specific help and examples",
|
|
66531
66961
|
"- `/steer <message>` or `\\s <message>`: inject a steering message into the active run immediately",
|
|
66532
66962
|
"- `/queue list`: show queued messages that have not started yet",
|
|
66533
66963
|
"- `/queue clear`: clear queued messages that have not started yet",
|
|
66964
|
+
"- `/loop help`: show loop-specific help and syntax examples",
|
|
66534
66965
|
...renderLoopHelpLines(),
|
|
66535
66966
|
"- `/bash` followed by a shell command: requires `shellExecute` on the resolved agent role",
|
|
66536
66967
|
"- shortcut prefixes such as `!` run bash only when the resolved agent role allows `shellExecute`",
|
|
@@ -66539,6 +66970,15 @@ function renderAgentControlSlashHelp() {
|
|
|
66539
66970
|
].join(`
|
|
66540
66971
|
`);
|
|
66541
66972
|
}
|
|
66973
|
+
function renderQueueHelpLines() {
|
|
66974
|
+
return [
|
|
66975
|
+
"- `/queue <message>` or `\\q <message>`: enqueue one later message behind the active run",
|
|
66976
|
+
"- `/queue list`: show queued messages that have not started yet",
|
|
66977
|
+
"- `/queue clear`: clear queued messages that have not started yet",
|
|
66978
|
+
"- `/queue help`: show this queue help again",
|
|
66979
|
+
"- `/steer <message>` or `\\s <message>`: inject an immediate steering message instead of queueing"
|
|
66980
|
+
];
|
|
66981
|
+
}
|
|
66542
66982
|
function parseWatchCommand(raw) {
|
|
66543
66983
|
const match = raw.match(/^every\s+(\S+)(?:\s+for\s+(\S+))?$/i);
|
|
66544
66984
|
if (!match) {
|
|
@@ -66760,7 +67200,8 @@ function renderWhoAmIMessage(params) {
|
|
|
66760
67200
|
`platform: \`${params.identity.platform}\``,
|
|
66761
67201
|
`conversationKind: \`${params.identity.conversationKind}\``,
|
|
66762
67202
|
`agentId: \`${params.route.agentId}\``,
|
|
66763
|
-
`sessionKey: \`${params.sessionTarget.sessionKey}
|
|
67203
|
+
`sessionKey: \`${params.sessionTarget.sessionKey}\``,
|
|
67204
|
+
`storedSessionId: \`${params.sessionDiagnostics.sessionId ?? "(not captured yet)"}\``
|
|
66764
67205
|
];
|
|
66765
67206
|
if (params.identity.senderId) {
|
|
66766
67207
|
lines.push(`senderId: \`${params.identity.senderId}\``);
|
|
@@ -66777,7 +67218,7 @@ function renderWhoAmIMessage(params) {
|
|
|
66777
67218
|
if (params.identity.topicId) {
|
|
66778
67219
|
lines.push(`topicId: \`${params.identity.topicId}\``);
|
|
66779
67220
|
}
|
|
66780
|
-
lines.push(`principal: \`${params.auth.principal ?? "(none)"}\``, `principalFormat: \`${renderPrincipalFormat(params.identity)}\``, `principalExample: \`${renderPrincipalExample(params.identity)}\``, `appRole: \`${params.auth.appRole}\``, `agentRole: \`${params.auth.agentRole}\``, `mayBypassPairing: \`${params.auth.mayBypassPairing}\``, `mayManageProtectedResources: \`${params.auth.mayManageProtectedResources}\``, `canUseShell: \`${params.auth.canUseShell}\``, `verbose: \`${params.route.verbose}\``);
|
|
67221
|
+
lines.push(`resumeCommand: \`${params.sessionDiagnostics.resumeCommand ?? "(not available yet)"}\``, `principal: \`${params.auth.principal ?? "(none)"}\``, `principalFormat: \`${renderPrincipalFormat(params.identity)}\``, `principalExample: \`${renderPrincipalExample(params.identity)}\``, `appRole: \`${params.auth.appRole}\``, `agentRole: \`${params.auth.agentRole}\``, `mayBypassPairing: \`${params.auth.mayBypassPairing}\``, `mayManageProtectedResources: \`${params.auth.mayManageProtectedResources}\``, `canUseShell: \`${params.auth.canUseShell}\``, `verbose: \`${params.route.verbose}\``);
|
|
66781
67222
|
return lines.join(`
|
|
66782
67223
|
`);
|
|
66783
67224
|
}
|
|
@@ -66788,7 +67229,8 @@ function renderRouteStatusMessage(params) {
|
|
|
66788
67229
|
`platform: \`${params.identity.platform}\``,
|
|
66789
67230
|
`conversationKind: \`${params.identity.conversationKind}\``,
|
|
66790
67231
|
`agentId: \`${params.route.agentId}\``,
|
|
66791
|
-
`sessionKey: \`${params.sessionTarget.sessionKey}
|
|
67232
|
+
`sessionKey: \`${params.sessionTarget.sessionKey}\``,
|
|
67233
|
+
`storedSessionId: \`${params.sessionDiagnostics.sessionId ?? "(not captured yet)"}\``
|
|
66792
67234
|
];
|
|
66793
67235
|
if (params.identity.senderId) {
|
|
66794
67236
|
lines.push(`senderId: \`${params.identity.senderId}\``);
|
|
@@ -66805,7 +67247,7 @@ function renderRouteStatusMessage(params) {
|
|
|
66805
67247
|
if (params.identity.topicId) {
|
|
66806
67248
|
lines.push(`topicId: \`${params.identity.topicId}\``);
|
|
66807
67249
|
}
|
|
66808
|
-
lines.push(`principal: \`${params.auth.principal ?? "(none)"}\``, `principalFormat: \`${renderPrincipalFormat(params.identity)}\``, `principalExample: \`${renderPrincipalExample(params.identity)}\``, `streaming: \`${params.route.streaming}\``, `response: \`${params.route.response}\``, `responseMode: \`${params.route.responseMode}\``, `additionalMessageMode: \`${params.route.additionalMessageMode}\``, `surfaceNotifications.queueStart: \`${params.route.surfaceNotifications.queueStart}\``, `surfaceNotifications.loopStart: \`${params.route.surfaceNotifications.loopStart}\``, `verbose: \`${params.route.verbose}\``, `appRole: \`${params.auth.appRole}\``, `agentRole: \`${params.auth.agentRole}\``, `mayManageProtectedResources: \`${params.auth.mayManageProtectedResources}\``, `canUseShell: \`${params.auth.canUseShell}\``, `timezone: \`${params.route.timezone ?? "(inherit host/app)"}\``, `followUp.mode: \`${params.followUpState.overrideMode ?? params.route.followUp.mode}\``, `followUp.windowMinutes: \`${formatFollowUpTtlMinutes(params.route.followUp.participationTtlMs)}\``, `run.state: \`${params.runtimeState.state}\``);
|
|
67250
|
+
lines.push(`resumeCommand: \`${params.sessionDiagnostics.resumeCommand ?? "(not available yet)"}\``, `principal: \`${params.auth.principal ?? "(none)"}\``, `principalFormat: \`${renderPrincipalFormat(params.identity)}\``, `principalExample: \`${renderPrincipalExample(params.identity)}\``, `streaming: \`${params.route.streaming}\``, `response: \`${params.route.response}\``, `responseMode: \`${params.route.responseMode}\``, `additionalMessageMode: \`${params.route.additionalMessageMode}\``, `surfaceNotifications.queueStart: \`${params.route.surfaceNotifications.queueStart}\``, `surfaceNotifications.loopStart: \`${params.route.surfaceNotifications.loopStart}\``, `verbose: \`${params.route.verbose}\``, `appRole: \`${params.auth.appRole}\``, `agentRole: \`${params.auth.agentRole}\``, `mayManageProtectedResources: \`${params.auth.mayManageProtectedResources}\``, `canUseShell: \`${params.auth.canUseShell}\``, `timezone: \`${params.route.timezone ?? "(inherit host/app)"}\``, `followUp.mode: \`${params.followUpState.overrideMode ?? params.route.followUp.mode}\``, `followUp.windowMinutes: \`${formatFollowUpTtlMinutes(params.route.followUp.participationTtlMs)}\``, `run.state: \`${params.runtimeState.state}\``);
|
|
66809
67251
|
if (params.runtimeState.startedAt) {
|
|
66810
67252
|
lines.push(`run.startedAt: \`${new Date(params.runtimeState.startedAt).toISOString()}\``);
|
|
66811
67253
|
}
|
|
@@ -66819,7 +67261,7 @@ function renderRouteStatusMessage(params) {
|
|
|
66819
67261
|
lines.push(`- \`${loop.id}\` ${renderLoopStatusSchedule(loop)} remaining \`${loop.remainingRuns}\` nextRunAt \`${new Date(loop.nextRunAt).toISOString()}\``);
|
|
66820
67262
|
}
|
|
66821
67263
|
}
|
|
66822
|
-
lines.push("", "Useful commands:", "- `/help`", "- `/whoami`", "- `/status`", "- `/attach`, `/detach`, `/watch every 30s`", "- `/followup status`", "- `/streaming status|on|off|latest|all`", "- `/responsemode status`", "- `/additionalmessagemode status`", "- `/loop status`, `/loop cancel`, `/loop cancel <id>`", "- `/queue <message>`, `/steer <message>`", "- `/queue list`, `/queue clear`", params.route.verbose === "off" ? "- `/transcript` disabled on this route (`verbose: off`)" : "- `/transcript` enabled on this route (`verbose: minimal`)", "- `/bash` requires `shellExecute`");
|
|
67264
|
+
lines.push("", "Useful commands:", "- `/help`", "- `/whoami`", "- `/status`", "- `/attach`, `/detach`, `/watch every 30s`", "- `/followup status`", "- `/streaming status|on|off|latest|all`", "- `/responsemode status`", "- `/additionalmessagemode status`", "- `/loop help`, `/loop status`, `/loop cancel`, `/loop cancel <id>`", "- `/queue help`, `/queue <message>`, `/steer <message>`", "- `/queue list`, `/queue clear`", params.route.verbose === "off" ? "- `/transcript` disabled on this route (`verbose: off`)" : "- `/transcript` enabled on this route (`verbose: minimal`)", "- `/bash` requires `shellExecute`");
|
|
66823
67265
|
return lines.join(`
|
|
66824
67266
|
`);
|
|
66825
67267
|
}
|
|
@@ -66877,25 +67319,6 @@ function buildChannelObserverId(identity) {
|
|
|
66877
67319
|
identity.topicId ?? ""
|
|
66878
67320
|
].join(":");
|
|
66879
67321
|
}
|
|
66880
|
-
function buildSteeringMessage(text, protectedControlMutationRule) {
|
|
66881
|
-
const systemLines = [
|
|
66882
|
-
"A new user message arrived while you were still working.",
|
|
66883
|
-
"Adjust your current work if needed and continue."
|
|
66884
|
-
];
|
|
66885
|
-
if (protectedControlMutationRule) {
|
|
66886
|
-
systemLines.push("", protectedControlMutationRule);
|
|
66887
|
-
}
|
|
66888
|
-
return [
|
|
66889
|
-
"<system>",
|
|
66890
|
-
...systemLines,
|
|
66891
|
-
"</system>",
|
|
66892
|
-
"",
|
|
66893
|
-
"<user>",
|
|
66894
|
-
text,
|
|
66895
|
-
"</user>"
|
|
66896
|
-
].join(`
|
|
66897
|
-
`);
|
|
66898
|
-
}
|
|
66899
67322
|
function renderQueuedMessagesList(items) {
|
|
66900
67323
|
if (items.length === 0) {
|
|
66901
67324
|
return "Queue is empty.";
|
|
@@ -66910,9 +67333,22 @@ function renderQueuedMessagesList(items) {
|
|
|
66910
67333
|
return lines.join(`
|
|
66911
67334
|
`).trimEnd();
|
|
66912
67335
|
}
|
|
67336
|
+
function renderQueueUsage() {
|
|
67337
|
+
return [
|
|
67338
|
+
"Queue commands",
|
|
67339
|
+
"",
|
|
67340
|
+
...renderQueueHelpLines(),
|
|
67341
|
+
"",
|
|
67342
|
+
"Notes:",
|
|
67343
|
+
"- use queue when the current run should finish first and the next user request can wait in order",
|
|
67344
|
+
"- use steer when the active run should be nudged or redirected immediately"
|
|
67345
|
+
].join(`
|
|
67346
|
+
`);
|
|
67347
|
+
}
|
|
66913
67348
|
function renderLoopUsage() {
|
|
66914
67349
|
return [
|
|
66915
67350
|
"Usage:",
|
|
67351
|
+
"- `/loop help`",
|
|
66916
67352
|
"- `/loop 5m check CI`",
|
|
66917
67353
|
"- `/loop 1m --force check CI`",
|
|
66918
67354
|
"- `/loop 5m`",
|
|
@@ -67463,6 +67899,7 @@ async function processChannelInteraction(params) {
|
|
|
67463
67899
|
};
|
|
67464
67900
|
let replyRecorded = false;
|
|
67465
67901
|
let renderChain = Promise.resolve();
|
|
67902
|
+
const sessionDiagnostics = await params.agentService.getSessionDiagnostics?.(params.sessionTarget) ?? {};
|
|
67466
67903
|
async function recordReplyIfNeeded() {
|
|
67467
67904
|
if (replyRecorded) {
|
|
67468
67905
|
return;
|
|
@@ -67537,6 +67974,7 @@ async function processChannelInteraction(params) {
|
|
|
67537
67974
|
route: params.route,
|
|
67538
67975
|
auth,
|
|
67539
67976
|
sessionTarget: params.sessionTarget,
|
|
67977
|
+
sessionDiagnostics,
|
|
67540
67978
|
followUpState,
|
|
67541
67979
|
runtimeState,
|
|
67542
67980
|
loopState: {
|
|
@@ -67557,7 +67995,8 @@ async function processChannelInteraction(params) {
|
|
|
67557
67995
|
identity: params.identity,
|
|
67558
67996
|
route: params.route,
|
|
67559
67997
|
auth,
|
|
67560
|
-
sessionTarget: params.sessionTarget
|
|
67998
|
+
sessionTarget: params.sessionTarget,
|
|
67999
|
+
sessionDiagnostics
|
|
67561
68000
|
}));
|
|
67562
68001
|
await params.agentService.recordConversationReply(params.sessionTarget);
|
|
67563
68002
|
return interactionResult;
|
|
@@ -67714,6 +68153,11 @@ async function processChannelInteraction(params) {
|
|
|
67714
68153
|
await params.agentService.recordConversationReply(params.sessionTarget);
|
|
67715
68154
|
return interactionResult;
|
|
67716
68155
|
}
|
|
68156
|
+
if (slashCommand.name === "queue-help") {
|
|
68157
|
+
await params.postText(renderQueueUsage());
|
|
68158
|
+
await params.agentService.recordConversationReply(params.sessionTarget);
|
|
68159
|
+
return interactionResult;
|
|
68160
|
+
}
|
|
67717
68161
|
if (slashCommand.name === "queue-clear") {
|
|
67718
68162
|
const clearedCount = params.agentService.clearQueuedPrompts?.(params.sessionTarget) ?? 0;
|
|
67719
68163
|
await params.postText(clearedCount > 0 ? `Cleared ${clearedCount} queued message${clearedCount === 1 ? "" : "s"}.` : "Queue was already empty.");
|
|
@@ -67721,6 +68165,11 @@ async function processChannelInteraction(params) {
|
|
|
67721
68165
|
return interactionResult;
|
|
67722
68166
|
}
|
|
67723
68167
|
}
|
|
68168
|
+
if (slashCommand?.type === "control" && slashCommand.name === "loop-help") {
|
|
68169
|
+
await params.postText(renderLoopUsage());
|
|
68170
|
+
await params.agentService.recordConversationReply(params.sessionTarget);
|
|
68171
|
+
return interactionResult;
|
|
68172
|
+
}
|
|
67724
68173
|
if (slashCommand?.type === "loop-control") {
|
|
67725
68174
|
if (slashCommand.action === "status") {
|
|
67726
68175
|
await params.postText(renderLoopStatusMessage({
|
|
@@ -67943,7 +68392,10 @@ ${escapeCodeFence(shellResult.output)}
|
|
|
67943
68392
|
await params.agentService.recordConversationReply(params.sessionTarget);
|
|
67944
68393
|
return interactionResult;
|
|
67945
68394
|
}
|
|
67946
|
-
await params.agentService.submitSessionInput(params.sessionTarget,
|
|
68395
|
+
await params.agentService.submitSessionInput(params.sessionTarget, buildSteeringPromptText({
|
|
68396
|
+
text: explicitSteerMessage,
|
|
68397
|
+
protectedControlMutationRule: params.protectedControlMutationRule
|
|
68398
|
+
}));
|
|
67947
68399
|
await params.postText("Steered.");
|
|
67948
68400
|
await params.agentService.recordConversationReply(params.sessionTarget);
|
|
67949
68401
|
return {
|
|
@@ -67952,7 +68404,10 @@ ${escapeCodeFence(shellResult.output)}
|
|
|
67952
68404
|
}
|
|
67953
68405
|
if (!forceQueuedDelivery && params.route.additionalMessageMode === "steer") {
|
|
67954
68406
|
if (sessionBusy && canSteerActiveRun) {
|
|
67955
|
-
await params.agentService.submitSessionInput(params.sessionTarget,
|
|
68407
|
+
await params.agentService.submitSessionInput(params.sessionTarget, buildSteeringPromptText({
|
|
68408
|
+
text: params.text,
|
|
68409
|
+
protectedControlMutationRule: params.protectedControlMutationRule
|
|
68410
|
+
}));
|
|
67956
68411
|
return {
|
|
67957
68412
|
processingIndicatorLifecycle: "active-run"
|
|
67958
68413
|
};
|
|
@@ -68126,6 +68581,43 @@ function resolveChannelAuth(params) {
|
|
|
68126
68581
|
|
|
68127
68582
|
// src/auth/owner-claim.ts
|
|
68128
68583
|
var import_proper_lockfile2 = __toESM(require_proper_lockfile(), 1);
|
|
68584
|
+
import { statSync as statSync3 } from "node:fs";
|
|
68585
|
+
|
|
68586
|
+
// src/control/config-reload-suppression.ts
|
|
68587
|
+
var suppressedReloads = new Map;
|
|
68588
|
+
var MTIME_MATCH_EPSILON_MS = 1;
|
|
68589
|
+
function normalizeConfigPath(configPath) {
|
|
68590
|
+
return configPath.trim();
|
|
68591
|
+
}
|
|
68592
|
+
function suppressConfigReload(configPath, mtimeMs) {
|
|
68593
|
+
const normalizedPath = normalizeConfigPath(configPath);
|
|
68594
|
+
if (!normalizedPath || !Number.isFinite(mtimeMs)) {
|
|
68595
|
+
return;
|
|
68596
|
+
}
|
|
68597
|
+
const existing = suppressedReloads.get(normalizedPath) ?? [];
|
|
68598
|
+
existing.push(mtimeMs);
|
|
68599
|
+
suppressedReloads.set(normalizedPath, existing);
|
|
68600
|
+
}
|
|
68601
|
+
function consumeSuppressedConfigReload(configPath, mtimeMs) {
|
|
68602
|
+
const normalizedPath = normalizeConfigPath(configPath);
|
|
68603
|
+
if (!normalizedPath || !Number.isFinite(mtimeMs)) {
|
|
68604
|
+
return false;
|
|
68605
|
+
}
|
|
68606
|
+
const existing = suppressedReloads.get(normalizedPath);
|
|
68607
|
+
if (!existing || existing.length === 0) {
|
|
68608
|
+
return false;
|
|
68609
|
+
}
|
|
68610
|
+
const next = existing.filter((candidate) => Math.abs(candidate - mtimeMs) > MTIME_MATCH_EPSILON_MS);
|
|
68611
|
+
const matched = next.length !== existing.length;
|
|
68612
|
+
if (next.length === 0) {
|
|
68613
|
+
suppressedReloads.delete(normalizedPath);
|
|
68614
|
+
} else {
|
|
68615
|
+
suppressedReloads.set(normalizedPath, next);
|
|
68616
|
+
}
|
|
68617
|
+
return matched;
|
|
68618
|
+
}
|
|
68619
|
+
|
|
68620
|
+
// src/auth/owner-claim.ts
|
|
68129
68621
|
var OWNER_CLAIM_RUNTIME_STARTED_AT_MS = Date.now();
|
|
68130
68622
|
var CONFIG_LOCK_OPTIONS = {
|
|
68131
68623
|
retries: {
|
|
@@ -68221,6 +68713,7 @@ async function claimFirstOwnerFromDirectMessage(params) {
|
|
|
68221
68713
|
}
|
|
68222
68714
|
freshConfig.app.auth.roles.owner.users = [...currentOwners, principal];
|
|
68223
68715
|
await writeEditableConfig(expandedPath, freshConfig);
|
|
68716
|
+
suppressConfigReload(expandedPath, statSync3(expandedPath).mtimeMs);
|
|
68224
68717
|
syncOwnerUsers(params.config, freshConfig);
|
|
68225
68718
|
ownerClaimRuntimeState.closed = true;
|
|
68226
68719
|
console.log(`clisbot auto-claimed first owner ${principal}`);
|
|
@@ -68503,7 +68996,7 @@ async function clearSlackAssistantThreadStatus(client, target) {
|
|
|
68503
68996
|
|
|
68504
68997
|
// src/channels/processing-indicator.ts
|
|
68505
68998
|
function shouldResolveIndicatorWait(update) {
|
|
68506
|
-
return isTerminalRunStatus(update.status)
|
|
68999
|
+
return isTerminalRunStatus(update.status);
|
|
68507
69000
|
}
|
|
68508
69001
|
async function waitForProcessingIndicatorLifecycle(params) {
|
|
68509
69002
|
if (params.lifecycle !== "active-run") {
|
|
@@ -68634,6 +69127,7 @@ class ConversationProcessingIndicatorCoordinator {
|
|
|
68634
69127
|
}
|
|
68635
69128
|
|
|
68636
69129
|
// src/channels/slack/processing-decoration.ts
|
|
69130
|
+
var DEFAULT_STATUS_REFRESH_INTERVAL_MS = 2000;
|
|
68637
69131
|
async function activateSlackProcessingDecoration(params) {
|
|
68638
69132
|
const [reactionResult, statusResult] = await Promise.allSettled([
|
|
68639
69133
|
params.addReaction(),
|
|
@@ -68655,7 +69149,38 @@ async function activateSlackProcessingDecoration(params) {
|
|
|
68655
69149
|
throw statusResult.reason;
|
|
68656
69150
|
}
|
|
68657
69151
|
}
|
|
69152
|
+
let statusRefreshTimer;
|
|
69153
|
+
let closed = false;
|
|
69154
|
+
let refreshInFlight = false;
|
|
69155
|
+
let refreshPromise;
|
|
69156
|
+
if (statusApplied) {
|
|
69157
|
+
const refreshIntervalMs = Math.max(0, params.statusRefreshIntervalMs ?? DEFAULT_STATUS_REFRESH_INTERVAL_MS);
|
|
69158
|
+
if (refreshIntervalMs > 0) {
|
|
69159
|
+
statusRefreshTimer = setInterval(() => {
|
|
69160
|
+
if (closed || refreshInFlight) {
|
|
69161
|
+
return;
|
|
69162
|
+
}
|
|
69163
|
+
refreshInFlight = true;
|
|
69164
|
+
refreshPromise = params.setStatus().then(() => {
|
|
69165
|
+
return;
|
|
69166
|
+
}).catch((error) => {
|
|
69167
|
+
if (!closed) {
|
|
69168
|
+
params.onUnexpectedError?.("refresh-status", error);
|
|
69169
|
+
}
|
|
69170
|
+
}).finally(() => {
|
|
69171
|
+
refreshInFlight = false;
|
|
69172
|
+
refreshPromise = undefined;
|
|
69173
|
+
});
|
|
69174
|
+
}, refreshIntervalMs);
|
|
69175
|
+
}
|
|
69176
|
+
}
|
|
68658
69177
|
return async () => {
|
|
69178
|
+
closed = true;
|
|
69179
|
+
if (statusRefreshTimer) {
|
|
69180
|
+
clearInterval(statusRefreshTimer);
|
|
69181
|
+
statusRefreshTimer = undefined;
|
|
69182
|
+
}
|
|
69183
|
+
await refreshPromise;
|
|
68659
69184
|
if (reactionApplied) {
|
|
68660
69185
|
try {
|
|
68661
69186
|
await params.removeReaction();
|
|
@@ -68726,6 +69251,14 @@ function stripBotMention(text, botUserId) {
|
|
|
68726
69251
|
}
|
|
68727
69252
|
return text.replaceAll(`<@${botUserId}>`, "").replaceAll(/<@[^>]+>/g, "").trim();
|
|
68728
69253
|
}
|
|
69254
|
+
function resolveSlackDirectReplyThreadTs(params) {
|
|
69255
|
+
const resolvedThreadTs = (params.resolvedThreadTs ?? "").trim();
|
|
69256
|
+
if (resolvedThreadTs) {
|
|
69257
|
+
return resolvedThreadTs;
|
|
69258
|
+
}
|
|
69259
|
+
const messageTs = (params.messageTs ?? "").trim();
|
|
69260
|
+
return messageTs || undefined;
|
|
69261
|
+
}
|
|
68729
69262
|
|
|
68730
69263
|
// src/channels/slack/reactions.ts
|
|
68731
69264
|
function normalizeSlackReactionName(value) {
|
|
@@ -69501,6 +70034,10 @@ class SlackSocketService {
|
|
|
69501
70034
|
await this.processedEventsStore.markCompleted(eventId);
|
|
69502
70035
|
return;
|
|
69503
70036
|
}
|
|
70037
|
+
const directReplyThreadTs = resolveSlackDirectReplyThreadTs({
|
|
70038
|
+
messageTs,
|
|
70039
|
+
resolvedThreadTs: await this.resolveThreadTs(event)
|
|
70040
|
+
});
|
|
69504
70041
|
let ownerClaimed = false;
|
|
69505
70042
|
let ownerPrincipal;
|
|
69506
70043
|
try {
|
|
@@ -69518,6 +70055,7 @@ class SlackSocketService {
|
|
|
69518
70055
|
try {
|
|
69519
70056
|
await postSlackText(this.app.client, {
|
|
69520
70057
|
channel: channelId,
|
|
70058
|
+
threadTs: directReplyThreadTs,
|
|
69521
70059
|
text: renderFirstOwnerClaimMessage({
|
|
69522
70060
|
principal: ownerPrincipal,
|
|
69523
70061
|
ownerClaimWindowMinutes: this.loadedConfig.raw.app.auth.ownerClaimWindowMinutes
|
|
@@ -69540,19 +70078,21 @@ class SlackSocketService {
|
|
|
69540
70078
|
});
|
|
69541
70079
|
if (!allowed) {
|
|
69542
70080
|
if (dmConfig.policy === "pairing") {
|
|
69543
|
-
const
|
|
70081
|
+
const pairingRequest = await upsertChannelPairingRequest({
|
|
69544
70082
|
channel: "slack",
|
|
69545
70083
|
id: directUserId
|
|
69546
70084
|
});
|
|
69547
|
-
|
|
70085
|
+
const pairingReply = buildPairingReplyFromRequest({
|
|
70086
|
+
channel: "slack",
|
|
70087
|
+
idLine: `Your Slack user id: ${directUserId}`,
|
|
70088
|
+
pairingRequest
|
|
70089
|
+
});
|
|
70090
|
+
if (pairingReply) {
|
|
69548
70091
|
try {
|
|
69549
70092
|
await postSlackText(this.app.client, {
|
|
69550
70093
|
channel: channelId,
|
|
69551
|
-
|
|
69552
|
-
|
|
69553
|
-
idLine: `Your Slack user id: ${directUserId}`,
|
|
69554
|
-
code
|
|
69555
|
-
})
|
|
70094
|
+
threadTs: directReplyThreadTs,
|
|
70095
|
+
text: pairingReply
|
|
69556
70096
|
});
|
|
69557
70097
|
} catch (error) {
|
|
69558
70098
|
console.error("slack pairing reply failed", error);
|
|
@@ -71123,6 +71663,7 @@ class TelegramPollingService {
|
|
|
71123
71663
|
try {
|
|
71124
71664
|
await callTelegramApi(this.accountConfig.botToken, "sendMessage", {
|
|
71125
71665
|
chat_id: message.chat.id,
|
|
71666
|
+
...message.message_id != null ? { reply_to_message_id: message.message_id } : {},
|
|
71126
71667
|
text: renderFirstOwnerClaimMessage({
|
|
71127
71668
|
principal: ownerPrincipal,
|
|
71128
71669
|
ownerClaimWindowMinutes: this.loadedConfig.raw.app.auth.ownerClaimWindowMinutes
|
|
@@ -71146,7 +71687,7 @@ class TelegramPollingService {
|
|
|
71146
71687
|
});
|
|
71147
71688
|
if (!allowed) {
|
|
71148
71689
|
if (directMessages.policy === "pairing") {
|
|
71149
|
-
const
|
|
71690
|
+
const pairingRequest = await upsertChannelPairingRequest({
|
|
71150
71691
|
channel: "telegram",
|
|
71151
71692
|
id: senderId,
|
|
71152
71693
|
meta: {
|
|
@@ -71155,15 +71696,16 @@ class TelegramPollingService {
|
|
|
71155
71696
|
lastName: message.from?.last_name
|
|
71156
71697
|
}
|
|
71157
71698
|
});
|
|
71158
|
-
|
|
71699
|
+
const pairingReply = buildPairingReplyFromRequest({
|
|
71700
|
+
channel: "telegram",
|
|
71701
|
+
idLine: `Your Telegram user id: ${senderId}`,
|
|
71702
|
+
pairingRequest
|
|
71703
|
+
});
|
|
71704
|
+
if (pairingReply) {
|
|
71159
71705
|
try {
|
|
71160
71706
|
await callTelegramApi(this.accountConfig.botToken, "sendMessage", {
|
|
71161
71707
|
chat_id: message.chat.id,
|
|
71162
|
-
text:
|
|
71163
|
-
channel: "telegram",
|
|
71164
|
-
idLine: `Your Telegram user id: ${senderId}`,
|
|
71165
|
-
code
|
|
71166
|
-
})
|
|
71708
|
+
text: pairingReply
|
|
71167
71709
|
});
|
|
71168
71710
|
} catch (error) {
|
|
71169
71711
|
console.error("telegram pairing reply failed", error);
|
|
@@ -71809,19 +72351,33 @@ function summarizeExit(params) {
|
|
|
71809
72351
|
return `code ${params.code ?? 0}`;
|
|
71810
72352
|
}
|
|
71811
72353
|
function getRestartPlan(config, restartNumber) {
|
|
71812
|
-
|
|
71813
|
-
const totalRestarts = config.stages.reduce((sum, stage) => sum + stage.maxRestarts, 0);
|
|
72354
|
+
const fastRetryMaxRestarts = config.fastRetry.maxRestarts;
|
|
72355
|
+
const totalRestarts = fastRetryMaxRestarts + config.stages.reduce((sum, stage) => sum + stage.maxRestarts, 0);
|
|
72356
|
+
if (restartNumber >= 1 && restartNumber <= fastRetryMaxRestarts) {
|
|
72357
|
+
return {
|
|
72358
|
+
mode: "fast-retry",
|
|
72359
|
+
stageIndex: -1,
|
|
72360
|
+
delayMs: config.fastRetry.delaySeconds * 1000,
|
|
72361
|
+
restartAttemptInStage: restartNumber,
|
|
72362
|
+
restartsRemaining: totalRestarts - restartNumber,
|
|
72363
|
+
totalRestarts,
|
|
72364
|
+
stageMaxRestarts: fastRetryMaxRestarts
|
|
72365
|
+
};
|
|
72366
|
+
}
|
|
72367
|
+
let completedRestarts = fastRetryMaxRestarts;
|
|
71814
72368
|
for (let index = 0;index < config.stages.length; index += 1) {
|
|
71815
72369
|
const stage = config.stages[index];
|
|
71816
72370
|
const stageStart = completedRestarts + 1;
|
|
71817
72371
|
const stageEnd = completedRestarts + stage.maxRestarts;
|
|
71818
72372
|
if (restartNumber >= stageStart && restartNumber <= stageEnd) {
|
|
71819
72373
|
return {
|
|
72374
|
+
mode: "backoff",
|
|
71820
72375
|
stageIndex: index,
|
|
71821
|
-
|
|
72376
|
+
delayMs: stage.delayMinutes * 60000,
|
|
71822
72377
|
restartAttemptInStage: restartNumber - completedRestarts,
|
|
71823
72378
|
restartsRemaining: totalRestarts - restartNumber,
|
|
71824
|
-
totalRestarts
|
|
72379
|
+
totalRestarts,
|
|
72380
|
+
stageMaxRestarts: stage.maxRestarts
|
|
71825
72381
|
};
|
|
71826
72382
|
}
|
|
71827
72383
|
completedRestarts = stageEnd;
|
|
@@ -71955,7 +72511,7 @@ function renderBackoffAlertMessage(params) {
|
|
|
71955
72511
|
`next restart: ${params.nextRestartAt}`,
|
|
71956
72512
|
`restart: ${params.restartNumber}/${params.totalRestarts}`,
|
|
71957
72513
|
`stage: ${params.stageIndex + 1}/${params.config.restartBackoff.stages.length}`,
|
|
71958
|
-
`stage attempt: ${params.restartAttemptInStage}/${params.
|
|
72514
|
+
`stage attempt: ${params.restartAttemptInStage}/${params.stageMaxRestarts}`
|
|
71959
72515
|
].join(`
|
|
71960
72516
|
`);
|
|
71961
72517
|
}
|
|
@@ -72048,20 +72604,23 @@ class RuntimeMonitor {
|
|
|
72048
72604
|
}
|
|
72049
72605
|
restartNumber = nextRestartNumber;
|
|
72050
72606
|
totalRestarts = plan.totalRestarts;
|
|
72051
|
-
const nextRestartAt = new Date(this.dependencies.now() + plan.
|
|
72052
|
-
|
|
72053
|
-
|
|
72054
|
-
|
|
72055
|
-
|
|
72056
|
-
|
|
72057
|
-
|
|
72058
|
-
|
|
72059
|
-
|
|
72060
|
-
|
|
72061
|
-
|
|
72062
|
-
|
|
72063
|
-
|
|
72064
|
-
|
|
72607
|
+
const nextRestartAt = new Date(this.dependencies.now() + plan.delayMs).toISOString();
|
|
72608
|
+
if (plan.mode === "backoff") {
|
|
72609
|
+
await this.maybeSendAlert("backoff", monitorConfig, renderBackoffAlertMessage({
|
|
72610
|
+
config: monitorConfig,
|
|
72611
|
+
restartNumber,
|
|
72612
|
+
stageIndex: plan.stageIndex,
|
|
72613
|
+
restartAttemptInStage: plan.restartAttemptInStage,
|
|
72614
|
+
stageMaxRestarts: plan.stageMaxRestarts,
|
|
72615
|
+
totalRestarts,
|
|
72616
|
+
nextRestartAt,
|
|
72617
|
+
exit: {
|
|
72618
|
+
code: exit.code,
|
|
72619
|
+
signal: exit.signal,
|
|
72620
|
+
at: exitAt
|
|
72621
|
+
}
|
|
72622
|
+
}));
|
|
72623
|
+
}
|
|
72065
72624
|
await this.writeState({
|
|
72066
72625
|
phase: "backoff",
|
|
72067
72626
|
runtimePid: undefined,
|
|
@@ -72071,6 +72630,7 @@ class RuntimeMonitor {
|
|
|
72071
72630
|
at: exitAt
|
|
72072
72631
|
},
|
|
72073
72632
|
restart: {
|
|
72633
|
+
mode: plan.mode,
|
|
72074
72634
|
stageIndex: plan.stageIndex,
|
|
72075
72635
|
restartNumber,
|
|
72076
72636
|
restartAttemptInStage: plan.restartAttemptInStage,
|
|
@@ -72078,7 +72638,7 @@ class RuntimeMonitor {
|
|
|
72078
72638
|
nextRestartAt
|
|
72079
72639
|
}
|
|
72080
72640
|
});
|
|
72081
|
-
await this.sleepWithStop(plan.
|
|
72641
|
+
await this.sleepWithStop(plan.delayMs);
|
|
72082
72642
|
}
|
|
72083
72643
|
} finally {
|
|
72084
72644
|
await this.stopActiveChild();
|
|
@@ -72239,17 +72799,87 @@ var PROCESS_POLL_INTERVAL_MS = 100;
|
|
|
72239
72799
|
function resolveConfigPath(configPath) {
|
|
72240
72800
|
return expandHomePath(configPath ?? process.env.CLISBOT_CONFIG_PATH ?? getDefaultConfigPath());
|
|
72241
72801
|
}
|
|
72242
|
-
function
|
|
72243
|
-
|
|
72802
|
+
function deriveRuntimeSiblingPath(configPath, filename) {
|
|
72803
|
+
if (!configPath) {
|
|
72804
|
+
return null;
|
|
72805
|
+
}
|
|
72806
|
+
return join11(dirname13(expandHomePath(configPath)), "state", filename);
|
|
72807
|
+
}
|
|
72808
|
+
function resolvePidPath(pidPath, configPath, options = {}) {
|
|
72809
|
+
if (pidPath) {
|
|
72810
|
+
return expandHomePath(pidPath);
|
|
72811
|
+
}
|
|
72812
|
+
if (options.preferConfigSibling) {
|
|
72813
|
+
const derivedFromExplicitConfig = deriveRuntimeSiblingPath(configPath, "clisbot.pid");
|
|
72814
|
+
if (derivedFromExplicitConfig) {
|
|
72815
|
+
return derivedFromExplicitConfig;
|
|
72816
|
+
}
|
|
72817
|
+
}
|
|
72818
|
+
if (process.env.CLISBOT_PID_PATH) {
|
|
72819
|
+
return expandHomePath(process.env.CLISBOT_PID_PATH);
|
|
72820
|
+
}
|
|
72821
|
+
const derivedFromConfig = deriveRuntimeSiblingPath(configPath ?? process.env.CLISBOT_CONFIG_PATH, "clisbot.pid");
|
|
72822
|
+
if (derivedFromConfig) {
|
|
72823
|
+
return derivedFromConfig;
|
|
72824
|
+
}
|
|
72825
|
+
return expandHomePath(getDefaultRuntimePidPath());
|
|
72244
72826
|
}
|
|
72245
|
-
function resolveLogPath(logPath) {
|
|
72246
|
-
|
|
72827
|
+
function resolveLogPath(logPath, configPath, options = {}) {
|
|
72828
|
+
if (logPath) {
|
|
72829
|
+
return expandHomePath(logPath);
|
|
72830
|
+
}
|
|
72831
|
+
if (options.preferConfigSibling) {
|
|
72832
|
+
const derivedFromExplicitConfig = deriveRuntimeSiblingPath(configPath, "clisbot.log");
|
|
72833
|
+
if (derivedFromExplicitConfig) {
|
|
72834
|
+
return derivedFromExplicitConfig;
|
|
72835
|
+
}
|
|
72836
|
+
}
|
|
72837
|
+
if (process.env.CLISBOT_LOG_PATH) {
|
|
72838
|
+
return expandHomePath(process.env.CLISBOT_LOG_PATH);
|
|
72839
|
+
}
|
|
72840
|
+
const derivedFromConfig = deriveRuntimeSiblingPath(configPath ?? process.env.CLISBOT_CONFIG_PATH, "clisbot.log");
|
|
72841
|
+
if (derivedFromConfig) {
|
|
72842
|
+
return derivedFromConfig;
|
|
72843
|
+
}
|
|
72844
|
+
return expandHomePath(getDefaultRuntimeLogPath());
|
|
72247
72845
|
}
|
|
72248
|
-
function resolveMonitorStatePath(monitorStatePath) {
|
|
72249
|
-
|
|
72846
|
+
function resolveMonitorStatePath(monitorStatePath, configPath, options = {}) {
|
|
72847
|
+
if (monitorStatePath) {
|
|
72848
|
+
return expandHomePath(monitorStatePath);
|
|
72849
|
+
}
|
|
72850
|
+
if (options.preferConfigSibling) {
|
|
72851
|
+
const derivedFromExplicitConfig = deriveRuntimeSiblingPath(configPath, "clisbot-monitor.json");
|
|
72852
|
+
if (derivedFromExplicitConfig) {
|
|
72853
|
+
return derivedFromExplicitConfig;
|
|
72854
|
+
}
|
|
72855
|
+
}
|
|
72856
|
+
if (process.env.CLISBOT_RUNTIME_MONITOR_STATE_PATH) {
|
|
72857
|
+
return expandHomePath(process.env.CLISBOT_RUNTIME_MONITOR_STATE_PATH);
|
|
72858
|
+
}
|
|
72859
|
+
const derivedFromConfig = deriveRuntimeSiblingPath(configPath ?? process.env.CLISBOT_CONFIG_PATH, "clisbot-monitor.json");
|
|
72860
|
+
if (derivedFromConfig) {
|
|
72861
|
+
return derivedFromConfig;
|
|
72862
|
+
}
|
|
72863
|
+
return expandHomePath(getDefaultRuntimeMonitorStatePath());
|
|
72250
72864
|
}
|
|
72251
|
-
function resolveRuntimeCredentialsPath(runtimeCredentialsPath) {
|
|
72252
|
-
|
|
72865
|
+
function resolveRuntimeCredentialsPath(runtimeCredentialsPath, configPath, options = {}) {
|
|
72866
|
+
if (runtimeCredentialsPath) {
|
|
72867
|
+
return expandHomePath(runtimeCredentialsPath);
|
|
72868
|
+
}
|
|
72869
|
+
if (options.preferConfigSibling) {
|
|
72870
|
+
const derivedFromExplicitConfig = deriveRuntimeSiblingPath(configPath, "runtime-credentials.json");
|
|
72871
|
+
if (derivedFromExplicitConfig) {
|
|
72872
|
+
return derivedFromExplicitConfig;
|
|
72873
|
+
}
|
|
72874
|
+
}
|
|
72875
|
+
if (process.env.CLISBOT_RUNTIME_CREDENTIALS_PATH) {
|
|
72876
|
+
return expandHomePath(process.env.CLISBOT_RUNTIME_CREDENTIALS_PATH);
|
|
72877
|
+
}
|
|
72878
|
+
const derivedFromConfig = deriveRuntimeSiblingPath(configPath ?? process.env.CLISBOT_CONFIG_PATH, "runtime-credentials.json");
|
|
72879
|
+
if (derivedFromConfig) {
|
|
72880
|
+
return derivedFromConfig;
|
|
72881
|
+
}
|
|
72882
|
+
return expandHomePath(getDefaultRuntimeCredentialsPath());
|
|
72253
72883
|
}
|
|
72254
72884
|
|
|
72255
72885
|
class StartDetachedRuntimeError extends Error {
|
|
@@ -72326,10 +72956,14 @@ async function ensureConfigFile(configPath, options = {}) {
|
|
|
72326
72956
|
};
|
|
72327
72957
|
}
|
|
72328
72958
|
async function startDetachedRuntime(params) {
|
|
72329
|
-
const
|
|
72330
|
-
const
|
|
72331
|
-
const
|
|
72332
|
-
const
|
|
72959
|
+
const configPath = resolveConfigPath(params.configPath);
|
|
72960
|
+
const preferConfigSibling = params.configPath != null;
|
|
72961
|
+
const pidPath = resolvePidPath(params.pidPath, configPath, { preferConfigSibling });
|
|
72962
|
+
const logPath = resolveLogPath(params.logPath, configPath, { preferConfigSibling });
|
|
72963
|
+
const monitorStatePath = resolveMonitorStatePath(params.monitorStatePath, configPath, {
|
|
72964
|
+
preferConfigSibling
|
|
72965
|
+
});
|
|
72966
|
+
const runtimeCredentialsPath = resolveRuntimeCredentialsPath(params.runtimeCredentialsPath, configPath, { preferConfigSibling });
|
|
72333
72967
|
const existingPid = await readRuntimePid(pidPath);
|
|
72334
72968
|
const existingMonitorState = await readRuntimeMonitorState(monitorStatePath);
|
|
72335
72969
|
if (existingPid && isProcessRunning(existingPid)) {
|
|
@@ -72337,7 +72971,7 @@ async function startDetachedRuntime(params) {
|
|
|
72337
72971
|
alreadyRunning: true,
|
|
72338
72972
|
createdConfig: false,
|
|
72339
72973
|
pid: existingPid,
|
|
72340
|
-
configPath
|
|
72974
|
+
configPath,
|
|
72341
72975
|
logPath
|
|
72342
72976
|
};
|
|
72343
72977
|
}
|
|
@@ -72402,9 +73036,13 @@ async function startDetachedRuntime(params) {
|
|
|
72402
73036
|
};
|
|
72403
73037
|
}
|
|
72404
73038
|
async function stopDetachedRuntime(params, dependencies = {}) {
|
|
72405
|
-
const
|
|
72406
|
-
const
|
|
72407
|
-
const
|
|
73039
|
+
const configPath = resolveConfigPath(params.configPath);
|
|
73040
|
+
const preferConfigSibling = params.configPath != null;
|
|
73041
|
+
const pidPath = resolvePidPath(params.pidPath, configPath, { preferConfigSibling });
|
|
73042
|
+
const monitorStatePath = resolveMonitorStatePath(params.monitorStatePath, configPath, {
|
|
73043
|
+
preferConfigSibling
|
|
73044
|
+
});
|
|
73045
|
+
const runtimeCredentialsPath = resolveRuntimeCredentialsPath(params.runtimeCredentialsPath, configPath, { preferConfigSibling });
|
|
72408
73046
|
const existingPid = await readRuntimePid(pidPath);
|
|
72409
73047
|
const monitorState = await readRuntimeMonitorState(monitorStatePath);
|
|
72410
73048
|
let stopped = false;
|
|
@@ -72445,7 +73083,7 @@ async function stopDetachedRuntime(params, dependencies = {}) {
|
|
|
72445
73083
|
}
|
|
72446
73084
|
rmSync3(pidPath, { force: true });
|
|
72447
73085
|
removeRuntimeCredentials(runtimeCredentialsPath);
|
|
72448
|
-
await disableExpiredMemAccountsInConfig(
|
|
73086
|
+
await disableExpiredMemAccountsInConfig(configPath);
|
|
72449
73087
|
if (monitorState) {
|
|
72450
73088
|
await writeRuntimeMonitorState(monitorStatePath, {
|
|
72451
73089
|
...monitorState,
|
|
@@ -72456,7 +73094,7 @@ async function stopDetachedRuntime(params, dependencies = {}) {
|
|
|
72456
73094
|
});
|
|
72457
73095
|
}
|
|
72458
73096
|
if (params.hard) {
|
|
72459
|
-
const socketPath = await resolveTmuxSocketPath(
|
|
73097
|
+
const socketPath = await resolveTmuxSocketPath(configPath);
|
|
72460
73098
|
const tmux = new TmuxClient(socketPath);
|
|
72461
73099
|
try {
|
|
72462
73100
|
await tmux.killServer();
|
|
@@ -72489,9 +73127,12 @@ function removeRuntimePid(pidPath) {
|
|
|
72489
73127
|
}
|
|
72490
73128
|
async function getRuntimeStatus(params = {}) {
|
|
72491
73129
|
const configPath = resolveConfigPath(params.configPath);
|
|
72492
|
-
const
|
|
72493
|
-
const
|
|
72494
|
-
const
|
|
73130
|
+
const preferConfigSibling = params.configPath != null;
|
|
73131
|
+
const pidPath = resolvePidPath(params.pidPath, configPath, { preferConfigSibling });
|
|
73132
|
+
const logPath = resolveLogPath(params.logPath, configPath, { preferConfigSibling });
|
|
73133
|
+
const monitorStatePath = resolveMonitorStatePath(params.monitorStatePath, configPath, {
|
|
73134
|
+
preferConfigSibling
|
|
73135
|
+
});
|
|
72495
73136
|
const pid = await readRuntimePid(pidPath);
|
|
72496
73137
|
const liveness = pid ? getProcessLiveness(pid) : "missing";
|
|
72497
73138
|
const monitorState = await readRuntimeMonitorState(monitorStatePath);
|
|
@@ -72508,6 +73149,7 @@ async function getRuntimeStatus(params = {}) {
|
|
|
72508
73149
|
runtimePid: monitorState?.runtimePid && getProcessLiveness(monitorState.runtimePid) === "running" ? monitorState.runtimePid : undefined,
|
|
72509
73150
|
nextRestartAt: monitorState?.restart?.nextRestartAt,
|
|
72510
73151
|
restartNumber: monitorState?.restart?.restartNumber,
|
|
73152
|
+
restartMode: monitorState?.restart?.mode,
|
|
72511
73153
|
restartStageIndex: monitorState?.restart?.stageIndex,
|
|
72512
73154
|
stopReason: monitorState?.stopReason
|
|
72513
73155
|
};
|
|
@@ -72545,7 +73187,7 @@ function getLogSize(logPath) {
|
|
|
72545
73187
|
return 0;
|
|
72546
73188
|
}
|
|
72547
73189
|
try {
|
|
72548
|
-
return
|
|
73190
|
+
return statSync4(logPath).size;
|
|
72549
73191
|
} catch {
|
|
72550
73192
|
return 0;
|
|
72551
73193
|
}
|
|
@@ -72737,10 +73379,17 @@ function renderAccountsHelp() {
|
|
|
72737
73379
|
"",
|
|
72738
73380
|
"Usage:",
|
|
72739
73381
|
" clisbot accounts --help",
|
|
73382
|
+
" clisbot accounts help",
|
|
72740
73383
|
" clisbot accounts add telegram --account <id> --token <ENV_NAME|${ENV_NAME}|literal> [--persist]",
|
|
72741
73384
|
" clisbot accounts add slack --account <id> --app-token <ENV_NAME|${ENV_NAME}|literal> --bot-token <ENV_NAME|${ENV_NAME}|literal> [--persist]",
|
|
72742
73385
|
" clisbot accounts persist --channel <slack|telegram> --account <id>",
|
|
72743
|
-
" clisbot accounts persist --all"
|
|
73386
|
+
" clisbot accounts persist --all",
|
|
73387
|
+
"",
|
|
73388
|
+
"Notes:",
|
|
73389
|
+
" - env-style input such as `TELEGRAM_BOT_TOKEN` or `${TELEGRAM_BOT_TOKEN}` keeps the account env-backed in config",
|
|
73390
|
+
" - literal token input without `--persist` stays runtime-only and requires a running clisbot runtime",
|
|
73391
|
+
" - `--persist` writes canonical token files so later plain `clisbot start` can reuse the account safely",
|
|
73392
|
+
" - `persist --all` converts every configured `credentialType=mem` account into canonical token files"
|
|
72744
73393
|
].join(`
|
|
72745
73394
|
`);
|
|
72746
73395
|
}
|
|
@@ -72917,7 +73566,7 @@ async function runAccountsCli(args, deps = {}) {
|
|
|
72917
73566
|
...deps
|
|
72918
73567
|
};
|
|
72919
73568
|
const action = args[0];
|
|
72920
|
-
if (!action || action === "--help" || action === "-h") {
|
|
73569
|
+
if (!action || action === "--help" || action === "-h" || action === "help") {
|
|
72921
73570
|
console.log(renderAccountsHelp());
|
|
72922
73571
|
return;
|
|
72923
73572
|
}
|
|
@@ -73021,6 +73670,7 @@ function renderAuthCliHelp() {
|
|
|
73021
73670
|
" add-user/remove-user mutate roles.<role>.users",
|
|
73022
73671
|
" add-permission/remove-permission mutate roles.<role>.allow",
|
|
73023
73672
|
" agent role edits clone the inherited agent-defaults role into the target agent override on first write",
|
|
73673
|
+
" app `owner` and `admin` principals bypass DM pairing automatically once they are granted",
|
|
73024
73674
|
"",
|
|
73025
73675
|
"Examples:",
|
|
73026
73676
|
" clisbot auth add-user app --role owner --user telegram:1276408333",
|
|
@@ -73283,6 +73933,7 @@ function renderChannelsHelp() {
|
|
|
73283
73933
|
" - Slack private groups need channels.slack.groups.<groupId>",
|
|
73284
73934
|
" - Telegram groups need channels.telegram.groups.<chatId>",
|
|
73285
73935
|
" - Telegram forum topics need channels.telegram.groups.<chatId>.topics.<topicId>",
|
|
73936
|
+
" - route adds for Slack channels, Slack groups, Telegram groups, and Telegram topics default to `requireMention: true` unless you pass `--require-mention false`",
|
|
73286
73937
|
" - Adding a route puts that surface on the allowlist; other channels, groups, or topics still need to be added explicitly",
|
|
73287
73938
|
" - Tune route settings such as requireMention and followUp in clisbot.json when a surface should behave differently",
|
|
73288
73939
|
` - Manage routed auth and /bash access in ${AUTH_USER_GUIDE_DOC_PATH}`,
|
|
@@ -73361,6 +74012,7 @@ function renderRouteAddGuidance(params) {
|
|
|
73361
74012
|
console.log(` - route added: ${routePath}`);
|
|
73362
74013
|
console.log(" - direct messages still follow channels.slack.directMessages.policy (`open`, `pairing`, `allowlist`, or `disabled`)");
|
|
73363
74014
|
console.log(` - this ${routeLabel} still follows channels.slack.groupPolicy and route settings such as requireMention and followUp`);
|
|
74015
|
+
console.log(" - new Slack channel/group routes default to `requireMention: true` unless you passed `--require-mention false`");
|
|
73364
74016
|
console.log(" - if you want pairing-style access control for DMs, set channels.slack.directMessages.policy to `pairing`");
|
|
73365
74017
|
console.log(" - if you want stricter route access, keep Slack groups on allowlist and only add the channels/groups you trust");
|
|
73366
74018
|
console.log(` - manage routed auth and /bash access in ${AUTH_USER_GUIDE_DOC_PATH}`);
|
|
@@ -73371,6 +74023,7 @@ function renderRouteAddGuidance(params) {
|
|
|
73371
74023
|
console.log(` - route added: ${routePath}`);
|
|
73372
74024
|
console.log(" - direct messages still follow channels.telegram.directMessages.policy (`open`, `pairing`, `allowlist`, or `disabled`)");
|
|
73373
74025
|
console.log(` - this ${params.kind} is now on the Telegram allowlist; other groups or topics still need to be added explicitly`);
|
|
74026
|
+
console.log(" - new Telegram group/topic routes default to `requireMention: true` unless you passed `--require-mention false`");
|
|
73374
74027
|
console.log(" - if you want pairing-style access control for DMs, set channels.telegram.directMessages.policy to `pairing`");
|
|
73375
74028
|
console.log(" - tune route settings such as requireMention and followUp in clisbot.json if this surface should behave differently");
|
|
73376
74029
|
console.log(` - manage routed auth and /bash access in ${AUTH_USER_GUIDE_DOC_PATH}`);
|
|
@@ -73569,7 +74222,7 @@ async function addSlackRoute(kind, args) {
|
|
|
73569
74222
|
}
|
|
73570
74223
|
const { config, configPath } = await readEditableConfig(getEditableConfigPath7());
|
|
73571
74224
|
const agentId = getAgentId(args);
|
|
73572
|
-
const requireMention = parseBooleanOption(args, "--require-mention",
|
|
74225
|
+
const requireMention = parseBooleanOption(args, "--require-mention", true);
|
|
73573
74226
|
const target = kind === "channel" ? config.channels.slack.channels : config.channels.slack.groups;
|
|
73574
74227
|
target[routeId] = {
|
|
73575
74228
|
...target[routeId] ?? {},
|
|
@@ -74800,6 +75453,41 @@ function assertSupportedPlatform(command) {
|
|
|
74800
75453
|
}
|
|
74801
75454
|
|
|
74802
75455
|
// src/control/runtime-bootstrap-cli.ts
|
|
75456
|
+
function hasHelpFlag(args) {
|
|
75457
|
+
return args.includes("--help") || args.includes("-h") || args.includes("help");
|
|
75458
|
+
}
|
|
75459
|
+
function renderBootstrapCommandHelp(commandName) {
|
|
75460
|
+
const behavior = commandName === "start" ? "seed config if needed and start the detached runtime" : "seed config and optionally bootstrap the first agent without starting runtime";
|
|
75461
|
+
return [
|
|
75462
|
+
`clisbot ${commandName}`,
|
|
75463
|
+
"",
|
|
75464
|
+
"Usage:",
|
|
75465
|
+
` clisbot ${commandName} --help`,
|
|
75466
|
+
` clisbot ${commandName} [--cli <codex|claude|gemini>] [--bot-type <personal|team>] [--persist]`,
|
|
75467
|
+
" [--slack-account <id> --slack-app-token <ENV_NAME|${ENV_NAME}|literal> --slack-bot-token <ENV_NAME|${ENV_NAME}|literal>]...",
|
|
75468
|
+
" [--telegram-account <id> --telegram-bot-token <ENV_NAME|${ENV_NAME}|literal>]...",
|
|
75469
|
+
"",
|
|
75470
|
+
"Behavior:",
|
|
75471
|
+
` - ${behavior}`,
|
|
75472
|
+
" - first-run agent bootstrap needs both `--cli` and `--bot-type`",
|
|
75473
|
+
" - `--bot-type personal` maps to `personal-assistant`; `--bot-type team` maps to `team-assistant`",
|
|
75474
|
+
" - explicit credential flags only enable the channels and accounts you named in this command",
|
|
75475
|
+
" - env-style values such as `SLACK_APP_TOKEN` or `${SLACK_APP_TOKEN}` stay env-backed in config",
|
|
75476
|
+
commandName === "start" ? " - literal token values without `--persist` stay runtime-only for this start invocation" : " - literal token values on `init` require `--persist` because no runtime exists yet",
|
|
75477
|
+
" - `--persist` writes canonical credential files so later plain `clisbot start` can reuse them",
|
|
75478
|
+
"",
|
|
75479
|
+
"Examples:",
|
|
75480
|
+
` clisbot ${commandName} --cli codex --bot-type personal --telegram-bot-token TELEGRAM_BOT_TOKEN`,
|
|
75481
|
+
` clisbot ${commandName} --cli codex --bot-type team --slack-app-token SLACK_APP_TOKEN --slack-bot-token SLACK_BOT_TOKEN`,
|
|
75482
|
+
` clisbot ${commandName} --cli gemini --bot-type personal --telegram-bot-token "$TELEGRAM_BOT_TOKEN" --persist`,
|
|
75483
|
+
"",
|
|
75484
|
+
"Related help:",
|
|
75485
|
+
" - `clisbot agents --help` for lower-level agent bootstrap and binding control",
|
|
75486
|
+
" - `clisbot accounts --help` for account persistence after first run",
|
|
75487
|
+
" - `clisbot channels --help` for route setup after bootstrap"
|
|
75488
|
+
].join(`
|
|
75489
|
+
`);
|
|
75490
|
+
}
|
|
74803
75491
|
function getPrimaryWorkspacePath(summary) {
|
|
74804
75492
|
const preferredAgentId = summary.channelSummaries.find((channel) => channel.enabled)?.defaultAgentId ?? "default";
|
|
74805
75493
|
return summary.agentSummaries.find((agent) => agent.id === preferredAgentId)?.workspacePath ?? summary.agentSummaries[0]?.workspacePath;
|
|
@@ -75016,6 +75704,10 @@ async function printStartedRuntimeSummary(pid, configPath, logPath) {
|
|
|
75016
75704
|
}
|
|
75017
75705
|
}
|
|
75018
75706
|
async function initConfig(args = []) {
|
|
75707
|
+
if (hasHelpFlag(args)) {
|
|
75708
|
+
console.log(renderBootstrapCommandHelp("init"));
|
|
75709
|
+
return;
|
|
75710
|
+
}
|
|
75019
75711
|
const state = await prepareBootstrapState(args, "init");
|
|
75020
75712
|
if (!state) {
|
|
75021
75713
|
return;
|
|
@@ -75034,6 +75726,10 @@ async function initConfig(args = []) {
|
|
|
75034
75726
|
}
|
|
75035
75727
|
}
|
|
75036
75728
|
async function start(args = []) {
|
|
75729
|
+
if (hasHelpFlag(args)) {
|
|
75730
|
+
console.log(renderBootstrapCommandHelp("start"));
|
|
75731
|
+
return;
|
|
75732
|
+
}
|
|
75037
75733
|
const runtimeStatus = await getRuntimeStatus();
|
|
75038
75734
|
const bootstrapFlags = parseBootstrapFlags(args);
|
|
75039
75735
|
const restartForLiteralBootstrap = runtimeStatus.running && hasLiteralMemCredentials(bootstrapFlags);
|
|
@@ -75086,7 +75782,7 @@ async function start(args = []) {
|
|
|
75086
75782
|
}
|
|
75087
75783
|
|
|
75088
75784
|
// src/control/runtime-supervisor.ts
|
|
75089
|
-
import { statSync as
|
|
75785
|
+
import { statSync as statSync5, watch } from "node:fs";
|
|
75090
75786
|
import { basename as basename4, dirname as dirname15 } from "node:path";
|
|
75091
75787
|
|
|
75092
75788
|
// src/channels/processed-events-store.ts
|
|
@@ -75249,12 +75945,22 @@ class RuntimeSupervisor {
|
|
|
75249
75945
|
let nextRuntime;
|
|
75250
75946
|
try {
|
|
75251
75947
|
const loadedConfig = await this.dependencies.loadConfig(this.configPath);
|
|
75948
|
+
const configMtimeMs = statSync5(loadedConfig.configPath).mtimeMs;
|
|
75949
|
+
if (reason === "watch" && consumeSuppressedConfigReload(loadedConfig.configPath, configMtimeMs)) {
|
|
75950
|
+
await this.reconcileConfigWatcher(loadedConfig);
|
|
75951
|
+
await this.dependencies.runtimeHealthStore.setReload({
|
|
75952
|
+
status: "success",
|
|
75953
|
+
reason,
|
|
75954
|
+
configMtimeMs
|
|
75955
|
+
});
|
|
75956
|
+
return;
|
|
75957
|
+
}
|
|
75252
75958
|
nextRuntime = await this.createRuntime(loadedConfig);
|
|
75253
75959
|
await this.reconcileConfigWatcher(loadedConfig);
|
|
75254
75960
|
await this.dependencies.runtimeHealthStore.setReload({
|
|
75255
75961
|
status: "success",
|
|
75256
75962
|
reason,
|
|
75257
|
-
configMtimeMs
|
|
75963
|
+
configMtimeMs
|
|
75258
75964
|
});
|
|
75259
75965
|
this.activeRuntime = nextRuntime;
|
|
75260
75966
|
if (previousRuntime) {
|
|
@@ -75596,6 +76302,9 @@ async function printStatusSummary() {
|
|
|
75596
76302
|
if (runtimeStatus.restartNumber) {
|
|
75597
76303
|
console.log(`restart attempt: ${runtimeStatus.restartNumber}`);
|
|
75598
76304
|
}
|
|
76305
|
+
if (runtimeStatus.restartMode) {
|
|
76306
|
+
console.log(`restart mode: ${runtimeStatus.restartMode}`);
|
|
76307
|
+
}
|
|
75599
76308
|
if (runtimeStatus.restartStageIndex != null && runtimeStatus.restartStageIndex >= 0) {
|
|
75600
76309
|
console.log(`restart stage: ${runtimeStatus.restartStageIndex + 1}`);
|
|
75601
76310
|
}
|