@cruxy/cli 0.16.0 → 0.18.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/dist/agent/loop.d.ts +15 -0
- package/dist/agent/loop.js +13 -2
- package/dist/agent/prompts.d.ts +7 -0
- package/dist/agent/prompts.js +6 -0
- package/dist/agent/session.d.ts +14 -0
- package/dist/agent/session.js +10 -1
- package/dist/brand/index.d.ts +1 -1
- package/dist/brand/index.js +1 -1
- package/dist/brand/voice.d.ts +20 -0
- package/dist/brand/voice.js +54 -0
- package/dist/cli/commands/memory.d.ts +8 -0
- package/dist/cli/commands/memory.js +98 -0
- package/dist/cli/commands/pr.js +9 -1
- package/dist/cli/program.js +2 -0
- package/dist/cli/session-factory.js +31 -1
- package/dist/config/schema.d.ts +114 -28
- package/dist/config/schema.js +38 -0
- package/dist/constants.d.ts +11 -0
- package/dist/constants.js +11 -0
- package/dist/errors/constructors.d.ts +23 -0
- package/dist/errors/constructors.js +86 -6
- package/dist/errors/types.d.ts +12 -0
- package/dist/errors/types.js +20 -0
- package/dist/hooks/types.d.ts +1 -1
- package/dist/memory/index.d.ts +7 -0
- package/dist/memory/index.js +7 -0
- package/dist/memory/recall.d.ts +32 -0
- package/dist/memory/recall.js +73 -0
- package/dist/memory/remember-tool.d.ts +25 -0
- package/dist/memory/remember-tool.js +56 -0
- package/dist/memory/secrets.d.ts +29 -0
- package/dist/memory/secrets.js +61 -0
- package/dist/memory/service.d.ts +92 -0
- package/dist/memory/service.js +164 -0
- package/dist/memory/store.d.ts +32 -0
- package/dist/memory/store.js +100 -0
- package/dist/memory/trust.d.ts +52 -0
- package/dist/memory/trust.js +106 -0
- package/dist/memory/types.d.ts +101 -0
- package/dist/memory/types.js +58 -0
- package/dist/plan/service.d.ts +9 -0
- package/dist/plan/service.js +6 -0
- package/dist/render/state.js +4 -1
- package/dist/render/types.d.ts +7 -1
- package/dist/routing/index.d.ts +2 -0
- package/dist/routing/index.js +5 -0
- package/dist/routing/resolve.d.ts +17 -0
- package/dist/routing/resolve.js +18 -0
- package/dist/routing/router.d.ts +47 -0
- package/dist/routing/router.js +84 -0
- package/dist/routing/types.d.ts +42 -0
- package/dist/routing/types.js +27 -0
- package/dist/subagent/orchestrator.d.ts +6 -0
- package/dist/subagent/orchestrator.js +2 -0
- package/dist/subagent/types.d.ts +6 -0
- package/dist/vcs/generate.d.ts +3 -1
- package/dist/vcs/generate.js +4 -1
- package/package.json +2 -2
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { MODEL_TIERS } from "../brand/voice.js";
|
|
2
|
+
import { routingTierUnavailable } from "../errors/index.js";
|
|
3
|
+
import { resolveModelId } from "./resolve.js";
|
|
4
|
+
/**
|
|
5
|
+
* The tier a config resolves to when nothing else pins one down — mirrors the
|
|
6
|
+
* gateway's `auto` fallback (`AUTO_FALLBACK_TIER` in the SDK), so an unrouted
|
|
7
|
+
* cruxy session lands on exactly the tier it does today.
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_TIER = "vaani";
|
|
10
|
+
/**
|
|
11
|
+
* The config-driven {@link Router}: maps a declared task class to a tier from
|
|
12
|
+
* `{ default, map }`, and fails loud when the resolved tier is not offered. It
|
|
13
|
+
* NEVER inspects prompt content — selection is purely `map[taskClass] ?? default`.
|
|
14
|
+
*/
|
|
15
|
+
export class ConfigRouter {
|
|
16
|
+
cfg;
|
|
17
|
+
offered;
|
|
18
|
+
/**
|
|
19
|
+
* @param cfg the resolved routing table (default tier + per-task map)
|
|
20
|
+
* @param offered the tiers this gateway/plan actually provides; a resolved
|
|
21
|
+
* tier outside this set fails loud. Defaults to all tiers — the
|
|
22
|
+
* seam a future entitlement check narrows (never a silent
|
|
23
|
+
* downgrade).
|
|
24
|
+
*/
|
|
25
|
+
constructor(cfg, offered = MODEL_TIERS) {
|
|
26
|
+
this.cfg = cfg;
|
|
27
|
+
this.offered = new Set(offered);
|
|
28
|
+
}
|
|
29
|
+
select(taskClass) {
|
|
30
|
+
// Explicit override, else the default — an unmapped/unknown class is not an
|
|
31
|
+
// error, it just takes the default (never a crash, never the cheapest).
|
|
32
|
+
const tier = this.cfg.map[taskClass] ?? this.cfg.default;
|
|
33
|
+
// Fail loud: a configured tier the gateway does not offer is a usage error
|
|
34
|
+
// to fix, NOT a silent substitution to some other tier (a user who asked for
|
|
35
|
+
// mira reasoning must never be quietly handed kavi).
|
|
36
|
+
if (!this.offered.has(tier)) {
|
|
37
|
+
throw routingTierUnavailable(tier, taskClass, [...this.offered]);
|
|
38
|
+
}
|
|
39
|
+
return tier;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The base tier implied by the session's `model.model`: a real tier passes
|
|
44
|
+
* through; `auto` (and any non-tier value) falls back to {@link DEFAULT_TIER}.
|
|
45
|
+
* Used so that when a user has pinned a single tier, an opt-in routing table
|
|
46
|
+
* that omits `routing.default` still defaults to THEIR tier, not a fixed one.
|
|
47
|
+
*/
|
|
48
|
+
function baseTierFromModel(model) {
|
|
49
|
+
return MODEL_TIERS.includes(model)
|
|
50
|
+
? model
|
|
51
|
+
: DEFAULT_TIER;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build a router from resolved config, or `null` when routing should stay
|
|
55
|
+
* inert. Routing is:
|
|
56
|
+
*
|
|
57
|
+
* - a cruxy-gateway concept — tiers do not apply to BYO providers, so non-cruxy
|
|
58
|
+
* providers get `null` (no override, their `model.model` is used unchanged);
|
|
59
|
+
* - opt-in — with no `routing.default` and an empty `routing.map`, this returns
|
|
60
|
+
* `null` so behavior (and the wire body, and the state line) is byte-identical
|
|
61
|
+
* to today. Multi-tier routing activates only once the user configures it.
|
|
62
|
+
*/
|
|
63
|
+
export function routerForConfig(config) {
|
|
64
|
+
if (config.model.provider !== "cruxy")
|
|
65
|
+
return null;
|
|
66
|
+
const { default: def, map } = config.routing;
|
|
67
|
+
const configured = def !== undefined || Object.keys(map).length > 0;
|
|
68
|
+
if (!configured)
|
|
69
|
+
return null;
|
|
70
|
+
return new ConfigRouter({
|
|
71
|
+
default: def ?? baseTierFromModel(config.model.model),
|
|
72
|
+
map,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Resolve a declared task class to `{ tier, model }`: the tier for honest
|
|
77
|
+
* surfacing (the U.4 state line), the wire model id for the request. The model
|
|
78
|
+
* id comes from the internal {@link resolveModelId} — callers never touch that
|
|
79
|
+
* mapping directly, so it stays the single source of truth.
|
|
80
|
+
*/
|
|
81
|
+
export function resolveTaskModel(router, taskClass) {
|
|
82
|
+
const tier = router.select(taskClass);
|
|
83
|
+
return { tier, model: resolveModelId(tier) };
|
|
84
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { MODEL_TIERS } from "../brand/voice.js";
|
|
2
|
+
/**
|
|
3
|
+
* Multi-model routing (C.30): route each unit of work to the right tier instead
|
|
4
|
+
* of running one model for everything. The caller DECLARES a {@link TaskClass}
|
|
5
|
+
* at the call site; a {@link Router} maps that class to a {@link Tier} via config
|
|
6
|
+
* — it never sniffs the prompt to guess difficulty. The tier→gateway model-id
|
|
7
|
+
* mapping is internal (see `resolve.ts`); only tier names ever appear in config,
|
|
8
|
+
* logs, errors, or the state line (the U.8 tier gag).
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* The unit-of-work classes a caller can declare. Each is an explicit intent —
|
|
12
|
+
* NOT a difficulty the router infers. Unknown/unset resolves to the router's
|
|
13
|
+
* default tier, never a crash.
|
|
14
|
+
*/
|
|
15
|
+
export declare const TASK_CLASSES: readonly ["main-turn", "subagent", "plan", "commit-msg", "classify", "summarize"];
|
|
16
|
+
export type TaskClass = (typeof TASK_CLASSES)[number];
|
|
17
|
+
/** A routing tier — the ONLY model vocabulary the user ever sees (U.8). */
|
|
18
|
+
export type Tier = (typeof MODEL_TIERS)[number];
|
|
19
|
+
/**
|
|
20
|
+
* Selects a tier for a declared task class. Deliberately one method: the caller
|
|
21
|
+
* passes intent, the router returns a tier from its configured mapping. No
|
|
22
|
+
* prompt-content inspection, ever — difficulty detection is explicitly out of
|
|
23
|
+
* scope (a caller declares; the router does not guess).
|
|
24
|
+
*/
|
|
25
|
+
export interface Router {
|
|
26
|
+
/**
|
|
27
|
+
* Map a task class to its tier. Falls back to the configured default when the
|
|
28
|
+
* class has no explicit mapping; throws `CRUXY_E_ROUTING_TIER_UNAVAILABLE`
|
|
29
|
+
* (fail loud, never a silent substitution) when the resolved tier is not
|
|
30
|
+
* offered.
|
|
31
|
+
*/
|
|
32
|
+
select(taskClass: TaskClass): Tier;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The declarative routing table: a `default` tier plus per-task overrides. Lives
|
|
36
|
+
* in config (`routing.default`, `routing.map`). An empty map means every class
|
|
37
|
+
* resolves to `default` — a single tier, no traffic splitting.
|
|
38
|
+
*/
|
|
39
|
+
export interface RoutingConfig {
|
|
40
|
+
default: Tier;
|
|
41
|
+
map: Partial<Record<TaskClass, Tier>>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-model routing (C.30): route each unit of work to the right tier instead
|
|
3
|
+
* of running one model for everything. The caller DECLARES a {@link TaskClass}
|
|
4
|
+
* at the call site; a {@link Router} maps that class to a {@link Tier} via config
|
|
5
|
+
* — it never sniffs the prompt to guess difficulty. The tier→gateway model-id
|
|
6
|
+
* mapping is internal (see `resolve.ts`); only tier names ever appear in config,
|
|
7
|
+
* logs, errors, or the state line (the U.8 tier gag).
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The unit-of-work classes a caller can declare. Each is an explicit intent —
|
|
11
|
+
* NOT a difficulty the router infers. Unknown/unset resolves to the router's
|
|
12
|
+
* default tier, never a crash.
|
|
13
|
+
*/
|
|
14
|
+
export const TASK_CLASSES = [
|
|
15
|
+
/** An interactive main-agent turn. */
|
|
16
|
+
"main-turn",
|
|
17
|
+
/** A spawned subagent task (C.14). */
|
|
18
|
+
"subagent",
|
|
19
|
+
/** Plan proposal / revision (C.31). */
|
|
20
|
+
"plan",
|
|
21
|
+
/** Commit / pull-request text generation. */
|
|
22
|
+
"commit-msg",
|
|
23
|
+
/** A cheap one-shot classification. */
|
|
24
|
+
"classify",
|
|
25
|
+
/** Context compaction / summarization. */
|
|
26
|
+
"summarize",
|
|
27
|
+
];
|
|
@@ -2,6 +2,7 @@ import type { Provider } from "@cruxy/sdk";
|
|
|
2
2
|
import type { ApprovalDecision } from "../approval/types.js";
|
|
3
3
|
import type { CruxyConfig } from "../config/index.js";
|
|
4
4
|
import type { StreamRenderer } from "../render/index.js";
|
|
5
|
+
import type { Router } from "../routing/index.js";
|
|
5
6
|
import type { ApproveAction, ToolContext, ToolRegistry } from "../tools/index.js";
|
|
6
7
|
import type { SandboxService } from "../sandbox/index.js";
|
|
7
8
|
import type { SubagentResult, SubagentSpec } from "./types.js";
|
|
@@ -15,6 +16,11 @@ import type { SubagentResult, SubagentSpec } from "./types.js";
|
|
|
15
16
|
export interface SubagentOrchestratorDeps {
|
|
16
17
|
provider: Provider;
|
|
17
18
|
config: CruxyConfig;
|
|
19
|
+
/**
|
|
20
|
+
* Multi-model routing (C.30). When set, child runs route on their spec's task
|
|
21
|
+
* class (default `subagent`); omitted → the provider default (unchanged).
|
|
22
|
+
*/
|
|
23
|
+
router?: Router;
|
|
18
24
|
/** The parent's registry — the ceiling every child scope derives from. */
|
|
19
25
|
parentRegistry: ToolRegistry;
|
|
20
26
|
cwd: string;
|
package/dist/subagent/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Usage } from "@cruxy/sdk";
|
|
2
|
+
import type { TaskClass } from "../routing/index.js";
|
|
2
3
|
/**
|
|
3
4
|
* Types for subagent orchestration (C.14): the main agent delegates a bounded
|
|
4
5
|
* subtask to a child agent that runs the SAME loop with its own fresh history,
|
|
@@ -35,6 +36,11 @@ export interface SubagentSpec {
|
|
|
35
36
|
* spawn can narrow its budget, never raise it past the configured ceilings.
|
|
36
37
|
*/
|
|
37
38
|
budget?: Partial<BudgetLimits>;
|
|
39
|
+
/**
|
|
40
|
+
* Routing task class for this spawn (C.30); defaults to `subagent`. A
|
|
41
|
+
* declaration at the spawn call site — not something the router guesses.
|
|
42
|
+
*/
|
|
43
|
+
taskClass?: TaskClass;
|
|
38
44
|
}
|
|
39
45
|
/**
|
|
40
46
|
* What the parent gets back — compact structured data, never the transcript.
|
package/dist/vcs/generate.d.ts
CHANGED
|
@@ -59,7 +59,9 @@ export declare function fillContent(input: GenerateInput): GeneratedContent;
|
|
|
59
59
|
* rules, then normalize + redact. Resilient: if the model's reply isn't the
|
|
60
60
|
* expected JSON, the first line becomes the subject and the rest the body.
|
|
61
61
|
*/
|
|
62
|
-
export declare function generateWithLlm(provider: Provider, input: GenerateInput
|
|
62
|
+
export declare function generateWithLlm(provider: Provider, input: GenerateInput, opts?: {
|
|
63
|
+
model?: string;
|
|
64
|
+
}): Promise<GeneratedContent>;
|
|
63
65
|
interface ParsedGenerated {
|
|
64
66
|
branchName?: string;
|
|
65
67
|
commitSubject?: string;
|
package/dist/vcs/generate.js
CHANGED
|
@@ -154,7 +154,7 @@ export function fillContent(input) {
|
|
|
154
154
|
* rules, then normalize + redact. Resilient: if the model's reply isn't the
|
|
155
155
|
* expected JSON, the first line becomes the subject and the rest the body.
|
|
156
156
|
*/
|
|
157
|
-
export async function generateWithLlm(provider, input) {
|
|
157
|
+
export async function generateWithLlm(provider, input, opts = {}) {
|
|
158
158
|
const redactedDiff = redactSecrets(input.diff);
|
|
159
159
|
const system = buildSystemPrompt(input);
|
|
160
160
|
const user = buildUserPrompt({ ...input, diff: redactedDiff });
|
|
@@ -162,6 +162,9 @@ export async function generateWithLlm(provider, input) {
|
|
|
162
162
|
for await (const ev of provider.stream({
|
|
163
163
|
system,
|
|
164
164
|
messages: [{ role: "user", content: user }],
|
|
165
|
+
// The commit-msg tier's wire model (C.30), resolved by the caller; omitted →
|
|
166
|
+
// the provider default.
|
|
167
|
+
...(opts.model ? { model: opts.model } : {}),
|
|
165
168
|
})) {
|
|
166
169
|
if (ev.type === "text_delta")
|
|
167
170
|
text += ev.text;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cruxy/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "an agentic coding CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"tinyglobby": "^0.2.10",
|
|
37
37
|
"zod": "^3.23.8",
|
|
38
38
|
"zod-to-json-schema": "^3.23.5",
|
|
39
|
-
"@cruxy/sdk": "0.
|
|
39
|
+
"@cruxy/sdk": "0.2.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/better-sqlite3": "^7.6.13",
|