switchroom 0.18.11 → 0.18.13
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 +37 -5
- package/dist/auth-broker/index.js +116 -78
- package/dist/cli/hindsight-mental-model-pretool.mjs +39 -0
- package/dist/cli/ms-365-write-pretool.mjs +31 -8
- package/dist/cli/notion-write-pretool.mjs +38 -6
- package/dist/cli/skill-validate-pretool.mjs +144 -2847
- package/dist/cli/switchroom.js +3529 -4543
- package/dist/cli/ui/index.html +163 -17
- package/dist/host-control/main.js +605 -2847
- package/dist/vault/approvals/kernel-server.js +120 -13
- package/dist/vault/broker/server.js +259 -157
- package/package.json +3 -4
- package/profiles/_base/start.sh.hbs +65 -0
- package/profiles/_shared/vault-protocol.md.hbs +3 -1
- package/profiles/coding/CLAUDE.md.hbs +1 -1
- package/profiles/default/CLAUDE.md.hbs +2 -2
- package/profiles/executive-assistant/CLAUDE.md.hbs +1 -1
- package/profiles/health-coach/CLAUDE.md.hbs +1 -1
- package/telegram-plugin/bridge/bridge.ts +37 -0
- package/telegram-plugin/bridge/inbound-dedup.ts +101 -0
- package/telegram-plugin/dist/bridge/bridge.js +122 -4
- package/telegram-plugin/dist/gateway/gateway.js +4213 -3288
- package/telegram-plugin/dist/server.js +139 -5
- package/telegram-plugin/flood-circuit-breaker.ts +493 -21
- package/telegram-plugin/format.ts +19 -0
- package/telegram-plugin/gateway/approval-hold.ts +602 -0
- package/telegram-plugin/gateway/auth-command.ts +92 -2
- package/telegram-plugin/gateway/auth-loopback-relay.ts +670 -0
- package/telegram-plugin/gateway/boot-card.ts +12 -5
- package/telegram-plugin/gateway/callback-query-handlers.ts +88 -1
- package/telegram-plugin/gateway/config-approval-handler.ts +6 -1
- package/telegram-plugin/gateway/disconnect-flush.ts +19 -0
- package/telegram-plugin/gateway/dm-pin-sweep.test.ts +251 -0
- package/telegram-plugin/gateway/dm-pin-sweep.ts +178 -0
- package/telegram-plugin/gateway/gateway.ts +1695 -230
- package/telegram-plugin/gateway/hostd-dispatch.ts +23 -0
- package/telegram-plugin/gateway/idle-clear.ts +90 -6
- package/telegram-plugin/gateway/inbound-delivery-machine-shadow.ts +26 -5
- package/telegram-plugin/gateway/inject-handler.ts +8 -0
- package/telegram-plugin/gateway/ipc-protocol.ts +46 -3
- package/telegram-plugin/gateway/ipc-server.ts +43 -0
- package/telegram-plugin/gateway/mental-model-propose-resolve.ts +145 -37
- package/telegram-plugin/gateway/model-command.ts +9 -3
- package/telegram-plugin/gateway/pending-session-command.ts +13 -1
- package/telegram-plugin/gateway/permission-ttl-sweep.ts +66 -0
- package/telegram-plugin/gateway/pre-approval-check.ts +74 -0
- package/telegram-plugin/gateway/queued-card-store.ts +217 -0
- package/telegram-plugin/gateway/session-model-file.ts +26 -1
- package/telegram-plugin/gateway/turn-end-gate-backstop.ts +59 -0
- package/telegram-plugin/gateway/turn-end-gate.ts +95 -0
- package/telegram-plugin/gateway/turn-typing-loop.ts +10 -2
- package/telegram-plugin/gateway/unhandled-rejection-policy.ts +13 -0
- package/telegram-plugin/history.ts +51 -0
- package/telegram-plugin/hooks/dispatch-claim-scan.mjs +259 -0
- package/telegram-plugin/hooks/dispatch-claim-stop.mjs +129 -0
- package/telegram-plugin/hooks/hooks.json +9 -0
- package/telegram-plugin/inline-keyboard-callbacks.ts +303 -2
- package/telegram-plugin/model-unavailable.ts +41 -11
- package/telegram-plugin/operator-events.ts +23 -0
- package/telegram-plugin/outbound-field-redact.ts +69 -0
- package/telegram-plugin/package.json +0 -1
- package/telegram-plugin/permission-rule.ts +1 -0
- package/telegram-plugin/permission-title.ts +1 -0
- package/telegram-plugin/render/render.ts +32 -14
- package/telegram-plugin/retry-api-call.ts +212 -2
- 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 +574 -0
- package/telegram-plugin/send-gate-observability.test.ts +470 -0
- package/telegram-plugin/send-gate-observability.ts +355 -0
- package/telegram-plugin/send-gate.test.ts +717 -0
- package/telegram-plugin/send-gate.ts +1056 -0
- package/telegram-plugin/session-tail.ts +82 -7
- package/telegram-plugin/shared/bot-runtime.ts +17 -5
- package/telegram-plugin/shared/gw-trace-gate.ts +105 -0
- package/telegram-plugin/status-pin-driver.ts +52 -7
- package/telegram-plugin/status-pin.ts +81 -0
- package/telegram-plugin/subagent-watcher.ts +173 -18
- package/telegram-plugin/tests/activity-card-wiring.test.ts +18 -5
- package/telegram-plugin/tests/approval-hold-harness.ts +425 -0
- package/telegram-plugin/tests/approval-hold-outcome.test.ts +327 -0
- package/telegram-plugin/tests/approval-hold-record.test.ts +531 -0
- package/telegram-plugin/tests/approval-hold-redeliver.test.ts +602 -0
- package/telegram-plugin/tests/auth-loopback-relay.test.ts +533 -0
- package/telegram-plugin/tests/boot-card-flood-suppress.test.ts +53 -7
- package/telegram-plugin/tests/busy-key-reaper.test.ts +1 -0
- package/telegram-plugin/tests/callback-query-handlers.test.ts +65 -0
- package/telegram-plugin/tests/dispatch-claim-scan.test.ts +250 -0
- package/telegram-plugin/tests/flood-breaker-blindness.test.ts +213 -0
- package/telegram-plugin/tests/flood-windows-persistence.test.ts +224 -0
- package/telegram-plugin/tests/gateway-boot-marker-clear.test.ts +3 -3
- package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +29 -1
- package/telegram-plugin/tests/gateway-loopback-paste-redact.test.ts +66 -0
- package/telegram-plugin/tests/gateway-outbound-redact.test.ts +57 -0
- package/telegram-plugin/tests/gw-trace-gate.test.ts +105 -0
- package/telegram-plugin/tests/history.test.ts +115 -0
- package/telegram-plugin/tests/idle-clear.test.ts +233 -3
- package/telegram-plugin/tests/inbound-dedup.test.ts +93 -0
- package/telegram-plugin/tests/inbound-message-types.test.ts +5 -1
- package/telegram-plugin/tests/inline-keyboard-callbacks.test.ts +448 -0
- package/telegram-plugin/tests/ipc-server-check-pre-approved.test.ts +194 -0
- package/telegram-plugin/tests/mental-model-propose-resolve.test.ts +123 -0
- package/telegram-plugin/tests/missed-approvals-wiring.test.ts +1 -1
- package/telegram-plugin/tests/model-command.test.ts +14 -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/pending-session-command.test.ts +21 -0
- package/telegram-plugin/tests/permission-card-routing.test.ts +30 -5
- package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +8 -7
- package/telegram-plugin/tests/permission-rearm-wiring.test.ts +1 -1
- package/telegram-plugin/tests/pre-approval-check.test.ts +148 -0
- package/telegram-plugin/tests/queued-card-store.test.ts +232 -0
- package/telegram-plugin/tests/reaction-flush-turn-gated.test.ts +100 -0
- package/telegram-plugin/tests/reaction-gate-routing.test.ts +173 -0
- package/telegram-plugin/tests/render/render.test.ts +88 -0
- package/telegram-plugin/tests/retry-api-call.test.ts +398 -0
- 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-model-file.test.ts +50 -0
- package/telegram-plugin/tests/session-tail-sidecar-reap.test.ts +268 -0
- package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +35 -14
- package/telegram-plugin/tests/status-pin.test.ts +275 -1
- package/telegram-plugin/tests/subagent-watcher-deferral-log-ratelimit.test.ts +316 -0
- package/telegram-plugin/tests/subagent-watcher-fd-leak.test.ts +275 -0
- package/telegram-plugin/tests/turn-end-gate-backstop.test.ts +92 -0
- package/telegram-plugin/tests/turn-end-gate.test.ts +137 -0
- package/telegram-plugin/tests/typing-emitter.test.ts +586 -0
- package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +20 -0
- package/telegram-plugin/tests/worktree-watch-cwds.test.ts +215 -1
- package/telegram-plugin/typing-emitter.ts +224 -0
- package/telegram-plugin/uat/scenarios/jtbd-feel-like-a-colleague-dm.test.ts +136 -0
- package/telegram-plugin/welcome-text.ts +42 -0
- package/telegram-plugin/worktree-watch-cwds.ts +194 -5
- package/vendor/hindsight-memory/scripts/drain_pending.py +22 -6
- package/vendor/hindsight-memory/scripts/lib/client.py +12 -5
- package/vendor/hindsight-memory/scripts/lib/directives.py +38 -3
- package/vendor/hindsight-memory/scripts/lib/pending.py +36 -9
- package/vendor/hindsight-memory/scripts/session_end.py +14 -3
- package/vendor/hindsight-memory/scripts/session_start.py +21 -0
- package/vendor/hindsight-memory/scripts/tests/test_directives.py +38 -0
- package/vendor/hindsight-memory/tests/test_drain_pending.py +68 -0
- package/vendor/hindsight-memory/tests/test_pending.py +44 -0
- package/vendor/hindsight-memory/tests/test_session_end_pending.py +38 -0
- package/vendor/hindsight-memory/tests/test_session_start_drain.py +155 -0
- package/telegram-plugin/channel-envelope-safety.test.ts +0 -56
- package/telegram-plugin/channel-envelope-safety.ts +0 -56
- package/telegram-plugin/secret-detect/secretlint-source.ts +0 -95
- package/telegram-plugin/tests/secret-detect-secretlint.test.ts +0 -105
package/dist/cli/ui/index.html
CHANGED
|
@@ -1685,12 +1685,22 @@
|
|
|
1685
1685
|
const safe = (p, fallback) =>
|
|
1686
1686
|
p.then(r => r.ok ? r.json() : fallback).catch(() => fallback);
|
|
1687
1687
|
try {
|
|
1688
|
-
const [approvals, grants] = await Promise.all([
|
|
1688
|
+
const [blocked, blockedStatus, approvals, grants] = await Promise.all([
|
|
1689
|
+
// Blocked approvals lead the tab — an agent held with no way to ask
|
|
1690
|
+
// is the only thing here that is actively costing the operator.
|
|
1691
|
+
// The fallback is a `null` SENTINEL, never `[]`: a 500 / auth
|
|
1692
|
+
// failure / network error must not render as "nothing is blocked".
|
|
1693
|
+
// That would be a positive claim we did not verify, over an agent
|
|
1694
|
+
// that may be hanging right now.
|
|
1695
|
+
safe(fetch(`${API}/api/blocked-approvals`, { headers: authHeaders() }), null),
|
|
1696
|
+
// Companion read: how many records we FAILED to read (0600 files).
|
|
1697
|
+
// Same rule — null means we don't know, which is not "all clear".
|
|
1698
|
+
safe(fetch(`${API}/api/blocked-approvals/status`, { headers: authHeaders() }), null),
|
|
1689
1699
|
safe(fetch(`${API}/api/approvals`, { headers: authHeaders() }), null),
|
|
1690
1700
|
safe(fetch(`${API}/api/grants`, { headers: authHeaders() }),
|
|
1691
1701
|
{ reachable: false, grants: [], error: 'Failed to fetch standing grants.' }),
|
|
1692
1702
|
]);
|
|
1693
|
-
renderApprovals(approvals, grants);
|
|
1703
|
+
renderApprovals(blocked, blockedStatus, approvals, grants);
|
|
1694
1704
|
clearError();
|
|
1695
1705
|
} catch (err) {
|
|
1696
1706
|
showError(`Failed to fetch approvals: ${err.message}`);
|
|
@@ -2063,15 +2073,50 @@
|
|
|
2063
2073
|
const apprReachable = !!(appr && appr.reachable !== false);
|
|
2064
2074
|
const apprDecisions = (appr && appr.decisions) || [];
|
|
2065
2075
|
const activeGrants = apprDecisions.filter(x => !x.revoked_at && !(x.ttl_expires_at && x.ttl_expires_at < Date.now())).length;
|
|
2076
|
+
// Agents HELD on an approval that couldn't be delivered to Telegram.
|
|
2077
|
+
// Surfaced twice on purpose: as critical rows in "Needs attention"
|
|
2078
|
+
// above (server-derived), and as a count here — a blocked agent must be
|
|
2079
|
+
// visible on the default tab without drilling in (#3084).
|
|
2080
|
+
const blockedAppr = Array.isArray(summary.blockedApprovals) ? summary.blockedApprovals : [];
|
|
2081
|
+
// Records that EXIST but could not be read (0600, IO error). Non-zero
|
|
2082
|
+
// means an empty `blockedAppr` proves nothing, so nothing below may
|
|
2083
|
+
// claim "none blocked" or "all nominal".
|
|
2084
|
+
const blockedUnreadable = Number(summary.blockedApprovalsUnreadable) || 0;
|
|
2066
2085
|
const schedEntries = (sched && Array.isArray(sched.entries)) ? sched.entries : [];
|
|
2067
2086
|
const schedAgents = new Set(schedEntries.map(e => e.agent)).size;
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2087
|
+
// Approvals is NOT ok if an agent is blocked (the leash is jammed and
|
|
2088
|
+
// someone is waiting on Ken) OR if we could not read the records at all
|
|
2089
|
+
// (we cannot claim "none blocked" without having looked).
|
|
2090
|
+
const apprOk = apprReachable && blockedAppr.length === 0 && blockedUnreadable === 0;
|
|
2091
|
+
const apprMeta = blockedUnreadable > 0
|
|
2092
|
+
? `<b>cannot read ${blockedUnreadable} blocked record(s)</b> · an agent may be held`
|
|
2093
|
+
: blockedAppr.length > 0
|
|
2094
|
+
? `<b>${blockedAppr.length} agent${blockedAppr.length === 1 ? '' : 's'} blocked</b> — ${escapeHtml(blockedAppr.map(b => b.agent).join(', '))}`
|
|
2095
|
+
: apprReachable ? `${activeGrants} active grants · none blocked` : 'unreachable';
|
|
2096
|
+
|
|
2097
|
+
// Build the rail from specs so the BAND can be derived from whether
|
|
2098
|
+
// every service is actually ok. The band used to hardcode "all nominal"
|
|
2099
|
+
// even with hindsight down and hostd red — the same claim-health-you-
|
|
2100
|
+
// haven't-verified bug this PR exists to kill, so it dies here too
|
|
2101
|
+
// rather than being carved out for one service.
|
|
2102
|
+
const svcSpecs = [
|
|
2103
|
+
{ name: 'auth-broker', ok: !!b.reachable, meta: b.reachable ? `active ${escapeHtml(b.active || '—')} · ${b.accounts ?? '?'} accts` : 'unreachable' },
|
|
2104
|
+
{ name: 'hindsight', ok: !!hs.running, meta: hs.running ? `${escapeHtml(hs.model || '?')} · ${hs.mcpStateless ? 'stateless' : 'stateful'}` : 'not running' },
|
|
2105
|
+
{ name: 'hostd', ok: !!hd.auditLogPresent, meta: hd.auditLogPresent ? `${(hd.recent || []).length} recent verbs` : 'no audit log' },
|
|
2106
|
+
{ name: 'schedule', ok: schedEntries.length > 0, meta: `${schedEntries.length} crons · ${schedAgents} agents` },
|
|
2107
|
+
{ name: 'approvals', ok: apprOk, meta: apprMeta },
|
|
2108
|
+
];
|
|
2109
|
+
const services = svcSpecs.map(s => svc(s.name, s.ok, s.meta)).join('');
|
|
2110
|
+
const badSvcs = svcSpecs.filter(s => !s.ok);
|
|
2111
|
+
// Blocked/unreadable approvals get named explicitly in the band — that
|
|
2112
|
+
// is the one the operator has to act on right now.
|
|
2113
|
+
const bandNote = blockedUnreadable > 0
|
|
2114
|
+
? `cannot read ${blockedUnreadable} blocked record(s)`
|
|
2115
|
+
: blockedAppr.length > 0
|
|
2116
|
+
? `${blockedAppr.length} blocked on approval`
|
|
2117
|
+
: badSvcs.length > 0
|
|
2118
|
+
? `${badSvcs.length} degraded — ${escapeHtml(badSvcs.map(s => s.name).join(', '))}`
|
|
2119
|
+
: 'all nominal';
|
|
2075
2120
|
|
|
2076
2121
|
el.innerHTML = `
|
|
2077
2122
|
${verdict}${asOf}
|
|
@@ -2079,7 +2124,7 @@
|
|
|
2079
2124
|
<div class="sm-attn-list">${attnHtml}</div>
|
|
2080
2125
|
<div class="sm-band">Fleet vitals</div>
|
|
2081
2126
|
<div class="sm-vitals">${agentsCard}${quotaCard}</div>
|
|
2082
|
-
<div class="sm-band">Services <span class="n"
|
|
2127
|
+
<div class="sm-band">Services <span class="n">${bandNote}</span></div>
|
|
2083
2128
|
<div class="sm-services">${services}</div>`;
|
|
2084
2129
|
}
|
|
2085
2130
|
|
|
@@ -3137,15 +3182,116 @@
|
|
|
3137
3182
|
container.innerHTML = degradedBanner + truncatedNote + posture + cards;
|
|
3138
3183
|
}
|
|
3139
3184
|
|
|
3140
|
-
function renderApprovals(data, grants) {
|
|
3185
|
+
function renderApprovals(blocked, blockedStatus, data, grants) {
|
|
3141
3186
|
const container = document.getElementById('approvals');
|
|
3142
|
-
//
|
|
3143
|
-
//
|
|
3144
|
-
//
|
|
3145
|
-
//
|
|
3146
|
-
//
|
|
3187
|
+
// Three independent sections, most-urgent first: agents HELD on an
|
|
3188
|
+
// approval they couldn't deliver (the only live, costing thing here),
|
|
3189
|
+
// then the operator's REAL standing capability grants (broker grants
|
|
3190
|
+
// DB), then the kernel decision ledger (honestly empty on most
|
|
3191
|
+
// installs). They degrade independently — one being unreachable never
|
|
3192
|
+
// blanks the others.
|
|
3147
3193
|
container.innerHTML =
|
|
3148
|
-
|
|
3194
|
+
renderBlockedApprovals(blocked, blockedStatus) +
|
|
3195
|
+
renderStandingGrants(grants) +
|
|
3196
|
+
renderApprovalDecisions(data);
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
// Humanized hold duration. Mirrors formatBlockedFor() in
|
|
3200
|
+
// src/web/blocked-approvals-read.ts so the Summary row and this table
|
|
3201
|
+
// read identically ("blocked 47m"). Clamps a future timestamp to 0.
|
|
3202
|
+
function formatBlockedFor(ms) {
|
|
3203
|
+
const t = Math.max(0, Math.floor(ms / 1000));
|
|
3204
|
+
if (t < 60) return `${t}s`;
|
|
3205
|
+
const m = Math.floor(t / 60);
|
|
3206
|
+
if (m < 60) return `${m}m`;
|
|
3207
|
+
const h = Math.floor(m / 60);
|
|
3208
|
+
if (h < 24) return (m % 60 > 0) ? `${h}h ${m % 60}m` : `${h}h`;
|
|
3209
|
+
const d = Math.floor(h / 24);
|
|
3210
|
+
return (h % 24 > 0) ? `${d}d ${h % 24}h` : `${d}d`;
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
// Agents held on an approval whose card could not be delivered to
|
|
3214
|
+
// Telegram (flood ban, API outage). The agent HOLDS — it never
|
|
3215
|
+
// auto-approves and never auto-denies — so it waits indefinitely. This
|
|
3216
|
+
// section is the whole point: a held agent has to be SEEN here, not
|
|
3217
|
+
// discovered an hour later (#3084).
|
|
3218
|
+
//
|
|
3219
|
+
// The record is metadata only; it never carries the raw tool input, and
|
|
3220
|
+
// the server hard-whitelists the fields. Empty is stated plainly rather
|
|
3221
|
+
// than implying health we haven't verified.
|
|
3222
|
+
function renderBlockedApprovals(blocked, blockedStatus) {
|
|
3223
|
+
const dim = (s) => `<span style="color:var(--text-dim)">${escapeHtml(s)}</span>`;
|
|
3224
|
+
const rows = Array.isArray(blocked) ? blocked : [];
|
|
3225
|
+
const heading = `<h3 style="margin:0 0 0.5rem;font-size:0.95rem">Blocked approvals${rows.length ? ` <span class="usage-pill primary">${rows.length}</span>` : ''}</h3>`;
|
|
3226
|
+
|
|
3227
|
+
// Did we actually LOOK? Three distinct states, and only one of them may
|
|
3228
|
+
// print an all-clear:
|
|
3229
|
+
// - fetch failed (`blocked` is the null sentinel) → we don't know
|
|
3230
|
+
// - status says records were unreadable (0600, IO) → we don't know
|
|
3231
|
+
// - read clean and empty → nothing is blocked
|
|
3232
|
+
// Collapsing the first two into the third is the failure this whole
|
|
3233
|
+
// surface exists to prevent: a confident "all clear" that really means
|
|
3234
|
+
// "I couldn't look", printed over an agent that is hanging.
|
|
3235
|
+
const fetchFailed = !Array.isArray(blocked);
|
|
3236
|
+
const unreadable = (blockedStatus && Number(blockedStatus.unreadable)) || 0;
|
|
3237
|
+
const blind = fetchFailed || unreadable > 0;
|
|
3238
|
+
|
|
3239
|
+
// The warning is a BANNER, not an early return: an unreadable record
|
|
3240
|
+
// must never hide the records we CAN read. Both can be true at once.
|
|
3241
|
+
const warning = blind
|
|
3242
|
+
? renderProblem({
|
|
3243
|
+
title: 'Could not read the blocked-approval records. An agent may be held.',
|
|
3244
|
+
detail: (fetchFailed
|
|
3245
|
+
? 'The dashboard could not fetch the blocked-approval records. '
|
|
3246
|
+
: `${unreadable} blocked-approval record(s) exist but could not be read (wrong file mode, or an IO error). `) +
|
|
3247
|
+
'This is not an all-clear: an agent may be blocked on an approval right now and this page cannot see it.',
|
|
3248
|
+
remediation: {
|
|
3249
|
+
kind: 'command',
|
|
3250
|
+
label: 'Records must be mode 0644. Check on the host:',
|
|
3251
|
+
value: 'ls -l ~/.switchroom/blocked-approvals/',
|
|
3252
|
+
},
|
|
3253
|
+
})
|
|
3254
|
+
: '';
|
|
3255
|
+
|
|
3256
|
+
if (rows.length === 0) {
|
|
3257
|
+
// Only an unblinded, clean read may print an all-clear.
|
|
3258
|
+
return heading + warning + (blind
|
|
3259
|
+
? ''
|
|
3260
|
+
: '<div class="loading" style="color:var(--text-dim)">No agent is blocked on an undeliverable approval.</div>');
|
|
3261
|
+
}
|
|
3262
|
+
const now = Date.now();
|
|
3263
|
+
const body = rows.map(b => {
|
|
3264
|
+
const held = formatBlockedFor(now - (Number(b.blockedSince) || now));
|
|
3265
|
+
const retryCell = (typeof b.retryableAt === 'number')
|
|
3266
|
+
? (b.retryableAt > now
|
|
3267
|
+
? `in ${escapeHtml(formatBlockedFor(b.retryableAt - now))} <span style="color:var(--text-dim)">(${escapeHtml(formatTimestamp(b.retryableAt))})</span>`
|
|
3268
|
+
: `${escapeHtml(formatTimestamp(b.retryableAt))} <span style="color:var(--text-dim)">(due)</span>`)
|
|
3269
|
+
: dim('unknown');
|
|
3270
|
+
return `
|
|
3271
|
+
<tr>
|
|
3272
|
+
<td><span class="status-dot inactive" style="display:inline-block;vertical-align:middle"></span> ${escapeHtml(b.agent || '—')}</td>
|
|
3273
|
+
<td><b>blocked ${escapeHtml(held)}</b></td>
|
|
3274
|
+
<td>${b.action ? escapeHtml(b.action) : dim('—')}</td>
|
|
3275
|
+
<td><span class="chip">${escapeHtml(b.toolName || '—')}</span></td>
|
|
3276
|
+
<td>${escapeHtml(b.reason || '—')}</td>
|
|
3277
|
+
<td>${retryCell}</td>
|
|
3278
|
+
<td title="${escapeHtml(b.requestId || '')}">${b.requestId ? escapeHtml(b.requestId) : dim('—')}</td>
|
|
3279
|
+
</tr>`;
|
|
3280
|
+
}).join('');
|
|
3281
|
+
return heading + warning + `
|
|
3282
|
+
<div style="margin-bottom:0.6rem;font-size:0.8rem;color:var(--text-dim)">
|
|
3283
|
+
These agents are waiting on you and cannot ask. The approval card could not be delivered to Telegram.
|
|
3284
|
+
Nothing is auto-approved and nothing is auto-denied; each one holds until it can reach you.
|
|
3285
|
+
</div>
|
|
3286
|
+
<div class="accounts-table-wrap">
|
|
3287
|
+
<table class="accounts-table">
|
|
3288
|
+
<thead><tr>
|
|
3289
|
+
<th>Agent</th><th>Held for</th><th>Waiting to</th>
|
|
3290
|
+
<th>Tool</th><th>Why undelivered</th><th>Retry</th><th>Request</th>
|
|
3291
|
+
</tr></thead>
|
|
3292
|
+
<tbody>${body}</tbody>
|
|
3293
|
+
</table>
|
|
3294
|
+
</div>`;
|
|
3149
3295
|
}
|
|
3150
3296
|
|
|
3151
3297
|
// Standing capability grants from the vault-broker grants DB — what
|