switchroom 0.13.32 → 0.13.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/timezone-hook.sh +1 -1
- package/dist/agent-scheduler/index.js +8 -1
- package/dist/auth-broker/index.js +8 -1
- package/dist/cli/switchroom.js +627 -343
- package/dist/host-control/main.js +5163 -192
- package/dist/vault/approvals/kernel-server.js +9 -2
- package/dist/vault/broker/server.js +9 -2
- package/package.json +1 -1
- package/profiles/default/CLAUDE.md.hbs +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +80 -9
- package/telegram-plugin/docs/waiting-ux-spec.md +40 -0
- package/telegram-plugin/gateway/error-envelope-card.ts +64 -0
- package/telegram-plugin/gateway/gateway.ts +70 -15
- package/telegram-plugin/gateway/unhandled-rejection-policy.ts +46 -1
- package/telegram-plugin/tests/boot-clears-clean-shutdown-marker.test.ts +75 -0
- package/telegram-plugin/tests/error-envelope-unlock-card.test.ts +79 -0
- package/telegram-plugin/tests/silent-end-integration.test.ts +268 -0
- package/telegram-plugin/tests/silent-end.test.ts +105 -0
- package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +51 -6
package/dist/cli/switchroom.js
CHANGED
|
@@ -13520,7 +13520,7 @@ var init_zod = __esm(() => {
|
|
|
13520
13520
|
});
|
|
13521
13521
|
|
|
13522
13522
|
// src/config/schema.ts
|
|
13523
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, ReactionsSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, HostControlConfigSchema, HostdConfigSchema, SwitchroomConfigSchema;
|
|
13523
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, ReactionsSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, HostdConfigSchema, SwitchroomConfigSchema;
|
|
13524
13524
|
var init_schema = __esm(() => {
|
|
13525
13525
|
init_zod();
|
|
13526
13526
|
CodeRepoEntrySchema = exports_external.object({
|
|
@@ -13866,8 +13866,15 @@ var init_schema = __esm(() => {
|
|
|
13866
13866
|
weekly_budget_usd: exports_external.number().positive().optional().describe("Weekly USD spend budget. If unset, the greeting shows raw usage only."),
|
|
13867
13867
|
monthly_budget_usd: exports_external.number().positive().optional().describe("Monthly USD spend budget. If unset, the greeting shows raw usage only.")
|
|
13868
13868
|
});
|
|
13869
|
+
AutoReleaseCheckSchema = exports_external.object({
|
|
13870
|
+
enabled: exports_external.boolean().default(false).describe("When true, hostd polls the remote release tag every " + "`interval_minutes` and applies + restarts the fleet when a new " + "release is detected. Default false \u2014 opt-in."),
|
|
13871
|
+
interval_minutes: exports_external.number().int().min(5).max(1440).default(5).describe("Poll interval in minutes. Floor of 5m matches the agent-config " + "cron rate limit; ceiling of 1440m (24h) is a sanity cap."),
|
|
13872
|
+
apply_on_detect: exports_external.boolean().default(true).describe("When false, hostd logs `release_detected` but does NOT call " + "update_apply / restart all. Useful for dogfooding the detector " + "without rolling the fleet."),
|
|
13873
|
+
image_ref: exports_external.string().default("ghcr.io/switchroom/switchroom-agent:latest").describe("Image reference whose remote digest is compared to the local " + "image digest. Defaults to the agent image's :latest tag, which " + "is the canonical signal that a release has been promoted.")
|
|
13874
|
+
});
|
|
13869
13875
|
HostControlConfigSchema = exports_external.object({
|
|
13870
|
-
enabled: exports_external.boolean().default(true).describe("Whether the host-control daemon is in use. Default: true (since " + "RFC C Phase 2 default-flip \u2014 the gateway's /restart, /new, /reset, " + "and /update apply slash-commands all dispatch through hostd, and " + "without it those verbs fail on docker-mode installs because the " + "agent container has no docker binary/socket). " + "When true, the compose generator emits per-agent bind mounts " + "at `~/.switchroom/hostd/<name>/sock` for every admin-flagged " + "agent. Install the daemon with `switchroom hostd install` \u2014 " + "it runs as a docker container in its own compose project " + "(`switchroom-hostd`), separate from the agent fleet's compose " + "project so `up -d --remove-orphans` cycles of the fleet " + "can't recreate the daemon mid-RPC. See RFC C \u00a75.1. " + "Set enabled: false only on legacy systemd-mode installs that " + "still rely on the in-container `spawnSwitchroomDetached` " + "shellout (removal is tracked as RFC C Phase 3).")
|
|
13876
|
+
enabled: exports_external.boolean().default(true).describe("Whether the host-control daemon is in use. Default: true (since " + "RFC C Phase 2 default-flip \u2014 the gateway's /restart, /new, /reset, " + "and /update apply slash-commands all dispatch through hostd, and " + "without it those verbs fail on docker-mode installs because the " + "agent container has no docker binary/socket). " + "When true, the compose generator emits per-agent bind mounts " + "at `~/.switchroom/hostd/<name>/sock` for every admin-flagged " + "agent. Install the daemon with `switchroom hostd install` \u2014 " + "it runs as a docker container in its own compose project " + "(`switchroom-hostd`), separate from the agent fleet's compose " + "project so `up -d --remove-orphans` cycles of the fleet " + "can't recreate the daemon mid-RPC. See RFC C \u00a75.1. " + "Set enabled: false only on legacy systemd-mode installs that " + "still rely on the in-container `spawnSwitchroomDetached` " + "shellout (removal is tracked as RFC C Phase 3)."),
|
|
13877
|
+
auto_release_check: AutoReleaseCheckSchema.default({}).describe("Pull-based release-triggered fleet restart (#1743). hostd polls " + "the remote release tag on a fixed interval and applies + " + "restarts the fleet (graceful) when a new release is detected. " + "Opt-in: default enabled=false.")
|
|
13871
13878
|
});
|
|
13872
13879
|
HostdConfigSchema = exports_external.object({
|
|
13873
13880
|
config_edit_enabled: exports_external.boolean().default(false).describe("Opt-in toggle for the `config_propose_edit` hostd verb (RFC " + "admin-agent-config-edit \u00a73). Default false \u2014 the verb returns " + "`E_CONFIG_EDIT_DISABLED` until the operator explicitly flips " + "this to true. When true (and once PR 1c lands the apply path), " + "admin agents can propose unified-diff patches against " + "`/state/config/switchroom.yaml`, gated by an operator approval " + "card in the primary chat. Same trust posture as `update_apply` " + "and `agent_restart`: the human-in-the-loop tap is the security " + "boundary, not the agent's judgement."),
|
|
@@ -29101,7 +29108,7 @@ function decodeResponse3(line) {
|
|
|
29101
29108
|
const obj = JSON.parse(line);
|
|
29102
29109
|
return ResponseSchema3.parse(obj);
|
|
29103
29110
|
}
|
|
29104
|
-
var MAX_FRAME_BYTES3, RequestEnvelope, AgentRestartRequestSchema, UpgradeStatusRequestSchema, GetStatusRequestSchema, AgentNameSchema2, UpdateCheckRequestSchema, UpdateApplyRequestSchema, ApplyRequestSchema, AgentStartRequestSchema, AgentStopRequestSchema, AgentLogsRequestSchema, AgentExecRequestSchema, DoctorRequestSchema, AgentSmokeRequestSchema, ConfigProposeEditRequestSchema, RequestSchema3, ResultSchema, ResponseEnvelope, ResponseSchema3;
|
|
29111
|
+
var MAX_FRAME_BYTES3, RequestEnvelope, AgentRestartRequestSchema, UpgradeStatusRequestSchema, GetStatusRequestSchema, AgentNameSchema2, UpdateCheckRequestSchema, UpdateApplyRequestSchema, ApplyRequestSchema, AgentStartRequestSchema, AgentStopRequestSchema, AgentLogsRequestSchema, AgentExecRequestSchema, DoctorRequestSchema, AgentSmokeRequestSchema, ConfigProposeEditRequestSchema, RequestSchema3, ResultSchema, ErrorFixSchema, ErrorEnvelopeSchema, ResponseEnvelope, ResponseSchema3;
|
|
29105
29112
|
var init_protocol3 = __esm(() => {
|
|
29106
29113
|
init_zod();
|
|
29107
29114
|
MAX_FRAME_BYTES3 = 64 * 1024;
|
|
@@ -29220,6 +29227,45 @@ var init_protocol3 = __esm(() => {
|
|
|
29220
29227
|
ConfigProposeEditRequestSchema
|
|
29221
29228
|
]);
|
|
29222
29229
|
ResultSchema = exports_external.enum(["started", "completed", "denied", "error"]);
|
|
29230
|
+
ErrorFixSchema = exports_external.discriminatedUnion("kind", [
|
|
29231
|
+
exports_external.object({
|
|
29232
|
+
kind: exports_external.literal("flip_yaml_flag"),
|
|
29233
|
+
yaml_path: exports_external.string(),
|
|
29234
|
+
to: exports_external.unknown()
|
|
29235
|
+
}),
|
|
29236
|
+
exports_external.object({
|
|
29237
|
+
kind: exports_external.literal("request_vault_grant"),
|
|
29238
|
+
vault_key: exports_external.string()
|
|
29239
|
+
}),
|
|
29240
|
+
exports_external.object({
|
|
29241
|
+
kind: exports_external.literal("operator_action"),
|
|
29242
|
+
subkind: exports_external.enum(["policy_denied", "infra", "out_of_scope"]),
|
|
29243
|
+
operator_steps: exports_external.array(exports_external.string()).min(1).optional()
|
|
29244
|
+
}),
|
|
29245
|
+
exports_external.object({
|
|
29246
|
+
kind: exports_external.literal("retry_after"),
|
|
29247
|
+
retry_at: exports_external.string()
|
|
29248
|
+
}),
|
|
29249
|
+
exports_external.object({
|
|
29250
|
+
kind: exports_external.literal("quota_exceeded"),
|
|
29251
|
+
quota: exports_external.string(),
|
|
29252
|
+
current: exports_external.number(),
|
|
29253
|
+
limit: exports_external.number()
|
|
29254
|
+
}),
|
|
29255
|
+
exports_external.object({
|
|
29256
|
+
kind: exports_external.literal("bad_input"),
|
|
29257
|
+
field: exports_external.string().optional()
|
|
29258
|
+
})
|
|
29259
|
+
]);
|
|
29260
|
+
ErrorEnvelopeSchema = exports_external.object({
|
|
29261
|
+
v: exports_external.literal(1),
|
|
29262
|
+
code: exports_external.string().regex(/^(E_[A-Z0-9_]+|VAULT-[A-Z-]+)$/),
|
|
29263
|
+
human: exports_external.string().min(1),
|
|
29264
|
+
why: exports_external.string().optional(),
|
|
29265
|
+
fix: ErrorFixSchema.optional(),
|
|
29266
|
+
docs: exports_external.string().url().optional(),
|
|
29267
|
+
request_id: exports_external.string().min(1)
|
|
29268
|
+
});
|
|
29223
29269
|
ResponseEnvelope = {
|
|
29224
29270
|
v: exports_external.literal(1),
|
|
29225
29271
|
request_id: exports_external.string().min(1).max(128),
|
|
@@ -29229,7 +29275,8 @@ var init_protocol3 = __esm(() => {
|
|
|
29229
29275
|
audit_id: exports_external.string().min(1).optional(),
|
|
29230
29276
|
stdout_tail: exports_external.string().optional(),
|
|
29231
29277
|
stderr_tail: exports_external.string().optional(),
|
|
29232
|
-
error: exports_external.string().optional()
|
|
29278
|
+
error: exports_external.string().optional(),
|
|
29279
|
+
error_envelope: ErrorEnvelopeSchema.optional()
|
|
29233
29280
|
};
|
|
29234
29281
|
ResponseSchema3 = exports_external.object(ResponseEnvelope);
|
|
29235
29282
|
});
|
|
@@ -29400,6 +29447,220 @@ var init_doctor_agent_smoke = __esm(() => {
|
|
|
29400
29447
|
init_client4();
|
|
29401
29448
|
});
|
|
29402
29449
|
|
|
29450
|
+
// src/cli/doctor-vault-broker-durability.ts
|
|
29451
|
+
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
29452
|
+
import { existsSync as existsSync50, statSync as statSync22 } from "node:fs";
|
|
29453
|
+
import { homedir as homedir27 } from "node:os";
|
|
29454
|
+
import { join as join46 } from "node:path";
|
|
29455
|
+
function probeBindMountInode(hostPath, brokerContainerPath, opts) {
|
|
29456
|
+
const statHost = opts?.statHost ?? defaultStatHost;
|
|
29457
|
+
const statBroker = opts?.statBroker ?? defaultStatBroker;
|
|
29458
|
+
const host = statHost(hostPath);
|
|
29459
|
+
if (host === null)
|
|
29460
|
+
return { kind: "host-missing", hostPath };
|
|
29461
|
+
const broker = statBroker(brokerContainerPath);
|
|
29462
|
+
if (broker.kind !== "ok-with-stat")
|
|
29463
|
+
return broker;
|
|
29464
|
+
if (String(host.ino) === broker.ino && host.size === broker.size) {
|
|
29465
|
+
return { kind: "ok" };
|
|
29466
|
+
}
|
|
29467
|
+
return {
|
|
29468
|
+
kind: "mismatch",
|
|
29469
|
+
hostInode: String(host.ino),
|
|
29470
|
+
brokerInode: broker.ino,
|
|
29471
|
+
hostSize: host.size,
|
|
29472
|
+
brokerSize: broker.size
|
|
29473
|
+
};
|
|
29474
|
+
}
|
|
29475
|
+
function defaultStatHost(p) {
|
|
29476
|
+
if (!existsSync50(p))
|
|
29477
|
+
return null;
|
|
29478
|
+
try {
|
|
29479
|
+
const s = statSync22(p, { bigint: true });
|
|
29480
|
+
return { ino: s.ino, size: Number(s.size) };
|
|
29481
|
+
} catch {
|
|
29482
|
+
return null;
|
|
29483
|
+
}
|
|
29484
|
+
}
|
|
29485
|
+
function defaultStatBroker(p) {
|
|
29486
|
+
const r = spawnDockerStat(p);
|
|
29487
|
+
if (r.error || r.status === null)
|
|
29488
|
+
return { kind: "broker-unreachable" };
|
|
29489
|
+
if (r.status !== 0) {
|
|
29490
|
+
if (r.status >= 125)
|
|
29491
|
+
return { kind: "broker-unreachable" };
|
|
29492
|
+
return {
|
|
29493
|
+
kind: "broker-stat-failed",
|
|
29494
|
+
msg: r.stderr?.trim() || `exit ${r.status}`
|
|
29495
|
+
};
|
|
29496
|
+
}
|
|
29497
|
+
const out = r.stdout.trim();
|
|
29498
|
+
const [inoStr, sizeStr] = out.split(/\s+/);
|
|
29499
|
+
const size = Number(sizeStr);
|
|
29500
|
+
if (!inoStr || !Number.isFinite(size)) {
|
|
29501
|
+
return {
|
|
29502
|
+
kind: "broker-stat-failed",
|
|
29503
|
+
msg: `unparseable stat output: ${out}`
|
|
29504
|
+
};
|
|
29505
|
+
}
|
|
29506
|
+
return { kind: "ok-with-stat", ino: inoStr, size };
|
|
29507
|
+
}
|
|
29508
|
+
function spawnDockerStat(p) {
|
|
29509
|
+
try {
|
|
29510
|
+
const stdout = execFileSync14("docker", ["exec", "switchroom-vault-broker", "stat", "-c", "%i %s", p], { stdio: ["ignore", "pipe", "pipe"], timeout: 3000, encoding: "utf8" });
|
|
29511
|
+
return { status: 0, stdout, stderr: "", error: null };
|
|
29512
|
+
} catch (err) {
|
|
29513
|
+
const e = err;
|
|
29514
|
+
return {
|
|
29515
|
+
status: typeof e.status === "number" ? e.status : null,
|
|
29516
|
+
stdout: e.stdout ?? "",
|
|
29517
|
+
stderr: e.stderr ?? "",
|
|
29518
|
+
error: e.status === undefined ? new Error(e.message ?? "spawn failed") : null
|
|
29519
|
+
};
|
|
29520
|
+
}
|
|
29521
|
+
}
|
|
29522
|
+
function formatBindMountResult(name, hostPath, brokerContainerPath, result) {
|
|
29523
|
+
if (result.kind === "ok") {
|
|
29524
|
+
return {
|
|
29525
|
+
name,
|
|
29526
|
+
status: "ok",
|
|
29527
|
+
detail: `${hostPath} == ${brokerContainerPath} (same inode)`
|
|
29528
|
+
};
|
|
29529
|
+
}
|
|
29530
|
+
if (result.kind === "host-missing") {
|
|
29531
|
+
return {
|
|
29532
|
+
name,
|
|
29533
|
+
status: "warn",
|
|
29534
|
+
detail: `host file ${hostPath} missing \u2014 pre-created by \`switchroom apply\` on greenfield`,
|
|
29535
|
+
fix: "Run `switchroom apply` to pre-create the host file at the correct mode"
|
|
29536
|
+
};
|
|
29537
|
+
}
|
|
29538
|
+
if (result.kind === "broker-unreachable") {
|
|
29539
|
+
return {
|
|
29540
|
+
name,
|
|
29541
|
+
status: "skip",
|
|
29542
|
+
detail: "vault-broker container unreachable \u2014 bind mount unverified"
|
|
29543
|
+
};
|
|
29544
|
+
}
|
|
29545
|
+
if (result.kind === "broker-stat-failed") {
|
|
29546
|
+
return {
|
|
29547
|
+
name,
|
|
29548
|
+
status: "warn",
|
|
29549
|
+
detail: `broker stat failed: ${result.msg}`
|
|
29550
|
+
};
|
|
29551
|
+
}
|
|
29552
|
+
return {
|
|
29553
|
+
name,
|
|
29554
|
+
status: "fail",
|
|
29555
|
+
detail: `inode mismatch \u2014 bind mount is NOT wiring host to broker. ` + `host inode=${result.hostInode} size=${result.hostSize}; ` + `broker inode=${result.brokerInode} size=${result.brokerSize}. ` + `The broker is operating on an ephemeral container-local file; ` + `data written there evaporates on container recreate.`,
|
|
29556
|
+
fix: "Run `switchroom apply` to regenerate compose with the correct " + "bind mount, then `docker compose -p switchroom up -d vault-broker` " + "to recreate the broker container."
|
|
29557
|
+
};
|
|
29558
|
+
}
|
|
29559
|
+
function probeBrokerUnlocked(opts) {
|
|
29560
|
+
const status = (opts?.statusProbe ?? defaultBrokerStatusProbe)();
|
|
29561
|
+
if (status === null) {
|
|
29562
|
+
return {
|
|
29563
|
+
name: "vault-broker unlocked (state)",
|
|
29564
|
+
status: "skip",
|
|
29565
|
+
detail: "vault-broker container unreachable"
|
|
29566
|
+
};
|
|
29567
|
+
}
|
|
29568
|
+
if (!status.unlocked) {
|
|
29569
|
+
return {
|
|
29570
|
+
name: "vault-broker unlocked (state)",
|
|
29571
|
+
status: "fail",
|
|
29572
|
+
detail: `broker reports locked despite config \u2014 auto-unlock failed silently. ` + `Common causes: \`/etc/machine-id\` mount missing or differs from the ` + `host the blob was sealed on; vault-auto-unlock blob corrupted; ` + `vault passphrase was rotated without re-running ` + `\`switchroom vault broker enable-auto-unlock\`.`,
|
|
29573
|
+
fix: "Re-run `switchroom vault broker enable-auto-unlock` on the host " + "to re-seal the blob against the current machine-id + passphrase. " + "Then restart the broker (`docker compose -p switchroom restart vault-broker`)."
|
|
29574
|
+
};
|
|
29575
|
+
}
|
|
29576
|
+
return {
|
|
29577
|
+
name: "vault-broker unlocked (state)",
|
|
29578
|
+
status: "ok",
|
|
29579
|
+
detail: `${status.keyCount} key(s) loaded`
|
|
29580
|
+
};
|
|
29581
|
+
}
|
|
29582
|
+
function defaultBrokerStatusProbe() {
|
|
29583
|
+
try {
|
|
29584
|
+
const out = execFileSync14("switchroom", ["vault", "broker", "status"], { stdio: ["ignore", "pipe", "pipe"], timeout: 3000, encoding: "utf8" });
|
|
29585
|
+
const parsed = JSON.parse(out.trim());
|
|
29586
|
+
if (!parsed.running)
|
|
29587
|
+
return null;
|
|
29588
|
+
return { unlocked: parsed.unlocked, keyCount: parsed.keyCount };
|
|
29589
|
+
} catch {
|
|
29590
|
+
return null;
|
|
29591
|
+
}
|
|
29592
|
+
}
|
|
29593
|
+
function runVaultBrokerDurabilityChecks(_config, opts) {
|
|
29594
|
+
const home2 = homedir27();
|
|
29595
|
+
const probe2 = opts?.inodeProbe ?? probeBindMountInode;
|
|
29596
|
+
return [
|
|
29597
|
+
probeBrokerUnlocked(opts?.statusProbe),
|
|
29598
|
+
probeAutoUnlockBlob(home2),
|
|
29599
|
+
probeMachineIdMount(),
|
|
29600
|
+
formatBindMountResult("vault-broker: vault.enc bind mount", join46(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join46(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
|
|
29601
|
+
formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)", join46(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db", probe2(join46(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db")),
|
|
29602
|
+
formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join46(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join46(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log"))
|
|
29603
|
+
];
|
|
29604
|
+
}
|
|
29605
|
+
function probeAutoUnlockBlob(home2) {
|
|
29606
|
+
const blobPath = join46(home2, ".switchroom", "vault-auto-unlock");
|
|
29607
|
+
if (!existsSync50(blobPath)) {
|
|
29608
|
+
return {
|
|
29609
|
+
name: "vault-broker: auto-unlock blob",
|
|
29610
|
+
status: "warn",
|
|
29611
|
+
detail: `${blobPath} not present \u2014 broker will fall back to interactive unlock`,
|
|
29612
|
+
fix: "Run `switchroom vault broker enable-auto-unlock` to seal the blob with the current passphrase + machine-id"
|
|
29613
|
+
};
|
|
29614
|
+
}
|
|
29615
|
+
const sz = statSync22(blobPath).size;
|
|
29616
|
+
if (sz === 0) {
|
|
29617
|
+
return {
|
|
29618
|
+
name: "vault-broker: auto-unlock blob",
|
|
29619
|
+
status: "warn",
|
|
29620
|
+
detail: `${blobPath} is 0 bytes (placeholder) \u2014 broker will fall back to interactive unlock`,
|
|
29621
|
+
fix: "Run `switchroom vault broker enable-auto-unlock` to actually seal the blob"
|
|
29622
|
+
};
|
|
29623
|
+
}
|
|
29624
|
+
return {
|
|
29625
|
+
name: "vault-broker: auto-unlock blob",
|
|
29626
|
+
status: "ok",
|
|
29627
|
+
detail: `${blobPath} present (${sz} bytes, machine-bound)`
|
|
29628
|
+
};
|
|
29629
|
+
}
|
|
29630
|
+
function probeMachineIdMount() {
|
|
29631
|
+
const hostExists = existsSync50("/etc/machine-id");
|
|
29632
|
+
if (!hostExists) {
|
|
29633
|
+
return {
|
|
29634
|
+
name: "vault-broker: machine-id passthrough",
|
|
29635
|
+
status: "fail",
|
|
29636
|
+
detail: "/etc/machine-id missing on host \u2014 auto-unlock key derivation impossible",
|
|
29637
|
+
fix: "Generate a machine-id (`systemd-machine-id-setup`) and re-seal the auto-unlock blob"
|
|
29638
|
+
};
|
|
29639
|
+
}
|
|
29640
|
+
const r = spawnDockerStat("/etc/machine-id");
|
|
29641
|
+
if (r.error || r.status === null || r.status >= 125) {
|
|
29642
|
+
return {
|
|
29643
|
+
name: "vault-broker: machine-id passthrough",
|
|
29644
|
+
status: "skip",
|
|
29645
|
+
detail: "vault-broker container unreachable"
|
|
29646
|
+
};
|
|
29647
|
+
}
|
|
29648
|
+
if (r.status !== 0) {
|
|
29649
|
+
return {
|
|
29650
|
+
name: "vault-broker: machine-id passthrough",
|
|
29651
|
+
status: "fail",
|
|
29652
|
+
detail: "broker container has no /etc/machine-id \u2014 compose `/etc/machine-id:/etc/machine-id:ro` mount is missing",
|
|
29653
|
+
fix: "Run `switchroom apply` to regenerate compose with the machine-id passthrough"
|
|
29654
|
+
};
|
|
29655
|
+
}
|
|
29656
|
+
return {
|
|
29657
|
+
name: "vault-broker: machine-id passthrough",
|
|
29658
|
+
status: "ok",
|
|
29659
|
+
detail: "broker reads the host machine-id"
|
|
29660
|
+
};
|
|
29661
|
+
}
|
|
29662
|
+
var init_doctor_vault_broker_durability = () => {};
|
|
29663
|
+
|
|
29403
29664
|
// src/cli/doctor.ts
|
|
29404
29665
|
var exports_doctor = {};
|
|
29405
29666
|
__export(exports_doctor, {
|
|
@@ -29445,25 +29706,25 @@ import { execSync as execSync3, spawnSync as spawnSync7 } from "node:child_proce
|
|
|
29445
29706
|
import {
|
|
29446
29707
|
accessSync as accessSync2,
|
|
29447
29708
|
constants as fsConstants5,
|
|
29448
|
-
existsSync as
|
|
29709
|
+
existsSync as existsSync51,
|
|
29449
29710
|
lstatSync as lstatSync5,
|
|
29450
29711
|
mkdirSync as mkdirSync27,
|
|
29451
29712
|
readFileSync as readFileSync45,
|
|
29452
29713
|
readdirSync as readdirSync19,
|
|
29453
|
-
statSync as
|
|
29714
|
+
statSync as statSync23
|
|
29454
29715
|
} from "node:fs";
|
|
29455
|
-
import { dirname as dirname12, join as
|
|
29716
|
+
import { dirname as dirname12, join as join47, resolve as resolve30 } from "node:path";
|
|
29456
29717
|
import { createPublicKey, createPrivateKey } from "node:crypto";
|
|
29457
29718
|
function findInNvm(bin) {
|
|
29458
|
-
const nvmRoot =
|
|
29459
|
-
if (!
|
|
29719
|
+
const nvmRoot = join47(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
29720
|
+
if (!existsSync51(nvmRoot))
|
|
29460
29721
|
return null;
|
|
29461
29722
|
try {
|
|
29462
29723
|
const versions = readdirSync19(nvmRoot).sort().reverse();
|
|
29463
29724
|
for (const v of versions) {
|
|
29464
|
-
const candidate =
|
|
29725
|
+
const candidate = join47(nvmRoot, v, "bin", bin);
|
|
29465
29726
|
try {
|
|
29466
|
-
const s =
|
|
29727
|
+
const s = statSync23(candidate);
|
|
29467
29728
|
if (s.isFile() || s.isSymbolicLink()) {
|
|
29468
29729
|
return candidate;
|
|
29469
29730
|
}
|
|
@@ -29626,21 +29887,21 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
29626
29887
|
if (envBrowsersPath && envBrowsersPath.length > 0) {
|
|
29627
29888
|
cacheLocations.push(envBrowsersPath);
|
|
29628
29889
|
}
|
|
29629
|
-
cacheLocations.push(
|
|
29890
|
+
cacheLocations.push(join47(homeDir, ".cache", "ms-playwright"));
|
|
29630
29891
|
for (const cacheDir of cacheLocations) {
|
|
29631
|
-
if (!
|
|
29892
|
+
if (!existsSync51(cacheDir))
|
|
29632
29893
|
continue;
|
|
29633
29894
|
try {
|
|
29634
29895
|
const entries = readdirSync19(cacheDir).filter((e) => e.startsWith("chromium"));
|
|
29635
29896
|
for (const entry of entries) {
|
|
29636
29897
|
const candidates2 = [
|
|
29637
|
-
|
|
29638
|
-
|
|
29639
|
-
|
|
29640
|
-
|
|
29898
|
+
join47(cacheDir, entry, "chrome-linux64", "chrome"),
|
|
29899
|
+
join47(cacheDir, entry, "chrome-linux", "chrome"),
|
|
29900
|
+
join47(cacheDir, entry, "chrome-linux64", "headless_shell"),
|
|
29901
|
+
join47(cacheDir, entry, "chrome-linux", "headless_shell")
|
|
29641
29902
|
];
|
|
29642
29903
|
for (const path4 of candidates2) {
|
|
29643
|
-
if (
|
|
29904
|
+
if (existsSync51(path4))
|
|
29644
29905
|
return path4;
|
|
29645
29906
|
}
|
|
29646
29907
|
}
|
|
@@ -29720,8 +29981,8 @@ function checkConfig(config, configPath) {
|
|
|
29720
29981
|
function checkLegacyState() {
|
|
29721
29982
|
const results = [];
|
|
29722
29983
|
const h = process.env.HOME ?? "/root";
|
|
29723
|
-
const clerkDir =
|
|
29724
|
-
const clerkPresent =
|
|
29984
|
+
const clerkDir = join47(h, LEGACY_STATE_DIR);
|
|
29985
|
+
const clerkPresent = existsSync51(clerkDir);
|
|
29725
29986
|
results.push({
|
|
29726
29987
|
name: "legacy ~/.clerk state",
|
|
29727
29988
|
status: clerkPresent ? "warn" : "ok",
|
|
@@ -29730,7 +29991,7 @@ function checkLegacyState() {
|
|
|
29730
29991
|
fix: "Legacy state detected. Run `mv ~/.clerk ~/.switchroom` and rename " + "any top-level `clerk:` key in switchroom.yaml to `switchroom:`. " + "This back-compat shim is REMOVED in v0.13.0 \u2014 no automatic " + "migration exists."
|
|
29731
29992
|
} : {}
|
|
29732
29993
|
});
|
|
29733
|
-
const legacySock =
|
|
29994
|
+
const legacySock = join47(h, ".switchroom", "vault-broker.sock");
|
|
29734
29995
|
let sockStat = null;
|
|
29735
29996
|
try {
|
|
29736
29997
|
sockStat = lstatSync5(legacySock);
|
|
@@ -29851,7 +30112,7 @@ function checkVault(config) {
|
|
|
29851
30112
|
detail: "Approval auth: passphrase (two-factor)"
|
|
29852
30113
|
};
|
|
29853
30114
|
const pairsResult = checkVaultBrokerSocketPairs(config);
|
|
29854
|
-
if (!
|
|
30115
|
+
if (!existsSync51(vaultPath)) {
|
|
29855
30116
|
return [
|
|
29856
30117
|
postureResult,
|
|
29857
30118
|
{
|
|
@@ -30026,8 +30287,8 @@ async function checkHindsight(config) {
|
|
|
30026
30287
|
}
|
|
30027
30288
|
function checkPendingRetainsQueue(dir) {
|
|
30028
30289
|
const home2 = process.env.HOME ?? "";
|
|
30029
|
-
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ??
|
|
30030
|
-
if (!
|
|
30290
|
+
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join47(home2, ".hindsight", "pending-retains");
|
|
30291
|
+
if (!existsSync51(pendingDir)) {
|
|
30031
30292
|
return {
|
|
30032
30293
|
name: "pending-retains queue",
|
|
30033
30294
|
status: "ok",
|
|
@@ -30098,7 +30359,7 @@ function tryReadHostFile(path4) {
|
|
|
30098
30359
|
}
|
|
30099
30360
|
}
|
|
30100
30361
|
function parseEnvFile(path4) {
|
|
30101
|
-
if (!
|
|
30362
|
+
if (!existsSync51(path4))
|
|
30102
30363
|
return {};
|
|
30103
30364
|
let content;
|
|
30104
30365
|
try {
|
|
@@ -30157,7 +30418,7 @@ async function checkTelegram(config) {
|
|
|
30157
30418
|
const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
|
|
30158
30419
|
if (plugin !== "switchroom")
|
|
30159
30420
|
continue;
|
|
30160
|
-
const envPath =
|
|
30421
|
+
const envPath = join47(agentsDir, name, "telegram", ".env");
|
|
30161
30422
|
const read = tryReadHostFile(envPath);
|
|
30162
30423
|
if (read.kind === "eacces") {
|
|
30163
30424
|
results.push({
|
|
@@ -30209,7 +30470,7 @@ async function checkTelegram(config) {
|
|
|
30209
30470
|
}
|
|
30210
30471
|
function checkStartShStale(agentName, startShPath) {
|
|
30211
30472
|
const label = `${agentName}: start.sh scheduler block`;
|
|
30212
|
-
if (!
|
|
30473
|
+
if (!existsSync51(startShPath)) {
|
|
30213
30474
|
return {
|
|
30214
30475
|
name: label,
|
|
30215
30476
|
status: "warn",
|
|
@@ -30240,7 +30501,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
30240
30501
|
}
|
|
30241
30502
|
function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
30242
30503
|
const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
|
|
30243
|
-
const path4 =
|
|
30504
|
+
const path4 = join47(agentDir, "home", ".switchroom");
|
|
30244
30505
|
let stats;
|
|
30245
30506
|
try {
|
|
30246
30507
|
stats = lstatSync5(path4);
|
|
@@ -30277,8 +30538,8 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
|
30277
30538
|
}
|
|
30278
30539
|
function checkRepoHygiene(repoRoot) {
|
|
30279
30540
|
const results = [];
|
|
30280
|
-
const exportDir =
|
|
30281
|
-
if (
|
|
30541
|
+
const exportDir = join47(repoRoot, "clerk-export");
|
|
30542
|
+
if (existsSync51(exportDir)) {
|
|
30282
30543
|
results.push({
|
|
30283
30544
|
name: "repo hygiene: clerk-export/ on disk (#1072)",
|
|
30284
30545
|
status: "warn",
|
|
@@ -30286,8 +30547,8 @@ function checkRepoHygiene(repoRoot) {
|
|
|
30286
30547
|
fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
|
|
30287
30548
|
});
|
|
30288
30549
|
}
|
|
30289
|
-
const knownTarball =
|
|
30290
|
-
if (
|
|
30550
|
+
const knownTarball = join47(repoRoot, "clerk-export-with-secrets.tar.gz");
|
|
30551
|
+
if (existsSync51(knownTarball)) {
|
|
30291
30552
|
results.push({
|
|
30292
30553
|
name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
|
|
30293
30554
|
status: "warn",
|
|
@@ -30304,7 +30565,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
30304
30565
|
results.push({
|
|
30305
30566
|
name: `repo hygiene: ${name} on disk (#1072)`,
|
|
30306
30567
|
status: "warn",
|
|
30307
|
-
detail: `${
|
|
30568
|
+
detail: `${join47(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
|
|
30308
30569
|
fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
|
|
30309
30570
|
});
|
|
30310
30571
|
}
|
|
@@ -30327,10 +30588,10 @@ function checkRepoHygiene(repoRoot) {
|
|
|
30327
30588
|
}
|
|
30328
30589
|
function isSwitchroomCheckout(dir) {
|
|
30329
30590
|
try {
|
|
30330
|
-
if (!
|
|
30591
|
+
if (!existsSync51(join47(dir, ".git")))
|
|
30331
30592
|
return false;
|
|
30332
|
-
const pkgPath =
|
|
30333
|
-
if (!
|
|
30593
|
+
const pkgPath = join47(dir, "package.json");
|
|
30594
|
+
if (!existsSync51(pkgPath))
|
|
30334
30595
|
return false;
|
|
30335
30596
|
const pkg = JSON.parse(readFileSync45(pkgPath, "utf-8"));
|
|
30336
30597
|
return pkg.name === "switchroom";
|
|
@@ -30345,7 +30606,7 @@ function checkAgents(config, configPath) {
|
|
|
30345
30606
|
const authStatuses = getAllAuthStatuses(config);
|
|
30346
30607
|
for (const [name, agentConfig] of Object.entries(config.agents)) {
|
|
30347
30608
|
const agentDir = resolve30(agentsDir, name);
|
|
30348
|
-
if (!
|
|
30609
|
+
if (!existsSync51(agentDir)) {
|
|
30349
30610
|
results.push({
|
|
30350
30611
|
name: `${name}: scaffold`,
|
|
30351
30612
|
status: "fail",
|
|
@@ -30366,7 +30627,7 @@ function checkAgents(config, configPath) {
|
|
|
30366
30627
|
fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
|
|
30367
30628
|
});
|
|
30368
30629
|
}
|
|
30369
|
-
results.push(checkStartShStale(name,
|
|
30630
|
+
results.push(checkStartShStale(name, join47(agentDir, "start.sh")));
|
|
30370
30631
|
results.push(checkLeakedHomeSwitchroom(name, agentDir));
|
|
30371
30632
|
const status = statuses[name];
|
|
30372
30633
|
const active = status?.active ?? "unknown";
|
|
@@ -30443,8 +30704,8 @@ function checkAgents(config, configPath) {
|
|
|
30443
30704
|
}
|
|
30444
30705
|
}
|
|
30445
30706
|
if (agentConfig.channels?.telegram?.plugin === "switchroom") {
|
|
30446
|
-
const mcpJsonPath =
|
|
30447
|
-
if (!
|
|
30707
|
+
const mcpJsonPath = join47(agentDir, ".mcp.json");
|
|
30708
|
+
if (!existsSync51(mcpJsonPath)) {
|
|
30448
30709
|
results.push({
|
|
30449
30710
|
name: `${name}: .mcp.json`,
|
|
30450
30711
|
status: "fail",
|
|
@@ -30529,7 +30790,7 @@ function mffEnvPath(config) {
|
|
|
30529
30790
|
return agent ? resolve30(home2, ".switchroom/credentials", agent, "my-family-finance/.env") : resolve30(home2, ".switchroom/credentials/my-family-finance/.env");
|
|
30530
30791
|
}
|
|
30531
30792
|
function mffEnvState(envPath) {
|
|
30532
|
-
if (!
|
|
30793
|
+
if (!existsSync51(envPath))
|
|
30533
30794
|
return "absent";
|
|
30534
30795
|
try {
|
|
30535
30796
|
accessSync2(envPath, fsConstants5.R_OK);
|
|
@@ -30547,7 +30808,7 @@ function checkMffVaultKeyPresent(passphrase, vaultPath) {
|
|
|
30547
30808
|
fix: "Export SWITCHROOM_VAULT_PASSPHRASE to enable MFF vault probes"
|
|
30548
30809
|
};
|
|
30549
30810
|
}
|
|
30550
|
-
if (!
|
|
30811
|
+
if (!existsSync51(vaultPath)) {
|
|
30551
30812
|
return {
|
|
30552
30813
|
name: "mff: vault key present",
|
|
30553
30814
|
status: "fail",
|
|
@@ -30600,7 +30861,7 @@ function deriveEd25519PublicKeyBytes(keyMaterial) {
|
|
|
30600
30861
|
}
|
|
30601
30862
|
}
|
|
30602
30863
|
function checkMffVaultKeyFormat(passphrase, vaultPath) {
|
|
30603
|
-
if (!passphrase || !
|
|
30864
|
+
if (!passphrase || !existsSync51(vaultPath)) {
|
|
30604
30865
|
return {
|
|
30605
30866
|
name: "mff: vault key format",
|
|
30606
30867
|
status: "warn",
|
|
@@ -30744,8 +31005,8 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
|
|
|
30744
31005
|
};
|
|
30745
31006
|
}
|
|
30746
31007
|
const credDir = dirname12(envPath);
|
|
30747
|
-
const authScript =
|
|
30748
|
-
if (!
|
|
31008
|
+
const authScript = join47(credDir, "claude-auth.py");
|
|
31009
|
+
if (!existsSync51(authScript)) {
|
|
30749
31010
|
return {
|
|
30750
31011
|
name: "mff: auth flow",
|
|
30751
31012
|
status: "warn",
|
|
@@ -31023,6 +31284,10 @@ function registerDoctorCommand(program3) {
|
|
|
31023
31284
|
},
|
|
31024
31285
|
{ title: "Legacy State", results: checkLegacyState() },
|
|
31025
31286
|
{ title: "Vault", results: checkVault(config) },
|
|
31287
|
+
{
|
|
31288
|
+
title: "Vault-broker durability",
|
|
31289
|
+
results: runVaultBrokerDurabilityChecks(config)
|
|
31290
|
+
},
|
|
31026
31291
|
{ title: "Vault access", results: await runSecretAccessChecks(config) },
|
|
31027
31292
|
{ title: "Memory (Hindsight)", results: await checkHindsight(config) },
|
|
31028
31293
|
{ title: "Telegram", results: await checkTelegram(config) },
|
|
@@ -31106,6 +31371,7 @@ var init_doctor = __esm(() => {
|
|
|
31106
31371
|
init_doctor_inlined_secrets();
|
|
31107
31372
|
init_doctor_audit_integrity();
|
|
31108
31373
|
init_doctor_agent_smoke();
|
|
31374
|
+
init_doctor_vault_broker_durability();
|
|
31109
31375
|
MANIFEST_WARN_ONLY = new Set([
|
|
31110
31376
|
"@playwright/mcp",
|
|
31111
31377
|
"hindsight.backend",
|
|
@@ -47034,7 +47300,7 @@ __export(exports_server2, {
|
|
|
47034
47300
|
TOOLS: () => TOOLS2
|
|
47035
47301
|
});
|
|
47036
47302
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
47037
|
-
import { existsSync as
|
|
47303
|
+
import { existsSync as existsSync75, readFileSync as readFileSync60 } from "node:fs";
|
|
47038
47304
|
function selfSocketPath() {
|
|
47039
47305
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
47040
47306
|
}
|
|
@@ -47049,7 +47315,7 @@ async function dispatchTool2(name, args) {
|
|
|
47049
47315
|
return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
|
|
47050
47316
|
}
|
|
47051
47317
|
const sockPath = selfSocketPath();
|
|
47052
|
-
if (!
|
|
47318
|
+
if (!existsSync75(sockPath)) {
|
|
47053
47319
|
return errorText2(`hostd MCP: socket not bound at ${sockPath}. The host-control ` + `daemon is either not installed (run \`switchroom hostd install\`) ` + `or this agent isn't admin-flagged in switchroom.yaml. RFC C ` + `bind-mounts the per-agent socket only when host_control.enabled ` + `is true AND the agent has admin: true.`);
|
|
47054
47320
|
}
|
|
47055
47321
|
let req;
|
|
@@ -47184,22 +47450,30 @@ async function dispatchTool2(name, args) {
|
|
|
47184
47450
|
if (resp.result === "started" || resp.result === "completed") {
|
|
47185
47451
|
return jsonText2(resp);
|
|
47186
47452
|
}
|
|
47187
|
-
|
|
47188
|
-
|
|
47189
|
-
|
|
47190
|
-
|
|
47453
|
+
const content = [
|
|
47454
|
+
{ type: "text", text: JSON.stringify(resp) }
|
|
47455
|
+
];
|
|
47456
|
+
if (resp.error_envelope) {
|
|
47457
|
+
const env2 = resp.error_envelope;
|
|
47458
|
+
content.push({
|
|
47459
|
+
type: "text",
|
|
47460
|
+
text: `Structured error \u2014 fix.kind=${env2.fix?.kind ?? "none"}
|
|
47461
|
+
` + JSON.stringify(env2, null, 2)
|
|
47462
|
+
});
|
|
47463
|
+
}
|
|
47464
|
+
return { content, isError: true };
|
|
47191
47465
|
}
|
|
47192
47466
|
function resolveAuditLogPath() {
|
|
47193
47467
|
if (process.env.HOSTD_AUDIT_LOG_PATH)
|
|
47194
47468
|
return process.env.HOSTD_AUDIT_LOG_PATH;
|
|
47195
47469
|
const bindMounted = "/host-home/.switchroom/host-control-audit.log";
|
|
47196
|
-
if (
|
|
47470
|
+
if (existsSync75(bindMounted))
|
|
47197
47471
|
return bindMounted;
|
|
47198
47472
|
return defaultAuditLogPath2();
|
|
47199
47473
|
}
|
|
47200
47474
|
function getLastUpdateApplyStatus() {
|
|
47201
47475
|
const path8 = resolveAuditLogPath();
|
|
47202
|
-
if (!
|
|
47476
|
+
if (!existsSync75(path8)) {
|
|
47203
47477
|
return errorText2(`get_status: audit log not found at ${path8}. No update_apply has run yet?`);
|
|
47204
47478
|
}
|
|
47205
47479
|
let raw;
|
|
@@ -47437,8 +47711,8 @@ var {
|
|
|
47437
47711
|
} = import__.default;
|
|
47438
47712
|
|
|
47439
47713
|
// src/build-info.ts
|
|
47440
|
-
var VERSION = "0.13.
|
|
47441
|
-
var COMMIT_SHA = "
|
|
47714
|
+
var VERSION = "0.13.35";
|
|
47715
|
+
var COMMIT_SHA = "c41aabe5";
|
|
47442
47716
|
|
|
47443
47717
|
// src/cli/agent.ts
|
|
47444
47718
|
init_source();
|
|
@@ -49488,17 +49762,16 @@ function classifyChange(path, agentDir, useHotReloadStable) {
|
|
|
49488
49762
|
return "stale-till-restart";
|
|
49489
49763
|
}
|
|
49490
49764
|
function buildSettingsHooksBlock(p) {
|
|
49491
|
-
const { agentName, agentConfig, hindsightEnabled, useSwitchroomPlugin
|
|
49765
|
+
const { agentName, agentConfig, hindsightEnabled, useSwitchroomPlugin } = p;
|
|
49492
49766
|
const userHooks = translateHooksToClaudeShape(agentConfig.hooks);
|
|
49493
49767
|
const wrapper = `bash "${join8(DOCKER_BIN_PATH, "run-hook.sh")}"`;
|
|
49494
49768
|
const wrap = (source, command) => `${wrapper} ${shellSingleQuote(source)} ${command}`;
|
|
49495
49769
|
const handoffEnabled = agentConfig.session_continuity?.enabled !== false;
|
|
49496
|
-
const handoffConfigArg = configPath ? ` --config ${shellSingleQuote(DOCKER_CONFIG_PATH)}` : "";
|
|
49497
49770
|
const stopHooks = [];
|
|
49498
49771
|
if (handoffEnabled) {
|
|
49499
49772
|
stopHooks.push({
|
|
49500
49773
|
type: "command",
|
|
49501
|
-
command: wrap("hook:handoff", `switchroom
|
|
49774
|
+
command: wrap("hook:handoff", `switchroom handoff ${agentName}`),
|
|
49502
49775
|
timeout: 35,
|
|
49503
49776
|
async: true
|
|
49504
49777
|
});
|
|
@@ -69811,17 +70084,17 @@ init_doctor();
|
|
|
69811
70084
|
init_source();
|
|
69812
70085
|
init_loader();
|
|
69813
70086
|
init_lifecycle();
|
|
69814
|
-
import { cpSync as cpSync2, existsSync as
|
|
70087
|
+
import { cpSync as cpSync2, existsSync as existsSync52, mkdirSync as mkdirSync28, readFileSync as readFileSync46, realpathSync as realpathSync5, rmSync as rmSync12, statSync as statSync24 } from "node:fs";
|
|
69815
70088
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
69816
|
-
import { join as
|
|
69817
|
-
import { homedir as
|
|
69818
|
-
var DEFAULT_COMPOSE_PATH =
|
|
70089
|
+
import { join as join48, dirname as dirname13, resolve as resolve31 } from "node:path";
|
|
70090
|
+
import { homedir as homedir28 } from "node:os";
|
|
70091
|
+
var DEFAULT_COMPOSE_PATH = join48(homedir28(), ".switchroom", "compose", "docker-compose.yml");
|
|
69819
70092
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
69820
70093
|
let dir = dirname13(scriptPath);
|
|
69821
70094
|
for (let i = 0;i < 12; i++) {
|
|
69822
|
-
if (
|
|
70095
|
+
if (existsSync52(join48(dir, ".git"))) {
|
|
69823
70096
|
try {
|
|
69824
|
-
const pkg = JSON.parse(readFileSync46(
|
|
70097
|
+
const pkg = JSON.parse(readFileSync46(join48(dir, "package.json"), "utf-8"));
|
|
69825
70098
|
if (pkg.name === "switchroom")
|
|
69826
70099
|
return true;
|
|
69827
70100
|
} catch {}
|
|
@@ -69873,7 +70146,7 @@ function planUpdate(opts) {
|
|
|
69873
70146
|
steps.push({
|
|
69874
70147
|
name: "pull-images",
|
|
69875
70148
|
description: "Pull broker / kernel / agent images from GHCR",
|
|
69876
|
-
skipReason: opts.skipImages ? "--skip-images flag set" : !
|
|
70149
|
+
skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync52(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
|
|
69877
70150
|
run: () => {
|
|
69878
70151
|
const r = runner("docker", [
|
|
69879
70152
|
"compose",
|
|
@@ -69952,14 +70225,14 @@ function planUpdate(opts) {
|
|
|
69952
70225
|
return;
|
|
69953
70226
|
}
|
|
69954
70227
|
const source = resolve31(import.meta.dirname, "../../skills");
|
|
69955
|
-
const dest =
|
|
69956
|
-
if (!
|
|
70228
|
+
const dest = join48(homedir28(), ".switchroom", "skills", "_bundled");
|
|
70229
|
+
if (!existsSync52(source)) {
|
|
69957
70230
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
69958
70231
|
`);
|
|
69959
70232
|
return;
|
|
69960
70233
|
}
|
|
69961
70234
|
try {
|
|
69962
|
-
if (
|
|
70235
|
+
if (existsSync52(dest)) {
|
|
69963
70236
|
rmSync12(dest, { recursive: true, force: true });
|
|
69964
70237
|
}
|
|
69965
70238
|
mkdirSync28(dirname13(dest), { recursive: true });
|
|
@@ -70062,12 +70335,12 @@ function defaultStatusProbe(composePath) {
|
|
|
70062
70335
|
} catch {}
|
|
70063
70336
|
if (scriptPath) {
|
|
70064
70337
|
try {
|
|
70065
|
-
cliBuiltAt = new Date(
|
|
70338
|
+
cliBuiltAt = new Date(statSync24(scriptPath).mtimeMs).toISOString();
|
|
70066
70339
|
} catch {}
|
|
70067
70340
|
let dir = dirname13(scriptPath);
|
|
70068
70341
|
for (let i = 0;i < 8; i++) {
|
|
70069
|
-
const pkgPath =
|
|
70070
|
-
if (
|
|
70342
|
+
const pkgPath = join48(dir, "package.json");
|
|
70343
|
+
if (existsSync52(pkgPath)) {
|
|
70071
70344
|
try {
|
|
70072
70345
|
const pkg = JSON.parse(readFileSync46(pkgPath, "utf-8"));
|
|
70073
70346
|
if (typeof pkg.version === "string")
|
|
@@ -70090,7 +70363,7 @@ function defaultStatusProbe(composePath) {
|
|
|
70090
70363
|
warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
|
|
70091
70364
|
}
|
|
70092
70365
|
const services = [];
|
|
70093
|
-
if (!
|
|
70366
|
+
if (!existsSync52(composePath)) {
|
|
70094
70367
|
warnings.push(`compose file not found at ${composePath}; service status unknown`);
|
|
70095
70368
|
return { cliVersion, cliBuiltAt, services, warnings };
|
|
70096
70369
|
}
|
|
@@ -70285,8 +70558,8 @@ init_source();
|
|
|
70285
70558
|
init_helpers();
|
|
70286
70559
|
init_lifecycle();
|
|
70287
70560
|
import { execSync as execSync4 } from "node:child_process";
|
|
70288
|
-
import { existsSync as
|
|
70289
|
-
import { dirname as dirname14, join as
|
|
70561
|
+
import { existsSync as existsSync53, readFileSync as readFileSync47 } from "node:fs";
|
|
70562
|
+
import { dirname as dirname14, join as join49 } from "node:path";
|
|
70290
70563
|
function getClaudeCodeVersion() {
|
|
70291
70564
|
try {
|
|
70292
70565
|
const out = execSync4("claude --version 2>/dev/null", {
|
|
@@ -70336,11 +70609,11 @@ function formatUptime3(timestamp) {
|
|
|
70336
70609
|
function locateSwitchroomInstallDir() {
|
|
70337
70610
|
let dir = import.meta.dirname;
|
|
70338
70611
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
70339
|
-
const pkgPath =
|
|
70340
|
-
if (
|
|
70612
|
+
const pkgPath = join49(dir, "package.json");
|
|
70613
|
+
if (existsSync53(pkgPath)) {
|
|
70341
70614
|
try {
|
|
70342
70615
|
const pkg = JSON.parse(readFileSync47(pkgPath, "utf-8"));
|
|
70343
|
-
if (pkg.name === "switchroom" &&
|
|
70616
|
+
if (pkg.name === "switchroom" && existsSync53(join49(dir, ".git"))) {
|
|
70344
70617
|
return dir;
|
|
70345
70618
|
}
|
|
70346
70619
|
} catch {}
|
|
@@ -70517,21 +70790,32 @@ init_loader();
|
|
|
70517
70790
|
import { resolve as resolve33 } from "node:path";
|
|
70518
70791
|
function registerHandoffCommand(program3) {
|
|
70519
70792
|
program3.command("handoff <agent>", { hidden: true }).description("Build the agent's session handoff sidecars \u2014 a transcript-tail " + "briefing (.handoff.md) and topic line (.handoff-topic). " + "[internal \u2014 used by the Stop hook]").option("--max-turns <n>", "Max turns kept in the handoff transcript tail", String(DEFAULT_MAX_TURNS)).action(withConfigError(async (agentName, opts) => {
|
|
70520
|
-
|
|
70521
|
-
|
|
70522
|
-
|
|
70523
|
-
|
|
70793
|
+
let agentConfig;
|
|
70794
|
+
let agentDir;
|
|
70795
|
+
try {
|
|
70796
|
+
const config = getConfig(program3);
|
|
70797
|
+
agentConfig = config.agents[agentName];
|
|
70798
|
+
if (!agentConfig) {
|
|
70799
|
+
process.stderr.write(`handoff: agent "${agentName}" not defined in switchroom.yaml
|
|
70524
70800
|
`);
|
|
70525
|
-
|
|
70801
|
+
return;
|
|
70802
|
+
}
|
|
70803
|
+
const agentsDir = resolveAgentsDir(config);
|
|
70804
|
+
agentDir = resolve33(agentsDir, agentName);
|
|
70805
|
+
} catch (err) {
|
|
70806
|
+
if (!(err instanceof ConfigError))
|
|
70807
|
+
throw err;
|
|
70808
|
+
process.stderr.write(`handoff: yaml unavailable (${err.message}); using defaults
|
|
70809
|
+
`);
|
|
70810
|
+
agentConfig = undefined;
|
|
70811
|
+
agentDir = process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
70526
70812
|
}
|
|
70527
|
-
const continuity = agentConfig
|
|
70813
|
+
const continuity = agentConfig?.session_continuity;
|
|
70528
70814
|
if (continuity?.enabled === false) {
|
|
70529
70815
|
process.stderr.write(`handoff: session_continuity.enabled=false for "${agentName}"; skipping
|
|
70530
70816
|
`);
|
|
70531
70817
|
return;
|
|
70532
70818
|
}
|
|
70533
|
-
const agentsDir = resolveAgentsDir(config);
|
|
70534
|
-
const agentDir = resolve33(agentsDir, agentName);
|
|
70535
70819
|
const claudeConfigDir = resolve33(agentDir, ".claude");
|
|
70536
70820
|
const jsonl = findLatestSessionJsonl(claudeConfigDir);
|
|
70537
70821
|
if (!jsonl) {
|
|
@@ -70555,18 +70839,18 @@ function registerHandoffCommand(program3) {
|
|
|
70555
70839
|
// src/issues/store.ts
|
|
70556
70840
|
import {
|
|
70557
70841
|
closeSync as closeSync11,
|
|
70558
|
-
existsSync as
|
|
70842
|
+
existsSync as existsSync54,
|
|
70559
70843
|
mkdirSync as mkdirSync29,
|
|
70560
70844
|
openSync as openSync11,
|
|
70561
70845
|
readdirSync as readdirSync20,
|
|
70562
70846
|
readFileSync as readFileSync48,
|
|
70563
70847
|
renameSync as renameSync11,
|
|
70564
|
-
statSync as
|
|
70848
|
+
statSync as statSync25,
|
|
70565
70849
|
unlinkSync as unlinkSync11,
|
|
70566
70850
|
writeFileSync as writeFileSync25,
|
|
70567
70851
|
writeSync as writeSync7
|
|
70568
70852
|
} from "node:fs";
|
|
70569
|
-
import { join as
|
|
70853
|
+
import { join as join50 } from "node:path";
|
|
70570
70854
|
import { randomBytes as randomBytes11 } from "node:crypto";
|
|
70571
70855
|
import { execSync as execSync5 } from "node:child_process";
|
|
70572
70856
|
|
|
@@ -70886,8 +71170,8 @@ function redactedMarker(ruleId) {
|
|
|
70886
71170
|
var ISSUES_FILE = "issues.jsonl";
|
|
70887
71171
|
var ISSUES_LOCK = "issues.lock";
|
|
70888
71172
|
function readAll(stateDir) {
|
|
70889
|
-
const path4 =
|
|
70890
|
-
if (!
|
|
71173
|
+
const path4 = join50(stateDir, ISSUES_FILE);
|
|
71174
|
+
if (!existsSync54(path4))
|
|
70891
71175
|
return [];
|
|
70892
71176
|
let raw;
|
|
70893
71177
|
try {
|
|
@@ -70964,7 +71248,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
70964
71248
|
});
|
|
70965
71249
|
}
|
|
70966
71250
|
function resolve34(stateDir, fingerprint, nowFn = Date.now) {
|
|
70967
|
-
if (!
|
|
71251
|
+
if (!existsSync54(join50(stateDir, ISSUES_FILE)))
|
|
70968
71252
|
return 0;
|
|
70969
71253
|
return withLock(stateDir, () => {
|
|
70970
71254
|
const all = readAll(stateDir);
|
|
@@ -70982,7 +71266,7 @@ function resolve34(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
70982
71266
|
});
|
|
70983
71267
|
}
|
|
70984
71268
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
70985
|
-
if (!
|
|
71269
|
+
if (!existsSync54(join50(stateDir, ISSUES_FILE)))
|
|
70986
71270
|
return 0;
|
|
70987
71271
|
return withLock(stateDir, () => {
|
|
70988
71272
|
const all = readAll(stateDir);
|
|
@@ -71000,7 +71284,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
71000
71284
|
});
|
|
71001
71285
|
}
|
|
71002
71286
|
function prune(stateDir, opts = {}) {
|
|
71003
|
-
if (!
|
|
71287
|
+
if (!existsSync54(join50(stateDir, ISSUES_FILE)))
|
|
71004
71288
|
return 0;
|
|
71005
71289
|
return withLock(stateDir, () => {
|
|
71006
71290
|
const all = readAll(stateDir);
|
|
@@ -71033,7 +71317,7 @@ function ensureDir(stateDir) {
|
|
|
71033
71317
|
mkdirSync29(stateDir, { recursive: true });
|
|
71034
71318
|
}
|
|
71035
71319
|
function writeAll(stateDir, events) {
|
|
71036
|
-
const path4 =
|
|
71320
|
+
const path4 = join50(stateDir, ISSUES_FILE);
|
|
71037
71321
|
sweepOrphanTmpFiles(stateDir);
|
|
71038
71322
|
const tmp = `${path4}.tmp-${process.pid}-${randomBytes11(4).toString("hex")}`;
|
|
71039
71323
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
@@ -71055,9 +71339,9 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
71055
71339
|
for (const entry of entries) {
|
|
71056
71340
|
if (!entry.startsWith(TMP_PREFIX))
|
|
71057
71341
|
continue;
|
|
71058
|
-
const tmpPath =
|
|
71342
|
+
const tmpPath = join50(stateDir, entry);
|
|
71059
71343
|
try {
|
|
71060
|
-
const stat =
|
|
71344
|
+
const stat = statSync25(tmpPath);
|
|
71061
71345
|
if (stat.mtimeMs < cutoff) {
|
|
71062
71346
|
unlinkSync11(tmpPath);
|
|
71063
71347
|
}
|
|
@@ -71067,7 +71351,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
71067
71351
|
var LOCK_RETRY_MS = 25;
|
|
71068
71352
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
71069
71353
|
function withLock(stateDir, fn) {
|
|
71070
|
-
const lockPath =
|
|
71354
|
+
const lockPath = join50(stateDir, ISSUES_LOCK);
|
|
71071
71355
|
const startedAt = Date.now();
|
|
71072
71356
|
let fd = null;
|
|
71073
71357
|
while (fd === null) {
|
|
@@ -71350,22 +71634,22 @@ function relTime(deltaMs) {
|
|
|
71350
71634
|
|
|
71351
71635
|
// src/cli/deps.ts
|
|
71352
71636
|
init_source();
|
|
71353
|
-
import { existsSync as
|
|
71354
|
-
import { homedir as
|
|
71355
|
-
import { join as
|
|
71637
|
+
import { existsSync as existsSync57 } from "node:fs";
|
|
71638
|
+
import { homedir as homedir31 } from "node:os";
|
|
71639
|
+
import { join as join53, resolve as resolve35 } from "node:path";
|
|
71356
71640
|
|
|
71357
71641
|
// src/deps/python.ts
|
|
71358
71642
|
import { createHash as createHash10 } from "node:crypto";
|
|
71359
71643
|
import {
|
|
71360
|
-
existsSync as
|
|
71644
|
+
existsSync as existsSync55,
|
|
71361
71645
|
mkdirSync as mkdirSync30,
|
|
71362
71646
|
readFileSync as readFileSync49,
|
|
71363
71647
|
rmSync as rmSync13,
|
|
71364
71648
|
writeFileSync as writeFileSync26
|
|
71365
71649
|
} from "node:fs";
|
|
71366
|
-
import { dirname as dirname15, join as
|
|
71367
|
-
import { homedir as
|
|
71368
|
-
import { execFileSync as
|
|
71650
|
+
import { dirname as dirname15, join as join51 } from "node:path";
|
|
71651
|
+
import { homedir as homedir29 } from "node:os";
|
|
71652
|
+
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
71369
71653
|
|
|
71370
71654
|
class PythonEnvError extends Error {
|
|
71371
71655
|
stderr;
|
|
@@ -71376,7 +71660,7 @@ class PythonEnvError extends Error {
|
|
|
71376
71660
|
}
|
|
71377
71661
|
}
|
|
71378
71662
|
function defaultPythonCacheRoot() {
|
|
71379
|
-
return
|
|
71663
|
+
return join51(homedir29(), ".switchroom", "deps", "python");
|
|
71380
71664
|
}
|
|
71381
71665
|
function hashFile(path4) {
|
|
71382
71666
|
return createHash10("sha256").update(readFileSync49(path4)).digest("hex");
|
|
@@ -71385,16 +71669,16 @@ function ensurePythonEnv(opts) {
|
|
|
71385
71669
|
const { skillName, requirementsPath, force = false } = opts;
|
|
71386
71670
|
const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
|
|
71387
71671
|
const hostPython = opts.pythonBin ?? "python3";
|
|
71388
|
-
if (!
|
|
71672
|
+
if (!existsSync55(requirementsPath)) {
|
|
71389
71673
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
71390
71674
|
}
|
|
71391
|
-
const venvDir =
|
|
71392
|
-
const stampPath =
|
|
71393
|
-
const binDir =
|
|
71394
|
-
const pythonBin =
|
|
71395
|
-
const pipBin =
|
|
71675
|
+
const venvDir = join51(cacheRoot, skillName);
|
|
71676
|
+
const stampPath = join51(venvDir, ".requirements.sha256");
|
|
71677
|
+
const binDir = join51(venvDir, "bin");
|
|
71678
|
+
const pythonBin = join51(binDir, "python");
|
|
71679
|
+
const pipBin = join51(binDir, "pip");
|
|
71396
71680
|
const targetHash = hashFile(requirementsPath);
|
|
71397
|
-
if (!force &&
|
|
71681
|
+
if (!force && existsSync55(stampPath) && existsSync55(pythonBin)) {
|
|
71398
71682
|
const existingHash = readFileSync49(stampPath, "utf8").trim();
|
|
71399
71683
|
if (existingHash === targetHash) {
|
|
71400
71684
|
return {
|
|
@@ -71407,12 +71691,12 @@ function ensurePythonEnv(opts) {
|
|
|
71407
71691
|
};
|
|
71408
71692
|
}
|
|
71409
71693
|
}
|
|
71410
|
-
if (
|
|
71694
|
+
if (existsSync55(venvDir)) {
|
|
71411
71695
|
rmSync13(venvDir, { recursive: true, force: true });
|
|
71412
71696
|
}
|
|
71413
71697
|
mkdirSync30(dirname15(venvDir), { recursive: true });
|
|
71414
71698
|
try {
|
|
71415
|
-
|
|
71699
|
+
execFileSync15(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
|
|
71416
71700
|
} catch (err) {
|
|
71417
71701
|
const e = err;
|
|
71418
71702
|
throw new PythonEnvError(`Failed to create venv for skill "${skillName}" with ${hostPython}: ${e.message}`, e.stderr?.toString());
|
|
@@ -71424,7 +71708,7 @@ function ensurePythonEnv(opts) {
|
|
|
71424
71708
|
delete childEnv.PIP_TARGET;
|
|
71425
71709
|
delete childEnv.PIP_PREFIX;
|
|
71426
71710
|
delete childEnv.PYTHONUSERBASE;
|
|
71427
|
-
|
|
71711
|
+
execFileSync15(pipBin, ["install", "--disable-pip-version-check", "-r", requirementsPath], { stdio: "pipe", env: childEnv });
|
|
71428
71712
|
} catch (err) {
|
|
71429
71713
|
const e = err;
|
|
71430
71714
|
throw new PythonEnvError(`Failed to install requirements for skill "${skillName}": ${e.message}`, e.stderr?.toString());
|
|
@@ -71445,15 +71729,15 @@ function ensurePythonEnv(opts) {
|
|
|
71445
71729
|
import { createHash as createHash11 } from "node:crypto";
|
|
71446
71730
|
import {
|
|
71447
71731
|
copyFileSync as copyFileSync9,
|
|
71448
|
-
existsSync as
|
|
71732
|
+
existsSync as existsSync56,
|
|
71449
71733
|
mkdirSync as mkdirSync31,
|
|
71450
71734
|
readFileSync as readFileSync50,
|
|
71451
71735
|
rmSync as rmSync14,
|
|
71452
71736
|
writeFileSync as writeFileSync27
|
|
71453
71737
|
} from "node:fs";
|
|
71454
|
-
import { dirname as dirname16, join as
|
|
71455
|
-
import { homedir as
|
|
71456
|
-
import { execFileSync as
|
|
71738
|
+
import { dirname as dirname16, join as join52 } from "node:path";
|
|
71739
|
+
import { homedir as homedir30 } from "node:os";
|
|
71740
|
+
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
71457
71741
|
|
|
71458
71742
|
class NodeEnvError extends Error {
|
|
71459
71743
|
stderr;
|
|
@@ -71475,7 +71759,7 @@ var LOCKFILES_FOR = {
|
|
|
71475
71759
|
npm: ["package-lock.json"]
|
|
71476
71760
|
};
|
|
71477
71761
|
function defaultNodeCacheRoot() {
|
|
71478
|
-
return
|
|
71762
|
+
return join52(homedir30(), ".switchroom", "deps", "node");
|
|
71479
71763
|
}
|
|
71480
71764
|
function hashDepInputs(packageJsonPath) {
|
|
71481
71765
|
const sourceDir = dirname16(packageJsonPath);
|
|
@@ -71484,8 +71768,8 @@ function hashDepInputs(packageJsonPath) {
|
|
|
71484
71768
|
`);
|
|
71485
71769
|
hasher.update(readFileSync50(packageJsonPath));
|
|
71486
71770
|
for (const lockName of ALL_LOCKFILES) {
|
|
71487
|
-
const lockPath =
|
|
71488
|
-
if (
|
|
71771
|
+
const lockPath = join52(sourceDir, lockName);
|
|
71772
|
+
if (existsSync56(lockPath)) {
|
|
71489
71773
|
hasher.update(`
|
|
71490
71774
|
`);
|
|
71491
71775
|
hasher.update(lockName);
|
|
@@ -71500,16 +71784,16 @@ function ensureNodeEnv(opts) {
|
|
|
71500
71784
|
const { skillName, packageJsonPath, force = false } = opts;
|
|
71501
71785
|
const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
|
|
71502
71786
|
const installer = opts.installer ?? "bun";
|
|
71503
|
-
if (!
|
|
71787
|
+
if (!existsSync56(packageJsonPath)) {
|
|
71504
71788
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
71505
71789
|
}
|
|
71506
71790
|
const sourceDir = dirname16(packageJsonPath);
|
|
71507
|
-
const envDir =
|
|
71508
|
-
const stampPath =
|
|
71509
|
-
const nodeModulesDir =
|
|
71510
|
-
const binDir =
|
|
71791
|
+
const envDir = join52(cacheRoot, skillName);
|
|
71792
|
+
const stampPath = join52(envDir, ".package.sha256");
|
|
71793
|
+
const nodeModulesDir = join52(envDir, "node_modules");
|
|
71794
|
+
const binDir = join52(nodeModulesDir, ".bin");
|
|
71511
71795
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
71512
|
-
if (!force &&
|
|
71796
|
+
if (!force && existsSync56(stampPath) && existsSync56(nodeModulesDir)) {
|
|
71513
71797
|
const existingHash = readFileSync50(stampPath, "utf8").trim();
|
|
71514
71798
|
if (existingHash === targetHash) {
|
|
71515
71799
|
return {
|
|
@@ -71521,26 +71805,26 @@ function ensureNodeEnv(opts) {
|
|
|
71521
71805
|
};
|
|
71522
71806
|
}
|
|
71523
71807
|
}
|
|
71524
|
-
if (
|
|
71808
|
+
if (existsSync56(envDir)) {
|
|
71525
71809
|
rmSync14(envDir, { recursive: true, force: true });
|
|
71526
71810
|
}
|
|
71527
71811
|
mkdirSync31(envDir, { recursive: true });
|
|
71528
|
-
copyFileSync9(packageJsonPath,
|
|
71812
|
+
copyFileSync9(packageJsonPath, join52(envDir, "package.json"));
|
|
71529
71813
|
let copiedLockfile = false;
|
|
71530
71814
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
71531
|
-
const lockPath =
|
|
71532
|
-
if (
|
|
71533
|
-
copyFileSync9(lockPath,
|
|
71815
|
+
const lockPath = join52(sourceDir, lockName);
|
|
71816
|
+
if (existsSync56(lockPath)) {
|
|
71817
|
+
copyFileSync9(lockPath, join52(envDir, lockName));
|
|
71534
71818
|
copiedLockfile = true;
|
|
71535
71819
|
}
|
|
71536
71820
|
}
|
|
71537
71821
|
try {
|
|
71538
71822
|
if (installer === "bun") {
|
|
71539
71823
|
const args = copiedLockfile ? ["install", "--frozen-lockfile"] : ["install"];
|
|
71540
|
-
|
|
71824
|
+
execFileSync16("bun", args, { cwd: envDir, stdio: "pipe" });
|
|
71541
71825
|
} else {
|
|
71542
71826
|
const args = copiedLockfile ? ["ci"] : ["install"];
|
|
71543
|
-
|
|
71827
|
+
execFileSync16("npm", args, { cwd: envDir, stdio: "pipe" });
|
|
71544
71828
|
}
|
|
71545
71829
|
} catch (err) {
|
|
71546
71830
|
const e = err;
|
|
@@ -71559,28 +71843,28 @@ function ensureNodeEnv(opts) {
|
|
|
71559
71843
|
|
|
71560
71844
|
// src/cli/deps.ts
|
|
71561
71845
|
function builtinSkillsRoot() {
|
|
71562
|
-
return resolve35(
|
|
71846
|
+
return resolve35(homedir31(), ".switchroom/skills/_bundled");
|
|
71563
71847
|
}
|
|
71564
71848
|
function registerDepsCommand(program3) {
|
|
71565
71849
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
71566
71850
|
deps.command("rebuild <skill>").description("Rebuild the Python venv and/or Node node_modules cache for a skill").option("-p, --python", "Rebuild only the Python env").option("-n, --node", "Rebuild only the Node env").action(async (skill, opts) => {
|
|
71567
71851
|
const skillsRoot = builtinSkillsRoot();
|
|
71568
|
-
if (!
|
|
71852
|
+
if (!existsSync57(skillsRoot)) {
|
|
71569
71853
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
71570
71854
|
process.exit(1);
|
|
71571
71855
|
}
|
|
71572
|
-
const skillDir =
|
|
71573
|
-
if (!
|
|
71856
|
+
const skillDir = join53(skillsRoot, skill);
|
|
71857
|
+
if (!existsSync57(skillDir)) {
|
|
71574
71858
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
71575
71859
|
process.exit(1);
|
|
71576
71860
|
}
|
|
71577
|
-
const requirementsPath =
|
|
71578
|
-
const packageJsonPath =
|
|
71579
|
-
const wantPython = opts.python ?? (!opts.python && !opts.node &&
|
|
71580
|
-
const wantNode = opts.node ?? (!opts.python && !opts.node &&
|
|
71861
|
+
const requirementsPath = join53(skillDir, "requirements.txt");
|
|
71862
|
+
const packageJsonPath = join53(skillDir, "package.json");
|
|
71863
|
+
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync57(requirementsPath));
|
|
71864
|
+
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync57(packageJsonPath));
|
|
71581
71865
|
let did = 0;
|
|
71582
71866
|
if (wantPython) {
|
|
71583
|
-
if (!
|
|
71867
|
+
if (!existsSync57(requirementsPath)) {
|
|
71584
71868
|
console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
|
|
71585
71869
|
process.exit(1);
|
|
71586
71870
|
}
|
|
@@ -71604,7 +71888,7 @@ function registerDepsCommand(program3) {
|
|
|
71604
71888
|
}
|
|
71605
71889
|
}
|
|
71606
71890
|
if (wantNode) {
|
|
71607
|
-
if (!
|
|
71891
|
+
if (!existsSync57(packageJsonPath)) {
|
|
71608
71892
|
console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
|
|
71609
71893
|
process.exit(1);
|
|
71610
71894
|
}
|
|
@@ -71637,7 +71921,7 @@ function registerDepsCommand(program3) {
|
|
|
71637
71921
|
// src/cli/workspace.ts
|
|
71638
71922
|
init_helpers();
|
|
71639
71923
|
init_loader();
|
|
71640
|
-
import { existsSync as
|
|
71924
|
+
import { existsSync as existsSync58 } from "node:fs";
|
|
71641
71925
|
import { resolve as resolve36, sep as sep3 } from "node:path";
|
|
71642
71926
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
71643
71927
|
|
|
@@ -72414,7 +72698,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
72414
72698
|
if (!dir)
|
|
72415
72699
|
return;
|
|
72416
72700
|
const gitDir = resolve36(dir, ".git");
|
|
72417
|
-
if (!
|
|
72701
|
+
if (!existsSync58(gitDir)) {
|
|
72418
72702
|
process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
|
|
72419
72703
|
`);
|
|
72420
72704
|
return;
|
|
@@ -72468,7 +72752,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
72468
72752
|
if (!dir)
|
|
72469
72753
|
return;
|
|
72470
72754
|
const gitDir = resolve36(dir, ".git");
|
|
72471
|
-
if (!
|
|
72755
|
+
if (!existsSync58(gitDir)) {
|
|
72472
72756
|
process.stdout.write(`Workspace is not a git repository.
|
|
72473
72757
|
`);
|
|
72474
72758
|
return;
|
|
@@ -72493,7 +72777,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
|
|
|
72493
72777
|
const agentsDir = resolveAgentsDir(config);
|
|
72494
72778
|
const agentDir = resolve36(agentsDir, agentName);
|
|
72495
72779
|
const dir = resolveAgentWorkspaceDir(agentDir);
|
|
72496
|
-
if (!
|
|
72780
|
+
if (!existsSync58(dir)) {
|
|
72497
72781
|
process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
|
|
72498
72782
|
`);
|
|
72499
72783
|
return;
|
|
@@ -72529,8 +72813,8 @@ function safeParseInt(value, fallback) {
|
|
|
72529
72813
|
init_helpers();
|
|
72530
72814
|
init_loader();
|
|
72531
72815
|
init_merge();
|
|
72532
|
-
import { copyFileSync as copyFileSync10, existsSync as
|
|
72533
|
-
import { join as
|
|
72816
|
+
import { copyFileSync as copyFileSync10, existsSync as existsSync59, readFileSync as readFileSync51, writeFileSync as writeFileSync28 } from "node:fs";
|
|
72817
|
+
import { join as join54, resolve as resolve37 } from "node:path";
|
|
72534
72818
|
init_schema();
|
|
72535
72819
|
function resolveSoulTargetOrExit(program3, agentName) {
|
|
72536
72820
|
const config = getConfig(program3);
|
|
@@ -72545,7 +72829,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
72545
72829
|
const agentsDir = resolveAgentsDir(config);
|
|
72546
72830
|
const agentDir = resolve37(agentsDir, agentName);
|
|
72547
72831
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
72548
|
-
if (!
|
|
72832
|
+
if (!existsSync59(workspaceDir)) {
|
|
72549
72833
|
console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
|
|
72550
72834
|
process.exit(1);
|
|
72551
72835
|
}
|
|
@@ -72554,7 +72838,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
72554
72838
|
profileName,
|
|
72555
72839
|
profilePath,
|
|
72556
72840
|
workspaceDir,
|
|
72557
|
-
soulPath:
|
|
72841
|
+
soulPath: join54(workspaceDir, "SOUL.md"),
|
|
72558
72842
|
soul: merged.soul
|
|
72559
72843
|
};
|
|
72560
72844
|
}
|
|
@@ -72571,7 +72855,7 @@ function registerSoulCommand(program3) {
|
|
|
72571
72855
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
72572
72856
|
if (!t)
|
|
72573
72857
|
return;
|
|
72574
|
-
if (!
|
|
72858
|
+
if (!existsSync59(t.soulPath)) {
|
|
72575
72859
|
console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
|
|
72576
72860
|
process.exit(1);
|
|
72577
72861
|
}
|
|
@@ -72586,7 +72870,7 @@ function registerSoulCommand(program3) {
|
|
|
72586
72870
|
console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
|
|
72587
72871
|
process.exit(1);
|
|
72588
72872
|
}
|
|
72589
|
-
const exists =
|
|
72873
|
+
const exists = existsSync59(t.soulPath);
|
|
72590
72874
|
if (exists && !opts.yes) {
|
|
72591
72875
|
if (!isInteractive()) {
|
|
72592
72876
|
console.error(`soul: ${t.soulPath} already exists. Re-run with --yes to ` + `replace it (the current file is backed up to SOUL.md.bak).`);
|
|
@@ -72601,7 +72885,7 @@ function registerSoulCommand(program3) {
|
|
|
72601
72885
|
let backupPath;
|
|
72602
72886
|
if (exists) {
|
|
72603
72887
|
backupPath = `${t.soulPath}.bak`;
|
|
72604
|
-
if (
|
|
72888
|
+
if (existsSync59(backupPath)) {
|
|
72605
72889
|
backupPath = `${t.soulPath}.bak.${Date.now()}`;
|
|
72606
72890
|
}
|
|
72607
72891
|
copyFileSync10(t.soulPath, backupPath);
|
|
@@ -72620,8 +72904,8 @@ function registerSoulCommand(program3) {
|
|
|
72620
72904
|
// src/cli/debug.ts
|
|
72621
72905
|
init_helpers();
|
|
72622
72906
|
init_loader();
|
|
72623
|
-
import { existsSync as
|
|
72624
|
-
import { resolve as resolve38, join as
|
|
72907
|
+
import { existsSync as existsSync60, readFileSync as readFileSync52, readdirSync as readdirSync21, statSync as statSync26 } from "node:fs";
|
|
72908
|
+
import { resolve as resolve38, join as join55 } from "node:path";
|
|
72625
72909
|
import { createHash as createHash12 } from "node:crypto";
|
|
72626
72910
|
init_merge();
|
|
72627
72911
|
init_hindsight();
|
|
@@ -72635,8 +72919,8 @@ function sha256(content) {
|
|
|
72635
72919
|
return createHash12("sha256").update(content).digest("hex").slice(0, 16);
|
|
72636
72920
|
}
|
|
72637
72921
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
72638
|
-
const projectsDir =
|
|
72639
|
-
if (!
|
|
72922
|
+
const projectsDir = join55(claudeConfigDir, "projects");
|
|
72923
|
+
if (!existsSync60(projectsDir))
|
|
72640
72924
|
return;
|
|
72641
72925
|
try {
|
|
72642
72926
|
const entries = readdirSync21(projectsDir, { withFileTypes: true });
|
|
@@ -72644,11 +72928,11 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
72644
72928
|
for (const entry of entries) {
|
|
72645
72929
|
if (!entry.isDirectory())
|
|
72646
72930
|
continue;
|
|
72647
|
-
const projectPath =
|
|
72648
|
-
const transcriptPath =
|
|
72649
|
-
if (!
|
|
72931
|
+
const projectPath = join55(projectsDir, entry.name);
|
|
72932
|
+
const transcriptPath = join55(projectPath, "transcript.jsonl");
|
|
72933
|
+
if (!existsSync60(transcriptPath))
|
|
72650
72934
|
continue;
|
|
72651
|
-
const stat3 =
|
|
72935
|
+
const stat3 = statSync26(transcriptPath);
|
|
72652
72936
|
if (!latest || stat3.mtimeMs > latest.mtime) {
|
|
72653
72937
|
latest = { path: transcriptPath, mtime: stat3.mtimeMs };
|
|
72654
72938
|
}
|
|
@@ -72709,16 +72993,16 @@ function registerDebugCommand(program3) {
|
|
|
72709
72993
|
}
|
|
72710
72994
|
const agentsDir = resolveAgentsDir(config);
|
|
72711
72995
|
const agentDir = resolve38(agentsDir, agentName);
|
|
72712
|
-
if (!
|
|
72996
|
+
if (!existsSync60(agentDir)) {
|
|
72713
72997
|
console.error(`Agent directory not found: ${agentDir}`);
|
|
72714
72998
|
process.exit(1);
|
|
72715
72999
|
}
|
|
72716
73000
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
72717
|
-
const claudeConfigDir =
|
|
72718
|
-
const claudeMdPath =
|
|
72719
|
-
const soulMdPath =
|
|
72720
|
-
const workspaceSoulMdPath =
|
|
72721
|
-
const handoffPath =
|
|
73001
|
+
const claudeConfigDir = join55(agentDir, ".claude");
|
|
73002
|
+
const claudeMdPath = join55(agentDir, "CLAUDE.md");
|
|
73003
|
+
const soulMdPath = join55(agentDir, "SOUL.md");
|
|
73004
|
+
const workspaceSoulMdPath = join55(workspaceDir, "SOUL.md");
|
|
73005
|
+
const handoffPath = join55(agentDir, ".handoff.md");
|
|
72722
73006
|
const lastN = parseInt(opts.last, 10);
|
|
72723
73007
|
if (isNaN(lastN) || lastN < 1) {
|
|
72724
73008
|
console.error("--last must be a positive integer");
|
|
@@ -72764,7 +73048,7 @@ function registerDebugCommand(program3) {
|
|
|
72764
73048
|
}
|
|
72765
73049
|
console.log(`=== Append System Prompt (per-session) ===
|
|
72766
73050
|
`);
|
|
72767
|
-
const handoffContent =
|
|
73051
|
+
const handoffContent = existsSync60(handoffPath) ? readFileSync52(handoffPath, "utf-8") : "";
|
|
72768
73052
|
if (handoffContent.trim().length > 0) {
|
|
72769
73053
|
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
72770
73054
|
console.log(handoffContent);
|
|
@@ -72775,7 +73059,7 @@ function registerDebugCommand(program3) {
|
|
|
72775
73059
|
}
|
|
72776
73060
|
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
72777
73061
|
`);
|
|
72778
|
-
const claudeMdContent =
|
|
73062
|
+
const claudeMdContent = existsSync60(claudeMdPath) ? readFileSync52(claudeMdPath, "utf-8") : "";
|
|
72779
73063
|
if (claudeMdContent.trim().length > 0) {
|
|
72780
73064
|
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
72781
73065
|
console.log(claudeMdContent);
|
|
@@ -72786,7 +73070,7 @@ function registerDebugCommand(program3) {
|
|
|
72786
73070
|
}
|
|
72787
73071
|
console.log(`=== Persona (SOUL.md) ===
|
|
72788
73072
|
`);
|
|
72789
|
-
const soulMdContent =
|
|
73073
|
+
const soulMdContent = existsSync60(soulMdPath) ? readFileSync52(soulMdPath, "utf-8") : existsSync60(workspaceSoulMdPath) ? readFileSync52(workspaceSoulMdPath, "utf-8") : "";
|
|
72790
73074
|
if (soulMdContent.trim().length > 0) {
|
|
72791
73075
|
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
72792
73076
|
console.log(soulMdContent);
|
|
@@ -72866,10 +73150,10 @@ function registerDebugCommand(program3) {
|
|
|
72866
73150
|
init_source();
|
|
72867
73151
|
|
|
72868
73152
|
// src/worktree/claim.ts
|
|
72869
|
-
import { execFileSync as
|
|
72870
|
-
import { closeSync as closeSync12, mkdirSync as mkdirSync33, openSync as openSync12, existsSync as
|
|
72871
|
-
import { join as
|
|
72872
|
-
import { homedir as
|
|
73153
|
+
import { execFileSync as execFileSync17 } from "node:child_process";
|
|
73154
|
+
import { closeSync as closeSync12, mkdirSync as mkdirSync33, openSync as openSync12, existsSync as existsSync62, unlinkSync as unlinkSync13 } from "node:fs";
|
|
73155
|
+
import { join as join57, resolve as resolve40 } from "node:path";
|
|
73156
|
+
import { homedir as homedir33 } from "node:os";
|
|
72873
73157
|
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
72874
73158
|
|
|
72875
73159
|
// src/worktree/registry.ts
|
|
@@ -72879,16 +73163,16 @@ import {
|
|
|
72879
73163
|
readFileSync as readFileSync53,
|
|
72880
73164
|
readdirSync as readdirSync22,
|
|
72881
73165
|
unlinkSync as unlinkSync12,
|
|
72882
|
-
existsSync as
|
|
73166
|
+
existsSync as existsSync61,
|
|
72883
73167
|
renameSync as renameSync12
|
|
72884
73168
|
} from "node:fs";
|
|
72885
|
-
import { join as
|
|
72886
|
-
import { homedir as
|
|
73169
|
+
import { join as join56, resolve as resolve39 } from "node:path";
|
|
73170
|
+
import { homedir as homedir32 } from "node:os";
|
|
72887
73171
|
function registryDir() {
|
|
72888
|
-
return resolve39(process.env.SWITCHROOM_WORKTREE_DIR ??
|
|
73172
|
+
return resolve39(process.env.SWITCHROOM_WORKTREE_DIR ?? join56(homedir32(), ".switchroom", "worktrees"));
|
|
72889
73173
|
}
|
|
72890
73174
|
function recordPath(id) {
|
|
72891
|
-
return
|
|
73175
|
+
return join56(registryDir(), `${id}.json`);
|
|
72892
73176
|
}
|
|
72893
73177
|
function ensureDir2() {
|
|
72894
73178
|
mkdirSync32(registryDir(), { recursive: true });
|
|
@@ -72939,7 +73223,7 @@ function acquireRepoLock(repoPath) {
|
|
|
72939
73223
|
const lockDir = registryDir();
|
|
72940
73224
|
mkdirSync33(lockDir, { recursive: true });
|
|
72941
73225
|
const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
|
|
72942
|
-
const lockPath =
|
|
73226
|
+
const lockPath = join57(lockDir, `.lock-${lockName}`);
|
|
72943
73227
|
const deadline = Date.now() + 5000;
|
|
72944
73228
|
let fd = null;
|
|
72945
73229
|
while (fd === null) {
|
|
@@ -72966,7 +73250,7 @@ function acquireRepoLock(repoPath) {
|
|
|
72966
73250
|
}
|
|
72967
73251
|
var DEFAULT_CONCURRENCY = 5;
|
|
72968
73252
|
function worktreesBaseDir() {
|
|
72969
|
-
return resolve40(process.env.SWITCHROOM_WORKTREE_BASE ??
|
|
73253
|
+
return resolve40(process.env.SWITCHROOM_WORKTREE_BASE ?? join57(homedir33(), ".switchroom", "worktree-checkouts"));
|
|
72970
73254
|
}
|
|
72971
73255
|
function shortId() {
|
|
72972
73256
|
return randomBytes12(4).toString("hex");
|
|
@@ -72988,12 +73272,12 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
72988
73272
|
}
|
|
72989
73273
|
function expandHome(p) {
|
|
72990
73274
|
if (p.startsWith("~/"))
|
|
72991
|
-
return
|
|
73275
|
+
return join57(homedir33(), p.slice(2));
|
|
72992
73276
|
return p;
|
|
72993
73277
|
}
|
|
72994
73278
|
async function claimWorktree(input, codeRepos) {
|
|
72995
73279
|
const repoPath = resolveRepoPath(input.repo, codeRepos);
|
|
72996
|
-
if (!
|
|
73280
|
+
if (!existsSync62(repoPath)) {
|
|
72997
73281
|
throw new Error(`Repository path does not exist: ${repoPath}`);
|
|
72998
73282
|
}
|
|
72999
73283
|
let concurrencyCap = DEFAULT_CONCURRENCY;
|
|
@@ -73016,7 +73300,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
73016
73300
|
branch = `task/${taskSuffix}-${id}`;
|
|
73017
73301
|
const baseDir = worktreesBaseDir();
|
|
73018
73302
|
mkdirSync33(baseDir, { recursive: true });
|
|
73019
|
-
worktreePath =
|
|
73303
|
+
worktreePath = join57(baseDir, `${id}-${taskSuffix}`);
|
|
73020
73304
|
const now = new Date().toISOString();
|
|
73021
73305
|
const record2 = {
|
|
73022
73306
|
id,
|
|
@@ -73033,7 +73317,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
73033
73317
|
releaseLock();
|
|
73034
73318
|
}
|
|
73035
73319
|
try {
|
|
73036
|
-
|
|
73320
|
+
execFileSync17("git", ["worktree", "add", "-b", branch, worktreePath], {
|
|
73037
73321
|
cwd: repoPath,
|
|
73038
73322
|
stdio: "pipe"
|
|
73039
73323
|
});
|
|
@@ -73046,8 +73330,8 @@ async function claimWorktree(input, codeRepos) {
|
|
|
73046
73330
|
}
|
|
73047
73331
|
|
|
73048
73332
|
// src/worktree/release.ts
|
|
73049
|
-
import { execFileSync as
|
|
73050
|
-
import { existsSync as
|
|
73333
|
+
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
73334
|
+
import { existsSync as existsSync63 } from "node:fs";
|
|
73051
73335
|
function releaseWorktree(input) {
|
|
73052
73336
|
const { id } = input;
|
|
73053
73337
|
const record2 = readRecord(id);
|
|
@@ -73055,9 +73339,9 @@ function releaseWorktree(input) {
|
|
|
73055
73339
|
return { released: true };
|
|
73056
73340
|
}
|
|
73057
73341
|
let gitSuccess = true;
|
|
73058
|
-
if (
|
|
73342
|
+
if (existsSync63(record2.path)) {
|
|
73059
73343
|
try {
|
|
73060
|
-
|
|
73344
|
+
execFileSync18("git", ["worktree", "remove", "--force", record2.path], {
|
|
73061
73345
|
cwd: record2.repo,
|
|
73062
73346
|
stdio: "pipe"
|
|
73063
73347
|
});
|
|
@@ -73093,16 +73377,16 @@ function listWorktrees() {
|
|
|
73093
73377
|
}
|
|
73094
73378
|
|
|
73095
73379
|
// src/worktree/reaper.ts
|
|
73096
|
-
import { execFileSync as
|
|
73097
|
-
import { existsSync as
|
|
73380
|
+
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
73381
|
+
import { existsSync as existsSync64 } from "node:fs";
|
|
73098
73382
|
var STALE_THRESHOLD_MS = 10 * 60 * 1000;
|
|
73099
73383
|
function isPathInUse(path7) {
|
|
73100
73384
|
try {
|
|
73101
|
-
|
|
73385
|
+
execFileSync19("fuser", [path7], { stdio: "pipe" });
|
|
73102
73386
|
return true;
|
|
73103
73387
|
} catch {}
|
|
73104
73388
|
try {
|
|
73105
|
-
const out =
|
|
73389
|
+
const out = execFileSync19("lsof", ["-t", path7], {
|
|
73106
73390
|
stdio: ["ignore", "pipe", "ignore"]
|
|
73107
73391
|
}).toString().trim();
|
|
73108
73392
|
if (out.length > 0)
|
|
@@ -73112,7 +73396,7 @@ function isPathInUse(path7) {
|
|
|
73112
73396
|
}
|
|
73113
73397
|
function hasUncommittedChanges(repoPath, worktreePath) {
|
|
73114
73398
|
try {
|
|
73115
|
-
const out =
|
|
73399
|
+
const out = execFileSync19("git", ["-C", worktreePath, "status", "--porcelain"], { stdio: "pipe" }).toString();
|
|
73116
73400
|
return out.trim().length > 0;
|
|
73117
73401
|
} catch {
|
|
73118
73402
|
return false;
|
|
@@ -73121,12 +73405,12 @@ function hasUncommittedChanges(repoPath, worktreePath) {
|
|
|
73121
73405
|
function reapRecord(record2) {
|
|
73122
73406
|
const { id, path: path7, repo, branch, ownerAgent } = record2;
|
|
73123
73407
|
let warning = null;
|
|
73124
|
-
if (
|
|
73408
|
+
if (existsSync64(path7)) {
|
|
73125
73409
|
if (hasUncommittedChanges(repo, path7)) {
|
|
73126
73410
|
warning = `[worktree-reaper] Reaped worktree with uncommitted changes: ` + `id=${id} branch=${branch} agent=${ownerAgent ?? "unknown"} path=${path7}`;
|
|
73127
73411
|
}
|
|
73128
73412
|
try {
|
|
73129
|
-
|
|
73413
|
+
execFileSync19("git", ["worktree", "remove", "--force", path7], {
|
|
73130
73414
|
cwd: repo,
|
|
73131
73415
|
stdio: "pipe"
|
|
73132
73416
|
});
|
|
@@ -73142,7 +73426,7 @@ function runReaper(nowMs) {
|
|
|
73142
73426
|
const warnings = [];
|
|
73143
73427
|
for (const record2 of records) {
|
|
73144
73428
|
const heartbeatAge = now - new Date(record2.heartbeatAt).getTime();
|
|
73145
|
-
const worktreeExists =
|
|
73429
|
+
const worktreeExists = existsSync64(record2.path);
|
|
73146
73430
|
if (!worktreeExists) {
|
|
73147
73431
|
deleteRecord(record2.id);
|
|
73148
73432
|
reaped.push(record2.id);
|
|
@@ -73271,7 +73555,7 @@ import {
|
|
|
73271
73555
|
rmSync as rmSync15,
|
|
73272
73556
|
writeFileSync as writeFileSync30
|
|
73273
73557
|
} from "node:fs";
|
|
73274
|
-
import { join as
|
|
73558
|
+
import { join as join58 } from "node:path";
|
|
73275
73559
|
function encodeCredentialsFilename(email) {
|
|
73276
73560
|
const SAFE = new Set([
|
|
73277
73561
|
..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
@@ -73461,16 +73745,16 @@ function resolveCredentialsDir(env2) {
|
|
|
73461
73745
|
if (explicit && explicit.length > 0)
|
|
73462
73746
|
return explicit;
|
|
73463
73747
|
const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
|
|
73464
|
-
return
|
|
73748
|
+
return join58(stateBase, "google-workspace-mcp", "credentials");
|
|
73465
73749
|
}
|
|
73466
73750
|
function writeSeedFile(dir, email, seed) {
|
|
73467
73751
|
mkdirSync34(dir, { recursive: true, mode: 448 });
|
|
73468
73752
|
chmodSync9(dir, 448);
|
|
73469
73753
|
for (const name of readdirSync23(dir)) {
|
|
73470
|
-
rmSync15(
|
|
73754
|
+
rmSync15(join58(dir, name), { force: true, recursive: true });
|
|
73471
73755
|
}
|
|
73472
73756
|
const filename = encodeCredentialsFilename(email);
|
|
73473
|
-
const filePath =
|
|
73757
|
+
const filePath = join58(dir, filename);
|
|
73474
73758
|
writeFileSync30(filePath, JSON.stringify(seed), { mode: 384 });
|
|
73475
73759
|
chmodSync9(filePath, 384);
|
|
73476
73760
|
return filePath;
|
|
@@ -73628,7 +73912,7 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
73628
73912
|
|
|
73629
73913
|
// src/cli/apply.ts
|
|
73630
73914
|
init_source();
|
|
73631
|
-
import { accessSync as accessSync3, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as
|
|
73915
|
+
import { accessSync as accessSync3, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync68, mkdirSync as mkdirSync36, readdirSync as readdirSync25, renameSync as renameSync13, writeFileSync as writeFileSync32 } from "node:fs";
|
|
73632
73916
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
73633
73917
|
import { spawnSync as childSpawnSync } from "node:child_process";
|
|
73634
73918
|
import readline from "node:readline";
|
|
@@ -73991,16 +74275,16 @@ agents:
|
|
|
73991
74275
|
|
|
73992
74276
|
// src/cli/apply.ts
|
|
73993
74277
|
init_resolver();
|
|
73994
|
-
import { dirname as dirname19, join as
|
|
73995
|
-
import { homedir as
|
|
73996
|
-
import { execFileSync as
|
|
74278
|
+
import { dirname as dirname19, join as join62, resolve as resolve42 } from "node:path";
|
|
74279
|
+
import { homedir as homedir35 } from "node:os";
|
|
74280
|
+
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
73997
74281
|
init_vault();
|
|
73998
74282
|
init_loader();
|
|
73999
74283
|
init_loader();
|
|
74000
74284
|
|
|
74001
74285
|
// src/cli/update-prompt-hook.ts
|
|
74002
|
-
import { existsSync as
|
|
74003
|
-
import { join as
|
|
74286
|
+
import { existsSync as existsSync65, readFileSync as readFileSync54, writeFileSync as writeFileSync31, chmodSync as chmodSync10, mkdirSync as mkdirSync35 } from "node:fs";
|
|
74287
|
+
import { join as join59 } from "node:path";
|
|
74004
74288
|
var HOOK_FILENAME = "update-card-on-prompt.sh";
|
|
74005
74289
|
function updatePromptHookScript() {
|
|
74006
74290
|
return `#!/bin/bash
|
|
@@ -74066,12 +74350,12 @@ exit 0
|
|
|
74066
74350
|
`;
|
|
74067
74351
|
}
|
|
74068
74352
|
function installUpdatePromptHook(agentDir) {
|
|
74069
|
-
const hooksDir =
|
|
74353
|
+
const hooksDir = join59(agentDir, ".claude", "hooks");
|
|
74070
74354
|
mkdirSync35(hooksDir, { recursive: true });
|
|
74071
|
-
const scriptPath =
|
|
74355
|
+
const scriptPath = join59(hooksDir, HOOK_FILENAME);
|
|
74072
74356
|
const desired = updatePromptHookScript();
|
|
74073
74357
|
let installed = false;
|
|
74074
|
-
const existing =
|
|
74358
|
+
const existing = existsSync65(scriptPath) ? readFileSync54(scriptPath, "utf-8") : "";
|
|
74075
74359
|
if (existing !== desired) {
|
|
74076
74360
|
writeFileSync31(scriptPath, desired, { mode: 493 });
|
|
74077
74361
|
chmodSync10(scriptPath, 493);
|
|
@@ -74081,8 +74365,8 @@ function installUpdatePromptHook(agentDir) {
|
|
|
74081
74365
|
chmodSync10(scriptPath, 493);
|
|
74082
74366
|
} catch {}
|
|
74083
74367
|
}
|
|
74084
|
-
const settingsPath =
|
|
74085
|
-
if (!
|
|
74368
|
+
const settingsPath = join59(agentDir, ".claude", "settings.json");
|
|
74369
|
+
if (!existsSync65(settingsPath)) {
|
|
74086
74370
|
return { scriptPath, settingsPath, installed };
|
|
74087
74371
|
}
|
|
74088
74372
|
const raw = readFileSync54(settingsPath, "utf-8");
|
|
@@ -74201,13 +74485,13 @@ function detectInstallType() {
|
|
|
74201
74485
|
// src/cli/operator-uid.ts
|
|
74202
74486
|
import {
|
|
74203
74487
|
chownSync as chownSync3,
|
|
74204
|
-
existsSync as
|
|
74488
|
+
existsSync as existsSync67,
|
|
74205
74489
|
lstatSync as lstatSync7,
|
|
74206
74490
|
readdirSync as readdirSync24,
|
|
74207
74491
|
realpathSync as realpathSync6,
|
|
74208
|
-
statSync as
|
|
74492
|
+
statSync as statSync27
|
|
74209
74493
|
} from "node:fs";
|
|
74210
|
-
import { join as
|
|
74494
|
+
import { join as join61 } from "node:path";
|
|
74211
74495
|
function resolveOperatorUid() {
|
|
74212
74496
|
const sudoUid = process.env.SUDO_UID;
|
|
74213
74497
|
if (sudoUid !== undefined) {
|
|
@@ -74223,19 +74507,19 @@ function resolveOperatorUid() {
|
|
|
74223
74507
|
return;
|
|
74224
74508
|
}
|
|
74225
74509
|
function operatorOwnedPaths(home2) {
|
|
74226
|
-
const root =
|
|
74510
|
+
const root = join61(home2, ".switchroom");
|
|
74227
74511
|
return [
|
|
74228
|
-
|
|
74229
|
-
|
|
74230
|
-
|
|
74231
|
-
|
|
74232
|
-
|
|
74233
|
-
|
|
74512
|
+
join61(root, "vault"),
|
|
74513
|
+
join61(root, "vault-auto-unlock"),
|
|
74514
|
+
join61(root, "vault-audit.log"),
|
|
74515
|
+
join61(root, "host-control-audit.log"),
|
|
74516
|
+
join61(root, "accounts"),
|
|
74517
|
+
join61(root, "compose")
|
|
74234
74518
|
];
|
|
74235
74519
|
}
|
|
74236
74520
|
function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
74237
74521
|
const chown = deps.chown ?? ((p, u, g) => chownSync3(p, u, g));
|
|
74238
|
-
const exists = deps.exists ?? ((p) =>
|
|
74522
|
+
const exists = deps.exists ?? ((p) => existsSync67(p));
|
|
74239
74523
|
const isSymlink = deps.isSymlink ?? ((p) => {
|
|
74240
74524
|
try {
|
|
74241
74525
|
return lstatSync7(p).isSymbolicLink();
|
|
@@ -74245,7 +74529,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
|
74245
74529
|
});
|
|
74246
74530
|
const isDir = deps.isDir ?? ((p) => {
|
|
74247
74531
|
try {
|
|
74248
|
-
return
|
|
74532
|
+
return statSync27(p).isDirectory();
|
|
74249
74533
|
} catch {
|
|
74250
74534
|
return false;
|
|
74251
74535
|
}
|
|
@@ -74279,7 +74563,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
|
|
|
74279
74563
|
} catch {}
|
|
74280
74564
|
if (isDir(target)) {
|
|
74281
74565
|
for (const entry of readdir2(target)) {
|
|
74282
|
-
visit(
|
|
74566
|
+
visit(join61(target, entry));
|
|
74283
74567
|
}
|
|
74284
74568
|
}
|
|
74285
74569
|
};
|
|
@@ -74293,17 +74577,17 @@ var EMBEDDED_EXAMPLES = {
|
|
|
74293
74577
|
switchroom: switchroom_default,
|
|
74294
74578
|
minimal: minimal_default
|
|
74295
74579
|
};
|
|
74296
|
-
var DEFAULT_COMPOSE_PATH2 =
|
|
74580
|
+
var DEFAULT_COMPOSE_PATH2 = join62(homedir35(), ".switchroom", "compose", "docker-compose.yml");
|
|
74297
74581
|
var COMPOSE_PROJECT2 = "switchroom";
|
|
74298
74582
|
function resolveVaultBindMountDir(homeDir, ctx) {
|
|
74299
74583
|
const isCustomPath = ctx.migrationKind === "custom-path-skipped";
|
|
74300
74584
|
if (isCustomPath && ctx.customVaultPath) {
|
|
74301
74585
|
return dirname19(ctx.customVaultPath);
|
|
74302
74586
|
}
|
|
74303
|
-
return
|
|
74587
|
+
return join62(homeDir, ".switchroom", "vault");
|
|
74304
74588
|
}
|
|
74305
74589
|
function inspectVaultBindMountDir(vaultDir) {
|
|
74306
|
-
if (!
|
|
74590
|
+
if (!existsSync68(vaultDir))
|
|
74307
74591
|
return { kind: "missing" };
|
|
74308
74592
|
const entries = readdirSync25(vaultDir);
|
|
74309
74593
|
const unknown = [];
|
|
@@ -74329,42 +74613,42 @@ function hasVaultRefs(value) {
|
|
|
74329
74613
|
return false;
|
|
74330
74614
|
}
|
|
74331
74615
|
async function ensureHostMountSources(config) {
|
|
74332
|
-
const home2 =
|
|
74616
|
+
const home2 = homedir35();
|
|
74333
74617
|
const dirs = [
|
|
74334
|
-
|
|
74335
|
-
|
|
74336
|
-
|
|
74337
|
-
|
|
74338
|
-
|
|
74618
|
+
join62(home2, ".switchroom", "approvals"),
|
|
74619
|
+
join62(home2, ".switchroom", "scheduler"),
|
|
74620
|
+
join62(home2, ".switchroom", "logs"),
|
|
74621
|
+
join62(home2, ".switchroom", "compose"),
|
|
74622
|
+
join62(home2, ".switchroom", "broker-operator")
|
|
74339
74623
|
];
|
|
74340
74624
|
for (const name of Object.keys(config.agents)) {
|
|
74341
|
-
dirs.push(
|
|
74342
|
-
dirs.push(
|
|
74343
|
-
dirs.push(
|
|
74625
|
+
dirs.push(join62(home2, ".switchroom", "agents", name));
|
|
74626
|
+
dirs.push(join62(home2, ".switchroom", "logs", name));
|
|
74627
|
+
dirs.push(join62(home2, ".claude", "projects", name));
|
|
74344
74628
|
}
|
|
74345
74629
|
for (const dir of dirs) {
|
|
74346
74630
|
await mkdir(dir, { recursive: true });
|
|
74347
74631
|
}
|
|
74348
|
-
const autoUnlockPath =
|
|
74349
|
-
if (!
|
|
74632
|
+
const autoUnlockPath = join62(home2, ".switchroom", "vault-auto-unlock");
|
|
74633
|
+
if (!existsSync68(autoUnlockPath)) {
|
|
74350
74634
|
writeFileSync32(autoUnlockPath, "", { mode: 384 });
|
|
74351
74635
|
}
|
|
74352
|
-
const auditLogPath =
|
|
74353
|
-
if (!
|
|
74636
|
+
const auditLogPath = join62(home2, ".switchroom", "vault-audit.log");
|
|
74637
|
+
if (!existsSync68(auditLogPath)) {
|
|
74354
74638
|
writeFileSync32(auditLogPath, "", { mode: 420 });
|
|
74355
74639
|
}
|
|
74356
|
-
const grantsDbPath =
|
|
74357
|
-
if (!
|
|
74640
|
+
const grantsDbPath = join62(home2, ".switchroom", "vault-grants.db");
|
|
74641
|
+
if (!existsSync68(grantsDbPath)) {
|
|
74358
74642
|
writeFileSync32(grantsDbPath, "", { mode: 384 });
|
|
74359
74643
|
}
|
|
74360
|
-
const hostdAuditLogPath =
|
|
74361
|
-
if (!
|
|
74644
|
+
const hostdAuditLogPath = join62(home2, ".switchroom", "host-control-audit.log");
|
|
74645
|
+
if (!existsSync68(hostdAuditLogPath)) {
|
|
74362
74646
|
writeFileSync32(hostdAuditLogPath, "", { mode: 420 });
|
|
74363
74647
|
}
|
|
74364
74648
|
}
|
|
74365
74649
|
function detectComposeV2() {
|
|
74366
74650
|
try {
|
|
74367
|
-
const out =
|
|
74651
|
+
const out = execFileSync20("docker", ["compose", "version"], {
|
|
74368
74652
|
stdio: ["ignore", "pipe", "pipe"],
|
|
74369
74653
|
encoding: "utf8"
|
|
74370
74654
|
});
|
|
@@ -74379,7 +74663,7 @@ ${out.trim()}`;
|
|
|
74379
74663
|
}
|
|
74380
74664
|
function runApplyPreflight(config, opts = {}) {
|
|
74381
74665
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
74382
|
-
if (hasVaultRefs(config) && !
|
|
74666
|
+
if (hasVaultRefs(config) && !existsSync68(vaultPath)) {
|
|
74383
74667
|
throw new Error(`Config references vault keys (vault:<name>) but ${vaultPath} is missing. ` + `Run \`switchroom setup\` first to initialise the vault.`);
|
|
74384
74668
|
}
|
|
74385
74669
|
const detect = opts.detectComposeV2 ?? detectComposeV2;
|
|
@@ -74390,7 +74674,7 @@ function runApplyPreflight(config, opts = {}) {
|
|
|
74390
74674
|
detectAndReportLegacyGdriveSlots(vaultPath);
|
|
74391
74675
|
}
|
|
74392
74676
|
function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
74393
|
-
if (!
|
|
74677
|
+
if (!existsSync68(vaultPath))
|
|
74394
74678
|
return;
|
|
74395
74679
|
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
74396
74680
|
if (!passphrase)
|
|
@@ -74429,10 +74713,10 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
74429
74713
|
`));
|
|
74430
74714
|
}
|
|
74431
74715
|
}
|
|
74432
|
-
function writeInstallTypeCache(homeDir =
|
|
74716
|
+
function writeInstallTypeCache(homeDir = homedir35()) {
|
|
74433
74717
|
const ctx = detectInstallType();
|
|
74434
|
-
const dir =
|
|
74435
|
-
const out =
|
|
74718
|
+
const dir = join62(homeDir, ".switchroom");
|
|
74719
|
+
const out = join62(dir, "install-type.json");
|
|
74436
74720
|
const tmp = `${out}.tmp`;
|
|
74437
74721
|
mkdirSync36(dir, { recursive: true });
|
|
74438
74722
|
const payload = {
|
|
@@ -74481,14 +74765,14 @@ Applying switchroom config...
|
|
|
74481
74765
|
writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
|
|
74482
74766
|
`));
|
|
74483
74767
|
try {
|
|
74484
|
-
installUpdatePromptHook(
|
|
74768
|
+
installUpdatePromptHook(join62(agentsDir, name));
|
|
74485
74769
|
} catch (hookErr) {
|
|
74486
74770
|
writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
|
|
74487
74771
|
`));
|
|
74488
74772
|
}
|
|
74489
74773
|
try {
|
|
74490
74774
|
const uid = allocateAgentUid(name);
|
|
74491
|
-
alignAgentUid(name,
|
|
74775
|
+
alignAgentUid(name, join62(agentsDir, name), uid, {
|
|
74492
74776
|
confirm: !options.nonInteractive,
|
|
74493
74777
|
writeOut
|
|
74494
74778
|
});
|
|
@@ -74525,7 +74809,7 @@ Applying switchroom config...
|
|
|
74525
74809
|
for (const name of agentNames) {
|
|
74526
74810
|
try {
|
|
74527
74811
|
const uid = allocateAgentUid(name);
|
|
74528
|
-
alignAgentUid(name,
|
|
74812
|
+
alignAgentUid(name, join62(agentsDir, name), uid, {
|
|
74529
74813
|
confirm: !options.nonInteractive,
|
|
74530
74814
|
writeOut
|
|
74531
74815
|
});
|
|
@@ -74539,7 +74823,7 @@ Applying switchroom config...
|
|
|
74539
74823
|
}
|
|
74540
74824
|
const vaultPathConfigured = config.vault?.path;
|
|
74541
74825
|
const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
|
|
74542
|
-
const migrationResult = migrateVaultLayout(
|
|
74826
|
+
const migrationResult = migrateVaultLayout(homedir35(), {
|
|
74543
74827
|
customVaultPath
|
|
74544
74828
|
});
|
|
74545
74829
|
switch (migrationResult.kind) {
|
|
@@ -74565,7 +74849,7 @@ Applying switchroom config...
|
|
|
74565
74849
|
writeErr(formatDivergentRecoveryMessage(migrationResult.details));
|
|
74566
74850
|
process.exit(4);
|
|
74567
74851
|
}
|
|
74568
|
-
const postMigrationInspect = inspectVaultLayout(
|
|
74852
|
+
const postMigrationInspect = inspectVaultLayout(homedir35());
|
|
74569
74853
|
const acceptable = [
|
|
74570
74854
|
"no-vault",
|
|
74571
74855
|
"already-migrated",
|
|
@@ -74580,7 +74864,7 @@ Applying switchroom config...
|
|
|
74580
74864
|
`));
|
|
74581
74865
|
process.exit(5);
|
|
74582
74866
|
}
|
|
74583
|
-
const vaultDir = resolveVaultBindMountDir(
|
|
74867
|
+
const vaultDir = resolveVaultBindMountDir(homedir35(), {
|
|
74584
74868
|
migrationKind: migrationResult.kind,
|
|
74585
74869
|
customVaultPath
|
|
74586
74870
|
});
|
|
@@ -74610,7 +74894,7 @@ Applying switchroom config...
|
|
|
74610
74894
|
imageTag: composeImageTag,
|
|
74611
74895
|
buildMode: options.buildLocal ? "local" : "pull",
|
|
74612
74896
|
buildContext: options.buildContext,
|
|
74613
|
-
homeDir:
|
|
74897
|
+
homeDir: homedir35(),
|
|
74614
74898
|
switchroomConfigPath,
|
|
74615
74899
|
operatorUid
|
|
74616
74900
|
});
|
|
@@ -74630,7 +74914,7 @@ Wrote `) + composePath + source_default.gray(` (${composeBytes} bytes)
|
|
|
74630
74914
|
writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
|
|
74631
74915
|
`));
|
|
74632
74916
|
if (process.geteuid?.() === 0 && operatorUid !== undefined) {
|
|
74633
|
-
const restored = restoreOperatorOwnership(
|
|
74917
|
+
const restored = restoreOperatorOwnership(homedir35(), operatorUid);
|
|
74634
74918
|
if (restored.length > 0) {
|
|
74635
74919
|
writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
|
|
74636
74920
|
`));
|
|
@@ -74678,7 +74962,7 @@ function copyExampleConfig2(name) {
|
|
|
74678
74962
|
throw new Error(`Invalid example name: ${name} (must match /^[a-z0-9_-]+$/)`);
|
|
74679
74963
|
}
|
|
74680
74964
|
const dest = resolve42(process.cwd(), "switchroom.yaml");
|
|
74681
|
-
if (
|
|
74965
|
+
if (existsSync68(dest)) {
|
|
74682
74966
|
console.error(source_default.yellow("switchroom.yaml already exists \u2014 skipping example copy"));
|
|
74683
74967
|
return;
|
|
74684
74968
|
}
|
|
@@ -74689,7 +74973,7 @@ function copyExampleConfig2(name) {
|
|
|
74689
74973
|
return;
|
|
74690
74974
|
}
|
|
74691
74975
|
const exampleFile = resolve42(import.meta.dirname, `../../examples/${name}.yaml`);
|
|
74692
|
-
if (!
|
|
74976
|
+
if (!existsSync68(exampleFile)) {
|
|
74693
74977
|
throw new Error(`Example config not found: ${name}.yaml (available: ${Object.keys(EMBEDDED_EXAMPLES).join(", ")})`);
|
|
74694
74978
|
}
|
|
74695
74979
|
copyFileSync11(exampleFile, dest);
|
|
@@ -74700,8 +74984,8 @@ function findUnwritableAgentDirs(config, opts) {
|
|
|
74700
74984
|
const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
|
|
74701
74985
|
const unwritable = [];
|
|
74702
74986
|
for (const name of targets) {
|
|
74703
|
-
const startSh =
|
|
74704
|
-
if (!
|
|
74987
|
+
const startSh = join62(agentsDir, name, "start.sh");
|
|
74988
|
+
if (!existsSync68(startSh))
|
|
74705
74989
|
continue;
|
|
74706
74990
|
try {
|
|
74707
74991
|
accessSync3(startSh, fsConstants6.W_OK);
|
|
@@ -74879,9 +75163,9 @@ function runRedactStdin() {
|
|
|
74879
75163
|
}
|
|
74880
75164
|
|
|
74881
75165
|
// src/cli/status-ask.ts
|
|
74882
|
-
import { readFileSync as readFileSync55, existsSync as
|
|
74883
|
-
import { join as
|
|
74884
|
-
import { homedir as
|
|
75166
|
+
import { readFileSync as readFileSync55, existsSync as existsSync69, readdirSync as readdirSync26 } from "node:fs";
|
|
75167
|
+
import { join as join63 } from "node:path";
|
|
75168
|
+
import { homedir as homedir36 } from "node:os";
|
|
74885
75169
|
|
|
74886
75170
|
// src/status-ask/report.ts
|
|
74887
75171
|
function parseJsonl(content) {
|
|
@@ -75202,7 +75486,7 @@ function runReport(opts) {
|
|
|
75202
75486
|
function resolveSources(explicitPath) {
|
|
75203
75487
|
if (explicitPath != null && explicitPath.trim() !== "") {
|
|
75204
75488
|
const trimmed = explicitPath.trim();
|
|
75205
|
-
if (!
|
|
75489
|
+
if (!existsSync69(trimmed)) {
|
|
75206
75490
|
process.stderr.write(`status-ask report: ${trimmed}: file not found
|
|
75207
75491
|
`);
|
|
75208
75492
|
process.exit(1);
|
|
@@ -75216,9 +75500,9 @@ function resolveSources(explicitPath) {
|
|
|
75216
75500
|
const config = loadConfig();
|
|
75217
75501
|
agentsDir = resolveAgentsDir(config);
|
|
75218
75502
|
} catch {
|
|
75219
|
-
agentsDir =
|
|
75503
|
+
agentsDir = join63(homedir36(), ".switchroom", "agents");
|
|
75220
75504
|
}
|
|
75221
|
-
if (!
|
|
75505
|
+
if (!existsSync69(agentsDir))
|
|
75222
75506
|
return [];
|
|
75223
75507
|
const sources = [];
|
|
75224
75508
|
let entries;
|
|
@@ -75228,8 +75512,8 @@ function resolveSources(explicitPath) {
|
|
|
75228
75512
|
return [];
|
|
75229
75513
|
}
|
|
75230
75514
|
for (const name of entries) {
|
|
75231
|
-
const path8 =
|
|
75232
|
-
if (
|
|
75515
|
+
const path8 = join63(agentsDir, name, "runtime-metrics.jsonl");
|
|
75516
|
+
if (existsSync69(path8)) {
|
|
75233
75517
|
sources.push({ path: path8, agent: name });
|
|
75234
75518
|
}
|
|
75235
75519
|
}
|
|
@@ -75250,17 +75534,17 @@ function inferAgentFromPath(p) {
|
|
|
75250
75534
|
|
|
75251
75535
|
// src/cli/agent-config.ts
|
|
75252
75536
|
init_helpers();
|
|
75253
|
-
import { join as
|
|
75254
|
-
import { homedir as
|
|
75537
|
+
import { join as join64 } from "node:path";
|
|
75538
|
+
import { homedir as homedir37 } from "node:os";
|
|
75255
75539
|
import {
|
|
75256
|
-
existsSync as
|
|
75540
|
+
existsSync as existsSync70,
|
|
75257
75541
|
mkdirSync as mkdirSync37,
|
|
75258
75542
|
appendFileSync as appendFileSync4,
|
|
75259
75543
|
readFileSync as readFileSync56
|
|
75260
75544
|
} from "node:fs";
|
|
75261
|
-
var AUDIT_ROOT =
|
|
75545
|
+
var AUDIT_ROOT = join64(homedir37(), ".switchroom", "audit");
|
|
75262
75546
|
function auditPathFor(agent) {
|
|
75263
|
-
return
|
|
75547
|
+
return join64(AUDIT_ROOT, agent, "agent-config.jsonl");
|
|
75264
75548
|
}
|
|
75265
75549
|
function appendAudit(agent, cmd, args, exit, opts = {}) {
|
|
75266
75550
|
const row = {
|
|
@@ -75274,7 +75558,7 @@ function appendAudit(agent, cmd, args, exit, opts = {}) {
|
|
|
75274
75558
|
const path8 = opts.auditPath ?? auditPathFor(agent);
|
|
75275
75559
|
const dir = path8.slice(0, path8.lastIndexOf("/"));
|
|
75276
75560
|
try {
|
|
75277
|
-
if (!
|
|
75561
|
+
if (!existsSync70(dir)) {
|
|
75278
75562
|
mkdirSync37(dir, { recursive: true });
|
|
75279
75563
|
}
|
|
75280
75564
|
appendFileSync4(path8, JSON.stringify(row) + `
|
|
@@ -75286,7 +75570,7 @@ function isContainerContext(env2 = process.env, opts = {}) {
|
|
|
75286
75570
|
return true;
|
|
75287
75571
|
const probe2 = opts.dockerEnvPath ?? "/.dockerenv";
|
|
75288
75572
|
try {
|
|
75289
|
-
if (
|
|
75573
|
+
if (existsSync70(probe2))
|
|
75290
75574
|
return true;
|
|
75291
75575
|
} catch {}
|
|
75292
75576
|
return false;
|
|
@@ -75347,7 +75631,7 @@ function getAgentSlice(config, agent) {
|
|
|
75347
75631
|
}
|
|
75348
75632
|
function readAuditTail(agent, limit, opts = {}) {
|
|
75349
75633
|
const path8 = opts.auditPath ?? auditPathFor(agent);
|
|
75350
|
-
if (!
|
|
75634
|
+
if (!existsSync70(path8))
|
|
75351
75635
|
return [];
|
|
75352
75636
|
let raw;
|
|
75353
75637
|
try {
|
|
@@ -75507,32 +75791,32 @@ var import_yaml14 = __toESM(require_dist(), 1);
|
|
|
75507
75791
|
init_paths();
|
|
75508
75792
|
import {
|
|
75509
75793
|
closeSync as closeSync13,
|
|
75510
|
-
existsSync as
|
|
75794
|
+
existsSync as existsSync71,
|
|
75511
75795
|
fsyncSync as fsyncSync6,
|
|
75512
75796
|
mkdirSync as mkdirSync38,
|
|
75513
75797
|
openSync as openSync13,
|
|
75514
75798
|
readdirSync as readdirSync27,
|
|
75515
75799
|
readFileSync as readFileSync57,
|
|
75516
75800
|
renameSync as renameSync14,
|
|
75517
|
-
statSync as
|
|
75801
|
+
statSync as statSync28,
|
|
75518
75802
|
unlinkSync as unlinkSync14,
|
|
75519
75803
|
writeSync as writeSync8
|
|
75520
75804
|
} from "node:fs";
|
|
75521
|
-
import { join as
|
|
75805
|
+
import { join as join65, resolve as resolve43 } from "node:path";
|
|
75522
75806
|
var STAGING_SUBDIR = ".staging";
|
|
75523
75807
|
function overlayPathsFor(agent, opts = {}) {
|
|
75524
75808
|
const base = opts.root ? resolve43(opts.root, agent) : resolve43(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
75525
|
-
const scheduleDir =
|
|
75526
|
-
const scheduleStagingDir =
|
|
75527
|
-
const skillsDir =
|
|
75528
|
-
const skillsStagingDir =
|
|
75809
|
+
const scheduleDir = join65(base, "schedule.d");
|
|
75810
|
+
const scheduleStagingDir = join65(scheduleDir, STAGING_SUBDIR);
|
|
75811
|
+
const skillsDir = join65(base, "skills.d");
|
|
75812
|
+
const skillsStagingDir = join65(skillsDir, STAGING_SUBDIR);
|
|
75529
75813
|
return {
|
|
75530
75814
|
agentRoot: base,
|
|
75531
75815
|
scheduleDir,
|
|
75532
75816
|
scheduleStagingDir,
|
|
75533
75817
|
skillsDir,
|
|
75534
75818
|
skillsStagingDir,
|
|
75535
|
-
lockPath:
|
|
75819
|
+
lockPath: join65(base, ".lock"),
|
|
75536
75820
|
stagingDir: scheduleStagingDir
|
|
75537
75821
|
};
|
|
75538
75822
|
}
|
|
@@ -75558,7 +75842,7 @@ function withAgentLock(paths, fn) {
|
|
|
75558
75842
|
if (e.code !== "EEXIST")
|
|
75559
75843
|
throw err;
|
|
75560
75844
|
try {
|
|
75561
|
-
const age = Date.now() -
|
|
75845
|
+
const age = Date.now() - statSync28(paths.lockPath).mtimeMs;
|
|
75562
75846
|
if (age > 30000) {
|
|
75563
75847
|
unlinkSync14(paths.lockPath);
|
|
75564
75848
|
continue;
|
|
@@ -75586,8 +75870,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
75586
75870
|
const paths = overlayPathsFor(agent, opts);
|
|
75587
75871
|
return withAgentLock(paths, () => {
|
|
75588
75872
|
ensureDirs(paths);
|
|
75589
|
-
const stagingPath =
|
|
75590
|
-
const finalPath =
|
|
75873
|
+
const stagingPath = join65(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
75874
|
+
const finalPath = join65(paths.scheduleDir, `${slug}.yaml`);
|
|
75591
75875
|
const fd = openSync13(stagingPath, "w", 384);
|
|
75592
75876
|
try {
|
|
75593
75877
|
writeSync8(fd, yamlText);
|
|
@@ -75603,8 +75887,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
75603
75887
|
const paths = overlayPathsFor(agent, opts);
|
|
75604
75888
|
return withAgentLock(paths, () => {
|
|
75605
75889
|
ensureSkillsDirs(paths);
|
|
75606
|
-
const stagingPath =
|
|
75607
|
-
const finalPath =
|
|
75890
|
+
const stagingPath = join65(paths.skillsStagingDir, `${slug}.yaml`);
|
|
75891
|
+
const finalPath = join65(paths.skillsDir, `${slug}.yaml`);
|
|
75608
75892
|
const fd = openSync13(stagingPath, "w", 384);
|
|
75609
75893
|
try {
|
|
75610
75894
|
writeSync8(fd, yamlText);
|
|
@@ -75619,8 +75903,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
75619
75903
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
75620
75904
|
const paths = overlayPathsFor(agent, opts);
|
|
75621
75905
|
return withAgentLock(paths, () => {
|
|
75622
|
-
const finalPath =
|
|
75623
|
-
if (!
|
|
75906
|
+
const finalPath = join65(paths.skillsDir, `${slug}.yaml`);
|
|
75907
|
+
if (!existsSync71(finalPath))
|
|
75624
75908
|
return false;
|
|
75625
75909
|
unlinkSync14(finalPath);
|
|
75626
75910
|
return true;
|
|
@@ -75628,13 +75912,13 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
|
75628
75912
|
}
|
|
75629
75913
|
function listSkillsOverlayEntries(agent, opts = {}) {
|
|
75630
75914
|
const paths = overlayPathsFor(agent, opts);
|
|
75631
|
-
if (!
|
|
75915
|
+
if (!existsSync71(paths.skillsDir))
|
|
75632
75916
|
return [];
|
|
75633
75917
|
const out = [];
|
|
75634
75918
|
for (const name of readdirSync27(paths.skillsDir)) {
|
|
75635
75919
|
if (!/\.ya?ml$/i.test(name))
|
|
75636
75920
|
continue;
|
|
75637
|
-
const full =
|
|
75921
|
+
const full = join65(paths.skillsDir, name);
|
|
75638
75922
|
try {
|
|
75639
75923
|
const raw = readFileSync57(full, "utf-8");
|
|
75640
75924
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -75646,8 +75930,8 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
75646
75930
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
75647
75931
|
const paths = overlayPathsFor(agent, opts);
|
|
75648
75932
|
return withAgentLock(paths, () => {
|
|
75649
|
-
const finalPath =
|
|
75650
|
-
if (!
|
|
75933
|
+
const finalPath = join65(paths.scheduleDir, `${slug}.yaml`);
|
|
75934
|
+
if (!existsSync71(finalPath))
|
|
75651
75935
|
return false;
|
|
75652
75936
|
unlinkSync14(finalPath);
|
|
75653
75937
|
return true;
|
|
@@ -75655,13 +75939,13 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
|
75655
75939
|
}
|
|
75656
75940
|
function listOverlayEntries(agent, opts = {}) {
|
|
75657
75941
|
const paths = overlayPathsFor(agent, opts);
|
|
75658
|
-
if (!
|
|
75942
|
+
if (!existsSync71(paths.scheduleDir))
|
|
75659
75943
|
return [];
|
|
75660
75944
|
const out = [];
|
|
75661
75945
|
for (const name of readdirSync27(paths.scheduleDir)) {
|
|
75662
75946
|
if (!/\.ya?ml$/i.test(name))
|
|
75663
75947
|
continue;
|
|
75664
|
-
const full =
|
|
75948
|
+
const full = join65(paths.scheduleDir, name);
|
|
75665
75949
|
try {
|
|
75666
75950
|
const raw = readFileSync57(full, "utf-8");
|
|
75667
75951
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -75806,7 +76090,7 @@ function reconcileAgentCronOnly(agent) {
|
|
|
75806
76090
|
// src/cli/agent-config-pending.ts
|
|
75807
76091
|
import {
|
|
75808
76092
|
closeSync as closeSync14,
|
|
75809
|
-
existsSync as
|
|
76093
|
+
existsSync as existsSync72,
|
|
75810
76094
|
fsyncSync as fsyncSync7,
|
|
75811
76095
|
mkdirSync as mkdirSync39,
|
|
75812
76096
|
openSync as openSync14,
|
|
@@ -75817,12 +76101,12 @@ import {
|
|
|
75817
76101
|
writeFileSync as writeFileSync33,
|
|
75818
76102
|
writeSync as writeSync9
|
|
75819
76103
|
} from "node:fs";
|
|
75820
|
-
import { join as
|
|
76104
|
+
import { join as join66 } from "node:path";
|
|
75821
76105
|
import { randomBytes as randomBytes13 } from "node:crypto";
|
|
75822
76106
|
var STAGE_ID_PREFIX = "cap_";
|
|
75823
76107
|
function pendingDir(agent, opts = {}) {
|
|
75824
76108
|
const paths = overlayPathsFor(agent, opts);
|
|
75825
|
-
return
|
|
76109
|
+
return join66(paths.scheduleDir, ".pending");
|
|
75826
76110
|
}
|
|
75827
76111
|
function ensurePendingDir(agent, opts = {}) {
|
|
75828
76112
|
const dir = pendingDir(agent, opts);
|
|
@@ -75835,8 +76119,8 @@ function newStageId() {
|
|
|
75835
76119
|
function stagePendingScheduleEntry(opts) {
|
|
75836
76120
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
75837
76121
|
const stageId = opts.stageId ?? newStageId();
|
|
75838
|
-
const yamlPath =
|
|
75839
|
-
const metaPath =
|
|
76122
|
+
const yamlPath = join66(dir, `${stageId}.yaml`);
|
|
76123
|
+
const metaPath = join66(dir, `${stageId}.meta.json`);
|
|
75840
76124
|
const meta = {
|
|
75841
76125
|
v: 1,
|
|
75842
76126
|
stage_id: stageId,
|
|
@@ -75863,16 +76147,16 @@ function stagePendingScheduleEntry(opts) {
|
|
|
75863
76147
|
}
|
|
75864
76148
|
function listPendingScheduleEntries(agent, opts = {}) {
|
|
75865
76149
|
const dir = pendingDir(agent, opts);
|
|
75866
|
-
if (!
|
|
76150
|
+
if (!existsSync72(dir))
|
|
75867
76151
|
return [];
|
|
75868
76152
|
const out = [];
|
|
75869
76153
|
for (const name of readdirSync28(dir).sort()) {
|
|
75870
76154
|
if (!name.endsWith(".meta.json"))
|
|
75871
76155
|
continue;
|
|
75872
76156
|
const stageId = name.slice(0, -".meta.json".length);
|
|
75873
|
-
const metaPath =
|
|
75874
|
-
const yamlPath =
|
|
75875
|
-
if (!
|
|
76157
|
+
const metaPath = join66(dir, name);
|
|
76158
|
+
const yamlPath = join66(dir, `${stageId}.yaml`);
|
|
76159
|
+
if (!existsSync72(yamlPath))
|
|
75876
76160
|
continue;
|
|
75877
76161
|
try {
|
|
75878
76162
|
const meta = JSON.parse(readFileSync58(metaPath, "utf-8"));
|
|
@@ -75890,8 +76174,8 @@ function commitPendingScheduleEntry(opts) {
|
|
|
75890
76174
|
return { committed: false, reason: "not_found" };
|
|
75891
76175
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
75892
76176
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
75893
|
-
const finalPath =
|
|
75894
|
-
if (
|
|
76177
|
+
const finalPath = join66(paths.scheduleDir, `${slug}.yaml`);
|
|
76178
|
+
if (existsSync72(finalPath)) {
|
|
75895
76179
|
return { committed: false, reason: "slug_collision" };
|
|
75896
76180
|
}
|
|
75897
76181
|
renameSync15(match.yamlPath, finalPath);
|
|
@@ -75913,7 +76197,7 @@ function denyPendingScheduleEntry(opts) {
|
|
|
75913
76197
|
}
|
|
75914
76198
|
|
|
75915
76199
|
// src/cli/agent-config-write.ts
|
|
75916
|
-
import { existsSync as
|
|
76200
|
+
import { existsSync as existsSync73, readFileSync as readFileSync59 } from "node:fs";
|
|
75917
76201
|
var MAX_ENTRIES_PER_AGENT = 20;
|
|
75918
76202
|
function checkOperatorContext(verb, env2 = process.env) {
|
|
75919
76203
|
if (env2.SWITCHROOM_OPERATOR === "1")
|
|
@@ -76147,7 +76431,7 @@ function scheduleRemove(opts) {
|
|
|
76147
76431
|
}
|
|
76148
76432
|
let priorContent = null;
|
|
76149
76433
|
try {
|
|
76150
|
-
if (
|
|
76434
|
+
if (existsSync73(match.path))
|
|
76151
76435
|
priorContent = readFileSync59(match.path, "utf-8");
|
|
76152
76436
|
} catch {}
|
|
76153
76437
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
@@ -76340,10 +76624,10 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
76340
76624
|
|
|
76341
76625
|
// src/cli/agent-config-skill-write.ts
|
|
76342
76626
|
var import_yaml15 = __toESM(require_dist(), 1);
|
|
76343
|
-
import { existsSync as
|
|
76627
|
+
import { existsSync as existsSync74 } from "node:fs";
|
|
76344
76628
|
init_reconcile_default_skills();
|
|
76345
76629
|
var import_yaml16 = __toESM(require_dist(), 1);
|
|
76346
|
-
import { join as
|
|
76630
|
+
import { join as join67 } from "node:path";
|
|
76347
76631
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
76348
76632
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
76349
76633
|
function exitCodeFor2(code) {
|
|
@@ -76418,8 +76702,8 @@ function skillInstall(opts) {
|
|
|
76418
76702
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
76419
76703
|
}
|
|
76420
76704
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
76421
|
-
const skillPath =
|
|
76422
|
-
if (!
|
|
76705
|
+
const skillPath = join67(poolDir, skillName);
|
|
76706
|
+
if (!existsSync74(skillPath)) {
|
|
76423
76707
|
return err("E_SKILL_NOT_FOUND", `bundled skill not found at ${skillPath}. The operator needs to ` + `place the skill at this path before the agent can opt in.`);
|
|
76424
76708
|
}
|
|
76425
76709
|
const yamlText = import_yaml15.stringify({ skills: [skillName] });
|
|
@@ -76603,23 +76887,23 @@ init_source();
|
|
|
76603
76887
|
init_helpers();
|
|
76604
76888
|
init_loader();
|
|
76605
76889
|
import {
|
|
76606
|
-
existsSync as
|
|
76890
|
+
existsSync as existsSync76,
|
|
76607
76891
|
readdirSync as readdirSync29,
|
|
76608
76892
|
readFileSync as readFileSync61,
|
|
76609
76893
|
renameSync as renameSync16,
|
|
76610
|
-
statSync as
|
|
76894
|
+
statSync as statSync29,
|
|
76611
76895
|
unlinkSync as unlinkSync16
|
|
76612
76896
|
} from "node:fs";
|
|
76613
76897
|
import { createHash as createHash13 } from "node:crypto";
|
|
76614
|
-
import { join as
|
|
76898
|
+
import { join as join68 } from "node:path";
|
|
76615
76899
|
function planCronUnitRenames(agentsDir, agents) {
|
|
76616
76900
|
const plans = [];
|
|
76617
76901
|
for (const [agentName, agentConfig] of Object.entries(agents)) {
|
|
76618
76902
|
const schedule = agentConfig.schedule ?? [];
|
|
76619
76903
|
if (schedule.length === 0)
|
|
76620
76904
|
continue;
|
|
76621
|
-
const telegramDir =
|
|
76622
|
-
if (!
|
|
76905
|
+
const telegramDir = join68(agentsDir, agentName, "telegram");
|
|
76906
|
+
if (!existsSync76(telegramDir))
|
|
76623
76907
|
continue;
|
|
76624
76908
|
let entries;
|
|
76625
76909
|
try {
|
|
@@ -76640,8 +76924,8 @@ function planCronUnitRenames(agentsDir, agents) {
|
|
|
76640
76924
|
continue;
|
|
76641
76925
|
plans.push({
|
|
76642
76926
|
agent: agentName,
|
|
76643
|
-
from:
|
|
76644
|
-
to:
|
|
76927
|
+
from: join68(telegramDir, file),
|
|
76928
|
+
to: join68(telegramDir, canonical),
|
|
76645
76929
|
scheduleIdx: idx,
|
|
76646
76930
|
entry
|
|
76647
76931
|
});
|
|
@@ -76653,7 +76937,7 @@ function sha256File2(path8) {
|
|
|
76653
76937
|
return createHash13("sha256").update(readFileSync61(path8)).digest("hex");
|
|
76654
76938
|
}
|
|
76655
76939
|
function renamePair(from, to, opts = {}) {
|
|
76656
|
-
if (
|
|
76940
|
+
if (existsSync76(to)) {
|
|
76657
76941
|
let identical = false;
|
|
76658
76942
|
try {
|
|
76659
76943
|
identical = sha256File2(from) === sha256File2(to);
|
|
@@ -76746,7 +77030,7 @@ function registerMigrateCommand(program3) {
|
|
|
76746
77030
|
const fromSidecar = p.from.replace(/\.sh$/, ".source");
|
|
76747
77031
|
const toSidecar = p.to.replace(/\.sh$/, ".source");
|
|
76748
77032
|
let sidecarStatus = null;
|
|
76749
|
-
if (
|
|
77033
|
+
if (existsSync76(fromSidecar) && statSync29(fromSidecar).isFile()) {
|
|
76750
77034
|
sidecarStatus = renamePair(fromSidecar, toSidecar);
|
|
76751
77035
|
}
|
|
76752
77036
|
switch (status.kind) {
|
|
@@ -76778,9 +77062,9 @@ function registerMigrateCommand(program3) {
|
|
|
76778
77062
|
// src/cli/hostd.ts
|
|
76779
77063
|
init_source();
|
|
76780
77064
|
init_helpers();
|
|
76781
|
-
import { existsSync as
|
|
76782
|
-
import { homedir as
|
|
76783
|
-
import { join as
|
|
77065
|
+
import { existsSync as existsSync77, mkdirSync as mkdirSync40, readdirSync as readdirSync30, readFileSync as readFileSync62, writeFileSync as writeFileSync34, statSync as statSync30, copyFileSync as copyFileSync12 } from "node:fs";
|
|
77066
|
+
import { homedir as homedir38 } from "node:os";
|
|
77067
|
+
import { join as join69 } from "node:path";
|
|
76784
77068
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
76785
77069
|
init_audit_reader();
|
|
76786
77070
|
var DEFAULT_IMAGE_TAG = "latest";
|
|
@@ -76871,14 +77155,14 @@ networks:
|
|
|
76871
77155
|
`;
|
|
76872
77156
|
}
|
|
76873
77157
|
function hostdDir() {
|
|
76874
|
-
return
|
|
77158
|
+
return join69(homedir38(), ".switchroom", "hostd");
|
|
76875
77159
|
}
|
|
76876
77160
|
function hostdComposePath() {
|
|
76877
|
-
return
|
|
77161
|
+
return join69(hostdDir(), "docker-compose.yml");
|
|
76878
77162
|
}
|
|
76879
77163
|
function backupExistingCompose() {
|
|
76880
77164
|
const p = hostdComposePath();
|
|
76881
|
-
if (!
|
|
77165
|
+
if (!existsSync77(p))
|
|
76882
77166
|
return null;
|
|
76883
77167
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
76884
77168
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -76913,7 +77197,7 @@ async function doInstall(opts, program3) {
|
|
|
76913
77197
|
const composePath = hostdComposePath();
|
|
76914
77198
|
mkdirSync40(dir, { recursive: true });
|
|
76915
77199
|
const yaml = renderHostdComposeFile({
|
|
76916
|
-
hostHome:
|
|
77200
|
+
hostHome: homedir38(),
|
|
76917
77201
|
imageTag: opts.tag ?? DEFAULT_IMAGE_TAG,
|
|
76918
77202
|
operatorUid: resolveOperatorUid()
|
|
76919
77203
|
});
|
|
@@ -76955,7 +77239,7 @@ function doStatus() {
|
|
|
76955
77239
|
const composeYml = hostdComposePath();
|
|
76956
77240
|
console.log(source_default.bold("switchroom-hostd"));
|
|
76957
77241
|
console.log("");
|
|
76958
|
-
if (!
|
|
77242
|
+
if (!existsSync77(composeYml)) {
|
|
76959
77243
|
console.log(source_default.yellow(" compose: not installed"));
|
|
76960
77244
|
console.log(source_default.dim(" run `switchroom hostd install` to set up."));
|
|
76961
77245
|
return;
|
|
@@ -76976,15 +77260,15 @@ function doStatus() {
|
|
|
76976
77260
|
} else {
|
|
76977
77261
|
console.log(source_default.green(` container: ${ps.stdout.trim()}`));
|
|
76978
77262
|
}
|
|
76979
|
-
if (
|
|
77263
|
+
if (existsSync77(dir)) {
|
|
76980
77264
|
const entries = [];
|
|
76981
77265
|
try {
|
|
76982
77266
|
for (const name of readdirSync30(dir)) {
|
|
76983
77267
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
76984
77268
|
continue;
|
|
76985
|
-
const sockPath =
|
|
76986
|
-
if (
|
|
76987
|
-
const st =
|
|
77269
|
+
const sockPath = join69(dir, name, "sock");
|
|
77270
|
+
if (existsSync77(sockPath)) {
|
|
77271
|
+
const st = statSync30(sockPath);
|
|
76988
77272
|
if ((st.mode & 61440) === 49152) {
|
|
76989
77273
|
entries.push(`${name} \u2192 ${sockPath}`);
|
|
76990
77274
|
}
|
|
@@ -77002,7 +77286,7 @@ function doStatus() {
|
|
|
77002
77286
|
}
|
|
77003
77287
|
function doUninstall() {
|
|
77004
77288
|
const composeYml = hostdComposePath();
|
|
77005
|
-
if (!
|
|
77289
|
+
if (!existsSync77(composeYml)) {
|
|
77006
77290
|
console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
|
|
77007
77291
|
return;
|
|
77008
77292
|
}
|
|
@@ -77026,7 +77310,7 @@ function registerHostdCommand(program3) {
|
|
|
77026
77310
|
hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
|
|
77027
77311
|
hostd.command("audit").description("Tail and filter the hostd audit log (privileged-verb call history)").option("--tail <n>", "Number of matching entries to show (default: 50)", "50").option("--agent <name>", "Filter to a specific caller agent").option("--op <verb>", "Filter to a specific hostd verb (e.g. update_apply, agent_restart)").option("--error", "Show only failed (error/denied) entries").option("--verbose", "Show the captured stderr / error tail under each failed row").option("--path <file>", "Override audit log path (for debugging)").action((opts) => {
|
|
77028
77312
|
const logPath = opts.path ?? defaultAuditLogPath2();
|
|
77029
|
-
if (!
|
|
77313
|
+
if (!existsSync77(logPath)) {
|
|
77030
77314
|
console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
|
|
77031
77315
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
77032
77316
|
return;
|