@bastani/atomic 0.9.14-alpha.3 → 0.9.14-alpha.4
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 +10 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/CHANGELOG.md +10 -0
- package/dist/builtin/web-access/README.md +3 -6
- package/dist/builtin/web-access/index.ts +1 -2
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/web-access/web-search-config.ts +2 -8
- package/dist/builtin/web-access/web-search-tool.ts +2 -7
- package/dist/builtin/web-access/web-search-workflow.ts +10 -0
- package/dist/builtin/workflows/CHANGELOG.md +6 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +58 -29
- package/dist/builtin/workflows/src/tui/graph-theme.ts +11 -0
- package/dist/builtin/workflows/src/tui/graph-view-render.ts +19 -10
- package/dist/builtin/workflows/src/tui/tool-detail.ts +45 -26
- package/docs/workflows.md +1 -1
- package/npm-shrinkwrap.json +29 -29
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.14-alpha.4] - 2026-08-18
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Bundled `web_search` no longer opens the curator confirmation UI by default. Persist `"workflow": "summary-review"` in web-search config or run `/curator on` to opt in.
|
|
10
|
+
|
|
11
|
+
### Removed
|
|
12
|
+
|
|
13
|
+
- The `workflow` parameter on bundled `web_search`. Models cannot select the curator per call.
|
|
14
|
+
|
|
5
15
|
## [0.9.14-alpha.3] - 2026-08-17
|
|
6
16
|
|
|
7
17
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/intercom",
|
|
3
|
-
"version": "0.9.14-alpha.
|
|
3
|
+
"version": "0.9.14-alpha.4",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension providing a private coordination channel between parent and child agent sessions. Fork of: https://github.com/nicobailon/pi-intercom",
|
|
6
6
|
"contributors": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/mcp",
|
|
3
|
-
"version": "0.9.14-alpha.
|
|
3
|
+
"version": "0.9.14-alpha.4",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension that adapts MCP (Model Context Protocol) servers into the coding agent. Fork of: https://github.com/nicobailon/pi-mcp-adapter",
|
|
6
6
|
"contributors": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/subagents",
|
|
3
|
-
"version": "0.9.14-alpha.
|
|
3
|
+
"version": "0.9.14-alpha.4",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for delegating tasks to subagents with parallel execution. Fork of: https://github.com/nicobailon/pi-subagents",
|
|
6
6
|
"contributors": [
|
|
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.9.14-alpha.4] - 2026-08-18
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- The interactive search curator is now opt-in. `web_search` returns raw results unless you set `"workflow": "summary-review"` in web-search config or run `/curator on`.
|
|
12
|
+
|
|
13
|
+
### Removed
|
|
14
|
+
|
|
15
|
+
- The `workflow` parameter on `web_search`. The curator is no longer selectable per tool call; enable it with web-search config or `/curator`.
|
|
16
|
+
|
|
7
17
|
## [0.9.14-alpha.3] - 2026-08-17
|
|
8
18
|
|
|
9
19
|
### Fixed
|
|
@@ -78,7 +78,7 @@ For batch search/fetch calls, partial success remains usable and retains item-le
|
|
|
78
78
|
|
|
79
79
|
### web_search
|
|
80
80
|
|
|
81
|
-
Search the web via Exa, Perplexity AI, or Gemini. Returns a synthesized answer with source citations.
|
|
81
|
+
Search the web via Exa, Perplexity AI, or Gemini. Returns a synthesized answer with source citations. The curator UI is off by default; enable it with `/curator on` or `"workflow": "summary-review"` in config.
|
|
82
82
|
|
|
83
83
|
```typescript
|
|
84
84
|
web_search({ query: "rust async programming" })
|
|
@@ -87,8 +87,6 @@ web_search({ query: "latest news", numResults: 10, recencyFilter: "week" })
|
|
|
87
87
|
web_search({ query: "...", domainFilter: ["github.com"] })
|
|
88
88
|
web_search({ query: "...", provider: "exa" })
|
|
89
89
|
web_search({ query: "...", includeContent: true })
|
|
90
|
-
web_search({ queries: ["query 1", "query 2"], workflow: "none" })
|
|
91
|
-
web_search({ queries: ["query 1", "query 2"], workflow: "summary-review" })
|
|
92
90
|
```
|
|
93
91
|
|
|
94
92
|
| Parameter | Description |
|
|
@@ -99,7 +97,6 @@ web_search({ queries: ["query 1", "query 2"], workflow: "summary-review" })
|
|
|
99
97
|
| `domainFilter` | Limit to domains (prefix with `-` to exclude) |
|
|
100
98
|
| `provider` | `auto` (default), `exa`, `perplexity`, or `gemini` |
|
|
101
99
|
| `includeContent` | Fetch full page content from sources in background |
|
|
102
|
-
| `workflow` | `none` (skip curator) or `summary-review` (auto-generate summary draft after search completion, default) |
|
|
103
100
|
|
|
104
101
|
### code_search
|
|
105
102
|
|
|
@@ -227,7 +224,7 @@ Toggle or configure the curator workflow at runtime.
|
|
|
227
224
|
/curator summary-review # explicit workflow
|
|
228
225
|
```
|
|
229
226
|
|
|
230
|
-
Persists to `~/.pi/web-search.json` and takes effect on the next `web_search` call. When disabled, `web_search` returns raw results without opening the curator window.
|
|
227
|
+
Persists to `~/.pi/web-search.json` and takes effect on the next `web_search` call. The curator is off by default. When disabled, `web_search` returns raw results without opening the curator window.
|
|
231
228
|
|
|
232
229
|
### /search
|
|
233
230
|
|
|
@@ -287,7 +284,7 @@ All config lives in `~/.pi/web-search.json`. Every field is optional.
|
|
|
287
284
|
}
|
|
288
285
|
```
|
|
289
286
|
|
|
290
|
-
`EXA_API_KEY`, `GEMINI_API_KEY`, and `PERPLEXITY_API_KEY` env vars take precedence over config file values. `provider` sets the default search provider: `"exa"`, `"perplexity"`, or `"gemini"`. This is also updated automatically when you change the provider in the curator UI. `workflow` sets the
|
|
287
|
+
`EXA_API_KEY`, `GEMINI_API_KEY`, and `PERPLEXITY_API_KEY` env vars take precedence over config file values. `provider` sets the default search provider: `"exa"`, `"perplexity"`, or `"gemini"`. This is also updated automatically when you change the provider in the curator UI. `workflow` sets the curator mode: `"none"` (default, raw results, no curator) or `"summary-review"` (opens curator with auto-generated summary draft). Enable it in this file or toggle at runtime with `/curator`. `chromeProfile` overrides the Chromium profile directory used for Gemini Web cookie lookup. `allowBrowserCookies` enables Chromium cookie extraction for Gemini Web; it defaults to `false` to avoid surprise macOS Keychain prompts. You can also set `PI_ALLOW_BROWSER_COOKIES=1`. `searchModel` overrides the Gemini API model used by `web_search` without changing URL, YouTube, or video extraction defaults. `summaryModel` sets the default model used for generating summary drafts in the curator UI (e.g. `"anthropic/claude-haiku-4-5"` or `"openai-codex/gpt-5.3-codex-spark"`). Only models available in your model registry are eligible; if the configured model is unavailable, the default falls back to the built-in preference list. `curatorTimeoutSeconds` controls the initial curator idle timeout (default `20`, max `600`); users can still adjust the timer in the curator UI.
|
|
291
288
|
|
|
292
289
|
### Shortcuts
|
|
293
290
|
|
|
@@ -320,7 +320,7 @@ export default function webAccess(pi: ExtensionAPI) {
|
|
|
320
320
|
pi.registerTool({
|
|
321
321
|
name: "web_search",
|
|
322
322
|
label: "Web Search",
|
|
323
|
-
description: "Search the web using Perplexity AI, Exa, or Gemini. Returns an AI-synthesized answer with source citations. For comprehensive research, prefer queries (plural) with 2-4 varied angles over a single query — each query gets its own synthesized answer, so varying phrasing and scope gives much broader coverage. When includeContent is true, full page content is fetched in the background. Searches
|
|
323
|
+
description: "Search the web using Perplexity AI, Exa, or Gemini. Returns an AI-synthesized answer with source citations. For comprehensive research, prefer queries (plural) with 2-4 varied angles over a single query — each query gets its own synthesized answer, so varying phrasing and scope gives much broader coverage. When includeContent is true, full page content is fetched in the background. Searches return raw results by default. Enable the interactive browser curator with /curator on or by setting workflow to \"summary-review\" in web-search config. Provider auto-selects: Exa (direct API with key, MCP fallback without), else Perplexity (needs key), else Gemini API (needs key), else Gemini Web (needs a supported Chromium-based browser login).",
|
|
324
324
|
promptSnippet: "Use for web research questions. Prefer {queries:[...]} with 2-4 varied angles over a single query for broader coverage.",
|
|
325
325
|
parameters: Type.Object({
|
|
326
326
|
query: Type.Optional(Type.String({ description: "Single search query. For research tasks, prefer 'queries' with multiple varied angles instead." })),
|
|
@@ -330,7 +330,6 @@ export default function webAccess(pi: ExtensionAPI) {
|
|
|
330
330
|
recencyFilter: Type.Optional(Type.String({ enum: ["day", "week", "month", "year"], description: "Filter by recency" })),
|
|
331
331
|
domainFilter: Type.Optional(Type.Array(Type.String(), { description: "Limit to domains (prefix with - to exclude)" })),
|
|
332
332
|
provider: Type.Optional(Type.String({ enum: ["auto", "perplexity", "gemini", "exa"], description: "Search provider (default: auto)" })),
|
|
333
|
-
workflow: Type.Optional(Type.String({ enum: ["none", "summary-review"], description: "Search workflow mode: none = no curator, summary-review = open curator with auto summary draft (default)" })),
|
|
334
333
|
}),
|
|
335
334
|
execute: (...args) => executeHeavyTool(loadHeavy, "web_search", args),
|
|
336
335
|
renderResult: (...args) => renderHeavyToolResult(loadedHeavy?.heavy ?? null, "web_search", args),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/web-access",
|
|
3
|
-
"version": "0.9.14-alpha.
|
|
3
|
+
"version": "0.9.14-alpha.4",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for web search, URL fetching, GitHub repo cloning, PDF/video extraction. Fork of: https://github.com/nicobailon/pi-web-access",
|
|
6
6
|
"contributors": [
|
|
@@ -33,8 +33,8 @@ export interface ProviderAvailability {
|
|
|
33
33
|
gemini: boolean;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export type WebSearchWorkflow
|
|
37
|
-
export
|
|
36
|
+
export type { CuratorWorkflow, WebSearchWorkflow } from "./web-search-workflow.js";
|
|
37
|
+
export { resolveWorkflow } from "./web-search-workflow.js";
|
|
38
38
|
|
|
39
39
|
export interface CuratorBootstrap {
|
|
40
40
|
availableProviders: ProviderAvailability;
|
|
@@ -99,12 +99,6 @@ function normalizeCuratorTimeoutSeconds(value: unknown): number | undefined {
|
|
|
99
99
|
return Math.min(normalized, MAX_CURATOR_TIMEOUT_SECONDS);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
export function resolveWorkflow(input: unknown, hasUI: boolean): WebSearchWorkflow {
|
|
103
|
-
if (!hasUI) return "none";
|
|
104
|
-
if (typeof input === "string" && input.trim().toLowerCase() === "none") return "none";
|
|
105
|
-
return "summary-review";
|
|
106
|
-
}
|
|
107
|
-
|
|
108
102
|
export function normalizeQueryList(queryList: unknown[]): string[] {
|
|
109
103
|
const normalized: string[] = [];
|
|
110
104
|
for (const query of queryList) {
|
|
@@ -32,7 +32,7 @@ export function registerWebSearchTool(pi: ExtensionAPI, deps: RegisterWebSearchT
|
|
|
32
32
|
name: "web_search",
|
|
33
33
|
label: "Web Search",
|
|
34
34
|
description:
|
|
35
|
-
`Search the web using Perplexity AI, Exa, or Gemini. Returns an AI-synthesized answer with source citations. For comprehensive research, prefer queries (plural) with 2-4 varied angles over a single query — each query gets its own synthesized answer, so varying phrasing and scope gives much broader coverage. When includeContent is true, full page content is fetched in the background. Searches
|
|
35
|
+
`Search the web using Perplexity AI, Exa, or Gemini. Returns an AI-synthesized answer with source citations. For comprehensive research, prefer queries (plural) with 2-4 varied angles over a single query — each query gets its own synthesized answer, so varying phrasing and scope gives much broader coverage. When includeContent is true, full page content is fetched in the background. Searches return raw results by default. Enable the interactive browser curator with /curator on or by setting workflow to "summary-review" in web-search config. Provider auto-selects: Exa (direct API with key, MCP fallback without), else Perplexity (needs key), else Gemini API (needs key), else Gemini Web (needs a supported Chromium-based browser login).`,
|
|
36
36
|
promptSnippet:
|
|
37
37
|
"Use for web research questions. Prefer {queries:[...]} with 2-4 varied angles over a single query for broader coverage.",
|
|
38
38
|
parameters: Type.Object({
|
|
@@ -47,11 +47,6 @@ export function registerWebSearchTool(pi: ExtensionAPI, deps: RegisterWebSearchT
|
|
|
47
47
|
provider: Type.Optional(
|
|
48
48
|
StringEnum(["auto", "perplexity", "gemini", "exa"], { description: "Search provider (default: auto)" }),
|
|
49
49
|
),
|
|
50
|
-
workflow: Type.Optional(
|
|
51
|
-
StringEnum(["none", "summary-review"], {
|
|
52
|
-
description: "Search workflow mode: none = no curator, summary-review = open curator with auto summary draft (default)",
|
|
53
|
-
}),
|
|
54
|
-
),
|
|
55
50
|
}),
|
|
56
51
|
|
|
57
52
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
@@ -60,7 +55,7 @@ export function registerWebSearchTool(pi: ExtensionAPI, deps: RegisterWebSearchT
|
|
|
60
55
|
: (params.query !== undefined ? [params.query] : []);
|
|
61
56
|
const queryList = normalizeQueryList(rawQueryList);
|
|
62
57
|
const configWorkflow = loadConfigForExtensionInit().workflow;
|
|
63
|
-
const workflow = resolveWorkflow(
|
|
58
|
+
const workflow = resolveWorkflow(configWorkflow, ctx?.hasUI !== false);
|
|
64
59
|
const shouldCurate = workflow !== "none";
|
|
65
60
|
|
|
66
61
|
if (queryList.length === 0) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type WebSearchWorkflow = "none" | "summary-review";
|
|
2
|
+
export type CuratorWorkflow = "summary-review";
|
|
3
|
+
|
|
4
|
+
export function resolveWorkflow(input: unknown, hasUI: boolean): WebSearchWorkflow {
|
|
5
|
+
if (!hasUI) return "none";
|
|
6
|
+
if (typeof input === "string" && input.trim().toLowerCase() === "summary-review") {
|
|
7
|
+
return "summary-review";
|
|
8
|
+
}
|
|
9
|
+
return "none";
|
|
10
|
+
}
|
|
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.14-alpha.4] - 2026-08-18
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Opened `ctx.tool` detail now matches the main-chat tool block: `Box(1,1)` inner padding, a blank row under the `$` header, full-width alignment with the orchestrator bars, unpainted canvas above and below the card, host `toolTitle`/`toolOutput` colors, a dim `ctrl+o Expand` hint, and second-resolution `Took`/`Elapsed` timing.
|
|
14
|
+
|
|
9
15
|
## [0.9.14-alpha.3] - 2026-08-17
|
|
10
16
|
|
|
11
17
|
### Changed
|
|
@@ -38912,7 +38912,8 @@ var MOCHA = {
|
|
|
38912
38912
|
yellow: "#f9e2af",
|
|
38913
38913
|
red: "#f38ba8",
|
|
38914
38914
|
mauve: "#cba6f7",
|
|
38915
|
-
sky: "#89dceb"
|
|
38915
|
+
sky: "#89dceb",
|
|
38916
|
+
lavender: "#b4befe"
|
|
38916
38917
|
};
|
|
38917
38918
|
function deriveGraphTheme(theme = {}) {
|
|
38918
38919
|
return {
|
|
@@ -38929,6 +38930,8 @@ function deriveGraphTheme(theme = {}) {
|
|
|
38929
38930
|
text: theme.text ?? MOCHA.text,
|
|
38930
38931
|
textMuted: theme.textMuted ?? MOCHA.subtext0,
|
|
38931
38932
|
dim: theme.dim ?? MOCHA.overlay1,
|
|
38933
|
+
toolTitle: theme.toolTitle ?? MOCHA.lavender,
|
|
38934
|
+
toolOutput: theme.toolOutput ?? MOCHA.text,
|
|
38932
38935
|
accent: theme.accent ?? MOCHA.blue,
|
|
38933
38936
|
mauve: theme.mauve ?? MOCHA.mauve,
|
|
38934
38937
|
success: theme.success ?? MOCHA.green,
|
|
@@ -39002,6 +39005,8 @@ function deriveGraphThemeFromPiTheme(theme) {
|
|
|
39002
39005
|
text: fgHex(t, "text") ?? fgHex(t, "customMessageText"),
|
|
39003
39006
|
textMuted: fgHex(t, "muted"),
|
|
39004
39007
|
dim: fgHex(t, "dim"),
|
|
39008
|
+
toolTitle: fgHex(t, "toolTitle"),
|
|
39009
|
+
toolOutput: fgHex(t, "toolOutput") ?? fgHex(t, "text") ?? fgHex(t, "customMessageText"),
|
|
39005
39010
|
accent,
|
|
39006
39011
|
success: fgHex(t, "success"),
|
|
39007
39012
|
warning: fgHex(t, "warning"),
|
|
@@ -43801,7 +43806,12 @@ function renderSwitcher(stages, state, opts) {
|
|
|
43801
43806
|
var TOOL_DETAIL_VALUE_LIMIT = TOOL_PAYLOAD_VALUE_LIMIT;
|
|
43802
43807
|
var COLLAPSED_ARGS_LIMIT = 240;
|
|
43803
43808
|
var COLLAPSED_RESULT_LINES = 10;
|
|
43804
|
-
var
|
|
43809
|
+
var BOX_PAD = 1;
|
|
43810
|
+
function boxMetrics(width) {
|
|
43811
|
+
const safeWidth = Math.max(1, Math.floor(width));
|
|
43812
|
+
const inset = safeWidth >= BOX_PAD * 2 + 1 ? BOX_PAD : 0;
|
|
43813
|
+
return { pad: " ".repeat(inset), inner: Math.max(1, safeWidth - inset * 2) };
|
|
43814
|
+
}
|
|
43805
43815
|
function statusColor2(status, theme) {
|
|
43806
43816
|
switch (status) {
|
|
43807
43817
|
case "running":
|
|
@@ -43842,14 +43852,26 @@ function styledStatus(status, theme) {
|
|
|
43842
43852
|
return theme === undefined ? glyph : `${hexToAnsi(statusColor2(status, theme))}${glyph}${RESET}`;
|
|
43843
43853
|
}
|
|
43844
43854
|
function styledTitle(text, theme) {
|
|
43845
|
-
return theme === undefined ? text : `${hexToAnsi(theme.
|
|
43855
|
+
return theme === undefined ? text : `${hexToAnsi(theme.toolTitle)}${BOLD}${text}${RESET}`;
|
|
43856
|
+
}
|
|
43857
|
+
function styledOutput(text, theme) {
|
|
43858
|
+
return theme === undefined ? text : `${hexToAnsi(theme.toolOutput)}${text}${RESET}`;
|
|
43846
43859
|
}
|
|
43847
43860
|
function styledMuted(text, theme) {
|
|
43848
43861
|
return theme === undefined ? text : `${hexToAnsi(theme.textMuted)}${text}${RESET}`;
|
|
43849
43862
|
}
|
|
43863
|
+
function styledDim(text, theme) {
|
|
43864
|
+
return theme === undefined ? text : `${hexToAnsi(theme.dim)}${text}${RESET}`;
|
|
43865
|
+
}
|
|
43850
43866
|
function styledError(text, theme) {
|
|
43851
43867
|
return theme === undefined ? text : `${hexToAnsi(theme.error)}${text}${RESET}`;
|
|
43852
43868
|
}
|
|
43869
|
+
function expandHintLine(earlier, expandKey, pad2, theme) {
|
|
43870
|
+
const lead = `... (${earlier} earlier lines`;
|
|
43871
|
+
if (expandKey.length === 0)
|
|
43872
|
+
return `${pad2}${styledMuted(`${lead})`, theme)}`;
|
|
43873
|
+
return `${pad2}${styledMuted(`${lead}, `, theme)}${styledDim(expandKey, theme)}${styledMuted(" Expand)", theme)}`;
|
|
43874
|
+
}
|
|
43853
43875
|
function toolBackground(status, theme) {
|
|
43854
43876
|
switch (status) {
|
|
43855
43877
|
case "completed":
|
|
@@ -43993,70 +44015,70 @@ function paintRow(content, width, theme, status) {
|
|
|
43993
44015
|
return `${background}${repainted}${background}${" ".repeat(padding)}${RESET}`;
|
|
43994
44016
|
}
|
|
43995
44017
|
function messageHeaderRows(tool, width, theme, expanded) {
|
|
43996
|
-
const
|
|
44018
|
+
const { pad: pad2, inner } = boxMetrics(width);
|
|
43997
44019
|
const name = sanitizeToolTitleText(tool.name);
|
|
43998
44020
|
const glyph = headerGlyph(tool.status);
|
|
43999
44021
|
const glyphSuffix = glyph === undefined ? "" : ` ${glyph}`;
|
|
44000
44022
|
const args = tool.args === undefined ? "" : boundedSerializedValue(tool.args, expanded ? TOOL_DETAIL_VALUE_LIMIT : COLLAPSED_ARGS_LIMIT);
|
|
44001
44023
|
const withArgs = `$ ${name}${args ? ` ${args}` : ""}${glyphSuffix}`;
|
|
44002
|
-
const plain = !expanded && visibleWidth(withArgs) >
|
|
44024
|
+
const plain = !expanded && visibleWidth(withArgs) > inner ? `$ ${name}${glyphSuffix}` : withArgs;
|
|
44003
44025
|
const titleEnd = `$ ${name}`;
|
|
44004
|
-
|
|
44005
|
-
const continuationWidth = Math.max(1, safeWidth - visibleWidth(continuationIndent));
|
|
44006
|
-
return wrapFieldValue(plain, safeWidth, continuationWidth).map((chunk, index, chunks) => {
|
|
44007
|
-
const prefix = index === 0 ? "" : continuationIndent;
|
|
44026
|
+
return wrapFieldValue(plain, inner, inner).map((chunk, index, chunks) => {
|
|
44008
44027
|
if (index !== 0 || !chunk.startsWith(titleEnd))
|
|
44009
|
-
return `${
|
|
44028
|
+
return `${pad2}${styledMuted(chunk, theme)}`;
|
|
44010
44029
|
const remainder = chunk.slice(titleEnd.length);
|
|
44011
44030
|
const hasStatus = glyphSuffix.length > 0 && index === chunks.length - 1 && remainder.endsWith(glyphSuffix);
|
|
44012
44031
|
const summary = hasStatus ? remainder.slice(0, -glyphSuffix.length) : remainder;
|
|
44013
44032
|
const suffix = hasStatus ? ` ${styledStatus(tool.status, theme)}` : "";
|
|
44014
|
-
|
|
44033
|
+
const title = styledTitle(titleEnd, theme);
|
|
44034
|
+
const rest = summary.trim().length > 0 ? styledMuted(summary, theme) : "";
|
|
44035
|
+
return `${pad2}${title}${rest}${suffix}`;
|
|
44015
44036
|
});
|
|
44016
44037
|
}
|
|
44017
44038
|
function bodyRows(text, error, width, theme) {
|
|
44018
|
-
const
|
|
44019
|
-
return wrapFieldValue(text,
|
|
44020
|
-
const styled = error ? styledError(line, theme) :
|
|
44021
|
-
return `${
|
|
44039
|
+
const { pad: pad2, inner } = boxMetrics(width);
|
|
44040
|
+
return wrapFieldValue(text, inner).map((line) => {
|
|
44041
|
+
const styled = error ? styledError(line, theme) : styledOutput(line, theme);
|
|
44042
|
+
return `${pad2}${styled}`;
|
|
44022
44043
|
});
|
|
44023
44044
|
}
|
|
44024
44045
|
function footerText(tool, elapsed) {
|
|
44025
44046
|
if (elapsed === undefined)
|
|
44026
44047
|
return;
|
|
44027
44048
|
const label = tool.status === "running" ? "Elapsed" : "Took";
|
|
44028
|
-
const duration = elapsed < 1000 ? `${Math.round(elapsed)}ms` : fmtDuration(elapsed);
|
|
44029
44049
|
const markers = [
|
|
44030
44050
|
tool.status === "cached" ? "cached" : undefined,
|
|
44031
44051
|
tool.replayed === true ? "replayed" : undefined
|
|
44032
44052
|
].filter((marker) => marker !== undefined);
|
|
44033
|
-
return `${label} ${
|
|
44053
|
+
return `${label} ${fmtDuration(elapsed)}${markers.length > 0 ? ` · ${markers.join(" · ")}` : ""}`;
|
|
44034
44054
|
}
|
|
44035
44055
|
function renderMessageLines(tool, width, theme, expanded, expandKey, now) {
|
|
44036
44056
|
const safeWidth = Math.max(1, Math.floor(width));
|
|
44057
|
+
const { pad: pad2, inner } = boxMetrics(safeWidth);
|
|
44037
44058
|
const result = resultText(tool, TOOL_DETAIL_VALUE_LIMIT);
|
|
44038
44059
|
const rows = messageHeaderRows(tool, safeWidth, theme, expanded);
|
|
44060
|
+
rows.push("");
|
|
44039
44061
|
const wrappedBody = bodyRows(result.text, result.error, safeWidth, theme);
|
|
44040
44062
|
if (expanded) {
|
|
44041
44063
|
rows.push(...wrappedBody);
|
|
44042
44064
|
if (tool.source !== undefined && tool.source.length > 0) {
|
|
44043
|
-
|
|
44044
|
-
rows.push(...wrapFieldValue(boundedToolText(sanitizeToolDisplayText(tool.source)), sourceWidth).map((line) => `${BODY_INDENT}${styledMuted(line, theme)}`));
|
|
44065
|
+
rows.push(...wrapFieldValue(boundedToolText(sanitizeToolDisplayText(tool.source)), inner).map((line) => `${pad2}${styledMuted(line, theme)}`));
|
|
44045
44066
|
}
|
|
44046
44067
|
} else {
|
|
44047
44068
|
const earlier = Math.max(0, wrappedBody.length - COLLAPSED_RESULT_LINES);
|
|
44048
44069
|
if (earlier > 0) {
|
|
44049
|
-
|
|
44050
|
-
rows.push(` ${styledMuted(hint, theme)}`);
|
|
44070
|
+
rows.push(expandHintLine(earlier, expandKey, pad2, theme));
|
|
44051
44071
|
}
|
|
44052
44072
|
rows.push(...wrappedBody.slice(-COLLAPSED_RESULT_LINES));
|
|
44053
44073
|
}
|
|
44054
44074
|
const footer = footerText(tool, durationMs(tool, now));
|
|
44055
44075
|
if (footer !== undefined) {
|
|
44056
44076
|
rows.push("");
|
|
44057
|
-
rows.push(styledMuted(footer, theme));
|
|
44077
|
+
rows.push(`${pad2}${styledMuted(footer, theme)}`);
|
|
44058
44078
|
}
|
|
44059
|
-
|
|
44079
|
+
const painted = rows.map((row) => paintRow(row, safeWidth, theme, tool.status));
|
|
44080
|
+
const padRow = paintRow("", safeWidth, theme, tool.status);
|
|
44081
|
+
return [padRow, ...painted, padRow];
|
|
44060
44082
|
}
|
|
44061
44083
|
function renderToolDetail(tool, opts = {}) {
|
|
44062
44084
|
const width = Math.max(1, Math.floor(opts.width ?? 80));
|
|
@@ -44078,6 +44100,10 @@ function stripLayoutWrapper(line) {
|
|
|
44078
44100
|
const end = endsWithWrapper ? line.length - LAYOUT_OSC_RESET.length : line.length;
|
|
44079
44101
|
return line.slice(start, Math.max(start, end));
|
|
44080
44102
|
}
|
|
44103
|
+
var TOOL_DETAIL_VIEWPORT_PAD = 1;
|
|
44104
|
+
function toolDetailViewportPad(rows) {
|
|
44105
|
+
return rows >= TOOL_DETAIL_VIEWPORT_PAD * 2 + 1 ? TOOL_DETAIL_VIEWPORT_PAD : 0;
|
|
44106
|
+
}
|
|
44081
44107
|
|
|
44082
44108
|
class GraphViewRenderer extends GraphViewGraphRenderer {
|
|
44083
44109
|
graphLayout;
|
|
@@ -44169,14 +44195,13 @@ class GraphViewRenderer extends GraphViewGraphRenderer {
|
|
|
44169
44195
|
const cached = this.detailFrame;
|
|
44170
44196
|
if (cached !== null && cached.width === safeWidth)
|
|
44171
44197
|
return cached;
|
|
44172
|
-
const blockWidth = Math.max(1, safeWidth - 1);
|
|
44173
44198
|
const frame = {
|
|
44174
44199
|
width: safeWidth,
|
|
44175
|
-
blockWidth,
|
|
44176
|
-
leftPad:
|
|
44200
|
+
blockWidth: safeWidth,
|
|
44201
|
+
leftPad: 0,
|
|
44177
44202
|
lines: renderToolDetailLines(node, {
|
|
44178
44203
|
theme: this.graphTheme,
|
|
44179
|
-
width:
|
|
44204
|
+
width: safeWidth,
|
|
44180
44205
|
expanded: this.toolDetailExpanded,
|
|
44181
44206
|
expandKey: toolExpandKey(isKeybindingsLike(this.piKeybindings) ? this.piKeybindings : undefined)
|
|
44182
44207
|
})
|
|
@@ -44187,7 +44212,8 @@ class GraphViewRenderer extends GraphViewGraphRenderer {
|
|
|
44187
44212
|
_graphBodyContentHeight(width, viewportRows) {
|
|
44188
44213
|
const rows = Math.max(0, Math.floor(viewportRows));
|
|
44189
44214
|
if (this.toolDetail !== null) {
|
|
44190
|
-
|
|
44215
|
+
const pad2 = toolDetailViewportPad(rows);
|
|
44216
|
+
return Math.max(rows, this._toolDetailFrame(this.toolDetail, width).lines.length + pad2 * 2);
|
|
44191
44217
|
}
|
|
44192
44218
|
if (!this._currentRenderRun() || this.cachedRenderGeometry.totalRows <= 0)
|
|
44193
44219
|
return rows;
|
|
@@ -44245,7 +44271,10 @@ class GraphViewRenderer extends GraphViewGraphRenderer {
|
|
|
44245
44271
|
_renderBody(width, top, rows, _contentRows) {
|
|
44246
44272
|
if (this.toolDetail !== null) {
|
|
44247
44273
|
const detail = this._toolDetailFrame(this.toolDetail, width);
|
|
44248
|
-
|
|
44274
|
+
const pad2 = toolDetailViewportPad(rows);
|
|
44275
|
+
const inner = Math.max(0, rows - pad2 * 2);
|
|
44276
|
+
const painted = detail.lines.slice(top, top + inner).map((line) => this._canvasRow(`${" ".repeat(detail.leftPad)}${line}`, width));
|
|
44277
|
+
return [...Array.from({ length: pad2 }, () => ""), ...painted, ...Array.from({ length: pad2 }, () => "")];
|
|
44249
44278
|
}
|
|
44250
44279
|
const run = this._currentRenderRun();
|
|
44251
44280
|
const raw = run ? this._renderGraph(width, top, rows, run) : this._renderEmptyBody(width, rows);
|
|
@@ -31,6 +31,7 @@ const MOCHA = {
|
|
|
31
31
|
red: "#f38ba8",
|
|
32
32
|
mauve: "#cba6f7",
|
|
33
33
|
sky: "#89dceb",
|
|
34
|
+
lavender: "#b4befe",
|
|
34
35
|
} as const;
|
|
35
36
|
|
|
36
37
|
/**
|
|
@@ -51,6 +52,8 @@ export interface GenericTheme {
|
|
|
51
52
|
text?: string;
|
|
52
53
|
textMuted?: string;
|
|
53
54
|
dim?: string;
|
|
55
|
+
toolTitle?: string;
|
|
56
|
+
toolOutput?: string;
|
|
54
57
|
accent?: string;
|
|
55
58
|
mauve?: string;
|
|
56
59
|
success?: string;
|
|
@@ -87,6 +90,10 @@ export interface GraphTheme {
|
|
|
87
90
|
text: string;
|
|
88
91
|
textMuted: string;
|
|
89
92
|
dim: string;
|
|
93
|
+
/** Host tool-card title (`toolTitle`, Catppuccin lavender). */
|
|
94
|
+
toolTitle: string;
|
|
95
|
+
/** Host tool-card body (`toolOutput`, usually the same as `text`). */
|
|
96
|
+
toolOutput: string;
|
|
90
97
|
|
|
91
98
|
/** Accents */
|
|
92
99
|
accent: string;
|
|
@@ -116,6 +123,8 @@ export function deriveGraphTheme(theme: GenericTheme = {}): GraphTheme {
|
|
|
116
123
|
text: theme.text ?? MOCHA.text,
|
|
117
124
|
textMuted: theme.textMuted ?? MOCHA.subtext0,
|
|
118
125
|
dim: theme.dim ?? MOCHA.overlay1,
|
|
126
|
+
toolTitle: theme.toolTitle ?? MOCHA.lavender,
|
|
127
|
+
toolOutput: theme.toolOutput ?? MOCHA.text,
|
|
119
128
|
|
|
120
129
|
accent: theme.accent ?? MOCHA.blue,
|
|
121
130
|
mauve: theme.mauve ?? MOCHA.mauve,
|
|
@@ -262,6 +271,8 @@ export function deriveGraphThemeFromPiTheme(theme: unknown): GraphTheme {
|
|
|
262
271
|
text: fgHex(t, "text") ?? fgHex(t, "customMessageText"),
|
|
263
272
|
textMuted: fgHex(t, "muted"),
|
|
264
273
|
dim: fgHex(t, "dim"),
|
|
274
|
+
toolTitle: fgHex(t, "toolTitle"),
|
|
275
|
+
toolOutput: fgHex(t, "toolOutput") ?? fgHex(t, "text") ?? fgHex(t, "customMessageText"),
|
|
265
276
|
|
|
266
277
|
accent,
|
|
267
278
|
success: fgHex(t, "success"),
|
|
@@ -26,6 +26,13 @@ function stripLayoutWrapper(line: string): string {
|
|
|
26
26
|
const end = endsWithWrapper ? line.length - LAYOUT_OSC_RESET.length : line.length;
|
|
27
27
|
return line.slice(start, Math.max(start, end));
|
|
28
28
|
}
|
|
29
|
+
/** Unpainted canvas between the orchestrator bars and the painted card. */
|
|
30
|
+
const TOOL_DETAIL_VIEWPORT_PAD = 1;
|
|
31
|
+
|
|
32
|
+
function toolDetailViewportPad(rows: number): number {
|
|
33
|
+
return rows >= TOOL_DETAIL_VIEWPORT_PAD * 2 + 1 ? TOOL_DETAIL_VIEWPORT_PAD : 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
29
36
|
/** One frame's tool-message geometry, derived once and shared by both callbacks. */
|
|
30
37
|
interface ToolDetailFrame {
|
|
31
38
|
readonly width: number;
|
|
@@ -159,17 +166,15 @@ export abstract class GraphViewRenderer extends GraphViewGraphRenderer {
|
|
|
159
166
|
const safeWidth = Math.max(1, Math.floor(width));
|
|
160
167
|
const cached = this.detailFrame;
|
|
161
168
|
if (cached !== null && cached.width === safeWidth) return cached;
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
// standalone rounded panel or hiding content behind a border.
|
|
165
|
-
const blockWidth = Math.max(1, safeWidth - 1);
|
|
169
|
+
// Span the same columns as the header/footer bars. Main-chat tool
|
|
170
|
+
// blocks are full width; only the Box(1,1) pad insets the `$` text.
|
|
166
171
|
const frame: ToolDetailFrame = {
|
|
167
172
|
width: safeWidth,
|
|
168
|
-
blockWidth,
|
|
169
|
-
leftPad:
|
|
173
|
+
blockWidth: safeWidth,
|
|
174
|
+
leftPad: 0,
|
|
170
175
|
lines: renderToolDetailLines(node, {
|
|
171
176
|
theme: this.graphTheme,
|
|
172
|
-
width:
|
|
177
|
+
width: safeWidth,
|
|
173
178
|
expanded: this.toolDetailExpanded,
|
|
174
179
|
expandKey: toolExpandKey(isKeybindingsLike(this.piKeybindings) ? this.piKeybindings : undefined),
|
|
175
180
|
}),
|
|
@@ -181,7 +186,8 @@ export abstract class GraphViewRenderer extends GraphViewGraphRenderer {
|
|
|
181
186
|
private _graphBodyContentHeight(width: number, viewportRows: number): number {
|
|
182
187
|
const rows = Math.max(0, Math.floor(viewportRows));
|
|
183
188
|
if (this.toolDetail !== null) {
|
|
184
|
-
|
|
189
|
+
const pad = toolDetailViewportPad(rows);
|
|
190
|
+
return Math.max(rows, this._toolDetailFrame(this.toolDetail, width).lines.length + pad * 2);
|
|
185
191
|
}
|
|
186
192
|
if (!this._currentRenderRun() || this.cachedRenderGeometry.totalRows <= 0) return rows;
|
|
187
193
|
return Math.max(rows, this.cachedRenderGeometry.totalRows);
|
|
@@ -249,9 +255,12 @@ export abstract class GraphViewRenderer extends GraphViewGraphRenderer {
|
|
|
249
255
|
private _renderBody(width: number, top: number, rows: number, _contentRows: number): string[] {
|
|
250
256
|
if (this.toolDetail !== null) {
|
|
251
257
|
const detail = this._toolDetailFrame(this.toolDetail, width);
|
|
252
|
-
|
|
253
|
-
|
|
258
|
+
const pad = toolDetailViewportPad(rows);
|
|
259
|
+
const inner = Math.max(0, rows - pad * 2);
|
|
260
|
+
const painted = detail.lines
|
|
261
|
+
.slice(top, top + inner)
|
|
254
262
|
.map((line) => this._canvasRow(`${" ".repeat(detail.leftPad)}${line}`, width));
|
|
263
|
+
return [...Array.from({ length: pad }, () => ""), ...painted, ...Array.from({ length: pad }, () => "")];
|
|
255
264
|
}
|
|
256
265
|
const run = this._currentRenderRun();
|
|
257
266
|
const raw = run ? this._renderGraph(width, top, rows, run) : this._renderEmptyBody(width, rows);
|
|
@@ -19,7 +19,14 @@ export const TOOL_DETAIL_VALUE_LIMIT = TOOL_PAYLOAD_VALUE_LIMIT;
|
|
|
19
19
|
const COLLAPSED_ARGS_LIMIT = 240;
|
|
20
20
|
/** Match the host tool renderer's collapsed fallback preview row bound. */
|
|
21
21
|
const COLLAPSED_RESULT_LINES = 10;
|
|
22
|
-
|
|
22
|
+
/** Match pi-tui `Box(paddingX=1, paddingY=1)` around the host tool card. */
|
|
23
|
+
const BOX_PAD = 1;
|
|
24
|
+
|
|
25
|
+
function boxMetrics(width: number): { pad: string; inner: number } {
|
|
26
|
+
const safeWidth = Math.max(1, Math.floor(width));
|
|
27
|
+
const inset = safeWidth >= BOX_PAD * 2 + 1 ? BOX_PAD : 0;
|
|
28
|
+
return { pad: " ".repeat(inset), inner: Math.max(1, safeWidth - inset * 2) };
|
|
29
|
+
}
|
|
23
30
|
|
|
24
31
|
export interface RenderToolDetailOpts {
|
|
25
32
|
/** Provide for ANSI output; omit for plain text. */
|
|
@@ -79,17 +86,31 @@ function styledStatus(status: ToolNodeStatus, theme: GraphTheme | undefined): st
|
|
|
79
86
|
}
|
|
80
87
|
|
|
81
88
|
function styledTitle(text: string, theme: GraphTheme | undefined): string {
|
|
82
|
-
return theme === undefined ? text : `${hexToAnsi(theme.
|
|
89
|
+
return theme === undefined ? text : `${hexToAnsi(theme.toolTitle)}${BOLD}${text}${RESET}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function styledOutput(text: string, theme: GraphTheme | undefined): string {
|
|
93
|
+
return theme === undefined ? text : `${hexToAnsi(theme.toolOutput)}${text}${RESET}`;
|
|
83
94
|
}
|
|
84
95
|
|
|
85
96
|
function styledMuted(text: string, theme: GraphTheme | undefined): string {
|
|
86
97
|
return theme === undefined ? text : `${hexToAnsi(theme.textMuted)}${text}${RESET}`;
|
|
87
98
|
}
|
|
88
99
|
|
|
100
|
+
function styledDim(text: string, theme: GraphTheme | undefined): string {
|
|
101
|
+
return theme === undefined ? text : `${hexToAnsi(theme.dim)}${text}${RESET}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
89
104
|
function styledError(text: string, theme: GraphTheme | undefined): string {
|
|
90
105
|
return theme === undefined ? text : `${hexToAnsi(theme.error)}${text}${RESET}`;
|
|
91
106
|
}
|
|
92
107
|
|
|
108
|
+
function expandHintLine(earlier: number, expandKey: string, pad: string, theme: GraphTheme | undefined): string {
|
|
109
|
+
const lead = `... (${earlier} earlier lines`;
|
|
110
|
+
if (expandKey.length === 0) return `${pad}${styledMuted(`${lead})`, theme)}`;
|
|
111
|
+
return `${pad}${styledMuted(`${lead}, `, theme)}${styledDim(expandKey, theme)}${styledMuted(" Expand)", theme)}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
93
114
|
function toolBackground(status: ToolNodeStatus, theme: GraphTheme): string {
|
|
94
115
|
switch (status) {
|
|
95
116
|
case "completed":
|
|
@@ -249,7 +270,7 @@ function messageHeaderRows(
|
|
|
249
270
|
theme: GraphTheme | undefined,
|
|
250
271
|
expanded: boolean,
|
|
251
272
|
): string[] {
|
|
252
|
-
const
|
|
273
|
+
const { pad, inner } = boxMetrics(width);
|
|
253
274
|
const name = sanitizeToolTitleText(tool.name);
|
|
254
275
|
const glyph = headerGlyph(tool.status);
|
|
255
276
|
const glyphSuffix = glyph === undefined ? "" : ` ${glyph}`;
|
|
@@ -258,38 +279,36 @@ function messageHeaderRows(
|
|
|
258
279
|
? ""
|
|
259
280
|
: boundedSerializedValue(tool.args, expanded ? TOOL_DETAIL_VALUE_LIMIT : COLLAPSED_ARGS_LIMIT);
|
|
260
281
|
const withArgs = `$ ${name}${args ? ` ${args}` : ""}${glyphSuffix}`;
|
|
261
|
-
const plain = !expanded && visibleWidth(withArgs) >
|
|
282
|
+
const plain = !expanded && visibleWidth(withArgs) > inner ? `$ ${name}${glyphSuffix}` : withArgs;
|
|
262
283
|
const titleEnd = `$ ${name}`;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
return wrapFieldValue(plain, safeWidth, continuationWidth).map((chunk, index, chunks) => {
|
|
266
|
-
const prefix = index === 0 ? "" : continuationIndent;
|
|
267
|
-
if (index !== 0 || !chunk.startsWith(titleEnd)) return `${prefix}${styledMuted(chunk, theme)}`;
|
|
284
|
+
return wrapFieldValue(plain, inner, inner).map((chunk, index, chunks) => {
|
|
285
|
+
if (index !== 0 || !chunk.startsWith(titleEnd)) return `${pad}${styledMuted(chunk, theme)}`;
|
|
268
286
|
const remainder = chunk.slice(titleEnd.length);
|
|
269
287
|
const hasStatus = glyphSuffix.length > 0 && index === chunks.length - 1 && remainder.endsWith(glyphSuffix);
|
|
270
288
|
const summary = hasStatus ? remainder.slice(0, -glyphSuffix.length) : remainder;
|
|
271
289
|
const suffix = hasStatus ? ` ${styledStatus(tool.status, theme)}` : "";
|
|
272
|
-
|
|
290
|
+
const title = styledTitle(titleEnd, theme);
|
|
291
|
+
const rest = summary.trim().length > 0 ? styledMuted(summary, theme) : "";
|
|
292
|
+
return `${pad}${title}${rest}${suffix}`;
|
|
273
293
|
});
|
|
274
294
|
}
|
|
275
295
|
|
|
276
296
|
function bodyRows(text: string, error: boolean, width: number, theme: GraphTheme | undefined): string[] {
|
|
277
|
-
const
|
|
278
|
-
return wrapFieldValue(text,
|
|
279
|
-
const styled = error ? styledError(line, theme) :
|
|
280
|
-
return `${
|
|
297
|
+
const { pad, inner } = boxMetrics(width);
|
|
298
|
+
return wrapFieldValue(text, inner).map((line) => {
|
|
299
|
+
const styled = error ? styledError(line, theme) : styledOutput(line, theme);
|
|
300
|
+
return `${pad}${styled}`;
|
|
281
301
|
});
|
|
282
302
|
}
|
|
283
303
|
|
|
284
304
|
function footerText(tool: ToolNodeSnapshot, elapsed: number | undefined): string | undefined {
|
|
285
305
|
if (elapsed === undefined) return undefined;
|
|
286
306
|
const label = tool.status === "running" ? "Elapsed" : "Took";
|
|
287
|
-
const duration = elapsed < 1000 ? `${Math.round(elapsed)}ms` : fmtDuration(elapsed);
|
|
288
307
|
const markers = [
|
|
289
308
|
tool.status === "cached" ? "cached" : undefined,
|
|
290
309
|
tool.replayed === true ? "replayed" : undefined,
|
|
291
310
|
].filter((marker): marker is string => marker !== undefined);
|
|
292
|
-
return `${label} ${
|
|
311
|
+
return `${label} ${fmtDuration(elapsed)}${markers.length > 0 ? ` · ${markers.join(" · ")}` : ""}`;
|
|
293
312
|
}
|
|
294
313
|
|
|
295
314
|
function renderMessageLines(
|
|
@@ -301,36 +320,36 @@ function renderMessageLines(
|
|
|
301
320
|
now: number,
|
|
302
321
|
): string[] {
|
|
303
322
|
const safeWidth = Math.max(1, Math.floor(width));
|
|
323
|
+
const { pad, inner } = boxMetrics(safeWidth);
|
|
304
324
|
const result = resultText(tool, TOOL_DETAIL_VALUE_LIMIT);
|
|
305
325
|
const rows: string[] = messageHeaderRows(tool, safeWidth, theme, expanded);
|
|
326
|
+
// Host bash/result renderers put a blank row between the `$` call and the body.
|
|
327
|
+
rows.push("");
|
|
306
328
|
const wrappedBody = bodyRows(result.text, result.error, safeWidth, theme);
|
|
307
329
|
if (expanded) {
|
|
308
330
|
rows.push(...wrappedBody);
|
|
309
331
|
if (tool.source !== undefined && tool.source.length > 0) {
|
|
310
|
-
const sourceWidth = Math.max(1, safeWidth - visibleWidth(BODY_INDENT));
|
|
311
332
|
rows.push(
|
|
312
|
-
...wrapFieldValue(boundedToolText(sanitizeToolDisplayText(tool.source)),
|
|
313
|
-
(line) => `${
|
|
333
|
+
...wrapFieldValue(boundedToolText(sanitizeToolDisplayText(tool.source)), inner).map(
|
|
334
|
+
(line) => `${pad}${styledMuted(line, theme)}`,
|
|
314
335
|
),
|
|
315
336
|
);
|
|
316
337
|
}
|
|
317
338
|
} else {
|
|
318
339
|
const earlier = Math.max(0, wrappedBody.length - COLLAPSED_RESULT_LINES);
|
|
319
340
|
if (earlier > 0) {
|
|
320
|
-
|
|
321
|
-
expandKey.length > 0
|
|
322
|
-
? `... (${earlier} earlier lines, ${expandKey} Expand)`
|
|
323
|
-
: `... (${earlier} earlier lines)`;
|
|
324
|
-
rows.push(` ${styledMuted(hint, theme)}`);
|
|
341
|
+
rows.push(expandHintLine(earlier, expandKey, pad, theme));
|
|
325
342
|
}
|
|
326
343
|
rows.push(...wrappedBody.slice(-COLLAPSED_RESULT_LINES));
|
|
327
344
|
}
|
|
328
345
|
const footer = footerText(tool, durationMs(tool, now));
|
|
329
346
|
if (footer !== undefined) {
|
|
330
347
|
rows.push("");
|
|
331
|
-
rows.push(styledMuted(footer, theme));
|
|
348
|
+
rows.push(`${pad}${styledMuted(footer, theme)}`);
|
|
332
349
|
}
|
|
333
|
-
|
|
350
|
+
const painted = rows.map((row) => paintRow(row, safeWidth, theme, tool.status));
|
|
351
|
+
const padRow = paintRow("", safeWidth, theme, tool.status);
|
|
352
|
+
return [padRow, ...painted, padRow];
|
|
334
353
|
}
|
|
335
354
|
|
|
336
355
|
/** Render one read-only host-style operator tool message block. */
|
package/docs/workflows.md
CHANGED
|
@@ -3314,7 +3314,7 @@ Repeated, sibling, sequential, parallel, and multi-level child calls keep indepe
|
|
|
3314
3314
|
|
|
3315
3315
|
### `ctx.tool` — durable cached tool execution
|
|
3316
3316
|
|
|
3317
|
-
The `ctx.tool(name, args, fn, options?)` primitive runs arbitrary TypeScript code as a first-class durable graph node and caches the result durably. The node is non-attachable and has no stage chat controls, and its graph card body is the constant `durable tool` in every state — status, timing, and dependency rows keep their own rows, and the card does not preview the result or error. In the graph viewer, focusing the node and pressing Enter, clicking it, or choosing it from the switcher opens a read-only host-style operator card from the snapshot: a status-tinted shaded rectangle with a `$ <tool-name>` call header, an optional short argument summary, and the result or error as its body. It is collapsed by default and wraps the fully bounded result or error before showing its last visual rows, with `... (N earlier lines, ctrl+o Expand)` above the tail when the action is bound; the configured `app.tools.expand` action (`ctrl+o` by default) toggles the full bounded result or error and then a muted callback-source block when source exists. The graph statusline advertises the resolved expand key with `expand` or `collapse` alongside return-to-graph and scroll hints, including remapped keys, and omits that segment entirely when the action is unbound. The footer says `Took` for settled calls or `Elapsed` for running calls, using
|
|
3317
|
+
The `ctx.tool(name, args, fn, options?)` primitive runs arbitrary TypeScript code as a first-class durable graph node and caches the result durably. The node is non-attachable and has no stage chat controls, and its graph card body is the constant `durable tool` in every state — status, timing, and dependency rows keep their own rows, and the card does not preview the result or error. In the graph viewer, focusing the node and pressing Enter, clicking it, or choosing it from the switcher opens a read-only host-style operator card from the snapshot: a status-tinted shaded rectangle with the same inner padding and header/body gap as the main-chat tool block, inset from the orchestrator header and footer bars, a `$ <tool-name>` call header, an optional short argument summary, and the result or error as its body. It is collapsed by default and wraps the fully bounded result or error before showing its last visual rows, with `... (N earlier lines, ctrl+o Expand)` above the tail when the action is bound; the configured `app.tools.expand` action (`ctrl+o` by default) toggles the full bounded result or error and then a muted callback-source block when source exists. The graph statusline advertises the resolved expand key with `expand` or `collapse` alongside return-to-graph and scroll hints, including remapped keys, and omits that segment entirely when the action is unbound. The footer says `Took` for settled calls or `Elapsed` for running calls, using the same second-resolution duration as the main-chat tool block, with cached/replayed markers kept as a quiet suffix. The operator surface has no ARGS/RESULT/SOURCE/TIMING/MARKERS debug table and does not expose raw clock fields. Source capture uses `fn.toString()` at registration without re-executing the callback or reading a file. `↑`/`↓`, `PageUp`/`PageDown`, `Home`/`End`, the wheel, and the scrollbar all scroll the block, so a long payload stays readable on a keyboard-only session or a terminal without mouse reporting; Escape or `ctrl+x` returns to the graph. The message block is read-only and never offers chat attachment, steering, interrupt, or resume. Bounded payloads remain width-safe and mark truncation explicitly with `… [truncated]`; source tabs expand and control bytes become `\xNN`, while cyclic payloads, throwing `toJSON`, or throwing property getters render `<cycle>`, `<unserializable>`, or `<unreadable>` instead of crashing the view. The same cap applies to what the live run snapshot retains for a tool node, while durable checkpoints keep the exact output, raw-args `argsHash`, and replay behavior unchanged.
|
|
3318
3318
|
|
|
3319
3319
|
When the workflow body fulfills but one or more admitted tool calls failed, Atomic promotes the first observed failure to the terminal run failure, regardless of admission order, and persists that selected tool-node identity for status inspection and lifecycle output. A direct uncaught `await ctx.tool(...)` rejection keeps the original error and persists its failed-node link through session and durable restore. First-event arbitration also preserves the selected node when concurrent failures throw the same object or primitive; unrelated later stage or body errors do not inherit a caught tool's origin. Tool admission remains open while author code can catch a failure and continue. Once the body settles and failure has won before any real cancellation, Atomic closes admission, cancels remaining non-failed tool nodes, waits for observed failed nodes to finish publication, and publishes the failed root without waiting for callbacks that ignore cancellation.
|
|
3320
3320
|
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/atomic",
|
|
3
|
-
"version": "0.9.14-alpha.
|
|
3
|
+
"version": "0.9.14-alpha.4",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bastani/atomic",
|
|
9
|
-
"version": "0.9.14-alpha.
|
|
9
|
+
"version": "0.9.14-alpha.4",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@bastani/atomic-natives": "0.9.14-alpha.
|
|
12
|
+
"@bastani/atomic-natives": "0.9.14-alpha.4",
|
|
13
13
|
"@dbos-inc/dbos-sdk": "4.25.14",
|
|
14
14
|
"@earendil-works/pi-agent-core": "^0.84.2",
|
|
15
15
|
"@earendil-works/pi-ai": "^0.84.2",
|
|
@@ -517,18 +517,18 @@
|
|
|
517
517
|
}
|
|
518
518
|
},
|
|
519
519
|
"node_modules/@bastani/atomic-natives": {
|
|
520
|
-
"version": "0.9.14-alpha.
|
|
521
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives/-/atomic-natives-0.9.14-alpha.
|
|
520
|
+
"version": "0.9.14-alpha.4",
|
|
521
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives/-/atomic-natives-0.9.14-alpha.4.tgz",
|
|
522
522
|
"license": "MIT",
|
|
523
523
|
"optionalDependencies": {
|
|
524
|
-
"@bastani/atomic-natives-darwin-arm64": "0.9.14-alpha.
|
|
525
|
-
"@bastani/atomic-natives-darwin-x64": "0.9.14-alpha.
|
|
526
|
-
"@bastani/atomic-natives-linux-arm64-gnu": "0.9.14-alpha.
|
|
527
|
-
"@bastani/atomic-natives-linux-arm64-musl": "0.9.14-alpha.
|
|
528
|
-
"@bastani/atomic-natives-linux-x64-gnu": "0.9.14-alpha.
|
|
529
|
-
"@bastani/atomic-natives-linux-x64-musl": "0.9.14-alpha.
|
|
530
|
-
"@bastani/atomic-natives-win32-arm64-msvc": "0.9.14-alpha.
|
|
531
|
-
"@bastani/atomic-natives-win32-x64-msvc": "0.9.14-alpha.
|
|
524
|
+
"@bastani/atomic-natives-darwin-arm64": "0.9.14-alpha.4",
|
|
525
|
+
"@bastani/atomic-natives-darwin-x64": "0.9.14-alpha.4",
|
|
526
|
+
"@bastani/atomic-natives-linux-arm64-gnu": "0.9.14-alpha.4",
|
|
527
|
+
"@bastani/atomic-natives-linux-arm64-musl": "0.9.14-alpha.4",
|
|
528
|
+
"@bastani/atomic-natives-linux-x64-gnu": "0.9.14-alpha.4",
|
|
529
|
+
"@bastani/atomic-natives-linux-x64-musl": "0.9.14-alpha.4",
|
|
530
|
+
"@bastani/atomic-natives-win32-arm64-msvc": "0.9.14-alpha.4",
|
|
531
|
+
"@bastani/atomic-natives-win32-x64-msvc": "0.9.14-alpha.4"
|
|
532
532
|
},
|
|
533
533
|
"engines": {
|
|
534
534
|
"bun": ">=1.3.14",
|
|
@@ -536,8 +536,8 @@
|
|
|
536
536
|
}
|
|
537
537
|
},
|
|
538
538
|
"node_modules/@bastani/atomic-natives-darwin-arm64": {
|
|
539
|
-
"version": "0.9.14-alpha.
|
|
540
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-arm64/-/atomic-natives-darwin-arm64-0.9.14-alpha.
|
|
539
|
+
"version": "0.9.14-alpha.4",
|
|
540
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-arm64/-/atomic-natives-darwin-arm64-0.9.14-alpha.4.tgz",
|
|
541
541
|
"license": "MIT",
|
|
542
542
|
"os": [
|
|
543
543
|
"darwin"
|
|
@@ -548,8 +548,8 @@
|
|
|
548
548
|
"optional": true
|
|
549
549
|
},
|
|
550
550
|
"node_modules/@bastani/atomic-natives-darwin-x64": {
|
|
551
|
-
"version": "0.9.14-alpha.
|
|
552
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-x64/-/atomic-natives-darwin-x64-0.9.14-alpha.
|
|
551
|
+
"version": "0.9.14-alpha.4",
|
|
552
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-x64/-/atomic-natives-darwin-x64-0.9.14-alpha.4.tgz",
|
|
553
553
|
"license": "MIT",
|
|
554
554
|
"os": [
|
|
555
555
|
"darwin"
|
|
@@ -560,8 +560,8 @@
|
|
|
560
560
|
"optional": true
|
|
561
561
|
},
|
|
562
562
|
"node_modules/@bastani/atomic-natives-linux-arm64-gnu": {
|
|
563
|
-
"version": "0.9.14-alpha.
|
|
564
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-gnu/-/atomic-natives-linux-arm64-gnu-0.9.14-alpha.
|
|
563
|
+
"version": "0.9.14-alpha.4",
|
|
564
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-gnu/-/atomic-natives-linux-arm64-gnu-0.9.14-alpha.4.tgz",
|
|
565
565
|
"license": "MIT",
|
|
566
566
|
"os": [
|
|
567
567
|
"linux"
|
|
@@ -575,8 +575,8 @@
|
|
|
575
575
|
"optional": true
|
|
576
576
|
},
|
|
577
577
|
"node_modules/@bastani/atomic-natives-linux-arm64-musl": {
|
|
578
|
-
"version": "0.9.14-alpha.
|
|
579
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-musl/-/atomic-natives-linux-arm64-musl-0.9.14-alpha.
|
|
578
|
+
"version": "0.9.14-alpha.4",
|
|
579
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-musl/-/atomic-natives-linux-arm64-musl-0.9.14-alpha.4.tgz",
|
|
580
580
|
"license": "MIT",
|
|
581
581
|
"os": [
|
|
582
582
|
"linux"
|
|
@@ -590,8 +590,8 @@
|
|
|
590
590
|
"optional": true
|
|
591
591
|
},
|
|
592
592
|
"node_modules/@bastani/atomic-natives-linux-x64-gnu": {
|
|
593
|
-
"version": "0.9.14-alpha.
|
|
594
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-gnu/-/atomic-natives-linux-x64-gnu-0.9.14-alpha.
|
|
593
|
+
"version": "0.9.14-alpha.4",
|
|
594
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-gnu/-/atomic-natives-linux-x64-gnu-0.9.14-alpha.4.tgz",
|
|
595
595
|
"license": "MIT",
|
|
596
596
|
"os": [
|
|
597
597
|
"linux"
|
|
@@ -605,8 +605,8 @@
|
|
|
605
605
|
"optional": true
|
|
606
606
|
},
|
|
607
607
|
"node_modules/@bastani/atomic-natives-linux-x64-musl": {
|
|
608
|
-
"version": "0.9.14-alpha.
|
|
609
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-musl/-/atomic-natives-linux-x64-musl-0.9.14-alpha.
|
|
608
|
+
"version": "0.9.14-alpha.4",
|
|
609
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-musl/-/atomic-natives-linux-x64-musl-0.9.14-alpha.4.tgz",
|
|
610
610
|
"license": "MIT",
|
|
611
611
|
"os": [
|
|
612
612
|
"linux"
|
|
@@ -620,8 +620,8 @@
|
|
|
620
620
|
"optional": true
|
|
621
621
|
},
|
|
622
622
|
"node_modules/@bastani/atomic-natives-win32-arm64-msvc": {
|
|
623
|
-
"version": "0.9.14-alpha.
|
|
624
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-arm64-msvc/-/atomic-natives-win32-arm64-msvc-0.9.14-alpha.
|
|
623
|
+
"version": "0.9.14-alpha.4",
|
|
624
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-arm64-msvc/-/atomic-natives-win32-arm64-msvc-0.9.14-alpha.4.tgz",
|
|
625
625
|
"license": "MIT",
|
|
626
626
|
"os": [
|
|
627
627
|
"win32"
|
|
@@ -632,8 +632,8 @@
|
|
|
632
632
|
"optional": true
|
|
633
633
|
},
|
|
634
634
|
"node_modules/@bastani/atomic-natives-win32-x64-msvc": {
|
|
635
|
-
"version": "0.9.14-alpha.
|
|
636
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-x64-msvc/-/atomic-natives-win32-x64-msvc-0.9.14-alpha.
|
|
635
|
+
"version": "0.9.14-alpha.4",
|
|
636
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-x64-msvc/-/atomic-natives-win32-x64-msvc-0.9.14-alpha.4.tgz",
|
|
637
637
|
"license": "MIT",
|
|
638
638
|
"os": [
|
|
639
639
|
"win32"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/atomic",
|
|
3
|
-
"version": "0.9.14-alpha.
|
|
3
|
+
"version": "0.9.14-alpha.4",
|
|
4
4
|
"description": "Atomic coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"atomicConfig": {
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"prepublishOnly": "bun run clean && bun run build && bun run shrinkwrap"
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
|
-
"@bastani/atomic-natives": "0.9.14-alpha.
|
|
86
|
+
"@bastani/atomic-natives": "0.9.14-alpha.4",
|
|
87
87
|
"@dbos-inc/dbos-sdk": "4.25.14",
|
|
88
88
|
"@earendil-works/pi-agent-core": "^0.84.2",
|
|
89
89
|
"@earendil-works/pi-ai": "^0.84.2",
|