switchroom 0.19.46 → 0.19.48
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 +16 -3
- package/dist/auth-broker/index.js +22 -5
- package/dist/cli/notion-write-pretool.mjs +16 -3
- package/dist/cli/switchroom.js +428 -53
- package/dist/host-control/main.js +23 -6
- package/dist/vault/approvals/kernel-server.js +22 -5
- package/dist/vault/broker/server.js +22 -5
- package/package.json +1 -1
- package/telegram-plugin/answer-ready-flush.ts +118 -6
- package/telegram-plugin/dist/gateway/gateway.js +118 -13
- package/telegram-plugin/gateway/gateway.ts +19 -18
- 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/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.48", COMMIT_SHA = "22eb634b";
|
|
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();
|
|
@@ -13721,6 +13721,8 @@ var init_schema = __esm(() => {
|
|
|
13721
13721
|
profile: exports_external.string().optional().describe("Memory profile bank this agent's curated memory defaults key off \u2014 " + "the built-in disposition + observations_mission in PROFILE_MEMORY_DEFAULTS. " + "Decouples the memory profile from `extends` (the filesystem persona " + "profile), so an agent on `extends: default` can opt into the `coding` " + "memory bundle via `memory.profile: coding` without inheriting the coding " + "persona. Resolution: memory.profile \u2192 extends \u2192 DEFAULT_PROFILE (see " + "resolveMemoryProfile). Unset \u21d2 byte-identical to keying off `extends`."),
|
|
13722
13722
|
bank_mission: exports_external.string().optional().describe("Bank-level mission statement used during recall to contextualize " + "results. NOTE: this is an alias for the Hindsight engine's " + "`reflect_mission` field (verified live: switchroom's bank_mission " + "lands in `config.reflect_mission`). Prefer `reflect_mission` going " + "forward; `bank_mission` is retained for back-compat. If both are " + "set, `reflect_mission` wins. Cascade: override."),
|
|
13723
13723
|
reflect_mission: exports_external.string().optional().describe("Mission/context steering Hindsight Reflect operations (the bank's " + "'who am I / what matters' framing applied during recall). The " + "engine-accurate name for what `bank_mission` sets. Cascade: override."),
|
|
13724
|
+
reflect_budget: exports_external.enum(["low", "mid", "high"]).optional().describe("Thinking/retrieval budget injected into reflect MCP calls when the " + "caller omits budget. Unset \u21d2 shim default (mid). Explicit per-call " + "budget always wins. Higher = better recall on fuzzy queries, more " + "backend latency/compute. stdio-shim transport only (ignored under " + "memory.config.mcp_transport: http). Cascade: override (per-agent " + "wins over default)."),
|
|
13725
|
+
reflect_max_tokens: exports_external.number().int().positive().max(8192).optional().describe("Token cap injected into reflect MCP calls when caller omits " + "max_tokens. Unset \u21d2 shim default 1024. Values much above ~2048 risk " + "exceeding Claude Code's MCP output cap, silently dropping the " + "payload \u2014 raise deliberately. Explicit per-call values always win. " + "stdio-shim transport only. Cascade: override."),
|
|
13724
13726
|
retain_mission: exports_external.string().optional().describe("Instructions for the fact extraction LLM during retain. Cascade: override."),
|
|
13725
13727
|
mental_models: exports_external.array(exports_external.object({
|
|
13726
13728
|
name: exports_external.string().min(1).describe("Stable model name (identity key for idempotent ensure). Two " + "declarations with the same name in one agent are rejected."),
|
|
@@ -13982,11 +13984,18 @@ var init_schema = __esm(() => {
|
|
|
13982
13984
|
"extended",
|
|
13983
13985
|
"complete"
|
|
13984
13986
|
]);
|
|
13987
|
+
GoogleServiceTokenSchema = exports_external.enum([
|
|
13988
|
+
"cal",
|
|
13989
|
+
"drive",
|
|
13990
|
+
"docs",
|
|
13991
|
+
"sheets",
|
|
13992
|
+
"slides"
|
|
13993
|
+
]);
|
|
13985
13994
|
GoogleWorkspaceConfigSchema = exports_external.object({
|
|
13986
13995
|
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
13996
|
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
13997
|
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).")
|
|
13998
|
+
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
13999
|
}).optional();
|
|
13991
14000
|
LiteLLMConfigSchema = exports_external.object({
|
|
13992
14001
|
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 +14028,7 @@ var init_schema = __esm(() => {
|
|
|
14019
14028
|
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
14029
|
consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent \u2192 global.")
|
|
14021
14030
|
}).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).")
|
|
14031
|
+
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
14032
|
});
|
|
14024
14033
|
MicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
14025
14034
|
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)."),
|
|
@@ -14205,6 +14214,8 @@ var init_schema = __esm(() => {
|
|
|
14205
14214
|
}).optional(),
|
|
14206
14215
|
bank_mission: exports_external.string().optional(),
|
|
14207
14216
|
reflect_mission: exports_external.string().optional(),
|
|
14217
|
+
reflect_budget: exports_external.enum(["low", "mid", "high"]).optional(),
|
|
14218
|
+
reflect_max_tokens: exports_external.number().int().positive().max(8192).optional(),
|
|
14208
14219
|
retain_mission: exports_external.string().optional(),
|
|
14209
14220
|
observations_mission: exports_external.string().optional(),
|
|
14210
14221
|
disposition: exports_external.object({
|
|
@@ -14493,7 +14504,9 @@ var init_schema = __esm(() => {
|
|
|
14493
14504
|
}).transform((v) => v.trim().toLowerCase()), exports_external.object({
|
|
14494
14505
|
enabled_for: exports_external.array(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
14495
14506
|
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.")
|
|
14507
|
+
})).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."),
|
|
14508
|
+
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)."),
|
|
14509
|
+
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
14510
|
})).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
14511
|
microsoft_accounts: exports_external.record(exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
14499
14512
|
message: "Account key must be a Microsoft account email like 'alice@outlook.com' or 'alice@contoso.com' (colons not allowed)"
|
|
@@ -16075,7 +16088,8 @@ var init_hindsight_perf_defaults = __esm(() => {
|
|
|
16075
16088
|
"HINDSIGHT_MCP_RECALL_BUDGET_MODE",
|
|
16076
16089
|
"HINDSIGHT_API_LLM_TEMPERATURE_REFLECT",
|
|
16077
16090
|
"HINDSIGHT_API_RETAIN_WALL_TIMEOUT",
|
|
16078
|
-
"HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT"
|
|
16091
|
+
"HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT",
|
|
16092
|
+
"HINDSIGHT_MM_REFRESH_MIN_INTERVAL_S"
|
|
16079
16093
|
]);
|
|
16080
16094
|
HINDSIGHT_WORKER_SLOT_TYPES = [
|
|
16081
16095
|
"consolidation",
|
|
@@ -19120,9 +19134,12 @@ var init_hindsight = __esm(() => {
|
|
|
19120
19134
|
});
|
|
19121
19135
|
|
|
19122
19136
|
// src/memory/hindsight.ts
|
|
19123
|
-
function generateHindsightMcpConfig(collection, memoryConfig) {
|
|
19137
|
+
function generateHindsightMcpConfig(collection, memoryConfig, opts = {}) {
|
|
19124
19138
|
const url = memoryConfig.config?.url ?? HINDSIGHT_DEFAULT_MCP_URL;
|
|
19125
19139
|
if (memoryConfig.config?.mcp_transport === "http") {
|
|
19140
|
+
if (opts.reflectBudget !== undefined || opts.reflectMaxTokens !== undefined) {
|
|
19141
|
+
console.error("memory.reflect_budget/reflect_max_tokens have no effect under " + "mcp_transport: http \u2014 bypasses the shim");
|
|
19142
|
+
}
|
|
19126
19143
|
return {
|
|
19127
19144
|
type: "http",
|
|
19128
19145
|
url,
|
|
@@ -19139,7 +19156,9 @@ function generateHindsightMcpConfig(collection, memoryConfig) {
|
|
|
19139
19156
|
HINDSIGHT_MCP_URL: url,
|
|
19140
19157
|
HINDSIGHT_BANK_ID: collection,
|
|
19141
19158
|
HINDSIGHT_SHIM_CACHE_DIR: `${HINDSIGHT_SHIM_AGENT_HOME}/.hindsight-shim`,
|
|
19142
|
-
HOME: HINDSIGHT_SHIM_AGENT_HOME
|
|
19159
|
+
HOME: HINDSIGHT_SHIM_AGENT_HOME,
|
|
19160
|
+
...opts.reflectBudget ? { HINDSIGHT_SHIM_REFLECT_BUDGET: opts.reflectBudget } : {},
|
|
19161
|
+
...opts.reflectMaxTokens ? { HINDSIGHT_SHIM_REFLECT_MAX_TOKENS: String(opts.reflectMaxTokens) } : {}
|
|
19143
19162
|
}
|
|
19144
19163
|
};
|
|
19145
19164
|
}
|
|
@@ -27349,7 +27368,11 @@ function getHindsightSettingsEntry(agentName, config) {
|
|
|
27349
27368
|
return null;
|
|
27350
27369
|
}
|
|
27351
27370
|
const collection = getCollectionForAgent(agentName, config);
|
|
27352
|
-
const
|
|
27371
|
+
const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[agentName] ?? {});
|
|
27372
|
+
const mcpConfig = generateHindsightMcpConfig(collection, memoryConfig, {
|
|
27373
|
+
reflectBudget: resolved.memory?.reflect_budget,
|
|
27374
|
+
reflectMaxTokens: resolved.memory?.reflect_max_tokens
|
|
27375
|
+
});
|
|
27353
27376
|
return { key: "hindsight", value: { ...mcpConfig, alwaysLoad: false } };
|
|
27354
27377
|
}
|
|
27355
27378
|
function getPlaywrightMcpSettingsEntry() {
|
|
@@ -27363,11 +27386,18 @@ function getPlaywrightMcpSettingsEntry() {
|
|
|
27363
27386
|
}
|
|
27364
27387
|
function getGdriveMcpSettingsEntry(switchroomCliPath, options = {}) {
|
|
27365
27388
|
const tierArgs = options.tier ? ["--tier", options.tier] : [];
|
|
27389
|
+
const servicesArgs = options.services && options.services.length > 0 ? ["--services", options.services.join(",")] : [];
|
|
27390
|
+
const readOnlyArgs = options.readOnly ? ["--read-only"] : [];
|
|
27366
27391
|
return {
|
|
27367
27392
|
key: "gdrive",
|
|
27368
27393
|
value: {
|
|
27369
27394
|
command: switchroomCliPath,
|
|
27370
|
-
args: [
|
|
27395
|
+
args: [
|
|
27396
|
+
"drive-mcp-launcher",
|
|
27397
|
+
...tierArgs,
|
|
27398
|
+
...servicesArgs,
|
|
27399
|
+
...readOnlyArgs
|
|
27400
|
+
]
|
|
27371
27401
|
}
|
|
27372
27402
|
};
|
|
27373
27403
|
}
|
|
@@ -27433,6 +27463,7 @@ function getBuiltinDefaultSkillEntries() {
|
|
|
27433
27463
|
}
|
|
27434
27464
|
var GOOGLE_WORKSPACE_MCP_PINNED_SHA = "9d69115b63e6bc2ef0d4b5d7a3b962396382b44c", MICROSOFT_WORKSPACE_MCP_PINNED_VERSION = "0.113.0", MICROSOFT_WORKSPACE_MCP_PACKAGE = "@softeria/ms-365-mcp-server", NOTION_MCP_PINNED_VERSION = "1.8.1", NOTION_MCP_PACKAGE = "@notionhq/notion-mcp-server", NOTION_TOKEN_ENV = "NOTION_TOKEN";
|
|
27435
27465
|
var init_scaffold_integration = __esm(() => {
|
|
27466
|
+
init_merge();
|
|
27436
27467
|
init_hindsight2();
|
|
27437
27468
|
});
|
|
27438
27469
|
|
|
@@ -29941,7 +29972,12 @@ function resolveGdriveMcpEntry(agentName, agentConfig, switchroomConfig) {
|
|
|
29941
29972
|
if (!shouldEmitGdriveMcp(agentName, account, googleAccounts))
|
|
29942
29973
|
return null;
|
|
29943
29974
|
const tier = agentConfig.google_workspace?.tier ?? switchroomConfig?.google_workspace?.tier;
|
|
29944
|
-
const
|
|
29975
|
+
const accountRecord = account ? googleAccounts?.[account.trim().toLowerCase()] : undefined;
|
|
29976
|
+
const entry = getGdriveMcpSettingsEntry(DOCKER_SWITCHROOM_CLI_PATH, {
|
|
29977
|
+
...tier ? { tier } : {},
|
|
29978
|
+
...accountRecord?.services && accountRecord.services.length > 0 ? { services: accountRecord.services } : {},
|
|
29979
|
+
...accountRecord?.readonly ? { readOnly: true } : {}
|
|
29980
|
+
});
|
|
29945
29981
|
entry.value.env = {
|
|
29946
29982
|
SWITCHROOM_CONFIG: DOCKER_CONFIG_PATH,
|
|
29947
29983
|
SWITCHROOM_AGENT_NAME: agentName,
|
|
@@ -37436,36 +37472,93 @@ var init_wait = __esm(() => {
|
|
|
37436
37472
|
var exports_drive = {};
|
|
37437
37473
|
__export(exports_drive, {
|
|
37438
37474
|
workspaceScopesForTier: () => workspaceScopesForTier,
|
|
37475
|
+
tierDefaultServices: () => tierDefaultServices,
|
|
37439
37476
|
selectGoogleWorkspaceScopes: () => selectGoogleWorkspaceScopes,
|
|
37440
37477
|
selectDriveAccountScopes: () => selectDriveAccountScopes,
|
|
37478
|
+
scopesForSelection: () => scopesForSelection,
|
|
37441
37479
|
runDriveOAuthFlow: () => runDriveOAuthFlow,
|
|
37480
|
+
resolveScopeSelection: () => resolveScopeSelection,
|
|
37442
37481
|
resolveReconsentCapabilities: () => resolveReconsentCapabilities,
|
|
37443
37482
|
registerDriveCommand: () => registerDriveCommand,
|
|
37483
|
+
parseServicesOption: () => parseServicesOption,
|
|
37444
37484
|
capabilitiesFromScopeString: () => capabilitiesFromScopeString,
|
|
37445
37485
|
__test: () => __test,
|
|
37446
37486
|
GOOGLE_SLIDES_SCOPE: () => GOOGLE_SLIDES_SCOPE,
|
|
37487
|
+
GOOGLE_SLIDES_READONLY_SCOPE: () => GOOGLE_SLIDES_READONLY_SCOPE,
|
|
37447
37488
|
GOOGLE_SHEETS_SCOPE: () => GOOGLE_SHEETS_SCOPE,
|
|
37489
|
+
GOOGLE_SHEETS_READONLY_SCOPE: () => GOOGLE_SHEETS_READONLY_SCOPE,
|
|
37490
|
+
GOOGLE_SERVICES: () => GOOGLE_SERVICES,
|
|
37448
37491
|
GOOGLE_DOCS_SCOPE: () => GOOGLE_DOCS_SCOPE,
|
|
37492
|
+
GOOGLE_DOCS_READONLY_SCOPE: () => GOOGLE_DOCS_READONLY_SCOPE,
|
|
37449
37493
|
GOOGLE_CALENDAR_READONLY_SCOPE: () => GOOGLE_CALENDAR_READONLY_SCOPE,
|
|
37450
37494
|
DRIVE_WRITE_SCOPES: () => DRIVE_WRITE_SCOPES,
|
|
37451
37495
|
DRIVE_READONLY_SCOPES: () => DRIVE_READONLY_SCOPES
|
|
37452
37496
|
});
|
|
37453
37497
|
import { createInterface as createInterface2 } from "node:readline";
|
|
37454
|
-
function
|
|
37455
|
-
const
|
|
37498
|
+
function parseServicesOption(raw) {
|
|
37499
|
+
const tokens = raw.split(",").map((s) => s.trim().toLowerCase()).filter((s) => s.length > 0).map((s) => s === "calendar" ? "cal" : s);
|
|
37500
|
+
if (tokens.length === 0) {
|
|
37501
|
+
throw new Error(`--services requires at least one service (valid: ${GOOGLE_SERVICES.join(", ")})`);
|
|
37502
|
+
}
|
|
37503
|
+
for (const t of tokens) {
|
|
37504
|
+
if (!GOOGLE_SERVICES.includes(t)) {
|
|
37505
|
+
throw new Error(`--services: unknown service '${t}' (valid: ${GOOGLE_SERVICES.join(", ")}; ` + `'calendar' is accepted as an alias for 'cal')`);
|
|
37506
|
+
}
|
|
37507
|
+
}
|
|
37508
|
+
const set = new Set(tokens);
|
|
37509
|
+
return GOOGLE_SERVICES.filter((s) => set.has(s));
|
|
37510
|
+
}
|
|
37511
|
+
function tierDefaultServices(tier) {
|
|
37456
37512
|
if (tier === "extended" || tier === "complete") {
|
|
37457
|
-
return [
|
|
37513
|
+
return ["drive", "docs", "sheets", "slides"];
|
|
37458
37514
|
}
|
|
37459
|
-
return
|
|
37515
|
+
return ["drive", "docs", "sheets"];
|
|
37516
|
+
}
|
|
37517
|
+
function scopesForSelection(sel) {
|
|
37518
|
+
const selected = new Set(sel.services);
|
|
37519
|
+
const out = [];
|
|
37520
|
+
if (selected.has("drive")) {
|
|
37521
|
+
out.push(...DRIVE_READONLY_SCOPES);
|
|
37522
|
+
if (sel.driveWrite && !sel.readonly) {
|
|
37523
|
+
out.push("https://www.googleapis.com/auth/drive.file");
|
|
37524
|
+
}
|
|
37525
|
+
}
|
|
37526
|
+
if (selected.has("docs")) {
|
|
37527
|
+
out.push(sel.readonly ? GOOGLE_DOCS_READONLY_SCOPE : GOOGLE_DOCS_SCOPE);
|
|
37528
|
+
}
|
|
37529
|
+
if (selected.has("sheets")) {
|
|
37530
|
+
out.push(sel.readonly ? GOOGLE_SHEETS_READONLY_SCOPE : GOOGLE_SHEETS_SCOPE);
|
|
37531
|
+
}
|
|
37532
|
+
if (selected.has("slides")) {
|
|
37533
|
+
out.push(sel.readonly ? GOOGLE_SLIDES_READONLY_SCOPE : GOOGLE_SLIDES_SCOPE);
|
|
37534
|
+
}
|
|
37535
|
+
if (selected.has("cal")) {
|
|
37536
|
+
out.push(GOOGLE_CALENDAR_READONLY_SCOPE);
|
|
37537
|
+
}
|
|
37538
|
+
return [...new Set(out)];
|
|
37539
|
+
}
|
|
37540
|
+
function workspaceScopesForTier(tier) {
|
|
37541
|
+
return scopesForSelection({
|
|
37542
|
+
readonly: false,
|
|
37543
|
+
driveWrite: false,
|
|
37544
|
+
services: tierDefaultServices(tier).filter((s) => s !== "drive")
|
|
37545
|
+
});
|
|
37460
37546
|
}
|
|
37461
37547
|
function selectDriveAccountScopes(write) {
|
|
37462
37548
|
return write ? DRIVE_WRITE_SCOPES : DRIVE_READONLY_SCOPES;
|
|
37463
37549
|
}
|
|
37464
37550
|
function selectGoogleWorkspaceScopes(opts) {
|
|
37465
|
-
|
|
37466
|
-
|
|
37467
|
-
|
|
37468
|
-
|
|
37551
|
+
if (opts.readonly && opts.write) {
|
|
37552
|
+
throw new Error("--readonly and --write are mutually exclusive: a read-only " + "selection mints zero write scopes by definition.");
|
|
37553
|
+
}
|
|
37554
|
+
const services = opts.services ?? tierDefaultServices(opts.tier ?? "core");
|
|
37555
|
+
const withCal = opts.calendar && !services.includes("cal") ? [...services, "cal"] : services;
|
|
37556
|
+
const set = new Set(withCal);
|
|
37557
|
+
return scopesForSelection({
|
|
37558
|
+
readonly: opts.readonly ?? false,
|
|
37559
|
+
driveWrite: opts.write,
|
|
37560
|
+
services: GOOGLE_SERVICES.filter((s) => set.has(s))
|
|
37561
|
+
});
|
|
37469
37562
|
}
|
|
37470
37563
|
function capabilitiesFromScopeString(scope) {
|
|
37471
37564
|
const have = new Set(scope.split(/\s+/).map((s) => s.trim()).filter((s) => s.length > 0));
|
|
@@ -37488,6 +37581,62 @@ function resolveReconsentCapabilities(requested, existing) {
|
|
|
37488
37581
|
added: keys.filter((k) => requested[k] && !existing[k])
|
|
37489
37582
|
};
|
|
37490
37583
|
}
|
|
37584
|
+
function resolveScopeSelection(args) {
|
|
37585
|
+
const { flags, persisted, existing, tier } = args;
|
|
37586
|
+
if (flags.readonly && flags.write) {
|
|
37587
|
+
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.");
|
|
37588
|
+
}
|
|
37589
|
+
const dropped = [];
|
|
37590
|
+
const readonly = flags.readonly ? true : flags.write ? false : persisted?.readonly ?? false;
|
|
37591
|
+
if (flags.write && persisted?.readonly) {
|
|
37592
|
+
dropped.push("read-only mode (persisted `readonly: true`) \u2014 dropped because --write was passed");
|
|
37593
|
+
}
|
|
37594
|
+
let driveWrite = existing.write;
|
|
37595
|
+
if (readonly && existing.write) {
|
|
37596
|
+
driveWrite = false;
|
|
37597
|
+
dropped.push("Drive write (drive.file) \u2014 the existing token carries it, but --readonly was passed");
|
|
37598
|
+
}
|
|
37599
|
+
const explicitServices = !!(flags.services && flags.services.length > 0);
|
|
37600
|
+
let services;
|
|
37601
|
+
let persistServices;
|
|
37602
|
+
if (explicitServices) {
|
|
37603
|
+
services = [...flags.services];
|
|
37604
|
+
persistServices = true;
|
|
37605
|
+
if (flags.calendar && !services.includes("cal"))
|
|
37606
|
+
services.push("cal");
|
|
37607
|
+
} else if (persisted?.services && persisted.services.length > 0) {
|
|
37608
|
+
services = [...persisted.services];
|
|
37609
|
+
persistServices = true;
|
|
37610
|
+
if ((flags.calendar || existing.calendar) && !services.includes("cal")) {
|
|
37611
|
+
services.push("cal");
|
|
37612
|
+
}
|
|
37613
|
+
} else {
|
|
37614
|
+
services = [...tierDefaultServices(tier)];
|
|
37615
|
+
persistServices = false;
|
|
37616
|
+
if ((flags.calendar || existing.calendar) && !services.includes("cal")) {
|
|
37617
|
+
services.push("cal");
|
|
37618
|
+
}
|
|
37619
|
+
}
|
|
37620
|
+
const set = new Set(services);
|
|
37621
|
+
const canonical = GOOGLE_SERVICES.filter((s) => set.has(s));
|
|
37622
|
+
if (explicitServices) {
|
|
37623
|
+
const prior = persisted?.services ?? (existing.calendar ? [...tierDefaultServices(tier), "cal"] : tierDefaultServices(tier));
|
|
37624
|
+
for (const s of prior) {
|
|
37625
|
+
if (!set.has(s)) {
|
|
37626
|
+
dropped.push(`service '${s}' \u2014 previously granted, not in the --services list passed`);
|
|
37627
|
+
}
|
|
37628
|
+
}
|
|
37629
|
+
}
|
|
37630
|
+
const persist = flags.readonly || persistServices || persisted !== undefined ? {
|
|
37631
|
+
readonly,
|
|
37632
|
+
services: canonical
|
|
37633
|
+
} : null;
|
|
37634
|
+
return {
|
|
37635
|
+
selection: { readonly, services: canonical, driveWrite },
|
|
37636
|
+
persist,
|
|
37637
|
+
dropped
|
|
37638
|
+
};
|
|
37639
|
+
}
|
|
37491
37640
|
function getVaultPath(configPath) {
|
|
37492
37641
|
try {
|
|
37493
37642
|
const config = loadConfig(configPath);
|
|
@@ -37900,7 +38049,7 @@ function registerDriveCommand(program3, deps = {}) {
|
|
|
37900
38049
|
await runDisconnect({ agentName: agent }, deps);
|
|
37901
38050
|
});
|
|
37902
38051
|
}
|
|
37903
|
-
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", __test;
|
|
38052
|
+
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;
|
|
37904
38053
|
var init_drive = __esm(() => {
|
|
37905
38054
|
init_source();
|
|
37906
38055
|
init_loader();
|
|
@@ -37920,6 +38069,13 @@ var init_drive = __esm(() => {
|
|
|
37920
38069
|
"https://www.googleapis.com/auth/drive.file"
|
|
37921
38070
|
];
|
|
37922
38071
|
DEFAULT_SCOPES = DRIVE_READONLY_SCOPES;
|
|
38072
|
+
GOOGLE_SERVICES = [
|
|
38073
|
+
"drive",
|
|
38074
|
+
"docs",
|
|
38075
|
+
"sheets",
|
|
38076
|
+
"slides",
|
|
38077
|
+
"cal"
|
|
38078
|
+
];
|
|
37923
38079
|
__test = {
|
|
37924
38080
|
runConnect,
|
|
37925
38081
|
runDisconnect,
|
|
@@ -77033,6 +77189,23 @@ function listGoogleAccounts(yamlText) {
|
|
|
77033
77189
|
}
|
|
77034
77190
|
return out;
|
|
77035
77191
|
}
|
|
77192
|
+
function setGoogleAccountSelection(yamlText, account, selection) {
|
|
77193
|
+
const doc = import_yaml6.parseDocument(yamlText);
|
|
77194
|
+
ensureGoogleAccountEntry(doc, account);
|
|
77195
|
+
const currentReadonly = doc.getIn([
|
|
77196
|
+
"google_accounts",
|
|
77197
|
+
account,
|
|
77198
|
+
"readonly"
|
|
77199
|
+
]);
|
|
77200
|
+
const currentServices = doc.getIn(["google_accounts", account, "services"]);
|
|
77201
|
+
const currentServicesArr = readEnabledFor(currentServices);
|
|
77202
|
+
const unchanged = currentReadonly === selection.readonly && currentServicesArr.length === selection.services.length && currentServicesArr.every((s, i) => s === selection.services[i]);
|
|
77203
|
+
if (unchanged)
|
|
77204
|
+
return yamlText;
|
|
77205
|
+
doc.setIn(["google_accounts", account, "readonly"], selection.readonly);
|
|
77206
|
+
doc.setIn(["google_accounts", account, "services"], [...selection.services]);
|
|
77207
|
+
return String(doc);
|
|
77208
|
+
}
|
|
77036
77209
|
function readEnabledFor(node) {
|
|
77037
77210
|
if (!import_yaml6.isSeq(node))
|
|
77038
77211
|
return [];
|
|
@@ -77068,7 +77241,7 @@ function registerAuthGoogleSubcommands(program3, authParent) {
|
|
|
77068
77241
|
registerDisable(google, program3);
|
|
77069
77242
|
registerList(google, program3);
|
|
77070
77243
|
const account = google.command("account").description("Manage Google account credentials in the auth-broker (add / remove / list).");
|
|
77071
|
-
registerAccountAdd(account);
|
|
77244
|
+
registerAccountAdd(account, program3);
|
|
77072
77245
|
registerAccountRemove(account);
|
|
77073
77246
|
registerAccountList(account);
|
|
77074
77247
|
}
|
|
@@ -77328,15 +77501,17 @@ function expandAllAgents(agents, config) {
|
|
|
77328
77501
|
function pad(s, width) {
|
|
77329
77502
|
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
77330
77503
|
}
|
|
77331
|
-
function registerAccountAdd(accountParent) {
|
|
77332
|
-
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).action(withConfigError(async (account, opts) => {
|
|
77504
|
+
function registerAccountAdd(accountParent, program3) {
|
|
77505
|
+
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) => {
|
|
77333
77506
|
const normalizedAccount = validateAndNormalizeAccountEmail(account);
|
|
77334
77507
|
const [
|
|
77335
77508
|
{
|
|
77336
77509
|
runDriveOAuthFlow: runDriveOAuthFlow2,
|
|
77337
77510
|
selectGoogleWorkspaceScopes: selectGoogleWorkspaceScopes2,
|
|
77338
77511
|
capabilitiesFromScopeString: capabilitiesFromScopeString2,
|
|
77339
|
-
resolveReconsentCapabilities: resolveReconsentCapabilities2
|
|
77512
|
+
resolveReconsentCapabilities: resolveReconsentCapabilities2,
|
|
77513
|
+
parseServicesOption: parseServicesOption2,
|
|
77514
|
+
resolveScopeSelection: resolveScopeSelection2
|
|
77340
77515
|
},
|
|
77341
77516
|
{ selectInitialTier: selectInitialTier2 },
|
|
77342
77517
|
{ brokerCall: brokerCall2 },
|
|
@@ -77407,14 +77582,41 @@ function registerAccountAdd(accountParent) {
|
|
|
77407
77582
|
clientSecretRaw = await resolveRef(clientSecretRaw, "google_client_secret");
|
|
77408
77583
|
}
|
|
77409
77584
|
const tier = gw.tier ?? "core";
|
|
77585
|
+
if (opts.readonly && opts.write) {
|
|
77586
|
+
throw new Error("--readonly and --write are mutually exclusive. A read-only " + "account mints zero write scopes; drop one of the flags.");
|
|
77587
|
+
}
|
|
77588
|
+
const requestedServices = opts.services ? parseServicesOption2(opts.services) : undefined;
|
|
77589
|
+
const persistedRecord = config.google_accounts?.[normalizedAccount];
|
|
77590
|
+
const persistedSelection = persistedRecord && (persistedRecord.readonly !== undefined || persistedRecord.services !== undefined) ? {
|
|
77591
|
+
readonly: persistedRecord.readonly,
|
|
77592
|
+
services: persistedRecord.services
|
|
77593
|
+
} : undefined;
|
|
77410
77594
|
const existingEntry = (await brokerCall2(async (client) => client.listGoogleAccounts())).accounts.find((a) => a.account === normalizedAccount);
|
|
77411
77595
|
const plan = resolveReconsentCapabilities2({ write: opts.write ?? false, calendar: opts.calendar ?? false }, existingEntry ? capabilitiesFromScopeString2(existingEntry.scope) : undefined);
|
|
77412
77596
|
if (existingEntry && !opts.replace && plan.added.length > 0) {
|
|
77413
77597
|
throw new Error(buildScopeReconsentRequiredError(normalizedAccount, plan.added));
|
|
77414
77598
|
}
|
|
77599
|
+
if (existingEntry && !opts.replace && (opts.readonly || requestedServices)) {
|
|
77600
|
+
throw new Error(buildSelectionReconsentRequiredError(normalizedAccount, {
|
|
77601
|
+
readonly: opts.readonly ?? false,
|
|
77602
|
+
services: opts.services
|
|
77603
|
+
}));
|
|
77604
|
+
}
|
|
77605
|
+
const selPlan = resolveScopeSelection2({
|
|
77606
|
+
flags: {
|
|
77607
|
+
readonly: opts.readonly ?? false,
|
|
77608
|
+
write: opts.write ?? false,
|
|
77609
|
+
calendar: opts.calendar ?? false,
|
|
77610
|
+
services: requestedServices
|
|
77611
|
+
},
|
|
77612
|
+
persisted: persistedSelection,
|
|
77613
|
+
existing: plan.effective,
|
|
77614
|
+
tier
|
|
77615
|
+
});
|
|
77415
77616
|
const accountScopes = selectGoogleWorkspaceScopes2({
|
|
77416
|
-
write:
|
|
77417
|
-
|
|
77617
|
+
write: selPlan.selection.driveWrite,
|
|
77618
|
+
readonly: selPlan.selection.readonly,
|
|
77619
|
+
services: selPlan.selection.services,
|
|
77418
77620
|
tier
|
|
77419
77621
|
});
|
|
77420
77622
|
const oauthCfg = {
|
|
@@ -77422,16 +77624,27 @@ function registerAccountAdd(accountParent) {
|
|
|
77422
77624
|
client_secret: clientSecretRaw,
|
|
77423
77625
|
scopes: accountScopes
|
|
77424
77626
|
};
|
|
77425
|
-
if (
|
|
77627
|
+
if (selPlan.selection.readonly) {
|
|
77628
|
+
console.log(source_default.yellow(" READ-ONLY selection: minting only .readonly scope variants \u2014 zero write scopes."));
|
|
77629
|
+
}
|
|
77630
|
+
if (selPlan.selection.driveWrite && !selPlan.selection.readonly) {
|
|
77426
77631
|
console.log(source_default.yellow(" Requesting Drive WRITE scope (drive.file \u2014 create/edit app-created files)."));
|
|
77427
77632
|
}
|
|
77428
|
-
if (
|
|
77633
|
+
if (selPlan.selection.services.includes("cal")) {
|
|
77429
77634
|
console.log(source_default.yellow(" Requesting Calendar READ-ONLY scope (calendar.readonly \u2014 list calendars, read events)."));
|
|
77430
77635
|
}
|
|
77431
|
-
for (const
|
|
77636
|
+
for (const d of selPlan.dropped) {
|
|
77637
|
+
console.log(source_default.yellow(` NOT carried forward: ${d}.`));
|
|
77638
|
+
}
|
|
77639
|
+
const carried = plan.carried.filter((k) => !(k === "write" && selPlan.selection.readonly) && !(k === "calendar" && !selPlan.selection.services.includes("cal")));
|
|
77640
|
+
for (const line of buildCarryForwardNotices(carried)) {
|
|
77432
77641
|
console.log(source_default.gray(line));
|
|
77433
77642
|
}
|
|
77434
|
-
|
|
77643
|
+
if (selPlan.persist) {
|
|
77644
|
+
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."));
|
|
77645
|
+
} else {
|
|
77646
|
+
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."));
|
|
77647
|
+
}
|
|
77435
77648
|
console.log(source_default.gray(` Changing the tier later requires re-running this command
|
|
77436
77649
|
` + " (`--replace`) \u2014 OAuth scopes are fixed at consent time."));
|
|
77437
77650
|
const oauthEnv = {
|
|
@@ -77461,6 +77674,18 @@ function registerAccountAdd(accountParent) {
|
|
|
77461
77674
|
});
|
|
77462
77675
|
console.log();
|
|
77463
77676
|
console.log(source_default.green(` \u2713 Registered Google account ${source_default.bold(normalizedAccount)} with auth-broker.`));
|
|
77677
|
+
if (selPlan.persist) {
|
|
77678
|
+
const configPath = getConfigPath(program3);
|
|
77679
|
+
const raw = readFileSync29(configPath, "utf-8");
|
|
77680
|
+
const patched = setGoogleAccountSelection(raw, normalizedAccount, {
|
|
77681
|
+
readonly: selPlan.persist.readonly ?? false,
|
|
77682
|
+
services: selPlan.persist.services ?? []
|
|
77683
|
+
});
|
|
77684
|
+
if (patched !== raw) {
|
|
77685
|
+
writeGoogleYaml(configPath, patched);
|
|
77686
|
+
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.`));
|
|
77687
|
+
}
|
|
77688
|
+
}
|
|
77464
77689
|
console.log();
|
|
77465
77690
|
console.log(` Next: enable on one or more agents:`);
|
|
77466
77691
|
console.log(source_default.cyan(` switchroom auth google enable ${normalizedAccount} <agent> [...]`));
|
|
@@ -77495,6 +77720,24 @@ function buildScopeReconsentRequiredError(account, added) {
|
|
|
77495
77720
|
].join(`
|
|
77496
77721
|
`);
|
|
77497
77722
|
}
|
|
77723
|
+
function buildSelectionReconsentRequiredError(account, requested) {
|
|
77724
|
+
const flags = [
|
|
77725
|
+
requested.readonly ? "--readonly" : "",
|
|
77726
|
+
requested.services ? `--services ${requested.services}` : ""
|
|
77727
|
+
].filter((s) => s.length > 0).join(" ");
|
|
77728
|
+
return [
|
|
77729
|
+
`'${account}' is already registered with the auth-broker, and changing ` + `its readonly/services selection changes the OAuth scope set.`,
|
|
77730
|
+
"",
|
|
77731
|
+
"OAuth scopes are fixed at consent time \u2014 a stored token cannot be",
|
|
77732
|
+
"reshaped in place. Re-consent to mint a token with the new selection:",
|
|
77733
|
+
"",
|
|
77734
|
+
` switchroom auth google account add ${account} --replace ${flags}`,
|
|
77735
|
+
"",
|
|
77736
|
+
"The persisted selection (and any capabilities the flags don't",
|
|
77737
|
+
"explicitly change) is carried forward, never silently widened."
|
|
77738
|
+
].join(`
|
|
77739
|
+
`);
|
|
77740
|
+
}
|
|
77498
77741
|
function buildCarryForwardNotices(carried) {
|
|
77499
77742
|
return carried.map((k) => {
|
|
77500
77743
|
const { flag, scope, summary } = CAPABILITY_LABELS[k];
|
|
@@ -96069,6 +96312,8 @@ init_agent_owned_tree();
|
|
|
96069
96312
|
init_component_versions();
|
|
96070
96313
|
var VERIFY_COMPONENTS_STEP = "verify-components";
|
|
96071
96314
|
var ENSURE_BANKS_STEP = "ensure-banks";
|
|
96315
|
+
var ENSURE_BANKS_ATTEMPTS = 3;
|
|
96316
|
+
var ENSURE_BANKS_BACKOFF_MS = [2000, 5000];
|
|
96072
96317
|
function normalizeVersion(v) {
|
|
96073
96318
|
return v.trim().replace(/^v/, "");
|
|
96074
96319
|
}
|
|
@@ -96390,25 +96635,47 @@ async function executeRollout(steps, target, deps, execOpts = {}) {
|
|
|
96390
96635
|
break;
|
|
96391
96636
|
}
|
|
96392
96637
|
deps.log(`ROLL_STEP ensure-banks \u2014 creating any missing agent bank (idempotent) for ${rolled.length} agent(s)`);
|
|
96638
|
+
const sleep4 = deps.sleepMs ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
96639
|
+
const bankUnverified = [];
|
|
96640
|
+
let lastUnreachableReason = "";
|
|
96641
|
+
let concludedUnreachable = false;
|
|
96393
96642
|
for (const a of rolled) {
|
|
96394
|
-
let res
|
|
96395
|
-
|
|
96396
|
-
|
|
96397
|
-
}
|
|
96398
|
-
|
|
96643
|
+
let res = {
|
|
96644
|
+
ok: false,
|
|
96645
|
+
reason: "not probed"
|
|
96646
|
+
};
|
|
96647
|
+
const maxAttempts = concludedUnreachable ? 1 : ENSURE_BANKS_ATTEMPTS;
|
|
96648
|
+
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
96649
|
+
try {
|
|
96650
|
+
res = await deps.ensureBank(a);
|
|
96651
|
+
} catch (e) {
|
|
96652
|
+
res = { ok: false, reason: e.message };
|
|
96653
|
+
}
|
|
96654
|
+
if (res.ok || !/unreachable|timeout/i.test(res.reason))
|
|
96655
|
+
break;
|
|
96656
|
+
if (attempt < maxAttempts) {
|
|
96657
|
+
const backoff = ENSURE_BANKS_BACKOFF_MS[attempt - 1] ?? ENSURE_BANKS_BACKOFF_MS[ENSURE_BANKS_BACKOFF_MS.length - 1] ?? 0;
|
|
96658
|
+
deps.log(` ${a}: bank check ${res.reason.toLowerCase()} \u2014 retrying in ${backoff}ms ` + `(attempt ${attempt}/${maxAttempts})`);
|
|
96659
|
+
await sleep4(backoff);
|
|
96660
|
+
}
|
|
96399
96661
|
}
|
|
96400
96662
|
if (res.ok) {
|
|
96401
96663
|
deps.log(` ${a}: bank present`);
|
|
96402
96664
|
continue;
|
|
96403
96665
|
}
|
|
96404
96666
|
if (/unreachable|timeout/i.test(res.reason)) {
|
|
96405
|
-
deps.log(` ${a}: bank check ${res.reason.toLowerCase()} (
|
|
96406
|
-
|
|
96667
|
+
deps.log(` ${a}: bank check ${res.reason.toLowerCase()} after ` + `${maxAttempts} attempt(s) (transient \u2014 not treated as a missing bank)`);
|
|
96668
|
+
bankUnverified.push(a);
|
|
96669
|
+
lastUnreachableReason = res.reason;
|
|
96670
|
+
concludedUnreachable = true;
|
|
96407
96671
|
continue;
|
|
96408
96672
|
}
|
|
96409
96673
|
deps.log(` \u2717 ${a}: bank could NOT be created \u2014 ${res.reason}`);
|
|
96410
96674
|
bankEnsureFailed.push(a);
|
|
96411
96675
|
}
|
|
96676
|
+
if (bankUnverified.length > 0) {
|
|
96677
|
+
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.`);
|
|
96678
|
+
}
|
|
96412
96679
|
break;
|
|
96413
96680
|
}
|
|
96414
96681
|
case "verify-components": {
|
|
@@ -100251,6 +100518,59 @@ function findMissingWorkspaceScopes(seedScope, tier) {
|
|
|
100251
100518
|
}
|
|
100252
100519
|
var DRIVE_FILE_SCOPE = "https://www.googleapis.com/auth/drive.file";
|
|
100253
100520
|
var CALENDAR_READONLY_SCOPE = "https://www.googleapis.com/auth/calendar.readonly";
|
|
100521
|
+
var LAUNCHER_GOOGLE_SERVICES = [
|
|
100522
|
+
"drive",
|
|
100523
|
+
"docs",
|
|
100524
|
+
"sheets",
|
|
100525
|
+
"slides",
|
|
100526
|
+
"cal"
|
|
100527
|
+
];
|
|
100528
|
+
var UPSTREAM_SERVICE_NAMES = {
|
|
100529
|
+
drive: "drive",
|
|
100530
|
+
docs: "docs",
|
|
100531
|
+
sheets: "sheets",
|
|
100532
|
+
slides: "slides",
|
|
100533
|
+
cal: "calendar"
|
|
100534
|
+
};
|
|
100535
|
+
function parseLauncherServices(raw) {
|
|
100536
|
+
const tokens = raw.split(",").map((s) => s.trim().toLowerCase()).filter((s) => s.length > 0).map((s) => s === "calendar" ? "cal" : s);
|
|
100537
|
+
for (const t of tokens) {
|
|
100538
|
+
if (!LAUNCHER_GOOGLE_SERVICES.includes(t)) {
|
|
100539
|
+
throw new Error(`drive-mcp-launcher: unknown service '${t}' in --services ` + `(valid: ${LAUNCHER_GOOGLE_SERVICES.join(", ")})`);
|
|
100540
|
+
}
|
|
100541
|
+
}
|
|
100542
|
+
const set = new Set(tokens);
|
|
100543
|
+
return LAUNCHER_GOOGLE_SERVICES.filter((s) => set.has(s));
|
|
100544
|
+
}
|
|
100545
|
+
function requiredScopesForSelection(selection) {
|
|
100546
|
+
const ro = selection.readonly;
|
|
100547
|
+
const out = [];
|
|
100548
|
+
for (const s of selection.services) {
|
|
100549
|
+
switch (s) {
|
|
100550
|
+
case "docs":
|
|
100551
|
+
out.push(ro ? "https://www.googleapis.com/auth/documents.readonly" : "https://www.googleapis.com/auth/documents");
|
|
100552
|
+
break;
|
|
100553
|
+
case "sheets":
|
|
100554
|
+
out.push(ro ? "https://www.googleapis.com/auth/spreadsheets.readonly" : "https://www.googleapis.com/auth/spreadsheets");
|
|
100555
|
+
break;
|
|
100556
|
+
case "slides":
|
|
100557
|
+
out.push(ro ? "https://www.googleapis.com/auth/presentations.readonly" : "https://www.googleapis.com/auth/presentations");
|
|
100558
|
+
break;
|
|
100559
|
+
case "cal":
|
|
100560
|
+
out.push(CALENDAR_READONLY_SCOPE);
|
|
100561
|
+
break;
|
|
100562
|
+
case "drive":
|
|
100563
|
+
break;
|
|
100564
|
+
}
|
|
100565
|
+
}
|
|
100566
|
+
return out;
|
|
100567
|
+
}
|
|
100568
|
+
function findMissingScopesForSelection(seedScope, tier, selection) {
|
|
100569
|
+
if (!selection)
|
|
100570
|
+
return findMissingWorkspaceScopes(seedScope, tier);
|
|
100571
|
+
const have = new Set(seedScope.split(/\s+/).map((s) => s.trim()).filter((s) => s.length > 0));
|
|
100572
|
+
return requiredScopesForSelection(selection).filter((s) => !have.has(s));
|
|
100573
|
+
}
|
|
100254
100574
|
function seedOptInCapabilities(seedScope) {
|
|
100255
100575
|
const have = new Set(seedScope.split(/\s+/).map((s) => s.trim()).filter((s) => s.length > 0));
|
|
100256
100576
|
return {
|
|
@@ -100258,20 +100578,22 @@ function seedOptInCapabilities(seedScope) {
|
|
|
100258
100578
|
calendar: have.has(CALENDAR_READONLY_SCOPE)
|
|
100259
100579
|
};
|
|
100260
100580
|
}
|
|
100261
|
-
function buildMissingScopeWarning(missing, tier, accountEmail, granted) {
|
|
100581
|
+
function buildMissingScopeWarning(missing, tier, accountEmail, granted, selection) {
|
|
100262
100582
|
const short = missing.map((s) => s.replace(/^https:\/\/www\.googleapis\.com\/auth\//, "")).join(", ");
|
|
100263
|
-
const suffix = (granted.write ? " --write" : "") + (granted.calendar ? " --calendar" : "");
|
|
100583
|
+
const suffix = selection ? (selection.readonly ? " --readonly" : granted.write ? " --write" : "") + ` --services ${selection.services.join(",")}` : (granted.write ? " --write" : "") + (granted.calendar ? " --calendar" : "");
|
|
100264
100584
|
const preserved = [
|
|
100265
|
-
|
|
100266
|
-
granted.
|
|
100585
|
+
selection ? `--services ${selection.services.join(",")}` + `${selection.readonly ? " --readonly" : ""} preserves the persisted per-account selection` : "",
|
|
100586
|
+
!selection && granted.write ? "--write preserves the existing Drive write capability" : "",
|
|
100587
|
+
!selection && granted.calendar ? "--calendar preserves the existing Calendar read capability" : ""
|
|
100267
100588
|
].filter((s) => s.length > 0);
|
|
100268
|
-
|
|
100269
|
-
|
|
100589
|
+
const scopeSource = selection ? `for its persisted selection (services: ${selection.services.join(",")}` + `${selection.readonly ? ", read-only" : ""})` : `for tier '${tier ?? "core"}'`;
|
|
100590
|
+
return `drive-mcp-launcher: WARNING \u2014 the Google account '${accountEmail}' was ` + `consented WITHOUT the scope(s) needed ${scopeSource}: ` + `${short}.
|
|
100591
|
+
` + ` 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:
|
|
100270
100592
|
` + ` switchroom auth google account add ${accountEmail} --replace` + `${suffix}
|
|
100271
|
-
` + ` (scopes are derived from
|
|
100593
|
+
` + ` (${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.
|
|
100272
100594
|
`;
|
|
100273
100595
|
}
|
|
100274
|
-
function buildUvxArgs(tier) {
|
|
100596
|
+
function buildUvxArgs(tier, selection) {
|
|
100275
100597
|
const args = [
|
|
100276
100598
|
"--from",
|
|
100277
100599
|
`git+https://github.com/taylorwilsdon/google_workspace_mcp.git@${GOOGLE_WORKSPACE_MCP_PINNED_SHA}`,
|
|
@@ -100285,6 +100607,12 @@ function buildUvxArgs(tier) {
|
|
|
100285
100607
|
if (tier && tier.length > 0) {
|
|
100286
100608
|
args.push("--tool-tier", tier);
|
|
100287
100609
|
}
|
|
100610
|
+
if (selection && selection.services.length > 0) {
|
|
100611
|
+
args.push("--tools", ...selection.services.map((s) => UPSTREAM_SERVICE_NAMES[s]));
|
|
100612
|
+
}
|
|
100613
|
+
if (selection?.readonly) {
|
|
100614
|
+
args.push("--read-only");
|
|
100615
|
+
}
|
|
100288
100616
|
return args;
|
|
100289
100617
|
}
|
|
100290
100618
|
function buildChildEnv(baseEnv, credentialsDir, accountEmail) {
|
|
@@ -100457,6 +100785,15 @@ async function resolveConfigSecret2(raw, label) {
|
|
|
100457
100785
|
}
|
|
100458
100786
|
throw new Error(`${label} vault key '${key}' unreachable via the vault-broker (${result.msg}). Refusing to spawn the Google Workspace MCP.`);
|
|
100459
100787
|
}
|
|
100788
|
+
function resolveLauncherSelection(record2, tier) {
|
|
100789
|
+
if (!record2)
|
|
100790
|
+
return;
|
|
100791
|
+
if (record2.readonly === undefined && record2.services === undefined) {
|
|
100792
|
+
return;
|
|
100793
|
+
}
|
|
100794
|
+
const services = record2.services && record2.services.length > 0 ? parseLauncherServices(record2.services.join(",")) : tier === "extended" || tier === "complete" ? ["drive", "docs", "sheets", "slides"] : ["drive", "docs", "sheets"];
|
|
100795
|
+
return { readonly: record2.readonly ?? false, services };
|
|
100796
|
+
}
|
|
100460
100797
|
async function loadConfigSecrets() {
|
|
100461
100798
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_loader(), exports_loader));
|
|
100462
100799
|
const config = loadConfig2();
|
|
@@ -100466,7 +100803,16 @@ async function loadConfigSecrets() {
|
|
|
100466
100803
|
}
|
|
100467
100804
|
const clientId = await resolveConfigSecret2(gw.google_client_id, "google_client_id");
|
|
100468
100805
|
const clientSecret = await resolveConfigSecret2(gw.google_client_secret, "google_client_secret");
|
|
100469
|
-
|
|
100806
|
+
const googleAccounts = {};
|
|
100807
|
+
for (const [email, record2] of Object.entries(config.google_accounts ?? {})) {
|
|
100808
|
+
if (!record2)
|
|
100809
|
+
continue;
|
|
100810
|
+
googleAccounts[email.toLowerCase()] = {
|
|
100811
|
+
readonly: record2.readonly,
|
|
100812
|
+
services: record2.services
|
|
100813
|
+
};
|
|
100814
|
+
}
|
|
100815
|
+
return { clientId, clientSecret, tier: gw.tier, googleAccounts };
|
|
100470
100816
|
}
|
|
100471
100817
|
async function runDriveMcpLauncher(opts) {
|
|
100472
100818
|
let brokerCreds;
|
|
@@ -100514,11 +100860,27 @@ async function runDriveMcpLauncher(opts) {
|
|
|
100514
100860
|
process.exit(1);
|
|
100515
100861
|
}
|
|
100516
100862
|
const tier = opts.tier ?? configSecrets.tier;
|
|
100517
|
-
|
|
100863
|
+
let selection;
|
|
100864
|
+
try {
|
|
100865
|
+
const configSelection = resolveLauncherSelection(configSecrets.googleAccounts[brokerCreds.accountEmail.toLowerCase()], tier);
|
|
100866
|
+
if (opts.services !== undefined || opts.readOnly) {
|
|
100867
|
+
selection = {
|
|
100868
|
+
readonly: opts.readOnly ?? configSelection?.readonly ?? false,
|
|
100869
|
+
services: opts.services !== undefined ? parseLauncherServices(opts.services) : configSelection?.services ?? resolveLauncherSelection({ readonly: false }, tier).services
|
|
100870
|
+
};
|
|
100871
|
+
} else {
|
|
100872
|
+
selection = configSelection;
|
|
100873
|
+
}
|
|
100874
|
+
} catch (err) {
|
|
100875
|
+
process.stderr.write(`drive-mcp-launcher: ${err instanceof Error ? err.message : String(err)}
|
|
100876
|
+
`);
|
|
100877
|
+
process.exit(1);
|
|
100878
|
+
}
|
|
100879
|
+
const missingScopes = findMissingScopesForSelection(brokerCreds.scope, tier, selection);
|
|
100518
100880
|
if (missingScopes.length > 0) {
|
|
100519
|
-
process.stderr.write(buildMissingScopeWarning(missingScopes, tier, brokerCreds.accountEmail, seedOptInCapabilities(brokerCreds.scope)));
|
|
100881
|
+
process.stderr.write(buildMissingScopeWarning(missingScopes, tier, brokerCreds.accountEmail, seedOptInCapabilities(brokerCreds.scope), selection));
|
|
100520
100882
|
}
|
|
100521
|
-
const args = buildUvxArgs(tier);
|
|
100883
|
+
const args = buildUvxArgs(tier, selection);
|
|
100522
100884
|
const env2 = buildChildEnv(process.env, credentialsDir, brokerCreds.accountEmail);
|
|
100523
100885
|
const { spawn: spawn6 } = await import("node:child_process");
|
|
100524
100886
|
const os6 = await import("node:os");
|
|
@@ -100550,8 +100912,12 @@ async function runDriveMcpLauncher(opts) {
|
|
|
100550
100912
|
return new Promise(() => {});
|
|
100551
100913
|
}
|
|
100552
100914
|
function registerDriveMcpLauncherCommand(program3) {
|
|
100553
|
-
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) => {
|
|
100554
|
-
await runDriveMcpLauncher({
|
|
100915
|
+
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) => {
|
|
100916
|
+
await runDriveMcpLauncher({
|
|
100917
|
+
tier: opts.tier,
|
|
100918
|
+
services: opts.services,
|
|
100919
|
+
readOnly: opts.readOnly
|
|
100920
|
+
});
|
|
100555
100921
|
});
|
|
100556
100922
|
}
|
|
100557
100923
|
|
|
@@ -101111,12 +101477,20 @@ function redactToolCallParams(params) {
|
|
|
101111
101477
|
}
|
|
101112
101478
|
var DEFAULT_RECALL_MAX_TOKENS = 1024;
|
|
101113
101479
|
var DEFAULT_REFLECT_MAX_TOKENS = 1024;
|
|
101480
|
+
var DEFAULT_RECALL_BUDGET = "low";
|
|
101481
|
+
var DEFAULT_REFLECT_BUDGET = "mid";
|
|
101482
|
+
var VALID_BUDGETS = new Set(["low", "mid", "high"]);
|
|
101114
101483
|
function resolveClampMaxTokens(envVal, fallback) {
|
|
101115
101484
|
if (envVal === undefined || envVal === "")
|
|
101116
101485
|
return fallback;
|
|
101117
101486
|
const n = Number.parseInt(envVal, 10);
|
|
101118
101487
|
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
101119
101488
|
}
|
|
101489
|
+
function resolveClampBudget(envVal, fallback) {
|
|
101490
|
+
if (envVal === undefined || envVal === "")
|
|
101491
|
+
return fallback;
|
|
101492
|
+
return VALID_BUDGETS.has(envVal) ? envVal : fallback;
|
|
101493
|
+
}
|
|
101120
101494
|
function acceptedArgs(name) {
|
|
101121
101495
|
const spec = FALLBACK_TOOL_TABLE[name];
|
|
101122
101496
|
if (!spec)
|
|
@@ -101151,8 +101525,9 @@ function guardAndClampToolCall(params, env2) {
|
|
|
101151
101525
|
if (injected.max_tokens === undefined) {
|
|
101152
101526
|
injected.max_tokens = name === "recall" ? resolveClampMaxTokens(env2.HINDSIGHT_SHIM_RECALL_MAX_TOKENS, DEFAULT_RECALL_MAX_TOKENS) : resolveClampMaxTokens(env2.HINDSIGHT_SHIM_REFLECT_MAX_TOKENS, DEFAULT_REFLECT_MAX_TOKENS);
|
|
101153
101527
|
}
|
|
101154
|
-
if (injected.budget === undefined)
|
|
101155
|
-
injected.budget = "
|
|
101528
|
+
if (injected.budget === undefined) {
|
|
101529
|
+
injected.budget = name === "recall" ? resolveClampBudget(env2.HINDSIGHT_SHIM_RECALL_BUDGET, DEFAULT_RECALL_BUDGET) : resolveClampBudget(env2.HINDSIGHT_SHIM_REFLECT_BUDGET, DEFAULT_REFLECT_BUDGET);
|
|
101530
|
+
}
|
|
101156
101531
|
return { ok: true, params: { ...called, arguments: injected } };
|
|
101157
101532
|
}
|
|
101158
101533
|
return { ok: true, params };
|