pi-ui-extend 0.1.34 → 0.1.35
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/README.md +20 -0
- package/dist/app/app.js +4 -2
- package/dist/app/constants.d.ts +2 -1
- package/dist/app/constants.js +6 -1
- package/dist/app/input/input-controller.d.ts +4 -1
- package/dist/app/input/input-controller.js +95 -16
- package/dist/app/input/input-paste-handler.js +3 -1
- package/dist/app/input/terminal-edit-shortcuts.d.ts +20 -0
- package/dist/app/input/terminal-edit-shortcuts.js +50 -16
- package/dist/app/rendering/conversation-entry-renderer.d.ts +1 -0
- package/dist/app/rendering/conversation-entry-renderer.js +1 -1
- package/dist/app/rendering/conversation-tool-renderer.d.ts +1 -0
- package/dist/app/rendering/conversation-tool-renderer.js +21 -0
- package/dist/app/rendering/conversation-viewport.d.ts +3 -0
- package/dist/app/rendering/conversation-viewport.js +41 -5
- package/dist/app/rendering/editor-layout-renderer.js +3 -2
- package/dist/app/rendering/editor-panels.js +27 -10
- package/dist/app/runtime.d.ts +1 -0
- package/dist/app/runtime.js +33 -14
- package/dist/app/session/session-event-controller.d.ts +7 -0
- package/dist/app/session/session-event-controller.js +78 -0
- package/dist/app/session/tabs-controller.js +3 -1
- package/dist/app/subagents/subagents-widget-controller.d.ts +10 -2
- package/dist/app/subagents/subagents-widget-controller.js +141 -70
- package/dist/app/terminal/terminal-controller.d.ts +10 -0
- package/dist/app/terminal/terminal-controller.js +91 -2
- package/dist/app/todo/todo-model.js +2 -0
- package/dist/app/todo/todo-widget-controller.d.ts +2 -0
- package/dist/app/todo/todo-widget-controller.js +17 -7
- package/dist/app/types.d.ts +4 -0
- package/dist/bundled-extensions/question/tui.js +8 -1
- package/dist/bundled-extensions/session-title/index.js +65 -14
- package/dist/input-editor-files.js +23 -4
- package/dist/markdown-format.d.ts +4 -1
- package/dist/markdown-format.js +76 -9
- package/external/pi-tools-suite/README.md +71 -1
- package/external/pi-tools-suite/package.json +3 -3
- package/external/pi-tools-suite/src/async-subagents/commands.ts +12 -6
- package/external/pi-tools-suite/src/async-subagents/index.ts +133 -37
- package/external/pi-tools-suite/src/context-usage.ts +6 -1
- package/external/pi-tools-suite/src/dcp/commands.ts +3 -2
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +9 -4
- package/external/pi-tools-suite/src/dcp/config.ts +142 -6
- package/external/pi-tools-suite/src/dcp/index.ts +20 -8
- package/external/pi-tools-suite/src/dcp/prompts.ts +17 -9
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +59 -15
- package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +6 -8
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +3 -3
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +51 -1
- package/external/pi-tools-suite/src/glm-coding-discipline/index.ts +16 -11
- package/external/pi-tools-suite/src/model-tools/index.ts +24 -12
- package/external/pi-tools-suite/src/prompt-commands/index.ts +11 -2
- package/external/pi-tools-suite/src/telegram-mirror/index.ts +66 -27
- package/external/pi-tools-suite/src/todo/index.ts +87 -16
- package/external/pi-tools-suite/src/todo/state/store.ts +41 -10
- package/external/pi-tools-suite/src/todo/todo.ts +49 -6
- package/external/pi-tools-suite/src/tool-descriptions.ts +4 -4
- package/package.json +7 -5
|
@@ -10,7 +10,9 @@ export class AppSubagentsWidgetController {
|
|
|
10
10
|
pollTimer;
|
|
11
11
|
pollInFlight = false;
|
|
12
12
|
currentRunDir;
|
|
13
|
+
currentRunDirs = [];
|
|
13
14
|
state;
|
|
15
|
+
runFreshnessByRunDir = new Map();
|
|
14
16
|
taskPreviewsByRunDir = new Map();
|
|
15
17
|
snapshotByRunDir = new Map();
|
|
16
18
|
refreshGeneration = 0;
|
|
@@ -38,6 +40,8 @@ export class AppSubagentsWidgetController {
|
|
|
38
40
|
reset() {
|
|
39
41
|
this.refreshGeneration++;
|
|
40
42
|
this.currentRunDir = undefined;
|
|
43
|
+
this.currentRunDirs = [];
|
|
44
|
+
this.runFreshnessByRunDir.clear();
|
|
41
45
|
this.taskPreviewsByRunDir.clear();
|
|
42
46
|
this.snapshotByRunDir.clear();
|
|
43
47
|
this.updateState(undefined);
|
|
@@ -59,15 +63,14 @@ export class AppSubagentsWidgetController {
|
|
|
59
63
|
}
|
|
60
64
|
this.currentRunDir = runDir;
|
|
61
65
|
this.snapshotByRunDir.set(runDir, normalizedDetails);
|
|
66
|
+
this.rememberRunFreshness(runDir, this.runFreshnessFromAgents(normalizedDetails.agents) ?? Date.now());
|
|
62
67
|
if (activeSubagentStates(normalizedDetails.agents).length > 0) {
|
|
63
|
-
this.
|
|
64
|
-
|
|
65
|
-
agents: normalizedDetails.agents,
|
|
66
|
-
...(normalizedDetails.tasks === undefined ? {} : { tasks: normalizedDetails.tasks }),
|
|
68
|
+
this.currentRunDirs = this.orderRunDirs([runDir, ...this.currentRunDirs]);
|
|
69
|
+
this.updateState(this.buildStateFromRuns([normalizedDetails], {
|
|
67
70
|
live: false,
|
|
68
71
|
snapshotOnly: true,
|
|
69
72
|
checkedAt: Date.now(),
|
|
70
|
-
};
|
|
73
|
+
}));
|
|
71
74
|
}
|
|
72
75
|
void this.refreshFromFiles(this.refreshGeneration);
|
|
73
76
|
this.schedulePoll(0);
|
|
@@ -88,6 +91,7 @@ export class AppSubagentsWidgetController {
|
|
|
88
91
|
}))
|
|
89
92
|
.filter((run) => activeSubagentStates(run.agents).length > 0);
|
|
90
93
|
for (const run of activeRuns) {
|
|
94
|
+
this.rememberRunFreshness(run.runDir, this.runFreshnessFromAgents(run.agents) ?? data.checkedAt);
|
|
91
95
|
this.snapshotByRunDir.set(run.runDir, {
|
|
92
96
|
runDir: run.runDir,
|
|
93
97
|
agents: run.agents,
|
|
@@ -97,22 +101,21 @@ export class AppSubagentsWidgetController {
|
|
|
97
101
|
if (run.tasks)
|
|
98
102
|
this.taskPreviewsByRunDir.set(run.runDir, run.tasks);
|
|
99
103
|
}
|
|
100
|
-
|
|
101
|
-
if (!preferred) {
|
|
104
|
+
if (activeRuns.length === 0) {
|
|
102
105
|
this.currentRunDir = undefined;
|
|
106
|
+
this.currentRunDirs = [];
|
|
103
107
|
this.updateState(undefined);
|
|
104
108
|
this.stopPolling();
|
|
105
109
|
return;
|
|
106
110
|
}
|
|
107
|
-
|
|
108
|
-
this.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
...(preferred.tasks === undefined ? {} : { tasks: preferred.tasks }),
|
|
111
|
+
const orderedRuns = this.orderRuns(activeRuns);
|
|
112
|
+
this.currentRunDir = orderedRuns[0]?.runDir;
|
|
113
|
+
this.currentRunDirs = orderedRuns.map((run) => run.runDir);
|
|
114
|
+
this.updateState(this.buildStateFromRuns(orderedRuns, {
|
|
112
115
|
live: true,
|
|
113
116
|
snapshotOnly: false,
|
|
114
117
|
checkedAt: data.checkedAt,
|
|
115
|
-
});
|
|
118
|
+
}));
|
|
116
119
|
this.startFileWatcher();
|
|
117
120
|
this.schedulePoll(SUBAGENTS_POLL_INTERVAL_MS);
|
|
118
121
|
}
|
|
@@ -182,71 +185,77 @@ export class AppSubagentsWidgetController {
|
|
|
182
185
|
}
|
|
183
186
|
}
|
|
184
187
|
async refreshFromFiles(generation = this.refreshGeneration) {
|
|
185
|
-
|
|
186
|
-
if (!
|
|
187
|
-
const registry = await readSubagentRegistry(this.host.cwd);
|
|
188
|
-
if (!this.isCurrentGeneration(generation))
|
|
189
|
-
return;
|
|
190
|
-
runDir = await this.findActiveRegistryRunDirForCurrentSession(registry, generation);
|
|
191
|
-
if (!this.isCurrentGeneration(generation))
|
|
192
|
-
return;
|
|
193
|
-
if (runDir)
|
|
194
|
-
this.currentRunDir = runDir;
|
|
195
|
-
}
|
|
196
|
-
if (!runDir) {
|
|
197
|
-
if (!this.isCurrentGeneration(generation))
|
|
198
|
-
return;
|
|
199
|
-
this.updateState(undefined);
|
|
188
|
+
const registry = await readSubagentRegistry(this.host.cwd);
|
|
189
|
+
if (!this.isCurrentGeneration(generation))
|
|
200
190
|
return;
|
|
201
|
-
|
|
202
|
-
this.currentRunDir = runDir;
|
|
203
|
-
const fileState = await readSubagentRunStateFromFiles(runDir, { includeLineCounts: false });
|
|
191
|
+
const runDirs = await this.findActiveRegistryRunDirsForCurrentSession(registry, generation);
|
|
204
192
|
if (!this.isCurrentGeneration(generation))
|
|
205
193
|
return;
|
|
206
|
-
if (
|
|
207
|
-
|
|
194
|
+
if (runDirs.length === 0) {
|
|
195
|
+
this.currentRunDir = undefined;
|
|
196
|
+
this.currentRunDirs = [];
|
|
197
|
+
this.updateState(undefined);
|
|
208
198
|
return;
|
|
209
199
|
}
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
200
|
+
const runs = [];
|
|
201
|
+
for (const runDir of runDirs) {
|
|
202
|
+
const fileState = await readSubagentRunStateFromFiles(runDir, { includeLineCounts: false });
|
|
203
|
+
if (!this.isCurrentGeneration(generation))
|
|
204
|
+
return;
|
|
205
|
+
if (!fileState) {
|
|
213
206
|
this.clearCachedRun(runDir);
|
|
214
|
-
|
|
215
|
-
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const activeAgents = activeSubagentStates(fileState.agents);
|
|
210
|
+
if (activeAgents.length === 0) {
|
|
211
|
+
if (allSubagentStatesTerminal(fileState.agents) || fileState.agents.length === 0)
|
|
212
|
+
this.clearCachedRun(runDir);
|
|
213
|
+
continue;
|
|
216
214
|
}
|
|
215
|
+
const tasks = this.taskPreviewsByRunDir.get(runDir);
|
|
216
|
+
runs.push({
|
|
217
|
+
runDir,
|
|
218
|
+
agents: fileState.agents,
|
|
219
|
+
...(tasks === undefined ? {} : { tasks }),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
if (runs.length === 0) {
|
|
223
|
+
this.currentRunDir = undefined;
|
|
224
|
+
this.currentRunDirs = [];
|
|
225
|
+
this.updateState(undefined);
|
|
217
226
|
return;
|
|
218
227
|
}
|
|
219
|
-
const
|
|
220
|
-
this.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
...(tasks === undefined ? {} : { tasks }),
|
|
228
|
+
const orderedRuns = this.orderRuns(runs);
|
|
229
|
+
this.currentRunDir = orderedRuns[0]?.runDir;
|
|
230
|
+
this.currentRunDirs = orderedRuns.map((run) => run.runDir);
|
|
231
|
+
this.updateState(this.buildStateFromRuns(orderedRuns, {
|
|
224
232
|
live: true,
|
|
225
233
|
snapshotOnly: false,
|
|
226
234
|
checkedAt: Date.now(),
|
|
227
|
-
});
|
|
235
|
+
}));
|
|
228
236
|
}
|
|
229
|
-
async
|
|
237
|
+
async findActiveRegistryRunDirsForCurrentSession(registry, generation) {
|
|
230
238
|
if (!registry)
|
|
231
|
-
return
|
|
239
|
+
return [];
|
|
232
240
|
const sessionFile = this.host.sessionFile();
|
|
233
241
|
if (!sessionFile)
|
|
234
|
-
return
|
|
242
|
+
return [];
|
|
235
243
|
const candidateRunDirs = this.registryRunDirsNewestFirst(registry);
|
|
244
|
+
const matchingRunDirs = [];
|
|
236
245
|
for (const runDir of candidateRunDirs) {
|
|
237
246
|
if (!this.isCurrentGeneration(generation))
|
|
238
|
-
return
|
|
247
|
+
return [];
|
|
239
248
|
if (!(await subagentRunHasParentSession(runDir, sessionFile)))
|
|
240
249
|
continue;
|
|
241
250
|
if (!this.isCurrentGeneration(generation))
|
|
242
|
-
return
|
|
251
|
+
return [];
|
|
243
252
|
const state = await readSubagentRunStateFromFiles(runDir, { includeLineCounts: false });
|
|
244
253
|
if (!this.isCurrentGeneration(generation))
|
|
245
|
-
return
|
|
254
|
+
return [];
|
|
246
255
|
if (activeSubagentStates(state?.agents ?? []).length > 0)
|
|
247
|
-
|
|
256
|
+
matchingRunDirs.push(runDir);
|
|
248
257
|
}
|
|
249
|
-
return
|
|
258
|
+
return matchingRunDirs;
|
|
250
259
|
}
|
|
251
260
|
registryRunDirsNewestFirst(registry) {
|
|
252
261
|
const runDirs = [];
|
|
@@ -263,30 +272,89 @@ export class AppSubagentsWidgetController {
|
|
|
263
272
|
addRunDir(registry.latestRunDir);
|
|
264
273
|
Object.values(registry.runs)
|
|
265
274
|
.sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt))
|
|
266
|
-
.forEach((run) =>
|
|
275
|
+
.forEach((run) => {
|
|
276
|
+
this.rememberRunFreshness(run.runDir, this.parseRunTimestamp(run.updatedAt) ?? this.parseRunTimestamp(run.createdAt));
|
|
277
|
+
addRunDir(run.runDir);
|
|
278
|
+
});
|
|
267
279
|
return runDirs;
|
|
268
280
|
}
|
|
269
|
-
async clearMissingRunAndMaybeSelectReplacement(runDir, generation) {
|
|
270
|
-
this.clearCachedRun(runDir);
|
|
271
|
-
if (this.currentRunDir === runDir)
|
|
272
|
-
this.currentRunDir = undefined;
|
|
273
|
-
const registry = await readSubagentRegistry(this.host.cwd);
|
|
274
|
-
if (!this.isCurrentGeneration(generation))
|
|
275
|
-
return;
|
|
276
|
-
const replacementRunDir = await this.findActiveRegistryRunDirForCurrentSession(registry, generation);
|
|
277
|
-
if (!this.isCurrentGeneration(generation))
|
|
278
|
-
return;
|
|
279
|
-
if (replacementRunDir) {
|
|
280
|
-
this.currentRunDir = replacementRunDir;
|
|
281
|
-
await this.refreshFromFiles(generation);
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
this.updateState(undefined);
|
|
285
|
-
}
|
|
286
281
|
clearCachedRun(runDir) {
|
|
282
|
+
this.runFreshnessByRunDir.delete(runDir);
|
|
287
283
|
this.snapshotByRunDir.delete(runDir);
|
|
288
284
|
this.taskPreviewsByRunDir.delete(runDir);
|
|
289
285
|
}
|
|
286
|
+
buildStateFromRuns(runs, meta) {
|
|
287
|
+
const activeRuns = runs
|
|
288
|
+
.map((run) => ({ ...run, agents: activeSubagentStates(run.agents) }))
|
|
289
|
+
.filter((run) => run.agents.length > 0);
|
|
290
|
+
if (activeRuns.length === 0)
|
|
291
|
+
return undefined;
|
|
292
|
+
const primary = activeRuns[0];
|
|
293
|
+
if (!primary)
|
|
294
|
+
return undefined;
|
|
295
|
+
return {
|
|
296
|
+
runs: activeRuns.map((run) => ({
|
|
297
|
+
runDir: run.runDir,
|
|
298
|
+
agents: run.agents,
|
|
299
|
+
...(run.tasks === undefined ? {} : { tasks: run.tasks }),
|
|
300
|
+
})),
|
|
301
|
+
runDir: primary.runDir,
|
|
302
|
+
agents: activeRuns.flatMap((run) => run.agents),
|
|
303
|
+
...(primary.tasks === undefined ? {} : { tasks: primary.tasks }),
|
|
304
|
+
...meta,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
orderRuns(runs) {
|
|
308
|
+
const order = this.orderRunDirs(runs.map((run) => run.runDir));
|
|
309
|
+
const priority = new Map(order.map((runDir, index) => [runDir, index]));
|
|
310
|
+
return [...runs].sort((a, b) => {
|
|
311
|
+
const freshness = this.runFreshness(b) - this.runFreshness(a);
|
|
312
|
+
if (freshness !== 0)
|
|
313
|
+
return freshness;
|
|
314
|
+
return (priority.get(a.runDir) ?? Number.MAX_SAFE_INTEGER) - (priority.get(b.runDir) ?? Number.MAX_SAFE_INTEGER);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
orderRunDirs(runDirs) {
|
|
318
|
+
const seen = new Set();
|
|
319
|
+
const preferred = [this.currentRunDir, ...this.currentRunDirs, ...runDirs].filter((runDir) => Boolean(runDir));
|
|
320
|
+
const ordered = [];
|
|
321
|
+
for (const runDir of preferred) {
|
|
322
|
+
if (seen.has(runDir))
|
|
323
|
+
continue;
|
|
324
|
+
seen.add(runDir);
|
|
325
|
+
ordered.push(runDir);
|
|
326
|
+
}
|
|
327
|
+
return ordered;
|
|
328
|
+
}
|
|
329
|
+
rememberRunFreshness(runDir, freshness) {
|
|
330
|
+
if (!Number.isFinite(freshness))
|
|
331
|
+
return;
|
|
332
|
+
const next = freshness ?? 0;
|
|
333
|
+
const previous = this.runFreshnessByRunDir.get(runDir) ?? Number.NEGATIVE_INFINITY;
|
|
334
|
+
if (next >= previous)
|
|
335
|
+
this.runFreshnessByRunDir.set(runDir, next);
|
|
336
|
+
}
|
|
337
|
+
runFreshness(run) {
|
|
338
|
+
return this.runFreshnessFromAgents(run.agents)
|
|
339
|
+
?? this.runFreshnessByRunDir.get(run.runDir)
|
|
340
|
+
?? 0;
|
|
341
|
+
}
|
|
342
|
+
runFreshnessFromAgents(agents) {
|
|
343
|
+
let freshest;
|
|
344
|
+
for (const agent of agents) {
|
|
345
|
+
const parsed = this.parseRunTimestamp(agent.startedAt);
|
|
346
|
+
if (parsed === undefined)
|
|
347
|
+
continue;
|
|
348
|
+
freshest = freshest === undefined ? parsed : Math.max(freshest, parsed);
|
|
349
|
+
}
|
|
350
|
+
return freshest;
|
|
351
|
+
}
|
|
352
|
+
parseRunTimestamp(value) {
|
|
353
|
+
if (!value)
|
|
354
|
+
return undefined;
|
|
355
|
+
const parsed = Date.parse(value);
|
|
356
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
357
|
+
}
|
|
290
358
|
updateState(next) {
|
|
291
359
|
const previous = stringifyUnknown(this.state);
|
|
292
360
|
const serializedNext = stringifyUnknown(next);
|
|
@@ -298,6 +366,9 @@ export class AppSubagentsWidgetController {
|
|
|
298
366
|
return generation === this.refreshGeneration;
|
|
299
367
|
}
|
|
300
368
|
shouldContinuePolling() {
|
|
369
|
+
const runs = this.state?.runs;
|
|
370
|
+
if (runs?.length)
|
|
371
|
+
return runs.some((run) => activeSubagentStates(run.agents).length > 0);
|
|
301
372
|
return activeSubagentStates(this.state?.agents ?? []).length > 0;
|
|
302
373
|
}
|
|
303
374
|
eventMatchesCurrentSession(eventSessionFile) {
|
|
@@ -24,6 +24,10 @@ export declare class AppTerminalController {
|
|
|
24
24
|
private terminalEnabled;
|
|
25
25
|
private interactiveSuspended;
|
|
26
26
|
private stopPromise;
|
|
27
|
+
private keyboardProtocolNegotiationBuffer;
|
|
28
|
+
private keyboardProtocolBufferFlushTimer;
|
|
29
|
+
private kittyProtocolActive;
|
|
30
|
+
private modifyOtherKeysActive;
|
|
27
31
|
private readonly enterInteractiveSequence;
|
|
28
32
|
private readonly exitInteractiveSequence;
|
|
29
33
|
constructor(host: AppTerminalControllerHost);
|
|
@@ -40,4 +44,10 @@ export declare class AppTerminalController {
|
|
|
40
44
|
private scheduleForcedProcessExit;
|
|
41
45
|
private readonly onResize;
|
|
42
46
|
private readonly onInputData;
|
|
47
|
+
private beginKeyboardProtocolNegotiation;
|
|
48
|
+
private filterKeyboardProtocolNegotiationInput;
|
|
49
|
+
private handleKeyboardProtocolNegotiationResponse;
|
|
50
|
+
private enableModifyOtherKeysFallback;
|
|
51
|
+
private setKeyboardProtocolNegotiationBuffer;
|
|
52
|
+
private clearKeyboardProtocolNegotiationBuffer;
|
|
43
53
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { ANSI_RESET } from "../../theme.js";
|
|
2
|
-
import { DISABLE_BRACKETED_PASTE, DISABLE_TERMINAL_KEY_REPORTING, DISABLE_TERMINAL_WRAP, CLEAR_TERMINAL, ENABLE_BRACKETED_PASTE, ENABLE_TERMINAL_KEY_REPORTING, ENABLE_TERMINAL_WRAP, HIDE_CURSOR, RESET_TERMINAL_VIEWPORT_STATE, RUNTIME_DISPOSE_GRACE_MS, SHOW_CURSOR, } from "../constants.js";
|
|
2
|
+
import { DISABLE_BRACKETED_PASTE, DISABLE_TERMINAL_KEY_REPORTING, DISABLE_TERMINAL_WRAP, CLEAR_TERMINAL, ENABLE_BRACKETED_PASTE, ENABLE_TERMINAL_MODIFY_OTHER_KEYS, ENABLE_TERMINAL_KEY_REPORTING, ENABLE_TERMINAL_WRAP, HIDE_CURSOR, RESET_TERMINAL_VIEWPORT_STATE, RUNTIME_DISPOSE_GRACE_MS, SHOW_CURSOR, } from "../constants.js";
|
|
3
3
|
export class AppTerminalController {
|
|
4
4
|
host;
|
|
5
5
|
terminalEnabled = false;
|
|
6
6
|
interactiveSuspended = false;
|
|
7
7
|
stopPromise;
|
|
8
|
+
keyboardProtocolNegotiationBuffer = "";
|
|
9
|
+
keyboardProtocolBufferFlushTimer;
|
|
10
|
+
kittyProtocolActive = false;
|
|
11
|
+
modifyOtherKeysActive = false;
|
|
8
12
|
enterInteractiveSequence = `${ANSI_RESET}${RESET_TERMINAL_VIEWPORT_STATE}${CLEAR_TERMINAL}\x1b[?1049h${RESET_TERMINAL_VIEWPORT_STATE}${CLEAR_TERMINAL}${ENABLE_TERMINAL_KEY_REPORTING}${ENABLE_BRACKETED_PASTE}${DISABLE_TERMINAL_WRAP}\x1b[?1002h\x1b[?1006h${HIDE_CURSOR}`;
|
|
9
13
|
exitInteractiveSequence = `${ANSI_RESET}${RESET_TERMINAL_VIEWPORT_STATE}${DISABLE_TERMINAL_KEY_REPORTING}${DISABLE_BRACKETED_PASTE}${ENABLE_TERMINAL_WRAP}\x1b[?1006l\x1b[?1002l\x1b[?1049l${SHOW_CURSOR}`;
|
|
10
14
|
constructor(host) {
|
|
@@ -17,6 +21,7 @@ export class AppTerminalController {
|
|
|
17
21
|
if (this.terminalEnabled)
|
|
18
22
|
return;
|
|
19
23
|
this.terminalEnabled = true;
|
|
24
|
+
this.beginKeyboardProtocolNegotiation();
|
|
20
25
|
process.stdin.setRawMode(true);
|
|
21
26
|
process.stdin.resume();
|
|
22
27
|
process.stdin.on("data", this.onInputData);
|
|
@@ -66,6 +71,7 @@ export class AppTerminalController {
|
|
|
66
71
|
restoreTerminal = () => {
|
|
67
72
|
if (!this.terminalEnabled)
|
|
68
73
|
return;
|
|
74
|
+
this.clearKeyboardProtocolNegotiationBuffer();
|
|
69
75
|
this.terminalEnabled = false;
|
|
70
76
|
this.interactiveSuspended = false;
|
|
71
77
|
process.stdout.write(this.exitInteractiveSequence);
|
|
@@ -87,6 +93,7 @@ export class AppTerminalController {
|
|
|
87
93
|
if (!this.terminalEnabled || !this.interactiveSuspended)
|
|
88
94
|
return;
|
|
89
95
|
this.interactiveSuspended = false;
|
|
96
|
+
this.beginKeyboardProtocolNegotiation();
|
|
90
97
|
if (process.stdin.isTTY)
|
|
91
98
|
process.stdin.setRawMode(true);
|
|
92
99
|
process.stdin.resume();
|
|
@@ -99,6 +106,7 @@ export class AppTerminalController {
|
|
|
99
106
|
async stopInternal() {
|
|
100
107
|
if (!this.host.isRunning())
|
|
101
108
|
return;
|
|
109
|
+
this.clearKeyboardProtocolNegotiationBuffer();
|
|
102
110
|
this.host.setRunning(false);
|
|
103
111
|
this.host.closeSdkMenuForStop();
|
|
104
112
|
this.host.clearToastTimers();
|
|
@@ -133,6 +141,87 @@ export class AppTerminalController {
|
|
|
133
141
|
this.host.render();
|
|
134
142
|
};
|
|
135
143
|
onInputData = (chunk) => {
|
|
136
|
-
this.
|
|
144
|
+
const input = this.filterKeyboardProtocolNegotiationInput(chunk.toString("utf8"));
|
|
145
|
+
if (input)
|
|
146
|
+
this.host.handleInputChunk(Buffer.from(input, "utf8"));
|
|
137
147
|
};
|
|
148
|
+
beginKeyboardProtocolNegotiation() {
|
|
149
|
+
this.clearKeyboardProtocolNegotiationBuffer();
|
|
150
|
+
this.kittyProtocolActive = false;
|
|
151
|
+
this.modifyOtherKeysActive = false;
|
|
152
|
+
}
|
|
153
|
+
filterKeyboardProtocolNegotiationInput(data) {
|
|
154
|
+
let input = this.keyboardProtocolNegotiationBuffer + data;
|
|
155
|
+
this.clearKeyboardProtocolNegotiationBuffer();
|
|
156
|
+
let output = "";
|
|
157
|
+
while (input.length > 0) {
|
|
158
|
+
const response = readKeyboardProtocolNegotiationResponse(input);
|
|
159
|
+
if (response.kind === "complete") {
|
|
160
|
+
this.handleKeyboardProtocolNegotiationResponse(response.response);
|
|
161
|
+
input = input.slice(response.length);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (response.kind === "pending") {
|
|
165
|
+
this.setKeyboardProtocolNegotiationBuffer(input);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
output += input[0];
|
|
169
|
+
input = input.slice(1);
|
|
170
|
+
}
|
|
171
|
+
return output;
|
|
172
|
+
}
|
|
173
|
+
handleKeyboardProtocolNegotiationResponse(response) {
|
|
174
|
+
if (response.type === "kitty-flags") {
|
|
175
|
+
if (response.flags !== 0) {
|
|
176
|
+
this.kittyProtocolActive = true;
|
|
177
|
+
this.modifyOtherKeysActive = false;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
this.enableModifyOtherKeysFallback();
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (!this.kittyProtocolActive)
|
|
185
|
+
this.enableModifyOtherKeysFallback();
|
|
186
|
+
}
|
|
187
|
+
enableModifyOtherKeysFallback() {
|
|
188
|
+
if (this.kittyProtocolActive || this.modifyOtherKeysActive)
|
|
189
|
+
return;
|
|
190
|
+
process.stdout.write(ENABLE_TERMINAL_MODIFY_OTHER_KEYS);
|
|
191
|
+
this.modifyOtherKeysActive = true;
|
|
192
|
+
}
|
|
193
|
+
setKeyboardProtocolNegotiationBuffer(data) {
|
|
194
|
+
this.clearKeyboardProtocolNegotiationBuffer();
|
|
195
|
+
this.keyboardProtocolNegotiationBuffer = data;
|
|
196
|
+
this.keyboardProtocolBufferFlushTimer = setTimeout(() => {
|
|
197
|
+
this.keyboardProtocolBufferFlushTimer = undefined;
|
|
198
|
+
const buffered = this.keyboardProtocolNegotiationBuffer;
|
|
199
|
+
this.keyboardProtocolNegotiationBuffer = "";
|
|
200
|
+
if (buffered)
|
|
201
|
+
this.host.handleInputChunk(Buffer.from(buffered, "utf8"));
|
|
202
|
+
}, 150);
|
|
203
|
+
}
|
|
204
|
+
clearKeyboardProtocolNegotiationBuffer() {
|
|
205
|
+
if (this.keyboardProtocolBufferFlushTimer)
|
|
206
|
+
clearTimeout(this.keyboardProtocolBufferFlushTimer);
|
|
207
|
+
this.keyboardProtocolBufferFlushTimer = undefined;
|
|
208
|
+
this.keyboardProtocolNegotiationBuffer = "";
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function readKeyboardProtocolNegotiationResponse(input) {
|
|
212
|
+
const kittyFlags = /^\x1b\[\?(\d+)u/.exec(input);
|
|
213
|
+
if (kittyFlags) {
|
|
214
|
+
return {
|
|
215
|
+
kind: "complete",
|
|
216
|
+
response: { type: "kitty-flags", flags: Number.parseInt(kittyFlags[1] ?? "0", 10) },
|
|
217
|
+
length: kittyFlags[0].length,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const deviceAttributes = /^\x1b\[\?[\d;]*c/.exec(input);
|
|
221
|
+
if (deviceAttributes) {
|
|
222
|
+
return { kind: "complete", response: { type: "device-attributes" }, length: deviceAttributes[0].length };
|
|
223
|
+
}
|
|
224
|
+
if (input === "\x1b[" || /^\x1b\[\?[\d;]*$/.test(input))
|
|
225
|
+
return { kind: "pending" };
|
|
226
|
+
return { kind: "none" };
|
|
138
227
|
}
|
|
@@ -59,6 +59,8 @@ export function isTodoLiveStateEvent(value) {
|
|
|
59
59
|
return false;
|
|
60
60
|
if (value.sessionFile !== undefined && typeof value.sessionFile !== "string")
|
|
61
61
|
return false;
|
|
62
|
+
if (value.sessionId !== undefined && typeof value.sessionId !== "string")
|
|
63
|
+
return false;
|
|
62
64
|
return typeof value.checkedAt === "number" && Number.isFinite(value.checkedAt);
|
|
63
65
|
}
|
|
64
66
|
export function todoStatusIcon(status) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TodoDetails } from "../types.js";
|
|
2
2
|
export type TodoWidgetControllerHost = {
|
|
3
3
|
sessionFile?(): string | undefined;
|
|
4
|
+
sessionId?(): string | undefined;
|
|
4
5
|
isRunning(): boolean;
|
|
5
6
|
render(): void;
|
|
6
7
|
};
|
|
@@ -17,5 +18,6 @@ export declare class AppTodoWidgetController {
|
|
|
17
18
|
private updateDetailsForCurrentSession;
|
|
18
19
|
private updateDetailsForSession;
|
|
19
20
|
private currentSessionDetails;
|
|
21
|
+
private currentSessionKey;
|
|
20
22
|
private sessionKey;
|
|
21
23
|
}
|
|
@@ -25,17 +25,21 @@ export class AppTodoWidgetController {
|
|
|
25
25
|
observeLiveState(data) {
|
|
26
26
|
if (!isTodoLiveStateEvent(data))
|
|
27
27
|
return;
|
|
28
|
-
|
|
28
|
+
const sessionFile = data.sessionFile;
|
|
29
|
+
const sessionId = data.sessionId;
|
|
30
|
+
if (!sessionFile && !sessionId && this.currentSessionKey())
|
|
31
|
+
return;
|
|
32
|
+
this.updateDetailsForSession(sessionFile, sessionId, this.visibleDetails(data.details));
|
|
29
33
|
}
|
|
30
34
|
visibleDetails(details) {
|
|
31
35
|
return hasOpenTodoTasks(details) ? details : undefined;
|
|
32
36
|
}
|
|
33
37
|
updateDetailsForCurrentSession(next, options = {}) {
|
|
34
|
-
this.updateDetailsForSession(this.host.sessionFile?.(), next, options);
|
|
38
|
+
this.updateDetailsForSession(this.host.sessionFile?.(), this.host.sessionId?.(), next, options);
|
|
35
39
|
}
|
|
36
|
-
updateDetailsForSession(sessionFile, next, options = {}) {
|
|
40
|
+
updateDetailsForSession(sessionFile, sessionId, next, options = {}) {
|
|
37
41
|
const previous = stringifyUnknown(this.currentSessionDetails());
|
|
38
|
-
const key = this.sessionKey(sessionFile);
|
|
42
|
+
const key = this.sessionKey(sessionFile, sessionId);
|
|
39
43
|
if (key) {
|
|
40
44
|
if (next)
|
|
41
45
|
this.detailsBySessionFile.set(key, next);
|
|
@@ -50,10 +54,16 @@ export class AppTodoWidgetController {
|
|
|
50
54
|
this.host.render();
|
|
51
55
|
}
|
|
52
56
|
currentSessionDetails() {
|
|
53
|
-
const key = this.
|
|
57
|
+
const key = this.currentSessionKey();
|
|
54
58
|
return key ? this.detailsBySessionFile.get(key) : this.unscopedDetails;
|
|
55
59
|
}
|
|
56
|
-
|
|
57
|
-
return sessionFile
|
|
60
|
+
currentSessionKey() {
|
|
61
|
+
return this.sessionKey(this.host.sessionFile?.(), this.host.sessionId?.());
|
|
62
|
+
}
|
|
63
|
+
sessionKey(sessionFile, sessionId) {
|
|
64
|
+
if (sessionFile)
|
|
65
|
+
return `file:${resolve(sessionFile)}`;
|
|
66
|
+
const normalizedSessionId = sessionId?.trim();
|
|
67
|
+
return normalizedSessionId ? `id:${normalizedSessionId}` : undefined;
|
|
58
68
|
}
|
|
59
69
|
}
|
package/dist/app/types.d.ts
CHANGED
|
@@ -74,6 +74,8 @@ export type Entry = {
|
|
|
74
74
|
expanded: boolean;
|
|
75
75
|
status: "running" | "done";
|
|
76
76
|
level?: string;
|
|
77
|
+
startedAt?: number;
|
|
78
|
+
finishedAt?: number;
|
|
77
79
|
} | {
|
|
78
80
|
id: string;
|
|
79
81
|
kind: "tool";
|
|
@@ -125,6 +127,7 @@ export type TodoLiveStateEvent = {
|
|
|
125
127
|
version: 1;
|
|
126
128
|
details: TodoDetails;
|
|
127
129
|
sessionFile?: string;
|
|
130
|
+
sessionId?: string;
|
|
128
131
|
checkedAt: number;
|
|
129
132
|
};
|
|
130
133
|
export type SubagentStatus = (typeof SUBAGENT_STATUSES)[number];
|
|
@@ -193,6 +196,7 @@ export type SubagentRegistryAgent = {
|
|
|
193
196
|
updatedAt: string;
|
|
194
197
|
};
|
|
195
198
|
export type SubagentsWidgetState = {
|
|
199
|
+
runs?: SubagentLiveStateRun[];
|
|
196
200
|
runDir: string;
|
|
197
201
|
agents: SubagentAgentState[];
|
|
198
202
|
tasks?: SubagentTaskPreview[];
|
|
@@ -60,6 +60,12 @@ function wrapLine(line, width) {
|
|
|
60
60
|
lines.push(current.trimEnd());
|
|
61
61
|
return lines.length > 0 ? lines : [""];
|
|
62
62
|
}
|
|
63
|
+
function wrapIndentedLine(line, width) {
|
|
64
|
+
const indent = line.match(/^\s*/)?.[0] ?? "";
|
|
65
|
+
const content = line.slice(indent.length);
|
|
66
|
+
const contentWidth = Math.max(1, width - indent.length);
|
|
67
|
+
return wrapLine(content, contentWidth).map((wrapped) => `${indent}${wrapped}`);
|
|
68
|
+
}
|
|
63
69
|
function stripAnsi(text) {
|
|
64
70
|
return text.replace(/\u001b\[[0-9;]*m/g, "");
|
|
65
71
|
}
|
|
@@ -191,7 +197,8 @@ export async function runQuestionnaire(questions, ctx) {
|
|
|
191
197
|
clickZones.push({ ...zone, row, startColumn: 1, endColumn: width + 1 });
|
|
192
198
|
}
|
|
193
199
|
function renderMutedLine(add, text, width) {
|
|
194
|
-
|
|
200
|
+
for (const line of wrapIndentedLine(text, width))
|
|
201
|
+
add(paintLine(theme, line, width, { foreground: "muted" }));
|
|
195
202
|
}
|
|
196
203
|
function moveToQuestion(index) {
|
|
197
204
|
captureCustomDraft();
|