@osovv/vv-opencode 1.2.7 → 1.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +29 -2
- package/dist/commands/patch-provider.js +2 -0
- package/dist/commands/patch-provider.js.map +1 -1
- package/dist/commands/plugin-list.js +8 -3
- package/dist/commands/plugin-list.js.map +1 -1
- package/dist/commands/plugin-toggle.js +8 -1
- package/dist/commands/plugin-toggle.js.map +1 -1
- package/dist/lib/plugin-toggle-config.d.ts +5 -1
- package/dist/lib/plugin-toggle-config.js +15 -10
- package/dist/lib/plugin-toggle-config.js.map +1 -1
- package/dist/lib/vvoc-config.d.ts +33 -1
- package/dist/lib/vvoc-config.js +49 -1
- package/dist/lib/vvoc-config.js.map +1 -1
- package/dist/plugins/hashline-edit/diff-summary.d.ts +7 -0
- package/dist/plugins/hashline-edit/diff-summary.js +79 -0
- package/dist/plugins/hashline-edit/diff-summary.js.map +1 -0
- package/dist/plugins/hashline-edit/edit-operation-primitives.js +22 -3
- package/dist/plugins/hashline-edit/edit-operation-primitives.js.map +1 -1
- package/dist/plugins/hashline-edit/index.js +315 -76
- package/dist/plugins/hashline-edit/index.js.map +1 -1
- package/dist/plugins/hashline-edit/normalize-edits.js +29 -25
- package/dist/plugins/hashline-edit/normalize-edits.js.map +1 -1
- package/dist/plugins/hashline-edit/replace-engine.d.ts +24 -0
- package/dist/plugins/hashline-edit/replace-engine.js +246 -0
- package/dist/plugins/hashline-edit/replace-engine.js.map +1 -0
- package/dist/plugins/hashline-edit/routing.d.ts +21 -0
- package/dist/plugins/hashline-edit/routing.js +113 -0
- package/dist/plugins/hashline-edit/routing.js.map +1 -0
- package/dist/plugins/hashline-edit/session-state.d.ts +23 -0
- package/dist/plugins/hashline-edit/session-state.js +70 -0
- package/dist/plugins/hashline-edit/session-state.js.map +1 -0
- package/dist/plugins/hashline-edit/str-replace-editor.d.ts +50 -0
- package/dist/plugins/hashline-edit/str-replace-editor.js +285 -0
- package/dist/plugins/hashline-edit/str-replace-editor.js.map +1 -0
- package/dist/plugins/hashline-edit/tool-description.d.ts +2 -1
- package/dist/plugins/hashline-edit/tool-description.js +31 -10
- package/dist/plugins/hashline-edit/tool-description.js.map +1 -1
- package/dist/plugins/hashline-edit/validation.d.ts +1 -0
- package/dist/plugins/hashline-edit/validation.js +13 -3
- package/dist/plugins/hashline-edit/validation.js.map +1 -1
- package/package.json +1 -1
- package/schemas/vvoc/v3.json +29 -2
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// FILE: src/plugins/hashline-edit/edit-operation-primitives.ts
|
|
2
|
-
// VERSION: 0.
|
|
2
|
+
// VERSION: 0.3.0
|
|
3
3
|
// START_MODULE_CONTRACT
|
|
4
4
|
// PURPOSE: Apply validated hashline edit operations literally to an in-memory file snapshot one mutation at a time.
|
|
5
|
-
// SCOPE: Single-line replace, range replace, anchored insert-before/after with exact-match echo trimming, and BOF/EOF insert helpers that preserve trailing-newline sentinels.
|
|
5
|
+
// SCOPE: Single-line replace, range replace, anchored insert-before/after with exact-match echo trimming, neighbor-duplication warnings for insertions, and BOF/EOF insert helpers that preserve trailing-newline sentinels.
|
|
6
6
|
// DEPENDS: [src/plugins/hashline-edit/edit-text-normalization.ts, src/plugins/hashline-edit/validation.ts]
|
|
7
7
|
// LINKS: [M-PLUGIN-HASHLINE-EDIT]
|
|
8
8
|
// ROLE: RUNTIME
|
|
@@ -20,13 +20,24 @@
|
|
|
20
20
|
// END_MODULE_MAP
|
|
21
21
|
//
|
|
22
22
|
// START_CHANGE_SUMMARY
|
|
23
|
-
// LAST_CHANGE: [v0.
|
|
23
|
+
// LAST_CHANGE: [v0.3.0 - Added visible warnings when append/prepend payloads duplicate the lines already adjacent to the anchor, applied literally.]
|
|
24
24
|
// END_CHANGE_SUMMARY
|
|
25
25
|
import { stripExactBoundaryEchoes, stripExactInsertBeforeEcho, stripExactInsertEcho, toNewLines, } from "./edit-text-normalization.js";
|
|
26
26
|
import { parseLineRef, validateLineRef } from "./validation.js";
|
|
27
27
|
function shouldValidate(options) {
|
|
28
28
|
return options?.skipValidation !== true;
|
|
29
29
|
}
|
|
30
|
+
function linesEqual(left, right) {
|
|
31
|
+
if (left.length !== right.length || left.length === 0) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
for (let i = 0; i < left.length; i += 1) {
|
|
35
|
+
if (left[i] !== right[i]) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
30
41
|
export function applySetLine(lines, anchor, newText, options) {
|
|
31
42
|
if (shouldValidate(options)) {
|
|
32
43
|
validateLineRef(lines, anchor);
|
|
@@ -74,6 +85,10 @@ export function applyInsertAfter(lines, anchor, text, options) {
|
|
|
74
85
|
if (trimmed.lines.length === 0) {
|
|
75
86
|
throw new Error(`append (anchored) requires non-empty text for ${anchor}`);
|
|
76
87
|
}
|
|
88
|
+
const following = lines.slice(line, line + trimmed.lines.length);
|
|
89
|
+
if (linesEqual(trimmed.lines, following)) {
|
|
90
|
+
options?.onWarning?.(`append at line ${line}: payload duplicates the ${trimmed.lines.length} line(s) already following the anchor; applying literally creates a duplicate block`);
|
|
91
|
+
}
|
|
77
92
|
result.splice(line, 0, ...trimmed.lines);
|
|
78
93
|
return result;
|
|
79
94
|
}
|
|
@@ -90,6 +105,10 @@ export function applyInsertBefore(lines, anchor, text, options) {
|
|
|
90
105
|
if (trimmed.lines.length === 0) {
|
|
91
106
|
throw new Error(`prepend (anchored) requires non-empty text for ${anchor}`);
|
|
92
107
|
}
|
|
108
|
+
const preceding = lines.slice(line - 1 - trimmed.lines.length, line - 1);
|
|
109
|
+
if (linesEqual(trimmed.lines, preceding)) {
|
|
110
|
+
options?.onWarning?.(`prepend at line ${line}: payload duplicates the ${trimmed.lines.length} line(s) already preceding the anchor; applying literally creates a duplicate block`);
|
|
111
|
+
}
|
|
93
112
|
result.splice(line - 1, 0, ...trimmed.lines);
|
|
94
113
|
return result;
|
|
95
114
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edit-operation-primitives.js","sourceRoot":"","sources":["../../../src/plugins/hashline-edit/edit-operation-primitives.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,iBAAiB;AACjB,wBAAwB;AACxB,sHAAsH;AACtH
|
|
1
|
+
{"version":3,"file":"edit-operation-primitives.js","sourceRoot":"","sources":["../../../src/plugins/hashline-edit/edit-operation-primitives.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,iBAAiB;AACjB,wBAAwB;AACxB,sHAAsH;AACtH,+NAA+N;AAC/N,6GAA6G;AAC7G,oCAAoC;AACpC,kBAAkB;AAClB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,iGAAiG;AACjG,uEAAuE;AACvE,2HAA2H;AAC3H,gGAAgG;AAChG,kGAAkG;AAClG,yGAAyG;AACzG,4EAA4E;AAC5E,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,uJAAuJ;AACvJ,qBAAqB;AAErB,OAAO,EACL,wBAAwB,EACxB,0BAA0B,EAC1B,oBAAoB,EACpB,UAAU,GACX,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAOhE,SAAS,cAAc,CAAC,OAA0B;IAChD,OAAO,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;AAC1C,CAAC;AAED,SAAS,UAAU,CAAC,IAAc,EAAE,KAAe;IACjD,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YACzB,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,KAAe,EACf,MAAc,EACd,OAAiC,EACjC,OAA0B;IAE1B,IAAI,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACjC,CAAC;IAED,MAAM,EAAE,IAAI,EAAE,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;IAC1B,MAAM,WAAW,GAAG,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAEhE,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,6DAA6D;YAC3D,iCAAiC,WAAW,CAAC,MAAM,2EAA2E,CACjI,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC,EAAE,GAAG,WAAW,CAAC,CAAC;IAC3C,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,KAAe,EACf,WAAmB,EACnB,SAAiB,EACjB,OAAiC,EACjC,OAA0B;IAE1B,IAAI,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,eAAe,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QACpC,eAAe,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IACpC,CAAC;IAED,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;IACtD,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;IAClD,IAAI,SAAS,GAAG,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,6BAA6B,SAAS,oCAAoC,OAAO,EAAE,CACpF,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;IAC1B,MAAM,UAAU,GAAG,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAG,wBAAwB,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;IAChF,MAAM,OAAO,GAAG,OAAO,CAAC,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IACjE,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,OAAO,EAAE,SAAS,EAAE,CAClB,uBAAuB,SAAS,IAAI,OAAO,aAAa,OAAO,6GAA6G,CAC7K,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,CAAC,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IACxE,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,KAAe,EACf,MAAc,EACd,IAAuB,EACvB,OAA0B;IAE1B,IAAI,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACjC,CAAC;IAED,MAAM,EAAE,IAAI,EAAE,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;IAC1B,MAAM,OAAO,GAAG,oBAAoB,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9E,IAAI,OAAO,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,SAAS,EAAE,CAClB,kBAAkB,IAAI,qDAAqD,CAC5E,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,iDAAiD,MAAM,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACjE,IAAI,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;QACzC,OAAO,EAAE,SAAS,EAAE,CAClB,kBAAkB,IAAI,4BAA4B,OAAO,CAAC,KAAK,CAAC,MAAM,qFAAqF,CAC5J,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IACzC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,KAAe,EACf,MAAc,EACd,IAAuB,EACvB,OAA0B;IAE1B,IAAI,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACjC,CAAC;IAED,MAAM,EAAE,IAAI,EAAE,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;IAC1B,MAAM,OAAO,GAAG,0BAA0B,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IACpF,IAAI,OAAO,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,SAAS,EAAE,CAClB,mBAAmB,IAAI,qDAAqD,CAC7E,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,kDAAkD,MAAM,EAAE,CAAC,CAAC;IAC9E,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC;IACzE,IAAI,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;QACzC,OAAO,EAAE,SAAS,EAAE,CAClB,mBAAmB,IAAI,4BAA4B,OAAO,CAAC,KAAK,CAAC,MAAM,qFAAqF,CAC7J,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7C,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAe,EAAE,IAAuB;IAClE,MAAM,UAAU,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;QAC1C,OAAO,CAAC,GAAG,UAAU,CAAC,CAAC;IACzB,CAAC;IACD,uEAAuE;IACvE,yEAAyE;IACzE,aAAa;IACb,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;QACvD,OAAO,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,UAAU,EAAE,EAAE,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,EAAE,GAAG,UAAU,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAe,EAAE,IAAuB;IACnE,MAAM,UAAU,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACrD,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;QAC1C,OAAO,CAAC,GAAG,UAAU,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,CAAC,GAAG,UAAU,EAAE,GAAG,KAAK,CAAC,CAAC;AACnC,CAAC"}
|
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
// FILE: src/plugins/hashline-edit/index.ts
|
|
2
|
-
// VERSION: 0.
|
|
2
|
+
// VERSION: 0.8.0
|
|
3
3
|
// START_MODULE_CONTRACT
|
|
4
|
-
// PURPOSE:
|
|
5
|
-
// SCOPE:
|
|
6
|
-
// DEPENDS: [@opencode-ai/plugin, src/lib/config-layers.ts, src/
|
|
4
|
+
// PURPOSE: Route per-model edit tooling: register hashline_edit, replace-profile edit, and dsh str_replace_editor; resolve the session edit mode from vvoc routing config; expose only the profile tool to each model; and transform read output with anchors for hashline sessions.
|
|
5
|
+
// SCOPE: Routing config loading, session model/file caches, chat.message tool-visibility mutation, tool.execute.before safety net, routed read transformation, hashline/replace/str_replace_editor execution, bounded post-edit diff feedback, and editMode telemetry metadata.
|
|
6
|
+
// DEPENDS: [@opencode-ai/plugin, node:fs/promises, node:path, src/lib/config-layers.ts, src/plugins/hashline-edit/diff-summary.ts, src/plugins/hashline-edit/edit-operations.ts, src/plugins/hashline-edit/file-text-canonicalization.ts, src/plugins/hashline-edit/hash-computation.ts, src/plugins/hashline-edit/normalize-edits.ts, src/plugins/hashline-edit/replace-engine.ts, src/plugins/hashline-edit/routing.ts, src/plugins/hashline-edit/session-state.ts, src/plugins/hashline-edit/str-replace-editor.ts, src/plugins/hashline-edit/tool-description.ts, src/plugins/hashline-edit/validation.ts]
|
|
7
7
|
// LINKS: [M-PLUGIN-HASHLINE-EDIT]
|
|
8
8
|
// ROLE: RUNTIME
|
|
9
9
|
// MAP_MODE: EXPORTS
|
|
10
10
|
// END_MODULE_CONTRACT
|
|
11
11
|
//
|
|
12
12
|
// START_MODULE_MAP
|
|
13
|
-
// HashlineEditPlugin - Registers
|
|
13
|
+
// HashlineEditPlugin - Registers routed edit tools (hashline_edit, edit, str_replace_editor), per-model tool visibility, and the routed read-output enhancer.
|
|
14
14
|
// END_MODULE_MAP
|
|
15
15
|
//
|
|
16
16
|
// START_CHANGE_SUMMARY
|
|
17
|
-
// LAST_CHANGE: [v0.
|
|
17
|
+
// LAST_CHANGE: [v0.8.0 - Added per-model edit-format routing with native replace and dsh str_replace_editor profiles, per-model tool visibility via chat.message, safety-net rejection, and editMode telemetry.]
|
|
18
18
|
// END_CHANGE_SUMMARY
|
|
19
19
|
import { tool } from "@opencode-ai/plugin";
|
|
20
|
-
import { resolve } from "node:path";
|
|
20
|
+
import { dirname, resolve } from "node:path";
|
|
21
|
+
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
22
|
+
import { findFirstChangedLine, summarizeEditDiff } from "./diff-summary.js";
|
|
21
23
|
import { applyHashlineEditsWithReport } from "./edit-operations.js";
|
|
22
24
|
import { canonicalizeFileText, restoreFileText } from "./file-text-canonicalization.js";
|
|
23
25
|
import { computeAnchorHash, computeLineHash } from "./hash-computation.js";
|
|
24
26
|
import { normalizeHashlineEdits } from "./normalize-edits.js";
|
|
25
|
-
import {
|
|
27
|
+
import { applyReplaceEdit, formatReplaceSuccess } from "./replace-engine.js";
|
|
28
|
+
import { parseHashlineEditPluginEntry, resolveEditMode, } from "./routing.js";
|
|
29
|
+
import { SessionFileCache, SessionModelCache } from "./session-state.js";
|
|
30
|
+
import { STR_REPLACE_EDITOR_DESCRIPTION, StrReplaceEditor, } from "./str-replace-editor.js";
|
|
31
|
+
import { HASHLINE_EDIT_DESCRIPTION, REPLACE_EDIT_DESCRIPTION } from "./tool-description.js";
|
|
26
32
|
import { HashlineMismatchError } from "./validation.js";
|
|
27
33
|
import { loadVvocConfig } from "../../lib/config-layers.js";
|
|
28
|
-
import { isVvocPluginEnabled } from "../../lib/plugin-toggle-config.js";
|
|
29
34
|
const z = tool.schema;
|
|
30
35
|
const CONTENT_OPEN_TAG = "<content>";
|
|
31
36
|
const CONTENT_CLOSE_TAG = "</content>";
|
|
@@ -34,65 +39,85 @@ const FILE_CLOSE_TAG = "</file>";
|
|
|
34
39
|
const OPENCODE_LINE_TRUNCATION_SUFFIX = "... (line truncated to 2000 chars)";
|
|
35
40
|
const COLON_READ_LINE_PATTERN = /^\s*(\d+): ?(.*)$/;
|
|
36
41
|
const PIPE_READ_LINE_PATTERN = /^\s*(\d+)\| ?(.*)$/;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return edits.every((edit) => (edit.op === "append" || edit.op === "prepend") && edit.pos === undefined);
|
|
42
|
+
// START_BLOCK_ROUTING_CONSTANTS
|
|
43
|
+
const EDIT_TYPE_TOOLS = ["hashline_edit", "edit", "str_replace_editor"];
|
|
44
|
+
function isEditTypeTool(toolName) {
|
|
45
|
+
return EDIT_TYPE_TOOLS.includes(toolName);
|
|
42
46
|
}
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
function visibleToolsForMode(mode) {
|
|
48
|
+
switch (mode) {
|
|
49
|
+
case "hashline":
|
|
50
|
+
return ["hashline_edit"];
|
|
51
|
+
case "replace":
|
|
52
|
+
return ["edit"];
|
|
53
|
+
case "str_replace_editor":
|
|
54
|
+
return ["str_replace_editor"];
|
|
55
|
+
case "passthrough":
|
|
56
|
+
return [];
|
|
51
57
|
}
|
|
52
|
-
return undefined;
|
|
53
58
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
prefix += 1;
|
|
63
|
-
}
|
|
64
|
-
let suffix = 0;
|
|
65
|
-
while (suffix < minLen - prefix &&
|
|
66
|
-
before[before.length - 1 - suffix] === after[after.length - 1 - suffix]) {
|
|
67
|
-
suffix += 1;
|
|
68
|
-
}
|
|
69
|
-
const removed = before.slice(prefix, before.length - suffix);
|
|
70
|
-
const added = after.slice(prefix, after.length - suffix);
|
|
71
|
-
if (removed.length === 0 && added.length === 0) {
|
|
72
|
-
return { additions: 0, deletions: 0, rendered: [] };
|
|
73
|
-
}
|
|
74
|
-
const rendered = [`@@ changed lines ${prefix + 1} @@`];
|
|
75
|
-
const contextStart = Math.max(0, prefix - DIFF_CONTEXT_LINES);
|
|
76
|
-
for (let i = contextStart; i < prefix; i += 1) {
|
|
77
|
-
rendered.push(` ${before[i]}`);
|
|
78
|
-
}
|
|
79
|
-
for (const line of removed) {
|
|
80
|
-
rendered.push(`- ${line}`);
|
|
81
|
-
}
|
|
82
|
-
for (const line of added) {
|
|
83
|
-
rendered.push(`+ ${line}`);
|
|
59
|
+
// START_BLOCK_FS_HELPERS
|
|
60
|
+
async function statSnapshot(filePath) {
|
|
61
|
+
try {
|
|
62
|
+
const info = await stat(filePath);
|
|
63
|
+
if (!info.isFile()) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
return { mtimeMs: info.mtimeMs, size: info.size };
|
|
84
67
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
for (let i = tailStart; i < contextEnd; i += 1) {
|
|
88
|
-
rendered.push(` ${before[i]}`);
|
|
68
|
+
catch {
|
|
69
|
+
return undefined;
|
|
89
70
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
71
|
+
}
|
|
72
|
+
function createNodeStrReplaceFs() {
|
|
73
|
+
return {
|
|
74
|
+
async exists(path) {
|
|
75
|
+
try {
|
|
76
|
+
await stat(path);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
async isDirectory(path) {
|
|
84
|
+
try {
|
|
85
|
+
return (await stat(path)).isDirectory();
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
async readText(path) {
|
|
92
|
+
return readFile(path, "utf8");
|
|
93
|
+
},
|
|
94
|
+
async writeText(path, content) {
|
|
95
|
+
await writeFile(path, content, "utf8");
|
|
96
|
+
},
|
|
97
|
+
async listDir(path) {
|
|
98
|
+
const entries = await readdir(path, { withFileTypes: true });
|
|
99
|
+
return entries.map((entry) => {
|
|
100
|
+
const type = entry.isDirectory() ? "directory" : "file";
|
|
101
|
+
return { name: entry.name, type };
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
async stat(path) {
|
|
105
|
+
try {
|
|
106
|
+
const info = await stat(path);
|
|
107
|
+
return { mtimeMs: info.mtimeMs, size: info.isFile() ? info.size : 0 };
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
// END_BLOCK_FS_HELPERS
|
|
116
|
+
function canCreateFromMissingFile(edits) {
|
|
117
|
+
if (edits.length === 0) {
|
|
118
|
+
return false;
|
|
94
119
|
}
|
|
95
|
-
return
|
|
120
|
+
return edits.every((edit) => (edit.op === "append" || edit.op === "prepend") && edit.pos === undefined);
|
|
96
121
|
}
|
|
97
122
|
function publishSuccessMetadata(args) {
|
|
98
123
|
args.context.metadata({
|
|
@@ -104,6 +129,9 @@ function publishSuccessMetadata(args) {
|
|
|
104
129
|
noopEdits: args.noopEdits,
|
|
105
130
|
deduplicatedEdits: args.deduplicatedEdits,
|
|
106
131
|
firstChangedLine: findFirstChangedLine(args.beforeContent, args.afterContent),
|
|
132
|
+
editMode: args.telemetry.editMode,
|
|
133
|
+
providerID: args.telemetry.providerID,
|
|
134
|
+
modelID: args.telemetry.modelID,
|
|
107
135
|
filediff: {
|
|
108
136
|
file: args.filePath,
|
|
109
137
|
path: args.filePath,
|
|
@@ -298,7 +326,8 @@ function transformReadOutput(output, sourceLines) {
|
|
|
298
326
|
const result = formatReadLines(parsedLines, lines, sourceMatchesVisibleRows(sourceLines, parsedLines) ? sourceLines : undefined);
|
|
299
327
|
return result.join("\n");
|
|
300
328
|
}
|
|
301
|
-
|
|
329
|
+
// START_BLOCK_HASHLINE_EXECUTE
|
|
330
|
+
async function executeHashlineEdit(args, context, telemetry) {
|
|
302
331
|
try {
|
|
303
332
|
const { filePath, rename, delete: deleteMode } = args;
|
|
304
333
|
if (deleteMode && rename) {
|
|
@@ -357,6 +386,7 @@ async function executeHashlineEdit(args, context) {
|
|
|
357
386
|
afterContent: canonicalNewContent,
|
|
358
387
|
noopEdits: applyResult.noopEdits,
|
|
359
388
|
deduplicatedEdits: applyResult.deduplicatedEdits,
|
|
389
|
+
telemetry,
|
|
360
390
|
});
|
|
361
391
|
const diffSummary = summarizeEditDiff(oldEnvelope.content, canonicalNewContent);
|
|
362
392
|
const firstChangedLine = findFirstChangedLine(oldEnvelope.content, canonicalNewContent);
|
|
@@ -382,13 +412,173 @@ async function executeHashlineEdit(args, context) {
|
|
|
382
412
|
return `Error: ${message}`;
|
|
383
413
|
}
|
|
384
414
|
}
|
|
415
|
+
// END_BLOCK_HASHLINE_EXECUTE
|
|
416
|
+
// START_BLOCK_REPLACE_EXECUTE
|
|
417
|
+
async function executeReplaceEdit(args, context, sessionID, fileCache, telemetry) {
|
|
418
|
+
const filePath = args.filePath ?? args.file_path;
|
|
419
|
+
if (!filePath || filePath.trim().length === 0) {
|
|
420
|
+
return "Error: filePath is required (filePath or file_path)";
|
|
421
|
+
}
|
|
422
|
+
const oldString = args.oldString ?? args.old_string ?? "";
|
|
423
|
+
const newString = args.newString ?? args.new_string ?? "";
|
|
424
|
+
const replaceAll = args.replaceAll ?? args.replace_all ?? false;
|
|
425
|
+
try {
|
|
426
|
+
const fileSnapshot = await statSnapshot(filePath);
|
|
427
|
+
if (fileSnapshot === undefined && oldString === "") {
|
|
428
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
429
|
+
await writeFile(filePath, newString, "utf8");
|
|
430
|
+
const created = await statSnapshot(filePath);
|
|
431
|
+
if (created) {
|
|
432
|
+
fileCache.record(sessionID, filePath, created);
|
|
433
|
+
}
|
|
434
|
+
publishSuccessMetadata({
|
|
435
|
+
context,
|
|
436
|
+
filePath,
|
|
437
|
+
beforeContent: "",
|
|
438
|
+
afterContent: newString,
|
|
439
|
+
noopEdits: 0,
|
|
440
|
+
deduplicatedEdits: 0,
|
|
441
|
+
telemetry,
|
|
442
|
+
});
|
|
443
|
+
return `Created ${filePath}\n${formatReplaceSuccess(filePath, "", {
|
|
444
|
+
content: newString,
|
|
445
|
+
layer: "exact",
|
|
446
|
+
replacements: 1,
|
|
447
|
+
warnings: [],
|
|
448
|
+
})}`;
|
|
449
|
+
}
|
|
450
|
+
if (fileSnapshot === undefined) {
|
|
451
|
+
return `Error: File not found: ${filePath}`;
|
|
452
|
+
}
|
|
453
|
+
const verdict = fileCache.check(sessionID, filePath, fileSnapshot);
|
|
454
|
+
if (verdict === "unread") {
|
|
455
|
+
return `Error: ${filePath} has not been read in this session. Use the Read tool to load the file first, then retry the edit.`;
|
|
456
|
+
}
|
|
457
|
+
if (verdict === "drifted") {
|
|
458
|
+
return `Error: ${filePath} changed since it was last read. Re-read the file and retry the edit against the current content.`;
|
|
459
|
+
}
|
|
460
|
+
const rawContent = await readFile(filePath, "utf8");
|
|
461
|
+
const envelope = canonicalizeFileText(rawContent);
|
|
462
|
+
const result = applyReplaceEdit(envelope.content, { oldString, newString, replaceAll });
|
|
463
|
+
if (!result.ok) {
|
|
464
|
+
return `Error: ${result.error}`;
|
|
465
|
+
}
|
|
466
|
+
await writeFile(filePath, restoreFileText(result.content, envelope), "utf8");
|
|
467
|
+
const updated = await statSnapshot(filePath);
|
|
468
|
+
if (updated) {
|
|
469
|
+
fileCache.record(sessionID, filePath, updated);
|
|
470
|
+
}
|
|
471
|
+
publishSuccessMetadata({
|
|
472
|
+
context,
|
|
473
|
+
filePath,
|
|
474
|
+
beforeContent: envelope.content,
|
|
475
|
+
afterContent: result.content,
|
|
476
|
+
noopEdits: 0,
|
|
477
|
+
deduplicatedEdits: 0,
|
|
478
|
+
telemetry,
|
|
479
|
+
});
|
|
480
|
+
return formatReplaceSuccess(filePath, envelope.content, result);
|
|
481
|
+
}
|
|
482
|
+
catch (error) {
|
|
483
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
484
|
+
return `Error: ${message}`;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
// END_BLOCK_REPLACE_EXECUTE
|
|
488
|
+
// START_BLOCK_STR_REPLACE_EXECUTE
|
|
489
|
+
async function executeStrReplaceEditor(args, context, sessionID, fileCache, telemetry) {
|
|
490
|
+
const editor = new StrReplaceEditor({
|
|
491
|
+
fs: createNodeStrReplaceFs(),
|
|
492
|
+
onViewed: (path, snapshot) => fileCache.record(sessionID, path, snapshot),
|
|
493
|
+
checkFreshness: (path, current) => fileCache.check(sessionID, path, current),
|
|
494
|
+
});
|
|
495
|
+
const result = await editor.execute(args);
|
|
496
|
+
if (!result.ok) {
|
|
497
|
+
return `Error: ${result.error}`;
|
|
498
|
+
}
|
|
499
|
+
if (args.command !== "view") {
|
|
500
|
+
context.metadata({
|
|
501
|
+
title: args.path,
|
|
502
|
+
metadata: {
|
|
503
|
+
filePath: args.path,
|
|
504
|
+
path: args.path,
|
|
505
|
+
file: args.path,
|
|
506
|
+
editMode: telemetry.editMode,
|
|
507
|
+
providerID: telemetry.providerID,
|
|
508
|
+
modelID: telemetry.modelID,
|
|
509
|
+
},
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
return result.output;
|
|
513
|
+
}
|
|
514
|
+
// END_BLOCK_STR_REPLACE_EXECUTE
|
|
515
|
+
// START_BLOCK_PLUGIN
|
|
385
516
|
export const HashlineEditPlugin = async ({ directory }) => {
|
|
386
517
|
const vvoc = await loadVvocConfig({ cwd: directory });
|
|
387
|
-
|
|
518
|
+
const settings = parseHashlineEditPluginEntry(vvoc.config.plugins?.["hashline-edit"]);
|
|
519
|
+
if (!settings.enabled)
|
|
388
520
|
return {};
|
|
521
|
+
const routing = settings.routing;
|
|
522
|
+
const modelCache = new SessionModelCache();
|
|
523
|
+
const fileCache = new SessionFileCache();
|
|
524
|
+
const resolveSessionMode = (sessionID) => resolveEditMode(routing, modelCache.get(sessionID));
|
|
525
|
+
const telemetryFor = (sessionID) => {
|
|
526
|
+
const model = modelCache.get(sessionID);
|
|
527
|
+
return {
|
|
528
|
+
editMode: resolveEditMode(routing, model),
|
|
529
|
+
providerID: model?.providerID,
|
|
530
|
+
modelID: model?.modelID,
|
|
531
|
+
};
|
|
532
|
+
};
|
|
389
533
|
return {
|
|
534
|
+
"chat.message": async (input, output) => {
|
|
535
|
+
const model = output.message?.model;
|
|
536
|
+
if (input.sessionID &&
|
|
537
|
+
model &&
|
|
538
|
+
typeof model.providerID === "string" &&
|
|
539
|
+
typeof model.modelID === "string") {
|
|
540
|
+
modelCache.set(input.sessionID, { providerID: model.providerID, modelID: model.modelID });
|
|
541
|
+
}
|
|
542
|
+
const mode = resolveEditMode(routing, model);
|
|
543
|
+
const visible = new Set(visibleToolsForMode(mode));
|
|
544
|
+
const message = output.message;
|
|
545
|
+
if (!message)
|
|
546
|
+
return;
|
|
547
|
+
const toolsMap = message.tools ?? {};
|
|
548
|
+
for (const toolName of EDIT_TYPE_TOOLS) {
|
|
549
|
+
if (!visible.has(toolName)) {
|
|
550
|
+
toolsMap[toolName] = false;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
message.tools = toolsMap;
|
|
554
|
+
},
|
|
555
|
+
"tool.execute.before": async (input) => {
|
|
556
|
+
if (!isEditTypeTool(input.tool)) {
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
const mode = resolveSessionMode(input.sessionID);
|
|
560
|
+
const visible = visibleToolsForMode(mode);
|
|
561
|
+
if (visible.includes(input.tool)) {
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
const preferred = visible[0] ?? "the host-provided edit tool (for example apply_patch)";
|
|
565
|
+
throw new Error(`${input.tool} is not available for this session's model (edit mode: ${mode}). Use ${preferred} instead.`);
|
|
566
|
+
},
|
|
390
567
|
"tool.execute.after": async (input, output) => {
|
|
391
|
-
if (!isReadTool(input.tool)
|
|
568
|
+
if (!isReadTool(input.tool)) {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
const filePath = readArgFilePath(input.args);
|
|
572
|
+
if (filePath) {
|
|
573
|
+
const snapshot = await statSnapshot(filePath);
|
|
574
|
+
if (snapshot) {
|
|
575
|
+
fileCache.record(input.sessionID, filePath, snapshot);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (typeof output.output !== "string") {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
if (resolveSessionMode(input.sessionID) !== "hashline") {
|
|
392
582
|
return;
|
|
393
583
|
}
|
|
394
584
|
if (!isHashlineEligibleReadOutput(output.output)) {
|
|
@@ -397,35 +587,84 @@ export const HashlineEditPlugin = async ({ directory }) => {
|
|
|
397
587
|
output.output = transformReadOutput(output.output, await readSourceLines(input.args));
|
|
398
588
|
},
|
|
399
589
|
tool: {
|
|
400
|
-
|
|
590
|
+
hashline_edit: tool({
|
|
401
591
|
description: HASHLINE_EDIT_DESCRIPTION,
|
|
402
592
|
args: {
|
|
403
593
|
filePath: z.string().describe("Absolute path to the file to edit"),
|
|
404
594
|
delete: z.boolean().optional().describe("Delete the file instead of editing it"),
|
|
405
595
|
rename: z.string().optional().describe("Rename the file after edits are applied"),
|
|
406
596
|
edits: z
|
|
407
|
-
.array(z
|
|
408
|
-
.object({
|
|
597
|
+
.array(z.object({
|
|
409
598
|
op: z.enum(["replace", "replace_range", "append", "prepend"]),
|
|
410
|
-
pos: z
|
|
599
|
+
pos: z
|
|
600
|
+
.string()
|
|
601
|
+
.optional()
|
|
602
|
+
.describe("Primary anchor in LINE#HASH#ANCHOR three-part format"),
|
|
411
603
|
end: z
|
|
412
604
|
.string()
|
|
413
605
|
.optional()
|
|
414
|
-
.describe("
|
|
606
|
+
.describe("Optional range end anchor in LINE#HASH#ANCHOR format. With end, replace covers the inclusive range pos..end; required when op is replace_range."),
|
|
415
607
|
lines: z
|
|
416
608
|
.union([z.array(z.string()), z.string(), z.null()])
|
|
417
609
|
.describe("Replacement or inserted lines as plain text content"),
|
|
418
|
-
})
|
|
419
|
-
.refine((obj) => !(obj.op === "replace" && Array.isArray(obj.lines) && obj.lines.length > 1), {
|
|
420
|
-
message: "replace accepts at most 1 replacement line. " +
|
|
421
|
-
"Use array with 1 element, a string, or null. " +
|
|
422
|
-
"For multi-line replacement, use replace_range with pos + end.",
|
|
423
610
|
}))
|
|
424
611
|
.describe("Hash-anchored edit operations to apply to the file"),
|
|
425
612
|
},
|
|
426
|
-
execute: (args, context) => executeHashlineEdit(args, context),
|
|
613
|
+
execute: (args, context) => executeHashlineEdit(args, context, telemetryFor(context.sessionID)),
|
|
614
|
+
}),
|
|
615
|
+
edit: tool({
|
|
616
|
+
description: REPLACE_EDIT_DESCRIPTION,
|
|
617
|
+
args: {
|
|
618
|
+
filePath: z.string().optional().describe("Absolute path to the file to modify"),
|
|
619
|
+
file_path: z
|
|
620
|
+
.string()
|
|
621
|
+
.optional()
|
|
622
|
+
.describe("Alias of filePath (snake_case form used by some models)"),
|
|
623
|
+
oldString: z.string().optional().describe("Exact text to replace"),
|
|
624
|
+
old_string: z.string().optional().describe("Alias of oldString"),
|
|
625
|
+
newString: z.string().optional().describe("Replacement text"),
|
|
626
|
+
new_string: z.string().optional().describe("Alias of newString"),
|
|
627
|
+
replaceAll: z
|
|
628
|
+
.boolean()
|
|
629
|
+
.optional()
|
|
630
|
+
.describe("Replace every occurrence instead of requiring a unique match"),
|
|
631
|
+
replace_all: z.boolean().optional().describe("Alias of replaceAll"),
|
|
632
|
+
},
|
|
633
|
+
execute: (args, context) => executeReplaceEdit(args, context, context.sessionID, fileCache, telemetryFor(context.sessionID)),
|
|
634
|
+
}),
|
|
635
|
+
str_replace_editor: tool({
|
|
636
|
+
description: STR_REPLACE_EDITOR_DESCRIPTION,
|
|
637
|
+
args: {
|
|
638
|
+
command: z
|
|
639
|
+
.enum(["view", "create", "str_replace", "insert"])
|
|
640
|
+
.describe("The command to run: view, create, str_replace, or insert"),
|
|
641
|
+
path: z.string().describe("Absolute path to file or directory"),
|
|
642
|
+
file_text: z
|
|
643
|
+
.string()
|
|
644
|
+
.optional()
|
|
645
|
+
.describe("Required parameter of create command with the content of the new file"),
|
|
646
|
+
old_str: z
|
|
647
|
+
.string()
|
|
648
|
+
.optional()
|
|
649
|
+
.describe("Required parameter of str_replace command: the exact text to replace"),
|
|
650
|
+
new_str: z
|
|
651
|
+
.string()
|
|
652
|
+
.optional()
|
|
653
|
+
.describe("Optional parameter of str_replace command with the replacement text; required for insert"),
|
|
654
|
+
insert_line: z
|
|
655
|
+
.number()
|
|
656
|
+
.int()
|
|
657
|
+
.optional()
|
|
658
|
+
.describe("Required parameter of insert command; new_str is inserted AFTER this line"),
|
|
659
|
+
view_range: z
|
|
660
|
+
.array(z.number().int())
|
|
661
|
+
.optional()
|
|
662
|
+
.describe("Optional [start, end] line range for view; end may be -1 for end of file"),
|
|
663
|
+
},
|
|
664
|
+
execute: (args, context) => executeStrReplaceEditor(args, context, context.sessionID, fileCache, telemetryFor(context.sessionID)),
|
|
427
665
|
}),
|
|
428
666
|
},
|
|
429
667
|
};
|
|
430
668
|
};
|
|
669
|
+
// END_BLOCK_PLUGIN
|
|
431
670
|
//# sourceMappingURL=index.js.map
|