switchroom 0.11.1 → 0.12.1
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/README.md +32 -16
- package/dist/agent-scheduler/index.js +216 -97
- package/dist/auth-broker/index.js +176 -97
- package/dist/cli/drive-write-pretool.mjs +26 -11
- package/dist/cli/skill-validate-pretool.mjs +7209 -0
- package/dist/cli/switchroom.js +45571 -42642
- package/dist/cli/ui/index.html +1281 -0
- package/dist/host-control/main.js +3628 -309
- package/dist/vault/approvals/kernel-server.js +207 -98
- package/dist/vault/broker/server.js +249 -119
- package/examples/personal-google-workspace-mcp/README.md +8 -3
- package/examples/switchroom.yaml +91 -42
- package/package.json +4 -3
- package/profiles/_base/start.sh.hbs +76 -36
- package/profiles/_shared/agent-self-service.md.hbs +1 -1
- package/profiles/default/CLAUDE.md.hbs +4 -2
- package/skills/file-bug/SKILL.md +6 -4
- package/skills/skill-creator/SKILL.md +52 -0
- package/skills/switchroom-cli/SKILL.md +20 -4
- package/skills/switchroom-install/SKILL.md +3 -3
- package/telegram-plugin/auth-snapshot-format.ts +9 -9
- package/telegram-plugin/card-format.ts +3 -3
- package/telegram-plugin/dist/bridge/bridge.js +112 -112
- package/telegram-plugin/dist/gateway/gateway.js +853 -414
- package/telegram-plugin/dist/server.js +162 -161
- package/telegram-plugin/format.ts +71 -0
- package/telegram-plugin/gateway/access-validator.test.ts +8 -8
- package/telegram-plugin/gateway/access-validator.ts +1 -1
- package/telegram-plugin/gateway/approval-card.test.ts +18 -18
- package/telegram-plugin/gateway/approval-card.ts +1 -1
- package/telegram-plugin/gateway/auth-command.ts +2 -2
- package/telegram-plugin/gateway/boot-card.ts +40 -3
- package/telegram-plugin/gateway/boot-probes.ts +114 -30
- package/telegram-plugin/gateway/diff-preview-card.test.ts +15 -15
- package/telegram-plugin/gateway/diff-preview-card.ts +1 -1
- package/telegram-plugin/gateway/drive-write-approval.test.ts +2 -2
- package/telegram-plugin/gateway/gateway.ts +265 -22
- package/telegram-plugin/gateway/update-announce.ts +167 -0
- package/telegram-plugin/quota-check.ts +0 -195
- package/telegram-plugin/recent-outbound-dedup.ts +1 -1
- package/telegram-plugin/registry/turns-schema.ts +1 -1
- package/telegram-plugin/retry-api-call.ts +24 -0
- package/telegram-plugin/server.ts +8 -5
- package/telegram-plugin/tests/auth-add-flow.test.ts +32 -3
- package/telegram-plugin/tests/auth-command-format2.test.ts +4 -4
- package/telegram-plugin/tests/auth-snapshot-format.test.ts +17 -17
- package/telegram-plugin/tests/auto-fallback-fleet.test.ts +10 -10
- package/telegram-plugin/tests/boot-probes.test.ts +90 -2
- package/telegram-plugin/tests/bot-runtime.test.ts +23 -1
- package/telegram-plugin/tests/fixtures/service-log-current-claude-code.bin +1 -1
- package/telegram-plugin/tests/fleet-state.test.ts +3 -2
- package/telegram-plugin/tests/quota-check.test.ts +0 -409
- package/telegram-plugin/tests/retry-api-call.test.ts +76 -0
- package/telegram-plugin/tests/secret-detect-audit.test.ts +1 -1
- package/telegram-plugin/tests/secret-detect-pipeline.test.ts +7 -6
- package/telegram-plugin/tests/secret-detect-suppressor-no-silent-allow.test.ts +6 -5
- package/telegram-plugin/tests/secret-detect.test.ts +8 -8
- package/telegram-plugin/tests/telegram-format.test.ts +84 -1
- package/telegram-plugin/tests/update-announce.test.ts +154 -0
- package/telegram-plugin/tests/vault-grant-inbound-builders.test.ts +8 -8
- package/telegram-plugin/tests/vault-request-access-tool.test.ts +51 -0
- package/telegram-plugin/welcome-text.ts +1 -8
- package/profiles/default/CLAUDE.md +0 -192
- package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/base.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/generate_report.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/improve_description.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/run_eval.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/run_loop.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/utils.cpython-313.pyc +0 -0
- package/telegram-plugin/first-paint.ts +0 -225
- package/telegram-plugin/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
- package/telegram-plugin/server.js +0 -41795
- package/telegram-plugin/tests/html-balanced.ts +0 -63
- package/telegram-plugin/tests/snapshot-serializer.ts +0 -79
- package/telegram-plugin/tool-error-filter.ts +0 -89
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tiny HTML tag-balance validator for the Telegram allowlist.
|
|
3
|
-
*
|
|
4
|
-
* Replaces fast-check (not a current dep, see #662 P1) — paired with
|
|
5
|
-
* vitest `it.each` it gives us property-style coverage across many
|
|
6
|
-
* randomised renderer inputs without a new dependency.
|
|
7
|
-
*
|
|
8
|
-
* Telegram's HTML parser only accepts a small allowlist of tags
|
|
9
|
-
* (https://core.telegram.org/bots/api#html-style). Anything outside
|
|
10
|
-
* the allowlist is treated as raw text — we don't try to validate it.
|
|
11
|
-
*
|
|
12
|
-
* Self-closing tags inside Telegram's allowlist: only `<br/>` (and even
|
|
13
|
-
* that is normalised to `\n`). We treat any `<x/>` as self-closing for
|
|
14
|
-
* robustness, but emit nothing into the stack.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
const ALLOWED = new Set([
|
|
18
|
-
'b', 'strong',
|
|
19
|
-
'i', 'em',
|
|
20
|
-
'u', 'ins',
|
|
21
|
-
's', 'strike', 'del',
|
|
22
|
-
'a',
|
|
23
|
-
'code',
|
|
24
|
-
'pre',
|
|
25
|
-
'tg-spoiler',
|
|
26
|
-
'span',
|
|
27
|
-
'tg-emoji',
|
|
28
|
-
'blockquote',
|
|
29
|
-
'br',
|
|
30
|
-
])
|
|
31
|
-
|
|
32
|
-
export interface BalanceResult {
|
|
33
|
-
balanced: boolean
|
|
34
|
-
openTags: string[]
|
|
35
|
-
extraCloses: string[]
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function isBalancedHtml(html: string): BalanceResult {
|
|
39
|
-
const stack: string[] = []
|
|
40
|
-
const extraCloses: string[] = []
|
|
41
|
-
// Match tags but skip HTML entities (`<` etc) — those are NOT tags.
|
|
42
|
-
const re = /<\/?\s*([A-Za-z][A-Za-z0-9-]*)([^>]*)>/g
|
|
43
|
-
let m: RegExpExecArray | null
|
|
44
|
-
while ((m = re.exec(html)) !== null) {
|
|
45
|
-
const raw = m[0]
|
|
46
|
-
const name = m[1].toLowerCase()
|
|
47
|
-
if (!ALLOWED.has(name)) continue
|
|
48
|
-
const isClose = raw.startsWith('</')
|
|
49
|
-
const selfClose = raw.endsWith('/>') || name === 'br'
|
|
50
|
-
if (selfClose && !isClose) continue
|
|
51
|
-
if (isClose) {
|
|
52
|
-
const top = stack[stack.length - 1]
|
|
53
|
-
if (top === name) {
|
|
54
|
-
stack.pop()
|
|
55
|
-
} else {
|
|
56
|
-
extraCloses.push(name)
|
|
57
|
-
}
|
|
58
|
-
} else {
|
|
59
|
-
stack.push(name)
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return { balanced: stack.length === 0 && extraCloses.length === 0, openTags: stack, extraCloses }
|
|
63
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Normalizing serializer for HTML / progress-card snapshot tests.
|
|
3
|
-
*
|
|
4
|
-
* Problem: the raw progress-card render contains
|
|
5
|
-
* - elapsed-time strings like "12s elapsed" / "1m34s"
|
|
6
|
-
* - ISO timestamps like "2026-04-18T12:34:56.789Z"
|
|
7
|
-
* - message-ids, toolUseIds, runtime-generated UUIDs
|
|
8
|
-
*
|
|
9
|
-
* Any of these flip on every test run, so a naive snapshot rots the
|
|
10
|
-
* moment it lands. This serializer canonicalizes those fields to stable
|
|
11
|
-
* tokens (`<ELAPSED>`, `<TIME>`, `<ID>`, `<TOOL_USE_ID>`) so the
|
|
12
|
-
* snapshot diff reflects *semantic* changes only.
|
|
13
|
-
*
|
|
14
|
-
* Use by passing `snapshotSerializer` to vitest's `expect.addSnapshotSerializer`
|
|
15
|
-
* in a test's `beforeAll`, or globally via `vitest.config.ts`
|
|
16
|
-
* (`test.snapshotSerializers`).
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
const PATTERNS: Array<{ re: RegExp; replacement: string }> = [
|
|
20
|
-
// ISO8601 timestamps
|
|
21
|
-
{ re: /\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z\b/g, replacement: '<TIME>' },
|
|
22
|
-
// Unix seconds (10 digits) — only within JSON-like contexts, prefixed by `date":` or `"date":`
|
|
23
|
-
{ re: /"date"\s*:\s*\d{10}\b/g, replacement: '"date":<UNIX_SEC>' },
|
|
24
|
-
// Elapsed-time strings rendered by progress-card (hhh? mm ss).
|
|
25
|
-
// Match "NNs elapsed", "NNmNNs", "NNhNNm".
|
|
26
|
-
{ re: /\b\d+m\d+s\b/g, replacement: '<ELAPSED>' },
|
|
27
|
-
{ re: /\b\d+h\d+m\b/g, replacement: '<ELAPSED>' },
|
|
28
|
-
{ re: /\b\d+s\s+elapsed\b/g, replacement: '<ELAPSED> elapsed' },
|
|
29
|
-
// Claude Code tool_use ids: `toolu_01ABC...` (base62, 20+ chars).
|
|
30
|
-
{ re: /\btoolu_[A-Za-z0-9_]{10,}\b/g, replacement: '<TOOL_USE_ID>' },
|
|
31
|
-
// grammy/telegram file_ids: long alphanumerics starting with AgACAgI / BQACAgI etc.
|
|
32
|
-
{ re: /\b(?:AgAC|BQAC|CQAC|DQAC)[A-Za-z0-9_-]{10,}\b/g, replacement: '<FILE_ID>' },
|
|
33
|
-
// UUIDs
|
|
34
|
-
{ re: /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/g, replacement: '<UUID>' },
|
|
35
|
-
]
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Apply all normalizing replacements. Safe to call on any string —
|
|
39
|
-
* non-matching content passes through unchanged.
|
|
40
|
-
*/
|
|
41
|
-
export function normalizeSnapshot(input: string): string {
|
|
42
|
-
let out = input
|
|
43
|
-
for (const { re, replacement } of PATTERNS) {
|
|
44
|
-
out = out.replace(re, replacement)
|
|
45
|
-
}
|
|
46
|
-
return out
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Vitest snapshot serializer compatible with `expect.addSnapshotSerializer`.
|
|
51
|
-
*
|
|
52
|
-
* Only applies to string values that clearly look like HTML / Telegram
|
|
53
|
-
* rendered content. Everything else passes through without interference
|
|
54
|
-
* so we don't accidentally mangle unrelated assertions.
|
|
55
|
-
*/
|
|
56
|
-
export const snapshotSerializer = {
|
|
57
|
-
test(val: unknown): boolean {
|
|
58
|
-
if (typeof val !== 'string') return false
|
|
59
|
-
// Cheap pre-filter: only normalize when at least one pattern would hit.
|
|
60
|
-
for (const { re } of PATTERNS) {
|
|
61
|
-
re.lastIndex = 0
|
|
62
|
-
if (re.test(val)) {
|
|
63
|
-
re.lastIndex = 0
|
|
64
|
-
return true
|
|
65
|
-
}
|
|
66
|
-
re.lastIndex = 0
|
|
67
|
-
}
|
|
68
|
-
return false
|
|
69
|
-
},
|
|
70
|
-
serialize(val: unknown): string {
|
|
71
|
-
return JSON.stringify(normalizeSnapshot(val as string))
|
|
72
|
-
},
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Convenience: normalize + stringify with stable indent. Good for
|
|
76
|
-
* inline-snapshot-style assertions without invoking the serializer. */
|
|
77
|
-
export function stableHtmlSnapshot(html: string): string {
|
|
78
|
-
return normalizeSnapshot(html)
|
|
79
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* tool-error-filter.ts — severity classifier for tool_result isError events.
|
|
3
|
-
*
|
|
4
|
-
* Acceptance item 5: benign tool failures (no-match, file-not-found,
|
|
5
|
-
* recoverable Telegram errors) don't surface as raw debug in the progress
|
|
6
|
-
* card checklist. Real failures (auth, crash, network) still escalate.
|
|
7
|
-
*
|
|
8
|
-
* Design: pure function, no side effects, no dependencies. The gateway
|
|
9
|
-
* calls isBenignToolError() on every tool_result with isError=true; when
|
|
10
|
-
* it returns true, the progress-card item is marked 'done' (✅) rather
|
|
11
|
-
* than 'failed' (❌) and no operator notification is raised.
|
|
12
|
-
*
|
|
13
|
-
* Important: this classification applies to the DISPLAY only. The agent
|
|
14
|
-
* session transcript is never mutated — the raw error text still reaches
|
|
15
|
-
* the model so it can reason about what happened.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Pattern groups for benign tool errors.
|
|
20
|
-
*
|
|
21
|
-
* These are errors that represent "no results" or "resource absent" — the
|
|
22
|
-
* tool ran correctly but found nothing. Surfacing them as ❌ in the checklist
|
|
23
|
-
* is noise: the user can't act on "grep found nothing" and the agent will
|
|
24
|
-
* handle it in context.
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
// File-not-found patterns (common across Bash, Read, Edit tools)
|
|
28
|
-
const FILE_NOT_FOUND_RE =
|
|
29
|
-
/no such file or directory|file not found|path does not exist|enoent/i
|
|
30
|
-
|
|
31
|
-
// No-match patterns (grep, find, search tools)
|
|
32
|
-
const NO_MATCH_RE =
|
|
33
|
-
/no match(es)? found|returned no results?|not found in|0 result/i
|
|
34
|
-
|
|
35
|
-
// Recoverable Telegram API patterns (message deleted, not modified, etc.)
|
|
36
|
-
const TELEGRAM_RECOVERABLE_RE =
|
|
37
|
-
/message (is not modified|to edit not found|can't be deleted|was deleted)|MESSAGE_ID_INVALID|message not found/i
|
|
38
|
-
|
|
39
|
-
// "Not a git repository" — narrow tool-setup pattern. Earlier drafts also
|
|
40
|
-
// matched `command not found` and `permission denied` but those were too
|
|
41
|
-
// broad: a real EACCES on /etc/passwd, a real "kubectl not found" during
|
|
42
|
-
// a deploy, are genuine failures the user must see. Kept tight to the one
|
|
43
|
-
// truly-benign case (running git outside a repo).
|
|
44
|
-
const TOOL_SETUP_RE =
|
|
45
|
-
/not a git repository/i
|
|
46
|
-
|
|
47
|
-
// Timeout / cancellation that the agent will retry. The bare `aborted`
|
|
48
|
-
// substring was previously included but matched DB transaction aborts,
|
|
49
|
-
// git merge aborts, and policy-rejection messages — all real failures.
|
|
50
|
-
// Dropped in favor of explicit timeout and operation-cancelled phrasing.
|
|
51
|
-
const TIMEOUT_RE =
|
|
52
|
-
/timed? ?out|operation cancelled/i
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Returns true when a tool error text matches a known benign pattern (no
|
|
56
|
-
* results / resource absent / recoverable Telegram error / explicit timeout).
|
|
57
|
-
* Returns false for empty input, unknown text, or any text that doesn't
|
|
58
|
-
* match a pattern.
|
|
59
|
-
*
|
|
60
|
-
* The function is fail-closed: empty / undefined input → false. Callers
|
|
61
|
-
* that want to suppress only on positive evidence should call this directly;
|
|
62
|
-
* callers that need an extra short-circuit on missing input should guard at
|
|
63
|
-
* the call site with `text && isBenignToolError(text)`.
|
|
64
|
-
*
|
|
65
|
-
* The text parameter is the raw tool result content; the first ~500 chars
|
|
66
|
-
* are sufficient for pattern matching, and callers should truncate before
|
|
67
|
-
* calling for performance / event-size reasons.
|
|
68
|
-
*/
|
|
69
|
-
export function isBenignToolError(text: string): boolean {
|
|
70
|
-
if (!text) return false
|
|
71
|
-
return (
|
|
72
|
-
FILE_NOT_FOUND_RE.test(text) ||
|
|
73
|
-
NO_MATCH_RE.test(text) ||
|
|
74
|
-
TELEGRAM_RECOVERABLE_RE.test(text) ||
|
|
75
|
-
TOOL_SETUP_RE.test(text) ||
|
|
76
|
-
TIMEOUT_RE.test(text)
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Severity of a tool error for routing purposes.
|
|
82
|
-
* - `benign`: no-match / not-found / recoverable — suppress from UI
|
|
83
|
-
* - `real`: auth failure, crash, unexpected error — surface in UI
|
|
84
|
-
*/
|
|
85
|
-
export type ToolErrorSeverity = 'benign' | 'real'
|
|
86
|
-
|
|
87
|
-
export function classifyToolError(text: string): ToolErrorSeverity {
|
|
88
|
-
return isBenignToolError(text) ? 'benign' : 'real'
|
|
89
|
-
}
|