agent-nuvira 2.6.14 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/cli/chat.d.ts +2 -0
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +107 -4
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/eval.d.ts.map +1 -1
- package/dist/cli/eval.js +14 -1
- package/dist/cli/eval.js.map +1 -1
- package/dist/cli/execute.d.ts +8 -0
- package/dist/cli/execute.d.ts.map +1 -1
- package/dist/cli/execute.js +105 -1
- package/dist/cli/execute.js.map +1 -1
- package/dist/cli/loop-executor.d.ts +81 -0
- package/dist/cli/loop-executor.d.ts.map +1 -0
- package/dist/cli/loop-executor.js +246 -0
- package/dist/cli/loop-executor.js.map +1 -0
- package/dist/config/types.d.ts +18 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/fresh.d.ts +2 -0
- package/dist/fresh.d.ts.map +1 -0
- package/dist/fresh.js +2 -0
- package/dist/fresh.js.map +1 -0
- package/dist/inference/anthropic-adapter.d.ts +9 -0
- package/dist/inference/anthropic-adapter.d.ts.map +1 -1
- package/dist/inference/anthropic-adapter.js +143 -0
- package/dist/inference/anthropic-adapter.js.map +1 -1
- package/dist/inference/gemini-adapter.d.ts +8 -0
- package/dist/inference/gemini-adapter.d.ts.map +1 -1
- package/dist/inference/gemini-adapter.js +145 -0
- package/dist/inference/gemini-adapter.js.map +1 -1
- package/dist/inference/model-probe.d.ts.map +1 -1
- package/dist/inference/model-probe.js +14 -0
- package/dist/inference/model-probe.js.map +1 -1
- package/dist/inference/native-tools.d.ts +226 -0
- package/dist/inference/native-tools.d.ts.map +1 -0
- package/dist/inference/native-tools.js +389 -0
- package/dist/inference/native-tools.js.map +1 -0
- package/dist/learning/auto-router.d.ts +36 -4
- package/dist/learning/auto-router.d.ts.map +1 -1
- package/dist/learning/auto-router.js +103 -2
- package/dist/learning/auto-router.js.map +1 -1
- package/dist/learning/context-pruner.d.ts.map +1 -1
- package/dist/learning/context-pruner.js +4 -2
- package/dist/learning/context-pruner.js.map +1 -1
- package/dist/learning/engine-router.d.ts +90 -0
- package/dist/learning/engine-router.d.ts.map +1 -0
- package/dist/learning/engine-router.js +128 -0
- package/dist/learning/engine-router.js.map +1 -0
- package/dist/learning/eval-framework.d.ts +42 -1
- package/dist/learning/eval-framework.d.ts.map +1 -1
- package/dist/learning/eval-framework.js +163 -7
- package/dist/learning/eval-framework.js.map +1 -1
- package/dist/learning/routing-cache.d.ts +64 -0
- package/dist/learning/routing-cache.d.ts.map +1 -0
- package/dist/learning/routing-cache.js +100 -0
- package/dist/learning/routing-cache.js.map +1 -0
- package/dist/tools/loop-project-context.d.ts +47 -0
- package/dist/tools/loop-project-context.d.ts.map +1 -0
- package/dist/tools/loop-project-context.js +210 -0
- package/dist/tools/loop-project-context.js.map +1 -0
- package/dist/tools/loop-skill-hint.d.ts +110 -0
- package/dist/tools/loop-skill-hint.d.ts.map +1 -0
- package/dist/tools/loop-skill-hint.js +266 -0
- package/dist/tools/loop-skill-hint.js.map +1 -0
- package/dist/tools/registry.d.ts +10 -0
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +66 -9
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/release-sync.d.ts.map +1 -1
- package/dist/tools/release-sync.js +5 -4
- package/dist/tools/release-sync.js.map +1 -1
- package/dist/tools/tool-loop.d.ts +28 -0
- package/dist/tools/tool-loop.d.ts.map +1 -1
- package/dist/tools/tool-loop.js +182 -6
- package/dist/tools/tool-loop.js.map +1 -1
- package/dist/tools/toolsets.d.ts +33 -0
- package/dist/tools/toolsets.d.ts.map +1 -1
- package/dist/tools/toolsets.js +84 -0
- package/dist/tools/toolsets.js.map +1 -1
- package/dist/web-dashboard/chat-console.d.ts +31 -0
- package/dist/web-dashboard/chat-console.d.ts.map +1 -1
- package/dist/web-dashboard/chat-console.js +73 -0
- package/dist/web-dashboard/chat-console.js.map +1 -1
- package/dist/web-dashboard/loop-turn-telemetry.d.ts +27 -0
- package/dist/web-dashboard/loop-turn-telemetry.d.ts.map +1 -0
- package/dist/web-dashboard/loop-turn-telemetry.js +43 -0
- package/dist/web-dashboard/loop-turn-telemetry.js.map +1 -0
- package/dist/web-dashboard/server.d.ts +62 -1
- package/dist/web-dashboard/server.d.ts.map +1 -1
- package/dist/web-dashboard/server.js +205 -4
- package/dist/web-dashboard/server.js.map +1 -1
- package/dist/web-dashboard/src/types.d.ts +44 -0
- package/dist/web-dashboard/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/web-dashboard/public/assets/{index-Cg9LaToa.js → index-C4frng1Q.js} +79 -79
- package/src/web-dashboard/public/assets/{index-Cg9LaToa.js.map → index-C4frng1Q.js.map} +1 -1
- package/src/web-dashboard/public/assets/index-C507EUWf.css +1 -0
- package/src/web-dashboard/public/index.html +2 -2
- package/src/web-dashboard/public/assets/index-B47v8pr1.css +0 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routing decision cache (`src/learning/routing-cache.ts`) — AGENTIC_CAPABILITY_ASSESSMENT
|
|
3
|
+
* Addendum v4 Phase 2.
|
|
4
|
+
*
|
|
5
|
+
* The loop-first engine calls the AutoModelRouter on EVERY model turn. Each
|
|
6
|
+
* resolve() scores 22+ catalog providers across 5 dimensions (+ capability
|
|
7
|
+
* fit, context preflight, bandit sampling, registry filtering) — meaningful
|
|
8
|
+
* CPU work repeated for turns whose ROUTING INPUTS are identical.
|
|
9
|
+
*
|
|
10
|
+
* The cache keys on the STABLE routing inputs only — never the raw message
|
|
11
|
+
* text (every message is unique, so a message-keyed cache would never hit).
|
|
12
|
+
* Two turns hit the same entry when all of the following match:
|
|
13
|
+
* agent type · task intent · complexity · preference mode · provider-health
|
|
14
|
+
* signature (session exclusions + breaker + quota) · registry usable-count
|
|
15
|
+
* · cacheable resolve-option flags.
|
|
16
|
+
*
|
|
17
|
+
* Correctness rules:
|
|
18
|
+
* 1. Provider health is part of the KEY, so a provider failing mid-session
|
|
19
|
+
* changes the key and can never be served a stale healthy-route.
|
|
20
|
+
* 2. TTL (default 30s) bounds staleness of everything NOT in the key
|
|
21
|
+
* (benchmarks recorded by concurrent runs, bandit draws).
|
|
22
|
+
* 3. Non-deterministic layers are opt-out at the call site: when the caller
|
|
23
|
+
* enables `useBandit` or `useMlRouter` with default settings, the cache
|
|
24
|
+
* is still safe for SELECTION (deterministic at cold start) but a caller
|
|
25
|
+
* may force-bypass via `bypass` for benchmark/explain flows that must
|
|
26
|
+
* observe the live ranking.
|
|
27
|
+
* 4. `invalidateRoutingCache()` is called by the model registry refresh and
|
|
28
|
+
* is safe to call anywhere — worst case one extra resolve.
|
|
29
|
+
*/
|
|
30
|
+
/** Module-level store — one cache per process (single-user CLI). */
|
|
31
|
+
const store = new Map();
|
|
32
|
+
/** Default TTL: short enough to bound staleness, long enough to cover a chat turn's repeated resolves. */
|
|
33
|
+
export const DEFAULT_ROUTING_CACHE_TTL_MS = 30_000;
|
|
34
|
+
/** Upper bound on entries — a pathological key-space cannot grow the map unbounded. */
|
|
35
|
+
const MAX_ENTRIES = 256;
|
|
36
|
+
/**
|
|
37
|
+
* Build the inputs signature from the STABLE routing inputs. Every part is
|
|
38
|
+
* stringified; null/undefined/'' parts are skipped (their absence IS signal —
|
|
39
|
+
* e.g. no NLU intent hint). Order-normalized: callers pass parts in a fixed
|
|
40
|
+
* order, but we join with '|' so collisions across part counts are visible.
|
|
41
|
+
*/
|
|
42
|
+
export function routingCacheSignature(parts) {
|
|
43
|
+
return parts
|
|
44
|
+
.map((p) => (p === null || p === undefined || p === '' ? '∅' : String(p)))
|
|
45
|
+
.join('|');
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get a cached routing decision. Returns undefined on miss/expiry.
|
|
49
|
+
* Expired entries are lazily dropped (no background sweeper — the CLI is
|
|
50
|
+
* single-user and resolve-frequency is bounded by turn count).
|
|
51
|
+
*/
|
|
52
|
+
export function getRoutingCache(signature) {
|
|
53
|
+
const entry = store.get(signature);
|
|
54
|
+
if (!entry)
|
|
55
|
+
return undefined;
|
|
56
|
+
if (Date.now() >= entry.expiresAt) {
|
|
57
|
+
store.delete(signature);
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
return entry.value;
|
|
61
|
+
}
|
|
62
|
+
/** Store a decision under a signature with the given TTL. */
|
|
63
|
+
export function setRoutingCache(signature, value, ttlMs = DEFAULT_ROUTING_CACHE_TTL_MS) {
|
|
64
|
+
// Bound the map: drop the EXPIRED entries first, then (rare) evict oldest.
|
|
65
|
+
if (store.size >= MAX_ENTRIES) {
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
for (const [k, v] of store) {
|
|
68
|
+
if (now >= v.expiresAt)
|
|
69
|
+
store.delete(k);
|
|
70
|
+
}
|
|
71
|
+
if (store.size >= MAX_ENTRIES) {
|
|
72
|
+
const oldest = store.keys().next().value;
|
|
73
|
+
if (oldest !== undefined)
|
|
74
|
+
store.delete(oldest);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
store.set(signature, { value, expiresAt: Date.now() + Math.max(0, ttlMs), signature });
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Memoize wrapper: return the cached decision for this signature, or compute
|
|
81
|
+
* + store. `compute` throwing propagates (never cache a failure — a transient
|
|
82
|
+
* registry read failure must not pin a degraded route for the TTL).
|
|
83
|
+
*/
|
|
84
|
+
export function withRoutingCache(signature, ttlMs, compute) {
|
|
85
|
+
const cached = getRoutingCache(signature);
|
|
86
|
+
if (cached !== undefined)
|
|
87
|
+
return cached;
|
|
88
|
+
const value = compute();
|
|
89
|
+
setRoutingCache(signature, value, ttlMs);
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
/** Clear the whole cache (registry refresh, tests, `nuvira models explain`). */
|
|
93
|
+
export function invalidateRoutingCache() {
|
|
94
|
+
store.clear();
|
|
95
|
+
}
|
|
96
|
+
/** Test/inspection helper: current entry count. */
|
|
97
|
+
export function routingCacheSize() {
|
|
98
|
+
return store.size;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=routing-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routing-cache.js","sourceRoot":"","sources":["../../src/learning/routing-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAUH,oEAAoE;AACpE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsC,CAAC;AAE5D,0GAA0G;AAC1G,MAAM,CAAC,MAAM,4BAA4B,GAAG,MAAM,CAAC;AAEnD,uFAAuF;AACvF,MAAM,WAAW,GAAG,GAAG,CAAC;AAExB;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAA0D;IAE1D,OAAO,KAAK;SACT,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;SACzE,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAI,SAAiB;IAClD,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAqC,CAAC;IACvE,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QAClC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACxB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC;AACrB,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,eAAe,CAAI,SAAiB,EAAE,KAAQ,EAAE,QAAgB,4BAA4B;IAC1G,2EAA2E;IAC3E,IAAI,KAAK,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC;YAC3B,IAAI,GAAG,IAAI,CAAC,CAAC,SAAS;gBAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC1C,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC;YAC9B,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YACzC,IAAI,MAAM,KAAK,SAAS;gBAAE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;AACzF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAC9B,SAAiB,EACjB,KAAyB,EACzB,OAAgB;IAEhB,MAAM,MAAM,GAAG,eAAe,CAAI,SAAS,CAAC,CAAC;IAC7C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IACxC,MAAM,KAAK,GAAG,OAAO,EAAE,CAAC;IACxB,eAAe,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IACzC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,sBAAsB;IACpC,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,gBAAgB;IAC9B,OAAO,KAAK,CAAC,IAAI,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loop project context (`src/tools/loop-project-context.ts`) — AGENTIC_CAPABILITY_ASSESSMENT
|
|
3
|
+
* Addendum v4 Phase 1.4: Freebuff-pattern AMBIENT CONTEXT for the tool loop.
|
|
4
|
+
*
|
|
5
|
+
* The assessment's core finding: a single-loop agent needs the project shape
|
|
6
|
+
* in its context FROM TURN ZERO — a token-budgeted file tree, a git-state
|
|
7
|
+
* digest, and a deterministic project assessment — instead of spending an
|
|
8
|
+
* entire ReasonerAgent + ContextGathererAgent call to re-discover it.
|
|
9
|
+
*
|
|
10
|
+
* The dashboard already builds a snapshot (its project-context module) and
|
|
11
|
+
* injects it via `ctxOverrides.projectContext`; the CLI never sent one. This
|
|
12
|
+
* module is the CLI twin: bounded (~2K tokens), best-effort — a failure
|
|
13
|
+
* yields an EMPTY string, never a broken turn.
|
|
14
|
+
*
|
|
15
|
+
* Consumers: chat.ts `runChatAnswer` — when no explicit projectContext was
|
|
16
|
+
* provided and the cwd looks like a project, inject this as the
|
|
17
|
+
* `[Project context]` message. The dashboard path is unchanged.
|
|
18
|
+
*
|
|
19
|
+
* Design notes:
|
|
20
|
+
* - The tree walk is a bounded BFS (readdirSync, depth-capped, ignore-dir
|
|
21
|
+
* filtered) rather than the orchestrator's full recursive builder — the
|
|
22
|
+
* loop needs SHAPE, not a complete index, and must never take >50ms on a
|
|
23
|
+
* huge repo. Entries past the line budget are truncated with an explicit
|
|
24
|
+
* note (honest truncation, like Freebuff's `truncate-file-tree`).
|
|
25
|
+
* - Git digests run read-only commands with a 3s cap each and are omitted
|
|
26
|
+
* entirely outside a git repo.
|
|
27
|
+
*/
|
|
28
|
+
/** True when the directory plausibly contains a project worth describing. */
|
|
29
|
+
export declare function looksLikeProject(dir: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Bounded BFS tree walk. Returns lines like `├── src/` with directories
|
|
32
|
+
* suffixed `/`, sorted dirs-first then files (deterministic ordering — the
|
|
33
|
+
* model sees a stable tree across turns, which keeps the prompt cache warm).
|
|
34
|
+
* Appends an ellipsis note when entries/depth were capped.
|
|
35
|
+
*/
|
|
36
|
+
export declare function walkBoundedTree(dir: string): string[];
|
|
37
|
+
/**
|
|
38
|
+
* Build the bounded `[Project context]` block for the loop system context.
|
|
39
|
+
* Returns '' when the directory is not a project (caller injects nothing).
|
|
40
|
+
*
|
|
41
|
+
* Layout (Freebuff system-prompt parity):
|
|
42
|
+
* ## Project — cwd + deterministic assessment (language/framework/tests)
|
|
43
|
+
* ## File tree — bounded BFS walk, honestly truncated
|
|
44
|
+
* ## Git state — branch, dirty files, recent commits
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildLoopProjectContext(dir: string): Promise<string>;
|
|
47
|
+
//# sourceMappingURL=loop-project-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop-project-context.d.ts","sourceRoot":"","sources":["../../src/tools/loop-project-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAyBH,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAgBrD;AAaD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAwCrD;AAED;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkE1E"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loop project context (`src/tools/loop-project-context.ts`) — AGENTIC_CAPABILITY_ASSESSMENT
|
|
3
|
+
* Addendum v4 Phase 1.4: Freebuff-pattern AMBIENT CONTEXT for the tool loop.
|
|
4
|
+
*
|
|
5
|
+
* The assessment's core finding: a single-loop agent needs the project shape
|
|
6
|
+
* in its context FROM TURN ZERO — a token-budgeted file tree, a git-state
|
|
7
|
+
* digest, and a deterministic project assessment — instead of spending an
|
|
8
|
+
* entire ReasonerAgent + ContextGathererAgent call to re-discover it.
|
|
9
|
+
*
|
|
10
|
+
* The dashboard already builds a snapshot (its project-context module) and
|
|
11
|
+
* injects it via `ctxOverrides.projectContext`; the CLI never sent one. This
|
|
12
|
+
* module is the CLI twin: bounded (~2K tokens), best-effort — a failure
|
|
13
|
+
* yields an EMPTY string, never a broken turn.
|
|
14
|
+
*
|
|
15
|
+
* Consumers: chat.ts `runChatAnswer` — when no explicit projectContext was
|
|
16
|
+
* provided and the cwd looks like a project, inject this as the
|
|
17
|
+
* `[Project context]` message. The dashboard path is unchanged.
|
|
18
|
+
*
|
|
19
|
+
* Design notes:
|
|
20
|
+
* - The tree walk is a bounded BFS (readdirSync, depth-capped, ignore-dir
|
|
21
|
+
* filtered) rather than the orchestrator's full recursive builder — the
|
|
22
|
+
* loop needs SHAPE, not a complete index, and must never take >50ms on a
|
|
23
|
+
* huge repo. Entries past the line budget are truncated with an explicit
|
|
24
|
+
* note (honest truncation, like Freebuff's `truncate-file-tree`).
|
|
25
|
+
* - Git digests run read-only commands with a 3s cap each and are omitted
|
|
26
|
+
* entirely outside a git repo.
|
|
27
|
+
*/
|
|
28
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
29
|
+
import { join } from 'node:path';
|
|
30
|
+
import { spawnSync } from 'node:child_process';
|
|
31
|
+
import { assessProject } from '../agents/prompt-assembly.js';
|
|
32
|
+
/** Hard budget: the tree block is truncated to this many lines (~1.5K tokens). */
|
|
33
|
+
const MAX_TREE_LINES = 60;
|
|
34
|
+
/** Max tree depth (BFS) — deep node_modules-style nesting is noise at this budget. */
|
|
35
|
+
const MAX_TREE_DEPTH = 4;
|
|
36
|
+
/** Max entries per directory (a flat 300-file dir gets an ellipsis note). */
|
|
37
|
+
const MAX_ENTRIES_PER_DIR = 25;
|
|
38
|
+
/** Git status porcelain lines kept (a huge dirty tree truncates). */
|
|
39
|
+
const MAX_GIT_STATUS_LINES = 30;
|
|
40
|
+
/** Overall block budget (~2K tokens ≈ 8K chars) — hard-truncated with a note. */
|
|
41
|
+
const MAX_BLOCK_CHARS = 8_000;
|
|
42
|
+
/** Directories never worth showing in the ambient tree (build artifacts, deps). */
|
|
43
|
+
const IGNORE_DIRS = new Set([
|
|
44
|
+
'node_modules', '.git', 'dist', 'build', '.next', 'out', 'coverage',
|
|
45
|
+
'.cache', '__pycache__', '.venv', 'venv', '.nuvira', '.turbo',
|
|
46
|
+
]);
|
|
47
|
+
/** True when the directory plausibly contains a project worth describing. */
|
|
48
|
+
export function looksLikeProject(dir) {
|
|
49
|
+
try {
|
|
50
|
+
if (!existsSync(dir) || !statSync(dir).isDirectory())
|
|
51
|
+
return false;
|
|
52
|
+
const entries = readdirSync(dir);
|
|
53
|
+
if (entries.length === 0)
|
|
54
|
+
return false;
|
|
55
|
+
// A project has at least one source/config marker OR a .git dir — a bare
|
|
56
|
+
// home directory or an empty scratch dir adds noise, not signal.
|
|
57
|
+
const MARKERS = new Set([
|
|
58
|
+
'package.json', 'tsconfig.json', 'pyproject.toml', 'setup.py', 'requirements.txt',
|
|
59
|
+
'Cargo.toml', 'go.mod', 'pom.xml', 'build.gradle', 'Gemfile', 'composer.json',
|
|
60
|
+
'.git', 'Makefile', 'CMakeLists.txt', 'pubspec.yaml', 'mix.exs', 'buffconfig.json',
|
|
61
|
+
]);
|
|
62
|
+
return entries.some((e) => MARKERS.has(e));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Run a read-only git command in `dir`; '' on any failure (best-effort). */
|
|
69
|
+
function git(dir, args) {
|
|
70
|
+
try {
|
|
71
|
+
const r = spawnSync('git', args, { cwd: dir, encoding: 'utf-8', timeout: 3_000 });
|
|
72
|
+
if (r.status !== 0 || !r.stdout)
|
|
73
|
+
return '';
|
|
74
|
+
return r.stdout.trim();
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return '';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Bounded BFS tree walk. Returns lines like `├── src/` with directories
|
|
82
|
+
* suffixed `/`, sorted dirs-first then files (deterministic ordering — the
|
|
83
|
+
* model sees a stable tree across turns, which keeps the prompt cache warm).
|
|
84
|
+
* Appends an ellipsis note when entries/depth were capped.
|
|
85
|
+
*/
|
|
86
|
+
export function walkBoundedTree(dir) {
|
|
87
|
+
const lines = [];
|
|
88
|
+
let truncatedNote = false;
|
|
89
|
+
const walk = (current, prefix, depth) => {
|
|
90
|
+
if (depth > MAX_TREE_DEPTH || lines.length >= MAX_TREE_LINES) {
|
|
91
|
+
if (lines.length >= MAX_TREE_LINES)
|
|
92
|
+
truncatedNote = true;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
let entries;
|
|
96
|
+
try {
|
|
97
|
+
entries = readdirSync(current, { withFileTypes: true })
|
|
98
|
+
.filter((e) => !IGNORE_DIRS.has(e.name) && !e.name.startsWith('.'))
|
|
99
|
+
.map((e) => ({ name: e.name, isDir: e.isDirectory() }))
|
|
100
|
+
.sort((a, b) => (a.isDir === b.isDir ? a.name.localeCompare(b.name) : a.isDir ? -1 : 1));
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return; // unreadable dir — skip silently
|
|
104
|
+
}
|
|
105
|
+
if (entries.length > MAX_ENTRIES_PER_DIR) {
|
|
106
|
+
entries = entries.slice(0, MAX_ENTRIES_PER_DIR);
|
|
107
|
+
truncatedNote = true;
|
|
108
|
+
}
|
|
109
|
+
entries.forEach((e, idx) => {
|
|
110
|
+
if (lines.length >= MAX_TREE_LINES) {
|
|
111
|
+
truncatedNote = true;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const last = idx === entries.length - 1;
|
|
115
|
+
const tee = last ? '└── ' : '├── ';
|
|
116
|
+
lines.push(`${prefix}${tee}${e.name}${e.isDir ? '/' : ''}`);
|
|
117
|
+
if (e.isDir) {
|
|
118
|
+
walk(join(current, e.name), prefix + (last ? ' ' : '│ '), depth + 1);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
walk(dir, '', 1);
|
|
123
|
+
if (truncatedNote)
|
|
124
|
+
lines.push('… (tree truncated to fit the context budget)');
|
|
125
|
+
return lines;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Build the bounded `[Project context]` block for the loop system context.
|
|
129
|
+
* Returns '' when the directory is not a project (caller injects nothing).
|
|
130
|
+
*
|
|
131
|
+
* Layout (Freebuff system-prompt parity):
|
|
132
|
+
* ## Project — cwd + deterministic assessment (language/framework/tests)
|
|
133
|
+
* ## File tree — bounded BFS walk, honestly truncated
|
|
134
|
+
* ## Git state — branch, dirty files, recent commits
|
|
135
|
+
*/
|
|
136
|
+
export async function buildLoopProjectContext(dir) {
|
|
137
|
+
try {
|
|
138
|
+
if (!looksLikeProject(dir))
|
|
139
|
+
return '';
|
|
140
|
+
const lines = [];
|
|
141
|
+
// ── Deterministic assessment (reuses the planner's own scanner) ──
|
|
142
|
+
try {
|
|
143
|
+
const a = assessProject(dir);
|
|
144
|
+
const bits = [];
|
|
145
|
+
if (a.language)
|
|
146
|
+
bits.push(`language: ${a.language}`);
|
|
147
|
+
if (a.framework)
|
|
148
|
+
bits.push(`framework: ${a.framework}`);
|
|
149
|
+
if (a.packageManager)
|
|
150
|
+
bits.push(`package manager: ${a.packageManager}`);
|
|
151
|
+
bits.push(a.isGreenfield ? 'empty/greenfield' : 'existing project');
|
|
152
|
+
bits.push(a.hasTests ? 'has tests' : 'no tests detected');
|
|
153
|
+
if (a.keyFiles?.length)
|
|
154
|
+
bits.push(`key files: ${a.keyFiles.slice(0, 8).join(', ')}`);
|
|
155
|
+
lines.push('## Project', `- path: ${dir}`, `- ${bits.join('; ')}`);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
lines.push('## Project', `- path: ${dir}`);
|
|
159
|
+
}
|
|
160
|
+
// ── Bounded file tree ──
|
|
161
|
+
try {
|
|
162
|
+
const treeLines = walkBoundedTree(dir);
|
|
163
|
+
if (treeLines.length > 0) {
|
|
164
|
+
lines.push('## File tree', ...treeLines);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// Tree failure must never break the block — omit the section.
|
|
169
|
+
}
|
|
170
|
+
// ── Git state digest (read-only, 3s cap per command) ──
|
|
171
|
+
try {
|
|
172
|
+
const branchLine = git(dir, ['status', '--porcelain', '-b']).split('\n')[0] || '';
|
|
173
|
+
const statusLines = git(dir, ['status', '--porcelain']).split('\n').filter(Boolean);
|
|
174
|
+
const log = git(dir, ['log', '--oneline', '-5']).split('\n').filter(Boolean);
|
|
175
|
+
if (branchLine) {
|
|
176
|
+
lines.push('## Git state');
|
|
177
|
+
lines.push(`- ${branchLine}`);
|
|
178
|
+
if (statusLines.length > 0) {
|
|
179
|
+
lines.push(`- ${statusLines.length} uncommitted change(s):`);
|
|
180
|
+
for (const s of statusLines.slice(0, MAX_GIT_STATUS_LINES))
|
|
181
|
+
lines.push(` ${s}`);
|
|
182
|
+
if (statusLines.length > MAX_GIT_STATUS_LINES) {
|
|
183
|
+
lines.push(` … ${statusLines.length - MAX_GIT_STATUS_LINES} more`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
lines.push('- working tree clean');
|
|
188
|
+
}
|
|
189
|
+
if (log.length > 0) {
|
|
190
|
+
lines.push('- recent commits:');
|
|
191
|
+
for (const c of log)
|
|
192
|
+
lines.push(` ${c}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Outside a git repo: no Git state section — fine.
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// Git digest is best-effort — omit on failure.
|
|
199
|
+
}
|
|
200
|
+
const body = lines.join('\n');
|
|
201
|
+
if (body.length > MAX_BLOCK_CHARS) {
|
|
202
|
+
return body.slice(0, MAX_BLOCK_CHARS) + '\n[project context truncated to fit the context budget]';
|
|
203
|
+
}
|
|
204
|
+
return body;
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return '';
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=loop-project-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop-project-context.js","sourceRoot":"","sources":["../../src/tools/loop-project-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loop skill hint (`src/tools/loop-skill-hint.ts`) — AGENTIC_CAPABILITY_ASSESSMENT
|
|
3
|
+
* Addendum v4 Phase 3.2: "The loop never hears about the orchestrator's skill
|
|
4
|
+
* layer: the pipeline consults SkillStore.findMatch + the hub catalog before
|
|
5
|
+
* planning and injects the matched methodology, but a chat/execute-loop goal
|
|
6
|
+
* starts with zero knowledge that a first-party playbook exists."
|
|
7
|
+
*
|
|
8
|
+
* This module closes that parity gap DETERMINISTICALLY (no LLM call): given
|
|
9
|
+
* the user's goal, it finds the best available skill the SAME way the
|
|
10
|
+
* orchestrator does —
|
|
11
|
+
*
|
|
12
|
+
* 1. compiled SkillStore.findMatch (the first-party capability batch),
|
|
13
|
+
* 2. the hub catalog (findHubSkillMatch — installed SKILL.md skills),
|
|
14
|
+
*
|
|
15
|
+
* then returns a system-prompt block that hands the model the skill's
|
|
16
|
+
* methodology with an EXPLICIT escape hatch ("this is a recommendation —
|
|
17
|
+
* ignore it when it does not fit") and the exact load syntax
|
|
18
|
+
* (`skill` tool, {"skill":"<name>"}).
|
|
19
|
+
*
|
|
20
|
+
* Why an evidence filter on top of findMatch: the compiled store's threshold
|
|
21
|
+
* is intentionally low (score >= 1, "manual discovery") and its scoring adds
|
|
22
|
+
* a quality/usage bonus to EVERY skill — so a goal merely containing a generic
|
|
23
|
+
* pattern word ("goal", "task") can false-positive. The chat loop runs on
|
|
24
|
+
* EVERY message (not just pipeline goals), so the loop hint requires REAL
|
|
25
|
+
* goal evidence: a name-word or tag hit, or two pattern-word hits. The
|
|
26
|
+
* orchestrator does not need this (its planner only sees pipeline goals); the
|
|
27
|
+
* hub catalog's own scoring is keyword-based and needs no filter.
|
|
28
|
+
*
|
|
29
|
+
* Safety rails (mirroring the orchestrator's injection contract):
|
|
30
|
+
* - skills.disabled[] gate — a dashboard/CLI-disabled skill is NEVER
|
|
31
|
+
* injected (the same "the toggle is never cosmetic" rule the match gates
|
|
32
|
+
* enforce elsewhere).
|
|
33
|
+
* - Website-deploy activation gate — the orchestrator requires
|
|
34
|
+
* hosting-specific intent before injecting website methodology; the loop
|
|
35
|
+
* hint applies the identical regex so "deploy the API" does not drag in
|
|
36
|
+
* static-site deployment steps.
|
|
37
|
+
* - Side-effect-free matching: the match itself marks nothing; usage is
|
|
38
|
+
* marked ONCE via markLoopSkillUsed by the caller (skillView()'s internal
|
|
39
|
+
* markUsed is deliberately avoided so the hint builder is idempotent).
|
|
40
|
+
* - Bounded injection: at most ONE methodology block per prompt, and the
|
|
41
|
+
* methodology text itself is capped (compiled: 8 steps; hub: 2000 chars)
|
|
42
|
+
* so a crowded catalog cannot balloon the system prompt.
|
|
43
|
+
* - Best-effort by construction: any store/catalog failure returns '' /
|
|
44
|
+
* null and the turn proceeds exactly as before (a hint must never break
|
|
45
|
+
* a turn).
|
|
46
|
+
*
|
|
47
|
+
* Consumers: chat's runChatAnswer (system prompt) and the execute loop's
|
|
48
|
+
* runLoopExecutor — the two runToolLoop callers that had no skill knowledge.
|
|
49
|
+
*/
|
|
50
|
+
import type { ConfigManager } from '../config/manager.js';
|
|
51
|
+
/**
|
|
52
|
+
* The match the hint was built from (echoed to callers for telemetry/tests).
|
|
53
|
+
* `null` = no match (no skill scored, or it was gated out).
|
|
54
|
+
*/
|
|
55
|
+
export interface LoopSkillHintMatch {
|
|
56
|
+
name: string;
|
|
57
|
+
id: string;
|
|
58
|
+
source: 'compiled' | 'hub';
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Did the goal show REAL evidence for this skill — a name-word hit, a tag
|
|
62
|
+
* hit, or two pattern-word hits (meta-words excluded)? Guards the compiled
|
|
63
|
+
* store's intentionally loose threshold: findMatch adds a quality/usage bonus
|
|
64
|
+
* to every skill, so a generic word like "goal" alone must never inject
|
|
65
|
+
* methodology into a chat turn. Deterministic, no LLM.
|
|
66
|
+
*/
|
|
67
|
+
export declare function hasRealGoalEvidence(goal: string, skill: {
|
|
68
|
+
name: string;
|
|
69
|
+
tags: string[];
|
|
70
|
+
goalPattern: string;
|
|
71
|
+
}): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Find the best skill for the goal across BOTH sources the orchestrator
|
|
74
|
+
* consults, honoring the disabled + website-deploy activation gates (+ the
|
|
75
|
+
* compiled evidence filter). Compiled wins ties (its id is the deterministic
|
|
76
|
+
* seed id). Returns null when nothing matches (never a forced match).
|
|
77
|
+
*/
|
|
78
|
+
export declare function findLoopSkillMatch(goal: string, cm?: ConfigManager): Promise<LoopSkillHintMatch | null>;
|
|
79
|
+
/**
|
|
80
|
+
* Build the system-prompt block for a matched skill, or '' when there is no
|
|
81
|
+
* match. The block follows the orchestrator's injection contract:
|
|
82
|
+
*
|
|
83
|
+
* - the skill is a RECOMMENDATION, the model still owns the plan (the
|
|
84
|
+
* orchestrator's "model-selected activation" phrasing),
|
|
85
|
+
* - the methodology rides in as Level-2 content (progressive disclosure —
|
|
86
|
+
* the model sees the steps without paying a tool call for them),
|
|
87
|
+
* - the exact load syntax is included so the model can refresh/parameterize
|
|
88
|
+
* via the `skill` tool mid-turn,
|
|
89
|
+
* - ONE block max (bounded system-prompt growth).
|
|
90
|
+
*
|
|
91
|
+
* Marks nothing: usage tracking belongs to markLoopSkillUsed (the caller
|
|
92
|
+
* decides when a match actually got USED — i.e. was injected).
|
|
93
|
+
*
|
|
94
|
+
* @param goal the user's goal text (matched against skill triggers)
|
|
95
|
+
* @param cm ConfigManager for the disabled-skills gate (optional)
|
|
96
|
+
* @param injected out-param: when provided, receives the match that was
|
|
97
|
+
* injected (null when none).
|
|
98
|
+
*/
|
|
99
|
+
export declare function buildLoopSkillHint(goal: string, cm?: ConfigManager, injected?: {
|
|
100
|
+
value: LoopSkillHintMatch | null;
|
|
101
|
+
}): Promise<string>;
|
|
102
|
+
/**
|
|
103
|
+
* Mark an injected skill as used (usage tracking parity with the orchestrator
|
|
104
|
+
* — compiled skills only; hub skills have no compiled usage counter). The
|
|
105
|
+
* SINGLE usage marker for the loop hint path (the hint builder never marks).
|
|
106
|
+
* Best-effort, fire-and-forget: never throws, never awaited by callers on the
|
|
107
|
+
* hot path.
|
|
108
|
+
*/
|
|
109
|
+
export declare function markLoopSkillUsed(match: LoopSkillHintMatch | null): Promise<void>;
|
|
110
|
+
//# sourceMappingURL=loop-skill-hint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop-skill-hint.d.ts","sourceRoot":"","sources":["../../src/tools/loop-skill-hint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAG1D;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,UAAU,GAAG,KAAK,CAAC;CAC5B;AA0CD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAC3D,OAAO,CAsBT;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,EAAE,CAAC,EAAE,aAAa,GACjB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAmCpC;AAmCD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,EAAE,CAAC,EAAE,aAAa,EAClB,QAAQ,CAAC,EAAE;IAAE,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAAA;CAAE,GAC9C,OAAO,CAAC,MAAM,CAAC,CAsCjB;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAQvF"}
|