makaron-cli 0.8.0 → 0.8.1

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
@@ -141,15 +141,16 @@ For a new Agent, keep the default service flow to two response commands:
141
141
  ```bash
142
142
  npx makaron-cli responses next <runId> --json
143
143
  npx makaron-cli responses handle <messageId> --run <runId> --choice approve
144
+ npx makaron-cli responses deliver <artifactId> --run <runId> --channel feishu --message-id <messageId>
144
145
  ```
145
146
 
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
+ `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. While the run is still generating, it returns `status: "running"` and `blocking: false`. If completed image/video artifacts exist and have not been delivered, it returns `status: "has_artifacts"` and `blocking: true` with compact `undelivered_artifacts` entries containing `kind`, `status`, `url`, `fileName`, and `contentType` when available, plus `next_commands.deliver`; the Agent must send the artifact URL to the user, then record delivery with `responses deliver`. A non-checkpoint response returns `status: "ready"` or `status: "delivered"`. If a run failed, `responses next --json` prints `status: "failed"` and `blocking: true` plus `error.type`, `error.message`, `error.recoverable`, `error.detail`, and `next_command` to stdout before exiting `1`, so wrappers can parse the failure without scraping stderr. Use `--no-fail` to inspect checkpoint JSON without failing. For pure Q&A runs, use:
147
148
 
148
149
  ```bash
149
150
  npx makaron-cli responses next <runId> --json --checkpoint-mode off
150
151
  ```
151
152
 
152
- `responses handle` is the single checkpoint action command. Valid choices are `approve`, `revise`, `ask_user`, and `continue`.
153
+ `responses handle` is the single checkpoint action command. Valid choices are `approve`, `revise`, `ask_user`, and `continue`. `responses deliver` records that a completed artifact was actually delivered to the user, preventing repeated delivery prompts.
153
154
 
154
155
  ### Dialogue events for external Agents
155
156
 
@@ -338,9 +339,20 @@ if ! NEXT=$(npx makaron-cli responses next "$RUN_ID" --json); then
338
339
  send_message "$TEXT"
339
340
  npx makaron-cli responses handle "$MSG_ID" --run "$RUN_ID" --choice ask_user
340
341
  exit 3
342
+ elif [ "$STATUS" = "failed" ]; then
343
+ send_message "$(echo "$NEXT" | jq -r .error.message)"
344
+ exit 1
341
345
  fi
342
346
  fi
343
347
 
348
+ NEXT=$(npx makaron-cli responses next "$RUN_ID" --json --no-fail)
349
+ if [ "$(echo "$NEXT" | jq -r .status)" = "has_artifacts" ]; then
350
+ ARTIFACT_ID=$(echo "$NEXT" | jq -r ".undelivered_artifacts[0].id")
351
+ ARTIFACT_URL=$(echo "$NEXT" | jq -r ".undelivered_artifacts[0].url")
352
+ DELIVERY_MESSAGE_ID=$(send_message "$ARTIFACT_URL")
353
+ npx makaron-cli responses deliver "$ARTIFACT_ID" --run "$RUN_ID" --channel feishu --message-id "$DELIVERY_MESSAGE_ID"
354
+ fi
355
+
344
356
  RESULT=$(npx makaron-cli responses get "$RUN_ID" --wait --json)
345
357
  ```
346
358
 
package/SKILL.md CHANGED
@@ -133,15 +133,16 @@ For a new Agent, keep the default service flow to two response commands:
133
133
  ```bash
134
134
  npx makaron-cli responses next <runId> --json
135
135
  npx makaron-cli responses handle <messageId> --run <runId> --choice approve
136
+ npx makaron-cli responses deliver <artifactId> --run <runId> --channel feishu --message-id <messageId>
136
137
  ```
137
138
 
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
+ `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. While the run is still generating, it returns `status: "running"` and `blocking: false`. If completed image/video artifacts exist and have not been delivered, it returns `status: "has_artifacts"` and `blocking: true` with compact `undelivered_artifacts` entries containing `kind`, `status`, `url`, `fileName`, and `contentType` when available, plus `next_commands.deliver`; the Agent must send the artifact URL to the user, then record delivery with `responses deliver`. A non-checkpoint response returns `status: "ready"` or `status: "delivered"`. If a run failed, `responses next --json` prints `status: "failed"` and `blocking: true` plus `error.type`, `error.message`, `error.recoverable`, `error.detail`, and `next_command` to stdout before exiting `1`, so wrappers can parse the failure without scraping stderr. Use `--no-fail` to inspect checkpoint JSON without failing. For pure Q&A runs, use:
139
140
 
140
141
  ```bash
141
142
  npx makaron-cli responses next <runId> --json --checkpoint-mode off
142
143
  ```
143
144
 
144
- `responses handle` is the single checkpoint action command. Valid choices are `approve`, `revise`, `ask_user`, and `continue`.
145
+ `responses handle` is the single checkpoint action command. Valid choices are `approve`, `revise`, `ask_user`, and `continue`. `responses deliver` records that a completed artifact was actually delivered to the user, preventing repeated delivery prompts.
145
146
 
146
147
  ### Dialogue events for external Agents
147
148
 
@@ -327,9 +328,20 @@ if ! NEXT=$(npx makaron-cli responses next "$RUN_ID" --json); then
327
328
  send_message "$TEXT"
328
329
  npx makaron-cli responses handle "$MSG_ID" --run "$RUN_ID" --choice ask_user
329
330
  exit 3
331
+ elif [ "$STATUS" = "failed" ]; then
332
+ send_message "$(echo "$NEXT" | jq -r .error.message)"
333
+ exit 1
330
334
  fi
331
335
  fi
332
336
 
337
+ NEXT=$(npx makaron-cli responses next "$RUN_ID" --json --no-fail)
338
+ if [ "$(echo "$NEXT" | jq -r .status)" = "has_artifacts" ]; then
339
+ ARTIFACT_ID=$(echo "$NEXT" | jq -r ".undelivered_artifacts[0].id")
340
+ ARTIFACT_URL=$(echo "$NEXT" | jq -r ".undelivered_artifacts[0].url")
341
+ DELIVERY_MESSAGE_ID=$(send_message "$ARTIFACT_URL")
342
+ npx makaron-cli responses deliver "$ARTIFACT_ID" --run "$RUN_ID" --channel feishu --message-id "$DELIVERY_MESSAGE_ID"
343
+ fi
344
+
333
345
  RESULT=$(npx makaron-cli responses get "$RUN_ID" --wait --json)
334
346
  ```
335
347
 
package/bin/makaron.mjs CHANGED
@@ -20,6 +20,7 @@ import { execFileSync } from 'child_process';
20
20
 
21
21
  const AUTH_FILE = path.join(process.env.HOME || '~', '.makaron', 'auth.json');
22
22
  const APPROVALS_FILE = path.join(path.dirname(AUTH_FILE), 'approvals.json');
23
+ const DELIVERIES_FILE = path.join(path.dirname(AUTH_FILE), 'deliveries.json');
23
24
  const DEFAULT_URL = 'https://www.makaron.app';
24
25
  const BASE_URL = process.env.MAKARON_URL || DEFAULT_URL;
25
26
  const APP_URL = process.env.MAKARON_APP_URL || DEFAULT_URL;
@@ -77,6 +78,20 @@ function saveApprovals(approvals) {
77
78
  fs.writeFileSync(APPROVALS_FILE, JSON.stringify(approvals, null, 2));
78
79
  }
79
80
 
81
+ function loadDeliveries() {
82
+ try {
83
+ return JSON.parse(fs.readFileSync(DELIVERIES_FILE, 'utf-8'));
84
+ } catch {
85
+ return [];
86
+ }
87
+ }
88
+
89
+ function saveDeliveries(deliveries) {
90
+ const dir = path.dirname(DELIVERIES_FILE);
91
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
92
+ fs.writeFileSync(DELIVERIES_FILE, JSON.stringify(deliveries, null, 2));
93
+ }
94
+
80
95
  function buildCookie(tokenJson) {
81
96
  const url = tokenJson._supabaseUrl || SUPABASE_URL;
82
97
  const ref = url.match(/\/\/([^.]+)\./)?.[1] || '';
@@ -498,6 +513,7 @@ function normalizeDialogueMessage(runId, projectId, ev, context = {}) {
498
513
  const explicitApproval = hasExplicitApprovalRequirement(data, text);
499
514
  const textOnlyCheckpoint = Boolean(
500
515
  context.stoppedWithoutArtifacts
516
+ && ev.type !== 'error'
501
517
  && !explicitApproval
502
518
  && !isPureStatusMessage(text, ev.type, data.status)
503
519
  );
@@ -537,6 +553,10 @@ function normalizeDialogueArtifact(runId, projectId, item, seq) {
537
553
  };
538
554
  const url = item.url || item.imageUrl || item.videoUrl || item.audioUrl;
539
555
  if (url) artifact.url = url;
556
+ const fileName = item.fileName || item.filename || item.name || (url ? url.split('/').pop()?.split('?')[0] : null);
557
+ const contentType = item.contentType || item.content_type || item.mimeType || item.mime_type;
558
+ if (fileName) artifact.fileName = fileName;
559
+ if (contentType) artifact.contentType = contentType;
540
560
  if (item.error) artifact.error = item.error;
541
561
  if (item.taskId) artifact.taskId = item.taskId;
542
562
  if (item.snapshotId) artifact.snapshotId = item.snapshotId;
@@ -632,21 +652,84 @@ function findUnhandledApprovalMessages(events) {
632
652
  return events.filter(ev => ev.type === 'message' && ev.requires_approval && !approved.has(ev.id));
633
653
  }
634
654
 
655
+ function isCompletedDeliverableArtifact(artifact) {
656
+ return artifact?.type === 'artifact'
657
+ && ['image', 'video', 'design', 'music', 'audio', 'file'].includes(artifact.kind)
658
+ && artifact.status === 'completed'
659
+ && Boolean(artifact.url);
660
+ }
661
+
662
+ function findUndeliveredArtifacts(runId, events) {
663
+ const delivered = new Set(loadDeliveries().filter(item => item.runId === runId).map(item => item.artifactId || item.artifact_id));
664
+ return events.filter(isCompletedDeliverableArtifact).filter(artifact => !delivered.has(artifact.id));
665
+ }
666
+
667
+ function buildCliCommand() {
668
+ return 'npx makaron-cli@latest';
669
+ }
670
+
635
671
  function buildHandleCommand(messageId, runId, choice) {
636
- return `npx makaron-cli responses handle ${messageId} --run ${runId} --choice ${choice}`;
672
+ return `${buildCliCommand()} responses handle ${messageId} --run ${runId} --choice ${choice}`;
637
673
  }
638
674
 
639
675
  function buildNextCommand(runId) {
640
- return `npx makaron-cli responses next ${runId} --json`;
676
+ return `${buildCliCommand()} responses next ${runId} --json`;
677
+ }
678
+
679
+ function buildDeliverCommand(artifactId, runId) {
680
+ return `${buildCliCommand()} responses deliver ${artifactId} --run ${runId}`;
681
+ }
682
+
683
+ function buildRunError(data, events) {
684
+ const errorEvent = events.find(ev => ev.type === 'message' && ev.source_type === 'error') || null;
685
+ const resultError = data.result?.error || data.error || data.message || null;
686
+ if (!errorEvent && !resultError && !['failed', 'aborted'].includes(data.status)) return null;
687
+ const errorType = (typeof resultError === 'object' && (resultError.type || resultError.code)) || data.error_type || data.errorType || data.status || 'failed';
688
+ return {
689
+ type: errorType,
690
+ status: data.status || 'failed',
691
+ message: errorEvent?.text || (typeof resultError === 'string' ? resultError : resultError?.message) || `Makaron run ${data.status || 'failed'}`,
692
+ recoverable: Boolean((typeof resultError === 'object' && resultError.recoverable) || data.recoverable),
693
+ detail: typeof resultError === 'object' ? resultError : undefined,
694
+ };
641
695
  }
642
696
 
643
- function buildNextAction(runId, events) {
697
+ function buildNextAction(runId, events, data = {}) {
698
+ const runError = buildRunError(data, events);
699
+ if (runError) {
700
+ return {
701
+ status: 'failed',
702
+ blocking: true,
703
+ runId,
704
+ error: runError,
705
+ next_command: buildNextCommand(runId),
706
+ next_commands: {
707
+ inspect: buildNextCommand(runId),
708
+ },
709
+ events,
710
+ };
711
+ }
644
712
  const checkpoint = findUnhandledApprovalMessages(events)[0] || null;
645
713
  const artifacts = events.filter(ev => ev.type === 'artifact');
646
714
  const approvals = events.filter(ev => ev.type === 'approval');
715
+ if (data.incomplete || ['queued', 'running', 'rendering', 'processing', 'generating', 'submitted'].includes(data.status)) {
716
+ return {
717
+ status: 'running',
718
+ blocking: false,
719
+ runId,
720
+ artifacts,
721
+ approvals,
722
+ next_command: buildNextCommand(runId),
723
+ next_commands: {
724
+ inspect: buildNextCommand(runId),
725
+ },
726
+ events,
727
+ };
728
+ }
647
729
  if (checkpoint) {
648
730
  return {
649
731
  status: 'needs_approval',
732
+ blocking: true,
650
733
  runId,
651
734
  checkpoint,
652
735
  next_commands: {
@@ -659,11 +742,30 @@ function buildNextAction(runId, events) {
659
742
  events,
660
743
  };
661
744
  }
745
+ const undeliveredArtifacts = findUndeliveredArtifacts(runId, events);
746
+ if (undeliveredArtifacts.length) {
747
+ const first = undeliveredArtifacts[0];
748
+ return {
749
+ status: 'has_artifacts',
750
+ blocking: true,
751
+ runId,
752
+ artifacts,
753
+ undelivered_artifacts: undeliveredArtifacts,
754
+ approvals,
755
+ next_commands: {
756
+ deliver: buildDeliverCommand(first.id, runId),
757
+ inspect: buildNextCommand(runId),
758
+ },
759
+ events,
760
+ };
761
+ }
662
762
  return {
663
- status: artifacts.length ? 'has_artifacts' : 'ready',
763
+ status: artifacts.length ? 'delivered' : 'ready',
764
+ blocking: false,
664
765
  runId,
665
766
  artifacts,
666
767
  approvals,
768
+ deliveries: loadDeliveries().filter(item => item.runId === runId),
667
769
  next_commands: {
668
770
  inspect: buildNextCommand(runId),
669
771
  },
@@ -713,9 +815,12 @@ async function printAgentNext(baseUrl, headers, runId, opts = {}) {
713
815
  const { json = false, checkpointMode = 'service', failOnCheckpoint = true } = opts;
714
816
  const data = await fetchRun(baseUrl, headers, runId, { events: true });
715
817
  const events = compactDialogueEvents(buildDialogueEvents(runId, { ...data, checkpointMode }));
716
- const action = buildNextAction(runId, events);
818
+ const action = buildNextAction(runId, events, data);
717
819
  if (json) {
718
820
  console.log(JSON.stringify(action, null, 2));
821
+ } else if (action.status === 'failed') {
822
+ console.log(`failed: ${action.error?.message || 'Makaron run failed'}`);
823
+ console.log(`inspect: ${action.next_commands.inspect}`);
719
824
  } else if (action.status === 'needs_approval') {
720
825
  console.log(`needs_approval: ${action.checkpoint.id}`);
721
826
  if (action.checkpoint.text) console.log(action.checkpoint.text);
@@ -725,14 +830,23 @@ async function printAgentNext(baseUrl, headers, runId, opts = {}) {
725
830
  console.log(`continue: ${action.next_commands.continue}`);
726
831
  console.log(`inspect: ${action.next_commands.inspect}`);
727
832
  } else if (action.status === 'has_artifacts') {
728
- for (const artifact of action.artifacts) {
833
+ for (const artifact of action.undelivered_artifacts || action.artifacts) {
729
834
  console.log(`${artifact.kind} ${artifact.status}${artifact.url ? ` ${artifact.url}` : ''}`);
730
835
  }
836
+ if (action.next_commands.deliver) console.log(`deliver: ${action.next_commands.deliver}`);
837
+ console.log(`inspect: ${action.next_commands.inspect}`);
838
+ } else if (action.status === 'running') {
839
+ console.log('running');
840
+ console.log(`inspect: ${action.next_commands.inspect}`);
841
+ } else if (action.status === 'delivered') {
842
+ console.log('delivered');
843
+ console.log(`inspect: ${action.next_commands.inspect}`);
731
844
  } else {
732
845
  console.log('ready');
733
846
  console.log(`inspect: ${action.next_commands.inspect}`);
734
847
  }
735
848
  if (action.status === 'needs_approval' && failOnCheckpoint) process.exit(3);
849
+ if (action.status === 'failed') process.exit(1);
736
850
  }
737
851
 
738
852
  function recordApproval(runId, messageId, choice, note) {
@@ -752,6 +866,24 @@ function recordApproval(runId, messageId, choice, note) {
752
866
  console.log(JSON.stringify(approval));
753
867
  }
754
868
 
869
+ function recordDelivery(runId, artifactId, opts = {}) {
870
+ const deliveries = loadDeliveries();
871
+ const delivery = {
872
+ type: 'delivery',
873
+ id: `delivery_${Date.now()}`,
874
+ runId,
875
+ artifactId,
876
+ status: 'recorded',
877
+ createdAt: new Date().toISOString(),
878
+ };
879
+ if (opts.channel) delivery.channel = opts.channel;
880
+ if (opts.messageId) delivery.messageId = opts.messageId;
881
+ if (opts.note) delivery.note = opts.note;
882
+ deliveries.push(delivery);
883
+ saveDeliveries(deliveries);
884
+ console.log(JSON.stringify(delivery));
885
+ }
886
+
755
887
  // ─── Watch (incremental event stream) ───────────────────────────────────────
756
888
 
757
889
  async function watchRun(baseUrl, headers, runId, opts = {}) {
@@ -1419,6 +1551,22 @@ if (command === '--version' || command === '-v' || command === 'version') {
1419
1551
  }
1420
1552
  await printDialogueEvents(baseUrl, headers, runId, { interval, jsonl, follow, failOnUnapproved, compact, checkpointMode });
1421
1553
 
1554
+ } else if (sub === 'deliver') {
1555
+ const artifactId = args[2];
1556
+ if (!artifactId) { console.error('Usage: makaron responses deliver <artifactId> --run <runId> [--channel <name>] [--message-id <id>] [--note <text>]'); process.exit(1); }
1557
+ let runId = null, note = null, channel = null, messageId = null;
1558
+ const noteParts = [];
1559
+ for (let i = 3; i < args.length; i++) {
1560
+ if (args[i] === '--run' && args[i + 1]) runId = args[++i];
1561
+ else if (args[i] === '--note' && args[i + 1]) note = args[++i];
1562
+ else if (args[i] === '--channel' && args[i + 1]) channel = args[++i];
1563
+ else if (args[i] === '--message-id' && args[i + 1]) messageId = args[++i];
1564
+ else noteParts.push(args[i]);
1565
+ }
1566
+ if (!runId) { console.error('Usage: makaron responses deliver <artifactId> --run <runId> [--channel <name>] [--message-id <id>] [--note <text>]'); process.exit(1); }
1567
+ if (!note && noteParts.length) note = noteParts.join(' ');
1568
+ recordDelivery(runId, artifactId, { note, channel, messageId });
1569
+
1422
1570
  } else if (sub === 'handle' || ['approve', 'revise', 'ask-user', 'continue'].includes(sub)) {
1423
1571
  const messageId = args[2];
1424
1572
  if (!messageId) {
@@ -1474,6 +1622,7 @@ if (command === '--version' || command === '-v' || command === 'version') {
1474
1622
  responses get <runId> --pick <field> Extract: first_image_url, first_video_url, project_url, output
1475
1623
  responses next <runId> --json New Agent entry: compact timeline + checkpoint guidance
1476
1624
  responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue
1625
+ responses deliver <artifactId> --run <runId> Record artifact delivered to the user
1477
1626
  responses events <runId> --jsonl Emit message/approval/artifact events for external Agents
1478
1627
  responses timeline <runId> --jsonl Alias for responses events
1479
1628
  responses timeline <runId> --checkpoint-mode off Disable text-only checkpoints for pure Q&A
@@ -1920,6 +2069,7 @@ Commands:
1920
2069
  responses get <runId> --wait Poll until completed
1921
2070
  responses next <runId> --json New Agent entry: compact timeline + checkpoint guidance
1922
2071
  responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue
2072
+ responses deliver <artifactId> --run <runId> Record artifact delivered to the user
1923
2073
  responses events <runId> --jsonl Emit message/approval/artifact events
1924
2074
  responses timeline <runId> --checkpoint-mode off Disable text-only checkpoints for pure Q&A
1925
2075
  responses list --project <id> List runs for a project
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makaron-cli",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Talk to Makaron Agent from the terminal — create projects, edit images, generate videos",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -133,15 +133,16 @@ For a new Agent, keep the default service flow to two response commands:
133
133
  ```bash
134
134
  npx makaron-cli responses next <runId> --json
135
135
  npx makaron-cli responses handle <messageId> --run <runId> --choice approve
136
+ npx makaron-cli responses deliver <artifactId> --run <runId> --channel feishu --message-id <messageId>
136
137
  ```
137
138
 
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
+ `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. While the run is still generating, it returns `status: "running"` and `blocking: false`. If completed image/video artifacts exist and have not been delivered, it returns `status: "has_artifacts"` and `blocking: true` with compact `undelivered_artifacts` entries containing `kind`, `status`, `url`, `fileName`, and `contentType` when available, plus `next_commands.deliver`; the Agent must send the artifact URL to the user, then record delivery with `responses deliver`. A non-checkpoint response returns `status: "ready"` or `status: "delivered"`. If a run failed, `responses next --json` prints `status: "failed"` and `blocking: true` plus `error.type`, `error.message`, `error.recoverable`, `error.detail`, and `next_command` to stdout before exiting `1`, so wrappers can parse the failure without scraping stderr. Use `--no-fail` to inspect checkpoint JSON without failing. For pure Q&A runs, use:
139
140
 
140
141
  ```bash
141
142
  npx makaron-cli responses next <runId> --json --checkpoint-mode off
142
143
  ```
143
144
 
144
- `responses handle` is the single checkpoint action command. Valid choices are `approve`, `revise`, `ask_user`, and `continue`.
145
+ `responses handle` is the single checkpoint action command. Valid choices are `approve`, `revise`, `ask_user`, and `continue`. `responses deliver` records that a completed artifact was actually delivered to the user, preventing repeated delivery prompts.
145
146
 
146
147
  ### Dialogue events for external Agents
147
148
 
@@ -327,9 +328,20 @@ if ! NEXT=$(npx makaron-cli responses next "$RUN_ID" --json); then
327
328
  send_message "$TEXT"
328
329
  npx makaron-cli responses handle "$MSG_ID" --run "$RUN_ID" --choice ask_user
329
330
  exit 3
331
+ elif [ "$STATUS" = "failed" ]; then
332
+ send_message "$(echo "$NEXT" | jq -r .error.message)"
333
+ exit 1
330
334
  fi
331
335
  fi
332
336
 
337
+ NEXT=$(npx makaron-cli responses next "$RUN_ID" --json --no-fail)
338
+ if [ "$(echo "$NEXT" | jq -r .status)" = "has_artifacts" ]; then
339
+ ARTIFACT_ID=$(echo "$NEXT" | jq -r ".undelivered_artifacts[0].id")
340
+ ARTIFACT_URL=$(echo "$NEXT" | jq -r ".undelivered_artifacts[0].url")
341
+ DELIVERY_MESSAGE_ID=$(send_message "$ARTIFACT_URL")
342
+ npx makaron-cli responses deliver "$ARTIFACT_ID" --run "$RUN_ID" --channel feishu --message-id "$DELIVERY_MESSAGE_ID"
343
+ fi
344
+
333
345
  RESULT=$(npx makaron-cli responses get "$RUN_ID" --wait --json)
334
346
  ```
335
347