ticlawk 0.1.16-dev.3 → 0.1.16-dev.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +14 -2
  2. package/bin/ticlawk.mjs +207 -25
  3. package/package.json +1 -1
  4. package/src/adapters/ticlawk/api.mjs +232 -23
  5. package/src/adapters/ticlawk/credentials.mjs +41 -1
  6. package/src/adapters/ticlawk/index.mjs +196 -195
  7. package/src/adapters/ticlawk/wake-client.mjs +1 -1
  8. package/src/cli/agent-commands.mjs +607 -37
  9. package/src/core/agent-cli-handlers.mjs +449 -20
  10. package/src/core/agent-home.mjs +86 -10
  11. package/src/core/argv.mjs +11 -1
  12. package/src/core/http.mjs +126 -0
  13. package/src/core/runtime-env.mjs +7 -0
  14. package/src/core/runtime-support.mjs +101 -30
  15. package/src/migrate/write-initial-memory.mjs +5 -5
  16. package/src/runtimes/_shared/agent-handbook.mjs +45 -0
  17. package/src/runtimes/_shared/brand.mjs +2 -0
  18. package/src/runtimes/_shared/goal-task-protocol.mjs +50 -0
  19. package/src/runtimes/_shared/handbook/BASICS.md +27 -0
  20. package/src/runtimes/_shared/handbook/COLLABORATION.md +37 -0
  21. package/src/runtimes/_shared/handbook/COMMUNICATION.md +48 -0
  22. package/src/runtimes/_shared/handbook/DM_SCOPE.md +13 -0
  23. package/src/runtimes/_shared/handbook/GOAL_AUTHORITY.md +43 -0
  24. package/src/runtimes/_shared/handbook/GOAL_TASK_CORE.md +43 -0
  25. package/src/runtimes/_shared/handbook/GROUP_ADMIN_SCOPE.md +21 -0
  26. package/src/runtimes/_shared/handbook/GROUP_MEMBER_SCOPE.md +15 -0
  27. package/src/runtimes/_shared/handbook/SURFACES.md +41 -0
  28. package/src/runtimes/_shared/handbook/TASK_WORKER.md +14 -0
  29. package/src/runtimes/_shared/standing-prompt.mjs +111 -264
  30. package/src/runtimes/_shared/wake-prompt.mjs +261 -0
  31. package/src/runtimes/claude-code/index.mjs +30 -108
  32. package/src/runtimes/codex/index.mjs +114 -23
  33. package/src/runtimes/openclaw/index.mjs +16 -26
  34. package/src/runtimes/opencode/index.mjs +42 -36
  35. package/src/runtimes/opencode/session.mjs +5 -4
  36. package/src/runtimes/pi/index.mjs +39 -31
  37. package/src/runtimes/pi/session.mjs +5 -2
  38. package/src/adapters/ticlawk/cards.mjs +0 -149
  39. package/src/core/media/outbound.mjs +0 -163
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Per-turn inbound wake prompt builder.
3
+ *
4
+ * Standing prompts define durable runtime behavior. This module owns the
5
+ * dynamic wrapper around each delivered Ticlawk message: who sent it, why it
6
+ * reached this agent, the exact reply target, and any attached goal/task/quote
7
+ * context for this turn.
8
+ */
9
+
10
+ function promptBlock(text) {
11
+ return text.trim();
12
+ }
13
+
14
+ export function buildEnvelopeTarget(msg) {
15
+ const convType = msg.conversation_type || 'dm';
16
+ const conversationId = msg.conversation_id || '';
17
+ const senderHandle = msg.sender_display_name || msg.sender_user_id || msg.sender_agent_id || '';
18
+ if (convType === 'dm') {
19
+ return senderHandle ? `dm:@${senderHandle}` : `dm:${conversationId}`;
20
+ }
21
+ if (convType === 'thread') {
22
+ const groupName = msg.conversation_name || conversationId;
23
+ const replyRoot = msg.thread_root_message_id || msg.message_id || '';
24
+ return `#${groupName}:${replyRoot}`;
25
+ }
26
+ // group
27
+ const groupName = msg.conversation_name || conversationId;
28
+ return `#${groupName}`;
29
+ }
30
+
31
+ export function buildDebugTaskSuffix(msg) {
32
+ if (msg.task_number == null) return '';
33
+ const status = msg.task_status || 'todo';
34
+ const parts = [`task #${msg.task_number} status=${status}`];
35
+ if (msg.task_assignee_agent_id || msg.task_assignee_user_id) {
36
+ const t = msg.task_assignee_type || 'agent';
37
+ const id = msg.task_assignee_agent_id || msg.task_assignee_user_id;
38
+ parts.push(`assignee=${t}:${id}`);
39
+ }
40
+ if (msg.task_title) {
41
+ parts.push(`title=${JSON.stringify(msg.task_title)}`);
42
+ }
43
+ return ` [${parts.join(' ')}]`;
44
+ }
45
+
46
+ export function buildDebugReactionsSuffix(msg) {
47
+ const entries = Array.isArray(msg.reactions_summary) ? msg.reactions_summary : [];
48
+ if (entries.length === 0) return '';
49
+ return ` [reactions: ${entries.join('; ')}]`;
50
+ }
51
+
52
+ function normalizeDeliveryReasonForPrompt(reason) {
53
+ return reason === 'thread_follow' ? 'reply_follow' : reason;
54
+ }
55
+
56
+ export function buildDebugEnvelopeHeader(msg) {
57
+ const target = buildEnvelopeTarget(msg);
58
+ const msgId = msg.id || msg.message_id || '';
59
+ const seq = msg.seq != null ? msg.seq : '';
60
+ const time = msg.created_at || new Date().toISOString();
61
+ const type = msg.sender_type || 'human';
62
+ const sender = msg.sender_display_name || msg.sender_user_id || msg.sender_agent_id || 'unknown';
63
+ const convType = msg.conversation_type || 'dm';
64
+ const displayReason = normalizeDeliveryReasonForPrompt(msg.reason || '');
65
+ const reason = displayReason ? ` reason=${displayReason}` : '';
66
+ const recipientRole = String(msg.recipient_conversation_role || msg.recipient_role || '').trim().toLowerCase();
67
+ const role = convType === 'group' && recipientRole ? ` role=${recipientRole}` : '';
68
+ return `[target=${target} msg=${msgId} seq=${seq} time=${time} type=${type}${reason}${role}] @${sender}:`;
69
+ }
70
+
71
+ export const buildEnvelopeHeader = buildDebugEnvelopeHeader;
72
+
73
+ export function buildGroupContextBlock(msg) {
74
+ // Only useful in groups — DMs don't need group-purpose context.
75
+ if ((msg.conversation_type || 'dm') !== 'group') return '';
76
+ const lines = [];
77
+ const name = msg.conversation_name || msg.conversation_display_name || '';
78
+ if (name) lines.push(`Group: #${name}`);
79
+ const description = (msg.conversation_description || '').trim();
80
+ if (description) lines.push(`Purpose: ${description}`);
81
+ if (lines.length === 0) return '';
82
+ return lines.join('\n');
83
+ }
84
+
85
+ export function buildCharterBlock(msg) {
86
+ const charter = (msg.conversation_charter || '').trim();
87
+ if (!charter) return '';
88
+ const authorityLine = hasGoalAuthority(msg)
89
+ ? 'Use it as the current goal and role spec. If the owner appears to change the goal, read GOAL_AUTHORITY.md and follow the goal-change flow before updating state.'
90
+ : 'Use it as current group goal and role context. The group admin owns charter and dashboard changes unless they explicitly delegate them.';
91
+ return promptBlock(`
92
+ [conversation_goal]
93
+ Current charter for this conversation:
94
+ ${charter}
95
+
96
+ ${authorityLine}
97
+ [/conversation_goal]
98
+ `);
99
+ }
100
+
101
+ export function buildGoalStateBlock(msg) {
102
+ if ((msg.conversation_charter || '').trim()) return '';
103
+ const convType = msg.conversation_type || 'dm';
104
+ const subject = convType === 'group' ? 'This group' : 'This conversation';
105
+ const authorityLine = hasGoalAuthority(msg)
106
+ ? 'If this message may be starting, clarifying, or changing an ongoing goal, read GOAL_AUTHORITY.md "Goal Setup When No Specific Goal Exists" and follow it to propose/confirm the goal before writing charter/dashboard state. If it is clearly one-off, answer normally.'
107
+ : 'The group admin owns goal setup; follow direct mentions or assigned tasks normally.';
108
+ return promptBlock(`
109
+ [conversation_goal]
110
+ ${subject} does not have a chartered goal yet.
111
+ ${authorityLine}
112
+ [/conversation_goal]
113
+ `);
114
+ }
115
+
116
+ function hasGoalAuthority(msg) {
117
+ const convType = msg.conversation_type || 'dm';
118
+ if (convType !== 'group') return true;
119
+ if (msg.recipient_is_conversation_admin === true) return true;
120
+ const recipientRole = String(msg.recipient_conversation_role || msg.recipient_role || '').trim().toLowerCase();
121
+ return recipientRole === 'admin' || recipientRole === 'owner';
122
+ }
123
+
124
+ export function buildQuoteBlock(msg, target = '') {
125
+ const meta = msg.message_metadata || msg.metadata || null;
126
+ const quote = meta && typeof meta === 'object' ? meta.quote : null;
127
+ if (!quote || typeof quote !== 'object') return '';
128
+ const kind = String(quote.kind || '').trim();
129
+ const ref = String(quote.ref || '').trim();
130
+ const snippet = String(quote.snippet || '').trim();
131
+ if (!kind || !ref) return '';
132
+ const fetchHint = kind === 'briefing'
133
+ ? `ticlawk briefing get ${ref}`
134
+ : kind === 'dashboard'
135
+ ? `ticlawk dashboard get --conversation-id ${ref}`
136
+ : kind === 'message'
137
+ ? `ticlawk message read --target ${JSON.stringify(target)} --around ${ref}`
138
+ : '';
139
+ const lines = [
140
+ `The incoming message is a reply to a ${kind}.`,
141
+ `Referenced ${kind}: \`${ref}\``,
142
+ ];
143
+ if (snippet) lines.push(`Quoted snippet: ${snippet}`);
144
+ if (fetchHint) lines.push(`Fetch it if needed: \`${fetchHint}\``);
145
+ return lines.join('\n');
146
+ }
147
+
148
+ function senderDescription(msg) {
149
+ const type = msg.sender_type === 'agent' ? 'an agent' : 'a human user';
150
+ const sender = msg.sender_display_name || msg.sender_user_id || msg.sender_agent_id || '';
151
+ return sender ? `@${sender}, ${type}` : type;
152
+ }
153
+
154
+ function senderFactDescription(msg) {
155
+ const type = msg.sender_type === 'agent' ? 'agent' : 'human user';
156
+ const sender = msg.sender_display_name || msg.sender_user_id || msg.sender_agent_id || '';
157
+ return sender ? `@${sender}, ${type}` : type;
158
+ }
159
+
160
+ function conversationLabel(msg, target) {
161
+ const convType = msg.conversation_type || 'dm';
162
+ if (convType === 'dm') return 'a one-on-one conversation';
163
+ return target || 'this group';
164
+ }
165
+
166
+ function buildMessageSummary(msg, target) {
167
+ const convType = msg.conversation_type || 'dm';
168
+ const reason = normalizeDeliveryReasonForPrompt(msg.reason || '');
169
+ const sender = senderDescription(msg);
170
+ const senderFact = senderFactDescription(msg);
171
+ const where = conversationLabel(msg, target);
172
+
173
+ if (convType === 'dm') {
174
+ return `This is a one-on-one message from ${sender}.`;
175
+ }
176
+ if (reason === 'assignment') {
177
+ return `This message assigns or routes work to you in ${where}.\nSender: ${senderFact}`;
178
+ }
179
+ if (reason === 'ambient') {
180
+ return `Sender: ${senderFact}`;
181
+ }
182
+ if (reason === 'mention') {
183
+ return `You were mentioned in ${where} by ${sender}.`;
184
+ }
185
+ if (reason === 'reply_follow') {
186
+ return `This is a reply in ${where} from ${sender}.`;
187
+ }
188
+ if (reason === 'manual') {
189
+ return `This is a manual wake-up or reminder for ${where}.\nSource: ${senderFact}`;
190
+ }
191
+ return `This is a message in ${where} from ${sender}.`;
192
+ }
193
+
194
+ function buildTaskDetailsBlock(msg) {
195
+ if (msg.task_number == null) return '';
196
+ const lines = [`Task #${msg.task_number} is currently \`${msg.task_status || 'todo'}\`.`];
197
+ if (msg.task_title) lines.push(`Task title: ${msg.task_title}`);
198
+ if (msg.task_assignee_agent_id || msg.task_assignee_user_id) {
199
+ const t = msg.task_assignee_type || 'agent';
200
+ const id = msg.task_assignee_agent_id || msg.task_assignee_user_id;
201
+ lines.push(`Assignee: ${t} \`${id}\``);
202
+ }
203
+ return lines.join('\n');
204
+ }
205
+
206
+ function buildReactionsBlock(msg) {
207
+ const entries = Array.isArray(msg.reactions_summary) ? msg.reactions_summary : [];
208
+ if (entries.length === 0) return '';
209
+ return `Recent reactions: ${entries.join('; ')}`;
210
+ }
211
+
212
+ function buildContentBlock(rawText) {
213
+ const text = String(rawText || '').trim();
214
+ if (!text) return '';
215
+ return promptBlock(`
216
+ Content:
217
+ ${text}
218
+ `);
219
+ }
220
+
221
+ // Wrap each per-turn message with the concrete dynamic context for this
222
+ // delivery. Durable communication rules live in COMMUNICATION.md; this block
223
+ // only carries the current message and its exact reply target.
224
+ export function buildWakePromptText({ messageSummary, target, rawText, groupContext, charterBlock, goalStateBlock, quoteBlock, taskDetails, reactionsBlock }) {
225
+ const contextPrefix = [charterBlock, goalStateBlock, quoteBlock].filter(Boolean).join('\n\n');
226
+ const prefix = contextPrefix ? `${contextPrefix}\n\n` : '';
227
+ const detailBlocks = [
228
+ messageSummary,
229
+ `Reply target: \`${target}\``,
230
+ groupContext,
231
+ taskDetails ? `Task details:\n${taskDetails}` : '',
232
+ reactionsBlock,
233
+ buildContentBlock(rawText),
234
+ ].filter(Boolean).join('\n\n');
235
+ return promptBlock(`
236
+ ${prefix}New message received:
237
+
238
+ ${detailBlocks}
239
+ `);
240
+ }
241
+
242
+ export function buildInboundWakePrompt(msg) {
243
+ const rawText = msg.text || '';
244
+ const baseHeader = buildDebugEnvelopeHeader(msg);
245
+ const header = baseHeader + buildDebugTaskSuffix(msg) + buildDebugReactionsSuffix(msg);
246
+ const target = buildEnvelopeTarget(msg);
247
+ const groupContext = buildGroupContextBlock(msg);
248
+ const charterBlock = buildCharterBlock(msg);
249
+ const goalStateBlock = buildGoalStateBlock(msg);
250
+ const quoteBlock = buildQuoteBlock(msg, target);
251
+ const messageSummary = buildMessageSummary(msg, target);
252
+ const taskDetails = buildTaskDetailsBlock(msg);
253
+ const reactionsBlock = buildReactionsBlock(msg);
254
+ const text = buildWakePromptText({ messageSummary, target, rawText, groupContext, charterBlock, goalStateBlock, quoteBlock, taskDetails, reactionsBlock });
255
+ return {
256
+ header,
257
+ target,
258
+ text,
259
+ rawText,
260
+ };
261
+ }
@@ -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: {