switchroom 0.18.28 → 0.18.30
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/bin/handoff-briefing.sh +15 -2
- package/dist/agent-scheduler/index.js +111 -7
- package/dist/auth-broker/index.js +154 -73
- package/dist/cli/autoaccept-poll.js +8 -3
- package/dist/cli/drive-write-pretool.mjs +8 -3
- package/dist/cli/ms-365-write-pretool.mjs +158 -11
- package/dist/cli/notion-write-pretool.mjs +103 -4
- package/dist/cli/switchroom.js +2712 -2219
- package/dist/host-control/main.js +110 -70
- package/dist/vault/approvals/kernel-server.js +116 -70
- package/dist/vault/broker/server.js +314 -202
- package/package.json +3 -3
- package/profiles/_base/start.sh.hbs +105 -34
- package/telegram-plugin/dist/bridge/bridge.js +71 -47
- package/telegram-plugin/dist/gateway/gateway.js +1128 -666
- package/telegram-plugin/dist/server.js +89 -64
- package/telegram-plugin/gateway/backstop-delivery.ts +272 -0
- package/telegram-plugin/gateway/forward-origin.ts +9 -1
- package/telegram-plugin/gateway/gateway.ts +656 -388
- package/telegram-plugin/gateway/model-command.ts +331 -602
- package/telegram-plugin/gateway/session-model-file.ts +40 -0
- package/telegram-plugin/gateway/turn-record-status.ts +45 -0
- package/telegram-plugin/gateway/unhandled-message.ts +177 -0
- package/telegram-plugin/history.ts +153 -23
- package/telegram-plugin/llm-error-present.ts +24 -0
- package/telegram-plugin/model-unavailable.ts +55 -0
- package/telegram-plugin/operator-events.ts +113 -0
- package/telegram-plugin/pending-user-notice.ts +88 -0
- package/telegram-plugin/shared/local-time.ts +99 -0
- package/telegram-plugin/tests/backstop-delivery.test.ts +250 -0
- package/telegram-plugin/tests/catch-all-forwarded-history.test.ts +103 -0
- package/telegram-plugin/tests/catch-all-unhandled-message.test.ts +264 -0
- package/telegram-plugin/tests/forward-origin.test.ts +30 -3
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +111 -60
- package/telegram-plugin/tests/history.test.ts +88 -0
- package/telegram-plugin/tests/litellm-proxy-auth-misconfig.test.ts +278 -0
- package/telegram-plugin/tests/local-time.test.ts +135 -0
- package/telegram-plugin/tests/model-command.test.ts +427 -1512
- package/telegram-plugin/tests/session-model-file.test.ts +23 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +34 -0
- package/telegram-plugin/tier-downgrade.ts +4 -3
- package/telegram-plugin/turn-flush-safety.ts +25 -1
- package/vendor/hindsight-memory/scripts/backfill_transcripts.py +399 -2
- package/vendor/hindsight-memory/scripts/lib/client.py +47 -0
- package/vendor/hindsight-memory/scripts/lib/content.py +93 -7
- package/vendor/hindsight-memory/scripts/lib/turnlog.py +450 -0
- package/vendor/hindsight-memory/scripts/tests/test_backfill_from_logs.py +467 -0
- package/vendor/hindsight-memory/tests/test_content.py +63 -7
|
@@ -4011,7 +4011,8 @@ var init_protocol = __esm(() => {
|
|
|
4011
4011
|
v: exports_external.literal(PROTOCOL_VERSION),
|
|
4012
4012
|
op: exports_external.literal("get-credentials"),
|
|
4013
4013
|
id: exports_external.string().min(1),
|
|
4014
|
-
provider: ProviderNameSchema.optional()
|
|
4014
|
+
provider: ProviderNameSchema.optional(),
|
|
4015
|
+
account: exports_external.string().min(1).optional()
|
|
4015
4016
|
});
|
|
4016
4017
|
ListStateRequestSchema = exports_external.object({
|
|
4017
4018
|
v: exports_external.literal(PROTOCOL_VERSION),
|
|
@@ -4326,13 +4327,17 @@ class AuthBrokerClient {
|
|
|
4326
4327
|
sock.destroy();
|
|
4327
4328
|
}
|
|
4328
4329
|
}
|
|
4329
|
-
async getCredentials(provider) {
|
|
4330
|
+
async getCredentials(provider, account) {
|
|
4330
4331
|
const base = {
|
|
4331
4332
|
v: PROTOCOL_VERSION,
|
|
4332
4333
|
id: randomUUID(),
|
|
4333
4334
|
op: "get-credentials"
|
|
4334
4335
|
};
|
|
4335
|
-
|
|
4336
|
+
let req = base;
|
|
4337
|
+
if (provider !== undefined)
|
|
4338
|
+
req = { ...req, provider };
|
|
4339
|
+
if (account !== undefined)
|
|
4340
|
+
req = { ...req, account };
|
|
4336
4341
|
const data = await this.send(req);
|
|
4337
4342
|
return data;
|
|
4338
4343
|
}
|
|
@@ -4606,6 +4611,106 @@ var init_client = __esm(() => {
|
|
|
4606
4611
|
};
|
|
4607
4612
|
});
|
|
4608
4613
|
|
|
4614
|
+
// src/config/microsoft-workspace-acl.ts
|
|
4615
|
+
var exports_microsoft_workspace_acl = {};
|
|
4616
|
+
__export(exports_microsoft_workspace_acl, {
|
|
4617
|
+
vaultRefKey: () => vaultRefKey,
|
|
4618
|
+
shouldEmitMs365Mcp: () => shouldEmitMs365Mcp,
|
|
4619
|
+
normalizeMicrosoftBindings: () => normalizeMicrosoftBindings,
|
|
4620
|
+
ms365McpKeyForBinding: () => ms365McpKeyForBinding,
|
|
4621
|
+
microsoftAccountSlug: () => microsoftAccountSlug,
|
|
4622
|
+
isMultiAccount: () => isMultiAccount,
|
|
4623
|
+
isMicrosoftClientCredentialKeyForAgent: () => isMicrosoftClientCredentialKeyForAgent,
|
|
4624
|
+
bindingsForAgent: () => bindingsForAgent
|
|
4625
|
+
});
|
|
4626
|
+
function normalizeMicrosoftBindings(mw) {
|
|
4627
|
+
if (!mw)
|
|
4628
|
+
return [];
|
|
4629
|
+
if (mw.accounts !== undefined) {
|
|
4630
|
+
return mw.accounts.filter((b) => b && typeof b.account === "string" && b.account.length > 0).map((b) => ({
|
|
4631
|
+
account: b.account.trim().toLowerCase(),
|
|
4632
|
+
tools: b.tools,
|
|
4633
|
+
org_mode: b.org_mode
|
|
4634
|
+
}));
|
|
4635
|
+
}
|
|
4636
|
+
if (typeof mw.account === "string" && mw.account.length > 0) {
|
|
4637
|
+
return [
|
|
4638
|
+
{
|
|
4639
|
+
account: mw.account.trim().toLowerCase(),
|
|
4640
|
+
tools: mw.tools,
|
|
4641
|
+
org_mode: mw.org_mode
|
|
4642
|
+
}
|
|
4643
|
+
];
|
|
4644
|
+
}
|
|
4645
|
+
return [];
|
|
4646
|
+
}
|
|
4647
|
+
function isMultiAccount(mw) {
|
|
4648
|
+
return normalizeMicrosoftBindings(mw).length > 1;
|
|
4649
|
+
}
|
|
4650
|
+
function microsoftAccountSlug(account) {
|
|
4651
|
+
const email = account.trim().toLowerCase();
|
|
4652
|
+
const local = email.split("@")[0] ?? email;
|
|
4653
|
+
let base = local.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
4654
|
+
if (base.length === 0)
|
|
4655
|
+
base = "acct";
|
|
4656
|
+
if (base.length > 24)
|
|
4657
|
+
base = base.slice(0, 24).replace(/-+$/g, "");
|
|
4658
|
+
let h = 2166136261;
|
|
4659
|
+
for (let i = 0;i < email.length; i++) {
|
|
4660
|
+
h ^= email.charCodeAt(i);
|
|
4661
|
+
h = Math.imul(h, 16777619);
|
|
4662
|
+
}
|
|
4663
|
+
const hex = (h >>> 0).toString(16).padStart(8, "0").slice(0, 4);
|
|
4664
|
+
return `${base}-${hex}`;
|
|
4665
|
+
}
|
|
4666
|
+
function ms365McpKeyForBinding(mw, account) {
|
|
4667
|
+
if (isMultiAccount(mw))
|
|
4668
|
+
return `ms-365-${microsoftAccountSlug(account)}`;
|
|
4669
|
+
return "ms-365";
|
|
4670
|
+
}
|
|
4671
|
+
function shouldEmitMs365Mcp(agentName, agentMicrosoftAccount, microsoftAccounts) {
|
|
4672
|
+
if (!agentMicrosoftAccount)
|
|
4673
|
+
return false;
|
|
4674
|
+
const account = agentMicrosoftAccount.trim().toLowerCase();
|
|
4675
|
+
if (account.length === 0)
|
|
4676
|
+
return false;
|
|
4677
|
+
const acctEntry = microsoftAccounts?.[account];
|
|
4678
|
+
if (!acctEntry)
|
|
4679
|
+
return false;
|
|
4680
|
+
const enabledFor = acctEntry.enabled_for ?? [];
|
|
4681
|
+
return enabledFor.includes(agentName);
|
|
4682
|
+
}
|
|
4683
|
+
function bindingsForAgent(agentName, mw, microsoftAccounts) {
|
|
4684
|
+
return normalizeMicrosoftBindings(mw).filter((b) => shouldEmitMs365Mcp(agentName, b.account, microsoftAccounts));
|
|
4685
|
+
}
|
|
4686
|
+
function vaultRefKey(value) {
|
|
4687
|
+
if (typeof value !== "string" || !value.startsWith("vault:"))
|
|
4688
|
+
return null;
|
|
4689
|
+
const key = value.slice("vault:".length).split("#")[0];
|
|
4690
|
+
return key.length > 0 ? key : null;
|
|
4691
|
+
}
|
|
4692
|
+
function isMicrosoftClientCredentialKeyForAgent(config, agentName, key) {
|
|
4693
|
+
if (!agentName || !key)
|
|
4694
|
+
return false;
|
|
4695
|
+
const agentConfig = config.agents?.[agentName];
|
|
4696
|
+
if (!agentConfig)
|
|
4697
|
+
return false;
|
|
4698
|
+
if (agentConfig.mcp_servers?.["ms-365"] === false) {
|
|
4699
|
+
return false;
|
|
4700
|
+
}
|
|
4701
|
+
if (bindingsForAgent(agentName, agentConfig.microsoft_workspace, config.microsoft_accounts).length === 0) {
|
|
4702
|
+
return false;
|
|
4703
|
+
}
|
|
4704
|
+
const mw = config.microsoft_workspace;
|
|
4705
|
+
if (!mw)
|
|
4706
|
+
return false;
|
|
4707
|
+
for (const ref of [mw.microsoft_client_id, mw.microsoft_client_secret]) {
|
|
4708
|
+
if (vaultRefKey(ref) === key)
|
|
4709
|
+
return true;
|
|
4710
|
+
}
|
|
4711
|
+
return false;
|
|
4712
|
+
}
|
|
4713
|
+
|
|
4609
4714
|
// src/cli/ms-365-write-pretool.ts
|
|
4610
4715
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
4611
4716
|
import { createConnection } from "node:net";
|
|
@@ -4621,7 +4726,7 @@ async function loadMicrosoftFromAuthBroker(options = {}) {
|
|
|
4621
4726
|
let result;
|
|
4622
4727
|
try {
|
|
4623
4728
|
result = await withAuthBrokerClient2(async (client) => {
|
|
4624
|
-
return await client.getCredentials("microsoft");
|
|
4729
|
+
return await client.getCredentials("microsoft", options.account);
|
|
4625
4730
|
}, options.socketPath !== undefined ? { socket: options.socketPath } : undefined);
|
|
4626
4731
|
} catch (err) {
|
|
4627
4732
|
if (err instanceof AuthBrokerUnreachableError2 || err instanceof AuthBrokerError2) {
|
|
@@ -4644,6 +4749,30 @@ async function loadMicrosoftFromAuthBroker(options = {}) {
|
|
|
4644
4749
|
account_email: creds.microsoftOauth?.accountEmail
|
|
4645
4750
|
};
|
|
4646
4751
|
}
|
|
4752
|
+
async function resolveMicrosoftAccountBySlug(slug, options = {}) {
|
|
4753
|
+
if (!slug)
|
|
4754
|
+
return null;
|
|
4755
|
+
const { microsoftAccountSlug: microsoftAccountSlug2 } = await Promise.resolve().then(() => exports_microsoft_workspace_acl);
|
|
4756
|
+
let accounts;
|
|
4757
|
+
try {
|
|
4758
|
+
if (options.listAccounts) {
|
|
4759
|
+
accounts = await options.listAccounts();
|
|
4760
|
+
} else {
|
|
4761
|
+
const { withAuthBrokerClient: withAuthBrokerClient2 } = await Promise.resolve().then(() => (init_client(), exports_client));
|
|
4762
|
+
accounts = await withAuthBrokerClient2(async (client) => {
|
|
4763
|
+
const data = await client.listMicrosoftAccounts();
|
|
4764
|
+
return (data.accounts ?? []).map((a) => a.account).filter((a) => typeof a === "string");
|
|
4765
|
+
}, options.socketPath !== undefined ? { socket: options.socketPath } : undefined);
|
|
4766
|
+
}
|
|
4767
|
+
} catch {
|
|
4768
|
+
return null;
|
|
4769
|
+
}
|
|
4770
|
+
for (const account of accounts) {
|
|
4771
|
+
if (microsoftAccountSlug2(account) === slug)
|
|
4772
|
+
return account;
|
|
4773
|
+
}
|
|
4774
|
+
return null;
|
|
4775
|
+
}
|
|
4647
4776
|
var GRAPH_BASE = "https://graph.microsoft.com/v1.0";
|
|
4648
4777
|
var GRAPH_TIMEOUT_MS = 4000;
|
|
4649
4778
|
function defaultFetch() {
|
|
@@ -4793,7 +4922,15 @@ var IPC_REPLY_TIMEOUT_MS = 1e4;
|
|
|
4793
4922
|
var KERNEL_RPC_TIMEOUT_MS = 3000;
|
|
4794
4923
|
var GATEWAY_SOCKET = process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR !== undefined ? join3(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join3(homedir3(), ".claude", "channels", "telegram", "gateway.sock"));
|
|
4795
4924
|
var KERNEL_SOCKET = process.env.SWITCHROOM_KERNEL_SOCKET ?? "/run/switchroom/kernel/sock";
|
|
4796
|
-
var
|
|
4925
|
+
var MS365_TOOL_RE = /^mcp__ms-365(?:-([a-z0-9-]+))?__(.+)$/;
|
|
4926
|
+
function ms365BareToolName(toolName) {
|
|
4927
|
+
const m = MS365_TOOL_RE.exec(toolName);
|
|
4928
|
+
return m ? m[2] : null;
|
|
4929
|
+
}
|
|
4930
|
+
function ms365AccountSlugFromToolName(toolName) {
|
|
4931
|
+
const m = MS365_TOOL_RE.exec(toolName);
|
|
4932
|
+
return m && m[1] ? m[1] : null;
|
|
4933
|
+
}
|
|
4797
4934
|
var GATED_MS365_WRITE_TOOLS = new Set([
|
|
4798
4935
|
"create-calendar",
|
|
4799
4936
|
"update-calendar",
|
|
@@ -4880,9 +5017,9 @@ var KNOWN_SAFE_MS365_READ_TOOLS = new Set([
|
|
|
4880
5017
|
"list-accounts"
|
|
4881
5018
|
]);
|
|
4882
5019
|
function isGatedMs365Tool(toolName) {
|
|
4883
|
-
|
|
5020
|
+
const bare = ms365BareToolName(toolName);
|
|
5021
|
+
if (bare === null)
|
|
4884
5022
|
return false;
|
|
4885
|
-
const bare = toolName.slice(TOOL_PREFIX.length);
|
|
4886
5023
|
if (bare.length === 0)
|
|
4887
5024
|
return false;
|
|
4888
5025
|
if (KNOWN_SAFE_MS365_READ_TOOLS.has(bare))
|
|
@@ -4920,9 +5057,9 @@ function parseHookInput() {
|
|
|
4920
5057
|
}
|
|
4921
5058
|
}
|
|
4922
5059
|
function isCalendarEventTool(toolName) {
|
|
4923
|
-
|
|
5060
|
+
const bare = ms365BareToolName(toolName);
|
|
5061
|
+
if (bare === null)
|
|
4924
5062
|
return false;
|
|
4925
|
-
const bare = toolName.slice(TOOL_PREFIX.length);
|
|
4926
5063
|
return bare.includes("calendar-event");
|
|
4927
5064
|
}
|
|
4928
5065
|
function extractEventId(toolInput) {
|
|
@@ -5005,11 +5142,19 @@ function buildCalendarChanges(toolInput, current) {
|
|
|
5005
5142
|
async function enrichCalendarPreview(toolName, toolInput, deps = {}) {
|
|
5006
5143
|
if (!isCalendarEventTool(toolName))
|
|
5007
5144
|
return {};
|
|
5008
|
-
const loadHandle = deps.loadHandle ?? (() => loadMicrosoftFromAuthBroker());
|
|
5145
|
+
const loadHandle = deps.loadHandle ?? ((account2) => loadMicrosoftFromAuthBroker(account2 !== undefined ? { account: account2 } : {}));
|
|
5146
|
+
const resolveAccount = deps.resolveAccount ?? ((slug2) => resolveMicrosoftAccountBySlug(slug2));
|
|
5009
5147
|
const fetchEvent = deps.fetchEvent ?? ((a) => fetchCalendarEventContext(a));
|
|
5148
|
+
const slug = ms365AccountSlugFromToolName(toolName);
|
|
5149
|
+
let account;
|
|
5150
|
+
try {
|
|
5151
|
+
account = await resolveAccount(slug) ?? undefined;
|
|
5152
|
+
} catch {
|
|
5153
|
+
account = undefined;
|
|
5154
|
+
}
|
|
5010
5155
|
let handle;
|
|
5011
5156
|
try {
|
|
5012
|
-
handle = await loadHandle();
|
|
5157
|
+
handle = await loadHandle(account);
|
|
5013
5158
|
} catch {
|
|
5014
5159
|
return { resolveAttemptedButFailed: true };
|
|
5015
5160
|
}
|
|
@@ -5281,6 +5426,8 @@ if (__require.main == __require.module) {
|
|
|
5281
5426
|
});
|
|
5282
5427
|
}
|
|
5283
5428
|
export {
|
|
5429
|
+
ms365BareToolName,
|
|
5430
|
+
ms365AccountSlugFromToolName,
|
|
5284
5431
|
loadAllowFrom,
|
|
5285
5432
|
isGatedMs365Tool,
|
|
5286
5433
|
isCalendarEventTool,
|
|
@@ -12112,11 +12112,52 @@ var AgentGoogleWorkspaceConfigSchema = exports_external.object({
|
|
|
12112
12112
|
approvers: exports_external.array(ApproverIdSchema).min(1).optional().describe("Per-agent approver override. When set, replaces (does not extend) " + "the top-level drive.approvers list for this agent's onboarding card."),
|
|
12113
12113
|
tier: GoogleWorkspaceTierSchema.optional().describe("Per-agent tier override (RFC G Phase 1). When set, replaces the " + "top-level google_workspace.tier for this agent. Common case: most " + "agents on `core`, one specialist on `extended` for Slides access.")
|
|
12114
12114
|
}).optional();
|
|
12115
|
+
var MicrosoftAccountEmailSchema = exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
12116
|
+
message: "microsoft_workspace.account must be a Microsoft account email like " + "'alice@outlook.com' or 'alice@contoso.com' (colons not allowed)"
|
|
12117
|
+
}).transform((v) => v.trim().toLowerCase());
|
|
12118
|
+
var MicrosoftToolTokenSchema = exports_external.string().min(1).regex(/^[a-z0-9-]+$/, {
|
|
12119
|
+
message: "microsoft_workspace tools[] tokens are forwarded to softeria's " + "--enabled-tools regex; each must be lowercase kebab-case " + "([a-z0-9-]+, e.g. 'mail', 'calendar', 'send-mail') so an unescaped " + "regex metacharacter can't crash the launcher"
|
|
12120
|
+
});
|
|
12121
|
+
var MicrosoftAccountBindingSchema = exports_external.object({
|
|
12122
|
+
account: MicrosoftAccountEmailSchema.describe("The Microsoft account this binding uses. Must be a key in top-level " + "`microsoft_accounts:` with this agent in its `enabled_for[]`."),
|
|
12123
|
+
tools: exports_external.array(MicrosoftToolTokenSchema).min(1).optional().describe("Per-account tool allowlist \u2192 softeria `--enabled-tools <regex>` " + "(tokens joined with `|`). Omitted = all tools exposed for this account."),
|
|
12124
|
+
org_mode: exports_external.boolean().optional().describe("Per-binding org_mode override (RFC #1873 \u00a76.4).")
|
|
12125
|
+
});
|
|
12115
12126
|
var AgentMicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
12116
|
-
account:
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
|
|
12127
|
+
account: MicrosoftAccountEmailSchema.optional().describe("RFC #1873: the Microsoft account this agent uses for the M365 MCP. " + "Must be a key in top-level `microsoft_accounts:` with this agent " + "listed in its `enabled_for[]`. Read by the auth-broker " + "(get-credentials, provider=microsoft) and by the scaffold to " + "decide whether to emit the `ms-365` MCP entry. Normalized to " + "lowercase so it matches the microsoft_accounts key (which is " + "also normalized). Mutually exclusive with `accounts` (plural)."),
|
|
12128
|
+
tools: exports_external.array(MicrosoftToolTokenSchema).min(1).optional().describe("Per-account tool allowlist for the SINGULAR `account` form \u2192 " + "softeria `--enabled-tools <regex>`. Omitted = all tools. Only " + "valid with the singular `account`; using it together with the " + "plural `accounts` (which carries per-binding `tools`) is an error."),
|
|
12129
|
+
org_mode: exports_external.boolean().optional().describe("Per-agent org_mode override (RFC #1873 \u00a76.4). When set, replaces " + "the top-level microsoft_workspace.org_mode for this agent. " + "Defaults to top-level value (which defaults to false)."),
|
|
12130
|
+
accounts: exports_external.array(MicrosoftAccountBindingSchema).min(1).optional().describe("Plural multi-account form: bind MULTIPLE Microsoft accounts to " + "this agent, each with its own tool scope. Mutually exclusive with " + "the singular `account`. Each account gets its own `ms-365-<slug>` " + "MCP server.")
|
|
12131
|
+
}).superRefine((v, ctx) => {
|
|
12132
|
+
const hasSingular = v.account !== undefined;
|
|
12133
|
+
const hasPlural = v.accounts !== undefined;
|
|
12134
|
+
if (hasSingular && hasPlural) {
|
|
12135
|
+
ctx.addIssue({
|
|
12136
|
+
code: exports_external.ZodIssueCode.custom,
|
|
12137
|
+
message: "microsoft_workspace: use EITHER `account` (singular) OR " + "`accounts` (plural array), not both",
|
|
12138
|
+
path: ["accounts"]
|
|
12139
|
+
});
|
|
12140
|
+
}
|
|
12141
|
+
if (hasPlural && v.tools !== undefined) {
|
|
12142
|
+
ctx.addIssue({
|
|
12143
|
+
code: exports_external.ZodIssueCode.custom,
|
|
12144
|
+
message: "microsoft_workspace: block-level `tools` applies to the singular " + "`account` only; with `accounts` put `tools` inside each binding",
|
|
12145
|
+
path: ["tools"]
|
|
12146
|
+
});
|
|
12147
|
+
}
|
|
12148
|
+
if (hasPlural) {
|
|
12149
|
+
const seen = new Set;
|
|
12150
|
+
for (const b of v.accounts) {
|
|
12151
|
+
if (seen.has(b.account)) {
|
|
12152
|
+
ctx.addIssue({
|
|
12153
|
+
code: exports_external.ZodIssueCode.custom,
|
|
12154
|
+
message: `microsoft_workspace: duplicate account '${b.account}' in accounts[]`,
|
|
12155
|
+
path: ["accounts"]
|
|
12156
|
+
});
|
|
12157
|
+
}
|
|
12158
|
+
seen.add(b.account);
|
|
12159
|
+
}
|
|
12160
|
+
}
|
|
12120
12161
|
}).optional();
|
|
12121
12162
|
var AgentNotionWorkspaceConfigSchema = exports_external.object({
|
|
12122
12163
|
databases: exports_external.array(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,62}$/, {
|
|
@@ -12491,6 +12532,26 @@ var SwitchroomConfigSchema = exports_external.object({
|
|
|
12491
12532
|
for (const [name, a] of Object.entries(cfg.agents ?? {})) {
|
|
12492
12533
|
checkServes(a.serves, ["agents", name, "serves"]);
|
|
12493
12534
|
}
|
|
12535
|
+
const microsoftAccounts = cfg.microsoft_accounts;
|
|
12536
|
+
for (const [name, a] of Object.entries(cfg.agents ?? {})) {
|
|
12537
|
+
const mw = a.microsoft_workspace;
|
|
12538
|
+
const accounts = mw?.accounts;
|
|
12539
|
+
if (!accounts)
|
|
12540
|
+
continue;
|
|
12541
|
+
accounts.forEach((b, i) => {
|
|
12542
|
+
const acct = b?.account?.trim().toLowerCase();
|
|
12543
|
+
if (!acct)
|
|
12544
|
+
return;
|
|
12545
|
+
const enabledFor = microsoftAccounts?.[acct]?.enabled_for ?? [];
|
|
12546
|
+
if (!enabledFor.includes(name)) {
|
|
12547
|
+
ctx.addIssue({
|
|
12548
|
+
code: exports_external.ZodIssueCode.custom,
|
|
12549
|
+
message: `agent '${name}' binds Microsoft account '${acct}' but is not in ` + `microsoft_accounts['${acct}'].enabled_for[] \u2014 operator must run ` + `\`switchroom auth microsoft enable ${acct} ${name}\``,
|
|
12550
|
+
path: ["agents", name, "microsoft_workspace", "accounts", i, "account"]
|
|
12551
|
+
});
|
|
12552
|
+
}
|
|
12553
|
+
});
|
|
12554
|
+
}
|
|
12494
12555
|
});
|
|
12495
12556
|
|
|
12496
12557
|
// src/config/paths.ts
|
|
@@ -13127,6 +13188,22 @@ function validateNotionWorkspaceConfig(config) {
|
|
|
13127
13188
|
return issues;
|
|
13128
13189
|
}
|
|
13129
13190
|
|
|
13191
|
+
// src/config/timezone.ts
|
|
13192
|
+
function isResolvableTimezone(zone) {
|
|
13193
|
+
try {
|
|
13194
|
+
new Intl.DateTimeFormat("en-US", { timeZone: zone });
|
|
13195
|
+
return true;
|
|
13196
|
+
} catch {
|
|
13197
|
+
return false;
|
|
13198
|
+
}
|
|
13199
|
+
}
|
|
13200
|
+
var CONTAINER_DEFAULT_UTC_ZONES = new Set([
|
|
13201
|
+
"UTC",
|
|
13202
|
+
"Etc/UTC",
|
|
13203
|
+
"Etc/Universal",
|
|
13204
|
+
"Universal"
|
|
13205
|
+
]);
|
|
13206
|
+
|
|
13130
13207
|
// src/config/loader.ts
|
|
13131
13208
|
class ConfigError extends Error {
|
|
13132
13209
|
details;
|
|
@@ -13250,8 +13327,30 @@ function loadConfig(configPath) {
|
|
|
13250
13327
|
if (notionIssues.length > 0) {
|
|
13251
13328
|
throw new ConfigError(`Invalid notion_workspace configuration in ${filePath}`, notionIssues);
|
|
13252
13329
|
}
|
|
13330
|
+
validateAllTimezones(config, filePath);
|
|
13253
13331
|
return config;
|
|
13254
13332
|
}
|
|
13333
|
+
function validateAllTimezones(config, filePath) {
|
|
13334
|
+
const issues = [];
|
|
13335
|
+
const check = (zone, where) => {
|
|
13336
|
+
if (zone == null)
|
|
13337
|
+
return;
|
|
13338
|
+
if (!isResolvableTimezone(zone)) {
|
|
13339
|
+
issues.push(` ${where}: "${zone}" is not a resolvable IANA timezone ` + `(shape is valid but no such zone exists \u2014 check for a typo, ` + `e.g. "Australia/Melbourne", "America/New_York", "UTC").`);
|
|
13340
|
+
}
|
|
13341
|
+
};
|
|
13342
|
+
check(config.switchroom?.timezone, "switchroom.timezone");
|
|
13343
|
+
check(config.defaults?.timezone, "defaults.timezone");
|
|
13344
|
+
for (const [profileName, profile] of Object.entries(config.profiles ?? {})) {
|
|
13345
|
+
check(profile?.timezone, `profiles.${profileName}.timezone`);
|
|
13346
|
+
}
|
|
13347
|
+
for (const [agentName, agentRaw] of Object.entries(config.agents)) {
|
|
13348
|
+
check(agentRaw?.timezone, `agents.${agentName}.timezone`);
|
|
13349
|
+
}
|
|
13350
|
+
if (issues.length > 0) {
|
|
13351
|
+
throw new ConfigError(`Invalid timezone configuration in ${filePath}`, issues);
|
|
13352
|
+
}
|
|
13353
|
+
}
|
|
13255
13354
|
function validateAllCronTopicAliases(config, filePath) {
|
|
13256
13355
|
const issues = [];
|
|
13257
13356
|
for (const [agentName, agentRaw] of Object.entries(config.agents)) {
|