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.
Files changed (42) hide show
  1. package/README.md +14 -2
  2. package/bin/ticlawk.mjs +207 -25
  3. package/package.json +1 -1
  4. package/src/adapters/ticlawk/api.mjs +293 -70
  5. package/src/adapters/ticlawk/credentials.mjs +41 -1
  6. package/src/adapters/ticlawk/index.mjs +199 -199
  7. package/src/adapters/ticlawk/wake-client.mjs +1 -1
  8. package/src/cli/agent-commands.mjs +607 -37
  9. package/src/core/agent-cli-handlers.mjs +449 -20
  10. package/src/core/agent-home.mjs +86 -10
  11. package/src/core/argv.mjs +11 -1
  12. package/src/core/events/worker-events.mjs +32 -36
  13. package/src/core/http.mjs +126 -0
  14. package/src/core/runtime-env.mjs +7 -0
  15. package/src/core/runtime-support.mjs +108 -107
  16. package/src/migrate/write-initial-memory.mjs +5 -5
  17. package/src/runtimes/_shared/agent-handbook.mjs +45 -0
  18. package/src/runtimes/_shared/brand.mjs +2 -0
  19. package/src/runtimes/_shared/goal-task-protocol.mjs +50 -0
  20. package/src/runtimes/_shared/handbook/BASICS.md +27 -0
  21. package/src/runtimes/_shared/handbook/COLLABORATION.md +37 -0
  22. package/src/runtimes/_shared/handbook/COMMUNICATION.md +50 -0
  23. package/src/runtimes/_shared/handbook/DM_SCOPE.md +13 -0
  24. package/src/runtimes/_shared/handbook/GOAL_AUTHORITY.md +43 -0
  25. package/src/runtimes/_shared/handbook/GOAL_TASK_CORE.md +43 -0
  26. package/src/runtimes/_shared/handbook/GROUP_ADMIN_SCOPE.md +21 -0
  27. package/src/runtimes/_shared/handbook/GROUP_MEMBER_SCOPE.md +15 -0
  28. package/src/runtimes/_shared/handbook/SURFACES.md +41 -0
  29. package/src/runtimes/_shared/handbook/TASK_WORKER.md +14 -0
  30. package/src/runtimes/_shared/standing-prompt.mjs +111 -262
  31. package/src/runtimes/_shared/wake-prompt.mjs +261 -0
  32. package/src/runtimes/claude-code/index.mjs +34 -127
  33. package/src/runtimes/claude-code/session.mjs +2 -7
  34. package/src/runtimes/codex/index.mjs +117 -54
  35. package/src/runtimes/codex/session.mjs +2 -12
  36. package/src/runtimes/openclaw/index.mjs +16 -26
  37. package/src/runtimes/opencode/index.mjs +45 -66
  38. package/src/runtimes/opencode/session.mjs +12 -12
  39. package/src/runtimes/pi/index.mjs +42 -60
  40. package/src/runtimes/pi/session.mjs +9 -6
  41. package/src/adapters/ticlawk/cards.mjs +0 -149
  42. 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
- // Agent event writes must preserve per-agent order while still allowing
34
- // different agents to proceed concurrently.
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
- // ── Upload ──
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 uploadAsset(fileName, fileData, contentType, kind = 'chat_media') {
486
- const formData = new FormData();
487
- formData.append('file', new Blob([fileData], { type: contentType }), fileName);
488
- formData.append('kind', kind);
489
- if (contentType) formData.append('content_type', contentType);
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
- const { data } = await apiFetch('/api/assets/upload', {
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: formData,
494
- timeout: 30000,
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
- return data;
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(async () => {
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
+ }