ticlawk 0.1.15-dev.6 → 0.1.16-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.
package/bin/ticlawk.mjs CHANGED
@@ -3,8 +3,6 @@
3
3
  import { request } from 'node:http';
4
4
  import { installProcessDiagnostics } from '../src/core/diagnostics.mjs';
5
5
  import {
6
- AF_ADAPTER_KEY,
7
- getConfiguredAdapter,
8
6
  loadPersistentConfig,
9
7
  getStreamingConfigView,
10
8
  normalizeAdapterConfigTarget,
@@ -20,7 +18,7 @@ import {
20
18
  getActiveProfile,
21
19
  listProfiles,
22
20
  } from '../src/core/profiles.mjs';
23
- import { getAdapterAuthHelp, runAdapterAuth } from '../src/core/adapter-registry.mjs';
21
+ import { runAdapterAuth } from '../src/core/adapter-registry.mjs';
24
22
  import { runTiclawkConnect } from '../src/core/ticlawk-control.mjs';
25
23
  import {
26
24
  RUNTIME_DEFINITIONS,
@@ -30,12 +28,22 @@ import { runSelfUpdate, readPkgVersion } from '../src/core/update.mjs';
30
28
  import { getUninstallHelp, runSelfUninstall } from '../src/core/uninstall.mjs';
31
29
  import { getInstallDaemonHelp, runInstallDaemon } from '../src/core/daemon-install.mjs';
32
30
  import { runSetupReadiness } from '../src/core/setup-readiness.mjs';
31
+ import {
32
+ AGENT_COMMAND_HELP,
33
+ runGroupMembersCommand,
34
+ runMessageReadCommand,
35
+ runMessageSendCommand,
36
+ runServerInfoCommand,
37
+ runTaskClaimCommand,
38
+ runTaskListCommand,
39
+ runTaskUpdateCommand,
40
+ } from '../src/cli/agent-commands.mjs';
33
41
 
34
42
  const TICLAWK_PORT = Number(process.env.FEED_RELAY_PORT || 8741);
35
43
 
36
44
  function getConfigKeyUsage() {
37
45
  const runtimeKeys = getRuntimeExecutableDefinitions().map((runtime) => runtime.executableCliKey);
38
- return ['adapter', 'streaming...', ...runtimeKeys, 'telegram.bot-token', 'ticlawk.connector-api-key', 'ticlawk.api-url', 'ticlawk.connector-ws-url'].join('|');
46
+ return ['streaming...', ...runtimeKeys, 'ticlawk.connector-api-key', 'ticlawk.api-url', 'ticlawk.connector-ws-url'].join('|');
39
47
  }
40
48
 
41
49
  function getRuntimeConfigExamples() {
@@ -56,7 +64,7 @@ Usage:
56
64
  ticlawk config
57
65
  ticlawk config get <${configKeys}>
58
66
  ticlawk config set <${configKeys}> <value>
59
- ticlawk auth <adapter> [adapter-auth-args...]
67
+ ticlawk auth --code <6-digit-code> [--api-url <url>]
60
68
  ticlawk connect
61
69
  ticlawk profile list
62
70
  ticlawk profile current
@@ -66,44 +74,50 @@ Usage:
66
74
  ticlawk uninstall [-y]
67
75
  ticlawk version
68
76
 
77
+ Agent CLI (run inside an agent runtime; requires TICLAWK_RUNTIME_AGENT_ID):
78
+ ticlawk message send --target <t> # body on stdin
79
+ ticlawk message read --target <t> [opts]
80
+ ticlawk task claim --message-id <id>
81
+ ticlawk task update --task-id <id> --status <s>
82
+ ticlawk task list [--target <t>]
83
+ ticlawk group members --target <t>
84
+ ticlawk server info [--refresh]
85
+
69
86
  Commands:
70
- auth store adapter-specific auth/setup input locally
71
- connect connect the selected adapter to a local runtime
87
+ auth store the ticlawk app pairing code locally
88
+ connect connect ticlawk to a local runtime
72
89
  profile list or switch saved local identities
90
+ message send/read chat messages (agent CLI surface)
91
+ task claim/update/list tasks (agent CLI surface)
92
+ group list members of a group conversation (agent CLI surface)
93
+ server server-info introspection (agent CLI surface)
73
94
  install-daemon install or refresh the background daemon
74
95
  update update the npm package and refresh the daemon
75
96
  uninstall remove service and managed install files, preserving ~/.ticlawk
76
97
 
77
98
  Config examples:
78
- ticlawk config get adapter
79
- ticlawk config set adapter telegram
80
- ticlawk config set adapter ticlawk
81
99
  ticlawk config set streaming off # turn streaming off for all runtimes
82
100
  ticlawk config set streaming.codex default # clear the codex-specific override and inherit the global streaming setting
83
101
  ${getRuntimeConfigExamples()}
84
- ticlawk config set telegram.bot-token <bot-token> # advanced/manual
85
102
  ticlawk config set ticlawk.connector-api-key <key> # advanced/manual
86
103
  ticlawk config set ticlawk.api-url <api-url> # advanced/manual
87
104
  ticlawk config set ticlawk.connector-ws-url <url> # advanced/manual
88
105
 
89
106
  Examples:
90
107
  ticlawk connect
91
- ticlawk auth telegram --bot-token <bot-token>
108
+ ticlawk auth --code <6-digit-code>
92
109
  `;
93
110
  }
94
111
 
95
112
  function getAuthHelp() {
96
- return `ticlawk auth
97
-
98
- Use \`auth\` to store adapter-specific auth/setup input locally.
99
- Adapter-specific args live after \`--\` so new adapters can define their own flags
100
- without changing the top-level CLI.
113
+ return `ticlawk auth --code <6-digit-code> [--api-url <url>]
101
114
 
102
- Examples:
103
- ticlawk auth telegram --bot-token <bot-token>
115
+ Store the ticlawk app pairing code locally. The daemon picks it up on the
116
+ next start and exchanges it for an API key.
104
117
 
105
- Per-adapter help:
106
- ticlawk auth telegram --help
118
+ Options:
119
+ --code <code> 6-digit setup code from the ticlawk app
120
+ --api-url <url> optional ticlawk API base URL override
107
121
  `;
108
122
  }
109
123
 
@@ -132,11 +146,6 @@ function printHelp(helpPath, args = {}) {
132
146
  return;
133
147
  }
134
148
  if (head === 'auth') {
135
- const adapterValue = helpPath[1] || args._?.[1];
136
- if (adapterValue) {
137
- console.log(getAdapterAuthHelp(requireAdapter(adapterValue)));
138
- return;
139
- }
140
149
  console.log(getAuthHelp());
141
150
  return;
142
151
  }
@@ -169,7 +178,6 @@ function parseConfigBoolean(value) {
169
178
  }
170
179
 
171
180
  function printConfigView(config = loadPersistentConfig()) {
172
- console.log(`adapter=${getConfiguredAdapter(config)}`);
173
181
  const view = getStreamingConfigView(config);
174
182
  console.log(`streaming=${formatStreamingValue(view.streaming.value, view.streaming.inherited)}`);
175
183
  for (const runtime of RUNTIME_DEFINITIONS) {
@@ -180,7 +188,6 @@ function printConfigView(config = loadPersistentConfig()) {
180
188
  for (const runtime of getRuntimeExecutableDefinitions()) {
181
189
  console.log(`${runtime.executableCliKey}=${config[runtime.executableConfigKey] || ''}`);
182
190
  }
183
- console.log(`telegram.bot-token=${config.TELEGRAM_BOT_TOKEN ? 'set' : 'unset'}`);
184
191
  console.log(`ticlawk.connector-api-key=${config[TICLAWK_CONNECTOR_API_KEY] ? 'set' : 'unset'}`);
185
192
  console.log(`ticlawk.api-url=${config.TICLAWK_API_URL || ''}`);
186
193
  console.log(`ticlawk.connector-ws-url=${config[TICLAWK_CONNECTOR_WS_URL] || ''}`);
@@ -211,7 +218,7 @@ function getLocal(path) {
211
218
  }
212
219
 
213
220
  function formatSecretConfigValue(configKey, value) {
214
- if (configKey === 'TELEGRAM_BOT_TOKEN' || configKey === TICLAWK_CONNECTOR_API_KEY) {
221
+ if (configKey === TICLAWK_CONNECTOR_API_KEY) {
215
222
  return value ? 'set' : 'unset';
216
223
  }
217
224
  return value || '';
@@ -302,6 +309,75 @@ async function main() {
302
309
  return;
303
310
  }
304
311
 
312
+ // ── Agent-facing subcommands (run inside a runtime; talk to local daemon) ──
313
+ if (command === 'message') {
314
+ const sub = args._[1];
315
+ if (args.help || args.h || !sub) {
316
+ console.log(AGENT_COMMAND_HELP.message);
317
+ return;
318
+ }
319
+ if (sub === 'send') {
320
+ process.exitCode = await runMessageSendCommand(args);
321
+ return;
322
+ }
323
+ if (sub === 'read') {
324
+ process.exitCode = await runMessageReadCommand(args);
325
+ return;
326
+ }
327
+ console.error(`unknown message subcommand: ${sub}`);
328
+ process.exit(1);
329
+ }
330
+
331
+ if (command === 'task') {
332
+ const sub = args._[1];
333
+ if (args.help || args.h || !sub) {
334
+ console.log(AGENT_COMMAND_HELP.task);
335
+ return;
336
+ }
337
+ if (sub === 'claim') {
338
+ process.exitCode = await runTaskClaimCommand(args);
339
+ return;
340
+ }
341
+ if (sub === 'update') {
342
+ process.exitCode = await runTaskUpdateCommand(args);
343
+ return;
344
+ }
345
+ if (sub === 'list') {
346
+ process.exitCode = await runTaskListCommand(args);
347
+ return;
348
+ }
349
+ console.error(`unknown task subcommand: ${sub}`);
350
+ process.exit(1);
351
+ }
352
+
353
+ if (command === 'group') {
354
+ const sub = args._[1];
355
+ if (args.help || args.h || !sub) {
356
+ console.log(AGENT_COMMAND_HELP.group);
357
+ return;
358
+ }
359
+ if (sub === 'members') {
360
+ process.exitCode = await runGroupMembersCommand(args);
361
+ return;
362
+ }
363
+ console.error(`unknown group subcommand: ${sub}`);
364
+ process.exit(1);
365
+ }
366
+
367
+ if (command === 'server') {
368
+ const sub = args._[1];
369
+ if (args.help || args.h || !sub) {
370
+ console.log(AGENT_COMMAND_HELP.server);
371
+ return;
372
+ }
373
+ if (sub === 'info') {
374
+ process.exitCode = await runServerInfoCommand(args);
375
+ return;
376
+ }
377
+ console.error(`unknown server subcommand: ${sub}`);
378
+ process.exit(1);
379
+ }
380
+
305
381
  if (command === 'config') {
306
382
  const subcommand = args._[1];
307
383
  const config = loadPersistentConfig();
@@ -312,10 +388,6 @@ async function main() {
312
388
 
313
389
  if (subcommand === 'get') {
314
390
  const key = args._[2];
315
- if (key === 'adapter') {
316
- console.log(getConfiguredAdapter(config));
317
- return;
318
- }
319
391
  const adapterTarget = normalizeAdapterConfigTarget(key);
320
392
  if (adapterTarget) {
321
393
  console.log(formatSecretConfigValue(adapterTarget.configKey, config[adapterTarget.configKey]));
@@ -340,16 +412,6 @@ async function main() {
340
412
  if (subcommand === 'set') {
341
413
  const key = args._[2];
342
414
  const rawValue = args._[3];
343
- if (key === 'adapter') {
344
- if (!rawValue) {
345
- console.error('config set adapter requires a value');
346
- process.exit(1);
347
- }
348
- const adapterId = requireAdapter(rawValue);
349
- persistConfig({ [AF_ADAPTER_KEY]: adapterId });
350
- console.log(`adapter=${adapterId}`);
351
- return;
352
- }
353
415
  const adapterTarget = normalizeAdapterConfigTarget(key);
354
416
  if (adapterTarget) {
355
417
  if (!rawValue) {
@@ -464,9 +526,8 @@ async function main() {
464
526
  printHelp(['auth'], args);
465
527
  return;
466
528
  }
467
- const adapterId = requireAdapter(args._[1]);
468
- const adapterArgv = rawArgv.slice(2);
469
- const res = await runAdapterAuth(adapterId, adapterArgv);
529
+ const adapterArgv = rawArgv.slice(1);
530
+ const res = await runAdapterAuth('ticlawk', adapterArgv);
470
531
  printCommandResult(res);
471
532
  process.exitCode = res.statusCode >= 400 ? 1 : 0;
472
533
  return;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ticlawk",
3
- "version": "0.1.15-dev.6",
4
- "description": "Connect local agent harnesses to Ticlawk, Telegram, and other mobile clients.",
3
+ "version": "0.1.16-dev.1",
4
+ "description": "Local connector that links agent harnesses (Claude Code, Codex, OpenClaw, opencode, Pi) to the Ticlawk mobile app.",
5
5
  "type": "module",
6
6
  "main": "ticlawk.mjs",
7
7
  "exports": {
@@ -48,9 +48,6 @@
48
48
  "codex",
49
49
  "agent",
50
50
  "ai-agent",
51
- "telegram",
52
- "telegram-bot",
53
- "chatops",
54
51
  "remote-agent",
55
52
  "mobile",
56
53
  "bridge",
@@ -150,26 +150,21 @@ export async function updateChannel(id, updates) {
150
150
  }
151
151
 
152
152
  export async function postRuntimeResult(body) {
153
+ // Activity-only: backend records the agent_event for UI trajectory
154
+ // display but does NOT project it into a chat message. The agent CLI's
155
+ // `ticlawk message send` is the only path that produces chat rows.
153
156
  const result = await apiFetch('/api/runtime-results', {
154
157
  method: 'POST',
155
158
  body: JSON.stringify(body),
156
159
  timeout: 30000,
157
160
  });
158
- if (result?.matched === false) {
159
- throw new Error('runtime result was not matched');
160
- }
161
161
  return result;
162
162
  }
163
163
 
164
- // ── Messages ──
165
-
166
- export async function getPendingMessages() {
167
- const { data } = await apiFetch('/api/messages/pending');
168
- return data || [];
169
- }
164
+ // ── Deliveries (replaces legacy message_jobs poll/ack) ──
170
165
 
171
- export async function claimPendingMessages(hostId, limit = 5, excludedAgentIds = []) {
172
- const { data } = await apiFetch('/api/messages/claim-pending', {
166
+ export async function claimPendingDeliveries(hostId, limit = 5, excludedAgentIds = []) {
167
+ const { data } = await apiFetch('/api/agent/deliveries/claim-pending', {
173
168
  method: 'POST',
174
169
  body: JSON.stringify(withTiclawkVersion({
175
170
  runtime_host_id: hostId,
@@ -180,38 +175,110 @@ export async function claimPendingMessages(hostId, limit = 5, excludedAgentIds =
180
175
  return data || [];
181
176
  }
182
177
 
183
- export async function syncMessages(rows) {
184
- return apiFetch('/api/messages/sync', {
178
+ export async function completeDelivery(deliveryId, hostId) {
179
+ return apiFetch(`/api/agent/deliveries/${deliveryId}/complete`, {
185
180
  method: 'POST',
186
- body: JSON.stringify({ rows }),
181
+ body: JSON.stringify({ runtime_host_id: hostId }),
187
182
  });
188
183
  }
189
184
 
190
- export async function claimMessage(id, hostId) {
191
- return apiFetch(`/api/messages/${id}/claim`, {
185
+ export async function releaseDelivery(deliveryId, hostId, reason = null) {
186
+ return apiFetch(`/api/agent/deliveries/${deliveryId}/release`, {
192
187
  method: 'POST',
193
- body: JSON.stringify(withTiclawkVersion({ runtime_host_id: hostId })),
188
+ body: JSON.stringify({ runtime_host_id: hostId, reason }),
194
189
  });
195
190
  }
196
191
 
197
- export async function releaseMessage(id, hostId) {
198
- return apiFetch(`/api/messages/${id}/release`, {
192
+ // ── Agent-facing CLI surface (group chat tool path) ──
193
+
194
+ export async function sendAgentMessage({
195
+ actingAgentId,
196
+ conversationId,
197
+ text,
198
+ seenUpToSeq,
199
+ replyToMessageId,
200
+ runtimeHostId,
201
+ }) {
202
+ const { data } = await apiFetch('/api/agent/messages/send', {
199
203
  method: 'POST',
200
- body: JSON.stringify({ runtime_host_id: hostId }),
204
+ body: JSON.stringify({
205
+ acting_as_agent_id: actingAgentId,
206
+ conversation_id: conversationId,
207
+ text,
208
+ seen_up_to_seq: seenUpToSeq ?? null,
209
+ reply_to_message_id: replyToMessageId ?? null,
210
+ runtime_host_id: runtimeHostId ?? null,
211
+ }),
201
212
  });
213
+ return data || null;
202
214
  }
203
215
 
204
- export async function completeMessage(id, hostId) {
205
- return apiFetch(`/api/messages/${id}/complete`, {
216
+ export async function readAgentMessages({
217
+ actingAgentId,
218
+ conversationId,
219
+ aroundMessageId,
220
+ beforeSeq,
221
+ limit,
222
+ }) {
223
+ const params = new URLSearchParams();
224
+ params.set('acting_as_agent_id', actingAgentId);
225
+ params.set('conversation_id', conversationId);
226
+ if (aroundMessageId) params.set('around_message_id', aroundMessageId);
227
+ if (beforeSeq != null) params.set('before_seq', String(beforeSeq));
228
+ if (limit != null) params.set('limit', String(limit));
229
+ const { data } = await apiFetch(`/api/agent/messages/read?${params}`);
230
+ return data || [];
231
+ }
232
+
233
+ export async function claimAgentTask({ actingAgentId, sourceMessageId, leaseSeconds }) {
234
+ return apiFetch('/api/agent/tasks/claim', {
206
235
  method: 'POST',
207
- body: JSON.stringify({ runtime_host_id: hostId }),
236
+ body: JSON.stringify({
237
+ acting_as_agent_id: actingAgentId,
238
+ source_message_id: sourceMessageId,
239
+ lease_seconds: leaseSeconds ?? null,
240
+ }),
208
241
  });
209
242
  }
210
243
 
211
- export async function recoverClaimedMessages(hostId) {
212
- return apiFetch('/api/messages/recover', {
244
+ export async function updateAgentTask({ actingAgentId, taskId, status }) {
245
+ return apiFetch('/api/agent/tasks/update', {
213
246
  method: 'POST',
214
- body: JSON.stringify(withTiclawkVersion({ runtime_host_id: hostId })),
247
+ body: JSON.stringify({
248
+ acting_as_agent_id: actingAgentId,
249
+ task_id: taskId,
250
+ status,
251
+ }),
252
+ });
253
+ }
254
+
255
+ export async function listAgentTasks({ actingAgentId, conversationId }) {
256
+ const params = new URLSearchParams();
257
+ params.set('acting_as_agent_id', actingAgentId);
258
+ if (conversationId) params.set('conversation_id', conversationId);
259
+ const { data } = await apiFetch(`/api/agent/tasks/list?${params}`);
260
+ return data || [];
261
+ }
262
+
263
+ export async function getConversationMembers({ actingAgentId, conversationId }) {
264
+ const params = new URLSearchParams();
265
+ params.set('acting_as_agent_id', actingAgentId);
266
+ const res = await apiFetch(`/api/agent/conversations/${conversationId}/members?${params}`);
267
+ return res || { data: [], conversation: null };
268
+ }
269
+
270
+ export async function getAgentServerInfo({ actingAgentId }) {
271
+ const params = new URLSearchParams();
272
+ params.set('acting_as_agent_id', actingAgentId);
273
+ return apiFetch(`/api/agent/server-info?${params}`);
274
+ }
275
+
276
+ // ── Transcript history ──
277
+
278
+ export async function syncMessages(rows) {
279
+ return apiFetch('/api/messages/sync', {
280
+ method: 'POST',
281
+ body: JSON.stringify({ rows }),
215
282
  });
216
283
  }
217
284
 
@@ -6,13 +6,12 @@
6
6
  * using the connector-specific env name.
7
7
  */
8
8
 
9
- import { AF_ADAPTER_KEY, AF_CONFIG_PATH, persistConfig, TICLAWK_CONNECTOR_API_KEY } from '../../core/config.mjs';
9
+ import { AF_CONFIG_PATH, persistConfig, TICLAWK_CONNECTOR_API_KEY } from '../../core/config.mjs';
10
10
 
11
11
  export function persistApiCredential(apiKey) {
12
12
  if (!apiKey || !apiKey.startsWith('tk_')) return;
13
13
 
14
14
  persistConfig({
15
- [AF_ADAPTER_KEY]: 'ticlawk',
16
15
  [TICLAWK_CONNECTOR_API_KEY]: apiKey,
17
16
  TICLAWK_SETUP_CODE: '',
18
17
  });
@@ -2,7 +2,7 @@ import { parseOptionArgs } from '../../core/argv.mjs';
2
2
  import { createHash, randomBytes } from 'node:crypto';
3
3
  import { createRequire } from 'node:module';
4
4
  import { basename } from 'node:path';
5
- import { AF_ADAPTER_KEY, loadPersistentConfig, persistConfig, TICLAWK_CONNECTOR_API_KEY, TICLAWK_CONNECTOR_WS_URL } from '../../core/config.mjs';
5
+ import { loadPersistentConfig, persistConfig, TICLAWK_CONNECTOR_API_KEY, TICLAWK_CONNECTOR_WS_URL } from '../../core/config.mjs';
6
6
  import { belongsToRuntimeHost, getBindingRuntimeHostId, getHostId, getHostLabel } from '../../core/host-id.mjs';
7
7
  import { debugError, debugLog } from '../../core/logger.mjs';
8
8
  import { getActiveProfile, ensureLegacyProfile, readProfileMeta, saveAndActivateProfile } from '../../core/profiles.mjs';
@@ -45,13 +45,62 @@ function normalizeInboundMediaAssets(msg) {
45
45
  .filter(Boolean);
46
46
  }
47
47
 
48
- function normalizeInboundMessage(msg) {
48
+ function shortMsgId(value) {
49
+ return value ? String(value).slice(0, 8) : '';
50
+ }
51
+
52
+ function buildEnvelopeTarget(msg) {
53
+ const convType = msg.conversation_type || 'dm';
54
+ const conversationId = msg.conversation_id || '';
55
+ const senderHandle = msg.sender_display_name || msg.sender_user_id || msg.sender_agent_id || '';
56
+ if (convType === 'dm') {
57
+ return senderHandle ? `dm:@${senderHandle}` : `dm:${conversationId}`;
58
+ }
59
+ if (convType === 'thread') {
60
+ // For thread deliveries the message itself is in-thread; reply target
61
+ // is the parent group's id + the thread root's short id. The RPC
62
+ // returns conversation_type='thread' only when the message lives in
63
+ // a thread conversation directly, which we represent as
64
+ // `<group>:<thread-root>`.
65
+ const groupName = msg.conversation_name || conversationId;
66
+ const threadRoot = shortMsgId(msg.thread_root_message_id || msg.message_id);
67
+ return `#${groupName}:${threadRoot}`;
68
+ }
69
+ // group
70
+ const groupName = msg.conversation_name || conversationId;
71
+ return `#${groupName}`;
72
+ }
73
+
74
+ function buildEnvelopeHeader(msg) {
75
+ const target = buildEnvelopeTarget(msg);
76
+ const msgId = shortMsgId(msg.id || msg.message_id);
77
+ const seq = msg.seq != null ? msg.seq : '';
78
+ const time = msg.created_at || new Date().toISOString();
79
+ const type = msg.sender_type || 'human';
80
+ const sender = msg.sender_display_name || msg.sender_user_id || msg.sender_agent_id || 'unknown';
81
+ return `[target=${target} msg=${msgId} seq=${seq} time=${time} type=${type}] @${sender}:`;
82
+ }
83
+
84
+ export function normalizeInboundMessage(msg) {
85
+ // Claimed delivery rows carry the recipient agent id; legacy messages
86
+ // (history sync, manual inserts) may still use plain agent_id. Prefer
87
+ // the new field but fall back so the same normalizer works for both.
88
+ const recipientAgentId = msg.recipient_agent_id || msg.agent_id || '';
49
89
  const messageId = msg.id || msg.message_id || null;
90
+ const deliveryId = msg.delivery_id || null;
50
91
  const media = normalizeInboundMediaAssets(msg);
92
+ const rawText = msg.text || '';
93
+ const header = buildEnvelopeHeader({ ...msg, id: messageId });
51
94
  return {
52
- bindingId: msg.agent_id || '',
95
+ bindingId: recipientAgentId,
53
96
  messageId,
54
- text: msg.text || '',
97
+ deliveryId,
98
+ conversationId: msg.conversation_id || null,
99
+ seq: msg.seq != null ? Number(msg.seq) : null,
100
+ senderType: msg.sender_type || 'human',
101
+ envelopeHeader: header,
102
+ text: header ? `${header} ${rawText}` : rawText,
103
+ rawText,
55
104
  action: msg.action || (media.length > 0 ? 'image' : 'task'),
56
105
  media,
57
106
  raw: {
@@ -95,7 +144,14 @@ function getAgentIdFromPayload(payload) {
95
144
  }
96
145
 
97
146
  function buildBindingFromClaimedMessage(msg) {
98
- const agentId = getAgentIdFromPayload(msg);
147
+ // Delivery rows expose the recipient agent under recipient_agent_id;
148
+ // older code paths may pass plain agent_id. Either works.
149
+ const agentId = String(
150
+ msg.recipient_agent_id
151
+ || msg.agent_id
152
+ || msg.agentId
153
+ || ''
154
+ ).trim();
99
155
  const runtime = msg.agent_service_type || msg.service_type;
100
156
  const rawMeta = msg.agent_meta || msg.meta;
101
157
  const sourceMeta = (rawMeta && typeof rawMeta === 'object') ? { ...rawMeta } : {};
@@ -458,12 +514,14 @@ export function createTiclawkAdapter(ctx) {
458
514
 
459
515
  async function processPendingMessagesForAgent(agentId, messages) {
460
516
  for (const msg of messages) {
517
+ const deliveryId = msg.delivery_id;
461
518
  try {
462
519
  const messageHostId = getRuntimeHostIdFromPayload(msg);
463
520
  if (messageHostId && messageHostId !== hostId) {
464
- await api.releaseMessage(msg.message_id, hostId);
521
+ await api.releaseDelivery(deliveryId, hostId, 'host-mismatch');
465
522
  debugError('ticlawk', 'message.host-mismatch', {
466
523
  agentId,
524
+ deliveryId,
467
525
  messageId: msg.message_id,
468
526
  hostId,
469
527
  runtime_host_id: messageHostId,
@@ -476,9 +534,10 @@ export function createTiclawkAdapter(ctx) {
476
534
  throw new Error('claimed message missing runtime binding');
477
535
  }
478
536
  if (!belongsToRuntimeHost(binding, hostId)) {
479
- await api.releaseMessage(msg.message_id, hostId);
537
+ await api.releaseDelivery(deliveryId, hostId, 'binding-host-mismatch');
480
538
  debugError('ticlawk', 'message.binding-host-mismatch', {
481
539
  agentId,
540
+ deliveryId,
482
541
  messageId: msg.message_id,
483
542
  hostId,
484
543
  runtime_host_id: getBindingRuntimeHostId(binding),
@@ -489,10 +548,11 @@ export function createTiclawkAdapter(ctx) {
489
548
  const completed = await ctx.bus.dispatchToAgent(binding.runtime, binding.id, normalizeInboundMessage(msg));
490
549
  if (completed !== true) {
491
550
  if (isTerminalRuntimeFailure(completed)) {
492
- await api.completeMessage(msg.message_id, hostId);
551
+ await api.completeDelivery(deliveryId, hostId);
493
552
  void requestDrain('message.terminal-completed');
494
553
  debugError('ticlawk', 'message.terminal-failed', {
495
554
  agentId,
555
+ deliveryId,
496
556
  messageId: msg.message_id,
497
557
  runtime: binding.runtime,
498
558
  runtimeVersion: getRuntimeVersion(binding),
@@ -502,10 +562,11 @@ export function createTiclawkAdapter(ctx) {
502
562
  }
503
563
  throw new Error('runtime did not complete turn');
504
564
  }
505
- await api.completeMessage(msg.message_id, hostId);
565
+ await api.completeDelivery(deliveryId, hostId);
506
566
  void requestDrain('message.completed');
507
567
  debugLog('ticlawk', 'message.completed', {
508
568
  agentId,
569
+ deliveryId,
509
570
  messageId: msg.message_id,
510
571
  runtime: binding.runtime,
511
572
  runtimeVersion: getRuntimeVersion(binding),
@@ -515,7 +576,7 @@ export function createTiclawkAdapter(ctx) {
515
576
  recordUpdateRequired(err, 'message.dispatch');
516
577
  }
517
578
  try {
518
- await api.releaseMessage(msg.message_id, hostId);
579
+ await api.releaseDelivery(deliveryId, hostId, 'dispatch-error');
519
580
  } catch (releaseErr) {
520
581
  debugError('ticlawk', 'message.release-failed', {
521
582
  agentId,
@@ -592,13 +653,14 @@ export function createTiclawkAdapter(ctx) {
592
653
 
593
654
  async function releaseBlockedRows(agentId, messages, reason) {
594
655
  for (const msg of messages) {
595
- if (!msg?.message_id) continue;
656
+ if (!msg?.delivery_id) continue;
596
657
  try {
597
- await api.releaseMessage(msg.message_id, hostId);
658
+ await api.releaseDelivery(msg.delivery_id, hostId, reason);
598
659
  } catch (err) {
599
660
  debugError('ticlawk', 'claim.blocked-release-failed', {
600
661
  reason,
601
662
  agentId,
663
+ deliveryId: msg.delivery_id,
602
664
  messageId: msg.message_id,
603
665
  error: err?.message || 'unknown error',
604
666
  });
@@ -620,7 +682,7 @@ export function createTiclawkAdapter(ctx) {
620
682
  return { failed: true, claimed: 0, launched: 0, updateRequired: true };
621
683
  }
622
684
  try {
623
- data = await api.claimPendingMessages(hostId, 5, excludedChannelIds);
685
+ data = await api.claimPendingDeliveries(hostId, 5, excludedChannelIds);
624
686
  clearUpdateRequired('claim');
625
687
  } catch (err) {
626
688
  if (api.isUpdateRequiredError(err)) {
@@ -954,24 +1016,9 @@ export function createTiclawkAdapter(ctx) {
954
1016
  id: 'ticlawk',
955
1017
 
956
1018
  async start() {
957
- try {
958
- const recovered = await api.recoverClaimedMessages(hostId);
959
- if (recovered?.recoveredCount) {
960
- debugLog('ticlawk', 'message.recovered', {
961
- recoveredCount: recovered.recoveredCount,
962
- hostId,
963
- });
964
- }
965
- } catch (err) {
966
- if (api.isUpdateRequiredError(err)) {
967
- recordUpdateRequired(err, 'recover');
968
- }
969
- debugError('ticlawk', 'message.recover-failed', {
970
- hostId,
971
- error: err?.message || 'unknown error',
972
- });
973
- }
974
-
1019
+ // Stale-delivery recovery is handled server-side via lease expiry
1020
+ // (message_deliveries.claimed_at + lease_seconds). The connector no
1021
+ // longer issues an explicit recover call at startup.
975
1022
  await refreshBindings('startup');
976
1023
  await requestDrain('startup');
977
1024
  connectWakeSocket();
@@ -1234,7 +1281,6 @@ export async function runTiclawkAuth(rawArgs) {
1234
1281
  };
1235
1282
  }
1236
1283
  const updates = {
1237
- [AF_ADAPTER_KEY]: 'ticlawk',
1238
1284
  TICLAWK_SETUP_CODE: code,
1239
1285
  };
1240
1286
  const apiUrl = String(args['api-url'] || '').trim();