@osovv/vv-opencode 1.2.8 → 1.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +36 -3
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +5 -4
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/opencode.js +3 -0
  7. package/dist/lib/opencode.js.map +1 -1
  8. package/dist/lib/plugin-toggle-config.d.ts +40 -2
  9. package/dist/lib/plugin-toggle-config.js +119 -3
  10. package/dist/lib/plugin-toggle-config.js.map +1 -1
  11. package/dist/lib/vvoc-config.d.ts +47 -1
  12. package/dist/lib/vvoc-config.js +24 -11
  13. package/dist/lib/vvoc-config.js.map +1 -1
  14. package/dist/plugins/hashline-edit/routing.js +6 -14
  15. package/dist/plugins/hashline-edit/routing.js.map +1 -1
  16. package/dist/plugins/system-context-injection/index.js +10 -1
  17. package/dist/plugins/system-context-injection/index.js.map +1 -1
  18. package/dist/plugins/tool-history-compaction/config.d.ts +28 -0
  19. package/dist/plugins/tool-history-compaction/config.js +133 -0
  20. package/dist/plugins/tool-history-compaction/config.js.map +1 -0
  21. package/dist/plugins/tool-history-compaction/index.d.ts +2 -0
  22. package/dist/plugins/tool-history-compaction/index.js +35 -0
  23. package/dist/plugins/tool-history-compaction/index.js.map +1 -0
  24. package/dist/plugins/tool-history-compaction/prune.d.ts +22 -0
  25. package/dist/plugins/tool-history-compaction/prune.js +70 -0
  26. package/dist/plugins/tool-history-compaction/prune.js.map +1 -0
  27. package/dist/plugins/tool-history-compaction/read-slim.d.ts +27 -0
  28. package/dist/plugins/tool-history-compaction/read-slim.js +91 -0
  29. package/dist/plugins/tool-history-compaction/read-slim.js.map +1 -0
  30. package/dist/plugins/tool-history-compaction/retention.d.ts +10 -0
  31. package/dist/plugins/tool-history-compaction/retention.js +45 -0
  32. package/dist/plugins/tool-history-compaction/retention.js.map +1 -0
  33. package/dist/plugins/tool-history-compaction/transform.d.ts +15 -0
  34. package/dist/plugins/tool-history-compaction/transform.js +93 -0
  35. package/dist/plugins/tool-history-compaction/transform.js.map +1 -0
  36. package/package.json +5 -1
  37. package/schemas/vvoc/v3.json +15 -1
  38. package/templates/agents/vv-controller.md +1 -0
@@ -0,0 +1,70 @@
1
+ // FILE: src/plugins/tool-history-compaction/prune.ts
2
+ // VERSION: 0.1.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Deterministic head/marker/tail pruning of over-budget tool outputs with code-point-safe slicing, min-savings guard, and marker-based idempotence.
5
+ // SCOPE: Code-point counting and slicing, the fixed prune marker, the prune decision function, and already-compacted detection.
6
+ // DEPENDS: [src/plugins/tool-history-compaction/config.ts]
7
+ // LINKS: [M-PLUGIN-TOOL-HISTORY-COMPACTION]
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // PRUNE_MARKER - Fixed marker substituted for every pruned output middle.
14
+ // countCodePoints - Unicode code-point count without splitting surrogate pairs.
15
+ // codePointSlice - Slice text by code points.
16
+ // pruneOutput - Return the pruned form when eligible, otherwise null.
17
+ // alreadyCompacted - Whether text already carries the prune marker.
18
+ // PruneResult - Pruned output plus measured savings.
19
+ // END_MODULE_MAP
20
+ //
21
+ // START_CHANGE_SUMMARY
22
+ // LAST_CHANGE: [v0.1.0 - Established the DSH-style head/marker/tail prune engine.]
23
+ // END_CHANGE_SUMMARY
24
+ // START_BLOCK_CONSTANTS
25
+ /** Fixed marker substituted for every pruned output middle. */
26
+ export const PRUNE_MARKER = "[... tool output pruned ...]";
27
+ // END_BLOCK_CONSTANTS
28
+ // START_BLOCK_MEASURE
29
+ /** Count Unicode code points without splitting surrogate pairs. */
30
+ export function countCodePoints(text) {
31
+ return Array.from(text).length;
32
+ }
33
+ /** Slice text by code points, never splitting a surrogate pair. */
34
+ export function codePointSlice(text, start, end) {
35
+ return Array.from(text).slice(start, end).join("");
36
+ }
37
+ // END_BLOCK_MEASURE
38
+ // START_BLOCK_DETECT
39
+ /** Whether text already carries the prune marker (previous compaction pass). */
40
+ export function alreadyCompacted(text) {
41
+ return typeof text === "string" && text.includes(PRUNE_MARKER);
42
+ }
43
+ /**
44
+ * Prune an over-budget output to head + marker + tail.
45
+ * Returns null when the output is ineligible: non-string, pruning disabled,
46
+ * under threshold, already compacted, or savings below minSavingsChars.
47
+ * @param text - tool output text.
48
+ * @param config - resolved compaction config.
49
+ * @returns pruned form or null.
50
+ */
51
+ export function pruneOutput(text, config) {
52
+ if (typeof text !== "string" || text.length === 0)
53
+ return null;
54
+ if (config.outputMaxChars <= 0)
55
+ return null;
56
+ if (alreadyCompacted(text))
57
+ return null;
58
+ const length = countCodePoints(text);
59
+ if (length <= config.outputMaxChars)
60
+ return null;
61
+ const head = codePointSlice(text, 0, config.headChars);
62
+ const tail = codePointSlice(text, length - config.tailChars, length);
63
+ const output = head + PRUNE_MARKER + tail;
64
+ const savings = length - countCodePoints(output);
65
+ if (savings < config.minSavingsChars)
66
+ return null;
67
+ return { output, savings };
68
+ }
69
+ // END_BLOCK_PRUNE
70
+ //# sourceMappingURL=prune.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prune.js","sourceRoot":"","sources":["../../../src/plugins/tool-history-compaction/prune.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,iBAAiB;AACjB,wBAAwB;AACxB,+JAA+J;AAC/J,kIAAkI;AAClI,6DAA6D;AAC7D,8CAA8C;AAC9C,kBAAkB;AAClB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,4EAA4E;AAC5E,kFAAkF;AAClF,gDAAgD;AAChD,wEAAwE;AACxE,sEAAsE;AACtE,uDAAuD;AACvD,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,qFAAqF;AACrF,qBAAqB;AAIrB,wBAAwB;AACxB,+DAA+D;AAC/D,MAAM,CAAC,MAAM,YAAY,GAAG,8BAA8B,CAAC;AAC3D,sBAAsB;AAEtB,sBAAsB;AACtB,mEAAmE;AACnE,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;AACjC,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,KAAa,EAAE,GAAW;IACrE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACrD,CAAC;AACD,oBAAoB;AAEpB,qBAAqB;AACrB,gFAAgF;AAChF,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;AACjE,CAAC;AASD;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,MAAmC;IAC3E,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/D,IAAI,MAAM,CAAC,cAAc,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,IAAI,gBAAgB,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAExC,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,MAAM,IAAI,MAAM,CAAC,cAAc;QAAE,OAAO,IAAI,CAAC;IAEjD,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IACvD,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACrE,MAAM,MAAM,GAAG,IAAI,GAAG,YAAY,GAAG,IAAI,CAAC;IAC1C,MAAM,OAAO,GAAG,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IACjD,IAAI,OAAO,GAAG,MAAM,CAAC,eAAe;QAAE,OAAO,IAAI,CAAC;IAElD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC7B,CAAC;AACD,kBAAkB"}
@@ -0,0 +1,27 @@
1
+ import type { ToolHistoryCompactionConfig } from "./config.js";
2
+ export interface ReadInputLike {
3
+ filePath?: unknown;
4
+ path?: unknown;
5
+ file?: unknown;
6
+ }
7
+ /** Recover the file path from read input aliases; unknown values yield undefined. */
8
+ export declare function fileFromReadInput(input: ReadInputLike | undefined): string | undefined;
9
+ /**
10
+ * Recover the covered line range ("first-last") from a line-numbered output.
11
+ * Returns undefined when the output is not deterministically line-numbered.
12
+ */
13
+ export declare function rangeFromReadOutput(output: string | undefined): string | undefined;
14
+ export interface SlimReadResult {
15
+ output: string;
16
+ savings: number;
17
+ }
18
+ /**
19
+ * Collapse an old read output to [Read <file>, lines X-Y] when the file is
20
+ * recoverable and the savings clear the min-savings guard. Returns null when
21
+ * not applicable; the caller falls back to output pruning (never fabricates).
22
+ * @param input - part.state.input.
23
+ * @param output - part.state.output.
24
+ * @param config - resolved compaction config.
25
+ * @returns slim form or null.
26
+ */
27
+ export declare function slimReadOutput(input: unknown, output: unknown, config: ToolHistoryCompactionConfig): SlimReadResult | null;
@@ -0,0 +1,91 @@
1
+ // FILE: src/plugins/tool-history-compaction/read-slim.ts
2
+ // VERSION: 0.1.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Collapse old read tool outputs to a compact [Read <file>, lines X-Y] header, recovering the file from the input and the line range from the output when deterministically available.
5
+ // SCOPE: Read input file extraction, line-numbered output range recovery, savings-gated slim decision, and idempotence detection.
6
+ // DEPENDS: [src/plugins/tool-history-compaction/config.ts]
7
+ // LINKS: [M-PLUGIN-TOOL-HISTORY-COMPACTION]
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // fileFromReadInput - Recover the file path from read input aliases.
14
+ // ReadInputLike - Read input object with filePath/path/file aliases.
15
+ // rangeFromReadOutput - Recover the covered line range from a line-numbered output.
16
+ // slimReadOutput - Return the [Read <file>] form when eligible, otherwise null (fallback to pruning).
17
+ // SlimReadResult - Slim form plus measured savings.
18
+ // END_MODULE_MAP
19
+ //
20
+ // START_CHANGE_SUMMARY
21
+ // LAST_CHANGE: [v0.1.0 - Established old-read collapse with fail-closed fallback.]
22
+ // END_CHANGE_SUMMARY
23
+ import { countCodePoints } from "./prune.js";
24
+ // START_BLOCK_RANGE
25
+ // Matches a line-numbered output line: OpenCode "N| content" / "N: content"
26
+ // and hashline anchored "N#ID#ANCHOR|content". Never fabricates a number.
27
+ const LINE_NUMBER_PREFIX = /^\s*(\d+)(?:#[^|:]*)?(?:\||:)/;
28
+ /** Recover the file path from read input aliases; unknown values yield undefined. */
29
+ export function fileFromReadInput(input) {
30
+ if (!input || typeof input !== "object")
31
+ return undefined;
32
+ for (const key of ["filePath", "path", "file"]) {
33
+ const value = input[key];
34
+ if (typeof value === "string" && value.length > 0)
35
+ return value;
36
+ }
37
+ return undefined;
38
+ }
39
+ // END_BLOCK_FILE
40
+ // START_BLOCK_RANGE_FN
41
+ /**
42
+ * Recover the covered line range ("first-last") from a line-numbered output.
43
+ * Returns undefined when the output is not deterministically line-numbered.
44
+ */
45
+ export function rangeFromReadOutput(output) {
46
+ if (typeof output !== "string" || output.length === 0)
47
+ return undefined;
48
+ let first;
49
+ let last;
50
+ for (const line of output.split("\n")) {
51
+ const match = LINE_NUMBER_PREFIX.exec(line);
52
+ if (match) {
53
+ const number = Number.parseInt(match[1] ?? "0", 10);
54
+ if (first === undefined)
55
+ first = number;
56
+ last = number;
57
+ }
58
+ }
59
+ if (first === undefined || last === undefined || first > last)
60
+ return undefined;
61
+ return `${first}-${last}`;
62
+ }
63
+ function alreadySlimmed(output) {
64
+ return output.startsWith("[Read ");
65
+ }
66
+ /**
67
+ * Collapse an old read output to [Read <file>, lines X-Y] when the file is
68
+ * recoverable and the savings clear the min-savings guard. Returns null when
69
+ * not applicable; the caller falls back to output pruning (never fabricates).
70
+ * @param input - part.state.input.
71
+ * @param output - part.state.output.
72
+ * @param config - resolved compaction config.
73
+ * @returns slim form or null.
74
+ */
75
+ export function slimReadOutput(input, output, config) {
76
+ if (typeof output !== "string" || output.length === 0)
77
+ return null;
78
+ if (alreadySlimmed(output))
79
+ return null;
80
+ const file = fileFromReadInput(input);
81
+ if (file === undefined)
82
+ return null;
83
+ const range = rangeFromReadOutput(output);
84
+ const slim = range === undefined ? `[Read ${file}]` : `[Read ${file}, lines ${range}]`;
85
+ const savings = countCodePoints(output) - countCodePoints(slim);
86
+ if (savings < config.minSavingsChars)
87
+ return null;
88
+ return { output: slim, savings };
89
+ }
90
+ // END_BLOCK_SLIM
91
+ //# sourceMappingURL=read-slim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-slim.js","sourceRoot":"","sources":["../../../src/plugins/tool-history-compaction/read-slim.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /** Knowledge-tool substrings: results that stay relevant for the whole session. */
2
+ export declare const DEFAULT_RETAIN_TOOLS: readonly string[];
3
+ export type RetainTools = readonly string[];
4
+ /**
5
+ * Whether a tool name matches any retain substring, case-insensitively.
6
+ * @param tool - tool name from a message part.
7
+ * @param retainTools - configured retain substrings.
8
+ * @returns true when the tool output must never be compacted.
9
+ */
10
+ export declare function isRetainedTool(tool: string | undefined, retainTools: RetainTools): boolean;
@@ -0,0 +1,45 @@
1
+ // FILE: src/plugins/tool-history-compaction/retention.ts
2
+ // VERSION: 0.1.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Classify tool outputs into knowledge (retained) versus ephemeral (compaction-eligible) by case-insensitive substring matching on the tool name.
5
+ // SCOPE: The pure isRetainedTool predicate; the default retention list is re-exported from the shared lib table.
6
+ // DEPENDS: [src/lib/plugin-toggle-config.ts]
7
+ // LINKS: [M-PLUGIN-TOOL-HISTORY-COMPACTION]
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // DEFAULT_RETAIN_TOOLS - Default knowledge-tool substrings (re-exported from the shared lib table).
14
+ // RetainTools - Readonly array of case-insensitive tool-name substrings.
15
+ // isRetainedTool - Whether a tool name matches any retain substring.
16
+ // END_MODULE_MAP
17
+ //
18
+ // START_CHANGE_SUMMARY
19
+ // LAST_CHANGE: [v0.1.0 - Established the knowledge-versus-ephemeral retention classification; defaults owned by the shared lib table.]
20
+ // END_CHANGE_SUMMARY
21
+ import { DEFAULT_TOOL_HISTORY_COMPACTION_RETAIN_TOOLS } from "../../lib/plugin-toggle-config.js";
22
+ // START_BLOCK_DEFAULTS
23
+ /** Knowledge-tool substrings: results that stay relevant for the whole session. */
24
+ export const DEFAULT_RETAIN_TOOLS = DEFAULT_TOOL_HISTORY_COMPACTION_RETAIN_TOOLS;
25
+ // END_BLOCK_DEFAULTS
26
+ // START_BLOCK_MATCH
27
+ /**
28
+ * Whether a tool name matches any retain substring, case-insensitively.
29
+ * @param tool - tool name from a message part.
30
+ * @param retainTools - configured retain substrings.
31
+ * @returns true when the tool output must never be compacted.
32
+ */
33
+ export function isRetainedTool(tool, retainTools) {
34
+ if (typeof tool !== "string" || tool.length === 0)
35
+ return false;
36
+ const lower = tool.toLowerCase();
37
+ for (const pattern of retainTools) {
38
+ if (pattern.length > 0 && lower.includes(pattern.toLowerCase())) {
39
+ return true;
40
+ }
41
+ }
42
+ return false;
43
+ }
44
+ // END_BLOCK_MATCH
45
+ //# sourceMappingURL=retention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.js","sourceRoot":"","sources":["../../../src/plugins/tool-history-compaction/retention.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,iBAAiB;AACjB,wBAAwB;AACxB,6JAA6J;AAC7J,mHAAmH;AACnH,+CAA+C;AAC/C,8CAA8C;AAC9C,kBAAkB;AAClB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,sGAAsG;AACtG,2EAA2E;AAC3E,uEAAuE;AACvE,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,yIAAyI;AACzI,qBAAqB;AACrB,OAAO,EAAE,4CAA4C,EAAE,MAAM,mCAAmC,CAAC;AAEjG,uBAAuB;AACvB,mFAAmF;AACnF,MAAM,CAAC,MAAM,oBAAoB,GAAsB,4CAA4C,CAAC;AAIpG,qBAAqB;AAErB,oBAAoB;AACpB;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,IAAwB,EAAE,WAAwB;IAC/E,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAChE,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACjC,KAAK,MAAM,OAAO,IAAI,WAAW,EAAE,CAAC;QAClC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YAChE,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AACD,kBAAkB"}
@@ -0,0 +1,15 @@
1
+ import type { Part } from "@opencode-ai/sdk";
2
+ import type { ToolHistoryCompactionConfig } from "./config.js";
3
+ export interface TransformMessage {
4
+ info: unknown;
5
+ parts: Part[];
6
+ }
7
+ /**
8
+ * Deterministically rewrite eligible tool part outputs in place.
9
+ * Model-agnostic: the current model is not needed. Non-destructive to inputs,
10
+ * part structure, and ordering. Idempotent: already-compacted parts are skipped
11
+ * by their markers, so a second pass is a no-op.
12
+ * @param messages - the transform hook's in-memory message list.
13
+ * @param config - resolved compaction config.
14
+ */
15
+ export declare function compactMessages(messages: TransformMessage[], config: ToolHistoryCompactionConfig): void;
@@ -0,0 +1,93 @@
1
+ // FILE: src/plugins/tool-history-compaction/transform.ts
2
+ // VERSION: 0.1.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Apply tool-history compaction to the in-memory message list the model is about to receive: walk messages newest-first, protect the recent tail, dispatch retained/read/other tools to the right compaction layer, and mutate only completed tool part outputs.
5
+ // SCOPE: Completed-tool-part detection, protected-tail accounting, retention dispatch, read-slim and prune application, and idempotent in-place output rewrites.
6
+ // DEPENDS: [src/plugins/tool-history-compaction/config.ts, src/plugins/tool-history-compaction/retention.ts, src/plugins/tool-history-compaction/prune.ts, src/plugins/tool-history-compaction/read-slim.ts]
7
+ // LINKS: [M-PLUGIN-TOOL-HISTORY-COMPACTION]
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // TransformMessage - One message entry in the transform hook output.
14
+ // compactMessages - Deterministically rewrite eligible tool part outputs in place.
15
+ // END_MODULE_MAP
16
+ //
17
+ // START_CHANGE_SUMMARY
18
+ // LAST_CHANGE: [v0.1.0 - Established the transform core with protected tail, retention dispatch, and idempotence.]
19
+ // END_CHANGE_SUMMARY
20
+ import { pruneOutput } from "./prune.js";
21
+ import { slimReadOutput } from "./read-slim.js";
22
+ import { isRetainedTool } from "./retention.js";
23
+ function isCompletedToolPart(part) {
24
+ return part.type === "tool" && part.state.status === "completed";
25
+ }
26
+ function isCompactedByOpencode(part) {
27
+ const time = part.state.time;
28
+ return (typeof time === "object" && time !== null && "compacted" in time && time.compacted !== undefined);
29
+ }
30
+ function isReadTool(tool) {
31
+ return tool.toLowerCase() === "read";
32
+ }
33
+ // END_BLOCK_GUARDS
34
+ // START_BLOCK_COMPACT
35
+ /**
36
+ * Deterministically rewrite eligible tool part outputs in place.
37
+ * Model-agnostic: the current model is not needed. Non-destructive to inputs,
38
+ * part structure, and ordering. Idempotent: already-compacted parts are skipped
39
+ * by their markers, so a second pass is a no-op.
40
+ * @param messages - the transform hook's in-memory message list.
41
+ * @param config - resolved compaction config.
42
+ */
43
+ export function compactMessages(messages, config) {
44
+ let remainingProtection = config.protectLastCalls;
45
+ let firstMessage = true;
46
+ for (let i = messages.length - 1; i >= 0; i--) {
47
+ const message = messages[i];
48
+ if (!message)
49
+ continue;
50
+ for (let j = message.parts.length - 1; j >= 0; j--) {
51
+ const part = message.parts[j];
52
+ if (!part || part.type !== "tool")
53
+ continue;
54
+ if (!isCompletedToolPart(part))
55
+ continue;
56
+ if (isCompactedByOpencode(part))
57
+ continue;
58
+ const withinTail = firstMessage || remainingProtection > 0;
59
+ if (withinTail) {
60
+ if (!firstMessage)
61
+ remainingProtection -= 1;
62
+ continue;
63
+ }
64
+ if (isRetainedTool(part.tool, config.retainTools))
65
+ continue;
66
+ const output = part.state.output;
67
+ let rewritten;
68
+ if (config.readSlim && isReadTool(part.tool)) {
69
+ const slim = slimReadOutput(part.state.input, output, config);
70
+ if (slim) {
71
+ rewritten = slim.output;
72
+ }
73
+ else {
74
+ const pruned = pruneOutput(output, config);
75
+ if (pruned)
76
+ rewritten = pruned.output;
77
+ }
78
+ }
79
+ else {
80
+ const pruned = pruneOutput(output, config);
81
+ if (pruned)
82
+ rewritten = pruned.output;
83
+ }
84
+ if (rewritten !== undefined && rewritten !== output) {
85
+ part.state.output = rewritten;
86
+ }
87
+ }
88
+ if (firstMessage)
89
+ firstMessage = false;
90
+ }
91
+ }
92
+ // END_BLOCK_COMPACT
93
+ //# sourceMappingURL=transform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform.js","sourceRoot":"","sources":["../../../src/plugins/tool-history-compaction/transform.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osovv/vv-opencode",
3
- "version": "1.2.8",
3
+ "version": "1.2.11",
4
4
  "description": "A curated, opinionated set of OpenCode plugins for spec-first, review-driven, safer agentic development.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -73,6 +73,10 @@
73
73
  "./plugins/web-tools": {
74
74
  "types": "./dist/plugins/web-tools/index.d.ts",
75
75
  "import": "./dist/plugins/web-tools/index.js"
76
+ },
77
+ "./plugins/tool-history-compaction": {
78
+ "types": "./dist/plugins/tool-history-compaction/index.d.ts",
79
+ "import": "./dist/plugins/tool-history-compaction/index.js"
76
80
  }
77
81
  },
78
82
  "scripts": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://cdn.jsdelivr.net/npm/@osovv/vv-opencode@1.2.8/schemas/vvoc/v3.json",
3
+ "$id": "https://cdn.jsdelivr.net/npm/@osovv/vv-opencode@1.2.11/schemas/vvoc/v3.json",
4
4
  "title": "vvoc config",
5
5
  "description": "Canonical vvoc configuration document.",
6
6
  "type": "object",
@@ -162,6 +162,20 @@
162
162
  }
163
163
  }
164
164
  }
165
+ },
166
+ {
167
+ "type": "object",
168
+ "additionalProperties": false,
169
+ "properties": {
170
+ "enabled": { "type": "boolean" },
171
+ "protectLastCalls": { "type": "integer", "minimum": 0 },
172
+ "minSavingsChars": { "type": "integer", "minimum": 0 },
173
+ "outputMaxChars": { "type": "integer", "minimum": 0 },
174
+ "headChars": { "type": "integer", "minimum": 0 },
175
+ "tailChars": { "type": "integer", "minimum": 0 },
176
+ "readSlim": { "type": "boolean" },
177
+ "retainTools": { "type": "array", "items": { "type": "string", "minLength": 1 } }
178
+ }
165
179
  }
166
180
  ]
167
181
  }
@@ -98,6 +98,7 @@ next safe step. Make it sufficient to resume without rediscovering settled facts
98
98
  <final_response_format>
99
99
  - Start with the outcome.
100
100
  - For review, analysis, planning, or investigation, start with findings or the plan.
101
+ - Before claiming completion, read the goal back line by line and mark each line met / partly / not met, then name the edge you did not check.
101
102
  - Mention changed files and verification only when implementation occurred.
102
103
  - Mention assumptions, skipped checks, blockers, or residual risks when they materially affect the
103
104
  outcome.