dsh-hashline-edit 0.2.3 → 0.3.0
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/cordis.patch.yml +9 -3
- package/lib/client.js +231 -0
- package/lib/index.js +146 -37
- package/lib/types/dsh/guidance.d.ts +7 -1
- package/lib/types/dsh/index.d.ts +39 -8
- package/lib/types/dsh/tools/read.d.ts +1 -1
- package/lib/types/src/apply-patch/grammar.d.ts +1 -1
- package/lib/types/src/apply-patch/types.d.ts +3 -3
- package/package.json +20 -1
- package/prompts/apply-patch-guidelines.md +4 -10
- package/prompts/apply-patch.md +1 -1
- package/prompts/read-guidelines.md +6 -6
- package/prompts/read.md +1 -1
- package/prompts/zh/apply-patch-guidelines.md +4 -0
- package/prompts/zh/apply-patch.md +1 -0
- package/prompts/zh/read-guidelines.md +6 -0
- package/prompts/zh/read-snippet.md +1 -0
- package/prompts/zh/read.md +1 -0
- package/prompts/zh/undo-last-edit-guidelines.md +2 -0
- package/prompts/zh/undo-last-edit-snippet.md +1 -0
- package/prompts/zh/undo-last-edit.md +1 -0
- package/src/apply-patch/grammar.ts +1 -1
- package/src/apply-patch/types.ts +3 -3
- package/prompts/read-snippet.md +0 -1
package/cordis.patch.yml
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# dsh-hashline-edit bundle patch: mount the host-plane plugin row.
|
|
2
2
|
# Installed via `dsh plugin --profile <name> add dsh-hashline-edit` (or a
|
|
3
|
-
# profile's bundle list). The
|
|
4
|
-
# read
|
|
5
|
-
#
|
|
3
|
+
# profile's bundle list). The row carries three feature switches, edited on the
|
|
4
|
+
# Plugins page: `read` shadows the built-in read, `apply_patch` hides the native
|
|
5
|
+
# write/edit/apply_patch/str_replace_editor surface, `undo` exposes the
|
|
6
|
+
# undo_last_edit tool. Each toggles live: the host reloads this row and the plugin
|
|
7
|
+
# re-mounts every live agent. Defaults: read on, apply_patch on, undo off.
|
|
6
8
|
- insert:
|
|
7
9
|
- id: dsh-hashline-edit
|
|
8
10
|
name: dsh-hashline-edit
|
|
11
|
+
config:
|
|
12
|
+
read: true
|
|
13
|
+
applyPatch: true
|
|
14
|
+
undo: false
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({id:"dsh-hashline-edit",factory:(require)=>{const module={exports:{}};const exports=module.exports;(function(module,exports,require){
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name2 in all)
|
|
9
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// dsh/client/index.tsx
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
apply: () => apply,
|
|
25
|
+
inject: () => inject,
|
|
26
|
+
name: () => name
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
var import_react = require("react");
|
|
30
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
31
|
+
var SWITCHES = [
|
|
32
|
+
{ key: "read", label: "swRead", hint: "swReadHint", fallback: true },
|
|
33
|
+
{ key: "applyPatch", label: "swApplyPatch", hint: "swApplyPatchHint", fallback: true },
|
|
34
|
+
{ key: "undo", label: "swUndo", hint: "swUndoHint", fallback: false }
|
|
35
|
+
];
|
|
36
|
+
var en = {
|
|
37
|
+
swRead: "Read (shadow the host read)",
|
|
38
|
+
swReadHint: "Expose the hashline read tool with hash anchors and shadow the host's read.",
|
|
39
|
+
swApplyPatch: "Apply patch",
|
|
40
|
+
swApplyPatchHint: "Expose apply_patch and hide the native edit / write / apply_patch / str_replace_editor tools.",
|
|
41
|
+
swUndo: "Undo",
|
|
42
|
+
swUndoHint: "Expose undo_last_edit. Snapshots are always recorded; this only shows the tool to the model.",
|
|
43
|
+
groupTools: "Hashline tools",
|
|
44
|
+
groupToolsHint: "Toggle each tool live. Turning one off restores the host's own tool.",
|
|
45
|
+
save: "Save",
|
|
46
|
+
saving: "Saving\u2026",
|
|
47
|
+
reset: "Reset",
|
|
48
|
+
saved: "Saved. The host reloaded this row.",
|
|
49
|
+
rejected: "The host refused this save; reset and review your changes.",
|
|
50
|
+
loading: "Loading host settings\u2026",
|
|
51
|
+
unavailable: "Enable this row with the built-in switch to edit its settings.",
|
|
52
|
+
rowSummary: "Read, apply_patch and undo switches."
|
|
53
|
+
};
|
|
54
|
+
var zh = {
|
|
55
|
+
swRead: "\u8BFB\u53D6\uFF08\u906E\u853D\u5BBF\u4E3B read\uFF09",
|
|
56
|
+
swReadHint: "\u542F\u7528\u5E26\u54C8\u5E0C\u951A\u70B9\u7684 hashline read \u5DE5\u5177\uFF0C\u5E76\u906E\u853D\u5BBF\u4E3B\u7684 read\u3002",
|
|
57
|
+
swApplyPatch: "\u5E94\u7528\u8865\u4E01",
|
|
58
|
+
swApplyPatchHint: "\u542F\u7528 apply_patch\uFF0C\u5E76\u9690\u85CF\u539F\u751F edit / write / apply_patch / str_replace_editor\u3002",
|
|
59
|
+
swUndo: "\u64A4\u9500",
|
|
60
|
+
swUndoHint: "\u542F\u7528 undo_last_edit\u3002\u5FEB\u7167\u59CB\u7EC8\u8BB0\u5F55\uFF0C\u6B64\u9879\u4EC5\u5411\u6A21\u578B\u66B4\u9732\u5DE5\u5177\u3002",
|
|
61
|
+
groupTools: "Hashline \u5DE5\u5177",
|
|
62
|
+
groupToolsHint: "\u5B9E\u65F6\u5207\u6362\u6BCF\u4E2A\u5DE5\u5177\uFF1B\u5173\u95ED\u540E\u6062\u590D\u5BBF\u4E3B\u81EA\u5E26\u5DE5\u5177\u3002",
|
|
63
|
+
save: "\u4FDD\u5B58",
|
|
64
|
+
saving: "\u6B63\u5728\u4FDD\u5B58\u2026",
|
|
65
|
+
reset: "\u91CD\u7F6E",
|
|
66
|
+
saved: "\u5DF2\u4FDD\u5B58\uFF0C\u5BBF\u4E3B\u5DF2\u91CD\u65B0\u52A0\u8F7D\u6B64\u884C\u3002",
|
|
67
|
+
rejected: "\u5BBF\u4E3B\u62D2\u7EDD\u4E86\u6B64\u6B21\u4FDD\u5B58\uFF1B\u8BF7\u91CD\u7F6E\u540E\u91CD\u65B0\u68C0\u67E5\u66F4\u6539\u3002",
|
|
68
|
+
loading: "\u6B63\u5728\u52A0\u8F7D\u5BBF\u4E3B\u8BBE\u7F6E\u2026",
|
|
69
|
+
unavailable: "\u8BF7\u5148\u7528\u5185\u7F6E\u5F00\u5173\u542F\u7528\u6B64\u884C\u540E\u518D\u7F16\u8F91\u8BBE\u7F6E\u3002",
|
|
70
|
+
rowSummary: "\u8BFB\u53D6\u3001apply_patch \u4E0E\u64A4\u9500\u5F00\u5173\u3002"
|
|
71
|
+
};
|
|
72
|
+
var CSS = `
|
|
73
|
+
.hashline-settings { display:flex; flex-direction:column; gap:12px; font:inherit; color:var(--dsw-alias-label-primary,#222); }
|
|
74
|
+
.hashline-card { display:flex; flex-direction:column; gap:10px; padding:14px; border:.5px solid var(--dsw-alias-border-l2,#ddd); border-radius:8px; }
|
|
75
|
+
.hashline-title { margin:0; font-size:13px; font-weight:600; color:var(--dsw-alias-label-secondary,#555); }
|
|
76
|
+
.hashline-hint { margin:0; font-size:12px; line-height:1.5; color:var(--dsw-alias-label-tertiary,#888); }
|
|
77
|
+
.hashline-rows { display:flex; flex-direction:column; }
|
|
78
|
+
.hashline-row { display:flex; align-items:flex-start; gap:16px; padding:10px 0; border-top:.5px solid var(--dsw-alias-border-l2,#eee); }
|
|
79
|
+
.hashline-row:first-child { border-top:0; padding-top:0; }
|
|
80
|
+
.hashline-text { flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:4px; }
|
|
81
|
+
.hashline-label { font-size:13px; font-weight:500; }
|
|
82
|
+
.hashline-switch { flex:none; position:relative; width:36px; height:20px; border:0; padding:0; border-radius:999px; background:#c8c8c8; cursor:pointer; transition:background .12s ease; }
|
|
83
|
+
.hashline-switch.is-on { background:#2f6fed; }
|
|
84
|
+
.hashline-switch:disabled { opacity:.5; cursor:default; }
|
|
85
|
+
.hashline-knob { position:absolute; top:2px; left:2px; width:16px; height:16px; border-radius:50%; background:#fff; transition:transform .12s ease; }
|
|
86
|
+
.hashline-switch.is-on .hashline-knob { transform:translateX(16px); }
|
|
87
|
+
.hashline-actions { display:flex; align-items:center; gap:8px; }
|
|
88
|
+
.hashline-primary { border:1px solid transparent; border-radius:6px; padding:5px 14px; font:inherit; font-size:13px; cursor:pointer; background:var(--dsw-alias-label-primary,#222); color:#fff; }
|
|
89
|
+
.hashline-primary:disabled { opacity:.4; cursor:default; }
|
|
90
|
+
.hashline-reset { border:0; background:none; font:inherit; font-size:12px; color:var(--dsw-alias-label-secondary,#555); cursor:pointer; }
|
|
91
|
+
.hashline-note { margin:0; font-size:12px; line-height:1.6; color:var(--dsw-alias-label-tertiary,#888); }
|
|
92
|
+
`;
|
|
93
|
+
function readValues(value) {
|
|
94
|
+
const out = {};
|
|
95
|
+
for (const spec of SWITCHES) {
|
|
96
|
+
const raw = value[spec.key];
|
|
97
|
+
out[spec.key] = typeof raw === "boolean" ? raw : spec.fallback;
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
function Switch(props) {
|
|
102
|
+
const { checked, label, disabled = false, onChange } = props;
|
|
103
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
104
|
+
"button",
|
|
105
|
+
{
|
|
106
|
+
type: "button",
|
|
107
|
+
role: "switch",
|
|
108
|
+
"aria-checked": checked,
|
|
109
|
+
"aria-label": label,
|
|
110
|
+
disabled,
|
|
111
|
+
className: `hashline-switch${checked ? " is-on" : ""}`,
|
|
112
|
+
onClick: () => onChange(!checked),
|
|
113
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hashline-knob" })
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
function Summary(props) {
|
|
118
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: props.t("rowSummary") });
|
|
119
|
+
}
|
|
120
|
+
function RowForm(props) {
|
|
121
|
+
const { form, t } = props;
|
|
122
|
+
const ready = form?.state.status === "ready" && form.state.writable !== false;
|
|
123
|
+
const [draft, setDraft] = (0, import_react.useState)(
|
|
124
|
+
() => readValues(form?.state.value ?? {})
|
|
125
|
+
);
|
|
126
|
+
const [baseline, setBaseline] = (0, import_react.useState)(() => form?.state.value ?? {});
|
|
127
|
+
const [revision, setRevision] = (0, import_react.useState)(form?.state.revision);
|
|
128
|
+
const [dirty, setDirty] = (0, import_react.useState)(false);
|
|
129
|
+
const [busy, setBusy] = (0, import_react.useState)(false);
|
|
130
|
+
const [message, setMessage] = (0, import_react.useState)("");
|
|
131
|
+
(0, import_react.useEffect)(() => {
|
|
132
|
+
if (dirty) return;
|
|
133
|
+
setDraft(readValues(form?.state.value ?? {}));
|
|
134
|
+
setBaseline(form?.state.value ?? {});
|
|
135
|
+
setRevision(form?.state.revision);
|
|
136
|
+
}, [form?.state.revision, form?.state.status, dirty]);
|
|
137
|
+
if (!ready) {
|
|
138
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "hashline-note", role: "status", children: t(form?.state.status === "loading" ? "loading" : "unavailable") });
|
|
139
|
+
}
|
|
140
|
+
const toggle = (key, checked) => {
|
|
141
|
+
setDraft({ ...draft, [key]: checked });
|
|
142
|
+
setDirty(true);
|
|
143
|
+
setMessage("");
|
|
144
|
+
};
|
|
145
|
+
const reset = () => {
|
|
146
|
+
setDraft(readValues(baseline));
|
|
147
|
+
setDirty(false);
|
|
148
|
+
setMessage("");
|
|
149
|
+
};
|
|
150
|
+
const save = async () => {
|
|
151
|
+
if (form === void 0 || busy || revision === void 0) return;
|
|
152
|
+
const base = readValues(baseline);
|
|
153
|
+
const operations = [];
|
|
154
|
+
for (const spec of SWITCHES) {
|
|
155
|
+
if (draft[spec.key] !== base[spec.key]) {
|
|
156
|
+
operations.push({ op: "set", path: [spec.key], value: draft[spec.key] });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
setBusy(true);
|
|
160
|
+
setMessage("");
|
|
161
|
+
try {
|
|
162
|
+
const accepted = await form.mutate(operations, revision);
|
|
163
|
+
setMessage(t(accepted ? "saved" : "rejected"));
|
|
164
|
+
if (accepted) setDirty(false);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
setMessage(error instanceof Error ? error.message : String(error));
|
|
167
|
+
} finally {
|
|
168
|
+
setBusy(false);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
172
|
+
"form",
|
|
173
|
+
{
|
|
174
|
+
className: "hashline-settings",
|
|
175
|
+
onSubmit: (event) => {
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
void save();
|
|
178
|
+
},
|
|
179
|
+
children: [
|
|
180
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "hashline-card", children: [
|
|
181
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h4", { className: "hashline-title", children: t("groupTools") }),
|
|
182
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "hashline-hint", children: t("groupToolsHint") }),
|
|
183
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hashline-rows", children: SWITCHES.map((spec) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hashline-row", children: [
|
|
184
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hashline-text", children: [
|
|
185
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hashline-label", children: t(spec.label) }),
|
|
186
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "hashline-hint", children: t(spec.hint) })
|
|
187
|
+
] }),
|
|
188
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
189
|
+
Switch,
|
|
190
|
+
{
|
|
191
|
+
checked: draft[spec.key] === true,
|
|
192
|
+
label: t(spec.label),
|
|
193
|
+
disabled: busy,
|
|
194
|
+
onChange: (checked) => toggle(spec.key, checked)
|
|
195
|
+
}
|
|
196
|
+
)
|
|
197
|
+
] }, spec.key)) })
|
|
198
|
+
] }),
|
|
199
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hashline-actions", children: [
|
|
200
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "submit", className: "hashline-primary", disabled: busy || !dirty, children: t(busy ? "saving" : "save") }),
|
|
201
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "hashline-reset", onClick: reset, disabled: busy, children: t("reset") })
|
|
202
|
+
] }),
|
|
203
|
+
message !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "hashline-note", role: "status", children: message })
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
var name = "dsh-hashline-edit-settings";
|
|
209
|
+
var inject = ["slots", "locale"];
|
|
210
|
+
function apply(ctx) {
|
|
211
|
+
ctx.effect(() => ctx.locale.register("hashlineEdit", { en, zh }));
|
|
212
|
+
ctx.effect(() => {
|
|
213
|
+
const style = document.createElement("style");
|
|
214
|
+
style.dataset.dshHashlineEdit = "true";
|
|
215
|
+
style.textContent = CSS;
|
|
216
|
+
document.head.append(style);
|
|
217
|
+
return () => style.remove();
|
|
218
|
+
});
|
|
219
|
+
ctx.slots.inject("plugins.row.config", function* () {
|
|
220
|
+
yield ctx.slots.register(
|
|
221
|
+
{
|
|
222
|
+
name: "plugins.row.config",
|
|
223
|
+
key: "dsh-hashline-edit#dsh-hashline-edit",
|
|
224
|
+
locale: "hashlineEdit"
|
|
225
|
+
},
|
|
226
|
+
(props) => props.view === "summary" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Summary, { ...props }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RowForm, { ...props })
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
})(module,module.exports,require);return module.exports;}});
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// dsh/index.ts
|
|
2
|
+
import Schema from "@deepseek-ai/schemastery";
|
|
3
|
+
|
|
1
4
|
// dsh/fs.ts
|
|
2
5
|
import { constants } from "node:fs";
|
|
3
6
|
import {
|
|
@@ -45,7 +48,7 @@ import {
|
|
|
45
48
|
import { dirname as dirname2, join as join4, parse, resolve, sep } from "node:path";
|
|
46
49
|
|
|
47
50
|
// src/hashline/alphabet.ts
|
|
48
|
-
var HASH_LEN = true ?
|
|
51
|
+
var HASH_LEN = true ? 4 : resolveAnchorWidth(process.env);
|
|
49
52
|
var ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
50
53
|
var ALPHA_SAFE = ALPHA.replace(/-/g, "\\-");
|
|
51
54
|
var ALPHA_RE = new RegExp(`^[${ALPHA_SAFE}]+$`);
|
|
@@ -1672,7 +1675,7 @@ function toDshToolError(error, fallbackCode = "E_UNKNOWN") {
|
|
|
1672
1675
|
// dsh/metrics/instrument.ts
|
|
1673
1676
|
var WARNING_CODE = /\b(W_[A-Z0-9_]+)\b/u;
|
|
1674
1677
|
var RESULT_CODE = /\b([EW]_[A-Z0-9_]+)\b/u;
|
|
1675
|
-
var HASH_ROW =
|
|
1678
|
+
var HASH_ROW = new RegExp(`^${HASH_CLASS}\u2502`, "u");
|
|
1676
1679
|
function jsonChars(value) {
|
|
1677
1680
|
if (value === void 0) return 0;
|
|
1678
1681
|
try {
|
|
@@ -2236,7 +2239,14 @@ function resolveSectionText(rootCtx, section, presetId, homeDir, orderFor, warne
|
|
|
2236
2239
|
return section.renderDefault();
|
|
2237
2240
|
}
|
|
2238
2241
|
}
|
|
2239
|
-
function
|
|
2242
|
+
function sectionEnabled(name2, toggles) {
|
|
2243
|
+
if (toggles === void 0) return true;
|
|
2244
|
+
if (name2 === "tool:read") return toggles.read;
|
|
2245
|
+
if (name2 === "tool:apply_patch") return toggles.applyPatch;
|
|
2246
|
+
if (name2 === "tool:undo_last_edit") return toggles.undo;
|
|
2247
|
+
return true;
|
|
2248
|
+
}
|
|
2249
|
+
function registerGuidanceSections(rootCtx, agentCtx, enabled) {
|
|
2240
2250
|
const registry = registryFor(agentCtx) ?? registryFor(rootCtx);
|
|
2241
2251
|
if (registry === void 0) {
|
|
2242
2252
|
warn2(rootCtx, "systemPrompt registry unavailable; hashline guidance not registered");
|
|
@@ -2267,6 +2277,7 @@ function registerGuidanceSections(rootCtx, agentCtx) {
|
|
|
2267
2277
|
for (let index = 0; index < sections.length; index++) {
|
|
2268
2278
|
const section = sections[index];
|
|
2269
2279
|
const definition = GUIDANCE_SECTIONS2[index];
|
|
2280
|
+
if (!sectionEnabled(definition.name, enabled)) continue;
|
|
2270
2281
|
if (section.malformed !== void 0) {
|
|
2271
2282
|
warn2(
|
|
2272
2283
|
rootCtx,
|
|
@@ -8957,7 +8968,7 @@ async function publishPluginWrites(deps, requestPaths, ctx, exec) {
|
|
|
8957
8968
|
|
|
8958
8969
|
// dsh/tools/read.ts
|
|
8959
8970
|
var READ_TOOL_NAME = "read";
|
|
8960
|
-
var READ_DESCRIPTION =
|
|
8971
|
+
var READ_DESCRIPTION = 'Read a UTF-8 text file as hash-anchored `HASH\u2502content` rows; each anchor is the `apply_patch` identity for that line. Served mode is the default; `mode: "verbatim"` returns plain text with no anchors. Supports offset/limit pages and `windows` (offset/limit or around_anchor).';
|
|
8961
8972
|
var RENDER_MODES = ["served", "verbatim"];
|
|
8962
8973
|
function createDefaultReadSpanProvider(io) {
|
|
8963
8974
|
const readTextFile = io === void 0 ? void 0 : async (path) => {
|
|
@@ -9415,7 +9426,7 @@ function buildReadTool(deps, options = {}) {
|
|
|
9415
9426
|
},
|
|
9416
9427
|
windows: {
|
|
9417
9428
|
type: "array",
|
|
9418
|
-
description: `Optional array of up to ${MAX_READ_WINDOWS}
|
|
9429
|
+
description: `Optional array of up to ${MAX_READ_WINDOWS} windows, each an offset/limit range or an around_anchor + radius around a served anchor.`,
|
|
9419
9430
|
items: {
|
|
9420
9431
|
oneOf: [
|
|
9421
9432
|
{
|
|
@@ -9425,7 +9436,7 @@ function buildReadTool(deps, options = {}) {
|
|
|
9425
9436
|
around_anchor: {
|
|
9426
9437
|
type: "string",
|
|
9427
9438
|
required: true,
|
|
9428
|
-
description: "A
|
|
9439
|
+
description: "A served anchor: the window centres on its line"
|
|
9429
9440
|
},
|
|
9430
9441
|
radius: {
|
|
9431
9442
|
type: "number",
|
|
@@ -9455,7 +9466,7 @@ function buildReadTool(deps, options = {}) {
|
|
|
9455
9466
|
mode: {
|
|
9456
9467
|
type: "string",
|
|
9457
9468
|
enum: RENDER_MODES,
|
|
9458
|
-
description:
|
|
9469
|
+
description: "`served` (default) returns `HASH\u2502content` rows and records them as served; `verbatim` returns plain text with no anchors."
|
|
9459
9470
|
}
|
|
9460
9471
|
},
|
|
9461
9472
|
output: {
|
|
@@ -13126,7 +13137,7 @@ var TOOL_RESULT_SCHEMA = {
|
|
|
13126
13137
|
details: { type: "json", required: true }
|
|
13127
13138
|
}
|
|
13128
13139
|
};
|
|
13129
|
-
var DIFF_ROW =
|
|
13140
|
+
var DIFF_ROW = new RegExp(`^([ +-]).{${HASH_LEN}}\u2502(.*)$`, "u");
|
|
13130
13141
|
var DIFF_FILE_HEADER = /^--- (.+) ---$/u;
|
|
13131
13142
|
var MAX_META_HUNKS = 24;
|
|
13132
13143
|
var MAX_META_CHARS = 8192;
|
|
@@ -13240,9 +13251,14 @@ var APPLY_PATCH_PARAMETERS = {
|
|
|
13240
13251
|
input: {
|
|
13241
13252
|
type: "string",
|
|
13242
13253
|
required: true,
|
|
13243
|
-
description: "
|
|
13254
|
+
description: "Patch text: start with *** Begin Patch and end with *** End Patch. `*** Add File: path` with + lines; `*** Update File: path`, optionally `*** Move to: path`, with fragment hunks (`@@` or `@@ <context>`, body lines prefixed space/+/-) or identity hunks (`@@<a>` / `@@<a>-<b>`, + lines only; empty body deletes); `*** Copy From: path <a>-<b>` / `*** Cut From: path <a>-<b>` reuse a served span; `*** End of File` pins a hunk to the file end; `*** Delete File: path` deletes. Paths are workspace-relative or absolute."
|
|
13244
13255
|
}
|
|
13245
13256
|
};
|
|
13257
|
+
var REFERENCE_HEADER = new RegExp(
|
|
13258
|
+
`^\\*\\*\\* (?:Copy From|Cut From): (.+) (${HASH_CLASS}(?:-${HASH_CLASS})?)$`,
|
|
13259
|
+
"u"
|
|
13260
|
+
);
|
|
13261
|
+
var CUT_HEADER = new RegExp(`^\\*\\*\\* Cut From: (.+) (${HASH_CLASS}(?:-${HASH_CLASS})?)$`, "u");
|
|
13246
13262
|
function previewPatch(input) {
|
|
13247
13263
|
const diffs = [];
|
|
13248
13264
|
const locations = [];
|
|
@@ -13271,7 +13287,7 @@ function previewPatch(input) {
|
|
|
13271
13287
|
locations.push(move2[1].trim());
|
|
13272
13288
|
continue;
|
|
13273
13289
|
}
|
|
13274
|
-
const reference =
|
|
13290
|
+
const reference = REFERENCE_HEADER.exec(line);
|
|
13275
13291
|
if (reference !== null) {
|
|
13276
13292
|
locations.push(reference[1].trim());
|
|
13277
13293
|
continue;
|
|
@@ -13302,7 +13318,7 @@ function writtenPatchPaths(input) {
|
|
|
13302
13318
|
add(move2[1]);
|
|
13303
13319
|
continue;
|
|
13304
13320
|
}
|
|
13305
|
-
const cut =
|
|
13321
|
+
const cut = CUT_HEADER.exec(line);
|
|
13306
13322
|
if (cut !== null) add(cut[1]);
|
|
13307
13323
|
}
|
|
13308
13324
|
return paths;
|
|
@@ -13779,8 +13795,34 @@ function registerWriteHook(rootCtx, agentCtx, io, observations) {
|
|
|
13779
13795
|
// dsh/index.ts
|
|
13780
13796
|
var name = "dsh-hashline-edit";
|
|
13781
13797
|
var inject = ["tools", "systemPrompt"];
|
|
13798
|
+
var Config = Schema.object({
|
|
13799
|
+
read: Schema.boolean().default(true).volatile(),
|
|
13800
|
+
applyPatch: Schema.boolean().default(true).volatile(),
|
|
13801
|
+
undo: Schema.boolean().default(false).volatile()
|
|
13802
|
+
});
|
|
13803
|
+
function switchValue(value, fallback) {
|
|
13804
|
+
if (typeof value === "boolean") return value;
|
|
13805
|
+
if (value !== null && typeof value === "object" && typeof value.get === "function") {
|
|
13806
|
+
try {
|
|
13807
|
+
const inner = value.get();
|
|
13808
|
+
return typeof inner === "boolean" ? inner : fallback;
|
|
13809
|
+
} catch {
|
|
13810
|
+
return fallback;
|
|
13811
|
+
}
|
|
13812
|
+
}
|
|
13813
|
+
return fallback;
|
|
13814
|
+
}
|
|
13815
|
+
function resolveSwitches(config = {}) {
|
|
13816
|
+
const fields = config;
|
|
13817
|
+
return {
|
|
13818
|
+
read: switchValue(fields.read, true),
|
|
13819
|
+
applyPatch: switchValue(fields.applyPatch, true),
|
|
13820
|
+
undo: switchValue(fields.undo, false)
|
|
13821
|
+
};
|
|
13822
|
+
}
|
|
13782
13823
|
function installAgentTools(rootCtx, agent, metrics, config = {}) {
|
|
13783
|
-
|
|
13824
|
+
const switches = resolveSwitches(config);
|
|
13825
|
+
return agent.ctx.effect(() => {
|
|
13784
13826
|
const io = createLocalFileIO();
|
|
13785
13827
|
const sandbox = new FsSandboxController(rootCtx);
|
|
13786
13828
|
const fsService = rootCtx.get("fs");
|
|
@@ -13795,27 +13837,31 @@ function installAgentTools(rootCtx, agent, metrics, config = {}) {
|
|
|
13795
13837
|
};
|
|
13796
13838
|
const disposers = [];
|
|
13797
13839
|
try {
|
|
13798
|
-
|
|
13799
|
-
|
|
13800
|
-
|
|
13801
|
-
|
|
13802
|
-
|
|
13803
|
-
|
|
13804
|
-
|
|
13805
|
-
|
|
13806
|
-
|
|
13807
|
-
|
|
13808
|
-
|
|
13809
|
-
|
|
13810
|
-
|
|
13811
|
-
|
|
13812
|
-
|
|
13840
|
+
if (switches.read) {
|
|
13841
|
+
const spanProvider = createDefaultReadSpanProvider(coreIo);
|
|
13842
|
+
disposers.push(agent.ctx.tools.register(buildReadTool(deps, { spanProvider })));
|
|
13843
|
+
}
|
|
13844
|
+
if (switches.applyPatch) {
|
|
13845
|
+
const applyPatch = registerApplyPatchTool(rootCtx, agent, deps);
|
|
13846
|
+
if (applyPatch !== null) {
|
|
13847
|
+
disposers.push(applyPatch.dispose);
|
|
13848
|
+
disposers.push(
|
|
13849
|
+
registerNativeToolHiding(rootCtx, agent.ctx, {
|
|
13850
|
+
toolName: applyPatch.name,
|
|
13851
|
+
scope: agent
|
|
13852
|
+
})
|
|
13853
|
+
);
|
|
13854
|
+
}
|
|
13855
|
+
}
|
|
13856
|
+
if (switches.undo) {
|
|
13857
|
+
disposers.push(agent.ctx.tools.register(buildUndoTool(deps)));
|
|
13858
|
+
}
|
|
13859
|
+
disposers.push(registerGuidanceSections(rootCtx, agent.ctx, switches));
|
|
13860
|
+
if (switches.read) {
|
|
13861
|
+
disposers.push(registerWriteHook(rootCtx, agent.ctx, io, bridge?.observations));
|
|
13862
|
+
disposers.push(registerShellHook(rootCtx, agent.ctx, deps));
|
|
13813
13863
|
}
|
|
13814
|
-
disposers.push(agent.ctx.tools.register(undoDefinition));
|
|
13815
|
-
disposers.push(registerGuidanceSections(rootCtx, agent.ctx));
|
|
13816
|
-
disposers.push(registerWriteHook(rootCtx, agent.ctx, io, bridge?.observations));
|
|
13817
13864
|
disposers.push(registerToolObserver(agent.ctx, metrics));
|
|
13818
|
-
disposers.push(registerShellHook(rootCtx, agent.ctx, deps));
|
|
13819
13865
|
} catch (error) {
|
|
13820
13866
|
disposeReverse(disposers);
|
|
13821
13867
|
throw error;
|
|
@@ -13846,6 +13892,15 @@ function disposeReverse(disposers) {
|
|
|
13846
13892
|
function warnVia3(rootCtx, message) {
|
|
13847
13893
|
rootCtx.logger.warn(`dsh-hashline-edit: ${message}`);
|
|
13848
13894
|
}
|
|
13895
|
+
var pluginStates = /* @__PURE__ */ new WeakMap();
|
|
13896
|
+
function liveAgentsFrom(rootCtx) {
|
|
13897
|
+
try {
|
|
13898
|
+
const service = rootCtx.get("agents");
|
|
13899
|
+
return typeof service?.list === "function" ? service.list() : [];
|
|
13900
|
+
} catch {
|
|
13901
|
+
return [];
|
|
13902
|
+
}
|
|
13903
|
+
}
|
|
13849
13904
|
function apply(rootCtx, config = {}) {
|
|
13850
13905
|
const metrics = createToolMetrics(
|
|
13851
13906
|
metricSinkFromEnv(process.env, (error) => {
|
|
@@ -13854,22 +13909,76 @@ function apply(rootCtx, config = {}) {
|
|
|
13854
13909
|
);
|
|
13855
13910
|
})
|
|
13856
13911
|
);
|
|
13857
|
-
|
|
13858
|
-
|
|
13859
|
-
|
|
13860
|
-
|
|
13912
|
+
let state = pluginStates.get(rootCtx);
|
|
13913
|
+
if (state === void 0) {
|
|
13914
|
+
state = { agents: /* @__PURE__ */ new Set(), installs: /* @__PURE__ */ new Map() };
|
|
13915
|
+
pluginStates.set(rootCtx, state);
|
|
13916
|
+
}
|
|
13917
|
+
const current = state;
|
|
13918
|
+
const reconcile = (agent) => {
|
|
13919
|
+
const previous = current.installs.get(agent);
|
|
13920
|
+
if (previous !== void 0) {
|
|
13921
|
+
current.installs.delete(agent);
|
|
13922
|
+
try {
|
|
13923
|
+
previous();
|
|
13924
|
+
} catch {
|
|
13925
|
+
}
|
|
13926
|
+
}
|
|
13861
13927
|
try {
|
|
13862
|
-
installAgentTools(rootCtx, agent, metrics, config);
|
|
13928
|
+
current.installs.set(agent, installAgentTools(rootCtx, agent, metrics, config));
|
|
13863
13929
|
} catch (error) {
|
|
13864
13930
|
rootCtx.logger.warn(
|
|
13865
13931
|
`dsh-hashline-edit: failed to install tools for agent ${agent.id}: ${error instanceof Error ? error.message : String(error)}`
|
|
13866
13932
|
);
|
|
13867
13933
|
}
|
|
13934
|
+
};
|
|
13935
|
+
const live = liveAgentsFrom(rootCtx);
|
|
13936
|
+
if (live.length > 0) {
|
|
13937
|
+
const alive = new Set(live);
|
|
13938
|
+
const gone = [];
|
|
13939
|
+
for (const known of current.agents) if (!alive.has(known)) gone.push(known);
|
|
13940
|
+
for (const agent of gone) current.agents.delete(agent);
|
|
13941
|
+
for (const agent of live) current.agents.add(agent);
|
|
13942
|
+
}
|
|
13943
|
+
for (const agent of current.agents) reconcile(agent);
|
|
13944
|
+
rootCtx.on("agent/created", ({ agent }) => {
|
|
13945
|
+
current.agents.add(agent);
|
|
13946
|
+
reconcile(agent);
|
|
13868
13947
|
});
|
|
13948
|
+
rootCtx.on("agent/disposed", ({ agent }) => {
|
|
13949
|
+
current.agents.delete(agent);
|
|
13950
|
+
const dispose = current.installs.get(agent);
|
|
13951
|
+
if (dispose !== void 0) {
|
|
13952
|
+
current.installs.delete(agent);
|
|
13953
|
+
try {
|
|
13954
|
+
dispose();
|
|
13955
|
+
} catch {
|
|
13956
|
+
}
|
|
13957
|
+
}
|
|
13958
|
+
});
|
|
13959
|
+
const onEvent = rootCtx.on;
|
|
13960
|
+
if (typeof onEvent === "function") {
|
|
13961
|
+
onEvent.call(rootCtx, "loader/volatile-update", () => {
|
|
13962
|
+
for (const agent of current.agents) reconcile(agent);
|
|
13963
|
+
});
|
|
13964
|
+
}
|
|
13965
|
+
if (typeof rootCtx.effect === "function") {
|
|
13966
|
+
rootCtx.effect(() => () => {
|
|
13967
|
+
for (const dispose of current.installs.values()) {
|
|
13968
|
+
try {
|
|
13969
|
+
dispose();
|
|
13970
|
+
} catch {
|
|
13971
|
+
}
|
|
13972
|
+
}
|
|
13973
|
+
current.installs.clear();
|
|
13974
|
+
});
|
|
13975
|
+
}
|
|
13869
13976
|
}
|
|
13870
13977
|
export {
|
|
13978
|
+
Config,
|
|
13871
13979
|
apply,
|
|
13872
13980
|
inject,
|
|
13873
13981
|
installAgentTools,
|
|
13874
|
-
name
|
|
13982
|
+
name,
|
|
13983
|
+
resolveSwitches
|
|
13875
13984
|
};
|
|
@@ -116,4 +116,10 @@ export declare function guidanceHomeDir(env?: Record<string, string | undefined>
|
|
|
116
116
|
* come from the shared prompt sources, so the plugin package must ship
|
|
117
117
|
* `prompts/` for them to load.
|
|
118
118
|
*/
|
|
119
|
-
|
|
119
|
+
/** Which feature tools are enabled, so a disabled tool's guidance section is not registered. */
|
|
120
|
+
export interface GuidanceToggles {
|
|
121
|
+
readonly read: boolean;
|
|
122
|
+
readonly applyPatch: boolean;
|
|
123
|
+
readonly undo: boolean;
|
|
124
|
+
}
|
|
125
|
+
export declare function registerGuidanceSections(rootCtx: Context, agentCtx: Context, enabled?: GuidanceToggles): () => void;
|
package/lib/types/dsh/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import type { Context } from "@deepseek-ai/cordis";
|
|
19
19
|
import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
20
|
+
import Schema from "@deepseek-ai/schemastery";
|
|
20
21
|
import { type ToolMetrics } from "./metrics/instrument.js";
|
|
21
22
|
/** Cordis plugin name used by loader diagnostics. */
|
|
22
23
|
export declare const name = "dsh-hashline-edit";
|
|
@@ -25,17 +26,47 @@ export declare const name = "dsh-hashline-edit";
|
|
|
25
26
|
* services are resolved with `ctx.get` inside `dsh/sandbox.ts` instead.
|
|
26
27
|
*/
|
|
27
28
|
export declare const inject: string[];
|
|
29
|
+
/**
|
|
30
|
+
* The row's editable config schema. Exporting `Config` is what makes the host
|
|
31
|
+
* expose `dsh-hashline-edit#dsh-hashline-edit` as a writable settings namespace,
|
|
32
|
+
* so the Plugins-page card can render its three switches and write them back.
|
|
33
|
+
*/
|
|
34
|
+
export declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
|
|
35
|
+
read: Schema<boolean, boolean, "volatile-defined">;
|
|
36
|
+
applyPatch: Schema<boolean, boolean, "volatile-defined">;
|
|
37
|
+
undo: Schema<boolean, boolean, "volatile-defined">;
|
|
38
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
39
|
+
read: Schema<boolean, boolean, "volatile-defined">;
|
|
40
|
+
applyPatch: Schema<boolean, boolean, "volatile-defined">;
|
|
41
|
+
undo: Schema<boolean, boolean, "volatile-defined">;
|
|
42
|
+
}>>, "plain">;
|
|
28
43
|
/** The plugin configuration the loader passes as the second `apply` argument. */
|
|
29
44
|
export interface PluginConfig {
|
|
30
45
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
|
|
46
|
+
* Expose the hashline `read` tool, shadowing the host's native `read`.
|
|
47
|
+
* Defaults to true; off leaves the native `read` in place.
|
|
48
|
+
*/
|
|
49
|
+
read?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Expose the hashline `apply_patch` tool and hide the native
|
|
52
|
+
* `edit`/`write`/`apply_patch`/`str_replace_editor` tools. Defaults to true;
|
|
53
|
+
* off keeps every native mutation tool.
|
|
34
54
|
*/
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
|
|
55
|
+
applyPatch?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Expose the `undo_last_edit` tool. Defaults to false. The undo snapshot row is
|
|
58
|
+
* still written on every commit; only the model-facing tool is opt-in.
|
|
59
|
+
*/
|
|
60
|
+
undo?: boolean;
|
|
61
|
+
}
|
|
62
|
+
/** The three feature switches after the shipped defaults are applied. */
|
|
63
|
+
export interface ResolvedSwitches {
|
|
64
|
+
readonly read: boolean;
|
|
65
|
+
readonly applyPatch: boolean;
|
|
66
|
+
readonly undo: boolean;
|
|
38
67
|
}
|
|
68
|
+
/** Resolve the plugin's three switches with the shipped defaults: read and apply_patch on, undo off. */
|
|
69
|
+
export declare function resolveSwitches(config?: PluginConfig): ResolvedSwitches;
|
|
39
70
|
/**
|
|
40
71
|
* Install the hashline toolset for one agent inside its own effect, so the
|
|
41
72
|
* registrations are disposed in reverse order when the agent scope unwinds.
|
|
@@ -43,6 +74,6 @@ export interface PluginConfig {
|
|
|
43
74
|
* Exported separately from {@link apply} so the host test can drive one fake
|
|
44
75
|
* agent directly and integration probes can reuse the exact install path.
|
|
45
76
|
*/
|
|
46
|
-
export declare function installAgentTools(rootCtx: Context, agent: Agent, metrics?: ToolMetrics, config?: PluginConfig): void;
|
|
47
|
-
/** Mount the plugin
|
|
77
|
+
export declare function installAgentTools(rootCtx: Context, agent: Agent, metrics?: ToolMetrics, config?: PluginConfig): () => void;
|
|
78
|
+
/** Mount the plugin and reconcile live agents on every (re)apply. */
|
|
48
79
|
export declare function apply(rootCtx: Context, config?: PluginConfig): void;
|
|
@@ -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
|
|
57
|
+
/** The line's served anchor, usable by `apply_patch`. */
|
|
58
58
|
readonly hash: string;
|
|
59
59
|
/** The line's decoded text, without the anchor prefix. */
|
|
60
60
|
readonly text: string;
|
|
@@ -20,7 +20,7 @@ export interface ParsePatchOptions {
|
|
|
20
20
|
export declare function parseApplyPatch(input: string, options?: ParsePatchOptions): ParsePatchResult;
|
|
21
21
|
/**
|
|
22
22
|
* Parse an anchor token: `<a>` (single line) or `<a>-<b>` (closed span).
|
|
23
|
-
* Returns `undefined` when the token is not a valid
|
|
23
|
+
* Returns `undefined` when the token is not a valid anchor pair; the
|
|
24
24
|
* grammar layer turns that into an `E_PATCH_SYNTAX` problem at the patch line.
|
|
25
25
|
*
|
|
26
26
|
* @param token - the text after `@@` (no leading space).
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* (`PatchPlan`, `PlannedFile`, `PlannedMutation`),
|
|
9
9
|
* - the **result / error** types the renderer and the host adapter consume.
|
|
10
10
|
*
|
|
11
|
-
* The engine underneath is the existing hashline kernel: content-addressed
|
|
11
|
+
* The engine underneath is the existing hashline kernel: content-addressed
|
|
12
12
|
* anchors, served leases, MVCC snapshots, correlated multi-file transactions and
|
|
13
13
|
* the undo store. Nothing here re-declares those primitives; the plan IR only
|
|
14
14
|
* adds the addressing and lifecycle vocabulary apply_patch needs.
|
|
@@ -23,7 +23,7 @@ import type { LineEnding } from "../edit-diff.js";
|
|
|
23
23
|
import type { HashStore } from "../hash-store.js";
|
|
24
24
|
import type { TransactionRecoveryReport } from "../mutation-engine/types.js";
|
|
25
25
|
/** Patch AST (grammar layer output). */
|
|
26
|
-
/**
|
|
26
|
+
/** An anchor token: `HASH_LEN` characters of the hashline alphabet. */
|
|
27
27
|
export type Anchor = string;
|
|
28
28
|
/** A 1-based line number in the submitted patch text, used by every diagnostic. */
|
|
29
29
|
export type PatchLineNumber = number;
|
|
@@ -164,7 +164,7 @@ export interface FileBaseline {
|
|
|
164
164
|
readonly absolutePath: string;
|
|
165
165
|
/** LF-normalized text; `""` for an Add File target. */
|
|
166
166
|
readonly text: string;
|
|
167
|
-
/**
|
|
167
|
+
/** Anchors parallel to the file's lines; empty for an Add File target. */
|
|
168
168
|
readonly hashes: readonly string[];
|
|
169
169
|
/** Byte-order mark the file carried ("" when none). */
|
|
170
170
|
readonly bom: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-hashline-edit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Hash-anchored read/apply_patch tools for DeepSeek Harness (dsh). Every line gets a unique 3- or 4-char hash (A-Za-z0-9, configurable) that stays stable across edits; apply_patch is the only file-mutation entry point and commits one patch batch per transaction. Undo persists across restarts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"anchors",
|
|
@@ -42,6 +42,9 @@
|
|
|
42
42
|
"types": "./lib/types/dsh/index.d.ts",
|
|
43
43
|
"default": "./lib/index.js"
|
|
44
44
|
},
|
|
45
|
+
"./client": {
|
|
46
|
+
"default": "./lib/client.js"
|
|
47
|
+
},
|
|
45
48
|
"./package.json": "./package.json"
|
|
46
49
|
},
|
|
47
50
|
"scripts": {
|
|
@@ -49,6 +52,7 @@
|
|
|
49
52
|
"test:watch": "vitest",
|
|
50
53
|
"test:coverage": "vitest run --coverage --coverage.thresholds.lines=85 --coverage.thresholds.statements=85 --coverage.thresholds.functions=85 --coverage.thresholds.branches=80",
|
|
51
54
|
"test:win": "vitest run --config vitest.config.windows.ts",
|
|
55
|
+
"test:win:3": "node scripts/test-width.mjs 3 dsh",
|
|
52
56
|
"test:win:coverage": "vitest run --config vitest.config.windows.ts --coverage --coverage.thresholds.lines=85 --coverage.thresholds.statements=85 --coverage.thresholds.functions=85 --coverage.thresholds.branches=80",
|
|
53
57
|
"lint": "oxlint . && knip --max-issues 0",
|
|
54
58
|
"lint:knip": "knip --max-issues 0",
|
|
@@ -78,16 +82,19 @@
|
|
|
78
82
|
"@deepseek-ai/dsh-home-paths": "0.2.2-rc.1",
|
|
79
83
|
"@deepseek-ai/dsh-sandbox": "0.2.2-rc.1",
|
|
80
84
|
"@deepseek-ai/dsh-tools": "0.2.2-rc.1",
|
|
85
|
+
"@deepseek-ai/schemastery": "3.19.0",
|
|
81
86
|
"@semantic-release/git": "^11.0.1",
|
|
82
87
|
"@semantic-release/github": "^12.0.9",
|
|
83
88
|
"@semantic-release/npm": "^13.1.5",
|
|
84
89
|
"@types/node": "^24.0.0",
|
|
90
|
+
"@types/react": "18.3.1",
|
|
85
91
|
"@vitest/coverage-v8": "^4.1.10",
|
|
86
92
|
"esbuild": "0.28.2",
|
|
87
93
|
"husky": "^9.1.7",
|
|
88
94
|
"knip": "6.40.0",
|
|
89
95
|
"oxfmt": "^0.66.0",
|
|
90
96
|
"oxlint": "^1.81.0",
|
|
97
|
+
"react": "18.3.1",
|
|
91
98
|
"semantic-release": "^25.0.9",
|
|
92
99
|
"typescript": "^7.0.0",
|
|
93
100
|
"vite": "^8.3.5",
|
|
@@ -112,6 +119,18 @@
|
|
|
112
119
|
"dsh": {
|
|
113
120
|
"bundle": {
|
|
114
121
|
"patch": "./cordis.patch.yml"
|
|
122
|
+
},
|
|
123
|
+
"client": {
|
|
124
|
+
"inject": [
|
|
125
|
+
"@deepseek-ai/dsh-api-gateway",
|
|
126
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
127
|
+
"@deepseek-ai/dsh-client-locale",
|
|
128
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager",
|
|
129
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
130
|
+
"@deepseek-ai/dsh-client-ui-primitives"
|
|
131
|
+
],
|
|
132
|
+
"platform": "web",
|
|
133
|
+
"immediately": true
|
|
115
134
|
}
|
|
116
135
|
}
|
|
117
136
|
}
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
- apply_patch: one call = one transaction
|
|
2
|
-
- apply_patch:
|
|
3
|
-
- apply_patch:
|
|
4
|
-
- apply_patch:
|
|
5
|
-
- apply_patch: identity mode accepts only `+` lines; an empty body deletes the span. A `-` or context line in an identity hunk, or body lines in a Copy/Cut hunk, rejects at admission with its patch line number.
|
|
6
|
-
- apply_patch: every hunk resolves against the file as it was before the call; later windows begin after the previous hunk's end. Overlapping target ranges in one file reject the whole batch.
|
|
7
|
-
- apply_patch: lifecycle joins the same transaction and undo. `*** Add File:` content lines all start with `+`; `*** Delete File:` removes the file; `*** Move to:` must immediately follow `*** Update File:` and renames before its hunks apply; an existing move target rejects.
|
|
8
|
-
- apply_patch: a rejection writes nothing. Read the `- patch line N ([E_*], path): ...` lines, then use the immediately following `HASH│content` block — those are the current rows and anchors; retry directly without issuing a separate `read`.
|
|
9
|
-
- apply_patch: a success starts with `Updated N files:` and ends with the changed lines' fresh anchors per file; use those anchors directly in the next identity hunk. `[W_*]` lines are informational.
|
|
10
|
-
- apply_patch: paths are workspace-relative or workspace-absolute; `..`, `.git`, and symlink targets reject. Whole-file replacement is one `*** Update File:` identity hunk spanning every served line with `+` lines for the new content.
|
|
1
|
+
- apply_patch: one call = one transaction; batch related changes and split only when an earlier RESULT decides the next.
|
|
2
|
+
- apply_patch: hunks are validated against the pre-call file in order (overlapping ranges reject the batch); a fragment hunk must match exactly once — add context to disambiguate, never guess — while identity hunks target served rows and a stale or unknown anchor is rejected; both serve the current rows back.
|
|
3
|
+
- apply_patch: a rejection writes nothing; retry from the served `HASH│content` rows without another `read`.
|
|
4
|
+
- apply_patch: success returns fresh anchors for changed lines; whole-file replacement is one identity hunk over every served line.
|
package/prompts/apply-patch.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
Create, update, delete, move, copy, or cut UTF-8 text files with a single transactional patch; prefer it over multiple single-file edits when a change spans files or separated regions. Pass the complete patch as `input`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
- read: call it
|
|
2
|
-
- read:
|
|
3
|
-
- read:
|
|
4
|
-
- read:
|
|
5
|
-
- read:
|
|
6
|
-
- read: PNG/JPG
|
|
1
|
+
- read: call it for content the tool has never served you; a bash view or an auto-read already served the file, so `apply_patch` can use those rows.
|
|
2
|
+
- read: batch several disjoint ranges in ONE call with `windows` — an offset/limit range, or `around_anchor` plus `radius` around a line this session served.
|
|
3
|
+
- read: pass `mode: "verbatim"` when you will not `apply_patch` the file (config, docs, a README, skill files); otherwise omit it so the rows carry anchors.
|
|
4
|
+
- read: a window of 61 lines or fewer may widen to its enclosing block; widened rows carry a `[widened …]` note. Caps are 80 lines for code/Markdown and 40 for plain text.
|
|
5
|
+
- read: read several files with parallel `read` calls, one file per call.
|
|
6
|
+
- read: PNG/JPG/GIF/WebP files return as an image attachment for multimodal models; binary files and directories are refused.
|
package/prompts/read.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
Reads a UTF-8 file as `HASH│content` rows in served mode; each row's anchor is the `apply_patch` identity anchor for that line. Pass `mode: "verbatim"` for plain text with no anchors when you will not apply_patch; verbatim is not capped by line budget. Batch disjoint ranges in one call with `windows` (offset/limit or `around_anchor`).
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
- apply_patch:一次调用 = 一个事务;把相关改动成批处理,只有当先前的结果决定下一步时才拆分。
|
|
2
|
+
- apply_patch:各 hunk 按顺序针对调用前的文件校验(重叠范围会拒绝整批);片段 hunk 必须恰好匹配一次——补充上下文以消歧,绝不猜测——身份 hunk 定位已服务的行,陈旧或未知锚点会被拒绝;两者都会回送当前行。
|
|
3
|
+
- apply_patch:拒绝时不写入任何内容;从回送的 `HASH│content` 行重试,无需再次 `read`。
|
|
4
|
+
- apply_patch:成功时返回改动行的新锚点;整文件替换是用一个身份 hunk 覆盖每一行已服务的行。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
用单个事务补丁创建、更新、删除、移动、复制或剪切 UTF-8 文本文件;当改动跨多个文件或分散区域时,优先于多次单文件编辑。把完整补丁作为 `input` 传入。
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
- read:仅在工具尚未服务给你信息时调用,例如自动读取 diff 之外的内容。bash 视图或自动读取已经服务过该文件,因此直接用那些行 apply_patch。
|
|
2
|
+
- read:当一次替换需要多个块或两端边界时,把每个范围放进同一次调用。用 `{ file, windows: [{ offset, limit }] }`。每个窗口都在当轮被服务,因此其锚点可在同一次 apply_patch 调用中使用。窗口也可以是 `{ around_anchor, radius }`,以本会话已服务的某行为中心。本会话未服务过的锚点会被省略,并内联标注 `[Window warning: ...]`。
|
|
3
|
+
- read:61 行及以内的请求窗口可扩展到其所在块;重叠或相邻窗口在此判定前合并。上限为:代码与 Markdown 80 行,纯文本 40 行。扩展的行以 `[widened <requested> -> <served>, <label>]` 开头;超过三个请求窗口的提示为 `[widened N windows: <ranges>]`。超限的 Markdown 章节会裁剪并标注 `(section <extent>)`;超限的代码或纯文本则拒绝且无提示。`mode: "verbatim"` 也会扩展,但不加锚点。
|
|
4
|
+
- read:当你不打算对该文件 apply_patch 时,用 `mode: "verbatim"` 获取无锚点的纯内容。例如配置值、文档、README 或技能内容(SKILL.md 或其所处目录中的任意文件)。当你打算用锚点配合 apply_patch 时,就不要指定 `mode`,让这些行带上锚点。
|
|
5
|
+
- read:每次调用只发送一个文件,形式为 `{ file, windows?, mode? }`,与 `apply_patch` 相同。单个顶层 `mode` 作用于该文件。要读多个文件就并行发起多次 `read` 调用。
|
|
6
|
+
- read:PNG/JPG 图像以图像附件形式返回,供多模态模型使用。像其他文件一样用 `file` 请求。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
通过 `file`、`windows?`、`mode?` 读取文件;默认返回可直接编辑的 HASH│content 行,`mode: "verbatim"` 在你不编辑时返回纯文本;用 `windows` 在一次调用中批量读取多个范围
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
通过 `file` 读取一个文本文件,可选 `windows` 与顶层 `mode`。要读多个文件就并行发起多次 `read` 调用。服务模式(默认)把每行返回为 HASH│content,带一个 {{HASH_LEN}} 字符的字母数字哈希。该哈希就是 `apply_patch` 的身份锚点,因此只复制 `│` 之前的 {{HASH_LEN}} 个字符。当你不打算 apply_patch 该文件时,用 `mode: "verbatim"` 获取无锚点的纯文本。`mode: "verbatim"` 不受行数上限限制。用 `windows` 指定范围:`[{ offset, limit }]`(offset 从 1 开始,limit 为行数,最多 16 个窗口)或 `[{ around_anchor: "<anchor>", radius: 10 }]`(针对本会话已服务的某行)。当页面未取全时,用 `windows: [{ offset, limit }]` 从下一个未显示行继续。61 行及以内的请求窗口在返回前可能扩展到其所在块;重叠或相邻的窗口在此判定前会合并。上限为:代码与 Markdown 80 行,纯文本 40 行。扩展页以 `[widened <requested> -> <served>, <label>]` 开头;超过三个请求窗口时折叠为 `[widened N windows: <ranges>]`。超限的 Markdown 章节会裁剪为上限大小的窗口并标注 `(section <extent>)`;超限的代码或纯文本块则拒绝,只返回所请求的窗口。`mode: "verbatim"` 同样会扩展,但不加锚点。行内的 `[...]` 提示描述的是返回结果,而非文件内容。空文件返回 `HASH│`(用 apply_patch 身份 hunk 插入)。PNG/JPG/GIF/WebP 以图像附件形式返回,供多模态模型使用。二进制与目录文件会被拒绝。哈希是文件作用域的:一个锚点绑定于它被服务时所在的文件。
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
- undo_last_edit:还原最近一次触及 `path` 处文件的 `apply_patch` 批次及其每个成员:文本 hunk 加文件生命周期操作(增/删/移动)。例外是外来 `cut`:此时撤销也会还原源文件。文件必须仍保有被编辑后的字节。带外写入会让调用以 `E_UNDO_STALE` 失败,且文件保持字节不变。一旦结果不对就立刻调用它。
|
|
2
|
+
- undo_last_edit:对该文件的 `write`,或之后的一次 `apply_patch` 批次,会替换撤销记录。一次读取、bash 视图或带外写入会保留该记录。当一次 apply_patch 的 diff 显示出你本想保留的行的 `-HASH│` 行时,该结果就是错的。例子包括一个收尾花括号、一句 import 或一个声明。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
撤销对 `path` 处文件最近一次的 `apply_patch` 批次
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
撤销最近一次触及 `path` 处文件的 `apply_patch` 批次,把整批——每个文本 hunk 加上文件生命周期操作(增/删/移动)——还原到先前状态。当某次 `apply_patch` 产生了错误结果(例如内容错误、重复行、语法损坏)时使用。该文件自那批之后必须未被改动。
|
|
@@ -101,7 +101,7 @@ export function parseApplyPatch(input: string, options?: ParsePatchOptions): Par
|
|
|
101
101
|
|
|
102
102
|
/**
|
|
103
103
|
* Parse an anchor token: `<a>` (single line) or `<a>-<b>` (closed span).
|
|
104
|
-
* Returns `undefined` when the token is not a valid
|
|
104
|
+
* Returns `undefined` when the token is not a valid anchor pair; the
|
|
105
105
|
* grammar layer turns that into an `E_PATCH_SYNTAX` problem at the patch line.
|
|
106
106
|
*
|
|
107
107
|
* @param token - the text after `@@` (no leading space).
|
package/src/apply-patch/types.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* (`PatchPlan`, `PlannedFile`, `PlannedMutation`),
|
|
9
9
|
* - the **result / error** types the renderer and the host adapter consume.
|
|
10
10
|
*
|
|
11
|
-
* The engine underneath is the existing hashline kernel: content-addressed
|
|
11
|
+
* The engine underneath is the existing hashline kernel: content-addressed
|
|
12
12
|
* anchors, served leases, MVCC snapshots, correlated multi-file transactions and
|
|
13
13
|
* the undo store. Nothing here re-declares those primitives; the plan IR only
|
|
14
14
|
* adds the addressing and lifecycle vocabulary apply_patch needs.
|
|
@@ -25,7 +25,7 @@ import type { TransactionRecoveryReport } from "../mutation-engine/types.js";
|
|
|
25
25
|
|
|
26
26
|
/** Patch AST (grammar layer output). */
|
|
27
27
|
|
|
28
|
-
/**
|
|
28
|
+
/** An anchor token: `HASH_LEN` characters of the hashline alphabet. */
|
|
29
29
|
export type Anchor = string;
|
|
30
30
|
|
|
31
31
|
/** A 1-based line number in the submitted patch text, used by every diagnostic. */
|
|
@@ -175,7 +175,7 @@ export interface FileBaseline {
|
|
|
175
175
|
readonly absolutePath: string;
|
|
176
176
|
/** LF-normalized text; `""` for an Add File target. */
|
|
177
177
|
readonly text: string;
|
|
178
|
-
/**
|
|
178
|
+
/** Anchors parallel to the file's lines; empty for an Add File target. */
|
|
179
179
|
readonly hashes: readonly string[];
|
|
180
180
|
/** Byte-order mark the file carried ("" when none). */
|
|
181
181
|
readonly bom: string;
|
package/prompts/read-snippet.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Read a file via `file`, `windows?`, `mode?`; default returns HASH│content rows for editing, `mode: "verbatim"` returns plain text when you will not edit; batch several ranges in one call via `windows`
|