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.
Files changed (117) hide show
  1. package/LIESMICH.txt +13 -869
  2. package/README.md +41 -833
  3. package/bin/assistant-message-offload-policy.cjs +3 -1
  4. package/bin/context-performance-policy.cjs +2 -5
  5. package/bin/context-pressure-policy.cjs +20 -0
  6. package/bin/cron-run-output.cjs +45 -0
  7. package/bin/cron-run-store.cjs +145 -0
  8. package/bin/durable-task-resume-policy.cjs +130 -0
  9. package/bin/durable-task-resume-runtime.cjs +117 -0
  10. package/bin/durable-task-resume-store.cjs +88 -0
  11. package/bin/editable-tool-approval-policy.cjs +540 -0
  12. package/bin/editable-tool-approval-runtime.cjs +99 -0
  13. package/bin/html-to-research-markdown.cjs +146 -0
  14. package/bin/programmatic-tool-runtime.mjs +330 -4
  15. package/bin/read-continuation-policy.cjs +36 -5
  16. package/bin/scoped-cron-run-policy.cjs +358 -0
  17. package/bin/startup-preferences.cjs +3 -3
  18. package/bin/structured-agent-swarm-output.cjs +325 -0
  19. package/bin/subagent-context-fork-policy.cjs +155 -0
  20. package/bin/subagent-skill-policy.cjs +204 -0
  21. package/bin/telegram-approval-relay.cjs +2 -1
  22. package/bin/tool-result-offload-policy.cjs +14 -1
  23. package/bin/update-notice.js +14 -18
  24. package/bin/user-message-offload-policy.cjs +3 -1
  25. package/blun.mjs +1089 -477
  26. package/codebase-index/README.md +12 -0
  27. package/codebase-index/codebase_index.py +129 -18
  28. package/package.json +23 -58
  29. package/telegram-plugin/bin/telegram-typing-keepalive.cjs +89 -0
  30. package/telegram-plugin/dist/bridge.mjs +8 -1
  31. package/CHANGELOG.md +0 -321
  32. package/agent-spine-plugin/CHANGELOG.md +0 -406
  33. package/agent-spine-plugin/CONTRIBUTING.md +0 -52
  34. package/agent-spine-plugin/README.md +0 -344
  35. package/agent-spine-plugin/SECURITY.md +0 -47
  36. package/agent-spine-plugin/docs/acceptance.md +0 -61
  37. package/agent-spine-plugin/docs/architecture.md +0 -183
  38. package/agent-spine-plugin/docs/attention.md +0 -121
  39. package/agent-spine-plugin/docs/automatic-continuity.md +0 -79
  40. package/agent-spine-plugin/docs/channel-runtime.md +0 -92
  41. package/agent-spine-plugin/docs/coordination.md +0 -138
  42. package/agent-spine-plugin/docs/feed-transport.md +0 -99
  43. package/agent-spine-plugin/docs/gateway-runtime.md +0 -116
  44. package/agent-spine-plugin/docs/harness-reference.md +0 -45
  45. package/agent-spine-plugin/docs/host-integration.md +0 -129
  46. package/agent-spine-plugin/docs/https-transport.md +0 -116
  47. package/agent-spine-plugin/docs/learning.md +0 -133
  48. package/agent-spine-plugin/docs/object-transport.md +0 -93
  49. package/agent-spine-plugin/docs/peer-transport.md +0 -88
  50. package/agent-spine-plugin/docs/preflight-recall.md +0 -69
  51. package/agent-spine-plugin/docs/preservation-contract.md +0 -53
  52. package/agent-spine-plugin/docs/quality-gates.md +0 -50
  53. package/agent-spine-plugin/docs/relationships.md +0 -73
  54. package/agent-spine-plugin/docs/releasing.md +0 -83
  55. package/agent-spine-plugin/docs/roadmap.md +0 -307
  56. package/agent-spine-plugin/docs/selfstarter.md +0 -88
  57. package/agent-spine-plugin/docs/session-briefing.md +0 -74
  58. package/agent-spine-plugin/docs/shared-memory.md +0 -259
  59. package/agent-spine-plugin/docs/source-roots.md +0 -86
  60. package/agent-spine-plugin/docs/sqlite-transport.md +0 -76
  61. package/agent-spine-plugin/scripts/check-hosts.js +0 -195
  62. package/agent-spine-plugin/scripts/check-install.js +0 -569
  63. package/agent-spine-plugin/scripts/check-syntax.js +0 -29
  64. package/agent-spine-plugin/scripts/github-actions.js +0 -11
  65. package/agent-spine-plugin/scripts/release-check.js +0 -128
  66. package/agent-spine-plugin/scripts/run-acceptance.js +0 -19
  67. package/agent-spine-plugin/scripts/run-checks.js +0 -46
  68. package/agent-spine-plugin/scripts/run-tests-hermetic.js +0 -73
  69. package/agent-spine-plugin/spine-example/1-identity.md +0 -12
  70. package/agent-spine-plugin/spine-example/2-voice.md +0 -6
  71. package/agent-spine-plugin/spine-example/3-conduct.md +0 -8
  72. package/agent-spine-plugin/spine-example/4-history.md +0 -4
  73. package/bin/fredrik-glm-provider.cjs +0 -256
  74. package/bin/package-regression-policy.cjs +0 -77
  75. package/fredrik-glm-profile.toml.example +0 -26
  76. package/release-planned-removals.json +0 -15
  77. package/scripts/check-active-profile-plugin-startup.js +0 -36
  78. package/scripts/check-active-work-steer-regression.js +0 -46
  79. package/scripts/check-approval-observability-regression.js +0 -111
  80. package/scripts/check-approval-queue-shortcuts-regression.js +0 -65
  81. package/scripts/check-bundled-agent-spine-regression.js +0 -48
  82. package/scripts/check-codebase-search-packaging-regression.js +0 -92
  83. package/scripts/check-copy-command-regression.js +0 -74
  84. package/scripts/check-current-turn-read-pin-mutation-regression.js +0 -72
  85. package/scripts/check-current-turn-read-pin-regression.js +0 -94
  86. package/scripts/check-deepseek-native-max-regression.js +0 -49
  87. package/scripts/check-empty-response-effort-downgrade-regression.js +0 -48
  88. package/scripts/check-fredrik-glm-mutation-regression.js +0 -18
  89. package/scripts/check-fredrik-glm-regression.js +0 -169
  90. package/scripts/check-historical-tool-result-preview-regression.js +0 -77
  91. package/scripts/check-history-pressure-offload-regression.js +0 -77
  92. package/scripts/check-mcp-startup-wait-budget.js +0 -48
  93. package/scripts/check-package-regression.js +0 -38
  94. package/scripts/check-plugin-startup-regression.js +0 -53
  95. package/scripts/check-programmatic-context-isolation-regression.js +0 -193
  96. package/scripts/check-programmatic-tool-regression.js +0 -294
  97. package/scripts/check-queue-controls-regression.js +0 -189
  98. package/scripts/check-release-metadata.js +0 -103
  99. package/scripts/check-reload-agent-spine-regression.js +0 -76
  100. package/scripts/check-resume-replay-regression.js +0 -102
  101. package/scripts/check-session-cancel-regression.js +0 -43
  102. package/scripts/check-session-picker-resume-metrics-regression.js +0 -97
  103. package/scripts/check-session-start-hook-context-regression.js +0 -228
  104. package/scripts/check-shell-terminal-isolation-regression.js +0 -81
  105. package/scripts/check-slash-escape-regression.js +0 -89
  106. package/scripts/check-startup-swarm-command-regression.js +0 -24
  107. package/scripts/check-structured-subagent-output-regression.js +0 -331
  108. package/scripts/check-telegram-bridge-watchdog.js +0 -60
  109. package/scripts/check-telegram-direct-work-resume-regression.js +0 -53
  110. package/scripts/check-telegram-loop-exactly-once-regression.js +0 -71
  111. package/scripts/check-todo-loop-regression.js +0 -78
  112. package/scripts/check-todo-progress-regression.js +0 -416
  113. package/scripts/check-todo-recovery-catalog-regression.js +0 -50
  114. package/scripts/check-tool-schema-capacity-regression.js +0 -40
  115. package/scripts/programmatic-tool-runtime.test.mjs +0 -365
  116. package/scripts/structured-subagent-output.test.cjs +0 -170
  117. /package/{scripts → bin}/fix-node-pty-perms.js +0 -0
@@ -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
- F1-Bau nach Konzept (handoffs/codebase-verstaendnis-lokal-konzept.md),
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
- # Iteration 2 (Dieter 43135): Rausch-Filter — Backup-Kopien und Vendor-
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 V3 (Dieter 43141): Fragen PARAPHRASIERT aus gelesenem
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 = os.path.abspath(repo) + "|" + _active_model_name
84
+ raw = canonical_repo_path(repo) + "|" + _active_model_name
68
85
  key = hashlib.sha256(raw.encode()).hexdigest()[:12]
69
- return os.path.join(os.path.expanduser("~"), ".blun", "codebase-index", key)
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 = index_dir(repo)
266
- man = load_manifest(dirpath)
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 = index_dir(repo)
301
- man = load_manifest(dirpath)
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
- if "--model" in sys.argv:
330
- _active_model_name = sys.argv[sys.argv.index("--model") + 1]
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
- return cmd_query(repo, sys.argv[3], top)
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
- return cmd_sample(repo)
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.536",
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
- "test": "node --test test/*.test.js",
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
- "blun-king-cli": "^9.1.62",
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
- "devDependencies": {}
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
- bot.api.sendChatAction(chatId, "typing").catch(() => {});
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));