switchroom 0.15.3 → 0.15.4

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.
@@ -0,0 +1,112 @@
1
+ #!/bin/bash
2
+ # UserPromptSubmit hook that emits the per-turn pacing directive.
3
+ #
4
+ # Wired into the agent's .claude/settings.json hooks.UserPromptSubmit by
5
+ # scaffold.ts when context_efficiency inject_on_change is enabled (the
6
+ # default). Replaces the inline `printf` that previously re-injected the
7
+ # static directive on every single turn, causing ~3 KB of redundant context
8
+ # per turn (measured ~290 KB over a 26-prompt session).
9
+ #
10
+ # Environment variables (set in the hook command by scaffold.ts):
11
+ #
12
+ # TURN_PACING_DIRECTIVE - The full directive text to inject. Scaffold.ts
13
+ # embeds the directive in the hook command string.
14
+ # TURN_PACING_HASH - sha256 of the directive text (hex). Used to
15
+ # detect when the directive changed between
16
+ # scaffold regenerations.
17
+ #
18
+ # Inject-on-change semantics: on every fire:
19
+ # 1. Read session_id from stdin JSON.
20
+ # 2. Check per-session state file under $TELEGRAM_STATE_DIR/.hook-state/.
21
+ # 3. If session matches AND directive hash matches recorded state → emit
22
+ # nothing (the model already has the directive in context).
23
+ # 4. Otherwise emit the directive and record the new state.
24
+ #
25
+ # Fail open: any error in state management falls back to emitting the full
26
+ # directive so context is never silently dropped.
27
+ #
28
+ # State files: $TELEGRAM_STATE_DIR/.hook-state/turn-pacing.<session_id>
29
+ # Each file contains: <directive_hash>\n
30
+ # Stale state files (different session_id) are cleaned up on each new-
31
+ # session write (keeps at most 5 files to avoid accumulation).
32
+
33
+ set -u
34
+
35
+ DIRECTIVE="${TURN_PACING_DIRECTIVE:-}"
36
+ DIRECTIVE_HASH="${TURN_PACING_HASH:-}"
37
+
38
+ # If directive or hash are missing, emit whatever we have and exit.
39
+ if [ -z "$DIRECTIVE" ]; then
40
+ exit 0
41
+ fi
42
+
43
+ emit_full() {
44
+ printf '%s\n' "$DIRECTIVE"
45
+ }
46
+
47
+ # Read stdin JSON to extract session_id.
48
+ SESSION_ID=""
49
+ if ! [ -t 0 ]; then
50
+ STDIN_JSON=$(cat 2>/dev/null || true)
51
+ if command -v jq >/dev/null 2>&1; then
52
+ SESSION_ID=$(printf '%s' "$STDIN_JSON" | jq -r '.session_id // empty' 2>/dev/null || true)
53
+ else
54
+ SESSION_ID=$(printf '%s' "$STDIN_JSON" | grep -o '"session_id":"[^"]*"' | sed 's/"session_id":"//;s/"//' 2>/dev/null || true)
55
+ fi
56
+ # Sanitise: session_id must be alphanumeric + hyphens only.
57
+ if ! printf '%s' "${SESSION_ID:-}" | grep -qE '^[a-zA-Z0-9_-]{1,128}$' 2>/dev/null; then
58
+ SESSION_ID=""
59
+ fi
60
+ fi
61
+
62
+ # If we can't get a session_id, emit full (fail-open).
63
+ if [ -z "$SESSION_ID" ]; then
64
+ emit_full
65
+ exit 0
66
+ fi
67
+
68
+ # If hash is missing, emit full (fail-open).
69
+ if [ -z "$DIRECTIVE_HASH" ]; then
70
+ emit_full
71
+ exit 0
72
+ fi
73
+
74
+ # Locate state dir. Prefer $TELEGRAM_STATE_DIR (set by start.sh in the
75
+ # container); fall back to $HOME/.claude/switchroom-hookcache for dev use.
76
+ STATE_BASE="${TELEGRAM_STATE_DIR:-${HOME:-/tmp}/.claude/switchroom-hookcache}"
77
+ HOOK_STATE_DIR="$STATE_BASE/.hook-state"
78
+
79
+ # Attempt to create the state dir; if that fails emit full (fail-open).
80
+ if ! mkdir -p "$HOOK_STATE_DIR" 2>/dev/null; then
81
+ emit_full
82
+ exit 0
83
+ fi
84
+
85
+ STATE_FILE="$HOOK_STATE_DIR/turn-pacing.$SESSION_ID"
86
+
87
+ # Check if already emitted for this session with the same directive.
88
+ if [ -f "$STATE_FILE" ]; then
89
+ RECORDED=$(head -1 "$STATE_FILE" 2>/dev/null || echo "")
90
+ if [ "$RECORDED" = "$DIRECTIVE_HASH" ]; then
91
+ # Already injected and content unchanged — suppress.
92
+ exit 0
93
+ fi
94
+ fi
95
+
96
+ # New session or directive changed — emit and record state.
97
+ emit_full
98
+
99
+ # Write state: hash on first line.
100
+ printf '%s\n' "$DIRECTIVE_HASH" > "$STATE_FILE" 2>/dev/null || true
101
+
102
+ # Clean up stale state files (different session_id) — keep at most 5 files.
103
+ # shellcheck disable=SC2012
104
+ OLD_COUNT=$(ls -1 "$HOOK_STATE_DIR"/turn-pacing.* 2>/dev/null | grep -cv "turn-pacing\.$SESSION_ID$" || true)
105
+ if [ "$OLD_COUNT" -gt 5 ]; then
106
+ ls -1t "$HOOK_STATE_DIR"/turn-pacing.* 2>/dev/null \
107
+ | grep -v "turn-pacing\.$SESSION_ID$" \
108
+ | tail -n +6 \
109
+ | xargs rm -f 2>/dev/null || true
110
+ fi
111
+
112
+ exit 0
@@ -1,6 +1,6 @@
1
1
  #!/bin/bash
2
2
  # UserPromptSubmit hook for dynamic workspace bootstrap (MEMORY.md, daily
3
- # notes, HEARTBEAT.md).
3
+ # notes).
4
4
  #
5
5
  # Wired into the agent's .claude/settings.json hooks.UserPromptSubmit by
6
6
  # scaffold.ts. On every inbound user prompt, this script re-renders the
@@ -9,17 +9,27 @@
9
9
  #
10
10
  # Configuration is via env vars (set at start.sh time):
11
11
  #
12
- # SWITCHROOM_AGENT_NAME - The agent name (required, set in start.sh)
12
+ # SWITCHROOM_AGENT_NAME - The agent name (required, set in start.sh)
13
+ # SWITCHROOM_INJECT_ON_CHANGE - When "1", enables inject-on-change
14
+ # semantics: content is only emitted when the
15
+ # session_id changes or file content changes.
16
+ # When "0" or unset (legacy mode), content is
17
+ # emitted every turn as before (full emit).
18
+ # Default in new agents is "1" (set by
19
+ # scaffold.ts when inject_on_change is true,
20
+ # the default).
13
21
  #
14
22
  # Failure modes (all silent — workspace injection must never block the turn):
15
23
  # - switchroom CLI missing → exit 0 with no output
16
24
  # - workspace dir missing → exit 0 with no output
17
25
  # - workspace render fails → exit 0 with no output
18
26
  # - empty result set → exit 0 with no output
27
+ # - state dir error → emit full content (fail-open)
19
28
 
20
29
  set -u
21
30
 
22
31
  AGENT_NAME="${SWITCHROOM_AGENT_NAME:-}"
32
+ INJECT_ON_CHANGE="${SWITCHROOM_INJECT_ON_CHANGE:-0}"
23
33
 
24
34
  if [ -z "$AGENT_NAME" ]; then
25
35
  exit 0
@@ -29,6 +39,26 @@ if ! command -v switchroom >/dev/null 2>&1; then
29
39
  exit 0
30
40
  fi
31
41
 
42
+ # ---------------------------------------------------------------------------
43
+ # Inject-on-change: read session_id from stdin JSON (when enabled)
44
+ # ---------------------------------------------------------------------------
45
+ SESSION_ID=""
46
+ if [ "$INJECT_ON_CHANGE" = "1" ]; then
47
+ # Read stdin (non-blocking: if no stdin supplied in tests, just skip).
48
+ if ! [ -t 0 ]; then
49
+ STDIN_JSON=$(cat 2>/dev/null || true)
50
+ if command -v jq >/dev/null 2>&1; then
51
+ SESSION_ID=$(printf '%s' "$STDIN_JSON" | jq -r '.session_id // empty' 2>/dev/null || true)
52
+ else
53
+ SESSION_ID=$(printf '%s' "$STDIN_JSON" | grep -o '"session_id":"[^"]*"' | sed 's/"session_id":"//;s/"//' 2>/dev/null || true)
54
+ fi
55
+ # Sanitise session_id — must be alphanumeric + hyphens only.
56
+ if ! printf '%s' "${SESSION_ID:-}" | grep -qE '^[a-zA-Z0-9_-]{1,128}$' 2>/dev/null; then
57
+ SESSION_ID=""
58
+ fi
59
+ fi
60
+ fi
61
+
32
62
  # Cache directory shared with the post-render dedupe sidecar below. We
33
63
  # need it earlier than the original code so the mtime-based fast-skip
34
64
  # can read its body file before invoking the (~800ms) switchroom CLI.
@@ -46,15 +76,15 @@ BODY_FILE="$CACHE_DIR/workspace-dynamic.${CACHE_DATE}.body"
46
76
  # Mtime-fast-skip: if BODY_FILE exists AND is newer than every workspace
47
77
  # source the renderer reads, we can emit the cached body and skip the
48
78
  # ~800ms `switchroom workspace render` invocation entirely. The renderer
49
- # reads MEMORY.md, HEARTBEAT.md, today's daily, yesterday's daily — see
79
+ # reads MEMORY.md, today's daily, yesterday's daily — see
50
80
  # `loadDynamicBootstrapFiles` in src/agents/workspace.ts.
51
81
  #
52
82
  # Skip semantics: a source file that doesn't exist contributes a "very
53
83
  # old" mtime (epoch-0 via stat fallback), which never invalidates the
54
84
  # cache. A source file that's been updated since BODY_FILE's mtime
55
85
  # triggers a fresh render. Forensics measured this fast-path saving
56
- # ~825ms on the common case (chat turns where MEMORY/HEARTBEAT haven't
57
- # changed since the last turn).
86
+ # ~825ms on the common case (chat turns where MEMORY hasn't changed
87
+ # since the last turn).
58
88
  #
59
89
  # Resolve the agent's workspace dir. Switchroom uses
60
90
  # `~/.switchroom/agents/<name>/workspace/` by default. We avoid invoking
@@ -75,7 +105,7 @@ if [ -f "$BODY_FILE" ]; then
75
105
  # missing), emit the cache and exit.
76
106
  body_mtime=$(stat -c '%Y' "$BODY_FILE" 2>/dev/null || echo 0)
77
107
  newest_src_mtime=0
78
- for src in "$WS_DIR/MEMORY.md" "$WS_DIR/HEARTBEAT.md" "$TODAY_FILE" "$YESTERDAY_FILE"; do
108
+ for src in "$WS_DIR/MEMORY.md" "$TODAY_FILE" "$YESTERDAY_FILE"; do
79
109
  if [ -f "$src" ]; then
80
110
  src_mtime=$(stat -c '%Y' "$src" 2>/dev/null || echo 0)
81
111
  if [ "$src_mtime" -gt "$newest_src_mtime" ]; then
@@ -84,18 +114,38 @@ if [ -f "$BODY_FILE" ]; then
84
114
  fi
85
115
  done
86
116
  if [ "$body_mtime" -gt "$newest_src_mtime" ]; then
87
- # Fast path: every source is older than the cached body. Emit and
88
- # skip the renderer entirely. Saves ~800ms cold-start of the
89
- # switchroom CLI plus the actual render work.
117
+ # Fast path: every source is older than the cached body.
118
+ # In inject-on-change mode, also check the session-state file — if the
119
+ # session_id matches the last-emitted session AND the hash matches, we
120
+ # can suppress entirely (model already has this content in context).
121
+ if [ "$INJECT_ON_CHANGE" = "1" ] && [ -n "$SESSION_ID" ]; then
122
+ SESSION_STATE_DIR="${TELEGRAM_STATE_DIR:-${HOME:-/tmp}/.claude/switchroom-hookcache}/.hook-state"
123
+ SESSION_STATE_FILE="$SESSION_STATE_DIR/ws-dynamic.$SESSION_ID"
124
+ if [ -f "$SESSION_STATE_FILE" ]; then
125
+ RECORDED_HASH=$(head -1 "$SESSION_STATE_FILE" 2>/dev/null || echo "")
126
+ # Read hash from body cache to compare with session state.
127
+ CACHED_HASH=$(head -1 "$CACHE_FILE" 2>/dev/null || echo "")
128
+ if [ -n "$CACHED_HASH" ] && [ "$CACHED_HASH" = "$RECORDED_HASH" ]; then
129
+ # Same session, same content — suppress injection.
130
+ exit 0
131
+ fi
132
+ fi
133
+ # Different session or hash changed — emit, then update session state.
134
+ mkdir -p "$SESSION_STATE_DIR" 2>/dev/null || true
135
+ CACHED_HASH_FOR_STATE=$(head -1 "$CACHE_FILE" 2>/dev/null || echo "")
136
+ if [ -n "$CACHED_HASH_FOR_STATE" ]; then
137
+ printf '%s\n%s\n' "$CACHED_HASH_FOR_STATE" "$SESSION_ID" > "$SESSION_STATE_FILE" 2>/dev/null || true
138
+ fi
139
+ fi
90
140
  cat "$BODY_FILE"
91
141
  exit 0
92
142
  fi
93
143
  fi
94
144
 
95
- # Render the dynamic workspace files (MEMORY.md, today/yesterday daily,
96
- # HEARTBEAT.md). The render command exits 0 and returns empty string if the
97
- # workspace doesn't exist or all dynamic files are missing/empty, so no
98
- # special-casing needed here.
145
+ # Render the dynamic workspace files (MEMORY.md, today/yesterday daily).
146
+ # The render command exits 0 and returns empty string if the workspace
147
+ # doesn't exist or all dynamic files are missing/empty, so no special-casing
148
+ # needed here.
99
149
  #
100
150
  # --warning-mode off: truncation warnings go to the stable render (where they
101
151
  # can surface during scaffold/reconcile), not the per-turn path where they'd
@@ -107,8 +157,8 @@ fi
107
157
  WS_DYNAMIC=$(timeout 3 switchroom workspace render "$AGENT_NAME" --dynamic --warning-mode off 2>/dev/null || true)
108
158
 
109
159
  # Empty render → emit nothing AND do NOT cache the empty body. Caching an
110
- # empty body would re-emit empty forever even after MEMORY/HEARTBEAT come
111
- # back online, defeating the whole purpose of the hook.
160
+ # empty body would re-emit empty forever even after MEMORY comes back online,
161
+ # defeating the whole purpose of the hook.
112
162
  if [ -z "$WS_DYNAMIC" ]; then
113
163
  exit 0
114
164
  fi
@@ -126,7 +176,44 @@ if [ -f "$CACHE_FILE" ]; then
126
176
  OLD_HASH=$(head -1 "$CACHE_FILE" 2>/dev/null || echo "")
127
177
  fi
128
178
 
179
+ # Session-state helper (shared by both the hash-match and hash-changed paths).
180
+ _ws_record_session_state() {
181
+ local hash="$1" sid="$2"
182
+ if [ -n "$hash" ] && [ -n "$sid" ]; then
183
+ local sdir="${TELEGRAM_STATE_DIR:-${HOME:-/tmp}/.claude/switchroom-hookcache}/.hook-state"
184
+ [ -d "$sdir" ] || mkdir -p "$sdir" 2>/dev/null || true
185
+ if [ -d "$sdir" ]; then
186
+ printf '%s\n%s\n' "$hash" "$sid" > "$sdir/ws-dynamic.$sid" 2>/dev/null || true
187
+ # Keep at most 5 ws-dynamic state files to bound growth.
188
+ # shellcheck disable=SC2012
189
+ local old_count
190
+ old_count=$(ls -1 "$sdir"/ws-dynamic.* 2>/dev/null | grep -cv "ws-dynamic\.$sid$" || true)
191
+ if [ "$old_count" -gt 5 ]; then
192
+ ls -1t "$sdir"/ws-dynamic.* 2>/dev/null \
193
+ | grep -v "ws-dynamic\.$sid$" \
194
+ | tail -n +6 \
195
+ | xargs rm -f 2>/dev/null || true
196
+ fi
197
+ fi
198
+ fi
199
+ }
200
+
129
201
  if [ -n "$NEW_HASH" ] && [ "$NEW_HASH" = "$OLD_HASH" ] && [ -f "$BODY_FILE" ]; then
202
+ # Content unchanged since last render. In inject-on-change mode, check if
203
+ # we already injected this content in the current session — if so, suppress.
204
+ if [ "$INJECT_ON_CHANGE" = "1" ] && [ -n "$SESSION_ID" ]; then
205
+ SESSION_STATE_DIR="${TELEGRAM_STATE_DIR:-${HOME:-/tmp}/.claude/switchroom-hookcache}/.hook-state"
206
+ SESSION_STATE_FILE="$SESSION_STATE_DIR/ws-dynamic.$SESSION_ID"
207
+ if [ -f "$SESSION_STATE_FILE" ]; then
208
+ RECORDED_HASH=$(head -1 "$SESSION_STATE_FILE" 2>/dev/null || echo "")
209
+ if [ "$RECORDED_HASH" = "$NEW_HASH" ]; then
210
+ # Same session, same content — suppress injection.
211
+ exit 0
212
+ fi
213
+ fi
214
+ # New session or session state mismatch — emit and record.
215
+ _ws_record_session_state "$NEW_HASH" "$SESSION_ID"
216
+ fi
130
217
  cat "$BODY_FILE"
131
218
  else
132
219
  # Refresh sidecar: write hash + body, then echo body. Touch the body
@@ -135,6 +222,9 @@ else
135
222
  if [ -n "$NEW_HASH" ]; then
136
223
  printf '%s\n' "$NEW_HASH" > "$CACHE_FILE" 2>/dev/null || true
137
224
  printf '%s\n' "$WS_DYNAMIC" > "$BODY_FILE" 2>/dev/null || true
225
+ if [ "$INJECT_ON_CHANGE" = "1" ] && [ -n "$SESSION_ID" ]; then
226
+ _ws_record_session_state "$NEW_HASH" "$SESSION_ID"
227
+ fi
138
228
  fi
139
229
  printf '%s\n' "$WS_DYNAMIC"
140
230
  fi
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # UserPromptSubmit hook for stable workspace content (AGENTS.md, SOUL.md,
3
- # USER.md, IDENTITY.md, TOOLS.md, HEARTBEAT.md).
3
+ # USER.md, IDENTITY.md, TOOLS.md).
4
4
  #
5
5
  # Used only when channels.telegram.hotReloadStable is true. In that mode,
6
6
  # the stable workspace render moves from a session-start bake into
@@ -34,7 +34,7 @@ if ! command -v switchroom >/dev/null 2>&1; then
34
34
  fi
35
35
 
36
36
  # Render the stable workspace files (AGENTS.md, SOUL.md, IDENTITY.md, USER.md,
37
- # TOOLS.md, HEARTBEAT.md). The render command exits 0 and returns empty string
37
+ # TOOLS.md). The render command exits 0 and returns empty string
38
38
  # if the workspace doesn't exist or all stable files are missing/empty, so no
39
39
  # special-casing needed here.
40
40
  #
@@ -11090,7 +11090,8 @@ var TelegramChannelSchema = exports_external.object({
11090
11090
  stream_mode: exports_external.enum(["pty", "checklist"]).optional().describe("How live progress is streamed to Telegram during a turn. " + "'pty' (default) surfaces text snapshots of Claude Code's TUI — " + "compatible but can flicker as Ink re-renders. 'checklist' drives " + "a structured progress card from session-tail events — stable " + "order, per-tool status emojis, fires only on semantic transitions."),
11091
11091
  stream_throttle_ms: exports_external.number().int().nonnegative().optional().describe("Throttle window in ms between successive stream edits (or " + "sendMessageDraft tics) during a turn. Lower = more responsive " + "stream, higher = fewer API calls. Floored at 250 by draft-stream " + "itself. Default 300 for draft transport (DMs) and 1000 for " + "message transport (groups/forums). Override per-agent if a " + "particular agent needs snappier or quieter streaming."),
11092
11092
  clear_status_on_completion: exports_external.boolean().optional().describe("When true, the live activity/status feed (the in-place 'what it's " + "doing' message — Reading X, Searching the web for Y, …) is DELETED " + "when the turn's final answer lands, so only the reply remains. " + "Default false: the status message is left in the chat as a record " + "(its last step marked done) — no post-then-delete. Per-agent " + "override; cascades defaults → profile → agent (per-key)."),
11093
- hotReloadStable: exports_external.boolean().optional().describe("If true, the stable workspace prefix (AGENTS.md, SOUL.md, USER.md, " + "IDENTITY.md, TOOLS.md, HEARTBEAT.md) is re-injected on every turn via " + "the UserPromptSubmit hook instead of baked into --append-system-prompt " + "at session start. Lets workspace edits propagate without a restart. " + "Costs ~5-10% per-turn latency/spend since the stable prefix is no " + "longer prompt-cached."),
11093
+ hotReloadStable: exports_external.boolean().optional().describe("If true, the stable workspace prefix (AGENTS.md, SOUL.md, USER.md, " + "IDENTITY.md, TOOLS.md) is re-injected on every turn via " + "the UserPromptSubmit hook instead of baked into --append-system-prompt " + "at session start. Lets workspace edits propagate without a restart. " + "Costs ~5-10% per-turn latency/spend since the stable prefix is no " + "longer prompt-cached."),
11094
+ inject_on_change: exports_external.boolean().optional().describe("Context-efficiency gate for per-turn hook injection (default true). " + "When true (the default), the turn-pacing directive and dynamic " + "workspace content are only re-emitted when their content changes or " + "the session_id changes — suppressing redundant injection that " + "otherwise triples compaction frequency. Set to false to revert to " + "the legacy always-emit behaviour (every turn injects the full " + "content regardless of whether it changed)."),
11094
11095
  orphan_promotion_ms: exports_external.number().int().nonnegative().optional().describe("How long (ms) a parent turn waits for a sub-agent JSONL watcher " + "to deliver sub_agent_started before the heartbeat promotes the spawn " + "to a synthesised 'running' row. Default 5000. Set to 0 to disable " + "orphan promotion entirely."),
11095
11096
  cold_sub_agent_threshold_ms: exports_external.number().int().nonnegative().optional().describe("JSONL-cold threshold (ms). When a running sub-agent emits no events " + "for this long, the heartbeat synthesises a turn_end for it so the " + "deferred-completion path can proceed. Default 30000. Set to 0 to " + "disable the synthetic close."),
11096
11097
  deferred_completion_timeout_ms: exports_external.number().int().nonnegative().optional().describe("Force-close timeout (ms) for deferred sub-agent completion. After " + "the parent turn_end arrives while sub-agents are still running, the " + "card is force-closed after this many ms even if sub-agents never " + "finish. Watcher-disconnect safety net. Default 180000 (3 min)."),
@@ -11090,7 +11090,8 @@ var TelegramChannelSchema = exports_external.object({
11090
11090
  stream_mode: exports_external.enum(["pty", "checklist"]).optional().describe("How live progress is streamed to Telegram during a turn. " + "'pty' (default) surfaces text snapshots of Claude Code's TUI — " + "compatible but can flicker as Ink re-renders. 'checklist' drives " + "a structured progress card from session-tail events — stable " + "order, per-tool status emojis, fires only on semantic transitions."),
11091
11091
  stream_throttle_ms: exports_external.number().int().nonnegative().optional().describe("Throttle window in ms between successive stream edits (or " + "sendMessageDraft tics) during a turn. Lower = more responsive " + "stream, higher = fewer API calls. Floored at 250 by draft-stream " + "itself. Default 300 for draft transport (DMs) and 1000 for " + "message transport (groups/forums). Override per-agent if a " + "particular agent needs snappier or quieter streaming."),
11092
11092
  clear_status_on_completion: exports_external.boolean().optional().describe("When true, the live activity/status feed (the in-place 'what it's " + "doing' message — Reading X, Searching the web for Y, …) is DELETED " + "when the turn's final answer lands, so only the reply remains. " + "Default false: the status message is left in the chat as a record " + "(its last step marked done) — no post-then-delete. Per-agent " + "override; cascades defaults → profile → agent (per-key)."),
11093
- hotReloadStable: exports_external.boolean().optional().describe("If true, the stable workspace prefix (AGENTS.md, SOUL.md, USER.md, " + "IDENTITY.md, TOOLS.md, HEARTBEAT.md) is re-injected on every turn via " + "the UserPromptSubmit hook instead of baked into --append-system-prompt " + "at session start. Lets workspace edits propagate without a restart. " + "Costs ~5-10% per-turn latency/spend since the stable prefix is no " + "longer prompt-cached."),
11093
+ hotReloadStable: exports_external.boolean().optional().describe("If true, the stable workspace prefix (AGENTS.md, SOUL.md, USER.md, " + "IDENTITY.md, TOOLS.md) is re-injected on every turn via " + "the UserPromptSubmit hook instead of baked into --append-system-prompt " + "at session start. Lets workspace edits propagate without a restart. " + "Costs ~5-10% per-turn latency/spend since the stable prefix is no " + "longer prompt-cached."),
11094
+ inject_on_change: exports_external.boolean().optional().describe("Context-efficiency gate for per-turn hook injection (default true). " + "When true (the default), the turn-pacing directive and dynamic " + "workspace content are only re-emitted when their content changes or " + "the session_id changes — suppressing redundant injection that " + "otherwise triples compaction frequency. Set to false to revert to " + "the legacy always-emit behaviour (every turn injects the full " + "content regardless of whether it changed)."),
11094
11095
  orphan_promotion_ms: exports_external.number().int().nonnegative().optional().describe("How long (ms) a parent turn waits for a sub-agent JSONL watcher " + "to deliver sub_agent_started before the heartbeat promotes the spawn " + "to a synthesised 'running' row. Default 5000. Set to 0 to disable " + "orphan promotion entirely."),
11095
11096
  cold_sub_agent_threshold_ms: exports_external.number().int().nonnegative().optional().describe("JSONL-cold threshold (ms). When a running sub-agent emits no events " + "for this long, the heartbeat synthesises a turn_end for it so the " + "deferred-completion path can proceed. Default 30000. Set to 0 to " + "disable the synthetic close."),
11096
11097
  deferred_completion_timeout_ms: exports_external.number().int().nonnegative().optional().describe("Force-close timeout (ms) for deferred sub-agent completion. After " + "the parent turn_end arrives while sub-agents are still running, the " + "card is force-closed after this many ms even if sub-agents never " + "finish. Watcher-disconnect safety net. Default 180000 (3 min)."),
@@ -11838,7 +11838,8 @@ var TelegramChannelSchema = exports_external.object({
11838
11838
  stream_mode: exports_external.enum(["pty", "checklist"]).optional().describe("How live progress is streamed to Telegram during a turn. " + "'pty' (default) surfaces text snapshots of Claude Code's TUI \u2014 " + "compatible but can flicker as Ink re-renders. 'checklist' drives " + "a structured progress card from session-tail events \u2014 stable " + "order, per-tool status emojis, fires only on semantic transitions."),
11839
11839
  stream_throttle_ms: exports_external.number().int().nonnegative().optional().describe("Throttle window in ms between successive stream edits (or " + "sendMessageDraft tics) during a turn. Lower = more responsive " + "stream, higher = fewer API calls. Floored at 250 by draft-stream " + "itself. Default 300 for draft transport (DMs) and 1000 for " + "message transport (groups/forums). Override per-agent if a " + "particular agent needs snappier or quieter streaming."),
11840
11840
  clear_status_on_completion: exports_external.boolean().optional().describe("When true, the live activity/status feed (the in-place 'what it's " + "doing' message \u2014 Reading X, Searching the web for Y, \u2026) is DELETED " + "when the turn's final answer lands, so only the reply remains. " + "Default false: the status message is left in the chat as a record " + "(its last step marked done) \u2014 no post-then-delete. Per-agent " + "override; cascades defaults \u2192 profile \u2192 agent (per-key)."),
11841
- hotReloadStable: exports_external.boolean().optional().describe("If true, the stable workspace prefix (AGENTS.md, SOUL.md, USER.md, " + "IDENTITY.md, TOOLS.md, HEARTBEAT.md) is re-injected on every turn via " + "the UserPromptSubmit hook instead of baked into --append-system-prompt " + "at session start. Lets workspace edits propagate without a restart. " + "Costs ~5-10% per-turn latency/spend since the stable prefix is no " + "longer prompt-cached."),
11841
+ hotReloadStable: exports_external.boolean().optional().describe("If true, the stable workspace prefix (AGENTS.md, SOUL.md, USER.md, " + "IDENTITY.md, TOOLS.md) is re-injected on every turn via " + "the UserPromptSubmit hook instead of baked into --append-system-prompt " + "at session start. Lets workspace edits propagate without a restart. " + "Costs ~5-10% per-turn latency/spend since the stable prefix is no " + "longer prompt-cached."),
11842
+ inject_on_change: exports_external.boolean().optional().describe("Context-efficiency gate for per-turn hook injection (default true). " + "When true (the default), the turn-pacing directive and dynamic " + "workspace content are only re-emitted when their content changes or " + "the session_id changes \u2014 suppressing redundant injection that " + "otherwise triples compaction frequency. Set to false to revert to " + "the legacy always-emit behaviour (every turn injects the full " + "content regardless of whether it changed)."),
11842
11843
  orphan_promotion_ms: exports_external.number().int().nonnegative().optional().describe("How long (ms) a parent turn waits for a sub-agent JSONL watcher " + "to deliver sub_agent_started before the heartbeat promotes the spawn " + "to a synthesised 'running' row. Default 5000. Set to 0 to disable " + "orphan promotion entirely."),
11843
11844
  cold_sub_agent_threshold_ms: exports_external.number().int().nonnegative().optional().describe("JSONL-cold threshold (ms). When a running sub-agent emits no events " + "for this long, the heartbeat synthesises a turn_end for it so the " + "deferred-completion path can proceed. Default 30000. Set to 0 to " + "disable the synthetic close."),
11844
11845
  deferred_completion_timeout_ms: exports_external.number().int().nonnegative().optional().describe("Force-close timeout (ms) for deferred sub-agent completion. After " + "the parent turn_end arrives while sub-agents are still running, the " + "card is force-closed after this many ms even if sub-agents never " + "finish. Watcher-disconnect safety net. Default 180000 (3 min)."),