switchroom 0.19.42 → 0.19.43
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 +1 -1
- package/dist/auth-broker/index.js +32 -7
- package/dist/cli/notion-write-pretool.mjs +1 -1
- package/dist/cli/switchroom.js +1187 -669
- package/dist/host-control/main.js +205 -28
- package/dist/vault/approvals/kernel-server.js +32 -7
- package/dist/vault/broker/server.js +32 -7
- package/package.json +3 -3
- package/profiles/_base/start.sh.hbs +82 -4
- package/telegram-plugin/bridge/bridge.ts +1 -1
- package/telegram-plugin/card-layout.ts +63 -1
- package/telegram-plugin/dist/bridge/bridge.js +4 -1
- package/telegram-plugin/dist/gateway/gateway.js +1225 -614
- package/telegram-plugin/dist/server.js +5 -2
- package/telegram-plugin/flood-429-ledger.ts +5 -3
- package/telegram-plugin/flood-circuit-breaker.ts +11 -3
- package/telegram-plugin/format.ts +223 -13
- package/telegram-plugin/gateway/boot-card.ts +7 -2
- package/telegram-plugin/gateway/flood-reply-queue.ts +5 -0
- package/telegram-plugin/gateway/gateway.ts +49 -51
- package/telegram-plugin/gateway/handback-orphan-recovery.ts +69 -0
- package/telegram-plugin/gateway/handback-preturn-signal.ts +170 -32
- package/telegram-plugin/gateway/ipc-protocol.ts +31 -3
- package/telegram-plugin/gateway/ipc-server.ts +13 -4
- package/telegram-plugin/gateway/obligation-store.ts +55 -0
- package/telegram-plugin/gateway/outbound-send-path.ts +127 -11
- package/telegram-plugin/gateway/outbox-sweep.ts +371 -61
- package/telegram-plugin/gateway/rollout-status-edit.ts +175 -0
- package/telegram-plugin/gateway/stream-render.ts +5 -0
- package/telegram-plugin/gateway/update-announce.ts +11 -4
- package/telegram-plugin/hooks/audience-classify.d.mts +50 -0
- package/telegram-plugin/hooks/audience-classify.mjs +364 -0
- package/telegram-plugin/hooks/silent-end-interrupt-stop.mjs +86 -3
- package/telegram-plugin/hooks/silent-end-scan.mjs +80 -4
- package/telegram-plugin/outbox.ts +103 -2
- package/telegram-plugin/render/rich-render.ts +10 -2
- package/telegram-plugin/reply-quote.ts +150 -0
- package/telegram-plugin/retry-api-call.ts +254 -11
- package/telegram-plugin/shared/bot-runtime.ts +69 -34
- package/telegram-plugin/shared/gw-trace-gate.ts +14 -2
- package/telegram-plugin/silent-end.ts +34 -2
- package/telegram-plugin/status-no-truncate.ts +31 -16
- package/telegram-plugin/stream-controller.ts +44 -9
- package/telegram-plugin/stream-reply-handler.ts +5 -3
- package/telegram-plugin/tests/boot-card-render.test.ts +2 -1
- package/telegram-plugin/tests/card-budget-never-overflows.test.ts +165 -0
- package/telegram-plugin/tests/card-hard-truncate-entity-safe.test.ts +153 -0
- package/telegram-plugin/tests/card-variants.golden.txt +9 -9
- package/telegram-plugin/tests/flood-reply-queue.test.ts +89 -4
- package/telegram-plugin/tests/format-consistency.test.ts +160 -14
- package/telegram-plugin/tests/handback-orphan-recovery.test.ts +131 -0
- package/telegram-plugin/tests/handback-preturn-signal.test.ts +212 -17
- package/telegram-plugin/tests/nested-indent-idiom.test.ts +162 -0
- package/telegram-plugin/tests/nested-prefix-indent.test.ts +115 -0
- package/telegram-plugin/tests/no-robust-api-call-factory.test.ts +62 -0
- package/telegram-plugin/tests/obligation-store.test.ts +51 -0
- package/telegram-plugin/tests/outbox-audience-3865.test.ts +579 -0
- package/telegram-plugin/tests/outbox-provenance-4141.test.ts +1126 -0
- package/telegram-plugin/tests/outbox-sweep-single-flight.test.ts +138 -0
- package/telegram-plugin/tests/pinned-card-collapse.test.ts +12 -10
- package/telegram-plugin/tests/plain-fallback-4096-cap.test.ts +509 -0
- package/telegram-plugin/tests/reply-quote-wire.test.ts +433 -0
- package/telegram-plugin/tests/retry-grammy-http-error.test.ts +404 -0
- package/telegram-plugin/tests/rollout-narration-edit-socket.test.ts +170 -0
- package/telegram-plugin/tests/rollout-status-edit-retry-policy.test.ts +366 -0
- package/telegram-plugin/tests/rollout-status-edit.test.ts +140 -0
- package/telegram-plugin/tests/rollout-status-wiring.test.ts +40 -7
- package/telegram-plugin/tests/tg-post-logger-error-shape.test.ts +4 -0
- package/telegram-plugin/tests/tg-post-retry-attribution.test.ts +196 -0
- package/telegram-plugin/tests/tool-activity-summary.test.ts +23 -17
- package/telegram-plugin/tool-activity-summary.ts +58 -30
- package/telegram-plugin/uat/scenarios/jtbd-foreground-subagent-activity-dm.test.ts +3 -2
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.
|
|
4
|
+
"version": "0.19.43",
|
|
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": {
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"build": "node scripts/build.mjs",
|
|
25
25
|
"build:cli": "node scripts/build.mjs && bun build --compile --target=bun-linux-x64 --minify bin/switchroom.ts --outfile switchroom-linux-amd64",
|
|
26
26
|
"pretest": "npm run build",
|
|
27
|
-
"test": "vitest run && bun test telegram-plugin/tests/agent-state-dir-preload.test.ts telegram-plugin/tests/catch-all-forwarded-history.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/ipc-server-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/registry/api-registry.test.ts telegram-plugin/registry/turns-schema.test.ts telegram-plugin/tests/idle-footer-wiring.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
|
|
27
|
+
"test": "vitest run && bun test telegram-plugin/tests/agent-state-dir-preload.test.ts telegram-plugin/tests/catch-all-forwarded-history.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/ipc-server-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.test.ts telegram-plugin/tests/rollout-narration-edit-socket.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/registry/api-registry.test.ts telegram-plugin/registry/turns-schema.test.ts telegram-plugin/tests/idle-footer-wiring.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
|
|
28
28
|
"test:vitest": "vitest run",
|
|
29
|
-
"test:bun": "bun test telegram-plugin/tests/agent-state-dir-preload.test.ts telegram-plugin/tests/catch-all-forwarded-history.test.ts src/vault/grants.test.ts src/vault/grants-db.test.ts src/vault/write-grants.test.ts src/vault/broker/server-grants.test.ts src/vault/broker/server-write-grants.test.ts src/vault/broker/server-scope-persist.test.ts src/vault/broker/server-tokenless-scope.test.ts src/vault/broker/server-mint-grant-passphrase-attest.test.ts src/vault/broker/server-passphrase-attest.test.ts src/vault/broker/server-mint-grant-posture-attest.test.ts src/vault/broker/server-admin-only-keys.test.ts src/vault/broker/client-token.test.ts src/vault/broker/server-unlock.test.ts src/vault/broker/auto-unlock.test.ts src/vault/broker/drift-detection.test.ts tests/vault-broker-passphrase.test.ts src/cli/vault-get-broker.test.ts src/vault/resolver-via-broker.test.ts src/vault/broker/scope.test.ts src/vault/broker/server.test.ts src/litellm/provision-apply-e2e.test.ts src/drive/disconnect.test.ts src/drive/grants.test.ts src/drive/oauth.test.ts src/drive/onboarding.test.ts src/drive/reconciler.test.ts src/drive/vault-slots.test.ts src/drive/wrapper.test.ts src/vault/approvals/kernel.test.ts src/vault/approvals/approval-origin.test.ts src/vault/approvals/self-approval-bypass.test.ts src/vault/approvals/schema-idempotent.test.ts src/vault/broker/server-approvals.test.ts telegram-plugin/tests/boot-probes.test.ts telegram-plugin/tests/boot-version-string.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/ipc-server-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/tests/subagent-watcher-workflow-visibility.test.ts telegram-plugin/uat/load-env.test.ts telegram-plugin/uat/feed-matcher.test.ts telegram-plugin/uat/uat-driver.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
|
|
29
|
+
"test:bun": "bun test telegram-plugin/tests/agent-state-dir-preload.test.ts telegram-plugin/tests/catch-all-forwarded-history.test.ts src/vault/grants.test.ts src/vault/grants-db.test.ts src/vault/write-grants.test.ts src/vault/broker/server-grants.test.ts src/vault/broker/server-write-grants.test.ts src/vault/broker/server-scope-persist.test.ts src/vault/broker/server-tokenless-scope.test.ts src/vault/broker/server-mint-grant-passphrase-attest.test.ts src/vault/broker/server-passphrase-attest.test.ts src/vault/broker/server-mint-grant-posture-attest.test.ts src/vault/broker/server-admin-only-keys.test.ts src/vault/broker/client-token.test.ts src/vault/broker/server-unlock.test.ts src/vault/broker/auto-unlock.test.ts src/vault/broker/drift-detection.test.ts tests/vault-broker-passphrase.test.ts src/cli/vault-get-broker.test.ts src/vault/resolver-via-broker.test.ts src/vault/broker/scope.test.ts src/vault/broker/server.test.ts src/litellm/provision-apply-e2e.test.ts src/drive/disconnect.test.ts src/drive/grants.test.ts src/drive/oauth.test.ts src/drive/onboarding.test.ts src/drive/reconciler.test.ts src/drive/vault-slots.test.ts src/drive/wrapper.test.ts src/vault/approvals/kernel.test.ts src/vault/approvals/approval-origin.test.ts src/vault/approvals/self-approval-bypass.test.ts src/vault/approvals/schema-idempotent.test.ts src/vault/broker/server-approvals.test.ts telegram-plugin/tests/boot-probes.test.ts telegram-plugin/tests/boot-version-string.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/ipc-server-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.test.ts telegram-plugin/tests/rollout-narration-edit-socket.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/tests/subagent-watcher-workflow-visibility.test.ts telegram-plugin/uat/load-env.test.ts telegram-plugin/uat/feed-matcher.test.ts telegram-plugin/uat/uat-driver.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
|
|
30
30
|
"test:watch": "vitest",
|
|
31
31
|
"lint": "tsc --noEmit && node scripts/check-plugin-references.mjs && bash scripts/check-bot-api-wrapping.sh && node scripts/check-bun-test-imports.mjs && node scripts/check-test-runner-coverage.mjs && node scripts/check-bun-module-mock-scope.mjs && node scripts/check-no-pii-secrets.mjs && node scripts/check-vault-test-hermeticity.mjs && node scripts/check-auth-test-hermeticity.mjs && node scripts/check-agent-state-dir-hermeticity.mjs && node scripts/check-no-broadcast-delivery.mjs && node scripts/check-stale-tool-descriptions.mjs && node scripts/check-mcp-instructions-budget.mjs && node scripts/check-web-subscription-honest.mjs && node scripts/check-no-unpinned-npx-playwright.mjs && node scripts/check-gateway-line-ratchet.mjs && node scripts/check-retry-flood-hooks.mjs && node scripts/check-callback-ctx-wrapping.mjs && node scripts/check-status-pin-single-path.mjs && node scripts/check-litellm-config-guard.mjs && node scripts/check-release-asset-names.mjs && node scripts/check-agent-attribution-trailers.mjs && node scripts/check-hindsight-write-redaction.mjs && bun scripts/check-secret-pattern-parity.ts",
|
|
32
32
|
"lint:tsc": "tsc --noEmit",
|
|
@@ -2002,10 +2002,88 @@ esac
|
|
|
2002
2002
|
# changed) appends to `.session-model-alert` so the gateway's boot relay
|
|
2003
2003
|
# tells the operator once.
|
|
2004
2004
|
#
|
|
2005
|
-
#
|
|
2006
|
-
#
|
|
2007
|
-
#
|
|
2008
|
-
|
|
2005
|
+
# --- Live configured-default resolution (effort sibling of the model block) ---
|
|
2006
|
+
#
|
|
2007
|
+
# The apply-time bake is only the LAST-DITCH fallback: editing
|
|
2008
|
+
# `thinking_effort:` in switchroom.yaml and restarting must take effect
|
|
2009
|
+
# WITHOUT a full `switchroom apply`, exactly as `model:` already does. Before
|
|
2010
|
+
# this, model was live-read and effort was bake-only, so a yaml effort change
|
|
2011
|
+
# needed an apply to re-render every start.sh — and the gateway's /effort menu
|
|
2012
|
+
# (which derives its configured default from `agent list --json`, i.e. the LIVE
|
|
2013
|
+
# yaml) disagreed with what the launcher actually passed to `--effort`.
|
|
2014
|
+
#
|
|
2015
|
+
# Same resolver as the gateway, asked as a verb (`switchroom agent
|
|
2016
|
+
# effective-effort`), never re-implemented in shell. Fallback chain, never
|
|
2017
|
+
# silent past step 1:
|
|
2018
|
+
# 1. live read of $SWITCHROOM_CONFIG (one bounded retry — a host-side
|
|
2019
|
+
# in-place rewrite can present a torn file for a moment);
|
|
2020
|
+
# 2. `.configured-default-effort` — the last-known-good live value this
|
|
2021
|
+
# launcher recorded on every prior boot (read BEFORE it is overwritten);
|
|
2022
|
+
# 3. the apply-time bake.
|
|
2023
|
+
# Steps 2/3 append to `.session-model-alert` so the gateway tells the operator
|
|
2024
|
+
# once at boot. No SWITCHROOM_CONFIG mount at all means there is nothing to
|
|
2025
|
+
# read live — the bake IS the truth there (stderr note only).
|
|
2026
|
+
#
|
|
2027
|
+
# Unlike the model sibling there is no routing class to flip: effort is a pure
|
|
2028
|
+
# CLI flag, so a live change is always safe to apply on a bare restart.
|
|
2029
|
+
#
|
|
2030
|
+
# NB {{thinkingEffort}} renders inside single quotes here (the scaffold does
|
|
2031
|
+
# not pre-quote it, unlike modelQ), and an unset value renders as '' — which
|
|
2032
|
+
# means "no --effort flag", handled by the $_EFFORT_ARG guard below.
|
|
2033
|
+
_BAKED_EFFORT={{#if thinkingEffort}}'{{thinkingEffort}}'{{else}}''{{/if}}
|
|
2034
|
+
_LKG_EFFORT="$(cat "{{agentDir}}/.configured-default-effort" 2>/dev/null | tr -d '[:space:]' || true)"
|
|
2035
|
+
_EFFECTIVE_EFFORT=""
|
|
2036
|
+
# Set only when step 1 actually produced a live value — it gates the
|
|
2037
|
+
# `.configured-default-effort` write below so the last-known-good file can
|
|
2038
|
+
# never record a value that was never live truth.
|
|
2039
|
+
_LE_LIVE_OK=""
|
|
2040
|
+
if [ -n "$SWITCHROOM_CONFIG" ] && [ -f "$SWITCHROOM_CONFIG" ] && command -v switchroom >/dev/null 2>&1; then
|
|
2041
|
+
_le_live=""
|
|
2042
|
+
for _le_try in 1 2; do
|
|
2043
|
+
_le_live="$(timeout 20 switchroom --config "$SWITCHROOM_CONFIG" agent effective-effort "{{name}}" 2>/dev/null | tail -n 1 | tr -d '[:space:]')" || _le_live=""
|
|
2044
|
+
# Allowlist gate — kept in sync with EFFORT_LEVELS in
|
|
2045
|
+
# telegram-plugin/gateway/effort-command.ts, byte-identical to the carrier
|
|
2046
|
+
# gate below. Anything else is treated as a failed read, not a value.
|
|
2047
|
+
if [ -n "$_le_live" ] && printf '%s' "$_le_live" | grep -Eq '^(low|medium|high|xhigh|max)$'; then
|
|
2048
|
+
break
|
|
2049
|
+
fi
|
|
2050
|
+
_le_live=""
|
|
2051
|
+
if [ "$_le_try" -eq 1 ]; then sleep 1; fi
|
|
2052
|
+
done
|
|
2053
|
+
if [ -n "$_le_live" ]; then
|
|
2054
|
+
_EFFECTIVE_EFFORT="$_le_live"
|
|
2055
|
+
_LE_LIVE_OK=1
|
|
2056
|
+
if [ "$_le_live" != "$_BAKED_EFFORT" ]; then
|
|
2057
|
+
echo "session-effort: live configured thinking_effort '$_le_live' differs from apply-time bake '${_BAKED_EFFORT:-<unset>}' — using the live value (switchroom.yaml is the source of truth)" >&2
|
|
2058
|
+
fi
|
|
2059
|
+
elif [ -n "$_LKG_EFFORT" ] && printf '%s' "$_LKG_EFFORT" | grep -Eq '^(low|medium|high|xhigh|max)$'; then
|
|
2060
|
+
# Live read failed (torn write mid-read, yaml schema error, CLI wedge).
|
|
2061
|
+
# Last-known-good beats the bake: it was live truth on a prior boot, while
|
|
2062
|
+
# the bake can predate any number of yaml edits. Loud either way.
|
|
2063
|
+
_EFFECTIVE_EFFORT="$_LKG_EFFORT"
|
|
2064
|
+
echo "session-effort: live effort read FAILED or returned an unrecognised level — using last-known-good configured thinking_effort '$_LKG_EFFORT' (apply-time bake: '${_BAKED_EFFORT:-<unset>}')" >&2
|
|
2065
|
+
printf 'The live `thinking_effort:` could not be resolved at boot — switchroom.yaml could not be read, or it held a value this launcher does not recognise. The agent booted on the last-known-good configured effort `%s`. Check `thinking_effort:` in the yaml (low/medium/high/xhigh/max) and restart the agent.\n' "$_LKG_EFFORT" >> "{{agentDir}}/.session-model-alert" 2>/dev/null || true
|
|
2066
|
+
else
|
|
2067
|
+
_EFFECTIVE_EFFORT="$_BAKED_EFFORT"
|
|
2068
|
+
echo "session-effort: live effort read FAILED or returned an unrecognised level, and no last-known-good value — using apply-time baked thinking_effort '${_BAKED_EFFORT:-<unset>}'" >&2
|
|
2069
|
+
printf 'The live `thinking_effort:` could not be resolved at boot — switchroom.yaml could not be read, or it held a value this launcher does not recognise — and no prior boot recorded an effort. The agent booted on the apply-time default `%s`. Check `thinking_effort:` in the yaml (low/medium/high/xhigh/max) and restart the agent.\n' "${_BAKED_EFFORT:-<unset>}" >> "{{agentDir}}/.session-model-alert" 2>/dev/null || true
|
|
2070
|
+
fi
|
|
2071
|
+
unset _le_live _le_try
|
|
2072
|
+
else
|
|
2073
|
+
_EFFECTIVE_EFFORT="$_BAKED_EFFORT"
|
|
2074
|
+
fi
|
|
2075
|
+
unset _BAKED_EFFORT _LKG_EFFORT
|
|
2076
|
+
# Record the LIVE-RESOLVED configured default, BEFORE the carrier below can
|
|
2077
|
+
# override it — this is the last-known-good the fallback chain reads on the
|
|
2078
|
+
# NEXT boot, and it must be the configured value, not a session override.
|
|
2079
|
+
# Written ONLY when step 1 genuinely produced a live value: a bake/LKG-fallback
|
|
2080
|
+
# boot (or a boot with no config mount at all) has no live truth to record, and
|
|
2081
|
+
# writing the bake here would let a LATER failed read cite a "last-known-good"
|
|
2082
|
+
# that was never read from switchroom.yaml.
|
|
2083
|
+
if [ -n "$_LE_LIVE_OK" ]; then
|
|
2084
|
+
printf '%s\n' "$_EFFECTIVE_EFFORT" > "{{agentDir}}/.configured-default-effort" 2>/dev/null || true
|
|
2085
|
+
fi
|
|
2086
|
+
unset _LE_LIVE_OK
|
|
2009
2087
|
if [ -f "{{agentDir}}/.session-effort" ]; then
|
|
2010
2088
|
_sef="$(cat "{{agentDir}}/.session-effort" 2>/dev/null || true)"
|
|
2011
2089
|
_se_level="$(printf '%s' "$_sef" | sed -n 's/.*"level"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
|
|
@@ -108,7 +108,7 @@ const TOOL_SCHEMAS = [
|
|
|
108
108
|
format: { type: 'string', enum: ['html', 'markdownv2', 'text'], description: "Rendering mode. Default renders your text as rich GFM markdown (Bot API 10.1). 'text' sends plain text verbatim. 'html' and 'markdownv2' are legacy aliases that route through the same single rich GFM path — no separate HTML engine, no auto-escaping." },
|
|
109
109
|
disable_web_page_preview: { type: 'boolean', description: 'Disable link preview thumbnails. Default: true.' },
|
|
110
110
|
protect_content: { type: 'boolean', description: 'When true, Telegram prevents the message from being forwarded or saved.' },
|
|
111
|
-
quote_text: { type: 'string', description: 'Surgical quote: specific text to highlight from the reply_to message. Requires reply_to.' },
|
|
111
|
+
quote_text: { type: 'string', description: 'Surgical quote: specific text to highlight from the reply_to message. Must be an EXACT substring of that message (copy it verbatim, do not paraphrase; max 1024 chars) — a quote Telegram cannot find is dropped and the reply lands unquoted. Requires reply_to.' },
|
|
112
112
|
disable_notification: { type: 'boolean', description: 'When true, Telegram delivers the message silently — no device ping for this user. Default false (pings). Use true for mid-turn updates ("still working through X") so only the final answer pings. Always omit (or pass false) on the final answer of a turn.' },
|
|
113
113
|
inline_keyboard: {
|
|
114
114
|
type: 'array',
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
import { escapeMarkdown, stripMarkdown, truncate, stackCardLines } from './card-format.js'
|
|
33
|
+
import { truncateMarkdownSafe } from './format.js'
|
|
33
34
|
import { formatModelLabel } from './model-label.js'
|
|
34
35
|
import { STATUS_CARD_CHAR_BUDGET, STATUS_LINE_MAX, STATUS_ROLLING_LINES } from './status-no-truncate.js'
|
|
35
36
|
|
|
@@ -313,6 +314,13 @@ export interface CardCandidate {
|
|
|
313
314
|
* an oversized newest bullet), while the combined card drops its newest worker
|
|
314
315
|
* row into `+M more working…`. The MECHANISM — render, measure, accept or go
|
|
315
316
|
* deeper — exists only here.
|
|
317
|
+
*
|
|
318
|
+
* The return is UNCONDITIONALLY within `STATUS_CARD_CHAR_BUDGET` (#3833). The
|
|
319
|
+
* loop used to return the deepest render unmeasured, so a card whose FIXED
|
|
320
|
+
* chrome/footer alone exceeds the budget (a 40k-char worker description — the
|
|
321
|
+
* #3682 repro) escaped over the wire limit and the send failed. No shrink level
|
|
322
|
+
* can rescue that case by construction — every level keeps the chrome — so the
|
|
323
|
+
* last resort hard-truncates. A clipped card beats no card.
|
|
316
324
|
*/
|
|
317
325
|
export function fitCardToBudget(
|
|
318
326
|
build: (level: number) => CardCandidate,
|
|
@@ -324,5 +332,59 @@ export function fitCardToBudget(
|
|
|
324
332
|
text = renderCardSpec(candidate.spec)
|
|
325
333
|
if (candidate.overflow !== true && text.length <= STATUS_CARD_CHAR_BUDGET) return text
|
|
326
334
|
}
|
|
327
|
-
return text
|
|
335
|
+
return hardTruncateCard(text)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Last-resort clip for a rendered card that no shrink level could fit — the
|
|
340
|
+
* only place a card's text is cut without going through a spec.
|
|
341
|
+
*
|
|
342
|
+
* Keeps as many WHOLE lines as fit, so line structure survives, and then runs
|
|
343
|
+
* the chosen cut through {@link truncateMarkdownSafe} so the emitted text is
|
|
344
|
+
* always PARSEABLE markdown (#4116). Two things make that necessary rather
|
|
345
|
+
* than decorative:
|
|
346
|
+
*
|
|
347
|
+
* - a first line longer than the whole budget keeps no whole line at all, so
|
|
348
|
+
* the cut lands mid-line — between a `**` pair, inside a `` ` `` span, in
|
|
349
|
+
* the middle of a `[label](href)` — and Telegram parse-REJECTS the result.
|
|
350
|
+
* The path that exists to guarantee delivery would itself fail the send.
|
|
351
|
+
* - even a whole-line cut can leave a FENCED block open, which is a
|
|
352
|
+
* multi-line entity and therefore invisible to a per-line rule.
|
|
353
|
+
*
|
|
354
|
+
* The tail is then cleaned of the stack's own break chrome (hard-break spaces /
|
|
355
|
+
* `COLLAPSE_SAFE_SEPARATOR`) and of a dangling lone surrogate, so the wire
|
|
356
|
+
* string is never invalid UTF-16.
|
|
357
|
+
*
|
|
358
|
+
* Exported for the budget tests; production callers go through
|
|
359
|
+
* {@link fitCardToBudget}.
|
|
360
|
+
*/
|
|
361
|
+
export function hardTruncateCard(
|
|
362
|
+
text: string,
|
|
363
|
+
budget: number = STATUS_CARD_CHAR_BUDGET,
|
|
364
|
+
): string {
|
|
365
|
+
if (text.length <= budget) return text
|
|
366
|
+
// The break chrome is measured on the KEPT prefix, not on the final line:
|
|
367
|
+
// a line that is followed by a break carries ` \n` (+ separator) which is
|
|
368
|
+
// stripped once it becomes the last line, so a prefix can fit after the
|
|
369
|
+
// strip even when its raw length is a few chars over.
|
|
370
|
+
let kept = ''
|
|
371
|
+
for (const line of text.split('\n')) {
|
|
372
|
+
const next = kept.length === 0 ? line : `${kept}\n${line}`
|
|
373
|
+
if (trimBreakChrome(next).length > budget) break
|
|
374
|
+
kept = next
|
|
375
|
+
}
|
|
376
|
+
// A first line longer than the budget keeps nothing; cut into it rather than
|
|
377
|
+
// emit a blank card. Either way the cut is taken against the FULL text — an
|
|
378
|
+
// entity straddling the boundary is only visible from there.
|
|
379
|
+
const target = kept.length > 0 ? kept.length : Math.min(budget, text.length)
|
|
380
|
+
let out = trimBreakChrome(truncateMarkdownSafe(text, target))
|
|
381
|
+
if (out.length > budget) out = trimBreakChrome(truncateMarkdownSafe(text, budget))
|
|
382
|
+
// Finally, never leave a lone high surrogate from slicing through an astral
|
|
383
|
+
// character.
|
|
384
|
+
return /[\uD800-\uDBFF]$/.test(out) ? out.slice(0, -1) : out
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** Strip a stacked card's trailing hard-break spaces / collapse separator. */
|
|
388
|
+
function trimBreakChrome(text: string): string {
|
|
389
|
+
return text.replace(/[ \t\r\u00A0]+$/, '')
|
|
328
390
|
}
|
|
@@ -22970,6 +22970,9 @@ import {
|
|
|
22970
22970
|
import { homedir as homedir2 } from "os";
|
|
22971
22971
|
import { basename, join as join3 } from "path";
|
|
22972
22972
|
|
|
22973
|
+
// format.ts
|
|
22974
|
+
var DELIMITER_RUN_CHARS = new Set(["*", "_", "`", "~", "|"]);
|
|
22975
|
+
|
|
22973
22976
|
// text-voice-scrub.ts
|
|
22974
22977
|
var NULL = "\x00";
|
|
22975
22978
|
var FENCE_PH = `${NULL}VS_FENCE`;
|
|
@@ -25083,7 +25086,7 @@ var TOOL_SCHEMAS = [
|
|
|
25083
25086
|
format: { type: "string", enum: ["html", "markdownv2", "text"], description: "Rendering mode. Default renders your text as rich GFM markdown (Bot API 10.1). 'text' sends plain text verbatim. 'html' and 'markdownv2' are legacy aliases that route through the same single rich GFM path \u2014 no separate HTML engine, no auto-escaping." },
|
|
25084
25087
|
disable_web_page_preview: { type: "boolean", description: "Disable link preview thumbnails. Default: true." },
|
|
25085
25088
|
protect_content: { type: "boolean", description: "When true, Telegram prevents the message from being forwarded or saved." },
|
|
25086
|
-
quote_text: { type: "string", description: "Surgical quote: specific text to highlight from the reply_to message. Requires reply_to." },
|
|
25089
|
+
quote_text: { type: "string", description: "Surgical quote: specific text to highlight from the reply_to message. Must be an EXACT substring of that message (copy it verbatim, do not paraphrase; max 1024 chars) \u2014 a quote Telegram cannot find is dropped and the reply lands unquoted. Requires reply_to." },
|
|
25087
25090
|
disable_notification: { type: "boolean", description: 'When true, Telegram delivers the message silently \u2014 no device ping for this user. Default false (pings). Use true for mid-turn updates ("still working through X") so only the final answer pings. Always omit (or pass false) on the final answer of a turn.' },
|
|
25088
25091
|
inline_keyboard: {
|
|
25089
25092
|
type: "array",
|