@vincemakes/kiso-code 0.15.11 → 0.16.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/dispatch.js CHANGED
@@ -5,9 +5,9 @@
5
5
  */
6
6
  import { contextRows, contextUnavailableRows, displayVerb, escapeTerminal, helpRows, kUnit, modelPickView, palette } from "@vincemakes/kiso-tui";
7
7
  import { newSessionId } from "./session-id.js";
8
- import { buildAdapter } from "@vincemakes/kiso-runtime/internal";
8
+ import { buildAdapter, resolveContinuationScope, resolveReasoning } from "@vincemakes/kiso-runtime/internal";
9
9
  import { MODES, getMode, setMode } from "./mode.js";
10
- import { agentModel, body, bodyLog, configModels, dock, readContextLedger, sessionsDir, setAgentModel, setCurrentModelName } from "./state.js";
10
+ import { agentModel, body, bodyLog, configModels, dock, readContextLedger, sessionsDir, setAgentModel, setCurrentModelName, setLastBinding } from "./state.js";
11
11
  import { directWriteProfile, profileAvailable } from "./config.js";
12
12
  /** The ONE dispatcher — slash commands, exit, and turns. The recovery
13
13
  * replay routes through it too — a queued "/last" must never become a
@@ -228,10 +228,15 @@ export function dispatch(line, ctx) {
228
228
  }
229
229
  else {
230
230
  try {
231
- const direct = directWriteProfile(arg);
232
- const profile = direct ?? configModels[arg];
231
+ // XP-1: /model owns the model PLUS the reasoning selector —
232
+ // an optional second token is the effort, validated
233
+ // against the NATIVE matrix (refused with the levels
234
+ // named; never silently downgraded).
235
+ const [profName = "", effortTok] = arg.split(/\s+/);
236
+ const direct = directWriteProfile(profName);
237
+ const profile = direct ?? configModels[profName];
233
238
  if (profile === undefined) {
234
- bodyLog(`no such model profile: ${arg}`);
239
+ bodyLog(`no such model profile: ${profName}`);
235
240
  }
236
241
  else if (!profileAvailable(profile)) {
237
242
  bodyLog(`model ${arg}: unavailable — the env var ${profile.apiKeyEnv} is not set (configs never store keys, only the env-var name)`);
@@ -250,10 +255,38 @@ export function dispatch(line, ctx) {
250
255
  // carrying the OLD model, so the status row claimed the
251
256
  // new model while every request still sent the old id
252
257
  // (and usage canonicalized under the old route).
253
- ctx.session.setModelBinding({ adapter, model: profile.model, provider: profile.kind });
254
- setAgentModel(profile.model);
255
- setCurrentModelName(arg);
256
- body.notice(`model → ${arg} (${profile.model}) — takes effect on the next turn`);
258
+ // MG-1 (A5): the scope moves WITH the adapter — the same
259
+ // atomic switch PH-F8 demanded, one more passenger.
260
+ const scope = resolveContinuationScope(profile.kind, profile.model, profile.baseUrl);
261
+ let reasoning;
262
+ let refused = null;
263
+ if (effortTok !== undefined) {
264
+ const candidate = { thinking: "default", effort: effortTok };
265
+ const resolved = resolveReasoning(profile.model, candidate);
266
+ if (resolved.ok)
267
+ reasoning = candidate;
268
+ else
269
+ refused = resolved.reason;
270
+ }
271
+ if (refused !== null) {
272
+ // native-only, never a silent downgrade: the switch
273
+ // does NOT happen — the refusal names the levels.
274
+ bodyLog(refused);
275
+ }
276
+ else {
277
+ const binding = {
278
+ adapter,
279
+ model: profile.model,
280
+ provider: profile.kind,
281
+ ...(scope !== undefined ? { scope } : {}),
282
+ ...(reasoning !== undefined ? { reasoning } : {}),
283
+ };
284
+ ctx.session.setModelBinding(binding);
285
+ setLastBinding(binding);
286
+ setAgentModel(profile.model);
287
+ setCurrentModelName(arg);
288
+ body.notice(`model → ${profName} (${profile.model}${effortTok !== undefined ? ` · ${effortTok}` : ""}) — takes effect on the next turn`);
289
+ }
257
290
  }
258
291
  }
259
292
  catch (err) {
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ import { createFauxProvider } from "@vincemakes/kiso-evals";
34
34
  import { createCodingTools } from "@vincemakes/kiso-tools-node";
35
35
  import { MODES, getMode, modeExtensions, modeFromEnv, modeSystemPrompt, setMode } from "./mode.js";
36
36
  import { builtInLayer } from "./builtin.js";
37
- import { agentModel, atFiles, body, bodyLog, kisoHome, builtInExtensions, currentFaux, dock, extensionsDir, loadedExtensions, mergedConfig, mergedTempPaths, projectExtensions, sessionStoreRef, sessionsDir, setAgentModel, setBody, setConfigModels, setConfiguredWindow, setCurrentAgentExtensions, setCurrentFaux, setCurrentModelName, setExtensionLists, setMergedConfig, setSessionStore, userExtensions, VERSION } from "./state.js";
37
+ import { agentModel, atFiles, body, bodyLog, kisoHome, builtInExtensions, currentFaux, dock, extensionsDir, loadedExtensions, mergedConfig, mergedTempPaths, projectExtensions, sessionStoreRef, sessionsDir, setAgentModel, setBody, setConfigModels, setConfiguredWindow, setCurrentAgentExtensions, setCurrentFaux, setCurrentModelName, setExtensionLists, setMergedConfig, setSessionStore, userExtensions, VERSION, lastBinding, acceptDrift, setAcceptDrift } from "./state.js";
38
38
  import { askUi, resolveProjectTrust } from "./trust-ui.js";
39
39
  import { isFirstRun, scaffoldFirstRun } from "./first-run.js";
40
40
  import { fauxSkip, readFauxScript } from "./faux-glue.js";
@@ -299,7 +299,7 @@ async function recentSessions(id, agent) {
299
299
  for (const m of picked) {
300
300
  let badge;
301
301
  if (store !== null) {
302
- const session = await agent.session({ id: m.id });
302
+ const session = await agent.session({ id: m.id, ...(acceptDrift() ? { acceptDrift: true } : {}) });
303
303
  const card = projectSessionCard({ id: m.id, updatedAt: m.updatedAt, records: store.load(m.id), asks: session.pendingApprovals().length });
304
304
  badge = BADGE_GLYPH[card.badge];
305
305
  }
@@ -598,7 +598,7 @@ async function chatLoop(agent, firstId, input, autoCompact) {
598
598
  let id = firstId;
599
599
  let prev = null;
600
600
  for (;;) {
601
- const session = await agent.session({ id });
601
+ const session = await agent.session({ id, ...(acceptDrift() ? { acceptDrift: true } : {}) });
602
602
  if (prev === null) {
603
603
  bodyLog(`session ${id}\n`);
604
604
  // REL-0152-D5: a session with history says what that history WAS.
@@ -617,6 +617,18 @@ async function chatLoop(agent, firstId, input, autoCompact) {
617
617
  if (currentFaux)
618
618
  session.setAdapter(createFauxProvider(readFauxScript().slice(fauxSkip(id))));
619
619
  }
620
+ // XP-1 §3.3.6: a /clear-fresh session INHERITS the live selection,
621
+ // recorded as its next revision — clearing context never silently
622
+ // reverts the model.
623
+ const inherited = lastBinding();
624
+ if (session.log.all.length === 0 && inherited !== null && !currentFaux) {
625
+ session.setModelBinding(inherited);
626
+ }
627
+ // XP-1 §2.1: the switched-to session's OWN truth repaints the row —
628
+ // the global display state never outlives the session it described
629
+ // (pre-XP the row kept the previous session's /model selection).
630
+ setAgentModel(session.model);
631
+ setCurrentModelName(session.model);
620
632
  paintBootStatus(session);
621
633
  const nav = {
622
634
  sessions: () => agent.sessions().map((m) => m.id),
@@ -646,6 +658,16 @@ async function main() {
646
658
  // first makeAgent (the tier extensions read `current` live). The flag
647
659
  // is stripped from the positional args, so it works in any position.
648
660
  const args = process.argv.slice(2);
661
+ // XP-1: --accept-drift (a flag, never an env var) authorizes opening a
662
+ // session whose recorded profile materially drifted — the
663
+ // acknowledgement is recorded as a new revision by the runtime.
664
+ {
665
+ const i = args.indexOf("--accept-drift");
666
+ if (i !== -1) {
667
+ args.splice(i, 1);
668
+ setAcceptDrift(true);
669
+ }
670
+ }
649
671
  // PH-1a (finding PH-F2): --help/-h/--version/-v are FLAGS, not session
650
672
  // ids. They used to fall through the default case and START A SESSION
651
673
  // literally named "--help" (writing ~/.kiso/sessions/--help.jsonl) —
@@ -793,7 +815,7 @@ async function main() {
793
815
  const id = command ?? newSessionId(sessionsDir());
794
816
  agent = await makeAgent(id, input, modelFlag);
795
817
  applyConfigMode();
796
- const session = await agent.session({ id });
818
+ const session = await agent.session({ id, ...(acceptDrift() ? { acceptDrift: true } : {}) });
797
819
  faux = currentFaux;
798
820
  await resume(session, printPrompt, faux, input);
799
821
  const last = [...session.log.all].reverse().find((e) => e.type === "terminal");
@@ -847,7 +869,7 @@ async function main() {
847
869
  await chatLoop(agent, picked, input, resolveAutoCompact(mergedConfig));
848
870
  break;
849
871
  }
850
- const session = await agent.session({ id });
872
+ const session = await agent.session({ id, ...(acceptDrift() ? { acceptDrift: true } : {}) });
851
873
  faux = currentFaux;
852
874
  // E area: the durable script position is computed from the
853
875
  // session id, and on the picker path the id did not exist when
@@ -88,8 +88,20 @@ export function projectSessionCard(input) {
88
88
  export async function collectSessionCards(agent, load) {
89
89
  const cards = [];
90
90
  for (const meta of [...agent.sessions()].sort((a, b) => b.updatedAt - a.updatedAt)) {
91
- const session = await agent.session({ id: meta.id });
92
- cards.push(projectSessionCard({ id: meta.id, updatedAt: meta.updatedAt, records: load(meta.id), asks: session.pendingApprovals().length }));
91
+ // XP-1: the LISTING never enforces the profile contract — a session
92
+ // whose recorded profile drifted (or whose sidecar is damaged) still
93
+ // LISTS; the honest refusal happens at the open, where the message
94
+ // can be acted on. acceptDrift is never passed here: it RECORDS an
95
+ // acknowledgement, and a listing must write nothing.
96
+ let asks = 0;
97
+ try {
98
+ const session = await agent.session({ id: meta.id });
99
+ asks = session.pendingApprovals().length;
100
+ }
101
+ catch {
102
+ // blocked by the profile contract — the card carries no ask badge
103
+ }
104
+ cards.push(projectSessionCard({ id: meta.id, updatedAt: meta.updatedAt, records: load(meta.id), asks }));
93
105
  }
94
106
  return cards;
95
107
  }
package/dist/state.d.ts CHANGED
@@ -195,3 +195,18 @@ export declare const VERSION: string;
195
195
  * real user answer (the empty line). The empty answer and the cancellation
196
196
  * are distinct facts. */
197
197
  export declare const CANCELLED: unique symbol;
198
+ /** XP-1: the last EXPLICIT model binding this process applied — a
199
+ * /clear-fresh session inherits it (clearing context never silently
200
+ * reverts the model). Never persisted here; the runtime records the
201
+ * durable revision when the binding is applied. */
202
+ export interface LastBinding {
203
+ readonly adapter: import("@vincemakes/kiso-core").Adapter;
204
+ readonly model: string;
205
+ readonly provider?: "anthropic" | "openai-compat";
206
+ readonly scope?: import("@vincemakes/kiso-core").ContinuationScope;
207
+ readonly reasoning?: import("@vincemakes/kiso-runtime/internal").ReasoningSetting;
208
+ }
209
+ export declare function setLastBinding(b: LastBinding): void;
210
+ export declare function lastBinding(): LastBinding | null;
211
+ export declare function setAcceptDrift(v: boolean): void;
212
+ export declare function acceptDrift(): boolean;
package/dist/state.js CHANGED
@@ -251,3 +251,20 @@ export const VERSION = (() => {
251
251
  * real user answer (the empty line). The empty answer and the cancellation
252
252
  * are distinct facts. */
253
253
  export const CANCELLED = Symbol("kiso-question-cancelled");
254
+ let lastBindingValue = null;
255
+ export function setLastBinding(b) {
256
+ lastBindingValue = b;
257
+ }
258
+ export function lastBinding() {
259
+ return lastBindingValue;
260
+ }
261
+ /** XP-1 (the adjudicated Q6): --accept-drift is a per-invocation FLAG,
262
+ * never an env var — a standing env would be a silent policy, the exact
263
+ * failure the drift protocol forbids. */
264
+ let acceptDriftValue = false;
265
+ export function setAcceptDrift(v) {
266
+ acceptDriftValue = v;
267
+ }
268
+ export function acceptDrift() {
269
+ return acceptDriftValue;
270
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-code",
3
- "version": "0.15.11",
3
+ "version": "0.16.0",
4
4
  "description": "kiso CLI \u2014 the durable coding agent that survives kill -9: kiso chat / kiso resume / kiso sessions.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -18,19 +18,19 @@
18
18
  "test": "vitest run"
19
19
  },
20
20
  "dependencies": {
21
- "@vincemakes/kiso-ask-ext": "0.15.11",
22
- "@vincemakes/kiso-core": "0.15.11",
23
- "@vincemakes/kiso-evals": "0.15.11",
24
- "@vincemakes/kiso-mcp-ext": "0.15.11",
25
- "@vincemakes/kiso-provider-anthropic": "0.15.11",
26
- "@vincemakes/kiso-provider-openai": "0.15.11",
27
- "@vincemakes/kiso-runtime": "0.15.11",
28
- "@vincemakes/kiso-skills-ext": "0.15.11",
29
- "@vincemakes/kiso-subagent-ext": "0.15.11",
30
- "@vincemakes/kiso-task-ext": "0.15.11",
31
- "@vincemakes/kiso-tools-node": "0.15.11",
32
- "@vincemakes/kiso-tui": "0.15.11",
33
- "@vincemakes/kiso-tui-cells": "0.15.11"
21
+ "@vincemakes/kiso-ask-ext": "0.16.0",
22
+ "@vincemakes/kiso-core": "0.16.0",
23
+ "@vincemakes/kiso-evals": "0.16.0",
24
+ "@vincemakes/kiso-mcp-ext": "0.16.0",
25
+ "@vincemakes/kiso-provider-anthropic": "0.16.0",
26
+ "@vincemakes/kiso-provider-openai": "0.16.0",
27
+ "@vincemakes/kiso-runtime": "0.16.0",
28
+ "@vincemakes/kiso-skills-ext": "0.16.0",
29
+ "@vincemakes/kiso-subagent-ext": "0.16.0",
30
+ "@vincemakes/kiso-task-ext": "0.16.0",
31
+ "@vincemakes/kiso-tools-node": "0.16.0",
32
+ "@vincemakes/kiso-tui": "0.16.0",
33
+ "@vincemakes/kiso-tui-cells": "0.16.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^26.1.2",