@oh-my-pi/pi-coding-agent 16.1.22 → 16.1.23

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/dist/cli.js +3441 -3300
  3. package/dist/types/cli/gc-cli.d.ts +58 -0
  4. package/dist/types/commands/gc.d.ts +37 -0
  5. package/dist/types/config/settings-schema.d.ts +54 -0
  6. package/dist/types/config/settings.d.ts +11 -0
  7. package/dist/types/mcp/transports/stdio.d.ts +25 -1
  8. package/dist/types/modes/theme/mermaid-rendering.test.d.ts +1 -0
  9. package/dist/types/modes/theme/theme.d.ts +1 -0
  10. package/dist/types/session/session-listing.d.ts +10 -1
  11. package/dist/types/system-prompt.d.ts +2 -0
  12. package/dist/types/tools/plan-mode-guard.d.ts +7 -0
  13. package/package.json +12 -12
  14. package/scripts/bench-guard.ts +1 -1
  15. package/src/cli/gc-cli.ts +939 -0
  16. package/src/cli-commands.ts +1 -0
  17. package/src/commands/gc.ts +46 -0
  18. package/src/config/settings-schema.ts +45 -0
  19. package/src/config/settings.ts +44 -6
  20. package/src/edit/hashline/filesystem.ts +11 -6
  21. package/src/eval/__tests__/julia-prelude.test.ts +18 -0
  22. package/src/eval/jl/runner.jl +7 -1
  23. package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +2 -0
  24. package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +6 -0
  25. package/src/internal-urls/docs-index.generated.txt +1 -1
  26. package/src/lsp/index.ts +8 -1
  27. package/src/mcp/oauth-discovery.ts +20 -20
  28. package/src/mcp/transports/stdio.test.ts +20 -3
  29. package/src/mcp/transports/stdio.ts +45 -14
  30. package/src/modes/controllers/input-controller.ts +2 -2
  31. package/src/modes/controllers/selector-controller.ts +10 -0
  32. package/src/modes/interactive-mode.ts +26 -9
  33. package/src/modes/theme/mermaid-rendering.test.ts +53 -0
  34. package/src/modes/theme/theme.ts +33 -14
  35. package/src/prompts/system/plan-mode-active.md +9 -0
  36. package/src/prompts/system/system-prompt.md +2 -0
  37. package/src/sdk.ts +43 -4
  38. package/src/session/agent-session.ts +323 -62
  39. package/src/session/session-listing.ts +35 -2
  40. package/src/slash-commands/builtin-registry.ts +20 -2
  41. package/src/system-prompt.ts +4 -0
  42. package/src/tools/acp-bridge.ts +6 -1
  43. package/src/tools/plan-mode-guard.ts +26 -13
  44. package/src/utils/edit-mode.ts +19 -2
  45. package/src/utils/shell-snapshot.ts +1 -1
@@ -10,7 +10,7 @@
10
10
  import type { ToolSession } from ".";
11
11
  import { invalidateFsScanAfterWrite } from "./fs-cache-invalidation";
12
12
  import { isInternalUrlPath } from "./path-utils";
13
- import { resolvePlanPath } from "./plan-mode-guard";
13
+ import { resolvePlanPath, targetsLocalSandbox } from "./plan-mode-guard";
14
14
  import { ToolError } from "./tool-errors";
15
15
 
16
16
  /**
@@ -26,6 +26,11 @@ export function shouldRouteWriteThroughBridge(
26
26
  absolutePath: string,
27
27
  ): boolean {
28
28
  if (isInternalUrlPath(requestedPath)) return false;
29
+ // OMP-owned session artifacts (plan files, scratch notes) must stay off the
30
+ // editor buffer even when addressed by their absolute sandbox path — e.g.
31
+ // after tag-based path recovery rebinds a bare `plan.md#tag` onto the
32
+ // `local://` artifact, `requestedPath` is the absolute path, not the URL.
33
+ if (targetsLocalSandbox(session, absolutePath)) return false;
29
34
 
30
35
  const state = session.getPlanModeState?.();
31
36
  if (!state?.enabled || !isInternalUrlPath(state.planFilePath)) return true;
@@ -1,7 +1,12 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { HL_FILE_HASH_LENGTH, HL_FILE_HASH_SEP, HL_FILE_PREFIX, HL_FILE_SUFFIX } from "@oh-my-pi/hashline";
4
- import { resolveLocalRoot, resolveLocalUrlToPath, resolveVaultUrlToPath } from "../internal-urls";
4
+ import {
5
+ type LocalProtocolOptions,
6
+ resolveLocalRoot,
7
+ resolveLocalUrlToPath,
8
+ resolveVaultUrlToPath,
9
+ } from "../internal-urls";
5
10
  import type { ToolSession } from ".";
6
11
  import { normalizeLocalScheme, resolveToCwd } from "./path-utils";
7
12
  import { ToolError } from "./tool-errors";
@@ -10,16 +15,27 @@ const VAULT_SCHEME_PREFIX = "vault:";
10
15
  const LOCAL_SCHEME_PREFIX = "local:";
11
16
  const HL_TRAILING_TAG_RE = new RegExp(`${HL_FILE_HASH_SEP}[0-9A-Fa-f]{${HL_FILE_HASH_LENGTH}}$`);
12
17
 
18
+ /** Resolve the `local://` options the session uses, preferring its own
19
+ * {@link LocalProtocolOptions} (the mapping `read`/`write`/`eval` resolve
20
+ * through) over the bare `getArtifactsDir`/`getSessionId` pair. Subagents and
21
+ * multi-session hosts (cmux/ACP, embedded SDK) pin `local://` to a parent/foreign
22
+ * root via `localProtocolOptions`; the sandbox root the plan-mode guard derives
23
+ * must match where the artifact actually lives, or it rejects a legitimate plan
24
+ * edit (and tag-based path recovery onto the sandbox would miss it). */
25
+ function planLocalProtocolOptions(session: ToolSession): LocalProtocolOptions {
26
+ return (
27
+ session.localProtocolOptions ?? {
28
+ getArtifactsDir: () => session.getArtifactsDir?.() ?? null,
29
+ getSessionId: () => session.getSessionId?.() ?? null,
30
+ }
31
+ );
32
+ }
33
+
13
34
  /** Resolve the absolute path of the session's `local://` artifact sandbox.
14
35
  * Returns `null` when the session has no artifact wiring (e.g. tests). */
15
36
  function localSandboxRoot(session: ToolSession): string | null {
16
37
  try {
17
- return path.resolve(
18
- resolveLocalRoot({
19
- getArtifactsDir: session.getArtifactsDir,
20
- getSessionId: session.getSessionId,
21
- }),
22
- );
38
+ return path.resolve(resolveLocalRoot(planLocalProtocolOptions(session)));
23
39
  } catch {
24
40
  return null;
25
41
  }
@@ -62,8 +78,8 @@ export function unwrapHashlineHeaderPath(targetPath: string): string {
62
78
  * always agree on the absolute target (including bracketed hashline headers,
63
79
  * `local://` URLs, and bare absolute paths). Files inside the sandbox are not
64
80
  * part of the working tree, so plan mode treats them as freely writable
65
- * scratch/plan space. */
66
- function targetsLocalSandbox(session: ToolSession, targetPath: string): boolean {
81
+ * scratch/plan space — and tag-based path recovery may rebind onto them. */
82
+ export function targetsLocalSandbox(session: ToolSession, targetPath: string): boolean {
67
83
  const root = localSandboxRoot(session);
68
84
  if (!root) return false;
69
85
  let resolved: string;
@@ -99,10 +115,7 @@ export function resolvePlanPath(session: ToolSession, targetPath: string): strin
99
115
  const unwrapped = unwrapHashlineHeaderPath(targetPath);
100
116
  const normalized = normalizeLocalScheme(unwrapped);
101
117
  if (normalized.startsWith(LOCAL_SCHEME_PREFIX)) {
102
- return resolveLocalUrlToPath(normalized, {
103
- getArtifactsDir: session.getArtifactsDir,
104
- getSessionId: session.getSessionId,
105
- });
118
+ return resolveLocalUrlToPath(normalized, planLocalProtocolOptions(session));
106
119
  }
107
120
 
108
121
  if (normalized.startsWith(VAULT_SCHEME_PREFIX)) {
@@ -1,4 +1,4 @@
1
- import { $env } from "@oh-my-pi/pi-utils";
1
+ import { $env, $flag } from "@oh-my-pi/pi-utils";
2
2
 
3
3
  export type EditMode = "replace" | "patch" | "hashline" | "apply_patch";
4
4
 
@@ -13,6 +13,19 @@ const EDIT_MODE_IDS = {
13
13
 
14
14
  export const EDIT_MODES = Object.keys(EDIT_MODE_IDS) as EditMode[];
15
15
 
16
+ const HASHLINE_EXCLUDED_MODEL_MODES: Array<{ pattern: string; mode: EditMode }> = [
17
+ { pattern: "kimi", mode: "replace" },
18
+ ];
19
+
20
+ function resolveHashlineExcludedModelMode(model: string | undefined): EditMode | null {
21
+ if (!model) return null;
22
+ const modelLower = model.toLowerCase();
23
+ for (const entry of HASHLINE_EXCLUDED_MODEL_MODES) {
24
+ if (modelLower.includes(entry.pattern)) return entry.mode;
25
+ }
26
+ return null;
27
+ }
28
+
16
29
  export function normalizeEditMode(mode?: string | null): EditMode | undefined {
17
30
  if (!mode) return undefined;
18
31
  return EDIT_MODE_IDS[mode as keyof typeof EDIT_MODE_IDS];
@@ -37,5 +50,9 @@ export function resolveEditMode(session: EditModeSessionLike): EditMode {
37
50
  if (envMode) return envMode;
38
51
 
39
52
  const settingsMode = normalizeEditMode(String(session.settings.get("edit.mode") ?? ""));
40
- return settingsMode ?? DEFAULT_EDIT_MODE;
53
+ const mode = settingsMode ?? DEFAULT_EDIT_MODE;
54
+ if (mode === "hashline" && !$flag("PI_STRICT_EDIT_MODE")) {
55
+ return resolveHashlineExcludedModelMode(activeModel) ?? mode;
56
+ }
57
+ return mode;
41
58
  }
@@ -17,7 +17,7 @@ const SNAPSHOT_TIMEOUT_MS = 2_000;
17
17
  /**
18
18
  * Characters that force brush's primitive alias expander down a path it does
19
19
  * not implement. brush-core resolves aliases via `value.split_ascii_whitespace()`
20
- * (`crates/brush-core-vendored/src/interp.rs:1500`, tracking
20
+ * (`crates/vendor/brush-core/src/interp.rs:1500`, tracking
21
21
  * https://github.com/reubeno/brush/issues/57): the resulting pieces are dropped
22
22
  * into argv verbatim instead of going through the shell parser. Any alias body
23
23
  * containing subshells `(...)`, pipes `|`, redirections `<` `>`, separators