dsh-hashline-edit 0.1.1 → 0.2.1
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 +70 -69
- package/cordis.patch.yml +3 -3
- package/lib/index.js +4151 -4516
- package/lib/types/dsh/contracts.d.ts +69 -6
- package/lib/types/dsh/fs.d.ts +38 -3
- package/lib/types/dsh/guidance.d.ts +5 -5
- package/lib/types/dsh/hide.d.ts +79 -0
- package/lib/types/dsh/host-compat.d.ts +7 -78
- package/lib/types/dsh/index.d.ts +21 -9
- package/lib/types/dsh/metrics/events.d.ts +8 -1
- package/lib/types/dsh/metrics/instrument.d.ts +20 -14
- package/lib/types/dsh/metrics/observer.d.ts +28 -0
- package/lib/types/dsh/sandbox.d.ts +2 -2
- package/lib/types/dsh/shell-hook.d.ts +17 -8
- package/lib/types/dsh/shell-view.d.ts +52 -0
- package/lib/types/dsh/tools/apply-patch.d.ts +38 -0
- package/lib/types/dsh/tools/context.d.ts +19 -1
- package/lib/types/dsh/tools/output.d.ts +32 -4
- package/lib/types/dsh/tools/read.d.ts +12 -15
- package/lib/types/dsh/tools/undo.d.ts +5 -0
- package/lib/types/dsh/write-hook.d.ts +8 -22
- package/lib/types/src/apply-patch/commit.d.ts +49 -0
- package/lib/types/src/apply-patch/grammar.d.ts +29 -0
- package/lib/types/src/apply-patch/lifecycle.d.ts +32 -0
- package/lib/types/src/apply-patch/plan.d.ts +50 -0
- package/lib/types/src/apply-patch/render.d.ts +51 -0
- package/lib/types/src/apply-patch/resolve.d.ts +129 -0
- package/lib/types/src/apply-patch/tool.d.ts +54 -0
- package/lib/types/src/apply-patch/types.d.ts +362 -0
- package/lib/types/src/domain-errors.d.ts +16 -2
- package/lib/types/src/edit-undo.d.ts +11 -2
- package/lib/types/src/file-content/detection.d.ts +17 -0
- package/lib/types/src/file-content/index.d.ts +2 -2
- package/lib/types/src/file-content/preview.d.ts +1 -1
- package/lib/types/src/fs-lifecycle.d.ts +44 -0
- package/lib/types/src/hashline/resolve.d.ts +6 -0
- package/lib/types/src/host-utils.d.ts +63 -0
- package/lib/types/src/mutation-engine/batch-span-gate.d.ts +6 -0
- package/lib/types/src/mutation-engine/edit-request.d.ts +34 -0
- package/lib/types/src/mutation-engine/pipeline.d.ts +18 -3
- package/lib/types/src/mutation-engine/types.d.ts +50 -58
- package/lib/types/src/read-expansion/grammar-manifest.d.ts +4 -2
- package/lib/types/src/read-expansion/java-decoration.d.ts +21 -0
- package/lib/types/src/read-expansion/span-expansion.d.ts +6 -2
- package/lib/types/src/read-expansion/span-kind.d.ts +3 -2
- package/lib/types/src/read-expansion/vue-decoration.d.ts +14 -0
- package/lib/types/src/served-session/serve-render.d.ts +9 -3
- package/lib/types/src/undo-store.d.ts +17 -1
- package/package.json +18 -22
- package/prompts/apply-patch-guidelines.md +10 -0
- package/prompts/apply-patch.md +1 -0
- package/prompts/read-guidelines.md +5 -5
- 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/apply-patch/commit.ts +190 -0
- package/src/apply-patch/grammar.ts +620 -0
- package/src/apply-patch/index.ts +17 -0
- package/src/apply-patch/lifecycle.ts +195 -0
- package/src/apply-patch/plan.ts +800 -0
- package/src/apply-patch/render.ts +137 -0
- package/src/apply-patch/resolve.ts +470 -0
- package/src/apply-patch/tool.ts +257 -0
- package/src/apply-patch/types.ts +387 -0
- package/src/cut-repair.ts +77 -11
- package/src/domain-errors.ts +58 -10
- package/src/edit-undo.ts +379 -25
- package/src/file-content/detection.ts +34 -0
- package/src/file-content/index.ts +2 -2
- package/src/file-content/preview.ts +1 -1
- package/src/file-content/served-header.ts +1 -1
- package/src/fs-lifecycle.ts +201 -0
- package/src/hash-store.ts +18 -1
- package/src/hashline/lease-resolve.ts +14 -8
- package/src/hashline/resolve.ts +30 -27
- package/src/host-utils.ts +224 -0
- package/src/mutation-engine/batch-span-gate.ts +17 -4
- package/src/mutation-engine/edit-request.ts +39 -0
- package/src/mutation-engine/pipeline.ts +535 -63
- package/src/mutation-engine/types.ts +49 -65
- package/src/mutation-queue.ts +1 -1
- package/src/prompts.ts +0 -28
- package/src/read-expansion/ast-provider.ts +4 -0
- package/src/read-expansion/grammar-manifest.ts +21 -2
- package/src/read-expansion/java-decoration.ts +34 -0
- package/src/read-expansion/span-expansion.ts +6 -2
- package/src/read-expansion/span-kind.ts +3 -1
- package/src/read-expansion/vue-decoration.ts +43 -0
- package/src/served-session/serve-render.ts +5 -5
- package/src/undo-store.ts +74 -5
- package/lib/types/dsh/encoding-bridge.d.ts +0 -25
- package/lib/types/dsh/encoding.d.ts +0 -144
- package/lib/types/dsh/tools/edit.d.ts +0 -10
- package/lib/types/src/edit-tool.d.ts +0 -41
- package/lib/types/src/mutation-engine/engine.d.ts +0 -31
- package/lib/types/src/payload-contract.d.ts +0 -124
- package/prompts/edit-guidelines.md +0 -10
- package/prompts/edit-snippet.md +0 -1
- package/prompts/edit.md +0 -1
- package/src/bash-classifier.ts +0 -1626
- package/src/bash-conversion/decide.ts +0 -812
- package/src/bash-display-transform.ts +0 -50
- package/src/bash-multifile-config.ts +0 -239
- package/src/bash-view-config.ts +0 -158
- package/src/edit-normalize.ts +0 -8
- package/src/edit-render.ts +0 -161
- package/src/edit-tool.ts +0 -116
- package/src/edit.ts +0 -136
- package/src/integrations/pi-lens/command.ts +0 -712
- package/src/integrations/pi-lens/config.ts +0 -148
- package/src/integrations/pi-lens/io-bridge-adapter.ts +0 -234
- package/src/integrations/pi-lens/io-bridge.ts +0 -112
- package/src/integrations/pi-lens/settings-overlay.ts +0 -272
- package/src/lifecycle-hooks/index.ts +0 -1992
- package/src/lifecycle-hooks/types.ts +0 -64
- package/src/mutated-files.ts +0 -75
- package/src/mutation-engine/engine.ts +0 -173
- package/src/mutation-engine/index.ts +0 -17
- package/src/payload-contract.ts +0 -637
- package/src/preview-controller.ts +0 -84
- package/src/read.ts +0 -865
- package/src/search-hook.ts +0 -41
- package/src/store-config.ts +0 -73
- package/src/store-janitor-warning.ts +0 -169
- package/src/store-janitor.ts +0 -539
- package/src/store-kernel/settings.ts +0 -233
- package/src/store.ts +0 -154
- package/src/tui-presenter.ts +0 -134
- package/src/write-hook.ts +0 -130
|
@@ -95,3 +95,55 @@ export declare function align(stdoutLines: readonly string[], fileLines: readonl
|
|
|
95
95
|
* @returns the plan, or `undefined` when the host must pass the output through.
|
|
96
96
|
*/
|
|
97
97
|
export declare function plan(deps: ToolDependencies, request: ShellViewRequest): Promise<ShellViewPlan | undefined>;
|
|
98
|
+
/** The host tool whose canonical value is `{ matches: [{ path, lineNumber, line }] }`. */
|
|
99
|
+
export declare const GREP_TOOL_NAME = "grep";
|
|
100
|
+
/** The host `grepMaxMatches` default (`@deepseek-ai/dsh-tool-fs-search` `Config`). */
|
|
101
|
+
export declare const GREP_INLINE_MAX_MATCHES = 250;
|
|
102
|
+
/**
|
|
103
|
+
* WHY: the host spill policy's own budget is `maxInlineTokens: 12500`
|
|
104
|
+
* (`dsh-base/cordis.patch.yml`, `@deepseek-ai/dsh-spill-policy`), and grep is not
|
|
105
|
+
* on its exemption list — only `read` is. A `post-execute` accept decision that
|
|
106
|
+
* carries a `value` bypasses that policy (`Object.hasOwn(decision, "value")`), so
|
|
107
|
+
* this seam must hold the same line itself, using the host's own price for one
|
|
108
|
+
* text block: `ceil(chars / 4) + 4` (`@deepseek-ai/dsh-token-meter/estimate`).
|
|
109
|
+
*/
|
|
110
|
+
export declare const GREP_VIEW_MAX_INLINE_TOKENS = 12500;
|
|
111
|
+
/** The host estimator's price for one text block, so a self-check matches the policy's. */
|
|
112
|
+
export declare function estimateTextTokens(text: string): number;
|
|
113
|
+
/** One match of the host `grep` value. */
|
|
114
|
+
export interface GrepViewMatch {
|
|
115
|
+
readonly path: string;
|
|
116
|
+
readonly lineNumber: number;
|
|
117
|
+
readonly line: string;
|
|
118
|
+
}
|
|
119
|
+
/** The matches plus the session to serve them to. */
|
|
120
|
+
export interface GrepViewRequest {
|
|
121
|
+
readonly matches: readonly GrepViewMatch[];
|
|
122
|
+
readonly cwd: string;
|
|
123
|
+
readonly sessionKey: string;
|
|
124
|
+
readonly signal: AbortSignal | undefined;
|
|
125
|
+
}
|
|
126
|
+
/** The rewritten model-facing grep result. */
|
|
127
|
+
export interface GrepViewPlan {
|
|
128
|
+
/** The shared search banner, one count header per file, and anchored `HASH│content` rows. */
|
|
129
|
+
readonly text: string;
|
|
130
|
+
/** Total rows served (and leased) by this plan. */
|
|
131
|
+
readonly servedLines: number;
|
|
132
|
+
/** The served files, in first-seen match order. */
|
|
133
|
+
readonly paths: readonly string[];
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Turn one built-in `grep` result into anchored rows for the files it matched,
|
|
137
|
+
* serving (and leasing) exactly those lines.
|
|
138
|
+
*
|
|
139
|
+
* Fail-closed discipline mirrors the shell view: a result that is empty, over the
|
|
140
|
+
* host's inline match cap, over the host's inline token budget, non-ascending per
|
|
141
|
+
* file, or whose matched text does not byte-equal the file's governed decode leaves
|
|
142
|
+
* the original result untouched and records no lease. The budget is checked BEFORE
|
|
143
|
+
* the first serve, so an oversized view can never leave partial leases behind.
|
|
144
|
+
*
|
|
145
|
+
* @param deps - host seams; `deps.io` is the only filesystem access used.
|
|
146
|
+
* @param request - the matches, working directory, session and cancellation signal.
|
|
147
|
+
* @returns the plan, or `undefined` when the host must pass the result through.
|
|
148
|
+
*/
|
|
149
|
+
export declare function planGrepView(deps: ToolDependencies, request: GrepViewRequest): Promise<GrepViewPlan | undefined>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { FileDiff, ToolDefinition } from "@deepseek-ai/dsh-tools";
|
|
2
|
+
import { APPLY_PATCH_TOOL_NAME } from "../../src/apply-patch/tool.js";
|
|
3
|
+
import type { ApplyPatchTool } from "../../src/apply-patch/tool.js";
|
|
4
|
+
/** Public tool name re-exported for host registration and surface tests. */
|
|
5
|
+
export { APPLY_PATCH_TOOL_NAME };
|
|
6
|
+
import type { ToolDependencies } from "../contracts.js";
|
|
7
|
+
/** One call's intended file diffs, parsed from the patch headers for the pending card. */
|
|
8
|
+
interface PatchPreview {
|
|
9
|
+
readonly diffs: FileDiff[];
|
|
10
|
+
readonly locations: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Scan the patch headers for the pending card. This is a presentation-only
|
|
14
|
+
* projection: it never decides whether the patch is valid (the grammar and
|
|
15
|
+
* planner do), and it never throws on malformed text.
|
|
16
|
+
* @param input - the raw patch text.
|
|
17
|
+
* @returns the intended additions and every path the patch names.
|
|
18
|
+
*/
|
|
19
|
+
export declare function previewPatch(input: string): PatchPreview;
|
|
20
|
+
/** Every path this patch writes, including a Cut From source; Copy From sources are read-only. */
|
|
21
|
+
export declare function writtenPatchPaths(input: string): string[];
|
|
22
|
+
/** Options for {@link buildApplyPatchTool}; the factory seam is for tests. */
|
|
23
|
+
export interface ApplyPatchToolOptions {
|
|
24
|
+
/** Test seam: build the use case without touching the real engine/store. */
|
|
25
|
+
readonly create?: () => ApplyPatchTool;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Build the dsh 'apply_patch' tool for one agent install.
|
|
29
|
+
*
|
|
30
|
+
* The use case is created lazily: building a tool definition must not touch the
|
|
31
|
+
* engine or the store, and a composition where 'apply_patch' is skipped must
|
|
32
|
+
* still install the rest of the toolset.
|
|
33
|
+
*
|
|
34
|
+
* @param deps - the host seams this tool may touch; 'sandbox' advertises and resolves escalation.
|
|
35
|
+
* @param options - test seam for the use case.
|
|
36
|
+
* @returns a registry-ready dsh tool definition named 'apply_patch'.
|
|
37
|
+
*/
|
|
38
|
+
export declare function buildApplyPatchTool(deps: ToolDependencies, options?: ApplyPatchToolOptions): ToolDefinition;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* @module dsh-hashline-edit/tools/context
|
|
15
15
|
*/
|
|
16
16
|
import type { ToolRunContext } from "@deepseek-ai/dsh-tools";
|
|
17
|
-
import type { ToolContext } from "../contracts.js";
|
|
17
|
+
import type { ToolContext, ToolDependencies } from "../contracts.js";
|
|
18
18
|
/** The pi-shaped context the upstream use cases consume. */
|
|
19
19
|
export interface UpstreamContext {
|
|
20
20
|
cwd: string;
|
|
@@ -35,3 +35,21 @@ export interface UpstreamContext {
|
|
|
35
35
|
export declare function resolveToolContext(exec: ToolRunContext): ToolContext;
|
|
36
36
|
/** Translate the dsh facts into the pi-shaped context the upstream seams still take. */
|
|
37
37
|
export declare function upstreamContext(ctx: ToolContext): UpstreamContext;
|
|
38
|
+
/**
|
|
39
|
+
* Announce, before the core writes them, every request path one mutating call
|
|
40
|
+
* owns.
|
|
41
|
+
*
|
|
42
|
+
* WHY announce and not enforce: these mutations go to `node:fs` and bypass the
|
|
43
|
+
* provider whose guarded write is where an intent would be applied, so the
|
|
44
|
+
* dispatch exists for host bookkeeping. The channel is present only when
|
|
45
|
+
* `ctx.fs` is mounted; without one there is nothing to announce.
|
|
46
|
+
*/
|
|
47
|
+
export declare function announcePluginWrites(deps: ToolDependencies, requestPaths: readonly string[], ctx: ToolContext, exec: object): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Publish what a successful write left on disk for each path it touched.
|
|
50
|
+
*
|
|
51
|
+
* WHY `fresh`: the version this call saw before writing is stale once the bytes
|
|
52
|
+
* changed, so the channel must re-stat for the version the host's guard is later
|
|
53
|
+
* compared against.
|
|
54
|
+
*/
|
|
55
|
+
export declare function publishPluginWrites(deps: ToolDependencies, requestPaths: readonly string[], ctx: ToolContext, exec: object): Promise<void>;
|
|
@@ -9,12 +9,19 @@
|
|
|
9
9
|
* - the canonical value IS `{ content, details }`, so no projection loses fields;
|
|
10
10
|
* - `render` is the identity on `content`, which keeps the model-facing text byte-identical to the
|
|
11
11
|
* upstream tool result;
|
|
12
|
-
* - `details` stays available to nested (`run_code`) callers as structured data;
|
|
13
|
-
*
|
|
12
|
+
* - `details` stays available to nested (`run_code`) callers as structured data; the UI projection
|
|
13
|
+
* rides `presentationMeta`, which is persisted beside the result and never reshapes the value.
|
|
14
|
+
*
|
|
15
|
+
* The presentation projection exists because the two consumers read different things: a host-local
|
|
16
|
+
* presenter consumes `presentCall`/`presentResult` directly, while the built-in Web Client derives
|
|
17
|
+
* its card props from persisted metadata — the same split the preset's own `write`/`edit` declare
|
|
18
|
+
* in their `output.presentationMeta`, which projects the applied hunks. Ours projects the annotated
|
|
19
|
+
* diff the tool already rendered, because that is the only before/after artifact the upstream
|
|
20
|
+
* envelope carries at this seam.
|
|
14
21
|
*
|
|
15
22
|
* @module dsh-hashline-edit/tools/output
|
|
16
23
|
*/
|
|
17
|
-
import type { InferValue } from "@deepseek-ai/dsh-tools";
|
|
24
|
+
import type { DiffResultView, FileDiff, InferValue, ToolOutputDefinition } from "@deepseek-ai/dsh-tools";
|
|
18
25
|
/** One model-facing text block; the upstream tools emit nothing else today. */
|
|
19
26
|
export interface TextContentBlock {
|
|
20
27
|
type: "text";
|
|
@@ -46,8 +53,17 @@ declare const TOOL_RESULT_SCHEMA: {
|
|
|
46
53
|
};
|
|
47
54
|
/** The inferred canonical value type of {@link TOOL_RESULT_SCHEMA}. */
|
|
48
55
|
export type ToolResultOutput = InferValue<typeof TOOL_RESULT_SCHEMA>;
|
|
56
|
+
/**
|
|
57
|
+
* The durable presentation payload both mutation tools project: the applied hunks a diff card
|
|
58
|
+
* renders. `path` is the model-facing path the call named (a UI bridge relativizes it); a hunk
|
|
59
|
+
* carries `oldText: null` for a pure insertion, exactly like the preset's own `computeHunkDiffs`.
|
|
60
|
+
*/
|
|
61
|
+
export interface HashlineResultMeta {
|
|
62
|
+
path?: string;
|
|
63
|
+
diffs: FileDiff[];
|
|
64
|
+
}
|
|
49
65
|
/** Registry-ready output declaration shared by `edit` and `undo_last_edit`. */
|
|
50
|
-
export declare const
|
|
66
|
+
export declare const HASHLINE_RESULT_OUTPUT: {
|
|
51
67
|
readonly schema: {
|
|
52
68
|
readonly type: "object";
|
|
53
69
|
readonly additionalProperties: false;
|
|
@@ -66,7 +82,19 @@ export declare const TOOL_RESULT_OUTPUT: {
|
|
|
66
82
|
};
|
|
67
83
|
};
|
|
68
84
|
readonly render: (_args: unknown, value: ToolResultOutput) => TextContentBlock[];
|
|
85
|
+
readonly presentationMeta: NonNullable<ToolOutputDefinition["presentationMeta"]>;
|
|
69
86
|
};
|
|
87
|
+
/**
|
|
88
|
+
* The completed state of a mutation call as one diff card, or `undefined` when the call failed or
|
|
89
|
+
* applied nothing — the presenter then keeps the pending card, exactly like the preset's `edit`.
|
|
90
|
+
* @param title - the card header, e.g. `Edit src/a.ts`.
|
|
91
|
+
* @param result - the completed outcome handed to `presentResult`.
|
|
92
|
+
* @returns the diff result view, or `undefined` for the generic path.
|
|
93
|
+
*/
|
|
94
|
+
export declare function diffResultView(title: string, result: {
|
|
95
|
+
readonly isError: boolean;
|
|
96
|
+
readonly meta?: unknown;
|
|
97
|
+
}): DiffResultView | undefined;
|
|
70
98
|
/**
|
|
71
99
|
* Present the upstream envelope as the declared canonical value.
|
|
72
100
|
*
|
|
@@ -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 `apply_patch` identity hunk 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
|
*
|
|
@@ -17,11 +17,14 @@
|
|
|
17
17
|
* - `around_anchor` windows are admitted and resolved against this session's
|
|
18
18
|
* active lineage, for both modes (spec §4.3 carries no mode qualifier); only
|
|
19
19
|
* a served read leases the rows it renders.
|
|
20
|
-
* - Read expansion mirrors `src/read.ts
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
20
|
+
* - Read expansion mirrors `src/read.ts` — every attempt's audit record comes from
|
|
21
|
+
* the core seams, and a missing provider or unavailable grammar degrades to the
|
|
22
|
+
* exact requested window: expansion never fails a read.
|
|
23
|
+
* - `encoding` — the word, and the parameter itself, is gone. This host reads and
|
|
24
|
+
* writes UTF-8 only: a target whose bytes are not valid UTF-8 is refused with
|
|
25
|
+
* `E_UNSUPPORTED_FILE` naming the path and telling the model to ask the user how
|
|
26
|
+
* to handle the file. The bytes still round-trip verbatim — only admission is
|
|
27
|
+
* narrowed.
|
|
25
28
|
* - ADR-0043 stat-identity adoption mirrors the core: on the default decode path
|
|
26
29
|
* the loader's stat identity (ino/size/mtime/ctime) is passed through, so an
|
|
27
30
|
* unchanged file adopts its committed snapshot and reuses its anchors; a moved
|
|
@@ -51,22 +54,16 @@ export declare function createDefaultReadSpanProvider(io?: CoreReadIo): SpanProv
|
|
|
51
54
|
export interface ServedFileLine {
|
|
52
55
|
/** 1-based line number the anchor addresses. */
|
|
53
56
|
readonly line: number;
|
|
54
|
-
/** The line's 4-character anchor, usable by `
|
|
57
|
+
/** The line's 4-character anchor, usable by `apply_patch`. */
|
|
55
58
|
readonly hash: string;
|
|
56
59
|
/** The line's decoded text, without the anchor prefix. */
|
|
57
60
|
readonly text: string;
|
|
58
61
|
}
|
|
59
|
-
/** The facts one line-set serve needs
|
|
62
|
+
/** The facts one line-set serve needs. */
|
|
60
63
|
export interface ServeFileLinesOptions {
|
|
61
64
|
readonly path: string;
|
|
62
65
|
readonly cwd: string;
|
|
63
66
|
readonly signal: AbortSignal | undefined;
|
|
64
|
-
/**
|
|
65
|
-
* Admit a probabilistic decode. WHY off for the read tool: only a caller with
|
|
66
|
-
* independent evidence of the text (a shell view's own stdout, aligned line by
|
|
67
|
-
* line) may accept a guessed code page.
|
|
68
|
-
*/
|
|
69
|
-
readonly autoGuess?: boolean;
|
|
70
67
|
}
|
|
71
68
|
/** A line-set serve: the file plus the lines to materialize and lease. */
|
|
72
69
|
export interface ServeFileLinesRequest extends ServeFileLinesOptions {
|
|
@@ -87,7 +84,7 @@ export declare function decodeFileLines(deps: ToolDependencies, request: ServeFi
|
|
|
87
84
|
*
|
|
88
85
|
* WHY a separate seam: the shell view must turn already-aligned disk lines into
|
|
89
86
|
* editable anchors without re-implementing the read pipeline, and those anchors
|
|
90
|
-
* must be leased to the session the following `
|
|
87
|
+
* must be leased to the session the following `apply_patch` hunk uses.
|
|
91
88
|
*/
|
|
92
89
|
export declare function serveFileLines(deps: ToolDependencies, request: ServeFileLinesRequest): Promise<ServedFileLine[]>;
|
|
93
90
|
/**
|
|
@@ -2,6 +2,11 @@ import type { ToolDefinition } from "@deepseek-ai/dsh-tools";
|
|
|
2
2
|
import type { ToolDependencies } from "../contracts.js";
|
|
3
3
|
/** dsh tool name; `undo_last_edit` is plugin-owned (no preset shadow). */
|
|
4
4
|
export declare const UNDO_TOOL_NAME = "undo_last_edit";
|
|
5
|
+
/**
|
|
6
|
+
* The core's prompt text, kept in sync with `prompts/undo-last-edit.md`. Exported only so the drift
|
|
7
|
+
* test can pin it to the file the guidance section loads; the tool itself uses the inlined copy.
|
|
8
|
+
*/
|
|
9
|
+
export declare const UNDO_DESCRIPTION: string;
|
|
5
10
|
/**
|
|
6
11
|
* Build the dsh `undo_last_edit` tool for one agent install.
|
|
7
12
|
* @param deps - the host seams this tool may touch; `sandbox` advertises and resolves escalation.
|
|
@@ -33,8 +33,7 @@
|
|
|
33
33
|
*/
|
|
34
34
|
import type { Context } from "@deepseek-ai/cordis";
|
|
35
35
|
import type { PostToolDecision, PreToolDecision, ToolExecution, ToolExecutionResult } from "@deepseek-ai/dsh-tools";
|
|
36
|
-
import type { FileIO } from "./contracts.js";
|
|
37
|
-
import { type EncodingState } from "./encoding.js";
|
|
36
|
+
import type { FileIO, FsObservationChannel } from "./contracts.js";
|
|
38
37
|
/** The heading the post-write auto-read appends to the write result. */
|
|
39
38
|
export declare const AUTO_READ_HEADING = "--- Auto-read (hashline anchors) ---";
|
|
40
39
|
/** The served evidence the guard compares a submitted write against. */
|
|
@@ -44,11 +43,6 @@ export interface ServedSnapshot {
|
|
|
44
43
|
/** Canon digests parallel to `served` (null = no evidence for that position). */
|
|
45
44
|
readonly canonDigests: readonly (string | null)[];
|
|
46
45
|
}
|
|
47
|
-
/** A write's captured encoding state: the restore target taken before the tool wrote. */
|
|
48
|
-
interface CapturedEncodingState {
|
|
49
|
-
readonly absolutePath: string;
|
|
50
|
-
readonly state: EncodingState;
|
|
51
|
-
}
|
|
52
46
|
/** Everything the hook needs from the host, injectable for tests. */
|
|
53
47
|
export interface WriteHookDeps {
|
|
54
48
|
/** Resolve a model-supplied path against the session cwd; must match the read/edit path keys. */
|
|
@@ -58,17 +52,11 @@ export interface WriteHookDeps {
|
|
|
58
52
|
/** Read + serve the just-written file, returning the model-facing anchored text. */
|
|
59
53
|
autoRead(rawPath: string, cwd: string, sessionKey: string, signal?: AbortSignal): Promise<string>;
|
|
60
54
|
/**
|
|
61
|
-
* Optional
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*/
|
|
65
|
-
captureEncodingState?(rawPath: string, cwd: string, signal?: AbortSignal): Promise<CapturedEncodingState | undefined>;
|
|
66
|
-
/**
|
|
67
|
-
* Optional host step between the committed write and the auto-read: re-encode
|
|
68
|
-
* the built-in tool's UTF-8 bytes to the captured encoding. Returns a
|
|
69
|
-
* model-facing warning when the bytes had to stay UTF-8.
|
|
55
|
+
* Optional host observation channel. The built-in write's own dispatch is what
|
|
56
|
+
* observes the write itself, so the hook only publishes the auto-read's re-observation
|
|
57
|
+
* here; a composition without the channel silently skips the announcement.
|
|
70
58
|
*/
|
|
71
|
-
|
|
59
|
+
readonly observations?: FsObservationChannel;
|
|
72
60
|
/** Host diagnostic sink; the hook must never throw through this. */
|
|
73
61
|
warn(message: string): void;
|
|
74
62
|
}
|
|
@@ -84,16 +72,14 @@ export interface WriteHooks {
|
|
|
84
72
|
*/
|
|
85
73
|
export declare function createWriteHooks(deps: WriteHookDeps): WriteHooks;
|
|
86
74
|
/** The production dependency set: the byte seam plus core served state, core read pipeline, local paths. */
|
|
87
|
-
export declare function defaultWriteHookDeps(warn: (message: string) => void, io: FileIO): WriteHookDeps;
|
|
75
|
+
export declare function defaultWriteHookDeps(warn: (message: string) => void, io: FileIO, observations?: FsObservationChannel): WriteHookDeps;
|
|
88
76
|
/**
|
|
89
77
|
* Register the write integration for one agent on its own scope layer.
|
|
90
78
|
*
|
|
91
79
|
* @param rootCtx - host context, used only for diagnostics.
|
|
92
80
|
* @param agentCtx - the agent's scoped context; the listeners see only that
|
|
93
81
|
* agent's tool calls.
|
|
94
|
-
* @param io - the host byte seam: the auto-read decodes through it
|
|
95
|
-
* post-write step inverts the recorded encoding with it.
|
|
82
|
+
* @param io - the host byte seam: the auto-read decodes the written bytes through it.
|
|
96
83
|
* @returns the exact disposer that unregisters both listeners.
|
|
97
84
|
*/
|
|
98
|
-
export declare function registerWriteHook(rootCtx: Context, agentCtx: Context, io: FileIO): () => void;
|
|
99
|
-
export {};
|
|
85
|
+
export declare function registerWriteHook(rootCtx: Context, agentCtx: Context, io: FileIO, observations?: FsObservationChannel): () => void;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { HashStore } from "../hash-store.js";
|
|
2
|
+
import type { ApplyPatchFailure, PatchPlan, ServedFileBlock } from "./types.js";
|
|
3
|
+
/** Options for {@link commitPlan}. */
|
|
4
|
+
export interface CommitPlanOptions {
|
|
5
|
+
readonly cwd: string;
|
|
6
|
+
readonly sessionKey: string;
|
|
7
|
+
readonly signal?: AbortSignal;
|
|
8
|
+
readonly store?: HashStore;
|
|
9
|
+
/** POSIX access-mode bitmask; defaults to read+write. */
|
|
10
|
+
readonly accessMode?: number;
|
|
11
|
+
/** TEST-ONLY: fires before the first rename, while the pre-images/undo rows are durable. */
|
|
12
|
+
readonly onBeforeFirstWrite?: () => void | Promise<void>;
|
|
13
|
+
/** TEST-ONLY: fires between two member renames, after the earlier one is durable. */
|
|
14
|
+
readonly onBetweenWrites?: (committedAbsolutePath: string) => void | Promise<void>;
|
|
15
|
+
/** TEST-ONLY: fires after a move rename and before the destination rewrite (mid-member failure). */
|
|
16
|
+
readonly onAfterMoveRename?: (toAbsolutePath: string) => void | Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
/** One committed file's lifecycle status and line accounting. */
|
|
19
|
+
export interface CommittedFile {
|
|
20
|
+
readonly path: string;
|
|
21
|
+
readonly status: "A" | "M" | "R" | "D";
|
|
22
|
+
/** Original path for a rename (`R` only). */
|
|
23
|
+
readonly fromPath?: string;
|
|
24
|
+
readonly addedLines: number;
|
|
25
|
+
readonly removedLines: number;
|
|
26
|
+
/** New anchors for the changed lines; absent when the member wrote no content. */
|
|
27
|
+
readonly served?: ServedFileBlock;
|
|
28
|
+
}
|
|
29
|
+
/** Commit outcome: the per-file facts on success, the whole failure on rejection. */
|
|
30
|
+
export type CommitPlanResult = {
|
|
31
|
+
readonly ok: true;
|
|
32
|
+
readonly files: readonly CommittedFile[];
|
|
33
|
+
readonly diff: string;
|
|
34
|
+
readonly warnings: readonly string[];
|
|
35
|
+
} | {
|
|
36
|
+
readonly ok: false;
|
|
37
|
+
readonly failure: ApplyPatchFailure;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Commit one plan atomically (one correlated transaction, one undo record set).
|
|
41
|
+
*
|
|
42
|
+
* The implementation reuses `commitTransaction` — the existing correlated skeleton — and extends
|
|
43
|
+
* its member shape with the plan's lifecycle action; the undo schema gains the matching action.
|
|
44
|
+
*
|
|
45
|
+
* @param plan - the fully resolved plan.
|
|
46
|
+
* @param options - cwd/session/store, cancellation and the test fault seams.
|
|
47
|
+
* @returns the committed facts, or a failure whose `changedNothing` reflects the rollback.
|
|
48
|
+
*/
|
|
49
|
+
export declare function commitPlan(plan: PatchPlan, options: CommitPlanOptions): Promise<CommitPlanResult>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AnchorSpan, ParsePatchResult } from "./types.js";
|
|
2
|
+
/** Hard cap on one patch input (bytes), mirroring the host request budget. */
|
|
3
|
+
export declare const PATCH_MAX_BYTES: number;
|
|
4
|
+
/** Options for {@link parseApplyPatch}. */
|
|
5
|
+
export interface ParsePatchOptions {
|
|
6
|
+
/** Maximum accepted input size in bytes; defaults to {@link PATCH_MAX_BYTES}. */
|
|
7
|
+
readonly maxBytes?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Parse one patch document.
|
|
11
|
+
*
|
|
12
|
+
* Success returns the AST defined in `types.ts`; failure returns every syntax
|
|
13
|
+
* problem the parser could diagnose in one pass (never a throw for malformed
|
|
14
|
+
* user input). Rejections use `E_PATCH_SYNTAX` with the offending patch line.
|
|
15
|
+
*
|
|
16
|
+
* @param input - the raw `*** Begin Patch` … `*** End Patch` text.
|
|
17
|
+
* @param options - size guard knobs.
|
|
18
|
+
* @returns the parsed document or the collected problems.
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseApplyPatch(input: string, options?: ParsePatchOptions): ParsePatchResult;
|
|
21
|
+
/**
|
|
22
|
+
* Parse an anchor token: `<a>` (single line) or `<a>-<b>` (closed span).
|
|
23
|
+
* Returns `undefined` when the token is not a valid four-character anchor pair; the
|
|
24
|
+
* grammar layer turns that into an `E_PATCH_SYNTAX` problem at the patch line.
|
|
25
|
+
*
|
|
26
|
+
* @param token - the text after `@@` (no leading space).
|
|
27
|
+
* @returns the closed span, or undefined for a malformed token.
|
|
28
|
+
*/
|
|
29
|
+
export declare function parseAnchorSpan(token: string): AnchorSpan | undefined;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type HashStore } from "../hash-store.js";
|
|
2
|
+
import type { ProcessedEditFile } from "../mutation-engine/types.js";
|
|
3
|
+
import type { PatchPath, PlannedFile } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* apply_patch lifecycle materialization — the plan-to-engine adapter.
|
|
6
|
+
*
|
|
7
|
+
* The planner resolves every mutation against a pre-call baseline; this module turns that plan
|
|
8
|
+
* into the mutation engine's `ProcessedEditFile` member shape (plus the engine-local lifecycle
|
|
9
|
+
* action). It never writes: the correlated transaction in `mutation-engine/pipeline.ts` owns the
|
|
10
|
+
* one commit path. Pure text application (splice at baseline coordinates) is the only work here,
|
|
11
|
+
* and the resulting anchors are always derived under the member's COMMITTED path.
|
|
12
|
+
*
|
|
13
|
+
* @module dsh-hashline-edit/apply-patch/lifecycle
|
|
14
|
+
*/
|
|
15
|
+
/** One materialized member: the plan facts plus the engine member the commit consumes. */
|
|
16
|
+
export interface MaterializedMember {
|
|
17
|
+
readonly planned: PlannedFile;
|
|
18
|
+
readonly member: ProcessedEditFile;
|
|
19
|
+
readonly status: "A" | "M" | "R" | "D";
|
|
20
|
+
/** The committed display path (destination for a move; the pre path otherwise). */
|
|
21
|
+
readonly committedPath: PatchPath;
|
|
22
|
+
/** The pre-transaction display path (move only). */
|
|
23
|
+
readonly fromPath?: PatchPath;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Materialize one planned file into the engine's member shape.
|
|
27
|
+
*
|
|
28
|
+
* The result text is produced by applying the plan's baseline-coordinate mutations; the anchors
|
|
29
|
+
* are then derived under the committed path. No file is read or written here — the plan already
|
|
30
|
+
* carries every baseline byte the commit needs.
|
|
31
|
+
*/
|
|
32
|
+
export declare function materializeMember(planned: PlannedFile, store?: HashStore): Promise<MaterializedMember>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { LeaseSpanSource } from "../hashline/resolve.js";
|
|
2
|
+
import { type HashStore } from "../hash-store.js";
|
|
3
|
+
import { type ServedAnchors } from "./resolve.js";
|
|
4
|
+
import type { ApplyPatchEngineOptions, FileBaseline, PatchDocument, PatchPath, PlannedMutation, PlanPatchResult } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* The baseline/served loader port.
|
|
7
|
+
*
|
|
8
|
+
* Production wires `readBaseline` to the file-content decode path,
|
|
9
|
+
* `readServed` to `createSessionHandle(...).load()` and `readLeaseSource` to
|
|
10
|
+
* the read-only lease identity source. Tests inject a pure in-memory
|
|
11
|
+
* implementation, which is what makes the planner testable without a store.
|
|
12
|
+
*/
|
|
13
|
+
export interface PlanFileLoader {
|
|
14
|
+
/** Load one file's baseline (LF-normalized text + anchors + BOM + ending). */
|
|
15
|
+
readBaseline(path: PatchPath, absolutePath: string, signal?: AbortSignal): Promise<FileBaseline>;
|
|
16
|
+
/** Load this session's served anchors for one file, parallel to its lines. */
|
|
17
|
+
readServed(absolutePath: string, sessionKey: string, store?: HashStore): Promise<ServedAnchors>;
|
|
18
|
+
/** Session-wide anchor homes, for foreign-anchor diagnosis (optional legacy port). */
|
|
19
|
+
anchorHomes?(anchor: string, sessionKey: string, store?: HashStore): readonly PatchPath[];
|
|
20
|
+
/** Build the read-only lease identity source for one file's baseline content. */
|
|
21
|
+
readLeaseSource?(absolutePath: string, sessionKey: string, content: string, store?: HashStore): Promise<LeaseSpanSource>;
|
|
22
|
+
}
|
|
23
|
+
/** Options for {@link planApplyPatch}; `loader` is the test seam. */
|
|
24
|
+
export interface PlanApplyPatchOptions extends ApplyPatchEngineOptions {
|
|
25
|
+
readonly loader?: PlanFileLoader;
|
|
26
|
+
}
|
|
27
|
+
/** A baseline for a file that does not exist yet (Add File / missing target). */
|
|
28
|
+
export declare function absentBaseline(path: PatchPath, absolutePath: string): FileBaseline;
|
|
29
|
+
/** The production loader over node:fs, the file-content decoder and the hash store. */
|
|
30
|
+
export declare function createDefaultPlanFileLoader(): PlanFileLoader;
|
|
31
|
+
/** Apply resolved baseline-coordinate mutations to a line array (descending, so coordinates stay valid). */
|
|
32
|
+
export declare function applyPlannedMutations(lines: readonly string[], mutations: readonly PlannedMutation[]): string[];
|
|
33
|
+
export declare function mutationDelta(baselineLines: readonly string[], mutation: PlannedMutation): {
|
|
34
|
+
added: number;
|
|
35
|
+
removed: number;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Plan one apply_patch call without writing anything.
|
|
39
|
+
*
|
|
40
|
+
* All hunks resolve against the pre-call baseline (never chained application);
|
|
41
|
+
* a later hunk's fragment window begins after the previous hunk's end. Any
|
|
42
|
+
* problem aborts the whole call: the returned list is every independently
|
|
43
|
+
* diagnosable problem, not just the first, together with the current rows of
|
|
44
|
+
* every named file (reject-and-serve).
|
|
45
|
+
*
|
|
46
|
+
* @param document - the parsed patch AST.
|
|
47
|
+
* @param options - cwd/session/store plus the optional baseline loader.
|
|
48
|
+
* @returns the plan, or every problem found plus the rows to serve.
|
|
49
|
+
*/
|
|
50
|
+
export declare function planApplyPatch(document: PatchDocument, options: PlanApplyPatchOptions): Promise<PlanPatchResult>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* apply_patch v4 — renderer.
|
|
3
|
+
*
|
|
4
|
+
* One renderer owns both surfaces:
|
|
5
|
+
*
|
|
6
|
+
* - success: file-level summary lines first (`A`/`M`/`R`/`D`), then the
|
|
7
|
+
* changed-line anchor rows (`HASH│content`) per file, so a second call needs
|
|
8
|
+
* no `read`;
|
|
9
|
+
* - rejection: every problem as `- patch line N ([E_*], path): message`,
|
|
10
|
+
* followed by each problem file's current rows.
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-hashline-edit/apply-patch/render
|
|
13
|
+
*/
|
|
14
|
+
import type { ApplyPatchFailure, ApplyPatchResult, ApplyPatchSuccess, ApplyPatchToolResult, AppliedFileSummary, PatchProblem, ServedFileBlock } from "./types.js";
|
|
15
|
+
/**
|
|
16
|
+
* Render one apply_patch outcome into the model-facing tool envelope.
|
|
17
|
+
* @param result - the committed success or the collected rejection.
|
|
18
|
+
* @returns `{ content, details }` ready for the dsh tool registry.
|
|
19
|
+
*/
|
|
20
|
+
export declare function renderApplyPatchResult(result: ApplyPatchResult): ApplyPatchToolResult;
|
|
21
|
+
/**
|
|
22
|
+
* Render a successful outcome.
|
|
23
|
+
* @param success - committed file facts plus new anchor rows.
|
|
24
|
+
* @returns the model-facing envelope.
|
|
25
|
+
*/
|
|
26
|
+
export declare function renderSuccess(success: ApplyPatchSuccess): ApplyPatchToolResult;
|
|
27
|
+
/**
|
|
28
|
+
* Render a rejected outcome: all problems first, then the served rows.
|
|
29
|
+
* @param failure - the collected problems and current rows.
|
|
30
|
+
* @returns the model-facing envelope (as an `isError` tool result at the host seam).
|
|
31
|
+
*/
|
|
32
|
+
export declare function renderFailure(failure: ApplyPatchFailure): ApplyPatchToolResult;
|
|
33
|
+
/**
|
|
34
|
+
* Render the file-level summary block: `Updated N files:` then one status line
|
|
35
|
+
* per file (`R src/old.ts -> src/new.ts` for a rename).
|
|
36
|
+
* @param files - the committed file summaries.
|
|
37
|
+
* @returns the summary text.
|
|
38
|
+
*/
|
|
39
|
+
export declare function renderFileSummary(files: readonly AppliedFileSummary[]): string;
|
|
40
|
+
/**
|
|
41
|
+
* Render the new-anchor blocks for every changed file.
|
|
42
|
+
* @param blocks - per-file served rows.
|
|
43
|
+
* @returns one `<path>\nHASH│content` section per block.
|
|
44
|
+
*/
|
|
45
|
+
export declare function renderServedBlocks(blocks: readonly ServedFileBlock[]): string;
|
|
46
|
+
/**
|
|
47
|
+
* Render one problem line: `- patch line N ([E_*], path): message`.
|
|
48
|
+
* @param problem - one collected problem.
|
|
49
|
+
* @returns the single-line rendering, without a trailing newline.
|
|
50
|
+
*/
|
|
51
|
+
export declare function formatPatchProblem(problem: PatchProblem): string;
|