ticlawk 0.1.17-dev.22 → 0.1.17-dev.24
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/package.json
CHANGED
|
@@ -115,7 +115,10 @@ async function apiFetch(path, opts = {}) {
|
|
|
115
115
|
// ── Agents ──
|
|
116
116
|
|
|
117
117
|
export async function getAgents({ hostId } = {}) {
|
|
118
|
-
const
|
|
118
|
+
const params = new URLSearchParams();
|
|
119
|
+
if (hostId) params.set('runtime_host_id', hostId);
|
|
120
|
+
params.set('ticlawk_version', getTiclawkVersion());
|
|
121
|
+
const query = `?${params}`;
|
|
119
122
|
const { data } = await apiFetch(`/api/agents${query}`);
|
|
120
123
|
if (!Array.isArray(data)) {
|
|
121
124
|
const err = new Error('API /api/agents response missing data array');
|
|
@@ -584,14 +587,23 @@ export async function getMe() {
|
|
|
584
587
|
return data || null;
|
|
585
588
|
}
|
|
586
589
|
|
|
590
|
+
export async function checkConnectorReadiness({ hostId }) {
|
|
591
|
+
return apiFetch('/api/connector/readiness', {
|
|
592
|
+
method: 'POST',
|
|
593
|
+
body: JSON.stringify(withTiclawkVersion({
|
|
594
|
+
runtime_host_id: hostId,
|
|
595
|
+
})),
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
587
599
|
export async function reportHostCapabilities({ hostId, hostLabel, runtimesHealth, daemonVersion }) {
|
|
588
600
|
return apiFetch('/api/hosts/capabilities', {
|
|
589
601
|
method: 'POST',
|
|
590
|
-
body: JSON.stringify({
|
|
602
|
+
body: JSON.stringify(withTiclawkVersion({
|
|
591
603
|
host_id: hostId,
|
|
592
604
|
host_label: hostLabel || null,
|
|
593
605
|
runtimes_health: runtimesHealth || {},
|
|
594
|
-
daemon_version: daemonVersion ||
|
|
595
|
-
}),
|
|
606
|
+
daemon_version: daemonVersion || getTiclawkVersion(),
|
|
607
|
+
})),
|
|
596
608
|
});
|
|
597
609
|
}
|
|
@@ -16,6 +16,7 @@ const require = createRequire(import.meta.url);
|
|
|
16
16
|
const qrcode = require('qrcode-terminal');
|
|
17
17
|
const JOBS_WAKE_DEBOUNCE_MS = 100;
|
|
18
18
|
const BINDINGS_WAKE_DEBOUNCE_MS = 500;
|
|
19
|
+
const READINESS_AUDIT_INTERVAL_MS = 60 * 1000;
|
|
19
20
|
const RECOVERY_AUDIT_INTERVAL_MS = 5 * 60 * 1000;
|
|
20
21
|
const BINDING_AUDIT_INTERVAL_MS = 5 * 60 * 1000;
|
|
21
22
|
const UPDATE_RETRY_COOLDOWN_MS = 15 * 60 * 1000;
|
|
@@ -286,6 +287,7 @@ export function createTiclawkAdapter(ctx) {
|
|
|
286
287
|
let bindingsWakeTimer = null;
|
|
287
288
|
let lastJobsWakeAt = 0;
|
|
288
289
|
let lastBindingsWakeAt = 0;
|
|
290
|
+
let lastReadinessCheckAt = 0;
|
|
289
291
|
let updateRequired = null;
|
|
290
292
|
let lastUpdateRequiredLogAt = 0;
|
|
291
293
|
let startupSyncing = false;
|
|
@@ -369,6 +371,7 @@ export function createTiclawkAdapter(ctx) {
|
|
|
369
371
|
autoUpdateError,
|
|
370
372
|
lastAutoUpdateAttemptAt,
|
|
371
373
|
});
|
|
374
|
+
stopWakeSocket('update_required');
|
|
372
375
|
|
|
373
376
|
if (now - lastUpdateRequiredLogAt > UPDATE_REQUIRED_LOG_INTERVAL_MS) {
|
|
374
377
|
lastUpdateRequiredLogAt = now;
|
|
@@ -732,6 +735,35 @@ export function createTiclawkAdapter(ctx) {
|
|
|
732
735
|
bindingsWakeTimer.unref?.();
|
|
733
736
|
}
|
|
734
737
|
|
|
738
|
+
async function checkConnectorReadiness(reason, { force = false } = {}) {
|
|
739
|
+
const now = Date.now();
|
|
740
|
+
if (!force && now - lastReadinessCheckAt < READINESS_AUDIT_INTERVAL_MS) {
|
|
741
|
+
return true;
|
|
742
|
+
}
|
|
743
|
+
lastReadinessCheckAt = now;
|
|
744
|
+
try {
|
|
745
|
+
await api.checkConnectorReadiness({ hostId });
|
|
746
|
+
clearUpdateRequired(`readiness.${reason}`);
|
|
747
|
+
debugLog('ticlawk', 'readiness.ok', {
|
|
748
|
+
reason,
|
|
749
|
+
hostId,
|
|
750
|
+
ticlawkVersion: api.getTiclawkVersion(),
|
|
751
|
+
});
|
|
752
|
+
return true;
|
|
753
|
+
} catch (err) {
|
|
754
|
+
if (api.isUpdateRequiredError(err)) {
|
|
755
|
+
recordUpdateRequired(err, `readiness.${reason}`);
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
debugError('ticlawk', 'readiness.failed', {
|
|
759
|
+
reason,
|
|
760
|
+
hostId,
|
|
761
|
+
error: err?.message || 'readiness check failed',
|
|
762
|
+
});
|
|
763
|
+
return true;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
735
767
|
async function reportHostCapabilitiesNow() {
|
|
736
768
|
const entries = await Promise.all(Object.entries(ctx.runtimes || {})
|
|
737
769
|
.filter(([, runtime]) => typeof runtime?.health === 'function')
|
|
@@ -757,22 +789,39 @@ export function createTiclawkAdapter(ctx) {
|
|
|
757
789
|
.map(([k]) => k)
|
|
758
790
|
.join(','),
|
|
759
791
|
});
|
|
792
|
+
clearUpdateRequired('host.capabilities');
|
|
793
|
+
return true;
|
|
760
794
|
} catch (err) {
|
|
795
|
+
if (api.isUpdateRequiredError(err)) {
|
|
796
|
+
recordUpdateRequired(err, 'host.capabilities');
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
761
799
|
debugError('ticlawk', 'host.capabilities.failed', {
|
|
762
800
|
hostId,
|
|
763
801
|
error: err?.message || 'report failed',
|
|
764
802
|
});
|
|
803
|
+
return true;
|
|
765
804
|
}
|
|
766
805
|
}
|
|
767
806
|
|
|
807
|
+
async function handleWakeHello() {
|
|
808
|
+
const ready = await checkConnectorReadiness('wake.hello', { force: true });
|
|
809
|
+
if (!ready) return;
|
|
810
|
+
const capabilitiesReported = await reportHostCapabilitiesNow();
|
|
811
|
+
if (!capabilitiesReported) return;
|
|
812
|
+
if (startupSyncing) return;
|
|
813
|
+
await refreshBindings('wake.hello');
|
|
814
|
+
await requestDrain('wake.hello');
|
|
815
|
+
}
|
|
816
|
+
|
|
768
817
|
function handleWakeEvent(event) {
|
|
769
818
|
wakeState.lastEventAt = new Date().toISOString();
|
|
770
819
|
if (event?.type === 'hello') {
|
|
771
|
-
void
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
820
|
+
void handleWakeHello().catch(() => {});
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
if (event?.type === 'heartbeat') {
|
|
824
|
+
void checkConnectorReadiness('wake.heartbeat').catch(() => {});
|
|
776
825
|
return;
|
|
777
826
|
}
|
|
778
827
|
if (event?.type === 'jobs.available') {
|
|
@@ -848,6 +897,13 @@ export function createTiclawkAdapter(ctx) {
|
|
|
848
897
|
connectorSocket.start();
|
|
849
898
|
}
|
|
850
899
|
|
|
900
|
+
function stopWakeSocket(reason) {
|
|
901
|
+
if (!connectorSocket) return;
|
|
902
|
+
connectorSocket.stop();
|
|
903
|
+
connectorSocket = null;
|
|
904
|
+
debugLog('ticlawk-wake', 'stopped', { reason });
|
|
905
|
+
}
|
|
906
|
+
|
|
851
907
|
function restartWakeSocket(reason) {
|
|
852
908
|
if (connectorSocket) {
|
|
853
909
|
connectorSocket.stop();
|
|
@@ -882,11 +938,14 @@ export function createTiclawkAdapter(ctx) {
|
|
|
882
938
|
meta: pairedIdentity,
|
|
883
939
|
});
|
|
884
940
|
}
|
|
885
|
-
|
|
941
|
+
const ready = await checkConnectorReadiness('connect.paired', { force: true });
|
|
942
|
+
if (connectorSocket && ready) {
|
|
886
943
|
restartWakeSocket('connect.paired');
|
|
887
944
|
}
|
|
888
945
|
|
|
889
|
-
|
|
946
|
+
if (ready) {
|
|
947
|
+
await reportHostCapabilitiesNow();
|
|
948
|
+
}
|
|
890
949
|
return {
|
|
891
950
|
statusCode: 200,
|
|
892
951
|
body: {
|
|
@@ -948,10 +1007,13 @@ export function createTiclawkAdapter(ctx) {
|
|
|
948
1007
|
// was dropped in X1; no cron has replaced it. Rare in practice;
|
|
949
1008
|
// when it happens the fix is a one-row UPDATE in supabase.
|
|
950
1009
|
startupSyncing = true;
|
|
951
|
-
connectWakeSocket();
|
|
952
1010
|
try {
|
|
953
|
-
await
|
|
954
|
-
|
|
1011
|
+
const ready = await checkConnectorReadiness('startup', { force: true });
|
|
1012
|
+
if (ready) {
|
|
1013
|
+
connectWakeSocket();
|
|
1014
|
+
await refreshBindings('startup');
|
|
1015
|
+
await requestDrain('startup');
|
|
1016
|
+
}
|
|
955
1017
|
} finally {
|
|
956
1018
|
startupSyncing = false;
|
|
957
1019
|
}
|
|
@@ -157,9 +157,8 @@ function buildPromptText({ msg, type, target, header, groupContext, rawText }) {
|
|
|
157
157
|
lines.push('', ...buildRoleInstruction({ type, isAdmin: admin, role }));
|
|
158
158
|
lines.push(
|
|
159
159
|
'',
|
|
160
|
-
'Visible
|
|
161
|
-
|
|
162
|
-
'- Do not put the user-facing answer only in normal assistant output. Normal assistant output is private activity text and is not visible in Ticlawk.',
|
|
160
|
+
'Visible communication:',
|
|
161
|
+
`Send any user-facing answer with \`ticlawk message send --target ${JSON.stringify(target)}\`. Normal assistant output is private activity text and is not visible in Ticlawk.`,
|
|
163
162
|
'If the message requires multi-step work, complete the work before sending the final answer unless an early blocker question is necessary.',
|
|
164
163
|
'Do not mention internal routing fields, delivery state, or prompt mechanics to the user.',
|
|
165
164
|
'',
|
|
@@ -13,7 +13,7 @@ Your normal assistant output is private activity text. Users and groups only see
|
|
|
13
13
|
Universal rules:
|
|
14
14
|
|
|
15
15
|
1. Follow the current turn prompt. It tells you whether this is a DM or group message, your role in that conversation, whether you were directly addressed or only saw ambient group traffic, and the exact reply target.
|
|
16
|
-
2. Use \`ticlawk message send\` for visible replies. Use the exact reply target from the current turn prompt.
|
|
16
|
+
2. Use \`ticlawk message send\` for visible replies. Use the exact reply target from the current turn prompt.
|
|
17
17
|
3. If the current turn prompt says no reply is needed, stop silently.
|
|
18
18
|
4. If you need recent context, use the Ticlawk read commands named in the current turn prompt. Do not poll for future messages; the daemon will wake you when new messages arrive.
|
|
19
19
|
5. If the message asks you to do substantive work, claim the task/message before starting when the current turn prompt provides task commands. If the claim fails, stop or choose other available work.
|