@a1hvdy/cc-openclaw 0.30.0 → 0.32.0

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 (207) hide show
  1. package/dist/src/channels/telegram-mirror/card-renderer.js +9 -5
  2. package/dist/src/channels/telegram-mirror/commands.js +0 -8
  3. package/dist/src/channels/telegram-mirror/status-line.js +32 -2
  4. package/dist/src/constants.js +16 -2
  5. package/dist/src/engines/persistent-session.js +11 -0
  6. package/dist/src/lib/config.js +40 -0
  7. package/dist/src/openai-compat/non-streaming-handler.js +2 -2
  8. package/dist/src/openai-compat/streaming-handler.js +121 -9
  9. package/package.json +3 -2
  10. package/dist/src/channels/adapter.d.ts +0 -103
  11. package/dist/src/channels/telegram-mirror/askuser.d.ts +0 -107
  12. package/dist/src/channels/telegram-mirror/burst-accumulator.d.ts +0 -96
  13. package/dist/src/channels/telegram-mirror/callback-mapping.d.ts +0 -61
  14. package/dist/src/channels/telegram-mirror/card-renderer.d.ts +0 -68
  15. package/dist/src/channels/telegram-mirror/card-state.d.ts +0 -83
  16. package/dist/src/channels/telegram-mirror/commands.d.ts +0 -183
  17. package/dist/src/channels/telegram-mirror/compose-buffer.d.ts +0 -71
  18. package/dist/src/channels/telegram-mirror/cost-views.d.ts +0 -58
  19. package/dist/src/channels/telegram-mirror/failure/callback-data-overflow.d.ts +0 -21
  20. package/dist/src/channels/telegram-mirror/failure/gateway-down.d.ts +0 -15
  21. package/dist/src/channels/telegram-mirror/failure/in-flight-conflict.d.ts +0 -15
  22. package/dist/src/channels/telegram-mirror/failure/index.d.ts +0 -23
  23. package/dist/src/channels/telegram-mirror/failure/model-5xx.d.ts +0 -16
  24. package/dist/src/channels/telegram-mirror/failure/network-blip.d.ts +0 -17
  25. package/dist/src/channels/telegram-mirror/failure/pool-exhausted-fallback.d.ts +0 -15
  26. package/dist/src/channels/telegram-mirror/failure/rate-limit.d.ts +0 -16
  27. package/dist/src/channels/telegram-mirror/failure/returning-after-24h.d.ts +0 -14
  28. package/dist/src/channels/telegram-mirror/failure/types.d.ts +0 -30
  29. package/dist/src/channels/telegram-mirror/inbound-handler.d.ts +0 -73
  30. package/dist/src/channels/telegram-mirror/index.d.ts +0 -32
  31. package/dist/src/channels/telegram-mirror/plan-attachment.d.ts +0 -120
  32. package/dist/src/channels/telegram-mirror/quota-reader.d.ts +0 -42
  33. package/dist/src/channels/telegram-mirror/sessions-keyboard.d.ts +0 -84
  34. package/dist/src/channels/telegram-mirror/soak-log.d.ts +0 -99
  35. package/dist/src/channels/telegram-mirror/state-machine.d.ts +0 -113
  36. package/dist/src/channels/telegram-mirror/status-line.d.ts +0 -51
  37. package/dist/src/channels/telegram-mirror/sync-commands.d.ts +0 -100
  38. package/dist/src/channels/telegram-mirror/threshold-watcher.d.ts +0 -54
  39. package/dist/src/channels/telegram-mirror/turn-bridge.d.ts +0 -125
  40. package/dist/src/cli/checks/bridge-wiring.d.ts +0 -14
  41. package/dist/src/cli/checks/config-schema.d.ts +0 -11
  42. package/dist/src/cli/checks/critical-openclaw-json-keys.d.ts +0 -21
  43. package/dist/src/cli/checks/install-path.d.ts +0 -11
  44. package/dist/src/cli/checks/patch-scaffold.d.ts +0 -17
  45. package/dist/src/cli/doctor.d.ts +0 -20
  46. package/dist/src/cli/index.d.ts +0 -8
  47. package/dist/src/cli/migrate.d.ts +0 -29
  48. package/dist/src/command-router/cc-handler.d.ts +0 -67
  49. package/dist/src/command-router/index.d.ts +0 -2
  50. package/dist/src/command-router/launch-policy.d.ts +0 -92
  51. package/dist/src/command-router/resume-policy.d.ts +0 -18
  52. package/dist/src/command-router/turn-formatter.d.ts +0 -19
  53. package/dist/src/config/loader.d.ts +0 -8
  54. package/dist/src/config/schema.d.ts +0 -192
  55. package/dist/src/constants.d.ts +0 -191
  56. package/dist/src/council/build-agent-prompt.d.ts +0 -11
  57. package/dist/src/council/cleanup-worktrees.d.ts +0 -10
  58. package/dist/src/council/consensus.d.ts +0 -20
  59. package/dist/src/council/council.d.ts +0 -67
  60. package/dist/src/council/index.d.ts +0 -2
  61. package/dist/src/council/system-prompt.d.ts +0 -16
  62. package/dist/src/council/write-worktree-claude-md.d.ts +0 -10
  63. package/dist/src/engines/base-oneshot-session.d.ts +0 -87
  64. package/dist/src/engines/heartbeat-guard.d.ts +0 -93
  65. package/dist/src/engines/index.d.ts +0 -8
  66. package/dist/src/engines/persistent-codex-session.d.ts +0 -16
  67. package/dist/src/engines/persistent-cursor-session.d.ts +0 -21
  68. package/dist/src/engines/persistent-custom-session.d.ts +0 -78
  69. package/dist/src/engines/persistent-gemini-session.d.ts +0 -21
  70. package/dist/src/engines/persistent-session.d.ts +0 -95
  71. package/dist/src/engines/resolve-bin.d.ts +0 -14
  72. package/dist/src/engines/subprocess-pool.d.ts +0 -78
  73. package/dist/src/health/handler.d.ts +0 -39
  74. package/dist/src/health/index.d.ts +0 -1
  75. package/dist/src/health/metrics.d.ts +0 -52
  76. package/dist/src/index.d.ts +0 -57
  77. package/dist/src/lib/auto-recovery.d.ts +0 -43
  78. package/dist/src/lib/cache-parity-decide.d.ts +0 -64
  79. package/dist/src/lib/cache-parity.d.ts +0 -38
  80. package/dist/src/lib/cc-cli-scan.d.ts +0 -52
  81. package/dist/src/lib/circuit-breaker.d.ts +0 -21
  82. package/dist/src/lib/config-service.d.ts +0 -106
  83. package/dist/src/lib/config.d.ts +0 -136
  84. package/dist/src/lib/cost-rollup.d.ts +0 -36
  85. package/dist/src/lib/debounce.d.ts +0 -12
  86. package/dist/src/lib/debug-tap.d.ts +0 -13
  87. package/dist/src/lib/domain-error.d.ts +0 -59
  88. package/dist/src/lib/drift-detector.d.ts +0 -46
  89. package/dist/src/lib/env-overrides.d.ts +0 -47
  90. package/dist/src/lib/error-formatter.d.ts +0 -91
  91. package/dist/src/lib/error-renderer.d.ts +0 -20
  92. package/dist/src/lib/heartbeat-config.d.ts +0 -34
  93. package/dist/src/lib/heartbeat-workaround.d.ts +0 -44
  94. package/dist/src/lib/html-render.d.ts +0 -50
  95. package/dist/src/lib/http-agent.d.ts +0 -47
  96. package/dist/src/lib/index.d.ts +0 -7
  97. package/dist/src/lib/index.js +0 -10
  98. package/dist/src/lib/json-array.d.ts +0 -10
  99. package/dist/src/lib/markdown-to-mdv2.d.ts +0 -53
  100. package/dist/src/lib/markdown-v2.d.ts +0 -27
  101. package/dist/src/lib/perf/async-compact.d.ts +0 -26
  102. package/dist/src/lib/perf/direct-sdk.d.ts +0 -26
  103. package/dist/src/lib/perf/haiku-route.d.ts +0 -19
  104. package/dist/src/lib/perf/predictive-continuation.d.ts +0 -18
  105. package/dist/src/lib/perf/read-batch.d.ts +0 -33
  106. package/dist/src/lib/perf/skill-list-collapse.d.ts +0 -22
  107. package/dist/src/lib/perf/speculative-bubble.d.ts +0 -27
  108. package/dist/src/lib/perf/typing-prefetch.d.ts +0 -25
  109. package/dist/src/lib/probes.d.ts +0 -50
  110. package/dist/src/lib/register-guard.d.ts +0 -56
  111. package/dist/src/lib/req-shape-log.d.ts +0 -31
  112. package/dist/src/lib/safe-upstream-probes.d.ts +0 -25
  113. package/dist/src/lib/session-registry.d.ts +0 -66
  114. package/dist/src/lib/spawn-async.d.ts +0 -18
  115. package/dist/src/lib/status-tee-reader.d.ts +0 -29
  116. package/dist/src/lib/sysprompt-strip.d.ts +0 -53
  117. package/dist/src/lib/telegram-bot-api.d.ts +0 -146
  118. package/dist/src/lib/telemetry.d.ts +0 -38
  119. package/dist/src/lib/test-mode.d.ts +0 -26
  120. package/dist/src/lib/trajectory.d.ts +0 -44
  121. package/dist/src/lib/vendor-paths.d.ts +0 -12
  122. package/dist/src/lifecycle/boot.d.ts +0 -48
  123. package/dist/src/lifecycle/patch-manifest.d.ts +0 -82
  124. package/dist/src/lifecycle/phase-import-upstream.d.ts +0 -12
  125. package/dist/src/lifecycle/phase-install-patches.d.ts +0 -12
  126. package/dist/src/lifecycle/phase-schedule-jobs.d.ts +0 -12
  127. package/dist/src/lifecycle/phase-start-server.d.ts +0 -11
  128. package/dist/src/lifecycle/phase-validate-config.d.ts +0 -9
  129. package/dist/src/lifecycle/phase-validate-upstream.d.ts +0 -11
  130. package/dist/src/lifecycle/phase-wire-handlers.d.ts +0 -12
  131. package/dist/src/lifecycle/safe-restart.d.ts +0 -99
  132. package/dist/src/logger.d.ts +0 -14
  133. package/dist/src/mcp/bridge.d.ts +0 -21
  134. package/dist/src/mcp/index.d.ts +0 -2
  135. package/dist/src/models.d.ts +0 -68
  136. package/dist/src/observability/event-bus.d.ts +0 -86
  137. package/dist/src/observability/get-event-bus.d.ts +0 -25
  138. package/dist/src/observability/observability-service.d.ts +0 -19
  139. package/dist/src/observability/perf-telemetry.d.ts +0 -65
  140. package/dist/src/observability/subscribers/metrics.d.ts +0 -11
  141. package/dist/src/observability/subscribers/session-capture.d.ts +0 -15
  142. package/dist/src/openai-compat/autonomy-rule.d.ts +0 -26
  143. package/dist/src/openai-compat/bridges/allowlist.d.ts +0 -19
  144. package/dist/src/openai-compat/bridges/factory.d.ts +0 -30
  145. package/dist/src/openai-compat/bridges/media-bridge.d.ts +0 -34
  146. package/dist/src/openai-compat/bridges/openclaw-api-shim.d.ts +0 -54
  147. package/dist/src/openai-compat/bridges/openclaw-native-tools.d.ts +0 -61
  148. package/dist/src/openai-compat/bridges/openclaw-tool-registry.d.ts +0 -26
  149. package/dist/src/openai-compat/bridges/tts-media-bridge.d.ts +0 -19
  150. package/dist/src/openai-compat/chat-cwd.d.ts +0 -22
  151. package/dist/src/openai-compat/cli-stream-parser.d.ts +0 -134
  152. package/dist/src/openai-compat/index.d.ts +0 -1
  153. package/dist/src/openai-compat/message-extractor.d.ts +0 -84
  154. package/dist/src/openai-compat/mode-flags.d.ts +0 -34
  155. package/dist/src/openai-compat/non-streaming-handler.d.ts +0 -29
  156. package/dist/src/openai-compat/openai-chunk-types.d.ts +0 -35
  157. package/dist/src/openai-compat/openai-compat.d.ts +0 -49
  158. package/dist/src/openai-compat/openai-types.d.ts +0 -71
  159. package/dist/src/openai-compat/parse-route-body.d.ts +0 -24
  160. package/dist/src/openai-compat/prompts.d.ts +0 -47
  161. package/dist/src/openai-compat/request-coalescer.d.ts +0 -77
  162. package/dist/src/openai-compat/response-formatter.d.ts +0 -33
  163. package/dist/src/openai-compat/session-key-resolver.d.ts +0 -41
  164. package/dist/src/openai-compat/skill-resolver.d.ts +0 -59
  165. package/dist/src/openai-compat/sse-translator.d.ts +0 -51
  166. package/dist/src/openai-compat/status-reporter.d.ts +0 -30
  167. package/dist/src/openai-compat/streaming-handler.d.ts +0 -52
  168. package/dist/src/openai-compat/tool-calls-parser.d.ts +0 -34
  169. package/dist/src/openai-compat/tool-results-serializer.d.ts +0 -60
  170. package/dist/src/openai-compat/tts-rule.d.ts +0 -20
  171. package/dist/src/openai-compat/voice-recovery.d.ts +0 -56
  172. package/dist/src/patches/cache-parity-registry.d.ts +0 -103
  173. package/dist/src/patches/claude-md-injection.d.ts +0 -10
  174. package/dist/src/patches/cwd-redirect.d.ts +0 -10
  175. package/dist/src/patches/embedded-server-route.d.ts +0 -23
  176. package/dist/src/patches/pricing-overrides.d.ts +0 -10
  177. package/dist/src/patches/resume-registry-restore.d.ts +0 -11
  178. package/dist/src/patches/session-pid-tracking.d.ts +0 -10
  179. package/dist/src/patches/sysprompt-strip.d.ts +0 -46
  180. package/dist/src/patches/tools-restoration.d.ts +0 -12
  181. package/dist/src/persistence/migration-v0.d.ts +0 -24
  182. package/dist/src/persistence/session-registry.d.ts +0 -58
  183. package/dist/src/proxy/anthropic-adapter.d.ts +0 -136
  184. package/dist/src/proxy/handler.d.ts +0 -39
  185. package/dist/src/proxy/index.d.ts +0 -4
  186. package/dist/src/proxy/schema-cleaner.d.ts +0 -11
  187. package/dist/src/proxy/thought-cache.d.ts +0 -19
  188. package/dist/src/session/embedded-server.d.ts +0 -25
  189. package/dist/src/session/inbox-manager.d.ts +0 -38
  190. package/dist/src/session/index.d.ts +0 -3
  191. package/dist/src/session/persisted-sessions.d.ts +0 -50
  192. package/dist/src/session/session-manager.d.ts +0 -247
  193. package/dist/src/session/watchdogs.d.ts +0 -92
  194. package/dist/src/session-bootstrap/boot-self-heal.d.ts +0 -32
  195. package/dist/src/session-bootstrap/cwd-patch.d.ts +0 -50
  196. package/dist/src/session-bootstrap/index.d.ts +0 -3
  197. package/dist/src/session-bootstrap/resume-registry.d.ts +0 -27
  198. package/dist/src/session-bootstrap/session-hygiene.d.ts +0 -23
  199. package/dist/src/session-bootstrap/sysprompt-strip.d.ts +0 -24
  200. package/dist/src/session-bootstrap/think-conflict-resolver.d.ts +0 -32
  201. package/dist/src/types/route.d.ts +0 -11
  202. package/dist/src/types/runtime-config.d.ts +0 -208
  203. package/dist/src/types/sse.d.ts +0 -29
  204. package/dist/src/types/tool-bridge.d.ts +0 -82
  205. package/dist/src/types/upstream.d.ts +0 -580
  206. package/dist/src/types.d.ts +0 -498
  207. package/dist/src/validation.d.ts +0 -31
@@ -30,8 +30,12 @@ function basename(p) {
30
30
  * Glyph picked per tool-call status. Mirrors terminal "running / done /
31
31
  * errored" iconography that A1 already reads in the live Claude Code TUI.
32
32
  */
33
- function toolGlyph(tc) {
34
- if (tc.isError)
33
+ function toolGlyph(tc, suppressToolErrors = false) {
34
+ // OpenClaw 2026.5.20 #81561 — when the user set messages.suppressToolErrors,
35
+ // an errored-but-completed tool reads as a normal "✓" rather than the "✗"
36
+ // error glyph. tc.result is set for an errored tool (it carries the error
37
+ // body), so the completed-✓ branch below picks it up once "✗" is skipped.
38
+ if (tc.isError && !suppressToolErrors)
35
39
  return '✗';
36
40
  if (tc.result !== undefined)
37
41
  return '✓';
@@ -260,8 +264,8 @@ export function toolIcon(name) {
260
264
  return '🔌';
261
265
  return '🔹';
262
266
  }
263
- export function renderToolLine(tc) {
264
- const glyph = toolGlyph(tc);
267
+ export function renderToolLine(tc, suppressToolErrors = false) {
268
+ const glyph = toolGlyph(tc, suppressToolErrors);
265
269
  const icon = toolIcon(tc.name);
266
270
  // Status glyph + emoji are HTML-safe; the tool name is HTML-escaped and the
267
271
  // input detail rides in a <code> span (v0.27.0 M1 HTML styling).
@@ -384,7 +388,7 @@ export function renderTurn(turn, meta) {
384
388
  let acc = 0;
385
389
  for (let i = turn.toolCalls.length - 1; i >= 0; i--) {
386
390
  const tc = turn.toolCalls[i];
387
- const line = renderToolLine(tc);
391
+ const line = renderToolLine(tc, meta?.suppressToolErrors);
388
392
  // v0.27.4 M2 — for Edit/Write/MultiEdit show a diff (from input) instead of
389
393
  // the "File updated" result; other tools keep the result preview.
390
394
  const diffText = toolDiffBlock(tc);
@@ -44,14 +44,6 @@ export function parseSlash(text) {
44
44
  return { cmd: cmd.toLowerCase(), args: tokens.slice(1) };
45
45
  }
46
46
  // ── /sessions ────────────────────────────────────────────────────────────
47
- function enrichRows(entries, stateLookup) {
48
- return entries.map((e) => ({
49
- slug: e.slug,
50
- sessionName: e.sessionName,
51
- state: stateLookup(e.slug),
52
- lastUsedAt: e.lastUsedAt,
53
- }));
54
- }
55
47
  /**
56
48
  * v0.28.0 — `/sessions` is now a `claude -r`-style picker over the REAL Claude
57
49
  * Code sessions. It mirrors `~/.claude/projects/**` (the store every `claude`
@@ -4,7 +4,7 @@
4
4
  * Builds the Claude-Code-CLI-style status line that sits at the top of the
5
5
  * Telegram live-mirror card, e.g.:
6
6
  *
7
- * [Opus 4.7] · CC 2.1.145 · ⏱ 2h13m · 🔧 3 · bypass
7
+ * [Opus 4.7] · CC 2.1.145 · GW 2026.5.20 · ⏱ 2h13m · 🔧 3 · bypass
8
8
  *
9
9
  * STRICT no-fake-data rule (per the v0.26.2 brief): every segment is rendered
10
10
  * ONLY when its value is genuinely available. Missing model / version / etc.
@@ -40,6 +40,33 @@ export function getCcVersion() {
40
40
  export function _resetCcVersionForTests() {
41
41
  _ccVersion = undefined;
42
42
  }
43
+ // ── Gateway version (cached; resolved at most once per process) ──────────────
44
+ // `openclaw --version` prints e.g. "OpenClaw 2026.5.20 (e510042)". Mirrors the
45
+ // getCcVersion() resolve-once-and-cache contract (including a failed lookup as
46
+ // `null`). Surfaced on the status line as "GW <ver>" so the card shows which
47
+ // OpenClaw core is actually running alongside the CC CLI version.
48
+ let _gatewayVersion;
49
+ export function getGatewayVersion() {
50
+ if (_gatewayVersion !== undefined)
51
+ return _gatewayVersion;
52
+ try {
53
+ const out = execFileSync('openclaw', ['--version'], {
54
+ timeout: 5_000,
55
+ stdio: ['ignore', 'pipe', 'ignore'],
56
+ encoding: 'utf8',
57
+ });
58
+ const m = /(\d+\.\d+\.\d+)/.exec(out);
59
+ _gatewayVersion = m ? m[1] : null;
60
+ }
61
+ catch {
62
+ _gatewayVersion = null;
63
+ }
64
+ return _gatewayVersion;
65
+ }
66
+ /** Test-only — reset the cached gateway version. */
67
+ export function _resetGatewayVersionForTests() {
68
+ _gatewayVersion = undefined;
69
+ }
43
70
  // ── Model id → short label ─────────────────────────────────────────────────
44
71
  // "claude-opus-4-7" → "Opus 4.7"; "claude-sonnet-4-6" → "Sonnet 4.6". Unknown
45
72
  // shapes pass through unchanged (still real data, just not prettified).
@@ -69,7 +96,7 @@ export function fmtElapsed(ms) {
69
96
  /**
70
97
  * Render the status line for a turn. Returns `undefined` when NOTHING is
71
98
  * available to show (so callers omit the line entirely). Segments, in order:
72
- * [model] · CC <ver> · ⏱ <elapsed> · 🔧 <toolCount> · bypass
99
+ * [model] · CC <ver> · GW <ver> · ⏱ <elapsed> · 🔧 <toolCount> · bypass
73
100
  * Every segment is conditional on real data.
74
101
  */
75
102
  export function renderStatusLine(turn, meta, now = Date.now()) {
@@ -80,6 +107,9 @@ export function renderStatusLine(turn, meta, now = Date.now()) {
80
107
  const ver = getCcVersion();
81
108
  if (ver)
82
109
  segs.push(`CC ${ver}`);
110
+ const gwVer = getGatewayVersion();
111
+ if (gwVer)
112
+ segs.push(`GW ${gwVer}`);
83
113
  // Turn elapsed is always real (Turn.startedAt is set at card creation). Use
84
114
  // endedAt once the turn is done so the finalized card shows total duration.
85
115
  const elapsedMs = (turn.endedAt ?? now) - turn.startedAt;
@@ -28,14 +28,28 @@ export const SESSION_READY_FALLBACK_MS = 2_000;
28
28
  *
29
29
  * v0.20.0: env-overridable via `CC_OPENCLAW_TURN_TIMEOUT_MS` (see
30
30
  * src/lib/env-overrides.ts). Setting `=0` disables the per-turn timer
31
- * entirely, matching direct-CLI behavior on long tool turns. */
31
+ * entirely, matching direct-CLI behavior on long tool turns.
32
+ *
33
+ * OpenClaw 2026.5.20 (#83979): core now HONORS
34
+ * `models.providers.cc-openclaw.timeoutSeconds` above its idle watchdog —
35
+ * previously the outer request was killed at ~120s regardless of that value,
36
+ * so this 900_000 alignment was a workaround fighting a hidden ceiling. It is
37
+ * now a genuine, end-to-end envelope. Keep it aligned to timeoutSeconds (do
38
+ * NOT reduce below the configured outer bound). */
32
39
  export const TURN_TIMEOUT_MS = 900_000;
33
40
  /** Runtime watchdog threshold for "stuck" sessions. If a session is
34
41
  * `isBusy === true` AND its stats.lastActivity hasn't moved in this many
35
42
  * ms, the SessionManager watchdog aborts + disposes the session. Mirrors
36
43
  * the boot-time orphan reaper in gateway-pm2-wrapper.sh:53-60, but runs
37
44
  * continuously on a setInterval. Env-overridable via
38
- * CC_OPENCLAW_STALLED_KILL_MS. v0.10.0. */
45
+ * CC_OPENCLAW_STALLED_KILL_MS. v0.10.0.
46
+ *
47
+ * NOTE (OpenClaw 2026.5.20 #83979): this is ORTHOGONAL to core's now-honored
48
+ * provider idle watchdog — that bounds first-token / inter-token waits on the
49
+ * request; this bounds a wedged Claude SUBPROCESS that emits no output at all.
50
+ * The core fix does NOT make this redundant. Keep it. Raise (don't remove) via
51
+ * CC_OPENCLAW_STALLED_KILL_MS only if legitimately long no-output tool runs
52
+ * (e.g. a long Bash) trip it. */
39
53
  export const STALLED_SESSION_KILL_MS = 180_000;
40
54
  /** How often the stalled-session watchdog scans the sessions Map. */
41
55
  export const STALLED_WATCH_INTERVAL_MS = 30_000;
@@ -63,6 +63,7 @@ export class PersistentClaudeSession extends EventEmitter {
63
63
  history: [],
64
64
  retries: 0,
65
65
  lastRetryError: undefined,
66
+ lastStopReason: undefined,
66
67
  lastTurnContextTokens: 0,
67
68
  };
68
69
  }
@@ -624,6 +625,15 @@ export class PersistentClaudeSession extends EventEmitter {
624
625
  (usage.cache_creation_input_tokens || 0);
625
626
  this._updateCost();
626
627
  }
628
+ // v0.32.0 — record the turn's stop_reason BEFORE TURN_COMPLETE is
629
+ // emitted (that emit resolves the streaming handler's await). The
630
+ // handler reads stats.lastStopReason to tell a hard rate_limit/error
631
+ // (→ non-200 so OpenClaw fails over to Sonnet) apart from an
632
+ // empty-but-successful turn (→ the "Done." backstop).
633
+ {
634
+ const sr = event.stop_reason;
635
+ this.stats.lastStopReason = typeof sr === 'string' ? sr : undefined;
636
+ }
627
637
  this.emit(SESSION_EVENT.RESULT, event);
628
638
  this.emit(SESSION_EVENT.TURN_COMPLETE, event);
629
639
  // v0.27.6 — liveness watchdog (Killer #1): turn is over; clear any
@@ -826,6 +836,7 @@ export class PersistentClaudeSession extends EventEmitter {
826
836
  100)),
827
837
  retries: this.stats.retries,
828
838
  lastRetryError: this.stats.lastRetryError,
839
+ lastStopReason: this.stats.lastStopReason,
829
840
  sessionId: this.sessionId,
830
841
  uptime: this.stats.startTime ? Math.round((Date.now() - new Date(this.stats.startTime).getTime()) / 1000) : 0,
831
842
  };
@@ -351,6 +351,46 @@ export function _resetTtsAutoModeCacheForTests() {
351
351
  _ttsAutoModeCache = null;
352
352
  _ttsAutoModeCacheChecked = false;
353
353
  }
354
+ // ── Suppress tool errors (OpenClaw 2026.5.20 #81561) ────────────────────────
355
+ // Reads `messages.suppressToolErrors` from `~/.openclaw/openclaw.json` once and
356
+ // caches it. When true, the user has opted out of tool-failure noise, so the
357
+ // Telegram live-card renders an errored-but-completed tool with its normal "✓"
358
+ // glyph instead of the "✗" error glyph (the card-side equivalent of the core's
359
+ // "no separate warning payloads" behavior). The TURN-level "❌ <reason>" header
360
+ // is unaffected — that's terminal turn state, not a per-tool warning.
361
+ //
362
+ // Same custom-file-reader rationale as getTtsAutoMode(): openclaw.json is a
363
+ // plugin-side decision that shouldn't reach through OpenClaw's typed runtime.
364
+ // Tests override via the CC_OPENCLAW_SUPPRESS_TOOL_ERRORS env var.
365
+ let _suppressToolErrorsCache = null;
366
+ let _suppressToolErrorsCacheChecked = false;
367
+ export function getSuppressToolErrors() {
368
+ // Test-time / explicit override takes precedence.
369
+ const envOverride = process.env.CC_OPENCLAW_SUPPRESS_TOOL_ERRORS?.trim().toLowerCase();
370
+ if (envOverride === '1' || envOverride === 'true' || envOverride === 'on')
371
+ return true;
372
+ if (envOverride === '0' || envOverride === 'false' || envOverride === 'off')
373
+ return false;
374
+ if (!_suppressToolErrorsCacheChecked) {
375
+ _suppressToolErrorsCacheChecked = true;
376
+ try {
377
+ const configPath = process.env.OPENCLAW_CONFIG_PATH || join(homedir(), '.openclaw', 'openclaw.json');
378
+ const raw = readFileSync(configPath, 'utf8');
379
+ const parsed = JSON.parse(raw);
380
+ _suppressToolErrorsCache = parsed.messages?.suppressToolErrors ?? null;
381
+ }
382
+ catch {
383
+ // File missing / unreadable / invalid JSON — fall through to false.
384
+ _suppressToolErrorsCache = null;
385
+ }
386
+ }
387
+ return _suppressToolErrorsCache === true;
388
+ }
389
+ /** Test-only: reset the suppress-tool-errors cache so the next call re-reads. */
390
+ export function _resetSuppressToolErrorsCacheForTests() {
391
+ _suppressToolErrorsCache = null;
392
+ _suppressToolErrorsCacheChecked = false;
393
+ }
354
394
  export function getClaudeBin() {
355
395
  const cfg = getConfigService();
356
396
  if (cfg)
@@ -23,7 +23,7 @@
23
23
  import { reportStatus, getToolDescription } from './status-reporter.js';
24
24
  import { parseToolCallsFromText } from './tool-calls-parser.js';
25
25
  import { formatCompletionResponse } from './response-formatter.js';
26
- import { getSurfaceThinkingEnabled, getTtsAutoMode, getCardAnswerMirrorEnabled } from '../lib/config.js';
26
+ import { getSurfaceThinkingEnabled, getTtsAutoMode, getCardAnswerMirrorEnabled, getSuppressToolErrors } from '../lib/config.js';
27
27
  import { emit as emitTrajectory, emitTurnTrace } from '../lib/trajectory.js';
28
28
  import { formatError, ERROR_CODES } from '../lib/error-formatter.js';
29
29
  import { applyVoiceRecovery, _logVoiceDebug, detectVoiceIntent, hasTtsMarkers } from './voice-recovery.js';
@@ -55,7 +55,7 @@ slashCommand) {
55
55
  // cards THIS module instance sees (see handleStreaming counterpart).
56
56
  process.stderr.write(`[cc-openclaw/openai-compat] handleNonStreaming pid=${process.pid} hasTools=${hasTools} ${mirrorCardStateDebug()} session=${sessionName}\n`);
57
57
  // v0.26.2 M1+M2 — feed the CC-CLI status line (model + bypass + quota; see streaming).
58
- mirrorSetCardMeta({ model, bypassPermissions: true, ...mirrorReadQuotaMeta() });
58
+ mirrorSetCardMeta({ model, bypassPermissions: true, suppressToolErrors: getSuppressToolErrors(), ...mirrorReadQuotaMeta() });
59
59
  // v0.14.0 turn-trace probe: capture wall-clock duration of the turn.
60
60
  const turnStartMs = Date.now();
61
61
  // v0.15.0 Slice 1: hoist userText so the catch-path probe emit can reference
@@ -41,7 +41,7 @@ import { formatCompletionChunk } from './response-formatter.js';
41
41
  import { isToolStreamMode } from './mode-flags.js';
42
42
  import { emit as emitTrajectory, emitTurnTrace } from '../lib/trajectory.js';
43
43
  import { formatError, ERROR_CODES } from '../lib/error-formatter.js';
44
- import { getSurfaceThinkingEnabled, getTtsAutoMode, getCardAnswerMirrorEnabled } from '../lib/config.js';
44
+ import { getSurfaceThinkingEnabled, getTtsAutoMode, getCardAnswerMirrorEnabled, getSuppressToolErrors } from '../lib/config.js';
45
45
  import { applyVoiceRecovery, detectVoiceIntent, hasTtsMarkers, _logVoiceDebug } from './voice-recovery.js';
46
46
  import { pushToolUse as mirrorPushToolUse, pushToolResult as mirrorPushToolResult, pushAssistantText as mirrorPushAssistantText, pushThinking as mirrorPushThinking, finalizeActiveCards as mirrorFinalizeActiveCards, extractInsightForCard as mirrorExtractInsightForCard, failActiveCards as mirrorFailActiveCards, classifyFailure, setCardMeta as mirrorSetCardMeta, readQuotaMeta as mirrorReadQuotaMeta, } from '../channels/telegram-mirror/turn-bridge.js';
47
47
  import { cardStateDebug as mirrorCardStateDebug } from '../channels/telegram-mirror/card-state.js';
@@ -83,16 +83,40 @@ onFinalText) {
83
83
  // v0.26.2 M1+M2 — feed the CC-CLI status line. The openai-compat path always
84
84
  // runs permissionMode 'bypassPermissions' (openai-compat.ts) so we assert it.
85
85
  // Quota is read once here (real status-tee snapshot or omitted).
86
- mirrorSetCardMeta({ model, bypassPermissions: true, ...mirrorReadQuotaMeta() });
86
+ mirrorSetCardMeta({ model, bypassPermissions: true, suppressToolErrors: getSuppressToolErrors(), ...mirrorReadQuotaMeta() });
87
87
  // #4 dual-surface seam — hoist once per turn (read off the hot delta loop).
88
88
  // Default OFF: card is the activity pane, gateway draft is the answer pane.
89
89
  const mirrorAnswerToCard = getCardAnswerMirrorEnabled();
90
- res.writeHead(200, {
90
+ // v0.32.0 — DEFERRED header commit. Previously res.writeHead(200) fired here
91
+ // eagerly (before any model output), permanently locking the status to 200.
92
+ // On an Anthropic weekly-cap rate_limit the turn then resolved as a 200-empty
93
+ // SSE stream that OpenClaw read as a successful (empty) turn — so its model
94
+ // fallback chain (opus → sonnet → haiku) never advanced. By holding the
95
+ // header until the first real SSE byte, a pre-stream rate_limit/error can
96
+ // instead return HTTP 429/5xx, which OpenClaw's status-code failover acts on.
97
+ // Once a byte has streamed the 200 is committed and we fall back to an in-band
98
+ // SSE error (can't retract a 200) — but a hard cap almost always trips before
99
+ // the first byte. For a normal turn the only change is that the role-opener
100
+ // chunk now flushes immediately before the first content delta (sub-second,
101
+ // immaterial to the SSE client) instead of at handler entry.
102
+ const SSE_HEADERS = {
91
103
  'Content-Type': 'text/event-stream',
92
104
  'Cache-Control': 'no-cache',
93
105
  Connection: 'keep-alive',
94
106
  'X-Accel-Buffering': 'no',
95
- });
107
+ };
108
+ let streamOpened = false;
109
+ // Opens the SSE stream exactly once: commits the 200 header + the role-opener
110
+ // chunk. Called lazily from writeSSE on the first content write. Until it
111
+ // runs, the response status is still mutable (so a pre-stream failure can
112
+ // return a non-200).
113
+ const openStream = () => {
114
+ if (streamOpened)
115
+ return;
116
+ streamOpened = true;
117
+ res.writeHead(200, SSE_HEADERS);
118
+ res.write(`data: ${JSON.stringify(formatCompletionChunk(completionId, model, { role: 'assistant' }, null))}\n\n`);
119
+ };
96
120
  // v0.14.0 turn-trace probe: track wall-clock + accumulate streamed text
97
121
  // for the final per-turn diagnostic emit. accumulatedText mirrors what
98
122
  // the client actually received (text deltas only — tool_calls are tracked
@@ -123,6 +147,7 @@ onFinalText) {
123
147
  });
124
148
  const writeSSE = (data) => {
125
149
  if (!clientDisconnected) {
150
+ openStream(); // commit 200 + role opener on the first real byte
126
151
  try {
127
152
  res.write(`data: ${data}\n\n`);
128
153
  }
@@ -131,8 +156,10 @@ onFinalText) {
131
156
  }
132
157
  }
133
158
  };
134
- // Initial chunk with role
135
- writeSSE(JSON.stringify(formatCompletionChunk(completionId, model, { role: 'assistant' }, null)));
159
+ // v0.32.0 — the role-opener chunk is now emitted by openStream() on the first
160
+ // content write (see the deferred-header note above), so the eager write that
161
+ // lived here is gone. This keeps the HTTP status mutable until real content
162
+ // exists, enabling the pre-stream 429/5xx failover path below.
136
163
  // SSE keepalive heartbeat. v0.27.5: 30s → 15s. A long quiet phase (Claude
137
164
  // CLI thinking, a slow Bash/tool step) with no SSE write can make OpenClaw's
138
165
  // HTTP client perceive the stream as dead and fire a RETRY — which the
@@ -142,7 +169,11 @@ onFinalText) {
142
169
  // retries at the source. (The request-coalescer is the second line of
143
170
  // defense for the retries that still slip through.)
144
171
  const heartbeatTimer = setInterval(() => {
145
- if (!clientDisconnected) {
172
+ // v0.32.0 — only heartbeat once the stream is actually open. Before the
173
+ // first byte the response status is still mutable (so a pre-stream
174
+ // rate_limit can 429); a keepalive write here would implicitly commit a 200
175
+ // and lock that failover option out.
176
+ if (!clientDisconnected && streamOpened) {
146
177
  try {
147
178
  res.write(': keepalive\n\n');
148
179
  }
@@ -376,6 +407,11 @@ onFinalText) {
376
407
  }
377
408
  // Get token usage for final chunk
378
409
  let usage;
410
+ // v0.32.0 — captured from the same getStatus() call to drive the no-output
411
+ // failover branch below. persistent-session sets stats.lastStopReason on the
412
+ // CLI `result` event BEFORE TURN_COMPLETE resolves this await, so it is
413
+ // populated by the time we read it here.
414
+ let lastStopReason;
379
415
  try {
380
416
  const status = manager.getStatus(sessionName);
381
417
  usage = {
@@ -383,6 +419,7 @@ onFinalText) {
383
419
  completion_tokens: status.stats.tokensOut,
384
420
  total_tokens: status.stats.tokensIn + status.stats.tokensOut,
385
421
  };
422
+ lastStopReason = status.stats.lastStopReason;
386
423
  }
387
424
  catch {
388
425
  /* best effort */
@@ -393,6 +430,68 @@ onFinalText) {
393
430
  // payload. Skip when tool_calls were emitted — those are openai-spec
394
431
  // valid as the only payload (multi-turn tool-use sessions).
395
432
  const noVisiblePayload = !streamedAnything && bufferedText.length === 0 && toolCallsEmitted === 0;
433
+ // v0.32.0 — quota/error fast-fail. A no-output turn whose stop_reason is a
434
+ // hard failure (rate_limit = Anthropic weekly cap reached; error = upstream
435
+ // fault) must NOT be masked as a "Done." 200 below — that is exactly what
436
+ // made OpenClaw accept a capped turn as an empty success and skip its
437
+ // opus → sonnet → haiku fallback chain. While no byte has streamed the HTTP
438
+ // status is still mutable, so return a non-200 (429 for rate_limit, 502 for
439
+ // error) with an OpenAI-shaped error body; OpenClaw's status-code failover
440
+ // then advances to the next model in the chain. Once a byte has streamed we
441
+ // cannot retract the 200 — fall through to the normal finalize in that rare
442
+ // partial-output case (the catch path / card ❌ still surface the failure).
443
+ const isFailureStop = lastStopReason === 'rate_limit' || lastStopReason === 'error';
444
+ if (noVisiblePayload && isFailureStop && !streamOpened && !clientDisconnected) {
445
+ clearInterval(heartbeatTimer);
446
+ const isRateLimit = lastStopReason === 'rate_limit';
447
+ const httpStatus = isRateLimit ? 429 : 502;
448
+ reportStatus('idle', isRateLimit ? 'Rate limited' : 'Upstream error');
449
+ // Flip the Telegram card to ❌ <reason> so it never finalizes a misleading
450
+ // "✓ Done" on a capped/failed turn. Mirrors the catch-path pattern; the
451
+ // finally block's finalize respects the already-failed card state.
452
+ try {
453
+ await mirrorFailActiveCards(isRateLimit ? 'rate limited — model quota reached' : 'upstream error');
454
+ }
455
+ catch {
456
+ /* card fail is cosmetic */
457
+ }
458
+ const errBody = {
459
+ error: {
460
+ message: isRateLimit
461
+ ? `Model ${model} is rate limited (quota reached).`
462
+ : `Model ${model} returned an upstream error.`,
463
+ type: isRateLimit ? 'rate_limit_error' : 'server_error',
464
+ code: isRateLimit ? 'rate_limited' : 'upstream_error',
465
+ },
466
+ };
467
+ formatError(new Error(errBody.error.message), {
468
+ code: ERROR_CODES.SESSION_ERROR,
469
+ sessionId: sessionName,
470
+ details: { phase: 'handleStreaming', stopReason: lastStopReason, httpStatus },
471
+ });
472
+ emitTurnTrace({
473
+ path: 'streaming',
474
+ model,
475
+ userTextPreview: userText.slice(0, 500),
476
+ userTextLen: userText.length,
477
+ hasTools,
478
+ useToolStream,
479
+ toolCallCount: 0,
480
+ outputTextPreview: '',
481
+ outputTextLen: 0,
482
+ finishReason: 'error',
483
+ doneBackstopFired: false,
484
+ voiceIntent,
485
+ tokensIn: usage?.prompt_tokens ?? 0,
486
+ tokensOut: usage?.completion_tokens ?? 0,
487
+ durationMs: Date.now() - turnStartMs,
488
+ errorMessage: errBody.error.message,
489
+ errorName: isRateLimit ? 'RateLimitError' : 'UpstreamError',
490
+ }, sessionName);
491
+ res.writeHead(httpStatus, { 'Content-Type': 'application/json' });
492
+ res.end(JSON.stringify(errBody));
493
+ return;
494
+ }
396
495
  if (noVisiblePayload) {
397
496
  markFirstByte();
398
497
  writeSSE(JSON.stringify(formatCompletionChunk(completionId, model, { content: 'Done.' }, null)));
@@ -580,8 +679,21 @@ onFinalText) {
580
679
  }
581
680
  // v0.4.3: route through formatError for errors_total + trajectory error.
582
681
  formatError(err, { code: ERROR_CODES.SESSION_ERROR, sessionId: sessionName, details: { phase: 'handleStreaming' } });
583
- writeSSE(JSON.stringify({ error: { message: err.message, type: 'server_error' } }));
584
- writeSSE('[DONE]');
682
+ // v0.32.0 if nothing has streamed yet the status is still mutable: return
683
+ // a 502 so OpenClaw's failover advances the model chain. OpenClaw does not
684
+ // treat an in-band SSE error on a committed 200 as a provider failure, so
685
+ // the old SSE-error path (kept for the already-streaming case) could not
686
+ // trigger a fallback.
687
+ if (!streamOpened && !clientDisconnected) {
688
+ res.writeHead(502, { 'Content-Type': 'application/json' });
689
+ res.end(JSON.stringify({
690
+ error: { message: err.message, type: 'server_error', code: 'upstream_error' },
691
+ }));
692
+ }
693
+ else {
694
+ writeSSE(JSON.stringify({ error: { message: err.message, type: 'server_error' } }));
695
+ writeSSE('[DONE]');
696
+ }
585
697
  // v0.15.0 Slice 1: turn-trace probe now ALSO fires on error-exit so
586
698
  // broken turns (claude CLI crash, timeout, stalled session kill) get
587
699
  // captured in the same trajectory stream as successful turns. Without
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "@a1hvdy/cc-openclaw",
3
- "version": "0.30.0",
3
+ "version": "0.32.0",
4
4
  "description": "A1xAI's Anthropic CLI bridge plugin for OpenClaw",
5
5
  "author": "@a1cy",
6
6
  "license": "MIT",
7
7
  "type": "module",
8
8
  "main": "dist/src/index.js",
9
- "types": "dist/src/index.d.ts",
10
9
  "homepage": "https://github.com/A1cy/cc-openclaw",
11
10
  "repository": "github:A1cy/cc-openclaw",
12
11
  "engines": {
@@ -23,6 +22,8 @@
23
22
  },
24
23
  "files": [
25
24
  "dist/",
25
+ "!dist/**/*.d.ts",
26
+ "!dist/**/*.d.ts.map",
26
27
  "configs/",
27
28
  "skills/",
28
29
  "openclaw.plugin.json",
@@ -1,103 +0,0 @@
1
- /**
2
- * Channel Adapter — generic interface for delivering session UX
3
- * to chat channels (Telegram, Discord, Signal, etc.).
4
- *
5
- * Telegram is the first concrete implementation (src/channels/telegram/).
6
- * Discord and Signal are deferred to v1.1 (Nice-to-Have).
7
- *
8
- * Each adapter:
9
- * - Renders live session cards (progress, tool calls, summaries)
10
- * - Receives inbound channel messages and forwards them as session inputs
11
- * - Sends session outputs back to the channel
12
- * - Provides idempotent register() to wire OpenClaw plugin lifecycle hooks
13
- *
14
- * AUDIT — B2 (PRP_v3 §11 vs reality, 2026-04-28):
15
- *
16
- * PRP_v3 §11.1 documents an event vocabulary (`session.created`,
17
- * `session.turn.start`, `session.tool.start`, `plugin.heartbeat`, etc.)
18
- * that does NOT exist in the actual codebase. Every `api.on()` call
19
- * site in src/ uses the openclaw-claude-code hook-event vocabulary
20
- * documented in `ChannelHookEvent` below. Likewise §11.2's
21
- * `api.emit('session.result', ...)` is unimplemented — no `api.emit`
22
- * call sites exist.
23
- *
24
- * Resolution: this audit treats the *code* as canonical and the spec
25
- * as drifted. The `ChannelHookEvent` literal union captures the actual
26
- * contract any future channel adapter must speak. The §11.x doc
27
- * sections need a v3.1 revision to match — tracked as a docs-tier
28
- * follow-up (Tier 3 §24), not a code-tier blocker.
29
- */
30
- /**
31
- * Canonical hook event vocabulary — every event name passed to `api.on()`
32
- * across src/channels/, src/command-router/, and src/session-bootstrap/
33
- * (verified by `rg "api\\.on\\(" src/`).
34
- *
35
- * This is the contract: any channel/handler that wants to subscribe to
36
- * gateway events MUST use one of these names. Adding a new event name
37
- * here without a corresponding gateway emitter is dead-code-typing.
38
- */
39
- type ChannelHookEvent = 'before_dispatch' | 'before_prompt_build' | 'before_model_resolve' | 'before_tool_call' | 'after_tool_call' | 'reply_dispatch' | 'message_sending';
40
- /** Hook handler signature — gateway passes (event, ctx?) positional args. */
41
- type ChannelHookHandler = (...args: unknown[]) => unknown | Promise<unknown>;
42
- export interface PluginApi {
43
- /**
44
- * Subscribe to a gateway hook event.
45
- *
46
- * Accepts `ChannelHookEvent` for compile-time event-name checking, or
47
- * `string` as an escape hatch for adapter-specific events not yet
48
- * canonicalized into the union. Future tightening: drop the `| string`
49
- * overload once all hook sites use the union.
50
- */
51
- on(event: ChannelHookEvent, handler: ChannelHookHandler): void;
52
- on(event: string, handler: ChannelHookHandler): void;
53
- }
54
- /**
55
- * Outbound message from session → channel.
56
- */
57
- export interface ChannelMessage {
58
- channel: string;
59
- target: string;
60
- text?: string;
61
- buttons?: ChannelButton[];
62
- metadata?: Record<string, unknown>;
63
- }
64
- export interface ChannelButton {
65
- text: string;
66
- callbackData?: string;
67
- url?: string;
68
- }
69
- /**
70
- * Inbound envelope from channel → session.
71
- */
72
- export interface ChannelInbound {
73
- channel: string;
74
- fromUserId: string;
75
- text: string;
76
- metadata?: Record<string, unknown>;
77
- }
78
- /**
79
- * Channel adapter contract. Each concrete adapter (e.g. Telegram) implements
80
- * this interface so cc-openclaw can route session events to any wired channel.
81
- */
82
- export interface ChannelAdapter {
83
- /** Adapter identifier — must be unique across registered adapters. */
84
- readonly id: string;
85
- /**
86
- * Idempotent registration of OpenClaw plugin lifecycle hooks.
87
- * Multiple calls with the same api MUST NOT stack listeners.
88
- */
89
- register(api: PluginApi): void;
90
- /** Send an outbound message to the channel. */
91
- send(message: ChannelMessage): Promise<void>;
92
- /** Update an existing live card in-place (rendering progress). */
93
- updateCard?(target: string, message: ChannelMessage): Promise<void>;
94
- /** Render a final completion summary to the channel. */
95
- completionSummary?(target: string, summary: ChannelMessage): Promise<void>;
96
- }
97
- /**
98
- * Helper to wire any ChannelAdapter via the plugin's main register() flow.
99
- * Centralizes the per-adapter `register()` call so src/index.ts can
100
- * iterate a list of adapters without coupling to each adapter's internals.
101
- */
102
- export declare function registerChannelAdapter(api: PluginApi, adapter: ChannelAdapter): void;
103
- export {};