switchroom 0.12.13 → 0.12.15

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.
@@ -11719,6 +11719,24 @@ var LockRequestSchema = exports_external.object({
11719
11719
  v: exports_external.literal(1),
11720
11720
  op: exports_external.literal("lock")
11721
11721
  });
11722
+ var PreflightAccessRequestSchema = exports_external.object({
11723
+ v: exports_external.literal(1),
11724
+ op: exports_external.literal("preflight_access"),
11725
+ agent: exports_external.string().min(1),
11726
+ keys: exports_external.array(exports_external.string().min(1)).min(1).max(128)
11727
+ });
11728
+ var OkPreflightAccessResponseSchema = exports_external.object({
11729
+ ok: exports_external.literal(true),
11730
+ op: exports_external.literal("preflight_access"),
11731
+ results: exports_external.array(exports_external.object({
11732
+ key: exports_external.string(),
11733
+ exists: exports_external.boolean(),
11734
+ acl_ok: exports_external.boolean(),
11735
+ acl_reason: exports_external.string().optional(),
11736
+ scope_ok: exports_external.boolean(),
11737
+ scope_reason: exports_external.string().optional()
11738
+ }))
11739
+ });
11722
11740
  var ApprovalRequestRequestSchema = exports_external.object({
11723
11741
  v: exports_external.literal(1),
11724
11742
  op: exports_external.literal("approval_request"),
@@ -11770,12 +11788,22 @@ var ApprovalRecordRequestSchema = exports_external.object({
11770
11788
  granted_by_user_id: exports_external.number().int(),
11771
11789
  ttl_ms: exports_external.number().int().positive().nullable().optional()
11772
11790
  });
11791
+ var ApprovalConsumeRecordRequestSchema = exports_external.object({
11792
+ v: exports_external.literal(1),
11793
+ op: exports_external.literal("approval_consume_record"),
11794
+ request_id: exports_external.string().regex(/^[0-9a-f]{32}$/),
11795
+ decision: ApprovalDecisionModeSchema,
11796
+ approver_set: exports_external.array(exports_external.string()),
11797
+ granted_by_user_id: exports_external.number().int(),
11798
+ ttl_ms: exports_external.number().int().positive().nullable().optional()
11799
+ });
11773
11800
  var RequestSchema = exports_external.discriminatedUnion("op", [
11774
11801
  GetRequestSchema,
11775
11802
  PutRequestSchema,
11776
11803
  ListRequestSchema,
11777
11804
  StatusRequestSchema,
11778
11805
  LockRequestSchema,
11806
+ PreflightAccessRequestSchema,
11779
11807
  MintGrantRequestSchema,
11780
11808
  ListGrantsRequestSchema,
11781
11809
  RevokeGrantRequestSchema,
@@ -11784,7 +11812,8 @@ var RequestSchema = exports_external.discriminatedUnion("op", [
11784
11812
  ApprovalConsumeRequestSchema,
11785
11813
  ApprovalRevokeRequestSchema,
11786
11814
  ApprovalListRequestSchema,
11787
- ApprovalRecordRequestSchema
11815
+ ApprovalRecordRequestSchema,
11816
+ ApprovalConsumeRecordRequestSchema
11788
11817
  ]);
11789
11818
  var VaultEntrySchema = exports_external.union([
11790
11819
  exports_external.object({ kind: exports_external.literal("string"), value: exports_external.string() }),
@@ -11906,6 +11935,15 @@ var OkApprovalRecordResponseSchema = exports_external.object({
11906
11935
  ok: exports_external.literal(true),
11907
11936
  decision_id: exports_external.string()
11908
11937
  });
11938
+ var OkApprovalConsumeRecordResponseSchema = exports_external.object({
11939
+ ok: exports_external.literal(true),
11940
+ consumed: exports_external.boolean(),
11941
+ decision_id: exports_external.string().optional(),
11942
+ agent_unit: exports_external.string().optional(),
11943
+ scope: exports_external.string().optional(),
11944
+ action: exports_external.string().optional(),
11945
+ why: exports_external.string().nullable().optional()
11946
+ });
11909
11947
  var ErrorResponseSchema = exports_external.object({
11910
11948
  ok: exports_external.literal(false),
11911
11949
  code: ErrorCode,
@@ -11916,6 +11954,7 @@ var ResponseSchema = exports_external.union([
11916
11954
  OkKeysResponseSchema,
11917
11955
  OkStatusResponseSchema,
11918
11956
  OkLockResponseSchema,
11957
+ OkPreflightAccessResponseSchema,
11919
11958
  OkPutResponseSchema,
11920
11959
  OkMintGrantResponseSchema,
11921
11960
  OkListGrantsResponseSchema,
@@ -11926,6 +11965,7 @@ var ResponseSchema = exports_external.union([
11926
11965
  OkApprovalRevokeResponseSchema,
11927
11966
  OkApprovalListResponseSchema,
11928
11967
  OkApprovalRecordResponseSchema,
11968
+ OkApprovalConsumeRecordResponseSchema,
11929
11969
  ErrorResponseSchema
11930
11970
  ]);
11931
11971
  function decodeRequest(line) {
@@ -12244,6 +12284,22 @@ function recordDecision(db, input, now = Date.now()) {
12244
12284
  });
12245
12285
  return id;
12246
12286
  }
12287
+ function consumeAndRecord(db, input, now = Date.now()) {
12288
+ const run = db.transaction(() => {
12289
+ const nonce = consumeNonce(db, input.request_id, now);
12290
+ if (nonce === null)
12291
+ return { consumed: false };
12292
+ const decision_id = recordDecision(db, {
12293
+ nonce,
12294
+ decision: input.decision,
12295
+ approver_set: input.approver_set,
12296
+ granted_by_user_id: input.granted_by_user_id,
12297
+ ttl_ms: input.ttl_ms
12298
+ }, now);
12299
+ return { consumed: true, decision_id, nonce };
12300
+ });
12301
+ return run();
12302
+ }
12247
12303
  function revokeDecision(db, decision_id, actor, reason, now = Date.now()) {
12248
12304
  const row = db.query(`SELECT * FROM approval_decisions WHERE id = ?`).get(decision_id);
12249
12305
  if (!row)
@@ -12953,6 +13009,37 @@ function handleRequest(socket, req, agent, db, peerUid, isOperator = false) {
12953
13009
  socket.write(encodeResponse({ ok: true, decision_id }));
12954
13010
  return;
12955
13011
  }
13012
+ if (req.op === "approval_consume_record") {
13013
+ const peek = getNonce(db, req.request_id);
13014
+ if (peek !== null) {
13015
+ const acl = checkApprovalAclByAgent(agent, peek.agent_unit);
13016
+ if (!acl.allow) {
13017
+ socket.write(encodeResponse(errorResponse("DENIED", "approval_consume_record denied: nonce does not belong to the calling agent")));
13018
+ return;
13019
+ }
13020
+ }
13021
+ const res = consumeAndRecord(db, {
13022
+ request_id: req.request_id,
13023
+ decision: req.decision,
13024
+ approver_set: req.approver_set,
13025
+ granted_by_user_id: req.granted_by_user_id,
13026
+ ttl_ms: req.ttl_ms ?? undefined
13027
+ });
13028
+ if (!res.consumed) {
13029
+ socket.write(encodeResponse({ ok: true, consumed: false }));
13030
+ return;
13031
+ }
13032
+ socket.write(encodeResponse({
13033
+ ok: true,
13034
+ consumed: true,
13035
+ decision_id: res.decision_id,
13036
+ agent_unit: res.nonce.agent_unit,
13037
+ scope: res.nonce.scope,
13038
+ action: res.nonce.action,
13039
+ why: res.nonce.why
13040
+ }));
13041
+ return;
13042
+ }
12956
13043
  if (req.op === "approval_list") {
12957
13044
  const decisions = listDecisions(db, { agent_unit: req.agent_unit });
12958
13045
  const meta = decisions.map((d) => ({
@@ -12899,6 +12899,48 @@ function readAutoUnlockFile(filePath) {
12899
12899
  }
12900
12900
  var DEFAULT_AUTO_UNLOCK_PATH = "~/.switchroom/vault-auto-unlock";
12901
12901
 
12902
+ // src/config/google-workspace-acl.ts
12903
+ function shouldEmitGdriveMcp(agentName, agentGoogleAccount, googleAccounts) {
12904
+ if (!agentGoogleAccount)
12905
+ return false;
12906
+ const account = agentGoogleAccount.trim().toLowerCase();
12907
+ if (account.length === 0)
12908
+ return false;
12909
+ const acctEntry = googleAccounts?.[account];
12910
+ if (!acctEntry)
12911
+ return false;
12912
+ const enabledFor = acctEntry.enabled_for ?? [];
12913
+ return enabledFor.includes(agentName);
12914
+ }
12915
+ function vaultRefKey(value) {
12916
+ if (typeof value !== "string" || !value.startsWith("vault:"))
12917
+ return null;
12918
+ const key = value.slice("vault:".length).split("#")[0];
12919
+ return key.length > 0 ? key : null;
12920
+ }
12921
+ function isGoogleClientCredentialKeyForAgent(config, agentName, key) {
12922
+ if (!agentName || !key)
12923
+ return false;
12924
+ const agentConfig = config.agents?.[agentName];
12925
+ if (!agentConfig)
12926
+ return false;
12927
+ if (agentConfig.mcp_servers?.["gdrive"] === false) {
12928
+ return false;
12929
+ }
12930
+ const account = agentConfig.google_workspace?.account;
12931
+ if (!shouldEmitGdriveMcp(agentName, account, config.google_accounts)) {
12932
+ return false;
12933
+ }
12934
+ const gw = config.google_workspace;
12935
+ if (!gw)
12936
+ return false;
12937
+ for (const ref of [gw.google_client_id, gw.google_client_secret]) {
12938
+ if (vaultRefKey(ref) === key)
12939
+ return true;
12940
+ }
12941
+ return false;
12942
+ }
12943
+
12902
12944
  // src/vault/broker/acl.ts
12903
12945
  function parseCronUnit(unitName) {
12904
12946
  const m = unitName.match(/^switchroom-([a-zA-Z0-9_-]+)-cron-(\d+)\.service$/);
@@ -12986,6 +13028,9 @@ function checkAclByAgent(config, agentName, key) {
12986
13028
  if (googleSlot !== null) {
12987
13029
  return checkGoogleAccountAcl(config, agentName, googleSlot.account, key);
12988
13030
  }
13031
+ if (isGoogleClientCredentialKeyForAgent(config, agentName, key)) {
13032
+ return { allow: true };
13033
+ }
12989
13034
  const agentBot = agentConfig.bot_token;
12990
13035
  const botRef = agentBot && agentBot.length > 0 ? agentBot : config.telegram?.bot_token;
12991
13036
  if (typeof botRef === "string" && botRef.startsWith("vault:")) {
@@ -13102,6 +13147,24 @@ var LockRequestSchema = exports_external.object({
13102
13147
  v: exports_external.literal(1),
13103
13148
  op: exports_external.literal("lock")
13104
13149
  });
13150
+ var PreflightAccessRequestSchema = exports_external.object({
13151
+ v: exports_external.literal(1),
13152
+ op: exports_external.literal("preflight_access"),
13153
+ agent: exports_external.string().min(1),
13154
+ keys: exports_external.array(exports_external.string().min(1)).min(1).max(128)
13155
+ });
13156
+ var OkPreflightAccessResponseSchema = exports_external.object({
13157
+ ok: exports_external.literal(true),
13158
+ op: exports_external.literal("preflight_access"),
13159
+ results: exports_external.array(exports_external.object({
13160
+ key: exports_external.string(),
13161
+ exists: exports_external.boolean(),
13162
+ acl_ok: exports_external.boolean(),
13163
+ acl_reason: exports_external.string().optional(),
13164
+ scope_ok: exports_external.boolean(),
13165
+ scope_reason: exports_external.string().optional()
13166
+ }))
13167
+ });
13105
13168
  var ApprovalRequestRequestSchema = exports_external.object({
13106
13169
  v: exports_external.literal(1),
13107
13170
  op: exports_external.literal("approval_request"),
@@ -13153,12 +13216,22 @@ var ApprovalRecordRequestSchema = exports_external.object({
13153
13216
  granted_by_user_id: exports_external.number().int(),
13154
13217
  ttl_ms: exports_external.number().int().positive().nullable().optional()
13155
13218
  });
13219
+ var ApprovalConsumeRecordRequestSchema = exports_external.object({
13220
+ v: exports_external.literal(1),
13221
+ op: exports_external.literal("approval_consume_record"),
13222
+ request_id: exports_external.string().regex(/^[0-9a-f]{32}$/),
13223
+ decision: ApprovalDecisionModeSchema,
13224
+ approver_set: exports_external.array(exports_external.string()),
13225
+ granted_by_user_id: exports_external.number().int(),
13226
+ ttl_ms: exports_external.number().int().positive().nullable().optional()
13227
+ });
13156
13228
  var RequestSchema = exports_external.discriminatedUnion("op", [
13157
13229
  GetRequestSchema,
13158
13230
  PutRequestSchema,
13159
13231
  ListRequestSchema,
13160
13232
  StatusRequestSchema,
13161
13233
  LockRequestSchema,
13234
+ PreflightAccessRequestSchema,
13162
13235
  MintGrantRequestSchema,
13163
13236
  ListGrantsRequestSchema,
13164
13237
  RevokeGrantRequestSchema,
@@ -13167,7 +13240,8 @@ var RequestSchema = exports_external.discriminatedUnion("op", [
13167
13240
  ApprovalConsumeRequestSchema,
13168
13241
  ApprovalRevokeRequestSchema,
13169
13242
  ApprovalListRequestSchema,
13170
- ApprovalRecordRequestSchema
13243
+ ApprovalRecordRequestSchema,
13244
+ ApprovalConsumeRecordRequestSchema
13171
13245
  ]);
13172
13246
  var VaultEntrySchema = exports_external.union([
13173
13247
  exports_external.object({ kind: exports_external.literal("string"), value: exports_external.string() }),
@@ -13289,6 +13363,15 @@ var OkApprovalRecordResponseSchema = exports_external.object({
13289
13363
  ok: exports_external.literal(true),
13290
13364
  decision_id: exports_external.string()
13291
13365
  });
13366
+ var OkApprovalConsumeRecordResponseSchema = exports_external.object({
13367
+ ok: exports_external.literal(true),
13368
+ consumed: exports_external.boolean(),
13369
+ decision_id: exports_external.string().optional(),
13370
+ agent_unit: exports_external.string().optional(),
13371
+ scope: exports_external.string().optional(),
13372
+ action: exports_external.string().optional(),
13373
+ why: exports_external.string().nullable().optional()
13374
+ });
13292
13375
  var ErrorResponseSchema = exports_external.object({
13293
13376
  ok: exports_external.literal(false),
13294
13377
  code: ErrorCode,
@@ -13299,6 +13382,7 @@ var ResponseSchema = exports_external.union([
13299
13382
  OkKeysResponseSchema,
13300
13383
  OkStatusResponseSchema,
13301
13384
  OkLockResponseSchema,
13385
+ OkPreflightAccessResponseSchema,
13302
13386
  OkPutResponseSchema,
13303
13387
  OkMintGrantResponseSchema,
13304
13388
  OkListGrantsResponseSchema,
@@ -13309,6 +13393,7 @@ var ResponseSchema = exports_external.union([
13309
13393
  OkApprovalRevokeResponseSchema,
13310
13394
  OkApprovalListResponseSchema,
13311
13395
  OkApprovalRecordResponseSchema,
13396
+ OkApprovalConsumeRecordResponseSchema,
13312
13397
  ErrorResponseSchema
13313
13398
  ]);
13314
13399
  function decodeRequest(line) {
@@ -15741,7 +15826,9 @@ class VaultBroker {
15741
15826
  this._writePidFile();
15742
15827
  this._sdNotify(`READY=1
15743
15828
  `);
15744
- this._tryAutoUnlock();
15829
+ if (this.testOpts._testSecrets === undefined) {
15830
+ this._tryAutoUnlock();
15831
+ }
15745
15832
  if (process.platform !== "linux") {
15746
15833
  process.stderr.write(`[vault-broker] WARNING: running on ${process.platform} with ` + `SWITCHROOM_BROKER_ALLOW_NON_LINUX=1 — peercred ACL is disabled. ` + `Access control is socket file mode 0600 ONLY. Do not use this configuration for production secrets.
15747
15834
  `);
@@ -16052,6 +16139,49 @@ class VaultBroker {
16052
16139
  socket.write(encodeResponse({ ok: true, locked: true }));
16053
16140
  return;
16054
16141
  }
16142
+ if (req.op === "preflight_access") {
16143
+ if (!isOperator) {
16144
+ writeAudit({
16145
+ ts: new Date().toISOString(),
16146
+ op: "preflight_access",
16147
+ caller: auditCaller,
16148
+ pid: auditPid,
16149
+ cgroup: auditCgroup,
16150
+ result: "denied:operator-only"
16151
+ });
16152
+ socket.write(encodeResponse(errorResponse("DENIED", "preflight_access is operator-only")));
16153
+ return;
16154
+ }
16155
+ if (this.secrets === null) {
16156
+ socket.write(encodeResponse(errorResponse("LOCKED", "Vault is locked")));
16157
+ return;
16158
+ }
16159
+ const pfSecrets = this.secrets;
16160
+ const pfConfig = this.config;
16161
+ const results = req.keys.map((key) => {
16162
+ const exists = Object.prototype.hasOwnProperty.call(pfSecrets, key);
16163
+ const acl = pfConfig !== null ? checkAclByAgent(pfConfig, req.agent, key) : { allow: false, reason: "broker has no config loaded" };
16164
+ const scope = checkEntryScope(pfSecrets[key]?.scope, req.agent);
16165
+ return {
16166
+ key,
16167
+ exists,
16168
+ acl_ok: acl.allow,
16169
+ ...acl.allow ? {} : { acl_reason: acl.reason },
16170
+ scope_ok: scope.allow,
16171
+ ...scope.allow ? {} : { scope_reason: scope.reason }
16172
+ };
16173
+ });
16174
+ writeAudit({
16175
+ ts: new Date().toISOString(),
16176
+ op: "preflight_access",
16177
+ caller: auditCaller,
16178
+ pid: auditPid,
16179
+ cgroup: auditCgroup,
16180
+ result: `allowed:agent=${req.agent},keys=${req.keys.length}`
16181
+ });
16182
+ socket.write(encodeResponse({ ok: true, op: "preflight_access", results }));
16183
+ return;
16184
+ }
16055
16185
  if (req.op === "list") {
16056
16186
  if (this.secrets === null) {
16057
16187
  socket.write(encodeResponse(errorResponse("LOCKED", "Vault is locked")));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.12.13",
3
+ "version": "0.12.15",
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": {