@vincemakes/kiso-code 0.40.3 → 0.40.5

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.
@@ -18,5 +18,11 @@ export interface AdapterOptions {
18
18
  readonly accountId: string;
19
19
  }>;
20
20
  readonly promptCacheKey?: string;
21
+ readonly headers?: Readonly<Record<string, string>>;
21
22
  }
23
+ /** The profile's headers with `{session}` replaced by the session id. A
24
+ * header that names `{session}` when there is no session (`kiso sessions`,
25
+ * which streams nothing) is left out, never sent with the placeholder in
26
+ * it. Undefined when nothing is left. */
27
+ export declare function expandProfileHeaders(headers: Readonly<Record<string, string>> | undefined, sessionId: string | undefined): Readonly<Record<string, string>> | undefined;
22
28
  export declare function adapterOptionsFor(profile: ModelProfile, auth: AdapterAuth, sessionId?: string): AdapterOptions;
@@ -33,6 +33,25 @@
33
33
  */
34
34
  import { effectiveBaseUrl } from "./credentials.js";
35
35
  import { oauthTokenThunk } from "./token.js";
36
+ /** The profile's headers with `{session}` replaced by the session id. A
37
+ * header that names `{session}` when there is no session (`kiso sessions`,
38
+ * which streams nothing) is left out, never sent with the placeholder in
39
+ * it. Undefined when nothing is left. */
40
+ export function expandProfileHeaders(headers, sessionId) {
41
+ if (headers === undefined)
42
+ return undefined;
43
+ const out = {};
44
+ for (const [name, value] of Object.entries(headers)) {
45
+ if (value.includes("{session}")) {
46
+ if (sessionId === undefined)
47
+ continue;
48
+ out[name] = value.split("{session}").join(sessionId);
49
+ }
50
+ else
51
+ out[name] = value;
52
+ }
53
+ return Object.keys(out).length === 0 ? undefined : out;
54
+ }
36
55
  export function adapterOptionsFor(profile, auth, sessionId) {
37
56
  // OR-1: exactly one sign-in shape reaches the adapter. An OAuth profile
38
57
  // has no key to pass — it passes the thunk the adapter re-resolves per
@@ -43,6 +62,7 @@ export function adapterOptionsFor(profile, auth, sessionId) {
43
62
  // own environment variable, and a profile with no baseUrl then sent the
44
63
  // stored vendor key wherever that variable pointed.
45
64
  const url = effectiveBaseUrl(profile.kind, profile.baseUrl);
65
+ const headers = expandProfileHeaders(profile.headers, sessionId);
46
66
  return {
47
67
  ...credential,
48
68
  ...(url !== undefined ? { baseUrl: url } : {}),
@@ -53,5 +73,9 @@ export function adapterOptionsFor(profile, auth, sessionId) {
53
73
  // cache.
54
74
  ...(sessionId !== undefined ? { promptCacheKey: sessionId } : {}),
55
75
  ...(profile.promptCaching !== undefined ? { promptCaching: profile.promptCaching } : {}),
76
+ // The endpoint's own headers (a gateway's session header, say), with
77
+ // the session id where the profile wrote `{session}` — the same id
78
+ // as the cache key above, so one conversation is one lane end to end.
79
+ ...(headers !== undefined ? { headers } : {}),
56
80
  };
57
81
  }
package/dist/chat.js CHANGED
@@ -14,8 +14,9 @@ import { canonicalizeUsage } from "@vincemakes/kiso-runtime";
14
14
  import { canonicalizeUsageForModel, requestBudget } from "@vincemakes/kiso-runtime/internal";
15
15
  import { dispatch, abortBangCommand } from "./dispatch.js";
16
16
  import { paintWindowTitle } from "./window-title.js";
17
- import { agentBaseUrl, agentModel, body, bodyLog, configuredWindow, dock, retryOnRow, retryShown, setRetryShown, floorOn, protectedFiles, upstreamOf } from "./state.js";
17
+ import { agentBaseUrl, agentModel, body, bodyLog, configuredWindow, dock, retryOnRow, retryShown, setRetryShown, floorOn, protectedFiles, upstreamOf, VERSION } from "./state.js";
18
18
  import { attachImages } from "./attachments.js";
19
+ import { installedVersion, staleVersionNotice } from "./stale-version.js";
19
20
  import { learnedWindowFor } from "./learned-windows.js";
20
21
  import { lookupContextWindow, lookupModelMetadata } from "@vincemakes/kiso-runtime/internal";
21
22
  import { addDontAskAgainRule, askPanel, fixHintFor, pendingAsk, resolveUncertains } from "./trust-ui.js";
@@ -1365,6 +1366,12 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
1365
1366
  stopSpinner();
1366
1367
  paintIdle();
1367
1368
  currentRun = null;
1369
+ // 0.40.5: the kiso on disk may no longer be the one this
1370
+ // session runs (an upgrade since it started) — said once per
1371
+ // installed version (stale-version.ts).
1372
+ const stale = staleVersionNotice(installedVersion(), VERSION, session.id);
1373
+ if (stale !== null)
1374
+ body.notice(stale);
1368
1375
  // round 8: a faux script that ran out of declared turns exits
1369
1376
  // loudly with a non-zero status — never a silent status 0.
1370
1377
  // round 4 (adversarial): the exhaustion is a CONTROLLED rejection of
package/dist/config.d.ts CHANGED
@@ -75,6 +75,16 @@ export interface ModelProfile {
75
75
  * the registry looks for this model's window when `baseUrl` has no row.
76
76
  */
77
77
  readonly upstream?: string;
78
+ /**
79
+ * Headers the endpoint needs on every request — a gateway that routes a
80
+ * conversation by a session header, say. `{session}` in a value becomes
81
+ * the kiso session's id, so one conversation keeps one id across /model
82
+ * and /resume, and two conversations never share one. Names are
83
+ * lower-cased. A credential header is refused by name: a key never lives
84
+ * in this file (`apiKeyEnv` and `kiso login` are the doors), and the
85
+ * framing headers belong to the adapter.
86
+ */
87
+ readonly headers?: Readonly<Record<string, string>>;
78
88
  }
79
89
  export interface AutoCompactConfig {
80
90
  readonly thresholdRatio: number;
package/dist/config.js CHANGED
@@ -34,6 +34,27 @@ import { join } from "node:path";
34
34
  import { kisoHome } from "./state.js";
35
35
  import { AuthError, getCredential, providerIdOf } from "./auth/credentials.js";
36
36
  import { BUILTIN_MANIFESTS } from "@vincemakes/kiso-runtime/internal";
37
+ /** Header names a profile may not set: credentials (a key never lives in the
38
+ * config file) and the framing the adapter owns. */
39
+ const REFUSED_HEADERS = new Set(["authorization", "proxy-authorization", "x-api-key", "api-key", "cookie", "host", "content-length", "content-type", "transfer-encoding", "connection"]);
40
+ function parseHeaders(field, raw, fail) {
41
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
42
+ fail(field, "expected an object of header names to string values");
43
+ const out = {};
44
+ for (const [name, value] of Object.entries(raw)) {
45
+ if (!/^[A-Za-z0-9-]+$/.test(name))
46
+ fail(`${field}.${name}`, "expected a header name of letters, digits and dashes");
47
+ const lower = name.toLowerCase();
48
+ if (REFUSED_HEADERS.has(lower))
49
+ fail(`${field}.${name}`, "a credential or framing header cannot be set here — the key comes from apiKeyEnv or kiso login");
50
+ if (lower in out)
51
+ fail(`${field}.${name}`, "named twice (header names are case-insensitive)");
52
+ if (typeof value !== "string" || /[\r\n\0]/.test(value))
53
+ fail(`${field}.${name}`, "expected a one-line string value");
54
+ out[lower] = value;
55
+ }
56
+ return out;
57
+ }
37
58
  const KINDS = ["openai-compat", "anthropic", "openai-responses"];
38
59
  const MODES_LIST = ["manual", "default", "accept-edits", "plan", "bypass", "dontAsk"];
39
60
  export class ConfigError extends Error {
@@ -149,6 +170,7 @@ export function parseConfig(text, source) {
149
170
  fail(`models.${name}.contextWindow`, "expected a positive token count");
150
171
  if (p.upstream !== undefined && (typeof p.upstream !== "string" || p.upstream.trim() === ""))
151
172
  fail(`models.${name}.upstream`, "expected the URL or name of what this endpoint forwards to");
173
+ const headers = p.headers === undefined ? undefined : parseHeaders(`models.${name}.headers`, p.headers, fail);
152
174
  models[name] = {
153
175
  kind: p.kind,
154
176
  model: p.model,
@@ -158,6 +180,7 @@ export function parseConfig(text, source) {
158
180
  ...(typeof p.streamIdleMs === "number" ? { streamIdleMs: p.streamIdleMs } : {}),
159
181
  ...(typeof p.contextWindow === "number" ? { contextWindow: p.contextWindow } : {}),
160
182
  ...(typeof p.upstream === "string" ? { upstream: p.upstream.trim() } : {}),
183
+ ...(headers !== undefined ? { headers } : {}),
161
184
  };
162
185
  }
163
186
  out.models = models;
package/dist/dispatch.js CHANGED
@@ -9,9 +9,10 @@ import { buildAdapter, lookupModelMetadata, resolveContinuationScope, resolveRea
9
9
  import { MODES, MODE_NOTE, OFFERED_MODES, getMode, setMode } from "./mode.js";
10
10
  import { clipboardWrite, lastAnswer } from "./clipboard.js";
11
11
  import { protectedBangReason, protectedShellVerdict } from "./protected-shell.js";
12
- import { agentBaseUrl, currentProfileName, setCurrentProfileName, currentModelName, agentModel, body, bodyLog, codingToolOptions, protectedFiles, kisoHome, configModels, dock, lastBinding, loadedSkillsCatalog, mergedConfig, readContextLedger, retryOnRow, sessionsDir, setAgentModel, setConfiguredWindow, setCurrentModelName, setModelChoice, setRetryShown, upstreamOf, setLastBinding } from "./state.js";
12
+ import { agentBaseUrl, currentProfileName, setCurrentProfileName, currentModelName, agentModel, body, bodyLog, codingToolOptions, protectedFiles, kisoHome, configModels, dock, lastBinding, loadedSkillsCatalog, mergedConfig, readContextLedger, retryOnRow, sessionsDir, setAgentModel, setConfiguredWindow, setCurrentModelName, setModelChoice, setRetryShown, upstreamOf, VERSION, setLastBinding } from "./state.js";
13
13
  import { adapterOptionsFor } from "./auth/adapter-options.js";
14
14
  import { profileProviderLabel, providerLabel } from "./provider-label.js";
15
+ import { installedVersion, versionStatusLine } from "./stale-version.js";
15
16
  import { queuedSwitchLines } from "./state.js";
16
17
  import { contextWindowTokens, microcompactThresholdFor, startStatusSpinner, statedContextWindow, windowSourceNote } from "./chat.js";
17
18
  import { authForProfile, directWriteProfile, profileAvailable, resolveContextWindow, unavailableReason } from "./config.js";
@@ -514,6 +515,9 @@ export function dispatch(line, ctx) {
514
515
  // variable; until then, a label that cannot lie.)
515
516
  const profileOf = currentProfileName === null ? "" : ` · profile ${currentProfileName}`;
516
517
  bodyLog(`model ${agentModel}${providerLabel(agentBaseUrl, upstreamOf(agentBaseUrl))}${profileOf}`);
518
+ // 0.40.5: the version this session RUNS, and the installed one when
519
+ // an upgrade (or a rollback) since start-up made them differ.
520
+ bodyLog(versionStatusLine(installedVersion(), VERSION));
517
521
  ctx.input.prompt();
518
522
  });
519
523
  return;
@@ -0,0 +1,14 @@
1
+ /** The version of the kiso installed where this one was loaded from, read
2
+ * now — null on any failure, never a guess. KISO_INSTALLED_PACKAGE_JSON
3
+ * names another file: the test rigs' knob (like KISO_FAUX_SCRIPT), so an
4
+ * end-to-end run can "upgrade" underneath a live session without touching
5
+ * the checkout's own package.json. */
6
+ export declare function installedVersion(pkgPath?: string): string | null;
7
+ /** The transcript notice, once per installed version that differs from the
8
+ * running one (a newer install, or a rollback); null otherwise. */
9
+ export declare function staleVersionNotice(installed: string | null, running: string, sessionId: string): string | null;
10
+ /** /status's version line: the running version, and the installed one when
11
+ * they differ. */
12
+ export declare function versionStatusLine(installed: string | null, running: string): string;
13
+ /** Tests: forget which version was noticed. */
14
+ export declare function resetStaleVersionNotice(): void;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * 0.40.5 — a session says when the kiso on disk is not the kiso it runs.
3
+ *
4
+ * The owner, 2026-09-23: "I am on 0.40.3 — why does edit_file still fail?"
5
+ * The session's process had started at 13:03 on 0.40.2; 0.40.3 was
6
+ * installed at ~14:05. The process kept the code it loaded, so its
7
+ * edit_file still refused overlapping hunks, while `kiso --version` in
8
+ * another shell printed the version ON DISK. Nothing in the session said
9
+ * otherwise. A process that loads a module lazily after an upgrade (a
10
+ * provider on its first /model switch) would also mix the new version with
11
+ * the old — the notice is the cue to restart before that.
12
+ *
13
+ * `VERSION` (state.ts) is read once at start-up from the package.json next
14
+ * to the build; a global install replaces that file, so reading it AGAIN
15
+ * sees what a new start would run. One small read per turn and per
16
+ * /status; no timer, no watcher, no network.
17
+ */
18
+ import { readFileSync } from "node:fs";
19
+ import { dirname, join } from "node:path";
20
+ import { fileURLToPath } from "node:url";
21
+ /** The version of the kiso installed where this one was loaded from, read
22
+ * now — null on any failure, never a guess. KISO_INSTALLED_PACKAGE_JSON
23
+ * names another file: the test rigs' knob (like KISO_FAUX_SCRIPT), so an
24
+ * end-to-end run can "upgrade" underneath a live session without touching
25
+ * the checkout's own package.json. */
26
+ export function installedVersion(pkgPath = process.env.KISO_INSTALLED_PACKAGE_JSON ?? join(dirname(fileURLToPath(import.meta.url)), "..", "package.json")) {
27
+ try {
28
+ const v = JSON.parse(readFileSync(pkgPath, "utf8")).version;
29
+ return typeof v === "string" && v !== "" ? v : null;
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ let lastNoticed = null;
36
+ /** The transcript notice, once per installed version that differs from the
37
+ * running one (a newer install, or a rollback); null otherwise. */
38
+ export function staleVersionNotice(installed, running, sessionId) {
39
+ if (installed === null || installed === running || running === "?" || installed === lastNoticed)
40
+ return null;
41
+ lastNoticed = installed;
42
+ return `✦ kiso ${installed} is installed — this session runs ${running}; exit and resume it (kiso resume ${sessionId}) to use it`;
43
+ }
44
+ /** /status's version line: the running version, and the installed one when
45
+ * they differ. */
46
+ export function versionStatusLine(installed, running) {
47
+ return installed === null || installed === running ? `version ${running}` : `version ${running} (running) · ${installed} installed — restart to use it`;
48
+ }
49
+ /** Tests: forget which version was noticed. */
50
+ export function resetStaleVersionNotice() {
51
+ lastNoticed = null;
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-code",
3
- "version": "0.40.3",
3
+ "version": "0.40.5",
4
4
  "description": "kiso-code \u2014 the coding agent on kiso, the durable runtime for AI agents: kiso chat / kiso resume / kiso sessions.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -18,20 +18,20 @@
18
18
  "test": "vitest run"
19
19
  },
20
20
  "dependencies": {
21
- "@vincemakes/kiso-ask-ext": "0.40.3",
22
- "@vincemakes/kiso-core": "0.40.3",
23
- "@vincemakes/kiso-evals": "0.40.3",
24
- "@vincemakes/kiso-mcp-ext": "0.40.3",
25
- "@vincemakes/kiso-provider-anthropic": "0.40.3",
26
- "@vincemakes/kiso-provider-openai": "0.40.3",
27
- "@vincemakes/kiso-provider-openai-responses": "0.40.3",
28
- "@vincemakes/kiso-runtime": "0.40.3",
29
- "@vincemakes/kiso-skills-ext": "0.40.3",
30
- "@vincemakes/kiso-subagent-ext": "0.40.3",
31
- "@vincemakes/kiso-task-ext": "0.40.3",
32
- "@vincemakes/kiso-tools-node": "0.40.3",
33
- "@vincemakes/kiso-tui": "0.40.3",
34
- "@vincemakes/kiso-tui-cells": "0.40.3"
21
+ "@vincemakes/kiso-ask-ext": "0.40.5",
22
+ "@vincemakes/kiso-core": "0.40.5",
23
+ "@vincemakes/kiso-evals": "0.40.5",
24
+ "@vincemakes/kiso-mcp-ext": "0.40.5",
25
+ "@vincemakes/kiso-provider-anthropic": "0.40.5",
26
+ "@vincemakes/kiso-provider-openai": "0.40.5",
27
+ "@vincemakes/kiso-provider-openai-responses": "0.40.5",
28
+ "@vincemakes/kiso-runtime": "0.40.5",
29
+ "@vincemakes/kiso-skills-ext": "0.40.5",
30
+ "@vincemakes/kiso-subagent-ext": "0.40.5",
31
+ "@vincemakes/kiso-task-ext": "0.40.5",
32
+ "@vincemakes/kiso-tools-node": "0.40.5",
33
+ "@vincemakes/kiso-tui": "0.40.5",
34
+ "@vincemakes/kiso-tui-cells": "0.40.5"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "^26.1.2",