@vincemakes/kiso-code 0.15.3 → 0.15.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.
package/dist/index.js CHANGED
@@ -41,6 +41,7 @@ import { fauxSkip, readFauxScript } from "./faux-glue.js";
41
41
  import { autoCompactFromEnv, chat, contextWindowTokens, estimateCtxRatio } from "./chat.js";
42
42
  import { loadProjectConfig, loadUserConfig, mergeConfigs, resolveAutoCompact, resolveContextWindow, resolveModel } from "./config.js";
43
43
  import { resume } from "./resume.js";
44
+ import { resumeTail } from "./resume-tail.js";
44
45
  import { collectSessionCards, projectSessionCard } from "./session-cards.js";
45
46
  // The moved exports stay reachable from this entry — the test imports
46
47
  // (project-trust, coding-agent) never change (B4: zero assertion changes).
@@ -591,10 +592,19 @@ async function chatLoop(agent, firstId, input, autoCompact) {
591
592
  const session = await agent.session({ id });
592
593
  if (prev === null) {
593
594
  bodyLog(`session ${id}\n`);
595
+ // REL-0152-D5: a session with history says what that history WAS.
596
+ // Resuming used to print this one line and drop you at an empty
597
+ // prompt inside a conversation with thousands of events — the
598
+ // durable log was right there and none of it was shown. Empty for
599
+ // a fresh session, so `kiso chat` is byte-identical.
600
+ for (const line of resumeTail(session.log.all))
601
+ bodyLog(line);
594
602
  extensionsBanner(await recentSessions(id, agent));
595
603
  }
596
604
  else {
597
605
  bodyLog(`session ${id} (switched — previous: ${prev}, /resume ${prev} returns)\n`);
606
+ for (const line of resumeTail(session.log.all))
607
+ bodyLog(line);
598
608
  if (currentFaux)
599
609
  session.setAdapter(createFauxProvider(readFauxScript().slice(fauxSkip(id))));
600
610
  }
@@ -822,6 +832,10 @@ async function main() {
822
832
  // exactly where `kiso resume <id>` would have.
823
833
  if (faux && arg === undefined)
824
834
  session.setAdapter(createFauxProvider(readFauxScript().slice(fauxSkip(id))));
835
+ // REL-0152-D5 — the same tail on the explicit-id form. NOT on
836
+ // the -p path above: that one's stdout is a machine's input.
837
+ for (const line of resumeTail(session.log.all))
838
+ bodyLog(line);
825
839
  await resume(session, prompt, faux, input);
826
840
  break;
827
841
  }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * REL-0152-D5 — what a resumed session shows you.
3
+ *
4
+ * `kiso resume <id>` printed one line naming the session and nothing
5
+ * else, dropping the user at an empty prompt inside a conversation with
6
+ * thousands of events. The product's claim is that a long task survives
7
+ * interruption and you can come back to it; coming back meant being told
8
+ * the session exists and shown none of it.
9
+ *
10
+ * The history is right there — the forensics in this round rebuilt an
11
+ * entire screen from 6,581 recorded events. This projects a bounded tail
12
+ * of it so the user can see where they were.
13
+ *
14
+ * A PROJECTION, never a second truth: it reads the durable log and
15
+ * renders text. It writes nothing, decides nothing, and cannot disagree
16
+ * with the session because it holds no state of its own.
17
+ */
18
+ /**
19
+ * The lines to print when a session is resumed — newest turn last, so it
20
+ * reads in conversation order and the newest text sits nearest the
21
+ * prompt. Empty when there is nothing to show, and the caller prints
22
+ * nothing rather than an empty frame.
23
+ */
24
+ export declare function resumeTail(events: readonly {
25
+ readonly type: string;
26
+ }[]): string[];
@@ -0,0 +1,63 @@
1
+ /**
2
+ * REL-0152-D5 — what a resumed session shows you.
3
+ *
4
+ * `kiso resume <id>` printed one line naming the session and nothing
5
+ * else, dropping the user at an empty prompt inside a conversation with
6
+ * thousands of events. The product's claim is that a long task survives
7
+ * interruption and you can come back to it; coming back meant being told
8
+ * the session exists and shown none of it.
9
+ *
10
+ * The history is right there — the forensics in this round rebuilt an
11
+ * entire screen from 6,581 recorded events. This projects a bounded tail
12
+ * of it so the user can see where they were.
13
+ *
14
+ * A PROJECTION, never a second truth: it reads the durable log and
15
+ * renders text. It writes nothing, decides nothing, and cannot disagree
16
+ * with the session because it holds no state of its own.
17
+ */
18
+ /** How much of the tail is worth showing. Two turns is enough to
19
+ * recognise a conversation and short enough that resuming does not
20
+ * bury the prompt. */
21
+ const TURNS = 2;
22
+ const REPLY_CHARS = 400;
23
+ const oneLine = (s) => s.replace(/\s+/g, " ").trim();
24
+ const clip = (s, n) => (s.length <= n ? s : `${s.slice(0, n - 1)}…`);
25
+ /**
26
+ * The lines to print when a session is resumed — newest turn last, so it
27
+ * reads in conversation order and the newest text sits nearest the
28
+ * prompt. Empty when there is nothing to show, and the caller prints
29
+ * nothing rather than an empty frame.
30
+ */
31
+ export function resumeTail(events) {
32
+ const turns = [];
33
+ let ask = null;
34
+ let reply = "";
35
+ for (const ev of events) {
36
+ const e = ev;
37
+ if (e.type === "user_input" && typeof e.content === "string") {
38
+ if (ask !== null)
39
+ turns.push({ ask, reply });
40
+ ask = e.content;
41
+ reply = "";
42
+ }
43
+ else if (e.type === "text_delta" && typeof e.text === "string" && ask !== null) {
44
+ reply += e.text;
45
+ }
46
+ }
47
+ if (ask !== null)
48
+ turns.push({ ask, reply });
49
+ const shown = turns.slice(-TURNS);
50
+ if (shown.length === 0)
51
+ return [];
52
+ const lines = [];
53
+ const skipped = turns.length - shown.length;
54
+ lines.push(skipped > 0 ? `─ resuming · ${turns.length} turns, showing the last ${shown.length} ─` : `─ resuming · ${turns.length} turn${turns.length === 1 ? "" : "s"} ─`);
55
+ for (const t of shown) {
56
+ lines.push(` › ${clip(oneLine(t.ask), 100)}`);
57
+ const body = oneLine(t.reply);
58
+ // A turn with no reply is a turn that was INTERRUPTED — the case
59
+ // resume exists for. Saying so beats printing a blank line.
60
+ lines.push(body === "" ? " (no reply recorded — this is where it stopped)" : ` ${clip(body, REPLY_CHARS)}`);
61
+ }
62
+ return lines;
63
+ }
@@ -33,6 +33,11 @@ import { type StoreRecord } from "@vincemakes/kiso-runtime/internal";
33
33
  export type SessionBadge = "uncertain" | "ask" | "interrupted" | "completed" | "failed";
34
34
  export interface SessionCard {
35
35
  readonly id: string;
36
+ /** REL-0152-D6b: what the conversation was ABOUT. An id is unique and
37
+ * says nothing; a picker of five ids is five rows the human cannot
38
+ * tell apart. From the runtime's one definition — never a second
39
+ * copy of the rule (the listing and the picker disagreed once). */
40
+ readonly title: string;
36
41
  readonly badge: SessionBadge;
37
42
  /** The human's unit: one user_input is one turn. (SessionMeta.runs
38
43
  * counts runIds, which a resume increments without the human having
@@ -28,7 +28,7 @@
28
28
  * terminal layer (sessionNote — the KC3 §1 split); this file owns the
29
29
  * FACTS.
30
30
  */
31
- import { executionLedger, openRunId } from "@vincemakes/kiso-runtime/internal";
31
+ import { executionLedger, openRunId, sessionTitle } from "@vincemakes/kiso-runtime/internal";
32
32
  /**
33
33
  * The projection. `asks` arrives as DATA because its accessor lives on
34
34
  * the session object (pendingApprovals) rather than on the record list —
@@ -74,7 +74,7 @@ export function projectSessionCard(input) {
74
74
  : outcome === "completed"
75
75
  ? "completed"
76
76
  : "failed";
77
- return { id: input.id, badge, turns, updatedAt: input.updatedAt, uncertain, asks: input.asks, outcome };
77
+ return { id: input.id, title: sessionTitle(input.records), badge, turns, updatedAt: input.updatedAt, uncertain, asks: input.asks, outcome };
78
78
  }
79
79
  /**
80
80
  * The listing's cards, newest first — the order both the picker and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-code",
3
- "version": "0.15.3",
3
+ "version": "0.15.5",
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.3",
22
- "@vincemakes/kiso-core": "0.15.3",
23
- "@vincemakes/kiso-evals": "0.15.3",
24
- "@vincemakes/kiso-mcp-ext": "0.15.3",
25
- "@vincemakes/kiso-provider-anthropic": "0.15.3",
26
- "@vincemakes/kiso-provider-openai": "0.15.3",
27
- "@vincemakes/kiso-runtime": "0.15.3",
28
- "@vincemakes/kiso-skills-ext": "0.15.3",
29
- "@vincemakes/kiso-subagent-ext": "0.15.3",
30
- "@vincemakes/kiso-task-ext": "0.15.3",
31
- "@vincemakes/kiso-tools-node": "0.15.3",
32
- "@vincemakes/kiso-tui": "0.15.3",
33
- "@vincemakes/kiso-tui-cells": "0.15.3"
21
+ "@vincemakes/kiso-ask-ext": "0.15.5",
22
+ "@vincemakes/kiso-core": "0.15.5",
23
+ "@vincemakes/kiso-evals": "0.15.5",
24
+ "@vincemakes/kiso-mcp-ext": "0.15.5",
25
+ "@vincemakes/kiso-provider-anthropic": "0.15.5",
26
+ "@vincemakes/kiso-provider-openai": "0.15.5",
27
+ "@vincemakes/kiso-runtime": "0.15.5",
28
+ "@vincemakes/kiso-skills-ext": "0.15.5",
29
+ "@vincemakes/kiso-subagent-ext": "0.15.5",
30
+ "@vincemakes/kiso-task-ext": "0.15.5",
31
+ "@vincemakes/kiso-tools-node": "0.15.5",
32
+ "@vincemakes/kiso-tui": "0.15.5",
33
+ "@vincemakes/kiso-tui-cells": "0.15.5"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^26.1.2",