clisbot 0.1.17 → 0.1.20
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 +49 -20
- package/config/clisbot.json.template +8 -0
- package/dist/main.js +1042 -111
- package/package.json +1 -1
- package/templates/slack/default/app-manifest.json +2 -3
package/dist/main.js
CHANGED
|
@@ -54471,14 +54471,22 @@ function renderPairingSetupHelpLines(prefix = "", options = {}) {
|
|
|
54471
54471
|
return lines;
|
|
54472
54472
|
}
|
|
54473
54473
|
lines.push(`${prefix}Pairing notes:`);
|
|
54474
|
+
if (shouldRenderSlack && shouldRenderTelegram) {
|
|
54475
|
+
lines.push(`${prefix} - Send a direct message (DM) to the Telegram or Slack bot. Send \`/start\` or \`hi\` to receive a pairing code.`);
|
|
54476
|
+
}
|
|
54474
54477
|
if (shouldRenderTelegram) {
|
|
54475
|
-
|
|
54478
|
+
if (!shouldRenderSlack) {
|
|
54479
|
+
lines.push(`${prefix} - Send a direct message (DM) to the Telegram bot. Send \`/start\` or \`hi\` to receive a pairing code.`);
|
|
54480
|
+
}
|
|
54476
54481
|
lines.push(`${prefix} - Approve the returned Telegram code with: \`clisbot pairing approve telegram <code>\``);
|
|
54477
54482
|
}
|
|
54478
54483
|
if (shouldRenderSlack) {
|
|
54479
|
-
|
|
54484
|
+
if (!shouldRenderTelegram) {
|
|
54485
|
+
lines.push(`${prefix} - Send a direct message (DM) to the Slack bot. Say \`hi\` to receive a pairing code.`);
|
|
54486
|
+
}
|
|
54480
54487
|
lines.push(`${prefix} - Approve the returned Slack code with: \`clisbot pairing approve slack <code>\``);
|
|
54481
54488
|
}
|
|
54489
|
+
lines.push(`${prefix} - Configured app owner/admin principals bypass pairing in DMs.`);
|
|
54482
54490
|
return lines;
|
|
54483
54491
|
}
|
|
54484
54492
|
function renderTmuxDebugHelpLines(prefix = "") {
|
|
@@ -54585,6 +54593,12 @@ function parseCliArgs(argv) {
|
|
|
54585
54593
|
args: args.slice(1)
|
|
54586
54594
|
};
|
|
54587
54595
|
}
|
|
54596
|
+
if (command === "auth") {
|
|
54597
|
+
return {
|
|
54598
|
+
name: "auth",
|
|
54599
|
+
args: args.slice(1)
|
|
54600
|
+
};
|
|
54601
|
+
}
|
|
54588
54602
|
if (command === "pairing") {
|
|
54589
54603
|
return {
|
|
54590
54604
|
name: "pairing",
|
|
@@ -54607,6 +54621,10 @@ function renderCliHelp() {
|
|
|
54607
54621
|
return [
|
|
54608
54622
|
`clisbot v${getClisbotVersion()}`,
|
|
54609
54623
|
"",
|
|
54624
|
+
"Platform support:",
|
|
54625
|
+
" Linux/macOS Supported.",
|
|
54626
|
+
" Windows Native Windows is not supported yet. Use WSL2.",
|
|
54627
|
+
"",
|
|
54610
54628
|
"Fastest start:",
|
|
54611
54629
|
" 1. Choose the channels you want to bootstrap explicitly.",
|
|
54612
54630
|
" 2. Run one of these commands:",
|
|
@@ -54639,6 +54657,7 @@ function renderCliHelp() {
|
|
|
54639
54657
|
" clisbot loops <subcommand>",
|
|
54640
54658
|
" clisbot message <subcommand>",
|
|
54641
54659
|
" clisbot agents <subcommand>",
|
|
54660
|
+
" clisbot auth <subcommand>",
|
|
54642
54661
|
" clisbot pairing <subcommand>",
|
|
54643
54662
|
" clisbot init [--cli <codex|claude|gemini>] [--bot-type <personal|team>] [--persist]",
|
|
54644
54663
|
" [--slack-account <id> --slack-app-token <ENV_NAME|${ENV_NAME}|literal> --slack-bot-token <ENV_NAME|${ENV_NAME}|literal>]...",
|
|
@@ -54675,6 +54694,7 @@ function renderCliHelp() {
|
|
|
54675
54694
|
" cancel --all",
|
|
54676
54695
|
" message Run provider message actions such as send, react, read, edit, delete, and pins.",
|
|
54677
54696
|
" agents Manage configured agents and top-level bindings.",
|
|
54697
|
+
" auth Manage app and agent auth roles, principals, and permissions in config. See `clisbot auth --help`.",
|
|
54678
54698
|
" pairing Run the pairing control CLI.",
|
|
54679
54699
|
` init Seed ${configPath} and optionally create the first agent without starting clisbot.`,
|
|
54680
54700
|
" --version, -v Show the installed clisbot version.",
|
|
@@ -59856,6 +59876,10 @@ var authRoleSchema = exports_external.object({
|
|
|
59856
59876
|
allow: exports_external.array(exports_external.string().min(1)).default([]),
|
|
59857
59877
|
users: exports_external.array(exports_external.string().min(1)).default([])
|
|
59858
59878
|
});
|
|
59879
|
+
var authRoleOverrideSchema = exports_external.object({
|
|
59880
|
+
allow: exports_external.array(exports_external.string().min(1)).optional(),
|
|
59881
|
+
users: exports_external.array(exports_external.string().min(1)).optional()
|
|
59882
|
+
});
|
|
59859
59883
|
var appAuthSchema = exports_external.object({
|
|
59860
59884
|
ownerClaimWindowMinutes: exports_external.number().int().positive().default(30),
|
|
59861
59885
|
defaultRole: exports_external.string().min(1).default("member"),
|
|
@@ -59887,11 +59911,15 @@ var agentAuthSchema = exports_external.object({
|
|
|
59887
59911
|
}
|
|
59888
59912
|
})
|
|
59889
59913
|
});
|
|
59914
|
+
var agentAuthOverrideSchema = exports_external.object({
|
|
59915
|
+
defaultRole: exports_external.string().min(1).optional(),
|
|
59916
|
+
roles: exports_external.record(exports_external.string(), authRoleOverrideSchema).default({})
|
|
59917
|
+
});
|
|
59890
59918
|
var agentOverrideSchema = exports_external.object({
|
|
59891
59919
|
workspace: exports_external.string().optional(),
|
|
59892
59920
|
responseMode: exports_external.enum(["capture-pane", "message-tool"]).optional(),
|
|
59893
59921
|
additionalMessageMode: exports_external.enum(["queue", "steer"]).optional(),
|
|
59894
|
-
auth:
|
|
59922
|
+
auth: agentAuthOverrideSchema.optional(),
|
|
59895
59923
|
runner: runnerOverrideSchema.optional(),
|
|
59896
59924
|
stream: streamSchema.partial().optional(),
|
|
59897
59925
|
session: sessionSchema.partial().optional()
|
|
@@ -59986,9 +60014,18 @@ var channelAgentPromptSchema = exports_external.object({
|
|
|
59986
60014
|
var channelResponseModeSchema = exports_external.enum(["capture-pane", "message-tool"]);
|
|
59987
60015
|
var channelAdditionalMessageModeSchema = exports_external.enum(["queue", "steer"]);
|
|
59988
60016
|
var channelVerboseSchema = exports_external.enum(["off", "minimal"]);
|
|
60017
|
+
var surfaceNotificationModeSchema = exports_external.enum(["none", "brief", "full"]);
|
|
59989
60018
|
var timezoneSchema = exports_external.string().refine(isValidLoopTimezone, {
|
|
59990
60019
|
message: "Expected a valid IANA timezone such as Asia/Ho_Chi_Minh"
|
|
59991
60020
|
});
|
|
60021
|
+
var surfaceNotificationsSchema = exports_external.object({
|
|
60022
|
+
queueStart: surfaceNotificationModeSchema.default("brief"),
|
|
60023
|
+
loopStart: surfaceNotificationModeSchema.default("brief")
|
|
60024
|
+
});
|
|
60025
|
+
var surfaceNotificationsOverrideSchema = exports_external.object({
|
|
60026
|
+
queueStart: surfaceNotificationModeSchema.optional(),
|
|
60027
|
+
loopStart: surfaceNotificationModeSchema.optional()
|
|
60028
|
+
});
|
|
59992
60029
|
var slackRouteSchema = exports_external.object({
|
|
59993
60030
|
requireMention: exports_external.boolean().default(true),
|
|
59994
60031
|
allowBots: exports_external.boolean().default(false),
|
|
@@ -59999,6 +60036,7 @@ var slackRouteSchema = exports_external.object({
|
|
|
59999
60036
|
response: slackResponseSchema.optional(),
|
|
60000
60037
|
responseMode: channelResponseModeSchema.optional(),
|
|
60001
60038
|
additionalMessageMode: channelAdditionalMessageModeSchema.optional(),
|
|
60039
|
+
surfaceNotifications: surfaceNotificationsOverrideSchema.optional(),
|
|
60002
60040
|
verbose: channelVerboseSchema.optional(),
|
|
60003
60041
|
followUp: slackFollowUpOverrideSchema.optional(),
|
|
60004
60042
|
timezone: timezoneSchema.optional()
|
|
@@ -60013,6 +60051,7 @@ var telegramTopicRouteSchema = exports_external.object({
|
|
|
60013
60051
|
response: slackResponseSchema.optional(),
|
|
60014
60052
|
responseMode: channelResponseModeSchema.optional(),
|
|
60015
60053
|
additionalMessageMode: channelAdditionalMessageModeSchema.optional(),
|
|
60054
|
+
surfaceNotifications: surfaceNotificationsOverrideSchema.optional(),
|
|
60016
60055
|
verbose: channelVerboseSchema.optional(),
|
|
60017
60056
|
followUp: slackFollowUpOverrideSchema.optional(),
|
|
60018
60057
|
timezone: timezoneSchema.optional()
|
|
@@ -60027,6 +60066,7 @@ var telegramGroupRouteSchema = exports_external.object({
|
|
|
60027
60066
|
response: slackResponseSchema.optional(),
|
|
60028
60067
|
responseMode: channelResponseModeSchema.optional(),
|
|
60029
60068
|
additionalMessageMode: channelAdditionalMessageModeSchema.optional(),
|
|
60069
|
+
surfaceNotifications: surfaceNotificationsOverrideSchema.optional(),
|
|
60030
60070
|
verbose: channelVerboseSchema.optional(),
|
|
60031
60071
|
followUp: slackFollowUpOverrideSchema.optional(),
|
|
60032
60072
|
timezone: timezoneSchema.optional(),
|
|
@@ -60045,6 +60085,7 @@ var telegramDirectMessagesSchema = exports_external.object({
|
|
|
60045
60085
|
response: slackResponseSchema.optional(),
|
|
60046
60086
|
responseMode: channelResponseModeSchema.optional(),
|
|
60047
60087
|
additionalMessageMode: channelAdditionalMessageModeSchema.optional(),
|
|
60088
|
+
surfaceNotifications: surfaceNotificationsOverrideSchema.optional(),
|
|
60048
60089
|
verbose: channelVerboseSchema.optional(),
|
|
60049
60090
|
followUp: slackFollowUpOverrideSchema.optional(),
|
|
60050
60091
|
timezone: timezoneSchema.optional()
|
|
@@ -60082,6 +60123,7 @@ var telegramSchema = exports_external.object({
|
|
|
60082
60123
|
response: slackResponseSchema.default("final"),
|
|
60083
60124
|
responseMode: channelResponseModeSchema.default("message-tool"),
|
|
60084
60125
|
additionalMessageMode: channelAdditionalMessageModeSchema.default("steer"),
|
|
60126
|
+
surfaceNotifications: surfaceNotificationsSchema.optional(),
|
|
60085
60127
|
verbose: channelVerboseSchema.default("minimal"),
|
|
60086
60128
|
followUp: slackFollowUpSchema.default({
|
|
60087
60129
|
mode: "auto",
|
|
@@ -60113,6 +60155,7 @@ var directMessagesSchema = exports_external.object({
|
|
|
60113
60155
|
response: slackResponseSchema.optional(),
|
|
60114
60156
|
responseMode: channelResponseModeSchema.optional(),
|
|
60115
60157
|
additionalMessageMode: channelAdditionalMessageModeSchema.optional(),
|
|
60158
|
+
surfaceNotifications: surfaceNotificationsOverrideSchema.optional(),
|
|
60116
60159
|
verbose: channelVerboseSchema.optional(),
|
|
60117
60160
|
followUp: slackFollowUpOverrideSchema.optional(),
|
|
60118
60161
|
timezone: timezoneSchema.optional()
|
|
@@ -60157,6 +60200,7 @@ var slackSchema = exports_external.object({
|
|
|
60157
60200
|
response: slackResponseSchema.default("final"),
|
|
60158
60201
|
responseMode: channelResponseModeSchema.default("message-tool"),
|
|
60159
60202
|
additionalMessageMode: channelAdditionalMessageModeSchema.default("steer"),
|
|
60203
|
+
surfaceNotifications: surfaceNotificationsSchema.optional(),
|
|
60160
60204
|
verbose: channelVerboseSchema.default("minimal"),
|
|
60161
60205
|
followUp: slackFollowUpSchema.default({
|
|
60162
60206
|
mode: "auto",
|
|
@@ -60308,6 +60352,10 @@ var clisbotConfigSchema = exports_external.object({
|
|
|
60308
60352
|
response: "final",
|
|
60309
60353
|
responseMode: "message-tool",
|
|
60310
60354
|
additionalMessageMode: "steer",
|
|
60355
|
+
surfaceNotifications: {
|
|
60356
|
+
queueStart: "brief",
|
|
60357
|
+
loopStart: "brief"
|
|
60358
|
+
},
|
|
60311
60359
|
verbose: "minimal",
|
|
60312
60360
|
followUp: {
|
|
60313
60361
|
mode: "auto",
|
|
@@ -60625,6 +60673,10 @@ function renderDefaultConfigTemplate(options = {}) {
|
|
|
60625
60673
|
response: "final",
|
|
60626
60674
|
responseMode: "message-tool",
|
|
60627
60675
|
additionalMessageMode: "steer",
|
|
60676
|
+
surfaceNotifications: {
|
|
60677
|
+
queueStart: "brief",
|
|
60678
|
+
loopStart: "brief"
|
|
60679
|
+
},
|
|
60628
60680
|
verbose: "minimal",
|
|
60629
60681
|
followUp: {
|
|
60630
60682
|
mode: "auto",
|
|
@@ -60666,6 +60718,10 @@ function renderDefaultConfigTemplate(options = {}) {
|
|
|
60666
60718
|
response: "final",
|
|
60667
60719
|
responseMode: "message-tool",
|
|
60668
60720
|
additionalMessageMode: "steer",
|
|
60721
|
+
surfaceNotifications: {
|
|
60722
|
+
queueStart: "brief",
|
|
60723
|
+
loopStart: "brief"
|
|
60724
|
+
},
|
|
60669
60725
|
verbose: "minimal",
|
|
60670
60726
|
followUp: {
|
|
60671
60727
|
mode: "auto",
|
|
@@ -61834,6 +61890,8 @@ function getExecutableNames(command) {
|
|
|
61834
61890
|
// src/runners/tmux/client.ts
|
|
61835
61891
|
var MAIN_WINDOW_NAME = "main";
|
|
61836
61892
|
var TMUX_NOT_FOUND_CODE = "ENOENT";
|
|
61893
|
+
var TMUX_SERVER_BOOTSTRAP_TIMEOUT_MS = 1000;
|
|
61894
|
+
var TMUX_SERVER_BOOTSTRAP_POLL_MS = 25;
|
|
61837
61895
|
var TMUX_SERVER_DEFAULTS = [
|
|
61838
61896
|
["exit-empty", "off"],
|
|
61839
61897
|
["destroy-unattached", "off"]
|
|
@@ -61844,6 +61902,9 @@ class TmuxClient {
|
|
|
61844
61902
|
constructor(socketPath) {
|
|
61845
61903
|
this.socketPath = socketPath;
|
|
61846
61904
|
}
|
|
61905
|
+
isServerUnavailableOutput(output) {
|
|
61906
|
+
return /no server running|No such file or directory|failed to connect to server|error connecting to/i.test(output);
|
|
61907
|
+
}
|
|
61847
61908
|
async exec(args, options = {}) {
|
|
61848
61909
|
if (!commandExists("tmux")) {
|
|
61849
61910
|
throw new Error("tmux is not installed or not available on PATH. Install tmux and restart clisbot.");
|
|
@@ -61895,7 +61956,10 @@ class TmuxClient {
|
|
|
61895
61956
|
}
|
|
61896
61957
|
const output = `${result.stderr}
|
|
61897
61958
|
${result.stdout}`.trim();
|
|
61898
|
-
|
|
61959
|
+
if (this.isServerUnavailableOutput(output)) {
|
|
61960
|
+
return false;
|
|
61961
|
+
}
|
|
61962
|
+
return false;
|
|
61899
61963
|
}
|
|
61900
61964
|
async ensureServerDefaults() {
|
|
61901
61965
|
if (!await this.isServerRunning()) {
|
|
@@ -61905,6 +61969,35 @@ ${result.stdout}`.trim();
|
|
|
61905
61969
|
await this.execOrThrow(["set-option", "-g", name, value]);
|
|
61906
61970
|
}
|
|
61907
61971
|
}
|
|
61972
|
+
isBootstrapRetryableError(error) {
|
|
61973
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
61974
|
+
return this.isServerUnavailableOutput(message);
|
|
61975
|
+
}
|
|
61976
|
+
async withServerBootstrapRetry(task) {
|
|
61977
|
+
const deadline = Date.now() + TMUX_SERVER_BOOTSTRAP_TIMEOUT_MS;
|
|
61978
|
+
while (true) {
|
|
61979
|
+
try {
|
|
61980
|
+
return await task();
|
|
61981
|
+
} catch (error) {
|
|
61982
|
+
if (!this.isBootstrapRetryableError(error) || Date.now() >= deadline) {
|
|
61983
|
+
throw error;
|
|
61984
|
+
}
|
|
61985
|
+
await sleep(TMUX_SERVER_BOOTSTRAP_POLL_MS);
|
|
61986
|
+
}
|
|
61987
|
+
}
|
|
61988
|
+
}
|
|
61989
|
+
async waitForSessionBootstrap(sessionName) {
|
|
61990
|
+
const deadline = Date.now() + TMUX_SERVER_BOOTSTRAP_TIMEOUT_MS;
|
|
61991
|
+
while (true) {
|
|
61992
|
+
if (await this.hasSession(sessionName)) {
|
|
61993
|
+
return;
|
|
61994
|
+
}
|
|
61995
|
+
if (Date.now() >= deadline) {
|
|
61996
|
+
throw new Error(`tmux session "${sessionName}" did not become reachable on socket ${this.socketPath} within ${TMUX_SERVER_BOOTSTRAP_TIMEOUT_MS}ms.`);
|
|
61997
|
+
}
|
|
61998
|
+
await sleep(TMUX_SERVER_BOOTSTRAP_POLL_MS);
|
|
61999
|
+
}
|
|
62000
|
+
}
|
|
61908
62001
|
async newSession(params) {
|
|
61909
62002
|
await this.execOrThrow([
|
|
61910
62003
|
"new-session",
|
|
@@ -61917,8 +62010,13 @@ ${result.stdout}`.trim();
|
|
|
61917
62010
|
params.cwd,
|
|
61918
62011
|
params.command
|
|
61919
62012
|
]);
|
|
61920
|
-
await this.
|
|
61921
|
-
await this.
|
|
62013
|
+
await this.waitForSessionBootstrap(params.sessionName);
|
|
62014
|
+
await this.withServerBootstrapRetry(async () => {
|
|
62015
|
+
await this.ensureServerDefaults();
|
|
62016
|
+
});
|
|
62017
|
+
await this.withServerBootstrapRetry(async () => {
|
|
62018
|
+
await this.freezeWindowName(`${params.sessionName}:${MAIN_WINDOW_NAME}`);
|
|
62019
|
+
});
|
|
61922
62020
|
}
|
|
61923
62021
|
async newWindow(params) {
|
|
61924
62022
|
const paneId = await this.execOrThrow([
|
|
@@ -62705,40 +62803,329 @@ async function runAccountsCli(args, deps = {}) {
|
|
|
62705
62803
|
throw new Error(renderAccountsHelp());
|
|
62706
62804
|
}
|
|
62707
62805
|
|
|
62708
|
-
// src/
|
|
62709
|
-
function
|
|
62710
|
-
|
|
62711
|
-
|
|
62712
|
-
|
|
62713
|
-
|
|
62806
|
+
// src/control/auth-cli.ts
|
|
62807
|
+
function getEditableConfigPath3() {
|
|
62808
|
+
return process.env.CLISBOT_CONFIG_PATH;
|
|
62809
|
+
}
|
|
62810
|
+
function parseRepeatedOption2(args, name) {
|
|
62811
|
+
const values = [];
|
|
62812
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
62813
|
+
if (args[index] !== name) {
|
|
62814
|
+
continue;
|
|
62815
|
+
}
|
|
62816
|
+
const value = args[index + 1]?.trim();
|
|
62817
|
+
if (!value) {
|
|
62818
|
+
throw new Error(`Missing value for ${name}`);
|
|
62819
|
+
}
|
|
62820
|
+
values.push(value);
|
|
62821
|
+
}
|
|
62822
|
+
return values;
|
|
62823
|
+
}
|
|
62824
|
+
function parseSingleOption2(args, name) {
|
|
62825
|
+
const values = parseRepeatedOption2(args, name);
|
|
62826
|
+
if (values.length === 0) {
|
|
62827
|
+
return;
|
|
62828
|
+
}
|
|
62829
|
+
return values[values.length - 1];
|
|
62830
|
+
}
|
|
62831
|
+
function hasFlag3(args, name) {
|
|
62832
|
+
return args.includes(name);
|
|
62833
|
+
}
|
|
62834
|
+
function parseScope(raw, args) {
|
|
62835
|
+
if (raw === "app") {
|
|
62836
|
+
return { kind: "app" };
|
|
62837
|
+
}
|
|
62838
|
+
if (raw === "agent-defaults") {
|
|
62839
|
+
return { kind: "agent-defaults" };
|
|
62840
|
+
}
|
|
62841
|
+
if (raw === "agent") {
|
|
62842
|
+
const agentId = parseSingleOption2(args, "--agent");
|
|
62843
|
+
if (!agentId) {
|
|
62844
|
+
throw new Error("Missing value for --agent");
|
|
62845
|
+
}
|
|
62846
|
+
return { kind: "agent", agentId };
|
|
62847
|
+
}
|
|
62848
|
+
throw new Error("Scope required: app | agent-defaults | agent");
|
|
62849
|
+
}
|
|
62850
|
+
function renderAuthCliHelp() {
|
|
62851
|
+
return [
|
|
62852
|
+
"clisbot auth",
|
|
62714
62853
|
"",
|
|
62715
|
-
"
|
|
62854
|
+
"Manage auth roles, principals, and permissions in config.",
|
|
62716
62855
|
"",
|
|
62717
|
-
"
|
|
62718
|
-
|
|
62856
|
+
"Usage:",
|
|
62857
|
+
" clisbot auth list [--json]",
|
|
62858
|
+
" clisbot auth show <app|agent-defaults|agent> [--agent <id>] [--json]",
|
|
62859
|
+
" clisbot auth add-user <app|agent-defaults|agent> --role <role> --user <principal> [--agent <id>]",
|
|
62860
|
+
" clisbot auth remove-user <app|agent-defaults|agent> --role <role> --user <principal> [--agent <id>]",
|
|
62861
|
+
" clisbot auth add-permission <app|agent-defaults|agent> --role <role> --permission <permission> [--agent <id>]",
|
|
62862
|
+
" clisbot auth remove-permission <app|agent-defaults|agent> --role <role> --permission <permission> [--agent <id>]",
|
|
62719
62863
|
"",
|
|
62720
|
-
"
|
|
62721
|
-
|
|
62722
|
-
|
|
62723
|
-
|
|
62724
|
-
|
|
62864
|
+
"Scopes:",
|
|
62865
|
+
" app edit app.auth",
|
|
62866
|
+
" agent-defaults edit agents.defaults.auth",
|
|
62867
|
+
" agent edit one agents.list[].auth override; requires --agent <id>",
|
|
62868
|
+
"",
|
|
62869
|
+
"Permission sets:",
|
|
62870
|
+
` app ${APP_ADMIN_PERMISSIONS.join(", ")}`,
|
|
62871
|
+
` agent ${DEFAULT_AGENT_ADMIN_PERMISSIONS.join(", ")}`,
|
|
62872
|
+
"",
|
|
62873
|
+
"Notes:",
|
|
62874
|
+
" add-user/remove-user mutate roles.<role>.users",
|
|
62875
|
+
" add-permission/remove-permission mutate roles.<role>.allow",
|
|
62876
|
+
" agent role edits clone the inherited agent-defaults role into the target agent override on first write",
|
|
62877
|
+
"",
|
|
62878
|
+
"Examples:",
|
|
62879
|
+
" clisbot auth add-user app --role owner --user telegram:1276408333",
|
|
62880
|
+
" clisbot auth remove-user app --role admin --user slack:U123",
|
|
62881
|
+
" clisbot auth add-user agent --agent default --role admin --user slack:U123",
|
|
62882
|
+
" clisbot auth add-permission agent-defaults --role member --permission shellExecute",
|
|
62883
|
+
" clisbot auth remove-permission agent --agent default --role member --permission shellExecute",
|
|
62884
|
+
" clisbot auth show agent-defaults",
|
|
62885
|
+
" clisbot auth list --json"
|
|
62886
|
+
].join(`
|
|
62887
|
+
`);
|
|
62888
|
+
}
|
|
62889
|
+
function cloneRoleDefinition(value) {
|
|
62890
|
+
return {
|
|
62891
|
+
allow: [...value?.allow ?? []],
|
|
62892
|
+
users: [...value?.users ?? []]
|
|
62893
|
+
};
|
|
62894
|
+
}
|
|
62895
|
+
function mergeRoleDefinitions(inherited, override) {
|
|
62896
|
+
return {
|
|
62897
|
+
allow: [...override?.allow ?? inherited?.allow ?? []],
|
|
62898
|
+
users: [...override?.users ?? inherited?.users ?? []]
|
|
62899
|
+
};
|
|
62900
|
+
}
|
|
62901
|
+
function mergeRoleRecord(defaults, overrides) {
|
|
62902
|
+
const merged = {};
|
|
62903
|
+
const roleNames = new Set([
|
|
62904
|
+
...Object.keys(defaults ?? {}),
|
|
62905
|
+
...Object.keys(overrides ?? {})
|
|
62906
|
+
]);
|
|
62907
|
+
for (const roleName of roleNames) {
|
|
62908
|
+
merged[roleName] = mergeRoleDefinitions(defaults?.[roleName], overrides?.[roleName]);
|
|
62725
62909
|
}
|
|
62726
|
-
|
|
62727
|
-
|
|
62728
|
-
|
|
62729
|
-
|
|
62910
|
+
return merged;
|
|
62911
|
+
}
|
|
62912
|
+
function normalizeUnique(values) {
|
|
62913
|
+
return [...new Set(values.map((value) => value.trim()).filter(Boolean))].sort();
|
|
62914
|
+
}
|
|
62915
|
+
function ensureAgentEntry(config, agentId) {
|
|
62916
|
+
const entry = config.agents.list.find((item) => item.id === agentId);
|
|
62917
|
+
if (!entry) {
|
|
62918
|
+
throw new Error(`Unknown agent: ${agentId}`);
|
|
62730
62919
|
}
|
|
62731
|
-
return
|
|
62732
|
-
|
|
62920
|
+
return entry;
|
|
62921
|
+
}
|
|
62922
|
+
function getAuthLabel(scope) {
|
|
62923
|
+
if (scope.kind === "app") {
|
|
62924
|
+
return "app.auth";
|
|
62925
|
+
}
|
|
62926
|
+
if (scope.kind === "agent-defaults") {
|
|
62927
|
+
return "agents.defaults.auth";
|
|
62928
|
+
}
|
|
62929
|
+
return `agents.list[${scope.agentId}].auth`;
|
|
62930
|
+
}
|
|
62931
|
+
function ensureEditableRoleDefinition(role) {
|
|
62932
|
+
role.allow = role.allow ?? [];
|
|
62933
|
+
role.users = role.users ?? [];
|
|
62934
|
+
return role;
|
|
62935
|
+
}
|
|
62936
|
+
function resolveAppRole(config, roleName) {
|
|
62937
|
+
const role = config.app.auth.roles[roleName];
|
|
62938
|
+
if (!role) {
|
|
62939
|
+
throw new Error(`Unknown app role: ${roleName}`);
|
|
62940
|
+
}
|
|
62941
|
+
return ensureEditableRoleDefinition(role);
|
|
62942
|
+
}
|
|
62943
|
+
function resolveAgentDefaultsRole(config, roleName) {
|
|
62944
|
+
const role = config.agents.defaults.auth.roles[roleName];
|
|
62945
|
+
if (!role) {
|
|
62946
|
+
throw new Error(`Unknown agent-defaults role: ${roleName}`);
|
|
62947
|
+
}
|
|
62948
|
+
return ensureEditableRoleDefinition(role);
|
|
62949
|
+
}
|
|
62950
|
+
function ensureAgentOverrideAuth(entry, config) {
|
|
62951
|
+
if (!entry.auth) {
|
|
62952
|
+
entry.auth = {
|
|
62953
|
+
defaultRole: config.agents.defaults.auth.defaultRole,
|
|
62954
|
+
roles: {}
|
|
62955
|
+
};
|
|
62956
|
+
}
|
|
62957
|
+
return entry.auth;
|
|
62958
|
+
}
|
|
62959
|
+
function resolveAgentRoleForEdit(config, agentId, roleName) {
|
|
62960
|
+
const entry = ensureAgentEntry(config, agentId);
|
|
62961
|
+
const explicitRole = entry.auth?.roles?.[roleName];
|
|
62962
|
+
if (explicitRole) {
|
|
62963
|
+
return ensureEditableRoleDefinition(explicitRole);
|
|
62964
|
+
}
|
|
62965
|
+
const inheritedRole = config.agents.defaults.auth.roles[roleName];
|
|
62966
|
+
if (!inheritedRole) {
|
|
62967
|
+
throw new Error(`Unknown agent role: ${roleName}`);
|
|
62968
|
+
}
|
|
62969
|
+
const auth = ensureAgentOverrideAuth(entry, config);
|
|
62970
|
+
auth.roles[roleName] = cloneRoleDefinition(inheritedRole);
|
|
62971
|
+
return ensureEditableRoleDefinition(auth.roles[roleName]);
|
|
62972
|
+
}
|
|
62973
|
+
function resolveRoleForEdit(config, scope, roleName) {
|
|
62974
|
+
if (scope.kind === "app") {
|
|
62975
|
+
return resolveAppRole(config, roleName);
|
|
62976
|
+
}
|
|
62977
|
+
if (scope.kind === "agent-defaults") {
|
|
62978
|
+
return resolveAgentDefaultsRole(config, roleName);
|
|
62979
|
+
}
|
|
62980
|
+
return resolveAgentRoleForEdit(config, scope.agentId, roleName);
|
|
62981
|
+
}
|
|
62982
|
+
function validatePermission(scope, permission) {
|
|
62983
|
+
const trimmed = permission.trim();
|
|
62984
|
+
if (!trimmed) {
|
|
62985
|
+
throw new Error("Missing value for --permission");
|
|
62986
|
+
}
|
|
62987
|
+
const allowedPermissions = new Set(scope.kind === "app" ? APP_ADMIN_PERMISSIONS : DEFAULT_AGENT_ADMIN_PERMISSIONS);
|
|
62988
|
+
if (!allowedPermissions.has(trimmed)) {
|
|
62989
|
+
const sorted = [...allowedPermissions].sort().join(", ");
|
|
62990
|
+
throw new Error(`Unknown permission for ${scope.kind}: ${trimmed}. Allowed: ${sorted}`);
|
|
62991
|
+
}
|
|
62992
|
+
return trimmed;
|
|
62993
|
+
}
|
|
62994
|
+
function buildShowPayload(config, scope) {
|
|
62995
|
+
if (scope.kind === "app") {
|
|
62996
|
+
return config.app.auth;
|
|
62997
|
+
}
|
|
62998
|
+
if (scope.kind === "agent-defaults") {
|
|
62999
|
+
return config.agents.defaults.auth;
|
|
63000
|
+
}
|
|
63001
|
+
const entry = ensureAgentEntry(config, scope.agentId);
|
|
63002
|
+
return {
|
|
63003
|
+
defaultRole: entry.auth?.defaultRole ?? config.agents.defaults.auth.defaultRole,
|
|
63004
|
+
roles: mergeRoleRecord(config.agents.defaults.auth.roles, entry.auth?.roles)
|
|
63005
|
+
};
|
|
63006
|
+
}
|
|
63007
|
+
async function listAuth(args) {
|
|
63008
|
+
const { config } = await readEditableConfig(getEditableConfigPath3());
|
|
63009
|
+
const payload = {
|
|
63010
|
+
app: config.app.auth,
|
|
63011
|
+
agentDefaults: config.agents.defaults.auth,
|
|
63012
|
+
agents: config.agents.list.map((entry) => ({
|
|
63013
|
+
agentId: entry.id,
|
|
63014
|
+
auth: buildShowPayload(config, { kind: "agent", agentId: entry.id })
|
|
63015
|
+
}))
|
|
63016
|
+
};
|
|
63017
|
+
if (hasFlag3(args, "--json")) {
|
|
63018
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
63019
|
+
return;
|
|
63020
|
+
}
|
|
63021
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
63022
|
+
}
|
|
63023
|
+
async function showAuth(args) {
|
|
63024
|
+
const scope = parseScope(args[0], args.slice(1));
|
|
63025
|
+
const { config } = await readEditableConfig(getEditableConfigPath3());
|
|
63026
|
+
const payload = buildShowPayload(config, scope);
|
|
63027
|
+
if (hasFlag3(args, "--json")) {
|
|
63028
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
63029
|
+
return;
|
|
63030
|
+
}
|
|
63031
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
63032
|
+
}
|
|
63033
|
+
async function mutateUsers(mode, args) {
|
|
63034
|
+
const scope = parseScope(args[0], args.slice(1));
|
|
63035
|
+
const roleName = parseSingleOption2(args, "--role");
|
|
63036
|
+
const principal = parseSingleOption2(args, "--user")?.trim();
|
|
63037
|
+
if (!roleName) {
|
|
63038
|
+
throw new Error("Missing value for --role");
|
|
63039
|
+
}
|
|
63040
|
+
if (!principal) {
|
|
63041
|
+
throw new Error("Missing value for --user");
|
|
63042
|
+
}
|
|
63043
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath3());
|
|
63044
|
+
const role = resolveRoleForEdit(config, scope, roleName);
|
|
63045
|
+
role.users = normalizeUnique(mode === "add" ? [...role.users, principal] : role.users.filter((value) => value !== principal));
|
|
63046
|
+
await writeEditableConfig(configPath, config);
|
|
63047
|
+
console.log(`${mode === "add" ? "Added" : "Removed"} user ${principal} ${mode === "add" ? "to" : "from"} ${getAuthLabel(scope)} role ${roleName}.`);
|
|
63048
|
+
}
|
|
63049
|
+
async function mutatePermissions(mode, args) {
|
|
63050
|
+
const scope = parseScope(args[0], args.slice(1));
|
|
63051
|
+
const roleName = parseSingleOption2(args, "--role");
|
|
63052
|
+
const permission = validatePermission(scope, parseSingleOption2(args, "--permission") ?? "");
|
|
63053
|
+
if (!roleName) {
|
|
63054
|
+
throw new Error("Missing value for --role");
|
|
63055
|
+
}
|
|
63056
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath3());
|
|
63057
|
+
const role = resolveRoleForEdit(config, scope, roleName);
|
|
63058
|
+
role.allow = normalizeUnique(mode === "add" ? [...role.allow, permission] : role.allow.filter((value) => value !== permission));
|
|
63059
|
+
await writeEditableConfig(configPath, config);
|
|
63060
|
+
console.log(`${mode === "add" ? "Added" : "Removed"} permission ${permission} ${mode === "add" ? "to" : "from"} ${getAuthLabel(scope)} role ${roleName}.`);
|
|
63061
|
+
}
|
|
63062
|
+
async function runAuthCli(args) {
|
|
63063
|
+
const [command, ...rest] = args;
|
|
63064
|
+
if (!command || command === "--help" || command === "-h" || command === "help") {
|
|
63065
|
+
console.log(renderAuthCliHelp());
|
|
63066
|
+
return;
|
|
63067
|
+
}
|
|
63068
|
+
if (command === "list") {
|
|
63069
|
+
await listAuth(rest);
|
|
63070
|
+
return;
|
|
63071
|
+
}
|
|
63072
|
+
if (command === "show") {
|
|
63073
|
+
await showAuth(rest);
|
|
63074
|
+
return;
|
|
63075
|
+
}
|
|
63076
|
+
if (command === "add-user") {
|
|
63077
|
+
await mutateUsers("add", rest);
|
|
63078
|
+
return;
|
|
63079
|
+
}
|
|
63080
|
+
if (command === "remove-user") {
|
|
63081
|
+
await mutateUsers("remove", rest);
|
|
63082
|
+
return;
|
|
63083
|
+
}
|
|
63084
|
+
if (command === "add-permission") {
|
|
63085
|
+
await mutatePermissions("add", rest);
|
|
63086
|
+
return;
|
|
63087
|
+
}
|
|
63088
|
+
if (command === "remove-permission") {
|
|
63089
|
+
await mutatePermissions("remove", rest);
|
|
63090
|
+
return;
|
|
63091
|
+
}
|
|
63092
|
+
throw new Error(renderAuthCliHelp());
|
|
62733
63093
|
}
|
|
62734
63094
|
|
|
62735
63095
|
// src/channels/mode-config-shared.ts
|
|
63096
|
+
function createTelegramGroupRoute() {
|
|
63097
|
+
return {
|
|
63098
|
+
requireMention: true,
|
|
63099
|
+
allowBots: false,
|
|
63100
|
+
topics: {}
|
|
63101
|
+
};
|
|
63102
|
+
}
|
|
63103
|
+
function ensureTelegramGroupRoute(config, chatId) {
|
|
63104
|
+
const existing = config.channels.telegram.groups[chatId];
|
|
63105
|
+
if (existing) {
|
|
63106
|
+
return existing;
|
|
63107
|
+
}
|
|
63108
|
+
const created = createTelegramGroupRoute();
|
|
63109
|
+
config.channels.telegram.groups[chatId] = created;
|
|
63110
|
+
return created;
|
|
63111
|
+
}
|
|
63112
|
+
function ensureTelegramTopicRoute(config, chatId, topicId) {
|
|
63113
|
+
const group = ensureTelegramGroupRoute(config, chatId);
|
|
63114
|
+
const existing = group.topics[topicId];
|
|
63115
|
+
if (existing) {
|
|
63116
|
+
return existing;
|
|
63117
|
+
}
|
|
63118
|
+
const created = {};
|
|
63119
|
+
group.topics[topicId] = created;
|
|
63120
|
+
return created;
|
|
63121
|
+
}
|
|
62736
63122
|
function getModeValue(source, field) {
|
|
62737
63123
|
return source[field];
|
|
62738
63124
|
}
|
|
62739
63125
|
function setModeValue(source, field, value) {
|
|
62740
63126
|
source[field] = value;
|
|
62741
63127
|
}
|
|
63128
|
+
var EMPTY_MODE_SOURCE = {};
|
|
62742
63129
|
function resolveSlackConfigTarget(config, field, params) {
|
|
62743
63130
|
if (!params.target) {
|
|
62744
63131
|
return {
|
|
@@ -62820,22 +63207,25 @@ function resolveTelegramConfigTarget(config, field, params) {
|
|
|
62820
63207
|
};
|
|
62821
63208
|
}
|
|
62822
63209
|
const group = config.channels.telegram.groups[chatId];
|
|
62823
|
-
if (!group) {
|
|
62824
|
-
throw new Error(renderTelegramRouteChoiceMessage({ chatId }));
|
|
62825
|
-
}
|
|
62826
63210
|
if (topicId) {
|
|
62827
|
-
const topic = group
|
|
62828
|
-
if (!topic) {
|
|
62829
|
-
throw new Error(renderTelegramRouteChoiceMessage({ chatId, topicId }));
|
|
62830
|
-
}
|
|
63211
|
+
const topic = group?.topics?.[topicId];
|
|
62831
63212
|
return {
|
|
62832
|
-
get: () => getModeValue(topic, field) ?? getModeValue(group, field) ?? getModeValue(config.channels.telegram, field),
|
|
63213
|
+
get: () => getModeValue(topic ?? EMPTY_MODE_SOURCE, field) ?? getModeValue(group ?? EMPTY_MODE_SOURCE, field) ?? getModeValue(config.channels.telegram, field),
|
|
62833
63214
|
set: (value) => {
|
|
62834
|
-
setModeValue(
|
|
63215
|
+
setModeValue(ensureTelegramTopicRoute(config, chatId, topicId), field, value);
|
|
62835
63216
|
},
|
|
62836
63217
|
label: `telegram topic ${chatId}/${topicId}`
|
|
62837
63218
|
};
|
|
62838
63219
|
}
|
|
63220
|
+
if (!group) {
|
|
63221
|
+
return {
|
|
63222
|
+
get: () => getModeValue(config.channels.telegram, field),
|
|
63223
|
+
set: (value) => {
|
|
63224
|
+
setModeValue(ensureTelegramGroupRoute(config, chatId), field, value);
|
|
63225
|
+
},
|
|
63226
|
+
label: `telegram group ${chatId}`
|
|
63227
|
+
};
|
|
63228
|
+
}
|
|
62839
63229
|
return {
|
|
62840
63230
|
get: () => getModeValue(group, field) ?? getModeValue(config.channels.telegram, field),
|
|
62841
63231
|
set: (value) => {
|
|
@@ -62876,11 +63266,11 @@ function renderFieldLabel(field) {
|
|
|
62876
63266
|
}
|
|
62877
63267
|
|
|
62878
63268
|
// src/channels/additional-message-mode-config.ts
|
|
62879
|
-
function
|
|
63269
|
+
function getEditableConfigPath4() {
|
|
62880
63270
|
return process.env.CLISBOT_CONFIG_PATH;
|
|
62881
63271
|
}
|
|
62882
63272
|
async function getConversationAdditionalMessageMode(params) {
|
|
62883
|
-
const { config } = await readEditableConfig(
|
|
63273
|
+
const { config } = await readEditableConfig(getEditableConfigPath4());
|
|
62884
63274
|
const target = resolveConfiguredSurfaceModeTarget(config, "additionalMessageMode", buildConfiguredTargetFromIdentity(params.identity));
|
|
62885
63275
|
return {
|
|
62886
63276
|
label: target.label,
|
|
@@ -62888,7 +63278,7 @@ async function getConversationAdditionalMessageMode(params) {
|
|
|
62888
63278
|
};
|
|
62889
63279
|
}
|
|
62890
63280
|
async function setConversationAdditionalMessageMode(params) {
|
|
62891
|
-
const { config, configPath } = await readEditableConfig(
|
|
63281
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath4());
|
|
62892
63282
|
const target = resolveConfiguredSurfaceModeTarget(config, "additionalMessageMode", buildConfiguredTargetFromIdentity(params.identity));
|
|
62893
63283
|
target.set(params.additionalMessageMode);
|
|
62894
63284
|
await writeEditableConfig(configPath, config);
|
|
@@ -62899,7 +63289,7 @@ async function setConversationAdditionalMessageMode(params) {
|
|
|
62899
63289
|
};
|
|
62900
63290
|
}
|
|
62901
63291
|
async function getConfiguredAdditionalMessageMode(params) {
|
|
62902
|
-
const { config, configPath } = await readEditableConfig(
|
|
63292
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath4());
|
|
62903
63293
|
const target = resolveConfiguredSurfaceModeTarget(config, "additionalMessageMode", params);
|
|
62904
63294
|
return {
|
|
62905
63295
|
configPath,
|
|
@@ -62908,7 +63298,7 @@ async function getConfiguredAdditionalMessageMode(params) {
|
|
|
62908
63298
|
};
|
|
62909
63299
|
}
|
|
62910
63300
|
async function setConfiguredAdditionalMessageMode(params) {
|
|
62911
|
-
const { config, configPath } = await readEditableConfig(
|
|
63301
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath4());
|
|
62912
63302
|
const target = resolveConfiguredSurfaceModeTarget(config, "additionalMessageMode", params);
|
|
62913
63303
|
target.set(params.additionalMessageMode);
|
|
62914
63304
|
await writeEditableConfig(configPath, config);
|
|
@@ -62920,11 +63310,11 @@ async function setConfiguredAdditionalMessageMode(params) {
|
|
|
62920
63310
|
}
|
|
62921
63311
|
|
|
62922
63312
|
// src/channels/response-mode-config.ts
|
|
62923
|
-
function
|
|
63313
|
+
function getEditableConfigPath5() {
|
|
62924
63314
|
return process.env.CLISBOT_CONFIG_PATH;
|
|
62925
63315
|
}
|
|
62926
63316
|
async function getConversationResponseMode(params) {
|
|
62927
|
-
const { config } = await readEditableConfig(
|
|
63317
|
+
const { config } = await readEditableConfig(getEditableConfigPath5());
|
|
62928
63318
|
const target = resolveConfiguredSurfaceModeTarget(config, "responseMode", buildConfiguredTargetFromIdentity(params.identity));
|
|
62929
63319
|
return {
|
|
62930
63320
|
label: target.label,
|
|
@@ -62932,7 +63322,7 @@ async function getConversationResponseMode(params) {
|
|
|
62932
63322
|
};
|
|
62933
63323
|
}
|
|
62934
63324
|
async function setConversationResponseMode(params) {
|
|
62935
|
-
const { config, configPath } = await readEditableConfig(
|
|
63325
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath5());
|
|
62936
63326
|
const target = resolveConfiguredSurfaceModeTarget(config, "responseMode", buildConfiguredTargetFromIdentity(params.identity));
|
|
62937
63327
|
target.set(params.responseMode);
|
|
62938
63328
|
await writeEditableConfig(configPath, config);
|
|
@@ -62943,7 +63333,7 @@ async function setConversationResponseMode(params) {
|
|
|
62943
63333
|
};
|
|
62944
63334
|
}
|
|
62945
63335
|
async function getConfiguredResponseMode(params) {
|
|
62946
|
-
const { config, configPath } = await readEditableConfig(
|
|
63336
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath5());
|
|
62947
63337
|
const target = resolveConfiguredSurfaceModeTarget(config, "responseMode", params);
|
|
62948
63338
|
return {
|
|
62949
63339
|
configPath,
|
|
@@ -62952,7 +63342,7 @@ async function getConfiguredResponseMode(params) {
|
|
|
62952
63342
|
};
|
|
62953
63343
|
}
|
|
62954
63344
|
async function setConfiguredResponseMode(params) {
|
|
62955
|
-
const { config, configPath } = await readEditableConfig(
|
|
63345
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath5());
|
|
62956
63346
|
const target = resolveConfiguredSurfaceModeTarget(config, "responseMode", params);
|
|
62957
63347
|
target.set(params.responseMode);
|
|
62958
63348
|
await writeEditableConfig(configPath, config);
|
|
@@ -62978,7 +63368,7 @@ async function runChannelPrivilegeCli(_args) {
|
|
|
62978
63368
|
|
|
62979
63369
|
// src/control/channels-cli.ts
|
|
62980
63370
|
var AUTH_USER_GUIDE_DOC_PATH = "docs/user-guide/auth-and-roles.md";
|
|
62981
|
-
function
|
|
63371
|
+
function getEditableConfigPath6() {
|
|
62982
63372
|
return process.env.CLISBOT_CONFIG_PATH;
|
|
62983
63373
|
}
|
|
62984
63374
|
function renderChannelsHelp() {
|
|
@@ -63127,7 +63517,7 @@ function getAgentId(args) {
|
|
|
63127
63517
|
return parseOptionValue2(args, "--agent") ?? "default";
|
|
63128
63518
|
}
|
|
63129
63519
|
async function setChannelEnabled(action, channel) {
|
|
63130
|
-
const { config, configPath } = await readEditableConfig(
|
|
63520
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath6());
|
|
63131
63521
|
const enabled = action === "enable";
|
|
63132
63522
|
const current = config.channels[channel].enabled;
|
|
63133
63523
|
if (current === enabled) {
|
|
@@ -63147,7 +63537,7 @@ async function addTelegramGroup(args) {
|
|
|
63147
63537
|
if (!chatId) {
|
|
63148
63538
|
throw new Error("Usage: clisbot channels add telegram-group <chatId> [--topic <topicId>] [--agent <id>] [--require-mention true|false]");
|
|
63149
63539
|
}
|
|
63150
|
-
const { config, configPath } = await readEditableConfig(
|
|
63540
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath6());
|
|
63151
63541
|
const topicId = parseOptionValue2(args, "--topic");
|
|
63152
63542
|
const agentId = getAgentId(args);
|
|
63153
63543
|
const requireMention = parseBooleanOption(args, "--require-mention", true);
|
|
@@ -63200,7 +63590,7 @@ async function removeTelegramGroup(args) {
|
|
|
63200
63590
|
if (!chatId) {
|
|
63201
63591
|
throw new Error("Usage: clisbot channels remove telegram-group <chatId> [--topic <topicId>]");
|
|
63202
63592
|
}
|
|
63203
|
-
const { config, configPath } = await readEditableConfig(
|
|
63593
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath6());
|
|
63204
63594
|
const topicId = parseOptionValue2(args, "--topic");
|
|
63205
63595
|
const groupRoute = config.channels.telegram.groups[chatId];
|
|
63206
63596
|
if (!groupRoute) {
|
|
@@ -63230,7 +63620,7 @@ async function addSlackRoute(kind, args) {
|
|
|
63230
63620
|
if (!routeId) {
|
|
63231
63621
|
throw new Error(`Usage: clisbot channels add slack-${kind} <${kind}Id> [--agent <id>] [--require-mention true|false]`);
|
|
63232
63622
|
}
|
|
63233
|
-
const { config, configPath } = await readEditableConfig(
|
|
63623
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath6());
|
|
63234
63624
|
const agentId = getAgentId(args);
|
|
63235
63625
|
const requireMention = parseBooleanOption(args, "--require-mention", false);
|
|
63236
63626
|
const target = kind === "channel" ? config.channels.slack.channels : config.channels.slack.groups;
|
|
@@ -63254,7 +63644,7 @@ async function removeSlackRoute(kind, args) {
|
|
|
63254
63644
|
if (!routeId) {
|
|
63255
63645
|
throw new Error(`Usage: clisbot channels remove slack-${kind} <${kind}Id>`);
|
|
63256
63646
|
}
|
|
63257
|
-
const { config, configPath } = await readEditableConfig(
|
|
63647
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath6());
|
|
63258
63648
|
const target = kind === "channel" ? config.channels.slack.channels : config.channels.slack.groups;
|
|
63259
63649
|
if (!target[routeId]) {
|
|
63260
63650
|
console.log(`slack ${kind} route ${routeId} is not configured`);
|
|
@@ -63267,7 +63657,7 @@ async function removeSlackRoute(kind, args) {
|
|
|
63267
63657
|
console.log(`config: ${configPath}`);
|
|
63268
63658
|
}
|
|
63269
63659
|
async function setToken(target, value) {
|
|
63270
|
-
const { config, configPath } = await readEditableConfig(
|
|
63660
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath6());
|
|
63271
63661
|
if (target === "slack-app") {
|
|
63272
63662
|
config.channels.slack.appToken = value;
|
|
63273
63663
|
const defaultAccountId = config.channels.slack.defaultAccount || "default";
|
|
@@ -63299,7 +63689,7 @@ async function setToken(target, value) {
|
|
|
63299
63689
|
console.log(`config: ${configPath}`);
|
|
63300
63690
|
}
|
|
63301
63691
|
async function clearToken(target) {
|
|
63302
|
-
const { config, configPath } = await readEditableConfig(
|
|
63692
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath6());
|
|
63303
63693
|
if (target === "slack-app") {
|
|
63304
63694
|
config.channels.slack.appToken = "";
|
|
63305
63695
|
const defaultAccountId = config.channels.slack.defaultAccount || "default";
|
|
@@ -64038,7 +64428,7 @@ class SessionStore {
|
|
|
64038
64428
|
}
|
|
64039
64429
|
|
|
64040
64430
|
// src/control/loops-cli.ts
|
|
64041
|
-
function
|
|
64431
|
+
function getEditableConfigPath7() {
|
|
64042
64432
|
return process.env.CLISBOT_CONFIG_PATH;
|
|
64043
64433
|
}
|
|
64044
64434
|
function renderLoopsHelp() {
|
|
@@ -64096,7 +64486,7 @@ function getSessionState(sessionStorePath) {
|
|
|
64096
64486
|
return new AgentSessionState(new SessionStore(sessionStorePath));
|
|
64097
64487
|
}
|
|
64098
64488
|
async function loadLoopControlState() {
|
|
64099
|
-
const configPath = await ensureEditableConfigFile(
|
|
64489
|
+
const configPath = await ensureEditableConfigFile(getEditableConfigPath7());
|
|
64100
64490
|
const loadedConfig = await loadConfigWithoutEnvResolution(configPath);
|
|
64101
64491
|
const sessionStorePath = resolveSessionStorePath(loadedConfig);
|
|
64102
64492
|
return {
|
|
@@ -64272,6 +64662,9 @@ function buildReplyCommand(params) {
|
|
|
64272
64662
|
const lines = [`${params.command} message send \\`];
|
|
64273
64663
|
if (params.identity.platform === "slack") {
|
|
64274
64664
|
lines.push(" --channel slack \\");
|
|
64665
|
+
if (params.identity.accountId) {
|
|
64666
|
+
lines.push(` --account ${params.identity.accountId} \\`);
|
|
64667
|
+
}
|
|
64275
64668
|
lines.push(` --target channel:${params.identity.channelId ?? ""} \\`);
|
|
64276
64669
|
if (params.identity.threadTs) {
|
|
64277
64670
|
lines.push(` --thread-id ${params.identity.threadTs} \\`);
|
|
@@ -64286,6 +64679,9 @@ function buildReplyCommand(params) {
|
|
|
64286
64679
|
`);
|
|
64287
64680
|
}
|
|
64288
64681
|
lines.push(" --channel telegram \\");
|
|
64682
|
+
if (params.identity.accountId) {
|
|
64683
|
+
lines.push(` --account ${params.identity.accountId} \\`);
|
|
64684
|
+
}
|
|
64289
64685
|
lines.push(` --target ${params.identity.chatId ?? ""} \\`);
|
|
64290
64686
|
if (params.identity.topicId) {
|
|
64291
64687
|
lines.push(` --thread-id ${params.identity.topicId} \\`);
|
|
@@ -64300,6 +64696,57 @@ function buildReplyCommand(params) {
|
|
|
64300
64696
|
`);
|
|
64301
64697
|
}
|
|
64302
64698
|
|
|
64699
|
+
// src/channels/surface-notifications.ts
|
|
64700
|
+
function sanitizeInlineCode(text) {
|
|
64701
|
+
return text.replaceAll("`", "'");
|
|
64702
|
+
}
|
|
64703
|
+
function summarizeSurfaceNotificationText(text, maxLength = 60) {
|
|
64704
|
+
const singleLine = sanitizeInlineCode(text.replace(/\s+/g, " ").trim());
|
|
64705
|
+
if (!singleLine) {
|
|
64706
|
+
return "(empty)";
|
|
64707
|
+
}
|
|
64708
|
+
if (singleLine.length <= maxLength) {
|
|
64709
|
+
return singleLine;
|
|
64710
|
+
}
|
|
64711
|
+
return `${singleLine.slice(0, maxLength - 3)}...`;
|
|
64712
|
+
}
|
|
64713
|
+
function renderQueueStartNotification(params) {
|
|
64714
|
+
if (params.mode === "none") {
|
|
64715
|
+
return;
|
|
64716
|
+
}
|
|
64717
|
+
const summary = summarizeSurfaceNotificationText(params.promptSummary);
|
|
64718
|
+
if (params.mode === "full") {
|
|
64719
|
+
return `Queued message is now running for agent \`${params.agentId}\`: \`${summary}\`.`;
|
|
64720
|
+
}
|
|
64721
|
+
return `Queued message is now running: \`${summary}\`.`;
|
|
64722
|
+
}
|
|
64723
|
+
function renderLoopScheduleSegment(params) {
|
|
64724
|
+
if (params.kind === "calendar") {
|
|
64725
|
+
return formatCalendarLoopSchedule({
|
|
64726
|
+
cadence: params.cadence,
|
|
64727
|
+
dayOfWeek: params.dayOfWeek,
|
|
64728
|
+
localTime: params.localTime
|
|
64729
|
+
});
|
|
64730
|
+
}
|
|
64731
|
+
return `every ${formatLoopIntervalShort(params.intervalMs)}`;
|
|
64732
|
+
}
|
|
64733
|
+
function renderLoopStartNotification(params) {
|
|
64734
|
+
if (params.mode === "none") {
|
|
64735
|
+
return;
|
|
64736
|
+
}
|
|
64737
|
+
const summary = summarizeSurfaceNotificationText(params.promptSummary);
|
|
64738
|
+
const segments = [
|
|
64739
|
+
`\`${summary}\``,
|
|
64740
|
+
renderLoopScheduleSegment(params),
|
|
64741
|
+
`next run \`${new Date(params.nextRunAt).toISOString()}\``,
|
|
64742
|
+
`remaining \`${params.remainingRuns}/${params.maxRuns}\``
|
|
64743
|
+
];
|
|
64744
|
+
if (params.mode === "full") {
|
|
64745
|
+
return `Loop \`${params.loopId}\` is now running for agent \`${params.agentId}\`: ${segments.join(" · ")}.`;
|
|
64746
|
+
}
|
|
64747
|
+
return `Loop \`${params.loopId}\` is now running: ${segments.join(" · ")}.`;
|
|
64748
|
+
}
|
|
64749
|
+
|
|
64303
64750
|
// src/agents/session-key.ts
|
|
64304
64751
|
var DEFAULT_MAIN_KEY = "main";
|
|
64305
64752
|
var DEFAULT_ACCOUNT_ID = "default";
|
|
@@ -66583,6 +67030,7 @@ class AgentService {
|
|
|
66583
67030
|
cleanupTimer;
|
|
66584
67031
|
loopTimers = new Set;
|
|
66585
67032
|
intervalLoops = new Map;
|
|
67033
|
+
surfaceNotificationHandlers = new Map;
|
|
66586
67034
|
constructor(loadedConfig, deps = {}) {
|
|
66587
67035
|
this.loadedConfig = loadedConfig;
|
|
66588
67036
|
this.tmuxClient = deps.tmux ?? new TmuxClient(this.loadedConfig.raw.tmux.socketPath);
|
|
@@ -66611,7 +67059,9 @@ class AgentService {
|
|
|
66611
67059
|
}
|
|
66612
67060
|
await this.runnerSessions.runSessionCleanup();
|
|
66613
67061
|
this.cleanupTimer = setInterval(() => {
|
|
66614
|
-
this.runnerSessions.runSessionCleanup()
|
|
67062
|
+
this.runnerSessions.runSessionCleanup().catch((error) => {
|
|
67063
|
+
console.error("session cleanup failed", error);
|
|
67064
|
+
});
|
|
66615
67065
|
}, cleanup.intervalMinutes * 60000);
|
|
66616
67066
|
}
|
|
66617
67067
|
async stop() {
|
|
@@ -66635,6 +67085,12 @@ class AgentService {
|
|
|
66635
67085
|
async cleanupStaleSessions() {
|
|
66636
67086
|
await this.runnerSessions.runSessionCleanup();
|
|
66637
67087
|
}
|
|
67088
|
+
registerSurfaceNotificationHandler(params) {
|
|
67089
|
+
this.surfaceNotificationHandlers.set(this.getSurfaceNotificationHandlerKey(params.platform, params.accountId), params.handler);
|
|
67090
|
+
}
|
|
67091
|
+
unregisterSurfaceNotificationHandler(params) {
|
|
67092
|
+
this.surfaceNotificationHandlers.delete(this.getSurfaceNotificationHandlerKey(params.platform, params.accountId));
|
|
67093
|
+
}
|
|
66638
67094
|
resolveTarget(target) {
|
|
66639
67095
|
return resolveAgentTarget(this.loadedConfig, target);
|
|
66640
67096
|
}
|
|
@@ -66743,7 +67199,9 @@ class AgentService {
|
|
|
66743
67199
|
target: params.target,
|
|
66744
67200
|
loop
|
|
66745
67201
|
});
|
|
66746
|
-
await this.runIntervalLoopIteration(loop.id
|
|
67202
|
+
await this.runIntervalLoopIteration(loop.id, {
|
|
67203
|
+
notifyStart: false
|
|
67204
|
+
});
|
|
66747
67205
|
return this.getIntervalLoop(loop.id);
|
|
66748
67206
|
}
|
|
66749
67207
|
async createCalendarLoop(params) {
|
|
@@ -66905,7 +67363,7 @@ class AgentService {
|
|
|
66905
67363
|
}
|
|
66906
67364
|
this.intervalLoops.delete(loopId);
|
|
66907
67365
|
}
|
|
66908
|
-
async runIntervalLoopIteration(loopId) {
|
|
67366
|
+
async runIntervalLoopIteration(loopId, options = {}) {
|
|
66909
67367
|
const managed = this.intervalLoops.get(loopId);
|
|
66910
67368
|
if (!managed) {
|
|
66911
67369
|
return;
|
|
@@ -66941,6 +67399,9 @@ class AgentService {
|
|
|
66941
67399
|
this.dropManagedIntervalLoop(loopId);
|
|
66942
67400
|
return;
|
|
66943
67401
|
}
|
|
67402
|
+
if (options.notifyStart !== false) {
|
|
67403
|
+
await this.notifyManagedLoopStart(managed.target, nextLoopState);
|
|
67404
|
+
}
|
|
66944
67405
|
const promptText = this.buildManagedLoopPrompt(managed.target.agentId, nextLoopState);
|
|
66945
67406
|
const { result } = this.enqueuePrompt(managed.target, promptText, {
|
|
66946
67407
|
observerId: `loop:${loopId}:${attemptedRuns}`,
|
|
@@ -66960,6 +67421,101 @@ class AgentService {
|
|
|
66960
67421
|
}
|
|
66961
67422
|
this.scheduleIntervalLoopTimer(loopId, Math.max(0, nextLoopState.nextRunAt - now));
|
|
66962
67423
|
}
|
|
67424
|
+
getSurfaceNotificationHandlerKey(platform, accountId) {
|
|
67425
|
+
return `${platform}:${accountId?.trim() || "default"}`;
|
|
67426
|
+
}
|
|
67427
|
+
async notifySurface(request) {
|
|
67428
|
+
const handler = this.surfaceNotificationHandlers.get(this.getSurfaceNotificationHandlerKey(request.binding.platform, request.binding.accountId));
|
|
67429
|
+
if (!handler) {
|
|
67430
|
+
return;
|
|
67431
|
+
}
|
|
67432
|
+
await handler(request);
|
|
67433
|
+
}
|
|
67434
|
+
resolveLoopSurfaceNotifications(identity) {
|
|
67435
|
+
if (identity.platform === "slack") {
|
|
67436
|
+
const channelConfig2 = this.loadedConfig.raw.channels.slack;
|
|
67437
|
+
let resolved2 = {
|
|
67438
|
+
queueStart: channelConfig2.surfaceNotifications?.queueStart ?? "brief",
|
|
67439
|
+
loopStart: channelConfig2.surfaceNotifications?.loopStart ?? "brief"
|
|
67440
|
+
};
|
|
67441
|
+
if (identity.conversationKind === "dm") {
|
|
67442
|
+
return {
|
|
67443
|
+
...resolved2,
|
|
67444
|
+
...channelConfig2.directMessages.surfaceNotifications ?? {}
|
|
67445
|
+
};
|
|
67446
|
+
}
|
|
67447
|
+
const routeCollection = identity.conversationKind === "group" ? channelConfig2.groups : channelConfig2.channels;
|
|
67448
|
+
const route = identity.channelId ? routeCollection[identity.channelId] ?? routeCollection["*"] : undefined;
|
|
67449
|
+
return {
|
|
67450
|
+
...resolved2,
|
|
67451
|
+
...route?.surfaceNotifications ?? {}
|
|
67452
|
+
};
|
|
67453
|
+
}
|
|
67454
|
+
const channelConfig = this.loadedConfig.raw.channels.telegram;
|
|
67455
|
+
let resolved = {
|
|
67456
|
+
queueStart: channelConfig.surfaceNotifications?.queueStart ?? "brief",
|
|
67457
|
+
loopStart: channelConfig.surfaceNotifications?.loopStart ?? "brief"
|
|
67458
|
+
};
|
|
67459
|
+
if (identity.conversationKind === "dm") {
|
|
67460
|
+
return {
|
|
67461
|
+
...resolved,
|
|
67462
|
+
...channelConfig.directMessages.surfaceNotifications ?? {}
|
|
67463
|
+
};
|
|
67464
|
+
}
|
|
67465
|
+
const groupRoute = identity.chatId ? channelConfig.groups[identity.chatId] ?? channelConfig.groups["*"] : undefined;
|
|
67466
|
+
resolved = {
|
|
67467
|
+
...resolved,
|
|
67468
|
+
...groupRoute?.surfaceNotifications ?? {}
|
|
67469
|
+
};
|
|
67470
|
+
if (identity.conversationKind === "topic" && identity.topicId) {
|
|
67471
|
+
return {
|
|
67472
|
+
...resolved,
|
|
67473
|
+
...groupRoute?.topics?.[identity.topicId]?.surfaceNotifications ?? {}
|
|
67474
|
+
};
|
|
67475
|
+
}
|
|
67476
|
+
return resolved;
|
|
67477
|
+
}
|
|
67478
|
+
async notifyManagedLoopStart(target, loop) {
|
|
67479
|
+
if (!loop.surfaceBinding) {
|
|
67480
|
+
return;
|
|
67481
|
+
}
|
|
67482
|
+
const identity = this.buildLoopChannelIdentity(loop.surfaceBinding);
|
|
67483
|
+
const notifications = this.resolveLoopSurfaceNotifications(identity);
|
|
67484
|
+
const text = loop.kind === "calendar" ? renderLoopStartNotification({
|
|
67485
|
+
mode: notifications.loopStart,
|
|
67486
|
+
agentId: target.agentId,
|
|
67487
|
+
loopId: loop.id,
|
|
67488
|
+
promptSummary: loop.promptSummary,
|
|
67489
|
+
cadence: loop.cadence,
|
|
67490
|
+
dayOfWeek: loop.dayOfWeek,
|
|
67491
|
+
localTime: loop.localTime,
|
|
67492
|
+
timezone: loop.timezone,
|
|
67493
|
+
nextRunAt: loop.nextRunAt,
|
|
67494
|
+
remainingRuns: Math.max(0, loop.maxRuns - loop.attemptedRuns),
|
|
67495
|
+
maxRuns: loop.maxRuns,
|
|
67496
|
+
kind: "calendar"
|
|
67497
|
+
}) : renderLoopStartNotification({
|
|
67498
|
+
mode: notifications.loopStart,
|
|
67499
|
+
agentId: target.agentId,
|
|
67500
|
+
loopId: loop.id,
|
|
67501
|
+
promptSummary: loop.promptSummary,
|
|
67502
|
+
intervalMs: loop.intervalMs,
|
|
67503
|
+
nextRunAt: loop.nextRunAt,
|
|
67504
|
+
remainingRuns: Math.max(0, loop.maxRuns - loop.attemptedRuns),
|
|
67505
|
+
maxRuns: loop.maxRuns
|
|
67506
|
+
});
|
|
67507
|
+
if (!text) {
|
|
67508
|
+
return;
|
|
67509
|
+
}
|
|
67510
|
+
try {
|
|
67511
|
+
await this.notifySurface({
|
|
67512
|
+
binding: loop.surfaceBinding,
|
|
67513
|
+
text
|
|
67514
|
+
});
|
|
67515
|
+
} catch (error) {
|
|
67516
|
+
console.error("loop start notification failed", error);
|
|
67517
|
+
}
|
|
67518
|
+
}
|
|
66963
67519
|
scheduleIntervalLoopTimer(loopId, delayMs) {
|
|
66964
67520
|
const managed = this.intervalLoops.get(loopId);
|
|
66965
67521
|
if (!managed) {
|
|
@@ -66976,7 +67532,9 @@ class AgentService {
|
|
|
66976
67532
|
return;
|
|
66977
67533
|
}
|
|
66978
67534
|
current.timer = undefined;
|
|
66979
|
-
this.runIntervalLoopIteration(loopId
|
|
67535
|
+
this.runIntervalLoopIteration(loopId, {
|
|
67536
|
+
notifyStart: true
|
|
67537
|
+
}).catch((error) => {
|
|
66980
67538
|
if (this.shouldSuppressLoopShutdownError(error)) {
|
|
66981
67539
|
return;
|
|
66982
67540
|
}
|
|
@@ -67034,6 +67592,7 @@ class AgentService {
|
|
|
67034
67592
|
buildLoopChannelIdentity(binding) {
|
|
67035
67593
|
return {
|
|
67036
67594
|
platform: binding.platform,
|
|
67595
|
+
accountId: binding.accountId,
|
|
67037
67596
|
conversationKind: binding.conversationKind,
|
|
67038
67597
|
channelId: binding.channelId,
|
|
67039
67598
|
chatId: binding.chatId,
|
|
@@ -67649,11 +68208,11 @@ function formatChannelFollowUpStatus(params) {
|
|
|
67649
68208
|
}
|
|
67650
68209
|
|
|
67651
68210
|
// src/channels/streaming-config.ts
|
|
67652
|
-
function
|
|
68211
|
+
function getEditableConfigPath8() {
|
|
67653
68212
|
return process.env.CLISBOT_CONFIG_PATH;
|
|
67654
68213
|
}
|
|
67655
68214
|
async function getConversationStreaming(params) {
|
|
67656
|
-
const { config } = await readEditableConfig(
|
|
68215
|
+
const { config } = await readEditableConfig(getEditableConfigPath8());
|
|
67657
68216
|
const target = resolveConfiguredSurfaceModeTarget(config, "streaming", buildConfiguredTargetFromIdentity(params.identity));
|
|
67658
68217
|
return {
|
|
67659
68218
|
label: target.label,
|
|
@@ -67661,7 +68220,7 @@ async function getConversationStreaming(params) {
|
|
|
67661
68220
|
};
|
|
67662
68221
|
}
|
|
67663
68222
|
async function setConversationStreaming(params) {
|
|
67664
|
-
const { config, configPath } = await readEditableConfig(
|
|
68223
|
+
const { config, configPath } = await readEditableConfig(getEditableConfigPath8());
|
|
67665
68224
|
const target = resolveConfiguredSurfaceModeTarget(config, "streaming", buildConfiguredTargetFromIdentity(params.identity));
|
|
67666
68225
|
target.set(params.streaming);
|
|
67667
68226
|
await writeEditableConfig(configPath, config);
|
|
@@ -67742,7 +68301,7 @@ function renderRouteStatusMessage(params) {
|
|
|
67742
68301
|
if (params.identity.topicId) {
|
|
67743
68302
|
lines.push(`topicId: \`${params.identity.topicId}\``);
|
|
67744
68303
|
}
|
|
67745
|
-
lines.push(`streaming: \`${params.route.streaming}\``, `response: \`${params.route.response}\``, `responseMode: \`${params.route.responseMode}\``, `additionalMessageMode: \`${params.route.additionalMessageMode}\``, `verbose: \`${params.route.verbose}\``, `principal: \`${params.auth.principal ?? "(none)"}\``, `appRole: \`${params.auth.appRole}\``, `agentRole: \`${params.auth.agentRole}\``, `mayManageProtectedResources: \`${params.auth.mayManageProtectedResources}\``, `canUseShell: \`${params.auth.canUseShell}\``, `timezone: \`${params.route.timezone ?? "(inherit host/app)"}\``, `followUp.mode: \`${params.followUpState.overrideMode ?? params.route.followUp.mode}\``, `followUp.windowMinutes: \`${formatFollowUpTtlMinutes(params.route.followUp.participationTtlMs)}\``, `run.state: \`${params.runtimeState.state}\``);
|
|
68304
|
+
lines.push(`streaming: \`${params.route.streaming}\``, `response: \`${params.route.response}\``, `responseMode: \`${params.route.responseMode}\``, `additionalMessageMode: \`${params.route.additionalMessageMode}\``, `surfaceNotifications.queueStart: \`${params.route.surfaceNotifications.queueStart}\``, `surfaceNotifications.loopStart: \`${params.route.surfaceNotifications.loopStart}\``, `verbose: \`${params.route.verbose}\``, `principal: \`${params.auth.principal ?? "(none)"}\``, `appRole: \`${params.auth.appRole}\``, `agentRole: \`${params.auth.agentRole}\``, `mayManageProtectedResources: \`${params.auth.mayManageProtectedResources}\``, `canUseShell: \`${params.auth.canUseShell}\``, `timezone: \`${params.route.timezone ?? "(inherit host/app)"}\``, `followUp.mode: \`${params.followUpState.overrideMode ?? params.route.followUp.mode}\``, `followUp.windowMinutes: \`${formatFollowUpTtlMinutes(params.route.followUp.participationTtlMs)}\``, `run.state: \`${params.runtimeState.state}\``);
|
|
67746
68305
|
if (params.runtimeState.startedAt) {
|
|
67747
68306
|
lines.push(`run.startedAt: \`${new Date(params.runtimeState.startedAt).toISOString()}\``);
|
|
67748
68307
|
}
|
|
@@ -67979,6 +68538,7 @@ async function resolveLoopPromptText(params) {
|
|
|
67979
68538
|
function buildLoopSurfaceBinding(identity) {
|
|
67980
68539
|
return {
|
|
67981
68540
|
platform: identity.platform,
|
|
68541
|
+
accountId: identity.accountId,
|
|
67982
68542
|
conversationKind: identity.conversationKind,
|
|
67983
68543
|
channelId: identity.channelId,
|
|
67984
68544
|
chatId: identity.chatId,
|
|
@@ -67995,6 +68555,8 @@ async function executePromptDelivery(params) {
|
|
|
67995
68555
|
let loggedFirstRunningUpdate = false;
|
|
67996
68556
|
let activePreviewStartedAt;
|
|
67997
68557
|
let messageToolPreviewHandedOff = false;
|
|
68558
|
+
let queueStartPending = false;
|
|
68559
|
+
let deferredQueueStartPreview = false;
|
|
67998
68560
|
const paneManagedDelivery = params.route.responseMode === "capture-pane" || params.forceQueuedDelivery === true;
|
|
67999
68561
|
const messageToolPreview = params.route.responseMode === "message-tool" && params.forceQueuedDelivery !== true && params.route.streaming !== "off";
|
|
68000
68562
|
const previewEnabled = params.route.streaming !== "off" && (paneManagedDelivery || messageToolPreview);
|
|
@@ -68069,6 +68631,68 @@ async function executePromptDelivery(params) {
|
|
|
68069
68631
|
agentId: params.route.agentId,
|
|
68070
68632
|
sessionKey: params.sessionTarget.sessionKey
|
|
68071
68633
|
});
|
|
68634
|
+
async function maybeRenderQueueStartNotification() {
|
|
68635
|
+
if (!queueStartPending) {
|
|
68636
|
+
return false;
|
|
68637
|
+
}
|
|
68638
|
+
const text = renderQueueStartNotification({
|
|
68639
|
+
mode: params.queueStartMode ?? "none",
|
|
68640
|
+
agentId: params.route.agentId,
|
|
68641
|
+
promptSummary: params.notificationPromptSummary ?? summarizeSurfaceNotificationText(params.promptText)
|
|
68642
|
+
});
|
|
68643
|
+
if (!text) {
|
|
68644
|
+
queueStartPending = false;
|
|
68645
|
+
return false;
|
|
68646
|
+
}
|
|
68647
|
+
if (previewEnabled && responseChunks.length === 0) {
|
|
68648
|
+
deferredQueueStartPreview = true;
|
|
68649
|
+
return true;
|
|
68650
|
+
}
|
|
68651
|
+
queueStartPending = false;
|
|
68652
|
+
if (responseChunks.length > 0) {
|
|
68653
|
+
const postedNew = await renderResponseText(text);
|
|
68654
|
+
if (postedNew) {
|
|
68655
|
+
await recordVisibleReply("reply", "channel");
|
|
68656
|
+
activePreviewStartedAt = Date.now();
|
|
68657
|
+
}
|
|
68658
|
+
renderedState = {
|
|
68659
|
+
text,
|
|
68660
|
+
body: ""
|
|
68661
|
+
};
|
|
68662
|
+
return true;
|
|
68663
|
+
}
|
|
68664
|
+
const posted = await params.postText(text);
|
|
68665
|
+
if (posted.length > 0) {
|
|
68666
|
+
await recordVisibleReply("reply", "channel");
|
|
68667
|
+
}
|
|
68668
|
+
return posted.length > 0;
|
|
68669
|
+
}
|
|
68670
|
+
function buildInitialPlaceholderText(positionAhead) {
|
|
68671
|
+
if (deferredQueueStartPreview && queueStartPending) {
|
|
68672
|
+
deferredQueueStartPreview = false;
|
|
68673
|
+
queueStartPending = false;
|
|
68674
|
+
return renderQueueStartNotification({
|
|
68675
|
+
mode: params.queueStartMode ?? "none",
|
|
68676
|
+
agentId: params.route.agentId,
|
|
68677
|
+
promptSummary: params.notificationPromptSummary ?? summarizeSurfaceNotificationText(params.promptText)
|
|
68678
|
+
}) ?? renderPlatformInteraction({
|
|
68679
|
+
platform: params.identity.platform,
|
|
68680
|
+
status: positionAhead > 0 ? "queued" : "running",
|
|
68681
|
+
content: "",
|
|
68682
|
+
queuePosition: positionAhead,
|
|
68683
|
+
maxChars: Number.POSITIVE_INFINITY,
|
|
68684
|
+
note: positionAhead > 0 ? "Waiting for the agent queue to clear." : "Working..."
|
|
68685
|
+
});
|
|
68686
|
+
}
|
|
68687
|
+
return renderPlatformInteraction({
|
|
68688
|
+
platform: params.identity.platform,
|
|
68689
|
+
status: positionAhead > 0 ? "queued" : "running",
|
|
68690
|
+
content: "",
|
|
68691
|
+
queuePosition: positionAhead,
|
|
68692
|
+
maxChars: Number.POSITIVE_INFINITY,
|
|
68693
|
+
note: positionAhead > 0 ? "Waiting for the agent queue to clear." : "Working..."
|
|
68694
|
+
});
|
|
68695
|
+
}
|
|
68072
68696
|
try {
|
|
68073
68697
|
const { positionAhead, result } = params.agentService.enqueuePrompt(params.sessionTarget, params.promptText, {
|
|
68074
68698
|
observerId: params.observerId,
|
|
@@ -68077,9 +68701,6 @@ async function executePromptDelivery(params) {
|
|
|
68077
68701
|
if (!paneManagedDelivery && !messageToolPreview) {
|
|
68078
68702
|
return;
|
|
68079
68703
|
}
|
|
68080
|
-
if (params.route.streaming === "off" && update.status === "running") {
|
|
68081
|
-
return;
|
|
68082
|
-
}
|
|
68083
68704
|
if (update.status === "running" && !loggedFirstRunningUpdate) {
|
|
68084
68705
|
loggedFirstRunningUpdate = true;
|
|
68085
68706
|
logLatencyDebug("channel-first-running-update", params.timingContext, {
|
|
@@ -68088,14 +68709,21 @@ async function executePromptDelivery(params) {
|
|
|
68088
68709
|
});
|
|
68089
68710
|
}
|
|
68090
68711
|
await (renderChain = renderChain.then(async () => {
|
|
68712
|
+
let renderedQueueStart = false;
|
|
68713
|
+
if (update.status === "running") {
|
|
68714
|
+
renderedQueueStart = await maybeRenderQueueStartNotification();
|
|
68715
|
+
}
|
|
68716
|
+
if (params.route.streaming === "off" && update.status === "running") {
|
|
68717
|
+
return;
|
|
68718
|
+
}
|
|
68719
|
+
if (renderedQueueStart) {
|
|
68720
|
+
return;
|
|
68721
|
+
}
|
|
68091
68722
|
const signals = await getMessageToolRuntimeSignals();
|
|
68092
68723
|
if (messageToolPreview && typeof activePreviewStartedAt === "number" && (typeof signals.messageToolFinalReplyAt === "number" && signals.messageToolFinalReplyAt >= activePreviewStartedAt || typeof signals.lastMessageToolReplyAt === "number" && signals.lastMessageToolReplyAt >= activePreviewStartedAt)) {
|
|
68093
68724
|
await handoffMessageToolPreview();
|
|
68094
68725
|
return;
|
|
68095
68726
|
}
|
|
68096
|
-
if (messageToolPreview) {
|
|
68097
|
-
return;
|
|
68098
|
-
}
|
|
68099
68727
|
const nextState2 = buildRenderedMessageState({
|
|
68100
68728
|
platform: params.identity.platform,
|
|
68101
68729
|
status: update.status,
|
|
@@ -68119,15 +68747,9 @@ async function executePromptDelivery(params) {
|
|
|
68119
68747
|
}));
|
|
68120
68748
|
}
|
|
68121
68749
|
});
|
|
68750
|
+
queueStartPending = positionAhead > 0 && (params.queueStartMode ?? "none") !== "none";
|
|
68122
68751
|
if (previewEnabled) {
|
|
68123
|
-
const placeholderText =
|
|
68124
|
-
platform: params.identity.platform,
|
|
68125
|
-
status: positionAhead > 0 ? "queued" : "running",
|
|
68126
|
-
content: "",
|
|
68127
|
-
queuePosition: positionAhead,
|
|
68128
|
-
maxChars: Number.POSITIVE_INFINITY,
|
|
68129
|
-
note: positionAhead > 0 ? "Waiting for the agent queue to clear." : "Working..."
|
|
68130
|
-
});
|
|
68752
|
+
const placeholderText = buildInitialPlaceholderText(positionAhead);
|
|
68131
68753
|
const postedNew2 = await renderResponseText(placeholderText);
|
|
68132
68754
|
if (postedNew2) {
|
|
68133
68755
|
await recordVisibleReply("reply", "channel");
|
|
@@ -68157,6 +68779,7 @@ async function executePromptDelivery(params) {
|
|
|
68157
68779
|
}
|
|
68158
68780
|
const finalResult = await result;
|
|
68159
68781
|
await renderChain;
|
|
68782
|
+
await maybeRenderQueueStartNotification();
|
|
68160
68783
|
if (!paneManagedDelivery && messageToolPreviewHandedOff) {
|
|
68161
68784
|
return;
|
|
68162
68785
|
}
|
|
@@ -68641,6 +69264,8 @@ ${renderLoopUsage()}`);
|
|
|
68641
69264
|
route: params.route,
|
|
68642
69265
|
maxChars: params.maxChars,
|
|
68643
69266
|
promptText: buildLoopPromptText(resolvedLoopPrompt.text),
|
|
69267
|
+
queueStartMode: params.route.surfaceNotifications.queueStart,
|
|
69268
|
+
notificationPromptSummary: summarizeLoopPrompt(resolvedLoopPrompt.text, resolvedLoopPrompt.maintenancePrompt),
|
|
68644
69269
|
postText: params.postText,
|
|
68645
69270
|
reconcileText: params.reconcileText,
|
|
68646
69271
|
observerId: `${observerId}:loop:${index + 1}`
|
|
@@ -68781,6 +69406,8 @@ ${escapeCodeFence(shellResult.output)}
|
|
|
68781
69406
|
route: params.route,
|
|
68782
69407
|
maxChars: params.maxChars,
|
|
68783
69408
|
promptText: delayedPromptText,
|
|
69409
|
+
queueStartMode: params.route.surfaceNotifications.queueStart,
|
|
69410
|
+
notificationPromptSummary: explicitQueueMessage ?? summarizeSurfaceNotificationText(params.text),
|
|
68784
69411
|
postText: params.postText,
|
|
68785
69412
|
reconcileText: params.reconcileText,
|
|
68786
69413
|
observerId,
|
|
@@ -68841,6 +69468,23 @@ function isTelegramSenderAllowed(params) {
|
|
|
68841
69468
|
}
|
|
68842
69469
|
|
|
68843
69470
|
// src/auth/resolve.ts
|
|
69471
|
+
function mergeRoleDefinitions2(inherited, override) {
|
|
69472
|
+
return {
|
|
69473
|
+
allow: override?.allow ?? inherited?.allow ?? [],
|
|
69474
|
+
users: override?.users ?? inherited?.users ?? []
|
|
69475
|
+
};
|
|
69476
|
+
}
|
|
69477
|
+
function mergeRoleRecord2(defaults, overrides) {
|
|
69478
|
+
const merged = {};
|
|
69479
|
+
const roleNames = new Set([
|
|
69480
|
+
...Object.keys(defaults ?? {}),
|
|
69481
|
+
...Object.keys(overrides ?? {})
|
|
69482
|
+
]);
|
|
69483
|
+
for (const roleName of roleNames) {
|
|
69484
|
+
merged[roleName] = mergeRoleDefinitions2(defaults?.[roleName], overrides?.[roleName]);
|
|
69485
|
+
}
|
|
69486
|
+
return merged;
|
|
69487
|
+
}
|
|
68844
69488
|
function normalizePrincipal(principal) {
|
|
68845
69489
|
const trimmed = principal.trim();
|
|
68846
69490
|
if (!trimmed) {
|
|
@@ -68888,10 +69532,7 @@ function getAgentAuth(config, agentId) {
|
|
|
68888
69532
|
const override = entry?.auth;
|
|
68889
69533
|
return {
|
|
68890
69534
|
defaultRole: override?.defaultRole ?? defaults.defaultRole,
|
|
68891
|
-
roles:
|
|
68892
|
-
...defaults.roles,
|
|
68893
|
-
...override?.roles ?? {}
|
|
68894
|
-
}
|
|
69535
|
+
roles: mergeRoleRecord2(defaults.roles, override?.roles)
|
|
68895
69536
|
};
|
|
68896
69537
|
}
|
|
68897
69538
|
function getAllowedPermissions(roles, role) {
|
|
@@ -69014,6 +69655,10 @@ function buildSharedChannelRoute(params) {
|
|
|
69014
69655
|
response: params.route?.response ?? params.channelConfig.response,
|
|
69015
69656
|
responseMode: params.route?.responseMode ?? agentEntry?.responseMode ?? params.channelConfig.responseMode,
|
|
69016
69657
|
additionalMessageMode: params.route?.additionalMessageMode ?? agentEntry?.additionalMessageMode ?? params.channelConfig.additionalMessageMode,
|
|
69658
|
+
surfaceNotifications: {
|
|
69659
|
+
queueStart: params.route?.surfaceNotifications?.queueStart ?? params.channelConfig.surfaceNotifications?.queueStart ?? "brief",
|
|
69660
|
+
loopStart: params.route?.surfaceNotifications?.loopStart ?? params.channelConfig.surfaceNotifications?.loopStart ?? "brief"
|
|
69661
|
+
},
|
|
69017
69662
|
verbose: params.route?.verbose ?? params.channelConfig.verbose,
|
|
69018
69663
|
followUp: {
|
|
69019
69664
|
mode: params.route?.followUp?.mode ?? params.channelConfig.followUp.mode,
|
|
@@ -69990,6 +70635,20 @@ class SlackSocketService {
|
|
|
69990
70635
|
appToken: this.accountConfig.appToken,
|
|
69991
70636
|
socketMode: true
|
|
69992
70637
|
});
|
|
70638
|
+
this.agentService.registerSurfaceNotificationHandler({
|
|
70639
|
+
platform: "slack",
|
|
70640
|
+
accountId: this.accountId,
|
|
70641
|
+
handler: async ({ binding, text }) => {
|
|
70642
|
+
if (!binding.channelId) {
|
|
70643
|
+
return;
|
|
70644
|
+
}
|
|
70645
|
+
await postSlackText(this.app.client, {
|
|
70646
|
+
channel: binding.channelId,
|
|
70647
|
+
threadTs: binding.threadTs,
|
|
70648
|
+
text
|
|
70649
|
+
});
|
|
70650
|
+
}
|
|
70651
|
+
});
|
|
69993
70652
|
this.registerEvents();
|
|
69994
70653
|
}
|
|
69995
70654
|
getSlackMaxChars(agentId) {
|
|
@@ -70243,6 +70902,7 @@ class SlackSocketService {
|
|
|
70243
70902
|
const cliTool = getAgentEntry(this.loadedConfig, params.route.agentId)?.cliTool;
|
|
70244
70903
|
const identity = {
|
|
70245
70904
|
platform: "slack",
|
|
70905
|
+
accountId: this.accountId,
|
|
70246
70906
|
conversationKind: params.conversationKind,
|
|
70247
70907
|
senderId: typeof event.user === "string" ? event.user.trim().toUpperCase() : undefined,
|
|
70248
70908
|
channelId,
|
|
@@ -70426,6 +71086,10 @@ class SlackSocketService {
|
|
|
70426
71086
|
await this.startPromise;
|
|
70427
71087
|
}
|
|
70428
71088
|
async stop() {
|
|
71089
|
+
this.agentService.unregisterSurfaceNotificationHandler({
|
|
71090
|
+
platform: "slack",
|
|
71091
|
+
accountId: this.accountId
|
|
71092
|
+
});
|
|
70429
71093
|
await this.app.stop();
|
|
70430
71094
|
}
|
|
70431
71095
|
getBotUserLabel() {
|
|
@@ -71280,6 +71944,33 @@ async function resolveTelegramAttachmentPaths(params) {
|
|
|
71280
71944
|
return attachmentPaths;
|
|
71281
71945
|
}
|
|
71282
71946
|
|
|
71947
|
+
// src/channels/telegram/route-guidance.ts
|
|
71948
|
+
function renderTelegramRouteChoiceMessage(params) {
|
|
71949
|
+
const chatId = String(params.chatId);
|
|
71950
|
+
const topicId = params.topicId != null ? String(params.topicId) : undefined;
|
|
71951
|
+
const lines = [
|
|
71952
|
+
topicId != null ? "clisbot: this Telegram topic is not configured yet." : "clisbot: this Telegram group is not configured yet.",
|
|
71953
|
+
"",
|
|
71954
|
+
"Ask the bot owner to choose one of these:",
|
|
71955
|
+
"",
|
|
71956
|
+
"Add the whole group with the default agent:",
|
|
71957
|
+
`\`clisbot channels add telegram-group ${chatId}\``,
|
|
71958
|
+
"",
|
|
71959
|
+
"Add the whole group with a specific agent:",
|
|
71960
|
+
`\`clisbot channels add telegram-group ${chatId} --agent <id>\``
|
|
71961
|
+
];
|
|
71962
|
+
if (topicId != null) {
|
|
71963
|
+
lines.push("", "Add only this topic with a specific agent:", `\`clisbot channels add telegram-group ${chatId} --topic ${topicId} --agent <id>\``);
|
|
71964
|
+
}
|
|
71965
|
+
if (params.includeConfigPath) {
|
|
71966
|
+
lines.push("", topicId != null ? `Config path: \`channels.telegram.groups."${chatId}".topics."${topicId}"\`` : `Config path: \`channels.telegram.groups."${chatId}"\``);
|
|
71967
|
+
} else {
|
|
71968
|
+
lines.push("", "After that, routed commands such as `/status`, `/stop`, `/nudge`, `/followup`, and `/bash` will work here.");
|
|
71969
|
+
}
|
|
71970
|
+
return lines.join(`
|
|
71971
|
+
`);
|
|
71972
|
+
}
|
|
71973
|
+
|
|
71283
71974
|
// src/channels/telegram/typing.ts
|
|
71284
71975
|
var TELEGRAM_TYPING_HEARTBEAT_MS = 4500;
|
|
71285
71976
|
function logTelegramTypingError(onError, error) {
|
|
@@ -71423,6 +72114,7 @@ class TelegramPollingService {
|
|
|
71423
72114
|
activityStore;
|
|
71424
72115
|
accountId;
|
|
71425
72116
|
accountConfig;
|
|
72117
|
+
reportLifecycle;
|
|
71426
72118
|
botUserId = 0;
|
|
71427
72119
|
botUsername = "";
|
|
71428
72120
|
running = false;
|
|
@@ -71431,13 +72123,35 @@ class TelegramPollingService {
|
|
|
71431
72123
|
activePollController;
|
|
71432
72124
|
inFlightUpdates = new Set;
|
|
71433
72125
|
processingIndicators = new ConversationProcessingIndicatorCoordinator;
|
|
71434
|
-
constructor(loadedConfig, agentService, processedEventsStore, activityStore, accountId = "default", accountConfig) {
|
|
72126
|
+
constructor(loadedConfig, agentService, processedEventsStore, activityStore, accountId = "default", accountConfig, reportLifecycle) {
|
|
71435
72127
|
this.loadedConfig = loadedConfig;
|
|
71436
72128
|
this.agentService = agentService;
|
|
71437
72129
|
this.processedEventsStore = processedEventsStore;
|
|
71438
72130
|
this.activityStore = activityStore;
|
|
71439
72131
|
this.accountId = accountId;
|
|
71440
72132
|
this.accountConfig = accountConfig;
|
|
72133
|
+
this.reportLifecycle = reportLifecycle;
|
|
72134
|
+
this.agentService.registerSurfaceNotificationHandler({
|
|
72135
|
+
platform: "telegram",
|
|
72136
|
+
accountId: this.accountId,
|
|
72137
|
+
handler: async ({ binding, text }) => {
|
|
72138
|
+
if (!binding.chatId) {
|
|
72139
|
+
return;
|
|
72140
|
+
}
|
|
72141
|
+
const chatId = Number(binding.chatId);
|
|
72142
|
+
if (!Number.isFinite(chatId)) {
|
|
72143
|
+
return;
|
|
72144
|
+
}
|
|
72145
|
+
const topicId = binding.topicId ? Number(binding.topicId) : undefined;
|
|
72146
|
+
await postTelegramText({
|
|
72147
|
+
token: this.accountConfig.botToken,
|
|
72148
|
+
chatId,
|
|
72149
|
+
text,
|
|
72150
|
+
topicId: Number.isFinite(topicId) ? topicId : undefined,
|
|
72151
|
+
omitThreadId: shouldOmitTelegramThreadId(Number.isFinite(topicId) ? topicId : undefined)
|
|
72152
|
+
});
|
|
72153
|
+
}
|
|
72154
|
+
});
|
|
71441
72155
|
}
|
|
71442
72156
|
async start() {
|
|
71443
72157
|
const me = await callTelegramApi(this.accountConfig.botToken, "getMe", {});
|
|
@@ -71454,6 +72168,10 @@ class TelegramPollingService {
|
|
|
71454
72168
|
this.activePollController?.abort();
|
|
71455
72169
|
await this.loopPromise;
|
|
71456
72170
|
await Promise.allSettled([...this.inFlightUpdates]);
|
|
72171
|
+
this.agentService.unregisterSurfaceNotificationHandler({
|
|
72172
|
+
platform: "telegram",
|
|
72173
|
+
accountId: this.accountId
|
|
72174
|
+
});
|
|
71457
72175
|
}
|
|
71458
72176
|
getBotLabel() {
|
|
71459
72177
|
return this.botUsername ? `@${this.botUsername}` : `${this.botUserId || "unknown"}`;
|
|
@@ -71500,6 +72218,15 @@ class TelegramPollingService {
|
|
|
71500
72218
|
}
|
|
71501
72219
|
if (isTelegramPollingConflict(error)) {
|
|
71502
72220
|
this.running = false;
|
|
72221
|
+
await this.reportLifecycle?.({
|
|
72222
|
+
connection: "failed",
|
|
72223
|
+
summary: "Telegram polling stopped because another instance is already using this bot token.",
|
|
72224
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
72225
|
+
actions: [
|
|
72226
|
+
"stop the other Telegram poller that is using the same bot token",
|
|
72227
|
+
"run `clisbot start` again after the token is no longer in use elsewhere"
|
|
72228
|
+
]
|
|
72229
|
+
});
|
|
71503
72230
|
console.error("telegram polling stopped: another bot instance is already calling getUpdates for this token");
|
|
71504
72231
|
return;
|
|
71505
72232
|
}
|
|
@@ -71667,6 +72394,7 @@ class TelegramPollingService {
|
|
|
71667
72394
|
const senderName = [message.from?.first_name, message.from?.last_name].filter((value) => typeof value === "string" && value.trim().length > 0).join(" ").trim();
|
|
71668
72395
|
const identity = {
|
|
71669
72396
|
platform: "telegram",
|
|
72397
|
+
accountId: this.accountId,
|
|
71670
72398
|
conversationKind: routeInfo.conversationKind,
|
|
71671
72399
|
senderId: message.from?.id != null ? String(message.from.id).trim() : undefined,
|
|
71672
72400
|
senderName: senderName || message.from?.username?.trim() || undefined,
|
|
@@ -72079,7 +72807,7 @@ var telegramChannelPlugin = {
|
|
|
72079
72807
|
accountId,
|
|
72080
72808
|
config
|
|
72081
72809
|
})),
|
|
72082
|
-
createRuntimeService: (context, account) => new TelegramPollingService(context.loadedConfig, context.agentService, context.processedEventsStore, context.activityStore, account.accountId, account.config),
|
|
72810
|
+
createRuntimeService: (context, account) => new TelegramPollingService(context.loadedConfig, context.agentService, context.processedEventsStore, context.activityStore, account.accountId, account.config, context.reportLifecycle),
|
|
72083
72811
|
renderHealthSummary: (state) => {
|
|
72084
72812
|
switch (state) {
|
|
72085
72813
|
case "starting":
|
|
@@ -72173,7 +72901,7 @@ var defaultMessageCliDependencies = {
|
|
|
72173
72901
|
await agentService.recordConversationReply(target, kind, source);
|
|
72174
72902
|
}
|
|
72175
72903
|
};
|
|
72176
|
-
function
|
|
72904
|
+
function parseRepeatedOption3(args, name) {
|
|
72177
72905
|
const values = [];
|
|
72178
72906
|
for (let index = 0;index < args.length; index += 1) {
|
|
72179
72907
|
if (args[index] !== name) {
|
|
@@ -72188,7 +72916,7 @@ function parseRepeatedOption2(args, name) {
|
|
|
72188
72916
|
return values;
|
|
72189
72917
|
}
|
|
72190
72918
|
function parseOptionValue4(args, name) {
|
|
72191
|
-
const values =
|
|
72919
|
+
const values = parseRepeatedOption3(args, name);
|
|
72192
72920
|
return values.length > 0 ? values.at(-1) : undefined;
|
|
72193
72921
|
}
|
|
72194
72922
|
function parseIntegerOption(args, name) {
|
|
@@ -72202,7 +72930,7 @@ function parseIntegerOption(args, name) {
|
|
|
72202
72930
|
}
|
|
72203
72931
|
return parsed;
|
|
72204
72932
|
}
|
|
72205
|
-
function
|
|
72933
|
+
function hasFlag4(args, name) {
|
|
72206
72934
|
return args.includes(name);
|
|
72207
72935
|
}
|
|
72208
72936
|
function resolveReplyKind(command) {
|
|
@@ -72234,18 +72962,18 @@ function parseMessageCommand(args) {
|
|
|
72234
72962
|
media: parseOptionValue4(rest, "--media"),
|
|
72235
72963
|
messageId: parseOptionValue4(rest, "--message-id"),
|
|
72236
72964
|
emoji: parseOptionValue4(rest, "--emoji"),
|
|
72237
|
-
remove:
|
|
72965
|
+
remove: hasFlag4(rest, "--remove"),
|
|
72238
72966
|
threadId: parseOptionValue4(rest, "--thread-id"),
|
|
72239
72967
|
replyTo: parseOptionValue4(rest, "--reply-to"),
|
|
72240
72968
|
limit: parseIntegerOption(rest, "--limit"),
|
|
72241
72969
|
query: parseOptionValue4(rest, "--query"),
|
|
72242
72970
|
pollQuestion: parseOptionValue4(rest, "--poll-question"),
|
|
72243
|
-
pollOptions:
|
|
72244
|
-
forceDocument:
|
|
72245
|
-
silent:
|
|
72246
|
-
progress:
|
|
72247
|
-
final:
|
|
72248
|
-
json:
|
|
72971
|
+
pollOptions: parseRepeatedOption3(rest, "--poll-option"),
|
|
72972
|
+
forceDocument: hasFlag4(rest, "--force-document"),
|
|
72973
|
+
silent: hasFlag4(rest, "--silent"),
|
|
72974
|
+
progress: hasFlag4(rest, "--progress"),
|
|
72975
|
+
final: hasFlag4(rest, "--final"),
|
|
72976
|
+
json: hasFlag4(rest, "--json")
|
|
72249
72977
|
};
|
|
72250
72978
|
}
|
|
72251
72979
|
function renderMessageHelp() {
|
|
@@ -72468,6 +73196,7 @@ class RuntimeSupervisor {
|
|
|
72468
73196
|
reloadInFlight = false;
|
|
72469
73197
|
reloadRequested = false;
|
|
72470
73198
|
configWatchDebounceMs = 250;
|
|
73199
|
+
nextRuntimeId = 1;
|
|
72471
73200
|
dependencies;
|
|
72472
73201
|
constructor(configPath, dependencies) {
|
|
72473
73202
|
this.configPath = configPath;
|
|
@@ -72484,15 +73213,50 @@ class RuntimeSupervisor {
|
|
|
72484
73213
|
async start() {
|
|
72485
73214
|
await this.reload("initial");
|
|
72486
73215
|
}
|
|
72487
|
-
async stop() {
|
|
73216
|
+
async stop(options = {}) {
|
|
72488
73217
|
this.clearReloadTimer();
|
|
72489
73218
|
this.stopWatchingConfig();
|
|
72490
73219
|
await this.stopActiveRuntime();
|
|
73220
|
+
if (options.markChannelsStopped !== false) {
|
|
73221
|
+
for (const plugin of this.dependencies.listChannelPlugins()) {
|
|
73222
|
+
await this.dependencies.runtimeHealthStore.setChannel({
|
|
73223
|
+
channel: plugin.id,
|
|
73224
|
+
connection: "stopped",
|
|
73225
|
+
summary: plugin.renderHealthSummary("stopped")
|
|
73226
|
+
});
|
|
73227
|
+
}
|
|
73228
|
+
}
|
|
73229
|
+
}
|
|
73230
|
+
async markFatalFailure(error) {
|
|
73231
|
+
const activeRuntime = this.activeRuntime;
|
|
73232
|
+
if (!activeRuntime) {
|
|
73233
|
+
return;
|
|
73234
|
+
}
|
|
73235
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
73236
|
+
const instancesByChannel = new Map;
|
|
73237
|
+
for (const entry of activeRuntime.channelServices) {
|
|
73238
|
+
const identity = entry.service.getRuntimeIdentity?.();
|
|
73239
|
+
if (!identity) {
|
|
73240
|
+
continue;
|
|
73241
|
+
}
|
|
73242
|
+
const existing = instancesByChannel.get(entry.channel) ?? [];
|
|
73243
|
+
existing.push(identity);
|
|
73244
|
+
instancesByChannel.set(entry.channel, existing);
|
|
73245
|
+
}
|
|
72491
73246
|
for (const plugin of this.dependencies.listChannelPlugins()) {
|
|
73247
|
+
if (!plugin.isEnabled(activeRuntime.loadedConfig)) {
|
|
73248
|
+
continue;
|
|
73249
|
+
}
|
|
72492
73250
|
await this.dependencies.runtimeHealthStore.setChannel({
|
|
72493
73251
|
channel: plugin.id,
|
|
72494
|
-
connection: "
|
|
72495
|
-
summary:
|
|
73252
|
+
connection: "failed",
|
|
73253
|
+
summary: "Runtime crashed due to a fatal error.",
|
|
73254
|
+
detail,
|
|
73255
|
+
actions: [
|
|
73256
|
+
"run `clisbot logs` and inspect the fatal error",
|
|
73257
|
+
"fix the underlying runtime fault, then restart with `clisbot start`"
|
|
73258
|
+
],
|
|
73259
|
+
instances: instancesByChannel.get(plugin.id) ?? []
|
|
72496
73260
|
});
|
|
72497
73261
|
}
|
|
72498
73262
|
}
|
|
@@ -72570,6 +73334,7 @@ class RuntimeSupervisor {
|
|
|
72570
73334
|
}
|
|
72571
73335
|
}
|
|
72572
73336
|
async createRuntime(loadedConfig) {
|
|
73337
|
+
const runtimeId = this.nextRuntimeId++;
|
|
72573
73338
|
const agentService = this.dependencies.createAgentService(loadedConfig);
|
|
72574
73339
|
const processedEventsStore = this.dependencies.createProcessedEventsStore(loadedConfig.processedEventsPath);
|
|
72575
73340
|
const activityStore = this.dependencies.createActivityStore();
|
|
@@ -72591,7 +73356,14 @@ class RuntimeSupervisor {
|
|
|
72591
73356
|
loadedConfig,
|
|
72592
73357
|
agentService,
|
|
72593
73358
|
processedEventsStore,
|
|
72594
|
-
activityStore
|
|
73359
|
+
activityStore,
|
|
73360
|
+
reportLifecycle: (event) => this.reportChannelLifecycle({
|
|
73361
|
+
runtimeId,
|
|
73362
|
+
plugin,
|
|
73363
|
+
channelServices,
|
|
73364
|
+
accountId: account.accountId,
|
|
73365
|
+
event
|
|
73366
|
+
})
|
|
72595
73367
|
}, account)
|
|
72596
73368
|
});
|
|
72597
73369
|
}
|
|
@@ -72622,6 +73394,8 @@ class RuntimeSupervisor {
|
|
|
72622
73394
|
});
|
|
72623
73395
|
}
|
|
72624
73396
|
return {
|
|
73397
|
+
id: runtimeId,
|
|
73398
|
+
loadedConfig,
|
|
72625
73399
|
agentService,
|
|
72626
73400
|
channelServices
|
|
72627
73401
|
};
|
|
@@ -72661,6 +73435,38 @@ class RuntimeSupervisor {
|
|
|
72661
73435
|
});
|
|
72662
73436
|
}
|
|
72663
73437
|
}
|
|
73438
|
+
getChannelInstances(channelServices, channel) {
|
|
73439
|
+
return channelServices.filter((entry) => entry.channel === channel).map((entry) => entry.service.getRuntimeIdentity?.()).filter((identity) => identity != null);
|
|
73440
|
+
}
|
|
73441
|
+
async reportChannelLifecycle(params) {
|
|
73442
|
+
if (this.activeRuntime?.id !== params.runtimeId) {
|
|
73443
|
+
return;
|
|
73444
|
+
}
|
|
73445
|
+
const instances = this.getChannelInstances(params.channelServices, params.plugin.id);
|
|
73446
|
+
if (params.event.connection === "active") {
|
|
73447
|
+
await this.dependencies.runtimeHealthStore.setChannel({
|
|
73448
|
+
channel: params.plugin.id,
|
|
73449
|
+
connection: "active",
|
|
73450
|
+
summary: params.event.summary ?? params.plugin.renderActiveHealthSummary(Math.max(1, instances.length)),
|
|
73451
|
+
detail: params.event.detail,
|
|
73452
|
+
actions: params.event.actions,
|
|
73453
|
+
instances
|
|
73454
|
+
});
|
|
73455
|
+
return;
|
|
73456
|
+
}
|
|
73457
|
+
const detailPrefix = `account=${params.accountId}`;
|
|
73458
|
+
await this.dependencies.runtimeHealthStore.setChannel({
|
|
73459
|
+
channel: params.plugin.id,
|
|
73460
|
+
connection: "failed",
|
|
73461
|
+
summary: params.event.summary ?? `${params.plugin.id} channel failed after startup.`,
|
|
73462
|
+
detail: params.event.detail ? `${detailPrefix}; ${params.event.detail}` : detailPrefix,
|
|
73463
|
+
actions: params.event.actions ?? [
|
|
73464
|
+
"run `clisbot logs` and inspect the latest channel error",
|
|
73465
|
+
"restart `clisbot` after fixing the channel-level issue"
|
|
73466
|
+
],
|
|
73467
|
+
instances
|
|
73468
|
+
});
|
|
73469
|
+
}
|
|
72664
73470
|
async reconcileConfigWatcher(loadedConfig) {
|
|
72665
73471
|
const configReload = loadedConfig.raw.control.configReload;
|
|
72666
73472
|
this.configWatchDebounceMs = configReload.watchDebounceMs;
|
|
@@ -72851,6 +73657,11 @@ async function getRuntimeOperatorSummary(params) {
|
|
|
72851
73657
|
];
|
|
72852
73658
|
return {
|
|
72853
73659
|
loadedConfig,
|
|
73660
|
+
ownerSummary: {
|
|
73661
|
+
ownerPrincipals: loadedConfig.raw.app.auth.roles.owner?.users ?? [],
|
|
73662
|
+
adminPrincipals: loadedConfig.raw.app.auth.roles.admin?.users ?? [],
|
|
73663
|
+
ownerClaimWindowMinutes: loadedConfig.raw.app.auth.ownerClaimWindowMinutes
|
|
73664
|
+
},
|
|
72854
73665
|
agentSummaries,
|
|
72855
73666
|
channelSummaries,
|
|
72856
73667
|
activeRuns: await agentService.listActiveSessionRuntimes(),
|
|
@@ -72932,11 +73743,75 @@ function renderAgentSummaryLines(summary) {
|
|
|
72932
73743
|
})
|
|
72933
73744
|
];
|
|
72934
73745
|
}
|
|
73746
|
+
function renderOwnerSummaryLines(summary) {
|
|
73747
|
+
const ownerPrincipals = summary.ownerSummary.ownerPrincipals;
|
|
73748
|
+
const adminPrincipals = summary.ownerSummary.adminPrincipals;
|
|
73749
|
+
const configuredOwners = ownerPrincipals.length > 0 ? ownerPrincipals.join(",") : "none";
|
|
73750
|
+
const claimWindow = `${summary.ownerSummary.ownerClaimWindowMinutes}m`;
|
|
73751
|
+
const lines = [
|
|
73752
|
+
"Owner:",
|
|
73753
|
+
` - configured=${ownerPrincipals.length > 0 ? "yes" : "no"} principals=${configuredOwners} claimWindow=${claimWindow}`,
|
|
73754
|
+
" - access=full app control, DM pairing bypass, implicit admin across all agents/channels"
|
|
73755
|
+
];
|
|
73756
|
+
if (adminPrincipals.length > 0) {
|
|
73757
|
+
lines.push(` - appAdmins=${adminPrincipals.join(",")}`);
|
|
73758
|
+
}
|
|
73759
|
+
return lines;
|
|
73760
|
+
}
|
|
73761
|
+
function hasConfiguredPrivilegedPrincipal(summary) {
|
|
73762
|
+
return summary.ownerSummary.ownerPrincipals.length > 0 || summary.ownerSummary.adminPrincipals.length > 0;
|
|
73763
|
+
}
|
|
73764
|
+
function renderPrivilegedChatHint(summary, action) {
|
|
73765
|
+
if (hasConfiguredPrivilegedPrincipal(summary)) {
|
|
73766
|
+
return `chat with the bot from that owner/admin account to ${action}`;
|
|
73767
|
+
}
|
|
73768
|
+
return `after you configure an owner/admin principal, use that account to ${action}`;
|
|
73769
|
+
}
|
|
73770
|
+
function appendChannelNextStepLines(lines, summary, prefix = "") {
|
|
73771
|
+
const slackEnabled = summary.channelSummaries.some((channel) => channel.channel === "slack" && channel.enabled);
|
|
73772
|
+
const telegramEnabled = summary.channelSummaries.some((channel) => channel.channel === "telegram" && channel.enabled);
|
|
73773
|
+
const hasEnabledChannel = slackEnabled || telegramEnabled;
|
|
73774
|
+
if (!hasEnabledChannel) {
|
|
73775
|
+
lines.push(`${prefix}- run \`clisbot channels enable <slack|telegram>\` for the first channel you want to expose`);
|
|
73776
|
+
return;
|
|
73777
|
+
}
|
|
73778
|
+
if (telegramEnabled && slackEnabled) {
|
|
73779
|
+
lines.push(`${prefix}- DM the Telegram or Slack bot first to confirm it responds normally`);
|
|
73780
|
+
} else if (telegramEnabled) {
|
|
73781
|
+
lines.push(`${prefix}- DM the Telegram bot first to confirm it responds normally`);
|
|
73782
|
+
} else {
|
|
73783
|
+
lines.push(`${prefix}- DM the Slack bot first to confirm it responds normally`);
|
|
73784
|
+
}
|
|
73785
|
+
lines.push(`${prefix}- after DM works, add the bot to the target Slack channel or Telegram group/topic`);
|
|
73786
|
+
lines.push(`${prefix}- route that surface with \`clisbot channels add slack-channel <channelId> --agent <id>\` or \`clisbot channels add telegram-group <chatId> --agent <id>\``);
|
|
73787
|
+
if (telegramEnabled) {
|
|
73788
|
+
lines.push(`${prefix}- Telegram: send \`/start\` in the target DM, group, or topic to get onboarding or pairing guidance`);
|
|
73789
|
+
}
|
|
73790
|
+
if (slackEnabled) {
|
|
73791
|
+
lines.push(`${prefix}- Slack: mention \`@<botname> \\start\` in the target channel to verify mention flow`);
|
|
73792
|
+
}
|
|
73793
|
+
}
|
|
73794
|
+
function appendAuthOnboardingLines(lines, summary, prefix = "") {
|
|
73795
|
+
lines.push(`${prefix}Auth onboarding:`);
|
|
73796
|
+
if (!hasConfiguredPrivilegedPrincipal(summary)) {
|
|
73797
|
+
lines.push(`${prefix} - get the principal from a surface the bot can already see; Telegram groups or topics can use \`/whoami\` before routing, while DMs with pairing must pair first`);
|
|
73798
|
+
lines.push(`${prefix} - set the first owner with: \`clisbot auth add-user app --role owner --user <principal>\``);
|
|
73799
|
+
} else {
|
|
73800
|
+
lines.push(`${prefix} - inspect current app roles with: \`clisbot auth show app\``);
|
|
73801
|
+
}
|
|
73802
|
+
lines.push(`${prefix} - inspect default agent roles with: \`clisbot auth show agent-defaults\``);
|
|
73803
|
+
lines.push(`${prefix} - add or remove principals with: \`clisbot auth add-user ...\` and \`clisbot auth remove-user ...\``);
|
|
73804
|
+
lines.push(`${prefix} - tune role permissions with: \`clisbot auth add-permission ...\` and \`clisbot auth remove-permission ...\``);
|
|
73805
|
+
lines.push(`${prefix} - run \`clisbot auth --help\` or read docs/user-guide/auth-and-roles.md for scopes and permission names`);
|
|
73806
|
+
}
|
|
72935
73807
|
function renderChannelSummaryLines(summary) {
|
|
72936
73808
|
return [
|
|
72937
73809
|
"",
|
|
72938
73810
|
"Channels:",
|
|
72939
73811
|
...summary.channelSummaries.map((channel) => {
|
|
73812
|
+
if (!channel.enabled) {
|
|
73813
|
+
return ` - ${channel.channel} enabled=no`;
|
|
73814
|
+
}
|
|
72940
73815
|
const last = channel.lastActivityAt ? ` last=${formatTime(channel.lastActivityAt)} via ${channel.lastActivityAgentId ?? "unknown"}` : " last=never";
|
|
72941
73816
|
const dm = ` dm=${channel.directMessagesEnabled ? channel.directMessagesPolicy : "disabled"}`;
|
|
72942
73817
|
const group = channel.groupPolicy ? ` groups=${channel.groupPolicy}` : "";
|
|
@@ -72992,8 +73867,9 @@ function renderRuntimeDiagnosticsSummary(summary) {
|
|
|
72992
73867
|
`).trim();
|
|
72993
73868
|
}
|
|
72994
73869
|
function renderStartSummary(summary) {
|
|
72995
|
-
const configPath = collapseHomePath(getDefaultConfigPath());
|
|
72996
73870
|
const lines = [
|
|
73871
|
+
...renderOwnerSummaryLines(summary),
|
|
73872
|
+
"",
|
|
72997
73873
|
...renderAgentSummaryLines(summary),
|
|
72998
73874
|
...renderChannelSummaryLines(summary)
|
|
72999
73875
|
];
|
|
@@ -73031,7 +73907,10 @@ function renderStartSummary(summary) {
|
|
|
73031
73907
|
lines.push("");
|
|
73032
73908
|
lines.push(" Next steps after bootstrap:");
|
|
73033
73909
|
lines.push(" - chat with the bot or open the workspace, then follow BOOTSTRAP.md");
|
|
73034
|
-
lines
|
|
73910
|
+
appendChannelNextStepLines(lines, summary, " ");
|
|
73911
|
+
lines.push(` - ${renderPrivilegedChatHint(summary, "verify DM access and adjust in-chat settings")}`);
|
|
73912
|
+
lines.push("");
|
|
73913
|
+
appendAuthOnboardingLines(lines, summary, " ");
|
|
73035
73914
|
lines.push(" - run `clisbot status` to recheck runtime and bootstrap state");
|
|
73036
73915
|
lines.push(" - run `clisbot logs` if the bot does not answer as expected");
|
|
73037
73916
|
lines.push(...renderPairingSetupHelpLines(" ", {
|
|
@@ -73049,9 +73928,11 @@ function renderStartSummary(summary) {
|
|
|
73049
73928
|
}
|
|
73050
73929
|
lines.push("");
|
|
73051
73930
|
lines.push("Next steps:");
|
|
73052
|
-
lines
|
|
73053
|
-
lines.push(" - verify
|
|
73054
|
-
lines.push(
|
|
73931
|
+
appendChannelNextStepLines(lines, summary, " ");
|
|
73932
|
+
lines.push(" - verify routes and defaultAgentId values match the agent you want to expose");
|
|
73933
|
+
lines.push(` - ${renderPrivilegedChatHint(summary, "adjust in-chat surface settings")}`);
|
|
73934
|
+
lines.push("");
|
|
73935
|
+
appendAuthOnboardingLines(lines, summary);
|
|
73055
73936
|
lines.push(" - run `clisbot status` to inspect agents, channels, and tmux session state");
|
|
73056
73937
|
lines.push(" - run `clisbot logs` if anything looks wrong");
|
|
73057
73938
|
lines.push(...renderPairingSetupHelpLines("", {
|
|
@@ -73078,20 +73959,24 @@ function appendChannelSetupNotes(lines, summary, prefix = "") {
|
|
|
73078
73959
|
if (channel.channel === "telegram") {
|
|
73079
73960
|
lines.push(`${prefix} - telegram: no explicit group or topic routes are configured yet`);
|
|
73080
73961
|
lines.push(`${prefix} dms: ${channel.directMessagesEnabled ? `enabled (${channel.directMessagesPolicy})` : "disabled"}`);
|
|
73081
|
-
lines.push(`${prefix}
|
|
73082
|
-
lines.push(`${prefix}
|
|
73083
|
-
lines.push(`${prefix}
|
|
73962
|
+
lines.push(`${prefix} add group: \`clisbot channels add telegram-group <chatId> --agent <id>\``);
|
|
73963
|
+
lines.push(`${prefix} add topic: \`clisbot channels add telegram-group <chatId> --topic <topicId> --agent <id>\``);
|
|
73964
|
+
lines.push(`${prefix} adjust later: ${renderPrivilegedChatHint(summary, "run in-chat commands here")}`);
|
|
73084
73965
|
continue;
|
|
73085
73966
|
}
|
|
73086
73967
|
lines.push(`${prefix} - slack: no explicit channel or group routes are configured yet`);
|
|
73087
73968
|
lines.push(`${prefix} dms: ${channel.directMessagesEnabled ? `enabled (${channel.directMessagesPolicy})` : "disabled"}`);
|
|
73088
73969
|
lines.push(`${prefix} groups: ${channel.groupPolicy ?? "n/a"}`);
|
|
73089
|
-
lines.push(`${prefix}
|
|
73970
|
+
lines.push(`${prefix} add channel: \`clisbot channels add slack-channel <channelId> --agent <id>\``);
|
|
73971
|
+
lines.push(`${prefix} add group: \`clisbot channels add slack-group <groupId> --agent <id>\``);
|
|
73972
|
+
lines.push(`${prefix} adjust later: ${renderPrivilegedChatHint(summary, "run in-chat commands here")}`);
|
|
73090
73973
|
}
|
|
73091
73974
|
}
|
|
73092
73975
|
function renderStatusSummary(summary) {
|
|
73093
73976
|
const lines = [
|
|
73094
73977
|
`stats agents=${summary.configuredAgents} bootstrapped=${summary.bootstrappedAgents} pendingBootstrap=${summary.bootstrapPendingAgents} tmuxSessions=${summary.runningTmuxSessions}`,
|
|
73978
|
+
...renderOwnerSummaryLines(summary),
|
|
73979
|
+
"",
|
|
73095
73980
|
...renderAgentSummaryLines(summary),
|
|
73096
73981
|
...renderChannelSummaryLines(summary),
|
|
73097
73982
|
...renderChannelDiagnosticLines(summary),
|
|
@@ -73113,6 +73998,15 @@ function printCommandOutcomeBanner(outcome) {
|
|
|
73113
73998
|
function printCommandOutcomeFooter(outcome) {
|
|
73114
73999
|
printCommandOutcomeBanner(outcome);
|
|
73115
74000
|
}
|
|
74001
|
+
function assertSupportedPlatform(command) {
|
|
74002
|
+
if (process.platform !== "win32") {
|
|
74003
|
+
return;
|
|
74004
|
+
}
|
|
74005
|
+
if (command.name === "help" || command.name === "version") {
|
|
74006
|
+
return;
|
|
74007
|
+
}
|
|
74008
|
+
throw new Error("Native Windows is not supported yet. Run clisbot from WSL2 or use Linux/macOS instead.");
|
|
74009
|
+
}
|
|
73116
74010
|
function getPrimaryWorkspacePath(summary) {
|
|
73117
74011
|
const preferredAgentId = summary.channelSummaries.find((channel) => channel.enabled)?.defaultAgentId ?? "default";
|
|
73118
74012
|
return summary.agentSummaries.find((agent) => agent.id === preferredAgentId)?.workspacePath ?? summary.agentSummaries[0]?.workspacePath;
|
|
@@ -73262,25 +74156,57 @@ async function serveForeground() {
|
|
|
73262
74156
|
const runtimeCredentialsPath = getDefaultRuntimeCredentialsPath();
|
|
73263
74157
|
const runtimeSupervisor = new RuntimeSupervisor(configPath);
|
|
73264
74158
|
let shuttingDown = false;
|
|
73265
|
-
|
|
74159
|
+
let fatalHandling = false;
|
|
74160
|
+
const shutdown = async (exitCode = 0, options = {}) => {
|
|
73266
74161
|
if (shuttingDown) {
|
|
73267
74162
|
return;
|
|
73268
74163
|
}
|
|
73269
74164
|
shuttingDown = true;
|
|
73270
74165
|
try {
|
|
73271
|
-
await runtimeSupervisor.stop(
|
|
74166
|
+
await runtimeSupervisor.stop({
|
|
74167
|
+
markChannelsStopped: options.markChannelsStopped
|
|
74168
|
+
});
|
|
73272
74169
|
} finally {
|
|
73273
74170
|
removeRuntimeCredentials(runtimeCredentialsPath);
|
|
73274
74171
|
removeRuntimePid(pidPath);
|
|
73275
74172
|
process.exit(exitCode);
|
|
73276
74173
|
}
|
|
73277
74174
|
};
|
|
74175
|
+
const handleFatal = async (source, error) => {
|
|
74176
|
+
if (fatalHandling || shuttingDown) {
|
|
74177
|
+
return;
|
|
74178
|
+
}
|
|
74179
|
+
fatalHandling = true;
|
|
74180
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
74181
|
+
const fatalError = new Error(`fatal ${source}: ${detail}`);
|
|
74182
|
+
console.error(`clisbot fatal ${source}`, error);
|
|
74183
|
+
const forceExitTimer = setTimeout(() => {
|
|
74184
|
+
process.exit(1);
|
|
74185
|
+
}, 5000);
|
|
74186
|
+
forceExitTimer.unref?.();
|
|
74187
|
+
try {
|
|
74188
|
+
await runtimeSupervisor.markFatalFailure(fatalError);
|
|
74189
|
+
} catch (markError) {
|
|
74190
|
+
console.error("failed to record fatal runtime health", markError);
|
|
74191
|
+
}
|
|
74192
|
+
try {
|
|
74193
|
+
await shutdown(1, { markChannelsStopped: false });
|
|
74194
|
+
} finally {
|
|
74195
|
+
clearTimeout(forceExitTimer);
|
|
74196
|
+
}
|
|
74197
|
+
};
|
|
73278
74198
|
process.once("SIGINT", () => {
|
|
73279
74199
|
shutdown(0);
|
|
73280
74200
|
});
|
|
73281
74201
|
process.once("SIGTERM", () => {
|
|
73282
74202
|
shutdown(0);
|
|
73283
74203
|
});
|
|
74204
|
+
process.once("uncaughtException", (error) => {
|
|
74205
|
+
handleFatal("uncaughtException", error);
|
|
74206
|
+
});
|
|
74207
|
+
process.once("unhandledRejection", (error) => {
|
|
74208
|
+
handleFatal("unhandledRejection", error);
|
|
74209
|
+
});
|
|
73284
74210
|
try {
|
|
73285
74211
|
await runtimeSupervisor.start();
|
|
73286
74212
|
await writeRuntimePid(pidPath, process.pid);
|
|
@@ -73357,7 +74283,7 @@ async function start(args = []) {
|
|
|
73357
74283
|
console.log(`clisbot is already running with pid: ${result.pid}`);
|
|
73358
74284
|
const workspacePath = getPrimaryWorkspacePath(summary);
|
|
73359
74285
|
if (workspacePath) {
|
|
73360
|
-
console.log(`workspace: ${workspacePath}
|
|
74286
|
+
console.log(`workspace: ${workspacePath}`);
|
|
73361
74287
|
}
|
|
73362
74288
|
console.log(`config: ${result.configPath}`);
|
|
73363
74289
|
console.log(`log: ${result.logPath}`);
|
|
@@ -73389,7 +74315,7 @@ async function start(args = []) {
|
|
|
73389
74315
|
console.log(`clisbot started with pid: ${result.pid}`);
|
|
73390
74316
|
const workspacePath = getPrimaryWorkspacePath(summary);
|
|
73391
74317
|
if (workspacePath) {
|
|
73392
|
-
console.log(`workspace: ${workspacePath}
|
|
74318
|
+
console.log(`workspace: ${workspacePath}`);
|
|
73393
74319
|
}
|
|
73394
74320
|
console.log(`config: ${result.configPath}`);
|
|
73395
74321
|
console.log(`log: ${result.logPath}`);
|
|
@@ -73478,7 +74404,7 @@ async function status() {
|
|
|
73478
74404
|
});
|
|
73479
74405
|
const workspacePath = getPrimaryWorkspacePath(summary);
|
|
73480
74406
|
if (workspacePath) {
|
|
73481
|
-
console.log(`workspace: ${workspacePath}
|
|
74407
|
+
console.log(`workspace: ${workspacePath}`);
|
|
73482
74408
|
}
|
|
73483
74409
|
console.log(`config: ${runtimeStatus.configPath}`);
|
|
73484
74410
|
console.log(`pid file: ${runtimeStatus.pidPath}`);
|
|
@@ -73521,6 +74447,7 @@ async function logs(lines) {
|
|
|
73521
74447
|
} catch {}
|
|
73522
74448
|
}
|
|
73523
74449
|
async function main(command = parseCliArgs(process.argv)) {
|
|
74450
|
+
assertSupportedPlatform(command);
|
|
73524
74451
|
if (command.name === "help") {
|
|
73525
74452
|
console.log(renderCliHelp());
|
|
73526
74453
|
return;
|
|
@@ -73577,6 +74504,10 @@ async function main(command = parseCliArgs(process.argv)) {
|
|
|
73577
74504
|
await runAgentsCli(command.args);
|
|
73578
74505
|
return;
|
|
73579
74506
|
}
|
|
74507
|
+
if (command.name === "auth") {
|
|
74508
|
+
await runAuthCli(command.args);
|
|
74509
|
+
return;
|
|
74510
|
+
}
|
|
73580
74511
|
if (command.name === "pairing") {
|
|
73581
74512
|
await runPairingCli(command.args);
|
|
73582
74513
|
return;
|