makaron-cli 0.7.9 → 0.7.10

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/README.md CHANGED
@@ -136,11 +136,13 @@ Outputs one JSON per line as artifacts appear:
136
136
 
137
137
  ### Dialogue events for external Agents
138
138
 
139
- Use this when another Agent needs to read what Makaron said, detect approval requirements, and relay artifacts without inventing customer-service wording.
139
+ Use this when another Agent needs to read what Makaron said, handle text checkpoints, and relay artifacts without inventing customer-service wording.
140
140
 
141
141
  ```bash
142
142
  npx makaron-cli responses events <runId> --jsonl
143
143
  # alias: npx makaron-cli responses timeline <runId> --jsonl
144
+ # compact view: npx makaron-cli responses timeline <runId> --jsonl --compact
145
+ # pure Q&A view: npx makaron-cli responses timeline <runId> --jsonl --checkpoint-mode off
144
146
  ```
145
147
 
146
148
  Events use only three factual types:
@@ -151,7 +153,7 @@ Events use only three factual types:
151
153
  {"type":"artifact","kind":"image","status":"completed","url":"https://..."}
152
154
  ```
153
155
 
154
- If a message has `requires_approval: true`, the external Agent must record an approval before continuing to wait for artifacts or claiming completion:
156
+ Makaron uses a conservative checkpoint rule for creative/service Agents: if a run stops with substantive text, has no image/video/music/design artifact, and has no continuing execution action such as a tool call, the message is marked `requires_approval: true`. Pure status text such as queued, rendering, uploading, or completed is not treated as a checkpoint. Pure Q&A flows should pass `--checkpoint-mode off`. If a message has `requires_approval: true`, the external Agent must record an approval before continuing to wait for artifacts or claiming completion:
155
157
 
156
158
  ```bash
157
159
  npx makaron-cli responses approve msg_1 --run <runId> --note "Proceed."
@@ -163,10 +165,12 @@ npx makaron-cli responses continue msg_1 --run <runId>
163
165
  Important: approvals are a local Agent gate in v0. They are recorded by the CLI so wrappers and Skills can fail fast, but they do not pause or resume the remote Makaron runtime yet. Wrappers can enforce the local gate with:
164
166
 
165
167
  ```bash
166
- npx makaron-cli responses events <runId> --jsonl
167
- # alias: npx makaron-cli responses timeline <runId> --jsonl --fail-on-unapproved
168
+ npx makaron-cli responses events <runId> --jsonl --checkpoint-mode service --fail-on-unapproved
169
+ # alias: npx makaron-cli responses timeline <runId> --jsonl --compact --fail-on-unapproved
168
170
  ```
169
171
 
172
+ Use `--compact` when relaying to another Agent or chat system; it merges consecutive Makaron content chunks into a single readable message.
173
+
170
174
  ### Extract specific results
171
175
 
172
176
  ```bash
@@ -296,6 +300,33 @@ type MakaronOutput =
296
300
  | Motion design | "create an Instagram story with animated text" |
297
301
  | Multi-step | "edit the photo then make a video from it" |
298
302
 
303
+ ## Minimal Agent Wrapper
304
+
305
+ A new Agent can use this minimal flow:
306
+
307
+ ```bash
308
+ RUN_JSON=$(npx makaron-cli chat --project auto --json -b "$USER_PROMPT")
309
+ RUN_ID=$(echo "$RUN_JSON" | jq -r .runId)
310
+ PROJECT_URL=$(echo "$RUN_JSON" | jq -r .projectUrl)
311
+ send_message "Project created: $PROJECT_URL"
312
+
313
+ if ! npx makaron-cli responses timeline "$RUN_ID" --jsonl --compact --checkpoint-mode service --fail-on-unapproved > /tmp/makaron-events.jsonl; then
314
+ npx makaron-cli responses timeline "$RUN_ID" --jsonl --compact | while read -r event; do
315
+ TYPE=$(echo "$event" | jq -r .type)
316
+ REQUIRES=$(echo "$event" | jq -r ".requires_approval // false")
317
+ TEXT=$(echo "$event" | jq -r ".text // empty")
318
+ MSG_ID=$(echo "$event" | jq -r ".id // empty")
319
+ if [ "$TYPE" = "message" ] && [ "$REQUIRES" = "true" ]; then
320
+ send_message "$TEXT"
321
+ npx makaron-cli responses ask-user "$MSG_ID" --run "$RUN_ID"
322
+ exit 3
323
+ fi
324
+ done
325
+ fi
326
+
327
+ RESULT=$(npx makaron-cli responses get "$RUN_ID" --wait --json)
328
+ ```
329
+
299
330
  ## Recommended Pattern: Service Flow (Feishu/OpenClaw/Group Chat)
300
331
 
301
332
  When serving end-users in a chat environment (Feishu, Slack, Discord), use this proactive message pattern:
package/SKILL.md CHANGED
@@ -128,11 +128,13 @@ Outputs one JSON per line as artifacts appear:
128
128
 
129
129
  ### Dialogue events for external Agents
130
130
 
131
- Use this when another Agent needs to read what Makaron said, detect approval requirements, and relay artifacts without inventing customer-service wording.
131
+ Use this when another Agent needs to read what Makaron said, handle text checkpoints, and relay artifacts without inventing customer-service wording.
132
132
 
133
133
  ```bash
134
134
  npx makaron-cli responses events <runId> --jsonl
135
135
  # alias: npx makaron-cli responses timeline <runId> --jsonl
136
+ # compact view: npx makaron-cli responses timeline <runId> --jsonl --compact
137
+ # pure Q&A view: npx makaron-cli responses timeline <runId> --jsonl --checkpoint-mode off
136
138
  ```
137
139
 
138
140
  Events use only three factual types:
@@ -143,7 +145,7 @@ Events use only three factual types:
143
145
  {"type":"artifact","kind":"image","status":"completed","url":"https://..."}
144
146
  ```
145
147
 
146
- If a message has `requires_approval: true`, the external Agent must record an approval before continuing to wait for artifacts or claiming completion:
148
+ Makaron uses a conservative checkpoint rule for creative/service Agents: if a run stops with substantive text, has no image/video/music/design artifact, and has no continuing execution action such as a tool call, the message is marked `requires_approval: true`. Pure status text such as queued, rendering, uploading, or completed is not treated as a checkpoint. Pure Q&A flows should pass `--checkpoint-mode off`. If a message has `requires_approval: true`, the external Agent must record an approval before continuing to wait for artifacts or claiming completion:
147
149
 
148
150
  ```bash
149
151
  npx makaron-cli responses approve msg_1 --run <runId> --note "Proceed."
@@ -155,10 +157,12 @@ npx makaron-cli responses continue msg_1 --run <runId>
155
157
  Important: approvals are a local Agent gate in v0. They are recorded by the CLI so wrappers and Skills can fail fast, but they do not pause or resume the remote Makaron runtime yet. Wrappers can enforce the local gate with:
156
158
 
157
159
  ```bash
158
- npx makaron-cli responses events <runId> --jsonl
159
- # alias: npx makaron-cli responses timeline <runId> --jsonl --fail-on-unapproved
160
+ npx makaron-cli responses events <runId> --jsonl --checkpoint-mode service --fail-on-unapproved
161
+ # alias: npx makaron-cli responses timeline <runId> --jsonl --compact --fail-on-unapproved
160
162
  ```
161
163
 
164
+ Use `--compact` when relaying to another Agent or chat system; it merges consecutive Makaron content chunks into a single readable message.
165
+
162
166
  ### Extract specific results
163
167
 
164
168
  ```bash
@@ -285,6 +289,33 @@ type MakaronOutput =
285
289
  | Motion design | "create an Instagram story with animated text" |
286
290
  | Multi-step | "edit the photo then make a video from it" |
287
291
 
292
+ ## Minimal Agent Wrapper
293
+
294
+ A new Agent can use this minimal flow:
295
+
296
+ ```bash
297
+ RUN_JSON=$(npx makaron-cli chat --project auto --json -b "$USER_PROMPT")
298
+ RUN_ID=$(echo "$RUN_JSON" | jq -r .runId)
299
+ PROJECT_URL=$(echo "$RUN_JSON" | jq -r .projectUrl)
300
+ send_message "Project created: $PROJECT_URL"
301
+
302
+ if ! npx makaron-cli responses timeline "$RUN_ID" --jsonl --compact --checkpoint-mode service --fail-on-unapproved > /tmp/makaron-events.jsonl; then
303
+ npx makaron-cli responses timeline "$RUN_ID" --jsonl --compact | while read -r event; do
304
+ TYPE=$(echo "$event" | jq -r .type)
305
+ REQUIRES=$(echo "$event" | jq -r ".requires_approval // false")
306
+ TEXT=$(echo "$event" | jq -r ".text // empty")
307
+ MSG_ID=$(echo "$event" | jq -r ".id // empty")
308
+ if [ "$TYPE" = "message" ] && [ "$REQUIRES" = "true" ]; then
309
+ send_message "$TEXT"
310
+ npx makaron-cli responses ask-user "$MSG_ID" --run "$RUN_ID"
311
+ exit 3
312
+ fi
313
+ done
314
+ fi
315
+
316
+ RESULT=$(npx makaron-cli responses get "$RUN_ID" --wait --json)
317
+ ```
318
+
288
319
  ## Recommended Pattern: Service Flow (Feishu/OpenClaw/Group Chat)
289
320
 
290
321
  When serving end-users in a chat environment (Feishu, Slack, Discord), use this proactive message pattern:
package/bin/makaron.mjs CHANGED
@@ -447,25 +447,61 @@ function stableEventId(prefix, runId, seq, fallback) {
447
447
  return fallback || `${prefix}_${runId}_${seq ?? Date.now()}`;
448
448
  }
449
449
 
450
- function inferRequiresApproval(text) {
450
+ function inferApprovalIntent(text) {
451
451
  if (!text) return false;
452
452
  const normalized = text.toLowerCase().replace(/\s+/g, ' ').trim();
453
- const approvalPhrases = [
454
- /\bshall i\b.*\b(go ahead|proceed|continue|generate|create|submit|start)\b/,
455
- /\bshould i\b.*\b(go ahead|proceed|continue|generate|create|submit|start)\b/,
456
- /\bdo you want me to\b.*\b(go ahead|proceed|continue|generate|create|submit|start)\b/,
457
- /\bconfirm\b.*\b(before|to)\b.*\b(generate|create|submit|proceed|continue)\b/,
458
- /\bapprove\b.*\b(before|to)\b.*\b(generate|create|submit|proceed|continue)\b/,
459
- /\bplease\b.*\b(confirm|approve)\b.*\b(generate|create|submit|proceed|continue)\b/,
460
- ];
461
- return approvalPhrases.some(pattern => pattern.test(normalized));
453
+ const hasEnglishApprovalIntent = /\b(shall i|should i|do you want me to|confirm|approve|approval|permission)\b/.test(normalized);
454
+ const hasEnglishAction = /\b(go ahead|proceed|continue|generate|create|submit|start|run|render)\b/.test(normalized);
455
+ const hasChineseApprovalIntent = /(是否|要不要|是否要|需要我|请确认|确认|同意|批准|可以吗|是否可以)/.test(normalized);
456
+ const hasChineseAction = /(继续|开始|生成|创建|提交|执行|渲染|出图|出视频|制作)/.test(normalized);
457
+ return (hasEnglishApprovalIntent && hasEnglishAction) || (hasChineseApprovalIntent && hasChineseAction);
462
458
  }
463
459
 
464
- function normalizeDialogueMessage(runId, projectId, ev) {
460
+ function isPureStatusMessage(text, sourceType, status) {
461
+ const normalized = String(text || '').toLowerCase().replace(/\s+/g, ' ').trim();
462
+ if (sourceType === 'status') return true;
463
+ if (status && !normalized) return true;
464
+ if (!normalized) return false;
465
+ return /^(queued|running|rendering|uploading|processing|generating|completed|complete|done|failed|aborted|started|submitted|waiting|polling)(\.|…|\.\.\.)?$/.test(normalized)
466
+ || /^(排队中|队列中|运行中|渲染中|上传中|处理中|生成中|已完成|完成|失败|已失败|已提交|等待中)$/.test(normalized);
467
+ }
468
+
469
+ function hasExplicitApprovalRequirement(data, text) {
470
+ return Boolean(
471
+ data.requires_approval
472
+ || data.requiresApproval
473
+ || data.action_required
474
+ || data.actionRequired
475
+ || data.proposal
476
+ || inferApprovalIntent(text)
477
+ );
478
+ }
479
+
480
+ function isPureQaRun(data) {
481
+ const raw = [
482
+ data.intent,
483
+ data.mode,
484
+ data.workflow,
485
+ data.workflow_type,
486
+ data.workflowType,
487
+ data.run_type,
488
+ data.runType,
489
+ data.kind,
490
+ ].filter(Boolean).join(' ').toLowerCase();
491
+ return /\b(qa|q&a|question_answer|question-answer|answer|pure_qa|pure-qa)\b/.test(raw);
492
+ }
493
+
494
+ function normalizeDialogueMessage(runId, projectId, ev, context = {}) {
465
495
  const data = ev.data || ev;
466
496
  const text = data.text || data.message || data.content || data.statusText || '';
467
497
  if (!text && ev.type !== 'tool_call') return null;
468
- const requiresApproval = Boolean(data.requires_approval || data.requiresApproval || data.action_required || data.actionRequired || inferRequiresApproval(text));
498
+ const explicitApproval = hasExplicitApprovalRequirement(data, text);
499
+ const textOnlyCheckpoint = Boolean(
500
+ context.stoppedWithoutArtifacts
501
+ && !explicitApproval
502
+ && !isPureStatusMessage(text, ev.type, data.status)
503
+ );
504
+ const requiresApproval = explicitApproval || textOnlyCheckpoint;
469
505
  const message = {
470
506
  type: 'message',
471
507
  id: stableEventId('msg', runId, ev.seq, data.id || ev.id),
@@ -479,6 +515,7 @@ function normalizeDialogueMessage(runId, projectId, ev) {
479
515
  if (requiresApproval) {
480
516
  message.requires_approval = true;
481
517
  message.approval_options = data.approval_options || data.approvalOptions || ['approve', 'revise', 'ask_user', 'continue'];
518
+ if (textOnlyCheckpoint) message.approval_reason = 'text_only_checkpoint';
482
519
  }
483
520
  if (data.proposal) message.proposal = data.proposal;
484
521
  return message;
@@ -488,6 +525,7 @@ function normalizeDialogueArtifact(runId, projectId, item, seq) {
488
525
  if (!item) return null;
489
526
  const kind = item.type || item.kind || (item.imageUrl ? 'image' : item.videoUrl ? 'video' : undefined);
490
527
  if (!kind) return null;
528
+ if (!['image', 'video', 'design', 'music', 'audio', 'file'].includes(kind)) return null;
491
529
  const artifact = {
492
530
  type: 'artifact',
493
531
  id: stableEventId('artifact', runId, seq, item.id || item.snapshotId || item.taskId),
@@ -505,7 +543,7 @@ function normalizeDialogueArtifact(runId, projectId, item, seq) {
505
543
  return artifact;
506
544
  }
507
545
 
508
- function normalizeDialogueEvent(runId, projectId, ev) {
546
+ function normalizeDialogueEvent(runId, projectId, ev, context = {}) {
509
547
  const data = ev.data || {};
510
548
  if (ev.type === 'message' || ev.type === 'approval' || ev.type === 'artifact') {
511
549
  return { ...data, ...ev, runId: ev.runId || runId, projectId: ev.projectId || projectId };
@@ -515,7 +553,7 @@ function normalizeDialogueEvent(runId, projectId, ev) {
515
553
  case 'status':
516
554
  case 'tool_call':
517
555
  case 'error':
518
- return normalizeDialogueMessage(runId, projectId, ev);
556
+ return normalizeDialogueMessage(runId, projectId, ev, context);
519
557
  case 'image':
520
558
  return normalizeDialogueArtifact(runId, projectId, { type: 'image', status: data.imageUrl ? 'completed' : 'running', imageUrl: data.imageUrl, snapshotId: data.snapshotId }, ev.seq);
521
559
  case 'render':
@@ -533,8 +571,22 @@ function normalizeDialogueEvent(runId, projectId, ev) {
533
571
  function buildDialogueEvents(runId, data) {
534
572
  const projectId = data.projectId || data.project_id;
535
573
  const events = [];
574
+ const hasArtifacts = Boolean(
575
+ (data.output || []).some(item => normalizeDialogueArtifact(runId, projectId, item, item.seq))
576
+ || (data.events || []).some(ev => normalizeDialogueEvent(runId, projectId, ev, { stoppedWithoutArtifacts: false })?.type === 'artifact')
577
+ );
578
+ const hasContinuationAction = (data.events || []).some(ev => ['tool_call', 'image', 'render', 'animation_task', 'video_snapshot', 'music_task'].includes(ev.type));
579
+ const checkpointMode = data.checkpointMode || 'service';
580
+ const context = {
581
+ stoppedWithoutArtifacts: checkpointMode !== 'off'
582
+ && !isPureQaRun(data)
583
+ && !hasArtifacts
584
+ && !hasContinuationAction
585
+ && !data.incomplete
586
+ && ['completed', 'failed', 'aborted', 'waiting', 'needs_input'].includes(data.status),
587
+ };
536
588
  for (const ev of data.events || []) {
537
- const normalized = normalizeDialogueEvent(runId, projectId, ev);
589
+ const normalized = normalizeDialogueEvent(runId, projectId, ev, context);
538
590
  if (normalized) events.push(normalized);
539
591
  }
540
592
  for (const item of data.output || []) {
@@ -547,6 +599,30 @@ function buildDialogueEvents(runId, data) {
547
599
  return events;
548
600
  }
549
601
 
602
+ function compactDialogueEvents(events) {
603
+ const compacted = [];
604
+ for (const ev of events) {
605
+ const prev = compacted[compacted.length - 1];
606
+ const canMerge = prev
607
+ && ev.type === 'message'
608
+ && prev.type === 'message'
609
+ && ev.source_type === 'content'
610
+ && prev.source_type === 'content'
611
+ && !ev.requires_approval
612
+ && !prev.requires_approval
613
+ && !ev.proposal
614
+ && !prev.proposal;
615
+ if (canMerge) {
616
+ prev.text = `${prev.text}${ev.text}`;
617
+ prev.id = `${prev.id}+${ev.id}`;
618
+ prev.seq_end = ev.seq;
619
+ } else {
620
+ compacted.push({ ...ev });
621
+ }
622
+ }
623
+ return compacted;
624
+ }
625
+
550
626
  function findUnhandledApprovalMessages(events) {
551
627
  const approved = new Set(events.filter(ev => ev.type === 'approval').map(ev => ev.messageId || ev.message_id));
552
628
  return events.filter(ev => ev.type === 'message' && ev.requires_approval && !approved.has(ev.id));
@@ -562,12 +638,13 @@ async function fetchRun(baseUrl, headers, runId, opts = {}) {
562
638
  }
563
639
 
564
640
  async function printDialogueEvents(baseUrl, headers, runId, opts = {}) {
565
- const { jsonl = false, failOnUnapproved = false, follow = false, interval = 5000 } = opts;
641
+ const { jsonl = false, failOnUnapproved = false, follow = false, interval = 5000, compact = false, checkpointMode = 'service' } = opts;
566
642
  const printed = new Set();
567
643
 
568
644
  while (true) {
569
645
  const data = await fetchRun(baseUrl, headers, runId, { events: true });
570
- const events = buildDialogueEvents(runId, data);
646
+ const dialogueEvents = buildDialogueEvents(runId, { ...data, checkpointMode });
647
+ const events = compact ? compactDialogueEvents(dialogueEvents) : dialogueEvents;
571
648
  const unhandled = findUnhandledApprovalMessages(events);
572
649
  if (failOnUnapproved && unhandled.length) {
573
650
  process.stderr.write(`Unhandled Makaron message requires approval: ${unhandled.map(ev => ev.id).join(', ')}\n`);
@@ -1242,15 +1319,21 @@ if (command === '--version' || command === '-v' || command === 'version') {
1242
1319
 
1243
1320
  } else if (sub === 'events' || sub === 'timeline') {
1244
1321
  const runId = args[2];
1245
- if (!runId) { console.error(`Usage: makaron responses ${sub} <runId> [--jsonl] [--follow] [--interval <ms>] [--fail-on-unapproved]`); process.exit(1); }
1246
- let interval = 5000, jsonl = false, follow = false, failOnUnapproved = false;
1322
+ if (!runId) { console.error(`Usage: makaron responses ${sub} <runId> [--jsonl] [--compact] [--checkpoint-mode service|off] [--follow] [--interval <ms>] [--fail-on-unapproved]`); process.exit(1); }
1323
+ let interval = 5000, jsonl = false, follow = false, failOnUnapproved = false, compact = false, checkpointMode = 'service';
1247
1324
  for (let i = 3; i < args.length; i++) {
1248
1325
  if (args[i] === '--jsonl') jsonl = true;
1326
+ else if (args[i] === '--compact') compact = true;
1327
+ else if (args[i] === '--checkpoint-mode' && args[i + 1]) checkpointMode = args[++i];
1249
1328
  else if (args[i] === '--follow') follow = true;
1250
1329
  else if (args[i] === '--fail-on-unapproved') failOnUnapproved = true;
1251
1330
  else if (args[i] === '--interval' && args[i + 1]) interval = parseInt(args[++i]);
1252
1331
  }
1253
- await printDialogueEvents(baseUrl, headers, runId, { interval, jsonl, follow, failOnUnapproved });
1332
+ if (!['service', 'off'].includes(checkpointMode)) {
1333
+ console.error('--checkpoint-mode must be service or off');
1334
+ process.exit(1);
1335
+ }
1336
+ await printDialogueEvents(baseUrl, headers, runId, { interval, jsonl, follow, failOnUnapproved, compact, checkpointMode });
1254
1337
 
1255
1338
  } else if (['approve', 'revise', 'ask-user', 'continue'].includes(sub)) {
1256
1339
  const messageId = args[2];
@@ -1291,6 +1374,7 @@ if (command === '--version' || command === '-v' || command === 'version') {
1291
1374
  responses get <runId> --pick <field> Extract: first_image_url, first_video_url, project_url, output
1292
1375
  responses events <runId> --jsonl Emit message/approval/artifact events for external Agents
1293
1376
  responses timeline <runId> --jsonl Alias for responses events
1377
+ responses timeline <runId> --checkpoint-mode off Disable text-only checkpoints for pure Q&A
1294
1378
  responses approve <messageId> --run <runId> Record approval for a Makaron message
1295
1379
  responses revise <messageId> --run <runId> Record revision request for a Makaron message
1296
1380
  responses ask-user <messageId> --run <runId> Record that the user must decide
@@ -1733,6 +1817,7 @@ Commands:
1733
1817
  responses get <runId> Get run status and results
1734
1818
  responses get <runId> --wait Poll until completed
1735
1819
  responses events <runId> --jsonl Emit message/approval/artifact events
1820
+ responses timeline <runId> --checkpoint-mode off Disable text-only checkpoints for pure Q&A
1736
1821
  responses list --project <id> List runs for a project
1737
1822
  abort <runId> Abort a running Agent
1738
1823
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.7.9",
3
+ "version": "0.7.10",
4
4
  "description": "Talk to Makaron Agent from the terminal — create projects, edit images, generate videos",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -128,11 +128,13 @@ Outputs one JSON per line as artifacts appear:
128
128
 
129
129
  ### Dialogue events for external Agents
130
130
 
131
- Use this when another Agent needs to read what Makaron said, detect approval requirements, and relay artifacts without inventing customer-service wording.
131
+ Use this when another Agent needs to read what Makaron said, handle text checkpoints, and relay artifacts without inventing customer-service wording.
132
132
 
133
133
  ```bash
134
134
  npx makaron-cli responses events <runId> --jsonl
135
135
  # alias: npx makaron-cli responses timeline <runId> --jsonl
136
+ # compact view: npx makaron-cli responses timeline <runId> --jsonl --compact
137
+ # pure Q&A view: npx makaron-cli responses timeline <runId> --jsonl --checkpoint-mode off
136
138
  ```
137
139
 
138
140
  Events use only three factual types:
@@ -143,7 +145,7 @@ Events use only three factual types:
143
145
  {"type":"artifact","kind":"image","status":"completed","url":"https://..."}
144
146
  ```
145
147
 
146
- If a message has `requires_approval: true`, the external Agent must record an approval before continuing to wait for artifacts or claiming completion:
148
+ Makaron uses a conservative checkpoint rule for creative/service Agents: if a run stops with substantive text, has no image/video/music/design artifact, and has no continuing execution action such as a tool call, the message is marked `requires_approval: true`. Pure status text such as queued, rendering, uploading, or completed is not treated as a checkpoint. Pure Q&A flows should pass `--checkpoint-mode off`. If a message has `requires_approval: true`, the external Agent must record an approval before continuing to wait for artifacts or claiming completion:
147
149
 
148
150
  ```bash
149
151
  npx makaron-cli responses approve msg_1 --run <runId> --note "Proceed."
@@ -155,10 +157,12 @@ npx makaron-cli responses continue msg_1 --run <runId>
155
157
  Important: approvals are a local Agent gate in v0. They are recorded by the CLI so wrappers and Skills can fail fast, but they do not pause or resume the remote Makaron runtime yet. Wrappers can enforce the local gate with:
156
158
 
157
159
  ```bash
158
- npx makaron-cli responses events <runId> --jsonl
159
- # alias: npx makaron-cli responses timeline <runId> --jsonl --fail-on-unapproved
160
+ npx makaron-cli responses events <runId> --jsonl --checkpoint-mode service --fail-on-unapproved
161
+ # alias: npx makaron-cli responses timeline <runId> --jsonl --compact --fail-on-unapproved
160
162
  ```
161
163
 
164
+ Use `--compact` when relaying to another Agent or chat system; it merges consecutive Makaron content chunks into a single readable message.
165
+
162
166
  ### Extract specific results
163
167
 
164
168
  ```bash
@@ -285,6 +289,33 @@ type MakaronOutput =
285
289
  | Motion design | "create an Instagram story with animated text" |
286
290
  | Multi-step | "edit the photo then make a video from it" |
287
291
 
292
+ ## Minimal Agent Wrapper
293
+
294
+ A new Agent can use this minimal flow:
295
+
296
+ ```bash
297
+ RUN_JSON=$(npx makaron-cli chat --project auto --json -b "$USER_PROMPT")
298
+ RUN_ID=$(echo "$RUN_JSON" | jq -r .runId)
299
+ PROJECT_URL=$(echo "$RUN_JSON" | jq -r .projectUrl)
300
+ send_message "Project created: $PROJECT_URL"
301
+
302
+ if ! npx makaron-cli responses timeline "$RUN_ID" --jsonl --compact --checkpoint-mode service --fail-on-unapproved > /tmp/makaron-events.jsonl; then
303
+ npx makaron-cli responses timeline "$RUN_ID" --jsonl --compact | while read -r event; do
304
+ TYPE=$(echo "$event" | jq -r .type)
305
+ REQUIRES=$(echo "$event" | jq -r ".requires_approval // false")
306
+ TEXT=$(echo "$event" | jq -r ".text // empty")
307
+ MSG_ID=$(echo "$event" | jq -r ".id // empty")
308
+ if [ "$TYPE" = "message" ] && [ "$REQUIRES" = "true" ]; then
309
+ send_message "$TEXT"
310
+ npx makaron-cli responses ask-user "$MSG_ID" --run "$RUN_ID"
311
+ exit 3
312
+ fi
313
+ done
314
+ fi
315
+
316
+ RESULT=$(npx makaron-cli responses get "$RUN_ID" --wait --json)
317
+ ```
318
+
288
319
  ## Recommended Pattern: Service Flow (Feishu/OpenClaw/Group Chat)
289
320
 
290
321
  When serving end-users in a chat environment (Feishu, Slack, Discord), use this proactive message pattern: