dsh-hashline-edit 0.2.0 → 0.2.2
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/README.md +59 -55
- package/cordis.patch.yml +1 -1
- package/lib/index.js +7225 -6445
- package/lib/types/dsh/contracts.d.ts +3 -12
- package/lib/types/dsh/fs.d.ts +0 -3
- package/lib/types/dsh/guidance.d.ts +5 -5
- package/lib/types/dsh/host-compat.d.ts +78 -7
- package/lib/types/dsh/index.d.ts +7 -17
- package/lib/types/dsh/metrics/events.d.ts +1 -1
- package/lib/types/dsh/metrics/observer.d.ts +1 -1
- package/lib/types/dsh/sandbox.d.ts +2 -2
- package/lib/types/dsh/shell-hook.d.ts +1 -1
- package/lib/types/dsh/tools/edit.d.ts +10 -0
- package/lib/types/dsh/tools/read.d.ts +3 -3
- package/lib/types/src/domain-errors.d.ts +2 -16
- package/lib/types/src/edit-tool.d.ts +41 -0
- package/lib/types/src/edit-undo.d.ts +2 -11
- package/lib/types/src/file-content/index.d.ts +1 -1
- package/lib/types/src/file-content/preview.d.ts +1 -1
- package/lib/types/src/hash-store.d.ts +2 -2
- package/lib/types/src/hashline/alphabet.d.ts +16 -1
- package/lib/types/src/hashline/hash-identity.d.ts +10 -4
- package/lib/types/src/mutation-engine/engine.d.ts +31 -0
- package/lib/types/src/mutation-engine/pipeline.d.ts +1 -8
- package/lib/types/src/mutation-engine/types.d.ts +50 -40
- package/lib/types/src/payload-contract.d.ts +135 -0
- package/lib/types/src/served-session/serve-render.d.ts +0 -6
- package/lib/types/src/undo-store.d.ts +1 -17
- package/package.json +21 -10
- package/prompts/edit-guidelines.md +10 -0
- package/prompts/edit-snippet.md +1 -0
- package/prompts/edit.md +1 -0
- package/prompts/read-guidelines.md +4 -4
- package/prompts/read.md +1 -1
- package/prompts/undo-last-edit-guidelines.md +2 -2
- package/prompts/undo-last-edit-snippet.md +1 -1
- package/prompts/undo-last-edit.md +1 -1
- package/src/bash-classifier.ts +1626 -0
- package/src/bash-conversion/decide.ts +812 -0
- package/src/bash-display-transform.ts +50 -0
- package/src/bash-multifile-config.ts +239 -0
- package/src/bash-view-config.ts +158 -0
- package/src/cut-repair.ts +11 -77
- package/src/domain-errors.ts +3 -35
- package/src/edit-normalize.ts +8 -0
- package/src/edit-render.ts +161 -0
- package/src/edit-tool.ts +118 -0
- package/src/edit-undo.ts +25 -379
- package/src/edit.ts +136 -0
- package/src/file-content/index.ts +1 -1
- package/src/file-content/preview.ts +1 -1
- package/src/file-content/served-header.ts +1 -1
- package/src/hash-store.ts +1 -18
- package/src/hashline/alphabet.ts +42 -1
- package/src/hashline/hash-identity.ts +12 -3
- package/src/integrations/pi-lens/command.ts +712 -0
- package/src/integrations/pi-lens/config.ts +148 -0
- package/src/integrations/pi-lens/io-bridge-adapter.ts +234 -0
- package/src/integrations/pi-lens/io-bridge.ts +112 -0
- package/src/integrations/pi-lens/settings-overlay.ts +272 -0
- package/src/lifecycle-hooks/index.ts +1992 -0
- package/src/lifecycle-hooks/types.ts +64 -0
- package/src/mutated-files.ts +75 -0
- package/src/mutation-engine/engine.ts +239 -0
- package/src/mutation-engine/index.ts +17 -0
- package/src/mutation-engine/pipeline.ts +27 -198
- package/src/mutation-engine/types.ts +57 -39
- package/src/mutation-queue.ts +1 -1
- package/src/payload-contract.ts +685 -0
- package/src/preview-controller.ts +84 -0
- package/src/prompts.ts +43 -10
- package/src/read.ts +865 -0
- package/src/search-hook.ts +41 -0
- package/src/served-session/serve-render.ts +2 -2
- package/src/store-config.ts +73 -0
- package/src/store-janitor-warning.ts +169 -0
- package/src/store-janitor.ts +539 -0
- package/src/store-kernel/settings.ts +233 -0
- package/src/store.ts +154 -0
- package/src/tui-presenter.ts +134 -0
- package/src/undo-store.ts +5 -74
- package/src/write-hook.ts +130 -0
- package/lib/types/dsh/hide.d.ts +0 -78
- package/lib/types/dsh/tools/apply-patch.d.ts +0 -38
- package/lib/types/src/apply-patch/commit.d.ts +0 -49
- package/lib/types/src/apply-patch/grammar.d.ts +0 -29
- package/lib/types/src/apply-patch/lifecycle.d.ts +0 -32
- package/lib/types/src/apply-patch/plan.d.ts +0 -50
- package/lib/types/src/apply-patch/render.d.ts +0 -51
- package/lib/types/src/apply-patch/resolve.d.ts +0 -129
- package/lib/types/src/apply-patch/tool.d.ts +0 -54
- package/lib/types/src/apply-patch/types.d.ts +0 -362
- package/lib/types/src/fs-lifecycle.d.ts +0 -44
- package/lib/types/src/host-utils.d.ts +0 -63
- package/lib/types/src/mutation-engine/edit-request.d.ts +0 -34
- package/prompts/apply-patch-guidelines.md +0 -10
- package/prompts/apply-patch.md +0 -1
- package/src/apply-patch/commit.ts +0 -190
- package/src/apply-patch/grammar.ts +0 -620
- package/src/apply-patch/index.ts +0 -17
- package/src/apply-patch/lifecycle.ts +0 -195
- package/src/apply-patch/plan.ts +0 -800
- package/src/apply-patch/render.ts +0 -137
- package/src/apply-patch/resolve.ts +0 -470
- package/src/apply-patch/tool.ts +0 -257
- package/src/apply-patch/types.ts +0 -387
- package/src/fs-lifecycle.ts +0 -201
- package/src/host-utils.ts +0 -224
- package/src/mutation-engine/edit-request.ts +0 -39
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Shared host contracts for the dsh adapter.
|
|
3
3
|
*
|
|
4
4
|
* The `dsh/` tree is the DeepSeek Harness host layer over the host-agnostic
|
|
5
|
-
* hashline core in `src/`. Every lane (host, read,
|
|
5
|
+
* hashline core in `src/`. Every lane (host, read, edit, platform) compiles
|
|
6
6
|
* against this file; renaming or reshaping an export here is a cross-lane
|
|
7
7
|
* breaking change.
|
|
8
8
|
*
|
|
@@ -67,15 +67,6 @@ export interface FileIO {
|
|
|
67
67
|
stat(path: string, signal?: AbortSignal): Promise<FileStat | undefined>;
|
|
68
68
|
/** Direct child names of a directory, sorted lexicographically. */
|
|
69
69
|
readdir(path: string, signal?: AbortSignal): Promise<string[]>;
|
|
70
|
-
/**
|
|
71
|
-
* Delete one regular file. A lifecycle delete targets a file; a directory at the same path is
|
|
72
|
-
* a caller bug that must surface (never a recursive removal).
|
|
73
|
-
*/
|
|
74
|
-
remove?(path: string, signal?: AbortSignal): Promise<void>;
|
|
75
|
-
/** Rename one regular file. The destination must not exist and its parent is created first. */
|
|
76
|
-
rename?(from: string, to: string, signal?: AbortSignal): Promise<void>;
|
|
77
|
-
/** Create one directory and missing parents. */
|
|
78
|
-
mkdir?(path: string, signal?: AbortSignal): Promise<void>;
|
|
79
70
|
}
|
|
80
71
|
/** The two sandbox-escalation arguments a mutating tool may carry. */
|
|
81
72
|
export interface SandboxArgs {
|
|
@@ -133,7 +124,7 @@ export interface SandboxController {
|
|
|
133
124
|
*
|
|
134
125
|
* The host's observation policy fills its per-session state exclusively from
|
|
135
126
|
* `fs/observed`, and that state is what lets a built-in `write` replace an
|
|
136
|
-
* existing file. Shadowed `read`/`
|
|
127
|
+
* existing file. Shadowed `read`/`edit` calls never touch the provider's own
|
|
137
128
|
* write path, so without this channel a file this plugin served stays
|
|
138
129
|
* "unobserved" and the built-in overwrite is refused forever.
|
|
139
130
|
*
|
|
@@ -199,7 +190,7 @@ export interface ToolDependencies {
|
|
|
199
190
|
* factories at registration time. The fixed names are part of the lane
|
|
200
191
|
* contract:
|
|
201
192
|
* - `dsh/tools/read.ts` → `buildReadTool` (tool name `read`)
|
|
202
|
-
* - `dsh/tools/
|
|
193
|
+
* - `dsh/tools/edit.ts` → `buildEditTool` (tool name `edit`)
|
|
203
194
|
* - `dsh/tools/undo.ts` → `buildUndoTool` (tool name `undo_last_edit`)
|
|
204
195
|
*/
|
|
205
196
|
export type ToolDefinitionFactory = (deps: ToolDependencies) => ToolDefinition;
|
package/lib/types/dsh/fs.d.ts
CHANGED
|
@@ -41,9 +41,6 @@ export declare class LocalFileIO implements FileIO {
|
|
|
41
41
|
write(path: string, data: Uint8Array | string, signal?: AbortSignal): Promise<void>;
|
|
42
42
|
stat(path: string, signal?: AbortSignal): Promise<FileStat | undefined>;
|
|
43
43
|
readdir(path: string, signal?: AbortSignal): Promise<string[]>;
|
|
44
|
-
remove(path: string, signal?: AbortSignal): Promise<void>;
|
|
45
|
-
rename(from: string, to: string, signal?: AbortSignal): Promise<void>;
|
|
46
|
-
mkdir(path: string, signal?: AbortSignal): Promise<void>;
|
|
47
44
|
}
|
|
48
45
|
/** Create the M1 local filesystem seam. */
|
|
49
46
|
export declare function createLocalFileIO(): FileIO;
|
|
@@ -26,20 +26,20 @@ export declare function parseSectionFile(content: string): ParsedSection;
|
|
|
26
26
|
export declare function isBlankOverride(content: string): boolean;
|
|
27
27
|
/** One overridable tool section. */
|
|
28
28
|
export interface GuidanceSection {
|
|
29
|
-
/** Registry section name, e.g. `tool:
|
|
29
|
+
/** Registry section name, e.g. `tool:edit`. */
|
|
30
30
|
readonly name: string;
|
|
31
|
-
/** Override file name inside a preset directory, e.g. `
|
|
31
|
+
/** Override file name inside a preset directory, e.g. `edit.md`. */
|
|
32
32
|
readonly file: string;
|
|
33
33
|
/** Order used when the harness registry names no slot for this section. */
|
|
34
34
|
readonly defaultOrder: number;
|
|
35
35
|
/** The `systemPrompt.getSectionOrder` slot this section shadows, when one exists. */
|
|
36
|
-
readonly orderName?: "TOOL_READ" | "TOOL_EDIT"
|
|
36
|
+
readonly orderName?: "TOOL_READ" | "TOOL_EDIT";
|
|
37
37
|
/** The compiled default text. */
|
|
38
38
|
renderDefault(): string;
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
|
-
* The three sections, in default-order sequence.
|
|
42
|
-
* host's
|
|
41
|
+
* The three sections, in default-order sequence. Defaults are byte-identical to
|
|
42
|
+
* the pi host's guidance because they come from the same prompt sources.
|
|
43
43
|
*/
|
|
44
44
|
export declare const GUIDANCE_SECTIONS: readonly GuidanceSection[];
|
|
45
45
|
/** Render the compiled default text for one section. */
|
|
@@ -1,11 +1,82 @@
|
|
|
1
|
+
/** Retained line budget, matching pi's `DEFAULT_MAX_LINES`. */
|
|
2
|
+
export declare const DEFAULT_MAX_LINES = 2000;
|
|
3
|
+
/** Retained byte budget, matching pi's `DEFAULT_MAX_BYTES` (50KB). */
|
|
4
|
+
export declare const DEFAULT_MAX_BYTES: number;
|
|
5
|
+
/** The truncation report the core's read page renders. */
|
|
6
|
+
export interface TruncationResult {
|
|
7
|
+
/** The truncated content. */
|
|
8
|
+
content: string;
|
|
9
|
+
/** Whether truncation occurred. */
|
|
10
|
+
truncated: boolean;
|
|
11
|
+
/** Which limit was hit: `"lines"`, `"bytes"`, or `null` when nothing was cut. */
|
|
12
|
+
truncatedBy: "lines" | "bytes" | null;
|
|
13
|
+
/** Total number of lines in the original content. */
|
|
14
|
+
totalLines: number;
|
|
15
|
+
/** Total number of bytes in the original content. */
|
|
16
|
+
totalBytes: number;
|
|
17
|
+
/** Number of complete lines in the truncated output. */
|
|
18
|
+
outputLines: number;
|
|
19
|
+
/** Number of bytes in the truncated output. */
|
|
20
|
+
outputBytes: number;
|
|
21
|
+
/** Whether the last line was partially truncated (tail truncation only). */
|
|
22
|
+
lastLinePartial: boolean;
|
|
23
|
+
/** Whether the first line exceeded the byte limit (head truncation only). */
|
|
24
|
+
firstLineExceedsLimit: boolean;
|
|
25
|
+
/** The line limit that was applied. */
|
|
26
|
+
maxLines: number;
|
|
27
|
+
/** The byte limit that was applied. */
|
|
28
|
+
maxBytes: number;
|
|
29
|
+
}
|
|
30
|
+
/** The two independent limits a truncation call applies. */
|
|
31
|
+
export interface TruncationOptions {
|
|
32
|
+
/** Maximum number of lines; defaults to {@link DEFAULT_MAX_LINES}. */
|
|
33
|
+
maxLines?: number;
|
|
34
|
+
/** Maximum number of bytes; defaults to {@link DEFAULT_MAX_BYTES}. */
|
|
35
|
+
maxBytes?: number;
|
|
36
|
+
}
|
|
1
37
|
/**
|
|
2
|
-
*
|
|
38
|
+
* Minimal structural stand-in for pi's extension-registry type.
|
|
3
39
|
*
|
|
4
|
-
* WHY:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
40
|
+
* WHY: `src/edit-undo.ts` keeps the pi registration (and its `ExtensionAPI` type) next to the
|
|
41
|
+
* host-agnostic use case the dsh undo tool calls. The dsh build resolves the pi package to this
|
|
42
|
+
* shim and never invokes that registration, so the build typecheck needs only the single method
|
|
43
|
+
* the registration calls; the root tsconfig still checks the registration against the real pi API.
|
|
44
|
+
*/
|
|
45
|
+
export interface ExtensionAPI {
|
|
46
|
+
/**
|
|
47
|
+
* Register one pi tool definition. The argument is intentionally structural and loose: the dsh
|
|
48
|
+
* program only needs the registration to compile, never to execute, and the loose `execute`
|
|
49
|
+
* signature keeps the pi adapter's untyped callback out of the dsh build's implicit-any checks.
|
|
50
|
+
*/
|
|
51
|
+
registerTool(definition: {
|
|
52
|
+
execute: (...args: any[]) => unknown;
|
|
53
|
+
[key: string]: unknown;
|
|
54
|
+
}): void;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Format bytes as a human-readable size (`512B`, `1.5KB`, `2.0MB`).
|
|
58
|
+
* @param bytes - the byte count to render.
|
|
59
|
+
* @returns the pi-compatible size label.
|
|
60
|
+
*/
|
|
61
|
+
export declare function formatSize(bytes: number): string;
|
|
62
|
+
/**
|
|
63
|
+
* Truncate content from the head (keep the first N lines / bytes).
|
|
8
64
|
*
|
|
9
|
-
*
|
|
65
|
+
* WHY: the read page's oversize and truncation warnings must agree with the pi
|
|
66
|
+
* implementation the rest of the contract was written against, so this is a
|
|
67
|
+
* faithful port including its two edge rules: complete lines only, and an
|
|
68
|
+
* over-long first line yields empty content with `firstLineExceedsLimit`.
|
|
69
|
+
* @param content - the text to truncate.
|
|
70
|
+
* @param options - the line and byte limits.
|
|
71
|
+
* @returns the truncation report.
|
|
72
|
+
*/
|
|
73
|
+
export declare function truncateHead(content: string, options?: TruncationOptions): TruncationResult;
|
|
74
|
+
/**
|
|
75
|
+
* Serialize file mutation operations that target the same file; different files still run
|
|
76
|
+
* in parallel. A faithful port of pi's queue so the core's cut transaction keeps its
|
|
77
|
+
* lock ordering guarantees under the dsh host.
|
|
78
|
+
* @param filePath - the file the mutation targets.
|
|
79
|
+
* @param run - the critical section to run once the path is free.
|
|
80
|
+
* @returns the critical section's result.
|
|
10
81
|
*/
|
|
11
|
-
export
|
|
82
|
+
export declare function withFileMutationQueue<T>(filePath: string, run: () => Promise<T>): Promise<T>;
|
package/lib/types/dsh/index.d.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-hashline-edit host plugin entry.
|
|
3
3
|
*
|
|
4
|
-
* On every `agent/created`, install the hashline tools on the AGENT's
|
|
5
|
-
* own scope layer so they shadow the preset's built-in `read` for that
|
|
4
|
+
* On every `agent/created`, install the three hashline tools on the AGENT's
|
|
5
|
+
* own scope layer so they shadow the preset's built-in `read`/`edit` for that
|
|
6
6
|
* agent (nearest layer wins in dsh's tool registry) and unwind automatically
|
|
7
7
|
* when the agent is disposed. The built-in `write` stays in place; the
|
|
8
8
|
* platform lane's write hook integrates with it.
|
|
9
9
|
*
|
|
10
|
+
* `inject` deliberately lists only `tools` and `systemPrompt`: M1 has no
|
|
10
11
|
* `inject` deliberately lists only `tools` and `systemPrompt`: the read-path
|
|
11
12
|
* `ctx.fs` bridge is optional and resolved with `ctx.get`, so a minimal
|
|
12
|
-
* composition still loads with the local `node:fs` fallback.
|
|
13
|
-
*
|
|
13
|
+
* composition still loads with the local `node:fs` fallback. Edit/undo stay
|
|
14
|
+
* local-only (the M5 bridge covers reads; M6 owns mutation).
|
|
14
15
|
* @module dsh-hashline-edit
|
|
15
16
|
*/
|
|
16
17
|
import type { Context } from "@deepseek-ai/cordis";
|
|
@@ -23,17 +24,6 @@ export declare const name = "dsh-hashline-edit";
|
|
|
23
24
|
* services are resolved with `ctx.get` inside `dsh/sandbox.ts` instead.
|
|
24
25
|
*/
|
|
25
26
|
export declare const inject: string[];
|
|
26
|
-
/** The plugin configuration the loader passes as the second `apply` argument. */
|
|
27
|
-
export interface PluginConfig {
|
|
28
|
-
/**
|
|
29
|
-
* Hide the native `edit`/`str_replace_editor` tools so every precise text
|
|
30
|
-
* change goes through `apply_patch`. Defaults to true. `write` is not in
|
|
31
|
-
* that set on purpose: whole-file replacement has no apply_patch operation.
|
|
32
|
-
*/
|
|
33
|
-
hideNativeWriteTools?: boolean;
|
|
34
|
-
/** Extra global tool names to hide alongside the native write set. */
|
|
35
|
-
hiddenToolNames?: readonly string[];
|
|
36
|
-
}
|
|
37
27
|
/**
|
|
38
28
|
* Install the hashline toolset for one agent inside its own effect, so the
|
|
39
29
|
* registrations are disposed in reverse order when the agent scope unwinds.
|
|
@@ -41,6 +31,6 @@ export interface PluginConfig {
|
|
|
41
31
|
* Exported separately from {@link apply} so the host test can drive one fake
|
|
42
32
|
* agent directly and integration probes can reuse the exact install path.
|
|
43
33
|
*/
|
|
44
|
-
export declare function installAgentTools(rootCtx: Context, agent: Agent, metrics?: ToolMetrics
|
|
34
|
+
export declare function installAgentTools(rootCtx: Context, agent: Agent, metrics?: ToolMetrics): void;
|
|
45
35
|
/** Mount the plugin: install the hashline toolset on every new agent. */
|
|
46
|
-
export declare function apply(rootCtx: Context
|
|
36
|
+
export declare function apply(rootCtx: Context): void;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* lets the global observer record any other host tool — the built-in `write`,
|
|
6
6
|
* `pwsh`, `grep`, … — without widening this union once per tool.
|
|
7
7
|
*/
|
|
8
|
-
type ToolName = "read" | "
|
|
8
|
+
type ToolName = "read" | "edit" | "undo_last_edit" | "write" | "shell_view" | (string & {});
|
|
9
9
|
/** One tool call, as written to the JSONL file. Character counts are exact; the
|
|
10
10
|
* token estimate belongs to the summarizer so the ratio stays a reporting
|
|
11
11
|
* choice instead of being frozen into the event stream. */
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Global tool-call telemetry for the dsh host layer.
|
|
3
3
|
*
|
|
4
4
|
* Before this observer the adapter measured only its own surface: `read`,
|
|
5
|
-
* `
|
|
5
|
+
* `edit` and `undo_last_edit` from the tool boundaries, plus `shell_view` from
|
|
6
6
|
* the shell hook when it rewrites a result. Every other call the host
|
|
7
7
|
* dispatched — the built-in `write` that sits next to this plugin's shadowed
|
|
8
8
|
* tools, `pwsh`, `grep`, … — left no event at all, so no baseline could say
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Mirrors the built-in `@deepseek-ai/dsh-tool-fs` controller (same
|
|
5
5
|
* vocabulary, same fail-closed approval sequence, shared
|
|
6
|
-
* `@deepseek-ai/dsh-sandbox` pieces) so `
|
|
6
|
+
* `@deepseek-ai/dsh-sandbox` pieces) so `edit`/`undo_last_edit` ask for wider
|
|
7
7
|
* access exactly like the built-in `write`/`edit`. The differences are
|
|
8
8
|
* deliberate M1 facts:
|
|
9
9
|
*
|
|
@@ -21,7 +21,7 @@ import type { ToolExecution } from "@deepseek-ai/dsh-tools";
|
|
|
21
21
|
import type { SandboxMode } from "@deepseek-ai/dsh-sandbox";
|
|
22
22
|
import type { SandboxArgs, SandboxController, SandboxDecision } from "./contracts.js";
|
|
23
23
|
/**
|
|
24
|
-
* The filesystem escalation controller shared by `
|
|
24
|
+
* The filesystem escalation controller shared by `edit` and `undo_last_edit`
|
|
25
25
|
* for one agent install.
|
|
26
26
|
*/
|
|
27
27
|
export declare class FsSandboxController implements SandboxController {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* foreground stdout channel, or a shell view that refuses (not whitelisted /
|
|
8
8
|
* ambiguous alignment) all pass the decision through untouched. Only a
|
|
9
9
|
* successful, uniquely aligned view is rewritten, and its rows are served
|
|
10
|
-
* through the read tool's seam so a following `
|
|
10
|
+
* through the read tool's seam so a following `edit` resolves the anchors.
|
|
11
11
|
*
|
|
12
12
|
* Both foreground value shapes the host may project are understood:
|
|
13
13
|
* `{ kind: "foreground", stdout: { text, truncated } }` (the canonical
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ToolDefinition } from "@deepseek-ai/dsh-tools";
|
|
2
|
+
import type { ToolDependencies } from "../contracts.js";
|
|
3
|
+
/** dsh tool name; the agent-layer registration shadows the preset's built-in `edit`. */
|
|
4
|
+
export declare const EDIT_TOOL_NAME = "edit";
|
|
5
|
+
/**
|
|
6
|
+
* Build the dsh `edit` tool for one agent install.
|
|
7
|
+
* @param deps - the host seams this tool may touch; `sandbox` advertises and resolves escalation.
|
|
8
|
+
* @returns a registry-ready dsh tool definition named `edit`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildEditTool(deps: ToolDependencies): ToolDefinition;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The dsh `read` tool: hash-anchored reads (`HASH│content` rows) that shadow
|
|
3
3
|
* the built-in `read` on the agent's own scope layer. In the default `served`
|
|
4
|
-
* mode every shown row is recorded as served, so a later `
|
|
4
|
+
* mode every shown row is recorded as served, so a later `edit` can verify the
|
|
5
5
|
* model was actually shown the lines it targets; `verbatim` mode is a plain
|
|
6
6
|
* reference read that grants no anchor and leaves served state untouched.
|
|
7
7
|
*
|
|
@@ -54,7 +54,7 @@ export declare function createDefaultReadSpanProvider(io?: CoreReadIo): SpanProv
|
|
|
54
54
|
export interface ServedFileLine {
|
|
55
55
|
/** 1-based line number the anchor addresses. */
|
|
56
56
|
readonly line: number;
|
|
57
|
-
/** The line's 4-character anchor, usable by `
|
|
57
|
+
/** The line's 4-character anchor, usable by `edit`. */
|
|
58
58
|
readonly hash: string;
|
|
59
59
|
/** The line's decoded text, without the anchor prefix. */
|
|
60
60
|
readonly text: string;
|
|
@@ -84,7 +84,7 @@ export declare function decodeFileLines(deps: ToolDependencies, request: ServeFi
|
|
|
84
84
|
*
|
|
85
85
|
* WHY a separate seam: the shell view must turn already-aligned disk lines into
|
|
86
86
|
* editable anchors without re-implementing the read pipeline, and those anchors
|
|
87
|
-
* must be leased to the session the following `
|
|
87
|
+
* must be leased to the session the following `edit` uses.
|
|
88
88
|
*/
|
|
89
89
|
export declare function serveFileLines(deps: ToolDependencies, request: ServeFileLinesRequest): Promise<ServedFileLine[]>;
|
|
90
90
|
/**
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
* See ADR-0047.
|
|
29
29
|
*/
|
|
30
30
|
export type Audience = "MODEL" | "USER";
|
|
31
|
-
export type DomainErrorCode = "E_BAD_PAYLOAD" | "E_EMPTY_RANGE" | "E_STALE_ANCHOR" | "E_UNKNOWN_ANCHOR" | "E_FOREIGN_ANCHOR" | "E_STALE_RANGE" | "E_TARGET_LOST" | "E_UNVERIFIED_RANGE" | "E_MALFORMED_ANCHOR" | "E_SUSPICIOUS_TEXT" | "E_BATCH_ABORT" | "E_NOOP_LOOP" | "E_UNSUPPORTED_FILE" | "E_ACCESS" | "E_NOT_FOUND" | "E_UNDO_STALE" | "E_UNDO_UNAVAILABLE" | "E_LOSSY_TEXT" | "E_UNDO_REVERT_FAILED" | "E_UNKNOWN" | "E_LARGE_FILE" | "E_INTERNAL_INVARIANT"
|
|
31
|
+
export type DomainErrorCode = "E_BAD_PAYLOAD" | "E_EMPTY_RANGE" | "E_STALE_ANCHOR" | "E_UNKNOWN_ANCHOR" | "E_FOREIGN_ANCHOR" | "E_STALE_RANGE" | "E_TARGET_LOST" | "E_UNVERIFIED_RANGE" | "E_MALFORMED_ANCHOR" | "E_SUSPICIOUS_TEXT" | "E_BATCH_ABORT" | "E_NOOP_LOOP" | "E_UNSUPPORTED_FILE" | "E_ACCESS" | "E_NOT_FOUND" | "E_UNDO_STALE" | "E_UNDO_UNAVAILABLE" | "E_LOSSY_TEXT" | "E_UNDO_REVERT_FAILED" | "E_UNKNOWN" | "E_LARGE_FILE" | "E_INTERNAL_INVARIANT";
|
|
32
32
|
/**
|
|
33
33
|
* The registered tool whose call produced an `E_BAD_PAYLOAD`. WHY the registered name: the model
|
|
34
34
|
* reads the tool list, so `undo_last_edit` names the call it must repair and a short form would
|
|
35
35
|
* name nothing. A tool belongs here only after a live path is measured, never by assumption.
|
|
36
36
|
*/
|
|
37
|
-
export type PayloadSubject = "read" | "edit" | "
|
|
37
|
+
export type PayloadSubject = "read" | "edit" | "undo_last_edit";
|
|
38
38
|
export declare function isPayloadSubject(value: unknown): value is PayloadSubject;
|
|
39
39
|
/**
|
|
40
40
|
* Applied-tier warning codes — the `W_*` namespace (spec
|
|
@@ -180,20 +180,6 @@ export interface ErrorPayloadMap {
|
|
|
180
180
|
*/
|
|
181
181
|
subject?: PayloadSubject;
|
|
182
182
|
};
|
|
183
|
-
E_PATCH_SYNTAX: {
|
|
184
|
-
message: string;
|
|
185
|
-
path?: string;
|
|
186
|
-
patchLine?: number;
|
|
187
|
-
servedRows?: ServedRow[];
|
|
188
|
-
servedBlock?: string;
|
|
189
|
-
};
|
|
190
|
-
E_UNIQUE_MATCH: {
|
|
191
|
-
message: string;
|
|
192
|
-
path?: string;
|
|
193
|
-
patchLine?: number;
|
|
194
|
-
servedRows?: ServedRow[];
|
|
195
|
-
servedBlock?: string;
|
|
196
|
-
};
|
|
197
183
|
}
|
|
198
184
|
export interface CodeSpec<P> {
|
|
199
185
|
audience: Audience;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** SAFETY: EditTool — deep module owning the edit Use Case seam.
|
|
2
|
+
*
|
|
3
|
+
* Graded surface: one narrow interface `EditTool { execute, preview }`
|
|
4
|
+
* hides pipeline delegation, warnings/drift stitching.
|
|
5
|
+
* Frameworks (pi TUI) never cross this seam — that lives in TuiPresenter.
|
|
6
|
+
* Clean Architecture: Use Cases depend inward only (mutation-engine),
|
|
7
|
+
* never outward to Frameworks.
|
|
8
|
+
*
|
|
9
|
+
* Typed boundaries: validate once at admission (payload-contract via normReq/assertReq),
|
|
10
|
+
* trust inside. Immutable state: never mutates caller-owned request.
|
|
11
|
+
*/
|
|
12
|
+
type EditToolContext = {
|
|
13
|
+
cwd: string;
|
|
14
|
+
sessionManager?: {
|
|
15
|
+
getSessionId(): string;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export type PreviewContext = {
|
|
19
|
+
sessionManager?: {
|
|
20
|
+
getSessionId(): string;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
type PreviewResult = {
|
|
24
|
+
diff: string;
|
|
25
|
+
} | {
|
|
26
|
+
error: string;
|
|
27
|
+
};
|
|
28
|
+
export interface EditTool {
|
|
29
|
+
/** SAFETY: Execute a validated edit; returns pi tool_result or throws on failure. Admission: params validated by normReq/assertReq. */
|
|
30
|
+
execute(params: unknown, signal: AbortSignal | undefined, ctx: EditToolContext): Promise<{
|
|
31
|
+
content: Array<{
|
|
32
|
+
type: "text";
|
|
33
|
+
text: string;
|
|
34
|
+
}>;
|
|
35
|
+
details: unknown;
|
|
36
|
+
}>;
|
|
37
|
+
/** SAFETY: Preview without persisting — mirrors pi's compPreview(request, cwd, ctx). The ctx must carry the session whose serves the anchors came from (#165). */
|
|
38
|
+
preview(request: unknown, cwd: string, ctx: PreviewContext): Promise<PreviewResult>;
|
|
39
|
+
}
|
|
40
|
+
export declare function createEditTool(): EditTool;
|
|
41
|
+
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
1
2
|
import { type LineEnding } from "./edit-diff.js";
|
|
2
|
-
import type { UndoLifecycle } from "./undo-store.js";
|
|
3
3
|
export interface UndoEntry {
|
|
4
4
|
content: string;
|
|
5
5
|
bom: string;
|
|
@@ -26,16 +26,6 @@ export interface UndoEntry {
|
|
|
26
26
|
rawPre?: string | null;
|
|
27
27
|
/** The anchor generation the stored hashes were derived under (0 = pre-generation). */
|
|
28
28
|
anchorGeneration?: number | null;
|
|
29
|
-
/** The lifecycle action this entry reverses; absent/null is an ordinary edit. */
|
|
30
|
-
lifecycle?: UndoLifecycle | null;
|
|
31
|
-
/** Move: the absolute pre-transaction path. */
|
|
32
|
-
fromPath?: string | null;
|
|
33
|
-
/** Move: the absolute committed path. */
|
|
34
|
-
toPath?: string | null;
|
|
35
|
-
/** The raw post-transaction text when the commit wrote non-canonical bytes (pure move). */
|
|
36
|
-
rawPost?: string | null;
|
|
37
|
-
/** Add: directories the add created, outermost first. */
|
|
38
|
-
createdDirs?: readonly string[] | null;
|
|
39
29
|
}
|
|
40
30
|
export declare function saveUndo(path: string, entry: UndoEntry): Promise<{
|
|
41
31
|
persisted: boolean;
|
|
@@ -77,3 +67,4 @@ export declare function executeUndoLastEdit(params: {
|
|
|
77
67
|
}, ctx: UndoLastEditContext, options?: {
|
|
78
68
|
withFileMutationQueue?: UndoMutationQueue;
|
|
79
69
|
}): Promise<UndoLastEditResult>;
|
|
70
|
+
export declare function regEditUndo(pi: ExtensionAPI): void;
|
|
@@ -12,7 +12,7 @@ import { visLines } from "../utils.js";
|
|
|
12
12
|
import { type FileStats, type LFile } from "./detection.js";
|
|
13
13
|
import { type ReadWindow, type ShownRange } from "./preview.js";
|
|
14
14
|
import type { ServedRow } from "../served-session/serve-render.js";
|
|
15
|
-
import type { TruncationResult } from "
|
|
15
|
+
import type { TruncationResult } from "@earendil-works/pi-coding-agent";
|
|
16
16
|
export type { FileStats, LFile, LoadFileOptions } from "./detection.js";
|
|
17
17
|
export { decodeUtf8Strict, loadFileKindAndText } from "./detection.js";
|
|
18
18
|
export { readNormFile, decodeNormText, fileSnap, snapshotIdFor, statKeyOf, type NormText, type NormFile, type SnapInfo, type StatIdentity, type ReadNormOptions, } from "./loader.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type TruncationResult } from "
|
|
1
|
+
import { type TruncationResult } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { type AnchorWalk } from "../hashline/hash-identity.js";
|
|
3
3
|
import type { ServedRow } from "../served-session/serve-render.js";
|
|
4
4
|
/** The lines one page actually rendered, 1-based and inclusive. */
|
|
@@ -14,9 +14,9 @@ export type StoreOpenHook = (db: DatabaseSync, info: {
|
|
|
14
14
|
}) => void | Promise<void>;
|
|
15
15
|
export declare function onStoreOpen(hook: StoreOpenHook): void;
|
|
16
16
|
/** `file_snapshots` rows whose persisted anchor generation is not the current one. */
|
|
17
|
-
export declare const FOREIGN_GENERATION_SNAPSHOT_SQL
|
|
17
|
+
export declare const FOREIGN_GENERATION_SNAPSHOT_SQL: string;
|
|
18
18
|
/** `served_leases` rows whose served snapshot hash carries a foreign anchor generation. */
|
|
19
|
-
export declare const FOREIGN_GENERATION_LEASE_SQL
|
|
19
|
+
export declare const FOREIGN_GENERATION_LEASE_SQL: string;
|
|
20
20
|
/** `line_lineage` rows naming a snapshot that no longer exists. */
|
|
21
21
|
export declare const ORPHAN_LINEAGE_SQL = "snapshot_id NOT IN (SELECT snapshot_id FROM file_snapshots)";
|
|
22
22
|
/**
|
|
@@ -1,4 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Anchor width — the single owner of the anchor's length and shape.
|
|
3
|
+
*
|
|
4
|
+
* WHY an environment variable rather than a settings-file key: `alphabet.ts` is a zero-import leaf
|
|
5
|
+
* and every shape, regex, bitset and capacity constant below is derived at module load, so the
|
|
6
|
+
* width can only be a process-wide, load-time value. Switching it changes every served anchor; the
|
|
7
|
+
* persisted store stays safe because the anchor generation (`hash-identity.ts`) folds the width in,
|
|
8
|
+
* so a switch is a hard generation miss (re-read), never a cross-width hit.
|
|
9
|
+
*/
|
|
10
|
+
export declare const ANCHOR_WIDTH_ENV_VAR = "PI_BETTER_EDIT_ANCHOR_WIDTH";
|
|
11
|
+
/**
|
|
12
|
+
* Resolve the anchor width from an environment map. Pure and dependency-free so the leaf stays a
|
|
13
|
+
* leaf and the ladder is unit-testable without mutating `process.env`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveAnchorWidth(env: Record<string, string | undefined>): number;
|
|
16
|
+
export declare const HASH_LEN: number;
|
|
2
17
|
export declare const ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
3
18
|
export declare const ALPHA_RE: RegExp;
|
|
4
19
|
export declare const HASH_CLASS: string;
|
|
@@ -42,7 +42,7 @@ export interface AnchorWalk {
|
|
|
42
42
|
/** The anchors this content already has in the store, in line order. */
|
|
43
43
|
cached?: string[];
|
|
44
44
|
}
|
|
45
|
-
export declare const ANCHOR_LEN
|
|
45
|
+
export declare const ANCHOR_LEN: number;
|
|
46
46
|
export declare const HASH_SEP = "\u2502";
|
|
47
47
|
export declare const HASH_SPACE: number;
|
|
48
48
|
export declare const USABLE_HASH_SPACE: number;
|
|
@@ -61,19 +61,25 @@ export declare const HASH_PROBE_STRIDE: number;
|
|
|
61
61
|
*/
|
|
62
62
|
export declare const CANON_VERSION = 3;
|
|
63
63
|
/**
|
|
64
|
-
*
|
|
64
|
+
* The file-scoped anchor-derivation generation (issue #20, ADR-0031; ADR-0020 for the width axis).
|
|
65
65
|
* It covers exactly: (1) the width (`HASH_LEN`, `ALPHA`, `HASH_SPACE`/`USABLE_HASH_SPACE`);
|
|
66
66
|
* (2) the (path, content) seeding (`fileBaseIndex` seeds xxh32 with the canonical absolute path,
|
|
67
67
|
* `contentBaseIndex` is the explicit content-only fallback); (3) the all-digit reservation
|
|
68
68
|
* (`DIGIT_CHARS`/`RESERVED_HASH_SPELLINGS`/`RESERVED_BITS` pre-marked in allocation); (4) the probe
|
|
69
69
|
* stride (`HASH_PROBE_STRIDE`, coprime with both spaces). Bump it whenever any of those change.
|
|
70
70
|
*
|
|
71
|
-
* WHY
|
|
71
|
+
* WHY it is a function of the width now: the width is a runtime switch (default 4, optional 3), so a
|
|
72
|
+
* width change MUST flip the generation or a store written at one width could be read at another.
|
|
73
|
+
* Width 4 stays generation 1 — the original persisted generation — so existing stores keep their
|
|
74
|
+
* anchors across this change; width 3 gets generation 2, and any future width gets `100 + width`.
|
|
75
|
+
*
|
|
76
|
+
* WHY: single owner of the anchor-generation value — anchor-bearing artifacts (the undo/snapshot
|
|
72
77
|
* generation gate and the open-time sweep) read this axis only, never `CANON_VERSION`; the snapshot
|
|
73
78
|
* cache key carries both (`${CANON_VERSION}:${ANCHOR_GENERATION}:${checksum}`), so a canon-only change
|
|
74
79
|
* misses the cache without invalidating anchor state while a generation change invalidates both.
|
|
75
80
|
*/
|
|
76
|
-
export declare
|
|
81
|
+
export declare function anchorGenerationFor(width: number): number;
|
|
82
|
+
export declare const ANCHOR_GENERATION: number;
|
|
77
83
|
export declare function canon(line: string): string;
|
|
78
84
|
/**
|
|
79
85
|
* SAFETY: the canon digest — `String(xxh32(canon(line)))`, the exact value `line_lineage.canon_hash`
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SAFETY: MutationEngine — deep module behind the mutation seam.
|
|
3
|
+
*
|
|
4
|
+
* Small interface: `execute` + `preview`. Deep implementation: load → parse
|
|
5
|
+
* → mutate → finalize → drift → persist → serve hidden inside the pipeline.
|
|
6
|
+
* Internal seams (validate, verify, mutate, guard, persist, record) stay
|
|
7
|
+
* private — not exported. The interface is the test surface.
|
|
8
|
+
*
|
|
9
|
+
* Typed boundaries: validate once at admission (edit.ts via payload-contract),
|
|
10
|
+
* trust inside. Errors fail loud with typed `MutationFailure`.
|
|
11
|
+
*/
|
|
12
|
+
import type { PipelineOptions } from "./types.js";
|
|
13
|
+
import type { MutationResult } from "./types.js";
|
|
14
|
+
import type { NormalizedEditRequest } from "../payload-contract.js";
|
|
15
|
+
declare function toFailure(error: unknown): MutationResult;
|
|
16
|
+
/**
|
|
17
|
+
* SAFETY: Execute a mutation against the file system (persist + undo + serve).
|
|
18
|
+
* Returns discriminated `MutationResult` — callers must switch on `ok`.
|
|
19
|
+
*
|
|
20
|
+
* No `any` threading: input is `NormalizedEditRequest` (validated at
|
|
21
|
+
* admission), output is typed. Failures are `ok:false` with `code`, not
|
|
22
|
+
* loose `isError` flags.
|
|
23
|
+
*/
|
|
24
|
+
export declare function execute(request: NormalizedEditRequest, cwd: string, options?: PipelineOptions): Promise<MutationResult>;
|
|
25
|
+
/**
|
|
26
|
+
* SAFETY: Preview a mutation without persisting (noPersist). Same seam as `execute`,
|
|
27
|
+
* same `MutationResult` — one interface, N call sites (preview + apply share
|
|
28
|
+
* the internal path via `runMutations` with `noPersist:true`).
|
|
29
|
+
*/
|
|
30
|
+
export declare function preview(request: NormalizedEditRequest, cwd: string, options?: Omit<PipelineOptions, "noPersist">): Promise<MutationResult>;
|
|
31
|
+
export { toFailure };
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import type { NormalizedEditRequest } from "
|
|
1
|
+
import type { NormalizedEditRequest } from "../payload-contract.js";
|
|
2
2
|
import { buildBatchResult } from "../edit-response.js";
|
|
3
3
|
import type { PipelineOptions, ProcessedEditFile } from "./types.js";
|
|
4
4
|
export type { PipelineOptions, ProcessedEditFile };
|
|
5
5
|
export declare function previewEdits(request: NormalizedEditRequest, cwd: string, options?: Omit<PipelineOptions, "noPersist">): Promise<ProcessedEditFile>;
|
|
6
|
-
declare function runCorrelatedTransaction(writePlans: ProcessedEditFile[], sectionPlans: ProcessedEditFile[], options: PipelineOptions | undefined, sessionKey: string): Promise<BatchOutcome>;
|
|
7
6
|
/** What one plan or committed batch reports: the primary file leads, every section rides the result. */
|
|
8
7
|
interface BatchOutcome {
|
|
9
8
|
result: string;
|
|
@@ -21,9 +20,3 @@ interface BatchOutcome {
|
|
|
21
20
|
export declare function planEditTargets(request: NormalizedEditRequest, cwd: string, options?: PipelineOptions): Promise<ProcessedEditFile[] | undefined>;
|
|
22
21
|
export declare function apply(request: NormalizedEditRequest, cwd: string, options?: PipelineOptions): Promise<BatchOutcome>;
|
|
23
22
|
export declare function execEdits(request: NormalizedEditRequest, cwd: string, options?: PipelineOptions): Promise<ProcessedEditFile>;
|
|
24
|
-
/**
|
|
25
|
-
* Visibility-only export of the correlated transaction for apply_patch's commit seam. The
|
|
26
|
-
* function body is the one the cut path already uses; apply_patch members carry an optional
|
|
27
|
-
* lifecycle action read by that body. No engine semantics change.
|
|
28
|
-
*/
|
|
29
|
-
export { runCorrelatedTransaction as commitTransaction };
|