pi-ui-extend 0.1.36 → 0.1.38
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/dist/app/app.d.ts +7 -0
- package/dist/app/app.js +40 -5
- package/dist/app/commands/command-controller.js +1 -0
- package/dist/app/commands/command-registry.d.ts +1 -0
- package/dist/app/commands/command-registry.js +8 -0
- package/dist/app/commands/command-session-actions.d.ts +2 -0
- package/dist/app/commands/command-session-actions.js +79 -1
- package/dist/app/extensions/extension-actions-controller.d.ts +4 -1
- package/dist/app/extensions/extension-actions-controller.js +31 -2
- package/dist/app/input/input-controller.d.ts +1 -0
- package/dist/app/input/input-controller.js +23 -2
- package/dist/app/input/terminal-edit-shortcuts.d.ts +1 -0
- package/dist/app/input/terminal-edit-shortcuts.js +7 -0
- package/dist/app/input/voice-controller.js +1 -1
- package/dist/app/popup/popup-action-controller.d.ts +1 -3
- package/dist/app/popup/popup-action-controller.js +1 -5
- package/dist/app/rendering/message-content.js +4 -3
- package/dist/app/rendering/render-controller.js +21 -38
- package/dist/app/rendering/status-line-renderer.d.ts +1 -0
- package/dist/app/rendering/status-line-renderer.js +14 -2
- package/dist/app/runtime.js +12 -2
- package/dist/app/screen/mouse-controller.js +2 -0
- package/dist/app/session/session-event-controller.d.ts +7 -0
- package/dist/app/session/session-event-controller.js +10 -13
- package/dist/app/terminal/terminal-controller.js +1 -0
- package/dist/app/terminal/terminal-output-buffer.d.ts +8 -6
- package/dist/app/terminal/terminal-output-buffer.js +24 -16
- package/dist/bundled-extensions/terminal-bell/index.js +118 -33
- package/dist/markdown-format.d.ts +1 -0
- package/dist/markdown-format.js +30 -16
- package/dist/schemas/pi-tools-suite-schema.d.ts +5 -0
- package/dist/schemas/pi-tools-suite-schema.js +5 -0
- package/dist/tool-renderers/apply-patch.js +6 -1
- package/dist/tool-renderers/patch-normalize.d.ts +24 -0
- package/dist/tool-renderers/patch-normalize.js +163 -0
- package/external/pi-tools-suite/README.md +3 -2
- package/external/pi-tools-suite/package.json +3 -3
- package/external/pi-tools-suite/src/antigravity-auth/index.ts +15 -2
- package/external/pi-tools-suite/src/antigravity-auth/status.ts +36 -19
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +5 -2
- package/external/pi-tools-suite/src/async-subagents/commands.ts +12 -2
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +8 -3
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +63 -28
- package/external/pi-tools-suite/src/async-subagents/core/tool-guard.ts +9 -4
- package/external/pi-tools-suite/src/comment-checker/config.ts +98 -0
- package/external/pi-tools-suite/src/comment-checker/detect.ts +215 -0
- package/external/pi-tools-suite/src/comment-checker/index.ts +294 -0
- package/external/pi-tools-suite/src/dcp/commands.ts +29 -15
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +111 -60
- package/external/pi-tools-suite/src/dcp/config.ts +10 -6
- package/external/pi-tools-suite/src/dcp/debug-log.ts +235 -0
- package/external/pi-tools-suite/src/dcp/index.ts +204 -27
- package/external/pi-tools-suite/src/dcp/prompts.ts +25 -28
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +6 -10
- package/external/pi-tools-suite/src/dcp/pruner-compression-blocks.ts +19 -1
- package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +36 -58
- package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +18 -0
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +3 -3
- package/external/pi-tools-suite/src/dcp/pruner.ts +4 -2
- package/external/pi-tools-suite/src/dcp/state-persistence.ts +31 -2
- package/external/pi-tools-suite/src/dcp/state.ts +62 -4
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +18 -0
- package/external/pi-tools-suite/src/index.ts +1 -0
- package/external/pi-tools-suite/src/model-tools/index.ts +11 -3
- package/external/pi-tools-suite/src/telegram-mirror/index.ts +1 -1
- package/external/pi-tools-suite/src/todo/index.ts +24 -0
- package/external/pi-tools-suite/src/tool-descriptions.ts +3 -3
- package/external/pi-tools-suite/src/usage/index.ts +18 -4
- package/package.json +4 -4
- package/schemas/pi-tools-suite.json +24 -0
package/dist/app/app.d.ts
CHANGED
|
@@ -43,6 +43,13 @@ export declare class PiUiExtendApp {
|
|
|
43
43
|
private readonly toastNotifier;
|
|
44
44
|
private readonly extensionShutdownHandler;
|
|
45
45
|
private runtime;
|
|
46
|
+
/**
|
|
47
|
+
* Maps each session runtime to the isolated extension event bus it was
|
|
48
|
+
* created with. The renderer uses this to emit signals (e.g. retry state)
|
|
49
|
+
* back to the extensions running inside a specific runtime/tab, so a signal
|
|
50
|
+
* for one tab never reaches another tab's extensions.
|
|
51
|
+
*/
|
|
52
|
+
private readonly extensionEventBusByRuntime;
|
|
46
53
|
private readonly inputEditor;
|
|
47
54
|
private lastInputEditorContentVersion;
|
|
48
55
|
private readonly requestHistory;
|
package/dist/app/app.js
CHANGED
|
@@ -112,6 +112,13 @@ export class PiUiExtendApp {
|
|
|
112
112
|
};
|
|
113
113
|
extensionShutdownHandler = () => { };
|
|
114
114
|
runtime;
|
|
115
|
+
/**
|
|
116
|
+
* Maps each session runtime to the isolated extension event bus it was
|
|
117
|
+
* created with. The renderer uses this to emit signals (e.g. retry state)
|
|
118
|
+
* back to the extensions running inside a specific runtime/tab, so a signal
|
|
119
|
+
* for one tab never reaches another tab's extensions.
|
|
120
|
+
*/
|
|
121
|
+
extensionEventBusByRuntime = new WeakMap();
|
|
115
122
|
inputEditor = new InputEditor();
|
|
116
123
|
lastInputEditorContentVersion = this.inputEditor.contentVersion;
|
|
117
124
|
requestHistory;
|
|
@@ -166,7 +173,18 @@ export class PiUiExtendApp {
|
|
|
166
173
|
maxProjectSessions: () => this.pixConfig.maxProjectSessions,
|
|
167
174
|
blinkController: this.blinkController,
|
|
168
175
|
runtime: () => this.runtime,
|
|
169
|
-
createRuntimeForNewSession: () => this.createRuntime(
|
|
176
|
+
createRuntimeForNewSession: () => this.createRuntime(
|
|
177
|
+
// Never reuse services across tabs. The SDK ties the extension
|
|
178
|
+
// runtime (resourceLoader.getExtensions().runtime) to the
|
|
179
|
+
// resourceLoader, and that runtime is shared by every session
|
|
180
|
+
// built from the same services. When any such session is
|
|
181
|
+
// disposed (tab close / session replacement / reload), the SDK
|
|
182
|
+
// invalidates that shared runtime, which makes every sibling
|
|
183
|
+
// session's captured `pi`/ctx stale — session_start handlers
|
|
184
|
+
// then throw "ctx is stale after session replacement or reload".
|
|
185
|
+
// Fresh services per session = fresh extension runtime = no
|
|
186
|
+
// cross-session invalidation.
|
|
187
|
+
newTabRuntimeOptions(this.options)),
|
|
170
188
|
createRuntimeForSession: (sessionPath) => this.createRuntime({
|
|
171
189
|
...this.options,
|
|
172
190
|
noSession: false,
|
|
@@ -373,6 +391,16 @@ export class PiUiExtendApp {
|
|
|
373
391
|
setSessionActivity: (activity) => this.setSessionActivity(activity),
|
|
374
392
|
updateQueuedMessageStatus: () => this.queuedMessages.updateQueuedMessageStatus(),
|
|
375
393
|
flushAutoUserMessages: () => { void this.queuedMessages.flushAutoUserMessages(); },
|
|
394
|
+
emitExtensionEvent: (channel, data) => {
|
|
395
|
+
// Emit a signal to the extensions running inside the active
|
|
396
|
+
// runtime's event bus. Used to inform extensions (terminal-bell,
|
|
397
|
+
// todo) about session lifecycle state the SDK doesn't forward to
|
|
398
|
+
// them, such as auto-retry being in progress.
|
|
399
|
+
const runtime = this.runtime;
|
|
400
|
+
if (!runtime)
|
|
401
|
+
return;
|
|
402
|
+
this.extensionEventBusByRuntime.get(runtime)?.emit(channel, data);
|
|
403
|
+
},
|
|
376
404
|
prepareWorkspaceMutation: (toolName, args) => this.workspaceActions.prepareWorkspaceMutation(toolName, args),
|
|
377
405
|
workspaceMutationFromToolExecution: (input) => this.workspaceActions.workspaceMutationFromToolExecution(input),
|
|
378
406
|
recordWorkspaceMutationForUserEntry: (entryId, mutation) => this.workspaceActions.recordWorkspaceMutationForUserEntry(entryId, mutation),
|
|
@@ -522,10 +550,8 @@ export class PiUiExtendApp {
|
|
|
522
550
|
setSessionStatus: (session) => this.setSessionStatus(session),
|
|
523
551
|
showToast: (message, kind) => this.showToast(message, kind),
|
|
524
552
|
render: () => this.render(),
|
|
525
|
-
resetSessionView: () => this.resetSessionView(),
|
|
526
553
|
awaitCurrentSessionExtensions: (runtime) => this.awaitCurrentSessionExtensions(runtime),
|
|
527
|
-
|
|
528
|
-
loadSessionHistory: () => this.loadSessionHistory(),
|
|
554
|
+
afterSessionReplacement: (message) => this.afterSessionReplacement(message),
|
|
529
555
|
scrollToConversationEntry: (entryId) => this.scrollController.scrollToConversationEntry(entryId),
|
|
530
556
|
scrollToUserMessageJumpTarget: (target) => this.scrollToUserMessageJumpTarget(target),
|
|
531
557
|
}, this.popupMenus, this.commandController, this.menuItems, this.queuedMessages, this.workspaceActions);
|
|
@@ -768,10 +794,16 @@ export class PiUiExtendApp {
|
|
|
768
794
|
this.slashCommands = this.commandController.slashCommands;
|
|
769
795
|
}
|
|
770
796
|
createRuntime(options, runtimeOptions = {}) {
|
|
797
|
+
const eventBus = this.createExtensionEventBus();
|
|
771
798
|
return createPixRuntime(options, {
|
|
772
|
-
eventBus
|
|
799
|
+
eventBus,
|
|
773
800
|
config: this.pixConfig,
|
|
774
801
|
...runtimeOptions,
|
|
802
|
+
}).then((runtime) => {
|
|
803
|
+
// Record the bus this runtime's extensions use, so the renderer can
|
|
804
|
+
// emit signals (retry state, etc.) targeted at this runtime's extensions.
|
|
805
|
+
this.extensionEventBusByRuntime.set(runtime, eventBus);
|
|
806
|
+
return runtime;
|
|
775
807
|
});
|
|
776
808
|
}
|
|
777
809
|
async loadStartupConfig() {
|
|
@@ -837,6 +869,9 @@ export class PiUiExtendApp {
|
|
|
837
869
|
runtime.setRebindSession(async () => {
|
|
838
870
|
await this.bindCurrentSession({ awaitExtensions: false });
|
|
839
871
|
});
|
|
872
|
+
runtime.setBeforeSessionInvalidate(() => {
|
|
873
|
+
this.extensionUiController.clearWidgets(this.activeExtensionUiScope());
|
|
874
|
+
});
|
|
840
875
|
await this.bindCurrentSession(options);
|
|
841
876
|
}
|
|
842
877
|
createExtensionEventBus() {
|
|
@@ -49,6 +49,7 @@ export class AppCommandController {
|
|
|
49
49
|
runReloadCommand: () => this.sessionActions.runReloadCommand(),
|
|
50
50
|
runNewSessionCommand: () => this.sessionActions.runNewSessionCommand(),
|
|
51
51
|
runNewTabCommand: () => this.host.openNewTab(),
|
|
52
|
+
runDeleteCommand: (argumentsText) => this.sessionActions.runDeleteCommand(argumentsText),
|
|
52
53
|
runCompactCommand: (customInstructions) => this.sessionActions.runCompactCommand(customInstructions),
|
|
53
54
|
runForkCommand: (argumentsText) => this.navigationActions.runForkCommand(argumentsText),
|
|
54
55
|
runCloneCommand: () => this.navigationActions.runCloneCommand(),
|
|
@@ -33,6 +33,7 @@ export type CommandRegistryActions = {
|
|
|
33
33
|
runResumeCommand(): Promise<void>;
|
|
34
34
|
runNewSessionCommand(): Promise<void>;
|
|
35
35
|
runNewTabCommand(): Promise<void>;
|
|
36
|
+
runDeleteCommand(argumentsText: string): Promise<void>;
|
|
36
37
|
runCompactCommand(customInstructions?: string): Promise<void>;
|
|
37
38
|
};
|
|
38
39
|
export declare function createSlashCommands(actions: CommandRegistryActions, host: CommandControllerHost): readonly SlashCommand[];
|
|
@@ -247,6 +247,14 @@ export function createSlashCommands(actions, host) {
|
|
|
247
247
|
keywords: ["tab", "session", "fresh", "new"],
|
|
248
248
|
run: () => actions.runNewTabCommand(),
|
|
249
249
|
},
|
|
250
|
+
{
|
|
251
|
+
name: "delete",
|
|
252
|
+
description: "Delete the current (or specified) session file plus its sidecar DCP state",
|
|
253
|
+
kind: "builtin",
|
|
254
|
+
keywords: ["remove", "destroy", "purge", "session", "sidecar", "dcp"],
|
|
255
|
+
allowArguments: true,
|
|
256
|
+
run: (argumentsText) => actions.runDeleteCommand(argumentsText),
|
|
257
|
+
},
|
|
250
258
|
{
|
|
251
259
|
name: "compact",
|
|
252
260
|
description: "Manually compact the session context",
|
|
@@ -14,6 +14,8 @@ export declare class SessionCommandActions {
|
|
|
14
14
|
runUpdateCommand(argumentsText: string): Promise<void>;
|
|
15
15
|
runHotkeysCommand(): Promise<void>;
|
|
16
16
|
runReloadCommand(): Promise<void>;
|
|
17
|
+
runDeleteCommand(argumentsText: string): Promise<void>;
|
|
18
|
+
private removeDcpSidecarState;
|
|
17
19
|
runNewSessionCommand(): Promise<void>;
|
|
18
20
|
runCompactCommand(customInstructions?: string): Promise<void>;
|
|
19
21
|
private piPackageRoot;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { mkdir, readFile, rm } from "node:fs/promises";
|
|
3
|
-
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { getIdleRuntime, getRuntime, parsePathArgument } from "./command-runtime.js";
|
|
@@ -272,6 +272,79 @@ export class SessionCommandActions {
|
|
|
272
272
|
this.host.toast.error("Reload failed");
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
|
+
async runDeleteCommand(argumentsText) {
|
|
276
|
+
const runtime = getIdleRuntime(this.host, "delete");
|
|
277
|
+
if (!runtime)
|
|
278
|
+
return;
|
|
279
|
+
const sessionManager = runtime.session.sessionManager;
|
|
280
|
+
const currentSessionFile = sessionManager.getSessionFile();
|
|
281
|
+
const currentSessionId = sessionManager.getSessionId();
|
|
282
|
+
const targetArgument = argumentsText.trim();
|
|
283
|
+
const isCurrent = !targetArgument;
|
|
284
|
+
const targetSessionFile = targetArgument ? resolve(runtime.cwd, targetArgument) : currentSessionFile;
|
|
285
|
+
if (!targetSessionFile) {
|
|
286
|
+
this.host.addEntry({ id: createId("system"), kind: "system", text: "Nothing to delete: this session is in-memory and not persisted." });
|
|
287
|
+
this.host.toast.info("Nothing to delete");
|
|
288
|
+
this.host.setSessionStatus(runtime.session);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const targetSessionId = parseSessionIdFromFileName(targetSessionFile) ?? currentSessionId;
|
|
292
|
+
const confirm = await this.host.showMenu([
|
|
293
|
+
{
|
|
294
|
+
value: true,
|
|
295
|
+
label: `Yes, delete ${isCurrent ? "the current session" : basename(targetSessionFile)}`,
|
|
296
|
+
description: "This permanently removes the session file and any sidecar data. This cannot be undone.",
|
|
297
|
+
variant: "error",
|
|
298
|
+
},
|
|
299
|
+
{ value: false, label: "Cancel" },
|
|
300
|
+
], { title: "Delete session?", searchable: false, preserveStatus: true });
|
|
301
|
+
if (confirm !== true) {
|
|
302
|
+
this.host.addEntry({ id: createId("system"), kind: "system", text: "Delete cancelled." });
|
|
303
|
+
this.host.setSessionStatus(runtime.session);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
this.host.setStatus("deleting session");
|
|
307
|
+
this.host.render();
|
|
308
|
+
const sidecarRemoved = await this.removeDcpSidecarState(dirname(targetSessionFile), targetSessionId).catch(() => false);
|
|
309
|
+
await rm(targetSessionFile, { force: true }).catch(() => undefined);
|
|
310
|
+
const deleteCurrent = isCurrent || targetSessionFile === currentSessionFile;
|
|
311
|
+
if (deleteCurrent) {
|
|
312
|
+
await this.host.awaitCurrentSessionExtensions(runtime);
|
|
313
|
+
const result = await runtime.newSession();
|
|
314
|
+
if (result.cancelled) {
|
|
315
|
+
this.host.addEntry({ id: createId("system"), kind: "system", text: "Delete succeeded, but new session was cancelled." });
|
|
316
|
+
this.host.setSessionStatus(runtime.session);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
this.host.resetSessionView();
|
|
320
|
+
this.host.loadSessionHistory();
|
|
321
|
+
this.host.addEntry({
|
|
322
|
+
id: createId("system"),
|
|
323
|
+
kind: "system",
|
|
324
|
+
text: `Deleted session ${targetSessionId}. ${sidecarRemoved ? "Sidecar DCP state removed. " : ""}Started a new session. cwd=${runtime.cwd}`,
|
|
325
|
+
});
|
|
326
|
+
if (runtime.modelFallbackMessage)
|
|
327
|
+
this.host.addEntry({ id: createId("system"), kind: "system", text: runtime.modelFallbackMessage });
|
|
328
|
+
this.host.setSessionStatus(runtime.session);
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
this.host.addEntry({
|
|
332
|
+
id: createId("system"),
|
|
333
|
+
kind: "system",
|
|
334
|
+
text: `Deleted session file ${targetSessionFile}${sidecarRemoved ? " and its sidecar DCP state" : ""}.`,
|
|
335
|
+
});
|
|
336
|
+
this.host.setSessionStatus(runtime.session);
|
|
337
|
+
}
|
|
338
|
+
this.host.toast.success("Session deleted");
|
|
339
|
+
}
|
|
340
|
+
async removeDcpSidecarState(sessionDir, sessionId) {
|
|
341
|
+
if (!sessionId)
|
|
342
|
+
return false;
|
|
343
|
+
const safeName = `${sessionId.replace(/[^a-zA-Z0-9._-]/g, "_")}.json`;
|
|
344
|
+
const statePath = join(sessionDir, "dcp-state", safeName);
|
|
345
|
+
await rm(statePath, { force: true });
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
275
348
|
async runNewSessionCommand() {
|
|
276
349
|
const runtime = getIdleRuntime(this.host, "new");
|
|
277
350
|
if (!runtime)
|
|
@@ -336,3 +409,8 @@ function splitUpdateArguments(argumentsText) {
|
|
|
336
409
|
const trimmed = argumentsText.trim();
|
|
337
410
|
return trimmed ? trimmed.split(/\s+/u) : [];
|
|
338
411
|
}
|
|
412
|
+
function parseSessionIdFromFileName(sessionFile) {
|
|
413
|
+
const base = basename(sessionFile).replace(/\.jsonl$/iu, "");
|
|
414
|
+
const separator = base.indexOf("_");
|
|
415
|
+
return separator >= 0 ? base.slice(separator + 1) : undefined;
|
|
416
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentSessionRuntime, ExtensionCommandContextActions, ExtensionError } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { type PixLogDetails, type PixLogLevel } from "../logger.js";
|
|
2
3
|
import type { Entry } from "../types.js";
|
|
3
4
|
export type AppExtensionActionsHost = {
|
|
4
5
|
isRunning(): boolean;
|
|
@@ -14,9 +15,11 @@ export type AppExtensionActionsHost = {
|
|
|
14
15
|
showToast(message: string, kind: "success" | "error" | "warning" | "info"): void;
|
|
15
16
|
render(): void;
|
|
16
17
|
};
|
|
18
|
+
export type ExtensionErrorLogger = (level: PixLogLevel, event: string, details?: PixLogDetails) => void;
|
|
17
19
|
export declare class AppExtensionActionsController {
|
|
18
20
|
private readonly host;
|
|
19
|
-
|
|
21
|
+
private readonly logExtensionError;
|
|
22
|
+
constructor(host: AppExtensionActionsHost, logExtensionError?: ExtensionErrorLogger);
|
|
20
23
|
createCommandContextActions(runtime: AgentSessionRuntime): ExtensionCommandContextActions;
|
|
21
24
|
waitForSessionIdle(runtime: AgentSessionRuntime): Promise<void>;
|
|
22
25
|
handleExtensionError(error: ExtensionError): void;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import { basename } from "node:path";
|
|
1
2
|
import { createId } from "../id.js";
|
|
3
|
+
import { logPixEvent } from "../logger.js";
|
|
2
4
|
export class AppExtensionActionsController {
|
|
3
5
|
host;
|
|
4
|
-
|
|
6
|
+
logExtensionError;
|
|
7
|
+
constructor(host, logExtensionError = logPixEvent) {
|
|
5
8
|
this.host = host;
|
|
9
|
+
this.logExtensionError = logExtensionError;
|
|
6
10
|
}
|
|
7
11
|
createCommandContextActions(runtime) {
|
|
8
12
|
return {
|
|
@@ -55,10 +59,35 @@ export class AppExtensionActionsController {
|
|
|
55
59
|
}
|
|
56
60
|
}
|
|
57
61
|
handleExtensionError(error) {
|
|
62
|
+
const sourceText = formatExtensionErrorSource(error.extensionPath);
|
|
58
63
|
const pathText = error.extensionPath ? ` (${error.extensionPath})` : "";
|
|
59
|
-
this.
|
|
64
|
+
this.logExtensionError("error", "extension:error", extensionErrorLogDetails(error));
|
|
65
|
+
this.host.addEntry({
|
|
66
|
+
id: createId("error"),
|
|
67
|
+
kind: "error",
|
|
68
|
+
text: `Extension ${error.event} failed${sourceText}${pathText}: ${error.error}`,
|
|
69
|
+
});
|
|
60
70
|
this.host.showToast(`Extension ${error.event} failed`, "error");
|
|
61
71
|
if (this.host.isRunning())
|
|
62
72
|
this.host.render();
|
|
63
73
|
}
|
|
64
74
|
}
|
|
75
|
+
function formatExtensionErrorSource(extensionPath) {
|
|
76
|
+
if (!extensionPath)
|
|
77
|
+
return "";
|
|
78
|
+
const extensionName = basename(extensionPath);
|
|
79
|
+
return extensionName && extensionName !== extensionPath ? ` [${extensionName}]` : "";
|
|
80
|
+
}
|
|
81
|
+
function extensionErrorLogDetails(error) {
|
|
82
|
+
const details = {
|
|
83
|
+
event: error.event,
|
|
84
|
+
error: error.error,
|
|
85
|
+
};
|
|
86
|
+
if (error.extensionPath) {
|
|
87
|
+
details.extensionPath = error.extensionPath;
|
|
88
|
+
details.extensionName = basename(error.extensionPath);
|
|
89
|
+
}
|
|
90
|
+
if (error.stack)
|
|
91
|
+
details.stack = error.stack;
|
|
92
|
+
return details;
|
|
93
|
+
}
|
|
@@ -47,6 +47,7 @@ export declare class AppInputController {
|
|
|
47
47
|
private consumeTerminalEditShortcutSequence;
|
|
48
48
|
private consumeIgnoredModifiedKeySequence;
|
|
49
49
|
private consumeModifiedArrowKeySequence;
|
|
50
|
+
private consumeEscapeKeySequence;
|
|
50
51
|
private consumeClipboardImagePasteSequence;
|
|
51
52
|
private consumeShiftEnterSequence;
|
|
52
53
|
private consumeTerminalInterruptSequence;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InputPasteHandler } from "./input-paste-handler.js";
|
|
2
2
|
import { hasTerminalCommandModifier, isNativeCommandPressed, isNativeShiftPressed } from "./native-modifiers.js";
|
|
3
|
-
import { parseTerminalEditShortcutSequence, parseTerminalInterruptSequence, parseTerminalModifiedKeySequence, terminalKeyArrowDirection, terminalEditShortcutForControlChar, terminalKeyIsClipboardImagePaste, terminalKeyIsShiftEnter, terminalKeyShouldIgnore, } from "./terminal-edit-shortcuts.js";
|
|
3
|
+
import { parseTerminalEditShortcutSequence, parseTerminalInterruptSequence, parseTerminalModifiedKeySequence, terminalKeyArrowDirection, terminalEditShortcutForControlChar, terminalKeyIsClipboardImagePaste, terminalKeyIsEscape, terminalKeyIsShiftEnter, terminalKeyShouldIgnore, } from "./terminal-edit-shortcuts.js";
|
|
4
4
|
const SHIFT_ENTER_ESCAPE_SEQUENCES = ["\x1b\r", "\x1b\n"];
|
|
5
5
|
export class AppInputController {
|
|
6
6
|
host;
|
|
@@ -117,6 +117,11 @@ export class AppInputController {
|
|
|
117
117
|
continue;
|
|
118
118
|
if (modifiedArrowKeySequence === "pending")
|
|
119
119
|
return;
|
|
120
|
+
const escapeKeySequence = this.consumeEscapeKeySequence();
|
|
121
|
+
if (escapeKeySequence === "consumed")
|
|
122
|
+
continue;
|
|
123
|
+
if (escapeKeySequence === "pending")
|
|
124
|
+
return;
|
|
120
125
|
const ignoredModifiedKeySequence = this.consumeIgnoredModifiedKeySequence();
|
|
121
126
|
if (ignoredModifiedKeySequence === "consumed")
|
|
122
127
|
continue;
|
|
@@ -230,7 +235,10 @@ export class AppInputController {
|
|
|
230
235
|
}
|
|
231
236
|
if (/^\x1b\[(?:8|127);\d*$/.test(this.inputBuffer))
|
|
232
237
|
return "pending";
|
|
233
|
-
|
|
238
|
+
// modifyOtherKeys backspace looks like \x1b[27;<mod>;(8|127)~ (two semicolons, no colons).
|
|
239
|
+
// Kitty key sequences also begin with \x1b[27; (e.g. the ESC release \x1b[27;1:3u), so only
|
|
240
|
+
// treat the buffer as a pending modifyOtherKeys partial when it matches that exact shape.
|
|
241
|
+
if (/^\x1b\[27;\d+;\d*$/.test(this.inputBuffer))
|
|
234
242
|
return "pending";
|
|
235
243
|
return "none";
|
|
236
244
|
}
|
|
@@ -304,6 +312,19 @@ export class AppInputController {
|
|
|
304
312
|
this.handleArrowLeft();
|
|
305
313
|
return "consumed";
|
|
306
314
|
}
|
|
315
|
+
consumeEscapeKeySequence() {
|
|
316
|
+
const result = parseTerminalModifiedKeySequence(this.inputBuffer);
|
|
317
|
+
if (result.kind === "pending")
|
|
318
|
+
return "pending";
|
|
319
|
+
if (result.kind === "none")
|
|
320
|
+
return "none";
|
|
321
|
+
if (!terminalKeyIsEscape(result.key))
|
|
322
|
+
return "none";
|
|
323
|
+
this.inputBuffer = this.inputBuffer.slice(result.key.length);
|
|
324
|
+
if (!terminalKeyShouldIgnore(result.key))
|
|
325
|
+
void this.host.handleEscape();
|
|
326
|
+
return "consumed";
|
|
327
|
+
}
|
|
307
328
|
consumeClipboardImagePasteSequence() {
|
|
308
329
|
const result = parseTerminalModifiedKeySequence(this.inputBuffer);
|
|
309
330
|
if (result.kind === "pending")
|
|
@@ -39,6 +39,7 @@ export declare function parseTerminalInterruptSequence(input: string): {
|
|
|
39
39
|
export declare function terminalKeyIsShiftEnter(key: ParsedModifiedKey): boolean;
|
|
40
40
|
export declare function terminalKeyIsClipboardImagePaste(key: ParsedModifiedKey): boolean;
|
|
41
41
|
export declare function terminalKeyShouldIgnore(key: ParsedModifiedKey): boolean;
|
|
42
|
+
export declare function terminalKeyIsEscape(key: ParsedModifiedKey): boolean;
|
|
42
43
|
export declare function terminalKeyArrowDirection(key: ParsedModifiedKey): "up" | "down" | "right" | "left" | undefined;
|
|
43
44
|
export declare function terminalEditShortcutForControlChar(char: string, shiftPressed: boolean): TerminalEditShortcut | undefined;
|
|
44
45
|
export {};
|
|
@@ -4,6 +4,7 @@ const COMMAND_MODIFIER_FLAG = 8;
|
|
|
4
4
|
const LOCK_MODIFIER_MASK = 64 + 128;
|
|
5
5
|
const KEY_CODE_C = 99;
|
|
6
6
|
const KEY_CODE_ENTER = 13;
|
|
7
|
+
const KEY_CODE_ESCAPE = 27;
|
|
7
8
|
const KEY_CODE_V = 118;
|
|
8
9
|
const KEY_CODE_Y = 121;
|
|
9
10
|
const KEY_CODE_Z = 122;
|
|
@@ -67,6 +68,12 @@ export function terminalKeyIsClipboardImagePaste(key) {
|
|
|
67
68
|
export function terminalKeyShouldIgnore(key) {
|
|
68
69
|
return key.eventType === 3;
|
|
69
70
|
}
|
|
71
|
+
export function terminalKeyIsEscape(key) {
|
|
72
|
+
const effectiveModifier = key.modifier & ~LOCK_MODIFIER_MASK;
|
|
73
|
+
if (effectiveModifier !== 0)
|
|
74
|
+
return false;
|
|
75
|
+
return key.codepoint === KEY_CODE_ESCAPE;
|
|
76
|
+
}
|
|
70
77
|
export function terminalKeyArrowDirection(key) {
|
|
71
78
|
const effectiveModifier = key.modifier & ~LOCK_MODIFIER_MASK;
|
|
72
79
|
if (effectiveModifier !== 0)
|
|
@@ -66,7 +66,7 @@ export class AppVoiceController {
|
|
|
66
66
|
case "installing":
|
|
67
67
|
return `${APP_ICONS.microphone}${languageLabel} ${APP_ICONS.timerSand}`;
|
|
68
68
|
case "downloading":
|
|
69
|
-
return `${APP_ICONS.
|
|
69
|
+
return `${APP_ICONS.microphone}${languageLabel} ${APP_ICONS.timerSand}`;
|
|
70
70
|
case "loading":
|
|
71
71
|
return `${APP_ICONS.microphone}${languageLabel} ${APP_ICONS.timerSand}`;
|
|
72
72
|
case "listening":
|
|
@@ -16,9 +16,7 @@ export type AppPopupActionControllerHost = {
|
|
|
16
16
|
setSessionStatus(session: AgentSession | undefined): void;
|
|
17
17
|
showToast(message: string, kind: "success" | "error" | "warning" | "info"): void;
|
|
18
18
|
render(): void;
|
|
19
|
-
|
|
20
|
-
bindCurrentSession(): Promise<void>;
|
|
21
|
-
loadSessionHistory(): void;
|
|
19
|
+
afterSessionReplacement(message?: string): void;
|
|
22
20
|
scrollToConversationEntry(entryId: string): boolean;
|
|
23
21
|
scrollToUserMessageJumpTarget(target: UserMessageJumpMenuValue): Promise<boolean>;
|
|
24
22
|
};
|
|
@@ -236,12 +236,8 @@ export class AppPopupActionController {
|
|
|
236
236
|
this.host.render();
|
|
237
237
|
return true;
|
|
238
238
|
}
|
|
239
|
-
this.host.resetSessionView();
|
|
240
|
-
await this.host.bindCurrentSession();
|
|
241
|
-
this.host.loadSessionHistory();
|
|
242
239
|
const name = runtime.session.sessionName ?? session.id.slice(0, 8);
|
|
243
|
-
this.host.
|
|
244
|
-
this.host.setSessionStatus(runtime.session);
|
|
240
|
+
this.host.afterSessionReplacement(`Resumed session "${name}"`);
|
|
245
241
|
}
|
|
246
242
|
catch (error) {
|
|
247
243
|
this.host.addEntry({ id: createId("error"), kind: "error", text: `Resume failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stripDcpControlMetadata } from "../../markdown-format.js";
|
|
1
2
|
import { isRecord } from "../guards.js";
|
|
2
3
|
const MAX_FORMAT_STRING_CHARS = 256 * 1024;
|
|
3
4
|
const MAX_RENDERED_CONTENT_CHARS = 512 * 1024;
|
|
@@ -79,11 +80,11 @@ export function renderContent(content) {
|
|
|
79
80
|
if (!pushPart(stringifyUnknown(item)))
|
|
80
81
|
break;
|
|
81
82
|
}
|
|
82
|
-
return parts.join("\n");
|
|
83
|
+
return stripDcpControlMetadata(parts.join("\n"));
|
|
83
84
|
}
|
|
84
85
|
export function renderUserMessageContent(content) {
|
|
85
86
|
if (typeof content === "string")
|
|
86
|
-
return content;
|
|
87
|
+
return stripDcpControlMetadata(content);
|
|
87
88
|
if (!Array.isArray(content))
|
|
88
89
|
return stringifyUnknown(content);
|
|
89
90
|
const textParts = [];
|
|
@@ -103,7 +104,7 @@ export function renderUserMessageContent(content) {
|
|
|
103
104
|
}
|
|
104
105
|
textParts.push(stringifyUnknown(item));
|
|
105
106
|
}
|
|
106
|
-
const text = textParts.join("\n").replace(/\[Image \d+(?:: [^\]]+)?\]/g, "").trimEnd();
|
|
107
|
+
const text = stripDcpControlMetadata(textParts.join("\n")).replace(/\[Image \d+(?:: [^\]]+)?\]/g, "").trimEnd();
|
|
107
108
|
if (imageCount === 0)
|
|
108
109
|
return text;
|
|
109
110
|
const imageText = userImageLabels(imageCount);
|