clisbot 0.1.16 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -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
- lines.push(`${prefix} - Telegram DMs use \`pairing\`. Send \`/start\` or \`hi\` to the Telegram bot to get a pairing code.`);
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
- lines.push(`${prefix} - Slack DMs use \`pairing\`. Say \`hi\` to the Slack bot to get a pairing code.`);
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: agentAuthSchema.optional(),
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()
@@ -60078,10 +60119,11 @@ var telegramSchema = exports_external.object({
60078
60119
  slash: ["::", "\\"],
60079
60120
  bash: ["!"]
60080
60121
  }),
60081
- streaming: slackStreamingSchema.default("all"),
60122
+ streaming: slackStreamingSchema.default("off"),
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()
@@ -60153,10 +60196,11 @@ var slackSchema = exports_external.object({
60153
60196
  slash: ["::", "\\"],
60154
60197
  bash: ["!"]
60155
60198
  }),
60156
- streaming: slackStreamingSchema.default("all"),
60199
+ streaming: slackStreamingSchema.default("off"),
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",
@@ -60304,10 +60348,14 @@ var clisbotConfigSchema = exports_external.object({
60304
60348
  slash: ["::", "\\"],
60305
60349
  bash: ["!"]
60306
60350
  },
60307
- streaming: "all",
60351
+ streaming: "off",
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",
@@ -60621,10 +60669,14 @@ function renderDefaultConfigTemplate(options = {}) {
60621
60669
  slash: ["::", "\\"],
60622
60670
  bash: ["!"]
60623
60671
  },
60624
- streaming: "all",
60672
+ streaming: "off",
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",
@@ -60662,10 +60714,14 @@ function renderDefaultConfigTemplate(options = {}) {
60662
60714
  slash: ["::", "\\"],
60663
60715
  bash: ["!"]
60664
60716
  },
60665
- streaming: "all",
60717
+ streaming: "off",
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,10 @@ 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_DEFAULTS = [
61894
+ ["exit-empty", "off"],
61895
+ ["destroy-unattached", "off"]
61896
+ ];
61837
61897
 
61838
61898
  class TmuxClient {
61839
61899
  socketPath;
@@ -61893,6 +61953,14 @@ class TmuxClient {
61893
61953
  ${result.stdout}`.trim();
61894
61954
  return !output.includes("no server running");
61895
61955
  }
61956
+ async ensureServerDefaults() {
61957
+ if (!await this.isServerRunning()) {
61958
+ return;
61959
+ }
61960
+ for (const [name, value] of TMUX_SERVER_DEFAULTS) {
61961
+ await this.execOrThrow(["set-option", "-g", name, value]);
61962
+ }
61963
+ }
61896
61964
  async newSession(params) {
61897
61965
  await this.execOrThrow([
61898
61966
  "new-session",
@@ -61905,6 +61973,7 @@ ${result.stdout}`.trim();
61905
61973
  params.cwd,
61906
61974
  params.command
61907
61975
  ]);
61976
+ await this.ensureServerDefaults();
61908
61977
  await this.freezeWindowName(`${params.sessionName}:${MAIN_WINDOW_NAME}`);
61909
61978
  }
61910
61979
  async newWindow(params) {
@@ -62692,40 +62761,329 @@ async function runAccountsCli(args, deps = {}) {
62692
62761
  throw new Error(renderAccountsHelp());
62693
62762
  }
62694
62763
 
62695
- // src/channels/telegram/route-guidance.ts
62696
- function renderTelegramRouteChoiceMessage(params) {
62697
- const chatId = String(params.chatId);
62698
- const topicId = params.topicId != null ? String(params.topicId) : undefined;
62699
- const lines = [
62700
- topicId != null ? "clisbot: this Telegram topic is not configured yet." : "clisbot: this Telegram group is not configured yet.",
62764
+ // src/control/auth-cli.ts
62765
+ function getEditableConfigPath3() {
62766
+ return process.env.CLISBOT_CONFIG_PATH;
62767
+ }
62768
+ function parseRepeatedOption2(args, name) {
62769
+ const values = [];
62770
+ for (let index = 0;index < args.length; index += 1) {
62771
+ if (args[index] !== name) {
62772
+ continue;
62773
+ }
62774
+ const value = args[index + 1]?.trim();
62775
+ if (!value) {
62776
+ throw new Error(`Missing value for ${name}`);
62777
+ }
62778
+ values.push(value);
62779
+ }
62780
+ return values;
62781
+ }
62782
+ function parseSingleOption2(args, name) {
62783
+ const values = parseRepeatedOption2(args, name);
62784
+ if (values.length === 0) {
62785
+ return;
62786
+ }
62787
+ return values[values.length - 1];
62788
+ }
62789
+ function hasFlag3(args, name) {
62790
+ return args.includes(name);
62791
+ }
62792
+ function parseScope(raw, args) {
62793
+ if (raw === "app") {
62794
+ return { kind: "app" };
62795
+ }
62796
+ if (raw === "agent-defaults") {
62797
+ return { kind: "agent-defaults" };
62798
+ }
62799
+ if (raw === "agent") {
62800
+ const agentId = parseSingleOption2(args, "--agent");
62801
+ if (!agentId) {
62802
+ throw new Error("Missing value for --agent");
62803
+ }
62804
+ return { kind: "agent", agentId };
62805
+ }
62806
+ throw new Error("Scope required: app | agent-defaults | agent");
62807
+ }
62808
+ function renderAuthCliHelp() {
62809
+ return [
62810
+ "clisbot auth",
62701
62811
  "",
62702
- "Ask the bot owner to choose one of these:",
62812
+ "Manage auth roles, principals, and permissions in config.",
62703
62813
  "",
62704
- "Add the whole group with the default agent:",
62705
- `\`clisbot channels add telegram-group ${chatId}\``,
62814
+ "Usage:",
62815
+ " clisbot auth list [--json]",
62816
+ " clisbot auth show <app|agent-defaults|agent> [--agent <id>] [--json]",
62817
+ " clisbot auth add-user <app|agent-defaults|agent> --role <role> --user <principal> [--agent <id>]",
62818
+ " clisbot auth remove-user <app|agent-defaults|agent> --role <role> --user <principal> [--agent <id>]",
62819
+ " clisbot auth add-permission <app|agent-defaults|agent> --role <role> --permission <permission> [--agent <id>]",
62820
+ " clisbot auth remove-permission <app|agent-defaults|agent> --role <role> --permission <permission> [--agent <id>]",
62706
62821
  "",
62707
- "Add the whole group with a specific agent:",
62708
- `\`clisbot channels add telegram-group ${chatId} --agent <id>\``
62709
- ];
62710
- if (topicId != null) {
62711
- lines.push("", "Add only this topic with a specific agent:", `\`clisbot channels add telegram-group ${chatId} --topic ${topicId} --agent <id>\``);
62822
+ "Scopes:",
62823
+ " app edit app.auth",
62824
+ " agent-defaults edit agents.defaults.auth",
62825
+ " agent edit one agents.list[].auth override; requires --agent <id>",
62826
+ "",
62827
+ "Permission sets:",
62828
+ ` app ${APP_ADMIN_PERMISSIONS.join(", ")}`,
62829
+ ` agent ${DEFAULT_AGENT_ADMIN_PERMISSIONS.join(", ")}`,
62830
+ "",
62831
+ "Notes:",
62832
+ " add-user/remove-user mutate roles.<role>.users",
62833
+ " add-permission/remove-permission mutate roles.<role>.allow",
62834
+ " agent role edits clone the inherited agent-defaults role into the target agent override on first write",
62835
+ "",
62836
+ "Examples:",
62837
+ " clisbot auth add-user app --role owner --user telegram:1276408333",
62838
+ " clisbot auth remove-user app --role admin --user slack:U123",
62839
+ " clisbot auth add-user agent --agent default --role admin --user slack:U123",
62840
+ " clisbot auth add-permission agent-defaults --role member --permission shellExecute",
62841
+ " clisbot auth remove-permission agent --agent default --role member --permission shellExecute",
62842
+ " clisbot auth show agent-defaults",
62843
+ " clisbot auth list --json"
62844
+ ].join(`
62845
+ `);
62846
+ }
62847
+ function cloneRoleDefinition(value) {
62848
+ return {
62849
+ allow: [...value?.allow ?? []],
62850
+ users: [...value?.users ?? []]
62851
+ };
62852
+ }
62853
+ function mergeRoleDefinitions(inherited, override) {
62854
+ return {
62855
+ allow: [...override?.allow ?? inherited?.allow ?? []],
62856
+ users: [...override?.users ?? inherited?.users ?? []]
62857
+ };
62858
+ }
62859
+ function mergeRoleRecord(defaults, overrides) {
62860
+ const merged = {};
62861
+ const roleNames = new Set([
62862
+ ...Object.keys(defaults ?? {}),
62863
+ ...Object.keys(overrides ?? {})
62864
+ ]);
62865
+ for (const roleName of roleNames) {
62866
+ merged[roleName] = mergeRoleDefinitions(defaults?.[roleName], overrides?.[roleName]);
62712
62867
  }
62713
- if (params.includeConfigPath) {
62714
- lines.push("", topicId != null ? `Config path: \`channels.telegram.groups."${chatId}".topics."${topicId}"\`` : `Config path: \`channels.telegram.groups."${chatId}"\``);
62715
- } else {
62716
- lines.push("", "After that, routed commands such as `/status`, `/stop`, `/nudge`, `/followup`, and `/bash` will work here.");
62868
+ return merged;
62869
+ }
62870
+ function normalizeUnique(values) {
62871
+ return [...new Set(values.map((value) => value.trim()).filter(Boolean))].sort();
62872
+ }
62873
+ function ensureAgentEntry(config, agentId) {
62874
+ const entry = config.agents.list.find((item) => item.id === agentId);
62875
+ if (!entry) {
62876
+ throw new Error(`Unknown agent: ${agentId}`);
62717
62877
  }
62718
- return lines.join(`
62719
- `);
62878
+ return entry;
62879
+ }
62880
+ function getAuthLabel(scope) {
62881
+ if (scope.kind === "app") {
62882
+ return "app.auth";
62883
+ }
62884
+ if (scope.kind === "agent-defaults") {
62885
+ return "agents.defaults.auth";
62886
+ }
62887
+ return `agents.list[${scope.agentId}].auth`;
62888
+ }
62889
+ function ensureEditableRoleDefinition(role) {
62890
+ role.allow = role.allow ?? [];
62891
+ role.users = role.users ?? [];
62892
+ return role;
62893
+ }
62894
+ function resolveAppRole(config, roleName) {
62895
+ const role = config.app.auth.roles[roleName];
62896
+ if (!role) {
62897
+ throw new Error(`Unknown app role: ${roleName}`);
62898
+ }
62899
+ return ensureEditableRoleDefinition(role);
62900
+ }
62901
+ function resolveAgentDefaultsRole(config, roleName) {
62902
+ const role = config.agents.defaults.auth.roles[roleName];
62903
+ if (!role) {
62904
+ throw new Error(`Unknown agent-defaults role: ${roleName}`);
62905
+ }
62906
+ return ensureEditableRoleDefinition(role);
62907
+ }
62908
+ function ensureAgentOverrideAuth(entry, config) {
62909
+ if (!entry.auth) {
62910
+ entry.auth = {
62911
+ defaultRole: config.agents.defaults.auth.defaultRole,
62912
+ roles: {}
62913
+ };
62914
+ }
62915
+ return entry.auth;
62916
+ }
62917
+ function resolveAgentRoleForEdit(config, agentId, roleName) {
62918
+ const entry = ensureAgentEntry(config, agentId);
62919
+ const explicitRole = entry.auth?.roles?.[roleName];
62920
+ if (explicitRole) {
62921
+ return ensureEditableRoleDefinition(explicitRole);
62922
+ }
62923
+ const inheritedRole = config.agents.defaults.auth.roles[roleName];
62924
+ if (!inheritedRole) {
62925
+ throw new Error(`Unknown agent role: ${roleName}`);
62926
+ }
62927
+ const auth = ensureAgentOverrideAuth(entry, config);
62928
+ auth.roles[roleName] = cloneRoleDefinition(inheritedRole);
62929
+ return ensureEditableRoleDefinition(auth.roles[roleName]);
62930
+ }
62931
+ function resolveRoleForEdit(config, scope, roleName) {
62932
+ if (scope.kind === "app") {
62933
+ return resolveAppRole(config, roleName);
62934
+ }
62935
+ if (scope.kind === "agent-defaults") {
62936
+ return resolveAgentDefaultsRole(config, roleName);
62937
+ }
62938
+ return resolveAgentRoleForEdit(config, scope.agentId, roleName);
62939
+ }
62940
+ function validatePermission(scope, permission) {
62941
+ const trimmed = permission.trim();
62942
+ if (!trimmed) {
62943
+ throw new Error("Missing value for --permission");
62944
+ }
62945
+ const allowedPermissions = new Set(scope.kind === "app" ? APP_ADMIN_PERMISSIONS : DEFAULT_AGENT_ADMIN_PERMISSIONS);
62946
+ if (!allowedPermissions.has(trimmed)) {
62947
+ const sorted = [...allowedPermissions].sort().join(", ");
62948
+ throw new Error(`Unknown permission for ${scope.kind}: ${trimmed}. Allowed: ${sorted}`);
62949
+ }
62950
+ return trimmed;
62951
+ }
62952
+ function buildShowPayload(config, scope) {
62953
+ if (scope.kind === "app") {
62954
+ return config.app.auth;
62955
+ }
62956
+ if (scope.kind === "agent-defaults") {
62957
+ return config.agents.defaults.auth;
62958
+ }
62959
+ const entry = ensureAgentEntry(config, scope.agentId);
62960
+ return {
62961
+ defaultRole: entry.auth?.defaultRole ?? config.agents.defaults.auth.defaultRole,
62962
+ roles: mergeRoleRecord(config.agents.defaults.auth.roles, entry.auth?.roles)
62963
+ };
62964
+ }
62965
+ async function listAuth(args) {
62966
+ const { config } = await readEditableConfig(getEditableConfigPath3());
62967
+ const payload = {
62968
+ app: config.app.auth,
62969
+ agentDefaults: config.agents.defaults.auth,
62970
+ agents: config.agents.list.map((entry) => ({
62971
+ agentId: entry.id,
62972
+ auth: buildShowPayload(config, { kind: "agent", agentId: entry.id })
62973
+ }))
62974
+ };
62975
+ if (hasFlag3(args, "--json")) {
62976
+ console.log(JSON.stringify(payload, null, 2));
62977
+ return;
62978
+ }
62979
+ console.log(JSON.stringify(payload, null, 2));
62980
+ }
62981
+ async function showAuth(args) {
62982
+ const scope = parseScope(args[0], args.slice(1));
62983
+ const { config } = await readEditableConfig(getEditableConfigPath3());
62984
+ const payload = buildShowPayload(config, scope);
62985
+ if (hasFlag3(args, "--json")) {
62986
+ console.log(JSON.stringify(payload, null, 2));
62987
+ return;
62988
+ }
62989
+ console.log(JSON.stringify(payload, null, 2));
62990
+ }
62991
+ async function mutateUsers(mode, args) {
62992
+ const scope = parseScope(args[0], args.slice(1));
62993
+ const roleName = parseSingleOption2(args, "--role");
62994
+ const principal = parseSingleOption2(args, "--user")?.trim();
62995
+ if (!roleName) {
62996
+ throw new Error("Missing value for --role");
62997
+ }
62998
+ if (!principal) {
62999
+ throw new Error("Missing value for --user");
63000
+ }
63001
+ const { config, configPath } = await readEditableConfig(getEditableConfigPath3());
63002
+ const role = resolveRoleForEdit(config, scope, roleName);
63003
+ role.users = normalizeUnique(mode === "add" ? [...role.users, principal] : role.users.filter((value) => value !== principal));
63004
+ await writeEditableConfig(configPath, config);
63005
+ console.log(`${mode === "add" ? "Added" : "Removed"} user ${principal} ${mode === "add" ? "to" : "from"} ${getAuthLabel(scope)} role ${roleName}.`);
63006
+ }
63007
+ async function mutatePermissions(mode, args) {
63008
+ const scope = parseScope(args[0], args.slice(1));
63009
+ const roleName = parseSingleOption2(args, "--role");
63010
+ const permission = validatePermission(scope, parseSingleOption2(args, "--permission") ?? "");
63011
+ if (!roleName) {
63012
+ throw new Error("Missing value for --role");
63013
+ }
63014
+ const { config, configPath } = await readEditableConfig(getEditableConfigPath3());
63015
+ const role = resolveRoleForEdit(config, scope, roleName);
63016
+ role.allow = normalizeUnique(mode === "add" ? [...role.allow, permission] : role.allow.filter((value) => value !== permission));
63017
+ await writeEditableConfig(configPath, config);
63018
+ console.log(`${mode === "add" ? "Added" : "Removed"} permission ${permission} ${mode === "add" ? "to" : "from"} ${getAuthLabel(scope)} role ${roleName}.`);
63019
+ }
63020
+ async function runAuthCli(args) {
63021
+ const [command, ...rest] = args;
63022
+ if (!command || command === "--help" || command === "-h" || command === "help") {
63023
+ console.log(renderAuthCliHelp());
63024
+ return;
63025
+ }
63026
+ if (command === "list") {
63027
+ await listAuth(rest);
63028
+ return;
63029
+ }
63030
+ if (command === "show") {
63031
+ await showAuth(rest);
63032
+ return;
63033
+ }
63034
+ if (command === "add-user") {
63035
+ await mutateUsers("add", rest);
63036
+ return;
63037
+ }
63038
+ if (command === "remove-user") {
63039
+ await mutateUsers("remove", rest);
63040
+ return;
63041
+ }
63042
+ if (command === "add-permission") {
63043
+ await mutatePermissions("add", rest);
63044
+ return;
63045
+ }
63046
+ if (command === "remove-permission") {
63047
+ await mutatePermissions("remove", rest);
63048
+ return;
63049
+ }
63050
+ throw new Error(renderAuthCliHelp());
62720
63051
  }
62721
63052
 
62722
63053
  // src/channels/mode-config-shared.ts
63054
+ function createTelegramGroupRoute() {
63055
+ return {
63056
+ requireMention: true,
63057
+ allowBots: false,
63058
+ topics: {}
63059
+ };
63060
+ }
63061
+ function ensureTelegramGroupRoute(config, chatId) {
63062
+ const existing = config.channels.telegram.groups[chatId];
63063
+ if (existing) {
63064
+ return existing;
63065
+ }
63066
+ const created = createTelegramGroupRoute();
63067
+ config.channels.telegram.groups[chatId] = created;
63068
+ return created;
63069
+ }
63070
+ function ensureTelegramTopicRoute(config, chatId, topicId) {
63071
+ const group = ensureTelegramGroupRoute(config, chatId);
63072
+ const existing = group.topics[topicId];
63073
+ if (existing) {
63074
+ return existing;
63075
+ }
63076
+ const created = {};
63077
+ group.topics[topicId] = created;
63078
+ return created;
63079
+ }
62723
63080
  function getModeValue(source, field) {
62724
63081
  return source[field];
62725
63082
  }
62726
63083
  function setModeValue(source, field, value) {
62727
63084
  source[field] = value;
62728
63085
  }
63086
+ var EMPTY_MODE_SOURCE = {};
62729
63087
  function resolveSlackConfigTarget(config, field, params) {
62730
63088
  if (!params.target) {
62731
63089
  return {
@@ -62807,22 +63165,25 @@ function resolveTelegramConfigTarget(config, field, params) {
62807
63165
  };
62808
63166
  }
62809
63167
  const group = config.channels.telegram.groups[chatId];
62810
- if (!group) {
62811
- throw new Error(renderTelegramRouteChoiceMessage({ chatId }));
62812
- }
62813
63168
  if (topicId) {
62814
- const topic = group.topics?.[topicId];
62815
- if (!topic) {
62816
- throw new Error(renderTelegramRouteChoiceMessage({ chatId, topicId }));
62817
- }
63169
+ const topic = group?.topics?.[topicId];
62818
63170
  return {
62819
- get: () => getModeValue(topic, field) ?? getModeValue(group, field) ?? getModeValue(config.channels.telegram, field),
63171
+ get: () => getModeValue(topic ?? EMPTY_MODE_SOURCE, field) ?? getModeValue(group ?? EMPTY_MODE_SOURCE, field) ?? getModeValue(config.channels.telegram, field),
62820
63172
  set: (value) => {
62821
- setModeValue(topic, field, value);
63173
+ setModeValue(ensureTelegramTopicRoute(config, chatId, topicId), field, value);
62822
63174
  },
62823
63175
  label: `telegram topic ${chatId}/${topicId}`
62824
63176
  };
62825
63177
  }
63178
+ if (!group) {
63179
+ return {
63180
+ get: () => getModeValue(config.channels.telegram, field),
63181
+ set: (value) => {
63182
+ setModeValue(ensureTelegramGroupRoute(config, chatId), field, value);
63183
+ },
63184
+ label: `telegram group ${chatId}`
63185
+ };
63186
+ }
62826
63187
  return {
62827
63188
  get: () => getModeValue(group, field) ?? getModeValue(config.channels.telegram, field),
62828
63189
  set: (value) => {
@@ -62863,11 +63224,11 @@ function renderFieldLabel(field) {
62863
63224
  }
62864
63225
 
62865
63226
  // src/channels/additional-message-mode-config.ts
62866
- function getEditableConfigPath3() {
63227
+ function getEditableConfigPath4() {
62867
63228
  return process.env.CLISBOT_CONFIG_PATH;
62868
63229
  }
62869
63230
  async function getConversationAdditionalMessageMode(params) {
62870
- const { config } = await readEditableConfig(getEditableConfigPath3());
63231
+ const { config } = await readEditableConfig(getEditableConfigPath4());
62871
63232
  const target = resolveConfiguredSurfaceModeTarget(config, "additionalMessageMode", buildConfiguredTargetFromIdentity(params.identity));
62872
63233
  return {
62873
63234
  label: target.label,
@@ -62875,7 +63236,7 @@ async function getConversationAdditionalMessageMode(params) {
62875
63236
  };
62876
63237
  }
62877
63238
  async function setConversationAdditionalMessageMode(params) {
62878
- const { config, configPath } = await readEditableConfig(getEditableConfigPath3());
63239
+ const { config, configPath } = await readEditableConfig(getEditableConfigPath4());
62879
63240
  const target = resolveConfiguredSurfaceModeTarget(config, "additionalMessageMode", buildConfiguredTargetFromIdentity(params.identity));
62880
63241
  target.set(params.additionalMessageMode);
62881
63242
  await writeEditableConfig(configPath, config);
@@ -62886,7 +63247,7 @@ async function setConversationAdditionalMessageMode(params) {
62886
63247
  };
62887
63248
  }
62888
63249
  async function getConfiguredAdditionalMessageMode(params) {
62889
- const { config, configPath } = await readEditableConfig(getEditableConfigPath3());
63250
+ const { config, configPath } = await readEditableConfig(getEditableConfigPath4());
62890
63251
  const target = resolveConfiguredSurfaceModeTarget(config, "additionalMessageMode", params);
62891
63252
  return {
62892
63253
  configPath,
@@ -62895,7 +63256,7 @@ async function getConfiguredAdditionalMessageMode(params) {
62895
63256
  };
62896
63257
  }
62897
63258
  async function setConfiguredAdditionalMessageMode(params) {
62898
- const { config, configPath } = await readEditableConfig(getEditableConfigPath3());
63259
+ const { config, configPath } = await readEditableConfig(getEditableConfigPath4());
62899
63260
  const target = resolveConfiguredSurfaceModeTarget(config, "additionalMessageMode", params);
62900
63261
  target.set(params.additionalMessageMode);
62901
63262
  await writeEditableConfig(configPath, config);
@@ -62907,11 +63268,11 @@ async function setConfiguredAdditionalMessageMode(params) {
62907
63268
  }
62908
63269
 
62909
63270
  // src/channels/response-mode-config.ts
62910
- function getEditableConfigPath4() {
63271
+ function getEditableConfigPath5() {
62911
63272
  return process.env.CLISBOT_CONFIG_PATH;
62912
63273
  }
62913
63274
  async function getConversationResponseMode(params) {
62914
- const { config } = await readEditableConfig(getEditableConfigPath4());
63275
+ const { config } = await readEditableConfig(getEditableConfigPath5());
62915
63276
  const target = resolveConfiguredSurfaceModeTarget(config, "responseMode", buildConfiguredTargetFromIdentity(params.identity));
62916
63277
  return {
62917
63278
  label: target.label,
@@ -62919,7 +63280,7 @@ async function getConversationResponseMode(params) {
62919
63280
  };
62920
63281
  }
62921
63282
  async function setConversationResponseMode(params) {
62922
- const { config, configPath } = await readEditableConfig(getEditableConfigPath4());
63283
+ const { config, configPath } = await readEditableConfig(getEditableConfigPath5());
62923
63284
  const target = resolveConfiguredSurfaceModeTarget(config, "responseMode", buildConfiguredTargetFromIdentity(params.identity));
62924
63285
  target.set(params.responseMode);
62925
63286
  await writeEditableConfig(configPath, config);
@@ -62930,7 +63291,7 @@ async function setConversationResponseMode(params) {
62930
63291
  };
62931
63292
  }
62932
63293
  async function getConfiguredResponseMode(params) {
62933
- const { config, configPath } = await readEditableConfig(getEditableConfigPath4());
63294
+ const { config, configPath } = await readEditableConfig(getEditableConfigPath5());
62934
63295
  const target = resolveConfiguredSurfaceModeTarget(config, "responseMode", params);
62935
63296
  return {
62936
63297
  configPath,
@@ -62939,7 +63300,7 @@ async function getConfiguredResponseMode(params) {
62939
63300
  };
62940
63301
  }
62941
63302
  async function setConfiguredResponseMode(params) {
62942
- const { config, configPath } = await readEditableConfig(getEditableConfigPath4());
63303
+ const { config, configPath } = await readEditableConfig(getEditableConfigPath5());
62943
63304
  const target = resolveConfiguredSurfaceModeTarget(config, "responseMode", params);
62944
63305
  target.set(params.responseMode);
62945
63306
  await writeEditableConfig(configPath, config);
@@ -62965,7 +63326,7 @@ async function runChannelPrivilegeCli(_args) {
62965
63326
 
62966
63327
  // src/control/channels-cli.ts
62967
63328
  var AUTH_USER_GUIDE_DOC_PATH = "docs/user-guide/auth-and-roles.md";
62968
- function getEditableConfigPath5() {
63329
+ function getEditableConfigPath6() {
62969
63330
  return process.env.CLISBOT_CONFIG_PATH;
62970
63331
  }
62971
63332
  function renderChannelsHelp() {
@@ -63114,7 +63475,7 @@ function getAgentId(args) {
63114
63475
  return parseOptionValue2(args, "--agent") ?? "default";
63115
63476
  }
63116
63477
  async function setChannelEnabled(action, channel) {
63117
- const { config, configPath } = await readEditableConfig(getEditableConfigPath5());
63478
+ const { config, configPath } = await readEditableConfig(getEditableConfigPath6());
63118
63479
  const enabled = action === "enable";
63119
63480
  const current = config.channels[channel].enabled;
63120
63481
  if (current === enabled) {
@@ -63134,7 +63495,7 @@ async function addTelegramGroup(args) {
63134
63495
  if (!chatId) {
63135
63496
  throw new Error("Usage: clisbot channels add telegram-group <chatId> [--topic <topicId>] [--agent <id>] [--require-mention true|false]");
63136
63497
  }
63137
- const { config, configPath } = await readEditableConfig(getEditableConfigPath5());
63498
+ const { config, configPath } = await readEditableConfig(getEditableConfigPath6());
63138
63499
  const topicId = parseOptionValue2(args, "--topic");
63139
63500
  const agentId = getAgentId(args);
63140
63501
  const requireMention = parseBooleanOption(args, "--require-mention", true);
@@ -63187,7 +63548,7 @@ async function removeTelegramGroup(args) {
63187
63548
  if (!chatId) {
63188
63549
  throw new Error("Usage: clisbot channels remove telegram-group <chatId> [--topic <topicId>]");
63189
63550
  }
63190
- const { config, configPath } = await readEditableConfig(getEditableConfigPath5());
63551
+ const { config, configPath } = await readEditableConfig(getEditableConfigPath6());
63191
63552
  const topicId = parseOptionValue2(args, "--topic");
63192
63553
  const groupRoute = config.channels.telegram.groups[chatId];
63193
63554
  if (!groupRoute) {
@@ -63217,7 +63578,7 @@ async function addSlackRoute(kind, args) {
63217
63578
  if (!routeId) {
63218
63579
  throw new Error(`Usage: clisbot channels add slack-${kind} <${kind}Id> [--agent <id>] [--require-mention true|false]`);
63219
63580
  }
63220
- const { config, configPath } = await readEditableConfig(getEditableConfigPath5());
63581
+ const { config, configPath } = await readEditableConfig(getEditableConfigPath6());
63221
63582
  const agentId = getAgentId(args);
63222
63583
  const requireMention = parseBooleanOption(args, "--require-mention", false);
63223
63584
  const target = kind === "channel" ? config.channels.slack.channels : config.channels.slack.groups;
@@ -63241,7 +63602,7 @@ async function removeSlackRoute(kind, args) {
63241
63602
  if (!routeId) {
63242
63603
  throw new Error(`Usage: clisbot channels remove slack-${kind} <${kind}Id>`);
63243
63604
  }
63244
- const { config, configPath } = await readEditableConfig(getEditableConfigPath5());
63605
+ const { config, configPath } = await readEditableConfig(getEditableConfigPath6());
63245
63606
  const target = kind === "channel" ? config.channels.slack.channels : config.channels.slack.groups;
63246
63607
  if (!target[routeId]) {
63247
63608
  console.log(`slack ${kind} route ${routeId} is not configured`);
@@ -63254,7 +63615,7 @@ async function removeSlackRoute(kind, args) {
63254
63615
  console.log(`config: ${configPath}`);
63255
63616
  }
63256
63617
  async function setToken(target, value) {
63257
- const { config, configPath } = await readEditableConfig(getEditableConfigPath5());
63618
+ const { config, configPath } = await readEditableConfig(getEditableConfigPath6());
63258
63619
  if (target === "slack-app") {
63259
63620
  config.channels.slack.appToken = value;
63260
63621
  const defaultAccountId = config.channels.slack.defaultAccount || "default";
@@ -63286,7 +63647,7 @@ async function setToken(target, value) {
63286
63647
  console.log(`config: ${configPath}`);
63287
63648
  }
63288
63649
  async function clearToken(target) {
63289
- const { config, configPath } = await readEditableConfig(getEditableConfigPath5());
63650
+ const { config, configPath } = await readEditableConfig(getEditableConfigPath6());
63290
63651
  if (target === "slack-app") {
63291
63652
  config.channels.slack.appToken = "";
63292
63653
  const defaultAccountId = config.channels.slack.defaultAccount || "default";
@@ -64025,7 +64386,7 @@ class SessionStore {
64025
64386
  }
64026
64387
 
64027
64388
  // src/control/loops-cli.ts
64028
- function getEditableConfigPath6() {
64389
+ function getEditableConfigPath7() {
64029
64390
  return process.env.CLISBOT_CONFIG_PATH;
64030
64391
  }
64031
64392
  function renderLoopsHelp() {
@@ -64083,7 +64444,7 @@ function getSessionState(sessionStorePath) {
64083
64444
  return new AgentSessionState(new SessionStore(sessionStorePath));
64084
64445
  }
64085
64446
  async function loadLoopControlState() {
64086
- const configPath = await ensureEditableConfigFile(getEditableConfigPath6());
64447
+ const configPath = await ensureEditableConfigFile(getEditableConfigPath7());
64087
64448
  const loadedConfig = await loadConfigWithoutEnvResolution(configPath);
64088
64449
  const sessionStorePath = resolveSessionStorePath(loadedConfig);
64089
64450
  return {
@@ -64168,7 +64529,7 @@ ${params.text}
64168
64529
  }
64169
64530
  function renderAgentPromptInstruction(params) {
64170
64531
  const messageToolMode = (params.responseMode ?? "message-tool") === "message-tool";
64171
- const progressAllowed = messageToolMode && (params.streaming ?? "all") !== "off";
64532
+ const progressAllowed = messageToolMode && (params.streaming ?? "off") === "off";
64172
64533
  const lines = [
64173
64534
  `[${renderPromptTimestamp()}] ${renderIdentitySummary(params.identity)}`,
64174
64535
  "",
@@ -64259,13 +64620,16 @@ function buildReplyCommand(params) {
64259
64620
  const lines = [`${params.command} message send \\`];
64260
64621
  if (params.identity.platform === "slack") {
64261
64622
  lines.push(" --channel slack \\");
64623
+ if (params.identity.accountId) {
64624
+ lines.push(` --account ${params.identity.accountId} \\`);
64625
+ }
64262
64626
  lines.push(` --target channel:${params.identity.channelId ?? ""} \\`);
64263
64627
  if (params.identity.threadTs) {
64264
64628
  lines.push(` --thread-id ${params.identity.threadTs} \\`);
64265
64629
  }
64266
64630
  lines.push(" --final \\");
64267
64631
  lines.push(' --message "$(cat <<\\__CLISBOT_MESSAGE__');
64268
- lines.push("<short progress update>");
64632
+ lines.push("<user-facing reply>");
64269
64633
  lines.push("__CLISBOT_MESSAGE__");
64270
64634
  lines.push(')" \\');
64271
64635
  lines.push(" [--media /absolute/path/to/file]");
@@ -64273,13 +64637,16 @@ function buildReplyCommand(params) {
64273
64637
  `);
64274
64638
  }
64275
64639
  lines.push(" --channel telegram \\");
64640
+ if (params.identity.accountId) {
64641
+ lines.push(` --account ${params.identity.accountId} \\`);
64642
+ }
64276
64643
  lines.push(` --target ${params.identity.chatId ?? ""} \\`);
64277
64644
  if (params.identity.topicId) {
64278
64645
  lines.push(` --thread-id ${params.identity.topicId} \\`);
64279
64646
  }
64280
64647
  lines.push(" --final \\");
64281
64648
  lines.push(' --message "$(cat <<\\__CLISBOT_MESSAGE__');
64282
- lines.push("<short progress update>");
64649
+ lines.push("<user-facing reply>");
64283
64650
  lines.push("__CLISBOT_MESSAGE__");
64284
64651
  lines.push(')" \\');
64285
64652
  lines.push(" [--media /absolute/path/to/file]");
@@ -64287,6 +64654,57 @@ function buildReplyCommand(params) {
64287
64654
  `);
64288
64655
  }
64289
64656
 
64657
+ // src/channels/surface-notifications.ts
64658
+ function sanitizeInlineCode(text) {
64659
+ return text.replaceAll("`", "'");
64660
+ }
64661
+ function summarizeSurfaceNotificationText(text, maxLength = 60) {
64662
+ const singleLine = sanitizeInlineCode(text.replace(/\s+/g, " ").trim());
64663
+ if (!singleLine) {
64664
+ return "(empty)";
64665
+ }
64666
+ if (singleLine.length <= maxLength) {
64667
+ return singleLine;
64668
+ }
64669
+ return `${singleLine.slice(0, maxLength - 3)}...`;
64670
+ }
64671
+ function renderQueueStartNotification(params) {
64672
+ if (params.mode === "none") {
64673
+ return;
64674
+ }
64675
+ const summary = summarizeSurfaceNotificationText(params.promptSummary);
64676
+ if (params.mode === "full") {
64677
+ return `Queued message is now running for agent \`${params.agentId}\`: \`${summary}\`.`;
64678
+ }
64679
+ return `Queued message is now running: \`${summary}\`.`;
64680
+ }
64681
+ function renderLoopScheduleSegment(params) {
64682
+ if (params.kind === "calendar") {
64683
+ return formatCalendarLoopSchedule({
64684
+ cadence: params.cadence,
64685
+ dayOfWeek: params.dayOfWeek,
64686
+ localTime: params.localTime
64687
+ });
64688
+ }
64689
+ return `every ${formatLoopIntervalShort(params.intervalMs)}`;
64690
+ }
64691
+ function renderLoopStartNotification(params) {
64692
+ if (params.mode === "none") {
64693
+ return;
64694
+ }
64695
+ const summary = summarizeSurfaceNotificationText(params.promptSummary);
64696
+ const segments = [
64697
+ `\`${summary}\``,
64698
+ renderLoopScheduleSegment(params),
64699
+ `next run \`${new Date(params.nextRunAt).toISOString()}\``,
64700
+ `remaining \`${params.remainingRuns}/${params.maxRuns}\``
64701
+ ];
64702
+ if (params.mode === "full") {
64703
+ return `Loop \`${params.loopId}\` is now running for agent \`${params.agentId}\`: ${segments.join(" · ")}.`;
64704
+ }
64705
+ return `Loop \`${params.loopId}\` is now running: ${segments.join(" · ")}.`;
64706
+ }
64707
+
64290
64708
  // src/agents/session-key.ts
64291
64709
  var DEFAULT_MAIN_KEY = "main";
64292
64710
  var DEFAULT_ACCOUNT_ID = "default";
@@ -64931,11 +65349,50 @@ function extractScrolledAppend(previous, current) {
64931
65349
  }
64932
65350
  return "";
64933
65351
  }
65352
+ function deriveRunningInteractionText(previousSnapshot, currentSnapshot) {
65353
+ const previous = cleanInteractionSnapshot(previousSnapshot);
65354
+ const current = cleanInteractionSnapshot(currentSnapshot);
65355
+ if (!current || current === previous) {
65356
+ return "";
65357
+ }
65358
+ if (!previous) {
65359
+ return current;
65360
+ }
65361
+ return extractScrolledAppend(previous, current);
65362
+ }
64934
65363
  function deriveInteractionText(initialSnapshot, currentSnapshot) {
64935
65364
  const previous = cleanInteractionSnapshot(initialSnapshot);
64936
65365
  const current = cleanInteractionSnapshot(currentSnapshot);
64937
65366
  return extractScrolledAppend(previous, current) || diffText(previous, current);
64938
65367
  }
65368
+ function appendInteractionText(currentBody, nextDelta) {
65369
+ const trimmedCurrent = currentBody.trim();
65370
+ const trimmedDelta = nextDelta.trim();
65371
+ if (!trimmedDelta) {
65372
+ return trimmedCurrent;
65373
+ }
65374
+ if (!trimmedCurrent) {
65375
+ return trimmedDelta;
65376
+ }
65377
+ const currentLines = trimmedCurrent.split(`
65378
+ `);
65379
+ const deltaLines = trimmedDelta.split(`
65380
+ `);
65381
+ const maxOverlap = Math.min(currentLines.length, deltaLines.length, 8);
65382
+ let overlap = 0;
65383
+ for (let size = maxOverlap;size > 0; size -= 1) {
65384
+ const currentSuffix = currentLines.slice(currentLines.length - size).join(`
65385
+ `);
65386
+ const deltaPrefix = deltaLines.slice(0, size).join(`
65387
+ `);
65388
+ if (currentSuffix === deltaPrefix) {
65389
+ overlap = size;
65390
+ break;
65391
+ }
65392
+ }
65393
+ return [...currentLines, ...deltaLines.slice(overlap)].join(`
65394
+ `).trim();
65395
+ }
64939
65396
  function truncateTail(raw, maxChars) {
64940
65397
  if (raw.length <= maxChars) {
64941
65398
  return raw;
@@ -65713,6 +66170,9 @@ class RunnerSessionService {
65713
66170
  await ensureRunnerExitRecordDir(this.loadedConfig.stateDir, resolved.sessionName);
65714
66171
  const existing = await this.sessionState.getEntry(resolved.sessionKey);
65715
66172
  const serverRunning = await this.tmux.isServerRunning();
66173
+ if (serverRunning) {
66174
+ await this.tmux.ensureServerDefaults();
66175
+ }
65716
66176
  if (serverRunning && await this.tmux.hasSession(resolved.sessionName)) {
65717
66177
  logLatencyDebug("ensure-session-ready-existing-session", timingContext, {
65718
66178
  hasStoredSessionId: Boolean(existing?.sessionId)
@@ -66006,7 +66466,7 @@ async function monitorTmuxRun(params) {
66006
66466
  let previousSnapshot = params.initialSnapshot;
66007
66467
  let lastChangeAt = Date.now();
66008
66468
  let sawChange = false;
66009
- let lastVisibleSnapshot = "";
66469
+ let cumulativeInteractionSnapshot = "";
66010
66470
  let detachedNotified = params.detachedAlready;
66011
66471
  let firstMeaningfulDeltaLogged = false;
66012
66472
  if (params.prompt) {
@@ -66032,12 +66492,14 @@ async function monitorTmuxRun(params) {
66032
66492
  const snapshot = normalizePaneText(await params.tmux.capturePane(params.sessionName, params.captureLines));
66033
66493
  const now = Date.now();
66034
66494
  if (snapshot !== previousSnapshot) {
66495
+ const priorSnapshot = previousSnapshot;
66035
66496
  lastChangeAt = now;
66036
66497
  previousSnapshot = snapshot;
66037
- const interactionSnapshot = deriveInteractionText(params.initialSnapshot, snapshot);
66038
- if (interactionSnapshot && interactionSnapshot !== lastVisibleSnapshot) {
66498
+ const interactionDelta = deriveRunningInteractionText(priorSnapshot, snapshot);
66499
+ const nextInteractionSnapshot = appendInteractionText(cumulativeInteractionSnapshot, interactionDelta);
66500
+ if (nextInteractionSnapshot && nextInteractionSnapshot !== cumulativeInteractionSnapshot) {
66039
66501
  sawChange = true;
66040
- lastVisibleSnapshot = interactionSnapshot;
66502
+ cumulativeInteractionSnapshot = nextInteractionSnapshot;
66041
66503
  if (!firstMeaningfulDeltaLogged) {
66042
66504
  firstMeaningfulDeltaLogged = true;
66043
66505
  logLatencyDebug("tmux-first-meaningful-delta", params.timingContext, {
@@ -66046,7 +66508,7 @@ async function monitorTmuxRun(params) {
66046
66508
  });
66047
66509
  }
66048
66510
  await params.onRunning({
66049
- snapshot: interactionSnapshot,
66511
+ snapshot: cumulativeInteractionSnapshot,
66050
66512
  fullSnapshot: snapshot,
66051
66513
  initialSnapshot: params.initialSnapshot
66052
66514
  });
@@ -66055,14 +66517,14 @@ async function monitorTmuxRun(params) {
66055
66517
  if (!detachedNotified && now - params.startedAt >= params.maxRuntimeMs) {
66056
66518
  detachedNotified = true;
66057
66519
  await params.onDetached({
66058
- snapshot: deriveInteractionText(params.initialSnapshot, previousSnapshot),
66520
+ snapshot: cumulativeInteractionSnapshot || deriveInteractionText(params.initialSnapshot, previousSnapshot),
66059
66521
  fullSnapshot: previousSnapshot,
66060
66522
  initialSnapshot: params.initialSnapshot
66061
66523
  });
66062
66524
  }
66063
66525
  if (sawChange && now - lastChangeAt >= params.idleTimeoutMs) {
66064
66526
  await params.onCompleted({
66065
- snapshot: deriveInteractionText(params.initialSnapshot, previousSnapshot),
66527
+ snapshot: cumulativeInteractionSnapshot || deriveInteractionText(params.initialSnapshot, previousSnapshot),
66066
66528
  fullSnapshot: previousSnapshot,
66067
66529
  initialSnapshot: params.initialSnapshot
66068
66530
  });
@@ -66070,7 +66532,7 @@ async function monitorTmuxRun(params) {
66070
66532
  }
66071
66533
  if (!sawChange && now - params.startedAt >= params.noOutputTimeoutMs) {
66072
66534
  await params.onTimeout({
66073
- snapshot: deriveInteractionText(params.initialSnapshot, previousSnapshot),
66535
+ snapshot: cumulativeInteractionSnapshot || deriveInteractionText(params.initialSnapshot, previousSnapshot),
66074
66536
  fullSnapshot: previousSnapshot,
66075
66537
  initialSnapshot: params.initialSnapshot
66076
66538
  });
@@ -66526,6 +66988,7 @@ class AgentService {
66526
66988
  cleanupTimer;
66527
66989
  loopTimers = new Set;
66528
66990
  intervalLoops = new Map;
66991
+ surfaceNotificationHandlers = new Map;
66529
66992
  constructor(loadedConfig, deps = {}) {
66530
66993
  this.loadedConfig = loadedConfig;
66531
66994
  this.tmuxClient = deps.tmux ?? new TmuxClient(this.loadedConfig.raw.tmux.socketPath);
@@ -66554,7 +67017,9 @@ class AgentService {
66554
67017
  }
66555
67018
  await this.runnerSessions.runSessionCleanup();
66556
67019
  this.cleanupTimer = setInterval(() => {
66557
- this.runnerSessions.runSessionCleanup();
67020
+ this.runnerSessions.runSessionCleanup().catch((error) => {
67021
+ console.error("session cleanup failed", error);
67022
+ });
66558
67023
  }, cleanup.intervalMinutes * 60000);
66559
67024
  }
66560
67025
  async stop() {
@@ -66578,6 +67043,12 @@ class AgentService {
66578
67043
  async cleanupStaleSessions() {
66579
67044
  await this.runnerSessions.runSessionCleanup();
66580
67045
  }
67046
+ registerSurfaceNotificationHandler(params) {
67047
+ this.surfaceNotificationHandlers.set(this.getSurfaceNotificationHandlerKey(params.platform, params.accountId), params.handler);
67048
+ }
67049
+ unregisterSurfaceNotificationHandler(params) {
67050
+ this.surfaceNotificationHandlers.delete(this.getSurfaceNotificationHandlerKey(params.platform, params.accountId));
67051
+ }
66581
67052
  resolveTarget(target) {
66582
67053
  return resolveAgentTarget(this.loadedConfig, target);
66583
67054
  }
@@ -66686,7 +67157,9 @@ class AgentService {
66686
67157
  target: params.target,
66687
67158
  loop
66688
67159
  });
66689
- await this.runIntervalLoopIteration(loop.id);
67160
+ await this.runIntervalLoopIteration(loop.id, {
67161
+ notifyStart: false
67162
+ });
66690
67163
  return this.getIntervalLoop(loop.id);
66691
67164
  }
66692
67165
  async createCalendarLoop(params) {
@@ -66848,7 +67321,7 @@ class AgentService {
66848
67321
  }
66849
67322
  this.intervalLoops.delete(loopId);
66850
67323
  }
66851
- async runIntervalLoopIteration(loopId) {
67324
+ async runIntervalLoopIteration(loopId, options = {}) {
66852
67325
  const managed = this.intervalLoops.get(loopId);
66853
67326
  if (!managed) {
66854
67327
  return;
@@ -66884,6 +67357,9 @@ class AgentService {
66884
67357
  this.dropManagedIntervalLoop(loopId);
66885
67358
  return;
66886
67359
  }
67360
+ if (options.notifyStart !== false) {
67361
+ await this.notifyManagedLoopStart(managed.target, nextLoopState);
67362
+ }
66887
67363
  const promptText = this.buildManagedLoopPrompt(managed.target.agentId, nextLoopState);
66888
67364
  const { result } = this.enqueuePrompt(managed.target, promptText, {
66889
67365
  observerId: `loop:${loopId}:${attemptedRuns}`,
@@ -66903,12 +67379,107 @@ class AgentService {
66903
67379
  }
66904
67380
  this.scheduleIntervalLoopTimer(loopId, Math.max(0, nextLoopState.nextRunAt - now));
66905
67381
  }
66906
- scheduleIntervalLoopTimer(loopId, delayMs) {
66907
- const managed = this.intervalLoops.get(loopId);
66908
- if (!managed) {
67382
+ getSurfaceNotificationHandlerKey(platform, accountId) {
67383
+ return `${platform}:${accountId?.trim() || "default"}`;
67384
+ }
67385
+ async notifySurface(request) {
67386
+ const handler = this.surfaceNotificationHandlers.get(this.getSurfaceNotificationHandlerKey(request.binding.platform, request.binding.accountId));
67387
+ if (!handler) {
66909
67388
  return;
66910
67389
  }
66911
- if (managed.timer) {
67390
+ await handler(request);
67391
+ }
67392
+ resolveLoopSurfaceNotifications(identity) {
67393
+ if (identity.platform === "slack") {
67394
+ const channelConfig2 = this.loadedConfig.raw.channels.slack;
67395
+ let resolved2 = {
67396
+ queueStart: channelConfig2.surfaceNotifications?.queueStart ?? "brief",
67397
+ loopStart: channelConfig2.surfaceNotifications?.loopStart ?? "brief"
67398
+ };
67399
+ if (identity.conversationKind === "dm") {
67400
+ return {
67401
+ ...resolved2,
67402
+ ...channelConfig2.directMessages.surfaceNotifications ?? {}
67403
+ };
67404
+ }
67405
+ const routeCollection = identity.conversationKind === "group" ? channelConfig2.groups : channelConfig2.channels;
67406
+ const route = identity.channelId ? routeCollection[identity.channelId] ?? routeCollection["*"] : undefined;
67407
+ return {
67408
+ ...resolved2,
67409
+ ...route?.surfaceNotifications ?? {}
67410
+ };
67411
+ }
67412
+ const channelConfig = this.loadedConfig.raw.channels.telegram;
67413
+ let resolved = {
67414
+ queueStart: channelConfig.surfaceNotifications?.queueStart ?? "brief",
67415
+ loopStart: channelConfig.surfaceNotifications?.loopStart ?? "brief"
67416
+ };
67417
+ if (identity.conversationKind === "dm") {
67418
+ return {
67419
+ ...resolved,
67420
+ ...channelConfig.directMessages.surfaceNotifications ?? {}
67421
+ };
67422
+ }
67423
+ const groupRoute = identity.chatId ? channelConfig.groups[identity.chatId] ?? channelConfig.groups["*"] : undefined;
67424
+ resolved = {
67425
+ ...resolved,
67426
+ ...groupRoute?.surfaceNotifications ?? {}
67427
+ };
67428
+ if (identity.conversationKind === "topic" && identity.topicId) {
67429
+ return {
67430
+ ...resolved,
67431
+ ...groupRoute?.topics?.[identity.topicId]?.surfaceNotifications ?? {}
67432
+ };
67433
+ }
67434
+ return resolved;
67435
+ }
67436
+ async notifyManagedLoopStart(target, loop) {
67437
+ if (!loop.surfaceBinding) {
67438
+ return;
67439
+ }
67440
+ const identity = this.buildLoopChannelIdentity(loop.surfaceBinding);
67441
+ const notifications = this.resolveLoopSurfaceNotifications(identity);
67442
+ const text = loop.kind === "calendar" ? renderLoopStartNotification({
67443
+ mode: notifications.loopStart,
67444
+ agentId: target.agentId,
67445
+ loopId: loop.id,
67446
+ promptSummary: loop.promptSummary,
67447
+ cadence: loop.cadence,
67448
+ dayOfWeek: loop.dayOfWeek,
67449
+ localTime: loop.localTime,
67450
+ timezone: loop.timezone,
67451
+ nextRunAt: loop.nextRunAt,
67452
+ remainingRuns: Math.max(0, loop.maxRuns - loop.attemptedRuns),
67453
+ maxRuns: loop.maxRuns,
67454
+ kind: "calendar"
67455
+ }) : renderLoopStartNotification({
67456
+ mode: notifications.loopStart,
67457
+ agentId: target.agentId,
67458
+ loopId: loop.id,
67459
+ promptSummary: loop.promptSummary,
67460
+ intervalMs: loop.intervalMs,
67461
+ nextRunAt: loop.nextRunAt,
67462
+ remainingRuns: Math.max(0, loop.maxRuns - loop.attemptedRuns),
67463
+ maxRuns: loop.maxRuns
67464
+ });
67465
+ if (!text) {
67466
+ return;
67467
+ }
67468
+ try {
67469
+ await this.notifySurface({
67470
+ binding: loop.surfaceBinding,
67471
+ text
67472
+ });
67473
+ } catch (error) {
67474
+ console.error("loop start notification failed", error);
67475
+ }
67476
+ }
67477
+ scheduleIntervalLoopTimer(loopId, delayMs) {
67478
+ const managed = this.intervalLoops.get(loopId);
67479
+ if (!managed) {
67480
+ return;
67481
+ }
67482
+ if (managed.timer) {
66912
67483
  clearTimeout(managed.timer);
66913
67484
  this.loopTimers.delete(managed.timer);
66914
67485
  }
@@ -66919,7 +67490,9 @@ class AgentService {
66919
67490
  return;
66920
67491
  }
66921
67492
  current.timer = undefined;
66922
- this.runIntervalLoopIteration(loopId).catch((error) => {
67493
+ this.runIntervalLoopIteration(loopId, {
67494
+ notifyStart: true
67495
+ }).catch((error) => {
66923
67496
  if (this.shouldSuppressLoopShutdownError(error)) {
66924
67497
  return;
66925
67498
  }
@@ -66977,6 +67550,7 @@ class AgentService {
66977
67550
  buildLoopChannelIdentity(binding) {
66978
67551
  return {
66979
67552
  platform: binding.platform,
67553
+ accountId: binding.accountId,
66980
67554
  conversationKind: binding.conversationKind,
66981
67555
  channelId: binding.channelId,
66982
67556
  chatId: binding.chatId,
@@ -67386,18 +67960,6 @@ function parseAgentCommand(text, options = {}) {
67386
67960
  source: "slash"
67387
67961
  };
67388
67962
  }
67389
- if (lowered === "queue-list" || lowered === "queuelist") {
67390
- return {
67391
- type: "control",
67392
- name: "queue-list"
67393
- };
67394
- }
67395
- if (lowered === "queue-clear" || lowered === "queueclear") {
67396
- return {
67397
- type: "control",
67398
- name: "queue-clear"
67399
- };
67400
- }
67401
67963
  if (lowered === "loop") {
67402
67964
  const loopText = withoutSlash.slice(command.length).trim();
67403
67965
  const loweredLoopText = loopText.toLowerCase();
@@ -67445,9 +68007,37 @@ function parseAgentCommand(text, options = {}) {
67445
68007
  };
67446
68008
  }
67447
68009
  if (lowered === "queue" || lowered === "q") {
68010
+ const queueText = withoutSlash.slice(command.length).trim();
68011
+ const normalizedQueueText = queueText.toLowerCase();
68012
+ if (lowered === "queue") {
68013
+ if (normalizedQueueText === "list") {
68014
+ return {
68015
+ type: "control",
68016
+ name: "queue-list"
68017
+ };
68018
+ }
68019
+ if (normalizedQueueText === "clear") {
68020
+ return {
68021
+ type: "control",
68022
+ name: "queue-clear"
68023
+ };
68024
+ }
68025
+ }
67448
68026
  return {
67449
68027
  type: "queue",
67450
- text: withoutSlash.slice(command.length).trim()
68028
+ text: queueText
68029
+ };
68030
+ }
68031
+ if (lowered === "queue-list" || lowered === "queuelist") {
68032
+ return {
68033
+ type: "control",
68034
+ name: "queue-list"
68035
+ };
68036
+ }
68037
+ if (lowered === "queue-clear" || lowered === "queueclear") {
68038
+ return {
68039
+ type: "control",
68040
+ name: "queue-clear"
67451
68041
  };
67452
68042
  }
67453
68043
  if (lowered === "steer" || lowered === "s") {
@@ -67495,11 +68085,7 @@ function renderAgentControlSlashHelp() {
67495
68085
  "- `/followup mention-only`: require explicit mention for each later turn",
67496
68086
  "- `/followup pause`: stop passive follow-up until the next explicit mention",
67497
68087
  "- `/followup resume`: clear the runtime override and restore config defaults",
67498
- "- `/streaming status`: show the configured streaming mode for this surface",
67499
- "- `/streaming on`: enable streaming for this surface using the current `all` preview behavior",
67500
- "- `/streaming off`: disable surface streaming previews for this surface",
67501
- "- `/streaming latest`: prefer the latest preview shape for this surface",
67502
- "- `/streaming all`: retain the full current preview shape for this surface",
68088
+ "- `/streaming status|on|off|latest|all`: show or change streaming mode for this surface",
67503
68089
  "- `/responsemode status`: show the configured response mode for this surface",
67504
68090
  "- `/responsemode capture-pane`: settle replies from captured pane output for this surface",
67505
68091
  "- `/responsemode message-tool`: expect the agent to reply through `clisbot message send` for this surface",
@@ -67508,8 +68094,8 @@ function renderAgentControlSlashHelp() {
67508
68094
  "- `/additionalmessagemode queue`: queue later user messages behind the active run for this surface",
67509
68095
  "- `/queue <message>` or `\\q <message>`: enqueue a later message behind the active run and let clisbot deliver it in order",
67510
68096
  "- `/steer <message>` or `\\s <message>`: inject a steering message into the active run immediately",
67511
- "- `/queue-list`: show queued messages that have not started yet",
67512
- "- `/queue-clear`: clear queued messages that have not started yet",
68097
+ "- `/queue list`: show queued messages that have not started yet",
68098
+ "- `/queue clear`: clear queued messages that have not started yet",
67513
68099
  ...renderLoopHelpLines(),
67514
68100
  "- `/bash` followed by a shell command: requires `shellExecute` on the resolved agent role",
67515
68101
  "- shortcut prefixes such as `!` run bash only when the resolved agent role allows `shellExecute`",
@@ -67580,11 +68166,11 @@ function formatChannelFollowUpStatus(params) {
67580
68166
  }
67581
68167
 
67582
68168
  // src/channels/streaming-config.ts
67583
- function getEditableConfigPath7() {
68169
+ function getEditableConfigPath8() {
67584
68170
  return process.env.CLISBOT_CONFIG_PATH;
67585
68171
  }
67586
68172
  async function getConversationStreaming(params) {
67587
- const { config } = await readEditableConfig(getEditableConfigPath7());
68173
+ const { config } = await readEditableConfig(getEditableConfigPath8());
67588
68174
  const target = resolveConfiguredSurfaceModeTarget(config, "streaming", buildConfiguredTargetFromIdentity(params.identity));
67589
68175
  return {
67590
68176
  label: target.label,
@@ -67592,7 +68178,7 @@ async function getConversationStreaming(params) {
67592
68178
  };
67593
68179
  }
67594
68180
  async function setConversationStreaming(params) {
67595
- const { config, configPath } = await readEditableConfig(getEditableConfigPath7());
68181
+ const { config, configPath } = await readEditableConfig(getEditableConfigPath8());
67596
68182
  const target = resolveConfiguredSurfaceModeTarget(config, "streaming", buildConfiguredTargetFromIdentity(params.identity));
67597
68183
  target.set(params.streaming);
67598
68184
  await writeEditableConfig(configPath, config);
@@ -67673,7 +68259,7 @@ function renderRouteStatusMessage(params) {
67673
68259
  if (params.identity.topicId) {
67674
68260
  lines.push(`topicId: \`${params.identity.topicId}\``);
67675
68261
  }
67676
- 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}\``);
68262
+ 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}\``);
67677
68263
  if (params.runtimeState.startedAt) {
67678
68264
  lines.push(`run.startedAt: \`${new Date(params.runtimeState.startedAt).toISOString()}\``);
67679
68265
  }
@@ -67687,7 +68273,7 @@ function renderRouteStatusMessage(params) {
67687
68273
  lines.push(`- \`${loop.id}\` ${renderLoopStatusSchedule(loop)} remaining \`${loop.remainingRuns}\` nextRunAt \`${new Date(loop.nextRunAt).toISOString()}\``);
67688
68274
  }
67689
68275
  }
67690
- lines.push("", "Useful commands:", "- `/help`", "- `/whoami`", "- `/status`", "- `/attach`, `/detach`, `/watch every 30s`", "- `/followup status`", "- `/streaming status`", "- `/responsemode status`", "- `/additionalmessagemode status`", "- `/loop status`, `/loop cancel`, `/loop cancel <id>`", "- `/queue <message>`, `/steer <message>`", "- `/queue-list`, `/queue-clear`", params.route.verbose === "off" ? "- `/transcript` disabled on this route (`verbose: off`)" : "- `/transcript` enabled on this route (`verbose: minimal`)", "- `/bash` requires `shellExecute`");
68276
+ lines.push("", "Useful commands:", "- `/help`", "- `/whoami`", "- `/status`", "- `/attach`, `/detach`, `/watch every 30s`", "- `/followup status`", "- `/streaming status|on|off|latest|all`", "- `/responsemode status`", "- `/additionalmessagemode status`", "- `/loop status`, `/loop cancel`, `/loop cancel <id>`", "- `/queue <message>`, `/steer <message>`", "- `/queue list`, `/queue clear`", params.route.verbose === "off" ? "- `/transcript` disabled on this route (`verbose: off`)" : "- `/transcript` enabled on this route (`verbose: minimal`)", "- `/bash` requires `shellExecute`");
67691
68277
  return lines.join(`
67692
68278
  `);
67693
68279
  }
@@ -67910,6 +68496,7 @@ async function resolveLoopPromptText(params) {
67910
68496
  function buildLoopSurfaceBinding(identity) {
67911
68497
  return {
67912
68498
  platform: identity.platform,
68499
+ accountId: identity.accountId,
67913
68500
  conversationKind: identity.conversationKind,
67914
68501
  channelId: identity.channelId,
67915
68502
  chatId: identity.chatId,
@@ -67925,7 +68512,9 @@ async function executePromptDelivery(params) {
67925
68512
  let finalReplyRecorded = false;
67926
68513
  let loggedFirstRunningUpdate = false;
67927
68514
  let activePreviewStartedAt;
67928
- let lastFrozenPreviewText;
68515
+ let messageToolPreviewHandedOff = false;
68516
+ let queueStartPending = false;
68517
+ let deferredQueueStartPreview = false;
67929
68518
  const paneManagedDelivery = params.route.responseMode === "capture-pane" || params.forceQueuedDelivery === true;
67930
68519
  const messageToolPreview = params.route.responseMode === "message-tool" && params.forceQueuedDelivery !== true && params.route.streaming !== "off";
67931
68520
  const previewEnabled = params.route.streaming !== "off" && (paneManagedDelivery || messageToolPreview);
@@ -67961,11 +68550,12 @@ async function executePromptDelivery(params) {
67961
68550
  renderedState = undefined;
67962
68551
  activePreviewStartedAt = undefined;
67963
68552
  }
67964
- function resetPreviewBoundary() {
67965
- lastFrozenPreviewText = renderedState?.text ?? lastFrozenPreviewText;
67966
- responseChunks = [];
67967
- renderedState = undefined;
67968
- activePreviewStartedAt = undefined;
68553
+ async function handoffMessageToolPreview() {
68554
+ if (messageToolPreviewHandedOff) {
68555
+ return;
68556
+ }
68557
+ messageToolPreviewHandedOff = true;
68558
+ await clearResponseText();
67969
68559
  }
67970
68560
  async function getMessageToolRuntimeSignals() {
67971
68561
  if (params.route.responseMode !== "message-tool" || params.forceQueuedDelivery === true) {
@@ -67999,6 +68589,68 @@ async function executePromptDelivery(params) {
67999
68589
  agentId: params.route.agentId,
68000
68590
  sessionKey: params.sessionTarget.sessionKey
68001
68591
  });
68592
+ async function maybeRenderQueueStartNotification() {
68593
+ if (!queueStartPending) {
68594
+ return false;
68595
+ }
68596
+ const text = renderQueueStartNotification({
68597
+ mode: params.queueStartMode ?? "none",
68598
+ agentId: params.route.agentId,
68599
+ promptSummary: params.notificationPromptSummary ?? summarizeSurfaceNotificationText(params.promptText)
68600
+ });
68601
+ if (!text) {
68602
+ queueStartPending = false;
68603
+ return false;
68604
+ }
68605
+ if (previewEnabled && responseChunks.length === 0) {
68606
+ deferredQueueStartPreview = true;
68607
+ return true;
68608
+ }
68609
+ queueStartPending = false;
68610
+ if (responseChunks.length > 0) {
68611
+ const postedNew = await renderResponseText(text);
68612
+ if (postedNew) {
68613
+ await recordVisibleReply("reply", "channel");
68614
+ activePreviewStartedAt = Date.now();
68615
+ }
68616
+ renderedState = {
68617
+ text,
68618
+ body: ""
68619
+ };
68620
+ return true;
68621
+ }
68622
+ const posted = await params.postText(text);
68623
+ if (posted.length > 0) {
68624
+ await recordVisibleReply("reply", "channel");
68625
+ }
68626
+ return posted.length > 0;
68627
+ }
68628
+ function buildInitialPlaceholderText(positionAhead) {
68629
+ if (deferredQueueStartPreview && queueStartPending) {
68630
+ deferredQueueStartPreview = false;
68631
+ queueStartPending = false;
68632
+ return renderQueueStartNotification({
68633
+ mode: params.queueStartMode ?? "none",
68634
+ agentId: params.route.agentId,
68635
+ promptSummary: params.notificationPromptSummary ?? summarizeSurfaceNotificationText(params.promptText)
68636
+ }) ?? renderPlatformInteraction({
68637
+ platform: params.identity.platform,
68638
+ status: positionAhead > 0 ? "queued" : "running",
68639
+ content: "",
68640
+ queuePosition: positionAhead,
68641
+ maxChars: Number.POSITIVE_INFINITY,
68642
+ note: positionAhead > 0 ? "Waiting for the agent queue to clear." : "Working..."
68643
+ });
68644
+ }
68645
+ return renderPlatformInteraction({
68646
+ platform: params.identity.platform,
68647
+ status: positionAhead > 0 ? "queued" : "running",
68648
+ content: "",
68649
+ queuePosition: positionAhead,
68650
+ maxChars: Number.POSITIVE_INFINITY,
68651
+ note: positionAhead > 0 ? "Waiting for the agent queue to clear." : "Working..."
68652
+ });
68653
+ }
68002
68654
  try {
68003
68655
  const { positionAhead, result } = params.agentService.enqueuePrompt(params.sessionTarget, params.promptText, {
68004
68656
  observerId: params.observerId,
@@ -68007,9 +68659,6 @@ async function executePromptDelivery(params) {
68007
68659
  if (!paneManagedDelivery && !messageToolPreview) {
68008
68660
  return;
68009
68661
  }
68010
- if (params.route.streaming === "off" && update.status === "running") {
68011
- return;
68012
- }
68013
68662
  if (update.status === "running" && !loggedFirstRunningUpdate) {
68014
68663
  loggedFirstRunningUpdate = true;
68015
68664
  logLatencyDebug("channel-first-running-update", params.timingContext, {
@@ -68018,14 +68667,20 @@ async function executePromptDelivery(params) {
68018
68667
  });
68019
68668
  }
68020
68669
  await (renderChain = renderChain.then(async () => {
68021
- const signals = await getMessageToolRuntimeSignals();
68022
- if (messageToolPreview && typeof activePreviewStartedAt === "number" && typeof signals.messageToolFinalReplyAt === "number" && signals.messageToolFinalReplyAt >= activePreviewStartedAt) {
68023
- lastFrozenPreviewText = renderedState?.text ?? lastFrozenPreviewText;
68024
- activePreviewStartedAt = undefined;
68670
+ let renderedQueueStart = false;
68671
+ if (update.status === "running") {
68672
+ renderedQueueStart = await maybeRenderQueueStartNotification();
68673
+ }
68674
+ if (params.route.streaming === "off" && update.status === "running") {
68675
+ return;
68676
+ }
68677
+ if (renderedQueueStart) {
68025
68678
  return;
68026
68679
  }
68027
- if (messageToolPreview && typeof activePreviewStartedAt === "number" && typeof signals.lastMessageToolReplyAt === "number" && signals.lastMessageToolReplyAt >= activePreviewStartedAt) {
68028
- resetPreviewBoundary();
68680
+ const signals = await getMessageToolRuntimeSignals();
68681
+ if (messageToolPreview && typeof activePreviewStartedAt === "number" && (typeof signals.messageToolFinalReplyAt === "number" && signals.messageToolFinalReplyAt >= activePreviewStartedAt || typeof signals.lastMessageToolReplyAt === "number" && signals.lastMessageToolReplyAt >= activePreviewStartedAt)) {
68682
+ await handoffMessageToolPreview();
68683
+ return;
68029
68684
  }
68030
68685
  const nextState2 = buildRenderedMessageState({
68031
68686
  platform: params.identity.platform,
@@ -68041,9 +68696,6 @@ async function executePromptDelivery(params) {
68041
68696
  if (renderedState?.text === nextState2.text) {
68042
68697
  return;
68043
68698
  }
68044
- if (!renderedState && lastFrozenPreviewText === nextState2.text) {
68045
- return;
68046
- }
68047
68699
  const postedNew2 = await renderResponseText(nextState2.text);
68048
68700
  if (postedNew2) {
68049
68701
  await recordVisibleReply("reply", "channel");
@@ -68053,15 +68705,9 @@ async function executePromptDelivery(params) {
68053
68705
  }));
68054
68706
  }
68055
68707
  });
68708
+ queueStartPending = positionAhead > 0 && (params.queueStartMode ?? "none") !== "none";
68056
68709
  if (previewEnabled) {
68057
- const placeholderText = renderPlatformInteraction({
68058
- platform: params.identity.platform,
68059
- status: positionAhead > 0 ? "queued" : "running",
68060
- content: "",
68061
- queuePosition: positionAhead,
68062
- maxChars: Number.POSITIVE_INFINITY,
68063
- note: positionAhead > 0 ? "Waiting for the agent queue to clear." : "Working..."
68064
- });
68710
+ const placeholderText = buildInitialPlaceholderText(positionAhead);
68065
68711
  const postedNew2 = await renderResponseText(placeholderText);
68066
68712
  if (postedNew2) {
68067
68713
  await recordVisibleReply("reply", "channel");
@@ -68091,6 +68737,10 @@ async function executePromptDelivery(params) {
68091
68737
  }
68092
68738
  const finalResult = await result;
68093
68739
  await renderChain;
68740
+ await maybeRenderQueueStartNotification();
68741
+ if (!paneManagedDelivery && messageToolPreviewHandedOff) {
68742
+ return;
68743
+ }
68094
68744
  const nextState = buildRenderedMessageState({
68095
68745
  platform: params.identity.platform,
68096
68746
  status: finalResult.status,
@@ -68136,6 +68786,21 @@ async function executePromptDelivery(params) {
68136
68786
  if (finalResult.status === "completed") {
68137
68787
  return;
68138
68788
  }
68789
+ if (messageToolPreview && responseChunks.length > 0) {
68790
+ const postedNew2 = await renderResponseText(renderPlatformInteraction({
68791
+ platform: params.identity.platform,
68792
+ status: finalResult.status,
68793
+ content: "",
68794
+ maxChars: Number.POSITIVE_INFINITY,
68795
+ note: finalResult.note,
68796
+ allowTranscriptInspection: allowTranscriptInspectionForRoute(params.route),
68797
+ responsePolicy: params.route.response
68798
+ }));
68799
+ if (postedNew2) {
68800
+ await recordVisibleReply("reply", "channel");
68801
+ }
68802
+ return;
68803
+ }
68139
68804
  if (params.route.streaming === "off" || responseChunks.length === 0) {
68140
68805
  const postedNew2 = await renderResponseText(renderPlatformInteraction({
68141
68806
  platform: params.identity.platform,
@@ -68557,6 +69222,8 @@ ${renderLoopUsage()}`);
68557
69222
  route: params.route,
68558
69223
  maxChars: params.maxChars,
68559
69224
  promptText: buildLoopPromptText(resolvedLoopPrompt.text),
69225
+ queueStartMode: params.route.surfaceNotifications.queueStart,
69226
+ notificationPromptSummary: summarizeLoopPrompt(resolvedLoopPrompt.text, resolvedLoopPrompt.maintenancePrompt),
68560
69227
  postText: params.postText,
68561
69228
  reconcileText: params.reconcileText,
68562
69229
  observerId: `${observerId}:loop:${index + 1}`
@@ -68697,6 +69364,8 @@ ${escapeCodeFence(shellResult.output)}
68697
69364
  route: params.route,
68698
69365
  maxChars: params.maxChars,
68699
69366
  promptText: delayedPromptText,
69367
+ queueStartMode: params.route.surfaceNotifications.queueStart,
69368
+ notificationPromptSummary: explicitQueueMessage ?? summarizeSurfaceNotificationText(params.text),
68700
69369
  postText: params.postText,
68701
69370
  reconcileText: params.reconcileText,
68702
69371
  observerId,
@@ -68757,6 +69426,23 @@ function isTelegramSenderAllowed(params) {
68757
69426
  }
68758
69427
 
68759
69428
  // src/auth/resolve.ts
69429
+ function mergeRoleDefinitions2(inherited, override) {
69430
+ return {
69431
+ allow: override?.allow ?? inherited?.allow ?? [],
69432
+ users: override?.users ?? inherited?.users ?? []
69433
+ };
69434
+ }
69435
+ function mergeRoleRecord2(defaults, overrides) {
69436
+ const merged = {};
69437
+ const roleNames = new Set([
69438
+ ...Object.keys(defaults ?? {}),
69439
+ ...Object.keys(overrides ?? {})
69440
+ ]);
69441
+ for (const roleName of roleNames) {
69442
+ merged[roleName] = mergeRoleDefinitions2(defaults?.[roleName], overrides?.[roleName]);
69443
+ }
69444
+ return merged;
69445
+ }
68760
69446
  function normalizePrincipal(principal) {
68761
69447
  const trimmed = principal.trim();
68762
69448
  if (!trimmed) {
@@ -68804,10 +69490,7 @@ function getAgentAuth(config, agentId) {
68804
69490
  const override = entry?.auth;
68805
69491
  return {
68806
69492
  defaultRole: override?.defaultRole ?? defaults.defaultRole,
68807
- roles: {
68808
- ...defaults.roles,
68809
- ...override?.roles ?? {}
68810
- }
69493
+ roles: mergeRoleRecord2(defaults.roles, override?.roles)
68811
69494
  };
68812
69495
  }
68813
69496
  function getAllowedPermissions(roles, role) {
@@ -68930,6 +69613,10 @@ function buildSharedChannelRoute(params) {
68930
69613
  response: params.route?.response ?? params.channelConfig.response,
68931
69614
  responseMode: params.route?.responseMode ?? agentEntry?.responseMode ?? params.channelConfig.responseMode,
68932
69615
  additionalMessageMode: params.route?.additionalMessageMode ?? agentEntry?.additionalMessageMode ?? params.channelConfig.additionalMessageMode,
69616
+ surfaceNotifications: {
69617
+ queueStart: params.route?.surfaceNotifications?.queueStart ?? params.channelConfig.surfaceNotifications?.queueStart ?? "brief",
69618
+ loopStart: params.route?.surfaceNotifications?.loopStart ?? params.channelConfig.surfaceNotifications?.loopStart ?? "brief"
69619
+ },
68933
69620
  verbose: params.route?.verbose ?? params.channelConfig.verbose,
68934
69621
  followUp: {
68935
69622
  mode: params.route?.followUp?.mode ?? params.channelConfig.followUp.mode,
@@ -69906,6 +70593,20 @@ class SlackSocketService {
69906
70593
  appToken: this.accountConfig.appToken,
69907
70594
  socketMode: true
69908
70595
  });
70596
+ this.agentService.registerSurfaceNotificationHandler({
70597
+ platform: "slack",
70598
+ accountId: this.accountId,
70599
+ handler: async ({ binding, text }) => {
70600
+ if (!binding.channelId) {
70601
+ return;
70602
+ }
70603
+ await postSlackText(this.app.client, {
70604
+ channel: binding.channelId,
70605
+ threadTs: binding.threadTs,
70606
+ text
70607
+ });
70608
+ }
70609
+ });
69909
70610
  this.registerEvents();
69910
70611
  }
69911
70612
  getSlackMaxChars(agentId) {
@@ -70159,6 +70860,7 @@ class SlackSocketService {
70159
70860
  const cliTool = getAgentEntry(this.loadedConfig, params.route.agentId)?.cliTool;
70160
70861
  const identity = {
70161
70862
  platform: "slack",
70863
+ accountId: this.accountId,
70162
70864
  conversationKind: params.conversationKind,
70163
70865
  senderId: typeof event.user === "string" ? event.user.trim().toUpperCase() : undefined,
70164
70866
  channelId,
@@ -70342,6 +71044,10 @@ class SlackSocketService {
70342
71044
  await this.startPromise;
70343
71045
  }
70344
71046
  async stop() {
71047
+ this.agentService.unregisterSurfaceNotificationHandler({
71048
+ platform: "slack",
71049
+ accountId: this.accountId
71050
+ });
70345
71051
  await this.app.stop();
70346
71052
  }
70347
71053
  getBotUserLabel() {
@@ -71196,6 +71902,33 @@ async function resolveTelegramAttachmentPaths(params) {
71196
71902
  return attachmentPaths;
71197
71903
  }
71198
71904
 
71905
+ // src/channels/telegram/route-guidance.ts
71906
+ function renderTelegramRouteChoiceMessage(params) {
71907
+ const chatId = String(params.chatId);
71908
+ const topicId = params.topicId != null ? String(params.topicId) : undefined;
71909
+ const lines = [
71910
+ topicId != null ? "clisbot: this Telegram topic is not configured yet." : "clisbot: this Telegram group is not configured yet.",
71911
+ "",
71912
+ "Ask the bot owner to choose one of these:",
71913
+ "",
71914
+ "Add the whole group with the default agent:",
71915
+ `\`clisbot channels add telegram-group ${chatId}\``,
71916
+ "",
71917
+ "Add the whole group with a specific agent:",
71918
+ `\`clisbot channels add telegram-group ${chatId} --agent <id>\``
71919
+ ];
71920
+ if (topicId != null) {
71921
+ lines.push("", "Add only this topic with a specific agent:", `\`clisbot channels add telegram-group ${chatId} --topic ${topicId} --agent <id>\``);
71922
+ }
71923
+ if (params.includeConfigPath) {
71924
+ lines.push("", topicId != null ? `Config path: \`channels.telegram.groups."${chatId}".topics."${topicId}"\`` : `Config path: \`channels.telegram.groups."${chatId}"\``);
71925
+ } else {
71926
+ lines.push("", "After that, routed commands such as `/status`, `/stop`, `/nudge`, `/followup`, and `/bash` will work here.");
71927
+ }
71928
+ return lines.join(`
71929
+ `);
71930
+ }
71931
+
71199
71932
  // src/channels/telegram/typing.ts
71200
71933
  var TELEGRAM_TYPING_HEARTBEAT_MS = 4500;
71201
71934
  function logTelegramTypingError(onError, error) {
@@ -71258,8 +71991,10 @@ var TELEGRAM_FULL_COMMANDS = [
71258
71991
  { command: "followup", description: "Show or change follow-up mode" },
71259
71992
  { command: "streaming", description: "Show or change streaming mode" },
71260
71993
  { command: "responsemode", description: "Show or change response mode" },
71994
+ { command: "additionalmessagemode", description: "Show or change later-message mode" },
71261
71995
  { command: "queue", description: "Queue a later message behind the active run" },
71262
71996
  { command: "steer", description: "Steer the active run immediately" },
71997
+ { command: "loop", description: "Show or manage loops for this route" },
71263
71998
  { command: "bash", description: "Run bash in the agent workspace" }
71264
71999
  ];
71265
72000
  var TELEGRAM_STARTUP_CONFLICT_MAX_WAIT_MS = 6000;
@@ -71337,6 +72072,7 @@ class TelegramPollingService {
71337
72072
  activityStore;
71338
72073
  accountId;
71339
72074
  accountConfig;
72075
+ reportLifecycle;
71340
72076
  botUserId = 0;
71341
72077
  botUsername = "";
71342
72078
  running = false;
@@ -71345,13 +72081,35 @@ class TelegramPollingService {
71345
72081
  activePollController;
71346
72082
  inFlightUpdates = new Set;
71347
72083
  processingIndicators = new ConversationProcessingIndicatorCoordinator;
71348
- constructor(loadedConfig, agentService, processedEventsStore, activityStore, accountId = "default", accountConfig) {
72084
+ constructor(loadedConfig, agentService, processedEventsStore, activityStore, accountId = "default", accountConfig, reportLifecycle) {
71349
72085
  this.loadedConfig = loadedConfig;
71350
72086
  this.agentService = agentService;
71351
72087
  this.processedEventsStore = processedEventsStore;
71352
72088
  this.activityStore = activityStore;
71353
72089
  this.accountId = accountId;
71354
72090
  this.accountConfig = accountConfig;
72091
+ this.reportLifecycle = reportLifecycle;
72092
+ this.agentService.registerSurfaceNotificationHandler({
72093
+ platform: "telegram",
72094
+ accountId: this.accountId,
72095
+ handler: async ({ binding, text }) => {
72096
+ if (!binding.chatId) {
72097
+ return;
72098
+ }
72099
+ const chatId = Number(binding.chatId);
72100
+ if (!Number.isFinite(chatId)) {
72101
+ return;
72102
+ }
72103
+ const topicId = binding.topicId ? Number(binding.topicId) : undefined;
72104
+ await postTelegramText({
72105
+ token: this.accountConfig.botToken,
72106
+ chatId,
72107
+ text,
72108
+ topicId: Number.isFinite(topicId) ? topicId : undefined,
72109
+ omitThreadId: shouldOmitTelegramThreadId(Number.isFinite(topicId) ? topicId : undefined)
72110
+ });
72111
+ }
72112
+ });
71355
72113
  }
71356
72114
  async start() {
71357
72115
  const me = await callTelegramApi(this.accountConfig.botToken, "getMe", {});
@@ -71368,6 +72126,10 @@ class TelegramPollingService {
71368
72126
  this.activePollController?.abort();
71369
72127
  await this.loopPromise;
71370
72128
  await Promise.allSettled([...this.inFlightUpdates]);
72129
+ this.agentService.unregisterSurfaceNotificationHandler({
72130
+ platform: "telegram",
72131
+ accountId: this.accountId
72132
+ });
71371
72133
  }
71372
72134
  getBotLabel() {
71373
72135
  return this.botUsername ? `@${this.botUsername}` : `${this.botUserId || "unknown"}`;
@@ -71414,6 +72176,15 @@ class TelegramPollingService {
71414
72176
  }
71415
72177
  if (isTelegramPollingConflict(error)) {
71416
72178
  this.running = false;
72179
+ await this.reportLifecycle?.({
72180
+ connection: "failed",
72181
+ summary: "Telegram polling stopped because another instance is already using this bot token.",
72182
+ detail: error instanceof Error ? error.message : String(error),
72183
+ actions: [
72184
+ "stop the other Telegram poller that is using the same bot token",
72185
+ "run `clisbot start` again after the token is no longer in use elsewhere"
72186
+ ]
72187
+ });
71417
72188
  console.error("telegram polling stopped: another bot instance is already calling getUpdates for this token");
71418
72189
  return;
71419
72190
  }
@@ -71581,6 +72352,7 @@ class TelegramPollingService {
71581
72352
  const senderName = [message.from?.first_name, message.from?.last_name].filter((value) => typeof value === "string" && value.trim().length > 0).join(" ").trim();
71582
72353
  const identity = {
71583
72354
  platform: "telegram",
72355
+ accountId: this.accountId,
71584
72356
  conversationKind: routeInfo.conversationKind,
71585
72357
  senderId: message.from?.id != null ? String(message.from.id).trim() : undefined,
71586
72358
  senderName: senderName || message.from?.username?.trim() || undefined,
@@ -71993,7 +72765,7 @@ var telegramChannelPlugin = {
71993
72765
  accountId,
71994
72766
  config
71995
72767
  })),
71996
- createRuntimeService: (context, account) => new TelegramPollingService(context.loadedConfig, context.agentService, context.processedEventsStore, context.activityStore, account.accountId, account.config),
72768
+ createRuntimeService: (context, account) => new TelegramPollingService(context.loadedConfig, context.agentService, context.processedEventsStore, context.activityStore, account.accountId, account.config, context.reportLifecycle),
71997
72769
  renderHealthSummary: (state) => {
71998
72770
  switch (state) {
71999
72771
  case "starting":
@@ -72087,7 +72859,7 @@ var defaultMessageCliDependencies = {
72087
72859
  await agentService.recordConversationReply(target, kind, source);
72088
72860
  }
72089
72861
  };
72090
- function parseRepeatedOption2(args, name) {
72862
+ function parseRepeatedOption3(args, name) {
72091
72863
  const values = [];
72092
72864
  for (let index = 0;index < args.length; index += 1) {
72093
72865
  if (args[index] !== name) {
@@ -72102,7 +72874,7 @@ function parseRepeatedOption2(args, name) {
72102
72874
  return values;
72103
72875
  }
72104
72876
  function parseOptionValue4(args, name) {
72105
- const values = parseRepeatedOption2(args, name);
72877
+ const values = parseRepeatedOption3(args, name);
72106
72878
  return values.length > 0 ? values.at(-1) : undefined;
72107
72879
  }
72108
72880
  function parseIntegerOption(args, name) {
@@ -72116,7 +72888,7 @@ function parseIntegerOption(args, name) {
72116
72888
  }
72117
72889
  return parsed;
72118
72890
  }
72119
- function hasFlag3(args, name) {
72891
+ function hasFlag4(args, name) {
72120
72892
  return args.includes(name);
72121
72893
  }
72122
72894
  function resolveReplyKind(command) {
@@ -72148,18 +72920,18 @@ function parseMessageCommand(args) {
72148
72920
  media: parseOptionValue4(rest, "--media"),
72149
72921
  messageId: parseOptionValue4(rest, "--message-id"),
72150
72922
  emoji: parseOptionValue4(rest, "--emoji"),
72151
- remove: hasFlag3(rest, "--remove"),
72923
+ remove: hasFlag4(rest, "--remove"),
72152
72924
  threadId: parseOptionValue4(rest, "--thread-id"),
72153
72925
  replyTo: parseOptionValue4(rest, "--reply-to"),
72154
72926
  limit: parseIntegerOption(rest, "--limit"),
72155
72927
  query: parseOptionValue4(rest, "--query"),
72156
72928
  pollQuestion: parseOptionValue4(rest, "--poll-question"),
72157
- pollOptions: parseRepeatedOption2(rest, "--poll-option"),
72158
- forceDocument: hasFlag3(rest, "--force-document"),
72159
- silent: hasFlag3(rest, "--silent"),
72160
- progress: hasFlag3(rest, "--progress"),
72161
- final: hasFlag3(rest, "--final"),
72162
- json: hasFlag3(rest, "--json")
72929
+ pollOptions: parseRepeatedOption3(rest, "--poll-option"),
72930
+ forceDocument: hasFlag4(rest, "--force-document"),
72931
+ silent: hasFlag4(rest, "--silent"),
72932
+ progress: hasFlag4(rest, "--progress"),
72933
+ final: hasFlag4(rest, "--final"),
72934
+ json: hasFlag4(rest, "--json")
72163
72935
  };
72164
72936
  }
72165
72937
  function renderMessageHelp() {
@@ -72382,6 +73154,7 @@ class RuntimeSupervisor {
72382
73154
  reloadInFlight = false;
72383
73155
  reloadRequested = false;
72384
73156
  configWatchDebounceMs = 250;
73157
+ nextRuntimeId = 1;
72385
73158
  dependencies;
72386
73159
  constructor(configPath, dependencies) {
72387
73160
  this.configPath = configPath;
@@ -72398,15 +73171,50 @@ class RuntimeSupervisor {
72398
73171
  async start() {
72399
73172
  await this.reload("initial");
72400
73173
  }
72401
- async stop() {
73174
+ async stop(options = {}) {
72402
73175
  this.clearReloadTimer();
72403
73176
  this.stopWatchingConfig();
72404
73177
  await this.stopActiveRuntime();
73178
+ if (options.markChannelsStopped !== false) {
73179
+ for (const plugin of this.dependencies.listChannelPlugins()) {
73180
+ await this.dependencies.runtimeHealthStore.setChannel({
73181
+ channel: plugin.id,
73182
+ connection: "stopped",
73183
+ summary: plugin.renderHealthSummary("stopped")
73184
+ });
73185
+ }
73186
+ }
73187
+ }
73188
+ async markFatalFailure(error) {
73189
+ const activeRuntime = this.activeRuntime;
73190
+ if (!activeRuntime) {
73191
+ return;
73192
+ }
73193
+ const detail = error instanceof Error ? error.message : String(error);
73194
+ const instancesByChannel = new Map;
73195
+ for (const entry of activeRuntime.channelServices) {
73196
+ const identity = entry.service.getRuntimeIdentity?.();
73197
+ if (!identity) {
73198
+ continue;
73199
+ }
73200
+ const existing = instancesByChannel.get(entry.channel) ?? [];
73201
+ existing.push(identity);
73202
+ instancesByChannel.set(entry.channel, existing);
73203
+ }
72405
73204
  for (const plugin of this.dependencies.listChannelPlugins()) {
73205
+ if (!plugin.isEnabled(activeRuntime.loadedConfig)) {
73206
+ continue;
73207
+ }
72406
73208
  await this.dependencies.runtimeHealthStore.setChannel({
72407
73209
  channel: plugin.id,
72408
- connection: "stopped",
72409
- summary: plugin.renderHealthSummary("stopped")
73210
+ connection: "failed",
73211
+ summary: "Runtime crashed due to a fatal error.",
73212
+ detail,
73213
+ actions: [
73214
+ "run `clisbot logs` and inspect the fatal error",
73215
+ "fix the underlying runtime fault, then restart with `clisbot start`"
73216
+ ],
73217
+ instances: instancesByChannel.get(plugin.id) ?? []
72410
73218
  });
72411
73219
  }
72412
73220
  }
@@ -72484,6 +73292,7 @@ class RuntimeSupervisor {
72484
73292
  }
72485
73293
  }
72486
73294
  async createRuntime(loadedConfig) {
73295
+ const runtimeId = this.nextRuntimeId++;
72487
73296
  const agentService = this.dependencies.createAgentService(loadedConfig);
72488
73297
  const processedEventsStore = this.dependencies.createProcessedEventsStore(loadedConfig.processedEventsPath);
72489
73298
  const activityStore = this.dependencies.createActivityStore();
@@ -72505,7 +73314,14 @@ class RuntimeSupervisor {
72505
73314
  loadedConfig,
72506
73315
  agentService,
72507
73316
  processedEventsStore,
72508
- activityStore
73317
+ activityStore,
73318
+ reportLifecycle: (event) => this.reportChannelLifecycle({
73319
+ runtimeId,
73320
+ plugin,
73321
+ channelServices,
73322
+ accountId: account.accountId,
73323
+ event
73324
+ })
72509
73325
  }, account)
72510
73326
  });
72511
73327
  }
@@ -72536,6 +73352,8 @@ class RuntimeSupervisor {
72536
73352
  });
72537
73353
  }
72538
73354
  return {
73355
+ id: runtimeId,
73356
+ loadedConfig,
72539
73357
  agentService,
72540
73358
  channelServices
72541
73359
  };
@@ -72575,6 +73393,38 @@ class RuntimeSupervisor {
72575
73393
  });
72576
73394
  }
72577
73395
  }
73396
+ getChannelInstances(channelServices, channel) {
73397
+ return channelServices.filter((entry) => entry.channel === channel).map((entry) => entry.service.getRuntimeIdentity?.()).filter((identity) => identity != null);
73398
+ }
73399
+ async reportChannelLifecycle(params) {
73400
+ if (this.activeRuntime?.id !== params.runtimeId) {
73401
+ return;
73402
+ }
73403
+ const instances = this.getChannelInstances(params.channelServices, params.plugin.id);
73404
+ if (params.event.connection === "active") {
73405
+ await this.dependencies.runtimeHealthStore.setChannel({
73406
+ channel: params.plugin.id,
73407
+ connection: "active",
73408
+ summary: params.event.summary ?? params.plugin.renderActiveHealthSummary(Math.max(1, instances.length)),
73409
+ detail: params.event.detail,
73410
+ actions: params.event.actions,
73411
+ instances
73412
+ });
73413
+ return;
73414
+ }
73415
+ const detailPrefix = `account=${params.accountId}`;
73416
+ await this.dependencies.runtimeHealthStore.setChannel({
73417
+ channel: params.plugin.id,
73418
+ connection: "failed",
73419
+ summary: params.event.summary ?? `${params.plugin.id} channel failed after startup.`,
73420
+ detail: params.event.detail ? `${detailPrefix}; ${params.event.detail}` : detailPrefix,
73421
+ actions: params.event.actions ?? [
73422
+ "run `clisbot logs` and inspect the latest channel error",
73423
+ "restart `clisbot` after fixing the channel-level issue"
73424
+ ],
73425
+ instances
73426
+ });
73427
+ }
72578
73428
  async reconcileConfigWatcher(loadedConfig) {
72579
73429
  const configReload = loadedConfig.raw.control.configReload;
72580
73430
  this.configWatchDebounceMs = configReload.watchDebounceMs;
@@ -72765,6 +73615,11 @@ async function getRuntimeOperatorSummary(params) {
72765
73615
  ];
72766
73616
  return {
72767
73617
  loadedConfig,
73618
+ ownerSummary: {
73619
+ ownerPrincipals: loadedConfig.raw.app.auth.roles.owner?.users ?? [],
73620
+ adminPrincipals: loadedConfig.raw.app.auth.roles.admin?.users ?? [],
73621
+ ownerClaimWindowMinutes: loadedConfig.raw.app.auth.ownerClaimWindowMinutes
73622
+ },
72768
73623
  agentSummaries,
72769
73624
  channelSummaries,
72770
73625
  activeRuns: await agentService.listActiveSessionRuntimes(),
@@ -72846,11 +73701,75 @@ function renderAgentSummaryLines(summary) {
72846
73701
  })
72847
73702
  ];
72848
73703
  }
73704
+ function renderOwnerSummaryLines(summary) {
73705
+ const ownerPrincipals = summary.ownerSummary.ownerPrincipals;
73706
+ const adminPrincipals = summary.ownerSummary.adminPrincipals;
73707
+ const configuredOwners = ownerPrincipals.length > 0 ? ownerPrincipals.join(",") : "none";
73708
+ const claimWindow = `${summary.ownerSummary.ownerClaimWindowMinutes}m`;
73709
+ const lines = [
73710
+ "Owner:",
73711
+ ` - configured=${ownerPrincipals.length > 0 ? "yes" : "no"} principals=${configuredOwners} claimWindow=${claimWindow}`,
73712
+ " - access=full app control, DM pairing bypass, implicit admin across all agents/channels"
73713
+ ];
73714
+ if (adminPrincipals.length > 0) {
73715
+ lines.push(` - appAdmins=${adminPrincipals.join(",")}`);
73716
+ }
73717
+ return lines;
73718
+ }
73719
+ function hasConfiguredPrivilegedPrincipal(summary) {
73720
+ return summary.ownerSummary.ownerPrincipals.length > 0 || summary.ownerSummary.adminPrincipals.length > 0;
73721
+ }
73722
+ function renderPrivilegedChatHint(summary, action) {
73723
+ if (hasConfiguredPrivilegedPrincipal(summary)) {
73724
+ return `chat with the bot from that owner/admin account to ${action}`;
73725
+ }
73726
+ return `after you configure an owner/admin principal, use that account to ${action}`;
73727
+ }
73728
+ function appendChannelNextStepLines(lines, summary, prefix = "") {
73729
+ const slackEnabled = summary.channelSummaries.some((channel) => channel.channel === "slack" && channel.enabled);
73730
+ const telegramEnabled = summary.channelSummaries.some((channel) => channel.channel === "telegram" && channel.enabled);
73731
+ const hasEnabledChannel = slackEnabled || telegramEnabled;
73732
+ if (!hasEnabledChannel) {
73733
+ lines.push(`${prefix}- run \`clisbot channels enable <slack|telegram>\` for the first channel you want to expose`);
73734
+ return;
73735
+ }
73736
+ if (telegramEnabled && slackEnabled) {
73737
+ lines.push(`${prefix}- DM the Telegram or Slack bot first to confirm it responds normally`);
73738
+ } else if (telegramEnabled) {
73739
+ lines.push(`${prefix}- DM the Telegram bot first to confirm it responds normally`);
73740
+ } else {
73741
+ lines.push(`${prefix}- DM the Slack bot first to confirm it responds normally`);
73742
+ }
73743
+ lines.push(`${prefix}- after DM works, add the bot to the target Slack channel or Telegram group/topic`);
73744
+ lines.push(`${prefix}- route that surface with \`clisbot channels add slack-channel <channelId> --agent <id>\` or \`clisbot channels add telegram-group <chatId> --agent <id>\``);
73745
+ if (telegramEnabled) {
73746
+ lines.push(`${prefix}- Telegram: send \`/start\` in the target DM, group, or topic to get onboarding or pairing guidance`);
73747
+ }
73748
+ if (slackEnabled) {
73749
+ lines.push(`${prefix}- Slack: mention \`@<botname> \\start\` in the target channel to verify mention flow`);
73750
+ }
73751
+ }
73752
+ function appendAuthOnboardingLines(lines, summary, prefix = "") {
73753
+ lines.push(`${prefix}Auth onboarding:`);
73754
+ if (!hasConfiguredPrivilegedPrincipal(summary)) {
73755
+ 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`);
73756
+ lines.push(`${prefix} - set the first owner with: \`clisbot auth add-user app --role owner --user <principal>\``);
73757
+ } else {
73758
+ lines.push(`${prefix} - inspect current app roles with: \`clisbot auth show app\``);
73759
+ }
73760
+ lines.push(`${prefix} - inspect default agent roles with: \`clisbot auth show agent-defaults\``);
73761
+ lines.push(`${prefix} - add or remove principals with: \`clisbot auth add-user ...\` and \`clisbot auth remove-user ...\``);
73762
+ lines.push(`${prefix} - tune role permissions with: \`clisbot auth add-permission ...\` and \`clisbot auth remove-permission ...\``);
73763
+ lines.push(`${prefix} - run \`clisbot auth --help\` or read docs/user-guide/auth-and-roles.md for scopes and permission names`);
73764
+ }
72849
73765
  function renderChannelSummaryLines(summary) {
72850
73766
  return [
72851
73767
  "",
72852
73768
  "Channels:",
72853
73769
  ...summary.channelSummaries.map((channel) => {
73770
+ if (!channel.enabled) {
73771
+ return ` - ${channel.channel} enabled=no`;
73772
+ }
72854
73773
  const last = channel.lastActivityAt ? ` last=${formatTime(channel.lastActivityAt)} via ${channel.lastActivityAgentId ?? "unknown"}` : " last=never";
72855
73774
  const dm = ` dm=${channel.directMessagesEnabled ? channel.directMessagesPolicy : "disabled"}`;
72856
73775
  const group = channel.groupPolicy ? ` groups=${channel.groupPolicy}` : "";
@@ -72906,8 +73825,9 @@ function renderRuntimeDiagnosticsSummary(summary) {
72906
73825
  `).trim();
72907
73826
  }
72908
73827
  function renderStartSummary(summary) {
72909
- const configPath = collapseHomePath(getDefaultConfigPath());
72910
73828
  const lines = [
73829
+ ...renderOwnerSummaryLines(summary),
73830
+ "",
72911
73831
  ...renderAgentSummaryLines(summary),
72912
73832
  ...renderChannelSummaryLines(summary)
72913
73833
  ];
@@ -72945,7 +73865,10 @@ function renderStartSummary(summary) {
72945
73865
  lines.push("");
72946
73866
  lines.push(" Next steps after bootstrap:");
72947
73867
  lines.push(" - chat with the bot or open the workspace, then follow BOOTSTRAP.md");
72948
- lines.push(` - configure Slack channels or Telegram groups/topics in ${configPath}`);
73868
+ appendChannelNextStepLines(lines, summary, " ");
73869
+ lines.push(` - ${renderPrivilegedChatHint(summary, "verify DM access and adjust in-chat settings")}`);
73870
+ lines.push("");
73871
+ appendAuthOnboardingLines(lines, summary, " ");
72949
73872
  lines.push(" - run `clisbot status` to recheck runtime and bootstrap state");
72950
73873
  lines.push(" - run `clisbot logs` if the bot does not answer as expected");
72951
73874
  lines.push(...renderPairingSetupHelpLines(" ", {
@@ -72963,9 +73886,11 @@ function renderStartSummary(summary) {
72963
73886
  }
72964
73887
  lines.push("");
72965
73888
  lines.push("Next steps:");
72966
- lines.push(` - configure Slack channels or Telegram groups/topics in ${configPath}`);
72967
- lines.push(" - verify routing and defaultAgentId values match the agent you want to expose");
72968
- lines.push(" - send a test message from Slack or Telegram");
73889
+ appendChannelNextStepLines(lines, summary, " ");
73890
+ lines.push(" - verify routes and defaultAgentId values match the agent you want to expose");
73891
+ lines.push(` - ${renderPrivilegedChatHint(summary, "adjust in-chat surface settings")}`);
73892
+ lines.push("");
73893
+ appendAuthOnboardingLines(lines, summary);
72969
73894
  lines.push(" - run `clisbot status` to inspect agents, channels, and tmux session state");
72970
73895
  lines.push(" - run `clisbot logs` if anything looks wrong");
72971
73896
  lines.push(...renderPairingSetupHelpLines("", {
@@ -72992,20 +73917,24 @@ function appendChannelSetupNotes(lines, summary, prefix = "") {
72992
73917
  if (channel.channel === "telegram") {
72993
73918
  lines.push(`${prefix} - telegram: no explicit group or topic routes are configured yet`);
72994
73919
  lines.push(`${prefix} dms: ${channel.directMessagesEnabled ? `enabled (${channel.directMessagesPolicy})` : "disabled"}`);
72995
- lines.push(`${prefix} route: add channels.telegram.groups.<chatId> in ${collapseHomePath(getDefaultConfigPath())}`);
72996
- lines.push(`${prefix} example: channels.telegram.groups."-1001234567890".agentId = "default"`);
72997
- lines.push(`${prefix} forum topics: use channels.telegram.groups.<chatId>.topics.<topicId>`);
73920
+ lines.push(`${prefix} add group: \`clisbot channels add telegram-group <chatId> --agent <id>\``);
73921
+ lines.push(`${prefix} add topic: \`clisbot channels add telegram-group <chatId> --topic <topicId> --agent <id>\``);
73922
+ lines.push(`${prefix} adjust later: ${renderPrivilegedChatHint(summary, "run in-chat commands here")}`);
72998
73923
  continue;
72999
73924
  }
73000
73925
  lines.push(`${prefix} - slack: no explicit channel or group routes are configured yet`);
73001
73926
  lines.push(`${prefix} dms: ${channel.directMessagesEnabled ? `enabled (${channel.directMessagesPolicy})` : "disabled"}`);
73002
73927
  lines.push(`${prefix} groups: ${channel.groupPolicy ?? "n/a"}`);
73003
- lines.push(`${prefix} route: configure channels.slack.channels.<channelId> or channels.slack.groups.<groupId>`);
73928
+ lines.push(`${prefix} add channel: \`clisbot channels add slack-channel <channelId> --agent <id>\``);
73929
+ lines.push(`${prefix} add group: \`clisbot channels add slack-group <groupId> --agent <id>\``);
73930
+ lines.push(`${prefix} adjust later: ${renderPrivilegedChatHint(summary, "run in-chat commands here")}`);
73004
73931
  }
73005
73932
  }
73006
73933
  function renderStatusSummary(summary) {
73007
73934
  const lines = [
73008
73935
  `stats agents=${summary.configuredAgents} bootstrapped=${summary.bootstrappedAgents} pendingBootstrap=${summary.bootstrapPendingAgents} tmuxSessions=${summary.runningTmuxSessions}`,
73936
+ ...renderOwnerSummaryLines(summary),
73937
+ "",
73009
73938
  ...renderAgentSummaryLines(summary),
73010
73939
  ...renderChannelSummaryLines(summary),
73011
73940
  ...renderChannelDiagnosticLines(summary),
@@ -73027,6 +73956,15 @@ function printCommandOutcomeBanner(outcome) {
73027
73956
  function printCommandOutcomeFooter(outcome) {
73028
73957
  printCommandOutcomeBanner(outcome);
73029
73958
  }
73959
+ function assertSupportedPlatform(command) {
73960
+ if (process.platform !== "win32") {
73961
+ return;
73962
+ }
73963
+ if (command.name === "help" || command.name === "version") {
73964
+ return;
73965
+ }
73966
+ throw new Error("Native Windows is not supported yet. Run clisbot from WSL2 or use Linux/macOS instead.");
73967
+ }
73030
73968
  function getPrimaryWorkspacePath(summary) {
73031
73969
  const preferredAgentId = summary.channelSummaries.find((channel) => channel.enabled)?.defaultAgentId ?? "default";
73032
73970
  return summary.agentSummaries.find((agent) => agent.id === preferredAgentId)?.workspacePath ?? summary.agentSummaries[0]?.workspacePath;
@@ -73176,25 +74114,57 @@ async function serveForeground() {
73176
74114
  const runtimeCredentialsPath = getDefaultRuntimeCredentialsPath();
73177
74115
  const runtimeSupervisor = new RuntimeSupervisor(configPath);
73178
74116
  let shuttingDown = false;
73179
- const shutdown = async (exitCode = 0) => {
74117
+ let fatalHandling = false;
74118
+ const shutdown = async (exitCode = 0, options = {}) => {
73180
74119
  if (shuttingDown) {
73181
74120
  return;
73182
74121
  }
73183
74122
  shuttingDown = true;
73184
74123
  try {
73185
- await runtimeSupervisor.stop();
74124
+ await runtimeSupervisor.stop({
74125
+ markChannelsStopped: options.markChannelsStopped
74126
+ });
73186
74127
  } finally {
73187
74128
  removeRuntimeCredentials(runtimeCredentialsPath);
73188
74129
  removeRuntimePid(pidPath);
73189
74130
  process.exit(exitCode);
73190
74131
  }
73191
74132
  };
74133
+ const handleFatal = async (source, error) => {
74134
+ if (fatalHandling || shuttingDown) {
74135
+ return;
74136
+ }
74137
+ fatalHandling = true;
74138
+ const detail = error instanceof Error ? error.message : String(error);
74139
+ const fatalError = new Error(`fatal ${source}: ${detail}`);
74140
+ console.error(`clisbot fatal ${source}`, error);
74141
+ const forceExitTimer = setTimeout(() => {
74142
+ process.exit(1);
74143
+ }, 5000);
74144
+ forceExitTimer.unref?.();
74145
+ try {
74146
+ await runtimeSupervisor.markFatalFailure(fatalError);
74147
+ } catch (markError) {
74148
+ console.error("failed to record fatal runtime health", markError);
74149
+ }
74150
+ try {
74151
+ await shutdown(1, { markChannelsStopped: false });
74152
+ } finally {
74153
+ clearTimeout(forceExitTimer);
74154
+ }
74155
+ };
73192
74156
  process.once("SIGINT", () => {
73193
74157
  shutdown(0);
73194
74158
  });
73195
74159
  process.once("SIGTERM", () => {
73196
74160
  shutdown(0);
73197
74161
  });
74162
+ process.once("uncaughtException", (error) => {
74163
+ handleFatal("uncaughtException", error);
74164
+ });
74165
+ process.once("unhandledRejection", (error) => {
74166
+ handleFatal("unhandledRejection", error);
74167
+ });
73198
74168
  try {
73199
74169
  await runtimeSupervisor.start();
73200
74170
  await writeRuntimePid(pidPath, process.pid);
@@ -73271,7 +74241,7 @@ async function start(args = []) {
73271
74241
  console.log(`clisbot is already running with pid: ${result.pid}`);
73272
74242
  const workspacePath = getPrimaryWorkspacePath(summary);
73273
74243
  if (workspacePath) {
73274
- console.log(`workspace: ${workspacePath} (agent workspace: default work dir; contains state, sessions, personality, and guidance files)`);
74244
+ console.log(`workspace: ${workspacePath}`);
73275
74245
  }
73276
74246
  console.log(`config: ${result.configPath}`);
73277
74247
  console.log(`log: ${result.logPath}`);
@@ -73303,7 +74273,7 @@ async function start(args = []) {
73303
74273
  console.log(`clisbot started with pid: ${result.pid}`);
73304
74274
  const workspacePath = getPrimaryWorkspacePath(summary);
73305
74275
  if (workspacePath) {
73306
- console.log(`workspace: ${workspacePath} (agent workspace: default work dir; contains state, sessions, personality, and guidance files)`);
74276
+ console.log(`workspace: ${workspacePath}`);
73307
74277
  }
73308
74278
  console.log(`config: ${result.configPath}`);
73309
74279
  console.log(`log: ${result.logPath}`);
@@ -73392,7 +74362,7 @@ async function status() {
73392
74362
  });
73393
74363
  const workspacePath = getPrimaryWorkspacePath(summary);
73394
74364
  if (workspacePath) {
73395
- console.log(`workspace: ${workspacePath} (agent workspace: default work dir; contains state, sessions, personality, and guidance files)`);
74365
+ console.log(`workspace: ${workspacePath}`);
73396
74366
  }
73397
74367
  console.log(`config: ${runtimeStatus.configPath}`);
73398
74368
  console.log(`pid file: ${runtimeStatus.pidPath}`);
@@ -73435,6 +74405,7 @@ async function logs(lines) {
73435
74405
  } catch {}
73436
74406
  }
73437
74407
  async function main(command = parseCliArgs(process.argv)) {
74408
+ assertSupportedPlatform(command);
73438
74409
  if (command.name === "help") {
73439
74410
  console.log(renderCliHelp());
73440
74411
  return;
@@ -73491,6 +74462,10 @@ async function main(command = parseCliArgs(process.argv)) {
73491
74462
  await runAgentsCli(command.args);
73492
74463
  return;
73493
74464
  }
74465
+ if (command.name === "auth") {
74466
+ await runAuthCli(command.args);
74467
+ return;
74468
+ }
73494
74469
  if (command.name === "pairing") {
73495
74470
  await runPairingCli(command.args);
73496
74471
  return;