@vincemakes/kiso-code 0.1.22 → 0.1.24
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/chat.d.ts +4 -4
- package/dist/chat.js +8 -5
- package/dist/config.d.ts +96 -0
- package/dist/config.js +263 -0
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +55 -1
- package/dist/index.js +64 -29
- package/dist/mode.d.ts +4 -3
- package/dist/mode.js +4 -4
- package/dist/state.d.ts +20 -0
- package/dist/state.js +30 -0
- package/dist/trust-ui.js +7 -0
- package/package.json +9 -9
package/dist/chat.d.ts
CHANGED
|
@@ -21,10 +21,10 @@ export interface AutoCompact {
|
|
|
21
21
|
/** Parse KISO_AUTO_COMPACT — an invalid value is OFF, never a crash. */
|
|
22
22
|
export declare function autoCompactFromEnv(): AutoCompact | undefined;
|
|
23
23
|
/**
|
|
24
|
-
* C 区: the model window in tokens — KISO_CONTEXT_WINDOW
|
|
25
|
-
*
|
|
26
|
-
* the status line's ~ctx estimate
|
|
27
|
-
* truth for the window.
|
|
24
|
+
* C 区: the model window in tokens — env (KISO_CONTEXT_WINDOW) beats the
|
|
25
|
+
* config window (合并轮 B), both beat the 200k default. The microcompact
|
|
26
|
+
* threshold is derived from it (50%), and the status line's ~ctx estimate
|
|
27
|
+
* is measured against it — one source of truth for the window.
|
|
28
28
|
*/
|
|
29
29
|
export declare function contextWindowTokens(): number;
|
|
30
30
|
/**
|
package/dist/chat.js
CHANGED
|
@@ -9,7 +9,7 @@ import { escapeTerminal, kUnit, palette, renderEvent, renderRecap } from "@vince
|
|
|
9
9
|
import { editFileDiff, writeFileDiff } from "@vincemakes/kiso-tui";
|
|
10
10
|
import { canonicalTargetPath } from "@vincemakes/kiso-tools-node";
|
|
11
11
|
import { dispatch } from "./dispatch.js";
|
|
12
|
-
import { CANCELLED, agentModel, body, bodyLog, dock } from "./state.js";
|
|
12
|
+
import { CANCELLED, agentModel, body, bodyLog, configuredWindow, dock } from "./state.js";
|
|
13
13
|
import { ask, pendingAsk, resolveUncertains } from "./trust-ui.js";
|
|
14
14
|
import { FauxExhaustionError, failOnFauxExhaustion } from "./faux-glue.js";
|
|
15
15
|
import { MODES, getMode, setMode } from "./mode.js";
|
|
@@ -26,12 +26,15 @@ export function autoCompactFromEnv() {
|
|
|
26
26
|
return { thresholdRatio: ratio };
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
|
-
* C 区: the model window in tokens — KISO_CONTEXT_WINDOW
|
|
30
|
-
*
|
|
31
|
-
* the status line's ~ctx estimate
|
|
32
|
-
* truth for the window.
|
|
29
|
+
* C 区: the model window in tokens — env (KISO_CONTEXT_WINDOW) beats the
|
|
30
|
+
* config window (合并轮 B), both beat the 200k default. The microcompact
|
|
31
|
+
* threshold is derived from it (50%), and the status line's ~ctx estimate
|
|
32
|
+
* is measured against it — one source of truth for the window.
|
|
33
33
|
*/
|
|
34
34
|
export function contextWindowTokens() {
|
|
35
|
+
const windowOverride = configuredWindow;
|
|
36
|
+
if (windowOverride !== undefined)
|
|
37
|
+
return windowOverride;
|
|
35
38
|
const window = Number.parseInt(process.env.KISO_CONTEXT_WINDOW ?? "", 10);
|
|
36
39
|
return Number.isFinite(window) && window > 0 ? window : DEFAULT_CONTEXT_WINDOW;
|
|
37
40
|
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 合并轮 B — the config surface (schema v1).
|
|
3
|
+
*
|
|
4
|
+
* Two files: the user config `~/.kiso/config.json` and the project config
|
|
5
|
+
* `<cwd>/.kiso/config.json` (an artifact of the E3 trust package — a
|
|
6
|
+
* trusted project's config applies, an untrusted one is never even read).
|
|
7
|
+
* Precedence: flags > env > project config > user config > defaults.
|
|
8
|
+
*
|
|
9
|
+
* Schema v1:
|
|
10
|
+
* model?: string — a profile NAME (models.<name>) or "provider/model"
|
|
11
|
+
* direct write (provider: "openai-compat" | "anthropic")
|
|
12
|
+
* models?: { [name]: { kind: "openai-compat"|"anthropic",
|
|
13
|
+
* baseUrl?: string, model: string, apiKeyEnv: string } }
|
|
14
|
+
* mode?: "manual"|"default"|"accept-edits"|"plan"|"bypass"
|
|
15
|
+
* contextWindow?: number — tokens
|
|
16
|
+
* autoCompact?: { thresholdRatio: number } — 0<r<1; default off
|
|
17
|
+
* projectTrust?: "ask" | "never" — no "always" (the ruling)
|
|
18
|
+
*
|
|
19
|
+
* Credential discipline (HARD): a config NEVER stores a key — only the
|
|
20
|
+
* apiKeyEnv NAME it reads from the environment at use time. A profile
|
|
21
|
+
* whose apiKeyEnv is unset is UNAVAILABLE (listed as such; switching to
|
|
22
|
+
* it is refused loudly) — never a crash.
|
|
23
|
+
*
|
|
24
|
+
* Failure discipline: a broken JSON file fails LOUDLY (file + reason,
|
|
25
|
+
* non-zero exit) — a silently ignored config would mislead. A known key
|
|
26
|
+
* with an invalid value is likewise loud; unknown top-level keys are
|
|
27
|
+
* ignored (forward compatibility).
|
|
28
|
+
*/
|
|
29
|
+
import type { Mode } from "./mode.js";
|
|
30
|
+
export type ProfileKind = "openai-compat" | "anthropic";
|
|
31
|
+
export interface ModelProfile {
|
|
32
|
+
readonly kind: ProfileKind;
|
|
33
|
+
readonly baseUrl?: string;
|
|
34
|
+
readonly model: string;
|
|
35
|
+
/** The env var NAME holding the key — never the key itself. */
|
|
36
|
+
readonly apiKeyEnv: string;
|
|
37
|
+
}
|
|
38
|
+
export interface AutoCompactConfig {
|
|
39
|
+
readonly thresholdRatio: number;
|
|
40
|
+
}
|
|
41
|
+
export interface KisoConfig {
|
|
42
|
+
readonly model?: string;
|
|
43
|
+
readonly models?: Readonly<Record<string, ModelProfile>>;
|
|
44
|
+
readonly mode?: Mode;
|
|
45
|
+
readonly contextWindow?: number;
|
|
46
|
+
readonly autoCompact?: AutoCompactConfig;
|
|
47
|
+
readonly projectTrust?: "ask" | "never";
|
|
48
|
+
}
|
|
49
|
+
/** The resolved, merged config — project wins over user, both validated. */
|
|
50
|
+
export interface ResolvedConfig {
|
|
51
|
+
readonly user: KisoConfig;
|
|
52
|
+
readonly project: KisoConfig;
|
|
53
|
+
}
|
|
54
|
+
export declare class ConfigError extends Error {
|
|
55
|
+
}
|
|
56
|
+
/** Parse + validate one config file. Broken JSON / invalid known values →
|
|
57
|
+
* ConfigError with the file path (LOUD). Unknown keys pass (forward compat). */
|
|
58
|
+
export declare function parseConfig(text: string, source: string): KisoConfig;
|
|
59
|
+
/** The user config — never gated (the user's own file). */
|
|
60
|
+
export declare function loadUserConfig(): KisoConfig | null;
|
|
61
|
+
/** The project config — read ONLY when the project's .kiso passed the E3
|
|
62
|
+
* trust gate (an untrusted project's config is never even read). */
|
|
63
|
+
export declare function loadProjectConfig(cwd: string, trusted: boolean): KisoConfig | null;
|
|
64
|
+
/** Merge: project wins over user (each layer's own keys only). */
|
|
65
|
+
export declare function mergeConfigs(user: KisoConfig | null, project: KisoConfig | null): KisoConfig;
|
|
66
|
+
/** A profile is available when its apiKeyEnv var is set (the key exists). */
|
|
67
|
+
export declare function profileAvailable(p: ModelProfile): boolean;
|
|
68
|
+
/** The resolved runtime model — what the adapter is built from. */
|
|
69
|
+
export interface ResolvedModel {
|
|
70
|
+
readonly name: string;
|
|
71
|
+
readonly profile: ModelProfile;
|
|
72
|
+
readonly apiKey: string;
|
|
73
|
+
}
|
|
74
|
+
/** "provider/model" direct write → a profile. */
|
|
75
|
+
export declare function directWriteProfile(value: string): ModelProfile | null;
|
|
76
|
+
/**
|
|
77
|
+
* Resolve the model — precedence: --model flag > env (the OPENAI_* /
|
|
78
|
+
* ANTHROPIC_* key vars) > project config > user config > default (faux).
|
|
79
|
+
* The flag names a profile or writes provider/model directly. A named
|
|
80
|
+
* profile that does not exist is a LOUD ConfigError; an unavailable one
|
|
81
|
+
* (env key missing) is refused with the reason — never a silent fallback.
|
|
82
|
+
* Returns null when nothing resolves (faux mode).
|
|
83
|
+
*/
|
|
84
|
+
export declare function resolveModel(modelFlag: string | undefined, merged: KisoConfig): ResolvedModel | null;
|
|
85
|
+
/** Mode: env (KISO_MODE) beats config.mode; the --mode flag is applied by
|
|
86
|
+
* main before this runs (flags are the top of the chain). */
|
|
87
|
+
export declare function resolveModeFromConfig(merged: KisoConfig): Mode | undefined;
|
|
88
|
+
/** Context window: env (KISO_CONTEXT_WINDOW) > config.contextWindow >
|
|
89
|
+
* default (200k — the caller's default). */
|
|
90
|
+
export declare function resolveContextWindow(merged: KisoConfig): number | undefined;
|
|
91
|
+
/** Auto-compact: env (KISO_AUTO_COMPACT) > config.autoCompact > off. An
|
|
92
|
+
* invalid env value is OFF (it set the env → it wins, and it is invalid). */
|
|
93
|
+
export declare function resolveAutoCompact(merged: KisoConfig): AutoCompactConfig | undefined;
|
|
94
|
+
/** Project-trust policy: config.projectTrust (project wins over user) —
|
|
95
|
+
* "ask" (the E3 gate, default) or "never" (the gate auto-refuses). */
|
|
96
|
+
export declare function resolveProjectTrustPolicy(merged: KisoConfig): "ask" | "never";
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 合并轮 B — the config surface (schema v1).
|
|
3
|
+
*
|
|
4
|
+
* Two files: the user config `~/.kiso/config.json` and the project config
|
|
5
|
+
* `<cwd>/.kiso/config.json` (an artifact of the E3 trust package — a
|
|
6
|
+
* trusted project's config applies, an untrusted one is never even read).
|
|
7
|
+
* Precedence: flags > env > project config > user config > defaults.
|
|
8
|
+
*
|
|
9
|
+
* Schema v1:
|
|
10
|
+
* model?: string — a profile NAME (models.<name>) or "provider/model"
|
|
11
|
+
* direct write (provider: "openai-compat" | "anthropic")
|
|
12
|
+
* models?: { [name]: { kind: "openai-compat"|"anthropic",
|
|
13
|
+
* baseUrl?: string, model: string, apiKeyEnv: string } }
|
|
14
|
+
* mode?: "manual"|"default"|"accept-edits"|"plan"|"bypass"
|
|
15
|
+
* contextWindow?: number — tokens
|
|
16
|
+
* autoCompact?: { thresholdRatio: number } — 0<r<1; default off
|
|
17
|
+
* projectTrust?: "ask" | "never" — no "always" (the ruling)
|
|
18
|
+
*
|
|
19
|
+
* Credential discipline (HARD): a config NEVER stores a key — only the
|
|
20
|
+
* apiKeyEnv NAME it reads from the environment at use time. A profile
|
|
21
|
+
* whose apiKeyEnv is unset is UNAVAILABLE (listed as such; switching to
|
|
22
|
+
* it is refused loudly) — never a crash.
|
|
23
|
+
*
|
|
24
|
+
* Failure discipline: a broken JSON file fails LOUDLY (file + reason,
|
|
25
|
+
* non-zero exit) — a silently ignored config would mislead. A known key
|
|
26
|
+
* with an invalid value is likewise loud; unknown top-level keys are
|
|
27
|
+
* ignored (forward compatibility).
|
|
28
|
+
*/
|
|
29
|
+
import { readFileSync } from "node:fs";
|
|
30
|
+
import { join } from "node:path";
|
|
31
|
+
import { kisoHome } from "./state.js";
|
|
32
|
+
const KINDS = ["openai-compat", "anthropic"];
|
|
33
|
+
const MODES_LIST = ["manual", "default", "accept-edits", "plan", "bypass"];
|
|
34
|
+
export class ConfigError extends Error {
|
|
35
|
+
}
|
|
36
|
+
/** Parse + validate one config file. Broken JSON / invalid known values →
|
|
37
|
+
* ConfigError with the file path (LOUD). Unknown keys pass (forward compat). */
|
|
38
|
+
export function parseConfig(text, source) {
|
|
39
|
+
let raw;
|
|
40
|
+
try {
|
|
41
|
+
raw = JSON.parse(text);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
throw new ConfigError(`config ${source}: broken JSON — ${err instanceof Error ? err.message : String(err)}`);
|
|
45
|
+
}
|
|
46
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
47
|
+
throw new ConfigError(`config ${source}: the root must be a JSON object`);
|
|
48
|
+
}
|
|
49
|
+
const out = {};
|
|
50
|
+
const obj = raw;
|
|
51
|
+
const fail = (key, why) => {
|
|
52
|
+
throw new ConfigError(`config ${source}: ${key} — ${why}`);
|
|
53
|
+
};
|
|
54
|
+
if (obj.model !== undefined) {
|
|
55
|
+
if (typeof obj.model !== "string" || obj.model === "")
|
|
56
|
+
fail("model", "expected a profile name or provider/model string");
|
|
57
|
+
out.model = obj.model;
|
|
58
|
+
}
|
|
59
|
+
if (obj.models !== undefined) {
|
|
60
|
+
if (obj.models === null || typeof obj.models !== "object" || Array.isArray(obj.models))
|
|
61
|
+
fail("models", "expected an object of profiles");
|
|
62
|
+
const models = {};
|
|
63
|
+
for (const [name, v] of Object.entries(obj.models)) {
|
|
64
|
+
if (v === null || typeof v !== "object" || Array.isArray(v))
|
|
65
|
+
fail(`models.${name}`, "expected a profile object");
|
|
66
|
+
const p = v;
|
|
67
|
+
if (typeof p.kind !== "string" || !KINDS.includes(p.kind))
|
|
68
|
+
fail(`models.${name}.kind`, `expected one of ${KINDS.join(", ")}`);
|
|
69
|
+
if (typeof p.model !== "string" || p.model === "")
|
|
70
|
+
fail(`models.${name}.model`, "expected a model string");
|
|
71
|
+
if (typeof p.apiKeyEnv !== "string" || p.apiKeyEnv === "")
|
|
72
|
+
fail(`models.${name}.apiKeyEnv`, "expected an env var name (the config never stores keys)");
|
|
73
|
+
if (p.baseUrl !== undefined && typeof p.baseUrl !== "string")
|
|
74
|
+
fail(`models.${name}.baseUrl`, "expected a string");
|
|
75
|
+
models[name] = {
|
|
76
|
+
kind: p.kind,
|
|
77
|
+
model: p.model,
|
|
78
|
+
apiKeyEnv: p.apiKeyEnv,
|
|
79
|
+
...(typeof p.baseUrl === "string" ? { baseUrl: p.baseUrl } : {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
out.models = models;
|
|
83
|
+
}
|
|
84
|
+
if (obj.mode !== undefined) {
|
|
85
|
+
if (typeof obj.mode !== "string" || !MODES_LIST.includes(obj.mode))
|
|
86
|
+
fail("mode", `expected one of ${MODES_LIST.join(", ")}`);
|
|
87
|
+
out.mode = obj.mode;
|
|
88
|
+
}
|
|
89
|
+
if (obj.contextWindow !== undefined) {
|
|
90
|
+
if (typeof obj.contextWindow !== "number" || !Number.isFinite(obj.contextWindow) || obj.contextWindow <= 0) {
|
|
91
|
+
fail("contextWindow", "expected a positive token count");
|
|
92
|
+
}
|
|
93
|
+
out.contextWindow = obj.contextWindow;
|
|
94
|
+
}
|
|
95
|
+
if (obj.autoCompact !== undefined) {
|
|
96
|
+
if (obj.autoCompact === null || typeof obj.autoCompact !== "object" || Array.isArray(obj.autoCompact))
|
|
97
|
+
fail("autoCompact", "expected { thresholdRatio }");
|
|
98
|
+
const r = obj.autoCompact.thresholdRatio;
|
|
99
|
+
if (typeof r !== "number" || !Number.isFinite(r) || r <= 0 || r >= 1)
|
|
100
|
+
fail("autoCompact.thresholdRatio", "expected a number strictly between 0 and 1");
|
|
101
|
+
out.autoCompact = { thresholdRatio: r };
|
|
102
|
+
}
|
|
103
|
+
if (obj.projectTrust !== undefined) {
|
|
104
|
+
if (obj.projectTrust !== "ask" && obj.projectTrust !== "never")
|
|
105
|
+
fail("projectTrust", 'expected "ask" or "never" (there is deliberately no "always")');
|
|
106
|
+
out.projectTrust = obj.projectTrust;
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
function readConfigFile(path, source) {
|
|
111
|
+
let text;
|
|
112
|
+
try {
|
|
113
|
+
text = readFileSync(path, "utf8");
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (err.code === "ENOENT")
|
|
117
|
+
return null;
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
120
|
+
return parseConfig(text, source);
|
|
121
|
+
}
|
|
122
|
+
/** The user config — never gated (the user's own file). */
|
|
123
|
+
export function loadUserConfig() {
|
|
124
|
+
return readConfigFile(join(kisoHome(), "config.json"), "~/.kiso/config.json");
|
|
125
|
+
}
|
|
126
|
+
/** The project config — read ONLY when the project's .kiso passed the E3
|
|
127
|
+
* trust gate (an untrusted project's config is never even read). */
|
|
128
|
+
export function loadProjectConfig(cwd, trusted) {
|
|
129
|
+
if (!trusted)
|
|
130
|
+
return null;
|
|
131
|
+
return readConfigFile(join(cwd, ".kiso", "config.json"), "<cwd>/.kiso/config.json");
|
|
132
|
+
}
|
|
133
|
+
/** Merge: project wins over user (each layer's own keys only). */
|
|
134
|
+
export function mergeConfigs(user, project) {
|
|
135
|
+
const u = user ?? {};
|
|
136
|
+
const p = project ?? {};
|
|
137
|
+
return {
|
|
138
|
+
...(u.model !== undefined ? { model: u.model } : {}),
|
|
139
|
+
...(p.model !== undefined ? { model: p.model } : {}),
|
|
140
|
+
...(u.models !== undefined ? { models: u.models } : {}),
|
|
141
|
+
...(p.models !== undefined ? { models: { ...u.models, ...p.models } } : u.models !== undefined ? { models: u.models } : {}),
|
|
142
|
+
...(u.mode !== undefined ? { mode: u.mode } : {}),
|
|
143
|
+
...(p.mode !== undefined ? { mode: p.mode } : {}),
|
|
144
|
+
...(u.contextWindow !== undefined ? { contextWindow: u.contextWindow } : {}),
|
|
145
|
+
...(p.contextWindow !== undefined ? { contextWindow: p.contextWindow } : {}),
|
|
146
|
+
...(u.autoCompact !== undefined ? { autoCompact: u.autoCompact } : {}),
|
|
147
|
+
...(p.autoCompact !== undefined ? { autoCompact: p.autoCompact } : {}),
|
|
148
|
+
...(u.projectTrust !== undefined ? { projectTrust: u.projectTrust } : {}),
|
|
149
|
+
...(p.projectTrust !== undefined ? { projectTrust: p.projectTrust } : {}),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/** A profile is available when its apiKeyEnv var is set (the key exists). */
|
|
153
|
+
export function profileAvailable(p) {
|
|
154
|
+
return process.env[p.apiKeyEnv] !== undefined;
|
|
155
|
+
}
|
|
156
|
+
/** "provider/model" direct write → a profile. */
|
|
157
|
+
export function directWriteProfile(value) {
|
|
158
|
+
const slash = value.indexOf("/");
|
|
159
|
+
if (slash <= 0 || slash === value.length - 1)
|
|
160
|
+
return null;
|
|
161
|
+
const kind = value.slice(0, slash);
|
|
162
|
+
if (kind !== "openai-compat" && kind !== "anthropic")
|
|
163
|
+
return null;
|
|
164
|
+
return {
|
|
165
|
+
kind,
|
|
166
|
+
model: value.slice(slash + 1),
|
|
167
|
+
apiKeyEnv: kind === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY",
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Resolve the model — precedence: --model flag > env (the OPENAI_* /
|
|
172
|
+
* ANTHROPIC_* key vars) > project config > user config > default (faux).
|
|
173
|
+
* The flag names a profile or writes provider/model directly. A named
|
|
174
|
+
* profile that does not exist is a LOUD ConfigError; an unavailable one
|
|
175
|
+
* (env key missing) is refused with the reason — never a silent fallback.
|
|
176
|
+
* Returns null when nothing resolves (faux mode).
|
|
177
|
+
*/
|
|
178
|
+
export function resolveModel(modelFlag, merged) {
|
|
179
|
+
if (modelFlag !== undefined) {
|
|
180
|
+
const direct = directWriteProfile(modelFlag);
|
|
181
|
+
if (direct !== null)
|
|
182
|
+
return resolveProfile(modelFlag, direct);
|
|
183
|
+
const p = merged.models?.[modelFlag];
|
|
184
|
+
if (p === undefined)
|
|
185
|
+
throw new ConfigError(`unknown model profile: ${modelFlag} (see models in ~/.kiso/config.json)`);
|
|
186
|
+
return resolveProfile(modelFlag, p);
|
|
187
|
+
}
|
|
188
|
+
// env beats config: a key in the environment names the provider.
|
|
189
|
+
if (process.env.OPENAI_API_KEY !== undefined) {
|
|
190
|
+
return {
|
|
191
|
+
name: process.env.OPENAI_MODEL ?? "gpt-4o",
|
|
192
|
+
profile: {
|
|
193
|
+
kind: "openai-compat",
|
|
194
|
+
model: process.env.OPENAI_MODEL ?? "gpt-4o",
|
|
195
|
+
...(process.env.OPENAI_BASE_URL !== undefined ? { baseUrl: process.env.OPENAI_BASE_URL } : {}),
|
|
196
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
197
|
+
},
|
|
198
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (process.env.ANTHROPIC_API_KEY !== undefined) {
|
|
202
|
+
return {
|
|
203
|
+
name: process.env.ANTHROPIC_MODEL ?? "claude-sonnet-5",
|
|
204
|
+
profile: {
|
|
205
|
+
kind: "anthropic",
|
|
206
|
+
model: process.env.ANTHROPIC_MODEL ?? "claude-sonnet-5",
|
|
207
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
208
|
+
},
|
|
209
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
// config (project wins over user) names a model; default is faux.
|
|
213
|
+
const configured = merged.model;
|
|
214
|
+
if (configured !== undefined) {
|
|
215
|
+
const direct = directWriteProfile(configured);
|
|
216
|
+
if (direct !== null)
|
|
217
|
+
return resolveProfile(configured, direct);
|
|
218
|
+
const p = merged.models?.[configured];
|
|
219
|
+
if (p === undefined)
|
|
220
|
+
throw new ConfigError(`config model "${configured}" is not a defined profile (see models in ~/.kiso/config.json)`);
|
|
221
|
+
return resolveProfile(configured, p);
|
|
222
|
+
}
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
function resolveProfile(name, p) {
|
|
226
|
+
if (!profileAvailable(p)) {
|
|
227
|
+
throw new ConfigError(`model ${name}: unavailable — the env var ${p.apiKeyEnv} is not set (configs never store keys, only the env-var name)`);
|
|
228
|
+
}
|
|
229
|
+
return { name, profile: p, apiKey: process.env[p.apiKeyEnv] };
|
|
230
|
+
}
|
|
231
|
+
/** Mode: env (KISO_MODE) beats config.mode; the --mode flag is applied by
|
|
232
|
+
* main before this runs (flags are the top of the chain). */
|
|
233
|
+
export function resolveModeFromConfig(merged) {
|
|
234
|
+
const fromEnv = process.env.KISO_MODE;
|
|
235
|
+
if (fromEnv !== undefined)
|
|
236
|
+
return fromEnv;
|
|
237
|
+
return merged.mode;
|
|
238
|
+
}
|
|
239
|
+
/** Context window: env (KISO_CONTEXT_WINDOW) > config.contextWindow >
|
|
240
|
+
* default (200k — the caller's default). */
|
|
241
|
+
export function resolveContextWindow(merged) {
|
|
242
|
+
const fromEnv = Number.parseInt(process.env.KISO_CONTEXT_WINDOW ?? "", 10);
|
|
243
|
+
if (Number.isFinite(fromEnv) && fromEnv > 0)
|
|
244
|
+
return fromEnv;
|
|
245
|
+
return merged.contextWindow;
|
|
246
|
+
}
|
|
247
|
+
/** Auto-compact: env (KISO_AUTO_COMPACT) > config.autoCompact > off. An
|
|
248
|
+
* invalid env value is OFF (it set the env → it wins, and it is invalid). */
|
|
249
|
+
export function resolveAutoCompact(merged) {
|
|
250
|
+
const raw = process.env.KISO_AUTO_COMPACT;
|
|
251
|
+
if (raw !== undefined) {
|
|
252
|
+
const ratio = Number.parseFloat(raw);
|
|
253
|
+
if (!Number.isFinite(ratio) || ratio <= 0 || ratio >= 1)
|
|
254
|
+
return undefined;
|
|
255
|
+
return { thresholdRatio: ratio };
|
|
256
|
+
}
|
|
257
|
+
return merged.autoCompact;
|
|
258
|
+
}
|
|
259
|
+
/** Project-trust policy: config.projectTrust (project wins over user) —
|
|
260
|
+
* "ask" (the E3 gate, default) or "never" (the gate auto-refuses). */
|
|
261
|
+
export function resolveProjectTrustPolicy(merged) {
|
|
262
|
+
return merged.projectTrust ?? "ask";
|
|
263
|
+
}
|
package/dist/dispatch.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* turns. The bodies moved verbatim from chat()'s closure; chat provides
|
|
4
4
|
* the context (the chain, the run state, the prompt arming).
|
|
5
5
|
*/
|
|
6
|
-
import type
|
|
6
|
+
import { type AgentSession } from "@vincemakes/kiso-runtime";
|
|
7
7
|
import { type LineInput } from "./state.js";
|
|
8
8
|
/** Everything dispatch touches that chat() owns. */
|
|
9
9
|
export interface DispatchCtx {
|
package/dist/dispatch.js
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* the context (the chain, the run state, the prompt arming).
|
|
5
5
|
*/
|
|
6
6
|
import { escapeTerminal, palette } from "@vincemakes/kiso-tui";
|
|
7
|
+
import { buildAdapter } from "@vincemakes/kiso-runtime";
|
|
7
8
|
import { MODES, getMode, setMode } from "./mode.js";
|
|
8
|
-
import { body, bodyLog } from "./state.js";
|
|
9
|
+
import { agentModel, body, bodyLog, configModels, setAgentModel, setCurrentModelName } from "./state.js";
|
|
10
|
+
import { directWriteProfile, profileAvailable } from "./config.js";
|
|
9
11
|
/** The ONE dispatcher — slash commands, exit, and turns. The recovery
|
|
10
12
|
* replay routes through it too — a queued "/last" must never become a
|
|
11
13
|
* user turn (v2c: the rl lives in main, so lines arrive earlier and the
|
|
@@ -23,6 +25,7 @@ export function dispatch(line, ctx) {
|
|
|
23
25
|
bodyLog(cmd("/last", "show the most recent tool call's input and output"));
|
|
24
26
|
bodyLog(cmd("/status", "show session id, event count, and context estimate"));
|
|
25
27
|
bodyLog(cmd("/mode", "show the approval tier; /mode <name> switches (manual/default/accept-edits/plan/bypass)"));
|
|
28
|
+
bodyLog(cmd("/model", "list model profiles; /model <name|provider/model> switches"));
|
|
26
29
|
bodyLog(cmd("/compact", "summarize the older conversation to free context"));
|
|
27
30
|
bodyLog(cmd("exit", "leave the session"));
|
|
28
31
|
ctx.input.prompt();
|
|
@@ -100,6 +103,57 @@ export function dispatch(line, ctx) {
|
|
|
100
103
|
});
|
|
101
104
|
return;
|
|
102
105
|
}
|
|
106
|
+
if (trimmed === "/model" || trimmed.startsWith("/model ")) {
|
|
107
|
+
// 合并轮 B: /model lists the profiles (with availability — the
|
|
108
|
+
// config never stores keys, only apiKeyEnv NAMES; an unset env
|
|
109
|
+
// marks the profile unavailable, never a crash) and switches the
|
|
110
|
+
// session's adapter — the NEXT turn uses it (session.setAdapter),
|
|
111
|
+
// the notice cell leaves the audit line in the body.
|
|
112
|
+
ctx.chainRef.current = ctx.chainRef.current.then(async () => {
|
|
113
|
+
const arg = trimmed.slice(6).trim();
|
|
114
|
+
if (arg === "") {
|
|
115
|
+
bodyLog(`model: ${agentModel}`);
|
|
116
|
+
const names = Object.keys(configModels);
|
|
117
|
+
if (names.length === 0) {
|
|
118
|
+
bodyLog("profiles: (none — define models in ~/.kiso/config.json)");
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
for (const name of names) {
|
|
122
|
+
const p = configModels[name];
|
|
123
|
+
bodyLog(` ${name} → ${p.kind}/${p.model} · ${p.apiKeyEnv} ${profileAvailable(p) ? "(available)" : "(unavailable)"}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
bodyLog("switch with /model <profile-name|provider/model>");
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
try {
|
|
130
|
+
const direct = directWriteProfile(arg);
|
|
131
|
+
const profile = direct ?? configModels[arg];
|
|
132
|
+
if (profile === undefined) {
|
|
133
|
+
bodyLog(`no such model profile: ${arg}`);
|
|
134
|
+
}
|
|
135
|
+
else if (!profileAvailable(profile)) {
|
|
136
|
+
bodyLog(`model ${arg}: unavailable — the env var ${profile.apiKeyEnv} is not set (configs never store keys, only the env-var name)`);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
const adapter = await buildAdapter(profile.kind, {
|
|
140
|
+
apiKey: process.env[profile.apiKeyEnv],
|
|
141
|
+
...(profile.baseUrl !== undefined ? { baseUrl: profile.baseUrl } : {}),
|
|
142
|
+
});
|
|
143
|
+
ctx.session.setAdapter(adapter);
|
|
144
|
+
setAgentModel(profile.model);
|
|
145
|
+
setCurrentModelName(arg);
|
|
146
|
+
body.notice(`model → ${arg} (${profile.model}) — takes effect on the next turn`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
body.notice(`[/model] failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
ctx.input.prompt();
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
103
157
|
if (trimmed === "/compact") {
|
|
104
158
|
// /compact (ADR-0044): the older conversation becomes one
|
|
105
159
|
// model summary — an OFF-LOOP call through the session's own
|
package/dist/index.js
CHANGED
|
@@ -31,10 +31,11 @@ import { createAgent, disposeExtensions, loadExtensions, loadProjectExtensions,
|
|
|
31
31
|
import { createFauxProvider } from "@vincemakes/kiso-evals";
|
|
32
32
|
import { createCodingTools } from "@vincemakes/kiso-tools-node";
|
|
33
33
|
import { MODES, modeExtensions, modeFromEnv, modeSystemPrompt, setMode } from "./mode.js";
|
|
34
|
-
import { body, bodyLog, dock, extensionsDir, loadedExtensions, mergedTempPaths, projectExtensions, sessionsDir, setAgentModel, setBody, setExtensionLists, userExtensions, VERSION } from "./state.js";
|
|
34
|
+
import { body, bodyLog, currentFaux, dock, extensionsDir, loadedExtensions, mergedConfig, mergedTempPaths, projectExtensions, sessionsDir, setAgentModel, setBody, setConfigModels, setConfiguredWindow, setCurrentFaux, setCurrentModelName, setExtensionLists, setMergedConfig, userExtensions, VERSION } from "./state.js";
|
|
35
35
|
import { interactivePrompt, resolveProjectTrust } from "./trust-ui.js";
|
|
36
36
|
import { fauxSkip, readFauxScript } from "./faux-glue.js";
|
|
37
37
|
import { autoCompactFromEnv, chat, contextWindowTokens } from "./chat.js";
|
|
38
|
+
import { loadProjectConfig, loadUserConfig, mergeConfigs, resolveAutoCompact, resolveContextWindow, resolveModel } from "./config.js";
|
|
38
39
|
import { resume } from "./resume.js";
|
|
39
40
|
// The moved exports stay reachable from this entry — the test imports
|
|
40
41
|
// (project-trust, coding-agent) never change (B4: 断言零改动).
|
|
@@ -250,7 +251,7 @@ export function composeSystemPrompt(cwd) {
|
|
|
250
251
|
const injected = readProjectInstructions(cwd);
|
|
251
252
|
return injected === "" ? SYSTEM_PROMPT : `${SYSTEM_PROMPT}\n${injected}`;
|
|
252
253
|
}
|
|
253
|
-
async function makeAgent(fauxSkipTurns = 0, input) {
|
|
254
|
+
async function makeAgent(fauxSkipTurns = 0, input, modelFlag) {
|
|
254
255
|
const store = new SessionStore(sessionsDir());
|
|
255
256
|
// E3: the project-level trust gate runs BEFORE any extension load (the
|
|
256
257
|
// mcp/skills merges must be in the env when the user-level extensions
|
|
@@ -266,24 +267,27 @@ async function makeAgent(fauxSkipTurns = 0, input) {
|
|
|
266
267
|
else {
|
|
267
268
|
setExtensionLists(user, [], user);
|
|
268
269
|
}
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
// provider
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
270
|
+
// 合并轮 B — the config surface: user config + (trusted) project config,
|
|
271
|
+
// resolved with flags > env > project > user > default. The CLI never
|
|
272
|
+
// imports provider SDKs directly — the runtime's lazy provider
|
|
273
|
+
// resolution owns them (a config profile only ever NAMES an env var for
|
|
274
|
+
// its key; the key itself never sits in a config file).
|
|
275
|
+
const userCfg = loadUserConfig();
|
|
276
|
+
const projectCfg = loadProjectConfig(process.cwd(), project !== null);
|
|
277
|
+
const merged = mergeConfigs(userCfg, projectCfg);
|
|
278
|
+
setMergedConfig(merged);
|
|
279
|
+
setConfigModels(merged.models ?? {});
|
|
280
|
+
setConfiguredWindow(resolveContextWindow(merged));
|
|
281
|
+
const resolved = resolveModel(modelFlag, merged);
|
|
282
|
+
const model = resolved === null ? "faux" : resolved.profile.model;
|
|
283
|
+
if (resolved === null) {
|
|
284
|
+
console.log("[faux mode — set ANTHROPIC_API_KEY or OPENAI_API_KEY, or configure models in ~/.kiso/config.json]\n");
|
|
285
|
+
setCurrentFaux(true);
|
|
286
|
+
setCurrentModelName("faux");
|
|
283
287
|
}
|
|
284
288
|
else {
|
|
285
|
-
|
|
286
|
-
|
|
289
|
+
setCurrentFaux(false);
|
|
290
|
+
setCurrentModelName(resolved.name);
|
|
287
291
|
}
|
|
288
292
|
setAgentModel(model); // v2b: the status bar shows it
|
|
289
293
|
const definition = {
|
|
@@ -312,11 +316,11 @@ async function makeAgent(fauxSkipTurns = 0, input) {
|
|
|
312
316
|
// project extensions (the deny>ask>allow composition keeps a user
|
|
313
317
|
// deny winning over any mode tier — bypass included).
|
|
314
318
|
extensions: [...modeExtensions(), ...loadedExtensions],
|
|
315
|
-
...(
|
|
319
|
+
...(resolved !== null
|
|
316
320
|
? {
|
|
317
|
-
provider,
|
|
318
|
-
apiKey:
|
|
319
|
-
...(
|
|
321
|
+
provider: resolved.profile.kind,
|
|
322
|
+
apiKey: resolved.apiKey,
|
|
323
|
+
...(resolved.profile.baseUrl !== undefined ? { baseUrl: resolved.profile.baseUrl } : {}),
|
|
320
324
|
}
|
|
321
325
|
: { adapter: createFauxProvider(readFauxScript().slice(fauxSkipTurns)) }),
|
|
322
326
|
};
|
|
@@ -327,6 +331,21 @@ async function main() {
|
|
|
327
331
|
// first makeAgent (the tier extensions read `current` live). The flag
|
|
328
332
|
// is stripped from the positional args, so it works in any position.
|
|
329
333
|
const args = process.argv.slice(2);
|
|
334
|
+
// 合并轮 B: --model <profile|provider/model> — the top of the model
|
|
335
|
+
// precedence chain; the value flows into makeAgent's config resolution.
|
|
336
|
+
let modelFlag;
|
|
337
|
+
const modelArgIdx = args.indexOf("--model");
|
|
338
|
+
if (modelArgIdx !== -1) {
|
|
339
|
+
modelFlag = args[modelArgIdx + 1];
|
|
340
|
+
if (modelFlag === undefined) {
|
|
341
|
+
console.error("usage: --model <profile-name|provider/model>");
|
|
342
|
+
process.exit(2);
|
|
343
|
+
}
|
|
344
|
+
args.splice(modelArgIdx, 2);
|
|
345
|
+
}
|
|
346
|
+
// Modes: --mode wins over KISO_MODE, which wins over the USER config's
|
|
347
|
+
// mode (the project config's mode applies later — after the trust gate,
|
|
348
|
+
// inside makeAgent — unless a higher layer already decided).
|
|
330
349
|
const modeFlag = args.indexOf("--mode");
|
|
331
350
|
if (modeFlag !== -1) {
|
|
332
351
|
const m = MODES.find((x) => x === args[modeFlag + 1]);
|
|
@@ -338,12 +357,14 @@ async function main() {
|
|
|
338
357
|
args.splice(modeFlag, 2);
|
|
339
358
|
}
|
|
340
359
|
else {
|
|
341
|
-
setMode(modeFromEnv());
|
|
360
|
+
setMode(modeFromEnv() ?? loadUserConfig()?.mode ?? "default");
|
|
342
361
|
}
|
|
343
362
|
const [command, arg] = args;
|
|
344
363
|
// 八: faux mode is the keyless demo script — an exhausted script must
|
|
345
|
-
// exit non-zero, never masquerade as a successful provider run.
|
|
346
|
-
|
|
364
|
+
// exit non-zero, never masquerade as a successful provider run. The
|
|
365
|
+
// verdict comes from makeAgent's config resolution now (a config
|
|
366
|
+
// profile can provide a real model with no OPENAI_* env).
|
|
367
|
+
let faux = true;
|
|
347
368
|
let agent;
|
|
348
369
|
// v2c: ONE input source per process — the raw-mode editor on a TTY
|
|
349
370
|
// (entered here, dock-bound, trusted before any extension loads),
|
|
@@ -360,6 +381,16 @@ async function main() {
|
|
|
360
381
|
onDock: () => dock.redraw(), // v2d-B: the freeze scrolls the dock up — re-pin it
|
|
361
382
|
}));
|
|
362
383
|
try {
|
|
384
|
+
// 合并轮 B: the project config's mode applies AFTER the trust gate
|
|
385
|
+
// (its verdict decides whether the project config exists at all) —
|
|
386
|
+
// unless a higher layer (--mode flag / KISO_MODE) already decided.
|
|
387
|
+
const applyConfigMode = () => {
|
|
388
|
+
if (modeFlag !== -1 || process.env.KISO_MODE !== undefined)
|
|
389
|
+
return;
|
|
390
|
+
const m = mergedConfig.mode;
|
|
391
|
+
if (m !== undefined)
|
|
392
|
+
setMode(m);
|
|
393
|
+
};
|
|
363
394
|
switch (command) {
|
|
364
395
|
case "chat": {
|
|
365
396
|
const id = arg ?? new Date().toISOString().replace(/[:.]/g, "-").slice(0, 16);
|
|
@@ -368,11 +399,13 @@ async function main() {
|
|
|
368
399
|
dock.enter();
|
|
369
400
|
// E 区: a resumed session continues the script at its durable
|
|
370
401
|
// position — never restarts it (fauxSkip).
|
|
371
|
-
const agent = await makeAgent(fauxSkip(id), input);
|
|
402
|
+
const agent = await makeAgent(fauxSkip(id), input, modelFlag);
|
|
403
|
+
applyConfigMode();
|
|
372
404
|
const session = await agent.session({ id });
|
|
373
405
|
bodyLog(`session ${id}\n`);
|
|
374
406
|
extensionsBanner();
|
|
375
|
-
|
|
407
|
+
faux = currentFaux;
|
|
408
|
+
await chat(session, faux, input, resolveAutoCompact(mergedConfig));
|
|
376
409
|
break;
|
|
377
410
|
}
|
|
378
411
|
case "resume": {
|
|
@@ -384,13 +417,15 @@ async function main() {
|
|
|
384
417
|
// (argv = [node, script, resume, id, prompt?]).
|
|
385
418
|
const prompt = process.argv[4];
|
|
386
419
|
dock.enter();
|
|
387
|
-
const agent = await makeAgent(fauxSkip(arg), input);
|
|
420
|
+
const agent = await makeAgent(fauxSkip(arg), input, modelFlag);
|
|
421
|
+
applyConfigMode();
|
|
388
422
|
const session = await agent.session({ id: arg });
|
|
423
|
+
faux = currentFaux;
|
|
389
424
|
await resume(session, prompt, faux, input);
|
|
390
425
|
break;
|
|
391
426
|
}
|
|
392
427
|
case "sessions": {
|
|
393
|
-
const agent = await makeAgent();
|
|
428
|
+
const agent = await makeAgent(0, undefined, modelFlag);
|
|
394
429
|
for (const meta of agent.sessions()) {
|
|
395
430
|
console.log(renderSessionLine(meta));
|
|
396
431
|
}
|
package/dist/mode.d.ts
CHANGED
|
@@ -14,9 +14,10 @@ export type Mode = "manual" | "default" | "accept-edits" | "plan" | "bypass";
|
|
|
14
14
|
export declare const MODES: readonly Mode[];
|
|
15
15
|
export declare function getMode(): Mode;
|
|
16
16
|
export declare function setMode(m: Mode): void;
|
|
17
|
-
/** The startup mode: KISO_MODE
|
|
18
|
-
*
|
|
19
|
-
|
|
17
|
+
/** The startup mode from env: KISO_MODE, or undefined when unset (the
|
|
18
|
+
* config layer's mode then applies — 合并轮 B; the --mode flag is
|
|
19
|
+
* applied by main before the first makeAgent and wins over everything). */
|
|
20
|
+
export declare function modeFromEnv(): Mode | undefined;
|
|
20
21
|
/** The five built-in mode tiers as chain extensions — named "mode:<tier>"
|
|
21
22
|
* so the runtime's decidedBy records exactly that (the runtime derives
|
|
22
23
|
* approvalPolicies from extensions[].approvals, tagging each with the
|
package/dist/mode.js
CHANGED
|
@@ -20,12 +20,12 @@ export function getMode() {
|
|
|
20
20
|
export function setMode(m) {
|
|
21
21
|
current = m;
|
|
22
22
|
}
|
|
23
|
-
/** The startup mode: KISO_MODE
|
|
24
|
-
*
|
|
23
|
+
/** The startup mode from env: KISO_MODE, or undefined when unset (the
|
|
24
|
+
* config layer's mode then applies — 合并轮 B; the --mode flag is
|
|
25
|
+
* applied by main before the first makeAgent and wins over everything). */
|
|
25
26
|
export function modeFromEnv() {
|
|
26
27
|
const raw = process.env.KISO_MODE;
|
|
27
|
-
|
|
28
|
-
return m ?? "default";
|
|
28
|
+
return MODES.find((x) => x === raw);
|
|
29
29
|
}
|
|
30
30
|
/** The per-tier verdict for a tool call — only when this tier is current. */
|
|
31
31
|
function tierVerdict(tier, call) {
|
package/dist/state.d.ts
CHANGED
|
@@ -52,6 +52,26 @@ export declare function bodyLog(text: string): void;
|
|
|
52
52
|
/** The model name for the status bar — set by makeAgent. */
|
|
53
53
|
export declare let agentModel: string;
|
|
54
54
|
export declare function setAgentModel(value: string): void;
|
|
55
|
+
/** 合并轮 B: whether the agent runs on the faux provider (no real key) —
|
|
56
|
+
* set inside makeAgent, read by main for chat/resume's exhaustion check. */
|
|
57
|
+
export declare let currentFaux: boolean;
|
|
58
|
+
export declare function setCurrentFaux(value: boolean): void;
|
|
59
|
+
/** 合并轮 B: the merged config (user + trusted project) as resolved by the
|
|
60
|
+
* LAST makeAgent — /model and autoCompact resolve against it. */
|
|
61
|
+
export declare let mergedConfig: import("./config.js").KisoConfig;
|
|
62
|
+
export declare function setMergedConfig(value: import("./config.js").KisoConfig): void;
|
|
63
|
+
/** 合并轮 B: the resolved context window (env > config.contextWindow) —
|
|
64
|
+
* chat.ts's contextWindowTokens() consults it before the env. */
|
|
65
|
+
export declare let configuredWindow: number | undefined;
|
|
66
|
+
export declare function setConfiguredWindow(value: number | undefined): void;
|
|
67
|
+
/** 合并轮 B: the merged config (user + trusted project) + the current
|
|
68
|
+
* model's NAME — /model lists and switches against them. */
|
|
69
|
+
export declare let configModels: Readonly<Record<string, import("./config.js").ModelProfile>>;
|
|
70
|
+
export declare function setConfigModels(models: Readonly<Record<string, import("./config.js").ModelProfile>>): void;
|
|
71
|
+
/** The name of the model currently driving the session ("faux" or the
|
|
72
|
+
* profile name / provider/model write / env model). */
|
|
73
|
+
export declare let currentModelName: string;
|
|
74
|
+
export declare function setCurrentModelName(value: string): void;
|
|
55
75
|
/** E1: the extensions loaded by makeAgent — their names feed the banner. */
|
|
56
76
|
export declare let loadedExtensions: readonly KisoExtension[];
|
|
57
77
|
/** E1: the USER-level extensions alone — the banner's unmarked part (E3:
|
package/dist/state.js
CHANGED
|
@@ -46,6 +46,36 @@ export let agentModel = "faux";
|
|
|
46
46
|
export function setAgentModel(value) {
|
|
47
47
|
agentModel = value;
|
|
48
48
|
}
|
|
49
|
+
/** 合并轮 B: whether the agent runs on the faux provider (no real key) —
|
|
50
|
+
* set inside makeAgent, read by main for chat/resume's exhaustion check. */
|
|
51
|
+
export let currentFaux = true;
|
|
52
|
+
export function setCurrentFaux(value) {
|
|
53
|
+
currentFaux = value;
|
|
54
|
+
}
|
|
55
|
+
/** 合并轮 B: the merged config (user + trusted project) as resolved by the
|
|
56
|
+
* LAST makeAgent — /model and autoCompact resolve against it. */
|
|
57
|
+
export let mergedConfig = {};
|
|
58
|
+
export function setMergedConfig(value) {
|
|
59
|
+
mergedConfig = value;
|
|
60
|
+
}
|
|
61
|
+
/** 合并轮 B: the resolved context window (env > config.contextWindow) —
|
|
62
|
+
* chat.ts's contextWindowTokens() consults it before the env. */
|
|
63
|
+
export let configuredWindow;
|
|
64
|
+
export function setConfiguredWindow(value) {
|
|
65
|
+
configuredWindow = value;
|
|
66
|
+
}
|
|
67
|
+
/** 合并轮 B: the merged config (user + trusted project) + the current
|
|
68
|
+
* model's NAME — /model lists and switches against them. */
|
|
69
|
+
export let configModels = {};
|
|
70
|
+
export function setConfigModels(models) {
|
|
71
|
+
configModels = models;
|
|
72
|
+
}
|
|
73
|
+
/** The name of the model currently driving the session ("faux" or the
|
|
74
|
+
* profile name / provider/model write / env model). */
|
|
75
|
+
export let currentModelName = "faux";
|
|
76
|
+
export function setCurrentModelName(value) {
|
|
77
|
+
currentModelName = value;
|
|
78
|
+
}
|
|
49
79
|
/** E1: the extensions loaded by makeAgent — their names feed the banner. */
|
|
50
80
|
export let loadedExtensions = [];
|
|
51
81
|
/** E1: the USER-level extensions alone — the banner's unmarked part (E3:
|
package/dist/trust-ui.js
CHANGED
|
@@ -10,6 +10,7 @@ import { join } from "node:path";
|
|
|
10
10
|
import { escapeTerminal, palette } from "@vincemakes/kiso-tui";
|
|
11
11
|
import { projectArtifacts, recordTrust, trustFor } from "@vincemakes/kiso-runtime";
|
|
12
12
|
import { CANCELLED, bodyLog, dock, kisoHome, mergedTempPaths } from "./state.js";
|
|
13
|
+
import { loadUserConfig, resolveProjectTrustPolicy } from "./config.js";
|
|
13
14
|
/** v2a: the interactive prompt — blue, the identity accent. readline owns
|
|
14
15
|
* the echo of what the user types; we own the prompt's color. (v2c: the
|
|
15
16
|
* readline prompt keeps "you> " — the brick ▌ is the dock's row only;
|
|
@@ -90,6 +91,12 @@ export async function resolveProjectTrust(input) {
|
|
|
90
91
|
const artifacts = await projectArtifacts(process.cwd());
|
|
91
92
|
if (artifacts === null)
|
|
92
93
|
return null; // no .kiso artifacts — nothing to gate
|
|
94
|
+
// 合并轮 B: projectTrust "never" (user config) — the gate auto-refuses:
|
|
95
|
+
// no ask, no record, nothing loads. There is deliberately no "always".
|
|
96
|
+
if (resolveProjectTrustPolicy(loadUserConfig() ?? {}) === "never") {
|
|
97
|
+
bodyLog(`[project .kiso] projectTrust: never — ${artifacts.root} not loaded`);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
93
100
|
const record = trustFor(artifacts.root, artifacts.digest);
|
|
94
101
|
if (record?.decision === "granted") {
|
|
95
102
|
applyProjectMerges(artifacts);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-code",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "kiso CLI
|
|
3
|
+
"version": "0.1.24",
|
|
4
|
+
"description": "kiso CLI \u2014 the coding-agent reference product: kiso chat / kiso resume / kiso sessions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
"test": "vitest run"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@vincemakes/kiso-core": "0.1.
|
|
22
|
-
"@vincemakes/kiso-evals": "0.1.
|
|
23
|
-
"@vincemakes/kiso-provider-anthropic": "0.1.
|
|
24
|
-
"@vincemakes/kiso-provider-openai": "0.1.
|
|
25
|
-
"@vincemakes/kiso-runtime": "0.1.
|
|
26
|
-
"@vincemakes/kiso-tools-node": "0.1.
|
|
27
|
-
"@vincemakes/kiso-tui": "0.1.
|
|
21
|
+
"@vincemakes/kiso-core": "0.1.24",
|
|
22
|
+
"@vincemakes/kiso-evals": "0.1.24",
|
|
23
|
+
"@vincemakes/kiso-provider-anthropic": "0.1.24",
|
|
24
|
+
"@vincemakes/kiso-provider-openai": "0.1.24",
|
|
25
|
+
"@vincemakes/kiso-runtime": "0.1.24",
|
|
26
|
+
"@vincemakes/kiso-tools-node": "0.1.24",
|
|
27
|
+
"@vincemakes/kiso-tui": "0.1.24"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "^26.1.2",
|