@oh-my-pi/pi-coding-agent 15.5.10 → 15.5.12
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 +34 -0
- package/dist/types/cli-commands.d.ts +19 -0
- package/dist/types/commands/install.d.ts +51 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/discovery/omp-extension-roots.d.ts +43 -0
- package/dist/types/discovery/omp-plugins.d.ts +1 -0
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +14 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -0
- package/dist/types/extensibility/plugins/loader.d.ts +12 -2
- package/dist/types/index.d.ts +3 -0
- package/dist/types/modes/components/custom-editor.d.ts +3 -0
- package/dist/types/modes/ultrathink.d.ts +10 -0
- package/dist/types/session/redis-session-storage.d.ts +124 -0
- package/dist/types/session/sql-session-storage.d.ts +141 -0
- package/dist/types/tools/todo-write.d.ts +30 -0
- package/examples/sdk/12-redis-sessions.ts +54 -0
- package/examples/sdk/13-sql-sessions.ts +61 -0
- package/package.json +8 -8
- package/scripts/build-binary.ts +14 -9
- package/src/cli-commands.ts +44 -0
- package/src/cli.ts +2 -32
- package/src/commands/install.ts +107 -0
- package/src/discovery/index.ts +1 -0
- package/src/discovery/omp-extension-roots.ts +190 -0
- package/src/discovery/omp-plugins.ts +383 -0
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +15 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +63 -22
- package/src/extensibility/plugins/loader.ts +43 -18
- package/src/index.ts +3 -0
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/main.ts +12 -0
- package/src/memories/index.ts +8 -3
- package/src/modes/components/custom-editor.ts +3 -0
- package/src/modes/interactive-mode.ts +243 -12
- package/src/modes/ultrathink.ts +79 -0
- package/src/prompts/system/ultrathink-notice.md +3 -0
- package/src/session/agent-session.ts +28 -0
- package/src/session/redis-session-storage.ts +481 -0
- package/src/session/sql-session-storage.ts +565 -0
- package/src/tools/read.ts +23 -6
- package/src/tools/todo-write.ts +64 -0
- package/src/tools/write.ts +40 -6
package/src/tools/todo-write.ts
CHANGED
|
@@ -139,6 +139,70 @@ export function getLatestTodoPhasesFromEntries(entries: SessionEntry[]): TodoPha
|
|
|
139
139
|
return [];
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Pick the actionable window of tasks to display in the sticky todo panel.
|
|
144
|
+
*
|
|
145
|
+
* Returns up to `maxVisible` open (pending / in_progress) tasks in their
|
|
146
|
+
* original phase order, plus the count of remaining open tasks not shown so
|
|
147
|
+
* the caller can render a `+N more` hint. When every task in `tasks` is
|
|
148
|
+
* closed (completed or abandoned), returns the trailing `maxVisible` tasks
|
|
149
|
+
* with `hiddenOpenCount = 0`, so the panel keeps useful context until the
|
|
150
|
+
* active-phase pointer advances on the next `todo_write`.
|
|
151
|
+
*
|
|
152
|
+
* Task identity and order are preserved — this is a slice, never a sort.
|
|
153
|
+
*/
|
|
154
|
+
export function selectStickyTodoWindow(
|
|
155
|
+
tasks: TodoItem[],
|
|
156
|
+
maxVisible = 5,
|
|
157
|
+
): { visible: TodoItem[]; hiddenOpenCount: number } {
|
|
158
|
+
const openTasks = tasks.filter(t => t.status === "pending" || t.status === "in_progress");
|
|
159
|
+
if (openTasks.length > 0) {
|
|
160
|
+
const visible = openTasks.slice(0, maxVisible);
|
|
161
|
+
return { visible, hiddenOpenCount: openTasks.length - visible.length };
|
|
162
|
+
}
|
|
163
|
+
const start = Math.max(0, tasks.length - maxVisible);
|
|
164
|
+
return { visible: tasks.slice(start), hiddenOpenCount: 0 };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Minimum overlap (after normalization) required for a substring match.
|
|
168
|
+
* Picked at six chars to admit single-word identifiers like "review" /
|
|
169
|
+
* "Sonnet" without admitting tiny common substrings like "test" / "fix"
|
|
170
|
+
* that would collide across unrelated todos. */
|
|
171
|
+
const TODO_DESCRIPTION_MIN_OVERLAP = 6;
|
|
172
|
+
|
|
173
|
+
function normalizeForTodoMatch(value: string): string {
|
|
174
|
+
return value
|
|
175
|
+
.toLowerCase()
|
|
176
|
+
.replace(/[^\p{L}\p{N}]+/gu, " ")
|
|
177
|
+
.trim();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Report whether `content` likely names the same work as any entry in
|
|
182
|
+
* `descriptions`. Used by the sticky todo panel to light up a pending todo
|
|
183
|
+
* when an in-flight subagent is doing the work for it, without requiring
|
|
184
|
+
* the caller to flip the todo's status.
|
|
185
|
+
*
|
|
186
|
+
* Matching is normalize-then-equal first (lowercased; punctuation and
|
|
187
|
+
* whitespace runs both collapsed to a single space; trimmed), with a
|
|
188
|
+
* substring fallback in either direction so minor wording drift
|
|
189
|
+
* ("Sonnet #2: bug scan" vs "Sonnet #2") still links up. The substring
|
|
190
|
+
* fallback requires at least {@link TODO_DESCRIPTION_MIN_OVERLAP} chars on
|
|
191
|
+
* the contained side.
|
|
192
|
+
*/
|
|
193
|
+
export function todoMatchesAnyDescription(content: string, descriptions: readonly string[]): boolean {
|
|
194
|
+
const target = normalizeForTodoMatch(content);
|
|
195
|
+
if (!target) return false;
|
|
196
|
+
for (const desc of descriptions) {
|
|
197
|
+
const candidate = normalizeForTodoMatch(desc);
|
|
198
|
+
if (!candidate) continue;
|
|
199
|
+
if (target === candidate) return true;
|
|
200
|
+
if (target.length >= TODO_DESCRIPTION_MIN_OVERLAP && candidate.includes(target)) return true;
|
|
201
|
+
if (candidate.length >= TODO_DESCRIPTION_MIN_OVERLAP && target.includes(candidate)) return true;
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
142
206
|
function resolveTaskOrError(
|
|
143
207
|
phases: TodoPhase[],
|
|
144
208
|
content: string | undefined,
|
package/src/tools/write.ts
CHANGED
|
@@ -2,13 +2,15 @@ import { Database } from "bun:sqlite";
|
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
|
|
5
|
-
import { stripHashlinePrefixes } from "@oh-my-pi/hashline";
|
|
5
|
+
import { formatHashlineHeader, stripHashlinePrefixes } from "@oh-my-pi/hashline";
|
|
6
6
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
7
7
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
8
8
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
9
9
|
import { isEnoent, isRecord, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
10
10
|
import * as z from "zod/v4";
|
|
11
11
|
|
|
12
|
+
import { getFileSnapshotStore } from "../edit/file-snapshot-store";
|
|
13
|
+
import { normalizeToLF } from "../edit/normalize";
|
|
12
14
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
13
15
|
import { InternalUrlRouter } from "../internal-urls";
|
|
14
16
|
import { parseInternalUrl } from "../internal-urls/parse";
|
|
@@ -116,6 +118,24 @@ function stripWriteContent(session: ToolSession, content: string): { text: strin
|
|
|
116
118
|
return stripWriteContentWithPotentialLooseHeader(content.split("\n"));
|
|
117
119
|
}
|
|
118
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Record a snapshot of the freshly-written `content` for `absolutePath`
|
|
123
|
+
* so subsequent hashline edits address the new file with a current tag,
|
|
124
|
+
* and return the matching `¶displayPath#TAG` header. Returns `undefined`
|
|
125
|
+
* when the session is not in hashline mode so callers can no-op cheaply.
|
|
126
|
+
*
|
|
127
|
+
* Mirrors the post-commit snapshot recording the hashline patcher performs
|
|
128
|
+
* after a successful edit: the model gets a tag without an extra `read`.
|
|
129
|
+
*/
|
|
130
|
+
function maybeWriteSnapshotHeader(session: ToolSession, absolutePath: string, content: string): string | undefined {
|
|
131
|
+
if (!resolveFileDisplayMode(session).hashLines) return undefined;
|
|
132
|
+
const normalized = normalizeToLF(content);
|
|
133
|
+
const tag = getFileSnapshotStore(session).recordContiguous(absolutePath, 1, normalized.split("\n"), {
|
|
134
|
+
fullText: normalized,
|
|
135
|
+
});
|
|
136
|
+
return formatHashlineHeader(formatPathRelativeToCwd(absolutePath, session.cwd), tag);
|
|
137
|
+
}
|
|
138
|
+
|
|
119
139
|
/**
|
|
120
140
|
* Append a trailing note line to the first text block of a tool result.
|
|
121
141
|
* Mutates `result` in place (the result object is owned by this call).
|
|
@@ -540,11 +560,13 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
540
560
|
this.session.fileSnapshotStore?.invalidate(absolutePath);
|
|
541
561
|
this.session.conflictHistory?.invalidate(entry.id);
|
|
542
562
|
|
|
563
|
+
const header = maybeWriteSnapshotHeader(this.session, absolutePath, newContent);
|
|
543
564
|
const range =
|
|
544
565
|
entry.startLine === entry.endLine
|
|
545
566
|
? `line ${entry.startLine}`
|
|
546
567
|
: `lines ${entry.startLine}\u2013${entry.endLine}`;
|
|
547
|
-
|
|
568
|
+
const summary = `Resolved conflict #${entry.id} at ${range} in ${entry.displayPath}.`;
|
|
569
|
+
let resultText = header ? `${header}\n${summary}` : summary;
|
|
548
570
|
if (stripped) {
|
|
549
571
|
resultText += `\nNote: auto-stripped hashline display prefixes from content before writing.`;
|
|
550
572
|
}
|
|
@@ -624,7 +646,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
624
646
|
|
|
625
647
|
const batchRequest = getLspBatchRequest(context?.toolCall);
|
|
626
648
|
const allDiagnostics: FileDiagnosticsResult[] = [];
|
|
627
|
-
const succeededFiles: { displayPath: string; count: number }[] = [];
|
|
649
|
+
const succeededFiles: { displayPath: string; count: number; header?: string }[] = [];
|
|
628
650
|
const failedFiles: { displayPath: string; count: number; error: string }[] = [];
|
|
629
651
|
let totalResolvedIds = 0;
|
|
630
652
|
|
|
@@ -661,7 +683,8 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
661
683
|
invalidateFsScanAfterWrite(absolutePath);
|
|
662
684
|
this.session.fileSnapshotStore?.invalidate(absolutePath);
|
|
663
685
|
for (const entry of fileEntries) history.invalidate(entry.id);
|
|
664
|
-
|
|
686
|
+
const header = maybeWriteSnapshotHeader(this.session, absolutePath, text);
|
|
687
|
+
succeededFiles.push({ displayPath: sample.displayPath, count: fileEntries.length, header });
|
|
665
688
|
totalResolvedIds += fileEntries.length;
|
|
666
689
|
if (diagnostics) allDiagnostics.push(diagnostics);
|
|
667
690
|
}
|
|
@@ -685,6 +708,13 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
685
708
|
summaryLines.push(` ${file.displayPath}: ${file.count} ${conflictWord(file.count)} (${file.error})`);
|
|
686
709
|
}
|
|
687
710
|
}
|
|
711
|
+
const headerLines = succeededFiles
|
|
712
|
+
.map(file => file.header)
|
|
713
|
+
.filter((header): header is string => header !== undefined);
|
|
714
|
+
if (headerLines.length > 0) {
|
|
715
|
+
summaryLines.push("Snapshots:");
|
|
716
|
+
for (const header of headerLines) summaryLines.push(` ${header}`);
|
|
717
|
+
}
|
|
688
718
|
if (stripped) {
|
|
689
719
|
summaryLines.push("Note: auto-stripped hashline display prefixes from content before writing.");
|
|
690
720
|
}
|
|
@@ -813,7 +843,9 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
813
843
|
}
|
|
814
844
|
invalidateFsScanAfterWrite(absolutePath);
|
|
815
845
|
const displayPath = formatPathRelativeToCwd(absolutePath, this.session.cwd);
|
|
816
|
-
|
|
846
|
+
const header = maybeWriteSnapshotHeader(this.session, absolutePath, cleanContent);
|
|
847
|
+
const writeLine = `Successfully wrote ${cleanContent.length} bytes to ${displayPath}`;
|
|
848
|
+
let resultText = header ? `${header}\n${writeLine}` : writeLine;
|
|
817
849
|
if (stripped) {
|
|
818
850
|
resultText += `\nNote: auto-stripped hashline display prefixes from content before writing.`;
|
|
819
851
|
}
|
|
@@ -825,7 +857,9 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
825
857
|
const madeExecutable = await maybeMarkExecutableForShebang(absolutePath, cleanContent);
|
|
826
858
|
|
|
827
859
|
const displayPath = formatPathRelativeToCwd(absolutePath, this.session.cwd);
|
|
828
|
-
|
|
860
|
+
const header = maybeWriteSnapshotHeader(this.session, absolutePath, cleanContent);
|
|
861
|
+
const writeLine = `Successfully wrote ${cleanContent.length} bytes to ${displayPath}`;
|
|
862
|
+
let resultText = header ? `${header}\n${writeLine}` : writeLine;
|
|
829
863
|
if (stripped) {
|
|
830
864
|
resultText += `\nNote: auto-stripped hashline display prefixes from content before writing.`;
|
|
831
865
|
}
|