ticlawk 0.1.16-dev.14 → 0.1.16-dev.15

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
@@ -43,6 +43,7 @@ import {
43
43
  runDashboardGetCommand,
44
44
  runCredentialRequestCommand,
45
45
  runBriefingPublishCommand,
46
+ runBriefingGetCommand,
46
47
  runServiceCreateCommand,
47
48
  runServiceUpdateCommand,
48
49
  runServiceDeleteCommand,
@@ -549,6 +550,10 @@ async function main() {
549
550
  process.exitCode = await runBriefingPublishCommand(args);
550
551
  return;
551
552
  }
553
+ if (sub === 'get') {
554
+ process.exitCode = await runBriefingGetCommand(args);
555
+ return;
556
+ }
552
557
  console.error(`unknown briefing subcommand: ${sub}`);
553
558
  process.exit(1);
554
559
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ticlawk",
3
- "version": "0.1.16-dev.14",
3
+ "version": "0.1.16-dev.15",
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",
@@ -612,6 +612,12 @@ export async function callService({ actingAgentId, name, input }) {
612
612
 
613
613
  // ── Briefings (CoS publish) ──
614
614
 
615
+ export async function getBriefing({actingAgentId, briefingId}) {
616
+ const params = new URLSearchParams();
617
+ params.set('acting_as_agent_id', actingAgentId);
618
+ return apiFetch(`/api/agent/briefings/${encodeURIComponent(briefingId)}?${params}`);
619
+ }
620
+
615
621
  export async function publishBriefing({actingAgentId, bodyText, bodyHtml}) {
616
622
  const body = { acting_as_agent_id: actingAgentId };
617
623
  if (bodyText != null) body.body_text = bodyText;
@@ -180,12 +180,39 @@ function buildCosAddendum(msg) {
180
180
  return COS_ADDENDUM;
181
181
  }
182
182
 
183
+ // Quote block: surfaced just above the user's reply so the agent sees
184
+ // what artifact the reply is *about*. Source of truth is
185
+ // `messages.metadata.quote = { kind, ref, snippet }`. We render a
186
+ // short, prefix-cache-friendly block and tell the agent how to fetch
187
+ // the full content if needed.
188
+ function buildQuoteBlock(msg) {
189
+ const meta = msg.message_metadata || msg.metadata || null;
190
+ const quote = meta && typeof meta === 'object' ? meta.quote : null;
191
+ if (!quote || typeof quote !== 'object') return '';
192
+ const kind = String(quote.kind || '').trim();
193
+ const ref = String(quote.ref || '').trim();
194
+ const snippet = String(quote.snippet || '').trim();
195
+ if (!kind || !ref) return '';
196
+ const fetchHint = kind === 'briefing'
197
+ ? `ticlawk briefing get ${ref}`
198
+ : kind === 'dashboard'
199
+ ? `ticlawk dashboard get --target "#${ref}"`
200
+ : kind === 'message'
201
+ ? `ticlawk message read --around ${ref}`
202
+ : '';
203
+ const lines = ['[quote', ` kind=${kind} ref=${ref}`];
204
+ if (snippet) lines.push(` "${snippet.replace(/"/g, '\\"')}"`);
205
+ if (fetchHint) lines.push(` fetch: ${fetchHint}`);
206
+ lines.push('[/quote]');
207
+ return lines.join('\n');
208
+ }
209
+
183
210
  // Wrap each per-turn message with an explicit reply instruction so the
184
211
  // runtime LLM never has to remember the standing prompt to figure out
185
212
  // HOW to reply. Codex in particular treats the developerInstructions as
186
213
  // background and ignores the chat-send pattern without this per-turn
187
214
  // nudge.
188
- function buildWakePromptText({ envelopeHeader, target, rawText, groupContext, charterBlock, cosAddendum }) {
215
+ function buildWakePromptText({ envelopeHeader, target, rawText, groupContext, charterBlock, cosAddendum, quoteBlock }) {
189
216
  const body = `${envelopeHeader} ${rawText || ''}`.trim();
190
217
  const lines = [];
191
218
  if (charterBlock) {
@@ -194,6 +221,9 @@ function buildWakePromptText({ envelopeHeader, target, rawText, groupContext, ch
194
221
  if (cosAddendum) {
195
222
  lines.push(cosAddendum, '');
196
223
  }
224
+ if (quoteBlock) {
225
+ lines.push(quoteBlock, '');
226
+ }
197
227
  lines.push('New message received:', '', body);
198
228
  if (groupContext) {
199
229
  lines.push('', groupContext);
@@ -230,8 +260,9 @@ export function normalizeInboundMessage(msg) {
230
260
  const groupContext = buildGroupContextBlock(enriched);
231
261
  const charterBlock = buildCharterBlock(enriched);
232
262
  const cosAddendum = buildCosAddendum(enriched);
263
+ const quoteBlock = buildQuoteBlock(enriched);
233
264
  const text = header
234
- ? buildWakePromptText({ envelopeHeader: header, target, rawText, groupContext, charterBlock, cosAddendum })
265
+ ? buildWakePromptText({ envelopeHeader: header, target, rawText, groupContext, charterBlock, cosAddendum, quoteBlock })
235
266
  : rawText;
236
267
  return {
237
268
  bindingId: recipientAgentId,
@@ -1022,6 +1022,21 @@ export async function runServiceCallCommand(args) {
1022
1022
  return exitFromStatus(res.statusCode);
1023
1023
  }
1024
1024
 
1025
+ export async function runBriefingGetCommand(args) {
1026
+ const env = requireAgentEnv();
1027
+ const id = args._[2] || getArg(args, 'id');
1028
+ if (!id) { console.error('briefing id required (positional or --id)'); return 2; }
1029
+ const params = new URLSearchParams();
1030
+ params.set('id', id);
1031
+ const res = await daemonRequest({
1032
+ method: 'GET',
1033
+ path: `/agent/briefing/get?${params}`,
1034
+ headers: commonHeaders(env),
1035
+ });
1036
+ printJson(res.body);
1037
+ return exitFromStatus(res.statusCode);
1038
+ }
1039
+
1025
1040
  export async function runBriefingPublishCommand(args) {
1026
1041
  const env = requireAgentEnv();
1027
1042
  const textArg = getArg(args, 'text');
@@ -1272,13 +1287,18 @@ export const AGENT_COMMAND_HELP = {
1272
1287
  service call --name X # input from stdin (JSON)
1273
1288
  Any agent. Backend proxies to endpoint_config.url. No retry.
1274
1289
  `,
1275
- briefing: `ticlawk briefing publish (--text "..." | --html <path>)
1290
+ briefing: `ticlawk briefing <publish|get>
1291
+ briefing publish (--text "..." | --html <path>)
1276
1292
  CoS-only. Publish a briefing to the owner's Office → Briefings.
1277
1293
  --text short plain text (≤100 chars), use for one-liner pings
1278
1294
  --html path to an HTML file; body is rendered as a full-screen card
1279
1295
  Briefings are independent of chat — they do NOT appear in the CoS
1280
1296
  DM message stream. Use this verb (not \`message send\`) for any
1281
1297
  status surface the owner consumes in Office.
1298
+ briefing get <id>
1299
+ Fetch a briefing including body_text/body_html. Use this when a
1300
+ quote (metadata.quote.kind=briefing) points at a briefing whose
1301
+ full body you want to read.
1282
1302
  `,
1283
1303
  credential: `ticlawk credential request --name <ENV_VAR> [--description Y] [--workstream "#<ws>"]
1284
1304
  CoS-only. Pre-allocate a credential slot. Response includes a deep
@@ -908,6 +908,20 @@ export async function handleServiceCall(req, body, ctx) {
908
908
  }
909
909
  }
910
910
 
911
+ export async function handleBriefingGet(req, query, ctx) {
912
+ const actingAgentId = getActingAgentId(req, query);
913
+ const v = validateActingAgent(actingAgentId, ctx);
914
+ if (!v.ok) return { status: v.status, body: { error: v.error } };
915
+ const briefingId = String(query?.id || '').trim();
916
+ if (!briefingId) return { status: 400, body: { error: 'id is required' } };
917
+ try {
918
+ const data = await api.getBriefing({ actingAgentId, briefingId });
919
+ return { status: 200, body: data };
920
+ } catch (err) {
921
+ return { status: err?.status || 500, body: { error: err?.message || 'briefing get failed' } };
922
+ }
923
+ }
924
+
911
925
  export async function handleBriefingPublish(req, body, ctx) {
912
926
  const actingAgentId = getActingAgentId(req, body);
913
927
  const v = validateActingAgent(actingAgentId, ctx);
package/src/core/http.mjs CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  handleWorkstreamDashboardGet,
15
15
  handleCredentialRequest,
16
16
  handleBriefingPublish,
17
+ handleBriefingGet,
17
18
  handleServiceCreate,
18
19
  handleServiceUpdate,
19
20
  handleServiceDelete,
@@ -294,6 +295,10 @@ export function startLocalHttpServer({ port, adapter, ctx }) {
294
295
  const r = await handleBriefingPublish(req, body, cliCtx);
295
296
  return writeJson(res, r.status, r.body);
296
297
  }
298
+ if (urlNoQuery === '/agent/briefing/get' && method === 'GET') {
299
+ const r = await handleBriefingGet(req, parseQuery(req.url || ''), cliCtx);
300
+ return writeJson(res, r.status, r.body);
301
+ }
297
302
  if (urlNoQuery === '/agent/credential/request' && method === 'POST') {
298
303
  const body = await readJsonBody(req);
299
304
  if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
@@ -22,6 +22,7 @@ to users or groups.
22
22
  - Always claim a task via \`ticlawk task claim\` before doing any substantive work on it. If the claim fails, stop immediately and pick a different task.
23
23
  - Use only the provided \`ticlawk\` CLI commands for messaging.
24
24
  - If the turn opens with a \`[charter] ... [/charter]\` block, that is the workstream's binding spec (规章制度). Every action you take in this conversation must conform to it. If a request conflicts with the charter, stop and surface the conflict to the Chief of Staff (CoS) — don't silently route around it.
25
+ - If the turn opens with a \`[quote ... [/quote]\` block, the user is replying with a quoted artifact in context. The block carries \`kind=<message|briefing|dashboard>\`, \`ref=<id>\`, a snippet, and a \`fetch:\` command to read the full content if you need it. Treat the user's text as a response *to* that artifact.
25
26
  - Shared services are CoS-published tools you can invoke. \`ticlawk service list\` shows available names + descriptions; \`ticlawk service info --name X\` shows the input contract; \`ticlawk service call --name X\` runs it with stdin JSON input. There is no retry — call failure means stop and report; do not loop.
26
27
 
27
28
  ## Startup checklist (every turn)