blun-king-cli 9.1.536 → 9.1.550
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/LIESMICH.txt +13 -869
- package/README.md +41 -833
- package/bin/assistant-message-offload-policy.cjs +3 -1
- package/bin/context-performance-policy.cjs +2 -5
- package/bin/context-pressure-policy.cjs +20 -0
- package/bin/cron-run-output.cjs +45 -0
- package/bin/cron-run-store.cjs +145 -0
- package/bin/durable-task-resume-policy.cjs +130 -0
- package/bin/durable-task-resume-runtime.cjs +117 -0
- package/bin/durable-task-resume-store.cjs +88 -0
- package/bin/editable-tool-approval-policy.cjs +540 -0
- package/bin/editable-tool-approval-runtime.cjs +99 -0
- package/bin/html-to-research-markdown.cjs +146 -0
- package/bin/programmatic-tool-runtime.mjs +330 -4
- package/bin/read-continuation-policy.cjs +36 -5
- package/bin/scoped-cron-run-policy.cjs +358 -0
- package/bin/startup-preferences.cjs +3 -3
- package/bin/structured-agent-swarm-output.cjs +325 -0
- package/bin/subagent-context-fork-policy.cjs +155 -0
- package/bin/subagent-skill-policy.cjs +204 -0
- package/bin/telegram-approval-relay.cjs +2 -1
- package/bin/tool-result-offload-policy.cjs +14 -1
- package/bin/update-notice.js +14 -18
- package/bin/user-message-offload-policy.cjs +3 -1
- package/blun.mjs +1089 -477
- package/codebase-index/README.md +12 -0
- package/codebase-index/codebase_index.py +129 -18
- package/package.json +23 -58
- package/telegram-plugin/bin/telegram-typing-keepalive.cjs +89 -0
- package/telegram-plugin/dist/bridge.mjs +8 -1
- package/CHANGELOG.md +0 -321
- package/agent-spine-plugin/CHANGELOG.md +0 -406
- package/agent-spine-plugin/CONTRIBUTING.md +0 -52
- package/agent-spine-plugin/README.md +0 -344
- package/agent-spine-plugin/SECURITY.md +0 -47
- package/agent-spine-plugin/docs/acceptance.md +0 -61
- package/agent-spine-plugin/docs/architecture.md +0 -183
- package/agent-spine-plugin/docs/attention.md +0 -121
- package/agent-spine-plugin/docs/automatic-continuity.md +0 -79
- package/agent-spine-plugin/docs/channel-runtime.md +0 -92
- package/agent-spine-plugin/docs/coordination.md +0 -138
- package/agent-spine-plugin/docs/feed-transport.md +0 -99
- package/agent-spine-plugin/docs/gateway-runtime.md +0 -116
- package/agent-spine-plugin/docs/harness-reference.md +0 -45
- package/agent-spine-plugin/docs/host-integration.md +0 -129
- package/agent-spine-plugin/docs/https-transport.md +0 -116
- package/agent-spine-plugin/docs/learning.md +0 -133
- package/agent-spine-plugin/docs/object-transport.md +0 -93
- package/agent-spine-plugin/docs/peer-transport.md +0 -88
- package/agent-spine-plugin/docs/preflight-recall.md +0 -69
- package/agent-spine-plugin/docs/preservation-contract.md +0 -53
- package/agent-spine-plugin/docs/quality-gates.md +0 -50
- package/agent-spine-plugin/docs/relationships.md +0 -73
- package/agent-spine-plugin/docs/releasing.md +0 -83
- package/agent-spine-plugin/docs/roadmap.md +0 -307
- package/agent-spine-plugin/docs/selfstarter.md +0 -88
- package/agent-spine-plugin/docs/session-briefing.md +0 -74
- package/agent-spine-plugin/docs/shared-memory.md +0 -259
- package/agent-spine-plugin/docs/source-roots.md +0 -86
- package/agent-spine-plugin/docs/sqlite-transport.md +0 -76
- package/agent-spine-plugin/scripts/check-hosts.js +0 -195
- package/agent-spine-plugin/scripts/check-install.js +0 -569
- package/agent-spine-plugin/scripts/check-syntax.js +0 -29
- package/agent-spine-plugin/scripts/github-actions.js +0 -11
- package/agent-spine-plugin/scripts/release-check.js +0 -128
- package/agent-spine-plugin/scripts/run-acceptance.js +0 -19
- package/agent-spine-plugin/scripts/run-checks.js +0 -46
- package/agent-spine-plugin/scripts/run-tests-hermetic.js +0 -73
- package/agent-spine-plugin/spine-example/1-identity.md +0 -12
- package/agent-spine-plugin/spine-example/2-voice.md +0 -6
- package/agent-spine-plugin/spine-example/3-conduct.md +0 -8
- package/agent-spine-plugin/spine-example/4-history.md +0 -4
- package/bin/fredrik-glm-provider.cjs +0 -256
- package/bin/package-regression-policy.cjs +0 -77
- package/fredrik-glm-profile.toml.example +0 -26
- package/release-planned-removals.json +0 -15
- package/scripts/check-active-profile-plugin-startup.js +0 -36
- package/scripts/check-active-work-steer-regression.js +0 -46
- package/scripts/check-approval-observability-regression.js +0 -111
- package/scripts/check-approval-queue-shortcuts-regression.js +0 -65
- package/scripts/check-bundled-agent-spine-regression.js +0 -48
- package/scripts/check-codebase-search-packaging-regression.js +0 -92
- package/scripts/check-copy-command-regression.js +0 -74
- package/scripts/check-current-turn-read-pin-mutation-regression.js +0 -72
- package/scripts/check-current-turn-read-pin-regression.js +0 -94
- package/scripts/check-deepseek-native-max-regression.js +0 -49
- package/scripts/check-empty-response-effort-downgrade-regression.js +0 -48
- package/scripts/check-fredrik-glm-mutation-regression.js +0 -18
- package/scripts/check-fredrik-glm-regression.js +0 -169
- package/scripts/check-historical-tool-result-preview-regression.js +0 -77
- package/scripts/check-history-pressure-offload-regression.js +0 -77
- package/scripts/check-mcp-startup-wait-budget.js +0 -48
- package/scripts/check-package-regression.js +0 -38
- package/scripts/check-plugin-startup-regression.js +0 -53
- package/scripts/check-programmatic-context-isolation-regression.js +0 -193
- package/scripts/check-programmatic-tool-regression.js +0 -294
- package/scripts/check-queue-controls-regression.js +0 -189
- package/scripts/check-release-metadata.js +0 -103
- package/scripts/check-reload-agent-spine-regression.js +0 -76
- package/scripts/check-resume-replay-regression.js +0 -102
- package/scripts/check-session-cancel-regression.js +0 -43
- package/scripts/check-session-picker-resume-metrics-regression.js +0 -97
- package/scripts/check-session-start-hook-context-regression.js +0 -228
- package/scripts/check-shell-terminal-isolation-regression.js +0 -81
- package/scripts/check-slash-escape-regression.js +0 -89
- package/scripts/check-startup-swarm-command-regression.js +0 -24
- package/scripts/check-structured-subagent-output-regression.js +0 -331
- package/scripts/check-telegram-bridge-watchdog.js +0 -60
- package/scripts/check-telegram-direct-work-resume-regression.js +0 -53
- package/scripts/check-telegram-loop-exactly-once-regression.js +0 -71
- package/scripts/check-todo-loop-regression.js +0 -78
- package/scripts/check-todo-progress-regression.js +0 -416
- package/scripts/check-todo-recovery-catalog-regression.js +0 -50
- package/scripts/check-tool-schema-capacity-regression.js +0 -40
- package/scripts/programmatic-tool-runtime.test.mjs +0 -365
- package/scripts/structured-subagent-output.test.cjs +0 -170
- /package/{scripts → bin}/fix-node-pty-perms.js +0 -0
package/codebase-index/README.md
CHANGED
|
@@ -31,6 +31,18 @@ python codebase_index.py query <repo> "wo wird die retry-Kappe gesetzt?"
|
|
|
31
31
|
python codebase_index.py sample <repo>
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
`query` und `sample` waehlen standardmaessig `--model auto`: Ein vorhandener
|
|
35
|
+
Jina-Code-Index wird wegen des gemessenen 3/3-Qualitaetsgates bevorzugt, ein
|
|
36
|
+
vorhandener BGE-Index bleibt Fallback. `--model jina-code` und `--model bge`
|
|
37
|
+
waehlen explizit. Build und Update bleiben modellgebunden und akzeptieren kein
|
|
38
|
+
`auto`.
|
|
39
|
+
|
|
40
|
+
Vorhandene Indizes werden ueber die kanonische Repository-Identitaet im
|
|
41
|
+
Manifest gefunden, auch wenn ein aelterer BLUN-Stand einen anderen
|
|
42
|
+
Workspace-Hash als Ordnernamen erzeugt hat. Fremde Repositories, unbekannte
|
|
43
|
+
Modelle, falsche Dimensionen, fehlende Vektordateien und Links aus dem
|
|
44
|
+
Index-State heraus werden ignoriert.
|
|
45
|
+
|
|
34
46
|
Index-Ort: `~/.blun/codebase-index/<workspace-hash>/` mit
|
|
35
47
|
`vectors.npy` (float32-Matrix), `manifest.json` (Hashes, Spans, Meta,
|
|
36
48
|
Stand) und `query-log.jsonl` (jede Query — der Moat: welche Begriffe
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""codebase-index — lokaler semantischer Index ueber einen Git-Code-Baum.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
Auflagen Dieter 43102:
|
|
4
|
+
Lokaler Index nach dem geprueften Streaming-Konzept:
|
|
6
5
|
- Streaming in vorallokierte float32-Matrix (memmap), KEINE Vektoren-Liste
|
|
7
6
|
(Run-3-Befund: Liste trieb RAM-Spitze auf 10,5 GB).
|
|
8
7
|
- Inkrementell: Content-Hash je Datei im Manifest, Delta statt Vollindex.
|
|
@@ -36,8 +35,18 @@ CHUNK_OVERLAP = 100
|
|
|
36
35
|
EMBED_DIM = 384
|
|
37
36
|
BATCH = 256
|
|
38
37
|
MODEL_NAME = "BAAI/bge-small-en-v1.5"
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
JINA_CODE_MODEL_NAME = "jinaai/jina-embeddings-v2-base-code"
|
|
39
|
+
KNOWN_MODELS = {
|
|
40
|
+
MODEL_NAME: 384,
|
|
41
|
+
JINA_CODE_MODEL_NAME: 768,
|
|
42
|
+
}
|
|
43
|
+
MODEL_ALIASES = {
|
|
44
|
+
"bge": MODEL_NAME,
|
|
45
|
+
"jina-code": JINA_CODE_MODEL_NAME,
|
|
46
|
+
}
|
|
47
|
+
AUTO_MODEL_ORDER = (JINA_CODE_MODEL_NAME, MODEL_NAME)
|
|
48
|
+
|
|
49
|
+
# Rausch-Filter: Backup-Kopien und Vendor-
|
|
41
50
|
# Buelle machten 66% aller Index-Rows aus und dominieren Top-5.
|
|
42
51
|
EXCLUDE_PREFIXES = ("backup-", "vendor/")
|
|
43
52
|
|
|
@@ -45,7 +54,7 @@ EXCLUDE_PREFIXES = ("backup-", "vendor/")
|
|
|
45
54
|
_active_model_name = MODEL_NAME
|
|
46
55
|
_active_dim = EMBED_DIM
|
|
47
56
|
|
|
48
|
-
# Stichprobe
|
|
57
|
+
# Stichprobe: Fragen PARAPHRASIERT aus gelesenem
|
|
49
58
|
# Dateiinhalt — keine wörtlichen Bezeichner/Funktionsnamen/Kommentare aus
|
|
50
59
|
# den Dateien (misst semantische Suche, nicht Wortgleichheit).
|
|
51
60
|
# api-chat.js: routet Chat-Nachrichten ans Modell, Fallback-Kette, Stream.
|
|
@@ -63,10 +72,96 @@ SELSTAUSKUNFT = (
|
|
|
63
72
|
)
|
|
64
73
|
|
|
65
74
|
|
|
75
|
+
def canonical_repo_path(repo: str) -> str:
|
|
76
|
+
return os.path.normcase(os.path.realpath(os.path.abspath(repo)))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def index_root() -> str:
|
|
80
|
+
return os.path.join(os.path.expanduser("~"), ".blun", "codebase-index")
|
|
81
|
+
|
|
82
|
+
|
|
66
83
|
def index_dir(repo: str) -> str:
|
|
67
|
-
raw =
|
|
84
|
+
raw = canonical_repo_path(repo) + "|" + _active_model_name
|
|
68
85
|
key = hashlib.sha256(raw.encode()).hexdigest()[:12]
|
|
69
|
-
return os.path.join(
|
|
86
|
+
return os.path.join(index_root(), key)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def normalize_model_selector(selector: str) -> str:
|
|
90
|
+
if selector == "auto":
|
|
91
|
+
return selector
|
|
92
|
+
model = MODEL_ALIASES.get(selector, selector)
|
|
93
|
+
if model not in KNOWN_MODELS:
|
|
94
|
+
allowed = ", ".join(("auto", *MODEL_ALIASES, *KNOWN_MODELS))
|
|
95
|
+
raise ValueError(f"Unsupported index model {selector!r}; choose one of: {allowed}")
|
|
96
|
+
return model
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def find_existing_indexes(repo: str) -> list[tuple[str, dict]]:
|
|
100
|
+
root = index_root()
|
|
101
|
+
if not os.path.isdir(root):
|
|
102
|
+
return []
|
|
103
|
+
canonical_repo = canonical_repo_path(repo)
|
|
104
|
+
canonical_root = os.path.realpath(root)
|
|
105
|
+
candidates = []
|
|
106
|
+
for entry in os.scandir(root):
|
|
107
|
+
if not entry.is_dir(follow_symlinks=False):
|
|
108
|
+
continue
|
|
109
|
+
directory = os.path.realpath(entry.path)
|
|
110
|
+
try:
|
|
111
|
+
if os.path.commonpath((canonical_root, directory)) != canonical_root:
|
|
112
|
+
continue
|
|
113
|
+
except ValueError:
|
|
114
|
+
continue
|
|
115
|
+
manifest_path = os.path.join(directory, "manifest.json")
|
|
116
|
+
vectors_path = os.path.join(directory, "vectors.npy")
|
|
117
|
+
if not os.path.isfile(manifest_path) or not os.path.isfile(vectors_path):
|
|
118
|
+
continue
|
|
119
|
+
try:
|
|
120
|
+
manifest = load_manifest(directory)
|
|
121
|
+
except (OSError, ValueError, TypeError):
|
|
122
|
+
continue
|
|
123
|
+
model = manifest.get("model")
|
|
124
|
+
if model not in KNOWN_MODELS or manifest.get("dim") != KNOWN_MODELS[model]:
|
|
125
|
+
continue
|
|
126
|
+
manifest_repo = manifest.get("repo")
|
|
127
|
+
if not isinstance(manifest_repo, str):
|
|
128
|
+
continue
|
|
129
|
+
if canonical_repo_path(manifest_repo) != canonical_repo:
|
|
130
|
+
continue
|
|
131
|
+
candidates.append((directory, manifest))
|
|
132
|
+
return sorted(candidates, key=lambda item: item[0])
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def resolve_existing_index(repo: str, selector: str = "auto") -> tuple[str, dict]:
|
|
136
|
+
requested = normalize_model_selector(selector)
|
|
137
|
+
candidates = find_existing_indexes(repo)
|
|
138
|
+
model_order = AUTO_MODEL_ORDER if requested == "auto" else (requested,)
|
|
139
|
+
for model in model_order:
|
|
140
|
+
matches = [item for item in candidates if item[1]["model"] == model]
|
|
141
|
+
if matches:
|
|
142
|
+
return max(
|
|
143
|
+
matches,
|
|
144
|
+
key=lambda item: (str(item[1].get("built_at", "")), item[0]),
|
|
145
|
+
)
|
|
146
|
+
available = sorted({item[1]["model"] for item in candidates})
|
|
147
|
+
suffix = f"; available for this repository: {', '.join(available)}" if available else ""
|
|
148
|
+
raise FileNotFoundError(
|
|
149
|
+
f"No compatible codebase index for {os.path.abspath(repo)!r} and selector {selector!r}{suffix}"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def activate_model(model: str, dim: int | None = None) -> None:
|
|
154
|
+
global _active_model_name, _active_dim
|
|
155
|
+
normalized = normalize_model_selector(model)
|
|
156
|
+
if normalized == "auto":
|
|
157
|
+
raise ValueError("auto can select an existing index only; it cannot build a new one")
|
|
158
|
+
expected_dim = KNOWN_MODELS[normalized]
|
|
159
|
+
if dim is not None and dim != expected_dim:
|
|
160
|
+
raise ValueError(
|
|
161
|
+
f"Index dimension {dim} does not match {normalized} ({expected_dim})"
|
|
162
|
+
)
|
|
163
|
+
_active_model_name = normalized
|
|
164
|
+
_active_dim = expected_dim
|
|
70
165
|
|
|
71
166
|
|
|
72
167
|
def git_files(repo: str) -> list[str]:
|
|
@@ -261,9 +356,9 @@ def freshness(repo: str, man: dict) -> str:
|
|
|
261
356
|
return f"Index-Stand: {man['head']} ({man['built_at']}), {man['chunks']} Chunks | {same}"
|
|
262
357
|
|
|
263
358
|
|
|
264
|
-
def cmd_query(repo: str, question: str, top: int) -> int:
|
|
265
|
-
dirpath =
|
|
266
|
-
man
|
|
359
|
+
def cmd_query(repo: str, question: str, top: int, selector: str = "auto") -> int:
|
|
360
|
+
dirpath, man = resolve_existing_index(repo, selector)
|
|
361
|
+
activate_model(man["model"], man["dim"])
|
|
267
362
|
mat = np.load(os.path.join(dirpath, "vectors.npy"))
|
|
268
363
|
norms = np.linalg.norm(mat, axis=1, keepdims=True)
|
|
269
364
|
mat_n = mat / np.maximum(norms, 1e-12)
|
|
@@ -278,6 +373,7 @@ def cmd_query(repo: str, question: str, top: int) -> int:
|
|
|
278
373
|
|
|
279
374
|
hits = [(man["meta"][i], round(float(scores[i]), 4)) for i in idx]
|
|
280
375
|
print(SELSTAUSKUNFT)
|
|
376
|
+
print(f"Index-Modell: {man['model']}")
|
|
281
377
|
print(freshness(repo, man))
|
|
282
378
|
for m, s in hits:
|
|
283
379
|
print(f" {s:.4f} {m}")
|
|
@@ -295,10 +391,10 @@ def cmd_query(repo: str, question: str, top: int) -> int:
|
|
|
295
391
|
return 0
|
|
296
392
|
|
|
297
393
|
|
|
298
|
-
def cmd_sample(repo: str) -> int:
|
|
394
|
+
def cmd_sample(repo: str, selector: str = "auto") -> int:
|
|
299
395
|
"""Gate: Stichprobe aus dem Subjekt-Baum, Ziel >= 2/3 in Top-5."""
|
|
300
|
-
dirpath =
|
|
301
|
-
man
|
|
396
|
+
dirpath, man = resolve_existing_index(repo, selector)
|
|
397
|
+
activate_model(man["model"], man["dim"])
|
|
302
398
|
mat = np.load(os.path.join(dirpath, "vectors.npy"))
|
|
303
399
|
norms = np.linalg.norm(mat, axis=1, keepdims=True)
|
|
304
400
|
mat_n = mat / np.maximum(norms, 1e-12)
|
|
@@ -326,8 +422,16 @@ def main() -> int:
|
|
|
326
422
|
if len(sys.argv) < 3:
|
|
327
423
|
print(__doc__)
|
|
328
424
|
return 2
|
|
329
|
-
|
|
330
|
-
|
|
425
|
+
cmd, repo = sys.argv[1], sys.argv[2]
|
|
426
|
+
selector = sys.argv[sys.argv.index("--model") + 1] if "--model" in sys.argv else (
|
|
427
|
+
"auto" if cmd in ("query", "sample") else MODEL_NAME
|
|
428
|
+
)
|
|
429
|
+
if cmd in ("build", "update"):
|
|
430
|
+
try:
|
|
431
|
+
activate_model(selector)
|
|
432
|
+
except ValueError as error:
|
|
433
|
+
print(f"MODEL_ERROR {error}")
|
|
434
|
+
return 2
|
|
331
435
|
from fastembed import TextEmbedding
|
|
332
436
|
|
|
333
437
|
dims = {m["model"]: m.get("dim") for m in TextEmbedding.list_supported_models()}
|
|
@@ -336,7 +440,6 @@ def main() -> int:
|
|
|
336
440
|
return 2
|
|
337
441
|
_active_dim = int(dims[_active_model_name])
|
|
338
442
|
print(f"MODEL {_active_model_name} dim={_active_dim}")
|
|
339
|
-
cmd, repo = sys.argv[1], sys.argv[2]
|
|
340
443
|
if cmd == "build":
|
|
341
444
|
cmd_build(repo)
|
|
342
445
|
return 0
|
|
@@ -347,9 +450,17 @@ def main() -> int:
|
|
|
347
450
|
top = 5
|
|
348
451
|
if "--top" in sys.argv:
|
|
349
452
|
top = int(sys.argv[sys.argv.index("--top") + 1])
|
|
350
|
-
|
|
453
|
+
try:
|
|
454
|
+
return cmd_query(repo, sys.argv[3], top, selector)
|
|
455
|
+
except (FileNotFoundError, ValueError) as error:
|
|
456
|
+
print(f"INDEX_ERROR {error}")
|
|
457
|
+
return 2
|
|
351
458
|
if cmd == "sample":
|
|
352
|
-
|
|
459
|
+
try:
|
|
460
|
+
return cmd_sample(repo, selector)
|
|
461
|
+
except (FileNotFoundError, ValueError) as error:
|
|
462
|
+
print(f"INDEX_ERROR {error}")
|
|
463
|
+
return 2
|
|
353
464
|
print(f"unknown command: {cmd}")
|
|
354
465
|
return 2
|
|
355
466
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blun-king-cli",
|
|
3
|
-
"version": "9.1.
|
|
3
|
+
"version": "9.1.550",
|
|
4
4
|
"description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -8,10 +8,7 @@
|
|
|
8
8
|
"king": "bin/king.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"
|
|
12
|
-
"prepack": "node scripts/check-release-metadata.js && node scripts/check-current-turn-read-pin-regression.js && node scripts/check-current-turn-read-pin-mutation-regression.js && node scripts/check-active-work-steer-regression.js && node scripts/check-empty-response-effort-downgrade-regression.js && node scripts/check-tool-schema-capacity-regression.js && node scripts/check-history-pressure-offload-regression.js && node scripts/check-startup-swarm-command-regression.js && node scripts/check-telegram-direct-work-resume-regression.js && node scripts/check-fredrik-glm-regression.js && node scripts/check-fredrik-model-picker-regression.js && node scripts/check-fredrik-glm-mutation-regression.js && node scripts/check-deepseek-native-max-regression.js && node scripts/check-programmatic-tool-regression.js && node scripts/check-programmatic-context-isolation-regression.js && node scripts/check-structured-subagent-output-regression.js && node scripts/check-codebase-search-packaging-regression.js && node scripts/check-copy-command-regression.js && node scripts/check-shell-terminal-isolation-regression.js && node scripts/check-todo-loop-regression.js && node scripts/check-todo-progress-regression.js && node scripts/check-telegram-loop-exactly-once-regression.js && node scripts/check-session-picker-resume-metrics-regression.js && node scripts/check-bundled-agent-spine-regression.js && node scripts/check-reload-agent-spine-regression.js && node scripts/check-todo-recovery-catalog-regression.js && node scripts/check-historical-tool-result-preview-regression.js && node scripts/check-session-start-hook-context-regression.js && node scripts/check-queue-controls-regression.js && node scripts/check-approval-queue-shortcuts-regression.js && node scripts/check-approval-observability-regression.js && node scripts/check-slash-escape-regression.js && node scripts/check-telegram-bridge-watchdog.js && node scripts/check-resume-replay-regression.js && node scripts/check-session-cancel-regression.js && node scripts/check-plugin-startup-regression.js && node scripts/check-active-profile-plugin-startup.js && node scripts/check-mcp-startup-wait-budget.js",
|
|
13
|
-
"release:verify": "node scripts/check-release-metadata.js --external",
|
|
14
|
-
"postinstall": "node scripts/fix-node-pty-perms.js"
|
|
11
|
+
"postinstall": "node bin/fix-node-pty-perms.js"
|
|
15
12
|
},
|
|
16
13
|
"engines": {
|
|
17
14
|
"node": ">=24.15.0"
|
|
@@ -31,58 +28,27 @@
|
|
|
31
28
|
},
|
|
32
29
|
"files": [
|
|
33
30
|
"bin/",
|
|
31
|
+
"!bin/package-regression-policy.cjs",
|
|
34
32
|
"blun.mjs",
|
|
35
|
-
"codebase-index/",
|
|
33
|
+
"codebase-index/codebase_index.py",
|
|
34
|
+
"codebase-index/README.md",
|
|
36
35
|
"dist-web/",
|
|
37
36
|
"native/",
|
|
38
|
-
"release-planned-removals.json",
|
|
39
|
-
"scripts/check-package-regression.js",
|
|
40
|
-
"scripts/check-fredrik-glm-regression.js",
|
|
41
|
-
"scripts/check-fredrik-glm-mutation-regression.js",
|
|
42
|
-
"scripts/check-deepseek-native-max-regression.js",
|
|
43
|
-
"scripts/check-programmatic-tool-regression.js",
|
|
44
|
-
"scripts/check-programmatic-context-isolation-regression.js",
|
|
45
|
-
"scripts/programmatic-tool-runtime.test.mjs",
|
|
46
|
-
"scripts/check-structured-subagent-output-regression.js",
|
|
47
|
-
"scripts/structured-subagent-output.test.cjs",
|
|
48
|
-
"scripts/check-codebase-search-packaging-regression.js",
|
|
49
|
-
"scripts/check-copy-command-regression.js",
|
|
50
|
-
"scripts/check-active-profile-plugin-startup.js",
|
|
51
|
-
"scripts/check-approval-queue-shortcuts-regression.js",
|
|
52
|
-
"scripts/check-bundled-agent-spine-regression.js",
|
|
53
|
-
"scripts/check-reload-agent-spine-regression.js",
|
|
54
|
-
"scripts/check-approval-observability-regression.js",
|
|
55
|
-
"scripts/check-slash-escape-regression.js",
|
|
56
|
-
"scripts/check-mcp-startup-wait-budget.js",
|
|
57
|
-
"scripts/check-plugin-startup-regression.js",
|
|
58
|
-
"scripts/check-queue-controls-regression.js",
|
|
59
|
-
"scripts/check-telegram-bridge-watchdog.js",
|
|
60
|
-
"scripts/check-resume-replay-regression.js",
|
|
61
|
-
"scripts/check-session-cancel-regression.js",
|
|
62
|
-
"scripts/check-shell-terminal-isolation-regression.js",
|
|
63
|
-
"scripts/check-session-picker-resume-metrics-regression.js",
|
|
64
|
-
"scripts/check-release-metadata.js",
|
|
65
|
-
"scripts/check-historical-tool-result-preview-regression.js",
|
|
66
|
-
"scripts/check-current-turn-read-pin-regression.js",
|
|
67
|
-
"scripts/check-current-turn-read-pin-mutation-regression.js",
|
|
68
|
-
"scripts/check-active-work-steer-regression.js",
|
|
69
|
-
"scripts/check-empty-response-effort-downgrade-regression.js",
|
|
70
|
-
"scripts/check-tool-schema-capacity-regression.js",
|
|
71
|
-
"scripts/check-history-pressure-offload-regression.js",
|
|
72
|
-
"scripts/check-startup-swarm-command-regression.js",
|
|
73
|
-
"scripts/check-telegram-direct-work-resume-regression.js",
|
|
74
|
-
"scripts/check-session-start-hook-context-regression.js",
|
|
75
|
-
"scripts/check-telegram-loop-exactly-once-regression.js",
|
|
76
|
-
"scripts/check-todo-recovery-catalog-regression.js",
|
|
77
|
-
"scripts/check-todo-loop-regression.js",
|
|
78
|
-
"scripts/check-todo-progress-regression.js",
|
|
79
|
-
"scripts/fix-node-pty-perms.js",
|
|
80
37
|
"standard-skills/",
|
|
81
38
|
"standard-tools/",
|
|
82
|
-
"agent-spine-plugin/",
|
|
39
|
+
"agent-spine-plugin/.claude-plugin/",
|
|
40
|
+
"agent-spine-plugin/.codex-plugin/",
|
|
41
|
+
"agent-spine-plugin/.mcp.json",
|
|
42
|
+
"agent-spine-plugin/assets/",
|
|
43
|
+
"agent-spine-plugin/bin/",
|
|
44
|
+
"agent-spine-plugin/blun.plugin.json",
|
|
45
|
+
"agent-spine-plugin/hooks/",
|
|
46
|
+
"agent-spine-plugin/LICENSE",
|
|
47
|
+
"agent-spine-plugin/package.json",
|
|
48
|
+
"agent-spine-plugin/skill/",
|
|
49
|
+
"agent-spine-plugin/skills/",
|
|
50
|
+
"agent-spine-plugin/src/",
|
|
83
51
|
"telegram-plugin/",
|
|
84
|
-
"fredrik-glm-profile.toml.example",
|
|
85
|
-
"CHANGELOG.md",
|
|
86
52
|
"LIESMICH.txt"
|
|
87
53
|
],
|
|
88
54
|
"keywords": [
|
|
@@ -94,14 +60,13 @@
|
|
|
94
60
|
],
|
|
95
61
|
"author": "BLUN",
|
|
96
62
|
"main": "index.js",
|
|
97
|
-
"directories": {
|
|
98
|
-
"test": "test"
|
|
99
|
-
},
|
|
100
63
|
"type": "commonjs",
|
|
101
64
|
"dependencies": {
|
|
102
|
-
"
|
|
65
|
+
"@mozilla/readability": "0.6.0",
|
|
66
|
+
"linkedom": "0.18.12",
|
|
103
67
|
"node-addon-api": "^7.1.1",
|
|
104
|
-
"quickjs-emscripten": "0.32.0"
|
|
105
|
-
|
|
106
|
-
|
|
68
|
+
"quickjs-emscripten": "0.32.0",
|
|
69
|
+
"turndown": "7.2.4",
|
|
70
|
+
"turndown-plugin-gfm": "1.0.2"
|
|
71
|
+
}
|
|
107
72
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_INTERVAL_MS = 4_000;
|
|
4
|
+
const DEFAULT_MAX_DURATION_MS = 30 * 60_000;
|
|
5
|
+
|
|
6
|
+
function positiveInteger(value, fallback) {
|
|
7
|
+
return Number.isSafeInteger(value) && value > 0 ? value : fallback;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function createTelegramTypingKeepalive(options = {}) {
|
|
11
|
+
if (typeof options.sendTyping !== 'function') {
|
|
12
|
+
throw new TypeError('sendTyping must be a function');
|
|
13
|
+
}
|
|
14
|
+
if (typeof options.isComplete !== 'function') {
|
|
15
|
+
throw new TypeError('isComplete must be a function');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const now = typeof options.now === 'function' ? options.now : Date.now;
|
|
19
|
+
const setTimer = options.setTimer ?? setTimeout;
|
|
20
|
+
const clearTimer = options.clearTimer ?? clearTimeout;
|
|
21
|
+
const intervalMs = positiveInteger(options.intervalMs, DEFAULT_INTERVAL_MS);
|
|
22
|
+
const maxDurationMs = positiveInteger(options.maxDurationMs, DEFAULT_MAX_DURATION_MS);
|
|
23
|
+
const active = new Map();
|
|
24
|
+
|
|
25
|
+
function stop(chatIdValue) {
|
|
26
|
+
const chatId = String(chatIdValue);
|
|
27
|
+
const state = active.get(chatId);
|
|
28
|
+
if (state === undefined) return false;
|
|
29
|
+
if (state.timer !== undefined) clearTimer(state.timer);
|
|
30
|
+
active.delete(chatId);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function schedule(state) {
|
|
35
|
+
state.timer = setTimer(() => tick(state), intervalMs);
|
|
36
|
+
state.timer?.unref?.();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function tick(state) {
|
|
40
|
+
if (active.get(state.chatId) !== state) return;
|
|
41
|
+
state.timer = undefined;
|
|
42
|
+
if (now() >= state.deadline || options.isComplete(state.marker, state.chatId)) {
|
|
43
|
+
stop(state.chatId);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
await options.sendTyping(state.chatId);
|
|
48
|
+
} catch {}
|
|
49
|
+
if (active.get(state.chatId) !== state) return;
|
|
50
|
+
if (now() >= state.deadline || options.isComplete(state.marker, state.chatId)) {
|
|
51
|
+
stop(state.chatId);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
schedule(state);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function begin(chatIdValue, marker) {
|
|
58
|
+
const chatId = String(chatIdValue ?? '').trim();
|
|
59
|
+
if (chatId.length === 0) return false;
|
|
60
|
+
stop(chatId);
|
|
61
|
+
const state = {
|
|
62
|
+
chatId,
|
|
63
|
+
marker,
|
|
64
|
+
deadline: now() + maxDurationMs,
|
|
65
|
+
timer: undefined,
|
|
66
|
+
};
|
|
67
|
+
active.set(chatId, state);
|
|
68
|
+
void tick(state);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function dispose() {
|
|
73
|
+
for (const chatId of [...active.keys()]) stop(chatId);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
activeCount: () => active.size,
|
|
78
|
+
begin,
|
|
79
|
+
dispose,
|
|
80
|
+
has: (chatId) => active.has(String(chatId)),
|
|
81
|
+
stop,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
DEFAULT_INTERVAL_MS,
|
|
87
|
+
DEFAULT_MAX_DURATION_MS,
|
|
88
|
+
createTelegramTypingKeepalive,
|
|
89
|
+
};
|
|
@@ -13,6 +13,7 @@ import privateConversationPolicy from "../bin/telegram-private-conversation-poli
|
|
|
13
13
|
import telegramTextChunkPolicy from "../bin/telegram-text-chunk-policy.cjs";
|
|
14
14
|
import telegramMnemoCapture from "../bin/telegram-mnemo-capture.cjs";
|
|
15
15
|
import telegramDeliveryLifecyclePolicy from "../bin/telegram-delivery-lifecycle.cjs";
|
|
16
|
+
import telegramTypingKeepalivePolicy from "../bin/telegram-typing-keepalive.cjs";
|
|
16
17
|
const { buildTelegramRemoteStatus, resolveTelegramQueueSnapshot, resolveTelegramRemoteVersion } = remoteStatusPolicy;
|
|
17
18
|
const { parseTelegramConsoleStatus } = consoleStatusPolicy;
|
|
18
19
|
const { buildTelegramApprovalCard, isAuthorizedTelegramApprovalCallback, listPendingTelegramApprovals, markTelegramApprovalSent, parseTelegramApprovalCallback, resolveTelegramApprovalTarget, wasTelegramApprovalSent, writeTelegramApprovalResponse } = telegramApprovalRelay;
|
|
@@ -20,6 +21,7 @@ const { findDuplicateReplyWithoutNewInbound, isPrivateInternalStatusReply, sanit
|
|
|
20
21
|
const { sendTelegramTextChunks } = telegramTextChunkPolicy;
|
|
21
22
|
const { createTelegramMnemoCapture } = telegramMnemoCapture;
|
|
22
23
|
const { createTelegramDeliveryLifecycle, telegramDeliveryIdentity } = telegramDeliveryLifecyclePolicy;
|
|
24
|
+
const { createTelegramTypingKeepalive } = telegramTypingKeepalivePolicy;
|
|
23
25
|
//#region ../../node_modules/pathe/dist/shared/pathe.M-eThtNZ.mjs
|
|
24
26
|
const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
|
|
25
27
|
function normalizeWindowsPath(input = "") {
|
|
@@ -4650,6 +4652,10 @@ function alreadySeen(key) {
|
|
|
4650
4652
|
const MAX_INBOUND_AGE_S = Number(process.env.BLUN_TELEGRAM_MAX_INBOUND_AGE_S ?? 900);
|
|
4651
4653
|
const HEADLESS_ENABLED = (process.env.BLUN_TELEGRAM_HEADLESS ?? "on").trim().toLowerCase() !== "off";
|
|
4652
4654
|
const deliveryLifecycle = createTelegramDeliveryLifecycle({ stateDir: stateDir() });
|
|
4655
|
+
const typingKeepalive = createTelegramTypingKeepalive({
|
|
4656
|
+
sendTyping: (chatId) => bot.api.sendChatAction(chatId, "typing"),
|
|
4657
|
+
isComplete: (marker, chatId) => outboxGrewForChat(marker, chatId)
|
|
4658
|
+
});
|
|
4653
4659
|
let lastMnemoCaptureErrorAt = 0;
|
|
4654
4660
|
const mnemoCapture = createTelegramMnemoCapture({
|
|
4655
4661
|
env: process.env,
|
|
@@ -4700,7 +4706,7 @@ async function handleInbound(event) {
|
|
|
4700
4706
|
const targetedElsewhere = result.action === "deliver" ? result.targetedElsewhere ?? false : false;
|
|
4701
4707
|
const priority = result.action === "deliver" ? result.priority : void 0;
|
|
4702
4708
|
if (addressed) {
|
|
4703
|
-
|
|
4709
|
+
typingKeepalive.begin(chatId, outboxMarker());
|
|
4704
4710
|
if (result.access.ackReaction !== void 0 && result.access.ackReaction !== "" && msgId !== void 0) bot.api.setMessageReaction(chatId, msgId, [{
|
|
4705
4711
|
type: "emoji",
|
|
4706
4712
|
emoji: result.access.ackReaction
|
|
@@ -4863,6 +4869,7 @@ function shutdown() {
|
|
|
4863
4869
|
shuttingDown = true;
|
|
4864
4870
|
process.stderr.write("telegram bridge: shutting down\n");
|
|
4865
4871
|
releasePollerPid();
|
|
4872
|
+
typingKeepalive.dispose();
|
|
4866
4873
|
session.dispose();
|
|
4867
4874
|
setTimeout(() => process.exit(0), 2e3).unref();
|
|
4868
4875
|
Promise.resolve(bot.stop()).finally(() => process.exit(0));
|