@vincemakes/kiso-code 0.40.6 → 0.41.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/auth/credentials.d.ts +4 -0
- package/dist/auth/oauth/chatgpt.d.ts +1 -1
- package/dist/auth/oauth/chatgpt.js +6 -1
- package/dist/auth/oauth/index.d.ts +8 -0
- package/dist/auth/oauth/index.js +8 -1
- package/dist/auth/refresh.d.ts +1 -1
- package/dist/auth/refresh.js +20 -1
- package/dist/coding-prompt.d.ts +45 -0
- package/dist/coding-prompt.js +105 -0
- package/dist/config.d.ts +9 -1
- package/dist/config.js +19 -2
- package/dist/create-coding-agent.d.ts +30 -0
- package/dist/create-coding-agent.js +350 -0
- package/dist/dispatch.js +37 -3
- package/dist/index.d.ts +3 -44
- package/dist/index.js +41 -434
- package/dist/mode.d.ts +1 -1
- package/dist/mode.js +1 -1
- package/dist/readonly-shell.js +5 -9
- package/dist/state.d.ts +11 -7
- package/dist/state.js +13 -7
- package/dist/temp-sweep.d.ts +12 -0
- package/dist/temp-sweep.js +76 -0
- package/dist/trust-ui.js +4 -3
- package/package.json +15 -15
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kiso-code's composition root — the product's choices (trust, config,
|
|
3
|
+
* credentials, the coding tools, the built-in and loaded extensions, the
|
|
4
|
+
* coding prompt, the mode tiers) bound into ONE AgentDefinition and handed
|
|
5
|
+
* to runtime.createAgent(): the one door every agent goes through.
|
|
6
|
+
*
|
|
7
|
+
* Moved verbatim from index.ts's makeAgent (R1, 2026-09-23) and renamed;
|
|
8
|
+
* the body is not edited beyond the rename and activeStoreDir's setter.
|
|
9
|
+
*/
|
|
10
|
+
import { activeStoreDir, setActiveStoreDir, body, bodyLog, codingToolOptions, extensionsDir, floorOn, kisoHome, loadedExtensions, ownSessionsDir, projectRoot, protectedFiles, secretEnvNamesOf, sessionsDir, setAgentModel, setConfigModels, setConfiguredWindow, setCurrentAgentExtensions, setCurrentFaux, setCurrentModelName, setCurrentProfileName, setExtensionLists, setFloorOn, setMergedConfig, setModelChoice, setNeverInherited, setRetryShown, setSessionStore, setUserProtectedPaths, settingsLayers, workspaceRoot } from "./state.js";
|
|
11
|
+
import { claimProjectDir, projectLayoutActive } from "./projects.js";
|
|
12
|
+
import { migrationNotice, pendingLegacyIds, planMigration, runMigration } from "./session-migration.js";
|
|
13
|
+
import { askUi, resolveProjectTrust } from "./trust-ui.js";
|
|
14
|
+
import { isFirstRun, scaffoldFirstRun } from "./first-run.js";
|
|
15
|
+
import { fauxSkip, readFauxScript } from "./faux-glue.js";
|
|
16
|
+
import { loadProjectConfig, loadUserConfig, mergeConfigs, resolveContextWindow, resolveModel } from "./config.js";
|
|
17
|
+
import { builtInLayer } from "./builtin.js";
|
|
18
|
+
import { recordLearnedWindow, useLearnedWindows } from "./learned-windows.js";
|
|
19
|
+
import { compactionDiscardedNotice, contextWindowTokens, knownContextWindow, unknownWindowNotice, windowLearnedNotice } from "./chat.js";
|
|
20
|
+
import {} from "@vincemakes/kiso-core";
|
|
21
|
+
import { guardSavedAllow, isProtectedWrite } from "./protected-writes.js";
|
|
22
|
+
import { floorExtension, isDestructiveCall } from "./floor.js";
|
|
23
|
+
import { protectedShellExtension } from "./protected-shell.js";
|
|
24
|
+
import { homedir } from "node:os";
|
|
25
|
+
import { breakerExtension } from "./breaker.js";
|
|
26
|
+
import { modeExtensions, modeSystemPrompt } from "./mode.js";
|
|
27
|
+
import { readOnlyShellExtension } from "./readonly-shell.js";
|
|
28
|
+
import { maxRetriesFromEnv } from "./retries.js";
|
|
29
|
+
import { createCodingTools } from "@vincemakes/kiso-tools-node";
|
|
30
|
+
import { runsACheck } from "@vincemakes/kiso-runtime/internal";
|
|
31
|
+
import { providerHost } from "./provider-label.js";
|
|
32
|
+
import { adapterOptionsFor } from "./auth/adapter-options.js";
|
|
33
|
+
import { createFauxProvider } from "@vincemakes/kiso-evals";
|
|
34
|
+
import { sweepStaleMergeDirs } from "./temp-sweep.js";
|
|
35
|
+
import { CODING_TOOL_RULES, composeSystemPrompt } from "./coding-prompt.js";
|
|
36
|
+
import { createAgent, loadExtensions, loadProjectExtensions, SessionStore } from "@vincemakes/kiso-runtime";
|
|
37
|
+
/** LT-1: the stream watchdog's bound from the environment — a non-negative
|
|
38
|
+
* number of milliseconds (0 disables it); anything else is ignored. The
|
|
39
|
+
* PTY rigs use it to trip the watchdog in seconds against a stub that
|
|
40
|
+
* never finishes a stream. */
|
|
41
|
+
function streamIdleFromEnv() {
|
|
42
|
+
const raw = process.env.KISO_STREAM_IDLE_MS;
|
|
43
|
+
if (raw === undefined || raw === "")
|
|
44
|
+
return undefined;
|
|
45
|
+
const n = Number(raw);
|
|
46
|
+
return Number.isFinite(n) && n >= 0 ? n : undefined;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* E6: the run-start context policy, OFF unless env-armed (invalid values
|
|
50
|
+
* are ABSENT, never a crash — the autoCompactFromEnv convention).
|
|
51
|
+
* The product arming is KISO_CONTEXT_WINDOW → window − POLICY_RESERVE
|
|
52
|
+
* (the window rides the config as windowTokens; the runtime owns the
|
|
53
|
+
* arithmetic — never a fixed low absolute). KISO_POLICY_SUMMARY_TRIGGER
|
|
54
|
+
* survives ONLY as the legacy absolute override when no window is set
|
|
55
|
+
* (bench back-compat); the window wins when both are set.
|
|
56
|
+
* KISO_POLICY_SUMMARY_KEEP (rounds) and KISO_POLICY_SUMMARY_KEEP_TOKENS
|
|
57
|
+
* override the runtime defaults (KEEP_RECENT_ROUNDS = 4,
|
|
58
|
+
* KEEP_TOKENS_DEFAULT = 20,000) — emitted only when set.
|
|
59
|
+
* KISO_POLICY_SUMMARY_MAX_FAILURES overrides the (h) circuit-breaker
|
|
60
|
+
* default (MAX_SUMMARY_FAILURES = 3).
|
|
61
|
+
* KISO_POLICY_DROP=1 switches the armed mode to the crux C arm
|
|
62
|
+
* (mechanical drop — same trigger/keep envs); KISO_POLICY_MICROCOMPACT
|
|
63
|
+
* arms the session-aware override (MIN_TURNS = the no-fire guard).
|
|
64
|
+
*/
|
|
65
|
+
export function contextPolicyFromEnv() {
|
|
66
|
+
const summaryTrigger = positiveIntEnv("KISO_POLICY_SUMMARY_TRIGGER");
|
|
67
|
+
const contextWindow = positiveIntEnv("KISO_CONTEXT_WINDOW");
|
|
68
|
+
const microcompactTrigger = positiveIntEnv("KISO_POLICY_MICROCOMPACT");
|
|
69
|
+
if (summaryTrigger === undefined && contextWindow === undefined && microcompactTrigger === undefined)
|
|
70
|
+
return undefined;
|
|
71
|
+
return {
|
|
72
|
+
...(summaryTrigger === undefined && contextWindow === undefined ? {} : {
|
|
73
|
+
[(process.env.KISO_POLICY_DROP === "1" ? "drop" : "summary")]: {
|
|
74
|
+
...(contextWindow !== undefined ? { windowTokens: contextWindow } : summaryTrigger !== undefined ? { triggerTokens: summaryTrigger } : {}),
|
|
75
|
+
...kv("keepRounds", "KISO_POLICY_SUMMARY_KEEP"),
|
|
76
|
+
...kv("keepTokens", "KISO_POLICY_SUMMARY_KEEP_TOKENS"),
|
|
77
|
+
...kv("maxFailures", "KISO_POLICY_SUMMARY_MAX_FAILURES"),
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
...(microcompactTrigger !== undefined ? { microcompact: { thresholdTokens: microcompactTrigger, ...kv("keepResults", "KISO_POLICY_MICROCOMPACT_KEEP"), ...kv("minTurns", "KISO_POLICY_MICROCOMPACT_MIN_TURNS") } } : {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** { [key]: value } when the env int is set — the spread-friendly optional field. */
|
|
84
|
+
function kv(key, env) {
|
|
85
|
+
const v = positiveIntEnv(env);
|
|
86
|
+
return v !== undefined ? { [key]: v } : {};
|
|
87
|
+
}
|
|
88
|
+
/** Parse a positive-int env var — absent or invalid is undefined (no crash). */
|
|
89
|
+
function positiveIntEnv(name) {
|
|
90
|
+
const n = Number.parseInt(process.env[name] ?? "", 10);
|
|
91
|
+
return Number.isFinite(n) && n > 0 ? n : undefined;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 0.40.0 — before any session opens: the one-time move of the legacy
|
|
95
|
+
* folder into project folders (announced once, with the undo), then this
|
|
96
|
+
* project's folder claimed. Nothing happens under a pinned folder or after
|
|
97
|
+
* a reversed migration.
|
|
98
|
+
*/
|
|
99
|
+
function prepareSessionFolders() {
|
|
100
|
+
const home = kisoHome();
|
|
101
|
+
if (!projectLayoutActive(home))
|
|
102
|
+
return;
|
|
103
|
+
if (pendingLegacyIds(home).length > 0) {
|
|
104
|
+
const result = runMigration(home, planMigration(home));
|
|
105
|
+
if (result !== null && result.moved > 0) {
|
|
106
|
+
const line = migrationNotice(result);
|
|
107
|
+
if (process.stdout.isTTY)
|
|
108
|
+
bodyLog(line);
|
|
109
|
+
else
|
|
110
|
+
process.stderr.write(`${line}\n`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
claimProjectDir(ownSessionsDir(), projectRoot());
|
|
114
|
+
}
|
|
115
|
+
/** 0.40.7: the stale-merge-directory sweep runs once per process. */
|
|
116
|
+
let tempSwept = false;
|
|
117
|
+
/** Said once per process: the window is unknown, so no ctx gauge. */
|
|
118
|
+
let unknownWindowNoticed = false;
|
|
119
|
+
export async function createCodingAgent(sessionId, input, modelFlag) {
|
|
120
|
+
// §2.5: the ONE source a reload reads for the model, seeded here from
|
|
121
|
+
// the startup flag. Without this a session started with `--model X`
|
|
122
|
+
// would silently revert to the env/config default on its first reload.
|
|
123
|
+
if (modelFlag !== undefined)
|
|
124
|
+
setModelChoice(modelFlag);
|
|
125
|
+
// E3: the project-level trust gate runs BEFORE any extension load (the
|
|
126
|
+
// mcp/skills merges must be in the env when the user-level extensions
|
|
127
|
+
// load). Untrusted project capability is never loaded — never silently.
|
|
128
|
+
const project = input !== undefined ? await resolveProjectTrust(input) : await resolveProjectTrust(undefined);
|
|
129
|
+
// R-D 0.1.45 (deliverable B): the first-run scaffold lands AFTER the
|
|
130
|
+
// verdict — pre-trust zero-read/write/scan is absolute. The sessions
|
|
131
|
+
// dir (SessionStore's constructor mkdirs) moved behind the gate too:
|
|
132
|
+
// the trust record is the first home write, the scaffold the second.
|
|
133
|
+
if (isFirstRun())
|
|
134
|
+
scaffoldFirstRun();
|
|
135
|
+
// 0.40.0 — one folder per project: the one-time move of the legacy
|
|
136
|
+
// folder, then this project's folder claimed (made, its workspace
|
|
137
|
+
// recorded). Both write, so both sit behind the verdict, with the store.
|
|
138
|
+
prepareSessionFolders();
|
|
139
|
+
setActiveStoreDir(sessionsDir());
|
|
140
|
+
const store = new SessionStore(activeStoreDir);
|
|
141
|
+
// TUI2-R2 ②/③: the navigation surfaces read through THIS store — one
|
|
142
|
+
// store per process, and the picker/listing never write through it.
|
|
143
|
+
setSessionStore(store);
|
|
144
|
+
// E area: the durable script position — computed AFTER the verdict
|
|
145
|
+
// (fauxSkip's session-log read is a home read: pre-trust zero-read).
|
|
146
|
+
const fauxSkipTurns = sessionId === undefined ? 0 : fauxSkip(sessionId);
|
|
147
|
+
// E1: the startup extension scan — a broken extension fails the process
|
|
148
|
+
// LOUDLY here (loadExtensions throws), never silently.
|
|
149
|
+
const user = await loadExtensions(extensionsDir());
|
|
150
|
+
const proj = project !== null ? await loadProjectExtensions(process.cwd(), user) : [];
|
|
151
|
+
// R-D 0.1.45: the built-in layer registers by module import (builtin.ts)
|
|
152
|
+
// — a user extension may shadow a built-in, a project one may not.
|
|
153
|
+
// KC3.5: built-in #4 (ask) registers ONLY where a human can answer —
|
|
154
|
+
// the panel bridge is the argument, and a non-TTY session has none to
|
|
155
|
+
// give. A piped run's composed tool table therefore cannot contain
|
|
156
|
+
// ask_user (T-Q3: the bench's structural byte-identity proof).
|
|
157
|
+
// Astra F7: the config is READ here — a pure read, its setters still run
|
|
158
|
+
// below with the rest of merge round B — because the mcp extension spawns
|
|
159
|
+
// its stdio children while it is being constructed, and the strip needs
|
|
160
|
+
// the configured secret names by then.
|
|
161
|
+
const userCfg = loadUserConfig();
|
|
162
|
+
const projectCfg = loadProjectConfig(process.cwd(), project !== null);
|
|
163
|
+
const merged = mergeConfigs(userCfg, projectCfg);
|
|
164
|
+
// 0.40.6: /settings names each value's layer from these
|
|
165
|
+
settingsLayers.user = userCfg;
|
|
166
|
+
settingsLayers.project = projectCfg;
|
|
167
|
+
settingsLayers.modelFlag = modelFlag;
|
|
168
|
+
const secretEnvNames = secretEnvNamesOf(merged.models ?? {});
|
|
169
|
+
const builtIn = await builtInLayer(user, proj, input !== undefined && process.stdin.isTTY ? askUi(input) : undefined, secretEnvNames);
|
|
170
|
+
setExtensionLists(builtIn, user, proj, [...builtIn, ...user, ...proj]);
|
|
171
|
+
// merge round B — the config surface: user config + (trusted) project config,
|
|
172
|
+
// resolved with flags > env > project > user > default. The CLI never
|
|
173
|
+
// imports provider SDKs directly — the runtime's lazy provider
|
|
174
|
+
// resolution owns them (a config profile only ever NAMES an env var for
|
|
175
|
+
// its key; the key itself never sits in a config file).
|
|
176
|
+
setMergedConfig(merged);
|
|
177
|
+
// DT-1a: what a delegated task may NAME — the configured checks and the
|
|
178
|
+
// model profiles — handed to the (in-process) subagent extension through
|
|
179
|
+
// the environment. A model never supplies a command; it names a check.
|
|
180
|
+
// 0.40.0: and the folder this process keeps its sessions in — a child
|
|
181
|
+
// writes beside its parent, whatever directory it runs in. Through this
|
|
182
|
+
// channel and not an exported KISO_SESSIONS_DIR: a variable in
|
|
183
|
+
// process.env would reach every shell child, and a kiso started from a
|
|
184
|
+
// shell tool would then write into this project's folder.
|
|
185
|
+
process.env.KISO_DELEGATION_CONFIG_JSON = JSON.stringify({ checks: merged.checks ?? {}, evaluators: merged.evaluators ?? [], profiles: Object.keys(merged.models ?? {}), sessionsDir: sessionsDir() });
|
|
186
|
+
setConfigModels(merged.models ?? {});
|
|
187
|
+
// CW-1 batch 2: the windows endpoints stated by refusing — read before the
|
|
188
|
+
// first window is asked for (the unknown-window notice below).
|
|
189
|
+
useLearnedWindows();
|
|
190
|
+
// 0.40.7: the merge directories a killed kiso left in the temp dir —
|
|
191
|
+
// once per process, never this process's own (temp-sweep.ts)
|
|
192
|
+
if (!tempSwept) {
|
|
193
|
+
tempSwept = true;
|
|
194
|
+
sweepStaleMergeDirs();
|
|
195
|
+
}
|
|
196
|
+
const resolved = resolveModel(modelFlag, merged);
|
|
197
|
+
// AFTER the model resolves: the window a PROFILE states is about that
|
|
198
|
+
// profile's model, so it cannot be read before we know which profile is
|
|
199
|
+
// selected. Reading it a line too early is how the compaction threshold
|
|
200
|
+
// ended up frozen at the wrong model's value (CTX-1).
|
|
201
|
+
setConfiguredWindow(resolveContextWindow(merged, resolved?.profile));
|
|
202
|
+
const model = resolved === null ? "faux" : resolved.profile.model;
|
|
203
|
+
if (resolved === null) {
|
|
204
|
+
console.log("[faux mode — set ANTHROPIC_API_KEY or OPENAI_API_KEY, or configure models in ~/.kiso/config.json]\n");
|
|
205
|
+
setCurrentFaux(true);
|
|
206
|
+
setCurrentModelName("faux");
|
|
207
|
+
setCurrentProfileName(null);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
setCurrentFaux(false);
|
|
211
|
+
setCurrentModelName(resolved.name);
|
|
212
|
+
setCurrentProfileName(resolved.name);
|
|
213
|
+
}
|
|
214
|
+
setAgentModel(model, resolved?.profile.baseUrl); // v2b: the status bar shows it; OR-1: the endpoint rides along
|
|
215
|
+
// ADR-0055 Amendment 2: the row says `ctx ?` for an unstated window, but
|
|
216
|
+
// the tiers still need a number and assume the fallback — say so, once,
|
|
217
|
+
// at build.
|
|
218
|
+
if (!unknownWindowNoticed && knownContextWindow() === null) {
|
|
219
|
+
unknownWindowNoticed = true;
|
|
220
|
+
console.error(unknownWindowNotice(model));
|
|
221
|
+
}
|
|
222
|
+
// W21: the extensions array is built ONCE per agent and shared with
|
|
223
|
+
// the runtime by reference — the don't-ask-again writer pushes the
|
|
224
|
+
// generated extension into it so a first-time rule joins the chain
|
|
225
|
+
// at the NEXT run (the run's policies are fixed at its start; run.ts
|
|
226
|
+
// re-reads the config's extensions array per run).
|
|
227
|
+
// LT-2: the loop breaker at the chain HEAD — it speaks first when it speaks,
|
|
228
|
+
// so `decidedBy` names it; a deny there beats every tier, bypass included.
|
|
229
|
+
// 0.40.0: the read-only shell allow sits after the tiers — an allow from
|
|
230
|
+
// it outranks a tier's ask and names itself in decidedBy.
|
|
231
|
+
// 0.40.0: a saved allow never carries a write into .git/ or .kiso/, nor
|
|
232
|
+
// a destructive shell command.
|
|
233
|
+
const workspaceRoot = () => codingToolOptions().workspaceRoot;
|
|
234
|
+
const neverInherited = (call) => isProtectedWrite(call, workspaceRoot()) || isDestructiveCall(call);
|
|
235
|
+
setNeverInherited(neverInherited);
|
|
236
|
+
// 0.40.0: the catastrophe floor, at the chain's HEAD — a deny there
|
|
237
|
+
// names itself in decidedBy and outranks every tier, bypass included.
|
|
238
|
+
// Read per agent, so /reload picks up an edited user config.
|
|
239
|
+
const userConfig = loadUserConfig();
|
|
240
|
+
setFloorOn(userConfig?.floor !== "off");
|
|
241
|
+
// kiso never serves its own credential store to a model: the file tools
|
|
242
|
+
// refuse it through codingToolOptions, and this member denies a shell
|
|
243
|
+
// line naming it — at the HEAD with the floor, in every mode, and NOT
|
|
244
|
+
// switched off with it (`floor: "off"` lowers the catastrophe floor,
|
|
245
|
+
// never this).
|
|
246
|
+
setUserProtectedPaths(userConfig?.protectedPaths);
|
|
247
|
+
const extensions = [
|
|
248
|
+
protectedShellExtension({ files: protectedFiles, workspaceRoot, env: () => ({ home: homedir(), kisoHome: kisoHome() }) }),
|
|
249
|
+
floorExtension(() => floorOn, workspaceRoot),
|
|
250
|
+
breakerExtension(),
|
|
251
|
+
...modeExtensions(workspaceRoot),
|
|
252
|
+
readOnlyShellExtension(codingToolOptions),
|
|
253
|
+
...loadedExtensions.map((e) => guardSavedAllow(e, neverInherited)),
|
|
254
|
+
];
|
|
255
|
+
setCurrentAgentExtensions(extensions);
|
|
256
|
+
// E6: the run-start context policy (captured once — exactOptionalPropertyTypes).
|
|
257
|
+
const contextPolicy = contextPolicyFromEnv();
|
|
258
|
+
const idleFromEnv = streamIdleFromEnv(); // read once: a narrowed const, not a call per spread
|
|
259
|
+
const retriesFromEnv = maxRetriesFromEnv();
|
|
260
|
+
const definition = {
|
|
261
|
+
model,
|
|
262
|
+
store,
|
|
263
|
+
// 0.40.0: a NEW session records where it started; the picker scopes by
|
|
264
|
+
// it. The profile name is recorded only when the model came from a
|
|
265
|
+
// config profile — a direct provider/model or an env key names none.
|
|
266
|
+
workspace: workspaceRoot(),
|
|
267
|
+
...(resolved !== null && merged.models?.[resolved.name] === resolved.profile ? { profileName: resolved.name } : {}),
|
|
268
|
+
// Area 5: the coding tools are bound to the workspace — every path
|
|
269
|
+
// they touch is canonicalized inside cwd, escapes are refused.
|
|
270
|
+
tools: [...createCodingTools(codingToolOptions())], // DC-49 — the options live in state.ts, shared with the `!` command's runner
|
|
271
|
+
toolRules: CODING_TOOL_RULES, // R1: the product's rows, in the runtime's table
|
|
272
|
+
// Modes: the five tiers ride the E1 policy chain (mode:<tier>
|
|
273
|
+
// extensions, current tier first) — the old static PERMISSION_POLICY
|
|
274
|
+
// is gone, its semantics live in the "default" tier. The banner
|
|
275
|
+
// still counts loadedExtensions only — the modes are in-process,
|
|
276
|
+
// never a file extension.
|
|
277
|
+
systemPrompt: (() => {
|
|
278
|
+
const sp = composeSystemPrompt(process.cwd(), protectedFiles());
|
|
279
|
+
const extra = modeSystemPrompt();
|
|
280
|
+
return extra === undefined ? sp : `${sp}\n\n${extra}`;
|
|
281
|
+
})(),
|
|
282
|
+
// ADR-0055 Amendment 1 (A1b): compaction is ON by default and runs
|
|
283
|
+
// INSIDE a run, by tiers drawn from the model's window (CTX-1: the
|
|
284
|
+
// binding step moves the window with /model and /resume). The
|
|
285
|
+
// standing microcompact at half the window is gone (A4). Phase rule
|
|
286
|
+
// 1 reads the user's configured checks first, then the runner table.
|
|
287
|
+
contextPolicy: {
|
|
288
|
+
...(contextPolicy ?? {}),
|
|
289
|
+
tiers: {
|
|
290
|
+
windowTokens: contextWindowTokens(),
|
|
291
|
+
isCheck: (command) => runsACheck(command, Object.values(merged.checks ?? {})),
|
|
292
|
+
// ADR-0055 Amendment 2: a discarded checkpoint says so, in sizes only.
|
|
293
|
+
onDiscard: (d) => body.notice(compactionDiscardedNotice(d)),
|
|
294
|
+
// ADR-0055 Amendment 2 (decision 3): only a window someone stated
|
|
295
|
+
// arms the overflow belt — read from the LIVE binding, so it moves
|
|
296
|
+
// with /model and /resume; the fallback reads as null.
|
|
297
|
+
statedWindow: () => knownContextWindow(),
|
|
298
|
+
// CW-1 batch 2: an endpoint's refusal stated its cap. The tiers
|
|
299
|
+
// took it already; kept, it is where the next session starts, and
|
|
300
|
+
// the status row's denominator moves with it. Said once per new
|
|
301
|
+
// figure — a refusal at a cap already kept says nothing.
|
|
302
|
+
onWindowLearned: (w) => {
|
|
303
|
+
if (recordLearnedWindow(w.model, w.baseUrl, w.tokens))
|
|
304
|
+
body.notice(windowLearnedNotice(w.model, providerHost(w.baseUrl) ?? "", w.tokens));
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
// R3e (owner ruling, 2026-08-28): NO turn limit on an interactive
|
|
309
|
+
// session. This was `maxTurns: 20`, hardcoded on 2026-08-03 with no
|
|
310
|
+
// stated reason and no way to change it — and it was the thing that
|
|
311
|
+
// stopped a real 43-call session dead, mid-task, in silence. The
|
|
312
|
+
// field survives for the callers that want a bound (subagents, the
|
|
313
|
+
// SDK, `kiso run`); the interactive front door does not set one.
|
|
314
|
+
// Modes: the five tiers join at the CHAIN HEAD, before the user/
|
|
315
|
+
// project extensions (the deny>allow>ask composition keeps a user
|
|
316
|
+
// deny winning over any mode tier — bypass included).
|
|
317
|
+
extensions,
|
|
318
|
+
...(resolved !== null
|
|
319
|
+
? {
|
|
320
|
+
provider: resolved.profile.kind,
|
|
321
|
+
// Astra F1 (P0): the wire config is built in ONE place
|
|
322
|
+
// (auth/adapter-options.ts) — credential shape, the ALWAYS
|
|
323
|
+
// EXPLICIT endpoint, the session's cache key and the
|
|
324
|
+
// profile's caching flag. This site and the `/model` site
|
|
325
|
+
// were the same spread written twice.
|
|
326
|
+
...adapterOptionsFor(resolved.profile, resolved.oauthProviderId !== undefined
|
|
327
|
+
? { type: "oauth", providerId: resolved.oauthProviderId }
|
|
328
|
+
: { type: "api-key", apiKey: resolved.apiKey ?? "none" }, sessionId),
|
|
329
|
+
// LT-1: the profile's stream watchdog bound, if it states one.
|
|
330
|
+
// NOT an adapter option — an agent-definition field the
|
|
331
|
+
// runtime reads — so it stays here rather than moving into
|
|
332
|
+
// adapterOptionsFor.
|
|
333
|
+
...(resolved.profile.streamIdleMs !== undefined ? { streamIdleMs: resolved.profile.streamIdleMs } : {}),
|
|
334
|
+
}
|
|
335
|
+
: { adapter: createFauxProvider(readFauxScript().slice(fauxSkipTurns)) }),
|
|
336
|
+
// LT-1: KISO_STREAM_IDLE_MS (the test rigs' knob) beats the profile —
|
|
337
|
+
// the last spread wins, which is why it sits after the profile's.
|
|
338
|
+
...(idleFromEnv !== undefined ? { streamIdleMs: idleFromEnv } : {}),
|
|
339
|
+
...(retriesFromEnv !== undefined ? { maxRetries: retriesFromEnv } : {}),
|
|
340
|
+
// ADR-0005 Amendment 2: the kernel announces each retry before its
|
|
341
|
+
// wait; the running row shows it. Composed with every extension's
|
|
342
|
+
// hooks by the runtime — an extension observing retries too is heard.
|
|
343
|
+
hooks: {
|
|
344
|
+
onRetry: async (info) => {
|
|
345
|
+
setRetryShown({ attempt: info.attempt, maxRetries: info.maxRetries, code: info.code, until: Date.now() + info.delayMs });
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
return createAgent(definition);
|
|
350
|
+
}
|
package/dist/dispatch.js
CHANGED
|
@@ -101,13 +101,31 @@ function effortAxis(p) {
|
|
|
101
101
|
: {}),
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
|
-
/** What signs a profile in, for the listing:
|
|
105
|
-
*
|
|
104
|
+
/** What signs a profile in, for the listing: `stored key` when a key in
|
|
105
|
+
* auth.json pays (a vendor's, or a gateway's `kiso login --endpoint`),
|
|
106
|
+
* `oauth` for a subscription sign-in, else the env var's name, or `no key`
|
|
107
|
+
* for an unauthenticated endpoint.
|
|
108
|
+
*
|
|
109
|
+
* 0406-F1: this named the env var whenever a profile had one, so a row
|
|
110
|
+
* paid by the stored key read `DEEPSEEK_API_KEY (available)` with that
|
|
111
|
+
* variable unset — and after 0.40.6's endpoint login every gateway row
|
|
112
|
+
* would have named a variable nobody exports any more. */
|
|
113
|
+
/** MP-1 (0.40.7): who a profile's requests are billed to — the endpoint
|
|
114
|
+
* and the credential that pays there (`@api.deepseek.com · stored key`). */
|
|
115
|
+
function payeeOf(p) {
|
|
116
|
+
const host = profileProviderLabel(p.kind, p.baseUrl, p.upstream);
|
|
117
|
+
const who = signInNote(p).replace(/, expired — renews on use$/, "");
|
|
118
|
+
return host === "" ? who : `${host} · ${who}`;
|
|
119
|
+
}
|
|
106
120
|
function signInNote(p) {
|
|
107
121
|
try {
|
|
108
122
|
const auth = authForProfile("?", p);
|
|
123
|
+
// 0.40.7: an access token past its expiry is not a dead sign-in — the
|
|
124
|
+
// next use renews it — but the row says so rather than nothing
|
|
109
125
|
if (auth.type === "oauth")
|
|
110
|
-
return "oauth";
|
|
126
|
+
return auth.expires !== undefined && auth.expires < Date.now() ? "oauth, expired — renews on use" : "oauth";
|
|
127
|
+
if (auth.source === "store")
|
|
128
|
+
return "stored key";
|
|
111
129
|
}
|
|
112
130
|
catch {
|
|
113
131
|
// unavailable — the availability mark says so; the env name still names what would sign it in
|
|
@@ -564,6 +582,8 @@ export function dispatch(line, ctx) {
|
|
|
564
582
|
ctx.input.panelAsk(modePickView({
|
|
565
583
|
header: `mode — current: ${current}`,
|
|
566
584
|
options: OFFERED_MODES.map((name) => ({ label: name, note: [MODE_NOTE[name], ...(name === current ? ["current"] : [])].join(" · ") })),
|
|
585
|
+
// MP-1 (0.40.7): the cursor opens on the tier in force
|
|
586
|
+
...(OFFERED_MODES.indexOf(current) >= 0 ? { initial: OFFERED_MODES.indexOf(current) } : {}),
|
|
567
587
|
},
|
|
568
588
|
// DC-12 (design §4): a panel WAITING ON A HUMAN says ❯.
|
|
569
589
|
ctx.isRunning() ? "❯ run paused" : `▸ ${current}`), (v) => resolve(v.action === "picked" ? v.result : null));
|
|
@@ -653,6 +673,9 @@ export function dispatch(line, ctx) {
|
|
|
653
673
|
// "openai/…" hint failed with "no such model profile"
|
|
654
674
|
// on exactly the fresh-install path that shows it.
|
|
655
675
|
typeHint: names.length === 0 ? "type provider/model directly (e.g. openai-compat/deepseek-reasoner)" : "type provider/model directly",
|
|
676
|
+
// MP-1 (0.40.7): the cursor opens on the session's own
|
|
677
|
+
// profile — opening on row 0 plus one Enter changed who pays
|
|
678
|
+
...(currentProfileName !== null && names.indexOf(currentProfileName) >= 0 ? { initial: names.indexOf(currentProfileName) } : {}),
|
|
656
679
|
// the zero-profile copy is TODAY'S, verbatim: the
|
|
657
680
|
// user who sees it is exactly the user who needs
|
|
658
681
|
// the path spelled out
|
|
@@ -788,6 +811,10 @@ export function dispatch(line, ctx) {
|
|
|
788
811
|
// display — a direct provider/model has none.
|
|
789
812
|
profileName: direct === null ? profName : null,
|
|
790
813
|
};
|
|
814
|
+
// MP-1 (0.40.7): who pays BEFORE the switch, read while the
|
|
815
|
+
// session still names the outgoing profile
|
|
816
|
+
const prevProfile = currentProfileName !== null ? configModels[currentProfileName] : undefined;
|
|
817
|
+
const payeeBefore = prevProfile !== undefined ? payeeOf(prevProfile) : null;
|
|
791
818
|
ctx.session.setModelBinding(binding);
|
|
792
819
|
setLastBinding(binding);
|
|
793
820
|
setAgentModel(profile.model, profile.baseUrl);
|
|
@@ -814,6 +841,13 @@ export function dispatch(line, ctx) {
|
|
|
814
841
|
// model beside the previous model's figure.
|
|
815
842
|
ctx.modelSwitched();
|
|
816
843
|
body.notice(`model → ${profName} (${profile.model}${providerLabel(profile.baseUrl, profile.upstream) === "" ? "" : ` ${providerLabel(profile.baseUrl, profile.upstream)}`}${effortTok !== undefined ? ` · ${effortTok}` : ""}) — takes effect on the next turn`);
|
|
844
|
+
// MP-1 (0.40.7): when the switch changes WHO PAYS — another
|
|
845
|
+
// endpoint, or another credential for it — say so, once,
|
|
846
|
+
// under the switch line. A switch between two models on the
|
|
847
|
+
// same account says nothing more.
|
|
848
|
+
const payeeAfter = payeeOf(profile);
|
|
849
|
+
if (payeeBefore !== null && payeeBefore !== payeeAfter)
|
|
850
|
+
body.notice(`paid by ${payeeAfter} from the next turn — was ${payeeBefore}`);
|
|
817
851
|
}
|
|
818
852
|
}
|
|
819
853
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -21,53 +21,12 @@
|
|
|
21
21
|
* resume.ts, trust-ui.ts (the question surface + E3 merges), faux-glue.ts
|
|
22
22
|
* (the scripted-model plumbing), state.ts (the shared process state).
|
|
23
23
|
* index.ts keeps the entry: banner, input sources, the A area prompt,
|
|
24
|
-
*
|
|
24
|
+
* createCodingAgent, and main.
|
|
25
25
|
*/
|
|
26
|
-
import { type ContextPolicy } from "@vincemakes/kiso-runtime";
|
|
27
26
|
import { type SessionRoute } from "./projects.js";
|
|
27
|
+
export { contextPolicyFromEnv } from "./create-coding-agent.js";
|
|
28
|
+
export { SYSTEM_PROMPT, composeSystemPrompt, readProjectInstructions } from "./coding-prompt.js";
|
|
28
29
|
export { applyProjectMerges } from "./trust-ui.js";
|
|
29
|
-
/**
|
|
30
|
-
* A area: the coding-agent system prompt — ONE constant, byte-stable for the
|
|
31
|
-
* session's lifetime (D area). Kept under ~80 lines; no template engine.
|
|
32
|
-
*/
|
|
33
|
-
/** The built-in prompt. Exported for scripts/request-surface.mjs — the
|
|
34
|
-
* model-side token-rent counter measures the REAL bytes, never a copy. */
|
|
35
|
-
export declare const SYSTEM_PROMPT = "You are kiso, a coding agent. You work in a workspace\ndirectory and change code with tools. Be concise: answer in a few lines\nunless the task genuinely needs more. Never claim a file was changed\nunless a tool confirmed it.\n\nWhat you can reach:\n- The workspace: read_file, list_dir, search_text, write_file, edit_file.\n- This machine and the network: shell \u2014 builds, tests, git, package\n managers, curl for HTTP, system queries. A request one command can\n answer is answered by running it.\n- The human: ask_user, for a decision that is theirs.\n\nTool discipline:\n- READ BEFORE YOU EDIT. For any file you are about to change, read it\n first \u2014 never guess its content.\n- Use edit_file for targeted changes and write_file for full rewrites.\n Prefer many small edits over one large write.\n- Be careful \u2014 shell has side effects and may take time.\n- search_text and list_dir are cheap \u2014 locate first, then read ranges\n with read_file offset/limit; never read a whole large file in one call.\n- Do not re-read a file you already read unchanged, or one you changed\n yourself through a confirmed edit \u2014 rely on the earlier result and\n on the change you just made.\n- When a tool fails, read the error and adjust; do not repeat the same\n call blindly.\n\nAn authorization covers what it NAMES: when the scope is not named \u2014\nwhich files, which branches, whether to push \u2014 ask before acting.\nDelivering, sharing or handing over changes means committing locally and\nstopping; pushing, publishing or sending happens only when the human\nnames it.\n\nWorkflow: understand the request, find the relevant code, make the\nsmallest change that works, then verify with a command (tests/build).\nReport what you did in one or two lines per change.";
|
|
36
|
-
/** Hard cap for injected instructions — truncate and say so. */
|
|
37
|
-
/**
|
|
38
|
-
* A area: read the FIRST present instruction file (AGENTS.md preferred) and
|
|
39
|
-
* return it as an injected section, or "" when none exists. Truncated at
|
|
40
|
-
* 8KB with an explicit note. Pure — read once per session, so the prompt
|
|
41
|
-
* is byte-stable for the session's lifetime.
|
|
42
|
-
*
|
|
43
|
-
* kiso never serves its own credential store to a model, and this read is
|
|
44
|
-
* the one that needs no model at all: a cloned repository whose AGENTS.md
|
|
45
|
-
* is a symlink to the store would put it in the SYSTEM PROMPT of every
|
|
46
|
-
* request, with no tool call and no trust prompt. A file that resolves to
|
|
47
|
-
* a protected one is skipped as though absent.
|
|
48
|
-
*/
|
|
49
|
-
export declare function readProjectInstructions(cwd: string, protectedFiles?: readonly string[]): string;
|
|
50
|
-
/** A area: the session's system prompt — the constant plus any project
|
|
51
|
-
* instructions found in the workspace. Deterministic per cwd. */
|
|
52
|
-
export declare function composeSystemPrompt(cwd: string, protectedFiles?: readonly string[]): string;
|
|
53
|
-
/**
|
|
54
|
-
* E6: the run-start context policy, OFF unless env-armed (invalid values
|
|
55
|
-
* are ABSENT, never a crash — the autoCompactFromEnv convention).
|
|
56
|
-
* The product arming is KISO_CONTEXT_WINDOW → window − POLICY_RESERVE
|
|
57
|
-
* (the window rides the config as windowTokens; the runtime owns the
|
|
58
|
-
* arithmetic — never a fixed low absolute). KISO_POLICY_SUMMARY_TRIGGER
|
|
59
|
-
* survives ONLY as the legacy absolute override when no window is set
|
|
60
|
-
* (bench back-compat); the window wins when both are set.
|
|
61
|
-
* KISO_POLICY_SUMMARY_KEEP (rounds) and KISO_POLICY_SUMMARY_KEEP_TOKENS
|
|
62
|
-
* override the runtime defaults (KEEP_RECENT_ROUNDS = 4,
|
|
63
|
-
* KEEP_TOKENS_DEFAULT = 20,000) — emitted only when set.
|
|
64
|
-
* KISO_POLICY_SUMMARY_MAX_FAILURES overrides the (h) circuit-breaker
|
|
65
|
-
* default (MAX_SUMMARY_FAILURES = 3).
|
|
66
|
-
* KISO_POLICY_DROP=1 switches the armed mode to the crux C arm
|
|
67
|
-
* (mechanical drop — same trigger/keep envs); KISO_POLICY_MICROCOMPACT
|
|
68
|
-
* arms the session-aware override (MIN_TURNS = the no-fire guard).
|
|
69
|
-
*/
|
|
70
|
-
export declare function contextPolicyFromEnv(): ContextPolicy | undefined;
|
|
71
30
|
export { workspaceRoot } from "./state.js";
|
|
72
31
|
/**
|
|
73
32
|
* 0.40.0 (the owner's dogfood: a uooki session resumed from flowpix2) —
|