switchroom 0.19.39 → 0.19.41

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.
Files changed (37) hide show
  1. package/dist/agent-scheduler/index.js +10 -1
  2. package/dist/auth-broker/index.js +12 -3
  3. package/dist/cli/notion-write-pretool.mjs +10 -1
  4. package/dist/cli/switchroom.js +562 -196
  5. package/dist/host-control/main.js +287 -20
  6. package/dist/vault/approvals/kernel-server.js +12 -3
  7. package/dist/vault/broker/server.js +12 -3
  8. package/package.json +1 -1
  9. package/profiles/_base/start.sh.hbs +10 -0
  10. package/telegram-plugin/bridge/bridge.ts +2 -2
  11. package/telegram-plugin/dist/bridge/bridge.js +10 -2
  12. package/telegram-plugin/dist/gateway/gateway.js +549 -232
  13. package/telegram-plugin/dist/server.js +10 -2
  14. package/telegram-plugin/gateway/backstop-delivery.ts +48 -0
  15. package/telegram-plugin/gateway/checklist-fallback.ts +370 -0
  16. package/telegram-plugin/gateway/compaction-marker.ts +84 -0
  17. package/telegram-plugin/gateway/gateway.ts +72 -72
  18. package/telegram-plugin/gateway/liveness-wiring.ts +15 -0
  19. package/telegram-plugin/gateway/outbound-send-path.ts +20 -0
  20. package/telegram-plugin/gateway/outbox-sweep.ts +116 -18
  21. package/telegram-plugin/gateway/silence-poke-session-event.ts +13 -0
  22. package/telegram-plugin/gateway/stream-render.ts +39 -1
  23. package/telegram-plugin/gateway/turn-record-status.ts +80 -0
  24. package/telegram-plugin/hooks/compaction-marker-precompact.mjs +70 -0
  25. package/telegram-plugin/hooks/hooks.json +11 -0
  26. package/telegram-plugin/session-tail.ts +20 -0
  27. package/telegram-plugin/silence-poke.ts +28 -0
  28. package/telegram-plugin/tests/checklist-fallback.test.ts +317 -0
  29. package/telegram-plugin/tests/gateway-outbound-redact.test.ts +10 -6
  30. package/telegram-plugin/tests/outbox-delivery.test.ts +38 -1
  31. package/telegram-plugin/tests/outbox-flush-ack-claim-race.test.ts +213 -0
  32. package/telegram-plugin/tests/outbox-reply-then-recap-e2e.test.ts +1 -1
  33. package/telegram-plugin/tests/outbox-sweep-flood-breaker.test.ts +4 -4
  34. package/telegram-plugin/tests/outbox-sweep-listen-button.test.ts +71 -8
  35. package/telegram-plugin/tests/send-reply-golden.test.ts +47 -0
  36. package/telegram-plugin/tests/silence-poke-compaction.test.ts +222 -0
  37. package/telegram-plugin/tests/turn-record-status.test.ts +62 -0
@@ -4013,7 +4013,7 @@ var init_zod = __esm(() => {
4013
4013
  });
4014
4014
 
4015
4015
  // src/memory/observation-scopes.ts
4016
- var OBSERVATION_SCOPES;
4016
+ var OBSERVATION_SCOPES, OBSERVATION_SCOPE_STRATEGIES;
4017
4017
  var init_observation_scopes = __esm(() => {
4018
4018
  OBSERVATION_SCOPES = [
4019
4019
  "per_tag",
@@ -4021,10 +4021,16 @@ var init_observation_scopes = __esm(() => {
4021
4021
  "all_combinations",
4022
4022
  "shared"
4023
4023
  ];
4024
+ OBSERVATION_SCOPE_STRATEGIES = [
4025
+ "curated",
4026
+ "shared",
4027
+ "combined",
4028
+ "off"
4029
+ ];
4024
4030
  });
4025
4031
 
4026
4032
  // src/config/schema.ts
4027
- var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
4033
+ var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, ObservationScopeStrategySchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
4028
4034
  var init_schema = __esm(() => {
4029
4035
  init_zod();
4030
4036
  init_observation_scopes();
@@ -4139,7 +4145,8 @@ var init_schema = __esm(() => {
4139
4145
  allow: exports_external.array(exports_external.string()).default([]).describe("Allowed tools (use ['all'] for unrestricted)"),
4140
4146
  deny: exports_external.array(exports_external.string()).default([]).describe("Denied tools (overrides allow)")
4141
4147
  }).optional();
4142
- ObservationScopesSchema = exports_external.enum(OBSERVATION_SCOPES).optional().describe("Per-row observation scope stamped on every memory this agent " + `retains. "shared" makes Hindsight's consolidation write the ` + "resulting observations into ONE global untagged scope instead of a " + "scope per tag — what several agents pooling one bank need so their " + "observations actually merge rather than sitting in parallel " + "per-tag silos. OMITTED BY DEFAULT: unset means the field never goes " + "on the wire and the engine's own default stands, which is the " + "shipped behaviour. Applies to every retain path (Stop hook, " + "sidechain, boot reconcile, queue drain, backfill, session-handoff " + "mirror) and is carried on the queued payload, so a retain that fails " + "now and drains later still lands in this scope. Accepted values: " + `${OBSERVATION_SCOPES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
4148
+ ObservationScopesSchema = exports_external.enum(OBSERVATION_SCOPES).optional().describe("Operator PIN for the per-row observation scope, overriding the " + 'default `curated` strategy (see docs/configuration.md). "shared" ' + "forces Hindsight's consolidation to write EVERY observation into ONE " + "global untagged scope — what several agents pooling one bank want so " + "their observations merge rather than sitting in parallel silos. " + '"combined" is the pin form of opting out (restores the pre-#4035 ' + "engine default). UNSET does NOT mean 'nothing on the wire': the " + "`curated` strategy still computes a scope per retain. Applies to " + "every retain path (Stop hook, " + "sidechain, boot reconcile, queue drain, backfill, session-handoff " + "mirror) and is carried on the queued payload, so a retain that fails " + "now and drains later still lands in this scope. Accepted values: " + `${OBSERVATION_SCOPES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
4149
+ ObservationScopeStrategySchema = exports_external.enum(OBSERVATION_SCOPE_STRATEGIES).optional().describe("Per-retain strategy that decides how the observation scope is " + 'computed when no `observation_scopes` pin is set. "curated" ' + "(the default since #4035) strips volatile per-session provenance " + "tags from each retain's consolidation scope — keeping the stable " + "semantic ones on the source fact — so an agent's observations dedup " + "and merge across sessions instead of pocketing one-per-session. " + '"shared" sends every retain to the one global untagged scope. ' + '"combined" / "off" opt OUT: no per-row scope goes on the wire, ' + "restoring the pre-feature engine default (byte-identical to an " + "unconfigured client). OMITTED BY DEFAULT: unset leaves the plugin's " + "own default (`curated`) in force. A manual `observation_scopes` pin " + "wins over this strategy. Accepted values: " + `${OBSERVATION_SCOPE_STRATEGIES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
4143
4150
  AgentMemorySchema = exports_external.object({
4144
4151
  collection: exports_external.string().describe("Hindsight collection name for this agent"),
4145
4152
  auto_recall: exports_external.boolean().default(true).describe("Auto-search memories before each response"),
@@ -4175,6 +4182,7 @@ var init_schema = __esm(() => {
4175
4182
  }).optional().describe("Personality traits (1-5 each) steering how this bank frames recall, " + "reflect, and observation synthesis — a coach leans empathy-high, a " + "lawyer/analyst leans skepticism/literalism-high. Maps to the engine's " + "flat `disposition_skepticism`/`_literalism`/`_empathy` fields. " + "Cascade: per-key merge (an agent overrides individual traits and " + "inherits the rest, matching `recall`)."),
4176
4183
  directive_capture_nudge: exports_external.boolean().optional().describe("Deterministic directive-capture nudge (issue #2848 Stage B). When " + "on (switchroom default true — Stage A measured a ~55% miss rate on " + "durable corrections), the auto-recall hook regex-detects correction " + '/ standing-rule-shaped inbound ("always/never …", "from now on …", ' + `"stop doing …", a stated preference, "that's wrong, it's …") and ` + "appends a terse advisory to the turn's context telling the model to " + "persist the rule with mcp__hindsight__create_directive if it IS " + "durable. Detection is pure regex — the model does the judgment " + "in-session and calls create_directive itself (no model callsite, no " + "silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
4177
4184
  observation_scopes: ObservationScopesSchema,
4185
+ observation_scope_strategy: ObservationScopeStrategySchema,
4178
4186
  recall: exports_external.object({
4179
4187
  max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. Plugin default is 12. " + "0 disables the cap (all memories Hindsight returns are injected)."),
4180
4188
  cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
@@ -4575,6 +4583,7 @@ var init_schema = __esm(() => {
4575
4583
  isolation: exports_external.enum(["default", "strict"]).optional(),
4576
4584
  directive_capture_nudge: exports_external.boolean().optional(),
4577
4585
  observation_scopes: ObservationScopesSchema,
4586
+ observation_scope_strategy: ObservationScopeStrategySchema,
4578
4587
  recall: exports_external.object({
4579
4588
  max_memories: exports_external.number().int().min(0).optional(),
4580
4589
  cache_ttl_secs: exports_external.number().int().min(0).optional(),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "switchroom",
3
3
  "//version": "NOT the release version — source of truth is the git tag, resolved by scripts/build.mjs:resolveVersion() (see CLAUDE.md > Standard release process). This field is stale by design and only the Layer-4 dev/non-tag fallback for build.mjs + src/cli/resolve-version.ts; do NOT bump it expecting a release to pick it up. npm-pack tarball naming needs a real version — do that as an UNCOMMITTED pack-time bump (see release step 6), never a committed one.",
4
- "version": "0.19.39",
4
+ "version": "0.19.41",
5
5
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
6
6
  "type": "module",
7
7
  "bin": {
@@ -1018,6 +1018,16 @@ export HINDSIGHT_DIRECTIVE_CAPTURE_NUDGE={{hindsightDirectiveCaptureNudge}}
1018
1018
  {{#if hindsightObservationScopesQ}}
1019
1019
  export HINDSIGHT_OBSERVATION_SCOPES={{{hindsightObservationScopesQ}}}
1020
1020
  {{/if}}
1021
+ # Per-retain observation-scope STRATEGY (memory.observation_scope_strategy
1022
+ # cascade). Selects curated-vs-not: "curated" (plugin default) strips volatile
1023
+ # per-session provenance tags so observations dedup across sessions; "shared"
1024
+ # forces the one global untagged scope; "combined"/"off" opt out (no per-row
1025
+ # scope on the wire, pre-feature engine default). Export ONLY when the operator
1026
+ # set it: unset leaves the plugin's on-by-default `curated` in force. A pinned
1027
+ # per-row scope (memory.observation_scopes) still wins over this in the resolver.
1028
+ {{#if hindsightObservationScopeStrategyQ}}
1029
+ export HINDSIGHT_OBSERVATION_SCOPE_STRATEGY={{{hindsightObservationScopeStrategyQ}}}
1030
+ {{/if}}
1021
1031
  # PR6 — supergroup-mode topic tagging. JSON map of {alias: thread_id}
1022
1032
  # parsed by retain.py + recall.py to (a) stamp chat_id/thread_id/topic_alias
1023
1033
  # into retained memory metadata and (b) emit a "Current topic: …" preamble
@@ -268,7 +268,7 @@ const TOOL_SCHEMAS = [
268
268
  {
269
269
  name: 'send_checklist',
270
270
  description:
271
- 'Send a native Telegram checklist (interactive task list) to a chat. Users can tick tasks directly in the Telegram app. Returns the message_id of the created checklist. The bot is notified when tasks are ticked these arrive as channel events with kind="checklist_task_changed". Limit: 30 tasks per checklist.',
271
+ 'Send a checklist (task list) to a chat. Native interactive Telegram checklists require a Business-account connection, which most deployments do not have — in that normal case the checklist is sent as a formatted TEXT message (bold title + ✅/⬜ task lines) and the result carries degraded:"text": tasks are NOT tappable, and you tick them yourself via update_checklist (task ids are 1..N in send order). With a Business connection configured, the checklist is sent natively and tick events arrive as channel events with kind="checklist_task_changed". Returns JSON with message_id and mode. Limit: 30 tasks.',
272
272
  inputSchema: {
273
273
  type: 'object',
274
274
  properties: {
@@ -357,7 +357,7 @@ const TOOL_SCHEMAS = [
357
357
  {
358
358
  name: 'update_checklist',
359
359
  description:
360
- 'Patch an existing native Telegram checklist. Supports updating the title, adding new tasks, removing tasks, or marking tasks done/undone. Tasks with an id target existing items; tasks without an id are appended. Preserves existing task ids across edits.',
360
+ 'Patch a checklist previously sent with send_checklist: update the title, append tasks, or mark tasks done/undone. Tasks with an id target existing items (ids are 1..N in send order for text-mode checklists); tasks without an id are appended. Removal is not supported. The edit re-renders in the mode the checklist was sent in (text for most deployments). Returns JSON { ok, ... }; after a gateway restart the stored task state may be gone — then it returns ok:false with reason "unknown_checklist" unless you pass a full replacement (title + every task\'s text).',
361
361
  inputSchema: {
362
362
  type: 'object',
363
363
  properties: {
@@ -23543,6 +23543,14 @@ function projectTranscriptLine(line) {
23543
23543
  { kind: "turn_end", durationMs: obj.durationMs ?? 0 }
23544
23544
  ];
23545
23545
  }
23546
+ if (type === "system" && obj.subtype === "compact_boundary") {
23547
+ const meta = obj.compactMetadata;
23548
+ return [{
23549
+ kind: "compact_boundary",
23550
+ trigger: typeof meta?.trigger === "string" ? meta.trigger : null,
23551
+ compactDurationMs: typeof meta?.durationMs === "number" ? meta.durationMs : null
23552
+ }];
23553
+ }
23546
23554
  return [];
23547
23555
  }
23548
23556
  function projectSubagentLine(line, agentId, state) {
@@ -25233,7 +25241,7 @@ var TOOL_SCHEMAS = [
25233
25241
  },
25234
25242
  {
25235
25243
  name: "send_checklist",
25236
- description: 'Send a native Telegram checklist (interactive task list) to a chat. Users can tick tasks directly in the Telegram app. Returns the message_id of the created checklist. The bot is notified when tasks are ticked \u2014 these arrive as channel events with kind="checklist_task_changed". Limit: 30 tasks per checklist.',
25244
+ description: 'Send a checklist (task list) to a chat. Native interactive Telegram checklists require a Business-account connection, which most deployments do not have \u2014 in that normal case the checklist is sent as a formatted TEXT message (bold title + \u2705/\u2B1C task lines) and the result carries degraded:"text": tasks are NOT tappable, and you tick them yourself via update_checklist (task ids are 1..N in send order). With a Business connection configured, the checklist is sent natively and tick events arrive as channel events with kind="checklist_task_changed". Returns JSON with message_id and mode. Limit: 30 tasks.',
25237
25245
  inputSchema: {
25238
25246
  type: "object",
25239
25247
  properties: {
@@ -25318,7 +25326,7 @@ var TOOL_SCHEMAS = [
25318
25326
  },
25319
25327
  {
25320
25328
  name: "update_checklist",
25321
- description: "Patch an existing native Telegram checklist. Supports updating the title, adding new tasks, removing tasks, or marking tasks done/undone. Tasks with an id target existing items; tasks without an id are appended. Preserves existing task ids across edits.",
25329
+ description: `Patch a checklist previously sent with send_checklist: update the title, append tasks, or mark tasks done/undone. Tasks with an id target existing items (ids are 1..N in send order for text-mode checklists); tasks without an id are appended. Removal is not supported. The edit re-renders in the mode the checklist was sent in (text for most deployments). Returns JSON { ok, ... }; after a gateway restart the stored task state may be gone \u2014 then it returns ok:false with reason "unknown_checklist" unless you pass a full replacement (title + every task's text).`,
25322
25330
  inputSchema: {
25323
25331
  type: "object",
25324
25332
  properties: {