@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.
- package/dist/src/channels/telegram-mirror/card-renderer.js +9 -5
- package/dist/src/channels/telegram-mirror/commands.js +0 -8
- package/dist/src/channels/telegram-mirror/status-line.js +32 -2
- package/dist/src/constants.js +16 -2
- package/dist/src/engines/persistent-session.js +11 -0
- package/dist/src/lib/config.js +40 -0
- package/dist/src/openai-compat/non-streaming-handler.js +2 -2
- package/dist/src/openai-compat/streaming-handler.js +121 -9
- package/package.json +3 -2
- package/dist/src/channels/adapter.d.ts +0 -103
- package/dist/src/channels/telegram-mirror/askuser.d.ts +0 -107
- package/dist/src/channels/telegram-mirror/burst-accumulator.d.ts +0 -96
- package/dist/src/channels/telegram-mirror/callback-mapping.d.ts +0 -61
- package/dist/src/channels/telegram-mirror/card-renderer.d.ts +0 -68
- package/dist/src/channels/telegram-mirror/card-state.d.ts +0 -83
- package/dist/src/channels/telegram-mirror/commands.d.ts +0 -183
- package/dist/src/channels/telegram-mirror/compose-buffer.d.ts +0 -71
- package/dist/src/channels/telegram-mirror/cost-views.d.ts +0 -58
- package/dist/src/channels/telegram-mirror/failure/callback-data-overflow.d.ts +0 -21
- package/dist/src/channels/telegram-mirror/failure/gateway-down.d.ts +0 -15
- package/dist/src/channels/telegram-mirror/failure/in-flight-conflict.d.ts +0 -15
- package/dist/src/channels/telegram-mirror/failure/index.d.ts +0 -23
- package/dist/src/channels/telegram-mirror/failure/model-5xx.d.ts +0 -16
- package/dist/src/channels/telegram-mirror/failure/network-blip.d.ts +0 -17
- package/dist/src/channels/telegram-mirror/failure/pool-exhausted-fallback.d.ts +0 -15
- package/dist/src/channels/telegram-mirror/failure/rate-limit.d.ts +0 -16
- package/dist/src/channels/telegram-mirror/failure/returning-after-24h.d.ts +0 -14
- package/dist/src/channels/telegram-mirror/failure/types.d.ts +0 -30
- package/dist/src/channels/telegram-mirror/inbound-handler.d.ts +0 -73
- package/dist/src/channels/telegram-mirror/index.d.ts +0 -32
- package/dist/src/channels/telegram-mirror/plan-attachment.d.ts +0 -120
- package/dist/src/channels/telegram-mirror/quota-reader.d.ts +0 -42
- package/dist/src/channels/telegram-mirror/sessions-keyboard.d.ts +0 -84
- package/dist/src/channels/telegram-mirror/soak-log.d.ts +0 -99
- package/dist/src/channels/telegram-mirror/state-machine.d.ts +0 -113
- package/dist/src/channels/telegram-mirror/status-line.d.ts +0 -51
- package/dist/src/channels/telegram-mirror/sync-commands.d.ts +0 -100
- package/dist/src/channels/telegram-mirror/threshold-watcher.d.ts +0 -54
- package/dist/src/channels/telegram-mirror/turn-bridge.d.ts +0 -125
- package/dist/src/cli/checks/bridge-wiring.d.ts +0 -14
- package/dist/src/cli/checks/config-schema.d.ts +0 -11
- package/dist/src/cli/checks/critical-openclaw-json-keys.d.ts +0 -21
- package/dist/src/cli/checks/install-path.d.ts +0 -11
- package/dist/src/cli/checks/patch-scaffold.d.ts +0 -17
- package/dist/src/cli/doctor.d.ts +0 -20
- package/dist/src/cli/index.d.ts +0 -8
- package/dist/src/cli/migrate.d.ts +0 -29
- package/dist/src/command-router/cc-handler.d.ts +0 -67
- package/dist/src/command-router/index.d.ts +0 -2
- package/dist/src/command-router/launch-policy.d.ts +0 -92
- package/dist/src/command-router/resume-policy.d.ts +0 -18
- package/dist/src/command-router/turn-formatter.d.ts +0 -19
- package/dist/src/config/loader.d.ts +0 -8
- package/dist/src/config/schema.d.ts +0 -192
- package/dist/src/constants.d.ts +0 -191
- package/dist/src/council/build-agent-prompt.d.ts +0 -11
- package/dist/src/council/cleanup-worktrees.d.ts +0 -10
- package/dist/src/council/consensus.d.ts +0 -20
- package/dist/src/council/council.d.ts +0 -67
- package/dist/src/council/index.d.ts +0 -2
- package/dist/src/council/system-prompt.d.ts +0 -16
- package/dist/src/council/write-worktree-claude-md.d.ts +0 -10
- package/dist/src/engines/base-oneshot-session.d.ts +0 -87
- package/dist/src/engines/heartbeat-guard.d.ts +0 -93
- package/dist/src/engines/index.d.ts +0 -8
- package/dist/src/engines/persistent-codex-session.d.ts +0 -16
- package/dist/src/engines/persistent-cursor-session.d.ts +0 -21
- package/dist/src/engines/persistent-custom-session.d.ts +0 -78
- package/dist/src/engines/persistent-gemini-session.d.ts +0 -21
- package/dist/src/engines/persistent-session.d.ts +0 -95
- package/dist/src/engines/resolve-bin.d.ts +0 -14
- package/dist/src/engines/subprocess-pool.d.ts +0 -78
- package/dist/src/health/handler.d.ts +0 -39
- package/dist/src/health/index.d.ts +0 -1
- package/dist/src/health/metrics.d.ts +0 -52
- package/dist/src/index.d.ts +0 -57
- package/dist/src/lib/auto-recovery.d.ts +0 -43
- package/dist/src/lib/cache-parity-decide.d.ts +0 -64
- package/dist/src/lib/cache-parity.d.ts +0 -38
- package/dist/src/lib/cc-cli-scan.d.ts +0 -52
- package/dist/src/lib/circuit-breaker.d.ts +0 -21
- package/dist/src/lib/config-service.d.ts +0 -106
- package/dist/src/lib/config.d.ts +0 -136
- package/dist/src/lib/cost-rollup.d.ts +0 -36
- package/dist/src/lib/debounce.d.ts +0 -12
- package/dist/src/lib/debug-tap.d.ts +0 -13
- package/dist/src/lib/domain-error.d.ts +0 -59
- package/dist/src/lib/drift-detector.d.ts +0 -46
- package/dist/src/lib/env-overrides.d.ts +0 -47
- package/dist/src/lib/error-formatter.d.ts +0 -91
- package/dist/src/lib/error-renderer.d.ts +0 -20
- package/dist/src/lib/heartbeat-config.d.ts +0 -34
- package/dist/src/lib/heartbeat-workaround.d.ts +0 -44
- package/dist/src/lib/html-render.d.ts +0 -50
- package/dist/src/lib/http-agent.d.ts +0 -47
- package/dist/src/lib/index.d.ts +0 -7
- package/dist/src/lib/index.js +0 -10
- package/dist/src/lib/json-array.d.ts +0 -10
- package/dist/src/lib/markdown-to-mdv2.d.ts +0 -53
- package/dist/src/lib/markdown-v2.d.ts +0 -27
- package/dist/src/lib/perf/async-compact.d.ts +0 -26
- package/dist/src/lib/perf/direct-sdk.d.ts +0 -26
- package/dist/src/lib/perf/haiku-route.d.ts +0 -19
- package/dist/src/lib/perf/predictive-continuation.d.ts +0 -18
- package/dist/src/lib/perf/read-batch.d.ts +0 -33
- package/dist/src/lib/perf/skill-list-collapse.d.ts +0 -22
- package/dist/src/lib/perf/speculative-bubble.d.ts +0 -27
- package/dist/src/lib/perf/typing-prefetch.d.ts +0 -25
- package/dist/src/lib/probes.d.ts +0 -50
- package/dist/src/lib/register-guard.d.ts +0 -56
- package/dist/src/lib/req-shape-log.d.ts +0 -31
- package/dist/src/lib/safe-upstream-probes.d.ts +0 -25
- package/dist/src/lib/session-registry.d.ts +0 -66
- package/dist/src/lib/spawn-async.d.ts +0 -18
- package/dist/src/lib/status-tee-reader.d.ts +0 -29
- package/dist/src/lib/sysprompt-strip.d.ts +0 -53
- package/dist/src/lib/telegram-bot-api.d.ts +0 -146
- package/dist/src/lib/telemetry.d.ts +0 -38
- package/dist/src/lib/test-mode.d.ts +0 -26
- package/dist/src/lib/trajectory.d.ts +0 -44
- package/dist/src/lib/vendor-paths.d.ts +0 -12
- package/dist/src/lifecycle/boot.d.ts +0 -48
- package/dist/src/lifecycle/patch-manifest.d.ts +0 -82
- package/dist/src/lifecycle/phase-import-upstream.d.ts +0 -12
- package/dist/src/lifecycle/phase-install-patches.d.ts +0 -12
- package/dist/src/lifecycle/phase-schedule-jobs.d.ts +0 -12
- package/dist/src/lifecycle/phase-start-server.d.ts +0 -11
- package/dist/src/lifecycle/phase-validate-config.d.ts +0 -9
- package/dist/src/lifecycle/phase-validate-upstream.d.ts +0 -11
- package/dist/src/lifecycle/phase-wire-handlers.d.ts +0 -12
- package/dist/src/lifecycle/safe-restart.d.ts +0 -99
- package/dist/src/logger.d.ts +0 -14
- package/dist/src/mcp/bridge.d.ts +0 -21
- package/dist/src/mcp/index.d.ts +0 -2
- package/dist/src/models.d.ts +0 -68
- package/dist/src/observability/event-bus.d.ts +0 -86
- package/dist/src/observability/get-event-bus.d.ts +0 -25
- package/dist/src/observability/observability-service.d.ts +0 -19
- package/dist/src/observability/perf-telemetry.d.ts +0 -65
- package/dist/src/observability/subscribers/metrics.d.ts +0 -11
- package/dist/src/observability/subscribers/session-capture.d.ts +0 -15
- package/dist/src/openai-compat/autonomy-rule.d.ts +0 -26
- package/dist/src/openai-compat/bridges/allowlist.d.ts +0 -19
- package/dist/src/openai-compat/bridges/factory.d.ts +0 -30
- package/dist/src/openai-compat/bridges/media-bridge.d.ts +0 -34
- package/dist/src/openai-compat/bridges/openclaw-api-shim.d.ts +0 -54
- package/dist/src/openai-compat/bridges/openclaw-native-tools.d.ts +0 -61
- package/dist/src/openai-compat/bridges/openclaw-tool-registry.d.ts +0 -26
- package/dist/src/openai-compat/bridges/tts-media-bridge.d.ts +0 -19
- package/dist/src/openai-compat/chat-cwd.d.ts +0 -22
- package/dist/src/openai-compat/cli-stream-parser.d.ts +0 -134
- package/dist/src/openai-compat/index.d.ts +0 -1
- package/dist/src/openai-compat/message-extractor.d.ts +0 -84
- package/dist/src/openai-compat/mode-flags.d.ts +0 -34
- package/dist/src/openai-compat/non-streaming-handler.d.ts +0 -29
- package/dist/src/openai-compat/openai-chunk-types.d.ts +0 -35
- package/dist/src/openai-compat/openai-compat.d.ts +0 -49
- package/dist/src/openai-compat/openai-types.d.ts +0 -71
- package/dist/src/openai-compat/parse-route-body.d.ts +0 -24
- package/dist/src/openai-compat/prompts.d.ts +0 -47
- package/dist/src/openai-compat/request-coalescer.d.ts +0 -77
- package/dist/src/openai-compat/response-formatter.d.ts +0 -33
- package/dist/src/openai-compat/session-key-resolver.d.ts +0 -41
- package/dist/src/openai-compat/skill-resolver.d.ts +0 -59
- package/dist/src/openai-compat/sse-translator.d.ts +0 -51
- package/dist/src/openai-compat/status-reporter.d.ts +0 -30
- package/dist/src/openai-compat/streaming-handler.d.ts +0 -52
- package/dist/src/openai-compat/tool-calls-parser.d.ts +0 -34
- package/dist/src/openai-compat/tool-results-serializer.d.ts +0 -60
- package/dist/src/openai-compat/tts-rule.d.ts +0 -20
- package/dist/src/openai-compat/voice-recovery.d.ts +0 -56
- package/dist/src/patches/cache-parity-registry.d.ts +0 -103
- package/dist/src/patches/claude-md-injection.d.ts +0 -10
- package/dist/src/patches/cwd-redirect.d.ts +0 -10
- package/dist/src/patches/embedded-server-route.d.ts +0 -23
- package/dist/src/patches/pricing-overrides.d.ts +0 -10
- package/dist/src/patches/resume-registry-restore.d.ts +0 -11
- package/dist/src/patches/session-pid-tracking.d.ts +0 -10
- package/dist/src/patches/sysprompt-strip.d.ts +0 -46
- package/dist/src/patches/tools-restoration.d.ts +0 -12
- package/dist/src/persistence/migration-v0.d.ts +0 -24
- package/dist/src/persistence/session-registry.d.ts +0 -58
- package/dist/src/proxy/anthropic-adapter.d.ts +0 -136
- package/dist/src/proxy/handler.d.ts +0 -39
- package/dist/src/proxy/index.d.ts +0 -4
- package/dist/src/proxy/schema-cleaner.d.ts +0 -11
- package/dist/src/proxy/thought-cache.d.ts +0 -19
- package/dist/src/session/embedded-server.d.ts +0 -25
- package/dist/src/session/inbox-manager.d.ts +0 -38
- package/dist/src/session/index.d.ts +0 -3
- package/dist/src/session/persisted-sessions.d.ts +0 -50
- package/dist/src/session/session-manager.d.ts +0 -247
- package/dist/src/session/watchdogs.d.ts +0 -92
- package/dist/src/session-bootstrap/boot-self-heal.d.ts +0 -32
- package/dist/src/session-bootstrap/cwd-patch.d.ts +0 -50
- package/dist/src/session-bootstrap/index.d.ts +0 -3
- package/dist/src/session-bootstrap/resume-registry.d.ts +0 -27
- package/dist/src/session-bootstrap/session-hygiene.d.ts +0 -23
- package/dist/src/session-bootstrap/sysprompt-strip.d.ts +0 -24
- package/dist/src/session-bootstrap/think-conflict-resolver.d.ts +0 -32
- package/dist/src/types/route.d.ts +0 -11
- package/dist/src/types/runtime-config.d.ts +0 -208
- package/dist/src/types/sse.d.ts +0 -29
- package/dist/src/types/tool-bridge.d.ts +0 -82
- package/dist/src/types/upstream.d.ts +0 -580
- package/dist/src/types.d.ts +0 -498
- 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
|
-
|
|
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;
|
package/dist/src/constants.js
CHANGED
|
@@ -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
|
};
|
package/dist/src/lib/config.js
CHANGED
|
@@ -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
|
-
//
|
|
135
|
-
|
|
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
|
-
|
|
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
|
-
|
|
584
|
-
|
|
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.
|
|
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 {};
|