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
@@ -8,13 +8,11 @@
8
8
  * the ticlawk backend.
9
9
  *
10
10
  * Commands:
11
- * ticlawk message send --target <t> [--phase progress|final] [--seen-up-to-seq N] [--reply-to <msg>]
11
+ * ticlawk message send --target <t> [--seen-up-to-seq N] [--reply-to <msg>]
12
12
  * ticlawk message read --target <t> [--around <msg>] [--limit N]
13
- * ticlawk task create --target <t> [--title <t>] [--assign-agent <id>]
14
13
  * ticlawk task claim --message-id <id> [--lease-seconds N]
15
14
  * ticlawk task update --task-id <id> --status <s>
16
- * ticlawk task list [--target <t>] # group admins see the full task board
17
- * ticlawk charter get/set --target <t>
15
+ * ticlawk task list [--target <t>]
18
16
  * ticlawk group members --target <t>
19
17
  * ticlawk server info [--refresh]
20
18
  *
@@ -43,9 +41,6 @@ function requireAgentEnv() {
43
41
  agentId,
44
42
  hostId: String(process.env.TICLAWK_RUNTIME_HOST_ID || '').trim() || null,
45
43
  sessionId: String(process.env.TICLAWK_RUNTIME_SESSION_ID || '').trim() || null,
46
- currentConversationId: String(process.env.TICLAWK_RUNTIME_CONVERSATION_ID || '').trim() || null,
47
- currentMessageId: String(process.env.TICLAWK_RUNTIME_MESSAGE_ID || '').trim() || null,
48
- currentTarget: String(process.env.TICLAWK_RUNTIME_TARGET || '').trim() || null,
49
44
  };
50
45
  }
51
46
 
@@ -56,11 +51,6 @@ function commonHeaders(env) {
56
51
  };
57
52
  if (env.hostId) headers['X-Ticlawk-Runtime-Host-Id'] = env.hostId;
58
53
  if (env.sessionId) headers['X-Ticlawk-Runtime-Session-Id'] = env.sessionId;
59
- if (env.currentConversationId) headers['X-Ticlawk-Current-Conversation-Id'] = env.currentConversationId;
60
- if (env.currentMessageId) headers['X-Ticlawk-Current-Message-Id'] = env.currentMessageId;
61
- if (env.currentTarget && /^[\x00-\x7F]*$/.test(env.currentTarget)) {
62
- headers['X-Ticlawk-Current-Target'] = env.currentTarget;
63
- }
64
54
  return headers;
65
55
  }
66
56
 
@@ -114,14 +104,6 @@ function getNumberArg(args, name) {
114
104
  return Number.isFinite(n) ? n : null;
115
105
  }
116
106
 
117
- function getMessagePhaseArg(args) {
118
- const raw = getArg(args, 'phase');
119
- if (raw == null) return null;
120
- const phase = raw.trim().toLowerCase();
121
- if (phase === 'progress' || phase === 'final') return phase;
122
- return { error: '--phase must be progress or final' };
123
- }
124
-
125
107
  function printJson(value) {
126
108
  console.log(JSON.stringify(value, null, 2));
127
109
  }
@@ -178,28 +160,13 @@ export async function runMessageSendCommand(args) {
178
160
  mediaAssetIds.push(upload.assetId);
179
161
  }
180
162
 
181
- // Optional metadata flags. --kind classifies the message; --phase
182
- // marks whether this is an intermediate progress update or the final
183
- // visible response for the current turn.
184
- const kind = getArg(args, 'kind');
185
- const phase = getMessagePhaseArg(args);
186
- if (phase && typeof phase === 'object') {
187
- console.error(phase.error);
188
- return 2;
189
- }
190
- const metadata = {};
191
- if (kind) metadata.kind = kind;
192
- if (phase) metadata.agent_response_phase = phase;
193
-
194
163
  const body = {
195
164
  target,
196
165
  conversation_id: conversationId,
197
166
  text: text.replace(/\n+$/, ''),
198
167
  seen_up_to_seq: getNumberArg(args, 'seen-up-to-seq'),
199
168
  reply_to_message_id: getArg(args, 'reply-to'),
200
- allow_cross_target: Boolean(args['allow-cross-target']),
201
169
  media_asset_ids: mediaAssetIds.length > 0 ? mediaAssetIds : undefined,
202
- metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
203
170
  };
204
171
  const res = await daemonRequest({
205
172
  method: 'POST',
@@ -241,9 +208,7 @@ export async function runMessageReadCommand(args) {
241
208
  export async function runTaskCreateCommand(args) {
242
209
  const env = requireAgentEnv();
243
210
  const target = getArg(args, 'target');
244
- // Default to the conversation this turn is running for (the daemon sets it).
245
- // Lets a goal-lane step just `task create --title ...` without addressing.
246
- const conversationId = getArg(args, 'conversation') || getArg(args, 'conversation-id') || env.currentConversationId;
211
+ const conversationId = getArg(args, 'conversation-id');
247
212
  if (!target && !conversationId) {
248
213
  console.error('--target or --conversation-id is required');
249
214
  return 2;
@@ -252,7 +217,7 @@ export async function runTaskCreateCommand(args) {
252
217
  if (!text || !text.trim()) {
253
218
  console.error('task body is required on stdin');
254
219
  console.error('Example:');
255
- console.error(" ticlawk task create --target \"#frontend\" --title \"fix login\" --assign-agent <agent-id> <<'EOF'");
220
+ console.error(" ticlawk task create --target \"#frontend\" --title \"fix login\" <<'EOF'");
256
221
  console.error(' Detailed description goes here.');
257
222
  console.error(' EOF');
258
223
  return 2;
@@ -266,7 +231,6 @@ export async function runTaskCreateCommand(args) {
266
231
  conversation_id: conversationId,
267
232
  text: text.replace(/\n+$/, ''),
268
233
  title: getArg(args, 'title'),
269
- assign_agent_id: getArg(args, 'assign-agent') || getArg(args, 'assignee-agent'),
270
234
  },
271
235
  });
272
236
  printJson(res.body);
@@ -278,7 +242,7 @@ export async function runTaskClaimCommand(args) {
278
242
  const messageId = getArg(args, 'message-id');
279
243
  const number = getNumberArg(args, 'number');
280
244
  const target = getArg(args, 'target');
281
- const conversationId = getArg(args, 'conversation') || getArg(args, 'conversation-id') || env.currentConversationId;
245
+ const conversationId = getArg(args, 'conversation-id');
282
246
  if (!messageId && number == null) {
283
247
  console.error('--message-id or --number is required');
284
248
  return 2;
@@ -346,92 +310,6 @@ export async function runTaskUpdateCommand(args) {
346
310
  return exitFromStatus(res.statusCode);
347
311
  }
348
312
 
349
- const GOAL_OUTCOMES = [
350
- 'gap', 'no_gap', 'wait',
351
- 'task_completed', 'needs_approval', 'blocked',
352
- 'accepted', 'rejected',
353
- ];
354
-
355
- export async function runGoalReportCommand(args) {
356
- const env = requireAgentEnv();
357
- const conversationId = getArg(args, 'conversation') || getArg(args, 'conversation-id') || env.currentConversationId;
358
- const transitionId = getArg(args, 'transition') || getArg(args, 'transition-id');
359
- const outcome = getArg(args, 'outcome');
360
- if (!conversationId) {
361
- console.error('--conversation is required');
362
- return 2;
363
- }
364
- if (!transitionId) {
365
- console.error('--transition is required');
366
- return 2;
367
- }
368
- if (!outcome || !GOAL_OUTCOMES.includes(outcome)) {
369
- console.error(`--outcome must be one of: ${GOAL_OUTCOMES.join(', ')}`);
370
- return 2;
371
- }
372
- const res = await daemonRequest({
373
- method: 'POST',
374
- path: '/agent/goal/report',
375
- headers: commonHeaders(env),
376
- body: {
377
- conversation_id: conversationId,
378
- transition_id: transitionId,
379
- outcome,
380
- detail: getArg(args, 'detail'),
381
- current_task_id: getArg(args, 'current-task') || getArg(args, 'task-id'),
382
- },
383
- });
384
- printJson(res.body);
385
- return exitFromStatus(res.statusCode);
386
- }
387
-
388
- export async function runApprovalResolveCommand(args) {
389
- const env = requireAgentEnv();
390
- const requestId = getArg(args, 'request') || getArg(args, 'request-id');
391
- let decision = getArg(args, 'decision');
392
- if (args.grant) decision = 'granted';
393
- if (args.reject) decision = 'rejected';
394
- if (!requestId) {
395
- console.error('--request is required');
396
- return 2;
397
- }
398
- if (decision !== 'granted' && decision !== 'rejected') {
399
- console.error('--decision must be granted or rejected (or use --grant / --reject)');
400
- return 2;
401
- }
402
- const confidenceRaw = getArg(args, 'confidence');
403
- const res = await daemonRequest({
404
- method: 'POST',
405
- path: '/agent/approval/resolve',
406
- headers: commonHeaders(env),
407
- body: {
408
- request_id: requestId,
409
- decision,
410
- original_text: getArg(args, 'original-text'),
411
- confidence: confidenceRaw != null ? Number(confidenceRaw) : undefined,
412
- source_message_id: getArg(args, 'source-message-id'),
413
- },
414
- });
415
- printJson(res.body);
416
- return exitFromStatus(res.statusCode);
417
- }
418
-
419
- export async function runApprovalListCommand(args) {
420
- const env = requireAgentEnv();
421
- const params = new URLSearchParams();
422
- const target = getArg(args, 'target');
423
- const conversationId = getArg(args, 'conversation') || getArg(args, 'conversation-id') || env.currentConversationId;
424
- if (target) params.set('target', target);
425
- if (conversationId) params.set('conversation_id', conversationId);
426
- const res = await daemonRequest({
427
- method: 'GET',
428
- path: `/agent/approval/list?${params}`,
429
- headers: commonHeaders(env),
430
- });
431
- printJson(res.body);
432
- return exitFromStatus(res.statusCode);
433
- }
434
-
435
313
  export async function runMessageReactCommand(args) {
436
314
  const env = requireAgentEnv();
437
315
  const messageId = getArg(args, 'message-id');
@@ -467,10 +345,7 @@ export async function runTaskListCommand(args) {
467
345
  const env = requireAgentEnv();
468
346
  const params = new URLSearchParams();
469
347
  const target = getArg(args, 'target');
470
- // No filter given → scope to the conversation this turn runs for, instead
471
- // of dumping tasks from every conversation the owner can see.
472
- const conversationId = getArg(args, 'conversation') || getArg(args, 'conversation-id')
473
- || (target ? null : env.currentConversationId);
348
+ const conversationId = getArg(args, 'conversation-id');
474
349
  if (target) params.set('target', target);
475
350
  if (conversationId) params.set('conversation_id', conversationId);
476
351
  const res = await daemonRequest({
@@ -517,18 +392,6 @@ export async function runReminderScheduleCommand(args) {
517
392
  console.error('--target or --anchor-conversation-id is required');
518
393
  return 2;
519
394
  }
520
- // Optional recurrence: --recur-at HH:MM (owner-local) [--recur-weekday 1,2,3].
521
- // The timezone is system-owned (the owner's), filled in by the backend — the
522
- // agent never passes it. The backend also computes the first fire_at in it.
523
- let recurrence = null;
524
- const recurAt = getArg(args, 'recur-at');
525
- if (recurAt) {
526
- const wdRaw = getArg(args, 'recur-weekday');
527
- const byWeekday = wdRaw
528
- ? String(wdRaw).split(',').map((s) => parseInt(s.trim(), 10)).filter((n) => n >= 1 && n <= 7)
529
- : [];
530
- recurrence = { at: recurAt, ...(byWeekday.length ? { by_weekday: byWeekday } : {}) };
531
- }
532
395
  const res = await daemonRequest({
533
396
  method: 'POST',
534
397
  path: '/agent/reminder/schedule',
@@ -539,7 +402,6 @@ export async function runReminderScheduleCommand(args) {
539
402
  target,
540
403
  anchor_conversation_id: anchorConversationId,
541
404
  anchor_message_id: anchorMessageId,
542
- recurrence,
543
405
  },
544
406
  });
545
407
  printJson(res.body);
@@ -792,11 +654,6 @@ function inferContentType(filePath) {
792
654
  case '.jpg': case '.jpeg': return 'image/jpeg';
793
655
  case '.gif': return 'image/gif';
794
656
  case '.webp': return 'image/webp';
795
- case '.mp4': return 'video/mp4';
796
- case '.mov': return 'video/quicktime';
797
- case '.m4v': return 'video/x-m4v';
798
- case '.webm': return 'video/webm';
799
- case '.html': case '.htm': return 'text/html';
800
657
  case '.pdf': return 'application/pdf';
801
658
  case '.txt': return 'text/plain';
802
659
  case '.md': return 'text/markdown';
@@ -943,410 +800,6 @@ export async function runGroupMembersCommand(args) {
943
800
  return exitFromStatus(res.statusCode);
944
801
  }
945
802
 
946
- export async function runWorkstreamCreateCommand(args) {
947
- const env = requireAgentEnv();
948
- const name = getArg(args, 'name');
949
- if (!name) { console.error('--name is required'); return 2; }
950
- const description = getArg(args, 'description');
951
- const memberArgs = args.member;
952
- const memberAgentIds = Array.isArray(memberArgs)
953
- ? memberArgs
954
- : memberArgs ? [memberArgs] : [];
955
- // Optional charter from stdin when --charter is supplied with no value
956
- let charter = null;
957
- if (args.charter === true) {
958
- charter = await readStdin();
959
- } else if (typeof args.charter === 'string') {
960
- charter = args.charter;
961
- }
962
- const res = await daemonRequest({
963
- method: 'POST',
964
- path: '/agent/workstream/create',
965
- headers: commonHeaders(env),
966
- body: {
967
- name,
968
- description,
969
- charter,
970
- member_agent_ids: memberAgentIds.map((s) => String(s).trim()).filter(Boolean),
971
- },
972
- });
973
- printJson(res.body);
974
- return exitFromStatus(res.statusCode);
975
- }
976
-
977
- export async function runWorkstreamDeleteCommand(args) {
978
- const env = requireAgentEnv();
979
- const target = getArg(args, 'target');
980
- const conversationId = getArg(args, 'conversation-id');
981
- if (!target && !conversationId) {
982
- console.error('--target or --conversation-id is required');
983
- return 2;
984
- }
985
- const res = await daemonRequest({
986
- method: 'POST',
987
- path: '/agent/workstream/delete',
988
- headers: commonHeaders(env),
989
- body: { target, conversation_id: conversationId },
990
- });
991
- printJson(res.body);
992
- return exitFromStatus(res.statusCode);
993
- }
994
-
995
- export async function runWorkstreamListCommand(args) {
996
- const env = requireAgentEnv();
997
- const res = await daemonRequest({
998
- method: 'GET',
999
- path: '/agent/workstream/list',
1000
- headers: commonHeaders(env),
1001
- });
1002
- printJson(res.body);
1003
- return exitFromStatus(res.statusCode);
1004
- }
1005
-
1006
- export async function runAgentListCommand(args) {
1007
- const env = requireAgentEnv();
1008
- const res = await daemonRequest({
1009
- method: 'GET',
1010
- path: '/agent/agent/list',
1011
- headers: commonHeaders(env),
1012
- });
1013
- printJson(res.body);
1014
- return exitFromStatus(res.statusCode);
1015
- }
1016
-
1017
- export async function runAgentCreateCommand(args) {
1018
- const env = requireAgentEnv();
1019
- const name = getArg(args, 'name');
1020
- const runtime = getArg(args, 'runtime');
1021
- if (!name) { console.error('--name is required'); return 2; }
1022
- if (!runtime) { console.error('--runtime is required'); return 2; }
1023
- const res = await daemonRequest({
1024
- method: 'POST',
1025
- path: '/agent/agent/create',
1026
- headers: commonHeaders(env),
1027
- body: {
1028
- name,
1029
- runtime,
1030
- description: getArg(args, 'description'),
1031
- display_name: getArg(args, 'display-name'),
1032
- model: getArg(args, 'model'),
1033
- },
1034
- });
1035
- printJson(res.body);
1036
- return exitFromStatus(res.statusCode);
1037
- }
1038
-
1039
- export async function runAgentDeleteCommand(args) {
1040
- const env = requireAgentEnv();
1041
- const agentId = getArg(args, 'agent-id');
1042
- if (!agentId) { console.error('--agent-id is required'); return 2; }
1043
- const res = await daemonRequest({
1044
- method: 'POST',
1045
- path: '/agent/agent/delete',
1046
- headers: commonHeaders(env),
1047
- body: { agent_id: agentId },
1048
- });
1049
- printJson(res.body);
1050
- return exitFromStatus(res.statusCode);
1051
- }
1052
-
1053
- function readJsonFromFileOrInline(value) {
1054
- if (!value) return null;
1055
- // Accept either a path to a .json file or a raw JSON string.
1056
- try {
1057
- return JSON.parse(value);
1058
- } catch {}
1059
- try {
1060
- const fs = require('node:fs');
1061
- const txt = fs.readFileSync(value, 'utf8');
1062
- return JSON.parse(txt);
1063
- } catch (err) {
1064
- throw new Error(`could not parse JSON from ${value}: ${err?.message || err}`);
1065
- }
1066
- }
1067
-
1068
- export async function runServiceCreateCommand(args) {
1069
- const env = requireAgentEnv();
1070
- const name = getArg(args, 'name');
1071
- if (!name) { console.error('--name is required'); return 2; }
1072
- const description = getArg(args, 'description');
1073
- let contractSchema = null;
1074
- let endpointConfig = null;
1075
- try {
1076
- const contractRaw = getArg(args, 'contract');
1077
- if (contractRaw) contractSchema = readJsonFromFileOrInline(contractRaw);
1078
- const endpointRaw = getArg(args, 'endpoint');
1079
- if (endpointRaw) endpointConfig = readJsonFromFileOrInline(endpointRaw);
1080
- } catch (err) {
1081
- console.error(err.message);
1082
- return 2;
1083
- }
1084
- if (!endpointConfig) { console.error('--endpoint <path-or-json> is required'); return 2; }
1085
- const res = await daemonRequest({
1086
- method: 'POST', path: '/agent/service/create',
1087
- headers: commonHeaders(env),
1088
- body: { name, description, contract_schema: contractSchema, endpoint_config: endpointConfig },
1089
- });
1090
- printJson(res.body);
1091
- return exitFromStatus(res.statusCode);
1092
- }
1093
-
1094
- export async function runServiceUpdateCommand(args) {
1095
- const env = requireAgentEnv();
1096
- const serviceId = getArg(args, 'service-id');
1097
- if (!serviceId) { console.error('--service-id is required'); return 2; }
1098
- const body = { service_id: serviceId };
1099
- if (getArg(args, 'description')) body.description = getArg(args, 'description');
1100
- if (getArg(args, 'contract')) body.contract_schema = readJsonFromFileOrInline(getArg(args, 'contract'));
1101
- if (getArg(args, 'endpoint')) body.endpoint_config = readJsonFromFileOrInline(getArg(args, 'endpoint'));
1102
- if (getArg(args, 'status')) body.status = getArg(args, 'status');
1103
- const res = await daemonRequest({
1104
- method: 'POST', path: '/agent/service/update',
1105
- headers: commonHeaders(env), body,
1106
- });
1107
- printJson(res.body);
1108
- return exitFromStatus(res.statusCode);
1109
- }
1110
-
1111
- export async function runServiceDeleteCommand(args) {
1112
- const env = requireAgentEnv();
1113
- const serviceId = getArg(args, 'service-id');
1114
- if (!serviceId) { console.error('--service-id is required'); return 2; }
1115
- const res = await daemonRequest({
1116
- method: 'POST', path: '/agent/service/delete',
1117
- headers: commonHeaders(env),
1118
- body: { service_id: serviceId },
1119
- });
1120
- printJson(res.body);
1121
- return exitFromStatus(res.statusCode);
1122
- }
1123
-
1124
- export async function runServiceListCommand(args) {
1125
- const env = requireAgentEnv();
1126
- const res = await daemonRequest({
1127
- method: 'GET', path: '/agent/service/list',
1128
- headers: commonHeaders(env),
1129
- });
1130
- printJson(res.body);
1131
- return exitFromStatus(res.statusCode);
1132
- }
1133
-
1134
- export async function runServiceInfoCommand(args) {
1135
- const env = requireAgentEnv();
1136
- const name = getArg(args, 'name');
1137
- if (!name) { console.error('--name is required'); return 2; }
1138
- const params = new URLSearchParams();
1139
- params.set('name', name);
1140
- const res = await daemonRequest({
1141
- method: 'GET', path: `/agent/service/info?${params}`,
1142
- headers: commonHeaders(env),
1143
- });
1144
- printJson(res.body);
1145
- return exitFromStatus(res.statusCode);
1146
- }
1147
-
1148
- export async function runServiceCallCommand(args) {
1149
- const env = requireAgentEnv();
1150
- const name = getArg(args, 'name');
1151
- if (!name) { console.error('--name is required'); return 2; }
1152
- const inputText = await readStdin();
1153
- let input = null;
1154
- if (inputText && inputText.trim().length > 0) {
1155
- try {
1156
- input = JSON.parse(inputText);
1157
- } catch (err) {
1158
- console.error('stdin must be JSON for service call input');
1159
- return 2;
1160
- }
1161
- }
1162
- const res = await daemonRequest({
1163
- method: 'POST', path: '/agent/service/call',
1164
- headers: commonHeaders(env),
1165
- body: { name, input },
1166
- });
1167
- printJson(res.body);
1168
- return exitFromStatus(res.statusCode);
1169
- }
1170
-
1171
- export async function runBriefingGetCommand(args) {
1172
- const env = requireAgentEnv();
1173
- const id = args._[2] || getArg(args, 'id');
1174
- if (!id) { console.error('briefing id required (positional or --id)'); return 2; }
1175
- const params = new URLSearchParams();
1176
- params.set('id', id);
1177
- const res = await daemonRequest({
1178
- method: 'GET',
1179
- path: `/agent/briefing/get?${params}`,
1180
- headers: commonHeaders(env),
1181
- });
1182
- printJson(res.body);
1183
- return exitFromStatus(res.statusCode);
1184
- }
1185
-
1186
- export async function runBriefingPublishCommand(args) {
1187
- const env = requireAgentEnv();
1188
- const textArg = getArg(args, 'text');
1189
- const attachPath = getArg(args, 'attach');
1190
- const mode = String(getArg(args, 'mode') || 'info').trim().toLowerCase();
1191
- if (!textArg) {
1192
- console.error('--text "<short>" is required');
1193
- return 2;
1194
- }
1195
- if (textArg.length > 140) {
1196
- console.error('--text must be ≤140 chars');
1197
- return 2;
1198
- }
1199
- if (!['info', 'approval'].includes(mode)) {
1200
- console.error('--mode must be info or approval');
1201
- return 2;
1202
- }
1203
- let attachmentAssetId = null;
1204
- if (attachPath) {
1205
- const contentType = inferContentType(String(attachPath));
1206
- if (!contentType.startsWith('image/') && !contentType.startsWith('video/') && contentType !== 'text/html') {
1207
- console.error('--attach must be an image, video, or HTML file');
1208
- return 2;
1209
- }
1210
- const upload = await uploadFileViaDaemon(env, String(attachPath));
1211
- if (!upload.ok) {
1212
- console.error(`attachment upload failed for ${attachPath}: ${upload.error}`);
1213
- return 1;
1214
- }
1215
- attachmentAssetId = upload.assetId;
1216
- }
1217
- const res = await daemonRequest({
1218
- method: 'POST',
1219
- path: '/agent/briefing/publish',
1220
- headers: commonHeaders(env),
1221
- body: {
1222
- body_text: textArg,
1223
- attachment_asset_id: attachmentAssetId,
1224
- current_conversation_id: env.currentConversationId,
1225
- response_mode: mode,
1226
- },
1227
- });
1228
- printJson(res.body);
1229
- return exitFromStatus(res.statusCode);
1230
- }
1231
-
1232
- export async function runCredentialRequestCommand(args) {
1233
- const env = requireAgentEnv();
1234
- const name = getArg(args, 'name');
1235
- if (!name) { console.error('--name is required (e.g. GEMINI_API_KEY)'); return 2; }
1236
- const description = getArg(args, 'description');
1237
- const groupTarget = getArg(args, 'group') || getArg(args, 'workstream');
1238
- const res = await daemonRequest({
1239
- method: 'POST',
1240
- path: '/agent/credential/request',
1241
- headers: commonHeaders(env),
1242
- body: {
1243
- name,
1244
- description,
1245
- target: groupTarget,
1246
- },
1247
- });
1248
- printJson(res.body);
1249
- return exitFromStatus(res.statusCode);
1250
- }
1251
-
1252
- export async function runDashboardSetCommand(args) {
1253
- const env = requireAgentEnv();
1254
- const target = getArg(args, 'target');
1255
- const conversationId = getArg(args, 'conversation-id');
1256
- if (!target && !conversationId) {
1257
- console.error('--target or --conversation-id is required');
1258
- return 2;
1259
- }
1260
- // Body is read from stdin as JSON: { data_json?, html_template? }
1261
- const stdinText = await readStdin();
1262
- let payload = {};
1263
- if (stdinText && stdinText.trim().length > 0) {
1264
- try {
1265
- payload = JSON.parse(stdinText);
1266
- } catch (err) {
1267
- console.error('stdin must be JSON: { data_json?, html_template? }');
1268
- return 2;
1269
- }
1270
- }
1271
- const res = await daemonRequest({
1272
- method: 'POST',
1273
- path: '/agent/dashboard/set',
1274
- headers: commonHeaders(env),
1275
- body: {
1276
- target,
1277
- conversation_id: conversationId,
1278
- ...(('data_json' in payload) ? { data_json: payload.data_json } : {}),
1279
- ...(('html_template' in payload) ? { html_template: payload.html_template } : {}),
1280
- },
1281
- });
1282
- printJson(res.body);
1283
- return exitFromStatus(res.statusCode);
1284
- }
1285
-
1286
- export async function runDashboardGetCommand(args) {
1287
- const env = requireAgentEnv();
1288
- const target = getArg(args, 'target');
1289
- const conversationId = getArg(args, 'conversation-id');
1290
- if (!target && !conversationId) {
1291
- console.error('--target or --conversation-id is required');
1292
- return 2;
1293
- }
1294
- const params = new URLSearchParams();
1295
- if (target) params.set('target', target);
1296
- if (conversationId) params.set('conversation_id', conversationId);
1297
- const res = await daemonRequest({
1298
- method: 'GET',
1299
- path: `/agent/dashboard/get?${params}`,
1300
- headers: commonHeaders(env),
1301
- });
1302
- printJson(res.body);
1303
- return exitFromStatus(res.statusCode);
1304
- }
1305
-
1306
- export async function runWorkstreamCharterGetCommand(args) {
1307
- const env = requireAgentEnv();
1308
- const target = getArg(args, 'target');
1309
- const conversationId = getArg(args, 'conversation-id') || env.currentConversationId;
1310
- if (!target && !conversationId) {
1311
- console.error('--target or --conversation-id is required outside a delivered conversation');
1312
- return 2;
1313
- }
1314
- const params = new URLSearchParams();
1315
- if (target) params.set('target', target);
1316
- if (conversationId) params.set('conversation_id', conversationId);
1317
- const res = await daemonRequest({
1318
- method: 'GET',
1319
- path: `/agent/workstream/charter/get?${params}`,
1320
- headers: commonHeaders(env),
1321
- });
1322
- printJson(res.body);
1323
- return exitFromStatus(res.statusCode);
1324
- }
1325
-
1326
- export async function runWorkstreamCharterSetCommand(args) {
1327
- const env = requireAgentEnv();
1328
- const target = getArg(args, 'target');
1329
- const conversationId = getArg(args, 'conversation-id') || env.currentConversationId;
1330
- if (!target && !conversationId) {
1331
- console.error('--target or --conversation-id is required outside a delivered conversation');
1332
- return 2;
1333
- }
1334
- // Body from stdin (heredoc / pipe). Empty input clears the charter.
1335
- const charter = await readStdin();
1336
- const res = await daemonRequest({
1337
- method: 'POST',
1338
- path: '/agent/workstream/charter/set',
1339
- headers: commonHeaders(env),
1340
- body: {
1341
- target,
1342
- conversation_id: conversationId,
1343
- charter: charter ?? '',
1344
- },
1345
- });
1346
- printJson(res.body);
1347
- return exitFromStatus(res.statusCode);
1348
- }
1349
-
1350
803
  export async function runServerInfoCommand(args) {
1351
804
  const env = requireAgentEnv();
1352
805
  const params = new URLSearchParams();
@@ -1362,18 +815,12 @@ export async function runServerInfoCommand(args) {
1362
815
 
1363
816
  export const AGENT_COMMAND_HELP = {
1364
817
  message: `ticlawk message <send|read|check|search|react>
1365
- ticlawk message send --target "<target>" [--phase progress|final] [--seen-up-to-seq N] [--reply-to <msg-id>] [--allow-cross-target] [--attach <file> ...] [--kind <kind>]
818
+ ticlawk message send --target "<target>" [--seen-up-to-seq N] [--reply-to <msg-id>] [--attach <file> ...]
1366
819
  Body is read from stdin (use <<'EOF' ... EOF for multiline).
1367
- During a delivered turn, sending to a different conversation is blocked
1368
- unless --allow-cross-target is passed deliberately.
1369
- --phase progress|final tags whether this is an intermediate update or
1370
- the final visible response after the current work/goal check is done.
1371
- --kind <kind> tags this message via metadata.kind.
1372
820
  Targets:
1373
- dm:<conversation-id> private message by conversation id
1374
- dm:@<user> private message by user/agent handle
1375
- #<group> group conversation by name or id
1376
- #<group>:<msgid> replies under a top-level message in that group
821
+ dm:@<user> private message
822
+ #<group> group conversation
823
+ #<group>:<msgid> thread under a top-level message in that group
1377
824
  --attach <file> uploads a local file and attaches it to the message
1378
825
  (repeatable; max 10 attachments per message).
1379
826
  ticlawk message read --target "<target>" [--around <msg-id>] [--before-seq N] [--limit N]
@@ -1385,11 +832,7 @@ export const AGENT_COMMAND_HELP = {
1385
832
  Use sparingly: prefer acknowledgement/follow-up signals like 👀. Do not
1386
833
  auto-react to every merge, deploy, or task completion with celebratory emoji.
1387
834
  `,
1388
- profile: `ticlawk profile <list|current|use|show|update>
1389
- ticlawk profile list
1390
- ticlawk profile current
1391
- ticlawk profile use ticlawk:<user-id>
1392
-
835
+ profile: `ticlawk profile <show|update>
1393
836
  ticlawk profile show [@handle | --id <agent-id>]
1394
837
  ticlawk profile update [--display-name X] [--description Y] [--avatar-file path]
1395
838
  `,
@@ -1398,7 +841,7 @@ export const AGENT_COMMAND_HELP = {
1398
841
  to a user, use \`ticlawk message send --attach <file>\` instead.
1399
842
  `,
1400
843
  reminder: `ticlawk reminder <schedule|list|snooze|update|cancel|log>
1401
- ticlawk reminder schedule --title <t> (--fire-at <iso> | --in-seconds N | --in-minutes N) (--target "<target>" | --anchor-conversation-id <id>) [--anchor-message-id <id>] [--recur-at HH:MM [--recur-weekday 1,2,3]]
844
+ ticlawk reminder schedule --title <t> (--fire-at <iso> | --in-seconds N | --in-minutes N) (--target "<target>" | --anchor-conversation-id <id>) [--anchor-message-id <id>]
1402
845
  ticlawk reminder list [--status active|fired|canceled]
1403
846
  ticlawk reminder snooze <reminder-id> (--fire-at <iso> | --in-seconds N | --in-minutes N)
1404
847
  ticlawk reminder update <reminder-id> [--title <t>] [--fire-at <iso>]
@@ -1408,149 +851,25 @@ export const AGENT_COMMAND_HELP = {
1408
851
  Use reminders for follow-up that depends on future state you cannot resolve
1409
852
  now. A reminder fires by posting a system message into the anchor
1410
853
  conversation and waking the owner agent via an explicit delivery.
1411
-
1412
- RECURRING: for a fixed cadence (e.g. a daily/weekly meal-time check-in), use
1413
- ONE recurring reminder instead of enumerating many one-shots. Pass --recur-at
1414
- (the owner's local HH:MM) and optionally --recur-weekday (ISO 1=Mon..7=Sun,
1415
- comma-separated; omit for every day). You do NOT set a timezone — the backend
1416
- fills the owner's timezone and computes the fire time in it. On each fire the
1417
- reminder auto-advances to the next occurrence and stays active. Example —
1418
- weekday 18:30 dinner check-in (--fire-at is ignored for recurring, the backend
1419
- computes it):
1420
- ticlawk reminder schedule --title "晚餐" --anchor-conversation-id <id> \\
1421
- --in-minutes 1 --recur-at 18:30 --recur-weekday 1,2,3,4,5
1422
854
  `,
1423
855
  task: `ticlawk task <create|claim|unclaim|update|list>
1424
- ticlawk task create [--conversation <id>|--target "<target>"] [--title <t>] [--assign-agent <agent-id>]
1425
- Body is read from stdin. Creates a brand-new group task. Group admins
1426
- may assign it immediately to an agent member with --assign-agent.
1427
- Conversation defaults to the one this turn is running for; pass
1428
- --conversation <id> (or --target) only to address a different one.
856
+ ticlawk task create --target "<target>" [--title <t>]
857
+ Body is read from stdin. Creates a brand-new message published as a todo
858
+ task. To own it, follow up with \`ticlawk task claim --message-id <id>\`.
1429
859
  ticlawk task claim --message-id <id> [--lease-seconds N]
1430
860
  ticlawk task claim --number <N> --target "<target>" [--lease-seconds N]
1431
861
  ticlawk task unclaim --task-id <id>
1432
862
  ticlawk task update --task-id <id> --status <todo|in_progress|in_review|done|canceled>
1433
- Only a group admin can set status=done. Other agents should set
1434
- in_review and let the group's admin finalize. A group admin can also
1435
- reopen another agent's in_review task to in_progress for redo.
1436
- ticlawk task list [--conversation <id>|--target <target>]
1437
- Scopes to the conversation this turn runs for by default; pass
1438
- --conversation <id> (or --target) to look at a different one. When the
1439
- caller is a group admin and the scope is that group, the list shows the
1440
- full task board, including other agents' in_progress/in_review/done tasks.
1441
- `,
1442
- workstream: `ticlawk workstream <create|delete|list|charter>
1443
- Compatibility alias for group admin commands. Prefer \`ticlawk group ...\`
1444
- for groups and \`ticlawk charter ...\` for conversation charters.
1445
- `,
1446
- goal: `ticlawk goal <report>
1447
- Goal-lane (FSM) control. Normally invoked from a goal-step turn, not by hand.
1448
- ticlawk goal report --transition <id> --outcome <outcome> [--conversation <id>] [--detail <text>] [--current-task <task-id>]
1449
- Report the outcome of the current FSM step and advance the state machine.
1450
- --conversation defaults to the current goal-turn conversation.
1451
- Outcomes by step:
1452
- gap_analysis: gap | no_gap | wait
1453
- execute: task_completed | needs_approval | blocked
1454
- review: accepted | rejected
1455
- (To change a goal, write the charter with \`ticlawk charter set\` — that re-aims the goal lane.)
1456
- `,
1457
- approval: `ticlawk approval <resolve|list>
1458
- Goal-loop approval resolution (chat lane). The goal lane PARKS an approval by
1459
- publishing an approval briefing (\`ticlawk briefing publish --mode approval\`),
1460
- which both asks the owner and suspends the loop on it. The owner answers by
1461
- tapping approve (which posts an "approved" message) or replying in plain
1462
- language — either way the chat lane resolves it here, idempotently.
1463
- ticlawk approval list [--conversation <id> | --target "<target>"]
1464
- List pending approval requests in the conversation. Use this from the chat
1465
- lane to find which request an owner's natural-language approval refers to.
1466
- --conversation defaults to the current conversation.
1467
- ticlawk approval resolve --request <id> (--grant | --reject | --decision granted|rejected) [--original-text <text>] [--confidence <0-1>] [--source-message-id <id>]
1468
- Resolve a pending approval from a natural-language chat decision (source=chat).
1469
- Idempotent: a second resolve (or a button tap) on the same request is a no-op.
1470
- `,
1471
- charter: `ticlawk charter <get|set> [--target "<target>" | --conversation-id <id>]
1472
- ticlawk charter get [--target "<target>" | --conversation-id <id>]
1473
- Print the conversation charter. DM/group members can read.
1474
- ticlawk charter set [--target "<target>" | --conversation-id <id>] # body via stdin
1475
- Replace the conversation charter. DM member agents can write their DM
1476
- charter; group writes require group admin/owner. Empty stdin clears it.
1477
- During a delivered turn, omitting target/conversation-id uses the
1478
- current conversation.
1479
- `,
1480
- service: `ticlawk service <create|update|delete|list|info|call>
1481
- service create --name X --endpoint <path-or-json> [--description Y] [--contract <path-or-json>]
1482
- Any agent. Register a callable service. --endpoint takes either a
1483
- .json file path or a JSON string; same for --contract.
1484
- endpoint_config shape: { url, method?, headers? }.
1485
- service update --service-id <id> [--description Y] [--contract <json>] [--endpoint <json>] [--status <active|down|archived>]
1486
- Any agent.
1487
- service delete --service-id <id>
1488
- Any agent. Soft-archives.
1489
- service list
1490
- Any agent. Returns active services (no endpoint_config).
1491
- service info --name X
1492
- Any agent. Returns contract_schema + description.
1493
- service call --name X # input from stdin (JSON)
1494
- Any agent. Backend proxies to endpoint_config.url. No retry.
1495
- `,
1496
- briefing: `ticlawk briefing <publish|get>
1497
- briefing publish --text "..." [--mode info|approval] [--attach <image|video|html path>]
1498
- Publish a briefing to the owner's Briefings. Allowed from DMs, or
1499
- from groups where this agent is admin/owner.
1500
- --text short plain text (≤140 chars)
1501
- --mode owner response mode: info shows ack; approval shows approve
1502
- --attach optional image, video, or HTML file when visual context matters
1503
- Briefings are independent of chat — they do NOT appear in any DM
1504
- message stream. Use this verb (not \`message send\`) for any status
1505
- surface the owner consumes in Briefings.
1506
- briefing get <id>
1507
- Fetch a briefing including text and attachment metadata. Use this when a
1508
- quote (metadata.quote.kind=briefing) points at a briefing whose
1509
- full body you want to read.
1510
- `,
1511
- credential: `ticlawk credential request --name <ENV_VAR> [--description Y] [--group "#<group>"]
1512
- Any agent. Pre-allocate a credential slot. Response includes a deep
1513
- link the user opens in the mobile app (Settings → Credentials)
1514
- to fill the value. Once filled, the daemon syncs it locally and
1515
- injects it as an env var when spawning agents.
1516
- `,
1517
- dashboard: `ticlawk dashboard <set|get> (--target "<target>" | --conversation-id <id>)
1518
- dashboard set (--target "<target>" | --conversation-id <id>) # body via stdin (JSON)
1519
- Allowed from DMs, or from groups where this agent is admin/owner.
1520
- stdin = { "data_json": ..., "html_template": "..." }.
1521
- Either field may be omitted (leaves unchanged) or null (clears).
1522
- dashboard get (--target "<target>" | --conversation-id <id>)
1523
- Conversation members can read.
1524
- `,
1525
- agent: `ticlawk agent <list|create|delete>
1526
- agent list
1527
- Any agent. List all non-archived agents owned by the user, including
1528
- descriptions, runtime/status, and group memberships.
1529
- agent create --name X --runtime <claude_code|codex|opencode|openclaw|pi> [--description Y] [--display-name N] [--model M]
1530
- Any agent. Pre-allocate an agent slot (status='unpaired'). User
1531
- later pairs a runtime to fill it.
1532
- agent delete --agent-id <id>
1533
- Owner-only. Agent-facing deletion is disabled.
863
+ ticlawk task list [--target <target>]
1534
864
  `,
1535
- group: `ticlawk group <create|list|delete|charter|members>
865
+ group: `ticlawk group <create|members>
1536
866
  ticlawk group create --name <n> [--description <d>] [--member <agent-id> ...]
1537
- Agent self-creates a new group. The agent is added as admin; the
867
+ Agent self-creates a new group. The agent is added as a member; the
1538
868
  conversation owner is set to the user that owns this agent. Other
1539
- member agents join as regular members and must belong to the same user
1540
- (RLS enforces).
1541
- ticlawk group list
1542
- List group conversations the caller belongs to, including agent members,
1543
- descriptions, charters, and dashboard presence.
1544
- ticlawk group delete --target "#<group>"
1545
- Group-admin only. Hard-delete the group (messages, members, deliveries).
1546
- ticlawk group charter get (--target "#<group>" | --conversation-id <id>)
1547
- Compatibility alias for \`ticlawk charter get\` on groups.
1548
- ticlawk group charter set (--target "#<group>" | --conversation-id <id>) # body via stdin
1549
- Compatibility alias for \`ticlawk charter set\` on groups. Group-admin only.
1550
- ticlawk group members (--target "<target>" | --conversation-id <id>)
1551
- ticlawk group members (--target "<target>" | --conversation-id <id>) --add <agent-id> [--add <agent-id> ...]
1552
- ticlawk group members (--target "<target>" | --conversation-id <id>) --remove <agent-id>
1553
- Listing requires membership; add/remove requires group admin.
869
+ member agents must belong to the same user (RLS enforces).
870
+ ticlawk group members --target "<target>"
871
+ ticlawk group members --target "<target>" --add <agent-id> [--add <agent-id> ...]
872
+ ticlawk group members --target "<target>" --remove <agent-id>
1554
873
  `,
1555
874
  server: `ticlawk server info [--refresh]
1556
875
  `,