@integrity-labs/agt-cli 0.28.739 → 0.28.740

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/bin/agt.js CHANGED
@@ -41,7 +41,7 @@ import {
41
41
  success,
42
42
  table,
43
43
  warn
44
- } from "../chunk-ZKWY6MNV.js";
44
+ } from "../chunk-WO3BES5W.js";
45
45
  import {
46
46
  getProjectDir,
47
47
  isSessionResumeDisabled,
@@ -5467,7 +5467,7 @@ import { execFileSync, execSync } from "child_process";
5467
5467
  import { existsSync as existsSync11, realpathSync as realpathSync2 } from "fs";
5468
5468
  import chalk18 from "chalk";
5469
5469
  import ora16 from "ora";
5470
- var cliVersion = true ? "0.28.739" : "dev";
5470
+ var cliVersion = true ? "0.28.740" : "dev";
5471
5471
  async function fetchLatestVersion() {
5472
5472
  const host2 = getHost();
5473
5473
  if (!host2) return null;
@@ -6654,7 +6654,7 @@ function handleError(err) {
6654
6654
  }
6655
6655
 
6656
6656
  // src/bin/agt.ts
6657
- var cliVersion2 = true ? "0.28.739" : "dev";
6657
+ var cliVersion2 = true ? "0.28.740" : "dev";
6658
6658
  var program = new Command();
6659
6659
  program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
6660
6660
  program.hook("preAction", async (thisCommand, actionCommand) => {
@@ -6427,7 +6427,7 @@ function exchangeFailureKind(err) {
6427
6427
  }
6428
6428
 
6429
6429
  // src/lib/api-client.ts
6430
- var agtCliVersion = true ? "0.28.739" : "dev";
6430
+ var agtCliVersion = true ? "0.28.740" : "dev";
6431
6431
  var lastConfigHash = null;
6432
6432
  function setConfigHash(hash) {
6433
6433
  lastConfigHash = hash && hash.length > 0 ? hash : null;
@@ -10473,4 +10473,4 @@ export {
10473
10473
  managerInstallSystemUnitCommand,
10474
10474
  managerUninstallSystemUnitCommand
10475
10475
  };
10476
- //# sourceMappingURL=chunk-ZKWY6MNV.js.map
10476
+ //# sourceMappingURL=chunk-WO3BES5W.js.map
@@ -57,7 +57,7 @@ import {
57
57
  safeWriteJsonAtomic,
58
58
  setConfigHash,
59
59
  tripClass
60
- } from "../chunk-ZKWY6MNV.js";
60
+ } from "../chunk-WO3BES5W.js";
61
61
  import {
62
62
  getProjectDir as getProjectDir2,
63
63
  getReadyTasks,
@@ -13154,7 +13154,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
13154
13154
  var lastVersionCheckAt = 0;
13155
13155
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
13156
13156
  var lastResponsivenessProbeAt = 0;
13157
- var agtCliVersion = true ? "0.28.739" : "dev";
13157
+ var agtCliVersion = true ? "0.28.740" : "dev";
13158
13158
  function resolveBrewPath(execFileSync3) {
13159
13159
  try {
13160
13160
  const out = execFileSync3("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -39055,9 +39055,55 @@ var TELEGRAM_EGRESS_TOOLS = /* @__PURE__ */ new Set([
39055
39055
  "telegram.react",
39056
39056
  "telegram.upload_file",
39057
39057
  "telegram.consume",
39058
+ // ENG-9677: leaving a chat is delivery-state mutation of the bluntest kind -
39059
+ // after it, nothing from that chat can ever arrive. An impersonating operator
39060
+ // must not be able to cut an agent off from a customer's group.
39061
+ "telegram.leave_chat",
39058
39062
  "channel_request_input"
39059
39063
  ]);
39060
39064
 
39065
+ // src/telegram-leave-chat-policy.ts
39066
+ function validateLeaveChatArgs(args) {
39067
+ const { chat_id, reason } = args ?? {};
39068
+ if (typeof chat_id !== "string" || !chat_id.trim()) {
39069
+ return {
39070
+ ok: false,
39071
+ message: "telegram.leave_chat requires chat_id (from the <channel> tag)."
39072
+ };
39073
+ }
39074
+ if (typeof reason !== "string" || !reason.trim()) {
39075
+ return {
39076
+ ok: false,
39077
+ message: "telegram.leave_chat requires a reason (a non-empty string). It is logged, not sent to the chat."
39078
+ };
39079
+ }
39080
+ return { ok: true, chatId: chat_id.trim(), reason: reason.trim() };
39081
+ }
39082
+ function isGroupShapedChatId(chatId) {
39083
+ return /^-\d+$/.test(chatId.trim());
39084
+ }
39085
+ function decideLeaveChat(input) {
39086
+ const chatId = input.chatId.trim();
39087
+ if (input.allowedChats.size > 0 && !input.allowedChats.has(chatId)) {
39088
+ return { allowed: true, because: "not-on-the-allowlist" };
39089
+ }
39090
+ if (input.allowedChats.has(chatId)) {
39091
+ return {
39092
+ allowed: false,
39093
+ because: "operator-only",
39094
+ detail: "this chat is on the agent's allowed_chats list, so leaving it is an operator decision"
39095
+ };
39096
+ }
39097
+ if (input.privateChatsOnly && isGroupShapedChatId(chatId)) {
39098
+ return { allowed: true, because: "group-chat-under-private-chats-only" };
39099
+ }
39100
+ return {
39101
+ allowed: false,
39102
+ because: "operator-only",
39103
+ detail: input.allowedChats.size > 0 ? "this chat is on the agent's allowed_chats list, so leaving it is an operator decision" : input.privateChatsOnly ? "this chat is not a group, so private-chats-only does not bar the agent from it" : "no chat policy is configured for this agent, so nothing marks this chat as one it should not be in"
39104
+ };
39105
+ }
39106
+
39061
39107
  // src/telegram-upload.ts
39062
39108
  var TELEGRAM_PHOTO_MAX_BYTES = 10 * 1024 * 1024;
39063
39109
  var TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
@@ -41428,6 +41474,14 @@ async function computeTelegramOwnChannelLiveStatus() {
41428
41474
  try {
41429
41475
  const resp = await telegramApiCall("getMe", {}, 5e3);
41430
41476
  if (resp.ok) {
41477
+ const canJoinGroups = resp.result?.can_join_groups;
41478
+ if (PRIVATE_CHATS_ONLY && canJoinGroups === true) {
41479
+ return {
41480
+ level: "ok",
41481
+ checked_at,
41482
+ reason: "getMe ok \u2014 but this bot can still be ADDED to groups. private_chats_only makes the agent ignore group traffic; it does not close the door. Only the bot owner can: @BotFather \u2192 /mybots \u2192 this bot \u2192 Bot Settings \u2192 Allow Groups? \u2192 Turn off."
41483
+ };
41484
+ }
41431
41485
  return { level: "ok", checked_at, reason: "getMe ok" };
41432
41486
  }
41433
41487
  return { level: "error", checked_at, reason: `getMe rejected: ${resp.description ?? "unknown"}` };
@@ -41937,6 +41991,14 @@ async function refreshBotIdentity() {
41937
41991
  if (typeof result.id === "number") {
41938
41992
  cachedBotId = result.id;
41939
41993
  }
41994
+ if (typeof result.can_join_groups === "boolean") {
41995
+ if (result.can_join_groups && PRIVATE_CHATS_ONLY) {
41996
+ process.stderr.write(
41997
+ `telegram-channel(${AGENT_CODE_NAME}): WARNING private_chats_only is set but this bot's can_join_groups is TRUE, so it can still be added to groups. Only the bot owner can change that: message @BotFather -> /mybots -> select this bot -> Bot Settings -> Group Privacy / Allow Groups? -> Turn off.
41998
+ `
41999
+ );
42000
+ }
42001
+ }
41940
42002
  if (cachedBotUsername !== null && cachedBotId !== null) return;
41941
42003
  }
41942
42004
  process.stderr.write(
@@ -43109,6 +43171,24 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
43109
43171
  required: ["chat_id"]
43110
43172
  }
43111
43173
  },
43174
+ {
43175
+ name: "telegram.leave_chat",
43176
+ description: "Remove this agent's bot from a Telegram group, supergroup or channel it should not be in. Pass the chat_id from the <channel> tag and a short reason. ONLY works for a chat the agent is already barred from engaging with - one outside its allowed_chats, or a group while private_chats_only is set. Leaving a chat the agent is permitted to be in is an operator decision and this tool will refuse it, deliberately: an agent that can be talked into leaving any chat is a denial-of-service primitive. Leaving does NOT stop anyone re-adding the bot - that is a BotFather setting the owner has to change.",
43177
+ inputSchema: {
43178
+ type: "object",
43179
+ properties: {
43180
+ chat_id: {
43181
+ type: "string",
43182
+ description: "Telegram chat ID to leave (from the chat_id attribute in the <channel> tag)"
43183
+ },
43184
+ reason: {
43185
+ type: "string",
43186
+ description: "Why the bot is leaving. Required, logged, and not sent to the chat. Leaving a chat with no recorded reason is indistinguishable from a mistake when someone reads it back."
43187
+ }
43188
+ },
43189
+ required: ["chat_id", "reason"]
43190
+ }
43191
+ },
43112
43192
  {
43113
43193
  name: "telegram.send_message",
43114
43194
  description: "Send a new Telegram message to an arbitrary chat (agent-initiated, not a reply)",
@@ -43321,6 +43401,75 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
43321
43401
  return { content: [{ type: "text", text: `Telegram ${method} error: ${err.message}` }], isError: true };
43322
43402
  }
43323
43403
  }
43404
+ if (name === "telegram.leave_chat") {
43405
+ const parsed = validateLeaveChatArgs(args);
43406
+ if (!parsed.ok) {
43407
+ return { content: [{ type: "text", text: parsed.message }], isError: true };
43408
+ }
43409
+ const { chatId: chat_id, reason } = parsed;
43410
+ const decision = decideLeaveChat({
43411
+ chatId: chat_id,
43412
+ allowedChats: ALLOWED_CHATS,
43413
+ privateChatsOnly: PRIVATE_CHATS_ONLY
43414
+ });
43415
+ if (!decision.allowed) {
43416
+ process.stderr.write(
43417
+ `telegram-channel(${AGENT_CODE_NAME}): leave_chat REFUSED chat=${redactId(chat_id)} why=${decision.because} detail=${JSON.stringify(decision.detail)}
43418
+ `
43419
+ );
43420
+ return {
43421
+ content: [
43422
+ {
43423
+ type: "text",
43424
+ text: `Not leaving ${chat_id}: ${decision.detail}. This tool only removes the bot from chats the agent is already barred from engaging with, so that an instruction to leave cannot be used to cut the owner off from a chat they wanted. An operator can do it from the console.`
43425
+ }
43426
+ ],
43427
+ isError: true
43428
+ };
43429
+ }
43430
+ let resp;
43431
+ try {
43432
+ resp = await telegramApiCall("leaveChat", { chat_id }, 1e4);
43433
+ } catch (err) {
43434
+ const detail = err.message;
43435
+ process.stderr.write(
43436
+ `telegram-channel(${AGENT_CODE_NAME}): leave_chat chat=${redactId(chat_id)} allowed_because=${decision.because} ok=false transport_error=${JSON.stringify(detail)} reason=${JSON.stringify(reason)}
43437
+ `
43438
+ );
43439
+ return {
43440
+ content: [
43441
+ {
43442
+ type: "text",
43443
+ text: `Could not reach Telegram to leave ${chat_id}: ${detail}. The bot is still in the chat \u2014 nothing changed, so this is safe to retry.`
43444
+ }
43445
+ ],
43446
+ isError: true
43447
+ };
43448
+ }
43449
+ process.stderr.write(
43450
+ `telegram-channel(${AGENT_CODE_NAME}): leave_chat chat=${redactId(chat_id)} allowed_because=${decision.because} ok=${resp.ok} reason=${JSON.stringify(reason)}${resp.ok ? "" : ` error=${resp.description ?? "unknown"}`}
43451
+ `
43452
+ );
43453
+ if (!resp.ok) {
43454
+ return {
43455
+ content: [
43456
+ {
43457
+ type: "text",
43458
+ text: `Telegram refused the leave for ${chat_id}: ${resp.description ?? "unknown error"}.`
43459
+ }
43460
+ ],
43461
+ isError: true
43462
+ };
43463
+ }
43464
+ return {
43465
+ content: [
43466
+ {
43467
+ type: "text",
43468
+ text: `Left ${chat_id}. Note this does NOT stop anyone adding the bot back: whether it can be added to groups at all is a BotFather setting (/setjoingroups), which only the bot owner can change.`
43469
+ }
43470
+ ]
43471
+ };
43472
+ }
43324
43473
  if (name === "telegram.consume") {
43325
43474
  const { chat_id, reason } = args;
43326
43475
  if (!chat_id) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.28.739",
3
+ "version": "0.28.740",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {