ticlawk 0.1.16-dev.15 → 0.1.16-dev.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.
@@ -26,6 +26,8 @@ import {
26
26
  terminalRuntimeFailure,
27
27
  updateBindingRuntimeMeta,
28
28
  isRuntimeGatewayFailure,
29
+ resolveRuntimeSessionScope,
30
+ buildRuntimeSessionMetaPatch,
29
31
  } from '../../core/runtime-support.mjs';
30
32
  import { buildAgentRuntimeEnv } from '../../core/runtime-env.mjs';
31
33
  import { buildStandingPrompt } from '../_shared/standing-prompt.mjs';
@@ -140,14 +142,16 @@ export const codexRuntime = {
140
142
  ? (codexInput.find((item) => item?.type === 'text')?.text || inbound.text || '(image attached)')
141
143
  : inbound.text;
142
144
 
143
- const shouldRotate = !meta.sessionId || meta.rotatePending;
145
+ const sessionScope = resolveRuntimeSessionScope(meta, inbound);
146
+ const shouldRotate = sessionScope.shouldRotate;
147
+ const targetSessionId = shouldRotate ? null : sessionScope.sessionId;
144
148
  const deltaAggregator = createDeltaAggregator({
145
149
  flushDelta: async ({ text, sessionId, turnId, cwd }) => {
146
150
  await emitWorkerEvent({
147
151
  adapter,
148
152
  binding,
149
153
  agent: this.name,
150
- sessionId: sessionId || meta.sessionId || binding.id,
154
+ sessionId: sessionId || targetSessionId || binding.id,
151
155
  turnId: turnId || null,
152
156
  cwd: cwd || agentHome,
153
157
  replyToMessageId: inbound.messageId || null,
@@ -165,12 +169,15 @@ export const codexRuntime = {
165
169
  const runtimeCodexVersion = getCodexRuntimeHealth(runtimeCodexPath).version || meta.codexVersion || null;
166
170
  const agentEnv = buildAgentRuntimeEnv({
167
171
  agentId: binding.id,
168
- sessionId: meta.sessionId,
172
+ sessionId: targetSessionId,
169
173
  hostId: binding.runtime_host_id,
174
+ conversationId: inbound.conversationId,
175
+ messageId: inbound.messageId,
176
+ target: inbound.envelopeTarget,
170
177
  });
171
- const developerInstructions = buildStandingPrompt({ agentId: binding.id });
178
+ const developerInstructions = buildStandingPrompt({ agentId: binding.id, inbound });
172
179
  const result = (shouldRotate || inbound.action === 'image' || shouldStreamRuntime(this.name, this))
173
- ? await this.runTurnStream({ sessionId: shouldRotate ? null : meta.sessionId, cwd: agentHome, codexPath: runtimeCodexPath, agentEnv }, message, {
180
+ ? await this.runTurnStream({ sessionId: targetSessionId, cwd: agentHome, codexPath: runtimeCodexPath, agentEnv }, message, {
174
181
  input: codexInput,
175
182
  developerInstructions,
176
183
  onEvent: async (event) => {
@@ -179,7 +186,7 @@ export const codexRuntime = {
179
186
  adapter,
180
187
  binding,
181
188
  agent: this.name,
182
- sessionId: event.sessionId || meta.sessionId || binding.id,
189
+ sessionId: event.sessionId || targetSessionId || binding.id,
183
190
  turnId: event.turnId || null,
184
191
  cwd: agentHome,
185
192
  replyToMessageId: inbound.messageId || null,
@@ -191,32 +198,32 @@ export const codexRuntime = {
191
198
  });
192
199
  } else if (event?.type === 'message.delta' && event.text) {
193
200
  deltaAggregator.push(event.text, {
194
- sessionId: event.sessionId || meta.sessionId || binding.id,
201
+ sessionId: event.sessionId || targetSessionId || binding.id,
195
202
  turnId: event.turnId || null,
196
203
  cwd: agentHome,
197
204
  });
198
205
  }
199
206
  },
200
207
  })
201
- : await this.runTurn({ sessionId: meta.sessionId, cwd: agentHome, codexPath: runtimeCodexPath, agentEnv }, message, {
208
+ : await this.runTurn({ sessionId: targetSessionId, cwd: agentHome, codexPath: runtimeCodexPath, agentEnv }, message, {
202
209
  input: codexInput,
203
210
  });
204
211
 
205
212
  await deltaAggregator.flush();
206
213
  const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
207
- sessionId: result?.sessionId || meta.sessionId,
208
- path: result?.path || meta.path || null,
214
+ ...buildRuntimeSessionMetaPatch(meta, sessionScope, {
215
+ sessionId: result?.sessionId,
216
+ path: result?.path,
217
+ }),
209
218
  runtimePath: runtimeCodexPath,
210
219
  codexPath: runtimeCodexPath,
211
220
  codexVersion: runtimeCodexVersion,
212
- rotatePending: false,
213
- lastRotatedAt: shouldRotate ? new Date().toISOString() : meta.lastRotatedAt,
214
221
  }, { status: 'connected' });
215
222
  await emitWorkerEvent({
216
223
  adapter,
217
224
  binding: nextBinding,
218
225
  agent: this.name,
219
- sessionId: result?.sessionId || meta.sessionId || binding.id,
226
+ sessionId: result?.sessionId || targetSessionId || binding.id,
220
227
  turnId: result?.turnId || null,
221
228
  cwd: result?.cwd || agentHome,
222
229
  replyToMessageId: inbound.messageId || null,
@@ -247,7 +254,7 @@ export const codexRuntime = {
247
254
  adapter,
248
255
  binding: failureBinding,
249
256
  agent: this.name,
250
- sessionId: meta.sessionId || binding.id,
257
+ sessionId: targetSessionId || binding.id,
251
258
  turnId: failureInfo?.turnId || null,
252
259
  cwd: agentHome,
253
260
  replyToMessageId: inbound.messageId || null,
@@ -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) pairs already saw the standing
28
- // prompt this process lifetime. OpenClaw's gateway holds session state
29
- // out of process, so we err on the side of "inject on first observed
30
- // turn after daemon restart"; the gateway dedupes redundant context.
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) pair. OpenClaw has no
163
- // separate "system prompt" parameter on the gateway, so we prepend
164
- // exactly once.
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
  }
@@ -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 {
@@ -31,8 +32,12 @@ import {
31
32
  reportSubprocessFailure,
32
33
  terminalRuntimeFailure,
33
34
  updateBindingRuntimeMeta,
35
+ resolveRuntimeSessionScope,
36
+ buildRuntimeSessionMetaPatch,
34
37
  } from '../../core/runtime-support.mjs';
35
38
 
39
+ const standingPromptSeen = new Set();
40
+
36
41
  export const openCodeRuntime = {
37
42
  name: 'opencode',
38
43
 
@@ -48,6 +53,7 @@ export const openCodeRuntime = {
48
53
  opencodePath,
49
54
  agentEnv,
50
55
  standingPrompt: opts.standingPrompt || null,
56
+ forceStandingPrompt: Boolean(opts.forceStandingPrompt),
51
57
  files: opts.files,
52
58
  timeoutMs: opts.timeoutMs,
53
59
  });
@@ -61,6 +67,7 @@ export const openCodeRuntime = {
61
67
  opencodePath,
62
68
  agentEnv,
63
69
  standingPrompt: opts.standingPrompt || null,
70
+ forceStandingPrompt: Boolean(opts.forceStandingPrompt),
64
71
  files: opts.files,
65
72
  timeoutMs: opts.timeoutMs,
66
73
  onEvent: opts.onEvent,
@@ -156,7 +163,9 @@ export const openCodeRuntime = {
156
163
  // instruction so it has something to anchor on.
157
164
  message = captionText || 'Please analyze the attached image(s).';
158
165
  }
159
- const shouldRotate = !meta.sessionId || meta.rotatePending;
166
+ const sessionScope = resolveRuntimeSessionScope(meta, inbound);
167
+ const shouldRotate = sessionScope.shouldRotate;
168
+ const targetSessionId = shouldRotate ? null : sessionScope.sessionId;
160
169
 
161
170
  const deltaAggregator = createDeltaAggregator({
162
171
  flushDelta: async ({ text, sessionId, cwd }) => {
@@ -164,7 +173,7 @@ export const openCodeRuntime = {
164
173
  adapter,
165
174
  binding,
166
175
  agent: this.name,
167
- sessionId: sessionId || meta.sessionId || binding.id,
176
+ sessionId: sessionId || targetSessionId || binding.id,
168
177
  cwd: cwd || agentHome,
169
178
  replyToMessageId: inbound.messageId || null,
170
179
  event: {
@@ -182,13 +191,22 @@ export const openCodeRuntime = {
182
191
  const opencodeVersion = getOpenCodeRuntimeHealth(opencodePath).version || meta.opencodeVersion || null;
183
192
  const agentEnv = buildAgentRuntimeEnv({
184
193
  agentId: binding.id,
185
- sessionId: meta.sessionId,
194
+ sessionId: targetSessionId,
186
195
  hostId: binding.runtime_host_id,
196
+ conversationId: inbound.conversationId,
197
+ messageId: inbound.messageId,
198
+ target: inbound.envelopeTarget,
187
199
  });
188
- 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));
189
206
  const result = shouldStreamRuntime(this.name, this)
190
- ? await this.runTurnStream({ sessionId: shouldRotate ? null : meta.sessionId, cwd: agentHome, opencodePath, agentEnv }, message, {
207
+ ? await this.runTurnStream({ sessionId: targetSessionId, cwd: agentHome, opencodePath, agentEnv }, message, {
191
208
  standingPrompt,
209
+ forceStandingPrompt,
192
210
  files,
193
211
  onEvent: async (event) => {
194
212
  if (event?.type === 'turn.started') {
@@ -196,7 +214,7 @@ export const openCodeRuntime = {
196
214
  adapter,
197
215
  binding,
198
216
  agent: this.name,
199
- sessionId: event.sessionId || meta.sessionId || binding.id,
217
+ sessionId: event.sessionId || targetSessionId || binding.id,
200
218
  cwd: agentHome,
201
219
  replyToMessageId: inbound.messageId || null,
202
220
  event: {
@@ -207,29 +225,34 @@ export const openCodeRuntime = {
207
225
  });
208
226
  } else if (event?.type === 'message.delta' && event.text) {
209
227
  deltaAggregator.push(event.text, {
210
- sessionId: event.sessionId || meta.sessionId || binding.id,
228
+ sessionId: event.sessionId || targetSessionId || binding.id,
211
229
  cwd: agentHome,
212
230
  });
213
231
  }
214
232
  },
215
233
  })
216
- : await this.runTurn({ sessionId: shouldRotate ? null : meta.sessionId, cwd: agentHome, opencodePath, agentEnv }, message, { files, standingPrompt });
234
+ : await this.runTurn({ sessionId: targetSessionId, cwd: agentHome, opencodePath, agentEnv }, message, { files, standingPrompt, forceStandingPrompt });
217
235
 
218
236
  await deltaAggregator.flush();
237
+ const observedSessionId = result?.sessionId || targetSessionId;
238
+ if (observedSessionId) {
239
+ standingPromptSeen.add(`${binding.id}|${observedSessionId}|${protocolOverlayKey}`);
240
+ }
219
241
 
220
242
  const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
221
- sessionId: result?.sessionId || meta.sessionId,
243
+ ...buildRuntimeSessionMetaPatch(meta, sessionScope, {
244
+ sessionId: result?.sessionId,
245
+ path: result?.path,
246
+ }),
222
247
  runtimePath: opencodePath,
223
248
  opencodePath,
224
249
  opencodeVersion,
225
- rotatePending: false,
226
- lastRotatedAt: shouldRotate ? new Date().toISOString() : meta.lastRotatedAt,
227
250
  }, { status: 'connected' });
228
251
  await emitWorkerEvent({
229
252
  adapter,
230
253
  binding: nextBinding,
231
254
  agent: this.name,
232
- sessionId: result?.sessionId || meta.sessionId || binding.id,
255
+ sessionId: result?.sessionId || targetSessionId || binding.id,
233
256
  cwd: result?.cwd || agentHome,
234
257
  replyToMessageId: inbound.messageId || null,
235
258
  event: {
@@ -245,7 +268,7 @@ export const openCodeRuntime = {
245
268
  adapter,
246
269
  binding,
247
270
  agent: this.name,
248
- sessionId: meta.sessionId || binding.id,
271
+ sessionId: targetSessionId || binding.id,
249
272
  cwd: agentHome,
250
273
  replyToMessageId: inbound.messageId || null,
251
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. Resumed sessions
218
- // (sessionId set) skip injection because the model already saw it
219
- // on the originating turn.
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) => {
@@ -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 {
@@ -25,8 +26,12 @@ import {
25
26
  reportSubprocessFailure,
26
27
  terminalRuntimeFailure,
27
28
  updateBindingRuntimeMeta,
29
+ resolveRuntimeSessionScope,
30
+ buildRuntimeSessionMetaPatch,
28
31
  } from '../../core/runtime-support.mjs';
29
32
 
33
+ const standingPromptSeen = new Set();
34
+
30
35
  export const piRuntime = {
31
36
  name: 'pi',
32
37
 
@@ -39,6 +44,7 @@ export const piRuntime = {
39
44
  piPath,
40
45
  agentEnv,
41
46
  standingPrompt: opts.standingPrompt || null,
47
+ forceStandingPrompt: Boolean(opts.forceStandingPrompt),
42
48
  timeoutMs: opts.timeoutMs,
43
49
  });
44
50
  },
@@ -52,6 +58,7 @@ export const piRuntime = {
52
58
  piPath,
53
59
  agentEnv,
54
60
  standingPrompt: opts.standingPrompt || null,
61
+ forceStandingPrompt: Boolean(opts.forceStandingPrompt),
55
62
  timeoutMs: opts.timeoutMs,
56
63
  onEvent: opts.onEvent,
57
64
  });
@@ -128,14 +135,16 @@ export const piRuntime = {
128
135
  message = captionText || 'Please analyze the attached image(s).';
129
136
  }
130
137
 
131
- const shouldRotate = !meta.sessionId || meta.rotatePending;
138
+ const sessionScope = resolveRuntimeSessionScope(meta, inbound);
139
+ const shouldRotate = sessionScope.shouldRotate;
140
+ const targetSessionId = shouldRotate ? null : sessionScope.sessionId;
132
141
  const deltaAggregator = createDeltaAggregator({
133
142
  flushDelta: async ({ text, sessionId, cwd }) => {
134
143
  await emitWorkerEvent({
135
144
  adapter,
136
145
  binding,
137
146
  agent: this.name,
138
- sessionId: sessionId || meta.sessionId || binding.id,
147
+ sessionId: sessionId || targetSessionId || binding.id,
139
148
  cwd: cwd || agentHome,
140
149
  replyToMessageId: inbound.messageId || null,
141
150
  event: {
@@ -153,13 +162,22 @@ export const piRuntime = {
153
162
  const runtimePiVersion = getPiRuntimeHealth(runtimePiPath).version || meta.piVersion || null;
154
163
  const agentEnv = buildAgentRuntimeEnv({
155
164
  agentId: binding.id,
156
- sessionId: meta.sessionId,
165
+ sessionId: targetSessionId,
157
166
  hostId: binding.runtime_host_id,
167
+ conversationId: inbound.conversationId,
168
+ messageId: inbound.messageId,
169
+ target: inbound.envelopeTarget,
158
170
  });
159
- const standingPrompt = buildStandingPrompt({ agentId: binding.id });
171
+ const standingPrompt = buildStandingPrompt({ agentId: binding.id, inbound });
172
+ const protocolOverlayKey = getGoalTaskProtocolOverlayKey({ agentId: binding.id, inbound });
173
+ const standingPromptSeenKey = targetSessionId
174
+ ? `${binding.id}|${targetSessionId}|${protocolOverlayKey}`
175
+ : null;
176
+ const forceStandingPrompt = Boolean(standingPromptSeenKey && !standingPromptSeen.has(standingPromptSeenKey));
160
177
  const result = shouldStreamRuntime(this.name, this)
161
- ? await this.runTurnStream({ sessionId: shouldRotate ? null : meta.sessionId, cwd: agentHome, piPath: runtimePiPath, agentEnv }, message, {
178
+ ? await this.runTurnStream({ sessionId: targetSessionId, cwd: agentHome, piPath: runtimePiPath, agentEnv }, message, {
162
179
  standingPrompt,
180
+ forceStandingPrompt,
163
181
  images,
164
182
  onEvent: async (event) => {
165
183
  if (event?.type === 'turn.started') {
@@ -167,7 +185,7 @@ export const piRuntime = {
167
185
  adapter,
168
186
  binding,
169
187
  agent: this.name,
170
- sessionId: event.sessionId || meta.sessionId || binding.id,
188
+ sessionId: event.sessionId || targetSessionId || binding.id,
171
189
  cwd: agentHome,
172
190
  replyToMessageId: inbound.messageId || null,
173
191
  event: {
@@ -178,29 +196,33 @@ export const piRuntime = {
178
196
  });
179
197
  } else if (event?.type === 'message.delta' && event.text) {
180
198
  deltaAggregator.push(event.text, {
181
- sessionId: event.sessionId || meta.sessionId || binding.id,
199
+ sessionId: event.sessionId || targetSessionId || binding.id,
182
200
  cwd: agentHome,
183
201
  });
184
202
  }
185
203
  },
186
204
  })
187
- : await this.runTurn({ sessionId: shouldRotate ? null : meta.sessionId, cwd: agentHome, piPath: runtimePiPath, agentEnv }, message, { images, standingPrompt });
205
+ : await this.runTurn({ sessionId: targetSessionId, cwd: agentHome, piPath: runtimePiPath, agentEnv }, message, { images, standingPrompt, forceStandingPrompt });
188
206
 
189
207
  await deltaAggregator.flush();
208
+ const observedSessionId = result?.sessionId || targetSessionId;
209
+ if (observedSessionId) {
210
+ standingPromptSeen.add(`${binding.id}|${observedSessionId}|${protocolOverlayKey}`);
211
+ }
190
212
  const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
191
- sessionId: result?.sessionId || meta.sessionId,
192
- path: result?.path || meta.path || null,
213
+ ...buildRuntimeSessionMetaPatch(meta, sessionScope, {
214
+ sessionId: result?.sessionId,
215
+ path: result?.path,
216
+ }),
193
217
  runtimePath: runtimePiPath,
194
218
  piPath: runtimePiPath,
195
219
  piVersion: runtimePiVersion,
196
- rotatePending: false,
197
- lastRotatedAt: shouldRotate ? new Date().toISOString() : meta.lastRotatedAt,
198
220
  }, { status: 'connected' });
199
221
  await emitWorkerEvent({
200
222
  adapter,
201
223
  binding: nextBinding,
202
224
  agent: this.name,
203
- sessionId: result?.sessionId || meta.sessionId || binding.id,
225
+ sessionId: result?.sessionId || targetSessionId || binding.id,
204
226
  cwd: result?.cwd || agentHome,
205
227
  replyToMessageId: inbound.messageId || null,
206
228
  event: {
@@ -216,7 +238,7 @@ export const piRuntime = {
216
238
  adapter,
217
239
  binding,
218
240
  agent: this.name,
219
- sessionId: meta.sessionId || binding.id,
241
+ sessionId: targetSessionId || binding.id,
220
242
  cwd: agentHome,
221
243
  replyToMessageId: inbound.messageId || null,
222
244
  event: {
@@ -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 prepend on first turn.
204
- const finalMessage = standingPrompt && !sessionId
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) => {