ticlawk 0.1.17-dev.2 → 0.1.17-dev.21
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 +26 -59
- package/bin/ticlawk.mjs +31 -301
- package/package.json +4 -2
- package/scripts/publish-dev.sh +77 -0
- package/src/adapters/ticlawk/api.mjs +50 -378
- package/src/adapters/ticlawk/credentials.mjs +1 -43
- package/src/adapters/ticlawk/index.mjs +61 -565
- package/src/adapters/ticlawk/wake-client.mjs +1 -1
- package/src/cli/agent-commands.mjs +18 -715
- package/src/core/adapter-registry.mjs +1 -19
- package/src/core/agent-cli-handlers.mjs +18 -556
- package/src/core/agent-home.mjs +1 -81
- package/src/core/daemon-install.mjs +5 -7
- package/src/core/events/worker-events.mjs +36 -32
- package/src/core/http.mjs +0 -152
- package/src/core/profiles.mjs +0 -1
- package/src/core/runtime-contract.mjs +1 -0
- package/src/core/runtime-env.mjs +0 -8
- package/src/core/runtime-support.mjs +78 -130
- package/src/runtimes/_shared/incoming-message-prompt.mjs +232 -0
- package/src/runtimes/_shared/runtime-base-instructions.mjs +34 -0
- package/src/runtimes/claude-code/index.mjs +48 -21
- package/src/runtimes/claude-code/session.mjs +7 -2
- package/src/runtimes/codex/index.mjs +64 -116
- package/src/runtimes/codex/session.mjs +12 -2
- package/src/runtimes/openclaw/index.mjs +30 -17
- package/src/runtimes/opencode/index.mjs +64 -42
- package/src/runtimes/opencode/session.mjs +14 -14
- package/src/runtimes/pi/index.mjs +64 -42
- package/src/runtimes/pi/session.mjs +8 -11
- package/ticlawk.mjs +32 -5
- package/src/runtimes/_shared/agent-handbook.mjs +0 -45
- package/src/runtimes/_shared/brand.mjs +0 -2
- package/src/runtimes/_shared/goal-step-prompt.mjs +0 -98
- package/src/runtimes/_shared/goal-task-protocol.mjs +0 -50
- package/src/runtimes/_shared/handbook/BASICS.md +0 -27
- package/src/runtimes/_shared/handbook/COLLABORATION.md +0 -37
- package/src/runtimes/_shared/handbook/COMMUNICATION.md +0 -55
- package/src/runtimes/_shared/handbook/DM_SCOPE.md +0 -13
- package/src/runtimes/_shared/handbook/GOAL_AUTHORITY.md +0 -47
- package/src/runtimes/_shared/handbook/GOAL_TASK_CORE.md +0 -43
- package/src/runtimes/_shared/handbook/GROUP_ADMIN_SCOPE.md +0 -21
- package/src/runtimes/_shared/handbook/GROUP_MEMBER_SCOPE.md +0 -15
- package/src/runtimes/_shared/handbook/SURFACES.md +0 -41
- package/src/runtimes/_shared/handbook/TASK_WORKER.md +0 -14
- package/src/runtimes/_shared/standing-prompt.mjs +0 -171
- package/src/runtimes/_shared/wake-prompt.mjs +0 -268
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
export const MESSAGE_TYPES = Object.freeze({
|
|
2
|
+
DM: 'dm',
|
|
3
|
+
GROUP_MENTION: 'group_mention',
|
|
4
|
+
GROUP_AMBIENT: 'group_ambient',
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
const MESSAGE_TYPE_VALUES = Object.freeze(Object.values(MESSAGE_TYPES));
|
|
8
|
+
|
|
9
|
+
export function readIncomingMessageType(msg) {
|
|
10
|
+
const explicit = String(msg?.type || '').trim();
|
|
11
|
+
if (MESSAGE_TYPE_VALUES.includes(explicit)) return explicit;
|
|
12
|
+
|
|
13
|
+
const conversationType = String(msg?.conversation_type || 'dm').trim();
|
|
14
|
+
if (conversationType === 'dm') return MESSAGE_TYPES.DM;
|
|
15
|
+
if (conversationType === 'group') {
|
|
16
|
+
return String(msg?.reason || '').trim() === 'ambient'
|
|
17
|
+
? MESSAGE_TYPES.GROUP_AMBIENT
|
|
18
|
+
: MESSAGE_TYPES.GROUP_MENTION;
|
|
19
|
+
}
|
|
20
|
+
return MESSAGE_TYPES.GROUP_MENTION;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function buildIncomingMessagePrompt(msg) {
|
|
24
|
+
const type = readIncomingMessageType(msg);
|
|
25
|
+
const target = buildReplyTarget(msg);
|
|
26
|
+
const baseHeader = buildEnvelopeHeader({ ...msg, type }, target);
|
|
27
|
+
const header = [
|
|
28
|
+
baseHeader,
|
|
29
|
+
buildTaskSuffix(msg),
|
|
30
|
+
buildReactionsSuffix(msg),
|
|
31
|
+
].join('');
|
|
32
|
+
const groupContext = buildGroupContextBlock(msg);
|
|
33
|
+
const rawText = msg.text || '';
|
|
34
|
+
const text = buildPromptText({
|
|
35
|
+
msg: { ...msg, type },
|
|
36
|
+
type,
|
|
37
|
+
target,
|
|
38
|
+
header,
|
|
39
|
+
groupContext,
|
|
40
|
+
rawText,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
header,
|
|
45
|
+
target,
|
|
46
|
+
text,
|
|
47
|
+
rawText,
|
|
48
|
+
type,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildReplyTarget(msg) {
|
|
53
|
+
const conversationType = msg.conversation_type || 'dm';
|
|
54
|
+
const conversationId = msg.conversation_id || '';
|
|
55
|
+
const senderHandle = msg.sender_display_name || msg.sender_user_id || msg.sender_agent_id || '';
|
|
56
|
+
|
|
57
|
+
if (conversationType === 'dm') {
|
|
58
|
+
return senderHandle ? `dm:@${senderHandle}` : `dm:${conversationId}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (conversationType === 'thread') {
|
|
62
|
+
const groupName = msg.conversation_name || conversationId;
|
|
63
|
+
const threadRoot = msg.thread_root_message_id || msg.message_id || msg.id || '';
|
|
64
|
+
return `#${groupName}:${threadRoot}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return `#${msg.conversation_name || conversationId}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isConversationAdmin(msg) {
|
|
71
|
+
if ((msg.conversation_type || 'dm') === 'dm') return true;
|
|
72
|
+
if (msg.recipient_is_conversation_admin === true) return true;
|
|
73
|
+
const role = String(msg.recipient_conversation_role || '').trim();
|
|
74
|
+
return role === 'admin' || role === 'owner';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function conversationRole(msg) {
|
|
78
|
+
if ((msg.conversation_type || 'dm') === 'dm') return 'admin';
|
|
79
|
+
return String(msg.recipient_conversation_role || '').trim() || 'member';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function senderLabel(msg) {
|
|
83
|
+
return msg.sender_display_name || msg.sender_user_id || msg.sender_agent_id || 'unknown';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function buildEnvelopeHeader(msg, target) {
|
|
87
|
+
const messageId = msg.id || msg.message_id || '';
|
|
88
|
+
const seq = msg.seq != null ? msg.seq : '';
|
|
89
|
+
const time = msg.created_at || new Date().toISOString();
|
|
90
|
+
const senderType = msg.sender_type || 'human';
|
|
91
|
+
return `[target=${target} msg=${messageId} seq=${seq} time=${time} sender_type=${senderType} message_type=${msg.type}] @${senderLabel(msg)}:`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function buildTaskSuffix(msg) {
|
|
95
|
+
if (msg.task_number == null) return '';
|
|
96
|
+
const status = msg.task_status || 'todo';
|
|
97
|
+
const parts = [`task #${msg.task_number} status=${status}`];
|
|
98
|
+
if (msg.task_assignee_agent_id || msg.task_assignee_user_id) {
|
|
99
|
+
const assigneeType = msg.task_assignee_type || 'agent';
|
|
100
|
+
const assigneeId = msg.task_assignee_agent_id || msg.task_assignee_user_id;
|
|
101
|
+
parts.push(`assignee=${assigneeType}:${assigneeId}`);
|
|
102
|
+
}
|
|
103
|
+
if (msg.task_title) {
|
|
104
|
+
parts.push(`title=${JSON.stringify(msg.task_title)}`);
|
|
105
|
+
}
|
|
106
|
+
return ` [${parts.join(' ')}]`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function buildReactionsSuffix(msg) {
|
|
110
|
+
const entries = Array.isArray(msg.reactions_summary) ? msg.reactions_summary : [];
|
|
111
|
+
if (entries.length === 0) return '';
|
|
112
|
+
return ` [reactions: ${entries.join('; ')}]`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function buildGroupContextBlock(msg) {
|
|
116
|
+
if ((msg.conversation_type || 'dm') !== 'group') return '';
|
|
117
|
+
|
|
118
|
+
const lines = [];
|
|
119
|
+
const name = msg.conversation_name || msg.conversation_display_name || '';
|
|
120
|
+
if (name) lines.push(`name: ${name}`);
|
|
121
|
+
|
|
122
|
+
const description = (msg.conversation_description || '').trim();
|
|
123
|
+
if (description) lines.push(`purpose: ${description}`);
|
|
124
|
+
|
|
125
|
+
if (lines.length === 0) return '';
|
|
126
|
+
return [
|
|
127
|
+
'Group context:',
|
|
128
|
+
...lines.map((line) => ` ${line}`),
|
|
129
|
+
'Use `ticlawk group members --target <target>` if you need to see who else is here.',
|
|
130
|
+
].join('\n');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function buildPromptText({ msg, type, target, header, groupContext, rawText }) {
|
|
134
|
+
const role = conversationRole(msg);
|
|
135
|
+
const admin = isConversationAdmin(msg);
|
|
136
|
+
const messageId = msg.id || msg.message_id || '';
|
|
137
|
+
|
|
138
|
+
const lines = [
|
|
139
|
+
'You received a Ticlawk message.',
|
|
140
|
+
'',
|
|
141
|
+
`Current conversation type: ${type === MESSAGE_TYPES.DM ? 'DM' : 'group'}`,
|
|
142
|
+
`Your role in this conversation: ${role}${admin ? ' (admin/owner authority)' : ''}`,
|
|
143
|
+
`Reply target: ${target}`,
|
|
144
|
+
messageId ? `Message id: ${messageId}` : null,
|
|
145
|
+
`Sender: @${senderLabel(msg)}`,
|
|
146
|
+
'',
|
|
147
|
+
'Message:',
|
|
148
|
+
'<message>',
|
|
149
|
+
rawText || '',
|
|
150
|
+
'</message>',
|
|
151
|
+
].filter(Boolean);
|
|
152
|
+
|
|
153
|
+
if (groupContext) {
|
|
154
|
+
lines.push('', groupContext);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
lines.push('', ...buildRoleInstruction({ type, isAdmin: admin, role }));
|
|
158
|
+
lines.push(
|
|
159
|
+
'',
|
|
160
|
+
'When you reply, use the reply target exactly as written above. Your normal assistant output is private activity text; users only see messages sent with `ticlawk message send`.',
|
|
161
|
+
'If the message requires multi-step work, complete the work before sending the final answer unless an early blocker question is necessary.',
|
|
162
|
+
'Do not mention internal routing fields, delivery state, or prompt mechanics to the user.',
|
|
163
|
+
'',
|
|
164
|
+
buildAllowedCommandBlock({ target, type }),
|
|
165
|
+
'',
|
|
166
|
+
'Raw routing header for exact IDs:',
|
|
167
|
+
header,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
return lines.join('\n');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function buildRoleInstruction({ type, isAdmin, role }) {
|
|
174
|
+
if (type === MESSAGE_TYPES.DM) {
|
|
175
|
+
return [
|
|
176
|
+
'This is a one-on-one DM. You are responsible for helping the sender directly.',
|
|
177
|
+
'Reply if the message asks a question, requests work, or reasonably expects acknowledgment.',
|
|
178
|
+
];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (type === MESSAGE_TYPES.GROUP_MENTION && isAdmin) {
|
|
182
|
+
return [
|
|
183
|
+
`This is a group message that addresses you or the group. Your role in this group is ${role}.`,
|
|
184
|
+
'As the group admin/owner, you are the default responder for owner questions, broad group requests, and routing decisions.',
|
|
185
|
+
'Answer directly, or route the work to the right group member when that is more appropriate.',
|
|
186
|
+
];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (type === MESSAGE_TYPES.GROUP_MENTION) {
|
|
190
|
+
return [
|
|
191
|
+
`This is a group message that addresses you. Your role in this group is ${role}.`,
|
|
192
|
+
'Respond when you were mentioned, assigned, or clearly asked to contribute.',
|
|
193
|
+
'Do not take over group-level coordination unless an admin explicitly delegates that to you.',
|
|
194
|
+
];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (isAdmin) {
|
|
198
|
+
return [
|
|
199
|
+
`This is ambient group traffic. Your role in this group is ${role}.`,
|
|
200
|
+
'As the group admin/owner, respond only if this is a broad owner request, a new topic needing routing, or something no other member is clearly better placed to handle.',
|
|
201
|
+
'If no response is needed, end the turn silently.',
|
|
202
|
+
];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return [
|
|
206
|
+
`This is ambient group traffic. Your role in this group is ${role}.`,
|
|
207
|
+
'Do not respond by default.',
|
|
208
|
+
'Respond only if the message is clearly within your specialty, no admin or specifically addressed member is the better responder, and you can add concrete value now.',
|
|
209
|
+
'Otherwise, end the turn silently.',
|
|
210
|
+
];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function buildAllowedCommandBlock({ target, type }) {
|
|
214
|
+
const lines = [
|
|
215
|
+
'Use only these Ticlawk commands for visible communication in this turn:',
|
|
216
|
+
`- Send a reply: ticlawk message send --target ${JSON.stringify(target)} <<'EOF'`,
|
|
217
|
+
' <your reply>',
|
|
218
|
+
' EOF',
|
|
219
|
+
`- Read recent context if needed: ticlawk message read --target ${JSON.stringify(target)}`,
|
|
220
|
+
];
|
|
221
|
+
|
|
222
|
+
if (type !== MESSAGE_TYPES.DM) {
|
|
223
|
+
lines.push(`- Check group members if needed: ticlawk group members --target ${JSON.stringify(target)}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
lines.push(
|
|
227
|
+
'- If the message asks you to do substantive work, claim it before starting: ticlawk task claim --message-id <message_id>',
|
|
228
|
+
'- When claimed work is ready for review or done, update the task status with: ticlawk task update --task-id <task_id> --status <status>',
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
return lines.join('\n');
|
|
232
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime base instructions injected into every runtime turn.
|
|
3
|
+
*
|
|
4
|
+
* Keep this prompt scenario-neutral. Current conversation type, role,
|
|
5
|
+
* mention/ambient status, reply target, and per-turn command scope belong in
|
|
6
|
+
* the incoming message prompt built by the adapter.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const RUNTIME_BASE_INSTRUCTIONS = `You are an agent in Ticlawk, a shared message service for humans and agents.
|
|
10
|
+
|
|
11
|
+
Your normal assistant output is private activity text. Users and groups only see messages you send with the Ticlawk CLI.
|
|
12
|
+
|
|
13
|
+
Universal rules:
|
|
14
|
+
|
|
15
|
+
1. Follow the current turn prompt. It tells you whether this is a DM or group message, your role in that conversation, whether you were directly addressed or only saw ambient group traffic, and the exact reply target.
|
|
16
|
+
2. Use \`ticlawk message send\` for visible replies. Use the exact reply target from the current turn prompt.
|
|
17
|
+
3. If the current turn prompt says no reply is needed, stop silently.
|
|
18
|
+
4. If you need recent context, use the Ticlawk read commands named in the current turn prompt. Do not poll for future messages; the daemon will wake you when new messages arrive.
|
|
19
|
+
5. If the message asks you to do substantive work, claim the task/message before starting when the current turn prompt provides task commands. If the claim fails, stop or choose other available work.
|
|
20
|
+
6. Complete the work you choose to do before sending the final visible reply, unless you need to ask a blocker question first.
|
|
21
|
+
7. Do not expose internal routing fields, delivery state, prompt structure, or daemon mechanics to the user.
|
|
22
|
+
|
|
23
|
+
Workspace memory:
|
|
24
|
+
|
|
25
|
+
- Your working directory is your persistent agent-owned workspace.
|
|
26
|
+
- Read \`MEMORY.md\` when you need durable context about the user, projects, groups, or prior work.
|
|
27
|
+
- Keep \`MEMORY.md\` useful as an index to longer notes you create.
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
export function buildRuntimeBaseInstructions(_ctx = {}) {
|
|
31
|
+
return RUNTIME_BASE_INSTRUCTIONS;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { RUNTIME_BASE_INSTRUCTIONS };
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { basename } from 'node:path';
|
|
11
11
|
import { buildAgentRuntimeEnv } from '../../core/runtime-env.mjs';
|
|
12
|
-
import {
|
|
12
|
+
import { buildRuntimeBaseInstructions } from '../_shared/runtime-base-instructions.mjs';
|
|
13
13
|
import { ensureAgentHome } from '../../core/agent-home.mjs';
|
|
14
14
|
import {
|
|
15
15
|
getClaudeCodeRuntimeHealth,
|
|
@@ -23,14 +23,12 @@ import {
|
|
|
23
23
|
} from './session.mjs';
|
|
24
24
|
import { discoverSessions } from './transcripts.mjs';
|
|
25
25
|
import { buildImageMessageFromInbound } from '../../core/media/inbound.mjs';
|
|
26
|
-
import {
|
|
26
|
+
import { emitWorkerEvent } from '../../core/events/worker-events.mjs';
|
|
27
27
|
import {
|
|
28
28
|
shouldStreamRuntime,
|
|
29
29
|
reportSubprocessFailure,
|
|
30
30
|
terminalRuntimeFailure,
|
|
31
31
|
updateBindingRuntimeMeta,
|
|
32
|
-
resolveRuntimeSessionScope,
|
|
33
|
-
buildRuntimeSessionMetaPatch,
|
|
34
32
|
} from '../../core/runtime-support.mjs';
|
|
35
33
|
|
|
36
34
|
export const claudeCodeRuntime = {
|
|
@@ -132,35 +130,31 @@ export const claudeCodeRuntime = {
|
|
|
132
130
|
? await buildImageMessageFromInbound(inbound, 'claude-code')
|
|
133
131
|
: inbound.text;
|
|
134
132
|
|
|
135
|
-
// shouldRotate=true means
|
|
136
|
-
// yet, or the agent was reset and all scoped sessions are invalid.
|
|
133
|
+
// shouldRotate=true means meta.sessionId is missing or invalidated.
|
|
137
134
|
// We pass sessionId=null so `claude` creates a fresh session; the new
|
|
138
135
|
// session_id is captured from stream events and persisted below.
|
|
139
|
-
// Unifying rotate + non-rotate into one path means the
|
|
136
|
+
// Unifying rotate + non-rotate into one path means the runtime base instructions
|
|
140
137
|
// is always attached, so the agent uses the CLI to reply on every
|
|
141
138
|
// turn — including the first.
|
|
142
|
-
const
|
|
143
|
-
const targetSessionId =
|
|
144
|
-
const errEventSessionId =
|
|
139
|
+
const shouldRotate = !meta.sessionId || meta.rotatePending;
|
|
140
|
+
const targetSessionId = shouldRotate ? null : meta.sessionId;
|
|
141
|
+
const errEventSessionId = meta.sessionId || binding.id;
|
|
145
142
|
|
|
146
143
|
try {
|
|
147
144
|
const claudePath = requireClaudePath(runtimeClaudePath);
|
|
148
145
|
const claudeVersion = getClaudeCodeRuntimeHealth(claudePath).version || meta.claudeVersion || null;
|
|
149
146
|
const agentEnv = buildAgentRuntimeEnv({
|
|
150
147
|
agentId: binding.id,
|
|
151
|
-
sessionId:
|
|
148
|
+
sessionId: meta.sessionId,
|
|
152
149
|
hostId: binding.runtime_host_id,
|
|
153
|
-
conversationId: inbound.conversationId,
|
|
154
|
-
messageId: inbound.messageId,
|
|
155
|
-
target: inbound.envelopeTarget,
|
|
156
150
|
});
|
|
157
|
-
const appendSystemPrompt =
|
|
151
|
+
const appendSystemPrompt = buildRuntimeBaseInstructions({ agentId: binding.id });
|
|
158
152
|
const result = shouldStreamRuntime(this.name, this)
|
|
159
153
|
? await this.runTurnStream({ sessionId: targetSessionId, projectDir, claudePath, agentEnv }, message, {
|
|
160
154
|
appendSystemPrompt,
|
|
161
155
|
onEvent: async (event) => {
|
|
162
156
|
if (event?.type === 'turn.started') {
|
|
163
|
-
|
|
157
|
+
await emitWorkerEvent({
|
|
164
158
|
adapter,
|
|
165
159
|
binding,
|
|
166
160
|
agent: this.name,
|
|
@@ -173,19 +167,34 @@ export const claudeCodeRuntime = {
|
|
|
173
167
|
},
|
|
174
168
|
logger: ctx.logger,
|
|
175
169
|
});
|
|
170
|
+
} else if (event?.type === 'message.delta' && event.text) {
|
|
171
|
+
await emitWorkerEvent({
|
|
172
|
+
adapter,
|
|
173
|
+
binding,
|
|
174
|
+
agent: this.name,
|
|
175
|
+
sessionId: event.sessionId || targetSessionId || binding.id,
|
|
176
|
+
cwd: projectDir,
|
|
177
|
+
replyToMessageId: inbound.messageId || null,
|
|
178
|
+
event: {
|
|
179
|
+
hook_event_name: 'worker.message.delta',
|
|
180
|
+
worker_event_name: 'worker.message.delta',
|
|
181
|
+
delta: event.text,
|
|
182
|
+
},
|
|
183
|
+
logger: ctx.logger,
|
|
184
|
+
});
|
|
176
185
|
}
|
|
177
186
|
},
|
|
178
187
|
})
|
|
179
188
|
: await this.runTurn({ sessionId: targetSessionId, projectDir, claudePath, agentEnv }, message, { appendSystemPrompt });
|
|
180
189
|
const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
|
|
181
|
-
|
|
182
|
-
sessionId: result?.sessionId,
|
|
183
|
-
}),
|
|
190
|
+
sessionId: result?.sessionId || meta.sessionId,
|
|
184
191
|
runtimePath: claudePath,
|
|
185
192
|
claudePath,
|
|
186
193
|
claudeVersion,
|
|
194
|
+
rotatePending: false,
|
|
195
|
+
lastRotatedAt: shouldRotate ? new Date().toISOString() : meta.lastRotatedAt,
|
|
187
196
|
}, { status: 'connected' });
|
|
188
|
-
|
|
197
|
+
await emitWorkerEvent({
|
|
189
198
|
adapter,
|
|
190
199
|
binding: nextBinding,
|
|
191
200
|
agent: this.name,
|
|
@@ -200,7 +209,7 @@ export const claudeCodeRuntime = {
|
|
|
200
209
|
});
|
|
201
210
|
return true;
|
|
202
211
|
} catch (err) {
|
|
203
|
-
|
|
212
|
+
await emitWorkerEvent({
|
|
204
213
|
adapter,
|
|
205
214
|
binding,
|
|
206
215
|
agent: this.name,
|
|
@@ -230,6 +239,24 @@ export const claudeCodeRuntime = {
|
|
|
230
239
|
}
|
|
231
240
|
},
|
|
232
241
|
|
|
242
|
+
async reconcileAfterRestart(binding, ctx) {
|
|
243
|
+
const meta = binding.runtimeMeta || {};
|
|
244
|
+
await emitWorkerEvent({
|
|
245
|
+
adapter: ctx.adapter,
|
|
246
|
+
binding,
|
|
247
|
+
agent: this.name,
|
|
248
|
+
sessionId: meta.sessionId || binding.id,
|
|
249
|
+
cwd: ensureAgentHome(binding.id) || '',
|
|
250
|
+
event: {
|
|
251
|
+
hook_event_name: 'Stop',
|
|
252
|
+
worker_event_name: 'worker.turn.complete',
|
|
253
|
+
reason: 'connector.restart.reconcile',
|
|
254
|
+
},
|
|
255
|
+
logger: ctx.logger,
|
|
256
|
+
});
|
|
257
|
+
return 1;
|
|
258
|
+
},
|
|
259
|
+
|
|
233
260
|
// Friendly display name for a CC session (encoded project path →
|
|
234
261
|
// last meaningful segment). Used when materializing a binding.
|
|
235
262
|
formatDisplayName(session) {
|
|
@@ -231,19 +231,23 @@ export function streamCCPrompt({
|
|
|
231
231
|
let seenTurnStart = false;
|
|
232
232
|
let activeSessionId = sessionId || null;
|
|
233
233
|
let finalText = '';
|
|
234
|
+
let eventChain = Promise.resolve();
|
|
234
235
|
|
|
235
236
|
const emit = (event) => {
|
|
236
237
|
if (typeof onEvent !== 'function') return;
|
|
237
|
-
|
|
238
|
+
eventChain = eventChain
|
|
238
239
|
.then(() => onEvent(event))
|
|
239
240
|
.catch(() => {});
|
|
241
|
+
return eventChain;
|
|
240
242
|
};
|
|
241
243
|
|
|
242
244
|
const settle = (fn, value) => {
|
|
243
245
|
if (settled) return;
|
|
244
246
|
settled = true;
|
|
245
247
|
if (timeout) clearTimeout(timeout);
|
|
246
|
-
|
|
248
|
+
eventChain
|
|
249
|
+
.catch(() => {})
|
|
250
|
+
.finally(() => fn(value));
|
|
247
251
|
};
|
|
248
252
|
|
|
249
253
|
const parseLine = (line) => {
|
|
@@ -263,6 +267,7 @@ export function streamCCPrompt({
|
|
|
263
267
|
const deltaText = parsed.event?.delta?.text;
|
|
264
268
|
if (typeof deltaText === 'string' && deltaText) {
|
|
265
269
|
finalText += deltaText;
|
|
270
|
+
emit({ type: 'message.delta', sessionId: activeSessionId, text: deltaText });
|
|
266
271
|
}
|
|
267
272
|
return;
|
|
268
273
|
}
|