switchroom 0.19.45 → 0.19.47
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 +12 -3
- package/dist/auth-broker/index.js +15 -5
- package/dist/cli/notion-write-pretool.mjs +12 -3
- package/dist/cli/switchroom.js +483 -43
- package/dist/host-control/main.js +16 -6
- package/dist/vault/approvals/kernel-server.js +15 -5
- package/dist/vault/broker/server.js +15 -5
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +48 -4
- package/telegram-plugin/answer-ready-flush.ts +118 -6
- package/telegram-plugin/dist/gateway/gateway.js +124 -21
- package/telegram-plugin/gateway/auth-command.ts +7 -4
- package/telegram-plugin/gateway/auth-loopback-relay.ts +39 -20
- package/telegram-plugin/gateway/gateway.ts +20 -19
- package/telegram-plugin/gateway/handback-preturn-signal.ts +44 -6
- package/telegram-plugin/gateway/session-consume-signal.ts +72 -0
- package/telegram-plugin/gateway/stream-render.ts +5 -0
- package/telegram-plugin/tests/answer-ready-flush.test.ts +147 -3
- package/telegram-plugin/tests/auth-loopback-relay.test.ts +58 -0
- package/telegram-plugin/tests/handback-preturn-signal.test.ts +77 -0
package/dist/cli/switchroom.js
CHANGED
|
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
|
|
|
2120
2120
|
});
|
|
2121
2121
|
|
|
2122
2122
|
// src/build-info.ts
|
|
2123
|
-
var VERSION = "0.19.
|
|
2123
|
+
var VERSION = "0.19.47", COMMIT_SHA = "9693493e";
|
|
2124
2124
|
|
|
2125
2125
|
// src/cli/resolve-version.ts
|
|
2126
2126
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -13595,7 +13595,7 @@ function isValidTimezone(value) {
|
|
|
13595
13595
|
function resolveMemoryProfile(agentConfig) {
|
|
13596
13596
|
return agentConfig?.memory?.profile ?? agentConfig?.extends ?? DEFAULT_PROFILE;
|
|
13597
13597
|
}
|
|
13598
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, ObservationScopeStrategySchema, AntiConfabulationDirectiveSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
13598
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, ObservationScopeStrategySchema, AntiConfabulationDirectiveSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleServiceTokenSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
13599
13599
|
var init_schema = __esm(() => {
|
|
13600
13600
|
init_zod();
|
|
13601
13601
|
init_observation_scopes();
|
|
@@ -13982,11 +13982,18 @@ var init_schema = __esm(() => {
|
|
|
13982
13982
|
"extended",
|
|
13983
13983
|
"complete"
|
|
13984
13984
|
]);
|
|
13985
|
+
GoogleServiceTokenSchema = exports_external.enum([
|
|
13986
|
+
"cal",
|
|
13987
|
+
"drive",
|
|
13988
|
+
"docs",
|
|
13989
|
+
"sheets",
|
|
13990
|
+
"slides"
|
|
13991
|
+
]);
|
|
13985
13992
|
GoogleWorkspaceConfigSchema = exports_external.object({
|
|
13986
13993
|
google_client_id: exports_external.string().min(1).describe("Google OAuth client ID (literal string or vault reference e.g. 'vault:google-oauth-client-id')"),
|
|
13987
13994
|
google_client_secret: exports_external.string().min(1).describe("Google OAuth client secret (literal string or vault reference e.g. 'vault:google-oauth-client-secret')"),
|
|
13988
13995
|
approvers: exports_external.array(ApproverIdSchema).min(1).describe("Array of numeric Telegram user IDs authorized to approve drive onboarding. " + "At least one must be specified."),
|
|
13989
|
-
tier: GoogleWorkspaceTierSchema.optional().describe("RFC G Phase 1: which upstream MCP tier to expose. " + "core (default) = ~16 tools (Drive+Docs+Sheets+Calendar). " + "extended = ~40 tools (+Slides, Forms, Tasks, Chat). " + "complete = ~60+ tools (+Gmail; not recommended yet \u2014 see RFC G \u00a75).")
|
|
13996
|
+
tier: GoogleWorkspaceTierSchema.optional().describe("RFC G Phase 1: which upstream MCP tier to expose. " + "core (default) = ~16 tools (Drive+Docs+Sheets+Calendar tools; the " + "Calendar tools only authenticate when the account holds the opt-in " + "calendar scope \u2014 no tier mints it). " + "extended = ~40 tools (+Slides, Forms, Tasks, Chat). " + "complete = ~60+ tools (+Gmail; not recommended yet \u2014 see RFC G \u00a75).")
|
|
13990
13997
|
}).optional();
|
|
13991
13998
|
LiteLLMConfigSchema = exports_external.object({
|
|
13992
13999
|
enabled: exports_external.boolean().optional().describe("Opt-in toggle. When true, `switchroom apply` provisions a per-agent " + "LiteLLM virtual key and injects routing env into the container. " + "Default OFF."),
|
|
@@ -14019,7 +14026,7 @@ var init_schema = __esm(() => {
|
|
|
14019
14026
|
reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent \u2192 uses global."),
|
|
14020
14027
|
consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent \u2192 global.")
|
|
14021
14028
|
}).optional().describe("LLM knob for the hindsight container. The flat `provider`/`model` set " + "the global default (backward-compatible); optional `retain`/`reflect`/" + "`consolidation` blocks override individual ops. All fields optional; " + "unset fields fall back to the hard-coded defaults."),
|
|
14022
|
-
env: exports_external.record(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional().describe("Operator overrides for switchroom's capability-gated Hindsight " + "performance defaults. Only the keys switchroom actually manages are " + "honoured (`HINDSIGHT_PERF_ENV_KEYS` in " + "src/setup/hindsight-perf-defaults.ts: RERANKER_LOCAL_FP16, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_RESERVED_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, GRAPH_SEED_MIN_SIMILARITY, " + "LLM_SUPPORTS_MAX_ITEMS, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS \u2014 switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY \u2014 a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP \u2014 the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS \u2014 only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS \u2014 the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_RESERVED_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS \u2014 the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool; and " + "the pre-0.8.6 name HINDSIGHT_API_WORKER_<TYPE>_MAX_SLOTS is still " + "accepted for every operation type and normalised to " + "..._RESERVED_SLOTS on the way in, because setting both names for " + "one type is a hard boot failure in the engine and switchroom " + "therefore emits only the canonical name; and " + "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY \u2014 the cosine-similarity floor a " + "candidate must reach to be returned by the semantic retrieval arm at " + "all (0..1); unset means upstream's own 0.3, and where it should sit " + "depends on the bank's embedding model and phrasing diversity, so " + "switchroom ships no opinion; and " + "HINDSIGHT_MCP_RECALL_BUDGET_MODE \u2014 the rollback knob for switchroom's " + "mcp-recall-token-budget image patch; `legacy` restores upstream's " + "exact pre-patch recall returns, anything else or unset is the " + "honest-envelope mode; and " + "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT \u2014 upstream's own per-op reflect " + "temperature knob, made live by switchroom's reflect-temperature image " + "patch; a float, or `none` to omit the kwarg (provider default, " + "upstream's accidental pre-patch behaviour); unset means the image's " + "baked default 0.1; and " + "HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT \u2014 the background " + "half of switchroom's recall-admission split (#3660): of the " + "RECALL_MAX_CONCURRENT admission slots, at most this many may be held " + "by background consolidation recalls at once, so foreground per-turn " + "recall always keeps the remainder; must be >= 1 and strictly less " + "than RECALL_MAX_CONCURRENT or the engine refuses to boot; unset " + "means the image's derived default min(2, RECALL_MAX_CONCURRENT - 1); " + "1 biases hard toward the interactive lane while a consolidation " + "backlog drains), plus the " + "embedded-PostgreSQL (pg0) sizing keys switchroom manages in " + "src/setup/hindsight-pg-defaults.ts (`HINDSIGHT_PG_ENV_KEYS`: " + "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE, " + "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS \u2014 a postgres size string such " + "as `4GB`, or the sentinel `off` to leave pg0's own default for that " + "one knob). A value set here " + "REPLACES switchroom's default and is emitted even when the gating " + "capability is absent, so an operator can always force a knob. Other " + "`HINDSIGHT_API_*` keys are deliberately IGNORED \u2014 a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
|
|
14029
|
+
env: exports_external.record(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional().describe("Operator overrides for switchroom's capability-gated Hindsight " + "performance defaults. Only the keys switchroom actually manages are " + "honoured (`HINDSIGHT_PERF_ENV_KEYS` in " + "src/setup/hindsight-perf-defaults.ts: RERANKER_LOCAL_FP16, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_RESERVED_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, GRAPH_SEED_MIN_SIMILARITY, " + "LLM_SUPPORTS_MAX_ITEMS, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS \u2014 switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY \u2014 a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP \u2014 the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS \u2014 only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS \u2014 the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_RESERVED_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS \u2014 the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool; and " + "the pre-0.8.6 name HINDSIGHT_API_WORKER_<TYPE>_MAX_SLOTS is still " + "accepted for every operation type and normalised to " + "..._RESERVED_SLOTS on the way in, because setting both names for " + "one type is a hard boot failure in the engine and switchroom " + "therefore emits only the canonical name; and " + "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY \u2014 the cosine-similarity floor a " + "candidate must reach to be returned by the semantic retrieval arm at " + "all (0..1); unset means upstream's own 0.3, and where it should sit " + "depends on the bank's embedding model and phrasing diversity, so " + "switchroom ships no opinion; and " + "HINDSIGHT_MCP_RECALL_BUDGET_MODE \u2014 the rollback knob for switchroom's " + "mcp-recall-token-budget image patch; `legacy` restores upstream's " + "exact pre-patch recall returns, anything else or unset is the " + "honest-envelope mode; and " + "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT \u2014 upstream's own per-op reflect " + "temperature knob, made live by switchroom's reflect-temperature image " + "patch; a float, or `none` to omit the kwarg (provider default, " + "upstream's accidental pre-patch behaviour); unset means the image's " + "baked default 0.1; and " + "HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT \u2014 the background " + "half of switchroom's recall-admission split (#3660): of the " + "RECALL_MAX_CONCURRENT admission slots, at most this many may be held " + "by background consolidation recalls at once, so foreground per-turn " + "recall always keeps the remainder; must be >= 1 and strictly less " + "than RECALL_MAX_CONCURRENT or the engine refuses to boot; unset " + "means the image's derived default min(2, RECALL_MAX_CONCURRENT - 1); " + "1 biases hard toward the interactive lane while a consolidation " + "backlog drains; and " + "HINDSIGHT_MM_REFRESH_MIN_INTERVAL_S \u2014 the rollback/tuning knob for " + "switchroom's MM-refresh-debounce image patch: the minimum seconds " + "between consolidation-triggered refreshes of one mental model; unset " + "means the image's baked 3600, 0 restores upstream's " + "refresh-every-round behaviour; explicit and cron-scheduled refreshes " + "are never debounced), plus the " + "embedded-PostgreSQL (pg0) sizing keys switchroom manages in " + "src/setup/hindsight-pg-defaults.ts (`HINDSIGHT_PG_ENV_KEYS`: " + "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE, " + "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS \u2014 a postgres size string such " + "as `4GB`, or the sentinel `off` to leave pg0's own default for that " + "one knob). A value set here " + "REPLACES switchroom's default and is emitted even when the gating " + "capability is absent, so an operator can always force a knob. Other " + "`HINDSIGHT_API_*` keys are deliberately IGNORED \u2014 a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
|
|
14023
14030
|
});
|
|
14024
14031
|
MicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
14025
14032
|
microsoft_client_id: exports_external.string().min(1).optional().describe("Microsoft OAuth application (client) ID from Entra portal " + "(literal string or vault reference e.g. " + "'vault:microsoft-oauth-client-id'). OPTIONAL \u2014 omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
|
|
@@ -14493,7 +14500,9 @@ var init_schema = __esm(() => {
|
|
|
14493
14500
|
}).transform((v) => v.trim().toLowerCase()), exports_external.object({
|
|
14494
14501
|
enabled_for: exports_external.array(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
14495
14502
|
message: "Agent name must match the standard agent-name pattern"
|
|
14496
|
-
})).describe("Agent slugs that may read this account's vault slots " + "(`google:<account>:refresh_token` etc). Per-agent ACL is " + "enforced at the broker, not at the agent identity layer \u2014 " + "the agent still authenticates via socket-path-as-identity " + "per RFC D \u00a74.1, broker just gates the cross-agent token share.")
|
|
14503
|
+
})).describe("Agent slugs that may read this account's vault slots " + "(`google:<account>:refresh_token` etc). Per-agent ACL is " + "enforced at the broker, not at the agent identity layer \u2014 " + "the agent still authenticates via socket-path-as-identity " + "per RFC D \u00a74.1, broker just gates the cross-agent token share."),
|
|
14504
|
+
readonly: exports_external.boolean().optional().describe("Per-account read-only selection (v1 scope model). true \u2192 the " + "token minted by `auth google account add` carries ONLY " + ".readonly scope variants (zero write scopes), and the gdrive " + "MCP launcher passes upstream `--read-only` so write tools are " + "not exposed. Written by `account add --readonly`; re-read on " + "`--replace` so a re-consent cannot silently re-widen. Omitted " + "= legacy behaviour (tier-tied read-write document scopes)."),
|
|
14505
|
+
services: exports_external.array(GoogleServiceTokenSchema).min(1).optional().describe("Per-account service selection (v1 scope model). Which Google " + "services the minted token covers AND the gdrive MCP exposes " + "(upstream `--tools`): cal, drive, docs, sheets, slides. " + "Written by `account add --services`; re-read on `--replace`. " + "Omitted = the tier's default services (drive,docs,sheets; " + "+slides at extended/complete).")
|
|
14497
14506
|
})).optional().describe("RFC G Phase 2: per-Google-account ACL for vault slots holding " + "OAuth refresh tokens. Maps account email \u2192 list of agents " + "permitted to read that account's slots. Written by `switchroom " + "auth google enable|disable` (Phase 3); read by the broker on " + "every Google slot access. Replaces RFC D's per-agent vault slot " + "scope (which can't express 'two agents share one Google account')."),
|
|
14498
14507
|
microsoft_accounts: exports_external.record(exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
14499
14508
|
message: "Account key must be a Microsoft account email like 'alice@outlook.com' or 'alice@contoso.com' (colons not allowed)"
|
|
@@ -16075,7 +16084,8 @@ var init_hindsight_perf_defaults = __esm(() => {
|
|
|
16075
16084
|
"HINDSIGHT_MCP_RECALL_BUDGET_MODE",
|
|
16076
16085
|
"HINDSIGHT_API_LLM_TEMPERATURE_REFLECT",
|
|
16077
16086
|
"HINDSIGHT_API_RETAIN_WALL_TIMEOUT",
|
|
16078
|
-
"HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT"
|
|
16087
|
+
"HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT",
|
|
16088
|
+
"HINDSIGHT_MM_REFRESH_MIN_INTERVAL_S"
|
|
16079
16089
|
]);
|
|
16080
16090
|
HINDSIGHT_WORKER_SLOT_TYPES = [
|
|
16081
16091
|
"consolidation",
|
|
@@ -27363,11 +27373,18 @@ function getPlaywrightMcpSettingsEntry() {
|
|
|
27363
27373
|
}
|
|
27364
27374
|
function getGdriveMcpSettingsEntry(switchroomCliPath, options = {}) {
|
|
27365
27375
|
const tierArgs = options.tier ? ["--tier", options.tier] : [];
|
|
27376
|
+
const servicesArgs = options.services && options.services.length > 0 ? ["--services", options.services.join(",")] : [];
|
|
27377
|
+
const readOnlyArgs = options.readOnly ? ["--read-only"] : [];
|
|
27366
27378
|
return {
|
|
27367
27379
|
key: "gdrive",
|
|
27368
27380
|
value: {
|
|
27369
27381
|
command: switchroomCliPath,
|
|
27370
|
-
args: [
|
|
27382
|
+
args: [
|
|
27383
|
+
"drive-mcp-launcher",
|
|
27384
|
+
...tierArgs,
|
|
27385
|
+
...servicesArgs,
|
|
27386
|
+
...readOnlyArgs
|
|
27387
|
+
]
|
|
27371
27388
|
}
|
|
27372
27389
|
};
|
|
27373
27390
|
}
|
|
@@ -29941,7 +29958,12 @@ function resolveGdriveMcpEntry(agentName, agentConfig, switchroomConfig) {
|
|
|
29941
29958
|
if (!shouldEmitGdriveMcp(agentName, account, googleAccounts))
|
|
29942
29959
|
return null;
|
|
29943
29960
|
const tier = agentConfig.google_workspace?.tier ?? switchroomConfig?.google_workspace?.tier;
|
|
29944
|
-
const
|
|
29961
|
+
const accountRecord = account ? googleAccounts?.[account.trim().toLowerCase()] : undefined;
|
|
29962
|
+
const entry = getGdriveMcpSettingsEntry(DOCKER_SWITCHROOM_CLI_PATH, {
|
|
29963
|
+
...tier ? { tier } : {},
|
|
29964
|
+
...accountRecord?.services && accountRecord.services.length > 0 ? { services: accountRecord.services } : {},
|
|
29965
|
+
...accountRecord?.readonly ? { readOnly: true } : {}
|
|
29966
|
+
});
|
|
29945
29967
|
entry.value.env = {
|
|
29946
29968
|
SWITCHROOM_CONFIG: DOCKER_CONFIG_PATH,
|
|
29947
29969
|
SWITCHROOM_AGENT_NAME: agentName,
|
|
@@ -37436,32 +37458,170 @@ var init_wait = __esm(() => {
|
|
|
37436
37458
|
var exports_drive = {};
|
|
37437
37459
|
__export(exports_drive, {
|
|
37438
37460
|
workspaceScopesForTier: () => workspaceScopesForTier,
|
|
37461
|
+
tierDefaultServices: () => tierDefaultServices,
|
|
37439
37462
|
selectGoogleWorkspaceScopes: () => selectGoogleWorkspaceScopes,
|
|
37440
37463
|
selectDriveAccountScopes: () => selectDriveAccountScopes,
|
|
37464
|
+
scopesForSelection: () => scopesForSelection,
|
|
37441
37465
|
runDriveOAuthFlow: () => runDriveOAuthFlow,
|
|
37466
|
+
resolveScopeSelection: () => resolveScopeSelection,
|
|
37467
|
+
resolveReconsentCapabilities: () => resolveReconsentCapabilities,
|
|
37442
37468
|
registerDriveCommand: () => registerDriveCommand,
|
|
37469
|
+
parseServicesOption: () => parseServicesOption,
|
|
37470
|
+
capabilitiesFromScopeString: () => capabilitiesFromScopeString,
|
|
37443
37471
|
__test: () => __test,
|
|
37444
37472
|
GOOGLE_SLIDES_SCOPE: () => GOOGLE_SLIDES_SCOPE,
|
|
37473
|
+
GOOGLE_SLIDES_READONLY_SCOPE: () => GOOGLE_SLIDES_READONLY_SCOPE,
|
|
37445
37474
|
GOOGLE_SHEETS_SCOPE: () => GOOGLE_SHEETS_SCOPE,
|
|
37475
|
+
GOOGLE_SHEETS_READONLY_SCOPE: () => GOOGLE_SHEETS_READONLY_SCOPE,
|
|
37476
|
+
GOOGLE_SERVICES: () => GOOGLE_SERVICES,
|
|
37446
37477
|
GOOGLE_DOCS_SCOPE: () => GOOGLE_DOCS_SCOPE,
|
|
37478
|
+
GOOGLE_DOCS_READONLY_SCOPE: () => GOOGLE_DOCS_READONLY_SCOPE,
|
|
37479
|
+
GOOGLE_CALENDAR_READONLY_SCOPE: () => GOOGLE_CALENDAR_READONLY_SCOPE,
|
|
37447
37480
|
DRIVE_WRITE_SCOPES: () => DRIVE_WRITE_SCOPES,
|
|
37448
37481
|
DRIVE_READONLY_SCOPES: () => DRIVE_READONLY_SCOPES
|
|
37449
37482
|
});
|
|
37450
37483
|
import { createInterface as createInterface2 } from "node:readline";
|
|
37451
|
-
function
|
|
37452
|
-
const
|
|
37484
|
+
function parseServicesOption(raw) {
|
|
37485
|
+
const tokens = raw.split(",").map((s) => s.trim().toLowerCase()).filter((s) => s.length > 0).map((s) => s === "calendar" ? "cal" : s);
|
|
37486
|
+
if (tokens.length === 0) {
|
|
37487
|
+
throw new Error(`--services requires at least one service (valid: ${GOOGLE_SERVICES.join(", ")})`);
|
|
37488
|
+
}
|
|
37489
|
+
for (const t of tokens) {
|
|
37490
|
+
if (!GOOGLE_SERVICES.includes(t)) {
|
|
37491
|
+
throw new Error(`--services: unknown service '${t}' (valid: ${GOOGLE_SERVICES.join(", ")}; ` + `'calendar' is accepted as an alias for 'cal')`);
|
|
37492
|
+
}
|
|
37493
|
+
}
|
|
37494
|
+
const set = new Set(tokens);
|
|
37495
|
+
return GOOGLE_SERVICES.filter((s) => set.has(s));
|
|
37496
|
+
}
|
|
37497
|
+
function tierDefaultServices(tier) {
|
|
37453
37498
|
if (tier === "extended" || tier === "complete") {
|
|
37454
|
-
return [
|
|
37499
|
+
return ["drive", "docs", "sheets", "slides"];
|
|
37500
|
+
}
|
|
37501
|
+
return ["drive", "docs", "sheets"];
|
|
37502
|
+
}
|
|
37503
|
+
function scopesForSelection(sel) {
|
|
37504
|
+
const selected = new Set(sel.services);
|
|
37505
|
+
const out = [];
|
|
37506
|
+
if (selected.has("drive")) {
|
|
37507
|
+
out.push(...DRIVE_READONLY_SCOPES);
|
|
37508
|
+
if (sel.driveWrite && !sel.readonly) {
|
|
37509
|
+
out.push("https://www.googleapis.com/auth/drive.file");
|
|
37510
|
+
}
|
|
37511
|
+
}
|
|
37512
|
+
if (selected.has("docs")) {
|
|
37513
|
+
out.push(sel.readonly ? GOOGLE_DOCS_READONLY_SCOPE : GOOGLE_DOCS_SCOPE);
|
|
37514
|
+
}
|
|
37515
|
+
if (selected.has("sheets")) {
|
|
37516
|
+
out.push(sel.readonly ? GOOGLE_SHEETS_READONLY_SCOPE : GOOGLE_SHEETS_SCOPE);
|
|
37517
|
+
}
|
|
37518
|
+
if (selected.has("slides")) {
|
|
37519
|
+
out.push(sel.readonly ? GOOGLE_SLIDES_READONLY_SCOPE : GOOGLE_SLIDES_SCOPE);
|
|
37455
37520
|
}
|
|
37456
|
-
|
|
37521
|
+
if (selected.has("cal")) {
|
|
37522
|
+
out.push(GOOGLE_CALENDAR_READONLY_SCOPE);
|
|
37523
|
+
}
|
|
37524
|
+
return [...new Set(out)];
|
|
37525
|
+
}
|
|
37526
|
+
function workspaceScopesForTier(tier) {
|
|
37527
|
+
return scopesForSelection({
|
|
37528
|
+
readonly: false,
|
|
37529
|
+
driveWrite: false,
|
|
37530
|
+
services: tierDefaultServices(tier).filter((s) => s !== "drive")
|
|
37531
|
+
});
|
|
37457
37532
|
}
|
|
37458
37533
|
function selectDriveAccountScopes(write) {
|
|
37459
37534
|
return write ? DRIVE_WRITE_SCOPES : DRIVE_READONLY_SCOPES;
|
|
37460
37535
|
}
|
|
37461
37536
|
function selectGoogleWorkspaceScopes(opts) {
|
|
37462
|
-
|
|
37463
|
-
|
|
37464
|
-
|
|
37537
|
+
if (opts.readonly && opts.write) {
|
|
37538
|
+
throw new Error("--readonly and --write are mutually exclusive: a read-only " + "selection mints zero write scopes by definition.");
|
|
37539
|
+
}
|
|
37540
|
+
const services = opts.services ?? tierDefaultServices(opts.tier ?? "core");
|
|
37541
|
+
const withCal = opts.calendar && !services.includes("cal") ? [...services, "cal"] : services;
|
|
37542
|
+
const set = new Set(withCal);
|
|
37543
|
+
return scopesForSelection({
|
|
37544
|
+
readonly: opts.readonly ?? false,
|
|
37545
|
+
driveWrite: opts.write,
|
|
37546
|
+
services: GOOGLE_SERVICES.filter((s) => set.has(s))
|
|
37547
|
+
});
|
|
37548
|
+
}
|
|
37549
|
+
function capabilitiesFromScopeString(scope) {
|
|
37550
|
+
const have = new Set(scope.split(/\s+/).map((s) => s.trim()).filter((s) => s.length > 0));
|
|
37551
|
+
return {
|
|
37552
|
+
write: have.has("https://www.googleapis.com/auth/drive.file"),
|
|
37553
|
+
calendar: have.has(GOOGLE_CALENDAR_READONLY_SCOPE)
|
|
37554
|
+
};
|
|
37555
|
+
}
|
|
37556
|
+
function resolveReconsentCapabilities(requested, existing) {
|
|
37557
|
+
const keys = ["write", "calendar"];
|
|
37558
|
+
if (!existing) {
|
|
37559
|
+
return { effective: { ...requested }, carried: [], added: [] };
|
|
37560
|
+
}
|
|
37561
|
+
return {
|
|
37562
|
+
effective: {
|
|
37563
|
+
write: requested.write || existing.write,
|
|
37564
|
+
calendar: requested.calendar || existing.calendar
|
|
37565
|
+
},
|
|
37566
|
+
carried: keys.filter((k) => existing[k] && !requested[k]),
|
|
37567
|
+
added: keys.filter((k) => requested[k] && !existing[k])
|
|
37568
|
+
};
|
|
37569
|
+
}
|
|
37570
|
+
function resolveScopeSelection(args) {
|
|
37571
|
+
const { flags, persisted, existing, tier } = args;
|
|
37572
|
+
if (flags.readonly && flags.write) {
|
|
37573
|
+
throw new Error("--readonly and --write are mutually exclusive. A read-only account " + "mints zero write scopes; use --write (without --readonly) for the " + "drive.file write grant.");
|
|
37574
|
+
}
|
|
37575
|
+
const dropped = [];
|
|
37576
|
+
const readonly = flags.readonly ? true : flags.write ? false : persisted?.readonly ?? false;
|
|
37577
|
+
if (flags.write && persisted?.readonly) {
|
|
37578
|
+
dropped.push("read-only mode (persisted `readonly: true`) \u2014 dropped because --write was passed");
|
|
37579
|
+
}
|
|
37580
|
+
let driveWrite = existing.write;
|
|
37581
|
+
if (readonly && existing.write) {
|
|
37582
|
+
driveWrite = false;
|
|
37583
|
+
dropped.push("Drive write (drive.file) \u2014 the existing token carries it, but --readonly was passed");
|
|
37584
|
+
}
|
|
37585
|
+
const explicitServices = !!(flags.services && flags.services.length > 0);
|
|
37586
|
+
let services;
|
|
37587
|
+
let persistServices;
|
|
37588
|
+
if (explicitServices) {
|
|
37589
|
+
services = [...flags.services];
|
|
37590
|
+
persistServices = true;
|
|
37591
|
+
if (flags.calendar && !services.includes("cal"))
|
|
37592
|
+
services.push("cal");
|
|
37593
|
+
} else if (persisted?.services && persisted.services.length > 0) {
|
|
37594
|
+
services = [...persisted.services];
|
|
37595
|
+
persistServices = true;
|
|
37596
|
+
if ((flags.calendar || existing.calendar) && !services.includes("cal")) {
|
|
37597
|
+
services.push("cal");
|
|
37598
|
+
}
|
|
37599
|
+
} else {
|
|
37600
|
+
services = [...tierDefaultServices(tier)];
|
|
37601
|
+
persistServices = false;
|
|
37602
|
+
if ((flags.calendar || existing.calendar) && !services.includes("cal")) {
|
|
37603
|
+
services.push("cal");
|
|
37604
|
+
}
|
|
37605
|
+
}
|
|
37606
|
+
const set = new Set(services);
|
|
37607
|
+
const canonical = GOOGLE_SERVICES.filter((s) => set.has(s));
|
|
37608
|
+
if (explicitServices) {
|
|
37609
|
+
const prior = persisted?.services ?? (existing.calendar ? [...tierDefaultServices(tier), "cal"] : tierDefaultServices(tier));
|
|
37610
|
+
for (const s of prior) {
|
|
37611
|
+
if (!set.has(s)) {
|
|
37612
|
+
dropped.push(`service '${s}' \u2014 previously granted, not in the --services list passed`);
|
|
37613
|
+
}
|
|
37614
|
+
}
|
|
37615
|
+
}
|
|
37616
|
+
const persist = flags.readonly || persistServices || persisted !== undefined ? {
|
|
37617
|
+
readonly,
|
|
37618
|
+
services: canonical
|
|
37619
|
+
} : null;
|
|
37620
|
+
return {
|
|
37621
|
+
selection: { readonly, services: canonical, driveWrite },
|
|
37622
|
+
persist,
|
|
37623
|
+
dropped
|
|
37624
|
+
};
|
|
37465
37625
|
}
|
|
37466
37626
|
function getVaultPath(configPath) {
|
|
37467
37627
|
try {
|
|
@@ -37875,7 +38035,7 @@ function registerDriveCommand(program3, deps = {}) {
|
|
|
37875
38035
|
await runDisconnect({ agentName: agent }, deps);
|
|
37876
38036
|
});
|
|
37877
38037
|
}
|
|
37878
|
-
var EXIT_OK = 0, EXIT_DENIED = 1, EXIT_TIMEOUT = 2, EXIT_RATE_LIMITED = 3, EXIT_ERROR = 4, EXIT_ABORTED = 130, DRIVE_READONLY_SCOPES, DRIVE_WRITE_SCOPES, DEFAULT_SCOPES, GOOGLE_DOCS_SCOPE = "https://www.googleapis.com/auth/documents", GOOGLE_SHEETS_SCOPE = "https://www.googleapis.com/auth/spreadsheets", GOOGLE_SLIDES_SCOPE = "https://www.googleapis.com/auth/presentations", __test;
|
|
38038
|
+
var EXIT_OK = 0, EXIT_DENIED = 1, EXIT_TIMEOUT = 2, EXIT_RATE_LIMITED = 3, EXIT_ERROR = 4, EXIT_ABORTED = 130, DRIVE_READONLY_SCOPES, DRIVE_WRITE_SCOPES, DEFAULT_SCOPES, GOOGLE_DOCS_SCOPE = "https://www.googleapis.com/auth/documents", GOOGLE_SHEETS_SCOPE = "https://www.googleapis.com/auth/spreadsheets", GOOGLE_SLIDES_SCOPE = "https://www.googleapis.com/auth/presentations", GOOGLE_CALENDAR_READONLY_SCOPE = "https://www.googleapis.com/auth/calendar.readonly", GOOGLE_DOCS_READONLY_SCOPE = "https://www.googleapis.com/auth/documents.readonly", GOOGLE_SHEETS_READONLY_SCOPE = "https://www.googleapis.com/auth/spreadsheets.readonly", GOOGLE_SLIDES_READONLY_SCOPE = "https://www.googleapis.com/auth/presentations.readonly", GOOGLE_SERVICES, __test;
|
|
37879
38039
|
var init_drive = __esm(() => {
|
|
37880
38040
|
init_source();
|
|
37881
38041
|
init_loader();
|
|
@@ -37895,6 +38055,13 @@ var init_drive = __esm(() => {
|
|
|
37895
38055
|
"https://www.googleapis.com/auth/drive.file"
|
|
37896
38056
|
];
|
|
37897
38057
|
DEFAULT_SCOPES = DRIVE_READONLY_SCOPES;
|
|
38058
|
+
GOOGLE_SERVICES = [
|
|
38059
|
+
"drive",
|
|
38060
|
+
"docs",
|
|
38061
|
+
"sheets",
|
|
38062
|
+
"slides",
|
|
38063
|
+
"cal"
|
|
38064
|
+
];
|
|
37898
38065
|
__test = {
|
|
37899
38066
|
runConnect,
|
|
37900
38067
|
runDisconnect,
|
|
@@ -77008,6 +77175,23 @@ function listGoogleAccounts(yamlText) {
|
|
|
77008
77175
|
}
|
|
77009
77176
|
return out;
|
|
77010
77177
|
}
|
|
77178
|
+
function setGoogleAccountSelection(yamlText, account, selection) {
|
|
77179
|
+
const doc = import_yaml6.parseDocument(yamlText);
|
|
77180
|
+
ensureGoogleAccountEntry(doc, account);
|
|
77181
|
+
const currentReadonly = doc.getIn([
|
|
77182
|
+
"google_accounts",
|
|
77183
|
+
account,
|
|
77184
|
+
"readonly"
|
|
77185
|
+
]);
|
|
77186
|
+
const currentServices = doc.getIn(["google_accounts", account, "services"]);
|
|
77187
|
+
const currentServicesArr = readEnabledFor(currentServices);
|
|
77188
|
+
const unchanged = currentReadonly === selection.readonly && currentServicesArr.length === selection.services.length && currentServicesArr.every((s, i) => s === selection.services[i]);
|
|
77189
|
+
if (unchanged)
|
|
77190
|
+
return yamlText;
|
|
77191
|
+
doc.setIn(["google_accounts", account, "readonly"], selection.readonly);
|
|
77192
|
+
doc.setIn(["google_accounts", account, "services"], [...selection.services]);
|
|
77193
|
+
return String(doc);
|
|
77194
|
+
}
|
|
77011
77195
|
function readEnabledFor(node) {
|
|
77012
77196
|
if (!import_yaml6.isSeq(node))
|
|
77013
77197
|
return [];
|
|
@@ -77043,7 +77227,7 @@ function registerAuthGoogleSubcommands(program3, authParent) {
|
|
|
77043
77227
|
registerDisable(google, program3);
|
|
77044
77228
|
registerList(google, program3);
|
|
77045
77229
|
const account = google.command("account").description("Manage Google account credentials in the auth-broker (add / remove / list).");
|
|
77046
|
-
registerAccountAdd(account);
|
|
77230
|
+
registerAccountAdd(account, program3);
|
|
77047
77231
|
registerAccountRemove(account);
|
|
77048
77232
|
registerAccountList(account);
|
|
77049
77233
|
}
|
|
@@ -77303,11 +77487,18 @@ function expandAllAgents(agents, config) {
|
|
|
77303
77487
|
function pad(s, width) {
|
|
77304
77488
|
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
77305
77489
|
}
|
|
77306
|
-
function registerAccountAdd(accountParent) {
|
|
77307
|
-
accountParent.command("add <account>").description("Mint a Google OAuth refresh token for <account> and register it with the auth-broker. For Drive scopes the effective flow is desktop-loopback (device-code returns invalid_scope for Drive; OOB is retired) \u2014 use a Desktop OAuth client; on a headless host complete the browser step over an SSH port-forward. Add --write for create/edit (drive.file); default is read-only.").option("--replace", "Overwrite existing credentials for <account> (default refuses if account already registered)", false).option("--write", "Request Drive WRITE scope (drive.file: create + edit app-created files) in addition to read. Default is read-only \u2014 a read grant never silently becomes a write grant. Re-consent an existing account with `--replace --write`.", false).action(withConfigError(async (account, opts) => {
|
|
77490
|
+
function registerAccountAdd(accountParent, program3) {
|
|
77491
|
+
accountParent.command("add <account>").description("Mint a Google OAuth refresh token for <account> and register it with the auth-broker. For Drive scopes the effective flow is desktop-loopback (device-code returns invalid_scope for Drive; OOB is retired) \u2014 use a Desktop OAuth client; on a headless host complete the browser step over an SSH port-forward. Add --write for create/edit (drive.file) and --calendar for read-only Calendar; default is Drive read-only.").option("--replace", "Overwrite existing credentials for <account> (default refuses if account already registered)", false).option("--write", "Request Drive WRITE scope (drive.file: create + edit app-created files) in addition to read. Default is read-only \u2014 a read grant never silently becomes a write grant. Re-consent an existing account with `--replace --write`.", false).option("--calendar", "Request READ-ONLY Calendar scope (calendar.readonly) so the gdrive MCP's list_calendars / get_events tools authenticate. Opt-in \u2014 no tier grants it by default, and switchroom never requests a Calendar WRITE scope. Re-consent an existing account with `--replace --calendar`.", false).option("--readonly", "Mint ONLY read-only scope variants for every selected service (zero write scopes), and run the gdrive MCP in upstream --read-only mode. Mutually exclusive with --write. Persisted per account and carried forward on --replace \u2014 a read-only account cannot silently re-widen.", false).option("--services <list>", "Comma-separated per-account service selection: cal,drive,docs,sheets,slides ('calendar' accepted for 'cal'). Selects BOTH the OAuth scopes minted AND the tools the gdrive MCP exposes (upstream --tools). Omitted = the tier's default services (drive,docs,sheets; +slides at extended/complete). Persisted per account and carried forward on --replace.").action(withConfigError(async (account, opts) => {
|
|
77308
77492
|
const normalizedAccount = validateAndNormalizeAccountEmail(account);
|
|
77309
77493
|
const [
|
|
77310
|
-
{
|
|
77494
|
+
{
|
|
77495
|
+
runDriveOAuthFlow: runDriveOAuthFlow2,
|
|
77496
|
+
selectGoogleWorkspaceScopes: selectGoogleWorkspaceScopes2,
|
|
77497
|
+
capabilitiesFromScopeString: capabilitiesFromScopeString2,
|
|
77498
|
+
resolveReconsentCapabilities: resolveReconsentCapabilities2,
|
|
77499
|
+
parseServicesOption: parseServicesOption2,
|
|
77500
|
+
resolveScopeSelection: resolveScopeSelection2
|
|
77501
|
+
},
|
|
77311
77502
|
{ selectInitialTier: selectInitialTier2 },
|
|
77312
77503
|
{ brokerCall: brokerCall2 },
|
|
77313
77504
|
{ loadConfig: loadConfig2, resolvePath: resolvePath2 },
|
|
@@ -77377,8 +77568,41 @@ function registerAccountAdd(accountParent) {
|
|
|
77377
77568
|
clientSecretRaw = await resolveRef(clientSecretRaw, "google_client_secret");
|
|
77378
77569
|
}
|
|
77379
77570
|
const tier = gw.tier ?? "core";
|
|
77571
|
+
if (opts.readonly && opts.write) {
|
|
77572
|
+
throw new Error("--readonly and --write are mutually exclusive. A read-only " + "account mints zero write scopes; drop one of the flags.");
|
|
77573
|
+
}
|
|
77574
|
+
const requestedServices = opts.services ? parseServicesOption2(opts.services) : undefined;
|
|
77575
|
+
const persistedRecord = config.google_accounts?.[normalizedAccount];
|
|
77576
|
+
const persistedSelection = persistedRecord && (persistedRecord.readonly !== undefined || persistedRecord.services !== undefined) ? {
|
|
77577
|
+
readonly: persistedRecord.readonly,
|
|
77578
|
+
services: persistedRecord.services
|
|
77579
|
+
} : undefined;
|
|
77580
|
+
const existingEntry = (await brokerCall2(async (client) => client.listGoogleAccounts())).accounts.find((a) => a.account === normalizedAccount);
|
|
77581
|
+
const plan = resolveReconsentCapabilities2({ write: opts.write ?? false, calendar: opts.calendar ?? false }, existingEntry ? capabilitiesFromScopeString2(existingEntry.scope) : undefined);
|
|
77582
|
+
if (existingEntry && !opts.replace && plan.added.length > 0) {
|
|
77583
|
+
throw new Error(buildScopeReconsentRequiredError(normalizedAccount, plan.added));
|
|
77584
|
+
}
|
|
77585
|
+
if (existingEntry && !opts.replace && (opts.readonly || requestedServices)) {
|
|
77586
|
+
throw new Error(buildSelectionReconsentRequiredError(normalizedAccount, {
|
|
77587
|
+
readonly: opts.readonly ?? false,
|
|
77588
|
+
services: opts.services
|
|
77589
|
+
}));
|
|
77590
|
+
}
|
|
77591
|
+
const selPlan = resolveScopeSelection2({
|
|
77592
|
+
flags: {
|
|
77593
|
+
readonly: opts.readonly ?? false,
|
|
77594
|
+
write: opts.write ?? false,
|
|
77595
|
+
calendar: opts.calendar ?? false,
|
|
77596
|
+
services: requestedServices
|
|
77597
|
+
},
|
|
77598
|
+
persisted: persistedSelection,
|
|
77599
|
+
existing: plan.effective,
|
|
77600
|
+
tier
|
|
77601
|
+
});
|
|
77380
77602
|
const accountScopes = selectGoogleWorkspaceScopes2({
|
|
77381
|
-
write:
|
|
77603
|
+
write: selPlan.selection.driveWrite,
|
|
77604
|
+
readonly: selPlan.selection.readonly,
|
|
77605
|
+
services: selPlan.selection.services,
|
|
77382
77606
|
tier
|
|
77383
77607
|
});
|
|
77384
77608
|
const oauthCfg = {
|
|
@@ -77386,10 +77610,27 @@ function registerAccountAdd(accountParent) {
|
|
|
77386
77610
|
client_secret: clientSecretRaw,
|
|
77387
77611
|
scopes: accountScopes
|
|
77388
77612
|
};
|
|
77389
|
-
if (
|
|
77613
|
+
if (selPlan.selection.readonly) {
|
|
77614
|
+
console.log(source_default.yellow(" READ-ONLY selection: minting only .readonly scope variants \u2014 zero write scopes."));
|
|
77615
|
+
}
|
|
77616
|
+
if (selPlan.selection.driveWrite && !selPlan.selection.readonly) {
|
|
77390
77617
|
console.log(source_default.yellow(" Requesting Drive WRITE scope (drive.file \u2014 create/edit app-created files)."));
|
|
77391
77618
|
}
|
|
77392
|
-
|
|
77619
|
+
if (selPlan.selection.services.includes("cal")) {
|
|
77620
|
+
console.log(source_default.yellow(" Requesting Calendar READ-ONLY scope (calendar.readonly \u2014 list calendars, read events)."));
|
|
77621
|
+
}
|
|
77622
|
+
for (const d of selPlan.dropped) {
|
|
77623
|
+
console.log(source_default.yellow(` NOT carried forward: ${d}.`));
|
|
77624
|
+
}
|
|
77625
|
+
const carried = plan.carried.filter((k) => !(k === "write" && selPlan.selection.readonly) && !(k === "calendar" && !selPlan.selection.services.includes("cal")));
|
|
77626
|
+
for (const line of buildCarryForwardNotices(carried)) {
|
|
77627
|
+
console.log(source_default.gray(line));
|
|
77628
|
+
}
|
|
77629
|
+
if (selPlan.persist) {
|
|
77630
|
+
console.log(source_default.gray(` Service selection: ${selPlan.selection.services.join(", ")}` + (selPlan.selection.readonly ? " (read-only)" : "") + " \u2014 persisted per account and carried forward on --replace."));
|
|
77631
|
+
} else {
|
|
77632
|
+
console.log(source_default.gray(` Workspace tier: ${tier} \u2014 requesting Docs + Sheets` + (tier === "extended" || tier === "complete" ? " + Slides" : "") + " API scopes so the tier's tools can authenticate."));
|
|
77633
|
+
}
|
|
77393
77634
|
console.log(source_default.gray(` Changing the tier later requires re-running this command
|
|
77394
77635
|
` + " (`--replace`) \u2014 OAuth scopes are fixed at consent time."));
|
|
77395
77636
|
const oauthEnv = {
|
|
@@ -77419,12 +77660,76 @@ function registerAccountAdd(accountParent) {
|
|
|
77419
77660
|
});
|
|
77420
77661
|
console.log();
|
|
77421
77662
|
console.log(source_default.green(` \u2713 Registered Google account ${source_default.bold(normalizedAccount)} with auth-broker.`));
|
|
77663
|
+
if (selPlan.persist) {
|
|
77664
|
+
const configPath = getConfigPath(program3);
|
|
77665
|
+
const raw = readFileSync29(configPath, "utf-8");
|
|
77666
|
+
const patched = setGoogleAccountSelection(raw, normalizedAccount, {
|
|
77667
|
+
readonly: selPlan.persist.readonly ?? false,
|
|
77668
|
+
services: selPlan.persist.services ?? []
|
|
77669
|
+
});
|
|
77670
|
+
if (patched !== raw) {
|
|
77671
|
+
writeGoogleYaml(configPath, patched);
|
|
77672
|
+
console.log(source_default.gray(` Persisted selection to google_accounts.${normalizedAccount} ` + `(readonly: ${selPlan.persist.readonly ?? false}, services: ` + `${(selPlan.persist.services ?? []).join(",")}). Agents pick ` + `it up on their next restart.`));
|
|
77673
|
+
}
|
|
77674
|
+
}
|
|
77422
77675
|
console.log();
|
|
77423
77676
|
console.log(` Next: enable on one or more agents:`);
|
|
77424
77677
|
console.log(source_default.cyan(` switchroom auth google enable ${normalizedAccount} <agent> [...]`));
|
|
77425
77678
|
console.log();
|
|
77426
77679
|
}));
|
|
77427
77680
|
}
|
|
77681
|
+
var CAPABILITY_LABELS = {
|
|
77682
|
+
write: {
|
|
77683
|
+
flag: "--write",
|
|
77684
|
+
scope: "drive.file",
|
|
77685
|
+
summary: "Drive write (create + edit app-created files)"
|
|
77686
|
+
},
|
|
77687
|
+
calendar: {
|
|
77688
|
+
flag: "--calendar",
|
|
77689
|
+
scope: "calendar.readonly",
|
|
77690
|
+
summary: "Calendar read-only (list calendars, read events)"
|
|
77691
|
+
}
|
|
77692
|
+
};
|
|
77693
|
+
function buildScopeReconsentRequiredError(account, added) {
|
|
77694
|
+
const flags = added.map((k) => CAPABILITY_LABELS[k].flag);
|
|
77695
|
+
const scopes = added.map((k) => CAPABILITY_LABELS[k].scope).join(", ");
|
|
77696
|
+
return [
|
|
77697
|
+
`'${account}' is already registered with the auth-broker, and its stored ` + `token does not carry ${scopes}.`,
|
|
77698
|
+
"",
|
|
77699
|
+
"OAuth scopes are fixed at consent time \u2014 a stored token cannot be",
|
|
77700
|
+
"widened in place. Re-consent to mint a new token that includes it:",
|
|
77701
|
+
"",
|
|
77702
|
+
` switchroom auth google account add ${account} --replace ${flags.join(" ")}`,
|
|
77703
|
+
"",
|
|
77704
|
+
"Scopes the account already holds are carried forward automatically,",
|
|
77705
|
+
"so re-consenting will not drop existing capabilities."
|
|
77706
|
+
].join(`
|
|
77707
|
+
`);
|
|
77708
|
+
}
|
|
77709
|
+
function buildSelectionReconsentRequiredError(account, requested) {
|
|
77710
|
+
const flags = [
|
|
77711
|
+
requested.readonly ? "--readonly" : "",
|
|
77712
|
+
requested.services ? `--services ${requested.services}` : ""
|
|
77713
|
+
].filter((s) => s.length > 0).join(" ");
|
|
77714
|
+
return [
|
|
77715
|
+
`'${account}' is already registered with the auth-broker, and changing ` + `its readonly/services selection changes the OAuth scope set.`,
|
|
77716
|
+
"",
|
|
77717
|
+
"OAuth scopes are fixed at consent time \u2014 a stored token cannot be",
|
|
77718
|
+
"reshaped in place. Re-consent to mint a token with the new selection:",
|
|
77719
|
+
"",
|
|
77720
|
+
` switchroom auth google account add ${account} --replace ${flags}`,
|
|
77721
|
+
"",
|
|
77722
|
+
"The persisted selection (and any capabilities the flags don't",
|
|
77723
|
+
"explicitly change) is carried forward, never silently widened."
|
|
77724
|
+
].join(`
|
|
77725
|
+
`);
|
|
77726
|
+
}
|
|
77727
|
+
function buildCarryForwardNotices(carried) {
|
|
77728
|
+
return carried.map((k) => {
|
|
77729
|
+
const { flag, scope, summary } = CAPABILITY_LABELS[k];
|
|
77730
|
+
return ` Carrying forward ${summary} \u2014 the existing token holds ${scope}, ` + `so it is re-requested even though ${flag} was not passed ` + `(re-consent would otherwise silently revoke it).`;
|
|
77731
|
+
});
|
|
77732
|
+
}
|
|
77428
77733
|
function oauthClientSetupGuidance(reason) {
|
|
77429
77734
|
return [
|
|
77430
77735
|
reason,
|
|
@@ -95993,6 +96298,8 @@ init_agent_owned_tree();
|
|
|
95993
96298
|
init_component_versions();
|
|
95994
96299
|
var VERIFY_COMPONENTS_STEP = "verify-components";
|
|
95995
96300
|
var ENSURE_BANKS_STEP = "ensure-banks";
|
|
96301
|
+
var ENSURE_BANKS_ATTEMPTS = 3;
|
|
96302
|
+
var ENSURE_BANKS_BACKOFF_MS = [2000, 5000];
|
|
95996
96303
|
function normalizeVersion(v) {
|
|
95997
96304
|
return v.trim().replace(/^v/, "");
|
|
95998
96305
|
}
|
|
@@ -96314,25 +96621,47 @@ async function executeRollout(steps, target, deps, execOpts = {}) {
|
|
|
96314
96621
|
break;
|
|
96315
96622
|
}
|
|
96316
96623
|
deps.log(`ROLL_STEP ensure-banks \u2014 creating any missing agent bank (idempotent) for ${rolled.length} agent(s)`);
|
|
96624
|
+
const sleep4 = deps.sleepMs ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
96625
|
+
const bankUnverified = [];
|
|
96626
|
+
let lastUnreachableReason = "";
|
|
96627
|
+
let concludedUnreachable = false;
|
|
96317
96628
|
for (const a of rolled) {
|
|
96318
|
-
let res
|
|
96319
|
-
|
|
96320
|
-
|
|
96321
|
-
}
|
|
96322
|
-
|
|
96629
|
+
let res = {
|
|
96630
|
+
ok: false,
|
|
96631
|
+
reason: "not probed"
|
|
96632
|
+
};
|
|
96633
|
+
const maxAttempts = concludedUnreachable ? 1 : ENSURE_BANKS_ATTEMPTS;
|
|
96634
|
+
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
96635
|
+
try {
|
|
96636
|
+
res = await deps.ensureBank(a);
|
|
96637
|
+
} catch (e) {
|
|
96638
|
+
res = { ok: false, reason: e.message };
|
|
96639
|
+
}
|
|
96640
|
+
if (res.ok || !/unreachable|timeout/i.test(res.reason))
|
|
96641
|
+
break;
|
|
96642
|
+
if (attempt < maxAttempts) {
|
|
96643
|
+
const backoff = ENSURE_BANKS_BACKOFF_MS[attempt - 1] ?? ENSURE_BANKS_BACKOFF_MS[ENSURE_BANKS_BACKOFF_MS.length - 1] ?? 0;
|
|
96644
|
+
deps.log(` ${a}: bank check ${res.reason.toLowerCase()} \u2014 retrying in ${backoff}ms ` + `(attempt ${attempt}/${maxAttempts})`);
|
|
96645
|
+
await sleep4(backoff);
|
|
96646
|
+
}
|
|
96323
96647
|
}
|
|
96324
96648
|
if (res.ok) {
|
|
96325
96649
|
deps.log(` ${a}: bank present`);
|
|
96326
96650
|
continue;
|
|
96327
96651
|
}
|
|
96328
96652
|
if (/unreachable|timeout/i.test(res.reason)) {
|
|
96329
|
-
deps.log(` ${a}: bank check ${res.reason.toLowerCase()} (
|
|
96330
|
-
|
|
96653
|
+
deps.log(` ${a}: bank check ${res.reason.toLowerCase()} after ` + `${maxAttempts} attempt(s) (transient \u2014 not treated as a missing bank)`);
|
|
96654
|
+
bankUnverified.push(a);
|
|
96655
|
+
lastUnreachableReason = res.reason;
|
|
96656
|
+
concludedUnreachable = true;
|
|
96331
96657
|
continue;
|
|
96332
96658
|
}
|
|
96333
96659
|
deps.log(` \u2717 ${a}: bank could NOT be created \u2014 ${res.reason}`);
|
|
96334
96660
|
bankEnsureFailed.push(a);
|
|
96335
96661
|
}
|
|
96662
|
+
if (bankUnverified.length > 0) {
|
|
96663
|
+
deps.log(`ensure-banks: Hindsight was busy/unreachable (${lastUnreachableReason}) while ` + `verifying ${bankUnverified.length} bank(s) (${bankUnverified.join(", ")}) \u2014 ` + `existing banks are unaffected and nothing needs doing; a brand-new agent's ` + `bank is created automatically on its next restart-reconcile.`);
|
|
96664
|
+
}
|
|
96336
96665
|
break;
|
|
96337
96666
|
}
|
|
96338
96667
|
case "verify-components": {
|
|
@@ -100174,15 +100503,83 @@ function findMissingWorkspaceScopes(seedScope, tier) {
|
|
|
100174
100503
|
return requiredWorkspaceScopesForTier(tier).filter((s) => !have.has(s));
|
|
100175
100504
|
}
|
|
100176
100505
|
var DRIVE_FILE_SCOPE = "https://www.googleapis.com/auth/drive.file";
|
|
100177
|
-
|
|
100506
|
+
var CALENDAR_READONLY_SCOPE = "https://www.googleapis.com/auth/calendar.readonly";
|
|
100507
|
+
var LAUNCHER_GOOGLE_SERVICES = [
|
|
100508
|
+
"drive",
|
|
100509
|
+
"docs",
|
|
100510
|
+
"sheets",
|
|
100511
|
+
"slides",
|
|
100512
|
+
"cal"
|
|
100513
|
+
];
|
|
100514
|
+
var UPSTREAM_SERVICE_NAMES = {
|
|
100515
|
+
drive: "drive",
|
|
100516
|
+
docs: "docs",
|
|
100517
|
+
sheets: "sheets",
|
|
100518
|
+
slides: "slides",
|
|
100519
|
+
cal: "calendar"
|
|
100520
|
+
};
|
|
100521
|
+
function parseLauncherServices(raw) {
|
|
100522
|
+
const tokens = raw.split(",").map((s) => s.trim().toLowerCase()).filter((s) => s.length > 0).map((s) => s === "calendar" ? "cal" : s);
|
|
100523
|
+
for (const t of tokens) {
|
|
100524
|
+
if (!LAUNCHER_GOOGLE_SERVICES.includes(t)) {
|
|
100525
|
+
throw new Error(`drive-mcp-launcher: unknown service '${t}' in --services ` + `(valid: ${LAUNCHER_GOOGLE_SERVICES.join(", ")})`);
|
|
100526
|
+
}
|
|
100527
|
+
}
|
|
100528
|
+
const set = new Set(tokens);
|
|
100529
|
+
return LAUNCHER_GOOGLE_SERVICES.filter((s) => set.has(s));
|
|
100530
|
+
}
|
|
100531
|
+
function requiredScopesForSelection(selection) {
|
|
100532
|
+
const ro = selection.readonly;
|
|
100533
|
+
const out = [];
|
|
100534
|
+
for (const s of selection.services) {
|
|
100535
|
+
switch (s) {
|
|
100536
|
+
case "docs":
|
|
100537
|
+
out.push(ro ? "https://www.googleapis.com/auth/documents.readonly" : "https://www.googleapis.com/auth/documents");
|
|
100538
|
+
break;
|
|
100539
|
+
case "sheets":
|
|
100540
|
+
out.push(ro ? "https://www.googleapis.com/auth/spreadsheets.readonly" : "https://www.googleapis.com/auth/spreadsheets");
|
|
100541
|
+
break;
|
|
100542
|
+
case "slides":
|
|
100543
|
+
out.push(ro ? "https://www.googleapis.com/auth/presentations.readonly" : "https://www.googleapis.com/auth/presentations");
|
|
100544
|
+
break;
|
|
100545
|
+
case "cal":
|
|
100546
|
+
out.push(CALENDAR_READONLY_SCOPE);
|
|
100547
|
+
break;
|
|
100548
|
+
case "drive":
|
|
100549
|
+
break;
|
|
100550
|
+
}
|
|
100551
|
+
}
|
|
100552
|
+
return out;
|
|
100553
|
+
}
|
|
100554
|
+
function findMissingScopesForSelection(seedScope, tier, selection) {
|
|
100555
|
+
if (!selection)
|
|
100556
|
+
return findMissingWorkspaceScopes(seedScope, tier);
|
|
100557
|
+
const have = new Set(seedScope.split(/\s+/).map((s) => s.trim()).filter((s) => s.length > 0));
|
|
100558
|
+
return requiredScopesForSelection(selection).filter((s) => !have.has(s));
|
|
100559
|
+
}
|
|
100560
|
+
function seedOptInCapabilities(seedScope) {
|
|
100561
|
+
const have = new Set(seedScope.split(/\s+/).map((s) => s.trim()).filter((s) => s.length > 0));
|
|
100562
|
+
return {
|
|
100563
|
+
write: have.has(DRIVE_FILE_SCOPE),
|
|
100564
|
+
calendar: have.has(CALENDAR_READONLY_SCOPE)
|
|
100565
|
+
};
|
|
100566
|
+
}
|
|
100567
|
+
function buildMissingScopeWarning(missing, tier, accountEmail, granted, selection) {
|
|
100178
100568
|
const short = missing.map((s) => s.replace(/^https:\/\/www\.googleapis\.com\/auth\//, "")).join(", ");
|
|
100179
|
-
|
|
100180
|
-
|
|
100181
|
-
|
|
100182
|
-
|
|
100569
|
+
const suffix = selection ? (selection.readonly ? " --readonly" : granted.write ? " --write" : "") + ` --services ${selection.services.join(",")}` : (granted.write ? " --write" : "") + (granted.calendar ? " --calendar" : "");
|
|
100570
|
+
const preserved = [
|
|
100571
|
+
selection ? `--services ${selection.services.join(",")}` + `${selection.readonly ? " --readonly" : ""} preserves the persisted per-account selection` : "",
|
|
100572
|
+
!selection && granted.write ? "--write preserves the existing Drive write capability" : "",
|
|
100573
|
+
!selection && granted.calendar ? "--calendar preserves the existing Calendar read capability" : ""
|
|
100574
|
+
].filter((s) => s.length > 0);
|
|
100575
|
+
const scopeSource = selection ? `for its persisted selection (services: ${selection.services.join(",")}` + `${selection.readonly ? ", read-only" : ""})` : `for tier '${tier ?? "core"}'`;
|
|
100576
|
+
return `drive-mcp-launcher: WARNING \u2014 the Google account '${accountEmail}' was ` + `consented WITHOUT the scope(s) needed ${scopeSource}: ` + `${short}.
|
|
100577
|
+
` + ` The matching MCP tools will FAIL ` + `to authenticate. OAuth scopes are fixed at consent time \u2014 re-run on the ` + `host to re-mint the token with the correct scopes:
|
|
100578
|
+
` + ` switchroom auth google account add ${accountEmail} --replace` + `${suffix}
|
|
100579
|
+
` + ` (${selection ? "" : "scopes are derived from `google_workspace.tier` \u2014 set the tier before re-running"}${preserved.length > 0 ? (selection ? "" : "; ") + preserved.join("; ") : ""}). Drive read/file tools are unaffected.
|
|
100183
100580
|
`;
|
|
100184
100581
|
}
|
|
100185
|
-
function buildUvxArgs(tier) {
|
|
100582
|
+
function buildUvxArgs(tier, selection) {
|
|
100186
100583
|
const args = [
|
|
100187
100584
|
"--from",
|
|
100188
100585
|
`git+https://github.com/taylorwilsdon/google_workspace_mcp.git@${GOOGLE_WORKSPACE_MCP_PINNED_SHA}`,
|
|
@@ -100196,6 +100593,12 @@ function buildUvxArgs(tier) {
|
|
|
100196
100593
|
if (tier && tier.length > 0) {
|
|
100197
100594
|
args.push("--tool-tier", tier);
|
|
100198
100595
|
}
|
|
100596
|
+
if (selection && selection.services.length > 0) {
|
|
100597
|
+
args.push("--tools", ...selection.services.map((s) => UPSTREAM_SERVICE_NAMES[s]));
|
|
100598
|
+
}
|
|
100599
|
+
if (selection?.readonly) {
|
|
100600
|
+
args.push("--read-only");
|
|
100601
|
+
}
|
|
100199
100602
|
return args;
|
|
100200
100603
|
}
|
|
100201
100604
|
function buildChildEnv(baseEnv, credentialsDir, accountEmail) {
|
|
@@ -100368,6 +100771,15 @@ async function resolveConfigSecret2(raw, label) {
|
|
|
100368
100771
|
}
|
|
100369
100772
|
throw new Error(`${label} vault key '${key}' unreachable via the vault-broker (${result.msg}). Refusing to spawn the Google Workspace MCP.`);
|
|
100370
100773
|
}
|
|
100774
|
+
function resolveLauncherSelection(record2, tier) {
|
|
100775
|
+
if (!record2)
|
|
100776
|
+
return;
|
|
100777
|
+
if (record2.readonly === undefined && record2.services === undefined) {
|
|
100778
|
+
return;
|
|
100779
|
+
}
|
|
100780
|
+
const services = record2.services && record2.services.length > 0 ? parseLauncherServices(record2.services.join(",")) : tier === "extended" || tier === "complete" ? ["drive", "docs", "sheets", "slides"] : ["drive", "docs", "sheets"];
|
|
100781
|
+
return { readonly: record2.readonly ?? false, services };
|
|
100782
|
+
}
|
|
100371
100783
|
async function loadConfigSecrets() {
|
|
100372
100784
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_loader(), exports_loader));
|
|
100373
100785
|
const config = loadConfig2();
|
|
@@ -100377,7 +100789,16 @@ async function loadConfigSecrets() {
|
|
|
100377
100789
|
}
|
|
100378
100790
|
const clientId = await resolveConfigSecret2(gw.google_client_id, "google_client_id");
|
|
100379
100791
|
const clientSecret = await resolveConfigSecret2(gw.google_client_secret, "google_client_secret");
|
|
100380
|
-
|
|
100792
|
+
const googleAccounts = {};
|
|
100793
|
+
for (const [email, record2] of Object.entries(config.google_accounts ?? {})) {
|
|
100794
|
+
if (!record2)
|
|
100795
|
+
continue;
|
|
100796
|
+
googleAccounts[email.toLowerCase()] = {
|
|
100797
|
+
readonly: record2.readonly,
|
|
100798
|
+
services: record2.services
|
|
100799
|
+
};
|
|
100800
|
+
}
|
|
100801
|
+
return { clientId, clientSecret, tier: gw.tier, googleAccounts };
|
|
100381
100802
|
}
|
|
100382
100803
|
async function runDriveMcpLauncher(opts) {
|
|
100383
100804
|
let brokerCreds;
|
|
@@ -100425,12 +100846,27 @@ async function runDriveMcpLauncher(opts) {
|
|
|
100425
100846
|
process.exit(1);
|
|
100426
100847
|
}
|
|
100427
100848
|
const tier = opts.tier ?? configSecrets.tier;
|
|
100428
|
-
|
|
100849
|
+
let selection;
|
|
100850
|
+
try {
|
|
100851
|
+
const configSelection = resolveLauncherSelection(configSecrets.googleAccounts[brokerCreds.accountEmail.toLowerCase()], tier);
|
|
100852
|
+
if (opts.services !== undefined || opts.readOnly) {
|
|
100853
|
+
selection = {
|
|
100854
|
+
readonly: opts.readOnly ?? configSelection?.readonly ?? false,
|
|
100855
|
+
services: opts.services !== undefined ? parseLauncherServices(opts.services) : configSelection?.services ?? resolveLauncherSelection({ readonly: false }, tier).services
|
|
100856
|
+
};
|
|
100857
|
+
} else {
|
|
100858
|
+
selection = configSelection;
|
|
100859
|
+
}
|
|
100860
|
+
} catch (err) {
|
|
100861
|
+
process.stderr.write(`drive-mcp-launcher: ${err instanceof Error ? err.message : String(err)}
|
|
100862
|
+
`);
|
|
100863
|
+
process.exit(1);
|
|
100864
|
+
}
|
|
100865
|
+
const missingScopes = findMissingScopesForSelection(brokerCreds.scope, tier, selection);
|
|
100429
100866
|
if (missingScopes.length > 0) {
|
|
100430
|
-
|
|
100431
|
-
process.stderr.write(buildMissingScopeWarning(missingScopes, tier, brokerCreds.accountEmail, hasWriteScope));
|
|
100867
|
+
process.stderr.write(buildMissingScopeWarning(missingScopes, tier, brokerCreds.accountEmail, seedOptInCapabilities(brokerCreds.scope), selection));
|
|
100432
100868
|
}
|
|
100433
|
-
const args = buildUvxArgs(tier);
|
|
100869
|
+
const args = buildUvxArgs(tier, selection);
|
|
100434
100870
|
const env2 = buildChildEnv(process.env, credentialsDir, brokerCreds.accountEmail);
|
|
100435
100871
|
const { spawn: spawn6 } = await import("node:child_process");
|
|
100436
100872
|
const os6 = await import("node:os");
|
|
@@ -100462,8 +100898,12 @@ async function runDriveMcpLauncher(opts) {
|
|
|
100462
100898
|
return new Promise(() => {});
|
|
100463
100899
|
}
|
|
100464
100900
|
function registerDriveMcpLauncherCommand(program3) {
|
|
100465
|
-
program3.command("drive-mcp-launcher", { hidden: true }).description("Internal: launch the Google Workspace MCP browserless, fed by the auth-broker. Spawned as the `gdrive` MCP command inside agents.").option("--tier <tier>", "Upstream --tool-tier (core|extended|complete). Overrides config.").action(async (opts) => {
|
|
100466
|
-
await runDriveMcpLauncher({
|
|
100901
|
+
program3.command("drive-mcp-launcher", { hidden: true }).description("Internal: launch the Google Workspace MCP browserless, fed by the auth-broker. Spawned as the `gdrive` MCP command inside agents.").option("--tier <tier>", "Upstream --tool-tier (core|extended|complete). Overrides config.").option("--services <list>", "Comma-separated per-account service selection (cal,drive,docs,sheets,slides) \u2192 upstream --tools. Overrides the persisted google_accounts.<email>.services record.").option("--read-only", "Run upstream in --read-only mode (readonly scopes, write tools not registered). Overrides the persisted google_accounts.<email>.readonly record.").action(async (opts) => {
|
|
100902
|
+
await runDriveMcpLauncher({
|
|
100903
|
+
tier: opts.tier,
|
|
100904
|
+
services: opts.services,
|
|
100905
|
+
readOnly: opts.readOnly
|
|
100906
|
+
});
|
|
100467
100907
|
});
|
|
100468
100908
|
}
|
|
100469
100909
|
|