@agentdeck/bridge 1.0.20 → 1.0.22
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/adapters/openclaw.d.ts +55 -1
- package/dist/adapters/openclaw.d.ts.map +1 -1
- package/dist/adapters/openclaw.js +278 -45
- package/dist/adapters/openclaw.js.map +1 -1
- package/dist/adb-reverse.d.ts +26 -3
- package/dist/adb-reverse.d.ts.map +1 -1
- package/dist/adb-reverse.js +26 -5
- package/dist/adb-reverse.js.map +1 -1
- package/dist/apme/adapters/openclaw-hook.d.ts +45 -1
- package/dist/apme/adapters/openclaw-hook.d.ts.map +1 -1
- package/dist/apme/adapters/openclaw-hook.js +84 -4
- package/dist/apme/adapters/openclaw-hook.js.map +1 -1
- package/dist/apme/claude-transcript-reader.d.ts +54 -0
- package/dist/apme/claude-transcript-reader.d.ts.map +1 -1
- package/dist/apme/claude-transcript-reader.js +77 -0
- package/dist/apme/claude-transcript-reader.js.map +1 -1
- package/dist/apme/collector.d.ts +63 -5
- package/dist/apme/collector.d.ts.map +1 -1
- package/dist/apme/collector.js +147 -10
- package/dist/apme/collector.js.map +1 -1
- package/dist/apme/store.d.ts +23 -1
- package/dist/apme/store.d.ts.map +1 -1
- package/dist/apme/store.js +81 -2
- package/dist/apme/store.js.map +1 -1
- package/dist/apme/types.d.ts +1 -1
- package/dist/apme/types.d.ts.map +1 -1
- package/dist/apme/types.js.map +1 -1
- package/dist/auth.d.ts +37 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +23 -0
- package/dist/auth.js.map +1 -1
- package/dist/ble-sync-spawn.d.ts +27 -0
- package/dist/ble-sync-spawn.d.ts.map +1 -1
- package/dist/ble-sync-spawn.js +32 -0
- package/dist/ble-sync-spawn.js.map +1 -1
- package/dist/bridge-core.d.ts +21 -4
- package/dist/bridge-core.d.ts.map +1 -1
- package/dist/bridge-core.js +52 -23
- package/dist/bridge-core.js.map +1 -1
- package/dist/claude-interrupt-marker.d.ts +31 -0
- package/dist/claude-interrupt-marker.d.ts.map +1 -0
- package/dist/claude-interrupt-marker.js +58 -0
- package/dist/claude-interrupt-marker.js.map +1 -0
- package/dist/claude-turn-watchdog.d.ts +23 -0
- package/dist/claude-turn-watchdog.d.ts.map +1 -1
- package/dist/claude-turn-watchdog.js +30 -4
- package/dist/claude-turn-watchdog.js.map +1 -1
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +411 -115
- package/dist/cli.js.map +1 -1
- package/dist/codex-rate-limits-live.d.ts +58 -9
- package/dist/codex-rate-limits-live.d.ts.map +1 -1
- package/dist/codex-rate-limits-live.js +64 -15
- package/dist/codex-rate-limits-live.js.map +1 -1
- package/dist/codex-rate-limits.d.ts +2 -2
- package/dist/codex-rate-limits.d.ts.map +1 -1
- package/dist/codex-rate-limits.js +34 -16
- package/dist/codex-rate-limits.js.map +1 -1
- package/dist/daemon-port.d.ts +90 -0
- package/dist/daemon-port.d.ts.map +1 -0
- package/dist/daemon-port.js +119 -0
- package/dist/daemon-port.js.map +1 -0
- package/dist/daemon-server.d.ts +60 -3
- package/dist/daemon-server.d.ts.map +1 -1
- package/dist/daemon-server.js +362 -109
- package/dist/daemon-server.js.map +1 -1
- package/dist/daemon-settings.d.ts +46 -0
- package/dist/daemon-settings.d.ts.map +1 -0
- package/dist/daemon-settings.js +85 -0
- package/dist/daemon-settings.js.map +1 -0
- package/dist/daemon-takeover.d.ts +75 -0
- package/dist/daemon-takeover.d.ts.map +1 -0
- package/dist/daemon-takeover.js +137 -0
- package/dist/daemon-takeover.js.map +1 -0
- package/dist/daemon.js +1 -1
- package/dist/hook-server.d.ts.map +1 -1
- package/dist/hook-server.js +17 -0
- package/dist/hook-server.js.map +1 -1
- package/dist/idotmatrix/idotmatrix-daemon-sync.d.ts.map +1 -1
- package/dist/idotmatrix/idotmatrix-daemon-sync.js +13 -1
- package/dist/idotmatrix/idotmatrix-daemon-sync.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -12
- package/dist/index.js.map +1 -1
- package/dist/kiro-diagnostics.d.ts +90 -0
- package/dist/kiro-diagnostics.d.ts.map +1 -0
- package/dist/kiro-diagnostics.js +278 -0
- package/dist/kiro-diagnostics.js.map +1 -0
- package/dist/kiro-session.d.ts +113 -0
- package/dist/kiro-session.d.ts.map +1 -0
- package/dist/kiro-session.js +688 -0
- package/dist/kiro-session.js.map +1 -0
- package/dist/kiro-timeline-feed.d.ts +45 -0
- package/dist/kiro-timeline-feed.d.ts.map +1 -0
- package/dist/kiro-timeline-feed.js +116 -0
- package/dist/kiro-timeline-feed.js.map +1 -0
- package/dist/kiro-transcript-timeline.d.ts +57 -0
- package/dist/kiro-transcript-timeline.d.ts.map +1 -0
- package/dist/kiro-transcript-timeline.js +183 -0
- package/dist/kiro-transcript-timeline.js.map +1 -0
- package/dist/linux-service.d.ts +2 -1
- package/dist/linux-service.d.ts.map +1 -1
- package/dist/linux-service.js +8 -3
- package/dist/linux-service.js.map +1 -1
- package/dist/mdns-discover.d.ts +27 -0
- package/dist/mdns-discover.d.ts.map +1 -1
- package/dist/mdns-discover.js +60 -7
- package/dist/mdns-discover.js.map +1 -1
- package/dist/mdns.d.ts.map +1 -1
- package/dist/mdns.js +16 -3
- package/dist/mdns.js.map +1 -1
- package/dist/modules/adb-module.d.ts.map +1 -1
- package/dist/modules/adb-module.js +5 -2
- package/dist/modules/adb-module.js.map +1 -1
- package/dist/modules/index.d.ts +2 -1
- package/dist/modules/index.d.ts.map +1 -1
- package/dist/modules/index.js +1 -0
- package/dist/modules/index.js.map +1 -1
- package/dist/modules/types.d.ts +34 -9
- package/dist/modules/types.d.ts.map +1 -1
- package/dist/modules/types.js +34 -1
- package/dist/modules/types.js.map +1 -1
- package/dist/network-posture.d.ts +67 -0
- package/dist/network-posture.d.ts.map +1 -0
- package/dist/network-posture.js +100 -0
- package/dist/network-posture.js.map +1 -0
- package/dist/observed-steering.d.ts +14 -0
- package/dist/observed-steering.d.ts.map +1 -1
- package/dist/observed-steering.js +18 -0
- package/dist/observed-steering.js.map +1 -1
- package/dist/observed-turn-watchdogs.d.ts +84 -0
- package/dist/observed-turn-watchdogs.d.ts.map +1 -0
- package/dist/observed-turn-watchdogs.js +134 -0
- package/dist/observed-turn-watchdogs.js.map +1 -0
- package/dist/openclaw-session.d.ts +10 -0
- package/dist/openclaw-session.d.ts.map +1 -1
- package/dist/openclaw-session.js +12 -0
- package/dist/openclaw-session.js.map +1 -1
- package/dist/passive-observer.d.ts +19 -0
- package/dist/passive-observer.d.ts.map +1 -1
- package/dist/passive-observer.js +208 -24
- package/dist/passive-observer.js.map +1 -1
- package/dist/pixoo/micro-glyphs.d.ts +1 -1
- package/dist/pixoo/micro-glyphs.d.ts.map +1 -1
- package/dist/pixoo/micro-glyphs.js +2 -0
- package/dist/pixoo/micro-glyphs.js.map +1 -1
- package/dist/pixoo/official-dot-glyphs.generated.d.ts +1 -1
- package/dist/pixoo/official-dot-glyphs.generated.d.ts.map +1 -1
- package/dist/pixoo/official-dot-glyphs.generated.js +47 -0
- package/dist/pixoo/official-dot-glyphs.generated.js.map +1 -1
- package/dist/pixoo/pixoo-discover.d.ts +8 -1
- package/dist/pixoo/pixoo-discover.d.ts.map +1 -1
- package/dist/pixoo/pixoo-discover.js +15 -8
- package/dist/pixoo/pixoo-discover.js.map +1 -1
- package/dist/pixoo/pixoo-renderer.d.ts +4 -2
- package/dist/pixoo/pixoo-renderer.d.ts.map +1 -1
- package/dist/pixoo/pixoo-renderer.js +105 -36
- package/dist/pixoo/pixoo-renderer.js.map +1 -1
- package/dist/pixoo/pixoo-settings.d.ts +19 -2
- package/dist/pixoo/pixoo-settings.d.ts.map +1 -1
- package/dist/pixoo/pixoo-settings.js +22 -4
- package/dist/pixoo/pixoo-settings.js.map +1 -1
- package/dist/pixoo/pixoo-sprites.d.ts.map +1 -1
- package/dist/pixoo/pixoo-sprites.js +2 -0
- package/dist/pixoo/pixoo-sprites.js.map +1 -1
- package/dist/python-ble-runtime.d.ts +2 -0
- package/dist/python-ble-runtime.d.ts.map +1 -1
- package/dist/python-ble-runtime.js +101 -7
- package/dist/python-ble-runtime.js.map +1 -1
- package/dist/session-aggregator.d.ts +9 -1
- package/dist/session-aggregator.d.ts.map +1 -1
- package/dist/session-aggregator.js +1 -1
- package/dist/session-aggregator.js.map +1 -1
- package/dist/session-registry.d.ts +71 -3
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/session-registry.js +126 -19
- package/dist/session-registry.js.map +1 -1
- package/dist/subagent-timeline.d.ts +38 -7
- package/dist/subagent-timeline.d.ts.map +1 -1
- package/dist/subagent-timeline.js +154 -23
- package/dist/subagent-timeline.js.map +1 -1
- package/dist/timebox/timebox-daemon-sync.d.ts.map +1 -1
- package/dist/timebox/timebox-daemon-sync.js +11 -1
- package/dist/timebox/timebox-daemon-sync.js.map +1 -1
- package/dist/timeline-store.d.ts.map +1 -1
- package/dist/timeline-store.js +26 -1
- package/dist/timeline-store.js.map +1 -1
- package/dist/tui/renderer.d.ts.map +1 -1
- package/dist/tui/renderer.js +13 -1
- package/dist/tui/renderer.js.map +1 -1
- package/dist/tui/terrarium.d.ts.map +1 -1
- package/dist/tui/terrarium.js +10 -5
- package/dist/tui/terrarium.js.map +1 -1
- package/dist/usage-api.d.ts +28 -1
- package/dist/usage-api.d.ts.map +1 -1
- package/dist/usage-api.js +74 -33
- package/dist/usage-api.js.map +1 -1
- package/dist/usage-event.d.ts.map +1 -1
- package/dist/usage-event.js +2 -17
- package/dist/usage-event.js.map +1 -1
- package/dist/utils/redact-secrets.d.ts +3 -0
- package/dist/utils/redact-secrets.d.ts.map +1 -0
- package/dist/utils/redact-secrets.js +29 -0
- package/dist/utils/redact-secrets.js.map +1 -0
- package/dist/windows-service.d.ts +2 -1
- package/dist/windows-service.d.ts.map +1 -1
- package/dist/windows-service.js +6 -3
- package/dist/windows-service.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
2
|
import { type Server } from 'http';
|
|
3
3
|
import type { AgentAdapter, AgentCapabilities, AdapterStartOptions, PluginCommand } from '../types.js';
|
|
4
|
+
import { type OpenClawApprovalPrompt } from '@agentdeck/shared';
|
|
4
5
|
/**
|
|
5
6
|
* OpenClaw adapter — connects to OpenClaw Gateway via WebSocket.
|
|
6
7
|
*
|
|
@@ -34,7 +35,14 @@ export declare class OpenClawAdapter extends EventEmitter implements AgentAdapte
|
|
|
34
35
|
private autoReconnect;
|
|
35
36
|
private currentSessionKey;
|
|
36
37
|
private currentRunId;
|
|
37
|
-
|
|
38
|
+
/** The approval the Gateway is currently blocked on, normalized for display
|
|
39
|
+
* AND for answering — the options carry their own decision so an index press
|
|
40
|
+
* is never re-derived at the answer site. `null` ⇒ nothing to approve. */
|
|
41
|
+
private pendingApproval;
|
|
42
|
+
/** Fires at `expiresAtMs`. The Gateway drops an expired approval without
|
|
43
|
+
* emitting `exec.approval.resolved`, so without this the deck would keep
|
|
44
|
+
* offering buttons that resolve to "unknown or expired approval id". */
|
|
45
|
+
private approvalExpiryTimer;
|
|
38
46
|
private chatStarted;
|
|
39
47
|
private chatStartTime;
|
|
40
48
|
private chatToolCount;
|
|
@@ -76,6 +84,52 @@ export declare class OpenClawAdapter extends EventEmitter implements AgentAdapte
|
|
|
76
84
|
start(options: AdapterStartOptions & {
|
|
77
85
|
externalServer?: Server;
|
|
78
86
|
}): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* The approval the Gateway is blocked on, or `null`. Read by the daemon so
|
|
89
|
+
* the virtual `openclaw-gateway` session row can carry the real question and
|
|
90
|
+
* option list — without it every deck falls back to "PERMIT? / answer in
|
|
91
|
+
* terminal", which is not answerable anywhere.
|
|
92
|
+
*/
|
|
93
|
+
getPendingApproval(): OpenClawApprovalPrompt | null;
|
|
94
|
+
/**
|
|
95
|
+
* Read the approvals the Gateway is already blocked on and surface the oldest
|
|
96
|
+
* one. Called once per handshake.
|
|
97
|
+
*
|
|
98
|
+
* Only one is adopted: the deck renders a single question at a time, and the
|
|
99
|
+
* Gateway blocks the agent on each in turn, so the oldest is the one actually
|
|
100
|
+
* holding work up. The rest arrive as `requested` events (or on the next
|
|
101
|
+
* catch-up) once it is answered.
|
|
102
|
+
*/
|
|
103
|
+
private adoptPendingApprovals;
|
|
104
|
+
private setPendingApproval;
|
|
105
|
+
private clearPendingApproval;
|
|
106
|
+
/**
|
|
107
|
+
* The approval went away without a decision — it expired, its run was
|
|
108
|
+
* cancelled, or the Gateway link dropped. The Gateway emits no
|
|
109
|
+
* `exec.approval.resolved` for any of those, so nothing else would ever close
|
|
110
|
+
* the `tool_request` row or move the session off `awaiting_permission`: the
|
|
111
|
+
* deck kept showing PERMIT? for a request that no longer existed.
|
|
112
|
+
*/
|
|
113
|
+
private abandonPendingApproval;
|
|
114
|
+
/** Called from the turn-end paths (`final` / `aborted` / `error`). */
|
|
115
|
+
private abandonPendingApprovalForTurnEnd;
|
|
116
|
+
/**
|
|
117
|
+
* Send the decision the Gateway actually accepts. The pending pointer is held
|
|
118
|
+
* until the RPC succeeds: clearing it optimistically (as this used to) meant a
|
|
119
|
+
* rejected call left the user with no prompt on screen AND an approval still
|
|
120
|
+
* blocking the agent, with the failure swallowed into a debug line that is off
|
|
121
|
+
* by default.
|
|
122
|
+
*/
|
|
123
|
+
private resolveApproval;
|
|
124
|
+
/** Re-emit the pending prompt so devices re-sync after a dropped press. */
|
|
125
|
+
private rebroadcastPendingApproval;
|
|
126
|
+
/**
|
|
127
|
+
* Truncation-tolerant echo check. Devices cap the question they display
|
|
128
|
+
* (ESP32 buffers, 16-char key labels), so an exact compare would refuse every
|
|
129
|
+
* legitimate press from a small surface. Compare on UTF-16 units to match the
|
|
130
|
+
* TS producers; the Swift mirror compares `.utf16` for the same reason.
|
|
131
|
+
*/
|
|
132
|
+
private approvalEchoMatches;
|
|
79
133
|
handleCommand(cmd: PluginCommand): boolean;
|
|
80
134
|
writeInput(data: string): void;
|
|
81
135
|
isAlive(): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openclaw.d.ts","sourceRoot":"","sources":["../../src/adapters/openclaw.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,MAAM,CAAC;AAgBjD,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EAEnB,aAAa,EAQd,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"openclaw.d.ts","sourceRoot":"","sources":["../../src/adapters/openclaw.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,10 +9,11 @@ import { debug, logError } from '../logger.js';
|
|
|
9
9
|
import { summarizeResponse } from '../timeline-summarizer.js';
|
|
10
10
|
import { extractTopicHint, extractTopicHintWithKind, promptSnippetFallback, prepareMarkdownDetail } from '@agentdeck/shared';
|
|
11
11
|
import { cleanRawText, cleanNopMarkers, isOpenClawCronPrompt, ED25519_SPKI_PREFIX_LEN, GATEWAY_PROTOCOL_VERSION, } from '@agentdeck/shared';
|
|
12
|
+
import { parseExecApprovalRequest, decisionForOptionIndex, decisionForRespondValue, execApprovalAllows, } from '@agentdeck/shared';
|
|
12
13
|
import { OPENCLAW_CAPABILITIES, OPENCLAW_GATEWAY_PORT } from '../types.js';
|
|
13
14
|
import { fetchModelCatalog, getDefaultModelName, invalidateModelCache } from '../model-catalog.js';
|
|
14
15
|
import { getApme } from '../apme/index.js';
|
|
15
|
-
import { openclawChatEventToSpans, openclawChatSendToSpan, openclawIdleGapTaskBoundary, openclawSessionToolToSpans, openclawSessionMessageToSpans, OPENCLAW_IDLE_GAP_MS, } from '../apme/adapters/openclaw-hook.js';
|
|
16
|
+
import { openclawChatEventToSpans, openclawChatErrorLabel, openclawChatErrorDetail, openclawChatSendToSpan, openclawIdleGapTaskBoundary, openclawSessionToolToSpans, openclawSessionMessageToSpans, OPENCLAW_IDLE_GAP_MS, } from '../apme/adapters/openclaw-hook.js';
|
|
16
17
|
function extractGatewayTokenFromJson(json) {
|
|
17
18
|
if (!json || typeof json !== 'object')
|
|
18
19
|
return null;
|
|
@@ -63,7 +64,14 @@ export class OpenClawAdapter extends EventEmitter {
|
|
|
63
64
|
// Session tracking
|
|
64
65
|
currentSessionKey = null;
|
|
65
66
|
currentRunId = null;
|
|
66
|
-
|
|
67
|
+
/** The approval the Gateway is currently blocked on, normalized for display
|
|
68
|
+
* AND for answering — the options carry their own decision so an index press
|
|
69
|
+
* is never re-derived at the answer site. `null` ⇒ nothing to approve. */
|
|
70
|
+
pendingApproval = null;
|
|
71
|
+
/** Fires at `expiresAtMs`. The Gateway drops an expired approval without
|
|
72
|
+
* emitting `exec.approval.resolved`, so without this the deck would keep
|
|
73
|
+
* offering buttons that resolve to "unknown or expired approval id". */
|
|
74
|
+
approvalExpiryTimer = null;
|
|
67
75
|
// Chat tracking for timeline events
|
|
68
76
|
chatStarted = false;
|
|
69
77
|
chatStartTime = 0;
|
|
@@ -240,34 +248,182 @@ export class OpenClawAdapter extends EventEmitter {
|
|
|
240
248
|
// Connect to OpenClaw Gateway
|
|
241
249
|
this.connectGateway();
|
|
242
250
|
}
|
|
251
|
+
// ===== Pending exec approval =====
|
|
252
|
+
/**
|
|
253
|
+
* The approval the Gateway is blocked on, or `null`. Read by the daemon so
|
|
254
|
+
* the virtual `openclaw-gateway` session row can carry the real question and
|
|
255
|
+
* option list — without it every deck falls back to "PERMIT? / answer in
|
|
256
|
+
* terminal", which is not answerable anywhere.
|
|
257
|
+
*/
|
|
258
|
+
getPendingApproval() {
|
|
259
|
+
return this.pendingApproval;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Read the approvals the Gateway is already blocked on and surface the oldest
|
|
263
|
+
* one. Called once per handshake.
|
|
264
|
+
*
|
|
265
|
+
* Only one is adopted: the deck renders a single question at a time, and the
|
|
266
|
+
* Gateway blocks the agent on each in turn, so the oldest is the one actually
|
|
267
|
+
* holding work up. The rest arrive as `requested` events (or on the next
|
|
268
|
+
* catch-up) once it is answered.
|
|
269
|
+
*/
|
|
270
|
+
async adoptPendingApprovals() {
|
|
271
|
+
const pending = await this.rpcCall('exec.approval.list', {});
|
|
272
|
+
if (!Array.isArray(pending) || pending.length === 0)
|
|
273
|
+
return;
|
|
274
|
+
const oldest = [...pending].sort((a, b) => (a?.createdAtMs ?? 0) - (b?.createdAtMs ?? 0))[0];
|
|
275
|
+
const prompt = parseExecApprovalRequest(oldest, Date.now());
|
|
276
|
+
if (!prompt)
|
|
277
|
+
return;
|
|
278
|
+
// Already tracking it (a reconnect that raced the event) — don't re-emit.
|
|
279
|
+
if (this.pendingApproval?.id === prompt.id)
|
|
280
|
+
return;
|
|
281
|
+
debug('adapter:openclaw', `adopted pending approval ${prompt.id} on connect`);
|
|
282
|
+
this.setPendingApproval(prompt);
|
|
283
|
+
this.emitTimelineEntry({
|
|
284
|
+
ts: Date.now(), type: 'tool_request',
|
|
285
|
+
raw: prompt.question.length > 500 ? prompt.question.slice(0, 497) + '...' : prompt.question,
|
|
286
|
+
...(prompt.detail ? { detail: prompt.detail } : {}),
|
|
287
|
+
approvalId: prompt.id, status: 'pending',
|
|
288
|
+
});
|
|
289
|
+
this.rebroadcastPendingApproval();
|
|
290
|
+
}
|
|
291
|
+
setPendingApproval(prompt) {
|
|
292
|
+
this.clearPendingApproval();
|
|
293
|
+
this.pendingApproval = prompt;
|
|
294
|
+
if (typeof prompt.expiresAtMs === 'number') {
|
|
295
|
+
const delay = prompt.expiresAtMs - Date.now();
|
|
296
|
+
// An already-expired stamp still needs the sweep to run (on the next
|
|
297
|
+
// tick, not inline) so the row and the state settle the same way.
|
|
298
|
+
this.approvalExpiryTimer = setTimeout(() => this.abandonPendingApproval(prompt.id, 'Expired'), Math.max(0, delay));
|
|
299
|
+
this.approvalExpiryTimer.unref?.();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
clearPendingApproval() {
|
|
303
|
+
if (this.approvalExpiryTimer) {
|
|
304
|
+
clearTimeout(this.approvalExpiryTimer);
|
|
305
|
+
this.approvalExpiryTimer = null;
|
|
306
|
+
}
|
|
307
|
+
this.pendingApproval = null;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* The approval went away without a decision — it expired, its run was
|
|
311
|
+
* cancelled, or the Gateway link dropped. The Gateway emits no
|
|
312
|
+
* `exec.approval.resolved` for any of those, so nothing else would ever close
|
|
313
|
+
* the `tool_request` row or move the session off `awaiting_permission`: the
|
|
314
|
+
* deck kept showing PERMIT? for a request that no longer existed.
|
|
315
|
+
*/
|
|
316
|
+
abandonPendingApproval(approvalId, reason) {
|
|
317
|
+
if (!this.pendingApproval || this.pendingApproval.id !== approvalId)
|
|
318
|
+
return;
|
|
319
|
+
this.clearPendingApproval();
|
|
320
|
+
this.emitTimelineEntry({
|
|
321
|
+
ts: Date.now(), type: 'tool_resolved', raw: `Not approved · ${reason}`,
|
|
322
|
+
approvalId, status: 'denied',
|
|
323
|
+
});
|
|
324
|
+
this.emitAdapterEvent({ source: 'parser', event: 'idle' });
|
|
325
|
+
}
|
|
326
|
+
/** Called from the turn-end paths (`final` / `aborted` / `error`). */
|
|
327
|
+
abandonPendingApprovalForTurnEnd(reason) {
|
|
328
|
+
const pending = this.pendingApproval;
|
|
329
|
+
if (pending)
|
|
330
|
+
this.abandonPendingApproval(pending.id, reason);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Send the decision the Gateway actually accepts. The pending pointer is held
|
|
334
|
+
* until the RPC succeeds: clearing it optimistically (as this used to) meant a
|
|
335
|
+
* rejected call left the user with no prompt on screen AND an approval still
|
|
336
|
+
* blocking the agent, with the failure swallowed into a debug line that is off
|
|
337
|
+
* by default.
|
|
338
|
+
*/
|
|
339
|
+
resolveApproval(prompt, decision) {
|
|
340
|
+
debug('adapter:openclaw', `exec.approval.resolve ${prompt.id} → ${decision}`);
|
|
341
|
+
this.rpcCall('exec.approval.resolve', { id: prompt.id, decision })
|
|
342
|
+
.then(() => {
|
|
343
|
+
// The `exec.approval.resolved` event is the authoritative close (it also
|
|
344
|
+
// arrives when another operator answers). Clear here too so a dropped
|
|
345
|
+
// event cannot strand the prompt.
|
|
346
|
+
if (this.pendingApproval?.id === prompt.id)
|
|
347
|
+
this.clearPendingApproval();
|
|
348
|
+
})
|
|
349
|
+
.catch((err) => {
|
|
350
|
+
logError(`[adapter:openclaw] exec.approval.resolve failed (${decision}): ${String(err)}`);
|
|
351
|
+
this.emitTimelineEntry({
|
|
352
|
+
ts: Date.now(), type: 'error',
|
|
353
|
+
raw: `Approval failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
354
|
+
});
|
|
355
|
+
// Put the prompt back on every surface so the user can retry rather
|
|
356
|
+
// than staring at a deck that silently did nothing.
|
|
357
|
+
this.rebroadcastPendingApproval();
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
/** Re-emit the pending prompt so devices re-sync after a dropped press. */
|
|
361
|
+
rebroadcastPendingApproval() {
|
|
362
|
+
const prompt = this.pendingApproval;
|
|
363
|
+
if (!prompt)
|
|
364
|
+
return;
|
|
365
|
+
this.emitAdapterEvent({
|
|
366
|
+
source: 'parser',
|
|
367
|
+
event: 'permission_prompt',
|
|
368
|
+
data: {
|
|
369
|
+
question: prompt.question,
|
|
370
|
+
options: prompt.options.map((o) => ({
|
|
371
|
+
index: o.index, label: o.label, shortcut: o.shortcut,
|
|
372
|
+
})),
|
|
373
|
+
navigable: false,
|
|
374
|
+
cursorIndex: 0,
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Truncation-tolerant echo check. Devices cap the question they display
|
|
380
|
+
* (ESP32 buffers, 16-char key labels), so an exact compare would refuse every
|
|
381
|
+
* legitimate press from a small surface. Compare on UTF-16 units to match the
|
|
382
|
+
* TS producers; the Swift mirror compares `.utf16` for the same reason.
|
|
383
|
+
*/
|
|
384
|
+
approvalEchoMatches(prompt, echo) {
|
|
385
|
+
const a = prompt.question.trim();
|
|
386
|
+
const b = echo.trim();
|
|
387
|
+
if (!b)
|
|
388
|
+
return true;
|
|
389
|
+
return a.startsWith(b) || b.startsWith(a);
|
|
390
|
+
}
|
|
243
391
|
handleCommand(cmd) {
|
|
244
392
|
switch (cmd.type) {
|
|
245
393
|
case 'respond': {
|
|
246
394
|
debug('adapter:openclaw', `respond: "${cmd.value}"`);
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
decision
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
this.
|
|
395
|
+
const prompt = this.pendingApproval;
|
|
396
|
+
if (prompt) {
|
|
397
|
+
const decision = decisionForRespondValue(prompt, cmd.value);
|
|
398
|
+
if (!decision) {
|
|
399
|
+
logError(`[adapter:openclaw] respond "${cmd.value}" does not name a decision this approval allows` +
|
|
400
|
+
` (${prompt.options.map((o) => o.decision).join('/')}) — ignored`);
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
this.resolveApproval(prompt, decision);
|
|
256
404
|
}
|
|
257
405
|
return true;
|
|
258
406
|
}
|
|
259
407
|
case 'select_option': {
|
|
260
408
|
debug('adapter:openclaw', `select_option: idx=${cmd.index}`);
|
|
261
|
-
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
409
|
+
const prompt = this.pendingApproval;
|
|
410
|
+
if (prompt) {
|
|
411
|
+
// The index names an option in the list the device was DISPLAYING.
|
|
412
|
+
// `question` echoes that list's headline; a press aimed at an
|
|
413
|
+
// approval the Gateway has already moved past must be dropped rather
|
|
414
|
+
// than applied to the new one — approving the wrong command is the
|
|
415
|
+
// single worst outcome this path can produce.
|
|
416
|
+
if (cmd.question && !this.approvalEchoMatches(prompt, cmd.question)) {
|
|
417
|
+
logError('[adapter:openclaw] select_option dropped: question echo does not match the pending approval');
|
|
418
|
+
this.rebroadcastPendingApproval();
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
const decision = decisionForOptionIndex(prompt, cmd.index);
|
|
422
|
+
if (!decision) {
|
|
423
|
+
logError(`[adapter:openclaw] select_option index ${cmd.index} out of range — ignored`);
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
this.resolveApproval(prompt, decision);
|
|
271
427
|
}
|
|
272
428
|
return true;
|
|
273
429
|
}
|
|
@@ -530,6 +686,10 @@ export class OpenClawAdapter extends EventEmitter {
|
|
|
530
686
|
debug('adapter:openclaw', 'Gateway disconnected');
|
|
531
687
|
const wasAlive = this.alive;
|
|
532
688
|
this.alive = false;
|
|
689
|
+
// Pending approvals are Gateway-process state: a reconnect issues new
|
|
690
|
+
// ids, so holding the old one would offer buttons that resolve to
|
|
691
|
+
// "unknown or expired approval id".
|
|
692
|
+
this.abandonPendingApprovalForTurnEnd('gateway disconnected');
|
|
533
693
|
if (wasAlive) {
|
|
534
694
|
this.emitAdapterEvent({ source: 'connection', status: 'disconnected' });
|
|
535
695
|
this.emitAdapterEvent({ source: 'hook', event: 'SessionEnd', data: {} });
|
|
@@ -841,6 +1001,7 @@ export class OpenClawAdapter extends EventEmitter {
|
|
|
841
1001
|
this.lastPrompt = null;
|
|
842
1002
|
this.accumulatedResponse = '';
|
|
843
1003
|
this.currentRunId = null;
|
|
1004
|
+
this.abandonPendingApprovalForTurnEnd('turn ended');
|
|
844
1005
|
this.emitAdapterEvent({ source: 'parser', event: 'idle' });
|
|
845
1006
|
break;
|
|
846
1007
|
}
|
|
@@ -874,19 +1035,64 @@ export class OpenClawAdapter extends EventEmitter {
|
|
|
874
1035
|
this.lastPrompt = null;
|
|
875
1036
|
this.accumulatedResponse = '';
|
|
876
1037
|
this.currentRunId = null;
|
|
1038
|
+
// A run cancelled while an approval was outstanding is the shape
|
|
1039
|
+
// that stranded the deck on PERMIT?: the Gateway drops the approval
|
|
1040
|
+
// silently, so this is the only place that can close it.
|
|
1041
|
+
this.abandonPendingApprovalForTurnEnd('run cancelled');
|
|
877
1042
|
this.emitAdapterEvent({ source: 'parser', event: 'idle' });
|
|
878
1043
|
break;
|
|
879
1044
|
}
|
|
880
1045
|
case 'error': {
|
|
881
|
-
|
|
1046
|
+
// The Gateway's error frame reports only the user-facing message
|
|
1047
|
+
// plus `errorKind`/`stopReason`/`runId` — never the provider or
|
|
1048
|
+
// model that failed (OpenClaw's failover means the failing model
|
|
1049
|
+
// isn't the one the turn ends on anyway). So the row is built from
|
|
1050
|
+
// the frame PLUS this adapter's own turn counters; anything not
|
|
1051
|
+
// reported stays absent rather than getting guessed. `runId` is
|
|
1052
|
+
// the join key into the gateway log, where the provider/model and
|
|
1053
|
+
// the HTTP status do live.
|
|
1054
|
+
//
|
|
1055
|
+
// The frame spells the sentence `errorMessage` while
|
|
1056
|
+
// `ChatEventPayload` declares `error` — accept either rather than
|
|
1057
|
+
// silently losing it to a name mismatch.
|
|
1058
|
+
const errText = [payload.errorMessage, payload.error]
|
|
1059
|
+
.find((v) => typeof v === 'string' && v.trim().length > 0)?.trim();
|
|
1060
|
+
const errInput = {
|
|
1061
|
+
state: 'error',
|
|
1062
|
+
...(errText ? { error: errText } : {}),
|
|
1063
|
+
...(typeof payload.errorKind === 'string' ? { errorKind: payload.errorKind } : {}),
|
|
1064
|
+
...(typeof payload.stopReason === 'string' ? { stopReason: payload.stopReason } : {}),
|
|
1065
|
+
...(runId ? { runId } : {}),
|
|
1066
|
+
...(sessionKey ? { sessionKey } : {}),
|
|
1067
|
+
durationSec: this.chatStarted ? Math.round((Date.now() - this.chatStartTime) / 1000) : 0,
|
|
1068
|
+
toolNames: [...this.chatToolNames],
|
|
1069
|
+
};
|
|
1070
|
+
const errLabel = openclawChatErrorLabel(errInput);
|
|
1071
|
+
const errDetail = openclawChatErrorDetail(errInput);
|
|
1072
|
+
const errTs = Date.now();
|
|
882
1073
|
this.emitTimelineEntry({
|
|
883
|
-
ts:
|
|
1074
|
+
ts: errTs, type: 'error', raw: errLabel,
|
|
1075
|
+
...(errDetail ? { detail: errDetail } : {}),
|
|
1076
|
+
// Parity with the `final`/`aborted` rows: without this an
|
|
1077
|
+
// errored cron turn is indistinguishable from a user's.
|
|
1078
|
+
...(this.chatIsAutomated ? { automated: true } : {}),
|
|
1079
|
+
...(this.chatStarted ? { startedAt: this.chatStartTime, endedAt: errTs } : {}),
|
|
884
1080
|
});
|
|
1081
|
+
// APME: record the failure in the trajectory, then re-arm the
|
|
1082
|
+
// idle-gap timer. `chat.send` cleared that timer and only `final`
|
|
1083
|
+
// used to re-arm it, so a prompt that errored and was then
|
|
1084
|
+
// abandoned left its task open indefinitely.
|
|
1085
|
+
const errCtx = this.buildApmeCtx();
|
|
1086
|
+
if (errCtx) {
|
|
1087
|
+
this.ingestApmeSpans(openclawChatEventToSpans(errCtx, errInput));
|
|
1088
|
+
this.armIdleGapTimer();
|
|
1089
|
+
}
|
|
885
1090
|
this.chatStarted = false;
|
|
886
1091
|
this.lastPrompt = null;
|
|
887
1092
|
this.accumulatedResponse = '';
|
|
888
1093
|
this.currentRunId = null;
|
|
889
|
-
debug('adapter:openclaw', `Chat error: ${
|
|
1094
|
+
debug('adapter:openclaw', `Chat error: ${errLabel}`);
|
|
1095
|
+
this.abandonPendingApprovalForTurnEnd('turn failed');
|
|
890
1096
|
this.emitAdapterEvent({ source: 'parser', event: 'idle' });
|
|
891
1097
|
break;
|
|
892
1098
|
}
|
|
@@ -895,33 +1101,41 @@ export class OpenClawAdapter extends EventEmitter {
|
|
|
895
1101
|
}
|
|
896
1102
|
// ===== Tool approval =====
|
|
897
1103
|
case 'exec.approval.requested': {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1104
|
+
// The Gateway nests everything under `request` and describes the
|
|
1105
|
+
// permitted decisions per-request. Both facts are handled in the shared
|
|
1106
|
+
// parser — reading this payload flat here is what left every approval
|
|
1107
|
+
// rendered as a bare "Approve tool execution?" with no command on it.
|
|
1108
|
+
const prompt = parseExecApprovalRequest(payload, Date.now());
|
|
1109
|
+
if (!prompt) {
|
|
1110
|
+
logError('[adapter:openclaw] exec.approval.requested without a usable id — ignored');
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
1113
|
+
this.setPendingApproval(prompt);
|
|
902
1114
|
// Track tool for chat summary
|
|
903
|
-
const toolName = command
|
|
1115
|
+
const toolName = prompt.command.split(/\s+/)[0] || 'tool';
|
|
904
1116
|
this.chatToolCount++;
|
|
905
1117
|
if (!this.chatToolNames.includes(toolName)) {
|
|
906
1118
|
this.chatToolNames.push(toolName);
|
|
907
1119
|
}
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
1120
|
+
const toolRequestRaw = prompt.question.length > 500
|
|
1121
|
+
? prompt.question.slice(0, 497) + '...'
|
|
1122
|
+
: prompt.question;
|
|
1123
|
+
const toolRequestDetail = prompt.detail && prompt.detail.length > 1000
|
|
1124
|
+
? prompt.detail.slice(0, 997) + '...'
|
|
1125
|
+
: prompt.detail;
|
|
911
1126
|
this.emitTimelineEntry({
|
|
912
1127
|
ts: Date.now(), type: 'tool_request', raw: toolRequestRaw,
|
|
913
1128
|
...(toolRequestDetail ? { detail: toolRequestDetail } : {}),
|
|
914
|
-
approvalId, status: 'pending',
|
|
1129
|
+
approvalId: prompt.id, status: 'pending',
|
|
915
1130
|
});
|
|
916
1131
|
this.emitAdapterEvent({
|
|
917
1132
|
source: 'parser',
|
|
918
1133
|
event: 'permission_prompt',
|
|
919
1134
|
data: {
|
|
920
|
-
question:
|
|
921
|
-
options:
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
],
|
|
1135
|
+
question: prompt.question,
|
|
1136
|
+
options: prompt.options.map((o) => ({
|
|
1137
|
+
index: o.index, label: o.label, shortcut: o.shortcut,
|
|
1138
|
+
})),
|
|
925
1139
|
navigable: false,
|
|
926
1140
|
cursorIndex: 0,
|
|
927
1141
|
},
|
|
@@ -929,19 +1143,32 @@ export class OpenClawAdapter extends EventEmitter {
|
|
|
929
1143
|
break;
|
|
930
1144
|
}
|
|
931
1145
|
case 'exec.approval.resolved': {
|
|
932
|
-
//
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1146
|
+
// Resolved by us or by another operator client. Key off the payload's
|
|
1147
|
+
// own id, not our pending pointer: a resolution can arrive for an
|
|
1148
|
+
// approval we already dropped (expiry, abort), and treating it as
|
|
1149
|
+
// "whatever we last saw" mislabels an unrelated row.
|
|
1150
|
+
const resolvedId = typeof payload.id === 'string' && payload.id
|
|
1151
|
+
? payload.id
|
|
1152
|
+
: this.pendingApproval?.id;
|
|
1153
|
+
const decision = typeof payload.decision === 'string' ? payload.decision : '';
|
|
1154
|
+
const allowed = execApprovalAllows(decision);
|
|
1155
|
+
if (this.pendingApproval && resolvedId === this.pendingApproval.id) {
|
|
1156
|
+
this.clearPendingApproval();
|
|
1157
|
+
}
|
|
936
1158
|
if (resolvedId) {
|
|
937
1159
|
this.emitTimelineEntry({
|
|
938
1160
|
ts: Date.now(), type: 'tool_resolved',
|
|
939
|
-
|
|
1161
|
+
// `decision` is allow-once / allow-always / deny. Comparing it to
|
|
1162
|
+
// 'allow' (as this did) labeled every real approval "Denied".
|
|
1163
|
+
raw: decision === 'allow-always' ? 'Approved (always)'
|
|
1164
|
+
: allowed ? 'Approved' : 'Denied',
|
|
940
1165
|
approvalId: resolvedId,
|
|
941
|
-
status:
|
|
1166
|
+
status: allowed ? 'approved' : 'denied',
|
|
942
1167
|
});
|
|
943
1168
|
}
|
|
944
|
-
|
|
1169
|
+
// Denial does not resume the turn — the tool call fails and the agent
|
|
1170
|
+
// either recovers or ends. Only an allow means work continues.
|
|
1171
|
+
this.emitAdapterEvent({ source: 'parser', event: allowed ? 'spinner_start' : 'idle' });
|
|
945
1172
|
break;
|
|
946
1173
|
}
|
|
947
1174
|
// ===== Gateway health =====
|
|
@@ -1062,6 +1289,12 @@ export class OpenClawAdapter extends EventEmitter {
|
|
|
1062
1289
|
this.fetchSessions().catch(err => debug('adapter:openclaw', `fetchSessions error: ${err}`));
|
|
1063
1290
|
// Fetch model catalog (async, non-blocking)
|
|
1064
1291
|
this.emitModelCatalog().catch(err => debug('adapter:openclaw', `emitModelCatalog error: ${err}`));
|
|
1292
|
+
// Catch up on approvals that were already waiting. `exec.approval.requested`
|
|
1293
|
+
// is a broadcast and is never replayed, so without this read a daemon
|
|
1294
|
+
// restart (or any Gateway reconnect) leaves the agent blocked on an
|
|
1295
|
+
// approval that exists on exactly one side: the user sees an idle deck
|
|
1296
|
+
// and the agent never moves.
|
|
1297
|
+
this.adoptPendingApprovals().catch(err => debug('adapter:openclaw', `adoptPendingApprovals error: ${err}`));
|
|
1065
1298
|
},
|
|
1066
1299
|
reject: (err) => {
|
|
1067
1300
|
debug('adapter:openclaw', `Handshake failed: ${err.message}`);
|