ticlawk 0.1.16-dev.3 → 0.1.16-dev.30

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 (39) 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 +232 -23
  5. package/src/adapters/ticlawk/credentials.mjs +41 -1
  6. package/src/adapters/ticlawk/index.mjs +196 -195
  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/http.mjs +126 -0
  13. package/src/core/runtime-env.mjs +7 -0
  14. package/src/core/runtime-support.mjs +101 -30
  15. package/src/migrate/write-initial-memory.mjs +5 -5
  16. package/src/runtimes/_shared/agent-handbook.mjs +45 -0
  17. package/src/runtimes/_shared/brand.mjs +2 -0
  18. package/src/runtimes/_shared/goal-task-protocol.mjs +50 -0
  19. package/src/runtimes/_shared/handbook/BASICS.md +27 -0
  20. package/src/runtimes/_shared/handbook/COLLABORATION.md +37 -0
  21. package/src/runtimes/_shared/handbook/COMMUNICATION.md +48 -0
  22. package/src/runtimes/_shared/handbook/DM_SCOPE.md +13 -0
  23. package/src/runtimes/_shared/handbook/GOAL_AUTHORITY.md +43 -0
  24. package/src/runtimes/_shared/handbook/GOAL_TASK_CORE.md +43 -0
  25. package/src/runtimes/_shared/handbook/GROUP_ADMIN_SCOPE.md +21 -0
  26. package/src/runtimes/_shared/handbook/GROUP_MEMBER_SCOPE.md +15 -0
  27. package/src/runtimes/_shared/handbook/SURFACES.md +41 -0
  28. package/src/runtimes/_shared/handbook/TASK_WORKER.md +14 -0
  29. package/src/runtimes/_shared/standing-prompt.mjs +111 -264
  30. package/src/runtimes/_shared/wake-prompt.mjs +261 -0
  31. package/src/runtimes/claude-code/index.mjs +30 -108
  32. package/src/runtimes/codex/index.mjs +114 -23
  33. package/src/runtimes/openclaw/index.mjs +16 -26
  34. package/src/runtimes/opencode/index.mjs +42 -36
  35. package/src/runtimes/opencode/session.mjs +5 -4
  36. package/src/runtimes/pi/index.mjs +39 -31
  37. package/src/runtimes/pi/session.mjs +5 -2
  38. package/src/adapters/ticlawk/cards.mjs +0 -149
  39. package/src/core/media/outbound.mjs +0 -163
package/README.md CHANGED
@@ -216,8 +216,12 @@ Agent CLI (run inside an agent runtime; requires TICLAWK_RUNTIME_AGENT_ID):
216
216
  ticlawk message read --target <t> [opts]
217
217
  ticlawk task claim --message-id <id>
218
218
  ticlawk task update --task-id <id> --status <s>
219
- ticlawk task list [--target <t>]
219
+ ticlawk task list [--target <t>] # group admins see the full task board
220
+ ticlawk charter get --target <t>
220
221
  ticlawk group members --target <t>
222
+ ticlawk group list
223
+ ticlawk agent list
224
+ ticlawk dashboard get --conversation-id <id>
221
225
  ticlawk server info [--refresh]
222
226
 
223
227
  Commands:
@@ -226,7 +230,15 @@ Commands:
226
230
  profile list or switch saved local identities
227
231
  message send/read chat messages (agent CLI surface)
228
232
  task claim/update/list tasks (agent CLI surface)
229
- group list members of a group conversation (agent CLI surface)
233
+ charter get/set conversation goal and role spec (agent CLI surface)
234
+ group create/list/delete groups, manage charters/members (agent CLI surface)
235
+ dashboard set/get conversation dashboards (agent CLI surface)
236
+ briefing publish/get owner briefings (agent CLI surface)
237
+ agent list/create/delete owned agent slots (agent CLI surface)
238
+ service publish/list/call shared services (agent CLI surface)
239
+ credential request owner-filled credentials (agent CLI surface)
240
+ reminder schedule/list/snooze/cancel reminders (agent CLI surface)
241
+ attachment view private chat assets (agent CLI surface)
230
242
  server server-info introspection (agent CLI surface)
231
243
  install-daemon install or refresh the background daemon
232
244
  update update the npm package and refresh the daemon
package/bin/ticlawk.mjs CHANGED
@@ -30,9 +30,27 @@ import { getInstallDaemonHelp, runInstallDaemon } from '../src/core/daemon-insta
30
30
  import { runSetupReadiness } from '../src/core/setup-readiness.mjs';
31
31
  import {
32
32
  AGENT_COMMAND_HELP,
33
- runAttachmentUploadCommand,
34
33
  runAttachmentViewCommand,
35
34
  runGroupCreateCommand,
35
+ runWorkstreamCharterGetCommand,
36
+ runWorkstreamCharterSetCommand,
37
+ runWorkstreamCreateCommand,
38
+ runWorkstreamDeleteCommand,
39
+ runWorkstreamListCommand,
40
+ runAgentListCommand,
41
+ runAgentCreateCommand,
42
+ runAgentDeleteCommand,
43
+ runDashboardSetCommand,
44
+ runDashboardGetCommand,
45
+ runCredentialRequestCommand,
46
+ runBriefingPublishCommand,
47
+ runBriefingGetCommand,
48
+ runServiceCreateCommand,
49
+ runServiceUpdateCommand,
50
+ runServiceDeleteCommand,
51
+ runServiceListCommand,
52
+ runServiceInfoCommand,
53
+ runServiceCallCommand,
36
54
  runGroupMembersAddCommand,
37
55
  runGroupMembersCommand,
38
56
  runGroupMembersRemoveCommand,
@@ -97,8 +115,12 @@ Agent CLI (run inside an agent runtime; requires TICLAWK_RUNTIME_AGENT_ID):
97
115
  ticlawk message read --target <t> [opts]
98
116
  ticlawk task claim --message-id <id>
99
117
  ticlawk task update --task-id <id> --status <s>
100
- ticlawk task list [--target <t>]
118
+ ticlawk task list [--target <t>] # group admins see the full task board
119
+ ticlawk charter get --target <t>
101
120
  ticlawk group members --target <t>
121
+ ticlawk group list
122
+ ticlawk agent list
123
+ ticlawk dashboard get --conversation-id <id>
102
124
  ticlawk server info [--refresh]
103
125
 
104
126
  Commands:
@@ -107,7 +129,15 @@ Commands:
107
129
  profile list or switch saved local identities
108
130
  message send/read chat messages (agent CLI surface)
109
131
  task claim/update/list tasks (agent CLI surface)
110
- group list members of a group conversation (agent CLI surface)
132
+ charter get/set conversation goal and role spec (agent CLI surface)
133
+ group create/list/delete groups, manage charters/members (agent CLI surface)
134
+ dashboard set/get conversation dashboards (agent CLI surface)
135
+ briefing publish/get owner briefings (agent CLI surface)
136
+ agent list/create/delete owned agent slots (agent CLI surface)
137
+ service publish/list/call shared services (agent CLI surface)
138
+ credential request owner-filled credentials (agent CLI surface)
139
+ reminder schedule/list/snooze/cancel reminders (agent CLI surface)
140
+ attachment view private chat assets (agent CLI surface)
111
141
  server server-info introspection (agent CLI surface)
112
142
  install-daemon install or refresh the background daemon
113
143
  update update the npm package and refresh the daemon
@@ -179,6 +209,10 @@ function printHelp(helpPath, args = {}) {
179
209
  console.log(getInstallDaemonHelp());
180
210
  return;
181
211
  }
212
+ if (AGENT_COMMAND_HELP[head]) {
213
+ console.log(AGENT_COMMAND_HELP[head]);
214
+ return;
215
+ }
182
216
  printUsage();
183
217
  }
184
218
 
@@ -358,24 +392,6 @@ async function main() {
358
392
  process.exit(1);
359
393
  }
360
394
 
361
- if (command === 'profile') {
362
- const sub = args._[1];
363
- if (args.help || args.h || !sub) {
364
- console.log(AGENT_COMMAND_HELP.profile);
365
- return;
366
- }
367
- if (sub === 'show') {
368
- process.exitCode = await runProfileShowCommand(args);
369
- return;
370
- }
371
- if (sub === 'update') {
372
- process.exitCode = await runProfileUpdateCommand(args);
373
- return;
374
- }
375
- console.error(`unknown profile subcommand: ${sub}`);
376
- process.exit(1);
377
- }
378
-
379
395
  if (command === 'reminder') {
380
396
  const sub = args._[1];
381
397
  if (args.help || args.h || !sub) {
@@ -416,10 +432,6 @@ async function main() {
416
432
  console.log(AGENT_COMMAND_HELP.attachment);
417
433
  return;
418
434
  }
419
- if (sub === 'upload') {
420
- process.exitCode = await runAttachmentUploadCommand(args);
421
- return;
422
- }
423
435
  if (sub === 'view') {
424
436
  process.exitCode = await runAttachmentViewCommand(args);
425
437
  return;
@@ -458,6 +470,147 @@ async function main() {
458
470
  process.exit(1);
459
471
  }
460
472
 
473
+ if (command === 'workstream') {
474
+ const sub = args._[1];
475
+ if (args.help || args.h || !sub) {
476
+ console.log(AGENT_COMMAND_HELP.workstream);
477
+ return;
478
+ }
479
+ if (sub === 'create') {
480
+ process.exitCode = await runWorkstreamCreateCommand(args);
481
+ return;
482
+ }
483
+ if (sub === 'delete') {
484
+ process.exitCode = await runWorkstreamDeleteCommand(args);
485
+ return;
486
+ }
487
+ if (sub === 'list') {
488
+ process.exitCode = await runWorkstreamListCommand(args);
489
+ return;
490
+ }
491
+ if (sub === 'charter') {
492
+ const op = args._[2];
493
+ if (op === 'get') {
494
+ process.exitCode = await runWorkstreamCharterGetCommand(args);
495
+ return;
496
+ }
497
+ if (op === 'set') {
498
+ process.exitCode = await runWorkstreamCharterSetCommand(args);
499
+ return;
500
+ }
501
+ console.error(`unknown workstream charter op: ${op}`);
502
+ process.exit(1);
503
+ }
504
+ console.error(`unknown workstream subcommand: ${sub}`);
505
+ process.exit(1);
506
+ }
507
+
508
+ if (command === 'agent') {
509
+ const sub = args._[1];
510
+ if (args.help || args.h || !sub) {
511
+ console.log(AGENT_COMMAND_HELP.agent);
512
+ return;
513
+ }
514
+ if (sub === 'list') {
515
+ process.exitCode = await runAgentListCommand(args);
516
+ return;
517
+ }
518
+ if (sub === 'create') {
519
+ process.exitCode = await runAgentCreateCommand(args);
520
+ return;
521
+ }
522
+ if (sub === 'delete') {
523
+ process.exitCode = await runAgentDeleteCommand(args);
524
+ return;
525
+ }
526
+ console.error(`unknown agent subcommand: ${sub}`);
527
+ process.exit(1);
528
+ }
529
+
530
+ if (command === 'service') {
531
+ const sub = args._[1];
532
+ if (args.help || args.h || !sub) {
533
+ console.log(AGENT_COMMAND_HELP.service);
534
+ return;
535
+ }
536
+ if (sub === 'create') { process.exitCode = await runServiceCreateCommand(args); return; }
537
+ if (sub === 'update') { process.exitCode = await runServiceUpdateCommand(args); return; }
538
+ if (sub === 'delete') { process.exitCode = await runServiceDeleteCommand(args); return; }
539
+ if (sub === 'list') { process.exitCode = await runServiceListCommand(args); return; }
540
+ if (sub === 'info') { process.exitCode = await runServiceInfoCommand(args); return; }
541
+ if (sub === 'call') { process.exitCode = await runServiceCallCommand(args); return; }
542
+ console.error(`unknown service subcommand: ${sub}`);
543
+ process.exit(1);
544
+ }
545
+
546
+ if (command === 'briefing') {
547
+ const sub = args._[1];
548
+ if (args.help || args.h || !sub) {
549
+ console.log(AGENT_COMMAND_HELP.briefing);
550
+ return;
551
+ }
552
+ if (sub === 'publish') {
553
+ process.exitCode = await runBriefingPublishCommand(args);
554
+ return;
555
+ }
556
+ if (sub === 'get') {
557
+ process.exitCode = await runBriefingGetCommand(args);
558
+ return;
559
+ }
560
+ console.error(`unknown briefing subcommand: ${sub}`);
561
+ process.exit(1);
562
+ }
563
+
564
+ if (command === 'credential') {
565
+ const sub = args._[1];
566
+ if (args.help || args.h || !sub) {
567
+ console.log(AGENT_COMMAND_HELP.credential);
568
+ return;
569
+ }
570
+ if (sub === 'request') {
571
+ process.exitCode = await runCredentialRequestCommand(args);
572
+ return;
573
+ }
574
+ console.error(`unknown credential subcommand: ${sub}`);
575
+ process.exit(1);
576
+ }
577
+
578
+ if (command === 'dashboard') {
579
+ const sub = args._[1];
580
+ if (args.help || args.h || !sub) {
581
+ console.log(AGENT_COMMAND_HELP.dashboard);
582
+ return;
583
+ }
584
+ if (sub === 'set') {
585
+ process.exitCode = await runDashboardSetCommand(args);
586
+ return;
587
+ }
588
+ if (sub === 'get') {
589
+ process.exitCode = await runDashboardGetCommand(args);
590
+ return;
591
+ }
592
+ console.error(`unknown dashboard subcommand: ${sub}`);
593
+ process.exit(1);
594
+ }
595
+
596
+ if (command === 'charter') {
597
+ const sub = args._[1];
598
+ if (args.help || args.h || !sub) {
599
+ console.log(AGENT_COMMAND_HELP.charter);
600
+ return;
601
+ }
602
+ if (sub === 'get') {
603
+ process.exitCode = await runWorkstreamCharterGetCommand(args);
604
+ return;
605
+ }
606
+ if (sub === 'set') {
607
+ process.exitCode = await runWorkstreamCharterSetCommand(args);
608
+ return;
609
+ }
610
+ console.error(`unknown charter subcommand: ${sub}`);
611
+ process.exit(1);
612
+ }
613
+
461
614
  if (command === 'group') {
462
615
  const sub = args._[1];
463
616
  if (args.help || args.h || !sub) {
@@ -468,6 +621,27 @@ async function main() {
468
621
  process.exitCode = await runGroupCreateCommand(args);
469
622
  return;
470
623
  }
624
+ if (sub === 'list') {
625
+ process.exitCode = await runWorkstreamListCommand(args);
626
+ return;
627
+ }
628
+ if (sub === 'delete') {
629
+ process.exitCode = await runWorkstreamDeleteCommand(args);
630
+ return;
631
+ }
632
+ if (sub === 'charter') {
633
+ const op = args._[2];
634
+ if (op === 'get') {
635
+ process.exitCode = await runWorkstreamCharterGetCommand(args);
636
+ return;
637
+ }
638
+ if (op === 'set') {
639
+ process.exitCode = await runWorkstreamCharterSetCommand(args);
640
+ return;
641
+ }
642
+ console.error(`unknown group charter op: ${op}`);
643
+ process.exit(1);
644
+ }
471
645
  if (sub === 'members') {
472
646
  // Same subcommand handles list / add / remove based on flags.
473
647
  if (args.add) {
@@ -610,6 +784,14 @@ async function main() {
610
784
  console.log('Restart ticlawk for the daemon to use the selected profile.');
611
785
  return;
612
786
  }
787
+ if (subcommand === 'show') {
788
+ process.exitCode = await runProfileShowCommand(args);
789
+ return;
790
+ }
791
+ if (subcommand === 'update') {
792
+ process.exitCode = await runProfileUpdateCommand(args);
793
+ return;
794
+ }
613
795
  console.error(`unknown profile subcommand: ${subcommand}`);
614
796
  process.exit(1);
615
797
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ticlawk",
3
- "version": "0.1.16-dev.3",
3
+ "version": "0.1.16-dev.30",
4
4
  "description": "Local connector that links agent harnesses (Claude Code, Codex, OpenClaw, opencode, Pi) to the Ticlawk mobile app.",
5
5
  "type": "module",
6
6
  "main": "ticlawk.mjs",
@@ -149,18 +149,6 @@ export async function updateChannel(id, updates) {
149
149
  return updateAgent(id, updates);
150
150
  }
151
151
 
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
152
  // ── Deliveries (replaces legacy message_jobs poll/ack) ──
165
153
 
166
154
  export async function claimPendingDeliveries(hostId, limit = 5, excludedAgentIds = []) {
@@ -199,6 +187,8 @@ export async function sendAgentMessage({
199
187
  replyToMessageId,
200
188
  runtimeHostId,
201
189
  visibility,
190
+ mediaAssetIds,
191
+ metadata,
202
192
  }) {
203
193
  const { data } = await apiFetch('/api/agent/messages/send', {
204
194
  method: 'POST',
@@ -210,6 +200,8 @@ export async function sendAgentMessage({
210
200
  reply_to_message_id: replyToMessageId ?? null,
211
201
  runtime_host_id: runtimeHostId ?? null,
212
202
  visibility: visibility || null,
203
+ media_asset_ids: Array.isArray(mediaAssetIds) && mediaAssetIds.length > 0 ? mediaAssetIds : undefined,
204
+ metadata: metadata ?? undefined,
213
205
  }),
214
206
  });
215
207
  return data || null;
@@ -232,7 +224,7 @@ export async function readAgentMessages({
232
224
  return data || [];
233
225
  }
234
226
 
235
- export async function createAgentTask({ actingAgentId, conversationId, text, title }) {
227
+ export async function createAgentTask({ actingAgentId, conversationId, text, title, assignAgentId }) {
236
228
  return apiFetch('/api/agent/tasks/create', {
237
229
  method: 'POST',
238
230
  body: JSON.stringify({
@@ -240,6 +232,7 @@ export async function createAgentTask({ actingAgentId, conversationId, text, tit
240
232
  conversation_id: conversationId,
241
233
  text,
242
234
  title: title ?? null,
235
+ assign_agent_id: assignAgentId ?? null,
243
236
  }),
244
237
  });
245
238
  }
@@ -436,6 +429,20 @@ export async function uploadAgentAttachment({
436
429
  });
437
430
  }
438
431
 
432
+ export async function uploadAgentAvatar({
433
+ actingAgentId, filename, contentType, dataBase64,
434
+ }) {
435
+ return apiFetch('/api/agent/profile/avatar', {
436
+ method: 'POST',
437
+ body: JSON.stringify({
438
+ acting_as_agent_id: actingAgentId,
439
+ filename,
440
+ content_type: contentType,
441
+ data_base64: dataBase64,
442
+ }),
443
+ });
444
+ }
445
+
439
446
  export async function viewAgentAttachment({ actingAgentId, assetId }) {
440
447
  const params = new URLSearchParams();
441
448
  params.set('acting_as_agent_id', actingAgentId);
@@ -480,20 +487,222 @@ export async function removeAgentGroupMember({
480
487
  );
481
488
  }
482
489
 
483
- // ── Upload ──
490
+ // ── Workstreams (managed groups) ──
484
491
 
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);
492
+ export async function createWorkstream({
493
+ actingAgentId, name, description, charter, memberAgentIds,
494
+ }) {
495
+ return apiFetch('/api/agent/workstreams', {
496
+ method: 'POST',
497
+ body: JSON.stringify({
498
+ acting_as_agent_id: actingAgentId,
499
+ name,
500
+ description: description ?? null,
501
+ charter: charter ?? null,
502
+ member_agent_ids: memberAgentIds || [],
503
+ }),
504
+ });
505
+ }
490
506
 
491
- const { data } = await apiFetch('/api/assets/upload', {
507
+ export async function deleteWorkstream({ actingAgentId, conversationId }) {
508
+ return apiFetch(
509
+ `/api/agent/workstreams/${encodeURIComponent(conversationId)}`,
510
+ {
511
+ method: 'DELETE',
512
+ body: JSON.stringify({ acting_as_agent_id: actingAgentId }),
513
+ },
514
+ );
515
+ }
516
+
517
+ export async function listWorkstreams({ actingAgentId }) {
518
+ const params = new URLSearchParams();
519
+ params.set('acting_as_agent_id', actingAgentId);
520
+ const { data } = await apiFetch(`/api/agent/workstreams?${params}`);
521
+ return data || [];
522
+ }
523
+
524
+ // ── Agents ──
525
+
526
+ export async function listAgentSlots({ actingAgentId }) {
527
+ const params = new URLSearchParams();
528
+ params.set('acting_as_agent_id', actingAgentId);
529
+ const { data } = await apiFetch(`/api/agent/agents?${params}`);
530
+ return data || [];
531
+ }
532
+
533
+ export async function createAgentSlot({
534
+ actingAgentId, name, runtime, description, displayName, model,
535
+ }) {
536
+ return apiFetch('/api/agent/agents', {
492
537
  method: 'POST',
493
- body: formData,
494
- timeout: 30000,
538
+ body: JSON.stringify({
539
+ acting_as_agent_id: actingAgentId,
540
+ name,
541
+ runtime,
542
+ description: description ?? null,
543
+ display_name: displayName ?? null,
544
+ model: model ?? null,
545
+ }),
495
546
  });
496
- return data;
547
+ }
548
+
549
+ export async function archiveAgentSlot({ actingAgentId, agentId }) {
550
+ return apiFetch(
551
+ `/api/agent/agents/${encodeURIComponent(agentId)}`,
552
+ {
553
+ method: 'DELETE',
554
+ body: JSON.stringify({ acting_as_agent_id: actingAgentId }),
555
+ },
556
+ );
557
+ }
558
+
559
+ // ── Services ──
560
+
561
+ export async function createService({
562
+ actingAgentId, name, description, contractSchema, endpointConfig,
563
+ }) {
564
+ return apiFetch('/api/agent/services', {
565
+ method: 'POST',
566
+ body: JSON.stringify({
567
+ acting_as_agent_id: actingAgentId,
568
+ name,
569
+ description: description ?? null,
570
+ contract_schema: contractSchema ?? null,
571
+ endpoint_config: endpointConfig,
572
+ }),
573
+ });
574
+ }
575
+
576
+ export async function updateService({ actingAgentId, serviceId, ...patch }) {
577
+ return apiFetch(
578
+ `/api/agent/services/${encodeURIComponent(serviceId)}`,
579
+ {
580
+ method: 'PATCH',
581
+ body: JSON.stringify({ acting_as_agent_id: actingAgentId, ...patch }),
582
+ },
583
+ );
584
+ }
585
+
586
+ export async function deleteService({ actingAgentId, serviceId }) {
587
+ return apiFetch(
588
+ `/api/agent/services/${encodeURIComponent(serviceId)}`,
589
+ {
590
+ method: 'DELETE',
591
+ body: JSON.stringify({ acting_as_agent_id: actingAgentId }),
592
+ },
593
+ );
594
+ }
595
+
596
+ export async function listServices({ actingAgentId }) {
597
+ const params = new URLSearchParams();
598
+ params.set('acting_as_agent_id', actingAgentId);
599
+ const { data } = await apiFetch(`/api/agent/services?${params}`);
600
+ return data || [];
601
+ }
602
+
603
+ export async function getServiceInfo({ actingAgentId, name }) {
604
+ const params = new URLSearchParams();
605
+ params.set('acting_as_agent_id', actingAgentId);
606
+ return apiFetch(
607
+ `/api/agent/services/${encodeURIComponent(name)}/info?${params}`,
608
+ );
609
+ }
610
+
611
+ export async function callService({ actingAgentId, name, input }) {
612
+ return apiFetch(
613
+ `/api/agent/services/${encodeURIComponent(name)}/call`,
614
+ {
615
+ method: 'POST',
616
+ body: JSON.stringify({ acting_as_agent_id: actingAgentId, input }),
617
+ },
618
+ );
619
+ }
620
+
621
+ // ── Briefings ──
622
+
623
+ export async function getBriefing({actingAgentId, briefingId}) {
624
+ const params = new URLSearchParams();
625
+ params.set('acting_as_agent_id', actingAgentId);
626
+ return apiFetch(`/api/agent/briefings/${encodeURIComponent(briefingId)}?${params}`);
627
+ }
628
+
629
+ export async function publishBriefing({actingAgentId, bodyText, attachmentAssetId, currentConversationId, responseMode}) {
630
+ const body = { acting_as_agent_id: actingAgentId };
631
+ if (bodyText != null) body.body_text = bodyText;
632
+ if (attachmentAssetId != null) body.attachment_asset_id = attachmentAssetId;
633
+ if (currentConversationId != null) body.current_conversation_id = currentConversationId;
634
+ if (responseMode != null) body.response_mode = responseMode;
635
+ return apiFetch('/api/agent/briefings', {
636
+ method: 'POST',
637
+ body: JSON.stringify(body),
638
+ });
639
+ }
640
+
641
+ // ── Credentials (slot creation + daemon sync) ──
642
+
643
+ export async function fetchCredentials() {
644
+ return apiFetch('/api/agent/credentials', { method: 'GET' });
645
+ }
646
+
647
+ export async function requestCredential({
648
+ actingAgentId, name, description, workstreamId,
649
+ }) {
650
+ return apiFetch('/api/agent/credentials', {
651
+ method: 'POST',
652
+ body: JSON.stringify({
653
+ acting_as_agent_id: actingAgentId,
654
+ name,
655
+ description: description ?? null,
656
+ workstream_id: workstreamId ?? null,
657
+ }),
658
+ });
659
+ }
660
+
661
+ // ── Workstream dashboard ──
662
+
663
+ export async function setWorkstreamDashboard({
664
+ actingAgentId, conversationId, dataJson, htmlTemplate,
665
+ }) {
666
+ const body = { acting_as_agent_id: actingAgentId };
667
+ // Distinguish "omit" from "set to null" — only include keys the caller
668
+ // explicitly passed (including null clears the field).
669
+ if (dataJson !== undefined) body.data_json = dataJson;
670
+ if (htmlTemplate !== undefined) body.html_template = htmlTemplate;
671
+ return apiFetch(
672
+ `/api/agent/workstreams/${encodeURIComponent(conversationId)}/dashboard`,
673
+ { method: 'POST', body: JSON.stringify(body) },
674
+ );
675
+ }
676
+
677
+ export async function getWorkstreamDashboard({ actingAgentId, conversationId }) {
678
+ const params = new URLSearchParams();
679
+ params.set('acting_as_agent_id', actingAgentId);
680
+ return apiFetch(
681
+ `/api/agent/workstreams/${encodeURIComponent(conversationId)}/dashboard?${params}`,
682
+ );
683
+ }
684
+
685
+ // ── Workstream charter ──
686
+
687
+ export async function getWorkstreamCharter({ actingAgentId, conversationId }) {
688
+ const params = new URLSearchParams();
689
+ params.set('acting_as_agent_id', actingAgentId);
690
+ return apiFetch(
691
+ `/api/agent/workstreams/${encodeURIComponent(conversationId)}/charter?${params}`,
692
+ );
693
+ }
694
+
695
+ export async function setWorkstreamCharter({ actingAgentId, conversationId, charter }) {
696
+ return apiFetch(
697
+ `/api/agent/workstreams/${encodeURIComponent(conversationId)}/charter`,
698
+ {
699
+ method: 'POST',
700
+ body: JSON.stringify({
701
+ acting_as_agent_id: actingAgentId,
702
+ charter: charter ?? null,
703
+ }),
704
+ },
705
+ );
497
706
  }
498
707
 
499
708
  // ── Channel event pipe ──
@@ -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
+ }