switchroom 0.14.33 → 0.14.35
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 +8 -0
- package/dist/auth-broker/index.js +8 -0
- package/dist/cli/notion-write-pretool.mjs +10 -1
- package/dist/cli/switchroom.js +120 -16
- package/dist/host-control/main.js +8 -0
- package/dist/vault/approvals/kernel-server.js +30 -7
- package/dist/vault/broker/server.js +62 -7
- package/package.json +2 -2
- package/telegram-plugin/dist/gateway/gateway.js +20 -5
|
@@ -11219,6 +11219,7 @@ var profileFields = {
|
|
|
11219
11219
|
}).optional()
|
|
11220
11220
|
}).optional(),
|
|
11221
11221
|
schedule: exports_external.array(ScheduleEntrySchema).optional(),
|
|
11222
|
+
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional().describe("Operator-granted STANDING vault keys this agent may read via the " + "broker — independent of any cron or MCP server. Use when an agent " + "needs a credential both interactively and in its own (agent-managed) " + "schedules, so the grant lives with the agent rather than welded to a " + "specific cron's `secrets[]`. OPERATOR-SET ONLY: agents cannot edit " + "switchroom.yaml or self-grant (reference/vision.md outcome 2 — 'you " + "hold the leash; only your tap grants it'). Exact key names. Cascades " + "UNION across defaults -> profile -> agent (see docs/configuration.md)."),
|
|
11222
11223
|
reactions: ReactionsSchema,
|
|
11223
11224
|
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only").optional(),
|
|
11224
11225
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
@@ -11287,6 +11288,7 @@ var AgentSchema = exports_external.object({
|
|
|
11287
11288
|
tools: AgentToolsSchema,
|
|
11288
11289
|
memory: AgentMemorySchema,
|
|
11289
11290
|
schedule: exports_external.array(ScheduleEntrySchema).default([]),
|
|
11291
|
+
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional(),
|
|
11290
11292
|
reactions: ReactionsSchema,
|
|
11291
11293
|
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only (no spaces or shell specials)").optional().describe("Claude model override (e.g., 'claude-sonnet-4-6')"),
|
|
11292
11294
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "Per-agent override wins over defaults.thinking_effort. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
@@ -11756,6 +11758,12 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
11756
11758
|
deny: dedupe([...dDeny, ...aDeny])
|
|
11757
11759
|
};
|
|
11758
11760
|
}
|
|
11761
|
+
if (defaults.secrets || merged.secrets) {
|
|
11762
|
+
merged.secrets = dedupe([
|
|
11763
|
+
...defaults.secrets ?? [],
|
|
11764
|
+
...merged.secrets ?? []
|
|
11765
|
+
]);
|
|
11766
|
+
}
|
|
11759
11767
|
if (defaults.soul || merged.soul) {
|
|
11760
11768
|
const base = defaults.soul ?? {};
|
|
11761
11769
|
const override = merged.soul ?? {};
|
|
@@ -11219,6 +11219,7 @@ var profileFields = {
|
|
|
11219
11219
|
}).optional()
|
|
11220
11220
|
}).optional(),
|
|
11221
11221
|
schedule: exports_external.array(ScheduleEntrySchema).optional(),
|
|
11222
|
+
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional().describe("Operator-granted STANDING vault keys this agent may read via the " + "broker — independent of any cron or MCP server. Use when an agent " + "needs a credential both interactively and in its own (agent-managed) " + "schedules, so the grant lives with the agent rather than welded to a " + "specific cron's `secrets[]`. OPERATOR-SET ONLY: agents cannot edit " + "switchroom.yaml or self-grant (reference/vision.md outcome 2 — 'you " + "hold the leash; only your tap grants it'). Exact key names. Cascades " + "UNION across defaults -> profile -> agent (see docs/configuration.md)."),
|
|
11222
11223
|
reactions: ReactionsSchema,
|
|
11223
11224
|
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only").optional(),
|
|
11224
11225
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
@@ -11287,6 +11288,7 @@ var AgentSchema = exports_external.object({
|
|
|
11287
11288
|
tools: AgentToolsSchema,
|
|
11288
11289
|
memory: AgentMemorySchema,
|
|
11289
11290
|
schedule: exports_external.array(ScheduleEntrySchema).default([]),
|
|
11291
|
+
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional(),
|
|
11290
11292
|
reactions: ReactionsSchema,
|
|
11291
11293
|
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only (no spaces or shell specials)").optional().describe("Claude model override (e.g., 'claude-sonnet-4-6')"),
|
|
11292
11294
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "Per-agent override wins over defaults.thinking_effort. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
@@ -11756,6 +11758,12 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
11756
11758
|
deny: dedupe([...dDeny, ...aDeny])
|
|
11757
11759
|
};
|
|
11758
11760
|
}
|
|
11761
|
+
if (defaults.secrets || merged.secrets) {
|
|
11762
|
+
merged.secrets = dedupe([
|
|
11763
|
+
...defaults.secrets ?? [],
|
|
11764
|
+
...merged.secrets ?? []
|
|
11765
|
+
]);
|
|
11766
|
+
}
|
|
11759
11767
|
if (defaults.soul || merged.soul) {
|
|
11760
11768
|
const base = defaults.soul ?? {};
|
|
11761
11769
|
const override = merged.soul ?? {};
|
|
@@ -10969,7 +10969,8 @@ var init_protocol = __esm(() => {
|
|
|
10969
10969
|
description: exports_external.string().optional(),
|
|
10970
10970
|
write_keys: exports_external.array(exports_external.string().min(1)).optional(),
|
|
10971
10971
|
passphrase: exports_external.string().optional(),
|
|
10972
|
-
attest_via_posture: exports_external.boolean().optional()
|
|
10972
|
+
attest_via_posture: exports_external.boolean().optional(),
|
|
10973
|
+
decision_id: exports_external.string().optional()
|
|
10973
10974
|
});
|
|
10974
10975
|
ListGrantsRequestSchema = exports_external.object({
|
|
10975
10976
|
v: exports_external.literal(1),
|
|
@@ -11966,6 +11967,7 @@ var profileFields = {
|
|
|
11966
11967
|
}).optional()
|
|
11967
11968
|
}).optional(),
|
|
11968
11969
|
schedule: exports_external.array(ScheduleEntrySchema).optional(),
|
|
11970
|
+
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional().describe("Operator-granted STANDING vault keys this agent may read via the " + "broker \u2014 independent of any cron or MCP server. Use when an agent " + "needs a credential both interactively and in its own (agent-managed) " + "schedules, so the grant lives with the agent rather than welded to a " + "specific cron's `secrets[]`. OPERATOR-SET ONLY: agents cannot edit " + "switchroom.yaml or self-grant (reference/vision.md outcome 2 \u2014 'you " + "hold the leash; only your tap grants it'). Exact key names. Cascades " + "UNION across defaults -> profile -> agent (see docs/configuration.md)."),
|
|
11969
11971
|
reactions: ReactionsSchema,
|
|
11970
11972
|
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only").optional(),
|
|
11971
11973
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
@@ -12034,6 +12036,7 @@ var AgentSchema = exports_external.object({
|
|
|
12034
12036
|
tools: AgentToolsSchema,
|
|
12035
12037
|
memory: AgentMemorySchema,
|
|
12036
12038
|
schedule: exports_external.array(ScheduleEntrySchema).default([]),
|
|
12039
|
+
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional(),
|
|
12037
12040
|
reactions: ReactionsSchema,
|
|
12038
12041
|
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only (no spaces or shell specials)").optional().describe("Claude model override (e.g., 'claude-sonnet-4-6')"),
|
|
12039
12042
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "Per-agent override wins over defaults.thinking_effort. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
@@ -12505,6 +12508,12 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
12505
12508
|
deny: dedupe([...dDeny, ...aDeny])
|
|
12506
12509
|
};
|
|
12507
12510
|
}
|
|
12511
|
+
if (defaults.secrets || merged.secrets) {
|
|
12512
|
+
merged.secrets = dedupe([
|
|
12513
|
+
...defaults.secrets ?? [],
|
|
12514
|
+
...merged.secrets ?? []
|
|
12515
|
+
]);
|
|
12516
|
+
}
|
|
12508
12517
|
if (defaults.soul || merged.soul) {
|
|
12509
12518
|
const base = defaults.soul ?? {};
|
|
12510
12519
|
const override = merged.soul ?? {};
|
package/dist/cli/switchroom.js
CHANGED
|
@@ -13783,6 +13783,7 @@ var init_schema = __esm(() => {
|
|
|
13783
13783
|
}).optional()
|
|
13784
13784
|
}).optional(),
|
|
13785
13785
|
schedule: exports_external.array(ScheduleEntrySchema).optional(),
|
|
13786
|
+
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional().describe("Operator-granted STANDING vault keys this agent may read via the " + "broker \u2014 independent of any cron or MCP server. Use when an agent " + "needs a credential both interactively and in its own (agent-managed) " + "schedules, so the grant lives with the agent rather than welded to a " + "specific cron's `secrets[]`. OPERATOR-SET ONLY: agents cannot edit " + "switchroom.yaml or self-grant (reference/vision.md outcome 2 \u2014 'you " + "hold the leash; only your tap grants it'). Exact key names. Cascades " + "UNION across defaults -> profile -> agent (see docs/configuration.md)."),
|
|
13786
13787
|
reactions: ReactionsSchema,
|
|
13787
13788
|
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only").optional(),
|
|
13788
13789
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
@@ -13851,6 +13852,7 @@ var init_schema = __esm(() => {
|
|
|
13851
13852
|
tools: AgentToolsSchema,
|
|
13852
13853
|
memory: AgentMemorySchema,
|
|
13853
13854
|
schedule: exports_external.array(ScheduleEntrySchema).default([]),
|
|
13855
|
+
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional(),
|
|
13854
13856
|
reactions: ReactionsSchema,
|
|
13855
13857
|
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only (no spaces or shell specials)").optional().describe("Claude model override (e.g., 'claude-sonnet-4-6')"),
|
|
13856
13858
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "Per-agent override wins over defaults.thinking_effort. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
@@ -14372,6 +14374,12 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
14372
14374
|
deny: dedupe([...dDeny, ...aDeny])
|
|
14373
14375
|
};
|
|
14374
14376
|
}
|
|
14377
|
+
if (defaults.secrets || merged.secrets) {
|
|
14378
|
+
merged.secrets = dedupe([
|
|
14379
|
+
...defaults.secrets ?? [],
|
|
14380
|
+
...merged.secrets ?? []
|
|
14381
|
+
]);
|
|
14382
|
+
}
|
|
14375
14383
|
if (defaults.soul || merged.soul) {
|
|
14376
14384
|
const base = defaults.soul ?? {};
|
|
14377
14385
|
const override = merged.soul ?? {};
|
|
@@ -22004,7 +22012,8 @@ var init_protocol = __esm(() => {
|
|
|
22004
22012
|
description: exports_external.string().optional(),
|
|
22005
22013
|
write_keys: exports_external.array(exports_external.string().min(1)).optional(),
|
|
22006
22014
|
passphrase: exports_external.string().optional(),
|
|
22007
|
-
attest_via_posture: exports_external.boolean().optional()
|
|
22015
|
+
attest_via_posture: exports_external.boolean().optional(),
|
|
22016
|
+
decision_id: exports_external.string().optional()
|
|
22008
22017
|
});
|
|
22009
22018
|
ListGrantsRequestSchema = exports_external.object({
|
|
22010
22019
|
v: exports_external.literal(1),
|
|
@@ -27941,11 +27950,21 @@ function checkAclByAgent(config, agentName, key) {
|
|
|
27941
27950
|
return { allow: true };
|
|
27942
27951
|
}
|
|
27943
27952
|
}
|
|
27953
|
+
const cfgSecrets = config;
|
|
27954
|
+
const profileSecrets = profileName != null && profileName.length > 0 ? cfgSecrets.profiles?.[profileName]?.secrets : undefined;
|
|
27955
|
+
const standingSecrets = [
|
|
27956
|
+
...Array.isArray(cfgSecrets.defaults?.secrets) ? cfgSecrets.defaults.secrets : [],
|
|
27957
|
+
...Array.isArray(profileSecrets) ? profileSecrets : [],
|
|
27958
|
+
...Array.isArray(agentConfig.secrets) ? agentConfig.secrets : []
|
|
27959
|
+
];
|
|
27960
|
+
if (standingSecrets.includes(key)) {
|
|
27961
|
+
return { allow: true };
|
|
27962
|
+
}
|
|
27944
27963
|
const schedule = agentConfig.schedule ?? [];
|
|
27945
27964
|
if (schedule.length === 0) {
|
|
27946
27965
|
return {
|
|
27947
27966
|
allow: false,
|
|
27948
|
-
reason: `agent '${agentName}' has no schedule entries declaring 'secrets'
|
|
27967
|
+
reason: `agent '${agentName}' has no schedule entries declaring 'secrets', no mcp_servers.*.secrets[], and no agents.${agentName}.secrets[] standing grant declaring '${key}'; nothing is broker-accessible`
|
|
27949
27968
|
};
|
|
27950
27969
|
}
|
|
27951
27970
|
for (const entry of schedule) {
|
|
@@ -49420,8 +49439,8 @@ var {
|
|
|
49420
49439
|
} = import__.default;
|
|
49421
49440
|
|
|
49422
49441
|
// src/build-info.ts
|
|
49423
|
-
var VERSION = "0.14.
|
|
49424
|
-
var COMMIT_SHA = "
|
|
49442
|
+
var VERSION = "0.14.35";
|
|
49443
|
+
var COMMIT_SHA = "7ac06aea";
|
|
49425
49444
|
|
|
49426
49445
|
// src/cli/agent.ts
|
|
49427
49446
|
init_source();
|
|
@@ -50323,6 +50342,22 @@ If you genuinely need WebFetch back for one agent (e.g. a workflow
|
|
|
50323
50342
|
that depends on its specific output shape), set \`mcp_servers.webkite:
|
|
50324
50343
|
false\` in that agent's switchroom.yaml block \u2014 webkite goes away
|
|
50325
50344
|
and the native tools come back together.`;
|
|
50345
|
+
var VAULT_GUIDANCE = `## Secrets in the vault \u2014 discover, don't guess
|
|
50346
|
+
|
|
50347
|
+
Your API keys and credentials live in the Switchroom vault. For normal
|
|
50348
|
+
MCP-tool work they're injected by the launchers and you never touch the
|
|
50349
|
+
raw values. When a task needs a secret directly (a direct API call an
|
|
50350
|
+
MCP tool has no verb for), read it with \`switchroom vault get <key>\`.
|
|
50351
|
+
|
|
50352
|
+
**Never guess a key name.** Run \`switchroom vault list\` to see the
|
|
50353
|
+
exact keys you already hold \u2014 they're usually namespaced \`<you>/...\`
|
|
50354
|
+
(e.g. \`marko/postiz-api-key\`, not \`postiz/api-key\`). Use the real
|
|
50355
|
+
name from that list.
|
|
50356
|
+
|
|
50357
|
+
Only call the \`vault_request_access\` MCP tool \u2014 which pings the
|
|
50358
|
+
operator for a Telegram approval \u2014 for a key you've **confirmed via
|
|
50359
|
+
\`vault list\` you don't already have.** Requesting access to a guessed
|
|
50360
|
+
or already-held key wastes the operator's tap and fails.`;
|
|
50326
50361
|
function renderFleetInvariants() {
|
|
50327
50362
|
return [
|
|
50328
50363
|
"<!--",
|
|
@@ -50347,6 +50382,8 @@ function renderFleetInvariants() {
|
|
|
50347
50382
|
MEMORY_GUIDANCE,
|
|
50348
50383
|
"",
|
|
50349
50384
|
WEB_FETCH_GUIDANCE,
|
|
50385
|
+
"",
|
|
50386
|
+
VAULT_GUIDANCE,
|
|
50350
50387
|
""
|
|
50351
50388
|
].join(`
|
|
50352
50389
|
`);
|
|
@@ -58988,6 +59025,26 @@ function scrubSqlite(path, values, dryRun) {
|
|
|
58988
59025
|
// src/cli/vault.ts
|
|
58989
59026
|
init_client();
|
|
58990
59027
|
|
|
59028
|
+
// src/cli/vault-key-suggest.ts
|
|
59029
|
+
function tokenize(key) {
|
|
59030
|
+
return key.toLowerCase().split(/[/\-_.]+/).filter((t) => t.length > 0);
|
|
59031
|
+
}
|
|
59032
|
+
function suggestVaultKeys(requested, available, max = 3) {
|
|
59033
|
+
const want = new Set(tokenize(requested));
|
|
59034
|
+
if (want.size === 0)
|
|
59035
|
+
return [];
|
|
59036
|
+
const scored = available.map((key) => {
|
|
59037
|
+
const have = new Set(tokenize(key));
|
|
59038
|
+
let shared = 0;
|
|
59039
|
+
for (const t of have)
|
|
59040
|
+
if (want.has(t))
|
|
59041
|
+
shared++;
|
|
59042
|
+
const diff = want.size + have.size - 2 * shared;
|
|
59043
|
+
return { key, shared, diff };
|
|
59044
|
+
}).filter((c) => c.shared > 0 && c.key !== requested).sort((a, b) => b.shared - a.shared || a.diff - b.diff);
|
|
59045
|
+
return scored.slice(0, max).map((c) => c.key);
|
|
59046
|
+
}
|
|
59047
|
+
|
|
58991
59048
|
// src/cli/vault-broker.ts
|
|
58992
59049
|
init_loader();
|
|
58993
59050
|
init_loader();
|
|
@@ -61285,9 +61342,20 @@ function migrateApprovalSchema(db) {
|
|
|
61285
61342
|
approver_set_canonical TEXT NOT NULL,
|
|
61286
61343
|
last_used_at INTEGER,
|
|
61287
61344
|
revoked_at INTEGER,
|
|
61288
|
-
revoke_reason TEXT
|
|
61345
|
+
revoke_reason TEXT,
|
|
61346
|
+
-- Provenance of the operator-authorization (RFC vault-approval-hard-
|
|
61347
|
+
-- boundary). 'agent': recorded on a per-agent socket \u2014 claude shares
|
|
61348
|
+
-- that socket, so it is FORGEABLE and must NOT be trusted as proof an
|
|
61349
|
+
-- operator tapped. 'operator': recorded via the host-side verifier on a
|
|
61350
|
+
-- claude-unreachable channel \u2014 the only value the broker's mint gate
|
|
61351
|
+
-- trusts. Default 'agent' (fail-closed for the new gate).
|
|
61352
|
+
origin TEXT NOT NULL DEFAULT 'agent'
|
|
61289
61353
|
)
|
|
61290
61354
|
`);
|
|
61355
|
+
const decisionCols = db.query("PRAGMA table_info(approval_decisions)").all();
|
|
61356
|
+
if (!decisionCols.some((c) => c.name === "origin")) {
|
|
61357
|
+
db.run(`ALTER TABLE approval_decisions ADD COLUMN origin TEXT NOT NULL DEFAULT 'agent'`);
|
|
61358
|
+
}
|
|
61291
61359
|
db.run(`
|
|
61292
61360
|
CREATE INDEX IF NOT EXISTS approval_decisions_lookup
|
|
61293
61361
|
ON approval_decisions(agent_unit, scope, action)
|
|
@@ -61389,7 +61457,8 @@ function rowToDecision(row) {
|
|
|
61389
61457
|
approver_set_canonical: row.approver_set_canonical,
|
|
61390
61458
|
last_used_at: row.last_used_at ?? null,
|
|
61391
61459
|
revoked_at: row.revoked_at ?? null,
|
|
61392
|
-
revoke_reason: row.revoke_reason ?? null
|
|
61460
|
+
revoke_reason: row.revoke_reason ?? null,
|
|
61461
|
+
origin: row.origin === "operator" ? "operator" : "agent"
|
|
61393
61462
|
};
|
|
61394
61463
|
}
|
|
61395
61464
|
var MAX_PENDING_PER_AGENT = 2;
|
|
@@ -61555,8 +61624,8 @@ function recordDecision(db, input, now = Date.now()) {
|
|
|
61555
61624
|
db.run(`INSERT INTO approval_decisions
|
|
61556
61625
|
(id, agent_unit, scope, action, decision,
|
|
61557
61626
|
ttl_expires_at, granted_at, granted_by_user_id,
|
|
61558
|
-
approver_set_canonical, last_used_at, revoked_at, revoke_reason)
|
|
61559
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, NULL)`, [
|
|
61627
|
+
approver_set_canonical, last_used_at, revoked_at, revoke_reason, origin)
|
|
61628
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, NULL, ?)`, [
|
|
61560
61629
|
id,
|
|
61561
61630
|
input.nonce.agent_unit,
|
|
61562
61631
|
input.nonce.scope,
|
|
@@ -61565,7 +61634,8 @@ function recordDecision(db, input, now = Date.now()) {
|
|
|
61565
61634
|
ttl_expires_at,
|
|
61566
61635
|
now,
|
|
61567
61636
|
input.granted_by_user_id,
|
|
61568
|
-
canonical
|
|
61637
|
+
canonical,
|
|
61638
|
+
input.origin ?? "agent"
|
|
61569
61639
|
]);
|
|
61570
61640
|
db.run(`UPDATE approval_nonces SET decision_id = ? WHERE request_id = ?`, [id, input.nonce.request_id]);
|
|
61571
61641
|
const granted = input.decision === "allow_once" || input.decision === "allow_always" || input.decision === "allow_ttl";
|
|
@@ -61619,6 +61689,13 @@ function listDecisions(db, filter, now = Date.now()) {
|
|
|
61619
61689
|
const rows = db.query(sql).all(...params);
|
|
61620
61690
|
return rows.map(rowToDecision);
|
|
61621
61691
|
}
|
|
61692
|
+
function getDecision(db, id) {
|
|
61693
|
+
const row = db.query(`SELECT * FROM approval_decisions WHERE id = ?`).get(id);
|
|
61694
|
+
return row ? rowToDecision(row) : null;
|
|
61695
|
+
}
|
|
61696
|
+
function isOperatorVerifiedDecision(dec, agent_unit, now = Date.now()) {
|
|
61697
|
+
return dec !== null && dec.origin === "operator" && dec.agent_unit === agent_unit && dec.revoked_at === null && (dec.decision === "allow_once" || dec.decision === "allow_always" || dec.decision === "allow_ttl") && (dec.ttl_expires_at === null || dec.ttl_expires_at > now);
|
|
61698
|
+
}
|
|
61622
61699
|
function getNonce(db, request_id) {
|
|
61623
61700
|
const row = db.query(`SELECT * FROM approval_nonces WHERE request_id = ?`).get(request_id);
|
|
61624
61701
|
return row ? nonceFromRow(row) : null;
|
|
@@ -62634,6 +62711,22 @@ class VaultBroker {
|
|
|
62634
62711
|
socket.write(encodeResponse(errorResponse("LOCKED", "Broker is locked")));
|
|
62635
62712
|
return;
|
|
62636
62713
|
}
|
|
62714
|
+
if (req.op === "mint_grant" && process.env.SWITCHROOM_REQUIRE_OPERATOR_APPROVAL_MINT === "1") {
|
|
62715
|
+
const decisionId = req.decision_id;
|
|
62716
|
+
const dec = decisionId != null && decisionId !== "" ? getDecision(this.grantsDb, decisionId) : null;
|
|
62717
|
+
if (!isOperatorVerifiedDecision(dec, agentName ?? "")) {
|
|
62718
|
+
writeAudit({
|
|
62719
|
+
ts: new Date().toISOString(),
|
|
62720
|
+
op: req.op,
|
|
62721
|
+
caller: auditCaller,
|
|
62722
|
+
pid: auditPid,
|
|
62723
|
+
cgroup: auditCgroup,
|
|
62724
|
+
result: "denied:posture-mint-needs-operator-verified-decision"
|
|
62725
|
+
});
|
|
62726
|
+
socket.write(encodeResponse(errorResponse("DENIED", "posture-attested mint requires an operator-verified approval (host-side tap); none referenced. See RFC vault-approval-hard-boundary.")));
|
|
62727
|
+
return;
|
|
62728
|
+
}
|
|
62729
|
+
}
|
|
62637
62730
|
mintPostureAttested = true;
|
|
62638
62731
|
writeAudit({
|
|
62639
62732
|
ts: new Date().toISOString(),
|
|
@@ -64404,14 +64497,19 @@ function refuseSandboxDirectAccess(verbHint) {
|
|
|
64404
64497
|
`);
|
|
64405
64498
|
process.exit(VAULT_EXIT_SANDBOX_CONTEXT);
|
|
64406
64499
|
}
|
|
64407
|
-
function recoveryHint(situation, key) {
|
|
64500
|
+
function recoveryHint(situation, key, suggestions) {
|
|
64408
64501
|
if (!isSandboxContext()) {
|
|
64409
64502
|
const keyArg = key ? ` ${key}` : "";
|
|
64410
64503
|
return `Hint: run 'switchroom vault get --no-broker${keyArg}' for interactive (non-cron) access.`;
|
|
64411
64504
|
}
|
|
64412
64505
|
switch (situation) {
|
|
64413
|
-
case "denied":
|
|
64414
|
-
|
|
64506
|
+
case "denied": {
|
|
64507
|
+
const named = key ? `'${key}'` : "this key";
|
|
64508
|
+
if (suggestions && suggestions.length > 0) {
|
|
64509
|
+
return `Hint: you have no grant for ${named} \u2014 that key may not even exist. ` + `You ALREADY have access to similar keys: ${suggestions.join(", ")}. ` + `Try \`switchroom vault get <one-of-those>\` instead. ` + `Run \`switchroom vault list\` to see every key you hold. ` + `Only call the \`vault_request_access\` MCP tool for a key you've ` + `confirmed (via \`vault list\`) you don't already have. ` + `Do NOT retry with --no-broker \u2014 the vault file is not mounted ` + `into agent containers.`;
|
|
64510
|
+
}
|
|
64511
|
+
return `Hint: this agent has no grant for ${named}. ` + `FIRST run \`switchroom vault list\` to see the keys you already hold \u2014 ` + `the real name often differs from a guessed one (it's usually ` + `namespaced \`<you>/...\`). ` + `If you genuinely lack it, call the \`vault_request_access\` MCP tool ` + `(key=${key ? `'${key}'` : "'<key>'"}, scope='read') ` + `to ask the operator for access via a Telegram approval card. ` + `Do NOT retry with --no-broker \u2014 the vault file is not mounted ` + `into agent containers.`;
|
|
64512
|
+
}
|
|
64415
64513
|
case "locked":
|
|
64416
64514
|
return `Hint: the broker is locked. Ask the operator to unlock it ` + `(\`/vault unlock\` in this chat, or ` + `\`switchroom vault broker unlock\` on the host). ` + `Do NOT retry with --no-broker \u2014 the vault file is not mounted ` + `into agent containers.`;
|
|
64417
64515
|
case "unreachable":
|
|
@@ -64829,8 +64927,14 @@ Push passphrase to broker for future requests? [Y/n]: `);
|
|
|
64829
64927
|
if (process.stdin.isTTY) {
|
|
64830
64928
|
console.error(source_default.yellow(`broker denied request (${result.code}): ${result.msg}. ` + `Falling back to direct vault access.`));
|
|
64831
64929
|
} else {
|
|
64930
|
+
let suggestions = [];
|
|
64931
|
+
try {
|
|
64932
|
+
const myKeys = await listViaBroker(brokerOpts);
|
|
64933
|
+
if (myKeys && key)
|
|
64934
|
+
suggestions = suggestVaultKeys(key, myKeys);
|
|
64935
|
+
} catch {}
|
|
64832
64936
|
process.stderr.write(`VAULT-BROKER-DENIED [${result.code}]: ${result.msg}
|
|
64833
|
-
` + `${recoveryHint("denied", key)}
|
|
64937
|
+
` + `${recoveryHint("denied", key, suggestions)}
|
|
64834
64938
|
`);
|
|
64835
64939
|
writeVaultDeniedEnvelope(key, result.code, result.msg);
|
|
64836
64940
|
process.exit(2);
|
|
@@ -75544,7 +75648,7 @@ var DEFAULT_MEMORY_SEARCH_MAX_RESULTS = 6;
|
|
|
75544
75648
|
var DEFAULT_MEMORY_SEARCH_SNIPPET_CHARS = 220;
|
|
75545
75649
|
var MEMORY_SEARCH_MAX_INDEXED_CHARS = 2000000;
|
|
75546
75650
|
var MEMORY_SEARCH_MAX_FILE_SIZE = 512 * 1024;
|
|
75547
|
-
function
|
|
75651
|
+
function tokenize2(text) {
|
|
75548
75652
|
return text.toLowerCase().split(/[^a-z0-9]+/u).filter((t) => t.length > 1 && t.length < 40);
|
|
75549
75653
|
}
|
|
75550
75654
|
async function listMarkdownFiles(workspaceDir, maxDepth = 3) {
|
|
@@ -75596,7 +75700,7 @@ async function loadIndex(workspaceDir) {
|
|
|
75596
75700
|
if (content.length === 0)
|
|
75597
75701
|
continue;
|
|
75598
75702
|
totalChars += content.length;
|
|
75599
|
-
const terms =
|
|
75703
|
+
const terms = tokenize2(content);
|
|
75600
75704
|
const freq = new Map;
|
|
75601
75705
|
for (const t of terms) {
|
|
75602
75706
|
freq.set(t, (freq.get(t) ?? 0) + 1);
|
|
@@ -75669,7 +75773,7 @@ function computeIdf(index, queryTerms) {
|
|
|
75669
75773
|
async function searchWorkspaceMemory(params) {
|
|
75670
75774
|
const maxResults = params.maxResults ?? DEFAULT_MEMORY_SEARCH_MAX_RESULTS;
|
|
75671
75775
|
const snippetChars = params.snippetChars ?? DEFAULT_MEMORY_SEARCH_SNIPPET_CHARS;
|
|
75672
|
-
const queryTerms = Array.from(new Set(
|
|
75776
|
+
const queryTerms = Array.from(new Set(tokenize2(params.query)));
|
|
75673
75777
|
if (queryTerms.length === 0) {
|
|
75674
75778
|
return { query: params.query, indexedFiles: 0, totalMatches: 0, hits: [] };
|
|
75675
75779
|
}
|
|
@@ -13954,6 +13954,7 @@ var profileFields = {
|
|
|
13954
13954
|
}).optional()
|
|
13955
13955
|
}).optional(),
|
|
13956
13956
|
schedule: exports_external.array(ScheduleEntrySchema).optional(),
|
|
13957
|
+
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional().describe("Operator-granted STANDING vault keys this agent may read via the " + "broker — independent of any cron or MCP server. Use when an agent " + "needs a credential both interactively and in its own (agent-managed) " + "schedules, so the grant lives with the agent rather than welded to a " + "specific cron's `secrets[]`. OPERATOR-SET ONLY: agents cannot edit " + "switchroom.yaml or self-grant (reference/vision.md outcome 2 — 'you " + "hold the leash; only your tap grants it'). Exact key names. Cascades " + "UNION across defaults -> profile -> agent (see docs/configuration.md)."),
|
|
13957
13958
|
reactions: ReactionsSchema,
|
|
13958
13959
|
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only").optional(),
|
|
13959
13960
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
@@ -14022,6 +14023,7 @@ var AgentSchema = exports_external.object({
|
|
|
14022
14023
|
tools: AgentToolsSchema,
|
|
14023
14024
|
memory: AgentMemorySchema,
|
|
14024
14025
|
schedule: exports_external.array(ScheduleEntrySchema).default([]),
|
|
14026
|
+
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional(),
|
|
14025
14027
|
reactions: ReactionsSchema,
|
|
14026
14028
|
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only (no spaces or shell specials)").optional().describe("Claude model override (e.g., 'claude-sonnet-4-6')"),
|
|
14027
14029
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "Per-agent override wins over defaults.thinking_effort. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
@@ -14501,6 +14503,12 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
14501
14503
|
deny: dedupe([...dDeny, ...aDeny])
|
|
14502
14504
|
};
|
|
14503
14505
|
}
|
|
14506
|
+
if (defaults.secrets || merged.secrets) {
|
|
14507
|
+
merged.secrets = dedupe([
|
|
14508
|
+
...defaults.secrets ?? [],
|
|
14509
|
+
...merged.secrets ?? []
|
|
14510
|
+
]);
|
|
14511
|
+
}
|
|
14504
14512
|
if (defaults.soul || merged.soul) {
|
|
14505
14513
|
const base = defaults.soul ?? {};
|
|
14506
14514
|
const override = merged.soul ?? {};
|
|
@@ -4106,6 +4106,12 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
4106
4106
|
deny: dedupe([...dDeny, ...aDeny])
|
|
4107
4107
|
};
|
|
4108
4108
|
}
|
|
4109
|
+
if (defaults.secrets || merged.secrets) {
|
|
4110
|
+
merged.secrets = dedupe([
|
|
4111
|
+
...defaults.secrets ?? [],
|
|
4112
|
+
...merged.secrets ?? []
|
|
4113
|
+
]);
|
|
4114
|
+
}
|
|
4109
4115
|
if (defaults.soul || merged.soul) {
|
|
4110
4116
|
const base = defaults.soul ?? {};
|
|
4111
4117
|
const override = merged.soul ?? {};
|
|
@@ -11534,6 +11540,7 @@ var init_schema = __esm(() => {
|
|
|
11534
11540
|
}).optional()
|
|
11535
11541
|
}).optional(),
|
|
11536
11542
|
schedule: exports_external.array(ScheduleEntrySchema).optional(),
|
|
11543
|
+
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional().describe("Operator-granted STANDING vault keys this agent may read via the " + "broker — independent of any cron or MCP server. Use when an agent " + "needs a credential both interactively and in its own (agent-managed) " + "schedules, so the grant lives with the agent rather than welded to a " + "specific cron's `secrets[]`. OPERATOR-SET ONLY: agents cannot edit " + "switchroom.yaml or self-grant (reference/vision.md outcome 2 — 'you " + "hold the leash; only your tap grants it'). Exact key names. Cascades " + "UNION across defaults -> profile -> agent (see docs/configuration.md)."),
|
|
11537
11544
|
reactions: ReactionsSchema,
|
|
11538
11545
|
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only").optional(),
|
|
11539
11546
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
@@ -11602,6 +11609,7 @@ var init_schema = __esm(() => {
|
|
|
11602
11609
|
tools: AgentToolsSchema,
|
|
11603
11610
|
memory: AgentMemorySchema,
|
|
11604
11611
|
schedule: exports_external.array(ScheduleEntrySchema).default([]),
|
|
11612
|
+
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional(),
|
|
11605
11613
|
reactions: ReactionsSchema,
|
|
11606
11614
|
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only (no spaces or shell specials)").optional().describe("Claude model override (e.g., 'claude-sonnet-4-6')"),
|
|
11607
11615
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "Per-agent override wins over defaults.thinking_effort. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
@@ -12272,7 +12280,8 @@ var MintGrantRequestSchema = exports_external.object({
|
|
|
12272
12280
|
description: exports_external.string().optional(),
|
|
12273
12281
|
write_keys: exports_external.array(exports_external.string().min(1)).optional(),
|
|
12274
12282
|
passphrase: exports_external.string().optional(),
|
|
12275
|
-
attest_via_posture: exports_external.boolean().optional()
|
|
12283
|
+
attest_via_posture: exports_external.boolean().optional(),
|
|
12284
|
+
decision_id: exports_external.string().optional()
|
|
12276
12285
|
});
|
|
12277
12286
|
var ListGrantsRequestSchema = exports_external.object({
|
|
12278
12287
|
v: exports_external.literal(1),
|
|
@@ -12587,9 +12596,20 @@ function migrateApprovalSchema(db) {
|
|
|
12587
12596
|
approver_set_canonical TEXT NOT NULL,
|
|
12588
12597
|
last_used_at INTEGER,
|
|
12589
12598
|
revoked_at INTEGER,
|
|
12590
|
-
revoke_reason TEXT
|
|
12599
|
+
revoke_reason TEXT,
|
|
12600
|
+
-- Provenance of the operator-authorization (RFC vault-approval-hard-
|
|
12601
|
+
-- boundary). 'agent': recorded on a per-agent socket — claude shares
|
|
12602
|
+
-- that socket, so it is FORGEABLE and must NOT be trusted as proof an
|
|
12603
|
+
-- operator tapped. 'operator': recorded via the host-side verifier on a
|
|
12604
|
+
-- claude-unreachable channel — the only value the broker's mint gate
|
|
12605
|
+
-- trusts. Default 'agent' (fail-closed for the new gate).
|
|
12606
|
+
origin TEXT NOT NULL DEFAULT 'agent'
|
|
12591
12607
|
)
|
|
12592
12608
|
`);
|
|
12609
|
+
const decisionCols = db.query("PRAGMA table_info(approval_decisions)").all();
|
|
12610
|
+
if (!decisionCols.some((c) => c.name === "origin")) {
|
|
12611
|
+
db.run(`ALTER TABLE approval_decisions ADD COLUMN origin TEXT NOT NULL DEFAULT 'agent'`);
|
|
12612
|
+
}
|
|
12593
12613
|
db.run(`
|
|
12594
12614
|
CREATE INDEX IF NOT EXISTS approval_decisions_lookup
|
|
12595
12615
|
ON approval_decisions(agent_unit, scope, action)
|
|
@@ -12665,7 +12685,8 @@ function rowToDecision(row) {
|
|
|
12665
12685
|
approver_set_canonical: row.approver_set_canonical,
|
|
12666
12686
|
last_used_at: row.last_used_at ?? null,
|
|
12667
12687
|
revoked_at: row.revoked_at ?? null,
|
|
12668
|
-
revoke_reason: row.revoke_reason ?? null
|
|
12688
|
+
revoke_reason: row.revoke_reason ?? null,
|
|
12689
|
+
origin: row.origin === "operator" ? "operator" : "agent"
|
|
12669
12690
|
};
|
|
12670
12691
|
}
|
|
12671
12692
|
var MAX_PENDING_PER_AGENT = 2;
|
|
@@ -12831,8 +12852,8 @@ function recordDecision(db, input, now = Date.now()) {
|
|
|
12831
12852
|
db.run(`INSERT INTO approval_decisions
|
|
12832
12853
|
(id, agent_unit, scope, action, decision,
|
|
12833
12854
|
ttl_expires_at, granted_at, granted_by_user_id,
|
|
12834
|
-
approver_set_canonical, last_used_at, revoked_at, revoke_reason)
|
|
12835
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, NULL)`, [
|
|
12855
|
+
approver_set_canonical, last_used_at, revoked_at, revoke_reason, origin)
|
|
12856
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, NULL, ?)`, [
|
|
12836
12857
|
id,
|
|
12837
12858
|
input.nonce.agent_unit,
|
|
12838
12859
|
input.nonce.scope,
|
|
@@ -12841,7 +12862,8 @@ function recordDecision(db, input, now = Date.now()) {
|
|
|
12841
12862
|
ttl_expires_at,
|
|
12842
12863
|
now,
|
|
12843
12864
|
input.granted_by_user_id,
|
|
12844
|
-
canonical
|
|
12865
|
+
canonical,
|
|
12866
|
+
input.origin ?? "agent"
|
|
12845
12867
|
]);
|
|
12846
12868
|
db.run(`UPDATE approval_nonces SET decision_id = ? WHERE request_id = ?`, [id, input.nonce.request_id]);
|
|
12847
12869
|
const granted = input.decision === "allow_once" || input.decision === "allow_always" || input.decision === "allow_ttl";
|
|
@@ -12869,7 +12891,8 @@ function consumeAndRecord(db, input, now = Date.now()) {
|
|
|
12869
12891
|
decision: input.decision,
|
|
12870
12892
|
approver_set: input.approver_set,
|
|
12871
12893
|
granted_by_user_id: input.granted_by_user_id,
|
|
12872
|
-
ttl_ms: input.ttl_ms
|
|
12894
|
+
ttl_ms: input.ttl_ms,
|
|
12895
|
+
origin: input.origin
|
|
12873
12896
|
}, now);
|
|
12874
12897
|
return { consumed: true, decision_id, nonce };
|
|
12875
12898
|
});
|
|
@@ -140,6 +140,12 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
140
140
|
deny: dedupe([...dDeny, ...aDeny])
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
|
+
if (defaults.secrets || merged.secrets) {
|
|
144
|
+
merged.secrets = dedupe([
|
|
145
|
+
...defaults.secrets ?? [],
|
|
146
|
+
...merged.secrets ?? []
|
|
147
|
+
]);
|
|
148
|
+
}
|
|
143
149
|
if (defaults.soul || merged.soul) {
|
|
144
150
|
const base = defaults.soul ?? {};
|
|
145
151
|
const override = merged.soul ?? {};
|
|
@@ -11534,6 +11540,7 @@ var init_schema = __esm(() => {
|
|
|
11534
11540
|
}).optional()
|
|
11535
11541
|
}).optional(),
|
|
11536
11542
|
schedule: exports_external.array(ScheduleEntrySchema).optional(),
|
|
11543
|
+
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional().describe("Operator-granted STANDING vault keys this agent may read via the " + "broker — independent of any cron or MCP server. Use when an agent " + "needs a credential both interactively and in its own (agent-managed) " + "schedules, so the grant lives with the agent rather than welded to a " + "specific cron's `secrets[]`. OPERATOR-SET ONLY: agents cannot edit " + "switchroom.yaml or self-grant (reference/vision.md outcome 2 — 'you " + "hold the leash; only your tap grants it'). Exact key names. Cascades " + "UNION across defaults -> profile -> agent (see docs/configuration.md)."),
|
|
11537
11544
|
reactions: ReactionsSchema,
|
|
11538
11545
|
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only").optional(),
|
|
11539
11546
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
@@ -11602,6 +11609,7 @@ var init_schema = __esm(() => {
|
|
|
11602
11609
|
tools: AgentToolsSchema,
|
|
11603
11610
|
memory: AgentMemorySchema,
|
|
11604
11611
|
schedule: exports_external.array(ScheduleEntrySchema).default([]),
|
|
11612
|
+
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional(),
|
|
11605
11613
|
reactions: ReactionsSchema,
|
|
11606
11614
|
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only (no spaces or shell specials)").optional().describe("Claude model override (e.g., 'claude-sonnet-4-6')"),
|
|
11607
11615
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "Per-agent override wins over defaults.thinking_effort. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
@@ -13288,11 +13296,21 @@ function checkAclByAgent(config, agentName, key) {
|
|
|
13288
13296
|
return { allow: true };
|
|
13289
13297
|
}
|
|
13290
13298
|
}
|
|
13299
|
+
const cfgSecrets = config;
|
|
13300
|
+
const profileSecrets = profileName != null && profileName.length > 0 ? cfgSecrets.profiles?.[profileName]?.secrets : undefined;
|
|
13301
|
+
const standingSecrets = [
|
|
13302
|
+
...Array.isArray(cfgSecrets.defaults?.secrets) ? cfgSecrets.defaults.secrets : [],
|
|
13303
|
+
...Array.isArray(profileSecrets) ? profileSecrets : [],
|
|
13304
|
+
...Array.isArray(agentConfig.secrets) ? agentConfig.secrets : []
|
|
13305
|
+
];
|
|
13306
|
+
if (standingSecrets.includes(key)) {
|
|
13307
|
+
return { allow: true };
|
|
13308
|
+
}
|
|
13291
13309
|
const schedule = agentConfig.schedule ?? [];
|
|
13292
13310
|
if (schedule.length === 0) {
|
|
13293
13311
|
return {
|
|
13294
13312
|
allow: false,
|
|
13295
|
-
reason: `agent '${agentName}' has no schedule entries declaring 'secrets'
|
|
13313
|
+
reason: `agent '${agentName}' has no schedule entries declaring 'secrets', no mcp_servers.*.secrets[], and no agents.${agentName}.secrets[] standing grant declaring '${key}'; nothing is broker-accessible`
|
|
13296
13314
|
};
|
|
13297
13315
|
}
|
|
13298
13316
|
for (const entry of schedule) {
|
|
@@ -13377,7 +13395,8 @@ var MintGrantRequestSchema = exports_external.object({
|
|
|
13377
13395
|
description: exports_external.string().optional(),
|
|
13378
13396
|
write_keys: exports_external.array(exports_external.string().min(1)).optional(),
|
|
13379
13397
|
passphrase: exports_external.string().optional(),
|
|
13380
|
-
attest_via_posture: exports_external.boolean().optional()
|
|
13398
|
+
attest_via_posture: exports_external.boolean().optional(),
|
|
13399
|
+
decision_id: exports_external.string().optional()
|
|
13381
13400
|
});
|
|
13382
13401
|
var ListGrantsRequestSchema = exports_external.object({
|
|
13383
13402
|
v: exports_external.literal(1),
|
|
@@ -15714,9 +15733,20 @@ function migrateApprovalSchema(db) {
|
|
|
15714
15733
|
approver_set_canonical TEXT NOT NULL,
|
|
15715
15734
|
last_used_at INTEGER,
|
|
15716
15735
|
revoked_at INTEGER,
|
|
15717
|
-
revoke_reason TEXT
|
|
15736
|
+
revoke_reason TEXT,
|
|
15737
|
+
-- Provenance of the operator-authorization (RFC vault-approval-hard-
|
|
15738
|
+
-- boundary). 'agent': recorded on a per-agent socket — claude shares
|
|
15739
|
+
-- that socket, so it is FORGEABLE and must NOT be trusted as proof an
|
|
15740
|
+
-- operator tapped. 'operator': recorded via the host-side verifier on a
|
|
15741
|
+
-- claude-unreachable channel — the only value the broker's mint gate
|
|
15742
|
+
-- trusts. Default 'agent' (fail-closed for the new gate).
|
|
15743
|
+
origin TEXT NOT NULL DEFAULT 'agent'
|
|
15718
15744
|
)
|
|
15719
15745
|
`);
|
|
15746
|
+
const decisionCols = db.query("PRAGMA table_info(approval_decisions)").all();
|
|
15747
|
+
if (!decisionCols.some((c) => c.name === "origin")) {
|
|
15748
|
+
db.run(`ALTER TABLE approval_decisions ADD COLUMN origin TEXT NOT NULL DEFAULT 'agent'`);
|
|
15749
|
+
}
|
|
15720
15750
|
db.run(`
|
|
15721
15751
|
CREATE INDEX IF NOT EXISTS approval_decisions_lookup
|
|
15722
15752
|
ON approval_decisions(agent_unit, scope, action)
|
|
@@ -15818,7 +15848,8 @@ function rowToDecision(row) {
|
|
|
15818
15848
|
approver_set_canonical: row.approver_set_canonical,
|
|
15819
15849
|
last_used_at: row.last_used_at ?? null,
|
|
15820
15850
|
revoked_at: row.revoked_at ?? null,
|
|
15821
|
-
revoke_reason: row.revoke_reason ?? null
|
|
15851
|
+
revoke_reason: row.revoke_reason ?? null,
|
|
15852
|
+
origin: row.origin === "operator" ? "operator" : "agent"
|
|
15822
15853
|
};
|
|
15823
15854
|
}
|
|
15824
15855
|
var MAX_PENDING_PER_AGENT = 2;
|
|
@@ -15984,8 +16015,8 @@ function recordDecision(db, input, now = Date.now()) {
|
|
|
15984
16015
|
db.run(`INSERT INTO approval_decisions
|
|
15985
16016
|
(id, agent_unit, scope, action, decision,
|
|
15986
16017
|
ttl_expires_at, granted_at, granted_by_user_id,
|
|
15987
|
-
approver_set_canonical, last_used_at, revoked_at, revoke_reason)
|
|
15988
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, NULL)`, [
|
|
16018
|
+
approver_set_canonical, last_used_at, revoked_at, revoke_reason, origin)
|
|
16019
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, NULL, ?)`, [
|
|
15989
16020
|
id,
|
|
15990
16021
|
input.nonce.agent_unit,
|
|
15991
16022
|
input.nonce.scope,
|
|
@@ -15994,7 +16025,8 @@ function recordDecision(db, input, now = Date.now()) {
|
|
|
15994
16025
|
ttl_expires_at,
|
|
15995
16026
|
now,
|
|
15996
16027
|
input.granted_by_user_id,
|
|
15997
|
-
canonical
|
|
16028
|
+
canonical,
|
|
16029
|
+
input.origin ?? "agent"
|
|
15998
16030
|
]);
|
|
15999
16031
|
db.run(`UPDATE approval_nonces SET decision_id = ? WHERE request_id = ?`, [id, input.nonce.request_id]);
|
|
16000
16032
|
const granted = input.decision === "allow_once" || input.decision === "allow_always" || input.decision === "allow_ttl";
|
|
@@ -16048,6 +16080,13 @@ function listDecisions(db, filter, now = Date.now()) {
|
|
|
16048
16080
|
const rows = db.query(sql).all(...params);
|
|
16049
16081
|
return rows.map(rowToDecision);
|
|
16050
16082
|
}
|
|
16083
|
+
function getDecision(db, id) {
|
|
16084
|
+
const row = db.query(`SELECT * FROM approval_decisions WHERE id = ?`).get(id);
|
|
16085
|
+
return row ? rowToDecision(row) : null;
|
|
16086
|
+
}
|
|
16087
|
+
function isOperatorVerifiedDecision(dec, agent_unit, now = Date.now()) {
|
|
16088
|
+
return dec !== null && dec.origin === "operator" && dec.agent_unit === agent_unit && dec.revoked_at === null && (dec.decision === "allow_once" || dec.decision === "allow_always" || dec.decision === "allow_ttl") && (dec.ttl_expires_at === null || dec.ttl_expires_at > now);
|
|
16089
|
+
}
|
|
16051
16090
|
function getNonce(db, request_id) {
|
|
16052
16091
|
const row = db.query(`SELECT * FROM approval_nonces WHERE request_id = ?`).get(request_id);
|
|
16053
16092
|
return row ? nonceFromRow(row) : null;
|
|
@@ -17063,6 +17102,22 @@ class VaultBroker {
|
|
|
17063
17102
|
socket.write(encodeResponse(errorResponse("LOCKED", "Broker is locked")));
|
|
17064
17103
|
return;
|
|
17065
17104
|
}
|
|
17105
|
+
if (req.op === "mint_grant" && process.env.SWITCHROOM_REQUIRE_OPERATOR_APPROVAL_MINT === "1") {
|
|
17106
|
+
const decisionId = req.decision_id;
|
|
17107
|
+
const dec = decisionId != null && decisionId !== "" ? getDecision(this.grantsDb, decisionId) : null;
|
|
17108
|
+
if (!isOperatorVerifiedDecision(dec, agentName ?? "")) {
|
|
17109
|
+
writeAudit({
|
|
17110
|
+
ts: new Date().toISOString(),
|
|
17111
|
+
op: req.op,
|
|
17112
|
+
caller: auditCaller,
|
|
17113
|
+
pid: auditPid,
|
|
17114
|
+
cgroup: auditCgroup,
|
|
17115
|
+
result: "denied:posture-mint-needs-operator-verified-decision"
|
|
17116
|
+
});
|
|
17117
|
+
socket.write(encodeResponse(errorResponse("DENIED", "posture-attested mint requires an operator-verified approval (host-side tap); none referenced. See RFC vault-approval-hard-boundary.")));
|
|
17118
|
+
return;
|
|
17119
|
+
}
|
|
17120
|
+
}
|
|
17066
17121
|
mintPostureAttested = true;
|
|
17067
17122
|
writeAudit({
|
|
17068
17123
|
ts: new Date().toISOString(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchroom",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.35",
|
|
4
4
|
"description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"pretest": "npm run build",
|
|
26
26
|
"test": "vitest run && bun test telegram-plugin/tests/history.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/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/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/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.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",
|
|
27
27
|
"test:vitest": "vitest run",
|
|
28
|
-
"test:bun": "bun test src/watchdog/state.test.ts src/watchdog/policy.test.ts src/vault/grants.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-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/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/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/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/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.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/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/uat/load-env.test.ts telegram-plugin/uat/feed-matcher.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts",
|
|
28
|
+
"test:bun": "bun test src/watchdog/state.test.ts src/watchdog/policy.test.ts src/vault/grants.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-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/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/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/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.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/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/uat/load-env.test.ts telegram-plugin/uat/feed-matcher.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts",
|
|
29
29
|
"test:watch": "vitest",
|
|
30
30
|
"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",
|
|
31
31
|
"lint:tsc": "tsc --noEmit",
|
|
@@ -23925,6 +23925,7 @@ var init_schema = __esm(() => {
|
|
|
23925
23925
|
}).optional()
|
|
23926
23926
|
}).optional(),
|
|
23927
23927
|
schedule: exports_external.array(ScheduleEntrySchema).optional(),
|
|
23928
|
+
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional().describe("Operator-granted STANDING vault keys this agent may read via the " + "broker \u2014 independent of any cron or MCP server. Use when an agent " + "needs a credential both interactively and in its own (agent-managed) " + "schedules, so the grant lives with the agent rather than welded to a " + "specific cron's `secrets[]`. OPERATOR-SET ONLY: agents cannot edit " + "switchroom.yaml or self-grant (reference/vision.md outcome 2 \u2014 'you " + "hold the leash; only your tap grants it'). Exact key names. Cascades " + "UNION across defaults -> profile -> agent (see docs/configuration.md)."),
|
|
23928
23929
|
reactions: ReactionsSchema,
|
|
23929
23930
|
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only").optional(),
|
|
23930
23931
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
@@ -23993,6 +23994,7 @@ var init_schema = __esm(() => {
|
|
|
23993
23994
|
tools: AgentToolsSchema,
|
|
23994
23995
|
memory: AgentMemorySchema,
|
|
23995
23996
|
schedule: exports_external.array(ScheduleEntrySchema).default([]),
|
|
23997
|
+
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional(),
|
|
23996
23998
|
reactions: ReactionsSchema,
|
|
23997
23999
|
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only (no spaces or shell specials)").optional().describe("Claude model override (e.g., 'claude-sonnet-4-6')"),
|
|
23998
24000
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "Per-agent override wins over defaults.thinking_effort. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
@@ -24472,6 +24474,12 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
24472
24474
|
deny: dedupe([...dDeny, ...aDeny])
|
|
24473
24475
|
};
|
|
24474
24476
|
}
|
|
24477
|
+
if (defaults.secrets || merged.secrets) {
|
|
24478
|
+
merged.secrets = dedupe([
|
|
24479
|
+
...defaults.secrets ?? [],
|
|
24480
|
+
...merged.secrets ?? []
|
|
24481
|
+
]);
|
|
24482
|
+
}
|
|
24475
24483
|
if (defaults.soul || merged.soul) {
|
|
24476
24484
|
const base = defaults.soul ?? {};
|
|
24477
24485
|
const override = merged.soul ?? {};
|
|
@@ -27725,7 +27733,8 @@ var init_protocol2 = __esm(() => {
|
|
|
27725
27733
|
description: exports_external.string().optional(),
|
|
27726
27734
|
write_keys: exports_external.array(exports_external.string().min(1)).optional(),
|
|
27727
27735
|
passphrase: exports_external.string().optional(),
|
|
27728
|
-
attest_via_posture: exports_external.boolean().optional()
|
|
27736
|
+
attest_via_posture: exports_external.boolean().optional(),
|
|
27737
|
+
decision_id: exports_external.string().optional()
|
|
27729
27738
|
});
|
|
27730
27739
|
ListGrantsRequestSchema = exports_external.object({
|
|
27731
27740
|
v: exports_external.literal(1),
|
|
@@ -44481,6 +44490,12 @@ function mergeAgentConfig2(defaultsIn, agentIn) {
|
|
|
44481
44490
|
deny: dedupe2([...dDeny, ...aDeny])
|
|
44482
44491
|
};
|
|
44483
44492
|
}
|
|
44493
|
+
if (defaults.secrets || merged.secrets) {
|
|
44494
|
+
merged.secrets = dedupe2([
|
|
44495
|
+
...defaults.secrets ?? [],
|
|
44496
|
+
...merged.secrets ?? []
|
|
44497
|
+
]);
|
|
44498
|
+
}
|
|
44484
44499
|
if (defaults.soul || merged.soul) {
|
|
44485
44500
|
const base = defaults.soul ?? {};
|
|
44486
44501
|
const override = merged.soul ?? {};
|
|
@@ -51766,10 +51781,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
51766
51781
|
}
|
|
51767
51782
|
|
|
51768
51783
|
// ../src/build-info.ts
|
|
51769
|
-
var VERSION = "0.14.
|
|
51770
|
-
var COMMIT_SHA = "
|
|
51771
|
-
var COMMIT_DATE = "2026-06-
|
|
51772
|
-
var LATEST_PR =
|
|
51784
|
+
var VERSION = "0.14.35";
|
|
51785
|
+
var COMMIT_SHA = "7ac06aea";
|
|
51786
|
+
var COMMIT_DATE = "2026-06-01T21:48:46Z";
|
|
51787
|
+
var LATEST_PR = 2072;
|
|
51773
51788
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
51774
51789
|
|
|
51775
51790
|
// gateway/boot-version.ts
|