switchroom 0.13.24 → 0.13.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-scheduler/index.js +2 -4
- package/dist/auth-broker/index.js +31 -9
- package/dist/cli/switchroom.js +366 -343
- package/dist/host-control/main.js +2 -4
- package/dist/vault/approvals/kernel-server.js +68 -65
- package/dist/vault/broker/server.js +9 -8
- package/package.json +1 -1
- package/profiles/_shared/telegram-style.md.hbs +4 -4
- package/telegram-plugin/dist/gateway/gateway.js +34 -29
- package/telegram-plugin/tests/telegram-format.test.ts +2 -2
- package/telegram-plugin/uat/scenarios/jtbd-rapid-followup-dm.test.ts +1 -1
|
@@ -13769,7 +13769,6 @@ var SessionSchema = exports_external.object({
|
|
|
13769
13769
|
var SessionContinuitySchema = exports_external.object({
|
|
13770
13770
|
enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
|
|
13771
13771
|
show_handoff_line: exports_external.boolean().optional().describe("Whether the telegram plugin prepends a visible '↩️ Picked up…' " + "line to the first assistant reply after a restart (default true)."),
|
|
13772
|
-
summarizer_model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only").optional().describe("Anthropic model used to produce the handoff briefing."),
|
|
13773
13772
|
max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
|
|
13774
13773
|
resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
|
|
13775
13774
|
resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale.")
|
|
@@ -13816,10 +13815,9 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
13816
13815
|
start: exports_external.number().int().min(0).max(23),
|
|
13817
13816
|
end: exports_external.number().int().min(0).max(23),
|
|
13818
13817
|
tz: exports_external.string().optional()
|
|
13819
|
-
}).optional()
|
|
13820
|
-
model: exports_external.string().optional()
|
|
13818
|
+
}).optional()
|
|
13821
13819
|
})).optional()
|
|
13822
|
-
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "
|
|
13820
|
+
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Supports cooldowns, quiet hours, and label/action matchers. " + "Off by default — opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
13823
13821
|
webhook_rate_limit: exports_external.object({
|
|
13824
13822
|
rpm: exports_external.number().int().positive()
|
|
13825
13823
|
}).optional().describe("Per-source rate limit for the webhook ingest path (#714). " + "Off by default — when this key is absent the handler skips " + "rate-limit checks entirely. Opt in by setting `rpm` to an " + "integer requests-per-minute (token bucket per (agent, source); " + "burst equal to rpm). When enabled, exceeding the limit returns " + "429 with Retry-After header; first throttle event per " + "(agent, source) per 60s window is written to " + "<agent>/telegram/issues.jsonl. " + "Cascades from defaults.channels.telegram.webhook_rate_limit.")
|
|
@@ -11026,7 +11026,6 @@ var init_schema = __esm(() => {
|
|
|
11026
11026
|
SessionContinuitySchema = exports_external.object({
|
|
11027
11027
|
enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
|
|
11028
11028
|
show_handoff_line: exports_external.boolean().optional().describe("Whether the telegram plugin prepends a visible '↩️ Picked up…' " + "line to the first assistant reply after a restart (default true)."),
|
|
11029
|
-
summarizer_model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only").optional().describe("Anthropic model used to produce the handoff briefing."),
|
|
11030
11029
|
max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
|
|
11031
11030
|
resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
|
|
11032
11031
|
resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale.")
|
|
@@ -11073,10 +11072,9 @@ var init_schema = __esm(() => {
|
|
|
11073
11072
|
start: exports_external.number().int().min(0).max(23),
|
|
11074
11073
|
end: exports_external.number().int().min(0).max(23),
|
|
11075
11074
|
tz: exports_external.string().optional()
|
|
11076
|
-
}).optional()
|
|
11077
|
-
model: exports_external.string().optional()
|
|
11075
|
+
}).optional()
|
|
11078
11076
|
})).optional()
|
|
11079
|
-
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "
|
|
11077
|
+
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Supports cooldowns, quiet hours, and label/action matchers. " + "Off by default — opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
11080
11078
|
webhook_rate_limit: exports_external.object({
|
|
11081
11079
|
rpm: exports_external.number().int().positive()
|
|
11082
11080
|
}).optional().describe("Per-source rate limit for the webhook ingest path (#714). " + "Off by default — when this key is absent the handler skips " + "rate-limit checks entirely. Opt in by setting `rpm` to an " + "integer requests-per-minute (token bucket per (agent, source); " + "burst equal to rpm). When enabled, exceeding the limit returns " + "429 with Retry-After header; first throttle event per " + "(agent, source) per 60s window is written to " + "<agent>/telegram/issues.jsonl. " + "Cascades from defaults.channels.telegram.webhook_rate_limit.")
|
|
@@ -11674,7 +11672,70 @@ import { Database } from "bun:sqlite";
|
|
|
11674
11672
|
|
|
11675
11673
|
// src/vault/broker/protocol.ts
|
|
11676
11674
|
init_zod();
|
|
11675
|
+
|
|
11676
|
+
// src/vault/broker/peercred-ffi.ts
|
|
11677
|
+
function getPeerCred(fd) {
|
|
11678
|
+
if (process.platform !== "linux")
|
|
11679
|
+
return null;
|
|
11680
|
+
try {
|
|
11681
|
+
const ffi = __require("bun:ffi");
|
|
11682
|
+
const { dlopen, FFIType, ptr } = ffi;
|
|
11683
|
+
const SOL_SOCKET = 1;
|
|
11684
|
+
const SO_PEERCRED = 17;
|
|
11685
|
+
const UCRED_SIZE = 12;
|
|
11686
|
+
const cache = getPeerCred;
|
|
11687
|
+
const lib = cache._lib ?? (() => {
|
|
11688
|
+
const candidates = ["libc.so.6", "libc.so"];
|
|
11689
|
+
const symbolSpec = {
|
|
11690
|
+
getsockopt: {
|
|
11691
|
+
args: [FFIType.i32, FFIType.i32, FFIType.i32, FFIType.ptr, FFIType.ptr],
|
|
11692
|
+
returns: FFIType.i32
|
|
11693
|
+
}
|
|
11694
|
+
};
|
|
11695
|
+
const errors2 = [];
|
|
11696
|
+
for (const name of candidates) {
|
|
11697
|
+
try {
|
|
11698
|
+
const opened = dlopen(name, symbolSpec);
|
|
11699
|
+
cache._lib = opened;
|
|
11700
|
+
return opened;
|
|
11701
|
+
} catch (e) {
|
|
11702
|
+
errors2.push(`${name}: ${e instanceof Error ? e.message : String(e)}`);
|
|
11703
|
+
}
|
|
11704
|
+
}
|
|
11705
|
+
process.stderr.write(`[vault-broker] peercred-ffi: dlopen failed for all libc candidates ` + `(${errors2.join("; ")}); falling back to ss-parsing.
|
|
11706
|
+
`);
|
|
11707
|
+
throw new Error("no libc candidate could be opened");
|
|
11708
|
+
})();
|
|
11709
|
+
const credBuf = new ArrayBuffer(UCRED_SIZE);
|
|
11710
|
+
const lenBuf = new Uint32Array(1);
|
|
11711
|
+
lenBuf[0] = UCRED_SIZE;
|
|
11712
|
+
const rc = lib.symbols.getsockopt(fd, SOL_SOCKET, SO_PEERCRED, ptr(credBuf), ptr(lenBuf.buffer));
|
|
11713
|
+
if (rc !== 0)
|
|
11714
|
+
return null;
|
|
11715
|
+
if (lenBuf[0] !== UCRED_SIZE)
|
|
11716
|
+
return null;
|
|
11717
|
+
const view = new DataView(credBuf);
|
|
11718
|
+
return {
|
|
11719
|
+
pid: view.getInt32(0, true),
|
|
11720
|
+
uid: view.getInt32(4, true),
|
|
11721
|
+
gid: view.getInt32(8, true)
|
|
11722
|
+
};
|
|
11723
|
+
} catch {
|
|
11724
|
+
return null;
|
|
11725
|
+
}
|
|
11726
|
+
}
|
|
11727
|
+
|
|
11728
|
+
// src/vault/broker/peercred.ts
|
|
11729
|
+
var RESERVED_AGENT_NAMES = new Set(["operator", "hostd"]);
|
|
11730
|
+
function isReservedAgentName(name) {
|
|
11731
|
+
return RESERVED_AGENT_NAMES.has(name);
|
|
11732
|
+
}
|
|
11733
|
+
|
|
11734
|
+
// src/vault/broker/protocol.ts
|
|
11677
11735
|
var MAX_FRAME_BYTES = 64 * 1024;
|
|
11736
|
+
var AgentNameSchema = exports_external.string().min(1).max(64, "agent name max 64 chars").regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/, "agent name must be kebab-case ASCII (alnum + _- only, first char alnum)").refine((s) => !isReservedAgentName(s), {
|
|
11737
|
+
message: "agent name is reserved"
|
|
11738
|
+
});
|
|
11678
11739
|
var GetRequestSchema = exports_external.object({
|
|
11679
11740
|
v: exports_external.literal(1),
|
|
11680
11741
|
op: exports_external.literal("get"),
|
|
@@ -11702,7 +11763,7 @@ var ListRequestSchema = exports_external.object({
|
|
|
11702
11763
|
var MintGrantRequestSchema = exports_external.object({
|
|
11703
11764
|
v: exports_external.literal(1),
|
|
11704
11765
|
op: exports_external.literal("mint_grant"),
|
|
11705
|
-
agent:
|
|
11766
|
+
agent: AgentNameSchema,
|
|
11706
11767
|
keys: exports_external.array(exports_external.string().min(1)),
|
|
11707
11768
|
ttl_seconds: exports_external.number().int().positive().nullable(),
|
|
11708
11769
|
description: exports_external.string().optional(),
|
|
@@ -11713,7 +11774,7 @@ var MintGrantRequestSchema = exports_external.object({
|
|
|
11713
11774
|
var ListGrantsRequestSchema = exports_external.object({
|
|
11714
11775
|
v: exports_external.literal(1),
|
|
11715
11776
|
op: exports_external.literal("list_grants"),
|
|
11716
|
-
agent:
|
|
11777
|
+
agent: AgentNameSchema.optional(),
|
|
11717
11778
|
passphrase: exports_external.string().optional(),
|
|
11718
11779
|
attest_via_posture: exports_external.boolean().optional()
|
|
11719
11780
|
});
|
|
@@ -11733,7 +11794,7 @@ var LockRequestSchema = exports_external.object({
|
|
|
11733
11794
|
var PreflightAccessRequestSchema = exports_external.object({
|
|
11734
11795
|
v: exports_external.literal(1),
|
|
11735
11796
|
op: exports_external.literal("preflight_access"),
|
|
11736
|
-
agent:
|
|
11797
|
+
agent: AgentNameSchema,
|
|
11737
11798
|
keys: exports_external.array(exports_external.string().min(1)).min(1).max(128)
|
|
11738
11799
|
});
|
|
11739
11800
|
var OkPreflightAccessResponseSchema = exports_external.object({
|
|
@@ -12654,64 +12715,6 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
12654
12715
|
mergeAgentConfig.notifiedWorkerIsolationMove = false;
|
|
12655
12716
|
})(mergeAgentConfig ||= {});
|
|
12656
12717
|
|
|
12657
|
-
// src/vault/broker/peercred-ffi.ts
|
|
12658
|
-
function getPeerCred(fd) {
|
|
12659
|
-
if (process.platform !== "linux")
|
|
12660
|
-
return null;
|
|
12661
|
-
try {
|
|
12662
|
-
const ffi = __require("bun:ffi");
|
|
12663
|
-
const { dlopen, FFIType, ptr } = ffi;
|
|
12664
|
-
const SOL_SOCKET = 1;
|
|
12665
|
-
const SO_PEERCRED = 17;
|
|
12666
|
-
const UCRED_SIZE = 12;
|
|
12667
|
-
const cache = getPeerCred;
|
|
12668
|
-
const lib = cache._lib ?? (() => {
|
|
12669
|
-
const candidates = ["libc.so.6", "libc.so"];
|
|
12670
|
-
const symbolSpec = {
|
|
12671
|
-
getsockopt: {
|
|
12672
|
-
args: [FFIType.i32, FFIType.i32, FFIType.i32, FFIType.ptr, FFIType.ptr],
|
|
12673
|
-
returns: FFIType.i32
|
|
12674
|
-
}
|
|
12675
|
-
};
|
|
12676
|
-
const errors2 = [];
|
|
12677
|
-
for (const name of candidates) {
|
|
12678
|
-
try {
|
|
12679
|
-
const opened = dlopen(name, symbolSpec);
|
|
12680
|
-
cache._lib = opened;
|
|
12681
|
-
return opened;
|
|
12682
|
-
} catch (e) {
|
|
12683
|
-
errors2.push(`${name}: ${e instanceof Error ? e.message : String(e)}`);
|
|
12684
|
-
}
|
|
12685
|
-
}
|
|
12686
|
-
process.stderr.write(`[vault-broker] peercred-ffi: dlopen failed for all libc candidates ` + `(${errors2.join("; ")}); falling back to ss-parsing.
|
|
12687
|
-
`);
|
|
12688
|
-
throw new Error("no libc candidate could be opened");
|
|
12689
|
-
})();
|
|
12690
|
-
const credBuf = new ArrayBuffer(UCRED_SIZE);
|
|
12691
|
-
const lenBuf = new Uint32Array(1);
|
|
12692
|
-
lenBuf[0] = UCRED_SIZE;
|
|
12693
|
-
const rc = lib.symbols.getsockopt(fd, SOL_SOCKET, SO_PEERCRED, ptr(credBuf), ptr(lenBuf.buffer));
|
|
12694
|
-
if (rc !== 0)
|
|
12695
|
-
return null;
|
|
12696
|
-
if (lenBuf[0] !== UCRED_SIZE)
|
|
12697
|
-
return null;
|
|
12698
|
-
const view = new DataView(credBuf);
|
|
12699
|
-
return {
|
|
12700
|
-
pid: view.getInt32(0, true),
|
|
12701
|
-
uid: view.getInt32(4, true),
|
|
12702
|
-
gid: view.getInt32(8, true)
|
|
12703
|
-
};
|
|
12704
|
-
} catch {
|
|
12705
|
-
return null;
|
|
12706
|
-
}
|
|
12707
|
-
}
|
|
12708
|
-
|
|
12709
|
-
// src/vault/broker/peercred.ts
|
|
12710
|
-
var RESERVED_AGENT_NAMES = new Set(["operator", "hostd"]);
|
|
12711
|
-
function isReservedAgentName(name) {
|
|
12712
|
-
return RESERVED_AGENT_NAMES.has(name);
|
|
12713
|
-
}
|
|
12714
|
-
|
|
12715
12718
|
// src/memory/hindsight.ts
|
|
12716
12719
|
var DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise.";
|
|
12717
12720
|
|
|
@@ -11026,7 +11026,6 @@ var init_schema = __esm(() => {
|
|
|
11026
11026
|
SessionContinuitySchema = exports_external.object({
|
|
11027
11027
|
enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
|
|
11028
11028
|
show_handoff_line: exports_external.boolean().optional().describe("Whether the telegram plugin prepends a visible '↩️ Picked up…' " + "line to the first assistant reply after a restart (default true)."),
|
|
11029
|
-
summarizer_model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only").optional().describe("Anthropic model used to produce the handoff briefing."),
|
|
11030
11029
|
max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
|
|
11031
11030
|
resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
|
|
11032
11031
|
resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale.")
|
|
@@ -11073,10 +11072,9 @@ var init_schema = __esm(() => {
|
|
|
11073
11072
|
start: exports_external.number().int().min(0).max(23),
|
|
11074
11073
|
end: exports_external.number().int().min(0).max(23),
|
|
11075
11074
|
tz: exports_external.string().optional()
|
|
11076
|
-
}).optional()
|
|
11077
|
-
model: exports_external.string().optional()
|
|
11075
|
+
}).optional()
|
|
11078
11076
|
})).optional()
|
|
11079
|
-
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "
|
|
11077
|
+
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Supports cooldowns, quiet hours, and label/action matchers. " + "Off by default — opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
11080
11078
|
webhook_rate_limit: exports_external.object({
|
|
11081
11079
|
rpm: exports_external.number().int().positive()
|
|
11082
11080
|
}).optional().describe("Per-source rate limit for the webhook ingest path (#714). " + "Off by default — when this key is absent the handler skips " + "rate-limit checks entirely. Opt in by setting `rpm` to an " + "integer requests-per-minute (token bucket per (agent, source); " + "burst equal to rpm). When enabled, exceeding the limit returns " + "429 with Retry-After header; first throttle event per " + "(agent, source) per 60s window is written to " + "<agent>/telegram/issues.jsonl. " + "Cascades from defaults.channels.telegram.webhook_rate_limit.")
|
|
@@ -13103,6 +13101,9 @@ function checkGoogleAccountAcl(config, agentName, account, key) {
|
|
|
13103
13101
|
// src/vault/broker/protocol.ts
|
|
13104
13102
|
init_zod();
|
|
13105
13103
|
var MAX_FRAME_BYTES = 64 * 1024;
|
|
13104
|
+
var AgentNameSchema = exports_external.string().min(1).max(64, "agent name max 64 chars").regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/, "agent name must be kebab-case ASCII (alnum + _- only, first char alnum)").refine((s) => !isReservedAgentName(s), {
|
|
13105
|
+
message: "agent name is reserved"
|
|
13106
|
+
});
|
|
13106
13107
|
var GetRequestSchema = exports_external.object({
|
|
13107
13108
|
v: exports_external.literal(1),
|
|
13108
13109
|
op: exports_external.literal("get"),
|
|
@@ -13130,7 +13131,7 @@ var ListRequestSchema = exports_external.object({
|
|
|
13130
13131
|
var MintGrantRequestSchema = exports_external.object({
|
|
13131
13132
|
v: exports_external.literal(1),
|
|
13132
13133
|
op: exports_external.literal("mint_grant"),
|
|
13133
|
-
agent:
|
|
13134
|
+
agent: AgentNameSchema,
|
|
13134
13135
|
keys: exports_external.array(exports_external.string().min(1)),
|
|
13135
13136
|
ttl_seconds: exports_external.number().int().positive().nullable(),
|
|
13136
13137
|
description: exports_external.string().optional(),
|
|
@@ -13141,7 +13142,7 @@ var MintGrantRequestSchema = exports_external.object({
|
|
|
13141
13142
|
var ListGrantsRequestSchema = exports_external.object({
|
|
13142
13143
|
v: exports_external.literal(1),
|
|
13143
13144
|
op: exports_external.literal("list_grants"),
|
|
13144
|
-
agent:
|
|
13145
|
+
agent: AgentNameSchema.optional(),
|
|
13145
13146
|
passphrase: exports_external.string().optional(),
|
|
13146
13147
|
attest_via_posture: exports_external.boolean().optional()
|
|
13147
13148
|
});
|
|
@@ -13161,7 +13162,7 @@ var LockRequestSchema = exports_external.object({
|
|
|
13161
13162
|
var PreflightAccessRequestSchema = exports_external.object({
|
|
13162
13163
|
v: exports_external.literal(1),
|
|
13163
13164
|
op: exports_external.literal("preflight_access"),
|
|
13164
|
-
agent:
|
|
13165
|
+
agent: AgentNameSchema,
|
|
13165
13166
|
keys: exports_external.array(exports_external.string().min(1)).min(1).max(128)
|
|
13166
13167
|
});
|
|
13167
13168
|
var OkPreflightAccessResponseSchema = exports_external.object({
|
|
@@ -16948,7 +16949,7 @@ class VaultBroker {
|
|
|
16948
16949
|
const revoked = revokeGrant(this.grantsDb, id);
|
|
16949
16950
|
try {
|
|
16950
16951
|
const row = this.grantsDb.query("SELECT agent_slug FROM vault_grants WHERE id = ?").get(id);
|
|
16951
|
-
if (row) {
|
|
16952
|
+
if (row && AgentNameSchema.safeParse(row.agent_slug).success) {
|
|
16952
16953
|
const tokenPath = path3.join(os3.homedir(), ".switchroom", "agents", row.agent_slug, ".vault-token");
|
|
16953
16954
|
if (existsSync8(tokenPath)) {
|
|
16954
16955
|
try {
|
package/package.json
CHANGED
|
@@ -5,10 +5,10 @@ Telegram is a chat — replies should feel like one, not a terminal dump or a tr
|
|
|
5
5
|
**Every turn that responds to a user message MUST end with a `reply` (or `stream_reply` with `done=true`).** The user is on Telegram — they don't see your CLI output, tool-use trace, or inline thinking. The ONLY path for words to reach them is an MCP tool call. If you have a final answer, send it via `reply`. The text in your terminal is not the conversation.
|
|
6
6
|
|
|
7
7
|
**Conversational pacing — a human is on the other side.** Match the rhythm of a capable colleague messaging you back. Five beats:
|
|
8
|
-
- **1 · Acknowledge first.** Unless your whole reply is a single short sentence you can send right now, your first action is a short one-liner via `reply`, persona voice, sent fast (`disable_notification: true`): *"
|
|
8
|
+
- **1 · Acknowledge first.** Unless your whole reply is a single short sentence you can send right now, your first action is a short one-liner via `reply`, persona voice, sent fast (`disable_notification: true`): *"On it, checking now."*. This holds whether the work ahead is a tool call or a paragraph of pure reasoning — if the answer will run long, ack *before* you compose it. Skip the ack only for an immediate one-sentence answer (*"What's 2+2?"*). This is a beat, not filler — it's the line between a colleague and a black box.
|
|
9
9
|
- **2 · Then go quiet and work.** Heads-down is right — do **not** narrate every tool call. A typing indicator runs for you automatically; you don't keep it alive.
|
|
10
10
|
- **3 · Surface meaningful progress** at genuine inflection points — a hard step finished, a blocker, a pivot, dispatching a sub-agent, a notably slow wait, a finding worth knowing now. One short `reply`, `disable_notification: true` (no mid-turn ping).
|
|
11
|
-
- **4 · Hand back delegations with synthesis.** When a sub-agent reports back, re-enter in your own voice — what it found, what you're doing next (*"
|
|
11
|
+
- **4 · Hand back delegations with synthesis.** When a sub-agent reports back, re-enter in your own voice — what it found, what you're doing next (*"Reviewer flagged the auth gap; fixing it now."*). Never let its raw report stand as your reply. A *background* worker finishes after your turn has ended — its result is delivered to you as a fresh `<channel source="subagent_handback">` turn. That turn IS your cue: synthesise it for the user right then; don't treat it as noise and don't stay silent.
|
|
12
12
|
- **5 · Deliver the answer** as a fresh `reply` (omit `disable_notification` — pings once).
|
|
13
13
|
|
|
14
14
|
The one thing to avoid is **spam**: a reply on every tool call, on a cadence, or repeating yourself. Responsive and human, never a flood. A `<system-reminder>` containing `[silence-poke]` means you've gone quiet too long — send one short `reply` and carry on; skip it only if you're within ~5s of finishing.
|
|
@@ -29,7 +29,7 @@ The 👀→🤔→🔥→👍 status reaction and the typing indicator are *ambi
|
|
|
29
29
|
|
|
30
30
|
If both `queued` and `steering` are somehow present, `steering` wins (explicit opt-in beats default). If `prior_turn_in_progress="true"` is set without either flag (shouldn't happen but defensive), treat the message as a follow-up related to your last reply.
|
|
31
31
|
|
|
32
|
-
**Self-narrate the classification.** At the top of your reply for any `steering` or `queued` message, include a brief italic one-liner so the user can correct you — e.g. `_↪️
|
|
32
|
+
**Self-narrate the classification.** At the top of your reply for any `steering` or `queued` message, include a brief italic one-liner so the user can correct you — e.g. `_↪️ Treating as steer on the prior task_` or `_📥 Queued as a new task_`.
|
|
33
33
|
|
|
34
34
|
**Formatting** (Telegram HTML — `reply` and `stream_reply` default to `format: "html"` and convert markdown for you):
|
|
35
35
|
- Use **bold** sparingly for emphasis on key facts only
|
|
@@ -62,7 +62,7 @@ Don't use `accent` on routine conversational replies — it's for status communi
|
|
|
62
62
|
|
|
63
63
|
**When stickers / GIFs land badly**: in lieu of an actual answer, decorating routine acknowledgements ("got it 👍 [+sticker]"), peppering a long thread, or any time the user is task-focused. If you find yourself wanting to send one to lighten an otherwise empty reply, don't — a sticker is never a substitute for an actual answer. Two stickers in a row is always wrong.
|
|
64
64
|
|
|
65
|
-
**Interrupt marker.** If a user asks how to stop you mid-turn, tell them: *"Start your message with `!`
|
|
65
|
+
**Interrupt marker.** If a user asks how to stop you mid-turn, tell them: *"Start your message with `!` to interrupt whatever I'm doing and treat the rest as a fresh request."* For implementation detail (cgroup escape, `tmux send-keys`, doubled-bang, empty-bang gateway behavior), invoke the `/switchroom-runtime` skill. The `!` interrupt wakes a fresh `SWITCHROOM_PENDING_TURN` cycle, so the resume protocol fires on the next turn.
|
|
66
66
|
|
|
67
67
|
**Wake audit on fresh boot.** If `$TELEGRAM_STATE_DIR/.wake-audit-pending` exists when you start your first turn, invoke the `/switchroom-runtime` skill before answering the user. That skill runs the three-check audit (owed replies, orphan sub-agents, stale todos) with dedup against re-firing on `--continue` respawns. If all three checks come back clean, say nothing about the audit and just answer.
|
|
68
68
|
|
|
@@ -23670,7 +23670,6 @@ var init_schema = __esm(() => {
|
|
|
23670
23670
|
SessionContinuitySchema = exports_external.object({
|
|
23671
23671
|
enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
|
|
23672
23672
|
show_handoff_line: exports_external.boolean().optional().describe("Whether the telegram plugin prepends a visible '\u21a9\ufe0f Picked up\u2026' " + "line to the first assistant reply after a restart (default true)."),
|
|
23673
|
-
summarizer_model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only").optional().describe("Anthropic model used to produce the handoff briefing."),
|
|
23674
23673
|
max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
|
|
23675
23674
|
resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
|
|
23676
23675
|
resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale.")
|
|
@@ -23717,10 +23716,9 @@ var init_schema = __esm(() => {
|
|
|
23717
23716
|
start: exports_external.number().int().min(0).max(23),
|
|
23718
23717
|
end: exports_external.number().int().min(0).max(23),
|
|
23719
23718
|
tz: exports_external.string().optional()
|
|
23720
|
-
}).optional()
|
|
23721
|
-
model: exports_external.string().optional()
|
|
23719
|
+
}).optional()
|
|
23722
23720
|
})).optional()
|
|
23723
|
-
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "
|
|
23721
|
+
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Supports cooldowns, quiet hours, and label/action matchers. " + "Off by default \u2014 opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
23724
23722
|
webhook_rate_limit: exports_external.object({
|
|
23725
23723
|
rpm: exports_external.number().int().positive()
|
|
23726
23724
|
}).optional().describe("Per-source rate limit for the webhook ingest path (#714). " + "Off by default \u2014 when this key is absent the handler skips " + "rate-limit checks entirely. Opt in by setting `rpm` to an " + "integer requests-per-minute (token bucket per (agent, source); " + "burst equal to rpm). When enabled, exceeding the limit returns " + "429 with Retry-After header; first throttle event per " + "(agent, source) per 60s window is written to " + "<agent>/telegram/issues.jsonl. " + "Cascades from defaults.channels.telegram.webhook_rate_limit.")
|
|
@@ -24658,6 +24656,20 @@ function formatResetRelative(target, now = new Date) {
|
|
|
24658
24656
|
}
|
|
24659
24657
|
var OAUTH_BETA = "oauth-2025-04-20", DEFAULT_USER_AGENT = "claude-cli/1.0.0 (external, cli)", DEFAULT_PROBE_MODEL = "claude-haiku-4-5-20251001";
|
|
24660
24658
|
var init_quota_check = () => {};
|
|
24659
|
+
// ../src/vault/broker/peercred.ts
|
|
24660
|
+
function isReservedAgentName(name) {
|
|
24661
|
+
return RESERVED_AGENT_NAMES.has(name);
|
|
24662
|
+
}
|
|
24663
|
+
function unlockSocketFor(dataSocketPath) {
|
|
24664
|
+
if (dataSocketPath.endsWith("/sock")) {
|
|
24665
|
+
return dataSocketPath.slice(0, -"/sock".length) + "/unlock";
|
|
24666
|
+
}
|
|
24667
|
+
return dataSocketPath.replace(/\.sock$/, ".unlock.sock");
|
|
24668
|
+
}
|
|
24669
|
+
var RESERVED_AGENT_NAMES;
|
|
24670
|
+
var init_peercred = __esm(() => {
|
|
24671
|
+
RESERVED_AGENT_NAMES = new Set(["operator", "hostd"]);
|
|
24672
|
+
});
|
|
24661
24673
|
|
|
24662
24674
|
// ../src/vault/broker/protocol.ts
|
|
24663
24675
|
function encodeRequest2(req) {
|
|
@@ -24675,10 +24687,14 @@ function decodeResponse2(line) {
|
|
|
24675
24687
|
const obj = JSON.parse(line);
|
|
24676
24688
|
return ResponseSchema2.parse(obj);
|
|
24677
24689
|
}
|
|
24678
|
-
var MAX_FRAME_BYTES2, GetRequestSchema, PutRequestSchema, ListRequestSchema, MintGrantRequestSchema, ListGrantsRequestSchema, RevokeGrantRequestSchema, StatusRequestSchema, LockRequestSchema, PreflightAccessRequestSchema, OkPreflightAccessResponseSchema, ApprovalRequestRequestSchema, ApprovalLookupRequestSchema, ApprovalConsumeRequestSchema, ApprovalRevokeRequestSchema, ApprovalListRequestSchema, ApprovalDecisionModeSchema, ApprovalRecordRequestSchema, ApprovalConsumeRecordRequestSchema, RequestSchema2, VaultEntrySchema, ErrorCode, OkEntryResponseSchema, OkKeysResponseSchema, BrokerStatus, OkStatusResponseSchema, OkLockResponseSchema, OkPutResponseSchema, OkMintGrantResponseSchema, GrantMetaSchema, OkListGrantsResponseSchema, OkRevokeGrantResponseSchema, OkApprovalRequestResponseSchema, ApprovalDecisionMetaSchema, OkApprovalLookupResponseSchema, OkApprovalConsumeResponseSchema, OkApprovalRevokeResponseSchema, OkApprovalListResponseSchema, OkApprovalRecordResponseSchema, OkApprovalConsumeRecordResponseSchema, ErrorResponseSchema2, ResponseSchema2;
|
|
24690
|
+
var MAX_FRAME_BYTES2, AgentNameSchema, GetRequestSchema, PutRequestSchema, ListRequestSchema, MintGrantRequestSchema, ListGrantsRequestSchema, RevokeGrantRequestSchema, StatusRequestSchema, LockRequestSchema, PreflightAccessRequestSchema, OkPreflightAccessResponseSchema, ApprovalRequestRequestSchema, ApprovalLookupRequestSchema, ApprovalConsumeRequestSchema, ApprovalRevokeRequestSchema, ApprovalListRequestSchema, ApprovalDecisionModeSchema, ApprovalRecordRequestSchema, ApprovalConsumeRecordRequestSchema, RequestSchema2, VaultEntrySchema, ErrorCode, OkEntryResponseSchema, OkKeysResponseSchema, BrokerStatus, OkStatusResponseSchema, OkLockResponseSchema, OkPutResponseSchema, OkMintGrantResponseSchema, GrantMetaSchema, OkListGrantsResponseSchema, OkRevokeGrantResponseSchema, OkApprovalRequestResponseSchema, ApprovalDecisionMetaSchema, OkApprovalLookupResponseSchema, OkApprovalConsumeResponseSchema, OkApprovalRevokeResponseSchema, OkApprovalListResponseSchema, OkApprovalRecordResponseSchema, OkApprovalConsumeRecordResponseSchema, ErrorResponseSchema2, ResponseSchema2;
|
|
24679
24691
|
var init_protocol2 = __esm(() => {
|
|
24680
24692
|
init_zod();
|
|
24693
|
+
init_peercred();
|
|
24681
24694
|
MAX_FRAME_BYTES2 = 64 * 1024;
|
|
24695
|
+
AgentNameSchema = exports_external.string().min(1).max(64, "agent name max 64 chars").regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/, "agent name must be kebab-case ASCII (alnum + _- only, first char alnum)").refine((s) => !isReservedAgentName(s), {
|
|
24696
|
+
message: "agent name is reserved"
|
|
24697
|
+
});
|
|
24682
24698
|
GetRequestSchema = exports_external.object({
|
|
24683
24699
|
v: exports_external.literal(1),
|
|
24684
24700
|
op: exports_external.literal("get"),
|
|
@@ -24706,7 +24722,7 @@ var init_protocol2 = __esm(() => {
|
|
|
24706
24722
|
MintGrantRequestSchema = exports_external.object({
|
|
24707
24723
|
v: exports_external.literal(1),
|
|
24708
24724
|
op: exports_external.literal("mint_grant"),
|
|
24709
|
-
agent:
|
|
24725
|
+
agent: AgentNameSchema,
|
|
24710
24726
|
keys: exports_external.array(exports_external.string().min(1)),
|
|
24711
24727
|
ttl_seconds: exports_external.number().int().positive().nullable(),
|
|
24712
24728
|
description: exports_external.string().optional(),
|
|
@@ -24717,7 +24733,7 @@ var init_protocol2 = __esm(() => {
|
|
|
24717
24733
|
ListGrantsRequestSchema = exports_external.object({
|
|
24718
24734
|
v: exports_external.literal(1),
|
|
24719
24735
|
op: exports_external.literal("list_grants"),
|
|
24720
|
-
agent:
|
|
24736
|
+
agent: AgentNameSchema.optional(),
|
|
24721
24737
|
passphrase: exports_external.string().optional(),
|
|
24722
24738
|
attest_via_posture: exports_external.boolean().optional()
|
|
24723
24739
|
});
|
|
@@ -24737,7 +24753,7 @@ var init_protocol2 = __esm(() => {
|
|
|
24737
24753
|
PreflightAccessRequestSchema = exports_external.object({
|
|
24738
24754
|
v: exports_external.literal(1),
|
|
24739
24755
|
op: exports_external.literal("preflight_access"),
|
|
24740
|
-
agent:
|
|
24756
|
+
agent: AgentNameSchema,
|
|
24741
24757
|
keys: exports_external.array(exports_external.string().min(1)).min(1).max(128)
|
|
24742
24758
|
});
|
|
24743
24759
|
OkPreflightAccessResponseSchema = exports_external.object({
|
|
@@ -24984,17 +25000,6 @@ var init_protocol2 = __esm(() => {
|
|
|
24984
25000
|
ErrorResponseSchema2
|
|
24985
25001
|
]);
|
|
24986
25002
|
});
|
|
24987
|
-
// ../src/vault/broker/peercred.ts
|
|
24988
|
-
function unlockSocketFor(dataSocketPath) {
|
|
24989
|
-
if (dataSocketPath.endsWith("/sock")) {
|
|
24990
|
-
return dataSocketPath.slice(0, -"/sock".length) + "/unlock";
|
|
24991
|
-
}
|
|
24992
|
-
return dataSocketPath.replace(/\.sock$/, ".unlock.sock");
|
|
24993
|
-
}
|
|
24994
|
-
var RESERVED_AGENT_NAMES;
|
|
24995
|
-
var init_peercred = __esm(() => {
|
|
24996
|
-
RESERVED_AGENT_NAMES = new Set(["operator", "hostd"]);
|
|
24997
|
-
});
|
|
24998
25003
|
|
|
24999
25004
|
// ../src/runtime-mode.ts
|
|
25000
25005
|
function isDockerRuntime() {
|
|
@@ -43194,7 +43199,7 @@ var GetStatusRequestSchema = exports_external.object({
|
|
|
43194
43199
|
target_request_id: exports_external.string().min(1).max(128)
|
|
43195
43200
|
})
|
|
43196
43201
|
});
|
|
43197
|
-
var
|
|
43202
|
+
var AgentNameSchema2 = exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/, "agent name must be kebab-case ASCII");
|
|
43198
43203
|
var UpdateCheckRequestSchema = exports_external.object({
|
|
43199
43204
|
...RequestEnvelope,
|
|
43200
43205
|
op: exports_external.literal("update_check"),
|
|
@@ -43219,21 +43224,21 @@ var AgentStartRequestSchema = exports_external.object({
|
|
|
43219
43224
|
...RequestEnvelope,
|
|
43220
43225
|
op: exports_external.literal("agent_start"),
|
|
43221
43226
|
args: exports_external.object({
|
|
43222
|
-
name:
|
|
43227
|
+
name: AgentNameSchema2
|
|
43223
43228
|
})
|
|
43224
43229
|
});
|
|
43225
43230
|
var AgentStopRequestSchema = exports_external.object({
|
|
43226
43231
|
...RequestEnvelope,
|
|
43227
43232
|
op: exports_external.literal("agent_stop"),
|
|
43228
43233
|
args: exports_external.object({
|
|
43229
|
-
name:
|
|
43234
|
+
name: AgentNameSchema2
|
|
43230
43235
|
})
|
|
43231
43236
|
});
|
|
43232
43237
|
var AgentLogsRequestSchema = exports_external.object({
|
|
43233
43238
|
...RequestEnvelope,
|
|
43234
43239
|
op: exports_external.literal("agent_logs"),
|
|
43235
43240
|
args: exports_external.object({
|
|
43236
|
-
name:
|
|
43241
|
+
name: AgentNameSchema2,
|
|
43237
43242
|
tail: exports_external.number().int().positive().max(2000).optional()
|
|
43238
43243
|
})
|
|
43239
43244
|
});
|
|
@@ -43241,7 +43246,7 @@ var AgentExecRequestSchema = exports_external.object({
|
|
|
43241
43246
|
...RequestEnvelope,
|
|
43242
43247
|
op: exports_external.literal("agent_exec"),
|
|
43243
43248
|
args: exports_external.object({
|
|
43244
|
-
name:
|
|
43249
|
+
name: AgentNameSchema2,
|
|
43245
43250
|
argv: exports_external.array(exports_external.string().min(1)).min(1).max(32)
|
|
43246
43251
|
})
|
|
43247
43252
|
});
|
|
@@ -43254,7 +43259,7 @@ var AgentSmokeRequestSchema = exports_external.object({
|
|
|
43254
43259
|
...RequestEnvelope,
|
|
43255
43260
|
op: exports_external.literal("agent_smoke"),
|
|
43256
43261
|
args: exports_external.object({
|
|
43257
|
-
name:
|
|
43262
|
+
name: AgentNameSchema2,
|
|
43258
43263
|
deep: exports_external.boolean().optional()
|
|
43259
43264
|
})
|
|
43260
43265
|
});
|
|
@@ -48323,10 +48328,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
48323
48328
|
}
|
|
48324
48329
|
|
|
48325
48330
|
// ../src/build-info.ts
|
|
48326
|
-
var VERSION = "0.13.
|
|
48327
|
-
var COMMIT_SHA = "
|
|
48328
|
-
var COMMIT_DATE = "2026-05-
|
|
48329
|
-
var LATEST_PR =
|
|
48331
|
+
var VERSION = "0.13.25";
|
|
48332
|
+
var COMMIT_SHA = "e927d05d";
|
|
48333
|
+
var COMMIT_DATE = "2026-05-24T05:14:52Z";
|
|
48334
|
+
var LATEST_PR = 1717;
|
|
48330
48335
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
48331
48336
|
|
|
48332
48337
|
// gateway/boot-version.ts
|
|
@@ -32,8 +32,8 @@ describe('markdownToHtml', () => {
|
|
|
32
32
|
expect(markdownToHtml('Hello _world_')).toContain('<i>world</i>')
|
|
33
33
|
})
|
|
34
34
|
|
|
35
|
-
test('converts emoji-leading _📥
|
|
36
|
-
expect(markdownToHtml('_📥
|
|
35
|
+
test('converts emoji-leading _📥 Queued as a new task_', () => {
|
|
36
|
+
expect(markdownToHtml('_📥 Queued as a new task_')).toContain('<i>📥 Queued as a new task</i>')
|
|
37
37
|
})
|
|
38
38
|
|
|
39
39
|
test('converts emoji-trailing _steer on the prior task 🔁_', () => {
|
|
@@ -45,7 +45,7 @@ describe("uat: rapid follow-ups — steering vs queued classification", () => {
|
|
|
45
45
|
|
|
46
46
|
// The agent should reply mentioning md5 AND surface the italic
|
|
47
47
|
// classification line per the prompt
|
|
48
|
-
// ("_↪️
|
|
48
|
+
// ("_↪️ Treating as steer on the prior task_" or similar).
|
|
49
49
|
// We match either explicit-steer narration OR the steer emoji
|
|
50
50
|
// (`↪️`) to allow for natural-language variation while still
|
|
51
51
|
// failing if no narration appears (the previous version of
|