ticlawk 0.1.16-dev.2 → 0.1.16-dev.20

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.
@@ -12,7 +12,6 @@ import { buildAgentRuntimeEnv } from '../../core/runtime-env.mjs';
12
12
  import { buildStandingPrompt } from '../_shared/standing-prompt.mjs';
13
13
  import { ensureAgentHome } from '../../core/agent-home.mjs';
14
14
  import {
15
- createCCSession,
16
15
  getClaudeCodeRuntimeHealth,
17
16
  runCCPrompt,
18
17
  streamCCPrompt,
@@ -24,29 +23,22 @@ import {
24
23
  } from './session.mjs';
25
24
  import { discoverSessions } from './transcripts.mjs';
26
25
  import { buildImageMessageFromInbound } from '../../core/media/inbound.mjs';
27
- import { normalizeOutboundMedia } from '../../core/media/outbound.mjs';
28
26
  import { emitWorkerEvent } from '../../core/events/worker-events.mjs';
29
27
  import {
30
28
  shouldStreamRuntime,
31
- sendAdapterMessage,
32
- recordActivity,
33
29
  reportSubprocessFailure,
34
30
  terminalRuntimeFailure,
35
31
  updateBindingRuntimeMeta,
32
+ resolveRuntimeSessionScope,
33
+ buildRuntimeSessionMetaPatch,
36
34
  } from '../../core/runtime-support.mjs';
37
35
 
38
36
  export const claudeCodeRuntime = {
39
37
  name: 'claude_code',
40
38
 
41
- // Start a new Claude session without blocking on local transcript
42
- // discovery. The live turn path trusts stdout/session_id; transcript
43
- // indexing is a separate concern.
44
- async createSession({ projectDir, text, claudePath }) {
45
- return createCCSession({ projectDir, message: text, claudePath });
46
- },
47
-
48
- // Run a Claude turn and wait for the final result on stdout. This is
49
- // the worker-first path used by the adapter for direct reply delivery.
39
+ // Run a Claude turn and wait for the final result on stdout. Used by
40
+ // deliverTurn when streaming is disabled; both fresh sessions
41
+ // (sessionId=null) and resumed sessions go through here.
50
42
  runTurn({ sessionId, projectDir, claudePath, agentEnv }, text, opts = {}) {
51
43
  return runCCPrompt({
52
44
  sessionId,
@@ -130,109 +122,41 @@ export const claudeCodeRuntime = {
130
122
  if (!binding) return false;
131
123
  const adapter = ctx.adapter;
132
124
  const meta = binding.runtimeMeta || {};
133
- // slock-style: cwd is the per-agent home dir under ~/.ticlawk/agents/<id>/.
125
+ // cwd is the per-agent home dir under ~/.ticlawk/agents/<id>/.
134
126
  const projectDir = ensureAgentHome(binding.id, {
135
127
  displayName: binding.display_name || binding.name || null,
136
128
  });
137
- const sessionId = meta.sessionId || binding.id;
138
129
  const runtimeClaudePath = meta.claudePath || meta.runtimePath || null;
139
130
 
140
131
  const message = inbound.action === 'image'
141
132
  ? await buildImageMessageFromInbound(inbound, 'claude-code')
142
133
  : inbound.text;
143
134
 
144
- const shouldRotate = !meta.sessionId || meta.rotatePending;
145
- if (shouldRotate) {
146
- await emitWorkerEvent({
147
- adapter,
148
- binding,
149
- agent: this.name,
150
- sessionId: sessionId || binding.id,
151
- cwd: projectDir,
152
- replyToMessageId: inbound.messageId || null,
153
- event: {
154
- hook_event_name: 'worker.turn.start',
155
- worker_event_name: 'worker.turn.start',
156
- },
157
- logger: ctx.logger,
158
- });
159
- try {
160
- const claudePath = requireClaudePath(runtimeClaudePath);
161
- const claudeVersion = getClaudeCodeRuntimeHealth(claudePath).version || meta.claudeVersion || null;
162
- const created = await this.createSession({ projectDir, text: message, claudePath });
163
- const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
164
- sessionId: created.sessionId,
165
- path: null,
166
- runtimePath: claudePath,
167
- claudePath,
168
- claudeVersion,
169
- rotatePending: false,
170
- lastRotatedAt: new Date().toISOString(),
171
- }, { status: 'connected' });
172
- if (created.resultText && created.resultText.trim()) {
173
- await sendAdapterMessage(adapter, nextBinding, {
174
- type: 'assistant',
175
- text: created.resultText,
176
- media: [],
177
- replyToMessageId: inbound.messageId || null,
178
- });
179
- }
180
- await emitWorkerEvent({
181
- adapter,
182
- binding: nextBinding,
183
- agent: this.name,
184
- sessionId: created.sessionId,
185
- cwd: projectDir,
186
- replyToMessageId: inbound.messageId || null,
187
- event: {
188
- hook_event_name: 'Stop',
189
- worker_event_name: 'worker.turn.complete',
190
- },
191
- logger: ctx.logger,
192
- });
193
- return true;
194
- } catch (err) {
195
- await emitWorkerEvent({
196
- adapter,
197
- binding,
198
- agent: this.name,
199
- sessionId: sessionId || binding.id,
200
- cwd: projectDir,
201
- replyToMessageId: inbound.messageId || null,
202
- event: {
203
- hook_event_name: 'worker.turn.error',
204
- worker_event_name: 'worker.turn.error',
205
- error: err?.message || 'Claude Code failed',
206
- },
207
- logger: ctx.logger,
208
- });
209
- await reportSubprocessFailure({
210
- adapter,
211
- binding,
212
- inbound,
213
- runtimeName: 'Claude Code',
214
- info: err?.info || {
215
- ok: false,
216
- kind: 'exit-error',
217
- errorMessage: err?.message || 'Claude Code failed',
218
- durationMs: 0,
219
- },
220
- });
221
- return terminalRuntimeFailure(err?.message || 'Claude Code failed');
222
- }
223
- }
135
+ // shouldRotate=true means this conversation has no runtime session
136
+ // yet, or the agent was reset and all scoped sessions are invalid.
137
+ // We pass sessionId=null so `claude` creates a fresh session; the new
138
+ // session_id is captured from stream events and persisted below.
139
+ // Unifying rotate + non-rotate into one path means the standing prompt
140
+ // is always attached, so the agent uses the CLI to reply on every
141
+ // turn including the first.
142
+ const sessionScope = resolveRuntimeSessionScope(meta, inbound);
143
+ const targetSessionId = sessionScope.shouldRotate ? null : sessionScope.sessionId;
144
+ const errEventSessionId = targetSessionId || binding.id;
224
145
 
225
146
  try {
226
147
  const claudePath = requireClaudePath(runtimeClaudePath);
227
148
  const claudeVersion = getClaudeCodeRuntimeHealth(claudePath).version || meta.claudeVersion || null;
228
149
  const agentEnv = buildAgentRuntimeEnv({
229
150
  agentId: binding.id,
230
- sessionId,
151
+ sessionId: targetSessionId,
231
152
  hostId: binding.runtime_host_id,
153
+ conversationId: inbound.conversationId,
154
+ messageId: inbound.messageId,
155
+ target: inbound.envelopeTarget,
232
156
  });
233
- const appendSystemPrompt = buildStandingPrompt({ agentId: binding.id });
157
+ const appendSystemPrompt = buildStandingPrompt({ agentId: binding.id, inbound });
234
158
  const result = shouldStreamRuntime(this.name, this)
235
- ? await this.runTurnStream({ sessionId, projectDir, claudePath, agentEnv }, message, {
159
+ ? await this.runTurnStream({ sessionId: targetSessionId, projectDir, claudePath, agentEnv }, message, {
236
160
  appendSystemPrompt,
237
161
  onEvent: async (event) => {
238
162
  if (event?.type === 'turn.started') {
@@ -240,7 +164,7 @@ export const claudeCodeRuntime = {
240
164
  adapter,
241
165
  binding,
242
166
  agent: this.name,
243
- sessionId: event.sessionId || sessionId,
167
+ sessionId: event.sessionId || targetSessionId || binding.id,
244
168
  cwd: projectDir,
245
169
  replyToMessageId: inbound.messageId || null,
246
170
  event: {
@@ -254,7 +178,7 @@ export const claudeCodeRuntime = {
254
178
  adapter,
255
179
  binding,
256
180
  agent: this.name,
257
- sessionId: event.sessionId || sessionId,
181
+ sessionId: event.sessionId || targetSessionId || binding.id,
258
182
  cwd: projectDir,
259
183
  replyToMessageId: inbound.messageId || null,
260
184
  event: {
@@ -267,22 +191,20 @@ export const claudeCodeRuntime = {
267
191
  }
268
192
  },
269
193
  })
270
- : await this.runTurn({ sessionId, projectDir, claudePath, agentEnv }, message, { appendSystemPrompt });
194
+ : await this.runTurn({ sessionId: targetSessionId, projectDir, claudePath, agentEnv }, message, { appendSystemPrompt });
271
195
  const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
272
- sessionId: result?.sessionId || meta.sessionId,
196
+ ...buildRuntimeSessionMetaPatch(meta, sessionScope, {
197
+ sessionId: result?.sessionId,
198
+ }),
273
199
  runtimePath: claudePath,
274
200
  claudePath,
275
201
  claudeVersion,
276
202
  }, { status: 'connected' });
277
- await recordActivity(adapter, nextBinding, inbound, {
278
- ...result,
279
- media: normalizeOutboundMedia(result),
280
- });
281
203
  await emitWorkerEvent({
282
204
  adapter,
283
205
  binding: nextBinding,
284
206
  agent: this.name,
285
- sessionId: result?.sessionId || sessionId,
207
+ sessionId: result?.sessionId || targetSessionId || binding.id,
286
208
  cwd: projectDir,
287
209
  replyToMessageId: inbound.messageId || null,
288
210
  event: {
@@ -297,7 +219,7 @@ export const claudeCodeRuntime = {
297
219
  adapter,
298
220
  binding,
299
221
  agent: this.name,
300
- sessionId,
222
+ sessionId: errEventSessionId,
301
223
  cwd: projectDir,
302
224
  replyToMessageId: inbound.messageId || null,
303
225
  event: {
@@ -5,7 +5,10 @@
5
5
  * and discover local sessions.
6
6
  */
7
7
 
8
- import { basename } from 'node:path';
8
+ import { createHash } from 'node:crypto';
9
+ import { mkdirSync, writeFileSync } from 'node:fs';
10
+ import { homedir } from 'node:os';
11
+ import { basename, join } from 'node:path';
9
12
  import {
10
13
  createCodexSession,
11
14
  runCodexPrompt,
@@ -18,21 +21,98 @@ import {
18
21
  requireCodexPath,
19
22
  } from './session.mjs';
20
23
  import { buildCodexInputFromInbound } from '../../core/media/inbound.mjs';
21
- import { normalizeOutboundMedia } from '../../core/media/outbound.mjs';
22
24
  import { emitWorkerEvent } from '../../core/events/worker-events.mjs';
23
25
  import {
24
26
  shouldStreamRuntime,
25
27
  createDeltaAggregator,
26
- sendAdapterMessage,
27
- recordActivity,
28
28
  reportSubprocessFailure,
29
29
  terminalRuntimeFailure,
30
30
  updateBindingRuntimeMeta,
31
31
  isRuntimeGatewayFailure,
32
+ resolveRuntimeSessionScope,
33
+ buildRuntimeSessionMetaPatch,
32
34
  } from '../../core/runtime-support.mjs';
33
35
  import { buildAgentRuntimeEnv } from '../../core/runtime-env.mjs';
34
36
  import { buildStandingPrompt } from '../_shared/standing-prompt.mjs';
35
37
  import { ensureAgentHome } from '../../core/agent-home.mjs';
38
+ import { debugError, debugLog } from '../../core/logger.mjs';
39
+
40
+ function parseBooleanish(value) {
41
+ if (value === undefined || value === null || value === '') return false;
42
+ return ['1', 'true', 'on', 'yes'].includes(String(value).trim().toLowerCase());
43
+ }
44
+
45
+ function sha256(text) {
46
+ return createHash('sha256').update(String(text || ''), 'utf8').digest('hex');
47
+ }
48
+
49
+ function fileSafeId(value) {
50
+ return String(value || 'unknown')
51
+ .replace(/[^a-zA-Z0-9_-]+/g, '-')
52
+ .replace(/^-+|-+$/g, '')
53
+ .slice(0, 80) || 'unknown';
54
+ }
55
+
56
+ function writePromptSnapshot({
57
+ binding,
58
+ inbound,
59
+ agentHome,
60
+ targetSessionId,
61
+ shouldRotate,
62
+ developerInstructions,
63
+ message,
64
+ input,
65
+ }) {
66
+ if (!parseBooleanish(process.env.TICLAWK_LOG_RUNTIME_PROMPTS)) return;
67
+
68
+ try {
69
+ const rootDir = process.env.TICLAWK_RUNTIME_PROMPT_LOG_DIR
70
+ || join(homedir(), '.ticlawk', 'prompt-logs');
71
+ const dir = join(rootDir, fileSafeId(binding?.id));
72
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
73
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
74
+ const messageId = fileSafeId(inbound?.messageId);
75
+ const filePath = join(dir, `${stamp}-${messageId}.json`);
76
+ const payload = {
77
+ createdAt: new Date().toISOString(),
78
+ runtime: 'codex',
79
+ agentId: binding?.id || null,
80
+ conversationId: inbound?.conversationId || null,
81
+ messageId: inbound?.messageId || null,
82
+ target: inbound?.envelopeTarget || null,
83
+ action: inbound?.action || null,
84
+ sessionId: targetSessionId || null,
85
+ shouldRotate: Boolean(shouldRotate),
86
+ agentHome,
87
+ hashes: {
88
+ developerInstructionsSha256: sha256(developerInstructions),
89
+ messageSha256: sha256(message),
90
+ },
91
+ developerInstructions,
92
+ message,
93
+ input: input || null,
94
+ };
95
+ writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, { mode: 0o600 });
96
+ debugLog('codex', 'prompt.snapshot', {
97
+ agentId: binding?.id,
98
+ conversationId: inbound?.conversationId,
99
+ messageId: inbound?.messageId,
100
+ target: inbound?.envelopeTarget,
101
+ path: filePath,
102
+ developerInstructionChars: String(developerInstructions || '').length,
103
+ messageChars: String(message || '').length,
104
+ developerInstructionsSha256: payload.hashes.developerInstructionsSha256,
105
+ messageSha256: payload.hashes.messageSha256,
106
+ });
107
+ } catch (err) {
108
+ debugError('codex', 'prompt.snapshot.failed', {
109
+ agentId: binding?.id,
110
+ conversationId: inbound?.conversationId,
111
+ messageId: inbound?.messageId,
112
+ error: err?.message || String(err),
113
+ });
114
+ }
115
+ }
36
116
 
37
117
  export const codexRuntime = {
38
118
  name: 'codex',
@@ -130,7 +210,7 @@ export const codexRuntime = {
130
210
  if (!binding) return false;
131
211
  const adapter = ctx.adapter;
132
212
  const meta = binding.runtimeMeta || {};
133
- // slock-style: cwd is the per-agent home dir under ~/.ticlawk/agents/<id>/.
213
+ // cwd is the per-agent home dir under ~/.ticlawk/agents/<id>/.
134
214
  // ensureAgentHome creates it + seeds MEMORY.md if missing.
135
215
  const agentHome = ensureAgentHome(binding.id, {
136
216
  displayName: binding.display_name || binding.name || null,
@@ -143,14 +223,16 @@ export const codexRuntime = {
143
223
  ? (codexInput.find((item) => item?.type === 'text')?.text || inbound.text || '(image attached)')
144
224
  : inbound.text;
145
225
 
146
- const shouldRotate = !meta.sessionId || meta.rotatePending;
226
+ const sessionScope = resolveRuntimeSessionScope(meta, inbound);
227
+ const shouldRotate = sessionScope.shouldRotate;
228
+ const targetSessionId = shouldRotate ? null : sessionScope.sessionId;
147
229
  const deltaAggregator = createDeltaAggregator({
148
230
  flushDelta: async ({ text, sessionId, turnId, cwd }) => {
149
231
  await emitWorkerEvent({
150
232
  adapter,
151
233
  binding,
152
234
  agent: this.name,
153
- sessionId: sessionId || meta.sessionId || binding.id,
235
+ sessionId: sessionId || targetSessionId || binding.id,
154
236
  turnId: turnId || null,
155
237
  cwd: cwd || agentHome,
156
238
  replyToMessageId: inbound.messageId || null,
@@ -168,12 +250,25 @@ export const codexRuntime = {
168
250
  const runtimeCodexVersion = getCodexRuntimeHealth(runtimeCodexPath).version || meta.codexVersion || null;
169
251
  const agentEnv = buildAgentRuntimeEnv({
170
252
  agentId: binding.id,
171
- sessionId: meta.sessionId,
253
+ sessionId: targetSessionId,
172
254
  hostId: binding.runtime_host_id,
255
+ conversationId: inbound.conversationId,
256
+ messageId: inbound.messageId,
257
+ target: inbound.envelopeTarget,
258
+ });
259
+ const developerInstructions = buildStandingPrompt({ agentId: binding.id, inbound });
260
+ writePromptSnapshot({
261
+ binding,
262
+ inbound,
263
+ agentHome,
264
+ targetSessionId,
265
+ shouldRotate,
266
+ developerInstructions,
267
+ message,
268
+ input: codexInput,
173
269
  });
174
- const developerInstructions = buildStandingPrompt({ agentId: binding.id });
175
270
  const result = (shouldRotate || inbound.action === 'image' || shouldStreamRuntime(this.name, this))
176
- ? await this.runTurnStream({ sessionId: shouldRotate ? null : meta.sessionId, cwd: agentHome, codexPath: runtimeCodexPath, agentEnv }, message, {
271
+ ? await this.runTurnStream({ sessionId: targetSessionId, cwd: agentHome, codexPath: runtimeCodexPath, agentEnv }, message, {
177
272
  input: codexInput,
178
273
  developerInstructions,
179
274
  onEvent: async (event) => {
@@ -182,7 +277,7 @@ export const codexRuntime = {
182
277
  adapter,
183
278
  binding,
184
279
  agent: this.name,
185
- sessionId: event.sessionId || meta.sessionId || binding.id,
280
+ sessionId: event.sessionId || targetSessionId || binding.id,
186
281
  turnId: event.turnId || null,
187
282
  cwd: agentHome,
188
283
  replyToMessageId: inbound.messageId || null,
@@ -194,36 +289,32 @@ export const codexRuntime = {
194
289
  });
195
290
  } else if (event?.type === 'message.delta' && event.text) {
196
291
  deltaAggregator.push(event.text, {
197
- sessionId: event.sessionId || meta.sessionId || binding.id,
292
+ sessionId: event.sessionId || targetSessionId || binding.id,
198
293
  turnId: event.turnId || null,
199
294
  cwd: agentHome,
200
295
  });
201
296
  }
202
297
  },
203
298
  })
204
- : await this.runTurn({ sessionId: meta.sessionId, cwd: agentHome, codexPath: runtimeCodexPath, agentEnv }, message, {
299
+ : await this.runTurn({ sessionId: targetSessionId, cwd: agentHome, codexPath: runtimeCodexPath, agentEnv }, message, {
205
300
  input: codexInput,
206
301
  });
207
302
 
208
303
  await deltaAggregator.flush();
209
304
  const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
210
- sessionId: result?.sessionId || meta.sessionId,
211
- path: result?.path || meta.path || null,
305
+ ...buildRuntimeSessionMetaPatch(meta, sessionScope, {
306
+ sessionId: result?.sessionId,
307
+ path: result?.path,
308
+ }),
212
309
  runtimePath: runtimeCodexPath,
213
310
  codexPath: runtimeCodexPath,
214
311
  codexVersion: runtimeCodexVersion,
215
- rotatePending: false,
216
- lastRotatedAt: shouldRotate ? new Date().toISOString() : meta.lastRotatedAt,
217
312
  }, { status: 'connected' });
218
- await recordActivity(adapter, nextBinding, inbound, {
219
- ...result,
220
- media: normalizeOutboundMedia(result),
221
- });
222
313
  await emitWorkerEvent({
223
314
  adapter,
224
315
  binding: nextBinding,
225
316
  agent: this.name,
226
- sessionId: result?.sessionId || meta.sessionId || binding.id,
317
+ sessionId: result?.sessionId || targetSessionId || binding.id,
227
318
  turnId: result?.turnId || null,
228
319
  cwd: result?.cwd || agentHome,
229
320
  replyToMessageId: inbound.messageId || null,
@@ -254,7 +345,7 @@ export const codexRuntime = {
254
345
  adapter,
255
346
  binding: failureBinding,
256
347
  agent: this.name,
257
- sessionId: meta.sessionId || binding.id,
348
+ sessionId: targetSessionId || binding.id,
258
349
  turnId: failureInfo?.turnId || null,
259
350
  cwd: agentHome,
260
351
  replyToMessageId: inbound.messageId || null,
@@ -1,5 +1,5 @@
1
- import { normalizeOutboundMedia } from '../../core/media/outbound.mjs';
2
- import { reportSubprocessFailure, sendAdapterMessage, recordActivity, terminalRuntimeFailure } from '../../core/runtime-support.mjs';
1
+ import { reportSubprocessFailure, terminalRuntimeFailure } from '../../core/runtime-support.mjs';
2
+ import { getGoalTaskProtocolOverlayKey } from '../_shared/goal-task-protocol.mjs';
3
3
  import { buildStandingPrompt } from '../_shared/standing-prompt.mjs';
4
4
  import { GATEWAY_HOST, GATEWAY_PORT } from './identity.mjs';
5
5
 
@@ -25,10 +25,11 @@ async function probeOpenClawGatewayHealth() {
25
25
  import { buildOpenClawSessionKey, normalizeOpenClawAgentId } from './target.mjs';
26
26
  import { askGateway, isGatewayReady, registerOpenClawChannel } from './gateway.mjs';
27
27
 
28
- // Tracks which (agentId, sessionKey) pairs already saw the standing
29
- // prompt this process lifetime. OpenClaw's gateway holds session state
30
- // out of process, so we err on the side of "inject on first observed
31
- // turn after daemon restart"; the gateway dedupes redundant context.
28
+ // Tracks which (agentId, sessionKey, protocol overlay) combinations
29
+ // already saw the standing prompt this process lifetime. OpenClaw's
30
+ // gateway holds session state out of process, so we err on the side of
31
+ // "inject on first observed overlay after daemon restart"; the gateway
32
+ // dedupes redundant context.
32
33
  const standingPromptSeen = new Set();
33
34
  import { addInFlight, recoverInFlightEntries, removeInFlight } from './inflight.mjs';
34
35
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
@@ -160,13 +161,13 @@ export const openClawRuntime = {
160
161
  const sessionId = String(meta.sessionKey || buildOpenClawSessionKey(agentId)).trim();
161
162
 
162
163
  // Inject the standing prompt on the first observed turn after a
163
- // daemon start for this (agent, session) pair. OpenClaw has no
164
- // separate "system prompt" parameter on the gateway, so we prepend
165
- // exactly once.
166
- const standingKey = `${agentId}|${sessionId}`;
164
+ // daemon start for this (agent, session, protocol overlay). OpenClaw
165
+ // has no separate "system prompt" parameter on the gateway, so we
166
+ // prepend through the prompt body.
167
+ const standingKey = `${agentId}|${sessionId}|${getGoalTaskProtocolOverlayKey({ agentId: binding.id, inbound })}`;
167
168
  let prompt = rawPrompt;
168
169
  if (!standingPromptSeen.has(standingKey)) {
169
- const standing = buildStandingPrompt({ agentId: binding.id });
170
+ const standing = buildStandingPrompt({ agentId: binding.id, inbound });
170
171
  prompt = `${standing}\n\n---\n\n${rawPrompt}`;
171
172
  standingPromptSeen.add(standingKey);
172
173
  }
@@ -192,10 +193,6 @@ export const openClawRuntime = {
192
193
  });
193
194
  },
194
195
  });
195
- await recordActivity(adapter, binding, inbound, {
196
- ...result,
197
- media: normalizeOutboundMedia(result),
198
- });
199
196
  return true;
200
197
  } catch (err) {
201
198
  if (typeof adapter.emitEvent === 'function') {
@@ -233,18 +230,11 @@ export const openClawRuntime = {
233
230
  }
234
231
  },
235
232
 
236
- async recoverInFlight(ctx) {
233
+ async recoverInFlight() {
234
+ // Just drain the persisted in-flight set — the user-facing notice
235
+ // that used to surface here went through the dead chat-projection
236
+ // path. OpenClaw is non-primary; this no-ops cleanly for now.
237
237
  const entries = recoverInFlightEntries();
238
- for (const entry of entries) {
239
- const binding = ctx.getBinding(entry.bindingId);
240
- if (!binding) continue;
241
- await sendAdapterMessage(ctx.adapter, binding, {
242
- type: 'assistant',
243
- text: '⚠️ Lost while ticlawk restarted.\n\nThis OpenClaw message was in flight when ticlawk restarted. Please retry your message.',
244
- media: [],
245
- replyToMessageId: entry.messageId || null,
246
- }).catch(() => {});
247
- }
248
238
  return entries.length;
249
239
  },
250
240