@robota-sdk/agent-transport-tui 3.0.0-beta.63
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/LICENSE +21 -0
- package/dist/node/index.cjs +4041 -0
- package/dist/node/index.d.cts +62 -0
- package/dist/node/index.d.ts +62 -0
- package/dist/node/index.js +4008 -0
- package/package.json +62 -0
|
@@ -0,0 +1,4041 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
TuiTransport: () => TuiTransport
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// src/render.tsx
|
|
38
|
+
var import_ink25 = require("ink");
|
|
39
|
+
|
|
40
|
+
// src/App.tsx
|
|
41
|
+
var import_react23 = require("react");
|
|
42
|
+
var import_ink24 = require("ink");
|
|
43
|
+
var import_agent_sdk4 = require("@robota-sdk/agent-sdk");
|
|
44
|
+
var import_agent_core10 = require("@robota-sdk/agent-core");
|
|
45
|
+
|
|
46
|
+
// src/hooks/useInteractiveSession.ts
|
|
47
|
+
var import_react2 = require("react");
|
|
48
|
+
var import_agent_sdk = require("@robota-sdk/agent-sdk");
|
|
49
|
+
var import_agent_core2 = require("@robota-sdk/agent-core");
|
|
50
|
+
|
|
51
|
+
// src/tui-state-manager.ts
|
|
52
|
+
var MAX_RENDERED_MESSAGES = 100;
|
|
53
|
+
var STREAMING_DEBOUNCE_MS = 300;
|
|
54
|
+
function createDebouncedNotify(notify, ms) {
|
|
55
|
+
let timer = null;
|
|
56
|
+
return {
|
|
57
|
+
schedule() {
|
|
58
|
+
if (!timer) {
|
|
59
|
+
timer = setTimeout(() => {
|
|
60
|
+
timer = null;
|
|
61
|
+
notify();
|
|
62
|
+
}, ms);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
flush() {
|
|
66
|
+
if (timer) {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
timer = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
var TuiStateManager = class {
|
|
74
|
+
// ── Rendering state ───────────────────────────────────────────
|
|
75
|
+
history = [];
|
|
76
|
+
streamingText = "";
|
|
77
|
+
activeTools = [];
|
|
78
|
+
isThinking = false;
|
|
79
|
+
isAborting = false;
|
|
80
|
+
pendingPrompt = null;
|
|
81
|
+
contextState = { percentage: 0, usedTokens: 0, maxTokens: 0 };
|
|
82
|
+
executionWorkspaceSnapshot = null;
|
|
83
|
+
selectedExecutionEntryId;
|
|
84
|
+
/** Called after any state change. React hook sets this to trigger re-render. */
|
|
85
|
+
onChange = null;
|
|
86
|
+
// ── Internal ──────────────────────────────────────────────────
|
|
87
|
+
streamBuf = "";
|
|
88
|
+
debouncedStreamNotify = createDebouncedNotify(() => this.notify(), STREAMING_DEBOUNCE_MS);
|
|
89
|
+
notify() {
|
|
90
|
+
this.onChange?.();
|
|
91
|
+
}
|
|
92
|
+
// ── Event handlers (InteractiveSession → state) ───────────────
|
|
93
|
+
onTextDelta = (delta) => {
|
|
94
|
+
this.streamBuf += delta;
|
|
95
|
+
this.streamingText = this.streamBuf;
|
|
96
|
+
this.debouncedStreamNotify.schedule();
|
|
97
|
+
};
|
|
98
|
+
onToolStart = (state) => {
|
|
99
|
+
this.activeTools = [...this.activeTools, state];
|
|
100
|
+
this.notify();
|
|
101
|
+
};
|
|
102
|
+
onToolEnd = (state) => {
|
|
103
|
+
const idx = this.activeTools.findLastIndex((t) => t.toolName === state.toolName && t.isRunning);
|
|
104
|
+
if (idx !== -1) {
|
|
105
|
+
const updated = [...this.activeTools];
|
|
106
|
+
updated[idx] = state;
|
|
107
|
+
this.activeTools = updated;
|
|
108
|
+
}
|
|
109
|
+
this.notify();
|
|
110
|
+
};
|
|
111
|
+
onThinking = (thinking) => {
|
|
112
|
+
this.isThinking = thinking;
|
|
113
|
+
if (thinking) {
|
|
114
|
+
this.debouncedStreamNotify.flush();
|
|
115
|
+
this.streamBuf = "";
|
|
116
|
+
this.streamingText = "";
|
|
117
|
+
this.activeTools = [];
|
|
118
|
+
} else {
|
|
119
|
+
this.isAborting = false;
|
|
120
|
+
}
|
|
121
|
+
this.notify();
|
|
122
|
+
};
|
|
123
|
+
onComplete = (result) => {
|
|
124
|
+
this.debouncedStreamNotify.flush();
|
|
125
|
+
this.streamBuf = "";
|
|
126
|
+
this.streamingText = "";
|
|
127
|
+
this.activeTools = [];
|
|
128
|
+
this.contextState = {
|
|
129
|
+
percentage: result.contextState.usedPercentage,
|
|
130
|
+
usedTokens: result.contextState.usedTokens,
|
|
131
|
+
maxTokens: result.contextState.maxTokens
|
|
132
|
+
};
|
|
133
|
+
this.notify();
|
|
134
|
+
};
|
|
135
|
+
onInterrupted = () => {
|
|
136
|
+
this.debouncedStreamNotify.flush();
|
|
137
|
+
this.streamBuf = "";
|
|
138
|
+
this.streamingText = "";
|
|
139
|
+
this.activeTools = [];
|
|
140
|
+
this.notify();
|
|
141
|
+
};
|
|
142
|
+
onError = () => {
|
|
143
|
+
this.debouncedStreamNotify.flush();
|
|
144
|
+
this.streamBuf = "";
|
|
145
|
+
this.streamingText = "";
|
|
146
|
+
this.activeTools = [];
|
|
147
|
+
this.notify();
|
|
148
|
+
};
|
|
149
|
+
onContextUpdate = (state) => {
|
|
150
|
+
this.setContextState({
|
|
151
|
+
percentage: state.usedPercentage,
|
|
152
|
+
usedTokens: state.usedTokens,
|
|
153
|
+
maxTokens: state.maxTokens
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
// ── State updates from external sources ───────────────────────
|
|
157
|
+
/** Sync history from InteractiveSession */
|
|
158
|
+
syncHistory(entries) {
|
|
159
|
+
if (entries.length === 0) return;
|
|
160
|
+
this.history = entries.length > MAX_RENDERED_MESSAGES ? entries.slice(-MAX_RENDERED_MESSAGES) : [...entries];
|
|
161
|
+
this.notify();
|
|
162
|
+
}
|
|
163
|
+
/** Add a single history entry */
|
|
164
|
+
addEntry(entry) {
|
|
165
|
+
const updated = [...this.history, entry];
|
|
166
|
+
this.history = updated.length > MAX_RENDERED_MESSAGES ? updated.slice(-MAX_RENDERED_MESSAGES) : updated;
|
|
167
|
+
this.notify();
|
|
168
|
+
}
|
|
169
|
+
clearHistory() {
|
|
170
|
+
this.history = [];
|
|
171
|
+
this.debouncedStreamNotify.flush();
|
|
172
|
+
this.streamBuf = "";
|
|
173
|
+
this.streamingText = "";
|
|
174
|
+
this.activeTools = [];
|
|
175
|
+
this.notify();
|
|
176
|
+
}
|
|
177
|
+
/** Update pending prompt state */
|
|
178
|
+
setPendingPrompt(prompt) {
|
|
179
|
+
this.pendingPrompt = prompt;
|
|
180
|
+
this.notify();
|
|
181
|
+
}
|
|
182
|
+
/** Set aborting flag */
|
|
183
|
+
setAborting(aborting) {
|
|
184
|
+
this.isAborting = aborting;
|
|
185
|
+
this.notify();
|
|
186
|
+
}
|
|
187
|
+
/** Update context state */
|
|
188
|
+
setContextState(state) {
|
|
189
|
+
this.contextState = state;
|
|
190
|
+
this.notify();
|
|
191
|
+
}
|
|
192
|
+
syncExecutionWorkspaceSnapshot(snapshot) {
|
|
193
|
+
const currentSelection = this.selectedExecutionEntryId;
|
|
194
|
+
const hasCurrentSelection = currentSelection !== void 0 && snapshot.entries.some((entry) => entry.id === currentSelection);
|
|
195
|
+
const selectedExecutionEntryId = hasCurrentSelection ? currentSelection : snapshot.selectedEntryId ?? snapshot.entries[0]?.id;
|
|
196
|
+
this.executionWorkspaceSnapshot = {
|
|
197
|
+
...snapshot,
|
|
198
|
+
...selectedExecutionEntryId ? { selectedEntryId: selectedExecutionEntryId } : {}
|
|
199
|
+
};
|
|
200
|
+
this.selectedExecutionEntryId = selectedExecutionEntryId;
|
|
201
|
+
this.notify();
|
|
202
|
+
}
|
|
203
|
+
selectExecutionWorkspaceEntry(entryId) {
|
|
204
|
+
if (!this.executionWorkspaceSnapshot?.entries.some((entry) => entry.id === entryId)) return;
|
|
205
|
+
this.selectedExecutionEntryId = entryId;
|
|
206
|
+
this.executionWorkspaceSnapshot = {
|
|
207
|
+
...this.executionWorkspaceSnapshot,
|
|
208
|
+
selectedEntryId: entryId
|
|
209
|
+
};
|
|
210
|
+
this.notify();
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// src/hooks/useSlashRouting.ts
|
|
215
|
+
var import_react = require("react");
|
|
216
|
+
var import_agent_core = require("@robota-sdk/agent-core");
|
|
217
|
+
function useSlashRouting(interactiveSession, registry, manager, commandEffectQueue, reloadPluginCommandSource) {
|
|
218
|
+
return (0, import_react.useCallback)(
|
|
219
|
+
async (input) => {
|
|
220
|
+
if (!input.startsWith("/")) {
|
|
221
|
+
await interactiveSession.submit(input);
|
|
222
|
+
manager.setPendingPrompt(interactiveSession.getPendingPrompt());
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const parts = input.slice(1).split(/\s+/);
|
|
226
|
+
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
227
|
+
const args = parts.slice(1).join(" ");
|
|
228
|
+
const result = await interactiveSession.executeCommand(cmd, args);
|
|
229
|
+
if (result) {
|
|
230
|
+
if (result.effects?.some((effect) => effect.type === "session-execution-started")) {
|
|
231
|
+
manager.setPendingPrompt(interactiveSession.getPendingPrompt());
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
applySystemCommandResult(
|
|
235
|
+
result,
|
|
236
|
+
interactiveSession,
|
|
237
|
+
registry,
|
|
238
|
+
manager,
|
|
239
|
+
commandEffectQueue,
|
|
240
|
+
reloadPluginCommandSource
|
|
241
|
+
);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
manager.addEntry(
|
|
245
|
+
(0, import_agent_core.messageToHistoryEntry)(
|
|
246
|
+
(0, import_agent_core.createSystemMessage)(`Unknown command "/${cmd}". Type /help for help.`)
|
|
247
|
+
)
|
|
248
|
+
);
|
|
249
|
+
},
|
|
250
|
+
[interactiveSession, registry, manager, commandEffectQueue, reloadPluginCommandSource]
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
function applySystemCommandResult(result, interactiveSession, registry, manager, commandEffectQueue, reloadPluginCommandSource) {
|
|
254
|
+
const pendingEffects = applyImmediateCommandEffects(
|
|
255
|
+
result.effects,
|
|
256
|
+
registry,
|
|
257
|
+
manager,
|
|
258
|
+
reloadPluginCommandSource
|
|
259
|
+
);
|
|
260
|
+
manager.addEntry((0, import_agent_core.messageToHistoryEntry)((0, import_agent_core.createSystemMessage)(result.message)));
|
|
261
|
+
if (result.interaction !== void 0) {
|
|
262
|
+
commandEffectQueue.enqueueInteraction(result.interaction);
|
|
263
|
+
}
|
|
264
|
+
if (pendingEffects.length > 0) {
|
|
265
|
+
commandEffectQueue.enqueueEffects(pendingEffects);
|
|
266
|
+
}
|
|
267
|
+
if (interactiveSession.isInitialized) {
|
|
268
|
+
const ctx = interactiveSession.getContextState();
|
|
269
|
+
manager.setContextState({
|
|
270
|
+
percentage: ctx.usedPercentage,
|
|
271
|
+
usedTokens: ctx.usedTokens,
|
|
272
|
+
maxTokens: ctx.maxTokens
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function applyImmediateCommandEffects(effects, registry, manager, reloadPluginCommandSource) {
|
|
277
|
+
if (effects === void 0 || effects.length === 0) return [];
|
|
278
|
+
const pendingEffects = [];
|
|
279
|
+
for (const effect of effects) {
|
|
280
|
+
if (effect.type === "conversation-history-cleared") {
|
|
281
|
+
manager.clearHistory();
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (effect.type === "plugin-registry-reload-requested") {
|
|
285
|
+
reloadPluginCommandSource?.(registry);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
pendingEffects.push(effect);
|
|
289
|
+
}
|
|
290
|
+
return pendingEffects;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/hooks/command-effect-queue.ts
|
|
294
|
+
var CommandEffectQueue = class {
|
|
295
|
+
queue = [];
|
|
296
|
+
enqueueInteraction(interaction) {
|
|
297
|
+
this.queue.push({ type: "interaction", interaction });
|
|
298
|
+
}
|
|
299
|
+
enqueueEffects(effects) {
|
|
300
|
+
if (effects.length === 0) return;
|
|
301
|
+
this.queue.push({ type: "effects", effects: [...effects] });
|
|
302
|
+
}
|
|
303
|
+
drain() {
|
|
304
|
+
return this.queue.shift();
|
|
305
|
+
}
|
|
306
|
+
clear() {
|
|
307
|
+
this.queue.length = 0;
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// src/hooks/useInteractiveSession.ts
|
|
312
|
+
var SESSION_INIT_POLL_MS = 200;
|
|
313
|
+
function applyCompactEventToManager(interactiveSession, manager) {
|
|
314
|
+
manager.syncHistory(interactiveSession.getFullHistory());
|
|
315
|
+
}
|
|
316
|
+
function applySkillActivationEventToManager(interactiveSession, manager) {
|
|
317
|
+
manager.syncHistory(interactiveSession.getFullHistory());
|
|
318
|
+
}
|
|
319
|
+
function syncExecutionWorkspaceFromSession(interactiveSession, manager) {
|
|
320
|
+
try {
|
|
321
|
+
manager.syncExecutionWorkspaceSnapshot(
|
|
322
|
+
interactiveSession.getExecutionWorkspaceSnapshot({
|
|
323
|
+
selectedEntryId: manager.selectedExecutionEntryId
|
|
324
|
+
})
|
|
325
|
+
);
|
|
326
|
+
} catch {
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function initializeSession(props, permissionHandler) {
|
|
330
|
+
const interactiveSession = new import_agent_sdk.InteractiveSession({
|
|
331
|
+
cwd: props.cwd,
|
|
332
|
+
provider: props.provider,
|
|
333
|
+
permissionMode: props.permissionMode,
|
|
334
|
+
maxTurns: props.maxTurns,
|
|
335
|
+
permissionHandler,
|
|
336
|
+
sessionStore: props.sessionStore,
|
|
337
|
+
resumeSessionId: props.resumeSessionId,
|
|
338
|
+
forkSession: props.forkSession,
|
|
339
|
+
sessionName: props.sessionName,
|
|
340
|
+
backgroundTaskRunners: props.backgroundTaskRunners,
|
|
341
|
+
subagentRunnerFactory: props.subagentRunnerFactory,
|
|
342
|
+
commandModules: props.commandModules,
|
|
343
|
+
commandHostAdapters: props.commandHostAdapters,
|
|
344
|
+
language: props.language
|
|
345
|
+
});
|
|
346
|
+
const registry = new import_agent_sdk.CommandRegistry();
|
|
347
|
+
for (const module2 of props.commandModules ?? []) {
|
|
348
|
+
registry.addModule(module2);
|
|
349
|
+
}
|
|
350
|
+
props.reloadPluginCommandSource?.(registry);
|
|
351
|
+
const manager = new TuiStateManager();
|
|
352
|
+
const commandEffectQueue = new CommandEffectQueue();
|
|
353
|
+
return { interactiveSession, registry, manager, commandEffectQueue };
|
|
354
|
+
}
|
|
355
|
+
function useInteractiveSession(props) {
|
|
356
|
+
const [, forceRender] = (0, import_react2.useState)(0);
|
|
357
|
+
const [permissionRequest, setPermissionRequest] = (0, import_react2.useState)(null);
|
|
358
|
+
const [isShuttingDown, setIsShuttingDown] = (0, import_react2.useState)(false);
|
|
359
|
+
const permissionQueueRef = (0, import_react2.useRef)([]);
|
|
360
|
+
const processingRef = (0, import_react2.useRef)(false);
|
|
361
|
+
const processNextPermission = (0, import_react2.useCallback)(() => {
|
|
362
|
+
if (processingRef.current) return;
|
|
363
|
+
const next = permissionQueueRef.current[0];
|
|
364
|
+
if (!next) {
|
|
365
|
+
setPermissionRequest(null);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
processingRef.current = true;
|
|
369
|
+
setPermissionRequest({
|
|
370
|
+
toolName: next.toolName,
|
|
371
|
+
toolArgs: next.toolArgs,
|
|
372
|
+
resolve: (result) => {
|
|
373
|
+
permissionQueueRef.current.shift();
|
|
374
|
+
processingRef.current = false;
|
|
375
|
+
setPermissionRequest(null);
|
|
376
|
+
next.resolve(result);
|
|
377
|
+
setTimeout(() => processNextPermission(), 0);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
}, []);
|
|
381
|
+
const permissionHandler = (0, import_react2.useCallback)(
|
|
382
|
+
(toolName, toolArgs) => new Promise((resolve) => {
|
|
383
|
+
permissionQueueRef.current.push({ toolName, toolArgs, resolve });
|
|
384
|
+
processNextPermission();
|
|
385
|
+
}),
|
|
386
|
+
[processNextPermission]
|
|
387
|
+
);
|
|
388
|
+
const [initState] = (0, import_react2.useState)(() => initializeSession(props, permissionHandler));
|
|
389
|
+
const { interactiveSession, registry, manager, commandEffectQueue } = initState;
|
|
390
|
+
manager.onChange = () => forceRender((n) => n + 1);
|
|
391
|
+
if (manager.history.length === 0) {
|
|
392
|
+
const restored = interactiveSession.getFullHistory();
|
|
393
|
+
if (restored.length > 0) {
|
|
394
|
+
manager.syncHistory(restored);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
(0, import_react2.useEffect)(() => {
|
|
398
|
+
if (!props.transportRegistry) return;
|
|
399
|
+
const reg = props.transportRegistry;
|
|
400
|
+
reg.startAll(interactiveSession).catch(() => void 0);
|
|
401
|
+
return () => {
|
|
402
|
+
reg.stopAll().catch(() => void 0);
|
|
403
|
+
};
|
|
404
|
+
}, [interactiveSession, props.transportRegistry]);
|
|
405
|
+
(0, import_react2.useEffect)(() => {
|
|
406
|
+
const onCompact = () => applyCompactEventToManager(interactiveSession, manager);
|
|
407
|
+
const onSkillActivation = () => applySkillActivationEventToManager(interactiveSession, manager);
|
|
408
|
+
const onExecutionWorkspaceEvent = (event) => manager.syncExecutionWorkspaceSnapshot(event.snapshot);
|
|
409
|
+
interactiveSession.on("text_delta", manager.onTextDelta);
|
|
410
|
+
interactiveSession.on("tool_start", manager.onToolStart);
|
|
411
|
+
interactiveSession.on("tool_end", manager.onToolEnd);
|
|
412
|
+
interactiveSession.on("thinking", manager.onThinking);
|
|
413
|
+
interactiveSession.on("complete", manager.onComplete);
|
|
414
|
+
interactiveSession.on("interrupted", manager.onInterrupted);
|
|
415
|
+
interactiveSession.on("error", manager.onError);
|
|
416
|
+
interactiveSession.on("context_update", manager.onContextUpdate);
|
|
417
|
+
interactiveSession.on("compact", onCompact);
|
|
418
|
+
interactiveSession.on("skill_activation", onSkillActivation);
|
|
419
|
+
interactiveSession.on("execution_workspace_event", onExecutionWorkspaceEvent);
|
|
420
|
+
const initCheck = setInterval(() => {
|
|
421
|
+
try {
|
|
422
|
+
const ctx = interactiveSession.getContextState();
|
|
423
|
+
manager.setContextState({
|
|
424
|
+
percentage: ctx.usedPercentage,
|
|
425
|
+
usedTokens: ctx.usedTokens,
|
|
426
|
+
maxTokens: ctx.maxTokens
|
|
427
|
+
});
|
|
428
|
+
const restored = interactiveSession.getFullHistory();
|
|
429
|
+
if (restored.length > 0) {
|
|
430
|
+
manager.syncHistory(restored);
|
|
431
|
+
}
|
|
432
|
+
syncExecutionWorkspaceFromSession(interactiveSession, manager);
|
|
433
|
+
clearInterval(initCheck);
|
|
434
|
+
} catch {
|
|
435
|
+
}
|
|
436
|
+
}, SESSION_INIT_POLL_MS);
|
|
437
|
+
return () => {
|
|
438
|
+
clearInterval(initCheck);
|
|
439
|
+
interactiveSession.off("text_delta", manager.onTextDelta);
|
|
440
|
+
interactiveSession.off("tool_start", manager.onToolStart);
|
|
441
|
+
interactiveSession.off("tool_end", manager.onToolEnd);
|
|
442
|
+
interactiveSession.off("thinking", manager.onThinking);
|
|
443
|
+
interactiveSession.off("complete", manager.onComplete);
|
|
444
|
+
interactiveSession.off("interrupted", manager.onInterrupted);
|
|
445
|
+
interactiveSession.off("error", manager.onError);
|
|
446
|
+
interactiveSession.off("context_update", manager.onContextUpdate);
|
|
447
|
+
interactiveSession.off("compact", onCompact);
|
|
448
|
+
interactiveSession.off("skill_activation", onSkillActivation);
|
|
449
|
+
interactiveSession.off("execution_workspace_event", onExecutionWorkspaceEvent);
|
|
450
|
+
};
|
|
451
|
+
}, [interactiveSession, manager]);
|
|
452
|
+
(0, import_react2.useEffect)(() => {
|
|
453
|
+
manager.syncHistory(interactiveSession.getFullHistory());
|
|
454
|
+
syncExecutionWorkspaceFromSession(interactiveSession, manager);
|
|
455
|
+
if (!manager.isThinking) {
|
|
456
|
+
manager.setPendingPrompt(interactiveSession.getPendingPrompt());
|
|
457
|
+
}
|
|
458
|
+
}, [manager.isThinking, interactiveSession, manager]);
|
|
459
|
+
const handleSubmit = useSlashRouting(
|
|
460
|
+
interactiveSession,
|
|
461
|
+
registry,
|
|
462
|
+
manager,
|
|
463
|
+
commandEffectQueue,
|
|
464
|
+
props.reloadPluginCommandSource
|
|
465
|
+
);
|
|
466
|
+
const handleAbort = (0, import_react2.useCallback)(() => {
|
|
467
|
+
manager.setAborting(true);
|
|
468
|
+
interactiveSession.abort();
|
|
469
|
+
}, [interactiveSession, manager]);
|
|
470
|
+
const handleCancelQueue = (0, import_react2.useCallback)(() => {
|
|
471
|
+
interactiveSession.cancelQueue();
|
|
472
|
+
manager.setPendingPrompt(null);
|
|
473
|
+
}, [interactiveSession, manager]);
|
|
474
|
+
const handleShutdown = (0, import_react2.useCallback)(
|
|
475
|
+
async (reason = "prompt_input_exit") => {
|
|
476
|
+
if (isShuttingDown) return;
|
|
477
|
+
setIsShuttingDown(true);
|
|
478
|
+
manager.addEntry((0, import_agent_core2.messageToHistoryEntry)((0, import_agent_core2.createSystemMessage)("Shutting down...")));
|
|
479
|
+
await interactiveSession.shutdown({ reason, message: "CLI shutdown" });
|
|
480
|
+
},
|
|
481
|
+
[interactiveSession, manager, isShuttingDown]
|
|
482
|
+
);
|
|
483
|
+
const selectExecutionWorkspaceEntry = (0, import_react2.useCallback)(
|
|
484
|
+
(entryId) => manager.selectExecutionWorkspaceEntry(entryId),
|
|
485
|
+
[manager]
|
|
486
|
+
);
|
|
487
|
+
const readExecutionWorkspaceDetail = (0, import_react2.useCallback)(
|
|
488
|
+
(entryId) => interactiveSession.readExecutionWorkspaceDetail(entryId),
|
|
489
|
+
[interactiveSession]
|
|
490
|
+
);
|
|
491
|
+
return {
|
|
492
|
+
interactiveSession,
|
|
493
|
+
registry,
|
|
494
|
+
commandEffectQueue,
|
|
495
|
+
history: manager.history,
|
|
496
|
+
addEntry: (entry) => manager.addEntry(entry),
|
|
497
|
+
streamingText: manager.streamingText,
|
|
498
|
+
activeTools: manager.activeTools,
|
|
499
|
+
isThinking: manager.isThinking,
|
|
500
|
+
isAborting: manager.isAborting,
|
|
501
|
+
isShuttingDown,
|
|
502
|
+
pendingPrompt: manager.pendingPrompt,
|
|
503
|
+
executionWorkspaceSnapshot: manager.executionWorkspaceSnapshot,
|
|
504
|
+
selectedExecutionEntryId: manager.selectedExecutionEntryId,
|
|
505
|
+
permissionRequest,
|
|
506
|
+
contextState: manager.contextState,
|
|
507
|
+
handleSubmit,
|
|
508
|
+
handleAbort,
|
|
509
|
+
handleCancelQueue,
|
|
510
|
+
handleShutdown,
|
|
511
|
+
selectExecutionWorkspaceEntry,
|
|
512
|
+
readExecutionWorkspaceDetail
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// src/hooks/usePluginCallbacks.ts
|
|
517
|
+
var import_react3 = require("react");
|
|
518
|
+
function createNoOpPluginAdapter() {
|
|
519
|
+
return {
|
|
520
|
+
listInstalled: async () => [],
|
|
521
|
+
listAvailablePlugins: async () => [],
|
|
522
|
+
install: async () => void 0,
|
|
523
|
+
uninstall: async () => void 0,
|
|
524
|
+
enable: async () => void 0,
|
|
525
|
+
disable: async () => void 0,
|
|
526
|
+
marketplaceAdd: async () => "",
|
|
527
|
+
marketplaceRemove: async () => void 0,
|
|
528
|
+
marketplaceUpdate: async () => void 0,
|
|
529
|
+
marketplaceList: async () => [],
|
|
530
|
+
reloadPlugins: async () => ({ loadedPluginCount: 0 })
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
function usePluginCallbacks(_cwd) {
|
|
534
|
+
return (0, import_react3.useMemo)(() => createNoOpPluginAdapter(), []);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// src/hooks/useSideEffects.ts
|
|
538
|
+
var import_react5 = require("react");
|
|
539
|
+
var import_ink = require("ink");
|
|
540
|
+
var import_agent_core5 = require("@robota-sdk/agent-core");
|
|
541
|
+
|
|
542
|
+
// src/hooks/command-effect-handler.ts
|
|
543
|
+
var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
|
|
544
|
+
var import_agent_core3 = require("@robota-sdk/agent-core");
|
|
545
|
+
function applyCommandEffects(effects, deps) {
|
|
546
|
+
for (const effect of effects) {
|
|
547
|
+
if (effect.type === "model-change-requested") {
|
|
548
|
+
deps.requestModelChange(effect.modelId);
|
|
549
|
+
return true;
|
|
550
|
+
}
|
|
551
|
+
if (effect.type === "language-change-requested") {
|
|
552
|
+
applyLanguageEffect(effect.language, deps);
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
555
|
+
if (effect.type === "settings-reset-requested") {
|
|
556
|
+
applySettingsResetEffect(deps);
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
if (effect.type === "session-exit-requested") {
|
|
560
|
+
deps.requestShutdown(
|
|
561
|
+
effect.reason ?? "prompt_input_exit",
|
|
562
|
+
effect.message ?? "User requested exit"
|
|
563
|
+
);
|
|
564
|
+
return true;
|
|
565
|
+
}
|
|
566
|
+
if (effect.type === "session-restart-requested") {
|
|
567
|
+
deps.requestShutdown(effect.reason, effect.message);
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
if (effect.type === "plugin-tui-requested") {
|
|
571
|
+
deps.openPluginTUI();
|
|
572
|
+
return true;
|
|
573
|
+
}
|
|
574
|
+
if (effect.type === "settings-tui-requested") {
|
|
575
|
+
deps.openTransportTUI();
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
if (effect.type === "session-picker-requested") {
|
|
579
|
+
deps.openSessionPicker();
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
582
|
+
if (effect.type === "session-renamed") {
|
|
583
|
+
deps.renameSession(effect.name);
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
586
|
+
if (effect.type === "statusline-settings-patch") {
|
|
587
|
+
if ((0, import_agent_sdk2.isStatusLineCommandSettingsPatch)(effect.patch)) {
|
|
588
|
+
return deps.applyStatusLinePatch(effect.patch);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
function applyLanguageEffect(language, deps) {
|
|
595
|
+
const settingsPath = deps.cliAdapter.getUserSettingsPath();
|
|
596
|
+
const settings = deps.cliAdapter.readSettings(settingsPath);
|
|
597
|
+
settings.language = language;
|
|
598
|
+
deps.cliAdapter.writeSettings(settingsPath, settings);
|
|
599
|
+
deps.addEntry(
|
|
600
|
+
(0, import_agent_core3.messageToHistoryEntry)((0, import_agent_core3.createSystemMessage)(`Language set to "${language}". Restarting...`))
|
|
601
|
+
);
|
|
602
|
+
deps.requestShutdown("other", "Language change restart");
|
|
603
|
+
}
|
|
604
|
+
function applySettingsResetEffect(deps) {
|
|
605
|
+
const settingsPath = deps.cliAdapter.getUserSettingsPath();
|
|
606
|
+
if (deps.cliAdapter.deleteSettings(settingsPath)) {
|
|
607
|
+
deps.addEntry(
|
|
608
|
+
(0, import_agent_core3.messageToHistoryEntry)((0, import_agent_core3.createSystemMessage)(`Deleted ${settingsPath}. Exiting...`))
|
|
609
|
+
);
|
|
610
|
+
} else {
|
|
611
|
+
deps.addEntry((0, import_agent_core3.messageToHistoryEntry)((0, import_agent_core3.createSystemMessage)("No user settings found.")));
|
|
612
|
+
}
|
|
613
|
+
deps.requestShutdown("other", "Reset settings restart");
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// src/tui-cli-adapter-context.tsx
|
|
617
|
+
var import_react4 = require("react");
|
|
618
|
+
var TuiCliAdapterContext = (0, import_react4.createContext)(null);
|
|
619
|
+
var TuiCliAdapterProvider = TuiCliAdapterContext.Provider;
|
|
620
|
+
function useTuiCliAdapter() {
|
|
621
|
+
const adapter = (0, import_react4.useContext)(TuiCliAdapterContext);
|
|
622
|
+
if (!adapter) throw new Error("TuiCliAdapterContext not provided");
|
|
623
|
+
return adapter;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// src/hooks/model-change-side-effect.ts
|
|
627
|
+
var import_agent_core4 = require("@robota-sdk/agent-core");
|
|
628
|
+
function formatModelChangeConfirmationMessage(modelId) {
|
|
629
|
+
return `Change model to ${(0, import_agent_core4.getModelName)(modelId)}? This will exit the current session so the next session uses it.`;
|
|
630
|
+
}
|
|
631
|
+
function formatModelChangeExitMessage(modelId) {
|
|
632
|
+
return `Model changed to ${(0, import_agent_core4.getModelName)(modelId)}. Exiting so the next session uses it.`;
|
|
633
|
+
}
|
|
634
|
+
function applyConfirmedModelChange(deps) {
|
|
635
|
+
const applyModelChange = deps.applyModelChange;
|
|
636
|
+
if (!applyModelChange) {
|
|
637
|
+
deps.addEntry(
|
|
638
|
+
(0, import_agent_core4.messageToHistoryEntry)((0, import_agent_core4.createSystemMessage)("Model change unavailable: no adapter provided."))
|
|
639
|
+
);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
const options = deps.providerOverride !== void 0 ? { providerOverride: deps.providerOverride } : void 0;
|
|
643
|
+
try {
|
|
644
|
+
applyModelChange(deps.cwd, deps.modelId, options);
|
|
645
|
+
deps.addEntry(
|
|
646
|
+
(0, import_agent_core4.messageToHistoryEntry)((0, import_agent_core4.createSystemMessage)(formatModelChangeExitMessage(deps.modelId)))
|
|
647
|
+
);
|
|
648
|
+
deps.requestShutdown("other", "Model change applied");
|
|
649
|
+
} catch (error) {
|
|
650
|
+
deps.addEntry(
|
|
651
|
+
(0, import_agent_core4.messageToHistoryEntry)(
|
|
652
|
+
(0, import_agent_core4.createSystemMessage)(`Failed: ${error instanceof Error ? error.message : String(error)}`)
|
|
653
|
+
)
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
function addModelChangeCancelledMessage(addEntry) {
|
|
658
|
+
addEntry((0, import_agent_core4.messageToHistoryEntry)((0, import_agent_core4.createSystemMessage)("Model change cancelled.")));
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// src/hooks/useSideEffects.ts
|
|
662
|
+
var EXIT_DELAY_MS = 500;
|
|
663
|
+
function useSideEffects({
|
|
664
|
+
cwd,
|
|
665
|
+
providerOverride,
|
|
666
|
+
interactiveSession,
|
|
667
|
+
commandEffectQueue,
|
|
668
|
+
addEntry,
|
|
669
|
+
baseHandleSubmit,
|
|
670
|
+
setSessionName,
|
|
671
|
+
setStatusLineSettings,
|
|
672
|
+
showSessionPickerOnStart
|
|
673
|
+
}) {
|
|
674
|
+
const { exit } = (0, import_ink.useApp)();
|
|
675
|
+
const cliAdapter = useTuiCliAdapter();
|
|
676
|
+
const [pendingModelId, setPendingModelId] = (0, import_react5.useState)(null);
|
|
677
|
+
const pendingModelChangeRef = (0, import_react5.useRef)(null);
|
|
678
|
+
const [pendingInteractionPrompt, setPendingInteractionPrompt] = (0, import_react5.useState)(null);
|
|
679
|
+
const commandInteractionRef = (0, import_react5.useRef)(null);
|
|
680
|
+
const [showPluginTUI, setShowPluginTUI] = (0, import_react5.useState)(false);
|
|
681
|
+
const [showSessionPicker, setShowSessionPicker] = (0, import_react5.useState)(showSessionPickerOnStart ?? false);
|
|
682
|
+
const [showTransportTUI, setShowTransportTUI] = (0, import_react5.useState)(false);
|
|
683
|
+
const requestShutdown = (0, import_react5.useCallback)(
|
|
684
|
+
(reason, message) => {
|
|
685
|
+
addEntry((0, import_agent_core5.messageToHistoryEntry)((0, import_agent_core5.createSystemMessage)("Shutting down...")));
|
|
686
|
+
setTimeout(() => {
|
|
687
|
+
void interactiveSession.shutdown({ reason, message }).finally(() => exit());
|
|
688
|
+
}, EXIT_DELAY_MS);
|
|
689
|
+
},
|
|
690
|
+
[interactiveSession, addEntry, exit]
|
|
691
|
+
);
|
|
692
|
+
const applyEffects = (0, import_react5.useCallback)(
|
|
693
|
+
(effects) => applyCommandEffects(effects, {
|
|
694
|
+
addEntry,
|
|
695
|
+
requestShutdown,
|
|
696
|
+
requestModelChange: (modelId) => {
|
|
697
|
+
pendingModelChangeRef.current = modelId;
|
|
698
|
+
setPendingModelId(modelId);
|
|
699
|
+
},
|
|
700
|
+
openPluginTUI: () => setShowPluginTUI(true),
|
|
701
|
+
openSessionPicker: () => setShowSessionPicker(true),
|
|
702
|
+
openTransportTUI: () => setShowTransportTUI(true),
|
|
703
|
+
renameSession: (name) => {
|
|
704
|
+
interactiveSession.setName(name);
|
|
705
|
+
setSessionName(name);
|
|
706
|
+
},
|
|
707
|
+
applyStatusLinePatch: (patch) => {
|
|
708
|
+
setStatusLineSettings(
|
|
709
|
+
cliAdapter.applyStatusLineSettings(cliAdapter.getUserSettingsPath(), patch)
|
|
710
|
+
);
|
|
711
|
+
return true;
|
|
712
|
+
},
|
|
713
|
+
cliAdapter
|
|
714
|
+
}),
|
|
715
|
+
[
|
|
716
|
+
addEntry,
|
|
717
|
+
cliAdapter,
|
|
718
|
+
interactiveSession,
|
|
719
|
+
requestShutdown,
|
|
720
|
+
setSessionName,
|
|
721
|
+
setStatusLineSettings
|
|
722
|
+
]
|
|
723
|
+
);
|
|
724
|
+
const applyCommandResult = (0, import_react5.useCallback)(
|
|
725
|
+
(result) => {
|
|
726
|
+
if (result.message.length > 0) {
|
|
727
|
+
addEntry((0, import_agent_core5.messageToHistoryEntry)((0, import_agent_core5.createSystemMessage)(result.message)));
|
|
728
|
+
}
|
|
729
|
+
if (result.interaction !== void 0) {
|
|
730
|
+
commandInteractionRef.current = result.interaction;
|
|
731
|
+
setPendingInteractionPrompt(result.interaction.prompt);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
commandInteractionRef.current = null;
|
|
735
|
+
setPendingInteractionPrompt(null);
|
|
736
|
+
if (result.effects !== void 0 && result.effects.length > 0) {
|
|
737
|
+
applyEffects(result.effects);
|
|
738
|
+
}
|
|
739
|
+
},
|
|
740
|
+
[addEntry, applyEffects]
|
|
741
|
+
);
|
|
742
|
+
const applyQueuedCommandState = (0, import_react5.useCallback)(() => {
|
|
743
|
+
const queued = commandEffectQueue.drain();
|
|
744
|
+
if (queued === void 0) {
|
|
745
|
+
return false;
|
|
746
|
+
}
|
|
747
|
+
if (queued.type === "interaction") {
|
|
748
|
+
const { interaction } = queued;
|
|
749
|
+
commandInteractionRef.current = interaction;
|
|
750
|
+
setPendingInteractionPrompt(interaction.prompt);
|
|
751
|
+
return true;
|
|
752
|
+
}
|
|
753
|
+
return applyEffects(queued.effects);
|
|
754
|
+
}, [applyEffects, commandEffectQueue]);
|
|
755
|
+
const handleSubmit = (0, import_react5.useCallback)(
|
|
756
|
+
async (input) => {
|
|
757
|
+
await baseHandleSubmit(input);
|
|
758
|
+
applyQueuedCommandState();
|
|
759
|
+
},
|
|
760
|
+
[baseHandleSubmit, applyQueuedCommandState]
|
|
761
|
+
);
|
|
762
|
+
const handleModelConfirm = (0, import_react5.useCallback)(
|
|
763
|
+
(index) => {
|
|
764
|
+
const modelId = pendingModelChangeRef.current;
|
|
765
|
+
setPendingModelId(null);
|
|
766
|
+
pendingModelChangeRef.current = null;
|
|
767
|
+
if (index === 0 && modelId) {
|
|
768
|
+
applyConfirmedModelChange({
|
|
769
|
+
cwd,
|
|
770
|
+
modelId,
|
|
771
|
+
providerOverride,
|
|
772
|
+
addEntry,
|
|
773
|
+
requestShutdown,
|
|
774
|
+
applyModelChange: (c, m, opts) => {
|
|
775
|
+
cliAdapter.applyActiveModelChange(c, m, opts);
|
|
776
|
+
return { applied: true };
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
} else {
|
|
780
|
+
addModelChangeCancelledMessage(addEntry);
|
|
781
|
+
}
|
|
782
|
+
},
|
|
783
|
+
[cwd, providerOverride, addEntry, cliAdapter, requestShutdown]
|
|
784
|
+
);
|
|
785
|
+
const handleInteractionSubmit = (0, import_react5.useCallback)(
|
|
786
|
+
async (value) => {
|
|
787
|
+
const interaction = commandInteractionRef.current;
|
|
788
|
+
if (interaction === null) {
|
|
789
|
+
setPendingInteractionPrompt(null);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
try {
|
|
793
|
+
const result = await interaction.submit(value);
|
|
794
|
+
applyCommandResult(result);
|
|
795
|
+
} catch (err) {
|
|
796
|
+
commandInteractionRef.current = null;
|
|
797
|
+
setPendingInteractionPrompt(null);
|
|
798
|
+
addEntry(
|
|
799
|
+
(0, import_agent_core5.messageToHistoryEntry)(
|
|
800
|
+
(0, import_agent_core5.createSystemMessage)(`Failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
801
|
+
)
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
},
|
|
805
|
+
[addEntry, applyCommandResult]
|
|
806
|
+
);
|
|
807
|
+
const handleInteractionCancel = (0, import_react5.useCallback)(() => {
|
|
808
|
+
const interaction = commandInteractionRef.current;
|
|
809
|
+
commandInteractionRef.current = null;
|
|
810
|
+
setPendingInteractionPrompt(null);
|
|
811
|
+
if (interaction?.cancel === void 0) {
|
|
812
|
+
addEntry((0, import_agent_core5.messageToHistoryEntry)((0, import_agent_core5.createSystemMessage)("Command interaction cancelled.")));
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
Promise.resolve(interaction.cancel()).then((result) => applyCommandResult(result)).catch((err) => {
|
|
816
|
+
addEntry(
|
|
817
|
+
(0, import_agent_core5.messageToHistoryEntry)(
|
|
818
|
+
(0, import_agent_core5.createSystemMessage)(`Failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
819
|
+
)
|
|
820
|
+
);
|
|
821
|
+
});
|
|
822
|
+
}, [addEntry, applyCommandResult]);
|
|
823
|
+
return {
|
|
824
|
+
handleSubmit,
|
|
825
|
+
pendingModelId,
|
|
826
|
+
pendingInteractionPrompt,
|
|
827
|
+
showPluginTUI,
|
|
828
|
+
showSessionPicker,
|
|
829
|
+
showTransportTUI,
|
|
830
|
+
setPendingModelId,
|
|
831
|
+
setShowPluginTUI,
|
|
832
|
+
setShowSessionPicker,
|
|
833
|
+
setShowTransportTUI,
|
|
834
|
+
handleModelConfirm,
|
|
835
|
+
handleInteractionSubmit,
|
|
836
|
+
handleInteractionCancel
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// src/hooks/useStatusLineSettings.ts
|
|
841
|
+
var import_react6 = require("react");
|
|
842
|
+
var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
|
|
843
|
+
function readStatusLineSettings(settings) {
|
|
844
|
+
const defaults = { ...import_agent_sdk3.DEFAULT_STATUS_LINE_COMMAND_SETTINGS };
|
|
845
|
+
const raw = settings.statusline;
|
|
846
|
+
if (!isRecord(raw)) {
|
|
847
|
+
return defaults;
|
|
848
|
+
}
|
|
849
|
+
return {
|
|
850
|
+
enabled: typeof raw.enabled === "boolean" ? raw.enabled : defaults.enabled,
|
|
851
|
+
gitBranch: typeof raw.gitBranch === "boolean" ? raw.gitBranch : defaults.gitBranch
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
function isRecord(value) {
|
|
855
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
|
|
856
|
+
}
|
|
857
|
+
function useStatusLineSettings() {
|
|
858
|
+
const cliAdapter = useTuiCliAdapter();
|
|
859
|
+
return (0, import_react6.useState)(
|
|
860
|
+
() => readStatusLineSettings(cliAdapter.readSettings(cliAdapter.getUserSettingsPath()))
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// src/MessageList.tsx
|
|
865
|
+
var import_react7 = __toESM(require("react"), 1);
|
|
866
|
+
var import_ink5 = require("ink");
|
|
867
|
+
var import_agent_core7 = require("@robota-sdk/agent-core");
|
|
868
|
+
|
|
869
|
+
// src/render-markdown.ts
|
|
870
|
+
var import_marked = require("marked");
|
|
871
|
+
var import_marked_terminal = __toESM(require("marked-terminal"), 1);
|
|
872
|
+
var ANSI_LIGHT_RED = "\x1B[38;5;210m";
|
|
873
|
+
var ANSI_LIGHT_GREEN = "\x1B[38;5;120m";
|
|
874
|
+
var ANSI_CYAN = "\x1B[36m";
|
|
875
|
+
var ANSI_DIM = "\x1B[2m";
|
|
876
|
+
var ANSI_DARK_RED_BACKGROUND = "\x1B[48;5;52m";
|
|
877
|
+
var ANSI_DARK_GREEN_BACKGROUND = "\x1B[48;5;22m";
|
|
878
|
+
var ANSI_RESET = "\x1B[0m";
|
|
879
|
+
var CODE_BLOCK_INDENT = " ";
|
|
880
|
+
var ZERO_COLOR = "0";
|
|
881
|
+
var TerminalRendererConstructor = import_marked_terminal.default;
|
|
882
|
+
function shouldUseColor(option) {
|
|
883
|
+
if (option !== void 0) {
|
|
884
|
+
return option;
|
|
885
|
+
}
|
|
886
|
+
if (process.env.NO_COLOR || process.env.FORCE_COLOR === ZERO_COLOR) {
|
|
887
|
+
return false;
|
|
888
|
+
}
|
|
889
|
+
if (process.env.FORCE_COLOR) {
|
|
890
|
+
return true;
|
|
891
|
+
}
|
|
892
|
+
return Boolean(process.stdout.isTTY);
|
|
893
|
+
}
|
|
894
|
+
function isDiffLanguage(language) {
|
|
895
|
+
return language?.trim().toLowerCase() === "diff";
|
|
896
|
+
}
|
|
897
|
+
function styleAddedOrRemovedDiffRow(line, rowWidth, color) {
|
|
898
|
+
const row = `${CODE_BLOCK_INDENT}${line}`.padEnd(rowWidth);
|
|
899
|
+
if (!color) {
|
|
900
|
+
return row.trimEnd();
|
|
901
|
+
}
|
|
902
|
+
if (line.startsWith("+")) {
|
|
903
|
+
return `${ANSI_DARK_GREEN_BACKGROUND}${ANSI_LIGHT_GREEN}${row}${ANSI_RESET}`;
|
|
904
|
+
}
|
|
905
|
+
if (line.startsWith("-")) {
|
|
906
|
+
return `${ANSI_DARK_RED_BACKGROUND}${ANSI_LIGHT_RED}${row}${ANSI_RESET}`;
|
|
907
|
+
}
|
|
908
|
+
return row.trimEnd();
|
|
909
|
+
}
|
|
910
|
+
function colorizeDiffLine(line, color, rowWidth) {
|
|
911
|
+
if (line.startsWith("+") || line.startsWith("-")) {
|
|
912
|
+
return styleAddedOrRemovedDiffRow(line, rowWidth, color);
|
|
913
|
+
}
|
|
914
|
+
const row = `${CODE_BLOCK_INDENT}${line}`;
|
|
915
|
+
if (!color) {
|
|
916
|
+
return row;
|
|
917
|
+
}
|
|
918
|
+
if (line.startsWith("@@")) {
|
|
919
|
+
return `${ANSI_CYAN}${row}${ANSI_RESET}`;
|
|
920
|
+
}
|
|
921
|
+
if (line.startsWith("diff ") || line.startsWith("index ")) {
|
|
922
|
+
return `${ANSI_DIM}${row}${ANSI_RESET}`;
|
|
923
|
+
}
|
|
924
|
+
return row;
|
|
925
|
+
}
|
|
926
|
+
function resolveDiffRowWidth(lines, requestedWidth) {
|
|
927
|
+
const minimumWidth = lines.reduce(
|
|
928
|
+
(maxWidth, line) => Math.max(maxWidth, CODE_BLOCK_INDENT.length + line.length),
|
|
929
|
+
0
|
|
930
|
+
);
|
|
931
|
+
if (requestedWidth === void 0) {
|
|
932
|
+
return minimumWidth;
|
|
933
|
+
}
|
|
934
|
+
return Math.max(minimumWidth, requestedWidth);
|
|
935
|
+
}
|
|
936
|
+
function renderDiffCodeBlock(code, color, codeBlockWidth) {
|
|
937
|
+
const lines = code.split("\n");
|
|
938
|
+
const rowWidth = resolveDiffRowWidth(lines, codeBlockWidth);
|
|
939
|
+
const body = lines.map((line) => colorizeDiffLine(line, color, rowWidth)).join("\n");
|
|
940
|
+
return `${body}
|
|
941
|
+
|
|
942
|
+
`;
|
|
943
|
+
}
|
|
944
|
+
function createTerminalRenderer(color, codeBlockWidth) {
|
|
945
|
+
const renderer = new TerminalRendererConstructor(void 0, { ignoreIllegals: true });
|
|
946
|
+
const renderCode = renderer.code.bind(renderer);
|
|
947
|
+
renderer.code = (code, language, escaped) => {
|
|
948
|
+
if (isDiffLanguage(language)) {
|
|
949
|
+
return renderDiffCodeBlock(code, color, codeBlockWidth);
|
|
950
|
+
}
|
|
951
|
+
return renderCode(code, language, escaped);
|
|
952
|
+
};
|
|
953
|
+
return renderer;
|
|
954
|
+
}
|
|
955
|
+
function renderMarkdown(md, options = {}) {
|
|
956
|
+
const result = import_marked.marked.parse(md, {
|
|
957
|
+
renderer: createTerminalRenderer(shouldUseColor(options.color), options.codeBlockWidth)
|
|
958
|
+
});
|
|
959
|
+
return typeof result === "string" ? result.trimEnd() : md;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// src/ToolDiffBlock.tsx
|
|
963
|
+
var import_ink2 = require("ink");
|
|
964
|
+
|
|
965
|
+
// src/utils/tool-diff-summary.ts
|
|
966
|
+
var MAX_DIFF_LINES = 12;
|
|
967
|
+
var TRUNCATED_SHOW = 10;
|
|
968
|
+
function buildToolDiffSummary(input) {
|
|
969
|
+
const visibleLines = input.lines.length > MAX_DIFF_LINES ? selectVisibleDiffLines(input.lines) : input.lines;
|
|
970
|
+
const lineNumberWidth = Math.max(...visibleLines.map((line) => line.lineNumber), 0).toString().length;
|
|
971
|
+
const body = visibleLines.map((line) => formatDiffLine(line, lineNumberWidth));
|
|
972
|
+
const truncated = input.lines.length > MAX_DIFF_LINES;
|
|
973
|
+
return {
|
|
974
|
+
file: input.file,
|
|
975
|
+
markdown: ["```diff", ...body, "```"].join("\n"),
|
|
976
|
+
truncated,
|
|
977
|
+
remainingLineCount: truncated ? input.lines.length - visibleLines.length : 0
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
function formatDiffLine(line, lineNumberWidth) {
|
|
981
|
+
if (line.type === "hunk") return line.text;
|
|
982
|
+
const lineNumber = line.lineNumber.toString().padStart(lineNumberWidth, " ");
|
|
983
|
+
if (line.type === "remove") return `- ${lineNumber} | ${line.text}`;
|
|
984
|
+
if (line.type === "add") return `+ ${lineNumber} | ${line.text}`;
|
|
985
|
+
return ` ${lineNumber} | ${line.text}`;
|
|
986
|
+
}
|
|
987
|
+
function selectVisibleDiffLines(lines) {
|
|
988
|
+
const groups = groupByHunk(lines);
|
|
989
|
+
const visible = [];
|
|
990
|
+
for (const group of groups) {
|
|
991
|
+
if (visible.length === 0 && group.length > TRUNCATED_SHOW) {
|
|
992
|
+
return group.slice(0, TRUNCATED_SHOW);
|
|
993
|
+
}
|
|
994
|
+
if (visible.length + group.length > TRUNCATED_SHOW) break;
|
|
995
|
+
visible.push(...group);
|
|
996
|
+
}
|
|
997
|
+
return visible.length > 0 ? visible : lines.slice(0, TRUNCATED_SHOW);
|
|
998
|
+
}
|
|
999
|
+
function groupByHunk(lines) {
|
|
1000
|
+
const groups = [];
|
|
1001
|
+
let current = [];
|
|
1002
|
+
for (const line of lines) {
|
|
1003
|
+
if (line.type === "hunk" && current.length > 0) {
|
|
1004
|
+
groups.push(current);
|
|
1005
|
+
current = [line];
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
current.push(line);
|
|
1009
|
+
}
|
|
1010
|
+
if (current.length > 0) {
|
|
1011
|
+
groups.push(current);
|
|
1012
|
+
}
|
|
1013
|
+
return groups;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// src/ToolDiffBlock.tsx
|
|
1017
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1018
|
+
function ToolDiffBlock({ file, lines }) {
|
|
1019
|
+
const summary = buildToolDiffSummary({ file, lines });
|
|
1020
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink2.Box, { flexDirection: "column", marginLeft: 4, children: [
|
|
1021
|
+
summary.file && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink2.Text, { color: "white", dimColor: true, children: [
|
|
1022
|
+
"\u2502 ",
|
|
1023
|
+
summary.file
|
|
1024
|
+
] }),
|
|
1025
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink2.Text, { children: renderMarkdown(summary.markdown) }),
|
|
1026
|
+
summary.truncated && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink2.Text, { color: "white", dimColor: true, children: [
|
|
1027
|
+
"\u2502 ... and ",
|
|
1028
|
+
summary.remainingLineCount,
|
|
1029
|
+
" more lines"
|
|
1030
|
+
] })
|
|
1031
|
+
] });
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// src/UsageSummaryEntry.tsx
|
|
1035
|
+
var import_ink3 = require("ink");
|
|
1036
|
+
var import_agent_core6 = require("@robota-sdk/agent-core");
|
|
1037
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1038
|
+
var TOKEN_COMPACT_THRESHOLD = 1e3;
|
|
1039
|
+
function UsageSummaryEntry({ entry }) {
|
|
1040
|
+
const usage = entry.data;
|
|
1041
|
+
if (!usage) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, {});
|
|
1042
|
+
const prompt = usage.promptTokens !== void 0 ? formatUsageTokenCount(usage.promptTokens) : "?";
|
|
1043
|
+
const completion = usage.completionTokens !== void 0 ? formatUsageTokenCount(usage.completionTokens) : "?";
|
|
1044
|
+
const total = formatUsageTokenCount(usage.totalTokens);
|
|
1045
|
+
const context = `${Math.round(usage.contextUsedPercentage)}% (${(0, import_agent_core6.formatTokenCount)(
|
|
1046
|
+
usage.contextUsedTokens
|
|
1047
|
+
)}/${(0, import_agent_core6.formatTokenCount)(usage.contextMaxTokens)})`;
|
|
1048
|
+
const costLabel = usage.costStatus === "unknown" ? "cost unknown" : `cost ${usage.costStatus}`;
|
|
1049
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink3.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink3.Box, { children: [
|
|
1050
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink3.Text, { color: "white", bold: true, children: [
|
|
1051
|
+
"Usage:",
|
|
1052
|
+
" "
|
|
1053
|
+
] }),
|
|
1054
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink3.Text, { dimColor: true, children: [
|
|
1055
|
+
usage.kind,
|
|
1056
|
+
" ",
|
|
1057
|
+
total,
|
|
1058
|
+
" tokens (in ",
|
|
1059
|
+
prompt,
|
|
1060
|
+
" / out ",
|
|
1061
|
+
completion,
|
|
1062
|
+
") \xB7 Context ",
|
|
1063
|
+
context,
|
|
1064
|
+
" \xB7",
|
|
1065
|
+
" ",
|
|
1066
|
+
costLabel
|
|
1067
|
+
] })
|
|
1068
|
+
] }) });
|
|
1069
|
+
}
|
|
1070
|
+
function formatUsageTokenCount(tokens) {
|
|
1071
|
+
return tokens < TOKEN_COMPACT_THRESHOLD ? tokens.toLocaleString() : (0, import_agent_core6.formatTokenCount)(tokens);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// src/command-output-summary.ts
|
|
1075
|
+
var MAX_PREVIEW_LINES = 4;
|
|
1076
|
+
var SUCCESS_EXIT_CODE = 0;
|
|
1077
|
+
var COMMAND_TOOL_NAMES = /* @__PURE__ */ new Set(["Bash", "BackgroundProcess"]);
|
|
1078
|
+
function formatCommandOutputSummary(tool) {
|
|
1079
|
+
if (!COMMAND_TOOL_NAMES.has(tool.toolName) || !tool.toolResultData) return void 0;
|
|
1080
|
+
const parsed = parseToolResultData(tool.toolResultData);
|
|
1081
|
+
const exitCode = getNumberValue(parsed, "exitCode");
|
|
1082
|
+
const successValue = getBooleanValue(parsed, "success");
|
|
1083
|
+
const output = buildOutputText(tool.toolResultData, parsed);
|
|
1084
|
+
const lines = trimTrailingBlankLines(splitOutputLines(output));
|
|
1085
|
+
const previewLines = lines.slice(0, MAX_PREVIEW_LINES);
|
|
1086
|
+
const omittedLineCount = Math.max(0, lines.length - previewLines.length);
|
|
1087
|
+
const isFailed = tool.result === "error" || successValue === false || exitCode !== void 0 && exitCode !== SUCCESS_EXIT_CODE;
|
|
1088
|
+
return {
|
|
1089
|
+
status: isFailed ? "error" : "success",
|
|
1090
|
+
statusLabel: formatStatusLabel(isFailed, exitCode),
|
|
1091
|
+
previewLines,
|
|
1092
|
+
omittedLineCount,
|
|
1093
|
+
transcriptHint: omittedLineCount > 0 ? `... +${omittedLineCount} lines (full output in session transcript)` : void 0
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
function parseToolResultData(value) {
|
|
1097
|
+
try {
|
|
1098
|
+
return JSON.parse(value);
|
|
1099
|
+
} catch {
|
|
1100
|
+
return value;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
function buildOutputText(raw, parsed) {
|
|
1104
|
+
if (!isUniversalObject(parsed)) return raw;
|
|
1105
|
+
const output = getStringValue(parsed, "output");
|
|
1106
|
+
if (output !== void 0) return output;
|
|
1107
|
+
const stdout = getStringValue(parsed, "stdout");
|
|
1108
|
+
const stderr = getStringValue(parsed, "stderr");
|
|
1109
|
+
const error = getStringValue(parsed, "error");
|
|
1110
|
+
const lines = [];
|
|
1111
|
+
if (stdout) lines.push(stdout);
|
|
1112
|
+
if (stderr) lines.push(prefixLines(stderr, "[stderr] "));
|
|
1113
|
+
if (!stdout && !stderr && error) lines.push(error);
|
|
1114
|
+
return lines.join("\n");
|
|
1115
|
+
}
|
|
1116
|
+
function formatStatusLabel(isFailed, exitCode) {
|
|
1117
|
+
if (exitCode !== void 0 && exitCode !== SUCCESS_EXIT_CODE) return `exit ${exitCode}`;
|
|
1118
|
+
return isFailed ? "error" : "ok";
|
|
1119
|
+
}
|
|
1120
|
+
function splitOutputLines(output) {
|
|
1121
|
+
if (!output) return [];
|
|
1122
|
+
return output.replace(/\r\n/g, "\n").split("\n");
|
|
1123
|
+
}
|
|
1124
|
+
function trimTrailingBlankLines(lines) {
|
|
1125
|
+
let end = lines.length;
|
|
1126
|
+
while (end > 0 && lines[end - 1].trim().length === 0) {
|
|
1127
|
+
end -= 1;
|
|
1128
|
+
}
|
|
1129
|
+
return lines.slice(0, end);
|
|
1130
|
+
}
|
|
1131
|
+
function prefixLines(value, prefix) {
|
|
1132
|
+
return splitOutputLines(value).map((line) => `${prefix}${line}`).join("\n");
|
|
1133
|
+
}
|
|
1134
|
+
function isUniversalObject(value) {
|
|
1135
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
|
|
1136
|
+
}
|
|
1137
|
+
function getStringValue(source, key) {
|
|
1138
|
+
if (!isUniversalObject(source)) return void 0;
|
|
1139
|
+
const value = source[key];
|
|
1140
|
+
return typeof value === "string" ? value : void 0;
|
|
1141
|
+
}
|
|
1142
|
+
function getNumberValue(source, key) {
|
|
1143
|
+
if (!isUniversalObject(source)) return void 0;
|
|
1144
|
+
const value = source[key];
|
|
1145
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1146
|
+
}
|
|
1147
|
+
function getBooleanValue(source, key) {
|
|
1148
|
+
if (!isUniversalObject(source)) return void 0;
|
|
1149
|
+
const value = source[key];
|
|
1150
|
+
return typeof value === "boolean" ? value : void 0;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// src/ToolCommandOutput.tsx
|
|
1154
|
+
var import_ink4 = require("ink");
|
|
1155
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1156
|
+
function ToolCommandOutput({ tool }) {
|
|
1157
|
+
const summary = formatCommandOutputSummary(tool);
|
|
1158
|
+
if (!summary) return null;
|
|
1159
|
+
if (summary.statusLabel === "ok" && summary.previewLines.length === 0 && !summary.transcriptHint) {
|
|
1160
|
+
return null;
|
|
1161
|
+
}
|
|
1162
|
+
const color = summary.status === "error" ? "red" : "white";
|
|
1163
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink4.Box, { flexDirection: "column", marginLeft: 4, children: [
|
|
1164
|
+
summary.statusLabel !== "ok" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink4.Text, { color, dimColor: true, children: summary.statusLabel }),
|
|
1165
|
+
summary.previewLines.map((line, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink4.Text, { color, dimColor: true, children: line }, `${line}-${index}`)),
|
|
1166
|
+
summary.transcriptHint && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink4.Text, { dimColor: true, children: summary.transcriptHint })
|
|
1167
|
+
] });
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// src/MessageList.tsx
|
|
1171
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1172
|
+
function getToolSummaryStatus(tool) {
|
|
1173
|
+
if (formatCommandOutputSummary(tool)?.status === "error") return "\u2717";
|
|
1174
|
+
if (tool.isRunning) return "\u27F3";
|
|
1175
|
+
if (tool.result === "error") return "\u2717";
|
|
1176
|
+
if (tool.result === "denied") return "\u2298";
|
|
1177
|
+
return "\u2713";
|
|
1178
|
+
}
|
|
1179
|
+
function getToolSummaryColor(tool) {
|
|
1180
|
+
if (formatCommandOutputSummary(tool)?.status === "error" || tool.result === "error") return "red";
|
|
1181
|
+
if (tool.isRunning || tool.result === "denied") return "yellow";
|
|
1182
|
+
return "green";
|
|
1183
|
+
}
|
|
1184
|
+
function getToolSummaryLabel(tool) {
|
|
1185
|
+
return `${getToolSummaryStatus(tool)} ${tool.toolName}${tool.firstArg ? `(${tool.firstArg})` : ""}`;
|
|
1186
|
+
}
|
|
1187
|
+
function RoleLabel({ role }) {
|
|
1188
|
+
switch (role) {
|
|
1189
|
+
case "user":
|
|
1190
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { color: "green", bold: true, children: [
|
|
1191
|
+
"You:",
|
|
1192
|
+
" "
|
|
1193
|
+
] });
|
|
1194
|
+
case "assistant":
|
|
1195
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { color: "cyan", bold: true, children: [
|
|
1196
|
+
"Robota:",
|
|
1197
|
+
" "
|
|
1198
|
+
] });
|
|
1199
|
+
case "system":
|
|
1200
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { color: "yellow", bold: true, children: [
|
|
1201
|
+
"System:",
|
|
1202
|
+
" "
|
|
1203
|
+
] });
|
|
1204
|
+
case "tool":
|
|
1205
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { color: "white", bold: true, children: [
|
|
1206
|
+
"Tool:",
|
|
1207
|
+
" "
|
|
1208
|
+
] });
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
function ToolMessage({ message }) {
|
|
1212
|
+
if (!(0, import_agent_core7.isToolMessage)(message)) {
|
|
1213
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, {});
|
|
1214
|
+
}
|
|
1215
|
+
const toolName = message.name;
|
|
1216
|
+
const content = message.content;
|
|
1217
|
+
let summaries = null;
|
|
1218
|
+
try {
|
|
1219
|
+
const parsed = JSON.parse(content);
|
|
1220
|
+
if (Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0].line === "string") {
|
|
1221
|
+
summaries = parsed;
|
|
1222
|
+
}
|
|
1223
|
+
} catch {
|
|
1224
|
+
}
|
|
1225
|
+
if (summaries) {
|
|
1226
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1227
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Box, { children: [
|
|
1228
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { color: "white", bold: true, children: [
|
|
1229
|
+
"Tool:",
|
|
1230
|
+
" "
|
|
1231
|
+
] }),
|
|
1232
|
+
toolName && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { color: "white", dimColor: true, children: [
|
|
1233
|
+
"[",
|
|
1234
|
+
toolName,
|
|
1235
|
+
"]"
|
|
1236
|
+
] })
|
|
1237
|
+
] }),
|
|
1238
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink5.Text, { children: " " }),
|
|
1239
|
+
summaries.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Box, { flexDirection: "column", children: [
|
|
1240
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { color: "green", children: [
|
|
1241
|
+
" ",
|
|
1242
|
+
"\u2713",
|
|
1243
|
+
" ",
|
|
1244
|
+
s.line
|
|
1245
|
+
] }),
|
|
1246
|
+
s.diffLines && s.diffLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToolDiffBlock, { file: s.diffFile, lines: s.diffLines })
|
|
1247
|
+
] }, i))
|
|
1248
|
+
] });
|
|
1249
|
+
}
|
|
1250
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
1251
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1252
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Box, { children: [
|
|
1253
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { color: "white", bold: true, children: [
|
|
1254
|
+
"Tool:",
|
|
1255
|
+
" "
|
|
1256
|
+
] }),
|
|
1257
|
+
toolName && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { color: "white", dimColor: true, children: [
|
|
1258
|
+
"[",
|
|
1259
|
+
toolName,
|
|
1260
|
+
"]"
|
|
1261
|
+
] })
|
|
1262
|
+
] }),
|
|
1263
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink5.Text, { children: " " }),
|
|
1264
|
+
lines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { color: "green", children: [
|
|
1265
|
+
" ",
|
|
1266
|
+
"\u2713",
|
|
1267
|
+
" ",
|
|
1268
|
+
line
|
|
1269
|
+
] }, i))
|
|
1270
|
+
] });
|
|
1271
|
+
}
|
|
1272
|
+
var MessageItem = import_react7.default.memo(function MessageItem2({
|
|
1273
|
+
message
|
|
1274
|
+
}) {
|
|
1275
|
+
if ((0, import_agent_core7.isToolMessage)(message)) {
|
|
1276
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToolMessage, { message });
|
|
1277
|
+
}
|
|
1278
|
+
const content = message.content ?? "";
|
|
1279
|
+
const isInterrupted = message.state === "interrupted";
|
|
1280
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1281
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink5.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RoleLabel, { role: message.role }) }),
|
|
1282
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink5.Text, { children: " " }),
|
|
1283
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink5.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink5.Text, { wrap: "wrap", children: (0, import_agent_core7.isAssistantMessage)(message) ? renderMarkdown(content + (isInterrupted ? "\n\n_(interrupted)_" : "")) : content }) })
|
|
1284
|
+
] });
|
|
1285
|
+
});
|
|
1286
|
+
function ToolSummaryEntry({ entry }) {
|
|
1287
|
+
const data = entry.data;
|
|
1288
|
+
const tools = data?.tools;
|
|
1289
|
+
const lines = data?.summary?.split("\n") ?? [];
|
|
1290
|
+
if (tools && tools.length > 0) {
|
|
1291
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1292
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink5.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { color: "white", bold: true, children: [
|
|
1293
|
+
"Tool:",
|
|
1294
|
+
" "
|
|
1295
|
+
] }) }),
|
|
1296
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink5.Text, { children: " " }),
|
|
1297
|
+
tools.map((tool, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Box, { flexDirection: "column", children: [
|
|
1298
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { color: getToolSummaryColor(tool), children: [
|
|
1299
|
+
" ",
|
|
1300
|
+
getToolSummaryLabel(tool)
|
|
1301
|
+
] }),
|
|
1302
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToolCommandOutput, { tool }),
|
|
1303
|
+
tool.diffLines && tool.diffLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToolDiffBlock, { file: tool.diffFile, lines: tool.diffLines })
|
|
1304
|
+
] }, i))
|
|
1305
|
+
] });
|
|
1306
|
+
}
|
|
1307
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1308
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink5.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { color: "white", bold: true, children: [
|
|
1309
|
+
"Tool:",
|
|
1310
|
+
" "
|
|
1311
|
+
] }) }),
|
|
1312
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink5.Text, { children: " " }),
|
|
1313
|
+
lines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { color: "green", children: [
|
|
1314
|
+
" ",
|
|
1315
|
+
line
|
|
1316
|
+
] }, i))
|
|
1317
|
+
] });
|
|
1318
|
+
}
|
|
1319
|
+
function EventEntry({ entry }) {
|
|
1320
|
+
const eventData = entry.data;
|
|
1321
|
+
const eventMessage = typeof eventData?.message === "string" ? eventData.message : typeof eventData?.content === "string" ? eventData.content : entry.type;
|
|
1322
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1323
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink5.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_ink5.Text, { color: "yellow", bold: true, children: [
|
|
1324
|
+
"System:",
|
|
1325
|
+
" "
|
|
1326
|
+
] }) }),
|
|
1327
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink5.Text, { children: " " }),
|
|
1328
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink5.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink5.Text, { wrap: "wrap", children: eventMessage }) })
|
|
1329
|
+
] });
|
|
1330
|
+
}
|
|
1331
|
+
function EntryItem({ entry }) {
|
|
1332
|
+
if (entry.category === "chat") {
|
|
1333
|
+
const message = entry.data;
|
|
1334
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MessageItem, { message });
|
|
1335
|
+
}
|
|
1336
|
+
if (entry.type === "tool-summary") {
|
|
1337
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToolSummaryEntry, { entry });
|
|
1338
|
+
}
|
|
1339
|
+
if (entry.type === "usage-summary") {
|
|
1340
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UsageSummaryEntry, { entry });
|
|
1341
|
+
}
|
|
1342
|
+
if (entry.type === "tool-start" || entry.type === "tool-end") {
|
|
1343
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, {});
|
|
1344
|
+
}
|
|
1345
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(EventEntry, { entry });
|
|
1346
|
+
}
|
|
1347
|
+
function MessageList({ history }) {
|
|
1348
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink5.Box, { flexDirection: "column", children: history.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(EntryItem, { entry }, entry.id)) });
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// src/SessionStatusBar.tsx
|
|
1352
|
+
var import_react8 = require("react");
|
|
1353
|
+
var import_agent_core9 = require("@robota-sdk/agent-core");
|
|
1354
|
+
|
|
1355
|
+
// src/StatusBar.tsx
|
|
1356
|
+
var import_ink6 = require("ink");
|
|
1357
|
+
var import_agent_core8 = require("@robota-sdk/agent-core");
|
|
1358
|
+
|
|
1359
|
+
// src/status-activity.ts
|
|
1360
|
+
var NO_ACTIVE_ITEMS = 0;
|
|
1361
|
+
function formatStatusActivity(input) {
|
|
1362
|
+
const base = getPrimaryActivity(input);
|
|
1363
|
+
const segments = input.hasPendingPrompt && base.kind !== "queued" ? ["queued"] : [];
|
|
1364
|
+
const text = [base.label, ...segments].join(" \xB7 ");
|
|
1365
|
+
return { ...base, segments, text };
|
|
1366
|
+
}
|
|
1367
|
+
function getPrimaryActivity(input) {
|
|
1368
|
+
if (input.activeToolCount > NO_ACTIVE_ITEMS) {
|
|
1369
|
+
return {
|
|
1370
|
+
kind: "tools",
|
|
1371
|
+
label: `Tools x${input.activeToolCount}`,
|
|
1372
|
+
color: "cyan"
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
if (input.isThinking) {
|
|
1376
|
+
return {
|
|
1377
|
+
kind: "thinking",
|
|
1378
|
+
label: "Thinking",
|
|
1379
|
+
color: "yellow"
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
if (input.activeBackgroundTaskCount > NO_ACTIVE_ITEMS) {
|
|
1383
|
+
return {
|
|
1384
|
+
kind: "background",
|
|
1385
|
+
label: `Background x${input.activeBackgroundTaskCount}`,
|
|
1386
|
+
color: "cyan"
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
if (input.hasPendingPrompt) {
|
|
1390
|
+
return {
|
|
1391
|
+
kind: "queued",
|
|
1392
|
+
label: "Queued",
|
|
1393
|
+
color: "yellow"
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
return {
|
|
1397
|
+
kind: "idle",
|
|
1398
|
+
label: "Idle",
|
|
1399
|
+
color: "gray"
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// src/StatusBar.tsx
|
|
1404
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1405
|
+
var CONTEXT_YELLOW_THRESHOLD = 70;
|
|
1406
|
+
var CONTEXT_RED_THRESHOLD = 90;
|
|
1407
|
+
function getContextColor(percentage) {
|
|
1408
|
+
if (percentage >= CONTEXT_RED_THRESHOLD) return "red";
|
|
1409
|
+
if (percentage >= CONTEXT_YELLOW_THRESHOLD) return "yellow";
|
|
1410
|
+
return "green";
|
|
1411
|
+
}
|
|
1412
|
+
function StatusActivityText({
|
|
1413
|
+
isThinking,
|
|
1414
|
+
activeToolCount,
|
|
1415
|
+
activeBackgroundTaskCount,
|
|
1416
|
+
hasPendingPrompt
|
|
1417
|
+
}) {
|
|
1418
|
+
const activity = formatStatusActivity({
|
|
1419
|
+
isThinking,
|
|
1420
|
+
activeToolCount,
|
|
1421
|
+
activeBackgroundTaskCount,
|
|
1422
|
+
hasPendingPrompt
|
|
1423
|
+
});
|
|
1424
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink6.Text, { color: activity.color, bold: activity.kind !== "idle", children: activity.text });
|
|
1425
|
+
}
|
|
1426
|
+
function ContextText({
|
|
1427
|
+
percentage,
|
|
1428
|
+
usedTokens,
|
|
1429
|
+
maxTokens
|
|
1430
|
+
}) {
|
|
1431
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ink6.Text, { color: getContextColor(percentage), children: [
|
|
1432
|
+
"Context: ",
|
|
1433
|
+
Math.round(percentage),
|
|
1434
|
+
"% (",
|
|
1435
|
+
(0, import_agent_core8.formatTokenCount)(usedTokens),
|
|
1436
|
+
"/",
|
|
1437
|
+
(0, import_agent_core8.formatTokenCount)(maxTokens),
|
|
1438
|
+
")"
|
|
1439
|
+
] });
|
|
1440
|
+
}
|
|
1441
|
+
function ModeText({ permissionMode }) {
|
|
1442
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
1443
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink6.Text, { color: "cyan", bold: true, children: "Mode:" }),
|
|
1444
|
+
" ",
|
|
1445
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink6.Text, { children: permissionMode })
|
|
1446
|
+
] });
|
|
1447
|
+
}
|
|
1448
|
+
function shouldShowPermissionMode(permissionMode) {
|
|
1449
|
+
return permissionMode !== "default";
|
|
1450
|
+
}
|
|
1451
|
+
function ProviderText({
|
|
1452
|
+
modelName,
|
|
1453
|
+
providerProfileName,
|
|
1454
|
+
providerType
|
|
1455
|
+
}) {
|
|
1456
|
+
if (providerProfileName !== void 0 && providerType !== void 0) {
|
|
1457
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ink6.Text, { dimColor: true, children: [
|
|
1458
|
+
providerProfileName,
|
|
1459
|
+
" (",
|
|
1460
|
+
providerType,
|
|
1461
|
+
") ",
|
|
1462
|
+
modelName
|
|
1463
|
+
] });
|
|
1464
|
+
}
|
|
1465
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink6.Text, { dimColor: true, children: modelName });
|
|
1466
|
+
}
|
|
1467
|
+
function StatusLeft(props) {
|
|
1468
|
+
const shouldShowGitBranch = props.showGitBranch && props.gitBranch !== void 0 && props.gitBranch.length > 0;
|
|
1469
|
+
const showPermissionMode = shouldShowPermissionMode(props.permissionMode);
|
|
1470
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ink6.Text, { children: [
|
|
1471
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1472
|
+
StatusActivityText,
|
|
1473
|
+
{
|
|
1474
|
+
isThinking: props.isThinking,
|
|
1475
|
+
activeToolCount: props.activeToolCount,
|
|
1476
|
+
activeBackgroundTaskCount: props.activeBackgroundTaskCount,
|
|
1477
|
+
hasPendingPrompt: props.hasPendingPrompt
|
|
1478
|
+
}
|
|
1479
|
+
),
|
|
1480
|
+
showPermissionMode && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
1481
|
+
" | ",
|
|
1482
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ModeText, { permissionMode: props.permissionMode })
|
|
1483
|
+
] }),
|
|
1484
|
+
props.sessionName && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
1485
|
+
" | ",
|
|
1486
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink6.Text, { color: "magenta", children: props.sessionName })
|
|
1487
|
+
] }),
|
|
1488
|
+
shouldShowGitBranch && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
1489
|
+
" | ",
|
|
1490
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ink6.Text, { dimColor: true, children: [
|
|
1491
|
+
"git: ",
|
|
1492
|
+
props.gitBranch
|
|
1493
|
+
] })
|
|
1494
|
+
] }),
|
|
1495
|
+
" | ",
|
|
1496
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1497
|
+
ProviderText,
|
|
1498
|
+
{
|
|
1499
|
+
modelName: props.modelName,
|
|
1500
|
+
providerProfileName: props.providerProfileName,
|
|
1501
|
+
providerType: props.providerType
|
|
1502
|
+
}
|
|
1503
|
+
),
|
|
1504
|
+
" | ",
|
|
1505
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1506
|
+
ContextText,
|
|
1507
|
+
{
|
|
1508
|
+
percentage: props.contextPercentage,
|
|
1509
|
+
usedTokens: props.contextUsedTokens,
|
|
1510
|
+
maxTokens: props.contextMaxTokens
|
|
1511
|
+
}
|
|
1512
|
+
)
|
|
1513
|
+
] });
|
|
1514
|
+
}
|
|
1515
|
+
function StatusBar({
|
|
1516
|
+
permissionMode,
|
|
1517
|
+
modelName,
|
|
1518
|
+
providerProfileName,
|
|
1519
|
+
providerType,
|
|
1520
|
+
sessionId: _sessionId,
|
|
1521
|
+
isThinking,
|
|
1522
|
+
activeToolCount = 0,
|
|
1523
|
+
activeBackgroundTaskCount = 0,
|
|
1524
|
+
hasPendingPrompt = false,
|
|
1525
|
+
contextPercentage,
|
|
1526
|
+
contextUsedTokens,
|
|
1527
|
+
contextMaxTokens,
|
|
1528
|
+
sessionName,
|
|
1529
|
+
gitBranch,
|
|
1530
|
+
showGitBranch = true
|
|
1531
|
+
}) {
|
|
1532
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1533
|
+
import_ink6.Box,
|
|
1534
|
+
{
|
|
1535
|
+
borderStyle: "single",
|
|
1536
|
+
borderColor: "gray",
|
|
1537
|
+
paddingLeft: 1,
|
|
1538
|
+
paddingRight: 1,
|
|
1539
|
+
justifyContent: "space-between",
|
|
1540
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1541
|
+
StatusLeft,
|
|
1542
|
+
{
|
|
1543
|
+
permissionMode,
|
|
1544
|
+
modelName,
|
|
1545
|
+
providerProfileName,
|
|
1546
|
+
providerType,
|
|
1547
|
+
isThinking,
|
|
1548
|
+
activeToolCount,
|
|
1549
|
+
activeBackgroundTaskCount,
|
|
1550
|
+
hasPendingPrompt,
|
|
1551
|
+
contextPercentage,
|
|
1552
|
+
contextUsedTokens,
|
|
1553
|
+
contextMaxTokens,
|
|
1554
|
+
sessionName,
|
|
1555
|
+
gitBranch,
|
|
1556
|
+
showGitBranch
|
|
1557
|
+
}
|
|
1558
|
+
)
|
|
1559
|
+
}
|
|
1560
|
+
);
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
// src/SessionStatusBar.tsx
|
|
1564
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1565
|
+
function SessionStatusBar({
|
|
1566
|
+
cwd,
|
|
1567
|
+
permissionMode,
|
|
1568
|
+
modelId,
|
|
1569
|
+
providerProfileName,
|
|
1570
|
+
providerType,
|
|
1571
|
+
sessionId,
|
|
1572
|
+
isThinking,
|
|
1573
|
+
activeToolCount,
|
|
1574
|
+
activeBackgroundTaskCount,
|
|
1575
|
+
hasPendingPrompt,
|
|
1576
|
+
contextState,
|
|
1577
|
+
sessionName,
|
|
1578
|
+
settings
|
|
1579
|
+
}) {
|
|
1580
|
+
const cliAdapter = useTuiCliAdapter();
|
|
1581
|
+
const gitBranch = (0, import_react8.useMemo)(() => cliAdapter.getGitBranch(cwd), [cliAdapter, cwd]);
|
|
1582
|
+
if (!settings.enabled) return null;
|
|
1583
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1584
|
+
StatusBar,
|
|
1585
|
+
{
|
|
1586
|
+
permissionMode,
|
|
1587
|
+
modelName: modelId ? (0, import_agent_core9.getModelName)(modelId) : "",
|
|
1588
|
+
providerProfileName,
|
|
1589
|
+
providerType,
|
|
1590
|
+
sessionId,
|
|
1591
|
+
isThinking,
|
|
1592
|
+
activeToolCount,
|
|
1593
|
+
activeBackgroundTaskCount,
|
|
1594
|
+
hasPendingPrompt,
|
|
1595
|
+
contextPercentage: contextState.percentage,
|
|
1596
|
+
contextUsedTokens: contextState.usedTokens,
|
|
1597
|
+
contextMaxTokens: contextState.maxTokens,
|
|
1598
|
+
sessionName,
|
|
1599
|
+
gitBranch,
|
|
1600
|
+
showGitBranch: settings.gitBranch
|
|
1601
|
+
}
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
// src/InputArea.tsx
|
|
1606
|
+
var import_react13 = require("react");
|
|
1607
|
+
var import_ink10 = require("ink");
|
|
1608
|
+
|
|
1609
|
+
// src/CjkTextInput.tsx
|
|
1610
|
+
var import_react9 = require("react");
|
|
1611
|
+
var import_ink7 = require("ink");
|
|
1612
|
+
var import_chalk = __toESM(require("chalk"), 1);
|
|
1613
|
+
|
|
1614
|
+
// src/flows/cjk-text-input-flow.ts
|
|
1615
|
+
var import_string_width = __toESM(require("string-width"), 1);
|
|
1616
|
+
var PASTE_START = "[200~";
|
|
1617
|
+
var PASTE_END = "[201~";
|
|
1618
|
+
var LAST_ASCII_CONTROL_CODE = 31;
|
|
1619
|
+
var DELETE_CONTROL_CODE = 127;
|
|
1620
|
+
function createCjkTextInputFlowState(value) {
|
|
1621
|
+
return { value, cursor: value.length, isPasting: false, pasteBuffer: "" };
|
|
1622
|
+
}
|
|
1623
|
+
function syncCjkTextInputFlowState(state, value, cursorHint) {
|
|
1624
|
+
if (value === state.value) {
|
|
1625
|
+
return state;
|
|
1626
|
+
}
|
|
1627
|
+
return {
|
|
1628
|
+
...state,
|
|
1629
|
+
value,
|
|
1630
|
+
cursor: cursorHint != null ? Math.min(cursorHint, value.length) : value.length
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
function applyCjkTextInput(state, input, key, options) {
|
|
1634
|
+
const pasteResult = applyPasteBoundaryInput(state, input, options);
|
|
1635
|
+
if (pasteResult !== void 0) return pasteResult;
|
|
1636
|
+
const controlResult = applyControlInput(state, input, key, options);
|
|
1637
|
+
if (controlResult !== void 0) return controlResult;
|
|
1638
|
+
const cursorResult = applyCursorInput(state, key, options);
|
|
1639
|
+
if (cursorResult !== void 0) return cursorResult;
|
|
1640
|
+
return insertPrintableInput(state, input);
|
|
1641
|
+
}
|
|
1642
|
+
function applyCjkTextPaste(state, text, options) {
|
|
1643
|
+
const normalizedText = text.replace(/\r\n?/g, "\n");
|
|
1644
|
+
if (normalizedText.length === 0) {
|
|
1645
|
+
return { state, effect: { type: "none" } };
|
|
1646
|
+
}
|
|
1647
|
+
if (normalizedText.includes("\n") && options.canPaste) {
|
|
1648
|
+
return { state, effect: { type: "paste", text: normalizedText, cursor: state.cursor } };
|
|
1649
|
+
}
|
|
1650
|
+
return insertPrintableInput(state, normalizedText);
|
|
1651
|
+
}
|
|
1652
|
+
function applyPasteBoundaryInput(state, input, options) {
|
|
1653
|
+
if (input === PASTE_START || input.startsWith(PASTE_START)) {
|
|
1654
|
+
return startBracketedPaste(state, input);
|
|
1655
|
+
}
|
|
1656
|
+
if (state.isPasting) {
|
|
1657
|
+
return continueBracketedPaste(state, input, options);
|
|
1658
|
+
}
|
|
1659
|
+
return void 0;
|
|
1660
|
+
}
|
|
1661
|
+
function applyControlInput(state, input, key, options) {
|
|
1662
|
+
if (key.ctrl === true && input === "c" || key.tab === true) {
|
|
1663
|
+
return { state, effect: { type: "none" } };
|
|
1664
|
+
}
|
|
1665
|
+
if (key.return === true) {
|
|
1666
|
+
return { state, effect: { type: "submit", value: state.value } };
|
|
1667
|
+
}
|
|
1668
|
+
if (input.length > 1 && (input.includes("\n") || input.includes("\r")) && options.canPaste) {
|
|
1669
|
+
return {
|
|
1670
|
+
state,
|
|
1671
|
+
effect: { type: "paste", text: input.replace(/\r\n?/g, "\n"), cursor: state.cursor }
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
return void 0;
|
|
1675
|
+
}
|
|
1676
|
+
function applyCursorInput(state, key, options) {
|
|
1677
|
+
if (key.upArrow === true || key.downArrow === true) {
|
|
1678
|
+
if (options.enableVerticalNavigation === false) {
|
|
1679
|
+
return { state, effect: { type: "none" } };
|
|
1680
|
+
}
|
|
1681
|
+
return moveCursorVertically(
|
|
1682
|
+
state,
|
|
1683
|
+
key.upArrow === true ? "up" : "down",
|
|
1684
|
+
options.availableWidth
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1687
|
+
if (key.leftArrow === true) {
|
|
1688
|
+
return moveCursorHorizontally(state, "left");
|
|
1689
|
+
}
|
|
1690
|
+
if (key.rightArrow === true) {
|
|
1691
|
+
return moveCursorHorizontally(state, "right");
|
|
1692
|
+
}
|
|
1693
|
+
if (key.backspace === true || key.delete === true) {
|
|
1694
|
+
return deleteBeforeCursor(state);
|
|
1695
|
+
}
|
|
1696
|
+
return void 0;
|
|
1697
|
+
}
|
|
1698
|
+
function filterPrintable(input) {
|
|
1699
|
+
if (!input || input.length === 0) return "";
|
|
1700
|
+
let output = "";
|
|
1701
|
+
for (const char of input) {
|
|
1702
|
+
const code = char.charCodeAt(0);
|
|
1703
|
+
if (code > LAST_ASCII_CONTROL_CODE && code !== DELETE_CONTROL_CODE) {
|
|
1704
|
+
output += char;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
return output;
|
|
1708
|
+
}
|
|
1709
|
+
function insertAtCursor(value, cursor, input) {
|
|
1710
|
+
const next = value.slice(0, cursor) + input + value.slice(cursor);
|
|
1711
|
+
return { value: next, cursor: cursor + input.length };
|
|
1712
|
+
}
|
|
1713
|
+
function displayOffset(chars, charIndex, width) {
|
|
1714
|
+
let offset = 0;
|
|
1715
|
+
for (let i = 0; i < charIndex && i < chars.length; i++) {
|
|
1716
|
+
const w = (0, import_string_width.default)(chars[i]);
|
|
1717
|
+
const col = offset % width;
|
|
1718
|
+
if (col + w > width) offset += width - col;
|
|
1719
|
+
offset += w;
|
|
1720
|
+
}
|
|
1721
|
+
return offset;
|
|
1722
|
+
}
|
|
1723
|
+
function charIndexAtDisplayOffset(chars, target, width) {
|
|
1724
|
+
let offset = 0;
|
|
1725
|
+
for (let i = 0; i < chars.length; i++) {
|
|
1726
|
+
if (offset >= target) return i;
|
|
1727
|
+
const w = (0, import_string_width.default)(chars[i]);
|
|
1728
|
+
const col = offset % width;
|
|
1729
|
+
if (col + w > width) offset += width - col;
|
|
1730
|
+
offset += w;
|
|
1731
|
+
}
|
|
1732
|
+
return chars.length;
|
|
1733
|
+
}
|
|
1734
|
+
function startBracketedPaste(state, input) {
|
|
1735
|
+
return {
|
|
1736
|
+
state: { ...state, isPasting: true, pasteBuffer: input.slice(PASTE_START.length) },
|
|
1737
|
+
effect: { type: "none" }
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
function continueBracketedPaste(state, input, options) {
|
|
1741
|
+
if (input !== PASTE_END && !input.includes(PASTE_END)) {
|
|
1742
|
+
return {
|
|
1743
|
+
state: { ...state, pasteBuffer: state.pasteBuffer + input },
|
|
1744
|
+
effect: { type: "none" }
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
const beforeMarker = input.split(PASTE_END)[0] ?? "";
|
|
1748
|
+
const nextState = { ...state, isPasting: false, pasteBuffer: "" };
|
|
1749
|
+
return applyCjkTextPaste(nextState, state.pasteBuffer + beforeMarker, options);
|
|
1750
|
+
}
|
|
1751
|
+
function moveCursorVertically(state, direction, availableWidth) {
|
|
1752
|
+
if (!availableWidth || availableWidth <= 0) {
|
|
1753
|
+
return { state, effect: { type: "none" } };
|
|
1754
|
+
}
|
|
1755
|
+
const chars = [...state.value];
|
|
1756
|
+
const offset = displayOffset(chars, state.cursor, availableWidth);
|
|
1757
|
+
const target = direction === "up" ? offset - availableWidth : offset + availableWidth;
|
|
1758
|
+
if (target < 0) {
|
|
1759
|
+
return { state, effect: { type: "none" } };
|
|
1760
|
+
}
|
|
1761
|
+
const cursor = charIndexAtDisplayOffset(chars, target, availableWidth);
|
|
1762
|
+
if (cursor === state.cursor) {
|
|
1763
|
+
return { state, effect: { type: "none" } };
|
|
1764
|
+
}
|
|
1765
|
+
return { state: { ...state, cursor }, effect: { type: "render" } };
|
|
1766
|
+
}
|
|
1767
|
+
function moveCursorHorizontally(state, direction) {
|
|
1768
|
+
if (direction === "left" && state.cursor > 0) {
|
|
1769
|
+
return { state: { ...state, cursor: state.cursor - 1 }, effect: { type: "render" } };
|
|
1770
|
+
}
|
|
1771
|
+
if (direction === "right" && state.cursor < state.value.length) {
|
|
1772
|
+
return { state: { ...state, cursor: state.cursor + 1 }, effect: { type: "render" } };
|
|
1773
|
+
}
|
|
1774
|
+
return { state, effect: { type: "none" } };
|
|
1775
|
+
}
|
|
1776
|
+
function deleteBeforeCursor(state) {
|
|
1777
|
+
if (state.cursor === 0) {
|
|
1778
|
+
return { state, effect: { type: "none" } };
|
|
1779
|
+
}
|
|
1780
|
+
const value = state.value.slice(0, state.cursor - 1) + state.value.slice(state.cursor);
|
|
1781
|
+
return {
|
|
1782
|
+
state: { ...state, value, cursor: state.cursor - 1 },
|
|
1783
|
+
effect: { type: "change", value }
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
function insertPrintableInput(state, input) {
|
|
1787
|
+
const printable = filterPrintable(input);
|
|
1788
|
+
if (printable.length === 0) {
|
|
1789
|
+
return { state, effect: { type: "none" } };
|
|
1790
|
+
}
|
|
1791
|
+
const result = insertAtCursor(state.value, state.cursor, printable);
|
|
1792
|
+
return {
|
|
1793
|
+
state: { ...state, value: result.value, cursor: result.cursor },
|
|
1794
|
+
effect: { type: "change", value: result.value }
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
// src/CjkTextInput.tsx
|
|
1799
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1800
|
+
function CjkTextInput({
|
|
1801
|
+
value,
|
|
1802
|
+
onChange,
|
|
1803
|
+
onSubmit,
|
|
1804
|
+
onPaste,
|
|
1805
|
+
placeholder = "",
|
|
1806
|
+
focus = true,
|
|
1807
|
+
showCursor = true,
|
|
1808
|
+
availableWidth,
|
|
1809
|
+
cursorHint = null,
|
|
1810
|
+
enableVerticalNavigation = true
|
|
1811
|
+
}) {
|
|
1812
|
+
const stateRef = (0, import_react9.useRef)(createCjkTextInputFlowState(value));
|
|
1813
|
+
const [, forceRender] = (0, import_react9.useState)(0);
|
|
1814
|
+
stateRef.current = syncCjkTextInputFlowState(stateRef.current, value, cursorHint);
|
|
1815
|
+
useCjkTextInputHandlers({
|
|
1816
|
+
stateRef,
|
|
1817
|
+
onChange,
|
|
1818
|
+
onSubmit,
|
|
1819
|
+
onPaste,
|
|
1820
|
+
availableWidth,
|
|
1821
|
+
focus,
|
|
1822
|
+
enableVerticalNavigation,
|
|
1823
|
+
forceRender
|
|
1824
|
+
});
|
|
1825
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { children: renderWithCursor(
|
|
1826
|
+
stateRef.current.value,
|
|
1827
|
+
stateRef.current.cursor,
|
|
1828
|
+
placeholder,
|
|
1829
|
+
showCursor && focus
|
|
1830
|
+
) });
|
|
1831
|
+
}
|
|
1832
|
+
function useCjkTextInputHandlers(options) {
|
|
1833
|
+
(0, import_ink7.usePaste)(
|
|
1834
|
+
(text) => {
|
|
1835
|
+
applyCjkFlowSafely(
|
|
1836
|
+
options,
|
|
1837
|
+
() => applyCjkTextPaste(options.stateRef.current, text, createFlowOptions(options))
|
|
1838
|
+
);
|
|
1839
|
+
},
|
|
1840
|
+
{ isActive: options.focus }
|
|
1841
|
+
);
|
|
1842
|
+
(0, import_ink7.useInput)(
|
|
1843
|
+
(input, key) => {
|
|
1844
|
+
applyCjkFlowSafely(
|
|
1845
|
+
options,
|
|
1846
|
+
() => applyCjkTextInput(options.stateRef.current, input, key, createFlowOptions(options))
|
|
1847
|
+
);
|
|
1848
|
+
},
|
|
1849
|
+
{ isActive: options.focus }
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1852
|
+
function createFlowOptions(options) {
|
|
1853
|
+
return {
|
|
1854
|
+
availableWidth: options.availableWidth,
|
|
1855
|
+
canPaste: options.onPaste !== void 0,
|
|
1856
|
+
enableVerticalNavigation: options.enableVerticalNavigation
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
function applyCjkFlowSafely(options, run) {
|
|
1860
|
+
try {
|
|
1861
|
+
const result = run();
|
|
1862
|
+
options.stateRef.current = result.state;
|
|
1863
|
+
applyCjkTextInputEffect(
|
|
1864
|
+
result.effect,
|
|
1865
|
+
options.onChange,
|
|
1866
|
+
options.onSubmit,
|
|
1867
|
+
options.onPaste,
|
|
1868
|
+
options.forceRender
|
|
1869
|
+
);
|
|
1870
|
+
} catch {
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
function applyCjkTextInputEffect(effect, onChange, onSubmit, onPaste, forceRender) {
|
|
1874
|
+
if (effect.type === "change") {
|
|
1875
|
+
onChange(effect.value);
|
|
1876
|
+
} else if (effect.type === "submit") {
|
|
1877
|
+
onSubmit?.(effect.value);
|
|
1878
|
+
} else if (effect.type === "paste") {
|
|
1879
|
+
onPaste?.(effect.text, effect.cursor);
|
|
1880
|
+
} else if (effect.type === "render") {
|
|
1881
|
+
forceRender((n) => n + 1);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
|
|
1885
|
+
if (!showCursor) {
|
|
1886
|
+
return value.length > 0 ? value : placeholder ? import_chalk.default.gray(placeholder) : "";
|
|
1887
|
+
}
|
|
1888
|
+
if (value.length === 0) {
|
|
1889
|
+
if (placeholder.length > 0) {
|
|
1890
|
+
return import_chalk.default.inverse(placeholder[0]) + import_chalk.default.gray(placeholder.slice(1));
|
|
1891
|
+
}
|
|
1892
|
+
return import_chalk.default.inverse(" ");
|
|
1893
|
+
}
|
|
1894
|
+
const chars = [...value];
|
|
1895
|
+
let rendered = "";
|
|
1896
|
+
for (let i = 0; i < chars.length; i++) {
|
|
1897
|
+
const char = chars[i] ?? "";
|
|
1898
|
+
rendered += i === cursorOffset ? import_chalk.default.inverse(char) : char;
|
|
1899
|
+
}
|
|
1900
|
+
if (cursorOffset >= chars.length) {
|
|
1901
|
+
rendered += import_chalk.default.inverse(" ");
|
|
1902
|
+
}
|
|
1903
|
+
return rendered;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
// src/WaveText.tsx
|
|
1907
|
+
var import_react10 = require("react");
|
|
1908
|
+
var import_ink8 = require("ink");
|
|
1909
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1910
|
+
var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
|
|
1911
|
+
var INTERVAL_MS = 400;
|
|
1912
|
+
var CHARS_PER_GROUP = 4;
|
|
1913
|
+
function WaveText({ text }) {
|
|
1914
|
+
const [tick, setTick] = (0, import_react10.useState)(0);
|
|
1915
|
+
(0, import_react10.useEffect)(() => {
|
|
1916
|
+
const timer = setInterval(() => {
|
|
1917
|
+
setTick((prev) => prev + 1);
|
|
1918
|
+
}, INTERVAL_MS);
|
|
1919
|
+
return () => clearInterval(timer);
|
|
1920
|
+
}, []);
|
|
1921
|
+
const chars = [...text];
|
|
1922
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { children: chars.map((char, i) => {
|
|
1923
|
+
const group = Math.floor(i / CHARS_PER_GROUP);
|
|
1924
|
+
const colorIndex = (tick + group) % WAVE_COLORS.length;
|
|
1925
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: WAVE_COLORS[colorIndex], children: char }, i);
|
|
1926
|
+
}) });
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
// src/SlashAutocomplete.tsx
|
|
1930
|
+
var import_react11 = require("react");
|
|
1931
|
+
var import_ink9 = require("ink");
|
|
1932
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1933
|
+
var MAX_VISIBLE = 8;
|
|
1934
|
+
var OUTER_CHROME = 4;
|
|
1935
|
+
var MIN_ROW_WIDTH = 40;
|
|
1936
|
+
var NAME_COL_MAX = 20;
|
|
1937
|
+
function useRowWidth() {
|
|
1938
|
+
const { stdout } = (0, import_ink9.useStdout)();
|
|
1939
|
+
const measure = () => Math.max(MIN_ROW_WIDTH, (stdout.columns ?? 80) - OUTER_CHROME);
|
|
1940
|
+
const [width, setWidth] = (0, import_react11.useState)(measure);
|
|
1941
|
+
(0, import_react11.useEffect)(() => {
|
|
1942
|
+
const onResize = () => setWidth(measure());
|
|
1943
|
+
stdout.on("resize", onResize);
|
|
1944
|
+
return () => {
|
|
1945
|
+
stdout.off("resize", onResize);
|
|
1946
|
+
};
|
|
1947
|
+
}, [stdout]);
|
|
1948
|
+
return width;
|
|
1949
|
+
}
|
|
1950
|
+
function capName(name, colWidth) {
|
|
1951
|
+
return name.length > colWidth ? `${name.slice(0, colWidth - 1)}\u2026` : name.padEnd(colWidth);
|
|
1952
|
+
}
|
|
1953
|
+
function CommandRow(props) {
|
|
1954
|
+
const { cmd, isSelected, showSlash, rowWidth, nameColWidth } = props;
|
|
1955
|
+
const indicator = isSelected ? "\u25B8 " : " ";
|
|
1956
|
+
const nameColor = isSelected ? "cyan" : void 0;
|
|
1957
|
+
const dimmed = !isSelected;
|
|
1958
|
+
const namePart = capName(cmd.name, nameColWidth);
|
|
1959
|
+
const text = showSlash ? `${indicator}/${namePart} ${cmd.description ?? ""}` : `${indicator}${namePart} ${cmd.description ?? ""}`;
|
|
1960
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { width: rowWidth, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: nameColor, dimColor: dimmed, wrap: "truncate-end", children: text }) });
|
|
1961
|
+
}
|
|
1962
|
+
function SlashAutocomplete({
|
|
1963
|
+
commands,
|
|
1964
|
+
selectedIndex,
|
|
1965
|
+
visible,
|
|
1966
|
+
isSubcommandMode
|
|
1967
|
+
}) {
|
|
1968
|
+
const rowWidth = useRowWidth();
|
|
1969
|
+
if (!visible || commands.length === 0) return null;
|
|
1970
|
+
const scrollOffset = computeScrollOffset(selectedIndex, commands.length);
|
|
1971
|
+
const visibleCommands = commands.slice(scrollOffset, scrollOffset + MAX_VISIBLE);
|
|
1972
|
+
const nameColWidth = Math.min(
|
|
1973
|
+
NAME_COL_MAX,
|
|
1974
|
+
Math.max(...visibleCommands.map((c) => c.name.length))
|
|
1975
|
+
);
|
|
1976
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: visibleCommands.map((cmd, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1977
|
+
CommandRow,
|
|
1978
|
+
{
|
|
1979
|
+
cmd,
|
|
1980
|
+
isSelected: scrollOffset + i === selectedIndex,
|
|
1981
|
+
showSlash: !isSubcommandMode,
|
|
1982
|
+
rowWidth,
|
|
1983
|
+
nameColWidth
|
|
1984
|
+
},
|
|
1985
|
+
cmd.name
|
|
1986
|
+
)) });
|
|
1987
|
+
}
|
|
1988
|
+
function computeScrollOffset(selectedIndex, total) {
|
|
1989
|
+
if (total <= MAX_VISIBLE) return 0;
|
|
1990
|
+
if (selectedIndex < MAX_VISIBLE) return 0;
|
|
1991
|
+
const maxOffset = total - MAX_VISIBLE;
|
|
1992
|
+
return Math.min(selectedIndex - MAX_VISIBLE + 1, maxOffset);
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
// src/utils/paste-labels.ts
|
|
1996
|
+
var PASTE_LABEL_RE = /\[Pasted text #(\d+)(?: \+\d+ lines)?\]/g;
|
|
1997
|
+
function expandPasteLabels(text, store) {
|
|
1998
|
+
return text.replace(PASTE_LABEL_RE, (_, id) => store.get(Number(id)) ?? "");
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
// src/hooks/useAutocomplete.ts
|
|
2002
|
+
var import_react12 = __toESM(require("react"), 1);
|
|
2003
|
+
function parseSlashInput(value) {
|
|
2004
|
+
if (!value.startsWith("/")) return { isSlash: false, parentCommand: "", filter: "" };
|
|
2005
|
+
const afterSlash = value.slice(1);
|
|
2006
|
+
const spaceIndex = afterSlash.indexOf(" ");
|
|
2007
|
+
if (spaceIndex === -1) return { isSlash: true, parentCommand: "", filter: afterSlash };
|
|
2008
|
+
const parent = afterSlash.slice(0, spaceIndex);
|
|
2009
|
+
const rest = afterSlash.slice(spaceIndex + 1);
|
|
2010
|
+
return { isSlash: true, parentCommand: parent, filter: rest };
|
|
2011
|
+
}
|
|
2012
|
+
function useAutocomplete(value, registry) {
|
|
2013
|
+
const [selectedIndex, setSelectedIndex] = (0, import_react12.useState)(0);
|
|
2014
|
+
const [dismissed, setDismissed] = (0, import_react12.useState)(false);
|
|
2015
|
+
const prevValueRef = import_react12.default.useRef(value);
|
|
2016
|
+
if (prevValueRef.current !== value) {
|
|
2017
|
+
prevValueRef.current = value;
|
|
2018
|
+
if (dismissed) setDismissed(false);
|
|
2019
|
+
}
|
|
2020
|
+
const parsed = parseSlashInput(value);
|
|
2021
|
+
const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
|
|
2022
|
+
const filteredCommands = (0, import_react12.useMemo)(() => {
|
|
2023
|
+
if (!registry || !parsed.isSlash || dismissed) return [];
|
|
2024
|
+
if (isSubcommandMode) {
|
|
2025
|
+
const subs = registry.getSubcommands(parsed.parentCommand);
|
|
2026
|
+
if (subs.length === 0) return [];
|
|
2027
|
+
if (!parsed.filter) return subs;
|
|
2028
|
+
const lower = parsed.filter.toLowerCase();
|
|
2029
|
+
return subs.filter((c) => c.name.toLowerCase().startsWith(lower));
|
|
2030
|
+
}
|
|
2031
|
+
return registry.getCommands(parsed.filter);
|
|
2032
|
+
}, [registry, parsed.isSlash, parsed.parentCommand, parsed.filter, dismissed, isSubcommandMode]);
|
|
2033
|
+
const showPopup = parsed.isSlash && filteredCommands.length > 0 && !dismissed;
|
|
2034
|
+
if (selectedIndex >= filteredCommands.length && filteredCommands.length > 0) {
|
|
2035
|
+
setSelectedIndex(filteredCommands.length - 1);
|
|
2036
|
+
}
|
|
2037
|
+
return {
|
|
2038
|
+
showPopup,
|
|
2039
|
+
filteredCommands,
|
|
2040
|
+
selectedIndex,
|
|
2041
|
+
setSelectedIndex,
|
|
2042
|
+
isSubcommandMode,
|
|
2043
|
+
setShowPopup: (val) => {
|
|
2044
|
+
if (typeof val === "function") {
|
|
2045
|
+
setDismissed((prev) => {
|
|
2046
|
+
const nextVal = val(!prev);
|
|
2047
|
+
return !nextVal;
|
|
2048
|
+
});
|
|
2049
|
+
} else {
|
|
2050
|
+
setDismissed(!val);
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
// src/flows/input-area-flow.ts
|
|
2057
|
+
function getAutocompletePopupAction(key) {
|
|
2058
|
+
if (key.upArrow === true) return "previous";
|
|
2059
|
+
if (key.downArrow === true) return "next";
|
|
2060
|
+
if (key.escape === true) return "close";
|
|
2061
|
+
if (key.tab === true) return "complete";
|
|
2062
|
+
return void 0;
|
|
2063
|
+
}
|
|
2064
|
+
function getPendingPromptInputAction(key) {
|
|
2065
|
+
if (key.backspace === true || key.delete === true) {
|
|
2066
|
+
return "cancelQueue";
|
|
2067
|
+
}
|
|
2068
|
+
return void 0;
|
|
2069
|
+
}
|
|
2070
|
+
function getPromptHistoryInputAction(key) {
|
|
2071
|
+
if (key.upArrow === true) return "previous";
|
|
2072
|
+
if (key.downArrow === true) return "next";
|
|
2073
|
+
return void 0;
|
|
2074
|
+
}
|
|
2075
|
+
function createPromptHistoryNavigationState() {
|
|
2076
|
+
return { selectedIndex: null, draft: "" };
|
|
2077
|
+
}
|
|
2078
|
+
function navigatePromptHistory(value, history, state, action) {
|
|
2079
|
+
if (history.length === 0) {
|
|
2080
|
+
return { value, cursorHint: value.length, state };
|
|
2081
|
+
}
|
|
2082
|
+
if (action === "previous") {
|
|
2083
|
+
const selectedIndex = state.selectedIndex === null ? history.length - 1 : Math.max(0, state.selectedIndex - 1);
|
|
2084
|
+
const nextValue = history[selectedIndex] ?? value;
|
|
2085
|
+
return {
|
|
2086
|
+
value: nextValue,
|
|
2087
|
+
cursorHint: nextValue.length,
|
|
2088
|
+
state: { selectedIndex, draft: state.selectedIndex === null ? value : state.draft }
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
if (state.selectedIndex === null) {
|
|
2092
|
+
return { value, cursorHint: value.length, state };
|
|
2093
|
+
}
|
|
2094
|
+
if (state.selectedIndex < history.length - 1) {
|
|
2095
|
+
const selectedIndex = state.selectedIndex + 1;
|
|
2096
|
+
const nextValue = history[selectedIndex] ?? value;
|
|
2097
|
+
return {
|
|
2098
|
+
value: nextValue,
|
|
2099
|
+
cursorHint: nextValue.length,
|
|
2100
|
+
state: { ...state, selectedIndex }
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
return {
|
|
2104
|
+
value: state.draft,
|
|
2105
|
+
cursorHint: state.draft.length,
|
|
2106
|
+
state: createPromptHistoryNavigationState()
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
2109
|
+
function appendPromptHistory(history, value) {
|
|
2110
|
+
const prompt = value.trim();
|
|
2111
|
+
if (prompt.length === 0) return [...history];
|
|
2112
|
+
if (history[history.length - 1] === prompt) return [...history];
|
|
2113
|
+
return [...history, prompt];
|
|
2114
|
+
}
|
|
2115
|
+
function extractPromptHistory(entries) {
|
|
2116
|
+
let prompts = [];
|
|
2117
|
+
for (const entry of entries) {
|
|
2118
|
+
if (entry.category !== "chat" || entry.type !== "user") continue;
|
|
2119
|
+
const data = entry.data;
|
|
2120
|
+
if (typeof data?.content !== "string") continue;
|
|
2121
|
+
prompts = appendPromptHistory(prompts, data.content);
|
|
2122
|
+
}
|
|
2123
|
+
return prompts;
|
|
2124
|
+
}
|
|
2125
|
+
function moveAutocompleteSelection(selectedIndex, commandCount, direction) {
|
|
2126
|
+
if (commandCount === 0) return 0;
|
|
2127
|
+
if (direction === "previous") {
|
|
2128
|
+
return selectedIndex > 0 ? selectedIndex - 1 : commandCount - 1;
|
|
2129
|
+
}
|
|
2130
|
+
return selectedIndex < commandCount - 1 ? selectedIndex + 1 : 0;
|
|
2131
|
+
}
|
|
2132
|
+
function resolveTabCompletion(value, command) {
|
|
2133
|
+
const parsed = parseSlashInput(value);
|
|
2134
|
+
if (parsed.parentCommand) {
|
|
2135
|
+
return { type: "insert", value: `/${parsed.parentCommand} ${command.name} ` };
|
|
2136
|
+
}
|
|
2137
|
+
if (command.subcommands && command.subcommands.length > 0) {
|
|
2138
|
+
return { type: "insert", value: `/${command.name} `, selectedIndex: 0 };
|
|
2139
|
+
}
|
|
2140
|
+
return { type: "insert", value: `/${command.name} ` };
|
|
2141
|
+
}
|
|
2142
|
+
function resolveEnterCommandSelection(value, command) {
|
|
2143
|
+
const parsed = parseSlashInput(value);
|
|
2144
|
+
if (parsed.parentCommand) {
|
|
2145
|
+
return { type: "submit", value: `/${parsed.parentCommand} ${command.name}` };
|
|
2146
|
+
}
|
|
2147
|
+
if (command.subcommands && command.subcommands.length > 0) {
|
|
2148
|
+
return { type: "insert", value: `/${command.name} `, selectedIndex: 0 };
|
|
2149
|
+
}
|
|
2150
|
+
return { type: "submit", value: `/${command.name}` };
|
|
2151
|
+
}
|
|
2152
|
+
function createPasteLabelChange(value, cursorPosition, pasteId, text) {
|
|
2153
|
+
const lineCount = text.split("\n").length;
|
|
2154
|
+
const label = `[Pasted text #${pasteId} +${lineCount} lines]`;
|
|
2155
|
+
return {
|
|
2156
|
+
value: value.slice(0, cursorPosition) + label + value.slice(cursorPosition),
|
|
2157
|
+
cursorHint: cursorPosition + label.length,
|
|
2158
|
+
label,
|
|
2159
|
+
lineCount
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
function shouldSubmitInput(text) {
|
|
2163
|
+
return text.trim().length > 0;
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
// src/InputArea.tsx
|
|
2167
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
2168
|
+
var PENDING_PROMPT_DISPLAY_MAX = 50;
|
|
2169
|
+
var PENDING_PROMPT_TAIL_KEEP = 47;
|
|
2170
|
+
var BORDER_HORIZONTAL = 2;
|
|
2171
|
+
var PADDING_LEFT = 1;
|
|
2172
|
+
var PROMPT_WIDTH = 2;
|
|
2173
|
+
var INPUT_AREA_OVERHEAD = BORDER_HORIZONTAL + PADDING_LEFT + PROMPT_WIDTH;
|
|
2174
|
+
var DEFAULT_TERMINAL_COLUMNS = 80;
|
|
2175
|
+
function InputArea({
|
|
2176
|
+
onSubmit,
|
|
2177
|
+
onCancelQueue,
|
|
2178
|
+
isDisabled,
|
|
2179
|
+
isAborting,
|
|
2180
|
+
pendingPrompt,
|
|
2181
|
+
registry,
|
|
2182
|
+
sessionName,
|
|
2183
|
+
history
|
|
2184
|
+
}) {
|
|
2185
|
+
const [value, setValue] = (0, import_react13.useState)("");
|
|
2186
|
+
const [cursorHint, setCursorHint] = (0, import_react13.useState)(null);
|
|
2187
|
+
const [historyState, setHistoryState] = (0, import_react13.useState)(createPromptHistoryNavigationState);
|
|
2188
|
+
const [localPromptHistory, setLocalPromptHistory] = (0, import_react13.useState)([]);
|
|
2189
|
+
const restoredPromptHistory = (0, import_react13.useMemo)(() => extractPromptHistory(history ?? []), [history]);
|
|
2190
|
+
const promptHistory = (0, import_react13.useMemo)(
|
|
2191
|
+
() => localPromptHistory.reduce(
|
|
2192
|
+
(prompts, prompt) => appendPromptHistory(prompts, prompt),
|
|
2193
|
+
restoredPromptHistory
|
|
2194
|
+
),
|
|
2195
|
+
[restoredPromptHistory, localPromptHistory]
|
|
2196
|
+
);
|
|
2197
|
+
const pasteStore = (0, import_react13.useRef)(/* @__PURE__ */ new Map());
|
|
2198
|
+
const { columns } = (0, import_ink10.useWindowSize)();
|
|
2199
|
+
const terminalColumns = columns > 0 ? columns : DEFAULT_TERMINAL_COLUMNS;
|
|
2200
|
+
const availableWidth = Math.max(1, terminalColumns - INPUT_AREA_OVERHEAD);
|
|
2201
|
+
const pasteIdRef = (0, import_react13.useRef)(0);
|
|
2202
|
+
const {
|
|
2203
|
+
showPopup,
|
|
2204
|
+
filteredCommands,
|
|
2205
|
+
selectedIndex,
|
|
2206
|
+
setSelectedIndex,
|
|
2207
|
+
isSubcommandMode,
|
|
2208
|
+
setShowPopup
|
|
2209
|
+
} = useAutocomplete(value, registry);
|
|
2210
|
+
const handlePaste = (0, import_react13.useCallback)((text, cursorPosition) => {
|
|
2211
|
+
pasteIdRef.current += 1;
|
|
2212
|
+
const id = pasteIdRef.current;
|
|
2213
|
+
pasteStore.current.set(id, text);
|
|
2214
|
+
setValue((prev) => {
|
|
2215
|
+
const change = createPasteLabelChange(prev, cursorPosition, id, text);
|
|
2216
|
+
setCursorHint(change.cursorHint);
|
|
2217
|
+
return change.value;
|
|
2218
|
+
});
|
|
2219
|
+
}, []);
|
|
2220
|
+
const resetHistoryNavigation = (0, import_react13.useCallback)(() => {
|
|
2221
|
+
setHistoryState(createPromptHistoryNavigationState());
|
|
2222
|
+
}, []);
|
|
2223
|
+
const recordPromptHistory = (0, import_react13.useCallback)((prompt) => {
|
|
2224
|
+
setLocalPromptHistory((prev) => appendPromptHistory(prev, prompt));
|
|
2225
|
+
}, []);
|
|
2226
|
+
const submitPrompt = (0, import_react13.useCallback)(
|
|
2227
|
+
(prompt) => {
|
|
2228
|
+
recordPromptHistory(prompt);
|
|
2229
|
+
resetHistoryNavigation();
|
|
2230
|
+
onSubmit(prompt);
|
|
2231
|
+
},
|
|
2232
|
+
[onSubmit, recordPromptHistory, resetHistoryNavigation]
|
|
2233
|
+
);
|
|
2234
|
+
const tabCompleteCommand = (0, import_react13.useCallback)(
|
|
2235
|
+
(cmd) => {
|
|
2236
|
+
const result = resolveTabCompletion(value, cmd);
|
|
2237
|
+
if (result.type === "insert") {
|
|
2238
|
+
setValue(result.value);
|
|
2239
|
+
if (result.selectedIndex !== void 0) {
|
|
2240
|
+
setSelectedIndex(result.selectedIndex);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
},
|
|
2244
|
+
[value, setSelectedIndex]
|
|
2245
|
+
);
|
|
2246
|
+
const enterSelectCommand = (0, import_react13.useCallback)(
|
|
2247
|
+
(cmd) => {
|
|
2248
|
+
const result = resolveEnterCommandSelection(value, cmd);
|
|
2249
|
+
if (result.type === "insert") {
|
|
2250
|
+
setValue(result.value);
|
|
2251
|
+
if (result.selectedIndex !== void 0) {
|
|
2252
|
+
setSelectedIndex(result.selectedIndex);
|
|
2253
|
+
}
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
setValue("");
|
|
2257
|
+
submitPrompt(result.value);
|
|
2258
|
+
},
|
|
2259
|
+
[value, submitPrompt, setSelectedIndex]
|
|
2260
|
+
);
|
|
2261
|
+
const handleSubmit = (0, import_react13.useCallback)(
|
|
2262
|
+
(text) => {
|
|
2263
|
+
if (!shouldSubmitInput(text)) return;
|
|
2264
|
+
if (showPopup && filteredCommands[selectedIndex]) {
|
|
2265
|
+
enterSelectCommand(filteredCommands[selectedIndex]);
|
|
2266
|
+
return;
|
|
2267
|
+
}
|
|
2268
|
+
const expanded = expandPasteLabels(text.trim(), pasteStore.current);
|
|
2269
|
+
setValue("");
|
|
2270
|
+
pasteStore.current.clear();
|
|
2271
|
+
pasteIdRef.current = 0;
|
|
2272
|
+
submitPrompt(expanded);
|
|
2273
|
+
},
|
|
2274
|
+
[showPopup, filteredCommands, selectedIndex, enterSelectCommand, submitPrompt]
|
|
2275
|
+
);
|
|
2276
|
+
(0, import_ink10.useInput)(
|
|
2277
|
+
(_input, key) => {
|
|
2278
|
+
if (!showPopup) return;
|
|
2279
|
+
const action = getAutocompletePopupAction(key);
|
|
2280
|
+
if (action === "previous" || action === "next") {
|
|
2281
|
+
setSelectedIndex(
|
|
2282
|
+
(prev) => moveAutocompleteSelection(prev, filteredCommands.length, action)
|
|
2283
|
+
);
|
|
2284
|
+
} else if (action === "close") {
|
|
2285
|
+
setShowPopup(false);
|
|
2286
|
+
} else if (action === "complete") {
|
|
2287
|
+
const cmd = filteredCommands[selectedIndex];
|
|
2288
|
+
if (cmd) tabCompleteCommand(cmd);
|
|
2289
|
+
}
|
|
2290
|
+
},
|
|
2291
|
+
{ isActive: showPopup && !isDisabled }
|
|
2292
|
+
);
|
|
2293
|
+
(0, import_ink10.useInput)(
|
|
2294
|
+
(_input, key) => {
|
|
2295
|
+
const action = getPromptHistoryInputAction(key);
|
|
2296
|
+
if (!action) return;
|
|
2297
|
+
const result = navigatePromptHistory(value, promptHistory, historyState, action);
|
|
2298
|
+
setValue(result.value);
|
|
2299
|
+
setCursorHint(result.cursorHint);
|
|
2300
|
+
setHistoryState(result.state);
|
|
2301
|
+
},
|
|
2302
|
+
{ isActive: !showPopup && !isDisabled && !pendingPrompt }
|
|
2303
|
+
);
|
|
2304
|
+
(0, import_ink10.useInput)(
|
|
2305
|
+
(_input, key) => {
|
|
2306
|
+
if (getPendingPromptInputAction(key) === "cancelQueue" && pendingPrompt) {
|
|
2307
|
+
onCancelQueue?.();
|
|
2308
|
+
}
|
|
2309
|
+
},
|
|
2310
|
+
{ isActive: !!pendingPrompt }
|
|
2311
|
+
);
|
|
2312
|
+
const borderColor = isAborting ? "yellow" : pendingPrompt ? "cyan" : isDisabled ? "gray" : "green";
|
|
2313
|
+
const innerWidth = Math.max(1, terminalColumns - BORDER_HORIZONTAL);
|
|
2314
|
+
const topBorder = (() => {
|
|
2315
|
+
if (sessionName) {
|
|
2316
|
+
const label = ` "${sessionName}" `;
|
|
2317
|
+
const rightPad = 2;
|
|
2318
|
+
const leftLen = Math.max(0, innerWidth - label.length - rightPad);
|
|
2319
|
+
return { left: "\u250C" + "\u2500".repeat(leftLen), label, right: "\u2500".repeat(rightPad) + "\u2510" };
|
|
2320
|
+
}
|
|
2321
|
+
return { left: "\u250C" + "\u2500".repeat(innerWidth), label: "", right: "\u2510" };
|
|
2322
|
+
})();
|
|
2323
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
|
|
2324
|
+
showPopup && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2325
|
+
SlashAutocomplete,
|
|
2326
|
+
{
|
|
2327
|
+
commands: filteredCommands,
|
|
2328
|
+
selectedIndex,
|
|
2329
|
+
visible: showPopup,
|
|
2330
|
+
isSubcommandMode
|
|
2331
|
+
}
|
|
2332
|
+
),
|
|
2333
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Text, { color: borderColor, children: [
|
|
2334
|
+
topBorder.left,
|
|
2335
|
+
topBorder.label ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { backgroundColor: borderColor, color: "black", bold: true, children: topBorder.label }) : null,
|
|
2336
|
+
topBorder.right
|
|
2337
|
+
] }),
|
|
2338
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Box, { borderStyle: "single", borderTop: false, borderColor, paddingLeft: 1, children: isAborting ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "yellow", children: " Interrupting..." }) : pendingPrompt ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Text, { color: "cyan", children: [
|
|
2339
|
+
" ",
|
|
2340
|
+
"Queued:",
|
|
2341
|
+
" ",
|
|
2342
|
+
pendingPrompt.length > PENDING_PROMPT_DISPLAY_MAX ? pendingPrompt.slice(0, PENDING_PROMPT_TAIL_KEEP) + "..." : pendingPrompt,
|
|
2343
|
+
" ",
|
|
2344
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { dimColor: true, children: "(Backspace to cancel)" })
|
|
2345
|
+
] }) : isDisabled ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(WaveText, { text: " Waiting for response... (ESC to interrupt)" }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { children: [
|
|
2346
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "green", bold: true, children: "> " }),
|
|
2347
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2348
|
+
CjkTextInput,
|
|
2349
|
+
{
|
|
2350
|
+
value,
|
|
2351
|
+
onChange: (v) => {
|
|
2352
|
+
setValue(v);
|
|
2353
|
+
resetHistoryNavigation();
|
|
2354
|
+
setCursorHint(null);
|
|
2355
|
+
},
|
|
2356
|
+
onSubmit: handleSubmit,
|
|
2357
|
+
onPaste: handlePaste,
|
|
2358
|
+
placeholder: "Type a message or /help",
|
|
2359
|
+
availableWidth,
|
|
2360
|
+
cursorHint,
|
|
2361
|
+
enableVerticalNavigation: false
|
|
2362
|
+
}
|
|
2363
|
+
)
|
|
2364
|
+
] }) })
|
|
2365
|
+
] });
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
// src/ConfirmPrompt.tsx
|
|
2369
|
+
var import_react14 = require("react");
|
|
2370
|
+
var import_ink11 = require("ink");
|
|
2371
|
+
|
|
2372
|
+
// src/flows/selection-flow.ts
|
|
2373
|
+
function createSelectionFlowState() {
|
|
2374
|
+
return { selectedIndex: 0, scrollOffset: 0, resolved: false };
|
|
2375
|
+
}
|
|
2376
|
+
function getVerticalSelectionInputAction(key) {
|
|
2377
|
+
if (key.escape === true) return "cancel";
|
|
2378
|
+
if (key.upArrow === true) return "previous";
|
|
2379
|
+
if (key.downArrow === true) return "next";
|
|
2380
|
+
if (key.return === true) return "select";
|
|
2381
|
+
return void 0;
|
|
2382
|
+
}
|
|
2383
|
+
function getDirectionalSelectionInputAction(key) {
|
|
2384
|
+
if (key.escape === true) return "cancel";
|
|
2385
|
+
if (key.leftArrow === true || key.upArrow === true) return "previous";
|
|
2386
|
+
if (key.rightArrow === true || key.downArrow === true) return "next";
|
|
2387
|
+
if (key.return === true) return "select";
|
|
2388
|
+
return void 0;
|
|
2389
|
+
}
|
|
2390
|
+
function applySelectionInput(state, action, options) {
|
|
2391
|
+
if (state.resolved) {
|
|
2392
|
+
return { state, effect: { type: "none" } };
|
|
2393
|
+
}
|
|
2394
|
+
if (action === "cancel") {
|
|
2395
|
+
return { state: { ...state, resolved: true }, effect: { type: "cancel" } };
|
|
2396
|
+
}
|
|
2397
|
+
if (options.enabled === false || options.itemCount === 0) {
|
|
2398
|
+
return { state, effect: { type: "none" } };
|
|
2399
|
+
}
|
|
2400
|
+
if (action === "select") {
|
|
2401
|
+
const index = clampIndex(state.selectedIndex, options.itemCount);
|
|
2402
|
+
return {
|
|
2403
|
+
state: { ...state, selectedIndex: index, resolved: true },
|
|
2404
|
+
effect: { type: "select", index }
|
|
2405
|
+
};
|
|
2406
|
+
}
|
|
2407
|
+
const selectedIndex = moveSelection(state.selectedIndex, action, options);
|
|
2408
|
+
const scrollOffset = resolveScrollOffset(selectedIndex, state.scrollOffset, options);
|
|
2409
|
+
return { state: { ...state, selectedIndex, scrollOffset }, effect: { type: "none" } };
|
|
2410
|
+
}
|
|
2411
|
+
function normalizeSelectionState(state, options) {
|
|
2412
|
+
if (options.itemCount === 0) {
|
|
2413
|
+
return { ...state, selectedIndex: 0, scrollOffset: 0 };
|
|
2414
|
+
}
|
|
2415
|
+
const selectedIndex = clampIndex(state.selectedIndex, options.itemCount);
|
|
2416
|
+
const scrollOffset = resolveScrollOffset(selectedIndex, state.scrollOffset, options);
|
|
2417
|
+
if (selectedIndex === state.selectedIndex && scrollOffset === state.scrollOffset) {
|
|
2418
|
+
return state;
|
|
2419
|
+
}
|
|
2420
|
+
return {
|
|
2421
|
+
...state,
|
|
2422
|
+
selectedIndex,
|
|
2423
|
+
scrollOffset
|
|
2424
|
+
};
|
|
2425
|
+
}
|
|
2426
|
+
function moveSelection(selectedIndex, action, options) {
|
|
2427
|
+
if (action === "previous") {
|
|
2428
|
+
if (options.wrap === true && selectedIndex === 0) return options.itemCount - 1;
|
|
2429
|
+
return Math.max(0, selectedIndex - 1);
|
|
2430
|
+
}
|
|
2431
|
+
if (options.wrap === true && selectedIndex === options.itemCount - 1) return 0;
|
|
2432
|
+
return Math.min(options.itemCount - 1, selectedIndex + 1);
|
|
2433
|
+
}
|
|
2434
|
+
function resolveScrollOffset(selectedIndex, scrollOffset, options) {
|
|
2435
|
+
const maxVisible = options.maxVisible ?? options.itemCount;
|
|
2436
|
+
if (maxVisible <= 0) return 0;
|
|
2437
|
+
if (selectedIndex < scrollOffset) return selectedIndex;
|
|
2438
|
+
if (selectedIndex >= scrollOffset + maxVisible) return selectedIndex - maxVisible + 1;
|
|
2439
|
+
return Math.max(0, scrollOffset);
|
|
2440
|
+
}
|
|
2441
|
+
function clampIndex(index, itemCount) {
|
|
2442
|
+
return Math.min(Math.max(index, 0), itemCount - 1);
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
// src/flows/confirm-prompt-flow.ts
|
|
2446
|
+
function getConfirmPromptInputAction(input, key, optionCount) {
|
|
2447
|
+
const action = getDirectionalSelectionInputAction({ ...key, escape: false });
|
|
2448
|
+
if (action !== void 0) {
|
|
2449
|
+
return action;
|
|
2450
|
+
}
|
|
2451
|
+
if (optionCount === 2 && input === "y") {
|
|
2452
|
+
return { type: "shortcut", index: 0 };
|
|
2453
|
+
}
|
|
2454
|
+
if (optionCount === 2 && input === "n") {
|
|
2455
|
+
return { type: "shortcut", index: 1 };
|
|
2456
|
+
}
|
|
2457
|
+
return void 0;
|
|
2458
|
+
}
|
|
2459
|
+
function applyConfirmPromptInput(state, action, optionCount) {
|
|
2460
|
+
if (state.resolved) {
|
|
2461
|
+
return { state, effect: { type: "none" } };
|
|
2462
|
+
}
|
|
2463
|
+
if (typeof action !== "string") {
|
|
2464
|
+
return {
|
|
2465
|
+
state: { ...state, selectedIndex: action.index, resolved: true },
|
|
2466
|
+
effect: { type: "select", index: action.index }
|
|
2467
|
+
};
|
|
2468
|
+
}
|
|
2469
|
+
return applySelectionInput(state, action, { itemCount: optionCount });
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
// src/ConfirmPrompt.tsx
|
|
2473
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
2474
|
+
function ConfirmPrompt({
|
|
2475
|
+
message,
|
|
2476
|
+
options = ["Yes", "No"],
|
|
2477
|
+
onSelect
|
|
2478
|
+
}) {
|
|
2479
|
+
const [state, setState] = (0, import_react14.useState)(() => createSelectionFlowState());
|
|
2480
|
+
const stateRef = (0, import_react14.useRef)(state);
|
|
2481
|
+
const applyAction = (0, import_react14.useCallback)(
|
|
2482
|
+
(action) => {
|
|
2483
|
+
const result = applyConfirmPromptInput(stateRef.current, action, options.length);
|
|
2484
|
+
stateRef.current = result.state;
|
|
2485
|
+
setState(result.state);
|
|
2486
|
+
if (result.effect.type === "select") {
|
|
2487
|
+
onSelect(result.effect.index);
|
|
2488
|
+
}
|
|
2489
|
+
},
|
|
2490
|
+
[onSelect, options.length]
|
|
2491
|
+
);
|
|
2492
|
+
(0, import_ink11.useInput)((input, key) => {
|
|
2493
|
+
const action = getConfirmPromptInputAction(input, key, options.length);
|
|
2494
|
+
if (action !== void 0) {
|
|
2495
|
+
applyAction(action);
|
|
2496
|
+
}
|
|
2497
|
+
});
|
|
2498
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
2499
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { color: "yellow", children: message }),
|
|
2500
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Box, { marginTop: 1, children: options.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
2501
|
+
import_ink11.Text,
|
|
2502
|
+
{
|
|
2503
|
+
color: i === state.selectedIndex ? "cyan" : void 0,
|
|
2504
|
+
bold: i === state.selectedIndex,
|
|
2505
|
+
children: [
|
|
2506
|
+
i === state.selectedIndex ? "> " : " ",
|
|
2507
|
+
opt
|
|
2508
|
+
]
|
|
2509
|
+
}
|
|
2510
|
+
) }, opt)) }),
|
|
2511
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { dimColor: true, children: " arrow keys to select, Enter to confirm" })
|
|
2512
|
+
] });
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
// src/InteractivePrompt.tsx
|
|
2516
|
+
var import_ink14 = require("ink");
|
|
2517
|
+
|
|
2518
|
+
// src/ListPicker.tsx
|
|
2519
|
+
var import_react15 = require("react");
|
|
2520
|
+
var import_ink12 = require("ink");
|
|
2521
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
2522
|
+
var DEFAULT_MAX_VISIBLE = 3;
|
|
2523
|
+
function ListPicker({
|
|
2524
|
+
items,
|
|
2525
|
+
renderItem,
|
|
2526
|
+
onSelect,
|
|
2527
|
+
onCancel,
|
|
2528
|
+
maxVisible = DEFAULT_MAX_VISIBLE
|
|
2529
|
+
}) {
|
|
2530
|
+
const [state, setState] = (0, import_react15.useState)(() => createSelectionFlowState());
|
|
2531
|
+
const stateRef = (0, import_react15.useRef)(state);
|
|
2532
|
+
const applyAction = (0, import_react15.useCallback)(
|
|
2533
|
+
(action) => {
|
|
2534
|
+
const result = applySelectionInput(stateRef.current, action, {
|
|
2535
|
+
itemCount: items.length,
|
|
2536
|
+
maxVisible
|
|
2537
|
+
});
|
|
2538
|
+
stateRef.current = result.state;
|
|
2539
|
+
setState(result.state);
|
|
2540
|
+
if (result.effect.type === "cancel") {
|
|
2541
|
+
onCancel();
|
|
2542
|
+
} else if (result.effect.type === "select") {
|
|
2543
|
+
const item = items[result.effect.index];
|
|
2544
|
+
if (item !== void 0) {
|
|
2545
|
+
onSelect(item);
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
},
|
|
2549
|
+
[items, maxVisible, onCancel, onSelect]
|
|
2550
|
+
);
|
|
2551
|
+
(0, import_ink12.useInput)((_input, key) => {
|
|
2552
|
+
const action = getVerticalSelectionInputAction(key);
|
|
2553
|
+
if (action !== void 0) {
|
|
2554
|
+
applyAction(action);
|
|
2555
|
+
}
|
|
2556
|
+
});
|
|
2557
|
+
if (items.length === 0) {
|
|
2558
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Box, {});
|
|
2559
|
+
}
|
|
2560
|
+
const normalizedState = normalizeSelectionState(state, { itemCount: items.length, maxVisible });
|
|
2561
|
+
if (normalizedState !== state) {
|
|
2562
|
+
stateRef.current = normalizedState;
|
|
2563
|
+
}
|
|
2564
|
+
const { selectedIndex, scrollOffset } = normalizedState;
|
|
2565
|
+
const visibleItems = items.slice(scrollOffset, scrollOffset + maxVisible);
|
|
2566
|
+
const hasMore = scrollOffset + maxVisible < items.length;
|
|
2567
|
+
const hasLess = scrollOffset > 0;
|
|
2568
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink12.Box, { flexDirection: "column", children: [
|
|
2569
|
+
hasLess && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink12.Text, { dimColor: true, children: [
|
|
2570
|
+
" \u2191 ",
|
|
2571
|
+
scrollOffset,
|
|
2572
|
+
" more above"
|
|
2573
|
+
] }),
|
|
2574
|
+
visibleItems.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Box, { marginBottom: 1, children: renderItem(item, scrollOffset + index === selectedIndex) }, scrollOffset + index)),
|
|
2575
|
+
hasMore && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink12.Text, { dimColor: true, children: [
|
|
2576
|
+
" \u2193 ",
|
|
2577
|
+
items.length - scrollOffset - maxVisible,
|
|
2578
|
+
" more below"
|
|
2579
|
+
] })
|
|
2580
|
+
] });
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
// src/TextPrompt.tsx
|
|
2584
|
+
var import_react16 = require("react");
|
|
2585
|
+
var import_ink13 = require("ink");
|
|
2586
|
+
|
|
2587
|
+
// src/flows/text-prompt-flow.ts
|
|
2588
|
+
function createTextPromptFlowState() {
|
|
2589
|
+
return { value: "", resolved: false };
|
|
2590
|
+
}
|
|
2591
|
+
function getTextPromptInputAction(input, key) {
|
|
2592
|
+
if (key.escape === true) {
|
|
2593
|
+
return { type: "cancel" };
|
|
2594
|
+
}
|
|
2595
|
+
if (key.return === true) {
|
|
2596
|
+
return { type: "submit" };
|
|
2597
|
+
}
|
|
2598
|
+
if (key.backspace === true || key.delete === true) {
|
|
2599
|
+
return { type: "delete" };
|
|
2600
|
+
}
|
|
2601
|
+
if (input && key.ctrl !== true && key.meta !== true) {
|
|
2602
|
+
return { type: "insert", value: input };
|
|
2603
|
+
}
|
|
2604
|
+
return void 0;
|
|
2605
|
+
}
|
|
2606
|
+
function applyTextPromptInput(state, action, options) {
|
|
2607
|
+
if (state.resolved) {
|
|
2608
|
+
return { state, effect: { type: "none" } };
|
|
2609
|
+
}
|
|
2610
|
+
if (action.type === "cancel") {
|
|
2611
|
+
return { state: { ...state, resolved: true }, effect: { type: "cancel" } };
|
|
2612
|
+
}
|
|
2613
|
+
if (action.type === "delete") {
|
|
2614
|
+
return {
|
|
2615
|
+
state: { ...state, value: state.value.slice(0, -1), error: void 0 },
|
|
2616
|
+
effect: { type: "none" }
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
if (action.type === "insert") {
|
|
2620
|
+
return {
|
|
2621
|
+
state: { ...state, value: state.value + action.value, error: void 0 },
|
|
2622
|
+
effect: { type: "none" }
|
|
2623
|
+
};
|
|
2624
|
+
}
|
|
2625
|
+
return submitTextPromptValue(state, options);
|
|
2626
|
+
}
|
|
2627
|
+
function submitTextPromptValue(state, options) {
|
|
2628
|
+
const trimmed = state.value.trim();
|
|
2629
|
+
if (!trimmed && !options.allowEmpty) {
|
|
2630
|
+
const emptyError = options.validate?.(trimmed);
|
|
2631
|
+
return {
|
|
2632
|
+
state: emptyError ? { ...state, error: emptyError } : state,
|
|
2633
|
+
effect: { type: "none" }
|
|
2634
|
+
};
|
|
2635
|
+
}
|
|
2636
|
+
const error = options.validate?.(trimmed);
|
|
2637
|
+
if (error !== void 0) {
|
|
2638
|
+
return { state: { ...state, error }, effect: { type: "none" } };
|
|
2639
|
+
}
|
|
2640
|
+
return { state: { ...state, resolved: true }, effect: { type: "submit", value: trimmed } };
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
// src/TextPrompt.tsx
|
|
2644
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
2645
|
+
function TextPrompt({
|
|
2646
|
+
title,
|
|
2647
|
+
description,
|
|
2648
|
+
placeholder,
|
|
2649
|
+
onSubmit,
|
|
2650
|
+
onCancel,
|
|
2651
|
+
validate,
|
|
2652
|
+
allowEmpty = false,
|
|
2653
|
+
masked = false
|
|
2654
|
+
}) {
|
|
2655
|
+
const [state, setState] = (0, import_react16.useState)(() => createTextPromptFlowState());
|
|
2656
|
+
const stateRef = (0, import_react16.useRef)(state);
|
|
2657
|
+
const applyAction = (0, import_react16.useCallback)(
|
|
2658
|
+
(action) => {
|
|
2659
|
+
const result = applyTextPromptInput(stateRef.current, action, { allowEmpty, validate });
|
|
2660
|
+
stateRef.current = result.state;
|
|
2661
|
+
setState(result.state);
|
|
2662
|
+
if (result.effect.type === "cancel") {
|
|
2663
|
+
onCancel();
|
|
2664
|
+
} else if (result.effect.type === "submit") {
|
|
2665
|
+
onSubmit(result.effect.value);
|
|
2666
|
+
}
|
|
2667
|
+
},
|
|
2668
|
+
[allowEmpty, validate, onCancel, onSubmit]
|
|
2669
|
+
);
|
|
2670
|
+
(0, import_ink13.useInput)((input, key) => {
|
|
2671
|
+
const action = getTextPromptInputAction(input, key);
|
|
2672
|
+
if (action !== void 0) applyAction(action);
|
|
2673
|
+
});
|
|
2674
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_ink13.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
2675
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_ink13.Text, { color: "yellow", bold: true, children: title }),
|
|
2676
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PromptDescription, { description }),
|
|
2677
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_ink13.Box, { marginTop: 1, children: [
|
|
2678
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_ink13.Text, { color: "cyan", children: "> " }),
|
|
2679
|
+
state.value ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_ink13.Text, { children: masked ? "*".repeat(state.value.length) : state.value }) : placeholder ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_ink13.Text, { dimColor: true, children: placeholder }) : null,
|
|
2680
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_ink13.Text, { color: "cyan", children: "\u2588" })
|
|
2681
|
+
] }),
|
|
2682
|
+
state.error && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_ink13.Text, { color: "red", children: state.error }),
|
|
2683
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_ink13.Text, { dimColor: true, children: " Enter Submit Esc Cancel" })
|
|
2684
|
+
] });
|
|
2685
|
+
}
|
|
2686
|
+
function PromptDescription({ description }) {
|
|
2687
|
+
if (description === void 0 || description.length === 0) {
|
|
2688
|
+
return null;
|
|
2689
|
+
}
|
|
2690
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_ink13.Text, { dimColor: true, children: description });
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
// src/InteractivePrompt.tsx
|
|
2694
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
2695
|
+
function InteractivePrompt({
|
|
2696
|
+
prompt,
|
|
2697
|
+
onSubmit,
|
|
2698
|
+
onCancel
|
|
2699
|
+
}) {
|
|
2700
|
+
if (prompt.kind === "text") {
|
|
2701
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
2702
|
+
TextPrompt,
|
|
2703
|
+
{
|
|
2704
|
+
title: prompt.title,
|
|
2705
|
+
description: prompt.description,
|
|
2706
|
+
placeholder: prompt.placeholder,
|
|
2707
|
+
allowEmpty: prompt.allowEmpty,
|
|
2708
|
+
masked: prompt.masked,
|
|
2709
|
+
validate: prompt.validate,
|
|
2710
|
+
onSubmit,
|
|
2711
|
+
onCancel
|
|
2712
|
+
},
|
|
2713
|
+
`text:${prompt.title}`
|
|
2714
|
+
);
|
|
2715
|
+
}
|
|
2716
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink14.Box, { flexDirection: "column", children: [
|
|
2717
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink14.Text, { bold: true, children: prompt.title }),
|
|
2718
|
+
prompt.description !== void 0 && prompt.description.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink14.Text, { dimColor: true, children: prompt.description }),
|
|
2719
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
2720
|
+
ListPicker,
|
|
2721
|
+
{
|
|
2722
|
+
items: [...prompt.options],
|
|
2723
|
+
maxVisible: prompt.maxVisible,
|
|
2724
|
+
renderItem: (option, isSelected) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink14.Text, { color: isSelected ? "cyan" : void 0, children: [
|
|
2725
|
+
isSelected ? "> " : " ",
|
|
2726
|
+
option.label
|
|
2727
|
+
] }),
|
|
2728
|
+
onSelect: (option) => onSubmit(option.value),
|
|
2729
|
+
onCancel
|
|
2730
|
+
}
|
|
2731
|
+
)
|
|
2732
|
+
] });
|
|
2733
|
+
}
|
|
2734
|
+
|
|
2735
|
+
// src/PermissionPrompt.tsx
|
|
2736
|
+
var import_react17 = __toESM(require("react"), 1);
|
|
2737
|
+
var import_ink15 = require("ink");
|
|
2738
|
+
|
|
2739
|
+
// src/flows/permission-prompt-flow.ts
|
|
2740
|
+
var PERMISSION_PROMPT_OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
|
|
2741
|
+
function getPermissionPromptInputAction(input, key) {
|
|
2742
|
+
const action = getDirectionalSelectionInputAction({ ...key, escape: false });
|
|
2743
|
+
if (action !== void 0) {
|
|
2744
|
+
return action;
|
|
2745
|
+
}
|
|
2746
|
+
if (input === "y" || input === "1") {
|
|
2747
|
+
return { type: "shortcut", index: 0 };
|
|
2748
|
+
}
|
|
2749
|
+
if (input === "a" || input === "2") {
|
|
2750
|
+
return { type: "shortcut", index: 1 };
|
|
2751
|
+
}
|
|
2752
|
+
if (input === "n" || input === "d" || input === "3") {
|
|
2753
|
+
return { type: "shortcut", index: 2 };
|
|
2754
|
+
}
|
|
2755
|
+
return void 0;
|
|
2756
|
+
}
|
|
2757
|
+
function applyPermissionPromptInput(state, action) {
|
|
2758
|
+
if (state.resolved) {
|
|
2759
|
+
return { state, effect: { type: "none" } };
|
|
2760
|
+
}
|
|
2761
|
+
if (typeof action !== "string") {
|
|
2762
|
+
return resolvePermissionIndex(state, action.index);
|
|
2763
|
+
}
|
|
2764
|
+
const result = applySelectionInput(state, action, {
|
|
2765
|
+
itemCount: PERMISSION_PROMPT_OPTIONS.length
|
|
2766
|
+
});
|
|
2767
|
+
if (result.effect.type !== "select") {
|
|
2768
|
+
return { state: result.state, effect: { type: "none" } };
|
|
2769
|
+
}
|
|
2770
|
+
return {
|
|
2771
|
+
state: result.state,
|
|
2772
|
+
effect: { type: "resolve", decision: getPermissionDecision(result.effect.index) }
|
|
2773
|
+
};
|
|
2774
|
+
}
|
|
2775
|
+
function getPermissionDecision(index) {
|
|
2776
|
+
if (index === 0) return true;
|
|
2777
|
+
if (index === 1) return "allow-session";
|
|
2778
|
+
return false;
|
|
2779
|
+
}
|
|
2780
|
+
function resolvePermissionIndex(state, index) {
|
|
2781
|
+
return {
|
|
2782
|
+
state: { ...state, selectedIndex: index, resolved: true },
|
|
2783
|
+
effect: { type: "resolve", decision: getPermissionDecision(index) }
|
|
2784
|
+
};
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
// src/PermissionPrompt.tsx
|
|
2788
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
2789
|
+
function formatArgs(args) {
|
|
2790
|
+
const entries = Object.entries(args);
|
|
2791
|
+
if (entries.length === 0) return "(no arguments)";
|
|
2792
|
+
return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
2793
|
+
}
|
|
2794
|
+
function PermissionPrompt({ request }) {
|
|
2795
|
+
const [state, setState] = import_react17.default.useState(() => createSelectionFlowState());
|
|
2796
|
+
const stateRef = import_react17.default.useRef(state);
|
|
2797
|
+
const prevRequestRef = import_react17.default.useRef(request);
|
|
2798
|
+
if (prevRequestRef.current !== request) {
|
|
2799
|
+
prevRequestRef.current = request;
|
|
2800
|
+
const nextState = createSelectionFlowState();
|
|
2801
|
+
stateRef.current = nextState;
|
|
2802
|
+
setState(nextState);
|
|
2803
|
+
}
|
|
2804
|
+
const applyAction = import_react17.default.useCallback(
|
|
2805
|
+
(action) => {
|
|
2806
|
+
const result = applyPermissionPromptInput(stateRef.current, action);
|
|
2807
|
+
stateRef.current = result.state;
|
|
2808
|
+
setState(result.state);
|
|
2809
|
+
if (result.effect.type === "resolve") {
|
|
2810
|
+
request.resolve(result.effect.decision);
|
|
2811
|
+
}
|
|
2812
|
+
},
|
|
2813
|
+
[request]
|
|
2814
|
+
);
|
|
2815
|
+
(0, import_ink15.useInput)((input, key) => {
|
|
2816
|
+
const action = getPermissionPromptInputAction(input, key);
|
|
2817
|
+
if (action !== void 0) {
|
|
2818
|
+
applyAction(action);
|
|
2819
|
+
}
|
|
2820
|
+
});
|
|
2821
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_ink15.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
2822
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink15.Text, { color: "yellow", bold: true, children: "[Permission Required]" }),
|
|
2823
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_ink15.Text, { children: [
|
|
2824
|
+
"Tool:",
|
|
2825
|
+
" ",
|
|
2826
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink15.Text, { color: "cyan", bold: true, children: request.toolName })
|
|
2827
|
+
] }),
|
|
2828
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_ink15.Text, { dimColor: true, children: [
|
|
2829
|
+
" ",
|
|
2830
|
+
formatArgs(request.toolArgs)
|
|
2831
|
+
] }),
|
|
2832
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink15.Box, { marginTop: 1, children: PERMISSION_PROMPT_OPTIONS.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink15.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
|
|
2833
|
+
import_ink15.Text,
|
|
2834
|
+
{
|
|
2835
|
+
color: i === state.selectedIndex ? "cyan" : void 0,
|
|
2836
|
+
bold: i === state.selectedIndex,
|
|
2837
|
+
children: [
|
|
2838
|
+
i === state.selectedIndex ? "> " : " ",
|
|
2839
|
+
opt
|
|
2840
|
+
]
|
|
2841
|
+
}
|
|
2842
|
+
) }, opt)) }),
|
|
2843
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink15.Text, { dimColor: true, children: " left/right to select, Enter to confirm" })
|
|
2844
|
+
] });
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
// src/StreamingIndicator.tsx
|
|
2848
|
+
var import_ink16 = require("ink");
|
|
2849
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
2850
|
+
function getToolStyle(t) {
|
|
2851
|
+
if (t.isRunning) return { color: "yellow", icon: "\u27F3", strikethrough: false };
|
|
2852
|
+
if (t.result === "error") return { color: "red", icon: "\u2717", strikethrough: true };
|
|
2853
|
+
if (t.result === "denied") return { color: "yellowBright", icon: "\u2298", strikethrough: true };
|
|
2854
|
+
return { color: "green", icon: "\u2713", strikethrough: false };
|
|
2855
|
+
}
|
|
2856
|
+
function renderThinkingFallback(isThinking) {
|
|
2857
|
+
if (!isThinking) return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_jsx_runtime16.Fragment, {});
|
|
2858
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_ink16.Box, { marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_ink16.Text, { color: "yellow", children: "Thinking..." }) });
|
|
2859
|
+
}
|
|
2860
|
+
function renderTools(activeTools) {
|
|
2861
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_ink16.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
2862
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_ink16.Text, { color: "white", bold: true, children: "Tools:" }),
|
|
2863
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_ink16.Text, { children: " " }),
|
|
2864
|
+
activeTools.map((t, i) => {
|
|
2865
|
+
const { color, icon, strikethrough } = getToolStyle(t);
|
|
2866
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_ink16.Box, { flexDirection: "column", children: [
|
|
2867
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_ink16.Text, { color, strikethrough, children: [
|
|
2868
|
+
" ",
|
|
2869
|
+
icon,
|
|
2870
|
+
" ",
|
|
2871
|
+
t.toolName,
|
|
2872
|
+
"(",
|
|
2873
|
+
t.firstArg,
|
|
2874
|
+
")"
|
|
2875
|
+
] }),
|
|
2876
|
+
t.diffLines && t.diffLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ToolDiffBlock, { file: t.diffFile, lines: t.diffLines })
|
|
2877
|
+
] }, `${t.toolName}-${i}`);
|
|
2878
|
+
})
|
|
2879
|
+
] });
|
|
2880
|
+
}
|
|
2881
|
+
function StreamingIndicator({
|
|
2882
|
+
text,
|
|
2883
|
+
activeTools,
|
|
2884
|
+
isThinking = false
|
|
2885
|
+
}) {
|
|
2886
|
+
const hasTools = activeTools.length > 0;
|
|
2887
|
+
const hasText = text.length > 0;
|
|
2888
|
+
if (!hasTools && !hasText) {
|
|
2889
|
+
return renderThinkingFallback(isThinking);
|
|
2890
|
+
}
|
|
2891
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_ink16.Box, { flexDirection: "column", children: [
|
|
2892
|
+
hasTools && renderTools(activeTools),
|
|
2893
|
+
hasText && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_ink16.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
2894
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_ink16.Text, { color: "cyan", bold: true, children: "Robota:" }),
|
|
2895
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_ink16.Text, { children: " " }),
|
|
2896
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_ink16.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_ink16.Text, { wrap: "wrap", children: renderMarkdown(text) }) })
|
|
2897
|
+
] })
|
|
2898
|
+
] });
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
// src/PluginTUI.tsx
|
|
2902
|
+
var import_react20 = require("react");
|
|
2903
|
+
|
|
2904
|
+
// src/MenuSelect.tsx
|
|
2905
|
+
var import_react18 = require("react");
|
|
2906
|
+
var import_ink17 = require("ink");
|
|
2907
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
2908
|
+
function MenuSelect({
|
|
2909
|
+
title,
|
|
2910
|
+
items,
|
|
2911
|
+
onSelect,
|
|
2912
|
+
onBack,
|
|
2913
|
+
loading,
|
|
2914
|
+
error
|
|
2915
|
+
}) {
|
|
2916
|
+
const [state, setState] = (0, import_react18.useState)(() => createSelectionFlowState());
|
|
2917
|
+
const stateRef = (0, import_react18.useRef)(state);
|
|
2918
|
+
const isEnabled = !loading && !error;
|
|
2919
|
+
const applyAction = (0, import_react18.useCallback)(
|
|
2920
|
+
(action) => {
|
|
2921
|
+
const result = applySelectionInput(stateRef.current, action, {
|
|
2922
|
+
itemCount: items.length,
|
|
2923
|
+
enabled: isEnabled
|
|
2924
|
+
});
|
|
2925
|
+
stateRef.current = result.state;
|
|
2926
|
+
setState(result.state);
|
|
2927
|
+
if (result.effect.type === "cancel") {
|
|
2928
|
+
onBack();
|
|
2929
|
+
} else if (result.effect.type === "select") {
|
|
2930
|
+
const item = items[result.effect.index];
|
|
2931
|
+
if (item !== void 0) {
|
|
2932
|
+
onSelect(item.value);
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
},
|
|
2936
|
+
[isEnabled, items, onBack, onSelect]
|
|
2937
|
+
);
|
|
2938
|
+
(0, import_ink17.useInput)((input, key) => {
|
|
2939
|
+
const action = getVerticalSelectionInputAction(key);
|
|
2940
|
+
if (action !== void 0) {
|
|
2941
|
+
applyAction(action);
|
|
2942
|
+
}
|
|
2943
|
+
});
|
|
2944
|
+
const normalizedState = normalizeSelectionState(state, { itemCount: items.length });
|
|
2945
|
+
if (normalizedState !== state) {
|
|
2946
|
+
stateRef.current = normalizedState;
|
|
2947
|
+
}
|
|
2948
|
+
const selected = normalizedState.selectedIndex;
|
|
2949
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_ink17.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
2950
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_ink17.Text, { color: "yellow", bold: true, children: title }),
|
|
2951
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_ink17.Box, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_ink17.Text, { dimColor: true, children: "Loading..." }) }),
|
|
2952
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_ink17.Box, { marginTop: 1, flexDirection: "column", children: [
|
|
2953
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_ink17.Text, { color: "red", children: error }),
|
|
2954
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_ink17.Text, { dimColor: true, children: "Press Esc to go back" })
|
|
2955
|
+
] }),
|
|
2956
|
+
!loading && !error && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_ink17.Box, { flexDirection: "column", marginTop: 1, children: items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_ink17.Box, { children: [
|
|
2957
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_ink17.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
|
|
2958
|
+
i === selected ? "> " : " ",
|
|
2959
|
+
item.label
|
|
2960
|
+
] }),
|
|
2961
|
+
item.hint && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_ink17.Text, { dimColor: true, children: [
|
|
2962
|
+
" ",
|
|
2963
|
+
item.hint
|
|
2964
|
+
] })
|
|
2965
|
+
] }, item.value)) }),
|
|
2966
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_ink17.Text, { dimColor: true, children: loading || error ? "" : " \u2191\u2193 Navigate Enter Select Esc Back" })
|
|
2967
|
+
] });
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
// src/plugin-tui-handlers.ts
|
|
2971
|
+
function handleMainSelect(value, nav) {
|
|
2972
|
+
if (value === "marketplace") {
|
|
2973
|
+
nav.push({ screen: "marketplace-list" });
|
|
2974
|
+
} else if (value === "installed") {
|
|
2975
|
+
nav.push({ screen: "installed-list" });
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
function handleMarketplaceListSelect(value, nav) {
|
|
2979
|
+
if (value === "__add__") {
|
|
2980
|
+
nav.push({ screen: "marketplace-add" });
|
|
2981
|
+
} else {
|
|
2982
|
+
nav.push({ screen: "marketplace-action", context: { marketplace: value } });
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
function handleMarketplaceActionSelect(value, marketplace, callbacks, nav) {
|
|
2986
|
+
if (value === "browse") {
|
|
2987
|
+
nav.push({ screen: "marketplace-browse", context: { marketplace } });
|
|
2988
|
+
} else if (value === "update") {
|
|
2989
|
+
callbacks.marketplaceUpdate(marketplace).then(() => {
|
|
2990
|
+
nav.notify(`Updated marketplace "${marketplace}".`);
|
|
2991
|
+
nav.pop();
|
|
2992
|
+
}).catch((err) => {
|
|
2993
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2994
|
+
});
|
|
2995
|
+
} else if (value === "remove") {
|
|
2996
|
+
nav.setConfirm({
|
|
2997
|
+
message: `Remove marketplace "${marketplace}" and all its plugins?`,
|
|
2998
|
+
onConfirm: () => {
|
|
2999
|
+
nav.setConfirm(void 0);
|
|
3000
|
+
callbacks.marketplaceRemove(marketplace).then(() => {
|
|
3001
|
+
nav.notify(`Removed marketplace "${marketplace}".`);
|
|
3002
|
+
nav.popN(2);
|
|
3003
|
+
}).catch((err) => {
|
|
3004
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3005
|
+
});
|
|
3006
|
+
},
|
|
3007
|
+
onCancel: () => nav.setConfirm(void 0)
|
|
3008
|
+
});
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
function handleMarketplaceBrowseSelect(value, marketplace, items, nav) {
|
|
3012
|
+
const fullId = `${value}@${marketplace}`;
|
|
3013
|
+
const item = items.find((i) => i.value === value);
|
|
3014
|
+
if (item?.hint === "installed") {
|
|
3015
|
+
nav.push({ screen: "installed-action", context: { pluginId: fullId } });
|
|
3016
|
+
} else {
|
|
3017
|
+
nav.push({ screen: "marketplace-install-scope", context: { marketplace, pluginId: fullId } });
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
function handleInstallScopeSelect(value, pluginId, callbacks, nav) {
|
|
3021
|
+
const scope = value;
|
|
3022
|
+
callbacks.install(pluginId, scope).then(() => {
|
|
3023
|
+
nav.notify(`Installed plugin "${pluginId}" (${scope} scope).`);
|
|
3024
|
+
nav.popN(2);
|
|
3025
|
+
}).catch((err) => {
|
|
3026
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3027
|
+
});
|
|
3028
|
+
}
|
|
3029
|
+
function handleInstalledListSelect(value, callbacks, nav) {
|
|
3030
|
+
nav.setConfirm({
|
|
3031
|
+
message: `Uninstall plugin "${value}"?`,
|
|
3032
|
+
onConfirm: () => {
|
|
3033
|
+
nav.setConfirm(void 0);
|
|
3034
|
+
callbacks.uninstall(value).then(() => {
|
|
3035
|
+
nav.notify(`Uninstalled plugin "${value}".`);
|
|
3036
|
+
nav.refresh();
|
|
3037
|
+
}).catch((err) => {
|
|
3038
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3039
|
+
});
|
|
3040
|
+
},
|
|
3041
|
+
onCancel: () => nav.setConfirm(void 0)
|
|
3042
|
+
});
|
|
3043
|
+
}
|
|
3044
|
+
function handleInstalledActionSelect(value, pluginId, callbacks, nav) {
|
|
3045
|
+
if (value === "uninstall") {
|
|
3046
|
+
nav.setConfirm({
|
|
3047
|
+
message: `Uninstall plugin "${pluginId}"?`,
|
|
3048
|
+
onConfirm: () => {
|
|
3049
|
+
nav.setConfirm(void 0);
|
|
3050
|
+
callbacks.uninstall(pluginId).then(() => {
|
|
3051
|
+
nav.notify(`Uninstalled plugin "${pluginId}".`);
|
|
3052
|
+
nav.popN(2);
|
|
3053
|
+
}).catch((err) => {
|
|
3054
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3055
|
+
});
|
|
3056
|
+
},
|
|
3057
|
+
onCancel: () => nav.setConfirm(void 0)
|
|
3058
|
+
});
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
// src/hooks/usePluginScreenData.ts
|
|
3063
|
+
var import_react19 = require("react");
|
|
3064
|
+
function usePluginScreenData(screen, marketplace, callbacks, refreshCounter, stackLength) {
|
|
3065
|
+
const [items, setItems] = (0, import_react19.useState)([]);
|
|
3066
|
+
const [loading, setLoading] = (0, import_react19.useState)(false);
|
|
3067
|
+
const [error, setError] = (0, import_react19.useState)();
|
|
3068
|
+
(0, import_react19.useEffect)(() => {
|
|
3069
|
+
setItems([]);
|
|
3070
|
+
setError(void 0);
|
|
3071
|
+
if (screen === "marketplace-list") {
|
|
3072
|
+
setLoading(true);
|
|
3073
|
+
callbacks.marketplaceList().then((sources) => {
|
|
3074
|
+
const baseItems = [{ label: "Add Marketplace", value: "__add__" }];
|
|
3075
|
+
const sourceItems = sources.map((s) => ({
|
|
3076
|
+
label: s.name,
|
|
3077
|
+
value: s.name,
|
|
3078
|
+
hint: s.type
|
|
3079
|
+
}));
|
|
3080
|
+
setItems([...baseItems, ...sourceItems]);
|
|
3081
|
+
setLoading(false);
|
|
3082
|
+
}).catch((err) => {
|
|
3083
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
3084
|
+
setLoading(false);
|
|
3085
|
+
});
|
|
3086
|
+
} else if (screen === "marketplace-browse") {
|
|
3087
|
+
const mp = marketplace ?? "";
|
|
3088
|
+
setLoading(true);
|
|
3089
|
+
callbacks.listAvailablePlugins(mp).then((plugins) => {
|
|
3090
|
+
setItems(
|
|
3091
|
+
plugins.map((p) => ({
|
|
3092
|
+
label: p.name,
|
|
3093
|
+
value: p.name,
|
|
3094
|
+
hint: p.installed ? "installed" : p.description
|
|
3095
|
+
}))
|
|
3096
|
+
);
|
|
3097
|
+
setLoading(false);
|
|
3098
|
+
}).catch((err) => {
|
|
3099
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
3100
|
+
setLoading(false);
|
|
3101
|
+
});
|
|
3102
|
+
} else if (screen === "installed-list") {
|
|
3103
|
+
setLoading(true);
|
|
3104
|
+
callbacks.listInstalled().then((plugins) => {
|
|
3105
|
+
setItems(
|
|
3106
|
+
plugins.map((p) => ({
|
|
3107
|
+
label: p.name,
|
|
3108
|
+
value: p.name,
|
|
3109
|
+
hint: p.description
|
|
3110
|
+
}))
|
|
3111
|
+
);
|
|
3112
|
+
setLoading(false);
|
|
3113
|
+
}).catch((err) => {
|
|
3114
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
3115
|
+
setLoading(false);
|
|
3116
|
+
});
|
|
3117
|
+
}
|
|
3118
|
+
}, [stackLength, screen, marketplace, callbacks, refreshCounter]);
|
|
3119
|
+
return { items, loading, error };
|
|
3120
|
+
}
|
|
3121
|
+
|
|
3122
|
+
// src/PluginTUI.tsx
|
|
3123
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
3124
|
+
function PluginTUI({ callbacks, onClose, addMessage }) {
|
|
3125
|
+
const [stack, setStack] = (0, import_react20.useState)([{ screen: "main" }]);
|
|
3126
|
+
const [confirm, setConfirm] = (0, import_react20.useState)();
|
|
3127
|
+
const [refreshCounter, setRefreshCounter] = (0, import_react20.useState)(0);
|
|
3128
|
+
const current = stack[stack.length - 1] ?? { screen: "main" };
|
|
3129
|
+
const push = (0, import_react20.useCallback)((state) => {
|
|
3130
|
+
setStack((prev) => [...prev, state]);
|
|
3131
|
+
}, []);
|
|
3132
|
+
const pop = (0, import_react20.useCallback)(() => {
|
|
3133
|
+
setStack((prev) => {
|
|
3134
|
+
if (prev.length <= 1) {
|
|
3135
|
+
onClose();
|
|
3136
|
+
return prev;
|
|
3137
|
+
}
|
|
3138
|
+
return prev.slice(0, -1);
|
|
3139
|
+
});
|
|
3140
|
+
}, [onClose]);
|
|
3141
|
+
const popN = (0, import_react20.useCallback)(
|
|
3142
|
+
(n) => {
|
|
3143
|
+
setStack((prev) => {
|
|
3144
|
+
const next = prev.slice(0, Math.max(1, prev.length - n));
|
|
3145
|
+
if (next.length === 0) {
|
|
3146
|
+
onClose();
|
|
3147
|
+
return prev;
|
|
3148
|
+
}
|
|
3149
|
+
return next;
|
|
3150
|
+
});
|
|
3151
|
+
},
|
|
3152
|
+
[onClose]
|
|
3153
|
+
);
|
|
3154
|
+
const notify = (0, import_react20.useCallback)(
|
|
3155
|
+
(content) => {
|
|
3156
|
+
addMessage?.({ role: "system", content });
|
|
3157
|
+
},
|
|
3158
|
+
[addMessage]
|
|
3159
|
+
);
|
|
3160
|
+
const refresh = (0, import_react20.useCallback)(() => {
|
|
3161
|
+
setRefreshCounter((c) => c + 1);
|
|
3162
|
+
}, []);
|
|
3163
|
+
const setConfirmNav = (0, import_react20.useCallback)(
|
|
3164
|
+
(state) => setConfirm(state),
|
|
3165
|
+
[setConfirm]
|
|
3166
|
+
);
|
|
3167
|
+
const pushNav = (0, import_react20.useCallback)(
|
|
3168
|
+
(state) => push({ screen: state.screen, context: state.context }),
|
|
3169
|
+
[push]
|
|
3170
|
+
);
|
|
3171
|
+
const nav = { push: pushNav, pop, popN, notify, setConfirm: setConfirmNav, refresh };
|
|
3172
|
+
const { items, loading, error } = usePluginScreenData(
|
|
3173
|
+
current.screen,
|
|
3174
|
+
current.context?.marketplace,
|
|
3175
|
+
callbacks,
|
|
3176
|
+
refreshCounter,
|
|
3177
|
+
stack.length
|
|
3178
|
+
);
|
|
3179
|
+
const handleSelect = (0, import_react20.useCallback)(
|
|
3180
|
+
(value) => {
|
|
3181
|
+
const screen2 = current.screen;
|
|
3182
|
+
const ctx = current.context;
|
|
3183
|
+
if (screen2 === "main") handleMainSelect(value, nav);
|
|
3184
|
+
else if (screen2 === "marketplace-list") handleMarketplaceListSelect(value, nav);
|
|
3185
|
+
else if (screen2 === "marketplace-action")
|
|
3186
|
+
handleMarketplaceActionSelect(value, ctx?.marketplace ?? "", callbacks, nav);
|
|
3187
|
+
else if (screen2 === "marketplace-browse")
|
|
3188
|
+
handleMarketplaceBrowseSelect(value, ctx?.marketplace ?? "", items, nav);
|
|
3189
|
+
else if (screen2 === "marketplace-install-scope")
|
|
3190
|
+
handleInstallScopeSelect(value, ctx?.pluginId ?? "", callbacks, nav);
|
|
3191
|
+
else if (screen2 === "installed-list") handleInstalledListSelect(value, callbacks, nav);
|
|
3192
|
+
else if (screen2 === "installed-action")
|
|
3193
|
+
handleInstalledActionSelect(value, ctx?.pluginId ?? "", callbacks, nav);
|
|
3194
|
+
},
|
|
3195
|
+
[current, items, callbacks, push, pop, popN, notify, setConfirm, refresh]
|
|
3196
|
+
);
|
|
3197
|
+
const handleTextSubmit = (0, import_react20.useCallback)(
|
|
3198
|
+
(value) => {
|
|
3199
|
+
if (current.screen === "marketplace-add") {
|
|
3200
|
+
callbacks.marketplaceAdd(value).then((name) => {
|
|
3201
|
+
notify(`Added marketplace "${name}" from ${value}.`);
|
|
3202
|
+
pop();
|
|
3203
|
+
}).catch((err) => {
|
|
3204
|
+
notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3205
|
+
pop();
|
|
3206
|
+
});
|
|
3207
|
+
}
|
|
3208
|
+
},
|
|
3209
|
+
[current.screen, callbacks, notify, pop]
|
|
3210
|
+
);
|
|
3211
|
+
if (confirm) {
|
|
3212
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3213
|
+
ConfirmPrompt,
|
|
3214
|
+
{
|
|
3215
|
+
message: confirm.message,
|
|
3216
|
+
onSelect: (index) => {
|
|
3217
|
+
if (index === 0) confirm.onConfirm();
|
|
3218
|
+
else confirm.onCancel();
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
);
|
|
3222
|
+
}
|
|
3223
|
+
const screen = current.screen;
|
|
3224
|
+
if (screen === "marketplace-add") {
|
|
3225
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3226
|
+
TextPrompt,
|
|
3227
|
+
{
|
|
3228
|
+
title: "Add Marketplace Source",
|
|
3229
|
+
placeholder: "owner/repo or git URL",
|
|
3230
|
+
onSubmit: handleTextSubmit,
|
|
3231
|
+
onCancel: pop,
|
|
3232
|
+
validate: (v) => !v.includes("/") ? "Must be owner/repo or a git URL" : void 0
|
|
3233
|
+
}
|
|
3234
|
+
);
|
|
3235
|
+
}
|
|
3236
|
+
if (screen === "marketplace-action") {
|
|
3237
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3238
|
+
MenuSelect,
|
|
3239
|
+
{
|
|
3240
|
+
title: `Marketplace: ${current.context?.marketplace ?? ""}`,
|
|
3241
|
+
items: [
|
|
3242
|
+
{ label: "Browse plugins", value: "browse" },
|
|
3243
|
+
{ label: "Update", value: "update" },
|
|
3244
|
+
{ label: "Remove", value: "remove" }
|
|
3245
|
+
],
|
|
3246
|
+
onSelect: handleSelect,
|
|
3247
|
+
onBack: pop
|
|
3248
|
+
},
|
|
3249
|
+
stack.length
|
|
3250
|
+
);
|
|
3251
|
+
}
|
|
3252
|
+
if (screen === "marketplace-install-scope") {
|
|
3253
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3254
|
+
MenuSelect,
|
|
3255
|
+
{
|
|
3256
|
+
title: `Install scope for "${current.context?.pluginId ?? ""}"`,
|
|
3257
|
+
items: [
|
|
3258
|
+
{ label: "User scope", value: "user" },
|
|
3259
|
+
{ label: "Project scope", value: "project" }
|
|
3260
|
+
],
|
|
3261
|
+
onSelect: handleSelect,
|
|
3262
|
+
onBack: pop
|
|
3263
|
+
},
|
|
3264
|
+
stack.length
|
|
3265
|
+
);
|
|
3266
|
+
}
|
|
3267
|
+
if (screen === "installed-action") {
|
|
3268
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3269
|
+
MenuSelect,
|
|
3270
|
+
{
|
|
3271
|
+
title: `Plugin: ${current.context?.pluginId ?? ""}`,
|
|
3272
|
+
items: [{ label: "Uninstall", value: "uninstall" }],
|
|
3273
|
+
onSelect: handleSelect,
|
|
3274
|
+
onBack: pop
|
|
3275
|
+
},
|
|
3276
|
+
stack.length
|
|
3277
|
+
);
|
|
3278
|
+
}
|
|
3279
|
+
const titleMap = {
|
|
3280
|
+
main: "Plugin Management",
|
|
3281
|
+
"marketplace-list": "Marketplace",
|
|
3282
|
+
"marketplace-browse": `Browse: ${current.context?.marketplace ?? ""}`,
|
|
3283
|
+
"installed-list": "Installed Plugins"
|
|
3284
|
+
};
|
|
3285
|
+
const staticItemsMap = {
|
|
3286
|
+
main: [
|
|
3287
|
+
{ label: "Marketplace", value: "marketplace" },
|
|
3288
|
+
{ label: "Installed Plugins", value: "installed" }
|
|
3289
|
+
]
|
|
3290
|
+
};
|
|
3291
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
3292
|
+
MenuSelect,
|
|
3293
|
+
{
|
|
3294
|
+
title: titleMap[screen] ?? "Plugin Management",
|
|
3295
|
+
items: staticItemsMap[screen] ?? items,
|
|
3296
|
+
onSelect: handleSelect,
|
|
3297
|
+
onBack: pop,
|
|
3298
|
+
loading,
|
|
3299
|
+
error
|
|
3300
|
+
},
|
|
3301
|
+
`${screen}-${stack.length}-${refreshCounter}`
|
|
3302
|
+
);
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
// src/TransportTUI.tsx
|
|
3306
|
+
var import_react21 = require("react");
|
|
3307
|
+
var import_ink18 = require("ink");
|
|
3308
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
3309
|
+
var TRANSPORT_NAME_WIDTH = 18;
|
|
3310
|
+
function TransportEntryRow({ entry, selected }) {
|
|
3311
|
+
const enabled = entry.config.enabled;
|
|
3312
|
+
const dot = enabled ? "\u25CF" : "\u25CB";
|
|
3313
|
+
const badge = enabled ? "[enabled] " : "[disabled]";
|
|
3314
|
+
const portOpt = entry.config.options?.port;
|
|
3315
|
+
const portHint = typeof portOpt === "number" ? `port: ${portOpt}` : "";
|
|
3316
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ink18.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ink18.Text, { color: selected ? "cyan" : void 0, bold: selected, children: `${dot} ${entry.transport.name.padEnd(TRANSPORT_NAME_WIDTH)} ${badge} ${portHint}` }) });
|
|
3317
|
+
}
|
|
3318
|
+
function useTransportInput(entries, cursor, saving, registry, setCursor, setSaving, onClose, refresh) {
|
|
3319
|
+
(0, import_ink18.useInput)(
|
|
3320
|
+
(0, import_react21.useCallback)(
|
|
3321
|
+
(_input, key) => {
|
|
3322
|
+
if (saving) return;
|
|
3323
|
+
if (key.upArrow) {
|
|
3324
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
3325
|
+
return;
|
|
3326
|
+
}
|
|
3327
|
+
if (key.downArrow) {
|
|
3328
|
+
setCursor((c) => Math.min(entries.length - 1, c + 1));
|
|
3329
|
+
return;
|
|
3330
|
+
}
|
|
3331
|
+
if (key.escape || key.return) {
|
|
3332
|
+
onClose();
|
|
3333
|
+
return;
|
|
3334
|
+
}
|
|
3335
|
+
if (_input === " ") {
|
|
3336
|
+
const entry = entries[cursor];
|
|
3337
|
+
if (!entry) return;
|
|
3338
|
+
setSaving(true);
|
|
3339
|
+
registry.setEnabled(entry.transport.name, !entry.config.enabled).then(() => {
|
|
3340
|
+
refresh();
|
|
3341
|
+
setSaving(false);
|
|
3342
|
+
}).catch(() => setSaving(false));
|
|
3343
|
+
}
|
|
3344
|
+
},
|
|
3345
|
+
[saving, entries, cursor, registry, onClose, refresh, setCursor, setSaving]
|
|
3346
|
+
)
|
|
3347
|
+
);
|
|
3348
|
+
}
|
|
3349
|
+
function TransportTUI({ registry, onClose }) {
|
|
3350
|
+
const [entries, setEntries] = (0, import_react21.useState)(() => registry.getAll());
|
|
3351
|
+
const [cursor, setCursor] = (0, import_react21.useState)(0);
|
|
3352
|
+
const [saving, setSaving] = (0, import_react21.useState)(false);
|
|
3353
|
+
const refresh = (0, import_react21.useCallback)(() => {
|
|
3354
|
+
setEntries(registry.getAll());
|
|
3355
|
+
}, [registry]);
|
|
3356
|
+
useTransportInput(entries, cursor, saving, registry, setCursor, setSaving, onClose, refresh);
|
|
3357
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_ink18.Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
|
|
3358
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ink18.Text, { bold: true, children: "Settings \u203A Transports" }),
|
|
3359
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ink18.Box, { marginTop: 1, flexDirection: "column", children: entries.map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(TransportEntryRow, { entry, selected: i === cursor }, entry.transport.name)) }),
|
|
3360
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ink18.Box, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ink18.Text, { dimColor: true, children: "\u2191\u2193 select space toggle enter/esc close" }) }),
|
|
3361
|
+
saving && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ink18.Box, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_ink18.Text, { color: "yellow", children: "Saving\u2026" }) })
|
|
3362
|
+
] });
|
|
3363
|
+
}
|
|
3364
|
+
|
|
3365
|
+
// src/SessionPicker.tsx
|
|
3366
|
+
var import_ink19 = require("ink");
|
|
3367
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
3368
|
+
var SESSION_ID_DISPLAY_LENGTH = 8;
|
|
3369
|
+
var SESSION_PREVIEW_DISPLAY_LENGTH = 60;
|
|
3370
|
+
function SessionPicker({
|
|
3371
|
+
sessions,
|
|
3372
|
+
onSelect,
|
|
3373
|
+
onCancel
|
|
3374
|
+
}) {
|
|
3375
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_ink19.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
3376
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_ink19.Text, { bold: true, color: "cyan", children: "Select a session to resume (ESC to cancel):" }),
|
|
3377
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
3378
|
+
ListPicker,
|
|
3379
|
+
{
|
|
3380
|
+
items: [...sessions],
|
|
3381
|
+
renderItem: (session, isSelected) => {
|
|
3382
|
+
const preview = session.preview ? session.preview.slice(0, SESSION_PREVIEW_DISPLAY_LENGTH) + (session.preview.length > SESSION_PREVIEW_DISPLAY_LENGTH ? "..." : "") : "";
|
|
3383
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_ink19.Text, { children: [
|
|
3384
|
+
isSelected ? "> " : " ",
|
|
3385
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_ink19.Text, { bold: true, children: session.name ?? session.id.slice(0, SESSION_ID_DISPLAY_LENGTH) }),
|
|
3386
|
+
" ",
|
|
3387
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_ink19.Text, { dimColor: true, children: new Date(session.updatedAt).toLocaleString(void 0, {
|
|
3388
|
+
month: "short",
|
|
3389
|
+
day: "numeric",
|
|
3390
|
+
hour: "2-digit",
|
|
3391
|
+
minute: "2-digit"
|
|
3392
|
+
}) }),
|
|
3393
|
+
" ",
|
|
3394
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_ink19.Text, { dimColor: true, children: [
|
|
3395
|
+
"msgs: ",
|
|
3396
|
+
session.messageCount
|
|
3397
|
+
] }),
|
|
3398
|
+
preview ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
|
|
3399
|
+
"\n ",
|
|
3400
|
+
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_ink19.Text, { color: "gray", children: preview })
|
|
3401
|
+
] }) : null
|
|
3402
|
+
] });
|
|
3403
|
+
},
|
|
3404
|
+
onSelect: (session) => onSelect(session.id),
|
|
3405
|
+
onCancel
|
|
3406
|
+
}
|
|
3407
|
+
)
|
|
3408
|
+
] });
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
// src/BackgroundTaskPanel.tsx
|
|
3412
|
+
var import_ink20 = require("ink");
|
|
3413
|
+
|
|
3414
|
+
// src/execution-workspace-view-model.ts
|
|
3415
|
+
var ACTIVE_STATUSES = [
|
|
3416
|
+
"active",
|
|
3417
|
+
"queued",
|
|
3418
|
+
"running",
|
|
3419
|
+
"waiting_permission"
|
|
3420
|
+
];
|
|
3421
|
+
var DETAIL_RECORD_TEXT_LIMIT = 160;
|
|
3422
|
+
var PREVIEW_WHITESPACE = /\s+/g;
|
|
3423
|
+
var PREVIEW_SEPARATOR = " ";
|
|
3424
|
+
function getDefaultBackgroundWorkspaceEntries(snapshot) {
|
|
3425
|
+
return (snapshot?.entries ?? []).filter(
|
|
3426
|
+
(entry) => entry.kind === "background_task" && entry.visibility === "default"
|
|
3427
|
+
);
|
|
3428
|
+
}
|
|
3429
|
+
function countActiveBackgroundWorkspaceEntries(snapshot) {
|
|
3430
|
+
return getDefaultBackgroundWorkspaceEntries(snapshot).filter(
|
|
3431
|
+
(entry) => ACTIVE_STATUSES.includes(entry.status)
|
|
3432
|
+
).length;
|
|
3433
|
+
}
|
|
3434
|
+
function formatExecutionWorkspaceEntryRow(entry, options = {}) {
|
|
3435
|
+
const isSelected = entry.id === options.selectedEntryId;
|
|
3436
|
+
const row = {
|
|
3437
|
+
id: entry.id,
|
|
3438
|
+
radio: isSelected ? "\u25CF" : "\u25CB",
|
|
3439
|
+
title: formatEntryTitle(entry),
|
|
3440
|
+
subtitle: formatEntrySubtitle(entry),
|
|
3441
|
+
statusLabel: formatStatusLabel2(entry.status),
|
|
3442
|
+
preview: trimPreview(entry.preview ?? entry.currentAction),
|
|
3443
|
+
color: getEntryColor(entry),
|
|
3444
|
+
isSelected
|
|
3445
|
+
};
|
|
3446
|
+
return { ...row, accessibleText: formatAccessibleText(row) };
|
|
3447
|
+
}
|
|
3448
|
+
function formatExecutionDetailRecord(record) {
|
|
3449
|
+
const text = record.text.trim().replace(PREVIEW_WHITESPACE, PREVIEW_SEPARATOR);
|
|
3450
|
+
if (!text) return record.kind;
|
|
3451
|
+
return text.length > DETAIL_RECORD_TEXT_LIMIT ? `${text.slice(0, DETAIL_RECORD_TEXT_LIMIT)}...` : text;
|
|
3452
|
+
}
|
|
3453
|
+
function formatEntryTitle(entry) {
|
|
3454
|
+
if (entry.kind === "main_thread") return entry.title;
|
|
3455
|
+
if (entry.kind === "background_group") return `${entry.title} group`;
|
|
3456
|
+
if (entry.taskKind === "agent") return `${entry.title} agent`;
|
|
3457
|
+
if (entry.taskKind === "process") return entry.title || "Process";
|
|
3458
|
+
return entry.title;
|
|
3459
|
+
}
|
|
3460
|
+
function formatEntrySubtitle(entry) {
|
|
3461
|
+
if (entry.kind === "main_thread") return entry.subtitle;
|
|
3462
|
+
const parts = [
|
|
3463
|
+
entry.taskKind,
|
|
3464
|
+
entry.subtitle,
|
|
3465
|
+
entry.attention === "none" ? void 0 : entry.attention
|
|
3466
|
+
];
|
|
3467
|
+
return parts.filter((part) => typeof part === "string" && part.length > 0).join(" \xB7 ") || void 0;
|
|
3468
|
+
}
|
|
3469
|
+
function formatStatusLabel2(status) {
|
|
3470
|
+
return status.replace(/_/g, " ");
|
|
3471
|
+
}
|
|
3472
|
+
function getEntryColor(entry) {
|
|
3473
|
+
if (entry.attention === "failed" || entry.status === "failed") return "red";
|
|
3474
|
+
if (entry.attention === "permission" || entry.status === "waiting_permission") return "yellow";
|
|
3475
|
+
if (entry.status === "completed") return "green";
|
|
3476
|
+
if (entry.status === "cancelled") return "yellow";
|
|
3477
|
+
if (ACTIVE_STATUSES.includes(entry.status)) return "cyan";
|
|
3478
|
+
return "white";
|
|
3479
|
+
}
|
|
3480
|
+
function trimPreview(value) {
|
|
3481
|
+
const preview = value?.trim().replace(PREVIEW_WHITESPACE, PREVIEW_SEPARATOR);
|
|
3482
|
+
return preview || void 0;
|
|
3483
|
+
}
|
|
3484
|
+
function formatAccessibleText(row) {
|
|
3485
|
+
const parts = [row.radio, row.title, row.statusLabel, row.subtitle, row.preview];
|
|
3486
|
+
return parts.filter((part) => typeof part === "string" && part.length > 0).join(" \xB7 ");
|
|
3487
|
+
}
|
|
3488
|
+
|
|
3489
|
+
// src/background-task-row-format.ts
|
|
3490
|
+
function formatBackgroundTaskRow(entry, options = {}) {
|
|
3491
|
+
const row = formatExecutionWorkspaceEntryRow(entry);
|
|
3492
|
+
const marker = isActiveEntry(entry) ? "\u25A1" : "\u25A0";
|
|
3493
|
+
const segments = [row.statusLabel, row.subtitle].filter(
|
|
3494
|
+
(segment) => typeof segment === "string" && segment.length > 0
|
|
3495
|
+
);
|
|
3496
|
+
return {
|
|
3497
|
+
connector: options.isLast === false ? "\u251C" : "\u2514",
|
|
3498
|
+
marker,
|
|
3499
|
+
color: row.color,
|
|
3500
|
+
label: row.title,
|
|
3501
|
+
segments,
|
|
3502
|
+
preview: row.preview,
|
|
3503
|
+
accessibleText: [
|
|
3504
|
+
`${options.isLast === false ? "\u251C" : "\u2514"} ${marker} ${row.title}`,
|
|
3505
|
+
...segments,
|
|
3506
|
+
row.preview
|
|
3507
|
+
].filter((part) => typeof part === "string" && part.length > 0).join(" \xB7 ")
|
|
3508
|
+
};
|
|
3509
|
+
}
|
|
3510
|
+
function isActiveEntry(entry) {
|
|
3511
|
+
return entry.status === "active" || entry.status === "queued" || entry.status === "running" || entry.status === "waiting_permission";
|
|
3512
|
+
}
|
|
3513
|
+
|
|
3514
|
+
// src/BackgroundTaskPanel.tsx
|
|
3515
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
3516
|
+
function BackgroundTaskPanel({ entries }) {
|
|
3517
|
+
if (entries.length === 0) return null;
|
|
3518
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_ink20.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
3519
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_ink20.Text, { color: "cyan", bold: true, children: "Background work" }),
|
|
3520
|
+
entries.map((entry, index) => {
|
|
3521
|
+
const row = formatBackgroundTaskRow(entry, { isLast: index === entries.length - 1 });
|
|
3522
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_ink20.Text, { children: [
|
|
3523
|
+
`${row.connector} `,
|
|
3524
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_ink20.Text, { color: row.color, children: row.marker }),
|
|
3525
|
+
` ${row.label}`,
|
|
3526
|
+
row.segments.map((segment, segmentIndex) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_ink20.Text, { dimColor: true, children: ` \xB7 ${segment}` }, `${segment}-${segmentIndex}`)),
|
|
3527
|
+
row.preview ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_ink20.Text, { dimColor: true, children: ` \xB7 ${row.preview}` }) : null
|
|
3528
|
+
] }, entry.id);
|
|
3529
|
+
})
|
|
3530
|
+
] });
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3533
|
+
// src/ExecutionWorkspaceSwitcher.tsx
|
|
3534
|
+
var import_react22 = require("react");
|
|
3535
|
+
var import_ink21 = require("ink");
|
|
3536
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
3537
|
+
var MAX_VISIBLE_WORKSPACE_ENTRIES = 8;
|
|
3538
|
+
function ExecutionWorkspaceSwitcher({
|
|
3539
|
+
snapshot,
|
|
3540
|
+
selectedEntryId,
|
|
3541
|
+
onSelect,
|
|
3542
|
+
onClose
|
|
3543
|
+
}) {
|
|
3544
|
+
const entries = [...snapshot?.entries ?? []];
|
|
3545
|
+
const { normalized, visibleEntries, applyAction } = useWorkspaceSwitcherSelection({
|
|
3546
|
+
entries,
|
|
3547
|
+
selectedEntryId,
|
|
3548
|
+
onSelect,
|
|
3549
|
+
onClose
|
|
3550
|
+
});
|
|
3551
|
+
(0, import_ink21.useInput)((_input, key) => {
|
|
3552
|
+
const action = getVerticalSelectionInputAction(key);
|
|
3553
|
+
if (action !== void 0) applyAction(action);
|
|
3554
|
+
});
|
|
3555
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_ink21.Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
|
|
3556
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Text, { color: "cyan", bold: true, children: "Execution workspace" }),
|
|
3557
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Box, { flexDirection: "column", marginTop: 1, children: visibleEntries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Text, { dimColor: true, children: "No workspace entries" }) : visibleEntries.map((entry, index) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
3558
|
+
ExecutionWorkspaceSwitcherRow,
|
|
3559
|
+
{
|
|
3560
|
+
entry,
|
|
3561
|
+
isFocused: normalized.scrollOffset + index === normalized.selectedIndex,
|
|
3562
|
+
selectedEntryId
|
|
3563
|
+
},
|
|
3564
|
+
entry.id
|
|
3565
|
+
)) }),
|
|
3566
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Text, { dimColor: true, children: "Ctrl+B Close \u2191\u2193 Navigate Enter Switch Esc Close" })
|
|
3567
|
+
] });
|
|
3568
|
+
}
|
|
3569
|
+
function useWorkspaceSwitcherSelection({
|
|
3570
|
+
entries,
|
|
3571
|
+
selectedEntryId,
|
|
3572
|
+
onSelect,
|
|
3573
|
+
onClose
|
|
3574
|
+
}) {
|
|
3575
|
+
const [state, setState] = (0, import_react22.useState)(() => createSelectionFlowState());
|
|
3576
|
+
const stateRef = (0, import_react22.useRef)(state);
|
|
3577
|
+
(0, import_react22.useEffect)(() => {
|
|
3578
|
+
const selectedIndex = Math.max(
|
|
3579
|
+
0,
|
|
3580
|
+
entries.findIndex((entry) => entry.id === selectedEntryId)
|
|
3581
|
+
);
|
|
3582
|
+
const nextState = createNormalizedSelection({ selectedIndex, itemCount: entries.length });
|
|
3583
|
+
stateRef.current = nextState;
|
|
3584
|
+
setState(nextState);
|
|
3585
|
+
}, [entries.length, selectedEntryId]);
|
|
3586
|
+
const normalized = createNormalizedSelection({
|
|
3587
|
+
selectedIndex: state.selectedIndex,
|
|
3588
|
+
scrollOffset: state.scrollOffset,
|
|
3589
|
+
itemCount: entries.length
|
|
3590
|
+
});
|
|
3591
|
+
if (normalized !== state) stateRef.current = normalized;
|
|
3592
|
+
return {
|
|
3593
|
+
normalized,
|
|
3594
|
+
visibleEntries: entries.slice(
|
|
3595
|
+
normalized.scrollOffset,
|
|
3596
|
+
normalized.scrollOffset + MAX_VISIBLE_WORKSPACE_ENTRIES
|
|
3597
|
+
),
|
|
3598
|
+
applyAction: createApplyAction({ entries, stateRef, setState, onSelect, onClose })
|
|
3599
|
+
};
|
|
3600
|
+
}
|
|
3601
|
+
function createApplyAction({
|
|
3602
|
+
entries,
|
|
3603
|
+
stateRef,
|
|
3604
|
+
setState,
|
|
3605
|
+
onSelect,
|
|
3606
|
+
onClose
|
|
3607
|
+
}) {
|
|
3608
|
+
return (action) => {
|
|
3609
|
+
const result = applySelectionInput(stateRef.current, action, {
|
|
3610
|
+
itemCount: entries.length,
|
|
3611
|
+
maxVisible: MAX_VISIBLE_WORKSPACE_ENTRIES
|
|
3612
|
+
});
|
|
3613
|
+
const nextState = result.effect.type === "select" || result.effect.type === "cancel" ? { ...result.state, resolved: false } : result.state;
|
|
3614
|
+
stateRef.current = nextState;
|
|
3615
|
+
setState(nextState);
|
|
3616
|
+
if (result.effect.type === "cancel") {
|
|
3617
|
+
onClose();
|
|
3618
|
+
} else if (result.effect.type === "select") {
|
|
3619
|
+
const entry = entries[result.effect.index];
|
|
3620
|
+
if (entry) onSelect(entry.id);
|
|
3621
|
+
}
|
|
3622
|
+
};
|
|
3623
|
+
}
|
|
3624
|
+
function createNormalizedSelection(input) {
|
|
3625
|
+
return normalizeSelectionState(
|
|
3626
|
+
{
|
|
3627
|
+
selectedIndex: input.selectedIndex,
|
|
3628
|
+
scrollOffset: input.scrollOffset ?? 0,
|
|
3629
|
+
resolved: false
|
|
3630
|
+
},
|
|
3631
|
+
{ itemCount: input.itemCount, maxVisible: MAX_VISIBLE_WORKSPACE_ENTRIES }
|
|
3632
|
+
);
|
|
3633
|
+
}
|
|
3634
|
+
function ExecutionWorkspaceSwitcherRow({
|
|
3635
|
+
entry,
|
|
3636
|
+
isFocused,
|
|
3637
|
+
selectedEntryId
|
|
3638
|
+
}) {
|
|
3639
|
+
const row = formatExecutionWorkspaceEntryRow(entry, { selectedEntryId });
|
|
3640
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_ink21.Text, { children: [
|
|
3641
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Text, { color: isFocused ? "cyan" : void 0, bold: isFocused, children: isFocused ? "> " : " " }),
|
|
3642
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Text, { color: row.color, children: row.radio }),
|
|
3643
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Text, { color: isFocused ? "cyan" : void 0, bold: isFocused, children: ` ${row.title}` }),
|
|
3644
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Text, { dimColor: true, children: ` \xB7 ${row.statusLabel}` }),
|
|
3645
|
+
row.subtitle ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Text, { dimColor: true, children: ` \xB7 ${row.subtitle}` }) : null,
|
|
3646
|
+
row.preview ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_ink21.Text, { dimColor: true, children: ` \xB7 ${row.preview}` }) : null
|
|
3647
|
+
] });
|
|
3648
|
+
}
|
|
3649
|
+
|
|
3650
|
+
// src/ExecutionWorkspaceDetailPane.tsx
|
|
3651
|
+
var import_ink22 = require("ink");
|
|
3652
|
+
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
3653
|
+
var MAX_VISIBLE_DETAIL_RECORDS = 12;
|
|
3654
|
+
function ExecutionWorkspaceDetailPane({
|
|
3655
|
+
entry,
|
|
3656
|
+
page,
|
|
3657
|
+
loading,
|
|
3658
|
+
error
|
|
3659
|
+
}) {
|
|
3660
|
+
const row = formatExecutionWorkspaceEntryRow(entry, { selectedEntryId: entry.id });
|
|
3661
|
+
const records = page?.records.slice(-MAX_VISIBLE_DETAIL_RECORDS) ?? [];
|
|
3662
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_ink22.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
3663
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ink22.Text, { color: "cyan", bold: true, children: `Viewing ${row.title}` }),
|
|
3664
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_ink22.Text, { dimColor: true, children: [
|
|
3665
|
+
row.statusLabel,
|
|
3666
|
+
row.subtitle ? ` \xB7 ${row.subtitle}` : "",
|
|
3667
|
+
row.preview ? ` \xB7 ${row.preview}` : ""
|
|
3668
|
+
] }),
|
|
3669
|
+
loading ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ink22.Text, { dimColor: true, children: "Loading workspace detail..." }) : null,
|
|
3670
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ink22.Text, { color: "red", children: error }) : null,
|
|
3671
|
+
!loading && !error && records.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ink22.Text, { dimColor: true, children: "No detail yet" }) : null,
|
|
3672
|
+
!loading && !error && records.map((record) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ink22.Text, { color: getDetailRecordColor(record.kind), children: formatExecutionDetailRecord(record) }, record.id)),
|
|
3673
|
+
page?.nextCursor ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_ink22.Text, { dimColor: true, children: "... more detail available" }) : null
|
|
3674
|
+
] });
|
|
3675
|
+
}
|
|
3676
|
+
function getDetailRecordColor(kind) {
|
|
3677
|
+
if (kind === "error") return "red";
|
|
3678
|
+
if (kind === "result") return "green";
|
|
3679
|
+
if (kind === "process_output") return "white";
|
|
3680
|
+
if (kind === "group_summary") return "cyan";
|
|
3681
|
+
return void 0;
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3684
|
+
// src/UpdateNotice.tsx
|
|
3685
|
+
var import_ink23 = require("ink");
|
|
3686
|
+
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
3687
|
+
function UpdateNotice({ message }) {
|
|
3688
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ink23.Box, { paddingX: 1, marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ink23.Text, { color: "yellow", children: message }) });
|
|
3689
|
+
}
|
|
3690
|
+
|
|
3691
|
+
// src/App.tsx
|
|
3692
|
+
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
3693
|
+
function App(props) {
|
|
3694
|
+
const [activeSessionId, setActiveSessionId] = (0, import_react23.useState)(props.resumeSessionId);
|
|
3695
|
+
const [showInitialSessionPicker, setShowInitialSessionPicker] = (0, import_react23.useState)(
|
|
3696
|
+
props.showSessionPickerOnStart ?? false
|
|
3697
|
+
);
|
|
3698
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(TuiCliAdapterProvider, { value: props.cliAdapter, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
3699
|
+
AppInner,
|
|
3700
|
+
{
|
|
3701
|
+
...props,
|
|
3702
|
+
showSessionPickerOnStart: showInitialSessionPicker,
|
|
3703
|
+
resumeSessionId: activeSessionId,
|
|
3704
|
+
onSessionSwitch: (sessionId) => {
|
|
3705
|
+
setShowInitialSessionPicker(false);
|
|
3706
|
+
setActiveSessionId(sessionId);
|
|
3707
|
+
}
|
|
3708
|
+
},
|
|
3709
|
+
activeSessionId ?? "__new__"
|
|
3710
|
+
) });
|
|
3711
|
+
}
|
|
3712
|
+
function AppInner(props) {
|
|
3713
|
+
const cwd = props.cwd;
|
|
3714
|
+
const {
|
|
3715
|
+
interactiveSession,
|
|
3716
|
+
registry,
|
|
3717
|
+
commandEffectQueue,
|
|
3718
|
+
history,
|
|
3719
|
+
addEntry,
|
|
3720
|
+
streamingText,
|
|
3721
|
+
activeTools,
|
|
3722
|
+
isThinking,
|
|
3723
|
+
isAborting,
|
|
3724
|
+
isShuttingDown,
|
|
3725
|
+
pendingPrompt,
|
|
3726
|
+
executionWorkspaceSnapshot,
|
|
3727
|
+
selectedExecutionEntryId,
|
|
3728
|
+
selectExecutionWorkspaceEntry,
|
|
3729
|
+
readExecutionWorkspaceDetail,
|
|
3730
|
+
permissionRequest,
|
|
3731
|
+
contextState,
|
|
3732
|
+
handleSubmit: baseHandleSubmit,
|
|
3733
|
+
handleAbort,
|
|
3734
|
+
handleCancelQueue,
|
|
3735
|
+
handleShutdown
|
|
3736
|
+
} = useInteractiveSession({
|
|
3737
|
+
cwd,
|
|
3738
|
+
provider: props.provider,
|
|
3739
|
+
permissionMode: props.permissionMode,
|
|
3740
|
+
maxTurns: props.maxTurns,
|
|
3741
|
+
sessionStore: props.sessionStore,
|
|
3742
|
+
resumeSessionId: props.resumeSessionId,
|
|
3743
|
+
forkSession: props.forkSession,
|
|
3744
|
+
sessionName: props.sessionName,
|
|
3745
|
+
backgroundTaskRunners: props.backgroundTaskRunners,
|
|
3746
|
+
subagentRunnerFactory: props.subagentRunnerFactory,
|
|
3747
|
+
commandModules: props.commandModules,
|
|
3748
|
+
commandHostAdapters: props.commandHostAdapters,
|
|
3749
|
+
transportRegistry: props.transportRegistry,
|
|
3750
|
+
language: props.language,
|
|
3751
|
+
reloadPluginCommandSource: props.reloadPluginCommandSource
|
|
3752
|
+
});
|
|
3753
|
+
const fallbackPluginCallbacks = usePluginCallbacks(cwd);
|
|
3754
|
+
const pluginCallbacks = props.commandHostAdapters?.plugin ?? fallbackPluginCallbacks;
|
|
3755
|
+
const { exit } = (0, import_ink24.useApp)();
|
|
3756
|
+
const [sessionName, setSessionName] = (0, import_react23.useState)(props.sessionName);
|
|
3757
|
+
const [updateNotice, setUpdateNotice] = (0, import_react23.useState)();
|
|
3758
|
+
const [showExecutionWorkspaceSwitcher, setShowExecutionWorkspaceSwitcher] = (0, import_react23.useState)(false);
|
|
3759
|
+
const [executionDetailPage, setExecutionDetailPage] = (0, import_react23.useState)(null);
|
|
3760
|
+
const [executionDetailError, setExecutionDetailError] = (0, import_react23.useState)();
|
|
3761
|
+
const [isExecutionDetailLoading, setIsExecutionDetailLoading] = (0, import_react23.useState)(false);
|
|
3762
|
+
const [statusLineSettings, setStatusLineSettings] = useStatusLineSettings();
|
|
3763
|
+
const backgroundWorkspaceEntries = (0, import_react23.useMemo)(
|
|
3764
|
+
() => getDefaultBackgroundWorkspaceEntries(executionWorkspaceSnapshot),
|
|
3765
|
+
[executionWorkspaceSnapshot]
|
|
3766
|
+
);
|
|
3767
|
+
const activeBackgroundTaskCount = countActiveBackgroundWorkspaceEntries(
|
|
3768
|
+
executionWorkspaceSnapshot
|
|
3769
|
+
);
|
|
3770
|
+
const selectedExecutionEntry = (0, import_react23.useMemo)(
|
|
3771
|
+
() => executionWorkspaceSnapshot?.entries.find((entry) => entry.id === selectedExecutionEntryId),
|
|
3772
|
+
[executionWorkspaceSnapshot, selectedExecutionEntryId]
|
|
3773
|
+
);
|
|
3774
|
+
const {
|
|
3775
|
+
handleSubmit,
|
|
3776
|
+
pendingModelId,
|
|
3777
|
+
pendingInteractionPrompt,
|
|
3778
|
+
showPluginTUI,
|
|
3779
|
+
showSessionPicker,
|
|
3780
|
+
showTransportTUI,
|
|
3781
|
+
setShowPluginTUI,
|
|
3782
|
+
setShowSessionPicker,
|
|
3783
|
+
setShowTransportTUI,
|
|
3784
|
+
handleModelConfirm,
|
|
3785
|
+
handleInteractionSubmit,
|
|
3786
|
+
handleInteractionCancel
|
|
3787
|
+
} = useSideEffects({
|
|
3788
|
+
cwd,
|
|
3789
|
+
providerOverride: props.providerOverride,
|
|
3790
|
+
interactiveSession,
|
|
3791
|
+
commandEffectQueue,
|
|
3792
|
+
addEntry,
|
|
3793
|
+
baseHandleSubmit,
|
|
3794
|
+
setSessionName,
|
|
3795
|
+
setStatusLineSettings,
|
|
3796
|
+
showSessionPickerOnStart: props.showSessionPickerOnStart
|
|
3797
|
+
});
|
|
3798
|
+
(0, import_react23.useEffect)(() => {
|
|
3799
|
+
const name = interactiveSession?.getName?.();
|
|
3800
|
+
if (name && !sessionName) setSessionName(name);
|
|
3801
|
+
}, [interactiveSession, sessionName]);
|
|
3802
|
+
(0, import_react23.useEffect)(() => {
|
|
3803
|
+
let isMounted = true;
|
|
3804
|
+
props.startupUpdateNotice?.then((notice) => {
|
|
3805
|
+
if (isMounted && notice !== void 0) {
|
|
3806
|
+
setUpdateNotice(notice);
|
|
3807
|
+
}
|
|
3808
|
+
}).catch(() => {
|
|
3809
|
+
});
|
|
3810
|
+
return () => {
|
|
3811
|
+
isMounted = false;
|
|
3812
|
+
};
|
|
3813
|
+
}, [props.startupUpdateNotice]);
|
|
3814
|
+
(0, import_react23.useEffect)(() => {
|
|
3815
|
+
const title = sessionName ? `Robota \u2014 ${sessionName}` : "Robota";
|
|
3816
|
+
process.stdout.write(`\x1B]0;${title}\x07`);
|
|
3817
|
+
}, [sessionName]);
|
|
3818
|
+
(0, import_ink24.useInput)((_input, key) => {
|
|
3819
|
+
if (!key.escape || !isThinking) return;
|
|
3820
|
+
if (permissionRequest || showPluginTUI || showTransportTUI || showSessionPicker || showExecutionWorkspaceSwitcher) {
|
|
3821
|
+
return;
|
|
3822
|
+
}
|
|
3823
|
+
handleAbort();
|
|
3824
|
+
});
|
|
3825
|
+
(0, import_ink24.useInput)((input, key) => {
|
|
3826
|
+
if (!key.ctrl || input !== "b") return;
|
|
3827
|
+
if (permissionRequest || showPluginTUI || showSessionPicker || isShuttingDown) return;
|
|
3828
|
+
setShowExecutionWorkspaceSwitcher((shown) => !shown);
|
|
3829
|
+
});
|
|
3830
|
+
(0, import_ink24.useInput)((input, key) => {
|
|
3831
|
+
if (!key.ctrl || input !== "c" || isShuttingDown) return;
|
|
3832
|
+
void handleShutdown("prompt_input_exit").finally(() => exit());
|
|
3833
|
+
});
|
|
3834
|
+
(0, import_react23.useEffect)(() => {
|
|
3835
|
+
const onSigterm = () => {
|
|
3836
|
+
if (isShuttingDown) return;
|
|
3837
|
+
void handleShutdown("other").finally(() => exit());
|
|
3838
|
+
};
|
|
3839
|
+
process.once("SIGINT", onSigterm);
|
|
3840
|
+
process.once("SIGTERM", onSigterm);
|
|
3841
|
+
return () => {
|
|
3842
|
+
process.off("SIGINT", onSigterm);
|
|
3843
|
+
process.off("SIGTERM", onSigterm);
|
|
3844
|
+
};
|
|
3845
|
+
}, [handleShutdown, exit, isShuttingDown]);
|
|
3846
|
+
(0, import_react23.useEffect)(() => {
|
|
3847
|
+
if (!selectedExecutionEntry || selectedExecutionEntry.kind === "main_thread") {
|
|
3848
|
+
setExecutionDetailPage(null);
|
|
3849
|
+
setExecutionDetailError(void 0);
|
|
3850
|
+
setIsExecutionDetailLoading(false);
|
|
3851
|
+
return;
|
|
3852
|
+
}
|
|
3853
|
+
let isCurrent = true;
|
|
3854
|
+
setIsExecutionDetailLoading(true);
|
|
3855
|
+
setExecutionDetailError(void 0);
|
|
3856
|
+
readExecutionWorkspaceDetail(selectedExecutionEntry.id).then((page) => {
|
|
3857
|
+
if (!isCurrent) return;
|
|
3858
|
+
setExecutionDetailPage(page);
|
|
3859
|
+
setIsExecutionDetailLoading(false);
|
|
3860
|
+
}).catch((error) => {
|
|
3861
|
+
if (!isCurrent) return;
|
|
3862
|
+
setExecutionDetailError(error.message);
|
|
3863
|
+
setIsExecutionDetailLoading(false);
|
|
3864
|
+
});
|
|
3865
|
+
return () => {
|
|
3866
|
+
isCurrent = false;
|
|
3867
|
+
};
|
|
3868
|
+
}, [executionWorkspaceSnapshot, readExecutionWorkspaceDetail, selectedExecutionEntry]);
|
|
3869
|
+
let permissionMode = props.permissionMode ?? "default";
|
|
3870
|
+
let sessionId = "";
|
|
3871
|
+
try {
|
|
3872
|
+
const session = interactiveSession.getSession();
|
|
3873
|
+
permissionMode = session.getPermissionMode();
|
|
3874
|
+
sessionId = session.getSessionId();
|
|
3875
|
+
} catch {
|
|
3876
|
+
}
|
|
3877
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_ink24.Box, { flexDirection: "column", children: [
|
|
3878
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_ink24.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
3879
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_ink24.Text, { color: "cyan", bold: true, children: `
|
|
3880
|
+
____ ___ ____ ___ _____ _
|
|
3881
|
+
| _ \\ / _ \\| __ ) / _ \\_ _|/ \\
|
|
3882
|
+
| |_) | | | | _ \\| | | || | / _ \\
|
|
3883
|
+
| _ <| |_| | |_) | |_| || |/ ___ \\
|
|
3884
|
+
|_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
|
|
3885
|
+
` }),
|
|
3886
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_ink24.Text, { dimColor: true, children: [
|
|
3887
|
+
" v",
|
|
3888
|
+
props.version ?? "0.0.0"
|
|
3889
|
+
] })
|
|
3890
|
+
] }),
|
|
3891
|
+
updateNotice && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(UpdateNotice, { message: updateNotice }),
|
|
3892
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_ink24.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
|
|
3893
|
+
selectedExecutionEntry && selectedExecutionEntry.kind !== "main_thread" ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
3894
|
+
ExecutionWorkspaceDetailPane,
|
|
3895
|
+
{
|
|
3896
|
+
entry: selectedExecutionEntry,
|
|
3897
|
+
page: executionDetailPage,
|
|
3898
|
+
loading: isExecutionDetailLoading,
|
|
3899
|
+
error: executionDetailError
|
|
3900
|
+
}
|
|
3901
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(MessageList, { history }),
|
|
3902
|
+
isShuttingDown && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_ink24.Box, { marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_ink24.Text, { color: "yellow", children: "Shutting down..." }) }),
|
|
3903
|
+
(isThinking || activeTools.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_ink24.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
3904
|
+
StreamingIndicator,
|
|
3905
|
+
{
|
|
3906
|
+
text: streamingText,
|
|
3907
|
+
activeTools,
|
|
3908
|
+
isThinking
|
|
3909
|
+
}
|
|
3910
|
+
) }),
|
|
3911
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(BackgroundTaskPanel, { entries: backgroundWorkspaceEntries })
|
|
3912
|
+
] }),
|
|
3913
|
+
showExecutionWorkspaceSwitcher && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
3914
|
+
ExecutionWorkspaceSwitcher,
|
|
3915
|
+
{
|
|
3916
|
+
snapshot: executionWorkspaceSnapshot,
|
|
3917
|
+
selectedEntryId: selectedExecutionEntryId,
|
|
3918
|
+
onSelect: selectExecutionWorkspaceEntry,
|
|
3919
|
+
onClose: () => setShowExecutionWorkspaceSwitcher(false)
|
|
3920
|
+
}
|
|
3921
|
+
),
|
|
3922
|
+
permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(PermissionPrompt, { request: permissionRequest }),
|
|
3923
|
+
pendingModelId && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
3924
|
+
ConfirmPrompt,
|
|
3925
|
+
{
|
|
3926
|
+
message: formatModelChangeConfirmationMessage(pendingModelId),
|
|
3927
|
+
onSelect: handleModelConfirm
|
|
3928
|
+
}
|
|
3929
|
+
),
|
|
3930
|
+
pendingInteractionPrompt && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
3931
|
+
InteractivePrompt,
|
|
3932
|
+
{
|
|
3933
|
+
prompt: pendingInteractionPrompt,
|
|
3934
|
+
onSubmit: handleInteractionSubmit,
|
|
3935
|
+
onCancel: handleInteractionCancel
|
|
3936
|
+
}
|
|
3937
|
+
),
|
|
3938
|
+
showPluginTUI && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
3939
|
+
PluginTUI,
|
|
3940
|
+
{
|
|
3941
|
+
callbacks: pluginCallbacks,
|
|
3942
|
+
onClose: () => setShowPluginTUI(false),
|
|
3943
|
+
addMessage: (msg) => addEntry((0, import_agent_core10.messageToHistoryEntry)((0, import_agent_core10.createSystemMessage)(msg.content)))
|
|
3944
|
+
}
|
|
3945
|
+
),
|
|
3946
|
+
showTransportTUI && props.transportRegistry && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
3947
|
+
TransportTUI,
|
|
3948
|
+
{
|
|
3949
|
+
registry: props.transportRegistry,
|
|
3950
|
+
onClose: () => setShowTransportTUI(false)
|
|
3951
|
+
}
|
|
3952
|
+
),
|
|
3953
|
+
showSessionPicker && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
3954
|
+
SessionPicker,
|
|
3955
|
+
{
|
|
3956
|
+
sessions: (0, import_agent_sdk4.listResumableSessionSummaries)(props.sessionStore, props.cwd),
|
|
3957
|
+
onSelect: (id) => {
|
|
3958
|
+
setShowSessionPicker(false);
|
|
3959
|
+
props.onSessionSwitch(id);
|
|
3960
|
+
},
|
|
3961
|
+
onCancel: () => {
|
|
3962
|
+
setShowSessionPicker(false);
|
|
3963
|
+
addEntry((0, import_agent_core10.messageToHistoryEntry)((0, import_agent_core10.createSystemMessage)("Session resume cancelled.")));
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
),
|
|
3967
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
3968
|
+
SessionStatusBar,
|
|
3969
|
+
{
|
|
3970
|
+
cwd,
|
|
3971
|
+
permissionMode,
|
|
3972
|
+
modelId: props.modelId,
|
|
3973
|
+
providerProfileName: props.providerProfileName,
|
|
3974
|
+
providerType: props.providerType,
|
|
3975
|
+
sessionId,
|
|
3976
|
+
isThinking,
|
|
3977
|
+
activeToolCount: activeTools.length,
|
|
3978
|
+
activeBackgroundTaskCount,
|
|
3979
|
+
hasPendingPrompt: pendingPrompt !== null,
|
|
3980
|
+
contextState,
|
|
3981
|
+
sessionName,
|
|
3982
|
+
settings: statusLineSettings
|
|
3983
|
+
}
|
|
3984
|
+
),
|
|
3985
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
3986
|
+
InputArea,
|
|
3987
|
+
{
|
|
3988
|
+
onSubmit: handleSubmit,
|
|
3989
|
+
onCancelQueue: handleCancelQueue,
|
|
3990
|
+
isDisabled: !!permissionRequest || showPluginTUI || showTransportTUI || showSessionPicker || showExecutionWorkspaceSwitcher || isShuttingDown || pendingInteractionPrompt !== null || isThinking && !!pendingPrompt,
|
|
3991
|
+
isAborting,
|
|
3992
|
+
pendingPrompt,
|
|
3993
|
+
registry,
|
|
3994
|
+
sessionName,
|
|
3995
|
+
history
|
|
3996
|
+
}
|
|
3997
|
+
),
|
|
3998
|
+
/* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_ink24.Text, { children: " " })
|
|
3999
|
+
] });
|
|
4000
|
+
}
|
|
4001
|
+
|
|
4002
|
+
// src/render.tsx
|
|
4003
|
+
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
4004
|
+
async function renderApp(options) {
|
|
4005
|
+
process.on("unhandledRejection", (reason) => {
|
|
4006
|
+
process.stderr.write(`
|
|
4007
|
+
[UNHANDLED REJECTION] ${reason}
|
|
4008
|
+
`);
|
|
4009
|
+
if (reason instanceof Error) {
|
|
4010
|
+
process.stderr.write(`${reason.stack}
|
|
4011
|
+
`);
|
|
4012
|
+
}
|
|
4013
|
+
});
|
|
4014
|
+
const instance = (0, import_ink25.render)(/* @__PURE__ */ (0, import_jsx_runtime26.jsx)(App, { ...options }), { exitOnCtrlC: false });
|
|
4015
|
+
await instance.waitUntilExit();
|
|
4016
|
+
}
|
|
4017
|
+
|
|
4018
|
+
// src/tui-transport.ts
|
|
4019
|
+
var TuiTransport = class {
|
|
4020
|
+
name = "tui";
|
|
4021
|
+
defaultEnabled = true;
|
|
4022
|
+
optionsSchema = {};
|
|
4023
|
+
options;
|
|
4024
|
+
constructor(options) {
|
|
4025
|
+
this.options = options;
|
|
4026
|
+
}
|
|
4027
|
+
attach(_session) {
|
|
4028
|
+
}
|
|
4029
|
+
async start() {
|
|
4030
|
+
await renderApp(this.options);
|
|
4031
|
+
}
|
|
4032
|
+
async stop() {
|
|
4033
|
+
}
|
|
4034
|
+
validateOptions(_options) {
|
|
4035
|
+
return true;
|
|
4036
|
+
}
|
|
4037
|
+
};
|
|
4038
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
4039
|
+
0 && (module.exports = {
|
|
4040
|
+
TuiTransport
|
|
4041
|
+
});
|