switchroom 0.15.12 → 0.15.14
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 +324 -14
- package/dist/auth-broker/index.js +61 -4
- package/dist/cli/notion-write-pretool.mjs +61 -4
- package/dist/cli/switchroom.js +402 -113
- package/dist/host-control/main.js +61 -4
- package/dist/vault/approvals/kernel-server.js +62 -5
- package/dist/vault/broker/server.js +62 -5
- package/package.json +1 -1
- package/profiles/_base/cron-session.sh.hbs +30 -13
- package/profiles/_shared/agent-self-service.md.hbs +37 -0
- package/telegram-plugin/bridge/bridge.ts +38 -1
- package/telegram-plugin/dist/bridge/bridge.js +31 -1
- package/telegram-plugin/dist/gateway/gateway.js +536 -53
- package/telegram-plugin/dist/server.js +31 -1
- package/telegram-plugin/gateway/gateway.ts +169 -6
- package/telegram-plugin/gateway/ipc-protocol.ts +31 -1
- package/telegram-plugin/gateway/ipc-server.ts +29 -0
- package/telegram-plugin/gateway/linear-activity.ts +145 -0
- package/telegram-plugin/runtime-metrics.ts +14 -0
- package/telegram-plugin/scoped-approval.ts +253 -0
- package/telegram-plugin/tests/bridge-liveness-override.test.ts +21 -0
- package/telegram-plugin/tests/ipc-server-validate-send-outbound.test.ts +54 -0
- package/telegram-plugin/tests/linear-agent-activity.test.ts +1 -1
- package/telegram-plugin/tests/linear-create-issue.test.ts +211 -0
- package/telegram-plugin/tests/permission-verdict-resume-guard.test.ts +13 -0
- package/telegram-plugin/tests/runtime-metrics.test.ts +9 -0
- package/telegram-plugin/tests/scoped-approval.test.ts +254 -0
- package/telegram-plugin/tests/send-outbound-wiring.test.ts +63 -0
package/dist/cli/switchroom.js
CHANGED
|
@@ -13520,7 +13520,7 @@ var init_zod = __esm(() => {
|
|
|
13520
13520
|
});
|
|
13521
13521
|
|
|
13522
13522
|
// src/config/schema.ts
|
|
13523
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, TelegramReactionsPollSchema, PollSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, SwitchroomConfigSchema;
|
|
13523
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, TelegramReactionsPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, SwitchroomConfigSchema;
|
|
13524
13524
|
var init_schema = __esm(() => {
|
|
13525
13525
|
init_zod();
|
|
13526
13526
|
CodeRepoEntrySchema = exports_external.object({
|
|
@@ -13553,11 +13553,29 @@ var init_schema = __esm(() => {
|
|
|
13553
13553
|
HttpDiffPollSchema,
|
|
13554
13554
|
TelegramReactionsPollSchema
|
|
13555
13555
|
]);
|
|
13556
|
+
TelegramMessageActionSchema = exports_external.object({
|
|
13557
|
+
type: exports_external.literal("telegram-message"),
|
|
13558
|
+
text: exports_external.string().min(1).describe("Operator-authored message text. Posts to the AGENT'S OWN chat only " + "(the chat is not configurable \u2014 fenced by construction), into the " + "entry-level `topic` when set. Supports ONLY deterministic placeholders " + "{{date}} / {{time}} (UTC, from the fire clock) and {{agent}}. NO vault " + "secrets (use a webhook action for secret-bearing requests) and NO model " + "output \u2014 the text is fully determined by config."),
|
|
13559
|
+
parse_mode: exports_external.enum(["html", "text"]).default("html").describe("Telegram parse mode for the message body.")
|
|
13560
|
+
});
|
|
13561
|
+
WebhookActionSchema = exports_external.object({
|
|
13562
|
+
type: exports_external.literal("webhook"),
|
|
13563
|
+
url: exports_external.string().url().describe("Webhook target. SAME egress fence as an http-diff poll (\u00a76.1): https " + "only, host on the operator egress allowlist, loopback/private/link-local " + "rejected, resolved IP DNS-rebind-pinned. Not agent-writable without an " + "operator commit."),
|
|
13564
|
+
method: exports_external.enum(["GET", "POST"]).default("POST"),
|
|
13565
|
+
headers: exports_external.record(exports_external.string()).optional().describe("Static headers; {{secret}} substitution applies."),
|
|
13566
|
+
body: exports_external.string().optional().describe("Static request body; {{secret}} substitution applies."),
|
|
13567
|
+
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).default([]).describe("Vault keys this webhook may inject into headers/body via {{name}}. Each " + "is HOST-PINNED (\u00a76.1): a secret may only be sent to the host it is bound " + "to in operator config, so an approved action cannot exfil it elsewhere.")
|
|
13568
|
+
});
|
|
13569
|
+
ActionSpecSchema = exports_external.discriminatedUnion("type", [
|
|
13570
|
+
TelegramMessageActionSchema,
|
|
13571
|
+
WebhookActionSchema
|
|
13572
|
+
]);
|
|
13556
13573
|
ScheduleEntrySchema = exports_external.object({
|
|
13557
13574
|
cron: exports_external.string().describe("Cron expression (e.g., '0 8 * * *')"),
|
|
13558
|
-
prompt: exports_external.string().describe("Prompt to send at the scheduled time (the escalation prompt when kind=poll; templated with {{diff}})."),
|
|
13559
|
-
kind: exports_external.enum(["poll", "prompt"]).optional().describe("Tier-0 routing (docs/rfcs/cheap-cron-sessions.md). 'prompt' (default) " + "fires a model turn every tick (Tier 1/2 per `context`). 'poll' runs a " + "model-free deterministic check (requires `poll`) and only escalates to " + "a model fire on a hit.
|
|
13575
|
+
prompt: exports_external.string().optional().describe("Prompt to send at the scheduled time (the escalation prompt when " + "kind=poll; templated with {{diff}}). Required for kind prompt/poll; " + "absent for kind=action (an action has no model fire, so no prompt)."),
|
|
13576
|
+
kind: exports_external.enum(["poll", "prompt", "action"]).optional().describe("Tier-0 routing (docs/rfcs/cheap-cron-sessions.md). 'prompt' (default) " + "fires a model turn every tick (Tier 1/2 per `context`). 'poll' runs a " + "model-free deterministic check (requires `poll`) and only escalates to " + "a model fire on a hit. 'action' runs a model-free deterministic verb " + "(requires `action`) that COMPLETES the work and never escalates \u2014 zero " + "tokens, no session. poll/prompt are honoured only when " + "SWITCHROOM_CHEAP_CRON is on; an action is model-free regardless (the " + "kill-switch governs model tiering, not deterministic actions)."),
|
|
13560
13577
|
poll: PollSpecSchema.optional().describe("Required iff kind=poll. The declarative poll spec."),
|
|
13578
|
+
action: ActionSpecSchema.optional().describe("Required iff kind=action. The declarative action spec (telegram-message or webhook)."),
|
|
13561
13579
|
model: exports_external.string().optional().describe("Cron model hint. Reactivated by SWITCHROOM_CHEAP_CRON (was DEPRECATED/" + "IGNORED in v0.8). A known-cheap id (sonnet/haiku family) routes the " + "fire to a fresh cheap cron session (Tier 1, `context: fresh`); 'opus', " + "a custom id, or unset routes to the agent's live session (Tier 2, " + "`context: agent`) \u2014 the conservative default that preserves pre-v0.8 " + "behaviour. Note: a live session's model is fixed at launch, so on Tier " + "2 this is informational. See docs/scheduling.md."),
|
|
13562
13580
|
context: exports_external.enum(["fresh", "agent"]).optional().describe("Does this cron need the agent, or just a model? 'fresh' \u2192 a minimal-" + "context cheap cron session (Tier 1). 'agent' \u2192 the agent's live " + "session with full persona/memory (Tier 2). Unset \u2192 inferred from " + "`model` (cheap\u2192fresh, else agent). Honoured only when " + "SWITCHROOM_CHEAP_CRON is on."),
|
|
13563
13581
|
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).default([]).describe("Vault key names this cron task may read via the vault-broker daemon. " + "Empty by default \u2014 broker requests for unlisted keys are denied. " + "Note: this is misconfiguration protection (a typo in cron-A doesn't " + "accidentally read cron-B's keys) rather than a security boundary \u2014 " + "anyone who can edit cron scripts can also edit switchroom.yaml, and " + "anyone with the vault passphrase can read the vault file directly. " + "See docs/configuration.md for the full framing."),
|
|
@@ -13574,13 +13592,41 @@ var init_schema = __esm(() => {
|
|
|
13574
13592
|
message: "kind: poll requires a `poll` spec (http-diff or telegram-reactions)."
|
|
13575
13593
|
});
|
|
13576
13594
|
}
|
|
13577
|
-
if (kind
|
|
13595
|
+
if (kind !== "poll" && entry.poll) {
|
|
13578
13596
|
ctx.addIssue({
|
|
13579
13597
|
code: exports_external.ZodIssueCode.custom,
|
|
13580
13598
|
path: ["poll"],
|
|
13581
13599
|
message: "`poll` is only valid when kind: poll."
|
|
13582
13600
|
});
|
|
13583
13601
|
}
|
|
13602
|
+
if (kind === "action" && !entry.action) {
|
|
13603
|
+
ctx.addIssue({
|
|
13604
|
+
code: exports_external.ZodIssueCode.custom,
|
|
13605
|
+
path: ["action"],
|
|
13606
|
+
message: "kind: action requires an `action` spec (telegram-message or webhook)."
|
|
13607
|
+
});
|
|
13608
|
+
}
|
|
13609
|
+
if (kind !== "action" && entry.action) {
|
|
13610
|
+
ctx.addIssue({
|
|
13611
|
+
code: exports_external.ZodIssueCode.custom,
|
|
13612
|
+
path: ["action"],
|
|
13613
|
+
message: "`action` is only valid when kind: action."
|
|
13614
|
+
});
|
|
13615
|
+
}
|
|
13616
|
+
if (kind !== "action" && (entry.prompt === undefined || entry.prompt.trim() === "")) {
|
|
13617
|
+
ctx.addIssue({
|
|
13618
|
+
code: exports_external.ZodIssueCode.custom,
|
|
13619
|
+
path: ["prompt"],
|
|
13620
|
+
message: `kind: ${kind} requires a non-empty \`prompt\`.`
|
|
13621
|
+
});
|
|
13622
|
+
}
|
|
13623
|
+
if (kind === "action" && entry.prompt !== undefined) {
|
|
13624
|
+
ctx.addIssue({
|
|
13625
|
+
code: exports_external.ZodIssueCode.custom,
|
|
13626
|
+
path: ["prompt"],
|
|
13627
|
+
message: "`prompt` is not valid for kind: action (an action never fires a model)."
|
|
13628
|
+
});
|
|
13629
|
+
}
|
|
13584
13630
|
});
|
|
13585
13631
|
AgentSoulSchema = exports_external.object({
|
|
13586
13632
|
name: exports_external.string().describe("Agent persona name (e.g., 'Coach', 'Sage')"),
|
|
@@ -13714,7 +13760,8 @@ var init_schema = __esm(() => {
|
|
|
13714
13760
|
linear_agent: exports_external.object({
|
|
13715
13761
|
enabled: exports_external.boolean(),
|
|
13716
13762
|
token: exports_external.string().describe("vault:<key> reference to the Linear OAuth app token (actor=app). " + "Resolved at runtime via the vault broker (canonically " + "vault:linear/<agent>/token). Never an inline literal."),
|
|
13717
|
-
workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational \u2014 used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace.")
|
|
13763
|
+
workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational \u2014 used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace."),
|
|
13764
|
+
default_team_id: exports_external.string().optional().describe("Optional Linear team id new captured issues file into when the " + "agent doesn't pass an explicit team_id. Unnecessary for a " + "single-team workspace (auto-resolved); set it only when the " + "workspace has multiple teams. Manage via " + "`switchroom linear-agent set-team <agent> <team>`.")
|
|
13718
13765
|
}).optional().describe("Linear first-class agent integration (#2298). When enabled, the " + "agent appears in a Linear workspace as an app actor (own name/" + "avatar, @-mentionable, delegate-assignable). Linear AgentSessionEvent " + "webhooks (mention / delegation) wake the agent instantly via the " + "same gateway inject path as webhook_dispatch, tagged " + 'meta.source="linear" with the agent_session_id, and the agent ' + "responds with structured AgentActivity (thought/message/complete/" + "error) via the linear_agent_activity MCP tool. Builds the " + "session-lifecycle layer on top of the plain webhook_sources:[linear] " + "+ webhook_dispatch support (#2272). The OAuth app token is stored in " + "the vault and referenced here as vault:linear/<agent>/token; run " + "`switchroom linear-agent setup <agent>` to provision it. Off by " + "default \u2014 opt in per agent. Cascades from " + "defaults.channels.telegram.linear_agent."),
|
|
13719
13766
|
chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID \u2014 overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
|
|
13720
13767
|
default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted \u2014 set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field \u2014 the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
|
|
@@ -14640,6 +14687,16 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
14640
14687
|
}
|
|
14641
14688
|
merged.reaction_dispatch = combined;
|
|
14642
14689
|
}
|
|
14690
|
+
const linearEnabled = merged.channels?.telegram?.linear_agent?.enabled === true;
|
|
14691
|
+
if (linearEnabled) {
|
|
14692
|
+
const rd = merged.reaction_dispatch;
|
|
14693
|
+
if (!rd || rd.emojis === undefined) {
|
|
14694
|
+
merged.reaction_dispatch = {
|
|
14695
|
+
enabled: rd?.enabled ?? true,
|
|
14696
|
+
emojis: ["\uD83D\uDC68\u200d\uD83D\uDCBB", "\uD83D\uDCCC"]
|
|
14697
|
+
};
|
|
14698
|
+
}
|
|
14699
|
+
}
|
|
14643
14700
|
if (defaults.resources || merged.resources) {
|
|
14644
14701
|
const d = defaults.resources ?? {};
|
|
14645
14702
|
const a = merged.resources ?? {};
|
|
@@ -15396,6 +15453,9 @@ function resolveCronModel(model) {
|
|
|
15396
15453
|
return isKnownCheapModel(model) ? model : DEFAULT_CRON_MODEL;
|
|
15397
15454
|
}
|
|
15398
15455
|
function resolveCronRouting(input, opts) {
|
|
15456
|
+
if ((input.kind ?? "prompt") === "action") {
|
|
15457
|
+
return { tier: "action", session: null, customModelDowngrade: false };
|
|
15458
|
+
}
|
|
15399
15459
|
if (!opts.cheapCronEnabled) {
|
|
15400
15460
|
return { tier: "main", session: "main", customModelDowngrade: false };
|
|
15401
15461
|
}
|
|
@@ -15440,9 +15500,96 @@ var init_cron_routing = __esm(() => {
|
|
|
15440
15500
|
OPUS_MODEL_RE = /opus/i;
|
|
15441
15501
|
});
|
|
15442
15502
|
|
|
15503
|
+
// src/scheduler/cron-cadence.ts
|
|
15504
|
+
function csvSmallestGap(field) {
|
|
15505
|
+
if (!field.includes(","))
|
|
15506
|
+
return null;
|
|
15507
|
+
const parts = field.split(",").map((s) => Number(s)).filter((n) => Number.isInteger(n) && n >= 0);
|
|
15508
|
+
if (parts.length < 2)
|
|
15509
|
+
return null;
|
|
15510
|
+
const sorted = [...parts].sort((a, b) => a - b);
|
|
15511
|
+
let smallest = Infinity;
|
|
15512
|
+
for (let i = 1;i < sorted.length; i++) {
|
|
15513
|
+
const gap = sorted[i] - sorted[i - 1];
|
|
15514
|
+
if (gap > 0 && gap < smallest)
|
|
15515
|
+
smallest = gap;
|
|
15516
|
+
}
|
|
15517
|
+
return Number.isFinite(smallest) ? smallest : null;
|
|
15518
|
+
}
|
|
15519
|
+
function estimateCronGapMin(expr) {
|
|
15520
|
+
const fields = expr.trim().split(/\s+/);
|
|
15521
|
+
if (fields.length < 5)
|
|
15522
|
+
return Infinity;
|
|
15523
|
+
const [min, hour] = fields;
|
|
15524
|
+
if (min === "*")
|
|
15525
|
+
return 1;
|
|
15526
|
+
const minStep = min.match(/^\*\/(\d+)$/);
|
|
15527
|
+
if (minStep) {
|
|
15528
|
+
const n = Number(minStep[1]);
|
|
15529
|
+
return n > 0 ? n : Infinity;
|
|
15530
|
+
}
|
|
15531
|
+
const minCsv = csvSmallestGap(min);
|
|
15532
|
+
if (minCsv !== null)
|
|
15533
|
+
return minCsv;
|
|
15534
|
+
if (!/^\d+$/.test(min))
|
|
15535
|
+
return Infinity;
|
|
15536
|
+
if (hour === "*")
|
|
15537
|
+
return 60;
|
|
15538
|
+
const hourStep = hour.match(/^\*\/(\d+)$/);
|
|
15539
|
+
if (hourStep) {
|
|
15540
|
+
const n = Number(hourStep[1]);
|
|
15541
|
+
return n > 0 ? n * 60 : Infinity;
|
|
15542
|
+
}
|
|
15543
|
+
const hourCsv = csvSmallestGap(hour);
|
|
15544
|
+
if (hourCsv !== null)
|
|
15545
|
+
return hourCsv * 60;
|
|
15546
|
+
if (/^\d+$/.test(hour))
|
|
15547
|
+
return 1440;
|
|
15548
|
+
return Infinity;
|
|
15549
|
+
}
|
|
15550
|
+
|
|
15443
15551
|
// src/scheduler/tier-selector.ts
|
|
15444
|
-
function
|
|
15445
|
-
|
|
15552
|
+
function resolveCronAutoTier(env2 = process.env) {
|
|
15553
|
+
const v = (env2.SWITCHROOM_CRON_AUTO_TIER ?? "").toLowerCase();
|
|
15554
|
+
return v === "1" || v === "true" || v === "on";
|
|
15555
|
+
}
|
|
15556
|
+
function recommendCronTier(input, frequentGapMin = DEFAULT_FREQUENT_GAP_MIN) {
|
|
15557
|
+
if (input.kind === "action") {
|
|
15558
|
+
return { tier: "action", source: "explicit", reason: "declared kind: action (model-free verb)" };
|
|
15559
|
+
}
|
|
15560
|
+
if (input.kind === "poll") {
|
|
15561
|
+
return { tier: "poll", source: "explicit", reason: "declared kind: poll (model-free check)" };
|
|
15562
|
+
}
|
|
15563
|
+
if (input.context === "fresh") {
|
|
15564
|
+
return { tier: "cheap", source: "explicit", reason: "declared context: fresh (cheap cron session)" };
|
|
15565
|
+
}
|
|
15566
|
+
if (input.context === "agent") {
|
|
15567
|
+
return { tier: "main", source: "explicit", reason: "declared context: agent (full live session)" };
|
|
15568
|
+
}
|
|
15569
|
+
if (input.model !== undefined) {
|
|
15570
|
+
return isKnownCheapModel(input.model) ? { tier: "cheap", source: "explicit", reason: `cheap model '${input.model}' \u2192 cheap cron session` } : { tier: "main", source: "explicit", reason: `model '${input.model}' is not a known-cheap id \u2192 full live session` };
|
|
15571
|
+
}
|
|
15572
|
+
if (input.smallestGapMin <= frequentGapMin) {
|
|
15573
|
+
return {
|
|
15574
|
+
tier: "cheap",
|
|
15575
|
+
source: "cadence-default",
|
|
15576
|
+
reason: `fires every ~${input.smallestGapMin}min (\u2264 ${frequentGapMin}min) \u2014 defaulting to a cheap ` + `session; set context: agent (or an Opus/custom model) if this needs the agent's full context`
|
|
15577
|
+
};
|
|
15578
|
+
}
|
|
15579
|
+
return {
|
|
15580
|
+
tier: "main",
|
|
15581
|
+
source: "cadence-default",
|
|
15582
|
+
reason: `fires every ~${input.smallestGapMin}min (> ${frequentGapMin}min) \u2014 defaulting to the agent's ` + `full session; set model: sonnet (or context: fresh) to run it cheaply`
|
|
15583
|
+
};
|
|
15584
|
+
}
|
|
15585
|
+
function applyDefaultTier(entry, frequentGapMin = DEFAULT_FREQUENT_GAP_MIN, autoTierEnabled = resolveCronAutoTier()) {
|
|
15586
|
+
if (!autoTierEnabled)
|
|
15587
|
+
return entry;
|
|
15588
|
+
if (entry.kind !== undefined || entry.context !== undefined || entry.model !== undefined) {
|
|
15589
|
+
return entry;
|
|
15590
|
+
}
|
|
15591
|
+
const rec = recommendCronTier({ smallestGapMin: estimateCronGapMin(entry.cron) }, frequentGapMin);
|
|
15592
|
+
return rec.tier === "cheap" ? { ...entry, context: "fresh" } : entry;
|
|
15446
15593
|
}
|
|
15447
15594
|
var DEFAULT_FREQUENT_GAP_MIN = 60;
|
|
15448
15595
|
var init_tier_selector = __esm(() => {
|
|
@@ -30758,8 +30905,74 @@ var init_doctor_webkite = __esm(() => {
|
|
|
30758
30905
|
init_loader();
|
|
30759
30906
|
});
|
|
30760
30907
|
|
|
30908
|
+
// src/cli/doctor-cron-session.ts
|
|
30909
|
+
import { statSync as realStatSync } from "node:fs";
|
|
30910
|
+
import { resolve as resolve31 } from "node:path";
|
|
30911
|
+
function agentRunsCronSession(config, agent) {
|
|
30912
|
+
const raw = config.agents[agent];
|
|
30913
|
+
if (!raw)
|
|
30914
|
+
return false;
|
|
30915
|
+
const resolved = resolveAgentConfig(config.defaults, config.profiles, raw);
|
|
30916
|
+
const entries = (resolved.schedule ?? []).map((e) => applyDefaultTier({ cron: e.cron, kind: e.kind, model: e.model, context: e.context }));
|
|
30917
|
+
return scheduleNeedsCronSession(entries, { cheapCronEnabled: true });
|
|
30918
|
+
}
|
|
30919
|
+
function runCronSessionChecks(config, deps = defaultDeps2) {
|
|
30920
|
+
const agentsDir = resolveAgentsDir(config);
|
|
30921
|
+
const results = [];
|
|
30922
|
+
for (const agent of Object.keys(config.agents).sort()) {
|
|
30923
|
+
if (!agentRunsCronSession(config, agent))
|
|
30924
|
+
continue;
|
|
30925
|
+
const name = `cron-session: ${agent}`;
|
|
30926
|
+
const alivePath = resolve31(agentsDir, agent, "telegram", ".bridge-alive-cron");
|
|
30927
|
+
let mtimeMs;
|
|
30928
|
+
try {
|
|
30929
|
+
mtimeMs = deps.statMtimeMs(alivePath);
|
|
30930
|
+
} catch (err) {
|
|
30931
|
+
const code = err.code;
|
|
30932
|
+
if (code === "EACCES" || code === "EPERM") {
|
|
30933
|
+
results.push({
|
|
30934
|
+
name,
|
|
30935
|
+
status: "skip",
|
|
30936
|
+
detail: "cron-bridge liveness file present but unreadable by the operator \u2014 re-run doctor without sudo"
|
|
30937
|
+
});
|
|
30938
|
+
continue;
|
|
30939
|
+
}
|
|
30940
|
+
results.push({
|
|
30941
|
+
name,
|
|
30942
|
+
status: "fail",
|
|
30943
|
+
detail: `<${agent}-cron> bridge not registered \u2014 no liveness file at ${alivePath}. Tier-1 cron fires are falling back to the (expensive) main session.`,
|
|
30944
|
+
fix: `Restart the agent (\`switchroom agent restart ${agent} --wait --force\`) and check /var/log/switchroom/cron-session.log for a wizard wedge.`
|
|
30945
|
+
});
|
|
30946
|
+
continue;
|
|
30947
|
+
}
|
|
30948
|
+
const ageMs = deps.now() - mtimeMs;
|
|
30949
|
+
if (ageMs <= MAX_AGE_MS) {
|
|
30950
|
+
results.push({ name, status: "ok", detail: `<${agent}-cron> bridge live (heartbeat ${Math.round(ageMs / 1000)}s ago)` });
|
|
30951
|
+
} else {
|
|
30952
|
+
results.push({
|
|
30953
|
+
name,
|
|
30954
|
+
status: "fail",
|
|
30955
|
+
detail: `<${agent}-cron> bridge heartbeat is stale (${Math.round(ageMs / 1000)}s > ${MAX_AGE_MS / 1000}s) \u2014 the cron session registered then died. Tier-1 fires are falling back to main.`,
|
|
30956
|
+
fix: `Restart the agent (\`switchroom agent restart ${agent} --wait --force\`) and check /var/log/switchroom/cron-session.log.`
|
|
30957
|
+
});
|
|
30958
|
+
}
|
|
30959
|
+
}
|
|
30960
|
+
return results;
|
|
30961
|
+
}
|
|
30962
|
+
var defaultDeps2, MAX_AGE_MS = 30000;
|
|
30963
|
+
var init_doctor_cron_session = __esm(() => {
|
|
30964
|
+
init_loader();
|
|
30965
|
+
init_merge();
|
|
30966
|
+
init_cron_routing();
|
|
30967
|
+
init_tier_selector();
|
|
30968
|
+
defaultDeps2 = {
|
|
30969
|
+
statMtimeMs: (p) => realStatSync(p).mtimeMs,
|
|
30970
|
+
now: () => Date.now()
|
|
30971
|
+
};
|
|
30972
|
+
});
|
|
30973
|
+
|
|
30761
30974
|
// src/cli/doctor-scaffold-wiring.ts
|
|
30762
|
-
import { join as join52, resolve as
|
|
30975
|
+
import { join as join52, resolve as resolve32 } from "node:path";
|
|
30763
30976
|
function readJson2(d, path4) {
|
|
30764
30977
|
if (!d.existsSync(path4))
|
|
30765
30978
|
return { kind: "absent" };
|
|
@@ -30793,7 +31006,7 @@ function checkIntegrationScaffoldWiring(args) {
|
|
|
30793
31006
|
];
|
|
30794
31007
|
}
|
|
30795
31008
|
for (const name of agents) {
|
|
30796
|
-
const agentDir =
|
|
31009
|
+
const agentDir = resolve32(agentsDir, name);
|
|
30797
31010
|
if (!deps.existsSync(agentDir)) {
|
|
30798
31011
|
results.push({
|
|
30799
31012
|
name: `${label}: ${name} scaffold`,
|
|
@@ -30833,7 +31046,7 @@ function checkIntegrationScaffoldWiring(args) {
|
|
|
30833
31046
|
let trustDetail = "no .claude/.claude.json";
|
|
30834
31047
|
if (trustRead.kind === "ok") {
|
|
30835
31048
|
const projects = trustRead.data?.projects ?? {};
|
|
30836
|
-
const proj = projects[
|
|
31049
|
+
const proj = projects[resolve32(agentDir)];
|
|
30837
31050
|
const enabled = proj?.enabledMcpjsonServers;
|
|
30838
31051
|
if (Array.isArray(enabled) && enabled.includes(mcpKey)) {
|
|
30839
31052
|
trustOk = true;
|
|
@@ -31052,7 +31265,7 @@ var init_doctor_microsoft = __esm(() => {
|
|
|
31052
31265
|
import {
|
|
31053
31266
|
existsSync as realExistsSync4,
|
|
31054
31267
|
readFileSync as realReadFileSync4,
|
|
31055
|
-
statSync as
|
|
31268
|
+
statSync as realStatSync2
|
|
31056
31269
|
} from "node:fs";
|
|
31057
31270
|
import { join as join54 } from "node:path";
|
|
31058
31271
|
import { homedir as homedir31 } from "node:os";
|
|
@@ -31061,7 +31274,7 @@ function resolveDeps4(deps) {
|
|
|
31061
31274
|
return {
|
|
31062
31275
|
existsSync: deps.existsSync ?? realExistsSync4,
|
|
31063
31276
|
readFileSync: deps.readFileSync ?? realReadFileSync4,
|
|
31064
|
-
statSync: deps.statSync ??
|
|
31277
|
+
statSync: deps.statSync ?? realStatSync2,
|
|
31065
31278
|
agentsDir: join54(home2, ".switchroom", "agents"),
|
|
31066
31279
|
now: deps.now ?? Date.now,
|
|
31067
31280
|
vaultAclReader: deps.vaultAclReader ?? (async () => ({ kind: "unreachable", msg: "no default reader wired" }))
|
|
@@ -31260,7 +31473,7 @@ var init_doctor_notion = __esm(() => {
|
|
|
31260
31473
|
import {
|
|
31261
31474
|
existsSync as realExistsSync5,
|
|
31262
31475
|
readdirSync as realReaddirSync2,
|
|
31263
|
-
statSync as
|
|
31476
|
+
statSync as realStatSync3
|
|
31264
31477
|
} from "node:fs";
|
|
31265
31478
|
import { homedir as homedir32 } from "node:os";
|
|
31266
31479
|
import { join as join55 } from "node:path";
|
|
@@ -31270,7 +31483,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
31270
31483
|
const readdirSync21 = deps.readdirSync ?? ((p) => realReaddirSync2(p));
|
|
31271
31484
|
const isDirectory = deps.isDirectory ?? ((p) => {
|
|
31272
31485
|
try {
|
|
31273
|
-
return
|
|
31486
|
+
return realStatSync3(p).isDirectory();
|
|
31274
31487
|
} catch {
|
|
31275
31488
|
return false;
|
|
31276
31489
|
}
|
|
@@ -31912,7 +32125,7 @@ import {
|
|
|
31912
32125
|
readdirSync as readdirSync21,
|
|
31913
32126
|
statSync as statSync25
|
|
31914
32127
|
} from "node:fs";
|
|
31915
|
-
import { dirname as dirname13, join as join59, resolve as
|
|
32128
|
+
import { dirname as dirname13, join as join59, resolve as resolve33 } from "node:path";
|
|
31916
32129
|
import { createPublicKey, createPrivateKey } from "node:crypto";
|
|
31917
32130
|
function findInNvm(bin) {
|
|
31918
32131
|
const nvmRoot = join59(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
@@ -32926,7 +33139,7 @@ function checkAgents(config, configPath) {
|
|
|
32926
33139
|
const statuses = getAllAgentStatuses(config);
|
|
32927
33140
|
const authStatuses = getAllAuthStatuses(config);
|
|
32928
33141
|
for (const [name, agentConfig] of Object.entries(config.agents)) {
|
|
32929
|
-
const agentDir =
|
|
33142
|
+
const agentDir = resolve33(agentsDir, name);
|
|
32930
33143
|
if (!existsSync57(agentDir)) {
|
|
32931
33144
|
results.push({
|
|
32932
33145
|
name: `${name}: scaffold`,
|
|
@@ -33109,7 +33322,7 @@ function mffAgentName(config) {
|
|
|
33109
33322
|
function mffEnvPath(config) {
|
|
33110
33323
|
const home2 = process.env.HOME ?? "/root";
|
|
33111
33324
|
const agent = mffAgentName(config);
|
|
33112
|
-
return agent ?
|
|
33325
|
+
return agent ? resolve33(home2, ".switchroom/credentials", agent, "my-family-finance/.env") : resolve33(home2, ".switchroom/credentials/my-family-finance/.env");
|
|
33113
33326
|
}
|
|
33114
33327
|
function mffEnvState(envPath) {
|
|
33115
33328
|
if (!existsSync57(envPath))
|
|
@@ -33525,14 +33738,14 @@ async function checkManifestDrift(probers) {
|
|
|
33525
33738
|
return results;
|
|
33526
33739
|
}
|
|
33527
33740
|
function runDockerSection(config) {
|
|
33528
|
-
const composePath =
|
|
33741
|
+
const composePath = resolve33(process.env.HOME ?? "", ".switchroom", "compose", "docker-compose.yml");
|
|
33529
33742
|
const active = isDockerMode({ composePath });
|
|
33530
33743
|
let composeYaml;
|
|
33531
33744
|
let dockerfileAgent;
|
|
33532
33745
|
try {
|
|
33533
33746
|
composeYaml = readFileSync51(composePath, "utf8");
|
|
33534
33747
|
} catch {}
|
|
33535
|
-
const dockerfilePath =
|
|
33748
|
+
const dockerfilePath = resolve33(process.env.HOME ?? "", ".switchroom", "docker", "Dockerfile.agent");
|
|
33536
33749
|
try {
|
|
33537
33750
|
dockerfileAgent = readFileSync51(dockerfilePath, "utf8");
|
|
33538
33751
|
} catch {}
|
|
@@ -33657,7 +33870,8 @@ function registerDoctorCommand(program3) {
|
|
|
33657
33870
|
})
|
|
33658
33871
|
},
|
|
33659
33872
|
{ title: "MFF Skill", results: await checkMff(passphrase, vaultPath, config) },
|
|
33660
|
-
{ title: "Webkite", results: runWebkiteChecks(config) }
|
|
33873
|
+
{ title: "Webkite", results: runWebkiteChecks(config) },
|
|
33874
|
+
{ title: "Cron Session", results: runCronSessionChecks(config) }
|
|
33661
33875
|
];
|
|
33662
33876
|
const cwd = process.cwd();
|
|
33663
33877
|
if (isSwitchroomCheckout(cwd)) {
|
|
@@ -33719,6 +33933,7 @@ var init_doctor = __esm(() => {
|
|
|
33719
33933
|
init_doctor_hostd();
|
|
33720
33934
|
init_doctor_drive();
|
|
33721
33935
|
init_doctor_webkite();
|
|
33936
|
+
init_doctor_cron_session();
|
|
33722
33937
|
init_doctor_microsoft();
|
|
33723
33938
|
init_doctor_notion();
|
|
33724
33939
|
init_doctor_credentials_migration();
|
|
@@ -41903,7 +42118,7 @@ class Protocol {
|
|
|
41903
42118
|
return;
|
|
41904
42119
|
}
|
|
41905
42120
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1000;
|
|
41906
|
-
await new Promise((
|
|
42121
|
+
await new Promise((resolve50) => setTimeout(resolve50, pollInterval));
|
|
41907
42122
|
options?.signal?.throwIfAborted();
|
|
41908
42123
|
}
|
|
41909
42124
|
} catch (error2) {
|
|
@@ -41915,7 +42130,7 @@ class Protocol {
|
|
|
41915
42130
|
}
|
|
41916
42131
|
request(request, resultSchema, options) {
|
|
41917
42132
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
41918
|
-
return new Promise((
|
|
42133
|
+
return new Promise((resolve50, reject) => {
|
|
41919
42134
|
const earlyReject = (error2) => {
|
|
41920
42135
|
reject(error2);
|
|
41921
42136
|
};
|
|
@@ -41993,7 +42208,7 @@ class Protocol {
|
|
|
41993
42208
|
if (!parseResult.success) {
|
|
41994
42209
|
reject(parseResult.error);
|
|
41995
42210
|
} else {
|
|
41996
|
-
|
|
42211
|
+
resolve50(parseResult.data);
|
|
41997
42212
|
}
|
|
41998
42213
|
} catch (error2) {
|
|
41999
42214
|
reject(error2);
|
|
@@ -42184,12 +42399,12 @@ class Protocol {
|
|
|
42184
42399
|
interval = task.pollInterval;
|
|
42185
42400
|
}
|
|
42186
42401
|
} catch {}
|
|
42187
|
-
return new Promise((
|
|
42402
|
+
return new Promise((resolve50, reject) => {
|
|
42188
42403
|
if (signal.aborted) {
|
|
42189
42404
|
reject(new McpError(ErrorCode2.InvalidRequest, "Request cancelled"));
|
|
42190
42405
|
return;
|
|
42191
42406
|
}
|
|
42192
|
-
const timeoutId = setTimeout(
|
|
42407
|
+
const timeoutId = setTimeout(resolve50, interval);
|
|
42193
42408
|
signal.addEventListener("abort", () => {
|
|
42194
42409
|
clearTimeout(timeoutId);
|
|
42195
42410
|
reject(new McpError(ErrorCode2.InvalidRequest, "Request cancelled"));
|
|
@@ -45174,7 +45389,7 @@ var require_compile = __commonJS((exports2) => {
|
|
|
45174
45389
|
const schOrFunc = root.refs[ref];
|
|
45175
45390
|
if (schOrFunc)
|
|
45176
45391
|
return schOrFunc;
|
|
45177
|
-
let _sch =
|
|
45392
|
+
let _sch = resolve50.call(this, root, ref);
|
|
45178
45393
|
if (_sch === undefined) {
|
|
45179
45394
|
const schema = (_a = root.localRefs) === null || _a === undefined ? undefined : _a[ref];
|
|
45180
45395
|
const { schemaId } = this.opts;
|
|
@@ -45201,7 +45416,7 @@ var require_compile = __commonJS((exports2) => {
|
|
|
45201
45416
|
function sameSchemaEnv(s1, s2) {
|
|
45202
45417
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
45203
45418
|
}
|
|
45204
|
-
function
|
|
45419
|
+
function resolve50(root, ref) {
|
|
45205
45420
|
let sch;
|
|
45206
45421
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
45207
45422
|
ref = sch;
|
|
@@ -45731,7 +45946,7 @@ var require_fast_uri = __commonJS((exports2, module) => {
|
|
|
45731
45946
|
}
|
|
45732
45947
|
return uri;
|
|
45733
45948
|
}
|
|
45734
|
-
function
|
|
45949
|
+
function resolve50(baseURI, relativeURI, options) {
|
|
45735
45950
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
45736
45951
|
const resolved = resolveComponent(parse6(baseURI, schemelessOptions), parse6(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
45737
45952
|
schemelessOptions.skipEscape = true;
|
|
@@ -45959,7 +46174,7 @@ var require_fast_uri = __commonJS((exports2, module) => {
|
|
|
45959
46174
|
var fastUri = {
|
|
45960
46175
|
SCHEMES,
|
|
45961
46176
|
normalize,
|
|
45962
|
-
resolve:
|
|
46177
|
+
resolve: resolve50,
|
|
45963
46178
|
resolveComponent,
|
|
45964
46179
|
equal,
|
|
45965
46180
|
serialize,
|
|
@@ -49342,12 +49557,12 @@ class StdioServerTransport {
|
|
|
49342
49557
|
this.onclose?.();
|
|
49343
49558
|
}
|
|
49344
49559
|
send(message) {
|
|
49345
|
-
return new Promise((
|
|
49560
|
+
return new Promise((resolve50) => {
|
|
49346
49561
|
const json = serializeMessage(message);
|
|
49347
49562
|
if (this._stdout.write(json)) {
|
|
49348
|
-
|
|
49563
|
+
resolve50();
|
|
49349
49564
|
} else {
|
|
49350
|
-
this._stdout.once("drain",
|
|
49565
|
+
this._stdout.once("drain", resolve50);
|
|
49351
49566
|
}
|
|
49352
49567
|
});
|
|
49353
49568
|
}
|
|
@@ -50252,8 +50467,8 @@ var {
|
|
|
50252
50467
|
} = import__.default;
|
|
50253
50468
|
|
|
50254
50469
|
// src/build-info.ts
|
|
50255
|
-
var VERSION = "0.15.
|
|
50256
|
-
var COMMIT_SHA = "
|
|
50470
|
+
var VERSION = "0.15.14";
|
|
50471
|
+
var COMMIT_SHA = "91ae15d3";
|
|
50257
50472
|
|
|
50258
50473
|
// src/cli/agent.ts
|
|
50259
50474
|
init_source();
|
|
@@ -50353,7 +50568,7 @@ import { createHash as createHash2 } from "node:crypto";
|
|
|
50353
50568
|
// src/agents/cron-unit-name.ts
|
|
50354
50569
|
import { createHash } from "node:crypto";
|
|
50355
50570
|
function cronUnitHash(cron, prompt) {
|
|
50356
|
-
return createHash("sha256").update(cron).update("\x00").update(prompt).digest("hex").slice(0, 12);
|
|
50571
|
+
return createHash("sha256").update(cron).update("\x00").update(prompt ?? "").digest("hex").slice(0, 12);
|
|
50357
50572
|
}
|
|
50358
50573
|
function cronUnitName(cron, prompt) {
|
|
50359
50574
|
return `cron-${cronUnitHash(cron, prompt)}`;
|
|
@@ -50902,8 +51117,8 @@ function findExistingClaudeJson() {
|
|
|
50902
51117
|
console.warn(" Alternatively, agents can be onboarded individually via `switchroom agent attach <name>`.");
|
|
50903
51118
|
return null;
|
|
50904
51119
|
}
|
|
50905
|
-
function copyOnboardingState(sourcePath, agentDir) {
|
|
50906
|
-
const claudeDir = join7(agentDir,
|
|
51120
|
+
function copyOnboardingState(sourcePath, agentDir, configDirName = ".claude") {
|
|
51121
|
+
const claudeDir = join7(agentDir, configDirName);
|
|
50907
51122
|
mkdirSync7(claudeDir, { recursive: true });
|
|
50908
51123
|
const destPath = join7(claudeDir, ".claude.json");
|
|
50909
51124
|
if (!existsSync11(destPath)) {
|
|
@@ -50979,8 +51194,8 @@ function loadUserConfig() {
|
|
|
50979
51194
|
return null;
|
|
50980
51195
|
}
|
|
50981
51196
|
}
|
|
50982
|
-
function preTrustWorkspace(agentDir) {
|
|
50983
|
-
const configPath = join7(agentDir,
|
|
51197
|
+
function preTrustWorkspace(agentDir, configDirName = ".claude") {
|
|
51198
|
+
const configPath = join7(agentDir, configDirName, ".claude.json");
|
|
50984
51199
|
if (!existsSync11(configPath)) {
|
|
50985
51200
|
return;
|
|
50986
51201
|
}
|
|
@@ -51004,8 +51219,8 @@ function preTrustWorkspace(agentDir) {
|
|
|
51004
51219
|
});
|
|
51005
51220
|
} catch {}
|
|
51006
51221
|
}
|
|
51007
|
-
function ensureMcpServersTrusted(agentDir, serverKeys) {
|
|
51008
|
-
const configPath = join7(agentDir,
|
|
51222
|
+
function ensureMcpServersTrusted(agentDir, serverKeys, configDirName = ".claude") {
|
|
51223
|
+
const configPath = join7(agentDir, configDirName, ".claude.json");
|
|
51009
51224
|
if (!existsSync11(configPath)) {
|
|
51010
51225
|
return;
|
|
51011
51226
|
}
|
|
@@ -51033,8 +51248,8 @@ function ensureMcpServersTrusted(agentDir, serverKeys) {
|
|
|
51033
51248
|
console.warn(` WARNING: could not update MCP trust allowlist in ${configPath} ` + `(${err instanceof Error ? err.message : String(err)}). ` + `Scaffolded MCP servers (gdrive, agent-config, hostd) may be ` + `silently ignored by Claude Code for this agent.`);
|
|
51034
51249
|
}
|
|
51035
51250
|
}
|
|
51036
|
-
function createMinimalClaudeConfig(agentDir) {
|
|
51037
|
-
const claudeDir = join7(agentDir,
|
|
51251
|
+
function createMinimalClaudeConfig(agentDir, configDirName = ".claude") {
|
|
51252
|
+
const claudeDir = join7(agentDir, configDirName);
|
|
51038
51253
|
mkdirSync7(claudeDir, { recursive: true });
|
|
51039
51254
|
const configPath = join7(claudeDir, ".claude.json");
|
|
51040
51255
|
if (!existsSync11(configPath)) {
|
|
@@ -51049,6 +51264,11 @@ function createMinimalClaudeConfig(agentDir) {
|
|
|
51049
51264
|
});
|
|
51050
51265
|
}
|
|
51051
51266
|
}
|
|
51267
|
+
function seedCronConfigDir(agentDir, serverKeys) {
|
|
51268
|
+
createMinimalClaudeConfig(agentDir, ".claude-cron");
|
|
51269
|
+
preTrustWorkspace(agentDir, ".claude-cron");
|
|
51270
|
+
ensureMcpServersTrusted(agentDir, serverKeys, ".claude-cron");
|
|
51271
|
+
}
|
|
51052
51272
|
|
|
51053
51273
|
// src/repos/bare-clone.ts
|
|
51054
51274
|
init_paths();
|
|
@@ -51513,20 +51733,33 @@ function buildCronSessionContext(agentConfig) {
|
|
|
51513
51733
|
cronModelQ: shellSingleQuote(DEFAULT_CRON_MODEL)
|
|
51514
51734
|
};
|
|
51515
51735
|
}
|
|
51516
|
-
function
|
|
51736
|
+
function maybeWriteCronMcp(agentDir, mcpServers, cronSessionEnabled) {
|
|
51517
51737
|
if (!cronSessionEnabled)
|
|
51518
51738
|
return null;
|
|
51519
51739
|
const telegram = mcpServers["switchroom-telegram"];
|
|
51520
51740
|
if (!telegram)
|
|
51521
51741
|
return null;
|
|
51742
|
+
const baseStateDir = telegram.env?.TELEGRAM_STATE_DIR;
|
|
51743
|
+
const cronTelegram = {
|
|
51744
|
+
...telegram,
|
|
51745
|
+
env: {
|
|
51746
|
+
...telegram.env,
|
|
51747
|
+
...baseStateDir ? { SWITCHROOM_BRIDGE_ALIVE_PATH: `${baseStateDir}/.bridge-alive-cron` } : {}
|
|
51748
|
+
}
|
|
51749
|
+
};
|
|
51750
|
+
const cronServers = {
|
|
51751
|
+
...mcpServers,
|
|
51752
|
+
"switchroom-telegram": cronTelegram
|
|
51753
|
+
};
|
|
51522
51754
|
const cronDir = join9(agentDir, ".claude-cron");
|
|
51523
51755
|
mkdirSync10(cronDir, { recursive: true });
|
|
51524
51756
|
const path = join9(cronDir, ".mcp.json");
|
|
51525
|
-
const content = JSON.stringify({ mcpServers:
|
|
51757
|
+
const content = JSON.stringify({ mcpServers: cronServers }, null, 2) + `
|
|
51526
51758
|
`;
|
|
51527
51759
|
if (!existsSync14(path) || readFileSync12(path, "utf-8") !== content) {
|
|
51528
51760
|
writeFileSync5(path, content, { encoding: "utf-8", mode: 384 });
|
|
51529
51761
|
}
|
|
51762
|
+
seedCronConfigDir(agentDir, Object.keys(cronServers));
|
|
51530
51763
|
return path;
|
|
51531
51764
|
}
|
|
51532
51765
|
function alignAgentUid(name, agentDir, uid, opts = {}) {
|
|
@@ -51719,6 +51952,11 @@ function webkiteDenyForAgent(agentConfig) {
|
|
|
51719
51952
|
}
|
|
51720
51953
|
var SWITCHROOM_DEFAULT_MAIN_MODEL = "claude-sonnet-4-6";
|
|
51721
51954
|
var SWITCHROOM_DEFAULT_THINKING_EFFORT = "low";
|
|
51955
|
+
function resolveMainModel(model) {
|
|
51956
|
+
if (model === undefined || model === "default")
|
|
51957
|
+
return SWITCHROOM_DEFAULT_MAIN_MODEL;
|
|
51958
|
+
return model;
|
|
51959
|
+
}
|
|
51722
51960
|
function dedupe2(items) {
|
|
51723
51961
|
const seen = new Set;
|
|
51724
51962
|
const out = [];
|
|
@@ -51988,6 +52226,10 @@ function channelsToEnv(agent) {
|
|
|
51988
52226
|
if (tg.clear_status_on_completion !== undefined) {
|
|
51989
52227
|
out.SWITCHROOM_TG_CLEAR_STATUS_ON_COMPLETION = tg.clear_status_on_completion ? "1" : "0";
|
|
51990
52228
|
}
|
|
52229
|
+
const linearDefaultTeam = tg?.linear_agent?.default_team_id;
|
|
52230
|
+
if (linearDefaultTeam) {
|
|
52231
|
+
out.SWITCHROOM_LINEAR_DEFAULT_TEAM_ID = linearDefaultTeam;
|
|
52232
|
+
}
|
|
51991
52233
|
return out;
|
|
51992
52234
|
}
|
|
51993
52235
|
function buildRepoEnvVars(_agentName, agentDir, agent) {
|
|
@@ -52327,6 +52569,7 @@ function buildWorkspaceContext(args) {
|
|
|
52327
52569
|
useSwitchroomPlugin: usesSwitchroomTelegramPlugin(agentConfig),
|
|
52328
52570
|
useHotReloadStable: agentConfig.channels?.telegram?.hotReloadStable === true,
|
|
52329
52571
|
telegramEnabledFlag: agentConfig.channels?.telegram?.enabled === false ? "false" : "true",
|
|
52572
|
+
linearAgentEnabled: agentConfig.channels?.telegram?.linear_agent?.enabled === true,
|
|
52330
52573
|
securityPluginDir: DOCKER_SECURITY_PLUGIN_PATH,
|
|
52331
52574
|
hindsightEnabled: hindsightAutoRecallEnabled,
|
|
52332
52575
|
hindsightBankIdQ: shellSingleQuote(hindsightBankId),
|
|
@@ -52338,7 +52581,7 @@ function buildWorkspaceContext(args) {
|
|
|
52338
52581
|
hindsightTopicFilterMode,
|
|
52339
52582
|
switchroomConfigPathQ: switchroomConfigPath ? shellSingleQuote(resolve11(switchroomConfigPath)) : undefined,
|
|
52340
52583
|
hostHomeQ: process.env.HOME ? shellSingleQuote(process.env.HOME) : undefined,
|
|
52341
|
-
modelQ: shellSingleQuote(agentConfig.model
|
|
52584
|
+
modelQ: shellSingleQuote(resolveMainModel(agentConfig.model)),
|
|
52342
52585
|
...buildCronSessionContext(agentConfig),
|
|
52343
52586
|
thinkingEffort: agentConfig.thinking_effort ?? SWITCHROOM_DEFAULT_THINKING_EFFORT,
|
|
52344
52587
|
permissionMode: agentConfig.permission_mode,
|
|
@@ -52638,7 +52881,7 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
52638
52881
|
useSwitchroomPlugin: usesSwitchroomTelegramPlugin(agentConfig),
|
|
52639
52882
|
configPath: switchroomConfigPath
|
|
52640
52883
|
});
|
|
52641
|
-
settings.model = agentConfig.model
|
|
52884
|
+
settings.model = resolveMainModel(agentConfig.model);
|
|
52642
52885
|
const mergedSettings = agentConfig.settings_raw ? deepMergeJson(settings, agentConfig.settings_raw) : settings;
|
|
52643
52886
|
if (agentConfig.settings_raw && Object.keys(agentConfig.settings_raw).length > 0) {
|
|
52644
52887
|
mergedSettings._switchroomManagedRawKeys = Object.keys(agentConfig.settings_raw);
|
|
@@ -52709,7 +52952,7 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
52709
52952
|
}
|
|
52710
52953
|
writeIfChanged(mcpJsonPath, () => JSON.stringify({ mcpServers }, null, 2) + `
|
|
52711
52954
|
`, created, skipped, 384);
|
|
52712
|
-
|
|
52955
|
+
maybeWriteCronMcp(agentDir, mcpServers, buildCronSessionContext(agentConfig).cronSessionEnabled);
|
|
52713
52956
|
mcpServerKeysToTrust = Object.keys(mcpServers);
|
|
52714
52957
|
ensureMcpServersTrusted(agentDir, mcpServerKeysToTrust);
|
|
52715
52958
|
}
|
|
@@ -53429,7 +53672,7 @@ function reconcileAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchr
|
|
|
53429
53672
|
hindsightTopicAliasesJsonQ: hindsightTopicAliasesJson ? shellSingleQuote(hindsightTopicAliasesJson) : undefined,
|
|
53430
53673
|
hindsightTopicFilterMode,
|
|
53431
53674
|
hostHomeQ: process.env.HOME ? shellSingleQuote(process.env.HOME) : undefined,
|
|
53432
|
-
modelQ: shellSingleQuote(agentConfig.model
|
|
53675
|
+
modelQ: shellSingleQuote(resolveMainModel(agentConfig.model)),
|
|
53433
53676
|
...buildCronSessionContext(agentConfig),
|
|
53434
53677
|
thinkingEffort: agentConfig.thinking_effort ?? SWITCHROOM_DEFAULT_THINKING_EFFORT,
|
|
53435
53678
|
permissionMode: agentConfig.permission_mode,
|
|
@@ -53514,7 +53757,8 @@ function reconcileAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchr
|
|
|
53514
53757
|
schedule: agentConfig.schedule,
|
|
53515
53758
|
useSwitchroomPlugin: usesSwitchroomTelegramPlugin(agentConfig),
|
|
53516
53759
|
admin: agentConfig.admin === true || agentConfig.root === true,
|
|
53517
|
-
root: agentConfig.root === true
|
|
53760
|
+
root: agentConfig.root === true,
|
|
53761
|
+
linearAgentEnabled: agentConfig.channels?.telegram?.linear_agent?.enabled === true
|
|
53518
53762
|
};
|
|
53519
53763
|
let rendered = renderTemplate(claudeMdSrc, claudeContext);
|
|
53520
53764
|
const vaultProtocol = renderVaultProtocolFragment(claudeContext);
|
|
@@ -53680,7 +53924,7 @@ ${body}
|
|
|
53680
53924
|
}
|
|
53681
53925
|
}
|
|
53682
53926
|
}
|
|
53683
|
-
settings.model = agentConfig.model
|
|
53927
|
+
settings.model = resolveMainModel(agentConfig.model);
|
|
53684
53928
|
const mergedSettings = agentConfig.settings_raw ? deepMergeJson(settings, agentConfig.settings_raw) : settings;
|
|
53685
53929
|
if (agentConfig.settings_raw && Object.keys(agentConfig.settings_raw).length > 0) {
|
|
53686
53930
|
mergedSettings[META_KEY] = Object.keys(agentConfig.settings_raw);
|
|
@@ -53783,9 +54027,9 @@ ${body}
|
|
|
53783
54027
|
writeFileSync5(mcpJsonPath, after, { encoding: "utf-8", mode: 384 });
|
|
53784
54028
|
changes.push(mcpJsonPath);
|
|
53785
54029
|
}
|
|
53786
|
-
const
|
|
53787
|
-
if (
|
|
53788
|
-
changes.push(
|
|
54030
|
+
const cronMcp = maybeWriteCronMcp(agentDir, mcpServers, buildCronSessionContext(agentConfig).cronSessionEnabled);
|
|
54031
|
+
if (cronMcp)
|
|
54032
|
+
changes.push(cronMcp);
|
|
53789
54033
|
ensureMcpServersTrusted(agentDir, Object.keys(mcpServers));
|
|
53790
54034
|
}
|
|
53791
54035
|
const reconcileWorkspaceDir = join9(agentDir, "workspace");
|
|
@@ -67041,6 +67285,22 @@ function setLinearAgent(yamlText, agentName, opts) {
|
|
|
67041
67285
|
if (opts.workspaceId)
|
|
67042
67286
|
block.workspace_id = opts.workspaceId;
|
|
67043
67287
|
doc.setIn(["agents", agentName, "channels", "telegram", "linear_agent"], block);
|
|
67288
|
+
doc.setIn(["agents", agentName, "channels", "telegram", "webhook_via_gateway"], true);
|
|
67289
|
+
return String(doc);
|
|
67290
|
+
}
|
|
67291
|
+
function setLinearDefaultTeam(yamlText, agentName, teamId) {
|
|
67292
|
+
const doc = import_yaml11.parseDocument(yamlText);
|
|
67293
|
+
ensureAgent(doc, agentName);
|
|
67294
|
+
if (!doc.hasIn(["agents", agentName, "channels", "telegram", "linear_agent"])) {
|
|
67295
|
+
throw new Error(`agent '${agentName}' has no linear_agent block. Run 'switchroom linear-agent setup --agent ${agentName} --token <token>' first.`);
|
|
67296
|
+
}
|
|
67297
|
+
const path4 = ["agents", agentName, "channels", "telegram", "linear_agent", "default_team_id"];
|
|
67298
|
+
if (teamId === null) {
|
|
67299
|
+
if (doc.hasIn(path4))
|
|
67300
|
+
doc.deleteIn(path4);
|
|
67301
|
+
} else {
|
|
67302
|
+
doc.setIn(path4, teamId);
|
|
67303
|
+
}
|
|
67044
67304
|
return String(doc);
|
|
67045
67305
|
}
|
|
67046
67306
|
function setTelegramFeature(yamlText, agentName, feature, value) {
|
|
@@ -67642,6 +67902,29 @@ function registerLinearAgentCommand(program3) {
|
|
|
67642
67902
|
}
|
|
67643
67903
|
printLinearInstructions(opts, vaultKey);
|
|
67644
67904
|
}));
|
|
67905
|
+
linear.command("set-team").description("Set (or clear) the default Linear team captured issues file into for <agent>. Only needed when the workspace has multiple teams \u2014 a single-team workspace auto-resolves. Pass --clear to remove the default.").requiredOption("--agent <name>", "Agent name (must have a linear_agent block)").option("--team <id>", "Linear team id new captured issues default to.").option("--clear", "Remove the configured default team (revert to auto-resolve).").action(withConfigError(async (opts) => {
|
|
67906
|
+
if (!/^[a-z][a-z0-9_-]{0,63}$/.test(opts.agent)) {
|
|
67907
|
+
fail2(`--agent must be a lowercase agent slug (got '${opts.agent}').`);
|
|
67908
|
+
}
|
|
67909
|
+
if (!opts.clear && (!opts.team || opts.team.trim().length === 0)) {
|
|
67910
|
+
fail2("pass either --team <id> or --clear.");
|
|
67911
|
+
}
|
|
67912
|
+
const path4 = getConfigPath(program3);
|
|
67913
|
+
const before = readFileSync39(path4, "utf-8");
|
|
67914
|
+
let after;
|
|
67915
|
+
try {
|
|
67916
|
+
after = setLinearDefaultTeam(before, opts.agent, opts.clear ? null : opts.team.trim());
|
|
67917
|
+
} catch (err) {
|
|
67918
|
+
fail2(err.message);
|
|
67919
|
+
}
|
|
67920
|
+
writeFileSync22(path4, after, "utf-8");
|
|
67921
|
+
if (opts.clear) {
|
|
67922
|
+
console.log(source_default.green(`\u2713 Cleared default Linear team for '${opts.agent}' (auto-resolve).`));
|
|
67923
|
+
} else {
|
|
67924
|
+
console.log(source_default.green(`\u2713 Default Linear team for '${opts.agent}' set to ${opts.team.trim()}.`));
|
|
67925
|
+
}
|
|
67926
|
+
console.log(source_default.gray(` Run 'switchroom agent restart ${opts.agent}' to pick up the change.`));
|
|
67927
|
+
}));
|
|
67645
67928
|
}
|
|
67646
67929
|
function printLinearInstructions(opts, vaultKey) {
|
|
67647
67930
|
const base = opts.webhookBase ?? "https://<your-switchroom-web-host>";
|
|
@@ -68313,17 +68596,19 @@ function collectScheduleEntries(config) {
|
|
|
68313
68596
|
const schedule = resolved.schedule ?? [];
|
|
68314
68597
|
for (let i = 0;i < schedule.length; i++) {
|
|
68315
68598
|
const entry = schedule[i];
|
|
68599
|
+
const auditMaterial = entry.prompt ?? `action:${JSON.stringify(entry.action ?? {})}`;
|
|
68316
68600
|
out.push({
|
|
68317
68601
|
agent,
|
|
68318
68602
|
scheduleIndex: i,
|
|
68319
68603
|
cron: entry.cron,
|
|
68320
|
-
prompt: entry.prompt,
|
|
68321
|
-
promptKey: createHash9("sha256").update(
|
|
68604
|
+
...entry.prompt !== undefined ? { prompt: entry.prompt } : {},
|
|
68605
|
+
promptKey: createHash9("sha256").update(auditMaterial).digest("hex").slice(0, 12),
|
|
68322
68606
|
...entry.topic !== undefined ? { topic: entry.topic } : {},
|
|
68323
68607
|
...entry.kind !== undefined ? { kind: entry.kind } : {},
|
|
68324
68608
|
...entry.model !== undefined ? { model: entry.model } : {},
|
|
68325
68609
|
...entry.context !== undefined ? { context: entry.context } : {},
|
|
68326
|
-
...entry.poll !== undefined ? { poll: entry.poll } : {}
|
|
68610
|
+
...entry.poll !== undefined ? { poll: entry.poll } : {},
|
|
68611
|
+
...entry.action !== undefined ? { action: entry.action } : {}
|
|
68327
68612
|
});
|
|
68328
68613
|
}
|
|
68329
68614
|
}
|
|
@@ -76040,7 +76325,7 @@ init_loader();
|
|
|
76040
76325
|
init_lifecycle();
|
|
76041
76326
|
import { cpSync as cpSync2, existsSync as existsSync58, mkdirSync as mkdirSync32, readFileSync as readFileSync52, realpathSync as realpathSync6, rmSync as rmSync12, statSync as statSync26 } from "node:fs";
|
|
76042
76327
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
76043
|
-
import { join as join60, dirname as dirname14, resolve as
|
|
76328
|
+
import { join as join60, dirname as dirname14, resolve as resolve34 } from "node:path";
|
|
76044
76329
|
import { homedir as homedir36 } from "node:os";
|
|
76045
76330
|
var DEFAULT_COMPOSE_PATH = join60(homedir36(), ".switchroom", "compose", "docker-compose.yml");
|
|
76046
76331
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
@@ -76198,7 +76483,7 @@ function planUpdate(opts) {
|
|
|
76198
76483
|
opts.syncBundledSkillsFn();
|
|
76199
76484
|
return;
|
|
76200
76485
|
}
|
|
76201
|
-
const source =
|
|
76486
|
+
const source = resolve34(import.meta.dirname, "../../skills");
|
|
76202
76487
|
const dest = join60(homedir36(), ".switchroom", "skills", "_bundled");
|
|
76203
76488
|
if (!existsSync58(source)) {
|
|
76204
76489
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
@@ -76525,7 +76810,7 @@ init_source();
|
|
|
76525
76810
|
init_helpers();
|
|
76526
76811
|
init_loader();
|
|
76527
76812
|
init_lifecycle();
|
|
76528
|
-
import { resolve as
|
|
76813
|
+
import { resolve as resolve35 } from "node:path";
|
|
76529
76814
|
|
|
76530
76815
|
// src/cli/version.ts
|
|
76531
76816
|
init_source();
|
|
@@ -76680,7 +76965,7 @@ function registerRestartCommand(program3) {
|
|
|
76680
76965
|
}
|
|
76681
76966
|
const didRestart = res.restarted || !graceful;
|
|
76682
76967
|
if (didRestart) {
|
|
76683
|
-
const agentDir =
|
|
76968
|
+
const agentDir = resolve35(agentsDir, name);
|
|
76684
76969
|
const converged = waitForAuthConverge(name, agentDir);
|
|
76685
76970
|
if (!converged) {
|
|
76686
76971
|
console.log(source_default.yellow(` ${name}: agent is up but auth status didn't converge in 30s \u2014 check logs`));
|
|
@@ -76761,7 +77046,7 @@ Dependency manifest`));
|
|
|
76761
77046
|
// src/cli/handoff.ts
|
|
76762
77047
|
init_helpers();
|
|
76763
77048
|
init_loader();
|
|
76764
|
-
import { resolve as
|
|
77049
|
+
import { resolve as resolve36 } from "node:path";
|
|
76765
77050
|
function registerHandoffCommand(program3) {
|
|
76766
77051
|
program3.command("handoff <agent>", { hidden: true }).description("Build the agent's session handoff sidecars \u2014 a transcript-tail " + "briefing (.handoff.md) and topic line (.handoff-topic). " + "[internal \u2014 used by the Stop hook]").option("--max-turns <n>", "Max turns kept in the handoff transcript tail", String(DEFAULT_MAX_TURNS)).action(withConfigError(async (agentName, opts) => {
|
|
76767
77052
|
let agentConfig;
|
|
@@ -76775,7 +77060,7 @@ function registerHandoffCommand(program3) {
|
|
|
76775
77060
|
return;
|
|
76776
77061
|
}
|
|
76777
77062
|
const agentsDir = resolveAgentsDir(config);
|
|
76778
|
-
agentDir =
|
|
77063
|
+
agentDir = resolve36(agentsDir, agentName);
|
|
76779
77064
|
} catch (err) {
|
|
76780
77065
|
if (!(err instanceof ConfigError))
|
|
76781
77066
|
throw err;
|
|
@@ -76790,7 +77075,7 @@ function registerHandoffCommand(program3) {
|
|
|
76790
77075
|
`);
|
|
76791
77076
|
return;
|
|
76792
77077
|
}
|
|
76793
|
-
const claudeConfigDir =
|
|
77078
|
+
const claudeConfigDir = resolve36(agentDir, ".claude");
|
|
76794
77079
|
const jsonl = findLatestSessionJsonl(claudeConfigDir);
|
|
76795
77080
|
if (!jsonl) {
|
|
76796
77081
|
process.stderr.write(`handoff: no session JSONL under ${claudeConfigDir}/projects; skipping
|
|
@@ -77299,7 +77584,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
77299
77584
|
return result;
|
|
77300
77585
|
});
|
|
77301
77586
|
}
|
|
77302
|
-
function
|
|
77587
|
+
function resolve37(stateDir, fingerprint, nowFn = Date.now) {
|
|
77303
77588
|
if (!existsSync60(join62(stateDir, ISSUES_FILE)))
|
|
77304
77589
|
return 0;
|
|
77305
77590
|
return withLock(stateDir, () => {
|
|
@@ -77587,11 +77872,11 @@ function registerIssuesCommand(program3) {
|
|
|
77587
77872
|
const stateDir = resolveStateDir2(opts);
|
|
77588
77873
|
let flipped;
|
|
77589
77874
|
if (opts.source && opts.code) {
|
|
77590
|
-
flipped =
|
|
77875
|
+
flipped = resolve37(stateDir, computeFingerprint(opts.source, opts.code));
|
|
77591
77876
|
} else if (opts.source && !fingerprint) {
|
|
77592
77877
|
flipped = resolveAllBySource(stateDir, opts.source);
|
|
77593
77878
|
} else if (fingerprint) {
|
|
77594
|
-
flipped =
|
|
77879
|
+
flipped = resolve37(stateDir, fingerprint);
|
|
77595
77880
|
} else {
|
|
77596
77881
|
process.stderr.write(`issues resolve: need either <fingerprint>, --source, or --source + --code
|
|
77597
77882
|
`);
|
|
@@ -77688,7 +77973,7 @@ function relTime(deltaMs) {
|
|
|
77688
77973
|
init_source();
|
|
77689
77974
|
import { existsSync as existsSync63 } from "node:fs";
|
|
77690
77975
|
import { homedir as homedir39 } from "node:os";
|
|
77691
|
-
import { join as join65, resolve as
|
|
77976
|
+
import { join as join65, resolve as resolve38 } from "node:path";
|
|
77692
77977
|
|
|
77693
77978
|
// src/deps/python.ts
|
|
77694
77979
|
import { createHash as createHash11 } from "node:crypto";
|
|
@@ -77895,7 +78180,7 @@ function ensureNodeEnv(opts) {
|
|
|
77895
78180
|
|
|
77896
78181
|
// src/cli/deps.ts
|
|
77897
78182
|
function builtinSkillsRoot() {
|
|
77898
|
-
return
|
|
78183
|
+
return resolve38(homedir39(), ".switchroom/skills/_bundled");
|
|
77899
78184
|
}
|
|
77900
78185
|
function registerDepsCommand(program3) {
|
|
77901
78186
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
@@ -77974,7 +78259,7 @@ function registerDepsCommand(program3) {
|
|
|
77974
78259
|
init_helpers();
|
|
77975
78260
|
init_loader();
|
|
77976
78261
|
import { existsSync as existsSync64 } from "node:fs";
|
|
77977
|
-
import { resolve as
|
|
78262
|
+
import { resolve as resolve39, sep as sep3 } from "node:path";
|
|
77978
78263
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
77979
78264
|
|
|
77980
78265
|
// src/agents/workspace.ts
|
|
@@ -78680,8 +78965,8 @@ function registerWorkspaceCommand(program3) {
|
|
|
78680
78965
|
const dir = resolveAgentWorkspaceDirOrExit(program3, agentName);
|
|
78681
78966
|
if (!dir)
|
|
78682
78967
|
return;
|
|
78683
|
-
const resolvedWorkspace =
|
|
78684
|
-
const target =
|
|
78968
|
+
const resolvedWorkspace = resolve39(dir);
|
|
78969
|
+
const target = resolve39(resolvedWorkspace, file ?? "AGENTS.md");
|
|
78685
78970
|
if (!isInsideWorkspace(resolvedWorkspace, target)) {
|
|
78686
78971
|
process.stderr.write(`workspace edit: refusing path traversal outside workspace dir (${target})
|
|
78687
78972
|
`);
|
|
@@ -78749,7 +79034,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
78749
79034
|
const dir = resolveAgentWorkspaceDirOrExit(program3, agentName);
|
|
78750
79035
|
if (!dir)
|
|
78751
79036
|
return;
|
|
78752
|
-
const gitDir =
|
|
79037
|
+
const gitDir = resolve39(dir, ".git");
|
|
78753
79038
|
if (!existsSync64(gitDir)) {
|
|
78754
79039
|
process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
|
|
78755
79040
|
`);
|
|
@@ -78803,7 +79088,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
78803
79088
|
const dir = resolveAgentWorkspaceDirOrExit(program3, agentName);
|
|
78804
79089
|
if (!dir)
|
|
78805
79090
|
return;
|
|
78806
|
-
const gitDir =
|
|
79091
|
+
const gitDir = resolve39(dir, ".git");
|
|
78807
79092
|
if (!existsSync64(gitDir)) {
|
|
78808
79093
|
process.stdout.write(`Workspace is not a git repository.
|
|
78809
79094
|
`);
|
|
@@ -78827,7 +79112,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
|
|
|
78827
79112
|
return;
|
|
78828
79113
|
}
|
|
78829
79114
|
const agentsDir = resolveAgentsDir(config);
|
|
78830
|
-
const agentDir =
|
|
79115
|
+
const agentDir = resolve39(agentsDir, agentName);
|
|
78831
79116
|
const dir = resolveAgentWorkspaceDir(agentDir);
|
|
78832
79117
|
if (!existsSync64(dir)) {
|
|
78833
79118
|
process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
|
|
@@ -78866,7 +79151,7 @@ init_helpers();
|
|
|
78866
79151
|
init_loader();
|
|
78867
79152
|
init_merge();
|
|
78868
79153
|
import { copyFileSync as copyFileSync10, existsSync as existsSync65, readFileSync as readFileSync57, writeFileSync as writeFileSync31 } from "node:fs";
|
|
78869
|
-
import { join as join66, resolve as
|
|
79154
|
+
import { join as join66, resolve as resolve40 } from "node:path";
|
|
78870
79155
|
init_schema();
|
|
78871
79156
|
function resolveSoulTargetOrExit(program3, agentName) {
|
|
78872
79157
|
const config = getConfig(program3);
|
|
@@ -78879,7 +79164,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
78879
79164
|
const profileName = merged.extends ?? DEFAULT_PROFILE;
|
|
78880
79165
|
const profilePath = getProfilePath(profileName);
|
|
78881
79166
|
const agentsDir = resolveAgentsDir(config);
|
|
78882
|
-
const agentDir =
|
|
79167
|
+
const agentDir = resolve40(agentsDir, agentName);
|
|
78883
79168
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
78884
79169
|
if (!existsSync65(workspaceDir)) {
|
|
78885
79170
|
console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
|
|
@@ -78957,7 +79242,7 @@ function registerSoulCommand(program3) {
|
|
|
78957
79242
|
init_helpers();
|
|
78958
79243
|
init_loader();
|
|
78959
79244
|
import { existsSync as existsSync66, readFileSync as readFileSync58, readdirSync as readdirSync23, statSync as statSync28 } from "node:fs";
|
|
78960
|
-
import { resolve as
|
|
79245
|
+
import { resolve as resolve41, join as join67 } from "node:path";
|
|
78961
79246
|
import { createHash as createHash13 } from "node:crypto";
|
|
78962
79247
|
init_merge();
|
|
78963
79248
|
init_hindsight();
|
|
@@ -79055,7 +79340,7 @@ function registerDebugCommand(program3) {
|
|
|
79055
79340
|
process.exit(1);
|
|
79056
79341
|
}
|
|
79057
79342
|
const agentsDir = resolveAgentsDir(config);
|
|
79058
|
-
const agentDir =
|
|
79343
|
+
const agentDir = resolve41(agentsDir, agentName);
|
|
79059
79344
|
if (!existsSync66(agentDir)) {
|
|
79060
79345
|
console.error(`Agent directory not found: ${agentDir}`);
|
|
79061
79346
|
process.exit(1);
|
|
@@ -79232,7 +79517,7 @@ init_source();
|
|
|
79232
79517
|
// src/worktree/claim.ts
|
|
79233
79518
|
import { execFileSync as execFileSync21 } from "node:child_process";
|
|
79234
79519
|
import { closeSync as closeSync12, mkdirSync as mkdirSync37, openSync as openSync12, existsSync as existsSync68, unlinkSync as unlinkSync13 } from "node:fs";
|
|
79235
|
-
import { join as join69, resolve as
|
|
79520
|
+
import { join as join69, resolve as resolve43 } from "node:path";
|
|
79236
79521
|
import { homedir as homedir41 } from "node:os";
|
|
79237
79522
|
import { randomBytes as randomBytes13 } from "node:crypto";
|
|
79238
79523
|
|
|
@@ -79246,10 +79531,10 @@ import {
|
|
|
79246
79531
|
existsSync as existsSync67,
|
|
79247
79532
|
renameSync as renameSync13
|
|
79248
79533
|
} from "node:fs";
|
|
79249
|
-
import { join as join68, resolve as
|
|
79534
|
+
import { join as join68, resolve as resolve42 } from "node:path";
|
|
79250
79535
|
import { homedir as homedir40 } from "node:os";
|
|
79251
79536
|
function registryDir() {
|
|
79252
|
-
return
|
|
79537
|
+
return resolve42(process.env.SWITCHROOM_WORKTREE_DIR ?? join68(homedir40(), ".switchroom", "worktrees"));
|
|
79253
79538
|
}
|
|
79254
79539
|
function recordPath(id) {
|
|
79255
79540
|
return join68(registryDir(), `${id}.json`);
|
|
@@ -79330,7 +79615,7 @@ function acquireRepoLock(repoPath) {
|
|
|
79330
79615
|
}
|
|
79331
79616
|
var DEFAULT_CONCURRENCY = 5;
|
|
79332
79617
|
function worktreesBaseDir() {
|
|
79333
|
-
return
|
|
79618
|
+
return resolve43(process.env.SWITCHROOM_WORKTREE_BASE ?? join69(homedir41(), ".switchroom", "worktree-checkouts"));
|
|
79334
79619
|
}
|
|
79335
79620
|
function shortId() {
|
|
79336
79621
|
return randomBytes13(4).toString("hex");
|
|
@@ -80050,20 +80335,20 @@ function wireStdio(child) {
|
|
|
80050
80335
|
async function killChild(child, gracefulMs = 3000) {
|
|
80051
80336
|
if (child.exitCode !== null || child.signalCode !== null)
|
|
80052
80337
|
return;
|
|
80053
|
-
return new Promise((
|
|
80338
|
+
return new Promise((resolve44) => {
|
|
80054
80339
|
let killTimer = null;
|
|
80055
80340
|
const onExit = () => {
|
|
80056
80341
|
if (killTimer) {
|
|
80057
80342
|
clearTimeout(killTimer);
|
|
80058
80343
|
killTimer = null;
|
|
80059
80344
|
}
|
|
80060
|
-
|
|
80345
|
+
resolve44();
|
|
80061
80346
|
};
|
|
80062
80347
|
child.once("exit", onExit);
|
|
80063
80348
|
try {
|
|
80064
80349
|
child.kill("SIGTERM");
|
|
80065
80350
|
} catch {
|
|
80066
|
-
|
|
80351
|
+
resolve44();
|
|
80067
80352
|
return;
|
|
80068
80353
|
}
|
|
80069
80354
|
killTimer = setTimeout(() => {
|
|
@@ -80177,8 +80462,8 @@ async function runMs365McpLauncher(opts, rt) {
|
|
|
80177
80462
|
};
|
|
80178
80463
|
process.on("SIGINT", onSignal);
|
|
80179
80464
|
process.on("SIGTERM", onSignal);
|
|
80180
|
-
return new Promise((
|
|
80181
|
-
resolveLauncher =
|
|
80465
|
+
return new Promise((resolve44) => {
|
|
80466
|
+
resolveLauncher = resolve44;
|
|
80182
80467
|
});
|
|
80183
80468
|
}
|
|
80184
80469
|
function registerM365McpLauncherCommand(program3) {
|
|
@@ -80280,23 +80565,23 @@ async function runNotionMcpLauncher(opts, runtime) {
|
|
|
80280
80565
|
};
|
|
80281
80566
|
process.on("SIGTERM", () => forward("SIGTERM"));
|
|
80282
80567
|
process.on("SIGINT", () => forward("SIGINT"));
|
|
80283
|
-
const exitCode = await new Promise((
|
|
80568
|
+
const exitCode = await new Promise((resolve44) => {
|
|
80284
80569
|
child.once("exit", (code, signal) => {
|
|
80285
80570
|
clearTimer(heartbeatHandle);
|
|
80286
80571
|
if (typeof code === "number") {
|
|
80287
|
-
|
|
80572
|
+
resolve44(code);
|
|
80288
80573
|
} else if (signal) {
|
|
80289
80574
|
const sigCode = { SIGTERM: 15, SIGINT: 2, SIGKILL: 9 }[signal] ?? 1;
|
|
80290
|
-
|
|
80575
|
+
resolve44(128 + sigCode);
|
|
80291
80576
|
} else {
|
|
80292
|
-
|
|
80577
|
+
resolve44(1);
|
|
80293
80578
|
}
|
|
80294
80579
|
});
|
|
80295
80580
|
child.once("error", (err) => {
|
|
80296
80581
|
clearTimer(heartbeatHandle);
|
|
80297
80582
|
process.stderr.write(`notion-mcp-launcher: child spawn error: ${err.message}
|
|
80298
80583
|
`);
|
|
80299
|
-
|
|
80584
|
+
resolve44(1);
|
|
80300
80585
|
});
|
|
80301
80586
|
});
|
|
80302
80587
|
return exitCode;
|
|
@@ -81354,7 +81639,7 @@ agents:
|
|
|
81354
81639
|
|
|
81355
81640
|
// src/cli/apply.ts
|
|
81356
81641
|
init_resolver();
|
|
81357
|
-
import { dirname as dirname22, join as join74, resolve as
|
|
81642
|
+
import { dirname as dirname22, join as join74, resolve as resolve45 } from "node:path";
|
|
81358
81643
|
import { homedir as homedir43 } from "node:os";
|
|
81359
81644
|
import { execFileSync as execFileSync24 } from "node:child_process";
|
|
81360
81645
|
init_vault();
|
|
@@ -81955,7 +82240,7 @@ function copyExampleConfig2(name) {
|
|
|
81955
82240
|
if (!/^[a-z0-9_-]+$/.test(name)) {
|
|
81956
82241
|
throw new Error(`Invalid example name: ${name} (must match /^[a-z0-9_-]+$/)`);
|
|
81957
82242
|
}
|
|
81958
|
-
const dest =
|
|
82243
|
+
const dest = resolve45(process.cwd(), "switchroom.yaml");
|
|
81959
82244
|
if (existsSync74(dest)) {
|
|
81960
82245
|
console.error(source_default.yellow("switchroom.yaml already exists \u2014 skipping example copy"));
|
|
81961
82246
|
return;
|
|
@@ -81966,7 +82251,7 @@ function copyExampleConfig2(name) {
|
|
|
81966
82251
|
console.log(source_default.green(`Copied ${name}.yaml -> switchroom.yaml`));
|
|
81967
82252
|
return;
|
|
81968
82253
|
}
|
|
81969
|
-
const exampleFile =
|
|
82254
|
+
const exampleFile = resolve45(import.meta.dirname, `../../examples/${name}.yaml`);
|
|
81970
82255
|
if (!existsSync74(exampleFile)) {
|
|
81971
82256
|
throw new Error(`Example config not found: ${name}.yaml (available: ${Object.keys(EMBEDDED_EXAMPLES).join(", ")})`);
|
|
81972
82257
|
}
|
|
@@ -82544,10 +82829,10 @@ import {
|
|
|
82544
82829
|
unlinkSync as unlinkSync14,
|
|
82545
82830
|
writeSync as writeSync8
|
|
82546
82831
|
} from "node:fs";
|
|
82547
|
-
import { join as join76, resolve as
|
|
82832
|
+
import { join as join76, resolve as resolve46 } from "node:path";
|
|
82548
82833
|
var STAGING_SUBDIR = ".staging";
|
|
82549
82834
|
function overlayPathsFor(agent, opts = {}) {
|
|
82550
|
-
const base = opts.root ?
|
|
82835
|
+
const base = opts.root ? resolve46(opts.root, agent) : resolve46(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
82551
82836
|
const scheduleDir = join76(base, "schedule.d");
|
|
82552
82837
|
const scheduleStagingDir = join76(scheduleDir, STAGING_SUBDIR);
|
|
82553
82838
|
const skillsDir = join76(base, "skills.d");
|
|
@@ -82943,11 +83228,12 @@ import { existsSync as existsSync78, readFileSync as readFileSync66 } from "node
|
|
|
82943
83228
|
import { execFileSync as execFileSync25 } from "node:child_process";
|
|
82944
83229
|
|
|
82945
83230
|
// src/scheduler/schedule-report.ts
|
|
82946
|
-
var TIER_WEIGHT = { poll: 0, cheap: 1, main: 5 };
|
|
83231
|
+
var TIER_WEIGHT = { poll: 0, action: 0, cheap: 1, main: 5 };
|
|
82947
83232
|
function summarizeScheduleReport(rows, opts = {}) {
|
|
82948
83233
|
const s = {
|
|
82949
83234
|
total: 0,
|
|
82950
83235
|
pollFires: 0,
|
|
83236
|
+
actionFires: 0,
|
|
82951
83237
|
cheapFires: 0,
|
|
82952
83238
|
mainFires: 0,
|
|
82953
83239
|
errors: 0,
|
|
@@ -82962,6 +83248,8 @@ function summarizeScheduleReport(rows, opts = {}) {
|
|
|
82962
83248
|
const tier = r.tier ?? "main";
|
|
82963
83249
|
if (tier === "poll")
|
|
82964
83250
|
s.pollFires += 1;
|
|
83251
|
+
else if (tier === "action")
|
|
83252
|
+
s.actionFires += 1;
|
|
82965
83253
|
else if (tier === "cheap")
|
|
82966
83254
|
s.cheapFires += 1;
|
|
82967
83255
|
else
|
|
@@ -82993,11 +83281,12 @@ function parseScheduleJsonl(blob) {
|
|
|
82993
83281
|
}
|
|
82994
83282
|
function formatScheduleReport(agent, s) {
|
|
82995
83283
|
const modelFires = s.cheapFires + s.mainFires;
|
|
82996
|
-
const
|
|
83284
|
+
const modelFreePct = s.total > 0 ? Math.round((s.pollFires + s.actionFires) / s.total * 100) : 0;
|
|
82997
83285
|
const lines = [
|
|
82998
83286
|
`cron report \u2014 ${agent}`,
|
|
82999
83287
|
` total fires ${s.total}`,
|
|
83000
|
-
` Tier 0 poll (free) ${s.pollFires} (${
|
|
83288
|
+
` Tier 0 poll (free) ${s.pollFires} (${modelFreePct}% model-free incl. actions)`,
|
|
83289
|
+
` Tier 0 action(free)${s.actionFires}`,
|
|
83001
83290
|
` Tier 1 cheap ${s.cheapFires}`,
|
|
83002
83291
|
` Tier 2 main ${s.mainFires}`,
|
|
83003
83292
|
` model fires ${modelFires}`,
|
|
@@ -83799,7 +84088,7 @@ import {
|
|
|
83799
84088
|
writeFileSync as writeFileSync39
|
|
83800
84089
|
} from "node:fs";
|
|
83801
84090
|
import { tmpdir as tmpdir5, homedir as homedir45 } from "node:os";
|
|
83802
|
-
import { dirname as dirname23, join as join79, relative as relative2, resolve as
|
|
84091
|
+
import { dirname as dirname23, join as join79, relative as relative2, resolve as resolve47 } from "node:path";
|
|
83803
84092
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
83804
84093
|
|
|
83805
84094
|
// src/cli/skill-common.ts
|
|
@@ -83997,7 +84286,7 @@ function resolveSkillsPoolDir2(override) {
|
|
|
83997
84286
|
}
|
|
83998
84287
|
if (raw === "~")
|
|
83999
84288
|
return homedir45();
|
|
84000
|
-
return
|
|
84289
|
+
return resolve47(raw);
|
|
84001
84290
|
}
|
|
84002
84291
|
function readStdinSync() {
|
|
84003
84292
|
const chunks = [];
|
|
@@ -84281,7 +84570,7 @@ function registerSkillCommand(program3) {
|
|
|
84281
84570
|
if (opts.from === undefined) {
|
|
84282
84571
|
files = loadFromStdin();
|
|
84283
84572
|
} else {
|
|
84284
|
-
const fromPath =
|
|
84573
|
+
const fromPath = resolve47(opts.from);
|
|
84285
84574
|
if (!existsSync80(fromPath)) {
|
|
84286
84575
|
fail3(`--from path does not exist: ${opts.from}`);
|
|
84287
84576
|
}
|
|
@@ -84350,7 +84639,7 @@ import {
|
|
|
84350
84639
|
utimesSync,
|
|
84351
84640
|
writeFileSync as writeFileSync40
|
|
84352
84641
|
} from "node:fs";
|
|
84353
|
-
import { dirname as dirname24, join as join80, relative as relative3, resolve as
|
|
84642
|
+
import { dirname as dirname24, join as join80, relative as relative3, resolve as resolve48 } from "node:path";
|
|
84354
84643
|
import { homedir as homedir46, tmpdir as tmpdir6 } from "node:os";
|
|
84355
84644
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
84356
84645
|
init_helpers();
|
|
@@ -84361,7 +84650,7 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
84361
84650
|
var PERSONAL_SKILLS_SUBPATH = "personal-skills";
|
|
84362
84651
|
function resolveConfigSkillsDir(agent) {
|
|
84363
84652
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
84364
|
-
const candidate = override ?
|
|
84653
|
+
const candidate = override ? resolve48(override) : join80(homedir46(), ".switchroom-config");
|
|
84365
84654
|
if (!existsSync81(candidate))
|
|
84366
84655
|
return null;
|
|
84367
84656
|
return join80(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
|
|
@@ -84456,7 +84745,7 @@ function resolveAgent(opts) {
|
|
|
84456
84745
|
}
|
|
84457
84746
|
function resolveAgentsRoot(opts) {
|
|
84458
84747
|
if (opts.root)
|
|
84459
|
-
return
|
|
84748
|
+
return resolve48(opts.root);
|
|
84460
84749
|
return join80(homedir46(), ".switchroom", "agents");
|
|
84461
84750
|
}
|
|
84462
84751
|
function personalSkillDir(agentsRoot, agent, name) {
|
|
@@ -84486,7 +84775,7 @@ function readStdinSync2() {
|
|
|
84486
84775
|
return Buffer.concat(chunks).toString("utf-8");
|
|
84487
84776
|
}
|
|
84488
84777
|
function loadFromDir2(dir) {
|
|
84489
|
-
const abs =
|
|
84778
|
+
const abs = resolve48(dir);
|
|
84490
84779
|
if (!statSync32(abs).isDirectory()) {
|
|
84491
84780
|
fail4(`--from path is not a directory: ${dir}`);
|
|
84492
84781
|
}
|
|
@@ -84680,7 +84969,7 @@ function loadFiles(opts) {
|
|
|
84680
84969
|
if (opts.from === undefined) {
|
|
84681
84970
|
return loadFromStdin2();
|
|
84682
84971
|
}
|
|
84683
|
-
const p =
|
|
84972
|
+
const p = resolve48(opts.from);
|
|
84684
84973
|
if (!existsSync81(p)) {
|
|
84685
84974
|
fail4(`--from path does not exist: ${opts.from}`);
|
|
84686
84975
|
}
|
|
@@ -84949,18 +85238,18 @@ init_helpers();
|
|
|
84949
85238
|
var import_yaml23 = __toESM(require_dist(), 1);
|
|
84950
85239
|
import { existsSync as existsSync82, readdirSync as readdirSync32, readFileSync as readFileSync69, statSync as statSync33 } from "node:fs";
|
|
84951
85240
|
import { homedir as homedir47 } from "node:os";
|
|
84952
|
-
import { join as join81, resolve as
|
|
85241
|
+
import { join as join81, resolve as resolve49 } from "node:path";
|
|
84953
85242
|
var PERSONAL_PREFIX2 = "personal-";
|
|
84954
85243
|
var BUNDLED_SUBDIR = "_bundled";
|
|
84955
85244
|
var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
84956
85245
|
function defaultAgentsRoot() {
|
|
84957
|
-
return
|
|
85246
|
+
return resolve49(homedir47(), ".switchroom/agents");
|
|
84958
85247
|
}
|
|
84959
85248
|
function defaultSharedRoot2() {
|
|
84960
|
-
return
|
|
85249
|
+
return resolve49(homedir47(), ".switchroom/skills");
|
|
84961
85250
|
}
|
|
84962
85251
|
function defaultBundledRoot2() {
|
|
84963
|
-
return
|
|
85252
|
+
return resolve49(homedir47(), ".switchroom/skills/_bundled");
|
|
84964
85253
|
}
|
|
84965
85254
|
function readSkillFrontmatter(skillDir) {
|
|
84966
85255
|
const mdPath = join81(skillDir, "SKILL.md");
|