switchroom 0.21.7 → 0.21.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/tmp-reaper.sh +234 -0
- package/dist/agent-scheduler/index.js +1 -1
- package/dist/auth-broker/index.js +2 -2
- package/dist/cli/notion-write-pretool.mjs +1 -1
- package/dist/cli/switchroom.js +3520 -2782
- package/dist/host-control/main.js +177 -13
- package/dist/vault/approvals/kernel-server.js +2 -2
- package/dist/vault/broker/server.js +2 -2
- package/package.json +6 -5
- package/profiles/_base/start.sh.hbs +115 -0
- package/profiles/_shared/local-time.md.hbs +6 -0
- package/profiles/default/CLAUDE.md.hbs +0 -12
- package/skills/switchroom-architecture/telegram.md +12 -10
- package/skills/switchroom-cli/SKILL.md +1 -1
- package/telegram-plugin/README.md +3 -1
- package/telegram-plugin/dist/gateway/gateway.js +1164 -547
- package/telegram-plugin/format.ts +12 -4
- package/telegram-plugin/gateway/agent-process-liveness.ts +558 -0
- package/telegram-plugin/gateway/approval-hold.ts +32 -1
- package/telegram-plugin/gateway/approval-outcome-sources.ts +274 -0
- package/telegram-plugin/gateway/bridge-dead-watchdog.ts +21 -9
- package/telegram-plugin/gateway/callback-query-handlers.ts +87 -15
- package/telegram-plugin/gateway/eval-case-proposal-inbound-builders.ts +197 -0
- package/telegram-plugin/gateway/gateway.ts +12 -10
- package/telegram-plugin/gateway/pending-inbound-buffer.ts +167 -11
- package/telegram-plugin/gateway/self-improve-proposal-wiring.test.ts +333 -0
- package/telegram-plugin/gateway/self-improve-proposal-wiring.ts +152 -3
- package/telegram-plugin/gateway/subagent-handback-marker.ts +19 -0
- package/telegram-plugin/package.json +1 -1
- package/telegram-plugin/render/code-segments.ts +38 -4
- package/telegram-plugin/render/dollar-math-guard.ts +16 -1
- package/telegram-plugin/render/ir.ts +53 -3
- package/telegram-plugin/render/parse.ts +73 -14
- package/telegram-plugin/render/render.ts +53 -15
- package/telegram-plugin/render/unsupported-token-guard.ts +45 -80
- package/telegram-plugin/rich-send.ts +22 -7
- package/telegram-plugin/shared/bot-runtime.ts +3 -2
- package/telegram-plugin/telegraph.ts +6 -4
- package/telegram-plugin/tests/agent-process-liveness.test.ts +406 -0
- package/telegram-plugin/tests/approval-hold-record.test.ts +21 -8
- package/telegram-plugin/tests/boot-resume-gateway-only-respawn.test.ts +752 -0
- package/telegram-plugin/tests/boot-resume-guard-wiring.test.ts +203 -0
- package/telegram-plugin/tests/callback-query-handlers.test.ts +143 -1
- package/telegram-plugin/tests/eval-case-proposal-inbound-builders.test.ts +144 -0
- package/telegram-plugin/tests/grammy-rich-message-types.test.ts +199 -0
- package/telegram-plugin/tests/hermes-messages-paging.test.ts +149 -0
- package/telegram-plugin/tests/hermes-session-search.test.ts +146 -0
- package/telegram-plugin/tests/pending-inbound-buffer.test.ts +443 -2
- package/telegram-plugin/tests/render/dollar-math-guard.test.ts +43 -0
- package/telegram-plugin/tests/render/guard-composition.test.ts +102 -0
- package/telegram-plugin/tests/render/parse.test.ts +30 -5
- package/telegram-plugin/tests/render/render.test.ts +9 -4
- package/telegram-plugin/tests/render/rich-render.test.ts +46 -5
- package/telegram-plugin/tests/render/tg-entity.test.ts +242 -0
- package/telegram-plugin/tests/render/unsupported-token-guard.test.ts +66 -66
- package/telegram-plugin/tests/sent-text-capture.test.ts +3 -3
- package/telegram-plugin/tests/subagent-handback-marker.test.ts +14 -0
- package/telegram-plugin/tests/telegraph.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-rich-formatting-render-dm.test.ts +17 -8
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# switchroom-tmp-reaper — bounded janitor for the agent container's /tmp tmpfs.
|
|
3
|
+
#
|
|
4
|
+
# WHY THIS EXISTS
|
|
5
|
+
# ---------------
|
|
6
|
+
# An agent container mounts /tmp as a RAM-backed tmpfs, sized by
|
|
7
|
+
# `resources.tmp_size` (src/agents/compose.ts, DEFAULT_TMP_SIZE = 2g), with
|
|
8
|
+
# Docker's default tmpfs flags — `rw,nosuid,nodev,noexec`. Two consequences:
|
|
9
|
+
#
|
|
10
|
+
# 1. It is a hard ceiling. Nothing ages out of a tmpfs; a long-running
|
|
11
|
+
# container accumulates every scratch dir any tool ever forgot to
|
|
12
|
+
# remove until writes start failing ENOSPC. Observed on a live agent:
|
|
13
|
+
# 4,462 top-level entries, 490 MiB used, of which 204 MiB was two
|
|
14
|
+
# orphaned `bun build --compile` artifacts from a single test run
|
|
15
|
+
# (fixed at source in tests/install/static-binary.test.ts, but that
|
|
16
|
+
# only fixes the leak we already found).
|
|
17
|
+
# 2. The failure is NOT localised. Once /tmp is full, everything that
|
|
18
|
+
# stages through it fails — `npm ci`, git, the claude CLI's own
|
|
19
|
+
# scratch — with errors that name the victim, never the cause.
|
|
20
|
+
#
|
|
21
|
+
# The size is deliberately NOT the lever. Raising the tmpfs converts a fast,
|
|
22
|
+
# loud failure into a slow leak that eats host RAM instead. This reaper
|
|
23
|
+
# bounds the accumulation itself.
|
|
24
|
+
#
|
|
25
|
+
# SAFETY CONTRACT (each clause is pinned by tests/tmp-reaper.test.ts)
|
|
26
|
+
# -------------------------------------------------------------------
|
|
27
|
+
# * Scope. Only direct children of the root are considered, the root
|
|
28
|
+
# itself is never removed, and every traversal is `-xdev` with no
|
|
29
|
+
# symlink dereference — so a symlink under /tmp pointing at, say,
|
|
30
|
+
# /state/agent can have the LINK reaped but never its target.
|
|
31
|
+
# * Age. An entry is a candidate only when NOTHING anywhere in its
|
|
32
|
+
# subtree has been modified or accessed within the age threshold
|
|
33
|
+
# (default 24h). A directory's own mtime does not change when a
|
|
34
|
+
# grandchild is written, so checking the entry alone would reap a
|
|
35
|
+
# live tree — the whole subtree is checked.
|
|
36
|
+
# * Open files. An entry is skipped when it, or anything beneath it, is
|
|
37
|
+
# open by any process in the container: /proc/*/fd, /proc/*/cwd,
|
|
38
|
+
# /proc/*/exe and mmapped regions from /proc/*/maps are all consulted.
|
|
39
|
+
# * Logging. Every reaped entry is logged with its size and age, and every
|
|
40
|
+
# pass emits a one-line summary — including passes that reap nothing.
|
|
41
|
+
#
|
|
42
|
+
# TUNING (all validated; garbage falls back to the default)
|
|
43
|
+
# SWITCHROOM_TMP_REAPER=0 disable entirely
|
|
44
|
+
# SWITCHROOM_TMP_REAPER_MIN_AGE_SEC=86400 age threshold (min 3600)
|
|
45
|
+
# SWITCHROOM_TMP_REAPER_INTERVAL_SEC=3600 seconds between passes
|
|
46
|
+
# SWITCHROOM_TMP_REAPER_ROOT=/tmp root (test harness only)
|
|
47
|
+
#
|
|
48
|
+
# Run standalone for a one-shot pass: tmp-reaper.sh --once
|
|
49
|
+
set -uo pipefail
|
|
50
|
+
|
|
51
|
+
REAPER_NAME="switchroom-tmp-reaper"
|
|
52
|
+
|
|
53
|
+
TMP_REAPER_ROOT="${SWITCHROOM_TMP_REAPER_ROOT:-/tmp}"
|
|
54
|
+
|
|
55
|
+
# Age below which an entry is NEVER touched. One hour is the hard floor even
|
|
56
|
+
# if an operator sets something smaller: this reaper cannot distinguish "a
|
|
57
|
+
# build wrote this five minutes ago and will read it back in ten" from
|
|
58
|
+
# garbage, and the age bound is the only thing standing between it and a
|
|
59
|
+
# live workload.
|
|
60
|
+
MIN_AGE_SEC="${SWITCHROOM_TMP_REAPER_MIN_AGE_SEC:-86400}"
|
|
61
|
+
case "$MIN_AGE_SEC" in ''|*[!0-9]*) MIN_AGE_SEC=86400;; esac
|
|
62
|
+
[ "$MIN_AGE_SEC" -lt 3600 ] && MIN_AGE_SEC=3600
|
|
63
|
+
|
|
64
|
+
INTERVAL_SEC="${SWITCHROOM_TMP_REAPER_INTERVAL_SEC:-3600}"
|
|
65
|
+
case "$INTERVAL_SEC" in ''|0|*[!0-9]*) INTERVAL_SEC=3600;; esac
|
|
66
|
+
[ "$INTERVAL_SEC" -lt 60 ] && INTERVAL_SEC=60
|
|
67
|
+
|
|
68
|
+
# Never-reap names. These are conventional tmpfs fixtures whose mtime can sit
|
|
69
|
+
# still for weeks while they are very much in use, and whose removal breaks
|
|
70
|
+
# things in ways that are hard to trace back here.
|
|
71
|
+
TMP_REAPER_KEEP_DEFAULT=".X11-unix .ICE-unix .font-unix .Test-unix .XIM-unix"
|
|
72
|
+
TMP_REAPER_KEEP="${SWITCHROOM_TMP_REAPER_KEEP:-$TMP_REAPER_KEEP_DEFAULT}"
|
|
73
|
+
|
|
74
|
+
_log() { printf '[%s] %s %s\n' "$REAPER_NAME" "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$*"; }
|
|
75
|
+
|
|
76
|
+
# Refuse to operate on a root that is not a plain, existing directory, or that
|
|
77
|
+
# is the filesystem root. The default is /tmp and the override exists for the
|
|
78
|
+
# test harness; this guard is what keeps a mis-set override from being
|
|
79
|
+
# catastrophic rather than merely wrong.
|
|
80
|
+
_root_ok() {
|
|
81
|
+
local r="$1"
|
|
82
|
+
[ -n "$r" ] || { _log "refusing: empty root"; return 1; }
|
|
83
|
+
[ "$r" != "/" ] || { _log "refusing: root is /"; return 1; }
|
|
84
|
+
case "$r" in
|
|
85
|
+
/*) ;;
|
|
86
|
+
*) _log "refusing: root '$r' is not absolute"; return 1;;
|
|
87
|
+
esac
|
|
88
|
+
case "$r" in
|
|
89
|
+
*..*) _log "refusing: root '$r' contains '..'"; return 1;;
|
|
90
|
+
esac
|
|
91
|
+
[ -d "$r" ] || { _log "refusing: root '$r' is not a directory"; return 1; }
|
|
92
|
+
[ ! -L "$r" ] || { _log "refusing: root '$r' is a symlink"; return 1; }
|
|
93
|
+
return 0
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Emit every path under $1 that some process in this container currently has
|
|
97
|
+
# open — as an fd, as its cwd, as its executable, or as an mmapped region.
|
|
98
|
+
# Unreadable /proc entries (a process owned by another uid, or one that exited
|
|
99
|
+
# mid-scan) are skipped: they can only cause us to reap LESS, never more.
|
|
100
|
+
#
|
|
101
|
+
# Cost matters here: a busy container has hundreds of processes and thousands
|
|
102
|
+
# of fds, so this uses ONE `find` (with -printf '%l' to read the link targets
|
|
103
|
+
# in-process) and ONE `grep`+`awk` pipeline over the maps files, rather than a
|
|
104
|
+
# readlink fork per fd. A per-fd fork loop measured multiple seconds per pass.
|
|
105
|
+
#
|
|
106
|
+
# CRITICAL: neither traversal may ABORT on an unreadable input. `find` reports
|
|
107
|
+
# a vanished/denied start point and keeps going, which is what we need. `awk`
|
|
108
|
+
# does NOT: the image ships mawk 1.3.4, which stops at the FIRST file it cannot
|
|
109
|
+
# open and never reads the rest of its argument list. Handing mawk the raw
|
|
110
|
+
# /proc/[0-9]*/maps glob therefore meant that one PID exiting between bash's
|
|
111
|
+
# glob expansion and mawk's serial open (ESRCH), or one EPERM, silently
|
|
112
|
+
# discarded every LATER process's mappings — a fail-OPEN in a safety guard,
|
|
113
|
+
# after which the reaper would delete a tree a live process still had mapped.
|
|
114
|
+
# So `grep` (GNU grep continues past unreadable files, exit 2) does the
|
|
115
|
+
# multi-file read and `awk` only ever sees a single stdin stream.
|
|
116
|
+
#
|
|
117
|
+
# `awk substr` rather than `grep -o`: a maps line is
|
|
118
|
+
# "addr perms offset dev inode <pathname>", so the path runs to end-of-line and
|
|
119
|
+
# MAY CONTAIN SPACES. `grep -oE "$root/[^ ]*"` would truncate
|
|
120
|
+
# "/tmp/my dir/lib.so" to "/tmp/my", which no longer prefix-matches the entry
|
|
121
|
+
# in the caller below — fail-open again, for any /tmp entry with a space.
|
|
122
|
+
_open_paths() {
|
|
123
|
+
local root="$1"
|
|
124
|
+
find /proc/[0-9]*/fd /proc/[0-9]*/cwd /proc/[0-9]*/exe \
|
|
125
|
+
-maxdepth 1 -type l -printf '%l\n' 2>/dev/null \
|
|
126
|
+
| grep -F -- "$root/" 2>/dev/null
|
|
127
|
+
# mmapped files (a running binary, a memory-mapped DB) do not appear as an
|
|
128
|
+
# fd once mapped and the fd is closed, but are absolutely still in use.
|
|
129
|
+
grep -haF -- "$root/" /proc/[0-9]*/maps 2>/dev/null \
|
|
130
|
+
| awk -v r="$root/" '{ p=index($0, r); if (p) print substr($0, p) }'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
_human() {
|
|
134
|
+
local b="${1:-0}"
|
|
135
|
+
if [ "$b" -ge 1073741824 ] 2>/dev/null; then echo "$((b / 1073741824))G"
|
|
136
|
+
elif [ "$b" -ge 1048576 ] 2>/dev/null; then echo "$((b / 1048576))M"
|
|
137
|
+
elif [ "$b" -ge 1024 ] 2>/dev/null; then echo "$((b / 1024))K"
|
|
138
|
+
else echo "${b}B"; fi
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# One pass. Returns 0 always; a failure to remove one entry must not abort
|
|
142
|
+
# the pass (the next entry may be the one actually holding the space).
|
|
143
|
+
tmp_reaper_pass() {
|
|
144
|
+
local root="$TMP_REAPER_ROOT"
|
|
145
|
+
_root_ok "$root" || return 1
|
|
146
|
+
# Canonicalise. /proc/*/fd link targets are already fully resolved, so an
|
|
147
|
+
# un-canonicalised root with a symlinked ancestor would never prefix-match
|
|
148
|
+
# them and the open-file check would silently pass everything through —
|
|
149
|
+
# the guard would look present and do nothing.
|
|
150
|
+
root=$(readlink -f -- "$root" 2>/dev/null) || root="$TMP_REAPER_ROOT"
|
|
151
|
+
[ -n "$root" ] || root="$TMP_REAPER_ROOT"
|
|
152
|
+
_root_ok "$root" || return 1
|
|
153
|
+
|
|
154
|
+
local now cutoff
|
|
155
|
+
now=$(date +%s)
|
|
156
|
+
cutoff=$((now - MIN_AGE_SEC))
|
|
157
|
+
|
|
158
|
+
local open_list
|
|
159
|
+
open_list=$(_open_paths "$root")
|
|
160
|
+
|
|
161
|
+
local reaped=0 freed=0 kept_open=0 kept_young=0 kept_pinned=0 failed=0
|
|
162
|
+
local entry base
|
|
163
|
+
|
|
164
|
+
while IFS= read -r -d '' entry; do
|
|
165
|
+
base="${entry##*/}"
|
|
166
|
+
|
|
167
|
+
# 1. Never-reap list.
|
|
168
|
+
local pinned=0 k
|
|
169
|
+
for k in $TMP_REAPER_KEEP; do
|
|
170
|
+
[ "$base" = "$k" ] && { pinned=1; break; }
|
|
171
|
+
done
|
|
172
|
+
if [ "$pinned" -eq 1 ]; then kept_pinned=$((kept_pinned + 1)); continue; fi
|
|
173
|
+
|
|
174
|
+
# 2. Age — nothing ANYWHERE in the subtree may be newer than the cutoff,
|
|
175
|
+
# by mtime or atime. `-print -quit` stops at the first offender.
|
|
176
|
+
local young
|
|
177
|
+
young=$(find "$entry" -xdev \( -newermt "@$cutoff" -o -newerat "@$cutoff" \) \
|
|
178
|
+
-print -quit 2>/dev/null)
|
|
179
|
+
if [ -n "$young" ]; then kept_young=$((kept_young + 1)); continue; fi
|
|
180
|
+
|
|
181
|
+
# 3. Open by a live process (the entry itself, or anything under it).
|
|
182
|
+
local is_open=0 p
|
|
183
|
+
while IFS= read -r p; do
|
|
184
|
+
[ -n "$p" ] || continue
|
|
185
|
+
if [ "$p" = "$entry" ] || [ "${p#"$entry"/}" != "$p" ]; then
|
|
186
|
+
is_open=1; break
|
|
187
|
+
fi
|
|
188
|
+
done <<< "$open_list"
|
|
189
|
+
if [ "$is_open" -eq 1 ]; then kept_open=$((kept_open + 1)); continue; fi
|
|
190
|
+
|
|
191
|
+
# Size and age for the log line, computed BEFORE removal.
|
|
192
|
+
local bytes mtime age_h
|
|
193
|
+
bytes=$(du -sxb -- "$entry" 2>/dev/null | cut -f1)
|
|
194
|
+
case "$bytes" in ''|*[!0-9]*) bytes=0;; esac
|
|
195
|
+
mtime=$(stat -c %Y -- "$entry" 2>/dev/null || echo "$now")
|
|
196
|
+
age_h=$(( (now - mtime) / 3600 ))
|
|
197
|
+
|
|
198
|
+
if rm -rf --one-file-system --preserve-root -- "$entry" 2>/dev/null; then
|
|
199
|
+
reaped=$((reaped + 1))
|
|
200
|
+
freed=$((freed + bytes))
|
|
201
|
+
_log "reaped $entry ($(_human "$bytes"), idle ${age_h}h)"
|
|
202
|
+
else
|
|
203
|
+
failed=$((failed + 1))
|
|
204
|
+
_log "could NOT remove $entry ($(_human "$bytes"), idle ${age_h}h)"
|
|
205
|
+
fi
|
|
206
|
+
done < <(find "$root" -xdev -mindepth 1 -maxdepth 1 -print0 2>/dev/null)
|
|
207
|
+
|
|
208
|
+
_log "pass complete on $root: reaped=$reaped freed=$(_human "$freed")" \
|
|
209
|
+
"kept_young=$kept_young kept_open=$kept_open kept_pinned=$kept_pinned" \
|
|
210
|
+
"failed=$failed min_age=${MIN_AGE_SEC}s"
|
|
211
|
+
return 0
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
tmp_reaper_main() {
|
|
215
|
+
if [ "${SWITCHROOM_TMP_REAPER:-1}" = "0" ]; then
|
|
216
|
+
_log "disabled via SWITCHROOM_TMP_REAPER=0 — not starting"
|
|
217
|
+
return 0
|
|
218
|
+
fi
|
|
219
|
+
if [ "${1:-}" = "--once" ]; then
|
|
220
|
+
tmp_reaper_pass
|
|
221
|
+
return $?
|
|
222
|
+
fi
|
|
223
|
+
_log "starting: root=$TMP_REAPER_ROOT min_age=${MIN_AGE_SEC}s interval=${INTERVAL_SEC}s"
|
|
224
|
+
while true; do
|
|
225
|
+
tmp_reaper_pass
|
|
226
|
+
sleep "$INTERVAL_SEC"
|
|
227
|
+
done
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# Only auto-run when EXECUTED. Sourcing (the test harness, and any future
|
|
231
|
+
# caller that wants a single pass inline) gets the functions and nothing else.
|
|
232
|
+
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
|
233
|
+
tmp_reaper_main "$@"
|
|
234
|
+
fi
|
|
@@ -11738,7 +11738,7 @@ var AgentSchema = exports_external.object({
|
|
|
11738
11738
|
repos: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9-]*$/, "Repo slug must be kebab-case ASCII: start with a lowercase letter or digit, contain only lowercase letters, digits, and hyphens"), exports_external.object({
|
|
11739
11739
|
url: exports_external.string().min(1).describe("Git remote URL for the repo (e.g. 'git@github.com:org/repo.git' or " + "'https://github.com/org/repo.git'). Used verbatim for git clone."),
|
|
11740
11740
|
branch_default: exports_external.string().optional().describe("Default branch to track (defaults to the remote's HEAD, typically 'main'). " + "The per-agent branch 'agent/<agentName>/main' fast-forwards to this branch " + "when the worktree is clean on session start.")
|
|
11741
|
-
})).optional().describe("Repos this agent operates on. Switchroom provisions a dedicated
|
|
11741
|
+
})).optional().describe("Repos this agent operates on. Switchroom provisions a dedicated standing tree for " + "each repo at <agentDir>/work/<slug>/ on branch agent/<agentName>/main — an " + "independent clone (own refs/stash, hardlinked objects) seeded from a shared bare " + "clone at ~/.switchroom/repos/<slug>.git. The tree's path is injected " + "into the agent's environment as SWITCHROOM_REPO_<SLUG_UPPER>. " + "Agents without this field continue to work unchanged."),
|
|
11742
11742
|
experimental: exports_external.object({
|
|
11743
11743
|
legacy_pty: exports_external.boolean().optional().describe("Opt out of the default tmux supervisor (#725) and run the agent " + "under the legacy PTY supervisor instead. Default: false."),
|
|
11744
11744
|
legacy_autoaccept_expect: exports_external.boolean().optional().describe("Opt the autoaccept gateway back into the legacy expect-script " + "behaviour instead of the tmux send-keys path. Default: false.")
|
|
@@ -11763,7 +11763,7 @@ var init_schema = __esm(() => {
|
|
|
11763
11763
|
repos: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9-]*$/, "Repo slug must be kebab-case ASCII: start with a lowercase letter or digit, contain only lowercase letters, digits, and hyphens"), exports_external.object({
|
|
11764
11764
|
url: exports_external.string().min(1).describe("Git remote URL for the repo (e.g. 'git@github.com:org/repo.git' or " + "'https://github.com/org/repo.git'). Used verbatim for git clone."),
|
|
11765
11765
|
branch_default: exports_external.string().optional().describe("Default branch to track (defaults to the remote's HEAD, typically 'main'). " + "The per-agent branch 'agent/<agentName>/main' fast-forwards to this branch " + "when the worktree is clean on session start.")
|
|
11766
|
-
})).optional().describe("Repos this agent operates on. Switchroom provisions a dedicated
|
|
11766
|
+
})).optional().describe("Repos this agent operates on. Switchroom provisions a dedicated standing tree for " + "each repo at <agentDir>/work/<slug>/ on branch agent/<agentName>/main — an " + "independent clone (own refs/stash, hardlinked objects) seeded from a shared bare " + "clone at ~/.switchroom/repos/<slug>.git. The tree's path is injected " + "into the agent's environment as SWITCHROOM_REPO_<SLUG_UPPER>. " + "Agents without this field continue to work unchanged."),
|
|
11767
11767
|
experimental: exports_external.object({
|
|
11768
11768
|
legacy_pty: exports_external.boolean().optional().describe("Opt out of the default tmux supervisor (#725) and run the agent " + "under the legacy PTY supervisor instead. Default: false."),
|
|
11769
11769
|
legacy_autoaccept_expect: exports_external.boolean().optional().describe("Opt the autoaccept gateway back into the legacy expect-script " + "behaviour instead of the tmux send-keys path. Default: false.")
|
|
@@ -19111,7 +19111,7 @@ import_handlebars.default.registerHelper("isNumber", (value) => {
|
|
|
19111
19111
|
return typeof value === "number" && Number.isFinite(value);
|
|
19112
19112
|
});
|
|
19113
19113
|
var SHARED_FRAGMENTS_DIR = resolve5(PROFILES_ROOT, "_shared");
|
|
19114
|
-
var SHARED_FRAGMENTS = ["vault-protocol", "agent-self-service", "execution-discipline", "reply-discipline", "dev-protocol"];
|
|
19114
|
+
var SHARED_FRAGMENTS = ["vault-protocol", "agent-self-service", "execution-discipline", "reply-discipline", "dev-protocol", "local-time"];
|
|
19115
19115
|
for (const name of SHARED_FRAGMENTS) {
|
|
19116
19116
|
const fragPath = join2(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
|
|
19117
19117
|
if (existsSync5(fragPath)) {
|
|
@@ -12499,7 +12499,7 @@ var AgentSchema = exports_external.object({
|
|
|
12499
12499
|
repos: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9-]*$/, "Repo slug must be kebab-case ASCII: start with a lowercase letter or digit, contain only lowercase letters, digits, and hyphens"), exports_external.object({
|
|
12500
12500
|
url: exports_external.string().min(1).describe("Git remote URL for the repo (e.g. 'git@github.com:org/repo.git' or " + "'https://github.com/org/repo.git'). Used verbatim for git clone."),
|
|
12501
12501
|
branch_default: exports_external.string().optional().describe("Default branch to track (defaults to the remote's HEAD, typically 'main'). " + "The per-agent branch 'agent/<agentName>/main' fast-forwards to this branch " + "when the worktree is clean on session start.")
|
|
12502
|
-
})).optional().describe("Repos this agent operates on. Switchroom provisions a dedicated
|
|
12502
|
+
})).optional().describe("Repos this agent operates on. Switchroom provisions a dedicated standing tree for " + "each repo at <agentDir>/work/<slug>/ on branch agent/<agentName>/main \u2014 an " + "independent clone (own refs/stash, hardlinked objects) seeded from a shared bare " + "clone at ~/.switchroom/repos/<slug>.git. The tree's path is injected " + "into the agent's environment as SWITCHROOM_REPO_<SLUG_UPPER>. " + "Agents without this field continue to work unchanged."),
|
|
12503
12503
|
experimental: exports_external.object({
|
|
12504
12504
|
legacy_pty: exports_external.boolean().optional().describe("Opt out of the default tmux supervisor (#725) and run the agent " + "under the legacy PTY supervisor instead. Default: false."),
|
|
12505
12505
|
legacy_autoaccept_expect: exports_external.boolean().optional().describe("Opt the autoaccept gateway back into the legacy expect-script " + "behaviour instead of the tmux send-keys path. Default: false.")
|