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.
- package/README.md +14 -2
- package/bin/ticlawk.mjs +207 -25
- package/package.json +1 -1
- package/src/adapters/ticlawk/api.mjs +232 -23
- package/src/adapters/ticlawk/credentials.mjs +41 -1
- package/src/adapters/ticlawk/index.mjs +196 -195
- package/src/adapters/ticlawk/wake-client.mjs +1 -1
- package/src/cli/agent-commands.mjs +607 -37
- package/src/core/agent-cli-handlers.mjs +449 -20
- package/src/core/agent-home.mjs +86 -10
- package/src/core/argv.mjs +11 -1
- package/src/core/http.mjs +126 -0
- package/src/core/runtime-env.mjs +7 -0
- package/src/core/runtime-support.mjs +101 -30
- package/src/migrate/write-initial-memory.mjs +5 -5
- package/src/runtimes/_shared/agent-handbook.mjs +45 -0
- package/src/runtimes/_shared/brand.mjs +2 -0
- package/src/runtimes/_shared/goal-task-protocol.mjs +50 -0
- package/src/runtimes/_shared/handbook/BASICS.md +27 -0
- package/src/runtimes/_shared/handbook/COLLABORATION.md +37 -0
- package/src/runtimes/_shared/handbook/COMMUNICATION.md +48 -0
- package/src/runtimes/_shared/handbook/DM_SCOPE.md +13 -0
- package/src/runtimes/_shared/handbook/GOAL_AUTHORITY.md +43 -0
- package/src/runtimes/_shared/handbook/GOAL_TASK_CORE.md +43 -0
- package/src/runtimes/_shared/handbook/GROUP_ADMIN_SCOPE.md +21 -0
- package/src/runtimes/_shared/handbook/GROUP_MEMBER_SCOPE.md +15 -0
- package/src/runtimes/_shared/handbook/SURFACES.md +41 -0
- package/src/runtimes/_shared/handbook/TASK_WORKER.md +14 -0
- package/src/runtimes/_shared/standing-prompt.mjs +111 -264
- package/src/runtimes/_shared/wake-prompt.mjs +261 -0
- package/src/runtimes/claude-code/index.mjs +30 -108
- package/src/runtimes/codex/index.mjs +114 -23
- package/src/runtimes/openclaw/index.mjs +16 -26
- package/src/runtimes/opencode/index.mjs +42 -36
- package/src/runtimes/opencode/session.mjs +5 -4
- package/src/runtimes/pi/index.mjs +39 -31
- package/src/runtimes/pi/session.mjs +5 -2
- package/src/adapters/ticlawk/cards.mjs +0 -149
- 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
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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 ||
|
|
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 ||
|
|
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
|
-
|
|
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 ||
|
|
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: {
|