ticlawk 0.1.16-dev.3 → 0.1.16-dev.31
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 +14 -2
- package/bin/ticlawk.mjs +207 -25
- package/package.json +1 -1
- package/src/adapters/ticlawk/api.mjs +293 -70
- package/src/adapters/ticlawk/credentials.mjs +41 -1
- package/src/adapters/ticlawk/index.mjs +199 -199
- package/src/adapters/ticlawk/wake-client.mjs +1 -1
- package/src/cli/agent-commands.mjs +607 -37
- package/src/core/agent-cli-handlers.mjs +449 -20
- package/src/core/agent-home.mjs +86 -10
- package/src/core/argv.mjs +11 -1
- package/src/core/events/worker-events.mjs +32 -36
- package/src/core/http.mjs +126 -0
- package/src/core/runtime-env.mjs +7 -0
- package/src/core/runtime-support.mjs +108 -107
- package/src/migrate/write-initial-memory.mjs +5 -5
- 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 +50 -0
- package/src/runtimes/_shared/handbook/DM_SCOPE.md +13 -0
- package/src/runtimes/_shared/handbook/GOAL_AUTHORITY.md +43 -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 +111 -262
- package/src/runtimes/_shared/wake-prompt.mjs +261 -0
- package/src/runtimes/claude-code/index.mjs +34 -127
- package/src/runtimes/claude-code/session.mjs +2 -7
- package/src/runtimes/codex/index.mjs +117 -54
- package/src/runtimes/codex/session.mjs +2 -12
- package/src/runtimes/openclaw/index.mjs +16 -26
- package/src/runtimes/opencode/index.mjs +45 -66
- package/src/runtimes/opencode/session.mjs +12 -12
- package/src/runtimes/pi/index.mjs +42 -60
- package/src/runtimes/pi/session.mjs +9 -6
- package/src/adapters/ticlawk/cards.mjs +0 -149
- package/src/core/media/outbound.mjs +0 -163
|
@@ -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 {
|
|
@@ -149,18 +150,6 @@ export async function updateChannel(id, updates) {
|
|
|
149
150
|
return updateAgent(id, updates);
|
|
150
151
|
}
|
|
151
152
|
|
|
152
|
-
export async function postRuntimeResult(body) {
|
|
153
|
-
// Activity-only: backend records the agent_event for UI trajectory
|
|
154
|
-
// display but does NOT project it into a chat message. The agent CLI's
|
|
155
|
-
// `ticlawk message send` is the only path that produces chat rows.
|
|
156
|
-
const result = await apiFetch('/api/runtime-results', {
|
|
157
|
-
method: 'POST',
|
|
158
|
-
body: JSON.stringify(body),
|
|
159
|
-
timeout: 30000,
|
|
160
|
-
});
|
|
161
|
-
return result;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
153
|
// ── Deliveries (replaces legacy message_jobs poll/ack) ──
|
|
165
154
|
|
|
166
155
|
export async function claimPendingDeliveries(hostId, limit = 5, excludedAgentIds = []) {
|
|
@@ -199,6 +188,8 @@ export async function sendAgentMessage({
|
|
|
199
188
|
replyToMessageId,
|
|
200
189
|
runtimeHostId,
|
|
201
190
|
visibility,
|
|
191
|
+
mediaAssetIds,
|
|
192
|
+
metadata,
|
|
202
193
|
}) {
|
|
203
194
|
const { data } = await apiFetch('/api/agent/messages/send', {
|
|
204
195
|
method: 'POST',
|
|
@@ -210,6 +201,8 @@ export async function sendAgentMessage({
|
|
|
210
201
|
reply_to_message_id: replyToMessageId ?? null,
|
|
211
202
|
runtime_host_id: runtimeHostId ?? null,
|
|
212
203
|
visibility: visibility || null,
|
|
204
|
+
media_asset_ids: Array.isArray(mediaAssetIds) && mediaAssetIds.length > 0 ? mediaAssetIds : undefined,
|
|
205
|
+
metadata: metadata ?? undefined,
|
|
213
206
|
}),
|
|
214
207
|
});
|
|
215
208
|
return data || null;
|
|
@@ -232,7 +225,7 @@ export async function readAgentMessages({
|
|
|
232
225
|
return data || [];
|
|
233
226
|
}
|
|
234
227
|
|
|
235
|
-
export async function createAgentTask({ actingAgentId, conversationId, text, title }) {
|
|
228
|
+
export async function createAgentTask({ actingAgentId, conversationId, text, title, assignAgentId }) {
|
|
236
229
|
return apiFetch('/api/agent/tasks/create', {
|
|
237
230
|
method: 'POST',
|
|
238
231
|
body: JSON.stringify({
|
|
@@ -240,6 +233,7 @@ export async function createAgentTask({ actingAgentId, conversationId, text, tit
|
|
|
240
233
|
conversation_id: conversationId,
|
|
241
234
|
text,
|
|
242
235
|
title: title ?? null,
|
|
236
|
+
assign_agent_id: assignAgentId ?? null,
|
|
243
237
|
}),
|
|
244
238
|
});
|
|
245
239
|
}
|
|
@@ -436,6 +430,20 @@ export async function uploadAgentAttachment({
|
|
|
436
430
|
});
|
|
437
431
|
}
|
|
438
432
|
|
|
433
|
+
export async function uploadAgentAvatar({
|
|
434
|
+
actingAgentId, filename, contentType, dataBase64,
|
|
435
|
+
}) {
|
|
436
|
+
return apiFetch('/api/agent/profile/avatar', {
|
|
437
|
+
method: 'POST',
|
|
438
|
+
body: JSON.stringify({
|
|
439
|
+
acting_as_agent_id: actingAgentId,
|
|
440
|
+
filename,
|
|
441
|
+
content_type: contentType,
|
|
442
|
+
data_base64: dataBase64,
|
|
443
|
+
}),
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
439
447
|
export async function viewAgentAttachment({ actingAgentId, assetId }) {
|
|
440
448
|
const params = new URLSearchParams();
|
|
441
449
|
params.set('acting_as_agent_id', actingAgentId);
|
|
@@ -480,20 +488,222 @@ export async function removeAgentGroupMember({
|
|
|
480
488
|
);
|
|
481
489
|
}
|
|
482
490
|
|
|
483
|
-
// ──
|
|
491
|
+
// ── Workstreams (managed groups) ──
|
|
492
|
+
|
|
493
|
+
export async function createWorkstream({
|
|
494
|
+
actingAgentId, name, description, charter, memberAgentIds,
|
|
495
|
+
}) {
|
|
496
|
+
return apiFetch('/api/agent/workstreams', {
|
|
497
|
+
method: 'POST',
|
|
498
|
+
body: JSON.stringify({
|
|
499
|
+
acting_as_agent_id: actingAgentId,
|
|
500
|
+
name,
|
|
501
|
+
description: description ?? null,
|
|
502
|
+
charter: charter ?? null,
|
|
503
|
+
member_agent_ids: memberAgentIds || [],
|
|
504
|
+
}),
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
export async function deleteWorkstream({ actingAgentId, conversationId }) {
|
|
509
|
+
return apiFetch(
|
|
510
|
+
`/api/agent/workstreams/${encodeURIComponent(conversationId)}`,
|
|
511
|
+
{
|
|
512
|
+
method: 'DELETE',
|
|
513
|
+
body: JSON.stringify({ acting_as_agent_id: actingAgentId }),
|
|
514
|
+
},
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export async function listWorkstreams({ actingAgentId }) {
|
|
519
|
+
const params = new URLSearchParams();
|
|
520
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
521
|
+
const { data } = await apiFetch(`/api/agent/workstreams?${params}`);
|
|
522
|
+
return data || [];
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// ── Agents ──
|
|
484
526
|
|
|
485
|
-
export async function
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
527
|
+
export async function listAgentSlots({ actingAgentId }) {
|
|
528
|
+
const params = new URLSearchParams();
|
|
529
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
530
|
+
const { data } = await apiFetch(`/api/agent/agents?${params}`);
|
|
531
|
+
return data || [];
|
|
532
|
+
}
|
|
490
533
|
|
|
491
|
-
|
|
534
|
+
export async function createAgentSlot({
|
|
535
|
+
actingAgentId, name, runtime, description, displayName, model,
|
|
536
|
+
}) {
|
|
537
|
+
return apiFetch('/api/agent/agents', {
|
|
492
538
|
method: 'POST',
|
|
493
|
-
body:
|
|
494
|
-
|
|
539
|
+
body: JSON.stringify({
|
|
540
|
+
acting_as_agent_id: actingAgentId,
|
|
541
|
+
name,
|
|
542
|
+
runtime,
|
|
543
|
+
description: description ?? null,
|
|
544
|
+
display_name: displayName ?? null,
|
|
545
|
+
model: model ?? null,
|
|
546
|
+
}),
|
|
495
547
|
});
|
|
496
|
-
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
export async function archiveAgentSlot({ actingAgentId, agentId }) {
|
|
551
|
+
return apiFetch(
|
|
552
|
+
`/api/agent/agents/${encodeURIComponent(agentId)}`,
|
|
553
|
+
{
|
|
554
|
+
method: 'DELETE',
|
|
555
|
+
body: JSON.stringify({ acting_as_agent_id: actingAgentId }),
|
|
556
|
+
},
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ── Services ──
|
|
561
|
+
|
|
562
|
+
export async function createService({
|
|
563
|
+
actingAgentId, name, description, contractSchema, endpointConfig,
|
|
564
|
+
}) {
|
|
565
|
+
return apiFetch('/api/agent/services', {
|
|
566
|
+
method: 'POST',
|
|
567
|
+
body: JSON.stringify({
|
|
568
|
+
acting_as_agent_id: actingAgentId,
|
|
569
|
+
name,
|
|
570
|
+
description: description ?? null,
|
|
571
|
+
contract_schema: contractSchema ?? null,
|
|
572
|
+
endpoint_config: endpointConfig,
|
|
573
|
+
}),
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
export async function updateService({ actingAgentId, serviceId, ...patch }) {
|
|
578
|
+
return apiFetch(
|
|
579
|
+
`/api/agent/services/${encodeURIComponent(serviceId)}`,
|
|
580
|
+
{
|
|
581
|
+
method: 'PATCH',
|
|
582
|
+
body: JSON.stringify({ acting_as_agent_id: actingAgentId, ...patch }),
|
|
583
|
+
},
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
export async function deleteService({ actingAgentId, serviceId }) {
|
|
588
|
+
return apiFetch(
|
|
589
|
+
`/api/agent/services/${encodeURIComponent(serviceId)}`,
|
|
590
|
+
{
|
|
591
|
+
method: 'DELETE',
|
|
592
|
+
body: JSON.stringify({ acting_as_agent_id: actingAgentId }),
|
|
593
|
+
},
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export async function listServices({ actingAgentId }) {
|
|
598
|
+
const params = new URLSearchParams();
|
|
599
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
600
|
+
const { data } = await apiFetch(`/api/agent/services?${params}`);
|
|
601
|
+
return data || [];
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export async function getServiceInfo({ actingAgentId, name }) {
|
|
605
|
+
const params = new URLSearchParams();
|
|
606
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
607
|
+
return apiFetch(
|
|
608
|
+
`/api/agent/services/${encodeURIComponent(name)}/info?${params}`,
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
export async function callService({ actingAgentId, name, input }) {
|
|
613
|
+
return apiFetch(
|
|
614
|
+
`/api/agent/services/${encodeURIComponent(name)}/call`,
|
|
615
|
+
{
|
|
616
|
+
method: 'POST',
|
|
617
|
+
body: JSON.stringify({ acting_as_agent_id: actingAgentId, input }),
|
|
618
|
+
},
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// ── Briefings ──
|
|
623
|
+
|
|
624
|
+
export async function getBriefing({actingAgentId, briefingId}) {
|
|
625
|
+
const params = new URLSearchParams();
|
|
626
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
627
|
+
return apiFetch(`/api/agent/briefings/${encodeURIComponent(briefingId)}?${params}`);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
export async function publishBriefing({actingAgentId, bodyText, attachmentAssetId, currentConversationId, responseMode}) {
|
|
631
|
+
const body = { acting_as_agent_id: actingAgentId };
|
|
632
|
+
if (bodyText != null) body.body_text = bodyText;
|
|
633
|
+
if (attachmentAssetId != null) body.attachment_asset_id = attachmentAssetId;
|
|
634
|
+
if (currentConversationId != null) body.current_conversation_id = currentConversationId;
|
|
635
|
+
if (responseMode != null) body.response_mode = responseMode;
|
|
636
|
+
return apiFetch('/api/agent/briefings', {
|
|
637
|
+
method: 'POST',
|
|
638
|
+
body: JSON.stringify(body),
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// ── Credentials (slot creation + daemon sync) ──
|
|
643
|
+
|
|
644
|
+
export async function fetchCredentials() {
|
|
645
|
+
return apiFetch('/api/agent/credentials', { method: 'GET' });
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
export async function requestCredential({
|
|
649
|
+
actingAgentId, name, description, workstreamId,
|
|
650
|
+
}) {
|
|
651
|
+
return apiFetch('/api/agent/credentials', {
|
|
652
|
+
method: 'POST',
|
|
653
|
+
body: JSON.stringify({
|
|
654
|
+
acting_as_agent_id: actingAgentId,
|
|
655
|
+
name,
|
|
656
|
+
description: description ?? null,
|
|
657
|
+
workstream_id: workstreamId ?? null,
|
|
658
|
+
}),
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// ── Workstream dashboard ──
|
|
663
|
+
|
|
664
|
+
export async function setWorkstreamDashboard({
|
|
665
|
+
actingAgentId, conversationId, dataJson, htmlTemplate,
|
|
666
|
+
}) {
|
|
667
|
+
const body = { acting_as_agent_id: actingAgentId };
|
|
668
|
+
// Distinguish "omit" from "set to null" — only include keys the caller
|
|
669
|
+
// explicitly passed (including null clears the field).
|
|
670
|
+
if (dataJson !== undefined) body.data_json = dataJson;
|
|
671
|
+
if (htmlTemplate !== undefined) body.html_template = htmlTemplate;
|
|
672
|
+
return apiFetch(
|
|
673
|
+
`/api/agent/workstreams/${encodeURIComponent(conversationId)}/dashboard`,
|
|
674
|
+
{ method: 'POST', body: JSON.stringify(body) },
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
export async function getWorkstreamDashboard({ actingAgentId, conversationId }) {
|
|
679
|
+
const params = new URLSearchParams();
|
|
680
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
681
|
+
return apiFetch(
|
|
682
|
+
`/api/agent/workstreams/${encodeURIComponent(conversationId)}/dashboard?${params}`,
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// ── Workstream charter ──
|
|
687
|
+
|
|
688
|
+
export async function getWorkstreamCharter({ actingAgentId, conversationId }) {
|
|
689
|
+
const params = new URLSearchParams();
|
|
690
|
+
params.set('acting_as_agent_id', actingAgentId);
|
|
691
|
+
return apiFetch(
|
|
692
|
+
`/api/agent/workstreams/${encodeURIComponent(conversationId)}/charter?${params}`,
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
export async function setWorkstreamCharter({ actingAgentId, conversationId, charter }) {
|
|
697
|
+
return apiFetch(
|
|
698
|
+
`/api/agent/workstreams/${encodeURIComponent(conversationId)}/charter`,
|
|
699
|
+
{
|
|
700
|
+
method: 'POST',
|
|
701
|
+
body: JSON.stringify({
|
|
702
|
+
acting_as_agent_id: actingAgentId,
|
|
703
|
+
charter: charter ?? null,
|
|
704
|
+
}),
|
|
705
|
+
},
|
|
706
|
+
);
|
|
497
707
|
}
|
|
498
708
|
|
|
499
709
|
// ── Channel event pipe ──
|
|
@@ -501,58 +711,71 @@ export async function uploadAsset(fileName, fileData, contentType, kind = 'chat_
|
|
|
501
711
|
export async function postEvent({ agent, agent_id, runtime_host_id, session_id, cwd, runtime_version, event, required = false }) {
|
|
502
712
|
const agentId = agent_id || null;
|
|
503
713
|
const queueKey = agentId || `${agent}:${session_id || ''}`;
|
|
504
|
-
const previous = agentEventQueues.get(queueKey) || Promise.resolve(null);
|
|
505
714
|
const eventName = event?.worker_event_name || event?.hook_event_name || event?.event_name || 'unknown';
|
|
506
715
|
const turnId = event?.turn_id || event?.reply_to_message_id || null;
|
|
507
716
|
const seq = event?.event_seq ?? null;
|
|
508
717
|
const deltaChars = typeof event?.delta === 'string' ? event.delta.length : null;
|
|
509
718
|
|
|
719
|
+
if (eventName === 'worker.message.delta') {
|
|
720
|
+
debugLog('events', 'delta.drop', {
|
|
721
|
+
agent,
|
|
722
|
+
agentId,
|
|
723
|
+
sessionId: shortId(session_id),
|
|
724
|
+
turnId: shortId(turnId),
|
|
725
|
+
deltaChars,
|
|
726
|
+
});
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const postOnce = async () => {
|
|
731
|
+
const startedAt = Date.now();
|
|
732
|
+
try {
|
|
733
|
+
const result = await apiFetch('/api/events', {
|
|
734
|
+
method: 'POST',
|
|
735
|
+
body: JSON.stringify({ agent, agent_id: agentId, runtime_host_id, session_id, cwd, runtime_version, event }),
|
|
736
|
+
timeout: 5000,
|
|
737
|
+
});
|
|
738
|
+
if (required && result?.matched === false) {
|
|
739
|
+
throw new Error(`event was not matched (${eventName})`);
|
|
740
|
+
}
|
|
741
|
+
debugLog('events', 'post.ok', {
|
|
742
|
+
agent,
|
|
743
|
+
agentId,
|
|
744
|
+
sessionId: shortId(session_id),
|
|
745
|
+
runtimeVersion: runtime_version ?? null,
|
|
746
|
+
turnId: shortId(turnId),
|
|
747
|
+
eventName,
|
|
748
|
+
seq,
|
|
749
|
+
durationMs: Date.now() - startedAt,
|
|
750
|
+
});
|
|
751
|
+
return result;
|
|
752
|
+
} catch (err) {
|
|
753
|
+
debugError('events', 'post.failed', {
|
|
754
|
+
agent,
|
|
755
|
+
agentId,
|
|
756
|
+
sessionId: shortId(session_id),
|
|
757
|
+
runtimeVersion: runtime_version ?? null,
|
|
758
|
+
turnId: shortId(turnId),
|
|
759
|
+
eventName,
|
|
760
|
+
seq,
|
|
761
|
+
durationMs: Date.now() - startedAt,
|
|
762
|
+
error: err?.message || 'unknown error',
|
|
763
|
+
});
|
|
764
|
+
if (required) {
|
|
765
|
+
throw err;
|
|
766
|
+
}
|
|
767
|
+
return null;
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
if (eventName === 'worker.turn.complete' || eventName === 'worker.turn.error') {
|
|
772
|
+
return postOnce();
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const previous = agentEventQueues.get(queueKey) || Promise.resolve(null);
|
|
510
776
|
const queued = previous
|
|
511
777
|
.catch(() => null)
|
|
512
|
-
.then(
|
|
513
|
-
const startedAt = Date.now();
|
|
514
|
-
try {
|
|
515
|
-
const result = await apiFetch('/api/events', {
|
|
516
|
-
method: 'POST',
|
|
517
|
-
body: JSON.stringify({ agent, agent_id: agentId, runtime_host_id, session_id, cwd, runtime_version, event }),
|
|
518
|
-
timeout: 5000,
|
|
519
|
-
});
|
|
520
|
-
if (required && result?.matched === false) {
|
|
521
|
-
throw new Error(`event was not matched (${eventName})`);
|
|
522
|
-
}
|
|
523
|
-
debugLog('events', 'post.ok', {
|
|
524
|
-
agent,
|
|
525
|
-
agentId,
|
|
526
|
-
sessionId: shortId(session_id),
|
|
527
|
-
runtimeVersion: runtime_version ?? null,
|
|
528
|
-
turnId: shortId(turnId),
|
|
529
|
-
eventName,
|
|
530
|
-
seq,
|
|
531
|
-
deltaChars,
|
|
532
|
-
durationMs: Date.now() - startedAt,
|
|
533
|
-
});
|
|
534
|
-
return result;
|
|
535
|
-
} catch (err) {
|
|
536
|
-
debugError('events', 'post.failed', {
|
|
537
|
-
agent,
|
|
538
|
-
agentId,
|
|
539
|
-
sessionId: shortId(session_id),
|
|
540
|
-
runtimeVersion: runtime_version ?? null,
|
|
541
|
-
turnId: shortId(turnId),
|
|
542
|
-
eventName,
|
|
543
|
-
seq,
|
|
544
|
-
deltaChars,
|
|
545
|
-
durationMs: Date.now() - startedAt,
|
|
546
|
-
error: err?.message || 'unknown error',
|
|
547
|
-
});
|
|
548
|
-
if (required) {
|
|
549
|
-
throw err;
|
|
550
|
-
}
|
|
551
|
-
// Best-effort by default. Callers that need a hard failure set
|
|
552
|
-
// `required: true` (used for the final reply path).
|
|
553
|
-
return null;
|
|
554
|
-
}
|
|
555
|
-
});
|
|
778
|
+
.then(postOnce);
|
|
556
779
|
|
|
557
780
|
agentEventQueues.set(queueKey, queued);
|
|
558
781
|
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
|
+
}
|