@oh-my-pi/pi-coding-agent 14.6.2 → 14.6.4
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 +95 -2
- package/README.md +21 -0
- package/package.json +23 -7
- package/src/cli/grievances-cli.ts +89 -4
- package/src/commands/grievances.ts +33 -7
- package/src/config/prompt-templates.ts +14 -7
- package/src/config/settings-schema.ts +610 -100
- package/src/config/settings.ts +42 -0
- package/src/discovery/helpers.ts +13 -6
- package/src/edit/index.ts +3 -3
- package/src/edit/line-hash.ts +73 -25
- package/src/edit/modes/hashline.lark +10 -3
- package/src/edit/modes/hashline.ts +295 -40
- package/src/edit/renderer.ts +3 -3
- package/src/hindsight/backend.ts +205 -0
- package/src/hindsight/bank.ts +131 -0
- package/src/hindsight/client.ts +598 -0
- package/src/hindsight/config.ts +175 -0
- package/src/hindsight/content.ts +210 -0
- package/src/hindsight/index.ts +8 -0
- package/src/hindsight/mental-models.ts +382 -0
- package/src/hindsight/seeds.json +32 -0
- package/src/hindsight/state.ts +469 -0
- package/src/hindsight/transcript.ts +71 -0
- package/src/main.ts +7 -10
- package/src/memories/index.ts +1 -1
- package/src/memory-backend/index.ts +4 -0
- package/src/memory-backend/local-backend.ts +30 -0
- package/src/memory-backend/off-backend.ts +16 -0
- package/src/memory-backend/resolve.ts +24 -0
- package/src/memory-backend/types.ts +79 -0
- package/src/modes/components/settings-defs.ts +50 -451
- package/src/modes/components/settings-selector.ts +2 -2
- package/src/modes/components/status-line/presets.ts +1 -1
- package/src/modes/controllers/command-controller.ts +266 -6
- package/src/modes/controllers/event-controller.ts +12 -0
- package/src/modes/controllers/selector-controller.ts +3 -12
- package/src/modes/theme/theme.ts +4 -0
- package/src/prompts/tools/github.md +3 -0
- package/src/prompts/tools/hashline.md +21 -16
- package/src/prompts/tools/read.md +10 -6
- package/src/prompts/tools/recall.md +5 -0
- package/src/prompts/tools/reflect.md +5 -0
- package/src/prompts/tools/retain.md +5 -0
- package/src/prompts/tools/search.md +1 -1
- package/src/sdk.ts +21 -9
- package/src/session/agent-session.ts +118 -3
- package/src/slash-commands/builtin-registry.ts +12 -12
- package/src/task/executor.ts +3 -0
- package/src/task/index.ts +2 -0
- package/src/tools/ast-edit.ts +14 -5
- package/src/tools/ast-grep.ts +12 -3
- package/src/tools/find.ts +47 -7
- package/src/tools/gh-renderer.ts +10 -1
- package/src/tools/gh.ts +233 -5
- package/src/tools/hindsight-recall.ts +68 -0
- package/src/tools/hindsight-reflect.ts +55 -0
- package/src/tools/hindsight-retain.ts +60 -0
- package/src/tools/index.ts +20 -0
- package/src/tools/path-utils.ts +55 -0
- package/src/tools/read.ts +1 -1
- package/src/tools/search.ts +45 -8
package/src/config/settings.ts
CHANGED
|
@@ -568,6 +568,48 @@ export class Settings {
|
|
|
568
568
|
}
|
|
569
569
|
}
|
|
570
570
|
|
|
571
|
+
// Map legacy `memories.enabled` boolean to the explicit `memory.backend`
|
|
572
|
+
// enum if the latter hasn't been set yet. Idempotent: subsequent
|
|
573
|
+
// migrations are no-ops once memory.backend is materialised.
|
|
574
|
+
const memoryBackendObj = raw.memory as Record<string, unknown> | undefined;
|
|
575
|
+
const memoryBackendSet = memoryBackendObj && typeof memoryBackendObj.backend === "string";
|
|
576
|
+
const memoriesObj = raw.memories as Record<string, unknown> | undefined;
|
|
577
|
+
if (!memoryBackendSet && memoriesObj && typeof memoriesObj.enabled === "boolean") {
|
|
578
|
+
const next = memoriesObj.enabled ? "local" : "off";
|
|
579
|
+
const memoryRoot = (memoryBackendObj ?? {}) as Record<string, unknown>;
|
|
580
|
+
memoryRoot.backend = next;
|
|
581
|
+
raw.memory = memoryRoot;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// hindsight: dynamicBankId/agentName -> scoping enum + bankId
|
|
585
|
+
// - dynamicBankId=true → scoping="per-project" (closest semantic match;
|
|
586
|
+
// the legacy `agent::project::channel::user` tuple was per-project in
|
|
587
|
+
// practice — the channel/user env vars were rarely set).
|
|
588
|
+
// - hindsight.agentName was only used as the agent slot in the legacy
|
|
589
|
+
// dynamic tuple; if the user customised it we surface it as the new
|
|
590
|
+
// bankId base when no explicit bankId is set.
|
|
591
|
+
const hindsightObj = raw.hindsight as Record<string, unknown> | undefined;
|
|
592
|
+
if (hindsightObj) {
|
|
593
|
+
if ("dynamicBankId" in hindsightObj) {
|
|
594
|
+
if (!("scoping" in hindsightObj) && hindsightObj.dynamicBankId === true) {
|
|
595
|
+
hindsightObj.scoping = "per-project";
|
|
596
|
+
}
|
|
597
|
+
delete hindsightObj.dynamicBankId;
|
|
598
|
+
}
|
|
599
|
+
if ("agentName" in hindsightObj) {
|
|
600
|
+
const agentName = hindsightObj.agentName;
|
|
601
|
+
if (
|
|
602
|
+
!("bankId" in hindsightObj) &&
|
|
603
|
+
typeof agentName === "string" &&
|
|
604
|
+
agentName.trim().length > 0 &&
|
|
605
|
+
agentName !== "omp"
|
|
606
|
+
) {
|
|
607
|
+
hindsightObj.bankId = agentName;
|
|
608
|
+
}
|
|
609
|
+
delete hindsightObj.agentName;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
571
613
|
return raw;
|
|
572
614
|
}
|
|
573
615
|
|
package/src/discovery/helpers.ts
CHANGED
|
@@ -3,7 +3,14 @@ import * as os from "node:os";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
5
5
|
import { FileType, glob } from "@oh-my-pi/pi-natives";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
CONFIG_DIR_NAME,
|
|
8
|
+
getConfigDirName,
|
|
9
|
+
getPluginsDir,
|
|
10
|
+
getProjectDir,
|
|
11
|
+
parseFrontmatter,
|
|
12
|
+
tryParseJson,
|
|
13
|
+
} from "@oh-my-pi/pi-utils";
|
|
7
14
|
import type { ExtensionModule } from "../capability/extension-module";
|
|
8
15
|
import { invalidate as invalidateFsCache, readDirEntries, readFile } from "../capability/fs";
|
|
9
16
|
import { parseRuleConditionAndScope, type Rule, type RuleFrontmatter } from "../capability/rule";
|
|
@@ -804,8 +811,9 @@ export async function listClaudePluginRoots(
|
|
|
804
811
|
|
|
805
812
|
// ── OMP installed plugins registry ───────────────────────────────────────
|
|
806
813
|
// OMP registry is authoritative: its entries replace Claude's entries for the same plugin ID.
|
|
807
|
-
//
|
|
808
|
-
|
|
814
|
+
// getPluginsDir() resolves to the same path the marketplace writer uses
|
|
815
|
+
// (XDG-aware via the dir resolver), so reads and writes always agree.
|
|
816
|
+
const ompRegistryPath = path.join(getPluginsDir(), "installed_plugins.json");
|
|
809
817
|
const ompContent = await readFile(ompRegistryPath);
|
|
810
818
|
if (ompContent) {
|
|
811
819
|
const ompRegistry = parseClaudePluginsRegistry(ompContent);
|
|
@@ -928,9 +936,8 @@ export function clearClaudePluginRootsCache(): void {
|
|
|
928
936
|
* installing/uninstalling/enabling/disabling plugins.
|
|
929
937
|
*/
|
|
930
938
|
export function clearPluginRootsAndCaches(extraPaths?: readonly string[]): void {
|
|
931
|
-
|
|
932
|
-
invalidateFsCache(path.join(
|
|
933
|
-
invalidateFsCache(path.join(home, getConfigDirName(), "plugins", "installed_plugins.json"));
|
|
939
|
+
invalidateFsCache(path.join(os.homedir(), ".claude", "plugins", "installed_plugins.json"));
|
|
940
|
+
invalidateFsCache(path.join(getPluginsDir(), "installed_plugins.json"));
|
|
934
941
|
for (const p of extraPaths ?? []) invalidateFsCache(p);
|
|
935
942
|
clearClaudePluginRootsCache();
|
|
936
943
|
}
|
package/src/edit/index.ts
CHANGED
|
@@ -16,7 +16,7 @@ import type { ToolSession } from "../tools";
|
|
|
16
16
|
import { VimTool, vimSchema } from "../tools/vim";
|
|
17
17
|
import { type EditMode, normalizeEditMode, resolveEditMode } from "../utils/edit-mode";
|
|
18
18
|
import type { VimToolDetails } from "../vim/types";
|
|
19
|
-
import {
|
|
19
|
+
import { resolveHashlineGrammarPlaceholders } from "./line-hash";
|
|
20
20
|
import { type ApplyPatchParams, applyPatchSchema, expandApplyPatchToEntries } from "./modes/apply-patch";
|
|
21
21
|
import applyPatchGrammar from "./modes/apply-patch.lark" with { type: "text" };
|
|
22
22
|
import {
|
|
@@ -35,8 +35,8 @@ export * from "./apply-patch";
|
|
|
35
35
|
export * from "./diff";
|
|
36
36
|
export * from "./line-hash";
|
|
37
37
|
|
|
38
|
-
// Resolve the `$
|
|
39
|
-
const hashlineGrammar =
|
|
38
|
+
// Resolve the `$HFMT$` and `$HSEP$` placeholders in the hashline Lark grammar.
|
|
39
|
+
const hashlineGrammar = resolveHashlineGrammarPlaceholders(hashlineGrammarTemplate);
|
|
40
40
|
|
|
41
41
|
export * from "./modes/apply-patch";
|
|
42
42
|
export * from "./modes/hashline";
|
package/src/edit/line-hash.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* Order is stable forever — changing it would invalidate every saved
|
|
16
16
|
* `LINE+ID` reference in transcripts and prompts.
|
|
17
17
|
*/
|
|
18
|
-
export const
|
|
18
|
+
export const HL_BIGRAMS = [
|
|
19
19
|
"aa",
|
|
20
20
|
"ab",
|
|
21
21
|
"ac",
|
|
@@ -665,7 +665,7 @@ export const HASHLINE_BIGRAMS = [
|
|
|
665
665
|
"zz",
|
|
666
666
|
] as const;
|
|
667
667
|
|
|
668
|
-
export const
|
|
668
|
+
export const HL_BIGRAMS_COUNT = HL_BIGRAMS.length;
|
|
669
669
|
|
|
670
670
|
/**
|
|
671
671
|
* Decoration prefix that may precede a `LINE+HASH` anchor in tool output:
|
|
@@ -675,7 +675,7 @@ export const HASHLINE_BIGRAMS_COUNT = HASHLINE_BIGRAMS.length;
|
|
|
675
675
|
* regex stays liberal because anchor-ref parsers accept whatever the model
|
|
676
676
|
* echoes back.
|
|
677
677
|
*/
|
|
678
|
-
export const
|
|
678
|
+
export const HL_ANCHOR_DECORATION_RE_RAW = `\\s*[>+\\-*]*\\s*`;
|
|
679
679
|
|
|
680
680
|
/**
|
|
681
681
|
* Capture-group regex source for a decorated `LINE+HASH` anchor. Group 1
|
|
@@ -683,62 +683,110 @@ export const HASHLINE_ANCHOR_DECORATION_RE_SRC = `\\s*[>+\\-*]*\\s*`;
|
|
|
683
683
|
* source is intentionally unanchored — anchoring with `^` (or composing into a
|
|
684
684
|
* larger pattern) is the caller's responsibility.
|
|
685
685
|
*/
|
|
686
|
-
export const
|
|
686
|
+
export const HL_ANCHOR_RE_RAW = `${HL_ANCHOR_DECORATION_RE_RAW}(\\d+)([a-z]{2})`;
|
|
687
687
|
|
|
688
688
|
/**
|
|
689
689
|
* Bare `LINE+HASH` Lid (no decorations, no captures, no anchors). Use for
|
|
690
690
|
* embedding inside larger patterns where the line+hash unit appears as a
|
|
691
691
|
* literal (e.g. range bounds, alternation arms, op-line heuristics).
|
|
692
692
|
*/
|
|
693
|
-
export const
|
|
693
|
+
export const HL_HASH_RE_RAW = `[1-9]\\d*[a-z]{2}`;
|
|
694
694
|
|
|
695
695
|
/**
|
|
696
|
-
* Capture-group form of {@link
|
|
696
|
+
* Capture-group form of {@link HL_HASH_RE_RAW}: group 1 captures the
|
|
697
697
|
* line number, group 2 captures the hash.
|
|
698
698
|
*/
|
|
699
|
-
export const
|
|
699
|
+
export const HL_HASH_CAPTURE_RE_RAW = `([1-9]\\d*)([a-z]{2})`;
|
|
700
700
|
|
|
701
701
|
/** Width of a hash in display characters. */
|
|
702
|
-
export const
|
|
702
|
+
export const HL_HASH_WIDTH = 2;
|
|
703
703
|
|
|
704
704
|
/**
|
|
705
705
|
* Representative hash suffixes for use in user-facing error messages and
|
|
706
706
|
* prompt examples.
|
|
707
707
|
*/
|
|
708
|
-
export const
|
|
708
|
+
export const HL_HASH_EXAMPLES = ["sr", "ab", "th"] as const;
|
|
709
709
|
|
|
710
710
|
/**
|
|
711
711
|
* Format a comma-separated list of example anchors with an optional line-number
|
|
712
712
|
* prefix, quoted for inclusion in error messages: `"160sr", "160ab", "160th"`.
|
|
713
713
|
*/
|
|
714
714
|
export function describeAnchorExamples(linePrefix = ""): string {
|
|
715
|
-
return
|
|
715
|
+
return HL_HASH_EXAMPLES.map(e => `"${linePrefix}${e}"`).join(", ");
|
|
716
716
|
}
|
|
717
717
|
|
|
718
718
|
/**
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
-
* TypeScript consumers. Update the placeholder name here and in the grammar together.
|
|
719
|
+
* Substitute every grammar placeholder with the value derived from its
|
|
720
|
+
* TypeScript counterpart. Grammars that don't reference these placeholders
|
|
721
|
+
* pass through unchanged.
|
|
723
722
|
*/
|
|
724
|
-
export
|
|
723
|
+
export function resolveHashlineGrammarPlaceholders(grammar: string): string {
|
|
724
|
+
return grammar.replaceAll("$HFMT$", "[a-z]{2}").replaceAll("$HSEP$", JSON.stringify(HL_EDIT_SEP));
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/** @deprecated Use {@link resolveHashlineGrammarPlaceholders}. */
|
|
728
|
+
export const resolveLarkLidPlaceholders = resolveHashlineGrammarPlaceholders;
|
|
729
|
+
|
|
730
|
+
const regexEscape = (str: string): string => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
725
731
|
|
|
726
732
|
/**
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
*
|
|
733
|
+
* Single source of truth for the hashline edit payload separator. This is the
|
|
734
|
+
* configured separator that starts inserted/replacement payload lines in
|
|
735
|
+
* hashline edit input (`<separator>TEXT`) and separates inline modify ops from
|
|
736
|
+
* their appended/prepended text.
|
|
737
|
+
*
|
|
738
|
+
* Override at runtime with the `PI_HL_SEP` env var (e.g.
|
|
739
|
+
* `PI_HL_SEP=">"`, `PI_HL_SEP="\\"`). The value is read once at module load;
|
|
740
|
+
* the edit grammar, prompt helper, and edit parser derive from it.
|
|
741
|
+
*
|
|
742
|
+
* Default is `~`, chosen empirically. Benchmark across 8 candidate separators
|
|
743
|
+
* x 3 models (glm-4.7:nitro, gpt-5.4-nano, claude-sonnet-4-6), 24-48 runs per
|
|
744
|
+
* cell, hashline variant, 12 sampled tasks per run:
|
|
745
|
+
*
|
|
746
|
+
* sep | task ✓ | edit ✓ | patch fail | tok/run
|
|
747
|
+
* ----|--------|--------|-----------------|--------
|
|
748
|
+
* + | 70.8% | 78.0% | 27/125 (21.6%) | 32,127
|
|
749
|
+
* ÷ | 70.7% | 90.6% | 22/211 (10.4%) | 31,666
|
|
750
|
+
* ~ | 69.4% | 94.9% | 6/107 ( 5.6%) | 30,529 <-- default
|
|
751
|
+
* > | 69.2% | 91.5% | 21/219 ( 9.6%) | 30,777
|
|
752
|
+
* : | 66.7% | 86.4% | 20/126 (15.9%) | 33,900
|
|
753
|
+
* | | 65.9% | 86.9% | 20/127 (15.7%) | 34,589
|
|
754
|
+
* \ | 65.5% | 89.8% | 16/124 (12.9%) | 36,010
|
|
755
|
+
* % | 63.9% | 92.8% | 11/125 ( 8.8%) | 36,530
|
|
756
|
+
*
|
|
757
|
+
* `~` wins because:
|
|
758
|
+
* - highest edit-tool success rate (94.9%) of any tested separator
|
|
759
|
+
* - lowest patch-failure rate (5.6%) — model rarely emits a malformed payload
|
|
760
|
+
* - cheapest in tokens alongside `>` (no retry overhead from format collisions)
|
|
761
|
+
* - no line-leading role in any mainstream language, markdown, diff, regex,
|
|
762
|
+
* or shell, so payload lines are unambiguous to both the parser and models
|
|
763
|
+
* - task-success is statistically tied with `>` and `÷` (within run-to-run
|
|
764
|
+
* noise), so the edit-reliability win is free
|
|
765
|
+
*
|
|
766
|
+
* `+` and `÷` lead on raw task-success but at the cost of ~2-4x more patch
|
|
767
|
+
* failures (the model retries until it lands a valid edit). `:`, `|`, `\`
|
|
768
|
+
* collide with line-leading syntax (label/object-key, body separator, escape)
|
|
769
|
+
* and degrade both edit reliability and intent-match.
|
|
730
770
|
*/
|
|
731
|
-
export
|
|
732
|
-
|
|
733
|
-
|
|
771
|
+
export const HL_EDIT_SEP = (() => {
|
|
772
|
+
const sep = process.env.PI_HL_SEP?.trim();
|
|
773
|
+
return sep?.length === 1 ? sep : "~";
|
|
774
|
+
})();
|
|
775
|
+
|
|
776
|
+
/** Regex-escaped form of {@link HL_EDIT_SEP}, safe for regexes. */
|
|
777
|
+
export const HL_EDIT_SEP_RE_RAW = regexEscape(HL_EDIT_SEP);
|
|
778
|
+
|
|
779
|
+
/** Stable separator for read/search/hashline display output. Intentionally not configurable. */
|
|
780
|
+
export const HL_BODY_SEP = "|";
|
|
734
781
|
|
|
735
|
-
|
|
782
|
+
/** Regex-escaped form of {@link HL_BODY_SEP}, safe for embedding inside a regex. */
|
|
783
|
+
export const HL_BODY_SEP_RE_RAW = regexEscape(HL_BODY_SEP);
|
|
736
784
|
|
|
737
785
|
const RE_SIGNIFICANT = /[\p{L}\p{N}]/u;
|
|
738
786
|
|
|
739
787
|
/**
|
|
740
788
|
* Compute a 2-character hash of a single line via xxHash32 mod 647 over
|
|
741
|
-
* {@link
|
|
789
|
+
* {@link HL_BIGRAMS}. Lines with no letter or digit (e.g. bare `}`,
|
|
742
790
|
* bare `{`) mix the line number into the seed so adjacent identical
|
|
743
791
|
* brace-only lines get distinct hashes; lines with significant content stay
|
|
744
792
|
* line-number-independent so a line is identifiable across small shifts.
|
|
@@ -748,7 +796,7 @@ const RE_SIGNIFICANT = /[\p{L}\p{N}]/u;
|
|
|
748
796
|
export function computeLineHash(idx: number, line: string): string {
|
|
749
797
|
line = line.replace(/\r/g, "").trimEnd();
|
|
750
798
|
const seed = RE_SIGNIFICANT.test(line) ? 0 : idx;
|
|
751
|
-
return
|
|
799
|
+
return HL_BIGRAMS[Bun.hash.xxHash32(line, seed) % HL_BIGRAMS_COUNT];
|
|
752
800
|
}
|
|
753
801
|
|
|
754
802
|
/**
|
|
@@ -765,7 +813,7 @@ export function formatLineHash(line: number, lines: string): string {
|
|
|
765
813
|
* Returns `LINE+ID|TEXT` (e.g., `42sr|function hi() {`, `3ab|}`).
|
|
766
814
|
*/
|
|
767
815
|
export function formatHashLine(lineNumber: number, line: string): string {
|
|
768
|
-
return `${lineNumber}${computeLineHash(lineNumber, line)}${
|
|
816
|
+
return `${lineNumber}${computeLineHash(lineNumber, line)}${HL_BODY_SEP}${line}`;
|
|
769
817
|
}
|
|
770
818
|
|
|
771
819
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
%import common.LF
|
|
2
|
+
%import common.WS_INLINE
|
|
2
3
|
|
|
3
4
|
start: section+
|
|
4
5
|
|
|
@@ -6,21 +7,27 @@ section: file_header line_op*
|
|
|
6
7
|
|
|
7
8
|
file_header: "@" path LF
|
|
8
9
|
|
|
9
|
-
line_op:
|
|
10
|
+
line_op: inline_before_op payload*
|
|
11
|
+
| inline_after_op payload*
|
|
12
|
+
| insert_before_op payload+
|
|
10
13
|
| insert_after_op payload+
|
|
11
14
|
| replace_op payload*
|
|
12
15
|
| delete_op
|
|
13
16
|
| blank
|
|
14
17
|
|
|
18
|
+
inline_before_op: "<" LID $HSEP$ line_text? LF
|
|
19
|
+
inline_after_op: "+" LID $HSEP$ line_text? LF
|
|
15
20
|
insert_before_op: "<" insert_target LF
|
|
16
21
|
insert_after_op: "+" insert_target LF
|
|
17
22
|
replace_op: "=" range LF
|
|
18
23
|
delete_op: "-" range LF
|
|
19
|
-
payload:
|
|
24
|
+
payload: $HSEP$ line_text? LF
|
|
25
|
+
|
|
26
|
+
line_text: /[^\r\n]+/
|
|
20
27
|
|
|
21
28
|
insert_target: LID | "EOF" | "BOF"
|
|
22
29
|
range: LID (".." LID)?
|
|
23
30
|
|
|
24
31
|
path: /(?:[^\s\r\n]+|"[^"\r\n]+"|'[^'\r\n]+')/
|
|
25
|
-
LID: /[1-9][0-9]*$
|
|
32
|
+
LID: /[1-9][0-9]*$HFMT$/
|
|
26
33
|
blank: LF
|