ticlawk 0.1.16-dev.9 → 0.1.17-dev.1

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.
Files changed (42) hide show
  1. package/README.md +17 -3
  2. package/bin/ticlawk.mjs +255 -21
  3. package/package.json +1 -1
  4. package/src/adapters/ticlawk/api.mjs +350 -50
  5. package/src/adapters/ticlawk/credentials.mjs +41 -1
  6. package/src/adapters/ticlawk/index.mjs +248 -130
  7. package/src/adapters/ticlawk/wake-client.mjs +1 -1
  8. package/src/cli/agent-commands.mjs +715 -18
  9. package/src/core/agent-cli-handlers.mjs +556 -18
  10. package/src/core/agent-home.mjs +81 -1
  11. package/src/core/argv.mjs +11 -1
  12. package/src/core/events/worker-events.mjs +32 -36
  13. package/src/core/http.mjs +152 -0
  14. package/src/core/runtime-contract.mjs +0 -1
  15. package/src/core/runtime-env.mjs +7 -0
  16. package/src/core/runtime-support.mjs +130 -78
  17. package/src/runtimes/_shared/agent-handbook.mjs +45 -0
  18. package/src/runtimes/_shared/brand.mjs +2 -0
  19. package/src/runtimes/_shared/goal-step-prompt.mjs +98 -0
  20. package/src/runtimes/_shared/goal-task-protocol.mjs +50 -0
  21. package/src/runtimes/_shared/handbook/BASICS.md +27 -0
  22. package/src/runtimes/_shared/handbook/COLLABORATION.md +37 -0
  23. package/src/runtimes/_shared/handbook/COMMUNICATION.md +55 -0
  24. package/src/runtimes/_shared/handbook/DM_SCOPE.md +13 -0
  25. package/src/runtimes/_shared/handbook/GOAL_AUTHORITY.md +47 -0
  26. package/src/runtimes/_shared/handbook/GOAL_TASK_CORE.md +43 -0
  27. package/src/runtimes/_shared/handbook/GROUP_ADMIN_SCOPE.md +21 -0
  28. package/src/runtimes/_shared/handbook/GROUP_MEMBER_SCOPE.md +15 -0
  29. package/src/runtimes/_shared/handbook/SURFACES.md +41 -0
  30. package/src/runtimes/_shared/handbook/TASK_WORKER.md +14 -0
  31. package/src/runtimes/_shared/standing-prompt.mjs +124 -279
  32. package/src/runtimes/_shared/wake-prompt.mjs +268 -0
  33. package/src/runtimes/claude-code/index.mjs +19 -46
  34. package/src/runtimes/claude-code/session.mjs +2 -7
  35. package/src/runtimes/codex/index.mjs +115 -63
  36. package/src/runtimes/codex/session.mjs +2 -12
  37. package/src/runtimes/openclaw/index.mjs +11 -24
  38. package/src/runtimes/opencode/index.mjs +38 -60
  39. package/src/runtimes/opencode/session.mjs +12 -12
  40. package/src/runtimes/pi/index.mjs +38 -60
  41. package/src/runtimes/pi/session.mjs +9 -6
  42. package/ticlawk.mjs +0 -30
@@ -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 { emitWorkerEvent } from '../../core/events/worker-events.mjs';
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 shouldRotate = !meta.sessionId || meta.rotatePending;
132
- const deltaAggregator = createDeltaAggregator({
133
- flushDelta: async ({ text, sessionId, cwd }) => {
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: meta.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: shouldRotate ? null : meta.sessionId, cwd: agentHome, piPath: runtimePiPath, agentEnv }, message, {
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
- await emitWorkerEvent({
165
+ emitWorkerEventBestEffort({
167
166
  adapter,
168
167
  binding,
169
168
  agent: this.name,
170
- sessionId: event.sessionId || meta.sessionId || binding.id,
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: shouldRotate ? null : meta.sessionId, cwd: agentHome, piPath: runtimePiPath, agentEnv }, message, { images, standingPrompt });
181
+ : await this.runTurn({ sessionId: targetSessionId, cwd: agentHome, piPath: runtimePiPath, agentEnv }, message, { images, standingPrompt, forceStandingPrompt });
188
182
 
189
- await deltaAggregator.flush();
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
- sessionId: result?.sessionId || meta.sessionId,
192
- path: result?.path || meta.path || null,
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
- await emitWorkerEvent({
196
+ emitWorkerEventBestEffort({
200
197
  adapter,
201
198
  binding: nextBinding,
202
199
  agent: this.name,
203
- sessionId: result?.sessionId || meta.sessionId || binding.id,
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
- await deltaAggregator.flush().catch(() => {});
215
- await emitWorkerEvent({
211
+ emitWorkerEventBestEffort({
216
212
  adapter,
217
213
  binding,
218
214
  agent: this.name,
219
- sessionId: meta.sessionId || binding.id,
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 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) => {
@@ -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
- eventChain = eventChain.then(() => onEvent(event)).catch(() => {});
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
- eventChain.catch(() => {}).finally(() => fn(value));
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