switchroom 0.19.18 → 0.19.22
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 +2 -1
- package/dist/auth-broker/index.js +56 -1
- package/dist/cli/drive-write-pretool.mjs +48 -5
- package/dist/cli/ms-365-write-pretool.mjs +40 -2
- package/dist/cli/notion-write-pretool.mjs +2 -1
- package/dist/cli/switchroom.js +5242 -2239
- package/dist/host-control/main.js +12241 -11375
- package/dist/vault/approvals/kernel-server.js +113 -7
- package/dist/vault/broker/server.js +259 -76
- package/package.json +6 -3
- package/profiles/_base/start.sh.hbs +61 -1
- package/skills/switchroom-release/SKILL.md +103 -20
- package/telegram-plugin/bridge/bridge.ts +14 -0
- package/telegram-plugin/card-format.ts +92 -3
- package/telegram-plugin/dist/bridge/bridge.js +13 -0
- package/telegram-plugin/dist/gateway/gateway.js +2356 -1159
- package/telegram-plugin/dist/server.js +13 -0
- package/telegram-plugin/edit-flood-fuse.ts +477 -0
- package/telegram-plugin/format.ts +19 -7
- package/telegram-plugin/gateway/always-allow-persist-queue.ts +97 -11
- package/telegram-plugin/gateway/boot-sweep-gate.ts +164 -0
- package/telegram-plugin/gateway/callback-query-handlers.ts +454 -81
- package/telegram-plugin/gateway/gateway.ts +66 -56
- package/telegram-plugin/gateway/inbound-interceptors.ts +27 -4
- package/telegram-plugin/gateway/missed-approvals-store.ts +66 -17
- package/telegram-plugin/gateway/narrative-lane.ts +49 -3
- package/telegram-plugin/gateway/pending-card-store.ts +46 -16
- package/telegram-plugin/gateway/scoped-grant-store.ts +39 -14
- package/telegram-plugin/gateway/status-pin-api.ts +145 -0
- package/telegram-plugin/gateway/store-file.ts +244 -0
- package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +325 -45
- package/telegram-plugin/hooks/tool-label-pretool.mjs +88 -2
- package/telegram-plugin/retry-api-call.ts +15 -2
- package/telegram-plugin/send-gate.ts +1 -1
- package/telegram-plugin/status-no-truncate.ts +64 -1
- package/telegram-plugin/status-pin-driver.ts +50 -27
- package/telegram-plugin/status-pin.ts +43 -5
- package/telegram-plugin/tests/activity-card-send-gate.test.ts +275 -0
- package/telegram-plugin/tests/activity-card-wiring.test.ts +16 -7
- package/telegram-plugin/tests/boot-pin-sweep-wiring.test.ts +101 -0
- package/telegram-plugin/tests/boot-sweep-gate.test.ts +293 -0
- package/telegram-plugin/tests/boot-version-string.test.ts +0 -0
- package/telegram-plugin/tests/bridge-tool-parity.test.ts +95 -0
- package/telegram-plugin/tests/edit-flood-fuse.test.ts +431 -0
- package/telegram-plugin/tests/pinned-card-collapse.test.ts +356 -0
- package/telegram-plugin/tests/status-pin-api.test.ts +178 -0
- package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +94 -11
- package/telegram-plugin/tests/status-pin.test.ts +106 -5
- package/telegram-plugin/tests/store-atomic-write.test.ts +411 -0
- package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +631 -1
- package/telegram-plugin/tests/tool-activity-summary.test.ts +28 -12
- package/telegram-plugin/tests/tool-label-pretool.test.ts +94 -0
- package/telegram-plugin/tests/vault-approval-posture.test.ts +6 -1
- package/telegram-plugin/tests/vault-passphrase-retry.test.ts +666 -0
- package/telegram-plugin/tests/vault-request-access-unlock-resume.test.ts +42 -21
- package/telegram-plugin/tests/worker-feed-coalesce.test.ts +233 -1
- package/telegram-plugin/tests/worker-feed-repeat-steps.test.ts +147 -0
- package/telegram-plugin/tool-activity-summary.ts +85 -13
- package/telegram-plugin/worker-activity-feed.ts +56 -2
- package/vendor/hindsight-memory/scripts/drain_pending.py +847 -67
- package/vendor/hindsight-memory/scripts/lib/client.py +124 -0
- package/vendor/hindsight-memory/scripts/lib/pending.py +944 -33
- package/vendor/hindsight-memory/scripts/lib/retain_split.py +460 -0
- package/vendor/hindsight-memory/scripts/recall.py +74 -5
- package/vendor/hindsight-memory/scripts/session_start.py +48 -0
- package/vendor/hindsight-memory/scripts/tests/test_client_document_exists.py +470 -0
- package/vendor/hindsight-memory/scripts/tests/test_pending_drops.py +2275 -0
- package/vendor/hindsight-memory/scripts/tests/test_pending_failure_class.py +105 -0
- package/vendor/hindsight-memory/scripts/tests/test_pending_wedge.py +300 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_degraded_notice.py +365 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_envelope_strip_telemetry.py +12 -4
- package/vendor/hindsight-memory/scripts/tests/test_recall_transcript_fallback.py +27 -2
- package/vendor/hindsight-memory/scripts/tests/test_retain_split.py +438 -0
- package/vendor/hindsight-memory/scripts/tests/test_session_start_version_skew.py +204 -0
- package/vendor/hindsight-memory/tests/test_drain_pending.py +130 -8
- package/vendor/hindsight-memory/tests/test_pending.py +32 -7
|
@@ -18222,7 +18222,7 @@ var require_lib = __commonJS((exports, module) => {
|
|
|
18222
18222
|
|
|
18223
18223
|
// src/vault/broker/server.ts
|
|
18224
18224
|
import * as net from "node:net";
|
|
18225
|
-
import { mkdirSync as mkdirSync7, chmodSync as chmodSync5, chownSync, existsSync as existsSync12, readFileSync as readFileSync10, readdirSync as
|
|
18225
|
+
import { mkdirSync as mkdirSync7, chmodSync as chmodSync5, chownSync, existsSync as existsSync12, readFileSync as readFileSync10, readdirSync as readdirSync5, statSync as statSync7, unlinkSync as unlinkSync6, writeFileSync as writeFileSync3, renameSync as renameSync8 } from "node:fs";
|
|
18226
18226
|
|
|
18227
18227
|
// src/agents/compose.ts
|
|
18228
18228
|
init_schema();
|
|
@@ -18820,6 +18820,47 @@ for (const name of SHARED_FRAGMENTS) {
|
|
|
18820
18820
|
}
|
|
18821
18821
|
}
|
|
18822
18822
|
|
|
18823
|
+
// src/litellm/timeout-budget.ts
|
|
18824
|
+
var LITELLM_ROUTER_MARGIN_S = 10;
|
|
18825
|
+
var LITELLM_TIMEOUT_TIERS = {
|
|
18826
|
+
interactive: {
|
|
18827
|
+
group: "gpt-oss-20b",
|
|
18828
|
+
localTimeoutS: 90,
|
|
18829
|
+
fallbackGroup: "gpt-oss-20b-openrouter",
|
|
18830
|
+
fallbackTimeoutS: 60
|
|
18831
|
+
},
|
|
18832
|
+
retain: {
|
|
18833
|
+
group: "gpt-oss-20b-retain",
|
|
18834
|
+
localTimeoutS: 200,
|
|
18835
|
+
fallbackGroup: "gpt-oss-20b-retain-openrouter",
|
|
18836
|
+
fallbackTimeoutS: 90
|
|
18837
|
+
},
|
|
18838
|
+
consolidation: {
|
|
18839
|
+
group: "gpt-oss-20b-consolidation",
|
|
18840
|
+
localTimeoutS: 200,
|
|
18841
|
+
fallbackGroup: "gpt-oss-20b-consolidation-openrouter",
|
|
18842
|
+
fallbackTimeoutS: 90
|
|
18843
|
+
}
|
|
18844
|
+
};
|
|
18845
|
+
function litellmChainSeconds(tier) {
|
|
18846
|
+
assertPositive(tier.localTimeoutS, `${tier.group} localTimeoutS`);
|
|
18847
|
+
if (tier.fallbackGroup === null)
|
|
18848
|
+
return tier.localTimeoutS;
|
|
18849
|
+
assertPositive(tier.fallbackTimeoutS, `${tier.fallbackGroup} fallbackTimeoutS`);
|
|
18850
|
+
return tier.localTimeoutS + tier.fallbackTimeoutS;
|
|
18851
|
+
}
|
|
18852
|
+
function minimumClientBudgetSeconds(tier, marginS = LITELLM_ROUTER_MARGIN_S) {
|
|
18853
|
+
return litellmChainSeconds(tier) + marginS;
|
|
18854
|
+
}
|
|
18855
|
+
function clientBudgetSeconds(tier, floorS = 0, marginS = LITELLM_ROUTER_MARGIN_S) {
|
|
18856
|
+
return Math.max(Math.ceil(floorS), minimumClientBudgetSeconds(tier, marginS));
|
|
18857
|
+
}
|
|
18858
|
+
function assertPositive(value, label) {
|
|
18859
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
18860
|
+
throw new Error(`litellm timeout budget: ${label} must be a positive number, got ${value}`);
|
|
18861
|
+
}
|
|
18862
|
+
}
|
|
18863
|
+
|
|
18823
18864
|
// src/setup/hindsight.ts
|
|
18824
18865
|
var HINDSIGHT_DEFAULT_API_PORT = 18888;
|
|
18825
18866
|
var HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
|
|
@@ -18829,8 +18870,21 @@ var HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight";
|
|
|
18829
18870
|
var HINDSIGHT_IMAGE = `${HINDSIGHT_IMAGE_REPO}:latest`;
|
|
18830
18871
|
var HINDSIGHT_BROKER_SOCK_VOLUME = `auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock`;
|
|
18831
18872
|
var HINDSIGHT_CREDS_MIRROR_VOLUME = `consumer-creds-${HINDSIGHT_CONSUMER_NAME}`;
|
|
18873
|
+
var HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS = 16384;
|
|
18874
|
+
var HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND = 80.6;
|
|
18875
|
+
function hindsightRetainLlmTimeoutSeconds(maxCompletionTokens = HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS, tokensPerSecond = HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND) {
|
|
18876
|
+
if (!(maxCompletionTokens > 0) || !(tokensPerSecond > 0)) {
|
|
18877
|
+
throw new Error(`hindsight retain budget: maxCompletionTokens (${maxCompletionTokens}) and ` + `tokensPerSecond (${tokensPerSecond}) must both be positive`);
|
|
18878
|
+
}
|
|
18879
|
+
return Math.ceil(maxCompletionTokens / tokensPerSecond);
|
|
18880
|
+
}
|
|
18881
|
+
function hindsightRetainClientTimeoutSeconds() {
|
|
18882
|
+
return clientBudgetSeconds(LITELLM_TIMEOUT_TIERS.retain, hindsightRetainLlmTimeoutSeconds());
|
|
18883
|
+
}
|
|
18884
|
+
var HINDSIGHT_RETAIN_CLIENT_DEADLINE_S = hindsightRetainClientTimeoutSeconds() + LITELLM_ROUTER_MARGIN_S;
|
|
18832
18885
|
var HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)';
|
|
18833
18886
|
var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
|
|
18887
|
+
var DOCKER_PROBE_TIMEOUT_MS = 60 * 1000;
|
|
18834
18888
|
|
|
18835
18889
|
// src/memory/hindsight.ts
|
|
18836
18890
|
var DEFAULT_RETAIN_MISSION = "Extract durable facts that will still be true and useful weeks from now: " + "user preferences and standing rules, ongoing projects and recurring " + "commitments, technical and architectural decisions with their rationale, " + "and people/tool relationships. A preference revealed by a request is " + "durable — record the preference (what the user likes, wants, or always " + `does), not the request itself.
|
|
@@ -19546,7 +19600,8 @@ var ApprovalDecisionMetaSchema = exports_external.object({
|
|
|
19546
19600
|
ttl_expires_at: exports_external.number().nullable(),
|
|
19547
19601
|
last_used_at: exports_external.number().nullable(),
|
|
19548
19602
|
revoked_at: exports_external.number().nullable(),
|
|
19549
|
-
revoke_reason: exports_external.string().nullable()
|
|
19603
|
+
revoke_reason: exports_external.string().nullable(),
|
|
19604
|
+
origin: exports_external.enum(["agent", "operator"]).optional()
|
|
19550
19605
|
});
|
|
19551
19606
|
var OkApprovalLookupResponseSchema = exports_external.object({
|
|
19552
19607
|
ok: exports_external.literal(true),
|
|
@@ -19745,9 +19800,9 @@ function migrateLegacyGrantsDbLocation(newDbPath = getGrantsDbPath(), deps) {
|
|
|
19745
19800
|
var BIND_MOUNT_EXACT_SOURCE_DENY = new Set(["/var/run/docker.sock"]);
|
|
19746
19801
|
|
|
19747
19802
|
// src/vault/broker/server.ts
|
|
19748
|
-
import { dirname as dirname7, resolve as resolve9, basename as
|
|
19803
|
+
import { dirname as dirname7, resolve as resolve9, basename as basename5 } from "node:path";
|
|
19749
19804
|
import * as os3 from "node:os";
|
|
19750
|
-
import * as
|
|
19805
|
+
import * as path5 from "node:path";
|
|
19751
19806
|
|
|
19752
19807
|
// src/vault/migrate-layout.ts
|
|
19753
19808
|
import {
|
|
@@ -19981,9 +20036,9 @@ function readAutoUnlockFile(filePath) {
|
|
|
19981
20036
|
var DEFAULT_AUTO_UNLOCK_PATH = "~/.switchroom/vault-auto-unlock";
|
|
19982
20037
|
|
|
19983
20038
|
// src/vault/broker/audit-log.ts
|
|
19984
|
-
import * as
|
|
20039
|
+
import * as fs3 from "node:fs";
|
|
19985
20040
|
import * as os2 from "node:os";
|
|
19986
|
-
import * as
|
|
20041
|
+
import * as path3 from "node:path";
|
|
19987
20042
|
|
|
19988
20043
|
// src/util/audit-hashchain.ts
|
|
19989
20044
|
import { createHash as createHash4 } from "node:crypto";
|
|
@@ -20100,76 +20155,107 @@ function safeAuditLogPath(requestedPath) {
|
|
|
20100
20155
|
return cachedTmpAuditLog;
|
|
20101
20156
|
}
|
|
20102
20157
|
|
|
20103
|
-
// src/
|
|
20104
|
-
|
|
20105
|
-
|
|
20106
|
-
|
|
20107
|
-
|
|
20158
|
+
// src/util/log-rotation.ts
|
|
20159
|
+
import * as fs2 from "node:fs";
|
|
20160
|
+
import * as path2 from "node:path";
|
|
20161
|
+
function fsyncAndMeasure(snapshotPath, logPath, tag) {
|
|
20162
|
+
let step = "open";
|
|
20108
20163
|
try {
|
|
20109
|
-
const
|
|
20110
|
-
|
|
20111
|
-
|
|
20112
|
-
|
|
20113
|
-
|
|
20114
|
-
|
|
20115
|
-
|
|
20116
|
-
|
|
20117
|
-
|
|
20118
|
-
|
|
20164
|
+
const fd = fs2.openSync(snapshotPath, "r");
|
|
20165
|
+
try {
|
|
20166
|
+
step = "fsync";
|
|
20167
|
+
fs2.fsyncSync(fd);
|
|
20168
|
+
step = "measure";
|
|
20169
|
+
const size = fs2.fstatSync(fd).size;
|
|
20170
|
+
step = "close";
|
|
20171
|
+
return size;
|
|
20172
|
+
} finally {
|
|
20173
|
+
fs2.closeSync(fd);
|
|
20174
|
+
}
|
|
20175
|
+
} catch (err) {
|
|
20176
|
+
process.stderr.write(`[${tag}] ERROR: could not ${step} snapshot ${snapshotPath}; leaving active log ${logPath} intact to avoid data loss: ${err.message}
|
|
20177
|
+
`);
|
|
20178
|
+
return null;
|
|
20119
20179
|
}
|
|
20120
20180
|
}
|
|
20121
|
-
|
|
20122
|
-
|
|
20123
|
-
|
|
20124
|
-
|
|
20125
|
-
|
|
20126
|
-
|
|
20127
|
-
|
|
20128
|
-
|
|
20129
|
-
}
|
|
20130
|
-
|
|
20131
|
-
|
|
20181
|
+
function rotateLogFile(logPath, maxFiles, tag, stillHeld) {
|
|
20182
|
+
const keep = Math.max(1, Math.floor(maxFiles));
|
|
20183
|
+
const held = (what) => {
|
|
20184
|
+
if (!stillHeld || stillHeld())
|
|
20185
|
+
return true;
|
|
20186
|
+
process.stderr.write(`[${tag}] WARN: rotation lock for ${logPath} was reclaimed while we were inside it; declining to ${what}
|
|
20187
|
+
`);
|
|
20188
|
+
return false;
|
|
20189
|
+
};
|
|
20190
|
+
let entryBytes;
|
|
20191
|
+
try {
|
|
20192
|
+
entryBytes = fs2.statSync(logPath).size;
|
|
20193
|
+
} catch (err) {
|
|
20194
|
+
process.stderr.write(`[${tag}] ERROR: could not stat active log ${logPath} before rotating it: ${err.message}
|
|
20195
|
+
`);
|
|
20196
|
+
return false;
|
|
20197
|
+
}
|
|
20198
|
+
const notRotatedUnderUs = (what) => {
|
|
20199
|
+
let live;
|
|
20200
|
+
try {
|
|
20201
|
+
live = fs2.statSync(logPath).size;
|
|
20202
|
+
} catch (err) {
|
|
20203
|
+
process.stderr.write(`[${tag}] ERROR: could not re-stat active log ${logPath}; declining to ${what}: ${err.message}
|
|
20204
|
+
`);
|
|
20205
|
+
return false;
|
|
20206
|
+
}
|
|
20207
|
+
if (live < entryBytes) {
|
|
20208
|
+
process.stderr.write(`[${tag}] WARN: active log ${logPath} shrank from ${entryBytes} to ${live} bytes while we were rotating it; another rotator snapshotted it and these generations are its, not ours — declining to ${what}
|
|
20209
|
+
`);
|
|
20210
|
+
return false;
|
|
20211
|
+
}
|
|
20212
|
+
return true;
|
|
20213
|
+
};
|
|
20214
|
+
const oldest = `${logPath}.${keep}`;
|
|
20132
20215
|
if (fs2.existsSync(oldest)) {
|
|
20216
|
+
if (!held(`drop ${oldest}`))
|
|
20217
|
+
return false;
|
|
20218
|
+
if (!notRotatedUnderUs(`drop ${oldest}`))
|
|
20219
|
+
return false;
|
|
20133
20220
|
try {
|
|
20134
20221
|
fs2.unlinkSync(oldest);
|
|
20135
20222
|
} catch (err) {
|
|
20136
|
-
process.stderr.write(`[
|
|
20223
|
+
process.stderr.write(`[${tag}] ERROR: could not drop oldest rotation ${oldest}: ${err.message}
|
|
20137
20224
|
`);
|
|
20138
20225
|
}
|
|
20139
20226
|
}
|
|
20140
|
-
for (let n =
|
|
20227
|
+
for (let n = keep - 1;n >= 1; n--) {
|
|
20141
20228
|
const from = `${logPath}.${n}`;
|
|
20142
20229
|
const to = `${logPath}.${n + 1}`;
|
|
20143
20230
|
if (!fs2.existsSync(from))
|
|
20144
20231
|
continue;
|
|
20232
|
+
if (!held(`shift ${from} → ${to}`))
|
|
20233
|
+
return false;
|
|
20234
|
+
if (!notRotatedUnderUs(`shift ${from} → ${to}`))
|
|
20235
|
+
return false;
|
|
20145
20236
|
try {
|
|
20146
20237
|
fs2.renameSync(from, to);
|
|
20147
20238
|
} catch (err) {
|
|
20148
|
-
process.stderr.write(`[
|
|
20239
|
+
process.stderr.write(`[${tag}] ERROR: could not rotate ${from} → ${to}: ${err.message}
|
|
20149
20240
|
`);
|
|
20150
|
-
return;
|
|
20241
|
+
return false;
|
|
20151
20242
|
}
|
|
20152
20243
|
}
|
|
20153
20244
|
const snapshotPath = `${logPath}.1`;
|
|
20245
|
+
if (!held(`overwrite ${snapshotPath}`))
|
|
20246
|
+
return false;
|
|
20247
|
+
if (!notRotatedUnderUs(`overwrite ${snapshotPath}`))
|
|
20248
|
+
return false;
|
|
20154
20249
|
try {
|
|
20155
20250
|
fs2.copyFileSync(logPath, snapshotPath);
|
|
20156
20251
|
} catch (err) {
|
|
20157
|
-
process.stderr.write(`[
|
|
20252
|
+
process.stderr.write(`[${tag}] ERROR: could not snapshot active log ${logPath} → ${snapshotPath}: ${err.message}
|
|
20158
20253
|
`);
|
|
20159
|
-
return;
|
|
20160
|
-
}
|
|
20161
|
-
try {
|
|
20162
|
-
const fd = fs2.openSync(snapshotPath, "r");
|
|
20163
|
-
try {
|
|
20164
|
-
fs2.fsyncSync(fd);
|
|
20165
|
-
} finally {
|
|
20166
|
-
fs2.closeSync(fd);
|
|
20167
|
-
}
|
|
20168
|
-
} catch (err) {
|
|
20169
|
-
process.stderr.write(`[vault-audit] ERROR: could not fsync audit snapshot ${snapshotPath}; leaving active log intact to avoid data loss: ${err.message}
|
|
20170
|
-
`);
|
|
20171
|
-
return;
|
|
20254
|
+
return false;
|
|
20172
20255
|
}
|
|
20256
|
+
const snapshotBytes = fsyncAndMeasure(snapshotPath, logPath, tag);
|
|
20257
|
+
if (snapshotBytes === null)
|
|
20258
|
+
return false;
|
|
20173
20259
|
try {
|
|
20174
20260
|
const dirFd = fs2.openSync(path2.dirname(snapshotPath), "r");
|
|
20175
20261
|
try {
|
|
@@ -20178,15 +20264,78 @@ function rotateAuditLog(logPath, maxFiles) {
|
|
|
20178
20264
|
fs2.closeSync(dirFd);
|
|
20179
20265
|
}
|
|
20180
20266
|
} catch {}
|
|
20267
|
+
if (!held(`truncate ${logPath}`))
|
|
20268
|
+
return false;
|
|
20269
|
+
let liveBytes;
|
|
20270
|
+
try {
|
|
20271
|
+
liveBytes = fs2.statSync(logPath).size;
|
|
20272
|
+
} catch (err) {
|
|
20273
|
+
process.stderr.write(`[${tag}] ERROR: could not re-stat active log ${logPath} before truncating; leaving it intact to avoid data loss: ${err.message}
|
|
20274
|
+
`);
|
|
20275
|
+
return false;
|
|
20276
|
+
}
|
|
20277
|
+
if (liveBytes < snapshotBytes) {
|
|
20278
|
+
process.stderr.write(`[${tag}] WARN: active log ${logPath} shrank from ${snapshotBytes} to ${liveBytes} bytes after we snapshotted it; another rotator truncated it and these rows are its, not ours — declining to truncate
|
|
20279
|
+
`);
|
|
20280
|
+
return false;
|
|
20281
|
+
}
|
|
20181
20282
|
try {
|
|
20182
20283
|
fs2.truncateSync(logPath, 0);
|
|
20183
20284
|
} catch (err) {
|
|
20184
|
-
process.stderr.write(`[
|
|
20285
|
+
process.stderr.write(`[${tag}] ERROR: could not truncate active log ${logPath}: ${err.message}
|
|
20286
|
+
`);
|
|
20287
|
+
return false;
|
|
20288
|
+
}
|
|
20289
|
+
if (stillHeld && !stillHeld()) {
|
|
20290
|
+
process.stderr.write(`[${tag}] ERROR: truncated ${logPath} after our rotation lock was reclaimed; rows another rotator wrote between our size check and our truncate are LOST
|
|
20185
20291
|
`);
|
|
20186
20292
|
}
|
|
20293
|
+
return true;
|
|
20294
|
+
}
|
|
20295
|
+
function resolveRotationConfig(args) {
|
|
20296
|
+
const env = args.env ?? process.env;
|
|
20297
|
+
const envBytes = Number(env[args.envBytesVar]);
|
|
20298
|
+
const envFiles = Number(env[args.envFilesVar]);
|
|
20299
|
+
const maxBytes = args.maxBytes !== undefined && args.maxBytes !== 0 ? args.maxBytes : Number.isFinite(envBytes) && envBytes !== 0 ? envBytes : args.defaultBytes;
|
|
20300
|
+
const maxFiles = args.maxFiles !== undefined && args.maxFiles > 0 ? args.maxFiles : Number.isFinite(envFiles) && envFiles > 0 ? envFiles : args.defaultFiles;
|
|
20301
|
+
return { maxBytes, maxFiles };
|
|
20302
|
+
}
|
|
20303
|
+
|
|
20304
|
+
// src/vault/broker/audit-log.ts
|
|
20305
|
+
function failOpenStatePath(logPath) {
|
|
20306
|
+
return `${logPath}.failopen`;
|
|
20307
|
+
}
|
|
20308
|
+
function readFailOpenState(statePath) {
|
|
20309
|
+
try {
|
|
20310
|
+
const raw = fs3.readFileSync(statePath, "utf8");
|
|
20311
|
+
const parsed = JSON.parse(raw);
|
|
20312
|
+
const count = Number(parsed.failOpenCount);
|
|
20313
|
+
return {
|
|
20314
|
+
failOpenCount: Number.isFinite(count) && count > 0 ? count : 0,
|
|
20315
|
+
lastFailureTs: typeof parsed.lastFailureTs === "string" ? parsed.lastFailureTs : undefined,
|
|
20316
|
+
lastError: typeof parsed.lastError === "string" ? parsed.lastError : undefined
|
|
20317
|
+
};
|
|
20318
|
+
} catch {
|
|
20319
|
+
return { failOpenCount: 0 };
|
|
20320
|
+
}
|
|
20321
|
+
}
|
|
20322
|
+
var DEFAULT_AUDIT_MAX_BYTES = 32 * 1024 * 1024;
|
|
20323
|
+
var DEFAULT_AUDIT_MAX_FILES = 5;
|
|
20324
|
+
function resolveRotation(opts) {
|
|
20325
|
+
return resolveRotationConfig({
|
|
20326
|
+
maxBytes: opts.maxBytes,
|
|
20327
|
+
maxFiles: opts.maxFiles,
|
|
20328
|
+
envBytesVar: "SWITCHROOM_VAULT_AUDIT_MAX_BYTES",
|
|
20329
|
+
envFilesVar: "SWITCHROOM_VAULT_AUDIT_MAX_FILES",
|
|
20330
|
+
defaultBytes: DEFAULT_AUDIT_MAX_BYTES,
|
|
20331
|
+
defaultFiles: DEFAULT_AUDIT_MAX_FILES
|
|
20332
|
+
});
|
|
20333
|
+
}
|
|
20334
|
+
function rotateAuditLog(logPath, maxFiles) {
|
|
20335
|
+
rotateLogFile(logPath, maxFiles, "vault-audit");
|
|
20187
20336
|
}
|
|
20188
20337
|
function defaultAuditLogPath() {
|
|
20189
|
-
return
|
|
20338
|
+
return path3.join(os2.homedir(), ".switchroom", "vault-audit.log");
|
|
20190
20339
|
}
|
|
20191
20340
|
function callerFromPeer(peer) {
|
|
20192
20341
|
if (peer.systemdUnit !== null && peer.systemdUnit.length > 0) {
|
|
@@ -20208,7 +20357,7 @@ function createAuditLogger(opts = {}) {
|
|
|
20208
20357
|
lastError: String(err.message).slice(0, 300)
|
|
20209
20358
|
};
|
|
20210
20359
|
try {
|
|
20211
|
-
|
|
20360
|
+
fs3.writeFileSync(statePath, `${JSON.stringify(state)}
|
|
20212
20361
|
`, { mode: 384 });
|
|
20213
20362
|
} catch (persistErr) {
|
|
20214
20363
|
process.stderr.write(`[vault-audit] ERROR: could not persist fail-open counter ${statePath}: ${persistErr.message}
|
|
@@ -20222,7 +20371,7 @@ function createAuditLogger(opts = {}) {
|
|
|
20222
20371
|
write(entry) {
|
|
20223
20372
|
if (rotation.maxBytes > 0) {
|
|
20224
20373
|
try {
|
|
20225
|
-
const size =
|
|
20374
|
+
const size = fs3.statSync(logPath).size;
|
|
20226
20375
|
if (size >= rotation.maxBytes) {
|
|
20227
20376
|
rotateAuditLog(logPath, rotation.maxFiles);
|
|
20228
20377
|
}
|
|
@@ -20231,7 +20380,7 @@ function createAuditLogger(opts = {}) {
|
|
|
20231
20380
|
const { line, next } = chainRow(chain, entry);
|
|
20232
20381
|
let fd;
|
|
20233
20382
|
try {
|
|
20234
|
-
fd =
|
|
20383
|
+
fd = fs3.openSync(logPath, "a", 384);
|
|
20235
20384
|
} catch (err) {
|
|
20236
20385
|
process.stderr.write(`[vault-audit] ERROR: could not open audit log ${logPath}: ${err.message}
|
|
20237
20386
|
`);
|
|
@@ -20240,7 +20389,7 @@ function createAuditLogger(opts = {}) {
|
|
|
20240
20389
|
}
|
|
20241
20390
|
let durable = false;
|
|
20242
20391
|
try {
|
|
20243
|
-
|
|
20392
|
+
fs3.writeSync(fd, line);
|
|
20244
20393
|
chain = next;
|
|
20245
20394
|
durable = true;
|
|
20246
20395
|
} catch (err) {
|
|
@@ -20249,7 +20398,7 @@ function createAuditLogger(opts = {}) {
|
|
|
20249
20398
|
recordFailOpen(err);
|
|
20250
20399
|
} finally {
|
|
20251
20400
|
try {
|
|
20252
|
-
|
|
20401
|
+
fs3.closeSync(fd);
|
|
20253
20402
|
} catch (closeErr) {
|
|
20254
20403
|
process.stderr.write(`[vault-audit] ERROR: could not close audit log fd: ${closeErr.message}
|
|
20255
20404
|
`);
|
|
@@ -22168,8 +22317,8 @@ function adminOnlyKeysBeingAdded(requestedKeys, existingKeys, patterns) {
|
|
|
22168
22317
|
}
|
|
22169
22318
|
|
|
22170
22319
|
// src/vault/grants-db.ts
|
|
22171
|
-
import * as
|
|
22172
|
-
import * as
|
|
22320
|
+
import * as path4 from "node:path";
|
|
22321
|
+
import * as fs4 from "node:fs";
|
|
22173
22322
|
import { Database } from "bun:sqlite";
|
|
22174
22323
|
|
|
22175
22324
|
// src/vault/approvals/schema.ts
|
|
@@ -22262,12 +22411,12 @@ function isGrantsDbCorruption(err) {
|
|
|
22262
22411
|
function quarantineGrantsDb(dbPath) {
|
|
22263
22412
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
22264
22413
|
const quarantinePath = `${dbPath}.corrupt-${stamp}`;
|
|
22265
|
-
|
|
22414
|
+
fs4.renameSync(dbPath, quarantinePath);
|
|
22266
22415
|
for (const suffix of WAL_SIDECAR_SUFFIXES2) {
|
|
22267
22416
|
const sidecar = `${dbPath}${suffix}`;
|
|
22268
|
-
if (
|
|
22417
|
+
if (fs4.existsSync(sidecar)) {
|
|
22269
22418
|
try {
|
|
22270
|
-
|
|
22419
|
+
fs4.renameSync(sidecar, `${quarantinePath}${suffix}`);
|
|
22271
22420
|
} catch {}
|
|
22272
22421
|
}
|
|
22273
22422
|
}
|
|
@@ -22277,7 +22426,7 @@ function openAndMigrate(dbPath) {
|
|
|
22277
22426
|
const db = new Database(dbPath, { create: true });
|
|
22278
22427
|
try {
|
|
22279
22428
|
try {
|
|
22280
|
-
|
|
22429
|
+
fs4.chmodSync(dbPath, 384);
|
|
22281
22430
|
} catch {}
|
|
22282
22431
|
db.run("PRAGMA journal_mode=WAL");
|
|
22283
22432
|
db.run("PRAGMA busy_timeout=5000");
|
|
@@ -22293,8 +22442,8 @@ function openAndMigrate(dbPath) {
|
|
|
22293
22442
|
return db;
|
|
22294
22443
|
}
|
|
22295
22444
|
function openGrantsDb(dbPath = DEFAULT_GRANTS_DB_PATH, opener = openAndMigrate) {
|
|
22296
|
-
const dir =
|
|
22297
|
-
|
|
22445
|
+
const dir = path4.dirname(dbPath);
|
|
22446
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
22298
22447
|
migrateLegacyGrantsDbLocation(dbPath);
|
|
22299
22448
|
try {
|
|
22300
22449
|
return opener(dbPath);
|
|
@@ -22318,6 +22467,7 @@ function canonicalizeApproverSet(approvers) {
|
|
|
22318
22467
|
}
|
|
22319
22468
|
|
|
22320
22469
|
// src/vault/approvals/kernel.ts
|
|
22470
|
+
var ALLOW_ONCE_CONSUMED_REASON = "allow_once_consumed";
|
|
22321
22471
|
function generateRequestId() {
|
|
22322
22472
|
return randomBytes5(16).toString("hex");
|
|
22323
22473
|
}
|
|
@@ -22444,6 +22594,38 @@ function evaluateDecisionRow(db, row, currentCanonical, opts, now) {
|
|
|
22444
22594
|
if (decision.decision === "deny") {
|
|
22445
22595
|
return { state: "denied", decision };
|
|
22446
22596
|
}
|
|
22597
|
+
if (decision.decision === "allow_once") {
|
|
22598
|
+
const burn = db.run(`UPDATE approval_decisions
|
|
22599
|
+
SET last_used_at = ?, revoked_at = ?, revoke_reason = ?
|
|
22600
|
+
WHERE id = ? AND revoked_at IS NULL`, [now, now, ALLOW_ONCE_CONSUMED_REASON, decision.id]);
|
|
22601
|
+
if ((burn.changes ?? 0) === 0) {
|
|
22602
|
+
audit(db, "deny", {
|
|
22603
|
+
agent_unit: decision.agent_unit,
|
|
22604
|
+
scope: decision.scope,
|
|
22605
|
+
action: decision.action,
|
|
22606
|
+
decision_id: decision.id,
|
|
22607
|
+
context: { reason: ALLOW_ONCE_CONSUMED_REASON }
|
|
22608
|
+
});
|
|
22609
|
+
return { state: "denied", decision };
|
|
22610
|
+
}
|
|
22611
|
+
decision.last_used_at = now;
|
|
22612
|
+
decision.revoked_at = now;
|
|
22613
|
+
decision.revoke_reason = ALLOW_ONCE_CONSUMED_REASON;
|
|
22614
|
+
audit(db, "match", {
|
|
22615
|
+
agent_unit: decision.agent_unit,
|
|
22616
|
+
scope: decision.scope,
|
|
22617
|
+
action: decision.action,
|
|
22618
|
+
decision_id: decision.id
|
|
22619
|
+
});
|
|
22620
|
+
audit(db, "revoke", {
|
|
22621
|
+
agent_unit: decision.agent_unit,
|
|
22622
|
+
scope: decision.scope,
|
|
22623
|
+
action: decision.action,
|
|
22624
|
+
decision_id: decision.id,
|
|
22625
|
+
context: { actor: "kernel", reason: ALLOW_ONCE_CONSUMED_REASON }
|
|
22626
|
+
});
|
|
22627
|
+
return { state: "granted", decision };
|
|
22628
|
+
}
|
|
22447
22629
|
if (decision.decision === "allow_ttl") {
|
|
22448
22630
|
const maxLifetime = opts.max_ttl_lifetime_ms ?? DEFAULT_MAX_TTL_LIFETIME_MS;
|
|
22449
22631
|
const hardCap = decision.granted_at + maxLifetime;
|
|
@@ -22512,7 +22694,8 @@ function consumeNonce(db, request_id, now = Date.now()) {
|
|
|
22512
22694
|
}
|
|
22513
22695
|
function recordDecision(db, input, now = Date.now()) {
|
|
22514
22696
|
const id = randomUUID();
|
|
22515
|
-
const
|
|
22697
|
+
const maxLifetime = input.max_ttl_lifetime_ms ?? DEFAULT_MAX_TTL_LIFETIME_MS;
|
|
22698
|
+
const ttl_expires_at = input.decision === "allow_ttl" && input.ttl_ms ? now + Math.min(input.ttl_ms, maxLifetime) : null;
|
|
22516
22699
|
const canonical = canonicalizeApproverSet(input.approver_set);
|
|
22517
22700
|
db.run(`INSERT INTO approval_decisions
|
|
22518
22701
|
(id, agent_unit, scope, action, decision,
|
|
@@ -23827,10 +24010,10 @@ class VaultBroker {
|
|
|
23827
24010
|
return;
|
|
23828
24011
|
}
|
|
23829
24012
|
try {
|
|
23830
|
-
const agentsDir = this.config ? resolveAgentsDir(this.config) :
|
|
23831
|
-
const tokenDir =
|
|
24013
|
+
const agentsDir = this.config ? resolveAgentsDir(this.config) : path5.join(os3.homedir(), ".switchroom", "agents");
|
|
24014
|
+
const tokenDir = path5.join(agentsDir, agent);
|
|
23832
24015
|
mkdirSync7(tokenDir, { recursive: true });
|
|
23833
|
-
const tokenPath =
|
|
24016
|
+
const tokenPath = path5.join(tokenDir, ".vault-token");
|
|
23834
24017
|
const tmpPath = `${tokenPath}.tmp.${process.pid}`;
|
|
23835
24018
|
writeFileSync3(tmpPath, mintResult.token, { mode: 384 });
|
|
23836
24019
|
renameSync8(tmpPath, tokenPath);
|
|
@@ -23892,8 +24075,8 @@ class VaultBroker {
|
|
|
23892
24075
|
try {
|
|
23893
24076
|
const row = this.grantsDb.query("SELECT agent_slug FROM vault_grants WHERE id = ?").get(id);
|
|
23894
24077
|
if (row && AgentNameSchema.safeParse(row.agent_slug).success) {
|
|
23895
|
-
const agentsDir = this.config ? resolveAgentsDir(this.config) :
|
|
23896
|
-
const tokenPath =
|
|
24078
|
+
const agentsDir = this.config ? resolveAgentsDir(this.config) : path5.join(os3.homedir(), ".switchroom", "agents");
|
|
24079
|
+
const tokenPath = path5.join(agentsDir, row.agent_slug, ".vault-token");
|
|
23897
24080
|
if (existsSync12(tokenPath)) {
|
|
23898
24081
|
try {
|
|
23899
24082
|
unlinkSync6(tokenPath);
|
|
@@ -24135,7 +24318,7 @@ class VaultBroker {
|
|
|
24135
24318
|
if (!existsSync12(filePath))
|
|
24136
24319
|
return false;
|
|
24137
24320
|
try {
|
|
24138
|
-
if (
|
|
24321
|
+
if (statSync7(filePath).size === 0)
|
|
24139
24322
|
return false;
|
|
24140
24323
|
} catch {
|
|
24141
24324
|
return false;
|
|
@@ -24214,12 +24397,12 @@ class VaultBroker {
|
|
|
24214
24397
|
}
|
|
24215
24398
|
function detectVaultLayoutDrift(vaultPath) {
|
|
24216
24399
|
const dir = dirname7(vaultPath);
|
|
24217
|
-
if (
|
|
24400
|
+
if (basename5(dir) !== "vault")
|
|
24218
24401
|
return;
|
|
24219
|
-
if (
|
|
24402
|
+
if (basename5(vaultPath) !== "vault.enc")
|
|
24220
24403
|
return;
|
|
24221
24404
|
const switchroomDir = dirname7(dir);
|
|
24222
|
-
if (
|
|
24405
|
+
if (basename5(switchroomDir) !== ".switchroom")
|
|
24223
24406
|
return;
|
|
24224
24407
|
const home2 = dirname7(switchroomDir);
|
|
24225
24408
|
const result = inspectVaultLayout(home2);
|
|
@@ -24248,7 +24431,7 @@ async function main() {
|
|
|
24248
24431
|
let perAgentTargets = [];
|
|
24249
24432
|
try {
|
|
24250
24433
|
if (existsSync12(perAgentDir)) {
|
|
24251
|
-
const entries =
|
|
24434
|
+
const entries = readdirSync5(perAgentDir, { withFileTypes: true });
|
|
24252
24435
|
const flat = [];
|
|
24253
24436
|
const subdirs = [];
|
|
24254
24437
|
for (const e of entries) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchroom",
|
|
3
3
|
"//version": "NOT the release version — source of truth is the git tag, resolved by scripts/build.mjs:resolveVersion() (see CLAUDE.md > Standard release process). This field is stale by design and only the Layer-4 dev/non-tag fallback for build.mjs + src/cli/resolve-version.ts; do NOT bump it expecting a release to pick it up. npm-pack tarball naming needs a real version — do that as an UNCOMMITTED pack-time bump (see release step 6), never a committed one.",
|
|
4
|
-
"version": "0.19.
|
|
4
|
+
"version": "0.19.22",
|
|
5
5
|
"description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -26,19 +26,22 @@
|
|
|
26
26
|
"pretest": "npm run build",
|
|
27
27
|
"test": "vitest run && bun test telegram-plugin/tests/catch-all-forwarded-history.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/ipc-server-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/registry/api-registry.test.ts telegram-plugin/registry/turns-schema.test.ts telegram-plugin/tests/idle-footer-wiring.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
|
|
28
28
|
"test:vitest": "vitest run",
|
|
29
|
-
"test:bun": "bun test telegram-plugin/tests/catch-all-forwarded-history.test.ts src/vault/grants.test.ts src/vault/grants-db.test.ts src/vault/write-grants.test.ts src/vault/broker/server-grants.test.ts src/vault/broker/server-write-grants.test.ts src/vault/broker/server-scope-persist.test.ts src/vault/broker/server-tokenless-scope.test.ts src/vault/broker/server-mint-grant-passphrase-attest.test.ts src/vault/broker/server-passphrase-attest.test.ts src/vault/broker/server-mint-grant-posture-attest.test.ts src/vault/broker/server-admin-only-keys.test.ts src/vault/broker/client-token.test.ts src/vault/broker/server-unlock.test.ts src/vault/broker/auto-unlock.test.ts src/vault/broker/drift-detection.test.ts tests/vault-broker-passphrase.test.ts src/cli/vault-get-broker.test.ts src/vault/resolver-via-broker.test.ts src/vault/broker/scope.test.ts src/vault/broker/server.test.ts src/litellm/provision-apply-e2e.test.ts src/drive/disconnect.test.ts src/drive/grants.test.ts src/drive/oauth.test.ts src/drive/onboarding.test.ts src/drive/reconciler.test.ts src/drive/vault-slots.test.ts src/drive/wrapper.test.ts src/vault/approvals/kernel.test.ts src/vault/approvals/approval-origin.test.ts src/vault/approvals/schema-idempotent.test.ts src/vault/broker/server-approvals.test.ts telegram-plugin/tests/boot-probes.test.ts telegram-plugin/tests/boot-version-string.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/ipc-server-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/tests/subagent-watcher-workflow-visibility.test.ts telegram-plugin/uat/load-env.test.ts telegram-plugin/uat/feed-matcher.test.ts telegram-plugin/uat/uat-driver.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
|
|
29
|
+
"test:bun": "bun test telegram-plugin/tests/catch-all-forwarded-history.test.ts src/vault/grants.test.ts src/vault/grants-db.test.ts src/vault/write-grants.test.ts src/vault/broker/server-grants.test.ts src/vault/broker/server-write-grants.test.ts src/vault/broker/server-scope-persist.test.ts src/vault/broker/server-tokenless-scope.test.ts src/vault/broker/server-mint-grant-passphrase-attest.test.ts src/vault/broker/server-passphrase-attest.test.ts src/vault/broker/server-mint-grant-posture-attest.test.ts src/vault/broker/server-admin-only-keys.test.ts src/vault/broker/client-token.test.ts src/vault/broker/server-unlock.test.ts src/vault/broker/auto-unlock.test.ts src/vault/broker/drift-detection.test.ts tests/vault-broker-passphrase.test.ts src/cli/vault-get-broker.test.ts src/vault/resolver-via-broker.test.ts src/vault/broker/scope.test.ts src/vault/broker/server.test.ts src/litellm/provision-apply-e2e.test.ts src/drive/disconnect.test.ts src/drive/grants.test.ts src/drive/oauth.test.ts src/drive/onboarding.test.ts src/drive/reconciler.test.ts src/drive/vault-slots.test.ts src/drive/wrapper.test.ts src/vault/approvals/kernel.test.ts src/vault/approvals/approval-origin.test.ts src/vault/approvals/self-approval-bypass.test.ts src/vault/approvals/schema-idempotent.test.ts src/vault/broker/server-approvals.test.ts telegram-plugin/tests/boot-probes.test.ts telegram-plugin/tests/boot-version-string.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/ipc-server-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/tests/subagent-watcher-workflow-visibility.test.ts telegram-plugin/uat/load-env.test.ts telegram-plugin/uat/feed-matcher.test.ts telegram-plugin/uat/uat-driver.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
|
|
30
30
|
"test:watch": "vitest",
|
|
31
|
-
"lint": "tsc --noEmit && node scripts/check-plugin-references.mjs && bash scripts/check-bot-api-wrapping.sh && node scripts/check-bun-test-imports.mjs && node scripts/check-no-pii-secrets.mjs && node scripts/check-vault-test-hermeticity.mjs && node scripts/check-no-broadcast-delivery.mjs && node scripts/check-stale-tool-descriptions.mjs && node scripts/check-mcp-instructions-budget.mjs && node scripts/check-web-subscription-honest.mjs && node scripts/check-no-unpinned-npx-playwright.mjs && node scripts/check-gateway-line-ratchet.mjs && node scripts/check-litellm-config-guard.mjs",
|
|
31
|
+
"lint": "tsc --noEmit && node scripts/check-plugin-references.mjs && bash scripts/check-bot-api-wrapping.sh && node scripts/check-bun-test-imports.mjs && node scripts/check-bun-module-mock-scope.mjs && node scripts/check-no-pii-secrets.mjs && node scripts/check-vault-test-hermeticity.mjs && node scripts/check-auth-test-hermeticity.mjs && node scripts/check-no-broadcast-delivery.mjs && node scripts/check-stale-tool-descriptions.mjs && node scripts/check-mcp-instructions-budget.mjs && node scripts/check-web-subscription-honest.mjs && node scripts/check-no-unpinned-npx-playwright.mjs && node scripts/check-gateway-line-ratchet.mjs && node scripts/check-litellm-config-guard.mjs && node scripts/check-release-asset-names.mjs",
|
|
32
32
|
"lint:tsc": "tsc --noEmit",
|
|
33
33
|
"lint:plugin-references": "node scripts/check-plugin-references.mjs",
|
|
34
34
|
"lint:bot-api-wrapping": "bash scripts/check-bot-api-wrapping.sh",
|
|
35
35
|
"lint:bun-test-imports": "node scripts/check-bun-test-imports.mjs",
|
|
36
|
+
"lint:bun-module-mock-scope": "node scripts/check-bun-module-mock-scope.mjs",
|
|
36
37
|
"lint:no-pii": "node scripts/check-no-pii-secrets.mjs",
|
|
38
|
+
"lint:auth-test-hermeticity": "node scripts/check-auth-test-hermeticity.mjs",
|
|
37
39
|
"lint:web-subscription-honest": "node scripts/check-web-subscription-honest.mjs",
|
|
38
40
|
"lint:no-broadcast-delivery": "node scripts/check-no-broadcast-delivery.mjs",
|
|
39
41
|
"lint:gateway-line-ratchet": "node scripts/check-gateway-line-ratchet.mjs",
|
|
40
42
|
"lint:mcp-instructions-budget": "node scripts/check-mcp-instructions-budget.mjs",
|
|
41
43
|
"lint:litellm-config-guard": "node scripts/check-litellm-config-guard.mjs",
|
|
44
|
+
"lint:release-asset-contract": "node scripts/check-release-asset-names.mjs",
|
|
42
45
|
"prepublishOnly": "npm run build && npm run lint && npm test"
|
|
43
46
|
},
|
|
44
47
|
"dependencies": {
|