ticlawk 0.1.16-dev.9 → 0.1.17-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -3
- package/bin/ticlawk.mjs +255 -21
- package/package.json +1 -1
- package/src/adapters/ticlawk/api.mjs +350 -50
- package/src/adapters/ticlawk/credentials.mjs +41 -1
- package/src/adapters/ticlawk/index.mjs +248 -130
- package/src/adapters/ticlawk/wake-client.mjs +1 -1
- package/src/cli/agent-commands.mjs +715 -18
- package/src/core/agent-cli-handlers.mjs +556 -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 +152 -0
- package/src/core/runtime-contract.mjs +0 -1
- package/src/core/runtime-env.mjs +7 -0
- package/src/core/runtime-support.mjs +130 -78
- package/src/runtimes/_shared/agent-handbook.mjs +45 -0
- package/src/runtimes/_shared/brand.mjs +2 -0
- package/src/runtimes/_shared/goal-step-prompt.mjs +98 -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 +47 -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 +124 -279
- package/src/runtimes/_shared/wake-prompt.mjs +268 -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
|
@@ -30,8 +30,9 @@ export function getConnectorWsUrl() {
|
|
|
30
30
|
return DEFAULT_CONNECTOR_WS_URL;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
//
|
|
34
|
-
//
|
|
33
|
+
// Non-terminal event writes are serialized per agent for stable logs.
|
|
34
|
+
// Terminal turn events bypass this queue so telemetry cannot hold up the
|
|
35
|
+
// delivery lifecycle.
|
|
35
36
|
const agentEventQueues = new Map(); // agentId -> Promise
|
|
36
37
|
|
|
37
38
|
export class TiclawkUpdateRequiredError extends Error {
|
|
@@ -151,13 +152,13 @@ export async function updateChannel(id, updates) {
|
|
|
151
152
|
|
|
152
153
|
// ── Deliveries (replaces legacy message_jobs poll/ack) ──
|
|
153
154
|
|
|
154
|
-
export async function claimPendingDeliveries(hostId, limit = 5,
|
|
155
|
+
export async function claimPendingDeliveries(hostId, limit = 5, excludedChannels = []) {
|
|
155
156
|
const { data } = await apiFetch('/api/agent/deliveries/claim-pending', {
|
|
156
157
|
method: 'POST',
|
|
157
158
|
body: JSON.stringify(withTiclawkVersion({
|
|
158
159
|
runtime_host_id: hostId,
|
|
159
160
|
limit,
|
|
160
|
-
|
|
161
|
+
excluded_channels: excludedChannels,
|
|
161
162
|
})),
|
|
162
163
|
});
|
|
163
164
|
return data || [];
|
|
@@ -188,6 +189,7 @@ export async function sendAgentMessage({
|
|
|
188
189
|
runtimeHostId,
|
|
189
190
|
visibility,
|
|
190
191
|
mediaAssetIds,
|
|
192
|
+
metadata,
|
|
191
193
|
}) {
|
|
192
194
|
const { data } = await apiFetch('/api/agent/messages/send', {
|
|
193
195
|
method: 'POST',
|
|
@@ -200,6 +202,7 @@ export async function sendAgentMessage({
|
|
|
200
202
|
runtime_host_id: runtimeHostId ?? null,
|
|
201
203
|
visibility: visibility || null,
|
|
202
204
|
media_asset_ids: Array.isArray(mediaAssetIds) && mediaAssetIds.length > 0 ? mediaAssetIds : undefined,
|
|
205
|
+
metadata: metadata ?? undefined,
|
|
203
206
|
}),
|
|
204
207
|
});
|
|
205
208
|
return data || null;
|
|
@@ -222,7 +225,7 @@ export async function readAgentMessages({
|
|
|
222
225
|
return data || [];
|
|
223
226
|
}
|
|
224
227
|
|
|
225
|
-
export async function createAgentTask({ actingAgentId, conversationId, text, title }) {
|
|
228
|
+
export async function createAgentTask({ actingAgentId, conversationId, text, title, assignAgentId }) {
|
|
226
229
|
return apiFetch('/api/agent/tasks/create', {
|
|
227
230
|
method: 'POST',
|
|
228
231
|
body: JSON.stringify({
|
|
@@ -230,6 +233,7 @@ export async function createAgentTask({ actingAgentId, conversationId, text, tit
|
|
|
230
233
|
conversation_id: conversationId,
|
|
231
234
|
text,
|
|
232
235
|
title: title ?? null,
|
|
236
|
+
assign_agent_id: assignAgentId ?? null,
|
|
233
237
|
}),
|
|
234
238
|
});
|
|
235
239
|
}
|
|
@@ -282,6 +286,71 @@ export async function updateAgentTask({ actingAgentId, taskId, status }) {
|
|
|
282
286
|
});
|
|
283
287
|
}
|
|
284
288
|
|
|
289
|
+
export async function reportGoalTransition({
|
|
290
|
+
actingAgentId, conversationId, transitionId, outcome, detail, currentTaskId,
|
|
291
|
+
}) {
|
|
292
|
+
return apiFetch('/api/agent/goal/report', {
|
|
293
|
+
method: 'POST',
|
|
294
|
+
body: JSON.stringify({
|
|
295
|
+
acting_as_agent_id: actingAgentId,
|
|
296
|
+
conversation_id: conversationId,
|
|
297
|
+
transition_id: transitionId,
|
|
298
|
+
outcome,
|
|
299
|
+
detail: detail ?? null,
|
|
300
|
+
current_task_id: currentTaskId ?? null,
|
|
301
|
+
}),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export async function noteGoalChanged({ actingAgentId, conversationId }) {
|
|
306
|
+
return apiFetch('/api/agent/goal/changed', {
|
|
307
|
+
method: 'POST',
|
|
308
|
+
body: JSON.stringify({
|
|
309
|
+
acting_as_agent_id: actingAgentId,
|
|
310
|
+
conversation_id: conversationId,
|
|
311
|
+
}),
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export async function requestGoalApproval({
|
|
316
|
+
actingAgentId, conversationId, title, detail, ttlSeconds,
|
|
317
|
+
}) {
|
|
318
|
+
return apiFetch('/api/agent/approval/request', {
|
|
319
|
+
method: 'POST',
|
|
320
|
+
body: JSON.stringify({
|
|
321
|
+
acting_as_agent_id: actingAgentId,
|
|
322
|
+
conversation_id: conversationId,
|
|
323
|
+
title,
|
|
324
|
+
detail: detail ?? null,
|
|
325
|
+
ttl_seconds: ttlSeconds ?? null,
|
|
326
|
+
}),
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export async function listGoalApprovals({ actingAgentId, conversationId }) {
|
|
331
|
+
const params = new URLSearchParams();
|
|
332
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
333
|
+
params.set('conversation_id', conversationId);
|
|
334
|
+
const { data } = await apiFetch(`/api/agent/approval/list?${params}`);
|
|
335
|
+
return data || [];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export async function resolveGoalApproval({
|
|
339
|
+
actingAgentId, requestId, decision, originalText, confidence, sourceMessageId,
|
|
340
|
+
}) {
|
|
341
|
+
return apiFetch('/api/agent/approval/resolve', {
|
|
342
|
+
method: 'POST',
|
|
343
|
+
body: JSON.stringify({
|
|
344
|
+
acting_as_agent_id: actingAgentId,
|
|
345
|
+
request_id: requestId,
|
|
346
|
+
decision,
|
|
347
|
+
original_text: originalText ?? null,
|
|
348
|
+
confidence: confidence ?? null,
|
|
349
|
+
source_message_id: sourceMessageId ?? null,
|
|
350
|
+
}),
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
285
354
|
export async function listAgentTasks({ actingAgentId, conversationId }) {
|
|
286
355
|
const params = new URLSearchParams();
|
|
287
356
|
params.set('acting_as_agent_id', actingAgentId);
|
|
@@ -484,63 +553,294 @@ export async function removeAgentGroupMember({
|
|
|
484
553
|
);
|
|
485
554
|
}
|
|
486
555
|
|
|
556
|
+
// ── Workstreams (managed groups) ──
|
|
557
|
+
|
|
558
|
+
export async function createWorkstream({
|
|
559
|
+
actingAgentId, name, description, charter, memberAgentIds,
|
|
560
|
+
}) {
|
|
561
|
+
return apiFetch('/api/agent/workstreams', {
|
|
562
|
+
method: 'POST',
|
|
563
|
+
body: JSON.stringify({
|
|
564
|
+
acting_as_agent_id: actingAgentId,
|
|
565
|
+
name,
|
|
566
|
+
description: description ?? null,
|
|
567
|
+
charter: charter ?? null,
|
|
568
|
+
member_agent_ids: memberAgentIds || [],
|
|
569
|
+
}),
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export async function deleteWorkstream({ actingAgentId, conversationId }) {
|
|
574
|
+
return apiFetch(
|
|
575
|
+
`/api/agent/workstreams/${encodeURIComponent(conversationId)}`,
|
|
576
|
+
{
|
|
577
|
+
method: 'DELETE',
|
|
578
|
+
body: JSON.stringify({ acting_as_agent_id: actingAgentId }),
|
|
579
|
+
},
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
export async function listWorkstreams({ actingAgentId }) {
|
|
584
|
+
const params = new URLSearchParams();
|
|
585
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
586
|
+
const { data } = await apiFetch(`/api/agent/workstreams?${params}`);
|
|
587
|
+
return data || [];
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// ── Agents ──
|
|
591
|
+
|
|
592
|
+
export async function listAgentSlots({ actingAgentId }) {
|
|
593
|
+
const params = new URLSearchParams();
|
|
594
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
595
|
+
const { data } = await apiFetch(`/api/agent/agents?${params}`);
|
|
596
|
+
return data || [];
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
export async function createAgentSlot({
|
|
600
|
+
actingAgentId, name, runtime, description, displayName, model,
|
|
601
|
+
}) {
|
|
602
|
+
return apiFetch('/api/agent/agents', {
|
|
603
|
+
method: 'POST',
|
|
604
|
+
body: JSON.stringify({
|
|
605
|
+
acting_as_agent_id: actingAgentId,
|
|
606
|
+
name,
|
|
607
|
+
runtime,
|
|
608
|
+
description: description ?? null,
|
|
609
|
+
display_name: displayName ?? null,
|
|
610
|
+
model: model ?? null,
|
|
611
|
+
}),
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export async function archiveAgentSlot({ actingAgentId, agentId }) {
|
|
616
|
+
return apiFetch(
|
|
617
|
+
`/api/agent/agents/${encodeURIComponent(agentId)}`,
|
|
618
|
+
{
|
|
619
|
+
method: 'DELETE',
|
|
620
|
+
body: JSON.stringify({ acting_as_agent_id: actingAgentId }),
|
|
621
|
+
},
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// ── Services ──
|
|
626
|
+
|
|
627
|
+
export async function createService({
|
|
628
|
+
actingAgentId, name, description, contractSchema, endpointConfig,
|
|
629
|
+
}) {
|
|
630
|
+
return apiFetch('/api/agent/services', {
|
|
631
|
+
method: 'POST',
|
|
632
|
+
body: JSON.stringify({
|
|
633
|
+
acting_as_agent_id: actingAgentId,
|
|
634
|
+
name,
|
|
635
|
+
description: description ?? null,
|
|
636
|
+
contract_schema: contractSchema ?? null,
|
|
637
|
+
endpoint_config: endpointConfig,
|
|
638
|
+
}),
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
export async function updateService({ actingAgentId, serviceId, ...patch }) {
|
|
643
|
+
return apiFetch(
|
|
644
|
+
`/api/agent/services/${encodeURIComponent(serviceId)}`,
|
|
645
|
+
{
|
|
646
|
+
method: 'PATCH',
|
|
647
|
+
body: JSON.stringify({ acting_as_agent_id: actingAgentId, ...patch }),
|
|
648
|
+
},
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
export async function deleteService({ actingAgentId, serviceId }) {
|
|
653
|
+
return apiFetch(
|
|
654
|
+
`/api/agent/services/${encodeURIComponent(serviceId)}`,
|
|
655
|
+
{
|
|
656
|
+
method: 'DELETE',
|
|
657
|
+
body: JSON.stringify({ acting_as_agent_id: actingAgentId }),
|
|
658
|
+
},
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
export async function listServices({ actingAgentId }) {
|
|
663
|
+
const params = new URLSearchParams();
|
|
664
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
665
|
+
const { data } = await apiFetch(`/api/agent/services?${params}`);
|
|
666
|
+
return data || [];
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
export async function getServiceInfo({ actingAgentId, name }) {
|
|
670
|
+
const params = new URLSearchParams();
|
|
671
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
672
|
+
return apiFetch(
|
|
673
|
+
`/api/agent/services/${encodeURIComponent(name)}/info?${params}`,
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
export async function callService({ actingAgentId, name, input }) {
|
|
678
|
+
return apiFetch(
|
|
679
|
+
`/api/agent/services/${encodeURIComponent(name)}/call`,
|
|
680
|
+
{
|
|
681
|
+
method: 'POST',
|
|
682
|
+
body: JSON.stringify({ acting_as_agent_id: actingAgentId, input }),
|
|
683
|
+
},
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// ── Briefings ──
|
|
688
|
+
|
|
689
|
+
export async function getBriefing({actingAgentId, briefingId}) {
|
|
690
|
+
const params = new URLSearchParams();
|
|
691
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
692
|
+
return apiFetch(`/api/agent/briefings/${encodeURIComponent(briefingId)}?${params}`);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
export async function publishBriefing({actingAgentId, bodyText, attachmentAssetId, currentConversationId, responseMode}) {
|
|
696
|
+
const body = { acting_as_agent_id: actingAgentId };
|
|
697
|
+
if (bodyText != null) body.body_text = bodyText;
|
|
698
|
+
if (attachmentAssetId != null) body.attachment_asset_id = attachmentAssetId;
|
|
699
|
+
if (currentConversationId != null) body.current_conversation_id = currentConversationId;
|
|
700
|
+
if (responseMode != null) body.response_mode = responseMode;
|
|
701
|
+
return apiFetch('/api/agent/briefings', {
|
|
702
|
+
method: 'POST',
|
|
703
|
+
body: JSON.stringify(body),
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// ── Credentials (slot creation + daemon sync) ──
|
|
708
|
+
|
|
709
|
+
export async function fetchCredentials() {
|
|
710
|
+
return apiFetch('/api/agent/credentials', { method: 'GET' });
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
export async function requestCredential({
|
|
714
|
+
actingAgentId, name, description, workstreamId,
|
|
715
|
+
}) {
|
|
716
|
+
return apiFetch('/api/agent/credentials', {
|
|
717
|
+
method: 'POST',
|
|
718
|
+
body: JSON.stringify({
|
|
719
|
+
acting_as_agent_id: actingAgentId,
|
|
720
|
+
name,
|
|
721
|
+
description: description ?? null,
|
|
722
|
+
workstream_id: workstreamId ?? null,
|
|
723
|
+
}),
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// ── Workstream dashboard ──
|
|
728
|
+
|
|
729
|
+
export async function setWorkstreamDashboard({
|
|
730
|
+
actingAgentId, conversationId, dataJson, htmlTemplate,
|
|
731
|
+
}) {
|
|
732
|
+
const body = { acting_as_agent_id: actingAgentId };
|
|
733
|
+
// Distinguish "omit" from "set to null" — only include keys the caller
|
|
734
|
+
// explicitly passed (including null clears the field).
|
|
735
|
+
if (dataJson !== undefined) body.data_json = dataJson;
|
|
736
|
+
if (htmlTemplate !== undefined) body.html_template = htmlTemplate;
|
|
737
|
+
return apiFetch(
|
|
738
|
+
`/api/agent/workstreams/${encodeURIComponent(conversationId)}/dashboard`,
|
|
739
|
+
{ method: 'POST', body: JSON.stringify(body) },
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
export async function getWorkstreamDashboard({ actingAgentId, conversationId }) {
|
|
744
|
+
const params = new URLSearchParams();
|
|
745
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
746
|
+
return apiFetch(
|
|
747
|
+
`/api/agent/workstreams/${encodeURIComponent(conversationId)}/dashboard?${params}`,
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// ── Workstream charter ──
|
|
752
|
+
|
|
753
|
+
export async function getWorkstreamCharter({ actingAgentId, conversationId }) {
|
|
754
|
+
const params = new URLSearchParams();
|
|
755
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
756
|
+
return apiFetch(
|
|
757
|
+
`/api/agent/workstreams/${encodeURIComponent(conversationId)}/charter?${params}`,
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
export async function setWorkstreamCharter({ actingAgentId, conversationId, charter }) {
|
|
762
|
+
return apiFetch(
|
|
763
|
+
`/api/agent/workstreams/${encodeURIComponent(conversationId)}/charter`,
|
|
764
|
+
{
|
|
765
|
+
method: 'POST',
|
|
766
|
+
body: JSON.stringify({
|
|
767
|
+
acting_as_agent_id: actingAgentId,
|
|
768
|
+
charter: charter ?? null,
|
|
769
|
+
}),
|
|
770
|
+
},
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
|
|
487
774
|
// ── Channel event pipe ──
|
|
488
775
|
|
|
489
776
|
export async function postEvent({ agent, agent_id, runtime_host_id, session_id, cwd, runtime_version, event, required = false }) {
|
|
490
777
|
const agentId = agent_id || null;
|
|
491
778
|
const queueKey = agentId || `${agent}:${session_id || ''}`;
|
|
492
|
-
const previous = agentEventQueues.get(queueKey) || Promise.resolve(null);
|
|
493
779
|
const eventName = event?.worker_event_name || event?.hook_event_name || event?.event_name || 'unknown';
|
|
494
780
|
const turnId = event?.turn_id || event?.reply_to_message_id || null;
|
|
495
781
|
const seq = event?.event_seq ?? null;
|
|
496
782
|
const deltaChars = typeof event?.delta === 'string' ? event.delta.length : null;
|
|
497
783
|
|
|
784
|
+
if (eventName === 'worker.message.delta') {
|
|
785
|
+
debugLog('events', 'delta.drop', {
|
|
786
|
+
agent,
|
|
787
|
+
agentId,
|
|
788
|
+
sessionId: shortId(session_id),
|
|
789
|
+
turnId: shortId(turnId),
|
|
790
|
+
deltaChars,
|
|
791
|
+
});
|
|
792
|
+
return null;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const postOnce = async () => {
|
|
796
|
+
const startedAt = Date.now();
|
|
797
|
+
try {
|
|
798
|
+
const result = await apiFetch('/api/events', {
|
|
799
|
+
method: 'POST',
|
|
800
|
+
body: JSON.stringify({ agent, agent_id: agentId, runtime_host_id, session_id, cwd, runtime_version, event }),
|
|
801
|
+
timeout: 5000,
|
|
802
|
+
});
|
|
803
|
+
if (required && result?.matched === false) {
|
|
804
|
+
throw new Error(`event was not matched (${eventName})`);
|
|
805
|
+
}
|
|
806
|
+
debugLog('events', 'post.ok', {
|
|
807
|
+
agent,
|
|
808
|
+
agentId,
|
|
809
|
+
sessionId: shortId(session_id),
|
|
810
|
+
runtimeVersion: runtime_version ?? null,
|
|
811
|
+
turnId: shortId(turnId),
|
|
812
|
+
eventName,
|
|
813
|
+
seq,
|
|
814
|
+
durationMs: Date.now() - startedAt,
|
|
815
|
+
});
|
|
816
|
+
return result;
|
|
817
|
+
} catch (err) {
|
|
818
|
+
debugError('events', 'post.failed', {
|
|
819
|
+
agent,
|
|
820
|
+
agentId,
|
|
821
|
+
sessionId: shortId(session_id),
|
|
822
|
+
runtimeVersion: runtime_version ?? null,
|
|
823
|
+
turnId: shortId(turnId),
|
|
824
|
+
eventName,
|
|
825
|
+
seq,
|
|
826
|
+
durationMs: Date.now() - startedAt,
|
|
827
|
+
error: err?.message || 'unknown error',
|
|
828
|
+
});
|
|
829
|
+
if (required) {
|
|
830
|
+
throw err;
|
|
831
|
+
}
|
|
832
|
+
return null;
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
if (eventName === 'worker.turn.complete' || eventName === 'worker.turn.error') {
|
|
837
|
+
return postOnce();
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
const previous = agentEventQueues.get(queueKey) || Promise.resolve(null);
|
|
498
841
|
const queued = previous
|
|
499
842
|
.catch(() => null)
|
|
500
|
-
.then(
|
|
501
|
-
const startedAt = Date.now();
|
|
502
|
-
try {
|
|
503
|
-
const result = await apiFetch('/api/events', {
|
|
504
|
-
method: 'POST',
|
|
505
|
-
body: JSON.stringify({ agent, agent_id: agentId, runtime_host_id, session_id, cwd, runtime_version, event }),
|
|
506
|
-
timeout: 5000,
|
|
507
|
-
});
|
|
508
|
-
if (required && result?.matched === false) {
|
|
509
|
-
throw new Error(`event was not matched (${eventName})`);
|
|
510
|
-
}
|
|
511
|
-
debugLog('events', 'post.ok', {
|
|
512
|
-
agent,
|
|
513
|
-
agentId,
|
|
514
|
-
sessionId: shortId(session_id),
|
|
515
|
-
runtimeVersion: runtime_version ?? null,
|
|
516
|
-
turnId: shortId(turnId),
|
|
517
|
-
eventName,
|
|
518
|
-
seq,
|
|
519
|
-
deltaChars,
|
|
520
|
-
durationMs: Date.now() - startedAt,
|
|
521
|
-
});
|
|
522
|
-
return result;
|
|
523
|
-
} catch (err) {
|
|
524
|
-
debugError('events', 'post.failed', {
|
|
525
|
-
agent,
|
|
526
|
-
agentId,
|
|
527
|
-
sessionId: shortId(session_id),
|
|
528
|
-
runtimeVersion: runtime_version ?? null,
|
|
529
|
-
turnId: shortId(turnId),
|
|
530
|
-
eventName,
|
|
531
|
-
seq,
|
|
532
|
-
deltaChars,
|
|
533
|
-
durationMs: Date.now() - startedAt,
|
|
534
|
-
error: err?.message || 'unknown error',
|
|
535
|
-
});
|
|
536
|
-
if (required) {
|
|
537
|
-
throw err;
|
|
538
|
-
}
|
|
539
|
-
// Best-effort by default. Callers that need a hard failure set
|
|
540
|
-
// `required: true` (used for the final reply path).
|
|
541
|
-
return null;
|
|
542
|
-
}
|
|
543
|
-
});
|
|
843
|
+
.then(postOnce);
|
|
544
844
|
|
|
545
845
|
agentEventQueues.set(queueKey, queued);
|
|
546
846
|
queued.finally(() => {
|
|
@@ -6,7 +6,20 @@
|
|
|
6
6
|
* using the connector-specific env name.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { AF_CONFIG_PATH, persistConfig, TICLAWK_CONNECTOR_API_KEY } from '../../core/config.mjs';
|
|
9
|
+
import { AF_CONFIG_PATH, loadPersistentConfig, persistConfig, TICLAWK_CONNECTOR_API_KEY } from '../../core/config.mjs';
|
|
10
|
+
|
|
11
|
+
export const TICLAWK_CREDENTIAL_NAMES = 'TICLAWK_CREDENTIAL_NAMES';
|
|
12
|
+
|
|
13
|
+
function isRuntimeCredentialName(value) {
|
|
14
|
+
return /^[A-Z][A-Z0-9_]*$/.test(String(value || '').trim());
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function parseCredentialNames(value) {
|
|
18
|
+
return String(value || '')
|
|
19
|
+
.split(',')
|
|
20
|
+
.map((name) => name.trim())
|
|
21
|
+
.filter(isRuntimeCredentialName);
|
|
22
|
+
}
|
|
10
23
|
|
|
11
24
|
export function persistApiCredential(apiKey) {
|
|
12
25
|
if (!apiKey || !apiKey.startsWith('tk_')) return;
|
|
@@ -19,3 +32,30 @@ export function persistApiCredential(apiKey) {
|
|
|
19
32
|
delete process.env.TICLAWK_SETUP_CODE;
|
|
20
33
|
console.log(`[connect] saved ${TICLAWK_CONNECTOR_API_KEY} to ${AF_CONFIG_PATH}`);
|
|
21
34
|
}
|
|
35
|
+
|
|
36
|
+
export function persistRuntimeCredentials(credentials = []) {
|
|
37
|
+
const current = loadPersistentConfig();
|
|
38
|
+
const previousNames = new Set(parseCredentialNames(current[TICLAWK_CREDENTIAL_NAMES]));
|
|
39
|
+
const nextNames = [];
|
|
40
|
+
const updates = {};
|
|
41
|
+
|
|
42
|
+
for (const credential of Array.isArray(credentials) ? credentials : []) {
|
|
43
|
+
const name = String(credential?.name || '').trim();
|
|
44
|
+
const value = typeof credential?.value === 'string' ? credential.value : '';
|
|
45
|
+
if (!isRuntimeCredentialName(name) || !value) continue;
|
|
46
|
+
updates[name] = value;
|
|
47
|
+
nextNames.push(name);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const nextNameSet = new Set(nextNames);
|
|
51
|
+
for (const name of previousNames) {
|
|
52
|
+
if (!nextNameSet.has(name)) updates[name] = '';
|
|
53
|
+
}
|
|
54
|
+
updates[TICLAWK_CREDENTIAL_NAMES] = nextNames.join(',');
|
|
55
|
+
|
|
56
|
+
persistConfig(updates);
|
|
57
|
+
return {
|
|
58
|
+
saved: nextNames.length,
|
|
59
|
+
removed: [...previousNames].filter((name) => !nextNameSet.has(name)).length,
|
|
60
|
+
};
|
|
61
|
+
}
|