switchroom 0.15.36 → 0.15.38
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 +10 -9
- package/dist/auth-broker/index.js +9 -9
- package/dist/cli/autoaccept-poll.js +13 -7
- package/dist/cli/notion-write-pretool.mjs +9 -9
- package/dist/cli/switchroom.js +480 -217
- package/dist/cli/ui/index.html +87 -17
- package/dist/host-control/main.js +10 -10
- package/dist/vault/approvals/kernel-server.js +9 -9
- package/dist/vault/broker/server.js +9 -9
- package/package.json +1 -1
- package/profiles/_base/cron-session.sh.hbs +1 -1
- package/profiles/_base/start.sh.hbs +1 -1
- package/profiles/_shared/agent-self-service.md.hbs +25 -0
- package/skills/switchroom-manage/SKILL.md +1 -1
- package/skills/switchroom-runtime/SKILL.md +1 -1
- package/telegram-plugin/answer-stream.ts +1 -1
- package/telegram-plugin/bridge/bridge.ts +50 -1
- package/telegram-plugin/bridge/ipc-client.ts +4 -1
- package/telegram-plugin/bridge/tool-filter.ts +77 -0
- package/telegram-plugin/chat-lock.ts +1 -1
- package/telegram-plugin/credits-watch.ts +1 -1
- package/telegram-plugin/dist/bridge/bridge.js +60 -3
- package/telegram-plugin/dist/gateway/gateway.js +753 -207
- package/telegram-plugin/dist/server.js +64 -4
- package/telegram-plugin/gateway/auto-classify-mid-turn.ts +1 -1
- package/telegram-plugin/gateway/boot-card.ts +5 -1
- package/telegram-plugin/gateway/boot-probes.ts +62 -0
- package/telegram-plugin/gateway/cron-session.ts +1 -1
- package/telegram-plugin/gateway/gateway.ts +254 -15
- package/telegram-plugin/gateway/grant-restart.ts +1 -1
- package/telegram-plugin/gateway/inbound-delivery-machine-dispatch.ts +1 -1
- package/telegram-plugin/gateway/inbound-delivery-machine-shadow.ts +1 -1
- package/telegram-plugin/gateway/inbound-delivery-machine.ts +1 -1
- package/telegram-plugin/gateway/interrupt-defer.ts +1 -1
- package/telegram-plugin/gateway/ipc-protocol.ts +12 -0
- package/telegram-plugin/gateway/linear-activity.ts +56 -0
- package/telegram-plugin/gateway/linear-auth-watch.ts +102 -0
- package/telegram-plugin/gateway/linear-setup.ts +196 -0
- package/telegram-plugin/gateway/permission-card-origin.ts +62 -0
- package/telegram-plugin/gateway/permission-timeout.ts +70 -0
- package/telegram-plugin/gateway/prefix-warmup.ts +1 -1
- package/telegram-plugin/gateway/webhook-ingest-server.test.ts +1 -1
- package/telegram-plugin/gateway/webhook-ingest-server.ts +1 -1
- package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +1 -1
- package/telegram-plugin/interrupt-marker.ts +1 -1
- package/telegram-plugin/over-ping-safety-net.ts +1 -1
- package/telegram-plugin/scoped-approval.ts +1 -1
- package/telegram-plugin/secret-detect/vault-error.ts +1 -1
- package/telegram-plugin/silence-poke.ts +2 -2
- package/telegram-plugin/silent-reply-anchor.ts +1 -1
- package/telegram-plugin/slot-banner-driver.ts +1 -1
- package/telegram-plugin/startup-reset.ts +1 -1
- package/telegram-plugin/tests/boot-probes-connections.test.ts +66 -0
- package/telegram-plugin/tests/gateway-startup-reset.test.ts +1 -1
- package/telegram-plugin/tests/inbound-delivery-machine.test.ts +1 -1
- package/telegram-plugin/tests/linear-agent-activity.test.ts +77 -0
- package/telegram-plugin/tests/linear-agent-setup.test.ts +132 -0
- package/telegram-plugin/tests/linear-auth-watch.test.ts +79 -0
- package/telegram-plugin/tests/linear-create-issue.test.ts +3 -1
- package/telegram-plugin/tests/permission-card-origin.test.ts +97 -0
- package/telegram-plugin/tests/permission-card-routing.test.ts +23 -0
- package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +76 -0
- package/telegram-plugin/tests/permission-timeout.test.ts +87 -0
- package/telegram-plugin/tests/scoped-approval.test.ts +1 -1
- package/telegram-plugin/tests/silence-poke.test.ts +1 -1
- package/telegram-plugin/tests/tool-filter.test.ts +87 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +1 -1
- package/telegram-plugin/turn-flush-safety.ts +1 -1
- package/telegram-plugin/uat/assertions.ts +1 -1
- package/telegram-plugin/uat/scenarios/bg-sub-agent-dispatch-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/fuzz-extended-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-fast-ack-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-fast-trivial-dm.test.ts +2 -2
- package/telegram-plugin/uat/scenarios/jtbd-forwarded-burst-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-memory-survives-restart-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-rapid-followup-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-reflective-status-reaction-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-wake-audit-content-dm.test.ts +1 -1
|
@@ -23809,7 +23809,7 @@ function validateGatewayMessage(msg) {
|
|
|
23809
23809
|
case "inbound":
|
|
23810
23810
|
return typeof m.chatId === "string" && typeof m.text === "string";
|
|
23811
23811
|
case "permission":
|
|
23812
|
-
return typeof m.requestId === "string" && (m.behavior === "allow" || m.behavior === "deny") && (m.rule === undefined || typeof m.rule === "string" && m.rule.length > 0);
|
|
23812
|
+
return typeof m.requestId === "string" && (m.behavior === "allow" || m.behavior === "deny") && (m.rule === undefined || typeof m.rule === "string" && m.rule.length > 0) && (m.message === undefined || typeof m.message === "string" && m.message.length > 0);
|
|
23813
23813
|
case "status":
|
|
23814
23814
|
return typeof m.status === "string";
|
|
23815
23815
|
case "tool_call_result":
|
|
@@ -24047,6 +24047,28 @@ function createIpcClient(options) {
|
|
|
24047
24047
|
}
|
|
24048
24048
|
var init_ipc_client = () => {};
|
|
24049
24049
|
|
|
24050
|
+
// bridge/tool-filter.ts
|
|
24051
|
+
function buildEffectiveToolSchemas(schemas4, opts) {
|
|
24052
|
+
return schemas4.filter((t) => opts.linearEnabled || !LINEAR_TOOLS.has(t.name)).map((t) => ALWAYS_LOAD_TOOLS.has(t.name) ? { ...t, _meta: { "anthropic/alwaysLoad": true } } : t);
|
|
24053
|
+
}
|
|
24054
|
+
var ALWAYS_LOAD_TOOLS, LINEAR_TOOLS, LINEAR_ENV = "SWITCHROOM_TELEGRAM_LINEAR";
|
|
24055
|
+
var init_tool_filter = __esm(() => {
|
|
24056
|
+
ALWAYS_LOAD_TOOLS = new Set([
|
|
24057
|
+
"reply",
|
|
24058
|
+
"stream_reply",
|
|
24059
|
+
"get_recent_messages",
|
|
24060
|
+
"react",
|
|
24061
|
+
"edit_message",
|
|
24062
|
+
"send_typing",
|
|
24063
|
+
"download_attachment"
|
|
24064
|
+
]);
|
|
24065
|
+
LINEAR_TOOLS = new Set([
|
|
24066
|
+
"linear_agent_activity",
|
|
24067
|
+
"linear_create_issue",
|
|
24068
|
+
"linear_agent_setup"
|
|
24069
|
+
]);
|
|
24070
|
+
});
|
|
24071
|
+
|
|
24050
24072
|
// permission-rule.ts
|
|
24051
24073
|
import { basename as basename2 } from "node:path";
|
|
24052
24074
|
function resolveSkillName(input) {
|
|
@@ -24171,7 +24193,8 @@ function onPermission(msg) {
|
|
|
24171
24193
|
method: "notifications/claude/channel/permission",
|
|
24172
24194
|
params: {
|
|
24173
24195
|
request_id: msg.requestId,
|
|
24174
|
-
behavior: msg.behavior
|
|
24196
|
+
behavior: msg.behavior,
|
|
24197
|
+
...msg.message ? { message: msg.message } : {}
|
|
24175
24198
|
}
|
|
24176
24199
|
}).catch((err) => {
|
|
24177
24200
|
process.stderr.write(`telegram bridge: failed to deliver permission to Claude: ${err}
|
|
@@ -24232,7 +24255,7 @@ async function main() {
|
|
|
24232
24255
|
}
|
|
24233
24256
|
await mcp.connect(new StdioServerTransport);
|
|
24234
24257
|
}
|
|
24235
|
-
var STATE_DIR, SOCKET_PATH, TOPIC_ID, AGENT_NAME, mcp, TOOL_SCHEMAS, sessionAllowRules, ipc = null, sessionTailEnabled, sessionTailHandle = null, ptyTailEnabled, ptyTailHandle = null;
|
|
24258
|
+
var STATE_DIR, SOCKET_PATH, TOPIC_ID, AGENT_NAME, mcp, TOOL_SCHEMAS, EFFECTIVE_TOOL_SCHEMAS, sessionAllowRules, ipc = null, sessionTailEnabled, sessionTailHandle = null, ptyTailEnabled, ptyTailHandle = null;
|
|
24236
24259
|
var init_bridge = __esm(async () => {
|
|
24237
24260
|
init_server2();
|
|
24238
24261
|
init_stdio2();
|
|
@@ -24242,6 +24265,7 @@ var init_bridge = __esm(async () => {
|
|
|
24242
24265
|
init_session_tail();
|
|
24243
24266
|
init_pty_tail();
|
|
24244
24267
|
init_ipc_client();
|
|
24268
|
+
init_tool_filter();
|
|
24245
24269
|
init_permission_rule();
|
|
24246
24270
|
installPluginLogger2();
|
|
24247
24271
|
STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join5(homedir4(), ".claude", "channels", "telegram");
|
|
@@ -24684,9 +24708,45 @@ var init_bridge = __esm(async () => {
|
|
|
24684
24708
|
},
|
|
24685
24709
|
required: ["title", "body"]
|
|
24686
24710
|
}
|
|
24711
|
+
},
|
|
24712
|
+
{
|
|
24713
|
+
name: "linear_agent_setup",
|
|
24714
|
+
description: 'Provision THIS agent as a Linear app actor (actor=app OAuth) from inside the container \u2014 the operator-approved in-container path that replaces the host-only `switchroom linear-agent setup` (which silently no-ops in a sandbox). Two steps. action="authorize_url": pass the OAuth app client_id + redirect_uri; returns the browser URL the operator opens to consent. action="complete": pass client_id, client_secret, redirect_uri, and the code from the redirect; exchanges it and stores the access token (linear/<agent>/token) + the durable refresh bundle (linear/<agent>/oauth) via the vault broker so the token auto-renews. Writing those NEW keys needs a write-grant \u2014 if the broker denies, the tool returns the exact vault_request_access calls to make (operator approves), then re-run "complete". After it stores the values, follow the returned guidance to config_propose_edit the linear_agent block + secrets[] ACL (also operator-approved) to make it durable. The client_secret and code are used in-process only \u2014 never stored in config or logged.',
|
|
24715
|
+
inputSchema: {
|
|
24716
|
+
type: "object",
|
|
24717
|
+
properties: {
|
|
24718
|
+
action: {
|
|
24719
|
+
type: "string",
|
|
24720
|
+
enum: ["authorize_url", "complete"],
|
|
24721
|
+
description: '"authorize_url" to get the browser consent URL; "complete" to exchange the code and store the credentials.'
|
|
24722
|
+
},
|
|
24723
|
+
client_id: {
|
|
24724
|
+
type: "string",
|
|
24725
|
+
description: "Linear OAuth app client id (from Linear \u2192 Settings \u2192 API \u2192 your agent app)."
|
|
24726
|
+
},
|
|
24727
|
+
redirect_uri: {
|
|
24728
|
+
type: "string",
|
|
24729
|
+
description: "The redirect URI registered on the Linear OAuth app (e.g. http://localhost:3000/callback). Must match exactly in both steps."
|
|
24730
|
+
},
|
|
24731
|
+
client_secret: {
|
|
24732
|
+
type: "string",
|
|
24733
|
+
description: 'Linear OAuth app client secret. Required for action="complete"; used in-process for the token exchange, never stored or logged.'
|
|
24734
|
+
},
|
|
24735
|
+
code: {
|
|
24736
|
+
type: "string",
|
|
24737
|
+
description: 'The authorization code from the redirect URL (the `code=` query param). Required for action="complete"; single-use.'
|
|
24738
|
+
}
|
|
24739
|
+
},
|
|
24740
|
+
required: ["action", "client_id", "redirect_uri"]
|
|
24741
|
+
}
|
|
24687
24742
|
}
|
|
24688
24743
|
];
|
|
24689
|
-
|
|
24744
|
+
EFFECTIVE_TOOL_SCHEMAS = buildEffectiveToolSchemas(TOOL_SCHEMAS, {
|
|
24745
|
+
linearEnabled: process.env[LINEAR_ENV] === "1"
|
|
24746
|
+
});
|
|
24747
|
+
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
24748
|
+
tools: EFFECTIVE_TOOL_SCHEMAS
|
|
24749
|
+
}));
|
|
24690
24750
|
mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
24691
24751
|
const tool = req.params.name;
|
|
24692
24752
|
const args = req.params.arguments ?? {};
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Today a no-prefix mid-turn message always QUEUES (the default flipped
|
|
9
9
|
* 2026-04-17 away from the blunt "everything steers" — see
|
|
10
|
-
* reference/steer-or-queue-mid-flight.md). This module is the basis for a
|
|
10
|
+
* reference/jobs/steer-or-queue-mid-flight.md). This module is the basis for a
|
|
11
11
|
* smarter default. It ships first in SHADOW mode (the gateway logs what it WOULD
|
|
12
12
|
* decide but still queues), to gather real-world data — how often mid-turn
|
|
13
13
|
* messages are same-topic continuations vs cross-topic new tasks, and the
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
probeBroker,
|
|
47
47
|
probeKernel,
|
|
48
48
|
probeSkills,
|
|
49
|
+
probeConnections,
|
|
49
50
|
watchAgentProcess,
|
|
50
51
|
AGENT_LIVE_WINDOW_MS,
|
|
51
52
|
AGENT_LIVE_POLL_INTERVAL_MS,
|
|
@@ -120,6 +121,7 @@ export type ProbeKey =
|
|
|
120
121
|
| 'broker'
|
|
121
122
|
| 'kernel'
|
|
122
123
|
| 'skills'
|
|
124
|
+
| 'connections'
|
|
123
125
|
|
|
124
126
|
export type ProbeMap = Partial<Record<ProbeKey, ProbeResult | null>>
|
|
125
127
|
|
|
@@ -253,11 +255,12 @@ const PROBE_LABELS: Record<ProbeKey, string> = {
|
|
|
253
255
|
broker: 'Broker',
|
|
254
256
|
kernel: 'Kernel',
|
|
255
257
|
skills: 'Skills',
|
|
258
|
+
connections: 'Connections',
|
|
256
259
|
}
|
|
257
260
|
|
|
258
261
|
const PROBE_KEYS: ReadonlyArray<ProbeKey> = [
|
|
259
262
|
'account', 'agent', 'gateway', 'quota', 'hindsight',
|
|
260
|
-
'scheduler', 'broker', 'kernel', 'skills',
|
|
263
|
+
'scheduler', 'broker', 'kernel', 'skills', 'connections',
|
|
261
264
|
]
|
|
262
265
|
|
|
263
266
|
const REASON_EMOJI: Record<RestartReason, string> = {
|
|
@@ -617,6 +620,7 @@ export async function runAllProbes(opts: RunProbesOpts): Promise<ProbeMap> {
|
|
|
617
620
|
probeBroker(undefined, { dockerMode: opts.dockerMode }).then(r => { probes.broker = r }),
|
|
618
621
|
probeKernel(undefined, { dockerMode: opts.dockerMode }).then(r => { probes.kernel = r }),
|
|
619
622
|
probeSkills(opts.agentDir, { agentName: opts.agentSlug ?? opts.agentName }).then(r => { probes.skills = r }),
|
|
623
|
+
probeConnections(opts.agentDir).then(r => { probes.connections = r }),
|
|
620
624
|
])
|
|
621
625
|
|
|
622
626
|
return probes
|
|
@@ -1421,6 +1421,68 @@ function renderBucketedSkills(switchroom: string[], agent: string[]): string {
|
|
|
1421
1421
|
return parts.length === 0 ? 'none resolved' : parts.join(' · ')
|
|
1422
1422
|
}
|
|
1423
1423
|
|
|
1424
|
+
// ─── Probe: Connections (configured-but-unauthed MCP integrations) ───────────
|
|
1425
|
+
|
|
1426
|
+
/**
|
|
1427
|
+
* Surface configured-but-unauthed MCP connections at agent start. The auth
|
|
1428
|
+
* verdict can't be computed in-container (this boot probe must not do
|
|
1429
|
+
* vault/grant work — see the module header), so `switchroom apply` computes
|
|
1430
|
+
* it host-side and drops a snapshot at
|
|
1431
|
+
* `<agentDir>/.claude/connection-health.json` (src/agents/connection-health.ts).
|
|
1432
|
+
* This probe just reads it.
|
|
1433
|
+
*
|
|
1434
|
+
* ok — snapshot missing/unparseable (not yet computed → assume
|
|
1435
|
+
* healthy, don't nag) OR zero issues
|
|
1436
|
+
* degraded — ≥1 connection configured but not authed; detail names the
|
|
1437
|
+
* servers, nextStep carries the first fix
|
|
1438
|
+
*
|
|
1439
|
+
* Never `fail`: an unauthed integration degrades that one capability, it
|
|
1440
|
+
* doesn't take the agent down, and the silent-when-healthy boot card
|
|
1441
|
+
* should not red an agent over a missing third-party token.
|
|
1442
|
+
*/
|
|
1443
|
+
export interface ConnectionIssueShape {
|
|
1444
|
+
server: string
|
|
1445
|
+
key: string
|
|
1446
|
+
kind: string
|
|
1447
|
+
detail: string
|
|
1448
|
+
fix: string
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
export async function probeConnections(
|
|
1452
|
+
agentDir: string,
|
|
1453
|
+
opts: { readFileImpl?: (path: string) => string } = {},
|
|
1454
|
+
): Promise<ProbeResult> {
|
|
1455
|
+
return withTimeout('Connections', (async (): Promise<ProbeResult> => {
|
|
1456
|
+
const path = join(agentDir, '.claude', 'connection-health.json')
|
|
1457
|
+
const read = opts.readFileImpl ?? ((p: string) => readFileSync(p, 'utf8'))
|
|
1458
|
+
let issues: ConnectionIssueShape[] = []
|
|
1459
|
+
try {
|
|
1460
|
+
const parsed = JSON.parse(read(path)) as { issues?: ConnectionIssueShape[] }
|
|
1461
|
+
issues = Array.isArray(parsed.issues) ? parsed.issues : []
|
|
1462
|
+
} catch {
|
|
1463
|
+
// ENOENT (never applied with this build) or malformed — assume healthy.
|
|
1464
|
+
return { status: 'ok', label: 'Connections', detail: 'no issues' }
|
|
1465
|
+
}
|
|
1466
|
+
if (issues.length === 0) {
|
|
1467
|
+
return { status: 'ok', label: 'Connections', detail: 'all authed' }
|
|
1468
|
+
}
|
|
1469
|
+
const servers = [...new Set(issues.map((i) => i.server))]
|
|
1470
|
+
const named = servers.slice(0, 4).join(', ')
|
|
1471
|
+
const more = servers.length > 4 ? ` +${servers.length - 4} more` : ''
|
|
1472
|
+
const first = issues[0]
|
|
1473
|
+
const extra =
|
|
1474
|
+
issues.length > 1
|
|
1475
|
+
? ` (+${issues.length - 1} more — run \`switchroom doctor\`)`
|
|
1476
|
+
: ''
|
|
1477
|
+
return {
|
|
1478
|
+
status: 'degraded',
|
|
1479
|
+
label: 'Connections',
|
|
1480
|
+
detail: `${servers.length} integration(s) configured but not authed: ${named}${more}`,
|
|
1481
|
+
nextStep: `${first.fix}${extra}`,
|
|
1482
|
+
}
|
|
1483
|
+
})())
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1424
1486
|
export interface SkillsFsImpl {
|
|
1425
1487
|
readdir: (p: string) => string[]
|
|
1426
1488
|
exists: (p: string) => boolean
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cheap-cron session identity —
|
|
2
|
+
* Cheap-cron session identity — reference/rfcs/cheap-cron-sessions.md §3.3.
|
|
3
3
|
*
|
|
4
4
|
* Rather than rekey the gateway's hardened single-bridge machinery
|
|
5
5
|
* (agentIndex / pendingInboundBuffer / handleRegister, each carrying
|