@gajae-code/coding-agent 0.9.1 → 0.9.3
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 +36 -0
- package/dist/types/capability/mcp.d.ts +3 -1
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/notify-cli.d.ts +5 -0
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/commands/mcp.d.ts +1 -1
- package/dist/types/config/model-profile-activation.d.ts +7 -7
- package/dist/types/config/model-registry.d.ts +7 -3
- package/dist/types/config/model-resolver.d.ts +2 -2
- package/dist/types/config/settings-schema.d.ts +1 -1
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +20 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +9 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -2
- package/dist/types/modes/components/status-line/types.d.ts +6 -0
- package/dist/types/modes/components/status-line.d.ts +1 -0
- package/dist/types/modes/components/thinking-selector.d.ts +6 -1
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +12 -0
- package/dist/types/notifications/config-commands.d.ts +29 -0
- package/dist/types/notifications/index.d.ts +12 -1
- package/dist/types/notifications/telegram-daemon-cli.d.ts +3 -3
- package/dist/types/notifications/threaded-render.d.ts +2 -0
- package/dist/types/notifications/topic-registry.d.ts +4 -5
- package/dist/types/runtime-credential-selector.d.ts +7 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +4 -3
- package/dist/types/runtime-mcp/types.d.ts +5 -4
- package/dist/types/sdk.d.ts +7 -1
- package/dist/types/session/agent-session.d.ts +22 -3
- package/dist/types/session/session-manager.d.ts +1 -0
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/skill-discovery.d.ts +40 -0
- package/dist/types/tools/skill.d.ts +9 -8
- package/dist/types/utils/linkedom.d.ts +4 -0
- package/dist/types/utils/pasted-image-path.d.ts +5 -3
- package/package.json +11 -7
- package/scripts/build-binary.ts +2 -40
- package/scripts/compile-args.ts +91 -0
- package/scripts/generate-docs-index.ts +1 -0
- package/src/capability/mcp.ts +3 -1
- package/src/cli/args.ts +8 -0
- package/src/cli/mcp-cli.ts +108 -26
- package/src/cli/notify-cli.ts +27 -1
- package/src/cli.ts +26 -0
- package/src/commands/launch.ts +5 -0
- package/src/commands/mcp.ts +8 -8
- package/src/commands/notify.ts +2 -2
- package/src/commands/stats.ts +6 -2
- package/src/config/mcp-schema.json +21 -17
- package/src/config/model-profile-activation.ts +23 -7
- package/src/config/model-registry.ts +21 -4
- package/src/config/model-resolver.ts +5 -16
- package/src/config/settings-schema.ts +3 -2
- package/src/config/settings.ts +24 -1
- package/src/coordinator-mcp/server.ts +7 -4
- package/src/discovery/builtin.ts +14 -0
- package/src/discovery/mcp-json.ts +14 -0
- package/src/extensibility/runtime-skill-discovery.ts +229 -0
- package/src/gjc-runtime/launch-tmux.ts +72 -12
- package/src/gjc-runtime/psmux-detect.ts +17 -2
- package/src/gjc-runtime/state-runtime.ts +105 -12
- package/src/gjc-runtime/team-runtime.ts +35 -3
- package/src/gjc-runtime/workflow-manifest.generated.json +4 -28
- package/src/gjc-runtime/workflow-manifest.ts +1 -7
- package/src/internal-urls/docs-index.generated.ts +3 -2
- package/src/internal-urls/gjc-protocol.ts +14 -1
- package/src/main.ts +26 -3
- package/src/modes/components/assistant-message.ts +1 -1
- package/src/modes/components/settings-selector.ts +3 -3
- package/src/modes/components/status-line/presets.ts +5 -7
- package/src/modes/components/status-line/segments.ts +14 -0
- package/src/modes/components/status-line/types.ts +6 -0
- package/src/modes/components/status-line.ts +7 -1
- package/src/modes/components/thinking-selector.ts +79 -18
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/modes/controllers/event-controller.ts +9 -9
- package/src/modes/controllers/input-controller.ts +52 -11
- package/src/modes/controllers/selector-controller.ts +7 -5
- package/src/modes/interactive-mode.ts +12 -4
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +202 -47
- package/src/notifications/config-commands.ts +70 -0
- package/src/notifications/index.ts +156 -9
- package/src/notifications/rich-render.ts +1 -0
- package/src/notifications/telegram-daemon-cli.ts +13 -5
- package/src/notifications/telegram-daemon.ts +87 -4
- package/src/notifications/threaded-render.ts +14 -0
- package/src/notifications/topic-registry.ts +9 -8
- package/src/prompts/tools/skill-discovery.md +13 -0
- package/src/prompts/tools/skill.md +2 -2
- package/src/runtime-credential-selector.ts +35 -0
- package/src/runtime-mcp/config-writer.ts +4 -3
- package/src/runtime-mcp/config.ts +1 -0
- package/src/runtime-mcp/types.ts +5 -4
- package/src/sdk.ts +68 -5
- package/src/session/agent-session.ts +159 -21
- package/src/session/session-manager.ts +68 -40
- package/src/system-prompt.ts +4 -6
- package/src/tools/bash-interactive.ts +9 -4
- package/src/tools/browser/readable.ts +2 -2
- package/src/tools/fetch.ts +5 -5
- package/src/tools/index.ts +4 -0
- package/src/tools/skill-discovery.ts +73 -0
- package/src/tools/skill.ts +29 -13
- package/src/utils/linkedom.ts +14 -0
- package/src/utils/pasted-image-path.ts +23 -9
- package/src/web/scrapers/arxiv.ts +2 -2
- package/src/web/scrapers/go-pkg.ts +2 -2
- package/src/web/scrapers/iacr.ts +2 -2
- package/src/web/scrapers/readthedocs.ts +2 -2
- package/src/web/scrapers/twitter.ts +3 -2
- package/src/web/scrapers/wikipedia.ts +2 -2
- package/src/export/html/template.css +0 -1060
- package/src/export/html/template.html +0 -47
- package/src/export/html/template.js +0 -2314
- package/vendor/insane-search/engine/tests/test_hardening.py +0 -57
- package/vendor/insane-search/engine/tests/test_smoke.py +0 -152
- package/vendor/insane-search/engine/tests/test_u1.py +0 -200
- package/vendor/insane-search/engine/tests/test_u4.py +0 -131
- package/vendor/insane-search/engine/tests/test_u5.py +0 -163
- package/vendor/insane-search/engine/tests/test_u7.py +0 -124
- package/vendor/insane-search/engine/tests/test_u8.py +0 -216
|
@@ -269,7 +269,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
269
269
|
displayImage.data,
|
|
270
270
|
displayImage.mimeType,
|
|
271
271
|
{ fallbackColor: (text: string) => theme.fg("toolOutput", text) },
|
|
272
|
-
resolveImageOptions(),
|
|
272
|
+
{ ...resolveImageOptions(), refetch: () => displayImage.data },
|
|
273
273
|
),
|
|
274
274
|
),
|
|
275
275
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ThinkingLevel, type ThinkingLevel as ThinkingLevelValue } from "@gajae-code/agent-core";
|
|
2
2
|
import type { Effort } from "@gajae-code/ai";
|
|
3
3
|
import {
|
|
4
4
|
Container,
|
|
@@ -654,7 +654,7 @@ export interface SettingsRuntimeContext {
|
|
|
654
654
|
/** Available thinking levels (from session) */
|
|
655
655
|
availableThinkingLevels: Effort[];
|
|
656
656
|
/** Current thinking level (from session) */
|
|
657
|
-
thinkingLevel:
|
|
657
|
+
thinkingLevel: ThinkingLevelValue | undefined;
|
|
658
658
|
/** Available themes */
|
|
659
659
|
availableThemes: string[];
|
|
660
660
|
/** Available model profile names (from the model registry) */
|
|
@@ -843,7 +843,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
843
843
|
|
|
844
844
|
// Special case: inject runtime options for thinking level
|
|
845
845
|
if (def.path === "defaultThinkingLevel") {
|
|
846
|
-
options = this.context.availableThinkingLevels.map(level => {
|
|
846
|
+
options = [ThinkingLevel.Off, ...this.context.availableThinkingLevels].map(level => {
|
|
847
847
|
const baseOpt = options.find(o => o.value === level);
|
|
848
848
|
return baseOpt || { value: level, label: level };
|
|
849
849
|
});
|
|
@@ -3,7 +3,7 @@ import type { PresetDef, StatusLinePreset } from "./types";
|
|
|
3
3
|
export const STATUS_LINE_PRESETS: Record<StatusLinePreset, PresetDef> = {
|
|
4
4
|
default: {
|
|
5
5
|
leftSegments: ["model", "mode", "git", "pr", "path"],
|
|
6
|
-
rightSegments: ["session_name", "jobs", "token_rate", "
|
|
6
|
+
rightSegments: ["session_name", "jobs", "token_rate", "cost"],
|
|
7
7
|
separator: "slash",
|
|
8
8
|
segmentOptions: {
|
|
9
9
|
model: { showThinkingLevel: true },
|
|
@@ -14,7 +14,7 @@ export const STATUS_LINE_PRESETS: Record<StatusLinePreset, PresetDef> = {
|
|
|
14
14
|
|
|
15
15
|
"default-usage": {
|
|
16
16
|
leftSegments: ["model", "mode", "git", "pr", "path"],
|
|
17
|
-
rightSegments: ["session_name", "jobs", "token_rate", "usage", "
|
|
17
|
+
rightSegments: ["session_name", "jobs", "token_rate", "usage", "cost"],
|
|
18
18
|
separator: "slash",
|
|
19
19
|
segmentOptions: {
|
|
20
20
|
model: { showThinkingLevel: true },
|
|
@@ -35,7 +35,7 @@ export const STATUS_LINE_PRESETS: Record<StatusLinePreset, PresetDef> = {
|
|
|
35
35
|
|
|
36
36
|
compact: {
|
|
37
37
|
leftSegments: ["model", "mode", "git", "pr"],
|
|
38
|
-
rightSegments: ["session_name", "jobs", "cost"
|
|
38
|
+
rightSegments: ["session_name", "jobs", "cost"],
|
|
39
39
|
separator: "slash",
|
|
40
40
|
segmentOptions: {
|
|
41
41
|
model: { showThinkingLevel: false },
|
|
@@ -53,7 +53,6 @@ export const STATUS_LINE_PRESETS: Record<StatusLinePreset, PresetDef> = {
|
|
|
53
53
|
"token_rate",
|
|
54
54
|
"cache_read",
|
|
55
55
|
"cost",
|
|
56
|
-
"context_pct",
|
|
57
56
|
"time_spent",
|
|
58
57
|
"time",
|
|
59
58
|
],
|
|
@@ -78,7 +77,6 @@ export const STATUS_LINE_PRESETS: Record<StatusLinePreset, PresetDef> = {
|
|
|
78
77
|
"cache_write",
|
|
79
78
|
"token_rate",
|
|
80
79
|
"cost",
|
|
81
|
-
"context_pct",
|
|
82
80
|
"context_total",
|
|
83
81
|
"time_spent",
|
|
84
82
|
"time",
|
|
@@ -95,7 +93,7 @@ export const STATUS_LINE_PRESETS: Record<StatusLinePreset, PresetDef> = {
|
|
|
95
93
|
ascii: {
|
|
96
94
|
// No Nerd Font dependencies
|
|
97
95
|
leftSegments: ["model", "mode", "path", "git", "pr"],
|
|
98
|
-
rightSegments: ["session_name", "jobs", "token_total", "cost"
|
|
96
|
+
rightSegments: ["session_name", "jobs", "token_total", "cost"],
|
|
99
97
|
separator: "ascii",
|
|
100
98
|
segmentOptions: {
|
|
101
99
|
model: { showThinkingLevel: true },
|
|
@@ -107,7 +105,7 @@ export const STATUS_LINE_PRESETS: Record<StatusLinePreset, PresetDef> = {
|
|
|
107
105
|
custom: {
|
|
108
106
|
// User-defined - these are just defaults that get overridden
|
|
109
107
|
leftSegments: ["model", "mode", "path", "git", "pr"],
|
|
110
|
-
rightSegments: ["session_name", "jobs", "token_total", "cost"
|
|
108
|
+
rightSegments: ["session_name", "jobs", "token_total", "cost"],
|
|
111
109
|
separator: "slash",
|
|
112
110
|
segmentOptions: {},
|
|
113
111
|
},
|
|
@@ -105,6 +105,20 @@ const modelSegment: StatusLineSegment = {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
+
// Show the context-window token percentage right next to the model /
|
|
109
|
+
// reasoning effort (rather than as a trailing segment) so it stays
|
|
110
|
+
// grouped with the model it describes. Disable per-preset with
|
|
111
|
+
// `segmentOptions.model.showContextPercent: false`. Suppressed
|
|
112
|
+
// automatically when a standalone context_pct segment is also in the
|
|
113
|
+
// active layout, so the value is never shown twice.
|
|
114
|
+
if (opts.showContextPercent !== false && ctx.contextPctSegmentActive !== true) {
|
|
115
|
+
const pct = ctx.contextPercent;
|
|
116
|
+
const window = ctx.contextWindow;
|
|
117
|
+
if (window > 0 && Number.isFinite(pct)) {
|
|
118
|
+
const color = getContextUsageThemeColor(getContextUsageLevel(pct, window));
|
|
119
|
+
content += `${theme.sep.dot}${theme.fg(color, `${pct.toFixed(1)}%`)}`;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
108
122
|
|
|
109
123
|
return { content: theme.fg("statusLineModel", content), visible: true };
|
|
110
124
|
},
|
|
@@ -41,6 +41,12 @@ export interface SegmentContext {
|
|
|
41
41
|
};
|
|
42
42
|
contextPercent: number;
|
|
43
43
|
contextWindow: number;
|
|
44
|
+
/**
|
|
45
|
+
* True when a standalone `context_pct` segment is also part of the active
|
|
46
|
+
* layout. The model segment suppresses its inline percentage in that case to
|
|
47
|
+
* avoid showing the same value twice.
|
|
48
|
+
*/
|
|
49
|
+
contextPctSegmentActive?: boolean;
|
|
44
50
|
autoCompactEnabled: boolean;
|
|
45
51
|
subagentCount: number;
|
|
46
52
|
jobs: JobsSnapshot;
|
|
@@ -29,7 +29,7 @@ import { calculateTokensPerSecond } from "./status-line/token-rate";
|
|
|
29
29
|
import type { SeparatorDef } from "./status-line/types";
|
|
30
30
|
|
|
31
31
|
export interface StatusLineSegmentOptions {
|
|
32
|
-
model?: { showThinkingLevel?: boolean };
|
|
32
|
+
model?: { showThinkingLevel?: boolean; showContextPercent?: boolean };
|
|
33
33
|
path?: { abbreviate?: boolean; maxLength?: number; stripWorkPrefix?: boolean };
|
|
34
34
|
git?: { showBranch?: boolean; showStaged?: boolean; showUnstaged?: boolean; showUntracked?: boolean };
|
|
35
35
|
time?: { format?: "12h" | "24h"; showSeconds?: boolean };
|
|
@@ -700,6 +700,11 @@ export class StatusLineComponent implements Component {
|
|
|
700
700
|
const contextTokens = breakdown.usedTokens;
|
|
701
701
|
const contextWindow = breakdown.contextWindow || state.model?.contextWindow || 0;
|
|
702
702
|
const contextPercent = contextWindow > 0 ? (contextTokens / contextWindow) * 100 : 0;
|
|
703
|
+
// Suppress the inline model percentage when a standalone context_pct
|
|
704
|
+
// segment is also rendered, so the value is not shown twice.
|
|
705
|
+
const contextPctSegmentActive =
|
|
706
|
+
effectiveSettings.leftSegments.includes("context_pct") ||
|
|
707
|
+
effectiveSettings.rightSegments.includes("context_pct");
|
|
703
708
|
|
|
704
709
|
return {
|
|
705
710
|
session: this.session,
|
|
@@ -710,6 +715,7 @@ export class StatusLineComponent implements Component {
|
|
|
710
715
|
usageStats,
|
|
711
716
|
contextPercent,
|
|
712
717
|
contextWindow,
|
|
718
|
+
contextPctSegmentActive,
|
|
713
719
|
autoCompactEnabled: this.#autoCompactEnabled,
|
|
714
720
|
subagentCount: this.#subagentCount,
|
|
715
721
|
jobs: this.#jobs,
|
|
@@ -1,53 +1,114 @@
|
|
|
1
1
|
import { ThinkingLevel, type ThinkingLevel as ThinkingLevelValue } from "@gajae-code/agent-core";
|
|
2
|
-
import { Container, type SelectItem, SelectList } from "@gajae-code/tui";
|
|
3
|
-
import { getSelectListTheme } from "../../modes/theme/theme";
|
|
2
|
+
import { Container, type SelectItem, SelectList, Text } from "@gajae-code/tui";
|
|
3
|
+
import { getSelectListTheme, theme } from "../../modes/theme/theme";
|
|
4
4
|
import { getThinkingLevelMetadata, type ThinkingLevelValue as ThinkingMetadataValue } from "../../thinking-metadata";
|
|
5
5
|
import { DynamicBorder } from "./dynamic-border";
|
|
6
6
|
|
|
7
|
+
const SCOPE_ITEMS = [
|
|
8
|
+
{
|
|
9
|
+
value: "session",
|
|
10
|
+
label: "Apply for this session",
|
|
11
|
+
description: "Use this effort until the session changes it again",
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
value: "default",
|
|
15
|
+
label: "Set as default",
|
|
16
|
+
description: "Save this effort for future sessions",
|
|
17
|
+
},
|
|
18
|
+
] as const satisfies readonly SelectItem[];
|
|
19
|
+
|
|
20
|
+
export interface ThinkingSelectorSelection {
|
|
21
|
+
level: ThinkingLevelValue;
|
|
22
|
+
persistDefault: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
7
25
|
/**
|
|
8
26
|
* Component that renders a thinking level selector with borders
|
|
9
27
|
*/
|
|
10
28
|
export class ThinkingSelectorComponent extends Container {
|
|
11
29
|
#selectList: SelectList;
|
|
30
|
+
#levelList: SelectList;
|
|
31
|
+
#selectedLevel: ThinkingLevelValue | undefined;
|
|
32
|
+
readonly #onSelect: (selection: ThinkingSelectorSelection) => void;
|
|
33
|
+
readonly #onCancel: () => void;
|
|
12
34
|
|
|
13
35
|
constructor(
|
|
14
36
|
currentLevel: ThinkingLevelValue | undefined,
|
|
15
37
|
availableLevels: ThinkingLevelValue[],
|
|
16
|
-
onSelect: (
|
|
38
|
+
onSelect: (selection: ThinkingSelectorSelection) => void,
|
|
17
39
|
onCancel: () => void,
|
|
18
40
|
) {
|
|
19
41
|
super();
|
|
20
42
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
);
|
|
43
|
+
this.#onSelect = onSelect;
|
|
44
|
+
this.#onCancel = onCancel;
|
|
24
45
|
|
|
25
|
-
|
|
26
|
-
|
|
46
|
+
const currentValue = currentLevel ?? ThinkingLevel.Off;
|
|
47
|
+
const thinkingLevels: SelectItem[] = availableLevels.map(level => {
|
|
48
|
+
const metadata = getThinkingLevelMetadata(level as ThinkingMetadataValue);
|
|
49
|
+
return level === currentValue
|
|
50
|
+
? { ...metadata, label: metadata.label + theme.fg("muted", " (current)") }
|
|
51
|
+
: metadata;
|
|
52
|
+
});
|
|
27
53
|
|
|
28
|
-
|
|
29
|
-
this.#selectList =
|
|
54
|
+
this.#levelList = new SelectList(thinkingLevels, thinkingLevels.length, getSelectListTheme());
|
|
55
|
+
this.#selectList = this.#levelList;
|
|
30
56
|
|
|
31
57
|
// Preselect current level
|
|
32
|
-
const currentIndex = thinkingLevels.findIndex(item => item.value ===
|
|
58
|
+
const currentIndex = thinkingLevels.findIndex(item => item.value === currentValue);
|
|
33
59
|
if (currentIndex !== -1) {
|
|
34
|
-
this.#
|
|
60
|
+
this.#levelList.setSelectedIndex(currentIndex);
|
|
35
61
|
}
|
|
36
62
|
|
|
37
|
-
this.#
|
|
38
|
-
|
|
63
|
+
this.#levelList.onSelect = item => {
|
|
64
|
+
this.#selectedLevel = item.value as ThinkingLevelValue;
|
|
65
|
+
this.#renderScopeList();
|
|
39
66
|
};
|
|
40
67
|
|
|
41
|
-
this.#
|
|
42
|
-
onCancel();
|
|
68
|
+
this.#levelList.onCancel = () => {
|
|
69
|
+
this.#onCancel();
|
|
43
70
|
};
|
|
44
71
|
|
|
45
|
-
this
|
|
72
|
+
this.#renderLevelList();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#renderLevelList(): void {
|
|
76
|
+
this.detachAll();
|
|
77
|
+
this.#selectedLevel = undefined;
|
|
78
|
+
this.#selectList = this.#levelList;
|
|
79
|
+
this.addChild(new DynamicBorder());
|
|
80
|
+
this.addChild(this.#levelList);
|
|
81
|
+
this.addChild(new DynamicBorder());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#renderScopeList(): void {
|
|
85
|
+
const level = this.#selectedLevel;
|
|
86
|
+
if (!level) return;
|
|
87
|
+
|
|
88
|
+
const scopeList = new SelectList(SCOPE_ITEMS, SCOPE_ITEMS.length, getSelectListTheme());
|
|
89
|
+
scopeList.onSelect = item => {
|
|
90
|
+
this.#onSelect({
|
|
91
|
+
level,
|
|
92
|
+
persistDefault: item.value === "default",
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
scopeList.onCancel = () => {
|
|
96
|
+
this.#renderLevelList();
|
|
97
|
+
};
|
|
46
98
|
|
|
47
|
-
|
|
99
|
+
const metadata = getThinkingLevelMetadata(level as ThinkingMetadataValue);
|
|
100
|
+
this.detachAll();
|
|
101
|
+
this.#selectList = scopeList;
|
|
102
|
+
this.addChild(new DynamicBorder());
|
|
103
|
+
this.addChild(new Text(theme.fg("muted", ` Reasoning effort: ${metadata.label}`), 0, 0));
|
|
104
|
+
this.addChild(scopeList);
|
|
48
105
|
this.addChild(new DynamicBorder());
|
|
49
106
|
}
|
|
50
107
|
|
|
108
|
+
handleInput(data: string): void {
|
|
109
|
+
this.#selectList.handleInput(data);
|
|
110
|
+
}
|
|
111
|
+
|
|
51
112
|
getSelectList(): SelectList {
|
|
52
113
|
return this.#selectList;
|
|
53
114
|
}
|
|
@@ -678,7 +678,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
678
678
|
imageData,
|
|
679
679
|
imageMimeType,
|
|
680
680
|
{ fallbackColor: (s: string) => theme.fg("toolOutput", s) },
|
|
681
|
-
resolveImageOptions(),
|
|
681
|
+
{ ...resolveImageOptions(), refetch: () => imageData },
|
|
682
682
|
);
|
|
683
683
|
this.#imageComponents.push(imageComponent);
|
|
684
684
|
this.addChild(imageComponent);
|
|
@@ -26,7 +26,7 @@ import { interruptHint } from "../shared";
|
|
|
26
26
|
import { buildAbortDisplayMessage } from "../utils/abort-message";
|
|
27
27
|
import { consumeInjectedOptimisticSignature } from "../utils/injected-user-submission";
|
|
28
28
|
import { ringTerminalBell } from "../utils/terminal-bell";
|
|
29
|
-
import { argsWithPartialJson } from "../utils/ui-helpers";
|
|
29
|
+
import { addChatChild, argsWithPartialJson } from "../utils/ui-helpers";
|
|
30
30
|
|
|
31
31
|
type AgentSessionEventKind = AgentSessionEvent["type"];
|
|
32
32
|
|
|
@@ -169,12 +169,12 @@ export class EventController {
|
|
|
169
169
|
|
|
170
170
|
#getReadGroup(): ReadToolGroupComponent {
|
|
171
171
|
if (!this.#lastReadGroup) {
|
|
172
|
-
this.ctx
|
|
172
|
+
addChatChild(this.ctx, new Text("", 0, 0));
|
|
173
173
|
const group = new ReadToolGroupComponent({
|
|
174
174
|
showContentPreview: this.ctx.settings.get("read.toolResultPreview"),
|
|
175
175
|
});
|
|
176
176
|
group.setExpanded(this.ctx.toolOutputExpanded);
|
|
177
|
-
this.ctx
|
|
177
|
+
addChatChild(this.ctx, group);
|
|
178
178
|
this.#lastReadGroup = group;
|
|
179
179
|
}
|
|
180
180
|
return this.#lastReadGroup;
|
|
@@ -332,7 +332,7 @@ export class EventController {
|
|
|
332
332
|
this.ctx.ui.requestRender(),
|
|
333
333
|
);
|
|
334
334
|
this.ctx.streamingMessage = event.message;
|
|
335
|
-
this.ctx
|
|
335
|
+
addChatChild(this.ctx, this.ctx.streamingComponent);
|
|
336
336
|
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
|
|
337
337
|
this.ctx.ui.requestRender();
|
|
338
338
|
}
|
|
@@ -445,7 +445,7 @@ export class EventController {
|
|
|
445
445
|
);
|
|
446
446
|
if (!this.ctx.pendingTools.has(content.id)) {
|
|
447
447
|
this.#resetReadGroup();
|
|
448
|
-
this.ctx
|
|
448
|
+
addChatChild(this.ctx, new Text("", 0, 0));
|
|
449
449
|
const tool = this.ctx.session.getToolByName(content.name);
|
|
450
450
|
const component = new ToolExecutionComponent(
|
|
451
451
|
content.name,
|
|
@@ -462,8 +462,8 @@ export class EventController {
|
|
|
462
462
|
content.id,
|
|
463
463
|
);
|
|
464
464
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
465
|
-
this.ctx.chatContainer.addChild(component);
|
|
466
465
|
this.ctx.pendingTools.set(content.id, component);
|
|
466
|
+
addChatChild(this.ctx, component);
|
|
467
467
|
} else {
|
|
468
468
|
this.ctx.pendingTools.get(content.id)?.updateArgs(renderArgs, content.id);
|
|
469
469
|
}
|
|
@@ -575,8 +575,8 @@ export class EventController {
|
|
|
575
575
|
event.toolCallId,
|
|
576
576
|
);
|
|
577
577
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
578
|
-
this.ctx.chatContainer.addChild(component);
|
|
579
578
|
this.ctx.pendingTools.set(event.toolCallId, component);
|
|
579
|
+
addChatChild(this.ctx, component);
|
|
580
580
|
this.ctx.ui.requestRender();
|
|
581
581
|
}
|
|
582
582
|
}
|
|
@@ -869,13 +869,13 @@ export class EventController {
|
|
|
869
869
|
async #handleTtsrTriggered(event: Extract<AgentSessionEvent, { type: "ttsr_triggered" }>): Promise<void> {
|
|
870
870
|
const component = new TtsrNotificationComponent(event.rules);
|
|
871
871
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
872
|
-
this.ctx
|
|
872
|
+
addChatChild(this.ctx, component);
|
|
873
873
|
this.ctx.ui.requestRender();
|
|
874
874
|
}
|
|
875
875
|
|
|
876
876
|
async #handleTodoReminder(event: Extract<AgentSessionEvent, { type: "todo_reminder" }>): Promise<void> {
|
|
877
877
|
const component = new TodoReminderComponent(event.todos, event.attempt, event.maxAttempts);
|
|
878
|
-
this.ctx
|
|
878
|
+
addChatChild(this.ctx, component);
|
|
879
879
|
this.ctx.ui.requestRender();
|
|
880
880
|
}
|
|
881
881
|
|
|
@@ -18,7 +18,7 @@ import { copyToClipboard, readImageFromClipboard } from "../../utils/clipboard";
|
|
|
18
18
|
import { getEditorCommand, openInEditor } from "../../utils/external-editor";
|
|
19
19
|
import { ensureSupportedImageInput, ImageInputTooLargeError, loadImageInput } from "../../utils/image-loading";
|
|
20
20
|
import { resizeImage } from "../../utils/image-resize";
|
|
21
|
-
import { resolvePastedImagePath } from "../../utils/pasted-image-path";
|
|
21
|
+
import { formatPastedImageReference, resolvePastedImagePath } from "../../utils/pasted-image-path";
|
|
22
22
|
import { generateSessionTitle, setSessionTerminalTitle } from "../../utils/title-generator";
|
|
23
23
|
import { type QueuedMessageMoveDirection, QueuedMessageSelectorComponent } from "../components/queued-message-selector";
|
|
24
24
|
|
|
@@ -27,6 +27,8 @@ interface Expandable {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const INTERACTIVE_ABORT_CLEANUP_TIMEOUT_MS = 5_000;
|
|
30
|
+
const IMAGE_PLACEHOLDER_PATTERN = /\[image ([1-9]\d*)\]/g;
|
|
31
|
+
const IMAGE_PLACEHOLDER_PRESENT_PATTERN = /\[image [1-9]\d*\]/;
|
|
30
32
|
|
|
31
33
|
function isExpandable(obj: unknown): obj is Expandable {
|
|
32
34
|
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
|
@@ -383,6 +385,7 @@ export class InputController {
|
|
|
383
385
|
this.ctx.isBashMode = trimmed.startsWith("!");
|
|
384
386
|
this.ctx.isBashNoContext = trimmed.startsWith("!!");
|
|
385
387
|
this.ctx.isPythonMode = trimmed.startsWith("$") && !trimmed.startsWith("${");
|
|
388
|
+
this.#clearPendingImagesIfPlaceholdersRemoved(text);
|
|
386
389
|
if (
|
|
387
390
|
wasBashMode !== this.ctx.isBashMode ||
|
|
388
391
|
wasBashNoContext !== this.ctx.isBashNoContext ||
|
|
@@ -418,7 +421,7 @@ export class InputController {
|
|
|
418
421
|
}
|
|
419
422
|
|
|
420
423
|
const runner = this.ctx.session.extensionRunner;
|
|
421
|
-
let inputImages = this
|
|
424
|
+
let inputImages = this.#visiblePendingImagesForText(text);
|
|
422
425
|
|
|
423
426
|
if (runner?.hasHandlers("input")) {
|
|
424
427
|
const result = await runner.emitInput(text, inputImages, "interactive");
|
|
@@ -498,7 +501,7 @@ export class InputController {
|
|
|
498
501
|
|
|
499
502
|
// Queue input during compaction
|
|
500
503
|
if (this.ctx.session.isCompacting) {
|
|
501
|
-
if (
|
|
504
|
+
if ((inputImages?.length ?? 0) > 0) {
|
|
502
505
|
this.ctx.showStatus("Compaction in progress. Retry after it completes to send images.");
|
|
503
506
|
return;
|
|
504
507
|
}
|
|
@@ -520,11 +523,13 @@ export class InputController {
|
|
|
520
523
|
// typed since queuing intact. Same protection as #783, applied to
|
|
521
524
|
// the streaming/queue path.
|
|
522
525
|
const streamingBehavior = this.#busyStreamingBehavior();
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
)
|
|
526
|
+
const promptOptions =
|
|
527
|
+
streamingBehavior === "followUp"
|
|
528
|
+
? { streamingBehavior, images, followUpQueuePolicy: "sequential" as const }
|
|
529
|
+
: { streamingBehavior, images };
|
|
530
|
+
await this.ctx.withLocalSubmission(text, () => this.ctx.session.prompt(text, promptOptions), {
|
|
531
|
+
imageCount: images?.length ?? 0,
|
|
532
|
+
});
|
|
528
533
|
this.ctx.updatePendingMessagesDisplay();
|
|
529
534
|
this.ctx.ui.requestRender();
|
|
530
535
|
return;
|
|
@@ -869,7 +874,9 @@ export class InputController {
|
|
|
869
874
|
details,
|
|
870
875
|
attribution: "user",
|
|
871
876
|
},
|
|
872
|
-
|
|
877
|
+
streamingBehavior === "followUp"
|
|
878
|
+
? { streamingBehavior, followUpQueuePolicy: "sequential" }
|
|
879
|
+
: { streamingBehavior },
|
|
873
880
|
);
|
|
874
881
|
}
|
|
875
882
|
if (this.ctx.session.isStreaming) {
|
|
@@ -911,7 +918,10 @@ export class InputController {
|
|
|
911
918
|
this.ctx.editor.addToHistory(text);
|
|
912
919
|
this.ctx.editor.setText("");
|
|
913
920
|
await this.ctx.withLocalSubmission(text, () =>
|
|
914
|
-
this.ctx.session.prompt(text, {
|
|
921
|
+
this.ctx.session.prompt(text, {
|
|
922
|
+
streamingBehavior: "followUp",
|
|
923
|
+
followUpQueuePolicy: "sequential",
|
|
924
|
+
}),
|
|
915
925
|
);
|
|
916
926
|
this.ctx.updatePendingMessagesDisplay();
|
|
917
927
|
this.ctx.ui.requestRender();
|
|
@@ -1086,7 +1096,7 @@ export class InputController {
|
|
|
1086
1096
|
data: image.data,
|
|
1087
1097
|
mimeType: image.mimeType,
|
|
1088
1098
|
});
|
|
1089
|
-
this.ctx.editor.insertText(`${this.#nextImagePlaceholder()} `);
|
|
1099
|
+
this.ctx.editor.insertText(`${formatPastedImageReference(this.#nextImagePlaceholder(), image.resolvedPath)} `);
|
|
1090
1100
|
this.ctx.showStatus(`Attached image: ${path.basename(image.resolvedPath)}`, { dim: true });
|
|
1091
1101
|
this.ctx.ui.requestRender();
|
|
1092
1102
|
return true;
|
|
@@ -1104,6 +1114,37 @@ export class InputController {
|
|
|
1104
1114
|
return `[image ${this.ctx.pendingImages.length}]`;
|
|
1105
1115
|
}
|
|
1106
1116
|
|
|
1117
|
+
#visiblePendingImagesForText(text: string): InteractiveModeContext["pendingImages"] | undefined {
|
|
1118
|
+
if (this.ctx.pendingImages.length === 0) {
|
|
1119
|
+
return undefined;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
const images: InteractiveModeContext["pendingImages"] = [];
|
|
1123
|
+
const seenImageIndexes = new Set<number>();
|
|
1124
|
+
for (const match of text.matchAll(IMAGE_PLACEHOLDER_PATTERN)) {
|
|
1125
|
+
const placeholderNumberText = match[1];
|
|
1126
|
+
if (!placeholderNumberText) continue;
|
|
1127
|
+
const placeholderNumber = Number.parseInt(placeholderNumberText, 10);
|
|
1128
|
+
const imageIndex = placeholderNumber - 1;
|
|
1129
|
+
if (imageIndex < 0 || imageIndex >= this.ctx.pendingImages.length || seenImageIndexes.has(imageIndex)) {
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
const image = this.ctx.pendingImages[imageIndex];
|
|
1133
|
+
if (!image) continue;
|
|
1134
|
+
images.push(image);
|
|
1135
|
+
seenImageIndexes.add(imageIndex);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
return images.length > 0 ? images : undefined;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
#clearPendingImagesIfPlaceholdersRemoved(text: string): void {
|
|
1142
|
+
if (this.ctx.pendingImages.length === 0 || IMAGE_PLACEHOLDER_PRESENT_PATTERN.test(text)) {
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
this.ctx.pendingImages = [];
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1107
1148
|
async handleImagePaste(): Promise<boolean> {
|
|
1108
1149
|
try {
|
|
1109
1150
|
const image = await readImageFromClipboard();
|
|
@@ -443,12 +443,13 @@ export class SelectorController {
|
|
|
443
443
|
const selector = new ThinkingSelectorComponent(
|
|
444
444
|
this.ctx.session.thinkingLevel,
|
|
445
445
|
availableLevels,
|
|
446
|
-
|
|
446
|
+
selection => {
|
|
447
447
|
done();
|
|
448
448
|
|
|
449
|
+
const { level, persistDefault } = selection;
|
|
449
450
|
const configuredDefault = this.ctx.settings.get("defaultThinkingLevel");
|
|
450
451
|
const levelToApply = level === ThinkingLevel.Inherit ? configuredDefault : level;
|
|
451
|
-
this.ctx.session.setThinkingLevel(levelToApply,
|
|
452
|
+
this.ctx.session.setThinkingLevel(levelToApply, persistDefault);
|
|
452
453
|
const effectiveLevel = this.ctx.session.thinkingLevel ?? ThinkingLevel.Off;
|
|
453
454
|
const requestedLabel =
|
|
454
455
|
level === ThinkingLevel.Inherit ? `${level} (configured default: ${configuredDefault})` : level;
|
|
@@ -458,9 +459,11 @@ export class SelectorController {
|
|
|
458
459
|
this.ctx.statusLine.invalidate();
|
|
459
460
|
this.ctx.updateEditorBorderColor();
|
|
460
461
|
this.ctx.updateEditorTopBorder();
|
|
462
|
+
if (persistDefault) void this.ctx.notifyConfigChanged?.();
|
|
461
463
|
this.ctx.ui.requestRender();
|
|
464
|
+
const scopeLabel = persistDefault ? "Default reasoning effort" : "Reasoning effort";
|
|
462
465
|
this.ctx.showStatus(
|
|
463
|
-
|
|
466
|
+
`${scopeLabel} set to ${requestedLabel}. Effective effort: ${effectiveLevel}.${clampedSuffix}`,
|
|
464
467
|
);
|
|
465
468
|
},
|
|
466
469
|
() => {
|
|
@@ -468,10 +471,9 @@ export class SelectorController {
|
|
|
468
471
|
this.ctx.ui.requestRender();
|
|
469
472
|
},
|
|
470
473
|
);
|
|
471
|
-
return { component: selector, focus: selector
|
|
474
|
+
return { component: selector, focus: selector };
|
|
472
475
|
});
|
|
473
476
|
}
|
|
474
|
-
|
|
475
477
|
showSettingsSelector(): void {
|
|
476
478
|
getAvailableThemes().then(availableThemes => {
|
|
477
479
|
this.showSelector(done => {
|
|
@@ -18,6 +18,7 @@ import type { Component, EditorTheme, SlashCommand } from "@gajae-code/tui";
|
|
|
18
18
|
import {
|
|
19
19
|
Container,
|
|
20
20
|
clearRenderCache,
|
|
21
|
+
getRenderCacheRetainedBytes,
|
|
21
22
|
Loader,
|
|
22
23
|
Markdown,
|
|
23
24
|
ProcessTerminal,
|
|
@@ -114,7 +115,7 @@ import {
|
|
|
114
115
|
theme,
|
|
115
116
|
} from "./theme/theme";
|
|
116
117
|
import type { CompactionQueuedMessage, InteractiveModeContext, SubmittedUserInput, TodoItem, TodoPhase } from "./types";
|
|
117
|
-
import { UiHelpers } from "./utils/ui-helpers";
|
|
118
|
+
import { addChatChild, UiHelpers } from "./utils/ui-helpers";
|
|
118
119
|
|
|
119
120
|
const INTERACTIVE_ABORT_CLEANUP_TIMEOUT_MS = 5_000;
|
|
120
121
|
const COMPOSER_NEWLINE_HINT = process.platform === "win32" ? "Alt+Enter/Ctrl+J" : "Shift+Enter/Ctrl+J";
|
|
@@ -398,6 +399,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
398
399
|
) {
|
|
399
400
|
this.session = session;
|
|
400
401
|
this.sessionManager = session.sessionManager;
|
|
402
|
+
this.session.setRetainedMemorySampler(() => ({
|
|
403
|
+
tuiChatChildren: this.chatContainer.children.length,
|
|
404
|
+
tuiCachedRenderBytes: getRenderCacheRetainedBytes(),
|
|
405
|
+
}));
|
|
401
406
|
this.settings = session.settings;
|
|
402
407
|
this.keybindings = KeybindingsManager.inMemory();
|
|
403
408
|
this.agent = session.agent;
|
|
@@ -1514,8 +1519,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1514
1519
|
|
|
1515
1520
|
#renderPlanPreview(planContent: string, options?: { append?: boolean }): void {
|
|
1516
1521
|
const existingContainer = this.#planReviewContainer;
|
|
1517
|
-
|
|
1518
|
-
|
|
1522
|
+
// Only reuse the existing preview when it is still attached to the chat container;
|
|
1523
|
+
// a collapsed/disposed container must be re-added, not mutated in place.
|
|
1524
|
+
const stillAttached = existingContainer !== undefined && this.chatContainer.children.includes(existingContainer);
|
|
1525
|
+
const replaceExisting = options?.append !== true && stillAttached;
|
|
1526
|
+
const planReviewContainer = replaceExisting && existingContainer ? existingContainer : new Container();
|
|
1519
1527
|
planReviewContainer.clear();
|
|
1520
1528
|
planReviewContainer.addChild(new Spacer(1));
|
|
1521
1529
|
planReviewContainer.addChild(new DynamicBorder());
|
|
@@ -1524,7 +1532,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1524
1532
|
planReviewContainer.addChild(new Markdown(planContent, 1, 1, getMarkdownTheme()));
|
|
1525
1533
|
planReviewContainer.addChild(new DynamicBorder());
|
|
1526
1534
|
if (!replaceExisting) {
|
|
1527
|
-
this
|
|
1535
|
+
addChatChild(this, planReviewContainer);
|
|
1528
1536
|
}
|
|
1529
1537
|
this.#planReviewContainer = planReviewContainer;
|
|
1530
1538
|
this.ui.requestRender();
|