@oh-my-pi/pi-coding-agent 15.5.13 → 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 +77 -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/model-registry.d.ts +1 -1
- package/dist/types/config/models-config-schema.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +233 -17
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
- package/dist/types/eval/__tests__/llm-bridge.test.d.ts +1 -0
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +16 -1
- package/dist/types/eval/llm-bridge.d.ts +25 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +15 -0
- 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/theme/theme.d.ts +2 -1
- 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 +35 -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 -4
- 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 +166 -8
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +206 -14
- package/src/edit/hashline/diff.ts +5 -7
- package/src/eval/__tests__/llm-bridge.test.ts +297 -0
- 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/prelude.txt +8 -0
- package/src/eval/js/shared/rewrite-imports.ts +31 -26
- package/src/eval/js/tool-bridge.ts +4 -0
- package/src/eval/llm-bridge.ts +181 -0
- package/src/eval/py/prelude.py +52 -31
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +0 -13
- package/src/extensibility/plugins/legacy-pi-compat.ts +60 -23
- 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 +5 -4
- 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/main.ts +4 -0
- 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 +124 -26
- 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/presets.ts +1 -0
- package/src/modes/components/status-line/segments.ts +25 -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 +80 -196
- 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/theme/theme.ts +7 -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/eval.md +2 -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 +147 -44
- package/src/session/session-manager.ts +47 -0
- 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/eval.ts +24 -48
- package/src/tools/index.ts +17 -15
- 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 -2
- 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/dist/types/tools/calculator.d.ts +0 -77
- package/src/prompts/tools/calculator.md +0 -10
- package/src/tools/calculator.ts +0 -541
- package/src/tools/hindsight-recall.ts +0 -69
- package/src/tools/hindsight-reflect.ts +0 -58
- package/src/tools/hindsight-retain.ts +0 -57
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import * as z from "zod/v4";
|
|
3
|
+
import retainDescription from "../prompts/tools/retain.md" with { type: "text" };
|
|
4
|
+
import type { ToolSession } from ".";
|
|
5
|
+
|
|
6
|
+
const memoryRetainSchema = z.object({
|
|
7
|
+
items: z
|
|
8
|
+
.array(
|
|
9
|
+
z.object({
|
|
10
|
+
content: z.string().describe("information to remember"),
|
|
11
|
+
context: z.string().describe("source context").optional(),
|
|
12
|
+
}),
|
|
13
|
+
)
|
|
14
|
+
.min(1)
|
|
15
|
+
.describe("memories to retain"),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export type MemoryRetainParams = z.infer<typeof memoryRetainSchema>;
|
|
19
|
+
export class MemoryRetainTool implements AgentTool<typeof memoryRetainSchema> {
|
|
20
|
+
readonly name = "retain";
|
|
21
|
+
readonly approval = "read" as const;
|
|
22
|
+
readonly label = "Retain";
|
|
23
|
+
readonly description = retainDescription;
|
|
24
|
+
readonly parameters = memoryRetainSchema;
|
|
25
|
+
readonly strict = true;
|
|
26
|
+
readonly loadMode = "discoverable";
|
|
27
|
+
readonly summary = "Store important facts in long-term memory";
|
|
28
|
+
|
|
29
|
+
constructor(private readonly session: ToolSession) {}
|
|
30
|
+
|
|
31
|
+
static createIf(session: ToolSession): MemoryRetainTool | null {
|
|
32
|
+
const backend = session.settings.get("memory.backend");
|
|
33
|
+
if (backend !== "hindsight" && backend !== "mnemosyne") return null;
|
|
34
|
+
return new MemoryRetainTool(session);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async execute(_id: string, params: MemoryRetainParams): Promise<AgentToolResult> {
|
|
38
|
+
const backend = this.session.settings.get("memory.backend");
|
|
39
|
+
if (backend === "mnemosyne") {
|
|
40
|
+
const state = this.session.getMnemosyneSessionState?.();
|
|
41
|
+
if (!state) {
|
|
42
|
+
throw new Error("Mnemosyne backend is not initialised for this session.");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const item of params.items) {
|
|
46
|
+
state.rememberScoped(item.content, {
|
|
47
|
+
source: "coding-agent-retain",
|
|
48
|
+
importance: 0.75,
|
|
49
|
+
metadata: {
|
|
50
|
+
session_id: state.sessionId,
|
|
51
|
+
cwd: state.session.sessionManager.getCwd(),
|
|
52
|
+
context: item.context ?? null,
|
|
53
|
+
tool: "retain",
|
|
54
|
+
},
|
|
55
|
+
scope: "bank",
|
|
56
|
+
extract: true,
|
|
57
|
+
extractEntities: true,
|
|
58
|
+
veracity: "tool",
|
|
59
|
+
memoryType: "fact",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const count = params.items.length;
|
|
64
|
+
const noun = count === 1 ? "memory" : "memories";
|
|
65
|
+
return {
|
|
66
|
+
content: [{ type: "text", text: `${count} ${noun} stored.` }],
|
|
67
|
+
details: { count },
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const state = this.session.getHindsightSessionState?.();
|
|
72
|
+
if (!state) {
|
|
73
|
+
throw new Error("Hindsight backend is not initialised for this session.");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Push every item onto the session-owned queue and return immediately.
|
|
77
|
+
// The queue flushes either when it reaches its batch threshold or when
|
|
78
|
+
// its debounce timer fires. If the eventual batch fails, the queue
|
|
79
|
+
// surfaces a UI-only warning notice — the LLM is not informed.
|
|
80
|
+
for (const item of params.items) {
|
|
81
|
+
state.enqueueRetain(item.content, item.context);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const count = params.items.length;
|
|
85
|
+
const noun = count === 1 ? "memory" : "memories";
|
|
86
|
+
return {
|
|
87
|
+
content: [{ type: "text", text: `${count} ${noun} queued.` }],
|
|
88
|
+
details: { count },
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/tools/renderers.ts
CHANGED
|
@@ -16,13 +16,13 @@ import { astEditToolRenderer } from "./ast-edit";
|
|
|
16
16
|
import { astGrepToolRenderer } from "./ast-grep";
|
|
17
17
|
import { bashToolRenderer } from "./bash";
|
|
18
18
|
import { browserToolRenderer } from "./browser/render";
|
|
19
|
-
import { calculatorToolRenderer } from "./calculator";
|
|
20
19
|
import { debugToolRenderer } from "./debug";
|
|
21
20
|
import { evalToolRenderer } from "./eval";
|
|
22
21
|
import { findToolRenderer } from "./find";
|
|
23
22
|
import { githubToolRenderer } from "./gh-renderer";
|
|
24
23
|
import { inspectImageToolRenderer } from "./inspect-image-renderer";
|
|
25
24
|
import { jobToolRenderer } from "./job";
|
|
25
|
+
import { recallToolRenderer, reflectToolRenderer, retainToolRenderer } from "./memory-render";
|
|
26
26
|
import { readToolRenderer } from "./read";
|
|
27
27
|
import { recipeToolRenderer } from "./recipe/render";
|
|
28
28
|
import { resolveToolRenderer } from "./resolve";
|
|
@@ -54,7 +54,6 @@ export const toolRenderers: Record<string, ToolRenderer> = {
|
|
|
54
54
|
recipe: recipeToolRenderer as ToolRenderer,
|
|
55
55
|
debug: debugToolRenderer as ToolRenderer,
|
|
56
56
|
eval: evalToolRenderer as ToolRenderer,
|
|
57
|
-
calc: calculatorToolRenderer as ToolRenderer,
|
|
58
57
|
edit: editToolRenderer as ToolRenderer,
|
|
59
58
|
apply_patch: editToolRenderer as ToolRenderer,
|
|
60
59
|
find: findToolRenderer as ToolRenderer,
|
|
@@ -64,6 +63,9 @@ export const toolRenderers: Record<string, ToolRenderer> = {
|
|
|
64
63
|
read: readToolRenderer as ToolRenderer,
|
|
65
64
|
job: jobToolRenderer as ToolRenderer,
|
|
66
65
|
resolve: resolveToolRenderer as ToolRenderer,
|
|
66
|
+
retain: retainToolRenderer as ToolRenderer,
|
|
67
|
+
recall: recallToolRenderer as ToolRenderer,
|
|
68
|
+
reflect: reflectToolRenderer as ToolRenderer,
|
|
67
69
|
search_tool_bm25: searchToolBm25Renderer as ToolRenderer,
|
|
68
70
|
ssh: sshToolRenderer as ToolRenderer,
|
|
69
71
|
task: taskToolRenderer as ToolRenderer,
|
package/src/tools/todo-write.ts
CHANGED
|
@@ -35,9 +35,15 @@ export interface TodoPhase {
|
|
|
35
35
|
tasks: TodoItem[];
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
export interface TodoCompletionTransition {
|
|
39
|
+
phase: string;
|
|
40
|
+
content: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
38
43
|
export interface TodoWriteToolDetails {
|
|
39
44
|
phases: TodoPhase[];
|
|
40
45
|
storage: "session" | "memory";
|
|
46
|
+
completedTasks?: TodoCompletionTransition[];
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
// =============================================================================
|
|
@@ -97,6 +103,31 @@ function clonePhases(phases: TodoPhase[]): TodoPhase[] {
|
|
|
97
103
|
return phases.map(phase => ({ name: phase.name, tasks: phase.tasks.map(cloneTask) }));
|
|
98
104
|
}
|
|
99
105
|
|
|
106
|
+
function todoTransitionKey(phase: string, content: string): string {
|
|
107
|
+
return `${phase}\u0000${content}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getCompletionTransitions(previous: TodoPhase[], updated: TodoPhase[]): TodoCompletionTransition[] {
|
|
111
|
+
const previousStatuses = new Map<string, TodoStatus>();
|
|
112
|
+
for (const phase of previous) {
|
|
113
|
+
for (const task of phase.tasks) {
|
|
114
|
+
previousStatuses.set(todoTransitionKey(phase.name, task.content), task.status);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const transitions: TodoCompletionTransition[] = [];
|
|
119
|
+
for (const phase of updated) {
|
|
120
|
+
for (const task of phase.tasks) {
|
|
121
|
+
if (task.status !== "completed") continue;
|
|
122
|
+
const previousStatus = previousStatuses.get(todoTransitionKey(phase.name, task.content));
|
|
123
|
+
if (previousStatus && previousStatus !== "completed") {
|
|
124
|
+
transitions.push({ phase: phase.name, content: task.content });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return transitions;
|
|
129
|
+
}
|
|
130
|
+
|
|
100
131
|
function normalizeInProgressTask(phases: TodoPhase[]): void {
|
|
101
132
|
const orderedTasks = phases.flatMap(phase => phase.tasks);
|
|
102
133
|
if (orderedTasks.length === 0) return;
|
|
@@ -577,13 +608,16 @@ export class TodoWriteTool implements AgentTool<typeof todoWriteSchema, TodoWrit
|
|
|
577
608
|
_context?: AgentToolContext,
|
|
578
609
|
): Promise<AgentToolResult<TodoWriteToolDetails>> {
|
|
579
610
|
const previousPhases = clonePhases(this.session.getTodoPhases?.() ?? []);
|
|
580
|
-
const { phases: updated, errors } = applyParams(previousPhases, params);
|
|
611
|
+
const { phases: updated, errors } = applyParams(clonePhases(previousPhases), params);
|
|
612
|
+
const completedTasks = getCompletionTransitions(previousPhases, updated);
|
|
581
613
|
this.session.setTodoPhases?.(updated);
|
|
582
614
|
const storage = this.session.getSessionFile() ? "session" : "memory";
|
|
615
|
+
const details: TodoWriteToolDetails = { phases: updated, storage };
|
|
616
|
+
if (completedTasks.length > 0) details.completedTasks = completedTasks;
|
|
583
617
|
|
|
584
618
|
return {
|
|
585
619
|
content: [{ type: "text", text: formatSummary(updated, errors) }],
|
|
586
|
-
details
|
|
620
|
+
details,
|
|
587
621
|
isError: errors.length > 0 ? true : undefined,
|
|
588
622
|
};
|
|
589
623
|
}
|
|
@@ -667,16 +701,55 @@ function noteMarker(count: number, uiTheme: Theme): string {
|
|
|
667
701
|
return uiTheme.fg("dim", chalk.italic(` \u207a${toSuperscript(count)}`));
|
|
668
702
|
}
|
|
669
703
|
|
|
670
|
-
|
|
704
|
+
export const TODO_WRITE_STRIKE_HOLD_FRAMES = 2;
|
|
705
|
+
export const TODO_WRITE_STRIKE_REVEAL_FRAMES = 12;
|
|
706
|
+
export const TODO_WRITE_STRIKE_TOTAL_FRAMES = TODO_WRITE_STRIKE_HOLD_FRAMES + TODO_WRITE_STRIKE_REVEAL_FRAMES;
|
|
707
|
+
const EMPTY_COMPLETION_KEYS = new Set<string>();
|
|
708
|
+
const STRIKE_START = "\x1b[9m";
|
|
709
|
+
const STRIKE_END = "\x1b[29m";
|
|
710
|
+
|
|
711
|
+
function strikethroughText(text: string): string {
|
|
712
|
+
return `${STRIKE_START}${text}${STRIKE_END}`;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function partialStrikethrough(text: string, visibleChars: number): string {
|
|
716
|
+
if (visibleChars <= 0) return text;
|
|
717
|
+
const chars = [...text];
|
|
718
|
+
if (visibleChars >= chars.length) return strikethroughText(text);
|
|
719
|
+
return `${strikethroughText(chars.slice(0, visibleChars).join(""))}${chars.slice(visibleChars).join("")}`;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function strikeRevealCount(text: string, frame: number | undefined): number | undefined {
|
|
723
|
+
if (frame === undefined) return undefined;
|
|
724
|
+
if (frame <= TODO_WRITE_STRIKE_HOLD_FRAMES) return 0;
|
|
725
|
+
const chars = [...text];
|
|
726
|
+
if (chars.length === 0) return undefined;
|
|
727
|
+
const revealFrame = Math.min(frame - TODO_WRITE_STRIKE_HOLD_FRAMES, TODO_WRITE_STRIKE_REVEAL_FRAMES);
|
|
728
|
+
return Math.ceil((chars.length * revealFrame) / TODO_WRITE_STRIKE_REVEAL_FRAMES);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function formatTodoLine(
|
|
732
|
+
item: TodoItem,
|
|
733
|
+
uiTheme: Theme,
|
|
734
|
+
prefix: string,
|
|
735
|
+
completionKeys: Set<string>,
|
|
736
|
+
frame: number | undefined,
|
|
737
|
+
): string {
|
|
671
738
|
const checkbox = uiTheme.checkbox;
|
|
672
739
|
const marker = noteMarker(item.notes?.length ?? 0, uiTheme);
|
|
673
740
|
switch (item.status) {
|
|
674
|
-
case "completed":
|
|
675
|
-
|
|
741
|
+
case "completed": {
|
|
742
|
+
const revealCount = completionKeys.has(item.content) ? strikeRevealCount(item.content, frame) : undefined;
|
|
743
|
+
const content =
|
|
744
|
+
revealCount === undefined
|
|
745
|
+
? strikethroughText(item.content)
|
|
746
|
+
: partialStrikethrough(item.content, revealCount);
|
|
747
|
+
return uiTheme.fg("success", `${prefix}${checkbox.checked} ${content}`) + marker;
|
|
748
|
+
}
|
|
676
749
|
case "in_progress":
|
|
677
750
|
return uiTheme.fg("accent", `${prefix}${checkbox.unchecked} ${item.content}`) + marker;
|
|
678
751
|
case "abandoned":
|
|
679
|
-
return uiTheme.fg("error", `${prefix}${checkbox.unchecked} ${
|
|
752
|
+
return uiTheme.fg("error", `${prefix}${checkbox.unchecked} ${strikethroughText(item.content)}`) + marker;
|
|
680
753
|
default:
|
|
681
754
|
return uiTheme.fg("dim", `${prefix}${checkbox.unchecked} ${item.content}`) + marker;
|
|
682
755
|
}
|
|
@@ -722,6 +795,16 @@ export const todoWriteToolRenderer = {
|
|
|
722
795
|
_args?: TodoWriteRenderArgs,
|
|
723
796
|
): Component {
|
|
724
797
|
const phases = (result.details?.phases ?? []).filter(phase => phase.tasks.length > 0);
|
|
798
|
+
const completedTasks = result.details?.completedTasks ?? [];
|
|
799
|
+
const completionKeysByPhase = new Map<string, Set<string>>();
|
|
800
|
+
for (const task of completedTasks) {
|
|
801
|
+
let keys = completionKeysByPhase.get(task.phase);
|
|
802
|
+
if (!keys) {
|
|
803
|
+
keys = new Set<string>();
|
|
804
|
+
completionKeysByPhase.set(task.phase, keys);
|
|
805
|
+
}
|
|
806
|
+
keys.add(task.content);
|
|
807
|
+
}
|
|
725
808
|
const allTasks = phases.flatMap(phase => phase.tasks);
|
|
726
809
|
const header = renderStatusLine(
|
|
727
810
|
{ icon: "success", title: "Todo Write", meta: [`${allTasks.length} tasks`] },
|
|
@@ -732,29 +815,45 @@ export const todoWriteToolRenderer = {
|
|
|
732
815
|
return new Text(`${header}\n${uiTheme.fg("dim", fallback)}`, 0, 0);
|
|
733
816
|
}
|
|
734
817
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
{
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
818
|
+
let cachedKey: string | undefined;
|
|
819
|
+
let cachedLines: string[] | undefined;
|
|
820
|
+
return {
|
|
821
|
+
invalidate(): void {
|
|
822
|
+
cachedKey = undefined;
|
|
823
|
+
cachedLines = undefined;
|
|
824
|
+
},
|
|
825
|
+
render(width: number): string[] {
|
|
826
|
+
const { expanded, spinnerFrame } = options;
|
|
827
|
+
const key = `${expanded ? 1 : 0}:${spinnerFrame ?? -1}:${width}`;
|
|
828
|
+
if (cachedKey === key && cachedLines) return cachedLines;
|
|
829
|
+
|
|
830
|
+
const lines: string[] = [header];
|
|
831
|
+
for (let p = 0; p < phases.length; p++) {
|
|
832
|
+
const phase = phases[p];
|
|
833
|
+
if (phases.length > 1) {
|
|
834
|
+
lines.push(uiTheme.fg("accent", chalk.bold(` ${formatPhaseDisplayName(phase.name, p + 1)}`)));
|
|
835
|
+
}
|
|
836
|
+
const completionKeys = completionKeysByPhase.get(phase.name) ?? EMPTY_COMPLETION_KEYS;
|
|
837
|
+
const treeLines = renderTreeList(
|
|
838
|
+
{
|
|
839
|
+
items: phase.tasks,
|
|
840
|
+
expanded,
|
|
841
|
+
maxCollapsed: PREVIEW_LIMITS.COLLAPSED_ITEMS,
|
|
842
|
+
itemType: "todo",
|
|
843
|
+
renderItem: todo => formatTodoLine(todo, uiTheme, "", completionKeys, spinnerFrame),
|
|
844
|
+
},
|
|
845
|
+
uiTheme,
|
|
846
|
+
);
|
|
847
|
+
for (const line of treeLines) {
|
|
848
|
+
lines.push(` ${line}`);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
lines.push(...renderNoteAttachments(phases, uiTheme));
|
|
852
|
+
cachedKey = key;
|
|
853
|
+
cachedLines = lines;
|
|
854
|
+
return lines;
|
|
855
|
+
},
|
|
856
|
+
};
|
|
758
857
|
},
|
|
759
858
|
mergeCallAndResult: true,
|
|
760
859
|
};
|
package/src/tools/tool-result.ts
CHANGED
|
@@ -12,6 +12,7 @@ export class ToolResultBuilder<TDetails extends DetailsWithMeta> {
|
|
|
12
12
|
#details: TDetails;
|
|
13
13
|
#meta = outputMeta();
|
|
14
14
|
#content: ToolContent = [];
|
|
15
|
+
#isError = false;
|
|
15
16
|
|
|
16
17
|
constructor(details?: TDetails) {
|
|
17
18
|
this.#details = details ?? ({} as TDetails);
|
|
@@ -67,6 +68,12 @@ export class ToolResultBuilder<TDetails extends DetailsWithMeta> {
|
|
|
67
68
|
return this;
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
/** Flag the result as a non-throwing failure (agent-loop surfaces it as a tool error). */
|
|
72
|
+
error(value = true): this {
|
|
73
|
+
this.#isError = value;
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
|
|
70
77
|
done(): AgentToolResult<TDetails> {
|
|
71
78
|
const meta = this.#meta.get();
|
|
72
79
|
if (meta) {
|
|
@@ -77,6 +84,7 @@ export class ToolResultBuilder<TDetails extends DetailsWithMeta> {
|
|
|
77
84
|
return {
|
|
78
85
|
content: this.#content,
|
|
79
86
|
details: hasDetails ? this.#details : undefined,
|
|
87
|
+
...(this.#isError ? { isError: true } : {}),
|
|
80
88
|
};
|
|
81
89
|
}
|
|
82
90
|
}
|
|
@@ -9,13 +9,16 @@ import type { ModelRegistry } from "../config/model-registry";
|
|
|
9
9
|
import { resolveRoleSelection } from "../config/model-resolver";
|
|
10
10
|
import type { Settings } from "../config/settings";
|
|
11
11
|
import titleSystemPrompt from "../prompts/system/title-system.md" with { type: "text" };
|
|
12
|
+
import { ONLINE_TINY_TITLE_MODEL_KEY } from "../tiny/models";
|
|
13
|
+
import { formatTitleUserMessage, normalizeGeneratedTitle } from "../tiny/text";
|
|
14
|
+
import { tinyTitleClient } from "../tiny/title-client";
|
|
12
15
|
|
|
13
16
|
const TITLE_SYSTEM_PROMPT = prompt.render(titleSystemPrompt);
|
|
14
17
|
|
|
15
18
|
const DEFAULT_TERMINAL_TITLE = "π";
|
|
16
19
|
const TERMINAL_TITLE_CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f]/g;
|
|
17
20
|
|
|
18
|
-
const
|
|
21
|
+
export const TITLE_LOCAL_FALLBACK_DELAY_MS = 10_000;
|
|
19
22
|
const TITLE_MAX_TOKENS = 30;
|
|
20
23
|
const REASONING_SAFE_MAX_TOKENS = 1024;
|
|
21
24
|
const SET_TITLE_TOOL_NAME = "set_title";
|
|
@@ -48,6 +51,78 @@ function getTitleModel(registry: ModelRegistry, settings: Settings, currentModel
|
|
|
48
51
|
return undefined;
|
|
49
52
|
}
|
|
50
53
|
|
|
54
|
+
export async function raceFirstNonNull<T>(
|
|
55
|
+
primary: Promise<T | null>,
|
|
56
|
+
startFallback: () => Promise<T | null>,
|
|
57
|
+
delayMs: number = TITLE_LOCAL_FALLBACK_DELAY_MS,
|
|
58
|
+
onPrimaryWinAfterFallback?: () => void,
|
|
59
|
+
): Promise<T | null> {
|
|
60
|
+
const { promise, resolve } = Promise.withResolvers<T | null>();
|
|
61
|
+
let resolved = false;
|
|
62
|
+
let primarySettled = false;
|
|
63
|
+
let fallbackStarted = false;
|
|
64
|
+
let fallbackSettled = false;
|
|
65
|
+
|
|
66
|
+
const resolveOnce = (value: T | null): void => {
|
|
67
|
+
if (resolved) return;
|
|
68
|
+
resolved = true;
|
|
69
|
+
resolve(value);
|
|
70
|
+
};
|
|
71
|
+
const maybeResolveNull = (): void => {
|
|
72
|
+
if (primarySettled && fallbackStarted && fallbackSettled) resolveOnce(null);
|
|
73
|
+
};
|
|
74
|
+
const startFallbackOnce = (): void => {
|
|
75
|
+
if (fallbackStarted || resolved) return;
|
|
76
|
+
fallbackStarted = true;
|
|
77
|
+
let fallback: Promise<T | null>;
|
|
78
|
+
try {
|
|
79
|
+
fallback = startFallback();
|
|
80
|
+
} catch {
|
|
81
|
+
fallbackSettled = true;
|
|
82
|
+
maybeResolveNull();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
void fallback.then(
|
|
86
|
+
value => {
|
|
87
|
+
fallbackSettled = true;
|
|
88
|
+
if (value !== null) resolveOnce(value);
|
|
89
|
+
else maybeResolveNull();
|
|
90
|
+
},
|
|
91
|
+
() => {
|
|
92
|
+
fallbackSettled = true;
|
|
93
|
+
maybeResolveNull();
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const timer = setTimeout(startFallbackOnce, delayMs);
|
|
99
|
+
void primary.then(
|
|
100
|
+
value => {
|
|
101
|
+
primarySettled = true;
|
|
102
|
+
clearTimeout(timer);
|
|
103
|
+
if (value !== null) {
|
|
104
|
+
if (fallbackStarted) onPrimaryWinAfterFallback?.();
|
|
105
|
+
resolveOnce(value);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
startFallbackOnce();
|
|
109
|
+
maybeResolveNull();
|
|
110
|
+
},
|
|
111
|
+
() => {
|
|
112
|
+
primarySettled = true;
|
|
113
|
+
clearTimeout(timer);
|
|
114
|
+
startFallbackOnce();
|
|
115
|
+
maybeResolveNull();
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
return await promise;
|
|
121
|
+
} finally {
|
|
122
|
+
clearTimeout(timer);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
51
126
|
/**
|
|
52
127
|
* Generate a title for a session based on the first user message.
|
|
53
128
|
*
|
|
@@ -68,6 +143,41 @@ export async function generateSessionTitle(
|
|
|
68
143
|
sessionId?: string,
|
|
69
144
|
currentModel?: Model<Api>,
|
|
70
145
|
metadataResolver?: (provider: string) => Record<string, unknown> | undefined,
|
|
146
|
+
): Promise<string | null> {
|
|
147
|
+
const tinyModel = settings.get("providers.tinyModel");
|
|
148
|
+
if (tinyModel === ONLINE_TINY_TITLE_MODEL_KEY) {
|
|
149
|
+
return generateTitleOnline(firstMessage, registry, settings, sessionId, currentModel, metadataResolver);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const onlineAbortController = new AbortController();
|
|
153
|
+
const localTitle = tinyTitleClient.generate(tinyModel, firstMessage).then(
|
|
154
|
+
title => title || null,
|
|
155
|
+
() => null,
|
|
156
|
+
);
|
|
157
|
+
const startOnline = (): Promise<string | null> =>
|
|
158
|
+
generateTitleOnline(
|
|
159
|
+
firstMessage,
|
|
160
|
+
registry,
|
|
161
|
+
settings,
|
|
162
|
+
sessionId,
|
|
163
|
+
currentModel,
|
|
164
|
+
metadataResolver,
|
|
165
|
+
onlineAbortController.signal,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
return raceFirstNonNull(localTitle, startOnline, TITLE_LOCAL_FALLBACK_DELAY_MS, () => {
|
|
169
|
+
onlineAbortController.abort();
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function generateTitleOnline(
|
|
174
|
+
firstMessage: string,
|
|
175
|
+
registry: ModelRegistry,
|
|
176
|
+
settings: Settings,
|
|
177
|
+
sessionId?: string,
|
|
178
|
+
currentModel?: Model<Api>,
|
|
179
|
+
metadataResolver?: (provider: string) => Record<string, unknown> | undefined,
|
|
180
|
+
signal?: AbortSignal,
|
|
71
181
|
): Promise<string | null> {
|
|
72
182
|
const model = getTitleModel(registry, settings, currentModel);
|
|
73
183
|
if (!model) {
|
|
@@ -75,12 +185,7 @@ export async function generateSessionTitle(
|
|
|
75
185
|
return null;
|
|
76
186
|
}
|
|
77
187
|
|
|
78
|
-
|
|
79
|
-
const truncatedMessage =
|
|
80
|
-
firstMessage.length > MAX_INPUT_CHARS ? `${firstMessage.slice(0, MAX_INPUT_CHARS)}…` : firstMessage;
|
|
81
|
-
const userMessage = `<user-message>
|
|
82
|
-
${truncatedMessage}
|
|
83
|
-
</user-message>`;
|
|
188
|
+
const userMessage = formatTitleUserMessage(firstMessage);
|
|
84
189
|
|
|
85
190
|
const apiKey = await registry.getApiKey(model, sessionId);
|
|
86
191
|
if (!apiKey) {
|
|
@@ -122,6 +227,7 @@ ${truncatedMessage}
|
|
|
122
227
|
disableReasoning: true,
|
|
123
228
|
toolChoice: { type: "tool", name: SET_TITLE_TOOL_NAME },
|
|
124
229
|
metadata,
|
|
230
|
+
signal,
|
|
125
231
|
},
|
|
126
232
|
);
|
|
127
233
|
|
|
@@ -134,7 +240,7 @@ ${truncatedMessage}
|
|
|
134
240
|
return null;
|
|
135
241
|
}
|
|
136
242
|
|
|
137
|
-
const title = extractGeneratedTitle(response.content);
|
|
243
|
+
const title = normalizeGeneratedTitle(extractGeneratedTitle(response.content));
|
|
138
244
|
|
|
139
245
|
logger.debug("title-generator: response", {
|
|
140
246
|
model: request.model,
|
|
@@ -143,11 +249,7 @@ ${truncatedMessage}
|
|
|
143
249
|
stopReason: response.stopReason,
|
|
144
250
|
});
|
|
145
251
|
|
|
146
|
-
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return title.replace(/^["']|["']$/g, "").replace(/[.!?]$/, "");
|
|
252
|
+
return title;
|
|
151
253
|
} catch (err) {
|
|
152
254
|
logger.debug("title-generator: error", {
|
|
153
255
|
model: request.model,
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { Component } from "@oh-my-pi/pi-tui";
|
|
3
|
-
import * as z from "zod/v4";
|
|
4
|
-
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
5
|
-
import type { Theme } from "../modes/theme/theme";
|
|
6
|
-
import type { ToolSession } from ".";
|
|
7
|
-
declare const calculatorSchema: z.ZodObject<{
|
|
8
|
-
calculations: z.ZodArray<z.ZodObject<{
|
|
9
|
-
expression: z.ZodString;
|
|
10
|
-
prefix: z.ZodString;
|
|
11
|
-
suffix: z.ZodString;
|
|
12
|
-
}, z.core.$strip>>;
|
|
13
|
-
}, z.core.$strip>;
|
|
14
|
-
export interface CalculatorToolDetails {
|
|
15
|
-
results: Array<{
|
|
16
|
-
expression: string;
|
|
17
|
-
value: number;
|
|
18
|
-
output: string;
|
|
19
|
-
}>;
|
|
20
|
-
}
|
|
21
|
-
type CalculatorParams = z.infer<typeof calculatorSchema>;
|
|
22
|
-
/**
|
|
23
|
-
* Calculator tool for evaluating mathematical expressions.
|
|
24
|
-
*
|
|
25
|
-
* Supports decimal, hex (0x), binary (0b), octal (0o) literals,
|
|
26
|
-
* standard arithmetic operators, and parentheses.
|
|
27
|
-
*/
|
|
28
|
-
export declare class CalculatorTool implements AgentTool<typeof calculatorSchema, CalculatorToolDetails> {
|
|
29
|
-
readonly name = "calc";
|
|
30
|
-
readonly approval: "read";
|
|
31
|
-
readonly label = "Calc";
|
|
32
|
-
readonly summary = "Evaluate a mathematical expression";
|
|
33
|
-
readonly loadMode = "discoverable";
|
|
34
|
-
readonly description: string;
|
|
35
|
-
readonly parameters: z.ZodObject<{
|
|
36
|
-
calculations: z.ZodArray<z.ZodObject<{
|
|
37
|
-
expression: z.ZodString;
|
|
38
|
-
prefix: z.ZodString;
|
|
39
|
-
suffix: z.ZodString;
|
|
40
|
-
}, z.core.$strip>>;
|
|
41
|
-
}, z.core.$strip>;
|
|
42
|
-
readonly strict = true;
|
|
43
|
-
constructor(_session: ToolSession);
|
|
44
|
-
execute(_toolCallId: string, { calculations }: CalculatorParams, signal?: AbortSignal): Promise<AgentToolResult<CalculatorToolDetails>>;
|
|
45
|
-
}
|
|
46
|
-
interface CalculatorRenderArgs {
|
|
47
|
-
calculations?: Array<{
|
|
48
|
-
expression: string;
|
|
49
|
-
prefix?: string;
|
|
50
|
-
suffix?: string;
|
|
51
|
-
}>;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* TUI renderer for calculator tool calls and results.
|
|
55
|
-
* Handles both collapsed (preview) and expanded (full) display modes.
|
|
56
|
-
*/
|
|
57
|
-
export declare const calculatorToolRenderer: {
|
|
58
|
-
/**
|
|
59
|
-
* Render the tool call header showing the first expression and count.
|
|
60
|
-
* Format: "Calc <expression> (N calcs)"
|
|
61
|
-
*/
|
|
62
|
-
renderCall(args: CalculatorRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component;
|
|
63
|
-
/**
|
|
64
|
-
* Render calculation results as a tree list.
|
|
65
|
-
* Collapsed mode shows first N items with expand hint; expanded shows all.
|
|
66
|
-
*/
|
|
67
|
-
renderResult(result: {
|
|
68
|
-
content: Array<{
|
|
69
|
-
type: string;
|
|
70
|
-
text?: string;
|
|
71
|
-
}>;
|
|
72
|
-
details?: CalculatorToolDetails;
|
|
73
|
-
isError?: boolean;
|
|
74
|
-
}, options: RenderResultOptions, uiTheme: Theme, args?: CalculatorRenderArgs): Component;
|
|
75
|
-
mergeCallAndResult: boolean;
|
|
76
|
-
};
|
|
77
|
-
export {};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
Performs basic calculations.
|
|
2
|
-
|
|
3
|
-
<instruction>
|
|
4
|
-
- Supports +, -, *, /, %, ** and parentheses
|
|
5
|
-
- Supports decimal, hex (0x), binary (0b), and octal (0o) literals
|
|
6
|
-
</instruction>
|
|
7
|
-
|
|
8
|
-
<output>
|
|
9
|
-
Returns each calculation result with its prefix and suffix applied.
|
|
10
|
-
</output>
|