@integrity-labs/agt-cli 0.28.1111 → 0.28.1112

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.
@@ -22926,6 +22926,86 @@ var init_slack_block_kit_runtime = __esm({
22926
22926
  }
22927
22927
  });
22928
22928
 
22929
+ // src/telegram-tap-reply.ts
22930
+ var telegram_tap_reply_exports = {};
22931
+ __export(telegram_tap_reply_exports, {
22932
+ ANSWER_BUDGET_MS: () => ANSWER_BUDGET_MS,
22933
+ REFUSED_OUTCOMES: () => REFUSED_OUTCOMES,
22934
+ TELEGRAM_ALERT_MAX: () => TELEGRAM_ALERT_MAX,
22935
+ answerApprovalTap: () => answerApprovalTap,
22936
+ classifyResolve: () => classifyResolve,
22937
+ fitAlert: () => fitAlert,
22938
+ tappersOwnChatId: () => tappersOwnChatId
22939
+ });
22940
+ function classifyResolve(result) {
22941
+ if (result.kind === "threw") return { kind: "failed", detail: `threw: ${result.error}` };
22942
+ const { httpOk, status, body } = result;
22943
+ if (httpOk && body.ok === false && typeof body.outcome === "string" && REFUSED_OUTCOMES.has(body.outcome)) {
22944
+ const text = typeof body.message === "string" && body.message.trim() ? body.message.trim() : GENERIC_REFUSAL;
22945
+ return { kind: "refused", text };
22946
+ }
22947
+ if (!httpOk || !body.ok) return { kind: "failed", detail: `http=${status} reason=${body.reason ?? "unknown"}` };
22948
+ return { kind: "decided" };
22949
+ }
22950
+ function fitAlert(text) {
22951
+ if (text.length <= TELEGRAM_ALERT_MAX) return { alert: text, truncated: false };
22952
+ const room = TELEGRAM_ALERT_MAX - 1;
22953
+ const cut = text.slice(0, room);
22954
+ const space = cut.lastIndexOf(" ");
22955
+ const head = (space > room / 2 ? cut.slice(0, space) : cut).trimEnd();
22956
+ return { alert: `${head}\u2026`, truncated: true };
22957
+ }
22958
+ function tappersOwnChatId(cb) {
22959
+ const chat = cb.message?.chat;
22960
+ return chat && chat.type === "private" && chat.id === cb.from.id ? chat.id : null;
22961
+ }
22962
+ async function answerApprovalTap(deps) {
22963
+ const budget = deps.budgetMs ?? ANSWER_BUDGET_MS;
22964
+ const pending = deps.resolve().catch((err) => ({ kind: "threw", error: err?.message ?? String(err) }));
22965
+ let timer;
22966
+ const first = await Promise.race([
22967
+ pending.then((r) => ({ r })),
22968
+ new Promise((resolve3) => {
22969
+ timer = setTimeout(() => resolve3(null), budget);
22970
+ })
22971
+ ]);
22972
+ if (timer) clearTimeout(timer);
22973
+ let answeredEarly = false;
22974
+ if (first === null) {
22975
+ answeredEarly = true;
22976
+ await deps.answer("Got it", false);
22977
+ }
22978
+ const outcome = classifyResolve(first ? first.r : await pending);
22979
+ switch (outcome.kind) {
22980
+ case "decided":
22981
+ if (!answeredEarly) await deps.answer("Got it", false);
22982
+ await deps.clearKeyboard();
22983
+ break;
22984
+ case "refused": {
22985
+ const { alert, truncated } = fitAlert(outcome.text);
22986
+ if (!answeredEarly) await deps.answer(alert, true);
22987
+ if ((answeredEarly || truncated) && deps.replyPrivately) await deps.replyPrivately(outcome.text);
22988
+ deps.log(`refused (answeredEarly=${answeredEarly}, truncated=${truncated}, private=${deps.replyPrivately !== null})`);
22989
+ break;
22990
+ }
22991
+ case "failed":
22992
+ if (!answeredEarly) await deps.answer("Could not record your choice", false);
22993
+ deps.log(`resolve failed: ${outcome.detail}`);
22994
+ break;
22995
+ }
22996
+ return { ...outcome, answeredEarly };
22997
+ }
22998
+ var TELEGRAM_ALERT_MAX, ANSWER_BUDGET_MS, REFUSED_OUTCOMES, GENERIC_REFUSAL;
22999
+ var init_telegram_tap_reply = __esm({
23000
+ "src/telegram-tap-reply.ts"() {
23001
+ "use strict";
23002
+ TELEGRAM_ALERT_MAX = 200;
23003
+ ANSWER_BUDGET_MS = 8e3;
23004
+ REFUSED_OUTCOMES = /* @__PURE__ */ new Set(["authority_refused", "sod_refused", "interaction_refused"]);
23005
+ GENERIC_REFUSAL = "You can't decide this request.";
23006
+ }
23007
+ });
23008
+
22929
23009
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/core.js
22930
23010
  var NEVER = Object.freeze({
22931
23011
  status: "aborted"
@@ -30923,19 +31003,190 @@ function resolveGhostReplyMode(opts) {
30923
31003
  if (cached2 !== void 0) return cached2;
30924
31004
  return "off";
30925
31005
  }
31006
+ var MAINTENANCE_FLAG_KEY = "platform-maintenance-mode";
31007
+ function parseMaintenanceMode(raw) {
31008
+ if (raw === void 0) return void 0;
31009
+ const v = raw.trim().toLowerCase();
31010
+ if (v === "") return void 0;
31011
+ if (v === "off" || v === "tools-offline" || v === "fleet-offline") return v;
31012
+ const b = envBoolean(raw);
31013
+ if (b === true) return "fleet-offline";
31014
+ if (b === false) return "off";
31015
+ return void 0;
31016
+ }
31017
+ function cachedMaintenanceMode(path) {
31018
+ try {
31019
+ if (!existsSync2(path)) return void 0;
31020
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
31021
+ if (!parsed || typeof parsed !== "object") return void 0;
31022
+ const flags = parsed.flags;
31023
+ if (!flags || typeof flags !== "object") return void 0;
31024
+ const value = flags[MAINTENANCE_FLAG_KEY];
31025
+ if (typeof value === "string") return parseMaintenanceMode(value);
31026
+ if (typeof value === "boolean") return value ? "fleet-offline" : "off";
31027
+ return void 0;
31028
+ } catch {
31029
+ return void 0;
31030
+ }
31031
+ }
31032
+ function resolveMaintenanceMode(opts) {
31033
+ const cached2 = cachedMaintenanceMode(opts?.cachePath ?? defaultFlagsCachePath());
31034
+ if (cached2 !== void 0) return cached2;
31035
+ return "off";
31036
+ }
30926
31037
 
30927
31038
  // src/maintenance-mode.ts
30928
- var FLAG_KEY = "platform-maintenance-mode";
30929
31039
  var MAINTENANCE_OFFLINE_MESSAGE = "The Ninjafy platform is offline for scheduled maintenance right now, so I can't pick this up \u2014 please try again shortly.";
30930
31040
  function isMaintenanceModeActive(opts) {
30931
- return resolveHostBooleanFlag({
30932
- key: FLAG_KEY,
30933
- envVar: "",
30934
- defaultValue: false,
31041
+ return resolveMaintenanceMode({
30935
31042
  cachePath: opts?.cachePath,
30936
31043
  env: opts?.env
30937
- });
31044
+ }) === "fleet-offline";
31045
+ }
31046
+
31047
+ // src/maintenance-tools-offline.ts
31048
+ function isToolsOfflineActive(opts) {
31049
+ const mode = resolveMaintenanceMode({ cachePath: opts?.cachePath, env: opts?.env });
31050
+ return mode === "tools-offline" || mode === "fleet-offline";
31051
+ }
31052
+ var MAINTENANCE_GATED_TOOLS = /* @__PURE__ */ new Set([
31053
+ // Tasks.
31054
+ "kanban_add",
31055
+ "kanban_update",
31056
+ "kanban_move",
31057
+ "kanban_done",
31058
+ "kanban_reopen",
31059
+ "kanban_assign",
31060
+ "kanban_reassign",
31061
+ "kanban_recall",
31062
+ "kanban_respond",
31063
+ "kanban_progress",
31064
+ "kanban_log",
31065
+ "kanban_assign_project",
31066
+ "assign_kanban_to_human",
31067
+ // Canvases. `dashboards_request_refresh` is gated because it enqueues
31068
+ // server-side work, not because it renders anything itself.
31069
+ "dashboards_upsert",
31070
+ "dashboards_persist_widget",
31071
+ "dashboards_request_refresh",
31072
+ // ───────────────────────────────────────────────────────────────────────
31073
+ // FORWARDED writes (ENG-11436 / CodeRabbit on #6903). These reach the agent
31074
+ // through `registerForwardedApiTools()` -> `server.registerTool`, from
31075
+ // `packages/api/src/routes/mcp-tools.ts`, with names resolved at RUNTIME. They
31076
+ // are gated only because index.ts now wraps that method too; before that, this
31077
+ // whole block would have been a list the gate could not see.
31078
+ //
31079
+ // CodeRabbit named five (workflow_update, workflow_run_{start,phase,end},
31080
+ // memory_delete). Enumerating the surface to check them found 37 of the 50
31081
+ // forwarded tools unclassified, so the five were a subset — these are all of
31082
+ // the writes among them. The test now classifies that surface exhaustively, so
31083
+ // the next addition fails CI rather than slipping through.
31084
+ "composio_upload_file",
31085
+ "memory_delete",
31086
+ "memory_save",
31087
+ "onboarding_advance",
31088
+ "onboarding_provision_skills",
31089
+ "onboarding_provision_tasks",
31090
+ "onboarding_record_manager_contact",
31091
+ "request_batch_approval",
31092
+ "request_integration_access",
31093
+ "skill_contribute_fragment",
31094
+ "skill_create",
31095
+ "skill_delete",
31096
+ "skill_improve",
31097
+ "skill_propose_revision",
31098
+ "skill_publish_to_marketplace",
31099
+ "skill_update",
31100
+ "submit_performance_review",
31101
+ "workflow_run_end",
31102
+ "workflow_run_phase",
31103
+ "workflow_run_start",
31104
+ "workflow_update",
31105
+ // CHANNEL-SERVER write (Obi Wan `b3537c1e8495` on #6903). Dispatched by NAME in
31106
+ // slack-channel.ts / telegram-channel.ts / direct-chat-channel.ts, which are
31107
+ // separate processes that never call server.tool — so this one is enforced by
31108
+ // `refuseIfToolsOffline` at each dispatch point, not by the registration
31109
+ // wrapper. It inserts a `pending_interaction` row, the same durable
31110
+ // human-actionable record that gates request_buttons / request_form.
31111
+ "channel_request_input",
31112
+ // `slack.ask_user` is the SAME write under a dotted name, and it was missed
31113
+ // because the test's enumerator could not see dotted names at all (Obi Wan
31114
+ // `122ac55ff278` / `d7245eddd77f` on #6903). handleAskUser calls
31115
+ // runtime.createPendingInteraction -> POST /host/pending-interactions ->
31116
+ // inserts `pending_interaction`. Ungated, an agent on a /loop files the row,
31117
+ // the restore discards it, and the agent blocks forever on a resolution that
31118
+ // no longer exists — the exact failure the entry above is gated for.
31119
+ //
31120
+ // Verified it is the ONLY dotted channel tool that writes: createPendingInteraction
31121
+ // is CALLED once in the three channel servers (slack-channel.ts:8053);
31122
+ // slack-block-kit-runtime.ts only re-exports it.
31123
+ "slack.ask_user",
31124
+ // Workflows. FORWARDED, not local — it reaches the agent through
31125
+ // `server.registerTool` in registerForwardedApiTools, so it is gated only
31126
+ // because that method is now wrapped too (CodeRabbit on #6903).
31127
+ "workflow_create",
31128
+ // Knowledge.
31129
+ "knowledge_add",
31130
+ "knowledge_update",
31131
+ "knowledge_delete",
31132
+ // Projects and project tagging.
31133
+ "projects_create",
31134
+ "projects_update",
31135
+ "artefacts_assign_project",
31136
+ // Routines.
31137
+ "routine_propose",
31138
+ "routine_revise",
31139
+ "routine_retire",
31140
+ "submit_routine_output",
31141
+ // Standing rules.
31142
+ "rule_add",
31143
+ "rule_remove",
31144
+ // Scheduled work.
31145
+ "schedule_create",
31146
+ "schedule_update",
31147
+ "schedule_delete",
31148
+ // Requests that file a durable record a human is expected to act on. Filing
31149
+ // one mid-window is worse than refusing it: the record is discarded by the
31150
+ // restore, so the agent would have promised a human a follow-up that no
31151
+ // longer exists anywhere.
31152
+ "request_approval",
31153
+ "request_buttons",
31154
+ "request_channel_access",
31155
+ "request_form",
31156
+ "request_reconnect",
31157
+ "request_feature",
31158
+ "request_feature_withdraw",
31159
+ // ADR-0025 approval tools, registered by `approval-tools.ts` against the same
31160
+ // server object, so the wrapper in index.ts covers them too.
31161
+ "cancel_approval",
31162
+ // Status / identity.
31163
+ "status_standup",
31164
+ "status_update",
31165
+ "update_identity",
31166
+ "update_timezone",
31167
+ // Fleet changes.
31168
+ "recruit_agent",
31169
+ "integration_improve"
31170
+ ]);
31171
+ function toolsOfflineRefusalText(toolName) {
31172
+ return `Ninjafy is in a control-plane maintenance window, so \`${toolName}\` is unavailable right now. Nothing was created, changed or saved. Do NOT retry this call, and do not try a different Ninjafy tool to work around it \u2014 every tool that writes to the control plane is refusing for the same reason, and the window is short. Tell the person plainly that you cannot save this at the moment and ask them to bring it back afterwards; carry on with anything that does not need saving.`;
31173
+ }
31174
+ function refuseIfToolsOffline(name, opts) {
31175
+ if (!MAINTENANCE_GATED_TOOLS.has(name)) return null;
31176
+ const isActive = opts?.isActive ?? (() => isToolsOfflineActive());
31177
+ if (!isActive()) return null;
31178
+ return { content: [{ type: "text", text: toolsOfflineRefusalText(name) }] };
31179
+ }
31180
+ function shouldSendToolsOfflineNotice(opts) {
31181
+ const active = opts.isActive ?? (() => isToolsOfflineActive());
31182
+ if (!active()) return false;
31183
+ const now = opts.now ?? Date.now();
31184
+ const last = opts.cache.get(opts.key);
31185
+ if (last !== void 0 && now - last < opts.cooldownMs) return false;
31186
+ opts.cache.set(opts.key, now);
31187
+ return true;
30938
31188
  }
31189
+ var TOOLS_OFFLINE_USER_NOTICE = "Heads up \u2014 Ninjafy is in a maintenance window right now, so I can't save anything: no tasks, canvases, notes or scheduled items. I can still talk things through, and you can ask me to set it up once maintenance is done.";
30939
31190
 
30940
31191
  // ../core/dist/types/agent.js
30941
31192
  var FRAMEWORK_MODEL_SLOTS = {
@@ -39333,14 +39584,37 @@ var FLAG_REGISTRY = [
39333
39584
  },
39334
39585
  {
39335
39586
  key: "platform-maintenance-mode",
39336
- description: 'Platform-wide maintenance mode (ENG-6506): when on, every agent across every team/org auto-replies to admitted human inbound with a "we are offline for maintenance" notice instead of dispatching to the agent \u2014 for whole-platform downtime such as DB upgrades. Read host-side per inbound from the flags cache (no MCP restart). Boolean gate; ships dark \u2014 when off, channels behave exactly as today. (Proactive "back online" follow-up is the ENG-6508 fast-follow, not this flag.)',
39337
- flagType: "boolean",
39338
- // Declared safe value is `false` (normal operation). Fail-safe direction: a
39339
- // flag-DB read error or absent flag map must never silence the fleet on its
39340
- // own — agents stay responsive unless an operator deliberately flips this on
39341
- // from the admin Feature Flags page. No envVar: net-new control, no
39342
- // pre-flags env gate to migrate.
39343
- defaultValue: false,
39587
+ description: 'Platform-wide maintenance mode (ENG-6506; widened to modes by ENG-11436). off = normal operation (ships dark; channels behave exactly as today). tools-offline = every agent KEEPS conversing but every Augmented tool that WRITES to the control plane refuses with a reason, so nothing is created during a window whose database restore would discard it; reads are untouched. fleet-offline = the previous boolean-true behaviour AND the write gate: admitted human inbound gets a "we are offline for maintenance" notice instead of being dispatched, for whole-platform downtime such as DB upgrades. The modes are CUMULATIVE \u2014 fleet-offline gates writes too, because silencing channel inbound does not stop a /loop or a scheduled task filing work. Read host-side per inbound and per tool call from the flags cache (no MCP restart). (Proactive "back online" follow-up is the ENG-6508 fast-follow, not this flag.)',
39588
+ flagType: "enum",
39589
+ allowedValues: ["off", "tools-offline", "fleet-offline"],
39590
+ // Declared safe value is `off` (normal operation). Fail-safe direction: a
39591
+ // flag-DB read error or absent flag map must never silence the fleet OR take
39592
+ // its write tools away on its own — agents stay fully functional unless an
39593
+ // operator deliberately sets a mode from the admin Feature Flags page. No
39594
+ // envVar: net-new control, no pre-flags env gate to migrate.
39595
+ //
39596
+ // ENG-11436 widened this from a boolean rather than adding a second flag, so
39597
+ // there is ONE operator control whose modes are mutually exclusive by type
39598
+ // instead of two similar booleans with four combinations.
39599
+ //
39600
+ // The host resolver GRANDFATHERS a boolean-shaped cache value
39601
+ // (true -> fleet-offline, false -> off), and the DIRECTION it protects is
39602
+ // narrower than an earlier version of this comment claimed (Obi Wan
39603
+ // `e3f0d2c154b9` on #6903). It said "an older manager still writing a boolean
39604
+ // must not make a central flip a silent no-op". That situation cannot arise:
39605
+ // once this registry change deploys, normalizeFlagValue only ever yields one
39606
+ // of the three strings, feature-flags-host.ts writes the heartbeat map
39607
+ // verbatim, so no manager writes a boolean again. What grandfathering
39608
+ // actually covers is a NEW MCP reading a STALE PRE-DEPLOY cache that still
39609
+ // holds one.
39610
+ //
39611
+ // The direction that DOES happen is not handled and cannot be: an OLD MCP
39612
+ // reads the new string with resolveHostBooleanFlag, gets undefined, and keeps
39613
+ // dispatching. That is a real regression against ENG-6506 until the fleet
39614
+ // converges, and it is why the runbook makes convergence a deploy-order
39615
+ // PRECONDITION rather than offering a boolean fallback — there is none, since
39616
+ // an enum flag rejects `{value: true}` with a 400.
39617
+ defaultValue: "off",
39344
39618
  // Flipping this takes the ENTIRE fleet offline to end users; a mis-flip is a
39345
39619
  // platform-wide availability incident, so mutations require explicit
39346
39620
  // confirmation (the admin flip-reach modal).
@@ -43937,6 +44211,42 @@ var TELEGRAM_ACCOUNT_WARN_COOLDOWN_MS = (() => {
43937
44211
  const n = raw ? Number(raw) : NaN;
43938
44212
  return Number.isFinite(n) && n >= 0 ? n * 1e3 : 24 * 60 * 60 * 1e3;
43939
44213
  })();
44214
+ var TELEGRAM_TOOLS_OFFLINE_NOTICE_CACHE = /* @__PURE__ */ new Map();
44215
+ var TELEGRAM_TOOLS_OFFLINE_COOLDOWN_MS = (() => {
44216
+ const raw = process.env["TELEGRAM_TOOLS_OFFLINE_COOLDOWN_SEC"];
44217
+ const n = raw ? Number(raw) : NaN;
44218
+ return Number.isFinite(n) && n >= 0 ? n * 1e3 : 60 * 60 * 1e3;
44219
+ })();
44220
+ async function maybeSendTelegramToolsOfflineNotice(args) {
44221
+ const key2 = `${args.chatId}|tools_offline`;
44222
+ if (!shouldSendToolsOfflineNotice({
44223
+ cache: TELEGRAM_TOOLS_OFFLINE_NOTICE_CACHE,
44224
+ key: key2,
44225
+ cooldownMs: TELEGRAM_TOOLS_OFFLINE_COOLDOWN_MS
44226
+ })) {
44227
+ return;
44228
+ }
44229
+ try {
44230
+ const resp = await telegramApiCall(
44231
+ "sendMessage",
44232
+ { chat_id: args.chatId, text: TOOLS_OFFLINE_USER_NOTICE },
44233
+ 1e4
44234
+ );
44235
+ if (!resp.ok) {
44236
+ TELEGRAM_TOOLS_OFFLINE_NOTICE_CACHE.delete(key2);
44237
+ process.stderr.write(
44238
+ `telegram-channel(${AGENT_CODE_NAME}): tools-offline notice failed: ${resp.description ?? "unknown"}
44239
+ `
44240
+ );
44241
+ }
44242
+ } catch (err) {
44243
+ TELEGRAM_TOOLS_OFFLINE_NOTICE_CACHE.delete(key2);
44244
+ process.stderr.write(
44245
+ `telegram-channel(${AGENT_CODE_NAME}): tools-offline notice error: ${err.message}
44246
+ `
44247
+ );
44248
+ }
44249
+ }
43940
44250
  async function maybeSendTelegramAccountWarn(args) {
43941
44251
  const key2 = `${args.chatId}|account_warn`;
43942
44252
  if (!shouldSendAccountWarn({
@@ -46176,6 +46486,10 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
46176
46486
  ]
46177
46487
  }));
46178
46488
  mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
46489
+ {
46490
+ const offline = refuseIfToolsOffline(req.params.name);
46491
+ if (offline) return offline;
46492
+ }
46179
46493
  const { name, arguments: args } = req.params;
46180
46494
  const impersonationRefusal = shouldRefuseImpersonatedEgress(
46181
46495
  name,
@@ -46521,6 +46835,8 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
46521
46835
  });
46522
46836
  }
46523
46837
  await maybeSendTelegramAccountWarn({ chatId: chat_id });
46838
+ void maybeSendTelegramToolsOfflineNotice({ chatId: chat_id }).catch(() => {
46839
+ });
46524
46840
  return {
46525
46841
  content: [{ type: "text", text: promiseNote ? `sent. ${promiseNote}` : "sent" }]
46526
46842
  };
@@ -46755,72 +47071,82 @@ async function handleCallbackQuery(cb) {
46755
47071
  await handlePerfReviewTap(cb, decoded.callbackId, decoded.token);
46756
47072
  return;
46757
47073
  }
46758
- await ackCallbackQuery(cb.id, "Got it").catch(() => {
46759
- });
46760
47074
  const apiCallRuntime = await Promise.resolve().then(() => (init_ask_user_runtime(), ask_user_runtime_exports));
47075
+ const { answerApprovalTap: answerApprovalTap2, tappersOwnChatId: tappersOwnChatId2 } = await Promise.resolve().then(() => (init_telegram_tap_reply(), telegram_tap_reply_exports));
46761
47076
  const cfg = {
46762
47077
  apiHost: AGT_HOST,
46763
47078
  apiKey: AGT_API_KEY,
46764
47079
  agentId: AGT_AGENT_ID
46765
47080
  };
46766
- let resolveResult;
46767
- try {
46768
- const res = await apiCallRuntime.apiCall(cfg, "POST", "/host/interactive/resolve", {
46769
- agent_id: cfg.agentId,
46770
- callback_id: decoded.callbackId,
46771
- token: decoded.token,
46772
- responded_by_user: String(cb.from.id),
46773
- // ENG-11372 PR3: names the chat for the API's binding shadow.
46774
- surface_channel: "telegram"
46775
- });
46776
- resolveResult = await res.json().catch(() => ({}));
46777
- if (!res.ok || !resolveResult.ok) {
46778
- process.stderr.write(
46779
- `telegram-channel(${AGENT_CODE_NAME}): /host/interactive/resolve returned non-ok (callback_id=${decoded.callbackId}, http=${res.status}, reason=${resolveResult.reason ?? "unknown"})
46780
- `
46781
- );
46782
- await ackCallbackQuery(cb.id, "Could not record your choice").catch(() => {
46783
- });
46784
- return;
46785
- }
46786
- } catch (err) {
46787
- process.stderr.write(
46788
- `telegram-channel(${AGENT_CODE_NAME}): /host/interactive/resolve threw (callback_id=${decoded.callbackId}): ${err.message}
47081
+ const ownChatId = tappersOwnChatId2(cb);
47082
+ await answerApprovalTap2({
47083
+ resolve: async () => {
47084
+ try {
47085
+ const res = await apiCallRuntime.apiCall(cfg, "POST", "/host/interactive/resolve", {
47086
+ agent_id: cfg.agentId,
47087
+ callback_id: decoded.callbackId,
47088
+ token: decoded.token,
47089
+ responded_by_user: String(cb.from.id),
47090
+ // ENG-11372 PR3: names the chat for the API's binding shadow.
47091
+ surface_channel: "telegram"
47092
+ });
47093
+ const body = await res.json().catch(() => ({}));
47094
+ return { kind: "response", httpOk: res.ok, status: res.status, body };
47095
+ } catch (err) {
47096
+ return { kind: "threw", error: err.message };
47097
+ }
47098
+ },
47099
+ answer: (text, showAlert) => ackCallbackQuery(cb.id, text, { showAlert }),
47100
+ // Edit the original message to clear the keyboard. We only have the chat
47101
+ // + message coordinates when `callback_query.message` is set (Telegram
47102
+ // omits it for inline-mode bots, which we aren't). Empty inline_keyboard
47103
+ // removes the buttons but leaves the prompt text intact, so the prompt
47104
+ // stays scrollable history. Edit failure is non-fatal: the row is already
47105
+ // resolved and the agent sees the value on its next poll.
47106
+ clearKeyboard: async () => {
47107
+ if (!cb.message) return;
47108
+ try {
47109
+ const resp = await telegramApiCall(
47110
+ "editMessageReplyMarkup",
47111
+ { chat_id: cb.message.chat.id, message_id: cb.message.message_id, reply_markup: { inline_keyboard: [] } },
47112
+ 5e3
47113
+ );
47114
+ if (!resp.ok) {
47115
+ process.stderr.write(
47116
+ `telegram-channel(${AGENT_CODE_NAME}): editMessageReplyMarkup failed (callback_id=${decoded.callbackId}): ${resp.description ?? "unknown"}
46789
47117
  `
46790
- );
46791
- await ackCallbackQuery(cb.id, "Could not record your choice").catch(() => {
46792
- });
46793
- return;
46794
- }
46795
- const confirmationLabel = "\u2713";
46796
- if (cb.message) {
46797
- try {
46798
- const resp = await telegramApiCall(
46799
- "editMessageReplyMarkup",
46800
- {
46801
- chat_id: cb.message.chat.id,
46802
- message_id: cb.message.message_id,
46803
- // Empty inline_keyboard removes the buttons but leaves the
46804
- // original prompt text intact. Less destructive than
46805
- // editMessageText, and the prompt remains scrollable history.
46806
- reply_markup: { inline_keyboard: [] }
46807
- },
46808
- 5e3
46809
- );
46810
- if (!resp.ok) {
47118
+ );
47119
+ }
47120
+ } catch (err) {
46811
47121
  process.stderr.write(
46812
- `telegram-channel(${AGENT_CODE_NAME}): editMessageReplyMarkup failed (callback_id=${decoded.callbackId}): ${resp.description ?? "unknown"}
47122
+ `telegram-channel(${AGENT_CODE_NAME}): editMessageReplyMarkup threw (callback_id=${decoded.callbackId}): ${err.message}
46813
47123
  `
46814
47124
  );
46815
47125
  }
46816
- } catch (err) {
46817
- process.stderr.write(
46818
- `telegram-channel(${AGENT_CODE_NAME}): editMessageReplyMarkup threw (callback_id=${decoded.callbackId}): ${err.message}
47126
+ },
47127
+ replyPrivately: ownChatId !== null && cb.message ? async (text) => {
47128
+ try {
47129
+ const resp = await telegramApiCall(
47130
+ "sendMessage",
47131
+ { chat_id: ownChatId, text, reply_parameters: { message_id: cb.message.message_id } },
47132
+ 5e3
47133
+ );
47134
+ if (!resp.ok) {
47135
+ process.stderr.write(
47136
+ `telegram-channel(${AGENT_CODE_NAME}): refusal reply rejected (callback_id=${decoded.callbackId}): ${resp.description ?? "unknown"}
46819
47137
  `
46820
- );
46821
- }
46822
- void confirmationLabel;
46823
- }
47138
+ );
47139
+ }
47140
+ } catch (err) {
47141
+ process.stderr.write(
47142
+ `telegram-channel(${AGENT_CODE_NAME}): refusal reply threw (callback_id=${decoded.callbackId}): ${err.message}
47143
+ `
47144
+ );
47145
+ }
47146
+ } : null,
47147
+ log: (line) => process.stderr.write(`telegram-channel(${AGENT_CODE_NAME}): approval tap ${line} (callback_id=${decoded.callbackId})
47148
+ `)
47149
+ });
46824
47150
  }
46825
47151
  var PERF_REVIEW_TOKEN_PREFIX = "pr_";
46826
47152
  var PERF_REVIEW_COMMENT_TOKEN = "pr_comment";
@@ -46897,13 +47223,16 @@ async function postPerfReviewResolve(reviewId, token, respondedByUser, comment)
46897
47223
  return false;
46898
47224
  }
46899
47225
  }
46900
- async function ackCallbackQuery(callbackQueryId, text) {
47226
+ async function ackCallbackQuery(callbackQueryId, text, opts = {}) {
46901
47227
  try {
46902
47228
  const resp = await telegramApiCall(
46903
47229
  "answerCallbackQuery",
46904
47230
  {
46905
47231
  callback_query_id: callbackQueryId,
46906
- ...text ? { text } : {}
47232
+ ...text ? { text } : {},
47233
+ // ENG-11210: an alert is a popup the tapper dismisses, for a refusal
47234
+ // they need to read rather than a toast that fades.
47235
+ ...opts.showAlert ? { show_alert: true } : {}
46907
47236
  },
46908
47237
  5e3
46909
47238
  );
@@ -68,8 +68,8 @@ import {
68
68
  writeDirectChatSessionState,
69
69
  writeEgressAllowlist,
70
70
  writePersistentClaudeWrapper
71
- } from "./chunk-4PTXGOVO.js";
72
- import "./chunk-TZM6YVE7.js";
71
+ } from "./chunk-5EYINOIW.js";
72
+ import "./chunk-NO57I56V.js";
73
73
  import "./chunk-OCMJ5E6J.js";
74
74
  import "./chunk-XWVM4KPK.js";
75
75
  import "./chunk-EL5MWGGH.js";
@@ -145,4 +145,4 @@ export {
145
145
  writeEgressAllowlist,
146
146
  writePersistentClaudeWrapper
147
147
  };
148
- //# sourceMappingURL=persistent-session-HSYN3SQ2.js.map
148
+ //# sourceMappingURL=persistent-session-SDYY75X7.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  paneLogPath
3
- } from "./chunk-4PTXGOVO.js";
4
- import "./chunk-TZM6YVE7.js";
3
+ } from "./chunk-5EYINOIW.js";
4
+ import "./chunk-NO57I56V.js";
5
5
  import "./chunk-OCMJ5E6J.js";
6
6
  import "./chunk-XWVM4KPK.js";
7
7
  import "./chunk-EL5MWGGH.js";
@@ -802,4 +802,4 @@ export {
802
802
  readAndResetSlackReplyBindingClassifications,
803
803
  readAndResetSlackReplyTargetClassifications
804
804
  };
805
- //# sourceMappingURL=responsiveness-probe-MO4L5O6F.js.map
805
+ //# sourceMappingURL=responsiveness-probe-NEBHDSMI.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionTranscriptDir
3
- } from "./chunk-TZM6YVE7.js";
3
+ } from "./chunk-NO57I56V.js";
4
4
  import "./chunk-DGUM43GV.js";
5
5
 
6
6
  // src/lib/session-auth-dead.ts
@@ -204,4 +204,4 @@ export {
204
204
  decideSessionAuthState,
205
205
  probeSessionAuth
206
206
  };
207
- //# sourceMappingURL=session-auth-dead-ZKI4XCSI.js.map
207
+ //# sourceMappingURL=session-auth-dead-CNV4O7RF.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.28.1111",
3
+ "version": "0.28.1112",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {