skydive-cli 0.5.0-beta.29 → 0.5.0-beta.30
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 +7 -7
- package/dist/js/{boot-DOTGKAyK.mjs → boot-0-dpPM1_.mjs} +331 -348
- package/dist/js/{daemon-BZ150Qid.mjs → daemon-Ce8WLdGI.mjs} +1 -1
- package/dist/js/{daemon-DBrQbBCG.mjs → daemon-DuxaUuek.mjs} +1 -1
- package/dist/js/{daemon-client-BpBjNzyD.mjs → daemon-client-C4heMWTq.mjs} +1 -1
- package/dist/js/daemon-client-LJdnM_nE.mjs +7 -0
- package/dist/js/{profiler-BRvwJNwH.mjs → profiler-BoO7ldO0.mjs} +1 -1
- package/package.json +1 -1
- package/dist/js/daemon-client-BrOr8CNG.mjs +0 -7
package/dist/js/bin.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { A as takenAliasNames, B as name, D as dedupeAliasName, E as aliasActivationHint, F as getActiveWorkspaceId, I as getSessionIdentity, L as listWorkspaces, M as defaultInstallEnv, N as detectShell, O as installAgentAlias, P as ensureActiveOrganization, R as resolveWorkspaceId, S as themes, T as machineOsFromPlatform, V as version, a as installCrashHandler, j as SUPPORTED_SHELLS, k as slugifyAliasName, t as maybeStartProfiling, u as brandHelpArt, w as buildImportSeedPrompt, z as setActiveWorkspace } from "./profiler-
|
|
2
|
+
import { A as takenAliasNames, B as name, D as dedupeAliasName, E as aliasActivationHint, F as getActiveWorkspaceId, I as getSessionIdentity, L as listWorkspaces, M as defaultInstallEnv, N as detectShell, O as installAgentAlias, P as ensureActiveOrganization, R as resolveWorkspaceId, S as themes, T as machineOsFromPlatform, V as version, a as installCrashHandler, j as SUPPORTED_SHELLS, k as slugifyAliasName, t as maybeStartProfiling, u as brandHelpArt, w as buildImportSeedPrompt, z as setActiveWorkspace } from "./profiler-BoO7ldO0.mjs";
|
|
3
3
|
import { A as getShareMachineDefault, C as getConfigPath, D as getPromptHistoryPath, E as getPreference, F as resolveAppUrl, H as saveSession, I as resolveChatAuth, L as resolveConfig, M as getStoredApiKeyWorkspaceName, N as getUpdateCheckDisabled, R as resolveManagementAuth, S as deleteConfig, T as getLastSeenVersion, V as saveConfig, W as setLastSeenVersion, _ as API_KEY_FAMILY_PREFIX, g as API_KEYS_URL, i as resolveAgent$1, j as getStoredApiKeyId, v as API_KEY_PREFIX, w as getDefaultAgent, x as PREFERENCES, y as DEFAULT_API_URL, z as resolveSession } from "./print-DYiQC5IW.mjs";
|
|
4
4
|
import { n as printError, r as printTable, t as output } from "./output-DYzzdXYV.mjs";
|
|
5
5
|
import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
|
|
6
6
|
import { t as createRestClient } from "./rest-xXfqT4SX.mjs";
|
|
7
7
|
import "./billing-blocked-2wju4gC_.mjs";
|
|
8
8
|
import { r as resolveMachineIdentity } from "./client-DbqRBquD.mjs";
|
|
9
|
-
import { i as queryDaemonStatus, l as PORTAL_DAEMON_FLAG, n as ensureDaemonRunning, s as stopDaemon, u as daemonPaths } from "./daemon-
|
|
9
|
+
import { i as queryDaemonStatus, l as PORTAL_DAEMON_FLAG, n as ensureDaemonRunning, s as stopDaemon, u as daemonPaths } from "./daemon-DuxaUuek.mjs";
|
|
10
10
|
import { i as grantPortalAccess, n as fetchPortalDevices, o as registerPortalDevice, r as findThisDevice, s as revokePortalAccess } from "./api-DG5W6iwx.mjs";
|
|
11
11
|
import { t as SandboxStream } from "./client-c4c5MmgN.mjs";
|
|
12
12
|
import { hideBin } from "yargs/helpers";
|
|
@@ -1584,7 +1584,7 @@ const importCommand = {
|
|
|
1584
1584
|
process.exit(1);
|
|
1585
1585
|
}
|
|
1586
1586
|
}
|
|
1587
|
-
const { runChat } = await import("./boot-
|
|
1587
|
+
const { runChat } = await import("./boot-0-dpPM1_.mjs");
|
|
1588
1588
|
await runChat({
|
|
1589
1589
|
appUrl,
|
|
1590
1590
|
sessionToken: session.value.sessionToken,
|
|
@@ -2029,7 +2029,7 @@ const chatCommand = {
|
|
|
2029
2029
|
sessionToken: auth.value.token,
|
|
2030
2030
|
agentSelector: argv.agent ?? null
|
|
2031
2031
|
});
|
|
2032
|
-
const { runChat } = await import("./boot-
|
|
2032
|
+
const { runChat } = await import("./boot-0-dpPM1_.mjs");
|
|
2033
2033
|
await runChat({
|
|
2034
2034
|
appUrl: auth.value.appUrl,
|
|
2035
2035
|
sessionToken: auth.value.token,
|
|
@@ -2411,7 +2411,7 @@ const switchCommand = {
|
|
|
2411
2411
|
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.");
|
|
2412
2412
|
process.exit(1);
|
|
2413
2413
|
}
|
|
2414
|
-
const { runWorkspacePicker } = await import("./boot-
|
|
2414
|
+
const { runWorkspacePicker } = await import("./boot-0-dpPM1_.mjs");
|
|
2415
2415
|
await runWorkspacePicker(session);
|
|
2416
2416
|
return;
|
|
2417
2417
|
}
|
|
@@ -2490,7 +2490,7 @@ const openCommand = {
|
|
|
2490
2490
|
const agent = argv.agent ? resolveAgent$1((await fetchPortalDevices(session)).agents, argv.agent) : null;
|
|
2491
2491
|
const cwd = argv.cwd ? path.resolve(argv.cwd) : process.cwd();
|
|
2492
2492
|
const { machineName } = await resolveMachineIdentity(null);
|
|
2493
|
-
const { PortalDaemonClient } = await import("./daemon-client-
|
|
2493
|
+
const { PortalDaemonClient } = await import("./daemon-client-LJdnM_nE.mjs");
|
|
2494
2494
|
let lastLine = "";
|
|
2495
2495
|
let signalConnected;
|
|
2496
2496
|
const connected = new Promise((resolve) => {
|
|
@@ -4336,7 +4336,7 @@ if (process.argv.includes(UPDATE_WORKER_FLAG)) {
|
|
|
4336
4336
|
process.exit(0);
|
|
4337
4337
|
}
|
|
4338
4338
|
if (process.argv.includes(PORTAL_DAEMON_FLAG)) {
|
|
4339
|
-
const { runPortalDaemon } = await import("./daemon-
|
|
4339
|
+
const { runPortalDaemon } = await import("./daemon-Ce8WLdGI.mjs");
|
|
4340
4340
|
runPortalDaemon(process.argv);
|
|
4341
4341
|
} else runCli();
|
|
4342
4342
|
function runCli() {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { A as takenAliasNames, C as themesForMode, D as dedupeAliasName, E as aliasActivationHint, F as getActiveWorkspaceId, L as listWorkspaces, N as detectShell, O as installAgentAlias, T as machineOsFromPlatform, _ as theme, a as installCrashHandler, b as themeModeFromColorFgBg, c as MARK_CELLS, d as splashFitsWidth, f as DEFAULT_THEME_ID, g as noColorRequested, h as monoTheme, i as writeArtifact, k as slugifyAliasName, l as WORDMARK, m as findTheme, n as profilingEnabled, o as buildCrashReport, p as applyTheme, r as record, s as writeCrashReport, v as themeForMode, x as themeVersion, y as themeMode, z as setActiveWorkspace } from "./profiler-
|
|
2
|
+
import { A as takenAliasNames, C as themesForMode, D as dedupeAliasName, E as aliasActivationHint, F as getActiveWorkspaceId, L as listWorkspaces, N as detectShell, O as installAgentAlias, T as machineOsFromPlatform, _ as theme, a as installCrashHandler, b as themeModeFromColorFgBg, c as MARK_CELLS, d as splashFitsWidth, f as DEFAULT_THEME_ID, g as noColorRequested, h as monoTheme, i as writeArtifact, k as slugifyAliasName, l as WORDMARK, m as findTheme, n as profilingEnabled, o as buildCrashReport, p as applyTheme, r as record, s as writeCrashReport, v as themeForMode, x as themeVersion, y as themeMode, z as setActiveWorkspace } from "./profiler-BoO7ldO0.mjs";
|
|
3
3
|
import { B as resolveWebUrl, C as getConfigPath, O as getReviewStateDir, P as recordDefaultAgent, U as saveTheme, b as DEFAULT_APP_URL, c as cardActionErrorMessage, d as reconcileMaskedInput, f as resolveConnectUrl, h as specKeyFor, i as resolveAgent, k as getSavedTheme, l as parseExternalOauthConnectParams, m as parseConnectCard, p as computeSettledLabel, s as MASK_CHAR, u as parseOauthConnectParams, y as DEFAULT_API_URL } from "./print-DYiQC5IW.mjs";
|
|
4
4
|
import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
|
|
5
5
|
import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-xXfqT4SX.mjs";
|
|
6
6
|
import { i as billingBlockedOutcomeFromSendResponse } from "./billing-blocked-2wju4gC_.mjs";
|
|
7
7
|
import { t as PortalClient } from "./client-DbqRBquD.mjs";
|
|
8
|
-
import "./daemon-
|
|
8
|
+
import "./daemon-DuxaUuek.mjs";
|
|
9
9
|
import "./api-DG5W6iwx.mjs";
|
|
10
10
|
import { t as SandboxStream } from "./client-c4c5MmgN.mjs";
|
|
11
|
-
import { t as PortalDaemonClient } from "./daemon-client-
|
|
11
|
+
import { t as PortalDaemonClient } from "./daemon-client-C4heMWTq.mjs";
|
|
12
12
|
import { t as runRawPtyPassthrough } from "./raw-pty-DY4KelZW.mjs";
|
|
13
13
|
import * as os$1 from "node:os";
|
|
14
14
|
import { homedir, platform, release, tmpdir } from "node:os";
|
|
@@ -4749,299 +4749,6 @@ function parseMatches(output) {
|
|
|
4749
4749
|
return null;
|
|
4750
4750
|
}
|
|
4751
4751
|
|
|
4752
|
-
//#endregion
|
|
4753
|
-
//#region src/chat/tui/chat/conversation-context.tsx
|
|
4754
|
-
const ConversationContext = createContext({
|
|
4755
|
-
rest: null,
|
|
4756
|
-
conversationId: null,
|
|
4757
|
-
collectSandboxStats: null,
|
|
4758
|
-
latestComputerId: null,
|
|
4759
|
-
scrolledUp: false
|
|
4760
|
-
});
|
|
4761
|
-
function useConversationContext() {
|
|
4762
|
-
return useContext(ConversationContext);
|
|
4763
|
-
}
|
|
4764
|
-
|
|
4765
|
-
//#endregion
|
|
4766
|
-
//#region src/chat/tui/spinner-frames.ts
|
|
4767
|
-
/** Braille dot-cycle frames — the classic smooth terminal spinner. */
|
|
4768
|
-
const SPINNER_FRAMES = [
|
|
4769
|
-
"⠋",
|
|
4770
|
-
"⠙",
|
|
4771
|
-
"⠹",
|
|
4772
|
-
"⠸",
|
|
4773
|
-
"⠼",
|
|
4774
|
-
"⠴",
|
|
4775
|
-
"⠦",
|
|
4776
|
-
"⠧",
|
|
4777
|
-
"⠇",
|
|
4778
|
-
"⠏"
|
|
4779
|
-
];
|
|
4780
|
-
/** Milliseconds between frames. ~12.5fps reads as smooth without thrashing. */
|
|
4781
|
-
const SPINNER_INTERVAL_MS = 80;
|
|
4782
|
-
/** Frame for a given tick count. Wraps at the end of the cycle; negative-safe. */
|
|
4783
|
-
function spinnerFrame(tick) {
|
|
4784
|
-
const n = SPINNER_FRAMES.length;
|
|
4785
|
-
return SPINNER_FRAMES[(Math.trunc(tick) % n + n) % n] ?? SPINNER_FRAMES[0];
|
|
4786
|
-
}
|
|
4787
|
-
|
|
4788
|
-
//#endregion
|
|
4789
|
-
//#region src/chat/tui/spinner.tsx
|
|
4790
|
-
/**
|
|
4791
|
-
* An animated spinner glyph. Advances one frame every SPINNER_INTERVAL_MS via
|
|
4792
|
-
* a self-owned interval that's cleared on unmount, so the animation runs only
|
|
4793
|
-
* while the component is mounted. Returns a <span>, so render it inside a
|
|
4794
|
-
* <text> (matching how the rest of the TUI composes inline glyphs).
|
|
4795
|
-
*/
|
|
4796
|
-
function Spinner({ color }) {
|
|
4797
|
-
const [tick, setTick] = useState(0);
|
|
4798
|
-
useEffect(() => {
|
|
4799
|
-
const id = setInterval(() => setTick((t) => t + 1), SPINNER_INTERVAL_MS);
|
|
4800
|
-
return () => clearInterval(id);
|
|
4801
|
-
}, []);
|
|
4802
|
-
return /* @__PURE__ */ jsx("span", {
|
|
4803
|
-
fg: color,
|
|
4804
|
-
children: spinnerFrame(tick)
|
|
4805
|
-
});
|
|
4806
|
-
}
|
|
4807
|
-
|
|
4808
|
-
//#endregion
|
|
4809
|
-
//#region src/chat/tui/chat/subagents.ts
|
|
4810
|
-
/** A run the platform hasn't settled yet — still queued or mid-run. */
|
|
4811
|
-
function isLiveStatus(status) {
|
|
4812
|
-
return status === "queued" || status === "running";
|
|
4813
|
-
}
|
|
4814
|
-
function toTaskStatus(raw) {
|
|
4815
|
-
switch (raw) {
|
|
4816
|
-
case "queued":
|
|
4817
|
-
case "running":
|
|
4818
|
-
case "completed":
|
|
4819
|
-
case "failed":
|
|
4820
|
-
case "canceled": return raw;
|
|
4821
|
-
default: return "queued";
|
|
4822
|
-
}
|
|
4823
|
-
}
|
|
4824
|
-
const FALLBACK_TITLE = "Subagent task";
|
|
4825
|
-
/**
|
|
4826
|
-
* Each delegated task's display title from the `subagent` call's input, in
|
|
4827
|
-
* call order — the short name the tool requires per task. A malformed entry
|
|
4828
|
-
* yields a fallback label rather than being dropped, so the row count always
|
|
4829
|
-
* matches the fan-out width. Mirrors the web client's `subagentTaskTitles`.
|
|
4830
|
-
*/
|
|
4831
|
-
function subagentTaskTitles(input) {
|
|
4832
|
-
if (!isRecord(input)) return [];
|
|
4833
|
-
const tasks = input["tasks"];
|
|
4834
|
-
if (!Array.isArray(tasks)) return [];
|
|
4835
|
-
return tasks.map((task) => {
|
|
4836
|
-
if (!isRecord(task)) return FALLBACK_TITLE;
|
|
4837
|
-
const title = task["title"];
|
|
4838
|
-
return typeof title === "string" && title.trim() ? title.trim() : FALLBACK_TITLE;
|
|
4839
|
-
});
|
|
4840
|
-
}
|
|
4841
|
-
const QUEUED_TASK_ID_LINE = /^\s*-\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}):\s/i;
|
|
4842
|
-
/**
|
|
4843
|
-
* The ids of the runs a `subagent` call queued, read back out of its own
|
|
4844
|
-
* output. The call returns the moment the runs exist, so the ids are there
|
|
4845
|
-
* long before any child finishes — they are what ties this card to the task
|
|
4846
|
-
* rows the status poll returns, and their order is the tasks' call order.
|
|
4847
|
-
*/
|
|
4848
|
-
function queuedSubagentTaskIds(output) {
|
|
4849
|
-
const text = flattenOutputText(output);
|
|
4850
|
-
if (!text) return [];
|
|
4851
|
-
return text.split("\n").map((line) => line.match(QUEUED_TASK_ID_LINE)?.[1] ?? null).filter((id) => id !== null);
|
|
4852
|
-
}
|
|
4853
|
-
/**
|
|
4854
|
-
* Tool output reaches the TUI as `unknown`: the raw string live, possibly
|
|
4855
|
-
* wrapped in content parts or arrays when loaded from history. Walk it for
|
|
4856
|
-
* string leaves, newline-joined — the PR scanner's `stringifyToolOutput`
|
|
4857
|
-
* shape, bounded small because the id list sits at the top of the output.
|
|
4858
|
-
*/
|
|
4859
|
-
function flattenOutputText(value) {
|
|
4860
|
-
const MAX_LEN = 2e4;
|
|
4861
|
-
const parts = [];
|
|
4862
|
-
let total = 0;
|
|
4863
|
-
const visit = (v, depth) => {
|
|
4864
|
-
if (total >= MAX_LEN || depth > 4) return;
|
|
4865
|
-
if (typeof v === "string") {
|
|
4866
|
-
parts.push(v);
|
|
4867
|
-
total += v.length;
|
|
4868
|
-
return;
|
|
4869
|
-
}
|
|
4870
|
-
if (Array.isArray(v)) {
|
|
4871
|
-
for (const item of v) visit(item, depth + 1);
|
|
4872
|
-
return;
|
|
4873
|
-
}
|
|
4874
|
-
if (isRecord(v)) for (const item of Object.values(v)) visit(item, depth + 1);
|
|
4875
|
-
};
|
|
4876
|
-
visit(value, 0);
|
|
4877
|
-
return parts.join("\n");
|
|
4878
|
-
}
|
|
4879
|
-
/**
|
|
4880
|
-
* The card's rows: one per queued run id (call order), each carrying the
|
|
4881
|
-
* freshest known state. A task the status fetch hasn't returned yet falls
|
|
4882
|
-
* back to the input's title and reads as queued; a fetched row's own title
|
|
4883
|
-
* wins over the input's (the server's copy is the one the child actually
|
|
4884
|
-
* ran under). Before the ids exist — the call is still streaming — rows come
|
|
4885
|
-
* from the input titles alone, so the card never renders empty mid-turn.
|
|
4886
|
-
*/
|
|
4887
|
-
function subagentRows({ titles, ids, fetched }) {
|
|
4888
|
-
if (ids.length === 0) return titles.map((title, index) => ({
|
|
4889
|
-
id: `queued:${index}`,
|
|
4890
|
-
title,
|
|
4891
|
-
status: "queued",
|
|
4892
|
-
createdAt: null,
|
|
4893
|
-
completedAt: null
|
|
4894
|
-
}));
|
|
4895
|
-
return ids.map((id, index) => {
|
|
4896
|
-
const row = fetched?.get(id) ?? null;
|
|
4897
|
-
return {
|
|
4898
|
-
id,
|
|
4899
|
-
title: row?.title ?? titles[index] ?? FALLBACK_TITLE,
|
|
4900
|
-
status: row ? toTaskStatus(row.status) : "queued",
|
|
4901
|
-
createdAt: row?.createdAt ?? null,
|
|
4902
|
-
completedAt: row?.completedAt ?? null
|
|
4903
|
-
};
|
|
4904
|
-
});
|
|
4905
|
-
}
|
|
4906
|
-
|
|
4907
|
-
//#endregion
|
|
4908
|
-
//#region src/chat/tui/chunks/tool-subagent.tsx
|
|
4909
|
-
/** How often a live batch re-reads its tasks' status. */
|
|
4910
|
-
const POLL_INTERVAL_MS = 3e3;
|
|
4911
|
-
function rowGlyph(status) {
|
|
4912
|
-
switch (status) {
|
|
4913
|
-
case "completed": return {
|
|
4914
|
-
ch: "✔",
|
|
4915
|
-
fg: theme.success
|
|
4916
|
-
};
|
|
4917
|
-
case "failed":
|
|
4918
|
-
case "canceled": return {
|
|
4919
|
-
ch: "✗",
|
|
4920
|
-
fg: theme.error
|
|
4921
|
-
};
|
|
4922
|
-
case "running": return {
|
|
4923
|
-
ch: "▶",
|
|
4924
|
-
fg: theme.accent
|
|
4925
|
-
};
|
|
4926
|
-
case "queued": return {
|
|
4927
|
-
ch: "○",
|
|
4928
|
-
fg: theme.dim
|
|
4929
|
-
};
|
|
4930
|
-
}
|
|
4931
|
-
}
|
|
4932
|
-
function rowColor$1(status) {
|
|
4933
|
-
switch (status) {
|
|
4934
|
-
case "running": return theme.fg;
|
|
4935
|
-
case "completed": return theme.dim;
|
|
4936
|
-
default: return theme.muted;
|
|
4937
|
-
}
|
|
4938
|
-
}
|
|
4939
|
-
function headerLabel(rows) {
|
|
4940
|
-
const subject = rows.length === 1 ? "Subagent" : `${rows.length} subagents`;
|
|
4941
|
-
return rows.some((row) => isLiveStatus(row.status)) ? `${subject} running` : subject;
|
|
4942
|
-
}
|
|
4943
|
-
/**
|
|
4944
|
-
* The transcript block for a `subagent` tool call — the TUI analog of the web
|
|
4945
|
-
* chat's fan-out card. One row per delegated run: status glyph, the short
|
|
4946
|
-
* title the delegating agent wrote, ticking over live as children settle.
|
|
4947
|
-
*
|
|
4948
|
-
* Rows are seeded from the call itself (titles from its input, task ids from
|
|
4949
|
-
* its own output), so the card renders the moment the call does. Status then
|
|
4950
|
-
* comes from the server's task rows, polled while any run is live — the
|
|
4951
|
-
* delegated runs outlive the call that spawned them, so the transcript stream
|
|
4952
|
-
* carries nothing more about them. The poll stops the moment the batch
|
|
4953
|
-
* settles; reopening an old conversation does one fetch and stops there. A
|
|
4954
|
-
* failed poll is skipped silently (the next tick retries; stale rows beat an
|
|
4955
|
-
* error row in the scrollback).
|
|
4956
|
-
*/
|
|
4957
|
-
function ToolSubagent({ item, pinned = false, onLiveChange }) {
|
|
4958
|
-
const { rest, conversationId } = useConversationContext();
|
|
4959
|
-
const titles = subagentTaskTitles(item.input);
|
|
4960
|
-
const ids = item.output?.kind === "ok" ? queuedSubagentTaskIds(item.output.value) : [];
|
|
4961
|
-
const idsKey = ids.join(",");
|
|
4962
|
-
const [fetched, setFetched] = useState(null);
|
|
4963
|
-
const rows = subagentRows({
|
|
4964
|
-
titles,
|
|
4965
|
-
ids,
|
|
4966
|
-
fetched
|
|
4967
|
-
});
|
|
4968
|
-
const live = rows.length === 0 || rows.some((row) => isLiveStatus(row.status));
|
|
4969
|
-
useEffect(() => {
|
|
4970
|
-
onLiveChange?.(live);
|
|
4971
|
-
}, [live, onLiveChange]);
|
|
4972
|
-
useEffect(() => {
|
|
4973
|
-
if (!rest || !conversationId || idsKey.length === 0 || !live) return;
|
|
4974
|
-
let cancelled = false;
|
|
4975
|
-
let timer = null;
|
|
4976
|
-
const tick = async () => {
|
|
4977
|
-
try {
|
|
4978
|
-
const tasks = await rest.listSubagentTasks({ conversationId });
|
|
4979
|
-
if (cancelled) return;
|
|
4980
|
-
setFetched(tasks);
|
|
4981
|
-
} catch (_error) {}
|
|
4982
|
-
if (!cancelled) timer = setTimeout(tick, POLL_INTERVAL_MS);
|
|
4983
|
-
};
|
|
4984
|
-
tick();
|
|
4985
|
-
return () => {
|
|
4986
|
-
cancelled = true;
|
|
4987
|
-
if (timer !== null) clearTimeout(timer);
|
|
4988
|
-
};
|
|
4989
|
-
}, [
|
|
4990
|
-
rest,
|
|
4991
|
-
conversationId,
|
|
4992
|
-
idsKey,
|
|
4993
|
-
live
|
|
4994
|
-
]);
|
|
4995
|
-
if (rows.length === 0) return /* @__PURE__ */ jsxs("text", { children: [/* @__PURE__ */ jsx("span", {
|
|
4996
|
-
fg: theme.warning,
|
|
4997
|
-
children: "◐ "
|
|
4998
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
4999
|
-
fg: theme.tool,
|
|
5000
|
-
children: "Delegating to subagents…"
|
|
5001
|
-
})] });
|
|
5002
|
-
if (pinned && !live) return null;
|
|
5003
|
-
return /* @__PURE__ */ jsxs("box", {
|
|
5004
|
-
style: { flexDirection: "column" },
|
|
5005
|
-
children: [/* @__PURE__ */ jsxs("text", { children: [
|
|
5006
|
-
live ? /* @__PURE__ */ jsx(Spinner, { color: theme.accent }) : /* @__PURE__ */ jsx("span", {
|
|
5007
|
-
fg: theme.success,
|
|
5008
|
-
children: "✓"
|
|
5009
|
-
}),
|
|
5010
|
-
/* @__PURE__ */ jsxs("span", {
|
|
5011
|
-
fg: theme.tool,
|
|
5012
|
-
children: [" ", /* @__PURE__ */ jsx("b", { children: headerLabel(rows) })]
|
|
5013
|
-
}),
|
|
5014
|
-
/* @__PURE__ */ jsxs("span", {
|
|
5015
|
-
fg: theme.dim,
|
|
5016
|
-
children: [
|
|
5017
|
-
" ",
|
|
5018
|
-
rows.filter((row) => row.status === "completed").length,
|
|
5019
|
-
"/",
|
|
5020
|
-
rows.length
|
|
5021
|
-
]
|
|
5022
|
-
})
|
|
5023
|
-
] }), /* @__PURE__ */ jsx("box", {
|
|
5024
|
-
style: {
|
|
5025
|
-
flexDirection: "column",
|
|
5026
|
-
paddingLeft: 2
|
|
5027
|
-
},
|
|
5028
|
-
children: rows.map((row) => {
|
|
5029
|
-
const glyph = rowGlyph(row.status);
|
|
5030
|
-
return /* @__PURE__ */ jsxs("box", {
|
|
5031
|
-
style: { flexDirection: "row" },
|
|
5032
|
-
children: [/* @__PURE__ */ jsxs("text", {
|
|
5033
|
-
fg: glyph.fg,
|
|
5034
|
-
children: [glyph.ch, " "]
|
|
5035
|
-
}), /* @__PURE__ */ jsx("text", {
|
|
5036
|
-
fg: rowColor$1(row.status),
|
|
5037
|
-
children: row.title
|
|
5038
|
-
})]
|
|
5039
|
-
}, row.id);
|
|
5040
|
-
})
|
|
5041
|
-
})]
|
|
5042
|
-
});
|
|
5043
|
-
}
|
|
5044
|
-
|
|
5045
4752
|
//#endregion
|
|
5046
4753
|
//#region src/chat/tui/chunks/shell-local.tsx
|
|
5047
4754
|
/**
|
|
@@ -5080,6 +4787,19 @@ function ShellLocalItem({ item }) {
|
|
|
5080
4787
|
});
|
|
5081
4788
|
}
|
|
5082
4789
|
|
|
4790
|
+
//#endregion
|
|
4791
|
+
//#region src/chat/tui/chat/conversation-context.tsx
|
|
4792
|
+
const ConversationContext = createContext({
|
|
4793
|
+
rest: null,
|
|
4794
|
+
conversationId: null,
|
|
4795
|
+
collectSandboxStats: null,
|
|
4796
|
+
latestComputerId: null,
|
|
4797
|
+
scrolledUp: false
|
|
4798
|
+
});
|
|
4799
|
+
function useConversationContext() {
|
|
4800
|
+
return useContext(ConversationContext);
|
|
4801
|
+
}
|
|
4802
|
+
|
|
5083
4803
|
//#endregion
|
|
5084
4804
|
//#region src/chat/tui/chat/sandbox-stats.ts
|
|
5085
4805
|
/**
|
|
@@ -5764,7 +5484,7 @@ function ToolItem({ item }) {
|
|
|
5764
5484
|
case "read": return /* @__PURE__ */ jsx(ToolRead, { item });
|
|
5765
5485
|
case "grep":
|
|
5766
5486
|
case "glob": return /* @__PURE__ */ jsx(ToolGrep, { item });
|
|
5767
|
-
case "subagent": return
|
|
5487
|
+
case "subagent": return null;
|
|
5768
5488
|
default: return /* @__PURE__ */ jsx(ToolGeneric, { item });
|
|
5769
5489
|
}
|
|
5770
5490
|
}
|
|
@@ -6792,7 +6512,7 @@ function windowTodos(todos, cap = MAX_VISIBLE_ROWS) {
|
|
|
6792
6512
|
const sorted = [...todos].sort((a, b) => a.position - b.position);
|
|
6793
6513
|
let leadingDone = 0;
|
|
6794
6514
|
while (leadingDone < sorted.length && sorted[leadingDone]?.status === "completed") leadingDone += 1;
|
|
6795
|
-
const head = leadingDone >= 2 || sorted.length > cap ? leadingDone : 0;
|
|
6515
|
+
const head = leadingDone === sorted.length && sorted.length > 0 || leadingDone >= 2 || sorted.length > cap ? leadingDone : 0;
|
|
6796
6516
|
const rest = sorted.slice(head);
|
|
6797
6517
|
const rowBudget = Math.max(1, head > 0 ? cap - 1 : cap);
|
|
6798
6518
|
if (rest.length <= rowBudget) return {
|
|
@@ -6823,7 +6543,7 @@ function todoCardHeight(todos) {
|
|
|
6823
6543
|
}
|
|
6824
6544
|
|
|
6825
6545
|
//#endregion
|
|
6826
|
-
//#region src/chat/tui/chat/
|
|
6546
|
+
//#region src/chat/tui/chat/task-card.tsx
|
|
6827
6547
|
function glyph(status) {
|
|
6828
6548
|
switch (status) {
|
|
6829
6549
|
case "completed": return {
|
|
@@ -6844,46 +6564,26 @@ function glyph(status) {
|
|
|
6844
6564
|
};
|
|
6845
6565
|
}
|
|
6846
6566
|
}
|
|
6847
|
-
function rowColor(status) {
|
|
6848
|
-
switch (status) {
|
|
6849
|
-
case "completed": return theme.dim;
|
|
6850
|
-
case "in_progress": return theme.fg;
|
|
6851
|
-
case "failed": return theme.muted;
|
|
6852
|
-
default: return theme.muted;
|
|
6853
|
-
}
|
|
6854
|
-
}
|
|
6855
|
-
/**
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
* written a list (`platform todo write`) and until it clears one, so a
|
|
6859
|
-
* conversation the agent never wrote todos for grows no chrome for it.
|
|
6860
|
-
*
|
|
6861
|
-
* The card can't scroll like the web card, so it windows (see `windowTodos`):
|
|
6862
|
-
* a leading run of completed todos folds into a `✔ N done` line, the active
|
|
6863
|
-
* and upcoming rows fill the rest, and a `+N more` tail counts pending work
|
|
6864
|
-
* past the cap. The window follows the in-progress row down as todos complete.
|
|
6865
|
-
*
|
|
6866
|
-
* `isActive` dims the header while the run is idle: an in-progress row only
|
|
6867
|
-
* reads as "happening now" when a run is actually producing output.
|
|
6868
|
-
*/
|
|
6869
|
-
function TodoCardView({ todos, isActive }) {
|
|
6870
|
-
if (todos.length === 0) return null;
|
|
6871
|
-
const completed = todos.filter((t) => t.status === "completed").length;
|
|
6567
|
+
function rowColor(status) {
|
|
6568
|
+
switch (status) {
|
|
6569
|
+
case "completed": return theme.dim;
|
|
6570
|
+
case "in_progress": return theme.fg;
|
|
6571
|
+
case "failed": return theme.muted;
|
|
6572
|
+
default: return theme.muted;
|
|
6573
|
+
}
|
|
6574
|
+
}
|
|
6575
|
+
/** Shared plan/subagent checklist content. The caller owns outer spacing. */
|
|
6576
|
+
function TaskCardContent({ label, todos, isActive }) {
|
|
6577
|
+
const completed = todos.filter((todo) => todo.status === "completed").length;
|
|
6872
6578
|
const { doneSummary, rows, moreCount } = windowTodos(todos);
|
|
6873
6579
|
return /* @__PURE__ */ jsxs("box", {
|
|
6874
|
-
style: {
|
|
6875
|
-
flexDirection: "column",
|
|
6876
|
-
flexShrink: 0,
|
|
6877
|
-
marginTop: 1,
|
|
6878
|
-
paddingLeft: 1,
|
|
6879
|
-
paddingRight: 1
|
|
6880
|
-
},
|
|
6580
|
+
style: { flexDirection: "column" },
|
|
6881
6581
|
children: [
|
|
6882
6582
|
/* @__PURE__ */ jsxs("box", {
|
|
6883
6583
|
style: { flexDirection: "row" },
|
|
6884
6584
|
children: [/* @__PURE__ */ jsx("text", {
|
|
6885
6585
|
fg: isActive ? theme.accent : theme.muted,
|
|
6886
|
-
children: /* @__PURE__ */ jsx("b", { children:
|
|
6586
|
+
children: /* @__PURE__ */ jsx("b", { children: label })
|
|
6887
6587
|
}), /* @__PURE__ */ jsxs("text", {
|
|
6888
6588
|
fg: theme.dim,
|
|
6889
6589
|
children: [
|
|
@@ -6928,26 +6628,243 @@ function TodoCardView({ todos, isActive }) {
|
|
|
6928
6628
|
]
|
|
6929
6629
|
});
|
|
6930
6630
|
}
|
|
6631
|
+
function taskCardContentHeight(todos) {
|
|
6632
|
+
if (todos.length === 0) return 0;
|
|
6633
|
+
const window = windowTodos(todos);
|
|
6634
|
+
return 1 + (window.doneSummary > 0 ? 1 : 0) + window.rows.length + (window.moreCount > 0 ? 1 : 0);
|
|
6635
|
+
}
|
|
6636
|
+
|
|
6637
|
+
//#endregion
|
|
6638
|
+
//#region src/chat/tui/chat/todo-card.tsx
|
|
6639
|
+
/**
|
|
6640
|
+
* The agent's live todo list, pinned just above the composer — the TUI analog
|
|
6641
|
+
* of the web chat's todo card. Rendered only once the agent has actually
|
|
6642
|
+
* written a list (`platform todo write`) and until it clears one, so a
|
|
6643
|
+
* conversation the agent never wrote todos for grows no chrome for it.
|
|
6644
|
+
*
|
|
6645
|
+
* The card can't scroll like the web card, so it windows completed and pending
|
|
6646
|
+
* work. `isActive` dims the header while the run is idle.
|
|
6647
|
+
*/
|
|
6648
|
+
function TodoCardView({ todos, isActive }) {
|
|
6649
|
+
if (todos.length === 0) return null;
|
|
6650
|
+
return /* @__PURE__ */ jsx("box", {
|
|
6651
|
+
style: {
|
|
6652
|
+
flexDirection: "column",
|
|
6653
|
+
flexShrink: 0,
|
|
6654
|
+
marginTop: 1,
|
|
6655
|
+
paddingLeft: 1,
|
|
6656
|
+
paddingRight: 1
|
|
6657
|
+
},
|
|
6658
|
+
children: /* @__PURE__ */ jsx(TaskCardContent, {
|
|
6659
|
+
label: "Plan",
|
|
6660
|
+
todos,
|
|
6661
|
+
isActive
|
|
6662
|
+
})
|
|
6663
|
+
});
|
|
6664
|
+
}
|
|
6665
|
+
|
|
6666
|
+
//#endregion
|
|
6667
|
+
//#region src/chat/tui/chat/subagents.ts
|
|
6668
|
+
/** A run the platform hasn't settled yet — still queued or mid-run. */
|
|
6669
|
+
function isLiveStatus(status) {
|
|
6670
|
+
return status === "queued" || status === "running";
|
|
6671
|
+
}
|
|
6672
|
+
function toTaskStatus(raw) {
|
|
6673
|
+
switch (raw) {
|
|
6674
|
+
case "queued":
|
|
6675
|
+
case "running":
|
|
6676
|
+
case "completed":
|
|
6677
|
+
case "failed":
|
|
6678
|
+
case "canceled": return raw;
|
|
6679
|
+
default: return "queued";
|
|
6680
|
+
}
|
|
6681
|
+
}
|
|
6682
|
+
const FALLBACK_TITLE = "Subagent task";
|
|
6683
|
+
/**
|
|
6684
|
+
* Each delegated task's display title from the `subagent` call's input, in
|
|
6685
|
+
* call order — the short name the tool requires per task. A malformed entry
|
|
6686
|
+
* yields a fallback label rather than being dropped, so the row count always
|
|
6687
|
+
* matches the fan-out width. Mirrors the web client's `subagentTaskTitles`.
|
|
6688
|
+
*/
|
|
6689
|
+
function subagentTaskTitles(input) {
|
|
6690
|
+
if (!isRecord(input)) return [];
|
|
6691
|
+
const tasks = input["tasks"];
|
|
6692
|
+
if (!Array.isArray(tasks)) return [];
|
|
6693
|
+
return tasks.map((task) => {
|
|
6694
|
+
if (!isRecord(task)) return FALLBACK_TITLE;
|
|
6695
|
+
const title = task["title"];
|
|
6696
|
+
return typeof title === "string" && title.trim() ? title.trim() : FALLBACK_TITLE;
|
|
6697
|
+
});
|
|
6698
|
+
}
|
|
6699
|
+
const QUEUED_TASK_ID_LINE = /^\s*-\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}):\s/i;
|
|
6700
|
+
/**
|
|
6701
|
+
* The ids of the runs a `subagent` call queued, read back out of its own
|
|
6702
|
+
* output. The call returns the moment the runs exist, so the ids are there
|
|
6703
|
+
* long before any child finishes — they are what ties this card to the task
|
|
6704
|
+
* rows the status poll returns, and their order is the tasks' call order.
|
|
6705
|
+
*/
|
|
6706
|
+
function queuedSubagentTaskIds(output) {
|
|
6707
|
+
const text = flattenOutputText(output);
|
|
6708
|
+
if (!text) return [];
|
|
6709
|
+
return text.split("\n").map((line) => line.match(QUEUED_TASK_ID_LINE)?.[1] ?? null).filter((id) => id !== null);
|
|
6710
|
+
}
|
|
6711
|
+
/**
|
|
6712
|
+
* Tool output reaches the TUI as `unknown`: the raw string live, possibly
|
|
6713
|
+
* wrapped in content parts or arrays when loaded from history. Walk it for
|
|
6714
|
+
* string leaves, newline-joined — the PR scanner's `stringifyToolOutput`
|
|
6715
|
+
* shape, bounded small because the id list sits at the top of the output.
|
|
6716
|
+
*/
|
|
6717
|
+
function flattenOutputText(value) {
|
|
6718
|
+
const MAX_LEN = 2e4;
|
|
6719
|
+
const parts = [];
|
|
6720
|
+
let total = 0;
|
|
6721
|
+
const visit = (v, depth) => {
|
|
6722
|
+
if (total >= MAX_LEN || depth > 4) return;
|
|
6723
|
+
if (typeof v === "string") {
|
|
6724
|
+
parts.push(v);
|
|
6725
|
+
total += v.length;
|
|
6726
|
+
return;
|
|
6727
|
+
}
|
|
6728
|
+
if (Array.isArray(v)) {
|
|
6729
|
+
for (const item of v) visit(item, depth + 1);
|
|
6730
|
+
return;
|
|
6731
|
+
}
|
|
6732
|
+
if (isRecord(v)) for (const item of Object.values(v)) visit(item, depth + 1);
|
|
6733
|
+
};
|
|
6734
|
+
visit(value, 0);
|
|
6735
|
+
return parts.join("\n");
|
|
6736
|
+
}
|
|
6737
|
+
function nextAnchoredSubagentIds({ current, nextId, liveById }) {
|
|
6738
|
+
if (current.includes(nextId)) return current;
|
|
6739
|
+
return current.some((id) => liveById[id] ?? true) ? [...current, nextId] : [nextId];
|
|
6740
|
+
}
|
|
6741
|
+
/**
|
|
6742
|
+
* The card's rows: one per queued run id (call order), each carrying the
|
|
6743
|
+
* freshest known state. A task the status fetch hasn't returned yet falls
|
|
6744
|
+
* back to the input's title and reads as queued; a fetched row's own title
|
|
6745
|
+
* wins over the input's (the server's copy is the one the child actually
|
|
6746
|
+
* ran under). Before the ids exist — the call is still streaming — rows come
|
|
6747
|
+
* from the input titles alone, so the card never renders empty mid-turn.
|
|
6748
|
+
*/
|
|
6749
|
+
function subagentRows({ titles, ids, fetched }) {
|
|
6750
|
+
if (ids.length === 0) return titles.map((title, index) => ({
|
|
6751
|
+
id: `queued:${index}`,
|
|
6752
|
+
title,
|
|
6753
|
+
status: "queued",
|
|
6754
|
+
createdAt: null,
|
|
6755
|
+
completedAt: null
|
|
6756
|
+
}));
|
|
6757
|
+
return ids.map((id, index) => {
|
|
6758
|
+
const row = fetched?.get(id) ?? null;
|
|
6759
|
+
return {
|
|
6760
|
+
id,
|
|
6761
|
+
title: row?.title ?? titles[index] ?? FALLBACK_TITLE,
|
|
6762
|
+
status: row ? toTaskStatus(row.status) : "queued",
|
|
6763
|
+
createdAt: row?.createdAt ?? null,
|
|
6764
|
+
completedAt: row?.completedAt ?? null
|
|
6765
|
+
};
|
|
6766
|
+
});
|
|
6767
|
+
}
|
|
6768
|
+
|
|
6769
|
+
//#endregion
|
|
6770
|
+
//#region src/chat/tui/chunks/tool-subagent.tsx
|
|
6771
|
+
/** How often a live batch re-reads its tasks' status. */
|
|
6772
|
+
const POLL_INTERVAL_MS = 3e3;
|
|
6773
|
+
function subagentTodos(rows) {
|
|
6774
|
+
return rows.map((row, position) => ({
|
|
6775
|
+
id: row.id,
|
|
6776
|
+
content: row.title,
|
|
6777
|
+
position,
|
|
6778
|
+
status: row.status === "completed" ? "completed" : row.status === "running" ? "in_progress" : row.status === "failed" || row.status === "canceled" ? "failed" : "pending"
|
|
6779
|
+
}));
|
|
6780
|
+
}
|
|
6781
|
+
/**
|
|
6782
|
+
* The transcript block for a `subagent` tool call — the TUI analog of the web
|
|
6783
|
+
* chat's fan-out card. One row per delegated run: status glyph, the short
|
|
6784
|
+
* title the delegating agent wrote, ticking over live as children settle.
|
|
6785
|
+
*
|
|
6786
|
+
* Rows are seeded from the call itself (titles from its input, task ids from
|
|
6787
|
+
* its own output), so the card renders the moment the call does. Status then
|
|
6788
|
+
* comes from the server's task rows, polled while any run is live — the
|
|
6789
|
+
* delegated runs outlive the call that spawned them, so the transcript stream
|
|
6790
|
+
* carries nothing more about them. The poll stops the moment the batch
|
|
6791
|
+
* settles; reopening an old conversation does one fetch and stops there. A
|
|
6792
|
+
* failed poll is skipped silently (the next tick retries; stale rows beat an
|
|
6793
|
+
* error row in the scrollback).
|
|
6794
|
+
*/
|
|
6795
|
+
function ToolSubagent({ item, isActive, onLiveChange, onHeightChange }) {
|
|
6796
|
+
const { rest, conversationId } = useConversationContext();
|
|
6797
|
+
const titles = subagentTaskTitles(item.input);
|
|
6798
|
+
const ids = item.output?.kind === "ok" ? queuedSubagentTaskIds(item.output.value) : [];
|
|
6799
|
+
const idsKey = ids.join(",");
|
|
6800
|
+
const [fetched, setFetched] = useState(null);
|
|
6801
|
+
const rows = subagentRows({
|
|
6802
|
+
titles,
|
|
6803
|
+
ids,
|
|
6804
|
+
fetched
|
|
6805
|
+
});
|
|
6806
|
+
const live = rows.length === 0 || rows.some((row) => isLiveStatus(row.status));
|
|
6807
|
+
const todos = subagentTodos(rows);
|
|
6808
|
+
useEffect(() => {
|
|
6809
|
+
onLiveChange?.(live);
|
|
6810
|
+
}, [live, onLiveChange]);
|
|
6811
|
+
useEffect(() => {
|
|
6812
|
+
onHeightChange?.(taskCardContentHeight(todos));
|
|
6813
|
+
}, [onHeightChange, todos]);
|
|
6814
|
+
useEffect(() => {
|
|
6815
|
+
if (!rest || !conversationId || idsKey.length === 0 || !live) return;
|
|
6816
|
+
let cancelled = false;
|
|
6817
|
+
let timer = null;
|
|
6818
|
+
const tick = async () => {
|
|
6819
|
+
try {
|
|
6820
|
+
const tasks = await rest.listSubagentTasks({ conversationId });
|
|
6821
|
+
if (cancelled) return;
|
|
6822
|
+
setFetched(tasks);
|
|
6823
|
+
} catch (_error) {}
|
|
6824
|
+
if (!cancelled) timer = setTimeout(tick, POLL_INTERVAL_MS);
|
|
6825
|
+
};
|
|
6826
|
+
tick();
|
|
6827
|
+
return () => {
|
|
6828
|
+
cancelled = true;
|
|
6829
|
+
if (timer !== null) clearTimeout(timer);
|
|
6830
|
+
};
|
|
6831
|
+
}, [
|
|
6832
|
+
rest,
|
|
6833
|
+
conversationId,
|
|
6834
|
+
idsKey,
|
|
6835
|
+
live
|
|
6836
|
+
]);
|
|
6837
|
+
if (rows.length === 0) return /* @__PURE__ */ jsxs("text", { children: [/* @__PURE__ */ jsx("span", {
|
|
6838
|
+
fg: theme.warning,
|
|
6839
|
+
children: "◐ "
|
|
6840
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
6841
|
+
fg: theme.tool,
|
|
6842
|
+
children: "Delegating to subagents…"
|
|
6843
|
+
})] });
|
|
6844
|
+
return /* @__PURE__ */ jsx(TaskCardContent, {
|
|
6845
|
+
label: rows.length === 1 ? "Subagent" : "Subagents",
|
|
6846
|
+
todos,
|
|
6847
|
+
isActive: isActive ?? live
|
|
6848
|
+
});
|
|
6849
|
+
}
|
|
6931
6850
|
|
|
6932
6851
|
//#endregion
|
|
6933
6852
|
//#region src/chat/tui/chat/subagent-card.tsx
|
|
6934
6853
|
/**
|
|
6935
|
-
*
|
|
6936
|
-
*
|
|
6937
|
-
* at their original transcript position.
|
|
6854
|
+
* Delegated work pinned above the plan. Each batch owns its status polling and
|
|
6855
|
+
* stays as a dimmed completion summary after every run settles.
|
|
6938
6856
|
*/
|
|
6939
|
-
function SubagentCardView({ items, onHeightChange }) {
|
|
6940
|
-
const [
|
|
6941
|
-
const
|
|
6942
|
-
|
|
6857
|
+
function SubagentCardView({ items, isActive, onHeightChange, onBatchLiveChange }) {
|
|
6858
|
+
const [heightById, setHeightById] = useState({});
|
|
6859
|
+
const setBatchHeight = useCallback((id, height) => {
|
|
6860
|
+
setHeightById((current) => current[id] === height ? current : {
|
|
6943
6861
|
...current,
|
|
6944
|
-
[id]:
|
|
6862
|
+
[id]: height
|
|
6945
6863
|
});
|
|
6946
6864
|
}, []);
|
|
6947
|
-
const
|
|
6948
|
-
const height = liveItems.length === 0 ? 0 : 1 + liveItems.reduce((rows, item) => rows + 1 + subagentTaskTitles(item.input).length, 0) + (liveItems.length - 1);
|
|
6865
|
+
const height = useMemo(() => items.length === 0 ? 0 : 1 + items.reduce((rows, item) => rows + (heightById[item.id] ?? 0), 0) + (items.length - 1), [heightById, items]);
|
|
6949
6866
|
useEffect(() => onHeightChange?.(height), [height, onHeightChange]);
|
|
6950
|
-
if (
|
|
6867
|
+
if (items.length === 0) return null;
|
|
6951
6868
|
return /* @__PURE__ */ jsx("box", {
|
|
6952
6869
|
style: {
|
|
6953
6870
|
flexDirection: "column",
|
|
@@ -6957,10 +6874,11 @@ function SubagentCardView({ items, onHeightChange }) {
|
|
|
6957
6874
|
paddingRight: 1,
|
|
6958
6875
|
gap: 1
|
|
6959
6876
|
},
|
|
6960
|
-
children:
|
|
6877
|
+
children: items.map((item) => /* @__PURE__ */ jsx(ToolSubagent, {
|
|
6961
6878
|
item,
|
|
6962
|
-
|
|
6963
|
-
onLiveChange: (live) =>
|
|
6879
|
+
isActive,
|
|
6880
|
+
onLiveChange: (live) => onBatchLiveChange?.(item.id, live),
|
|
6881
|
+
onHeightChange: (batchHeight) => setBatchHeight(item.id, batchHeight)
|
|
6964
6882
|
}, item.id))
|
|
6965
6883
|
});
|
|
6966
6884
|
}
|
|
@@ -7283,6 +7201,49 @@ function NewChatSplash({ agentName, agentTitle, width, height }) {
|
|
|
7283
7201
|
});
|
|
7284
7202
|
}
|
|
7285
7203
|
|
|
7204
|
+
//#endregion
|
|
7205
|
+
//#region src/chat/tui/spinner-frames.ts
|
|
7206
|
+
/** Braille dot-cycle frames — the classic smooth terminal spinner. */
|
|
7207
|
+
const SPINNER_FRAMES = [
|
|
7208
|
+
"⠋",
|
|
7209
|
+
"⠙",
|
|
7210
|
+
"⠹",
|
|
7211
|
+
"⠸",
|
|
7212
|
+
"⠼",
|
|
7213
|
+
"⠴",
|
|
7214
|
+
"⠦",
|
|
7215
|
+
"⠧",
|
|
7216
|
+
"⠇",
|
|
7217
|
+
"⠏"
|
|
7218
|
+
];
|
|
7219
|
+
/** Milliseconds between frames. ~12.5fps reads as smooth without thrashing. */
|
|
7220
|
+
const SPINNER_INTERVAL_MS = 80;
|
|
7221
|
+
/** Frame for a given tick count. Wraps at the end of the cycle; negative-safe. */
|
|
7222
|
+
function spinnerFrame(tick) {
|
|
7223
|
+
const n = SPINNER_FRAMES.length;
|
|
7224
|
+
return SPINNER_FRAMES[(Math.trunc(tick) % n + n) % n] ?? SPINNER_FRAMES[0];
|
|
7225
|
+
}
|
|
7226
|
+
|
|
7227
|
+
//#endregion
|
|
7228
|
+
//#region src/chat/tui/spinner.tsx
|
|
7229
|
+
/**
|
|
7230
|
+
* An animated spinner glyph. Advances one frame every SPINNER_INTERVAL_MS via
|
|
7231
|
+
* a self-owned interval that's cleared on unmount, so the animation runs only
|
|
7232
|
+
* while the component is mounted. Returns a <span>, so render it inside a
|
|
7233
|
+
* <text> (matching how the rest of the TUI composes inline glyphs).
|
|
7234
|
+
*/
|
|
7235
|
+
function Spinner({ color }) {
|
|
7236
|
+
const [tick, setTick] = useState(0);
|
|
7237
|
+
useEffect(() => {
|
|
7238
|
+
const id = setInterval(() => setTick((t) => t + 1), SPINNER_INTERVAL_MS);
|
|
7239
|
+
return () => clearInterval(id);
|
|
7240
|
+
}, []);
|
|
7241
|
+
return /* @__PURE__ */ jsx("span", {
|
|
7242
|
+
fg: color,
|
|
7243
|
+
children: spinnerFrame(tick)
|
|
7244
|
+
});
|
|
7245
|
+
}
|
|
7246
|
+
|
|
7286
7247
|
//#endregion
|
|
7287
7248
|
//#region src/chat/tui/prompt-history.ts
|
|
7288
7249
|
/**
|
|
@@ -11418,6 +11379,11 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11418
11379
|
const [todos, setTodos] = useState(null);
|
|
11419
11380
|
const [planHidden, setPlanHidden] = useState(false);
|
|
11420
11381
|
const [subagentRows, setSubagentRows] = useState(0);
|
|
11382
|
+
const [anchoredSubagentIds, setAnchoredSubagentIds] = useState([]);
|
|
11383
|
+
const anchoredSubagentLiveRef = useRef({});
|
|
11384
|
+
const setAnchoredBatchLive = useCallback((id, live) => {
|
|
11385
|
+
anchoredSubagentLiveRef.current[id] = live;
|
|
11386
|
+
}, []);
|
|
11421
11387
|
const [scrolledUp, setScrolledUp] = useState(false);
|
|
11422
11388
|
const hadTodosRef = useRef(false);
|
|
11423
11389
|
useEffect(() => {
|
|
@@ -11560,7 +11526,10 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11560
11526
|
const menuRows = menuOpen ? completionMenuHeight(menuCommands.length) : 0;
|
|
11561
11527
|
const todoCardVisible = !credPrompt && !planHidden && !!todos && todos.length > 0;
|
|
11562
11528
|
const todoRows = todoCardVisible ? todoCardHeight(todos) : 0;
|
|
11563
|
-
const subagentItems = useMemo(() =>
|
|
11529
|
+
const subagentItems = useMemo(() => {
|
|
11530
|
+
const ids = new Set(anchoredSubagentIds);
|
|
11531
|
+
return items.filter((item) => item.kind === "tool" && item.toolName === "subagent" && ids.has(item.id));
|
|
11532
|
+
}, [items, anchoredSubagentIds]);
|
|
11564
11533
|
const jumpHintVisible = scrolledUp && !credPrompt;
|
|
11565
11534
|
const jumpHintRows = jumpHintVisible ? 1 : 0;
|
|
11566
11535
|
const scrollHeight = Math.max(3, chatHeight - 2 - bottomBoxHeight - pendingRows - inlineReviewHeight - menuRows - jumpHintRows - subagentRows - todoRows);
|
|
@@ -11683,6 +11652,18 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11683
11652
|
if (event.chunk.type === "error") sawErrorChunk = true;
|
|
11684
11653
|
responsePreview.addChunk(event.chunk);
|
|
11685
11654
|
prScanner.addChunk(event.chunk);
|
|
11655
|
+
if ((event.chunk.type === "tool-input-start" || event.chunk.type === "tool-input-available") && event.chunk.toolName === "subagent" && typeof event.chunk.toolCallId === "string") {
|
|
11656
|
+
const id = event.chunk.toolCallId;
|
|
11657
|
+
setAnchoredSubagentIds((current) => {
|
|
11658
|
+
const next = nextAnchoredSubagentIds({
|
|
11659
|
+
current,
|
|
11660
|
+
nextId: id,
|
|
11661
|
+
liveById: anchoredSubagentLiveRef.current
|
|
11662
|
+
});
|
|
11663
|
+
if (next.length === 1 && next[0] === id) anchoredSubagentLiveRef.current = { [id]: true };
|
|
11664
|
+
return next;
|
|
11665
|
+
});
|
|
11666
|
+
}
|
|
11686
11667
|
if (event.chunk.type === "data-anyone-todos-updated") {
|
|
11687
11668
|
const next = todosFromPayload(event.chunk.data);
|
|
11688
11669
|
if (next) setTodos(next.length > 0 ? next : null);
|
|
@@ -13363,7 +13344,9 @@ function ChatScreen({ agent, conversation }) {
|
|
|
13363
13344
|
}) : null,
|
|
13364
13345
|
/* @__PURE__ */ jsx(SubagentCardView, {
|
|
13365
13346
|
items: subagentItems,
|
|
13366
|
-
|
|
13347
|
+
isActive: run.kind !== "idle",
|
|
13348
|
+
onHeightChange: setSubagentRows,
|
|
13349
|
+
onBatchLiveChange: setAnchoredBatchLive
|
|
13367
13350
|
}),
|
|
13368
13351
|
todoCardVisible && todos ? /* @__PURE__ */ jsx(TodoCardView, {
|
|
13369
13352
|
todos,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./client-DbqRBquD.mjs";
|
|
3
|
-
import { a as runPortalDaemon, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-
|
|
3
|
+
import { a as runPortalDaemon, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-DuxaUuek.mjs";
|
|
4
4
|
import "./api-DG5W6iwx.mjs";
|
|
5
5
|
|
|
6
6
|
export { runPortalDaemon };
|
|
@@ -20,7 +20,7 @@ import { access, appendFile, mkdir, readFile, unlink, writeFile } from "node:fs/
|
|
|
20
20
|
* the commit time of the built tree is one monotonic clock they share.
|
|
21
21
|
*/
|
|
22
22
|
function portalDaemonBuild() {
|
|
23
|
-
return "
|
|
23
|
+
return "1786755327";
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Whether a client carrying `mine` should replace a running daemon carrying
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { c as LOCAL_PROTOCOL_VERSION, d as encodeLine, f as makeLineParser, n as ensureDaemonRunning, p as parseDaemonMessage, u as daemonPaths } from "./daemon-
|
|
2
|
+
import { c as LOCAL_PROTOCOL_VERSION, d as encodeLine, f as makeLineParser, n as ensureDaemonRunning, p as parseDaemonMessage, u as daemonPaths } from "./daemon-DuxaUuek.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { connect } from "node:net";
|
|
5
5
|
|
package/package.json
CHANGED