switchroom 0.15.12 → 0.15.13

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.
@@ -11150,7 +11150,8 @@ var TelegramChannelSchema = exports_external.object({
11150
11150
  linear_agent: exports_external.object({
11151
11151
  enabled: exports_external.boolean(),
11152
11152
  token: exports_external.string().describe("vault:<key> reference to the Linear OAuth app token (actor=app). " + "Resolved at runtime via the vault broker (canonically " + "vault:linear/<agent>/token). Never an inline literal."),
11153
- workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational — used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace.")
11153
+ workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational — used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace."),
11154
+ default_team_id: exports_external.string().optional().describe("Optional Linear team id new captured issues file into when the " + "agent doesn't pass an explicit team_id. Unnecessary for a " + "single-team workspace (auto-resolved); set it only when the " + "workspace has multiple teams. Manage via " + "`switchroom linear-agent set-team <agent> <team>`.")
11154
11155
  }).optional().describe("Linear first-class agent integration (#2298). When enabled, the " + "agent appears in a Linear workspace as an app actor (own name/" + "avatar, @-mentionable, delegate-assignable). Linear AgentSessionEvent " + "webhooks (mention / delegation) wake the agent instantly via the " + "same gateway inject path as webhook_dispatch, tagged " + 'meta.source="linear" with the agent_session_id, and the agent ' + "responds with structured AgentActivity (thought/message/complete/" + "error) via the linear_agent_activity MCP tool. Builds the " + "session-lifecycle layer on top of the plain webhook_sources:[linear] " + "+ webhook_dispatch support (#2272). The OAuth app token is stored in " + "the vault and referenced here as vault:linear/<agent>/token; run " + "`switchroom linear-agent setup <agent>` to provision it. Off by " + "default — opt in per agent. Cascades from " + "defaults.channels.telegram.linear_agent."),
11155
11156
  chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
11156
11157
  default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted — set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
@@ -12024,6 +12025,16 @@ function mergeAgentConfig(defaultsIn, agentIn) {
12024
12025
  }
12025
12026
  merged.reaction_dispatch = combined;
12026
12027
  }
12028
+ const linearEnabled = merged.channels?.telegram?.linear_agent?.enabled === true;
12029
+ if (linearEnabled) {
12030
+ const rd = merged.reaction_dispatch;
12031
+ if (!rd || rd.emojis === undefined) {
12032
+ merged.reaction_dispatch = {
12033
+ enabled: rd?.enabled ?? true,
12034
+ emojis: ["\uD83D\uDC68‍\uD83D\uDCBB", "\uD83D\uDCCC"]
12035
+ };
12036
+ }
12037
+ }
12027
12038
  if (defaults.resources || merged.resources) {
12028
12039
  const d = defaults.resources ?? {};
12029
12040
  const a = merged.resources ?? {};
@@ -11150,7 +11150,8 @@ var TelegramChannelSchema = exports_external.object({
11150
11150
  linear_agent: exports_external.object({
11151
11151
  enabled: exports_external.boolean(),
11152
11152
  token: exports_external.string().describe("vault:<key> reference to the Linear OAuth app token (actor=app). " + "Resolved at runtime via the vault broker (canonically " + "vault:linear/<agent>/token). Never an inline literal."),
11153
- workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational — used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace.")
11153
+ workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational — used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace."),
11154
+ default_team_id: exports_external.string().optional().describe("Optional Linear team id new captured issues file into when the " + "agent doesn't pass an explicit team_id. Unnecessary for a " + "single-team workspace (auto-resolved); set it only when the " + "workspace has multiple teams. Manage via " + "`switchroom linear-agent set-team <agent> <team>`.")
11154
11155
  }).optional().describe("Linear first-class agent integration (#2298). When enabled, the " + "agent appears in a Linear workspace as an app actor (own name/" + "avatar, @-mentionable, delegate-assignable). Linear AgentSessionEvent " + "webhooks (mention / delegation) wake the agent instantly via the " + "same gateway inject path as webhook_dispatch, tagged " + 'meta.source="linear" with the agent_session_id, and the agent ' + "responds with structured AgentActivity (thought/message/complete/" + "error) via the linear_agent_activity MCP tool. Builds the " + "session-lifecycle layer on top of the plain webhook_sources:[linear] " + "+ webhook_dispatch support (#2272). The OAuth app token is stored in " + "the vault and referenced here as vault:linear/<agent>/token; run " + "`switchroom linear-agent setup <agent>` to provision it. Off by " + "default — opt in per agent. Cascades from " + "defaults.channels.telegram.linear_agent."),
11155
11156
  chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
11156
11157
  default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted — set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
@@ -12024,6 +12025,16 @@ function mergeAgentConfig(defaultsIn, agentIn) {
12024
12025
  }
12025
12026
  merged.reaction_dispatch = combined;
12026
12027
  }
12028
+ const linearEnabled = merged.channels?.telegram?.linear_agent?.enabled === true;
12029
+ if (linearEnabled) {
12030
+ const rd = merged.reaction_dispatch;
12031
+ if (!rd || rd.emojis === undefined) {
12032
+ merged.reaction_dispatch = {
12033
+ enabled: rd?.enabled ?? true,
12034
+ emojis: ["\uD83D\uDC68‍\uD83D\uDCBB", "\uD83D\uDCCC"]
12035
+ };
12036
+ }
12037
+ }
12027
12038
  if (defaults.resources || merged.resources) {
12028
12039
  const d = defaults.resources ?? {};
12029
12040
  const a = merged.resources ?? {};
@@ -11898,7 +11898,8 @@ var TelegramChannelSchema = exports_external.object({
11898
11898
  linear_agent: exports_external.object({
11899
11899
  enabled: exports_external.boolean(),
11900
11900
  token: exports_external.string().describe("vault:<key> reference to the Linear OAuth app token (actor=app). " + "Resolved at runtime via the vault broker (canonically " + "vault:linear/<agent>/token). Never an inline literal."),
11901
- workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational \u2014 used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace.")
11901
+ workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational \u2014 used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace."),
11902
+ default_team_id: exports_external.string().optional().describe("Optional Linear team id new captured issues file into when the " + "agent doesn't pass an explicit team_id. Unnecessary for a " + "single-team workspace (auto-resolved); set it only when the " + "workspace has multiple teams. Manage via " + "`switchroom linear-agent set-team <agent> <team>`.")
11902
11903
  }).optional().describe("Linear first-class agent integration (#2298). When enabled, the " + "agent appears in a Linear workspace as an app actor (own name/" + "avatar, @-mentionable, delegate-assignable). Linear AgentSessionEvent " + "webhooks (mention / delegation) wake the agent instantly via the " + "same gateway inject path as webhook_dispatch, tagged " + 'meta.source="linear" with the agent_session_id, and the agent ' + "responds with structured AgentActivity (thought/message/complete/" + "error) via the linear_agent_activity MCP tool. Builds the " + "session-lifecycle layer on top of the plain webhook_sources:[linear] " + "+ webhook_dispatch support (#2272). The OAuth app token is stored in " + "the vault and referenced here as vault:linear/<agent>/token; run " + "`switchroom linear-agent setup <agent>` to provision it. Off by " + "default \u2014 opt in per agent. Cascades from " + "defaults.channels.telegram.linear_agent."),
11903
11904
  chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID \u2014 overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
11904
11905
  default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted \u2014 set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field \u2014 the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
@@ -12774,6 +12775,16 @@ function mergeAgentConfig(defaultsIn, agentIn) {
12774
12775
  }
12775
12776
  merged.reaction_dispatch = combined;
12776
12777
  }
12778
+ const linearEnabled = merged.channels?.telegram?.linear_agent?.enabled === true;
12779
+ if (linearEnabled) {
12780
+ const rd = merged.reaction_dispatch;
12781
+ if (!rd || rd.emojis === undefined) {
12782
+ merged.reaction_dispatch = {
12783
+ enabled: rd?.enabled ?? true,
12784
+ emojis: ["\uD83D\uDC68\u200d\uD83D\uDCBB", "\uD83D\uDCCC"]
12785
+ };
12786
+ }
12787
+ }
12777
12788
  if (defaults.resources || merged.resources) {
12778
12789
  const d = defaults.resources ?? {};
12779
12790
  const a = merged.resources ?? {};
@@ -13714,7 +13714,8 @@ var init_schema = __esm(() => {
13714
13714
  linear_agent: exports_external.object({
13715
13715
  enabled: exports_external.boolean(),
13716
13716
  token: exports_external.string().describe("vault:<key> reference to the Linear OAuth app token (actor=app). " + "Resolved at runtime via the vault broker (canonically " + "vault:linear/<agent>/token). Never an inline literal."),
13717
- workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational \u2014 used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace.")
13717
+ workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational \u2014 used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace."),
13718
+ default_team_id: exports_external.string().optional().describe("Optional Linear team id new captured issues file into when the " + "agent doesn't pass an explicit team_id. Unnecessary for a " + "single-team workspace (auto-resolved); set it only when the " + "workspace has multiple teams. Manage via " + "`switchroom linear-agent set-team <agent> <team>`.")
13718
13719
  }).optional().describe("Linear first-class agent integration (#2298). When enabled, the " + "agent appears in a Linear workspace as an app actor (own name/" + "avatar, @-mentionable, delegate-assignable). Linear AgentSessionEvent " + "webhooks (mention / delegation) wake the agent instantly via the " + "same gateway inject path as webhook_dispatch, tagged " + 'meta.source="linear" with the agent_session_id, and the agent ' + "responds with structured AgentActivity (thought/message/complete/" + "error) via the linear_agent_activity MCP tool. Builds the " + "session-lifecycle layer on top of the plain webhook_sources:[linear] " + "+ webhook_dispatch support (#2272). The OAuth app token is stored in " + "the vault and referenced here as vault:linear/<agent>/token; run " + "`switchroom linear-agent setup <agent>` to provision it. Off by " + "default \u2014 opt in per agent. Cascades from " + "defaults.channels.telegram.linear_agent."),
13719
13720
  chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID \u2014 overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
13720
13721
  default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted \u2014 set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field \u2014 the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
@@ -14640,6 +14641,16 @@ function mergeAgentConfig(defaultsIn, agentIn) {
14640
14641
  }
14641
14642
  merged.reaction_dispatch = combined;
14642
14643
  }
14644
+ const linearEnabled = merged.channels?.telegram?.linear_agent?.enabled === true;
14645
+ if (linearEnabled) {
14646
+ const rd = merged.reaction_dispatch;
14647
+ if (!rd || rd.emojis === undefined) {
14648
+ merged.reaction_dispatch = {
14649
+ enabled: rd?.enabled ?? true,
14650
+ emojis: ["\uD83D\uDC68\u200d\uD83D\uDCBB", "\uD83D\uDCCC"]
14651
+ };
14652
+ }
14653
+ }
14643
14654
  if (defaults.resources || merged.resources) {
14644
14655
  const d = defaults.resources ?? {};
14645
14656
  const a = merged.resources ?? {};
@@ -50252,8 +50263,8 @@ var {
50252
50263
  } = import__.default;
50253
50264
 
50254
50265
  // src/build-info.ts
50255
- var VERSION = "0.15.12";
50256
- var COMMIT_SHA = "18b7b6e6";
50266
+ var VERSION = "0.15.13";
50267
+ var COMMIT_SHA = "36ba2682";
50257
50268
 
50258
50269
  // src/cli/agent.ts
50259
50270
  init_source();
@@ -51719,6 +51730,11 @@ function webkiteDenyForAgent(agentConfig) {
51719
51730
  }
51720
51731
  var SWITCHROOM_DEFAULT_MAIN_MODEL = "claude-sonnet-4-6";
51721
51732
  var SWITCHROOM_DEFAULT_THINKING_EFFORT = "low";
51733
+ function resolveMainModel(model) {
51734
+ if (model === undefined || model === "default")
51735
+ return SWITCHROOM_DEFAULT_MAIN_MODEL;
51736
+ return model;
51737
+ }
51722
51738
  function dedupe2(items) {
51723
51739
  const seen = new Set;
51724
51740
  const out = [];
@@ -51988,6 +52004,10 @@ function channelsToEnv(agent) {
51988
52004
  if (tg.clear_status_on_completion !== undefined) {
51989
52005
  out.SWITCHROOM_TG_CLEAR_STATUS_ON_COMPLETION = tg.clear_status_on_completion ? "1" : "0";
51990
52006
  }
52007
+ const linearDefaultTeam = tg?.linear_agent?.default_team_id;
52008
+ if (linearDefaultTeam) {
52009
+ out.SWITCHROOM_LINEAR_DEFAULT_TEAM_ID = linearDefaultTeam;
52010
+ }
51991
52011
  return out;
51992
52012
  }
51993
52013
  function buildRepoEnvVars(_agentName, agentDir, agent) {
@@ -52327,6 +52347,7 @@ function buildWorkspaceContext(args) {
52327
52347
  useSwitchroomPlugin: usesSwitchroomTelegramPlugin(agentConfig),
52328
52348
  useHotReloadStable: agentConfig.channels?.telegram?.hotReloadStable === true,
52329
52349
  telegramEnabledFlag: agentConfig.channels?.telegram?.enabled === false ? "false" : "true",
52350
+ linearAgentEnabled: agentConfig.channels?.telegram?.linear_agent?.enabled === true,
52330
52351
  securityPluginDir: DOCKER_SECURITY_PLUGIN_PATH,
52331
52352
  hindsightEnabled: hindsightAutoRecallEnabled,
52332
52353
  hindsightBankIdQ: shellSingleQuote(hindsightBankId),
@@ -52338,7 +52359,7 @@ function buildWorkspaceContext(args) {
52338
52359
  hindsightTopicFilterMode,
52339
52360
  switchroomConfigPathQ: switchroomConfigPath ? shellSingleQuote(resolve11(switchroomConfigPath)) : undefined,
52340
52361
  hostHomeQ: process.env.HOME ? shellSingleQuote(process.env.HOME) : undefined,
52341
- modelQ: shellSingleQuote(agentConfig.model ?? SWITCHROOM_DEFAULT_MAIN_MODEL),
52362
+ modelQ: shellSingleQuote(resolveMainModel(agentConfig.model)),
52342
52363
  ...buildCronSessionContext(agentConfig),
52343
52364
  thinkingEffort: agentConfig.thinking_effort ?? SWITCHROOM_DEFAULT_THINKING_EFFORT,
52344
52365
  permissionMode: agentConfig.permission_mode,
@@ -52638,7 +52659,7 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
52638
52659
  useSwitchroomPlugin: usesSwitchroomTelegramPlugin(agentConfig),
52639
52660
  configPath: switchroomConfigPath
52640
52661
  });
52641
- settings.model = agentConfig.model ?? SWITCHROOM_DEFAULT_MAIN_MODEL;
52662
+ settings.model = resolveMainModel(agentConfig.model);
52642
52663
  const mergedSettings = agentConfig.settings_raw ? deepMergeJson(settings, agentConfig.settings_raw) : settings;
52643
52664
  if (agentConfig.settings_raw && Object.keys(agentConfig.settings_raw).length > 0) {
52644
52665
  mergedSettings._switchroomManagedRawKeys = Object.keys(agentConfig.settings_raw);
@@ -53429,7 +53450,7 @@ function reconcileAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchr
53429
53450
  hindsightTopicAliasesJsonQ: hindsightTopicAliasesJson ? shellSingleQuote(hindsightTopicAliasesJson) : undefined,
53430
53451
  hindsightTopicFilterMode,
53431
53452
  hostHomeQ: process.env.HOME ? shellSingleQuote(process.env.HOME) : undefined,
53432
- modelQ: shellSingleQuote(agentConfig.model ?? SWITCHROOM_DEFAULT_MAIN_MODEL),
53453
+ modelQ: shellSingleQuote(resolveMainModel(agentConfig.model)),
53433
53454
  ...buildCronSessionContext(agentConfig),
53434
53455
  thinkingEffort: agentConfig.thinking_effort ?? SWITCHROOM_DEFAULT_THINKING_EFFORT,
53435
53456
  permissionMode: agentConfig.permission_mode,
@@ -53514,7 +53535,8 @@ function reconcileAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchr
53514
53535
  schedule: agentConfig.schedule,
53515
53536
  useSwitchroomPlugin: usesSwitchroomTelegramPlugin(agentConfig),
53516
53537
  admin: agentConfig.admin === true || agentConfig.root === true,
53517
- root: agentConfig.root === true
53538
+ root: agentConfig.root === true,
53539
+ linearAgentEnabled: agentConfig.channels?.telegram?.linear_agent?.enabled === true
53518
53540
  };
53519
53541
  let rendered = renderTemplate(claudeMdSrc, claudeContext);
53520
53542
  const vaultProtocol = renderVaultProtocolFragment(claudeContext);
@@ -53680,7 +53702,7 @@ ${body}
53680
53702
  }
53681
53703
  }
53682
53704
  }
53683
- settings.model = agentConfig.model ?? SWITCHROOM_DEFAULT_MAIN_MODEL;
53705
+ settings.model = resolveMainModel(agentConfig.model);
53684
53706
  const mergedSettings = agentConfig.settings_raw ? deepMergeJson(settings, agentConfig.settings_raw) : settings;
53685
53707
  if (agentConfig.settings_raw && Object.keys(agentConfig.settings_raw).length > 0) {
53686
53708
  mergedSettings[META_KEY] = Object.keys(agentConfig.settings_raw);
@@ -67041,6 +67063,22 @@ function setLinearAgent(yamlText, agentName, opts) {
67041
67063
  if (opts.workspaceId)
67042
67064
  block.workspace_id = opts.workspaceId;
67043
67065
  doc.setIn(["agents", agentName, "channels", "telegram", "linear_agent"], block);
67066
+ doc.setIn(["agents", agentName, "channels", "telegram", "webhook_via_gateway"], true);
67067
+ return String(doc);
67068
+ }
67069
+ function setLinearDefaultTeam(yamlText, agentName, teamId) {
67070
+ const doc = import_yaml11.parseDocument(yamlText);
67071
+ ensureAgent(doc, agentName);
67072
+ if (!doc.hasIn(["agents", agentName, "channels", "telegram", "linear_agent"])) {
67073
+ throw new Error(`agent '${agentName}' has no linear_agent block. Run 'switchroom linear-agent setup --agent ${agentName} --token <token>' first.`);
67074
+ }
67075
+ const path4 = ["agents", agentName, "channels", "telegram", "linear_agent", "default_team_id"];
67076
+ if (teamId === null) {
67077
+ if (doc.hasIn(path4))
67078
+ doc.deleteIn(path4);
67079
+ } else {
67080
+ doc.setIn(path4, teamId);
67081
+ }
67044
67082
  return String(doc);
67045
67083
  }
67046
67084
  function setTelegramFeature(yamlText, agentName, feature, value) {
@@ -67642,6 +67680,29 @@ function registerLinearAgentCommand(program3) {
67642
67680
  }
67643
67681
  printLinearInstructions(opts, vaultKey);
67644
67682
  }));
67683
+ linear.command("set-team").description("Set (or clear) the default Linear team captured issues file into for <agent>. Only needed when the workspace has multiple teams \u2014 a single-team workspace auto-resolves. Pass --clear to remove the default.").requiredOption("--agent <name>", "Agent name (must have a linear_agent block)").option("--team <id>", "Linear team id new captured issues default to.").option("--clear", "Remove the configured default team (revert to auto-resolve).").action(withConfigError(async (opts) => {
67684
+ if (!/^[a-z][a-z0-9_-]{0,63}$/.test(opts.agent)) {
67685
+ fail2(`--agent must be a lowercase agent slug (got '${opts.agent}').`);
67686
+ }
67687
+ if (!opts.clear && (!opts.team || opts.team.trim().length === 0)) {
67688
+ fail2("pass either --team <id> or --clear.");
67689
+ }
67690
+ const path4 = getConfigPath(program3);
67691
+ const before = readFileSync39(path4, "utf-8");
67692
+ let after;
67693
+ try {
67694
+ after = setLinearDefaultTeam(before, opts.agent, opts.clear ? null : opts.team.trim());
67695
+ } catch (err) {
67696
+ fail2(err.message);
67697
+ }
67698
+ writeFileSync22(path4, after, "utf-8");
67699
+ if (opts.clear) {
67700
+ console.log(source_default.green(`\u2713 Cleared default Linear team for '${opts.agent}' (auto-resolve).`));
67701
+ } else {
67702
+ console.log(source_default.green(`\u2713 Default Linear team for '${opts.agent}' set to ${opts.team.trim()}.`));
67703
+ }
67704
+ console.log(source_default.gray(` Run 'switchroom agent restart ${opts.agent}' to pick up the change.`));
67705
+ }));
67645
67706
  }
67646
67707
  function printLinearInstructions(opts, vaultKey) {
67647
67708
  const base = opts.webhookBase ?? "https://<your-switchroom-web-host>";
@@ -13885,7 +13885,8 @@ var TelegramChannelSchema = exports_external.object({
13885
13885
  linear_agent: exports_external.object({
13886
13886
  enabled: exports_external.boolean(),
13887
13887
  token: exports_external.string().describe("vault:<key> reference to the Linear OAuth app token (actor=app). " + "Resolved at runtime via the vault broker (canonically " + "vault:linear/<agent>/token). Never an inline literal."),
13888
- workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational — used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace.")
13888
+ workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational — used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace."),
13889
+ default_team_id: exports_external.string().optional().describe("Optional Linear team id new captured issues file into when the " + "agent doesn't pass an explicit team_id. Unnecessary for a " + "single-team workspace (auto-resolved); set it only when the " + "workspace has multiple teams. Manage via " + "`switchroom linear-agent set-team <agent> <team>`.")
13889
13890
  }).optional().describe("Linear first-class agent integration (#2298). When enabled, the " + "agent appears in a Linear workspace as an app actor (own name/" + "avatar, @-mentionable, delegate-assignable). Linear AgentSessionEvent " + "webhooks (mention / delegation) wake the agent instantly via the " + "same gateway inject path as webhook_dispatch, tagged " + 'meta.source="linear" with the agent_session_id, and the agent ' + "responds with structured AgentActivity (thought/message/complete/" + "error) via the linear_agent_activity MCP tool. Builds the " + "session-lifecycle layer on top of the plain webhook_sources:[linear] " + "+ webhook_dispatch support (#2272). The OAuth app token is stored in " + "the vault and referenced here as vault:linear/<agent>/token; run " + "`switchroom linear-agent setup <agent>` to provision it. Off by " + "default — opt in per agent. Cascades from " + "defaults.channels.telegram.linear_agent."),
13890
13891
  chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
13891
13892
  default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted — set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
@@ -14769,6 +14770,16 @@ function mergeAgentConfig(defaultsIn, agentIn) {
14769
14770
  }
14770
14771
  merged.reaction_dispatch = combined;
14771
14772
  }
14773
+ const linearEnabled = merged.channels?.telegram?.linear_agent?.enabled === true;
14774
+ if (linearEnabled) {
14775
+ const rd = merged.reaction_dispatch;
14776
+ if (!rd || rd.emojis === undefined) {
14777
+ merged.reaction_dispatch = {
14778
+ enabled: rd?.enabled ?? true,
14779
+ emojis: ["\uD83D\uDC68‍\uD83D\uDCBB", "\uD83D\uDCCC"]
14780
+ };
14781
+ }
14782
+ }
14772
14783
  if (defaults.resources || merged.resources) {
14773
14784
  const d = defaults.resources ?? {};
14774
14785
  const a = merged.resources ?? {};
@@ -4305,6 +4305,16 @@ function mergeAgentConfig(defaultsIn, agentIn) {
4305
4305
  }
4306
4306
  merged.reaction_dispatch = combined;
4307
4307
  }
4308
+ const linearEnabled = merged.channels?.telegram?.linear_agent?.enabled === true;
4309
+ if (linearEnabled) {
4310
+ const rd = merged.reaction_dispatch;
4311
+ if (!rd || rd.emojis === undefined) {
4312
+ merged.reaction_dispatch = {
4313
+ enabled: rd?.enabled ?? true,
4314
+ emojis: ["\uD83D\uDC68‍\uD83D\uDCBB", "\uD83D\uDCCC"]
4315
+ };
4316
+ }
4317
+ }
4308
4318
  if (defaults.resources || merged.resources) {
4309
4319
  const d = defaults.resources ?? {};
4310
4320
  const a = merged.resources ?? {};
@@ -11483,7 +11493,8 @@ var init_schema = __esm(() => {
11483
11493
  linear_agent: exports_external.object({
11484
11494
  enabled: exports_external.boolean(),
11485
11495
  token: exports_external.string().describe("vault:<key> reference to the Linear OAuth app token (actor=app). " + "Resolved at runtime via the vault broker (canonically " + "vault:linear/<agent>/token). Never an inline literal."),
11486
- workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational — used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace.")
11496
+ workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational — used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace."),
11497
+ default_team_id: exports_external.string().optional().describe("Optional Linear team id new captured issues file into when the " + "agent doesn't pass an explicit team_id. Unnecessary for a " + "single-team workspace (auto-resolved); set it only when the " + "workspace has multiple teams. Manage via " + "`switchroom linear-agent set-team <agent> <team>`.")
11487
11498
  }).optional().describe("Linear first-class agent integration (#2298). When enabled, the " + "agent appears in a Linear workspace as an app actor (own name/" + "avatar, @-mentionable, delegate-assignable). Linear AgentSessionEvent " + "webhooks (mention / delegation) wake the agent instantly via the " + "same gateway inject path as webhook_dispatch, tagged " + 'meta.source="linear" with the agent_session_id, and the agent ' + "responds with structured AgentActivity (thought/message/complete/" + "error) via the linear_agent_activity MCP tool. Builds the " + "session-lifecycle layer on top of the plain webhook_sources:[linear] " + "+ webhook_dispatch support (#2272). The OAuth app token is stored in " + "the vault and referenced here as vault:linear/<agent>/token; run " + "`switchroom linear-agent setup <agent>` to provision it. Off by " + "default — opt in per agent. Cascades from " + "defaults.channels.telegram.linear_agent."),
11488
11499
  chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
11489
11500
  default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted — set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
@@ -339,6 +339,16 @@ function mergeAgentConfig(defaultsIn, agentIn) {
339
339
  }
340
340
  merged.reaction_dispatch = combined;
341
341
  }
342
+ const linearEnabled = merged.channels?.telegram?.linear_agent?.enabled === true;
343
+ if (linearEnabled) {
344
+ const rd = merged.reaction_dispatch;
345
+ if (!rd || rd.emojis === undefined) {
346
+ merged.reaction_dispatch = {
347
+ enabled: rd?.enabled ?? true,
348
+ emojis: ["\uD83D\uDC68‍\uD83D\uDCBB", "\uD83D\uDCCC"]
349
+ };
350
+ }
351
+ }
342
352
  if (defaults.resources || merged.resources) {
343
353
  const d = defaults.resources ?? {};
344
354
  const a = merged.resources ?? {};
@@ -11483,7 +11493,8 @@ var init_schema = __esm(() => {
11483
11493
  linear_agent: exports_external.object({
11484
11494
  enabled: exports_external.boolean(),
11485
11495
  token: exports_external.string().describe("vault:<key> reference to the Linear OAuth app token (actor=app). " + "Resolved at runtime via the vault broker (canonically " + "vault:linear/<agent>/token). Never an inline literal."),
11486
- workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational — used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace.")
11496
+ workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational — used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace."),
11497
+ default_team_id: exports_external.string().optional().describe("Optional Linear team id new captured issues file into when the " + "agent doesn't pass an explicit team_id. Unnecessary for a " + "single-team workspace (auto-resolved); set it only when the " + "workspace has multiple teams. Manage via " + "`switchroom linear-agent set-team <agent> <team>`.")
11487
11498
  }).optional().describe("Linear first-class agent integration (#2298). When enabled, the " + "agent appears in a Linear workspace as an app actor (own name/" + "avatar, @-mentionable, delegate-assignable). Linear AgentSessionEvent " + "webhooks (mention / delegation) wake the agent instantly via the " + "same gateway inject path as webhook_dispatch, tagged " + 'meta.source="linear" with the agent_session_id, and the agent ' + "responds with structured AgentActivity (thought/message/complete/" + "error) via the linear_agent_activity MCP tool. Builds the " + "session-lifecycle layer on top of the plain webhook_sources:[linear] " + "+ webhook_dispatch support (#2272). The OAuth app token is stored in " + "the vault and referenced here as vault:linear/<agent>/token; run " + "`switchroom linear-agent setup <agent>` to provision it. Off by " + "default — opt in per agent. Cascades from " + "defaults.channels.telegram.linear_agent."),
11488
11499
  chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
11489
11500
  default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted — set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.15.12",
3
+ "version": "0.15.13",
4
4
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -123,6 +123,43 @@ After a successful `schedule_add`, confirm to the user with:
123
123
  After a failed write (any `E_*` code from the rails above), surface the
124
124
  specific error verbatim, explain which rail tripped, and offer the
125
125
  closest legal alternative.
126
+ {{#if linearAgentEnabled}}
127
+
128
+ ## Capture to Linear (👨‍💻 / 📌 reaction → new issue)
129
+
130
+ You're connected to Linear as a first-class app actor. The operator has the
131
+ **capture reactions** wired: when they react to a Telegram message with **👨‍💻**
132
+ (laptop) or **📌** (pushpin), you're woken with a turn whose inbound is shaped
133
+ `<channel … event="reaction" emoji="👨‍💻" message_id="…" chat_id="…">[the
134
+ reacted message text]</channel>`. That reaction means: **"turn this into a
135
+ Linear issue."**
136
+
137
+ When you get such a reaction turn:
138
+
139
+ 1. **Read the reacted message + nearby context.** The `[reacted text]` is the
140
+ seed. Pull the surrounding thread (`get_recent_messages`) when the ask needs
141
+ it — the issue should stand on its own without the operator re-explaining.
142
+ 2. **Call `linear_create_issue`** with:
143
+ - `title` — a crisp, imperative one-liner ("Fix duplicate Brevo webhook
144
+ retries"), not a restatement of the chat.
145
+ - `body` — the ask, the context you gathered, and any acceptance criteria
146
+ you can reasonably infer. Markdown.
147
+ - `dedup_key` — set it to `"<chat_id>:<message_id>"` from the reaction event.
148
+ This makes a re-react of the same message idempotent (it returns the
149
+ existing issue instead of filing a duplicate).
150
+ - `team_id` — omit it. A single-team workspace auto-resolves; you'll only be
151
+ asked for one if the workspace has multiple teams (then tell the operator
152
+ to set a default via `switchroom linear-agent set-team`).
153
+ 3. **Reply in plain text, not an emoji.** On success the tool returns
154
+ `Filed: <title> → <url>` — reply that link so the operator can click
155
+ through ("📋 Filed: <url>"). On failure (vault denial, multi-team, API
156
+ error) the tool returns actionable text — relay it plainly ("Couldn't file
157
+ it — …") and, if it's a vault denial, follow the `vault_request_access`
158
+ instruction it gives you, then retry.
159
+
160
+ Don't acknowledge with only a reaction or a bare "done" — the operator wants
161
+ the link (or the honest reason it didn't file).
162
+ {{/if}}
126
163
 
127
164
  ### Don't lie about scheduling
128
165
 
@@ -479,6 +479,37 @@ const TOOL_SCHEMAS = [
479
479
  required: ['agent_session_id', 'type'],
480
480
  },
481
481
  },
482
+ {
483
+ name: 'linear_create_issue',
484
+ description:
485
+ 'File a new Linear issue from a Telegram message the operator flagged for capture (#2312). Use this when a turn was triggered by a capture reaction (the inbound carries event="reaction" with a capture emoji like 👨‍💻 or 📌) — turn the reacted message + any relevant thread context into a well-formed issue. Write a crisp imperative title and a body that captures the ask, the context, and any acceptance criteria you can infer; the agent files it AS its own Linear app actor. Team is auto-resolved when the workspace has a single team; if there are multiple it returns text asking for an explicit team_id. Pass dedup_key (e.g. the chat_id:message_id of the reacted message) so a re-react of the same message does not file a duplicate. Resolves the agent\'s Linear app token from the vault; on VAULT-BROKER-DENIED it returns text instructing you to vault_request_access for `linear/<agent>/token`. Returns "Filed: <title> → <url>" on success — reply that link to the operator in plain text.',
486
+ inputSchema: {
487
+ type: 'object',
488
+ properties: {
489
+ title: {
490
+ type: 'string',
491
+ description: 'Issue title — a crisp, imperative one-liner (e.g. "Fix duplicate webhook retries on Brevo sync").',
492
+ },
493
+ body: {
494
+ type: 'string',
495
+ description: 'Issue description (Markdown). Capture the ask, relevant context from the message/thread, and any acceptance criteria you can infer.',
496
+ },
497
+ team_id: {
498
+ type: 'string',
499
+ description: 'Optional Linear team id. Omit to auto-resolve when the workspace has a single team; required only when the workspace has multiple teams.',
500
+ },
501
+ dedup_key: {
502
+ type: 'string',
503
+ description: 'Optional idempotency key (use the reacted message identity, e.g. "<chat_id>:<message_id>"). A prior capture with the same key short-circuits to "Already filed: <url>".',
504
+ },
505
+ priority: {
506
+ type: 'number',
507
+ description: 'Optional Linear priority (0 none, 1 urgent, 2 high, 3 normal, 4 low).',
508
+ },
509
+ },
510
+ required: ['title', 'body'],
511
+ },
512
+ },
482
513
  ]
483
514
 
484
515
  mcp.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOL_SCHEMAS }))
@@ -24957,6 +24957,36 @@ var TOOL_SCHEMAS = [
24957
24957
  },
24958
24958
  required: ["agent_session_id", "type"]
24959
24959
  }
24960
+ },
24961
+ {
24962
+ name: "linear_create_issue",
24963
+ description: 'File a new Linear issue from a Telegram message the operator flagged for capture (#2312). Use this when a turn was triggered by a capture reaction (the inbound carries event="reaction" with a capture emoji like \uD83D\uDC68\u200D\uD83D\uDCBB or \uD83D\uDCCC) \u2014 turn the reacted message + any relevant thread context into a well-formed issue. Write a crisp imperative title and a body that captures the ask, the context, and any acceptance criteria you can infer; the agent files it AS its own Linear app actor. Team is auto-resolved when the workspace has a single team; if there are multiple it returns text asking for an explicit team_id. Pass dedup_key (e.g. the chat_id:message_id of the reacted message) so a re-react of the same message does not file a duplicate. Resolves the agent\'s Linear app token from the vault; on VAULT-BROKER-DENIED it returns text instructing you to vault_request_access for `linear/<agent>/token`. Returns "Filed: <title> \u2192 <url>" on success \u2014 reply that link to the operator in plain text.',
24964
+ inputSchema: {
24965
+ type: "object",
24966
+ properties: {
24967
+ title: {
24968
+ type: "string",
24969
+ description: 'Issue title \u2014 a crisp, imperative one-liner (e.g. "Fix duplicate webhook retries on Brevo sync").'
24970
+ },
24971
+ body: {
24972
+ type: "string",
24973
+ description: "Issue description (Markdown). Capture the ask, relevant context from the message/thread, and any acceptance criteria you can infer."
24974
+ },
24975
+ team_id: {
24976
+ type: "string",
24977
+ description: "Optional Linear team id. Omit to auto-resolve when the workspace has a single team; required only when the workspace has multiple teams."
24978
+ },
24979
+ dedup_key: {
24980
+ type: "string",
24981
+ description: 'Optional idempotency key (use the reacted message identity, e.g. "<chat_id>:<message_id>"). A prior capture with the same key short-circuits to "Already filed: <url>".'
24982
+ },
24983
+ priority: {
24984
+ type: "number",
24985
+ description: "Optional Linear priority (0 none, 1 urgent, 2 high, 3 normal, 4 low)."
24986
+ }
24987
+ },
24988
+ required: ["title", "body"]
24989
+ }
24960
24990
  }
24961
24991
  ];
24962
24992
  mcp.setRequestHandler(ListToolsRequestSchema2, async () => ({ tools: TOOL_SCHEMAS }));