ticlawk 0.1.17-dev.2 → 0.1.17-dev.21
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 +26 -59
- package/bin/ticlawk.mjs +31 -301
- package/package.json +4 -2
- package/scripts/publish-dev.sh +77 -0
- package/src/adapters/ticlawk/api.mjs +50 -378
- package/src/adapters/ticlawk/credentials.mjs +1 -43
- package/src/adapters/ticlawk/index.mjs +61 -565
- package/src/adapters/ticlawk/wake-client.mjs +1 -1
- package/src/cli/agent-commands.mjs +18 -715
- package/src/core/adapter-registry.mjs +1 -19
- package/src/core/agent-cli-handlers.mjs +18 -556
- package/src/core/agent-home.mjs +1 -81
- package/src/core/daemon-install.mjs +5 -7
- package/src/core/events/worker-events.mjs +36 -32
- package/src/core/http.mjs +0 -152
- package/src/core/profiles.mjs +0 -1
- package/src/core/runtime-contract.mjs +1 -0
- package/src/core/runtime-env.mjs +0 -8
- package/src/core/runtime-support.mjs +78 -130
- package/src/runtimes/_shared/incoming-message-prompt.mjs +232 -0
- package/src/runtimes/_shared/runtime-base-instructions.mjs +34 -0
- package/src/runtimes/claude-code/index.mjs +48 -21
- package/src/runtimes/claude-code/session.mjs +7 -2
- package/src/runtimes/codex/index.mjs +64 -116
- package/src/runtimes/codex/session.mjs +12 -2
- package/src/runtimes/openclaw/index.mjs +30 -17
- package/src/runtimes/opencode/index.mjs +64 -42
- package/src/runtimes/opencode/session.mjs +14 -14
- package/src/runtimes/pi/index.mjs +64 -42
- package/src/runtimes/pi/session.mjs +8 -11
- package/ticlawk.mjs +32 -5
- package/src/runtimes/_shared/agent-handbook.mjs +0 -45
- package/src/runtimes/_shared/brand.mjs +0 -2
- package/src/runtimes/_shared/goal-step-prompt.mjs +0 -98
- package/src/runtimes/_shared/goal-task-protocol.mjs +0 -50
- package/src/runtimes/_shared/handbook/BASICS.md +0 -27
- package/src/runtimes/_shared/handbook/COLLABORATION.md +0 -37
- package/src/runtimes/_shared/handbook/COMMUNICATION.md +0 -55
- package/src/runtimes/_shared/handbook/DM_SCOPE.md +0 -13
- package/src/runtimes/_shared/handbook/GOAL_AUTHORITY.md +0 -47
- package/src/runtimes/_shared/handbook/GOAL_TASK_CORE.md +0 -43
- package/src/runtimes/_shared/handbook/GROUP_ADMIN_SCOPE.md +0 -21
- package/src/runtimes/_shared/handbook/GROUP_MEMBER_SCOPE.md +0 -15
- package/src/runtimes/_shared/handbook/SURFACES.md +0 -41
- package/src/runtimes/_shared/handbook/TASK_WORKER.md +0 -14
- package/src/runtimes/_shared/standing-prompt.mjs +0 -171
- package/src/runtimes/_shared/wake-prompt.mjs +0 -268
|
@@ -1,25 +1,21 @@
|
|
|
1
|
-
import { parseOptionArgs } from '../../core/argv.mjs';
|
|
2
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
3
2
|
import { createRequire } from 'node:module';
|
|
4
|
-
import { basename } from 'node:path';
|
|
5
3
|
import { loadPersistentConfig, persistConfig, TICLAWK_CONNECTOR_API_KEY, TICLAWK_CONNECTOR_WS_URL } from '../../core/config.mjs';
|
|
6
4
|
import { belongsToRuntimeHost, getBindingRuntimeHostId, getHostId, getHostLabel } from '../../core/host-id.mjs';
|
|
7
5
|
import { debugError, debugLog } from '../../core/logger.mjs';
|
|
8
|
-
import { getActiveProfile,
|
|
6
|
+
import { getActiveProfile, saveAndActivateProfile } from '../../core/profiles.mjs';
|
|
9
7
|
import { isTerminalRuntimeFailure } from '../../core/runtime-support.mjs';
|
|
10
8
|
import { clearUpdateRequiredState, readUpdateState, setUpdateRequiredState } from '../../core/update-state.mjs';
|
|
11
9
|
import { isManagedInstall, startDetachedSelfUpdate } from '../../core/update.mjs';
|
|
10
|
+
import { buildIncomingMessagePrompt } from '../../runtimes/_shared/incoming-message-prompt.mjs';
|
|
12
11
|
import * as api from './api.mjs';
|
|
13
|
-
import { persistApiCredential
|
|
12
|
+
import { persistApiCredential } from './credentials.mjs';
|
|
14
13
|
import { TiclawkWakeClient } from './wake-client.mjs';
|
|
15
|
-
import { buildInboundWakePrompt } from '../../runtimes/_shared/wake-prompt.mjs';
|
|
16
|
-
import { buildGoalStepPrompt } from '../../runtimes/_shared/goal-step-prompt.mjs';
|
|
17
14
|
|
|
18
15
|
const require = createRequire(import.meta.url);
|
|
19
16
|
const qrcode = require('qrcode-terminal');
|
|
20
17
|
const JOBS_WAKE_DEBOUNCE_MS = 100;
|
|
21
18
|
const BINDINGS_WAKE_DEBOUNCE_MS = 500;
|
|
22
|
-
const CREDENTIALS_WAKE_DEBOUNCE_MS = 500;
|
|
23
19
|
const RECOVERY_AUDIT_INTERVAL_MS = 5 * 60 * 1000;
|
|
24
20
|
const BINDING_AUDIT_INTERVAL_MS = 5 * 60 * 1000;
|
|
25
21
|
const UPDATE_RETRY_COOLDOWN_MS = 15 * 60 * 1000;
|
|
@@ -73,35 +69,28 @@ function normalizeInboundMediaAssets(msg) {
|
|
|
73
69
|
}
|
|
74
70
|
|
|
75
71
|
export function normalizeInboundMessage(msg) {
|
|
76
|
-
// Claimed delivery rows carry the recipient agent id; legacy messages
|
|
77
|
-
// (history sync, manual inserts) may still use plain agent_id. Prefer
|
|
78
|
-
// the new field but fall back so the same normalizer works for both.
|
|
79
72
|
const recipientAgentId = msg.recipient_agent_id || '';
|
|
80
73
|
const messageId = msg.id || msg.message_id || null;
|
|
81
74
|
const deliveryId = msg.delivery_id || null;
|
|
82
75
|
const media = normalizeInboundMediaAssets(msg);
|
|
83
|
-
const
|
|
84
|
-
const isTransition = msg.reason === 'transition';
|
|
85
|
-
// A transition delivery is a goal-lane FSM step, not a chat message: it has
|
|
86
|
-
// no backing message, so build a per-step goal prompt from its payload.
|
|
87
|
-
const wakePrompt = isTransition ? buildGoalStepPrompt(enriched) : buildInboundWakePrompt(enriched);
|
|
76
|
+
const prompt = buildIncomingMessagePrompt({ ...msg, id: messageId });
|
|
88
77
|
return {
|
|
89
78
|
bindingId: recipientAgentId,
|
|
90
79
|
messageId,
|
|
91
80
|
deliveryId,
|
|
92
81
|
conversationId: msg.conversation_id || null,
|
|
93
|
-
lane: isTransition ? 'goal' : 'chat',
|
|
94
82
|
seq: msg.seq != null ? Number(msg.seq) : null,
|
|
95
83
|
senderType: msg.sender_type || 'human',
|
|
96
|
-
envelopeHeader:
|
|
97
|
-
envelopeTarget:
|
|
98
|
-
text:
|
|
99
|
-
rawText:
|
|
100
|
-
action:
|
|
84
|
+
envelopeHeader: prompt.header,
|
|
85
|
+
envelopeTarget: prompt.target,
|
|
86
|
+
text: prompt.text,
|
|
87
|
+
rawText: prompt.rawText,
|
|
88
|
+
action: msg.action || (media.length > 0 ? 'image' : 'task'),
|
|
101
89
|
media,
|
|
102
90
|
raw: {
|
|
103
91
|
...msg,
|
|
104
92
|
id: messageId,
|
|
93
|
+
type: prompt.type,
|
|
105
94
|
},
|
|
106
95
|
};
|
|
107
96
|
}
|
|
@@ -129,40 +118,18 @@ function getRuntimeHostLabelFromPayload(payload) {
|
|
|
129
118
|
|
|
130
119
|
function getAgentIdFromPayload(payload) {
|
|
131
120
|
// claim_pending_deliveries is the canonical source and returns
|
|
132
|
-
// `recipient_agent_id`.
|
|
133
|
-
// `agentId`) were left in by an earlier partial cleanup but they
|
|
134
|
-
// never fire against the current schema — dead branches.
|
|
121
|
+
// `recipient_agent_id`.
|
|
135
122
|
return String(payload?.recipient_agent_id || '').trim();
|
|
136
123
|
}
|
|
137
124
|
|
|
138
|
-
// Two lanes run concurrently per agent (see the lane-aware claim RPC):
|
|
139
|
-
// the goal lane carries FSM transition deliveries (reason='transition'),
|
|
140
|
-
// everything else is the user-facing chat lane. The claim unit and the
|
|
141
|
-
// daemon's in-flight key are the (agent, lane) channel, so a running chat
|
|
142
|
-
// turn and a running goal transition never block each other.
|
|
143
|
-
function getDeliveryLaneFromPayload(payload) {
|
|
144
|
-
return payload?.reason === 'transition' ? 'goal' : 'chat';
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function channelKeyFor(agentId, lane) {
|
|
148
|
-
return `${agentId}:${lane}`;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
125
|
// Whitelist of meta keys runtimes actually consume. Anything else in
|
|
152
|
-
// the source row's meta blob is dropped
|
|
153
|
-
// (like the post-Y2 workdir/cwd/projectDir leftovers) can't sneak
|
|
154
|
-
// back into runtimeMeta and confuse downstream code.
|
|
126
|
+
// the source row's meta blob is dropped before reaching runtimeMeta.
|
|
155
127
|
const RUNTIME_META_KEYS = [
|
|
156
128
|
'sessionId',
|
|
157
129
|
'path',
|
|
158
130
|
'runtimePath',
|
|
159
131
|
'rotatePending',
|
|
160
132
|
'lastRotatedAt',
|
|
161
|
-
// Per-lane scoped session maps (keyed by conversation): the chat lane and
|
|
162
|
-
// the goal-FSM lane keep separate runtime sessions so a transition turn
|
|
163
|
-
// never resumes a user-chat session or vice-versa.
|
|
164
|
-
'chatSessions',
|
|
165
|
-
'goalSessions',
|
|
166
133
|
// claude_code
|
|
167
134
|
'claudePath',
|
|
168
135
|
'claudeVersion',
|
|
@@ -249,24 +216,6 @@ function maskIdentity(identity = {}) {
|
|
|
249
216
|
};
|
|
250
217
|
}
|
|
251
218
|
|
|
252
|
-
function formatIdentityLines(identity = {}) {
|
|
253
|
-
const normalized = maskIdentity(identity);
|
|
254
|
-
return [
|
|
255
|
-
` user: ${normalized.userId || 'unknown'}`,
|
|
256
|
-
normalized.emailMasked ? ` email: ${normalized.emailMasked}` : null,
|
|
257
|
-
normalized.phoneMasked ? ` phone: ${normalized.phoneMasked}` : null,
|
|
258
|
-
].filter(Boolean);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function formatAffectedAgents(agents = []) {
|
|
262
|
-
if (!agents.length) return [' - none found for this host'];
|
|
263
|
-
return agents.map((agent) => {
|
|
264
|
-
const display = agent.display_name || agent.name || agent.id;
|
|
265
|
-
const runtime = agent.service_type || 'unknown';
|
|
266
|
-
return ` - ${display} (${runtime}) ${agent.id}`;
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
|
|
270
219
|
function sha256Hex(value) {
|
|
271
220
|
return createHash('sha256').update(String(value)).digest('hex');
|
|
272
221
|
}
|
|
@@ -275,88 +224,6 @@ function makeClientSecret() {
|
|
|
275
224
|
return randomBytes(32).toString('base64url');
|
|
276
225
|
}
|
|
277
226
|
|
|
278
|
-
function getRuntimeWorkdir(runtimeMeta = {}) {
|
|
279
|
-
return String(runtimeMeta.workdir || runtimeMeta.cwd || runtimeMeta.projectDir || '').trim();
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function compareStrings(a, b) {
|
|
283
|
-
const av = String(a || '');
|
|
284
|
-
const bv = String(b || '');
|
|
285
|
-
if (av < bv) return -1;
|
|
286
|
-
if (av > bv) return 1;
|
|
287
|
-
return 0;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function compareNumbers(a, b) {
|
|
291
|
-
const av = Number(a);
|
|
292
|
-
const bv = Number(b);
|
|
293
|
-
const aFinite = Number.isFinite(av);
|
|
294
|
-
const bFinite = Number.isFinite(bv);
|
|
295
|
-
if (aFinite && bFinite && av !== bv) return av - bv;
|
|
296
|
-
if (aFinite !== bFinite) return aFinite ? -1 : 1;
|
|
297
|
-
return 0;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function compareClaimedDeliveries(a, b) {
|
|
301
|
-
return compareStrings(a?.created_at, b?.created_at)
|
|
302
|
-
|| compareStrings(a?.conversation_id, b?.conversation_id)
|
|
303
|
-
|| compareNumbers(a?.seq, b?.seq)
|
|
304
|
-
|| compareStrings(a?.delivery_id, b?.delivery_id);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function runtimeLabel(runtime) {
|
|
308
|
-
if (runtime === 'claude_code') return 'Claude Code';
|
|
309
|
-
if (runtime === 'opencode') return 'OpenCode';
|
|
310
|
-
if (runtime === 'openclaw') return 'OpenClaw';
|
|
311
|
-
if (runtime === 'codex') return 'Codex';
|
|
312
|
-
if (runtime === 'pi') return 'Pi';
|
|
313
|
-
return runtime || 'Agent';
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function printDetectedRuntimeOptions(runtimeOptions = []) {
|
|
317
|
-
console.log('Detected agent harness:');
|
|
318
|
-
if (!runtimeOptions.length) {
|
|
319
|
-
console.log(' none');
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
for (const option of runtimeOptions) {
|
|
323
|
-
const label = option.runtime_label || runtimeLabel(option.runtime);
|
|
324
|
-
const workdir = option.workdir ? ` (${option.workdir})` : '';
|
|
325
|
-
console.log(` - ${label}${workdir}`);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
async function buildAutoRuntimeOptions(ctx, payload = {}) {
|
|
330
|
-
const workdir = getRuntimeWorkdir(payload) || process.cwd();
|
|
331
|
-
const candidates = ['codex', 'claude_code', 'opencode', 'pi'];
|
|
332
|
-
const options = [];
|
|
333
|
-
for (const serviceType of candidates) {
|
|
334
|
-
try {
|
|
335
|
-
const resolved = await ctx.resolveRuntimeBinding({
|
|
336
|
-
...payload,
|
|
337
|
-
serviceType,
|
|
338
|
-
workdir,
|
|
339
|
-
});
|
|
340
|
-
const runtimeMeta = resolved.runtimeMeta || {};
|
|
341
|
-
const optionWorkdir = getRuntimeWorkdir(runtimeMeta) || workdir;
|
|
342
|
-
options.push({
|
|
343
|
-
runtime: resolved.runtime,
|
|
344
|
-
runtime_label: runtimeLabel(resolved.runtime),
|
|
345
|
-
display_name: resolved.displayName || basename(optionWorkdir) || runtimeLabel(resolved.runtime),
|
|
346
|
-
workdir: optionWorkdir,
|
|
347
|
-
binding_key: optionWorkdir,
|
|
348
|
-
runtime_meta: runtimeMeta,
|
|
349
|
-
});
|
|
350
|
-
} catch (err) {
|
|
351
|
-
debugLog('ticlawk-pairing', 'auto-runtime.skip', {
|
|
352
|
-
runtime: serviceType,
|
|
353
|
-
reason: err?.message || String(err),
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
return options;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
227
|
function printPairingChallenge(session) {
|
|
361
228
|
console.log();
|
|
362
229
|
console.log('Open Ticlawk and scan the QR code below, or enter the pairing code.');
|
|
@@ -417,12 +284,11 @@ export function createTiclawkAdapter(ctx) {
|
|
|
417
284
|
let bindingAuditTimer = null;
|
|
418
285
|
let jobsWakeTimer = null;
|
|
419
286
|
let bindingsWakeTimer = null;
|
|
420
|
-
let credentialsWakeTimer = null;
|
|
421
287
|
let lastJobsWakeAt = 0;
|
|
422
288
|
let lastBindingsWakeAt = 0;
|
|
423
|
-
let lastCredentialsWakeAt = 0;
|
|
424
289
|
let updateRequired = null;
|
|
425
290
|
let lastUpdateRequiredLogAt = 0;
|
|
291
|
+
let startupSyncing = false;
|
|
426
292
|
|
|
427
293
|
function clearDebounce(timer) {
|
|
428
294
|
if (timer) clearTimeout(timer);
|
|
@@ -596,7 +462,7 @@ export function createTiclawkAdapter(ctx) {
|
|
|
596
462
|
|
|
597
463
|
const binding = await ctx.persistBinding(buildBindingFromSource(msg));
|
|
598
464
|
if (!binding?.runtime) {
|
|
599
|
-
throw new Error('claimed
|
|
465
|
+
throw new Error('claimed message missing runtime binding');
|
|
600
466
|
}
|
|
601
467
|
if (!belongsToRuntimeHost(binding, hostId)) {
|
|
602
468
|
await api.releaseDelivery(deliveryId, hostId, 'binding-host-mismatch');
|
|
@@ -664,91 +530,6 @@ export function createTiclawkAdapter(ctx) {
|
|
|
664
530
|
}
|
|
665
531
|
}
|
|
666
532
|
|
|
667
|
-
// The goal lane's canonical completion is `ticlawk goal report`, which
|
|
668
|
-
// completes the transition delivery inside report_goal_transition (before
|
|
669
|
-
// emitting the next step) so the live-transition slot frees up. By the time
|
|
670
|
-
// the turn returns the row is usually already 'completed', so this
|
|
671
|
-
// best-effort complete then 409s — that is the expected healthy path, not an
|
|
672
|
-
// error. A turn that finished without reporting leaves the row 'claimed' for
|
|
673
|
-
// stale-claimed recovery.
|
|
674
|
-
async function completeGoalDelivery(deliveryId) {
|
|
675
|
-
try {
|
|
676
|
-
await api.completeDelivery(deliveryId, hostId);
|
|
677
|
-
} catch (err) {
|
|
678
|
-
if (err?.status === 409) return;
|
|
679
|
-
throw err;
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
async function processGoalTransitionsForAgent(agentId, messages) {
|
|
684
|
-
for (const msg of messages) {
|
|
685
|
-
const deliveryId = msg.delivery_id;
|
|
686
|
-
const step = msg?.payload?.step || null;
|
|
687
|
-
try {
|
|
688
|
-
const messageHostId = getRuntimeHostIdFromPayload(msg);
|
|
689
|
-
if (messageHostId && messageHostId !== hostId) {
|
|
690
|
-
await api.releaseDelivery(deliveryId, hostId, 'host-mismatch');
|
|
691
|
-
continue;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
const binding = await ctx.persistBinding(buildBindingFromSource(msg));
|
|
695
|
-
if (!binding?.runtime) {
|
|
696
|
-
throw new Error('claimed transition missing runtime binding');
|
|
697
|
-
}
|
|
698
|
-
if (!belongsToRuntimeHost(binding, hostId)) {
|
|
699
|
-
await api.releaseDelivery(deliveryId, hostId, 'binding-host-mismatch');
|
|
700
|
-
continue;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
const completed = await ctx.bus.dispatchToAgent(binding.runtime, binding.id, normalizeInboundMessage(msg));
|
|
704
|
-
if (completed !== true) {
|
|
705
|
-
if (isTerminalRuntimeFailure(completed)) {
|
|
706
|
-
await completeGoalDelivery(deliveryId);
|
|
707
|
-
void requestDrain('transition.terminal-completed');
|
|
708
|
-
debugError('ticlawk', 'transition.terminal-failed', {
|
|
709
|
-
agentId,
|
|
710
|
-
deliveryId,
|
|
711
|
-
step,
|
|
712
|
-
runtime: binding.runtime,
|
|
713
|
-
reason: completed.reason || 'runtime terminal failure',
|
|
714
|
-
});
|
|
715
|
-
continue;
|
|
716
|
-
}
|
|
717
|
-
throw new Error('runtime did not complete transition turn');
|
|
718
|
-
}
|
|
719
|
-
await completeGoalDelivery(deliveryId);
|
|
720
|
-
void requestDrain('transition.completed');
|
|
721
|
-
debugLog('ticlawk', 'transition.completed', {
|
|
722
|
-
agentId,
|
|
723
|
-
deliveryId,
|
|
724
|
-
step,
|
|
725
|
-
runtime: binding.runtime,
|
|
726
|
-
});
|
|
727
|
-
} catch (err) {
|
|
728
|
-
if (api.isUpdateRequiredError(err)) {
|
|
729
|
-
recordUpdateRequired(err, 'transition.dispatch');
|
|
730
|
-
}
|
|
731
|
-
try {
|
|
732
|
-
await api.releaseDelivery(deliveryId, hostId, 'transition-dispatch-error');
|
|
733
|
-
} catch (releaseErr) {
|
|
734
|
-
debugError('ticlawk', 'transition.release-failed', {
|
|
735
|
-
agentId,
|
|
736
|
-
deliveryId,
|
|
737
|
-
hostId,
|
|
738
|
-
error: releaseErr?.message || 'unknown error',
|
|
739
|
-
});
|
|
740
|
-
}
|
|
741
|
-
debugError('ticlawk', 'transition.dispatch-failed', {
|
|
742
|
-
agentId,
|
|
743
|
-
deliveryId,
|
|
744
|
-
step,
|
|
745
|
-
hostId,
|
|
746
|
-
error: err?.message || 'unknown error',
|
|
747
|
-
});
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
533
|
async function refreshBindings(reason = 'manual') {
|
|
753
534
|
let channels = [];
|
|
754
535
|
const startedAt = Date.now();
|
|
@@ -787,27 +568,6 @@ export function createTiclawkAdapter(ctx) {
|
|
|
787
568
|
error: err?.message || 'unknown error',
|
|
788
569
|
});
|
|
789
570
|
}
|
|
790
|
-
|
|
791
|
-
// Agents created from the App via POST /me/agents land here in
|
|
792
|
-
// status='unpaired'. Once we've successfully registered the
|
|
793
|
-
// binding locally, flip them to 'connected' — same end state the
|
|
794
|
-
// legacy QR pairing flow leaves agents in. spawn itself stays
|
|
795
|
-
// lazy (happens on first delivery via deliverTurn).
|
|
796
|
-
if (agent.status === 'unpaired' && agentHostId === hostId) {
|
|
797
|
-
try {
|
|
798
|
-
await api.updateAgent(agent.id, {
|
|
799
|
-
status: 'connected',
|
|
800
|
-
runtime_host_id: hostId,
|
|
801
|
-
runtime_host_label: getHostLabel(),
|
|
802
|
-
});
|
|
803
|
-
debugLog('ticlawk', 'binding.unpaired-claimed', { agentId: agent.id });
|
|
804
|
-
} catch (err) {
|
|
805
|
-
debugError('ticlawk', 'binding.unpaired-claim-failed', {
|
|
806
|
-
agentId: agent.id,
|
|
807
|
-
error: err?.message || 'unknown error',
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
571
|
}
|
|
812
572
|
const pruned = await pruneDeletedBindings(channels, reason);
|
|
813
573
|
debugLog('ticlawk', 'binding.refresh-ok', {
|
|
@@ -822,33 +582,6 @@ export function createTiclawkAdapter(ctx) {
|
|
|
822
582
|
return hydrated;
|
|
823
583
|
}
|
|
824
584
|
|
|
825
|
-
const syncCredentials = coalesce(async (reason = 'manual') => {
|
|
826
|
-
const startedAt = Date.now();
|
|
827
|
-
try {
|
|
828
|
-
const payload = await api.fetchCredentials();
|
|
829
|
-
const credentials = Array.isArray(payload?.credentials) ? payload.credentials : [];
|
|
830
|
-
const result = persistRuntimeCredentials(credentials);
|
|
831
|
-
debugLog('ticlawk-credentials', 'sync-ok', {
|
|
832
|
-
reason,
|
|
833
|
-
saved: result.saved,
|
|
834
|
-
removed: result.removed,
|
|
835
|
-
durationMs: Date.now() - startedAt,
|
|
836
|
-
wakeToSyncMs: String(reason || '').startsWith('wake') && lastCredentialsWakeAt
|
|
837
|
-
? Date.now() - lastCredentialsWakeAt
|
|
838
|
-
: null,
|
|
839
|
-
});
|
|
840
|
-
} catch (err) {
|
|
841
|
-
if (api.isUpdateRequiredError(err)) {
|
|
842
|
-
recordUpdateRequired(err, 'credentials.sync');
|
|
843
|
-
}
|
|
844
|
-
debugError('ticlawk-credentials', 'sync-failed', {
|
|
845
|
-
reason,
|
|
846
|
-
durationMs: Date.now() - startedAt,
|
|
847
|
-
error: err?.message || 'unknown error',
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
});
|
|
851
|
-
|
|
852
585
|
async function releaseBlockedRows(agentId, messages, reason) {
|
|
853
586
|
for (const msg of messages) {
|
|
854
587
|
if (!msg?.delivery_id) continue;
|
|
@@ -897,8 +630,7 @@ export function createTiclawkAdapter(ctx) {
|
|
|
897
630
|
return { failed: true, claimed: 0, launched: 0 };
|
|
898
631
|
}
|
|
899
632
|
|
|
900
|
-
const
|
|
901
|
-
const claimed = orderedData.length;
|
|
633
|
+
const claimed = Array.isArray(data) ? data.length : 0;
|
|
902
634
|
debugLog('ticlawk', 'claim.result', {
|
|
903
635
|
reason,
|
|
904
636
|
hostId,
|
|
@@ -917,12 +649,12 @@ export function createTiclawkAdapter(ctx) {
|
|
|
917
649
|
});
|
|
918
650
|
}
|
|
919
651
|
|
|
920
|
-
if (
|
|
652
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
921
653
|
return { failed: false, claimed: 0, launched: 0 };
|
|
922
654
|
}
|
|
923
655
|
|
|
924
656
|
const grouped = new Map();
|
|
925
|
-
for (const msg of
|
|
657
|
+
for (const msg of data) {
|
|
926
658
|
const agentId = getAgentIdFromPayload(msg);
|
|
927
659
|
if (!agentId) {
|
|
928
660
|
// Claim rows must carry the recipient agent id; a missing value
|
|
@@ -935,39 +667,33 @@ export function createTiclawkAdapter(ctx) {
|
|
|
935
667
|
});
|
|
936
668
|
continue;
|
|
937
669
|
}
|
|
938
|
-
const
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
bucket.messages.push(msg);
|
|
942
|
-
grouped.set(channelKey, bucket);
|
|
670
|
+
const bucket = grouped.get(agentId) || [];
|
|
671
|
+
bucket.push(msg);
|
|
672
|
+
grouped.set(agentId, bucket);
|
|
943
673
|
}
|
|
944
674
|
|
|
945
675
|
let launched = 0;
|
|
946
|
-
for (const [
|
|
947
|
-
if (processingChannels.has(
|
|
676
|
+
for (const [agentId, messages] of grouped.entries()) {
|
|
677
|
+
if (processingChannels.has(agentId)) {
|
|
948
678
|
debugError('ticlawk', 'claim.blocked-claimed-rows', {
|
|
949
679
|
reason,
|
|
950
|
-
channelKey,
|
|
951
680
|
agentId,
|
|
952
|
-
lane,
|
|
953
681
|
blockedRows: messages.length,
|
|
954
682
|
});
|
|
955
683
|
await releaseBlockedRows(agentId, messages, reason);
|
|
956
684
|
continue;
|
|
957
685
|
}
|
|
958
|
-
const run = (
|
|
959
|
-
? processGoalTransitionsForAgent(agentId, messages)
|
|
960
|
-
: processPendingMessagesForAgent(agentId, messages))
|
|
686
|
+
const run = processPendingMessagesForAgent(agentId, messages)
|
|
961
687
|
.catch(() => {})
|
|
962
688
|
.finally(() => {
|
|
963
|
-
processingChannels.delete(
|
|
689
|
+
processingChannels.delete(agentId);
|
|
964
690
|
void requestDrain('channel.completed');
|
|
965
691
|
});
|
|
966
|
-
processingChannels.set(
|
|
692
|
+
processingChannels.set(agentId, run);
|
|
967
693
|
launched += messages.length;
|
|
968
694
|
}
|
|
969
695
|
|
|
970
|
-
return { failed: false, claimed:
|
|
696
|
+
return { failed: false, claimed: data.length, launched };
|
|
971
697
|
}
|
|
972
698
|
|
|
973
699
|
async function runDrain(reason) {
|
|
@@ -1006,15 +732,6 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1006
732
|
bindingsWakeTimer.unref?.();
|
|
1007
733
|
}
|
|
1008
734
|
|
|
1009
|
-
function scheduleCredentialSync(reason) {
|
|
1010
|
-
credentialsWakeTimer = clearDebounce(credentialsWakeTimer);
|
|
1011
|
-
credentialsWakeTimer = setTimeout(() => {
|
|
1012
|
-
credentialsWakeTimer = null;
|
|
1013
|
-
void syncCredentials(reason);
|
|
1014
|
-
}, CREDENTIALS_WAKE_DEBOUNCE_MS);
|
|
1015
|
-
credentialsWakeTimer.unref?.();
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
735
|
async function reportHostCapabilitiesNow() {
|
|
1019
736
|
const entries = await Promise.all(Object.entries(ctx.runtimes || {})
|
|
1020
737
|
.filter(([, runtime]) => typeof runtime?.health === 'function')
|
|
@@ -1051,11 +768,11 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1051
768
|
function handleWakeEvent(event) {
|
|
1052
769
|
wakeState.lastEventAt = new Date().toISOString();
|
|
1053
770
|
if (event?.type === 'hello') {
|
|
771
|
+
void reportHostCapabilitiesNow();
|
|
772
|
+
if (startupSyncing) return;
|
|
1054
773
|
void refreshBindings('wake.hello')
|
|
1055
774
|
.then(() => requestDrain('wake.hello'))
|
|
1056
775
|
.catch(() => {});
|
|
1057
|
-
void syncCredentials('wake.hello');
|
|
1058
|
-
void reportHostCapabilitiesNow();
|
|
1059
776
|
return;
|
|
1060
777
|
}
|
|
1061
778
|
if (event?.type === 'jobs.available') {
|
|
@@ -1076,17 +793,6 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1076
793
|
scheduleRefreshAndDrain('wake.bindings.changed');
|
|
1077
794
|
return;
|
|
1078
795
|
}
|
|
1079
|
-
if (event?.type === 'credentials.changed') {
|
|
1080
|
-
lastCredentialsWakeAt = Date.now();
|
|
1081
|
-
debugLog('ticlawk-wake', 'credentials.changed', {
|
|
1082
|
-
credentialId: event.credential_id || null,
|
|
1083
|
-
name: event.name || null,
|
|
1084
|
-
status: event.status || null,
|
|
1085
|
-
reason: event.reason || null,
|
|
1086
|
-
});
|
|
1087
|
-
scheduleCredentialSync('wake.credentials.changed');
|
|
1088
|
-
return;
|
|
1089
|
-
}
|
|
1090
796
|
if (event?.type === 'auth.revoked') {
|
|
1091
797
|
wakeState.lastError = 'auth revoked';
|
|
1092
798
|
debugError('ticlawk-wake', 'auth.revoked', {});
|
|
@@ -1119,19 +825,21 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1119
825
|
});
|
|
1120
826
|
}
|
|
1121
827
|
|
|
828
|
+
function getConnectorWakeUrlForHost() {
|
|
829
|
+
const raw = api.getConnectorWsUrl();
|
|
830
|
+
try {
|
|
831
|
+
const url = new URL(raw);
|
|
832
|
+
url.searchParams.set('runtime_host_id', hostId);
|
|
833
|
+
return url.toString();
|
|
834
|
+
} catch {
|
|
835
|
+
const separator = raw.includes('?') ? '&' : '?';
|
|
836
|
+
return `${raw}${separator}runtime_host_id=${encodeURIComponent(hostId)}`;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
1122
840
|
function connectWakeSocket() {
|
|
1123
841
|
connectorSocket = new TiclawkWakeClient({
|
|
1124
|
-
|
|
1125
|
-
// runtime_hosts.online for the right row. Empty host_id is
|
|
1126
|
-
// tolerated by the server (it just skips the state write).
|
|
1127
|
-
getUrl: () => {
|
|
1128
|
-
const base = String(api.getConnectorWsUrl() || '').trim();
|
|
1129
|
-
if (!base) return '';
|
|
1130
|
-
const hostId = String(getHostId() || '').trim();
|
|
1131
|
-
if (!hostId) return base;
|
|
1132
|
-
const sep = base.includes('?') ? '&' : '?';
|
|
1133
|
-
return `${base}${sep}host_id=${encodeURIComponent(hostId)}`;
|
|
1134
|
-
},
|
|
842
|
+
getUrl: getConnectorWakeUrlForHost,
|
|
1135
843
|
getApiKey: api.getApiKey,
|
|
1136
844
|
onEvent: handleWakeEvent,
|
|
1137
845
|
onStatus: handleWakeStatus,
|
|
@@ -1159,7 +867,7 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1159
867
|
bindingAuditTimer.unref?.();
|
|
1160
868
|
}
|
|
1161
869
|
|
|
1162
|
-
async function
|
|
870
|
+
async function finishHostPairing(pairData) {
|
|
1163
871
|
const apiKey = pairData.connector_api_key || pairData.apiKey || pairData.api_key;
|
|
1164
872
|
persistApiCredential(apiKey);
|
|
1165
873
|
if (pairData.connector_ws_url) {
|
|
@@ -1178,61 +886,26 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1178
886
|
restartWakeSocket('connect.paired');
|
|
1179
887
|
}
|
|
1180
888
|
|
|
1181
|
-
|
|
1182
|
-
if (!bindingId) {
|
|
1183
|
-
throw new Error('pairing did not return agent_id');
|
|
1184
|
-
}
|
|
1185
|
-
const runtimeMeta = pairData.runtime_meta || resolved.runtimeMeta;
|
|
1186
|
-
const binding = await ctx.upsertBinding({
|
|
1187
|
-
id: bindingId,
|
|
1188
|
-
adapter: 'ticlawk',
|
|
1189
|
-
targetKey: bindingId,
|
|
1190
|
-
targetMeta: {
|
|
1191
|
-
agentId: bindingId,
|
|
1192
|
-
runtime_host_id: hostId,
|
|
1193
|
-
binding_key: pairData.binding_key || null,
|
|
1194
|
-
},
|
|
1195
|
-
runtime_host_id: hostId,
|
|
1196
|
-
runtime_host_label: hostLabel,
|
|
1197
|
-
runtime: resolved.runtime,
|
|
1198
|
-
runtimeMeta,
|
|
1199
|
-
displayName: resolved.displayName,
|
|
1200
|
-
status: 'connected',
|
|
1201
|
-
});
|
|
889
|
+
await reportHostCapabilitiesNow();
|
|
1202
890
|
return {
|
|
1203
891
|
statusCode: 200,
|
|
1204
892
|
body: {
|
|
1205
893
|
ok: true,
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
name: binding.displayName,
|
|
1209
|
-
bindingKey: pairData.binding_key || null,
|
|
894
|
+
hostId,
|
|
895
|
+
hostLabel,
|
|
1210
896
|
user: pairedIdentity,
|
|
1211
897
|
},
|
|
1212
898
|
};
|
|
1213
899
|
}
|
|
1214
900
|
|
|
1215
|
-
async function connectWithQrPairing(
|
|
1216
|
-
const autoRuntime = Boolean(payload?.autoRuntime);
|
|
1217
|
-
const runtimeOptions = autoRuntime ? await buildAutoRuntimeOptions(ctx, payload) : [];
|
|
1218
|
-
if (autoRuntime) {
|
|
1219
|
-
printDetectedRuntimeOptions(runtimeOptions);
|
|
1220
|
-
if (runtimeOptions.length === 0) {
|
|
1221
|
-
return connectError(400, 'No supported local agent harness detected in this terminal. Install or sign in to Codex, Claude Code, OpenCode, or pi, then run `ticlawk connect` again.');
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
const resolved = autoRuntime ? null : await ctx.resolveRuntimeBinding(payload);
|
|
1225
|
-
const runtimeMeta = resolved?.runtimeMeta || {};
|
|
1226
|
-
const workdir = getRuntimeWorkdir(runtimeMeta) || getRuntimeWorkdir(payload) || process.cwd();
|
|
901
|
+
async function connectWithQrPairing() {
|
|
1227
902
|
const clientSecret = makeClientSecret();
|
|
1228
903
|
const created = await api.createPairingSession({
|
|
1229
904
|
client: 'ticlawk',
|
|
1230
905
|
client_version: api.getTiclawkVersion(),
|
|
1231
906
|
host_id: hostId,
|
|
1232
907
|
host_label: hostLabel,
|
|
1233
|
-
|
|
1234
|
-
workdir,
|
|
1235
|
-
display_name: resolved?.displayName || basename(workdir) || 'Agent',
|
|
908
|
+
scope: 'host',
|
|
1236
909
|
challenge_hash: sha256Hex(clientSecret),
|
|
1237
910
|
});
|
|
1238
911
|
if (!created?.ok) {
|
|
@@ -1253,20 +926,9 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1253
926
|
pollAfterMs: Number(created.poll_after_ms || 1500),
|
|
1254
927
|
expiresAt: created.expires_at,
|
|
1255
928
|
});
|
|
1256
|
-
|
|
1257
|
-
const approvedOption = runtimeOptions.find((option) => option.runtime === approvedRuntime) || null;
|
|
1258
|
-
const finalResolved = resolved || {
|
|
1259
|
-
runtime: approvedRuntime || approvedOption?.runtime,
|
|
1260
|
-
displayName: approved.display_name || approvedOption?.display_name || runtimeLabel(approvedRuntime),
|
|
1261
|
-
runtimeMeta: approved.runtime_meta || approvedOption?.runtime_meta || {},
|
|
1262
|
-
};
|
|
1263
|
-
if (!finalResolved.runtime) {
|
|
1264
|
-
throw new Error('ticlawk pairing did not return a selected runtime');
|
|
1265
|
-
}
|
|
1266
|
-
return await finishPairing.call(this, finalResolved, {
|
|
929
|
+
return await finishHostPairing({
|
|
1267
930
|
...created,
|
|
1268
931
|
...approved,
|
|
1269
|
-
binding_key: approved.binding_key || created.binding_key || null,
|
|
1270
932
|
});
|
|
1271
933
|
} catch (err) {
|
|
1272
934
|
await api.cancelPairingSession({
|
|
@@ -1281,13 +943,18 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1281
943
|
id: 'ticlawk',
|
|
1282
944
|
|
|
1283
945
|
async start() {
|
|
1284
|
-
//
|
|
1285
|
-
// claim
|
|
1286
|
-
//
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
await requestDrain('startup');
|
|
946
|
+
// No stale-delivery recovery anywhere — if the daemon is killed
|
|
947
|
+
// mid-claim, the row stays `claimed` forever. lease_expires_at
|
|
948
|
+
// was dropped in X1; no cron has replaced it. Rare in practice;
|
|
949
|
+
// when it happens the fix is a one-row UPDATE in supabase.
|
|
950
|
+
startupSyncing = true;
|
|
1290
951
|
connectWakeSocket();
|
|
952
|
+
try {
|
|
953
|
+
await refreshBindings('startup');
|
|
954
|
+
await requestDrain('startup');
|
|
955
|
+
} finally {
|
|
956
|
+
startupSyncing = false;
|
|
957
|
+
}
|
|
1291
958
|
startAuditTimers();
|
|
1292
959
|
},
|
|
1293
960
|
|
|
@@ -1324,131 +991,9 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1324
991
|
},
|
|
1325
992
|
|
|
1326
993
|
async connect(payload) {
|
|
1327
|
-
const config = loadPersistentConfig();
|
|
1328
|
-
if (!payload?.__legacyConnect) {
|
|
1329
|
-
try {
|
|
1330
|
-
return await connectWithQrPairing.call(this, {
|
|
1331
|
-
...payload,
|
|
1332
|
-
autoRuntime: true,
|
|
1333
|
-
});
|
|
1334
|
-
} catch (err) {
|
|
1335
|
-
return connectError(err?.status || 500, err.message);
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
// Archived setup-code connect path. It is intentionally not reachable
|
|
1340
|
-
// from the public CLI; keep it here only until the old flow is deleted.
|
|
1341
|
-
const connectCode = String(payload?.code || config.TICLAWK_SETUP_CODE || '').trim();
|
|
1342
|
-
if (!connectCode) {
|
|
1343
|
-
return connectError(400, 'legacy ticlawk setup-code connect requires code');
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
994
|
try {
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
let currentIdentity = null;
|
|
1350
|
-
const activeProfile = getActiveProfile();
|
|
1351
|
-
if (activeProfile?.adapter === 'ticlawk') {
|
|
1352
|
-
currentIdentity = readProfileMeta(activeProfile.adapter, activeProfile.userId) || {
|
|
1353
|
-
userId: activeProfile.userId,
|
|
1354
|
-
};
|
|
1355
|
-
} else if (config[TICLAWK_CONNECTOR_API_KEY]) {
|
|
1356
|
-
try {
|
|
1357
|
-
const me = await api.getMe();
|
|
1358
|
-
if (me?.userId || me?.user_id) {
|
|
1359
|
-
currentIdentity = maskIdentity(me);
|
|
1360
|
-
ensureLegacyProfile({
|
|
1361
|
-
adapter: 'ticlawk',
|
|
1362
|
-
userId: currentIdentity.userId,
|
|
1363
|
-
meta: currentIdentity,
|
|
1364
|
-
});
|
|
1365
|
-
}
|
|
1366
|
-
} catch {}
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
let previewIdentity = null;
|
|
1370
|
-
try {
|
|
1371
|
-
const preview = await api.pairPreview({ code: connectCode });
|
|
1372
|
-
if (preview?.ok) {
|
|
1373
|
-
previewIdentity = maskIdentity(preview);
|
|
1374
|
-
} else if (preview?.userId || preview?.user_id) {
|
|
1375
|
-
previewIdentity = maskIdentity(preview);
|
|
1376
|
-
}
|
|
1377
|
-
} catch {}
|
|
1378
|
-
|
|
1379
|
-
if (currentIdentity?.userId && !previewIdentity?.userId && !payload.switchUser) {
|
|
1380
|
-
return {
|
|
1381
|
-
statusCode: 409,
|
|
1382
|
-
body: {
|
|
1383
|
-
ok: false,
|
|
1384
|
-
code: 'ticlawk_pairing_user_unverified',
|
|
1385
|
-
currentUser: currentIdentity,
|
|
1386
|
-
error: [
|
|
1387
|
-
'This ticlawk home is already paired to ticlawk user:',
|
|
1388
|
-
...formatIdentityLines(currentIdentity),
|
|
1389
|
-
'',
|
|
1390
|
-
'Could not verify which ticlawk user owns the new pairing code.',
|
|
1391
|
-
'Refusing to switch users because existing agents may stop processing messages.',
|
|
1392
|
-
'',
|
|
1393
|
-
'If you want to switch user, please rerun this command with --switch-user.',
|
|
1394
|
-
].join('\n'),
|
|
1395
|
-
},
|
|
1396
|
-
};
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
if (
|
|
1400
|
-
currentIdentity?.userId
|
|
1401
|
-
&& previewIdentity?.userId
|
|
1402
|
-
&& currentIdentity.userId !== previewIdentity.userId
|
|
1403
|
-
&& !payload.switchUser
|
|
1404
|
-
) {
|
|
1405
|
-
let affectedAgents = [];
|
|
1406
|
-
try {
|
|
1407
|
-
affectedAgents = await api.getAgents({ hostId });
|
|
1408
|
-
} catch {}
|
|
1409
|
-
const message = [
|
|
1410
|
-
'This ticlawk home is already paired to ticlawk user:',
|
|
1411
|
-
...formatIdentityLines(currentIdentity),
|
|
1412
|
-
'',
|
|
1413
|
-
'The pairing code belongs to a different ticlawk user:',
|
|
1414
|
-
...formatIdentityLines(previewIdentity),
|
|
1415
|
-
'',
|
|
1416
|
-
'Refusing to switch users because these agents are currently bound to this host:',
|
|
1417
|
-
...formatAffectedAgents(affectedAgents),
|
|
1418
|
-
'',
|
|
1419
|
-
'Switching would stop this daemon from processing messages for those agents.',
|
|
1420
|
-
'',
|
|
1421
|
-
'If you want to switch user, please rerun this command with --switch-user.',
|
|
1422
|
-
].join('\n');
|
|
1423
|
-
return {
|
|
1424
|
-
statusCode: 409,
|
|
1425
|
-
body: {
|
|
1426
|
-
ok: false,
|
|
1427
|
-
error: message,
|
|
1428
|
-
code: 'ticlawk_user_mismatch',
|
|
1429
|
-
currentUser: currentIdentity,
|
|
1430
|
-
pairingUser: previewIdentity,
|
|
1431
|
-
affectedAgents,
|
|
1432
|
-
},
|
|
1433
|
-
};
|
|
1434
|
-
}
|
|
1435
|
-
|
|
1436
|
-
const pairData = await api.pair({
|
|
1437
|
-
code: connectCode,
|
|
1438
|
-
name: resolved.displayName,
|
|
1439
|
-
serviceType: resolved.runtime,
|
|
1440
|
-
runtimeMeta,
|
|
1441
|
-
runtime_host_id: hostId,
|
|
1442
|
-
runtime_host_label: hostLabel,
|
|
1443
|
-
});
|
|
1444
|
-
if (!pairData?.ok) {
|
|
1445
|
-
return { statusCode: pairData?.statusCode || 401, body: pairData };
|
|
1446
|
-
}
|
|
1447
|
-
return finishPairing.call(this, resolved, {
|
|
1448
|
-
...pairData,
|
|
1449
|
-
agent_id: pairData.agentId,
|
|
1450
|
-
connector_api_key: pairData.apiKey,
|
|
1451
|
-
});
|
|
995
|
+
void payload;
|
|
996
|
+
return await connectWithQrPairing();
|
|
1452
997
|
} catch (err) {
|
|
1453
998
|
return connectError(err?.status || 500, err.message);
|
|
1454
999
|
}
|
|
@@ -1512,52 +1057,3 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1512
1057
|
},
|
|
1513
1058
|
};
|
|
1514
1059
|
}
|
|
1515
|
-
|
|
1516
|
-
export function getTiclawkAuthHelp() {
|
|
1517
|
-
return `ticlawk auth ticlawk --code <6-digit-code> [--api-url <url>]
|
|
1518
|
-
|
|
1519
|
-
Options:
|
|
1520
|
-
--code <code> 6-digit setup code from the ticlawk app
|
|
1521
|
-
--api-url <url> optional ticlawk API base URL override
|
|
1522
|
-
`;
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
export async function runTiclawkAuth(rawArgs) {
|
|
1526
|
-
const args = parseOptionArgs(rawArgs);
|
|
1527
|
-
if (args.help || args.h) {
|
|
1528
|
-
return {
|
|
1529
|
-
statusCode: 200,
|
|
1530
|
-
body: {
|
|
1531
|
-
ok: true,
|
|
1532
|
-
help: getTiclawkAuthHelp(),
|
|
1533
|
-
},
|
|
1534
|
-
};
|
|
1535
|
-
}
|
|
1536
|
-
const code = String(args.code || '').trim();
|
|
1537
|
-
if (!code) {
|
|
1538
|
-
return {
|
|
1539
|
-
statusCode: 400,
|
|
1540
|
-
body: {
|
|
1541
|
-
ok: false,
|
|
1542
|
-
error: 'ticlawk auth requires --code',
|
|
1543
|
-
},
|
|
1544
|
-
};
|
|
1545
|
-
}
|
|
1546
|
-
const updates = {
|
|
1547
|
-
TICLAWK_SETUP_CODE: code,
|
|
1548
|
-
};
|
|
1549
|
-
const apiUrl = String(args['api-url'] || '').trim();
|
|
1550
|
-
if (apiUrl) {
|
|
1551
|
-
updates.TICLAWK_API_URL = apiUrl;
|
|
1552
|
-
}
|
|
1553
|
-
persistConfig(updates);
|
|
1554
|
-
return {
|
|
1555
|
-
statusCode: 200,
|
|
1556
|
-
body: {
|
|
1557
|
-
ok: true,
|
|
1558
|
-
adapter: 'ticlawk',
|
|
1559
|
-
setupCode: 'set',
|
|
1560
|
-
apiUrl: apiUrl || undefined,
|
|
1561
|
-
},
|
|
1562
|
-
};
|
|
1563
|
-
}
|