ticlawk 0.1.16-dev.8 → 0.1.16

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 (41) hide show
  1. package/README.md +15 -3
  2. package/bin/ticlawk.mjs +208 -26
  3. package/package.json +1 -1
  4. package/src/adapters/ticlawk/api.mjs +283 -48
  5. package/src/adapters/ticlawk/credentials.mjs +41 -1
  6. package/src/adapters/ticlawk/index.mjs +126 -121
  7. package/src/adapters/ticlawk/wake-client.mjs +1 -1
  8. package/src/cli/agent-commands.mjs +560 -36
  9. package/src/core/agent-cli-handlers.mjs +435 -18
  10. package/src/core/agent-home.mjs +81 -1
  11. package/src/core/argv.mjs +11 -1
  12. package/src/core/events/worker-events.mjs +32 -36
  13. package/src/core/http.mjs +119 -0
  14. package/src/core/runtime-contract.mjs +0 -1
  15. package/src/core/runtime-env.mjs +7 -0
  16. package/src/core/runtime-support.mjs +108 -77
  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 +55 -0
  23. package/src/runtimes/_shared/handbook/DM_SCOPE.md +13 -0
  24. package/src/runtimes/_shared/handbook/GOAL_AUTHORITY.md +46 -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 +134 -275
  31. package/src/runtimes/_shared/wake-prompt.mjs +261 -0
  32. package/src/runtimes/claude-code/index.mjs +19 -46
  33. package/src/runtimes/claude-code/session.mjs +2 -7
  34. package/src/runtimes/codex/index.mjs +115 -63
  35. package/src/runtimes/codex/session.mjs +2 -12
  36. package/src/runtimes/openclaw/index.mjs +11 -24
  37. package/src/runtimes/opencode/index.mjs +38 -60
  38. package/src/runtimes/opencode/session.mjs +12 -12
  39. package/src/runtimes/pi/index.mjs +38 -60
  40. package/src/runtimes/pi/session.mjs +9 -6
  41. package/ticlawk.mjs +0 -30
@@ -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 {
@@ -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
  }
@@ -484,63 +488,294 @@ export async function removeAgentGroupMember({
484
488
  );
485
489
  }
486
490
 
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 ──
526
+
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
+ }
533
+
534
+ export async function createAgentSlot({
535
+ actingAgentId, name, runtime, description, displayName, model,
536
+ }) {
537
+ return apiFetch('/api/agent/agents', {
538
+ method: 'POST',
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
+ }),
547
+ });
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
+ );
707
+ }
708
+
487
709
  // ── Channel event pipe ──
488
710
 
489
711
  export async function postEvent({ agent, agent_id, runtime_host_id, session_id, cwd, runtime_version, event, required = false }) {
490
712
  const agentId = agent_id || null;
491
713
  const queueKey = agentId || `${agent}:${session_id || ''}`;
492
- const previous = agentEventQueues.get(queueKey) || Promise.resolve(null);
493
714
  const eventName = event?.worker_event_name || event?.hook_event_name || event?.event_name || 'unknown';
494
715
  const turnId = event?.turn_id || event?.reply_to_message_id || null;
495
716
  const seq = event?.event_seq ?? null;
496
717
  const deltaChars = typeof event?.delta === 'string' ? event.delta.length : null;
497
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);
498
776
  const queued = previous
499
777
  .catch(() => null)
500
- .then(async () => {
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
- });
778
+ .then(postOnce);
544
779
 
545
780
  agentEventQueues.set(queueKey, queued);
546
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
+ }