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.
- package/dist/agent-scheduler/index.js +57 -9
- package/dist/auth-broker/index.js +174 -72
- package/dist/cli/autoaccept-poll.js +23 -0
- package/dist/cli/drive-write-pretool.mjs +24 -1
- package/dist/cli/foreground-hog-pretool.mjs +264 -0
- package/dist/cli/ms-365-write-pretool.mjs +31 -8
- package/dist/cli/notion-write-pretool.mjs +9 -2
- package/dist/cli/skill-validate-pretool.mjs +144 -2847
- package/dist/cli/switchroom.js +986 -3131
- package/dist/host-control/main.js +216 -2863
- package/dist/vault/approvals/kernel-server.js +67 -1
- package/dist/vault/broker/server.js +98 -45
- package/package.json +1 -1
- package/profiles/coding/CLAUDE.md.hbs +2 -0
- package/profiles/default/CLAUDE.md.hbs +2 -0
- package/skills/switchroom-architecture/telegram.md +0 -1
- package/telegram-plugin/auth-snapshot-format.ts +37 -5
- package/telegram-plugin/auto-fallback-fleet.ts +29 -1
- package/telegram-plugin/bridge/bridge.ts +2 -0
- package/telegram-plugin/dist/bridge/bridge.js +51 -3
- package/telegram-plugin/dist/gateway/gateway.js +1251 -2368
- package/telegram-plugin/dist/server.js +67 -3
- package/telegram-plugin/format.ts +19 -0
- package/telegram-plugin/gateway/approval-hold.ts +21 -2
- package/telegram-plugin/gateway/auth-broker-client.ts +1 -0
- package/telegram-plugin/gateway/auth-command.ts +14 -0
- package/telegram-plugin/gateway/callback-query-handlers.ts +12 -0
- package/telegram-plugin/gateway/forward-origin.ts +235 -0
- package/telegram-plugin/gateway/gateway.ts +445 -83
- package/telegram-plugin/gateway/throttle-tier-wiring.ts +268 -0
- package/telegram-plugin/history.ts +106 -6
- package/telegram-plugin/inline-keyboard-callbacks.ts +94 -0
- package/telegram-plugin/model-unavailable.ts +61 -13
- package/telegram-plugin/outbound-field-redact.ts +69 -0
- package/telegram-plugin/render/render.ts +32 -14
- package/telegram-plugin/render/rich-render.ts +40 -32
- package/telegram-plugin/scoped-approval.ts +11 -2
- package/telegram-plugin/secret-detect/chunker.ts +18 -4
- package/telegram-plugin/secret-detect/index.ts +12 -56
- package/telegram-plugin/send-gate-degraded.test.ts +131 -0
- package/telegram-plugin/send-gate.test.ts +25 -6
- package/telegram-plugin/send-gate.ts +82 -8
- package/telegram-plugin/session-tail.ts +82 -7
- package/telegram-plugin/stream-controller.ts +3 -2
- package/telegram-plugin/subagent-watcher.ts +71 -16
- package/telegram-plugin/tests/approval-hold-outcome.test.ts +36 -5
- package/telegram-plugin/tests/auto-fallback-fleet.test.ts +72 -0
- package/telegram-plugin/tests/callback-query-handlers.test.ts +65 -0
- package/telegram-plugin/tests/forward-origin.test.ts +309 -0
- package/telegram-plugin/tests/gateway-outbound-redact.test.ts +57 -0
- package/telegram-plugin/tests/history.test.ts +272 -0
- package/telegram-plugin/tests/inbound-message-types.test.ts +5 -1
- package/telegram-plugin/tests/inline-keyboard-callbacks.test.ts +164 -0
- package/telegram-plugin/tests/operator-events-session-tail.test.ts +74 -0
- package/telegram-plugin/tests/outbound-field-redact.test.ts +107 -0
- package/telegram-plugin/tests/reaction-gate-routing.test.ts +173 -0
- package/telegram-plugin/tests/render/render-outbound-chunks.test.ts +6 -4
- package/telegram-plugin/tests/render/render.test.ts +88 -0
- package/telegram-plugin/tests/render/rich-render.test.ts +41 -22
- package/telegram-plugin/tests/scoped-approval.test.ts +27 -0
- package/telegram-plugin/tests/secret-detect-chunk-overlap.test.ts +65 -0
- package/telegram-plugin/tests/secret-detect-oauth-code.test.ts +5 -4
- package/telegram-plugin/tests/session-tail-sidecar-reap.test.ts +268 -0
- package/telegram-plugin/tests/single-mode-stream-reply.test.ts +5 -3
- package/telegram-plugin/tests/status-accent.test.ts +5 -3
- package/telegram-plugin/tests/stream-controller-chunk-cap.test.ts +20 -20
- package/telegram-plugin/tests/stream-reply-handler.test.ts +5 -2
- package/telegram-plugin/tests/subagent-watcher-fd-leak.test.ts +275 -0
- package/telegram-plugin/tests/throttle-tier-wiring.test.ts +290 -0
- package/telegram-plugin/tests/throttle-tier.test.ts +278 -0
- package/telegram-plugin/tests/worktree-watch-cwds.test.ts +215 -1
- package/telegram-plugin/throttle-tier.ts +226 -0
- package/telegram-plugin/uat/scenarios/jtbd-rich-formatting-render-dm.test.ts +8 -7
- package/telegram-plugin/worktree-watch-cwds.ts +194 -5
- package/telegram-plugin/secret-detect/secretlint-source.ts +0 -95
- 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
|
-
|
|
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
|
|
196
|
+
async function approvalLookupByRequest(agentUnit, requestId, approverSet) {
|
|
174
197
|
const res = await rpcKernel({
|
|
175
198
|
v: 1,
|
|
176
|
-
op: "
|
|
199
|
+
op: "approval_lookup_by_request",
|
|
177
200
|
agent_unit: agentUnit,
|
|
178
|
-
|
|
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
|
-
|
|
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
|
|
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)."),
|