@miller-tech/uap 1.87.0 → 1.88.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/cli/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE/F;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,MAAM,CAO3E;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AASD;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BnF"}
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/cli/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG/F;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,MAAM,CAO3E;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AASD;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuCnF"}
package/dist/cli/react.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { resolve } from '../coordination/reactor.js';
2
+ import { maybeWriteRecipeSignal } from '../coordination/recipe-signal.js';
2
3
  /**
3
4
  * Pure core of `uap react`: parse a JSON ReactorContext payload, resolve the
4
5
  * dynamic UAP capabilities for the event, and return the JSON ReactorResult.
@@ -59,5 +60,14 @@ export async function reactCommand(options = {}) {
59
60
  if (options.maxInjectChars)
60
61
  opts.maxInjectChars = Number(options.maxInjectChars);
61
62
  process.stdout.write(runReact(payload, opts) + '\n');
63
+ // Cross-process: stamp the recipe signal for this prompt so the serving-layer
64
+ // proxy can route on the reactor's actual capability/complexity. Best-effort.
65
+ try {
66
+ const promptText = JSON.parse(payload).promptText ?? '';
67
+ maybeWriteRecipeSignal(promptText);
68
+ }
69
+ catch {
70
+ /* fail open */
71
+ }
62
72
  }
63
73
  //# sourceMappingURL=react.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","sourceRoot":"","sources":["../../src/cli/react.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"react.js","sourceRoot":"","sources":["../../src/cli/react.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { type CapabilityRouter } from './capability-router.js';
2
+ export interface RecipeSignal {
3
+ ts: number;
4
+ promptHash: string;
5
+ complexity: string;
6
+ capabilities: string[];
7
+ shape: string;
8
+ recipe: string;
9
+ confidence: number;
10
+ }
11
+ export declare function normalizePrompt(text: string): string;
12
+ export declare function promptHash(text: string): string;
13
+ export declare function computeRecipeSignal(promptText: string, router?: CapabilityRouter): RecipeSignal;
14
+ export declare function signalDir(dir?: string): string;
15
+ export declare function writeRecipeSignal(sig: RecipeSignal, dir?: string): void;
16
+ /** Best-effort: compute + write the signal for a prompt. Never throws. */
17
+ export declare function maybeWriteRecipeSignal(promptText: string, dir?: string): void;
18
+ //# sourceMappingURL=recipe-signal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipe-signal.d.ts","sourceRoot":"","sources":["../../src/coordination/recipe-signal.ts"],"names":[],"mappings":"AAYA,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAQpF,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,gBAAwC,GAC/C,YAAY,CAgCd;AAED,wBAAgB,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAUvE;AAED,0EAA0E;AAC1E,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAO7E"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Recipe signal (cross-process): the reactor writes per-prompt routing signals
3
+ * that the serving-layer proxy consumes for recipe selection. Keyed by a hash
4
+ * of the normalized prompt (identical algorithm to the Python proxy), plus a
5
+ * rolling latest.json for the single-session common case. Fails open.
6
+ */
7
+ import { createHash } from 'crypto';
8
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
9
+ import { homedir } from 'os';
10
+ import { join } from 'path';
11
+ import { measureQueryComplexity } from '../utils/query-complexity.js';
12
+ import { getCapabilityRouter } from './capability-router.js';
13
+ const CODE_CAPS = new Set([
14
+ 'typescript', 'javascript', 'python', 'rust', 'go', 'cpp', 'java',
15
+ 'frontend', 'backend', 'cli', 'api-design',
16
+ ]);
17
+ const REASONING_CAPS = new Set(['architecture', 'product', 'test-strategy', 'compliance']);
18
+ export function normalizePrompt(text) {
19
+ return (text || '').trim().toLowerCase().replace(/\s+/g, ' ');
20
+ }
21
+ export function promptHash(text) {
22
+ return createHash('sha1').update(normalizePrompt(text)).digest('hex');
23
+ }
24
+ export function computeRecipeSignal(promptText, router = getCapabilityRouter()) {
25
+ const complexity = measureQueryComplexity(promptText); // simple | moderate | complex
26
+ let capabilities = [];
27
+ let confidence = 0;
28
+ try {
29
+ const r = router.routeTask({
30
+ id: 'react-signal', title: promptText, type: 'task', status: 'open',
31
+ priority: 2, labels: [], createdAt: '1970-01-01T00:00:00.000Z',
32
+ updatedAt: '1970-01-01T00:00:00.000Z',
33
+ });
34
+ capabilities = r.matchedCapabilities ?? [];
35
+ confidence = r.confidence ?? 0;
36
+ }
37
+ catch {
38
+ /* fall back to text-only shape */
39
+ }
40
+ const shape = capabilities.some((c) => REASONING_CAPS.has(c))
41
+ ? 'reasoning'
42
+ : capabilities.some((c) => CODE_CAPS.has(c))
43
+ ? 'code'
44
+ : /\b(prove|why|explain|derive|which of|true or false)\b/i.test(promptText)
45
+ ? 'reasoning'
46
+ : 'general';
47
+ const recipe = complexity === 'complex' || shape === 'reasoning' ? 'fusion' : 'confidence';
48
+ return {
49
+ ts: Date.now() / 1000,
50
+ promptHash: promptHash(promptText),
51
+ complexity,
52
+ capabilities,
53
+ shape,
54
+ recipe,
55
+ confidence,
56
+ };
57
+ }
58
+ export function signalDir(dir) {
59
+ return dir || process.env.UAP_RECIPE_SIGNAL_DIR || join(homedir(), '.cache', 'uap', 'recipe-signals');
60
+ }
61
+ export function writeRecipeSignal(sig, dir) {
62
+ try {
63
+ const d = signalDir(dir);
64
+ if (!existsSync(d))
65
+ mkdirSync(d, { recursive: true });
66
+ const data = JSON.stringify(sig);
67
+ writeFileSync(join(d, `${sig.promptHash}.json`), data);
68
+ writeFileSync(join(d, 'latest.json'), data);
69
+ }
70
+ catch {
71
+ /* fail open — the proxy falls back to its own signal extraction */
72
+ }
73
+ }
74
+ /** Best-effort: compute + write the signal for a prompt. Never throws. */
75
+ export function maybeWriteRecipeSignal(promptText, dir) {
76
+ if (!promptText || !promptText.trim())
77
+ return;
78
+ try {
79
+ writeRecipeSignal(computeRecipeSignal(promptText), dir);
80
+ }
81
+ catch {
82
+ /* fail open */
83
+ }
84
+ }
85
+ //# sourceMappingURL=recipe-signal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipe-signal.js","sourceRoot":"","sources":["../../src/coordination/recipe-signal.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miller-tech/uap",
3
- "version": "1.87.0",
3
+ "version": "1.88.0",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -27,8 +27,11 @@ Env:
27
27
  from __future__ import annotations
28
28
 
29
29
  import asyncio
30
+ import hashlib
31
+ import json
30
32
  import os
31
33
  import re
34
+ import time
32
35
  from dataclasses import dataclass
33
36
 
34
37
 
@@ -175,20 +178,66 @@ def task_signals(anthropic_body: dict) -> dict:
175
178
  return {"complexity": query_complexity(text), "shape": task_shape(text)}
176
179
 
177
180
 
181
+ # ---- cross-process: consume the harness reactor's actual routeResult ----
182
+ # The reactor (TS, in the harness) writes a per-prompt signal keyed by a hash of
183
+ # the normalized prompt, plus a rolling latest.json. The proxy reads it here so
184
+ # recipe selection uses the reactor's real capability/complexity routing rather
185
+ # than re-deriving it. Falls back to the proxy's own signal extraction on miss.
186
+ def _default_signal_dir() -> str:
187
+ return os.environ.get("UAP_RECIPE_SIGNAL_DIR") or os.path.join(
188
+ os.path.expanduser("~"), ".cache", "uap", "recipe-signals"
189
+ )
190
+
191
+
192
+ def normalize_prompt(text: str) -> str:
193
+ return " ".join((text or "").strip().lower().split())
194
+
195
+
196
+ def prompt_hash(text: str) -> str:
197
+ return hashlib.sha1(normalize_prompt(text).encode("utf-8")).hexdigest()
198
+
199
+
200
+ def load_reactor_signal(prompt_text, signal_dir=None, ttl=180.0, now=None):
201
+ """Return the reactor's signal dict for this prompt, or None. Exact
202
+ prompt-hash match first; then a recent latest.json fallback (single-session
203
+ common case). Fresh within ttl seconds. Never raises."""
204
+ d = signal_dir or _default_signal_dir()
205
+ now = time.time() if now is None else now
206
+ for path in (os.path.join(d, prompt_hash(prompt_text) + ".json"), os.path.join(d, "latest.json")):
207
+ try:
208
+ if os.path.exists(path):
209
+ with open(path) as f:
210
+ sig = json.load(f)
211
+ if now - float(sig.get("ts", 0)) <= ttl:
212
+ return sig
213
+ except Exception:
214
+ continue
215
+ return None
216
+
217
+
178
218
  def select_recipe(anthropic_body: dict, settings: Settings, has_tools: bool) -> str:
179
219
  if not settings.enabled or has_tools:
180
220
  return "single"
181
221
  if settings.recipe != "auto":
182
222
  return settings.recipe
183
- # Task-shaped auto-selection (the article's lesson: best loop is task-shaped).
184
- sig = task_signals(anthropic_body)
185
- if settings.backend_configured() and (
186
- sig["complexity"] == "complex" or sig["shape"] == "reasoning"
187
- ):
188
- # High reasoning variance / disagreement-prone -> breadth + judge.
223
+ text = latest_user_text(anthropic_body)
224
+ # Prefer the harness reactor's ACTUAL routeResult signal when fresh; else the
225
+ # proxy's own signal extraction (faithful port of query-complexity).
226
+ rsig = load_reactor_signal(text)
227
+ if rsig is not None:
228
+ rec = rsig.get("recipe")
229
+ if rec in {"single", "confidence", "fusion"}:
230
+ if rec == "fusion" and not settings.backend_configured():
231
+ return "confidence"
232
+ return rec
233
+ complexity = rsig.get("complexity") or query_complexity(text)
234
+ shape = rsig.get("shape") or task_shape(text)
235
+ else:
236
+ complexity = query_complexity(text)
237
+ shape = task_shape(text)
238
+ if settings.backend_configured() and (complexity == "complex" or shape == "reasoning"):
189
239
  return "fusion"
190
- # Long-prompt fallback trigger (tunable) keeps the old escape hatch.
191
- if settings.backend_configured() and len(latest_user_text(anthropic_body)) >= settings.auto_fusion_chars:
240
+ if settings.backend_configured() and len(text) >= settings.auto_fusion_chars:
192
241
  return "fusion"
193
242
  return "confidence"
194
243
 
@@ -9,6 +9,8 @@ mod_path = Path(__file__).resolve().parents[3] / "tools" / "agents" / "scripts"
9
9
  spec = importlib.util.spec_from_file_location("confidence_escalation", mod_path)
10
10
  ce = importlib.util.module_from_spec(spec)
11
11
  sys.modules["confidence_escalation"] = ce
12
+ import tempfile, os as _os2
13
+ _os2.environ["UAP_RECIPE_SIGNAL_DIR"] = tempfile.mkdtemp(prefix="uap-sig-")
12
14
  spec.loader.exec_module(ce)
13
15
 
14
16
 
@@ -133,5 +135,50 @@ class ApplyFusionTest(unittest.TestCase): # #3
133
135
  self.assertEqual(ce.extract_text(out), "c0")
134
136
 
135
137
 
138
+
139
+ class CrossProcessSignalTest(unittest.TestCase): # reactor -> proxy
140
+ def setUp(self):
141
+ import tempfile
142
+ self.d = tempfile.mkdtemp(prefix="uap-sig-test-")
143
+
144
+ def _write(self, name, sig):
145
+ import json, os
146
+ with open(os.path.join(self.d, name), "w") as f:
147
+ json.dump(sig, f)
148
+
149
+ def test_prompt_hash_match_consumed(self):
150
+ text = "build the thing"
151
+ h = ce.prompt_hash(text)
152
+ self._write(h + ".json", {"ts": __import__("time").time(), "recipe": "fusion"})
153
+ sig = ce.load_reactor_signal(text, signal_dir=self.d)
154
+ self.assertEqual(sig["recipe"], "fusion")
155
+
156
+ def test_stale_signal_ignored(self):
157
+ text = "old prompt"
158
+ self._write(ce.prompt_hash(text) + ".json", {"ts": 1, "recipe": "fusion"})
159
+ self.assertIsNone(ce.load_reactor_signal(text, signal_dir=self.d, ttl=180.0))
160
+
161
+ def test_latest_fallback(self):
162
+ self._write("latest.json", {"ts": __import__("time").time(), "recipe": "fusion"})
163
+ sig = ce.load_reactor_signal("anything not hashed", signal_dir=self.d)
164
+ self.assertEqual(sig["recipe"], "fusion")
165
+
166
+ def test_select_recipe_uses_reactor_recommendation(self):
167
+ text = "add a button" # self-extract would say confidence (simple)
168
+ self._write(ce.prompt_hash(text) + ".json",
169
+ {"ts": __import__("time").time(), "recipe": "fusion"})
170
+ import os
171
+ prev = os.environ.get("UAP_RECIPE_SIGNAL_DIR")
172
+ os.environ["UAP_RECIPE_SIGNAL_DIR"] = self.d
173
+ try:
174
+ r = ce.select_recipe(body(text), S(recipe="auto"), False)
175
+ finally:
176
+ if prev is None:
177
+ os.environ.pop("UAP_RECIPE_SIGNAL_DIR", None)
178
+ else:
179
+ os.environ["UAP_RECIPE_SIGNAL_DIR"] = prev
180
+ self.assertEqual(r, "fusion") # reactor signal overrides the simple self-classification
181
+
182
+
136
183
  if __name__ == "__main__":
137
184
  unittest.main()