@oh-my-pi/pi-coding-agent 15.5.15 → 15.6.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 +46 -0
- package/dist/types/cli/classify-install-target.d.ts +0 -10
- package/dist/types/cli/initial-message.d.ts +1 -1
- package/dist/types/cli/tiny-models-cli.d.ts +9 -0
- package/dist/types/commands/tiny-models.d.ts +22 -0
- package/dist/types/commit/analysis/conventional.d.ts +1 -1
- package/dist/types/commit/analysis/summary.d.ts +1 -1
- package/dist/types/commit/changelog/generate.d.ts +1 -1
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
- package/dist/types/config/model-id-affixes.d.ts +10 -0
- package/dist/types/config/settings-schema.d.ts +232 -7
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +16 -1
- package/dist/types/internal-urls/agent-protocol.d.ts +2 -1
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -1
- package/dist/types/internal-urls/local-protocol.d.ts +2 -1
- package/dist/types/internal-urls/memory-protocol.d.ts +2 -1
- package/dist/types/internal-urls/omp-protocol.d.ts +2 -1
- package/dist/types/internal-urls/router.d.ts +8 -1
- package/dist/types/internal-urls/rule-protocol.d.ts +2 -1
- package/dist/types/internal-urls/skill-protocol.d.ts +2 -1
- package/dist/types/internal-urls/types.d.ts +26 -0
- package/dist/types/memory-backend/index.d.ts +1 -0
- package/dist/types/memory-backend/resolve.d.ts +2 -1
- package/dist/types/memory-backend/types.d.ts +7 -1
- package/dist/types/mnemosyne/backend.d.ts +4 -0
- package/dist/types/mnemosyne/config.d.ts +29 -0
- package/dist/types/mnemosyne/index.d.ts +3 -0
- package/dist/types/mnemosyne/state.d.ts +72 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -3
- package/dist/types/modes/components/hook-selector.d.ts +27 -0
- package/dist/types/modes/components/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/context-thresholds.d.ts +6 -0
- package/dist/types/modes/components/tiny-title-download-progress.d.ts +11 -0
- package/dist/types/modes/components/welcome.d.ts +1 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +4 -1
- package/dist/types/modes/gradient-highlight.d.ts +23 -0
- package/dist/types/modes/interactive-mode.d.ts +4 -2
- package/dist/types/modes/internal-url-autocomplete.d.ts +43 -0
- package/dist/types/modes/orchestrate.d.ts +10 -0
- package/dist/types/modes/theme/defaults/index.d.ts +8406 -8406
- package/dist/types/modes/ultrathink.d.ts +3 -3
- package/dist/types/modes/utils/keybinding-matchers.d.ts +5 -0
- package/dist/types/sdk.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +33 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/task/render.d.ts +5 -1
- package/dist/types/tiny/models.d.ts +185 -0
- package/dist/types/tiny/text.d.ts +4 -0
- package/dist/types/tiny/title-client.d.ts +24 -0
- package/dist/types/tiny/title-protocol.d.ts +74 -0
- package/dist/types/tiny/worker.d.ts +2 -0
- package/dist/types/tools/bash.d.ts +3 -1
- package/dist/types/tools/index.d.ts +7 -3
- package/dist/types/tools/memory-edit.d.ts +40 -0
- package/dist/types/tools/{hindsight-recall.d.ts → memory-recall.d.ts} +6 -6
- package/dist/types/tools/{hindsight-reflect.d.ts → memory-reflect.d.ts} +6 -6
- package/dist/types/tools/memory-render.d.ts +60 -0
- package/dist/types/tools/{hindsight-retain.d.ts → memory-retain.d.ts} +6 -6
- package/dist/types/tools/todo-write.d.ts +8 -0
- package/dist/types/tools/tool-result.d.ts +2 -0
- package/dist/types/utils/title-generator.d.ts +3 -0
- package/package.json +18 -14
- package/scripts/build-binary.ts +1 -0
- package/src/cli/tiny-models-cli.ts +127 -0
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +8 -8
- package/src/commands/tiny-models.ts +36 -0
- package/src/config/model-equivalence.ts +43 -2
- package/src/config/model-id-affixes.ts +64 -0
- package/src/config/model-registry.ts +84 -10
- package/src/config/settings-schema.ts +205 -4
- package/src/edit/hashline/diff.ts +5 -7
- package/src/eval/__tests__/shared-executors.test.ts +36 -0
- package/src/eval/js/shared/local-module-loader.ts +13 -1
- package/src/eval/js/shared/rewrite-imports.ts +31 -26
- package/src/internal-urls/agent-protocol.ts +18 -1
- package/src/internal-urls/artifact-protocol.ts +19 -1
- package/src/internal-urls/docs-index.generated.ts +3 -1
- package/src/internal-urls/local-protocol.ts +14 -1
- package/src/internal-urls/memory-protocol.ts +6 -1
- package/src/internal-urls/omp-protocol.ts +5 -1
- package/src/internal-urls/router.ts +20 -1
- package/src/internal-urls/rule-protocol.ts +8 -1
- package/src/internal-urls/skill-protocol.ts +8 -1
- package/src/internal-urls/types.ts +27 -0
- package/src/lsp/render.ts +1 -1
- package/src/mcp/oauth-flow.ts +2 -2
- package/src/memory-backend/index.ts +1 -0
- package/src/memory-backend/resolve.ts +4 -1
- package/src/memory-backend/types.ts +8 -1
- package/src/mnemosyne/backend.ts +374 -0
- package/src/mnemosyne/config.ts +160 -0
- package/src/mnemosyne/index.ts +3 -0
- package/src/mnemosyne/state.ts +548 -0
- package/src/modes/acp/acp-agent.ts +11 -6
- package/src/modes/components/agent-dashboard.ts +4 -4
- package/src/modes/components/custom-editor.ts +3 -2
- package/src/modes/components/diff.ts +2 -2
- package/src/modes/components/extensions/extension-list.ts +3 -2
- package/src/modes/components/footer.ts +5 -6
- package/src/modes/components/history-search.ts +3 -3
- package/src/modes/components/hook-selector.ts +94 -8
- package/src/modes/components/index.ts +1 -0
- package/src/modes/components/mcp-add-wizard.ts +3 -3
- package/src/modes/components/model-selector.ts +5 -4
- package/src/modes/components/oauth-selector.ts +3 -3
- package/src/modes/components/session-observer-overlay.ts +19 -13
- package/src/modes/components/session-selector.ts +3 -3
- package/src/modes/components/settings-defs.ts +7 -0
- package/src/modes/components/status-line/context-thresholds.ts +11 -0
- package/src/modes/components/status-line/segments.ts +2 -2
- package/src/modes/components/tiny-title-download-progress.ts +90 -0
- package/src/modes/components/tips.txt +12 -0
- package/src/modes/components/tool-execution.ts +67 -3
- package/src/modes/components/tree-selector.ts +3 -3
- package/src/modes/components/user-message-selector.ts +3 -3
- package/src/modes/components/welcome.ts +55 -1
- package/src/modes/controllers/command-controller.ts +16 -1
- package/src/modes/controllers/extension-ui-controller.ts +3 -1
- package/src/modes/controllers/input-controller.ts +57 -0
- package/src/modes/gradient-highlight.ts +70 -0
- package/src/modes/interactive-mode.ts +58 -109
- package/src/modes/internal-url-autocomplete.ts +143 -0
- package/src/modes/orchestrate.ts +36 -0
- package/src/modes/prompt-action-autocomplete.ts +12 -0
- package/src/modes/ultrathink.ts +9 -53
- package/src/modes/utils/keybinding-matchers.ts +11 -0
- package/src/prompts/system/memory-consolidation-system.md +8 -0
- package/src/prompts/system/memory-extraction-system.md +26 -0
- package/src/prompts/{commands/orchestrate.md → system/orchestrate-notice.md} +5 -16
- package/src/prompts/system/system-prompt.md +2 -0
- package/src/prompts/system/tiny-title-system.md +8 -0
- package/src/prompts/tools/memory-edit.md +8 -0
- package/src/prompts/tools/task.md +4 -7
- package/src/sdk.ts +8 -6
- package/src/session/agent-session.ts +128 -44
- package/src/slash-commands/builtin-registry.ts +10 -1
- package/src/system-prompt.ts +4 -0
- package/src/task/commands.ts +1 -5
- package/src/task/executor.ts +8 -0
- package/src/task/index.ts +2 -0
- package/src/task/render.ts +69 -26
- package/src/tiny/models.ts +217 -0
- package/src/tiny/text.ts +19 -0
- package/src/tiny/title-client.ts +340 -0
- package/src/tiny/title-protocol.ts +51 -0
- package/src/tiny/worker.ts +523 -0
- package/src/tools/bash.ts +58 -16
- package/src/tools/browser/tab-worker.ts +1 -1
- package/src/tools/index.ts +17 -11
- package/src/tools/memory-edit.ts +59 -0
- package/src/tools/memory-recall.ts +100 -0
- package/src/tools/memory-reflect.ts +88 -0
- package/src/tools/memory-render.ts +185 -0
- package/src/tools/memory-retain.ts +91 -0
- package/src/tools/renderers.ts +4 -0
- package/src/tools/todo-write.ts +128 -29
- package/src/tools/tool-result.ts +8 -0
- package/src/utils/title-generator.ts +115 -13
- package/src/tools/hindsight-recall.ts +0 -69
- package/src/tools/hindsight-reflect.ts +0 -58
- package/src/tools/hindsight-retain.ts +0 -57
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from "../../tools/json-tree";
|
|
32
32
|
import { formatExpandHint, replaceTabs, resolveImageOptions, truncateToWidth } from "../../tools/render-utils";
|
|
33
33
|
import { toolRenderers } from "../../tools/renderers";
|
|
34
|
+
import { TODO_WRITE_STRIKE_TOTAL_FRAMES } from "../../tools/todo-write";
|
|
34
35
|
import { renderStatusLine } from "../../tui";
|
|
35
36
|
import { sanitizeWithOptionalSixelPassthrough } from "../../utils/sixel";
|
|
36
37
|
import { renderDiff } from "./diff";
|
|
@@ -163,6 +164,8 @@ export class ToolExecutionComponent extends Container {
|
|
|
163
164
|
// Spinner animation for partial task results
|
|
164
165
|
#spinnerFrame?: number;
|
|
165
166
|
#spinnerInterval?: NodeJS.Timeout;
|
|
167
|
+
// Todo write completion strikethrough reveal animation
|
|
168
|
+
#todoStrikeInterval?: NodeJS.Timeout;
|
|
166
169
|
// Track if args are still being streamed (for edit/write spinner)
|
|
167
170
|
#argsComplete = false;
|
|
168
171
|
#renderState: {
|
|
@@ -251,12 +254,24 @@ export class ToolExecutionComponent extends Container {
|
|
|
251
254
|
effectiveArgs = args;
|
|
252
255
|
}
|
|
253
256
|
|
|
254
|
-
// Coalesce duplicate computes for identical args.
|
|
257
|
+
// Coalesce duplicate computes for identical args. The key pairs the
|
|
258
|
+
// streaming flag with a content hash: the final (args-complete) pass
|
|
259
|
+
// computes an untrimmed diff and must run even when the payload is
|
|
260
|
+
// byte-identical to the last streamed chunk — only `isStreaming` differs,
|
|
261
|
+
// and it flips the trailing-line trim. Without the flag a single-line edit
|
|
262
|
+
// whose trailing payload line never gets a newline stays stuck on the
|
|
263
|
+
// trimmed "no changes" streaming preview and renders no diff. Hashing keeps
|
|
264
|
+
// the retained key tiny instead of holding the whole serialized blob.
|
|
265
|
+
const streamingState = this.#argsComplete ? "final" : "stream";
|
|
255
266
|
let argsKey: string;
|
|
256
267
|
try {
|
|
257
|
-
argsKey = JSON.stringify(effectiveArgs)
|
|
268
|
+
argsKey = `${streamingState}:${Bun.hash(JSON.stringify(effectiveArgs))}`;
|
|
258
269
|
} catch {
|
|
259
|
-
|
|
270
|
+
// effectiveArgs isn't JSON-serializable (exotic value in tool args).
|
|
271
|
+
// The raw streamed JSON is a plain string, so hash that instead of a
|
|
272
|
+
// timestamp — a deterministic key keeps the dedup cache working
|
|
273
|
+
// instead of recomputing (and re-reading the file) on every render.
|
|
274
|
+
argsKey = `${streamingState}:partial:${Bun.hash(partialJson ?? "")}`;
|
|
260
275
|
}
|
|
261
276
|
if (argsKey === this.#editDiffLastArgsKey) return;
|
|
262
277
|
this.#editDiffLastArgsKey = argsKey;
|
|
@@ -304,6 +319,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
304
319
|
this.#argsComplete = true;
|
|
305
320
|
}
|
|
306
321
|
this.#updateSpinnerAnimation();
|
|
322
|
+
this.#updateTodoStrikeAnimation();
|
|
307
323
|
this.#updateDisplay();
|
|
308
324
|
// Convert non-PNG images to PNG for Kitty protocol (async)
|
|
309
325
|
this.#maybeConvertImagesForKitty();
|
|
@@ -379,6 +395,43 @@ export class ToolExecutionComponent extends Container {
|
|
|
379
395
|
}
|
|
380
396
|
}
|
|
381
397
|
|
|
398
|
+
#updateTodoStrikeAnimation(): void {
|
|
399
|
+
if (this.#toolName !== "todo_write" || this.#isPartial || this.#result?.isError) {
|
|
400
|
+
this.#stopTodoStrikeAnimation();
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const completedTasks = (this.#result?.details as { completedTasks?: unknown[] } | undefined)?.completedTasks;
|
|
404
|
+
if (!completedTasks || completedTasks.length === 0) {
|
|
405
|
+
this.#stopTodoStrikeAnimation();
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (this.#todoStrikeInterval) return;
|
|
409
|
+
|
|
410
|
+
this.#spinnerFrame = 0;
|
|
411
|
+
this.#renderState.spinnerFrame = 0;
|
|
412
|
+
this.#todoStrikeInterval = setInterval(() => {
|
|
413
|
+
const nextFrame = (this.#spinnerFrame ?? 0) + 1;
|
|
414
|
+
if (nextFrame > TODO_WRITE_STRIKE_TOTAL_FRAMES) {
|
|
415
|
+
this.#stopTodoStrikeAnimation();
|
|
416
|
+
} else {
|
|
417
|
+
this.#spinnerFrame = nextFrame;
|
|
418
|
+
this.#renderState.spinnerFrame = nextFrame;
|
|
419
|
+
}
|
|
420
|
+
this.#ui.requestRender();
|
|
421
|
+
}, 65);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
#stopTodoStrikeAnimation(): void {
|
|
425
|
+
if (this.#todoStrikeInterval) {
|
|
426
|
+
clearInterval(this.#todoStrikeInterval);
|
|
427
|
+
this.#todoStrikeInterval = undefined;
|
|
428
|
+
}
|
|
429
|
+
if (!this.#spinnerInterval) {
|
|
430
|
+
this.#spinnerFrame = undefined;
|
|
431
|
+
this.#renderState.spinnerFrame = undefined;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
382
435
|
/**
|
|
383
436
|
* Stop spinner animation and cleanup resources.
|
|
384
437
|
*/
|
|
@@ -388,6 +441,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
388
441
|
this.#spinnerInterval = undefined;
|
|
389
442
|
this.#spinnerFrame = undefined;
|
|
390
443
|
}
|
|
444
|
+
this.#stopTodoStrikeAnimation();
|
|
391
445
|
this.#editDiffAbort?.abort();
|
|
392
446
|
this.#editDiffAbort = undefined;
|
|
393
447
|
}
|
|
@@ -428,6 +482,11 @@ export class ToolExecutionComponent extends Container {
|
|
|
428
482
|
const inline = Boolean((tool as { inline?: boolean }).inline);
|
|
429
483
|
this.#contentBox.setBgFn(inline ? undefined : bgFn);
|
|
430
484
|
this.#contentBox.clear();
|
|
485
|
+
// Mirror the built-in renderer branch so custom renderers (notably the
|
|
486
|
+
// task tool, whose live instance routes through here) receive the same
|
|
487
|
+
// render context — e.g. the `hasResult` flag that suppresses the task
|
|
488
|
+
// call preview once result lines exist.
|
|
489
|
+
this.#renderState.renderContext = this.#buildRenderContext();
|
|
431
490
|
|
|
432
491
|
// Render call component
|
|
433
492
|
const shouldRenderCall = !this.#result || !mergeCallAndResult;
|
|
@@ -696,6 +755,11 @@ export class ToolExecutionComponent extends Container {
|
|
|
696
755
|
context.output = output;
|
|
697
756
|
context.expanded = this.#expanded;
|
|
698
757
|
context.previewLines = EVAL_DEFAULT_PREVIEW_LINES;
|
|
758
|
+
} else if (this.#toolName === "task") {
|
|
759
|
+
// Once a result snapshot exists the task renderer's `renderResult`
|
|
760
|
+
// draws every dispatched agent as a progress/result line, so tell
|
|
761
|
+
// `renderCall` to drop its duplicate streaming preview list.
|
|
762
|
+
context.hasResult = Boolean(this.#result);
|
|
699
763
|
} else if (isEditLikeToolName(this.#toolName)) {
|
|
700
764
|
context.editMode = this.#editMode;
|
|
701
765
|
const previews = this.#editDiffPreview;
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "@oh-my-pi/pi-tui";
|
|
13
13
|
import type { TreeFilterMode } from "../../config/settings-schema";
|
|
14
14
|
import { theme } from "../../modes/theme/theme";
|
|
15
|
-
import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
|
|
15
|
+
import { matchesAppInterrupt, matchesSelectDown, matchesSelectUp } from "../../modes/utils/keybinding-matchers";
|
|
16
16
|
import type { SessionTreeNode } from "../../session/session-manager";
|
|
17
17
|
import { shortenPath } from "../../tools/render-utils";
|
|
18
18
|
import { toPathList } from "../../tools/search";
|
|
@@ -718,9 +718,9 @@ class TreeList implements Component {
|
|
|
718
718
|
}
|
|
719
719
|
|
|
720
720
|
handleInput(keyData: string): void {
|
|
721
|
-
if (
|
|
721
|
+
if (matchesSelectUp(keyData)) {
|
|
722
722
|
this.#selectedIndex = this.#selectedIndex === 0 ? this.#filteredNodes.length - 1 : this.#selectedIndex - 1;
|
|
723
|
-
} else if (
|
|
723
|
+
} else if (matchesSelectDown(keyData)) {
|
|
724
724
|
this.#selectedIndex = this.#selectedIndex === this.#filteredNodes.length - 1 ? 0 : this.#selectedIndex + 1;
|
|
725
725
|
} else if (matchesKey(keyData, "left")) {
|
|
726
726
|
// Page up
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Component, Container, matchesKey, Spacer, Text, truncateToWidth } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import { theme } from "../../modes/theme/theme";
|
|
3
|
-
import { matchesSelectCancel } from "../../modes/utils/keybinding-matchers";
|
|
3
|
+
import { matchesSelectCancel, matchesSelectDown, matchesSelectUp } from "../../modes/utils/keybinding-matchers";
|
|
4
4
|
import { DynamicBorder } from "./dynamic-border";
|
|
5
5
|
|
|
6
6
|
interface UserMessageItem {
|
|
@@ -78,11 +78,11 @@ class UserMessageList implements Component {
|
|
|
78
78
|
|
|
79
79
|
handleInput(keyData: string): void {
|
|
80
80
|
// Up arrow - go to previous (older) message, wrap to bottom when at top
|
|
81
|
-
if (
|
|
81
|
+
if (matchesSelectUp(keyData)) {
|
|
82
82
|
this.#selectedIndex = this.#selectedIndex === 0 ? this.messages.length - 1 : this.#selectedIndex - 1;
|
|
83
83
|
}
|
|
84
84
|
// Down arrow - go to next (newer) message, wrap to top when at bottom
|
|
85
|
-
else if (
|
|
85
|
+
else if (matchesSelectDown(keyData)) {
|
|
86
86
|
this.#selectedIndex = this.#selectedIndex === this.messages.length - 1 ? 0 : this.#selectedIndex + 1;
|
|
87
87
|
}
|
|
88
88
|
// Enter - select message and branch
|
|
@@ -1,6 +1,45 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type Component,
|
|
3
|
+
padding,
|
|
4
|
+
replaceTabs,
|
|
5
|
+
TERMINAL,
|
|
6
|
+
truncateToWidth,
|
|
7
|
+
visibleWidth,
|
|
8
|
+
wrapTextWithAnsi,
|
|
9
|
+
} from "@oh-my-pi/pi-tui";
|
|
2
10
|
import { APP_NAME } from "@oh-my-pi/pi-utils";
|
|
3
11
|
import { theme } from "../../modes/theme/theme";
|
|
12
|
+
import tipsText from "./tips.txt" with { type: "text" };
|
|
13
|
+
|
|
14
|
+
/** Tips embedded at build time, one per line; blanks dropped. */
|
|
15
|
+
const TIPS: readonly string[] = tipsText
|
|
16
|
+
.split("\n")
|
|
17
|
+
.map(line => line.trim())
|
|
18
|
+
.filter(line => line.length > 0);
|
|
19
|
+
|
|
20
|
+
export function renderWelcomeTip(tip: string, boxWidth: number): string[] {
|
|
21
|
+
const label = "Tip: ";
|
|
22
|
+
const labelWidth = visibleWidth(label);
|
|
23
|
+
const bodyBudget = boxWidth - 1 - labelWidth; // 1 = leading indent
|
|
24
|
+
if (bodyBudget < 8) return [];
|
|
25
|
+
|
|
26
|
+
const wrappedBody = wrapTextWithAnsi(replaceTabs(tip), bodyBudget);
|
|
27
|
+
if (wrappedBody.length === 0) return [];
|
|
28
|
+
|
|
29
|
+
const encoding = TERMINAL.trueColor ? "ansi-16m" : "ansi-256";
|
|
30
|
+
const purple = Bun.color("#b48cff", encoding) ?? "";
|
|
31
|
+
const lightBlue = Bun.color("#9ccfff", encoding) ?? "";
|
|
32
|
+
const italic = "\x1b[3m";
|
|
33
|
+
const dim = "\x1b[2m";
|
|
34
|
+
const reset = "\x1b[0m";
|
|
35
|
+
const continuationIndent = padding(labelWidth);
|
|
36
|
+
|
|
37
|
+
return wrappedBody.map((body, index) =>
|
|
38
|
+
index === 0
|
|
39
|
+
? ` ${italic}${purple}${label}${dim}${lightBlue}${body}${reset}`
|
|
40
|
+
: ` ${italic}${continuationIndent}${dim}${lightBlue}${body}${reset}`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
4
43
|
|
|
5
44
|
export interface RecentSession {
|
|
6
45
|
name: string;
|
|
@@ -19,6 +58,8 @@ export interface LspServerInfo {
|
|
|
19
58
|
export class WelcomeComponent implements Component {
|
|
20
59
|
#animStart: number | null = null;
|
|
21
60
|
#animTimer: ReturnType<typeof setInterval> | null = null;
|
|
61
|
+
/** Tip chosen once per instance so re-renders (intro, LSP updates) don't shuffle it. */
|
|
62
|
+
readonly #tip: string | undefined = TIPS.length > 0 ? TIPS[Math.floor(Math.random() * TIPS.length)] : undefined;
|
|
22
63
|
|
|
23
64
|
constructor(
|
|
24
65
|
private readonly version: string,
|
|
@@ -212,9 +253,22 @@ export class WelcomeComponent implements Component {
|
|
|
212
253
|
lines.push(bl + h.repeat(leftCol) + br);
|
|
213
254
|
}
|
|
214
255
|
|
|
256
|
+
// Randomly picked tip, rendered directly beneath the box.
|
|
257
|
+
lines.push(...this.#renderTip(boxWidth));
|
|
258
|
+
|
|
215
259
|
return lines;
|
|
216
260
|
}
|
|
217
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Render the per-instance tip line: a purple "Tip:" label followed by the
|
|
264
|
+
* tip body in dimmed light blue, the whole line italicized. Returns `[]`
|
|
265
|
+
* when no tip is available or the box is too narrow to be useful.
|
|
266
|
+
*/
|
|
267
|
+
#renderTip(boxWidth: number): string[] {
|
|
268
|
+
if (!this.#tip) return [];
|
|
269
|
+
return renderWelcomeTip(this.#tip, boxWidth);
|
|
270
|
+
}
|
|
271
|
+
|
|
218
272
|
/** Center text within a given width */
|
|
219
273
|
#centerText(text: string, width: number): string {
|
|
220
274
|
const visLen = visibleWidth(text);
|
|
@@ -620,12 +620,27 @@ export class CommandController {
|
|
|
620
620
|
return;
|
|
621
621
|
}
|
|
622
622
|
|
|
623
|
+
if (action === "stats" || action === "diagnose") {
|
|
624
|
+
const hook = action === "stats" ? backend.stats : backend.diagnose;
|
|
625
|
+
try {
|
|
626
|
+
const payload = await hook?.(agentDir, this.ctx.sessionManager.getCwd(), this.ctx.session);
|
|
627
|
+
if (!payload) {
|
|
628
|
+
this.ctx.showWarning(`Memory ${action} is not available for the ${backend.id} backend.`);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
showMarkdownPanel(this.ctx, `Memory ${action === "stats" ? "Stats" : "Diagnostics"}`, payload);
|
|
632
|
+
} catch (error) {
|
|
633
|
+
this.ctx.showError(`Memory ${action} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
634
|
+
}
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
|
|
623
638
|
if (action === "mm") {
|
|
624
639
|
await this.#handleMentalModelsSubcommand(argumentText);
|
|
625
640
|
return;
|
|
626
641
|
}
|
|
627
642
|
|
|
628
|
-
this.ctx.showError("Usage: /memory <view|clear|reset|enqueue|rebuild|mm ...>");
|
|
643
|
+
this.ctx.showError("Usage: /memory <view|stats|diagnose|clear|reset|enqueue|rebuild|mm ...>");
|
|
629
644
|
}
|
|
630
645
|
|
|
631
646
|
async #handleMentalModelsSubcommand(argumentText: string): Promise<void> {
|
|
@@ -19,7 +19,7 @@ import type {
|
|
|
19
19
|
import { getSessionSlashCommands } from "../../extensibility/extensions/get-commands-handler";
|
|
20
20
|
import { HookEditorComponent } from "../../modes/components/hook-editor";
|
|
21
21
|
import { HookInputComponent } from "../../modes/components/hook-input";
|
|
22
|
-
import { HookSelectorComponent } from "../../modes/components/hook-selector";
|
|
22
|
+
import { HookSelectorComponent, type HookSelectorSlider } from "../../modes/components/hook-selector";
|
|
23
23
|
import { getAvailableThemesWithPaths, getThemeByName, setTheme, type Theme, theme } from "../../modes/theme/theme";
|
|
24
24
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
25
25
|
import { setSessionTerminalTitle, setTerminalTitle } from "../../utils/title-generator";
|
|
@@ -583,6 +583,7 @@ export class ExtensionUiController {
|
|
|
583
583
|
title: string,
|
|
584
584
|
options: string[],
|
|
585
585
|
dialogOptions?: ExtensionUIDialogOptions,
|
|
586
|
+
extra?: { slider?: HookSelectorSlider },
|
|
586
587
|
): Promise<string | undefined> {
|
|
587
588
|
const { promise, finish, attachAbort } = this.#createHookDialogState(
|
|
588
589
|
() => this.hideHookSelector(),
|
|
@@ -623,6 +624,7 @@ export class ExtensionUiController {
|
|
|
623
624
|
tui: this.ctx.ui,
|
|
624
625
|
outline: dialogOptions?.outline,
|
|
625
626
|
maxVisible,
|
|
627
|
+
slider: extra?.slider,
|
|
626
628
|
},
|
|
627
629
|
);
|
|
628
630
|
this.ctx.editorContainer.clear();
|
|
@@ -3,6 +3,7 @@ import { type AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
|
3
3
|
import type { AutocompleteProvider, SlashCommand } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { $env, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import { isSettingsInitialized, settings } from "../../config/settings";
|
|
6
|
+
import { TinyTitleDownloadProgressComponent } from "../../modes/components/tiny-title-download-progress";
|
|
6
7
|
import { expandEmoticons } from "../../modes/emoji-autocomplete";
|
|
7
8
|
import { createPromptActionAutocompleteProvider } from "../../modes/prompt-action-autocomplete";
|
|
8
9
|
import { theme } from "../../modes/theme/theme";
|
|
@@ -10,6 +11,9 @@ import type { InteractiveModeContext } from "../../modes/types";
|
|
|
10
11
|
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
11
12
|
import { SKILL_PROMPT_MESSAGE_TYPE, type SkillPromptDetails } from "../../session/messages";
|
|
12
13
|
import { executeBuiltinSlashCommand } from "../../slash-commands/builtin-registry";
|
|
14
|
+
import { isTinyTitleLocalModelKey } from "../../tiny/models";
|
|
15
|
+
import { tinyTitleClient } from "../../tiny/title-client";
|
|
16
|
+
import type { TinyTitleProgressEvent } from "../../tiny/title-protocol";
|
|
13
17
|
import { copyToClipboard, readImageFromClipboard } from "../../utils/clipboard";
|
|
14
18
|
import { getEditorCommand, openInEditor } from "../../utils/external-editor";
|
|
15
19
|
import { ensureSupportedImageInput } from "../../utils/image-loading";
|
|
@@ -24,9 +28,61 @@ function isExpandable(obj: unknown): obj is Expandable {
|
|
|
24
28
|
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
const TINY_TITLE_PROGRESS_DONE_TTL_MS = 3_000;
|
|
32
|
+
// A cached model fires its file-load events in a short burst and then goes silent
|
|
33
|
+
// while onnxruntime builds the session; a genuine download keeps streaming progress
|
|
34
|
+
// events for seconds. Only reveal the bar once a still-incomplete event arrives after
|
|
35
|
+
// this grace window, so an already-downloaded model never flashes the bar.
|
|
36
|
+
const TINY_TITLE_PROGRESS_REVEAL_DELAY_MS = 1_000;
|
|
37
|
+
|
|
27
38
|
export class InputController {
|
|
28
39
|
constructor(private ctx: InteractiveModeContext) {}
|
|
29
40
|
|
|
41
|
+
#showTinyTitleDownloadProgress(modelKey: string): void {
|
|
42
|
+
if (!isTinyTitleLocalModelKey(modelKey) || this.ctx.isBackgrounded) return;
|
|
43
|
+
const component = new TinyTitleDownloadProgressComponent(modelKey);
|
|
44
|
+
let added = false;
|
|
45
|
+
let disposed = false;
|
|
46
|
+
let removeTimer: NodeJS.Timeout | undefined;
|
|
47
|
+
const remove = (): void => {
|
|
48
|
+
if (disposed) return;
|
|
49
|
+
disposed = true;
|
|
50
|
+
unsubscribe();
|
|
51
|
+
if (removeTimer) {
|
|
52
|
+
clearTimeout(removeTimer);
|
|
53
|
+
removeTimer = undefined;
|
|
54
|
+
}
|
|
55
|
+
if (added) {
|
|
56
|
+
this.ctx.chatContainer.removeChild(component);
|
|
57
|
+
this.ctx.ui.requestRender();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const scheduleRemove = (): void => {
|
|
61
|
+
if (removeTimer) clearTimeout(removeTimer);
|
|
62
|
+
removeTimer = setTimeout(remove, TINY_TITLE_PROGRESS_DONE_TTL_MS);
|
|
63
|
+
removeTimer.unref?.();
|
|
64
|
+
};
|
|
65
|
+
let revealAt = 0;
|
|
66
|
+
const update = (event: TinyTitleProgressEvent): void => {
|
|
67
|
+
if (disposed || event.modelKey !== modelKey) return;
|
|
68
|
+
component.update(event);
|
|
69
|
+
if (revealAt === 0) revealAt = performance.now() + TINY_TITLE_PROGRESS_REVEAL_DELAY_MS;
|
|
70
|
+
const complete = component.isComplete();
|
|
71
|
+
// Reveal only for a download still in flight past the grace window. Cache hits
|
|
72
|
+
// either complete or fall silent (onnx init emits no events) before this fires.
|
|
73
|
+
if (!added && !complete && performance.now() >= revealAt) {
|
|
74
|
+
this.ctx.chatContainer.addChild(component);
|
|
75
|
+
added = true;
|
|
76
|
+
}
|
|
77
|
+
if (added) this.ctx.ui.requestRender();
|
|
78
|
+
if (complete) {
|
|
79
|
+
if (added) scheduleRemove();
|
|
80
|
+
else remove();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const unsubscribe = tinyTitleClient.onProgress(update);
|
|
84
|
+
}
|
|
85
|
+
|
|
30
86
|
setupKeyHandlers(): void {
|
|
31
87
|
this.ctx.editor.setActionKeys("app.interrupt", this.ctx.keybindings.getKeys("app.interrupt"));
|
|
32
88
|
this.ctx.editor.shouldBypassAutocompleteOnEscape = () =>
|
|
@@ -329,6 +385,7 @@ export class InputController {
|
|
|
329
385
|
// Generate session title on first message
|
|
330
386
|
const hasUserMessages = this.ctx.session.messages.some((m: AgentMessage) => m.role === "user");
|
|
331
387
|
if (!hasUserMessages && !this.ctx.sessionManager.getSessionName() && !$env.PI_NO_TITLE) {
|
|
388
|
+
this.#showTinyTitleDownloadProgress(this.ctx.settings.get("providers.tinyModel"));
|
|
332
389
|
const registry = this.ctx.session.modelRegistry;
|
|
333
390
|
generateSessionTitle(
|
|
334
391
|
text,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { theme } from "./theme/theme";
|
|
2
|
+
|
|
3
|
+
const FG_RESET = "\x1b[39m";
|
|
4
|
+
|
|
5
|
+
/** Declarative spec for {@link createGradientHighlighter}. */
|
|
6
|
+
export interface GradientHighlightSpec {
|
|
7
|
+
/** Cheap, stateless presence probe used to skip the boundary regex on most lines. Must be non-global. */
|
|
8
|
+
probe: RegExp;
|
|
9
|
+
/** Global, word-bounded match regex walked by `.replace`. */
|
|
10
|
+
highlight: RegExp;
|
|
11
|
+
/** Number of color stops swept across the gradient. */
|
|
12
|
+
stops: number;
|
|
13
|
+
/** Maps a normalized position `t` in [0, 1) to an HSL hue in degrees. */
|
|
14
|
+
hue: (t: number) => number;
|
|
15
|
+
/** HSL saturation percentage. Default 90. */
|
|
16
|
+
saturation?: number;
|
|
17
|
+
/** HSL lightness percentage. Default 62. */
|
|
18
|
+
lightness?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Build a stateless highlighter that paints each standalone match of `highlight`
|
|
23
|
+
* with a smooth HSL gradient for editor display. The returned function adds only
|
|
24
|
+
* zero-width SGR escapes — the visible width is unchanged — and returns the input
|
|
25
|
+
* untouched when `probe` does not match. The palette is compiled lazily and
|
|
26
|
+
* memoized per active color mode.
|
|
27
|
+
*/
|
|
28
|
+
export function createGradientHighlighter(spec: GradientHighlightSpec): (text: string) => string {
|
|
29
|
+
const { probe, highlight, stops, hue, saturation = 90, lightness = 62 } = spec;
|
|
30
|
+
|
|
31
|
+
let cachedMode: string | undefined;
|
|
32
|
+
let cachedPalette: readonly string[] | undefined;
|
|
33
|
+
|
|
34
|
+
/** Gradient foreground escapes for the active color mode, compiled once per mode. */
|
|
35
|
+
const palette = (): readonly string[] => {
|
|
36
|
+
const mode = theme.getColorMode();
|
|
37
|
+
if (cachedPalette && cachedMode === mode) return cachedPalette;
|
|
38
|
+
const format = mode === "truecolor" ? "ansi-16m" : "ansi-256";
|
|
39
|
+
const next: string[] = [];
|
|
40
|
+
for (let i = 0; i < stops; i++) {
|
|
41
|
+
next.push(Bun.color(`hsl(${Math.round(hue(i / stops))}, ${saturation}%, ${lightness}%)`, format) ?? "");
|
|
42
|
+
}
|
|
43
|
+
cachedMode = mode;
|
|
44
|
+
cachedPalette = next;
|
|
45
|
+
return next;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** Paint each character of `word` with the next gradient stop, resetting fg after. */
|
|
49
|
+
const paint = (word: string): string => {
|
|
50
|
+
const stopsArr = palette();
|
|
51
|
+
const n = word.length;
|
|
52
|
+
let out = "";
|
|
53
|
+
let prev = "";
|
|
54
|
+
for (let i = 0; i < n; i++) {
|
|
55
|
+
const color = stopsArr[Math.floor((i / n) * stopsArr.length)] ?? stopsArr[0] ?? "";
|
|
56
|
+
// Coalesce consecutive characters that resolve to the same stop.
|
|
57
|
+
if (color !== prev) {
|
|
58
|
+
out += color;
|
|
59
|
+
prev = color;
|
|
60
|
+
}
|
|
61
|
+
out += word[i];
|
|
62
|
+
}
|
|
63
|
+
return `${out}${FG_RESET}`;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return (text: string): string => {
|
|
67
|
+
if (!probe.test(text)) return text;
|
|
68
|
+
return text.replace(highlight, paint);
|
|
69
|
+
};
|
|
70
|
+
}
|