makaron-cli 0.7.9 → 0.8.0

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
@@ -134,15 +134,36 @@ Outputs one JSON per line as artifacts appear:
134
134
  {"event":"done","status":"completed"}
135
135
  ```
136
136
 
137
+ ### New Agent quickstart
138
+
139
+ For a new Agent, keep the default service flow to two response commands:
140
+
141
+ ```bash
142
+ npx makaron-cli responses next <runId> --json
143
+ npx makaron-cli responses handle <messageId> --run <runId> --choice approve
144
+ ```
145
+
146
+ `responses next` emits a compact timeline, detects text-only checkpoints for creative/service work, and returns `next_commands` when the Agent must handle a Makaron message before waiting for artifacts. It exits with code `3` on an unhandled checkpoint so wrappers stop instead of silently waiting. After handling a checkpoint, run the returned `next_commands.inspect` command to continue. A non-checkpoint response returns `status: "ready"` or `status: "has_artifacts"`. Use `--no-fail` to inspect the JSON without failing. For pure Q&A runs, use:
147
+
148
+ ```bash
149
+ npx makaron-cli responses next <runId> --json --checkpoint-mode off
150
+ ```
151
+
152
+ `responses handle` is the single checkpoint action command. Valid choices are `approve`, `revise`, `ask_user`, and `continue`.
153
+
137
154
  ### Dialogue events for external Agents
138
155
 
139
- Use this when another Agent needs to read what Makaron said, detect approval requirements, and relay artifacts without inventing customer-service wording.
156
+ Use this when another Agent needs to read what Makaron said, handle text checkpoints, and relay artifacts without inventing customer-service wording.
140
157
 
141
158
  ```bash
142
159
  npx makaron-cli responses events <runId> --jsonl
143
160
  # alias: npx makaron-cli responses timeline <runId> --jsonl
161
+ # compact view: npx makaron-cli responses timeline <runId> --jsonl --compact
162
+ # pure Q&A view: npx makaron-cli responses timeline <runId> --jsonl --checkpoint-mode off
144
163
  ```
145
164
 
165
+ `events` and `timeline` are lower-level commands. New Agents should start with `responses next` and use these only when they need raw event streams.
166
+
146
167
  Events use only three factual types:
147
168
 
148
169
  ```json
@@ -151,22 +172,25 @@ Events use only three factual types:
151
172
  {"type":"artifact","kind":"image","status":"completed","url":"https://..."}
152
173
  ```
153
174
 
154
- If a message has `requires_approval: true`, the external Agent must record an approval before continuing to wait for artifacts or claiming completion:
175
+ 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
176
 
156
177
  ```bash
157
178
  npx makaron-cli responses approve msg_1 --run <runId> --note "Proceed."
158
179
  npx makaron-cli responses revise msg_1 --run <runId> "make it softer"
159
180
  npx makaron-cli responses ask-user msg_1 --run <runId>
160
181
  npx makaron-cli responses continue msg_1 --run <runId>
182
+ npx makaron-cli responses handle msg_1 --run <runId> --choice approve
161
183
  ```
162
184
 
163
185
  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
186
 
165
187
  ```bash
166
- npx makaron-cli responses events <runId> --jsonl
167
- # alias: npx makaron-cli responses timeline <runId> --jsonl --fail-on-unapproved
188
+ npx makaron-cli responses events <runId> --jsonl --checkpoint-mode service --fail-on-unapproved
189
+ # alias: npx makaron-cli responses timeline <runId> --jsonl --compact --fail-on-unapproved
168
190
  ```
169
191
 
192
+ Use `--compact` when relaying to another Agent or chat system; it merges consecutive Makaron content chunks into a single readable message.
193
+
170
194
  ### Extract specific results
171
195
 
172
196
  ```bash
@@ -296,6 +320,30 @@ type MakaronOutput =
296
320
  | Motion design | "create an Instagram story with animated text" |
297
321
  | Multi-step | "edit the photo then make a video from it" |
298
322
 
323
+ ## Minimal Agent Wrapper
324
+
325
+ A new Agent can use this minimal flow:
326
+
327
+ ```bash
328
+ RUN_JSON=$(npx makaron-cli chat --project auto --json -b "$USER_PROMPT")
329
+ RUN_ID=$(echo "$RUN_JSON" | jq -r .runId)
330
+ PROJECT_URL=$(echo "$RUN_JSON" | jq -r .projectUrl)
331
+ send_message "Project created: $PROJECT_URL"
332
+
333
+ if ! NEXT=$(npx makaron-cli responses next "$RUN_ID" --json); then
334
+ STATUS=$(echo "$NEXT" | jq -r .status)
335
+ if [ "$STATUS" = "needs_approval" ]; then
336
+ MSG_ID=$(echo "$NEXT" | jq -r .checkpoint.id)
337
+ TEXT=$(echo "$NEXT" | jq -r .checkpoint.text)
338
+ send_message "$TEXT"
339
+ npx makaron-cli responses handle "$MSG_ID" --run "$RUN_ID" --choice ask_user
340
+ exit 3
341
+ fi
342
+ fi
343
+
344
+ RESULT=$(npx makaron-cli responses get "$RUN_ID" --wait --json)
345
+ ```
346
+
299
347
  ## Recommended Pattern: Service Flow (Feishu/OpenClaw/Group Chat)
300
348
 
301
349
  When serving end-users in a chat environment (Feishu, Slack, Discord), use this proactive message pattern:
package/SKILL.md CHANGED
@@ -126,15 +126,36 @@ Outputs one JSON per line as artifacts appear:
126
126
  {"event":"done","status":"completed"}
127
127
  ```
128
128
 
129
+ ### New Agent quickstart
130
+
131
+ For a new Agent, keep the default service flow to two response commands:
132
+
133
+ ```bash
134
+ npx makaron-cli responses next <runId> --json
135
+ npx makaron-cli responses handle <messageId> --run <runId> --choice approve
136
+ ```
137
+
138
+ `responses next` emits a compact timeline, detects text-only checkpoints for creative/service work, and returns `next_commands` when the Agent must handle a Makaron message before waiting for artifacts. It exits with code `3` on an unhandled checkpoint so wrappers stop instead of silently waiting. After handling a checkpoint, run the returned `next_commands.inspect` command to continue. A non-checkpoint response returns `status: "ready"` or `status: "has_artifacts"`. Use `--no-fail` to inspect the JSON without failing. For pure Q&A runs, use:
139
+
140
+ ```bash
141
+ npx makaron-cli responses next <runId> --json --checkpoint-mode off
142
+ ```
143
+
144
+ `responses handle` is the single checkpoint action command. Valid choices are `approve`, `revise`, `ask_user`, and `continue`.
145
+
129
146
  ### Dialogue events for external Agents
130
147
 
131
- Use this when another Agent needs to read what Makaron said, detect approval requirements, and relay artifacts without inventing customer-service wording.
148
+ Use this when another Agent needs to read what Makaron said, handle text checkpoints, and relay artifacts without inventing customer-service wording.
132
149
 
133
150
  ```bash
134
151
  npx makaron-cli responses events <runId> --jsonl
135
152
  # alias: npx makaron-cli responses timeline <runId> --jsonl
153
+ # compact view: npx makaron-cli responses timeline <runId> --jsonl --compact
154
+ # pure Q&A view: npx makaron-cli responses timeline <runId> --jsonl --checkpoint-mode off
136
155
  ```
137
156
 
157
+ `events` and `timeline` are lower-level commands. New Agents should start with `responses next` and use these only when they need raw event streams.
158
+
138
159
  Events use only three factual types:
139
160
 
140
161
  ```json
@@ -143,22 +164,25 @@ Events use only three factual types:
143
164
  {"type":"artifact","kind":"image","status":"completed","url":"https://..."}
144
165
  ```
145
166
 
146
- If a message has `requires_approval: true`, the external Agent must record an approval before continuing to wait for artifacts or claiming completion:
167
+ 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
168
 
148
169
  ```bash
149
170
  npx makaron-cli responses approve msg_1 --run <runId> --note "Proceed."
150
171
  npx makaron-cli responses revise msg_1 --run <runId> "make it softer"
151
172
  npx makaron-cli responses ask-user msg_1 --run <runId>
152
173
  npx makaron-cli responses continue msg_1 --run <runId>
174
+ npx makaron-cli responses handle msg_1 --run <runId> --choice approve
153
175
  ```
154
176
 
155
177
  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
178
 
157
179
  ```bash
158
- npx makaron-cli responses events <runId> --jsonl
159
- # alias: npx makaron-cli responses timeline <runId> --jsonl --fail-on-unapproved
180
+ npx makaron-cli responses events <runId> --jsonl --checkpoint-mode service --fail-on-unapproved
181
+ # alias: npx makaron-cli responses timeline <runId> --jsonl --compact --fail-on-unapproved
160
182
  ```
161
183
 
184
+ Use `--compact` when relaying to another Agent or chat system; it merges consecutive Makaron content chunks into a single readable message.
185
+
162
186
  ### Extract specific results
163
187
 
164
188
  ```bash
@@ -285,6 +309,30 @@ type MakaronOutput =
285
309
  | Motion design | "create an Instagram story with animated text" |
286
310
  | Multi-step | "edit the photo then make a video from it" |
287
311
 
312
+ ## Minimal Agent Wrapper
313
+
314
+ A new Agent can use this minimal flow:
315
+
316
+ ```bash
317
+ RUN_JSON=$(npx makaron-cli chat --project auto --json -b "$USER_PROMPT")
318
+ RUN_ID=$(echo "$RUN_JSON" | jq -r .runId)
319
+ PROJECT_URL=$(echo "$RUN_JSON" | jq -r .projectUrl)
320
+ send_message "Project created: $PROJECT_URL"
321
+
322
+ if ! NEXT=$(npx makaron-cli responses next "$RUN_ID" --json); then
323
+ STATUS=$(echo "$NEXT" | jq -r .status)
324
+ if [ "$STATUS" = "needs_approval" ]; then
325
+ MSG_ID=$(echo "$NEXT" | jq -r .checkpoint.id)
326
+ TEXT=$(echo "$NEXT" | jq -r .checkpoint.text)
327
+ send_message "$TEXT"
328
+ npx makaron-cli responses handle "$MSG_ID" --run "$RUN_ID" --choice ask_user
329
+ exit 3
330
+ fi
331
+ fi
332
+
333
+ RESULT=$(npx makaron-cli responses get "$RUN_ID" --wait --json)
334
+ ```
335
+
288
336
  ## Recommended Pattern: Service Flow (Feishu/OpenClaw/Group Chat)
289
337
 
290
338
  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,11 +599,78 @@ 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.proposal
612
+ && !prev.proposal
613
+ && Boolean(ev.requires_approval) === Boolean(prev.requires_approval)
614
+ && (ev.approval_reason || '') === (prev.approval_reason || '');
615
+ if (canMerge) {
616
+ prev.text = `${prev.text}${ev.text}`;
617
+ prev.id = `${prev.id}+${ev.id}`;
618
+ prev.seq_end = ev.seq;
619
+ if (ev.requires_approval) {
620
+ prev.requires_approval = true;
621
+ prev.approval_options = prev.approval_options || ev.approval_options;
622
+ }
623
+ } else {
624
+ compacted.push({ ...ev });
625
+ }
626
+ }
627
+ return compacted;
628
+ }
629
+
550
630
  function findUnhandledApprovalMessages(events) {
551
631
  const approved = new Set(events.filter(ev => ev.type === 'approval').map(ev => ev.messageId || ev.message_id));
552
632
  return events.filter(ev => ev.type === 'message' && ev.requires_approval && !approved.has(ev.id));
553
633
  }
554
634
 
635
+ function buildHandleCommand(messageId, runId, choice) {
636
+ return `npx makaron-cli responses handle ${messageId} --run ${runId} --choice ${choice}`;
637
+ }
638
+
639
+ function buildNextCommand(runId) {
640
+ return `npx makaron-cli responses next ${runId} --json`;
641
+ }
642
+
643
+ function buildNextAction(runId, events) {
644
+ const checkpoint = findUnhandledApprovalMessages(events)[0] || null;
645
+ const artifacts = events.filter(ev => ev.type === 'artifact');
646
+ const approvals = events.filter(ev => ev.type === 'approval');
647
+ if (checkpoint) {
648
+ return {
649
+ status: 'needs_approval',
650
+ runId,
651
+ checkpoint,
652
+ next_commands: {
653
+ approve: buildHandleCommand(checkpoint.id, runId, 'approve'),
654
+ revise: `${buildHandleCommand(checkpoint.id, runId, 'revise')} --note "what to change"`,
655
+ ask_user: buildHandleCommand(checkpoint.id, runId, 'ask_user'),
656
+ continue: buildHandleCommand(checkpoint.id, runId, 'continue'),
657
+ inspect: buildNextCommand(runId),
658
+ },
659
+ events,
660
+ };
661
+ }
662
+ return {
663
+ status: artifacts.length ? 'has_artifacts' : 'ready',
664
+ runId,
665
+ artifacts,
666
+ approvals,
667
+ next_commands: {
668
+ inspect: buildNextCommand(runId),
669
+ },
670
+ events,
671
+ };
672
+ }
673
+
555
674
  async function fetchRun(baseUrl, headers, runId, opts = {}) {
556
675
  const params = new URLSearchParams();
557
676
  if (opts.events) params.set('events', 'true');
@@ -562,12 +681,13 @@ async function fetchRun(baseUrl, headers, runId, opts = {}) {
562
681
  }
563
682
 
564
683
  async function printDialogueEvents(baseUrl, headers, runId, opts = {}) {
565
- const { jsonl = false, failOnUnapproved = false, follow = false, interval = 5000 } = opts;
684
+ const { jsonl = false, failOnUnapproved = false, follow = false, interval = 5000, compact = false, checkpointMode = 'service' } = opts;
566
685
  const printed = new Set();
567
686
 
568
687
  while (true) {
569
688
  const data = await fetchRun(baseUrl, headers, runId, { events: true });
570
- const events = buildDialogueEvents(runId, data);
689
+ const dialogueEvents = buildDialogueEvents(runId, { ...data, checkpointMode });
690
+ const events = compact ? compactDialogueEvents(dialogueEvents) : dialogueEvents;
571
691
  const unhandled = findUnhandledApprovalMessages(events);
572
692
  if (failOnUnapproved && unhandled.length) {
573
693
  process.stderr.write(`Unhandled Makaron message requires approval: ${unhandled.map(ev => ev.id).join(', ')}\n`);
@@ -589,6 +709,32 @@ async function printDialogueEvents(baseUrl, headers, runId, opts = {}) {
589
709
  }
590
710
  }
591
711
 
712
+ async function printAgentNext(baseUrl, headers, runId, opts = {}) {
713
+ const { json = false, checkpointMode = 'service', failOnCheckpoint = true } = opts;
714
+ const data = await fetchRun(baseUrl, headers, runId, { events: true });
715
+ const events = compactDialogueEvents(buildDialogueEvents(runId, { ...data, checkpointMode }));
716
+ const action = buildNextAction(runId, events);
717
+ if (json) {
718
+ console.log(JSON.stringify(action, null, 2));
719
+ } else if (action.status === 'needs_approval') {
720
+ console.log(`needs_approval: ${action.checkpoint.id}`);
721
+ if (action.checkpoint.text) console.log(action.checkpoint.text);
722
+ console.log(`approve: ${action.next_commands.approve}`);
723
+ console.log(`revise: ${action.next_commands.revise}`);
724
+ console.log(`ask_user: ${action.next_commands.ask_user}`);
725
+ console.log(`continue: ${action.next_commands.continue}`);
726
+ console.log(`inspect: ${action.next_commands.inspect}`);
727
+ } else if (action.status === 'has_artifacts') {
728
+ for (const artifact of action.artifacts) {
729
+ console.log(`${artifact.kind} ${artifact.status}${artifact.url ? ` ${artifact.url}` : ''}`);
730
+ }
731
+ } else {
732
+ console.log('ready');
733
+ console.log(`inspect: ${action.next_commands.inspect}`);
734
+ }
735
+ if (action.status === 'needs_approval' && failOnCheckpoint) process.exit(3);
736
+ }
737
+
592
738
  function recordApproval(runId, messageId, choice, note) {
593
739
  const approvals = loadApprovals();
594
740
  const approval = {
@@ -1240,31 +1386,68 @@ if (command === '--version' || command === '-v' || command === 'version') {
1240
1386
  }
1241
1387
  await watchRun(baseUrl, headers, runId, { interval, jsonl });
1242
1388
 
1389
+ } else if (sub === 'next') {
1390
+ const runId = args[2];
1391
+ if (!runId) { console.error('Usage: makaron responses next <runId> [--json] [--checkpoint-mode service|off] [--no-fail]'); process.exit(1); }
1392
+ let jsonOutput = false, checkpointMode = 'service', failOnCheckpoint = true;
1393
+ for (let i = 3; i < args.length; i++) {
1394
+ if (args[i] === '--json') jsonOutput = true;
1395
+ else if (args[i] === '--checkpoint-mode' && args[i + 1]) checkpointMode = args[++i];
1396
+ else if (args[i] === '--no-fail') failOnCheckpoint = false;
1397
+ }
1398
+ if (!['service', 'off'].includes(checkpointMode)) {
1399
+ console.error('--checkpoint-mode must be service or off');
1400
+ process.exit(1);
1401
+ }
1402
+ await printAgentNext(baseUrl, headers, runId, { json: jsonOutput, checkpointMode, failOnCheckpoint });
1403
+
1243
1404
  } else if (sub === 'events' || sub === 'timeline') {
1244
1405
  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;
1406
+ if (!runId) { console.error(`Usage: makaron responses ${sub} <runId> [--jsonl] [--compact] [--checkpoint-mode service|off] [--follow] [--interval <ms>] [--fail-on-unapproved]`); process.exit(1); }
1407
+ let interval = 5000, jsonl = false, follow = false, failOnUnapproved = false, compact = false, checkpointMode = 'service';
1247
1408
  for (let i = 3; i < args.length; i++) {
1248
1409
  if (args[i] === '--jsonl') jsonl = true;
1410
+ else if (args[i] === '--compact') compact = true;
1411
+ else if (args[i] === '--checkpoint-mode' && args[i + 1]) checkpointMode = args[++i];
1249
1412
  else if (args[i] === '--follow') follow = true;
1250
1413
  else if (args[i] === '--fail-on-unapproved') failOnUnapproved = true;
1251
1414
  else if (args[i] === '--interval' && args[i + 1]) interval = parseInt(args[++i]);
1252
1415
  }
1253
- await printDialogueEvents(baseUrl, headers, runId, { interval, jsonl, follow, failOnUnapproved });
1416
+ if (!['service', 'off'].includes(checkpointMode)) {
1417
+ console.error('--checkpoint-mode must be service or off');
1418
+ process.exit(1);
1419
+ }
1420
+ await printDialogueEvents(baseUrl, headers, runId, { interval, jsonl, follow, failOnUnapproved, compact, checkpointMode });
1254
1421
 
1255
- } else if (['approve', 'revise', 'ask-user', 'continue'].includes(sub)) {
1422
+ } else if (sub === 'handle' || ['approve', 'revise', 'ask-user', 'continue'].includes(sub)) {
1256
1423
  const messageId = args[2];
1257
- if (!messageId) { console.error(`Usage: makaron responses ${sub} <messageId> --run <runId> [--note <text>]`); process.exit(1); }
1258
- let runId = null, note = null;
1424
+ if (!messageId) {
1425
+ console.error(sub === 'handle'
1426
+ ? 'Usage: makaron responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue [--note <text>]'
1427
+ : `Usage: makaron responses ${sub} <messageId> --run <runId> [--note <text>]`);
1428
+ process.exit(1);
1429
+ }
1430
+ let runId = null, note = null, choice = sub === 'handle' ? null : sub;
1259
1431
  const noteParts = [];
1260
1432
  for (let i = 3; i < args.length; i++) {
1261
1433
  if (args[i] === '--run' && args[i + 1]) runId = args[++i];
1262
1434
  else if (args[i] === '--note' && args[i + 1]) note = args[++i];
1435
+ else if (args[i] === '--choice' && args[i + 1]) choice = args[++i];
1263
1436
  else noteParts.push(args[i]);
1264
1437
  }
1265
- if (!runId) { console.error(`Usage: makaron responses ${sub} <messageId> --run <runId> [--note <text>]`); process.exit(1); }
1438
+ if (!runId || !choice) {
1439
+ console.error(sub === 'handle'
1440
+ ? 'Usage: makaron responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue [--note <text>]'
1441
+ : `Usage: makaron responses ${sub} <messageId> --run <runId> [--note <text>]`);
1442
+ process.exit(1);
1443
+ }
1444
+ choice = choice === 'ask-user' ? 'ask_user' : choice;
1445
+ if (!['approve', 'revise', 'ask_user', 'continue'].includes(choice)) {
1446
+ console.error('--choice must be approve, revise, ask_user, or continue');
1447
+ process.exit(1);
1448
+ }
1266
1449
  if (!note && noteParts.length) note = noteParts.join(' ');
1267
- recordApproval(runId, messageId, sub === 'ask-user' ? 'ask_user' : sub, note);
1450
+ recordApproval(runId, messageId, choice, note);
1268
1451
 
1269
1452
  } else if (sub === 'list') {
1270
1453
  let projectId = null;
@@ -1289,8 +1472,11 @@ if (command === '--version' || command === '-v' || command === 'version') {
1289
1472
  responses get <runId> Get status and output (JSON)
1290
1473
  responses get <runId> --wait Poll until completed
1291
1474
  responses get <runId> --pick <field> Extract: first_image_url, first_video_url, project_url, output
1475
+ responses next <runId> --json New Agent entry: compact timeline + checkpoint guidance
1476
+ responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue
1292
1477
  responses events <runId> --jsonl Emit message/approval/artifact events for external Agents
1293
1478
  responses timeline <runId> --jsonl Alias for responses events
1479
+ responses timeline <runId> --checkpoint-mode off Disable text-only checkpoints for pure Q&A
1294
1480
  responses approve <messageId> --run <runId> Record approval for a Makaron message
1295
1481
  responses revise <messageId> --run <runId> Record revision request for a Makaron message
1296
1482
  responses ask-user <messageId> --run <runId> Record that the user must decide
@@ -1732,7 +1918,10 @@ Commands:
1732
1918
 
1733
1919
  responses get <runId> Get run status and results
1734
1920
  responses get <runId> --wait Poll until completed
1921
+ responses next <runId> --json New Agent entry: compact timeline + checkpoint guidance
1922
+ responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue
1735
1923
  responses events <runId> --jsonl Emit message/approval/artifact events
1924
+ responses timeline <runId> --checkpoint-mode off Disable text-only checkpoints for pure Q&A
1736
1925
  responses list --project <id> List runs for a project
1737
1926
  abort <runId> Abort a running Agent
1738
1927
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.7.9",
3
+ "version": "0.8.0",
4
4
  "description": "Talk to Makaron Agent from the terminal — create projects, edit images, generate videos",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -126,15 +126,36 @@ Outputs one JSON per line as artifacts appear:
126
126
  {"event":"done","status":"completed"}
127
127
  ```
128
128
 
129
+ ### New Agent quickstart
130
+
131
+ For a new Agent, keep the default service flow to two response commands:
132
+
133
+ ```bash
134
+ npx makaron-cli responses next <runId> --json
135
+ npx makaron-cli responses handle <messageId> --run <runId> --choice approve
136
+ ```
137
+
138
+ `responses next` emits a compact timeline, detects text-only checkpoints for creative/service work, and returns `next_commands` when the Agent must handle a Makaron message before waiting for artifacts. It exits with code `3` on an unhandled checkpoint so wrappers stop instead of silently waiting. After handling a checkpoint, run the returned `next_commands.inspect` command to continue. A non-checkpoint response returns `status: "ready"` or `status: "has_artifacts"`. Use `--no-fail` to inspect the JSON without failing. For pure Q&A runs, use:
139
+
140
+ ```bash
141
+ npx makaron-cli responses next <runId> --json --checkpoint-mode off
142
+ ```
143
+
144
+ `responses handle` is the single checkpoint action command. Valid choices are `approve`, `revise`, `ask_user`, and `continue`.
145
+
129
146
  ### Dialogue events for external Agents
130
147
 
131
- Use this when another Agent needs to read what Makaron said, detect approval requirements, and relay artifacts without inventing customer-service wording.
148
+ Use this when another Agent needs to read what Makaron said, handle text checkpoints, and relay artifacts without inventing customer-service wording.
132
149
 
133
150
  ```bash
134
151
  npx makaron-cli responses events <runId> --jsonl
135
152
  # alias: npx makaron-cli responses timeline <runId> --jsonl
153
+ # compact view: npx makaron-cli responses timeline <runId> --jsonl --compact
154
+ # pure Q&A view: npx makaron-cli responses timeline <runId> --jsonl --checkpoint-mode off
136
155
  ```
137
156
 
157
+ `events` and `timeline` are lower-level commands. New Agents should start with `responses next` and use these only when they need raw event streams.
158
+
138
159
  Events use only three factual types:
139
160
 
140
161
  ```json
@@ -143,22 +164,25 @@ Events use only three factual types:
143
164
  {"type":"artifact","kind":"image","status":"completed","url":"https://..."}
144
165
  ```
145
166
 
146
- If a message has `requires_approval: true`, the external Agent must record an approval before continuing to wait for artifacts or claiming completion:
167
+ 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
168
 
148
169
  ```bash
149
170
  npx makaron-cli responses approve msg_1 --run <runId> --note "Proceed."
150
171
  npx makaron-cli responses revise msg_1 --run <runId> "make it softer"
151
172
  npx makaron-cli responses ask-user msg_1 --run <runId>
152
173
  npx makaron-cli responses continue msg_1 --run <runId>
174
+ npx makaron-cli responses handle msg_1 --run <runId> --choice approve
153
175
  ```
154
176
 
155
177
  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
178
 
157
179
  ```bash
158
- npx makaron-cli responses events <runId> --jsonl
159
- # alias: npx makaron-cli responses timeline <runId> --jsonl --fail-on-unapproved
180
+ npx makaron-cli responses events <runId> --jsonl --checkpoint-mode service --fail-on-unapproved
181
+ # alias: npx makaron-cli responses timeline <runId> --jsonl --compact --fail-on-unapproved
160
182
  ```
161
183
 
184
+ Use `--compact` when relaying to another Agent or chat system; it merges consecutive Makaron content chunks into a single readable message.
185
+
162
186
  ### Extract specific results
163
187
 
164
188
  ```bash
@@ -285,6 +309,30 @@ type MakaronOutput =
285
309
  | Motion design | "create an Instagram story with animated text" |
286
310
  | Multi-step | "edit the photo then make a video from it" |
287
311
 
312
+ ## Minimal Agent Wrapper
313
+
314
+ A new Agent can use this minimal flow:
315
+
316
+ ```bash
317
+ RUN_JSON=$(npx makaron-cli chat --project auto --json -b "$USER_PROMPT")
318
+ RUN_ID=$(echo "$RUN_JSON" | jq -r .runId)
319
+ PROJECT_URL=$(echo "$RUN_JSON" | jq -r .projectUrl)
320
+ send_message "Project created: $PROJECT_URL"
321
+
322
+ if ! NEXT=$(npx makaron-cli responses next "$RUN_ID" --json); then
323
+ STATUS=$(echo "$NEXT" | jq -r .status)
324
+ if [ "$STATUS" = "needs_approval" ]; then
325
+ MSG_ID=$(echo "$NEXT" | jq -r .checkpoint.id)
326
+ TEXT=$(echo "$NEXT" | jq -r .checkpoint.text)
327
+ send_message "$TEXT"
328
+ npx makaron-cli responses handle "$MSG_ID" --run "$RUN_ID" --choice ask_user
329
+ exit 3
330
+ fi
331
+ fi
332
+
333
+ RESULT=$(npx makaron-cli responses get "$RUN_ID" --wait --json)
334
+ ```
335
+
288
336
  ## Recommended Pattern: Service Flow (Feishu/OpenClaw/Group Chat)
289
337
 
290
338
  When serving end-users in a chat environment (Feishu, Slack, Discord), use this proactive message pattern: