@kolisachint/hoocode-agent 0.4.71 → 0.4.73

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.
@@ -2,7 +2,7 @@
2
2
  * Local-inference routing.
3
3
  *
4
4
  * Optional, opt-in routing of certain non-critical work (conversation
5
- * compaction, and large read/bash tool-result compression) to a local
5
+ * compaction, and large bash tool-result compression) to a local
6
6
  * "executor" model running on an OpenAI-compatible endpoint (for example an
7
7
  * MLX server), while the primary model handles all planning, reasoning, edits,
8
8
  * and tool-call synthesis.
@@ -21,10 +21,23 @@ import type { ModelRegistry } from "../model-registry.js";
21
21
  export type TurnKind = "primary" | "summarization" | "tool-result";
22
22
  export type RoutingMode = "primary-only" | "executor-for-summarization" | "executor-for-tool-results" | "shadow-executor";
23
23
  export declare const ROUTING_MODES: readonly RoutingMode[];
24
- /** Tool names whose output is worth compressing (validated). Others pass through. */
24
+ /**
25
+ * Tool names whose output is worth compressing (validated). Others pass
26
+ * through. Only `bash` qualifies: its verbose output is mostly low-value noise
27
+ * around a few load-bearing facts. `read` was measured to compress ~0% on real
28
+ * source code (every line is a keep-line) and was removed. Fact-list tools
29
+ * (grep/find/ls) were never compressible (every line is a distinct fact).
30
+ */
25
31
  export declare const COMPRESSIBLE_TOOLS: Set<string>;
26
- /** Default minimum tool-result size (bytes) before compression is attempted. */
27
- export declare const DEFAULT_TOOL_RESULT_MIN_BYTES = 2048;
32
+ /**
33
+ * Global size band (bytes) for local-inference routing. Applies to BOTH
34
+ * tool-result compression and compaction summarization. Inputs below the
35
+ * minimum are not worth offloading; inputs above the maximum are slow and risk
36
+ * GPU OOM on small machines, so they fall back to the primary model. Tunable
37
+ * per-machine via `minBytes`/`maxBytes` in the executor config block.
38
+ */
39
+ export declare const DEFAULT_MIN_BYTES = 2048;
40
+ export declare const DEFAULT_MAX_BYTES = 8192;
28
41
  /** Optional local server the harness manages for the executor. */
29
42
  export interface ExecutorServerConfig {
30
43
  /** Command to launch (default: "mlx_lm.server"). */
@@ -42,8 +55,10 @@ export interface ExecutorServerConfig {
42
55
  export interface ExecutorConfig {
43
56
  provider: string;
44
57
  model: string;
45
- /** Minimum tool-result size (bytes) before compression is attempted. */
46
- toolResultMinBytes?: number;
58
+ /** Minimum input size (bytes) before local inference is attempted. */
59
+ minBytes?: number;
60
+ /** Maximum input size (bytes); larger inputs fall back to the primary model. */
61
+ maxBytes?: number;
47
62
  /** When set, the harness spawns/health-checks/stops this local server. */
48
63
  server?: ExecutorServerConfig;
49
64
  }
@@ -74,7 +89,8 @@ export declare class LocalInferenceRouter {
74
89
  private readonly mode;
75
90
  private readonly executorConfig;
76
91
  private readonly executor;
77
- private readonly toolResultMinBytes;
92
+ private readonly minBytes;
93
+ private readonly maxBytes;
78
94
  private constructor();
79
95
  static create(opts: {
80
96
  mode: RoutingMode;
@@ -92,8 +108,22 @@ export declare class LocalInferenceRouter {
92
108
  * executor is exercised separately for measurement).
93
109
  */
94
110
  selectModel(turnKind: TurnKind, primary: Model<Api>): Model<Api>;
111
+ /** True when an input size falls within the configured local-inference band. */
112
+ withinSizeBand(bytes: number): boolean;
113
+ /** The configured size band, for logging/diagnostics. */
114
+ getSizeBand(): {
115
+ minBytes: number;
116
+ maxBytes: number;
117
+ };
95
118
  /** Whether a given tool's result should be compressed via the executor. */
96
119
  shouldCompressToolResult(toolName: string, contentBytes: number): boolean;
120
+ /**
121
+ * Whether to route summarization to the executor for a conversation of the
122
+ * given serialized size. Requires summarization routing active, the executor
123
+ * available, and the size within the band (oversized conversations fall back
124
+ * to the primary to avoid slow local runs and GPU OOM).
125
+ */
126
+ shouldRouteSummarization(bytes: number): boolean;
97
127
  getExecutorModel(): Model<Api> | undefined;
98
128
  }
99
129
  //# sourceMappingURL=local-inference.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"local-inference.d.ts","sourceRoot":"","sources":["../../../src/core/routing/local-inference.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,4EAA4E;AAC5E,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,eAAe,GAAG,aAAa,CAAC;AAEnE,MAAM,MAAM,WAAW,GACpB,cAAc,GACd,4BAA4B,GAC5B,2BAA2B,GAC3B,iBAAiB,CAAC;AAErB,eAAO,MAAM,aAAa,EAAE,SAAS,WAAW,EAKtC,CAAC;AAEX,qFAAqF;AACrF,eAAO,MAAM,kBAAkB,aAA4B,CAAC;AAE5D,gFAAgF;AAChF,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAElD,kEAAkE;AAClE,MAAM,WAAW,oBAAoB;IACpC,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,2FAA2F;IAC3F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,0EAA0E;IAC1E,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC9B;AAED,sCAAsC;AACtC,MAAM,WAAW,aAAa;IAC7B,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC1B;AAMD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACxC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,WAAW,CAAC;CACzB,GAAG,WAAW,CASd;AAED;;;;GAIG;AACH,qBAAa,oBAAoB;IAChC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAC5D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyB;IAClD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAE5C,OAAO,eASN;IAED,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;QACnB,IAAI,EAAE,WAAW,CAAC;QAClB,MAAM,EAAE,aAAa,GAAG,SAAS,CAAC;QAClC,QAAQ,EAAE,aAAa,CAAC;KACxB,GAAG,oBAAoB,CAOvB;IAED,OAAO,IAAI,WAAW,CAErB;IAED,gFAAgF;IAChF,mBAAmB,IAAI,OAAO,CAE7B;IAED,iBAAiB,IAAI,cAAc,GAAG,SAAS,CAE9C;IAED;;;;;OAKG;IACH,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAW/D;IAED,2EAA2E;IAC3E,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAKxE;IAED,gBAAgB,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CAEzC;CACD","sourcesContent":["/**\n * Local-inference routing.\n *\n * Optional, opt-in routing of certain non-critical work (conversation\n * compaction, and large read/bash tool-result compression) to a local\n * \"executor\" model running on an OpenAI-compatible endpoint (for example an\n * MLX server), while the primary model handles all planning, reasoning, edits,\n * and tool-call synthesis.\n *\n * Everything here is INERT unless explicitly enabled via the\n * `--enable-local-inference` flag or the `HOOCODE_ROUTING_MODE` env var. On any\n * executor resolution/availability problem the caller falls back to the primary\n * model (compaction) or the raw tool result (tool-result compression). The\n * router never throws into the agent loop.\n *\n * Design and validation: see docs/local-executor-routing.md.\n */\n\nimport type { Api, Model } from \"@kolisachint/hoocode-ai\";\nimport type { ModelRegistry } from \"../model-registry.js\";\n\n/** Work that may be routed to the executor instead of the primary model. */\nexport type TurnKind = \"primary\" | \"summarization\" | \"tool-result\";\n\nexport type RoutingMode =\n\t| \"primary-only\"\n\t| \"executor-for-summarization\"\n\t| \"executor-for-tool-results\"\n\t| \"shadow-executor\";\n\nexport const ROUTING_MODES: readonly RoutingMode[] = [\n\t\"primary-only\",\n\t\"executor-for-summarization\",\n\t\"executor-for-tool-results\",\n\t\"shadow-executor\",\n] as const;\n\n/** Tool names whose output is worth compressing (validated). Others pass through. */\nexport const COMPRESSIBLE_TOOLS = new Set([\"read\", \"bash\"]);\n\n/** Default minimum tool-result size (bytes) before compression is attempted. */\nexport const DEFAULT_TOOL_RESULT_MIN_BYTES = 2048;\n\n/** Optional local server the harness manages for the executor. */\nexport interface ExecutorServerConfig {\n\t/** Command to launch (default: \"mlx_lm.server\"). */\n\tcommand?: string;\n\t/** Extra args appended to the launch command. */\n\targs?: string[];\n\t/** Host to health-check and bind (default: derived from executor baseUrl or 127.0.0.1). */\n\thost?: string;\n\t/** Port to health-check and bind (default: derived from executor baseUrl or 8080). */\n\tport?: number;\n\t/** Max milliseconds to wait for the server to become healthy (default: 30000). */\n\tstartupTimeoutMs?: number;\n}\n\n/** Executor model reference as configured in models.json. */\nexport interface ExecutorConfig {\n\tprovider: string;\n\tmodel: string;\n\t/** Minimum tool-result size (bytes) before compression is attempted. */\n\ttoolResultMinBytes?: number;\n\t/** When set, the harness spawns/health-checks/stops this local server. */\n\tserver?: ExecutorServerConfig;\n}\n\n/** `routing` block in models.json. */\nexport interface RoutingConfig {\n\tmode?: RoutingMode;\n\texecutor?: ExecutorConfig;\n}\n\nfunction isRoutingMode(value: unknown): value is RoutingMode {\n\treturn typeof value === \"string\" && (ROUTING_MODES as readonly string[]).includes(value);\n}\n\n/**\n * Resolve the effective routing mode from CLI flag, env var, and config.\n *\n * Activation requires either the flag or the env var; config alone never\n * activates routing (decision: explicit opt-in only). When activated without an\n * explicit mode, defaults to `executor-for-summarization` (the lowest-risk\n * mode). When not activated, always `primary-only`.\n */\nexport function resolveRoutingMode(opts: {\n\tenableFlag?: boolean;\n\tenvMode?: string;\n\tconfigMode?: RoutingMode;\n}): RoutingMode {\n\tconst envMode = opts.envMode?.trim();\n\tconst envActivates = envMode !== undefined && envMode !== \"\" && envMode !== \"primary-only\";\n\tconst activated = opts.enableFlag === true || envActivates;\n\tif (!activated) return \"primary-only\";\n\n\tif (envMode && isRoutingMode(envMode)) return envMode;\n\tif (opts.configMode && isRoutingMode(opts.configMode)) return opts.configMode;\n\treturn \"executor-for-summarization\";\n}\n\n/**\n * Router that decides, per turn kind, whether to use the executor model and\n * resolves it from the registry. Holds no mutable state beyond the resolved\n * executor model.\n */\nexport class LocalInferenceRouter {\n\tprivate readonly mode: RoutingMode;\n\tprivate readonly executorConfig: ExecutorConfig | undefined;\n\tprivate readonly executor: Model<Api> | undefined;\n\tprivate readonly toolResultMinBytes: number;\n\n\tprivate constructor(\n\t\tmode: RoutingMode,\n\t\texecutorConfig: ExecutorConfig | undefined,\n\t\texecutor: Model<Api> | undefined,\n\t) {\n\t\tthis.mode = mode;\n\t\tthis.executorConfig = executorConfig;\n\t\tthis.executor = executor;\n\t\tthis.toolResultMinBytes = executorConfig?.toolResultMinBytes ?? DEFAULT_TOOL_RESULT_MIN_BYTES;\n\t}\n\n\tstatic create(opts: {\n\t\tmode: RoutingMode;\n\t\tconfig: RoutingConfig | undefined;\n\t\tregistry: ModelRegistry;\n\t}): LocalInferenceRouter {\n\t\tconst executorConfig = opts.config?.executor;\n\t\tlet executor: Model<Api> | undefined;\n\t\tif (opts.mode !== \"primary-only\" && executorConfig) {\n\t\t\texecutor = opts.registry.find(executorConfig.provider, executorConfig.model);\n\t\t}\n\t\treturn new LocalInferenceRouter(opts.mode, executorConfig, executor);\n\t}\n\n\tgetMode(): RoutingMode {\n\t\treturn this.mode;\n\t}\n\n\t/** True when routing is active and an executor model is resolved and usable. */\n\tisExecutorAvailable(): boolean {\n\t\treturn this.mode !== \"primary-only\" && this.executor !== undefined;\n\t}\n\n\tgetExecutorConfig(): ExecutorConfig | undefined {\n\t\treturn this.executorConfig;\n\t}\n\n\t/**\n\t * Pick the model to use for a turn. Returns the executor when the mode routes\n\t * that turn kind and the executor is available; otherwise returns the primary\n\t * model. `shadow-executor` always returns the primary for the live path (the\n\t * executor is exercised separately for measurement).\n\t */\n\tselectModel(turnKind: TurnKind, primary: Model<Api>): Model<Api> {\n\t\tif (!this.isExecutorAvailable() || !this.executor) return primary;\n\t\tswitch (this.mode) {\n\t\t\tcase \"executor-for-summarization\":\n\t\t\t\treturn turnKind === \"summarization\" ? this.executor : primary;\n\t\t\tcase \"executor-for-tool-results\":\n\t\t\t\treturn turnKind === \"tool-result\" ? this.executor : primary;\n\t\t\tcase \"shadow-executor\":\n\t\t\tcase \"primary-only\":\n\t\t\t\treturn primary;\n\t\t}\n\t}\n\n\t/** Whether a given tool's result should be compressed via the executor. */\n\tshouldCompressToolResult(toolName: string, contentBytes: number): boolean {\n\t\tif (this.mode !== \"executor-for-tool-results\") return false;\n\t\tif (!this.isExecutorAvailable()) return false;\n\t\tif (!COMPRESSIBLE_TOOLS.has(toolName)) return false;\n\t\treturn contentBytes >= this.toolResultMinBytes;\n\t}\n\n\tgetExecutorModel(): Model<Api> | undefined {\n\t\treturn this.executor;\n\t}\n}\n"]}
1
+ {"version":3,"file":"local-inference.d.ts","sourceRoot":"","sources":["../../../src/core/routing/local-inference.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Local-inference routing.\n *\n * Optional, opt-in routing of certain non-critical work (conversation\n * compaction, and large bash tool-result compression) to a local\n * \"executor\" model running on an OpenAI-compatible endpoint (for example an\n * MLX server), while the primary model handles all planning, reasoning, edits,\n * and tool-call synthesis.\n *\n * Everything here is INERT unless explicitly enabled via the\n * `--enable-local-inference` flag or the `HOOCODE_ROUTING_MODE` env var. On any\n * executor resolution/availability problem the caller falls back to the primary\n * model (compaction) or the raw tool result (tool-result compression). The\n * router never throws into the agent loop.\n *\n * Design and validation: see docs/local-executor-routing.md.\n */\n\nimport type { Api, Model } from \"@kolisachint/hoocode-ai\";\nimport type { ModelRegistry } from \"../model-registry.js\";\n\n/** Work that may be routed to the executor instead of the primary model. */\nexport type TurnKind = \"primary\" | \"summarization\" | \"tool-result\";\n\nexport type RoutingMode =\n\t| \"primary-only\"\n\t| \"executor-for-summarization\"\n\t| \"executor-for-tool-results\"\n\t| \"shadow-executor\";\n\nexport const ROUTING_MODES: readonly RoutingMode[] = [\n\t\"primary-only\",\n\t\"executor-for-summarization\",\n\t\"executor-for-tool-results\",\n\t\"shadow-executor\",\n] as const;\n\n/**\n * Tool names whose output is worth compressing (validated). Others pass\n * through. Only `bash` qualifies: its verbose output is mostly low-value noise\n * around a few load-bearing facts. `read` was measured to compress ~0% on real\n * source code (every line is a keep-line) and was removed. Fact-list tools\n * (grep/find/ls) were never compressible (every line is a distinct fact).\n */\nexport const COMPRESSIBLE_TOOLS = new Set([\"bash\"]);\n\n/**\n * Global size band (bytes) for local-inference routing. Applies to BOTH\n * tool-result compression and compaction summarization. Inputs below the\n * minimum are not worth offloading; inputs above the maximum are slow and risk\n * GPU OOM on small machines, so they fall back to the primary model. Tunable\n * per-machine via `minBytes`/`maxBytes` in the executor config block.\n */\nexport const DEFAULT_MIN_BYTES = 2048;\nexport const DEFAULT_MAX_BYTES = 8192;\n\n/** Optional local server the harness manages for the executor. */\nexport interface ExecutorServerConfig {\n\t/** Command to launch (default: \"mlx_lm.server\"). */\n\tcommand?: string;\n\t/** Extra args appended to the launch command. */\n\targs?: string[];\n\t/** Host to health-check and bind (default: derived from executor baseUrl or 127.0.0.1). */\n\thost?: string;\n\t/** Port to health-check and bind (default: derived from executor baseUrl or 8080). */\n\tport?: number;\n\t/** Max milliseconds to wait for the server to become healthy (default: 30000). */\n\tstartupTimeoutMs?: number;\n}\n\n/** Executor model reference as configured in models.json. */\nexport interface ExecutorConfig {\n\tprovider: string;\n\tmodel: string;\n\t/** Minimum input size (bytes) before local inference is attempted. */\n\tminBytes?: number;\n\t/** Maximum input size (bytes); larger inputs fall back to the primary model. */\n\tmaxBytes?: number;\n\t/** When set, the harness spawns/health-checks/stops this local server. */\n\tserver?: ExecutorServerConfig;\n}\n\n/** `routing` block in models.json. */\nexport interface RoutingConfig {\n\tmode?: RoutingMode;\n\texecutor?: ExecutorConfig;\n}\n\nfunction isRoutingMode(value: unknown): value is RoutingMode {\n\treturn typeof value === \"string\" && (ROUTING_MODES as readonly string[]).includes(value);\n}\n\n/**\n * Resolve the effective routing mode from CLI flag, env var, and config.\n *\n * Activation requires either the flag or the env var; config alone never\n * activates routing (decision: explicit opt-in only). When activated without an\n * explicit mode, defaults to `executor-for-summarization` (the lowest-risk\n * mode). When not activated, always `primary-only`.\n */\nexport function resolveRoutingMode(opts: {\n\tenableFlag?: boolean;\n\tenvMode?: string;\n\tconfigMode?: RoutingMode;\n}): RoutingMode {\n\tconst envMode = opts.envMode?.trim();\n\tconst envActivates = envMode !== undefined && envMode !== \"\" && envMode !== \"primary-only\";\n\tconst activated = opts.enableFlag === true || envActivates;\n\tif (!activated) return \"primary-only\";\n\n\tif (envMode && isRoutingMode(envMode)) return envMode;\n\tif (opts.configMode && isRoutingMode(opts.configMode)) return opts.configMode;\n\treturn \"executor-for-summarization\";\n}\n\n/**\n * Router that decides, per turn kind, whether to use the executor model and\n * resolves it from the registry. Holds no mutable state beyond the resolved\n * executor model.\n */\nexport class LocalInferenceRouter {\n\tprivate readonly mode: RoutingMode;\n\tprivate readonly executorConfig: ExecutorConfig | undefined;\n\tprivate readonly executor: Model<Api> | undefined;\n\tprivate readonly minBytes: number;\n\tprivate readonly maxBytes: number;\n\n\tprivate constructor(\n\t\tmode: RoutingMode,\n\t\texecutorConfig: ExecutorConfig | undefined,\n\t\texecutor: Model<Api> | undefined,\n\t) {\n\t\tthis.mode = mode;\n\t\tthis.executorConfig = executorConfig;\n\t\tthis.executor = executor;\n\t\tthis.minBytes = executorConfig?.minBytes ?? DEFAULT_MIN_BYTES;\n\t\tthis.maxBytes = executorConfig?.maxBytes ?? DEFAULT_MAX_BYTES;\n\t}\n\n\tstatic create(opts: {\n\t\tmode: RoutingMode;\n\t\tconfig: RoutingConfig | undefined;\n\t\tregistry: ModelRegistry;\n\t}): LocalInferenceRouter {\n\t\tconst executorConfig = opts.config?.executor;\n\t\tlet executor: Model<Api> | undefined;\n\t\tif (opts.mode !== \"primary-only\" && executorConfig) {\n\t\t\texecutor = opts.registry.find(executorConfig.provider, executorConfig.model);\n\t\t}\n\t\treturn new LocalInferenceRouter(opts.mode, executorConfig, executor);\n\t}\n\n\tgetMode(): RoutingMode {\n\t\treturn this.mode;\n\t}\n\n\t/** True when routing is active and an executor model is resolved and usable. */\n\tisExecutorAvailable(): boolean {\n\t\treturn this.mode !== \"primary-only\" && this.executor !== undefined;\n\t}\n\n\tgetExecutorConfig(): ExecutorConfig | undefined {\n\t\treturn this.executorConfig;\n\t}\n\n\t/**\n\t * Pick the model to use for a turn. Returns the executor when the mode routes\n\t * that turn kind and the executor is available; otherwise returns the primary\n\t * model. `shadow-executor` always returns the primary for the live path (the\n\t * executor is exercised separately for measurement).\n\t */\n\tselectModel(turnKind: TurnKind, primary: Model<Api>): Model<Api> {\n\t\tif (!this.isExecutorAvailable() || !this.executor) return primary;\n\t\tswitch (this.mode) {\n\t\t\tcase \"executor-for-summarization\":\n\t\t\t\treturn turnKind === \"summarization\" ? this.executor : primary;\n\t\t\tcase \"executor-for-tool-results\":\n\t\t\t\treturn turnKind === \"tool-result\" ? this.executor : primary;\n\t\t\tcase \"shadow-executor\":\n\t\t\tcase \"primary-only\":\n\t\t\t\treturn primary;\n\t\t}\n\t}\n\n\t/** True when an input size falls within the configured local-inference band. */\n\twithinSizeBand(bytes: number): boolean {\n\t\treturn bytes >= this.minBytes && bytes <= this.maxBytes;\n\t}\n\n\t/** The configured size band, for logging/diagnostics. */\n\tgetSizeBand(): { minBytes: number; maxBytes: number } {\n\t\treturn { minBytes: this.minBytes, maxBytes: this.maxBytes };\n\t}\n\n\t/** Whether a given tool's result should be compressed via the executor. */\n\tshouldCompressToolResult(toolName: string, contentBytes: number): boolean {\n\t\tif (this.mode !== \"executor-for-tool-results\") return false;\n\t\tif (!this.isExecutorAvailable()) return false;\n\t\tif (!COMPRESSIBLE_TOOLS.has(toolName)) return false;\n\t\treturn this.withinSizeBand(contentBytes);\n\t}\n\n\t/**\n\t * Whether to route summarization to the executor for a conversation of the\n\t * given serialized size. Requires summarization routing active, the executor\n\t * available, and the size within the band (oversized conversations fall back\n\t * to the primary to avoid slow local runs and GPU OOM).\n\t */\n\tshouldRouteSummarization(bytes: number): boolean {\n\t\tif (this.mode !== \"executor-for-summarization\") return false;\n\t\tif (!this.isExecutorAvailable()) return false;\n\t\treturn this.withinSizeBand(bytes);\n\t}\n\n\tgetExecutorModel(): Model<Api> | undefined {\n\t\treturn this.executor;\n\t}\n}\n"]}
@@ -2,7 +2,7 @@
2
2
  * Local-inference routing.
3
3
  *
4
4
  * Optional, opt-in routing of certain non-critical work (conversation
5
- * compaction, and large read/bash tool-result compression) to a local
5
+ * compaction, and large bash tool-result compression) to a local
6
6
  * "executor" model running on an OpenAI-compatible endpoint (for example an
7
7
  * MLX server), while the primary model handles all planning, reasoning, edits,
8
8
  * and tool-call synthesis.
@@ -21,10 +21,23 @@ export const ROUTING_MODES = [
21
21
  "executor-for-tool-results",
22
22
  "shadow-executor",
23
23
  ];
24
- /** Tool names whose output is worth compressing (validated). Others pass through. */
25
- export const COMPRESSIBLE_TOOLS = new Set(["read", "bash"]);
26
- /** Default minimum tool-result size (bytes) before compression is attempted. */
27
- export const DEFAULT_TOOL_RESULT_MIN_BYTES = 2048;
24
+ /**
25
+ * Tool names whose output is worth compressing (validated). Others pass
26
+ * through. Only `bash` qualifies: its verbose output is mostly low-value noise
27
+ * around a few load-bearing facts. `read` was measured to compress ~0% on real
28
+ * source code (every line is a keep-line) and was removed. Fact-list tools
29
+ * (grep/find/ls) were never compressible (every line is a distinct fact).
30
+ */
31
+ export const COMPRESSIBLE_TOOLS = new Set(["bash"]);
32
+ /**
33
+ * Global size band (bytes) for local-inference routing. Applies to BOTH
34
+ * tool-result compression and compaction summarization. Inputs below the
35
+ * minimum are not worth offloading; inputs above the maximum are slow and risk
36
+ * GPU OOM on small machines, so they fall back to the primary model. Tunable
37
+ * per-machine via `minBytes`/`maxBytes` in the executor config block.
38
+ */
39
+ export const DEFAULT_MIN_BYTES = 2048;
40
+ export const DEFAULT_MAX_BYTES = 8192;
28
41
  function isRoutingMode(value) {
29
42
  return typeof value === "string" && ROUTING_MODES.includes(value);
30
43
  }
@@ -57,12 +70,14 @@ export class LocalInferenceRouter {
57
70
  mode;
58
71
  executorConfig;
59
72
  executor;
60
- toolResultMinBytes;
73
+ minBytes;
74
+ maxBytes;
61
75
  constructor(mode, executorConfig, executor) {
62
76
  this.mode = mode;
63
77
  this.executorConfig = executorConfig;
64
78
  this.executor = executor;
65
- this.toolResultMinBytes = executorConfig?.toolResultMinBytes ?? DEFAULT_TOOL_RESULT_MIN_BYTES;
79
+ this.minBytes = executorConfig?.minBytes ?? DEFAULT_MIN_BYTES;
80
+ this.maxBytes = executorConfig?.maxBytes ?? DEFAULT_MAX_BYTES;
66
81
  }
67
82
  static create(opts) {
68
83
  const executorConfig = opts.config?.executor;
@@ -101,6 +116,14 @@ export class LocalInferenceRouter {
101
116
  return primary;
102
117
  }
103
118
  }
119
+ /** True when an input size falls within the configured local-inference band. */
120
+ withinSizeBand(bytes) {
121
+ return bytes >= this.minBytes && bytes <= this.maxBytes;
122
+ }
123
+ /** The configured size band, for logging/diagnostics. */
124
+ getSizeBand() {
125
+ return { minBytes: this.minBytes, maxBytes: this.maxBytes };
126
+ }
104
127
  /** Whether a given tool's result should be compressed via the executor. */
105
128
  shouldCompressToolResult(toolName, contentBytes) {
106
129
  if (this.mode !== "executor-for-tool-results")
@@ -109,7 +132,20 @@ export class LocalInferenceRouter {
109
132
  return false;
110
133
  if (!COMPRESSIBLE_TOOLS.has(toolName))
111
134
  return false;
112
- return contentBytes >= this.toolResultMinBytes;
135
+ return this.withinSizeBand(contentBytes);
136
+ }
137
+ /**
138
+ * Whether to route summarization to the executor for a conversation of the
139
+ * given serialized size. Requires summarization routing active, the executor
140
+ * available, and the size within the band (oversized conversations fall back
141
+ * to the primary to avoid slow local runs and GPU OOM).
142
+ */
143
+ shouldRouteSummarization(bytes) {
144
+ if (this.mode !== "executor-for-summarization")
145
+ return false;
146
+ if (!this.isExecutorAvailable())
147
+ return false;
148
+ return this.withinSizeBand(bytes);
113
149
  }
114
150
  getExecutorModel() {
115
151
  return this.executor;
@@ -1 +1 @@
1
- {"version":3,"file":"local-inference.js","sourceRoot":"","sources":["../../../src/core/routing/local-inference.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAcH,MAAM,CAAC,MAAM,aAAa,GAA2B;IACpD,cAAc;IACd,4BAA4B;IAC5B,2BAA2B;IAC3B,iBAAiB;CACR,CAAC;AAEX,qFAAqF;AACrF,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAE5D,gFAAgF;AAChF,MAAM,CAAC,MAAM,6BAA6B,GAAG,IAAI,CAAC;AAgClD,SAAS,aAAa,CAAC,KAAc,EAAwB;IAC5D,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,aAAmC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAAA,CACzF;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAIlC,EAAe;IACf,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;IACrC,MAAM,YAAY,GAAG,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,cAAc,CAAC;IAC3F,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,KAAK,IAAI,IAAI,YAAY,CAAC;IAC3D,IAAI,CAAC,SAAS;QAAE,OAAO,cAAc,CAAC;IAEtC,IAAI,OAAO,IAAI,aAAa,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IACtD,IAAI,IAAI,CAAC,UAAU,IAAI,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC,UAAU,CAAC;IAC9E,OAAO,4BAA4B,CAAC;AAAA,CACpC;AAED;;;;GAIG;AACH,MAAM,OAAO,oBAAoB;IACf,IAAI,CAAc;IAClB,cAAc,CAA6B;IAC3C,QAAQ,CAAyB;IACjC,kBAAkB,CAAS;IAE5C,YACC,IAAiB,EACjB,cAA0C,EAC1C,QAAgC,EAC/B;QACD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACrC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,kBAAkB,GAAG,cAAc,EAAE,kBAAkB,IAAI,6BAA6B,CAAC;IAAA,CAC9F;IAED,MAAM,CAAC,MAAM,CAAC,IAIb,EAAwB;QACxB,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC;QAC7C,IAAI,QAAgC,CAAC;QACrC,IAAI,IAAI,CAAC,IAAI,KAAK,cAAc,IAAI,cAAc,EAAE,CAAC;YACpD,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;QAC9E,CAAC;QACD,OAAO,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,QAAQ,CAAC,CAAC;IAAA,CACrE;IAED,OAAO,GAAgB;QACtB,OAAO,IAAI,CAAC,IAAI,CAAC;IAAA,CACjB;IAED,gFAAgF;IAChF,mBAAmB,GAAY;QAC9B,OAAO,IAAI,CAAC,IAAI,KAAK,cAAc,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC;IAAA,CACnE;IAED,iBAAiB,GAA+B;QAC/C,OAAO,IAAI,CAAC,cAAc,CAAC;IAAA,CAC3B;IAED;;;;;OAKG;IACH,WAAW,CAAC,QAAkB,EAAE,OAAmB,EAAc;QAChE,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAO,OAAO,CAAC;QAClE,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,4BAA4B;gBAChC,OAAO,QAAQ,KAAK,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;YAC/D,KAAK,2BAA2B;gBAC/B,OAAO,QAAQ,KAAK,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;YAC7D,KAAK,iBAAiB,CAAC;YACvB,KAAK,cAAc;gBAClB,OAAO,OAAO,CAAC;QACjB,CAAC;IAAA,CACD;IAED,2EAA2E;IAC3E,wBAAwB,CAAC,QAAgB,EAAE,YAAoB,EAAW;QACzE,IAAI,IAAI,CAAC,IAAI,KAAK,2BAA2B;YAAE,OAAO,KAAK,CAAC;QAC5D,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE;YAAE,OAAO,KAAK,CAAC;QAC9C,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;QACpD,OAAO,YAAY,IAAI,IAAI,CAAC,kBAAkB,CAAC;IAAA,CAC/C;IAED,gBAAgB,GAA2B;QAC1C,OAAO,IAAI,CAAC,QAAQ,CAAC;IAAA,CACrB;CACD","sourcesContent":["/**\n * Local-inference routing.\n *\n * Optional, opt-in routing of certain non-critical work (conversation\n * compaction, and large read/bash tool-result compression) to a local\n * \"executor\" model running on an OpenAI-compatible endpoint (for example an\n * MLX server), while the primary model handles all planning, reasoning, edits,\n * and tool-call synthesis.\n *\n * Everything here is INERT unless explicitly enabled via the\n * `--enable-local-inference` flag or the `HOOCODE_ROUTING_MODE` env var. On any\n * executor resolution/availability problem the caller falls back to the primary\n * model (compaction) or the raw tool result (tool-result compression). The\n * router never throws into the agent loop.\n *\n * Design and validation: see docs/local-executor-routing.md.\n */\n\nimport type { Api, Model } from \"@kolisachint/hoocode-ai\";\nimport type { ModelRegistry } from \"../model-registry.js\";\n\n/** Work that may be routed to the executor instead of the primary model. */\nexport type TurnKind = \"primary\" | \"summarization\" | \"tool-result\";\n\nexport type RoutingMode =\n\t| \"primary-only\"\n\t| \"executor-for-summarization\"\n\t| \"executor-for-tool-results\"\n\t| \"shadow-executor\";\n\nexport const ROUTING_MODES: readonly RoutingMode[] = [\n\t\"primary-only\",\n\t\"executor-for-summarization\",\n\t\"executor-for-tool-results\",\n\t\"shadow-executor\",\n] as const;\n\n/** Tool names whose output is worth compressing (validated). Others pass through. */\nexport const COMPRESSIBLE_TOOLS = new Set([\"read\", \"bash\"]);\n\n/** Default minimum tool-result size (bytes) before compression is attempted. */\nexport const DEFAULT_TOOL_RESULT_MIN_BYTES = 2048;\n\n/** Optional local server the harness manages for the executor. */\nexport interface ExecutorServerConfig {\n\t/** Command to launch (default: \"mlx_lm.server\"). */\n\tcommand?: string;\n\t/** Extra args appended to the launch command. */\n\targs?: string[];\n\t/** Host to health-check and bind (default: derived from executor baseUrl or 127.0.0.1). */\n\thost?: string;\n\t/** Port to health-check and bind (default: derived from executor baseUrl or 8080). */\n\tport?: number;\n\t/** Max milliseconds to wait for the server to become healthy (default: 30000). */\n\tstartupTimeoutMs?: number;\n}\n\n/** Executor model reference as configured in models.json. */\nexport interface ExecutorConfig {\n\tprovider: string;\n\tmodel: string;\n\t/** Minimum tool-result size (bytes) before compression is attempted. */\n\ttoolResultMinBytes?: number;\n\t/** When set, the harness spawns/health-checks/stops this local server. */\n\tserver?: ExecutorServerConfig;\n}\n\n/** `routing` block in models.json. */\nexport interface RoutingConfig {\n\tmode?: RoutingMode;\n\texecutor?: ExecutorConfig;\n}\n\nfunction isRoutingMode(value: unknown): value is RoutingMode {\n\treturn typeof value === \"string\" && (ROUTING_MODES as readonly string[]).includes(value);\n}\n\n/**\n * Resolve the effective routing mode from CLI flag, env var, and config.\n *\n * Activation requires either the flag or the env var; config alone never\n * activates routing (decision: explicit opt-in only). When activated without an\n * explicit mode, defaults to `executor-for-summarization` (the lowest-risk\n * mode). When not activated, always `primary-only`.\n */\nexport function resolveRoutingMode(opts: {\n\tenableFlag?: boolean;\n\tenvMode?: string;\n\tconfigMode?: RoutingMode;\n}): RoutingMode {\n\tconst envMode = opts.envMode?.trim();\n\tconst envActivates = envMode !== undefined && envMode !== \"\" && envMode !== \"primary-only\";\n\tconst activated = opts.enableFlag === true || envActivates;\n\tif (!activated) return \"primary-only\";\n\n\tif (envMode && isRoutingMode(envMode)) return envMode;\n\tif (opts.configMode && isRoutingMode(opts.configMode)) return opts.configMode;\n\treturn \"executor-for-summarization\";\n}\n\n/**\n * Router that decides, per turn kind, whether to use the executor model and\n * resolves it from the registry. Holds no mutable state beyond the resolved\n * executor model.\n */\nexport class LocalInferenceRouter {\n\tprivate readonly mode: RoutingMode;\n\tprivate readonly executorConfig: ExecutorConfig | undefined;\n\tprivate readonly executor: Model<Api> | undefined;\n\tprivate readonly toolResultMinBytes: number;\n\n\tprivate constructor(\n\t\tmode: RoutingMode,\n\t\texecutorConfig: ExecutorConfig | undefined,\n\t\texecutor: Model<Api> | undefined,\n\t) {\n\t\tthis.mode = mode;\n\t\tthis.executorConfig = executorConfig;\n\t\tthis.executor = executor;\n\t\tthis.toolResultMinBytes = executorConfig?.toolResultMinBytes ?? DEFAULT_TOOL_RESULT_MIN_BYTES;\n\t}\n\n\tstatic create(opts: {\n\t\tmode: RoutingMode;\n\t\tconfig: RoutingConfig | undefined;\n\t\tregistry: ModelRegistry;\n\t}): LocalInferenceRouter {\n\t\tconst executorConfig = opts.config?.executor;\n\t\tlet executor: Model<Api> | undefined;\n\t\tif (opts.mode !== \"primary-only\" && executorConfig) {\n\t\t\texecutor = opts.registry.find(executorConfig.provider, executorConfig.model);\n\t\t}\n\t\treturn new LocalInferenceRouter(opts.mode, executorConfig, executor);\n\t}\n\n\tgetMode(): RoutingMode {\n\t\treturn this.mode;\n\t}\n\n\t/** True when routing is active and an executor model is resolved and usable. */\n\tisExecutorAvailable(): boolean {\n\t\treturn this.mode !== \"primary-only\" && this.executor !== undefined;\n\t}\n\n\tgetExecutorConfig(): ExecutorConfig | undefined {\n\t\treturn this.executorConfig;\n\t}\n\n\t/**\n\t * Pick the model to use for a turn. Returns the executor when the mode routes\n\t * that turn kind and the executor is available; otherwise returns the primary\n\t * model. `shadow-executor` always returns the primary for the live path (the\n\t * executor is exercised separately for measurement).\n\t */\n\tselectModel(turnKind: TurnKind, primary: Model<Api>): Model<Api> {\n\t\tif (!this.isExecutorAvailable() || !this.executor) return primary;\n\t\tswitch (this.mode) {\n\t\t\tcase \"executor-for-summarization\":\n\t\t\t\treturn turnKind === \"summarization\" ? this.executor : primary;\n\t\t\tcase \"executor-for-tool-results\":\n\t\t\t\treturn turnKind === \"tool-result\" ? this.executor : primary;\n\t\t\tcase \"shadow-executor\":\n\t\t\tcase \"primary-only\":\n\t\t\t\treturn primary;\n\t\t}\n\t}\n\n\t/** Whether a given tool's result should be compressed via the executor. */\n\tshouldCompressToolResult(toolName: string, contentBytes: number): boolean {\n\t\tif (this.mode !== \"executor-for-tool-results\") return false;\n\t\tif (!this.isExecutorAvailable()) return false;\n\t\tif (!COMPRESSIBLE_TOOLS.has(toolName)) return false;\n\t\treturn contentBytes >= this.toolResultMinBytes;\n\t}\n\n\tgetExecutorModel(): Model<Api> | undefined {\n\t\treturn this.executor;\n\t}\n}\n"]}
1
+ {"version":3,"file":"local-inference.js","sourceRoot":"","sources":["../../../src/core/routing/local-inference.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAcH,MAAM,CAAC,MAAM,aAAa,GAA2B;IACpD,cAAc;IACd,4BAA4B;IAC5B,2BAA2B;IAC3B,iBAAiB;CACR,CAAC;AAEX;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC;AACtC,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC;AAkCtC,SAAS,aAAa,CAAC,KAAc,EAAwB;IAC5D,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,aAAmC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAAA,CACzF;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAIlC,EAAe;IACf,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;IACrC,MAAM,YAAY,GAAG,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,cAAc,CAAC;IAC3F,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,KAAK,IAAI,IAAI,YAAY,CAAC;IAC3D,IAAI,CAAC,SAAS;QAAE,OAAO,cAAc,CAAC;IAEtC,IAAI,OAAO,IAAI,aAAa,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IACtD,IAAI,IAAI,CAAC,UAAU,IAAI,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC,UAAU,CAAC;IAC9E,OAAO,4BAA4B,CAAC;AAAA,CACpC;AAED;;;;GAIG;AACH,MAAM,OAAO,oBAAoB;IACf,IAAI,CAAc;IAClB,cAAc,CAA6B;IAC3C,QAAQ,CAAyB;IACjC,QAAQ,CAAS;IACjB,QAAQ,CAAS;IAElC,YACC,IAAiB,EACjB,cAA0C,EAC1C,QAAgC,EAC/B;QACD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACrC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,QAAQ,GAAG,cAAc,EAAE,QAAQ,IAAI,iBAAiB,CAAC;QAC9D,IAAI,CAAC,QAAQ,GAAG,cAAc,EAAE,QAAQ,IAAI,iBAAiB,CAAC;IAAA,CAC9D;IAED,MAAM,CAAC,MAAM,CAAC,IAIb,EAAwB;QACxB,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC;QAC7C,IAAI,QAAgC,CAAC;QACrC,IAAI,IAAI,CAAC,IAAI,KAAK,cAAc,IAAI,cAAc,EAAE,CAAC;YACpD,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;QAC9E,CAAC;QACD,OAAO,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,QAAQ,CAAC,CAAC;IAAA,CACrE;IAED,OAAO,GAAgB;QACtB,OAAO,IAAI,CAAC,IAAI,CAAC;IAAA,CACjB;IAED,gFAAgF;IAChF,mBAAmB,GAAY;QAC9B,OAAO,IAAI,CAAC,IAAI,KAAK,cAAc,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC;IAAA,CACnE;IAED,iBAAiB,GAA+B;QAC/C,OAAO,IAAI,CAAC,cAAc,CAAC;IAAA,CAC3B;IAED;;;;;OAKG;IACH,WAAW,CAAC,QAAkB,EAAE,OAAmB,EAAc;QAChE,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAO,OAAO,CAAC;QAClE,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,4BAA4B;gBAChC,OAAO,QAAQ,KAAK,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;YAC/D,KAAK,2BAA2B;gBAC/B,OAAO,QAAQ,KAAK,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;YAC7D,KAAK,iBAAiB,CAAC;YACvB,KAAK,cAAc;gBAClB,OAAO,OAAO,CAAC;QACjB,CAAC;IAAA,CACD;IAED,gFAAgF;IAChF,cAAc,CAAC,KAAa,EAAW;QACtC,OAAO,KAAK,IAAI,IAAI,CAAC,QAAQ,IAAI,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC;IAAA,CACxD;IAED,yDAAyD;IACzD,WAAW,GAA2C;QACrD,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;IAAA,CAC5D;IAED,2EAA2E;IAC3E,wBAAwB,CAAC,QAAgB,EAAE,YAAoB,EAAW;QACzE,IAAI,IAAI,CAAC,IAAI,KAAK,2BAA2B;YAAE,OAAO,KAAK,CAAC;QAC5D,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE;YAAE,OAAO,KAAK,CAAC;QAC9C,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;QACpD,OAAO,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IAAA,CACzC;IAED;;;;;OAKG;IACH,wBAAwB,CAAC,KAAa,EAAW;QAChD,IAAI,IAAI,CAAC,IAAI,KAAK,4BAA4B;YAAE,OAAO,KAAK,CAAC;QAC7D,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE;YAAE,OAAO,KAAK,CAAC;QAC9C,OAAO,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAAA,CAClC;IAED,gBAAgB,GAA2B;QAC1C,OAAO,IAAI,CAAC,QAAQ,CAAC;IAAA,CACrB;CACD","sourcesContent":["/**\n * Local-inference routing.\n *\n * Optional, opt-in routing of certain non-critical work (conversation\n * compaction, and large bash tool-result compression) to a local\n * \"executor\" model running on an OpenAI-compatible endpoint (for example an\n * MLX server), while the primary model handles all planning, reasoning, edits,\n * and tool-call synthesis.\n *\n * Everything here is INERT unless explicitly enabled via the\n * `--enable-local-inference` flag or the `HOOCODE_ROUTING_MODE` env var. On any\n * executor resolution/availability problem the caller falls back to the primary\n * model (compaction) or the raw tool result (tool-result compression). The\n * router never throws into the agent loop.\n *\n * Design and validation: see docs/local-executor-routing.md.\n */\n\nimport type { Api, Model } from \"@kolisachint/hoocode-ai\";\nimport type { ModelRegistry } from \"../model-registry.js\";\n\n/** Work that may be routed to the executor instead of the primary model. */\nexport type TurnKind = \"primary\" | \"summarization\" | \"tool-result\";\n\nexport type RoutingMode =\n\t| \"primary-only\"\n\t| \"executor-for-summarization\"\n\t| \"executor-for-tool-results\"\n\t| \"shadow-executor\";\n\nexport const ROUTING_MODES: readonly RoutingMode[] = [\n\t\"primary-only\",\n\t\"executor-for-summarization\",\n\t\"executor-for-tool-results\",\n\t\"shadow-executor\",\n] as const;\n\n/**\n * Tool names whose output is worth compressing (validated). Others pass\n * through. Only `bash` qualifies: its verbose output is mostly low-value noise\n * around a few load-bearing facts. `read` was measured to compress ~0% on real\n * source code (every line is a keep-line) and was removed. Fact-list tools\n * (grep/find/ls) were never compressible (every line is a distinct fact).\n */\nexport const COMPRESSIBLE_TOOLS = new Set([\"bash\"]);\n\n/**\n * Global size band (bytes) for local-inference routing. Applies to BOTH\n * tool-result compression and compaction summarization. Inputs below the\n * minimum are not worth offloading; inputs above the maximum are slow and risk\n * GPU OOM on small machines, so they fall back to the primary model. Tunable\n * per-machine via `minBytes`/`maxBytes` in the executor config block.\n */\nexport const DEFAULT_MIN_BYTES = 2048;\nexport const DEFAULT_MAX_BYTES = 8192;\n\n/** Optional local server the harness manages for the executor. */\nexport interface ExecutorServerConfig {\n\t/** Command to launch (default: \"mlx_lm.server\"). */\n\tcommand?: string;\n\t/** Extra args appended to the launch command. */\n\targs?: string[];\n\t/** Host to health-check and bind (default: derived from executor baseUrl or 127.0.0.1). */\n\thost?: string;\n\t/** Port to health-check and bind (default: derived from executor baseUrl or 8080). */\n\tport?: number;\n\t/** Max milliseconds to wait for the server to become healthy (default: 30000). */\n\tstartupTimeoutMs?: number;\n}\n\n/** Executor model reference as configured in models.json. */\nexport interface ExecutorConfig {\n\tprovider: string;\n\tmodel: string;\n\t/** Minimum input size (bytes) before local inference is attempted. */\n\tminBytes?: number;\n\t/** Maximum input size (bytes); larger inputs fall back to the primary model. */\n\tmaxBytes?: number;\n\t/** When set, the harness spawns/health-checks/stops this local server. */\n\tserver?: ExecutorServerConfig;\n}\n\n/** `routing` block in models.json. */\nexport interface RoutingConfig {\n\tmode?: RoutingMode;\n\texecutor?: ExecutorConfig;\n}\n\nfunction isRoutingMode(value: unknown): value is RoutingMode {\n\treturn typeof value === \"string\" && (ROUTING_MODES as readonly string[]).includes(value);\n}\n\n/**\n * Resolve the effective routing mode from CLI flag, env var, and config.\n *\n * Activation requires either the flag or the env var; config alone never\n * activates routing (decision: explicit opt-in only). When activated without an\n * explicit mode, defaults to `executor-for-summarization` (the lowest-risk\n * mode). When not activated, always `primary-only`.\n */\nexport function resolveRoutingMode(opts: {\n\tenableFlag?: boolean;\n\tenvMode?: string;\n\tconfigMode?: RoutingMode;\n}): RoutingMode {\n\tconst envMode = opts.envMode?.trim();\n\tconst envActivates = envMode !== undefined && envMode !== \"\" && envMode !== \"primary-only\";\n\tconst activated = opts.enableFlag === true || envActivates;\n\tif (!activated) return \"primary-only\";\n\n\tif (envMode && isRoutingMode(envMode)) return envMode;\n\tif (opts.configMode && isRoutingMode(opts.configMode)) return opts.configMode;\n\treturn \"executor-for-summarization\";\n}\n\n/**\n * Router that decides, per turn kind, whether to use the executor model and\n * resolves it from the registry. Holds no mutable state beyond the resolved\n * executor model.\n */\nexport class LocalInferenceRouter {\n\tprivate readonly mode: RoutingMode;\n\tprivate readonly executorConfig: ExecutorConfig | undefined;\n\tprivate readonly executor: Model<Api> | undefined;\n\tprivate readonly minBytes: number;\n\tprivate readonly maxBytes: number;\n\n\tprivate constructor(\n\t\tmode: RoutingMode,\n\t\texecutorConfig: ExecutorConfig | undefined,\n\t\texecutor: Model<Api> | undefined,\n\t) {\n\t\tthis.mode = mode;\n\t\tthis.executorConfig = executorConfig;\n\t\tthis.executor = executor;\n\t\tthis.minBytes = executorConfig?.minBytes ?? DEFAULT_MIN_BYTES;\n\t\tthis.maxBytes = executorConfig?.maxBytes ?? DEFAULT_MAX_BYTES;\n\t}\n\n\tstatic create(opts: {\n\t\tmode: RoutingMode;\n\t\tconfig: RoutingConfig | undefined;\n\t\tregistry: ModelRegistry;\n\t}): LocalInferenceRouter {\n\t\tconst executorConfig = opts.config?.executor;\n\t\tlet executor: Model<Api> | undefined;\n\t\tif (opts.mode !== \"primary-only\" && executorConfig) {\n\t\t\texecutor = opts.registry.find(executorConfig.provider, executorConfig.model);\n\t\t}\n\t\treturn new LocalInferenceRouter(opts.mode, executorConfig, executor);\n\t}\n\n\tgetMode(): RoutingMode {\n\t\treturn this.mode;\n\t}\n\n\t/** True when routing is active and an executor model is resolved and usable. */\n\tisExecutorAvailable(): boolean {\n\t\treturn this.mode !== \"primary-only\" && this.executor !== undefined;\n\t}\n\n\tgetExecutorConfig(): ExecutorConfig | undefined {\n\t\treturn this.executorConfig;\n\t}\n\n\t/**\n\t * Pick the model to use for a turn. Returns the executor when the mode routes\n\t * that turn kind and the executor is available; otherwise returns the primary\n\t * model. `shadow-executor` always returns the primary for the live path (the\n\t * executor is exercised separately for measurement).\n\t */\n\tselectModel(turnKind: TurnKind, primary: Model<Api>): Model<Api> {\n\t\tif (!this.isExecutorAvailable() || !this.executor) return primary;\n\t\tswitch (this.mode) {\n\t\t\tcase \"executor-for-summarization\":\n\t\t\t\treturn turnKind === \"summarization\" ? this.executor : primary;\n\t\t\tcase \"executor-for-tool-results\":\n\t\t\t\treturn turnKind === \"tool-result\" ? this.executor : primary;\n\t\t\tcase \"shadow-executor\":\n\t\t\tcase \"primary-only\":\n\t\t\t\treturn primary;\n\t\t}\n\t}\n\n\t/** True when an input size falls within the configured local-inference band. */\n\twithinSizeBand(bytes: number): boolean {\n\t\treturn bytes >= this.minBytes && bytes <= this.maxBytes;\n\t}\n\n\t/** The configured size band, for logging/diagnostics. */\n\tgetSizeBand(): { minBytes: number; maxBytes: number } {\n\t\treturn { minBytes: this.minBytes, maxBytes: this.maxBytes };\n\t}\n\n\t/** Whether a given tool's result should be compressed via the executor. */\n\tshouldCompressToolResult(toolName: string, contentBytes: number): boolean {\n\t\tif (this.mode !== \"executor-for-tool-results\") return false;\n\t\tif (!this.isExecutorAvailable()) return false;\n\t\tif (!COMPRESSIBLE_TOOLS.has(toolName)) return false;\n\t\treturn this.withinSizeBand(contentBytes);\n\t}\n\n\t/**\n\t * Whether to route summarization to the executor for a conversation of the\n\t * given serialized size. Requires summarization routing active, the executor\n\t * available, and the size within the band (oversized conversations fall back\n\t * to the primary to avoid slow local runs and GPU OOM).\n\t */\n\tshouldRouteSummarization(bytes: number): boolean {\n\t\tif (this.mode !== \"executor-for-summarization\") return false;\n\t\tif (!this.isExecutorAvailable()) return false;\n\t\treturn this.withinSizeBand(bytes);\n\t}\n\n\tgetExecutorModel(): Model<Api> | undefined {\n\t\treturn this.executor;\n\t}\n}\n"]}
@@ -2,13 +2,14 @@
2
2
  * Validated per-tool extractive compression prompts.
3
3
  *
4
4
  * These prompts were validated against Qwen3-4B (see
5
- * docs/local-executor-routing.md). Only `read` and `bash` outputs compress
6
- * safely (95%/94% size reduction at 100% critical-fact retention) because their
7
- * verbose output is mostly low-value noise around a few load-bearing facts.
8
- * Fact-list outputs (grep/find/ls) are intentionally excluded: every line is a
9
- * distinct fact, so compression drops matches.
5
+ * docs/local-executor-routing.md). Only `bash` output compresses safely: its
6
+ * verbose output is mostly low-value noise (progress/passing lines) around a
7
+ * few load-bearing facts (errors, counts, exit codes). `read` was removed after
8
+ * measurement showed ~0% reduction on real source code (every line is a
9
+ * keep-line). Fact-list outputs (grep/find/ls) are intentionally excluded:
10
+ * every line is a distinct fact, so compression drops matches.
10
11
  *
11
- * The prompts are extractive (keep identifiers, drop only redundant filler),
12
+ * The prompt is extractive (keep identifiers, drop only redundant filler),
12
13
  * not abstractive (rewrite in prose), which is what made retention reliable.
13
14
  */
14
15
  export declare const TOOL_RESULT_SYSTEM_PROMPT: string;
@@ -1 +1 @@
1
- {"version":3,"file":"tool-result-prompts.d.ts","sourceRoot":"","sources":["../../../src/core/routing/tool-result-prompts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,yBAAyB,QAEkD,CAAC;AAkBzF,0FAA0F;AAC1F,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExE;AAED,gEAAgE;AAChE,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI1F;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKnD","sourcesContent":["/**\n * Validated per-tool extractive compression prompts.\n *\n * These prompts were validated against Qwen3-4B (see\n * docs/local-executor-routing.md). Only `read` and `bash` outputs compress\n * safely (95%/94% size reduction at 100% critical-fact retention) because their\n * verbose output is mostly low-value noise around a few load-bearing facts.\n * Fact-list outputs (grep/find/ls) are intentionally excluded: every line is a\n * distinct fact, so compression drops matches.\n *\n * The prompts are extractive (keep identifiers, drop only redundant filler),\n * not abstractive (rewrite in prose), which is what made retention reliable.\n */\n\nexport const TOOL_RESULT_SYSTEM_PROMPT =\n\t\"You compress tool output for another AI to consume. Be extractive: keep exact identifiers, \" +\n\t\"never paraphrase facts, and never invent anything. Output only the compressed result.\";\n\nconst READ_PROMPT =\n\t\"Compress this file read. Output the path EXACTLY as given, then ONLY declaration lines as \" +\n\t\"`LINE: code` for: imports, class/function/const declarations WITH their values, throw/status-check \" +\n\t\"lines, and TODO comments. Drop plain function-body statements and blank/comment-only lines. Keep \" +\n\t\"every line number, identifier, string literal, and numeric value exactly. No prose.\";\n\nconst BASH_PROMPT =\n\t\"Compress this command output. Keep ONLY: the command, every error/warning with its file:line:col \" +\n\t\"and code, and any final counts/timings/exit code. Drop progress bars, info lines, and passing/OK \" +\n\t\"lines. Keep all numbers and paths exactly. No prose.\";\n\nconst TOOL_RESULT_PROMPTS: Record<string, string> = {\n\tread: READ_PROMPT,\n\tbash: BASH_PROMPT,\n};\n\n/** Get the extractive compression prompt for a tool, or undefined if not compressible. */\nexport function getToolResultPrompt(toolName: string): string | undefined {\n\treturn TOOL_RESULT_PROMPTS[toolName];\n}\n\n/** Build the full prompt text for compressing a tool result. */\nexport function buildToolResultPrompt(toolName: string, output: string): string | undefined {\n\tconst instruction = getToolResultPrompt(toolName);\n\tif (!instruction) return undefined;\n\treturn `${instruction}\\n\\n${output}`;\n}\n\n/**\n * Remove reasoning-model `<think>...</think>` blocks (including empty ones that\n * Qwen3 emits even under `/no_think`) and trim the result. Reasoning models\n * leave these tags in the text stream; they must not leak into context.\n */\nexport function stripThinkTags(text: string): string {\n\treturn text\n\t\t.replace(/<think>[\\s\\S]*?<\\/think>/g, \"\")\n\t\t.replace(/<\\/?think>/g, \"\")\n\t\t.trim();\n}\n"]}
1
+ {"version":3,"file":"tool-result-prompts.d.ts","sourceRoot":"","sources":["../../../src/core/routing/tool-result-prompts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,eAAO,MAAM,yBAAyB,QAEkD,CAAC;AAWzF,0FAA0F;AAC1F,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExE;AAED,gEAAgE;AAChE,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI1F;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKnD","sourcesContent":["/**\n * Validated per-tool extractive compression prompts.\n *\n * These prompts were validated against Qwen3-4B (see\n * docs/local-executor-routing.md). Only `bash` output compresses safely: its\n * verbose output is mostly low-value noise (progress/passing lines) around a\n * few load-bearing facts (errors, counts, exit codes). `read` was removed after\n * measurement showed ~0% reduction on real source code (every line is a\n * keep-line). Fact-list outputs (grep/find/ls) are intentionally excluded:\n * every line is a distinct fact, so compression drops matches.\n *\n * The prompt is extractive (keep identifiers, drop only redundant filler),\n * not abstractive (rewrite in prose), which is what made retention reliable.\n */\n\nexport const TOOL_RESULT_SYSTEM_PROMPT =\n\t\"You compress tool output for another AI to consume. Be extractive: keep exact identifiers, \" +\n\t\"never paraphrase facts, and never invent anything. Output only the compressed result.\";\n\nconst BASH_PROMPT =\n\t\"Compress this command output. Keep ONLY: the command, every error/warning with its file:line:col \" +\n\t\"and code, and any final counts/timings/exit code. Drop progress bars, info lines, and passing/OK \" +\n\t\"lines. Keep all numbers and paths exactly. No prose.\";\n\nconst TOOL_RESULT_PROMPTS: Record<string, string> = {\n\tbash: BASH_PROMPT,\n};\n\n/** Get the extractive compression prompt for a tool, or undefined if not compressible. */\nexport function getToolResultPrompt(toolName: string): string | undefined {\n\treturn TOOL_RESULT_PROMPTS[toolName];\n}\n\n/** Build the full prompt text for compressing a tool result. */\nexport function buildToolResultPrompt(toolName: string, output: string): string | undefined {\n\tconst instruction = getToolResultPrompt(toolName);\n\tif (!instruction) return undefined;\n\treturn `${instruction}\\n\\n${output}`;\n}\n\n/**\n * Remove reasoning-model `<think>...</think>` blocks (including empty ones that\n * Qwen3 emits even under `/no_think`) and trim the result. Reasoning models\n * leave these tags in the text stream; they must not leak into context.\n */\nexport function stripThinkTags(text: string): string {\n\treturn text\n\t\t.replace(/<think>[\\s\\S]*?<\\/think>/g, \"\")\n\t\t.replace(/<\\/?think>/g, \"\")\n\t\t.trim();\n}\n"]}
@@ -2,26 +2,22 @@
2
2
  * Validated per-tool extractive compression prompts.
3
3
  *
4
4
  * These prompts were validated against Qwen3-4B (see
5
- * docs/local-executor-routing.md). Only `read` and `bash` outputs compress
6
- * safely (95%/94% size reduction at 100% critical-fact retention) because their
7
- * verbose output is mostly low-value noise around a few load-bearing facts.
8
- * Fact-list outputs (grep/find/ls) are intentionally excluded: every line is a
9
- * distinct fact, so compression drops matches.
5
+ * docs/local-executor-routing.md). Only `bash` output compresses safely: its
6
+ * verbose output is mostly low-value noise (progress/passing lines) around a
7
+ * few load-bearing facts (errors, counts, exit codes). `read` was removed after
8
+ * measurement showed ~0% reduction on real source code (every line is a
9
+ * keep-line). Fact-list outputs (grep/find/ls) are intentionally excluded:
10
+ * every line is a distinct fact, so compression drops matches.
10
11
  *
11
- * The prompts are extractive (keep identifiers, drop only redundant filler),
12
+ * The prompt is extractive (keep identifiers, drop only redundant filler),
12
13
  * not abstractive (rewrite in prose), which is what made retention reliable.
13
14
  */
14
15
  export const TOOL_RESULT_SYSTEM_PROMPT = "You compress tool output for another AI to consume. Be extractive: keep exact identifiers, " +
15
16
  "never paraphrase facts, and never invent anything. Output only the compressed result.";
16
- const READ_PROMPT = "Compress this file read. Output the path EXACTLY as given, then ONLY declaration lines as " +
17
- "`LINE: code` for: imports, class/function/const declarations WITH their values, throw/status-check " +
18
- "lines, and TODO comments. Drop plain function-body statements and blank/comment-only lines. Keep " +
19
- "every line number, identifier, string literal, and numeric value exactly. No prose.";
20
17
  const BASH_PROMPT = "Compress this command output. Keep ONLY: the command, every error/warning with its file:line:col " +
21
18
  "and code, and any final counts/timings/exit code. Drop progress bars, info lines, and passing/OK " +
22
19
  "lines. Keep all numbers and paths exactly. No prose.";
23
20
  const TOOL_RESULT_PROMPTS = {
24
- read: READ_PROMPT,
25
21
  bash: BASH_PROMPT,
26
22
  };
27
23
  /** Get the extractive compression prompt for a tool, or undefined if not compressible. */
@@ -1 +1 @@
1
- {"version":3,"file":"tool-result-prompts.js","sourceRoot":"","sources":["../../../src/core/routing/tool-result-prompts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,CAAC,MAAM,yBAAyB,GACrC,6FAA6F;IAC7F,uFAAuF,CAAC;AAEzF,MAAM,WAAW,GAChB,4FAA4F;IAC5F,qGAAqG;IACrG,mGAAmG;IACnG,qFAAqF,CAAC;AAEvF,MAAM,WAAW,GAChB,mGAAmG;IACnG,mGAAmG;IACnG,sDAAsD,CAAC;AAExD,MAAM,mBAAmB,GAA2B;IACnD,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,WAAW;CACjB,CAAC;AAEF,0FAA0F;AAC1F,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAsB;IACzE,OAAO,mBAAmB,CAAC,QAAQ,CAAC,CAAC;AAAA,CACrC;AAED,gEAAgE;AAChE,MAAM,UAAU,qBAAqB,CAAC,QAAgB,EAAE,MAAc,EAAsB;IAC3F,MAAM,WAAW,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAClD,IAAI,CAAC,WAAW;QAAE,OAAO,SAAS,CAAC;IACnC,OAAO,GAAG,WAAW,OAAO,MAAM,EAAE,CAAC;AAAA,CACrC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAU;IACpD,OAAO,IAAI;SACT,OAAO,CAAC,2BAA2B,EAAE,EAAE,CAAC;SACxC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC;SAC1B,IAAI,EAAE,CAAC;AAAA,CACT","sourcesContent":["/**\n * Validated per-tool extractive compression prompts.\n *\n * These prompts were validated against Qwen3-4B (see\n * docs/local-executor-routing.md). Only `read` and `bash` outputs compress\n * safely (95%/94% size reduction at 100% critical-fact retention) because their\n * verbose output is mostly low-value noise around a few load-bearing facts.\n * Fact-list outputs (grep/find/ls) are intentionally excluded: every line is a\n * distinct fact, so compression drops matches.\n *\n * The prompts are extractive (keep identifiers, drop only redundant filler),\n * not abstractive (rewrite in prose), which is what made retention reliable.\n */\n\nexport const TOOL_RESULT_SYSTEM_PROMPT =\n\t\"You compress tool output for another AI to consume. Be extractive: keep exact identifiers, \" +\n\t\"never paraphrase facts, and never invent anything. Output only the compressed result.\";\n\nconst READ_PROMPT =\n\t\"Compress this file read. Output the path EXACTLY as given, then ONLY declaration lines as \" +\n\t\"`LINE: code` for: imports, class/function/const declarations WITH their values, throw/status-check \" +\n\t\"lines, and TODO comments. Drop plain function-body statements and blank/comment-only lines. Keep \" +\n\t\"every line number, identifier, string literal, and numeric value exactly. No prose.\";\n\nconst BASH_PROMPT =\n\t\"Compress this command output. Keep ONLY: the command, every error/warning with its file:line:col \" +\n\t\"and code, and any final counts/timings/exit code. Drop progress bars, info lines, and passing/OK \" +\n\t\"lines. Keep all numbers and paths exactly. No prose.\";\n\nconst TOOL_RESULT_PROMPTS: Record<string, string> = {\n\tread: READ_PROMPT,\n\tbash: BASH_PROMPT,\n};\n\n/** Get the extractive compression prompt for a tool, or undefined if not compressible. */\nexport function getToolResultPrompt(toolName: string): string | undefined {\n\treturn TOOL_RESULT_PROMPTS[toolName];\n}\n\n/** Build the full prompt text for compressing a tool result. */\nexport function buildToolResultPrompt(toolName: string, output: string): string | undefined {\n\tconst instruction = getToolResultPrompt(toolName);\n\tif (!instruction) return undefined;\n\treturn `${instruction}\\n\\n${output}`;\n}\n\n/**\n * Remove reasoning-model `<think>...</think>` blocks (including empty ones that\n * Qwen3 emits even under `/no_think`) and trim the result. Reasoning models\n * leave these tags in the text stream; they must not leak into context.\n */\nexport function stripThinkTags(text: string): string {\n\treturn text\n\t\t.replace(/<think>[\\s\\S]*?<\\/think>/g, \"\")\n\t\t.replace(/<\\/?think>/g, \"\")\n\t\t.trim();\n}\n"]}
1
+ {"version":3,"file":"tool-result-prompts.js","sourceRoot":"","sources":["../../../src/core/routing/tool-result-prompts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,CAAC,MAAM,yBAAyB,GACrC,6FAA6F;IAC7F,uFAAuF,CAAC;AAEzF,MAAM,WAAW,GAChB,mGAAmG;IACnG,mGAAmG;IACnG,sDAAsD,CAAC;AAExD,MAAM,mBAAmB,GAA2B;IACnD,IAAI,EAAE,WAAW;CACjB,CAAC;AAEF,0FAA0F;AAC1F,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAsB;IACzE,OAAO,mBAAmB,CAAC,QAAQ,CAAC,CAAC;AAAA,CACrC;AAED,gEAAgE;AAChE,MAAM,UAAU,qBAAqB,CAAC,QAAgB,EAAE,MAAc,EAAsB;IAC3F,MAAM,WAAW,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAClD,IAAI,CAAC,WAAW;QAAE,OAAO,SAAS,CAAC;IACnC,OAAO,GAAG,WAAW,OAAO,MAAM,EAAE,CAAC;AAAA,CACrC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAU;IACpD,OAAO,IAAI;SACT,OAAO,CAAC,2BAA2B,EAAE,EAAE,CAAC;SACxC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC;SAC1B,IAAI,EAAE,CAAC;AAAA,CACT","sourcesContent":["/**\n * Validated per-tool extractive compression prompts.\n *\n * These prompts were validated against Qwen3-4B (see\n * docs/local-executor-routing.md). Only `bash` output compresses safely: its\n * verbose output is mostly low-value noise (progress/passing lines) around a\n * few load-bearing facts (errors, counts, exit codes). `read` was removed after\n * measurement showed ~0% reduction on real source code (every line is a\n * keep-line). Fact-list outputs (grep/find/ls) are intentionally excluded:\n * every line is a distinct fact, so compression drops matches.\n *\n * The prompt is extractive (keep identifiers, drop only redundant filler),\n * not abstractive (rewrite in prose), which is what made retention reliable.\n */\n\nexport const TOOL_RESULT_SYSTEM_PROMPT =\n\t\"You compress tool output for another AI to consume. Be extractive: keep exact identifiers, \" +\n\t\"never paraphrase facts, and never invent anything. Output only the compressed result.\";\n\nconst BASH_PROMPT =\n\t\"Compress this command output. Keep ONLY: the command, every error/warning with its file:line:col \" +\n\t\"and code, and any final counts/timings/exit code. Drop progress bars, info lines, and passing/OK \" +\n\t\"lines. Keep all numbers and paths exactly. No prose.\";\n\nconst TOOL_RESULT_PROMPTS: Record<string, string> = {\n\tbash: BASH_PROMPT,\n};\n\n/** Get the extractive compression prompt for a tool, or undefined if not compressible. */\nexport function getToolResultPrompt(toolName: string): string | undefined {\n\treturn TOOL_RESULT_PROMPTS[toolName];\n}\n\n/** Build the full prompt text for compressing a tool result. */\nexport function buildToolResultPrompt(toolName: string, output: string): string | undefined {\n\tconst instruction = getToolResultPrompt(toolName);\n\tif (!instruction) return undefined;\n\treturn `${instruction}\\n\\n${output}`;\n}\n\n/**\n * Remove reasoning-model `<think>...</think>` blocks (including empty ones that\n * Qwen3 emits even under `/no_think`) and trim the result. Reasoning models\n * leave these tags in the text stream; they must not leak into context.\n */\nexport function stripThinkTags(text: string): string {\n\treturn text\n\t\t.replace(/<think>[\\s\\S]*?<\\/think>/g, \"\")\n\t\t.replace(/<\\/?think>/g, \"\")\n\t\t.trim();\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "0.2.68",
4
+ "version": "0.2.70",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "0.2.68",
4
+ "version": "0.2.70",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-sandbox",
3
3
  "private": true,
4
- "version": "0.2.68",
4
+ "version": "0.2.70",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-with-deps",
3
3
  "private": true,
4
- "version": "0.2.68",
4
+ "version": "0.2.70",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-agent",
3
- "version": "0.4.71",
3
+ "version": "0.4.73",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "hoocodeConfig": {
@@ -50,9 +50,9 @@
50
50
  "prepublishOnly": "npm run clean && npm run build"
51
51
  },
52
52
  "dependencies": {
53
- "@kolisachint/hoocode-agent-core": "^0.4.71",
54
- "@kolisachint/hoocode-ai": "^0.4.71",
55
- "@kolisachint/hoocode-tui": "^0.4.71",
53
+ "@kolisachint/hoocode-agent-core": "^0.4.73",
54
+ "@kolisachint/hoocode-ai": "^0.4.73",
55
+ "@kolisachint/hoocode-tui": "^0.4.73",
56
56
  "@silvia-odwyer/photon-node": "^0.3.4",
57
57
  "chalk": "^5.5.0",
58
58
  "cli-highlight": "^2.1.11",