@vincemakes/kiso-code 0.46.1 → 0.47.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/auth/oauth/chatgpt.js +25 -8
- package/dist/auth/oauth/page.d.ts +25 -0
- package/dist/auth/oauth/page.js +44 -0
- package/dist/chat.d.ts +12 -1
- package/dist/chat.js +133 -46
- package/dist/coding-prompt.d.ts +7 -0
- package/dist/coding-prompt.js +12 -4
- package/dist/config.d.ts +7 -3
- package/dist/config.js +39 -4
- package/dist/create-coding-agent.js +16 -6
- package/dist/dispatch.d.ts +2 -0
- package/dist/dispatch.js +337 -83
- package/dist/floor.d.ts +21 -0
- package/dist/floor.js +35 -35
- package/dist/git-branch.d.ts +17 -0
- package/dist/git-branch.js +69 -0
- package/dist/in-run-compaction.d.ts +54 -0
- package/dist/in-run-compaction.js +44 -0
- package/dist/index.js +106 -45
- package/dist/opening.d.ts +48 -0
- package/dist/opening.js +43 -0
- package/dist/protected-writes.d.ts +8 -0
- package/dist/protected-writes.js +12 -0
- package/dist/remote-boundary.d.ts +55 -0
- package/dist/remote-boundary.js +217 -0
- package/dist/replay.d.ts +8 -1
- package/dist/replay.js +22 -10
- package/dist/resume.js +21 -16
- package/dist/session-cards.d.ts +2 -0
- package/dist/session-cards.js +3 -3
- package/dist/settings.d.ts +19 -0
- package/dist/settings.js +34 -5
- package/dist/skill-invoke.d.ts +11 -0
- package/dist/skill-invoke.js +59 -0
- package/dist/state.d.ts +14 -3
- package/dist/state.js +29 -3
- package/dist/task-notice.d.ts +48 -7
- package/dist/task-notice.js +123 -13
- package/dist/trust-ui.d.ts +7 -0
- package/dist/trust-ui.js +54 -9
- package/dist/window-title.d.ts +43 -23
- package/dist/window-title.js +78 -50
- package/package.json +14 -14
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { createServer } from "node:http";
|
|
20
20
|
import { RefreshRejectedError } from "./index.js";
|
|
21
|
+
import { callbackPage } from "./page.js";
|
|
21
22
|
import { generatePkce, randomState } from "./pkce.js";
|
|
22
23
|
export const CHATGPT = {
|
|
23
24
|
providerId: "chatgpt",
|
|
@@ -122,30 +123,46 @@ export function credentialFromTokens(tokens) {
|
|
|
122
123
|
throw new Error("ChatGPT sign-in: the access token carries no chatgpt_account_id claim — signed in to something the backend will not serve");
|
|
123
124
|
return { type: "oauth", access: tokens.access, refresh: tokens.refresh, expires: tokens.expires, accountId, savedAt: Date.now() };
|
|
124
125
|
}
|
|
125
|
-
const PAGE = (title, body) => `<!doctype html><meta charset="utf-8"><title>${title}</title><body style="font:16px system-ui;padding:2rem"><h1>${title}</h1><p>${body}</p></body>`;
|
|
126
126
|
/** The local callback: one code for one state, or null when the port cannot
|
|
127
127
|
* be bound (the paste path takes over). */
|
|
128
128
|
export function startCallbackServer(state, host, port) {
|
|
129
129
|
return new Promise((resolve) => {
|
|
130
130
|
let settle;
|
|
131
|
-
|
|
131
|
+
let fail;
|
|
132
|
+
const waited = new Promise((r, j) => {
|
|
132
133
|
settle = r;
|
|
134
|
+
fail = j;
|
|
133
135
|
});
|
|
136
|
+
// a failure that lands after the paste path won the race has no one
|
|
137
|
+
// awaiting it — it must not become an unhandled rejection
|
|
138
|
+
waited.catch(() => { });
|
|
134
139
|
const server = createServer((req, res) => {
|
|
135
140
|
const url = new URL(req.url ?? "", `http://${host}`);
|
|
136
|
-
|
|
141
|
+
// Graphite R3c: the page looks like kiso.work (page.ts)
|
|
142
|
+
const reply = (status, page) => {
|
|
137
143
|
res.statusCode = status;
|
|
138
144
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
139
|
-
res.end(
|
|
145
|
+
res.end(callbackPage(page));
|
|
140
146
|
};
|
|
141
147
|
if (url.pathname !== "/auth/callback")
|
|
142
|
-
return reply(404, "
|
|
148
|
+
return reply(404, { kind: "note", state: "not found", title: "Nothing here.", body: "This is kiso's sign-in callback. Return to your terminal." });
|
|
143
149
|
if (url.searchParams.get("state") !== state)
|
|
144
|
-
return reply(400, "
|
|
150
|
+
return reply(400, { kind: "fail", state: "state mismatch", title: "Start again.", body: "This sign-in did not start from the kiso that is waiting. Run kiso login chatgpt again in your terminal." });
|
|
151
|
+
// R3c: a sign-in the person declined (or the server refused) comes
|
|
152
|
+
// back as `error` and `error_description` — said as itself, here
|
|
153
|
+
// and in the terminal, where it used to read "Missing code"
|
|
154
|
+
const error = url.searchParams.get("error");
|
|
155
|
+
if (error !== null) {
|
|
156
|
+
const why = url.searchParams.get("error_description");
|
|
157
|
+
const reason = why === null || why === "" ? error : `${error}: ${why}`;
|
|
158
|
+
reply(400, { kind: "fail", state: "sign-in failed", title: "Sign-in failed.", body: "Return to your terminal and run kiso login chatgpt again.", detail: reason });
|
|
159
|
+
fail?.(new Error(`ChatGPT sign-in failed — ${reason}`));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
145
162
|
const code = url.searchParams.get("code");
|
|
146
163
|
if (!code)
|
|
147
|
-
return reply(400, "
|
|
148
|
-
reply(200, "
|
|
164
|
+
return reply(400, { kind: "fail", state: "sign-in failed", title: "No code came back.", body: "The authorization server sent no code. Return to your terminal and run kiso login chatgpt again." });
|
|
165
|
+
reply(200, { kind: "ok", state: "signed in", title: "You're signed in.", body: "kiso has your ChatGPT sign-in. Close this tab and return to your terminal." });
|
|
149
166
|
settle?.(code);
|
|
150
167
|
});
|
|
151
168
|
server.on("error", () => resolve(null));
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graphite R3c — the page a browser shows when a sign-in comes back to
|
|
3
|
+
* kiso's local callback. It is the one kiso surface outside the terminal,
|
|
4
|
+
* so it looks like kiso.work: the quatrefoil mark and the `kiso` wordmark,
|
|
5
|
+
* the site's warm ground and ink (a dark twin for `prefers-color-scheme:
|
|
6
|
+
* dark`), a small mono status line with its dot, one large headline, one
|
|
7
|
+
* sentence, and the reason in a mono box when there is one. Static — no
|
|
8
|
+
* script, no request, no web font (the system's own faces stand in for
|
|
9
|
+
* the site's) — and every word from outside (the server's
|
|
10
|
+
* `error_description`) is escaped before it reaches the page.
|
|
11
|
+
*/
|
|
12
|
+
export declare const escapeHtml: (s: string) => string;
|
|
13
|
+
export interface CallbackPage {
|
|
14
|
+
/** the dot's colour, and the status line's word */
|
|
15
|
+
readonly kind: "ok" | "fail" | "note";
|
|
16
|
+
/** the headline — its trailing period is drawn muted, like the site's */
|
|
17
|
+
readonly title: string;
|
|
18
|
+
/** one sentence under it */
|
|
19
|
+
readonly body: string;
|
|
20
|
+
/** a reason from outside (the server's), shown verbatim in a mono box */
|
|
21
|
+
readonly detail?: string;
|
|
22
|
+
/** the status line's words (`signed in`, `sign-in failed`) */
|
|
23
|
+
readonly state: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function callbackPage(page: CallbackPage): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graphite R3c — the page a browser shows when a sign-in comes back to
|
|
3
|
+
* kiso's local callback. It is the one kiso surface outside the terminal,
|
|
4
|
+
* so it looks like kiso.work: the quatrefoil mark and the `kiso` wordmark,
|
|
5
|
+
* the site's warm ground and ink (a dark twin for `prefers-color-scheme:
|
|
6
|
+
* dark`), a small mono status line with its dot, one large headline, one
|
|
7
|
+
* sentence, and the reason in a mono box when there is one. Static — no
|
|
8
|
+
* script, no request, no web font (the system's own faces stand in for
|
|
9
|
+
* the site's) — and every word from outside (the server's
|
|
10
|
+
* `error_description`) is escaped before it reaches the page.
|
|
11
|
+
*/
|
|
12
|
+
const ESC = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
13
|
+
export const escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => ESC[c]);
|
|
14
|
+
/** kiso's quatrefoil — the mark in `assets/logo.svg`, shared with kiso.work. */
|
|
15
|
+
const MARK = '<svg class="mark" viewBox="0 0 100 100" aria-hidden="true"><path fill="currentColor" d="m89 54c-0.5-1.3-0.8-2.7-0.8-3.9 0.2-5.5 7-11.1 9.5-19.4 2.2-7.3 2.2-17.3-5.2-23.8-2.7-2.4-7-4.7-12.7-4.7-2.6 0-5.3 0.4-7.8 1.2-4.7 1.5-7.8 3.5-14.5 7.8-2.6 1.5-4.6 2.6-7.5 2.6s-5-1.1-7.5-2.6c-6.7-4.3-11.1-7.2-15.8-8.3-1.6-0.4-3.5-0.7-6.1-0.7-5.7 0-10 2-12.9 4.7-7.3 6.7-7.4 16-5.2 22.9 2.6 7.9 8.4 13.1 8.9 18.8 0.2 1.6 0 3.5-0.9 5.4-2.8 5.7-8.4 10.9-8.9 21.5-0.2 4.2 0.3 8.9 2.8 13.1 2.1 3.3 6.5 8.3 16 8.5 12.5 0.2 18.8-6.9 25.8-9.6 1.3-0.5 2.5-0.7 3.8-0.7 6.7 0 11.4 6.9 21.9 9.8 2.2 0.7 4.7 1.1 7.5 1.2 4.2 0 7.5-0.8 10.2-2.3 4-2.1 6.5-5.8 7.7-8.8 1.1-2.9 1.6-5.9 1.6-8.8-0.2-10.6-7.3-17.4-9.9-23.9zm-71.2-3.6c6.4-0.4 11.6-1 17.4-4.2 3.3-1.8 6.4-4.5 8.6-7.3 3.1-4.2 5.6-9.5 6.1-17.1l0.1-2.2 0.2 2.2c1.2 13.1 9.3 20.8 16.2 24.3 4.5 2.4 8.7 3.6 15.8 4.1v0.2c-5.3 0.2-9.3 1-13.2 2.6-4.8 1.9-9.4 5.1-12.6 9.2-3.5 4.7-5.8 10-6.3 17.5l-0.1 1.4-0.2-2c-0.4-5.7-2.3-11-5.4-15.3-2.3-3.3-5.3-6.1-8.9-8.2-3.4-2.1-8-4.2-15.9-5.1l-1.8-0.1z"/></svg>';
|
|
16
|
+
const STYLE = [
|
|
17
|
+
":root{--bg:#f7f7f4;--ink:#20211f;--muted:#666761;--line:#d9dad4;--ok:#2f7a3a;--fail:#b3261e;--sans:ui-sans-serif,-apple-system,'Helvetica Neue',Arial,sans-serif;--mono:ui-monospace,'SF Mono',Menlo,monospace;--pad:clamp(20px,5vw,72px)}",
|
|
18
|
+
"@media (prefers-color-scheme:dark){:root{--bg:#151513;--ink:#ecece6;--muted:#9a9b93;--line:#33342f;--ok:#8fd19e;--fail:#f2877a}}",
|
|
19
|
+
"*{box-sizing:border-box}html,body{margin:0;min-height:100%}",
|
|
20
|
+
"body{min-height:100vh;display:flex;flex-direction:column;background:var(--bg);color:var(--ink);font:16px/1.6 var(--sans);-webkit-font-smoothing:antialiased}",
|
|
21
|
+
"header,footer{display:flex;justify-content:space-between;align-items:center;gap:16px;padding:24px var(--pad)}",
|
|
22
|
+
".logo{display:flex;align-items:center;gap:10px;font-size:26px;font-weight:600;letter-spacing:-1.3px}.mark{width:26px;height:26px}",
|
|
23
|
+
".mono{font-family:var(--mono);font-size:11px;letter-spacing:.07em;text-transform:uppercase;color:var(--muted)}",
|
|
24
|
+
"main{flex:1;display:flex;flex-direction:column;justify-content:center;padding:32px var(--pad) 56px;max-width:880px}",
|
|
25
|
+
".state{display:flex;align-items:center;gap:10px}.dot{width:7px;height:7px;border-radius:50%;background:var(--tone)}",
|
|
26
|
+
"h1{margin:22px 0 0;font-size:clamp(40px,7vw,76px);line-height:1.03;font-weight:500;letter-spacing:-.06em}.period{color:var(--muted)}",
|
|
27
|
+
"p.lead{margin:22px 0 0;color:var(--muted);font-size:17px;max-width:34rem}",
|
|
28
|
+
"pre{margin:26px 0 0;padding:14px 16px;border:1px solid var(--line);font:13px/1.55 var(--mono);white-space:pre-wrap;overflow-wrap:anywhere;max-width:40rem}",
|
|
29
|
+
"code{font:14px var(--mono);color:var(--ink)}",
|
|
30
|
+
"footer{border-top:1px solid var(--line)}",
|
|
31
|
+
].join("");
|
|
32
|
+
export function callbackPage(page) {
|
|
33
|
+
const tone = page.kind === "ok" ? "var(--ok)" : page.kind === "fail" ? "var(--fail)" : "var(--muted)";
|
|
34
|
+
const title = escapeHtml(page.title.replace(/\.$/, ""));
|
|
35
|
+
return [
|
|
36
|
+
`<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">`,
|
|
37
|
+
`<title>${escapeHtml(page.title.replace(/\.$/, ""))} · kiso</title><style>${STYLE}</style>`,
|
|
38
|
+
`<header><div class="logo">${MARK}kiso</div><span class="mono">ChatGPT sign-in</span></header>`,
|
|
39
|
+
`<main style="--tone:${tone}"><div class="state mono"><span class="dot"></span>${escapeHtml(page.state)}</div>`,
|
|
40
|
+
`<h1>${title}<span class="period">.</span></h1><p class="lead">${escapeHtml(page.body)}</p>`,
|
|
41
|
+
page.detail === undefined ? "" : `<pre>${escapeHtml(page.detail)}</pre>`,
|
|
42
|
+
`</main><footer><span class="mono">A small core. A long memory.</span><span class="mono">kiso.work</span></footer></html>`,
|
|
43
|
+
].join("");
|
|
44
|
+
}
|
package/dist/chat.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* estimates. All bodies moved verbatim from index.ts.
|
|
6
6
|
*/
|
|
7
7
|
import type { SessionRoute } from "./projects.js";
|
|
8
|
-
import { type RunUsage } from "@vincemakes/kiso-tui";
|
|
8
|
+
import { type BarInput, type RunUsage, type TaskCountsOnRow } from "@vincemakes/kiso-tui";
|
|
9
9
|
import { type SaferFailure, type SaferOption } from "@vincemakes/kiso-tui";
|
|
10
10
|
import type { AgentSession, Run } from "@vincemakes/kiso-runtime";
|
|
11
11
|
import { type LineInput } from "./state.js";
|
|
@@ -150,6 +150,17 @@ export declare function compactionDiscardedNotice(d: {
|
|
|
150
150
|
readonly summary: number;
|
|
151
151
|
}): string;
|
|
152
152
|
export declare function displayCtxRatio(session: AgentSession): number;
|
|
153
|
+
/**
|
|
154
|
+
* Graphite §8.9 — the status bar's facts, one builder for every caller
|
|
155
|
+
* (the boot row, the idle row, the running turn's row), so they cannot
|
|
156
|
+
* drift apart. The context share is USED, with the runtime's own
|
|
157
|
+
* compaction tiers as shares of the same window (`tiersFor`, never a
|
|
158
|
+
* fixed fraction); an unknown window is `ctx ?`.
|
|
159
|
+
*/
|
|
160
|
+
export declare function barFor(session: AgentSession, measured?: {
|
|
161
|
+
readonly tokPerSec: number | null;
|
|
162
|
+
readonly tasks?: TaskCountsOnRow | undefined;
|
|
163
|
+
}): BarInput;
|
|
153
164
|
/** 0.40.0 item 9: how long a prompt cache is assumed to live. PROVISIONAL:
|
|
154
165
|
* DeepSeek does not publish its TTL (the owner's cache was gone after 27
|
|
155
166
|
* minutes); Anthropic's default is 5 minutes. */
|
package/dist/chat.js
CHANGED
|
@@ -5,16 +5,19 @@
|
|
|
5
5
|
* estimates. All bodies moved verbatim from index.ts.
|
|
6
6
|
*/
|
|
7
7
|
import { readFileSync, statSync } from "node:fs";
|
|
8
|
-
import { escapeTerminal, cacheHitPct, decodeRate,
|
|
9
|
-
import { askView, coldResumeLine, coldResumeView, deletionRiskHint,
|
|
8
|
+
import { escapeTerminal, cacheHitPct, decodeRate, palette, renderEvent, renderRecap, sealTiers, toolTarget, STATUS_GLYPHS, kUnit, workingRow, compactingStatus, liveRow, namedPickView, } from "@vincemakes/kiso-tui";
|
|
9
|
+
import { askView, coldResumeLine, coldResumeView, deletionRiskHint, editFileHunksDiff, hunksOf, writeFileDiff } from "@vincemakes/kiso-tui";
|
|
10
10
|
import { canonicalTargetPath, isProtectedPath, protectedIdentity, shellProgressPath } from "@vincemakes/kiso-tools-node";
|
|
11
|
-
import { liveTasks, mergedConfig, queuedSwitchLines,
|
|
12
|
-
import { taskCounts,
|
|
11
|
+
import { deliveryLines, liveTasks, lostToldOf, mergedConfig, queuedSwitchLines, setExitTasks, tasksFor, taskWhatOf } from "./state.js";
|
|
12
|
+
import { taskCounts, taskNoticeLines, taskNoticeRows, tasksForDisplay } from "./task-notice.js";
|
|
13
|
+
import { current as inRunCompaction, takeKept as takeKeptCompaction } from "./in-run-compaction.js";
|
|
13
14
|
import { echoText } from "@vincemakes/kiso-tui-cells/render";
|
|
14
15
|
import { canonicalizeUsage, RunClosedError } from "@vincemakes/kiso-runtime";
|
|
15
|
-
import { canonicalizeUsageForModel, requestBudget } from "@vincemakes/kiso-runtime/internal";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { canonicalizeUsageForModel, requestBudget, tiersFor } from "@vincemakes/kiso-runtime/internal";
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
import { currentBranch } from "./git-branch.js";
|
|
19
|
+
import { dispatch, markAutoCompact, markCompactWhy, abortBangCommand } from "./dispatch.js";
|
|
20
|
+
import { setTitleState } from "./window-title.js";
|
|
18
21
|
import { agentBaseUrl, agentModel, body, bodyLog, configuredWindow, dock, retryOnRow, retryShown, setRetryShown, floorOn, protectedFiles, upstreamOf, VERSION } from "./state.js";
|
|
19
22
|
import { attachImages } from "./attachments.js";
|
|
20
23
|
import { installedVersion, staleVersionNotice } from "./stale-version.js";
|
|
@@ -266,6 +269,36 @@ export function displayCtxRatio(session) {
|
|
|
266
269
|
return anchored / window;
|
|
267
270
|
return requestBudget(session.requestParts(), window).ratio;
|
|
268
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* Graphite §8.9 — the status bar's facts, one builder for every caller
|
|
274
|
+
* (the boot row, the idle row, the running turn's row), so they cannot
|
|
275
|
+
* drift apart. The context share is USED, with the runtime's own
|
|
276
|
+
* compaction tiers as shares of the same window (`tiersFor`, never a
|
|
277
|
+
* fixed fraction); an unknown window is `ctx ?`.
|
|
278
|
+
*/
|
|
279
|
+
export function barFor(session, measured = { tokPerSec: null }) {
|
|
280
|
+
const window = knownContextWindow();
|
|
281
|
+
const used = displayCtxRatio(session);
|
|
282
|
+
const tiers = window === null ? null : tiersFor(window, 0);
|
|
283
|
+
const home = homedir();
|
|
284
|
+
const cwd = process.cwd();
|
|
285
|
+
return {
|
|
286
|
+
// the main-sync round: the tier by its name (#203's MODE_LABEL), full
|
|
287
|
+
// access in the failure colour, and the don't-ask switch as its own chip
|
|
288
|
+
mode: getMode() === "plan" ? "plan \u00b7 read-only" : MODE_LABEL[getMode()],
|
|
289
|
+
modeAlert: getMode() === "full-access",
|
|
290
|
+
dontAsk: getDontAsk(),
|
|
291
|
+
floorOff: !floorOn,
|
|
292
|
+
model: statusModelLabel(session),
|
|
293
|
+
ctx: tiers === null || window === null || !Number.isFinite(used) ? null : { used, soft: tiers.soft / window, hard: tiers.hard / window },
|
|
294
|
+
tokPerSec: measured.tokPerSec,
|
|
295
|
+
// ADR-0058 (3e): the session's tasks — the bar is where they are
|
|
296
|
+
// counted in Graphite (§8.9); absent, the bar is unchanged
|
|
297
|
+
...(measured.tasks !== undefined ? { tasks: measured.tasks } : {}),
|
|
298
|
+
branch: currentBranch(cwd),
|
|
299
|
+
folder: cwd === home ? "~" : cwd.startsWith(`${home}/`) ? `~${cwd.slice(home.length)}` : cwd,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
269
302
|
/** 0.40.0 item 9: how long a prompt cache is assumed to live. PROVISIONAL:
|
|
270
303
|
* DeepSeek does not publish its TTL (the owner's cache was gone after 27
|
|
271
304
|
* minutes); Anthropic's default is 5 minutes. */
|
|
@@ -505,8 +538,8 @@ export function startStatusSpinner(onTick) {
|
|
|
505
538
|
setRetryShown(null);
|
|
506
539
|
if (!dock.active)
|
|
507
540
|
return () => { };
|
|
508
|
-
// v3 §03/§05: the working glyph family
|
|
509
|
-
// callback repaints the
|
|
541
|
+
// v3 §03/§05: the working glyph family (STATUS_GLYPHS), 200ms
|
|
542
|
+
// rotation — the callback repaints the live row with the new glyph.
|
|
510
543
|
// KC2 §5: the family itself moved to the tui's status formatters.
|
|
511
544
|
let i = 0;
|
|
512
545
|
const timer = setInterval(() => onTick(STATUS_GLYPHS[i++ % STATUS_GLYPHS.length]), 200);
|
|
@@ -578,13 +611,15 @@ function approvalDiff(name, input) {
|
|
|
578
611
|
}
|
|
579
612
|
try {
|
|
580
613
|
if (name === "edit_file") {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
614
|
+
// R2a: the single pair or the batch — a batch's hunks applied in
|
|
615
|
+
// order, as the tool applies them (it reached the panel with no
|
|
616
|
+
// diff while only search/replace was read)
|
|
617
|
+
const hunks = hunksOf(input);
|
|
618
|
+
if (hunks === null || hunks.some((h) => h.search === ""))
|
|
584
619
|
return null;
|
|
585
620
|
// TUI2-R1.5 ② (VD-2): the path rides along so a miss can name the
|
|
586
621
|
// file in its honest note instead of fabricating a diff.
|
|
587
|
-
return
|
|
622
|
+
return editFileHunksDiff(oldContent ?? "", hunks, path);
|
|
588
623
|
}
|
|
589
624
|
const content = typeof input.content === "string" ? input.content : "";
|
|
590
625
|
return writeFileDiff(oldContent, content);
|
|
@@ -919,7 +954,16 @@ onInputLanded) {
|
|
|
919
954
|
if (ev.type === "summarized" || ev.type === "microcompacted") {
|
|
920
955
|
const r = displayCtxRatio(session);
|
|
921
956
|
const ctx = Number.isFinite(r) ? ` · ctx now ~${Math.round(r * 100)}% used` : "";
|
|
922
|
-
|
|
957
|
+
const pipe = ev.type === "summarized" ? `✦ compacted mid-run — the conversation before this point is a summary now${ctx}` : `✦ pruned old tool output mid-run${ctx}`;
|
|
958
|
+
// the compaction round (owner, 2026-10-06): the COMPACTED row says
|
|
959
|
+
// the numbers — how big the context was, how big it is now, the
|
|
960
|
+
// share of the window used — where it said a sentence. A pipe keeps
|
|
961
|
+
// the sentence.
|
|
962
|
+
const kept = ev.type === "summarized" ? takeKeptCompaction() : null;
|
|
963
|
+
if (kept !== null)
|
|
964
|
+
body.metaNotice(pipe, [{ label: "COMPACTED", sentence: `mid-run \u00b7 ~${kUnit(kept.pre)} \u2192 ~${kUnit(kept.post)}${Number.isFinite(r) ? ` \u00b7 ctx now ${Math.round(r * 100)}%` : ""}` }]);
|
|
965
|
+
else
|
|
966
|
+
body.notice(pipe);
|
|
923
967
|
}
|
|
924
968
|
if (ev.type !== "thinking") {
|
|
925
969
|
if (thinkingSince !== null) {
|
|
@@ -938,13 +982,6 @@ onInputLanded) {
|
|
|
938
982
|
inputsSeen += 1;
|
|
939
983
|
if (inputsSeen > 1)
|
|
940
984
|
onInputLanded?.();
|
|
941
|
-
// The window title is re-derived here because THIS is when a
|
|
942
|
-
// session stops being nameless: the tab opened as `kiso —
|
|
943
|
-
// <workspace>` and the first substantive prompt is what gives
|
|
944
|
-
// it a name. Re-derived rather than set, so the opener rule is
|
|
945
|
-
// `sessionTitle`'s and a greeting-first session upgrades when
|
|
946
|
-
// the real prompt lands instead of keeping "hi" forever.
|
|
947
|
-
paintWindowTitle(session.log.all);
|
|
948
985
|
// TV-1B: a system-sourced input is PRODUCT MACHINERY — visible
|
|
949
986
|
// (every durable input renders) but never painted as the
|
|
950
987
|
// user's words. Provenance is honest on screen, not only in
|
|
@@ -953,7 +990,13 @@ onInputLanded) {
|
|
|
953
990
|
// any run that carries one, as replay.ts does for old logs.
|
|
954
991
|
// ADR-0058: a task notice is a row, never the person's chip
|
|
955
992
|
if (ev.via?.kind === "tasks") {
|
|
956
|
-
|
|
993
|
+
// the tasks round: one row per task, how it ended and what ran —
|
|
994
|
+
// a loss already said (Amendment 8) is left out, as the pipe's
|
|
995
|
+
// lines (deliveryLines) leave it out
|
|
996
|
+
const told = lostToldOf(session.id);
|
|
997
|
+
const shown = ev.via.items.filter((i) => !(i.transition === "unknown" && told.has(i.taskId)));
|
|
998
|
+
if (shown.length > 0)
|
|
999
|
+
body.metaNotice(deliveryLines(session.id, ev.via.items).join("\n"), taskNoticeRows(shown, tasksForDisplay(tasksFor(session.id))));
|
|
957
1000
|
break;
|
|
958
1001
|
}
|
|
959
1002
|
if (ev.source === "system") {
|
|
@@ -1196,8 +1239,11 @@ onInputLanded) {
|
|
|
1196
1239
|
const e = ev.outcome.error;
|
|
1197
1240
|
body.notice(`run failed — ${e.code}${e.status !== undefined ? ` ${e.status}` : ""}${e.retryable ? " (retryable)" : ""}: ${escapeTerminal(e.message)}`);
|
|
1198
1241
|
}
|
|
1199
|
-
|
|
1242
|
+
// Graphite §7.11: the seal is a cell on the terminal (drawn at
|
|
1243
|
+
// the current width, no context share); the pipe keeps the recap.
|
|
1244
|
+
const recapStats = {
|
|
1200
1245
|
seconds: Math.round((Date.now() - turnStart) / 1000),
|
|
1246
|
+
stopped: ev.outcome.kind === "aborted",
|
|
1201
1247
|
// R3g: the work terms are NOT passed — the fold line
|
|
1202
1248
|
// says what the turn did, once, where it happened and
|
|
1203
1249
|
// with the key that reopens it. This row is the turn's
|
|
@@ -1219,12 +1265,18 @@ onInputLanded) {
|
|
|
1219
1265
|
// W19: under plan the recap becomes the way-forward row
|
|
1220
1266
|
// (the /mode hints are the mode's exits).
|
|
1221
1267
|
mode: getMode(),
|
|
1222
|
-
}
|
|
1268
|
+
};
|
|
1269
|
+
body.seal(sealTiers(recapStats), renderRecap(recapStats));
|
|
1223
1270
|
break;
|
|
1224
1271
|
}
|
|
1225
1272
|
default: {
|
|
1226
1273
|
// Events without a cell (stop, …) — the generic render, byte-
|
|
1227
1274
|
// preserved for the pipe path (C5: Event → RenderInput first).
|
|
1275
|
+
// the compaction round (owner, 2026-10-06): on a dock the
|
|
1276
|
+
// COMPACTED row above already says it — `[summarized up to seq
|
|
1277
|
+
// N]` was a raw row under it. A pipe keeps the line.
|
|
1278
|
+
if (ev.type === "summarized" && dock.active)
|
|
1279
|
+
break;
|
|
1228
1280
|
const input = toRenderInput(ev);
|
|
1229
1281
|
if (input === null)
|
|
1230
1282
|
break;
|
|
@@ -1334,6 +1386,11 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
|
|
|
1334
1386
|
runGlyph = g;
|
|
1335
1387
|
paintRunning();
|
|
1336
1388
|
});
|
|
1389
|
+
// Graphite §8.7: the live row stands from the run's FIRST frame —
|
|
1390
|
+
// the spinner's first tick is 200ms away, and a row that arrived
|
|
1391
|
+
// then would move the content above it once the turn had begun.
|
|
1392
|
+
paintRunning();
|
|
1393
|
+
setTitleState("working"); // §8.10: the title says so, once, not per tick
|
|
1337
1394
|
(async () => {
|
|
1338
1395
|
let last;
|
|
1339
1396
|
try {
|
|
@@ -1510,7 +1567,7 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
|
|
|
1510
1567
|
// no rate in the pricing table contributes nothing and the row shows no
|
|
1511
1568
|
// $ at all, because a partial total presented as a total is a lie.
|
|
1512
1569
|
let spentUsd = null;
|
|
1513
|
-
let runGlyph =
|
|
1570
|
+
let runGlyph = STATUS_GLYPHS[0];
|
|
1514
1571
|
let runStart = Date.now();
|
|
1515
1572
|
// TPS-1: the decode rate of the last SETTLED call. One variable serves
|
|
1516
1573
|
// both rows because they want the same number at different moments;
|
|
@@ -1536,7 +1593,7 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
|
|
|
1536
1593
|
const manager = tasksFor(session.id);
|
|
1537
1594
|
if (manager === undefined)
|
|
1538
1595
|
return undefined;
|
|
1539
|
-
if (fresh || taskRow === null || Date.now() - taskRow.at > 1_000
|
|
1596
|
+
if (fresh || taskRow === null || Date.now() - taskRow.at > 1_000) {
|
|
1540
1597
|
let list = [];
|
|
1541
1598
|
try {
|
|
1542
1599
|
list = manager.list();
|
|
@@ -1544,7 +1601,7 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
|
|
|
1544
1601
|
catch {
|
|
1545
1602
|
// a corrupt journal is reported where it is read on purpose
|
|
1546
1603
|
}
|
|
1547
|
-
taskRow = { at: Date.now(),
|
|
1604
|
+
taskRow = { at: Date.now(), counts: taskCounts(list) };
|
|
1548
1605
|
}
|
|
1549
1606
|
return taskRow.counts;
|
|
1550
1607
|
};
|
|
@@ -1556,27 +1613,39 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
|
|
|
1556
1613
|
// (DF-0330-F1). A pending retry makes it thirty columns longer, and
|
|
1557
1614
|
// composed blind it would be cut from the END — the context figure,
|
|
1558
1615
|
// a fact, going before the gesture hints.
|
|
1559
|
-
|
|
1560
|
-
|
|
1616
|
+
// Graphite §8.7 / §8.9: the turn's own facts ride the LIVE row above
|
|
1617
|
+
// the composer; the status bar keeps the session's (its ctx moves as
|
|
1618
|
+
// the turn reads).
|
|
1619
|
+
if (!dock.active)
|
|
1620
|
+
return;
|
|
1621
|
+
// the main-sync round (ADR-0058 3e): the live row teaches ctrl+b exactly
|
|
1622
|
+
// while a command can be moved to the background; the bar counts the
|
|
1623
|
+
// session's tasks (§8.9)
|
|
1624
|
+
// the compaction round (owner, 2026-10-06): a summary inside the run is
|
|
1625
|
+
// the row /compact draws — `compacting · auto`, its progress in the
|
|
1626
|
+
// bar's cells — not `working` with a clock and nothing arriving
|
|
1627
|
+
const summary = inRunCompaction();
|
|
1628
|
+
if (summary !== null) {
|
|
1629
|
+
const W = rowWidth();
|
|
1630
|
+
dock.setLive(liveRow(compactingStatus(runGlyph, summary.rounds, summary.tokens, Math.round((Date.now() - summary.since) / 1000), W - 20, retryOnRow(), summary.progress, "auto"), ["esc stops"], W));
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
dock.setLive(workingRow(runGlyph, runStart, runUsage.out, lastTokPerSec, rowWidth(), retryOnRow(), detachableNow()));
|
|
1634
|
+
dock.setBar(barFor(session, { tokPerSec: lastTokPerSec, tasks: taskCountsNow() }));
|
|
1561
1635
|
};
|
|
1562
1636
|
// W19: under plan the idle row makes the posture unmistakable — the W4
|
|
1563
1637
|
// parentheses idiom names the read-only constraint. The tier is the
|
|
1564
1638
|
// CALLER's word (the recovery flow passes the bare mode).
|
|
1565
1639
|
const paintIdle = () => {
|
|
1640
|
+
setTitleState("ready"); // §8.10 — a dock-less TTY has a title too
|
|
1566
1641
|
if (!dock.active)
|
|
1567
1642
|
return;
|
|
1568
|
-
//
|
|
1569
|
-
//
|
|
1570
|
-
//
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
tokPerSec: lastTokPerSec,
|
|
1575
|
-
},
|
|
1576
|
-
// DF-0330-F1: the row's budget. Without it the row is composed
|
|
1577
|
-
// blind and invariant ① cuts whatever sits last — which is how
|
|
1578
|
-
// a measured rate went missing at 100 columns.
|
|
1579
|
-
rowWidth(), !floorOn, taskCountsNow(true)));
|
|
1643
|
+
// Graphite §8.9: nothing is live; the bar carries the session and
|
|
1644
|
+
// its health. TUI2-R1 (E): the meter's fields are omitted while
|
|
1645
|
+
// unmeasured, so a session that has not called the model paints no
|
|
1646
|
+
// rate.
|
|
1647
|
+
dock.setLive(null);
|
|
1648
|
+
dock.setBar(barFor(session, { tokPerSec: lastTokPerSec, tasks: taskCountsNow(true) }));
|
|
1580
1649
|
};
|
|
1581
1650
|
// TUI2-R2 ⑥ — the BOOT status line. The row is the product's one
|
|
1582
1651
|
// persistent claim about itself (the tier, how to change it, the model,
|
|
@@ -1621,7 +1690,7 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
|
|
|
1621
1690
|
spentUsd = (spentUsd ?? 0) + usd;
|
|
1622
1691
|
};
|
|
1623
1692
|
const queueTurn = (line, via) => {
|
|
1624
|
-
const slot = { line: via?.kind === "skill" ? via.line : via?.kind === "tasks" ?
|
|
1693
|
+
const slot = { line: via?.kind === "skill" ? via.line : via?.kind === "tasks" ? (deliveryLines(session.id, via.items).join(" · ") || taskNoticeLines(via.items, taskWhatOf(session.id)).join(" · ")) : line, content: line, ...(via !== undefined ? { via } : {}), cancelled: false };
|
|
1625
1694
|
pendingTurns.push(slot);
|
|
1626
1695
|
queued += 1;
|
|
1627
1696
|
chainRef.current = chainRef.current.then(async () => {
|
|
@@ -1706,6 +1775,15 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
|
|
|
1706
1775
|
: session.useTasks(taskManager, {
|
|
1707
1776
|
wake: mergedConfig.taskWake !== false,
|
|
1708
1777
|
onWake: (w) => queueTurn(w.content, w.via),
|
|
1778
|
+
// Amendment 8: a loss is said when kiso concludes it — the
|
|
1779
|
+
// model's notice still rides the next run, and is not said again
|
|
1780
|
+
onLost: (ids) => {
|
|
1781
|
+
const told = lostToldOf(session.id);
|
|
1782
|
+
for (const id of ids)
|
|
1783
|
+
told.add(id);
|
|
1784
|
+
for (const line of taskNoticeLines(ids.map((taskId) => ({ taskId, transition: "unknown" })), taskWhatOf(session.id)))
|
|
1785
|
+
body.notice(line);
|
|
1786
|
+
},
|
|
1709
1787
|
...(Number.isFinite(autoDetachFromEnv) && autoDetachFromEnv >= 0 ? { autoDetachMinAgeMs: autoDetachFromEnv } : {}),
|
|
1710
1788
|
});
|
|
1711
1789
|
// 3e: a task's change repaints the row it is counted on
|
|
@@ -1814,6 +1892,7 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
|
|
|
1814
1892
|
const ratio = autoCompactRatio(session); // A1a: the policy keeps the pre-A1a number — A1b decides if it moves
|
|
1815
1893
|
if (!Number.isFinite(ratio) || ratio < autoCompact.thresholdRatio)
|
|
1816
1894
|
return;
|
|
1895
|
+
markAutoCompact();
|
|
1817
1896
|
dispatch("/compact", dispatchCtx);
|
|
1818
1897
|
};
|
|
1819
1898
|
// CX-1 F5: a literal input (task-file mode) submits the turn directly —
|
|
@@ -1847,14 +1926,16 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
|
|
|
1847
1926
|
// The switch is the SAME live-extension flip /mode performs; the status
|
|
1848
1927
|
// row repaints at once with a one-line notice.
|
|
1849
1928
|
input.onModeCycle?.(() => {
|
|
1850
|
-
const
|
|
1929
|
+
const was = getMode();
|
|
1930
|
+
const next = OFFERED_MODES[(OFFERED_MODES.indexOf(was) + 1) % OFFERED_MODES.length];
|
|
1851
1931
|
const wasOn = getDontAsk();
|
|
1852
1932
|
setMode(next);
|
|
1853
1933
|
paintIdle();
|
|
1854
|
-
|
|
1934
|
+
// Graphite R3e: the MODE row on a terminal (the pipe keeps these words)
|
|
1935
|
+
body.modeNotice(`mode → ${MODE_LABEL[next]} (shift+tab cycles)`, MODE_LABEL[was], MODE_LABEL[next]);
|
|
1855
1936
|
// the switch came with the old dontAsk tier, and left with it
|
|
1856
1937
|
if (getDontAsk() !== wasOn)
|
|
1857
|
-
body.
|
|
1938
|
+
body.dontAskNotice("don't ask → off", false);
|
|
1858
1939
|
});
|
|
1859
1940
|
// Recovery first: a session with a dangling pause or uncertain
|
|
1860
1941
|
// executions must resolve them BEFORE the REPL accepts new turns —
|
|
@@ -1876,11 +1957,17 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
|
|
|
1876
1957
|
// before the run resumes: the run continues on the compacted projection.
|
|
1877
1958
|
const cold = !cancelled && process.stdin.isTTY ? coldResumeOffer(session) : null;
|
|
1878
1959
|
if (cold !== null) {
|
|
1960
|
+
// Graphite P1b (owner, 2026-09-30): either way the compaction row
|
|
1961
|
+
// says why it runs — `cold cache` — so on a dock dontAsk needs no line
|
|
1962
|
+
// of its own; off a dock the line is printed as it always was
|
|
1879
1963
|
if (getDontAsk()) {
|
|
1880
|
-
|
|
1964
|
+
if (!dock.active)
|
|
1965
|
+
body.notice(`[dontAsk] ${coldResumeLine(cold.tokens, cold.minutes)} — compacting first`);
|
|
1966
|
+
markCompactWhy("cold cache");
|
|
1881
1967
|
dispatch("/compact", dispatchCtx);
|
|
1882
1968
|
}
|
|
1883
1969
|
else if ((await askPanel(input, coldResumeView(cold.tokens, cold.minutes))).action === "allow") {
|
|
1970
|
+
markCompactWhy("cold cache");
|
|
1884
1971
|
dispatch("/compact", dispatchCtx);
|
|
1885
1972
|
}
|
|
1886
1973
|
await chainRef.current;
|
package/dist/coding-prompt.d.ts
CHANGED
|
@@ -24,6 +24,13 @@ export declare const SYSTEM_PROMPT = "You are kiso, a coding agent. You work in
|
|
|
24
24
|
* a protected one is skipped as though absent.
|
|
25
25
|
*/
|
|
26
26
|
export declare function readProjectInstructions(cwd: string, protectedFiles?: readonly string[]): string;
|
|
27
|
+
/** The instruction file the prompt reads, and its text — the ONE lookup,
|
|
28
|
+
* so the opening's RULES fact (Graphite §7.10) names exactly the file the
|
|
29
|
+
* model is given, never a file it is not. */
|
|
30
|
+
export declare function projectInstructions(cwd: string, protectedFiles?: readonly string[]): {
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly text: string;
|
|
33
|
+
} | null;
|
|
27
34
|
/** A area: the session's system prompt — the constant plus any project
|
|
28
35
|
* instructions found in the workspace. Deterministic per cwd. */
|
|
29
36
|
export declare function composeSystemPrompt(cwd: string, protectedFiles?: readonly string[]): string;
|
package/dist/coding-prompt.js
CHANGED
|
@@ -64,20 +64,28 @@ const INSTRUCTION_FILES = ["AGENTS.md", "CLAUDE.md"];
|
|
|
64
64
|
* a protected one is skipped as though absent.
|
|
65
65
|
*/
|
|
66
66
|
export function readProjectInstructions(cwd, protectedFiles = []) {
|
|
67
|
+
const found = projectInstructions(cwd, protectedFiles);
|
|
68
|
+
if (found === null)
|
|
69
|
+
return "";
|
|
70
|
+
const { name, text } = found;
|
|
71
|
+
return `\n\n=== Project instructions (${name}) ===\n${text.length > 8 * 1024 ? text.slice(0, 8 * 1024) + `\n\n[truncated at ${8 * 1024} chars]` : text}`;
|
|
72
|
+
}
|
|
73
|
+
/** The instruction file the prompt reads, and its text — the ONE lookup,
|
|
74
|
+
* so the opening's RULES fact (Graphite §7.10) names exactly the file the
|
|
75
|
+
* model is given, never a file it is not. */
|
|
76
|
+
export function projectInstructions(cwd, protectedFiles = []) {
|
|
67
77
|
const id = protectedIdentity(protectedFiles);
|
|
68
78
|
for (const name of INSTRUCTION_FILES) {
|
|
69
79
|
if (isProtectedPath(join(cwd, name), id))
|
|
70
80
|
continue;
|
|
71
|
-
let text;
|
|
72
81
|
try {
|
|
73
|
-
text
|
|
82
|
+
return { name, text: readFileSync(join(cwd, name), "utf8") };
|
|
74
83
|
}
|
|
75
84
|
catch {
|
|
76
85
|
continue; // not present — try the next
|
|
77
86
|
}
|
|
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
87
|
}
|
|
80
|
-
return
|
|
88
|
+
return null;
|
|
81
89
|
}
|
|
82
90
|
/** A area: the session's system prompt — the constant plus any project
|
|
83
91
|
* instructions found in the workspace. Deterministic per cwd. */
|
package/dist/config.d.ts
CHANGED
|
@@ -151,7 +151,8 @@ export declare function loadUserConfig(): KisoConfig | null;
|
|
|
151
151
|
/** The project config — read ONLY when the project's .kiso passed the E3
|
|
152
152
|
* trust gate (an untrusted project's config is never even read). */
|
|
153
153
|
export declare function loadProjectConfig(cwd: string, trusted: boolean): KisoConfig | null;
|
|
154
|
-
/** Merge: project
|
|
154
|
+
/** Merge: a project's keys win over the user's (each layer's own keys
|
|
155
|
+
* only) — except that a project may only tighten (assertProjectTightens). */
|
|
155
156
|
export declare function mergeConfigs(user: KisoConfig | null, project: KisoConfig | null): KisoConfig;
|
|
156
157
|
/** What a profile's sign-in actually IS. The two shapes are not
|
|
157
158
|
* interchangeable: an API key is a string an adapter holds, an OAuth
|
|
@@ -210,6 +211,9 @@ export declare function resolveContextWindow(merged: KisoConfig, profile?: Model
|
|
|
210
211
|
/** Auto-compact: env (KISO_AUTO_COMPACT) > config.autoCompact > off. An
|
|
211
212
|
* invalid env value is OFF (it set the env → it wins, and it is invalid). */
|
|
212
213
|
export declare function resolveAutoCompact(merged: KisoConfig): AutoCompactConfig | undefined;
|
|
213
|
-
/** Project-trust policy: config
|
|
214
|
-
*
|
|
214
|
+
/** Project-trust policy: the USER config's projectTrust — "ask" (the E3
|
|
215
|
+
* gate, default) or "never" (the gate auto-refuses). The gate passes
|
|
216
|
+
* loadUserConfig() alone (trust-ui.ts): a project never relaxes its own
|
|
217
|
+
* gate, and it could not anyway, since an untrusted project's config is
|
|
218
|
+
* never read. */
|
|
215
219
|
export declare function resolveProjectTrustPolicy(merged: KisoConfig): "ask" | "never";
|