ticlawk 0.1.17-dev.17 → 0.1.17-dev.19
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 +3 -17
- package/bin/ticlawk.mjs +21 -245
- package/package.json +1 -1
- package/src/adapters/ticlawk/api.mjs +51 -327
- package/src/adapters/ticlawk/credentials.mjs +1 -41
- package/src/adapters/ticlawk/index.mjs +27 -249
- package/src/adapters/ticlawk/wake-client.mjs +1 -1
- package/src/cli/agent-commands.mjs +22 -703
- package/src/core/agent-cli-handlers.mjs +18 -519
- package/src/core/agent-home.mjs +1 -64
- package/src/core/events/worker-events.mjs +36 -32
- package/src/core/http.mjs +0 -138
- package/src/core/runtime-contract.mjs +1 -0
- package/src/core/runtime-env.mjs +0 -7
- 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 +30 -0
- package/src/runtimes/_shared/agent-handbook.mjs +0 -38
- package/src/runtimes/_shared/brand.mjs +0 -2
- package/src/runtimes/_shared/goal-step-prompt.mjs +0 -133
- package/src/runtimes/_shared/goal-task-protocol.mjs +0 -50
- package/src/runtimes/_shared/standing-prompt.mjs +0 -331
- package/src/runtimes/_shared/wake-prompt.mjs +0 -296
|
@@ -9,17 +9,15 @@ import { getActiveProfile, ensureLegacyProfile, readProfileMeta, saveAndActivate
|
|
|
9
9
|
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
|
+
import { buildIncomingMessagePrompt } from '../../runtimes/_shared/incoming-message-prompt.mjs';
|
|
12
13
|
import * as api from './api.mjs';
|
|
13
|
-
import { persistApiCredential
|
|
14
|
+
import { persistApiCredential } from './credentials.mjs';
|
|
14
15
|
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
16
|
|
|
18
17
|
const require = createRequire(import.meta.url);
|
|
19
18
|
const qrcode = require('qrcode-terminal');
|
|
20
19
|
const JOBS_WAKE_DEBOUNCE_MS = 100;
|
|
21
20
|
const BINDINGS_WAKE_DEBOUNCE_MS = 500;
|
|
22
|
-
const CREDENTIALS_WAKE_DEBOUNCE_MS = 500;
|
|
23
21
|
const RECOVERY_AUDIT_INTERVAL_MS = 5 * 60 * 1000;
|
|
24
22
|
const BINDING_AUDIT_INTERVAL_MS = 5 * 60 * 1000;
|
|
25
23
|
const UPDATE_RETRY_COOLDOWN_MS = 15 * 60 * 1000;
|
|
@@ -80,28 +78,24 @@ export function normalizeInboundMessage(msg) {
|
|
|
80
78
|
const messageId = msg.id || msg.message_id || null;
|
|
81
79
|
const deliveryId = msg.delivery_id || null;
|
|
82
80
|
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);
|
|
81
|
+
const prompt = buildIncomingMessagePrompt({ ...msg, id: messageId });
|
|
88
82
|
return {
|
|
89
83
|
bindingId: recipientAgentId,
|
|
90
84
|
messageId,
|
|
91
85
|
deliveryId,
|
|
92
86
|
conversationId: msg.conversation_id || null,
|
|
93
|
-
lane: isTransition ? 'goal' : 'chat',
|
|
94
87
|
seq: msg.seq != null ? Number(msg.seq) : null,
|
|
95
88
|
senderType: msg.sender_type || 'human',
|
|
96
|
-
envelopeHeader:
|
|
97
|
-
envelopeTarget:
|
|
98
|
-
text:
|
|
99
|
-
rawText:
|
|
100
|
-
action:
|
|
89
|
+
envelopeHeader: prompt.header,
|
|
90
|
+
envelopeTarget: prompt.target,
|
|
91
|
+
text: prompt.text,
|
|
92
|
+
rawText: prompt.rawText,
|
|
93
|
+
action: msg.action || (media.length > 0 ? 'image' : 'task'),
|
|
101
94
|
media,
|
|
102
95
|
raw: {
|
|
103
96
|
...msg,
|
|
104
97
|
id: messageId,
|
|
98
|
+
type: prompt.type,
|
|
105
99
|
},
|
|
106
100
|
};
|
|
107
101
|
}
|
|
@@ -135,19 +129,6 @@ function getAgentIdFromPayload(payload) {
|
|
|
135
129
|
return String(payload?.recipient_agent_id || '').trim();
|
|
136
130
|
}
|
|
137
131
|
|
|
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
132
|
// Whitelist of meta keys runtimes actually consume. Anything else in
|
|
152
133
|
// the source row's meta blob is dropped on the floor so stale fields
|
|
153
134
|
// (like the post-Y2 workdir/cwd/projectDir leftovers) can't sneak
|
|
@@ -158,11 +139,6 @@ const RUNTIME_META_KEYS = [
|
|
|
158
139
|
'runtimePath',
|
|
159
140
|
'rotatePending',
|
|
160
141
|
'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
142
|
// claude_code
|
|
167
143
|
'claudePath',
|
|
168
144
|
'claudeVersion',
|
|
@@ -279,31 +255,6 @@ function getRuntimeWorkdir(runtimeMeta = {}) {
|
|
|
279
255
|
return String(runtimeMeta.workdir || runtimeMeta.cwd || runtimeMeta.projectDir || '').trim();
|
|
280
256
|
}
|
|
281
257
|
|
|
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
258
|
function runtimeLabel(runtime) {
|
|
308
259
|
if (runtime === 'claude_code') return 'Claude Code';
|
|
309
260
|
if (runtime === 'opencode') return 'OpenCode';
|
|
@@ -417,10 +368,8 @@ export function createTiclawkAdapter(ctx) {
|
|
|
417
368
|
let bindingAuditTimer = null;
|
|
418
369
|
let jobsWakeTimer = null;
|
|
419
370
|
let bindingsWakeTimer = null;
|
|
420
|
-
let credentialsWakeTimer = null;
|
|
421
371
|
let lastJobsWakeAt = 0;
|
|
422
372
|
let lastBindingsWakeAt = 0;
|
|
423
|
-
let lastCredentialsWakeAt = 0;
|
|
424
373
|
let updateRequired = null;
|
|
425
374
|
let lastUpdateRequiredLogAt = 0;
|
|
426
375
|
|
|
@@ -596,7 +545,7 @@ export function createTiclawkAdapter(ctx) {
|
|
|
596
545
|
|
|
597
546
|
const binding = await ctx.persistBinding(buildBindingFromSource(msg));
|
|
598
547
|
if (!binding?.runtime) {
|
|
599
|
-
throw new Error('claimed
|
|
548
|
+
throw new Error('claimed message missing runtime binding');
|
|
600
549
|
}
|
|
601
550
|
if (!belongsToRuntimeHost(binding, hostId)) {
|
|
602
551
|
await api.releaseDelivery(deliveryId, hostId, 'binding-host-mismatch');
|
|
@@ -664,91 +613,6 @@ export function createTiclawkAdapter(ctx) {
|
|
|
664
613
|
}
|
|
665
614
|
}
|
|
666
615
|
|
|
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
616
|
async function refreshBindings(reason = 'manual') {
|
|
753
617
|
let channels = [];
|
|
754
618
|
const startedAt = Date.now();
|
|
@@ -787,27 +651,6 @@ export function createTiclawkAdapter(ctx) {
|
|
|
787
651
|
error: err?.message || 'unknown error',
|
|
788
652
|
});
|
|
789
653
|
}
|
|
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
654
|
}
|
|
812
655
|
const pruned = await pruneDeletedBindings(channels, reason);
|
|
813
656
|
debugLog('ticlawk', 'binding.refresh-ok', {
|
|
@@ -822,33 +665,6 @@ export function createTiclawkAdapter(ctx) {
|
|
|
822
665
|
return hydrated;
|
|
823
666
|
}
|
|
824
667
|
|
|
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
668
|
async function releaseBlockedRows(agentId, messages, reason) {
|
|
853
669
|
for (const msg of messages) {
|
|
854
670
|
if (!msg?.delivery_id) continue;
|
|
@@ -897,8 +713,7 @@ export function createTiclawkAdapter(ctx) {
|
|
|
897
713
|
return { failed: true, claimed: 0, launched: 0 };
|
|
898
714
|
}
|
|
899
715
|
|
|
900
|
-
const
|
|
901
|
-
const claimed = orderedData.length;
|
|
716
|
+
const claimed = Array.isArray(data) ? data.length : 0;
|
|
902
717
|
debugLog('ticlawk', 'claim.result', {
|
|
903
718
|
reason,
|
|
904
719
|
hostId,
|
|
@@ -917,12 +732,12 @@ export function createTiclawkAdapter(ctx) {
|
|
|
917
732
|
});
|
|
918
733
|
}
|
|
919
734
|
|
|
920
|
-
if (
|
|
735
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
921
736
|
return { failed: false, claimed: 0, launched: 0 };
|
|
922
737
|
}
|
|
923
738
|
|
|
924
739
|
const grouped = new Map();
|
|
925
|
-
for (const msg of
|
|
740
|
+
for (const msg of data) {
|
|
926
741
|
const agentId = getAgentIdFromPayload(msg);
|
|
927
742
|
if (!agentId) {
|
|
928
743
|
// Claim rows must carry the recipient agent id; a missing value
|
|
@@ -935,39 +750,33 @@ export function createTiclawkAdapter(ctx) {
|
|
|
935
750
|
});
|
|
936
751
|
continue;
|
|
937
752
|
}
|
|
938
|
-
const
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
bucket.messages.push(msg);
|
|
942
|
-
grouped.set(channelKey, bucket);
|
|
753
|
+
const bucket = grouped.get(agentId) || [];
|
|
754
|
+
bucket.push(msg);
|
|
755
|
+
grouped.set(agentId, bucket);
|
|
943
756
|
}
|
|
944
757
|
|
|
945
758
|
let launched = 0;
|
|
946
|
-
for (const [
|
|
947
|
-
if (processingChannels.has(
|
|
759
|
+
for (const [agentId, messages] of grouped.entries()) {
|
|
760
|
+
if (processingChannels.has(agentId)) {
|
|
948
761
|
debugError('ticlawk', 'claim.blocked-claimed-rows', {
|
|
949
762
|
reason,
|
|
950
|
-
channelKey,
|
|
951
763
|
agentId,
|
|
952
|
-
lane,
|
|
953
764
|
blockedRows: messages.length,
|
|
954
765
|
});
|
|
955
766
|
await releaseBlockedRows(agentId, messages, reason);
|
|
956
767
|
continue;
|
|
957
768
|
}
|
|
958
|
-
const run = (
|
|
959
|
-
? processGoalTransitionsForAgent(agentId, messages)
|
|
960
|
-
: processPendingMessagesForAgent(agentId, messages))
|
|
769
|
+
const run = processPendingMessagesForAgent(agentId, messages)
|
|
961
770
|
.catch(() => {})
|
|
962
771
|
.finally(() => {
|
|
963
|
-
processingChannels.delete(
|
|
772
|
+
processingChannels.delete(agentId);
|
|
964
773
|
void requestDrain('channel.completed');
|
|
965
774
|
});
|
|
966
|
-
processingChannels.set(
|
|
775
|
+
processingChannels.set(agentId, run);
|
|
967
776
|
launched += messages.length;
|
|
968
777
|
}
|
|
969
778
|
|
|
970
|
-
return { failed: false, claimed:
|
|
779
|
+
return { failed: false, claimed: data.length, launched };
|
|
971
780
|
}
|
|
972
781
|
|
|
973
782
|
async function runDrain(reason) {
|
|
@@ -1006,15 +815,6 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1006
815
|
bindingsWakeTimer.unref?.();
|
|
1007
816
|
}
|
|
1008
817
|
|
|
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
818
|
async function reportHostCapabilitiesNow() {
|
|
1019
819
|
const entries = await Promise.all(Object.entries(ctx.runtimes || {})
|
|
1020
820
|
.filter(([, runtime]) => typeof runtime?.health === 'function')
|
|
@@ -1054,7 +854,6 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1054
854
|
void refreshBindings('wake.hello')
|
|
1055
855
|
.then(() => requestDrain('wake.hello'))
|
|
1056
856
|
.catch(() => {});
|
|
1057
|
-
void syncCredentials('wake.hello');
|
|
1058
857
|
void reportHostCapabilitiesNow();
|
|
1059
858
|
return;
|
|
1060
859
|
}
|
|
@@ -1076,17 +875,6 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1076
875
|
scheduleRefreshAndDrain('wake.bindings.changed');
|
|
1077
876
|
return;
|
|
1078
877
|
}
|
|
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
878
|
if (event?.type === 'auth.revoked') {
|
|
1091
879
|
wakeState.lastError = 'auth revoked';
|
|
1092
880
|
debugError('ticlawk-wake', 'auth.revoked', {});
|
|
@@ -1121,17 +909,7 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1121
909
|
|
|
1122
910
|
function connectWakeSocket() {
|
|
1123
911
|
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
|
-
},
|
|
912
|
+
getUrl: api.getConnectorWsUrl,
|
|
1135
913
|
getApiKey: api.getApiKey,
|
|
1136
914
|
onEvent: handleWakeEvent,
|
|
1137
915
|
onStatus: handleWakeStatus,
|
|
@@ -1281,11 +1059,11 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1281
1059
|
id: 'ticlawk',
|
|
1282
1060
|
|
|
1283
1061
|
async start() {
|
|
1284
|
-
//
|
|
1285
|
-
// claim
|
|
1286
|
-
//
|
|
1062
|
+
// No stale-delivery recovery anywhere — if the daemon is killed
|
|
1063
|
+
// mid-claim, the row stays `claimed` forever. lease_expires_at
|
|
1064
|
+
// was dropped in X1; no cron has replaced it. Rare in practice;
|
|
1065
|
+
// when it happens the fix is a one-row UPDATE in supabase.
|
|
1287
1066
|
await refreshBindings('startup');
|
|
1288
|
-
await syncCredentials('startup');
|
|
1289
1067
|
await requestDrain('startup');
|
|
1290
1068
|
connectWakeSocket();
|
|
1291
1069
|
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') {
|
|
169
169
|
this.onEvent?.(msg);
|
|
170
170
|
return;
|
|
171
171
|
}
|