@vincemakes/kiso-code 0.40.7 → 0.41.1

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.
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The coding product's prompt policy — the base system prompt and the
3
+ * project-instruction injection. Moved verbatim from index.ts (R1,
4
+ * 2026-09-23): the entry module keeps no prompt text of its own.
5
+ */
6
+ /**
7
+ * A area: the coding-agent system prompt — ONE constant, byte-stable for the
8
+ * session's lifetime (D area). Kept under ~80 lines; no template engine.
9
+ */
10
+ /** The built-in prompt. Exported for scripts/request-surface.mjs — the
11
+ * model-side token-rent counter measures the REAL bytes, never a copy. */
12
+ 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.";
13
+ /** Hard cap for injected instructions — truncate and say so. */
14
+ /**
15
+ * A area: read the FIRST present instruction file (AGENTS.md preferred) and
16
+ * return it as an injected section, or "" when none exists. Truncated at
17
+ * 8KB with an explicit note. Pure — read once per session, so the prompt
18
+ * is byte-stable for the session's lifetime.
19
+ *
20
+ * kiso never serves its own credential store to a model, and this read is
21
+ * the one that needs no model at all: a cloned repository whose AGENTS.md
22
+ * is a symlink to the store would put it in the SYSTEM PROMPT of every
23
+ * request, with no tool call and no trust prompt. A file that resolves to
24
+ * a protected one is skipped as though absent.
25
+ */
26
+ export declare function readProjectInstructions(cwd: string, protectedFiles?: readonly string[]): string;
27
+ /** A area: the session's system prompt — the constant plus any project
28
+ * instructions found in the workspace. Deterministic per cwd. */
29
+ export declare function composeSystemPrompt(cwd: string, protectedFiles?: readonly string[]): string;
30
+ /**
31
+ * The coding agent's tool-table vocabulary — one routing line per tool, in
32
+ * the table only while that tool is active (0.1.40, R-C item 1: the
33
+ * reference implementation's content in kiso's voice). ACI-6 made this the
34
+ * ONE canonical statement of the routing policy; the base prompt above no
35
+ * longer restates it.
36
+ *
37
+ * R1 (2026-09-23): moved verbatim from the runtime's compose.ts, where it
38
+ * sat as a constant and reached every host whose tools shared these names.
39
+ * The rows, their order and their position in the table are unchanged —
40
+ * the byte fixture in tests/fixtures pins that.
41
+ */
42
+ export declare const CODING_TOOL_RULES: ReadonlyArray<{
43
+ readonly tool: string;
44
+ readonly line: string;
45
+ }>;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * The coding product's prompt policy — the base system prompt and the
3
+ * project-instruction injection. Moved verbatim from index.ts (R1,
4
+ * 2026-09-23): the entry module keeps no prompt text of its own.
5
+ */
6
+ import { protectedFiles } from "./state.js";
7
+ import { isProtectedPath, protectedIdentity } from "@vincemakes/kiso-tools-node";
8
+ import { join } from "node:path";
9
+ import { readFileSync } from "node:fs";
10
+ /**
11
+ * A area: the coding-agent system prompt — ONE constant, byte-stable for the
12
+ * session's lifetime (D area). Kept under ~80 lines; no template engine.
13
+ */
14
+ /** The built-in prompt. Exported for scripts/request-surface.mjs — the
15
+ * model-side token-rent counter measures the REAL bytes, never a copy. */
16
+ export const SYSTEM_PROMPT = `You are kiso, a coding agent. You work in a workspace
17
+ directory and change code with tools. Be concise: answer in a few lines
18
+ unless the task genuinely needs more. Never claim a file was changed
19
+ unless a tool confirmed it.
20
+
21
+ What you can reach:
22
+ - The workspace: read_file, list_dir, search_text, write_file, edit_file.
23
+ - This machine and the network: shell — builds, tests, git, package
24
+ managers, curl for HTTP, system queries. A request one command can
25
+ answer is answered by running it.
26
+ - The human: ask_user, for a decision that is theirs.
27
+
28
+ Tool discipline:
29
+ - READ BEFORE YOU EDIT. For any file you are about to change, read it
30
+ first — never guess its content.
31
+ - Use edit_file for targeted changes and write_file for full rewrites.
32
+ Prefer many small edits over one large write.
33
+ - Be careful — shell has side effects and may take time.
34
+ - search_text and list_dir are cheap — locate first, then read ranges
35
+ with read_file offset/limit; never read a whole large file in one call.
36
+ - Do not re-read a file you already read unchanged, or one you changed
37
+ yourself through a confirmed edit — rely on the earlier result and
38
+ on the change you just made.
39
+ - When a tool fails, read the error and adjust; do not repeat the same
40
+ call blindly.
41
+
42
+ An authorization covers what it NAMES: when the scope is not named —
43
+ which files, which branches, whether to push — ask before acting.
44
+ Delivering, sharing or handing over changes means committing locally and
45
+ stopping; pushing, publishing or sending happens only when the human
46
+ names it.
47
+
48
+ Workflow: understand the request, find the relevant code, make the
49
+ smallest change that works, then verify with a command (tests/build).
50
+ Report what you did in one or two lines per change.`;
51
+ /** The project-instructions file names, in priority order (A area). */
52
+ const INSTRUCTION_FILES = ["AGENTS.md", "CLAUDE.md"];
53
+ /** Hard cap for injected instructions — truncate and say so. */
54
+ /**
55
+ * A area: read the FIRST present instruction file (AGENTS.md preferred) and
56
+ * return it as an injected section, or "" when none exists. Truncated at
57
+ * 8KB with an explicit note. Pure — read once per session, so the prompt
58
+ * is byte-stable for the session's lifetime.
59
+ *
60
+ * kiso never serves its own credential store to a model, and this read is
61
+ * the one that needs no model at all: a cloned repository whose AGENTS.md
62
+ * is a symlink to the store would put it in the SYSTEM PROMPT of every
63
+ * request, with no tool call and no trust prompt. A file that resolves to
64
+ * a protected one is skipped as though absent.
65
+ */
66
+ export function readProjectInstructions(cwd, protectedFiles = []) {
67
+ const id = protectedIdentity(protectedFiles);
68
+ for (const name of INSTRUCTION_FILES) {
69
+ if (isProtectedPath(join(cwd, name), id))
70
+ continue;
71
+ let text;
72
+ try {
73
+ text = readFileSync(join(cwd, name), "utf8");
74
+ }
75
+ catch {
76
+ continue; // not present — try the next
77
+ }
78
+ return `\n\n=== Project instructions (${name}) ===\n${text.length > 8 * 1024 ? text.slice(0, 8 * 1024) + `\n\n[truncated at ${8 * 1024} chars]` : text}`;
79
+ }
80
+ return "";
81
+ }
82
+ /** A area: the session's system prompt — the constant plus any project
83
+ * instructions found in the workspace. Deterministic per cwd. */
84
+ export function composeSystemPrompt(cwd, protectedFiles = []) {
85
+ const injected = readProjectInstructions(cwd, protectedFiles);
86
+ return injected === "" ? SYSTEM_PROMPT : `${SYSTEM_PROMPT}\n${injected}`;
87
+ }
88
+ /**
89
+ * The coding agent's tool-table vocabulary — one routing line per tool, in
90
+ * the table only while that tool is active (0.1.40, R-C item 1: the
91
+ * reference implementation's content in kiso's voice). ACI-6 made this the
92
+ * ONE canonical statement of the routing policy; the base prompt above no
93
+ * longer restates it.
94
+ *
95
+ * R1 (2026-09-23): moved verbatim from the runtime's compose.ts, where it
96
+ * sat as a constant and reached every host whose tools shared these names.
97
+ * The rows, their order and their position in the table are unchanged —
98
+ * the byte fixture in tests/fixtures pins that.
99
+ */
100
+ export const CODING_TOOL_RULES = [
101
+ { tool: "read_file", line: "read files with read_file, never shell cat/head/tail" },
102
+ { tool: "search_text", line: "search with search_text, never shell grep/rg" },
103
+ { tool: "list_dir", line: "list with list_dir, never ls" },
104
+ { tool: "shell", line: "shell for what the file tools cannot do: commands, git, the network, the system" },
105
+ ];
@@ -0,0 +1,30 @@
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 { type LineInput } from "./state.js";
11
+ import { type ContextPolicy } from "@vincemakes/kiso-runtime";
12
+ /**
13
+ * E6: the run-start context policy, OFF unless env-armed (invalid values
14
+ * are ABSENT, never a crash — the autoCompactFromEnv convention).
15
+ * The product arming is KISO_CONTEXT_WINDOW → window − POLICY_RESERVE
16
+ * (the window rides the config as windowTokens; the runtime owns the
17
+ * arithmetic — never a fixed low absolute). KISO_POLICY_SUMMARY_TRIGGER
18
+ * survives ONLY as the legacy absolute override when no window is set
19
+ * (bench back-compat); the window wins when both are set.
20
+ * KISO_POLICY_SUMMARY_KEEP (rounds) and KISO_POLICY_SUMMARY_KEEP_TOKENS
21
+ * override the runtime defaults (KEEP_RECENT_ROUNDS = 4,
22
+ * KEEP_TOKENS_DEFAULT = 20,000) — emitted only when set.
23
+ * KISO_POLICY_SUMMARY_MAX_FAILURES overrides the (h) circuit-breaker
24
+ * default (MAX_SUMMARY_FAILURES = 3).
25
+ * KISO_POLICY_DROP=1 switches the armed mode to the crux C arm
26
+ * (mechanical drop — same trigger/keep envs); KISO_POLICY_MICROCOMPACT
27
+ * arms the session-aware override (MIN_TURNS = the no-fire guard).
28
+ */
29
+ export declare function contextPolicyFromEnv(): ContextPolicy | undefined;
30
+ export declare function createCodingAgent(sessionId: string | undefined, input?: LineInput, modelFlag?: string): Promise<import("@vincemakes/kiso-runtime").AgentRuntime>;
@@ -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/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
- * makeAgent, and main.
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) —