wave-code 1.0.10 → 1.1.1
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/bin/wave-code.js +8 -2
- package/dist/bundle/wave.mjs +3231 -0
- package/package.json +24 -25
- package/src/cli.tsx +6 -1
- package/src/commands/update.ts +2 -7
- package/src/components/App.tsx +3 -7
- package/src/components/BtwDisplay.tsx +10 -1
- package/src/components/ChatInterface.tsx +21 -52
- package/src/components/ConfirmationDetails.tsx +144 -37
- package/src/components/DiffDisplay.tsx +335 -301
- package/src/components/InputBox.tsx +17 -2
- package/src/components/LoadingIndicator.tsx +16 -2
- package/src/components/MessageBlockItem.tsx +2 -4
- package/src/components/MessageList.tsx +75 -14
- package/src/components/ReasoningDisplay.tsx +2 -4
- package/src/components/SessionSelector.tsx +82 -11
- package/src/components/SkillsManager.tsx +312 -0
- package/src/components/StatusCommand.tsx +2 -9
- package/src/components/ToolDisplay.tsx +2 -4
- package/src/constants/commands.ts +6 -0
- package/src/contexts/useChat.tsx +245 -301
- package/src/daemon/commands.ts +57 -27
- package/src/daemon/jsonRpcClient.ts +1 -1
- package/src/daemon/socketClient.ts +2 -2
- package/src/hooks/useInputManager.ts +7 -1
- package/src/index.ts +57 -10
- package/src/managers/inputHandlers.ts +2 -0
- package/src/managers/inputReducer.ts +195 -104
- package/src/print-cli.ts +25 -17
- package/src/reducers/selectorReducer.ts +10 -0
- package/src/reducers/skillsManagerReducer.ts +91 -0
- package/src/session-selector-cli.tsx +269 -28
- package/src/stdio/agentBridge.ts +30 -21
- package/src/stdio/daemonServer.ts +3 -1
- package/src/stdio/protocol.ts +2 -0
- package/src/utils/clipboard.ts +21 -0
- package/src/utils/highlightUtils.ts +46 -1
- package/src/utils/readPackageJson.ts +29 -0
- package/src/utils/streamingText.ts +11 -0
- package/src/utils/worktree.ts +191 -43
- package/dist/cli.d.ts +0 -6
- package/dist/cli.js +0 -53
- package/dist/commands/plugin/disable.d.ts +0 -5
- package/dist/commands/plugin/disable.js +0 -15
- package/dist/commands/plugin/enable.d.ts +0 -5
- package/dist/commands/plugin/enable.js +0 -15
- package/dist/commands/plugin/install.d.ts +0 -5
- package/dist/commands/plugin/install.js +0 -21
- package/dist/commands/plugin/list.d.ts +0 -1
- package/dist/commands/plugin/list.js +0 -31
- package/dist/commands/plugin/marketplace.d.ts +0 -10
- package/dist/commands/plugin/marketplace.js +0 -90
- package/dist/commands/plugin/uninstall.d.ts +0 -3
- package/dist/commands/plugin/uninstall.js +0 -16
- package/dist/commands/plugin/update.d.ts +0 -3
- package/dist/commands/plugin/update.js +0 -15
- package/dist/commands/update.d.ts +0 -1
- package/dist/commands/update.js +0 -97
- package/dist/components/AgentsManager.d.ts +0 -7
- package/dist/components/AgentsManager.js +0 -109
- package/dist/components/App.d.ts +0 -9
- package/dist/components/App.js +0 -76
- package/dist/components/BackgroundTaskManager.d.ts +0 -5
- package/dist/components/BackgroundTaskManager.js +0 -99
- package/dist/components/BangDisplay.d.ts +0 -8
- package/dist/components/BangDisplay.js +0 -17
- package/dist/components/BtwDisplay.d.ts +0 -7
- package/dist/components/BtwDisplay.js +0 -19
- package/dist/components/ChatInterface.d.ts +0 -2
- package/dist/components/ChatInterface.js +0 -61
- package/dist/components/CommandSelector.d.ts +0 -10
- package/dist/components/CommandSelector.js +0 -66
- package/dist/components/CompactDisplay.d.ts +0 -8
- package/dist/components/CompactDisplay.js +0 -13
- package/dist/components/ConfirmationDetails.d.ts +0 -9
- package/dist/components/ConfirmationDetails.js +0 -45
- package/dist/components/ConfirmationSelector.d.ts +0 -13
- package/dist/components/ConfirmationSelector.js +0 -127
- package/dist/components/DiffDisplay.d.ts +0 -8
- package/dist/components/DiffDisplay.js +0 -201
- package/dist/components/DiscoverView.d.ts +0 -2
- package/dist/components/DiscoverView.js +0 -37
- package/dist/components/FileSelector.d.ts +0 -11
- package/dist/components/FileSelector.js +0 -50
- package/dist/components/HelpView.d.ts +0 -7
- package/dist/components/HelpView.js +0 -74
- package/dist/components/HistorySearch.d.ts +0 -8
- package/dist/components/HistorySearch.js +0 -66
- package/dist/components/InputBox.d.ts +0 -24
- package/dist/components/InputBox.js +0 -149
- package/dist/components/InstalledView.d.ts +0 -2
- package/dist/components/InstalledView.js +0 -45
- package/dist/components/LoadingIndicator.d.ts +0 -10
- package/dist/components/LoadingIndicator.js +0 -6
- package/dist/components/LoginCommand.d.ts +0 -5
- package/dist/components/LoginCommand.js +0 -153
- package/dist/components/Markdown.d.ts +0 -5
- package/dist/components/Markdown.js +0 -127
- package/dist/components/MarketplaceAddForm.d.ts +0 -2
- package/dist/components/MarketplaceAddForm.js +0 -55
- package/dist/components/MarketplaceDetail.d.ts +0 -2
- package/dist/components/MarketplaceDetail.js +0 -74
- package/dist/components/MarketplaceList.d.ts +0 -8
- package/dist/components/MarketplaceList.js +0 -17
- package/dist/components/MarketplaceView.d.ts +0 -2
- package/dist/components/MarketplaceView.js +0 -41
- package/dist/components/McpManager.d.ts +0 -9
- package/dist/components/McpManager.js +0 -88
- package/dist/components/MessageBlockItem.d.ts +0 -8
- package/dist/components/MessageBlockItem.js +0 -15
- package/dist/components/MessageList.d.ts +0 -10
- package/dist/components/MessageList.js +0 -80
- package/dist/components/ModelSelector.d.ts +0 -8
- package/dist/components/ModelSelector.js +0 -45
- package/dist/components/Notifications.d.ts +0 -7
- package/dist/components/Notifications.js +0 -9
- package/dist/components/PlanDisplay.d.ts +0 -7
- package/dist/components/PlanDisplay.js +0 -6
- package/dist/components/PluginDetail.d.ts +0 -2
- package/dist/components/PluginDetail.js +0 -87
- package/dist/components/PluginList.d.ts +0 -13
- package/dist/components/PluginList.js +0 -15
- package/dist/components/PluginManagerShell.d.ts +0 -6
- package/dist/components/PluginManagerShell.js +0 -92
- package/dist/components/PluginManagerTypes.d.ts +0 -35
- package/dist/components/PluginManagerTypes.js +0 -1
- package/dist/components/QueuedMessageList.d.ts +0 -2
- package/dist/components/QueuedMessageList.js +0 -18
- package/dist/components/ReasoningDisplay.d.ts +0 -8
- package/dist/components/ReasoningDisplay.js +0 -13
- package/dist/components/RewindCommand.d.ts +0 -12
- package/dist/components/RewindCommand.js +0 -78
- package/dist/components/SessionSelector.d.ts +0 -12
- package/dist/components/SessionSelector.js +0 -47
- package/dist/components/StatusCommand.d.ts +0 -5
- package/dist/components/StatusCommand.js +0 -28
- package/dist/components/StatusLine.d.ts +0 -6
- package/dist/components/StatusLine.js +0 -9
- package/dist/components/TaskList.d.ts +0 -5
- package/dist/components/TaskList.js +0 -197
- package/dist/components/TaskNotificationMessage.d.ts +0 -5
- package/dist/components/TaskNotificationMessage.js +0 -12
- package/dist/components/ToolDisplay.d.ts +0 -9
- package/dist/components/ToolDisplay.js +0 -55
- package/dist/components/WorkflowManager.d.ts +0 -5
- package/dist/components/WorkflowManager.js +0 -102
- package/dist/components/WorktreeExitPrompt.d.ts +0 -12
- package/dist/components/WorktreeExitPrompt.js +0 -35
- package/dist/constants/commands.d.ts +0 -2
- package/dist/constants/commands.js +0 -92
- package/dist/contexts/PluginManagerContext.d.ts +0 -3
- package/dist/contexts/PluginManagerContext.js +0 -9
- package/dist/contexts/useAppConfig.d.ts +0 -10
- package/dist/contexts/useAppConfig.js +0 -13
- package/dist/contexts/useChat.d.ts +0 -101
- package/dist/contexts/useChat.js +0 -993
- package/dist/contracts/status.d.ts +0 -7
- package/dist/contracts/status.js +0 -1
- package/dist/daemon/commands.d.ts +0 -49
- package/dist/daemon/commands.js +0 -341
- package/dist/daemon/jsonRpcClient.d.ts +0 -38
- package/dist/daemon/jsonRpcClient.js +0 -129
- package/dist/daemon/socketClient.d.ts +0 -13
- package/dist/daemon/socketClient.js +0 -26
- package/dist/daemon-cli.d.ts +0 -10
- package/dist/daemon-cli.js +0 -15
- package/dist/hooks/useInputManager.d.ts +0 -75
- package/dist/hooks/useInputManager.js +0 -583
- package/dist/hooks/usePluginManager.d.ts +0 -4
- package/dist/hooks/usePluginManager.js +0 -265
- package/dist/hooks/useTasks.d.ts +0 -1
- package/dist/hooks/useTasks.js +0 -5
- package/dist/index.d.ts +0 -4
- package/dist/index.js +0 -447
- package/dist/managers/inputHandlers.d.ts +0 -30
- package/dist/managers/inputHandlers.js +0 -625
- package/dist/managers/inputReducer.d.ts +0 -271
- package/dist/managers/inputReducer.js +0 -1121
- package/dist/plugin-manager-cli.d.ts +0 -5
- package/dist/plugin-manager-cli.js +0 -12
- package/dist/print-cli.d.ts +0 -10
- package/dist/print-cli.js +0 -209
- package/dist/reducers/agentsManagerReducer.d.ts +0 -26
- package/dist/reducers/agentsManagerReducer.js +0 -54
- package/dist/reducers/backgroundTaskManagerReducer.d.ts +0 -62
- package/dist/reducers/backgroundTaskManagerReducer.js +0 -106
- package/dist/reducers/confirmationReducer.d.ts +0 -37
- package/dist/reducers/confirmationReducer.js +0 -193
- package/dist/reducers/helpSelectorReducer.d.ts +0 -25
- package/dist/reducers/helpSelectorReducer.js +0 -52
- package/dist/reducers/marketplaceAddFormReducer.d.ts +0 -24
- package/dist/reducers/marketplaceAddFormReducer.js +0 -58
- package/dist/reducers/mcpManagerReducer.d.ts +0 -37
- package/dist/reducers/mcpManagerReducer.js +0 -74
- package/dist/reducers/pluginDetailReducer.d.ts +0 -32
- package/dist/reducers/pluginDetailReducer.js +0 -70
- package/dist/reducers/questionReducer.d.ts +0 -82
- package/dist/reducers/questionReducer.js +0 -375
- package/dist/reducers/rewindSelectorReducer.d.ts +0 -21
- package/dist/reducers/rewindSelectorReducer.js +0 -37
- package/dist/reducers/selectorReducer.d.ts +0 -23
- package/dist/reducers/selectorReducer.js +0 -42
- package/dist/reducers/workflowManagerReducer.d.ts +0 -40
- package/dist/reducers/workflowManagerReducer.js +0 -100
- package/dist/session-selector-cli.d.ts +0 -3
- package/dist/session-selector-cli.js +0 -25
- package/dist/stdio/agentBridge.d.ts +0 -139
- package/dist/stdio/agentBridge.js +0 -999
- package/dist/stdio/daemonServer.d.ts +0 -67
- package/dist/stdio/daemonServer.js +0 -191
- package/dist/stdio/index.d.ts +0 -5
- package/dist/stdio/index.js +0 -5
- package/dist/stdio/jsonRpcConnection.d.ts +0 -39
- package/dist/stdio/jsonRpcConnection.js +0 -135
- package/dist/stdio/protocol.d.ts +0 -41
- package/dist/stdio/protocol.js +0 -26
- package/dist/stdio/stdioServer.d.ts +0 -36
- package/dist/stdio/stdioServer.js +0 -51
- package/dist/stdio-cli.d.ts +0 -8
- package/dist/stdio-cli.js +0 -15
- package/dist/types.d.ts +0 -20
- package/dist/types.js +0 -1
- package/dist/utils/bracketedPaste.d.ts +0 -39
- package/dist/utils/bracketedPaste.js +0 -122
- package/dist/utils/clipboard.d.ts +0 -21
- package/dist/utils/clipboard.js +0 -347
- package/dist/utils/constants.d.ts +0 -16
- package/dist/utils/constants.js +0 -18
- package/dist/utils/highlightUtils.d.ts +0 -1
- package/dist/utils/highlightUtils.js +0 -93
- package/dist/utils/inputUtils.d.ts +0 -17
- package/dist/utils/inputUtils.js +0 -67
- package/dist/utils/logger.d.ts +0 -71
- package/dist/utils/logger.js +0 -259
- package/dist/utils/markdownTable.d.ts +0 -34
- package/dist/utils/markdownTable.js +0 -302
- package/dist/utils/rewindCheckpoints.d.ts +0 -8
- package/dist/utils/rewindCheckpoints.js +0 -17
- package/dist/utils/throttle.d.ts +0 -14
- package/dist/utils/throttle.js +0 -55
- package/dist/utils/toolParameterTransforms.d.ts +0 -18
- package/dist/utils/toolParameterTransforms.js +0 -73
- package/dist/utils/usageSummary.d.ts +0 -28
- package/dist/utils/usageSummary.js +0 -121
- package/dist/utils/version.d.ts +0 -1
- package/dist/utils/version.js +0 -4
- package/dist/utils/worktree.d.ts +0 -35
- package/dist/utils/worktree.js +0 -219
package/dist/contexts/useChat.js
DELETED
|
@@ -1,993 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useCallback, useRef, useEffect, useState, useMemo, } from "react";
|
|
3
|
-
import { useInput, useStdout } from "ink";
|
|
4
|
-
import { useAppConfig } from "./useAppConfig.js";
|
|
5
|
-
import { Agent, OPERATION_CANCELLED_BY_USER, extractLatestTotalTokens, } from "wave-agent-sdk";
|
|
6
|
-
import { logger } from "../utils/logger.js";
|
|
7
|
-
import { displayUsageSummary } from "../utils/usageSummary.js";
|
|
8
|
-
import { expandLongTextPlaceholders } from "../managers/inputHandlers.js";
|
|
9
|
-
const ChatContext = createContext(null);
|
|
10
|
-
export const useChat = () => {
|
|
11
|
-
const context = useContext(ChatContext);
|
|
12
|
-
if (!context) {
|
|
13
|
-
throw new Error("useChat must be used within ChatProvider");
|
|
14
|
-
}
|
|
15
|
-
return context;
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Snapshot a SDK message for consumer state. The SDK mutates its internal
|
|
19
|
-
* message blocks in-place BEFORE firing the delta callback (it writes the full
|
|
20
|
-
* accumulated value to the shared block, then computes the chunk delta by
|
|
21
|
-
* slicing the new value). A consumer that pushed the SDK message object by
|
|
22
|
-
* live reference would read the already-updated block and append the delta
|
|
23
|
-
* again — the first delta is double-counted ("LetLet me think..."), affecting
|
|
24
|
-
* reasoning and text content alike. See docs/specs/core/stream-content-updates.md.
|
|
25
|
-
* The clone must be at least one layer deep (message + blocks) so the in-place
|
|
26
|
-
* block mutation never leaks into consumer state.
|
|
27
|
-
*/
|
|
28
|
-
const snapshotMessage = (message) => ({
|
|
29
|
-
...message,
|
|
30
|
-
blocks: message.blocks.map((block) => ({ ...block })),
|
|
31
|
-
});
|
|
32
|
-
/**
|
|
33
|
-
* Window-concat throttle for pure-delta streaming updates: chunks arriving
|
|
34
|
-
* within the cooldown window are merged so no delta is lost (a dropped delta
|
|
35
|
-
* would permanently lose content, unlike the accumulated-payload throttle it
|
|
36
|
-
* replaces). Leading edge fires immediately; the trailing edge carries only
|
|
37
|
-
* chunks that arrived within the window. `end` flushes any pending deltas
|
|
38
|
-
* first, then forwards the end signal right away.
|
|
39
|
-
*/
|
|
40
|
-
function createStreamingWindowThrottle(fn, wait) {
|
|
41
|
-
let timer = null;
|
|
42
|
-
let pending = null;
|
|
43
|
-
const fire = (stage) => {
|
|
44
|
-
if (pending) {
|
|
45
|
-
fn({ ...pending, stage });
|
|
46
|
-
pending = null;
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
const throttled = (params) => {
|
|
50
|
-
if (params.stage === "end") {
|
|
51
|
-
// Flush any deltas still pending inside the cooldown window first
|
|
52
|
-
if (timer) {
|
|
53
|
-
clearTimeout(timer);
|
|
54
|
-
timer = null;
|
|
55
|
-
}
|
|
56
|
-
fire("streaming");
|
|
57
|
-
fn(params);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
if (pending) {
|
|
61
|
-
pending.chunk += params.chunk;
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
pending = { messageId: params.messageId, chunk: params.chunk };
|
|
65
|
-
}
|
|
66
|
-
if (!timer) {
|
|
67
|
-
// Leading edge: fire the current delta immediately, then reset pending so
|
|
68
|
-
// the trailing edge only carries chunks arriving within this window
|
|
69
|
-
fire("streaming");
|
|
70
|
-
timer = setTimeout(() => {
|
|
71
|
-
timer = null;
|
|
72
|
-
fire("streaming");
|
|
73
|
-
}, wait);
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
throttled.cancel = () => {
|
|
77
|
-
if (timer) {
|
|
78
|
-
clearTimeout(timer);
|
|
79
|
-
timer = null;
|
|
80
|
-
}
|
|
81
|
-
pending = null;
|
|
82
|
-
};
|
|
83
|
-
throttled.flush = () => {
|
|
84
|
-
if (timer) {
|
|
85
|
-
clearTimeout(timer);
|
|
86
|
-
timer = null;
|
|
87
|
-
}
|
|
88
|
-
fire("streaming");
|
|
89
|
-
};
|
|
90
|
-
return throttled;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Per-tool window-concat throttle for pure-delta tool parameter streaming:
|
|
94
|
-
* `parametersChunk` deltas are accumulated independently per tool block id
|
|
95
|
-
* within the cooldown window, so interleaved multi-tool streams lose no delta
|
|
96
|
-
* (a plain throttle's single last-args slot would drop every earlier tool's
|
|
97
|
-
* deltas, leaving the first tool without streaming parameters). `start` /
|
|
98
|
-
* `running` apply immediately (one-shot snapshots); `end` flushes pending
|
|
99
|
-
* streaming deltas first, then applies the authoritative parameters/result.
|
|
100
|
-
*/
|
|
101
|
-
export function createToolStreamingThrottle(fn, wait) {
|
|
102
|
-
let timer = null;
|
|
103
|
-
let pending = null;
|
|
104
|
-
const fire = () => {
|
|
105
|
-
if (pending && pending.chunks.size > 0) {
|
|
106
|
-
const { messageId, chunks } = pending;
|
|
107
|
-
pending = null;
|
|
108
|
-
for (const [id, chunk] of chunks) {
|
|
109
|
-
fn({ messageId, id, parametersChunk: chunk, stage: "streaming" });
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
const throttled = (params) => {
|
|
114
|
-
if (params.stage === "end") {
|
|
115
|
-
// Flush any deltas still pending inside the cooldown window first
|
|
116
|
-
if (timer) {
|
|
117
|
-
clearTimeout(timer);
|
|
118
|
-
timer = null;
|
|
119
|
-
}
|
|
120
|
-
fire();
|
|
121
|
-
fn(params);
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
if (params.stage === "streaming") {
|
|
125
|
-
if (!pending) {
|
|
126
|
-
pending = { messageId: params.messageId, chunks: new Map() };
|
|
127
|
-
}
|
|
128
|
-
const prev = pending.chunks.get(params.id) || "";
|
|
129
|
-
pending.chunks.set(params.id, prev + (params.parametersChunk || ""));
|
|
130
|
-
if (!timer) {
|
|
131
|
-
timer = setTimeout(() => {
|
|
132
|
-
timer = null;
|
|
133
|
-
fire();
|
|
134
|
-
}, wait);
|
|
135
|
-
}
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
// start / running — one-shot snapshots applied immediately. Drop this
|
|
139
|
-
// tool's buffered streaming deltas first: start/running carry the
|
|
140
|
-
// authoritative parameters, and a pending timer would otherwise fire late
|
|
141
|
-
// with a stale `streaming` event, regressing this tool block's stage back
|
|
142
|
-
// to streaming (yellow dot -> gray) mid-execution. Other tools' in-flight
|
|
143
|
-
// chunks are kept so interleaved multi-tool streaming still accumulates.
|
|
144
|
-
if (pending) {
|
|
145
|
-
pending.chunks.delete(params.id);
|
|
146
|
-
if (pending.chunks.size === 0) {
|
|
147
|
-
pending = null;
|
|
148
|
-
if (timer) {
|
|
149
|
-
clearTimeout(timer);
|
|
150
|
-
timer = null;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
fn(params);
|
|
155
|
-
};
|
|
156
|
-
throttled.cancel = () => {
|
|
157
|
-
if (timer) {
|
|
158
|
-
clearTimeout(timer);
|
|
159
|
-
timer = null;
|
|
160
|
-
}
|
|
161
|
-
pending = null;
|
|
162
|
-
};
|
|
163
|
-
throttled.flush = () => {
|
|
164
|
-
if (timer) {
|
|
165
|
-
clearTimeout(timer);
|
|
166
|
-
timer = null;
|
|
167
|
-
}
|
|
168
|
-
fire();
|
|
169
|
-
};
|
|
170
|
-
return throttled;
|
|
171
|
-
}
|
|
172
|
-
export const ChatProvider = ({ children, bypassPermissions, permissionMode: initialPermissionMode, pluginDirs, additionalDirectories, tools, allowedTools, disallowedTools, workdir, worktreeSession, originalCwd, version, model, mcpServers, }) => {
|
|
173
|
-
const { restoreSessionId, continueLastSession } = useAppConfig();
|
|
174
|
-
const { stdout } = useStdout();
|
|
175
|
-
// Message Display State
|
|
176
|
-
const [isExpanded, setIsExpanded] = useState(false);
|
|
177
|
-
const isExpandedRef = useRef(isExpanded);
|
|
178
|
-
const [isTaskListVisible, setIsTaskListVisible] = useState(true);
|
|
179
|
-
const [isBtwActive, setIsBtwActive] = useState(false);
|
|
180
|
-
const [messages, setMessages] = useState([]);
|
|
181
|
-
const [latestTotalTokens, setLatestTotalTokens] = useState(0);
|
|
182
|
-
const [maxInputTokens, setMaxInputTokens] = useState(200000);
|
|
183
|
-
// Throttled incremental streaming updaters — 500ms window-concat, the same
|
|
184
|
-
// interval as the pre-incremental throttledSetMessages. Chunks are pure
|
|
185
|
-
// deltas: within-window chunks are merged so none is dropped, and
|
|
186
|
-
// `stage === "end"` flushes pending deltas + applies the end signal
|
|
187
|
-
// immediately so completion results are never delayed.
|
|
188
|
-
const throttledContentUpdate = useMemo(() => createStreamingWindowThrottle((params) => {
|
|
189
|
-
const { messageId, chunk, stage } = params;
|
|
190
|
-
setMessages((prev) => prev.map((m) => {
|
|
191
|
-
if (m.id !== messageId)
|
|
192
|
-
return m;
|
|
193
|
-
const textBlockIndex = m.blocks.findIndex((b) => b.type === "text");
|
|
194
|
-
if (textBlockIndex === -1) {
|
|
195
|
-
return {
|
|
196
|
-
...m,
|
|
197
|
-
blocks: [...m.blocks, { type: "text", content: chunk, stage }],
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
return {
|
|
201
|
-
...m,
|
|
202
|
-
blocks: m.blocks.map((b, idx) => idx === textBlockIndex && b.type === "text"
|
|
203
|
-
? {
|
|
204
|
-
...b,
|
|
205
|
-
content: (b.content || "") + chunk,
|
|
206
|
-
stage,
|
|
207
|
-
}
|
|
208
|
-
: b),
|
|
209
|
-
};
|
|
210
|
-
}));
|
|
211
|
-
}, 500), []);
|
|
212
|
-
const throttledReasoningUpdate = useMemo(() => createStreamingWindowThrottle((params) => {
|
|
213
|
-
const { messageId, chunk, stage } = params;
|
|
214
|
-
setMessages((prev) => prev.map((m) => {
|
|
215
|
-
if (m.id !== messageId)
|
|
216
|
-
return m;
|
|
217
|
-
const reasoningBlockIndex = m.blocks.findIndex((b) => b.type === "reasoning");
|
|
218
|
-
if (reasoningBlockIndex === -1) {
|
|
219
|
-
return {
|
|
220
|
-
...m,
|
|
221
|
-
blocks: [
|
|
222
|
-
...m.blocks,
|
|
223
|
-
{ type: "reasoning", content: chunk, stage },
|
|
224
|
-
],
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
return {
|
|
228
|
-
...m,
|
|
229
|
-
blocks: m.blocks.map((b, idx) => idx === reasoningBlockIndex && b.type === "reasoning"
|
|
230
|
-
? {
|
|
231
|
-
...b,
|
|
232
|
-
content: (b.content || "") + chunk,
|
|
233
|
-
stage,
|
|
234
|
-
}
|
|
235
|
-
: b),
|
|
236
|
-
};
|
|
237
|
-
}));
|
|
238
|
-
}, 500), []);
|
|
239
|
-
const throttledToolBlockUpdate = useMemo(() => createToolStreamingThrottle((params) => {
|
|
240
|
-
const { messageId, id: toolBlockId, parametersChunk, ...updates } = params;
|
|
241
|
-
setMessages((prev) => prev.map((m) => {
|
|
242
|
-
if (m.id !== messageId)
|
|
243
|
-
return m;
|
|
244
|
-
const toolBlockIndex = m.blocks.findIndex((b) => b.type === "tool" && b.id === toolBlockId);
|
|
245
|
-
if (toolBlockIndex === -1) {
|
|
246
|
-
return {
|
|
247
|
-
...m,
|
|
248
|
-
blocks: [
|
|
249
|
-
...m.blocks,
|
|
250
|
-
{
|
|
251
|
-
type: "tool",
|
|
252
|
-
id: toolBlockId,
|
|
253
|
-
name: updates.name || "",
|
|
254
|
-
stage: updates.stage || "start",
|
|
255
|
-
parameters: (updates.parameters || "") + (parametersChunk || ""),
|
|
256
|
-
result: updates.result || "",
|
|
257
|
-
...updates,
|
|
258
|
-
},
|
|
259
|
-
],
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
return {
|
|
263
|
-
...m,
|
|
264
|
-
blocks: m.blocks.map((b, idx) => idx === toolBlockIndex && b.type === "tool"
|
|
265
|
-
? {
|
|
266
|
-
...b,
|
|
267
|
-
...updates,
|
|
268
|
-
// Streaming carries only the delta; append it to the
|
|
269
|
-
// accumulated parameters. start/running/end carry the
|
|
270
|
-
// authoritative value and replace wholesale.
|
|
271
|
-
parameters: parametersChunk
|
|
272
|
-
? (b.parameters || "") + parametersChunk
|
|
273
|
-
: updates.parameters !== undefined
|
|
274
|
-
? updates.parameters
|
|
275
|
-
: b.parameters,
|
|
276
|
-
}
|
|
277
|
-
: b),
|
|
278
|
-
};
|
|
279
|
-
}));
|
|
280
|
-
}, 500), []);
|
|
281
|
-
useEffect(() => {
|
|
282
|
-
isExpandedRef.current = isExpanded;
|
|
283
|
-
if (isExpanded) {
|
|
284
|
-
// Cancel pending throttled updates so the frozen expanded view isn't overwritten
|
|
285
|
-
throttledContentUpdate.cancel();
|
|
286
|
-
throttledReasoningUpdate.cancel();
|
|
287
|
-
throttledToolBlockUpdate.cancel();
|
|
288
|
-
}
|
|
289
|
-
}, [
|
|
290
|
-
isExpanded,
|
|
291
|
-
throttledContentUpdate,
|
|
292
|
-
throttledReasoningUpdate,
|
|
293
|
-
throttledToolBlockUpdate,
|
|
294
|
-
]);
|
|
295
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
296
|
-
const [sessionId, setSessionId] = useState("");
|
|
297
|
-
const [isCommandRunning, setIsCommandRunning] = useState(false);
|
|
298
|
-
const [isCompacting, setIsCompacting] = useState(false);
|
|
299
|
-
const [currentModel, setCurrentModelState] = useState("");
|
|
300
|
-
const [configuredModels, setConfiguredModels] = useState([]);
|
|
301
|
-
const [queuedMessages, setQueuedMessages] = useState([]);
|
|
302
|
-
// MCP State
|
|
303
|
-
const [mcpServerStatuses, setMcpServerStatuses] = useState([]);
|
|
304
|
-
// Background tasks state
|
|
305
|
-
const [backgroundTasks, setBackgroundTasks] = useState([]);
|
|
306
|
-
// Workflow runs state
|
|
307
|
-
const [workflowRuns, setWorkflowRuns] = useState([]);
|
|
308
|
-
// Tasks state
|
|
309
|
-
const [tasks, setTasks] = useState([]);
|
|
310
|
-
// Command state
|
|
311
|
-
const [slashCommands, setSlashCommands] = useState([]);
|
|
312
|
-
// Agent definitions (for /agents overlay)
|
|
313
|
-
const [agentDefinitions, setAgentDefinitions] = useState([]);
|
|
314
|
-
// Permission state
|
|
315
|
-
const [permissionMode, setPermissionModeState] = useState(initialPermissionMode ||
|
|
316
|
-
(bypassPermissions ? "bypassPermissions" : "default"));
|
|
317
|
-
// Confirmation state with queue-based architecture
|
|
318
|
-
const [isConfirmationVisible, setIsConfirmationVisible] = useState(false);
|
|
319
|
-
const [confirmingTool, setConfirmingTool] = useState();
|
|
320
|
-
const [confirmationQueue, setConfirmationQueue] = useState([]);
|
|
321
|
-
const [currentConfirmation, setCurrentConfirmation] = useState(null);
|
|
322
|
-
// Remount state
|
|
323
|
-
const [remountKey, setRemountKey] = useState(0);
|
|
324
|
-
// Full terminal clear + remount so Ink's append-only <Static> re-renders.
|
|
325
|
-
// Used on structural actions (/clear, /compact, rewind, ctrl-o, forceStatic
|
|
326
|
-
// exit) where stale Static output must not linger on screen.
|
|
327
|
-
const forceRemount = useCallback(() => {
|
|
328
|
-
stdout?.write("\u001b[2J\u001b[3J\u001b[0;0H", () => {
|
|
329
|
-
setRemountKey((prev) => prev + 1);
|
|
330
|
-
});
|
|
331
|
-
}, [stdout]);
|
|
332
|
-
// Status metadata state
|
|
333
|
-
const [workingDirectory, setWorkingDirectory] = useState("");
|
|
334
|
-
const agentRef = useRef(null);
|
|
335
|
-
// Full-list refresh — one-shot pull from the agent, used only for structural
|
|
336
|
-
// changes (compact/clear/rewind/collapse/init). Streaming updates flow through
|
|
337
|
-
// the incremental callbacks in initializeAgent below.
|
|
338
|
-
const refreshMessages = useCallback(() => {
|
|
339
|
-
if (!isExpandedRef.current && agentRef.current) {
|
|
340
|
-
const msgs = agentRef.current.messages.map(snapshotMessage);
|
|
341
|
-
setMessages(msgs);
|
|
342
|
-
setLatestTotalTokens(extractLatestTotalTokens(msgs));
|
|
343
|
-
}
|
|
344
|
-
}, []);
|
|
345
|
-
// Permission confirmation methods with queue support
|
|
346
|
-
const showConfirmation = useCallback(async (toolName, toolInput, suggestedPrefix, hidePersistentOption, planContent, permissionMode, warning) => {
|
|
347
|
-
return new Promise((resolve, reject) => {
|
|
348
|
-
const queueItem = {
|
|
349
|
-
toolName,
|
|
350
|
-
toolInput,
|
|
351
|
-
suggestedPrefix,
|
|
352
|
-
hidePersistentOption,
|
|
353
|
-
planContent,
|
|
354
|
-
permissionMode,
|
|
355
|
-
warning,
|
|
356
|
-
resolver: resolve,
|
|
357
|
-
reject,
|
|
358
|
-
};
|
|
359
|
-
setConfirmationQueue((prev) => [...prev, queueItem]);
|
|
360
|
-
// processNextConfirmation will be called via useEffect
|
|
361
|
-
});
|
|
362
|
-
}, []);
|
|
363
|
-
// Initialize AI manager
|
|
364
|
-
const initializeAgent = useCallback(async (restoreSessionIdOverride) => {
|
|
365
|
-
const effectiveRestoreSessionId = restoreSessionIdOverride ?? restoreSessionId;
|
|
366
|
-
const callbacks = {
|
|
367
|
-
// ── Incremental message updates (no full-list pushes) ──────
|
|
368
|
-
onUserMessageAdded: () => {
|
|
369
|
-
if (isExpandedRef.current || !agentRef.current)
|
|
370
|
-
return;
|
|
371
|
-
const msgs = agentRef.current.messages;
|
|
372
|
-
const last = msgs[msgs.length - 1];
|
|
373
|
-
if (!last || last.role !== "user")
|
|
374
|
-
return;
|
|
375
|
-
setMessages((prev) => prev.some((m) => m.id === last.id)
|
|
376
|
-
? prev
|
|
377
|
-
: [...prev, snapshotMessage(last)]);
|
|
378
|
-
},
|
|
379
|
-
onAssistantMessageAdded: (messageId) => {
|
|
380
|
-
if (isExpandedRef.current || !agentRef.current)
|
|
381
|
-
return;
|
|
382
|
-
const msg = agentRef.current.messages.find((m) => m.id === messageId);
|
|
383
|
-
if (!msg)
|
|
384
|
-
return;
|
|
385
|
-
setMessages((prev) => prev.some((m) => m.id === messageId)
|
|
386
|
-
? prev
|
|
387
|
-
: [...prev, snapshotMessage(msg)]);
|
|
388
|
-
},
|
|
389
|
-
onAssistantContentUpdated: (params) => {
|
|
390
|
-
if (isExpandedRef.current)
|
|
391
|
-
return;
|
|
392
|
-
throttledContentUpdate(params);
|
|
393
|
-
},
|
|
394
|
-
onAssistantReasoningUpdated: (params) => {
|
|
395
|
-
if (isExpandedRef.current)
|
|
396
|
-
return;
|
|
397
|
-
throttledReasoningUpdate(params);
|
|
398
|
-
},
|
|
399
|
-
onToolBlockUpdated: (params) => {
|
|
400
|
-
if (isExpandedRef.current)
|
|
401
|
-
return;
|
|
402
|
-
throttledToolBlockUpdate(params);
|
|
403
|
-
if (params.stage === "end")
|
|
404
|
-
throttledToolBlockUpdate.flush();
|
|
405
|
-
},
|
|
406
|
-
onErrorBlockAdded: (error) => {
|
|
407
|
-
if (isExpandedRef.current)
|
|
408
|
-
return;
|
|
409
|
-
setMessages((prev) => {
|
|
410
|
-
// Append to the LAST message only if it is an assistant message
|
|
411
|
-
// (the current turn's in-flight reply). If the last message is a
|
|
412
|
-
// user message, the error belongs BELOW it — create a new
|
|
413
|
-
// assistant message instead of polluting the stale assistant from
|
|
414
|
-
// an earlier turn (which surfaced the error above the latest
|
|
415
|
-
// user message and accumulated it there).
|
|
416
|
-
const last = prev[prev.length - 1];
|
|
417
|
-
if (last && last.role === "assistant") {
|
|
418
|
-
return prev.map((m, idx) => idx === prev.length - 1
|
|
419
|
-
? {
|
|
420
|
-
...m,
|
|
421
|
-
blocks: [...m.blocks, { type: "error", content: error }],
|
|
422
|
-
}
|
|
423
|
-
: m);
|
|
424
|
-
}
|
|
425
|
-
return [
|
|
426
|
-
...prev,
|
|
427
|
-
{
|
|
428
|
-
id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
|
429
|
-
role: "assistant",
|
|
430
|
-
timestamp: new Date().toISOString(),
|
|
431
|
-
blocks: [{ type: "error", content: error }],
|
|
432
|
-
},
|
|
433
|
-
];
|
|
434
|
-
});
|
|
435
|
-
},
|
|
436
|
-
onAddBangMessage: (command, messageId) => {
|
|
437
|
-
if (isExpandedRef.current)
|
|
438
|
-
return;
|
|
439
|
-
setMessages((prev) => prev.some((m) => m.id === messageId)
|
|
440
|
-
? prev
|
|
441
|
-
: [
|
|
442
|
-
...prev,
|
|
443
|
-
{
|
|
444
|
-
id: messageId,
|
|
445
|
-
role: "user",
|
|
446
|
-
timestamp: new Date().toISOString(),
|
|
447
|
-
blocks: [
|
|
448
|
-
{
|
|
449
|
-
type: "bang",
|
|
450
|
-
command,
|
|
451
|
-
output: "",
|
|
452
|
-
stage: "running",
|
|
453
|
-
exitCode: null,
|
|
454
|
-
},
|
|
455
|
-
],
|
|
456
|
-
},
|
|
457
|
-
]);
|
|
458
|
-
},
|
|
459
|
-
onUpdateBangMessage: (command, output, messageId) => {
|
|
460
|
-
if (isExpandedRef.current)
|
|
461
|
-
return;
|
|
462
|
-
setMessages((prev) => prev.map((m) => m.id === messageId
|
|
463
|
-
? {
|
|
464
|
-
...m,
|
|
465
|
-
blocks: m.blocks.map((b, idx) => idx === m.blocks.length - 1 && b.type === "bang"
|
|
466
|
-
? { ...b, command, output }
|
|
467
|
-
: b),
|
|
468
|
-
}
|
|
469
|
-
: m));
|
|
470
|
-
},
|
|
471
|
-
onCompleteBangMessage: (command, exitCode, messageId, output) => {
|
|
472
|
-
if (isExpandedRef.current)
|
|
473
|
-
return;
|
|
474
|
-
setMessages((prev) => prev.map((m) => m.id === messageId
|
|
475
|
-
? {
|
|
476
|
-
...m,
|
|
477
|
-
blocks: m.blocks.map((b, idx) => idx === m.blocks.length - 1 && b.type === "bang"
|
|
478
|
-
? {
|
|
479
|
-
...b,
|
|
480
|
-
command,
|
|
481
|
-
exitCode,
|
|
482
|
-
stage: "end",
|
|
483
|
-
...(output !== undefined ? { output } : {}),
|
|
484
|
-
}
|
|
485
|
-
: b),
|
|
486
|
-
}
|
|
487
|
-
: m));
|
|
488
|
-
},
|
|
489
|
-
onLatestTotalTokensChange: (tokens) => {
|
|
490
|
-
setLatestTotalTokens(tokens);
|
|
491
|
-
},
|
|
492
|
-
onMcpServersChange: (servers) => {
|
|
493
|
-
setMcpServerStatuses([...servers]);
|
|
494
|
-
},
|
|
495
|
-
onSessionIdChange: (sessionId) => {
|
|
496
|
-
setSessionId(sessionId);
|
|
497
|
-
},
|
|
498
|
-
onCompactionStateChange: (isCompactingState) => {
|
|
499
|
-
setIsCompacting(isCompactingState);
|
|
500
|
-
},
|
|
501
|
-
onBackgroundTasksChange: async (tasks) => {
|
|
502
|
-
setBackgroundTasks([...tasks]);
|
|
503
|
-
// Also refresh workflow runs since workflows are background tasks
|
|
504
|
-
const runs = await agentRef.current?.getWorkflowRuns();
|
|
505
|
-
if (runs)
|
|
506
|
-
setWorkflowRuns([...runs]);
|
|
507
|
-
},
|
|
508
|
-
onTasksChange: (newTasks) => {
|
|
509
|
-
setTasks((prev) => {
|
|
510
|
-
if (prev.length === newTasks.length &&
|
|
511
|
-
prev.every((t, i) => t.id === newTasks[i].id && t.status === newTasks[i].status)) {
|
|
512
|
-
return prev;
|
|
513
|
-
}
|
|
514
|
-
return [...newTasks];
|
|
515
|
-
});
|
|
516
|
-
},
|
|
517
|
-
onPermissionModeChange: (mode) => {
|
|
518
|
-
setPermissionModeState(mode);
|
|
519
|
-
},
|
|
520
|
-
onModelChange: (model) => {
|
|
521
|
-
setCurrentModelState(model);
|
|
522
|
-
},
|
|
523
|
-
onConfiguredModelsChange: (models) => {
|
|
524
|
-
setConfiguredModels(models);
|
|
525
|
-
},
|
|
526
|
-
onLoadingChange: (loading) => {
|
|
527
|
-
setIsLoading(loading);
|
|
528
|
-
},
|
|
529
|
-
onCommandRunningChange: (running) => {
|
|
530
|
-
setIsCommandRunning(running);
|
|
531
|
-
},
|
|
532
|
-
onQueuedMessagesChange: (messages) => {
|
|
533
|
-
setQueuedMessages([...messages]);
|
|
534
|
-
},
|
|
535
|
-
};
|
|
536
|
-
try {
|
|
537
|
-
// Create the permission callback inside the try block to access showConfirmation
|
|
538
|
-
const permissionCallback = async (context) => {
|
|
539
|
-
try {
|
|
540
|
-
return await showConfirmation(context.toolName, context.toolInput, context.suggestedPrefix, context.hidePersistentOption, context.planContent, context.permissionMode, context.warning);
|
|
541
|
-
}
|
|
542
|
-
catch {
|
|
543
|
-
// If confirmation was cancelled or failed, deny the operation
|
|
544
|
-
return {
|
|
545
|
-
behavior: "deny",
|
|
546
|
-
message: OPERATION_CANCELLED_BY_USER,
|
|
547
|
-
};
|
|
548
|
-
}
|
|
549
|
-
};
|
|
550
|
-
const agent = await Agent.create({
|
|
551
|
-
callbacks,
|
|
552
|
-
restoreSessionId: effectiveRestoreSessionId,
|
|
553
|
-
continueLastSession,
|
|
554
|
-
logger,
|
|
555
|
-
permissionMode: initialPermissionMode ||
|
|
556
|
-
(bypassPermissions ? "bypassPermissions" : undefined),
|
|
557
|
-
canUseTool: permissionCallback,
|
|
558
|
-
stream: true,
|
|
559
|
-
plugins: pluginDirs?.map((path) => ({ type: "local", path })),
|
|
560
|
-
additionalDirectories,
|
|
561
|
-
tools,
|
|
562
|
-
allowedTools,
|
|
563
|
-
disallowedTools,
|
|
564
|
-
workdir,
|
|
565
|
-
worktreeName: worktreeSession?.name,
|
|
566
|
-
isNewWorktree: worktreeSession?.isNew,
|
|
567
|
-
model,
|
|
568
|
-
mcpServers,
|
|
569
|
-
});
|
|
570
|
-
agentRef.current = agent;
|
|
571
|
-
// Inject worktree session into this agent's own DI container so system
|
|
572
|
-
// prompts and permission checks reflect the CLI -w worktree. This state is
|
|
573
|
-
// per-session, not process-global (see docs/specs/multi-agent/worktree.md FR-042).
|
|
574
|
-
if (worktreeSession) {
|
|
575
|
-
const session = {
|
|
576
|
-
originalCwd: originalCwd ?? worktreeSession.repoRoot,
|
|
577
|
-
worktreePath: worktreeSession.path,
|
|
578
|
-
worktreeBranch: worktreeSession.branch,
|
|
579
|
-
worktreeName: worktreeSession.name,
|
|
580
|
-
isNew: worktreeSession.isNew,
|
|
581
|
-
repoRoot: worktreeSession.repoRoot,
|
|
582
|
-
};
|
|
583
|
-
agent.setWorktreeSession(session);
|
|
584
|
-
}
|
|
585
|
-
// Get initial state — snapshot the SDK messages (never hold live
|
|
586
|
-
// references; see snapshotMessage)
|
|
587
|
-
setSessionId(agent.sessionId);
|
|
588
|
-
setMessages(agent.messages.map(snapshotMessage));
|
|
589
|
-
setIsLoading(agent.isLoading);
|
|
590
|
-
setLatestTotalTokens(extractLatestTotalTokens(agent.messages));
|
|
591
|
-
setIsCommandRunning(agent.isCommandRunning);
|
|
592
|
-
setIsCompacting(agent.isCompacting);
|
|
593
|
-
setPermissionModeState(agent.getPermissionMode());
|
|
594
|
-
setWorkingDirectory(agent.workingDirectory);
|
|
595
|
-
setCurrentModelState(agent.getModelConfig().model || "");
|
|
596
|
-
setConfiguredModels(agent.getConfiguredModels());
|
|
597
|
-
setMaxInputTokens(agent.getMaxInputTokens());
|
|
598
|
-
// Get initial MCP servers state
|
|
599
|
-
const initialMcpServers = agent.getMcpServers?.() || [];
|
|
600
|
-
setMcpServerStatuses(initialMcpServers);
|
|
601
|
-
// Get initial commands
|
|
602
|
-
const agentSlashCommands = agent.getSlashCommands?.() || [];
|
|
603
|
-
setSlashCommands(agentSlashCommands);
|
|
604
|
-
// Get initial agent definitions
|
|
605
|
-
const initialAgentDefinitions = agent.getSubagentConfigurations?.() || [];
|
|
606
|
-
setAgentDefinitions(initialAgentDefinitions);
|
|
607
|
-
}
|
|
608
|
-
catch (error) {
|
|
609
|
-
console.error("Failed to initialize AI manager:", error);
|
|
610
|
-
}
|
|
611
|
-
}, [
|
|
612
|
-
restoreSessionId,
|
|
613
|
-
continueLastSession,
|
|
614
|
-
bypassPermissions,
|
|
615
|
-
showConfirmation,
|
|
616
|
-
pluginDirs,
|
|
617
|
-
additionalDirectories,
|
|
618
|
-
tools,
|
|
619
|
-
allowedTools,
|
|
620
|
-
disallowedTools,
|
|
621
|
-
workdir,
|
|
622
|
-
worktreeSession,
|
|
623
|
-
originalCwd,
|
|
624
|
-
model,
|
|
625
|
-
initialPermissionMode,
|
|
626
|
-
throttledContentUpdate,
|
|
627
|
-
throttledReasoningUpdate,
|
|
628
|
-
throttledToolBlockUpdate,
|
|
629
|
-
mcpServers,
|
|
630
|
-
]);
|
|
631
|
-
// Recreate agent (e.g. after plugin install) — destroys current agent and reinitializes
|
|
632
|
-
const recreateAgent = useCallback(() => {
|
|
633
|
-
const currentSessionId = agentRef.current?.sessionId;
|
|
634
|
-
if (agentRef.current) {
|
|
635
|
-
try {
|
|
636
|
-
agentRef.current.destroy();
|
|
637
|
-
}
|
|
638
|
-
catch {
|
|
639
|
-
// Ignore destroy errors
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
agentRef.current = null;
|
|
643
|
-
setMessages([]);
|
|
644
|
-
setMcpServerStatuses([]);
|
|
645
|
-
setSlashCommands([]);
|
|
646
|
-
setAgentDefinitions([]);
|
|
647
|
-
setSessionId("");
|
|
648
|
-
setIsLoading(false);
|
|
649
|
-
setLatestTotalTokens(0);
|
|
650
|
-
setMaxInputTokens(200000);
|
|
651
|
-
setIsCommandRunning(false);
|
|
652
|
-
setIsCompacting(false);
|
|
653
|
-
if (currentSessionId) {
|
|
654
|
-
initializeAgent(currentSessionId);
|
|
655
|
-
}
|
|
656
|
-
}, [initializeAgent]);
|
|
657
|
-
// Run initial agent creation
|
|
658
|
-
useEffect(() => {
|
|
659
|
-
initializeAgent();
|
|
660
|
-
}, [initializeAgent]);
|
|
661
|
-
// Cleanup on unmount
|
|
662
|
-
useEffect(() => {
|
|
663
|
-
return () => {
|
|
664
|
-
throttledContentUpdate.cancel();
|
|
665
|
-
throttledReasoningUpdate.cancel();
|
|
666
|
-
throttledToolBlockUpdate.cancel();
|
|
667
|
-
if (agentRef.current) {
|
|
668
|
-
try {
|
|
669
|
-
// Display usage summary before cleanup
|
|
670
|
-
const usages = agentRef.current.usages;
|
|
671
|
-
const sessionFilePath = agentRef.current.sessionFilePath;
|
|
672
|
-
displayUsageSummary(usages, sessionFilePath);
|
|
673
|
-
}
|
|
674
|
-
catch {
|
|
675
|
-
// Silently ignore usage summary errors during cleanup
|
|
676
|
-
}
|
|
677
|
-
agentRef.current.destroy();
|
|
678
|
-
}
|
|
679
|
-
};
|
|
680
|
-
}, [
|
|
681
|
-
throttledContentUpdate,
|
|
682
|
-
throttledReasoningUpdate,
|
|
683
|
-
throttledToolBlockUpdate,
|
|
684
|
-
]);
|
|
685
|
-
// Trigger WorktreeRemove hook BEFORE agent destruction
|
|
686
|
-
const triggerWorktreeRemoveHook = useCallback(async (worktreePath) => {
|
|
687
|
-
await agentRef.current?.triggerWorktreeRemoveHook(worktreePath);
|
|
688
|
-
}, []);
|
|
689
|
-
// Send message function (including judgment logic)
|
|
690
|
-
const sendMessage = useCallback(async (content, images, longTextMap) => {
|
|
691
|
-
// Check if there's content to send (text content or image attachments)
|
|
692
|
-
const hasTextContent = content.trim();
|
|
693
|
-
const hasImageAttachments = images && images.length > 0;
|
|
694
|
-
if (!hasTextContent && !hasImageAttachments)
|
|
695
|
-
return;
|
|
696
|
-
try {
|
|
697
|
-
const expandedContent = longTextMap
|
|
698
|
-
? expandLongTextPlaceholders(content, longTextMap)
|
|
699
|
-
: content;
|
|
700
|
-
// Handle bash mode - check if it's a bash command (starts with ! and only one line)
|
|
701
|
-
if (expandedContent.startsWith("!") &&
|
|
702
|
-
!expandedContent.includes("\n") &&
|
|
703
|
-
!hasImageAttachments) {
|
|
704
|
-
const command = expandedContent.substring(1).trim();
|
|
705
|
-
if (command) {
|
|
706
|
-
await agentRef.current?.bang(command);
|
|
707
|
-
return;
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
try {
|
|
711
|
-
await agentRef.current?.sendMessage(expandedContent, images);
|
|
712
|
-
}
|
|
713
|
-
catch (error) {
|
|
714
|
-
console.error("Failed to send message:", error);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
catch (error) {
|
|
718
|
-
console.error("Failed to send message:", error);
|
|
719
|
-
}
|
|
720
|
-
}, []);
|
|
721
|
-
const askBtw = useCallback(async (question, abortSignal, onContent) => {
|
|
722
|
-
if (!agentRef.current) {
|
|
723
|
-
throw new Error("Agent not initialized");
|
|
724
|
-
}
|
|
725
|
-
return await agentRef.current.askBtw(question, abortSignal, onContent);
|
|
726
|
-
}, []);
|
|
727
|
-
const clearMessages = useCallback(async () => {
|
|
728
|
-
await agentRef.current?.clearMessages();
|
|
729
|
-
refreshMessages();
|
|
730
|
-
forceRemount();
|
|
731
|
-
}, [refreshMessages, forceRemount]);
|
|
732
|
-
const compact = useCallback(async (instructions) => {
|
|
733
|
-
await agentRef.current?.compact(instructions);
|
|
734
|
-
refreshMessages();
|
|
735
|
-
forceRemount();
|
|
736
|
-
}, [refreshMessages, forceRemount]);
|
|
737
|
-
// /add-dir: add a directory to the session Safe Zone (session-level, with
|
|
738
|
-
// optional --remember persistence). Bare command shows usage + current list.
|
|
739
|
-
const addDir = useCallback(async (args) => {
|
|
740
|
-
const agent = agentRef.current;
|
|
741
|
-
if (!agent)
|
|
742
|
-
return;
|
|
743
|
-
// Show the result as a UI-only assistant text message (same display path
|
|
744
|
-
// as error blocks; not persisted to session history).
|
|
745
|
-
const showResult = (content) => {
|
|
746
|
-
setMessages((prev) => [
|
|
747
|
-
...prev,
|
|
748
|
-
{
|
|
749
|
-
id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
|
750
|
-
role: "assistant",
|
|
751
|
-
timestamp: new Date().toISOString(),
|
|
752
|
-
blocks: [{ type: "text", content, stage: "end" }],
|
|
753
|
-
},
|
|
754
|
-
]);
|
|
755
|
-
};
|
|
756
|
-
const rawArgs = (args ?? "").trim();
|
|
757
|
-
if (!rawArgs) {
|
|
758
|
-
const dirs = agent.getAdditionalDirectories();
|
|
759
|
-
const list = dirs.length > 0
|
|
760
|
-
? dirs.map((dir) => ` - ${dir}`).join("\n")
|
|
761
|
-
: " (none)";
|
|
762
|
-
showResult(`Usage: /add-dir <path> [--remember]\n\nAdditional working directories:\n${list}`);
|
|
763
|
-
return;
|
|
764
|
-
}
|
|
765
|
-
const remember = rawArgs.includes("--remember");
|
|
766
|
-
const dir = rawArgs.replace("--remember", "").trim();
|
|
767
|
-
if (!dir) {
|
|
768
|
-
showResult("Usage: /add-dir <path> [--remember]");
|
|
769
|
-
return;
|
|
770
|
-
}
|
|
771
|
-
await agent.addAdditionalDirectory(dir, { remember });
|
|
772
|
-
showResult(`Added ${dir} to the session Safe Zone${remember ? " (remembered)" : ""}.`);
|
|
773
|
-
}, []);
|
|
774
|
-
// Unified interrupt method, interrupt both AI messages and command execution
|
|
775
|
-
const abortMessage = useCallback(() => {
|
|
776
|
-
agentRef.current?.abortMessage();
|
|
777
|
-
}, []);
|
|
778
|
-
const recallQueuedMessage = useCallback(() => {
|
|
779
|
-
return agentRef.current?.recallQueuedMessage() ?? null;
|
|
780
|
-
}, []);
|
|
781
|
-
const removeQueuedMessageById = useCallback((id) => {
|
|
782
|
-
return agentRef.current?.removeQueuedMessageById(id) ?? false;
|
|
783
|
-
}, []);
|
|
784
|
-
// Permission management methods
|
|
785
|
-
const setPermissionMode = useCallback((mode) => {
|
|
786
|
-
setPermissionModeState((prev) => {
|
|
787
|
-
if (prev === mode)
|
|
788
|
-
return prev;
|
|
789
|
-
if (agentRef.current && agentRef.current.getPermissionMode() !== mode) {
|
|
790
|
-
agentRef.current.setPermissionMode(mode);
|
|
791
|
-
}
|
|
792
|
-
return mode;
|
|
793
|
-
});
|
|
794
|
-
}, []);
|
|
795
|
-
// MCP management methods - delegate to Agent
|
|
796
|
-
const connectMcpServer = useCallback(async (serverName) => {
|
|
797
|
-
return (await agentRef.current?.connectMcpServer(serverName)) ?? false;
|
|
798
|
-
}, []);
|
|
799
|
-
const disconnectMcpServer = useCallback(async (serverName) => {
|
|
800
|
-
return (await agentRef.current?.disconnectMcpServer(serverName)) ?? false;
|
|
801
|
-
}, []);
|
|
802
|
-
// Background task management methods - delegate to Agent
|
|
803
|
-
const getBackgroundTaskOutput = useCallback((taskId) => {
|
|
804
|
-
if (!agentRef.current)
|
|
805
|
-
return null;
|
|
806
|
-
return agentRef.current.getBackgroundTaskOutput(taskId);
|
|
807
|
-
}, []);
|
|
808
|
-
const stopBackgroundTask = useCallback((taskId) => {
|
|
809
|
-
if (!agentRef.current)
|
|
810
|
-
return false;
|
|
811
|
-
return agentRef.current.stopBackgroundTask(taskId);
|
|
812
|
-
}, []);
|
|
813
|
-
const stopWorkflowRun = useCallback((runId) => {
|
|
814
|
-
agentRef.current?.stopWorkflowRun(runId);
|
|
815
|
-
}, []);
|
|
816
|
-
const hasSlashCommand = useCallback((commandId) => {
|
|
817
|
-
if (!agentRef.current)
|
|
818
|
-
return false;
|
|
819
|
-
return agentRef.current.hasSlashCommand(commandId);
|
|
820
|
-
}, []);
|
|
821
|
-
// Queue processing helper
|
|
822
|
-
const processNextConfirmation = useCallback(() => {
|
|
823
|
-
if (confirmationQueue.length > 0 && !isConfirmationVisible) {
|
|
824
|
-
const next = confirmationQueue[0];
|
|
825
|
-
setCurrentConfirmation(next);
|
|
826
|
-
setConfirmingTool({
|
|
827
|
-
name: next.toolName,
|
|
828
|
-
input: next.toolInput,
|
|
829
|
-
suggestedPrefix: next.suggestedPrefix,
|
|
830
|
-
hidePersistentOption: next.hidePersistentOption,
|
|
831
|
-
planContent: next.planContent,
|
|
832
|
-
permissionMode: next.permissionMode,
|
|
833
|
-
warning: next.warning,
|
|
834
|
-
});
|
|
835
|
-
setIsConfirmationVisible(true);
|
|
836
|
-
setConfirmationQueue((prev) => prev.slice(1));
|
|
837
|
-
}
|
|
838
|
-
}, [confirmationQueue, isConfirmationVisible]);
|
|
839
|
-
// Process queue when queue changes or confirmation is hidden
|
|
840
|
-
useEffect(() => {
|
|
841
|
-
processNextConfirmation();
|
|
842
|
-
}, [processNextConfirmation]);
|
|
843
|
-
const hideConfirmation = useCallback(() => {
|
|
844
|
-
setIsConfirmationVisible(false);
|
|
845
|
-
setConfirmingTool(undefined);
|
|
846
|
-
setCurrentConfirmation(null);
|
|
847
|
-
}, []);
|
|
848
|
-
const handleConfirmationDecision = useCallback((decision) => {
|
|
849
|
-
if (currentConfirmation) {
|
|
850
|
-
currentConfirmation.resolver(decision);
|
|
851
|
-
}
|
|
852
|
-
hideConfirmation();
|
|
853
|
-
}, [currentConfirmation, hideConfirmation]);
|
|
854
|
-
const handleConfirmationCancel = useCallback(() => {
|
|
855
|
-
if (currentConfirmation) {
|
|
856
|
-
currentConfirmation.reject();
|
|
857
|
-
}
|
|
858
|
-
agentRef.current?.abortMessage();
|
|
859
|
-
hideConfirmation();
|
|
860
|
-
}, [currentConfirmation, hideConfirmation]);
|
|
861
|
-
const backgroundCurrentTask = useCallback(() => {
|
|
862
|
-
agentRef.current?.backgroundCurrentTask();
|
|
863
|
-
}, []);
|
|
864
|
-
const handleRewindSelect = useCallback(async (index) => {
|
|
865
|
-
if (agentRef.current) {
|
|
866
|
-
try {
|
|
867
|
-
await agentRef.current.truncateHistory(index);
|
|
868
|
-
refreshMessages();
|
|
869
|
-
forceRemount();
|
|
870
|
-
}
|
|
871
|
-
catch (error) {
|
|
872
|
-
logger.error("Failed to rewind:", error);
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
}, [forceRemount, refreshMessages]);
|
|
876
|
-
const getFullMessageThread = useCallback(async () => {
|
|
877
|
-
if (agentRef.current) {
|
|
878
|
-
return await agentRef.current.getFullMessageThread();
|
|
879
|
-
}
|
|
880
|
-
return { messages: [], sessionIds: [] };
|
|
881
|
-
}, []);
|
|
882
|
-
const getGatewayConfig = useCallback(() => {
|
|
883
|
-
if (!agentRef.current) {
|
|
884
|
-
return { baseURL: "" };
|
|
885
|
-
}
|
|
886
|
-
return agentRef.current.getGatewayConfig();
|
|
887
|
-
}, []);
|
|
888
|
-
const getModelConfig = useCallback(() => {
|
|
889
|
-
if (!agentRef.current) {
|
|
890
|
-
return { model: "", fastModel: "" };
|
|
891
|
-
}
|
|
892
|
-
return agentRef.current.getModelConfig();
|
|
893
|
-
}, []);
|
|
894
|
-
const getConfiguredModels = useCallback(() => {
|
|
895
|
-
if (!agentRef.current) {
|
|
896
|
-
return [];
|
|
897
|
-
}
|
|
898
|
-
return agentRef.current.getConfiguredModels();
|
|
899
|
-
}, []);
|
|
900
|
-
const setModel = useCallback((model) => {
|
|
901
|
-
if (agentRef.current) {
|
|
902
|
-
agentRef.current.setModel(model);
|
|
903
|
-
setCurrentModelState(model);
|
|
904
|
-
}
|
|
905
|
-
}, []);
|
|
906
|
-
// Listen for Ctrl+O hotkey to toggle collapse/expand state and ESC to cancel confirmation
|
|
907
|
-
useInput((input, key) => {
|
|
908
|
-
if (key.ctrl && input === "o") {
|
|
909
|
-
// Use ref to get the current value to avoid stale closure
|
|
910
|
-
const nextExpanded = !isExpandedRef.current;
|
|
911
|
-
setIsExpanded(nextExpanded);
|
|
912
|
-
isExpandedRef.current = nextExpanded;
|
|
913
|
-
if (nextExpanded) {
|
|
914
|
-
// Transitioning to EXPANDED: Freeze the current view
|
|
915
|
-
// Incremental updates are skipped while expanded (isExpandedRef guard)
|
|
916
|
-
}
|
|
917
|
-
else {
|
|
918
|
-
// Transitioning to COLLAPSED: Restore from agent's actual state
|
|
919
|
-
refreshMessages();
|
|
920
|
-
}
|
|
921
|
-
// Force remount to ensure Static items re-render
|
|
922
|
-
forceRemount();
|
|
923
|
-
}
|
|
924
|
-
if (key.ctrl && input === "t") {
|
|
925
|
-
setIsTaskListVisible((prev) => !prev);
|
|
926
|
-
}
|
|
927
|
-
// Handle ESC key to cancel confirmation
|
|
928
|
-
if (key.escape && isConfirmationVisible) {
|
|
929
|
-
handleConfirmationCancel();
|
|
930
|
-
}
|
|
931
|
-
});
|
|
932
|
-
const contextValue = {
|
|
933
|
-
messages,
|
|
934
|
-
isLoading,
|
|
935
|
-
isCommandRunning,
|
|
936
|
-
isExpanded,
|
|
937
|
-
isTaskListVisible,
|
|
938
|
-
setIsTaskListVisible,
|
|
939
|
-
isBtwActive,
|
|
940
|
-
setIsBtwActive,
|
|
941
|
-
queuedMessages,
|
|
942
|
-
sessionId,
|
|
943
|
-
sendMessage,
|
|
944
|
-
askBtw,
|
|
945
|
-
clearMessages,
|
|
946
|
-
compact,
|
|
947
|
-
addDir,
|
|
948
|
-
abortMessage,
|
|
949
|
-
recallQueuedMessage,
|
|
950
|
-
removeQueuedMessageById,
|
|
951
|
-
latestTotalTokens,
|
|
952
|
-
maxInputTokens,
|
|
953
|
-
currentModel,
|
|
954
|
-
configuredModels,
|
|
955
|
-
getConfiguredModels,
|
|
956
|
-
setModel,
|
|
957
|
-
isCompacting,
|
|
958
|
-
mcpServers: mcpServerStatuses,
|
|
959
|
-
connectMcpServer,
|
|
960
|
-
disconnectMcpServer,
|
|
961
|
-
backgroundTasks,
|
|
962
|
-
workflowRuns,
|
|
963
|
-
stopWorkflowRun,
|
|
964
|
-
tasks,
|
|
965
|
-
getBackgroundTaskOutput,
|
|
966
|
-
stopBackgroundTask,
|
|
967
|
-
slashCommands,
|
|
968
|
-
hasSlashCommand,
|
|
969
|
-
agentDefinitions,
|
|
970
|
-
permissionMode,
|
|
971
|
-
setPermissionMode,
|
|
972
|
-
isConfirmationVisible,
|
|
973
|
-
hasPendingConfirmations: confirmationQueue.length > 0,
|
|
974
|
-
confirmingTool,
|
|
975
|
-
showConfirmation,
|
|
976
|
-
hideConfirmation,
|
|
977
|
-
handleConfirmationDecision,
|
|
978
|
-
handleConfirmationCancel,
|
|
979
|
-
backgroundCurrentTask,
|
|
980
|
-
remountKey,
|
|
981
|
-
forceRemount,
|
|
982
|
-
handleRewindSelect,
|
|
983
|
-
getFullMessageThread,
|
|
984
|
-
getGatewayConfig,
|
|
985
|
-
getModelConfig,
|
|
986
|
-
workingDirectory,
|
|
987
|
-
version,
|
|
988
|
-
workdir,
|
|
989
|
-
recreateAgent,
|
|
990
|
-
triggerWorktreeRemoveHook,
|
|
991
|
-
};
|
|
992
|
-
return (_jsx(ChatContext.Provider, { value: contextValue, children: children }));
|
|
993
|
-
};
|