@esso0428/pi-subagents 0.15.6 → 0.15.7
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 +9 -0
- package/dist/agent-history-list.d.ts +1 -0
- package/dist/agent-history-list.d.ts.map +1 -0
- package/dist/agent-history-list.js +1 -0
- package/dist/agent-history-list.js.map +1 -0
- package/dist/agent-history.d.ts +1 -0
- package/dist/agent-history.d.ts.map +1 -0
- package/dist/agent-history.js +1 -0
- package/dist/agent-history.js.map +1 -0
- package/dist/agent-manager.d.ts +2 -0
- package/dist/agent-manager.d.ts.map +1 -0
- package/dist/agent-manager.js +71 -0
- package/dist/agent-manager.js.map +1 -0
- package/dist/agent-runner.d.ts +1 -0
- package/dist/agent-runner.d.ts.map +1 -0
- package/dist/agent-runner.js +8 -0
- package/dist/agent-runner.js.map +1 -0
- package/dist/agent-types.d.ts +1 -0
- package/dist/agent-types.d.ts.map +1 -0
- package/dist/agent-types.js +1 -0
- package/dist/agent-types.js.map +1 -0
- package/dist/context.d.ts +1 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +1 -0
- package/dist/context.js.map +1 -0
- package/dist/cross-extension-rpc.d.ts +1 -0
- package/dist/cross-extension-rpc.d.ts.map +1 -0
- package/dist/cross-extension-rpc.js +1 -0
- package/dist/cross-extension-rpc.js.map +1 -0
- package/dist/custom-agents.d.ts +1 -0
- package/dist/custom-agents.d.ts.map +1 -0
- package/dist/custom-agents.js +1 -0
- package/dist/custom-agents.js.map +1 -0
- package/dist/default-agents.d.ts +1 -0
- package/dist/default-agents.d.ts.map +1 -0
- package/dist/default-agents.js +1 -0
- package/dist/default-agents.js.map +1 -0
- package/dist/enabled-models.d.ts +1 -0
- package/dist/enabled-models.d.ts.map +1 -0
- package/dist/enabled-models.js +1 -0
- package/dist/enabled-models.js.map +1 -0
- package/dist/env.d.ts +1 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +1 -0
- package/dist/env.js.map +1 -0
- package/dist/group-join.d.ts +1 -0
- package/dist/group-join.d.ts.map +1 -0
- package/dist/group-join.js +1 -0
- package/dist/group-join.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -10
- package/dist/index.js.map +1 -0
- package/dist/invocation-config.d.ts +1 -0
- package/dist/invocation-config.d.ts.map +1 -0
- package/dist/invocation-config.js +1 -0
- package/dist/invocation-config.js.map +1 -0
- package/dist/memory.d.ts +1 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +1 -0
- package/dist/memory.js.map +1 -0
- package/dist/model-resolver.d.ts +1 -0
- package/dist/model-resolver.d.ts.map +1 -0
- package/dist/model-resolver.js +1 -0
- package/dist/model-resolver.js.map +1 -0
- package/dist/nico-overrides.d.ts +1 -0
- package/dist/nico-overrides.d.ts.map +1 -0
- package/dist/nico-overrides.js +1 -0
- package/dist/nico-overrides.js.map +1 -0
- package/dist/output-file.d.ts +1 -0
- package/dist/output-file.d.ts.map +1 -0
- package/dist/output-file.js +1 -0
- package/dist/output-file.js.map +1 -0
- package/dist/prompts.d.ts +1 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +1 -0
- package/dist/prompts.js.map +1 -0
- package/dist/schedule-store.d.ts +1 -0
- package/dist/schedule-store.d.ts.map +1 -0
- package/dist/schedule-store.js +1 -0
- package/dist/schedule-store.js.map +1 -0
- package/dist/schedule.d.ts +1 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/schedule.js +1 -0
- package/dist/schedule.js.map +1 -0
- package/dist/settings.d.ts +1 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +1 -0
- package/dist/settings.js.map +1 -0
- package/dist/skill-loader.d.ts +1 -0
- package/dist/skill-loader.d.ts.map +1 -0
- package/dist/skill-loader.js +1 -0
- package/dist/skill-loader.js.map +1 -0
- package/dist/status-note.d.ts +1 -0
- package/dist/status-note.d.ts.map +1 -0
- package/dist/status-note.js +1 -0
- package/dist/status-note.js.map +1 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/agent-widget.d.ts +1 -0
- package/dist/ui/agent-widget.d.ts.map +1 -0
- package/dist/ui/agent-widget.js +4 -2
- package/dist/ui/agent-widget.js.map +1 -0
- package/dist/ui/ccstyle/diff/ansi-utils.d.ts +11 -0
- package/dist/ui/ccstyle/diff/ansi-utils.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/ansi-utils.js +145 -0
- package/dist/ui/ccstyle/diff/ansi-utils.js.map +1 -0
- package/dist/ui/ccstyle/diff/diff-presentation.d.ts +12 -0
- package/dist/ui/ccstyle/diff/diff-presentation.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/diff-presentation.js +49 -0
- package/dist/ui/ccstyle/diff/diff-presentation.js.map +1 -0
- package/dist/ui/ccstyle/diff/diff-renderer.d.ts +46 -0
- package/dist/ui/ccstyle/diff/diff-renderer.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/diff-renderer.js +2044 -0
- package/dist/ui/ccstyle/diff/diff-renderer.js.map +1 -0
- package/dist/ui/ccstyle/diff/line-width-safety.d.ts +12 -0
- package/dist/ui/ccstyle/diff/line-width-safety.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/line-width-safety.js +59 -0
- package/dist/ui/ccstyle/diff/line-width-safety.js.map +1 -0
- package/dist/ui/ccstyle/diff/render-utils.d.ts +6 -0
- package/dist/ui/ccstyle/diff/render-utils.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/render-utils.js +25 -0
- package/dist/ui/ccstyle/diff/render-utils.js.map +1 -0
- package/dist/ui/ccstyle/diff/types.d.ts +22 -0
- package/dist/ui/ccstyle/diff/types.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/types.js +10 -0
- package/dist/ui/ccstyle/diff/types.js.map +1 -0
- package/dist/ui/ccstyle/diff/write-display-utils.d.ts +2 -0
- package/dist/ui/ccstyle/diff/write-display-utils.d.ts.map +1 -0
- package/dist/ui/ccstyle/diff/write-display-utils.js +12 -0
- package/dist/ui/ccstyle/diff/write-display-utils.js.map +1 -0
- package/dist/ui/ccstyle/tool-renderer.d.ts +17 -0
- package/dist/ui/ccstyle/tool-renderer.d.ts.map +1 -0
- package/dist/ui/ccstyle/tool-renderer.js +58 -0
- package/dist/ui/ccstyle/tool-renderer.js.map +1 -0
- package/dist/ui/ccstyle/tool-result.d.ts +24 -0
- package/dist/ui/ccstyle/tool-result.d.ts.map +1 -0
- package/dist/ui/ccstyle/tool-result.js +193 -0
- package/dist/ui/ccstyle/tool-result.js.map +1 -0
- package/dist/ui/conversation-blocks.d.ts +28 -0
- package/dist/ui/conversation-blocks.d.ts.map +1 -0
- package/dist/ui/conversation-blocks.js +119 -27
- package/dist/ui/conversation-blocks.js.map +1 -0
- package/dist/ui/conversation-nvim.d.ts +1 -0
- package/dist/ui/conversation-nvim.d.ts.map +1 -0
- package/dist/ui/conversation-nvim.js +1 -0
- package/dist/ui/conversation-nvim.js.map +1 -0
- package/dist/ui/conversation-role.d.ts +13 -0
- package/dist/ui/conversation-role.d.ts.map +1 -0
- package/dist/ui/conversation-role.js +39 -0
- package/dist/ui/conversation-role.js.map +1 -0
- package/dist/ui/conversation-search.d.ts +1 -0
- package/dist/ui/conversation-search.d.ts.map +1 -0
- package/dist/ui/conversation-search.js +1 -0
- package/dist/ui/conversation-search.js.map +1 -0
- package/dist/ui/conversation-timeline.d.ts +62 -0
- package/dist/ui/conversation-timeline.d.ts.map +1 -0
- package/dist/ui/conversation-timeline.js +263 -0
- package/dist/ui/conversation-timeline.js.map +1 -0
- package/dist/ui/conversation-viewer.d.ts +7 -1
- package/dist/ui/conversation-viewer.d.ts.map +1 -0
- package/dist/ui/conversation-viewer.js +96 -54
- package/dist/ui/conversation-viewer.js.map +1 -0
- package/dist/ui/fleet-list.d.ts +1 -0
- package/dist/ui/fleet-list.d.ts.map +1 -0
- package/dist/ui/fleet-list.js +1 -0
- package/dist/ui/fleet-list.js.map +1 -0
- package/dist/ui/schedule-menu.d.ts +1 -0
- package/dist/ui/schedule-menu.d.ts.map +1 -0
- package/dist/ui/schedule-menu.js +1 -0
- package/dist/ui/schedule-menu.js.map +1 -0
- package/dist/ui/viewer-keys.d.ts +1 -0
- package/dist/ui/viewer-keys.d.ts.map +1 -0
- package/dist/ui/viewer-keys.js +1 -0
- package/dist/ui/viewer-keys.js.map +1 -0
- package/dist/usage.d.ts +1 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +1 -0
- package/dist/usage.js.map +1 -0
- package/dist/worktree.d.ts +1 -0
- package/dist/worktree.d.ts.map +1 -0
- package/dist/worktree.js +1 -0
- package/dist/worktree.js.map +1 -0
- package/dist/write-execution.d.ts +42 -0
- package/dist/write-execution.d.ts.map +1 -0
- package/dist/write-execution.js +138 -0
- package/dist/write-execution.js.map +1 -0
- package/package.json +1 -1
- package/src/agent-manager.ts +61 -0
- package/src/agent-runner.ts +7 -0
- package/src/index.ts +35 -11
- package/src/types.ts +4 -0
- package/src/ui/agent-widget.ts +2 -1
- package/src/ui/ccstyle/ATTRIBUTION.md +37 -0
- package/src/ui/ccstyle/diff/ansi-utils.ts +168 -0
- package/src/ui/ccstyle/diff/diff-presentation.ts +70 -0
- package/src/ui/ccstyle/diff/diff-renderer.ts +3148 -0
- package/src/ui/ccstyle/diff/line-width-safety.ts +92 -0
- package/src/ui/ccstyle/diff/render-utils.ts +32 -0
- package/src/ui/ccstyle/diff/types.ts +33 -0
- package/src/ui/ccstyle/diff/write-display-utils.ts +12 -0
- package/src/ui/ccstyle/tool-renderer.ts +71 -0
- package/src/ui/ccstyle/tool-result.ts +238 -0
- package/src/ui/conversation-blocks.ts +160 -26
- package/src/ui/conversation-role.ts +45 -0
- package/src/ui/conversation-timeline.ts +315 -0
- package/src/ui/conversation-viewer.ts +91 -54
- package/src/write-execution.ts +180 -0
- package/test/output-file-history.test.ts +50 -0
- package/test/ui/conversation-blocks.test.ts +31 -0
- package/test/ui/conversation-diff.test.ts +91 -0
- package/test/ui/conversation-viewer.test.ts +50 -0
- package/test/write-execution.test.ts +83 -0
- package/tsconfig.json +16 -0
- package/vitest.temp.config.mts +6 -0
package/src/index.ts
CHANGED
|
@@ -451,6 +451,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
451
451
|
id: record.id, type: record.type, description: record.description,
|
|
452
452
|
status: record.status, result: record.result, error: record.error,
|
|
453
453
|
startedAt: record.startedAt, completedAt: record.completedAt,
|
|
454
|
+
toolUses: record.toolUses,
|
|
455
|
+
lifetimeUsage: record.lifetimeUsage,
|
|
456
|
+
invocation: record.invocation,
|
|
454
457
|
transcriptPath: record.transcriptPath,
|
|
455
458
|
});
|
|
456
459
|
|
|
@@ -1168,7 +1171,9 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1168
1171
|
const effectiveMaxTurns = normalizeMaxTurns(resolvedConfig.maxTurns ?? getDefaultMaxTurns());
|
|
1169
1172
|
const agentInvocation: AgentInvocation = {
|
|
1170
1173
|
modelName,
|
|
1174
|
+
effectiveModelName: model?.name ?? model?.id ?? ctx.model?.name ?? ctx.model?.id,
|
|
1171
1175
|
thinking,
|
|
1176
|
+
effectiveThinking: thinking,
|
|
1172
1177
|
// Explicit value only — the default fallback would just add noise.
|
|
1173
1178
|
// Normalize so `0` (unlimited) doesn't surface as a misleading "max turns: 0".
|
|
1174
1179
|
maxTurns: normalizeMaxTurns(resolvedConfig.maxTurns),
|
|
@@ -1758,6 +1763,26 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1758
1763
|
}
|
|
1759
1764
|
}
|
|
1760
1765
|
|
|
1766
|
+
function makeUniqueAgentOptionLabels(pairs: Array<{ record: AgentRecord; label: string }>): string[] {
|
|
1767
|
+
const counts = new Map<string, number>();
|
|
1768
|
+
for (const pair of pairs) counts.set(pair.label, (counts.get(pair.label) ?? 0) + 1);
|
|
1769
|
+
const used = new Set<string>();
|
|
1770
|
+
return pairs.map((pair) => {
|
|
1771
|
+
const { record, label } = pair;
|
|
1772
|
+
if ((counts.get(label) ?? 0) === 1) {
|
|
1773
|
+
used.add(label);
|
|
1774
|
+
return label;
|
|
1775
|
+
}
|
|
1776
|
+
const suffix = ` · #${record.id.slice(-8)}`;
|
|
1777
|
+
let candidate = `${label}${suffix}`;
|
|
1778
|
+
let n = 2;
|
|
1779
|
+
while (used.has(candidate)) candidate = `${label}${suffix}-${n++}`;
|
|
1780
|
+
used.add(candidate);
|
|
1781
|
+
pair.label = candidate;
|
|
1782
|
+
return candidate;
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1761
1786
|
async function showRunningAgents(ctx: ExtensionCommandContext) {
|
|
1762
1787
|
const { active: agents } = splitAgentRecords(manager.listAgents(), ctx.cwd);
|
|
1763
1788
|
if (agents.length === 0) {
|
|
@@ -1765,19 +1790,17 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1765
1790
|
return;
|
|
1766
1791
|
}
|
|
1767
1792
|
|
|
1768
|
-
const
|
|
1769
|
-
const dn = getDisplayName(
|
|
1770
|
-
const dur = formatDuration(
|
|
1771
|
-
return `${dn} (${
|
|
1793
|
+
const pairs = agents.map((record) => {
|
|
1794
|
+
const dn = getDisplayName(record.type);
|
|
1795
|
+
const dur = formatDuration(record.startedAt, record.completedAt);
|
|
1796
|
+
return { record, label: `${dn} (${record.description}) · ${record.toolUses} tools · ${record.status} · ${dur}` };
|
|
1772
1797
|
});
|
|
1798
|
+
const options = makeUniqueAgentOptionLabels(pairs);
|
|
1773
1799
|
|
|
1774
1800
|
const choice = await ctx.ui.select("Running agents", options);
|
|
1775
1801
|
if (!choice) return;
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
const idx = options.indexOf(choice);
|
|
1779
|
-
if (idx < 0) return;
|
|
1780
|
-
const record = agents[idx];
|
|
1802
|
+
const record = pairs.find((pair) => pair.label === choice)?.record;
|
|
1803
|
+
if (!record) return;
|
|
1781
1804
|
|
|
1782
1805
|
await viewAgentConversation(ctx, record, "live");
|
|
1783
1806
|
// Back-navigation: re-show the list
|
|
@@ -1791,10 +1814,11 @@ Terse command-style prompts produce shallow, generic work.
|
|
|
1791
1814
|
return;
|
|
1792
1815
|
}
|
|
1793
1816
|
|
|
1794
|
-
const
|
|
1817
|
+
const pairs = history.map((record) => ({ record, label: formatAgentHistoryOption(record, Date.now()) }));
|
|
1818
|
+
const options = makeUniqueAgentOptionLabels(pairs);
|
|
1795
1819
|
const choice = await ctx.ui.select("Agent history", options);
|
|
1796
1820
|
if (!choice) return;
|
|
1797
|
-
const record =
|
|
1821
|
+
const record = pairs.find((pair) => pair.label === choice)?.record;
|
|
1798
1822
|
if (!record) return;
|
|
1799
1823
|
|
|
1800
1824
|
await viewAgentConversation(ctx, record, "history");
|
package/src/types.ts
CHANGED
|
@@ -137,6 +137,10 @@ export interface AgentRecord {
|
|
|
137
137
|
export interface AgentInvocation {
|
|
138
138
|
/** Short display name, e.g. "haiku" — only set when different from parent. */
|
|
139
139
|
modelName?: string;
|
|
140
|
+
/** Effective model captured from the child session, including inherited models. */
|
|
141
|
+
effectiveModelName?: string;
|
|
142
|
+
/** Requested/ effective thinking level. */
|
|
143
|
+
effectiveThinking?: AgentSession["thinkingLevel"];
|
|
140
144
|
thinking?: ThinkingLevel;
|
|
141
145
|
maxTurns?: number;
|
|
142
146
|
isolated?: boolean;
|
package/src/ui/agent-widget.ts
CHANGED
|
@@ -166,7 +166,8 @@ export function buildInvocationTags(
|
|
|
166
166
|
): { modelName?: string; tags: string[] } {
|
|
167
167
|
const tags: string[] = [];
|
|
168
168
|
if (!invocation) return { tags };
|
|
169
|
-
|
|
169
|
+
const thinking = invocation.effectiveThinking ?? invocation.thinking;
|
|
170
|
+
if (thinking) tags.push(`thinking: ${thinking}`);
|
|
170
171
|
if (invocation.isolated) tags.push("isolated");
|
|
171
172
|
if (invocation.isolation === "worktree") tags.push("worktree");
|
|
172
173
|
if (invocation.inheritContext) tags.push("inherit context");
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Attribution
|
|
2
|
+
|
|
3
|
+
The viewer-local ccstyle tool-card and diff renderer are adapted from
|
|
4
|
+
`@herbertgao/pi-cc-extensions` version `0.9.0`, specifically its
|
|
5
|
+
`extensions/renderer/tool/result.ts` and
|
|
6
|
+
`extensions/renderer/tool/diff/` modules.
|
|
7
|
+
|
|
8
|
+
The adaptation is local to this package and changes the configuration boundary,
|
|
9
|
+
write payload handling, width guards, and syntax highlighting fallback. It does
|
|
10
|
+
not install `@shikijs/cli`, patch Pi prototypes, or register global terminal
|
|
11
|
+
handlers. The local copy uses Pi's public `highlightCode` API and plain text
|
|
12
|
+
when highlighting is unavailable.
|
|
13
|
+
|
|
14
|
+
The source package is distributed under the MIT License. The relevant license
|
|
15
|
+
text is reproduced below:
|
|
16
|
+
|
|
17
|
+
> MIT License
|
|
18
|
+
>
|
|
19
|
+
> Copyright (c) 2026 Herbert Gao and pi-cc-extensions contributors
|
|
20
|
+
>
|
|
21
|
+
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
22
|
+
> of this software and associated documentation files (the "Software"), to deal
|
|
23
|
+
> in the Software without restriction, including without limitation the rights
|
|
24
|
+
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
25
|
+
> copies of the Software, and to permit persons to whom the Software is
|
|
26
|
+
> furnished to do so, subject to the following conditions:
|
|
27
|
+
>
|
|
28
|
+
> The above copyright notice and this permission notice shall be included in all
|
|
29
|
+
> copies or substantial portions of the Software.
|
|
30
|
+
>
|
|
31
|
+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
32
|
+
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
33
|
+
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
34
|
+
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
35
|
+
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
36
|
+
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
37
|
+
> SOFTWARE.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
const ANSI_SGR_PATTERN = /\x1b\[([0-9;]*)m/g;
|
|
2
|
+
const ANSI_SGR_WITH_COLON_PATTERN = /\x1b\[([0-9;:]*)m/g;
|
|
3
|
+
const STYLE_RESET_PARAMS = [39, 22, 23, 24, 25, 27, 28, 29, 59] as const;
|
|
4
|
+
|
|
5
|
+
export { ANSI_SGR_PATTERN, STYLE_RESET_PARAMS };
|
|
6
|
+
|
|
7
|
+
export function expandSgrReset(param: number): readonly number[] | undefined {
|
|
8
|
+
return param === 0 ? STYLE_RESET_PARAMS : undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function toSgrParams(rawParams: string): number[] {
|
|
12
|
+
if (!rawParams.trim()) {
|
|
13
|
+
return [0];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const parsed = rawParams
|
|
17
|
+
.split(";")
|
|
18
|
+
.map((token) => Number.parseInt(token, 10))
|
|
19
|
+
.filter((value) => Number.isFinite(value));
|
|
20
|
+
|
|
21
|
+
return parsed.length > 0 ? parsed : [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isFiniteSgrParam(value: number | undefined): value is number {
|
|
25
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function readSgrColorSequence(params: number[], index: number): number[] | undefined {
|
|
29
|
+
const param = params[index];
|
|
30
|
+
if (param !== 38 && param !== 48) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const colorMode = params[index + 1];
|
|
35
|
+
if (colorMode === 5) {
|
|
36
|
+
const colorValue = params[index + 2];
|
|
37
|
+
return isFiniteSgrParam(colorValue) ? [param, colorMode, colorValue] : undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (colorMode === 2) {
|
|
41
|
+
const red = params[index + 2];
|
|
42
|
+
const green = params[index + 3];
|
|
43
|
+
const blue = params[index + 4];
|
|
44
|
+
return isFiniteSgrParam(red) && isFiniteSgrParam(green) && isFiniteSgrParam(blue)
|
|
45
|
+
? [param, colorMode, red, green, blue]
|
|
46
|
+
: undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function stripBackgroundSgrParams(params: readonly number[]): number[] {
|
|
53
|
+
const sanitized: number[] = [];
|
|
54
|
+
|
|
55
|
+
for (let index = 0; index < params.length; index++) {
|
|
56
|
+
const param = params[index] ?? 0;
|
|
57
|
+
|
|
58
|
+
if (param === 0) {
|
|
59
|
+
sanitized.push(...expandSgrReset(param)!);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (param === 49 || (param >= 40 && param <= 47) || (param >= 100 && param <= 107)) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (param === 38 || param === 48) {
|
|
68
|
+
const sequence = readSgrColorSequence(params as number[], index);
|
|
69
|
+
if (sequence) {
|
|
70
|
+
if (param === 38) {
|
|
71
|
+
sanitized.push(...sequence);
|
|
72
|
+
}
|
|
73
|
+
index += sequence.length - 1;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const advance = params[index + 1] === 5 ? 2 : params[index + 1] === 2 ? 4 : 0;
|
|
77
|
+
if (advance > 0) {
|
|
78
|
+
index += advance;
|
|
79
|
+
if (param === 38) {
|
|
80
|
+
sanitized.push(param);
|
|
81
|
+
}
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
sanitized.push(param);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return sanitized;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function filterSgrSequences(text: string, filter: (params: number[]) => number[]): string {
|
|
93
|
+
if (!text || !text.includes("\x1b[")) {
|
|
94
|
+
return text;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return text.replace(ANSI_SGR_PATTERN, (_sequence, rawParams: string) => {
|
|
98
|
+
const parsed = toSgrParams(rawParams);
|
|
99
|
+
if (parsed.length === 0) {
|
|
100
|
+
return "";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const sanitized = filter(parsed);
|
|
104
|
+
if (sanitized.length === 0) {
|
|
105
|
+
return "";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return `\x1b[${sanitized.join(";")}m`;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isValidColonForeground(segment: string): boolean {
|
|
113
|
+
const params = segment.split(":");
|
|
114
|
+
const isByte = (value: string | undefined) =>
|
|
115
|
+
value !== undefined && /^\d+$/.test(value) && Number(value) <= 255;
|
|
116
|
+
if (params[0] !== "38") return false;
|
|
117
|
+
if (params[1] === "5") return params.length === 3 && isByte(params[2]);
|
|
118
|
+
return (
|
|
119
|
+
params[1] === "2" &&
|
|
120
|
+
params.length === 6 &&
|
|
121
|
+
(params[2] === "" || /^\d+$/.test(params[2] ?? "")) &&
|
|
122
|
+
params.slice(3).every(isByte)
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function sanitizeColonSgr(rawParams: string): string {
|
|
127
|
+
const segments = rawParams.split(";");
|
|
128
|
+
const sanitized: string[] = [];
|
|
129
|
+
for (let index = 0; index < segments.length; index++) {
|
|
130
|
+
const segment = segments[index] ?? "";
|
|
131
|
+
if (segment.includes(":")) {
|
|
132
|
+
const code = Number.parseInt(segment, 10);
|
|
133
|
+
if (code !== 48 && (code !== 38 || isValidColonForeground(segment))) {
|
|
134
|
+
sanitized.push(segment);
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const param = Number.parseInt(segment, 10);
|
|
139
|
+
if (!Number.isFinite(param)) continue;
|
|
140
|
+
if (param === 0) {
|
|
141
|
+
sanitized.push(...STYLE_RESET_PARAMS.map(String));
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (param === 49 || (param >= 40 && param <= 47) || (param >= 100 && param <= 107)) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (param === 38 || param === 48) {
|
|
148
|
+
const mode = Number.parseInt(segments[index + 1] ?? "", 10);
|
|
149
|
+
const advance = mode === 5 ? 2 : mode === 2 ? 4 : 0;
|
|
150
|
+
if (advance > 0) {
|
|
151
|
+
if (param === 38) sanitized.push(...segments.slice(index, index + advance + 1));
|
|
152
|
+
index += advance;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
sanitized.push(segment);
|
|
157
|
+
}
|
|
158
|
+
return sanitized.length > 0 ? `\x1b[${sanitized.join(";")}m` : "";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function sanitizeAnsiForThemedOutput(text: string): string {
|
|
162
|
+
if (!text || !text.includes("\x1b[")) return text;
|
|
163
|
+
return text.replace(ANSI_SGR_WITH_COLON_PATTERN, (sequence, rawParams: string) =>
|
|
164
|
+
rawParams.includes(":")
|
|
165
|
+
? sanitizeColonSgr(rawParams)
|
|
166
|
+
: filterSgrSequences(sequence, stripBackgroundSgrParams),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
2
|
+
import { pluralize } from "./render-utils.js";
|
|
3
|
+
import type { ToolDisplayConfig } from "./types.js";
|
|
4
|
+
|
|
5
|
+
export interface DiffSummaryStats {
|
|
6
|
+
added: number;
|
|
7
|
+
removed: number;
|
|
8
|
+
hunks: number;
|
|
9
|
+
files: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type DiffPresentationMode = "split" | "unified" | "compact" | "summary";
|
|
13
|
+
|
|
14
|
+
const MIN_COMPACT_DIFF_WIDTH = 8;
|
|
15
|
+
const MIN_UNIFIED_DIFF_WIDTH = 18;
|
|
16
|
+
|
|
17
|
+
export function normalizeDiffRenderWidth(width: number): number {
|
|
18
|
+
if (!Number.isFinite(width)) {
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
return Math.max(0, Math.floor(width));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resolveDiffPresentationMode(
|
|
25
|
+
config: Pick<ToolDisplayConfig, "diffViewMode" | "diffSplitMinWidth">,
|
|
26
|
+
width: number,
|
|
27
|
+
canRenderSplitLayout: boolean,
|
|
28
|
+
): DiffPresentationMode {
|
|
29
|
+
const safeWidth = normalizeDiffRenderWidth(width);
|
|
30
|
+
if (safeWidth < MIN_COMPACT_DIFF_WIDTH) {
|
|
31
|
+
return "summary";
|
|
32
|
+
}
|
|
33
|
+
if (safeWidth < MIN_UNIFIED_DIFF_WIDTH) {
|
|
34
|
+
return "compact";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
switch (config.diffViewMode) {
|
|
38
|
+
case "split":
|
|
39
|
+
return canRenderSplitLayout ? "split" : "unified";
|
|
40
|
+
case "unified":
|
|
41
|
+
return "unified";
|
|
42
|
+
case "auto":
|
|
43
|
+
default:
|
|
44
|
+
return safeWidth >= (config.diffSplitMinWidth ?? 120) && canRenderSplitLayout ? "split" : "unified";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function buildDiffSummaryText(stats: DiffSummaryStats, width: number): string {
|
|
49
|
+
const safeWidth = normalizeDiffRenderWidth(width);
|
|
50
|
+
if (safeWidth === 0) {
|
|
51
|
+
return "";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const summaryCandidates = [
|
|
55
|
+
`↳ diff +${stats.added} -${stats.removed} • ${stats.hunks} ${pluralize(stats.hunks, "hunk")} • ${stats.files} ${pluralize(stats.files, "file")}`,
|
|
56
|
+
`↳ diff +${stats.added} -${stats.removed} • ${stats.hunks}h • ${stats.files}f`,
|
|
57
|
+
`↳ diff +${stats.added} -${stats.removed}`,
|
|
58
|
+
`+${stats.added} -${stats.removed}`,
|
|
59
|
+
"diff",
|
|
60
|
+
"…",
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
for (const candidate of summaryCandidates) {
|
|
64
|
+
if (visibleWidth(candidate) <= safeWidth) {
|
|
65
|
+
return candidate;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return truncateToWidth(summaryCandidates[summaryCandidates.length - 1] ?? "", safeWidth, "");
|
|
70
|
+
}
|