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/README.md +83 -212
- package/bin/ticlawk.mjs +107 -46
- package/package.json +2 -5
- package/src/adapters/ticlawk/api.mjs +93 -26
- package/src/adapters/ticlawk/credentials.mjs +1 -2
- package/src/adapters/ticlawk/index.mjs +78 -32
- package/src/cli/agent-commands.mjs +290 -0
- package/src/core/adapter-registry.mjs +12 -28
- package/src/core/agent-cli-handlers.mjs +291 -0
- package/src/core/config.mjs +0 -15
- package/src/core/http.mjs +90 -18
- package/src/core/runtime-env.mjs +41 -5
- package/src/core/runtime-support.mjs +16 -1
- package/src/core/ticlawk-control.mjs +4 -3
- package/src/runtimes/_shared/standing-prompt.mjs +89 -0
- package/src/runtimes/claude-code/index.mjs +26 -7
- package/src/runtimes/claude-code/session.mjs +15 -7
- package/src/runtimes/codex/index.mjs +18 -6
- package/src/runtimes/codex/session.mjs +9 -5
- package/src/runtimes/openclaw/index.mjs +22 -3
- package/src/runtimes/opencode/index.mjs +19 -6
- package/src/runtimes/opencode/session.mjs +11 -2
- package/src/runtimes/pi/index.mjs +19 -6
- package/src/runtimes/pi/session.mjs +8 -2
- package/ticlawk.mjs +6 -4
- package/assets/ticlawk-concept.svg +0 -137
- package/src/adapters/telegram/index.mjs +0 -359
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 {
|
|
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 ['
|
|
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 <
|
|
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
|
|
71
|
-
connect connect
|
|
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
|
|
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
|
-
|
|
103
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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 ===
|
|
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
|
|
468
|
-
const
|
|
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.
|
|
4
|
-
"description": "
|
|
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
|
-
// ──
|
|
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
|
|
172
|
-
const { data } = await apiFetch('/api/
|
|
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
|
|
184
|
-
return apiFetch(
|
|
178
|
+
export async function completeDelivery(deliveryId, hostId) {
|
|
179
|
+
return apiFetch(`/api/agent/deliveries/${deliveryId}/complete`, {
|
|
185
180
|
method: 'POST',
|
|
186
|
-
body: JSON.stringify({
|
|
181
|
+
body: JSON.stringify({ runtime_host_id: hostId }),
|
|
187
182
|
});
|
|
188
183
|
}
|
|
189
184
|
|
|
190
|
-
export async function
|
|
191
|
-
return apiFetch(`/api/
|
|
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(
|
|
188
|
+
body: JSON.stringify({ runtime_host_id: hostId, reason }),
|
|
194
189
|
});
|
|
195
190
|
}
|
|
196
191
|
|
|
197
|
-
|
|
198
|
-
|
|
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({
|
|
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
|
|
205
|
-
|
|
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({
|
|
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
|
|
212
|
-
return apiFetch('/api/
|
|
244
|
+
export async function updateAgentTask({ actingAgentId, taskId, status }) {
|
|
245
|
+
return apiFetch('/api/agent/tasks/update', {
|
|
213
246
|
method: 'POST',
|
|
214
|
-
body: JSON.stringify(
|
|
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 {
|
|
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 {
|
|
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
|
|
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:
|
|
95
|
+
bindingId: recipientAgentId,
|
|
53
96
|
messageId,
|
|
54
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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?.
|
|
656
|
+
if (!msg?.delivery_id) continue;
|
|
596
657
|
try {
|
|
597
|
-
await api.
|
|
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.
|
|
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
|
-
|
|
958
|
-
|
|
959
|
-
|
|
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();
|