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 +52 -4
- package/SKILL.md +52 -4
- package/bin/makaron.mjs +214 -25
- package/package.json +1 -1
- package/skills/makaron/SKILL.md +52 -4
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,
|
|
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,
|
|
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
|
|
450
|
+
function inferApprovalIntent(text) {
|
|
451
451
|
if (!text) return false;
|
|
452
452
|
const normalized = text.toLowerCase().replace(/\s+/g, ' ').trim();
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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) {
|
|
1258
|
-
|
|
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
|
|
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,
|
|
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
package/skills/makaron/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,
|
|
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:
|