@mariozechner/pi-coding-agent 0.16.0 → 0.18.0
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/CHANGELOG.md +38 -0
- package/README.md +58 -1
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +5 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +30 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +181 -21
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction.d.ts +30 -5
- package/dist/core/compaction.d.ts.map +1 -1
- package/dist/core/compaction.js +194 -61
- package/dist/core/compaction.js.map +1 -1
- package/dist/core/hooks/index.d.ts +5 -0
- package/dist/core/hooks/index.d.ts.map +1 -0
- package/dist/core/hooks/index.js +4 -0
- package/dist/core/hooks/index.js.map +1 -0
- package/dist/core/hooks/loader.d.ts +56 -0
- package/dist/core/hooks/loader.d.ts.map +1 -0
- package/dist/core/hooks/loader.js +158 -0
- package/dist/core/hooks/loader.js.map +1 -0
- package/dist/core/hooks/runner.d.ts +69 -0
- package/dist/core/hooks/runner.d.ts.map +1 -0
- package/dist/core/hooks/runner.js +203 -0
- package/dist/core/hooks/runner.js.map +1 -0
- package/dist/core/hooks/tool-wrapper.d.ts +16 -0
- package/dist/core/hooks/tool-wrapper.d.ts.map +1 -0
- package/dist/core/hooks/tool-wrapper.js +71 -0
- package/dist/core/hooks/tool-wrapper.js.map +1 -0
- package/dist/core/hooks/types.d.ts +220 -0
- package/dist/core/hooks/types.d.ts.map +1 -0
- package/dist/core/hooks/types.js +8 -0
- package/dist/core/hooks/types.js.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/session-manager.d.ts +10 -3
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +78 -28
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +6 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +14 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +5 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/truncate.d.ts +6 -2
- package/dist/core/tools/truncate.d.ts.map +1 -1
- package/dist/core/tools/truncate.js +11 -1
- package/dist/core/tools/truncate.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +23 -12
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +1 -0
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +17 -6
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/hook-input.d.ts +12 -0
- package/dist/modes/interactive/components/hook-input.d.ts.map +1 -0
- package/dist/modes/interactive/components/hook-input.js +46 -0
- package/dist/modes/interactive/components/hook-input.js.map +1 -0
- package/dist/modes/interactive/components/hook-selector.d.ts +16 -0
- package/dist/modes/interactive/components/hook-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/hook-selector.js +76 -0
- package/dist/modes/interactive/components/hook-selector.js.map +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +12 -7
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +37 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +190 -7
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +15 -0
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +2 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +118 -3
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +41 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/compaction.md +519 -0
- package/docs/hooks.md +609 -0
- package/docs/rpc.md +870 -0
- package/docs/session.md +89 -0
- package/docs/theme.md +586 -0
- package/docs/truncation.md +235 -0
- package/docs/undercompaction.md +313 -0
- package/package.json +18 -6
|
@@ -11,6 +11,7 @@ import { isBashExecutionMessage } from "../../core/messages.js";
|
|
|
11
11
|
import { invalidateOAuthCache } from "../../core/model-config.js";
|
|
12
12
|
import { listOAuthProviders, login, logout } from "../../core/oauth/index.js";
|
|
13
13
|
import { getLatestCompactionEntry, SUMMARY_PREFIX, SUMMARY_SUFFIX } from "../../core/session-manager.js";
|
|
14
|
+
import { loadProjectContextFiles } from "../../core/system-prompt.js";
|
|
14
15
|
import { getChangelogPath, parseChangelog } from "../../utils/changelog.js";
|
|
15
16
|
import { copyToClipboard } from "../../utils/clipboard.js";
|
|
16
17
|
import { AssistantMessageComponent } from "./components/assistant-message.js";
|
|
@@ -19,6 +20,8 @@ import { CompactionComponent } from "./components/compaction.js";
|
|
|
19
20
|
import { CustomEditor } from "./components/custom-editor.js";
|
|
20
21
|
import { DynamicBorder } from "./components/dynamic-border.js";
|
|
21
22
|
import { FooterComponent } from "./components/footer.js";
|
|
23
|
+
import { HookInputComponent } from "./components/hook-input.js";
|
|
24
|
+
import { HookSelectorComponent } from "./components/hook-selector.js";
|
|
22
25
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
23
26
|
import { OAuthSelectorComponent } from "./components/oauth-selector.js";
|
|
24
27
|
import { QueueModeSelectorComponent } from "./components/queue-mode-selector.js";
|
|
@@ -66,6 +69,9 @@ export class InteractiveMode {
|
|
|
66
69
|
// Auto-compaction state
|
|
67
70
|
autoCompactionLoader = null;
|
|
68
71
|
autoCompactionEscapeHandler;
|
|
72
|
+
// Hook UI state
|
|
73
|
+
hookSelector = null;
|
|
74
|
+
hookInput = null;
|
|
69
75
|
// Convenience accessors
|
|
70
76
|
get agent() {
|
|
71
77
|
return this.session.agent;
|
|
@@ -189,6 +195,8 @@ export class InteractiveMode {
|
|
|
189
195
|
// Start the UI
|
|
190
196
|
this.ui.start();
|
|
191
197
|
this.isInitialized = true;
|
|
198
|
+
// Initialize hooks with TUI-based UI context
|
|
199
|
+
await this.initHooks();
|
|
192
200
|
// Subscribe to agent events
|
|
193
201
|
this.subscribeToAgent();
|
|
194
202
|
// Set up theme file watcher
|
|
@@ -202,6 +210,161 @@ export class InteractiveMode {
|
|
|
202
210
|
this.ui.requestRender();
|
|
203
211
|
});
|
|
204
212
|
}
|
|
213
|
+
// =========================================================================
|
|
214
|
+
// Hook System
|
|
215
|
+
// =========================================================================
|
|
216
|
+
/**
|
|
217
|
+
* Initialize the hook system with TUI-based UI context.
|
|
218
|
+
*/
|
|
219
|
+
async initHooks() {
|
|
220
|
+
// Show loaded project context files
|
|
221
|
+
const contextFiles = loadProjectContextFiles();
|
|
222
|
+
if (contextFiles.length > 0) {
|
|
223
|
+
const contextList = contextFiles.map((f) => theme.fg("dim", ` ${f.path}`)).join("\n");
|
|
224
|
+
this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded context:\n") + contextList, 0, 0));
|
|
225
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
226
|
+
}
|
|
227
|
+
const hookRunner = this.session.hookRunner;
|
|
228
|
+
if (!hookRunner) {
|
|
229
|
+
return; // No hooks loaded
|
|
230
|
+
}
|
|
231
|
+
// Set TUI-based UI context on the hook runner
|
|
232
|
+
hookRunner.setUIContext(this.createHookUIContext(), true);
|
|
233
|
+
hookRunner.setSessionFile(this.session.sessionFile);
|
|
234
|
+
// Subscribe to hook errors
|
|
235
|
+
hookRunner.onError((error) => {
|
|
236
|
+
this.showHookError(error.hookPath, error.error);
|
|
237
|
+
});
|
|
238
|
+
// Set up send handler for pi.send()
|
|
239
|
+
hookRunner.setSendHandler((text, attachments) => {
|
|
240
|
+
this.handleHookSend(text, attachments);
|
|
241
|
+
});
|
|
242
|
+
// Show loaded hooks
|
|
243
|
+
const hookPaths = hookRunner.getHookPaths();
|
|
244
|
+
if (hookPaths.length > 0) {
|
|
245
|
+
const hookList = hookPaths.map((p) => theme.fg("dim", ` ${p}`)).join("\n");
|
|
246
|
+
this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded hooks:\n") + hookList, 0, 0));
|
|
247
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
248
|
+
}
|
|
249
|
+
// Emit session_start event
|
|
250
|
+
await hookRunner.emit({ type: "session_start" });
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Create the UI context for hooks.
|
|
254
|
+
*/
|
|
255
|
+
createHookUIContext() {
|
|
256
|
+
return {
|
|
257
|
+
select: (title, options) => this.showHookSelector(title, options),
|
|
258
|
+
confirm: (title, message) => this.showHookConfirm(title, message),
|
|
259
|
+
input: (title, placeholder) => this.showHookInput(title, placeholder),
|
|
260
|
+
notify: (message, type) => this.showHookNotify(message, type),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Show a selector for hooks.
|
|
265
|
+
*/
|
|
266
|
+
showHookSelector(title, options) {
|
|
267
|
+
return new Promise((resolve) => {
|
|
268
|
+
this.hookSelector = new HookSelectorComponent(title, options, (option) => {
|
|
269
|
+
this.hideHookSelector();
|
|
270
|
+
resolve(option);
|
|
271
|
+
}, () => {
|
|
272
|
+
this.hideHookSelector();
|
|
273
|
+
resolve(null);
|
|
274
|
+
});
|
|
275
|
+
this.editorContainer.clear();
|
|
276
|
+
this.editorContainer.addChild(this.hookSelector);
|
|
277
|
+
this.ui.setFocus(this.hookSelector);
|
|
278
|
+
this.ui.requestRender();
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Hide the hook selector.
|
|
283
|
+
*/
|
|
284
|
+
hideHookSelector() {
|
|
285
|
+
this.editorContainer.clear();
|
|
286
|
+
this.editorContainer.addChild(this.editor);
|
|
287
|
+
this.hookSelector = null;
|
|
288
|
+
this.ui.setFocus(this.editor);
|
|
289
|
+
this.ui.requestRender();
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Show a confirmation dialog for hooks.
|
|
293
|
+
*/
|
|
294
|
+
async showHookConfirm(title, message) {
|
|
295
|
+
const result = await this.showHookSelector(`${title}\n${message}`, ["Yes", "No"]);
|
|
296
|
+
return result === "Yes";
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Show a text input for hooks.
|
|
300
|
+
*/
|
|
301
|
+
showHookInput(title, placeholder) {
|
|
302
|
+
return new Promise((resolve) => {
|
|
303
|
+
this.hookInput = new HookInputComponent(title, placeholder, (value) => {
|
|
304
|
+
this.hideHookInput();
|
|
305
|
+
resolve(value);
|
|
306
|
+
}, () => {
|
|
307
|
+
this.hideHookInput();
|
|
308
|
+
resolve(null);
|
|
309
|
+
});
|
|
310
|
+
this.editorContainer.clear();
|
|
311
|
+
this.editorContainer.addChild(this.hookInput);
|
|
312
|
+
this.ui.setFocus(this.hookInput);
|
|
313
|
+
this.ui.requestRender();
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Hide the hook input.
|
|
318
|
+
*/
|
|
319
|
+
hideHookInput() {
|
|
320
|
+
this.editorContainer.clear();
|
|
321
|
+
this.editorContainer.addChild(this.editor);
|
|
322
|
+
this.hookInput = null;
|
|
323
|
+
this.ui.setFocus(this.editor);
|
|
324
|
+
this.ui.requestRender();
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Show a notification for hooks.
|
|
328
|
+
*/
|
|
329
|
+
showHookNotify(message, type) {
|
|
330
|
+
if (type === "error") {
|
|
331
|
+
this.showError(message);
|
|
332
|
+
}
|
|
333
|
+
else if (type === "warning") {
|
|
334
|
+
this.showWarning(message);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
this.showStatus(message);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Show a hook error in the UI.
|
|
342
|
+
*/
|
|
343
|
+
showHookError(hookPath, error) {
|
|
344
|
+
const errorText = new Text(theme.fg("error", `Hook "${hookPath}" error: ${error}`), 1, 0);
|
|
345
|
+
this.chatContainer.addChild(errorText);
|
|
346
|
+
this.ui.requestRender();
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Handle pi.send() from hooks.
|
|
350
|
+
* If streaming, queue the message. Otherwise, start a new agent loop.
|
|
351
|
+
*/
|
|
352
|
+
handleHookSend(text, attachments) {
|
|
353
|
+
if (this.session.isStreaming) {
|
|
354
|
+
// Queue the message for later (note: attachments are lost when queuing)
|
|
355
|
+
this.session.queueMessage(text);
|
|
356
|
+
this.updatePendingMessagesDisplay();
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
// Start a new agent loop immediately
|
|
360
|
+
this.session.prompt(text, { attachments }).catch((err) => {
|
|
361
|
+
this.showError(err instanceof Error ? err.message : String(err));
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// =========================================================================
|
|
366
|
+
// Key Handlers
|
|
367
|
+
// =========================================================================
|
|
205
368
|
setupKeyHandlers() {
|
|
206
369
|
this.editor.onEscape = () => {
|
|
207
370
|
if (this.loadingAnimation) {
|
|
@@ -350,6 +513,10 @@ export class InteractiveMode {
|
|
|
350
513
|
return;
|
|
351
514
|
}
|
|
352
515
|
}
|
|
516
|
+
// Block input during compaction (will retry automatically)
|
|
517
|
+
if (this.session.isCompacting) {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
353
520
|
// Queue message if agent is streaming
|
|
354
521
|
if (this.session.isStreaming) {
|
|
355
522
|
await this.session.queueMessage(text);
|
|
@@ -484,19 +651,21 @@ export class InteractiveMode {
|
|
|
484
651
|
this.pendingTools.clear();
|
|
485
652
|
this.ui.requestRender();
|
|
486
653
|
break;
|
|
487
|
-
case "auto_compaction_start":
|
|
654
|
+
case "auto_compaction_start": {
|
|
488
655
|
// Set up escape to abort auto-compaction
|
|
489
656
|
this.autoCompactionEscapeHandler = this.editor.onEscape;
|
|
490
657
|
this.editor.onEscape = () => {
|
|
491
658
|
this.session.abortCompaction();
|
|
492
659
|
};
|
|
493
|
-
// Show compacting indicator
|
|
660
|
+
// Show compacting indicator with reason
|
|
494
661
|
this.statusContainer.clear();
|
|
495
|
-
|
|
662
|
+
const reasonText = event.reason === "overflow" ? "Context overflow detected, " : "";
|
|
663
|
+
this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `${reasonText}Auto-compacting... (esc to cancel)`);
|
|
496
664
|
this.statusContainer.addChild(this.autoCompactionLoader);
|
|
497
665
|
this.ui.requestRender();
|
|
498
666
|
break;
|
|
499
|
-
|
|
667
|
+
}
|
|
668
|
+
case "auto_compaction_end": {
|
|
500
669
|
// Restore escape handler
|
|
501
670
|
if (this.autoCompactionEscapeHandler) {
|
|
502
671
|
this.editor.onEscape = this.autoCompactionEscapeHandler;
|
|
@@ -524,6 +693,7 @@ export class InteractiveMode {
|
|
|
524
693
|
}
|
|
525
694
|
this.ui.requestRender();
|
|
526
695
|
break;
|
|
696
|
+
}
|
|
527
697
|
}
|
|
528
698
|
}
|
|
529
699
|
/** Extract text content from a user message */
|
|
@@ -639,6 +809,13 @@ export class InteractiveMode {
|
|
|
639
809
|
}
|
|
640
810
|
renderInitialMessages(state) {
|
|
641
811
|
this.renderMessages(state.messages, { updateFooter: true, populateHistory: true });
|
|
812
|
+
// Show compaction info if session was compacted
|
|
813
|
+
const entries = this.sessionManager.loadEntries();
|
|
814
|
+
const compactionCount = entries.filter((e) => e.type === "compaction").length;
|
|
815
|
+
if (compactionCount > 0) {
|
|
816
|
+
const times = compactionCount === 1 ? "1 time" : `${compactionCount} times`;
|
|
817
|
+
this.showStatus(`Session compacted ${times}`);
|
|
818
|
+
}
|
|
642
819
|
}
|
|
643
820
|
async getUserInput() {
|
|
644
821
|
return new Promise((resolve) => {
|
|
@@ -869,12 +1046,18 @@ export class InteractiveMode {
|
|
|
869
1046
|
return;
|
|
870
1047
|
}
|
|
871
1048
|
this.showSelector((done) => {
|
|
872
|
-
const selector = new UserMessageSelectorComponent(userMessages.map((m) => ({ index: m.entryIndex, text: m.text })), (entryIndex) => {
|
|
873
|
-
const
|
|
1049
|
+
const selector = new UserMessageSelectorComponent(userMessages.map((m) => ({ index: m.entryIndex, text: m.text })), async (entryIndex) => {
|
|
1050
|
+
const result = await this.session.branch(entryIndex);
|
|
1051
|
+
if (result.skipped) {
|
|
1052
|
+
// Hook requested to skip conversation restore
|
|
1053
|
+
done();
|
|
1054
|
+
this.ui.requestRender();
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
874
1057
|
this.chatContainer.clear();
|
|
875
1058
|
this.isFirstUserMessage = true;
|
|
876
1059
|
this.renderInitialMessages(this.session.state);
|
|
877
|
-
this.editor.setText(selectedText);
|
|
1060
|
+
this.editor.setText(result.selectedText);
|
|
878
1061
|
done();
|
|
879
1062
|
this.showStatus("Branched to new session");
|
|
880
1063
|
}, () => {
|