switchroom 0.15.10 → 0.15.12
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 +7 -80
- package/dist/auth-broker/index.js +5 -0
- package/dist/cli/notion-write-pretool.mjs +5 -0
- package/dist/cli/switchroom.js +292 -261
- package/dist/host-control/main.js +5 -0
- package/dist/vault/approvals/kernel-server.js +5 -0
- package/dist/vault/broker/server.js +5 -0
- package/package.json +1 -1
- package/profiles/_base/cron-session.sh.hbs +10 -6
- package/telegram-plugin/bridge/bridge.ts +24 -0
- package/telegram-plugin/dist/bridge/bridge.js +23 -0
- package/telegram-plugin/dist/gateway/gateway.js +266 -23
- package/telegram-plugin/dist/server.js +23 -0
- package/telegram-plugin/gateway/gateway.ts +100 -24
- package/telegram-plugin/gateway/linear-activity.ts +160 -0
- package/telegram-plugin/gateway/model-command.ts +13 -5
- package/telegram-plugin/gateway/obligation-ledger.ts +56 -15
- package/telegram-plugin/history.ts +57 -0
- package/telegram-plugin/tests/gateway-request-secret.test.ts +1 -1
- package/telegram-plugin/tests/history.test.ts +83 -0
- package/telegram-plugin/tests/linear-agent-activity.test.ts +124 -0
- package/telegram-plugin/tests/model-command.test.ts +40 -0
- package/telegram-plugin/tests/obligation-ledger.test.ts +213 -5
- package/telegram-plugin/tests/obligation-store.test.ts +17 -0
package/dist/cli/switchroom.js
CHANGED
|
@@ -13711,6 +13711,11 @@ var init_schema = __esm(() => {
|
|
|
13711
13711
|
}).optional().describe("Per-source rate limit for the webhook ingest path (#714). " + "Off by default \u2014 when this key is absent the handler skips " + "rate-limit checks entirely. Opt in by setting `rpm` to an " + "integer requests-per-minute (token bucket per (agent, source); " + "burst equal to rpm). When enabled, exceeding the limit returns " + "429 with Retry-After header; first throttle event per " + "(agent, source) per 60s window is written to " + "<agent>/telegram/issues.jsonl. " + "Cascades from defaults.channels.telegram.webhook_rate_limit."),
|
|
13712
13712
|
webhook_via_gateway: exports_external.boolean().optional().describe("Route verified webhook events to the agent's in-container gateway " + "over a peercred-gated UDS (<agent>/telegram/webhook.sock) instead " + "of having the host-side web receiver write the agent dir directly. " + "Required under the Docker runtime: the receiver runs as the host " + "operator UID and cannot write the per-agent-UID-owned agent dir " + "(EACCES 500) nor connect the gateway socket. When true the gateway " + "(running as the agent UID) becomes the sole writer of " + "webhook-events.jsonl + dedup/cooldown state and also fires " + "webhook_dispatch. Off by default for back-compat with host-runtime " + "installs. See docs/rfcs/webhook-via-gateway-socket.md."),
|
|
13713
13713
|
webhook_require_edge: exports_external.boolean().optional().describe("Cloudflare-only edge lock: require the X-Switchroom-Edge header " + "(injected by a Cloudflare Transform Rule on hooks.switchroom.ai) to " + "match the operator's edge secret at ~/.switchroom/webhook-edge-secret " + "before any HMAC verification; reject 403 otherwise. Proves the " + "request entered through our Cloudflare edge \u2014 the per-agent HMAC " + "alone can't (it proves body provenance, not network path). Stacks " + "on the GitHub-IP WAF + per-agent HMAC. Fail-closed: when required " + "but the secret file is missing/empty every request is rejected. Off " + "by default. See docs/rfcs/webhook-cloudflare-edge-lock.md."),
|
|
13714
|
+
linear_agent: exports_external.object({
|
|
13715
|
+
enabled: exports_external.boolean(),
|
|
13716
|
+
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.")
|
|
13718
|
+
}).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."),
|
|
13714
13719
|
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."),
|
|
13715
13720
|
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`."),
|
|
13716
13721
|
topic_aliases: exports_external.record(exports_external.string(), exports_external.number().int().positive()).optional().describe("Operator-friendly names for forum topic IDs (e.g. " + "`{ general: 1, planning: 17, cron: 23, admin: 31, alerts: 41 }`). " + "Referenced from per-cron `topic:` fields and the outbound router " + "for autonomous events (boot \u2192 alerts, hostd \u2192 admin, etc.). " + "Cascades per-key through defaults \u2192 profile \u2192 agent.")
|
|
@@ -15435,87 +15440,9 @@ var init_cron_routing = __esm(() => {
|
|
|
15435
15440
|
OPUS_MODEL_RE = /opus/i;
|
|
15436
15441
|
});
|
|
15437
15442
|
|
|
15438
|
-
// src/scheduler/cron-cadence.ts
|
|
15439
|
-
function csvSmallestGap(field) {
|
|
15440
|
-
if (!field.includes(","))
|
|
15441
|
-
return null;
|
|
15442
|
-
const parts = field.split(",").map((s) => Number(s)).filter((n) => Number.isInteger(n) && n >= 0);
|
|
15443
|
-
if (parts.length < 2)
|
|
15444
|
-
return null;
|
|
15445
|
-
const sorted = [...parts].sort((a, b) => a - b);
|
|
15446
|
-
let smallest = Infinity;
|
|
15447
|
-
for (let i = 1;i < sorted.length; i++) {
|
|
15448
|
-
const gap = sorted[i] - sorted[i - 1];
|
|
15449
|
-
if (gap > 0 && gap < smallest)
|
|
15450
|
-
smallest = gap;
|
|
15451
|
-
}
|
|
15452
|
-
return Number.isFinite(smallest) ? smallest : null;
|
|
15453
|
-
}
|
|
15454
|
-
function estimateCronGapMin(expr) {
|
|
15455
|
-
const fields = expr.trim().split(/\s+/);
|
|
15456
|
-
if (fields.length < 5)
|
|
15457
|
-
return Infinity;
|
|
15458
|
-
const [min, hour] = fields;
|
|
15459
|
-
if (min === "*")
|
|
15460
|
-
return 1;
|
|
15461
|
-
const minStep = min.match(/^\*\/(\d+)$/);
|
|
15462
|
-
if (minStep) {
|
|
15463
|
-
const n = Number(minStep[1]);
|
|
15464
|
-
return n > 0 ? n : Infinity;
|
|
15465
|
-
}
|
|
15466
|
-
const minCsv = csvSmallestGap(min);
|
|
15467
|
-
if (minCsv !== null)
|
|
15468
|
-
return minCsv;
|
|
15469
|
-
if (!/^\d+$/.test(min))
|
|
15470
|
-
return Infinity;
|
|
15471
|
-
if (hour === "*")
|
|
15472
|
-
return 60;
|
|
15473
|
-
const hourStep = hour.match(/^\*\/(\d+)$/);
|
|
15474
|
-
if (hourStep) {
|
|
15475
|
-
const n = Number(hourStep[1]);
|
|
15476
|
-
return n > 0 ? n * 60 : Infinity;
|
|
15477
|
-
}
|
|
15478
|
-
const hourCsv = csvSmallestGap(hour);
|
|
15479
|
-
if (hourCsv !== null)
|
|
15480
|
-
return hourCsv * 60;
|
|
15481
|
-
if (/^\d+$/.test(hour))
|
|
15482
|
-
return 1440;
|
|
15483
|
-
return Infinity;
|
|
15484
|
-
}
|
|
15485
|
-
|
|
15486
15443
|
// src/scheduler/tier-selector.ts
|
|
15487
|
-
function
|
|
15488
|
-
|
|
15489
|
-
return { tier: "poll", source: "explicit", reason: "declared kind: poll (model-free check)" };
|
|
15490
|
-
}
|
|
15491
|
-
if (input.context === "fresh") {
|
|
15492
|
-
return { tier: "cheap", source: "explicit", reason: "declared context: fresh (cheap cron session)" };
|
|
15493
|
-
}
|
|
15494
|
-
if (input.context === "agent") {
|
|
15495
|
-
return { tier: "main", source: "explicit", reason: "declared context: agent (full live session)" };
|
|
15496
|
-
}
|
|
15497
|
-
if (input.model !== undefined) {
|
|
15498
|
-
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` };
|
|
15499
|
-
}
|
|
15500
|
-
if (input.smallestGapMin <= frequentGapMin) {
|
|
15501
|
-
return {
|
|
15502
|
-
tier: "cheap",
|
|
15503
|
-
source: "cadence-default",
|
|
15504
|
-
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`
|
|
15505
|
-
};
|
|
15506
|
-
}
|
|
15507
|
-
return {
|
|
15508
|
-
tier: "main",
|
|
15509
|
-
source: "cadence-default",
|
|
15510
|
-
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`
|
|
15511
|
-
};
|
|
15512
|
-
}
|
|
15513
|
-
function applyDefaultTier(entry, frequentGapMin = DEFAULT_FREQUENT_GAP_MIN) {
|
|
15514
|
-
if (entry.kind === "poll" || entry.context !== undefined || entry.model !== undefined) {
|
|
15515
|
-
return entry;
|
|
15516
|
-
}
|
|
15517
|
-
const rec = recommendCronTier({ smallestGapMin: estimateCronGapMin(entry.cron) }, frequentGapMin);
|
|
15518
|
-
return rec.tier === "cheap" ? { ...entry, context: "fresh" } : entry;
|
|
15444
|
+
function applyDefaultTier(entry, _frequentGapMin = DEFAULT_FREQUENT_GAP_MIN) {
|
|
15445
|
+
return entry;
|
|
15519
15446
|
}
|
|
15520
15447
|
var DEFAULT_FREQUENT_GAP_MIN = 60;
|
|
15521
15448
|
var init_tier_selector = __esm(() => {
|
|
@@ -29244,7 +29171,7 @@ var init_thinking_effort_risk = __esm(() => {
|
|
|
29244
29171
|
// src/manifest.ts
|
|
29245
29172
|
import {
|
|
29246
29173
|
existsSync as existsSync52,
|
|
29247
|
-
readFileSync as
|
|
29174
|
+
readFileSync as readFileSync48,
|
|
29248
29175
|
readdirSync as readdirSync19
|
|
29249
29176
|
} from "node:fs";
|
|
29250
29177
|
import { dirname as dirname12, join as join47 } from "node:path";
|
|
@@ -29266,7 +29193,7 @@ function loadManifest(manifestPath) {
|
|
|
29266
29193
|
}
|
|
29267
29194
|
let raw;
|
|
29268
29195
|
try {
|
|
29269
|
-
raw =
|
|
29196
|
+
raw = readFileSync48(path4, "utf-8");
|
|
29270
29197
|
} catch (err) {
|
|
29271
29198
|
throw new Error(`Failed to read manifest at ${path4}: ${err.message}`);
|
|
29272
29199
|
}
|
|
@@ -29331,7 +29258,7 @@ function probePlaywrightMcpVersion() {
|
|
|
29331
29258
|
const pkgPath = join47(npxCache, entry, "node_modules/@playwright/mcp/package.json");
|
|
29332
29259
|
if (existsSync52(pkgPath)) {
|
|
29333
29260
|
try {
|
|
29334
|
-
const pkg = JSON.parse(
|
|
29261
|
+
const pkg = JSON.parse(readFileSync48(pkgPath, "utf-8"));
|
|
29335
29262
|
if (pkg.version)
|
|
29336
29263
|
return pkg.version;
|
|
29337
29264
|
} catch {}
|
|
@@ -29566,7 +29493,7 @@ var init_doctor_memory = __esm(() => {
|
|
|
29566
29493
|
});
|
|
29567
29494
|
|
|
29568
29495
|
// src/cli/doctor-docker.ts
|
|
29569
|
-
import { readFileSync as
|
|
29496
|
+
import { readFileSync as readFileSync49 } from "node:fs";
|
|
29570
29497
|
function imageTagOf(ref) {
|
|
29571
29498
|
if (!ref)
|
|
29572
29499
|
return "<absent>";
|
|
@@ -29581,7 +29508,7 @@ function isDockerMode(opts) {
|
|
|
29581
29508
|
return true;
|
|
29582
29509
|
if (opts?.composePath) {
|
|
29583
29510
|
try {
|
|
29584
|
-
|
|
29511
|
+
readFileSync49(opts.composePath, "utf8");
|
|
29585
29512
|
return true;
|
|
29586
29513
|
} catch {}
|
|
29587
29514
|
}
|
|
@@ -29770,7 +29697,7 @@ var init_doctor_docker = __esm(() => {
|
|
|
29770
29697
|
});
|
|
29771
29698
|
|
|
29772
29699
|
// src/cli/doctor-auth-broker.ts
|
|
29773
|
-
import { existsSync as existsSync53, readFileSync as
|
|
29700
|
+
import { existsSync as existsSync53, readFileSync as readFileSync50 } from "node:fs";
|
|
29774
29701
|
import { createHash as createHash10 } from "node:crypto";
|
|
29775
29702
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
29776
29703
|
import { homedir as homedir26 } from "node:os";
|
|
@@ -29884,7 +29811,7 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
29884
29811
|
}
|
|
29885
29812
|
let index;
|
|
29886
29813
|
try {
|
|
29887
|
-
index = JSON.parse(
|
|
29814
|
+
index = JSON.parse(readFileSync50(indexPath, "utf-8"));
|
|
29888
29815
|
} catch (err) {
|
|
29889
29816
|
return {
|
|
29890
29817
|
name: "auth-broker: drift",
|
|
@@ -29904,7 +29831,7 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
29904
29831
|
}
|
|
29905
29832
|
let got;
|
|
29906
29833
|
try {
|
|
29907
|
-
got = sha256Hex(
|
|
29834
|
+
got = sha256Hex(readFileSync50(credsPath, "utf-8"));
|
|
29908
29835
|
} catch (err) {
|
|
29909
29836
|
divergent.push(`${label} (read failed: ${err.message})`);
|
|
29910
29837
|
continue;
|
|
@@ -29945,7 +29872,7 @@ function checkAuthBrokerThresholdViolations(deps = {}) {
|
|
|
29945
29872
|
}
|
|
29946
29873
|
let violations;
|
|
29947
29874
|
try {
|
|
29948
|
-
violations = JSON.parse(
|
|
29875
|
+
violations = JSON.parse(readFileSync50(path4, "utf-8"));
|
|
29949
29876
|
} catch (err) {
|
|
29950
29877
|
return {
|
|
29951
29878
|
name: "auth-broker: threshold violations",
|
|
@@ -31981,7 +31908,7 @@ import {
|
|
|
31981
31908
|
existsSync as existsSync57,
|
|
31982
31909
|
lstatSync as lstatSync6,
|
|
31983
31910
|
mkdirSync as mkdirSync31,
|
|
31984
|
-
readFileSync as
|
|
31911
|
+
readFileSync as readFileSync51,
|
|
31985
31912
|
readdirSync as readdirSync21,
|
|
31986
31913
|
statSync as statSync25
|
|
31987
31914
|
} from "node:fs";
|
|
@@ -32741,7 +32668,7 @@ function classifyReadError(err) {
|
|
|
32741
32668
|
}
|
|
32742
32669
|
function tryReadHostFile(path4) {
|
|
32743
32670
|
try {
|
|
32744
|
-
return { kind: "ok", content:
|
|
32671
|
+
return { kind: "ok", content: readFileSync51(path4, "utf-8") };
|
|
32745
32672
|
} catch (err) {
|
|
32746
32673
|
const kind = classifyReadError(err);
|
|
32747
32674
|
const error = err?.message ?? String(err);
|
|
@@ -32757,7 +32684,7 @@ function parseEnvFile(path4) {
|
|
|
32757
32684
|
return {};
|
|
32758
32685
|
let content;
|
|
32759
32686
|
try {
|
|
32760
|
-
content =
|
|
32687
|
+
content = readFileSync51(path4, "utf-8");
|
|
32761
32688
|
} catch {
|
|
32762
32689
|
return {};
|
|
32763
32690
|
}
|
|
@@ -32874,7 +32801,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
32874
32801
|
}
|
|
32875
32802
|
let content;
|
|
32876
32803
|
try {
|
|
32877
|
-
content =
|
|
32804
|
+
content = readFileSync51(startShPath, "utf-8");
|
|
32878
32805
|
} catch (err) {
|
|
32879
32806
|
return {
|
|
32880
32807
|
name: label,
|
|
@@ -32987,7 +32914,7 @@ function isSwitchroomCheckout(dir) {
|
|
|
32987
32914
|
const pkgPath = join59(dir, "package.json");
|
|
32988
32915
|
if (!existsSync57(pkgPath))
|
|
32989
32916
|
return false;
|
|
32990
|
-
const pkg = JSON.parse(
|
|
32917
|
+
const pkg = JSON.parse(readFileSync51(pkgPath, "utf-8"));
|
|
32991
32918
|
return pkg.name === "switchroom";
|
|
32992
32919
|
} catch {
|
|
32993
32920
|
return false;
|
|
@@ -33108,7 +33035,7 @@ function checkAgents(config, configPath) {
|
|
|
33108
33035
|
});
|
|
33109
33036
|
} else {
|
|
33110
33037
|
try {
|
|
33111
|
-
const mcp = JSON.parse(
|
|
33038
|
+
const mcp = JSON.parse(readFileSync51(mcpJsonPath, "utf-8"));
|
|
33112
33039
|
const hasSwitchroomTelegram = !!mcp.mcpServers?.["switchroom-telegram"];
|
|
33113
33040
|
const memoryEnabled = isHindsightEnabled(config);
|
|
33114
33041
|
const hasHindsight = !!mcp.mcpServers?.hindsight;
|
|
@@ -33603,11 +33530,11 @@ function runDockerSection(config) {
|
|
|
33603
33530
|
let composeYaml;
|
|
33604
33531
|
let dockerfileAgent;
|
|
33605
33532
|
try {
|
|
33606
|
-
composeYaml =
|
|
33533
|
+
composeYaml = readFileSync51(composePath, "utf8");
|
|
33607
33534
|
} catch {}
|
|
33608
33535
|
const dockerfilePath = resolve32(process.env.HOME ?? "", ".switchroom", "docker", "Dockerfile.agent");
|
|
33609
33536
|
try {
|
|
33610
|
-
dockerfileAgent =
|
|
33537
|
+
dockerfileAgent = readFileSync51(dockerfilePath, "utf8");
|
|
33611
33538
|
} catch {}
|
|
33612
33539
|
return runDockerChecks({
|
|
33613
33540
|
config,
|
|
@@ -49914,7 +49841,7 @@ __export(exports_server2, {
|
|
|
49914
49841
|
TOOLS: () => TOOLS2
|
|
49915
49842
|
});
|
|
49916
49843
|
import { randomBytes as randomBytes15 } from "node:crypto";
|
|
49917
|
-
import { existsSync as existsSync83, readFileSync as
|
|
49844
|
+
import { existsSync as existsSync83, readFileSync as readFileSync70 } from "node:fs";
|
|
49918
49845
|
function selfSocketPath() {
|
|
49919
49846
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
49920
49847
|
}
|
|
@@ -50092,7 +50019,7 @@ function getLastUpdateApplyStatus() {
|
|
|
50092
50019
|
}
|
|
50093
50020
|
let raw;
|
|
50094
50021
|
try {
|
|
50095
|
-
raw =
|
|
50022
|
+
raw = readFileSync70(path8, "utf-8");
|
|
50096
50023
|
} catch (err2) {
|
|
50097
50024
|
return errorText2(`get_status: failed to read audit log at ${path8}: ${err2.message}`);
|
|
50098
50025
|
}
|
|
@@ -50325,8 +50252,8 @@ var {
|
|
|
50325
50252
|
} = import__.default;
|
|
50326
50253
|
|
|
50327
50254
|
// src/build-info.ts
|
|
50328
|
-
var VERSION = "0.15.
|
|
50329
|
-
var COMMIT_SHA = "
|
|
50255
|
+
var VERSION = "0.15.12";
|
|
50256
|
+
var COMMIT_SHA = "18b7b6e6";
|
|
50330
50257
|
|
|
50331
50258
|
// src/cli/agent.ts
|
|
50332
50259
|
init_source();
|
|
@@ -67107,6 +67034,15 @@ import { createInterface as createInterface4 } from "node:readline";
|
|
|
67107
67034
|
|
|
67108
67035
|
// src/cli/telegram-yaml.ts
|
|
67109
67036
|
var import_yaml11 = __toESM(require_dist(), 1);
|
|
67037
|
+
function setLinearAgent(yamlText, agentName, opts) {
|
|
67038
|
+
const doc = import_yaml11.parseDocument(yamlText);
|
|
67039
|
+
ensureAgent(doc, agentName);
|
|
67040
|
+
const block = { enabled: true, token: opts.token };
|
|
67041
|
+
if (opts.workspaceId)
|
|
67042
|
+
block.workspace_id = opts.workspaceId;
|
|
67043
|
+
doc.setIn(["agents", agentName, "channels", "telegram", "linear_agent"], block);
|
|
67044
|
+
return String(doc);
|
|
67045
|
+
}
|
|
67110
67046
|
function setTelegramFeature(yamlText, agentName, feature, value) {
|
|
67111
67047
|
const doc = import_yaml11.parseDocument(yamlText);
|
|
67112
67048
|
ensureAgent(doc, agentName);
|
|
@@ -67665,6 +67601,100 @@ function promptHidden2(prompt) {
|
|
|
67665
67601
|
});
|
|
67666
67602
|
}
|
|
67667
67603
|
|
|
67604
|
+
// src/cli/linear-agent.ts
|
|
67605
|
+
init_source();
|
|
67606
|
+
init_helpers();
|
|
67607
|
+
import { readFileSync as readFileSync39, writeFileSync as writeFileSync22 } from "node:fs";
|
|
67608
|
+
function registerLinearAgentCommand(program3) {
|
|
67609
|
+
const linear = program3.command("linear-agent").description("Install an agent into a Linear workspace as a first-class app actor (#2298) \u2014 @-mentionable, delegate-assignable, agent sessions wake it instantly.");
|
|
67610
|
+
linear.command("setup").description("Provision <agent> as a Linear agent. Vault-stores the Linear OAuth app token (actor=app) under 'linear/<agent>/token' and enables the linear_agent block in switchroom.yaml. The OAuth browser authorize step is printed as instructions (it can't run headless); pass the already-obtained --token.").requiredOption("--agent <name>", "Agent name (must exist in switchroom.yaml)").requiredOption("--token <token>", "The Linear OAuth app token (actor=app), obtained out-of-band via the browser authorize step. Stored in the vault, never in switchroom.yaml.").option("--client-id <id>", "Linear OAuth app client id (for the printed authorize-URL hint).").option("--client-secret <secret>", "Linear OAuth app client secret (informational \u2014 not stored by this verb).").option("--redirect-uri <uri>", "OAuth redirect URI registered on the Linear app (for the authorize-URL hint).").option("--workspace-id <id>", "Optional Linear workspace (organization) id to record in config.").option("--webhook-base <url>", "Base URL of the switchroom web server (e.g. https://hooks.switchroom.ai). Used to print the webhook URL to register in Linear. Defaults to a placeholder.").option("--dry-run", "Print the YAML diff + instructions without writing or vaulting anything").action(withConfigError(async (opts) => {
|
|
67611
|
+
if (!/^[a-z][a-z0-9_-]{0,63}$/.test(opts.agent)) {
|
|
67612
|
+
fail2(`--agent must be a lowercase agent slug (got '${opts.agent}').`);
|
|
67613
|
+
}
|
|
67614
|
+
if (!opts.token || opts.token.trim().length === 0) {
|
|
67615
|
+
fail2("--token must be a non-empty Linear app token.");
|
|
67616
|
+
}
|
|
67617
|
+
const vaultKey = `linear/${opts.agent}/token`;
|
|
67618
|
+
if (!opts.dryRun) {
|
|
67619
|
+
await vaultPut(program3, vaultKey, opts.token);
|
|
67620
|
+
} else {
|
|
67621
|
+
console.log(source_default.gray(`[dry-run] would store the Linear token in the vault as '${vaultKey}'`));
|
|
67622
|
+
}
|
|
67623
|
+
const path4 = getConfigPath(program3);
|
|
67624
|
+
const before = readFileSync39(path4, "utf-8");
|
|
67625
|
+
let after;
|
|
67626
|
+
try {
|
|
67627
|
+
after = setLinearAgent(before, opts.agent, {
|
|
67628
|
+
token: `vault:${vaultKey}`,
|
|
67629
|
+
...opts.workspaceId ? { workspaceId: opts.workspaceId } : {}
|
|
67630
|
+
});
|
|
67631
|
+
} catch (err) {
|
|
67632
|
+
fail2(err.message);
|
|
67633
|
+
}
|
|
67634
|
+
if (opts.dryRun) {
|
|
67635
|
+
console.log(source_default.bold(`[dry-run] would edit ${path4}`));
|
|
67636
|
+
console.log(makeUnifiedDiff2(before, after));
|
|
67637
|
+
} else {
|
|
67638
|
+
writeFileSync22(path4, after, "utf-8");
|
|
67639
|
+
console.log(source_default.green(`\u2713 Enabled linear-agent for agent '${opts.agent}'`));
|
|
67640
|
+
console.log(source_default.gray(` Vault key: ${vaultKey}`));
|
|
67641
|
+
console.log(source_default.gray(` Run 'switchroom agent restart ${opts.agent}' to pick up the change.`));
|
|
67642
|
+
}
|
|
67643
|
+
printLinearInstructions(opts, vaultKey);
|
|
67644
|
+
}));
|
|
67645
|
+
}
|
|
67646
|
+
function printLinearInstructions(opts, vaultKey) {
|
|
67647
|
+
const base = opts.webhookBase ?? "https://<your-switchroom-web-host>";
|
|
67648
|
+
const webhookUrl = `${base.replace(/\/$/, "")}/webhook/${opts.agent}/linear`;
|
|
67649
|
+
console.log("");
|
|
67650
|
+
console.log(source_default.bold("Next steps in Linear (browser, one-time per agent):"));
|
|
67651
|
+
console.log(source_default.gray(" 1. Create / open your Linear OAuth app with actor=app and scopes"));
|
|
67652
|
+
console.log(source_default.gray(" app:mentionable + app:assignable (https://linear.app/developers/agents)."));
|
|
67653
|
+
if (opts.clientId) {
|
|
67654
|
+
const redirect = opts.redirectUri ?? `${base.replace(/\/$/, "")}/oauth/callback`;
|
|
67655
|
+
const authorizeUrl = `https://linear.app/oauth/authorize?` + `client_id=${encodeURIComponent(opts.clientId)}` + `&redirect_uri=${encodeURIComponent(redirect)}` + `&response_type=code` + `&scope=${encodeURIComponent("read,write,app:assignable,app:mentionable")}` + `&actor=app`;
|
|
67656
|
+
console.log(source_default.gray(" 2. Authorize the app as an actor (open in a browser):"));
|
|
67657
|
+
console.log(source_default.cyan(` ${authorizeUrl}`));
|
|
67658
|
+
console.log(source_default.gray(" The redirect delivers the app token you pass to this verb via --token."));
|
|
67659
|
+
} else {
|
|
67660
|
+
console.log(source_default.gray(" 2. Authorize the app (actor=app) in a browser; capture the app token and"));
|
|
67661
|
+
console.log(source_default.gray(" re-run this verb with --token (and optionally --client-id to print the"));
|
|
67662
|
+
console.log(source_default.gray(" authorize URL)."));
|
|
67663
|
+
}
|
|
67664
|
+
console.log(source_default.gray(" 3. Register this webhook URL on the app (AgentSessionEvent + Issue/Comment):"));
|
|
67665
|
+
console.log(source_default.cyan(` ${webhookUrl}`));
|
|
67666
|
+
console.log(source_default.gray(" Use Linear's signing secret as the webhook secret \u2014 store it in the vault " + `under webhook/${opts.agent}/linear (e.g. 'switchroom telegram enable webhook ` + `--agent ${opts.agent} --source linear --secret <signing-secret>').`));
|
|
67667
|
+
console.log("");
|
|
67668
|
+
console.log(source_default.gray(` Token is read at runtime from vault:${vaultKey} (actor=app).`));
|
|
67669
|
+
}
|
|
67670
|
+
function makeUnifiedDiff2(before, after) {
|
|
67671
|
+
const a = before.split(`
|
|
67672
|
+
`);
|
|
67673
|
+
const b = after.split(`
|
|
67674
|
+
`);
|
|
67675
|
+
const out = [];
|
|
67676
|
+
let i = 0, j = 0;
|
|
67677
|
+
while (i < a.length || j < b.length) {
|
|
67678
|
+
if (i < a.length && j < b.length && a[i] === b[j]) {
|
|
67679
|
+
out.push(` ${a[i]}`);
|
|
67680
|
+
i++;
|
|
67681
|
+
j++;
|
|
67682
|
+
} else if (j < b.length && (i >= a.length || a[i] !== b[j])) {
|
|
67683
|
+
out.push(source_default.green(`+ ${b[j]}`));
|
|
67684
|
+
j++;
|
|
67685
|
+
} else {
|
|
67686
|
+
out.push(source_default.red(`- ${a[i]}`));
|
|
67687
|
+
i++;
|
|
67688
|
+
}
|
|
67689
|
+
}
|
|
67690
|
+
return out.join(`
|
|
67691
|
+
`);
|
|
67692
|
+
}
|
|
67693
|
+
function fail2(msg) {
|
|
67694
|
+
console.error(source_default.red(`Error: ${msg}`));
|
|
67695
|
+
process.exit(1);
|
|
67696
|
+
}
|
|
67697
|
+
|
|
67668
67698
|
// src/cli/memory.ts
|
|
67669
67699
|
init_source();
|
|
67670
67700
|
init_hindsight();
|
|
@@ -67934,7 +67964,7 @@ async function ensureHindsightConsumer(configPath, account, uid = HINDSIGHT_DEFA
|
|
|
67934
67964
|
// src/cli/memory.ts
|
|
67935
67965
|
init_loader();
|
|
67936
67966
|
var import_yaml12 = __toESM(require_dist(), 1);
|
|
67937
|
-
import { existsSync as existsSync44, readFileSync as
|
|
67967
|
+
import { existsSync as existsSync44, readFileSync as readFileSync40, writeFileSync as writeFileSync23 } from "node:fs";
|
|
67938
67968
|
import { join as join39 } from "node:path";
|
|
67939
67969
|
function readRecallLog(agentDir, limit) {
|
|
67940
67970
|
const path4 = join39(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
|
|
@@ -67942,7 +67972,7 @@ function readRecallLog(agentDir, limit) {
|
|
|
67942
67972
|
return [];
|
|
67943
67973
|
let raw;
|
|
67944
67974
|
try {
|
|
67945
|
-
raw =
|
|
67975
|
+
raw = readFileSync40(path4, "utf-8");
|
|
67946
67976
|
} catch {
|
|
67947
67977
|
return [];
|
|
67948
67978
|
}
|
|
@@ -68137,7 +68167,7 @@ Cross-agent reflection plan
|
|
|
68137
68167
|
const configPath = getConfigPath(program3);
|
|
68138
68168
|
try {
|
|
68139
68169
|
if (existsSync44(configPath)) {
|
|
68140
|
-
const raw =
|
|
68170
|
+
const raw = readFileSync40(configPath, "utf-8");
|
|
68141
68171
|
const doc = import_yaml12.default.parseDocument(raw);
|
|
68142
68172
|
if (!doc.has("memory")) {
|
|
68143
68173
|
doc.set("memory", { backend: "hindsight", shared_collection: "shared", config: { provider, url } });
|
|
@@ -68153,7 +68183,7 @@ Cross-agent reflection plan
|
|
|
68153
68183
|
}
|
|
68154
68184
|
}
|
|
68155
68185
|
}
|
|
68156
|
-
|
|
68186
|
+
writeFileSync23(configPath, doc.toString(), "utf-8");
|
|
68157
68187
|
console.log(source_default.gray(` Updated ${configPath} with memory.config.url = ${url}`));
|
|
68158
68188
|
console.log(source_default.gray(" Run `switchroom agent reconcile all --restart` to apply this to existing agents."));
|
|
68159
68189
|
}
|
|
@@ -68246,7 +68276,7 @@ init_loader();
|
|
|
68246
68276
|
init_client();
|
|
68247
68277
|
init_lifecycle();
|
|
68248
68278
|
import {
|
|
68249
|
-
readFileSync as
|
|
68279
|
+
readFileSync as readFileSync46,
|
|
68250
68280
|
existsSync as existsSync50,
|
|
68251
68281
|
realpathSync as realpathSync4,
|
|
68252
68282
|
mkdirSync as mkdirSync29,
|
|
@@ -68265,7 +68295,7 @@ init_lifecycle();
|
|
|
68265
68295
|
init_manager();
|
|
68266
68296
|
init_hindsight();
|
|
68267
68297
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
68268
|
-
import { existsSync as existsSync47, readFileSync as
|
|
68298
|
+
import { existsSync as existsSync47, readFileSync as readFileSync43, statSync as statSync22 } from "node:fs";
|
|
68269
68299
|
import { resolve as resolve27 } from "node:path";
|
|
68270
68300
|
init_audit_reader();
|
|
68271
68301
|
|
|
@@ -72773,8 +72803,8 @@ init_paths();
|
|
|
72773
72803
|
import {
|
|
72774
72804
|
existsSync as existsSync45,
|
|
72775
72805
|
mkdirSync as mkdirSync25,
|
|
72776
|
-
readFileSync as
|
|
72777
|
-
writeFileSync as
|
|
72806
|
+
readFileSync as readFileSync41,
|
|
72807
|
+
writeFileSync as writeFileSync24
|
|
72778
72808
|
} from "node:fs";
|
|
72779
72809
|
import { dirname as dirname9 } from "node:path";
|
|
72780
72810
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
@@ -72794,7 +72824,7 @@ function getDistinctId() {
|
|
|
72794
72824
|
const path4 = resolveStatePath("analytics-id");
|
|
72795
72825
|
try {
|
|
72796
72826
|
if (existsSync45(path4)) {
|
|
72797
|
-
const existing =
|
|
72827
|
+
const existing = readFileSync41(path4, "utf-8").trim();
|
|
72798
72828
|
if (existing) {
|
|
72799
72829
|
cachedDistinctId = existing;
|
|
72800
72830
|
return existing;
|
|
@@ -72805,7 +72835,7 @@ function getDistinctId() {
|
|
|
72805
72835
|
cachedDistinctId = id;
|
|
72806
72836
|
try {
|
|
72807
72837
|
mkdirSync25(dirname9(path4), { recursive: true });
|
|
72808
|
-
|
|
72838
|
+
writeFileSync24(path4, id, "utf-8");
|
|
72809
72839
|
} catch {}
|
|
72810
72840
|
return id;
|
|
72811
72841
|
}
|
|
@@ -72930,7 +72960,7 @@ function getAgentWorkspaceAccount(yamlText, provider, agent) {
|
|
|
72930
72960
|
// src/web/config-edit-plan.ts
|
|
72931
72961
|
init_schema();
|
|
72932
72962
|
var import_yaml14 = __toESM(require_dist(), 1);
|
|
72933
|
-
import { readFileSync as
|
|
72963
|
+
import { readFileSync as readFileSync42 } from "node:fs";
|
|
72934
72964
|
|
|
72935
72965
|
class ConfigPlanError extends Error {
|
|
72936
72966
|
}
|
|
@@ -72940,7 +72970,7 @@ function composeTransforms(...transforms) {
|
|
|
72940
72970
|
function planConfigEdit(configPath, transform) {
|
|
72941
72971
|
let before;
|
|
72942
72972
|
try {
|
|
72943
|
-
before =
|
|
72973
|
+
before = readFileSync42(configPath, "utf-8");
|
|
72944
72974
|
} catch (err) {
|
|
72945
72975
|
throw new ConfigPlanError(`could not read config: ${err.message}`);
|
|
72946
72976
|
}
|
|
@@ -72971,7 +73001,7 @@ import {
|
|
|
72971
73001
|
mkdirSync as mkdirSync26,
|
|
72972
73002
|
mkdtempSync as mkdtemp,
|
|
72973
73003
|
rmSync as rmrf,
|
|
72974
|
-
writeFileSync as
|
|
73004
|
+
writeFileSync as writeFileSync25
|
|
72975
73005
|
} from "node:fs";
|
|
72976
73006
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
72977
73007
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
@@ -72986,8 +73016,8 @@ function generateUnifiedDiff(before, after, name = "switchroom.yaml", gitBin = "
|
|
|
72986
73016
|
try {
|
|
72987
73017
|
mkdirSync26(join41(dir, "cur"), { recursive: true });
|
|
72988
73018
|
mkdirSync26(join41(dir, "new"), { recursive: true });
|
|
72989
|
-
|
|
72990
|
-
|
|
73019
|
+
writeFileSync25(join41(dir, "cur", name), before);
|
|
73020
|
+
writeFileSync25(join41(dir, "new", name), after);
|
|
72991
73021
|
const r = spawnSync4(gitBin, ["diff", "--no-index", "--no-color", "--", `cur/${name}`, `new/${name}`], { cwd: dir, encoding: "utf-8", timeout: 1e4 });
|
|
72992
73022
|
if (r.status === 0)
|
|
72993
73023
|
return "";
|
|
@@ -73624,7 +73654,7 @@ async function handleGetSystemHealth(config, home2) {
|
|
|
73624
73654
|
const logPath = defaultAuditLogPath2(home2);
|
|
73625
73655
|
if (existsSync47(logPath)) {
|
|
73626
73656
|
hostd.auditLogPresent = true;
|
|
73627
|
-
const raw =
|
|
73657
|
+
const raw = readFileSync43(logPath, "utf-8");
|
|
73628
73658
|
hostd.recent = readAndFilter(raw, {}, 10);
|
|
73629
73659
|
}
|
|
73630
73660
|
} catch (err) {
|
|
@@ -73984,7 +74014,7 @@ async function handleGetMemoryHealth(config, opts) {
|
|
|
73984
74014
|
}
|
|
73985
74015
|
|
|
73986
74016
|
// src/web/webhook-handler.ts
|
|
73987
|
-
import { appendFileSync as appendFileSync4, existsSync as existsSync49, mkdirSync as mkdirSync28, readFileSync as
|
|
74017
|
+
import { appendFileSync as appendFileSync4, existsSync as existsSync49, mkdirSync as mkdirSync28, readFileSync as readFileSync45, writeFileSync as writeFileSync26 } from "fs";
|
|
73988
74018
|
import { join as join45 } from "path";
|
|
73989
74019
|
import { homedir as homedir24 } from "os";
|
|
73990
74020
|
|
|
@@ -74191,7 +74221,7 @@ function forwardToGateway(socketPath, req, opts = {}) {
|
|
|
74191
74221
|
}
|
|
74192
74222
|
|
|
74193
74223
|
// src/web/webhook-edge.ts
|
|
74194
|
-
import { existsSync as existsSync48, readFileSync as
|
|
74224
|
+
import { existsSync as existsSync48, readFileSync as readFileSync44 } from "fs";
|
|
74195
74225
|
import { join as join44 } from "path";
|
|
74196
74226
|
import { homedir as homedir23 } from "os";
|
|
74197
74227
|
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
@@ -74204,7 +74234,7 @@ function loadEdgeSecret(path4) {
|
|
|
74204
74234
|
try {
|
|
74205
74235
|
if (!existsSync48(p))
|
|
74206
74236
|
return null;
|
|
74207
|
-
const raw =
|
|
74237
|
+
const raw = readFileSync44(p, "utf-8").trim();
|
|
74208
74238
|
return raw.length > 0 ? raw : null;
|
|
74209
74239
|
} catch {
|
|
74210
74240
|
return null;
|
|
@@ -74238,7 +74268,7 @@ function loadDedupFile(path4) {
|
|
|
74238
74268
|
try {
|
|
74239
74269
|
if (!existsSync49(path4))
|
|
74240
74270
|
return {};
|
|
74241
|
-
const raw = JSON.parse(
|
|
74271
|
+
const raw = JSON.parse(readFileSync45(path4, "utf-8"));
|
|
74242
74272
|
return typeof raw.deliveries === "object" && raw.deliveries !== null ? raw.deliveries : {};
|
|
74243
74273
|
} catch {
|
|
74244
74274
|
return {};
|
|
@@ -74252,7 +74282,7 @@ function saveDedupFile(path4, deliveries, now) {
|
|
|
74252
74282
|
}
|
|
74253
74283
|
const sorted = Object.entries(pruned).sort((a, b) => b[1] - a[1]).slice(0, DEDUP_MAX);
|
|
74254
74284
|
const final = Object.fromEntries(sorted);
|
|
74255
|
-
|
|
74285
|
+
writeFileSync26(path4, JSON.stringify({ deliveries: final }), {
|
|
74256
74286
|
mode: 384
|
|
74257
74287
|
});
|
|
74258
74288
|
}
|
|
@@ -74521,7 +74551,7 @@ function resolveWebToken() {
|
|
|
74521
74551
|
const home2 = process.env.HOME ?? homedir25();
|
|
74522
74552
|
const tokenPath = join46(home2, ".switchroom", "web-token");
|
|
74523
74553
|
if (existsSync50(tokenPath)) {
|
|
74524
|
-
const existing =
|
|
74554
|
+
const existing = readFileSync46(tokenPath, "utf8").trim();
|
|
74525
74555
|
if (existing.length > 0)
|
|
74526
74556
|
return existing;
|
|
74527
74557
|
}
|
|
@@ -74538,7 +74568,7 @@ function resolveWebToken() {
|
|
|
74538
74568
|
return token;
|
|
74539
74569
|
} catch (err) {
|
|
74540
74570
|
if (err.code === "EEXIST") {
|
|
74541
|
-
const existing =
|
|
74571
|
+
const existing = readFileSync46(tokenPath, "utf8").trim();
|
|
74542
74572
|
if (existing.length > 0)
|
|
74543
74573
|
return existing;
|
|
74544
74574
|
}
|
|
@@ -74608,7 +74638,7 @@ function loadWebhookSecrets() {
|
|
|
74608
74638
|
if (!existsSync50(path4))
|
|
74609
74639
|
return {};
|
|
74610
74640
|
try {
|
|
74611
|
-
const parsed = JSON.parse(
|
|
74641
|
+
const parsed = JSON.parse(readFileSync46(path4, "utf-8"));
|
|
74612
74642
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
74613
74643
|
} catch (err) {
|
|
74614
74644
|
process.stderr.write(`webhook-ingest: failed to parse ${path4}: ${err.message} \u2014 webhooks will return 401 until fixed
|
|
@@ -74981,7 +75011,7 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
74981
75011
|
}
|
|
74982
75012
|
const ext = extname(realFullPath);
|
|
74983
75013
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
74984
|
-
const content =
|
|
75014
|
+
const content = readFileSync46(realFullPath);
|
|
74985
75015
|
return new Response(content, {
|
|
74986
75016
|
headers: { "Content-Type": contentType }
|
|
74987
75017
|
});
|
|
@@ -75115,7 +75145,7 @@ Starting Switchroom dashboard...
|
|
|
75115
75145
|
// src/cli/setup.ts
|
|
75116
75146
|
init_source();
|
|
75117
75147
|
init_loader();
|
|
75118
|
-
import { existsSync as existsSync51, copyFileSync as copyFileSync8, readFileSync as
|
|
75148
|
+
import { existsSync as existsSync51, copyFileSync as copyFileSync8, readFileSync as readFileSync47, writeFileSync as writeFileSync27, mkdirSync as mkdirSync30 } from "node:fs";
|
|
75119
75149
|
import { resolve as resolve29, dirname as dirname11 } from "node:path";
|
|
75120
75150
|
init_vault();
|
|
75121
75151
|
init_manager();
|
|
@@ -75844,10 +75874,10 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
|
|
|
75844
75874
|
try {
|
|
75845
75875
|
const yamlPath = existsSync51(resolve29(process.cwd(), "switchroom.yaml")) ? resolve29(process.cwd(), "switchroom.yaml") : resolve29(process.cwd(), "switchroom.yml");
|
|
75846
75876
|
if (existsSync51(yamlPath)) {
|
|
75847
|
-
const content =
|
|
75877
|
+
const content = readFileSync47(yamlPath, "utf-8");
|
|
75848
75878
|
const result = insertVaultBrokerApprovalAuth(content, "telegram-id");
|
|
75849
75879
|
if (result.kind === "rewritten") {
|
|
75850
|
-
|
|
75880
|
+
writeFileSync27(yamlPath, result.content, "utf-8");
|
|
75851
75881
|
console.log(source_default.green(` ${STEP_DONE} Set vault.broker.approvalAuth: telegram-id in ${yamlPath}`));
|
|
75852
75882
|
} else if (result.kind === "already-set") {
|
|
75853
75883
|
console.log(source_default.gray(" approvalAuth already set \u2014 leaving it alone."));
|
|
@@ -75879,7 +75909,7 @@ async function stepDangerousMode(config, nonInteractive) {
|
|
|
75879
75909
|
];
|
|
75880
75910
|
for (const configPath of configPaths) {
|
|
75881
75911
|
if (existsSync51(configPath)) {
|
|
75882
|
-
let content =
|
|
75912
|
+
let content = readFileSync47(configPath, "utf-8");
|
|
75883
75913
|
const agentNames = Object.keys(config.agents);
|
|
75884
75914
|
for (const name of agentNames) {
|
|
75885
75915
|
const agentPattern = new RegExp(`(^ ${name}:\\s*\\n)`, "m");
|
|
@@ -75893,7 +75923,7 @@ async function stepDangerousMode(config, nonInteractive) {
|
|
|
75893
75923
|
}
|
|
75894
75924
|
config.agents[name].dangerous_mode = true;
|
|
75895
75925
|
}
|
|
75896
|
-
|
|
75926
|
+
writeFileSync27(configPath, content, "utf-8");
|
|
75897
75927
|
console.log(source_default.green(` ${STEP_DONE} Enabled dangerous_mode for all agents in ${configPath}`));
|
|
75898
75928
|
break;
|
|
75899
75929
|
}
|
|
@@ -76008,7 +76038,7 @@ init_doctor();
|
|
|
76008
76038
|
init_source();
|
|
76009
76039
|
init_loader();
|
|
76010
76040
|
init_lifecycle();
|
|
76011
|
-
import { cpSync as cpSync2, existsSync as existsSync58, mkdirSync as mkdirSync32, readFileSync as
|
|
76041
|
+
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";
|
|
76012
76042
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
76013
76043
|
import { join as join60, dirname as dirname14, resolve as resolve33 } from "node:path";
|
|
76014
76044
|
import { homedir as homedir36 } from "node:os";
|
|
@@ -76018,7 +76048,7 @@ function runningFromSwitchroomCheckout(scriptPath) {
|
|
|
76018
76048
|
for (let i = 0;i < 12; i++) {
|
|
76019
76049
|
if (existsSync58(join60(dir, ".git"))) {
|
|
76020
76050
|
try {
|
|
76021
|
-
const pkg = JSON.parse(
|
|
76051
|
+
const pkg = JSON.parse(readFileSync52(join60(dir, "package.json"), "utf-8"));
|
|
76022
76052
|
if (pkg.name === "switchroom")
|
|
76023
76053
|
return true;
|
|
76024
76054
|
} catch {}
|
|
@@ -76286,7 +76316,7 @@ function defaultStatusProbe(composePath) {
|
|
|
76286
76316
|
const pkgPath = join60(dir, "package.json");
|
|
76287
76317
|
if (existsSync58(pkgPath)) {
|
|
76288
76318
|
try {
|
|
76289
|
-
const pkg = JSON.parse(
|
|
76319
|
+
const pkg = JSON.parse(readFileSync52(pkgPath, "utf-8"));
|
|
76290
76320
|
if (typeof pkg.version === "string")
|
|
76291
76321
|
cliVersion = pkg.version;
|
|
76292
76322
|
} catch (err) {
|
|
@@ -76502,7 +76532,7 @@ init_source();
|
|
|
76502
76532
|
init_helpers();
|
|
76503
76533
|
init_lifecycle();
|
|
76504
76534
|
import { execSync as execSync4 } from "node:child_process";
|
|
76505
|
-
import { existsSync as existsSync59, readFileSync as
|
|
76535
|
+
import { existsSync as existsSync59, readFileSync as readFileSync53 } from "node:fs";
|
|
76506
76536
|
import { dirname as dirname15, join as join61 } from "node:path";
|
|
76507
76537
|
function getClaudeCodeVersion() {
|
|
76508
76538
|
try {
|
|
@@ -76556,7 +76586,7 @@ function locateSwitchroomInstallDir() {
|
|
|
76556
76586
|
const pkgPath = join61(dir, "package.json");
|
|
76557
76587
|
if (existsSync59(pkgPath)) {
|
|
76558
76588
|
try {
|
|
76559
|
-
const pkg = JSON.parse(
|
|
76589
|
+
const pkg = JSON.parse(readFileSync53(pkgPath, "utf-8"));
|
|
76560
76590
|
if (pkg.name === "switchroom" && existsSync59(join61(dir, ".git"))) {
|
|
76561
76591
|
return dir;
|
|
76562
76592
|
}
|
|
@@ -76787,11 +76817,11 @@ import {
|
|
|
76787
76817
|
mkdirSync as mkdirSync33,
|
|
76788
76818
|
openSync as openSync11,
|
|
76789
76819
|
readdirSync as readdirSync22,
|
|
76790
|
-
readFileSync as
|
|
76820
|
+
readFileSync as readFileSync54,
|
|
76791
76821
|
renameSync as renameSync12,
|
|
76792
76822
|
statSync as statSync27,
|
|
76793
76823
|
unlinkSync as unlinkSync11,
|
|
76794
|
-
writeFileSync as
|
|
76824
|
+
writeFileSync as writeFileSync28,
|
|
76795
76825
|
writeSync as writeSync7
|
|
76796
76826
|
} from "node:fs";
|
|
76797
76827
|
import { join as join62 } from "node:path";
|
|
@@ -77197,7 +77227,7 @@ function readAll(stateDir) {
|
|
|
77197
77227
|
return [];
|
|
77198
77228
|
let raw;
|
|
77199
77229
|
try {
|
|
77200
|
-
raw =
|
|
77230
|
+
raw = readFileSync54(path4, "utf-8");
|
|
77201
77231
|
} catch {
|
|
77202
77232
|
return [];
|
|
77203
77233
|
}
|
|
@@ -77345,7 +77375,7 @@ function writeAll(stateDir, events) {
|
|
|
77345
77375
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
77346
77376
|
`) + `
|
|
77347
77377
|
`;
|
|
77348
|
-
|
|
77378
|
+
writeFileSync28(tmp, body, "utf-8");
|
|
77349
77379
|
renameSync12(tmp, path4);
|
|
77350
77380
|
}
|
|
77351
77381
|
var ORPHAN_TMP_TTL_MS = 60000;
|
|
@@ -77408,7 +77438,7 @@ function withLock(stateDir, fn) {
|
|
|
77408
77438
|
function tryStealStaleLock(lockPath) {
|
|
77409
77439
|
let pidStr;
|
|
77410
77440
|
try {
|
|
77411
|
-
pidStr =
|
|
77441
|
+
pidStr = readFileSync54(lockPath, "utf-8").trim();
|
|
77412
77442
|
} catch {
|
|
77413
77443
|
return true;
|
|
77414
77444
|
}
|
|
@@ -77665,9 +77695,9 @@ import { createHash as createHash11 } from "node:crypto";
|
|
|
77665
77695
|
import {
|
|
77666
77696
|
existsSync as existsSync61,
|
|
77667
77697
|
mkdirSync as mkdirSync34,
|
|
77668
|
-
readFileSync as
|
|
77698
|
+
readFileSync as readFileSync55,
|
|
77669
77699
|
rmSync as rmSync13,
|
|
77670
|
-
writeFileSync as
|
|
77700
|
+
writeFileSync as writeFileSync29
|
|
77671
77701
|
} from "node:fs";
|
|
77672
77702
|
import { dirname as dirname16, join as join63 } from "node:path";
|
|
77673
77703
|
import { homedir as homedir37 } from "node:os";
|
|
@@ -77685,7 +77715,7 @@ function defaultPythonCacheRoot() {
|
|
|
77685
77715
|
return join63(homedir37(), ".switchroom", "deps", "python");
|
|
77686
77716
|
}
|
|
77687
77717
|
function hashFile(path4) {
|
|
77688
|
-
return createHash11("sha256").update(
|
|
77718
|
+
return createHash11("sha256").update(readFileSync55(path4)).digest("hex");
|
|
77689
77719
|
}
|
|
77690
77720
|
function ensurePythonEnv(opts) {
|
|
77691
77721
|
const { skillName, requirementsPath, force = false } = opts;
|
|
@@ -77701,7 +77731,7 @@ function ensurePythonEnv(opts) {
|
|
|
77701
77731
|
const pipBin = join63(binDir, "pip");
|
|
77702
77732
|
const targetHash = hashFile(requirementsPath);
|
|
77703
77733
|
if (!force && existsSync61(stampPath) && existsSync61(pythonBin)) {
|
|
77704
|
-
const existingHash =
|
|
77734
|
+
const existingHash = readFileSync55(stampPath, "utf8").trim();
|
|
77705
77735
|
if (existingHash === targetHash) {
|
|
77706
77736
|
return {
|
|
77707
77737
|
skillName,
|
|
@@ -77735,7 +77765,7 @@ function ensurePythonEnv(opts) {
|
|
|
77735
77765
|
const e = err;
|
|
77736
77766
|
throw new PythonEnvError(`Failed to install requirements for skill "${skillName}": ${e.message}`, e.stderr?.toString());
|
|
77737
77767
|
}
|
|
77738
|
-
|
|
77768
|
+
writeFileSync29(stampPath, targetHash + `
|
|
77739
77769
|
`);
|
|
77740
77770
|
return {
|
|
77741
77771
|
skillName,
|
|
@@ -77753,9 +77783,9 @@ import {
|
|
|
77753
77783
|
copyFileSync as copyFileSync9,
|
|
77754
77784
|
existsSync as existsSync62,
|
|
77755
77785
|
mkdirSync as mkdirSync35,
|
|
77756
|
-
readFileSync as
|
|
77786
|
+
readFileSync as readFileSync56,
|
|
77757
77787
|
rmSync as rmSync14,
|
|
77758
|
-
writeFileSync as
|
|
77788
|
+
writeFileSync as writeFileSync30
|
|
77759
77789
|
} from "node:fs";
|
|
77760
77790
|
import { dirname as dirname17, join as join64 } from "node:path";
|
|
77761
77791
|
import { homedir as homedir38 } from "node:os";
|
|
@@ -77788,7 +77818,7 @@ function hashDepInputs(packageJsonPath) {
|
|
|
77788
77818
|
const hasher = createHash12("sha256");
|
|
77789
77819
|
hasher.update(`package.json
|
|
77790
77820
|
`);
|
|
77791
|
-
hasher.update(
|
|
77821
|
+
hasher.update(readFileSync56(packageJsonPath));
|
|
77792
77822
|
for (const lockName of ALL_LOCKFILES) {
|
|
77793
77823
|
const lockPath = join64(sourceDir, lockName);
|
|
77794
77824
|
if (existsSync62(lockPath)) {
|
|
@@ -77797,7 +77827,7 @@ function hashDepInputs(packageJsonPath) {
|
|
|
77797
77827
|
hasher.update(lockName);
|
|
77798
77828
|
hasher.update(`
|
|
77799
77829
|
`);
|
|
77800
|
-
hasher.update(
|
|
77830
|
+
hasher.update(readFileSync56(lockPath));
|
|
77801
77831
|
}
|
|
77802
77832
|
}
|
|
77803
77833
|
return hasher.digest("hex");
|
|
@@ -77816,7 +77846,7 @@ function ensureNodeEnv(opts) {
|
|
|
77816
77846
|
const binDir = join64(nodeModulesDir, ".bin");
|
|
77817
77847
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
77818
77848
|
if (!force && existsSync62(stampPath) && existsSync62(nodeModulesDir)) {
|
|
77819
|
-
const existingHash =
|
|
77849
|
+
const existingHash = readFileSync56(stampPath, "utf8").trim();
|
|
77820
77850
|
if (existingHash === targetHash) {
|
|
77821
77851
|
return {
|
|
77822
77852
|
skillName,
|
|
@@ -77852,7 +77882,7 @@ function ensureNodeEnv(opts) {
|
|
|
77852
77882
|
const e = err;
|
|
77853
77883
|
throw new NodeEnvError(`Failed to install node deps for skill "${skillName}" with ${installer}: ${e.message}`, e.stderr?.toString());
|
|
77854
77884
|
}
|
|
77855
|
-
|
|
77885
|
+
writeFileSync30(stampPath, targetHash + `
|
|
77856
77886
|
`);
|
|
77857
77887
|
return {
|
|
77858
77888
|
skillName,
|
|
@@ -78835,7 +78865,7 @@ function safeParseInt(value, fallback) {
|
|
|
78835
78865
|
init_helpers();
|
|
78836
78866
|
init_loader();
|
|
78837
78867
|
init_merge();
|
|
78838
|
-
import { copyFileSync as copyFileSync10, existsSync as existsSync65, readFileSync as
|
|
78868
|
+
import { copyFileSync as copyFileSync10, existsSync as existsSync65, readFileSync as readFileSync57, writeFileSync as writeFileSync31 } from "node:fs";
|
|
78839
78869
|
import { join as join66, resolve as resolve39 } from "node:path";
|
|
78840
78870
|
init_schema();
|
|
78841
78871
|
function resolveSoulTargetOrExit(program3, agentName) {
|
|
@@ -78881,7 +78911,7 @@ function registerSoulCommand(program3) {
|
|
|
78881
78911
|
console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
|
|
78882
78912
|
process.exit(1);
|
|
78883
78913
|
}
|
|
78884
|
-
process.stdout.write(
|
|
78914
|
+
process.stdout.write(readFileSync57(t.soulPath, "utf-8"));
|
|
78885
78915
|
}));
|
|
78886
78916
|
cmd.command("reset <agent>").description("Re-seed SOUL.md from the agent's current profile " + "(backs the existing file up to SOUL.md.bak first)").option("-y, --yes", "Skip the confirmation prompt").action(withConfigError(async (agentName, opts) => {
|
|
78887
78917
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
@@ -78912,7 +78942,7 @@ function registerSoulCommand(program3) {
|
|
|
78912
78942
|
}
|
|
78913
78943
|
copyFileSync10(t.soulPath, backupPath);
|
|
78914
78944
|
}
|
|
78915
|
-
|
|
78945
|
+
writeFileSync31(t.soulPath, content, "utf-8");
|
|
78916
78946
|
if (backupPath) {
|
|
78917
78947
|
console.log(`soul: re-seeded ${agentName}'s SOUL.md from profile ` + `"${t.profileName}".
|
|
78918
78948
|
` + ` Previous version saved to ${backupPath}`);
|
|
@@ -78926,7 +78956,7 @@ function registerSoulCommand(program3) {
|
|
|
78926
78956
|
// src/cli/debug.ts
|
|
78927
78957
|
init_helpers();
|
|
78928
78958
|
init_loader();
|
|
78929
|
-
import { existsSync as existsSync66, readFileSync as
|
|
78959
|
+
import { existsSync as existsSync66, readFileSync as readFileSync58, readdirSync as readdirSync23, statSync as statSync28 } from "node:fs";
|
|
78930
78960
|
import { resolve as resolve40, join as join67 } from "node:path";
|
|
78931
78961
|
import { createHash as createHash13 } from "node:crypto";
|
|
78932
78962
|
init_merge();
|
|
@@ -78942,7 +78972,7 @@ function readMcpServerNames(agentDir) {
|
|
|
78942
78972
|
if (!existsSync66(mcpPath))
|
|
78943
78973
|
return [];
|
|
78944
78974
|
try {
|
|
78945
|
-
const parsed = JSON.parse(
|
|
78975
|
+
const parsed = JSON.parse(readFileSync58(mcpPath, "utf-8"));
|
|
78946
78976
|
return Object.keys(parsed.mcpServers ?? {});
|
|
78947
78977
|
} catch {
|
|
78948
78978
|
return null;
|
|
@@ -78977,7 +79007,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
78977
79007
|
}
|
|
78978
79008
|
function extractLatestUserMessage(transcriptPath) {
|
|
78979
79009
|
try {
|
|
78980
|
-
const content =
|
|
79010
|
+
const content = readFileSync58(transcriptPath, "utf-8");
|
|
78981
79011
|
const lines = content.trim().split(`
|
|
78982
79012
|
`).filter(Boolean);
|
|
78983
79013
|
for (let i = lines.length - 1;i >= 0; i--) {
|
|
@@ -79081,7 +79111,7 @@ function registerDebugCommand(program3) {
|
|
|
79081
79111
|
}
|
|
79082
79112
|
console.log(`=== Append System Prompt (per-session) ===
|
|
79083
79113
|
`);
|
|
79084
|
-
const handoffContent = existsSync66(handoffPath) ?
|
|
79114
|
+
const handoffContent = existsSync66(handoffPath) ? readFileSync58(handoffPath, "utf-8") : "";
|
|
79085
79115
|
if (handoffContent.trim().length > 0) {
|
|
79086
79116
|
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
79087
79117
|
console.log(handoffContent);
|
|
@@ -79092,7 +79122,7 @@ function registerDebugCommand(program3) {
|
|
|
79092
79122
|
}
|
|
79093
79123
|
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
79094
79124
|
`);
|
|
79095
|
-
const claudeMdContent = existsSync66(claudeMdPath) ?
|
|
79125
|
+
const claudeMdContent = existsSync66(claudeMdPath) ? readFileSync58(claudeMdPath, "utf-8") : "";
|
|
79096
79126
|
if (claudeMdContent.trim().length > 0) {
|
|
79097
79127
|
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
79098
79128
|
console.log(claudeMdContent);
|
|
@@ -79103,7 +79133,7 @@ function registerDebugCommand(program3) {
|
|
|
79103
79133
|
}
|
|
79104
79134
|
console.log(`=== Persona (SOUL.md) ===
|
|
79105
79135
|
`);
|
|
79106
|
-
const soulMdContent = existsSync66(soulMdPath) ?
|
|
79136
|
+
const soulMdContent = existsSync66(soulMdPath) ? readFileSync58(soulMdPath, "utf-8") : existsSync66(workspaceSoulMdPath) ? readFileSync58(workspaceSoulMdPath, "utf-8") : "";
|
|
79107
79137
|
if (soulMdContent.trim().length > 0) {
|
|
79108
79138
|
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
79109
79139
|
console.log(soulMdContent);
|
|
@@ -79167,8 +79197,8 @@ function registerDebugCommand(program3) {
|
|
|
79167
79197
|
const fleetDir = join67(agentsDir, "..", "fleet");
|
|
79168
79198
|
const fleetInvPath = join67(fleetDir, "switchroom-invariants.md");
|
|
79169
79199
|
const fleetClaudePath = join67(fleetDir, "CLAUDE.md");
|
|
79170
|
-
const fleetInvBytes = existsSync66(fleetInvPath) ?
|
|
79171
|
-
const fleetClaudeBytes = existsSync66(fleetClaudePath) ?
|
|
79200
|
+
const fleetInvBytes = existsSync66(fleetInvPath) ? readFileSync58(fleetInvPath, "utf-8").length : 0;
|
|
79201
|
+
const fleetClaudeBytes = existsSync66(fleetClaudePath) ? readFileSync58(fleetClaudePath, "utf-8").length : 0;
|
|
79172
79202
|
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
79173
79203
|
const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
|
|
79174
79204
|
console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
|
|
@@ -79209,8 +79239,8 @@ import { randomBytes as randomBytes13 } from "node:crypto";
|
|
|
79209
79239
|
// src/worktree/registry.ts
|
|
79210
79240
|
import {
|
|
79211
79241
|
mkdirSync as mkdirSync36,
|
|
79212
|
-
writeFileSync as
|
|
79213
|
-
readFileSync as
|
|
79242
|
+
writeFileSync as writeFileSync32,
|
|
79243
|
+
readFileSync as readFileSync59,
|
|
79214
79244
|
readdirSync as readdirSync24,
|
|
79215
79245
|
unlinkSync as unlinkSync12,
|
|
79216
79246
|
existsSync as existsSync67,
|
|
@@ -79231,14 +79261,14 @@ function writeRecord(record2) {
|
|
|
79231
79261
|
ensureDir2();
|
|
79232
79262
|
const target = recordPath(record2.id);
|
|
79233
79263
|
const tmp = `${target}.tmp${process.pid}`;
|
|
79234
|
-
|
|
79264
|
+
writeFileSync32(tmp, JSON.stringify(record2, null, 2) + `
|
|
79235
79265
|
`, { mode: 384 });
|
|
79236
79266
|
renameSync13(tmp, target);
|
|
79237
79267
|
}
|
|
79238
79268
|
function readRecord(id) {
|
|
79239
79269
|
const path7 = recordPath(id);
|
|
79240
79270
|
try {
|
|
79241
|
-
const raw =
|
|
79271
|
+
const raw = readFileSync59(path7, "utf8");
|
|
79242
79272
|
return JSON.parse(raw);
|
|
79243
79273
|
} catch {
|
|
79244
79274
|
return null;
|
|
@@ -79603,7 +79633,7 @@ import {
|
|
|
79603
79633
|
mkdirSync as mkdirSync38,
|
|
79604
79634
|
readdirSync as readdirSync25,
|
|
79605
79635
|
rmSync as rmSync15,
|
|
79606
|
-
writeFileSync as
|
|
79636
|
+
writeFileSync as writeFileSync33
|
|
79607
79637
|
} from "node:fs";
|
|
79608
79638
|
import { join as join70 } from "node:path";
|
|
79609
79639
|
function encodeCredentialsFilename(email) {
|
|
@@ -79805,7 +79835,7 @@ function writeSeedFile(dir, email, seed) {
|
|
|
79805
79835
|
}
|
|
79806
79836
|
const filename = encodeCredentialsFilename(email);
|
|
79807
79837
|
const filePath = join70(dir, filename);
|
|
79808
|
-
|
|
79838
|
+
writeFileSync33(filePath, JSON.stringify(seed), { mode: 384 });
|
|
79809
79839
|
chmodSync9(filePath, 384);
|
|
79810
79840
|
return filePath;
|
|
79811
79841
|
}
|
|
@@ -79963,7 +79993,7 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
79963
79993
|
// src/cli/m365-mcp-launcher.ts
|
|
79964
79994
|
init_scaffold_integration();
|
|
79965
79995
|
import { spawn as spawn6 } from "node:child_process";
|
|
79966
|
-
import { writeFileSync as
|
|
79996
|
+
import { writeFileSync as writeFileSync34, mkdirSync as mkdirSync39 } from "node:fs";
|
|
79967
79997
|
import { dirname as dirname18, join as join71 } from "node:path";
|
|
79968
79998
|
var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
|
|
79969
79999
|
var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
|
|
@@ -79990,7 +80020,7 @@ function writeRefreshHeartbeat(agentName, data) {
|
|
|
79990
80020
|
const path7 = heartbeatPath(agentName);
|
|
79991
80021
|
try {
|
|
79992
80022
|
mkdirSync39(dirname18(path7), { recursive: true });
|
|
79993
|
-
|
|
80023
|
+
writeFileSync34(path7, JSON.stringify(data, null, 2), { mode: 420 });
|
|
79994
80024
|
} catch {}
|
|
79995
80025
|
}
|
|
79996
80026
|
function heartbeatPath(agentName) {
|
|
@@ -80181,7 +80211,7 @@ function registerM365McpLauncherCommand(program3) {
|
|
|
80181
80211
|
// src/cli/notion-mcp-launcher.ts
|
|
80182
80212
|
init_scaffold_integration();
|
|
80183
80213
|
import { spawn as spawn7 } from "node:child_process";
|
|
80184
|
-
import { existsSync as existsSync71, mkdirSync as mkdirSync40, writeFileSync as
|
|
80214
|
+
import { existsSync as existsSync71, mkdirSync as mkdirSync40, writeFileSync as writeFileSync35 } from "node:fs";
|
|
80185
80215
|
import { dirname as dirname19 } from "node:path";
|
|
80186
80216
|
var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
|
|
80187
80217
|
var DEFAULT_HEARTBEAT_PATH = "/state/agent/notion-launcher.heartbeat.json";
|
|
@@ -80195,7 +80225,7 @@ function defaultWriteHeartbeat(path7, contents) {
|
|
|
80195
80225
|
const dir = dirname19(path7);
|
|
80196
80226
|
if (!existsSync71(dir))
|
|
80197
80227
|
mkdirSync40(dir, { recursive: true });
|
|
80198
|
-
|
|
80228
|
+
writeFileSync35(path7, contents);
|
|
80199
80229
|
} catch {}
|
|
80200
80230
|
}
|
|
80201
80231
|
async function runNotionMcpLauncher(opts, runtime) {
|
|
@@ -80305,7 +80335,7 @@ function registerNotionMcpLauncherCommand(program3) {
|
|
|
80305
80335
|
|
|
80306
80336
|
// src/cli/deliver-file.ts
|
|
80307
80337
|
init_client2();
|
|
80308
|
-
import { readFileSync as
|
|
80338
|
+
import { readFileSync as readFileSync60, statSync as statSync29 } from "node:fs";
|
|
80309
80339
|
import { basename as basename8 } from "node:path";
|
|
80310
80340
|
|
|
80311
80341
|
// src/delivery/onedrive.ts
|
|
@@ -80645,7 +80675,7 @@ async function defaultResolveProvider() {
|
|
|
80645
80675
|
async function runDeliverFile(localPath, deps = {}) {
|
|
80646
80676
|
const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
|
|
80647
80677
|
const sizeOf = deps.fileSize ?? ((p) => statSync29(p).size);
|
|
80648
|
-
const read = deps.readFile ?? ((p) => new Uint8Array(
|
|
80678
|
+
const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync60(p)));
|
|
80649
80679
|
const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
|
|
80650
80680
|
let size;
|
|
80651
80681
|
try {
|
|
@@ -80935,7 +80965,7 @@ async function fetchToken(vaultKey) {
|
|
|
80935
80965
|
|
|
80936
80966
|
// src/cli/apply.ts
|
|
80937
80967
|
init_source();
|
|
80938
|
-
import { accessSync as accessSync3, chownSync as chownSync5, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync74, mkdirSync as mkdirSync42, readFileSync as
|
|
80968
|
+
import { accessSync as accessSync3, chownSync as chownSync5, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync74, mkdirSync as mkdirSync42, readFileSync as readFileSync62, readdirSync as readdirSync26, renameSync as renameSync14, writeFileSync as writeFileSync37 } from "node:fs";
|
|
80939
80969
|
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
80940
80970
|
import { spawnSync as childSpawnSync } from "node:child_process";
|
|
80941
80971
|
import readline from "node:readline";
|
|
@@ -81332,7 +81362,7 @@ init_loader();
|
|
|
81332
81362
|
init_loader();
|
|
81333
81363
|
|
|
81334
81364
|
// src/cli/update-prompt-hook.ts
|
|
81335
|
-
import { existsSync as existsSync72, readFileSync as
|
|
81365
|
+
import { existsSync as existsSync72, readFileSync as readFileSync61, writeFileSync as writeFileSync36, chmodSync as chmodSync10, mkdirSync as mkdirSync41 } from "node:fs";
|
|
81336
81366
|
import { join as join72 } from "node:path";
|
|
81337
81367
|
var HOOK_FILENAME = "update-card-on-prompt.sh";
|
|
81338
81368
|
function updatePromptHookScript() {
|
|
@@ -81404,9 +81434,9 @@ function installUpdatePromptHook(agentDir) {
|
|
|
81404
81434
|
const scriptPath = join72(hooksDir, HOOK_FILENAME);
|
|
81405
81435
|
const desired = updatePromptHookScript();
|
|
81406
81436
|
let installed = false;
|
|
81407
|
-
const existing = existsSync72(scriptPath) ?
|
|
81437
|
+
const existing = existsSync72(scriptPath) ? readFileSync61(scriptPath, "utf-8") : "";
|
|
81408
81438
|
if (existing !== desired) {
|
|
81409
|
-
|
|
81439
|
+
writeFileSync36(scriptPath, desired, { mode: 493 });
|
|
81410
81440
|
chmodSync10(scriptPath, 493);
|
|
81411
81441
|
installed = true;
|
|
81412
81442
|
} else {
|
|
@@ -81418,7 +81448,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
81418
81448
|
if (!existsSync72(settingsPath)) {
|
|
81419
81449
|
return { scriptPath, settingsPath, installed };
|
|
81420
81450
|
}
|
|
81421
|
-
const raw =
|
|
81451
|
+
const raw = readFileSync61(settingsPath, "utf-8");
|
|
81422
81452
|
let parsed;
|
|
81423
81453
|
try {
|
|
81424
81454
|
parsed = JSON.parse(raw);
|
|
@@ -81451,7 +81481,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
81451
81481
|
});
|
|
81452
81482
|
hooks.UserPromptSubmit = list2;
|
|
81453
81483
|
parsed.hooks = hooks;
|
|
81454
|
-
|
|
81484
|
+
writeFileSync36(settingsPath, JSON.stringify(parsed, null, 2) + `
|
|
81455
81485
|
`, { mode: 384 });
|
|
81456
81486
|
installed = true;
|
|
81457
81487
|
}
|
|
@@ -81574,24 +81604,24 @@ async function ensureHostMountSources(config) {
|
|
|
81574
81604
|
}
|
|
81575
81605
|
const autoUnlockPath = join74(home2, ".switchroom", "vault-auto-unlock");
|
|
81576
81606
|
if (!existsSync74(autoUnlockPath)) {
|
|
81577
|
-
|
|
81607
|
+
writeFileSync37(autoUnlockPath, "", { mode: 384 });
|
|
81578
81608
|
}
|
|
81579
81609
|
const auditLogPath = join74(home2, ".switchroom", "vault-audit.log");
|
|
81580
81610
|
if (!existsSync74(auditLogPath)) {
|
|
81581
|
-
|
|
81611
|
+
writeFileSync37(auditLogPath, "", { mode: 420 });
|
|
81582
81612
|
}
|
|
81583
81613
|
const grantsDbPath = join74(home2, ".switchroom", "vault-grants.db");
|
|
81584
81614
|
if (!existsSync74(grantsDbPath)) {
|
|
81585
|
-
|
|
81615
|
+
writeFileSync37(grantsDbPath, "", { mode: 384 });
|
|
81586
81616
|
}
|
|
81587
81617
|
const hostdAuditLogPath = join74(home2, ".switchroom", "host-control-audit.log");
|
|
81588
81618
|
if (!existsSync74(hostdAuditLogPath)) {
|
|
81589
|
-
|
|
81619
|
+
writeFileSync37(hostdAuditLogPath, "", { mode: 420 });
|
|
81590
81620
|
}
|
|
81591
81621
|
for (const name of Object.keys(config.agents)) {
|
|
81592
81622
|
const tokenPath = join74(home2, ".switchroom", "agents", name, ".vault-token");
|
|
81593
81623
|
if (!existsSync74(tokenPath)) {
|
|
81594
|
-
|
|
81624
|
+
writeFileSync37(tokenPath, "", { mode: 384 });
|
|
81595
81625
|
}
|
|
81596
81626
|
try {
|
|
81597
81627
|
const uid = allocateAgentUid(name);
|
|
@@ -81602,13 +81632,13 @@ async function ensureHostMountSources(config) {
|
|
|
81602
81632
|
await mkdir2(fleetDir, { recursive: true });
|
|
81603
81633
|
const invariantsPath = join74(fleetDir, "switchroom-invariants.md");
|
|
81604
81634
|
const invariantsCanonical = renderFleetInvariants();
|
|
81605
|
-
const invariantsCurrent = existsSync74(invariantsPath) ?
|
|
81635
|
+
const invariantsCurrent = existsSync74(invariantsPath) ? readFileSync62(invariantsPath, "utf-8") : null;
|
|
81606
81636
|
if (invariantsCurrent !== invariantsCanonical) {
|
|
81607
|
-
|
|
81637
|
+
writeFileSync37(invariantsPath, invariantsCanonical, { mode: 420 });
|
|
81608
81638
|
}
|
|
81609
81639
|
const fleetClaudePath = join74(fleetDir, "CLAUDE.md");
|
|
81610
81640
|
if (!existsSync74(fleetClaudePath)) {
|
|
81611
|
-
|
|
81641
|
+
writeFileSync37(fleetClaudePath, [
|
|
81612
81642
|
"# Switchroom fleet defaults",
|
|
81613
81643
|
"",
|
|
81614
81644
|
"Operator-owned fleet brain. Every agent reads this via",
|
|
@@ -81700,7 +81730,7 @@ function writeInstallTypeCache(homeDir = homedir43()) {
|
|
|
81700
81730
|
detected_at: new Date().toISOString(),
|
|
81701
81731
|
source_paths: ctx.source_paths
|
|
81702
81732
|
};
|
|
81703
|
-
|
|
81733
|
+
writeFileSync37(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
|
|
81704
81734
|
renameSync14(tmp, out);
|
|
81705
81735
|
return out;
|
|
81706
81736
|
}
|
|
@@ -81896,10 +81926,10 @@ Done. Scaffolded ${scaffolded}/${allAgentNames.length} agents.
|
|
|
81896
81926
|
};
|
|
81897
81927
|
}
|
|
81898
81928
|
function formatScaffoldFailureResolution(failures, scaffolded, agentsTotal) {
|
|
81899
|
-
const
|
|
81929
|
+
const fail3 = failures.length;
|
|
81900
81930
|
const lines = [];
|
|
81901
81931
|
lines.push("");
|
|
81902
|
-
lines.push(`ERROR: Scaffolded ${scaffolded}/${agentsTotal} agents. ${
|
|
81932
|
+
lines.push(`ERROR: Scaffolded ${scaffolded}/${agentsTotal} agents. ${fail3} failed.`);
|
|
81903
81933
|
lines.push("");
|
|
81904
81934
|
lines.push("Per-agent state dirs are mode 0700 owned by per-agent UIDs (the v0.7+");
|
|
81905
81935
|
lines.push("docker model). The operator cannot write into them without privilege");
|
|
@@ -81932,7 +81962,7 @@ function copyExampleConfig2(name) {
|
|
|
81932
81962
|
}
|
|
81933
81963
|
const embedded = EMBEDDED_EXAMPLES[name];
|
|
81934
81964
|
if (embedded !== undefined) {
|
|
81935
|
-
|
|
81965
|
+
writeFileSync37(dest, embedded, { encoding: "utf8" });
|
|
81936
81966
|
console.log(source_default.green(`Copied ${name}.yaml -> switchroom.yaml`));
|
|
81937
81967
|
return;
|
|
81938
81968
|
}
|
|
@@ -82127,7 +82157,7 @@ function runRedactStdin() {
|
|
|
82127
82157
|
}
|
|
82128
82158
|
|
|
82129
82159
|
// src/cli/status-ask.ts
|
|
82130
|
-
import { readFileSync as
|
|
82160
|
+
import { readFileSync as readFileSync63, existsSync as existsSync75, readdirSync as readdirSync27 } from "node:fs";
|
|
82131
82161
|
import { join as join75 } from "node:path";
|
|
82132
82162
|
import { homedir as homedir44 } from "node:os";
|
|
82133
82163
|
|
|
@@ -82403,7 +82433,7 @@ function runReport(opts) {
|
|
|
82403
82433
|
for (const src of sources) {
|
|
82404
82434
|
let content;
|
|
82405
82435
|
try {
|
|
82406
|
-
content =
|
|
82436
|
+
content = readFileSync63(src.path, "utf-8");
|
|
82407
82437
|
} catch (err) {
|
|
82408
82438
|
process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
|
|
82409
82439
|
`);
|
|
@@ -82508,7 +82538,7 @@ import {
|
|
|
82508
82538
|
mkdirSync as mkdirSync43,
|
|
82509
82539
|
openSync as openSync13,
|
|
82510
82540
|
readdirSync as readdirSync28,
|
|
82511
|
-
readFileSync as
|
|
82541
|
+
readFileSync as readFileSync64,
|
|
82512
82542
|
renameSync as renameSync15,
|
|
82513
82543
|
statSync as statSync30,
|
|
82514
82544
|
unlinkSync as unlinkSync14,
|
|
@@ -82632,7 +82662,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
82632
82662
|
continue;
|
|
82633
82663
|
const full = join76(paths.skillsDir, name);
|
|
82634
82664
|
try {
|
|
82635
|
-
const raw =
|
|
82665
|
+
const raw = readFileSync64(full, "utf-8");
|
|
82636
82666
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
82637
82667
|
out.push({ slug, path: full, raw });
|
|
82638
82668
|
} catch {}
|
|
@@ -82659,7 +82689,7 @@ function listOverlayEntries(agent, opts = {}) {
|
|
|
82659
82689
|
continue;
|
|
82660
82690
|
const full = join76(paths.scheduleDir, name);
|
|
82661
82691
|
try {
|
|
82662
|
-
const raw =
|
|
82692
|
+
const raw = readFileSync64(full, "utf-8");
|
|
82663
82693
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
82664
82694
|
out.push({ slug, path: full, raw });
|
|
82665
82695
|
} catch {}
|
|
@@ -82807,10 +82837,10 @@ import {
|
|
|
82807
82837
|
mkdirSync as mkdirSync44,
|
|
82808
82838
|
openSync as openSync14,
|
|
82809
82839
|
readdirSync as readdirSync29,
|
|
82810
|
-
readFileSync as
|
|
82840
|
+
readFileSync as readFileSync65,
|
|
82811
82841
|
renameSync as renameSync16,
|
|
82812
82842
|
unlinkSync as unlinkSync15,
|
|
82813
|
-
writeFileSync as
|
|
82843
|
+
writeFileSync as writeFileSync38,
|
|
82814
82844
|
writeSync as writeSync9
|
|
82815
82845
|
} from "node:fs";
|
|
82816
82846
|
import { join as join77 } from "node:path";
|
|
@@ -82853,7 +82883,7 @@ function stagePendingScheduleEntry(opts) {
|
|
|
82853
82883
|
}
|
|
82854
82884
|
renameSync16(yamlTmp, yamlPath);
|
|
82855
82885
|
}
|
|
82856
|
-
|
|
82886
|
+
writeFileSync38(metaPath, JSON.stringify(meta, null, 2) + `
|
|
82857
82887
|
`, { mode: 384 });
|
|
82858
82888
|
return { stageId, yamlPath, metaPath };
|
|
82859
82889
|
}
|
|
@@ -82871,7 +82901,7 @@ function listPendingScheduleEntries(agent, opts = {}) {
|
|
|
82871
82901
|
if (!existsSync77(yamlPath))
|
|
82872
82902
|
continue;
|
|
82873
82903
|
try {
|
|
82874
|
-
const meta = JSON.parse(
|
|
82904
|
+
const meta = JSON.parse(readFileSync65(metaPath, "utf-8"));
|
|
82875
82905
|
if (meta?.v !== 1 || typeof meta.stage_id !== "string")
|
|
82876
82906
|
continue;
|
|
82877
82907
|
out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
|
|
@@ -82909,7 +82939,7 @@ function denyPendingScheduleEntry(opts) {
|
|
|
82909
82939
|
}
|
|
82910
82940
|
|
|
82911
82941
|
// src/cli/agent-config-write.ts
|
|
82912
|
-
import { existsSync as existsSync78, readFileSync as
|
|
82942
|
+
import { existsSync as existsSync78, readFileSync as readFileSync66 } from "node:fs";
|
|
82913
82943
|
import { execFileSync as execFileSync25 } from "node:child_process";
|
|
82914
82944
|
|
|
82915
82945
|
// src/scheduler/schedule-report.ts
|
|
@@ -83277,7 +83307,7 @@ function scheduleRemove(opts) {
|
|
|
83277
83307
|
let priorContent = null;
|
|
83278
83308
|
try {
|
|
83279
83309
|
if (existsSync78(match.path))
|
|
83280
|
-
priorContent =
|
|
83310
|
+
priorContent = readFileSync66(match.path, "utf-8");
|
|
83281
83311
|
} catch {}
|
|
83282
83312
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
83283
83313
|
const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
|
|
@@ -83480,7 +83510,7 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
83480
83510
|
}
|
|
83481
83511
|
let blob;
|
|
83482
83512
|
if (opts.jsonl) {
|
|
83483
|
-
blob = existsSync78(opts.jsonl) ?
|
|
83513
|
+
blob = existsSync78(opts.jsonl) ? readFileSync66(opts.jsonl, "utf-8") : "";
|
|
83484
83514
|
} else {
|
|
83485
83515
|
try {
|
|
83486
83516
|
blob = execFileSync25("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
|
|
@@ -83760,13 +83790,13 @@ import {
|
|
|
83760
83790
|
mkdirSync as mkdirSync45,
|
|
83761
83791
|
mkdtempSync as mkdtempSync5,
|
|
83762
83792
|
openSync as openSync15,
|
|
83763
|
-
readFileSync as
|
|
83793
|
+
readFileSync as readFileSync67,
|
|
83764
83794
|
readdirSync as readdirSync30,
|
|
83765
83795
|
realpathSync as realpathSync7,
|
|
83766
83796
|
renameSync as renameSync17,
|
|
83767
83797
|
rmSync as rmSync16,
|
|
83768
83798
|
statSync as statSync31,
|
|
83769
|
-
writeFileSync as
|
|
83799
|
+
writeFileSync as writeFileSync39
|
|
83770
83800
|
} from "node:fs";
|
|
83771
83801
|
import { tmpdir as tmpdir5, homedir as homedir45 } from "node:os";
|
|
83772
83802
|
import { dirname as dirname23, join as join79, relative as relative2, resolve as resolve46 } from "node:path";
|
|
@@ -83996,7 +84026,7 @@ function isTarballPath(p) {
|
|
|
83996
84026
|
function loadFromDir(dir) {
|
|
83997
84027
|
const abs = realpathSync7(dir);
|
|
83998
84028
|
if (!statSync31(abs).isDirectory()) {
|
|
83999
|
-
|
|
84029
|
+
fail3(`--from path is not a directory: ${dir}`);
|
|
84000
84030
|
}
|
|
84001
84031
|
const files = {};
|
|
84002
84032
|
const walk2 = (sub) => {
|
|
@@ -84005,14 +84035,14 @@ function loadFromDir(dir) {
|
|
|
84005
84035
|
const full = join79(sub, ent.name);
|
|
84006
84036
|
const rel = relative2(abs, full);
|
|
84007
84037
|
if (ent.isSymbolicLink()) {
|
|
84008
|
-
|
|
84038
|
+
fail3(`refusing to read symlink inside --from dir: ${rel}`);
|
|
84009
84039
|
}
|
|
84010
84040
|
if (ent.isDirectory()) {
|
|
84011
84041
|
walk2(full);
|
|
84012
84042
|
continue;
|
|
84013
84043
|
}
|
|
84014
84044
|
if (ent.isFile()) {
|
|
84015
|
-
const buf =
|
|
84045
|
+
const buf = readFileSync67(full);
|
|
84016
84046
|
files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
|
|
84017
84047
|
}
|
|
84018
84048
|
}
|
|
@@ -84028,13 +84058,13 @@ function loadFromTarball(tarPath) {
|
|
|
84028
84058
|
stdio: ["ignore", "pipe", "pipe"]
|
|
84029
84059
|
});
|
|
84030
84060
|
if (list2.status !== 0) {
|
|
84031
|
-
|
|
84061
|
+
fail3(`tar list failed (exit ${list2.status}): ${(list2.stderr ?? "").trim()}`);
|
|
84032
84062
|
}
|
|
84033
84063
|
const entries = (list2.stdout ?? "").split(`
|
|
84034
84064
|
`).map((s) => s.trim()).filter((s) => s.length > 0 && !s.endsWith("/"));
|
|
84035
84065
|
for (const entry of entries) {
|
|
84036
84066
|
if (!validateRelPath(entry)) {
|
|
84037
|
-
|
|
84067
|
+
fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
|
|
84038
84068
|
}
|
|
84039
84069
|
}
|
|
84040
84070
|
const staging = mkdtempSync5(join79(tmpdir5(), "skill-apply-extract-"));
|
|
@@ -84051,7 +84081,7 @@ function loadFromTarball(tarPath) {
|
|
|
84051
84081
|
], { stdio: "pipe" });
|
|
84052
84082
|
if (r.status !== 0) {
|
|
84053
84083
|
const stderr = r.stderr?.toString("utf-8") ?? "(no stderr)";
|
|
84054
|
-
|
|
84084
|
+
fail3(`tar extraction failed (exit ${r.status}): ${stderr.trim()}`);
|
|
84055
84085
|
}
|
|
84056
84086
|
return loadFromDir(staging);
|
|
84057
84087
|
} finally {
|
|
@@ -84061,13 +84091,13 @@ function loadFromTarball(tarPath) {
|
|
|
84061
84091
|
}
|
|
84062
84092
|
}
|
|
84063
84093
|
function loadSingleFile(filePath) {
|
|
84064
|
-
const content =
|
|
84094
|
+
const content = readFileSync67(filePath, "utf-8");
|
|
84065
84095
|
return { "SKILL.md": content };
|
|
84066
84096
|
}
|
|
84067
84097
|
function loadFromStdin() {
|
|
84068
84098
|
const content = readStdinSync();
|
|
84069
84099
|
if (content.length === 0) {
|
|
84070
|
-
|
|
84100
|
+
fail3("no content on stdin; either pipe SKILL.md content in or pass --from");
|
|
84071
84101
|
}
|
|
84072
84102
|
return { "SKILL.md": content };
|
|
84073
84103
|
}
|
|
@@ -84126,7 +84156,7 @@ function validatePayload(name, files) {
|
|
|
84126
84156
|
const tmp = mkdtempSync5(join79(tmpdir5(), "skill-apply-py-"));
|
|
84127
84157
|
const tmpPy = join79(tmp, "check.py");
|
|
84128
84158
|
try {
|
|
84129
|
-
|
|
84159
|
+
writeFileSync39(tmpPy, content);
|
|
84130
84160
|
const r = spawnSync11("python3", ["-m", "py_compile", tmpPy], {
|
|
84131
84161
|
encoding: "utf-8"
|
|
84132
84162
|
});
|
|
@@ -84152,7 +84182,7 @@ function diffSummary(currentDir, files) {
|
|
|
84152
84182
|
if (ent.isDirectory()) {
|
|
84153
84183
|
walk2(full);
|
|
84154
84184
|
} else if (ent.isFile()) {
|
|
84155
|
-
currentFiles[rel.replace(/\\/g, "/")] =
|
|
84185
|
+
currentFiles[rel.replace(/\\/g, "/")] = readFileSync67(full, "utf-8");
|
|
84156
84186
|
}
|
|
84157
84187
|
}
|
|
84158
84188
|
};
|
|
@@ -84192,7 +84222,7 @@ function writePayload(poolDir, name, files) {
|
|
|
84192
84222
|
}
|
|
84193
84223
|
} catch {}
|
|
84194
84224
|
if (targetIsSymlink) {
|
|
84195
|
-
|
|
84225
|
+
fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
|
|
84196
84226
|
}
|
|
84197
84227
|
const staging = mkdtempSync5(join79(poolDir, `.skill-apply-stage-${name}-`));
|
|
84198
84228
|
let oldRename = null;
|
|
@@ -84202,7 +84232,7 @@ function writePayload(poolDir, name, files) {
|
|
|
84202
84232
|
mkdirSync45(dirname23(full), { recursive: true, mode: 493 });
|
|
84203
84233
|
const fd = openSync15(full, "wx");
|
|
84204
84234
|
try {
|
|
84205
|
-
|
|
84235
|
+
writeFileSync39(fd, content);
|
|
84206
84236
|
} finally {
|
|
84207
84237
|
closeSync15(fd);
|
|
84208
84238
|
}
|
|
@@ -84240,7 +84270,7 @@ function writePayload(poolDir, name, files) {
|
|
|
84240
84270
|
throw err2;
|
|
84241
84271
|
}
|
|
84242
84272
|
}
|
|
84243
|
-
function
|
|
84273
|
+
function fail3(msg) {
|
|
84244
84274
|
console.error(source_default.red(`error: ${msg}`));
|
|
84245
84275
|
process.exit(2);
|
|
84246
84276
|
}
|
|
@@ -84253,7 +84283,7 @@ function registerSkillCommand(program3) {
|
|
|
84253
84283
|
} else {
|
|
84254
84284
|
const fromPath = resolve46(opts.from);
|
|
84255
84285
|
if (!existsSync80(fromPath)) {
|
|
84256
|
-
|
|
84286
|
+
fail3(`--from path does not exist: ${opts.from}`);
|
|
84257
84287
|
}
|
|
84258
84288
|
const st = statSync31(fromPath);
|
|
84259
84289
|
if (st.isDirectory()) {
|
|
@@ -84263,7 +84293,7 @@ function registerSkillCommand(program3) {
|
|
|
84263
84293
|
} else if (fromPath.endsWith(".md")) {
|
|
84264
84294
|
files = loadSingleFile(fromPath);
|
|
84265
84295
|
} else {
|
|
84266
|
-
|
|
84296
|
+
fail3(`--from must be a directory, a .tar/.tar.gz/.tgz tarball, or a ` + `.md file. Got: ${opts.from}`);
|
|
84267
84297
|
}
|
|
84268
84298
|
}
|
|
84269
84299
|
const v = validatePayload(name, files);
|
|
@@ -84312,13 +84342,13 @@ import {
|
|
|
84312
84342
|
mkdirSync as mkdirSync46,
|
|
84313
84343
|
mkdtempSync as mkdtempSync6,
|
|
84314
84344
|
openSync as openSync16,
|
|
84315
|
-
readFileSync as
|
|
84345
|
+
readFileSync as readFileSync68,
|
|
84316
84346
|
readdirSync as readdirSync31,
|
|
84317
84347
|
renameSync as renameSync18,
|
|
84318
84348
|
rmSync as rmSync17,
|
|
84319
84349
|
statSync as statSync32,
|
|
84320
84350
|
utimesSync,
|
|
84321
|
-
writeFileSync as
|
|
84351
|
+
writeFileSync as writeFileSync40
|
|
84322
84352
|
} from "node:fs";
|
|
84323
84353
|
import { dirname as dirname24, join as join80, relative as relative3, resolve as resolve47 } from "node:path";
|
|
84324
84354
|
import { homedir as homedir46, tmpdir as tmpdir6 } from "node:os";
|
|
@@ -84394,7 +84424,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
84394
84424
|
if (ent.isDirectory())
|
|
84395
84425
|
walk2(s, d);
|
|
84396
84426
|
else if (ent.isFile()) {
|
|
84397
|
-
|
|
84427
|
+
writeFileSync40(d, readFileSync68(s));
|
|
84398
84428
|
}
|
|
84399
84429
|
}
|
|
84400
84430
|
};
|
|
@@ -84409,7 +84439,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
84409
84439
|
`));
|
|
84410
84440
|
}
|
|
84411
84441
|
}
|
|
84412
|
-
function
|
|
84442
|
+
function fail4(msg, exit = 2) {
|
|
84413
84443
|
console.error(source_default.red(`error: ${msg}`));
|
|
84414
84444
|
process.exit(exit);
|
|
84415
84445
|
}
|
|
@@ -84417,10 +84447,10 @@ function resolveAgent(opts) {
|
|
|
84417
84447
|
const fromEnv = process.env.SWITCHROOM_AGENT_NAME;
|
|
84418
84448
|
const agent = opts.agent ?? fromEnv;
|
|
84419
84449
|
if (!agent) {
|
|
84420
|
-
|
|
84450
|
+
fail4("agent name required: pass --agent <name>, or set SWITCHROOM_AGENT_NAME " + "in the calling environment (set by switchroom for in-container agents).");
|
|
84421
84451
|
}
|
|
84422
84452
|
if (!SKILL_SLUG_RE.test(agent)) {
|
|
84423
|
-
|
|
84453
|
+
fail4(`agent name has invalid shape: ${JSON.stringify(agent)}`);
|
|
84424
84454
|
}
|
|
84425
84455
|
return agent;
|
|
84426
84456
|
}
|
|
@@ -84458,14 +84488,14 @@ function readStdinSync2() {
|
|
|
84458
84488
|
function loadFromDir2(dir) {
|
|
84459
84489
|
const abs = resolve47(dir);
|
|
84460
84490
|
if (!statSync32(abs).isDirectory()) {
|
|
84461
|
-
|
|
84491
|
+
fail4(`--from path is not a directory: ${dir}`);
|
|
84462
84492
|
}
|
|
84463
84493
|
const files = {};
|
|
84464
84494
|
const walk2 = (sub) => {
|
|
84465
84495
|
for (const ent of readdirSync31(sub, { withFileTypes: true })) {
|
|
84466
84496
|
const full = join80(sub, ent.name);
|
|
84467
84497
|
if (ent.isSymbolicLink()) {
|
|
84468
|
-
|
|
84498
|
+
fail4(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
|
|
84469
84499
|
}
|
|
84470
84500
|
if (ent.isDirectory()) {
|
|
84471
84501
|
walk2(full);
|
|
@@ -84473,7 +84503,7 @@ function loadFromDir2(dir) {
|
|
|
84473
84503
|
}
|
|
84474
84504
|
if (ent.isFile()) {
|
|
84475
84505
|
const rel = relative3(abs, full).replace(/\\/g, "/");
|
|
84476
|
-
files[rel] =
|
|
84506
|
+
files[rel] = readFileSync68(full, "utf-8");
|
|
84477
84507
|
}
|
|
84478
84508
|
}
|
|
84479
84509
|
};
|
|
@@ -84483,7 +84513,7 @@ function loadFromDir2(dir) {
|
|
|
84483
84513
|
function loadFromStdin2() {
|
|
84484
84514
|
const raw = readStdinSync2();
|
|
84485
84515
|
if (raw.length === 0) {
|
|
84486
|
-
|
|
84516
|
+
fail4("no content on stdin; pipe a SKILL.md or JSON file-map");
|
|
84487
84517
|
}
|
|
84488
84518
|
const trimmed = raw.trimStart();
|
|
84489
84519
|
if (trimmed.startsWith("{")) {
|
|
@@ -84491,15 +84521,15 @@ function loadFromStdin2() {
|
|
|
84491
84521
|
try {
|
|
84492
84522
|
parsed = JSON.parse(raw);
|
|
84493
84523
|
} catch (err2) {
|
|
84494
|
-
|
|
84524
|
+
fail4(`stdin starts with '{' but is not valid JSON: ${err2.message}`);
|
|
84495
84525
|
}
|
|
84496
84526
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
84497
|
-
|
|
84527
|
+
fail4("stdin JSON must be an object of {path: content, ...}");
|
|
84498
84528
|
}
|
|
84499
84529
|
const files = {};
|
|
84500
84530
|
for (const [k, v] of Object.entries(parsed)) {
|
|
84501
84531
|
if (typeof v !== "string") {
|
|
84502
|
-
|
|
84532
|
+
fail4(`stdin JSON: value for ${JSON.stringify(k)} must be a string`);
|
|
84503
84533
|
}
|
|
84504
84534
|
files[k] = v;
|
|
84505
84535
|
}
|
|
@@ -84519,7 +84549,7 @@ function behavioralValidate(files) {
|
|
|
84519
84549
|
const tmp = mkdtempSync6(join80(tmpdir6(), "skill-personal-py-"));
|
|
84520
84550
|
const tmpPy = join80(tmp, "check.py");
|
|
84521
84551
|
try {
|
|
84522
|
-
|
|
84552
|
+
writeFileSync40(tmpPy, content);
|
|
84523
84553
|
const r = spawnSync12("python3", ["-m", "py_compile", tmpPy], {
|
|
84524
84554
|
encoding: "utf-8"
|
|
84525
84555
|
});
|
|
@@ -84559,7 +84589,7 @@ function writePersonalSkill(targetDir, files) {
|
|
|
84559
84589
|
}
|
|
84560
84590
|
} catch {}
|
|
84561
84591
|
if (targetIsSymlink) {
|
|
84562
|
-
|
|
84592
|
+
fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
|
|
84563
84593
|
}
|
|
84564
84594
|
mkdirSync46(dirname24(targetDir), { recursive: true, mode: 493 });
|
|
84565
84595
|
const staging = mkdtempSync6(join80(dirname24(targetDir), `.skill-personal-stage-`));
|
|
@@ -84570,7 +84600,7 @@ function writePersonalSkill(targetDir, files) {
|
|
|
84570
84600
|
mkdirSync46(dirname24(full), { recursive: true, mode: 493 });
|
|
84571
84601
|
const fd = openSync16(full, "wx");
|
|
84572
84602
|
try {
|
|
84573
|
-
|
|
84603
|
+
writeFileSync40(fd, content);
|
|
84574
84604
|
} finally {
|
|
84575
84605
|
closeSync16(fd);
|
|
84576
84606
|
}
|
|
@@ -84611,7 +84641,7 @@ function writePersonalSkill(targetDir, files) {
|
|
|
84611
84641
|
function loadValidateWrite(agentsRoot, agent, name, files, ensureNew) {
|
|
84612
84642
|
sweepTrash(agentsRoot, agent);
|
|
84613
84643
|
if (!SKILL_SLUG_RE.test(name)) {
|
|
84614
|
-
|
|
84644
|
+
fail4(`skill name must match ${SKILL_SLUG_RE.source}: got ${JSON.stringify(name)}`);
|
|
84615
84645
|
}
|
|
84616
84646
|
const target = personalSkillDir(agentsRoot, agent, name);
|
|
84617
84647
|
const exists = (() => {
|
|
@@ -84623,10 +84653,10 @@ function loadValidateWrite(agentsRoot, agent, name, files, ensureNew) {
|
|
|
84623
84653
|
}
|
|
84624
84654
|
})();
|
|
84625
84655
|
if (ensureNew && exists) {
|
|
84626
|
-
|
|
84656
|
+
fail4(`personal skill ${JSON.stringify(name)} already exists for agent ${JSON.stringify(agent)}. ` + `Use \`skill edit-personal\` to overwrite, or \`skill remove-personal\` first.`, 9);
|
|
84627
84657
|
}
|
|
84628
84658
|
if (!ensureNew && !exists) {
|
|
84629
|
-
|
|
84659
|
+
fail4(`personal skill ${JSON.stringify(name)} does not exist for agent ${JSON.stringify(agent)}. ` + `Use \`skill init-personal\` to create it.`, 9);
|
|
84630
84660
|
}
|
|
84631
84661
|
const v = validateSkillBundle(name, files);
|
|
84632
84662
|
if (!v.ok) {
|
|
@@ -84652,16 +84682,16 @@ function loadFiles(opts) {
|
|
|
84652
84682
|
}
|
|
84653
84683
|
const p = resolve47(opts.from);
|
|
84654
84684
|
if (!existsSync81(p)) {
|
|
84655
|
-
|
|
84685
|
+
fail4(`--from path does not exist: ${opts.from}`);
|
|
84656
84686
|
}
|
|
84657
84687
|
const st = statSync32(p);
|
|
84658
84688
|
if (st.isDirectory()) {
|
|
84659
84689
|
return loadFromDir2(p);
|
|
84660
84690
|
}
|
|
84661
84691
|
if (p.endsWith(".md")) {
|
|
84662
|
-
return { "SKILL.md":
|
|
84692
|
+
return { "SKILL.md": readFileSync68(p, "utf-8") };
|
|
84663
84693
|
}
|
|
84664
|
-
|
|
84694
|
+
fail4(`--from must be a directory or a .md file. Got: ${opts.from}`);
|
|
84665
84695
|
}
|
|
84666
84696
|
function initPersonalAction(name, opts) {
|
|
84667
84697
|
const agent = resolveAgent(opts);
|
|
@@ -84707,18 +84737,18 @@ function defaultBundledRoot() {
|
|
|
84707
84737
|
function resolveCloneSource(source, opts) {
|
|
84708
84738
|
const m = CLONE_SOURCE_RE.exec(source);
|
|
84709
84739
|
if (!m) {
|
|
84710
|
-
|
|
84740
|
+
fail4(`source must be \`shared:<name>\` or \`bundled:<name>\` (got ${JSON.stringify(source)})`);
|
|
84711
84741
|
}
|
|
84712
84742
|
const tier = m[1];
|
|
84713
84743
|
const slug = m[2];
|
|
84714
84744
|
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
84715
84745
|
const dir = join80(root, slug);
|
|
84716
84746
|
if (!existsSync81(dir)) {
|
|
84717
|
-
|
|
84747
|
+
fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
84718
84748
|
}
|
|
84719
84749
|
const st = lstatSync9(dir);
|
|
84720
84750
|
if (st.isSymbolicLink()) {
|
|
84721
|
-
|
|
84751
|
+
fail4(`clone source ${JSON.stringify(source)} is a symlink at ${dir}; ` + `point clone at the canonical pool path instead`);
|
|
84722
84752
|
}
|
|
84723
84753
|
return { tier, slug, dir };
|
|
84724
84754
|
}
|
|
@@ -84745,10 +84775,10 @@ function readSourceFiles(dir) {
|
|
|
84745
84775
|
try {
|
|
84746
84776
|
const st = lstatSync9(full);
|
|
84747
84777
|
if (st.size > CLONE_MAX_FILE_BYTES) {
|
|
84748
|
-
|
|
84778
|
+
fail4(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
|
|
84749
84779
|
}
|
|
84750
84780
|
} catch {}
|
|
84751
|
-
files[rel] =
|
|
84781
|
+
files[rel] = readFileSync68(full, "utf-8");
|
|
84752
84782
|
}
|
|
84753
84783
|
}
|
|
84754
84784
|
};
|
|
@@ -84779,11 +84809,11 @@ function clonePersonalAction(source, opts) {
|
|
|
84779
84809
|
const src = resolveCloneSource(source, opts);
|
|
84780
84810
|
const newName = opts.name ?? src.slug;
|
|
84781
84811
|
if (!SKILL_SLUG_RE.test(newName)) {
|
|
84782
|
-
|
|
84812
|
+
fail4(`destination name must match ${SKILL_SLUG_RE.source}: got ${JSON.stringify(newName)}`);
|
|
84783
84813
|
}
|
|
84784
84814
|
const { files, skipped } = readSourceFiles(src.dir);
|
|
84785
84815
|
if (!files["SKILL.md"]) {
|
|
84786
|
-
|
|
84816
|
+
fail4(`source ${JSON.stringify(source)} has no SKILL.md at ${src.dir}`);
|
|
84787
84817
|
}
|
|
84788
84818
|
if (newName !== src.slug) {
|
|
84789
84819
|
files["SKILL.md"] = rewriteSkillMdName(files["SKILL.md"], newName);
|
|
@@ -84821,18 +84851,18 @@ function removePersonalAction(name, opts) {
|
|
|
84821
84851
|
const agentsRoot = resolveAgentsRoot(opts);
|
|
84822
84852
|
sweepTrash(agentsRoot, agent);
|
|
84823
84853
|
if (!SKILL_SLUG_RE.test(name)) {
|
|
84824
|
-
|
|
84854
|
+
fail4(`skill name must match ${SKILL_SLUG_RE.source}: got ${JSON.stringify(name)}`);
|
|
84825
84855
|
}
|
|
84826
84856
|
const target = personalSkillDir(agentsRoot, agent, name);
|
|
84827
84857
|
try {
|
|
84828
84858
|
const st = lstatSync9(target);
|
|
84829
84859
|
if (st.isSymbolicLink()) {
|
|
84830
|
-
|
|
84860
|
+
fail4(`refusing to remove symlink at ${target}; investigate manually`);
|
|
84831
84861
|
}
|
|
84832
84862
|
} catch (err2) {
|
|
84833
84863
|
const e = err2;
|
|
84834
84864
|
if (e.code === "ENOENT") {
|
|
84835
|
-
|
|
84865
|
+
fail4(`personal skill ${JSON.stringify(name)} does not exist for agent ${JSON.stringify(agent)}`, 1);
|
|
84836
84866
|
}
|
|
84837
84867
|
throw err2;
|
|
84838
84868
|
}
|
|
@@ -84917,7 +84947,7 @@ function registerSkillPersonalCommands(program3) {
|
|
|
84917
84947
|
// src/cli/skill-search.ts
|
|
84918
84948
|
init_helpers();
|
|
84919
84949
|
var import_yaml23 = __toESM(require_dist(), 1);
|
|
84920
|
-
import { existsSync as existsSync82, readdirSync as readdirSync32, readFileSync as
|
|
84950
|
+
import { existsSync as existsSync82, readdirSync as readdirSync32, readFileSync as readFileSync69, statSync as statSync33 } from "node:fs";
|
|
84921
84951
|
import { homedir as homedir47 } from "node:os";
|
|
84922
84952
|
import { join as join81, resolve as resolve48 } from "node:path";
|
|
84923
84953
|
var PERSONAL_PREFIX2 = "personal-";
|
|
@@ -84938,7 +84968,7 @@ function readSkillFrontmatter(skillDir) {
|
|
|
84938
84968
|
return null;
|
|
84939
84969
|
let content;
|
|
84940
84970
|
try {
|
|
84941
|
-
content =
|
|
84971
|
+
content = readFileSync69(mdPath, "utf-8");
|
|
84942
84972
|
} catch {
|
|
84943
84973
|
return null;
|
|
84944
84974
|
}
|
|
@@ -85210,7 +85240,7 @@ function registerHostdMcpCommand(program3) {
|
|
|
85210
85240
|
// src/cli/hostd.ts
|
|
85211
85241
|
init_source();
|
|
85212
85242
|
init_helpers();
|
|
85213
|
-
import { existsSync as existsSync84, mkdirSync as mkdirSync47, readdirSync as readdirSync33, readFileSync as
|
|
85243
|
+
import { existsSync as existsSync84, mkdirSync as mkdirSync47, readdirSync as readdirSync33, readFileSync as readFileSync71, writeFileSync as writeFileSync41, statSync as statSync34, copyFileSync as copyFileSync12 } from "node:fs";
|
|
85214
85244
|
import { homedir as homedir48 } from "node:os";
|
|
85215
85245
|
import { join as join82 } from "node:path";
|
|
85216
85246
|
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
@@ -85386,7 +85416,7 @@ async function doInstall(opts, program3) {
|
|
|
85386
85416
|
const bak = backupExistingCompose();
|
|
85387
85417
|
if (bak)
|
|
85388
85418
|
console.log(source_default.dim(` Backed up existing compose to ${bak}`));
|
|
85389
|
-
|
|
85419
|
+
writeFileSync41(composePath, yaml, "utf8");
|
|
85390
85420
|
console.log(source_default.green(` \u2713 Wrote ${composePath}`));
|
|
85391
85421
|
const adminAgents = Object.entries(cfg.agents ?? {}).filter(([, a]) => a?.admin === true).map(([name]) => name);
|
|
85392
85422
|
console.log(source_default.dim(` agents served (one socket each): ${allAgents.length === 0 ? "(none)" : allAgents.join(", ")}`));
|
|
@@ -85493,7 +85523,7 @@ function registerHostdCommand(program3) {
|
|
|
85493
85523
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
85494
85524
|
return;
|
|
85495
85525
|
}
|
|
85496
|
-
const raw =
|
|
85526
|
+
const raw = readFileSync71(logPath, "utf-8");
|
|
85497
85527
|
const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
|
|
85498
85528
|
const filters = {
|
|
85499
85529
|
agent: opts.agent,
|
|
@@ -85535,7 +85565,7 @@ The log is created when hostd handles its first privileged-verb request.`));
|
|
|
85535
85565
|
// src/cli/webd.ts
|
|
85536
85566
|
init_source();
|
|
85537
85567
|
init_helpers();
|
|
85538
|
-
import { existsSync as existsSync85, mkdirSync as mkdirSync48, writeFileSync as
|
|
85568
|
+
import { existsSync as existsSync85, mkdirSync as mkdirSync48, writeFileSync as writeFileSync42, copyFileSync as copyFileSync13 } from "node:fs";
|
|
85539
85569
|
import { homedir as homedir49 } from "node:os";
|
|
85540
85570
|
import { join as join83 } from "node:path";
|
|
85541
85571
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
@@ -85671,7 +85701,7 @@ async function doInstall2(opts, program3) {
|
|
|
85671
85701
|
const bak = backupExistingCompose2();
|
|
85672
85702
|
if (bak)
|
|
85673
85703
|
console.log(source_default.dim(` Backed up existing compose to ${bak}`));
|
|
85674
|
-
|
|
85704
|
+
writeFileSync42(composePath, yaml, "utf8");
|
|
85675
85705
|
console.log(source_default.green(` \u2713 Wrote ${composePath}`));
|
|
85676
85706
|
console.log(source_default.dim(` running as uid ${operatorUid} (operator), network_mode: host`));
|
|
85677
85707
|
console.log(source_default.dim(` Pulling ghcr.io/switchroom/switchroom-web:${imageTag}\u2026`));
|
|
@@ -85776,6 +85806,7 @@ registerTopicsCommand(program3);
|
|
|
85776
85806
|
registerAuthCommand(program3);
|
|
85777
85807
|
registerVaultCommand(program3);
|
|
85778
85808
|
registerTelegramCommand(program3);
|
|
85809
|
+
registerLinearAgentCommand(program3);
|
|
85779
85810
|
registerMemoryCommand(program3);
|
|
85780
85811
|
registerWebCommand(program3);
|
|
85781
85812
|
registerHandoffCommand(program3);
|