clisbot 0.1.22 → 0.1.26
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 +2 -0
- package/config/clisbot.json.template +11 -7
- package/dist/main.js +460 -149
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -111,6 +111,8 @@ bun install
|
|
|
111
111
|
bun run start --cli codex --bot-type personal --telegram-bot-token <your-telegram-bot-token> --persist
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
Repo-local `bun run start|stop|restart|status|logs|init|pairing` is pinned by `.env` to `CLISBOT_HOME=~/.clisbot-dev`, so local testing does not accidentally reuse your main `~/.clisbot` runtime.
|
|
115
|
+
|
|
114
116
|
First conversation path:
|
|
115
117
|
|
|
116
118
|
- send a DM to the bot in Slack or Telegram
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"session": {
|
|
10
10
|
"mainKey": "main",
|
|
11
|
-
"dmScope": "
|
|
11
|
+
"dmScope": "per-channel-peer",
|
|
12
12
|
"identityLinks": {},
|
|
13
13
|
"storePath": "~/.clisbot/state/sessions.json"
|
|
14
14
|
},
|
|
@@ -154,11 +154,15 @@
|
|
|
154
154
|
"maxActiveLoops": 10,
|
|
155
155
|
"defaultTimezone": "UTC"
|
|
156
156
|
},
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
"
|
|
157
|
+
"runtimeMonitor": {
|
|
158
|
+
"restartBackoff": {
|
|
159
|
+
"fastRetry": {
|
|
160
|
+
"delaySeconds": 10,
|
|
161
|
+
"maxRestarts": 3
|
|
162
|
+
},
|
|
163
|
+
"stages": [
|
|
164
|
+
{
|
|
165
|
+
"delayMinutes": 15,
|
|
162
166
|
"maxRestarts": 4
|
|
163
167
|
},
|
|
164
168
|
{
|
|
@@ -290,4 +294,4 @@
|
|
|
290
294
|
}
|
|
291
295
|
}
|
|
292
296
|
}
|
|
293
|
-
}
|
|
297
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -59932,7 +59932,7 @@ var sessionDmScopeSchema = exports_external.enum([
|
|
|
59932
59932
|
]);
|
|
59933
59933
|
var sessionConfigSchema = exports_external.object({
|
|
59934
59934
|
mainKey: exports_external.string().min(1).default("main"),
|
|
59935
|
-
dmScope: sessionDmScopeSchema.default("
|
|
59935
|
+
dmScope: sessionDmScopeSchema.default("per-channel-peer"),
|
|
59936
59936
|
identityLinks: exports_external.record(exports_external.string(), exports_external.array(exports_external.string())).default({}),
|
|
59937
59937
|
storePath: exports_external.string().default("~/.clisbot/state/sessions.json")
|
|
59938
59938
|
});
|
|
@@ -60250,7 +60250,15 @@ var controlRuntimeMonitorRestartStageSchema = exports_external.object({
|
|
|
60250
60250
|
delayMinutes: exports_external.number().int().positive().default(15),
|
|
60251
60251
|
maxRestarts: exports_external.number().int().positive().default(4)
|
|
60252
60252
|
});
|
|
60253
|
+
var controlRuntimeMonitorFastRetrySchema = exports_external.object({
|
|
60254
|
+
delaySeconds: exports_external.number().int().positive().default(10),
|
|
60255
|
+
maxRestarts: exports_external.number().int().min(0).default(3)
|
|
60256
|
+
});
|
|
60253
60257
|
var controlRuntimeMonitorRestartBackoffSchema = exports_external.object({
|
|
60258
|
+
fastRetry: controlRuntimeMonitorFastRetrySchema.default({
|
|
60259
|
+
delaySeconds: 10,
|
|
60260
|
+
maxRestarts: 3
|
|
60261
|
+
}),
|
|
60254
60262
|
stages: exports_external.array(controlRuntimeMonitorRestartStageSchema).min(1).default([
|
|
60255
60263
|
{
|
|
60256
60264
|
delayMinutes: 15,
|
|
@@ -60268,6 +60276,10 @@ var controlRuntimeMonitorOwnerAlertsSchema = exports_external.object({
|
|
|
60268
60276
|
});
|
|
60269
60277
|
var controlRuntimeMonitorSchema = exports_external.object({
|
|
60270
60278
|
restartBackoff: controlRuntimeMonitorRestartBackoffSchema.default({
|
|
60279
|
+
fastRetry: {
|
|
60280
|
+
delaySeconds: 10,
|
|
60281
|
+
maxRestarts: 3
|
|
60282
|
+
},
|
|
60271
60283
|
stages: [
|
|
60272
60284
|
{
|
|
60273
60285
|
delayMinutes: 15,
|
|
@@ -60328,7 +60340,7 @@ var clisbotConfigSchema = exports_external.object({
|
|
|
60328
60340
|
}),
|
|
60329
60341
|
session: sessionConfigSchema.default({
|
|
60330
60342
|
mainKey: "main",
|
|
60331
|
-
dmScope: "
|
|
60343
|
+
dmScope: "per-channel-peer",
|
|
60332
60344
|
identityLinks: {},
|
|
60333
60345
|
storePath: "~/.clisbot/state/sessions.json"
|
|
60334
60346
|
}),
|
|
@@ -60367,6 +60379,10 @@ var clisbotConfigSchema = exports_external.object({
|
|
|
60367
60379
|
},
|
|
60368
60380
|
runtimeMonitor: {
|
|
60369
60381
|
restartBackoff: {
|
|
60382
|
+
fastRetry: {
|
|
60383
|
+
delaySeconds: 10,
|
|
60384
|
+
maxRestarts: 3
|
|
60385
|
+
},
|
|
60370
60386
|
stages: [
|
|
60371
60387
|
{
|
|
60372
60388
|
delayMinutes: 15,
|
|
@@ -60581,7 +60597,7 @@ function renderDefaultConfigTemplate(options = {}) {
|
|
|
60581
60597
|
},
|
|
60582
60598
|
session: {
|
|
60583
60599
|
mainKey: "main",
|
|
60584
|
-
dmScope: "
|
|
60600
|
+
dmScope: "per-channel-peer",
|
|
60585
60601
|
identityLinks: {},
|
|
60586
60602
|
storePath: sessionStorePath
|
|
60587
60603
|
},
|
|
@@ -60691,6 +60707,10 @@ function renderDefaultConfigTemplate(options = {}) {
|
|
|
60691
60707
|
},
|
|
60692
60708
|
runtimeMonitor: {
|
|
60693
60709
|
restartBackoff: {
|
|
60710
|
+
fastRetry: {
|
|
60711
|
+
delaySeconds: 10,
|
|
60712
|
+
maxRestarts: 3
|
|
60713
|
+
},
|
|
60694
60714
|
stages: [
|
|
60695
60715
|
{
|
|
60696
60716
|
delayMinutes: 15,
|
|
@@ -61731,8 +61751,8 @@ class RuntimeHealthStore {
|
|
|
61731
61751
|
|
|
61732
61752
|
// src/control/runtime-process.ts
|
|
61733
61753
|
import { execFileSync, spawn as spawn3 } from "node:child_process";
|
|
61734
|
-
import { closeSync, existsSync as existsSync7, openSync, readFileSync as readFileSync3, rmSync as rmSync3, statSync as
|
|
61735
|
-
import { dirname as dirname13 } from "node:path";
|
|
61754
|
+
import { closeSync, existsSync as existsSync7, openSync, readFileSync as readFileSync3, rmSync as rmSync3, statSync as statSync4 } from "node:fs";
|
|
61755
|
+
import { dirname as dirname13, join as join11 } from "node:path";
|
|
61736
61756
|
import { kill as kill2 } from "node:process";
|
|
61737
61757
|
|
|
61738
61758
|
// src/control/clisbot-wrapper.ts
|
|
@@ -62719,43 +62739,151 @@ function hasActiveRuntime(entry) {
|
|
|
62719
62739
|
}
|
|
62720
62740
|
|
|
62721
62741
|
// src/channels/agent-prompt.ts
|
|
62722
|
-
|
|
62723
|
-
|
|
62724
|
-
|
|
62725
|
-
|
|
62726
|
-
|
|
62727
|
-
|
|
62728
|
-
|
|
62742
|
+
var CONFIGURATION_GUIDANCE = "When the user asks to change clisbot configuration, use clisbot CLI commands; see `clisbot --help`, `clisbot channels --help`, or `clisbot auth --help` for details.";
|
|
62743
|
+
var BASE_TEMPLATE = `<system>
|
|
62744
|
+
[{{timestamp}}] {{identity_summary}}
|
|
62745
|
+
|
|
62746
|
+
You are operating inside clisbot.
|
|
62747
|
+
{{delivery_intro}}
|
|
62748
|
+
{{reply_command}}
|
|
62749
|
+
{{reply_rules}}
|
|
62750
|
+
${CONFIGURATION_GUIDANCE}{{protected_control_suffix}}
|
|
62751
|
+
</system>
|
|
62752
|
+
|
|
62753
|
+
<user>
|
|
62754
|
+
{{message_body}}
|
|
62755
|
+
</user>`;
|
|
62756
|
+
var STEERING_TEMPLATE = `<system>
|
|
62757
|
+
A new user message arrived while you were still working.
|
|
62758
|
+
Adjust your current work if needed and continue.{{protected_control_suffix}}
|
|
62729
62759
|
</system>
|
|
62730
62760
|
|
|
62731
62761
|
<user>
|
|
62732
|
-
|
|
62762
|
+
{{message_body}}
|
|
62733
62763
|
</user>`;
|
|
62764
|
+
var DELIVERY_INTRO = "To send a user-visible {{progress_phrase}}final reply, use the following CLI command:";
|
|
62765
|
+
var DELIVERY_INTRO_CAPTURE_PANE = "channel auto-delivery remains enabled for this conversation; do not send user-facing progress updates or the final response with clisbot message send";
|
|
62766
|
+
var REPLY_COMMAND = `{{reply_command_base}}
|
|
62767
|
+
--final{{progress_flag_suffix}} \\
|
|
62768
|
+
--message "$(cat <<\\__CLISBOT_MESSAGE__
|
|
62769
|
+
<user-facing reply>
|
|
62770
|
+
__CLISBOT_MESSAGE__
|
|
62771
|
+
)" \\
|
|
62772
|
+
[--media /absolute/path/to/file]`;
|
|
62773
|
+
var REPLY_RULES = `When replying to the user:
|
|
62774
|
+
- put the user-facing message inside the --message body of that command
|
|
62775
|
+
{{progress_rules_block}}- {{final_rule_line}}`;
|
|
62776
|
+
var PROGRESS_PHRASE = "progress update or ";
|
|
62777
|
+
var EMPTY_PROGRESS_PHRASE = "";
|
|
62778
|
+
var PROGRESS_FLAG_SUFFIX = "|progress";
|
|
62779
|
+
var EMPTY_PROGRESS_FLAG_SUFFIX = "";
|
|
62780
|
+
var PROGRESS_RULES_BLOCK = `- use that command to send progress updates and the final reply back to the conversation
|
|
62781
|
+
- send at most {{max_progress_messages}} progress updates
|
|
62782
|
+
- keep progress updates short and meaningful
|
|
62783
|
+
- do not send progress updates for trivial internal steps
|
|
62784
|
+
`;
|
|
62785
|
+
var FINAL_ONLY_RULES_BLOCK = `- use that command only for the final user-facing reply
|
|
62786
|
+
- do not send user-facing progress updates for this conversation
|
|
62787
|
+
`;
|
|
62788
|
+
var FINAL_RULE_REQUIRED = "send exactly 1 final user-facing response";
|
|
62789
|
+
var FINAL_RULE_OPTIONAL = "final response is optional";
|
|
62790
|
+
var EMPTY_REPLY_COMMAND = "";
|
|
62791
|
+
var EMPTY_REPLY_RULES = "";
|
|
62792
|
+
var SLACK_REPLY_COMMAND_BASE = `{{command}} message send \\
|
|
62793
|
+
--channel slack \\
|
|
62794
|
+
{{account_clause}} --target channel:{{channel_id}} \\
|
|
62795
|
+
{{thread_clause}}`;
|
|
62796
|
+
var TELEGRAM_REPLY_COMMAND_BASE = `{{command}} message send \\
|
|
62797
|
+
--channel telegram \\
|
|
62798
|
+
{{account_clause}} --target {{chat_id}} \\
|
|
62799
|
+
{{thread_clause}}`;
|
|
62800
|
+
var ACCOUNT_CLAUSE = " --account {{account_id}} \\\n";
|
|
62801
|
+
var EMPTY_ACCOUNT_CLAUSE = "";
|
|
62802
|
+
var SLACK_THREAD_CLAUSE = " --thread-id {{thread_ts}} \\\n";
|
|
62803
|
+
var TELEGRAM_THREAD_CLAUSE = " --thread-id {{topic_id}} \\\n";
|
|
62804
|
+
var EMPTY_THREAD_CLAUSE = "";
|
|
62805
|
+
function buildAgentPromptText(params) {
|
|
62806
|
+
return buildChannelPromptText({
|
|
62807
|
+
...params,
|
|
62808
|
+
mode: "message"
|
|
62809
|
+
});
|
|
62734
62810
|
}
|
|
62735
|
-
function
|
|
62736
|
-
|
|
62737
|
-
|
|
62738
|
-
|
|
62739
|
-
|
|
62740
|
-
|
|
62741
|
-
|
|
62742
|
-
|
|
62743
|
-
|
|
62744
|
-
|
|
62745
|
-
|
|
62746
|
-
|
|
62747
|
-
|
|
62811
|
+
function buildSteeringPromptText(params) {
|
|
62812
|
+
return buildChannelPromptText({
|
|
62813
|
+
text: params.text,
|
|
62814
|
+
mode: "steer",
|
|
62815
|
+
protectedControlMutationRule: params.protectedControlMutationRule
|
|
62816
|
+
});
|
|
62817
|
+
}
|
|
62818
|
+
function buildChannelPromptText(params) {
|
|
62819
|
+
if (params.mode === "message" && !params.config?.enabled) {
|
|
62820
|
+
return params.text;
|
|
62821
|
+
}
|
|
62822
|
+
if (params.mode === "steer") {
|
|
62823
|
+
return renderTemplate(STEERING_TEMPLATE, {
|
|
62824
|
+
message_body: params.text,
|
|
62825
|
+
protected_control_suffix: renderProtectedControlSuffix(params.protectedControlMutationRule)
|
|
62748
62826
|
});
|
|
62749
|
-
lines.push(replyCommand, "When replying to the user:", "- put the user-facing message inside the --message body of that command", progressAllowed ? "- use that command to send progress updates and the final reply back to the conversation" : "- use that command only for the final user-facing reply", ...progressAllowed ? [`- send at most ${params.config.maxProgressMessages} progress updates`] : ["- do not send user-facing progress updates for this conversation"], params.config.requireFinalResponse ? "- send exactly 1 final user-facing response" : "- final response is optional", ...progressAllowed ? [
|
|
62750
|
-
"- keep progress updates short and meaningful",
|
|
62751
|
-
"- do not send progress updates for trivial internal steps"
|
|
62752
|
-
] : []);
|
|
62753
62827
|
}
|
|
62754
|
-
|
|
62755
|
-
|
|
62828
|
+
const promptParts = renderMessagePromptParts({
|
|
62829
|
+
identity: params.identity,
|
|
62830
|
+
config: params.config,
|
|
62831
|
+
responseMode: params.responseMode,
|
|
62832
|
+
streaming: params.streaming
|
|
62833
|
+
});
|
|
62834
|
+
return renderTemplate(BASE_TEMPLATE, {
|
|
62835
|
+
timestamp: renderPromptTimestamp(),
|
|
62836
|
+
identity_summary: renderIdentitySummary(params.identity),
|
|
62837
|
+
delivery_intro: promptParts.deliveryIntro,
|
|
62838
|
+
reply_command: promptParts.replyCommand,
|
|
62839
|
+
reply_rules: promptParts.replyRules,
|
|
62840
|
+
protected_control_suffix: renderProtectedControlSuffix(params.protectedControlMutationRule),
|
|
62841
|
+
message_body: params.text
|
|
62842
|
+
});
|
|
62843
|
+
}
|
|
62844
|
+
function renderMessagePromptParts(params) {
|
|
62845
|
+
const messageToolMode = (params.responseMode ?? "message-tool") === "message-tool";
|
|
62846
|
+
if (!messageToolMode) {
|
|
62847
|
+
return {
|
|
62848
|
+
deliveryIntro: DELIVERY_INTRO_CAPTURE_PANE,
|
|
62849
|
+
replyCommand: EMPTY_REPLY_COMMAND,
|
|
62850
|
+
replyRules: EMPTY_REPLY_RULES
|
|
62851
|
+
};
|
|
62756
62852
|
}
|
|
62757
|
-
|
|
62758
|
-
|
|
62853
|
+
const allowProgress = (params.streaming ?? "off") === "off";
|
|
62854
|
+
const progressPhrase = allowProgress ? PROGRESS_PHRASE : EMPTY_PROGRESS_PHRASE;
|
|
62855
|
+
const progressFlagSuffix = allowProgress ? PROGRESS_FLAG_SUFFIX : EMPTY_PROGRESS_FLAG_SUFFIX;
|
|
62856
|
+
const progressRulesBlock = allowProgress ? PROGRESS_RULES_BLOCK : FINAL_ONLY_RULES_BLOCK;
|
|
62857
|
+
const finalRuleLine = params.config.requireFinalResponse ? FINAL_RULE_REQUIRED : FINAL_RULE_OPTIONAL;
|
|
62858
|
+
return {
|
|
62859
|
+
deliveryIntro: renderTemplate(DELIVERY_INTRO, {
|
|
62860
|
+
progress_phrase: progressPhrase
|
|
62861
|
+
}),
|
|
62862
|
+
replyCommand: renderTemplate(REPLY_COMMAND, {
|
|
62863
|
+
reply_command_base: buildReplyCommandBase({
|
|
62864
|
+
command: getClisbotPromptCommand(),
|
|
62865
|
+
identity: params.identity
|
|
62866
|
+
}).trimEnd(),
|
|
62867
|
+
progress_flag_suffix: progressFlagSuffix
|
|
62868
|
+
}),
|
|
62869
|
+
replyRules: renderTemplate(REPLY_RULES, {
|
|
62870
|
+
progress_rules_block: renderTemplate(progressRulesBlock, {
|
|
62871
|
+
max_progress_messages: String(params.config.maxProgressMessages)
|
|
62872
|
+
}),
|
|
62873
|
+
final_rule_line: finalRuleLine
|
|
62874
|
+
})
|
|
62875
|
+
};
|
|
62876
|
+
}
|
|
62877
|
+
function renderProtectedControlSuffix(rule) {
|
|
62878
|
+
if (!rule) {
|
|
62879
|
+
return "";
|
|
62880
|
+
}
|
|
62881
|
+
return `
|
|
62882
|
+
|
|
62883
|
+
${rule}`;
|
|
62884
|
+
}
|
|
62885
|
+
function renderTemplate(template, values) {
|
|
62886
|
+
return template.replaceAll(/\{\{([a-zA-Z0-9_]+)\}\}/g, (_, key) => values[key] ?? "");
|
|
62759
62887
|
}
|
|
62760
62888
|
function renderPromptTimestamp() {
|
|
62761
62889
|
const date = new Date;
|
|
@@ -62821,42 +62949,29 @@ function renderNamedValue(label, name, id) {
|
|
|
62821
62949
|
const value = renderLabeledTarget(name, id);
|
|
62822
62950
|
return value ? `${label} ${value}` : "";
|
|
62823
62951
|
}
|
|
62824
|
-
function
|
|
62825
|
-
const lines = [`${params.command} message send \\`];
|
|
62952
|
+
function buildReplyCommandBase(params) {
|
|
62826
62953
|
if (params.identity.platform === "slack") {
|
|
62827
|
-
|
|
62828
|
-
|
|
62829
|
-
|
|
62830
|
-
|
|
62831
|
-
|
|
62832
|
-
|
|
62833
|
-
|
|
62834
|
-
|
|
62835
|
-
|
|
62836
|
-
|
|
62837
|
-
lines.push("<user-facing reply>");
|
|
62838
|
-
lines.push("__CLISBOT_MESSAGE__");
|
|
62839
|
-
lines.push(')" \\');
|
|
62840
|
-
lines.push(" [--media /absolute/path/to/file]");
|
|
62841
|
-
return lines.join(`
|
|
62842
|
-
`);
|
|
62843
|
-
}
|
|
62844
|
-
lines.push(" --channel telegram \\");
|
|
62845
|
-
if (params.identity.accountId) {
|
|
62846
|
-
lines.push(` --account ${params.identity.accountId} \\`);
|
|
62954
|
+
return renderTemplate(SLACK_REPLY_COMMAND_BASE, {
|
|
62955
|
+
command: params.command,
|
|
62956
|
+
account_clause: params.identity.accountId ? renderTemplate(ACCOUNT_CLAUSE, {
|
|
62957
|
+
account_id: params.identity.accountId
|
|
62958
|
+
}) : EMPTY_ACCOUNT_CLAUSE,
|
|
62959
|
+
channel_id: params.identity.channelId ?? "",
|
|
62960
|
+
thread_clause: params.identity.threadTs ? renderTemplate(SLACK_THREAD_CLAUSE, {
|
|
62961
|
+
thread_ts: params.identity.threadTs
|
|
62962
|
+
}) : EMPTY_THREAD_CLAUSE
|
|
62963
|
+
});
|
|
62847
62964
|
}
|
|
62848
|
-
|
|
62849
|
-
|
|
62850
|
-
|
|
62851
|
-
|
|
62852
|
-
|
|
62853
|
-
|
|
62854
|
-
|
|
62855
|
-
|
|
62856
|
-
|
|
62857
|
-
|
|
62858
|
-
return lines.join(`
|
|
62859
|
-
`);
|
|
62965
|
+
return renderTemplate(TELEGRAM_REPLY_COMMAND_BASE, {
|
|
62966
|
+
command: params.command,
|
|
62967
|
+
account_clause: params.identity.accountId ? renderTemplate(ACCOUNT_CLAUSE, {
|
|
62968
|
+
account_id: params.identity.accountId
|
|
62969
|
+
}) : EMPTY_ACCOUNT_CLAUSE,
|
|
62970
|
+
chat_id: params.identity.chatId ?? "",
|
|
62971
|
+
thread_clause: params.identity.topicId ? renderTemplate(TELEGRAM_THREAD_CLAUSE, {
|
|
62972
|
+
topic_id: params.identity.topicId
|
|
62973
|
+
}) : EMPTY_THREAD_CLAUSE
|
|
62974
|
+
});
|
|
62860
62975
|
}
|
|
62861
62976
|
|
|
62862
62977
|
// src/channels/surface-notifications.ts
|
|
@@ -63940,6 +64055,36 @@ function renderInteractionBody(params) {
|
|
|
63940
64055
|
}
|
|
63941
64056
|
return truncateTail(completedBody, params.maxChars);
|
|
63942
64057
|
}
|
|
64058
|
+
function normalizeLeadingStatusLine(line) {
|
|
64059
|
+
return line.trim().replace(/^[_*`\s]+|[_*`\s]+$/g, "");
|
|
64060
|
+
}
|
|
64061
|
+
function startsWithExplicitErrorLabel(body) {
|
|
64062
|
+
const firstLine = body.split(`
|
|
64063
|
+
`).map((line) => normalizeLeadingStatusLine(line)).find((line) => line.length > 0);
|
|
64064
|
+
if (!firstLine) {
|
|
64065
|
+
return false;
|
|
64066
|
+
}
|
|
64067
|
+
return /^(error|failed|failure|denied|forbidden)(?::|\.)/i.test(firstLine);
|
|
64068
|
+
}
|
|
64069
|
+
function shouldInlineErrorPrefix(body) {
|
|
64070
|
+
return !body.includes(`
|
|
64071
|
+
`) && !body.includes("```");
|
|
64072
|
+
}
|
|
64073
|
+
function renderErrorInteractionBody(body, footer) {
|
|
64074
|
+
const trimmedBody = body.trim();
|
|
64075
|
+
if (!trimmedBody) {
|
|
64076
|
+
return footer;
|
|
64077
|
+
}
|
|
64078
|
+
if (startsWithExplicitErrorLabel(trimmedBody)) {
|
|
64079
|
+
return trimmedBody;
|
|
64080
|
+
}
|
|
64081
|
+
if (shouldInlineErrorPrefix(trimmedBody)) {
|
|
64082
|
+
return `Error: ${trimmedBody}`;
|
|
64083
|
+
}
|
|
64084
|
+
return `${trimmedBody}
|
|
64085
|
+
|
|
64086
|
+
${footer}`;
|
|
64087
|
+
}
|
|
63943
64088
|
function renderSlackInteraction(params) {
|
|
63944
64089
|
const body = renderInteractionBody(params);
|
|
63945
64090
|
if (params.status === "queued") {
|
|
@@ -63967,9 +64112,7 @@ _Timed out waiting for more output._` : "_Timed out waiting for visible output._
|
|
|
63967
64112
|
_${note}_` : `_${note}_`;
|
|
63968
64113
|
}
|
|
63969
64114
|
if (params.status === "error") {
|
|
63970
|
-
return body
|
|
63971
|
-
|
|
63972
|
-
_Error._` : "_Error._";
|
|
64115
|
+
return renderErrorInteractionBody(body, "_Error._");
|
|
63973
64116
|
}
|
|
63974
64117
|
return body || "_Completed with no new visible output._";
|
|
63975
64118
|
}
|
|
@@ -64000,9 +64143,7 @@ Timed out waiting for more output.` : "Timed out waiting for visible output.";
|
|
|
64000
64143
|
${note}` : note;
|
|
64001
64144
|
}
|
|
64002
64145
|
if (params.status === "error") {
|
|
64003
|
-
return body
|
|
64004
|
-
|
|
64005
|
-
Error.` : "Error.";
|
|
64146
|
+
return renderErrorInteractionBody(body, "Error.");
|
|
64006
64147
|
}
|
|
64007
64148
|
return body || "Completed with no new visible output.";
|
|
64008
64149
|
}
|
|
@@ -64936,15 +65077,19 @@ class RunnerService {
|
|
|
64936
65077
|
}
|
|
64937
65078
|
|
|
64938
65079
|
// src/agents/run-recovery.ts
|
|
65080
|
+
var MID_RUN_RECOVERY_MAX_ATTEMPTS = 2;
|
|
65081
|
+
var MID_RUN_RECOVERY_CONTINUE_PROMPT = "continue exactly where you left off";
|
|
64939
65082
|
function mergeRunSnapshot(snapshotPrefix, snapshot) {
|
|
64940
65083
|
return appendInteractionText(snapshotPrefix, snapshot);
|
|
64941
65084
|
}
|
|
64942
|
-
function buildRunRecoveryNote(kind) {
|
|
65085
|
+
function buildRunRecoveryNote(kind, params) {
|
|
64943
65086
|
if (kind === "resume-attempt") {
|
|
64944
|
-
|
|
65087
|
+
const attempt = params?.attempt ?? 1;
|
|
65088
|
+
const maxAttempts = params?.maxAttempts ?? MID_RUN_RECOVERY_MAX_ATTEMPTS;
|
|
65089
|
+
return `Runner session was lost. Attempting recovery ${attempt}/${maxAttempts} by reopening the same conversation context.`;
|
|
64945
65090
|
}
|
|
64946
65091
|
if (kind === "resume-success") {
|
|
64947
|
-
return "Recovery succeeded.
|
|
65092
|
+
return "Recovery succeeded. Asking the runner to continue exactly where it left off.";
|
|
64948
65093
|
}
|
|
64949
65094
|
if (kind === "fresh-attempt") {
|
|
64950
65095
|
return "The previous runner session could not be resumed. Opening a fresh runner session 2/2 without replaying your prompt.";
|
|
@@ -65416,10 +65561,13 @@ class SessionService {
|
|
|
65416
65561
|
agentId: run.resolved.agentId,
|
|
65417
65562
|
sessionKey: run.resolved.sessionKey
|
|
65418
65563
|
};
|
|
65564
|
+
const recoveryAttempt = params.recoveryAttempt ?? 1;
|
|
65419
65565
|
const snapshotPrefix = run.latestUpdate.snapshot;
|
|
65420
|
-
const previousFullSnapshot = run.latestUpdate.fullSnapshot;
|
|
65421
65566
|
const detachedAlready = run.latestUpdate.status === "detached";
|
|
65422
|
-
await this.notifyRecoveryStep(run, buildRunRecoveryNote("resume-attempt"
|
|
65567
|
+
await this.notifyRecoveryStep(run, buildRunRecoveryNote("resume-attempt", {
|
|
65568
|
+
attempt: recoveryAttempt,
|
|
65569
|
+
maxAttempts: MID_RUN_RECOVERY_MAX_ATTEMPTS
|
|
65570
|
+
}));
|
|
65423
65571
|
try {
|
|
65424
65572
|
const recovered = await this.runnerSessions.reopenRunContext(target, params.timingContext);
|
|
65425
65573
|
const currentRun = this.activeRuns.get(sessionKey);
|
|
@@ -65438,15 +65586,22 @@ class SessionService {
|
|
|
65438
65586
|
});
|
|
65439
65587
|
await this.notifyRunObservers(currentRun, currentRun.latestUpdate);
|
|
65440
65588
|
this.startRunMonitor(sessionKey, {
|
|
65441
|
-
prompt:
|
|
65442
|
-
initialSnapshot:
|
|
65589
|
+
prompt: MID_RUN_RECOVERY_CONTINUE_PROMPT,
|
|
65590
|
+
initialSnapshot: recovered.initialSnapshot,
|
|
65443
65591
|
startedAt: currentRun.startedAt,
|
|
65444
65592
|
detachedAlready,
|
|
65445
65593
|
timingContext: params.timingContext,
|
|
65446
|
-
snapshotPrefix
|
|
65594
|
+
snapshotPrefix,
|
|
65595
|
+
recoveryAttempt
|
|
65447
65596
|
});
|
|
65448
65597
|
return true;
|
|
65449
|
-
} catch {
|
|
65598
|
+
} catch (reopenError) {
|
|
65599
|
+
if (recoveryAttempt < MID_RUN_RECOVERY_MAX_ATTEMPTS && this.runnerSessions.canRecoverMidRun(reopenError)) {
|
|
65600
|
+
return await this.recoverLostMidRun(sessionKey, {
|
|
65601
|
+
timingContext: params.timingContext,
|
|
65602
|
+
recoveryAttempt: recoveryAttempt + 1
|
|
65603
|
+
}, reopenError);
|
|
65604
|
+
}
|
|
65450
65605
|
const currentRun = this.activeRuns.get(sessionKey);
|
|
65451
65606
|
if (!currentRun) {
|
|
65452
65607
|
return true;
|
|
@@ -65552,7 +65707,10 @@ class SessionService {
|
|
|
65552
65707
|
}
|
|
65553
65708
|
});
|
|
65554
65709
|
} catch (error) {
|
|
65555
|
-
if (await this.recoverLostMidRun(sessionKey, {
|
|
65710
|
+
if (await this.recoverLostMidRun(sessionKey, {
|
|
65711
|
+
timingContext: params.timingContext,
|
|
65712
|
+
recoveryAttempt: (params.recoveryAttempt ?? 0) + 1
|
|
65713
|
+
}, error)) {
|
|
65556
65714
|
return;
|
|
65557
65715
|
}
|
|
65558
65716
|
await this.failActiveRun(sessionKey, await this.runnerSessions.mapRunError(error, run.resolved.sessionName, run.latestUpdate.fullSnapshot));
|
|
@@ -66877,25 +67035,6 @@ function buildChannelObserverId(identity) {
|
|
|
66877
67035
|
identity.topicId ?? ""
|
|
66878
67036
|
].join(":");
|
|
66879
67037
|
}
|
|
66880
|
-
function buildSteeringMessage(text, protectedControlMutationRule) {
|
|
66881
|
-
const systemLines = [
|
|
66882
|
-
"A new user message arrived while you were still working.",
|
|
66883
|
-
"Adjust your current work if needed and continue."
|
|
66884
|
-
];
|
|
66885
|
-
if (protectedControlMutationRule) {
|
|
66886
|
-
systemLines.push("", protectedControlMutationRule);
|
|
66887
|
-
}
|
|
66888
|
-
return [
|
|
66889
|
-
"<system>",
|
|
66890
|
-
...systemLines,
|
|
66891
|
-
"</system>",
|
|
66892
|
-
"",
|
|
66893
|
-
"<user>",
|
|
66894
|
-
text,
|
|
66895
|
-
"</user>"
|
|
66896
|
-
].join(`
|
|
66897
|
-
`);
|
|
66898
|
-
}
|
|
66899
67038
|
function renderQueuedMessagesList(items) {
|
|
66900
67039
|
if (items.length === 0) {
|
|
66901
67040
|
return "Queue is empty.";
|
|
@@ -67943,7 +68082,10 @@ ${escapeCodeFence(shellResult.output)}
|
|
|
67943
68082
|
await params.agentService.recordConversationReply(params.sessionTarget);
|
|
67944
68083
|
return interactionResult;
|
|
67945
68084
|
}
|
|
67946
|
-
await params.agentService.submitSessionInput(params.sessionTarget,
|
|
68085
|
+
await params.agentService.submitSessionInput(params.sessionTarget, buildSteeringPromptText({
|
|
68086
|
+
text: explicitSteerMessage,
|
|
68087
|
+
protectedControlMutationRule: params.protectedControlMutationRule
|
|
68088
|
+
}));
|
|
67947
68089
|
await params.postText("Steered.");
|
|
67948
68090
|
await params.agentService.recordConversationReply(params.sessionTarget);
|
|
67949
68091
|
return {
|
|
@@ -67952,7 +68094,10 @@ ${escapeCodeFence(shellResult.output)}
|
|
|
67952
68094
|
}
|
|
67953
68095
|
if (!forceQueuedDelivery && params.route.additionalMessageMode === "steer") {
|
|
67954
68096
|
if (sessionBusy && canSteerActiveRun) {
|
|
67955
|
-
await params.agentService.submitSessionInput(params.sessionTarget,
|
|
68097
|
+
await params.agentService.submitSessionInput(params.sessionTarget, buildSteeringPromptText({
|
|
68098
|
+
text: params.text,
|
|
68099
|
+
protectedControlMutationRule: params.protectedControlMutationRule
|
|
68100
|
+
}));
|
|
67956
68101
|
return {
|
|
67957
68102
|
processingIndicatorLifecycle: "active-run"
|
|
67958
68103
|
};
|
|
@@ -68126,6 +68271,43 @@ function resolveChannelAuth(params) {
|
|
|
68126
68271
|
|
|
68127
68272
|
// src/auth/owner-claim.ts
|
|
68128
68273
|
var import_proper_lockfile2 = __toESM(require_proper_lockfile(), 1);
|
|
68274
|
+
import { statSync as statSync3 } from "node:fs";
|
|
68275
|
+
|
|
68276
|
+
// src/control/config-reload-suppression.ts
|
|
68277
|
+
var suppressedReloads = new Map;
|
|
68278
|
+
var MTIME_MATCH_EPSILON_MS = 1;
|
|
68279
|
+
function normalizeConfigPath(configPath) {
|
|
68280
|
+
return configPath.trim();
|
|
68281
|
+
}
|
|
68282
|
+
function suppressConfigReload(configPath, mtimeMs) {
|
|
68283
|
+
const normalizedPath = normalizeConfigPath(configPath);
|
|
68284
|
+
if (!normalizedPath || !Number.isFinite(mtimeMs)) {
|
|
68285
|
+
return;
|
|
68286
|
+
}
|
|
68287
|
+
const existing = suppressedReloads.get(normalizedPath) ?? [];
|
|
68288
|
+
existing.push(mtimeMs);
|
|
68289
|
+
suppressedReloads.set(normalizedPath, existing);
|
|
68290
|
+
}
|
|
68291
|
+
function consumeSuppressedConfigReload(configPath, mtimeMs) {
|
|
68292
|
+
const normalizedPath = normalizeConfigPath(configPath);
|
|
68293
|
+
if (!normalizedPath || !Number.isFinite(mtimeMs)) {
|
|
68294
|
+
return false;
|
|
68295
|
+
}
|
|
68296
|
+
const existing = suppressedReloads.get(normalizedPath);
|
|
68297
|
+
if (!existing || existing.length === 0) {
|
|
68298
|
+
return false;
|
|
68299
|
+
}
|
|
68300
|
+
const next = existing.filter((candidate) => Math.abs(candidate - mtimeMs) > MTIME_MATCH_EPSILON_MS);
|
|
68301
|
+
const matched = next.length !== existing.length;
|
|
68302
|
+
if (next.length === 0) {
|
|
68303
|
+
suppressedReloads.delete(normalizedPath);
|
|
68304
|
+
} else {
|
|
68305
|
+
suppressedReloads.set(normalizedPath, next);
|
|
68306
|
+
}
|
|
68307
|
+
return matched;
|
|
68308
|
+
}
|
|
68309
|
+
|
|
68310
|
+
// src/auth/owner-claim.ts
|
|
68129
68311
|
var OWNER_CLAIM_RUNTIME_STARTED_AT_MS = Date.now();
|
|
68130
68312
|
var CONFIG_LOCK_OPTIONS = {
|
|
68131
68313
|
retries: {
|
|
@@ -68221,6 +68403,7 @@ async function claimFirstOwnerFromDirectMessage(params) {
|
|
|
68221
68403
|
}
|
|
68222
68404
|
freshConfig.app.auth.roles.owner.users = [...currentOwners, principal];
|
|
68223
68405
|
await writeEditableConfig(expandedPath, freshConfig);
|
|
68406
|
+
suppressConfigReload(expandedPath, statSync3(expandedPath).mtimeMs);
|
|
68224
68407
|
syncOwnerUsers(params.config, freshConfig);
|
|
68225
68408
|
ownerClaimRuntimeState.closed = true;
|
|
68226
68409
|
console.log(`clisbot auto-claimed first owner ${principal}`);
|
|
@@ -68726,6 +68909,14 @@ function stripBotMention(text, botUserId) {
|
|
|
68726
68909
|
}
|
|
68727
68910
|
return text.replaceAll(`<@${botUserId}>`, "").replaceAll(/<@[^>]+>/g, "").trim();
|
|
68728
68911
|
}
|
|
68912
|
+
function resolveSlackDirectReplyThreadTs(params) {
|
|
68913
|
+
const resolvedThreadTs = (params.resolvedThreadTs ?? "").trim();
|
|
68914
|
+
if (resolvedThreadTs) {
|
|
68915
|
+
return resolvedThreadTs;
|
|
68916
|
+
}
|
|
68917
|
+
const messageTs = (params.messageTs ?? "").trim();
|
|
68918
|
+
return messageTs || undefined;
|
|
68919
|
+
}
|
|
68729
68920
|
|
|
68730
68921
|
// src/channels/slack/reactions.ts
|
|
68731
68922
|
function normalizeSlackReactionName(value) {
|
|
@@ -69501,6 +69692,10 @@ class SlackSocketService {
|
|
|
69501
69692
|
await this.processedEventsStore.markCompleted(eventId);
|
|
69502
69693
|
return;
|
|
69503
69694
|
}
|
|
69695
|
+
const directReplyThreadTs = resolveSlackDirectReplyThreadTs({
|
|
69696
|
+
messageTs,
|
|
69697
|
+
resolvedThreadTs: await this.resolveThreadTs(event)
|
|
69698
|
+
});
|
|
69504
69699
|
let ownerClaimed = false;
|
|
69505
69700
|
let ownerPrincipal;
|
|
69506
69701
|
try {
|
|
@@ -69518,6 +69713,7 @@ class SlackSocketService {
|
|
|
69518
69713
|
try {
|
|
69519
69714
|
await postSlackText(this.app.client, {
|
|
69520
69715
|
channel: channelId,
|
|
69716
|
+
threadTs: directReplyThreadTs,
|
|
69521
69717
|
text: renderFirstOwnerClaimMessage({
|
|
69522
69718
|
principal: ownerPrincipal,
|
|
69523
69719
|
ownerClaimWindowMinutes: this.loadedConfig.raw.app.auth.ownerClaimWindowMinutes
|
|
@@ -69548,6 +69744,7 @@ class SlackSocketService {
|
|
|
69548
69744
|
try {
|
|
69549
69745
|
await postSlackText(this.app.client, {
|
|
69550
69746
|
channel: channelId,
|
|
69747
|
+
threadTs: directReplyThreadTs,
|
|
69551
69748
|
text: buildPairingReply({
|
|
69552
69749
|
channel: "slack",
|
|
69553
69750
|
idLine: `Your Slack user id: ${directUserId}`,
|
|
@@ -71123,6 +71320,7 @@ class TelegramPollingService {
|
|
|
71123
71320
|
try {
|
|
71124
71321
|
await callTelegramApi(this.accountConfig.botToken, "sendMessage", {
|
|
71125
71322
|
chat_id: message.chat.id,
|
|
71323
|
+
...message.message_id != null ? { reply_to_message_id: message.message_id } : {},
|
|
71126
71324
|
text: renderFirstOwnerClaimMessage({
|
|
71127
71325
|
principal: ownerPrincipal,
|
|
71128
71326
|
ownerClaimWindowMinutes: this.loadedConfig.raw.app.auth.ownerClaimWindowMinutes
|
|
@@ -71809,19 +72007,33 @@ function summarizeExit(params) {
|
|
|
71809
72007
|
return `code ${params.code ?? 0}`;
|
|
71810
72008
|
}
|
|
71811
72009
|
function getRestartPlan(config, restartNumber) {
|
|
71812
|
-
|
|
71813
|
-
const totalRestarts = config.stages.reduce((sum, stage) => sum + stage.maxRestarts, 0);
|
|
72010
|
+
const fastRetryMaxRestarts = config.fastRetry.maxRestarts;
|
|
72011
|
+
const totalRestarts = fastRetryMaxRestarts + config.stages.reduce((sum, stage) => sum + stage.maxRestarts, 0);
|
|
72012
|
+
if (restartNumber >= 1 && restartNumber <= fastRetryMaxRestarts) {
|
|
72013
|
+
return {
|
|
72014
|
+
mode: "fast-retry",
|
|
72015
|
+
stageIndex: -1,
|
|
72016
|
+
delayMs: config.fastRetry.delaySeconds * 1000,
|
|
72017
|
+
restartAttemptInStage: restartNumber,
|
|
72018
|
+
restartsRemaining: totalRestarts - restartNumber,
|
|
72019
|
+
totalRestarts,
|
|
72020
|
+
stageMaxRestarts: fastRetryMaxRestarts
|
|
72021
|
+
};
|
|
72022
|
+
}
|
|
72023
|
+
let completedRestarts = fastRetryMaxRestarts;
|
|
71814
72024
|
for (let index = 0;index < config.stages.length; index += 1) {
|
|
71815
72025
|
const stage = config.stages[index];
|
|
71816
72026
|
const stageStart = completedRestarts + 1;
|
|
71817
72027
|
const stageEnd = completedRestarts + stage.maxRestarts;
|
|
71818
72028
|
if (restartNumber >= stageStart && restartNumber <= stageEnd) {
|
|
71819
72029
|
return {
|
|
72030
|
+
mode: "backoff",
|
|
71820
72031
|
stageIndex: index,
|
|
71821
|
-
|
|
72032
|
+
delayMs: stage.delayMinutes * 60000,
|
|
71822
72033
|
restartAttemptInStage: restartNumber - completedRestarts,
|
|
71823
72034
|
restartsRemaining: totalRestarts - restartNumber,
|
|
71824
|
-
totalRestarts
|
|
72035
|
+
totalRestarts,
|
|
72036
|
+
stageMaxRestarts: stage.maxRestarts
|
|
71825
72037
|
};
|
|
71826
72038
|
}
|
|
71827
72039
|
completedRestarts = stageEnd;
|
|
@@ -71955,7 +72167,7 @@ function renderBackoffAlertMessage(params) {
|
|
|
71955
72167
|
`next restart: ${params.nextRestartAt}`,
|
|
71956
72168
|
`restart: ${params.restartNumber}/${params.totalRestarts}`,
|
|
71957
72169
|
`stage: ${params.stageIndex + 1}/${params.config.restartBackoff.stages.length}`,
|
|
71958
|
-
`stage attempt: ${params.restartAttemptInStage}/${params.
|
|
72170
|
+
`stage attempt: ${params.restartAttemptInStage}/${params.stageMaxRestarts}`
|
|
71959
72171
|
].join(`
|
|
71960
72172
|
`);
|
|
71961
72173
|
}
|
|
@@ -72048,20 +72260,23 @@ class RuntimeMonitor {
|
|
|
72048
72260
|
}
|
|
72049
72261
|
restartNumber = nextRestartNumber;
|
|
72050
72262
|
totalRestarts = plan.totalRestarts;
|
|
72051
|
-
const nextRestartAt = new Date(this.dependencies.now() + plan.
|
|
72052
|
-
|
|
72053
|
-
|
|
72054
|
-
|
|
72055
|
-
|
|
72056
|
-
|
|
72057
|
-
|
|
72058
|
-
|
|
72059
|
-
|
|
72060
|
-
|
|
72061
|
-
|
|
72062
|
-
|
|
72063
|
-
|
|
72064
|
-
|
|
72263
|
+
const nextRestartAt = new Date(this.dependencies.now() + plan.delayMs).toISOString();
|
|
72264
|
+
if (plan.mode === "backoff") {
|
|
72265
|
+
await this.maybeSendAlert("backoff", monitorConfig, renderBackoffAlertMessage({
|
|
72266
|
+
config: monitorConfig,
|
|
72267
|
+
restartNumber,
|
|
72268
|
+
stageIndex: plan.stageIndex,
|
|
72269
|
+
restartAttemptInStage: plan.restartAttemptInStage,
|
|
72270
|
+
stageMaxRestarts: plan.stageMaxRestarts,
|
|
72271
|
+
totalRestarts,
|
|
72272
|
+
nextRestartAt,
|
|
72273
|
+
exit: {
|
|
72274
|
+
code: exit.code,
|
|
72275
|
+
signal: exit.signal,
|
|
72276
|
+
at: exitAt
|
|
72277
|
+
}
|
|
72278
|
+
}));
|
|
72279
|
+
}
|
|
72065
72280
|
await this.writeState({
|
|
72066
72281
|
phase: "backoff",
|
|
72067
72282
|
runtimePid: undefined,
|
|
@@ -72071,6 +72286,7 @@ class RuntimeMonitor {
|
|
|
72071
72286
|
at: exitAt
|
|
72072
72287
|
},
|
|
72073
72288
|
restart: {
|
|
72289
|
+
mode: plan.mode,
|
|
72074
72290
|
stageIndex: plan.stageIndex,
|
|
72075
72291
|
restartNumber,
|
|
72076
72292
|
restartAttemptInStage: plan.restartAttemptInStage,
|
|
@@ -72078,7 +72294,7 @@ class RuntimeMonitor {
|
|
|
72078
72294
|
nextRestartAt
|
|
72079
72295
|
}
|
|
72080
72296
|
});
|
|
72081
|
-
await this.sleepWithStop(plan.
|
|
72297
|
+
await this.sleepWithStop(plan.delayMs);
|
|
72082
72298
|
}
|
|
72083
72299
|
} finally {
|
|
72084
72300
|
await this.stopActiveChild();
|
|
@@ -72239,17 +72455,87 @@ var PROCESS_POLL_INTERVAL_MS = 100;
|
|
|
72239
72455
|
function resolveConfigPath(configPath) {
|
|
72240
72456
|
return expandHomePath(configPath ?? process.env.CLISBOT_CONFIG_PATH ?? getDefaultConfigPath());
|
|
72241
72457
|
}
|
|
72242
|
-
function
|
|
72243
|
-
|
|
72458
|
+
function deriveRuntimeSiblingPath(configPath, filename) {
|
|
72459
|
+
if (!configPath) {
|
|
72460
|
+
return null;
|
|
72461
|
+
}
|
|
72462
|
+
return join11(dirname13(expandHomePath(configPath)), "state", filename);
|
|
72244
72463
|
}
|
|
72245
|
-
function
|
|
72246
|
-
|
|
72464
|
+
function resolvePidPath(pidPath, configPath, options = {}) {
|
|
72465
|
+
if (pidPath) {
|
|
72466
|
+
return expandHomePath(pidPath);
|
|
72467
|
+
}
|
|
72468
|
+
if (options.preferConfigSibling) {
|
|
72469
|
+
const derivedFromExplicitConfig = deriveRuntimeSiblingPath(configPath, "clisbot.pid");
|
|
72470
|
+
if (derivedFromExplicitConfig) {
|
|
72471
|
+
return derivedFromExplicitConfig;
|
|
72472
|
+
}
|
|
72473
|
+
}
|
|
72474
|
+
if (process.env.CLISBOT_PID_PATH) {
|
|
72475
|
+
return expandHomePath(process.env.CLISBOT_PID_PATH);
|
|
72476
|
+
}
|
|
72477
|
+
const derivedFromConfig = deriveRuntimeSiblingPath(configPath ?? process.env.CLISBOT_CONFIG_PATH, "clisbot.pid");
|
|
72478
|
+
if (derivedFromConfig) {
|
|
72479
|
+
return derivedFromConfig;
|
|
72480
|
+
}
|
|
72481
|
+
return expandHomePath(getDefaultRuntimePidPath());
|
|
72247
72482
|
}
|
|
72248
|
-
function
|
|
72249
|
-
|
|
72483
|
+
function resolveLogPath(logPath, configPath, options = {}) {
|
|
72484
|
+
if (logPath) {
|
|
72485
|
+
return expandHomePath(logPath);
|
|
72486
|
+
}
|
|
72487
|
+
if (options.preferConfigSibling) {
|
|
72488
|
+
const derivedFromExplicitConfig = deriveRuntimeSiblingPath(configPath, "clisbot.log");
|
|
72489
|
+
if (derivedFromExplicitConfig) {
|
|
72490
|
+
return derivedFromExplicitConfig;
|
|
72491
|
+
}
|
|
72492
|
+
}
|
|
72493
|
+
if (process.env.CLISBOT_LOG_PATH) {
|
|
72494
|
+
return expandHomePath(process.env.CLISBOT_LOG_PATH);
|
|
72495
|
+
}
|
|
72496
|
+
const derivedFromConfig = deriveRuntimeSiblingPath(configPath ?? process.env.CLISBOT_CONFIG_PATH, "clisbot.log");
|
|
72497
|
+
if (derivedFromConfig) {
|
|
72498
|
+
return derivedFromConfig;
|
|
72499
|
+
}
|
|
72500
|
+
return expandHomePath(getDefaultRuntimeLogPath());
|
|
72250
72501
|
}
|
|
72251
|
-
function
|
|
72252
|
-
|
|
72502
|
+
function resolveMonitorStatePath(monitorStatePath, configPath, options = {}) {
|
|
72503
|
+
if (monitorStatePath) {
|
|
72504
|
+
return expandHomePath(monitorStatePath);
|
|
72505
|
+
}
|
|
72506
|
+
if (options.preferConfigSibling) {
|
|
72507
|
+
const derivedFromExplicitConfig = deriveRuntimeSiblingPath(configPath, "clisbot-monitor.json");
|
|
72508
|
+
if (derivedFromExplicitConfig) {
|
|
72509
|
+
return derivedFromExplicitConfig;
|
|
72510
|
+
}
|
|
72511
|
+
}
|
|
72512
|
+
if (process.env.CLISBOT_RUNTIME_MONITOR_STATE_PATH) {
|
|
72513
|
+
return expandHomePath(process.env.CLISBOT_RUNTIME_MONITOR_STATE_PATH);
|
|
72514
|
+
}
|
|
72515
|
+
const derivedFromConfig = deriveRuntimeSiblingPath(configPath ?? process.env.CLISBOT_CONFIG_PATH, "clisbot-monitor.json");
|
|
72516
|
+
if (derivedFromConfig) {
|
|
72517
|
+
return derivedFromConfig;
|
|
72518
|
+
}
|
|
72519
|
+
return expandHomePath(getDefaultRuntimeMonitorStatePath());
|
|
72520
|
+
}
|
|
72521
|
+
function resolveRuntimeCredentialsPath(runtimeCredentialsPath, configPath, options = {}) {
|
|
72522
|
+
if (runtimeCredentialsPath) {
|
|
72523
|
+
return expandHomePath(runtimeCredentialsPath);
|
|
72524
|
+
}
|
|
72525
|
+
if (options.preferConfigSibling) {
|
|
72526
|
+
const derivedFromExplicitConfig = deriveRuntimeSiblingPath(configPath, "runtime-credentials.json");
|
|
72527
|
+
if (derivedFromExplicitConfig) {
|
|
72528
|
+
return derivedFromExplicitConfig;
|
|
72529
|
+
}
|
|
72530
|
+
}
|
|
72531
|
+
if (process.env.CLISBOT_RUNTIME_CREDENTIALS_PATH) {
|
|
72532
|
+
return expandHomePath(process.env.CLISBOT_RUNTIME_CREDENTIALS_PATH);
|
|
72533
|
+
}
|
|
72534
|
+
const derivedFromConfig = deriveRuntimeSiblingPath(configPath ?? process.env.CLISBOT_CONFIG_PATH, "runtime-credentials.json");
|
|
72535
|
+
if (derivedFromConfig) {
|
|
72536
|
+
return derivedFromConfig;
|
|
72537
|
+
}
|
|
72538
|
+
return expandHomePath(getDefaultRuntimeCredentialsPath());
|
|
72253
72539
|
}
|
|
72254
72540
|
|
|
72255
72541
|
class StartDetachedRuntimeError extends Error {
|
|
@@ -72326,10 +72612,14 @@ async function ensureConfigFile(configPath, options = {}) {
|
|
|
72326
72612
|
};
|
|
72327
72613
|
}
|
|
72328
72614
|
async function startDetachedRuntime(params) {
|
|
72329
|
-
const
|
|
72330
|
-
const
|
|
72331
|
-
const
|
|
72332
|
-
const
|
|
72615
|
+
const configPath = resolveConfigPath(params.configPath);
|
|
72616
|
+
const preferConfigSibling = params.configPath != null;
|
|
72617
|
+
const pidPath = resolvePidPath(params.pidPath, configPath, { preferConfigSibling });
|
|
72618
|
+
const logPath = resolveLogPath(params.logPath, configPath, { preferConfigSibling });
|
|
72619
|
+
const monitorStatePath = resolveMonitorStatePath(params.monitorStatePath, configPath, {
|
|
72620
|
+
preferConfigSibling
|
|
72621
|
+
});
|
|
72622
|
+
const runtimeCredentialsPath = resolveRuntimeCredentialsPath(params.runtimeCredentialsPath, configPath, { preferConfigSibling });
|
|
72333
72623
|
const existingPid = await readRuntimePid(pidPath);
|
|
72334
72624
|
const existingMonitorState = await readRuntimeMonitorState(monitorStatePath);
|
|
72335
72625
|
if (existingPid && isProcessRunning(existingPid)) {
|
|
@@ -72337,7 +72627,7 @@ async function startDetachedRuntime(params) {
|
|
|
72337
72627
|
alreadyRunning: true,
|
|
72338
72628
|
createdConfig: false,
|
|
72339
72629
|
pid: existingPid,
|
|
72340
|
-
configPath
|
|
72630
|
+
configPath,
|
|
72341
72631
|
logPath
|
|
72342
72632
|
};
|
|
72343
72633
|
}
|
|
@@ -72402,9 +72692,13 @@ async function startDetachedRuntime(params) {
|
|
|
72402
72692
|
};
|
|
72403
72693
|
}
|
|
72404
72694
|
async function stopDetachedRuntime(params, dependencies = {}) {
|
|
72405
|
-
const
|
|
72406
|
-
const
|
|
72407
|
-
const
|
|
72695
|
+
const configPath = resolveConfigPath(params.configPath);
|
|
72696
|
+
const preferConfigSibling = params.configPath != null;
|
|
72697
|
+
const pidPath = resolvePidPath(params.pidPath, configPath, { preferConfigSibling });
|
|
72698
|
+
const monitorStatePath = resolveMonitorStatePath(params.monitorStatePath, configPath, {
|
|
72699
|
+
preferConfigSibling
|
|
72700
|
+
});
|
|
72701
|
+
const runtimeCredentialsPath = resolveRuntimeCredentialsPath(params.runtimeCredentialsPath, configPath, { preferConfigSibling });
|
|
72408
72702
|
const existingPid = await readRuntimePid(pidPath);
|
|
72409
72703
|
const monitorState = await readRuntimeMonitorState(monitorStatePath);
|
|
72410
72704
|
let stopped = false;
|
|
@@ -72445,7 +72739,7 @@ async function stopDetachedRuntime(params, dependencies = {}) {
|
|
|
72445
72739
|
}
|
|
72446
72740
|
rmSync3(pidPath, { force: true });
|
|
72447
72741
|
removeRuntimeCredentials(runtimeCredentialsPath);
|
|
72448
|
-
await disableExpiredMemAccountsInConfig(
|
|
72742
|
+
await disableExpiredMemAccountsInConfig(configPath);
|
|
72449
72743
|
if (monitorState) {
|
|
72450
72744
|
await writeRuntimeMonitorState(monitorStatePath, {
|
|
72451
72745
|
...monitorState,
|
|
@@ -72456,7 +72750,7 @@ async function stopDetachedRuntime(params, dependencies = {}) {
|
|
|
72456
72750
|
});
|
|
72457
72751
|
}
|
|
72458
72752
|
if (params.hard) {
|
|
72459
|
-
const socketPath = await resolveTmuxSocketPath(
|
|
72753
|
+
const socketPath = await resolveTmuxSocketPath(configPath);
|
|
72460
72754
|
const tmux = new TmuxClient(socketPath);
|
|
72461
72755
|
try {
|
|
72462
72756
|
await tmux.killServer();
|
|
@@ -72489,9 +72783,12 @@ function removeRuntimePid(pidPath) {
|
|
|
72489
72783
|
}
|
|
72490
72784
|
async function getRuntimeStatus(params = {}) {
|
|
72491
72785
|
const configPath = resolveConfigPath(params.configPath);
|
|
72492
|
-
const
|
|
72493
|
-
const
|
|
72494
|
-
const
|
|
72786
|
+
const preferConfigSibling = params.configPath != null;
|
|
72787
|
+
const pidPath = resolvePidPath(params.pidPath, configPath, { preferConfigSibling });
|
|
72788
|
+
const logPath = resolveLogPath(params.logPath, configPath, { preferConfigSibling });
|
|
72789
|
+
const monitorStatePath = resolveMonitorStatePath(params.monitorStatePath, configPath, {
|
|
72790
|
+
preferConfigSibling
|
|
72791
|
+
});
|
|
72495
72792
|
const pid = await readRuntimePid(pidPath);
|
|
72496
72793
|
const liveness = pid ? getProcessLiveness(pid) : "missing";
|
|
72497
72794
|
const monitorState = await readRuntimeMonitorState(monitorStatePath);
|
|
@@ -72508,6 +72805,7 @@ async function getRuntimeStatus(params = {}) {
|
|
|
72508
72805
|
runtimePid: monitorState?.runtimePid && getProcessLiveness(monitorState.runtimePid) === "running" ? monitorState.runtimePid : undefined,
|
|
72509
72806
|
nextRestartAt: monitorState?.restart?.nextRestartAt,
|
|
72510
72807
|
restartNumber: monitorState?.restart?.restartNumber,
|
|
72808
|
+
restartMode: monitorState?.restart?.mode,
|
|
72511
72809
|
restartStageIndex: monitorState?.restart?.stageIndex,
|
|
72512
72810
|
stopReason: monitorState?.stopReason
|
|
72513
72811
|
};
|
|
@@ -72545,7 +72843,7 @@ function getLogSize(logPath) {
|
|
|
72545
72843
|
return 0;
|
|
72546
72844
|
}
|
|
72547
72845
|
try {
|
|
72548
|
-
return
|
|
72846
|
+
return statSync4(logPath).size;
|
|
72549
72847
|
} catch {
|
|
72550
72848
|
return 0;
|
|
72551
72849
|
}
|
|
@@ -75086,7 +75384,7 @@ async function start(args = []) {
|
|
|
75086
75384
|
}
|
|
75087
75385
|
|
|
75088
75386
|
// src/control/runtime-supervisor.ts
|
|
75089
|
-
import { statSync as
|
|
75387
|
+
import { statSync as statSync5, watch } from "node:fs";
|
|
75090
75388
|
import { basename as basename4, dirname as dirname15 } from "node:path";
|
|
75091
75389
|
|
|
75092
75390
|
// src/channels/processed-events-store.ts
|
|
@@ -75249,12 +75547,22 @@ class RuntimeSupervisor {
|
|
|
75249
75547
|
let nextRuntime;
|
|
75250
75548
|
try {
|
|
75251
75549
|
const loadedConfig = await this.dependencies.loadConfig(this.configPath);
|
|
75550
|
+
const configMtimeMs = statSync5(loadedConfig.configPath).mtimeMs;
|
|
75551
|
+
if (reason === "watch" && consumeSuppressedConfigReload(loadedConfig.configPath, configMtimeMs)) {
|
|
75552
|
+
await this.reconcileConfigWatcher(loadedConfig);
|
|
75553
|
+
await this.dependencies.runtimeHealthStore.setReload({
|
|
75554
|
+
status: "success",
|
|
75555
|
+
reason,
|
|
75556
|
+
configMtimeMs
|
|
75557
|
+
});
|
|
75558
|
+
return;
|
|
75559
|
+
}
|
|
75252
75560
|
nextRuntime = await this.createRuntime(loadedConfig);
|
|
75253
75561
|
await this.reconcileConfigWatcher(loadedConfig);
|
|
75254
75562
|
await this.dependencies.runtimeHealthStore.setReload({
|
|
75255
75563
|
status: "success",
|
|
75256
75564
|
reason,
|
|
75257
|
-
configMtimeMs
|
|
75565
|
+
configMtimeMs
|
|
75258
75566
|
});
|
|
75259
75567
|
this.activeRuntime = nextRuntime;
|
|
75260
75568
|
if (previousRuntime) {
|
|
@@ -75596,6 +75904,9 @@ async function printStatusSummary() {
|
|
|
75596
75904
|
if (runtimeStatus.restartNumber) {
|
|
75597
75905
|
console.log(`restart attempt: ${runtimeStatus.restartNumber}`);
|
|
75598
75906
|
}
|
|
75907
|
+
if (runtimeStatus.restartMode) {
|
|
75908
|
+
console.log(`restart mode: ${runtimeStatus.restartMode}`);
|
|
75909
|
+
}
|
|
75599
75910
|
if (runtimeStatus.restartStageIndex != null && runtimeStatus.restartStageIndex >= 0) {
|
|
75600
75911
|
console.log(`restart stage: ${runtimeStatus.restartStageIndex + 1}`);
|
|
75601
75912
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clisbot",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Chat surfaces for durable AI coding agents running in tmux",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,14 +29,14 @@
|
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && bun build ./src/main.ts --target=node --outfile ./dist/main.js",
|
|
31
31
|
"dev": "bun --watch run src/main.ts serve-foreground",
|
|
32
|
-
"start": "
|
|
33
|
-
"restart": "
|
|
34
|
-
"stop": "
|
|
35
|
-
"status": "
|
|
36
|
-
"logs": "
|
|
37
|
-
"init": "
|
|
32
|
+
"start": "bash ./scripts/run-dev-cli.sh start",
|
|
33
|
+
"restart": "bash ./scripts/run-dev-cli.sh restart",
|
|
34
|
+
"stop": "bash ./scripts/run-dev-cli.sh stop",
|
|
35
|
+
"status": "bash ./scripts/run-dev-cli.sh status",
|
|
36
|
+
"logs": "bash ./scripts/run-dev-cli.sh logs",
|
|
37
|
+
"init": "bash ./scripts/run-dev-cli.sh init",
|
|
38
38
|
"print-config-path": "bun run src/main.ts print-config-path",
|
|
39
|
-
"pairing": "
|
|
39
|
+
"pairing": "bash ./scripts/run-dev-cli.sh pairing",
|
|
40
40
|
"typecheck": "bunx tsc --noEmit",
|
|
41
41
|
"test": "bun test",
|
|
42
42
|
"prepack": "bun run build",
|