clisbot 0.1.41 → 0.1.42
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/README.md +28 -67
- package/config/clisbot.json.template +1 -1
- package/dist/main.js +1075 -379
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -59566,6 +59566,139 @@ var agentAuthOverrideSchema = exports_external.object({
|
|
|
59566
59566
|
roles: exports_external.record(exports_external.string(), authRoleOverrideSchema).default({})
|
|
59567
59567
|
});
|
|
59568
59568
|
|
|
59569
|
+
// src/config/runtime-monitor-backoff.ts
|
|
59570
|
+
var LEGACY_DEFAULT_RUNTIME_MONITOR_RESTART_BACKOFF = {
|
|
59571
|
+
fastRetry: {
|
|
59572
|
+
delaySeconds: 10,
|
|
59573
|
+
maxRestarts: 3
|
|
59574
|
+
},
|
|
59575
|
+
stages: [
|
|
59576
|
+
{
|
|
59577
|
+
delayMinutes: 15,
|
|
59578
|
+
maxRestarts: 4
|
|
59579
|
+
},
|
|
59580
|
+
{
|
|
59581
|
+
delayMinutes: 30,
|
|
59582
|
+
maxRestarts: 4
|
|
59583
|
+
}
|
|
59584
|
+
]
|
|
59585
|
+
};
|
|
59586
|
+
var DEFAULT_RUNTIME_MONITOR_RESTART_BACKOFF = {
|
|
59587
|
+
fastRetry: {
|
|
59588
|
+
delaySeconds: 10,
|
|
59589
|
+
maxRestarts: 3
|
|
59590
|
+
},
|
|
59591
|
+
stages: [
|
|
59592
|
+
{
|
|
59593
|
+
delayMinutes: 1,
|
|
59594
|
+
maxRestarts: 2
|
|
59595
|
+
},
|
|
59596
|
+
{
|
|
59597
|
+
delayMinutes: 3,
|
|
59598
|
+
maxRestarts: 2
|
|
59599
|
+
},
|
|
59600
|
+
{
|
|
59601
|
+
delayMinutes: 5,
|
|
59602
|
+
maxRestarts: 2
|
|
59603
|
+
},
|
|
59604
|
+
{
|
|
59605
|
+
delayMinutes: 10,
|
|
59606
|
+
maxRestarts: 3
|
|
59607
|
+
},
|
|
59608
|
+
{
|
|
59609
|
+
delayMinutes: 15,
|
|
59610
|
+
maxRestarts: 4
|
|
59611
|
+
},
|
|
59612
|
+
{
|
|
59613
|
+
delayMinutes: 30,
|
|
59614
|
+
maxRestarts: 4
|
|
59615
|
+
}
|
|
59616
|
+
]
|
|
59617
|
+
};
|
|
59618
|
+
var RUNTIME_MONITOR_RESTART_RESET_AFTER_MS = 15 * 60000;
|
|
59619
|
+
function cloneRestartBackoff(restartBackoff) {
|
|
59620
|
+
return {
|
|
59621
|
+
fastRetry: {
|
|
59622
|
+
...restartBackoff.fastRetry
|
|
59623
|
+
},
|
|
59624
|
+
stages: restartBackoff.stages.map((stage) => ({
|
|
59625
|
+
...stage
|
|
59626
|
+
}))
|
|
59627
|
+
};
|
|
59628
|
+
}
|
|
59629
|
+
function matchesRestartBackoffShape(left, right) {
|
|
59630
|
+
if (left.fastRetry.delaySeconds !== right.fastRetry.delaySeconds || left.fastRetry.maxRestarts !== right.fastRetry.maxRestarts || left.stages.length !== right.stages.length) {
|
|
59631
|
+
return false;
|
|
59632
|
+
}
|
|
59633
|
+
return left.stages.every((stage, index) => {
|
|
59634
|
+
const other = right.stages[index];
|
|
59635
|
+
return other != null && stage.delayMinutes === other.delayMinutes && stage.maxRestarts === other.maxRestarts;
|
|
59636
|
+
});
|
|
59637
|
+
}
|
|
59638
|
+
function getDefaultRuntimeMonitorRestartBackoff() {
|
|
59639
|
+
return cloneRestartBackoff(DEFAULT_RUNTIME_MONITOR_RESTART_BACKOFF);
|
|
59640
|
+
}
|
|
59641
|
+
function normalizeRuntimeMonitorRestartBackoff(restartBackoff) {
|
|
59642
|
+
if (matchesRestartBackoffShape(restartBackoff, LEGACY_DEFAULT_RUNTIME_MONITOR_RESTART_BACKOFF)) {
|
|
59643
|
+
return getDefaultRuntimeMonitorRestartBackoff();
|
|
59644
|
+
}
|
|
59645
|
+
return cloneRestartBackoff(restartBackoff);
|
|
59646
|
+
}
|
|
59647
|
+
function getConfiguredRuntimeMonitorRestartBudget(restartBackoff) {
|
|
59648
|
+
const normalized = normalizeRuntimeMonitorRestartBackoff(restartBackoff);
|
|
59649
|
+
return normalized.fastRetry.maxRestarts + normalized.stages.reduce((sum, stage) => sum + stage.maxRestarts, 0);
|
|
59650
|
+
}
|
|
59651
|
+
function getRuntimeMonitorRestartPlan(restartBackoff, restartNumber) {
|
|
59652
|
+
const normalized = normalizeRuntimeMonitorRestartBackoff(restartBackoff);
|
|
59653
|
+
const totalConfiguredRestarts = getConfiguredRuntimeMonitorRestartBudget(normalized);
|
|
59654
|
+
const fastRetryMaxRestarts = normalized.fastRetry.maxRestarts;
|
|
59655
|
+
if (restartNumber >= 1 && restartNumber <= fastRetryMaxRestarts) {
|
|
59656
|
+
return {
|
|
59657
|
+
mode: "fast-retry",
|
|
59658
|
+
stageIndex: -1,
|
|
59659
|
+
delayMs: normalized.fastRetry.delaySeconds * 1000,
|
|
59660
|
+
restartAttemptInStage: restartNumber,
|
|
59661
|
+
restartsRemaining: Math.max(0, totalConfiguredRestarts - restartNumber),
|
|
59662
|
+
totalConfiguredRestarts,
|
|
59663
|
+
stageMaxRestarts: fastRetryMaxRestarts,
|
|
59664
|
+
repeatingFinalStage: false
|
|
59665
|
+
};
|
|
59666
|
+
}
|
|
59667
|
+
let completedRestarts = fastRetryMaxRestarts;
|
|
59668
|
+
for (let index = 0;index < normalized.stages.length; index += 1) {
|
|
59669
|
+
const stage = normalized.stages[index];
|
|
59670
|
+
const stageStart = completedRestarts + 1;
|
|
59671
|
+
const stageEnd = completedRestarts + stage.maxRestarts;
|
|
59672
|
+
if (restartNumber >= stageStart && restartNumber <= stageEnd) {
|
|
59673
|
+
return {
|
|
59674
|
+
mode: "backoff",
|
|
59675
|
+
stageIndex: index,
|
|
59676
|
+
delayMs: stage.delayMinutes * 60000,
|
|
59677
|
+
restartAttemptInStage: restartNumber - completedRestarts,
|
|
59678
|
+
restartsRemaining: Math.max(0, totalConfiguredRestarts - restartNumber),
|
|
59679
|
+
totalConfiguredRestarts,
|
|
59680
|
+
stageMaxRestarts: stage.maxRestarts,
|
|
59681
|
+
repeatingFinalStage: false
|
|
59682
|
+
};
|
|
59683
|
+
}
|
|
59684
|
+
completedRestarts = stageEnd;
|
|
59685
|
+
}
|
|
59686
|
+
const finalStage = normalized.stages.at(-1);
|
|
59687
|
+
if (!finalStage) {
|
|
59688
|
+
return null;
|
|
59689
|
+
}
|
|
59690
|
+
return {
|
|
59691
|
+
mode: "backoff",
|
|
59692
|
+
stageIndex: normalized.stages.length - 1,
|
|
59693
|
+
delayMs: finalStage.delayMinutes * 60000,
|
|
59694
|
+
restartAttemptInStage: Math.max(1, restartNumber - completedRestarts),
|
|
59695
|
+
restartsRemaining: 0,
|
|
59696
|
+
totalConfiguredRestarts,
|
|
59697
|
+
stageMaxRestarts: finalStage.maxRestarts,
|
|
59698
|
+
repeatingFinalStage: true
|
|
59699
|
+
};
|
|
59700
|
+
}
|
|
59701
|
+
|
|
59569
59702
|
// src/config/schema.ts
|
|
59570
59703
|
var defaultSessionIdPattern = "\\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\\b";
|
|
59571
59704
|
var runnerSessionIdCreateSchema = exports_external.object({
|
|
@@ -60017,6 +60150,7 @@ var appControlLoopSchema = exports_external.object({
|
|
|
60017
60150
|
defaultIntervalMinutes: exports_external.number().int().positive().optional(),
|
|
60018
60151
|
maxTimes: exports_external.number().int().positive().optional()
|
|
60019
60152
|
});
|
|
60153
|
+
var defaultRuntimeMonitorRestartBackoff = getDefaultRuntimeMonitorRestartBackoff();
|
|
60020
60154
|
var appControlRuntimeMonitorSchema = exports_external.object({
|
|
60021
60155
|
restartBackoff: exports_external.object({
|
|
60022
60156
|
fastRetry: exports_external.object({
|
|
@@ -60029,32 +60163,8 @@ var appControlRuntimeMonitorSchema = exports_external.object({
|
|
|
60029
60163
|
stages: exports_external.array(exports_external.object({
|
|
60030
60164
|
delayMinutes: exports_external.number().int().positive().default(15),
|
|
60031
60165
|
maxRestarts: exports_external.number().int().positive().default(4)
|
|
60032
|
-
})).min(1).default(
|
|
60033
|
-
|
|
60034
|
-
delayMinutes: 15,
|
|
60035
|
-
maxRestarts: 4
|
|
60036
|
-
},
|
|
60037
|
-
{
|
|
60038
|
-
delayMinutes: 30,
|
|
60039
|
-
maxRestarts: 4
|
|
60040
|
-
}
|
|
60041
|
-
])
|
|
60042
|
-
}).default({
|
|
60043
|
-
fastRetry: {
|
|
60044
|
-
delaySeconds: 10,
|
|
60045
|
-
maxRestarts: 3
|
|
60046
|
-
},
|
|
60047
|
-
stages: [
|
|
60048
|
-
{
|
|
60049
|
-
delayMinutes: 15,
|
|
60050
|
-
maxRestarts: 4
|
|
60051
|
-
},
|
|
60052
|
-
{
|
|
60053
|
-
delayMinutes: 30,
|
|
60054
|
-
maxRestarts: 4
|
|
60055
|
-
}
|
|
60056
|
-
]
|
|
60057
|
-
}),
|
|
60166
|
+
})).min(1).default(defaultRuntimeMonitorRestartBackoff.stages)
|
|
60167
|
+
}).default(defaultRuntimeMonitorRestartBackoff),
|
|
60058
60168
|
ownerAlerts: exports_external.object({
|
|
60059
60169
|
enabled: exports_external.boolean().default(true),
|
|
60060
60170
|
minIntervalMinutes: exports_external.number().int().positive().default(30)
|
|
@@ -60222,10 +60332,10 @@ var agentsDefaultsSchema = exports_external.object({
|
|
|
60222
60332
|
});
|
|
60223
60333
|
var clisbotConfigSchema = exports_external.object({
|
|
60224
60334
|
meta: exports_external.object({
|
|
60225
|
-
schemaVersion: exports_external.string().min(1).default("0.1.
|
|
60335
|
+
schemaVersion: exports_external.string().min(1).default("0.1.42"),
|
|
60226
60336
|
lastTouchedAt: exports_external.string().optional()
|
|
60227
60337
|
}).default({
|
|
60228
|
-
schemaVersion: "0.1.
|
|
60338
|
+
schemaVersion: "0.1.42"
|
|
60229
60339
|
}),
|
|
60230
60340
|
app: exports_external.object({
|
|
60231
60341
|
session: appSessionSchema.default({
|
|
@@ -60248,22 +60358,7 @@ var clisbotConfigSchema = exports_external.object({
|
|
|
60248
60358
|
maxActiveLoops: 10
|
|
60249
60359
|
}),
|
|
60250
60360
|
runtimeMonitor: appControlRuntimeMonitorSchema.default({
|
|
60251
|
-
restartBackoff:
|
|
60252
|
-
fastRetry: {
|
|
60253
|
-
delaySeconds: 10,
|
|
60254
|
-
maxRestarts: 3
|
|
60255
|
-
},
|
|
60256
|
-
stages: [
|
|
60257
|
-
{
|
|
60258
|
-
delayMinutes: 15,
|
|
60259
|
-
maxRestarts: 4
|
|
60260
|
-
},
|
|
60261
|
-
{
|
|
60262
|
-
delayMinutes: 30,
|
|
60263
|
-
maxRestarts: 4
|
|
60264
|
-
}
|
|
60265
|
-
]
|
|
60266
|
-
},
|
|
60361
|
+
restartBackoff: defaultRuntimeMonitorRestartBackoff,
|
|
60267
60362
|
ownerAlerts: {
|
|
60268
60363
|
enabled: true,
|
|
60269
60364
|
minIntervalMinutes: 30
|
|
@@ -60283,22 +60378,7 @@ var clisbotConfigSchema = exports_external.object({
|
|
|
60283
60378
|
maxActiveLoops: 10
|
|
60284
60379
|
},
|
|
60285
60380
|
runtimeMonitor: {
|
|
60286
|
-
restartBackoff:
|
|
60287
|
-
fastRetry: {
|
|
60288
|
-
delaySeconds: 10,
|
|
60289
|
-
maxRestarts: 3
|
|
60290
|
-
},
|
|
60291
|
-
stages: [
|
|
60292
|
-
{
|
|
60293
|
-
delayMinutes: 15,
|
|
60294
|
-
maxRestarts: 4
|
|
60295
|
-
},
|
|
60296
|
-
{
|
|
60297
|
-
delayMinutes: 30,
|
|
60298
|
-
maxRestarts: 4
|
|
60299
|
-
}
|
|
60300
|
-
]
|
|
60301
|
-
},
|
|
60381
|
+
restartBackoff: defaultRuntimeMonitorRestartBackoff,
|
|
60302
60382
|
ownerAlerts: {
|
|
60303
60383
|
enabled: true,
|
|
60304
60384
|
minIntervalMinutes: 30
|
|
@@ -60326,22 +60406,7 @@ var clisbotConfigSchema = exports_external.object({
|
|
|
60326
60406
|
maxActiveLoops: 10
|
|
60327
60407
|
},
|
|
60328
60408
|
runtimeMonitor: {
|
|
60329
|
-
restartBackoff:
|
|
60330
|
-
fastRetry: {
|
|
60331
|
-
delaySeconds: 10,
|
|
60332
|
-
maxRestarts: 3
|
|
60333
|
-
},
|
|
60334
|
-
stages: [
|
|
60335
|
-
{
|
|
60336
|
-
delayMinutes: 15,
|
|
60337
|
-
maxRestarts: 4
|
|
60338
|
-
},
|
|
60339
|
-
{
|
|
60340
|
-
delayMinutes: 30,
|
|
60341
|
-
maxRestarts: 4
|
|
60342
|
-
}
|
|
60343
|
-
]
|
|
60344
|
-
},
|
|
60409
|
+
restartBackoff: defaultRuntimeMonitorRestartBackoff,
|
|
60345
60410
|
ownerAlerts: {
|
|
60346
60411
|
enabled: true,
|
|
60347
60412
|
minIntervalMinutes: 30
|
|
@@ -61022,6 +61087,8 @@ function materializeLoadedConfig(expandedConfigPath, validated) {
|
|
|
61022
61087
|
socketPath: expandHomePath(validated.agents.defaults.runner.defaults.tmux.socketPath || getDefaultTmuxSocketPath())
|
|
61023
61088
|
}
|
|
61024
61089
|
};
|
|
61090
|
+
runtimeRaw.app.control.runtimeMonitor.restartBackoff = normalizeRuntimeMonitorRestartBackoff(runtimeRaw.app.control.runtimeMonitor.restartBackoff);
|
|
61091
|
+
runtimeRaw.control.runtimeMonitor.restartBackoff = normalizeRuntimeMonitorRestartBackoff(runtimeRaw.control.runtimeMonitor.restartBackoff);
|
|
61025
61092
|
return {
|
|
61026
61093
|
configPath: expandedConfigPath,
|
|
61027
61094
|
processedEventsPath: getDefaultProcessedEventsPath(),
|
|
@@ -61104,9 +61171,10 @@ function renderDefaultConfigTemplate(options = {}) {
|
|
|
61104
61171
|
const sessionStorePath = collapseHomePath(getDefaultSessionStorePath());
|
|
61105
61172
|
const workspaceTemplate = collapseHomePath(getDefaultWorkspaceTemplate());
|
|
61106
61173
|
const defaultTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
61174
|
+
const defaultRuntimeMonitorRestartBackoff2 = getDefaultRuntimeMonitorRestartBackoff();
|
|
61107
61175
|
return JSON.stringify({
|
|
61108
61176
|
meta: {
|
|
61109
|
-
schemaVersion: "0.1.
|
|
61177
|
+
schemaVersion: "0.1.42",
|
|
61110
61178
|
lastTouchedAt: new Date().toISOString()
|
|
61111
61179
|
},
|
|
61112
61180
|
app: {
|
|
@@ -61148,22 +61216,7 @@ function renderDefaultConfigTemplate(options = {}) {
|
|
|
61148
61216
|
defaultTimezone
|
|
61149
61217
|
},
|
|
61150
61218
|
runtimeMonitor: {
|
|
61151
|
-
restartBackoff:
|
|
61152
|
-
fastRetry: {
|
|
61153
|
-
delaySeconds: 10,
|
|
61154
|
-
maxRestarts: 3
|
|
61155
|
-
},
|
|
61156
|
-
stages: [
|
|
61157
|
-
{
|
|
61158
|
-
delayMinutes: 15,
|
|
61159
|
-
maxRestarts: 4
|
|
61160
|
-
},
|
|
61161
|
-
{
|
|
61162
|
-
delayMinutes: 30,
|
|
61163
|
-
maxRestarts: 4
|
|
61164
|
-
}
|
|
61165
|
-
]
|
|
61166
|
-
},
|
|
61219
|
+
restartBackoff: defaultRuntimeMonitorRestartBackoff2,
|
|
61167
61220
|
ownerAlerts: {
|
|
61168
61221
|
enabled: true,
|
|
61169
61222
|
minIntervalMinutes: 30
|
|
@@ -64076,9 +64129,11 @@ function parseAgentCommand(text, options = {}) {
|
|
|
64076
64129
|
};
|
|
64077
64130
|
}
|
|
64078
64131
|
if (lowered === "transcript") {
|
|
64132
|
+
const transcriptMode = withoutSlash.slice(command.length).trim().toLowerCase() === "full" ? "full" : "default";
|
|
64079
64133
|
return {
|
|
64080
64134
|
type: "control",
|
|
64081
|
-
name: "transcript"
|
|
64135
|
+
name: "transcript",
|
|
64136
|
+
mode: transcriptMode
|
|
64082
64137
|
};
|
|
64083
64138
|
}
|
|
64084
64139
|
if (lowered === "attach") {
|
|
@@ -64123,6 +64178,10 @@ function parseAgentCommand(text, options = {}) {
|
|
|
64123
64178
|
if (lowered === "followup") {
|
|
64124
64179
|
return parseFollowUpSlashCommand(withoutSlash.slice(command.length).trim().toLowerCase());
|
|
64125
64180
|
}
|
|
64181
|
+
if (lowered === "mention") {
|
|
64182
|
+
const scope = withoutSlash.slice(command.length).trim().toLowerCase();
|
|
64183
|
+
return parseFollowUpSlashCommand(scope ? `mention-only ${scope}` : "mention-only");
|
|
64184
|
+
}
|
|
64126
64185
|
if (lowered === "pause") {
|
|
64127
64186
|
return parseFollowUpSlashCommand("pause");
|
|
64128
64187
|
}
|
|
@@ -64339,7 +64398,8 @@ function renderAgentControlSlashHelp() {
|
|
|
64339
64398
|
"- `/status`: show the current route status and operator setup commands",
|
|
64340
64399
|
"- `/help`: show available control slash commands",
|
|
64341
64400
|
"- `/whoami`: show the current platform, route, and sender identity details",
|
|
64342
|
-
"- `/transcript`: show
|
|
64401
|
+
"- `/transcript`: show a short recent session snapshot when the route verbose policy allows it",
|
|
64402
|
+
"- `/transcript full`: show a longer session snapshot when you need the full pane context",
|
|
64343
64403
|
"- `/attach`: attach this thread to the active run and resume live updates when it is still processing",
|
|
64344
64404
|
"- `/detach`: stop live updates for this thread while still posting the final result here",
|
|
64345
64405
|
"- `/watch every 30s [for 10m]`: post the latest state on an interval until the run settles or the watch window ends",
|
|
@@ -64347,11 +64407,11 @@ function renderAgentControlSlashHelp() {
|
|
|
64347
64407
|
"- `/nudge`: send one extra Enter to the current tmux session without resending the prompt text",
|
|
64348
64408
|
"- `/followup status`: show the current conversation follow-up policy",
|
|
64349
64409
|
"- `/followup auto`: allow natural follow-up after the bot has replied in-thread",
|
|
64350
|
-
"- `/followup mention-only`: require explicit mention for each later turn",
|
|
64351
|
-
"- `/followup
|
|
64352
|
-
"- `/followup
|
|
64353
|
-
"- `/pause`:
|
|
64354
|
-
"- `/resume`:
|
|
64410
|
+
"- `/followup mention-only` or `/mention`: require explicit mention for each later turn",
|
|
64411
|
+
"- `/followup mention-only channel` or `/mention channel`: persist mention-only as the default for the current channel or group",
|
|
64412
|
+
"- `/followup mention-only all` or `/mention all`: persist mention-only as the default for all routed conversations on this bot",
|
|
64413
|
+
"- `/followup pause` or `/pause`: stop passive follow-up until the next explicit mention",
|
|
64414
|
+
"- `/followup resume` or `/resume`: clear the runtime override and restore config defaults",
|
|
64355
64415
|
"- `/streaming status|on|off|latest|all`: show or change streaming mode for this surface",
|
|
64356
64416
|
"- `/responsemode status`: show the configured response mode for this surface",
|
|
64357
64417
|
"- `/responsemode capture-pane`: settle replies from captured pane output for this surface",
|
|
@@ -64401,31 +64461,44 @@ function parseWatchCommand(raw) {
|
|
|
64401
64461
|
durationMs: parsedDurationMs ?? undefined
|
|
64402
64462
|
};
|
|
64403
64463
|
}
|
|
64464
|
+
function parseFollowUpScope(raw) {
|
|
64465
|
+
if (raw === "channel") {
|
|
64466
|
+
return "channel";
|
|
64467
|
+
}
|
|
64468
|
+
if (raw === "all") {
|
|
64469
|
+
return "all";
|
|
64470
|
+
}
|
|
64471
|
+
return "conversation";
|
|
64472
|
+
}
|
|
64404
64473
|
function parseFollowUpSlashCommand(action) {
|
|
64405
|
-
|
|
64474
|
+
const [rawAction = "", rawScope = ""] = action.split(/\s+/, 2).map((token) => token.trim().toLowerCase());
|
|
64475
|
+
const scope = parseFollowUpScope(rawScope);
|
|
64476
|
+
if (!rawAction || rawAction === "status") {
|
|
64406
64477
|
return {
|
|
64407
64478
|
type: "control",
|
|
64408
64479
|
name: "followup",
|
|
64409
64480
|
action: "status"
|
|
64410
64481
|
};
|
|
64411
64482
|
}
|
|
64412
|
-
if (
|
|
64483
|
+
if (rawAction === "auto") {
|
|
64413
64484
|
return {
|
|
64414
64485
|
type: "control",
|
|
64415
64486
|
name: "followup",
|
|
64416
64487
|
action: "auto",
|
|
64417
|
-
mode: "auto"
|
|
64488
|
+
mode: "auto",
|
|
64489
|
+
scope
|
|
64418
64490
|
};
|
|
64419
64491
|
}
|
|
64420
|
-
if (
|
|
64492
|
+
if (rawAction === "mention-only") {
|
|
64421
64493
|
return {
|
|
64422
64494
|
type: "control",
|
|
64423
64495
|
name: "followup",
|
|
64424
64496
|
action: "mention-only",
|
|
64425
|
-
mode: "mention-only"
|
|
64497
|
+
mode: "mention-only",
|
|
64498
|
+
scope
|
|
64426
64499
|
};
|
|
64427
64500
|
}
|
|
64428
|
-
if (
|
|
64501
|
+
if (rawAction === "pause") {
|
|
64429
64502
|
return {
|
|
64430
64503
|
type: "control",
|
|
64431
64504
|
name: "followup",
|
|
@@ -64433,7 +64506,7 @@ function parseFollowUpSlashCommand(action) {
|
|
|
64433
64506
|
mode: "paused"
|
|
64434
64507
|
};
|
|
64435
64508
|
}
|
|
64436
|
-
if (
|
|
64509
|
+
if (rawAction === "resume") {
|
|
64437
64510
|
return {
|
|
64438
64511
|
type: "control",
|
|
64439
64512
|
name: "followup",
|
|
@@ -65126,7 +65199,7 @@ var REPLY_COMMAND = `{{reply_command_base}}
|
|
|
65126
65199
|
<user-facing reply>
|
|
65127
65200
|
__CLISBOT_MESSAGE__
|
|
65128
65201
|
)" \\
|
|
65129
|
-
[--
|
|
65202
|
+
[--file /absolute/path/to/file]`;
|
|
65130
65203
|
var REPLY_RULES = `When replying to the user:
|
|
65131
65204
|
- put the user-facing message inside the --message body of that command
|
|
65132
65205
|
{{progress_rules_block}}- {{final_rule_line}}`;
|
|
@@ -65159,8 +65232,10 @@ var TELEGRAM_REPLY_COMMAND_BASE = `{{command}} message send \\
|
|
|
65159
65232
|
{{thread_clause}} --input md \\
|
|
65160
65233
|
--render native \\
|
|
65161
65234
|
`;
|
|
65162
|
-
var SLACK_REPLY_STYLE_HINT =
|
|
65163
|
-
|
|
65235
|
+
var SLACK_REPLY_STYLE_HINT = `Put readable hierarchical Markdown in the --message body.
|
|
65236
|
+
Keep each paragraph, list, or code block under 2500 chars.`;
|
|
65237
|
+
var TELEGRAM_REPLY_STYLE_HINT = `Put readable hierarchical Markdown in the --message body.
|
|
65238
|
+
Keep the Markdown body under 3000 chars.`;
|
|
65164
65239
|
var ACCOUNT_CLAUSE = " --account {{account_id}} \\\n";
|
|
65165
65240
|
var EMPTY_ACCOUNT_CLAUSE = "";
|
|
65166
65241
|
var SLACK_THREAD_CLAUSE = " --thread-id {{thread_ts}} \\\n";
|
|
@@ -66706,6 +66781,20 @@ function renderSlackTranscript(params) {
|
|
|
66706
66781
|
${body}
|
|
66707
66782
|
\`\`\``;
|
|
66708
66783
|
}
|
|
66784
|
+
function renderCompactChannelTranscript(params) {
|
|
66785
|
+
const body = escapeCodeFence(truncateTail(params.snapshot || "(no tmux output yet)", params.maxChars));
|
|
66786
|
+
const fullCommand = params.fullCommand ?? "/transcript full";
|
|
66787
|
+
return [
|
|
66788
|
+
"Transcript",
|
|
66789
|
+
"",
|
|
66790
|
+
"Recent session snapshot:",
|
|
66791
|
+
"```",
|
|
66792
|
+
body,
|
|
66793
|
+
"```",
|
|
66794
|
+
`Use \`${fullCommand}\` if you want the longer pane snapshot.`
|
|
66795
|
+
].join(`
|
|
66796
|
+
`);
|
|
66797
|
+
}
|
|
66709
66798
|
var renderSlackSnapshot = renderSlackTranscript;
|
|
66710
66799
|
var renderChannelSnapshot = renderSlackSnapshot;
|
|
66711
66800
|
function resolveDetachedInteractionNote(params) {
|
|
@@ -66742,12 +66831,16 @@ var PASTE_SETTLE_POLL_INTERVAL_MS = 40;
|
|
|
66742
66831
|
var PASTE_SETTLE_QUIET_WINDOW_MS = 60;
|
|
66743
66832
|
var PASTE_SETTLE_MULTILINE_MAX_WAIT_MS = 800;
|
|
66744
66833
|
var PASTE_SETTLE_SINGLE_LINE_MAX_WAIT_MS = 80;
|
|
66834
|
+
var PASTE_CONFIRM_MAX_ATTEMPTS = 3;
|
|
66745
66835
|
var PASTE_CAPTURE_REVALIDATE_POLL_INTERVAL_MS = 40;
|
|
66746
66836
|
var PASTE_CAPTURE_REVALIDATE_MAX_WAIT_MS = 160;
|
|
66747
66837
|
var SUBMIT_CONFIRM_POLL_INTERVAL_MS = 40;
|
|
66748
66838
|
var SUBMIT_CONFIRM_MAX_WAIT_MS = 160;
|
|
66749
66839
|
var SUBMIT_SNAPSHOT_CONFIRM_POLL_INTERVAL_MS = 40;
|
|
66750
66840
|
var SUBMIT_SNAPSHOT_CONFIRM_MAX_WAIT_MS = 320;
|
|
66841
|
+
var POST_STATUS_SETTLE_POLL_INTERVAL_MS = 40;
|
|
66842
|
+
var POST_STATUS_SETTLE_QUIET_WINDOW_MS = 80;
|
|
66843
|
+
var POST_STATUS_SETTLE_MAX_WAIT_MS = 240;
|
|
66751
66844
|
var TMUX_MISSING_TARGET_PATTERN = /(?:no current target|can't find pane|can't find window)/i;
|
|
66752
66845
|
var TMUX_MISSING_SESSION_PATTERN = /(?:can't find session:|no server running on )/i;
|
|
66753
66846
|
var TMUX_SERVER_UNAVAILABLE_PATTERN = /(?:No such file or directory|error connecting to|failed to connect to server)/i;
|
|
@@ -66760,38 +66853,44 @@ class TmuxBootstrapSessionLostError extends Error {
|
|
|
66760
66853
|
this.name = "TmuxBootstrapSessionLostError";
|
|
66761
66854
|
}
|
|
66762
66855
|
}
|
|
66856
|
+
|
|
66857
|
+
class TmuxPasteUnconfirmedError extends Error {
|
|
66858
|
+
attempts;
|
|
66859
|
+
constructor(attempts) {
|
|
66860
|
+
super(`tmux paste was not confirmed after ${attempts} delivery attempts. clisbot did not send Enter because the prompt was not truthfully visible in the pane.`);
|
|
66861
|
+
this.attempts = attempts;
|
|
66862
|
+
this.name = "TmuxPasteUnconfirmedError";
|
|
66863
|
+
}
|
|
66864
|
+
}
|
|
66865
|
+
|
|
66866
|
+
class TmuxSubmitUnconfirmedError extends Error {
|
|
66867
|
+
constructor() {
|
|
66868
|
+
super("tmux submit was not confirmed after Enter. The pane state did not change, so clisbot did not treat the prompt as truthfully submitted.");
|
|
66869
|
+
this.name = "TmuxSubmitUnconfirmedError";
|
|
66870
|
+
}
|
|
66871
|
+
}
|
|
66763
66872
|
async function submitTmuxSessionInput(params) {
|
|
66764
66873
|
const prePasteState = await params.tmux.getPaneState(params.sessionName);
|
|
66765
66874
|
const captureLines = estimatePasteCaptureLines(params.text);
|
|
66766
66875
|
const prePasteSnapshot = normalizePaneText(await params.tmux.capturePane(params.sessionName, captureLines));
|
|
66767
|
-
await
|
|
66768
|
-
const pasteSettlement = await waitForPanePasteSettlement({
|
|
66876
|
+
const pasteDelivery = await deliverTmuxPasteWithConfirmation({
|
|
66769
66877
|
tmux: params.tmux,
|
|
66770
66878
|
sessionName: params.sessionName,
|
|
66771
|
-
baseline: prePasteState,
|
|
66772
66879
|
text: params.text,
|
|
66773
|
-
|
|
66880
|
+
baselineState: prePasteState,
|
|
66881
|
+
baselineSnapshot: prePasteSnapshot,
|
|
66882
|
+
captureLines,
|
|
66883
|
+
promptSubmitDelayMs: params.promptSubmitDelayMs,
|
|
66884
|
+
timingContext: params.timingContext
|
|
66774
66885
|
});
|
|
66775
|
-
|
|
66776
|
-
|
|
66777
|
-
logLatencyDebug("tmux-paste-retry", params.timingContext, {
|
|
66778
|
-
sessionName: params.sessionName
|
|
66779
|
-
});
|
|
66780
|
-
const snapshotConfirmed = await waitForPanePasteSnapshotConfirmation({
|
|
66781
|
-
tmux: params.tmux,
|
|
66886
|
+
if (!pasteDelivery.confirmed) {
|
|
66887
|
+
logLatencyDebug("tmux-paste-unconfirmed", params.timingContext, {
|
|
66782
66888
|
sessionName: params.sessionName,
|
|
66783
|
-
|
|
66784
|
-
captureLines
|
|
66889
|
+
attempts: pasteDelivery.attempts
|
|
66785
66890
|
});
|
|
66786
|
-
|
|
66787
|
-
logLatencyDebug("tmux-paste-unconfirmed", params.timingContext, {
|
|
66788
|
-
sessionName: params.sessionName
|
|
66789
|
-
});
|
|
66790
|
-
preSubmitState = prePasteState;
|
|
66791
|
-
} else {
|
|
66792
|
-
preSubmitState = await params.tmux.getPaneState(params.sessionName);
|
|
66793
|
-
}
|
|
66891
|
+
throw new TmuxPasteUnconfirmedError(pasteDelivery.attempts);
|
|
66794
66892
|
}
|
|
66893
|
+
const preSubmitState = pasteDelivery.state;
|
|
66795
66894
|
await params.tmux.sendKey(params.sessionName, "Enter");
|
|
66796
66895
|
if (await waitForPaneSubmitConfirmation({
|
|
66797
66896
|
tmux: params.tmux,
|
|
@@ -66815,13 +66914,10 @@ async function submitTmuxSessionInput(params) {
|
|
|
66815
66914
|
})) {
|
|
66816
66915
|
return;
|
|
66817
66916
|
}
|
|
66818
|
-
if (!pasteSettlement.visible) {
|
|
66819
|
-
throw new Error("tmux paste was not confirmed before Enter, and submission still could not be confirmed after Enter. clisbot did not treat the prompt as truthfully delivered.");
|
|
66820
|
-
}
|
|
66821
66917
|
logLatencyDebug("tmux-submit-unconfirmed", params.timingContext, {
|
|
66822
66918
|
sessionName: params.sessionName
|
|
66823
66919
|
});
|
|
66824
|
-
throw new
|
|
66920
|
+
throw new TmuxSubmitUnconfirmedError;
|
|
66825
66921
|
}
|
|
66826
66922
|
async function captureTmuxSessionIdentity(params) {
|
|
66827
66923
|
await submitTmuxSessionInput({
|
|
@@ -66864,6 +66960,14 @@ async function captureTmuxSessionIdentity(params) {
|
|
|
66864
66960
|
}
|
|
66865
66961
|
const sessionId = extractSessionId(snapshot, params.pattern);
|
|
66866
66962
|
if (sessionId) {
|
|
66963
|
+
await waitForTmuxPaneSettle({
|
|
66964
|
+
tmux: params.tmux,
|
|
66965
|
+
sessionName: params.sessionName,
|
|
66966
|
+
captureLines: params.captureLines,
|
|
66967
|
+
pollIntervalMs: POST_STATUS_SETTLE_POLL_INTERVAL_MS,
|
|
66968
|
+
quietWindowMs: POST_STATUS_SETTLE_QUIET_WINDOW_MS,
|
|
66969
|
+
maxWaitMs: POST_STATUS_SETTLE_MAX_WAIT_MS
|
|
66970
|
+
});
|
|
66867
66971
|
return sessionId;
|
|
66868
66972
|
}
|
|
66869
66973
|
}
|
|
@@ -67018,6 +67122,49 @@ async function waitForPaneSubmitSnapshotConfirmation(params) {
|
|
|
67018
67122
|
await sleep(Math.min(SUBMIT_SNAPSHOT_CONFIRM_POLL_INTERVAL_MS, remainingMs));
|
|
67019
67123
|
}
|
|
67020
67124
|
}
|
|
67125
|
+
async function deliverTmuxPasteWithConfirmation(params) {
|
|
67126
|
+
for (let attempt = 1;attempt <= PASTE_CONFIRM_MAX_ATTEMPTS; attempt += 1) {
|
|
67127
|
+
if (attempt > 1) {
|
|
67128
|
+
logLatencyDebug("tmux-paste-retry", params.timingContext, {
|
|
67129
|
+
sessionName: params.sessionName,
|
|
67130
|
+
attempt
|
|
67131
|
+
});
|
|
67132
|
+
}
|
|
67133
|
+
await params.tmux.sendLiteral(params.sessionName, params.text);
|
|
67134
|
+
const pasteSettlement = await waitForPanePasteSettlement({
|
|
67135
|
+
tmux: params.tmux,
|
|
67136
|
+
sessionName: params.sessionName,
|
|
67137
|
+
baseline: params.baselineState,
|
|
67138
|
+
text: params.text,
|
|
67139
|
+
minDelayMs: params.promptSubmitDelayMs
|
|
67140
|
+
});
|
|
67141
|
+
if (pasteSettlement.visible) {
|
|
67142
|
+
return {
|
|
67143
|
+
confirmed: true,
|
|
67144
|
+
state: pasteSettlement.state,
|
|
67145
|
+
attempts: attempt
|
|
67146
|
+
};
|
|
67147
|
+
}
|
|
67148
|
+
const snapshotConfirmed = await waitForPanePasteSnapshotConfirmation({
|
|
67149
|
+
tmux: params.tmux,
|
|
67150
|
+
sessionName: params.sessionName,
|
|
67151
|
+
baselineSnapshot: params.baselineSnapshot,
|
|
67152
|
+
captureLines: params.captureLines
|
|
67153
|
+
});
|
|
67154
|
+
if (snapshotConfirmed) {
|
|
67155
|
+
return {
|
|
67156
|
+
confirmed: true,
|
|
67157
|
+
state: await params.tmux.getPaneState(params.sessionName),
|
|
67158
|
+
attempts: attempt
|
|
67159
|
+
};
|
|
67160
|
+
}
|
|
67161
|
+
}
|
|
67162
|
+
return {
|
|
67163
|
+
confirmed: false,
|
|
67164
|
+
state: params.baselineState,
|
|
67165
|
+
attempts: PASTE_CONFIRM_MAX_ATTEMPTS
|
|
67166
|
+
};
|
|
67167
|
+
}
|
|
67021
67168
|
async function waitForPanePasteSettlement(params) {
|
|
67022
67169
|
await sleep(params.minDelayMs);
|
|
67023
67170
|
let currentState = await params.tmux.getPaneState(params.sessionName);
|
|
@@ -67063,6 +67210,41 @@ async function waitForPanePasteSnapshotConfirmation(params) {
|
|
|
67063
67210
|
await sleep(Math.min(PASTE_CAPTURE_REVALIDATE_POLL_INTERVAL_MS, remainingMs));
|
|
67064
67211
|
}
|
|
67065
67212
|
}
|
|
67213
|
+
async function waitForTmuxPaneSettle(params) {
|
|
67214
|
+
let previousSnapshot = "";
|
|
67215
|
+
let previousState = null;
|
|
67216
|
+
let lastChangeAt = Date.now();
|
|
67217
|
+
const deadline = Date.now() + params.maxWaitMs;
|
|
67218
|
+
while (true) {
|
|
67219
|
+
let snapshot = "";
|
|
67220
|
+
let state;
|
|
67221
|
+
try {
|
|
67222
|
+
snapshot = normalizePaneText(await params.tmux.capturePane(params.sessionName, params.captureLines));
|
|
67223
|
+
state = await params.tmux.getPaneState(params.sessionName);
|
|
67224
|
+
} catch (error) {
|
|
67225
|
+
if (isRetryableBootstrapTargetError(error)) {
|
|
67226
|
+
if (Date.now() >= deadline) {
|
|
67227
|
+
return;
|
|
67228
|
+
}
|
|
67229
|
+
await sleep(params.pollIntervalMs);
|
|
67230
|
+
continue;
|
|
67231
|
+
}
|
|
67232
|
+
if (isBootstrapSessionGoneError(error)) {
|
|
67233
|
+
throw buildBootstrapSessionLostError(params.sessionName, error);
|
|
67234
|
+
}
|
|
67235
|
+
throw error;
|
|
67236
|
+
}
|
|
67237
|
+
if (snapshot !== previousSnapshot || !previousState || !arePaneStatesEqual(previousState, state)) {
|
|
67238
|
+
previousSnapshot = snapshot;
|
|
67239
|
+
previousState = state;
|
|
67240
|
+
lastChangeAt = Date.now();
|
|
67241
|
+
}
|
|
67242
|
+
if (Date.now() - lastChangeAt >= params.quietWindowMs || Date.now() >= deadline) {
|
|
67243
|
+
return;
|
|
67244
|
+
}
|
|
67245
|
+
await sleep(params.pollIntervalMs);
|
|
67246
|
+
}
|
|
67247
|
+
}
|
|
67066
67248
|
function estimatePasteCaptureLines(text) {
|
|
67067
67249
|
return Math.max(40, Math.min(160, text.split(`
|
|
67068
67250
|
`).length + 24));
|
|
@@ -67284,6 +67466,9 @@ function isBootstrapSessionLostError(error) {
|
|
|
67284
67466
|
function isRecoverableStartupSessionLoss(error) {
|
|
67285
67467
|
return isMissingTmuxSessionError(error) || isTmuxServerUnavailableError(error) || isBootstrapSessionLostError(error);
|
|
67286
67468
|
}
|
|
67469
|
+
function isFreshStartRetryablePromptDeliveryError(error) {
|
|
67470
|
+
return error instanceof TmuxPasteUnconfirmedError;
|
|
67471
|
+
}
|
|
67287
67472
|
|
|
67288
67473
|
class RunnerService {
|
|
67289
67474
|
loadedConfig;
|
|
@@ -67398,7 +67583,7 @@ class RunnerService {
|
|
|
67398
67583
|
});
|
|
67399
67584
|
}
|
|
67400
67585
|
async retryAfterStartupFault(target, resolved, error, remainingFreshRetries) {
|
|
67401
|
-
if (!isRecoverableStartupSessionLoss(error)) {
|
|
67586
|
+
if (!isRecoverableStartupSessionLoss(error) && !isFreshStartRetryablePromptDeliveryError(error)) {
|
|
67402
67587
|
return null;
|
|
67403
67588
|
}
|
|
67404
67589
|
return this.retryFreshStartWithClearedSessionId(target, resolved, remainingFreshRetries);
|
|
@@ -67640,6 +67825,9 @@ class RunnerService {
|
|
|
67640
67825
|
canRecoverMidRun(error) {
|
|
67641
67826
|
return isRecoverableStartupSessionLoss(error);
|
|
67642
67827
|
}
|
|
67828
|
+
canRetryPromptAfterFreshStart(error) {
|
|
67829
|
+
return isFreshStartRetryablePromptDeliveryError(error);
|
|
67830
|
+
}
|
|
67643
67831
|
async reopenRunContext(target, timingContext) {
|
|
67644
67832
|
const resolved = this.resolveTarget(target);
|
|
67645
67833
|
const existing = await this.sessionState.getEntry(resolved.sessionKey);
|
|
@@ -67654,7 +67842,10 @@ class RunnerService {
|
|
|
67654
67842
|
return;
|
|
67655
67843
|
});
|
|
67656
67844
|
await this.sessionState.clearSessionIdEntry(resolved, { runnerCommand: resolved.runner.command });
|
|
67657
|
-
return this.
|
|
67845
|
+
return this.ensureRunnerReady(target, {
|
|
67846
|
+
allowFreshRetryBeforePrompt: false,
|
|
67847
|
+
timingContext
|
|
67848
|
+
});
|
|
67658
67849
|
}
|
|
67659
67850
|
async captureTranscript(target) {
|
|
67660
67851
|
const resolved = this.resolveTarget(target);
|
|
@@ -68295,6 +68486,55 @@ class SessionService {
|
|
|
68295
68486
|
}
|
|
68296
68487
|
this.activeRuns.delete(run.resolved.sessionKey);
|
|
68297
68488
|
}
|
|
68489
|
+
async recoverPromptDeliveryFailure(sessionKey, params, error) {
|
|
68490
|
+
if (!params.prompt || params.promptRetryAttempt || !this.runnerSessions.canRetryPromptAfterFreshStart(error)) {
|
|
68491
|
+
return false;
|
|
68492
|
+
}
|
|
68493
|
+
const run = this.getRun(sessionKey, params.runId);
|
|
68494
|
+
if (!run) {
|
|
68495
|
+
return true;
|
|
68496
|
+
}
|
|
68497
|
+
const target = {
|
|
68498
|
+
agentId: run.resolved.agentId,
|
|
68499
|
+
sessionKey: run.resolved.sessionKey
|
|
68500
|
+
};
|
|
68501
|
+
await this.notifyRecoveryStep(run, "Prompt delivery did not settle truthfully in the current runner session. clisbot is opening one fresh runner session and retrying the prompt once.");
|
|
68502
|
+
try {
|
|
68503
|
+
const fresh = await this.runnerSessions.startFreshSession(target, params.timingContext);
|
|
68504
|
+
const currentRun = this.getRun(sessionKey, params.runId);
|
|
68505
|
+
if (!currentRun) {
|
|
68506
|
+
return true;
|
|
68507
|
+
}
|
|
68508
|
+
const restartedAt = Date.now();
|
|
68509
|
+
currentRun.resolved = fresh.resolved;
|
|
68510
|
+
currentRun.steeringReady = false;
|
|
68511
|
+
currentRun.startedAt = restartedAt;
|
|
68512
|
+
currentRun.latestUpdate = this.createRunUpdate({
|
|
68513
|
+
resolved: currentRun.resolved,
|
|
68514
|
+
status: currentRun.latestUpdate.status === "detached" ? "detached" : "running",
|
|
68515
|
+
snapshot: "",
|
|
68516
|
+
fullSnapshot: fresh.initialSnapshot,
|
|
68517
|
+
initialSnapshot: fresh.initialSnapshot,
|
|
68518
|
+
note: "Retrying the prompt in one fresh runner session.",
|
|
68519
|
+
forceVisible: true
|
|
68520
|
+
});
|
|
68521
|
+
await this.sessionState.setSessionRuntime(currentRun.resolved, {
|
|
68522
|
+
state: "running",
|
|
68523
|
+
startedAt: restartedAt
|
|
68524
|
+
});
|
|
68525
|
+
await this.notifyRunObservers(currentRun, currentRun.latestUpdate);
|
|
68526
|
+
this.startRunMonitor(sessionKey, {
|
|
68527
|
+
...params,
|
|
68528
|
+
promptRetryAttempt: 1,
|
|
68529
|
+
initialSnapshot: fresh.initialSnapshot,
|
|
68530
|
+
startedAt: restartedAt
|
|
68531
|
+
});
|
|
68532
|
+
return true;
|
|
68533
|
+
} catch (freshError) {
|
|
68534
|
+
await this.failActiveRun(sessionKey, run.runId, await this.runnerSessions.mapRunError(freshError, run.resolved.sessionName, run.latestUpdate.fullSnapshot));
|
|
68535
|
+
return true;
|
|
68536
|
+
}
|
|
68537
|
+
}
|
|
68298
68538
|
async recoverLostMidRun(sessionKey, params, error) {
|
|
68299
68539
|
if (!this.runnerSessions.canRecoverMidRun(error)) {
|
|
68300
68540
|
return false;
|
|
@@ -68450,6 +68690,16 @@ class SessionService {
|
|
|
68450
68690
|
}
|
|
68451
68691
|
});
|
|
68452
68692
|
} catch (error) {
|
|
68693
|
+
if (await this.recoverPromptDeliveryFailure(sessionKey, {
|
|
68694
|
+
runId: params.runId,
|
|
68695
|
+
prompt: params.prompt,
|
|
68696
|
+
startedAt: params.startedAt,
|
|
68697
|
+
detachedAlready: params.detachedAlready,
|
|
68698
|
+
timingContext: params.timingContext,
|
|
68699
|
+
promptRetryAttempt: params.promptRetryAttempt
|
|
68700
|
+
}, error)) {
|
|
68701
|
+
return;
|
|
68702
|
+
}
|
|
68453
68703
|
if (await this.recoverLostMidRun(sessionKey, {
|
|
68454
68704
|
runId: params.runId,
|
|
68455
68705
|
timingContext: params.timingContext,
|
|
@@ -69331,43 +69581,188 @@ async function setConversationStreaming(params) {
|
|
|
69331
69581
|
};
|
|
69332
69582
|
}
|
|
69333
69583
|
|
|
69334
|
-
// src/channels/
|
|
69335
|
-
|
|
69336
|
-
|
|
69337
|
-
var MESSAGE_TOOL_PREVIEW_SIGNAL_POLL_MS = 100;
|
|
69338
|
-
function renderSensitiveCommandDisabledMessage() {
|
|
69339
|
-
return [
|
|
69340
|
-
"Shell execution is not allowed for your current role on this agent.",
|
|
69341
|
-
"Ask an app or agent admin to grant `shellExecute` if this surface should allow `/bash`."
|
|
69342
|
-
].join(`
|
|
69343
|
-
`);
|
|
69344
|
-
}
|
|
69345
|
-
function renderTranscriptDisabledMessage() {
|
|
69346
|
-
return [
|
|
69347
|
-
"Transcript inspection is disabled for this route.",
|
|
69348
|
-
'Set `verbose: "minimal"` on the route or channel to allow `/transcript`.'
|
|
69349
|
-
].join(`
|
|
69350
|
-
`);
|
|
69351
|
-
}
|
|
69352
|
-
function renderStartupSteeringUnavailableMessage() {
|
|
69353
|
-
return [
|
|
69354
|
-
"The active run is still starting and cannot accept steering input yet.",
|
|
69355
|
-
"Send a normal follow-up message to keep it ordered behind the first prompt, or wait until startup finishes before using `/steer`."
|
|
69356
|
-
].join(`
|
|
69357
|
-
`);
|
|
69584
|
+
// src/channels/follow-up-mode-config.ts
|
|
69585
|
+
function getEditableConfigPath6() {
|
|
69586
|
+
return process.env.CLISBOT_CONFIG_PATH;
|
|
69358
69587
|
}
|
|
69359
|
-
function
|
|
69360
|
-
|
|
69361
|
-
|
|
69588
|
+
function getOrCreateFollowUp(source) {
|
|
69589
|
+
const existing = source.followUp;
|
|
69590
|
+
if (existing && typeof existing === "object" && !Array.isArray(existing)) {
|
|
69591
|
+
return existing;
|
|
69362
69592
|
}
|
|
69363
|
-
|
|
69593
|
+
const created = {};
|
|
69594
|
+
source.followUp = created;
|
|
69595
|
+
return created;
|
|
69364
69596
|
}
|
|
69365
|
-
function
|
|
69366
|
-
|
|
69367
|
-
|
|
69368
|
-
|
|
69369
|
-
|
|
69370
|
-
|
|
69597
|
+
function createTelegramRouteOverride2() {
|
|
69598
|
+
return {
|
|
69599
|
+
enabled: true,
|
|
69600
|
+
allowUsers: [],
|
|
69601
|
+
blockUsers: [],
|
|
69602
|
+
topics: {}
|
|
69603
|
+
};
|
|
69604
|
+
}
|
|
69605
|
+
function getOrCreateTelegramGroupRoute2(bot, chatId) {
|
|
69606
|
+
const existingGroup = bot.groups[chatId];
|
|
69607
|
+
if (existingGroup) {
|
|
69608
|
+
return existingGroup;
|
|
69609
|
+
}
|
|
69610
|
+
const createdGroup = createTelegramRouteOverride2();
|
|
69611
|
+
bot.groups[chatId] = createdGroup;
|
|
69612
|
+
return createdGroup;
|
|
69613
|
+
}
|
|
69614
|
+
function resolveSlackFollowUpModeTarget(config, params) {
|
|
69615
|
+
const botId = resolveSlackBotId(config.bots.slack, params.botId);
|
|
69616
|
+
const bot = getSlackBotRecord(config.bots.slack, botId);
|
|
69617
|
+
if (!bot) {
|
|
69618
|
+
throw new Error(`Unknown Slack bot: ${botId}`);
|
|
69619
|
+
}
|
|
69620
|
+
if (params.scope === "all") {
|
|
69621
|
+
return {
|
|
69622
|
+
get: () => bot.followUp?.mode,
|
|
69623
|
+
set: (value) => {
|
|
69624
|
+
getOrCreateFollowUp(bot).mode = value;
|
|
69625
|
+
},
|
|
69626
|
+
label: `slack bot ${botId}`
|
|
69627
|
+
};
|
|
69628
|
+
}
|
|
69629
|
+
if (params.identity.conversationKind === "dm") {
|
|
69630
|
+
const targetId = params.identity.senderId?.trim() || params.identity.channelId?.trim();
|
|
69631
|
+
if (!targetId) {
|
|
69632
|
+
throw new Error("Slack follow-up channel scope requires a senderId or channelId.");
|
|
69633
|
+
}
|
|
69634
|
+
const routeKey2 = `dm:${targetId}`;
|
|
69635
|
+
const existingRoute = resolveDirectMessageExactRoute(bot.directMessages, targetId) ?? (bot.directMessages[routeKey2] = createDirectMessageBehaviorOverride());
|
|
69636
|
+
return {
|
|
69637
|
+
get: () => existingRoute.followUp?.mode ?? bot.followUp?.mode,
|
|
69638
|
+
set: (value) => {
|
|
69639
|
+
getOrCreateFollowUp(existingRoute).mode = value;
|
|
69640
|
+
},
|
|
69641
|
+
label: `slack ${routeKey2}`
|
|
69642
|
+
};
|
|
69643
|
+
}
|
|
69644
|
+
const routeKind = params.identity.conversationKind === "group" ? "group" : "channel";
|
|
69645
|
+
const channelId = params.identity.channelId?.trim();
|
|
69646
|
+
if (!channelId) {
|
|
69647
|
+
throw new Error("Slack follow-up channel scope requires a channelId.");
|
|
69648
|
+
}
|
|
69649
|
+
const routeKey = `${routeKind}:${channelId}`;
|
|
69650
|
+
const route = bot.groups[routeKey];
|
|
69651
|
+
if (!route) {
|
|
69652
|
+
throw new Error(`Route not configured yet: slack ${routeKey}. Add the route first.`);
|
|
69653
|
+
}
|
|
69654
|
+
return {
|
|
69655
|
+
get: () => route.followUp?.mode ?? bot.followUp?.mode,
|
|
69656
|
+
set: (value) => {
|
|
69657
|
+
getOrCreateFollowUp(route).mode = value;
|
|
69658
|
+
},
|
|
69659
|
+
label: `slack ${routeKey}`
|
|
69660
|
+
};
|
|
69661
|
+
}
|
|
69662
|
+
function resolveTelegramFollowUpModeTarget(config, params) {
|
|
69663
|
+
const botId = resolveTelegramBotId(config.bots.telegram, params.botId);
|
|
69664
|
+
const bot = getTelegramBotRecord(config.bots.telegram, botId);
|
|
69665
|
+
if (!bot) {
|
|
69666
|
+
throw new Error(`Unknown Telegram bot: ${botId}`);
|
|
69667
|
+
}
|
|
69668
|
+
if (params.scope === "all") {
|
|
69669
|
+
return {
|
|
69670
|
+
get: () => bot.followUp?.mode,
|
|
69671
|
+
set: (value) => {
|
|
69672
|
+
getOrCreateFollowUp(bot).mode = value;
|
|
69673
|
+
},
|
|
69674
|
+
label: `telegram bot ${botId}`
|
|
69675
|
+
};
|
|
69676
|
+
}
|
|
69677
|
+
if (params.identity.conversationKind === "dm") {
|
|
69678
|
+
const targetId = params.identity.senderId?.trim() || params.identity.chatId?.trim();
|
|
69679
|
+
if (!targetId) {
|
|
69680
|
+
throw new Error("Telegram follow-up channel scope requires a senderId or chatId.");
|
|
69681
|
+
}
|
|
69682
|
+
const routeKey = `dm:${targetId}`;
|
|
69683
|
+
const existingRoute = resolveDirectMessageExactRoute(bot.directMessages, targetId) ?? (bot.directMessages[routeKey] = createDirectMessageBehaviorOverride());
|
|
69684
|
+
return {
|
|
69685
|
+
get: () => existingRoute.followUp?.mode ?? bot.followUp?.mode,
|
|
69686
|
+
set: (value) => {
|
|
69687
|
+
getOrCreateFollowUp(existingRoute).mode = value;
|
|
69688
|
+
},
|
|
69689
|
+
label: `telegram ${routeKey}`
|
|
69690
|
+
};
|
|
69691
|
+
}
|
|
69692
|
+
const chatId = params.identity.chatId?.trim();
|
|
69693
|
+
if (!chatId) {
|
|
69694
|
+
throw new Error("Telegram follow-up channel scope requires a chatId.");
|
|
69695
|
+
}
|
|
69696
|
+
const group = getOrCreateTelegramGroupRoute2(bot, chatId);
|
|
69697
|
+
return {
|
|
69698
|
+
get: () => group.followUp?.mode ?? bot.followUp?.mode,
|
|
69699
|
+
set: (value) => {
|
|
69700
|
+
getOrCreateFollowUp(group).mode = value;
|
|
69701
|
+
},
|
|
69702
|
+
label: `telegram group:${chatId}`
|
|
69703
|
+
};
|
|
69704
|
+
}
|
|
69705
|
+
function resolveConfiguredFollowUpModeTarget(config, params) {
|
|
69706
|
+
if (params.channel === "slack") {
|
|
69707
|
+
return resolveSlackFollowUpModeTarget(config, params);
|
|
69708
|
+
}
|
|
69709
|
+
return resolveTelegramFollowUpModeTarget(config, params);
|
|
69710
|
+
}
|
|
69711
|
+
async function setScopedConversationFollowUpMode(params) {
|
|
69712
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath6());
|
|
69713
|
+
const target = resolveConfiguredFollowUpModeTarget(config, {
|
|
69714
|
+
channel: params.identity.platform,
|
|
69715
|
+
botId: resolveChannelIdentityBotId(params.identity),
|
|
69716
|
+
scope: params.scope,
|
|
69717
|
+
identity: params.identity
|
|
69718
|
+
});
|
|
69719
|
+
target.set(params.mode);
|
|
69720
|
+
await writeEditableConfig(configPath, config);
|
|
69721
|
+
return {
|
|
69722
|
+
configPath,
|
|
69723
|
+
label: target.label,
|
|
69724
|
+
followUpMode: params.mode
|
|
69725
|
+
};
|
|
69726
|
+
}
|
|
69727
|
+
|
|
69728
|
+
// src/channels/interaction-processing.ts
|
|
69729
|
+
var MESSAGE_TOOL_FINAL_GRACE_WINDOW_MS = 3000;
|
|
69730
|
+
var MESSAGE_TOOL_FINAL_GRACE_POLL_MS = 100;
|
|
69731
|
+
var MESSAGE_TOOL_PREVIEW_SIGNAL_POLL_MS = 100;
|
|
69732
|
+
var TRANSCRIPT_PREVIEW_MAX_CHARS = 1200;
|
|
69733
|
+
function renderSensitiveCommandDisabledMessage() {
|
|
69734
|
+
return [
|
|
69735
|
+
"Shell execution is not allowed for your current role on this agent.",
|
|
69736
|
+
"Ask an app or agent admin to grant `shellExecute` if this surface should allow `/bash`."
|
|
69737
|
+
].join(`
|
|
69738
|
+
`);
|
|
69739
|
+
}
|
|
69740
|
+
function renderTranscriptDisabledMessage() {
|
|
69741
|
+
return [
|
|
69742
|
+
"Transcript inspection is disabled for this route.",
|
|
69743
|
+
'Set `verbose: "minimal"` on the route or channel to allow `/transcript`.'
|
|
69744
|
+
].join(`
|
|
69745
|
+
`);
|
|
69746
|
+
}
|
|
69747
|
+
function renderStartupSteeringUnavailableMessage() {
|
|
69748
|
+
return [
|
|
69749
|
+
"The active run is still starting and cannot accept steering input yet.",
|
|
69750
|
+
"Send a normal follow-up message to keep it ordered behind the first prompt, or wait until startup finishes before using `/steer`."
|
|
69751
|
+
].join(`
|
|
69752
|
+
`);
|
|
69753
|
+
}
|
|
69754
|
+
function renderPrincipalFormat(identity) {
|
|
69755
|
+
if (identity.platform === "slack") {
|
|
69756
|
+
return "slack:<nativeUserId>";
|
|
69757
|
+
}
|
|
69758
|
+
return "telegram:<nativeUserId>";
|
|
69759
|
+
}
|
|
69760
|
+
function renderPrincipalExample(identity) {
|
|
69761
|
+
if (identity.senderId) {
|
|
69762
|
+
return `${identity.platform}:${identity.senderId}`;
|
|
69763
|
+
}
|
|
69764
|
+
if (identity.platform === "slack") {
|
|
69765
|
+
return "slack:U123ABC456";
|
|
69371
69766
|
}
|
|
69372
69767
|
return "telegram:1276408333";
|
|
69373
69768
|
}
|
|
@@ -69439,7 +69834,7 @@ function renderRouteStatusMessage(params) {
|
|
|
69439
69834
|
lines.push(`- \`${loop.id}\` ${renderLoopStatusSchedule(loop)} remaining \`${loop.remainingRuns}\` nextRunAt \`${new Date(loop.nextRunAt).toISOString()}\``);
|
|
69440
69835
|
}
|
|
69441
69836
|
}
|
|
69442
|
-
lines.push("", "Useful commands:", "- `/help`", "- `/whoami`", "- `/status`", "- `/attach`, `/detach`, `/watch every <duration>`", "- `/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`");
|
|
69837
|
+
lines.push("", "Useful commands:", "- `/help`", "- `/whoami`", "- `/status`", "- `/attach`, `/detach`, `/watch every <duration>`", "- `/followup status`, `/mention`, `/mention channel`, `/mention all`", "- `/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`");
|
|
69443
69838
|
return lines.join(`
|
|
69444
69839
|
`);
|
|
69445
69840
|
}
|
|
@@ -69486,6 +69881,29 @@ function renderAdditionalMessageModeStatusMessage(params) {
|
|
|
69486
69881
|
return lines.join(`
|
|
69487
69882
|
`);
|
|
69488
69883
|
}
|
|
69884
|
+
function renderFollowUpModeUpdateMessage(params) {
|
|
69885
|
+
if (!params.persisted) {
|
|
69886
|
+
if (params.mode === "paused") {
|
|
69887
|
+
return "Follow-up paused for this conversation until the next explicit mention.";
|
|
69888
|
+
}
|
|
69889
|
+
return `Follow-up mode set to \`${params.mode}\` for this conversation.`;
|
|
69890
|
+
}
|
|
69891
|
+
const lines = [
|
|
69892
|
+
`Updated follow-up mode for \`${params.persisted.label}\`.`,
|
|
69893
|
+
`config.followUp.mode: \`${params.persisted.followUpMode}\``,
|
|
69894
|
+
`config: \`${params.persisted.configPath}\``,
|
|
69895
|
+
`currentConversation.overrideMode: \`${params.mode}\``,
|
|
69896
|
+
"The current conversation changes immediately.",
|
|
69897
|
+
"If config reload is enabled, the broader default should apply automatically shortly."
|
|
69898
|
+
];
|
|
69899
|
+
if (params.scope === "all") {
|
|
69900
|
+
lines.splice(4, 0, "This persists the bot-wide default for later routed conversations on this bot.");
|
|
69901
|
+
} else if (params.scope === "channel") {
|
|
69902
|
+
lines.splice(4, 0, "This persists the default for the current channel, group, or DM container.");
|
|
69903
|
+
}
|
|
69904
|
+
return lines.join(`
|
|
69905
|
+
`);
|
|
69906
|
+
}
|
|
69489
69907
|
function buildChannelObserverId(identity) {
|
|
69490
69908
|
return [
|
|
69491
69909
|
identity.platform,
|
|
@@ -70132,14 +70550,18 @@ async function processChannelInteraction(params) {
|
|
|
70132
70550
|
return interactionResult;
|
|
70133
70551
|
}
|
|
70134
70552
|
const transcript = await params.agentService.captureTranscript(params.sessionTarget);
|
|
70135
|
-
await params.postText(renderChannelSnapshot({
|
|
70553
|
+
await params.postText(slashCommand.mode === "full" ? renderChannelSnapshot({
|
|
70136
70554
|
agentId: transcript.agentId,
|
|
70137
70555
|
sessionName: transcript.sessionName,
|
|
70138
70556
|
workspacePath: transcript.workspacePath,
|
|
70139
70557
|
status: "completed",
|
|
70140
70558
|
snapshot: transcript.snapshot || "(no tmux output yet)",
|
|
70141
70559
|
maxChars: params.maxChars,
|
|
70142
|
-
note: "transcript command"
|
|
70560
|
+
note: "transcript command (full)"
|
|
70561
|
+
}) : renderCompactChannelTranscript({
|
|
70562
|
+
snapshot: transcript.snapshot || "(no tmux output yet)",
|
|
70563
|
+
maxChars: Math.min(params.maxChars, TRANSCRIPT_PREVIEW_MAX_CHARS),
|
|
70564
|
+
fullCommand: "/transcript full"
|
|
70143
70565
|
}));
|
|
70144
70566
|
return interactionResult;
|
|
70145
70567
|
}
|
|
@@ -70202,8 +70624,25 @@ async function processChannelInteraction(params) {
|
|
|
70202
70624
|
await params.agentService.resetConversationFollowUpMode(params.sessionTarget);
|
|
70203
70625
|
await params.postText("Follow-up policy reset to route defaults for this conversation.");
|
|
70204
70626
|
} else if (slashCommand.mode) {
|
|
70205
|
-
|
|
70206
|
-
|
|
70627
|
+
if (slashCommand.scope === "channel" || slashCommand.scope === "all") {
|
|
70628
|
+
await params.agentService.setConversationFollowUpMode(params.sessionTarget, slashCommand.mode);
|
|
70629
|
+
const persisted = await setScopedConversationFollowUpMode({
|
|
70630
|
+
identity: params.identity,
|
|
70631
|
+
scope: slashCommand.scope,
|
|
70632
|
+
mode: slashCommand.mode
|
|
70633
|
+
});
|
|
70634
|
+
await params.postText(renderFollowUpModeUpdateMessage({
|
|
70635
|
+
scope: slashCommand.scope,
|
|
70636
|
+
mode: slashCommand.mode,
|
|
70637
|
+
persisted
|
|
70638
|
+
}));
|
|
70639
|
+
} else {
|
|
70640
|
+
await params.agentService.setConversationFollowUpMode(params.sessionTarget, slashCommand.mode);
|
|
70641
|
+
await params.postText(renderFollowUpModeUpdateMessage({
|
|
70642
|
+
scope: "conversation",
|
|
70643
|
+
mode: slashCommand.mode
|
|
70644
|
+
}));
|
|
70645
|
+
}
|
|
70207
70646
|
}
|
|
70208
70647
|
await params.agentService.recordConversationReply(params.sessionTarget);
|
|
70209
70648
|
return interactionResult;
|
|
@@ -71518,7 +71957,7 @@ function renderSlackRouteChoiceMessage(params) {
|
|
|
71518
71957
|
`- ${renderCliCommand(`routes add --channel slack channel:${params.channelId} --bot default`, { inline: true })}`,
|
|
71519
71958
|
`- ${renderCliCommand(`routes set-agent --channel slack channel:${params.channelId} --bot default --agent <id>`, { inline: true })}`,
|
|
71520
71959
|
"",
|
|
71521
|
-
`After that, ${botReference} and send \`\\start
|
|
71960
|
+
`After that, ${botReference} and send \`\\start\`, \`\\status\`, or \`\\mention\` here.`
|
|
71522
71961
|
].join(`
|
|
71523
71962
|
`);
|
|
71524
71963
|
}
|
|
@@ -71526,7 +71965,7 @@ function renderSlackMentionRequiredMessage(botLabel) {
|
|
|
71526
71965
|
const botReference = botLabel?.trim() ? `mention this bot (${botLabel.trim()})` : "mention this bot";
|
|
71527
71966
|
return [
|
|
71528
71967
|
"clisbot: this Slack channel requires a bot mention for new commands.",
|
|
71529
|
-
`Try ${botReference} and send \`\\
|
|
71968
|
+
`Try ${botReference} and send \`\\status\` or \`\\mention\` here.`,
|
|
71530
71969
|
"After the bot replies in a thread, normal follow-up messages there can continue according to the follow-up policy."
|
|
71531
71970
|
].join(`
|
|
71532
71971
|
`);
|
|
@@ -74302,7 +74741,7 @@ function renderTelegramRouteChoiceMessage(params) {
|
|
|
74302
74741
|
if (params.includeConfigPath) {
|
|
74303
74742
|
lines.push("", topicId != null ? `Config path: \`bots.telegram.default.groups."${chatId}".topics."${topicId}"\`` : `Config path: \`bots.telegram.default.groups."${chatId}"\``);
|
|
74304
74743
|
} else {
|
|
74305
|
-
lines.push("", "After that, routed commands such as `/status`, `/stop`, `/nudge`, `/followup`, and `/bash` will work here.");
|
|
74744
|
+
lines.push("", "After that, routed commands such as `/status`, `/mention`, `/stop`, `/nudge`, `/followup`, and `/bash` will work here.");
|
|
74306
74745
|
}
|
|
74307
74746
|
return lines.join(`
|
|
74308
74747
|
`);
|
|
@@ -74368,6 +74807,7 @@ var TELEGRAM_FULL_COMMANDS = [
|
|
|
74368
74807
|
{ command: "stop", description: "Interrupt current run" },
|
|
74369
74808
|
{ command: "nudge", description: "Send one extra Enter to the session" },
|
|
74370
74809
|
{ command: "followup", description: "Show or change follow-up mode" },
|
|
74810
|
+
{ command: "mention", description: "Require explicit mention for later turns" },
|
|
74371
74811
|
{ command: "pause", description: "Pause passive follow-up for this conversation" },
|
|
74372
74812
|
{ command: "resume", description: "Restore route follow-up defaults for this conversation" },
|
|
74373
74813
|
{ command: "streaming", description: "Show or change streaming mode" },
|
|
@@ -74379,6 +74819,15 @@ var TELEGRAM_FULL_COMMANDS = [
|
|
|
74379
74819
|
{ command: "bash", description: "Run bash in the agent workspace" }
|
|
74380
74820
|
];
|
|
74381
74821
|
var TELEGRAM_STARTUP_CONFLICT_MAX_WAIT_MS = 6000;
|
|
74822
|
+
var TELEGRAM_POLLING_CONFLICT_BACKOFF_MAX_DELAY_MS = 30000;
|
|
74823
|
+
var TELEGRAM_POLLING_CONFLICT_SLEEP_SLICE_MS = 250;
|
|
74824
|
+
var TELEGRAM_POLLING_CONFLICT_OWNER_ALERT_DELAY_MS = 60000;
|
|
74825
|
+
var TELEGRAM_POLLING_CONFLICT_OWNER_ALERT_REPEAT_MS = 15 * 60000;
|
|
74826
|
+
function computeTelegramPollingConflictBackoffDelayMs(baseDelayMs, attempt) {
|
|
74827
|
+
const safeBaseDelayMs = Math.max(1, baseDelayMs);
|
|
74828
|
+
const boundedAttempt = Math.max(1, attempt);
|
|
74829
|
+
return Math.min(safeBaseDelayMs * 2 ** (boundedAttempt - 1), TELEGRAM_POLLING_CONFLICT_BACKOFF_MAX_DELAY_MS);
|
|
74830
|
+
}
|
|
74382
74831
|
function renderTelegramUnroutedRouteMessage(params) {
|
|
74383
74832
|
const lines = params.mode === "whoami" ? [
|
|
74384
74833
|
"Who am I",
|
|
@@ -74460,6 +74909,8 @@ class TelegramPollingService {
|
|
|
74460
74909
|
nextUpdateId;
|
|
74461
74910
|
loopPromise;
|
|
74462
74911
|
activePollController;
|
|
74912
|
+
pollingConflictActive = false;
|
|
74913
|
+
pollingConflictAttempt = 0;
|
|
74463
74914
|
inFlightUpdates = new Set;
|
|
74464
74915
|
processingIndicators = new ConversationProcessingIndicatorCoordinator;
|
|
74465
74916
|
constructor(loadedConfig, agentService, processedEventsStore, activityStore, botId = "default", botCredentials, reportLifecycle) {
|
|
@@ -74509,7 +74960,13 @@ class TelegramPollingService {
|
|
|
74509
74960
|
this.botUserId = me.id;
|
|
74510
74961
|
this.botUsername = me.username ?? "";
|
|
74511
74962
|
console.log(`telegram bot @${this.botUsername || this.botUserId} (${this.botId})`);
|
|
74512
|
-
|
|
74963
|
+
try {
|
|
74964
|
+
await this.initializeOffset();
|
|
74965
|
+
} catch (error) {
|
|
74966
|
+
if (!isTelegramPollingConflict(error)) {
|
|
74967
|
+
throw error;
|
|
74968
|
+
}
|
|
74969
|
+
}
|
|
74513
74970
|
await this.registerCommands();
|
|
74514
74971
|
this.running = true;
|
|
74515
74972
|
this.loopPromise = this.pollLoop();
|
|
@@ -74549,6 +75006,7 @@ class TelegramPollingService {
|
|
|
74549
75006
|
timeoutMs: (telegramConfig.polling.timeoutSeconds + 5) * 1000
|
|
74550
75007
|
});
|
|
74551
75008
|
this.activePollController = undefined;
|
|
75009
|
+
await this.recoverFromPollingConflictIfNeeded();
|
|
74552
75010
|
const dispatched = dispatchTelegramUpdates({
|
|
74553
75011
|
updates,
|
|
74554
75012
|
handleUpdate: (update) => this.handleUpdate(update),
|
|
@@ -74568,24 +75026,54 @@ class TelegramPollingService {
|
|
|
74568
75026
|
return;
|
|
74569
75027
|
}
|
|
74570
75028
|
if (isTelegramPollingConflict(error)) {
|
|
74571
|
-
this.
|
|
74572
|
-
|
|
74573
|
-
connection: "failed",
|
|
74574
|
-
summary: "Telegram polling stopped because another instance is already using this bot token.",
|
|
74575
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
74576
|
-
actions: [
|
|
74577
|
-
"stop the other Telegram poller that is using the same bot token",
|
|
74578
|
-
`run ${renderCliCommand("start", { inline: true })} again after the token is no longer in use elsewhere`
|
|
74579
|
-
]
|
|
74580
|
-
});
|
|
74581
|
-
console.error("telegram polling stopped: another bot instance is already calling getUpdates for this token");
|
|
74582
|
-
return;
|
|
75029
|
+
await this.handlePollingConflict(error, telegramConfig.polling.retryDelayMs);
|
|
75030
|
+
continue;
|
|
74583
75031
|
}
|
|
74584
75032
|
console.error("telegram polling error", error);
|
|
74585
75033
|
await sleep(telegramConfig.polling.retryDelayMs);
|
|
74586
75034
|
}
|
|
74587
75035
|
}
|
|
74588
75036
|
}
|
|
75037
|
+
async handlePollingConflict(error, retryDelayMs) {
|
|
75038
|
+
this.pollingConflictAttempt += 1;
|
|
75039
|
+
const nextDelayMs = computeTelegramPollingConflictBackoffDelayMs(retryDelayMs, this.pollingConflictAttempt);
|
|
75040
|
+
if (!this.pollingConflictActive) {
|
|
75041
|
+
this.pollingConflictActive = true;
|
|
75042
|
+
await this.reportLifecycle?.({
|
|
75043
|
+
connection: "failed",
|
|
75044
|
+
summary: "Telegram polling is temporarily blocked because another poller is already using this bot token.",
|
|
75045
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
75046
|
+
actions: [
|
|
75047
|
+
"stop the other Telegram poller that is using the same bot token if it is unintended",
|
|
75048
|
+
"clisbot will keep retrying automatically with backoff until Telegram polling can recover"
|
|
75049
|
+
],
|
|
75050
|
+
ownerAlertAfterMs: TELEGRAM_POLLING_CONFLICT_OWNER_ALERT_DELAY_MS,
|
|
75051
|
+
ownerAlertRepeatMs: TELEGRAM_POLLING_CONFLICT_OWNER_ALERT_REPEAT_MS
|
|
75052
|
+
});
|
|
75053
|
+
console.error("telegram polling blocked: another bot instance is already calling getUpdates for this token; retrying with backoff");
|
|
75054
|
+
}
|
|
75055
|
+
await this.waitForPollingConflictRetryDelay(nextDelayMs);
|
|
75056
|
+
}
|
|
75057
|
+
async recoverFromPollingConflictIfNeeded() {
|
|
75058
|
+
if (!this.pollingConflictActive) {
|
|
75059
|
+
return;
|
|
75060
|
+
}
|
|
75061
|
+
this.pollingConflictActive = false;
|
|
75062
|
+
this.pollingConflictAttempt = 0;
|
|
75063
|
+
await this.reportLifecycle?.({
|
|
75064
|
+
connection: "active",
|
|
75065
|
+
detail: "Telegram polling recovered after a polling-conflict retry."
|
|
75066
|
+
});
|
|
75067
|
+
console.log("telegram polling recovered after polling conflict");
|
|
75068
|
+
}
|
|
75069
|
+
async waitForPollingConflictRetryDelay(delayMs) {
|
|
75070
|
+
let remainingMs = Math.max(0, delayMs);
|
|
75071
|
+
while (this.running && remainingMs > 0) {
|
|
75072
|
+
const sliceMs = Math.min(remainingMs, TELEGRAM_POLLING_CONFLICT_SLEEP_SLICE_MS);
|
|
75073
|
+
await sleep(sliceMs);
|
|
75074
|
+
remainingMs -= sliceMs;
|
|
75075
|
+
}
|
|
75076
|
+
}
|
|
74589
75077
|
trackInFlightUpdate(task) {
|
|
74590
75078
|
this.inFlightUpdates.add(task);
|
|
74591
75079
|
task.finally(() => {
|
|
@@ -75449,97 +75937,7 @@ function installRuntimeConsoleTimestamps() {
|
|
|
75449
75937
|
};
|
|
75450
75938
|
}
|
|
75451
75939
|
|
|
75452
|
-
// src/control/
|
|
75453
|
-
var defaultRuntimeMonitorDependencies = {
|
|
75454
|
-
loadConfig,
|
|
75455
|
-
listChannelPlugins,
|
|
75456
|
-
writePid: async (pidPath, pid = process.pid) => {
|
|
75457
|
-
await ensureDir2(dirname12(pidPath));
|
|
75458
|
-
await writeTextFile(pidPath, `${pid}
|
|
75459
|
-
`);
|
|
75460
|
-
},
|
|
75461
|
-
readState: readRuntimeMonitorState,
|
|
75462
|
-
writeState: writeRuntimeMonitorState,
|
|
75463
|
-
removePid: (pidPath) => rmSync2(pidPath, { force: true }),
|
|
75464
|
-
removeRuntimeCredentials: (runtimeCredentialsPath) => rmSync2(runtimeCredentialsPath, { force: true }),
|
|
75465
|
-
sleep,
|
|
75466
|
-
now: () => Date.now(),
|
|
75467
|
-
spawnChild: (command, args, options) => spawn2(command, args, {
|
|
75468
|
-
stdio: ["ignore", "inherit", "inherit"],
|
|
75469
|
-
env: options.env
|
|
75470
|
-
}),
|
|
75471
|
-
sendSignal: kill
|
|
75472
|
-
};
|
|
75473
|
-
function isProcessAlive(pid) {
|
|
75474
|
-
try {
|
|
75475
|
-
kill(pid, 0);
|
|
75476
|
-
return true;
|
|
75477
|
-
} catch {
|
|
75478
|
-
return false;
|
|
75479
|
-
}
|
|
75480
|
-
}
|
|
75481
|
-
async function readRuntimeMonitorState(statePath = getDefaultRuntimeMonitorStatePath()) {
|
|
75482
|
-
if (!await fileExists(statePath)) {
|
|
75483
|
-
return null;
|
|
75484
|
-
}
|
|
75485
|
-
try {
|
|
75486
|
-
const raw = await readTextFile(statePath);
|
|
75487
|
-
if (!raw.trim()) {
|
|
75488
|
-
return null;
|
|
75489
|
-
}
|
|
75490
|
-
return JSON.parse(raw);
|
|
75491
|
-
} catch {
|
|
75492
|
-
return null;
|
|
75493
|
-
}
|
|
75494
|
-
}
|
|
75495
|
-
async function writeRuntimeMonitorState(statePath, state) {
|
|
75496
|
-
await ensureDir2(dirname12(statePath));
|
|
75497
|
-
await writeTextFile(statePath, `${JSON.stringify(state, null, 2)}
|
|
75498
|
-
`);
|
|
75499
|
-
}
|
|
75500
|
-
function dedupe(values) {
|
|
75501
|
-
return [...new Set(values.filter(Boolean))];
|
|
75502
|
-
}
|
|
75503
|
-
function summarizeExit(params) {
|
|
75504
|
-
if (params.signal) {
|
|
75505
|
-
return `signal ${params.signal}`;
|
|
75506
|
-
}
|
|
75507
|
-
return `code ${params.code ?? 0}`;
|
|
75508
|
-
}
|
|
75509
|
-
function getRestartPlan(config, restartNumber) {
|
|
75510
|
-
const fastRetryMaxRestarts = config.fastRetry.maxRestarts;
|
|
75511
|
-
const totalRestarts = fastRetryMaxRestarts + config.stages.reduce((sum, stage) => sum + stage.maxRestarts, 0);
|
|
75512
|
-
if (restartNumber >= 1 && restartNumber <= fastRetryMaxRestarts) {
|
|
75513
|
-
return {
|
|
75514
|
-
mode: "fast-retry",
|
|
75515
|
-
stageIndex: -1,
|
|
75516
|
-
delayMs: config.fastRetry.delaySeconds * 1000,
|
|
75517
|
-
restartAttemptInStage: restartNumber,
|
|
75518
|
-
restartsRemaining: totalRestarts - restartNumber,
|
|
75519
|
-
totalRestarts,
|
|
75520
|
-
stageMaxRestarts: fastRetryMaxRestarts
|
|
75521
|
-
};
|
|
75522
|
-
}
|
|
75523
|
-
let completedRestarts = fastRetryMaxRestarts;
|
|
75524
|
-
for (let index = 0;index < config.stages.length; index += 1) {
|
|
75525
|
-
const stage = config.stages[index];
|
|
75526
|
-
const stageStart = completedRestarts + 1;
|
|
75527
|
-
const stageEnd = completedRestarts + stage.maxRestarts;
|
|
75528
|
-
if (restartNumber >= stageStart && restartNumber <= stageEnd) {
|
|
75529
|
-
return {
|
|
75530
|
-
mode: "backoff",
|
|
75531
|
-
stageIndex: index,
|
|
75532
|
-
delayMs: stage.delayMinutes * 60000,
|
|
75533
|
-
restartAttemptInStage: restartNumber - completedRestarts,
|
|
75534
|
-
restartsRemaining: totalRestarts - restartNumber,
|
|
75535
|
-
totalRestarts,
|
|
75536
|
-
stageMaxRestarts: stage.maxRestarts
|
|
75537
|
-
};
|
|
75538
|
-
}
|
|
75539
|
-
completedRestarts = stageEnd;
|
|
75540
|
-
}
|
|
75541
|
-
return null;
|
|
75542
|
-
}
|
|
75940
|
+
// src/control/owner-alerts.ts
|
|
75543
75941
|
function parseOwnerPrincipal(principal) {
|
|
75544
75942
|
const trimmed = principal.trim();
|
|
75545
75943
|
if (!trimmed) {
|
|
@@ -75581,58 +75979,30 @@ function buildOwnerAlertCommand(params) {
|
|
|
75581
75979
|
renderMode: "native"
|
|
75582
75980
|
};
|
|
75583
75981
|
}
|
|
75982
|
+
function dedupe(values) {
|
|
75983
|
+
return [...new Set(values.filter(Boolean))];
|
|
75984
|
+
}
|
|
75584
75985
|
async function sendOwnerAlert(params) {
|
|
75585
|
-
const plugins = params.
|
|
75586
|
-
const loadedByPlatform = new Map;
|
|
75986
|
+
const plugins = params.listChannelPlugins();
|
|
75587
75987
|
const delivered = [];
|
|
75588
75988
|
const failed = [];
|
|
75589
|
-
async function loadPlatform(platform) {
|
|
75590
|
-
const existing = loadedByPlatform.get(platform);
|
|
75591
|
-
if (existing) {
|
|
75592
|
-
return existing;
|
|
75593
|
-
}
|
|
75594
|
-
const loaded = await params.dependencies.loadConfig(params.configPath, {
|
|
75595
|
-
materializeChannels: [platform]
|
|
75596
|
-
});
|
|
75597
|
-
loadedByPlatform.set(platform, loaded);
|
|
75598
|
-
return loaded;
|
|
75599
|
-
}
|
|
75600
|
-
const ownersByPlatform = new Map;
|
|
75601
75989
|
for (const platform of ["slack", "telegram"]) {
|
|
75602
|
-
|
|
75603
|
-
|
|
75604
|
-
const principals = dedupe(loaded.raw.app.auth.roles.owner?.users ?? []);
|
|
75605
|
-
ownersByPlatform.set(platform, principals.map(parseOwnerPrincipal).filter((entry) => entry?.platform === platform).map((entry) => entry.userId));
|
|
75606
|
-
} catch {
|
|
75607
|
-
ownersByPlatform.set(platform, []);
|
|
75608
|
-
}
|
|
75609
|
-
}
|
|
75610
|
-
for (const platform of ["slack", "telegram"]) {
|
|
75611
|
-
const ownerIds = ownersByPlatform.get(platform) ?? [];
|
|
75990
|
+
const principals = dedupe(params.loadedConfig.raw.app.auth.roles.owner?.users ?? []);
|
|
75991
|
+
const ownerIds = principals.map(parseOwnerPrincipal).filter((entry) => entry?.platform === platform).map((entry) => entry.userId);
|
|
75612
75992
|
if (ownerIds.length === 0) {
|
|
75613
75993
|
continue;
|
|
75614
75994
|
}
|
|
75615
|
-
const loaded = await loadPlatform(platform).catch(() => null);
|
|
75616
|
-
if (!loaded) {
|
|
75617
|
-
for (const userId of ownerIds) {
|
|
75618
|
-
failed.push({
|
|
75619
|
-
principal: `${platform}:${userId}`,
|
|
75620
|
-
detail: "config could not be loaded with resolved credentials"
|
|
75621
|
-
});
|
|
75622
|
-
}
|
|
75623
|
-
continue;
|
|
75624
|
-
}
|
|
75625
75995
|
const plugin = plugins.find((entry) => entry.id === platform);
|
|
75626
|
-
if (!plugin || !plugin.isEnabled(
|
|
75996
|
+
if (!plugin || !plugin.isEnabled(params.loadedConfig)) {
|
|
75627
75997
|
continue;
|
|
75628
75998
|
}
|
|
75629
|
-
const botIds = dedupe(plugin.listBots(
|
|
75999
|
+
const botIds = dedupe(plugin.listBots(params.loadedConfig).map((entry) => entry.botId));
|
|
75630
76000
|
for (const userId of ownerIds) {
|
|
75631
76001
|
let deliveredToPrincipal = false;
|
|
75632
76002
|
const principal = `${platform}:${userId}`;
|
|
75633
76003
|
for (const botId of botIds) {
|
|
75634
76004
|
try {
|
|
75635
|
-
await plugin.runMessageCommand(
|
|
76005
|
+
await plugin.runMessageCommand(params.loadedConfig, buildOwnerAlertCommand({
|
|
75636
76006
|
platform,
|
|
75637
76007
|
botId,
|
|
75638
76008
|
userId,
|
|
@@ -75661,16 +76031,73 @@ async function sendOwnerAlert(params) {
|
|
|
75661
76031
|
failed
|
|
75662
76032
|
};
|
|
75663
76033
|
}
|
|
76034
|
+
|
|
76035
|
+
// src/control/runtime-monitor.ts
|
|
76036
|
+
var defaultRuntimeMonitorDependencies = {
|
|
76037
|
+
loadConfig,
|
|
76038
|
+
listChannelPlugins,
|
|
76039
|
+
writePid: async (pidPath, pid = process.pid) => {
|
|
76040
|
+
await ensureDir2(dirname12(pidPath));
|
|
76041
|
+
await writeTextFile(pidPath, `${pid}
|
|
76042
|
+
`);
|
|
76043
|
+
},
|
|
76044
|
+
readState: readRuntimeMonitorState,
|
|
76045
|
+
writeState: writeRuntimeMonitorState,
|
|
76046
|
+
removePid: (pidPath) => rmSync2(pidPath, { force: true }),
|
|
76047
|
+
removeRuntimeCredentials: (runtimeCredentialsPath) => rmSync2(runtimeCredentialsPath, { force: true }),
|
|
76048
|
+
sleep,
|
|
76049
|
+
now: () => Date.now(),
|
|
76050
|
+
spawnChild: (command, args, options) => spawn2(command, args, {
|
|
76051
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
76052
|
+
env: options.env
|
|
76053
|
+
}),
|
|
76054
|
+
sendSignal: kill
|
|
76055
|
+
};
|
|
76056
|
+
function isProcessAlive(pid) {
|
|
76057
|
+
try {
|
|
76058
|
+
kill(pid, 0);
|
|
76059
|
+
return true;
|
|
76060
|
+
} catch {
|
|
76061
|
+
return false;
|
|
76062
|
+
}
|
|
76063
|
+
}
|
|
76064
|
+
async function readRuntimeMonitorState(statePath = getDefaultRuntimeMonitorStatePath()) {
|
|
76065
|
+
if (!await fileExists(statePath)) {
|
|
76066
|
+
return null;
|
|
76067
|
+
}
|
|
76068
|
+
try {
|
|
76069
|
+
const raw = await readTextFile(statePath);
|
|
76070
|
+
if (!raw.trim()) {
|
|
76071
|
+
return null;
|
|
76072
|
+
}
|
|
76073
|
+
return JSON.parse(raw);
|
|
76074
|
+
} catch {
|
|
76075
|
+
return null;
|
|
76076
|
+
}
|
|
76077
|
+
}
|
|
76078
|
+
async function writeRuntimeMonitorState(statePath, state) {
|
|
76079
|
+
await ensureDir2(dirname12(statePath));
|
|
76080
|
+
await writeTextFile(statePath, `${JSON.stringify(state, null, 2)}
|
|
76081
|
+
`);
|
|
76082
|
+
}
|
|
76083
|
+
function summarizeExit(params) {
|
|
76084
|
+
if (params.signal) {
|
|
76085
|
+
return `signal ${params.signal}`;
|
|
76086
|
+
}
|
|
76087
|
+
return `code ${params.code ?? 0}`;
|
|
76088
|
+
}
|
|
75664
76089
|
function renderBackoffAlertMessage(params) {
|
|
76090
|
+
const restartLine = params.repeatingFinalStage ? `restart: ${params.restartNumber} (steady-state at final stage; configured ladder ${params.totalConfiguredRestarts})` : `restart: ${params.restartNumber}/${params.totalConfiguredRestarts}`;
|
|
76091
|
+
const stageAttemptLine = params.repeatingFinalStage ? "stage attempt: steady-state retry on final stage" : `stage attempt: ${params.restartAttemptInStage}/${params.stageMaxRestarts}`;
|
|
75665
76092
|
return [
|
|
75666
76093
|
"clisbot runtime alert",
|
|
75667
76094
|
"",
|
|
75668
76095
|
"status: runtime exited unexpectedly and entered restart backoff",
|
|
75669
76096
|
`last exit: ${summarizeExit(params.exit)} at ${params.exit.at}`,
|
|
75670
76097
|
`next restart: ${params.nextRestartAt}`,
|
|
75671
|
-
|
|
76098
|
+
restartLine,
|
|
75672
76099
|
`stage: ${params.stageIndex + 1}/${params.config.restartBackoff.stages.length}`,
|
|
75673
|
-
|
|
76100
|
+
stageAttemptLine
|
|
75674
76101
|
].join(`
|
|
75675
76102
|
`);
|
|
75676
76103
|
}
|
|
@@ -75680,7 +76107,7 @@ function renderStoppedAlertMessage(params) {
|
|
|
75680
76107
|
"",
|
|
75681
76108
|
"status: runtime stopped after exhausting the configured restart budget",
|
|
75682
76109
|
`last exit: ${summarizeExit(params.exit)} at ${params.exit.at}`,
|
|
75683
|
-
`restart budget used: ${params.
|
|
76110
|
+
`restart budget used: ${params.totalConfiguredRestarts}`,
|
|
75684
76111
|
`action: inspect ${renderCliCommand("logs", { inline: true })}, fix the fault, then start the service again`
|
|
75685
76112
|
].join(`
|
|
75686
76113
|
`);
|
|
@@ -75697,6 +76124,7 @@ class RuntimeMonitor {
|
|
|
75697
76124
|
stopRequested = false;
|
|
75698
76125
|
activeChild = null;
|
|
75699
76126
|
latestState = null;
|
|
76127
|
+
loadedConfig;
|
|
75700
76128
|
constructor(scriptPath, configPath, pidPath, statePath, runtimeCredentialsPath, dependencies) {
|
|
75701
76129
|
this.scriptPath = scriptPath;
|
|
75702
76130
|
this.configPath = configPath;
|
|
@@ -75710,13 +76138,16 @@ class RuntimeMonitor {
|
|
|
75710
76138
|
this.registerProcessHandlers();
|
|
75711
76139
|
try {
|
|
75712
76140
|
const loadedConfig = await this.dependencies.loadConfig(this.configPath);
|
|
76141
|
+
this.loadedConfig = loadedConfig;
|
|
75713
76142
|
const monitorConfig = loadedConfig.raw.control.runtimeMonitor;
|
|
76143
|
+
monitorConfig.restartBackoff = normalizeRuntimeMonitorRestartBackoff(monitorConfig.restartBackoff);
|
|
75714
76144
|
let restartNumber = 0;
|
|
75715
|
-
let
|
|
76145
|
+
let totalConfiguredRestarts = getConfiguredRuntimeMonitorRestartBudget(monitorConfig.restartBackoff);
|
|
75716
76146
|
await this.writeState({
|
|
75717
76147
|
phase: "starting"
|
|
75718
76148
|
});
|
|
75719
76149
|
while (!this.stopRequested) {
|
|
76150
|
+
const runStartedAt = this.dependencies.now();
|
|
75720
76151
|
const child = this.dependencies.spawnChild(process.execPath, [this.scriptPath, "serve-foreground"], {
|
|
75721
76152
|
env: {
|
|
75722
76153
|
...process.env,
|
|
@@ -75738,11 +76169,14 @@ class RuntimeMonitor {
|
|
|
75738
76169
|
break;
|
|
75739
76170
|
}
|
|
75740
76171
|
const exitAt = new Date().toISOString();
|
|
76172
|
+
if (this.dependencies.now() - runStartedAt >= RUNTIME_MONITOR_RESTART_RESET_AFTER_MS) {
|
|
76173
|
+
restartNumber = 0;
|
|
76174
|
+
}
|
|
75741
76175
|
const nextRestartNumber = restartNumber + 1;
|
|
75742
|
-
const plan =
|
|
76176
|
+
const plan = getRuntimeMonitorRestartPlan(monitorConfig.restartBackoff, nextRestartNumber);
|
|
75743
76177
|
if (!plan) {
|
|
75744
76178
|
await this.maybeSendAlert("stopped", monitorConfig, renderStoppedAlertMessage({
|
|
75745
|
-
|
|
76179
|
+
totalConfiguredRestarts,
|
|
75746
76180
|
exit: {
|
|
75747
76181
|
code: exit.code,
|
|
75748
76182
|
signal: exit.signal,
|
|
@@ -75762,7 +76196,7 @@ class RuntimeMonitor {
|
|
|
75762
76196
|
return;
|
|
75763
76197
|
}
|
|
75764
76198
|
restartNumber = nextRestartNumber;
|
|
75765
|
-
|
|
76199
|
+
totalConfiguredRestarts = plan.totalConfiguredRestarts;
|
|
75766
76200
|
const nextRestartAt = new Date(this.dependencies.now() + plan.delayMs).toISOString();
|
|
75767
76201
|
if (plan.mode === "backoff") {
|
|
75768
76202
|
await this.maybeSendAlert("backoff", monitorConfig, renderBackoffAlertMessage({
|
|
@@ -75771,8 +76205,9 @@ class RuntimeMonitor {
|
|
|
75771
76205
|
stageIndex: plan.stageIndex,
|
|
75772
76206
|
restartAttemptInStage: plan.restartAttemptInStage,
|
|
75773
76207
|
stageMaxRestarts: plan.stageMaxRestarts,
|
|
75774
|
-
|
|
76208
|
+
totalConfiguredRestarts,
|
|
75775
76209
|
nextRestartAt,
|
|
76210
|
+
repeatingFinalStage: plan.repeatingFinalStage,
|
|
75776
76211
|
exit: {
|
|
75777
76212
|
code: exit.code,
|
|
75778
76213
|
signal: exit.signal,
|
|
@@ -75873,10 +76308,13 @@ class RuntimeMonitor {
|
|
|
75873
76308
|
}
|
|
75874
76309
|
}
|
|
75875
76310
|
try {
|
|
76311
|
+
if (!this.loadedConfig) {
|
|
76312
|
+
return;
|
|
76313
|
+
}
|
|
75876
76314
|
const result = await sendOwnerAlert({
|
|
75877
|
-
|
|
76315
|
+
loadedConfig: this.loadedConfig,
|
|
75878
76316
|
message,
|
|
75879
|
-
|
|
76317
|
+
listChannelPlugins: this.dependencies.listChannelPlugins
|
|
75880
76318
|
});
|
|
75881
76319
|
if (result.delivered.length === 0 && result.failed.length > 0) {
|
|
75882
76320
|
console.error("clisbot runtime alert delivery failed", result.failed.map((entry) => `${entry.principal}: ${entry.detail}`).join("; "));
|
|
@@ -76021,6 +76459,20 @@ function resolveMonitorStatePath(monitorStatePath, configPath, options = {}) {
|
|
|
76021
76459
|
}
|
|
76022
76460
|
return expandHomePath(getDefaultRuntimeMonitorStatePath());
|
|
76023
76461
|
}
|
|
76462
|
+
function resolveLiveMonitorPid(params) {
|
|
76463
|
+
const processLiveness = params.processLiveness ?? getProcessLiveness;
|
|
76464
|
+
if (params.pidFromFile && processLiveness(params.pidFromFile) === "running") {
|
|
76465
|
+
return params.pidFromFile;
|
|
76466
|
+
}
|
|
76467
|
+
const monitorPid = params.monitorState?.monitorPid;
|
|
76468
|
+
if (monitorPid && processLiveness(monitorPid) === "running") {
|
|
76469
|
+
return monitorPid;
|
|
76470
|
+
}
|
|
76471
|
+
return null;
|
|
76472
|
+
}
|
|
76473
|
+
function resolveKnownMonitorPid(params) {
|
|
76474
|
+
return params.pidFromFile ?? params.monitorState?.monitorPid ?? null;
|
|
76475
|
+
}
|
|
76024
76476
|
function resolveRuntimeCredentialsPath(runtimeCredentialsPath, configPath, options = {}) {
|
|
76025
76477
|
if (runtimeCredentialsPath) {
|
|
76026
76478
|
return expandHomePath(runtimeCredentialsPath);
|
|
@@ -76125,11 +76577,18 @@ async function startDetachedRuntime(params) {
|
|
|
76125
76577
|
const runtimeCredentialsPath = resolveRuntimeCredentialsPath(params.runtimeCredentialsPath, configPath, { preferConfigSibling });
|
|
76126
76578
|
const existingPid = await readRuntimePid(pidPath);
|
|
76127
76579
|
const existingMonitorState = await readRuntimeMonitorState(monitorStatePath);
|
|
76128
|
-
|
|
76580
|
+
const liveMonitorPid = resolveLiveMonitorPid({
|
|
76581
|
+
pidFromFile: existingPid,
|
|
76582
|
+
monitorState: existingMonitorState
|
|
76583
|
+
});
|
|
76584
|
+
if (liveMonitorPid) {
|
|
76585
|
+
if (existingPid !== liveMonitorPid) {
|
|
76586
|
+
await writeRuntimePid(pidPath, liveMonitorPid);
|
|
76587
|
+
}
|
|
76129
76588
|
return {
|
|
76130
76589
|
alreadyRunning: true,
|
|
76131
76590
|
createdConfig: false,
|
|
76132
|
-
pid:
|
|
76591
|
+
pid: liveMonitorPid,
|
|
76133
76592
|
configPath,
|
|
76134
76593
|
logPath
|
|
76135
76594
|
};
|
|
@@ -76208,10 +76667,14 @@ async function stopDetachedRuntime(params, dependencies = {}) {
|
|
|
76208
76667
|
const processLiveness = dependencies.processLiveness ?? getProcessLiveness;
|
|
76209
76668
|
const sendSignal = dependencies.sendSignal ?? kill2;
|
|
76210
76669
|
const sleepFn = dependencies.sleep ?? sleep;
|
|
76211
|
-
const
|
|
76212
|
-
|
|
76213
|
-
|
|
76214
|
-
|
|
76670
|
+
const monitorPid = resolveKnownMonitorPid({
|
|
76671
|
+
pidFromFile: existingPid,
|
|
76672
|
+
monitorState
|
|
76673
|
+
});
|
|
76674
|
+
const monitorLiveness = monitorPid ? processLiveness(monitorPid) : "missing";
|
|
76675
|
+
if (monitorPid && monitorLiveness === "running") {
|
|
76676
|
+
sendSignal(monitorPid, "SIGTERM");
|
|
76677
|
+
const exited = await waitForProcessExit(monitorPid, STOP_WAIT_TIMEOUT_MS, {
|
|
76215
76678
|
processLiveness,
|
|
76216
76679
|
sleep: sleepFn
|
|
76217
76680
|
});
|
|
@@ -76219,7 +76682,7 @@ async function stopDetachedRuntime(params, dependencies = {}) {
|
|
|
76219
76682
|
throw new Error(`clisbot did not stop within ${STOP_WAIT_TIMEOUT_MS}ms`);
|
|
76220
76683
|
}
|
|
76221
76684
|
stopped = true;
|
|
76222
|
-
} else if (
|
|
76685
|
+
} else if (monitorPid && monitorLiveness === "zombie") {
|
|
76223
76686
|
stopped = true;
|
|
76224
76687
|
}
|
|
76225
76688
|
const runtimePid = monitorState?.runtimePid;
|
|
@@ -76235,7 +76698,7 @@ async function stopDetachedRuntime(params, dependencies = {}) {
|
|
|
76235
76698
|
}
|
|
76236
76699
|
stopped = true;
|
|
76237
76700
|
} catch (error) {
|
|
76238
|
-
if (!(
|
|
76701
|
+
if (!(monitorPid && monitorLiveness === "running")) {
|
|
76239
76702
|
throw error;
|
|
76240
76703
|
}
|
|
76241
76704
|
}
|
|
@@ -76293,11 +76756,14 @@ async function getRuntimeStatus(params = {}) {
|
|
|
76293
76756
|
preferConfigSibling
|
|
76294
76757
|
});
|
|
76295
76758
|
const pid = await readRuntimePid(pidPath);
|
|
76296
|
-
const liveness = pid ? getProcessLiveness(pid) : "missing";
|
|
76297
76759
|
const monitorState = await readRuntimeMonitorState(monitorStatePath);
|
|
76760
|
+
const liveMonitorPid = resolveLiveMonitorPid({
|
|
76761
|
+
pidFromFile: pid,
|
|
76762
|
+
monitorState
|
|
76763
|
+
});
|
|
76298
76764
|
return {
|
|
76299
|
-
running:
|
|
76300
|
-
pid:
|
|
76765
|
+
running: liveMonitorPid != null,
|
|
76766
|
+
pid: liveMonitorPid ?? undefined,
|
|
76301
76767
|
configPath,
|
|
76302
76768
|
pidPath,
|
|
76303
76769
|
logPath,
|
|
@@ -76495,7 +76961,7 @@ function extractLinuxProcState(raw) {
|
|
|
76495
76961
|
}
|
|
76496
76962
|
|
|
76497
76963
|
// src/control/bots-cli.ts
|
|
76498
|
-
function
|
|
76964
|
+
function getEditableConfigPath7() {
|
|
76499
76965
|
return process.env.CLISBOT_CONFIG_PATH;
|
|
76500
76966
|
}
|
|
76501
76967
|
function parseOptionValue2(args, name) {
|
|
@@ -76685,7 +77151,7 @@ function summarizeBotConfig(provider, botId, bot) {
|
|
|
76685
77151
|
};
|
|
76686
77152
|
}
|
|
76687
77153
|
async function listBots(args) {
|
|
76688
|
-
const { config } = await readEditableConfig(
|
|
77154
|
+
const { config } = await readEditableConfig(getEditableConfigPath7());
|
|
76689
77155
|
const provider = parseOptionValue2(args, "--channel");
|
|
76690
77156
|
const printJson = hasFlag3(args, "--json");
|
|
76691
77157
|
const summaries = [
|
|
@@ -76710,7 +77176,7 @@ async function addOrSetBotCredentials(args, deps, action) {
|
|
|
76710
77176
|
const botId = getBotId(args);
|
|
76711
77177
|
const persist = hasFlag3(args, "--persist");
|
|
76712
77178
|
const runtimeStatus = await deps.getRuntimeStatus();
|
|
76713
|
-
const { config, configPath } = await readEditableConfig(
|
|
77179
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath7());
|
|
76714
77180
|
const exists = provider === "slack" ? botId in getSlackBots2(config) : (botId in getTelegramBots2(config));
|
|
76715
77181
|
if (action === "add" && exists) {
|
|
76716
77182
|
throw new Error(`Bot already exists: ${provider}/${botId}. Use ${renderCliCommand("bots set-agent ...", { inline: true })}, ${renderCliCommand("bots set-credentials ...", { inline: true })}, or another \`set-<key>\` command.`);
|
|
@@ -76838,7 +77304,7 @@ async function getBot(args) {
|
|
|
76838
77304
|
const provider = parseProvider(args);
|
|
76839
77305
|
const botId = getBotId(args);
|
|
76840
77306
|
const printJson = hasFlag3(args, "--json");
|
|
76841
|
-
const { config, configPath } = await readEditableConfig(
|
|
77307
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath7());
|
|
76842
77308
|
const bot = ensureProviderBot(config, provider, botId);
|
|
76843
77309
|
if (printJson) {
|
|
76844
77310
|
console.log(JSON.stringify(bot, null, 2));
|
|
@@ -76849,7 +77315,7 @@ async function getBot(args) {
|
|
|
76849
77315
|
async function setBotEnabled(args, enabled) {
|
|
76850
77316
|
const provider = parseProvider(args);
|
|
76851
77317
|
const botId = getBotId(args);
|
|
76852
|
-
const { config, configPath } = await readEditableConfig(
|
|
77318
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath7());
|
|
76853
77319
|
const bot = ensureProviderBot(config, provider, botId);
|
|
76854
77320
|
bot.enabled = enabled;
|
|
76855
77321
|
reconcileProviderDefaults(config, provider);
|
|
@@ -76860,7 +77326,7 @@ async function setBotEnabled(args, enabled) {
|
|
|
76860
77326
|
async function removeBot(args) {
|
|
76861
77327
|
const provider = parseProvider(args);
|
|
76862
77328
|
const botId = getBotId(args);
|
|
76863
|
-
const { config, configPath } = await readEditableConfig(
|
|
77329
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath7());
|
|
76864
77330
|
const bot = ensureProviderBot(config, provider, botId);
|
|
76865
77331
|
const directMessages = "directMessages" in bot ? Object.keys(bot.directMessages ?? {}) : [];
|
|
76866
77332
|
const groups = "groups" in bot ? Object.keys(bot.groups ?? {}) : [];
|
|
@@ -76879,7 +77345,7 @@ async function removeBot(args) {
|
|
|
76879
77345
|
}
|
|
76880
77346
|
async function getOrSetDefaultBot(args, action) {
|
|
76881
77347
|
const provider = parseProvider(args);
|
|
76882
|
-
const { config, configPath } = await readEditableConfig(
|
|
77348
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath7());
|
|
76883
77349
|
if (action === "get-default") {
|
|
76884
77350
|
const botId2 = provider === "slack" ? config.bots.slack.defaults.defaultBotId : config.bots.telegram.defaults.defaultBotId;
|
|
76885
77351
|
console.log(`${provider} default bot: ${botId2}`);
|
|
@@ -76904,7 +77370,7 @@ async function getOrSetDefaultBot(args, action) {
|
|
|
76904
77370
|
async function getOrSetBotAgent(args, action) {
|
|
76905
77371
|
const provider = parseProvider(args);
|
|
76906
77372
|
const botId = getBotId(args);
|
|
76907
|
-
const { config, configPath } = await readEditableConfig(
|
|
77373
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath7());
|
|
76908
77374
|
const bot = ensureProviderBot(config, provider, botId);
|
|
76909
77375
|
if (action === "get-agent") {
|
|
76910
77376
|
console.log(`${provider}/${botId} agent: ${bot.agentId ?? "(inherit)"}`);
|
|
@@ -76934,7 +77400,7 @@ function ensureDefaultDmRoute(config, provider, botId) {
|
|
|
76934
77400
|
async function getOrSetBotPolicy(args, action) {
|
|
76935
77401
|
const provider = parseProvider(args);
|
|
76936
77402
|
const botId = getBotId(args);
|
|
76937
|
-
const { config, configPath } = await readEditableConfig(
|
|
77403
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath7());
|
|
76938
77404
|
const bot = ensureProviderBot(config, provider, botId);
|
|
76939
77405
|
if (action === "get-dm-policy") {
|
|
76940
77406
|
console.log(`${provider}/${botId} dmPolicy: ${ensureDefaultDmRoute(config, provider, botId).policy ?? "pairing"}`);
|
|
@@ -76989,7 +77455,7 @@ async function getOrSetBotPolicy(args, action) {
|
|
|
76989
77455
|
async function getCredentialSource(args) {
|
|
76990
77456
|
const provider = parseProvider(args);
|
|
76991
77457
|
const botId = getBotId(args);
|
|
76992
|
-
const { config, configPath } = await readEditableConfig(
|
|
77458
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath7());
|
|
76993
77459
|
ensureProviderBot(config, provider, botId);
|
|
76994
77460
|
const source = provider === "slack" ? describeSlackCredentialSource({ config: config.bots.slack, botId }) : describeTelegramCredentialSource({ config: config.bots.telegram, botId });
|
|
76995
77461
|
console.log(`${provider}/${botId} credentials: ${source.detail}`);
|
|
@@ -77580,14 +78046,14 @@ async function getScopedLoopCounts(params) {
|
|
|
77580
78046
|
|
|
77581
78047
|
// src/control/loops-cli.ts
|
|
77582
78048
|
var LOOP_BUSY_RETRY_MS = 250;
|
|
77583
|
-
function
|
|
78049
|
+
function getEditableConfigPath8() {
|
|
77584
78050
|
return process.env.CLISBOT_CONFIG_PATH;
|
|
77585
78051
|
}
|
|
77586
78052
|
function getSessionState(sessionStorePath) {
|
|
77587
78053
|
return new AgentSessionState(new SessionStore(sessionStorePath));
|
|
77588
78054
|
}
|
|
77589
78055
|
async function loadLoopControlState() {
|
|
77590
|
-
const configPath = await ensureEditableConfigFile(
|
|
78056
|
+
const configPath = await ensureEditableConfigFile(getEditableConfigPath8());
|
|
77591
78057
|
const loadedConfig = await loadConfigWithoutEnvResolution(configPath);
|
|
77592
78058
|
const sessionStorePath = resolveSessionStorePath(loadedConfig);
|
|
77593
78059
|
return {
|
|
@@ -78102,6 +78568,14 @@ function parseOptionValue4(args, name) {
|
|
|
78102
78568
|
const values = parseRepeatedOption3(args, name);
|
|
78103
78569
|
return values.length > 0 ? values.at(-1) : undefined;
|
|
78104
78570
|
}
|
|
78571
|
+
function parseAliasedOptionValue2(args, preferredName, aliasName) {
|
|
78572
|
+
const preferredValues = parseRepeatedOption3(args, preferredName);
|
|
78573
|
+
const aliasValues = parseRepeatedOption3(args, aliasName);
|
|
78574
|
+
if (preferredValues.length > 0 && aliasValues.length > 0) {
|
|
78575
|
+
throw new Error(`${preferredName} and ${aliasName} are aliases; use only one`);
|
|
78576
|
+
}
|
|
78577
|
+
return preferredValues.at(-1) ?? aliasValues.at(-1);
|
|
78578
|
+
}
|
|
78105
78579
|
function parseThreadingOptions(args, channel) {
|
|
78106
78580
|
const threadId = parseOptionValue4(args, "--thread-id");
|
|
78107
78581
|
const topicId = parseOptionValue4(args, "--topic-id");
|
|
@@ -78124,6 +78598,9 @@ function parseMessageBodyFileOption(args) {
|
|
|
78124
78598
|
}
|
|
78125
78599
|
return bodyFileValues.at(-1) ?? messageFileValues.at(-1);
|
|
78126
78600
|
}
|
|
78601
|
+
function parseMessageAttachmentOption(args) {
|
|
78602
|
+
return parseAliasedOptionValue2(args, "--file", "--media");
|
|
78603
|
+
}
|
|
78127
78604
|
function parseIntegerOption(args, name) {
|
|
78128
78605
|
const raw = parseOptionValue4(args, name);
|
|
78129
78606
|
if (!raw) {
|
|
@@ -78165,7 +78642,7 @@ function parseMessageCommand(args) {
|
|
|
78165
78642
|
target: parseOptionValue4(rest, "--target"),
|
|
78166
78643
|
message: parseOptionValue4(rest, "--message") ?? parseOptionValue4(rest, "-m"),
|
|
78167
78644
|
messageFile: parseMessageBodyFileOption(rest),
|
|
78168
|
-
media:
|
|
78645
|
+
media: parseMessageAttachmentOption(rest),
|
|
78169
78646
|
messageId: parseOptionValue4(rest, "--message-id"),
|
|
78170
78647
|
emoji: parseOptionValue4(rest, "--emoji"),
|
|
78171
78648
|
remove: hasFlag5(rest, "--remove"),
|
|
@@ -78189,7 +78666,7 @@ function renderMessageHelp() {
|
|
|
78189
78666
|
renderCliCommand("message"),
|
|
78190
78667
|
"",
|
|
78191
78668
|
"Usage:",
|
|
78192
|
-
` ${renderCliCommand("message send --channel <slack|telegram> --target <dest> [--message <text> | --body-file <path>] [--input <plain|md|html|mrkdwn|blocks>] [--render <native|none|html|mrkdwn|blocks>] [--account <id>] [--
|
|
78669
|
+
` ${renderCliCommand("message send --channel <slack|telegram> --target <dest> [--message <text> | --body-file <path>] [--input <plain|md|html|mrkdwn|blocks>] [--render <native|none|html|mrkdwn|blocks>] [--account <id>] [--file <path-or-url>] [--reply-to <id>] [--thread-id <slack-thread-ts>] [--topic-id <telegram-topic-id>] [--force-document] [--silent] [--progress|--final]")}`,
|
|
78193
78670
|
` ${renderCliCommand("message poll --channel <slack|telegram> --target <dest> --poll-question <text> --poll-option <value> [--poll-option <value>] [--account <id>] [--thread-id <slack-thread-ts>] [--topic-id <telegram-topic-id>] [--silent]")}`,
|
|
78194
78671
|
` ${renderCliCommand("message react --channel <slack|telegram> --target <dest> --message-id <id> --emoji <emoji> [--account <id>] [--remove]")}`,
|
|
78195
78672
|
` ${renderCliCommand("message reactions --channel <slack|telegram> --target <dest> --message-id <id> [--account <id>]")}`,
|
|
@@ -78205,6 +78682,8 @@ function renderMessageHelp() {
|
|
|
78205
78682
|
" --message <text> Inline message body",
|
|
78206
78683
|
" --body-file <path> Read the message body from a file",
|
|
78207
78684
|
" Alias: --message-file (compat only)",
|
|
78685
|
+
" --file <path-or-url> Attach a file or remote URL",
|
|
78686
|
+
" Alias: --media (compat only)",
|
|
78208
78687
|
" --input <plain|md|html|mrkdwn|blocks>",
|
|
78209
78688
|
" Input content format. Default: md",
|
|
78210
78689
|
" --render <native|none|html|mrkdwn|blocks>",
|
|
@@ -78221,6 +78700,11 @@ function renderMessageHelp() {
|
|
|
78221
78700
|
" html Telegram only",
|
|
78222
78701
|
" mrkdwn Slack only",
|
|
78223
78702
|
"",
|
|
78703
|
+
"Length Guidance:",
|
|
78704
|
+
" Telegram native/html Final payload must stay under 4096 chars; leave headroom after HTML-safe rendering",
|
|
78705
|
+
" Slack text/mrkdwn Prefer text under 4000 chars; Slack truncates very long text after 40000",
|
|
78706
|
+
" Slack blocks Max 50 blocks; keep header text under 150 and section text under 3000",
|
|
78707
|
+
"",
|
|
78224
78708
|
"Threading:",
|
|
78225
78709
|
" --thread-id <id> Slack thread ts",
|
|
78226
78710
|
" --topic-id <id> Telegram topic id",
|
|
@@ -78291,7 +78775,7 @@ async function runMessageCli(args, dependencies = defaultMessageCliDependencies)
|
|
|
78291
78775
|
}
|
|
78292
78776
|
|
|
78293
78777
|
// src/control/routes-cli.ts
|
|
78294
|
-
function
|
|
78778
|
+
function getEditableConfigPath9() {
|
|
78295
78779
|
return process.env.CLISBOT_CONFIG_PATH;
|
|
78296
78780
|
}
|
|
78297
78781
|
function getSlackBots3(config) {
|
|
@@ -78567,7 +79051,7 @@ function renderRoutesHelp() {
|
|
|
78567
79051
|
`);
|
|
78568
79052
|
}
|
|
78569
79053
|
async function listRoutes(args) {
|
|
78570
|
-
const { config } = await readEditableConfig(
|
|
79054
|
+
const { config } = await readEditableConfig(getEditableConfigPath9());
|
|
78571
79055
|
const provider = parseOptionalProvider(args);
|
|
78572
79056
|
const botIdFilter = parseOptionValue5(args, "--bot");
|
|
78573
79057
|
const printJson = hasFlag6(args, "--json");
|
|
@@ -78628,7 +79112,7 @@ async function addRoute(args) {
|
|
|
78628
79112
|
const policy = parseOptionValue5(args, "--policy");
|
|
78629
79113
|
const requireMention = parseOptionValue5(args, "--require-mention");
|
|
78630
79114
|
const allowBots = parseOptionValue5(args, "--allow-bots");
|
|
78631
|
-
const { config, configPath } = await readEditableConfig(
|
|
79115
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath9());
|
|
78632
79116
|
const existing = getOrCreateRoute(config, provider, botId, parsed);
|
|
78633
79117
|
if (existing) {
|
|
78634
79118
|
throw new Error(`Route already exists: ${provider}/${botId}/${parsed.routeId}. Use a matching \`set-<key>\` command instead.`);
|
|
@@ -78652,7 +79136,7 @@ async function getRoute(args) {
|
|
|
78652
79136
|
const botId = getBotId2(args);
|
|
78653
79137
|
const parsed = parseCommandRoute(args, provider);
|
|
78654
79138
|
const printJson = hasFlag6(args, "--json");
|
|
78655
|
-
const { config, configPath } = await readEditableConfig(
|
|
79139
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath9());
|
|
78656
79140
|
const route = ensureRoute(config, provider, botId, parsed);
|
|
78657
79141
|
if (printJson) {
|
|
78658
79142
|
console.log(JSON.stringify(route, null, 2));
|
|
@@ -78665,7 +79149,7 @@ async function setRouteEnabled(args, enabled) {
|
|
|
78665
79149
|
const botId = getBotId2(args);
|
|
78666
79150
|
const parsed = parseCommandRoute(args, provider);
|
|
78667
79151
|
rejectExactDirectMessageAdmissionChange(parsed, enabled ? "Enabling" : "Disabling");
|
|
78668
|
-
const { config, configPath } = await readEditableConfig(
|
|
79152
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath9());
|
|
78669
79153
|
const route = ensureRoute(config, provider, botId, parsed);
|
|
78670
79154
|
route.enabled = enabled;
|
|
78671
79155
|
await writeEditableConfig(configPath, config);
|
|
@@ -78676,7 +79160,7 @@ async function removeRoute(args) {
|
|
|
78676
79160
|
const provider = parseProvider2(args);
|
|
78677
79161
|
const botId = getBotId2(args);
|
|
78678
79162
|
const parsed = parseCommandRoute(args, provider);
|
|
78679
|
-
const { config, configPath } = await readEditableConfig(
|
|
79163
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath9());
|
|
78680
79164
|
ensureRoute(config, provider, botId, parsed);
|
|
78681
79165
|
if (provider === "slack") {
|
|
78682
79166
|
const bot = ensureSlackBot(config, botId);
|
|
@@ -78703,7 +79187,7 @@ async function getSetClearRouteField(args, action) {
|
|
|
78703
79187
|
const provider = parseProvider2(args);
|
|
78704
79188
|
const botId = getBotId2(args);
|
|
78705
79189
|
const parsed = parseCommandRoute(args, provider);
|
|
78706
|
-
const { config, configPath } = await readEditableConfig(
|
|
79190
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath9());
|
|
78707
79191
|
const route = ensureRoute(config, provider, botId, parsed);
|
|
78708
79192
|
if (action === "get-agent") {
|
|
78709
79193
|
console.log(`${provider}/${botId}/${parsed.routeId} agent: ${route.agentId ?? "(inherit)"}`);
|
|
@@ -78809,7 +79293,7 @@ async function mutateRouteUsers(args, action) {
|
|
|
78809
79293
|
if (!user) {
|
|
78810
79294
|
throw new Error(renderRoutesHelp());
|
|
78811
79295
|
}
|
|
78812
|
-
const { config, configPath } = await readEditableConfig(
|
|
79296
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath9());
|
|
78813
79297
|
const route = parsed.storage === "directMessages" && isDirectMessageWildcardRouteId(parsed.routeId) ? ensureBotDirectMessageWildcardRoute(config, provider, botId) : ensureRoute(config, provider, botId, parsed);
|
|
78814
79298
|
const field = action.includes("allow") ? "allowUsers" : "blockUsers";
|
|
78815
79299
|
const current = Array.from(new Set((route[field] ?? []).filter(Boolean)));
|
|
@@ -79055,7 +79539,7 @@ function renderRunnerHelp() {
|
|
|
79055
79539
|
` ${renderCliCommand("runner smoke --backend all --suite launch-trio [--workspace <path>] [--agent <id>] [--artifact-dir <path>] [--timeout-ms <n>] [--keep-session] [--json]")}`,
|
|
79056
79540
|
"",
|
|
79057
79541
|
"Operator session debugging:",
|
|
79058
|
-
" - `list` shows current tmux runner sessions, newest admitted turn first when known",
|
|
79542
|
+
" - `list` shows current tmux runner sessions, newest admitted turn first when known, plus stored sessionId/state when available",
|
|
79059
79543
|
" - `inspect` captures one snapshot from a named tmux session",
|
|
79060
79544
|
" - `watch --latest` follows the session that most recently admitted a new prompt",
|
|
79061
79545
|
" - `watch --next` waits for the next newly admitted prompt, then follows that session",
|
|
@@ -79255,21 +79739,29 @@ async function runListCli() {
|
|
|
79255
79739
|
console.log([
|
|
79256
79740
|
renderCliCommand("runner list"),
|
|
79257
79741
|
"",
|
|
79258
|
-
...sessions.map(
|
|
79259
|
-
if (!session.entry) {
|
|
79260
|
-
return `- ${session.sessionName}`;
|
|
79261
|
-
}
|
|
79262
|
-
return [
|
|
79263
|
-
`- ${session.sessionName}`,
|
|
79264
|
-
` agent: ${session.entry.agentId}`,
|
|
79265
|
-
` sessionKey: ${session.entry.sessionKey}`,
|
|
79266
|
-
` lastAdmittedPromptAt: ${formatTimestamp(session.entry.lastAdmittedPromptAt)}`
|
|
79267
|
-
].join(`
|
|
79268
|
-
`);
|
|
79269
|
-
})
|
|
79742
|
+
...sessions.map(renderRunnerListSession)
|
|
79270
79743
|
].join(`
|
|
79271
79744
|
`));
|
|
79272
79745
|
}
|
|
79746
|
+
function renderRunnerListSession(session) {
|
|
79747
|
+
if (!session.entry) {
|
|
79748
|
+
return [
|
|
79749
|
+
`- sessionName: ${session.sessionName}`,
|
|
79750
|
+
" sessionId: none",
|
|
79751
|
+
" state: unmanaged"
|
|
79752
|
+
].join(`
|
|
79753
|
+
`);
|
|
79754
|
+
}
|
|
79755
|
+
return [
|
|
79756
|
+
`- sessionName: ${session.sessionName}`,
|
|
79757
|
+
` agent: ${session.entry.agentId}`,
|
|
79758
|
+
` sessionKey: ${session.entry.sessionKey}`,
|
|
79759
|
+
` sessionId: ${session.entry.sessionId?.trim() || "none"}`,
|
|
79760
|
+
` state: ${session.entry.runtime?.state ?? "no-runtime"}`,
|
|
79761
|
+
` lastAdmittedPromptAt: ${formatTimestamp(session.entry.lastAdmittedPromptAt)}`
|
|
79762
|
+
].join(`
|
|
79763
|
+
`);
|
|
79764
|
+
}
|
|
79273
79765
|
async function runInspectCli(args) {
|
|
79274
79766
|
const options = parseInspectCommand(args);
|
|
79275
79767
|
const { tmux } = await loadRunnerContext();
|
|
@@ -80377,6 +80869,40 @@ class ProcessedEventsStore {
|
|
|
80377
80869
|
|
|
80378
80870
|
// src/control/runtime-supervisor.ts
|
|
80379
80871
|
var SERVICE_START_TIMEOUT_MS = 8000;
|
|
80872
|
+
function buildChannelOwnerAlertKey(params) {
|
|
80873
|
+
return `${params.runtimeId}:${params.channel}:${params.botId}`;
|
|
80874
|
+
}
|
|
80875
|
+
function formatElapsedDuration(elapsedMs) {
|
|
80876
|
+
const totalMinutes = Math.max(1, Math.floor(elapsedMs / 60000));
|
|
80877
|
+
if (totalMinutes < 60) {
|
|
80878
|
+
return `${totalMinutes} minute${totalMinutes === 1 ? "" : "s"}`;
|
|
80879
|
+
}
|
|
80880
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
80881
|
+
const minutes = totalMinutes % 60;
|
|
80882
|
+
if (minutes === 0) {
|
|
80883
|
+
return `${hours} hour${hours === 1 ? "" : "s"}`;
|
|
80884
|
+
}
|
|
80885
|
+
return `${hours} hour${hours === 1 ? "" : "s"} ${minutes} minute${minutes === 1 ? "" : "s"}`;
|
|
80886
|
+
}
|
|
80887
|
+
function renderChannelOwnerAlertMessage(params) {
|
|
80888
|
+
const elapsed = formatElapsedDuration(params.elapsedMs);
|
|
80889
|
+
const statusLine = params.incidentState === "failed" ? `status: ${params.channel} channel has remained failed for ${elapsed}` : params.incidentState === "still-failed" ? `status: ${params.channel} channel is still failing after ${elapsed}` : `status: ${params.channel} channel recovered after ${elapsed}`;
|
|
80890
|
+
return [
|
|
80891
|
+
"clisbot channel alert",
|
|
80892
|
+
"",
|
|
80893
|
+
statusLine,
|
|
80894
|
+
`channel: ${params.channel}/${params.botId}`,
|
|
80895
|
+
...params.summary ? [`summary: ${params.summary}`] : [],
|
|
80896
|
+
...params.detail ? [`detail: ${params.detail}`] : [],
|
|
80897
|
+
...params.incidentState === "resolved" ? [
|
|
80898
|
+
"note: the channel recovered without requiring a runtime restart"
|
|
80899
|
+
] : [
|
|
80900
|
+
"note: the runtime process is still alive; clisbot is continuing automatic channel-level recovery attempts",
|
|
80901
|
+
`action: inspect ${renderCliCommand("logs", { inline: true })} and fix the channel-level fault or conflicting poller`
|
|
80902
|
+
]
|
|
80903
|
+
].join(`
|
|
80904
|
+
`);
|
|
80905
|
+
}
|
|
80380
80906
|
|
|
80381
80907
|
class RuntimeSupervisor {
|
|
80382
80908
|
configPath;
|
|
@@ -80387,6 +80913,7 @@ class RuntimeSupervisor {
|
|
|
80387
80913
|
reloadRequested = false;
|
|
80388
80914
|
configWatchDebounceMs = 250;
|
|
80389
80915
|
nextRuntimeId = 1;
|
|
80916
|
+
channelOwnerAlertIncidents = new Map;
|
|
80390
80917
|
dependencies;
|
|
80391
80918
|
constructor(configPath, dependencies) {
|
|
80392
80919
|
this.configPath = configPath;
|
|
@@ -80479,6 +81006,7 @@ class RuntimeSupervisor {
|
|
|
80479
81006
|
});
|
|
80480
81007
|
this.activeRuntime = nextRuntime;
|
|
80481
81008
|
if (previousRuntime) {
|
|
81009
|
+
this.clearChannelOwnerAlertsForRuntime(previousRuntime.id);
|
|
80482
81010
|
for (const service of previousRuntime.channelServices) {
|
|
80483
81011
|
await service.service.stop();
|
|
80484
81012
|
}
|
|
@@ -80517,6 +81045,7 @@ class RuntimeSupervisor {
|
|
|
80517
81045
|
this.activeRuntime = previousRuntime;
|
|
80518
81046
|
}
|
|
80519
81047
|
if (nextRuntime && nextRuntime !== this.activeRuntime) {
|
|
81048
|
+
this.clearChannelOwnerAlertsForRuntime(nextRuntime.id);
|
|
80520
81049
|
for (const service of nextRuntime.channelServices) {
|
|
80521
81050
|
await service.service.stop();
|
|
80522
81051
|
}
|
|
@@ -80640,11 +81169,18 @@ class RuntimeSupervisor {
|
|
|
80640
81169
|
return channelServices.filter((entry) => entry.channel === channel).map((entry) => entry.service.getRuntimeIdentity?.()).filter((identity) => identity != null);
|
|
80641
81170
|
}
|
|
80642
81171
|
async reportChannelLifecycle(params) {
|
|
80643
|
-
|
|
81172
|
+
const activeRuntime = this.activeRuntime;
|
|
81173
|
+
if (activeRuntime?.id !== params.runtimeId) {
|
|
80644
81174
|
return;
|
|
80645
81175
|
}
|
|
80646
81176
|
const instances = this.getChannelInstances(params.channelServices, params.plugin.id);
|
|
81177
|
+
const incidentKey = buildChannelOwnerAlertKey({
|
|
81178
|
+
runtimeId: params.runtimeId,
|
|
81179
|
+
channel: params.plugin.id,
|
|
81180
|
+
botId: params.botId
|
|
81181
|
+
});
|
|
80647
81182
|
if (params.event.connection === "active") {
|
|
81183
|
+
await this.clearChannelOwnerAlert(incidentKey, params.event);
|
|
80648
81184
|
await this.dependencies.runtimeHealthStore.setChannel({
|
|
80649
81185
|
channel: params.plugin.id,
|
|
80650
81186
|
connection: "active",
|
|
@@ -80667,6 +81203,14 @@ class RuntimeSupervisor {
|
|
|
80667
81203
|
],
|
|
80668
81204
|
instances
|
|
80669
81205
|
});
|
|
81206
|
+
this.scheduleChannelOwnerAlert({
|
|
81207
|
+
key: incidentKey,
|
|
81208
|
+
runtimeId: params.runtimeId,
|
|
81209
|
+
loadedConfig: activeRuntime.loadedConfig,
|
|
81210
|
+
pluginId: params.plugin.id,
|
|
81211
|
+
botId: params.botId,
|
|
81212
|
+
event: params.event
|
|
81213
|
+
});
|
|
80670
81214
|
}
|
|
80671
81215
|
async reconcileConfigWatcher(loadedConfig) {
|
|
80672
81216
|
const configReload = loadedConfig.raw.control.configReload;
|
|
@@ -80712,12 +81256,151 @@ class RuntimeSupervisor {
|
|
|
80712
81256
|
if (!this.activeRuntime) {
|
|
80713
81257
|
return;
|
|
80714
81258
|
}
|
|
81259
|
+
this.clearChannelOwnerAlertsForRuntime(this.activeRuntime.id);
|
|
80715
81260
|
for (const service of this.activeRuntime.channelServices) {
|
|
80716
81261
|
await service.service.stop();
|
|
80717
81262
|
}
|
|
80718
81263
|
await this.activeRuntime.agentService.stop();
|
|
80719
81264
|
this.activeRuntime = undefined;
|
|
80720
81265
|
}
|
|
81266
|
+
scheduleChannelOwnerAlert(params) {
|
|
81267
|
+
const delayMs = params.event.ownerAlertAfterMs;
|
|
81268
|
+
if (!delayMs || delayMs <= 0 || !params.loadedConfig.raw.control.runtimeMonitor.ownerAlerts.enabled) {
|
|
81269
|
+
return;
|
|
81270
|
+
}
|
|
81271
|
+
const repeatAlertEveryMs = params.event.ownerAlertRepeatMs ?? params.loadedConfig.raw.control.runtimeMonitor.ownerAlerts.minIntervalMinutes * 60000;
|
|
81272
|
+
const existingIncident = this.channelOwnerAlertIncidents.get(params.key);
|
|
81273
|
+
const incident = existingIncident ?? {
|
|
81274
|
+
runtimeId: params.runtimeId,
|
|
81275
|
+
channel: params.pluginId,
|
|
81276
|
+
botId: params.botId,
|
|
81277
|
+
repeatAlertEveryMs: Math.max(1, repeatAlertEveryMs),
|
|
81278
|
+
startedAtMs: Date.now(),
|
|
81279
|
+
deliveredAlerts: 0
|
|
81280
|
+
};
|
|
81281
|
+
incident.channel = params.pluginId;
|
|
81282
|
+
incident.botId = params.botId;
|
|
81283
|
+
incident.summary = params.event.summary;
|
|
81284
|
+
incident.detail = params.event.detail;
|
|
81285
|
+
incident.repeatAlertEveryMs = Math.max(1, repeatAlertEveryMs);
|
|
81286
|
+
this.channelOwnerAlertIncidents.set(params.key, incident);
|
|
81287
|
+
if (incident.timer) {
|
|
81288
|
+
return;
|
|
81289
|
+
}
|
|
81290
|
+
this.scheduleNextChannelOwnerAlert({
|
|
81291
|
+
key: params.key,
|
|
81292
|
+
runtimeId: params.runtimeId,
|
|
81293
|
+
channel: params.pluginId,
|
|
81294
|
+
botId: params.botId,
|
|
81295
|
+
delayMs: existingIncident ? incident.repeatAlertEveryMs : delayMs
|
|
81296
|
+
});
|
|
81297
|
+
}
|
|
81298
|
+
scheduleNextChannelOwnerAlert(params) {
|
|
81299
|
+
const incident = this.channelOwnerAlertIncidents.get(params.key);
|
|
81300
|
+
if (!incident || incident.runtimeId !== params.runtimeId) {
|
|
81301
|
+
return;
|
|
81302
|
+
}
|
|
81303
|
+
incident.timer = setTimeout(() => {
|
|
81304
|
+
this.fireChannelOwnerAlert({
|
|
81305
|
+
key: params.key,
|
|
81306
|
+
runtimeId: params.runtimeId,
|
|
81307
|
+
channel: params.channel,
|
|
81308
|
+
botId: params.botId
|
|
81309
|
+
});
|
|
81310
|
+
}, params.delayMs);
|
|
81311
|
+
incident.timer.unref?.();
|
|
81312
|
+
this.channelOwnerAlertIncidents.set(params.key, incident);
|
|
81313
|
+
}
|
|
81314
|
+
async fireChannelOwnerAlert(params) {
|
|
81315
|
+
const incident = this.channelOwnerAlertIncidents.get(params.key);
|
|
81316
|
+
if (!incident || incident.runtimeId !== params.runtimeId) {
|
|
81317
|
+
return;
|
|
81318
|
+
}
|
|
81319
|
+
incident.timer = undefined;
|
|
81320
|
+
const activeRuntime = this.activeRuntime;
|
|
81321
|
+
if (activeRuntime?.id !== params.runtimeId) {
|
|
81322
|
+
return;
|
|
81323
|
+
}
|
|
81324
|
+
try {
|
|
81325
|
+
const message = renderChannelOwnerAlertMessage({
|
|
81326
|
+
channel: params.channel,
|
|
81327
|
+
botId: params.botId,
|
|
81328
|
+
incidentState: incident.deliveredAlerts === 0 ? "failed" : "still-failed",
|
|
81329
|
+
elapsedMs: Date.now() - incident.startedAtMs,
|
|
81330
|
+
summary: incident.summary,
|
|
81331
|
+
detail: incident.detail
|
|
81332
|
+
});
|
|
81333
|
+
const result = await sendOwnerAlert({
|
|
81334
|
+
loadedConfig: activeRuntime.loadedConfig,
|
|
81335
|
+
message,
|
|
81336
|
+
listChannelPlugins: this.dependencies.listChannelPlugins
|
|
81337
|
+
});
|
|
81338
|
+
if (result.delivered.length > 0) {
|
|
81339
|
+
incident.deliveredAlerts += 1;
|
|
81340
|
+
}
|
|
81341
|
+
this.channelOwnerAlertIncidents.set(params.key, incident);
|
|
81342
|
+
if (result.delivered.length === 0 && result.failed.length > 0) {
|
|
81343
|
+
console.error("clisbot channel alert delivery failed", result.failed.map((entry) => `${entry.principal}: ${entry.detail}`).join("; "));
|
|
81344
|
+
}
|
|
81345
|
+
} catch (error) {
|
|
81346
|
+
console.error("clisbot channel alert dispatch failed", error);
|
|
81347
|
+
}
|
|
81348
|
+
const nextIncident = this.channelOwnerAlertIncidents.get(params.key);
|
|
81349
|
+
if (!nextIncident || nextIncident.runtimeId !== params.runtimeId || nextIncident.timer || nextIncident.deliveredAlerts >= 2) {
|
|
81350
|
+
return;
|
|
81351
|
+
}
|
|
81352
|
+
this.scheduleNextChannelOwnerAlert({
|
|
81353
|
+
key: params.key,
|
|
81354
|
+
runtimeId: params.runtimeId,
|
|
81355
|
+
channel: params.channel,
|
|
81356
|
+
botId: params.botId,
|
|
81357
|
+
delayMs: nextIncident.repeatAlertEveryMs
|
|
81358
|
+
});
|
|
81359
|
+
}
|
|
81360
|
+
async clearChannelOwnerAlert(key, activeEvent) {
|
|
81361
|
+
const incident = this.channelOwnerAlertIncidents.get(key);
|
|
81362
|
+
if (!incident) {
|
|
81363
|
+
return;
|
|
81364
|
+
}
|
|
81365
|
+
if (incident.timer) {
|
|
81366
|
+
clearTimeout(incident.timer);
|
|
81367
|
+
}
|
|
81368
|
+
this.channelOwnerAlertIncidents.delete(key);
|
|
81369
|
+
if (incident.deliveredAlerts === 0) {
|
|
81370
|
+
return;
|
|
81371
|
+
}
|
|
81372
|
+
const activeRuntime = this.activeRuntime;
|
|
81373
|
+
if (!activeRuntime || activeRuntime.id !== incident.runtimeId) {
|
|
81374
|
+
return;
|
|
81375
|
+
}
|
|
81376
|
+
try {
|
|
81377
|
+
await sendOwnerAlert({
|
|
81378
|
+
loadedConfig: activeRuntime.loadedConfig,
|
|
81379
|
+
message: renderChannelOwnerAlertMessage({
|
|
81380
|
+
channel: incident.channel,
|
|
81381
|
+
botId: incident.botId,
|
|
81382
|
+
incidentState: "resolved",
|
|
81383
|
+
elapsedMs: Date.now() - incident.startedAtMs,
|
|
81384
|
+
summary: activeEvent?.summary ?? "Channel recovered.",
|
|
81385
|
+
detail: activeEvent?.detail
|
|
81386
|
+
}),
|
|
81387
|
+
listChannelPlugins: this.dependencies.listChannelPlugins
|
|
81388
|
+
});
|
|
81389
|
+
} catch (error) {
|
|
81390
|
+
console.error("clisbot channel recovery alert dispatch failed", error);
|
|
81391
|
+
}
|
|
81392
|
+
}
|
|
81393
|
+
clearChannelOwnerAlertsForRuntime(runtimeId) {
|
|
81394
|
+
for (const [key, incident] of this.channelOwnerAlertIncidents.entries()) {
|
|
81395
|
+
if (incident.runtimeId !== runtimeId) {
|
|
81396
|
+
continue;
|
|
81397
|
+
}
|
|
81398
|
+
if (incident.timer) {
|
|
81399
|
+
clearTimeout(incident.timer);
|
|
81400
|
+
}
|
|
81401
|
+
this.channelOwnerAlertIncidents.delete(key);
|
|
81402
|
+
}
|
|
81403
|
+
}
|
|
80721
81404
|
}
|
|
80722
81405
|
async function withStartupTimeout(name, start2) {
|
|
80723
81406
|
let timer;
|
|
@@ -80738,6 +81421,9 @@ async function withStartupTimeout(name, start2) {
|
|
|
80738
81421
|
}
|
|
80739
81422
|
|
|
80740
81423
|
// src/control/runtime-management-cli.ts
|
|
81424
|
+
function getOperatorConfigPath() {
|
|
81425
|
+
return expandHomePath(process.env.CLISBOT_CONFIG_PATH || DEFAULT_CONFIG_PATH);
|
|
81426
|
+
}
|
|
80741
81427
|
function getPrimaryWorkspacePath2(summary) {
|
|
80742
81428
|
const preferredAgentId = summary.channelSummaries.find((channel) => channel.enabled)?.defaultAgentId ?? "default";
|
|
80743
81429
|
return summary.agentSummaries.find((agent) => agent.id === preferredAgentId)?.workspacePath ?? summary.agentSummaries[0]?.workspacePath;
|
|
@@ -80801,7 +81487,9 @@ function registerProcessHandlers(runtimeSupervisor, shutdown) {
|
|
|
80801
81487
|
});
|
|
80802
81488
|
}
|
|
80803
81489
|
async function printStatusSummary() {
|
|
80804
|
-
const runtimeStatus = await getRuntimeStatus(
|
|
81490
|
+
const runtimeStatus = await getRuntimeStatus({
|
|
81491
|
+
configPath: getOperatorConfigPath()
|
|
81492
|
+
});
|
|
80805
81493
|
console.log(`version: ${getClisbotVersion()}`);
|
|
80806
81494
|
console.log(`running: ${runtimeStatus.running ? "yes" : "no"}`);
|
|
80807
81495
|
if (runtimeStatus.pid) {
|
|
@@ -80860,7 +81548,9 @@ async function printStatusSummary() {
|
|
|
80860
81548
|
}
|
|
80861
81549
|
async function printDiagnosticsAfterLogTail() {
|
|
80862
81550
|
try {
|
|
80863
|
-
const runtimeStatus = await getRuntimeStatus(
|
|
81551
|
+
const runtimeStatus = await getRuntimeStatus({
|
|
81552
|
+
configPath: getOperatorConfigPath()
|
|
81553
|
+
});
|
|
80864
81554
|
const summary = await getRuntimeOperatorSummary({
|
|
80865
81555
|
configPath: runtimeStatus.configPath,
|
|
80866
81556
|
runtimeRunning: runtimeStatus.running
|
|
@@ -80948,6 +81638,7 @@ async function printCliError(error) {
|
|
|
80948
81638
|
}
|
|
80949
81639
|
async function stop(hard = false) {
|
|
80950
81640
|
const result = await stopDetachedRuntime({
|
|
81641
|
+
configPath: getOperatorConfigPath(),
|
|
80951
81642
|
hard
|
|
80952
81643
|
});
|
|
80953
81644
|
if (!result.stopped && !hard) {
|
|
@@ -80968,6 +81659,7 @@ async function stop(hard = false) {
|
|
|
80968
81659
|
}
|
|
80969
81660
|
async function restart() {
|
|
80970
81661
|
await stopDetachedRuntime({
|
|
81662
|
+
configPath: getOperatorConfigPath(),
|
|
80971
81663
|
hard: false
|
|
80972
81664
|
});
|
|
80973
81665
|
}
|
|
@@ -80975,7 +81667,11 @@ async function status() {
|
|
|
80975
81667
|
await printStatusSummary();
|
|
80976
81668
|
}
|
|
80977
81669
|
async function logs(lines) {
|
|
81670
|
+
const runtimeStatus = await getRuntimeStatus({
|
|
81671
|
+
configPath: getOperatorConfigPath()
|
|
81672
|
+
});
|
|
80978
81673
|
const result = await readRuntimeLog({
|
|
81674
|
+
logPath: runtimeStatus.logPath,
|
|
80979
81675
|
lines
|
|
80980
81676
|
});
|
|
80981
81677
|
if (!result.text) {
|