pi-ui-extend 0.1.8 → 0.1.11
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 +57 -2
- package/bin/pix.mjs +4 -4
- package/dist/app/app.d.ts +4 -0
- package/dist/app/app.js +112 -45
- package/dist/app/{cli.d.ts → cli/cli.d.ts} +1 -1
- package/dist/app/{cli.js → cli/cli.js} +1 -1
- package/dist/app/{install.d.ts → cli/install.d.ts} +2 -0
- package/dist/app/{install.js → cli/install.js} +18 -3
- package/dist/app/{command-controller.d.ts → commands/command-controller.d.ts} +1 -1
- package/dist/app/{command-controller.js → commands/command-controller.js} +4 -0
- package/dist/app/{command-host.d.ts → commands/command-host.d.ts} +7 -3
- package/dist/app/{command-model-actions.d.ts → commands/command-model-actions.d.ts} +6 -1
- package/dist/app/{command-model-actions.js → commands/command-model-actions.js} +106 -2
- package/dist/app/{command-navigation-actions.d.ts → commands/command-navigation-actions.d.ts} +7 -2
- package/dist/app/{command-navigation-actions.js → commands/command-navigation-actions.js} +42 -19
- package/dist/app/{command-registry.d.ts → commands/command-registry.d.ts} +5 -1
- package/dist/app/{command-registry.js → commands/command-registry.js} +32 -0
- package/dist/app/{command-runtime.js → commands/command-runtime.js} +1 -1
- package/dist/app/{command-session-actions.d.ts → commands/command-session-actions.d.ts} +1 -0
- package/dist/app/{command-session-actions.js → commands/command-session-actions.js} +18 -8
- package/dist/app/{shell-controller.d.ts → commands/shell-controller.d.ts} +2 -1
- package/dist/app/{shell-controller.js → commands/shell-controller.js} +2 -2
- package/dist/app/{slash-commands.d.ts → commands/slash-commands.d.ts} +2 -2
- package/dist/app/{slash-commands.js → commands/slash-commands.js} +1 -1
- package/dist/app/constants.d.ts +1 -1
- package/dist/app/constants.js +1 -1
- package/dist/app/{extension-actions-controller.d.ts → extensions/extension-actions-controller.d.ts} +1 -1
- package/dist/app/{extension-actions-controller.js → extensions/extension-actions-controller.js} +1 -1
- package/dist/app/{extension-ui-controller.d.ts → extensions/extension-ui-controller.d.ts} +3 -3
- package/dist/app/{extension-ui-controller.js → extensions/extension-ui-controller.js} +3 -3
- package/dist/app/icons.js +1 -1
- package/dist/app/input/autocomplete-controller.d.ts +52 -0
- package/dist/app/input/autocomplete-controller.js +352 -0
- package/dist/app/{input-action-controller.d.ts → input/input-action-controller.d.ts} +8 -7
- package/dist/app/{input-action-controller.js → input/input-action-controller.js} +24 -3
- package/dist/app/{input-controller.d.ts → input/input-controller.d.ts} +4 -3
- package/dist/app/{input-controller.js → input/input-controller.js} +3 -1
- package/dist/app/{input-paste-handler.d.ts → input/input-paste-handler.d.ts} +2 -1
- package/dist/app/{input-paste-handler.js → input/input-paste-handler.js} +25 -21
- package/dist/app/{native-modifiers.js → input/native-modifiers.js} +2 -2
- package/dist/app/{prompt-enhancer-controller.d.ts → input/prompt-enhancer-controller.d.ts} +5 -5
- package/dist/app/{prompt-enhancer-controller.js → input/prompt-enhancer-controller.js} +3 -3
- package/dist/app/{voice-controller.d.ts → input/voice-controller.d.ts} +3 -1
- package/dist/app/{voice-controller.js → input/voice-controller.js} +29 -17
- package/dist/app/{model-ref.d.ts → model/model-ref.d.ts} +1 -1
- package/dist/app/{model-ref.js → model/model-ref.js} +1 -1
- package/dist/app/{model-usage-controller.js → model/model-usage-controller.js} +1 -1
- package/dist/app/{model-usage-status.d.ts → model/model-usage-status.d.ts} +10 -1
- package/dist/app/{model-usage-status.js → model/model-usage-status.js} +125 -35
- package/dist/app/{menu-items-controller.d.ts → popup/menu-items-controller.d.ts} +4 -4
- package/dist/app/{menu-items-controller.js → popup/menu-items-controller.js} +5 -5
- package/dist/app/{popup-action-controller.d.ts → popup/popup-action-controller.d.ts} +4 -4
- package/dist/app/{popup-action-controller.js → popup/popup-action-controller.js} +3 -3
- package/dist/app/{popup-menu-controller.d.ts → popup/popup-menu-controller.d.ts} +4 -4
- package/dist/app/{popup-menu-controller.js → popup/popup-menu-controller.js} +7 -7
- package/dist/app/process.d.ts +17 -0
- package/dist/app/process.js +68 -0
- package/dist/app/{conversation-entry-renderer.d.ts → rendering/conversation-entry-renderer.d.ts} +3 -3
- package/dist/app/{conversation-entry-renderer.js → rendering/conversation-entry-renderer.js} +20 -9
- package/dist/app/{conversation-shell-renderer.d.ts → rendering/conversation-shell-renderer.d.ts} +1 -1
- package/dist/app/{conversation-shell-renderer.js → rendering/conversation-shell-renderer.js} +1 -1
- package/dist/app/{conversation-tool-renderer.d.ts → rendering/conversation-tool-renderer.d.ts} +3 -3
- package/dist/app/{conversation-tool-renderer.js → rendering/conversation-tool-renderer.js} +10 -9
- package/dist/app/{conversation-viewport.d.ts → rendering/conversation-viewport.d.ts} +3 -3
- package/dist/app/{dcp-stats.js → rendering/dcp-stats.js} +1 -1
- package/dist/app/{editor-layout-renderer.d.ts → rendering/editor-layout-renderer.d.ts} +4 -3
- package/dist/app/{editor-layout-renderer.js → rendering/editor-layout-renderer.js} +13 -3
- package/dist/app/{editor-panels.d.ts → rendering/editor-panels.d.ts} +2 -2
- package/dist/app/{editor-panels.js → rendering/editor-panels.js} +4 -4
- package/dist/app/{message-content.d.ts → rendering/message-content.d.ts} +1 -1
- package/dist/app/{message-content.js → rendering/message-content.js} +66 -8
- package/dist/app/{render-controller.d.ts → rendering/render-controller.d.ts} +6 -6
- package/dist/app/{render-controller.js → rendering/render-controller.js} +11 -6
- package/dist/app/{render-text.d.ts → rendering/render-text.d.ts} +5 -2
- package/dist/app/{render-text.js → rendering/render-text.js} +53 -5
- package/dist/app/{status-line-renderer.d.ts → rendering/status-line-renderer.d.ts} +8 -4
- package/dist/app/{status-line-renderer.js → rendering/status-line-renderer.js} +73 -29
- package/dist/app/{tab-line-renderer.d.ts → rendering/tab-line-renderer.d.ts} +3 -3
- package/dist/app/{tab-line-renderer.js → rendering/tab-line-renderer.js} +2 -2
- package/dist/app/{toast-controller.d.ts → rendering/toast-controller.d.ts} +1 -1
- package/dist/app/{toast-controller.js → rendering/toast-controller.js} +2 -2
- package/dist/app/{toast-renderer.d.ts → rendering/toast-renderer.d.ts} +3 -3
- package/dist/app/{toast-renderer.js → rendering/toast-renderer.js} +3 -3
- package/dist/app/{tool-block-renderer.d.ts → rendering/tool-block-renderer.d.ts} +5 -5
- package/dist/app/{tool-block-renderer.js → rendering/tool-block-renderer.js} +15 -33
- package/dist/app/runtime.d.ts +6 -1
- package/dist/app/runtime.js +35 -2
- package/dist/app/{blink-controller.js → screen/blink-controller.js} +1 -1
- package/dist/app/{clipboard.d.ts → screen/clipboard.d.ts} +2 -2
- package/dist/app/{clipboard.js → screen/clipboard.js} +13 -18
- package/dist/app/{image-click-targets.d.ts → screen/image-click-targets.d.ts} +2 -2
- package/dist/app/{image-opener.d.ts → screen/image-opener.d.ts} +1 -1
- package/dist/app/{mouse-controller.d.ts → screen/mouse-controller.d.ts} +17 -10
- package/dist/app/{mouse-controller.js → screen/mouse-controller.js} +72 -29
- package/dist/app/{screen-selection.d.ts → screen/screen-selection.d.ts} +1 -1
- package/dist/app/{screen-styler.d.ts → screen/screen-styler.d.ts} +6 -3
- package/dist/app/{screen-styler.js → screen/screen-styler.js} +7 -6
- package/dist/app/{scroll-controller.d.ts → screen/scroll-controller.d.ts} +3 -3
- package/dist/app/{scroll-controller.js → screen/scroll-controller.js} +1 -1
- package/dist/app/{status-controller.d.ts → screen/status-controller.d.ts} +5 -2
- package/dist/app/{status-controller.js → screen/status-controller.js} +24 -9
- package/dist/app/{queued-message-controller.d.ts → session/queued-message-controller.d.ts} +9 -3
- package/dist/app/{queued-message-controller.js → session/queued-message-controller.js} +34 -23
- package/dist/app/{request-history.js → session/request-history.js} +2 -2
- package/dist/app/session/resume-session-loader.d.ts +15 -0
- package/dist/app/session/resume-session-loader.js +204 -0
- package/dist/app/{session-event-controller.d.ts → session/session-event-controller.d.ts} +8 -4
- package/dist/app/{session-event-controller.js → session/session-event-controller.js} +75 -8
- package/dist/app/{session-history.d.ts → session/session-history.d.ts} +1 -1
- package/dist/app/{session-history.js → session/session-history.js} +7 -6
- package/dist/app/{session-lifecycle-controller.d.ts → session/session-lifecycle-controller.d.ts} +7 -2
- package/dist/app/{session-lifecycle-controller.js → session/session-lifecycle-controller.js} +13 -5
- package/dist/app/{session-search.d.ts → session/session-search.d.ts} +1 -1
- package/dist/app/{session-search.js → session/session-search.js} +3 -3
- package/dist/app/{tabs-controller.d.ts → session/tabs-controller.d.ts} +11 -2
- package/dist/app/{tabs-controller.js → session/tabs-controller.js} +105 -9
- package/dist/app/{subagents-files.d.ts → subagents/subagents-files.d.ts} +1 -1
- package/dist/app/{subagents-files.js → subagents/subagents-files.js} +1 -1
- package/dist/app/{subagents-model.d.ts → subagents/subagents-model.d.ts} +1 -1
- package/dist/app/{subagents-model.js → subagents/subagents-model.js} +4 -4
- package/dist/app/{subagents-widget-controller.d.ts → subagents/subagents-widget-controller.d.ts} +1 -1
- package/dist/app/{subagents-widget-controller.js → subagents/subagents-widget-controller.js} +2 -2
- package/dist/app/{nerd-font-controller.js → terminal/nerd-font-controller.js} +16 -17
- package/dist/app/{terminal-bell-sound-controller.js → terminal/terminal-bell-sound-controller.js} +1 -1
- package/dist/app/{terminal-controller.d.ts → terminal/terminal-controller.d.ts} +1 -0
- package/dist/app/{terminal-controller.js → terminal/terminal-controller.js} +3 -2
- package/dist/app/{todo-model.d.ts → todo/todo-model.d.ts} +1 -1
- package/dist/app/{todo-model.js → todo/todo-model.js} +3 -3
- package/dist/app/{todo-widget-controller.d.ts → todo/todo-widget-controller.d.ts} +1 -1
- package/dist/app/{todo-widget-controller.js → todo/todo-widget-controller.js} +2 -2
- package/dist/app/types.d.ts +16 -2
- package/dist/app/{workspace-actions-controller.d.ts → workspace/workspace-actions-controller.d.ts} +2 -2
- package/dist/app/{workspace-actions-controller.js → workspace/workspace-actions-controller.js} +6 -6
- package/dist/app/{workspace-undo.d.ts → workspace/workspace-undo.d.ts} +1 -1
- package/dist/app/{workspace-undo.js → workspace/workspace-undo.js} +22 -20
- package/dist/config.d.ts +27 -0
- package/dist/config.js +174 -1
- package/dist/default-pix-config.js +38 -353
- package/dist/input-editor.d.ts +7 -1
- package/dist/input-editor.js +47 -6
- package/dist/main.js +2 -2
- package/dist/markdown-format.d.ts +1 -0
- package/dist/markdown-format.js +26 -1
- package/external/pi-tools-suite/README.md +78 -0
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +4 -0
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +6 -1
- package/external/pi-tools-suite/src/dcp/compression-blocks.ts +1 -0
- package/external/pi-tools-suite/src/dcp/prompts.ts +5 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +314 -193
- package/external/pi-tools-suite/src/index.ts +1 -0
- package/external/pi-tools-suite/src/lib/lsp.ts +2 -1
- package/external/pi-tools-suite/src/lsp/_shared/output.ts +8 -7
- package/external/pi-tools-suite/src/lsp/manager.ts +4 -4
- package/external/pi-tools-suite/src/opencode-import/commands.ts +86 -0
- package/external/pi-tools-suite/src/opencode-import/importer.ts +208 -0
- package/external/pi-tools-suite/src/opencode-import/index.ts +25 -0
- package/external/pi-tools-suite/src/repo-discovery/index.ts +49 -2
- package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +9 -1
- package/external/pi-tools-suite/src/tool-descriptions.ts +1 -1
- package/package.json +1 -1
- /package/dist/app/{startup-checks.d.ts → cli/startup-checks.d.ts} +0 -0
- /package/dist/app/{startup-checks.js → cli/startup-checks.js} +0 -0
- /package/dist/app/{startup-info.d.ts → cli/startup-info.d.ts} +0 -0
- /package/dist/app/{startup-info.js → cli/startup-info.js} +0 -0
- /package/dist/app/{update.d.ts → cli/update.d.ts} +0 -0
- /package/dist/app/{update.js → cli/update.js} +0 -0
- /package/dist/app/{command-host.js → commands/command-host.js} +0 -0
- /package/dist/app/{command-runtime.d.ts → commands/command-runtime.d.ts} +0 -0
- /package/dist/app/{shell-command.d.ts → commands/shell-command.d.ts} +0 -0
- /package/dist/app/{shell-command.js → commands/shell-command.js} +0 -0
- /package/dist/app/{extension-event-bus.d.ts → extensions/extension-event-bus.d.ts} +0 -0
- /package/dist/app/{extension-event-bus.js → extensions/extension-event-bus.js} +0 -0
- /package/dist/app/{native-modifiers.d.ts → input/native-modifiers.d.ts} +0 -0
- /package/dist/app/{terminal-edit-shortcuts.d.ts → input/terminal-edit-shortcuts.d.ts} +0 -0
- /package/dist/app/{terminal-edit-shortcuts.js → input/terminal-edit-shortcuts.js} +0 -0
- /package/dist/app/{model-usage-controller.d.ts → model/model-usage-controller.d.ts} +0 -0
- /package/dist/app/{conversation-viewport.js → rendering/conversation-viewport.js} +0 -0
- /package/dist/app/{dcp-stats.d.ts → rendering/dcp-stats.d.ts} +0 -0
- /package/dist/app/{blink-controller.d.ts → screen/blink-controller.d.ts} +0 -0
- /package/dist/app/{file-link-opener.d.ts → screen/file-link-opener.d.ts} +0 -0
- /package/dist/app/{file-link-opener.js → screen/file-link-opener.js} +0 -0
- /package/dist/app/{file-links.d.ts → screen/file-links.d.ts} +0 -0
- /package/dist/app/{file-links.js → screen/file-links.js} +0 -0
- /package/dist/app/{image-click-targets.js → screen/image-click-targets.js} +0 -0
- /package/dist/app/{image-opener.js → screen/image-opener.js} +0 -0
- /package/dist/app/{screen-selection.js → screen/screen-selection.js} +0 -0
- /package/dist/app/{request-history.d.ts → session/request-history.d.ts} +0 -0
- /package/dist/app/{nerd-font-controller.d.ts → terminal/nerd-font-controller.d.ts} +0 -0
- /package/dist/app/{terminal-bell-sound-controller.d.ts → terminal/terminal-bell-sound-controller.d.ts} +0 -0
- /package/dist/app/{terminal-output-buffer.d.ts → terminal/terminal-output-buffer.d.ts} +0 -0
- /package/dist/app/{terminal-output-buffer.js → terminal/terminal-output-buffer.js} +0 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { streamSimple } from "@earendil-works/pi-ai";
|
|
2
|
+
import { isRecord } from "../guards.js";
|
|
3
|
+
import { parseModelRef } from "../model/model-ref.js";
|
|
4
|
+
const AUTOCOMPLETE_DEBOUNCE_MS = 350;
|
|
5
|
+
const AUTOCOMPLETE_MIN_TEXT_LENGTH = 3;
|
|
6
|
+
const AUTOCOMPLETE_TIMEOUT_MS = 3_000;
|
|
7
|
+
const AUTOCOMPLETE_MAX_TOKENS = 48;
|
|
8
|
+
const AUTOCOMPLETE_MAX_PROMPT_TOKENS = 1_200;
|
|
9
|
+
const AUTOCOMPLETE_INCLUDE_RECENT_MESSAGES = 0;
|
|
10
|
+
const AUTOCOMPLETE_MAX_SUFFIX_LENGTH = 320;
|
|
11
|
+
const AUTOCOMPLETE_HISTORY_MESSAGE_MAX_CHARS = 700;
|
|
12
|
+
const AUTOCOMPLETE_HISTORY_CONTEXT_MAX_CHARS = 3_600;
|
|
13
|
+
const AUTOCOMPLETE_TOKEN_CHARS = 4;
|
|
14
|
+
const AUTOCOMPLETE_SYSTEM_PROMPT = `You are an inline autocomplete engine for pix, a terminal UI for a coding agent.
|
|
15
|
+
Use provided recent active-session messages only as optional context; the current draft is the source of truth.
|
|
16
|
+
Continue only the user's current draft at the cursor.
|
|
17
|
+
Output only the exact suffix to append after the draft.
|
|
18
|
+
Do not repeat the draft. Do not answer the user. Do not explain.
|
|
19
|
+
If the draft already looks complete or the continuation is uncertain, output an empty string.
|
|
20
|
+
Keep the suffix short, in the user's language/style, and stop at a natural boundary.`;
|
|
21
|
+
export class AppAutocompleteController {
|
|
22
|
+
host;
|
|
23
|
+
timer;
|
|
24
|
+
lastObservedKey = "";
|
|
25
|
+
requestSeq = 0;
|
|
26
|
+
suggestion;
|
|
27
|
+
activeAbortController;
|
|
28
|
+
completeInputWithPi;
|
|
29
|
+
debounceOverrideMs;
|
|
30
|
+
constructor(host, options = {}) {
|
|
31
|
+
this.host = host;
|
|
32
|
+
this.completeInputWithPi = options.completeInputWithPi ?? completeInputWithPi;
|
|
33
|
+
this.debounceOverrideMs = options.debounceMs;
|
|
34
|
+
}
|
|
35
|
+
observeInput() {
|
|
36
|
+
const target = this.currentTarget();
|
|
37
|
+
const key = target ? this.targetKey(target) : "";
|
|
38
|
+
if (key === this.lastObservedKey)
|
|
39
|
+
return;
|
|
40
|
+
this.lastObservedKey = key;
|
|
41
|
+
this.suggestion = undefined;
|
|
42
|
+
this.clearTimer();
|
|
43
|
+
this.cancelInFlight();
|
|
44
|
+
if (!target)
|
|
45
|
+
return;
|
|
46
|
+
const requestSeq = ++this.requestSeq;
|
|
47
|
+
this.timer = setTimeout(() => {
|
|
48
|
+
void this.runAutocomplete(target, requestSeq);
|
|
49
|
+
}, this.currentDebounceMs());
|
|
50
|
+
this.timer.unref?.();
|
|
51
|
+
}
|
|
52
|
+
suggestionText() {
|
|
53
|
+
const target = this.currentTarget();
|
|
54
|
+
if (!target || !this.suggestion)
|
|
55
|
+
return undefined;
|
|
56
|
+
return this.sameTarget(target, this.suggestion.target) ? this.suggestion.text : undefined;
|
|
57
|
+
}
|
|
58
|
+
acceptSuggestion() {
|
|
59
|
+
const suggestion = this.suggestionText();
|
|
60
|
+
if (!suggestion)
|
|
61
|
+
return false;
|
|
62
|
+
this.host.inputEditor().insert(suggestion);
|
|
63
|
+
this.suggestion = undefined;
|
|
64
|
+
this.lastObservedKey = "";
|
|
65
|
+
this.clearTimer();
|
|
66
|
+
this.host.render();
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
dispose() {
|
|
70
|
+
this.clearTimer();
|
|
71
|
+
this.cancelInFlight();
|
|
72
|
+
this.requestSeq += 1;
|
|
73
|
+
this.suggestion = undefined;
|
|
74
|
+
}
|
|
75
|
+
async runAutocomplete(target, requestSeq) {
|
|
76
|
+
const runtime = this.host.runtime();
|
|
77
|
+
const config = { ...this.host.autocompleteConfig() };
|
|
78
|
+
if (!runtime || !config.modelRef.trim())
|
|
79
|
+
return;
|
|
80
|
+
const abortController = new AbortController();
|
|
81
|
+
this.activeAbortController = abortController;
|
|
82
|
+
try {
|
|
83
|
+
const completion = await this.completeInputWithPi(runtime, target.text, config, abortController.signal);
|
|
84
|
+
if (requestSeq !== this.requestSeq)
|
|
85
|
+
return;
|
|
86
|
+
const current = this.currentTarget();
|
|
87
|
+
if (!current || !this.sameTarget(current, target))
|
|
88
|
+
return;
|
|
89
|
+
const suggestion = cleanupCompletion(completion, target.text, config);
|
|
90
|
+
if (!suggestion)
|
|
91
|
+
return;
|
|
92
|
+
this.suggestion = { target, text: suggestion };
|
|
93
|
+
if (this.host.isRunning())
|
|
94
|
+
this.host.render();
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Inline autocomplete is best-effort; avoid surfacing transient model/auth errors while typing.
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
if (this.activeAbortController === abortController)
|
|
101
|
+
this.activeAbortController = undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
currentTarget() {
|
|
105
|
+
const config = this.host.autocompleteConfig();
|
|
106
|
+
if (!config.modelRef.trim())
|
|
107
|
+
return undefined;
|
|
108
|
+
const editor = this.host.inputEditor();
|
|
109
|
+
const text = editor.text;
|
|
110
|
+
const cursor = editor.cursor;
|
|
111
|
+
if (editor.hasSelection || editor.hasAttachments)
|
|
112
|
+
return undefined;
|
|
113
|
+
if (cursor !== text.length)
|
|
114
|
+
return undefined;
|
|
115
|
+
if (text.trim().length < AUTOCOMPLETE_MIN_TEXT_LENGTH)
|
|
116
|
+
return undefined;
|
|
117
|
+
if (text.startsWith("/") || text.startsWith("!"))
|
|
118
|
+
return undefined;
|
|
119
|
+
return { text, cursor };
|
|
120
|
+
}
|
|
121
|
+
targetKey(target) {
|
|
122
|
+
return `${target.cursor}\u0000${target.text}`;
|
|
123
|
+
}
|
|
124
|
+
sameTarget(a, b) {
|
|
125
|
+
return a.cursor === b.cursor && a.text === b.text;
|
|
126
|
+
}
|
|
127
|
+
currentDebounceMs() {
|
|
128
|
+
return numberInRange(this.debounceOverrideMs ?? this.host.autocompleteConfig().debounceMs, AUTOCOMPLETE_DEBOUNCE_MS, 0, 5_000);
|
|
129
|
+
}
|
|
130
|
+
clearTimer() {
|
|
131
|
+
if (!this.timer)
|
|
132
|
+
return;
|
|
133
|
+
clearTimeout(this.timer);
|
|
134
|
+
this.timer = undefined;
|
|
135
|
+
}
|
|
136
|
+
cancelInFlight() {
|
|
137
|
+
this.activeAbortController?.abort();
|
|
138
|
+
this.activeAbortController = undefined;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export async function completeInputWithPi(runtime, draft, config, signal) {
|
|
142
|
+
const parsedModel = parseModelRef(config.modelRef);
|
|
143
|
+
const registry = runtime.services.modelRegistry;
|
|
144
|
+
let model = registry.find(parsedModel.provider, parsedModel.modelId);
|
|
145
|
+
if (!model) {
|
|
146
|
+
registry.refresh();
|
|
147
|
+
model = registry.find(parsedModel.provider, parsedModel.modelId);
|
|
148
|
+
}
|
|
149
|
+
if (!model)
|
|
150
|
+
throw new Error(`Model not found: ${parsedModel.provider}/${parsedModel.modelId}`);
|
|
151
|
+
const auth = await registry.getApiKeyAndHeaders(model);
|
|
152
|
+
if (!auth.ok)
|
|
153
|
+
throw new Error(auth.error);
|
|
154
|
+
const timeoutMs = numberInRange(config.timeoutMs, AUTOCOMPLETE_TIMEOUT_MS, 250, 10_000);
|
|
155
|
+
const maxTokens = numberInRange(config.maxTokens, AUTOCOMPLETE_MAX_TOKENS, 8, 256);
|
|
156
|
+
const maxPromptTokens = numberInRange(config.maxPromptTokens, AUTOCOMPLETE_MAX_PROMPT_TOKENS, 256, 16_000);
|
|
157
|
+
const requestSignal = createTimeoutSignal(signal, timeoutMs);
|
|
158
|
+
const requestMaxTokens = model.maxTokens > 0 ? Math.min(model.maxTokens, maxTokens) : maxTokens;
|
|
159
|
+
const requestModel = { ...model, maxTokens: requestMaxTokens };
|
|
160
|
+
const includeRecentMessages = numberInRange(config.includeRecentMessages, AUTOCOMPLETE_INCLUDE_RECENT_MESSAGES, 0, 20);
|
|
161
|
+
const history = includeRecentMessages > 0 ? autocompleteHistoryFromMessages(runtime.session.messages, includeRecentMessages) : [];
|
|
162
|
+
const prompt = buildAutocompletePrompt({ cwd: runtime.cwd, draft, history, maxPromptTokens });
|
|
163
|
+
if (!prompt)
|
|
164
|
+
return "";
|
|
165
|
+
let output = "";
|
|
166
|
+
let streamError;
|
|
167
|
+
try {
|
|
168
|
+
const stream = streamSimple(requestModel, {
|
|
169
|
+
systemPrompt: AUTOCOMPLETE_SYSTEM_PROMPT,
|
|
170
|
+
messages: [{ role: "user", content: prompt, timestamp: Date.now() }],
|
|
171
|
+
}, {
|
|
172
|
+
...(auth.apiKey === undefined ? {} : { apiKey: auth.apiKey }),
|
|
173
|
+
...(auth.headers === undefined ? {} : { headers: auth.headers }),
|
|
174
|
+
cacheRetention: "none",
|
|
175
|
+
maxRetryDelayMs: 0,
|
|
176
|
+
maxRetries: 0,
|
|
177
|
+
maxTokens: requestModel.maxTokens,
|
|
178
|
+
...(parsedModel.thinkingLevel && parsedModel.thinkingLevel !== "off" ? { reasoning: parsedModel.thinkingLevel } : {}),
|
|
179
|
+
signal: requestSignal.signal,
|
|
180
|
+
temperature: 0.1,
|
|
181
|
+
timeoutMs,
|
|
182
|
+
});
|
|
183
|
+
for await (const event of stream) {
|
|
184
|
+
if (event.type === "text_delta")
|
|
185
|
+
output += event.delta;
|
|
186
|
+
else if (event.type === "done" && !output)
|
|
187
|
+
output = assistantMessageText(event.message);
|
|
188
|
+
else if (event.type === "error")
|
|
189
|
+
streamError = event.error.errorMessage ?? event.reason;
|
|
190
|
+
}
|
|
191
|
+
if (streamError)
|
|
192
|
+
throw new Error(streamError);
|
|
193
|
+
return output;
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
requestSignal.dispose();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
export function autocompleteHistoryFromMessages(messages, includeRecentMessages) {
|
|
200
|
+
const limit = numberInRange(includeRecentMessages, AUTOCOMPLETE_INCLUDE_RECENT_MESSAGES, 0, 20);
|
|
201
|
+
if (limit <= 0)
|
|
202
|
+
return [];
|
|
203
|
+
const history = [];
|
|
204
|
+
for (let index = messages.length - 1; index >= 0 && history.length < limit; index -= 1) {
|
|
205
|
+
const message = messages[index];
|
|
206
|
+
if (!isRecord(message))
|
|
207
|
+
continue;
|
|
208
|
+
const role = message.role === "user" || message.role === "assistant" ? message.role : undefined;
|
|
209
|
+
if (!role)
|
|
210
|
+
continue;
|
|
211
|
+
const text = compactHistoryText(messageText(message, role));
|
|
212
|
+
if (!text)
|
|
213
|
+
continue;
|
|
214
|
+
history.push({ role, text: clipHistoryText(text, AUTOCOMPLETE_HISTORY_MESSAGE_MAX_CHARS) });
|
|
215
|
+
}
|
|
216
|
+
return trimHistoryContext(history.reverse(), AUTOCOMPLETE_HISTORY_CONTEXT_MAX_CHARS);
|
|
217
|
+
}
|
|
218
|
+
export function buildAutocompletePrompt(input) {
|
|
219
|
+
const maxPromptTokens = numberInRange(input.maxPromptTokens, AUTOCOMPLETE_MAX_PROMPT_TOKENS, 256, 16_000);
|
|
220
|
+
let history = input.history.slice();
|
|
221
|
+
let prompt = renderAutocompletePrompt({ ...input, history });
|
|
222
|
+
while (history.length > 0 && autocompletePromptTokenEstimate(prompt) > maxPromptTokens) {
|
|
223
|
+
history = history.slice(1);
|
|
224
|
+
prompt = renderAutocompletePrompt({ ...input, history });
|
|
225
|
+
}
|
|
226
|
+
return autocompletePromptTokenEstimate(prompt) <= maxPromptTokens ? prompt : "";
|
|
227
|
+
}
|
|
228
|
+
function renderAutocompletePrompt(input) {
|
|
229
|
+
const lines = [
|
|
230
|
+
"Complete the current terminal input for the active pix/pi coding-agent session.",
|
|
231
|
+
`cwd: ${input.cwd}`,
|
|
232
|
+
];
|
|
233
|
+
if (input.history.length > 0) {
|
|
234
|
+
lines.push("", "Recent messages are context only; never continue them directly.", "<recent-active-session-messages>", formatAutocompleteHistory(input.history), "</recent-active-session-messages>");
|
|
235
|
+
}
|
|
236
|
+
return [
|
|
237
|
+
...lines,
|
|
238
|
+
"",
|
|
239
|
+
"Return only the suffix to append after <cursor>. Return nothing if unsure.",
|
|
240
|
+
"<draft>",
|
|
241
|
+
input.draft,
|
|
242
|
+
"<cursor>",
|
|
243
|
+
"</draft>",
|
|
244
|
+
].join("\n");
|
|
245
|
+
}
|
|
246
|
+
export function autocompletePromptTokenEstimate(prompt, systemPrompt = AUTOCOMPLETE_SYSTEM_PROMPT) {
|
|
247
|
+
return estimateTextTokens(systemPrompt) + estimateTextTokens(prompt);
|
|
248
|
+
}
|
|
249
|
+
function estimateTextTokens(text) {
|
|
250
|
+
return Math.ceil(text.length / AUTOCOMPLETE_TOKEN_CHARS);
|
|
251
|
+
}
|
|
252
|
+
export function cleanupCompletion(output, draft, config) {
|
|
253
|
+
let text = output.replace(/\r\n/gu, "\n").trimEnd();
|
|
254
|
+
const fenced = /^```[^\n`]*\n([\s\S]*?)\n```$/u.exec(text.trim());
|
|
255
|
+
if (fenced)
|
|
256
|
+
text = fenced[1].trimEnd();
|
|
257
|
+
if (text.startsWith(draft))
|
|
258
|
+
text = text.slice(draft.length);
|
|
259
|
+
text = text
|
|
260
|
+
.replace(/^<cursor>/iu, "")
|
|
261
|
+
.replace(/^\s*(?:completion|suffix|autocomplete|продолжение)\s*:\s*/iu, "")
|
|
262
|
+
.replace(/^\n+/u, "");
|
|
263
|
+
if (!text.trim())
|
|
264
|
+
return "";
|
|
265
|
+
const maxTokens = numberInRange(config?.maxTokens, AUTOCOMPLETE_MAX_TOKENS, 8, 256);
|
|
266
|
+
const maxChars = Math.min(AUTOCOMPLETE_MAX_SUFFIX_LENGTH, maxTokens * 8);
|
|
267
|
+
return text.slice(0, maxChars);
|
|
268
|
+
}
|
|
269
|
+
function formatAutocompleteHistory(history) {
|
|
270
|
+
if (history.length === 0)
|
|
271
|
+
return "(no previous user/assistant messages in this active session)";
|
|
272
|
+
return history.map((message) => [
|
|
273
|
+
`<message role="${message.role}">`,
|
|
274
|
+
message.text.replace(/<\/message>/giu, "</ message>"),
|
|
275
|
+
"</message>",
|
|
276
|
+
].join("\n")).join("\n\n");
|
|
277
|
+
}
|
|
278
|
+
function assistantMessageText(message) {
|
|
279
|
+
return message.content
|
|
280
|
+
.flatMap((content) => content.type === "text" ? [content.text] : [])
|
|
281
|
+
.join("\n");
|
|
282
|
+
}
|
|
283
|
+
function messageText(message, role) {
|
|
284
|
+
return contentText(message.content, { includeImages: role === "user" });
|
|
285
|
+
}
|
|
286
|
+
function contentText(content, options) {
|
|
287
|
+
if (typeof content === "string")
|
|
288
|
+
return content;
|
|
289
|
+
if (!Array.isArray(content))
|
|
290
|
+
return "";
|
|
291
|
+
return content.flatMap((part) => {
|
|
292
|
+
if (!isRecord(part) || typeof part.type !== "string")
|
|
293
|
+
return [];
|
|
294
|
+
if (part.type === "text" && typeof part.text === "string")
|
|
295
|
+
return [part.text];
|
|
296
|
+
if (part.type === "image" && options.includeImages)
|
|
297
|
+
return ["[image]"];
|
|
298
|
+
return [];
|
|
299
|
+
}).join("\n");
|
|
300
|
+
}
|
|
301
|
+
function compactHistoryText(text) {
|
|
302
|
+
return text
|
|
303
|
+
.replace(/\r\n/gu, "\n")
|
|
304
|
+
.split("\n")
|
|
305
|
+
.filter((line) => !isMarkdownReferenceDefinition(line))
|
|
306
|
+
.join("\n")
|
|
307
|
+
.replace(/[\t ]+/gu, " ")
|
|
308
|
+
.replace(/\n{3,}/gu, "\n\n")
|
|
309
|
+
.trim();
|
|
310
|
+
}
|
|
311
|
+
function clipHistoryText(text, maxChars) {
|
|
312
|
+
if (text.length <= maxChars)
|
|
313
|
+
return text;
|
|
314
|
+
const headLength = Math.floor((maxChars - 3) / 2);
|
|
315
|
+
const tailLength = maxChars - 3 - headLength;
|
|
316
|
+
return `${text.slice(0, headLength).trimEnd()}\n…\n${text.slice(-tailLength).trimStart()}`;
|
|
317
|
+
}
|
|
318
|
+
function trimHistoryContext(history, maxChars) {
|
|
319
|
+
const trimmed = history.slice();
|
|
320
|
+
while (trimmed.length > 0 && historyContextChars(trimmed) > maxChars)
|
|
321
|
+
trimmed.shift();
|
|
322
|
+
return trimmed;
|
|
323
|
+
}
|
|
324
|
+
function historyContextChars(history) {
|
|
325
|
+
return history.reduce((sum, message) => sum + message.text.length + message.role.length + 32, 0);
|
|
326
|
+
}
|
|
327
|
+
function isMarkdownReferenceDefinition(line) {
|
|
328
|
+
return /^ {0,3}\[[^\]\n]+\]:[ \t]*\S.*$/u.test(line);
|
|
329
|
+
}
|
|
330
|
+
function numberInRange(value, fallback, min, max) {
|
|
331
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
332
|
+
return fallback;
|
|
333
|
+
const rounded = Math.round(value);
|
|
334
|
+
return Math.min(max, Math.max(min, rounded));
|
|
335
|
+
}
|
|
336
|
+
function createTimeoutSignal(parent, timeoutMs) {
|
|
337
|
+
const abortController = new AbortController();
|
|
338
|
+
const abort = () => abortController.abort();
|
|
339
|
+
if (parent?.aborted)
|
|
340
|
+
abort();
|
|
341
|
+
else
|
|
342
|
+
parent?.addEventListener("abort", abort, { once: true });
|
|
343
|
+
const timer = setTimeout(abort, timeoutMs);
|
|
344
|
+
timer.unref?.();
|
|
345
|
+
return {
|
|
346
|
+
signal: abortController.signal,
|
|
347
|
+
dispose: () => {
|
|
348
|
+
clearTimeout(timer);
|
|
349
|
+
parent?.removeEventListener("abort", abort);
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { InputEditor } from "
|
|
1
|
+
import type { InputEditor } from "../../input-editor.js";
|
|
2
2
|
import type { AgentSessionRuntime } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import type { AppPopupActionController } from "
|
|
4
|
-
import type { AppPopupMenuController } from "
|
|
5
|
-
import type { AppQueuedMessageController } from "
|
|
6
|
-
import type { AppRequestHistory } from "
|
|
7
|
-
import { type InteractiveShellCommandResult } from "
|
|
8
|
-
import type { Entry, SessionActivity } from "
|
|
3
|
+
import type { AppPopupActionController } from "../popup/popup-action-controller.js";
|
|
4
|
+
import type { AppPopupMenuController } from "../popup/popup-menu-controller.js";
|
|
5
|
+
import type { AppQueuedMessageController } from "../session/queued-message-controller.js";
|
|
6
|
+
import type { AppRequestHistory } from "../session/request-history.js";
|
|
7
|
+
import { type InteractiveShellCommandResult } from "../commands/shell-command.js";
|
|
8
|
+
import type { Entry, SessionActivity } from "../types.js";
|
|
9
9
|
export type AppInputActionControllerHost = {
|
|
10
10
|
runtime(): AgentSessionRuntime | undefined;
|
|
11
11
|
isRunning(): boolean;
|
|
@@ -36,6 +36,7 @@ export declare class AppInputActionController {
|
|
|
36
36
|
private abortInFlight;
|
|
37
37
|
constructor(host: AppInputActionControllerHost, popupMenus: AppPopupMenuController, popupActions: AppPopupActionController, queuedMessages: AppQueuedMessageController);
|
|
38
38
|
handleEnter(): void;
|
|
39
|
+
queueInputFromEditor(): Promise<void>;
|
|
39
40
|
handleInterrupt(): Promise<void>;
|
|
40
41
|
handleEscape(): Promise<void>;
|
|
41
42
|
private abortStreamingSession;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createId } from "
|
|
2
|
-
import { stringifyUnknown } from "
|
|
3
|
-
import { bangShellCommandFromInput, formatShellCommandEntry, } from "
|
|
1
|
+
import { createId } from "../id.js";
|
|
2
|
+
import { stringifyUnknown } from "../rendering/message-content.js";
|
|
3
|
+
import { bangShellCommandFromInput, formatShellCommandEntry, } from "../commands/shell-command.js";
|
|
4
4
|
const ABORT_STATUS_RESTORE_MS = 1200;
|
|
5
5
|
export class AppInputActionController {
|
|
6
6
|
host;
|
|
@@ -21,6 +21,27 @@ export class AppInputActionController {
|
|
|
21
21
|
}
|
|
22
22
|
void this.submitInput();
|
|
23
23
|
}
|
|
24
|
+
async queueInputFromEditor() {
|
|
25
|
+
await this.host.stopVoiceInput();
|
|
26
|
+
if (this.popupMenus.syncActivePopupMenu())
|
|
27
|
+
this.popupMenus.cancelActivePopupMenu();
|
|
28
|
+
const inputEditor = this.host.inputEditor();
|
|
29
|
+
const rawPromptText = inputEditor.promptText;
|
|
30
|
+
const rawDisplayText = inputEditor.expandedText;
|
|
31
|
+
const promptText = rawPromptText.trimEnd();
|
|
32
|
+
const displayText = rawDisplayText.trimEnd();
|
|
33
|
+
const images = [...inputEditor.images];
|
|
34
|
+
if (!promptText && images.length === 0)
|
|
35
|
+
return;
|
|
36
|
+
const message = this.queuedMessages.createSubmittedUserMessage(promptText, displayText, images);
|
|
37
|
+
this.host.requestHistory().add(message.displayText);
|
|
38
|
+
inputEditor.clear();
|
|
39
|
+
await this.host.clearPersistedInputDraft();
|
|
40
|
+
this.host.render();
|
|
41
|
+
this.queuedMessages.deferUserMessage(message);
|
|
42
|
+
if (this.host.isRunning())
|
|
43
|
+
this.host.render();
|
|
44
|
+
}
|
|
24
45
|
async handleInterrupt() {
|
|
25
46
|
if (this.host.interruptShellCommand()) {
|
|
26
47
|
this.host.inputEditor().clear();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { InputEditor } from "
|
|
2
|
-
import type { ExtensionTerminalInputResult } from "
|
|
3
|
-
import type { MouseEvent, ActivePopupMenu } from "
|
|
1
|
+
import { InputEditor } from "../../input-editor.js";
|
|
2
|
+
import type { ExtensionTerminalInputResult } from "../extensions/extension-ui-controller.js";
|
|
3
|
+
import type { MouseEvent, ActivePopupMenu } from "../types.js";
|
|
4
4
|
type DirectPopupMenu = Exclude<ActivePopupMenu, "slash">;
|
|
5
5
|
export type InputControllerHost = {
|
|
6
6
|
readonly inputEditor: InputEditor;
|
|
@@ -24,6 +24,7 @@ export type InputControllerHost = {
|
|
|
24
24
|
handleDirectPopupInput(char: string): boolean;
|
|
25
25
|
autocompleteModel(): boolean;
|
|
26
26
|
autocompleteThinking(): boolean;
|
|
27
|
+
acceptAutocompleteSuggestion(): boolean;
|
|
27
28
|
autocompleteSlashCommand(): void;
|
|
28
29
|
toggleVoiceRecording(): void;
|
|
29
30
|
stop(): Promise<void>;
|
|
@@ -42,7 +42,7 @@ export class AppInputController {
|
|
|
42
42
|
}
|
|
43
43
|
drainInputBuffer() {
|
|
44
44
|
while (this.inputBuffer.length > 0) {
|
|
45
|
-
const mouseMatch = /^\x1b\[<(\d+);(
|
|
45
|
+
const mouseMatch = /^\x1b\[<(\d+);(-?\d+);(-?\d+)([mM])/.exec(this.inputBuffer);
|
|
46
46
|
if (mouseMatch) {
|
|
47
47
|
this.inputBuffer = this.inputBuffer.slice(mouseMatch[0].length);
|
|
48
48
|
this.host.handleMouse({
|
|
@@ -357,6 +357,8 @@ export class AppInputController {
|
|
|
357
357
|
if (char === "\t") {
|
|
358
358
|
if (this.host.getDirectPopupMenu() === "sdk-menu")
|
|
359
359
|
return;
|
|
360
|
+
if (this.host.acceptAutocompleteSuggestion())
|
|
361
|
+
return;
|
|
360
362
|
if (this.host.autocompleteModel())
|
|
361
363
|
return;
|
|
362
364
|
if (this.host.autocompleteThinking())
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { InputEditor } from "
|
|
1
|
+
import { InputEditor } from "../../input-editor.js";
|
|
2
2
|
export type InputPasteHost = {
|
|
3
3
|
readonly inputEditor: InputEditor;
|
|
4
4
|
readonly cwd: string;
|
|
@@ -20,6 +20,7 @@ export declare class InputPasteHandler {
|
|
|
20
20
|
private plainPasteFilePath;
|
|
21
21
|
private isDuplicatePaste;
|
|
22
22
|
private handlePasteEnd;
|
|
23
|
+
private schedulePastedText;
|
|
23
24
|
private handleFilePaste;
|
|
24
25
|
private filePathForInput;
|
|
25
26
|
private displayPathForInput;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { stat } from "node:fs/promises";
|
|
3
3
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
4
|
-
import { isImagePath, looksLikeFilePath, quoteFilePathForInput, readClipboardImage } from "
|
|
5
|
-
import { PASTE_DUPLICATE_WINDOW_MS } from "
|
|
6
|
-
import { normalizePastedTextForDuplicateKey } from "
|
|
4
|
+
import { isImagePath, looksLikeFilePath, quoteFilePathForInput, readClipboardImage } from "../../input-editor.js";
|
|
5
|
+
import { PASTE_DUPLICATE_WINDOW_MS } from "../constants.js";
|
|
6
|
+
import { normalizePastedTextForDuplicateKey } from "../rendering/render-text.js";
|
|
7
|
+
const PASTE_FINGERPRINT_PREFIX_CHARS = 64 * 1024;
|
|
7
8
|
export class InputPasteHandler {
|
|
8
9
|
host;
|
|
9
10
|
pasteBuffer = "";
|
|
@@ -23,13 +24,7 @@ export class InputPasteHandler {
|
|
|
23
24
|
return true;
|
|
24
25
|
}
|
|
25
26
|
if (!this.host.inputEditor.isInBracketedPaste && this.isPlainMultilinePasteChunk(data)) {
|
|
26
|
-
|
|
27
|
-
this.host.render();
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
this.host.resetRequestHistoryNavigation();
|
|
31
|
-
this.host.inputEditor.attachPastedText(data);
|
|
32
|
-
this.host.render();
|
|
27
|
+
this.schedulePastedText(data);
|
|
33
28
|
return true;
|
|
34
29
|
}
|
|
35
30
|
return false;
|
|
@@ -43,7 +38,9 @@ export class InputPasteHandler {
|
|
|
43
38
|
}
|
|
44
39
|
endBracketedPaste() {
|
|
45
40
|
this.host.inputEditor.endBracketedPaste();
|
|
46
|
-
this.
|
|
41
|
+
const text = this.pasteBuffer;
|
|
42
|
+
this.pasteBuffer = "";
|
|
43
|
+
this.handlePasteEnd(text);
|
|
47
44
|
}
|
|
48
45
|
async handleClipboardImagePaste() {
|
|
49
46
|
const image = await readClipboardImage();
|
|
@@ -83,16 +80,17 @@ export class InputPasteHandler {
|
|
|
83
80
|
this.recentPasteFingerprints.delete(fingerprint);
|
|
84
81
|
}
|
|
85
82
|
const normalizedPayload = kind === "text" ? normalizePastedTextForDuplicateKey(payload) : payload;
|
|
86
|
-
const
|
|
83
|
+
const fingerprintPayload = normalizedPayload.length > PASTE_FINGERPRINT_PREFIX_CHARS
|
|
84
|
+
? `${normalizedPayload.length}:${normalizedPayload.slice(0, PASTE_FINGERPRINT_PREFIX_CHARS)}`
|
|
85
|
+
: normalizedPayload;
|
|
86
|
+
const fingerprint = `${kind}:${createHash("sha256").update(fingerprintPayload).digest("hex")}`;
|
|
87
87
|
const previousTimestamp = this.recentPasteFingerprints.get(fingerprint);
|
|
88
88
|
if (previousTimestamp !== undefined && now - previousTimestamp <= PASTE_DUPLICATE_WINDOW_MS)
|
|
89
89
|
return true;
|
|
90
90
|
this.recentPasteFingerprints.set(fingerprint, now);
|
|
91
91
|
return false;
|
|
92
92
|
}
|
|
93
|
-
handlePasteEnd() {
|
|
94
|
-
const text = this.pasteBuffer;
|
|
95
|
-
this.pasteBuffer = "";
|
|
93
|
+
handlePasteEnd(text) {
|
|
96
94
|
if (!text)
|
|
97
95
|
return;
|
|
98
96
|
const filePath = this.plainPasteFilePath(text);
|
|
@@ -104,13 +102,19 @@ export class InputPasteHandler {
|
|
|
104
102
|
void this.handleFilePaste(filePath);
|
|
105
103
|
return;
|
|
106
104
|
}
|
|
107
|
-
|
|
105
|
+
this.schedulePastedText(text);
|
|
106
|
+
}
|
|
107
|
+
schedulePastedText(text) {
|
|
108
|
+
const timer = setTimeout(() => {
|
|
109
|
+
if (this.isDuplicatePaste("text", text)) {
|
|
110
|
+
this.host.render();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.host.resetRequestHistoryNavigation();
|
|
114
|
+
this.host.inputEditor.attachPastedText(text);
|
|
108
115
|
this.host.render();
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
this.host.resetRequestHistoryNavigation();
|
|
112
|
-
this.host.inputEditor.attachPastedText(text);
|
|
113
|
-
this.host.render();
|
|
116
|
+
}, 0);
|
|
117
|
+
timer.unref?.();
|
|
114
118
|
}
|
|
115
119
|
async handleFilePaste(filePath) {
|
|
116
120
|
const inputPath = await this.filePathForInput(filePath);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { TERMINAL_COMMAND_MODIFIER_FLAG } from "
|
|
5
|
-
import { isRecord } from "
|
|
4
|
+
import { TERMINAL_COMMAND_MODIFIER_FLAG } from "../constants.js";
|
|
5
|
+
import { isRecord } from "../guards.js";
|
|
6
6
|
const cjsRequire = createRequire(import.meta.url);
|
|
7
7
|
let nativeModifiersHelper;
|
|
8
8
|
export function isNativeShiftPressed() {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type AgentSessionRuntime } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { PromptEnhancerConfig } from "
|
|
3
|
-
import type { InputEditor } from "
|
|
4
|
-
import type { ToastNotifier } from "
|
|
5
|
-
import type { TabInputState } from "
|
|
6
|
-
import type { SessionActivity } from "
|
|
2
|
+
import type { PromptEnhancerConfig } from "../../config.js";
|
|
3
|
+
import type { InputEditor } from "../../input-editor.js";
|
|
4
|
+
import type { ToastNotifier } from "../../ui.js";
|
|
5
|
+
import type { TabInputState } from "../session/tabs-controller.js";
|
|
6
|
+
import type { SessionActivity } from "../types.js";
|
|
7
7
|
export type AppPromptEnhancerControllerHost = {
|
|
8
8
|
runtime(): AgentSessionRuntime | undefined;
|
|
9
9
|
inputEditor(): InputEditor;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createAgentSessionFromServices, createAgentSessionServices, SessionManager, } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { APP_ICONS } from "
|
|
3
|
-
import { stringifyUnknown } from "
|
|
4
|
-
import { parseModelRef } from "
|
|
2
|
+
import { APP_ICONS } from "../icons.js";
|
|
3
|
+
import { stringifyUnknown } from "../rendering/message-content.js";
|
|
4
|
+
import { parseModelRef } from "../model/model-ref.js";
|
|
5
5
|
const PROMPT_ENHANCER_SYSTEM_PROMPT = `You improve prompts for a coding agent.
|
|
6
6
|
|
|
7
7
|
Rewrite the user's draft into a clearer, more actionable prompt.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type DictationConfig } from "
|
|
1
|
+
import { type DictationConfig } from "../../config.js";
|
|
2
2
|
export type VoiceLanguage = string;
|
|
3
3
|
export type VoiceInputState = "idle" | "installing" | "downloading" | "loading" | "listening";
|
|
4
4
|
export type AppVoiceControllerHost = {
|
|
@@ -22,6 +22,7 @@ export declare class AppVoiceController {
|
|
|
22
22
|
private progressTimer;
|
|
23
23
|
private lastSystemProgressMessage;
|
|
24
24
|
private partialTranscript;
|
|
25
|
+
private partialTranscriptTimer;
|
|
25
26
|
private startGeneration;
|
|
26
27
|
constructor(host: AppVoiceControllerHost, dictationConfig: DictationConfig);
|
|
27
28
|
statusWidgetText(): string;
|
|
@@ -47,6 +48,7 @@ export declare class AppVoiceController {
|
|
|
47
48
|
private emitTranscript;
|
|
48
49
|
private emitPartialTranscript;
|
|
49
50
|
private clearPartialTranscript;
|
|
51
|
+
private schedulePartialTranscriptEmit;
|
|
50
52
|
private isCurrentStart;
|
|
51
53
|
private isCurrentAudioProcess;
|
|
52
54
|
}
|