cozyclay 3.1.0 → 3.1.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/CHANGELOG.md +26 -0
- package/bin/agent/agent-routes.mjs +8 -7
- package/bin/agent/plan-tool.mjs +114 -0
- package/bin/agent/script-tool.mjs +2 -0
- package/bin/agent/sculpt-sketch.mjs +190 -0
- package/bin/agent/session-store.mjs +2 -1
- package/bin/agent/sketch-code.mjs +151 -0
- package/bin/agent/sketch-sandbox.mjs +102 -0
- package/bin/agent/studio-prompt.mjs +1 -1
- package/bin/agent/turn-frames.mjs +4 -0
- package/dist/app/index.html +5 -5
- package/dist/assets/TaskBoard-ngdqiu7u.js +1 -0
- package/dist/assets/app-DmR7jBkz.js +5144 -0
- package/dist/assets/{motion3-compile-B887n77J.js → motion3-compile-BB3_1-EB.js} +1 -1
- package/dist/assets/{scene-asset-sync-COGOlF5T.css → scene-asset-sync-CBx7XRti.css} +1 -1
- package/dist/assets/scene-asset-sync-Cgdtr1Yw.js +13 -0
- package/dist/assets/task-board-ErQvfoaz.js +14 -0
- package/dist/assets/workflow-zQM-EKW1.js +179 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/workflow/index.html +4 -4
- package/mcp/tool-handlers.mjs +2 -2
- package/package.json +3 -1
- package/src/App.jsx +2 -2
- package/src/commands/elements.js +4 -3
- package/src/commands/objects.js +1 -1
- package/src/material-look.js +189 -0
- package/src/material-presets.js +78 -0
- package/src/material-textures.js +157 -0
- package/src/object-look.js +9 -5
- package/src/primitive-look.js +38 -0
- package/src/props.jsx +20 -28
- package/src/render-passes.js +7 -1
- package/src/scene-objects.js +2 -2
- package/src/sculpt-colours.js +65 -0
- package/src/sculpt-compare.js +13 -50
- package/src/sculpt-csg.js +112 -0
- package/src/sculpt-curve.js +123 -0
- package/src/sculpt-cutters.js +43 -0
- package/src/sculpt-detail.js +207 -0
- package/src/sculpt-geometry-loft-convex.js +100 -0
- package/src/sculpt-geometry-shapes.js +250 -0
- package/src/sculpt-geometry.js +33 -15
- package/src/sculpt-loft-rings.js +225 -0
- package/src/sculpt-noise.js +65 -0
- package/src/sculpt-part-fields.js +27 -0
- package/src/sculpt-recipe-read.js +65 -0
- package/src/sculpt-recipe.js +33 -75
- package/src/sculpt-run.js +1 -1
- package/src/sculpt-scatter-items.js +183 -0
- package/src/sculpt-scatter-shape.js +57 -0
- package/src/sculpt-scatter.js +124 -0
- package/src/sculpt-shapes-curved.js +211 -0
- package/src/sculpt-shapes-loft-convex.js +218 -0
- package/src/sculpt-shapes.js +138 -0
- package/src/sculpt-sketch-state.js +76 -0
- package/src/sculpt-sketch.jsx +124 -0
- package/src/sculpt-surface-fields.js +65 -0
- package/src/studio-elements.js +5 -1
- package/src/workflow/AgentPanel.jsx +4 -4
- package/src/workflow/AgentPlanCard.jsx +41 -0
- package/src/workflow/agent-client.js +14 -11
- package/src/workflow/agent-plan.css +93 -0
- package/src/workflow/agent-plan.js +59 -0
- package/tools/run-tests.mjs +3 -3
- package/dist/assets/TaskBoard-DdDXMgZm.js +0 -1
- package/dist/assets/app-B2BpfCl2.js +0 -5088
- package/dist/assets/scene-asset-sync-CQzEdsqu.js +0 -13
- package/dist/assets/task-board-BKaM7d-9.js +0 -14
- package/dist/assets/workflow-B8Dhzexj.js +0 -179
|
@@ -7,8 +7,7 @@ import { ko } from "../locale.js";
|
|
|
7
7
|
import {
|
|
8
8
|
AGENT_PANEL_OVERLAY_BREAKPOINT,
|
|
9
9
|
AGENT_PANEL_RAIL_WIDTH,
|
|
10
|
-
AGENT_PANEL_WIDTH_MAX,
|
|
11
|
-
AGENT_PANEL_WIDTH_MIN,
|
|
10
|
+
AGENT_PANEL_WIDTH_MAX, AGENT_PANEL_WIDTH_MIN,
|
|
12
11
|
AGENT_STATES,
|
|
13
12
|
agentServerUnavailable,
|
|
14
13
|
describeActivity,
|
|
@@ -29,8 +28,7 @@ import {
|
|
|
29
28
|
formatTurnClock,
|
|
30
29
|
formatResetIn,
|
|
31
30
|
readStoredPanelWidth,
|
|
32
|
-
storeModel,
|
|
33
|
-
storePanelWidth,
|
|
31
|
+
storeModel, storePanelWidth,
|
|
34
32
|
STUDIO_SESSION_STORAGE_KEY,
|
|
35
33
|
selectedModelRoles,
|
|
36
34
|
shortModelId,
|
|
@@ -43,6 +41,7 @@ const TaskBoard = lazy(() => import("./TaskBoard.jsx"));
|
|
|
43
41
|
import { AgentProgressCard } from "./AgentProgressCard.jsx";
|
|
44
42
|
import { LoginPanel } from "./LoginPanel.jsx";
|
|
45
43
|
import { TurnSteps } from "./AgentTurnSteps.jsx";
|
|
44
|
+
import { PlanCard } from "./AgentPlanCard.jsx";
|
|
46
45
|
import { ReplyText } from "./AgentReplyText.jsx";
|
|
47
46
|
import { stepLabel, turnLayout } from "./agent-turn.js";
|
|
48
47
|
|
|
@@ -908,6 +907,7 @@ export default function AgentPanel({
|
|
|
908
907
|
if (item.kind === "receipt") return <div className="agent-row" key={item.id}><ReceiptCard item={item} /></div>;
|
|
909
908
|
if (item.kind === "failure") return <div className="agent-row" key={item.id}><FailureCard failure={item.failure} onRetry={() => runTurn(chat.lastPrompt)} onFixKey={openProviderKeys} /></div>;
|
|
910
909
|
if (item.kind === "supervisor") return <div className="agent-row" key={item.id}><SupervisorCard note={item} onSend={(text) => { store.setDraft(text); runTurn(text); }} /></div>;
|
|
910
|
+
if (item.kind === "plan") return <PlanCard key={item.id} plan={item} live={streaming && !item.outcome} />;
|
|
911
911
|
return <div className="agent-row" key={item.id}><ImageResultCard image={item} onUse={(image) => store.applyImage(image.id)} onUndo={(image) => store.undoImage(image.id)} onRegenerate={() => runTurn(chat.lastPrompt)} onOpen={setLightbox} /></div>;
|
|
912
912
|
})}
|
|
913
913
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// A turn's step checklist in the agent panel (agent-plan.js keeps it): a header that says how far the plan is ("2/5")
|
|
2
|
+
// beside a thin progress bar, and one row per step: done steps checked, the step under way highlighted with a soft
|
|
3
|
+
// pulse, the rest waiting. While the turn runs the card stays pinned to the top of the transcript, so what is happening
|
|
4
|
+
// and what comes next stay in view however long the step list below grows. Once the turn ends nothing pulses, and a
|
|
5
|
+
// step a failed or stopped turn left unfinished reads muted.
|
|
6
|
+
import { FiCheck, FiMinus } from "react-icons/fi";
|
|
7
|
+
import { ko } from "../locale.js";
|
|
8
|
+
import "./agent-plan.css";
|
|
9
|
+
|
|
10
|
+
const STATUS_WORDS = {
|
|
11
|
+
done: ko("done", "완료"),
|
|
12
|
+
active: ko("in progress", "진행 중"),
|
|
13
|
+
pending: ko("next", "대기"),
|
|
14
|
+
unfinished: ko("not finished", "미완료"),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function PlanMark({ status }) {
|
|
18
|
+
if (status === "done") return <FiCheck className="agent-plan-mark done" size={11} aria-hidden="true" />;
|
|
19
|
+
if (status === "unfinished") return <FiMinus className="agent-plan-mark unfinished" size={11} aria-hidden="true" />;
|
|
20
|
+
return <span className={`agent-plan-mark ${status}`} aria-hidden="true" />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function PlanCard({ plan, live }) {
|
|
24
|
+
const total = plan.steps.length, done = plan.steps.filter(step => step.status === "done").length;
|
|
25
|
+
const state = live ? "live" : plan.outcome ?? "done";
|
|
26
|
+
return <div className="agent-row agent-plan-row" data-plan-live={live ? "true" : "false"}>
|
|
27
|
+
<section className="agent-card agent-plan" data-agent-card="plan" data-plan-state={state} aria-label={ko(`Plan, ${done} of ${total} done`, `계획, ${total}단계 중 ${done}단계 완료`)}>
|
|
28
|
+
<div className="agent-plan-head">
|
|
29
|
+
<span className="agent-plan-title">{ko("Plan", "계획")}</span>
|
|
30
|
+
<span className="agent-plan-bar" aria-hidden="true"><span className="agent-plan-fill" style={{ "--agent-plan-done": total ? done / total : 0 }} /></span>
|
|
31
|
+
<span className="agent-plan-count">{done}/{total}</span>
|
|
32
|
+
</div>
|
|
33
|
+
<ol className="agent-plan-steps">
|
|
34
|
+
{plan.steps.map((step, index) => <li key={index} className={`agent-plan-step ${step.status}`} data-plan-status={step.status} aria-current={step.status === "active" ? "step" : undefined} aria-label={`${step.text}: ${STATUS_WORDS[step.status] ?? step.status}`}>
|
|
35
|
+
<PlanMark status={step.status} />
|
|
36
|
+
<span className="agent-plan-text">{step.text}</span>
|
|
37
|
+
</li>)}
|
|
38
|
+
</ol>
|
|
39
|
+
</section>
|
|
40
|
+
</div>;
|
|
41
|
+
}
|
|
@@ -28,8 +28,9 @@ import { appendAttachments, ATTACHMENT_MAX_COUNT } from "./attachment-image.js";
|
|
|
28
28
|
import { ko } from "../locale.js";
|
|
29
29
|
import { AGENT_TOOL_CATEGORIES, EXECUTION_TELEMETRY_VALUES } from "../execution-telemetry.js";
|
|
30
30
|
import { STUDIO_VARIANTS, validateReceipt } from "../studio-agent-protocol.js";
|
|
31
|
-
import { requestAgentImage, sidecarRequest, sidecarUrl } from "../agent-image-client.js";
|
|
31
|
+
import { requestAgentImage, sidecarRequest, sidecarUrl } from "../agent-image-client.js"; import { clearSculptSketch, sculptSketchFrame } from "../sculpt-sketch-state.js";
|
|
32
32
|
import { actionLabelsOf, changedLine, scriptNow, stepFields, thinkingLine, withCompose, withThinking, withToolStart, withTurnEnd } from "./agent-turn.js";
|
|
33
|
+
import { isPlanFrame, restoredPlans, withPlanEnd, withPlanFrame } from "./agent-plan.js";
|
|
33
34
|
|
|
34
35
|
const AGENT_PANEL_WIDTH_KEY = "cozyclay.workflow.agentPanel.width";
|
|
35
36
|
export const AGENT_PANEL_WIDTH_DEFAULT = 360;
|
|
@@ -1325,6 +1326,8 @@ export function createAgentChatStore({
|
|
|
1325
1326
|
});
|
|
1326
1327
|
return;
|
|
1327
1328
|
}
|
|
1329
|
+
// The turn's step checklist (bin/agent/plan-tool.mjs): one card under the prompt; the plan call itself is no step.
|
|
1330
|
+
if (isPlanFrame(event)) { setItems((items) => withPlanFrame(items, event, newId)); return; }
|
|
1328
1331
|
if (event?.type === "tool.start") {
|
|
1329
1332
|
setItems((items) => withToolStart(items, { kind: "tool", id: event.callId || newId(), callId: event.callId, name: event.name, label: event.label, args: event.args, status: "running", startedAt: clock(), ...stepFields(event.name, event.args, actionLabels) }));
|
|
1330
1333
|
return;
|
|
@@ -1406,11 +1409,10 @@ export function createAgentChatStore({
|
|
|
1406
1409
|
set({ sessionId: newId(), turnId: null, items: [], pendingAttachments: [], rateLimit: null, lastPrompt: "", turnStartedAt: null, lastTurn: null });
|
|
1407
1410
|
};
|
|
1408
1411
|
const restore = (transcript, sessionId = state.sessionId) => {
|
|
1409
|
-
abandonSupervisorNotes();
|
|
1410
|
-
seenReceipts.clear();
|
|
1411
|
-
settledActions.clear();
|
|
1412
|
+
abandonSupervisorNotes(); seenReceipts.clear(); settledActions.clear();
|
|
1412
1413
|
const restoredReceiptIds = new Set();
|
|
1413
|
-
|
|
1414
|
+
// Each turn's plan card comes back under its prompt, settled by how the turn ended (agent-plan.js).
|
|
1415
|
+
const restored = restoredPlans((Array.isArray(transcript) ? transcript : []).flatMap((item) => {
|
|
1414
1416
|
if (item?.kind === "user" || item?.kind === "assistant") {
|
|
1415
1417
|
const attachments = Array.isArray(item.attachments) ? item.attachments.filter((entry) => typeof entry?.dataUrl === "string") : [];
|
|
1416
1418
|
return [{ kind: item.kind, id: newId(), text: String(item.text ?? ""), ...(attachments.length ? { attachments } : {}) }];
|
|
@@ -1425,8 +1427,8 @@ export function createAgentChatStore({
|
|
|
1425
1427
|
return [{ kind: "receipt", id: `receipt:${item.receiptId || newId()}`, receiptId: item.receiptId, summary: item.summary || "Receipt", receipt: { status: "applied", warnings: [], verification: { status: "verified" } } }];
|
|
1426
1428
|
}
|
|
1427
1429
|
if (item?.kind === "failure") return [{ kind: "failure", id: newId(), failure: { code: item.code || "upstream", message: String(item.message || "The turn failed."), recovery: { action: "retry", retryAllowed: true } } }];
|
|
1428
|
-
return [];
|
|
1429
|
-
});
|
|
1430
|
+
return item?.kind === "plan" ? [{ kind: "plan", id: newId(), steps: item.steps }] : [];
|
|
1431
|
+
}));
|
|
1430
1432
|
const lastPrompt = [...restored].reverse().find((item) => item.kind === "user")?.text || "";
|
|
1431
1433
|
set({ sessionId, turnId: null, items: restored, rateLimit: null, lastPrompt, turnStartedAt: null, lastTurn: null, streaming: false });
|
|
1432
1434
|
return restored;
|
|
@@ -1498,14 +1500,15 @@ export function createAgentChatStore({
|
|
|
1498
1500
|
model: options.model,
|
|
1499
1501
|
effort: options.effort ?? undefined,
|
|
1500
1502
|
...(typeof options.supervisor === "boolean" ? { supervisor: options.supervisor } : {}), ...(options.tasks ? { tasks: true } : {}),
|
|
1501
|
-
}, applyEvent, signal);
|
|
1503
|
+
}, (event) => { sculptSketchFrame(event); applyEvent(event); }, signal);
|
|
1502
1504
|
} catch (error) {
|
|
1503
1505
|
if (!signal.aborted) applyEvent({ type: "error", code: "upstream", message: String(error?.message || error) });
|
|
1504
1506
|
} finally {
|
|
1505
1507
|
if (controller?.signal === signal) controller = null;
|
|
1506
|
-
// A turn the author already replaced owns none of this state.
|
|
1508
|
+
// A turn the author already replaced owns none of this state. Its live sculpt sketch
|
|
1509
|
+
// (the ghost clay of a call still being written) ends with it, whatever ended it.
|
|
1507
1510
|
if (activeTurn === turn) {
|
|
1508
|
-
wireTurnId = null;
|
|
1511
|
+
wireTurnId = null; clearSculptSketch();
|
|
1509
1512
|
// Ending with nothing on screen is a failure, not a result.
|
|
1510
1513
|
if (!turn.produced && !turn.failure && !signal.aborted) {
|
|
1511
1514
|
turn.failure = { code: "no_output", message: "The turn ended without a response.", recovery: { action: "retry", retryAllowed: true } };
|
|
@@ -1515,7 +1518,7 @@ export function createAgentChatStore({
|
|
|
1515
1518
|
activeTurn = null;
|
|
1516
1519
|
// The end state stays in the transcript with the turn's steps (agent-turn.js withTurnEnd).
|
|
1517
1520
|
const lastTurn = { status: signal.aborted ? "stopped" : turn.failure ? "failed" : "done", endedAt, durationMs: endedAt - startedAt, failure: turn.failure };
|
|
1518
|
-
set({ streaming: false, turnStartedAt: null, lastTurn, items: withTurnEnd(state.items, lastTurn, newId) });
|
|
1521
|
+
set({ streaming: false, turnStartedAt: null, lastTurn, items: withPlanEnd(withTurnEnd(state.items, lastTurn, newId), lastTurn.status) });
|
|
1519
1522
|
if (studio && turn.reviewable && options.supervisor === true && !signal.aborted) requestSupervisorNote(turnId);
|
|
1520
1523
|
}
|
|
1521
1524
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/* A turn's step checklist (AgentPlanCard.jsx); colours, sizes and motion come from agent-panel.css. */
|
|
2
|
+
.agent-plan-row[data-plan-live="true"] {
|
|
3
|
+
position: sticky;
|
|
4
|
+
top: calc(-1 * var(--agent-space-5));
|
|
5
|
+
z-index: 1;
|
|
6
|
+
/* the transcript's own colour above the card, so steps scrolling under it never show through the gap */
|
|
7
|
+
padding-top: var(--agent-space-2);
|
|
8
|
+
margin-top: calc(-1 * var(--agent-space-2));
|
|
9
|
+
background: var(--agent-bg);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.agent-plan {
|
|
13
|
+
--agent-plan-mark: 11px;
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.agent-plan-row[data-plan-live="true"] .agent-plan { box-shadow: 0 6px 14px -8px #000c; }
|
|
19
|
+
|
|
20
|
+
.agent-plan-head {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
gap: var(--agent-space-3);
|
|
24
|
+
padding: var(--agent-space-2) var(--agent-space-4);
|
|
25
|
+
color: var(--agent-text-soft, var(--agent-text));
|
|
26
|
+
font-size: var(--agent-font-md);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.agent-plan-title { flex: none; font-weight: 600; }
|
|
30
|
+
|
|
31
|
+
.agent-plan-bar {
|
|
32
|
+
flex: 1;
|
|
33
|
+
height: 3px;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
border-radius: var(--agent-radius-pill);
|
|
36
|
+
background: var(--agent-track);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.agent-plan-fill {
|
|
40
|
+
display: block;
|
|
41
|
+
height: 100%;
|
|
42
|
+
background: var(--agent-ok);
|
|
43
|
+
transform: scaleX(var(--agent-plan-done, 0));
|
|
44
|
+
transform-origin: left;
|
|
45
|
+
transition: transform var(--agent-motion);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.agent-plan-count { flex: none; color: var(--agent-text-dim); font: var(--agent-font-sm) ui-monospace, monospace; font-variant-numeric: tabular-nums; }
|
|
49
|
+
|
|
50
|
+
.agent-plan-steps {
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
gap: 2px;
|
|
54
|
+
margin: 0;
|
|
55
|
+
padding: var(--agent-space-1) 0 var(--agent-space-2);
|
|
56
|
+
border-top: 1px solid var(--agent-border);
|
|
57
|
+
list-style: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.agent-plan-step {
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
gap: var(--agent-space-3);
|
|
64
|
+
min-width: 0;
|
|
65
|
+
padding: 2px var(--agent-space-4);
|
|
66
|
+
color: var(--agent-text-muted);
|
|
67
|
+
font-size: var(--agent-font-md);
|
|
68
|
+
line-height: 1.4;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.agent-plan-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
72
|
+
.agent-plan-step.done { color: var(--agent-text-dim); }
|
|
73
|
+
.agent-plan-step.active { background: color-mix(in srgb, var(--agent-accent) 12%, transparent); color: var(--agent-text); }
|
|
74
|
+
.agent-plan-step.unfinished { color: var(--agent-text-dim); }
|
|
75
|
+
.agent-plan-step.unfinished .agent-plan-text { font-style: italic; }
|
|
76
|
+
|
|
77
|
+
.agent-plan-mark { flex: none; width: var(--agent-plan-mark); height: var(--agent-plan-mark); }
|
|
78
|
+
.agent-plan-mark.done { color: var(--agent-ok); }
|
|
79
|
+
.agent-plan-mark.unfinished { color: var(--agent-warn); }
|
|
80
|
+
.agent-plan-mark.pending { border: 1.5px solid var(--agent-border-strong); border-radius: var(--agent-radius-pill); }
|
|
81
|
+
/* the step under way: an accent ring whose centre breathes while the turn runs */
|
|
82
|
+
.agent-plan-mark.active { position: relative; border: 1.5px solid var(--agent-accent); border-radius: var(--agent-radius-pill); }
|
|
83
|
+
.agent-plan-mark.active::after { content: ""; position: absolute; inset: 2px; border-radius: var(--agent-radius-pill); background: var(--agent-accent); }
|
|
84
|
+
.agent-plan[data-plan-state="live"] .agent-plan-mark.active::after { animation: agent-pulse 1.4s ease-in-out infinite; }
|
|
85
|
+
|
|
86
|
+
/* A finished turn's card is a record: it keeps its rows but no longer pulls the eye. */
|
|
87
|
+
.agent-plan:not([data-plan-state="live"]) .agent-plan-fill { background: var(--agent-text-dim); }
|
|
88
|
+
.agent-plan[data-plan-state="done"] .agent-plan-fill { background: var(--agent-ok); }
|
|
89
|
+
|
|
90
|
+
@media (prefers-reduced-motion: reduce) {
|
|
91
|
+
.agent-plan[data-plan-state="live"] .agent-plan-mark.active::after { animation: none; }
|
|
92
|
+
.agent-plan-fill { transition: none; }
|
|
93
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// A turn's step checklist as the agent panel keeps it (AgentPlanCard.jsx draws it): the sidecar's plan tool
|
|
2
|
+
// (bin/agent/plan-tool.mjs) sends the whole list in every plan.update frame, and the chat store (agent-client.js) keeps
|
|
3
|
+
// one plan card per turn, right under the turn's prompt. The card never claims more than happened: when the turn ends,
|
|
4
|
+
// the step still under way counts as done only if the turn succeeded, and is left unfinished when it failed or stopped.
|
|
5
|
+
// Pure data, like agent-turn.js.
|
|
6
|
+
|
|
7
|
+
const STATUSES = new Set(["pending", "active", "done"]);
|
|
8
|
+
const MAX_STEPS = 12, TEXT_MAX = 80;
|
|
9
|
+
|
|
10
|
+
/** A frame's steps as the card keeps them, or null when they are not a plan. */
|
|
11
|
+
function planSteps(steps) {
|
|
12
|
+
if (!Array.isArray(steps) || !steps.length || steps.length > MAX_STEPS) return null;
|
|
13
|
+
const kept = steps.map(step => ({ text: String(step?.text ?? "").replace(/\s+/g, " ").trim().slice(0, TEXT_MAX), status: step?.status }));
|
|
14
|
+
return kept.every(step => step.text && STATUSES.has(step.status)) ? kept : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Whether a frame belongs to the plan: its update, or the plan tool's own call (the card shows it, never a step row). */
|
|
18
|
+
export const isPlanFrame = event => event?.type === "plan.update" || (event?.name === "plan" && ["tool.compose", "tool.start", "tool.progress"].includes(event?.type));
|
|
19
|
+
|
|
20
|
+
/** items after a plan frame: the turn's one plan card, under its prompt, takes the frame's list whole. */
|
|
21
|
+
export function withPlanFrame(items, event, newId) {
|
|
22
|
+
const steps = event?.type === "plan.update" ? planSteps(event.steps) : null;
|
|
23
|
+
if (!steps) return items;
|
|
24
|
+
const prompt = items.findLastIndex(item => item.kind === "user" && !item.steered);
|
|
25
|
+
const at = items.findIndex((item, i) => i > prompt && item.kind === "plan");
|
|
26
|
+
if (at !== -1) return items.map((item, i) => i === at ? { ...item, steps } : item);
|
|
27
|
+
return [...items.slice(0, prompt + 1), { kind: "plan", id: newId(), steps }, ...items.slice(prompt + 1)];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** A plan whose turn ended ("done", "failed" or "stopped"): the step under way is done only when the turn succeeded. */
|
|
31
|
+
export function settledPlan(plan, status) {
|
|
32
|
+
const outcome = status === "done" || status === "stopped" ? status : "failed";
|
|
33
|
+
return { ...plan, outcome, steps: plan.steps.map(step => step.status === "active" ? { ...step, status: outcome === "done" ? "done" : "unfinished" } : step) };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** items at the end of a turn: its plan card settles (settledPlan); earlier turns' cards already have. */
|
|
37
|
+
export const withPlanEnd = (items, status) => items.map(item => item.kind === "plan" && !item.outcome ? settledPlan(item, status) : item);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A restored transcript's plans (session-store.mjs lists one per change): each turn keeps its last list, under its
|
|
41
|
+
* prompt; the plan tool's own calls go; and the card settles by how the turn ended: failed when a failure follows, done
|
|
42
|
+
* when the turn ends on the reply, else stopped (the history ends mid-turn: the panel reloaded while it ran).
|
|
43
|
+
*/
|
|
44
|
+
export function restoredPlans(items) {
|
|
45
|
+
const turns = [];
|
|
46
|
+
for (const item of items) {
|
|
47
|
+
if (item.kind === "tool" && item.name === "plan") continue;
|
|
48
|
+
if (item.kind === "user" || !turns.length) turns.push([]);
|
|
49
|
+
turns.at(-1).push(item);
|
|
50
|
+
}
|
|
51
|
+
return turns.flatMap(turn => {
|
|
52
|
+
const rest = turn.filter(item => item.kind !== "plan"), plan = turn.findLast(item => item.kind === "plan");
|
|
53
|
+
const steps = plan ? planSteps(plan.steps) : null;
|
|
54
|
+
if (!steps) return rest;
|
|
55
|
+
const status = rest.some(item => item.kind === "failure") ? "failed" : rest.at(-1)?.kind === "assistant" ? "done" : "stopped";
|
|
56
|
+
const prompt = rest[0]?.kind === "user" ? 1 : 0;
|
|
57
|
+
return [...rest.slice(0, prompt), settledPlan({ ...plan, steps }, status), ...rest.slice(prompt)];
|
|
58
|
+
});
|
|
59
|
+
}
|
package/tools/run-tests.mjs
CHANGED
|
@@ -49,7 +49,7 @@ const NODE_FILES = [
|
|
|
49
49
|
"test/process/verify-package-telemetry.mjs",
|
|
50
50
|
"test/process/verify-agent-deps.mjs",
|
|
51
51
|
"test/verify-agent-image-references.mjs",
|
|
52
|
-
"test/verify-agent-panel.mjs", "test/verify-agent-turn-view.mjs", "test/verify-agent-reply-text.mjs",
|
|
52
|
+
"test/verify-agent-panel.mjs", "test/verify-agent-turn-view.mjs", "test/verify-agent-plan.mjs", "test/verify-agent-reply-text.mjs", "test/verify-agent-sculpt-sketch.mjs",
|
|
53
53
|
"test/verify-agent-host.mjs",
|
|
54
54
|
"test/verify-agent-routes.mjs",
|
|
55
55
|
"test/verify-studio-owner-restart.mjs",
|
|
@@ -81,14 +81,14 @@ const NODE_FILES = [
|
|
|
81
81
|
"test/verify-auto-color.mjs",
|
|
82
82
|
"test/verify-coplanar-depth.mjs",
|
|
83
83
|
"test/verify-empty-object.mjs",
|
|
84
|
-
"test/verify-sculpt-recipe.mjs",
|
|
84
|
+
"test/verify-sculpt-recipe.mjs", "test/verify-sculpt-shapes.mjs", "test/verify-sculpt-loft-convex.mjs", "test/verify-sculpt-detail.mjs",
|
|
85
85
|
"test/verify-sculpt-object.mjs",
|
|
86
86
|
"test/verify-sculpt-compare.mjs",
|
|
87
87
|
"test/verify-sculpt-run.mjs",
|
|
88
88
|
"test/verify-sculpt-compare-command.mjs",
|
|
89
89
|
"test/verify-sculpt-panel.mjs",
|
|
90
90
|
"test/verify-sculpt-blueprint.mjs",
|
|
91
|
-
"test/verify-sculpt-mode.mjs",
|
|
91
|
+
"test/verify-sculpt-mode.mjs", "test/verify-sculpt-csg.mjs", "test/verify-material-presets.mjs",
|
|
92
92
|
"test/verify-route-owner.mjs",
|
|
93
93
|
"test/verify-part-colours.mjs",
|
|
94
94
|
"test/verify-asset-shelf.mjs",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as e}from"./rolldown-runtime-hePW80VL.js";import{Pr as t,c as n,d as r,f as i,l as a,m as o,o as s,v as c,xi as l}from"./task-board-BKaM7d-9.js";import{o as u,r as d,t as f}from"./motion3-compile-B887n77J.js";var p=e(l(),1),m=2e3,h=(e,t)=>new Promise(n=>{let r=setTimeout(n,e);t.addEventListener(`abort`,()=>{clearTimeout(r),n()},{once:!0})});function g(e,n){(0,p.useEffect)(()=>{let r=new Map;for(let[t,i]of Object.entries(e))for(let e of i)r.set(e,n[t]??t);let i=()=>{for(let e of document.querySelectorAll(`[data-node-id]`)){let n=r.get(e.getAttribute(`data-node-id`));n?(e.setAttribute(`data-task-scope`,n),e.setAttribute(`title`,t(`In task: ${n}`,`작업 중: ${n}`))):e.hasAttribute(`data-task-scope`)&&(e.removeAttribute(`data-task-scope`),e.removeAttribute(`title`))}};i();let a=new MutationObserver(i);return a.observe(document.body,{childList:!0,subtree:!0}),()=>{a.disconnect(),r.clear(),i()}},[e,n])}function _({sessionId:e,pulse:t,labelOf:l,onMainNote:_}){let v=(0,p.useMemo)(()=>s(),[]),y=u(),[b,x]=(0,p.useReducer)((e,t)=>t.seed?o(e,t.seed):t.reset?n():r(e,t.event),void 0,n),[S,C]=(0,p.useState)({}),[w,T]=(0,p.useState)(f),E=(0,p.useRef)(0),D=(0,p.useRef)(_);(0,p.useEffect)(()=>{E.current=b.after,D.current=_},[b.after,_]),(0,p.useEffect)(()=>{x({reset:!0}),E.current=0},[e]),(0,p.useEffect)(()=>d(T),[]),(0,p.useEffect)(()=>y.subscribe(e=>{typeof e?.type!=`string`||!e.type.startsWith(`task.`)||(C(t=>i(t,e)),(e.type===`task.userEdit`||e.type===`task.undone`)&&x({event:e}))}),[y]),(0,p.useEffect)(()=>{if(!e)return;let t=new AbortController;return(async()=>{for(;!t.signal.aborted;){try{let{tasks:n}=await v.board(e);x({seed:n}),await v.events(e,E.current,e=>{if(e.type===`main.turn`&&e.turnId){let n=``;v.frames(e.turnId,e=>{e.type===`text.delta`&&(n+=e.text??``),e.type===`tool.start`&&(n=``)},t.signal).then(()=>D.current?.(n),()=>{})}x({event:e})},t.signal)}catch{}await h(m,t.signal)}})(),()=>t.abort()},[v,e,t]);let O=b.order.filter(e=>b.tasks[e]?.status===`running`&&b.tasks[e]?.turnId),k=O.map(e=>`${e}:${b.tasks[e].turnId}`).join();return(0,p.useEffect)(()=>{let e=O.map(e=>{let t=new AbortController,{turnId:n}=b.tasks[e];return v.frames(n,t=>{let n=a(t,l);n&&x({event:{type:`task.progress`,taskId:e,status:`running`,progress:n}})},t.signal).catch(()=>{}),t});return()=>e.forEach(e=>e.abort())},[v,k,l]),g(S,(0,p.useMemo)(()=>Object.fromEntries(b.order.map(e=>[e,b.tasks[e].title])),[b])),(0,p.useEffect)(()=>{c.set({client:v,labelOf:l,undo:async e=>{let t=await y.run(`task.undo`,{taskId:e});if(t?.ok===!1)throw Error(t.message??t.code);x({event:{type:`task.undone`,taskId:e}})}})},[y,v,l]),(0,p.useEffect)(()=>{c.set({board:b,activity:w})},[b,w]),(0,p.useEffect)(()=>()=>c.set({board:n(),open:null}),[e]),null}export{_ as default};
|