switchroom 0.16.22 → 0.16.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/self-improve-apply-guard-pretool.mjs +6 -0
- package/dist/cli/skill-validate-pretool.mjs +2865 -48
- package/dist/cli/switchroom.js +338 -92
- package/dist/host-control/main.js +1 -1
- package/package.json +5 -5
- package/telegram-plugin/answer-stream.ts +13 -43
- package/telegram-plugin/auth-snapshot-format.ts +27 -30
- package/telegram-plugin/auto-fallback-fleet.ts +6 -11
- package/telegram-plugin/card-format.ts +28 -25
- package/telegram-plugin/credits-watch.ts +5 -10
- package/telegram-plugin/dist/gateway/gateway.js +1670 -1892
- package/telegram-plugin/draft-stream.ts +4 -4
- package/telegram-plugin/format.ts +101 -662
- package/telegram-plugin/gateway/approval-callback.ts +2 -3
- package/telegram-plugin/gateway/approval-card.test.ts +17 -4
- package/telegram-plugin/gateway/approval-card.ts +16 -6
- package/telegram-plugin/gateway/approvals-commands.ts +18 -24
- package/telegram-plugin/gateway/auth-command.ts +74 -74
- package/telegram-plugin/gateway/auth-line.ts +5 -15
- package/telegram-plugin/gateway/boot-card.ts +20 -22
- package/telegram-plugin/gateway/boot-version.ts +3 -2
- package/telegram-plugin/gateway/config-approval-handler.test.ts +35 -33
- package/telegram-plugin/gateway/config-approval-handler.ts +24 -24
- package/telegram-plugin/gateway/config-snapshot.ts +9 -9
- package/telegram-plugin/gateway/diff-preview-card.test.ts +8 -8
- package/telegram-plugin/gateway/diff-preview-card.ts +2 -5
- package/telegram-plugin/gateway/disconnect-flush.ts +0 -4
- package/telegram-plugin/gateway/effort-command.ts +17 -17
- package/telegram-plugin/gateway/folder-picker-handler.test.ts +8 -2
- package/telegram-plugin/gateway/folder-picker-handler.ts +3 -4
- package/telegram-plugin/gateway/gateway.ts +751 -624
- package/telegram-plugin/gateway/inject-handler.test.ts +15 -13
- package/telegram-plugin/gateway/inject-handler.ts +5 -5
- package/telegram-plugin/gateway/ipc-protocol.ts +33 -1
- package/telegram-plugin/gateway/ipc-server.ts +28 -0
- package/telegram-plugin/gateway/linear-activity.ts +16 -14
- package/telegram-plugin/gateway/linear-setup.ts +1 -1
- package/telegram-plugin/gateway/model-command.ts +25 -25
- package/telegram-plugin/gateway/oversize-card-body.ts +6 -7
- package/telegram-plugin/gateway/skill-proposal-card.ts +167 -0
- package/telegram-plugin/inline-keyboard-callbacks.ts +19 -13
- package/telegram-plugin/issues-card.ts +6 -7
- package/telegram-plugin/model-unavailable.ts +8 -12
- package/telegram-plugin/operator-events-history.ts +1 -1
- package/telegram-plugin/operator-events.ts +24 -28
- package/telegram-plugin/package.json +1 -1
- package/telegram-plugin/pending-work-progress.ts +36 -36
- package/telegram-plugin/permission-title.ts +36 -20
- package/telegram-plugin/pty-partial-handler.ts +5 -13
- package/telegram-plugin/quota-check.ts +5 -5
- package/telegram-plugin/quota-watch.ts +13 -18
- package/telegram-plugin/recent-outbound-dedup.ts +5 -5
- package/telegram-plugin/registry/turns-schema.ts +44 -2
- package/telegram-plugin/retry-api-call.ts +15 -7
- package/telegram-plugin/rich-send.ts +57 -0
- package/telegram-plugin/secret-detect/vault-error.test.ts +6 -6
- package/telegram-plugin/secret-detect/vault-error.ts +29 -22
- package/telegram-plugin/shared/bot-runtime.ts +23 -6
- package/telegram-plugin/silence-poke.ts +26 -69
- package/telegram-plugin/slot-banner-driver.ts +9 -6
- package/telegram-plugin/slot-banner.ts +5 -8
- package/telegram-plugin/steering.ts +0 -4
- package/telegram-plugin/stream-controller.ts +59 -62
- package/telegram-plugin/stream-reply-handler.ts +40 -97
- package/telegram-plugin/subagent-watcher.ts +2 -2
- package/telegram-plugin/tests/answer-stream-silent-markers.test.ts +5 -2
- package/telegram-plugin/tests/answer-stream.test.ts +54 -63
- package/telegram-plugin/tests/auth-command-format2.test.ts +4 -4
- package/telegram-plugin/tests/auth-command-vernacular.test.ts +3 -2
- package/telegram-plugin/tests/auth-snapshot-format.test.ts +19 -18
- package/telegram-plugin/tests/auto-fallback-fleet.test.ts +15 -13
- package/telegram-plugin/tests/boot-card-reason-to-render.test.ts +27 -12
- package/telegram-plugin/tests/boot-card-render.test.ts +59 -48
- package/telegram-plugin/tests/boot-version-string.test.ts +0 -0
- package/telegram-plugin/tests/bot-api.harness.ts +23 -1
- package/telegram-plugin/tests/bot-runtime.test.ts +23 -18
- package/telegram-plugin/tests/card-format.test.ts +6 -4
- package/telegram-plugin/tests/config-snapshot.test.ts +1 -1
- package/telegram-plugin/tests/credits-watch.test.ts +5 -5
- package/telegram-plugin/tests/fake-bot-api.ts +58 -4
- package/telegram-plugin/tests/finalize-callback.test.ts +11 -9
- package/telegram-plugin/tests/foreground-nesting.test.ts +1 -1
- package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +3 -13
- package/telegram-plugin/tests/issues-card.test.ts +15 -12
- package/telegram-plugin/tests/linear-agent-activity.test.ts +8 -5
- package/telegram-plugin/tests/model-command.test.ts +2 -2
- package/telegram-plugin/tests/model-unavailable.test.ts +13 -13
- package/telegram-plugin/tests/multi-turn-continuity.test.ts +6 -10
- package/telegram-plugin/tests/operator-events.test.ts +7 -9
- package/telegram-plugin/tests/pending-work-progress.test.ts +20 -21
- package/telegram-plugin/tests/permission-title.test.ts +45 -41
- package/telegram-plugin/tests/pty-partial-handler.test.ts +8 -8
- package/telegram-plugin/tests/quota-check.test.ts +3 -3
- package/telegram-plugin/tests/quota-watch.test.ts +8 -4
- package/telegram-plugin/tests/secret-detect-delete-must-surface-failures.test.ts +4 -3
- package/telegram-plugin/tests/silence-poke.test.ts +75 -112
- package/telegram-plugin/tests/single-mode-stream-reply.test.ts +137 -0
- package/telegram-plugin/tests/skill-proposal-card.test.ts +103 -0
- package/telegram-plugin/tests/slot-banner-driver.e2e.test.ts +36 -24
- package/telegram-plugin/tests/slot-banner.test.ts +9 -6
- package/telegram-plugin/tests/status-accent.test.ts +29 -32
- package/telegram-plugin/tests/{stream-controller-html-fallback.test.ts → stream-controller-parse-fallback.test.ts} +40 -42
- package/telegram-plugin/tests/stream-controller.test.ts +63 -52
- package/telegram-plugin/tests/stream-reply-error-paths.test.ts +43 -38
- package/telegram-plugin/tests/stream-reply-handler.test.ts +122 -249
- package/telegram-plugin/tests/streaming-e2e.test.ts +35 -30
- package/telegram-plugin/tests/streaming-orchestration.test.ts +29 -28
- package/telegram-plugin/tests/telegram-format.test.ts +120 -1083
- package/telegram-plugin/tests/tool-activity-summary.test.ts +144 -145
- package/telegram-plugin/tests/welcome-text.test.ts +72 -65
- package/telegram-plugin/tests/worker-activity-feed.test.ts +119 -137
- package/telegram-plugin/text-voice-scrub.ts +8 -11
- package/telegram-plugin/tool-activity-summary.ts +29 -29
- package/telegram-plugin/welcome-text.ts +82 -83
- package/telegram-plugin/worker-activity-feed.ts +2 -3
- package/telegram-plugin/html-sanitize.ts +0 -244
- package/telegram-plugin/tests/html-sanitize.test.ts +0 -146
- package/telegram-plugin/tests/parse-mode-rotation.test.ts +0 -162
package/dist/cli/switchroom.js
CHANGED
|
@@ -51218,7 +51218,7 @@ __export(exports_server2, {
|
|
|
51218
51218
|
TOOLS: () => TOOLS2
|
|
51219
51219
|
});
|
|
51220
51220
|
import { randomBytes as randomBytes15 } from "node:crypto";
|
|
51221
|
-
import { existsSync as existsSync86, readFileSync as
|
|
51221
|
+
import { existsSync as existsSync86, readFileSync as readFileSync75 } from "node:fs";
|
|
51222
51222
|
function selfSocketPath() {
|
|
51223
51223
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
51224
51224
|
}
|
|
@@ -51422,7 +51422,7 @@ function getLastUpdateApplyStatus() {
|
|
|
51422
51422
|
}
|
|
51423
51423
|
let raw;
|
|
51424
51424
|
try {
|
|
51425
|
-
raw =
|
|
51425
|
+
raw = readFileSync75(path8, "utf-8");
|
|
51426
51426
|
} catch (err2) {
|
|
51427
51427
|
return errorText2(`get_status: failed to read audit log at ${path8}: ${err2.message}`);
|
|
51428
51428
|
}
|
|
@@ -51692,8 +51692,8 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
51692
51692
|
import { dirname, join } from "node:path";
|
|
51693
51693
|
|
|
51694
51694
|
// src/build-info.ts
|
|
51695
|
-
var VERSION = "0.16.
|
|
51696
|
-
var COMMIT_SHA = "
|
|
51695
|
+
var VERSION = "0.16.23";
|
|
51696
|
+
var COMMIT_SHA = "3c17304c";
|
|
51697
51697
|
|
|
51698
51698
|
// src/cli/resolve-version.ts
|
|
51699
51699
|
function readPackageVersion() {
|
|
@@ -75714,7 +75714,12 @@ function openTurnsDb(agentDir) {
|
|
|
75714
75714
|
const db = new Database2(path4, { create: true });
|
|
75715
75715
|
applySchema(db);
|
|
75716
75716
|
try {
|
|
75717
|
-
chmodSync8(path4,
|
|
75717
|
+
chmodSync8(path4, 420);
|
|
75718
|
+
for (const suffix of ["-shm", "-wal"]) {
|
|
75719
|
+
try {
|
|
75720
|
+
chmodSync8(path4 + suffix, 420);
|
|
75721
|
+
} catch {}
|
|
75722
|
+
}
|
|
75718
75723
|
} catch {}
|
|
75719
75724
|
return db;
|
|
75720
75725
|
}
|
|
@@ -75737,8 +75742,34 @@ function mapRow(row) {
|
|
|
75737
75742
|
updated_at: row.updated_at
|
|
75738
75743
|
};
|
|
75739
75744
|
}
|
|
75745
|
+
function listDistinctThreadIds(db, chatId) {
|
|
75746
|
+
const rows = db.prepare(`
|
|
75747
|
+
SELECT DISTINCT thread_id FROM turns
|
|
75748
|
+
WHERE chat_id = ?
|
|
75749
|
+
ORDER BY thread_id ASC
|
|
75750
|
+
`).all(chatId);
|
|
75751
|
+
return rows.map((r) => r.thread_id);
|
|
75752
|
+
}
|
|
75740
75753
|
function listTurnsForAgent(db, opts = {}) {
|
|
75741
75754
|
const limit = Math.min(Math.max(1, opts.limit ?? 20), 200);
|
|
75755
|
+
if (opts.chatId && "threadId" in opts) {
|
|
75756
|
+
const rows2 = db.prepare(`
|
|
75757
|
+
SELECT * FROM turns
|
|
75758
|
+
WHERE chat_id = ? AND thread_id IS ?
|
|
75759
|
+
ORDER BY started_at DESC
|
|
75760
|
+
LIMIT ?
|
|
75761
|
+
`).all(opts.chatId, opts.threadId ?? null, limit);
|
|
75762
|
+
return rows2.map(mapRow);
|
|
75763
|
+
}
|
|
75764
|
+
if (opts.chatId) {
|
|
75765
|
+
const rows2 = db.prepare(`
|
|
75766
|
+
SELECT * FROM turns
|
|
75767
|
+
WHERE chat_id = ?
|
|
75768
|
+
ORDER BY started_at DESC
|
|
75769
|
+
LIMIT ?
|
|
75770
|
+
`).all(opts.chatId, limit);
|
|
75771
|
+
return rows2.map(mapRow);
|
|
75772
|
+
}
|
|
75742
75773
|
const rows = db.prepare(`
|
|
75743
75774
|
SELECT * FROM turns
|
|
75744
75775
|
ORDER BY started_at DESC
|
|
@@ -75985,13 +76016,24 @@ async function handleGetLogs(name, lines = 50, deps = {}) {
|
|
|
75985
76016
|
const fetchLogs = deps.fetchLogs ?? fetchAgentLogsViaHostd;
|
|
75986
76017
|
return await fetchLogs(name, lines);
|
|
75987
76018
|
}
|
|
75988
|
-
function
|
|
76019
|
+
function parseHermesSessionId(sessionId) {
|
|
76020
|
+
const tilde = sessionId.indexOf("~");
|
|
76021
|
+
if (tilde === -1)
|
|
76022
|
+
return { agentName: sessionId };
|
|
76023
|
+
return { agentName: sessionId.slice(0, tilde), threadId: sessionId.slice(tilde + 1) || null };
|
|
76024
|
+
}
|
|
76025
|
+
function handleGetTurns(config, sessionId, limit) {
|
|
75989
76026
|
try {
|
|
76027
|
+
const parsed = parseHermesSessionId(sessionId);
|
|
76028
|
+
const agentName = parsed.agentName;
|
|
76029
|
+
const hasThread = "threadId" in parsed;
|
|
75990
76030
|
const agentsDir = resolveAgentsDir(config);
|
|
75991
76031
|
const agentDir = resolve28(agentsDir, agentName);
|
|
76032
|
+
const chatId = config.agents?.[agentName]?.channels?.telegram?.chat_id ?? undefined;
|
|
75992
76033
|
const db = openTurnsDb(agentDir);
|
|
75993
76034
|
try {
|
|
75994
|
-
const
|
|
76035
|
+
const opts = chatId && hasThread ? { limit, chatId, threadId: parsed.threadId } : chatId ? { limit, chatId } : { limit };
|
|
76036
|
+
const turns = listTurnsForAgent(db, opts);
|
|
75995
76037
|
return { ok: true, turns };
|
|
75996
76038
|
} finally {
|
|
75997
76039
|
db.close();
|
|
@@ -76003,6 +76045,22 @@ function handleGetTurns(config, agentName, limit) {
|
|
|
76003
76045
|
};
|
|
76004
76046
|
}
|
|
76005
76047
|
}
|
|
76048
|
+
function handleListThreadIds(config, agentName) {
|
|
76049
|
+
try {
|
|
76050
|
+
const chatId = config.agents?.[agentName]?.channels?.telegram?.chat_id;
|
|
76051
|
+
if (!chatId)
|
|
76052
|
+
return [];
|
|
76053
|
+
const agentsDir = resolveAgentsDir(config);
|
|
76054
|
+
const db = openTurnsDb(resolve28(agentsDir, agentName));
|
|
76055
|
+
try {
|
|
76056
|
+
return listDistinctThreadIds(db, chatId);
|
|
76057
|
+
} finally {
|
|
76058
|
+
db.close();
|
|
76059
|
+
}
|
|
76060
|
+
} catch {
|
|
76061
|
+
return [];
|
|
76062
|
+
}
|
|
76063
|
+
}
|
|
76006
76064
|
function handleGetSubagents(config, agentName, status) {
|
|
76007
76065
|
try {
|
|
76008
76066
|
const agentsDir = resolveAgentsDir(config);
|
|
@@ -77519,12 +77577,28 @@ function agentLiveness(config, agentName) {
|
|
|
77519
77577
|
return "idle";
|
|
77520
77578
|
return "offline";
|
|
77521
77579
|
}
|
|
77522
|
-
function
|
|
77580
|
+
function sessionTitle(config, sessionId) {
|
|
77581
|
+
const { agentName, threadId } = parseHermesSessionId(sessionId);
|
|
77582
|
+
if (threadId === undefined)
|
|
77583
|
+
return agentName;
|
|
77584
|
+
const aliases = config.agents?.[agentName]?.channels?.telegram?.topic_aliases ?? {};
|
|
77585
|
+
const byId = Object.fromEntries(Object.entries(aliases).map(([name2, id]) => [String(id), name2]));
|
|
77586
|
+
if (threadId === null) {
|
|
77587
|
+
return `${agentName} \u00b7 ${byId["1"] ?? "General"}`;
|
|
77588
|
+
}
|
|
77589
|
+
const name = byId[threadId];
|
|
77590
|
+
return name ? `${agentName} \u00b7 ${name}` : `${agentName} \u00b7 ${threadId}`;
|
|
77591
|
+
}
|
|
77592
|
+
function isKnownSession(config, sessionId) {
|
|
77593
|
+
const { agentName } = parseHermesSessionId(sessionId);
|
|
77594
|
+
return !!config.agents?.[agentName];
|
|
77595
|
+
}
|
|
77596
|
+
function toHermesSession(sessionId, agent, liveness, config) {
|
|
77523
77597
|
const nowSec = Math.floor(Date.now() / 1000);
|
|
77524
77598
|
return {
|
|
77525
|
-
id:
|
|
77526
|
-
name:
|
|
77527
|
-
title:
|
|
77599
|
+
id: sessionId,
|
|
77600
|
+
name: sessionId,
|
|
77601
|
+
title: sessionTitle(config, sessionId),
|
|
77528
77602
|
status: liveness,
|
|
77529
77603
|
model: "claude",
|
|
77530
77604
|
is_active: liveness === "active",
|
|
@@ -77542,10 +77616,35 @@ function toHermesSession(agent, liveness) {
|
|
|
77542
77616
|
quota: agent.primaryAccount ? { used_pct: null, slot: agent.primaryAccount } : null
|
|
77543
77617
|
};
|
|
77544
77618
|
}
|
|
77545
|
-
function
|
|
77619
|
+
async function buildAllSessions(config) {
|
|
77620
|
+
const agents = await handleGetAgents(config);
|
|
77621
|
+
const sessions = [];
|
|
77622
|
+
for (const agent of agents) {
|
|
77623
|
+
const liveness = agentLiveness(config, agent.name);
|
|
77624
|
+
const chatId = config.agents?.[agent.name]?.channels?.telegram?.chat_id;
|
|
77625
|
+
if (chatId) {
|
|
77626
|
+
const threadIds = handleListThreadIds(config, agent.name);
|
|
77627
|
+
if (threadIds.length === 0) {
|
|
77628
|
+
sessions.push(toHermesSession(agent.name, agent, liveness, config));
|
|
77629
|
+
} else {
|
|
77630
|
+
for (const threadId of threadIds) {
|
|
77631
|
+
const sessionId = threadId === null ? agent.name : `${agent.name}~${threadId}`;
|
|
77632
|
+
sessions.push(toHermesSession(sessionId, agent, liveness, config));
|
|
77633
|
+
}
|
|
77634
|
+
}
|
|
77635
|
+
} else {
|
|
77636
|
+
sessions.push(toHermesSession(agent.name, agent, liveness, config));
|
|
77637
|
+
}
|
|
77638
|
+
}
|
|
77639
|
+
return sessions;
|
|
77640
|
+
}
|
|
77641
|
+
function resolveAgentChat(config, sessionId, agentsDir) {
|
|
77642
|
+
const { agentName, threadId: topicId } = parseHermesSessionId(sessionId);
|
|
77546
77643
|
const channel = resolveChannelTarget(config, agentName);
|
|
77547
|
-
if (channel)
|
|
77548
|
-
|
|
77644
|
+
if (channel) {
|
|
77645
|
+
const threadId = topicId !== undefined ? topicId === null ? undefined : parseInt(topicId, 10) || undefined : channel.threadId;
|
|
77646
|
+
return { chatId: channel.chatId, threadId };
|
|
77647
|
+
}
|
|
77549
77648
|
const accessPath = resolve29(agentsDir, agentName, "telegram", "access.json");
|
|
77550
77649
|
if (!existsSync51(accessPath))
|
|
77551
77650
|
return null;
|
|
@@ -77637,15 +77736,26 @@ function cronJobRuns(schedule, jobId) {
|
|
|
77637
77736
|
const agent = jobId.slice(0, tilde);
|
|
77638
77737
|
const index = parseInt(jobId.slice(tilde + 1), 10);
|
|
77639
77738
|
const fires = schedule.recentByAgent[agent] ?? [];
|
|
77640
|
-
return fires.filter((f) => f.scheduleIndex === index).map((f) =>
|
|
77641
|
-
|
|
77642
|
-
|
|
77643
|
-
|
|
77644
|
-
|
|
77645
|
-
|
|
77646
|
-
|
|
77647
|
-
|
|
77648
|
-
|
|
77739
|
+
return fires.filter((f) => f.scheduleIndex === index).map((f) => {
|
|
77740
|
+
const d = new Date(f.startedAt);
|
|
77741
|
+
const name = d.toLocaleString("en-US", {
|
|
77742
|
+
month: "short",
|
|
77743
|
+
day: "numeric",
|
|
77744
|
+
hour: "2-digit",
|
|
77745
|
+
minute: "2-digit",
|
|
77746
|
+
hour12: false
|
|
77747
|
+
});
|
|
77748
|
+
return {
|
|
77749
|
+
id: `${jobId}/${f.startedAt}`,
|
|
77750
|
+
job_id: jobId,
|
|
77751
|
+
name,
|
|
77752
|
+
started_at: new Date(f.startedAt).toISOString(),
|
|
77753
|
+
finished_at: new Date(f.finishedAt).toISOString(),
|
|
77754
|
+
exit_code: f.exitCode,
|
|
77755
|
+
output: f.outputSummary,
|
|
77756
|
+
status: f.exitCode === 0 ? "success" : "error"
|
|
77757
|
+
};
|
|
77758
|
+
});
|
|
77649
77759
|
}
|
|
77650
77760
|
function turnsToMessages(turns) {
|
|
77651
77761
|
const messages = [];
|
|
@@ -77730,8 +77840,7 @@ function switchroomCommandsCatalog() {
|
|
|
77730
77840
|
}
|
|
77731
77841
|
async function handleHermesRest(method, pathname, config, search = "") {
|
|
77732
77842
|
if (method === "GET" && (pathname === "/api/sessions" || pathname === "/api/profiles/sessions")) {
|
|
77733
|
-
const
|
|
77734
|
-
const sessions = agents.map((a) => toHermesSession(a, agentLiveness(config, a.name)));
|
|
77843
|
+
const sessions = await buildAllSessions(config);
|
|
77735
77844
|
return {
|
|
77736
77845
|
status: 200,
|
|
77737
77846
|
body: {
|
|
@@ -77746,22 +77855,21 @@ async function handleHermesRest(method, pathname, config, search = "") {
|
|
|
77746
77855
|
const sessionMatch = pathname.match(/^\/api\/sessions\/([^/]+)$/);
|
|
77747
77856
|
if (method === "GET" && sessionMatch) {
|
|
77748
77857
|
const id = decodeURIComponent(sessionMatch[1]);
|
|
77749
|
-
if (!config
|
|
77858
|
+
if (!isKnownSession(config, id))
|
|
77750
77859
|
return { status: 404, body: { error: "Unknown session" } };
|
|
77860
|
+
const { agentName } = parseHermesSessionId(id);
|
|
77751
77861
|
const agents = await handleGetAgents(config);
|
|
77752
|
-
const agent = agents.find((a) => a.name ===
|
|
77862
|
+
const agent = agents.find((a) => a.name === agentName);
|
|
77753
77863
|
if (!agent)
|
|
77754
77864
|
return { status: 404, body: { error: "Unknown session" } };
|
|
77755
|
-
return { status: 200, body: { session: toHermesSession(agent, agentLiveness(config,
|
|
77865
|
+
return { status: 200, body: { session: toHermesSession(id, agent, agentLiveness(config, agentName), config) } };
|
|
77756
77866
|
}
|
|
77757
77867
|
const messagesMatch = pathname.match(/^\/api\/sessions\/([^/]+)\/messages$/);
|
|
77758
77868
|
if (method === "GET" && messagesMatch) {
|
|
77759
77869
|
const id = decodeURIComponent(messagesMatch[1]);
|
|
77760
|
-
if (!config
|
|
77870
|
+
if (!isKnownSession(config, id))
|
|
77761
77871
|
return { status: 404, body: { error: "Unknown session" } };
|
|
77762
77872
|
const result = handleGetTurns(config, id, 100);
|
|
77763
|
-
if (!result.ok)
|
|
77764
|
-
return { status: 500, body: { error: result.error } };
|
|
77765
77873
|
const messages = turnsToMessages(result.turns ?? []);
|
|
77766
77874
|
return { status: 200, body: { session_id: id, messages } };
|
|
77767
77875
|
}
|
|
@@ -77929,52 +78037,48 @@ async function onHermesMessage(ctx, raw) {
|
|
|
77929
78037
|
const config = ctx.config;
|
|
77930
78038
|
switch (req.method) {
|
|
77931
78039
|
case "session.list": {
|
|
77932
|
-
const
|
|
77933
|
-
const sessions = agents.map((a) => toHermesSession(a, agentLiveness(config, a.name)));
|
|
78040
|
+
const sessions = await buildAllSessions(config);
|
|
77934
78041
|
sendResponse(ctx, rpcOk(id, { sessions }));
|
|
77935
78042
|
break;
|
|
77936
78043
|
}
|
|
77937
78044
|
case "session.most_recent": {
|
|
77938
|
-
const
|
|
77939
|
-
|
|
77940
|
-
sendResponse(ctx, rpcOk(id, { session: null }));
|
|
77941
|
-
break;
|
|
77942
|
-
}
|
|
77943
|
-
const a = agents[0];
|
|
77944
|
-
sendResponse(ctx, rpcOk(id, { session: toHermesSession(a, agentLiveness(config, a.name)) }));
|
|
78045
|
+
const sessions = await buildAllSessions(config);
|
|
78046
|
+
sendResponse(ctx, rpcOk(id, { session: sessions[0] ?? null }));
|
|
77945
78047
|
break;
|
|
77946
78048
|
}
|
|
77947
78049
|
case "session.status": {
|
|
77948
78050
|
const sessionId = String(params.session_id ?? "");
|
|
77949
|
-
if (!config
|
|
78051
|
+
if (!isKnownSession(config, sessionId)) {
|
|
77950
78052
|
sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
|
|
77951
78053
|
break;
|
|
77952
78054
|
}
|
|
78055
|
+
const { agentName } = parseHermesSessionId(sessionId);
|
|
77953
78056
|
const agents = await handleGetAgents(config);
|
|
77954
|
-
const agent = agents.find((a) => a.name ===
|
|
78057
|
+
const agent = agents.find((a) => a.name === agentName);
|
|
77955
78058
|
if (!agent) {
|
|
77956
78059
|
sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
|
|
77957
78060
|
break;
|
|
77958
78061
|
}
|
|
77959
|
-
sendResponse(ctx, rpcOk(id, { session: toHermesSession(agent, agentLiveness(config,
|
|
78062
|
+
sendResponse(ctx, rpcOk(id, { session: toHermesSession(sessionId, agent, agentLiveness(config, agentName), config) }));
|
|
77960
78063
|
break;
|
|
77961
78064
|
}
|
|
77962
78065
|
case "session.create":
|
|
77963
78066
|
case "session.resume":
|
|
77964
78067
|
case "session.activate": {
|
|
77965
78068
|
const sessionId = String(params.session_id ?? params.name ?? "");
|
|
77966
|
-
if (!config
|
|
78069
|
+
if (!isKnownSession(config, sessionId)) {
|
|
77967
78070
|
sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
|
|
77968
78071
|
break;
|
|
77969
78072
|
}
|
|
77970
78073
|
ctx.activeSessionId = sessionId;
|
|
78074
|
+
const { agentName } = parseHermesSessionId(sessionId);
|
|
77971
78075
|
const agents = await handleGetAgents(config);
|
|
77972
|
-
const agent = agents.find((a) => a.name ===
|
|
78076
|
+
const agent = agents.find((a) => a.name === agentName);
|
|
77973
78077
|
if (!agent) {
|
|
77974
78078
|
sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
|
|
77975
78079
|
break;
|
|
77976
78080
|
}
|
|
77977
|
-
const session = toHermesSession(agent, agentLiveness(config,
|
|
78081
|
+
const session = toHermesSession(sessionId, agent, agentLiveness(config, agentName), config);
|
|
77978
78082
|
sendResponse(ctx, rpcOk(id, { session }));
|
|
77979
78083
|
sendEvent(ctx, "session.info", sessionId, session);
|
|
77980
78084
|
break;
|
|
@@ -77986,27 +78090,24 @@ async function onHermesMessage(ctx, raw) {
|
|
|
77986
78090
|
}
|
|
77987
78091
|
case "session.history": {
|
|
77988
78092
|
const sessionId = String(params.session_id ?? ctx.activeSessionId ?? "");
|
|
77989
|
-
if (!config
|
|
78093
|
+
if (!isKnownSession(config, sessionId)) {
|
|
77990
78094
|
sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
|
|
77991
78095
|
break;
|
|
77992
78096
|
}
|
|
77993
78097
|
const limit = typeof params.limit === "number" ? params.limit : 50;
|
|
77994
78098
|
const result = handleGetTurns(config, sessionId, Math.min(limit, 200));
|
|
77995
|
-
if (!result.ok) {
|
|
77996
|
-
sendResponse(ctx, rpcErr(id, -32603, result.error ?? "Failed to read history"));
|
|
77997
|
-
break;
|
|
77998
|
-
}
|
|
77999
78099
|
sendResponse(ctx, rpcOk(id, { session_id: sessionId, messages: turnsToMessages(result.turns ?? []) }));
|
|
78000
78100
|
break;
|
|
78001
78101
|
}
|
|
78002
78102
|
case "session.usage": {
|
|
78003
78103
|
const sessionId = String(params.session_id ?? ctx.activeSessionId ?? "");
|
|
78004
|
-
if (!config
|
|
78104
|
+
if (!isKnownSession(config, sessionId)) {
|
|
78005
78105
|
sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
|
|
78006
78106
|
break;
|
|
78007
78107
|
}
|
|
78108
|
+
const { agentName: usageAgentName } = parseHermesSessionId(sessionId);
|
|
78008
78109
|
const agents = await handleGetAgents(config);
|
|
78009
|
-
const agent = agents.find((a) => a.name ===
|
|
78110
|
+
const agent = agents.find((a) => a.name === usageAgentName);
|
|
78010
78111
|
sendResponse(ctx, rpcOk(id, {
|
|
78011
78112
|
session_id: sessionId,
|
|
78012
78113
|
quota: agent?.primaryAccount ? { slot: agent.primaryAccount, state: agent.auth.subscriptionType ?? null } : null
|
|
@@ -78017,7 +78118,7 @@ async function onHermesMessage(ctx, raw) {
|
|
|
78017
78118
|
case "prompt.background": {
|
|
78018
78119
|
const sessionId = String(params.session_id ?? ctx.activeSessionId ?? "");
|
|
78019
78120
|
const content = String(params.content ?? params.text ?? "");
|
|
78020
|
-
if (!sessionId || !config
|
|
78121
|
+
if (!sessionId || !isKnownSession(config, sessionId)) {
|
|
78021
78122
|
sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
|
|
78022
78123
|
break;
|
|
78023
78124
|
}
|
|
@@ -78025,6 +78126,7 @@ async function onHermesMessage(ctx, raw) {
|
|
|
78025
78126
|
sendResponse(ctx, rpcErr(id, -32602, "content is required"));
|
|
78026
78127
|
break;
|
|
78027
78128
|
}
|
|
78129
|
+
const { agentName: submitAgent } = parseHermesSessionId(sessionId);
|
|
78028
78130
|
const agentsDir = resolveAgentsDir(config);
|
|
78029
78131
|
const chat = resolveAgentChat(config, sessionId, agentsDir);
|
|
78030
78132
|
if (!chat) {
|
|
@@ -78033,7 +78135,7 @@ async function onHermesMessage(ctx, raw) {
|
|
|
78033
78135
|
}
|
|
78034
78136
|
const promptKey = createHash10("sha256").update(`${sessionId}:${Date.now()}:${content}`).digest("hex").slice(0, 12);
|
|
78035
78137
|
sendEvent(ctx, "message.start", sessionId, { prompt_key: promptKey });
|
|
78036
|
-
const result = await injectInbound(agentsDir,
|
|
78138
|
+
const result = await injectInbound(agentsDir, submitAgent, chat.chatId, chat.threadId, content, promptKey);
|
|
78037
78139
|
if (!result.ok) {
|
|
78038
78140
|
sendEvent(ctx, "error", sessionId, { message: result.error, prompt_key: promptKey });
|
|
78039
78141
|
sendResponse(ctx, rpcErr(id, -32603, result.error ?? "inject failed"));
|
|
@@ -78052,10 +78154,11 @@ async function onHermesMessage(ctx, raw) {
|
|
|
78052
78154
|
}
|
|
78053
78155
|
case "session.interrupt": {
|
|
78054
78156
|
const sessionId = String(params.session_id ?? ctx.activeSessionId ?? "");
|
|
78055
|
-
if (!sessionId || !config
|
|
78157
|
+
if (!sessionId || !isKnownSession(config, sessionId)) {
|
|
78056
78158
|
sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
|
|
78057
78159
|
break;
|
|
78058
78160
|
}
|
|
78161
|
+
const { agentName: intAgent } = parseHermesSessionId(sessionId);
|
|
78059
78162
|
const agentsDir = resolveAgentsDir(config);
|
|
78060
78163
|
const chat = resolveAgentChat(config, sessionId, agentsDir);
|
|
78061
78164
|
if (!chat) {
|
|
@@ -78063,7 +78166,7 @@ async function onHermesMessage(ctx, raw) {
|
|
|
78063
78166
|
break;
|
|
78064
78167
|
}
|
|
78065
78168
|
const promptKey = `interrupt-${Date.now()}`;
|
|
78066
|
-
const intResult = await injectInbound(agentsDir,
|
|
78169
|
+
const intResult = await injectInbound(agentsDir, intAgent, chat.chatId, chat.threadId, "! ", promptKey);
|
|
78067
78170
|
if (!intResult.ok) {
|
|
78068
78171
|
sendResponse(ctx, rpcErr(id, -32603, intResult.error ?? "interrupt inject failed"));
|
|
78069
78172
|
break;
|
|
@@ -78091,14 +78194,15 @@ async function onHermesMessage(ctx, raw) {
|
|
|
78091
78194
|
const sessionId = String(params.session_id ?? ctx.activeSessionId ?? "");
|
|
78092
78195
|
const key = String(params.key ?? "");
|
|
78093
78196
|
const value = String(params.value ?? "");
|
|
78094
|
-
if (key === "model" && sessionId && config
|
|
78197
|
+
if (key === "model" && sessionId && isKnownSession(config, sessionId)) {
|
|
78095
78198
|
const modelName = value.replace(/\s+--provider\s+\S+$/, "").trim();
|
|
78096
78199
|
if (modelName) {
|
|
78097
78200
|
const agentsDir = resolveAgentsDir(config);
|
|
78098
78201
|
const chat = resolveAgentChat(config, sessionId, agentsDir);
|
|
78099
78202
|
if (chat) {
|
|
78100
78203
|
const promptKey = `model-switch-${Date.now()}`;
|
|
78101
|
-
|
|
78204
|
+
const { agentName: modelAgent } = parseHermesSessionId(sessionId);
|
|
78205
|
+
injectInbound(agentsDir, modelAgent, chat.chatId, chat.threadId, `/model ${modelName}`, promptKey);
|
|
78102
78206
|
}
|
|
78103
78207
|
}
|
|
78104
78208
|
}
|
|
@@ -78113,7 +78217,7 @@ async function onHermesMessage(ctx, raw) {
|
|
|
78113
78217
|
const sessionId = String(params.session_id ?? ctx.activeSessionId ?? "");
|
|
78114
78218
|
const bare = String(params.command ?? "").replace(/^\/+/, "");
|
|
78115
78219
|
const arg = params.arg != null ? String(params.arg) : "";
|
|
78116
|
-
if (!sessionId || !config
|
|
78220
|
+
if (!sessionId || !isKnownSession(config, sessionId)) {
|
|
78117
78221
|
sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
|
|
78118
78222
|
break;
|
|
78119
78223
|
}
|
|
@@ -78121,6 +78225,7 @@ async function onHermesMessage(ctx, raw) {
|
|
|
78121
78225
|
sendResponse(ctx, rpcErr(id, -32602, "command is required"));
|
|
78122
78226
|
break;
|
|
78123
78227
|
}
|
|
78228
|
+
const { agentName: slashAgent } = parseHermesSessionId(sessionId);
|
|
78124
78229
|
const fullCommand = arg ? `/${bare} ${arg}` : `/${bare}`;
|
|
78125
78230
|
const verb = `/${bare}`;
|
|
78126
78231
|
if (INJECT_BLOCKLIST.has(verb)) {
|
|
@@ -78130,7 +78235,7 @@ async function onHermesMessage(ctx, raw) {
|
|
|
78130
78235
|
if (INJECT_ALLOWLIST.has(verb)) {
|
|
78131
78236
|
let injectResult;
|
|
78132
78237
|
try {
|
|
78133
|
-
injectResult = await injectSlashCommand(
|
|
78238
|
+
injectResult = await injectSlashCommand(slashAgent, fullCommand);
|
|
78134
78239
|
} catch (err) {
|
|
78135
78240
|
const msg = err instanceof Error ? err.message : String(err);
|
|
78136
78241
|
sendResponse(ctx, rpcErr(id, -32603, msg));
|
|
@@ -78147,7 +78252,7 @@ async function onHermesMessage(ctx, raw) {
|
|
|
78147
78252
|
break;
|
|
78148
78253
|
}
|
|
78149
78254
|
const promptKey = `slash-${bare}-${Date.now()}`;
|
|
78150
|
-
const result = await injectInbound(agentsDir,
|
|
78255
|
+
const result = await injectInbound(agentsDir, slashAgent, chat.chatId, chat.threadId, fullCommand, promptKey);
|
|
78151
78256
|
if (!result.ok) {
|
|
78152
78257
|
sendResponse(ctx, rpcErr(id, -32603, result.error ?? "inject failed"));
|
|
78153
78258
|
break;
|
|
@@ -89080,6 +89185,7 @@ import { spawnSync as spawnSync14 } from "node:child_process";
|
|
|
89080
89185
|
|
|
89081
89186
|
// src/cli/skill-common.ts
|
|
89082
89187
|
var import_yaml24 = __toESM(require_dist(), 1);
|
|
89188
|
+
var MAX_SKILLS_PER_AGENT2 = 20;
|
|
89083
89189
|
var MAX_FILE_BYTES = 256 * 1024;
|
|
89084
89190
|
var MAX_SKILL_BYTES = 2 * 1024 * 1024;
|
|
89085
89191
|
var MAX_FILES_PER_SKILL = 50;
|
|
@@ -89252,6 +89358,45 @@ function validateSkillBundle(name, files) {
|
|
|
89252
89358
|
}
|
|
89253
89359
|
return { ok: errors2.length === 0, errors: errors2 };
|
|
89254
89360
|
}
|
|
89361
|
+
var SECRET_RULE_LABELS = {
|
|
89362
|
+
anthropic_api_key: "Anthropic API key (sk-ant-\u2026)",
|
|
89363
|
+
anthropic_oauth_code: "Anthropic OAuth code",
|
|
89364
|
+
openai_api_key: "API key (sk-\u2026)",
|
|
89365
|
+
github_pat_classic: "GitHub personal access token (ghp_\u2026)",
|
|
89366
|
+
github_pat_fine_grained: "GitHub fine-grained PAT (github_pat_\u2026)",
|
|
89367
|
+
slack_token: "Slack token (xox\u2026)",
|
|
89368
|
+
slack_app_token: "Slack app token (xapp-\u2026)",
|
|
89369
|
+
google_api_key: "Google API key (AIza\u2026)",
|
|
89370
|
+
aws_access_key: "AWS access key (AKIA\u2026)",
|
|
89371
|
+
aws_temp_access_key: "AWS temporary access key (ASIA\u2026)",
|
|
89372
|
+
jwt: "JWT",
|
|
89373
|
+
pem_private_key: "PEM private-key block",
|
|
89374
|
+
env_key_value: "KEY=value secret assignment",
|
|
89375
|
+
json_secret_field: "JSON secret field",
|
|
89376
|
+
bearer_auth_header: "Authorization: Bearer token",
|
|
89377
|
+
bearer_loose: "Bearer token",
|
|
89378
|
+
cli_flag: "secret CLI flag",
|
|
89379
|
+
laravel_sanctum_token: "personal access token (id|token)",
|
|
89380
|
+
telegram_bot_token: "Telegram bot token",
|
|
89381
|
+
telegram_bot_token_prefixed: "Telegram bot token",
|
|
89382
|
+
generic_high_entropy: "high-entropy string (likely a credential)"
|
|
89383
|
+
};
|
|
89384
|
+
function scanBundleForSecrets(files) {
|
|
89385
|
+
const findings = [];
|
|
89386
|
+
for (const [file, content] of Object.entries(files)) {
|
|
89387
|
+
if (typeof content !== "string" || content.length === 0)
|
|
89388
|
+
continue;
|
|
89389
|
+
for (const d of detectSecrets(content)) {
|
|
89390
|
+
findings.push({
|
|
89391
|
+
file,
|
|
89392
|
+
pattern: SECRET_RULE_LABELS[d.rule_id] ?? d.rule_id,
|
|
89393
|
+
rule_id: d.rule_id,
|
|
89394
|
+
offset: d.start
|
|
89395
|
+
});
|
|
89396
|
+
}
|
|
89397
|
+
}
|
|
89398
|
+
return findings;
|
|
89399
|
+
}
|
|
89255
89400
|
|
|
89256
89401
|
// src/cli/skill.ts
|
|
89257
89402
|
init_loader();
|
|
@@ -89741,6 +89886,17 @@ function personalSkillDir(agentsRoot, agent, name) {
|
|
|
89741
89886
|
function trashDir(agentsRoot, agent) {
|
|
89742
89887
|
return join85(agentsRoot, agent, ".claude", TRASH_DIRNAME);
|
|
89743
89888
|
}
|
|
89889
|
+
function countPersonalSkills(agentsRoot, agent) {
|
|
89890
|
+
const skillsDir = join85(agentsRoot, agent, ".claude", "skills");
|
|
89891
|
+
if (!existsSync84(skillsDir))
|
|
89892
|
+
return 0;
|
|
89893
|
+
let n = 0;
|
|
89894
|
+
for (const ent of readdirSync32(skillsDir, { withFileTypes: true })) {
|
|
89895
|
+
if (ent.isDirectory() && ent.name.startsWith(PERSONAL_PREFIX))
|
|
89896
|
+
n += 1;
|
|
89897
|
+
}
|
|
89898
|
+
return n;
|
|
89899
|
+
}
|
|
89744
89900
|
function readStdinSync2() {
|
|
89745
89901
|
const chunks = [];
|
|
89746
89902
|
const buf = Buffer.alloc(64 * 1024);
|
|
@@ -89934,6 +90090,12 @@ function loadValidateWrite(agentsRoot, agent, name, files, ensureNew) {
|
|
|
89934
90090
|
if (!ensureNew && !exists) {
|
|
89935
90091
|
fail4(`personal skill ${JSON.stringify(name)} does not exist for agent ${JSON.stringify(agent)}. ` + `Use \`skill init-personal\` to create it.`, 9);
|
|
89936
90092
|
}
|
|
90093
|
+
if (ensureNew) {
|
|
90094
|
+
const existingCount = countPersonalSkills(agentsRoot, agent);
|
|
90095
|
+
if (existingCount >= MAX_SKILLS_PER_AGENT2) {
|
|
90096
|
+
fail4(`agent ${JSON.stringify(agent)} already has ${existingCount} personal skills ` + `(cap is ${MAX_SKILLS_PER_AGENT2}). Remove one with \`skill remove-personal\` ` + `before adding a new skill, or edit an existing skill instead.`, 15);
|
|
90097
|
+
}
|
|
90098
|
+
}
|
|
89937
90099
|
const v = validateSkillBundle(name, files);
|
|
89938
90100
|
if (!v.ok) {
|
|
89939
90101
|
console.error(source_default.red("Validation failed:"));
|
|
@@ -89950,6 +90112,14 @@ function loadValidateWrite(agentsRoot, agent, name, files, ensureNew) {
|
|
|
89950
90112
|
}
|
|
89951
90113
|
process.exit(3);
|
|
89952
90114
|
}
|
|
90115
|
+
const secretFindings = scanBundleForSecrets(files);
|
|
90116
|
+
if (secretFindings.length > 0) {
|
|
90117
|
+
console.error(source_default.red("Secret scan failed \u2014 refusing to write skill:"));
|
|
90118
|
+
for (const f of secretFindings) {
|
|
90119
|
+
console.error(source_default.red(` - ${f.file}: ${f.pattern} detected (at byte offset ${f.offset}). ` + `Remove the secret from the skill content and use the vault instead.`));
|
|
90120
|
+
}
|
|
90121
|
+
process.exit(3);
|
|
90122
|
+
}
|
|
89953
90123
|
writePersonalSkill(target, files);
|
|
89954
90124
|
}
|
|
89955
90125
|
function loadFiles(opts) {
|
|
@@ -90220,31 +90390,106 @@ function registerSkillPersonalCommands(program3) {
|
|
|
90220
90390
|
}));
|
|
90221
90391
|
}
|
|
90222
90392
|
|
|
90393
|
+
// src/cli/self-improve-propose-skill.ts
|
|
90394
|
+
import { createConnection as createConnection4 } from "node:net";
|
|
90395
|
+
import { homedir as homedir50 } from "node:os";
|
|
90396
|
+
import { join as join86 } from "node:path";
|
|
90397
|
+
import { readFileSync as readFileSync73 } from "node:fs";
|
|
90398
|
+
var IPC_CONNECT_TIMEOUT_MS = 5000;
|
|
90399
|
+
function gatewaySocketPath() {
|
|
90400
|
+
return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join86(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join86(homedir50(), ".claude", "channels", "telegram", "gateway.sock"));
|
|
90401
|
+
}
|
|
90402
|
+
function fail5(msg, code = 1) {
|
|
90403
|
+
console.error(msg);
|
|
90404
|
+
process.exit(code);
|
|
90405
|
+
}
|
|
90406
|
+
function sendProposal(payload) {
|
|
90407
|
+
return new Promise((resolve52, reject) => {
|
|
90408
|
+
const sock = createConnection4({ path: gatewaySocketPath() });
|
|
90409
|
+
const t = setTimeout(() => {
|
|
90410
|
+
sock.destroy();
|
|
90411
|
+
reject(new Error("gateway IPC connect timeout"));
|
|
90412
|
+
}, IPC_CONNECT_TIMEOUT_MS);
|
|
90413
|
+
sock.on("connect", () => {
|
|
90414
|
+
clearTimeout(t);
|
|
90415
|
+
sock.write(JSON.stringify(payload) + `
|
|
90416
|
+
`, () => {
|
|
90417
|
+
sock.end();
|
|
90418
|
+
resolve52();
|
|
90419
|
+
});
|
|
90420
|
+
});
|
|
90421
|
+
sock.on("error", (err2) => {
|
|
90422
|
+
clearTimeout(t);
|
|
90423
|
+
reject(new Error(`gateway IPC error: ${err2.message}`));
|
|
90424
|
+
});
|
|
90425
|
+
});
|
|
90426
|
+
}
|
|
90427
|
+
function registerSelfImproveProposeSkillCommand(program3) {
|
|
90428
|
+
const parent = program3.commands.find((c) => c.name() === "self-improve") ?? program3.command("self-improve").description("Agent self-improvement ops");
|
|
90429
|
+
parent.command("propose-skill").description("Surface a drafted personal skill as a one-tap Telegram approval card").requiredOption("--slug <slug>", "target personal-skill slug").requiredOption("--lesson <text>", "one-line lesson the skill captures").option("--evidence <text>", "evidence line (e.g. 'seen across 3 sessions')", "").option("--edit", "this is an edit to an existing personal skill (not new)").requiredOption("--draft <path>", "path to a JSON file mapping skill-relative paths to file contents").requiredOption("--chat <id>", "agent's own chat id to post the card to").option("--thread <id>", "forum topic thread id").option("--agent <name>", "agent name (defaults to $SWITCHROOM_AGENT_NAME)").action((opts) => {
|
|
90430
|
+
const agent = opts.agent ?? process.env.SWITCHROOM_AGENT_NAME;
|
|
90431
|
+
if (!agent)
|
|
90432
|
+
fail5("agent name required (--agent or $SWITCHROOM_AGENT_NAME)");
|
|
90433
|
+
let draft;
|
|
90434
|
+
try {
|
|
90435
|
+
draft = JSON.parse(readFileSync73(opts.draft, "utf-8"));
|
|
90436
|
+
} catch (e) {
|
|
90437
|
+
fail5(`failed to read/parse --draft: ${e.message}`);
|
|
90438
|
+
}
|
|
90439
|
+
if (typeof draft !== "object" || draft === null || Array.isArray(draft)) {
|
|
90440
|
+
fail5("--draft must be a JSON object of { path: content }");
|
|
90441
|
+
}
|
|
90442
|
+
if (typeof draft["SKILL.md"] !== "string" || draft["SKILL.md"].length === 0) {
|
|
90443
|
+
fail5("--draft must contain a non-empty SKILL.md");
|
|
90444
|
+
}
|
|
90445
|
+
const payload = {
|
|
90446
|
+
type: "post_skill_proposal",
|
|
90447
|
+
agentName: agent,
|
|
90448
|
+
chatId: opts.chat,
|
|
90449
|
+
skillSlug: opts.slug,
|
|
90450
|
+
isNew: opts.edit !== true,
|
|
90451
|
+
lesson: opts.lesson,
|
|
90452
|
+
evidence: opts.evidence ?? "",
|
|
90453
|
+
draft
|
|
90454
|
+
};
|
|
90455
|
+
if (opts.thread != null) {
|
|
90456
|
+
const n = Number.parseInt(opts.thread, 10);
|
|
90457
|
+
if (Number.isInteger(n))
|
|
90458
|
+
payload.threadId = n;
|
|
90459
|
+
}
|
|
90460
|
+
sendProposal(payload).then(() => {
|
|
90461
|
+
console.log(JSON.stringify({ ok: true, action: "propose-skill", slug: opts.slug, agent }));
|
|
90462
|
+
}).catch((err2) => {
|
|
90463
|
+
fail5(`propose-skill failed: ${err2.message}`);
|
|
90464
|
+
});
|
|
90465
|
+
});
|
|
90466
|
+
}
|
|
90467
|
+
|
|
90223
90468
|
// src/cli/skill-search.ts
|
|
90224
90469
|
init_helpers();
|
|
90225
90470
|
var import_yaml25 = __toESM(require_dist(), 1);
|
|
90226
|
-
import { existsSync as existsSync85, readdirSync as readdirSync33, readFileSync as
|
|
90227
|
-
import { homedir as
|
|
90228
|
-
import { join as
|
|
90471
|
+
import { existsSync as existsSync85, readdirSync as readdirSync33, readFileSync as readFileSync74, statSync as statSync36 } from "node:fs";
|
|
90472
|
+
import { homedir as homedir51 } from "node:os";
|
|
90473
|
+
import { join as join87, resolve as resolve52 } from "node:path";
|
|
90229
90474
|
var PERSONAL_PREFIX2 = "personal-";
|
|
90230
90475
|
var BUNDLED_SUBDIR = "_bundled";
|
|
90231
90476
|
var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
90232
90477
|
function defaultAgentsRoot() {
|
|
90233
|
-
return resolve52(
|
|
90478
|
+
return resolve52(homedir51(), ".switchroom/agents");
|
|
90234
90479
|
}
|
|
90235
90480
|
function defaultSharedRoot2() {
|
|
90236
|
-
return resolve52(
|
|
90481
|
+
return resolve52(homedir51(), ".switchroom/skills");
|
|
90237
90482
|
}
|
|
90238
90483
|
function defaultBundledRoot2() {
|
|
90239
|
-
return resolve52(
|
|
90484
|
+
return resolve52(homedir51(), ".switchroom/skills/_bundled");
|
|
90240
90485
|
}
|
|
90241
90486
|
function readSkillFrontmatter(skillDir) {
|
|
90242
|
-
const mdPath =
|
|
90487
|
+
const mdPath = join87(skillDir, "SKILL.md");
|
|
90243
90488
|
if (!existsSync85(mdPath))
|
|
90244
90489
|
return null;
|
|
90245
90490
|
let content;
|
|
90246
90491
|
try {
|
|
90247
|
-
content =
|
|
90492
|
+
content = readFileSync74(mdPath, "utf-8");
|
|
90248
90493
|
} catch {
|
|
90249
90494
|
return null;
|
|
90250
90495
|
}
|
|
@@ -90272,7 +90517,7 @@ function readSkillFrontmatter(skillDir) {
|
|
|
90272
90517
|
return { fm: parsed };
|
|
90273
90518
|
}
|
|
90274
90519
|
function statSkillMd(skillDir) {
|
|
90275
|
-
const mdPath =
|
|
90520
|
+
const mdPath = join87(skillDir, "SKILL.md");
|
|
90276
90521
|
try {
|
|
90277
90522
|
const st = statSync36(mdPath);
|
|
90278
90523
|
return { size: st.size, mtime: st.mtime.toISOString() };
|
|
@@ -90283,7 +90528,7 @@ function statSkillMd(skillDir) {
|
|
|
90283
90528
|
function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
90284
90529
|
if (!AGENT_NAME_RE3.test(agent))
|
|
90285
90530
|
return [];
|
|
90286
|
-
const skillsDir =
|
|
90531
|
+
const skillsDir = join87(agentsRoot, agent, ".claude/skills");
|
|
90287
90532
|
if (!existsSync85(skillsDir))
|
|
90288
90533
|
return [];
|
|
90289
90534
|
const out = [];
|
|
@@ -90296,7 +90541,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
90296
90541
|
for (const ent of entries) {
|
|
90297
90542
|
if (!ent.startsWith(PERSONAL_PREFIX2))
|
|
90298
90543
|
continue;
|
|
90299
|
-
const dirPath =
|
|
90544
|
+
const dirPath = join87(skillsDir, ent);
|
|
90300
90545
|
try {
|
|
90301
90546
|
if (!statSync36(dirPath).isDirectory())
|
|
90302
90547
|
continue;
|
|
@@ -90336,7 +90581,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
90336
90581
|
continue;
|
|
90337
90582
|
if (ent.startsWith("."))
|
|
90338
90583
|
continue;
|
|
90339
|
-
const dirPath =
|
|
90584
|
+
const dirPath = join87(sharedRoot, ent);
|
|
90340
90585
|
try {
|
|
90341
90586
|
if (!statSync36(dirPath).isDirectory())
|
|
90342
90587
|
continue;
|
|
@@ -90372,7 +90617,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
|
90372
90617
|
for (const ent of entries) {
|
|
90373
90618
|
if (ent.startsWith("."))
|
|
90374
90619
|
continue;
|
|
90375
|
-
const dirPath =
|
|
90620
|
+
const dirPath = join87(bundledRoot, ent);
|
|
90376
90621
|
try {
|
|
90377
90622
|
if (!statSync36(dirPath).isDirectory())
|
|
90378
90623
|
continue;
|
|
@@ -90516,9 +90761,9 @@ function registerHostdMcpCommand(program3) {
|
|
|
90516
90761
|
// src/cli/hostd.ts
|
|
90517
90762
|
init_source();
|
|
90518
90763
|
init_helpers();
|
|
90519
|
-
import { existsSync as existsSync87, mkdirSync as mkdirSync49, readdirSync as readdirSync34, readFileSync as
|
|
90520
|
-
import { homedir as
|
|
90521
|
-
import { join as
|
|
90764
|
+
import { existsSync as existsSync87, mkdirSync as mkdirSync49, readdirSync as readdirSync34, readFileSync as readFileSync76, writeFileSync as writeFileSync42, statSync as statSync37, copyFileSync as copyFileSync12 } from "node:fs";
|
|
90765
|
+
import { homedir as homedir52 } from "node:os";
|
|
90766
|
+
import { join as join88 } from "node:path";
|
|
90522
90767
|
import { spawnSync as spawnSync19 } from "node:child_process";
|
|
90523
90768
|
|
|
90524
90769
|
// src/cli/deploy-version-guard.ts
|
|
@@ -90721,7 +90966,7 @@ networks:
|
|
|
90721
90966
|
# operator surface; the daemon's stderr lands in \`docker logs switchroom-hostd\`.
|
|
90722
90967
|
`;
|
|
90723
90968
|
}
|
|
90724
|
-
function resolveHostdHostHome(env2 = process.env, home2 =
|
|
90969
|
+
function resolveHostdHostHome(env2 = process.env, home2 = homedir52()) {
|
|
90725
90970
|
const fromEnv = env2.SWITCHROOM_HOST_HOME?.trim();
|
|
90726
90971
|
const resolved = fromEnv && fromEnv.length > 0 ? fromEnv : home2;
|
|
90727
90972
|
if (resolved === "/host-home" || resolved.startsWith("/host-home/")) {
|
|
@@ -90732,10 +90977,10 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir51()) {
|
|
|
90732
90977
|
return resolved;
|
|
90733
90978
|
}
|
|
90734
90979
|
function hostdDir() {
|
|
90735
|
-
return
|
|
90980
|
+
return join88(homedir52(), ".switchroom", "hostd");
|
|
90736
90981
|
}
|
|
90737
90982
|
function hostdComposePath() {
|
|
90738
|
-
return
|
|
90983
|
+
return join88(hostdDir(), "docker-compose.yml");
|
|
90739
90984
|
}
|
|
90740
90985
|
function backupExistingCompose() {
|
|
90741
90986
|
const p = hostdComposePath();
|
|
@@ -90862,7 +91107,7 @@ function doStatus() {
|
|
|
90862
91107
|
for (const name of readdirSync34(dir)) {
|
|
90863
91108
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
90864
91109
|
continue;
|
|
90865
|
-
const sockPath =
|
|
91110
|
+
const sockPath = join88(dir, name, "sock");
|
|
90866
91111
|
if (existsSync87(sockPath)) {
|
|
90867
91112
|
const st = statSync37(sockPath);
|
|
90868
91113
|
if ((st.mode & 61440) === 49152) {
|
|
@@ -90911,7 +91156,7 @@ function registerHostdCommand(program3) {
|
|
|
90911
91156
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
90912
91157
|
return;
|
|
90913
91158
|
}
|
|
90914
|
-
const raw =
|
|
91159
|
+
const raw = readFileSync76(logPath, "utf-8");
|
|
90915
91160
|
const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
|
|
90916
91161
|
const filters = {
|
|
90917
91162
|
agent: opts.agent,
|
|
@@ -90954,8 +91199,8 @@ The log is created when hostd handles its first privileged-verb request.`));
|
|
|
90954
91199
|
init_source();
|
|
90955
91200
|
init_helpers();
|
|
90956
91201
|
import { existsSync as existsSync88, mkdirSync as mkdirSync50, writeFileSync as writeFileSync43, copyFileSync as copyFileSync13 } from "node:fs";
|
|
90957
|
-
import { homedir as
|
|
90958
|
-
import { join as
|
|
91202
|
+
import { homedir as homedir53 } from "node:os";
|
|
91203
|
+
import { join as join89 } from "node:path";
|
|
90959
91204
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
90960
91205
|
function resolveWebImageTag(explicitTag, release) {
|
|
90961
91206
|
if (explicitTag)
|
|
@@ -91040,10 +91285,10 @@ services:
|
|
|
91040
91285
|
`;
|
|
91041
91286
|
}
|
|
91042
91287
|
function webdDir() {
|
|
91043
|
-
return
|
|
91288
|
+
return join89(homedir53(), ".switchroom", "web");
|
|
91044
91289
|
}
|
|
91045
91290
|
function webdComposePath() {
|
|
91046
|
-
return
|
|
91291
|
+
return join89(webdDir(), "docker-compose.yml");
|
|
91047
91292
|
}
|
|
91048
91293
|
function backupExistingCompose2() {
|
|
91049
91294
|
const p = webdComposePath();
|
|
@@ -91085,7 +91330,7 @@ async function doInstall2(opts, program3) {
|
|
|
91085
91330
|
return;
|
|
91086
91331
|
}
|
|
91087
91332
|
const yaml = renderWebComposeFile({
|
|
91088
|
-
hostHome:
|
|
91333
|
+
hostHome: homedir53(),
|
|
91089
91334
|
imageTag,
|
|
91090
91335
|
operatorUid
|
|
91091
91336
|
});
|
|
@@ -91184,10 +91429,10 @@ function registerWebdCommand(program3) {
|
|
|
91184
91429
|
|
|
91185
91430
|
// src/cli/host-repair.ts
|
|
91186
91431
|
init_source();
|
|
91187
|
-
import { homedir as
|
|
91188
|
-
import { join as
|
|
91432
|
+
import { homedir as homedir54 } from "node:os";
|
|
91433
|
+
import { join as join90 } from "node:path";
|
|
91189
91434
|
var ARTIFACT_ALLOWLIST = {
|
|
91190
|
-
dockerComposePluginDir: (home2) =>
|
|
91435
|
+
dockerComposePluginDir: (home2) => join90(home2, ".docker", "cli-plugins", "docker-compose"),
|
|
91191
91436
|
stateSentinel: "/state"
|
|
91192
91437
|
};
|
|
91193
91438
|
function isStateBogusAutoDir(probe2) {
|
|
@@ -91236,7 +91481,7 @@ function isStateBogusAutoDir(probe2) {
|
|
|
91236
91481
|
return true;
|
|
91237
91482
|
}
|
|
91238
91483
|
function planMountRepairs(probe2, opts = {}) {
|
|
91239
|
-
const home2 = opts.hostHome ??
|
|
91484
|
+
const home2 = opts.hostHome ?? homedir54();
|
|
91240
91485
|
const items = [];
|
|
91241
91486
|
const dockerComposePath = ARTIFACT_ALLOWLIST.dockerComposePluginDir(home2);
|
|
91242
91487
|
const dockerComposeSt = probe2.lstat(dockerComposePath);
|
|
@@ -91472,6 +91717,7 @@ registerAgentConfigWriteCommands(program3);
|
|
|
91472
91717
|
registerAgentConfigSkillWriteCommands(program3);
|
|
91473
91718
|
registerSkillCommand(program3);
|
|
91474
91719
|
registerSkillPersonalCommands(program3);
|
|
91720
|
+
registerSelfImproveProposeSkillCommand(program3);
|
|
91475
91721
|
registerSkillSearchCommand(program3);
|
|
91476
91722
|
registerAgentConfigMcpCommand(program3);
|
|
91477
91723
|
registerHostdMcpCommand(program3);
|