makaron-cli 0.7.10 → 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 +41 -12
- package/SKILL.md +41 -12
- package/bin/makaron.mjs +262 -8
- package/package.json +1 -1
- package/skills/makaron/SKILL.md +41 -12
package/README.md
CHANGED
|
@@ -134,6 +134,24 @@ 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
|
+
npx makaron-cli responses deliver <artifactId> --run <runId> --channel feishu --message-id <messageId>
|
|
145
|
+
```
|
|
146
|
+
|
|
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:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npx makaron-cli responses next <runId> --json --checkpoint-mode off
|
|
151
|
+
```
|
|
152
|
+
|
|
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.
|
|
154
|
+
|
|
137
155
|
### Dialogue events for external Agents
|
|
138
156
|
|
|
139
157
|
Use this when another Agent needs to read what Makaron said, handle text checkpoints, and relay artifacts without inventing customer-service wording.
|
|
@@ -145,6 +163,8 @@ npx makaron-cli responses events <runId> --jsonl
|
|
|
145
163
|
# pure Q&A view: npx makaron-cli responses timeline <runId> --jsonl --checkpoint-mode off
|
|
146
164
|
```
|
|
147
165
|
|
|
166
|
+
`events` and `timeline` are lower-level commands. New Agents should start with `responses next` and use these only when they need raw event streams.
|
|
167
|
+
|
|
148
168
|
Events use only three factual types:
|
|
149
169
|
|
|
150
170
|
```json
|
|
@@ -160,6 +180,7 @@ npx makaron-cli responses approve msg_1 --run <runId> --note "Proceed."
|
|
|
160
180
|
npx makaron-cli responses revise msg_1 --run <runId> "make it softer"
|
|
161
181
|
npx makaron-cli responses ask-user msg_1 --run <runId>
|
|
162
182
|
npx makaron-cli responses continue msg_1 --run <runId>
|
|
183
|
+
npx makaron-cli responses handle msg_1 --run <runId> --choice approve
|
|
163
184
|
```
|
|
164
185
|
|
|
165
186
|
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:
|
|
@@ -310,18 +331,26 @@ RUN_ID=$(echo "$RUN_JSON" | jq -r .runId)
|
|
|
310
331
|
PROJECT_URL=$(echo "$RUN_JSON" | jq -r .projectUrl)
|
|
311
332
|
send_message "Project created: $PROJECT_URL"
|
|
312
333
|
|
|
313
|
-
if ! npx makaron-cli responses
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
TEXT=$(echo "$
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
334
|
+
if ! NEXT=$(npx makaron-cli responses next "$RUN_ID" --json); then
|
|
335
|
+
STATUS=$(echo "$NEXT" | jq -r .status)
|
|
336
|
+
if [ "$STATUS" = "needs_approval" ]; then
|
|
337
|
+
MSG_ID=$(echo "$NEXT" | jq -r .checkpoint.id)
|
|
338
|
+
TEXT=$(echo "$NEXT" | jq -r .checkpoint.text)
|
|
339
|
+
send_message "$TEXT"
|
|
340
|
+
npx makaron-cli responses handle "$MSG_ID" --run "$RUN_ID" --choice ask_user
|
|
341
|
+
exit 3
|
|
342
|
+
elif [ "$STATUS" = "failed" ]; then
|
|
343
|
+
send_message "$(echo "$NEXT" | jq -r .error.message)"
|
|
344
|
+
exit 1
|
|
345
|
+
fi
|
|
346
|
+
fi
|
|
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"
|
|
325
354
|
fi
|
|
326
355
|
|
|
327
356
|
RESULT=$(npx makaron-cli responses get "$RUN_ID" --wait --json)
|
package/SKILL.md
CHANGED
|
@@ -126,6 +126,24 @@ 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
|
+
npx makaron-cli responses deliver <artifactId> --run <runId> --channel feishu --message-id <messageId>
|
|
137
|
+
```
|
|
138
|
+
|
|
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:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npx makaron-cli responses next <runId> --json --checkpoint-mode off
|
|
143
|
+
```
|
|
144
|
+
|
|
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.
|
|
146
|
+
|
|
129
147
|
### Dialogue events for external Agents
|
|
130
148
|
|
|
131
149
|
Use this when another Agent needs to read what Makaron said, handle text checkpoints, and relay artifacts without inventing customer-service wording.
|
|
@@ -137,6 +155,8 @@ npx makaron-cli responses events <runId> --jsonl
|
|
|
137
155
|
# pure Q&A view: npx makaron-cli responses timeline <runId> --jsonl --checkpoint-mode off
|
|
138
156
|
```
|
|
139
157
|
|
|
158
|
+
`events` and `timeline` are lower-level commands. New Agents should start with `responses next` and use these only when they need raw event streams.
|
|
159
|
+
|
|
140
160
|
Events use only three factual types:
|
|
141
161
|
|
|
142
162
|
```json
|
|
@@ -152,6 +172,7 @@ npx makaron-cli responses approve msg_1 --run <runId> --note "Proceed."
|
|
|
152
172
|
npx makaron-cli responses revise msg_1 --run <runId> "make it softer"
|
|
153
173
|
npx makaron-cli responses ask-user msg_1 --run <runId>
|
|
154
174
|
npx makaron-cli responses continue msg_1 --run <runId>
|
|
175
|
+
npx makaron-cli responses handle msg_1 --run <runId> --choice approve
|
|
155
176
|
```
|
|
156
177
|
|
|
157
178
|
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:
|
|
@@ -299,18 +320,26 @@ RUN_ID=$(echo "$RUN_JSON" | jq -r .runId)
|
|
|
299
320
|
PROJECT_URL=$(echo "$RUN_JSON" | jq -r .projectUrl)
|
|
300
321
|
send_message "Project created: $PROJECT_URL"
|
|
301
322
|
|
|
302
|
-
if ! npx makaron-cli responses
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
TEXT=$(echo "$
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
323
|
+
if ! NEXT=$(npx makaron-cli responses next "$RUN_ID" --json); then
|
|
324
|
+
STATUS=$(echo "$NEXT" | jq -r .status)
|
|
325
|
+
if [ "$STATUS" = "needs_approval" ]; then
|
|
326
|
+
MSG_ID=$(echo "$NEXT" | jq -r .checkpoint.id)
|
|
327
|
+
TEXT=$(echo "$NEXT" | jq -r .checkpoint.text)
|
|
328
|
+
send_message "$TEXT"
|
|
329
|
+
npx makaron-cli responses handle "$MSG_ID" --run "$RUN_ID" --choice ask_user
|
|
330
|
+
exit 3
|
|
331
|
+
elif [ "$STATUS" = "failed" ]; then
|
|
332
|
+
send_message "$(echo "$NEXT" | jq -r .error.message)"
|
|
333
|
+
exit 1
|
|
334
|
+
fi
|
|
335
|
+
fi
|
|
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"
|
|
314
343
|
fi
|
|
315
344
|
|
|
316
345
|
RESULT=$(npx makaron-cli responses get "$RUN_ID" --wait --json)
|
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;
|
|
@@ -608,14 +628,18 @@ function compactDialogueEvents(events) {
|
|
|
608
628
|
&& prev.type === 'message'
|
|
609
629
|
&& ev.source_type === 'content'
|
|
610
630
|
&& prev.source_type === 'content'
|
|
611
|
-
&& !ev.requires_approval
|
|
612
|
-
&& !prev.requires_approval
|
|
613
631
|
&& !ev.proposal
|
|
614
|
-
&& !prev.proposal
|
|
632
|
+
&& !prev.proposal
|
|
633
|
+
&& Boolean(ev.requires_approval) === Boolean(prev.requires_approval)
|
|
634
|
+
&& (ev.approval_reason || '') === (prev.approval_reason || '');
|
|
615
635
|
if (canMerge) {
|
|
616
636
|
prev.text = `${prev.text}${ev.text}`;
|
|
617
637
|
prev.id = `${prev.id}+${ev.id}`;
|
|
618
638
|
prev.seq_end = ev.seq;
|
|
639
|
+
if (ev.requires_approval) {
|
|
640
|
+
prev.requires_approval = true;
|
|
641
|
+
prev.approval_options = prev.approval_options || ev.approval_options;
|
|
642
|
+
}
|
|
619
643
|
} else {
|
|
620
644
|
compacted.push({ ...ev });
|
|
621
645
|
}
|
|
@@ -628,6 +652,127 @@ function findUnhandledApprovalMessages(events) {
|
|
|
628
652
|
return events.filter(ev => ev.type === 'message' && ev.requires_approval && !approved.has(ev.id));
|
|
629
653
|
}
|
|
630
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
|
+
|
|
671
|
+
function buildHandleCommand(messageId, runId, choice) {
|
|
672
|
+
return `${buildCliCommand()} responses handle ${messageId} --run ${runId} --choice ${choice}`;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function buildNextCommand(runId) {
|
|
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
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
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
|
+
}
|
|
712
|
+
const checkpoint = findUnhandledApprovalMessages(events)[0] || null;
|
|
713
|
+
const artifacts = events.filter(ev => ev.type === 'artifact');
|
|
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
|
+
}
|
|
729
|
+
if (checkpoint) {
|
|
730
|
+
return {
|
|
731
|
+
status: 'needs_approval',
|
|
732
|
+
blocking: true,
|
|
733
|
+
runId,
|
|
734
|
+
checkpoint,
|
|
735
|
+
next_commands: {
|
|
736
|
+
approve: buildHandleCommand(checkpoint.id, runId, 'approve'),
|
|
737
|
+
revise: `${buildHandleCommand(checkpoint.id, runId, 'revise')} --note "what to change"`,
|
|
738
|
+
ask_user: buildHandleCommand(checkpoint.id, runId, 'ask_user'),
|
|
739
|
+
continue: buildHandleCommand(checkpoint.id, runId, 'continue'),
|
|
740
|
+
inspect: buildNextCommand(runId),
|
|
741
|
+
},
|
|
742
|
+
events,
|
|
743
|
+
};
|
|
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
|
+
}
|
|
762
|
+
return {
|
|
763
|
+
status: artifacts.length ? 'delivered' : 'ready',
|
|
764
|
+
blocking: false,
|
|
765
|
+
runId,
|
|
766
|
+
artifacts,
|
|
767
|
+
approvals,
|
|
768
|
+
deliveries: loadDeliveries().filter(item => item.runId === runId),
|
|
769
|
+
next_commands: {
|
|
770
|
+
inspect: buildNextCommand(runId),
|
|
771
|
+
},
|
|
772
|
+
events,
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
|
|
631
776
|
async function fetchRun(baseUrl, headers, runId, opts = {}) {
|
|
632
777
|
const params = new URLSearchParams();
|
|
633
778
|
if (opts.events) params.set('events', 'true');
|
|
@@ -666,6 +811,44 @@ async function printDialogueEvents(baseUrl, headers, runId, opts = {}) {
|
|
|
666
811
|
}
|
|
667
812
|
}
|
|
668
813
|
|
|
814
|
+
async function printAgentNext(baseUrl, headers, runId, opts = {}) {
|
|
815
|
+
const { json = false, checkpointMode = 'service', failOnCheckpoint = true } = opts;
|
|
816
|
+
const data = await fetchRun(baseUrl, headers, runId, { events: true });
|
|
817
|
+
const events = compactDialogueEvents(buildDialogueEvents(runId, { ...data, checkpointMode }));
|
|
818
|
+
const action = buildNextAction(runId, events, data);
|
|
819
|
+
if (json) {
|
|
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}`);
|
|
824
|
+
} else if (action.status === 'needs_approval') {
|
|
825
|
+
console.log(`needs_approval: ${action.checkpoint.id}`);
|
|
826
|
+
if (action.checkpoint.text) console.log(action.checkpoint.text);
|
|
827
|
+
console.log(`approve: ${action.next_commands.approve}`);
|
|
828
|
+
console.log(`revise: ${action.next_commands.revise}`);
|
|
829
|
+
console.log(`ask_user: ${action.next_commands.ask_user}`);
|
|
830
|
+
console.log(`continue: ${action.next_commands.continue}`);
|
|
831
|
+
console.log(`inspect: ${action.next_commands.inspect}`);
|
|
832
|
+
} else if (action.status === 'has_artifacts') {
|
|
833
|
+
for (const artifact of action.undelivered_artifacts || action.artifacts) {
|
|
834
|
+
console.log(`${artifact.kind} ${artifact.status}${artifact.url ? ` ${artifact.url}` : ''}`);
|
|
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}`);
|
|
844
|
+
} else {
|
|
845
|
+
console.log('ready');
|
|
846
|
+
console.log(`inspect: ${action.next_commands.inspect}`);
|
|
847
|
+
}
|
|
848
|
+
if (action.status === 'needs_approval' && failOnCheckpoint) process.exit(3);
|
|
849
|
+
if (action.status === 'failed') process.exit(1);
|
|
850
|
+
}
|
|
851
|
+
|
|
669
852
|
function recordApproval(runId, messageId, choice, note) {
|
|
670
853
|
const approvals = loadApprovals();
|
|
671
854
|
const approval = {
|
|
@@ -683,6 +866,24 @@ function recordApproval(runId, messageId, choice, note) {
|
|
|
683
866
|
console.log(JSON.stringify(approval));
|
|
684
867
|
}
|
|
685
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
|
+
|
|
686
887
|
// ─── Watch (incremental event stream) ───────────────────────────────────────
|
|
687
888
|
|
|
688
889
|
async function watchRun(baseUrl, headers, runId, opts = {}) {
|
|
@@ -1317,6 +1518,21 @@ if (command === '--version' || command === '-v' || command === 'version') {
|
|
|
1317
1518
|
}
|
|
1318
1519
|
await watchRun(baseUrl, headers, runId, { interval, jsonl });
|
|
1319
1520
|
|
|
1521
|
+
} else if (sub === 'next') {
|
|
1522
|
+
const runId = args[2];
|
|
1523
|
+
if (!runId) { console.error('Usage: makaron responses next <runId> [--json] [--checkpoint-mode service|off] [--no-fail]'); process.exit(1); }
|
|
1524
|
+
let jsonOutput = false, checkpointMode = 'service', failOnCheckpoint = true;
|
|
1525
|
+
for (let i = 3; i < args.length; i++) {
|
|
1526
|
+
if (args[i] === '--json') jsonOutput = true;
|
|
1527
|
+
else if (args[i] === '--checkpoint-mode' && args[i + 1]) checkpointMode = args[++i];
|
|
1528
|
+
else if (args[i] === '--no-fail') failOnCheckpoint = false;
|
|
1529
|
+
}
|
|
1530
|
+
if (!['service', 'off'].includes(checkpointMode)) {
|
|
1531
|
+
console.error('--checkpoint-mode must be service or off');
|
|
1532
|
+
process.exit(1);
|
|
1533
|
+
}
|
|
1534
|
+
await printAgentNext(baseUrl, headers, runId, { json: jsonOutput, checkpointMode, failOnCheckpoint });
|
|
1535
|
+
|
|
1320
1536
|
} else if (sub === 'events' || sub === 'timeline') {
|
|
1321
1537
|
const runId = args[2];
|
|
1322
1538
|
if (!runId) { console.error(`Usage: makaron responses ${sub} <runId> [--jsonl] [--compact] [--checkpoint-mode service|off] [--follow] [--interval <ms>] [--fail-on-unapproved]`); process.exit(1); }
|
|
@@ -1335,19 +1551,51 @@ if (command === '--version' || command === '-v' || command === 'version') {
|
|
|
1335
1551
|
}
|
|
1336
1552
|
await printDialogueEvents(baseUrl, headers, runId, { interval, jsonl, follow, failOnUnapproved, compact, checkpointMode });
|
|
1337
1553
|
|
|
1338
|
-
} else if (
|
|
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
|
+
|
|
1570
|
+
} else if (sub === 'handle' || ['approve', 'revise', 'ask-user', 'continue'].includes(sub)) {
|
|
1339
1571
|
const messageId = args[2];
|
|
1340
|
-
if (!messageId) {
|
|
1341
|
-
|
|
1572
|
+
if (!messageId) {
|
|
1573
|
+
console.error(sub === 'handle'
|
|
1574
|
+
? 'Usage: makaron responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue [--note <text>]'
|
|
1575
|
+
: `Usage: makaron responses ${sub} <messageId> --run <runId> [--note <text>]`);
|
|
1576
|
+
process.exit(1);
|
|
1577
|
+
}
|
|
1578
|
+
let runId = null, note = null, choice = sub === 'handle' ? null : sub;
|
|
1342
1579
|
const noteParts = [];
|
|
1343
1580
|
for (let i = 3; i < args.length; i++) {
|
|
1344
1581
|
if (args[i] === '--run' && args[i + 1]) runId = args[++i];
|
|
1345
1582
|
else if (args[i] === '--note' && args[i + 1]) note = args[++i];
|
|
1583
|
+
else if (args[i] === '--choice' && args[i + 1]) choice = args[++i];
|
|
1346
1584
|
else noteParts.push(args[i]);
|
|
1347
1585
|
}
|
|
1348
|
-
if (!runId
|
|
1586
|
+
if (!runId || !choice) {
|
|
1587
|
+
console.error(sub === 'handle'
|
|
1588
|
+
? 'Usage: makaron responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue [--note <text>]'
|
|
1589
|
+
: `Usage: makaron responses ${sub} <messageId> --run <runId> [--note <text>]`);
|
|
1590
|
+
process.exit(1);
|
|
1591
|
+
}
|
|
1592
|
+
choice = choice === 'ask-user' ? 'ask_user' : choice;
|
|
1593
|
+
if (!['approve', 'revise', 'ask_user', 'continue'].includes(choice)) {
|
|
1594
|
+
console.error('--choice must be approve, revise, ask_user, or continue');
|
|
1595
|
+
process.exit(1);
|
|
1596
|
+
}
|
|
1349
1597
|
if (!note && noteParts.length) note = noteParts.join(' ');
|
|
1350
|
-
recordApproval(runId, messageId,
|
|
1598
|
+
recordApproval(runId, messageId, choice, note);
|
|
1351
1599
|
|
|
1352
1600
|
} else if (sub === 'list') {
|
|
1353
1601
|
let projectId = null;
|
|
@@ -1372,6 +1620,9 @@ if (command === '--version' || command === '-v' || command === 'version') {
|
|
|
1372
1620
|
responses get <runId> Get status and output (JSON)
|
|
1373
1621
|
responses get <runId> --wait Poll until completed
|
|
1374
1622
|
responses get <runId> --pick <field> Extract: first_image_url, first_video_url, project_url, output
|
|
1623
|
+
responses next <runId> --json New Agent entry: compact timeline + checkpoint guidance
|
|
1624
|
+
responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue
|
|
1625
|
+
responses deliver <artifactId> --run <runId> Record artifact delivered to the user
|
|
1375
1626
|
responses events <runId> --jsonl Emit message/approval/artifact events for external Agents
|
|
1376
1627
|
responses timeline <runId> --jsonl Alias for responses events
|
|
1377
1628
|
responses timeline <runId> --checkpoint-mode off Disable text-only checkpoints for pure Q&A
|
|
@@ -1816,6 +2067,9 @@ Commands:
|
|
|
1816
2067
|
|
|
1817
2068
|
responses get <runId> Get run status and results
|
|
1818
2069
|
responses get <runId> --wait Poll until completed
|
|
2070
|
+
responses next <runId> --json New Agent entry: compact timeline + checkpoint guidance
|
|
2071
|
+
responses handle <messageId> --run <runId> --choice approve|revise|ask_user|continue
|
|
2072
|
+
responses deliver <artifactId> --run <runId> Record artifact delivered to the user
|
|
1819
2073
|
responses events <runId> --jsonl Emit message/approval/artifact events
|
|
1820
2074
|
responses timeline <runId> --checkpoint-mode off Disable text-only checkpoints for pure Q&A
|
|
1821
2075
|
responses list --project <id> List runs for a project
|
package/package.json
CHANGED
package/skills/makaron/SKILL.md
CHANGED
|
@@ -126,6 +126,24 @@ 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
|
+
npx makaron-cli responses deliver <artifactId> --run <runId> --channel feishu --message-id <messageId>
|
|
137
|
+
```
|
|
138
|
+
|
|
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:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npx makaron-cli responses next <runId> --json --checkpoint-mode off
|
|
143
|
+
```
|
|
144
|
+
|
|
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.
|
|
146
|
+
|
|
129
147
|
### Dialogue events for external Agents
|
|
130
148
|
|
|
131
149
|
Use this when another Agent needs to read what Makaron said, handle text checkpoints, and relay artifacts without inventing customer-service wording.
|
|
@@ -137,6 +155,8 @@ npx makaron-cli responses events <runId> --jsonl
|
|
|
137
155
|
# pure Q&A view: npx makaron-cli responses timeline <runId> --jsonl --checkpoint-mode off
|
|
138
156
|
```
|
|
139
157
|
|
|
158
|
+
`events` and `timeline` are lower-level commands. New Agents should start with `responses next` and use these only when they need raw event streams.
|
|
159
|
+
|
|
140
160
|
Events use only three factual types:
|
|
141
161
|
|
|
142
162
|
```json
|
|
@@ -152,6 +172,7 @@ npx makaron-cli responses approve msg_1 --run <runId> --note "Proceed."
|
|
|
152
172
|
npx makaron-cli responses revise msg_1 --run <runId> "make it softer"
|
|
153
173
|
npx makaron-cli responses ask-user msg_1 --run <runId>
|
|
154
174
|
npx makaron-cli responses continue msg_1 --run <runId>
|
|
175
|
+
npx makaron-cli responses handle msg_1 --run <runId> --choice approve
|
|
155
176
|
```
|
|
156
177
|
|
|
157
178
|
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:
|
|
@@ -299,18 +320,26 @@ RUN_ID=$(echo "$RUN_JSON" | jq -r .runId)
|
|
|
299
320
|
PROJECT_URL=$(echo "$RUN_JSON" | jq -r .projectUrl)
|
|
300
321
|
send_message "Project created: $PROJECT_URL"
|
|
301
322
|
|
|
302
|
-
if ! npx makaron-cli responses
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
TEXT=$(echo "$
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
323
|
+
if ! NEXT=$(npx makaron-cli responses next "$RUN_ID" --json); then
|
|
324
|
+
STATUS=$(echo "$NEXT" | jq -r .status)
|
|
325
|
+
if [ "$STATUS" = "needs_approval" ]; then
|
|
326
|
+
MSG_ID=$(echo "$NEXT" | jq -r .checkpoint.id)
|
|
327
|
+
TEXT=$(echo "$NEXT" | jq -r .checkpoint.text)
|
|
328
|
+
send_message "$TEXT"
|
|
329
|
+
npx makaron-cli responses handle "$MSG_ID" --run "$RUN_ID" --choice ask_user
|
|
330
|
+
exit 3
|
|
331
|
+
elif [ "$STATUS" = "failed" ]; then
|
|
332
|
+
send_message "$(echo "$NEXT" | jq -r .error.message)"
|
|
333
|
+
exit 1
|
|
334
|
+
fi
|
|
335
|
+
fi
|
|
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"
|
|
314
343
|
fi
|
|
315
344
|
|
|
316
345
|
RESULT=$(npx makaron-cli responses get "$RUN_ID" --wait --json)
|