switchroom 0.18.12 → 0.18.14

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.
Files changed (76) hide show
  1. package/dist/agent-scheduler/index.js +57 -9
  2. package/dist/auth-broker/index.js +174 -72
  3. package/dist/cli/autoaccept-poll.js +23 -0
  4. package/dist/cli/drive-write-pretool.mjs +24 -1
  5. package/dist/cli/foreground-hog-pretool.mjs +264 -0
  6. package/dist/cli/ms-365-write-pretool.mjs +31 -8
  7. package/dist/cli/notion-write-pretool.mjs +9 -2
  8. package/dist/cli/skill-validate-pretool.mjs +144 -2847
  9. package/dist/cli/switchroom.js +986 -3131
  10. package/dist/host-control/main.js +216 -2863
  11. package/dist/vault/approvals/kernel-server.js +67 -1
  12. package/dist/vault/broker/server.js +98 -45
  13. package/package.json +1 -1
  14. package/profiles/coding/CLAUDE.md.hbs +2 -0
  15. package/profiles/default/CLAUDE.md.hbs +2 -0
  16. package/skills/switchroom-architecture/telegram.md +0 -1
  17. package/telegram-plugin/auth-snapshot-format.ts +37 -5
  18. package/telegram-plugin/auto-fallback-fleet.ts +29 -1
  19. package/telegram-plugin/bridge/bridge.ts +2 -0
  20. package/telegram-plugin/dist/bridge/bridge.js +51 -3
  21. package/telegram-plugin/dist/gateway/gateway.js +1251 -2368
  22. package/telegram-plugin/dist/server.js +67 -3
  23. package/telegram-plugin/format.ts +19 -0
  24. package/telegram-plugin/gateway/approval-hold.ts +21 -2
  25. package/telegram-plugin/gateway/auth-broker-client.ts +1 -0
  26. package/telegram-plugin/gateway/auth-command.ts +14 -0
  27. package/telegram-plugin/gateway/callback-query-handlers.ts +12 -0
  28. package/telegram-plugin/gateway/forward-origin.ts +235 -0
  29. package/telegram-plugin/gateway/gateway.ts +445 -83
  30. package/telegram-plugin/gateway/throttle-tier-wiring.ts +268 -0
  31. package/telegram-plugin/history.ts +106 -6
  32. package/telegram-plugin/inline-keyboard-callbacks.ts +94 -0
  33. package/telegram-plugin/model-unavailable.ts +61 -13
  34. package/telegram-plugin/outbound-field-redact.ts +69 -0
  35. package/telegram-plugin/render/render.ts +32 -14
  36. package/telegram-plugin/render/rich-render.ts +40 -32
  37. package/telegram-plugin/scoped-approval.ts +11 -2
  38. package/telegram-plugin/secret-detect/chunker.ts +18 -4
  39. package/telegram-plugin/secret-detect/index.ts +12 -56
  40. package/telegram-plugin/send-gate-degraded.test.ts +131 -0
  41. package/telegram-plugin/send-gate.test.ts +25 -6
  42. package/telegram-plugin/send-gate.ts +82 -8
  43. package/telegram-plugin/session-tail.ts +82 -7
  44. package/telegram-plugin/stream-controller.ts +3 -2
  45. package/telegram-plugin/subagent-watcher.ts +71 -16
  46. package/telegram-plugin/tests/approval-hold-outcome.test.ts +36 -5
  47. package/telegram-plugin/tests/auto-fallback-fleet.test.ts +72 -0
  48. package/telegram-plugin/tests/callback-query-handlers.test.ts +65 -0
  49. package/telegram-plugin/tests/forward-origin.test.ts +309 -0
  50. package/telegram-plugin/tests/gateway-outbound-redact.test.ts +57 -0
  51. package/telegram-plugin/tests/history.test.ts +272 -0
  52. package/telegram-plugin/tests/inbound-message-types.test.ts +5 -1
  53. package/telegram-plugin/tests/inline-keyboard-callbacks.test.ts +164 -0
  54. package/telegram-plugin/tests/operator-events-session-tail.test.ts +74 -0
  55. package/telegram-plugin/tests/outbound-field-redact.test.ts +107 -0
  56. package/telegram-plugin/tests/reaction-gate-routing.test.ts +173 -0
  57. package/telegram-plugin/tests/render/render-outbound-chunks.test.ts +6 -4
  58. package/telegram-plugin/tests/render/render.test.ts +88 -0
  59. package/telegram-plugin/tests/render/rich-render.test.ts +41 -22
  60. package/telegram-plugin/tests/scoped-approval.test.ts +27 -0
  61. package/telegram-plugin/tests/secret-detect-chunk-overlap.test.ts +65 -0
  62. package/telegram-plugin/tests/secret-detect-oauth-code.test.ts +5 -4
  63. package/telegram-plugin/tests/session-tail-sidecar-reap.test.ts +268 -0
  64. package/telegram-plugin/tests/single-mode-stream-reply.test.ts +5 -3
  65. package/telegram-plugin/tests/status-accent.test.ts +5 -3
  66. package/telegram-plugin/tests/stream-controller-chunk-cap.test.ts +20 -20
  67. package/telegram-plugin/tests/stream-reply-handler.test.ts +5 -2
  68. package/telegram-plugin/tests/subagent-watcher-fd-leak.test.ts +275 -0
  69. package/telegram-plugin/tests/throttle-tier-wiring.test.ts +290 -0
  70. package/telegram-plugin/tests/throttle-tier.test.ts +278 -0
  71. package/telegram-plugin/tests/worktree-watch-cwds.test.ts +215 -1
  72. package/telegram-plugin/throttle-tier.ts +226 -0
  73. package/telegram-plugin/uat/scenarios/jtbd-rich-formatting-render-dm.test.ts +8 -7
  74. package/telegram-plugin/worktree-watch-cwds.ts +194 -5
  75. package/telegram-plugin/secret-detect/secretlint-source.ts +0 -95
  76. package/telegram-plugin/tests/secret-detect-secretlint.test.ts +0 -105
@@ -4741,6 +4741,12 @@ var MarkExhaustedRequestSchema = exports_external.object({
4741
4741
  id: exports_external.string().min(1),
4742
4742
  until: exports_external.number().int().positive().optional()
4743
4743
  });
4744
+ var MarkThrottledRequestSchema = exports_external.object({
4745
+ v: exports_external.literal(PROTOCOL_VERSION),
4746
+ op: exports_external.literal("mark-throttled"),
4747
+ id: exports_external.string().min(1),
4748
+ until: exports_external.number().int().positive()
4749
+ });
4744
4750
  var RefreshAccountRequestSchema = exports_external.object({
4745
4751
  v: exports_external.literal(PROTOCOL_VERSION),
4746
4752
  op: exports_external.literal("refresh-account"),
@@ -4841,6 +4847,7 @@ var RequestSchema = exports_external.discriminatedUnion("op", [
4841
4847
  ListStateRequestSchema,
4842
4848
  SetActiveRequestSchema,
4843
4849
  MarkExhaustedRequestSchema,
4850
+ MarkThrottledRequestSchema,
4844
4851
  RefreshAccountRequestSchema,
4845
4852
  AddAccountRequestSchema,
4846
4853
  RmAccountRequestSchema,
@@ -4860,6 +4867,7 @@ var AccountStateSchema = exports_external.object({
4860
4867
  expiresAt: exports_external.number().optional(),
4861
4868
  exhausted: exports_external.boolean(),
4862
4869
  exhausted_until: exports_external.number().optional(),
4870
+ throttled_until: exports_external.number().optional(),
4863
4871
  threshold_violations: exports_external.number().int().nonnegative().optional(),
4864
4872
  last_refreshed_at: exports_external.number().optional()
4865
4873
  });
@@ -4890,6 +4898,12 @@ var MarkExhaustedDataSchema = exports_external.object({
4890
4898
  rolled: exports_external.array(exports_external.string()),
4891
4899
  rolledTo: exports_external.string().nullable().optional()
4892
4900
  });
4901
+ var MarkThrottledDataSchema = exports_external.object({
4902
+ account: exports_external.string(),
4903
+ throttled_until: exports_external.number(),
4904
+ escalated: exports_external.boolean(),
4905
+ rolledTo: exports_external.string().nullable().optional()
4906
+ });
4893
4907
  var RefreshAccountDataSchema = exports_external.object({
4894
4908
  account: exports_external.string(),
4895
4909
  expiresAt: exports_external.number().optional()
@@ -5115,6 +5129,15 @@ class AuthBrokerClient {
5115
5129
  const data = await this.send(req);
5116
5130
  return data;
5117
5131
  }
5132
+ async markThrottled(until) {
5133
+ const data = await this.send({
5134
+ v: PROTOCOL_VERSION,
5135
+ id: randomUUID2(),
5136
+ op: "mark-throttled",
5137
+ until
5138
+ });
5139
+ return data;
5140
+ }
5118
5141
  async claimNotification(key, windowMs) {
5119
5142
  const data = await this.send({
5120
5143
  v: PROTOCOL_VERSION,
@@ -4000,7 +4000,7 @@ function decodeResponse(line) {
4000
4000
  }
4001
4001
  return ResponseSchema.parse(parsed);
4002
4002
  }
4003
- var MAX_FRAME_BYTES, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, MicrosoftCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ListMicrosoftAccountsRequestSchema, ProbeQuotaRequestSchema, ClaimNotificationRequestSchema, RequestSchema, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, ClaimNotificationDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, MicrosoftAccountStateSchema, ListMicrosoftAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema, ResponseSchema;
4003
+ var MAX_FRAME_BYTES, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, MarkThrottledRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, MicrosoftCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ListMicrosoftAccountsRequestSchema, ProbeQuotaRequestSchema, ClaimNotificationRequestSchema, RequestSchema, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, MarkThrottledDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, ClaimNotificationDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, MicrosoftAccountStateSchema, ListMicrosoftAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema, ResponseSchema;
4004
4004
  var init_protocol = __esm(() => {
4005
4005
  init_zod();
4006
4006
  MAX_FRAME_BYTES = 64 * 1024;
@@ -4029,6 +4029,12 @@ var init_protocol = __esm(() => {
4029
4029
  id: exports_external.string().min(1),
4030
4030
  until: exports_external.number().int().positive().optional()
4031
4031
  });
4032
+ MarkThrottledRequestSchema = exports_external.object({
4033
+ v: exports_external.literal(PROTOCOL_VERSION),
4034
+ op: exports_external.literal("mark-throttled"),
4035
+ id: exports_external.string().min(1),
4036
+ until: exports_external.number().int().positive()
4037
+ });
4032
4038
  RefreshAccountRequestSchema = exports_external.object({
4033
4039
  v: exports_external.literal(PROTOCOL_VERSION),
4034
4040
  op: exports_external.literal("refresh-account"),
@@ -4129,6 +4135,7 @@ var init_protocol = __esm(() => {
4129
4135
  ListStateRequestSchema,
4130
4136
  SetActiveRequestSchema,
4131
4137
  MarkExhaustedRequestSchema,
4138
+ MarkThrottledRequestSchema,
4132
4139
  RefreshAccountRequestSchema,
4133
4140
  AddAccountRequestSchema,
4134
4141
  RmAccountRequestSchema,
@@ -4148,6 +4155,7 @@ var init_protocol = __esm(() => {
4148
4155
  expiresAt: exports_external.number().optional(),
4149
4156
  exhausted: exports_external.boolean(),
4150
4157
  exhausted_until: exports_external.number().optional(),
4158
+ throttled_until: exports_external.number().optional(),
4151
4159
  threshold_violations: exports_external.number().int().nonnegative().optional(),
4152
4160
  last_refreshed_at: exports_external.number().optional()
4153
4161
  });
@@ -4178,6 +4186,12 @@ var init_protocol = __esm(() => {
4178
4186
  rolled: exports_external.array(exports_external.string()),
4179
4187
  rolledTo: exports_external.string().nullable().optional()
4180
4188
  });
4189
+ MarkThrottledDataSchema = exports_external.object({
4190
+ account: exports_external.string(),
4191
+ throttled_until: exports_external.number(),
4192
+ escalated: exports_external.boolean(),
4193
+ rolledTo: exports_external.string().nullable().optional()
4194
+ });
4181
4195
  RefreshAccountDataSchema = exports_external.object({
4182
4196
  account: exports_external.string(),
4183
4197
  expiresAt: exports_external.number().optional()
@@ -4376,6 +4390,15 @@ class AuthBrokerClient {
4376
4390
  const data = await this.send(req);
4377
4391
  return data;
4378
4392
  }
4393
+ async markThrottled(until) {
4394
+ const data = await this.send({
4395
+ v: PROTOCOL_VERSION,
4396
+ id: randomUUID(),
4397
+ op: "mark-throttled",
4398
+ until
4399
+ });
4400
+ return data;
4401
+ }
4379
4402
  async claimNotification(key, windowMs) {
4380
4403
  const data = await this.send({
4381
4404
  v: PROTOCOL_VERSION,
@@ -0,0 +1,264 @@
1
+ // src/cli/foreground-hog-pretool.ts
2
+ import { readFileSync } from "node:fs";
3
+ function gateEnabledFromEnv(env = process.env) {
4
+ const v = env.SWITCHROOM_FOREGROUND_HOG_GATE;
5
+ if (v == null)
6
+ return true;
7
+ const t = v.trim().toLowerCase();
8
+ return !(t === "0" || t === "false" || t === "off" || t === "no");
9
+ }
10
+ function blankQuotedAndHeredocs(src) {
11
+ const out = src.split("");
12
+ const n = src.length;
13
+ const pending = [];
14
+ let i = 0;
15
+ while (i < n) {
16
+ const ch = src[i];
17
+ if (ch === "\\") {
18
+ out[i] = " ";
19
+ if (i + 1 < n)
20
+ out[i + 1] = " ";
21
+ i += 2;
22
+ continue;
23
+ }
24
+ if (ch === "'") {
25
+ let j = i + 1;
26
+ while (j < n && src[j] !== "'")
27
+ j++;
28
+ if (j >= n)
29
+ return null;
30
+ for (let k = i;k <= j; k++)
31
+ out[k] = " ";
32
+ i = j + 1;
33
+ continue;
34
+ }
35
+ if (ch === '"') {
36
+ let j = i + 1;
37
+ while (j < n && src[j] !== '"') {
38
+ j += src[j] === "\\" ? 2 : 1;
39
+ }
40
+ if (j >= n)
41
+ return null;
42
+ for (let k = i;k <= j && k < n; k++)
43
+ out[k] = " ";
44
+ i = j + 1;
45
+ continue;
46
+ }
47
+ if (ch === "<" && src[i + 1] === "<") {
48
+ if (src[i + 2] === "<") {
49
+ i += 3;
50
+ continue;
51
+ }
52
+ let j = i + 2;
53
+ let stripTabs = false;
54
+ if (src[j] === "-") {
55
+ stripTabs = true;
56
+ j++;
57
+ }
58
+ while (j < n && (src[j] === " " || src[j] === "\t"))
59
+ j++;
60
+ let quote = "";
61
+ if (src[j] === "'" || src[j] === '"') {
62
+ quote = src[j];
63
+ j++;
64
+ }
65
+ let tag = "";
66
+ while (j < n && /[A-Za-z0-9_]/.test(src[j])) {
67
+ tag += src[j];
68
+ j++;
69
+ }
70
+ if (quote) {
71
+ if (src[j] !== quote)
72
+ return null;
73
+ j++;
74
+ }
75
+ if (!tag)
76
+ return null;
77
+ pending.push({ tag, stripTabs });
78
+ i = j;
79
+ continue;
80
+ }
81
+ if (ch === `
82
+ ` && pending.length > 0) {
83
+ let j = i + 1;
84
+ while (pending.length > 0) {
85
+ const { tag, stripTabs } = pending.shift();
86
+ let found = false;
87
+ while (j <= n) {
88
+ let eol = src.indexOf(`
89
+ `, j);
90
+ if (eol === -1)
91
+ eol = n;
92
+ const line = src.slice(j, eol);
93
+ const cmp = stripTabs ? line.replace(/^\t+/, "") : line;
94
+ if (cmp === tag) {
95
+ found = true;
96
+ j = eol + 1;
97
+ break;
98
+ }
99
+ for (let k = j;k < eol; k++)
100
+ out[k] = " ";
101
+ if (eol === n) {
102
+ j = n + 1;
103
+ break;
104
+ }
105
+ j = eol + 1;
106
+ }
107
+ if (!found)
108
+ return null;
109
+ }
110
+ i = j;
111
+ continue;
112
+ }
113
+ i++;
114
+ }
115
+ return out.join("");
116
+ }
117
+ var LOOP_SLEEP = /\b(?:while|until|for)\b[\s\S]*?\bdo\b[\s\S]*?\bsleep\b[\s\S]*?\bdone\b/;
118
+ function sleepSeconds(token) {
119
+ const m = /^(\d+(?:\.\d+)?)([smhd]?)$/.exec(token);
120
+ if (!m)
121
+ return null;
122
+ const mult = { "": 1, s: 1, m: 60, h: 3600, d: 86400 }[m[2]];
123
+ return Number(m[1]) * mult;
124
+ }
125
+ function hasFollowFlag(args, allowCapitalF) {
126
+ return args.some((a) => a === "--follow" || a.startsWith("--follow=") || /^-[a-zA-Z0-9]+$/.test(a) && (a.includes("f") || allowCapitalF && a.includes("F")));
127
+ }
128
+ function foregroundSegments(command) {
129
+ const cleaned = command.replace(/\d*>&\d*/g, " ");
130
+ const out = [];
131
+ const sep = /&&|\|\||[;|\n&]/g;
132
+ let last = 0;
133
+ let m;
134
+ const push = (seg, backgrounded) => {
135
+ const t = seg.trim();
136
+ if (t && !backgrounded)
137
+ out.push(t);
138
+ };
139
+ while ((m = sep.exec(cleaned)) !== null) {
140
+ push(cleaned.slice(last, m.index), m[0] === "&");
141
+ last = m.index + m[0].length;
142
+ }
143
+ push(cleaned.slice(last), false);
144
+ return out;
145
+ }
146
+ function commandTokens(segment) {
147
+ const SKIP = new Set([
148
+ "if",
149
+ "elif",
150
+ "then",
151
+ "else",
152
+ "do",
153
+ "time",
154
+ "nohup",
155
+ "sudo",
156
+ "command",
157
+ "exec"
158
+ ]);
159
+ const tokens = segment.split(/\s+/);
160
+ const out = [];
161
+ let started = false;
162
+ for (const rawTok of tokens) {
163
+ const tok = rawTok.replace(/^[({]+/, "");
164
+ if (!tok)
165
+ continue;
166
+ if (!started) {
167
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(tok))
168
+ continue;
169
+ if (SKIP.has(tok))
170
+ continue;
171
+ started = true;
172
+ }
173
+ out.push(tok);
174
+ }
175
+ return out;
176
+ }
177
+ function matchSegment(segment) {
178
+ const tokens = commandTokens(segment);
179
+ const cmd = tokens[0];
180
+ if (!cmd)
181
+ return null;
182
+ const args = tokens.slice(1);
183
+ if (cmd === "tail" && hasFollowFlag(args, true))
184
+ return "tail -f/--follow";
185
+ if (cmd === "watch")
186
+ return "watch";
187
+ if (cmd === "sleep") {
188
+ const secs = sleepSeconds(args[0] ?? "");
189
+ if (secs !== null && secs > 30)
190
+ return `sleep ${args[0]} (> 30s foreground)`;
191
+ }
192
+ if (cmd === "gh") {
193
+ const positionals = args.filter((a) => !a.startsWith("-"));
194
+ if (positionals[0] === "pr" && positionals[1] === "checks" && args.includes("--watch"))
195
+ return "gh pr checks --watch";
196
+ if (positionals[0] === "run" && positionals[1] === "watch")
197
+ return "gh run watch";
198
+ }
199
+ if (cmd === "docker" && args[0] === "logs" && hasFollowFlag(args.slice(1), false))
200
+ return "docker logs -f/--follow";
201
+ if (cmd === "kubectl" && args[0] === "logs" && hasFollowFlag(args.slice(1), false))
202
+ return "kubectl logs -f/--follow";
203
+ if (cmd === "journalctl" && hasFollowFlag(args, false))
204
+ return "journalctl -f";
205
+ return null;
206
+ }
207
+ function detectForegroundHog(command) {
208
+ const blanked = blankQuotedAndHeredocs(command);
209
+ if (blanked === null)
210
+ return null;
211
+ if (LOOP_SLEEP.test(blanked))
212
+ return "sleep in a while/until/for loop";
213
+ for (const segment of foregroundSegments(blanked)) {
214
+ const hit = matchSegment(segment);
215
+ if (hit)
216
+ return hit;
217
+ }
218
+ return null;
219
+ }
220
+ function denyMessage(pattern) {
221
+ return `Foreground-hog gate: \`${pattern}\` would hold the session foreground for its full duration. ` + "Re-run this exact command with `run_in_background: true` and act on the completion notification, " + "or use a bounded alternative (e.g. `tail -n 200` instead of `tail -f`).";
222
+ }
223
+ function readStdin() {
224
+ try {
225
+ return readFileSync(0, "utf8");
226
+ } catch {
227
+ return "";
228
+ }
229
+ }
230
+ function allow() {
231
+ process.exit(0);
232
+ }
233
+ function block(reason) {
234
+ process.stdout.write(JSON.stringify({ decision: "block", reason }));
235
+ process.exit(0);
236
+ }
237
+ function main() {
238
+ if (!gateEnabledFromEnv())
239
+ allow();
240
+ const raw = readStdin().trim();
241
+ if (!raw)
242
+ allow();
243
+ let event;
244
+ try {
245
+ event = JSON.parse(raw);
246
+ } catch {
247
+ allow();
248
+ }
249
+ if (event.tool_name !== "Bash")
250
+ allow();
251
+ const input = event.tool_input;
252
+ if (!input || typeof input !== "object")
253
+ allow();
254
+ const { run_in_background: runInBackground, command } = input;
255
+ if (runInBackground === true)
256
+ allow();
257
+ if (typeof command !== "string" || command.length === 0)
258
+ allow();
259
+ const pattern = detectForegroundHog(command);
260
+ if (pattern)
261
+ block(denyMessage(pattern));
262
+ allow();
263
+ }
264
+ main();
@@ -24,10 +24,33 @@ var GATED_MS365_WRITE_TOOLS = new Set([
24
24
  "update-message",
25
25
  "delete-message"
26
26
  ]);
27
+ var KNOWN_SAFE_MS365_READ_TOOLS = new Set([
28
+ "list-files",
29
+ "list-drive-items",
30
+ "get-drive-item",
31
+ "download-bytes",
32
+ "search-files",
33
+ "list-events",
34
+ "get-event",
35
+ "list-calendars",
36
+ "get-calendar",
37
+ "list-messages",
38
+ "get-message",
39
+ "search-mail",
40
+ "list-mail-folders",
41
+ "get-mail-folder",
42
+ "whoami",
43
+ "get-current-user"
44
+ ]);
27
45
  function isGatedMs365Tool(toolName) {
28
46
  if (!toolName.startsWith(TOOL_PREFIX))
29
47
  return false;
30
- return GATED_MS365_WRITE_TOOLS.has(toolName.slice(TOOL_PREFIX.length));
48
+ const bare = toolName.slice(TOOL_PREFIX.length);
49
+ if (bare.length === 0)
50
+ return false;
51
+ if (KNOWN_SAFE_MS365_READ_TOOLS.has(bare))
52
+ return false;
53
+ return true;
31
54
  }
32
55
  function readStdin() {
33
56
  try {
@@ -170,13 +193,12 @@ async function rpcKernel(payload) {
170
193
  });
171
194
  });
172
195
  }
173
- async function approvalLookup(agentUnit, scope, approverSet) {
196
+ async function approvalLookupByRequest(agentUnit, requestId, approverSet) {
174
197
  const res = await rpcKernel({
175
198
  v: 1,
176
- op: "approval_lookup",
199
+ op: "approval_lookup_by_request",
177
200
  agent_unit: agentUnit,
178
- scope,
179
- action: "write",
201
+ request_id: requestId,
180
202
  current_approver_set: approverSet
181
203
  });
182
204
  if (!res.ok)
@@ -203,7 +225,7 @@ async function main() {
203
225
  }
204
226
  const agentName = process.env.SWITCHROOM_AGENT_NAME;
205
227
  if (!agentName) {
206
- allow();
228
+ fail("SWITCHROOM_AGENT_NAME unset \u2014 cannot identify agent to gate write");
207
229
  }
208
230
  const accountEmail = process.env.SWITCHROOM_MICROSOFT_ACCOUNT ?? "(unknown)";
209
231
  const extract = extractMs365Preview(toolName, input.tool_input);
@@ -230,11 +252,11 @@ async function main() {
230
252
  if (!response.ok || !response.requestId) {
231
253
  fail(response.reason ?? "gateway returned ok=false");
232
254
  }
255
+ const requestId = response.requestId;
233
256
  const deadline = response.expiresAtMs ?? Date.now() + HOOK_TIMEOUT_MS;
234
- const scope = `ms-365:write:${preview.itemId}`;
235
257
  while (Date.now() < deadline) {
236
258
  await new Promise((r) => setTimeout(r, KERNEL_POLL_INTERVAL_MS));
237
- const lookup = await approvalLookup(agentName, scope, []);
259
+ const lookup = await approvalLookupByRequest(agentName, requestId, []);
238
260
  if (!lookup)
239
261
  continue;
240
262
  const state = lookup.state;
@@ -255,5 +277,6 @@ if (__require.main == __require.module) {
255
277
  export {
256
278
  isGatedMs365Tool,
257
279
  extractMs365Preview,
280
+ KNOWN_SAFE_MS365_READ_TOOLS,
258
281
  GATED_MS365_WRITE_TOOLS
259
282
  };
@@ -10928,7 +10928,7 @@ function decodeResponse(line) {
10928
10928
  const obj = JSON.parse(line);
10929
10929
  return ResponseSchema.parse(obj);
10930
10930
  }
10931
- var MAX_FRAME_BYTES, AgentNameSchema, GetRequestSchema, PutRequestSchema, ListRequestSchema, MintGrantRequestSchema, ListGrantsRequestSchema, RevokeGrantRequestSchema, StatusRequestSchema, LockRequestSchema, PreflightAccessRequestSchema, OkPreflightAccessResponseSchema, ApprovalRequestRequestSchema, ApprovalLookupRequestSchema, ApprovalConsumeRequestSchema, ApprovalRevokeRequestSchema, ApprovalListRequestSchema, ApprovalDecisionModeSchema, ApprovalRecordRequestSchema, ApprovalConsumeRecordRequestSchema, RequestSchema, VaultEntrySchema, ErrorCode, OkEntryResponseSchema, OkKeysResponseSchema, BrokerStatus, OkStatusResponseSchema, OkLockResponseSchema, OkPutResponseSchema, OkMintGrantResponseSchema, GrantMetaSchema, OkListGrantsResponseSchema, OkRevokeGrantResponseSchema, OkApprovalRequestResponseSchema, ApprovalDecisionMetaSchema, OkApprovalLookupResponseSchema, OkApprovalConsumeResponseSchema, OkApprovalRevokeResponseSchema, OkApprovalListResponseSchema, OkApprovalRecordResponseSchema, OkApprovalConsumeRecordResponseSchema, ErrorResponseSchema, ResponseSchema;
10931
+ var MAX_FRAME_BYTES, AgentNameSchema, GetRequestSchema, PutRequestSchema, ListRequestSchema, MintGrantRequestSchema, ListGrantsRequestSchema, RevokeGrantRequestSchema, StatusRequestSchema, LockRequestSchema, PreflightAccessRequestSchema, OkPreflightAccessResponseSchema, ApprovalRequestRequestSchema, ApprovalLookupRequestSchema, ApprovalLookupByRequestRequestSchema, ApprovalConsumeRequestSchema, ApprovalRevokeRequestSchema, ApprovalListRequestSchema, ApprovalDecisionModeSchema, ApprovalRecordRequestSchema, ApprovalConsumeRecordRequestSchema, RequestSchema, VaultEntrySchema, ErrorCode, OkEntryResponseSchema, OkKeysResponseSchema, BrokerStatus, OkStatusResponseSchema, OkLockResponseSchema, OkPutResponseSchema, OkMintGrantResponseSchema, GrantMetaSchema, OkListGrantsResponseSchema, OkRevokeGrantResponseSchema, OkApprovalRequestResponseSchema, ApprovalDecisionMetaSchema, OkApprovalLookupResponseSchema, OkApprovalConsumeResponseSchema, OkApprovalRevokeResponseSchema, OkApprovalListResponseSchema, OkApprovalRecordResponseSchema, OkApprovalConsumeRecordResponseSchema, ErrorResponseSchema, ResponseSchema;
10932
10932
  var init_protocol = __esm(() => {
10933
10933
  init_zod();
10934
10934
  init_peercred();
@@ -11028,6 +11028,13 @@ var init_protocol = __esm(() => {
11028
11028
  action: exports_external.string().min(1),
11029
11029
  current_approver_set: exports_external.array(exports_external.string())
11030
11030
  });
11031
+ ApprovalLookupByRequestRequestSchema = exports_external.object({
11032
+ v: exports_external.literal(1),
11033
+ op: exports_external.literal("approval_lookup_by_request"),
11034
+ agent_unit: exports_external.string().min(1),
11035
+ request_id: exports_external.string().regex(/^[0-9a-f]{32}$/),
11036
+ current_approver_set: exports_external.array(exports_external.string())
11037
+ });
11031
11038
  ApprovalConsumeRequestSchema = exports_external.object({
11032
11039
  v: exports_external.literal(1),
11033
11040
  op: exports_external.literal("approval_consume"),
@@ -11082,6 +11089,7 @@ var init_protocol = __esm(() => {
11082
11089
  RevokeGrantRequestSchema,
11083
11090
  ApprovalRequestRequestSchema,
11084
11091
  ApprovalLookupRequestSchema,
11092
+ ApprovalLookupByRequestRequestSchema,
11085
11093
  ApprovalConsumeRequestSchema,
11086
11094
  ApprovalRevokeRequestSchema,
11087
11095
  ApprovalListRequestSchema,
@@ -11937,7 +11945,6 @@ var TelegramChannelSchema = exports_external.object({
11937
11945
  enabled: exports_external.boolean().default(true).describe("Master switch for the per-agent Telegram gateway sidecar. " + "When false, start.sh skips the gateway supervise loop and the " + "agent boots without bot-token requirements (smoke-test + " + "offline-dev use case)."),
11938
11946
  plugin: exports_external.enum(["switchroom", "official"]).optional().describe("Which Telegram MCP plugin to load. Default is 'switchroom' \u2014 the " + "enhanced fork with streaming edits, reactions, history, and " + "access control. Set to 'official' for the upstream marketplace " + "plugin (basic send/receive only)."),
11939
11947
  format: exports_external.enum(["html", "markdownv2", "text"]).optional().describe("Default reply format passed to the plugin"),
11940
- rate_limit_ms: exports_external.number().optional().describe("Minimum delay between outgoing messages in ms"),
11941
11948
  stream_mode: exports_external.enum(["pty", "checklist"]).optional().describe("How live progress is streamed to Telegram during a turn. " + "'pty' (default) surfaces text snapshots of Claude Code's TUI \u2014 " + "compatible but can flicker as Ink re-renders. 'checklist' drives " + "a structured progress card from session-tail events \u2014 stable " + "order, per-tool status emojis, fires only on semantic transitions."),
11942
11949
  stream_throttle_ms: exports_external.number().int().nonnegative().optional().describe("Throttle window in ms between successive in-place stream edits " + "during a turn. Lower = more responsive stream, higher = fewer API " + "calls. Floored at 250 by draft-stream itself. Default 400 ms for DMs " + "and 1000 ms for groups/forums (respects Telegram's ~1 edit/sec/message " + "practical ceiling). Override per-agent if a particular agent needs " + "snappier or quieter streaming."),
11943
11950
  clear_status_on_completion: exports_external.boolean().optional().describe("When true, the live activity/status feed (the in-place 'what it's " + "doing' message \u2014 Reading X, Searching the web for Y, \u2026) is DELETED " + "when the turn's final answer lands, so only the reply remains. " + "Default false: the status message is left in the chat as a record " + "(its last step marked done) \u2014 no post-then-delete. Per-agent " + "override; cascades defaults \u2192 profile \u2192 agent (per-key)."),