ticlawk 0.1.16-dev.8 → 0.1.16
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 +15 -3
- package/bin/ticlawk.mjs +208 -26
- package/package.json +1 -1
- package/src/adapters/ticlawk/api.mjs +283 -48
- package/src/adapters/ticlawk/credentials.mjs +41 -1
- package/src/adapters/ticlawk/index.mjs +126 -121
- package/src/adapters/ticlawk/wake-client.mjs +1 -1
- package/src/cli/agent-commands.mjs +560 -36
- package/src/core/agent-cli-handlers.mjs +435 -18
- package/src/core/agent-home.mjs +81 -1
- package/src/core/argv.mjs +11 -1
- package/src/core/events/worker-events.mjs +32 -36
- package/src/core/http.mjs +119 -0
- package/src/core/runtime-contract.mjs +0 -1
- package/src/core/runtime-env.mjs +7 -0
- package/src/core/runtime-support.mjs +108 -77
- 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 +55 -0
- package/src/runtimes/_shared/handbook/DM_SCOPE.md +13 -0
- package/src/runtimes/_shared/handbook/GOAL_AUTHORITY.md +46 -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 +134 -275
- package/src/runtimes/_shared/wake-prompt.mjs +261 -0
- package/src/runtimes/claude-code/index.mjs +19 -46
- package/src/runtimes/claude-code/session.mjs +2 -7
- package/src/runtimes/codex/index.mjs +115 -63
- package/src/runtimes/codex/session.mjs +2 -12
- package/src/runtimes/openclaw/index.mjs +11 -24
- package/src/runtimes/opencode/index.mjs +38 -60
- package/src/runtimes/opencode/session.mjs +12 -12
- package/src/runtimes/pi/index.mjs +38 -60
- package/src/runtimes/pi/session.mjs +9 -6
- package/ticlawk.mjs +0 -30
|
@@ -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,4 +1,5 @@
|
|
|
1
1
|
import { reportSubprocessFailure, terminalRuntimeFailure } from '../../core/runtime-support.mjs';
|
|
2
|
+
import { getGoalTaskProtocolOverlayKey } from '../_shared/goal-task-protocol.mjs';
|
|
2
3
|
import { buildStandingPrompt } from '../_shared/standing-prompt.mjs';
|
|
3
4
|
import { GATEWAY_HOST, GATEWAY_PORT } from './identity.mjs';
|
|
4
5
|
|
|
@@ -24,10 +25,11 @@ async function probeOpenClawGatewayHealth() {
|
|
|
24
25
|
import { buildOpenClawSessionKey, normalizeOpenClawAgentId } from './target.mjs';
|
|
25
26
|
import { askGateway, isGatewayReady, registerOpenClawChannel } from './gateway.mjs';
|
|
26
27
|
|
|
27
|
-
// Tracks which (agentId, sessionKey
|
|
28
|
-
// prompt this process lifetime. OpenClaw's
|
|
29
|
-
// out of process, so we err on the side of
|
|
30
|
-
//
|
|
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.
|
|
31
33
|
const standingPromptSeen = new Set();
|
|
32
34
|
import { addInFlight, recoverInFlightEntries, removeInFlight } from './inflight.mjs';
|
|
33
35
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
@@ -159,13 +161,13 @@ export const openClawRuntime = {
|
|
|
159
161
|
const sessionId = String(meta.sessionKey || buildOpenClawSessionKey(agentId)).trim();
|
|
160
162
|
|
|
161
163
|
// Inject the standing prompt on the first observed turn after a
|
|
162
|
-
// daemon start for this (agent, session)
|
|
163
|
-
// separate "system prompt" parameter on the gateway, so we
|
|
164
|
-
//
|
|
165
|
-
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 })}`;
|
|
166
168
|
let prompt = rawPrompt;
|
|
167
169
|
if (!standingPromptSeen.has(standingKey)) {
|
|
168
|
-
const standing = buildStandingPrompt({ agentId: binding.id });
|
|
170
|
+
const standing = buildStandingPrompt({ agentId: binding.id, inbound });
|
|
169
171
|
prompt = `${standing}\n\n---\n\n${rawPrompt}`;
|
|
170
172
|
standingPromptSeen.add(standingKey);
|
|
171
173
|
}
|
|
@@ -236,21 +238,6 @@ export const openClawRuntime = {
|
|
|
236
238
|
return entries.length;
|
|
237
239
|
},
|
|
238
240
|
|
|
239
|
-
async reconcileAfterRestart(binding, ctx) {
|
|
240
|
-
const meta = binding.runtimeMeta || {};
|
|
241
|
-
if (typeof ctx.adapter.emitEvent === 'function') {
|
|
242
|
-
await ctx.adapter.emitEvent(binding, {
|
|
243
|
-
agent: this.name,
|
|
244
|
-
sessionId: String(meta.sessionKey || buildOpenClawSessionKey(meta.agentId || binding.id)).trim(),
|
|
245
|
-
cwd: '',
|
|
246
|
-
event: {
|
|
247
|
-
hook_event_name: 'agent.run.end',
|
|
248
|
-
reason: 'connector.restart.reconcile',
|
|
249
|
-
},
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
return 1;
|
|
253
|
-
},
|
|
254
241
|
};
|
|
255
242
|
|
|
256
243
|
export default openClawRuntime;
|
|
@@ -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,15 +25,18 @@ import {
|
|
|
24
25
|
requireOpenCodePath,
|
|
25
26
|
} from './session.mjs';
|
|
26
27
|
import { buildOpenCodeInputFromInbound } from '../../core/media/inbound.mjs';
|
|
27
|
-
import {
|
|
28
|
+
import { emitWorkerEventBestEffort } from '../../core/events/worker-events.mjs';
|
|
28
29
|
import {
|
|
29
30
|
shouldStreamRuntime,
|
|
30
|
-
createDeltaAggregator,
|
|
31
31
|
reportSubprocessFailure,
|
|
32
32
|
terminalRuntimeFailure,
|
|
33
33
|
updateBindingRuntimeMeta,
|
|
34
|
+
resolveRuntimeSessionScope,
|
|
35
|
+
buildRuntimeSessionMetaPatch,
|
|
34
36
|
} from '../../core/runtime-support.mjs';
|
|
35
37
|
|
|
38
|
+
const standingPromptSeen = new Set();
|
|
39
|
+
|
|
36
40
|
export const openCodeRuntime = {
|
|
37
41
|
name: 'opencode',
|
|
38
42
|
|
|
@@ -48,6 +52,7 @@ export const openCodeRuntime = {
|
|
|
48
52
|
opencodePath,
|
|
49
53
|
agentEnv,
|
|
50
54
|
standingPrompt: opts.standingPrompt || null,
|
|
55
|
+
forceStandingPrompt: Boolean(opts.forceStandingPrompt),
|
|
51
56
|
files: opts.files,
|
|
52
57
|
timeoutMs: opts.timeoutMs,
|
|
53
58
|
});
|
|
@@ -61,6 +66,7 @@ export const openCodeRuntime = {
|
|
|
61
66
|
opencodePath,
|
|
62
67
|
agentEnv,
|
|
63
68
|
standingPrompt: opts.standingPrompt || null,
|
|
69
|
+
forceStandingPrompt: Boolean(opts.forceStandingPrompt),
|
|
64
70
|
files: opts.files,
|
|
65
71
|
timeoutMs: opts.timeoutMs,
|
|
66
72
|
onEvent: opts.onEvent,
|
|
@@ -156,47 +162,39 @@ export const openCodeRuntime = {
|
|
|
156
162
|
// instruction so it has something to anchor on.
|
|
157
163
|
message = captionText || 'Please analyze the attached image(s).';
|
|
158
164
|
}
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
flushDelta: async ({ text, sessionId, cwd }) => {
|
|
163
|
-
await emitWorkerEvent({
|
|
164
|
-
adapter,
|
|
165
|
-
binding,
|
|
166
|
-
agent: this.name,
|
|
167
|
-
sessionId: sessionId || meta.sessionId || binding.id,
|
|
168
|
-
cwd: cwd || agentHome,
|
|
169
|
-
replyToMessageId: inbound.messageId || null,
|
|
170
|
-
event: {
|
|
171
|
-
hook_event_name: 'worker.message.delta',
|
|
172
|
-
worker_event_name: 'worker.message.delta',
|
|
173
|
-
delta: text,
|
|
174
|
-
},
|
|
175
|
-
logger: ctx.logger,
|
|
176
|
-
});
|
|
177
|
-
},
|
|
178
|
-
});
|
|
165
|
+
const sessionScope = resolveRuntimeSessionScope(meta, inbound);
|
|
166
|
+
const shouldRotate = sessionScope.shouldRotate;
|
|
167
|
+
const targetSessionId = shouldRotate ? null : sessionScope.sessionId;
|
|
179
168
|
|
|
180
169
|
try {
|
|
181
170
|
const opencodePath = requireOpenCodePath(runtimeOpenCodePath);
|
|
182
171
|
const opencodeVersion = getOpenCodeRuntimeHealth(opencodePath).version || meta.opencodeVersion || null;
|
|
183
172
|
const agentEnv = buildAgentRuntimeEnv({
|
|
184
173
|
agentId: binding.id,
|
|
185
|
-
sessionId:
|
|
174
|
+
sessionId: targetSessionId,
|
|
186
175
|
hostId: binding.runtime_host_id,
|
|
176
|
+
conversationId: inbound.conversationId,
|
|
177
|
+
messageId: inbound.messageId,
|
|
178
|
+
target: inbound.envelopeTarget,
|
|
187
179
|
});
|
|
188
|
-
const standingPrompt = buildStandingPrompt({ agentId: binding.id });
|
|
180
|
+
const standingPrompt = buildStandingPrompt({ agentId: binding.id, inbound });
|
|
181
|
+
const protocolOverlayKey = getGoalTaskProtocolOverlayKey({ agentId: binding.id, inbound });
|
|
182
|
+
const standingPromptSeenKey = targetSessionId
|
|
183
|
+
? `${binding.id}|${targetSessionId}|${protocolOverlayKey}`
|
|
184
|
+
: null;
|
|
185
|
+
const forceStandingPrompt = Boolean(standingPromptSeenKey && !standingPromptSeen.has(standingPromptSeenKey));
|
|
189
186
|
const result = shouldStreamRuntime(this.name, this)
|
|
190
|
-
? await this.runTurnStream({ sessionId:
|
|
187
|
+
? await this.runTurnStream({ sessionId: targetSessionId, cwd: agentHome, opencodePath, agentEnv }, message, {
|
|
191
188
|
standingPrompt,
|
|
189
|
+
forceStandingPrompt,
|
|
192
190
|
files,
|
|
193
191
|
onEvent: async (event) => {
|
|
194
192
|
if (event?.type === 'turn.started') {
|
|
195
|
-
|
|
193
|
+
emitWorkerEventBestEffort({
|
|
196
194
|
adapter,
|
|
197
195
|
binding,
|
|
198
196
|
agent: this.name,
|
|
199
|
-
sessionId: event.sessionId ||
|
|
197
|
+
sessionId: event.sessionId || targetSessionId || binding.id,
|
|
200
198
|
cwd: agentHome,
|
|
201
199
|
replyToMessageId: inbound.messageId || null,
|
|
202
200
|
event: {
|
|
@@ -205,31 +203,30 @@ export const openCodeRuntime = {
|
|
|
205
203
|
},
|
|
206
204
|
logger: ctx.logger,
|
|
207
205
|
});
|
|
208
|
-
} else if (event?.type === 'message.delta' && event.text) {
|
|
209
|
-
deltaAggregator.push(event.text, {
|
|
210
|
-
sessionId: event.sessionId || meta.sessionId || binding.id,
|
|
211
|
-
cwd: agentHome,
|
|
212
|
-
});
|
|
213
206
|
}
|
|
214
207
|
},
|
|
215
208
|
})
|
|
216
|
-
: await this.runTurn({ sessionId:
|
|
209
|
+
: await this.runTurn({ sessionId: targetSessionId, cwd: agentHome, opencodePath, agentEnv }, message, { files, standingPrompt, forceStandingPrompt });
|
|
217
210
|
|
|
218
|
-
|
|
211
|
+
const observedSessionId = result?.sessionId || targetSessionId;
|
|
212
|
+
if (observedSessionId) {
|
|
213
|
+
standingPromptSeen.add(`${binding.id}|${observedSessionId}|${protocolOverlayKey}`);
|
|
214
|
+
}
|
|
219
215
|
|
|
220
216
|
const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
|
|
221
|
-
|
|
217
|
+
...buildRuntimeSessionMetaPatch(meta, sessionScope, {
|
|
218
|
+
sessionId: result?.sessionId,
|
|
219
|
+
path: result?.path,
|
|
220
|
+
}),
|
|
222
221
|
runtimePath: opencodePath,
|
|
223
222
|
opencodePath,
|
|
224
223
|
opencodeVersion,
|
|
225
|
-
rotatePending: false,
|
|
226
|
-
lastRotatedAt: shouldRotate ? new Date().toISOString() : meta.lastRotatedAt,
|
|
227
224
|
}, { status: 'connected' });
|
|
228
|
-
|
|
225
|
+
emitWorkerEventBestEffort({
|
|
229
226
|
adapter,
|
|
230
227
|
binding: nextBinding,
|
|
231
228
|
agent: this.name,
|
|
232
|
-
sessionId: result?.sessionId ||
|
|
229
|
+
sessionId: result?.sessionId || targetSessionId || binding.id,
|
|
233
230
|
cwd: result?.cwd || agentHome,
|
|
234
231
|
replyToMessageId: inbound.messageId || null,
|
|
235
232
|
event: {
|
|
@@ -240,12 +237,11 @@ export const openCodeRuntime = {
|
|
|
240
237
|
});
|
|
241
238
|
return true;
|
|
242
239
|
} catch (err) {
|
|
243
|
-
|
|
244
|
-
await emitWorkerEvent({
|
|
240
|
+
emitWorkerEventBestEffort({
|
|
245
241
|
adapter,
|
|
246
242
|
binding,
|
|
247
243
|
agent: this.name,
|
|
248
|
-
sessionId:
|
|
244
|
+
sessionId: targetSessionId || binding.id,
|
|
249
245
|
cwd: agentHome,
|
|
250
246
|
replyToMessageId: inbound.messageId || null,
|
|
251
247
|
event: {
|
|
@@ -271,24 +267,6 @@ export const openCodeRuntime = {
|
|
|
271
267
|
}
|
|
272
268
|
},
|
|
273
269
|
|
|
274
|
-
async reconcileAfterRestart(binding, ctx) {
|
|
275
|
-
const meta = binding.runtimeMeta || {};
|
|
276
|
-
await emitWorkerEvent({
|
|
277
|
-
adapter: ctx.adapter,
|
|
278
|
-
binding,
|
|
279
|
-
agent: this.name,
|
|
280
|
-
sessionId: meta.sessionId || binding.id,
|
|
281
|
-
cwd: ensureAgentHome(binding.id) || '',
|
|
282
|
-
event: {
|
|
283
|
-
hook_event_name: 'Stop',
|
|
284
|
-
worker_event_name: 'worker.turn.complete',
|
|
285
|
-
reason: 'connector.restart.reconcile',
|
|
286
|
-
},
|
|
287
|
-
logger: ctx.logger,
|
|
288
|
-
});
|
|
289
|
-
return 1;
|
|
290
|
-
},
|
|
291
|
-
|
|
292
270
|
dataDir: OPENCODE_DATA_DIR,
|
|
293
271
|
maxAgeMs: OPENCODE_MAX_AGE_MS,
|
|
294
272
|
};
|
|
@@ -193,10 +193,9 @@ function buildOpenCodeRunArgs({ sessionId, message, files = [] }) {
|
|
|
193
193
|
/**
|
|
194
194
|
* Spawn `opencode run` and resolve with the final result.
|
|
195
195
|
*
|
|
196
|
-
* If `onEvent` is provided it is invoked for
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
* forward typing indicators / partial text to the chat client.
|
|
196
|
+
* If `onEvent` is provided it is invoked for lifecycle events such as
|
|
197
|
+
* `{ type: 'turn.started', sessionId }`. Token deltas are kept local to
|
|
198
|
+
* build the final text and are not emitted to the caller.
|
|
200
199
|
*
|
|
201
200
|
* Resolves with `{ sessionId, cwd, text, durationMs }` on success.
|
|
202
201
|
* Rejects with an Error whose `.info` carries `{ ok: false, kind, ... }`
|
|
@@ -210,14 +209,15 @@ export function runOpenCodePrompt({
|
|
|
210
209
|
opencodePath = null,
|
|
211
210
|
agentEnv = null,
|
|
212
211
|
standingPrompt = null,
|
|
212
|
+
forceStandingPrompt = false,
|
|
213
213
|
timeoutMs = Number(process.env.OPENCODE_RUN_TIMEOUT_MS || DEFAULT_OPENCODE_RUN_TIMEOUT_MS),
|
|
214
214
|
onEvent,
|
|
215
215
|
}) {
|
|
216
216
|
// 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
|
|
217
|
+
// standing prompt to the first-turn message body. Callers may force a
|
|
218
|
+
// resumed-session injection when the selected protocol overlay changes
|
|
219
|
+
// or this daemon process has not yet observed the session.
|
|
220
|
+
const finalMessage = standingPrompt && (!sessionId || forceStandingPrompt)
|
|
221
221
|
? `${standingPrompt}\n\n---\n\n${message}`
|
|
222
222
|
: message;
|
|
223
223
|
return new Promise((resolve, reject) => {
|
|
@@ -245,18 +245,19 @@ export function runOpenCodePrompt({
|
|
|
245
245
|
let lastError = null;
|
|
246
246
|
let settled = false;
|
|
247
247
|
let turnStartedEmitted = false;
|
|
248
|
-
let eventChain = Promise.resolve();
|
|
249
248
|
|
|
250
249
|
const emit = (event) => {
|
|
251
250
|
if (typeof onEvent !== 'function') return;
|
|
252
|
-
|
|
251
|
+
void Promise.resolve()
|
|
252
|
+
.then(() => onEvent(event))
|
|
253
|
+
.catch(() => {});
|
|
253
254
|
};
|
|
254
255
|
|
|
255
256
|
const settle = (fn, value) => {
|
|
256
257
|
if (settled) return;
|
|
257
258
|
settled = true;
|
|
258
259
|
if (timeout) clearTimeout(timeout);
|
|
259
|
-
|
|
260
|
+
fn(value);
|
|
260
261
|
};
|
|
261
262
|
|
|
262
263
|
const handleEvent = (event) => {
|
|
@@ -272,7 +273,6 @@ export function runOpenCodePrompt({
|
|
|
272
273
|
const delta = extractEventText(event);
|
|
273
274
|
if (delta) {
|
|
274
275
|
finalText += delta;
|
|
275
|
-
emit({ type: 'message.delta', sessionId: activeSessionId, text: delta });
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { basename } from 'node:path';
|
|
8
8
|
import { buildAgentRuntimeEnv } from '../../core/runtime-env.mjs';
|
|
9
|
+
import { getGoalTaskProtocolOverlayKey } from '../_shared/goal-task-protocol.mjs';
|
|
9
10
|
import { buildStandingPrompt } from '../_shared/standing-prompt.mjs';
|
|
10
11
|
import { ensureAgentHome } from '../../core/agent-home.mjs';
|
|
11
12
|
import {
|
|
@@ -18,15 +19,18 @@ import {
|
|
|
18
19
|
requirePiPath,
|
|
19
20
|
runPiPrompt,
|
|
20
21
|
} from './session.mjs';
|
|
21
|
-
import {
|
|
22
|
+
import { emitWorkerEventBestEffort } from '../../core/events/worker-events.mjs';
|
|
22
23
|
import {
|
|
23
24
|
shouldStreamRuntime,
|
|
24
|
-
createDeltaAggregator,
|
|
25
25
|
reportSubprocessFailure,
|
|
26
26
|
terminalRuntimeFailure,
|
|
27
27
|
updateBindingRuntimeMeta,
|
|
28
|
+
resolveRuntimeSessionScope,
|
|
29
|
+
buildRuntimeSessionMetaPatch,
|
|
28
30
|
} from '../../core/runtime-support.mjs';
|
|
29
31
|
|
|
32
|
+
const standingPromptSeen = new Set();
|
|
33
|
+
|
|
30
34
|
export const piRuntime = {
|
|
31
35
|
name: 'pi',
|
|
32
36
|
|
|
@@ -39,6 +43,7 @@ export const piRuntime = {
|
|
|
39
43
|
piPath,
|
|
40
44
|
agentEnv,
|
|
41
45
|
standingPrompt: opts.standingPrompt || null,
|
|
46
|
+
forceStandingPrompt: Boolean(opts.forceStandingPrompt),
|
|
42
47
|
timeoutMs: opts.timeoutMs,
|
|
43
48
|
});
|
|
44
49
|
},
|
|
@@ -52,6 +57,7 @@ export const piRuntime = {
|
|
|
52
57
|
piPath,
|
|
53
58
|
agentEnv,
|
|
54
59
|
standingPrompt: opts.standingPrompt || null,
|
|
60
|
+
forceStandingPrompt: Boolean(opts.forceStandingPrompt),
|
|
55
61
|
timeoutMs: opts.timeoutMs,
|
|
56
62
|
onEvent: opts.onEvent,
|
|
57
63
|
});
|
|
@@ -128,46 +134,39 @@ export const piRuntime = {
|
|
|
128
134
|
message = captionText || 'Please analyze the attached image(s).';
|
|
129
135
|
}
|
|
130
136
|
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
await emitWorkerEvent({
|
|
135
|
-
adapter,
|
|
136
|
-
binding,
|
|
137
|
-
agent: this.name,
|
|
138
|
-
sessionId: sessionId || meta.sessionId || binding.id,
|
|
139
|
-
cwd: cwd || agentHome,
|
|
140
|
-
replyToMessageId: inbound.messageId || null,
|
|
141
|
-
event: {
|
|
142
|
-
hook_event_name: 'worker.message.delta',
|
|
143
|
-
worker_event_name: 'worker.message.delta',
|
|
144
|
-
delta: text,
|
|
145
|
-
},
|
|
146
|
-
logger: ctx.logger,
|
|
147
|
-
});
|
|
148
|
-
},
|
|
149
|
-
});
|
|
137
|
+
const sessionScope = resolveRuntimeSessionScope(meta, inbound);
|
|
138
|
+
const shouldRotate = sessionScope.shouldRotate;
|
|
139
|
+
const targetSessionId = shouldRotate ? null : sessionScope.sessionId;
|
|
150
140
|
|
|
151
141
|
try {
|
|
152
142
|
const runtimePiPath = requirePiPath(meta.piPath || meta.runtimePath);
|
|
153
143
|
const runtimePiVersion = getPiRuntimeHealth(runtimePiPath).version || meta.piVersion || null;
|
|
154
144
|
const agentEnv = buildAgentRuntimeEnv({
|
|
155
145
|
agentId: binding.id,
|
|
156
|
-
sessionId:
|
|
146
|
+
sessionId: targetSessionId,
|
|
157
147
|
hostId: binding.runtime_host_id,
|
|
148
|
+
conversationId: inbound.conversationId,
|
|
149
|
+
messageId: inbound.messageId,
|
|
150
|
+
target: inbound.envelopeTarget,
|
|
158
151
|
});
|
|
159
|
-
const standingPrompt = buildStandingPrompt({ agentId: binding.id });
|
|
152
|
+
const standingPrompt = buildStandingPrompt({ agentId: binding.id, inbound });
|
|
153
|
+
const protocolOverlayKey = getGoalTaskProtocolOverlayKey({ agentId: binding.id, inbound });
|
|
154
|
+
const standingPromptSeenKey = targetSessionId
|
|
155
|
+
? `${binding.id}|${targetSessionId}|${protocolOverlayKey}`
|
|
156
|
+
: null;
|
|
157
|
+
const forceStandingPrompt = Boolean(standingPromptSeenKey && !standingPromptSeen.has(standingPromptSeenKey));
|
|
160
158
|
const result = shouldStreamRuntime(this.name, this)
|
|
161
|
-
? await this.runTurnStream({ sessionId:
|
|
159
|
+
? await this.runTurnStream({ sessionId: targetSessionId, cwd: agentHome, piPath: runtimePiPath, agentEnv }, message, {
|
|
162
160
|
standingPrompt,
|
|
161
|
+
forceStandingPrompt,
|
|
163
162
|
images,
|
|
164
163
|
onEvent: async (event) => {
|
|
165
164
|
if (event?.type === 'turn.started') {
|
|
166
|
-
|
|
165
|
+
emitWorkerEventBestEffort({
|
|
167
166
|
adapter,
|
|
168
167
|
binding,
|
|
169
168
|
agent: this.name,
|
|
170
|
-
sessionId: event.sessionId ||
|
|
169
|
+
sessionId: event.sessionId || targetSessionId || binding.id,
|
|
171
170
|
cwd: agentHome,
|
|
172
171
|
replyToMessageId: inbound.messageId || null,
|
|
173
172
|
event: {
|
|
@@ -176,31 +175,29 @@ export const piRuntime = {
|
|
|
176
175
|
},
|
|
177
176
|
logger: ctx.logger,
|
|
178
177
|
});
|
|
179
|
-
} else if (event?.type === 'message.delta' && event.text) {
|
|
180
|
-
deltaAggregator.push(event.text, {
|
|
181
|
-
sessionId: event.sessionId || meta.sessionId || binding.id,
|
|
182
|
-
cwd: agentHome,
|
|
183
|
-
});
|
|
184
178
|
}
|
|
185
179
|
},
|
|
186
180
|
})
|
|
187
|
-
: await this.runTurn({ sessionId:
|
|
181
|
+
: await this.runTurn({ sessionId: targetSessionId, cwd: agentHome, piPath: runtimePiPath, agentEnv }, message, { images, standingPrompt, forceStandingPrompt });
|
|
188
182
|
|
|
189
|
-
|
|
183
|
+
const observedSessionId = result?.sessionId || targetSessionId;
|
|
184
|
+
if (observedSessionId) {
|
|
185
|
+
standingPromptSeen.add(`${binding.id}|${observedSessionId}|${protocolOverlayKey}`);
|
|
186
|
+
}
|
|
190
187
|
const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
|
|
191
|
-
|
|
192
|
-
|
|
188
|
+
...buildRuntimeSessionMetaPatch(meta, sessionScope, {
|
|
189
|
+
sessionId: result?.sessionId,
|
|
190
|
+
path: result?.path,
|
|
191
|
+
}),
|
|
193
192
|
runtimePath: runtimePiPath,
|
|
194
193
|
piPath: runtimePiPath,
|
|
195
194
|
piVersion: runtimePiVersion,
|
|
196
|
-
rotatePending: false,
|
|
197
|
-
lastRotatedAt: shouldRotate ? new Date().toISOString() : meta.lastRotatedAt,
|
|
198
195
|
}, { status: 'connected' });
|
|
199
|
-
|
|
196
|
+
emitWorkerEventBestEffort({
|
|
200
197
|
adapter,
|
|
201
198
|
binding: nextBinding,
|
|
202
199
|
agent: this.name,
|
|
203
|
-
sessionId: result?.sessionId ||
|
|
200
|
+
sessionId: result?.sessionId || targetSessionId || binding.id,
|
|
204
201
|
cwd: result?.cwd || agentHome,
|
|
205
202
|
replyToMessageId: inbound.messageId || null,
|
|
206
203
|
event: {
|
|
@@ -211,12 +208,11 @@ export const piRuntime = {
|
|
|
211
208
|
});
|
|
212
209
|
return true;
|
|
213
210
|
} catch (err) {
|
|
214
|
-
|
|
215
|
-
await emitWorkerEvent({
|
|
211
|
+
emitWorkerEventBestEffort({
|
|
216
212
|
adapter,
|
|
217
213
|
binding,
|
|
218
214
|
agent: this.name,
|
|
219
|
-
sessionId:
|
|
215
|
+
sessionId: targetSessionId || binding.id,
|
|
220
216
|
cwd: agentHome,
|
|
221
217
|
replyToMessageId: inbound.messageId || null,
|
|
222
218
|
event: {
|
|
@@ -242,24 +238,6 @@ export const piRuntime = {
|
|
|
242
238
|
}
|
|
243
239
|
},
|
|
244
240
|
|
|
245
|
-
async reconcileAfterRestart(binding, ctx) {
|
|
246
|
-
const meta = binding.runtimeMeta || {};
|
|
247
|
-
await emitWorkerEvent({
|
|
248
|
-
adapter: ctx.adapter,
|
|
249
|
-
binding,
|
|
250
|
-
agent: this.name,
|
|
251
|
-
sessionId: meta.sessionId || binding.id,
|
|
252
|
-
cwd: ensureAgentHome(binding.id) || '',
|
|
253
|
-
event: {
|
|
254
|
-
hook_event_name: 'Stop',
|
|
255
|
-
worker_event_name: 'worker.turn.complete',
|
|
256
|
-
reason: 'connector.restart.reconcile',
|
|
257
|
-
},
|
|
258
|
-
logger: ctx.logger,
|
|
259
|
-
});
|
|
260
|
-
return 1;
|
|
261
|
-
},
|
|
262
|
-
|
|
263
241
|
sessionsDir: PI_SESSIONS_DIR,
|
|
264
242
|
maxAgeMs: PI_MAX_AGE_MS,
|
|
265
243
|
};
|
|
@@ -197,11 +197,14 @@ export function runPiPrompt({
|
|
|
197
197
|
piPath = null,
|
|
198
198
|
agentEnv = null,
|
|
199
199
|
standingPrompt = null,
|
|
200
|
+
forceStandingPrompt = false,
|
|
200
201
|
timeoutMs = Number(process.env.PI_RUN_TIMEOUT_MS || DEFAULT_PI_RUN_TIMEOUT_MS),
|
|
201
202
|
onEvent,
|
|
202
203
|
}) {
|
|
203
|
-
// pi has no documented system-prompt flag
|
|
204
|
-
|
|
204
|
+
// pi has no documented system-prompt flag. Prepend on first turn, and
|
|
205
|
+
// let callers force one resumed-session injection for a newly selected
|
|
206
|
+
// protocol overlay.
|
|
207
|
+
const finalMessage = standingPrompt && (!sessionId || forceStandingPrompt)
|
|
205
208
|
? `${standingPrompt}\n\n---\n\n${message}`
|
|
206
209
|
: message;
|
|
207
210
|
return new Promise((resolve, reject) => {
|
|
@@ -220,12 +223,13 @@ export function runPiPrompt({
|
|
|
220
223
|
let activeSessionFile = null;
|
|
221
224
|
let finalText = '';
|
|
222
225
|
let settled = false;
|
|
223
|
-
let eventChain = Promise.resolve();
|
|
224
226
|
const pending = new Map();
|
|
225
227
|
|
|
226
228
|
const emit = (event) => {
|
|
227
229
|
if (typeof onEvent !== 'function') return;
|
|
228
|
-
|
|
230
|
+
void Promise.resolve()
|
|
231
|
+
.then(() => onEvent(event))
|
|
232
|
+
.catch(() => {});
|
|
229
233
|
};
|
|
230
234
|
|
|
231
235
|
const settle = (fn, value) => {
|
|
@@ -237,7 +241,7 @@ export function runPiPrompt({
|
|
|
237
241
|
}
|
|
238
242
|
pending.clear();
|
|
239
243
|
try { child.kill('SIGTERM'); } catch {}
|
|
240
|
-
|
|
244
|
+
fn(value);
|
|
241
245
|
};
|
|
242
246
|
|
|
243
247
|
const sendRaw = (payload) => {
|
|
@@ -305,7 +309,6 @@ export function runPiPrompt({
|
|
|
305
309
|
const delta = extractDeltaFromEvent(event);
|
|
306
310
|
if (delta) {
|
|
307
311
|
finalText += delta;
|
|
308
|
-
emit({ type: 'message.delta', sessionId: activeSessionId, text: delta });
|
|
309
312
|
}
|
|
310
313
|
return;
|
|
311
314
|
}
|
package/ticlawk.mjs
CHANGED
|
@@ -211,35 +211,6 @@ async function recoverAllRuntimes(runtimeList, adapter) {
|
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
async function reconcileBindingsAfterRestart(runtimes, adapter) {
|
|
215
|
-
const hostId = getHostId();
|
|
216
|
-
for (const binding of listBindings({ adapter: adapter.id })) {
|
|
217
|
-
if (!belongsToRuntimeHost(binding, hostId)) {
|
|
218
|
-
logger.debugError('core', 'binding.reconcile-host-mismatch', {
|
|
219
|
-
bindingId: binding.id,
|
|
220
|
-
adapter: binding.adapter,
|
|
221
|
-
hostId,
|
|
222
|
-
runtime_host_id: getBindingRuntimeHostId(binding),
|
|
223
|
-
});
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
const runtime = binding.runtime ? runtimes[binding.runtime] : null;
|
|
227
|
-
if (typeof runtime?.reconcileAfterRestart !== 'function') continue;
|
|
228
|
-
try {
|
|
229
|
-
await runtime.reconcileAfterRestart(binding, {
|
|
230
|
-
adapter,
|
|
231
|
-
logger,
|
|
232
|
-
});
|
|
233
|
-
} catch (err) {
|
|
234
|
-
logger.debugError('startup', 'reconcileAfterRestart.failed', {
|
|
235
|
-
runtime: binding.runtime,
|
|
236
|
-
bindingId: binding.id,
|
|
237
|
-
error: err?.message || String(err),
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
214
|
export async function startTiclawk() {
|
|
244
215
|
if (started) return;
|
|
245
216
|
started = true;
|
|
@@ -278,7 +249,6 @@ export async function startTiclawk() {
|
|
|
278
249
|
});
|
|
279
250
|
startReminderTicker();
|
|
280
251
|
await recoverAllRuntimes(runtimeList, adapter);
|
|
281
|
-
await reconcileBindingsAfterRestart(runtimes, adapter);
|
|
282
252
|
await adapter.start();
|
|
283
253
|
}
|
|
284
254
|
|