omegon 0.6.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/.gitattributes +3 -0
- package/AGENTS.md +16 -0
- package/LICENSE +15 -0
- package/README.md +289 -0
- package/bin/pi.mjs +30 -0
- package/extensions/00-secrets/index.ts +1126 -0
- package/extensions/01-auth/auth.ts +401 -0
- package/extensions/01-auth/index.ts +289 -0
- package/extensions/auto-compact.ts +42 -0
- package/extensions/bootstrap/deps.ts +291 -0
- package/extensions/bootstrap/index.ts +811 -0
- package/extensions/chronos/chronos.sh +487 -0
- package/extensions/chronos/index.ts +148 -0
- package/extensions/cleave/assessment.ts +754 -0
- package/extensions/cleave/bridge.ts +31 -0
- package/extensions/cleave/conflicts.ts +250 -0
- package/extensions/cleave/dispatcher.ts +808 -0
- package/extensions/cleave/guardrails.ts +426 -0
- package/extensions/cleave/index.ts +3121 -0
- package/extensions/cleave/lifecycle-emitter.ts +20 -0
- package/extensions/cleave/openspec.ts +811 -0
- package/extensions/cleave/planner.ts +260 -0
- package/extensions/cleave/review.ts +579 -0
- package/extensions/cleave/skills.ts +355 -0
- package/extensions/cleave/types.ts +261 -0
- package/extensions/cleave/workspace.ts +861 -0
- package/extensions/cleave/worktree.ts +243 -0
- package/extensions/core-renderers.ts +253 -0
- package/extensions/dashboard/context-gauge.ts +58 -0
- package/extensions/dashboard/file-watch.ts +14 -0
- package/extensions/dashboard/footer.ts +1145 -0
- package/extensions/dashboard/git.ts +185 -0
- package/extensions/dashboard/index.ts +478 -0
- package/extensions/dashboard/memory-audit.ts +34 -0
- package/extensions/dashboard/overlay-data.ts +705 -0
- package/extensions/dashboard/overlay.ts +365 -0
- package/extensions/dashboard/render-utils.ts +54 -0
- package/extensions/dashboard/types.ts +191 -0
- package/extensions/dashboard/uri-helper.ts +45 -0
- package/extensions/debug.ts +69 -0
- package/extensions/defaults.ts +282 -0
- package/extensions/design-tree/dashboard-state.ts +161 -0
- package/extensions/design-tree/design-card.ts +362 -0
- package/extensions/design-tree/index.ts +2130 -0
- package/extensions/design-tree/lifecycle-emitter.ts +41 -0
- package/extensions/design-tree/tree.ts +1607 -0
- package/extensions/design-tree/types.ts +163 -0
- package/extensions/distill.ts +127 -0
- package/extensions/effort/index.ts +395 -0
- package/extensions/effort/tiers.ts +146 -0
- package/extensions/effort/types.ts +105 -0
- package/extensions/lib/git-state.ts +227 -0
- package/extensions/lib/local-models.ts +157 -0
- package/extensions/lib/model-preferences.ts +51 -0
- package/extensions/lib/model-routing.ts +720 -0
- package/extensions/lib/operator-fallback.ts +205 -0
- package/extensions/lib/operator-profile.ts +360 -0
- package/extensions/lib/slash-command-bridge.ts +253 -0
- package/extensions/lib/typebox-helpers.ts +16 -0
- package/extensions/local-inference/index.ts +727 -0
- package/extensions/mcp-bridge/README.md +220 -0
- package/extensions/mcp-bridge/index.ts +951 -0
- package/extensions/mcp-bridge/lib.ts +365 -0
- package/extensions/mcp-bridge/mcp.json +3 -0
- package/extensions/mcp-bridge/package.json +11 -0
- package/extensions/model-budget.ts +752 -0
- package/extensions/offline-driver.ts +403 -0
- package/extensions/openspec/archive-gate.ts +164 -0
- package/extensions/openspec/branch-cleanup.ts +64 -0
- package/extensions/openspec/dashboard-state.ts +50 -0
- package/extensions/openspec/index.ts +1917 -0
- package/extensions/openspec/lifecycle-emitter.ts +65 -0
- package/extensions/openspec/lifecycle-files.ts +70 -0
- package/extensions/openspec/lifecycle.ts +50 -0
- package/extensions/openspec/reconcile.ts +187 -0
- package/extensions/openspec/spec.ts +1385 -0
- package/extensions/openspec/types.ts +98 -0
- package/extensions/project-memory/DESIGN-global-mind.md +198 -0
- package/extensions/project-memory/README.md +202 -0
- package/extensions/project-memory/api-types.ts +382 -0
- package/extensions/project-memory/compaction-policy.ts +29 -0
- package/extensions/project-memory/core.ts +164 -0
- package/extensions/project-memory/embeddings.ts +230 -0
- package/extensions/project-memory/extraction-v2.ts +861 -0
- package/extensions/project-memory/factstore.ts +2177 -0
- package/extensions/project-memory/index.ts +3459 -0
- package/extensions/project-memory/injection-metrics.ts +91 -0
- package/extensions/project-memory/jsonl-io.ts +12 -0
- package/extensions/project-memory/lifecycle.ts +331 -0
- package/extensions/project-memory/migration.ts +293 -0
- package/extensions/project-memory/package.json +9 -0
- package/extensions/project-memory/sci-renderers.ts +7 -0
- package/extensions/project-memory/template.ts +103 -0
- package/extensions/project-memory/triggers.ts +52 -0
- package/extensions/project-memory/types.ts +102 -0
- package/extensions/render/composition/fonts/Inter-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Inter-Regular.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Regular.ttf +0 -0
- package/extensions/render/composition/package-lock.json +534 -0
- package/extensions/render/composition/package.json +22 -0
- package/extensions/render/composition/render.mjs +246 -0
- package/extensions/render/composition/test-comp.tsx +87 -0
- package/extensions/render/composition/types.ts +24 -0
- package/extensions/render/excalidraw/UPSTREAM.md +81 -0
- package/extensions/render/excalidraw/elements.ts +764 -0
- package/extensions/render/excalidraw/index.ts +66 -0
- package/extensions/render/excalidraw/types.ts +223 -0
- package/extensions/render/excalidraw-renderer/pyproject.toml +8 -0
- package/extensions/render/excalidraw-renderer/render_excalidraw.py +182 -0
- package/extensions/render/excalidraw-renderer/render_template.html +59 -0
- package/extensions/render/index.ts +830 -0
- package/extensions/render/native-diagrams/index.ts +57 -0
- package/extensions/render/native-diagrams/motifs.ts +542 -0
- package/extensions/render/native-diagrams/raster.ts +8 -0
- package/extensions/render/native-diagrams/scene.ts +75 -0
- package/extensions/render/native-diagrams/spec.ts +204 -0
- package/extensions/render/native-diagrams/svg.ts +116 -0
- package/extensions/sci-ui.ts +304 -0
- package/extensions/session-log.ts +174 -0
- package/extensions/shared-state.ts +146 -0
- package/extensions/spinner-verbs.ts +91 -0
- package/extensions/style.ts +281 -0
- package/extensions/terminal-title.ts +191 -0
- package/extensions/tool-profile/index.ts +291 -0
- package/extensions/tool-profile/profiles.ts +290 -0
- package/extensions/types.d.ts +9 -0
- package/extensions/vault/index.ts +185 -0
- package/extensions/version-check.ts +90 -0
- package/extensions/view/index.ts +859 -0
- package/extensions/view/uri-resolver.ts +148 -0
- package/extensions/web-search/index.ts +182 -0
- package/extensions/web-search/providers.ts +121 -0
- package/extensions/web-ui/index.ts +110 -0
- package/extensions/web-ui/server.ts +265 -0
- package/extensions/web-ui/state.ts +462 -0
- package/extensions/web-ui/static/index.html +145 -0
- package/extensions/web-ui/types.ts +284 -0
- package/package.json +76 -0
- package/prompts/init.md +75 -0
- package/prompts/new-repo.md +54 -0
- package/prompts/oci-login.md +56 -0
- package/prompts/status.md +50 -0
- package/settings.json +4 -0
- package/skills/cleave/SKILL.md +218 -0
- package/skills/git/SKILL.md +209 -0
- package/skills/git/_reference/ci-validation.md +204 -0
- package/skills/oci/SKILL.md +338 -0
- package/skills/openspec/SKILL.md +346 -0
- package/skills/pi-extensions/SKILL.md +191 -0
- package/skills/pi-tui/SKILL.md +517 -0
- package/skills/python/SKILL.md +189 -0
- package/skills/rust/SKILL.md +268 -0
- package/skills/security/SKILL.md +206 -0
- package/skills/style/SKILL.md +264 -0
- package/skills/typescript/SKILL.md +225 -0
- package/skills/vault/SKILL.md +102 -0
- package/themes/alpharius-legacy.json +85 -0
- package/themes/alpharius.conf +59 -0
- package/themes/alpharius.json +88 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* effort/tiers — Pure functions for effort tier configuration.
|
|
3
|
+
*
|
|
4
|
+
* No side effects, no imports beyond types. Safe to call from any context.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { EffortConfig, EffortLevel } from "./types.ts";
|
|
8
|
+
import { EFFORT_NAMES } from "./types.ts";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Static tier configuration table.
|
|
12
|
+
* Each entry is a complete EffortConfig for levels 1-7.
|
|
13
|
+
*
|
|
14
|
+
* 1 = Servitor — all local
|
|
15
|
+
* 2 = Average — local driver, local background
|
|
16
|
+
* 3 = Substantial — victory driver, local background (daily default)
|
|
17
|
+
* 4 = Ruthless — victory + medium thinking
|
|
18
|
+
* 5 = Lethal — victory + high thinking, gloriana review
|
|
19
|
+
* 6 = Absolute — gloriana driver, victory background
|
|
20
|
+
* 7 = Omnissiah — all gloriana
|
|
21
|
+
*/
|
|
22
|
+
const TIERS: Record<EffortLevel, EffortConfig> = {
|
|
23
|
+
1: {
|
|
24
|
+
level: 1,
|
|
25
|
+
name: "Servitor",
|
|
26
|
+
driver: "local",
|
|
27
|
+
thinking: "off",
|
|
28
|
+
extraction: "local",
|
|
29
|
+
compaction: "local",
|
|
30
|
+
cleavePreferLocal: true,
|
|
31
|
+
cleaveFloor: "local",
|
|
32
|
+
reviewModel: "local",
|
|
33
|
+
},
|
|
34
|
+
2: {
|
|
35
|
+
level: 2,
|
|
36
|
+
name: "Average",
|
|
37
|
+
driver: "local",
|
|
38
|
+
thinking: "minimal",
|
|
39
|
+
extraction: "local",
|
|
40
|
+
compaction: "local",
|
|
41
|
+
cleavePreferLocal: false,
|
|
42
|
+
cleaveFloor: "local",
|
|
43
|
+
reviewModel: "local",
|
|
44
|
+
},
|
|
45
|
+
3: {
|
|
46
|
+
level: 3,
|
|
47
|
+
name: "Substantial",
|
|
48
|
+
driver: "victory",
|
|
49
|
+
thinking: "low",
|
|
50
|
+
extraction: "local",
|
|
51
|
+
compaction: "victory",
|
|
52
|
+
cleavePreferLocal: false,
|
|
53
|
+
cleaveFloor: "local",
|
|
54
|
+
reviewModel: "victory",
|
|
55
|
+
},
|
|
56
|
+
4: {
|
|
57
|
+
level: 4,
|
|
58
|
+
name: "Ruthless",
|
|
59
|
+
driver: "victory",
|
|
60
|
+
thinking: "medium",
|
|
61
|
+
extraction: "local",
|
|
62
|
+
compaction: "victory",
|
|
63
|
+
cleavePreferLocal: false,
|
|
64
|
+
cleaveFloor: "local",
|
|
65
|
+
reviewModel: "victory",
|
|
66
|
+
},
|
|
67
|
+
5: {
|
|
68
|
+
level: 5,
|
|
69
|
+
name: "Lethal",
|
|
70
|
+
driver: "victory",
|
|
71
|
+
thinking: "high",
|
|
72
|
+
extraction: "local",
|
|
73
|
+
compaction: "victory",
|
|
74
|
+
cleavePreferLocal: false,
|
|
75
|
+
cleaveFloor: "victory",
|
|
76
|
+
reviewModel: "gloriana",
|
|
77
|
+
},
|
|
78
|
+
6: {
|
|
79
|
+
level: 6,
|
|
80
|
+
name: "Absolute",
|
|
81
|
+
driver: "gloriana",
|
|
82
|
+
thinking: "high",
|
|
83
|
+
extraction: "victory",
|
|
84
|
+
compaction: "victory",
|
|
85
|
+
cleavePreferLocal: false,
|
|
86
|
+
cleaveFloor: "victory",
|
|
87
|
+
reviewModel: "gloriana",
|
|
88
|
+
},
|
|
89
|
+
7: {
|
|
90
|
+
level: 7,
|
|
91
|
+
name: "Omnissiah",
|
|
92
|
+
driver: "gloriana",
|
|
93
|
+
thinking: "high",
|
|
94
|
+
extraction: "gloriana",
|
|
95
|
+
compaction: "gloriana",
|
|
96
|
+
cleavePreferLocal: false,
|
|
97
|
+
cleaveFloor: "gloriana",
|
|
98
|
+
reviewModel: "gloriana",
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get the EffortConfig for a given numeric level.
|
|
104
|
+
*
|
|
105
|
+
* @param level - Effort level 1-7
|
|
106
|
+
* @returns EffortConfig for the requested level (shared reference — do not mutate)
|
|
107
|
+
* @throws RangeError if level is outside 1-7
|
|
108
|
+
*/
|
|
109
|
+
export function tierConfig(level: number): EffortConfig {
|
|
110
|
+
if (level < 1 || level > 7 || !Number.isInteger(level)) {
|
|
111
|
+
throw new RangeError(`Effort level must be an integer 1-7, got ${level}`);
|
|
112
|
+
}
|
|
113
|
+
return TIERS[level as EffortLevel];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Reverse lookup: tier name (case-insensitive) → level number. */
|
|
117
|
+
const NAME_TO_LEVEL: ReadonlyMap<string, EffortLevel> = new Map(
|
|
118
|
+
(Object.entries(EFFORT_NAMES) as [string, string][]).map(([k, v]) => [
|
|
119
|
+
v.toLowerCase(),
|
|
120
|
+
Number(k) as EffortLevel,
|
|
121
|
+
]),
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Parse a tier name string to its numeric level.
|
|
126
|
+
*
|
|
127
|
+
* Case-insensitive. Returns `undefined` for unknown names.
|
|
128
|
+
*
|
|
129
|
+
* @param name - Tier name like "Ruthless", "low", "OMNISSIAH"
|
|
130
|
+
* @returns Numeric level 1-7, or undefined if not recognized
|
|
131
|
+
*/
|
|
132
|
+
export function parseTierName(name: string): EffortLevel | undefined {
|
|
133
|
+
return NAME_TO_LEVEL.get(name.toLowerCase());
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Default effort level when no env var or config is present. */
|
|
137
|
+
export const DEFAULT_EFFORT_LEVEL: EffortLevel = 3;
|
|
138
|
+
|
|
139
|
+
/** All valid tier names for display/error messages. */
|
|
140
|
+
export const TIER_NAMES: readonly string[] = Object.values(EFFORT_NAMES);
|
|
141
|
+
|
|
142
|
+
/** Minimum valid effort level. */
|
|
143
|
+
export const MIN_LEVEL: EffortLevel = 1;
|
|
144
|
+
|
|
145
|
+
/** Maximum valid effort level. */
|
|
146
|
+
export const MAX_LEVEL: EffortLevel = 7;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* effort/types — Type definitions for the effort-tiers system.
|
|
3
|
+
*
|
|
4
|
+
* Effort tiers provide 7-level global inference cost control,
|
|
5
|
+
* from fully-local (Servitor) to all-gloriana (Omnissiah).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ─── Model Tiers ─────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Model tier for effort configuration — which model class to use.
|
|
12
|
+
* Includes "retribution" so that policy-upgraded extraction tiers (local→retribution) are
|
|
13
|
+
* representable without casts. Aligns with the shared ModelTier in model-routing.ts.
|
|
14
|
+
*/
|
|
15
|
+
export type EffortModelTier = "local" | "retribution" | "victory" | "gloriana";
|
|
16
|
+
|
|
17
|
+
/** Thinking level passed to the driver model. */
|
|
18
|
+
export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high";
|
|
19
|
+
|
|
20
|
+
// ─── Effort Level ────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/** Numeric effort levels 1-7. */
|
|
23
|
+
export type EffortLevel = 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
|
24
|
+
|
|
25
|
+
/** Human-readable tier names, indexed by level. */
|
|
26
|
+
export const EFFORT_NAMES = {
|
|
27
|
+
1: "Servitor",
|
|
28
|
+
2: "Average",
|
|
29
|
+
3: "Substantial",
|
|
30
|
+
4: "Ruthless",
|
|
31
|
+
5: "Lethal",
|
|
32
|
+
6: "Absolute",
|
|
33
|
+
7: "Omnissiah",
|
|
34
|
+
} as const satisfies Record<EffortLevel, string>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Greek letter glyphs for each effort level — α through ζ, then ω for Omnissiah.
|
|
38
|
+
* α = origin (Servitor), ω = complete arc (Omnissiah). Display alongside tier names.
|
|
39
|
+
*/
|
|
40
|
+
export const EFFORT_GLYPHS = {
|
|
41
|
+
1: "α",
|
|
42
|
+
2: "β",
|
|
43
|
+
3: "γ",
|
|
44
|
+
4: "δ",
|
|
45
|
+
5: "ε",
|
|
46
|
+
6: "ζ",
|
|
47
|
+
7: "ω",
|
|
48
|
+
} as const satisfies Record<EffortLevel, string>;
|
|
49
|
+
|
|
50
|
+
/** Union of valid tier name strings. */
|
|
51
|
+
export type EffortName = (typeof EFFORT_NAMES)[EffortLevel];
|
|
52
|
+
|
|
53
|
+
// ─── Effort Config ───────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Immutable configuration for a single effort tier.
|
|
57
|
+
* Returned by `tierConfig(level)` — pure data, no side effects.
|
|
58
|
+
*/
|
|
59
|
+
export interface EffortConfig {
|
|
60
|
+
/** Numeric level 1-7. */
|
|
61
|
+
level: EffortLevel;
|
|
62
|
+
/** Human-readable tier name. */
|
|
63
|
+
name: EffortName;
|
|
64
|
+
|
|
65
|
+
// ── Model selection ──
|
|
66
|
+
/** Primary driver model. */
|
|
67
|
+
driver: EffortModelTier;
|
|
68
|
+
/** Extended thinking budget for the driver. */
|
|
69
|
+
thinking: ThinkingLevel;
|
|
70
|
+
|
|
71
|
+
// ── Background tasks ──
|
|
72
|
+
/** Model for memory extraction / summarization. */
|
|
73
|
+
extraction: EffortModelTier;
|
|
74
|
+
/** Model for context compaction. */
|
|
75
|
+
compaction: EffortModelTier;
|
|
76
|
+
|
|
77
|
+
// ── Cleave dispatch ──
|
|
78
|
+
/** Whether cleave should prefer local models for child tasks. */
|
|
79
|
+
cleavePreferLocal: boolean;
|
|
80
|
+
/** Minimum model tier for cleave child tasks. */
|
|
81
|
+
cleaveFloor: EffortModelTier;
|
|
82
|
+
|
|
83
|
+
// ── Review ──
|
|
84
|
+
/** Model used for code review in cleave review loops. */
|
|
85
|
+
reviewModel: EffortModelTier;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ─── Effort State ────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Runtime effort state stored in SharedState.
|
|
92
|
+
* Extends EffortConfig with cap tracking and resolved model IDs.
|
|
93
|
+
*/
|
|
94
|
+
export interface EffortState extends EffortConfig {
|
|
95
|
+
/** Whether the effort level is capped (ceiling-locked by operator). */
|
|
96
|
+
capped: boolean;
|
|
97
|
+
/** If capped, the level at which the cap is set. */
|
|
98
|
+
capLevel?: EffortLevel;
|
|
99
|
+
/**
|
|
100
|
+
* Concrete model ID resolved for extraction work under the current routing policy.
|
|
101
|
+
* May differ from `extraction` when cheapCloudPreferredOverLocal upgrades local→retribution.
|
|
102
|
+
* Populated by resolveExtractionTier() on session_start and tier switches.
|
|
103
|
+
*/
|
|
104
|
+
resolvedExtractionModelId?: string;
|
|
105
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/git-state — Shared helpers for inspecting git dirty-tree state.
|
|
3
|
+
*
|
|
4
|
+
* Pure helpers only: parse porcelain output, separate tracked/untracked files,
|
|
5
|
+
* classify built-in volatile artifacts, and prepare checkpoint/stash plans
|
|
6
|
+
* without executing git mutations.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const BUILTIN_VOLATILE_ALLOWLIST = [".pi/memory/facts.jsonl"] as const;
|
|
10
|
+
|
|
11
|
+
export type GitStatusCode =
|
|
12
|
+
| "modified"
|
|
13
|
+
| "added"
|
|
14
|
+
| "deleted"
|
|
15
|
+
| "renamed"
|
|
16
|
+
| "copied"
|
|
17
|
+
| "unmerged"
|
|
18
|
+
| "untracked"
|
|
19
|
+
| "ignored"
|
|
20
|
+
| "unknown";
|
|
21
|
+
|
|
22
|
+
export interface GitStatusEntry {
|
|
23
|
+
path: string;
|
|
24
|
+
code: string;
|
|
25
|
+
indexStatus: string;
|
|
26
|
+
worktreeStatus: string;
|
|
27
|
+
tracked: boolean;
|
|
28
|
+
untracked: boolean;
|
|
29
|
+
staged: boolean;
|
|
30
|
+
unstaged: boolean;
|
|
31
|
+
deleted: boolean;
|
|
32
|
+
renamed: boolean;
|
|
33
|
+
copied: boolean;
|
|
34
|
+
originalPath?: string;
|
|
35
|
+
indexKind: GitStatusCode;
|
|
36
|
+
worktreeKind: GitStatusCode;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface GitStateSnapshot {
|
|
40
|
+
entries: GitStatusEntry[];
|
|
41
|
+
tracked: GitStatusEntry[];
|
|
42
|
+
untracked: GitStatusEntry[];
|
|
43
|
+
volatile: GitStatusEntry[];
|
|
44
|
+
nonVolatile: GitStatusEntry[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface GitPathsetPlan {
|
|
48
|
+
paths: string[];
|
|
49
|
+
includesTracked: boolean;
|
|
50
|
+
includesUntracked: boolean;
|
|
51
|
+
pathspec: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface GitPathsetInput {
|
|
55
|
+
tracked?: string[];
|
|
56
|
+
untracked?: string[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface PreparedStashPlan extends GitPathsetPlan {
|
|
60
|
+
kind: "stash";
|
|
61
|
+
label: string;
|
|
62
|
+
includeUntracked: boolean;
|
|
63
|
+
command: string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface PreparedCheckpointPlan extends GitPathsetPlan {
|
|
67
|
+
kind: "checkpoint";
|
|
68
|
+
message: string;
|
|
69
|
+
command: string[];
|
|
70
|
+
requiresApproval: true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function parseGitStatus(output: string): GitStatusEntry[] {
|
|
74
|
+
return output
|
|
75
|
+
.split("\n")
|
|
76
|
+
.map((line) => line.trimEnd())
|
|
77
|
+
.filter(Boolean)
|
|
78
|
+
.map(parseGitStatusLine);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function inspectGitState(
|
|
82
|
+
output: string,
|
|
83
|
+
volatileAllowlist: readonly string[] = BUILTIN_VOLATILE_ALLOWLIST,
|
|
84
|
+
): GitStateSnapshot {
|
|
85
|
+
const entries = parseGitStatus(output);
|
|
86
|
+
const tracked = entries.filter((entry) => entry.tracked);
|
|
87
|
+
const untracked = entries.filter((entry) => entry.untracked);
|
|
88
|
+
const volatile = entries.filter((entry) => isVolatilePath(entry.path, volatileAllowlist));
|
|
89
|
+
return {
|
|
90
|
+
entries,
|
|
91
|
+
tracked,
|
|
92
|
+
untracked,
|
|
93
|
+
volatile,
|
|
94
|
+
nonVolatile: entries.filter((entry) => !isVolatilePath(entry.path, volatileAllowlist)),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function buildPathsetFromEntries(entries: GitStatusEntry[]): GitPathsetInput {
|
|
99
|
+
return {
|
|
100
|
+
tracked: entries.filter((entry) => entry.tracked).map((entry) => entry.path),
|
|
101
|
+
untracked: entries.filter((entry) => entry.untracked).map((entry) => entry.path),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function isVolatilePath(
|
|
106
|
+
path: string,
|
|
107
|
+
volatileAllowlist: readonly string[] = BUILTIN_VOLATILE_ALLOWLIST,
|
|
108
|
+
): boolean {
|
|
109
|
+
return volatileAllowlist.some((allowed) => pathMatchesAllowlist(path, allowed));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function prepareStashPlan(input: {
|
|
113
|
+
paths?: string[];
|
|
114
|
+
tracked?: string[];
|
|
115
|
+
untracked?: string[];
|
|
116
|
+
label: string;
|
|
117
|
+
includesUntracked?: boolean;
|
|
118
|
+
}): PreparedStashPlan {
|
|
119
|
+
const pathset = prepareGitPathset({
|
|
120
|
+
tracked: input.paths ?? input.tracked,
|
|
121
|
+
untracked: input.untracked,
|
|
122
|
+
});
|
|
123
|
+
const includeUntracked = input.includesUntracked ?? pathset.includesUntracked;
|
|
124
|
+
const command = ["git", "stash", "push", "-m", input.label];
|
|
125
|
+
if (includeUntracked) command.push("--include-untracked");
|
|
126
|
+
command.push(...pathset.pathspec);
|
|
127
|
+
return {
|
|
128
|
+
kind: "stash",
|
|
129
|
+
label: input.label,
|
|
130
|
+
includeUntracked,
|
|
131
|
+
command,
|
|
132
|
+
...pathset,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function prepareCheckpointPlan(input: {
|
|
137
|
+
paths?: string[];
|
|
138
|
+
tracked?: string[];
|
|
139
|
+
untracked?: string[];
|
|
140
|
+
message: string;
|
|
141
|
+
}): PreparedCheckpointPlan {
|
|
142
|
+
const pathset = prepareGitPathset({
|
|
143
|
+
tracked: input.paths ?? input.tracked,
|
|
144
|
+
untracked: input.untracked,
|
|
145
|
+
});
|
|
146
|
+
return {
|
|
147
|
+
kind: "checkpoint",
|
|
148
|
+
message: input.message,
|
|
149
|
+
requiresApproval: true,
|
|
150
|
+
command: ["git", "commit", "-m", input.message],
|
|
151
|
+
...pathset,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function prepareGitPathset(input: string[] | GitPathsetInput): GitPathsetPlan {
|
|
156
|
+
const tracked = Array.isArray(input) ? input : (input.tracked ?? []);
|
|
157
|
+
const untracked = Array.isArray(input) ? [] : (input.untracked ?? []);
|
|
158
|
+
const uniquePaths = [...new Set([...tracked, ...untracked].filter(Boolean))].sort();
|
|
159
|
+
return {
|
|
160
|
+
paths: uniquePaths,
|
|
161
|
+
includesTracked: tracked.length > 0,
|
|
162
|
+
includesUntracked: untracked.length > 0,
|
|
163
|
+
pathspec: uniquePaths.length > 0 ? ["--", ...uniquePaths] : [],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function parseGitStatusLine(line: string): GitStatusEntry {
|
|
168
|
+
const indexStatus = line[0] ?? " ";
|
|
169
|
+
const worktreeStatus = line[1] ?? " ";
|
|
170
|
+
const payload = line.slice(3);
|
|
171
|
+
const renameParts = payload.split(" -> ");
|
|
172
|
+
const path = renameParts[renameParts.length - 1] ?? payload;
|
|
173
|
+
const originalPath = renameParts.length === 2 ? renameParts[0] : undefined;
|
|
174
|
+
const tracked = indexStatus !== "?" && worktreeStatus !== "?";
|
|
175
|
+
const untracked = indexStatus === "?" && worktreeStatus === "?";
|
|
176
|
+
return {
|
|
177
|
+
path,
|
|
178
|
+
code: `${indexStatus}${worktreeStatus}`,
|
|
179
|
+
indexStatus,
|
|
180
|
+
worktreeStatus,
|
|
181
|
+
tracked,
|
|
182
|
+
untracked,
|
|
183
|
+
staged: isChangedStatus(indexStatus),
|
|
184
|
+
unstaged: isChangedStatus(worktreeStatus),
|
|
185
|
+
deleted: indexStatus === "D" || worktreeStatus === "D",
|
|
186
|
+
renamed: indexStatus === "R" || worktreeStatus === "R",
|
|
187
|
+
copied: indexStatus === "C" || worktreeStatus === "C",
|
|
188
|
+
originalPath,
|
|
189
|
+
indexKind: decodeStatus(indexStatus),
|
|
190
|
+
worktreeKind: decodeStatus(worktreeStatus),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function decodeStatus(status: string): GitStatusCode {
|
|
195
|
+
switch (status) {
|
|
196
|
+
case "M":
|
|
197
|
+
return "modified";
|
|
198
|
+
case "A":
|
|
199
|
+
return "added";
|
|
200
|
+
case "D":
|
|
201
|
+
return "deleted";
|
|
202
|
+
case "R":
|
|
203
|
+
return "renamed";
|
|
204
|
+
case "C":
|
|
205
|
+
return "copied";
|
|
206
|
+
case "U":
|
|
207
|
+
return "unmerged";
|
|
208
|
+
case "?":
|
|
209
|
+
return "untracked";
|
|
210
|
+
case "!":
|
|
211
|
+
return "ignored";
|
|
212
|
+
case " ":
|
|
213
|
+
return "unknown";
|
|
214
|
+
default:
|
|
215
|
+
return "unknown";
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function isChangedStatus(status: string): boolean {
|
|
220
|
+
return status !== " " && status !== "?" && status !== "!";
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function pathMatchesAllowlist(path: string, allowed: string): boolean {
|
|
224
|
+
if (path === allowed) return true;
|
|
225
|
+
if (allowed.endsWith("/")) return path.startsWith(allowed);
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical local model registry for Omegon.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth consumed by: offline-driver, effort, cleave, project-memory.
|
|
5
|
+
* Update this file as new models are released — all preference logic reads from here.
|
|
6
|
+
*
|
|
7
|
+
* PREFERENCE ORDERING PRINCIPLES
|
|
8
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
+
* Quality-first: best available model wins automatically.
|
|
10
|
+
* Users install what fits their hardware; the lists handle selection.
|
|
11
|
+
*
|
|
12
|
+
* 64GB → 70B tier (qwen2.5:72b, llama3.3:70b)
|
|
13
|
+
* 32GB → 32B tier (qwen3:32b, qwen2.5:32b)
|
|
14
|
+
* 24GB → 14B Q8 or MoE 30B Q4 (qwen3-coder:30b, qwen3:14b)
|
|
15
|
+
* 16GB → 8B Q8 (qwen3:8b, llama3.1:8b)
|
|
16
|
+
* 8GB → 4B Q8 (qwen3:4b, phi4-mini)
|
|
17
|
+
*
|
|
18
|
+
* MoE NOTE: qwen3-coder:30b has 30B total params but only 3.3B activated —
|
|
19
|
+
* weight file is ~18GB at Q4 but inference cost is like a 3-4B dense model.
|
|
20
|
+
* Fits on 24GB, runs fast, explicitly trained on SWE-Bench agentic tasks.
|
|
21
|
+
*
|
|
22
|
+
* CONTEXT WINDOW NOTE: phi4:14b has 16K context (too small for deep agent
|
|
23
|
+
* sessions) — excluded from main preferences. phi4-mini has 128K and is fine.
|
|
24
|
+
* mistral:7b and codestral:22b are capped at 32K; prefer Qwen/Llama at those sizes.
|
|
25
|
+
*
|
|
26
|
+
* Last reviewed: 2026-03-07
|
|
27
|
+
* Next review triggers: major Qwen4/Llama4/Gemma4 releases, new MoE coding models.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export interface LocalModelMeta {
|
|
31
|
+
/** Human-readable display name */
|
|
32
|
+
label: string;
|
|
33
|
+
/** Emoji icon for UI display */
|
|
34
|
+
icon: string;
|
|
35
|
+
/** Native context window in tokens */
|
|
36
|
+
contextWindow: number;
|
|
37
|
+
/** Max generation tokens */
|
|
38
|
+
maxTokens: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Model registry — metadata for known models
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
export const KNOWN_MODELS: Record<string, LocalModelMeta> = {
|
|
46
|
+
// ── 70B tier (64GB+) ──────────────────────────────────────────────────────
|
|
47
|
+
"qwen2.5:72b": { label: "Qwen2.5 72B", icon: "🧠", contextWindow: 131072, maxTokens: 32768 },
|
|
48
|
+
"llama3.3:70b": { label: "Llama 3.3 70B", icon: "🦙", contextWindow: 131072, maxTokens: 32768 },
|
|
49
|
+
"llama3.1:70b": { label: "Llama 3.1 70B", icon: "🦙", contextWindow: 131072, maxTokens: 32768 },
|
|
50
|
+
|
|
51
|
+
// ── 32B tier (32GB+, or 24GB at Q4) ──────────────────────────────────────
|
|
52
|
+
"qwen3:32b": { label: "Qwen3 32B", icon: "🐉", contextWindow: 131072, maxTokens: 32768 },
|
|
53
|
+
"qwen2.5:32b": { label: "Qwen2.5 32B", icon: "🧠", contextWindow: 131072, maxTokens: 32768 },
|
|
54
|
+
"qwen2.5-coder:32b": { label: "Qwen2.5-Coder 32B", icon: "💻", contextWindow: 131072, maxTokens: 32768 },
|
|
55
|
+
|
|
56
|
+
// ── MoE 30B tier — runs lean despite total param count ───────────────────
|
|
57
|
+
// qwen3-coder: 30B total / 3.3B active, ~18GB at Q4. Fits 24GB+.
|
|
58
|
+
// Explicitly trained on SWE-Bench; best local code-agent model at its size.
|
|
59
|
+
"qwen3-coder:30b": { label: "Qwen3-Coder 30B (MoE)", icon: "🤖", contextWindow: 262144, maxTokens: 32768 },
|
|
60
|
+
"qwen3:30b": { label: "Qwen3 30B", icon: "🐲", contextWindow: 131072, maxTokens: 32768 },
|
|
61
|
+
"nemotron-3-nano:30b": { label: "Nemotron 3 Nano 30B", icon: "🏔️", contextWindow: 1048576, maxTokens: 32768 },
|
|
62
|
+
|
|
63
|
+
// ── 22–27B tier (16GB+ at Q4, 32GB at Q8) ───────────────────────────────
|
|
64
|
+
"gemma3:27b": { label: "Gemma3 27B", icon: "♊", contextWindow: 131072, maxTokens: 32768 },
|
|
65
|
+
// devstral:24b is the current canonical Ollama tag (replaces devstral-small-2:24b)
|
|
66
|
+
// Built on Mistral-Small-3.1; 53.6% SWE-Bench verified (top OSS as of mid-2025)
|
|
67
|
+
"devstral:24b": { label: "Devstral 24B", icon: "⚙️", contextWindow: 131072, maxTokens: 32768 },
|
|
68
|
+
"devstral-small-2:24b": { label: "Devstral Small 2 24B", icon: "⚙️", contextWindow: 393216, maxTokens: 32768 },
|
|
69
|
+
"mistral-small": { label: "Mistral Small 22B", icon: "🌬️", contextWindow: 32768, maxTokens: 32768 },
|
|
70
|
+
"codestral:22b": { label: "Codestral 22B", icon: "💻", contextWindow: 32768, maxTokens: 32768 },
|
|
71
|
+
|
|
72
|
+
// ── 14B tier (16GB+) ─────────────────────────────────────────────────────
|
|
73
|
+
"qwen3:14b": { label: "Qwen3 14B", icon: "🐉", contextWindow: 131072, maxTokens: 32768 },
|
|
74
|
+
"qwen2.5:14b": { label: "Qwen2.5 14B", icon: "🧠", contextWindow: 131072, maxTokens: 32768 },
|
|
75
|
+
"qwen2.5-coder:14b": { label: "Qwen2.5-Coder 14B", icon: "💻", contextWindow: 131072, maxTokens: 32768 },
|
|
76
|
+
"mistral-nemo": { label: "Mistral Nemo 12B", icon: "🌬️", contextWindow: 131072, maxTokens: 32768 },
|
|
77
|
+
|
|
78
|
+
// ── 7–9B tier (8GB+) ─────────────────────────────────────────────────────
|
|
79
|
+
"qwen3:8b": { label: "Qwen3 8B", icon: "🐉", contextWindow: 131072, maxTokens: 32768 },
|
|
80
|
+
"qwen2.5:7b": { label: "Qwen2.5 7B", icon: "🧠", contextWindow: 131072, maxTokens: 32768 },
|
|
81
|
+
"qwen2.5-coder:7b": { label: "Qwen2.5-Coder 7B", icon: "💻", contextWindow: 131072, maxTokens: 32768 },
|
|
82
|
+
"llama3.1:8b": { label: "Llama 3.1 8B", icon: "🦙", contextWindow: 131072, maxTokens: 32768 },
|
|
83
|
+
"llama3.2:11b": { label: "Llama 3.2 11B", icon: "🦙", contextWindow: 131072, maxTokens: 32768 },
|
|
84
|
+
"gemma3:9b": { label: "Gemma3 9B", icon: "♊", contextWindow: 131072, maxTokens: 32768 },
|
|
85
|
+
"phi4-mini": { label: "Phi-4 Mini 3.8B", icon: "Φ", contextWindow: 131072, maxTokens: 32768 },
|
|
86
|
+
"mistral:7b": { label: "Mistral 7B", icon: "🌬️", contextWindow: 32768, maxTokens: 32768 },
|
|
87
|
+
|
|
88
|
+
// ── 3–4B tier (8GB, last useful resort for tool calling) ─────────────────
|
|
89
|
+
"qwen3:4b": { label: "Qwen3 4B", icon: "🐉", contextWindow: 131072, maxTokens: 32768 },
|
|
90
|
+
"qwen2.5-coder:3b": { label: "Qwen2.5-Coder 3B", icon: "💻", contextWindow: 131072, maxTokens: 32768 },
|
|
91
|
+
"gemma3:4b": { label: "Gemma3 4B", icon: "♊", contextWindow: 131072, maxTokens: 32768 },
|
|
92
|
+
"llama3.2:3b": { label: "Llama 3.2 3B", icon: "🦙", contextWindow: 131072, maxTokens: 32768 },
|
|
93
|
+
|
|
94
|
+
// ── Sub-3B (emergency fallback — too small for reliable orchestration) ────
|
|
95
|
+
"qwen3:1.7b": { label: "Qwen3 1.7B", icon: "🐣", contextWindow: 131072, maxTokens: 8192 },
|
|
96
|
+
"llama3.2:1b": { label: "Llama 3.2 1B", icon: "🐣", contextWindow: 131072, maxTokens: 8192 },
|
|
97
|
+
"qwen3:0.6b": { label: "Qwen3 0.6B", icon: "🐣", contextWindow: 131072, maxTokens: 8192 },
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Preference lists — consumed by offline-driver, effort, cleave
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* General orchestration preference — best installed model wins.
|
|
106
|
+
* Ordered 70B → 32B → MoE-30B → 14B → 8B → 4B → sub-3B.
|
|
107
|
+
* On any hardware, only installed models are found; others are skipped.
|
|
108
|
+
*/
|
|
109
|
+
export const PREFERRED_ORDER: string[] = [
|
|
110
|
+
// 70B tier (64GB+)
|
|
111
|
+
"qwen2.5:72b", "llama3.3:70b", "llama3.1:70b",
|
|
112
|
+
// 32B tier (32GB+)
|
|
113
|
+
"qwen3:32b", "qwen2.5:32b",
|
|
114
|
+
// MoE 30B (fits 24GB+ at Q4 despite total param count)
|
|
115
|
+
"qwen3-coder:30b", "qwen3:30b", "nemotron-3-nano:30b",
|
|
116
|
+
// 22–27B tier
|
|
117
|
+
"gemma3:27b", "devstral:24b", "devstral-small-2:24b", "mistral-small",
|
|
118
|
+
// 14B tier (16GB+)
|
|
119
|
+
"qwen3:14b", "qwen2.5:14b", "mistral-nemo",
|
|
120
|
+
// 7–9B tier (8GB+)
|
|
121
|
+
"qwen3:8b", "llama3.2:11b", "gemma3:9b", "qwen2.5:7b", "llama3.1:8b", "mistral:7b",
|
|
122
|
+
// 4B tier
|
|
123
|
+
"qwen3:4b", "phi4-mini", "gemma3:4b",
|
|
124
|
+
// Sub-3B (last resort)
|
|
125
|
+
"llama3.2:3b", "qwen3:1.7b", "llama3.2:1b", "qwen3:0.6b",
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Code-focused preference for cleave leaf workers.
|
|
130
|
+
* Biases toward Coder/Devstral/Codestral variants at each size tier.
|
|
131
|
+
*/
|
|
132
|
+
export const PREFERRED_ORDER_CODE: string[] = [
|
|
133
|
+
// 32B code tier
|
|
134
|
+
"qwen2.5-coder:32b", "qwen3:32b", "codestral:22b",
|
|
135
|
+
// MoE 30B (excellent for agentic code tasks, lean inference)
|
|
136
|
+
"qwen3-coder:30b", "devstral:24b", "devstral-small-2:24b",
|
|
137
|
+
// 30B general
|
|
138
|
+
"qwen3:30b",
|
|
139
|
+
// 14B code tier
|
|
140
|
+
"qwen2.5-coder:14b", "qwen3:14b", "qwen2.5:14b",
|
|
141
|
+
// 7–8B code tier
|
|
142
|
+
"qwen2.5-coder:7b", "qwen3:8b", "llama3.1:8b",
|
|
143
|
+
// Small code
|
|
144
|
+
"qwen2.5-coder:3b", "qwen3:4b", "llama3.2:3b",
|
|
145
|
+
// General fallbacks
|
|
146
|
+
"nemotron-3-nano:30b", "qwen2.5:7b", "gemma3:9b",
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Model family prefixes for startsWith matching (used by project-memory compaction).
|
|
151
|
+
* Catches any installed variant not listed explicitly in PREFERRED_ORDER.
|
|
152
|
+
* Ordered by general capability preference within each family.
|
|
153
|
+
*/
|
|
154
|
+
export const PREFERRED_FAMILIES: string[] = [
|
|
155
|
+
"qwen3-coder", "qwen3", "qwen2.5-coder", "qwen2.5",
|
|
156
|
+
"llama3", "gemma3", "devstral", "mistral", "nemotron", "phi",
|
|
157
|
+
];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface PersistedDriverSelection {
|
|
5
|
+
provider: string;
|
|
6
|
+
modelId: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface PiConfig {
|
|
10
|
+
lastUsedModel?: PersistedDriverSelection;
|
|
11
|
+
operatorProfile?: unknown;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function configPath(root: string): string {
|
|
16
|
+
return join(root, ".pi", "config.json");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function loadPiConfig(root: string): PiConfig {
|
|
20
|
+
try {
|
|
21
|
+
const path = configPath(root);
|
|
22
|
+
if (!existsSync(path)) return {};
|
|
23
|
+
const raw = readFileSync(path, "utf-8");
|
|
24
|
+
const parsed = JSON.parse(raw);
|
|
25
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed as PiConfig : {};
|
|
26
|
+
} catch {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function savePiConfig(root: string, config: PiConfig): void {
|
|
32
|
+
const dir = join(root, ".pi");
|
|
33
|
+
mkdirSync(dir, { recursive: true });
|
|
34
|
+
writeFileSync(configPath(root), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function readLastUsedModel(root: string): PersistedDriverSelection | undefined {
|
|
38
|
+
const value = loadPiConfig(root).lastUsedModel;
|
|
39
|
+
if (!value || typeof value !== "object") return undefined;
|
|
40
|
+
const record = value as unknown as Record<string, unknown>;
|
|
41
|
+
const provider = record.provider;
|
|
42
|
+
const modelId = record.modelId;
|
|
43
|
+
if (typeof provider !== "string" || typeof modelId !== "string") return undefined;
|
|
44
|
+
return { provider, modelId };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function writeLastUsedModel(root: string, selection: PersistedDriverSelection): void {
|
|
48
|
+
const config = loadPiConfig(root);
|
|
49
|
+
config.lastUsedModel = selection;
|
|
50
|
+
savePiConfig(root, config);
|
|
51
|
+
}
|