ticlawk 0.1.16-dev.3 → 0.1.16-dev.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -2
- package/bin/ticlawk.mjs +207 -25
- package/package.json +1 -1
- package/src/adapters/ticlawk/api.mjs +232 -23
- package/src/adapters/ticlawk/credentials.mjs +41 -1
- package/src/adapters/ticlawk/index.mjs +196 -195
- package/src/adapters/ticlawk/wake-client.mjs +1 -1
- package/src/cli/agent-commands.mjs +607 -37
- package/src/core/agent-cli-handlers.mjs +449 -20
- package/src/core/agent-home.mjs +86 -10
- package/src/core/argv.mjs +11 -1
- package/src/core/http.mjs +126 -0
- package/src/core/runtime-env.mjs +7 -0
- package/src/core/runtime-support.mjs +101 -30
- package/src/migrate/write-initial-memory.mjs +5 -5
- package/src/runtimes/_shared/agent-handbook.mjs +45 -0
- package/src/runtimes/_shared/brand.mjs +2 -0
- package/src/runtimes/_shared/goal-task-protocol.mjs +50 -0
- package/src/runtimes/_shared/handbook/BASICS.md +27 -0
- package/src/runtimes/_shared/handbook/COLLABORATION.md +37 -0
- package/src/runtimes/_shared/handbook/COMMUNICATION.md +48 -0
- package/src/runtimes/_shared/handbook/DM_SCOPE.md +13 -0
- package/src/runtimes/_shared/handbook/GOAL_AUTHORITY.md +43 -0
- package/src/runtimes/_shared/handbook/GOAL_TASK_CORE.md +43 -0
- package/src/runtimes/_shared/handbook/GROUP_ADMIN_SCOPE.md +21 -0
- package/src/runtimes/_shared/handbook/GROUP_MEMBER_SCOPE.md +15 -0
- package/src/runtimes/_shared/handbook/SURFACES.md +41 -0
- package/src/runtimes/_shared/handbook/TASK_WORKER.md +14 -0
- package/src/runtimes/_shared/standing-prompt.mjs +111 -264
- package/src/runtimes/_shared/wake-prompt.mjs +261 -0
- package/src/runtimes/claude-code/index.mjs +30 -108
- package/src/runtimes/codex/index.mjs +114 -23
- package/src/runtimes/openclaw/index.mjs +16 -26
- package/src/runtimes/opencode/index.mjs +42 -36
- package/src/runtimes/opencode/session.mjs +5 -4
- package/src/runtimes/pi/index.mjs +39 -31
- package/src/runtimes/pi/session.mjs +5 -2
- package/src/adapters/ticlawk/cards.mjs +0 -149
- package/src/core/media/outbound.mjs +0 -163
|
@@ -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,98 @@ import {
|
|
|
18
21
|
requireCodexPath,
|
|
19
22
|
} from './session.mjs';
|
|
20
23
|
import { buildCodexInputFromInbound } from '../../core/media/inbound.mjs';
|
|
21
|
-
import { normalizeOutboundMedia } from '../../core/media/outbound.mjs';
|
|
22
24
|
import { emitWorkerEvent } from '../../core/events/worker-events.mjs';
|
|
23
25
|
import {
|
|
24
26
|
shouldStreamRuntime,
|
|
25
27
|
createDeltaAggregator,
|
|
26
|
-
sendAdapterMessage,
|
|
27
|
-
recordActivity,
|
|
28
28
|
reportSubprocessFailure,
|
|
29
29
|
terminalRuntimeFailure,
|
|
30
30
|
updateBindingRuntimeMeta,
|
|
31
31
|
isRuntimeGatewayFailure,
|
|
32
|
+
resolveRuntimeSessionScope,
|
|
33
|
+
buildRuntimeSessionMetaPatch,
|
|
32
34
|
} from '../../core/runtime-support.mjs';
|
|
33
35
|
import { buildAgentRuntimeEnv } from '../../core/runtime-env.mjs';
|
|
34
36
|
import { buildStandingPrompt } from '../_shared/standing-prompt.mjs';
|
|
35
37
|
import { ensureAgentHome } from '../../core/agent-home.mjs';
|
|
38
|
+
import { debugError, debugLog } from '../../core/logger.mjs';
|
|
39
|
+
|
|
40
|
+
function parseBooleanish(value) {
|
|
41
|
+
if (value === undefined || value === null || value === '') return false;
|
|
42
|
+
return ['1', 'true', 'on', 'yes'].includes(String(value).trim().toLowerCase());
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function sha256(text) {
|
|
46
|
+
return createHash('sha256').update(String(text || ''), 'utf8').digest('hex');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function fileSafeId(value) {
|
|
50
|
+
return String(value || 'unknown')
|
|
51
|
+
.replace(/[^a-zA-Z0-9_-]+/g, '-')
|
|
52
|
+
.replace(/^-+|-+$/g, '')
|
|
53
|
+
.slice(0, 80) || 'unknown';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function writePromptSnapshot({
|
|
57
|
+
binding,
|
|
58
|
+
inbound,
|
|
59
|
+
agentHome,
|
|
60
|
+
targetSessionId,
|
|
61
|
+
shouldRotate,
|
|
62
|
+
developerInstructions,
|
|
63
|
+
message,
|
|
64
|
+
input,
|
|
65
|
+
}) {
|
|
66
|
+
if (!parseBooleanish(process.env.TICLAWK_LOG_RUNTIME_PROMPTS)) return;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const rootDir = process.env.TICLAWK_RUNTIME_PROMPT_LOG_DIR
|
|
70
|
+
|| join(homedir(), '.ticlawk', 'prompt-logs');
|
|
71
|
+
const dir = join(rootDir, fileSafeId(binding?.id));
|
|
72
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
73
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
74
|
+
const messageId = fileSafeId(inbound?.messageId);
|
|
75
|
+
const filePath = join(dir, `${stamp}-${messageId}.json`);
|
|
76
|
+
const payload = {
|
|
77
|
+
createdAt: new Date().toISOString(),
|
|
78
|
+
runtime: 'codex',
|
|
79
|
+
agentId: binding?.id || null,
|
|
80
|
+
conversationId: inbound?.conversationId || null,
|
|
81
|
+
messageId: inbound?.messageId || null,
|
|
82
|
+
target: inbound?.envelopeTarget || null,
|
|
83
|
+
action: inbound?.action || null,
|
|
84
|
+
sessionId: targetSessionId || null,
|
|
85
|
+
shouldRotate: Boolean(shouldRotate),
|
|
86
|
+
agentHome,
|
|
87
|
+
hashes: {
|
|
88
|
+
developerInstructionsSha256: sha256(developerInstructions),
|
|
89
|
+
messageSha256: sha256(message),
|
|
90
|
+
},
|
|
91
|
+
developerInstructions,
|
|
92
|
+
message,
|
|
93
|
+
input: input || null,
|
|
94
|
+
};
|
|
95
|
+
writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, { mode: 0o600 });
|
|
96
|
+
debugLog('codex', 'prompt.snapshot', {
|
|
97
|
+
agentId: binding?.id,
|
|
98
|
+
conversationId: inbound?.conversationId,
|
|
99
|
+
messageId: inbound?.messageId,
|
|
100
|
+
target: inbound?.envelopeTarget,
|
|
101
|
+
path: filePath,
|
|
102
|
+
developerInstructionChars: String(developerInstructions || '').length,
|
|
103
|
+
messageChars: String(message || '').length,
|
|
104
|
+
developerInstructionsSha256: payload.hashes.developerInstructionsSha256,
|
|
105
|
+
messageSha256: payload.hashes.messageSha256,
|
|
106
|
+
});
|
|
107
|
+
} catch (err) {
|
|
108
|
+
debugError('codex', 'prompt.snapshot.failed', {
|
|
109
|
+
agentId: binding?.id,
|
|
110
|
+
conversationId: inbound?.conversationId,
|
|
111
|
+
messageId: inbound?.messageId,
|
|
112
|
+
error: err?.message || String(err),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
36
116
|
|
|
37
117
|
export const codexRuntime = {
|
|
38
118
|
name: 'codex',
|
|
@@ -130,7 +210,7 @@ export const codexRuntime = {
|
|
|
130
210
|
if (!binding) return false;
|
|
131
211
|
const adapter = ctx.adapter;
|
|
132
212
|
const meta = binding.runtimeMeta || {};
|
|
133
|
-
//
|
|
213
|
+
// cwd is the per-agent home dir under ~/.ticlawk/agents/<id>/.
|
|
134
214
|
// ensureAgentHome creates it + seeds MEMORY.md if missing.
|
|
135
215
|
const agentHome = ensureAgentHome(binding.id, {
|
|
136
216
|
displayName: binding.display_name || binding.name || null,
|
|
@@ -143,14 +223,16 @@ export const codexRuntime = {
|
|
|
143
223
|
? (codexInput.find((item) => item?.type === 'text')?.text || inbound.text || '(image attached)')
|
|
144
224
|
: inbound.text;
|
|
145
225
|
|
|
146
|
-
const
|
|
226
|
+
const sessionScope = resolveRuntimeSessionScope(meta, inbound);
|
|
227
|
+
const shouldRotate = sessionScope.shouldRotate;
|
|
228
|
+
const targetSessionId = shouldRotate ? null : sessionScope.sessionId;
|
|
147
229
|
const deltaAggregator = createDeltaAggregator({
|
|
148
230
|
flushDelta: async ({ text, sessionId, turnId, cwd }) => {
|
|
149
231
|
await emitWorkerEvent({
|
|
150
232
|
adapter,
|
|
151
233
|
binding,
|
|
152
234
|
agent: this.name,
|
|
153
|
-
sessionId: sessionId ||
|
|
235
|
+
sessionId: sessionId || targetSessionId || binding.id,
|
|
154
236
|
turnId: turnId || null,
|
|
155
237
|
cwd: cwd || agentHome,
|
|
156
238
|
replyToMessageId: inbound.messageId || null,
|
|
@@ -168,12 +250,25 @@ export const codexRuntime = {
|
|
|
168
250
|
const runtimeCodexVersion = getCodexRuntimeHealth(runtimeCodexPath).version || meta.codexVersion || null;
|
|
169
251
|
const agentEnv = buildAgentRuntimeEnv({
|
|
170
252
|
agentId: binding.id,
|
|
171
|
-
sessionId:
|
|
253
|
+
sessionId: targetSessionId,
|
|
172
254
|
hostId: binding.runtime_host_id,
|
|
255
|
+
conversationId: inbound.conversationId,
|
|
256
|
+
messageId: inbound.messageId,
|
|
257
|
+
target: inbound.envelopeTarget,
|
|
258
|
+
});
|
|
259
|
+
const developerInstructions = buildStandingPrompt({ agentId: binding.id, inbound });
|
|
260
|
+
writePromptSnapshot({
|
|
261
|
+
binding,
|
|
262
|
+
inbound,
|
|
263
|
+
agentHome,
|
|
264
|
+
targetSessionId,
|
|
265
|
+
shouldRotate,
|
|
266
|
+
developerInstructions,
|
|
267
|
+
message,
|
|
268
|
+
input: codexInput,
|
|
173
269
|
});
|
|
174
|
-
const developerInstructions = buildStandingPrompt({ agentId: binding.id });
|
|
175
270
|
const result = (shouldRotate || inbound.action === 'image' || shouldStreamRuntime(this.name, this))
|
|
176
|
-
? await this.runTurnStream({ sessionId:
|
|
271
|
+
? await this.runTurnStream({ sessionId: targetSessionId, cwd: agentHome, codexPath: runtimeCodexPath, agentEnv }, message, {
|
|
177
272
|
input: codexInput,
|
|
178
273
|
developerInstructions,
|
|
179
274
|
onEvent: async (event) => {
|
|
@@ -182,7 +277,7 @@ export const codexRuntime = {
|
|
|
182
277
|
adapter,
|
|
183
278
|
binding,
|
|
184
279
|
agent: this.name,
|
|
185
|
-
sessionId: event.sessionId ||
|
|
280
|
+
sessionId: event.sessionId || targetSessionId || binding.id,
|
|
186
281
|
turnId: event.turnId || null,
|
|
187
282
|
cwd: agentHome,
|
|
188
283
|
replyToMessageId: inbound.messageId || null,
|
|
@@ -194,36 +289,32 @@ export const codexRuntime = {
|
|
|
194
289
|
});
|
|
195
290
|
} else if (event?.type === 'message.delta' && event.text) {
|
|
196
291
|
deltaAggregator.push(event.text, {
|
|
197
|
-
sessionId: event.sessionId ||
|
|
292
|
+
sessionId: event.sessionId || targetSessionId || binding.id,
|
|
198
293
|
turnId: event.turnId || null,
|
|
199
294
|
cwd: agentHome,
|
|
200
295
|
});
|
|
201
296
|
}
|
|
202
297
|
},
|
|
203
298
|
})
|
|
204
|
-
: await this.runTurn({ sessionId:
|
|
299
|
+
: await this.runTurn({ sessionId: targetSessionId, cwd: agentHome, codexPath: runtimeCodexPath, agentEnv }, message, {
|
|
205
300
|
input: codexInput,
|
|
206
301
|
});
|
|
207
302
|
|
|
208
303
|
await deltaAggregator.flush();
|
|
209
304
|
const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
|
|
210
|
-
|
|
211
|
-
|
|
305
|
+
...buildRuntimeSessionMetaPatch(meta, sessionScope, {
|
|
306
|
+
sessionId: result?.sessionId,
|
|
307
|
+
path: result?.path,
|
|
308
|
+
}),
|
|
212
309
|
runtimePath: runtimeCodexPath,
|
|
213
310
|
codexPath: runtimeCodexPath,
|
|
214
311
|
codexVersion: runtimeCodexVersion,
|
|
215
|
-
rotatePending: false,
|
|
216
|
-
lastRotatedAt: shouldRotate ? new Date().toISOString() : meta.lastRotatedAt,
|
|
217
312
|
}, { status: 'connected' });
|
|
218
|
-
await recordActivity(adapter, nextBinding, inbound, {
|
|
219
|
-
...result,
|
|
220
|
-
media: normalizeOutboundMedia(result),
|
|
221
|
-
});
|
|
222
313
|
await emitWorkerEvent({
|
|
223
314
|
adapter,
|
|
224
315
|
binding: nextBinding,
|
|
225
316
|
agent: this.name,
|
|
226
|
-
sessionId: result?.sessionId ||
|
|
317
|
+
sessionId: result?.sessionId || targetSessionId || binding.id,
|
|
227
318
|
turnId: result?.turnId || null,
|
|
228
319
|
cwd: result?.cwd || agentHome,
|
|
229
320
|
replyToMessageId: inbound.messageId || null,
|
|
@@ -254,7 +345,7 @@ export const codexRuntime = {
|
|
|
254
345
|
adapter,
|
|
255
346
|
binding: failureBinding,
|
|
256
347
|
agent: this.name,
|
|
257
|
-
sessionId:
|
|
348
|
+
sessionId: targetSessionId || binding.id,
|
|
258
349
|
turnId: failureInfo?.turnId || null,
|
|
259
350
|
cwd: agentHome,
|
|
260
351
|
replyToMessageId: inbound.messageId || null,
|
|
@@ -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
|
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { existsSync } from 'node:fs';
|
|
11
11
|
import { basename } from 'node:path';
|
|
12
12
|
import { buildAgentRuntimeEnv } from '../../core/runtime-env.mjs';
|
|
13
|
+
import { getGoalTaskProtocolOverlayKey } from '../_shared/goal-task-protocol.mjs';
|
|
13
14
|
import { buildStandingPrompt } from '../_shared/standing-prompt.mjs';
|
|
14
15
|
import { ensureAgentHome } from '../../core/agent-home.mjs';
|
|
15
16
|
import {
|
|
@@ -24,18 +25,19 @@ import {
|
|
|
24
25
|
requireOpenCodePath,
|
|
25
26
|
} from './session.mjs';
|
|
26
27
|
import { buildOpenCodeInputFromInbound } from '../../core/media/inbound.mjs';
|
|
27
|
-
import { normalizeOutboundMedia } from '../../core/media/outbound.mjs';
|
|
28
28
|
import { emitWorkerEvent } from '../../core/events/worker-events.mjs';
|
|
29
29
|
import {
|
|
30
30
|
shouldStreamRuntime,
|
|
31
31
|
createDeltaAggregator,
|
|
32
|
-
sendAdapterMessage,
|
|
33
|
-
recordActivity,
|
|
34
32
|
reportSubprocessFailure,
|
|
35
33
|
terminalRuntimeFailure,
|
|
36
34
|
updateBindingRuntimeMeta,
|
|
35
|
+
resolveRuntimeSessionScope,
|
|
36
|
+
buildRuntimeSessionMetaPatch,
|
|
37
37
|
} from '../../core/runtime-support.mjs';
|
|
38
38
|
|
|
39
|
+
const standingPromptSeen = new Set();
|
|
40
|
+
|
|
39
41
|
export const openCodeRuntime = {
|
|
40
42
|
name: 'opencode',
|
|
41
43
|
|
|
@@ -51,6 +53,7 @@ export const openCodeRuntime = {
|
|
|
51
53
|
opencodePath,
|
|
52
54
|
agentEnv,
|
|
53
55
|
standingPrompt: opts.standingPrompt || null,
|
|
56
|
+
forceStandingPrompt: Boolean(opts.forceStandingPrompt),
|
|
54
57
|
files: opts.files,
|
|
55
58
|
timeoutMs: opts.timeoutMs,
|
|
56
59
|
});
|
|
@@ -64,6 +67,7 @@ export const openCodeRuntime = {
|
|
|
64
67
|
opencodePath,
|
|
65
68
|
agentEnv,
|
|
66
69
|
standingPrompt: opts.standingPrompt || null,
|
|
70
|
+
forceStandingPrompt: Boolean(opts.forceStandingPrompt),
|
|
67
71
|
files: opts.files,
|
|
68
72
|
timeoutMs: opts.timeoutMs,
|
|
69
73
|
onEvent: opts.onEvent,
|
|
@@ -146,30 +150,22 @@ export const openCodeRuntime = {
|
|
|
146
150
|
const captionText = (inbound.text || '').trim();
|
|
147
151
|
|
|
148
152
|
if (files.length === 0 && !captionText) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
replyToMessageId: inbound.messageId || null,
|
|
154
|
-
});
|
|
153
|
+
// Image decode failed and no caption to fall back on — we have
|
|
154
|
+
// nothing meaningful to feed the model. Bail without a user
|
|
155
|
+
// notice; this runtime is non-primary and the dead chat-projection
|
|
156
|
+
// path that used to surface such notices is gone.
|
|
155
157
|
return true;
|
|
156
158
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
// Downloads all failed; tell the user we're proceeding with the caption alone.
|
|
160
|
-
await sendAdapterMessage(adapter, binding, {
|
|
161
|
-
type: 'assistant',
|
|
162
|
-
text: '⚠️ Could not access the attached image data; acting on the caption text only.',
|
|
163
|
-
media: [],
|
|
164
|
-
replyToMessageId: inbound.messageId || null,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
159
|
+
// If files.length === 0 && captionText, fall through with the
|
|
160
|
+
// caption-only message below — no inline user notice.
|
|
167
161
|
|
|
168
162
|
// If user sent images with no caption, give the model a minimal
|
|
169
163
|
// instruction so it has something to anchor on.
|
|
170
164
|
message = captionText || 'Please analyze the attached image(s).';
|
|
171
165
|
}
|
|
172
|
-
const
|
|
166
|
+
const sessionScope = resolveRuntimeSessionScope(meta, inbound);
|
|
167
|
+
const shouldRotate = sessionScope.shouldRotate;
|
|
168
|
+
const targetSessionId = shouldRotate ? null : sessionScope.sessionId;
|
|
173
169
|
|
|
174
170
|
const deltaAggregator = createDeltaAggregator({
|
|
175
171
|
flushDelta: async ({ text, sessionId, cwd }) => {
|
|
@@ -177,7 +173,7 @@ export const openCodeRuntime = {
|
|
|
177
173
|
adapter,
|
|
178
174
|
binding,
|
|
179
175
|
agent: this.name,
|
|
180
|
-
sessionId: sessionId ||
|
|
176
|
+
sessionId: sessionId || targetSessionId || binding.id,
|
|
181
177
|
cwd: cwd || agentHome,
|
|
182
178
|
replyToMessageId: inbound.messageId || null,
|
|
183
179
|
event: {
|
|
@@ -195,13 +191,22 @@ export const openCodeRuntime = {
|
|
|
195
191
|
const opencodeVersion = getOpenCodeRuntimeHealth(opencodePath).version || meta.opencodeVersion || null;
|
|
196
192
|
const agentEnv = buildAgentRuntimeEnv({
|
|
197
193
|
agentId: binding.id,
|
|
198
|
-
sessionId:
|
|
194
|
+
sessionId: targetSessionId,
|
|
199
195
|
hostId: binding.runtime_host_id,
|
|
196
|
+
conversationId: inbound.conversationId,
|
|
197
|
+
messageId: inbound.messageId,
|
|
198
|
+
target: inbound.envelopeTarget,
|
|
200
199
|
});
|
|
201
|
-
const standingPrompt = buildStandingPrompt({ agentId: binding.id });
|
|
200
|
+
const standingPrompt = buildStandingPrompt({ agentId: binding.id, inbound });
|
|
201
|
+
const protocolOverlayKey = getGoalTaskProtocolOverlayKey({ agentId: binding.id, inbound });
|
|
202
|
+
const standingPromptSeenKey = targetSessionId
|
|
203
|
+
? `${binding.id}|${targetSessionId}|${protocolOverlayKey}`
|
|
204
|
+
: null;
|
|
205
|
+
const forceStandingPrompt = Boolean(standingPromptSeenKey && !standingPromptSeen.has(standingPromptSeenKey));
|
|
202
206
|
const result = shouldStreamRuntime(this.name, this)
|
|
203
|
-
? await this.runTurnStream({ sessionId:
|
|
207
|
+
? await this.runTurnStream({ sessionId: targetSessionId, cwd: agentHome, opencodePath, agentEnv }, message, {
|
|
204
208
|
standingPrompt,
|
|
209
|
+
forceStandingPrompt,
|
|
205
210
|
files,
|
|
206
211
|
onEvent: async (event) => {
|
|
207
212
|
if (event?.type === 'turn.started') {
|
|
@@ -209,7 +214,7 @@ export const openCodeRuntime = {
|
|
|
209
214
|
adapter,
|
|
210
215
|
binding,
|
|
211
216
|
agent: this.name,
|
|
212
|
-
sessionId: event.sessionId ||
|
|
217
|
+
sessionId: event.sessionId || targetSessionId || binding.id,
|
|
213
218
|
cwd: agentHome,
|
|
214
219
|
replyToMessageId: inbound.messageId || null,
|
|
215
220
|
event: {
|
|
@@ -220,33 +225,34 @@ export const openCodeRuntime = {
|
|
|
220
225
|
});
|
|
221
226
|
} else if (event?.type === 'message.delta' && event.text) {
|
|
222
227
|
deltaAggregator.push(event.text, {
|
|
223
|
-
sessionId: event.sessionId ||
|
|
228
|
+
sessionId: event.sessionId || targetSessionId || binding.id,
|
|
224
229
|
cwd: agentHome,
|
|
225
230
|
});
|
|
226
231
|
}
|
|
227
232
|
},
|
|
228
233
|
})
|
|
229
|
-
: await this.runTurn({ sessionId:
|
|
234
|
+
: await this.runTurn({ sessionId: targetSessionId, cwd: agentHome, opencodePath, agentEnv }, message, { files, standingPrompt, forceStandingPrompt });
|
|
230
235
|
|
|
231
236
|
await deltaAggregator.flush();
|
|
237
|
+
const observedSessionId = result?.sessionId || targetSessionId;
|
|
238
|
+
if (observedSessionId) {
|
|
239
|
+
standingPromptSeen.add(`${binding.id}|${observedSessionId}|${protocolOverlayKey}`);
|
|
240
|
+
}
|
|
232
241
|
|
|
233
242
|
const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
|
|
234
|
-
|
|
243
|
+
...buildRuntimeSessionMetaPatch(meta, sessionScope, {
|
|
244
|
+
sessionId: result?.sessionId,
|
|
245
|
+
path: result?.path,
|
|
246
|
+
}),
|
|
235
247
|
runtimePath: opencodePath,
|
|
236
248
|
opencodePath,
|
|
237
249
|
opencodeVersion,
|
|
238
|
-
rotatePending: false,
|
|
239
|
-
lastRotatedAt: shouldRotate ? new Date().toISOString() : meta.lastRotatedAt,
|
|
240
250
|
}, { status: 'connected' });
|
|
241
|
-
await recordActivity(adapter, nextBinding, inbound, {
|
|
242
|
-
...result,
|
|
243
|
-
media: normalizeOutboundMedia(result),
|
|
244
|
-
});
|
|
245
251
|
await emitWorkerEvent({
|
|
246
252
|
adapter,
|
|
247
253
|
binding: nextBinding,
|
|
248
254
|
agent: this.name,
|
|
249
|
-
sessionId: result?.sessionId ||
|
|
255
|
+
sessionId: result?.sessionId || targetSessionId || binding.id,
|
|
250
256
|
cwd: result?.cwd || agentHome,
|
|
251
257
|
replyToMessageId: inbound.messageId || null,
|
|
252
258
|
event: {
|
|
@@ -262,7 +268,7 @@ export const openCodeRuntime = {
|
|
|
262
268
|
adapter,
|
|
263
269
|
binding,
|
|
264
270
|
agent: this.name,
|
|
265
|
-
sessionId:
|
|
271
|
+
sessionId: targetSessionId || binding.id,
|
|
266
272
|
cwd: agentHome,
|
|
267
273
|
replyToMessageId: inbound.messageId || null,
|
|
268
274
|
event: {
|
|
@@ -210,14 +210,15 @@ export function runOpenCodePrompt({
|
|
|
210
210
|
opencodePath = null,
|
|
211
211
|
agentEnv = null,
|
|
212
212
|
standingPrompt = null,
|
|
213
|
+
forceStandingPrompt = false,
|
|
213
214
|
timeoutMs = Number(process.env.OPENCODE_RUN_TIMEOUT_MS || DEFAULT_OPENCODE_RUN_TIMEOUT_MS),
|
|
214
215
|
onEvent,
|
|
215
216
|
}) {
|
|
216
217
|
// opencode has no documented `--system` flag, so we prepend the
|
|
217
|
-
// standing prompt to the first-turn message body.
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
const finalMessage = standingPrompt && !sessionId
|
|
218
|
+
// standing prompt to the first-turn message body. Callers may force a
|
|
219
|
+
// resumed-session injection when the selected protocol overlay changes
|
|
220
|
+
// or this daemon process has not yet observed the session.
|
|
221
|
+
const finalMessage = standingPrompt && (!sessionId || forceStandingPrompt)
|
|
221
222
|
? `${standingPrompt}\n\n---\n\n${message}`
|
|
222
223
|
: message;
|
|
223
224
|
return new Promise((resolve, reject) => {
|