switchroom 0.18.28 → 0.18.29
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/handoff-briefing.sh +8 -1
- package/dist/auth-broker/index.js +0 -57
- package/dist/cli/switchroom.js +501 -497
- package/dist/host-control/main.js +1 -58
- package/dist/vault/approvals/kernel-server.js +0 -57
- package/dist/vault/broker/server.js +0 -57
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +37 -19
- package/telegram-plugin/dist/gateway/gateway.js +578 -580
- package/telegram-plugin/gateway/backstop-delivery.ts +272 -0
- package/telegram-plugin/gateway/forward-origin.ts +9 -1
- package/telegram-plugin/gateway/gateway.ts +458 -385
- package/telegram-plugin/gateway/model-command.ts +227 -602
- package/telegram-plugin/gateway/turn-record-status.ts +45 -0
- package/telegram-plugin/history.ts +153 -23
- package/telegram-plugin/shared/local-time.ts +56 -0
- package/telegram-plugin/tests/backstop-delivery.test.ts +250 -0
- package/telegram-plugin/tests/forward-origin.test.ts +30 -3
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +86 -59
- package/telegram-plugin/tests/history.test.ts +88 -0
- package/telegram-plugin/tests/local-time.test.ts +68 -0
- package/telegram-plugin/tests/model-command.test.ts +317 -1535
- package/telegram-plugin/tests/turn-flush-safety.test.ts +34 -0
- package/telegram-plugin/tier-downgrade.ts +4 -3
- package/telegram-plugin/turn-flush-safety.ts +25 -1
- package/vendor/hindsight-memory/scripts/lib/content.py +40 -6
- package/vendor/hindsight-memory/tests/test_content.py +28 -7
|
@@ -20610,63 +20610,6 @@ for (const name of SHARED_FRAGMENTS) {
|
|
|
20610
20610
|
}
|
|
20611
20611
|
}
|
|
20612
20612
|
|
|
20613
|
-
// src/agents/pane-lock.ts
|
|
20614
|
-
var tails = new Map;
|
|
20615
|
-
|
|
20616
|
-
// src/agents/inject.ts
|
|
20617
|
-
var INJECT_COMMANDS = new Map([
|
|
20618
|
-
["/cost", { description: "Show session cost", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
20619
|
-
["/status", { description: "Show session status", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
20620
|
-
["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
20621
|
-
["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
20622
|
-
["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
20623
|
-
["/help", { description: "Show help / command discovery", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
20624
|
-
["/context", { description: "Show context window usage", expectsOutput: true, argsAllowed: false }],
|
|
20625
|
-
["/release-notes", { description: "Show release-notes version list", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
20626
|
-
["/model", { description: "Open model picker", expectsOutput: true, argsAllowed: true }],
|
|
20627
|
-
[
|
|
20628
|
-
"/clear",
|
|
20629
|
-
{
|
|
20630
|
-
description: "Clear session screen",
|
|
20631
|
-
expectsOutput: false,
|
|
20632
|
-
silentNote: "context cleared — fresh slate",
|
|
20633
|
-
argsAllowed: false
|
|
20634
|
-
}
|
|
20635
|
-
],
|
|
20636
|
-
[
|
|
20637
|
-
"/compact",
|
|
20638
|
-
{
|
|
20639
|
-
description: "Compact conversation history",
|
|
20640
|
-
expectsOutput: false,
|
|
20641
|
-
silentNote: "compaction runs silently",
|
|
20642
|
-
argsAllowed: false
|
|
20643
|
-
}
|
|
20644
|
-
]
|
|
20645
|
-
]);
|
|
20646
|
-
var INJECT_ALLOWLIST = new Set(INJECT_COMMANDS.keys());
|
|
20647
|
-
var INJECT_BLOCKED = new Map([
|
|
20648
|
-
["/login", { reason: "would mutate auth state" }],
|
|
20649
|
-
["/logout", { reason: "would terminate the agent's auth session" }],
|
|
20650
|
-
["/exit", { reason: "would kill the agent process" }],
|
|
20651
|
-
["/quit", { reason: "would kill the agent process" }],
|
|
20652
|
-
["/upgrade", { reason: "mutates the Claude Code installation" }],
|
|
20653
|
-
["/init", { reason: "generates/overwrites CLAUDE.md and runs a model turn" }],
|
|
20654
|
-
["/mcp", { reason: "opens an interactive MCP server management dialog" }],
|
|
20655
|
-
["/permissions", { reason: "opens an interactive permissions editor that mutates tool policy" }],
|
|
20656
|
-
["/install-github-app", { reason: "runs a network/OAuth install flow" }],
|
|
20657
|
-
["/add-dir", { reason: "mutates the session's working-directory set" }],
|
|
20658
|
-
["/terminal-setup", { reason: "mutates terminal keybinding configuration" }],
|
|
20659
|
-
["/privacy-settings", { reason: "opens an interactive privacy-settings dialog" }],
|
|
20660
|
-
["/bug", { reason: "submits a bug report over the network" }],
|
|
20661
|
-
[
|
|
20662
|
-
"/effort",
|
|
20663
|
-
{
|
|
20664
|
-
reason: "leaves a blocking confirmation modal open and wedges the pane; use the /effort command (it drives the modal), not raw inject"
|
|
20665
|
-
}
|
|
20666
|
-
]
|
|
20667
|
-
]);
|
|
20668
|
-
var INJECT_BLOCKLIST = new Set(INJECT_BLOCKED.keys());
|
|
20669
|
-
|
|
20670
20613
|
// src/setup/hindsight.ts
|
|
20671
20614
|
var HINDSIGHT_DEFAULT_API_PORT = 18888;
|
|
20672
20615
|
var HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
|
|
@@ -26605,7 +26548,7 @@ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:f
|
|
|
26605
26548
|
import { dirname as dirname4, join as join7 } from "node:path";
|
|
26606
26549
|
|
|
26607
26550
|
// src/build-info.ts
|
|
26608
|
-
var VERSION = "0.18.
|
|
26551
|
+
var VERSION = "0.18.29";
|
|
26609
26552
|
|
|
26610
26553
|
// src/cli/resolve-version.ts
|
|
26611
26554
|
function readPackageVersion() {
|
|
@@ -18990,63 +18990,6 @@ for (const name of SHARED_FRAGMENTS) {
|
|
|
18990
18990
|
}
|
|
18991
18991
|
}
|
|
18992
18992
|
|
|
18993
|
-
// src/agents/pane-lock.ts
|
|
18994
|
-
var tails = new Map;
|
|
18995
|
-
|
|
18996
|
-
// src/agents/inject.ts
|
|
18997
|
-
var INJECT_COMMANDS = new Map([
|
|
18998
|
-
["/cost", { description: "Show session cost", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18999
|
-
["/status", { description: "Show session status", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
19000
|
-
["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
19001
|
-
["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
19002
|
-
["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
19003
|
-
["/help", { description: "Show help / command discovery", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
19004
|
-
["/context", { description: "Show context window usage", expectsOutput: true, argsAllowed: false }],
|
|
19005
|
-
["/release-notes", { description: "Show release-notes version list", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
19006
|
-
["/model", { description: "Open model picker", expectsOutput: true, argsAllowed: true }],
|
|
19007
|
-
[
|
|
19008
|
-
"/clear",
|
|
19009
|
-
{
|
|
19010
|
-
description: "Clear session screen",
|
|
19011
|
-
expectsOutput: false,
|
|
19012
|
-
silentNote: "context cleared — fresh slate",
|
|
19013
|
-
argsAllowed: false
|
|
19014
|
-
}
|
|
19015
|
-
],
|
|
19016
|
-
[
|
|
19017
|
-
"/compact",
|
|
19018
|
-
{
|
|
19019
|
-
description: "Compact conversation history",
|
|
19020
|
-
expectsOutput: false,
|
|
19021
|
-
silentNote: "compaction runs silently",
|
|
19022
|
-
argsAllowed: false
|
|
19023
|
-
}
|
|
19024
|
-
]
|
|
19025
|
-
]);
|
|
19026
|
-
var INJECT_ALLOWLIST = new Set(INJECT_COMMANDS.keys());
|
|
19027
|
-
var INJECT_BLOCKED = new Map([
|
|
19028
|
-
["/login", { reason: "would mutate auth state" }],
|
|
19029
|
-
["/logout", { reason: "would terminate the agent's auth session" }],
|
|
19030
|
-
["/exit", { reason: "would kill the agent process" }],
|
|
19031
|
-
["/quit", { reason: "would kill the agent process" }],
|
|
19032
|
-
["/upgrade", { reason: "mutates the Claude Code installation" }],
|
|
19033
|
-
["/init", { reason: "generates/overwrites CLAUDE.md and runs a model turn" }],
|
|
19034
|
-
["/mcp", { reason: "opens an interactive MCP server management dialog" }],
|
|
19035
|
-
["/permissions", { reason: "opens an interactive permissions editor that mutates tool policy" }],
|
|
19036
|
-
["/install-github-app", { reason: "runs a network/OAuth install flow" }],
|
|
19037
|
-
["/add-dir", { reason: "mutates the session's working-directory set" }],
|
|
19038
|
-
["/terminal-setup", { reason: "mutates terminal keybinding configuration" }],
|
|
19039
|
-
["/privacy-settings", { reason: "opens an interactive privacy-settings dialog" }],
|
|
19040
|
-
["/bug", { reason: "submits a bug report over the network" }],
|
|
19041
|
-
[
|
|
19042
|
-
"/effort",
|
|
19043
|
-
{
|
|
19044
|
-
reason: "leaves a blocking confirmation modal open and wedges the pane; use the /effort command (it drives the modal), not raw inject"
|
|
19045
|
-
}
|
|
19046
|
-
]
|
|
19047
|
-
]);
|
|
19048
|
-
var INJECT_BLOCKLIST = new Set(INJECT_BLOCKED.keys());
|
|
19049
|
-
|
|
19050
18993
|
// src/setup/hindsight.ts
|
|
19051
18994
|
var HINDSIGHT_DEFAULT_API_PORT = 18888;
|
|
19052
18995
|
var HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
|
|
@@ -18698,63 +18698,6 @@ for (const name of SHARED_FRAGMENTS) {
|
|
|
18698
18698
|
}
|
|
18699
18699
|
}
|
|
18700
18700
|
|
|
18701
|
-
// src/agents/pane-lock.ts
|
|
18702
|
-
var tails = new Map;
|
|
18703
|
-
|
|
18704
|
-
// src/agents/inject.ts
|
|
18705
|
-
var INJECT_COMMANDS = new Map([
|
|
18706
|
-
["/cost", { description: "Show session cost", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18707
|
-
["/status", { description: "Show session status", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18708
|
-
["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18709
|
-
["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18710
|
-
["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18711
|
-
["/help", { description: "Show help / command discovery", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18712
|
-
["/context", { description: "Show context window usage", expectsOutput: true, argsAllowed: false }],
|
|
18713
|
-
["/release-notes", { description: "Show release-notes version list", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18714
|
-
["/model", { description: "Open model picker", expectsOutput: true, argsAllowed: true }],
|
|
18715
|
-
[
|
|
18716
|
-
"/clear",
|
|
18717
|
-
{
|
|
18718
|
-
description: "Clear session screen",
|
|
18719
|
-
expectsOutput: false,
|
|
18720
|
-
silentNote: "context cleared — fresh slate",
|
|
18721
|
-
argsAllowed: false
|
|
18722
|
-
}
|
|
18723
|
-
],
|
|
18724
|
-
[
|
|
18725
|
-
"/compact",
|
|
18726
|
-
{
|
|
18727
|
-
description: "Compact conversation history",
|
|
18728
|
-
expectsOutput: false,
|
|
18729
|
-
silentNote: "compaction runs silently",
|
|
18730
|
-
argsAllowed: false
|
|
18731
|
-
}
|
|
18732
|
-
]
|
|
18733
|
-
]);
|
|
18734
|
-
var INJECT_ALLOWLIST = new Set(INJECT_COMMANDS.keys());
|
|
18735
|
-
var INJECT_BLOCKED = new Map([
|
|
18736
|
-
["/login", { reason: "would mutate auth state" }],
|
|
18737
|
-
["/logout", { reason: "would terminate the agent's auth session" }],
|
|
18738
|
-
["/exit", { reason: "would kill the agent process" }],
|
|
18739
|
-
["/quit", { reason: "would kill the agent process" }],
|
|
18740
|
-
["/upgrade", { reason: "mutates the Claude Code installation" }],
|
|
18741
|
-
["/init", { reason: "generates/overwrites CLAUDE.md and runs a model turn" }],
|
|
18742
|
-
["/mcp", { reason: "opens an interactive MCP server management dialog" }],
|
|
18743
|
-
["/permissions", { reason: "opens an interactive permissions editor that mutates tool policy" }],
|
|
18744
|
-
["/install-github-app", { reason: "runs a network/OAuth install flow" }],
|
|
18745
|
-
["/add-dir", { reason: "mutates the session's working-directory set" }],
|
|
18746
|
-
["/terminal-setup", { reason: "mutates terminal keybinding configuration" }],
|
|
18747
|
-
["/privacy-settings", { reason: "opens an interactive privacy-settings dialog" }],
|
|
18748
|
-
["/bug", { reason: "submits a bug report over the network" }],
|
|
18749
|
-
[
|
|
18750
|
-
"/effort",
|
|
18751
|
-
{
|
|
18752
|
-
reason: "leaves a blocking confirmation modal open and wedges the pane; use the /effort command (it drives the modal), not raw inject"
|
|
18753
|
-
}
|
|
18754
|
-
]
|
|
18755
|
-
]);
|
|
18756
|
-
var INJECT_BLOCKLIST = new Set(INJECT_BLOCKED.keys());
|
|
18757
|
-
|
|
18758
18701
|
// src/setup/hindsight.ts
|
|
18759
18702
|
var HINDSIGHT_DEFAULT_API_PORT = 18888;
|
|
18760
18703
|
var HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
|
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.18.
|
|
4
|
+
"version": "0.18.29",
|
|
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": {
|
|
@@ -1148,18 +1148,19 @@ fi
|
|
|
1148
1148
|
|
|
1149
1149
|
# --- Session model resolution (consume-once .session-model carrier) ---
|
|
1150
1150
|
#
|
|
1151
|
-
# Contract: reference/rfcs/session-model-stickiness.md §0.
|
|
1152
|
-
# decision 2026-07-
|
|
1153
|
-
# `/model` override lasts only for the
|
|
1151
|
+
# Contract: reference/rfcs/session-model-stickiness.md §0.05 (rev 5, operator
|
|
1152
|
+
# decision 2026-07-16 — DETERMINISTIC SWITCH, superseding rev 4 §0.1's "live
|
|
1153
|
+
# Claude switches write no carrier"). A `/model` override lasts only for the
|
|
1154
|
+
# current session:
|
|
1154
1155
|
#
|
|
1155
|
-
# -
|
|
1156
|
-
#
|
|
1157
|
-
#
|
|
1158
|
-
#
|
|
1159
|
-
#
|
|
1160
|
-
#
|
|
1161
|
-
#
|
|
1162
|
-
#
|
|
1156
|
+
# - EVERY `/model` switch — Claude→Claude, Claude→sr-*, sr-*→Claude, the
|
|
1157
|
+
# Fable/alias button, and the picker SELECT — writes `{{agentDir}}/.session-model`
|
|
1158
|
+
# (one-line JSON) IMMEDIATELY before the relaunch that applies it. The
|
|
1159
|
+
# inject-into-tmux + terminal-scrape path is RETIRED: it silently no-op'd
|
|
1160
|
+
# then optimistically lied to /status. This block APPLIES the carrier on the
|
|
1161
|
+
# single boot that reads it and then DELETES it (consume-once), and the
|
|
1162
|
+
# `exec claude --model <token>` below cannot silently no-op.
|
|
1163
|
+
# - Any SUBSEQUENT restart — deploy, `/restart`, `/new`, watchdog recovery,
|
|
1163
1164
|
# crash, raw `docker restart`, host reboot — finds no carrier and boots the
|
|
1164
1165
|
# configured default. That deletion is the marker distinguishing the
|
|
1165
1166
|
# model-apply relaunch from every later restart.
|
|
@@ -1230,12 +1231,16 @@ if [ -f "{{agentDir}}/.session-model" ]; then
|
|
|
1230
1231
|
# announce once.
|
|
1231
1232
|
echo "session-model: configured default changed ('$_sm_cfg' → '$_EFFECTIVE_MODEL') — dropping session override '$_sm_model'" >&2
|
|
1232
1233
|
printf 'The configured default model changed (`%s` → `%s`), so your session override to `%s` was not applied — the agent booted on the new configured default. Re-issue /model %s if you still want it.\n' "$_sm_cfg" "$_EFFECTIVE_MODEL" "$_sm_model" "$_sm_model" > "{{agentDir}}/.session-model-alert" 2>/dev/null || true
|
|
1233
|
-
elif [ "${_sm_model#sr-}" != "$_sm_model" ] && [ -z "$_LITELLM_OK" ]; then
|
|
1234
|
-
#
|
|
1235
|
-
# 4xx against Anthropic
|
|
1236
|
-
#
|
|
1237
|
-
#
|
|
1238
|
-
|
|
1234
|
+
elif { [ "${_sm_model#sr-}" != "$_sm_model" ] || [ "$_sm_model" = fable ] || [ "$_sm_model" = claude-fable-5 ]; } && [ -z "$_LITELLM_OK" ]; then
|
|
1235
|
+
# Proxy-only override + LiteLLM unreachable at the apply-boot: booting the
|
|
1236
|
+
# override would 4xx (sr-* against Anthropic; `fable`/`claude-fable-5` is a
|
|
1237
|
+
# retired codename direct-to-Anthropic and ONLY resolves via the LiteLLM
|
|
1238
|
+
# router — see the repoint case below). Consume-once means we can't retain it
|
|
1239
|
+
# for a later relaunch, so boot the configured default and tell the operator
|
|
1240
|
+
# to re-issue once the proxy is back. (Rev 5: Fable is now reachable via the
|
|
1241
|
+
# carrier relaunch — the old live-inject path never launched it — so it needs
|
|
1242
|
+
# the same down-guard as sr-*.)
|
|
1243
|
+
echo "session-model: LiteLLM proxy unreachable at boot — NOT applying proxy-only override '$_sm_model'; booting configured default '$_EFFECTIVE_MODEL' (re-issue /model when the proxy is back)" >&2
|
|
1239
1244
|
printf 'LiteLLM proxy was unreachable at boot, so the session model override `%s` was not applied — the agent booted on its configured default `%s`. Re-issue /model %s once the proxy is reachable.\n' "$_sm_model" "$_EFFECTIVE_MODEL" "$_sm_model" > "{{agentDir}}/.session-model-alert" 2>/dev/null || true
|
|
1240
1245
|
else
|
|
1241
1246
|
# Apply the carrier for THIS session. No boot alert — the gateway already
|
|
@@ -1312,11 +1317,24 @@ printf '%s\n' "$_EFFECTIVE_EFFORT" > "{{agentDir}}/.active-session-effort" 2>/de
|
|
|
1312
1317
|
# non-Claude default at the router root) and run degraded until litellm returns —
|
|
1313
1318
|
# there is NO Claude default to fall back to for a persistent sr-* agent, and
|
|
1314
1319
|
# that (loud-warned, non-fatal) degraded state is the correct behavior.
|
|
1320
|
+
#
|
|
1321
|
+
# Rev 5 (F2): `fable`/`claude-fable-5` ALSO needs the router root, not the
|
|
1322
|
+
# passthrough. The `fable` alias resolves in the CLI to `claude-fable-5`
|
|
1323
|
+
# (ANTHROPIC_DEFAULT_FABLE_MODEL above), a RETIRED codename that 4xxs
|
|
1324
|
+
# direct-to-Anthropic; it ONLY resolves via the LiteLLM router (maps
|
|
1325
|
+
# fable/claude-fable-5 -> anthropic/claude-fable-5, forwarding the OAuth). The
|
|
1326
|
+
# old Fable path was a live inject that silently no-op'd, so Fable never actually
|
|
1327
|
+
# launched via `--model`; routing the Fable button through the carrier relaunch
|
|
1328
|
+
# newly exposes this, so the repoint case must include it or a fable carrier
|
|
1329
|
+
# 4xxs even with LiteLLM UP. Verified against the live litellm config + router
|
|
1330
|
+
# /v1/models (both `fable` and `claude-fable-5` served). Other Claude sessions
|
|
1331
|
+
# (opus/sonnet/haiku/default) keep the passthrough — it dodges the Opus SSE
|
|
1332
|
+
# re-chunk stall; Fable trades that for being routable at all.
|
|
1315
1333
|
case "$_EFFECTIVE_MODEL" in
|
|
1316
|
-
sr
|
|
1334
|
+
sr-*|fable|claude-fable-5)
|
|
1317
1335
|
if [ -n "$_LITELLM_OK" ] && [ -n "$SWITCHROOM_LITELLM_BASE" ]; then
|
|
1318
1336
|
export ANTHROPIC_BASE_URL="$SWITCHROOM_LITELLM_BASE"
|
|
1319
|
-
echo "session-model: effective model '$_EFFECTIVE_MODEL' is sr
|
|
1337
|
+
echo "session-model: effective model '$_EFFECTIVE_MODEL' is proxy-only (sr-*/fable) — routing via LiteLLM model router '$SWITCHROOM_LITELLM_BASE' (off /anthropic passthrough)" >&2
|
|
1320
1338
|
fi
|
|
1321
1339
|
;;
|
|
1322
1340
|
esac
|