ticlawk 0.1.16-dev.3 → 0.1.16-dev.31
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 +293 -70
- package/src/adapters/ticlawk/credentials.mjs +41 -1
- package/src/adapters/ticlawk/index.mjs +199 -199
- 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/events/worker-events.mjs +32 -36
- package/src/core/http.mjs +126 -0
- package/src/core/runtime-env.mjs +7 -0
- package/src/core/runtime-support.mjs +108 -107
- 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 +50 -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 -262
- package/src/runtimes/_shared/wake-prompt.mjs +261 -0
- package/src/runtimes/claude-code/index.mjs +34 -127
- package/src/runtimes/claude-code/session.mjs +2 -7
- package/src/runtimes/codex/index.mjs +117 -54
- package/src/runtimes/codex/session.mjs +2 -12
- package/src/runtimes/openclaw/index.mjs +16 -26
- package/src/runtimes/opencode/index.mjs +45 -66
- package/src/runtimes/opencode/session.mjs +12 -12
- package/src/runtimes/pi/index.mjs +42 -60
- package/src/runtimes/pi/session.mjs +9 -6
- package/src/adapters/ticlawk/cards.mjs +0 -149
- package/src/core/media/outbound.mjs +0 -163
|
@@ -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 {
|
|
28
|
-
import { emitWorkerEvent } from '../../core/events/worker-events.mjs';
|
|
26
|
+
import { emitWorkerEventBestEffort } 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,117 +122,49 @@ 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') {
|
|
239
|
-
|
|
163
|
+
emitWorkerEventBestEffort({
|
|
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: {
|
|
@@ -249,40 +173,23 @@ export const claudeCodeRuntime = {
|
|
|
249
173
|
},
|
|
250
174
|
logger: ctx.logger,
|
|
251
175
|
});
|
|
252
|
-
} else if (event?.type === 'message.delta' && event.text) {
|
|
253
|
-
await emitWorkerEvent({
|
|
254
|
-
adapter,
|
|
255
|
-
binding,
|
|
256
|
-
agent: this.name,
|
|
257
|
-
sessionId: event.sessionId || sessionId,
|
|
258
|
-
cwd: projectDir,
|
|
259
|
-
replyToMessageId: inbound.messageId || null,
|
|
260
|
-
event: {
|
|
261
|
-
hook_event_name: 'worker.message.delta',
|
|
262
|
-
worker_event_name: 'worker.message.delta',
|
|
263
|
-
delta: event.text,
|
|
264
|
-
},
|
|
265
|
-
logger: ctx.logger,
|
|
266
|
-
});
|
|
267
176
|
}
|
|
268
177
|
},
|
|
269
178
|
})
|
|
270
|
-
: await this.runTurn({ sessionId, projectDir, claudePath, agentEnv }, message, { appendSystemPrompt });
|
|
179
|
+
: await this.runTurn({ sessionId: targetSessionId, projectDir, claudePath, agentEnv }, message, { appendSystemPrompt });
|
|
271
180
|
const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
|
|
272
|
-
|
|
181
|
+
...buildRuntimeSessionMetaPatch(meta, sessionScope, {
|
|
182
|
+
sessionId: result?.sessionId,
|
|
183
|
+
}),
|
|
273
184
|
runtimePath: claudePath,
|
|
274
185
|
claudePath,
|
|
275
186
|
claudeVersion,
|
|
276
187
|
}, { status: 'connected' });
|
|
277
|
-
|
|
278
|
-
...result,
|
|
279
|
-
media: normalizeOutboundMedia(result),
|
|
280
|
-
});
|
|
281
|
-
await emitWorkerEvent({
|
|
188
|
+
emitWorkerEventBestEffort({
|
|
282
189
|
adapter,
|
|
283
190
|
binding: nextBinding,
|
|
284
191
|
agent: this.name,
|
|
285
|
-
sessionId: result?.sessionId ||
|
|
192
|
+
sessionId: result?.sessionId || targetSessionId || binding.id,
|
|
286
193
|
cwd: projectDir,
|
|
287
194
|
replyToMessageId: inbound.messageId || null,
|
|
288
195
|
event: {
|
|
@@ -293,11 +200,11 @@ export const claudeCodeRuntime = {
|
|
|
293
200
|
});
|
|
294
201
|
return true;
|
|
295
202
|
} catch (err) {
|
|
296
|
-
|
|
203
|
+
emitWorkerEventBestEffort({
|
|
297
204
|
adapter,
|
|
298
205
|
binding,
|
|
299
206
|
agent: this.name,
|
|
300
|
-
sessionId,
|
|
207
|
+
sessionId: errEventSessionId,
|
|
301
208
|
cwd: projectDir,
|
|
302
209
|
replyToMessageId: inbound.messageId || null,
|
|
303
210
|
event: {
|
|
@@ -325,7 +232,7 @@ export const claudeCodeRuntime = {
|
|
|
325
232
|
|
|
326
233
|
async reconcileAfterRestart(binding, ctx) {
|
|
327
234
|
const meta = binding.runtimeMeta || {};
|
|
328
|
-
|
|
235
|
+
emitWorkerEventBestEffort({
|
|
329
236
|
adapter: ctx.adapter,
|
|
330
237
|
binding,
|
|
331
238
|
agent: this.name,
|
|
@@ -231,23 +231,19 @@ export function streamCCPrompt({
|
|
|
231
231
|
let seenTurnStart = false;
|
|
232
232
|
let activeSessionId = sessionId || null;
|
|
233
233
|
let finalText = '';
|
|
234
|
-
let eventChain = Promise.resolve();
|
|
235
234
|
|
|
236
235
|
const emit = (event) => {
|
|
237
236
|
if (typeof onEvent !== 'function') return;
|
|
238
|
-
|
|
237
|
+
void Promise.resolve()
|
|
239
238
|
.then(() => onEvent(event))
|
|
240
239
|
.catch(() => {});
|
|
241
|
-
return eventChain;
|
|
242
240
|
};
|
|
243
241
|
|
|
244
242
|
const settle = (fn, value) => {
|
|
245
243
|
if (settled) return;
|
|
246
244
|
settled = true;
|
|
247
245
|
if (timeout) clearTimeout(timeout);
|
|
248
|
-
|
|
249
|
-
.catch(() => {})
|
|
250
|
-
.finally(() => fn(value));
|
|
246
|
+
fn(value);
|
|
251
247
|
};
|
|
252
248
|
|
|
253
249
|
const parseLine = (line) => {
|
|
@@ -267,7 +263,6 @@ export function streamCCPrompt({
|
|
|
267
263
|
const deltaText = parsed.event?.delta?.text;
|
|
268
264
|
if (typeof deltaText === 'string' && deltaText) {
|
|
269
265
|
finalText += deltaText;
|
|
270
|
-
emit({ type: 'message.delta', sessionId: activeSessionId, text: deltaText });
|
|
271
266
|
}
|
|
272
267
|
return;
|
|
273
268
|
}
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
* and discover local sessions.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
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,97 @@ import {
|
|
|
18
21
|
requireCodexPath,
|
|
19
22
|
} from './session.mjs';
|
|
20
23
|
import { buildCodexInputFromInbound } from '../../core/media/inbound.mjs';
|
|
21
|
-
import {
|
|
22
|
-
import { emitWorkerEvent } from '../../core/events/worker-events.mjs';
|
|
24
|
+
import { emitWorkerEventBestEffort } from '../../core/events/worker-events.mjs';
|
|
23
25
|
import {
|
|
24
26
|
shouldStreamRuntime,
|
|
25
|
-
createDeltaAggregator,
|
|
26
|
-
sendAdapterMessage,
|
|
27
|
-
recordActivity,
|
|
28
27
|
reportSubprocessFailure,
|
|
29
28
|
terminalRuntimeFailure,
|
|
30
29
|
updateBindingRuntimeMeta,
|
|
31
30
|
isRuntimeGatewayFailure,
|
|
31
|
+
resolveRuntimeSessionScope,
|
|
32
|
+
buildRuntimeSessionMetaPatch,
|
|
32
33
|
} from '../../core/runtime-support.mjs';
|
|
33
34
|
import { buildAgentRuntimeEnv } from '../../core/runtime-env.mjs';
|
|
34
35
|
import { buildStandingPrompt } from '../_shared/standing-prompt.mjs';
|
|
35
36
|
import { ensureAgentHome } from '../../core/agent-home.mjs';
|
|
37
|
+
import { debugError, debugLog } from '../../core/logger.mjs';
|
|
38
|
+
|
|
39
|
+
function parseBooleanish(value) {
|
|
40
|
+
if (value === undefined || value === null || value === '') return false;
|
|
41
|
+
return ['1', 'true', 'on', 'yes'].includes(String(value).trim().toLowerCase());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sha256(text) {
|
|
45
|
+
return createHash('sha256').update(String(text || ''), 'utf8').digest('hex');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function fileSafeId(value) {
|
|
49
|
+
return String(value || 'unknown')
|
|
50
|
+
.replace(/[^a-zA-Z0-9_-]+/g, '-')
|
|
51
|
+
.replace(/^-+|-+$/g, '')
|
|
52
|
+
.slice(0, 80) || 'unknown';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function writePromptSnapshot({
|
|
56
|
+
binding,
|
|
57
|
+
inbound,
|
|
58
|
+
agentHome,
|
|
59
|
+
targetSessionId,
|
|
60
|
+
shouldRotate,
|
|
61
|
+
developerInstructions,
|
|
62
|
+
message,
|
|
63
|
+
input,
|
|
64
|
+
}) {
|
|
65
|
+
if (!parseBooleanish(process.env.TICLAWK_LOG_RUNTIME_PROMPTS)) return;
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const rootDir = process.env.TICLAWK_RUNTIME_PROMPT_LOG_DIR
|
|
69
|
+
|| join(homedir(), '.ticlawk', 'prompt-logs');
|
|
70
|
+
const dir = join(rootDir, fileSafeId(binding?.id));
|
|
71
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
72
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
73
|
+
const messageId = fileSafeId(inbound?.messageId);
|
|
74
|
+
const filePath = join(dir, `${stamp}-${messageId}.json`);
|
|
75
|
+
const payload = {
|
|
76
|
+
createdAt: new Date().toISOString(),
|
|
77
|
+
runtime: 'codex',
|
|
78
|
+
agentId: binding?.id || null,
|
|
79
|
+
conversationId: inbound?.conversationId || null,
|
|
80
|
+
messageId: inbound?.messageId || null,
|
|
81
|
+
target: inbound?.envelopeTarget || null,
|
|
82
|
+
action: inbound?.action || null,
|
|
83
|
+
sessionId: targetSessionId || null,
|
|
84
|
+
shouldRotate: Boolean(shouldRotate),
|
|
85
|
+
agentHome,
|
|
86
|
+
hashes: {
|
|
87
|
+
developerInstructionsSha256: sha256(developerInstructions),
|
|
88
|
+
messageSha256: sha256(message),
|
|
89
|
+
},
|
|
90
|
+
developerInstructions,
|
|
91
|
+
message,
|
|
92
|
+
input: input || null,
|
|
93
|
+
};
|
|
94
|
+
writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, { mode: 0o600 });
|
|
95
|
+
debugLog('codex', 'prompt.snapshot', {
|
|
96
|
+
agentId: binding?.id,
|
|
97
|
+
conversationId: inbound?.conversationId,
|
|
98
|
+
messageId: inbound?.messageId,
|
|
99
|
+
target: inbound?.envelopeTarget,
|
|
100
|
+
path: filePath,
|
|
101
|
+
developerInstructionChars: String(developerInstructions || '').length,
|
|
102
|
+
messageChars: String(message || '').length,
|
|
103
|
+
developerInstructionsSha256: payload.hashes.developerInstructionsSha256,
|
|
104
|
+
messageSha256: payload.hashes.messageSha256,
|
|
105
|
+
});
|
|
106
|
+
} catch (err) {
|
|
107
|
+
debugError('codex', 'prompt.snapshot.failed', {
|
|
108
|
+
agentId: binding?.id,
|
|
109
|
+
conversationId: inbound?.conversationId,
|
|
110
|
+
messageId: inbound?.messageId,
|
|
111
|
+
error: err?.message || String(err),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
36
115
|
|
|
37
116
|
export const codexRuntime = {
|
|
38
117
|
name: 'codex',
|
|
@@ -130,7 +209,7 @@ export const codexRuntime = {
|
|
|
130
209
|
if (!binding) return false;
|
|
131
210
|
const adapter = ctx.adapter;
|
|
132
211
|
const meta = binding.runtimeMeta || {};
|
|
133
|
-
//
|
|
212
|
+
// cwd is the per-agent home dir under ~/.ticlawk/agents/<id>/.
|
|
134
213
|
// ensureAgentHome creates it + seeds MEMORY.md if missing.
|
|
135
214
|
const agentHome = ensureAgentHome(binding.id, {
|
|
136
215
|
displayName: binding.display_name || binding.name || null,
|
|
@@ -143,46 +222,42 @@ export const codexRuntime = {
|
|
|
143
222
|
? (codexInput.find((item) => item?.type === 'text')?.text || inbound.text || '(image attached)')
|
|
144
223
|
: inbound.text;
|
|
145
224
|
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
await emitWorkerEvent({
|
|
150
|
-
adapter,
|
|
151
|
-
binding,
|
|
152
|
-
agent: this.name,
|
|
153
|
-
sessionId: sessionId || meta.sessionId || binding.id,
|
|
154
|
-
turnId: turnId || null,
|
|
155
|
-
cwd: cwd || agentHome,
|
|
156
|
-
replyToMessageId: inbound.messageId || null,
|
|
157
|
-
event: {
|
|
158
|
-
hook_event_name: 'worker.message.delta',
|
|
159
|
-
worker_event_name: 'worker.message.delta',
|
|
160
|
-
delta: text,
|
|
161
|
-
},
|
|
162
|
-
logger: ctx.logger,
|
|
163
|
-
});
|
|
164
|
-
},
|
|
165
|
-
});
|
|
225
|
+
const sessionScope = resolveRuntimeSessionScope(meta, inbound);
|
|
226
|
+
const shouldRotate = sessionScope.shouldRotate;
|
|
227
|
+
const targetSessionId = shouldRotate ? null : sessionScope.sessionId;
|
|
166
228
|
try {
|
|
167
229
|
const runtimeCodexPath = requireCodexPath(meta.codexPath || meta.runtimePath);
|
|
168
230
|
const runtimeCodexVersion = getCodexRuntimeHealth(runtimeCodexPath).version || meta.codexVersion || null;
|
|
169
231
|
const agentEnv = buildAgentRuntimeEnv({
|
|
170
232
|
agentId: binding.id,
|
|
171
|
-
sessionId:
|
|
233
|
+
sessionId: targetSessionId,
|
|
172
234
|
hostId: binding.runtime_host_id,
|
|
235
|
+
conversationId: inbound.conversationId,
|
|
236
|
+
messageId: inbound.messageId,
|
|
237
|
+
target: inbound.envelopeTarget,
|
|
238
|
+
});
|
|
239
|
+
const developerInstructions = buildStandingPrompt({ agentId: binding.id, inbound });
|
|
240
|
+
writePromptSnapshot({
|
|
241
|
+
binding,
|
|
242
|
+
inbound,
|
|
243
|
+
agentHome,
|
|
244
|
+
targetSessionId,
|
|
245
|
+
shouldRotate,
|
|
246
|
+
developerInstructions,
|
|
247
|
+
message,
|
|
248
|
+
input: codexInput,
|
|
173
249
|
});
|
|
174
|
-
const developerInstructions = buildStandingPrompt({ agentId: binding.id });
|
|
175
250
|
const result = (shouldRotate || inbound.action === 'image' || shouldStreamRuntime(this.name, this))
|
|
176
|
-
? await this.runTurnStream({ sessionId:
|
|
251
|
+
? await this.runTurnStream({ sessionId: targetSessionId, cwd: agentHome, codexPath: runtimeCodexPath, agentEnv }, message, {
|
|
177
252
|
input: codexInput,
|
|
178
253
|
developerInstructions,
|
|
179
254
|
onEvent: async (event) => {
|
|
180
255
|
if (event?.type === 'turn.started') {
|
|
181
|
-
|
|
256
|
+
emitWorkerEventBestEffort({
|
|
182
257
|
adapter,
|
|
183
258
|
binding,
|
|
184
259
|
agent: this.name,
|
|
185
|
-
sessionId: event.sessionId ||
|
|
260
|
+
sessionId: event.sessionId || targetSessionId || binding.id,
|
|
186
261
|
turnId: event.turnId || null,
|
|
187
262
|
cwd: agentHome,
|
|
188
263
|
replyToMessageId: inbound.messageId || null,
|
|
@@ -192,38 +267,27 @@ export const codexRuntime = {
|
|
|
192
267
|
},
|
|
193
268
|
logger: ctx.logger,
|
|
194
269
|
});
|
|
195
|
-
} else if (event?.type === 'message.delta' && event.text) {
|
|
196
|
-
deltaAggregator.push(event.text, {
|
|
197
|
-
sessionId: event.sessionId || meta.sessionId || binding.id,
|
|
198
|
-
turnId: event.turnId || null,
|
|
199
|
-
cwd: agentHome,
|
|
200
|
-
});
|
|
201
270
|
}
|
|
202
271
|
},
|
|
203
272
|
})
|
|
204
|
-
: await this.runTurn({ sessionId:
|
|
273
|
+
: await this.runTurn({ sessionId: targetSessionId, cwd: agentHome, codexPath: runtimeCodexPath, agentEnv }, message, {
|
|
205
274
|
input: codexInput,
|
|
206
275
|
});
|
|
207
276
|
|
|
208
|
-
await deltaAggregator.flush();
|
|
209
277
|
const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
|
|
210
|
-
|
|
211
|
-
|
|
278
|
+
...buildRuntimeSessionMetaPatch(meta, sessionScope, {
|
|
279
|
+
sessionId: result?.sessionId,
|
|
280
|
+
path: result?.path,
|
|
281
|
+
}),
|
|
212
282
|
runtimePath: runtimeCodexPath,
|
|
213
283
|
codexPath: runtimeCodexPath,
|
|
214
284
|
codexVersion: runtimeCodexVersion,
|
|
215
|
-
rotatePending: false,
|
|
216
|
-
lastRotatedAt: shouldRotate ? new Date().toISOString() : meta.lastRotatedAt,
|
|
217
285
|
}, { status: 'connected' });
|
|
218
|
-
|
|
219
|
-
...result,
|
|
220
|
-
media: normalizeOutboundMedia(result),
|
|
221
|
-
});
|
|
222
|
-
await emitWorkerEvent({
|
|
286
|
+
emitWorkerEventBestEffort({
|
|
223
287
|
adapter,
|
|
224
288
|
binding: nextBinding,
|
|
225
289
|
agent: this.name,
|
|
226
|
-
sessionId: result?.sessionId ||
|
|
290
|
+
sessionId: result?.sessionId || targetSessionId || binding.id,
|
|
227
291
|
turnId: result?.turnId || null,
|
|
228
292
|
cwd: result?.cwd || agentHome,
|
|
229
293
|
replyToMessageId: inbound.messageId || null,
|
|
@@ -235,7 +299,6 @@ export const codexRuntime = {
|
|
|
235
299
|
});
|
|
236
300
|
return true;
|
|
237
301
|
} catch (err) {
|
|
238
|
-
await deltaAggregator.flush().catch(() => {});
|
|
239
302
|
const failureInfo = err?.info || {
|
|
240
303
|
ok: false,
|
|
241
304
|
kind: 'exit-error',
|
|
@@ -250,11 +313,11 @@ export const codexRuntime = {
|
|
|
250
313
|
lastGatewayFailureReason: failureInfo.errorMessage || err?.message || 'Codex app-server error',
|
|
251
314
|
}, { status: 'degraded' }).catch(() => binding);
|
|
252
315
|
}
|
|
253
|
-
|
|
316
|
+
emitWorkerEventBestEffort({
|
|
254
317
|
adapter,
|
|
255
318
|
binding: failureBinding,
|
|
256
319
|
agent: this.name,
|
|
257
|
-
sessionId:
|
|
320
|
+
sessionId: targetSessionId || binding.id,
|
|
258
321
|
turnId: failureInfo?.turnId || null,
|
|
259
322
|
cwd: agentHome,
|
|
260
323
|
replyToMessageId: inbound.messageId || null,
|
|
@@ -278,7 +341,7 @@ export const codexRuntime = {
|
|
|
278
341
|
|
|
279
342
|
async reconcileAfterRestart(binding, ctx) {
|
|
280
343
|
const meta = binding.runtimeMeta || {};
|
|
281
|
-
|
|
344
|
+
emitWorkerEventBestEffort({
|
|
282
345
|
adapter: ctx.adapter,
|
|
283
346
|
binding,
|
|
284
347
|
agent: this.name,
|
|
@@ -368,14 +368,12 @@ export function streamCodexPrompt({
|
|
|
368
368
|
let finalText = '';
|
|
369
369
|
let threadPath = null;
|
|
370
370
|
const ignoredChildEventsLogged = new Set();
|
|
371
|
-
let eventChain = Promise.resolve();
|
|
372
371
|
|
|
373
372
|
const emit = (event) => {
|
|
374
373
|
if (typeof onEvent !== 'function') return;
|
|
375
|
-
|
|
374
|
+
void Promise.resolve()
|
|
376
375
|
.then(() => onEvent(event))
|
|
377
376
|
.catch(() => {});
|
|
378
|
-
return eventChain;
|
|
379
377
|
};
|
|
380
378
|
|
|
381
379
|
const settle = (fn, value) => {
|
|
@@ -387,9 +385,7 @@ export function streamCodexPrompt({
|
|
|
387
385
|
}
|
|
388
386
|
pending.clear();
|
|
389
387
|
try { child.kill('SIGTERM'); } catch {}
|
|
390
|
-
|
|
391
|
-
.catch(() => {})
|
|
392
|
-
.finally(() => fn(value));
|
|
388
|
+
fn(value);
|
|
393
389
|
};
|
|
394
390
|
|
|
395
391
|
const send = (method, params = {}) => {
|
|
@@ -451,12 +447,6 @@ export function streamCodexPrompt({
|
|
|
451
447
|
const delta = params?.delta;
|
|
452
448
|
if (typeof delta === 'string' && delta && isRootContext(params)) {
|
|
453
449
|
finalText += delta;
|
|
454
|
-
emit({
|
|
455
|
-
type: 'message.delta',
|
|
456
|
-
sessionId: rootThreadId,
|
|
457
|
-
turnId: rootTurnId,
|
|
458
|
-
text: delta,
|
|
459
|
-
});
|
|
460
450
|
} else if (typeof delta === 'string' && delta) {
|
|
461
451
|
logIgnoredChildEvent('codex.non-root-agent-delta', params);
|
|
462
452
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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
|
|
29
|
-
// prompt this process lifetime. OpenClaw's
|
|
30
|
-
// out of process, so we err on the side of
|
|
31
|
-
//
|
|
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)
|
|
164
|
-
// separate "system prompt" parameter on the gateway, so we
|
|
165
|
-
//
|
|
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(
|
|
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
|
|