ticlawk 0.1.16-dev.3 → 0.1.16-dev.6
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/package.json +1 -1
- package/src/adapters/ticlawk/index.mjs +79 -60
- package/src/cli/agent-commands.mjs +2 -1
- package/src/core/agent-home.mjs +5 -9
- package/src/migrate/write-initial-memory.mjs +5 -5
- package/src/runtimes/_shared/standing-prompt.mjs +6 -13
- package/src/runtimes/claude-code/index.mjs +21 -99
- package/src/runtimes/codex/index.mjs +1 -1
package/package.json
CHANGED
|
@@ -27,6 +27,32 @@ function connectError(statusCode, error) {
|
|
|
27
27
|
return { statusCode, body: { ok: false, error } };
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
// Coalesce: returns a wrapped fn that runs at most one invocation at a
|
|
31
|
+
// time. If it's called while an invocation is in flight, the most recent
|
|
32
|
+
// args are stashed and the wrapped fn re-runs exactly once after the
|
|
33
|
+
// current run completes (regardless of how many times it was called
|
|
34
|
+
// during the run). The wrapped fn always returns the in-flight Promise.
|
|
35
|
+
function coalesce(fn) {
|
|
36
|
+
let running = null;
|
|
37
|
+
let pendingArgs = null;
|
|
38
|
+
return function call(...args) {
|
|
39
|
+
if (running) {
|
|
40
|
+
pendingArgs = args;
|
|
41
|
+
return running;
|
|
42
|
+
}
|
|
43
|
+
running = (async () => {
|
|
44
|
+
let currentArgs = args;
|
|
45
|
+
while (true) {
|
|
46
|
+
pendingArgs = null;
|
|
47
|
+
await fn(...currentArgs);
|
|
48
|
+
if (pendingArgs === null) return;
|
|
49
|
+
currentArgs = pendingArgs;
|
|
50
|
+
}
|
|
51
|
+
})().finally(() => { running = null; });
|
|
52
|
+
return running;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
30
56
|
function normalizeInboundMediaAssets(msg) {
|
|
31
57
|
if (!Array.isArray(msg?.media_assets)) return [];
|
|
32
58
|
return msg.media_assets
|
|
@@ -119,7 +145,7 @@ function buildGroupContextBlock(msg) {
|
|
|
119
145
|
// runtime LLM never has to remember the standing prompt to figure out
|
|
120
146
|
// HOW to reply. Codex in particular treats the developerInstructions as
|
|
121
147
|
// background and ignores the chat-send pattern without this per-turn
|
|
122
|
-
// nudge
|
|
148
|
+
// nudge.
|
|
123
149
|
function buildWakePromptText({ envelopeHeader, target, rawText, groupContext }) {
|
|
124
150
|
const body = `${envelopeHeader} ${rawText || ''}`.trim();
|
|
125
151
|
const lines = ['New message received:', '', body];
|
|
@@ -149,8 +175,8 @@ export function normalizeInboundMessage(msg) {
|
|
|
149
175
|
const baseHeader = buildEnvelopeHeader(enriched);
|
|
150
176
|
// Task + reactions suffixes are appended INSIDE the envelope so an
|
|
151
177
|
// agent can see at a glance whether a message is already claimed and
|
|
152
|
-
// who has already acknowledged it
|
|
153
|
-
//
|
|
178
|
+
// who has already acknowledged it: `[task #N status=… assignee=…]` +
|
|
179
|
+
// `[reactions: …]`.
|
|
154
180
|
const taskSuffix = buildTaskSuffix(enriched);
|
|
155
181
|
const reactionsSuffix = buildReactionsSuffix(enriched);
|
|
156
182
|
const header = baseHeader + taskSuffix + reactionsSuffix;
|
|
@@ -257,51 +283,46 @@ function normalizeRuntimeVersion(value) {
|
|
|
257
283
|
return Number.isInteger(value) ? Number(value) : 0;
|
|
258
284
|
}
|
|
259
285
|
|
|
260
|
-
// Build a binding from
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
286
|
+
// Build a binding from any source row that carries agent metadata.
|
|
287
|
+
// Two source shapes feed in:
|
|
288
|
+
// /api/agents row — id, service_type, meta, …
|
|
289
|
+
// claim_pending_deliveries — recipient_agent_id, agent_service_type,
|
|
290
|
+
// agent_meta, … (prefixed because the
|
|
291
|
+
// delivery row also carries message fields)
|
|
292
|
+
// We try the prefixed names first, then fall through to the bare names,
|
|
293
|
+
// so one builder handles both without the caller having to remember
|
|
294
|
+
// which shape it has.
|
|
295
|
+
function buildBindingFromSource(source) {
|
|
296
|
+
const agentId = String(
|
|
297
|
+
source?.recipient_agent_id
|
|
298
|
+
|| source?.id
|
|
299
|
+
|| source?.agent_id
|
|
300
|
+
|| ''
|
|
301
|
+
).trim();
|
|
302
|
+
const runtime = source?.agent_service_type || source?.service_type;
|
|
303
|
+
const meta = source?.agent_meta ?? source?.meta;
|
|
304
|
+
const runtimeVersion = source?.agent_runtime_version ?? source?.runtime_version;
|
|
305
|
+
const displayName = source?.agent_display_name
|
|
306
|
+
|| source?.agent_name
|
|
307
|
+
|| source?.display_name
|
|
308
|
+
|| source?.name
|
|
309
|
+
|| agentId;
|
|
310
|
+
const status = source?.agent_status || source?.status || 'connected';
|
|
311
|
+
const hostId = getRuntimeHostIdFromPayload(source) || undefined;
|
|
267
312
|
return {
|
|
268
313
|
id: agentId,
|
|
269
314
|
adapter: 'ticlawk',
|
|
270
315
|
targetKey: agentId,
|
|
271
316
|
targetMeta: { agentId, runtime_host_id: hostId },
|
|
272
317
|
runtime_host_id: hostId,
|
|
273
|
-
runtime_host_label: getRuntimeHostLabelFromPayload(
|
|
318
|
+
runtime_host_label: getRuntimeHostLabelFromPayload(source) || undefined,
|
|
274
319
|
runtime,
|
|
275
320
|
runtimeMeta: {
|
|
276
|
-
...projectRuntimeMeta(
|
|
277
|
-
runtimeVersion: normalizeRuntimeVersion(
|
|
321
|
+
...projectRuntimeMeta(meta),
|
|
322
|
+
runtimeVersion: normalizeRuntimeVersion(runtimeVersion),
|
|
278
323
|
},
|
|
279
|
-
displayName
|
|
280
|
-
status
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Build a binding from a claimed delivery row. Reads
|
|
285
|
-
// recipient_agent_id, agent_service_type, agent_meta, etc. — the
|
|
286
|
-
// claim-payload shape returned by claim_pending_deliveries.
|
|
287
|
-
function buildBindingFromDeliveryRow(msg) {
|
|
288
|
-
const agentId = String(msg?.recipient_agent_id || '').trim();
|
|
289
|
-
const runtime = msg?.agent_service_type;
|
|
290
|
-
const hostId = getRuntimeHostIdFromPayload(msg) || undefined;
|
|
291
|
-
return {
|
|
292
|
-
id: agentId,
|
|
293
|
-
adapter: 'ticlawk',
|
|
294
|
-
targetKey: agentId,
|
|
295
|
-
targetMeta: { agentId, runtime_host_id: hostId },
|
|
296
|
-
runtime_host_id: hostId,
|
|
297
|
-
runtime_host_label: getRuntimeHostLabelFromPayload(msg) || undefined,
|
|
298
|
-
runtime,
|
|
299
|
-
runtimeMeta: {
|
|
300
|
-
...projectRuntimeMeta(msg?.agent_meta),
|
|
301
|
-
runtimeVersion: normalizeRuntimeVersion(msg?.agent_runtime_version),
|
|
302
|
-
},
|
|
303
|
-
displayName: msg?.agent_display_name || msg?.agent_name || agentId,
|
|
304
|
-
status: msg?.agent_status || 'connected',
|
|
324
|
+
displayName,
|
|
325
|
+
status,
|
|
305
326
|
};
|
|
306
327
|
}
|
|
307
328
|
|
|
@@ -456,8 +477,6 @@ export function createTiclawkAdapter(ctx) {
|
|
|
456
477
|
let bindingAuditTimer = null;
|
|
457
478
|
let jobsWakeTimer = null;
|
|
458
479
|
let bindingsWakeTimer = null;
|
|
459
|
-
let drainPromise = null;
|
|
460
|
-
let drainRequested = false;
|
|
461
480
|
let lastJobsWakeAt = 0;
|
|
462
481
|
let lastBindingsWakeAt = 0;
|
|
463
482
|
let updateRequired = null;
|
|
@@ -633,7 +652,7 @@ export function createTiclawkAdapter(ctx) {
|
|
|
633
652
|
continue;
|
|
634
653
|
}
|
|
635
654
|
|
|
636
|
-
const binding = await ctx.persistBinding(
|
|
655
|
+
const binding = await ctx.persistBinding(buildBindingFromSource(msg));
|
|
637
656
|
if (!binding?.runtime) {
|
|
638
657
|
throw new Error('claimed message missing runtime binding');
|
|
639
658
|
}
|
|
@@ -733,7 +752,7 @@ export function createTiclawkAdapter(ctx) {
|
|
|
733
752
|
continue;
|
|
734
753
|
}
|
|
735
754
|
try {
|
|
736
|
-
await ctx.persistBinding(
|
|
755
|
+
await ctx.persistBinding(buildBindingFromSource(agent));
|
|
737
756
|
hydrated += 1;
|
|
738
757
|
} catch (err) {
|
|
739
758
|
debugError('ticlawk', 'binding.hydrate-failed', {
|
|
@@ -883,23 +902,7 @@ export function createTiclawkAdapter(ctx) {
|
|
|
883
902
|
return { totalClaimed, iterations };
|
|
884
903
|
}
|
|
885
904
|
|
|
886
|
-
|
|
887
|
-
if (drainPromise) {
|
|
888
|
-
drainRequested = true;
|
|
889
|
-
return drainPromise;
|
|
890
|
-
}
|
|
891
|
-
drainPromise = (async () => {
|
|
892
|
-
let currentReason = reason;
|
|
893
|
-
do {
|
|
894
|
-
drainRequested = false;
|
|
895
|
-
await runDrain(currentReason);
|
|
896
|
-
currentReason = 'drain.requested-again';
|
|
897
|
-
} while (drainRequested);
|
|
898
|
-
})().finally(() => {
|
|
899
|
-
drainPromise = null;
|
|
900
|
-
});
|
|
901
|
-
return drainPromise;
|
|
902
|
-
}
|
|
905
|
+
const requestDrain = coalesce(runDrain);
|
|
903
906
|
|
|
904
907
|
function scheduleDrain(reason) {
|
|
905
908
|
jobsWakeTimer = clearDebounce(jobsWakeTimer);
|
|
@@ -1357,6 +1360,22 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1357
1360
|
});
|
|
1358
1361
|
},
|
|
1359
1362
|
|
|
1363
|
+
// Daemon-driven impersonated reply: the runtime never reached
|
|
1364
|
+
// `ticlawk message send` (subprocess died, timeout, etc.), so the
|
|
1365
|
+
// daemon speaks for the agent. Used by reportSubprocessFailure
|
|
1366
|
+
// with visibility='admin' so the notice only reaches owners.
|
|
1367
|
+
async postAgentReply(binding, { conversationId, text, replyToMessageId, visibility }) {
|
|
1368
|
+
if (!conversationId || !text) return null;
|
|
1369
|
+
return api.sendAgentMessage({
|
|
1370
|
+
actingAgentId: binding.id,
|
|
1371
|
+
conversationId,
|
|
1372
|
+
text,
|
|
1373
|
+
replyToMessageId: replyToMessageId || null,
|
|
1374
|
+
runtimeHostId: hostId,
|
|
1375
|
+
visibility: visibility || null,
|
|
1376
|
+
});
|
|
1377
|
+
},
|
|
1378
|
+
|
|
1360
1379
|
async emitEvent(binding, payload) {
|
|
1361
1380
|
if (!payload?.event) return;
|
|
1362
1381
|
await api.postEvent({
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
* ticlawk server info [--refresh]
|
|
18
18
|
*
|
|
19
19
|
* `ticlawk message send` reads the message body from stdin so heredocs
|
|
20
|
-
* work cleanly
|
|
20
|
+
* work cleanly — quotes, backticks, and code blocks survive without
|
|
21
|
+
* shell-quoting gymnastics.
|
|
21
22
|
*/
|
|
22
23
|
|
|
23
24
|
import { readFileSync, statSync, writeFileSync } from 'node:fs';
|
package/src/core/agent-home.mjs
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-agent
|
|
2
|
+
* Per-agent home directory: ~/.ticlawk/agents/<agent_id>/
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Following slock's daemon model (dist/chunk-M4A5QPUN.js:5079) —
|
|
9
|
-
* dataDir = ~/.slock/agents, agentDataDir = <dataDir>/<id>, and every
|
|
10
|
-
* driver.spawn() receives `workingDirectory: agentDataDir`. One MEMORY.md
|
|
11
|
-
* per agent, lives in cwd, agent reads it via `cat MEMORY.md`.
|
|
4
|
+
* The agent's authoritative workspace. The daemon spawns every runtime
|
|
5
|
+
* with this as cwd; the agent's MEMORY.md, notes/, and any artifacts
|
|
6
|
+
* it produces live here. No project binding — one MEMORY.md per agent,
|
|
7
|
+
* lives in cwd, agent reads it via `cat MEMORY.md`.
|
|
12
8
|
*/
|
|
13
9
|
|
|
14
10
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Seed the
|
|
3
|
-
//
|
|
2
|
+
// Seed the per-agent home + MEMORY.md for every paired agent in the
|
|
3
|
+
// linked Supabase project.
|
|
4
4
|
//
|
|
5
5
|
// ~/.ticlawk/agents/<agent_id>/MEMORY.md
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
7
|
+
// Replaces the Phase-B variant that wrote MEMORY.md into each agent's
|
|
8
|
+
// project workdir. Each agent now has its own home dir as cwd, with
|
|
9
|
+
// MEMORY.md living at the root of that home.
|
|
10
10
|
//
|
|
11
11
|
// Usage:
|
|
12
12
|
// node src/migrate/write-initial-memory.mjs # dry-run
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Standing prompt injected into every runtime turn.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* (
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* channel → group
|
|
11
|
-
* command surface trimmed to what Ticlawk actually exposes today
|
|
12
|
-
*
|
|
13
|
-
* Adapted sections deliberately preserved verbatim where they encode the
|
|
14
|
-
* etiquette rules that make multi-agent coordination work without
|
|
15
|
-
* runtime-level orchestration. Trim with care; this prompt has been
|
|
16
|
-
* field-calibrated upstream.
|
|
4
|
+
* Encodes the agent's communication contract: how to receive messages,
|
|
5
|
+
* how to reply (always via the `ticlawk` CLI), how to handle ambient
|
|
6
|
+
* vs mention traffic, task lifecycle, and the per-agent home-dir +
|
|
7
|
+
* MEMORY.md workspace convention. Trim with care — the etiquette
|
|
8
|
+
* sections are load-bearing for multi-agent coordination without
|
|
9
|
+
* runtime-level orchestration.
|
|
17
10
|
*/
|
|
18
11
|
|
|
19
12
|
const STANDING_PROMPT = `You are an agent in Ticlawk — a collaborative platform for human-AI
|
|
@@ -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,
|
|
@@ -28,7 +27,6 @@ import { normalizeOutboundMedia } from '../../core/media/outbound.mjs';
|
|
|
28
27
|
import { emitWorkerEvent } from '../../core/events/worker-events.mjs';
|
|
29
28
|
import {
|
|
30
29
|
shouldStreamRuntime,
|
|
31
|
-
sendAdapterMessage,
|
|
32
30
|
recordActivity,
|
|
33
31
|
reportSubprocessFailure,
|
|
34
32
|
terminalRuntimeFailure,
|
|
@@ -38,15 +36,9 @@ import {
|
|
|
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,37 @@ 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
|
|
|
135
|
+
// shouldRotate=true means meta.sessionId is missing or invalidated.
|
|
136
|
+
// We pass sessionId=null so `claude` creates a fresh session; the new
|
|
137
|
+
// session_id is captured from stream events and persisted below.
|
|
138
|
+
// Unifying rotate + non-rotate into one path means the standing prompt
|
|
139
|
+
// is always attached, so the agent uses the CLI to reply on every
|
|
140
|
+
// turn — including the first.
|
|
144
141
|
const shouldRotate = !meta.sessionId || meta.rotatePending;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
adapter,
|
|
148
|
-
binding,
|
|
149
|
-
agent: this.name,
|
|
150
|
-
sessionId: sessionId || binding.id,
|
|
151
|
-
cwd: projectDir,
|
|
152
|
-
replyToMessageId: inbound.messageId || null,
|
|
153
|
-
event: {
|
|
154
|
-
hook_event_name: 'worker.turn.start',
|
|
155
|
-
worker_event_name: 'worker.turn.start',
|
|
156
|
-
},
|
|
157
|
-
logger: ctx.logger,
|
|
158
|
-
});
|
|
159
|
-
try {
|
|
160
|
-
const claudePath = requireClaudePath(runtimeClaudePath);
|
|
161
|
-
const claudeVersion = getClaudeCodeRuntimeHealth(claudePath).version || meta.claudeVersion || null;
|
|
162
|
-
const created = await this.createSession({ projectDir, text: message, claudePath });
|
|
163
|
-
const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
|
|
164
|
-
sessionId: created.sessionId,
|
|
165
|
-
path: null,
|
|
166
|
-
runtimePath: claudePath,
|
|
167
|
-
claudePath,
|
|
168
|
-
claudeVersion,
|
|
169
|
-
rotatePending: false,
|
|
170
|
-
lastRotatedAt: new Date().toISOString(),
|
|
171
|
-
}, { status: 'connected' });
|
|
172
|
-
if (created.resultText && created.resultText.trim()) {
|
|
173
|
-
await sendAdapterMessage(adapter, nextBinding, {
|
|
174
|
-
type: 'assistant',
|
|
175
|
-
text: created.resultText,
|
|
176
|
-
media: [],
|
|
177
|
-
replyToMessageId: inbound.messageId || null,
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
await emitWorkerEvent({
|
|
181
|
-
adapter,
|
|
182
|
-
binding: nextBinding,
|
|
183
|
-
agent: this.name,
|
|
184
|
-
sessionId: created.sessionId,
|
|
185
|
-
cwd: projectDir,
|
|
186
|
-
replyToMessageId: inbound.messageId || null,
|
|
187
|
-
event: {
|
|
188
|
-
hook_event_name: 'Stop',
|
|
189
|
-
worker_event_name: 'worker.turn.complete',
|
|
190
|
-
},
|
|
191
|
-
logger: ctx.logger,
|
|
192
|
-
});
|
|
193
|
-
return true;
|
|
194
|
-
} catch (err) {
|
|
195
|
-
await emitWorkerEvent({
|
|
196
|
-
adapter,
|
|
197
|
-
binding,
|
|
198
|
-
agent: this.name,
|
|
199
|
-
sessionId: sessionId || binding.id,
|
|
200
|
-
cwd: projectDir,
|
|
201
|
-
replyToMessageId: inbound.messageId || null,
|
|
202
|
-
event: {
|
|
203
|
-
hook_event_name: 'worker.turn.error',
|
|
204
|
-
worker_event_name: 'worker.turn.error',
|
|
205
|
-
error: err?.message || 'Claude Code failed',
|
|
206
|
-
},
|
|
207
|
-
logger: ctx.logger,
|
|
208
|
-
});
|
|
209
|
-
await reportSubprocessFailure({
|
|
210
|
-
adapter,
|
|
211
|
-
binding,
|
|
212
|
-
inbound,
|
|
213
|
-
runtimeName: 'Claude Code',
|
|
214
|
-
info: err?.info || {
|
|
215
|
-
ok: false,
|
|
216
|
-
kind: 'exit-error',
|
|
217
|
-
errorMessage: err?.message || 'Claude Code failed',
|
|
218
|
-
durationMs: 0,
|
|
219
|
-
},
|
|
220
|
-
});
|
|
221
|
-
return terminalRuntimeFailure(err?.message || 'Claude Code failed');
|
|
222
|
-
}
|
|
223
|
-
}
|
|
142
|
+
const targetSessionId = shouldRotate ? null : meta.sessionId;
|
|
143
|
+
const errEventSessionId = meta.sessionId || binding.id;
|
|
224
144
|
|
|
225
145
|
try {
|
|
226
146
|
const claudePath = requireClaudePath(runtimeClaudePath);
|
|
227
147
|
const claudeVersion = getClaudeCodeRuntimeHealth(claudePath).version || meta.claudeVersion || null;
|
|
228
148
|
const agentEnv = buildAgentRuntimeEnv({
|
|
229
149
|
agentId: binding.id,
|
|
230
|
-
sessionId,
|
|
150
|
+
sessionId: meta.sessionId,
|
|
231
151
|
hostId: binding.runtime_host_id,
|
|
232
152
|
});
|
|
233
153
|
const appendSystemPrompt = buildStandingPrompt({ agentId: binding.id });
|
|
234
154
|
const result = shouldStreamRuntime(this.name, this)
|
|
235
|
-
? await this.runTurnStream({ sessionId, projectDir, claudePath, agentEnv }, message, {
|
|
155
|
+
? await this.runTurnStream({ sessionId: targetSessionId, projectDir, claudePath, agentEnv }, message, {
|
|
236
156
|
appendSystemPrompt,
|
|
237
157
|
onEvent: async (event) => {
|
|
238
158
|
if (event?.type === 'turn.started') {
|
|
@@ -240,7 +160,7 @@ export const claudeCodeRuntime = {
|
|
|
240
160
|
adapter,
|
|
241
161
|
binding,
|
|
242
162
|
agent: this.name,
|
|
243
|
-
sessionId: event.sessionId ||
|
|
163
|
+
sessionId: event.sessionId || targetSessionId || binding.id,
|
|
244
164
|
cwd: projectDir,
|
|
245
165
|
replyToMessageId: inbound.messageId || null,
|
|
246
166
|
event: {
|
|
@@ -254,7 +174,7 @@ export const claudeCodeRuntime = {
|
|
|
254
174
|
adapter,
|
|
255
175
|
binding,
|
|
256
176
|
agent: this.name,
|
|
257
|
-
sessionId: event.sessionId ||
|
|
177
|
+
sessionId: event.sessionId || targetSessionId || binding.id,
|
|
258
178
|
cwd: projectDir,
|
|
259
179
|
replyToMessageId: inbound.messageId || null,
|
|
260
180
|
event: {
|
|
@@ -267,12 +187,14 @@ export const claudeCodeRuntime = {
|
|
|
267
187
|
}
|
|
268
188
|
},
|
|
269
189
|
})
|
|
270
|
-
: await this.runTurn({ sessionId, projectDir, claudePath, agentEnv }, message, { appendSystemPrompt });
|
|
190
|
+
: await this.runTurn({ sessionId: targetSessionId, projectDir, claudePath, agentEnv }, message, { appendSystemPrompt });
|
|
271
191
|
const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
|
|
272
192
|
sessionId: result?.sessionId || meta.sessionId,
|
|
273
193
|
runtimePath: claudePath,
|
|
274
194
|
claudePath,
|
|
275
195
|
claudeVersion,
|
|
196
|
+
rotatePending: false,
|
|
197
|
+
lastRotatedAt: shouldRotate ? new Date().toISOString() : meta.lastRotatedAt,
|
|
276
198
|
}, { status: 'connected' });
|
|
277
199
|
await recordActivity(adapter, nextBinding, inbound, {
|
|
278
200
|
...result,
|
|
@@ -282,7 +204,7 @@ export const claudeCodeRuntime = {
|
|
|
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: {
|
|
@@ -130,7 +130,7 @@ export const codexRuntime = {
|
|
|
130
130
|
if (!binding) return false;
|
|
131
131
|
const adapter = ctx.adapter;
|
|
132
132
|
const meta = binding.runtimeMeta || {};
|
|
133
|
-
//
|
|
133
|
+
// cwd is the per-agent home dir under ~/.ticlawk/agents/<id>/.
|
|
134
134
|
// ensureAgentHome creates it + seeds MEMORY.md if missing.
|
|
135
135
|
const agentHome = ensureAgentHome(binding.id, {
|
|
136
136
|
displayName: binding.display_name || binding.name || null,
|