skydive-cli 0.1.0-beta.390 → 0.1.0-beta.396
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/dist/js/bin.mjs
CHANGED
|
@@ -19,7 +19,7 @@ import semver from "semver";
|
|
|
19
19
|
|
|
20
20
|
//#region package.json
|
|
21
21
|
var name = "skydive-cli";
|
|
22
|
-
var version = "0.1.0-beta.
|
|
22
|
+
var version = "0.1.0-beta.396";
|
|
23
23
|
|
|
24
24
|
//#endregion
|
|
25
25
|
//#region src/types.ts
|
|
@@ -1423,7 +1423,7 @@ const chatCommand = {
|
|
|
1423
1423
|
printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
|
|
1424
1424
|
process.exit(1);
|
|
1425
1425
|
}
|
|
1426
|
-
const { runChat } = await import("./boot-
|
|
1426
|
+
const { runChat } = await import("./boot-BhSVccnq.mjs");
|
|
1427
1427
|
await runChat({
|
|
1428
1428
|
appUrl,
|
|
1429
1429
|
sessionToken: session.value.sessionToken,
|
|
@@ -1735,7 +1735,7 @@ const switchCommand = {
|
|
|
1735
1735
|
printError("The workspace picker needs the Bun runtime and it could not be set up automatically. Pass a workspace slug instead, or install Bun and retry.");
|
|
1736
1736
|
process.exit(1);
|
|
1737
1737
|
}
|
|
1738
|
-
const { runWorkspacePicker } = await import("./boot-
|
|
1738
|
+
const { runWorkspacePicker } = await import("./boot-BhSVccnq.mjs");
|
|
1739
1739
|
await runWorkspacePicker(session);
|
|
1740
1740
|
return;
|
|
1741
1741
|
}
|
|
@@ -1461,6 +1461,25 @@ function Indented({ children }) {
|
|
|
1461
1461
|
//#endregion
|
|
1462
1462
|
//#region src/chat/tui/list-line.ts
|
|
1463
1463
|
/**
|
|
1464
|
+
* Flatten `text` to a single terminal line: newlines, carriage returns, tabs
|
|
1465
|
+
* and other C0/C1 control characters each become one space. A picker row is
|
|
1466
|
+
* budgeted as exactly one line (see the header comment), so a title carrying a
|
|
1467
|
+
* literal newline would otherwise render as two lines, push the rows box past
|
|
1468
|
+
* the height flexbox gave it, and collapse the rows above onto each other.
|
|
1469
|
+
*
|
|
1470
|
+
* The replacement is 1:1 (one code point in, one out) so fuzzy-match highlight
|
|
1471
|
+
* indexes computed against the raw string still line up with the displayed
|
|
1472
|
+
* text — do the same substitution on the fuzzy key.
|
|
1473
|
+
*/
|
|
1474
|
+
function singleLine(text) {
|
|
1475
|
+
let out = "";
|
|
1476
|
+
for (const ch of text) {
|
|
1477
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
1478
|
+
out += code <= 31 || code >= 127 && code <= 159 ? " " : ch;
|
|
1479
|
+
}
|
|
1480
|
+
return out;
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1464
1483
|
* Fit a row's title and trailing detail into `width` columns. The title
|
|
1465
1484
|
* identifies the row so it gets the space first; the detail takes what is
|
|
1466
1485
|
* left and is dropped when nothing is.
|
|
@@ -1718,7 +1737,7 @@ function ConversationList({ agent, conversations, complete, showAutomated, onTog
|
|
|
1718
1737
|
fg,
|
|
1719
1738
|
children: [marker, truncate("+ new conversation", rowWidth)]
|
|
1720
1739
|
}, "new");
|
|
1721
|
-
const { title, detail } = fitRowText(row.hit.item.title ?? "(untitled)", previewLine(row.hit.item), rowWidth);
|
|
1740
|
+
const { title, detail } = fitRowText(singleLine(row.hit.item.title ?? "(untitled)"), previewLine(row.hit.item), rowWidth);
|
|
1722
1741
|
return /* @__PURE__ */ jsxs("text", {
|
|
1723
1742
|
fg,
|
|
1724
1743
|
children: [
|
|
@@ -1738,7 +1757,7 @@ function ConversationList({ agent, conversations, complete, showAutomated, onTog
|
|
|
1738
1757
|
]
|
|
1739
1758
|
});
|
|
1740
1759
|
}
|
|
1741
|
-
const conversationKeys = [(c) => c.title ?? "", (c) => c.preview ?? ""];
|
|
1760
|
+
const conversationKeys = [(c) => singleLine(c.title ?? ""), (c) => c.preview ?? ""];
|
|
1742
1761
|
function previewLine(c) {
|
|
1743
1762
|
const when = relativeTime(c.updatedAt);
|
|
1744
1763
|
const prefix = c.preview ? c.preview.replace(/\s+/g, " ").trim() : "";
|
|
@@ -3637,6 +3656,198 @@ function mapToolState(raw) {
|
|
|
3637
3656
|
}
|
|
3638
3657
|
}
|
|
3639
3658
|
|
|
3659
|
+
//#endregion
|
|
3660
|
+
//#region src/chat/tui/chat/todos.ts
|
|
3661
|
+
const todoItemSchema = z.object({
|
|
3662
|
+
id: z.string(),
|
|
3663
|
+
content: z.string(),
|
|
3664
|
+
status: z.enum([
|
|
3665
|
+
"pending",
|
|
3666
|
+
"in_progress",
|
|
3667
|
+
"completed",
|
|
3668
|
+
"failed"
|
|
3669
|
+
]),
|
|
3670
|
+
position: z.number()
|
|
3671
|
+
});
|
|
3672
|
+
const todosPayloadSchema = z.object({ todos: z.array(todoItemSchema) });
|
|
3673
|
+
/**
|
|
3674
|
+
* The todos carried by a `data-anyone-todos-updated` chunk/part `data`
|
|
3675
|
+
* payload, or null when the payload isn't the documented shape. Used for both
|
|
3676
|
+
* the live stream (reducer-adjacent, in chat.tsx) and history parts, which
|
|
3677
|
+
* carry the identical `{ data: { todos } }` shape.
|
|
3678
|
+
*/
|
|
3679
|
+
function todosFromPayload(data) {
|
|
3680
|
+
const parsed = todosPayloadSchema.safeParse(data);
|
|
3681
|
+
return parsed.success ? parsed.data.todos : null;
|
|
3682
|
+
}
|
|
3683
|
+
/**
|
|
3684
|
+
* The conversation's current todo list from loaded history: the newest
|
|
3685
|
+
* `data-anyone-todos-updated` part in the transcript. Every `platform todo
|
|
3686
|
+
* write` appends a fresh snapshot rather than editing one in place, so the
|
|
3687
|
+
* last part wins and earlier ones are history — matching the web client's
|
|
3688
|
+
* `latestTodoSnapshot`.
|
|
3689
|
+
*
|
|
3690
|
+
* Returns null when the agent never wrote a list, and when the newest snapshot
|
|
3691
|
+
* is empty: `platform todo dismiss` writes an empty list, so an empty newest
|
|
3692
|
+
* snapshot means "the plan is gone", not "fall back to the previous one".
|
|
3693
|
+
*/
|
|
3694
|
+
function latestTodosFromHistory(messages) {
|
|
3695
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
3696
|
+
const parts = messages[i]?.parts ?? [];
|
|
3697
|
+
for (let j = parts.length - 1; j >= 0; j -= 1) {
|
|
3698
|
+
const part = parts[j];
|
|
3699
|
+
if (!part || part.type !== "data-anyone-todos-updated") continue;
|
|
3700
|
+
const data = isRecord(part) && "data" in part && isRecord(part.data) ? part.data : null;
|
|
3701
|
+
const todos = data ? todosFromPayload(data) : null;
|
|
3702
|
+
if (!todos) return null;
|
|
3703
|
+
return todos.length > 0 ? todos : null;
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
return null;
|
|
3707
|
+
}
|
|
3708
|
+
/** How many todo rows the card shows before it caps the tail with "+N more". */
|
|
3709
|
+
const MAX_VISIBLE_ROWS = 6;
|
|
3710
|
+
function windowTodos(todos, cap = MAX_VISIBLE_ROWS) {
|
|
3711
|
+
const sorted = [...todos].sort((a, b) => a.position - b.position);
|
|
3712
|
+
let leadingDone = 0;
|
|
3713
|
+
while (leadingDone < sorted.length && sorted[leadingDone]?.status === "completed") leadingDone += 1;
|
|
3714
|
+
const head = leadingDone >= 2 || sorted.length > cap ? leadingDone : 0;
|
|
3715
|
+
const rest = sorted.slice(head);
|
|
3716
|
+
const rowBudget = Math.max(1, head > 0 ? cap - 1 : cap);
|
|
3717
|
+
if (rest.length <= rowBudget) return {
|
|
3718
|
+
doneSummary: head,
|
|
3719
|
+
rows: rest,
|
|
3720
|
+
moreCount: 0
|
|
3721
|
+
};
|
|
3722
|
+
const windowSize = Math.max(1, rowBudget - 1);
|
|
3723
|
+
const shown = rest.slice(0, windowSize);
|
|
3724
|
+
return {
|
|
3725
|
+
doneSummary: head,
|
|
3726
|
+
rows: shown,
|
|
3727
|
+
moreCount: rest.length - shown.length
|
|
3728
|
+
};
|
|
3729
|
+
}
|
|
3730
|
+
/**
|
|
3731
|
+
* Total terminal rows the card occupies for `todos`, so the chat screen can
|
|
3732
|
+
* reserve exactly this much and keep the composer pinned. Header + optional
|
|
3733
|
+
* done-summary line + rendered rows + optional "+N more" tail + top margin.
|
|
3734
|
+
* Must track `windowTodos` and `TodoCardView`'s layout.
|
|
3735
|
+
*/
|
|
3736
|
+
function todoCardHeight(todos) {
|
|
3737
|
+
if (todos.length === 0) return 0;
|
|
3738
|
+
const w = windowTodos(todos);
|
|
3739
|
+
const summaryRow = w.doneSummary > 0 ? 1 : 0;
|
|
3740
|
+
const moreRow = w.moreCount > 0 ? 1 : 0;
|
|
3741
|
+
return 1 + summaryRow + w.rows.length + moreRow + 1;
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3744
|
+
//#endregion
|
|
3745
|
+
//#region src/chat/tui/chat/todo-card.tsx
|
|
3746
|
+
function glyph(status) {
|
|
3747
|
+
switch (status) {
|
|
3748
|
+
case "completed": return {
|
|
3749
|
+
char: "✔",
|
|
3750
|
+
fg: theme.success
|
|
3751
|
+
};
|
|
3752
|
+
case "in_progress": return {
|
|
3753
|
+
char: "▶",
|
|
3754
|
+
fg: theme.accent
|
|
3755
|
+
};
|
|
3756
|
+
case "failed": return {
|
|
3757
|
+
char: "✗",
|
|
3758
|
+
fg: theme.error
|
|
3759
|
+
};
|
|
3760
|
+
default: return {
|
|
3761
|
+
char: "○",
|
|
3762
|
+
fg: theme.dim
|
|
3763
|
+
};
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
function rowColor(status) {
|
|
3767
|
+
switch (status) {
|
|
3768
|
+
case "completed": return theme.dim;
|
|
3769
|
+
case "in_progress": return theme.fg;
|
|
3770
|
+
case "failed": return theme.muted;
|
|
3771
|
+
default: return theme.muted;
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
/**
|
|
3775
|
+
* The agent's live todo list, pinned just above the composer — the TUI analog
|
|
3776
|
+
* of the web chat's todo card. Rendered only once the agent has actually
|
|
3777
|
+
* written a list (`platform todo write`) and until it clears one, so a
|
|
3778
|
+
* conversation the agent never wrote todos for grows no chrome for it.
|
|
3779
|
+
*
|
|
3780
|
+
* The card can't scroll like the web card, so it windows (see `windowTodos`):
|
|
3781
|
+
* a leading run of completed todos folds into a `✔ N done` line, the active
|
|
3782
|
+
* and upcoming rows fill the rest, and a `+N more` tail counts pending work
|
|
3783
|
+
* past the cap. The window follows the in-progress row down as todos complete.
|
|
3784
|
+
*
|
|
3785
|
+
* `isActive` dims the header while the run is idle: an in-progress row only
|
|
3786
|
+
* reads as "happening now" when a run is actually producing output.
|
|
3787
|
+
*/
|
|
3788
|
+
function TodoCardView({ todos, isActive }) {
|
|
3789
|
+
if (todos.length === 0) return null;
|
|
3790
|
+
const completed = todos.filter((t) => t.status === "completed").length;
|
|
3791
|
+
const { doneSummary, rows, moreCount } = windowTodos(todos);
|
|
3792
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
3793
|
+
style: {
|
|
3794
|
+
flexDirection: "column",
|
|
3795
|
+
flexShrink: 0,
|
|
3796
|
+
marginTop: 1,
|
|
3797
|
+
paddingLeft: 1,
|
|
3798
|
+
paddingRight: 1
|
|
3799
|
+
},
|
|
3800
|
+
children: [
|
|
3801
|
+
/* @__PURE__ */ jsxs("box", {
|
|
3802
|
+
style: { flexDirection: "row" },
|
|
3803
|
+
children: [/* @__PURE__ */ jsx("text", {
|
|
3804
|
+
fg: isActive ? theme.accent : theme.muted,
|
|
3805
|
+
children: /* @__PURE__ */ jsx("b", { children: "Plan" })
|
|
3806
|
+
}), /* @__PURE__ */ jsxs("text", {
|
|
3807
|
+
fg: theme.dim,
|
|
3808
|
+
children: [
|
|
3809
|
+
" ",
|
|
3810
|
+
completed,
|
|
3811
|
+
"/",
|
|
3812
|
+
todos.length
|
|
3813
|
+
]
|
|
3814
|
+
})]
|
|
3815
|
+
}),
|
|
3816
|
+
doneSummary > 0 ? /* @__PURE__ */ jsxs("box", {
|
|
3817
|
+
style: { flexDirection: "row" },
|
|
3818
|
+
children: [/* @__PURE__ */ jsx("text", {
|
|
3819
|
+
fg: theme.success,
|
|
3820
|
+
children: "✔ "
|
|
3821
|
+
}), /* @__PURE__ */ jsxs("text", {
|
|
3822
|
+
fg: theme.dim,
|
|
3823
|
+
children: [doneSummary, " done"]
|
|
3824
|
+
})]
|
|
3825
|
+
}) : null,
|
|
3826
|
+
rows.map((todo) => {
|
|
3827
|
+
const g = glyph(todo.status);
|
|
3828
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
3829
|
+
style: { flexDirection: "row" },
|
|
3830
|
+
children: [/* @__PURE__ */ jsxs("text", {
|
|
3831
|
+
fg: g.fg,
|
|
3832
|
+
children: [g.char, " "]
|
|
3833
|
+
}), /* @__PURE__ */ jsx("text", {
|
|
3834
|
+
fg: rowColor(todo.status),
|
|
3835
|
+
children: todo.content
|
|
3836
|
+
})]
|
|
3837
|
+
}, todo.id);
|
|
3838
|
+
}),
|
|
3839
|
+
moreCount > 0 ? /* @__PURE__ */ jsxs("text", {
|
|
3840
|
+
fg: theme.dim,
|
|
3841
|
+
children: [
|
|
3842
|
+
" … +",
|
|
3843
|
+
moreCount,
|
|
3844
|
+
" more"
|
|
3845
|
+
]
|
|
3846
|
+
}) : null
|
|
3847
|
+
]
|
|
3848
|
+
});
|
|
3849
|
+
}
|
|
3850
|
+
|
|
3640
3851
|
//#endregion
|
|
3641
3852
|
//#region src/chat/tui/chat/footer-hints.ts
|
|
3642
3853
|
/** Build the chat footer copy independently of its colors and layout. */
|
|
@@ -7481,6 +7692,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
7481
7692
|
const agentHost = useAgentHost();
|
|
7482
7693
|
const [conversationId, setConversationId] = useState(initialConversationId);
|
|
7483
7694
|
const [items, setItems] = useState([]);
|
|
7695
|
+
const [todos, setTodos] = useState(null);
|
|
7484
7696
|
const [historyLoaded, setHistoryLoaded] = useState(initialConversationId === null);
|
|
7485
7697
|
const [input, setInput] = useState("");
|
|
7486
7698
|
const [run, setRun] = useState({ kind: "idle" });
|
|
@@ -7564,7 +7776,9 @@ function ChatScreen({ agent, conversation }) {
|
|
|
7564
7776
|
const chatHeight = bodyHeight;
|
|
7565
7777
|
const inlineReviewHeight = reviewOpen && reviewPlacement === "below" ? reviewHeight : 0;
|
|
7566
7778
|
const menuRows = menuOpen ? completionMenuHeight(menuCommands.length) : 0;
|
|
7567
|
-
const
|
|
7779
|
+
const todoCardVisible = !credPrompt && !!todos && todos.length > 0;
|
|
7780
|
+
const todoRows = todoCardVisible ? todoCardHeight(todos) : 0;
|
|
7781
|
+
const scrollHeight = Math.max(3, chatHeight - 2 - bottomBoxHeight - pendingRows - inlineReviewHeight - menuRows - todoRows);
|
|
7568
7782
|
useEffect(() => {
|
|
7569
7783
|
if (reviewOpen && reviewFocused) {
|
|
7570
7784
|
const composer = composerRef.current;
|
|
@@ -7612,6 +7826,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
7612
7826
|
text: recapText
|
|
7613
7827
|
});
|
|
7614
7828
|
setItems(loaded);
|
|
7829
|
+
setTodos(latestTodosFromHistory(messages));
|
|
7615
7830
|
} catch (err) {
|
|
7616
7831
|
if (cancelled) return;
|
|
7617
7832
|
setItems((prev) => [...prev, {
|
|
@@ -7644,6 +7859,11 @@ function ChatScreen({ agent, conversation }) {
|
|
|
7644
7859
|
onEvent: (event) => {
|
|
7645
7860
|
if (event.kind === "chunk") {
|
|
7646
7861
|
responsePreview.addChunk(event.chunk);
|
|
7862
|
+
if (event.chunk.type === "data-anyone-todos-updated") {
|
|
7863
|
+
const next = todosFromPayload(event.chunk.data);
|
|
7864
|
+
if (next) setTodos(next.length > 0 ? next : null);
|
|
7865
|
+
return;
|
|
7866
|
+
}
|
|
7647
7867
|
setItems((prev) => applyChunk(prev, event.chunk, streamAgentName));
|
|
7648
7868
|
return;
|
|
7649
7869
|
}
|
|
@@ -8751,6 +8971,10 @@ function ChatScreen({ agent, conversation }) {
|
|
|
8751
8971
|
}) }), run.kind !== "idle" ? /* @__PURE__ */ jsx(ActivityRow, { label: run.kind === "sending" ? "sending" : "working" }) : null]
|
|
8752
8972
|
})
|
|
8753
8973
|
}),
|
|
8974
|
+
todoCardVisible && todos ? /* @__PURE__ */ jsx(TodoCardView, {
|
|
8975
|
+
todos,
|
|
8976
|
+
isActive: run.kind !== "idle"
|
|
8977
|
+
}) : null,
|
|
8754
8978
|
pendingVisible ? /* @__PURE__ */ jsxs("box", {
|
|
8755
8979
|
style: {
|
|
8756
8980
|
paddingLeft: 1,
|