ticlawk 0.1.16-dev.1 → 0.1.16-dev.11
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 +13 -0
- package/bin/ticlawk.mjs +116 -0
- package/package.json +1 -1
- package/src/adapters/ticlawk/api.mjs +226 -28
- package/src/adapters/ticlawk/index.mjs +258 -113
- package/src/cli/agent-commands.mjs +594 -8
- package/src/core/agent-cli-handlers.mjs +443 -3
- package/src/core/agent-home.mjs +85 -0
- package/src/core/argv.mjs +11 -1
- package/src/core/http.mjs +121 -0
- package/src/core/reminder-ticker.mjs +70 -0
- package/src/core/runtime-contract.mjs +1 -1
- package/src/core/runtime-support.mjs +31 -59
- package/src/core/ticlawk-control.mjs +3 -3
- package/src/migrate/write-initial-memory.mjs +101 -0
- package/src/runtimes/_shared/standing-prompt.mjs +296 -77
- package/src/runtimes/claude-code/index.mjs +28 -131
- package/src/runtimes/codex/index.mjs +15 -39
- package/src/runtimes/openclaw/index.mjs +39 -30
- package/src/runtimes/openclaw/target.mjs +0 -30
- package/src/runtimes/opencode/index.mjs +19 -54
- package/src/runtimes/pi/index.mjs +16 -49
- package/ticlawk.mjs +31 -6
- package/src/adapters/ticlawk/cards.mjs +0 -149
- package/src/core/media/outbound.mjs +0 -163
|
@@ -7,12 +7,11 @@
|
|
|
7
7
|
* runtime owns the binary-level details.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { existsSync } from 'node:fs';
|
|
11
10
|
import { basename } from 'node:path';
|
|
12
11
|
import { buildAgentRuntimeEnv } from '../../core/runtime-env.mjs';
|
|
13
12
|
import { buildStandingPrompt } from '../_shared/standing-prompt.mjs';
|
|
13
|
+
import { ensureAgentHome } from '../../core/agent-home.mjs';
|
|
14
14
|
import {
|
|
15
|
-
createCCSession,
|
|
16
15
|
getClaudeCodeRuntimeHealth,
|
|
17
16
|
runCCPrompt,
|
|
18
17
|
streamCCPrompt,
|
|
@@ -24,12 +23,9 @@ 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,
|
|
@@ -38,15 +34,9 @@ import {
|
|
|
38
34
|
export const claudeCodeRuntime = {
|
|
39
35
|
name: 'claude_code',
|
|
40
36
|
|
|
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.
|
|
37
|
+
// Run a Claude turn and wait for the final result on stdout. Used by
|
|
38
|
+
// deliverTurn when streaming is disabled; both fresh sessions
|
|
39
|
+
// (sessionId=null) and resumed sessions go through here.
|
|
50
40
|
runTurn({ sessionId, projectDir, claudePath, agentEnv }, text, opts = {}) {
|
|
51
41
|
return runCCPrompt({
|
|
52
42
|
sessionId,
|
|
@@ -97,8 +87,6 @@ export const claudeCodeRuntime = {
|
|
|
97
87
|
runtimeMeta: {
|
|
98
88
|
sessionId: session.sessionId,
|
|
99
89
|
project: session.project,
|
|
100
|
-
workdir: session.projectDir,
|
|
101
|
-
projectDir: session.projectDir,
|
|
102
90
|
path: session.path,
|
|
103
91
|
runtimePath: claudePath,
|
|
104
92
|
claudePath,
|
|
@@ -108,21 +96,12 @@ export const claudeCodeRuntime = {
|
|
|
108
96
|
};
|
|
109
97
|
}
|
|
110
98
|
|
|
111
|
-
if (!requestedProjectDir) {
|
|
112
|
-
throw new Error('projectDir or sessionId is required for claude_code binding');
|
|
113
|
-
}
|
|
114
|
-
if (!existsSync(requestedProjectDir)) {
|
|
115
|
-
throw new Error(`project dir not found locally: ${requestedProjectDir}`);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
99
|
return {
|
|
119
100
|
runtime: this.name,
|
|
120
|
-
displayName: payload?.name || basename(requestedProjectDir)
|
|
101
|
+
displayName: payload?.name || (requestedProjectDir ? basename(requestedProjectDir) : 'Claude Code'),
|
|
121
102
|
runtimeMeta: {
|
|
122
103
|
sessionId: null,
|
|
123
|
-
project: basename(requestedProjectDir)
|
|
124
|
-
workdir: requestedProjectDir,
|
|
125
|
-
projectDir: requestedProjectDir,
|
|
104
|
+
project: requestedProjectDir ? basename(requestedProjectDir) : '',
|
|
126
105
|
path: null,
|
|
127
106
|
runtimePath: claudePath,
|
|
128
107
|
claudePath,
|
|
@@ -141,117 +120,37 @@ export const claudeCodeRuntime = {
|
|
|
141
120
|
if (!binding) return false;
|
|
142
121
|
const adapter = ctx.adapter;
|
|
143
122
|
const meta = binding.runtimeMeta || {};
|
|
144
|
-
|
|
145
|
-
const
|
|
123
|
+
// cwd is the per-agent home dir under ~/.ticlawk/agents/<id>/.
|
|
124
|
+
const projectDir = ensureAgentHome(binding.id, {
|
|
125
|
+
displayName: binding.display_name || binding.name || null,
|
|
126
|
+
});
|
|
146
127
|
const runtimeClaudePath = meta.claudePath || meta.runtimePath || null;
|
|
147
128
|
|
|
148
|
-
if (!projectDir || !existsSync(projectDir)) {
|
|
149
|
-
await sendAdapterMessage(adapter, binding, {
|
|
150
|
-
type: 'assistant',
|
|
151
|
-
text: `⚠️ Claude Code project dir not found: ${projectDir || '(missing)'}`,
|
|
152
|
-
media: [],
|
|
153
|
-
replyToMessageId: inbound.messageId || null,
|
|
154
|
-
});
|
|
155
|
-
return true;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
129
|
const message = inbound.action === 'image'
|
|
159
130
|
? await buildImageMessageFromInbound(inbound, 'claude-code')
|
|
160
131
|
: inbound.text;
|
|
161
132
|
|
|
133
|
+
// shouldRotate=true means meta.sessionId is missing or invalidated.
|
|
134
|
+
// We pass sessionId=null so `claude` creates a fresh session; the new
|
|
135
|
+
// session_id is captured from stream events and persisted below.
|
|
136
|
+
// Unifying rotate + non-rotate into one path means the standing prompt
|
|
137
|
+
// is always attached, so the agent uses the CLI to reply on every
|
|
138
|
+
// turn — including the first.
|
|
162
139
|
const shouldRotate = !meta.sessionId || meta.rotatePending;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
adapter,
|
|
166
|
-
binding,
|
|
167
|
-
agent: this.name,
|
|
168
|
-
sessionId: sessionId || binding.id,
|
|
169
|
-
cwd: projectDir,
|
|
170
|
-
replyToMessageId: inbound.messageId || null,
|
|
171
|
-
event: {
|
|
172
|
-
hook_event_name: 'worker.turn.start',
|
|
173
|
-
worker_event_name: 'worker.turn.start',
|
|
174
|
-
},
|
|
175
|
-
logger: ctx.logger,
|
|
176
|
-
});
|
|
177
|
-
try {
|
|
178
|
-
const claudePath = requireClaudePath(runtimeClaudePath);
|
|
179
|
-
const claudeVersion = getClaudeCodeRuntimeHealth(claudePath).version || meta.claudeVersion || null;
|
|
180
|
-
const created = await this.createSession({ projectDir, text: message, claudePath });
|
|
181
|
-
const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
|
|
182
|
-
sessionId: created.sessionId,
|
|
183
|
-
projectDir,
|
|
184
|
-
path: null,
|
|
185
|
-
runtimePath: claudePath,
|
|
186
|
-
claudePath,
|
|
187
|
-
claudeVersion,
|
|
188
|
-
rotatePending: false,
|
|
189
|
-
lastRotatedAt: new Date().toISOString(),
|
|
190
|
-
}, { status: 'connected' });
|
|
191
|
-
if (created.resultText && created.resultText.trim()) {
|
|
192
|
-
await sendAdapterMessage(adapter, nextBinding, {
|
|
193
|
-
type: 'assistant',
|
|
194
|
-
text: created.resultText,
|
|
195
|
-
media: [],
|
|
196
|
-
replyToMessageId: inbound.messageId || null,
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
await emitWorkerEvent({
|
|
200
|
-
adapter,
|
|
201
|
-
binding: nextBinding,
|
|
202
|
-
agent: this.name,
|
|
203
|
-
sessionId: created.sessionId,
|
|
204
|
-
cwd: projectDir,
|
|
205
|
-
replyToMessageId: inbound.messageId || null,
|
|
206
|
-
event: {
|
|
207
|
-
hook_event_name: 'Stop',
|
|
208
|
-
worker_event_name: 'worker.turn.complete',
|
|
209
|
-
},
|
|
210
|
-
logger: ctx.logger,
|
|
211
|
-
});
|
|
212
|
-
return true;
|
|
213
|
-
} catch (err) {
|
|
214
|
-
await emitWorkerEvent({
|
|
215
|
-
adapter,
|
|
216
|
-
binding,
|
|
217
|
-
agent: this.name,
|
|
218
|
-
sessionId: sessionId || binding.id,
|
|
219
|
-
cwd: projectDir,
|
|
220
|
-
replyToMessageId: inbound.messageId || null,
|
|
221
|
-
event: {
|
|
222
|
-
hook_event_name: 'worker.turn.error',
|
|
223
|
-
worker_event_name: 'worker.turn.error',
|
|
224
|
-
error: err?.message || 'Claude Code failed',
|
|
225
|
-
},
|
|
226
|
-
logger: ctx.logger,
|
|
227
|
-
});
|
|
228
|
-
await reportSubprocessFailure({
|
|
229
|
-
adapter,
|
|
230
|
-
binding,
|
|
231
|
-
inbound,
|
|
232
|
-
runtimeName: 'Claude Code',
|
|
233
|
-
info: err?.info || {
|
|
234
|
-
ok: false,
|
|
235
|
-
kind: 'exit-error',
|
|
236
|
-
errorMessage: err?.message || 'Claude Code failed',
|
|
237
|
-
durationMs: 0,
|
|
238
|
-
},
|
|
239
|
-
});
|
|
240
|
-
return terminalRuntimeFailure(err?.message || 'Claude Code failed');
|
|
241
|
-
}
|
|
242
|
-
}
|
|
140
|
+
const targetSessionId = shouldRotate ? null : meta.sessionId;
|
|
141
|
+
const errEventSessionId = meta.sessionId || binding.id;
|
|
243
142
|
|
|
244
143
|
try {
|
|
245
144
|
const claudePath = requireClaudePath(runtimeClaudePath);
|
|
246
145
|
const claudeVersion = getClaudeCodeRuntimeHealth(claudePath).version || meta.claudeVersion || null;
|
|
247
146
|
const agentEnv = buildAgentRuntimeEnv({
|
|
248
147
|
agentId: binding.id,
|
|
249
|
-
sessionId,
|
|
148
|
+
sessionId: meta.sessionId,
|
|
250
149
|
hostId: binding.runtime_host_id,
|
|
251
150
|
});
|
|
252
151
|
const appendSystemPrompt = buildStandingPrompt({ agentId: binding.id });
|
|
253
152
|
const result = shouldStreamRuntime(this.name, this)
|
|
254
|
-
? await this.runTurnStream({ sessionId, projectDir, claudePath, agentEnv }, message, {
|
|
153
|
+
? await this.runTurnStream({ sessionId: targetSessionId, projectDir, claudePath, agentEnv }, message, {
|
|
255
154
|
appendSystemPrompt,
|
|
256
155
|
onEvent: async (event) => {
|
|
257
156
|
if (event?.type === 'turn.started') {
|
|
@@ -259,7 +158,7 @@ export const claudeCodeRuntime = {
|
|
|
259
158
|
adapter,
|
|
260
159
|
binding,
|
|
261
160
|
agent: this.name,
|
|
262
|
-
sessionId: event.sessionId ||
|
|
161
|
+
sessionId: event.sessionId || targetSessionId || binding.id,
|
|
263
162
|
cwd: projectDir,
|
|
264
163
|
replyToMessageId: inbound.messageId || null,
|
|
265
164
|
event: {
|
|
@@ -273,7 +172,7 @@ export const claudeCodeRuntime = {
|
|
|
273
172
|
adapter,
|
|
274
173
|
binding,
|
|
275
174
|
agent: this.name,
|
|
276
|
-
sessionId: event.sessionId ||
|
|
175
|
+
sessionId: event.sessionId || targetSessionId || binding.id,
|
|
277
176
|
cwd: projectDir,
|
|
278
177
|
replyToMessageId: inbound.messageId || null,
|
|
279
178
|
event: {
|
|
@@ -286,22 +185,20 @@ export const claudeCodeRuntime = {
|
|
|
286
185
|
}
|
|
287
186
|
},
|
|
288
187
|
})
|
|
289
|
-
: await this.runTurn({ sessionId, projectDir, claudePath, agentEnv }, message, { appendSystemPrompt });
|
|
188
|
+
: await this.runTurn({ sessionId: targetSessionId, projectDir, claudePath, agentEnv }, message, { appendSystemPrompt });
|
|
290
189
|
const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
|
|
291
190
|
sessionId: result?.sessionId || meta.sessionId,
|
|
292
191
|
runtimePath: claudePath,
|
|
293
192
|
claudePath,
|
|
294
193
|
claudeVersion,
|
|
194
|
+
rotatePending: false,
|
|
195
|
+
lastRotatedAt: shouldRotate ? new Date().toISOString() : meta.lastRotatedAt,
|
|
295
196
|
}, { status: 'connected' });
|
|
296
|
-
await recordActivity(adapter, nextBinding, inbound, {
|
|
297
|
-
...result,
|
|
298
|
-
media: normalizeOutboundMedia(result),
|
|
299
|
-
});
|
|
300
197
|
await emitWorkerEvent({
|
|
301
198
|
adapter,
|
|
302
199
|
binding: nextBinding,
|
|
303
200
|
agent: this.name,
|
|
304
|
-
sessionId: result?.sessionId ||
|
|
201
|
+
sessionId: result?.sessionId || targetSessionId || binding.id,
|
|
305
202
|
cwd: projectDir,
|
|
306
203
|
replyToMessageId: inbound.messageId || null,
|
|
307
204
|
event: {
|
|
@@ -316,7 +213,7 @@ export const claudeCodeRuntime = {
|
|
|
316
213
|
adapter,
|
|
317
214
|
binding,
|
|
318
215
|
agent: this.name,
|
|
319
|
-
sessionId,
|
|
216
|
+
sessionId: errEventSessionId,
|
|
320
217
|
cwd: projectDir,
|
|
321
218
|
replyToMessageId: inbound.messageId || null,
|
|
322
219
|
event: {
|
|
@@ -349,7 +246,7 @@ export const claudeCodeRuntime = {
|
|
|
349
246
|
binding,
|
|
350
247
|
agent: this.name,
|
|
351
248
|
sessionId: meta.sessionId || binding.id,
|
|
352
|
-
cwd:
|
|
249
|
+
cwd: ensureAgentHome(binding.id) || '',
|
|
353
250
|
event: {
|
|
354
251
|
hook_event_name: 'Stop',
|
|
355
252
|
worker_event_name: 'worker.turn.complete',
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* and discover local sessions.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { existsSync } from 'node:fs';
|
|
9
8
|
import { basename } from 'node:path';
|
|
10
9
|
import {
|
|
11
10
|
createCodexSession,
|
|
@@ -19,13 +18,10 @@ import {
|
|
|
19
18
|
requireCodexPath,
|
|
20
19
|
} from './session.mjs';
|
|
21
20
|
import { buildCodexInputFromInbound } from '../../core/media/inbound.mjs';
|
|
22
|
-
import { normalizeOutboundMedia } from '../../core/media/outbound.mjs';
|
|
23
21
|
import { emitWorkerEvent } from '../../core/events/worker-events.mjs';
|
|
24
22
|
import {
|
|
25
23
|
shouldStreamRuntime,
|
|
26
24
|
createDeltaAggregator,
|
|
27
|
-
sendAdapterMessage,
|
|
28
|
-
recordActivity,
|
|
29
25
|
reportSubprocessFailure,
|
|
30
26
|
terminalRuntimeFailure,
|
|
31
27
|
updateBindingRuntimeMeta,
|
|
@@ -33,6 +29,7 @@ import {
|
|
|
33
29
|
} from '../../core/runtime-support.mjs';
|
|
34
30
|
import { buildAgentRuntimeEnv } from '../../core/runtime-env.mjs';
|
|
35
31
|
import { buildStandingPrompt } from '../_shared/standing-prompt.mjs';
|
|
32
|
+
import { ensureAgentHome } from '../../core/agent-home.mjs';
|
|
36
33
|
|
|
37
34
|
export const codexRuntime = {
|
|
38
35
|
name: 'codex',
|
|
@@ -98,8 +95,6 @@ export const codexRuntime = {
|
|
|
98
95
|
displayName: payload.name || basename(session.cwd) || 'Codex',
|
|
99
96
|
runtimeMeta: {
|
|
100
97
|
sessionId: session.sessionId,
|
|
101
|
-
workdir: session.cwd,
|
|
102
|
-
cwd: session.cwd,
|
|
103
98
|
path: session.path || null,
|
|
104
99
|
runtimePath: codexPath,
|
|
105
100
|
codexPath,
|
|
@@ -109,20 +104,11 @@ export const codexRuntime = {
|
|
|
109
104
|
};
|
|
110
105
|
}
|
|
111
106
|
|
|
112
|
-
if (!requestedCwd) {
|
|
113
|
-
throw new Error('cwd or sessionId is required for codex binding');
|
|
114
|
-
}
|
|
115
|
-
if (!existsSync(requestedCwd)) {
|
|
116
|
-
throw new Error(`codex cwd not found locally: ${requestedCwd}`);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
107
|
return {
|
|
120
108
|
runtime: this.name,
|
|
121
|
-
displayName: payload.name || basename(requestedCwd)
|
|
109
|
+
displayName: payload.name || (requestedCwd ? basename(requestedCwd) : 'Codex'),
|
|
122
110
|
runtimeMeta: {
|
|
123
111
|
sessionId: null,
|
|
124
|
-
workdir: requestedCwd,
|
|
125
|
-
cwd: requestedCwd,
|
|
126
112
|
path: null,
|
|
127
113
|
runtimePath: codexPath,
|
|
128
114
|
codexPath,
|
|
@@ -141,16 +127,11 @@ export const codexRuntime = {
|
|
|
141
127
|
if (!binding) return false;
|
|
142
128
|
const adapter = ctx.adapter;
|
|
143
129
|
const meta = binding.runtimeMeta || {};
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
media: [],
|
|
150
|
-
replyToMessageId: inbound.messageId || null,
|
|
151
|
-
});
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
130
|
+
// cwd is the per-agent home dir under ~/.ticlawk/agents/<id>/.
|
|
131
|
+
// ensureAgentHome creates it + seeds MEMORY.md if missing.
|
|
132
|
+
const agentHome = ensureAgentHome(binding.id, {
|
|
133
|
+
displayName: binding.display_name || binding.name || null,
|
|
134
|
+
});
|
|
154
135
|
|
|
155
136
|
const codexInput = inbound.action === 'image'
|
|
156
137
|
? await buildCodexInputFromInbound(inbound, 'codex')
|
|
@@ -168,7 +149,7 @@ export const codexRuntime = {
|
|
|
168
149
|
agent: this.name,
|
|
169
150
|
sessionId: sessionId || meta.sessionId || binding.id,
|
|
170
151
|
turnId: turnId || null,
|
|
171
|
-
cwd: cwd ||
|
|
152
|
+
cwd: cwd || agentHome,
|
|
172
153
|
replyToMessageId: inbound.messageId || null,
|
|
173
154
|
event: {
|
|
174
155
|
hook_event_name: 'worker.message.delta',
|
|
@@ -189,7 +170,7 @@ export const codexRuntime = {
|
|
|
189
170
|
});
|
|
190
171
|
const developerInstructions = buildStandingPrompt({ agentId: binding.id });
|
|
191
172
|
const result = (shouldRotate || inbound.action === 'image' || shouldStreamRuntime(this.name, this))
|
|
192
|
-
? await this.runTurnStream({ sessionId: shouldRotate ? null : meta.sessionId, cwd:
|
|
173
|
+
? await this.runTurnStream({ sessionId: shouldRotate ? null : meta.sessionId, cwd: agentHome, codexPath: runtimeCodexPath, agentEnv }, message, {
|
|
193
174
|
input: codexInput,
|
|
194
175
|
developerInstructions,
|
|
195
176
|
onEvent: async (event) => {
|
|
@@ -200,7 +181,7 @@ export const codexRuntime = {
|
|
|
200
181
|
agent: this.name,
|
|
201
182
|
sessionId: event.sessionId || meta.sessionId || binding.id,
|
|
202
183
|
turnId: event.turnId || null,
|
|
203
|
-
cwd:
|
|
184
|
+
cwd: agentHome,
|
|
204
185
|
replyToMessageId: inbound.messageId || null,
|
|
205
186
|
event: {
|
|
206
187
|
hook_event_name: 'worker.turn.start',
|
|
@@ -212,19 +193,18 @@ export const codexRuntime = {
|
|
|
212
193
|
deltaAggregator.push(event.text, {
|
|
213
194
|
sessionId: event.sessionId || meta.sessionId || binding.id,
|
|
214
195
|
turnId: event.turnId || null,
|
|
215
|
-
cwd:
|
|
196
|
+
cwd: agentHome,
|
|
216
197
|
});
|
|
217
198
|
}
|
|
218
199
|
},
|
|
219
200
|
})
|
|
220
|
-
: await this.runTurn({ sessionId: meta.sessionId, cwd:
|
|
201
|
+
: await this.runTurn({ sessionId: meta.sessionId, cwd: agentHome, codexPath: runtimeCodexPath, agentEnv }, message, {
|
|
221
202
|
input: codexInput,
|
|
222
203
|
});
|
|
223
204
|
|
|
224
205
|
await deltaAggregator.flush();
|
|
225
206
|
const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
|
|
226
207
|
sessionId: result?.sessionId || meta.sessionId,
|
|
227
|
-
cwd: result?.cwd || meta.cwd,
|
|
228
208
|
path: result?.path || meta.path || null,
|
|
229
209
|
runtimePath: runtimeCodexPath,
|
|
230
210
|
codexPath: runtimeCodexPath,
|
|
@@ -232,17 +212,13 @@ export const codexRuntime = {
|
|
|
232
212
|
rotatePending: false,
|
|
233
213
|
lastRotatedAt: shouldRotate ? new Date().toISOString() : meta.lastRotatedAt,
|
|
234
214
|
}, { status: 'connected' });
|
|
235
|
-
await recordActivity(adapter, nextBinding, inbound, {
|
|
236
|
-
...result,
|
|
237
|
-
media: normalizeOutboundMedia(result),
|
|
238
|
-
});
|
|
239
215
|
await emitWorkerEvent({
|
|
240
216
|
adapter,
|
|
241
217
|
binding: nextBinding,
|
|
242
218
|
agent: this.name,
|
|
243
219
|
sessionId: result?.sessionId || meta.sessionId || binding.id,
|
|
244
220
|
turnId: result?.turnId || null,
|
|
245
|
-
cwd: result?.cwd ||
|
|
221
|
+
cwd: result?.cwd || agentHome,
|
|
246
222
|
replyToMessageId: inbound.messageId || null,
|
|
247
223
|
event: {
|
|
248
224
|
hook_event_name: 'Stop',
|
|
@@ -273,7 +249,7 @@ export const codexRuntime = {
|
|
|
273
249
|
agent: this.name,
|
|
274
250
|
sessionId: meta.sessionId || binding.id,
|
|
275
251
|
turnId: failureInfo?.turnId || null,
|
|
276
|
-
cwd:
|
|
252
|
+
cwd: agentHome,
|
|
277
253
|
replyToMessageId: inbound.messageId || null,
|
|
278
254
|
event: {
|
|
279
255
|
hook_event_name: 'worker.turn.error',
|
|
@@ -300,7 +276,7 @@ export const codexRuntime = {
|
|
|
300
276
|
binding,
|
|
301
277
|
agent: this.name,
|
|
302
278
|
sessionId: meta.sessionId || binding.id,
|
|
303
|
-
cwd:
|
|
279
|
+
cwd: ensureAgentHome(binding.id) || '',
|
|
304
280
|
event: {
|
|
305
281
|
hook_event_name: 'Stop',
|
|
306
282
|
worker_event_name: 'worker.turn.complete',
|
|
@@ -1,8 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { reportSubprocessFailure, sendAdapterMessage, recordActivity, terminalRuntimeFailure } from '../../core/runtime-support.mjs';
|
|
1
|
+
import { reportSubprocessFailure, terminalRuntimeFailure } from '../../core/runtime-support.mjs';
|
|
3
2
|
import { buildStandingPrompt } from '../_shared/standing-prompt.mjs';
|
|
4
3
|
import { GATEWAY_HOST, GATEWAY_PORT } from './identity.mjs';
|
|
5
|
-
|
|
4
|
+
|
|
5
|
+
// Cheap availability probe used by the harness picker. We can't use
|
|
6
|
+
// isGatewayReady() here because the daemon only opens the gateway WS
|
|
7
|
+
// after at least one openclaw binding exists (registerOpenClawChannel),
|
|
8
|
+
// so an empty install would always look "unavailable" and you'd never
|
|
9
|
+
// be able to pick it for the first time. Probing the gateway's plain
|
|
10
|
+
// HTTP /health avoids that chicken-and-egg.
|
|
11
|
+
const OPENCLAW_HEALTH_TIMEOUT_MS = 1500;
|
|
12
|
+
async function probeOpenClawGatewayHealth() {
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetch(`http://${GATEWAY_HOST}:${GATEWAY_PORT}/health`, {
|
|
15
|
+
signal: AbortSignal.timeout(OPENCLAW_HEALTH_TIMEOUT_MS),
|
|
16
|
+
});
|
|
17
|
+
if (!res.ok) return false;
|
|
18
|
+
const body = await res.json().catch(() => null);
|
|
19
|
+
return body?.ok === true;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
import { buildOpenClawSessionKey, normalizeOpenClawAgentId } from './target.mjs';
|
|
6
25
|
import { askGateway, isGatewayReady, registerOpenClawChannel } from './gateway.mjs';
|
|
7
26
|
|
|
8
27
|
// Tracks which (agentId, sessionKey) pairs already saw the standing
|
|
@@ -87,18 +106,24 @@ export const openClawRuntime = {
|
|
|
87
106
|
return isGatewayReady();
|
|
88
107
|
},
|
|
89
108
|
|
|
109
|
+
// Health probe for the harness picker. openclaw is gateway-based —
|
|
110
|
+
// "available" means the local openclaw gateway is up and responds to
|
|
111
|
+
// an unauthenticated /health request. Doesn't depend on the daemon
|
|
112
|
+
// having an open WS, so the picker can offer openclaw on a fresh
|
|
113
|
+
// install where no binding exists yet.
|
|
114
|
+
async health() {
|
|
115
|
+
return {
|
|
116
|
+
available: await probeOpenClawGatewayHealth(),
|
|
117
|
+
path: null,
|
|
118
|
+
version: null,
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
|
|
90
122
|
async resolveBinding(payload) {
|
|
91
123
|
if (!payload?.agentId) {
|
|
92
124
|
throw new Error('agentId is required for openclaw binding');
|
|
93
125
|
}
|
|
94
126
|
const agentId = normalizeOpenClawAgentId(payload.agentId);
|
|
95
|
-
const workspace = String(
|
|
96
|
-
payload.workdir
|
|
97
|
-
|| payload.projectDir
|
|
98
|
-
|| payload.cwd
|
|
99
|
-
|| resolveOpenClawWorkspace(agentId)
|
|
100
|
-
|| '',
|
|
101
|
-
).trim();
|
|
102
127
|
const sessionKey = payload.sessionKey
|
|
103
128
|
? String(payload.sessionKey).trim()
|
|
104
129
|
: buildOpenClawSessionKey(agentId);
|
|
@@ -110,11 +135,6 @@ export const openClawRuntime = {
|
|
|
110
135
|
sessionKey,
|
|
111
136
|
gatewayHost: GATEWAY_HOST,
|
|
112
137
|
gatewayPort: GATEWAY_PORT,
|
|
113
|
-
...(workspace ? {
|
|
114
|
-
workdir: workspace,
|
|
115
|
-
projectDir: workspace,
|
|
116
|
-
cwd: workspace,
|
|
117
|
-
} : {}),
|
|
118
138
|
},
|
|
119
139
|
};
|
|
120
140
|
},
|
|
@@ -171,10 +191,6 @@ export const openClawRuntime = {
|
|
|
171
191
|
});
|
|
172
192
|
},
|
|
173
193
|
});
|
|
174
|
-
await recordActivity(adapter, binding, inbound, {
|
|
175
|
-
...result,
|
|
176
|
-
media: normalizeOutboundMedia(result),
|
|
177
|
-
});
|
|
178
194
|
return true;
|
|
179
195
|
} catch (err) {
|
|
180
196
|
if (typeof adapter.emitEvent === 'function') {
|
|
@@ -212,18 +228,11 @@ export const openClawRuntime = {
|
|
|
212
228
|
}
|
|
213
229
|
},
|
|
214
230
|
|
|
215
|
-
async recoverInFlight(
|
|
231
|
+
async recoverInFlight() {
|
|
232
|
+
// Just drain the persisted in-flight set — the user-facing notice
|
|
233
|
+
// that used to surface here went through the dead chat-projection
|
|
234
|
+
// path. OpenClaw is non-primary; this no-ops cleanly for now.
|
|
216
235
|
const entries = recoverInFlightEntries();
|
|
217
|
-
for (const entry of entries) {
|
|
218
|
-
const binding = ctx.getBinding(entry.bindingId);
|
|
219
|
-
if (!binding) continue;
|
|
220
|
-
await sendAdapterMessage(ctx.adapter, binding, {
|
|
221
|
-
type: 'assistant',
|
|
222
|
-
text: '⚠️ Lost while ticlawk restarted.\n\nThis OpenClaw message was in flight when ticlawk restarted. Please retry your message.',
|
|
223
|
-
media: [],
|
|
224
|
-
replyToMessageId: entry.messageId || null,
|
|
225
|
-
}).catch(() => {});
|
|
226
|
-
}
|
|
227
236
|
return entries.length;
|
|
228
237
|
},
|
|
229
238
|
|
|
@@ -5,42 +5,12 @@
|
|
|
5
5
|
* `agent:<id>:main` session key is derived internally when dispatching.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
9
|
-
import { join } from 'node:path';
|
|
10
|
-
|
|
11
|
-
let cachedOpenClawConfig = null;
|
|
12
|
-
|
|
13
|
-
function loadOpenClawConfig() {
|
|
14
|
-
if (cachedOpenClawConfig !== null) return cachedOpenClawConfig;
|
|
15
|
-
const home = process.env.HOME || '';
|
|
16
|
-
const configPath = home ? join(home, '.openclaw', 'openclaw.json') : '';
|
|
17
|
-
if (!configPath || !existsSync(configPath)) {
|
|
18
|
-
cachedOpenClawConfig = {};
|
|
19
|
-
return cachedOpenClawConfig;
|
|
20
|
-
}
|
|
21
|
-
try {
|
|
22
|
-
cachedOpenClawConfig = JSON.parse(readFileSync(configPath, 'utf8')) || {};
|
|
23
|
-
} catch {
|
|
24
|
-
cachedOpenClawConfig = {};
|
|
25
|
-
}
|
|
26
|
-
return cachedOpenClawConfig;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
8
|
export function normalizeOpenClawAgentId(value) {
|
|
30
9
|
const trimmed = String(value || '').trim().toLowerCase();
|
|
31
10
|
if (!trimmed) return 'main';
|
|
32
11
|
return trimmed.replace(/[^a-z0-9_-]+/g, '-').replace(/^-+|-+$/g, '') || 'main';
|
|
33
12
|
}
|
|
34
13
|
|
|
35
|
-
export function resolveOpenClawWorkspace(agentId) {
|
|
36
|
-
const normalizedId = normalizeOpenClawAgentId(agentId);
|
|
37
|
-
const config = loadOpenClawConfig();
|
|
38
|
-
const agents = Array.isArray(config?.agents?.list) ? config.agents.list : [];
|
|
39
|
-
const matched = agents.find((agent) => normalizeOpenClawAgentId(agent?.id || agent?.name || '') === normalizedId);
|
|
40
|
-
const workspace = String(matched?.workspace || '').trim();
|
|
41
|
-
return workspace || '';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
14
|
export function buildOpenClawSessionKey(agentId = 'main') {
|
|
45
15
|
return `agent:${normalizeOpenClawAgentId(agentId)}:main`;
|
|
46
16
|
}
|