@vincemakes/kiso-code 0.24.5 → 0.26.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/chat.js CHANGED
@@ -1193,6 +1193,9 @@ export async function chat(session, faux, input, autoCompact, nav) {
1193
1193
  // chain action (the sentinel's control char marks the key, so a typed
1194
1194
  // "expand" turn is never intercepted).
1195
1195
  input.onExpand(() => dispatch("\x12expand", dispatchCtx));
1196
+ // E1 §3 — ctrl+x and `/copy` are the same action reached two ways, so
1197
+ // they are the same sentinel: one implementation, one behaviour.
1198
+ input.onCopy(() => dispatch("\x18copy", dispatchCtx));
1196
1199
  // R3a — Shift+Tab: the approval-tier cycle (the /mode ring, in the
1197
1200
  // MODES order). The switch is the SAME live-extension flip /mode
1198
1201
  // performs; the status row repaints at once with a one-line notice.
@@ -35,3 +35,55 @@
35
35
  export declare function clipboardImage(dir: string, run?: (cmd: string, args: readonly string[]) => {
36
36
  status: number | null;
37
37
  }): string | null;
38
+ /**
39
+ * E1 §3 — THE LAST ANSWER, from the record rather than from the screen.
40
+ *
41
+ * The source is the session's PROJECTION (`session.projected()`), which
42
+ * is derived from committed events: a message a retry or an abort voided
43
+ * is already absent from it, and so is anything still streaming. Two
44
+ * things this deliberately is NOT:
45
+ *
46
+ * - the rendered rows, which are folded, washed and cut — a paste of
47
+ * those is a paste of the terminal's layout, not of the answer;
48
+ * - a join of the trailing `text_delta` frames, which are a live
49
+ * projection and not a record.
50
+ *
51
+ * Null when there is no complete answer yet, so the caller can say so
52
+ * rather than copying an empty string over whatever the user had.
53
+ */
54
+ export declare function lastAnswer(messages: readonly import("@vincemakes/kiso-core").Message[]): string | null;
55
+ /** What the clipboard route did, and the one sentence the status row may
56
+ * say about it. The message is the product of this function because the
57
+ * claim and the act must not be able to drift apart. */
58
+ export interface ClipboardWrite {
59
+ readonly ok: boolean;
60
+ readonly message: string;
61
+ }
62
+ interface WriteEnv {
63
+ readonly platform: string;
64
+ readonly isTTY: boolean;
65
+ readonly run: (cmd: string, args: readonly string[], stdin?: string) => {
66
+ status: number | null;
67
+ };
68
+ readonly write: (s: string) => void;
69
+ }
70
+ /**
71
+ * E1 §3 — PUT TEXT ON THE CLIPBOARD, and say only what happened.
72
+ *
73
+ * Two routes, and they differ in what they can KNOW:
74
+ *
75
+ * - **darwin, `pbcopy`**: a real process with a real exit status. When
76
+ * it succeeds we can say `copied`. When it fails we say so.
77
+ * - **everywhere else, OSC 52**: a REQUEST to the terminal. Most
78
+ * terminals do not answer it and Apple Terminal does not implement
79
+ * it at all, so there is no success to report — only that we asked.
80
+ * The row says `asked the terminal to copy N chars`. An earlier
81
+ * draft said `copied N chars (via the terminal)`, which still leads
82
+ * with "copied"; a reader takes the first word.
83
+ *
84
+ * And a route that does not exist: **stdout that is not a TTY emits
85
+ * nothing**. An OSC written into a pipe is escape bytes in someone's
86
+ * data.
87
+ */
88
+ export declare function clipboardWrite(text: string, env?: Partial<WriteEnv>): ClipboardWrite;
89
+ export {};
package/dist/clipboard.js CHANGED
@@ -68,3 +68,81 @@ export function clipboardImage(dir, run = defaultRun) {
68
68
  function defaultRun(cmd, args) {
69
69
  return spawnSync(cmd, [...args], { stdio: "ignore", timeout: 5000 });
70
70
  }
71
+ /**
72
+ * E1 §3 — THE LAST ANSWER, from the record rather than from the screen.
73
+ *
74
+ * The source is the session's PROJECTION (`session.projected()`), which
75
+ * is derived from committed events: a message a retry or an abort voided
76
+ * is already absent from it, and so is anything still streaming. Two
77
+ * things this deliberately is NOT:
78
+ *
79
+ * - the rendered rows, which are folded, washed and cut — a paste of
80
+ * those is a paste of the terminal's layout, not of the answer;
81
+ * - a join of the trailing `text_delta` frames, which are a live
82
+ * projection and not a record.
83
+ *
84
+ * Null when there is no complete answer yet, so the caller can say so
85
+ * rather than copying an empty string over whatever the user had.
86
+ */
87
+ export function lastAnswer(messages) {
88
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
89
+ const m = messages[i];
90
+ if (m.role !== "assistant")
91
+ continue;
92
+ // A turn that only called tools said nothing to copy. Its blocks
93
+ // are real, so `blocks.length > 0` is not the test — the test is
94
+ // whether any of them is TEXT.
95
+ const text = m.blocks
96
+ .filter((b) => b.type === "text")
97
+ .map((b) => b.text)
98
+ .join("\n\n");
99
+ if (text !== "")
100
+ return text;
101
+ }
102
+ return null;
103
+ }
104
+ /**
105
+ * E1 §3 — PUT TEXT ON THE CLIPBOARD, and say only what happened.
106
+ *
107
+ * Two routes, and they differ in what they can KNOW:
108
+ *
109
+ * - **darwin, `pbcopy`**: a real process with a real exit status. When
110
+ * it succeeds we can say `copied`. When it fails we say so.
111
+ * - **everywhere else, OSC 52**: a REQUEST to the terminal. Most
112
+ * terminals do not answer it and Apple Terminal does not implement
113
+ * it at all, so there is no success to report — only that we asked.
114
+ * The row says `asked the terminal to copy N chars`. An earlier
115
+ * draft said `copied N chars (via the terminal)`, which still leads
116
+ * with "copied"; a reader takes the first word.
117
+ *
118
+ * And a route that does not exist: **stdout that is not a TTY emits
119
+ * nothing**. An OSC written into a pipe is escape bytes in someone's
120
+ * data.
121
+ */
122
+ export function clipboardWrite(text, env = {}) {
123
+ const platform = env.platform ?? process.platform;
124
+ const isTTY = env.isTTY ?? process.stdout.isTTY === true;
125
+ const run = env.run ?? ((cmd, args, stdin) => spawnSync(cmd, [...args], { input: stdin, stdio: ["pipe", "ignore", "ignore"], timeout: 5000 }));
126
+ const write = env.write ?? ((s) => process.stdout.write(s));
127
+ const n = [...text].length;
128
+ // NO TTY, NO COPY — and this is checked BEFORE the platform, which is
129
+ // the correction the pipe gate forced.
130
+ //
131
+ // The first build let darwin's `pbcopy` run regardless, on the
132
+ // reasoning that pbcopy is a process and not a terminal route so it
133
+ // works with stdout redirected. It does work — and that is the
134
+ // problem: a script running `kiso chat < input.txt` would silently
135
+ // overwrite whatever the human had on their clipboard. A side effect
136
+ // on the user's desktop is not something a pipe should be able to
137
+ // cause by accident.
138
+ if (!isTTY)
139
+ return { ok: false, message: "no terminal to copy to" };
140
+ if (platform === "darwin") {
141
+ const r = run("pbcopy", [], text);
142
+ return r.status === 0
143
+ ? { ok: true, message: `copied ${n} chars` }
144
+ : { ok: false, message: `could not copy — pbcopy exited ${r.status ?? "abnormally"}` };
145
+ }
146
+ write(`\x1b]52;c;${Buffer.from(text, "utf8").toString("base64")}\x07`);
147
+ return { ok: true, message: `asked the terminal to copy ${n} chars` };
148
+ }
package/dist/dispatch.js CHANGED
@@ -7,6 +7,7 @@ import { contextRows, contextUnavailableRows, displayVerb, escapeTerminal, helpR
7
7
  import { newSessionId } from "./session-id.js";
8
8
  import { buildAdapter, resolveContinuationScope, resolveReasoning } from "@vincemakes/kiso-runtime/internal";
9
9
  import { MODES, MODE_NOTE, getMode, setMode } from "./mode.js";
10
+ import { clipboardWrite, lastAnswer } from "./clipboard.js";
10
11
  import { agentModel, body, bodyLog, configModels, dock, readContextLedger, sessionsDir, setAgentModel, setCurrentModelName, setLastBinding } from "./state.js";
11
12
  import { directWriteProfile, profileAvailable } from "./config.js";
12
13
  /** The ONE dispatcher — slash commands, exit, and turns. The recovery
@@ -82,51 +83,70 @@ export function dispatch(line, ctx) {
82
83
  return;
83
84
  }
84
85
  if (trimmed === "\x12expand") {
85
- // W15: the expand key (ctrl+o) — /last aimed at a chosen cell.
86
- // The TARGET is picked at press time: a LIVE tool cell toggles
87
- // IMMEDIATELY in place (the compositor owns those rows and
88
- // redraws them — the approval pause is exactly when the user
89
- // reads a cut diff, and the key must answer then, never after
90
- // the run). A COMMITTED cell can never toggle — history is never
91
- // rewritten (ADR-0046) — so its expanded block and the empty
92
- // answer queue on the chain like /last: the block lands as new
93
- // content after any in-flight turn. The sentinel carries the
94
- // control char so a typed "expand" turn is never intercepted.
95
- const r = body.expandNext();
96
- if (r.kind === "toggled") {
97
- ctx.input.prompt(); // in place — the frame already repainted
98
- return;
99
- }
100
- const land = async () => {
101
- if (r.kind === "appended") {
102
- // DC-51 — ONE CALL, because one call is ONE CELL.
103
- //
104
- // This was a loop, one `bodyLog` per line, and each call
105
- // makes its own raw cell — so D1 put a blank between every
106
- // pair and the expansion came out laced with unpainted
107
- // rows, twice the height of the card it is meant to be.
108
- // The owner saw it as stripes.
109
- //
110
- // It only became wrong when the block became RENDERED ROWS
111
- // (0.24.2 ③). Before that the lines were a header and two
112
- // multi-line payloads — four cells, and the payloads' own
113
- // newlines kept their rows inside one cell each.
114
- //
115
- // `bodyLog` splits on newlines, and a rendered row may not
116
- // contain one (invariant ①b throws on it), so joining is
117
- // exactly reversible here.
118
- bodyLog(r.lines.join("\n"));
86
+ // DC-50 / R14 — ONE SWITCH, AND THE SESSION IS PRINTED AGAIN.
87
+ //
88
+ // What stood here: the key picked a TARGET at press time. A live
89
+ // tool cell toggled in place; a committed one could not, because
90
+ // ADR-0046 §3 forbade rewriting history — so its body was
91
+ // APPENDED as new content, chained behind any in-flight turn,
92
+ // with a ring choosing which card was next, `#opened` keeping the
93
+ // ring from repeating, `#lastAppend` stopping a held key printing
94
+ // the same rows three times (DC-35), and a two-branch "nothing to
95
+ // expand" message for the two ways it could decline.
96
+ //
97
+ // Amendment 1 removes the premise the whole apparatus served: the
98
+ // terminal's scrollback is ours to erase, so a committed card can
99
+ // be re-rendered where it stands. The key flips one boolean and
100
+ // the session is reprinted; every settled card obeys it, running
101
+ // cards keep their own window (E2/DC-43).
102
+ //
103
+ // It no longer chains. There is no content to land after an
104
+ // in-flight turn — nothing is appended — so the repaint is
105
+ // immediate, exactly as the live-cell toggle always was.
106
+ body.toggleExpanded();
107
+ ctx.input.prompt();
108
+ return;
109
+ }
110
+ if (trimmed === "\x18copy" || trimmed === "/copy") {
111
+ // E1 §3 — the last answer onto the clipboard, as RAW MARKDOWN.
112
+ //
113
+ // The source is the session's PROJECTION, which is derived from
114
+ // committed events: a message a retry or an abort voided is already
115
+ // absent from it, and anything still streaming has not landed in it
116
+ // yet. Deliberately NOT the rendered rows (folded, washed, cut —
117
+ // that is the terminal's layout, not the answer) and NOT a join of
118
+ // the trailing text_delta frames (a live projection, not a record).
119
+ //
120
+ // One implementation for the key and the command: they are the same
121
+ // action reached two ways, and two implementations would be two
122
+ // behaviours waiting to drift.
123
+ // IT CHAINS, like `/last` and unlike `ctrl+o`. The distinction is
124
+ // what the action DEPENDS ON: ctrl+o repaints what is already on
125
+ // screen and can answer at once, while a copy reads the last
126
+ // ANSWER — which may be the turn currently in flight.
127
+ //
128
+ // Found by the pipe gate: a pipe hands the CLI every line at once,
129
+ // so an unchained `/copy` typed after `go` ran BEFORE the turn and
130
+ // said "nothing to copy — no answer yet" above the answer it was
131
+ // asked to copy.
132
+ ctx.chainRef.current = ctx.chainRef.current.then(async () => {
133
+ const answer = lastAnswer(ctx.session.projected());
134
+ if (answer === null) {
135
+ bodyLog("[nothing to copy — no answer yet]");
119
136
  }
120
- else if (r.kind === "none") {
121
- // DC-35: "nothing to expand" was said in two situations and
122
- // was only true in one of them. A reader who pressed the key
123
- // on a ring of one had something to open — they were already
124
- // looking at it — and the old line told them the opposite.
125
- bodyLog(r.why === "already-last" ? "[that expansion is already the last thing on screen — ctrl+r opens the transcript]" : "[nothing to expand]");
137
+ else {
138
+ const r = clipboardWrite(answer);
139
+ // The MESSAGE comes from clipboardWrite, never from here —
140
+ // the claim and the act must not be able to drift apart. On
141
+ // the dock it rides the W18 hint slot (gone at the next
142
+ // keypress); in a pipe it is a line like any other.
143
+ if (dock.active)
144
+ dock.setStatus(r.message);
145
+ else
146
+ bodyLog(`[${r.message}]`);
126
147
  }
127
148
  ctx.input.prompt();
128
- };
129
- ctx.chainRef.current = ctx.chainRef.current.then(land);
149
+ });
130
150
  return;
131
151
  }
132
152
  if (trimmed === "/rewrap") {
package/dist/index.js CHANGED
@@ -84,6 +84,10 @@ function readlineInput(rl) {
84
84
  /* readline has no ctrl+o binding — ignored (W15 rides the
85
85
  * editor path only). */
86
86
  },
87
+ onCopy() {
88
+ /* readline has no ctrl+x binding — ignored. `/copy` still
89
+ * works here: it is a typed command, not a key. */
90
+ },
87
91
  question(query, cb) {
88
92
  rl.question(query, cb);
89
93
  },
@@ -164,6 +168,9 @@ function editorInput(editor) {
164
168
  onExpand(cb) {
165
169
  editor.onExpand(cb);
166
170
  },
171
+ onCopy(cb) {
172
+ editor.onCopy(cb);
173
+ },
167
174
  // KC2 §2: the redirect gesture — the editor decides WHEN (the
168
175
  // same-chunk pair, the precedence gate); chat decides what it MEANS.
169
176
  onRedirect(cb) {
@@ -402,10 +409,26 @@ function extensionsBanner(resume = []) {
402
409
  // filesystem calls it.
403
410
  const home = homedir();
404
411
  const cwd = process.cwd();
412
+ /** DC-49: realpath, falling back to the raw path when it cannot be
413
+ * resolved — an unresolvable path is not a match, and throwing here
414
+ * would take the banner down over a cosmetic row. */
415
+ const realOf = (dir) => {
416
+ try {
417
+ return realpathSync(dir);
418
+ }
419
+ catch {
420
+ return dir;
421
+ }
422
+ };
405
423
  body.banner(VERSION, text.replace(/^ · /, ""), resume, {
406
424
  model: agentModel,
407
425
  mode: getMode() === "plan" ? "plan (read-only)" : getMode(),
408
426
  cwd: cwd.startsWith(home) ? `~${cwd.slice(home.length)}` : cwd,
427
+ // DC-49 — REALPATH on both sides. A symlinked HOME (or a symlinked
428
+ // cwd) compares unequal as raw strings while being the same
429
+ // directory, and the row would then be absent exactly where it is
430
+ // most needed.
431
+ homeWorkspace: realOf(cwd) === realOf(home),
409
432
  });
410
433
  }
411
434
  /**
@@ -578,7 +601,7 @@ async function makeAgent(sessionId, input, modelFlag) {
578
601
  store,
579
602
  // Area 5: the coding tools are bound to the workspace — every path
580
603
  // they touch is canonicalized inside cwd, escapes are refused.
581
- tools: [...createCodingTools({ workspaceRoot: process.cwd() })],
604
+ tools: [...createCodingTools({ workspaceRoot: process.cwd(), excludeRoots: [kisoHome()] })], // DC-49
582
605
  // Modes: the five tiers ride the E1 policy chain (mode:<tier>
583
606
  // extensions, current tier first) — the old static PERMISSION_POLICY
584
607
  // is gone, its semantics live in the "default" tier. The banner
package/dist/state.d.ts CHANGED
@@ -55,6 +55,10 @@ export declare function readContextLedger(sessionId: string, window: number): im
55
55
  * the fuzzy filter sees one alphabet whichever branch ran.
56
56
  */
57
57
  export declare function atFiles(): readonly AtItem[];
58
+ /** DC-49 — the walk, addressable for the gate. `atFiles` is driven by
59
+ * `process.cwd()` and `kisoHome()`, neither of which a unit case can
60
+ * set honestly; this is the same function with both supplied. */
61
+ export declare function atWalkFor(root: string, excludeRoots: readonly string[]): readonly string[];
58
62
  /**
59
63
  * v2c — the interactive input source. TTYs use the raw-mode Editor (the
60
64
  * self-drawn input row — width-aware, the CJK-drift root cause retired,
@@ -70,6 +74,10 @@ export interface LineInput {
70
74
  /** W15: the expand key (ctrl+o) — the chain-level action, never the
71
75
  * editor's own interpretation. */
72
76
  onExpand(cb: () => void): void;
77
+ /** E1 §3: the copy key (ctrl+x) — the chain-level action, never the
78
+ * composer's own. The readline input ignores it; `/copy` is the
79
+ * route that works everywhere. */
80
+ onCopy(cb: () => void): void;
73
81
  /** KC2 §2: the redirect gesture (Alt+Enter / Ctrl+Enter) — the
74
82
  * buffer's text arrives as a line at the same instant the run is told
75
83
  * to stop. OPTIONAL: the pipe path has no raw keys and never wires
package/dist/state.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * the moved modules read and mutate at call time.
7
7
  */
8
8
  import { execFileSync } from "node:child_process";
9
- import { readdirSync, readFileSync } from "node:fs";
9
+ import { readdirSync, readFileSync, realpathSync } from "node:fs";
10
10
  import { homedir } from "node:os";
11
11
  import { dirname, join } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
@@ -119,7 +119,8 @@ export function atFiles() {
119
119
  paths = execFileSync("git", ["ls-files", "-c", "-o", "--exclude-standard"], { cwd: process.cwd(), encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], maxBuffer: 1 << 24 }).split("\n").filter((p) => p !== "");
120
120
  }
121
121
  catch {
122
- paths = atWalk(process.cwd(), "", []);
122
+ // DC-49: the fallback walk excludes kiso's own state directory.
123
+ paths = atWalk(process.cwd(), "", [], [kisoHome()]);
123
124
  }
124
125
  return paths.slice(0, AT_CAP + 1).map((path) => ({ path }));
125
126
  }
@@ -128,15 +129,26 @@ export function atFiles() {
128
129
  * directory contributes nothing and ends nothing — the recursion's own
129
130
  * try catches it at that level, so one locked subtree cannot stop a
130
131
  * file picker from opening. */
131
- function atWalk(dir, prefix, out) {
132
+ function atWalk(dir, prefix, out, excluded = []) {
132
133
  try {
133
134
  for (const e of readdirSync(dir, { withFileTypes: true })) {
134
135
  if (out.length > AT_CAP)
135
136
  break;
136
137
  if (AT_SKIP.has(e.name))
137
138
  continue;
138
- if (e.isDirectory())
139
- atWalk(`${dir}/${e.name}`, `${prefix}${e.name}/`, out);
139
+ const full = `${dir}/${e.name}`;
140
+ if (e.isDirectory()) {
141
+ // DC-49 — the picker does not DESCEND into an excluded root.
142
+ //
143
+ // `AT_SKIP` is a list of NAMES with no dot rule, so unlike
144
+ // tools-node's walk this one has never skipped `~/.kiso` at
145
+ // all: with the workspace at `~` and no git repo there, `@`
146
+ // lists the user's own session logs among their files. That
147
+ // is current behaviour, not a future risk.
148
+ if (excluded.length > 0 && isUnder(full, excluded))
149
+ continue;
150
+ atWalk(full, `${prefix}${e.name}/`, out, excluded);
151
+ }
140
152
  else if (e.isFile())
141
153
  out.push(`${prefix}${e.name}`);
142
154
  }
@@ -146,6 +158,34 @@ function atWalk(dir, prefix, out) {
146
158
  }
147
159
  return out;
148
160
  }
161
+ /** DC-49 — realpath on BOTH sides. darwin's `/tmp` is a symlink to
162
+ * `/private/tmp`, so a raw string compare is a check that passes on one
163
+ * machine and not another. */
164
+ function isUnder(dir, roots) {
165
+ let real;
166
+ try {
167
+ real = realpathSync(dir);
168
+ }
169
+ catch {
170
+ real = dir;
171
+ }
172
+ return roots.some((r) => {
173
+ let rr;
174
+ try {
175
+ rr = realpathSync(r);
176
+ }
177
+ catch {
178
+ rr = r;
179
+ }
180
+ return real === rr || real.startsWith(`${rr}/`);
181
+ });
182
+ }
183
+ /** DC-49 — the walk, addressable for the gate. `atFiles` is driven by
184
+ * `process.cwd()` and `kisoHome()`, neither of which a unit case can
185
+ * set honestly; this is the same function with both supplied. */
186
+ export function atWalkFor(root, excludeRoots) {
187
+ return atWalk(root, "", [], excludeRoots);
188
+ }
149
189
  /** v2b: the bottom-anchored UI — docked only on a color TTY; pipes and
150
190
  * NO_COLOR stay the v2a line mode byte-for-byte. Created at load, like
151
191
  * the pre-split module-scope const. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-code",
3
- "version": "0.24.5",
3
+ "version": "0.26.0",
4
4
  "description": "kiso CLI — 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.24.5",
22
- "@vincemakes/kiso-core": "0.24.5",
23
- "@vincemakes/kiso-evals": "0.24.5",
24
- "@vincemakes/kiso-mcp-ext": "0.24.5",
25
- "@vincemakes/kiso-provider-anthropic": "0.24.5",
26
- "@vincemakes/kiso-provider-openai": "0.24.5",
27
- "@vincemakes/kiso-runtime": "0.24.5",
28
- "@vincemakes/kiso-skills-ext": "0.24.5",
29
- "@vincemakes/kiso-subagent-ext": "0.24.5",
30
- "@vincemakes/kiso-task-ext": "0.24.5",
31
- "@vincemakes/kiso-tools-node": "0.24.5",
32
- "@vincemakes/kiso-tui": "0.24.5",
33
- "@vincemakes/kiso-tui-cells": "0.24.5"
21
+ "@vincemakes/kiso-ask-ext": "0.26.0",
22
+ "@vincemakes/kiso-core": "0.26.0",
23
+ "@vincemakes/kiso-evals": "0.26.0",
24
+ "@vincemakes/kiso-mcp-ext": "0.26.0",
25
+ "@vincemakes/kiso-provider-anthropic": "0.26.0",
26
+ "@vincemakes/kiso-provider-openai": "0.26.0",
27
+ "@vincemakes/kiso-runtime": "0.26.0",
28
+ "@vincemakes/kiso-skills-ext": "0.26.0",
29
+ "@vincemakes/kiso-subagent-ext": "0.26.0",
30
+ "@vincemakes/kiso-task-ext": "0.26.0",
31
+ "@vincemakes/kiso-tools-node": "0.26.0",
32
+ "@vincemakes/kiso-tui": "0.26.0",
33
+ "@vincemakes/kiso-tui-cells": "0.26.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^26.1.2",