ticlawk 0.1.16-dev.9 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -3
- package/bin/ticlawk.mjs +208 -21
- package/package.json +1 -1
- package/src/adapters/ticlawk/api.mjs +283 -48
- package/src/adapters/ticlawk/credentials.mjs +41 -1
- package/src/adapters/ticlawk/index.mjs +126 -121
- package/src/adapters/ticlawk/wake-client.mjs +1 -1
- package/src/cli/agent-commands.mjs +557 -18
- package/src/core/agent-cli-handlers.mjs +435 -18
- package/src/core/agent-home.mjs +81 -1
- package/src/core/argv.mjs +11 -1
- package/src/core/events/worker-events.mjs +32 -36
- package/src/core/http.mjs +119 -0
- package/src/core/runtime-contract.mjs +0 -1
- package/src/core/runtime-env.mjs +7 -0
- package/src/core/runtime-support.mjs +108 -77
- package/src/runtimes/_shared/agent-handbook.mjs +45 -0
- package/src/runtimes/_shared/brand.mjs +2 -0
- package/src/runtimes/_shared/goal-task-protocol.mjs +50 -0
- package/src/runtimes/_shared/handbook/BASICS.md +27 -0
- package/src/runtimes/_shared/handbook/COLLABORATION.md +37 -0
- package/src/runtimes/_shared/handbook/COMMUNICATION.md +55 -0
- package/src/runtimes/_shared/handbook/DM_SCOPE.md +13 -0
- package/src/runtimes/_shared/handbook/GOAL_AUTHORITY.md +46 -0
- package/src/runtimes/_shared/handbook/GOAL_TASK_CORE.md +43 -0
- package/src/runtimes/_shared/handbook/GROUP_ADMIN_SCOPE.md +21 -0
- package/src/runtimes/_shared/handbook/GROUP_MEMBER_SCOPE.md +15 -0
- package/src/runtimes/_shared/handbook/SURFACES.md +41 -0
- package/src/runtimes/_shared/handbook/TASK_WORKER.md +14 -0
- package/src/runtimes/_shared/standing-prompt.mjs +134 -278
- package/src/runtimes/_shared/wake-prompt.mjs +261 -0
- package/src/runtimes/claude-code/index.mjs +19 -46
- package/src/runtimes/claude-code/session.mjs +2 -7
- package/src/runtimes/codex/index.mjs +115 -63
- package/src/runtimes/codex/session.mjs +2 -12
- package/src/runtimes/openclaw/index.mjs +11 -24
- package/src/runtimes/opencode/index.mjs +38 -60
- package/src/runtimes/opencode/session.mjs +12 -12
- package/src/runtimes/pi/index.mjs +38 -60
- package/src/runtimes/pi/session.mjs +9 -6
- package/ticlawk.mjs +0 -30
|
@@ -10,13 +10,15 @@ import { isTerminalRuntimeFailure } from '../../core/runtime-support.mjs';
|
|
|
10
10
|
import { clearUpdateRequiredState, readUpdateState, setUpdateRequiredState } from '../../core/update-state.mjs';
|
|
11
11
|
import { isManagedInstall, startDetachedSelfUpdate } from '../../core/update.mjs';
|
|
12
12
|
import * as api from './api.mjs';
|
|
13
|
-
import { persistApiCredential } from './credentials.mjs';
|
|
13
|
+
import { persistApiCredential, persistRuntimeCredentials } from './credentials.mjs';
|
|
14
14
|
import { TiclawkWakeClient } from './wake-client.mjs';
|
|
15
|
+
import { buildInboundWakePrompt } from '../../runtimes/_shared/wake-prompt.mjs';
|
|
15
16
|
|
|
16
17
|
const require = createRequire(import.meta.url);
|
|
17
18
|
const qrcode = require('qrcode-terminal');
|
|
18
19
|
const JOBS_WAKE_DEBOUNCE_MS = 100;
|
|
19
20
|
const BINDINGS_WAKE_DEBOUNCE_MS = 500;
|
|
21
|
+
const CREDENTIALS_WAKE_DEBOUNCE_MS = 500;
|
|
20
22
|
const RECOVERY_AUDIT_INTERVAL_MS = 5 * 60 * 1000;
|
|
21
23
|
const BINDING_AUDIT_INTERVAL_MS = 5 * 60 * 1000;
|
|
22
24
|
const UPDATE_RETRY_COOLDOWN_MS = 15 * 60 * 1000;
|
|
@@ -69,98 +71,6 @@ function normalizeInboundMediaAssets(msg) {
|
|
|
69
71
|
.filter(Boolean);
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
function buildEnvelopeTarget(msg) {
|
|
73
|
-
const convType = msg.conversation_type || 'dm';
|
|
74
|
-
const conversationId = msg.conversation_id || '';
|
|
75
|
-
const senderHandle = msg.sender_display_name || msg.sender_user_id || msg.sender_agent_id || '';
|
|
76
|
-
if (convType === 'dm') {
|
|
77
|
-
return senderHandle ? `dm:@${senderHandle}` : `dm:${conversationId}`;
|
|
78
|
-
}
|
|
79
|
-
if (convType === 'thread') {
|
|
80
|
-
const groupName = msg.conversation_name || conversationId;
|
|
81
|
-
const threadRoot = msg.thread_root_message_id || msg.message_id || '';
|
|
82
|
-
return `#${groupName}:${threadRoot}`;
|
|
83
|
-
}
|
|
84
|
-
// group
|
|
85
|
-
const groupName = msg.conversation_name || conversationId;
|
|
86
|
-
return `#${groupName}`;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function buildTaskSuffix(msg) {
|
|
90
|
-
if (msg.task_number == null) return '';
|
|
91
|
-
const status = msg.task_status || 'todo';
|
|
92
|
-
const parts = [`task #${msg.task_number} status=${status}`];
|
|
93
|
-
if (msg.task_assignee_agent_id || msg.task_assignee_user_id) {
|
|
94
|
-
const t = msg.task_assignee_type || 'agent';
|
|
95
|
-
const id = msg.task_assignee_agent_id || msg.task_assignee_user_id;
|
|
96
|
-
parts.push(`assignee=${t}:${id}`);
|
|
97
|
-
}
|
|
98
|
-
if (msg.task_title) {
|
|
99
|
-
parts.push(`title=${JSON.stringify(msg.task_title)}`);
|
|
100
|
-
}
|
|
101
|
-
return ` [${parts.join(' ')}]`;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function buildReactionsSuffix(msg) {
|
|
105
|
-
const entries = Array.isArray(msg.reactions_summary) ? msg.reactions_summary : [];
|
|
106
|
-
if (entries.length === 0) return '';
|
|
107
|
-
return ` [reactions: ${entries.join('; ')}]`;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function buildEnvelopeHeader(msg) {
|
|
111
|
-
const target = buildEnvelopeTarget(msg);
|
|
112
|
-
const msgId = msg.id || msg.message_id || '';
|
|
113
|
-
const seq = msg.seq != null ? msg.seq : '';
|
|
114
|
-
const time = msg.created_at || new Date().toISOString();
|
|
115
|
-
const type = msg.sender_type || 'human';
|
|
116
|
-
const sender = msg.sender_display_name || msg.sender_user_id || msg.sender_agent_id || 'unknown';
|
|
117
|
-
// `reason` tells the agent how this delivery was routed: 'mention'
|
|
118
|
-
// / 'assignment' = you were directly addressed → respond by default;
|
|
119
|
-
// 'ambient' = you are in the room and saw it → respond only if
|
|
120
|
-
// clearly the right responder; 'dm' / 'thread_follow' / 'manual' =
|
|
121
|
-
// the legacy direct paths. The agent's behaviour split is in the
|
|
122
|
-
// standing prompt; we just surface the field.
|
|
123
|
-
const reason = msg.reason ? ` reason=${msg.reason}` : '';
|
|
124
|
-
return `[target=${target} msg=${msgId} seq=${seq} time=${time} type=${type}${reason}] @${sender}:`;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function buildGroupContextBlock(msg) {
|
|
128
|
-
// Only useful in groups — DMs don't need group-purpose context.
|
|
129
|
-
if ((msg.conversation_type || 'dm') !== 'group') return '';
|
|
130
|
-
const lines = [];
|
|
131
|
-
const name = msg.conversation_name || msg.conversation_display_name || '';
|
|
132
|
-
if (name) lines.push(`name: ${name}`);
|
|
133
|
-
const description = (msg.conversation_description || '').trim();
|
|
134
|
-
if (description) lines.push(`purpose: ${description}`);
|
|
135
|
-
if (lines.length === 0) return '';
|
|
136
|
-
return [
|
|
137
|
-
'Group context:',
|
|
138
|
-
...lines.map((l) => ` ${l}`),
|
|
139
|
-
'Use `ticlawk group members --target <target>` if you need to see who else is here.',
|
|
140
|
-
].join('\n');
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Wrap each per-turn message with an explicit reply instruction so the
|
|
144
|
-
// runtime LLM never has to remember the standing prompt to figure out
|
|
145
|
-
// HOW to reply. Codex in particular treats the developerInstructions as
|
|
146
|
-
// background and ignores the chat-send pattern without this per-turn
|
|
147
|
-
// nudge.
|
|
148
|
-
function buildWakePromptText({ envelopeHeader, target, rawText, groupContext }) {
|
|
149
|
-
const body = `${envelopeHeader} ${rawText || ''}`.trim();
|
|
150
|
-
const lines = ['New message received:', '', body];
|
|
151
|
-
if (groupContext) {
|
|
152
|
-
lines.push('', groupContext);
|
|
153
|
-
}
|
|
154
|
-
lines.push(
|
|
155
|
-
'',
|
|
156
|
-
`Respond as appropriate — reply using \`ticlawk message send --target "${target}"\` (body via stdin / heredoc), or take action as needed. Complete ALL your work before stopping.`,
|
|
157
|
-
'Reply in the channel or create/reply in a thread as appropriate; use each message\'s `target` and `msg` fields to choose the exact target.',
|
|
158
|
-
'',
|
|
159
|
-
'IMPORTANT: If the message requires multi-step work (research, code changes, testing), complete ALL steps before stopping. Sending a progress update does NOT mean your task is done — only stop when you have NO more work to do. The daemon will wake you again automatically when new messages arrive.',
|
|
160
|
-
);
|
|
161
|
-
return lines.join('\n');
|
|
162
|
-
}
|
|
163
|
-
|
|
164
74
|
export function normalizeInboundMessage(msg) {
|
|
165
75
|
// Claimed delivery rows carry the recipient agent id; legacy messages
|
|
166
76
|
// (history sync, manual inserts) may still use plain agent_id. Prefer
|
|
@@ -169,21 +79,8 @@ export function normalizeInboundMessage(msg) {
|
|
|
169
79
|
const messageId = msg.id || msg.message_id || null;
|
|
170
80
|
const deliveryId = msg.delivery_id || null;
|
|
171
81
|
const media = normalizeInboundMediaAssets(msg);
|
|
172
|
-
const rawText = msg.text || '';
|
|
173
82
|
const enriched = { ...msg, id: messageId };
|
|
174
|
-
const
|
|
175
|
-
// Task + reactions suffixes are appended INSIDE the envelope so an
|
|
176
|
-
// agent can see at a glance whether a message is already claimed and
|
|
177
|
-
// who has already acknowledged it: `[task #N status=… assignee=…]` +
|
|
178
|
-
// `[reactions: …]`.
|
|
179
|
-
const taskSuffix = buildTaskSuffix(enriched);
|
|
180
|
-
const reactionsSuffix = buildReactionsSuffix(enriched);
|
|
181
|
-
const header = baseHeader + taskSuffix + reactionsSuffix;
|
|
182
|
-
const target = buildEnvelopeTarget(enriched);
|
|
183
|
-
const groupContext = buildGroupContextBlock(enriched);
|
|
184
|
-
const text = header
|
|
185
|
-
? buildWakePromptText({ envelopeHeader: header, target, rawText, groupContext })
|
|
186
|
-
: rawText;
|
|
83
|
+
const wakePrompt = buildInboundWakePrompt(enriched);
|
|
187
84
|
return {
|
|
188
85
|
bindingId: recipientAgentId,
|
|
189
86
|
messageId,
|
|
@@ -191,10 +88,10 @@ export function normalizeInboundMessage(msg) {
|
|
|
191
88
|
conversationId: msg.conversation_id || null,
|
|
192
89
|
seq: msg.seq != null ? Number(msg.seq) : null,
|
|
193
90
|
senderType: msg.sender_type || 'human',
|
|
194
|
-
envelopeHeader: header,
|
|
195
|
-
envelopeTarget: target,
|
|
196
|
-
text,
|
|
197
|
-
rawText,
|
|
91
|
+
envelopeHeader: wakePrompt.header,
|
|
92
|
+
envelopeTarget: wakePrompt.target,
|
|
93
|
+
text: wakePrompt.text,
|
|
94
|
+
rawText: wakePrompt.rawText,
|
|
198
95
|
action: msg.action || (media.length > 0 ? 'image' : 'task'),
|
|
199
96
|
media,
|
|
200
97
|
raw: {
|
|
@@ -243,6 +140,7 @@ const RUNTIME_META_KEYS = [
|
|
|
243
140
|
'runtimePath',
|
|
244
141
|
'rotatePending',
|
|
245
142
|
'lastRotatedAt',
|
|
143
|
+
'conversationSessions',
|
|
246
144
|
// claude_code
|
|
247
145
|
'claudePath',
|
|
248
146
|
'claudeVersion',
|
|
@@ -359,6 +257,31 @@ function getRuntimeWorkdir(runtimeMeta = {}) {
|
|
|
359
257
|
return String(runtimeMeta.workdir || runtimeMeta.cwd || runtimeMeta.projectDir || '').trim();
|
|
360
258
|
}
|
|
361
259
|
|
|
260
|
+
function compareStrings(a, b) {
|
|
261
|
+
const av = String(a || '');
|
|
262
|
+
const bv = String(b || '');
|
|
263
|
+
if (av < bv) return -1;
|
|
264
|
+
if (av > bv) return 1;
|
|
265
|
+
return 0;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function compareNumbers(a, b) {
|
|
269
|
+
const av = Number(a);
|
|
270
|
+
const bv = Number(b);
|
|
271
|
+
const aFinite = Number.isFinite(av);
|
|
272
|
+
const bFinite = Number.isFinite(bv);
|
|
273
|
+
if (aFinite && bFinite && av !== bv) return av - bv;
|
|
274
|
+
if (aFinite !== bFinite) return aFinite ? -1 : 1;
|
|
275
|
+
return 0;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function compareClaimedDeliveries(a, b) {
|
|
279
|
+
return compareStrings(a?.created_at, b?.created_at)
|
|
280
|
+
|| compareStrings(a?.conversation_id, b?.conversation_id)
|
|
281
|
+
|| compareNumbers(a?.seq, b?.seq)
|
|
282
|
+
|| compareStrings(a?.delivery_id, b?.delivery_id);
|
|
283
|
+
}
|
|
284
|
+
|
|
362
285
|
function runtimeLabel(runtime) {
|
|
363
286
|
if (runtime === 'claude_code') return 'Claude Code';
|
|
364
287
|
if (runtime === 'opencode') return 'OpenCode';
|
|
@@ -472,8 +395,10 @@ export function createTiclawkAdapter(ctx) {
|
|
|
472
395
|
let bindingAuditTimer = null;
|
|
473
396
|
let jobsWakeTimer = null;
|
|
474
397
|
let bindingsWakeTimer = null;
|
|
398
|
+
let credentialsWakeTimer = null;
|
|
475
399
|
let lastJobsWakeAt = 0;
|
|
476
400
|
let lastBindingsWakeAt = 0;
|
|
401
|
+
let lastCredentialsWakeAt = 0;
|
|
477
402
|
let updateRequired = null;
|
|
478
403
|
let lastUpdateRequiredLogAt = 0;
|
|
479
404
|
|
|
@@ -649,7 +574,7 @@ export function createTiclawkAdapter(ctx) {
|
|
|
649
574
|
|
|
650
575
|
const binding = await ctx.persistBinding(buildBindingFromSource(msg));
|
|
651
576
|
if (!binding?.runtime) {
|
|
652
|
-
throw new Error('claimed
|
|
577
|
+
throw new Error('claimed delivery missing runtime binding');
|
|
653
578
|
}
|
|
654
579
|
if (!belongsToRuntimeHost(binding, hostId)) {
|
|
655
580
|
await api.releaseDelivery(deliveryId, hostId, 'binding-host-mismatch');
|
|
@@ -755,6 +680,27 @@ export function createTiclawkAdapter(ctx) {
|
|
|
755
680
|
error: err?.message || 'unknown error',
|
|
756
681
|
});
|
|
757
682
|
}
|
|
683
|
+
|
|
684
|
+
// Agents created from the App via POST /me/agents land here in
|
|
685
|
+
// status='unpaired'. Once we've successfully registered the
|
|
686
|
+
// binding locally, flip them to 'connected' — same end state the
|
|
687
|
+
// legacy QR pairing flow leaves agents in. spawn itself stays
|
|
688
|
+
// lazy (happens on first delivery via deliverTurn).
|
|
689
|
+
if (agent.status === 'unpaired' && agentHostId === hostId) {
|
|
690
|
+
try {
|
|
691
|
+
await api.updateAgent(agent.id, {
|
|
692
|
+
status: 'connected',
|
|
693
|
+
runtime_host_id: hostId,
|
|
694
|
+
runtime_host_label: getHostLabel(),
|
|
695
|
+
});
|
|
696
|
+
debugLog('ticlawk', 'binding.unpaired-claimed', { agentId: agent.id });
|
|
697
|
+
} catch (err) {
|
|
698
|
+
debugError('ticlawk', 'binding.unpaired-claim-failed', {
|
|
699
|
+
agentId: agent.id,
|
|
700
|
+
error: err?.message || 'unknown error',
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
758
704
|
}
|
|
759
705
|
const pruned = await pruneDeletedBindings(channels, reason);
|
|
760
706
|
debugLog('ticlawk', 'binding.refresh-ok', {
|
|
@@ -769,6 +715,33 @@ export function createTiclawkAdapter(ctx) {
|
|
|
769
715
|
return hydrated;
|
|
770
716
|
}
|
|
771
717
|
|
|
718
|
+
const syncCredentials = coalesce(async (reason = 'manual') => {
|
|
719
|
+
const startedAt = Date.now();
|
|
720
|
+
try {
|
|
721
|
+
const payload = await api.fetchCredentials();
|
|
722
|
+
const credentials = Array.isArray(payload?.credentials) ? payload.credentials : [];
|
|
723
|
+
const result = persistRuntimeCredentials(credentials);
|
|
724
|
+
debugLog('ticlawk-credentials', 'sync-ok', {
|
|
725
|
+
reason,
|
|
726
|
+
saved: result.saved,
|
|
727
|
+
removed: result.removed,
|
|
728
|
+
durationMs: Date.now() - startedAt,
|
|
729
|
+
wakeToSyncMs: String(reason || '').startsWith('wake') && lastCredentialsWakeAt
|
|
730
|
+
? Date.now() - lastCredentialsWakeAt
|
|
731
|
+
: null,
|
|
732
|
+
});
|
|
733
|
+
} catch (err) {
|
|
734
|
+
if (api.isUpdateRequiredError(err)) {
|
|
735
|
+
recordUpdateRequired(err, 'credentials.sync');
|
|
736
|
+
}
|
|
737
|
+
debugError('ticlawk-credentials', 'sync-failed', {
|
|
738
|
+
reason,
|
|
739
|
+
durationMs: Date.now() - startedAt,
|
|
740
|
+
error: err?.message || 'unknown error',
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
|
|
772
745
|
async function releaseBlockedRows(agentId, messages, reason) {
|
|
773
746
|
for (const msg of messages) {
|
|
774
747
|
if (!msg?.delivery_id) continue;
|
|
@@ -817,7 +790,8 @@ export function createTiclawkAdapter(ctx) {
|
|
|
817
790
|
return { failed: true, claimed: 0, launched: 0 };
|
|
818
791
|
}
|
|
819
792
|
|
|
820
|
-
const
|
|
793
|
+
const orderedData = Array.isArray(data) ? [...data].sort(compareClaimedDeliveries) : [];
|
|
794
|
+
const claimed = orderedData.length;
|
|
821
795
|
debugLog('ticlawk', 'claim.result', {
|
|
822
796
|
reason,
|
|
823
797
|
hostId,
|
|
@@ -836,12 +810,12 @@ export function createTiclawkAdapter(ctx) {
|
|
|
836
810
|
});
|
|
837
811
|
}
|
|
838
812
|
|
|
839
|
-
if (
|
|
813
|
+
if (orderedData.length === 0) {
|
|
840
814
|
return { failed: false, claimed: 0, launched: 0 };
|
|
841
815
|
}
|
|
842
816
|
|
|
843
817
|
const grouped = new Map();
|
|
844
|
-
for (const msg of
|
|
818
|
+
for (const msg of orderedData) {
|
|
845
819
|
const agentId = getAgentIdFromPayload(msg);
|
|
846
820
|
if (!agentId) {
|
|
847
821
|
// Claim rows must carry the recipient agent id; a missing value
|
|
@@ -880,7 +854,7 @@ export function createTiclawkAdapter(ctx) {
|
|
|
880
854
|
launched += messages.length;
|
|
881
855
|
}
|
|
882
856
|
|
|
883
|
-
return { failed: false, claimed:
|
|
857
|
+
return { failed: false, claimed: orderedData.length, launched };
|
|
884
858
|
}
|
|
885
859
|
|
|
886
860
|
async function runDrain(reason) {
|
|
@@ -919,6 +893,15 @@ export function createTiclawkAdapter(ctx) {
|
|
|
919
893
|
bindingsWakeTimer.unref?.();
|
|
920
894
|
}
|
|
921
895
|
|
|
896
|
+
function scheduleCredentialSync(reason) {
|
|
897
|
+
credentialsWakeTimer = clearDebounce(credentialsWakeTimer);
|
|
898
|
+
credentialsWakeTimer = setTimeout(() => {
|
|
899
|
+
credentialsWakeTimer = null;
|
|
900
|
+
void syncCredentials(reason);
|
|
901
|
+
}, CREDENTIALS_WAKE_DEBOUNCE_MS);
|
|
902
|
+
credentialsWakeTimer.unref?.();
|
|
903
|
+
}
|
|
904
|
+
|
|
922
905
|
async function reportHostCapabilitiesNow() {
|
|
923
906
|
const entries = await Promise.all(Object.entries(ctx.runtimes || {})
|
|
924
907
|
.filter(([, runtime]) => typeof runtime?.health === 'function')
|
|
@@ -958,6 +941,7 @@ export function createTiclawkAdapter(ctx) {
|
|
|
958
941
|
void refreshBindings('wake.hello')
|
|
959
942
|
.then(() => requestDrain('wake.hello'))
|
|
960
943
|
.catch(() => {});
|
|
944
|
+
void syncCredentials('wake.hello');
|
|
961
945
|
void reportHostCapabilitiesNow();
|
|
962
946
|
return;
|
|
963
947
|
}
|
|
@@ -979,6 +963,17 @@ export function createTiclawkAdapter(ctx) {
|
|
|
979
963
|
scheduleRefreshAndDrain('wake.bindings.changed');
|
|
980
964
|
return;
|
|
981
965
|
}
|
|
966
|
+
if (event?.type === 'credentials.changed') {
|
|
967
|
+
lastCredentialsWakeAt = Date.now();
|
|
968
|
+
debugLog('ticlawk-wake', 'credentials.changed', {
|
|
969
|
+
credentialId: event.credential_id || null,
|
|
970
|
+
name: event.name || null,
|
|
971
|
+
status: event.status || null,
|
|
972
|
+
reason: event.reason || null,
|
|
973
|
+
});
|
|
974
|
+
scheduleCredentialSync('wake.credentials.changed');
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
982
977
|
if (event?.type === 'auth.revoked') {
|
|
983
978
|
wakeState.lastError = 'auth revoked';
|
|
984
979
|
debugError('ticlawk-wake', 'auth.revoked', {});
|
|
@@ -1013,7 +1008,17 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1013
1008
|
|
|
1014
1009
|
function connectWakeSocket() {
|
|
1015
1010
|
connectorSocket = new TiclawkWakeClient({
|
|
1016
|
-
|
|
1011
|
+
// host_id rides on the WS query string so connector-wake can flip
|
|
1012
|
+
// runtime_hosts.online for the right row. Empty host_id is
|
|
1013
|
+
// tolerated by the server (it just skips the state write).
|
|
1014
|
+
getUrl: () => {
|
|
1015
|
+
const base = String(api.getConnectorWsUrl() || '').trim();
|
|
1016
|
+
if (!base) return '';
|
|
1017
|
+
const hostId = String(getHostId() || '').trim();
|
|
1018
|
+
if (!hostId) return base;
|
|
1019
|
+
const sep = base.includes('?') ? '&' : '?';
|
|
1020
|
+
return `${base}${sep}host_id=${encodeURIComponent(hostId)}`;
|
|
1021
|
+
},
|
|
1017
1022
|
getApiKey: api.getApiKey,
|
|
1018
1023
|
onEvent: handleWakeEvent,
|
|
1019
1024
|
onStatus: handleWakeStatus,
|
|
@@ -1163,11 +1168,11 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1163
1168
|
id: 'ticlawk',
|
|
1164
1169
|
|
|
1165
1170
|
async start() {
|
|
1166
|
-
//
|
|
1167
|
-
//
|
|
1168
|
-
//
|
|
1169
|
-
// when it happens the fix is a one-row UPDATE in supabase.
|
|
1171
|
+
// Stale claimed deliveries are recovered conservatively by the
|
|
1172
|
+
// claim RPC; this startup path only refreshes local bindings and
|
|
1173
|
+
// asks for the next drain.
|
|
1170
1174
|
await refreshBindings('startup');
|
|
1175
|
+
await syncCredentials('startup');
|
|
1171
1176
|
await requestDrain('startup');
|
|
1172
1177
|
connectWakeSocket();
|
|
1173
1178
|
startAuditTimers();
|
|
@@ -165,7 +165,7 @@ export class TiclawkWakeClient {
|
|
|
165
165
|
return;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
if (type === 'jobs.available' || type === 'bindings.changed') {
|
|
168
|
+
if (type === 'jobs.available' || type === 'bindings.changed' || type === 'credentials.changed') {
|
|
169
169
|
this.onEvent?.(msg);
|
|
170
170
|
return;
|
|
171
171
|
}
|