ticlawk 0.1.16-dev.13 → 0.1.16-dev.14

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.
package/bin/ticlawk.mjs CHANGED
@@ -42,6 +42,7 @@ import {
42
42
  runDashboardSetCommand,
43
43
  runDashboardGetCommand,
44
44
  runCredentialRequestCommand,
45
+ runBriefingPublishCommand,
45
46
  runServiceCreateCommand,
46
47
  runServiceUpdateCommand,
47
48
  runServiceDeleteCommand,
@@ -538,6 +539,20 @@ async function main() {
538
539
  process.exit(1);
539
540
  }
540
541
 
542
+ if (command === 'briefing') {
543
+ const sub = args._[1];
544
+ if (args.help || args.h || !sub) {
545
+ console.log(AGENT_COMMAND_HELP.briefing);
546
+ return;
547
+ }
548
+ if (sub === 'publish') {
549
+ process.exitCode = await runBriefingPublishCommand(args);
550
+ return;
551
+ }
552
+ console.error(`unknown briefing subcommand: ${sub}`);
553
+ process.exit(1);
554
+ }
555
+
541
556
  if (command === 'credential') {
542
557
  const sub = args._[1];
543
558
  if (args.help || args.h || !sub) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ticlawk",
3
- "version": "0.1.16-dev.13",
3
+ "version": "0.1.16-dev.14",
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",
@@ -610,6 +610,18 @@ export async function callService({ actingAgentId, name, input }) {
610
610
  );
611
611
  }
612
612
 
613
+ // ── Briefings (CoS publish) ──
614
+
615
+ export async function publishBriefing({actingAgentId, bodyText, bodyHtml}) {
616
+ const body = { acting_as_agent_id: actingAgentId };
617
+ if (bodyText != null) body.body_text = bodyText;
618
+ if (bodyHtml != null) body.body_html = bodyHtml;
619
+ return apiFetch('/api/agent/briefings', {
620
+ method: 'POST',
621
+ body: JSON.stringify(body),
622
+ });
623
+ }
624
+
613
625
  // ── Credentials (CoS slot creation) ──
614
626
 
615
627
  export async function requestCredential({
@@ -167,11 +167,11 @@ const COS_ADDENDUM = [
167
167
  '',
168
168
  'Owner model: hold the user\'s preferences and constraints across workstreams. Apply them consistently. Do not "for-your-own-good" around stated preferences.',
169
169
  '',
170
- '奏折 / report posture: publish status to the user by sending a message into the CoS DM with `ticlawk message send --target dm:@<owner> --kind report` (stdin = the report body). Use this for scheduled summaries, escalations, or workstream check-ins. Reports surface in the Office → 奏折 sub-tab; chat replies do not. Do not over-spam reports — one per meaningful state change.',
170
+ 'Briefing posture: publish briefings via `ticlawk briefing publish` this is a SEPARATE surface from chat. Two content formats: `--text "<≤100 chars>"` for short pings, or `--html <path/to/file.html>` for rich one-page bodies. Briefings do NOT appear in the chat stream they only render in Office → Briefings as full-screen cards owner steps through. Do not over-spam — one per meaningful state change. When the owner taps "comment" on a briefing, the inbound reply message carries metadata.context_ref = { kind: "briefing", briefing_id }; thread the conversation from there.',
171
171
  '',
172
- 'Dashboard posture: maintain a per-workstream dashboard via `ticlawk dashboard set --target "#<ws>"` (stdin JSON: { data_json, html_template }). data_json holds the live numbers; html_template is the hand-written rendering. CoS replaces wholesale — there is no partial update protocol, and there is no schema lock-in on data_json. Use the dashboard for at-a-glance state; use 奏折 for narrative.',
172
+ 'Dashboard posture: maintain a per-workstream dashboard via `ticlawk dashboard set --target "#<ws>"` (stdin JSON: { data_json, html_template }). data_json holds the live numbers; html_template is the hand-written rendering. CoS replaces wholesale — there is no partial update protocol, and there is no schema lock-in on data_json. Use the dashboard for at-a-glance state; use Briefings for narrative. When the owner replies to a dashboard via the Office "聊一下" path, the inbound message carries metadata.context_ref pointing at the workstream; thread the conversation from there.',
173
173
  '',
174
- 'Cadence: schedule your own daily 晨报 via `ticlawk reminder schedule --title "晨报" --in-minutes 1440 --anchor-conversation-id <your DM with owner>` after you handle this turn. The reminder fires by waking you with a system message; treat that wake as the trigger to compile the day\'s 奏折 across workstreams. Re-schedule on each fire so the cadence persists. Skip a day only if you are explicitly told to.',
174
+ 'Cadence: schedule your own daily morning briefing via `ticlawk reminder schedule --title "morning briefing" --in-minutes 1440 --anchor-conversation-id <your DM with owner>` after you handle this turn. The reminder fires by waking you with a system message; treat that wake as the trigger to compile the day\'s briefings across workstreams. Re-schedule on each fire so the cadence persists. Skip a day only if you are explicitly told to.',
175
175
  '[/cos-role]',
176
176
  ].join('\n');
177
177
 
@@ -161,7 +161,7 @@ export async function runMessageSendCommand(args) {
161
161
  }
162
162
 
163
163
  // Optional --kind <s> classifies the message via metadata.kind. The
164
- // canonical user-facing value is 'report' (奏折 surface). Anything else
164
+ // canonical user-facing value is 'briefing' (Office Briefings surface). Anything else
165
165
  // is passed through as-is so we don't have to update this list to add
166
166
  // new conventions.
167
167
  const kind = getArg(args, 'kind');
@@ -1022,6 +1022,41 @@ export async function runServiceCallCommand(args) {
1022
1022
  return exitFromStatus(res.statusCode);
1023
1023
  }
1024
1024
 
1025
+ export async function runBriefingPublishCommand(args) {
1026
+ const env = requireAgentEnv();
1027
+ const textArg = getArg(args, 'text');
1028
+ const htmlPath = getArg(args, 'html');
1029
+ if ((textArg && htmlPath) || (!textArg && !htmlPath)) {
1030
+ console.error('exactly one of --text "<short>" or --html <path> is required');
1031
+ return 2;
1032
+ }
1033
+ let bodyText = null;
1034
+ let bodyHtml = null;
1035
+ if (textArg) {
1036
+ if (textArg.length > 100) {
1037
+ console.error('--text must be ≤100 chars');
1038
+ return 2;
1039
+ }
1040
+ bodyText = textArg;
1041
+ } else {
1042
+ try {
1043
+ const fs = await import('node:fs');
1044
+ bodyHtml = fs.readFileSync(String(htmlPath), 'utf8');
1045
+ } catch (err) {
1046
+ console.error(`could not read --html file: ${err?.message || err}`);
1047
+ return 2;
1048
+ }
1049
+ }
1050
+ const res = await daemonRequest({
1051
+ method: 'POST',
1052
+ path: '/agent/briefing/publish',
1053
+ headers: commonHeaders(env),
1054
+ body: { body_text: bodyText, body_html: bodyHtml },
1055
+ });
1056
+ printJson(res.body);
1057
+ return exitFromStatus(res.statusCode);
1058
+ }
1059
+
1025
1060
  export async function runCredentialRequestCommand(args) {
1026
1061
  const env = requireAgentEnv();
1027
1062
  const name = getArg(args, 'name');
@@ -1158,7 +1193,7 @@ export const AGENT_COMMAND_HELP = {
1158
1193
  ticlawk message send --target "<target>" [--seen-up-to-seq N] [--reply-to <msg-id>] [--attach <file> ...] [--kind <kind>]
1159
1194
  Body is read from stdin (use <<'EOF' ... EOF for multiline).
1160
1195
  --kind <kind> tags this message via metadata.kind. The CoS uses
1161
- --kind report to publish a 奏折 / status report that the Office
1196
+ --kind briefing to publish a status briefing that the Office
1162
1197
  tab can list separately.
1163
1198
  Targets:
1164
1199
  dm:@<user> private message
@@ -1236,6 +1271,14 @@ export const AGENT_COMMAND_HELP = {
1236
1271
  Any agent. Returns contract_schema + description.
1237
1272
  service call --name X # input from stdin (JSON)
1238
1273
  Any agent. Backend proxies to endpoint_config.url. No retry.
1274
+ `,
1275
+ briefing: `ticlawk briefing publish (--text "..." | --html <path>)
1276
+ CoS-only. Publish a briefing to the owner's Office → Briefings.
1277
+ --text short plain text (≤100 chars), use for one-liner pings
1278
+ --html path to an HTML file; body is rendered as a full-screen card
1279
+ Briefings are independent of chat — they do NOT appear in the CoS
1280
+ DM message stream. Use this verb (not \`message send\`) for any
1281
+ status surface the owner consumes in Office.
1239
1282
  `,
1240
1283
  credential: `ticlawk credential request --name <ENV_VAR> [--description Y] [--workstream "#<ws>"]
1241
1284
  CoS-only. Pre-allocate a credential slot. Response includes a deep
@@ -908,6 +908,26 @@ export async function handleServiceCall(req, body, ctx) {
908
908
  }
909
909
  }
910
910
 
911
+ export async function handleBriefingPublish(req, body, ctx) {
912
+ const actingAgentId = getActingAgentId(req, body);
913
+ const v = validateActingAgent(actingAgentId, ctx);
914
+ if (!v.ok) return { status: v.status, body: { error: v.error } };
915
+ const bodyText = typeof body?.body_text === 'string' && body.body_text.trim() ? body.body_text : null;
916
+ const bodyHtml = typeof body?.body_html === 'string' && body.body_html.trim() ? body.body_html : null;
917
+ if ((bodyText && bodyHtml) || (!bodyText && !bodyHtml)) {
918
+ return { status: 400, body: { error: 'exactly one of body_text or body_html is required' } };
919
+ }
920
+ if (bodyText && bodyText.length > 100) {
921
+ return { status: 400, body: { error: 'body_text must be ≤100 chars' } };
922
+ }
923
+ try {
924
+ const data = await api.publishBriefing({ actingAgentId, bodyText, bodyHtml });
925
+ return { status: 200, body: data };
926
+ } catch (err) {
927
+ return { status: err?.status || 500, body: { error: err?.message || 'briefing publish failed' } };
928
+ }
929
+ }
930
+
911
931
  export async function handleCredentialRequest(req, body, ctx) {
912
932
  const actingAgentId = getActingAgentId(req, body);
913
933
  const v = validateActingAgent(actingAgentId, ctx);
package/src/core/http.mjs CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  handleWorkstreamDashboardSet,
14
14
  handleWorkstreamDashboardGet,
15
15
  handleCredentialRequest,
16
+ handleBriefingPublish,
16
17
  handleServiceCreate,
17
18
  handleServiceUpdate,
18
19
  handleServiceDelete,
@@ -287,6 +288,12 @@ export function startLocalHttpServer({ port, adapter, ctx }) {
287
288
  const r = await handleWorkstreamDashboardGet(req, parseQuery(req.url || ''), cliCtx);
288
289
  return writeJson(res, r.status, r.body);
289
290
  }
291
+ if (urlNoQuery === '/agent/briefing/publish' && method === 'POST') {
292
+ const body = await readJsonBody(req);
293
+ if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
294
+ const r = await handleBriefingPublish(req, body, cliCtx);
295
+ return writeJson(res, r.status, r.body);
296
+ }
290
297
  if (urlNoQuery === '/agent/credential/request' && method === 'POST') {
291
298
  const body = await readJsonBody(req);
292
299
  if (body === null) return writeJson(res, 400, { error: 'invalid json body' });