ticlawk 0.1.17-dev.18 → 0.1.17-dev.19

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 (33) hide show
  1. package/README.md +3 -17
  2. package/bin/ticlawk.mjs +21 -245
  3. package/package.json +1 -1
  4. package/src/adapters/ticlawk/api.mjs +51 -327
  5. package/src/adapters/ticlawk/credentials.mjs +1 -41
  6. package/src/adapters/ticlawk/index.mjs +27 -249
  7. package/src/adapters/ticlawk/wake-client.mjs +1 -1
  8. package/src/cli/agent-commands.mjs +22 -703
  9. package/src/core/agent-cli-handlers.mjs +18 -519
  10. package/src/core/agent-home.mjs +1 -64
  11. package/src/core/events/worker-events.mjs +36 -32
  12. package/src/core/http.mjs +0 -138
  13. package/src/core/runtime-contract.mjs +1 -0
  14. package/src/core/runtime-env.mjs +0 -7
  15. package/src/core/runtime-support.mjs +78 -130
  16. package/src/runtimes/_shared/incoming-message-prompt.mjs +232 -0
  17. package/src/runtimes/_shared/runtime-base-instructions.mjs +34 -0
  18. package/src/runtimes/claude-code/index.mjs +48 -21
  19. package/src/runtimes/claude-code/session.mjs +7 -2
  20. package/src/runtimes/codex/index.mjs +64 -116
  21. package/src/runtimes/codex/session.mjs +12 -2
  22. package/src/runtimes/openclaw/index.mjs +30 -17
  23. package/src/runtimes/opencode/index.mjs +64 -42
  24. package/src/runtimes/opencode/session.mjs +14 -14
  25. package/src/runtimes/pi/index.mjs +64 -42
  26. package/src/runtimes/pi/session.mjs +8 -11
  27. package/ticlawk.mjs +30 -0
  28. package/src/runtimes/_shared/agent-handbook.mjs +0 -38
  29. package/src/runtimes/_shared/brand.mjs +0 -2
  30. package/src/runtimes/_shared/goal-step-prompt.mjs +0 -133
  31. package/src/runtimes/_shared/goal-task-protocol.mjs +0 -50
  32. package/src/runtimes/_shared/standing-prompt.mjs +0 -331
  33. package/src/runtimes/_shared/wake-prompt.mjs +0 -296
@@ -7,8 +7,7 @@
7
7
  * and forwards to the ticlawk backend using the connector API key.
8
8
  *
9
9
  * Targets are parsed in the daemon (not on the wire to backend) so the
10
- * CLI can speak `#<group>` / `dm:<conversation-id>` / `dm:@<user>` /
11
- * `#<group>:<short-msg-id>`
10
+ * CLI can speak `#<group>` / `dm:@<user>` / `#<group>:<short-msg-id>`
12
11
  * while backend keeps a flat conversation_id contract.
13
12
  */
14
13
 
@@ -35,7 +34,7 @@ export function invalidateServerInfoCache(actingAgentId = null) {
35
34
  }
36
35
 
37
36
  /**
38
- * Parse a target string into { conversationId, replyToMessageId } using a
37
+ * Parse a target string into { conversationId, threadRootMsgId } using a
39
38
  * cached server-info lookup. Returns null fields if the target cannot be
40
39
  * resolved; callers should treat that as a 404.
41
40
  *
@@ -44,36 +43,29 @@ export function invalidateServerInfoCache(actingAgentId = null) {
44
43
  * dm:@<handle> -> find DM conversation whose other member is <handle>
45
44
  * #<uuid> -> conversation_id = <uuid>
46
45
  * #<group-name> -> find group conversation by name
47
- * <foo>:<short-msg-id> -> replies under <foo>, root = first message whose
46
+ * <foo>:<short-msg-id> -> thread under <foo>, root = first message whose
48
47
  * id startsWith <short-msg-id>
49
48
  */
50
49
  export async function resolveTarget(actingAgentId, target) {
51
- if (!target) return { conversationId: null, replyToMessageId: null, error: 'target is required' };
50
+ if (!target) return { conversationId: null, threadRootMsgId: null, error: 'target is required' };
52
51
 
53
- // `conv:<uuid>` an explicit conversation by id (the intuitive first guess).
54
- // Handled before the generic colon-split so the uuid isn't mistaken for a
55
- // reply-message suffix.
56
- if (/^conv:[0-9a-f-]{36}$/i.test(target)) {
57
- return { conversationId: target.slice(5), replyToMessageId: null, error: null };
58
- }
59
-
60
- // Strip optional message-reply suffix.
52
+ // Strip optional thread suffix.
61
53
  let base = target;
62
- let replyToMessageId = null;
54
+ let threadShort = null;
63
55
  const colonIdx = target.indexOf(':', target.startsWith('dm:') ? 3 : 1);
64
56
  if (colonIdx > 0 && colonIdx < target.length - 1) {
65
- replyToMessageId = target.slice(colonIdx + 1);
57
+ threadShort = target.slice(colonIdx + 1);
66
58
  base = target.slice(0, colonIdx);
67
59
  }
68
60
 
69
61
  if (/^[0-9a-f-]{36}$/i.test(base)) {
70
- return { conversationId: base, replyToMessageId, error: null };
62
+ return { conversationId: base, threadRootMsgId: threadShort, error: null };
71
63
  }
72
64
  if (base.startsWith('dm:') && /^[0-9a-f-]{36}$/i.test(base.slice(3))) {
73
- return { conversationId: base.slice(3), replyToMessageId, error: null };
65
+ return { conversationId: base.slice(3), threadRootMsgId: threadShort, error: null };
74
66
  }
75
67
  if (base.startsWith('#') && /^[0-9a-f-]{36}$/i.test(base.slice(1))) {
76
- return { conversationId: base.slice(1), replyToMessageId, error: null };
68
+ return { conversationId: base.slice(1), threadRootMsgId: threadShort, error: null };
77
69
  }
78
70
 
79
71
  const info = await getCachedServerInfo(actingAgentId);
@@ -84,30 +76,19 @@ export async function resolveTarget(actingAgentId, target) {
84
76
  const match = convs.find((c) =>
85
77
  c.type === 'dm' && (String(c.display_name || c.name || '').toLowerCase() === handle)
86
78
  );
87
- if (match) return { conversationId: match.id, replyToMessageId, error: null };
88
- return { conversationId: null, replyToMessageId: null, error: `unknown dm target: ${target}` };
79
+ if (match) return { conversationId: match.id, threadRootMsgId: threadShort, error: null };
80
+ return { conversationId: null, threadRootMsgId: null, error: `unknown dm target: ${target}` };
89
81
  }
90
82
  if (base.startsWith('#')) {
91
83
  const name = base.slice(1).toLowerCase();
92
84
  const match = convs.find((c) =>
93
85
  c.type === 'group' && (String(c.name || c.display_name || '').toLowerCase() === name)
94
86
  );
95
- if (match) return { conversationId: match.id, replyToMessageId, error: null };
96
-
97
- if (info?.agent?.is_cos) {
98
- const workstreams = await api.listWorkstreams({ actingAgentId });
99
- const workstreamMatch = workstreams.find((c) =>
100
- String(c.name || c.display_name || '').toLowerCase() === name
101
- );
102
- if (workstreamMatch) {
103
- return { conversationId: workstreamMatch.id, replyToMessageId, error: null };
104
- }
105
- }
106
-
107
- return { conversationId: null, replyToMessageId: null, error: `unknown group target: ${target}` };
87
+ if (match) return { conversationId: match.id, threadRootMsgId: threadShort, error: null };
88
+ return { conversationId: null, threadRootMsgId: null, error: `unknown group target: ${target}` };
108
89
  }
109
90
 
110
- return { conversationId: null, replyToMessageId: null, error: `invalid target syntax: ${target}` };
91
+ return { conversationId: null, threadRootMsgId: null, error: `invalid target syntax: ${target}` };
111
92
  }
112
93
 
113
94
  function getActingAgentId(req, body = {}) {
@@ -128,15 +109,6 @@ function getRuntimeHostId(req, body = {}) {
128
109
  return null;
129
110
  }
130
111
 
131
- function getCurrentConversationId(req, body = {}) {
132
- const fromHeader = req.headers['x-ticlawk-current-conversation-id'];
133
- if (typeof fromHeader === 'string' && fromHeader.trim()) return fromHeader.trim();
134
- if (typeof body?.current_conversation_id === 'string' && body.current_conversation_id.trim()) {
135
- return body.current_conversation_id.trim();
136
- }
137
- return null;
138
- }
139
-
140
112
  function validateActingAgent(actingAgentId, ctx) {
141
113
  if (!actingAgentId) {
142
114
  return { ok: false, status: 400, error: 'TICLAWK_RUNTIME_AGENT_ID required (passed via X-Ticlawk-Acting-Agent-Id or body.acting_as_agent_id)' };
@@ -151,16 +123,6 @@ function validateActingAgent(actingAgentId, ctx) {
151
123
  return { ok: true };
152
124
  }
153
125
 
154
- function validateAgentResponsePhase(metadata) {
155
- const phase = metadata?.agent_response_phase;
156
- if (phase == null) return { ok: true, phase: null };
157
- const normalized = String(phase).trim().toLowerCase();
158
- if (normalized === 'progress' || normalized === 'final') {
159
- return { ok: true, phase: normalized };
160
- }
161
- return { ok: false, error: 'metadata.agent_response_phase must be progress or final' };
162
- }
163
-
164
126
  export async function handleMessageSend(req, body, ctx) {
165
127
  const actingAgentId = getActingAgentId(req, body);
166
128
  const v = validateActingAgent(actingAgentId, ctx);
@@ -170,45 +132,22 @@ export async function handleMessageSend(req, body, ctx) {
170
132
  if (!text) return { status: 400, body: { error: 'text is required' } };
171
133
 
172
134
  let conversationId = body?.conversation_id || null;
173
- let targetReplyToMessageId = null;
135
+ let threadRootMsgId = null;
174
136
  if (!conversationId && body?.target) {
175
137
  const resolved = await resolveTarget(actingAgentId, String(body.target));
176
138
  if (resolved.error) {
177
139
  return { status: 404, body: { error: resolved.error } };
178
140
  }
179
141
  conversationId = resolved.conversationId;
180
- targetReplyToMessageId = resolved.replyToMessageId;
142
+ threadRootMsgId = resolved.threadRootMsgId;
181
143
  }
182
144
  if (!conversationId) {
183
145
  return { status: 400, body: { error: 'target or conversation_id is required' } };
184
146
  }
185
147
 
186
- const currentConversationId = getCurrentConversationId(req, body);
187
- if (currentConversationId && currentConversationId !== conversationId && !body?.allow_cross_target) {
188
- debugLog('agent-cli', 'send.blocked-cross-target', {
189
- actingAgentId,
190
- currentConversationId,
191
- conversationId,
192
- target: body?.target || null,
193
- });
194
- return {
195
- status: 409,
196
- body: {
197
- error: 'refusing to send to a different conversation from the current runtime turn',
198
- current_conversation_id: currentConversationId,
199
- target_conversation_id: conversationId,
200
- hint: 'Use --allow-cross-target only for an intentional cross-conversation send.',
201
- },
202
- };
203
- }
204
-
205
148
  const mediaAssetIds = Array.isArray(body?.media_asset_ids)
206
149
  ? body.media_asset_ids.map((v) => String(v).trim()).filter(Boolean)
207
150
  : [];
208
- const metadata = body?.metadata;
209
- const phaseRes = validateAgentResponsePhase(metadata);
210
- if (!phaseRes.ok) return { status: 400, body: { error: phaseRes.error } };
211
- if (metadata && phaseRes.phase) metadata.agent_response_phase = phaseRes.phase;
212
151
 
213
152
  try {
214
153
  const data = await api.sendAgentMessage({
@@ -216,17 +155,15 @@ export async function handleMessageSend(req, body, ctx) {
216
155
  conversationId,
217
156
  text,
218
157
  seenUpToSeq: body?.seen_up_to_seq,
219
- replyToMessageId: body?.reply_to_message_id || targetReplyToMessageId || null,
158
+ replyToMessageId: body?.reply_to_message_id || threadRootMsgId || null,
220
159
  runtimeHostId: getRuntimeHostId(req, body),
221
160
  mediaAssetIds,
222
- metadata,
223
161
  });
224
162
  debugLog('agent-cli', 'send.ok', {
225
163
  actingAgentId,
226
164
  conversationId,
227
165
  messageId: data?.id,
228
166
  seq: data?.seq,
229
- agentResponsePhase: phaseRes.phase,
230
167
  bodyChars: text.length,
231
168
  });
232
169
  return { status: 200, body: { ok: true, data } };
@@ -292,7 +229,6 @@ export async function handleTaskCreate(req, body, ctx) {
292
229
  conversationId,
293
230
  text,
294
231
  title: body?.title ?? null,
295
- assignAgentId: body?.assign_agent_id || body?.assignee_agent_id || null,
296
232
  });
297
233
  debugLog('agent-cli', 'task.create', {
298
234
  actingAgentId,
@@ -394,82 +330,6 @@ export async function handleTaskUpdate(req, body, ctx) {
394
330
  }
395
331
  }
396
332
 
397
- export async function handleGoalReport(req, body, ctx) {
398
- const actingAgentId = getActingAgentId(req, body);
399
- const v = validateActingAgent(actingAgentId, ctx);
400
- if (!v.ok) return { status: v.status, body: { error: v.error } };
401
- const conversationId = body?.conversation_id || getCurrentConversationId(req, body);
402
- if (!conversationId) return { status: 400, body: { error: 'conversation_id is required' } };
403
- if (!body?.transition_id) return { status: 400, body: { error: 'transition_id is required' } };
404
- if (!body?.outcome) return { status: 400, body: { error: 'outcome is required' } };
405
- try {
406
- const data = await api.reportGoalTransition({
407
- actingAgentId,
408
- conversationId,
409
- transitionId: body.transition_id,
410
- outcome: body.outcome,
411
- detail: body.detail || null,
412
- currentTaskId: body.current_task_id || null,
413
- });
414
- debugLog('agent-cli', 'goal.report', {
415
- actingAgentId,
416
- conversationId,
417
- outcome: body.outcome,
418
- state: data?.state,
419
- });
420
- return { status: data?.ok ? 200 : 400, body: data };
421
- } catch (err) {
422
- return { status: err?.status || 500, body: { error: err?.message || 'goal report failed' } };
423
- }
424
- }
425
-
426
- export async function handleApprovalList(req, query, ctx) {
427
- const actingAgentId = getActingAgentId(req, query);
428
- const v = validateActingAgent(actingAgentId, ctx);
429
- if (!v.ok) return { status: v.status, body: { error: v.error } };
430
- let conversationId = query?.conversation_id || null;
431
- if (!conversationId && query?.target) {
432
- const resolved = await resolveTarget(actingAgentId, String(query.target));
433
- if (!resolved.error) conversationId = resolved.conversationId;
434
- }
435
- if (!conversationId) return { status: 400, body: { error: 'conversation_id is required' } };
436
- try {
437
- const data = await api.listGoalApprovals({ actingAgentId, conversationId });
438
- return { status: 200, body: { data } };
439
- } catch (err) {
440
- return { status: err?.status || 500, body: { error: err?.message || 'approval list failed' } };
441
- }
442
- }
443
-
444
- export async function handleApprovalResolve(req, body, ctx) {
445
- const actingAgentId = getActingAgentId(req, body);
446
- const v = validateActingAgent(actingAgentId, ctx);
447
- if (!v.ok) return { status: v.status, body: { error: v.error } };
448
- if (!body?.request_id) return { status: 400, body: { error: 'request_id is required' } };
449
- if (body?.decision !== 'granted' && body?.decision !== 'rejected') {
450
- return { status: 400, body: { error: 'decision must be granted or rejected' } };
451
- }
452
- try {
453
- const data = await api.resolveGoalApproval({
454
- actingAgentId,
455
- requestId: body.request_id,
456
- decision: body.decision,
457
- originalText: body.original_text || null,
458
- confidence: body.confidence ?? null,
459
- sourceMessageId: body.source_message_id || null,
460
- });
461
- debugLog('agent-cli', 'approval.resolve', {
462
- actingAgentId,
463
- requestId: body.request_id,
464
- decision: body.decision,
465
- resumed: data?.resumed,
466
- });
467
- return { status: data?.ok ? 200 : 400, body: data };
468
- } catch (err) {
469
- return { status: err?.status || 500, body: { error: err?.message || 'approval resolve failed' } };
470
- }
471
- }
472
-
473
333
  export async function handleTaskList(req, query, ctx) {
474
334
  const actingAgentId = getActingAgentId(req, query);
475
335
  const v = validateActingAgent(actingAgentId, ctx);
@@ -552,7 +412,6 @@ export async function handleReminderSchedule(req, body, ctx) {
552
412
  fireAt: body.fire_at,
553
413
  anchorConversationId,
554
414
  anchorMessageId: body?.anchor_message_id || null,
555
- recurrence: body?.recurrence ?? null,
556
415
  });
557
416
  debugLog('agent-cli', 'reminder.schedule', {
558
417
  actingAgentId,
@@ -857,366 +716,6 @@ export async function handleGroupMembersRemove(req, body, ctx) {
857
716
  }
858
717
  }
859
718
 
860
- export async function handleWorkstreamCreate(req, body, ctx) {
861
- const actingAgentId = getActingAgentId(req, body);
862
- const v = validateActingAgent(actingAgentId, ctx);
863
- if (!v.ok) return { status: v.status, body: { error: v.error } };
864
- const name = String(body?.name || '').trim();
865
- if (!name) return { status: 400, body: { error: 'name is required' } };
866
- try {
867
- const data = await api.createWorkstream({
868
- actingAgentId,
869
- name,
870
- description: body?.description || null,
871
- charter: body?.charter ?? null,
872
- memberAgentIds: Array.isArray(body?.member_agent_ids) ? body.member_agent_ids : [],
873
- });
874
- invalidateServerInfoCache(actingAgentId);
875
- debugLog('agent-cli', 'workstream.create', {
876
- actingAgentId, conversationId: data?.conversation?.id,
877
- });
878
- return { status: 200, body: data };
879
- } catch (err) {
880
- return { status: err?.status || 500, body: { error: err?.message || 'workstream create failed' } };
881
- }
882
- }
883
-
884
- export async function handleWorkstreamDelete(req, body, ctx) {
885
- const actingAgentId = getActingAgentId(req, body);
886
- const v = validateActingAgent(actingAgentId, ctx);
887
- if (!v.ok) return { status: v.status, body: { error: v.error } };
888
- let conversationId = body?.conversation_id || null;
889
- if (!conversationId && body?.target) {
890
- const resolved = await resolveTarget(actingAgentId, String(body.target));
891
- if (resolved.error) return { status: 404, body: { error: resolved.error } };
892
- conversationId = resolved.conversationId;
893
- }
894
- if (!conversationId) return { status: 400, body: { error: 'target or conversation_id is required' } };
895
- try {
896
- const data = await api.deleteWorkstream({ actingAgentId, conversationId });
897
- invalidateServerInfoCache(actingAgentId);
898
- return { status: 200, body: data };
899
- } catch (err) {
900
- return { status: err?.status || 500, body: { error: err?.message || 'workstream delete failed' } };
901
- }
902
- }
903
-
904
- export async function handleWorkstreamList(req, query, ctx) {
905
- const actingAgentId = getActingAgentId(req, query);
906
- const v = validateActingAgent(actingAgentId, ctx);
907
- if (!v.ok) return { status: v.status, body: { error: v.error } };
908
- try {
909
- const data = await api.listWorkstreams({ actingAgentId });
910
- return { status: 200, body: { data } };
911
- } catch (err) {
912
- return { status: err?.status || 500, body: { error: err?.message || 'workstream list failed' } };
913
- }
914
- }
915
-
916
- export async function handleAgentList(req, query, ctx) {
917
- const actingAgentId = getActingAgentId(req, query);
918
- const v = validateActingAgent(actingAgentId, ctx);
919
- if (!v.ok) return { status: v.status, body: { error: v.error } };
920
- try {
921
- const data = await api.listAgentSlots({ actingAgentId });
922
- return { status: 200, body: { data } };
923
- } catch (err) {
924
- return { status: err?.status || 500, body: { error: err?.message || 'agent list failed' } };
925
- }
926
- }
927
-
928
- export async function handleAgentCreate(req, body, ctx) {
929
- const actingAgentId = getActingAgentId(req, body);
930
- const v = validateActingAgent(actingAgentId, ctx);
931
- if (!v.ok) return { status: v.status, body: { error: v.error } };
932
- const name = String(body?.name || '').trim();
933
- const runtime = String(body?.runtime || '').trim();
934
- if (!name) return { status: 400, body: { error: 'name is required' } };
935
- if (!runtime) return { status: 400, body: { error: 'runtime is required' } };
936
- try {
937
- const data = await api.createAgentSlot({
938
- actingAgentId,
939
- name,
940
- runtime,
941
- description: body?.description || null,
942
- displayName: body?.display_name || null,
943
- model: body?.model || null,
944
- });
945
- return { status: 200, body: data };
946
- } catch (err) {
947
- return { status: err?.status || 500, body: { error: err?.message || 'agent create failed' } };
948
- }
949
- }
950
-
951
- export async function handleAgentDelete(req, body, ctx) {
952
- const actingAgentId = getActingAgentId(req, body);
953
- const v = validateActingAgent(actingAgentId, ctx);
954
- if (!v.ok) return { status: v.status, body: { error: v.error } };
955
- const agentId = String(body?.agent_id || '').trim();
956
- if (!agentId) return { status: 400, body: { error: 'agent_id is required' } };
957
- try {
958
- const data = await api.archiveAgentSlot({ actingAgentId, agentId });
959
- return { status: 200, body: data };
960
- } catch (err) {
961
- return { status: err?.status || 500, body: { error: err?.message || 'agent delete failed' } };
962
- }
963
- }
964
-
965
- export async function handleServiceCreate(req, body, ctx) {
966
- const actingAgentId = getActingAgentId(req, body);
967
- const v = validateActingAgent(actingAgentId, ctx);
968
- if (!v.ok) return { status: v.status, body: { error: v.error } };
969
- const name = String(body?.name || '').trim();
970
- if (!name) return { status: 400, body: { error: 'name is required' } };
971
- if (!body?.endpoint_config || typeof body.endpoint_config !== 'object') {
972
- return { status: 400, body: { error: 'endpoint_config is required' } };
973
- }
974
- try {
975
- const data = await api.createService({
976
- actingAgentId, name,
977
- description: body?.description || null,
978
- contractSchema: body?.contract_schema ?? null,
979
- endpointConfig: body.endpoint_config,
980
- });
981
- return { status: 200, body: data };
982
- } catch (err) {
983
- return { status: err?.status || 500, body: { error: err?.message || 'service create failed' } };
984
- }
985
- }
986
-
987
- export async function handleServiceUpdate(req, body, ctx) {
988
- const actingAgentId = getActingAgentId(req, body);
989
- const v = validateActingAgent(actingAgentId, ctx);
990
- if (!v.ok) return { status: v.status, body: { error: v.error } };
991
- const serviceId = String(body?.service_id || '').trim();
992
- if (!serviceId) return { status: 400, body: { error: 'service_id is required' } };
993
- const patch = {};
994
- if ('description' in (body || {})) patch.description = body.description;
995
- if ('contract_schema' in (body || {})) patch.contract_schema = body.contract_schema;
996
- if ('endpoint_config' in (body || {})) patch.endpoint_config = body.endpoint_config;
997
- if ('status' in (body || {})) patch.status = body.status;
998
- try {
999
- const data = await api.updateService({ actingAgentId, serviceId, ...patch });
1000
- return { status: 200, body: data };
1001
- } catch (err) {
1002
- return { status: err?.status || 500, body: { error: err?.message || 'service update failed' } };
1003
- }
1004
- }
1005
-
1006
- export async function handleServiceDelete(req, body, ctx) {
1007
- const actingAgentId = getActingAgentId(req, body);
1008
- const v = validateActingAgent(actingAgentId, ctx);
1009
- if (!v.ok) return { status: v.status, body: { error: v.error } };
1010
- const serviceId = String(body?.service_id || '').trim();
1011
- if (!serviceId) return { status: 400, body: { error: 'service_id is required' } };
1012
- try {
1013
- const data = await api.deleteService({ actingAgentId, serviceId });
1014
- return { status: 200, body: data };
1015
- } catch (err) {
1016
- return { status: err?.status || 500, body: { error: err?.message || 'service delete failed' } };
1017
- }
1018
- }
1019
-
1020
- export async function handleServiceList(req, query, ctx) {
1021
- const actingAgentId = getActingAgentId(req, query);
1022
- const v = validateActingAgent(actingAgentId, ctx);
1023
- if (!v.ok) return { status: v.status, body: { error: v.error } };
1024
- try {
1025
- const data = await api.listServices({ actingAgentId });
1026
- return { status: 200, body: { data } };
1027
- } catch (err) {
1028
- return { status: err?.status || 500, body: { error: err?.message || 'service list failed' } };
1029
- }
1030
- }
1031
-
1032
- export async function handleServiceInfo(req, query, ctx) {
1033
- const actingAgentId = getActingAgentId(req, query);
1034
- const v = validateActingAgent(actingAgentId, ctx);
1035
- if (!v.ok) return { status: v.status, body: { error: v.error } };
1036
- const name = String(query?.name || '').trim();
1037
- if (!name) return { status: 400, body: { error: 'name is required' } };
1038
- try {
1039
- const data = await api.getServiceInfo({ actingAgentId, name });
1040
- return { status: 200, body: data };
1041
- } catch (err) {
1042
- return { status: err?.status || 500, body: { error: err?.message || 'service info failed' } };
1043
- }
1044
- }
1045
-
1046
- export async function handleServiceCall(req, body, ctx) {
1047
- const actingAgentId = getActingAgentId(req, body);
1048
- const v = validateActingAgent(actingAgentId, ctx);
1049
- if (!v.ok) return { status: v.status, body: { error: v.error } };
1050
- const name = String(body?.name || '').trim();
1051
- if (!name) return { status: 400, body: { error: 'name is required' } };
1052
- try {
1053
- const data = await api.callService({
1054
- actingAgentId, name,
1055
- input: body?.input ?? null,
1056
- });
1057
- return { status: 200, body: data };
1058
- } catch (err) {
1059
- return { status: err?.status || 500, body: { error: err?.message || 'service call failed' } };
1060
- }
1061
- }
1062
-
1063
- export async function handleBriefingGet(req, query, ctx) {
1064
- const actingAgentId = getActingAgentId(req, query);
1065
- const v = validateActingAgent(actingAgentId, ctx);
1066
- if (!v.ok) return { status: v.status, body: { error: v.error } };
1067
- const briefingId = String(query?.id || '').trim();
1068
- if (!briefingId) return { status: 400, body: { error: 'id is required' } };
1069
- try {
1070
- const data = await api.getBriefing({ actingAgentId, briefingId });
1071
- return { status: 200, body: data };
1072
- } catch (err) {
1073
- return { status: err?.status || 500, body: { error: err?.message || 'briefing get failed' } };
1074
- }
1075
- }
1076
-
1077
- export async function handleBriefingPublish(req, body, ctx) {
1078
- const actingAgentId = getActingAgentId(req, body);
1079
- const v = validateActingAgent(actingAgentId, ctx);
1080
- if (!v.ok) return { status: v.status, body: { error: v.error } };
1081
- const bodyText = typeof body?.body_text === 'string' && body.body_text.trim() ? body.body_text.trim() : null;
1082
- const attachmentAssetId = typeof body?.attachment_asset_id === 'string' && body.attachment_asset_id.trim()
1083
- ? body.attachment_asset_id.trim()
1084
- : null;
1085
- const responseMode = typeof body?.response_mode === 'string' && body.response_mode.trim()
1086
- ? body.response_mode.trim().toLowerCase()
1087
- : 'info';
1088
- if (!bodyText) {
1089
- return { status: 400, body: { error: 'body_text is required' } };
1090
- }
1091
- if (bodyText && bodyText.length > 140) {
1092
- return { status: 400, body: { error: 'body_text must be ≤140 chars' } };
1093
- }
1094
- if (!['info', 'approval'].includes(responseMode)) {
1095
- return { status: 400, body: { error: 'response_mode must be info or approval' } };
1096
- }
1097
- const currentConversationId = getCurrentConversationId(req, body);
1098
- try {
1099
- const data = await api.publishBriefing({
1100
- actingAgentId,
1101
- bodyText,
1102
- attachmentAssetId,
1103
- currentConversationId,
1104
- responseMode,
1105
- });
1106
- return { status: 200, body: data };
1107
- } catch (err) {
1108
- return { status: err?.status || 500, body: { error: err?.message || 'briefing publish failed' } };
1109
- }
1110
- }
1111
-
1112
- export async function handleCredentialRequest(req, body, ctx) {
1113
- const actingAgentId = getActingAgentId(req, body);
1114
- const v = validateActingAgent(actingAgentId, ctx);
1115
- if (!v.ok) return { status: v.status, body: { error: v.error } };
1116
- const name = String(body?.name || '').trim();
1117
- if (!name) return { status: 400, body: { error: 'name is required' } };
1118
- let workstreamId = body?.workstream_id || null;
1119
- if (!workstreamId && body?.target) {
1120
- const resolved = await resolveTarget(actingAgentId, String(body.target));
1121
- if (resolved.error) return { status: 404, body: { error: resolved.error } };
1122
- workstreamId = resolved.conversationId;
1123
- }
1124
- try {
1125
- const data = await api.requestCredential({
1126
- actingAgentId,
1127
- name,
1128
- description: body?.description || null,
1129
- workstreamId,
1130
- });
1131
- return { status: 200, body: data };
1132
- } catch (err) {
1133
- return { status: err?.status || 500, body: { error: err?.message || 'credential request failed' } };
1134
- }
1135
- }
1136
-
1137
- export async function handleWorkstreamDashboardSet(req, body, ctx) {
1138
- const actingAgentId = getActingAgentId(req, body);
1139
- const v = validateActingAgent(actingAgentId, ctx);
1140
- if (!v.ok) return { status: v.status, body: { error: v.error } };
1141
- let conversationId = body?.conversation_id || null;
1142
- if (!conversationId && body?.target) {
1143
- const resolved = await resolveTarget(actingAgentId, String(body.target));
1144
- if (resolved.error) return { status: 404, body: { error: resolved.error } };
1145
- conversationId = resolved.conversationId;
1146
- }
1147
- if (!conversationId) return { status: 400, body: { error: 'target or conversation_id is required' } };
1148
- const payload = { actingAgentId, conversationId };
1149
- if ('data_json' in (body || {})) payload.dataJson = body.data_json;
1150
- if ('html_template' in (body || {})) payload.htmlTemplate = body.html_template;
1151
- try {
1152
- const data = await api.setWorkstreamDashboard(payload);
1153
- return { status: 200, body: data };
1154
- } catch (err) {
1155
- return { status: err?.status || 500, body: { error: err?.message || 'dashboard set failed' } };
1156
- }
1157
- }
1158
-
1159
- export async function handleWorkstreamDashboardGet(req, query, ctx) {
1160
- const actingAgentId = getActingAgentId(req, query);
1161
- const v = validateActingAgent(actingAgentId, ctx);
1162
- if (!v.ok) return { status: v.status, body: { error: v.error } };
1163
- let conversationId = query?.conversation_id || null;
1164
- if (!conversationId && query?.target) {
1165
- const resolved = await resolveTarget(actingAgentId, String(query.target));
1166
- if (resolved.error) return { status: 404, body: { error: resolved.error } };
1167
- conversationId = resolved.conversationId;
1168
- }
1169
- if (!conversationId) return { status: 400, body: { error: 'target or conversation_id is required' } };
1170
- try {
1171
- const data = await api.getWorkstreamDashboard({ actingAgentId, conversationId });
1172
- return { status: 200, body: data };
1173
- } catch (err) {
1174
- return { status: err?.status || 500, body: { error: err?.message || 'dashboard get failed' } };
1175
- }
1176
- }
1177
-
1178
- export async function handleWorkstreamCharterGet(req, query, ctx) {
1179
- const actingAgentId = getActingAgentId(req, query);
1180
- const v = validateActingAgent(actingAgentId, ctx);
1181
- if (!v.ok) return { status: v.status, body: { error: v.error } };
1182
- let conversationId = query?.conversation_id || null;
1183
- if (!conversationId && query?.target) {
1184
- const resolved = await resolveTarget(actingAgentId, String(query.target));
1185
- if (resolved.error) return { status: 404, body: { error: resolved.error } };
1186
- conversationId = resolved.conversationId;
1187
- }
1188
- if (!conversationId) return { status: 400, body: { error: 'target or conversation_id is required' } };
1189
- try {
1190
- const data = await api.getWorkstreamCharter({ actingAgentId, conversationId });
1191
- return { status: 200, body: data };
1192
- } catch (err) {
1193
- return { status: err?.status || 500, body: { error: err?.message || 'charter get failed' } };
1194
- }
1195
- }
1196
-
1197
- export async function handleWorkstreamCharterSet(req, body, ctx) {
1198
- const actingAgentId = getActingAgentId(req, body);
1199
- const v = validateActingAgent(actingAgentId, ctx);
1200
- if (!v.ok) return { status: v.status, body: { error: v.error } };
1201
- let conversationId = body?.conversation_id || null;
1202
- if (!conversationId && body?.target) {
1203
- const resolved = await resolveTarget(actingAgentId, String(body.target));
1204
- if (resolved.error) return { status: 404, body: { error: resolved.error } };
1205
- conversationId = resolved.conversationId;
1206
- }
1207
- if (!conversationId) return { status: 400, body: { error: 'target or conversation_id is required' } };
1208
- const charter = typeof body?.charter === 'string' ? body.charter : null;
1209
- try {
1210
- const data = await api.setWorkstreamCharter({ actingAgentId, conversationId, charter });
1211
- debugLog('agent-cli', 'workstream.charter.set', {
1212
- actingAgentId, conversationId, len: charter?.length ?? 0,
1213
- });
1214
- return { status: 200, body: data };
1215
- } catch (err) {
1216
- return { status: err?.status || 500, body: { error: err?.message || 'charter set failed' } };
1217
- }
1218
- }
1219
-
1220
719
  export async function handleServerInfo(req, query, ctx) {
1221
720
  const actingAgentId = getActingAgentId(req, query);
1222
721
  const v = validateActingAgent(actingAgentId, ctx);