@vincemakes/kiso-code 0.46.2 → 0.47.1
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 +126 -44
- package/dist/coding-prompt.d.ts +7 -0
- package/dist/coding-prompt.js +12 -4
- package/dist/create-coding-agent.js +11 -2
- package/dist/dispatch.d.ts +2 -0
- package/dist/dispatch.js +335 -78
- 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 +101 -41
- package/dist/opening.d.ts +48 -0
- package/dist/opening.js +43 -0
- package/dist/replay.d.ts +8 -1
- package/dist/replay.js +22 -11
- 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 +7 -0
- package/dist/task-notice.d.ts +33 -0
- package/dist/task-notice.js +73 -0
- 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
11
|
import { deliveryLines, liveTasks, lostToldOf, mergedConfig, queuedSwitchLines, setExitTasks, tasksFor, taskWhatOf } from "./state.js";
|
|
12
|
-
import { taskCounts, taskNoticeLines } from "./task-notice.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,8 +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
|
-
|
|
957
|
-
|
|
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))));
|
|
958
1000
|
break;
|
|
959
1001
|
}
|
|
960
1002
|
if (ev.source === "system") {
|
|
@@ -1108,7 +1150,12 @@ onInputLanded) {
|
|
|
1108
1150
|
case "cancel": {
|
|
1109
1151
|
// round 10: a cancellation is a CONSERVATIVE denial,
|
|
1110
1152
|
// explicitly distinguished from the user typing "n".
|
|
1111
|
-
|
|
1153
|
+
// 0.47.1 (finding 0470-F4, owner 2026-10-07): on a dock
|
|
1154
|
+
// esc IS the panel's deny key (`esc denies`), and the
|
|
1155
|
+
// card says `denied by you` — this row said it a third
|
|
1156
|
+
// time. A pipe keeps it.
|
|
1157
|
+
if (!dock.active)
|
|
1158
|
+
body.notice("[approval cancelled — treated as a denial]");
|
|
1112
1159
|
await session.approve(decisionId, false);
|
|
1113
1160
|
break;
|
|
1114
1161
|
}
|
|
@@ -1197,8 +1244,11 @@ onInputLanded) {
|
|
|
1197
1244
|
const e = ev.outcome.error;
|
|
1198
1245
|
body.notice(`run failed — ${e.code}${e.status !== undefined ? ` ${e.status}` : ""}${e.retryable ? " (retryable)" : ""}: ${escapeTerminal(e.message)}`);
|
|
1199
1246
|
}
|
|
1200
|
-
|
|
1247
|
+
// Graphite §7.11: the seal is a cell on the terminal (drawn at
|
|
1248
|
+
// the current width, no context share); the pipe keeps the recap.
|
|
1249
|
+
const recapStats = {
|
|
1201
1250
|
seconds: Math.round((Date.now() - turnStart) / 1000),
|
|
1251
|
+
stopped: ev.outcome.kind === "aborted",
|
|
1202
1252
|
// R3g: the work terms are NOT passed — the fold line
|
|
1203
1253
|
// says what the turn did, once, where it happened and
|
|
1204
1254
|
// with the key that reopens it. This row is the turn's
|
|
@@ -1220,12 +1270,18 @@ onInputLanded) {
|
|
|
1220
1270
|
// W19: under plan the recap becomes the way-forward row
|
|
1221
1271
|
// (the /mode hints are the mode's exits).
|
|
1222
1272
|
mode: getMode(),
|
|
1223
|
-
}
|
|
1273
|
+
};
|
|
1274
|
+
body.seal(sealTiers(recapStats), renderRecap(recapStats));
|
|
1224
1275
|
break;
|
|
1225
1276
|
}
|
|
1226
1277
|
default: {
|
|
1227
1278
|
// Events without a cell (stop, …) — the generic render, byte-
|
|
1228
1279
|
// preserved for the pipe path (C5: Event → RenderInput first).
|
|
1280
|
+
// the compaction round (owner, 2026-10-06): on a dock the
|
|
1281
|
+
// COMPACTED row above already says it — `[summarized up to seq
|
|
1282
|
+
// N]` was a raw row under it. A pipe keeps the line.
|
|
1283
|
+
if (ev.type === "summarized" && dock.active)
|
|
1284
|
+
break;
|
|
1229
1285
|
const input = toRenderInput(ev);
|
|
1230
1286
|
if (input === null)
|
|
1231
1287
|
break;
|
|
@@ -1335,6 +1391,11 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
|
|
|
1335
1391
|
runGlyph = g;
|
|
1336
1392
|
paintRunning();
|
|
1337
1393
|
});
|
|
1394
|
+
// Graphite §8.7: the live row stands from the run's FIRST frame —
|
|
1395
|
+
// the spinner's first tick is 200ms away, and a row that arrived
|
|
1396
|
+
// then would move the content above it once the turn had begun.
|
|
1397
|
+
paintRunning();
|
|
1398
|
+
setTitleState("working"); // §8.10: the title says so, once, not per tick
|
|
1338
1399
|
(async () => {
|
|
1339
1400
|
let last;
|
|
1340
1401
|
try {
|
|
@@ -1511,7 +1572,7 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
|
|
|
1511
1572
|
// no rate in the pricing table contributes nothing and the row shows no
|
|
1512
1573
|
// $ at all, because a partial total presented as a total is a lie.
|
|
1513
1574
|
let spentUsd = null;
|
|
1514
|
-
let runGlyph =
|
|
1575
|
+
let runGlyph = STATUS_GLYPHS[0];
|
|
1515
1576
|
let runStart = Date.now();
|
|
1516
1577
|
// TPS-1: the decode rate of the last SETTLED call. One variable serves
|
|
1517
1578
|
// both rows because they want the same number at different moments;
|
|
@@ -1557,27 +1618,39 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
|
|
|
1557
1618
|
// (DF-0330-F1). A pending retry makes it thirty columns longer, and
|
|
1558
1619
|
// composed blind it would be cut from the END — the context figure,
|
|
1559
1620
|
// a fact, going before the gesture hints.
|
|
1560
|
-
|
|
1561
|
-
|
|
1621
|
+
// Graphite §8.7 / §8.9: the turn's own facts ride the LIVE row above
|
|
1622
|
+
// the composer; the status bar keeps the session's (its ctx moves as
|
|
1623
|
+
// the turn reads).
|
|
1624
|
+
if (!dock.active)
|
|
1625
|
+
return;
|
|
1626
|
+
// the main-sync round (ADR-0058 3e): the live row teaches ctrl+b exactly
|
|
1627
|
+
// while a command can be moved to the background; the bar counts the
|
|
1628
|
+
// session's tasks (§8.9)
|
|
1629
|
+
// the compaction round (owner, 2026-10-06): a summary inside the run is
|
|
1630
|
+
// the row /compact draws — `compacting · auto`, its progress in the
|
|
1631
|
+
// bar's cells — not `working` with a clock and nothing arriving
|
|
1632
|
+
const summary = inRunCompaction();
|
|
1633
|
+
if (summary !== null) {
|
|
1634
|
+
const W = rowWidth();
|
|
1635
|
+
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));
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
dock.setLive(workingRow(runGlyph, runStart, runUsage.out, lastTokPerSec, rowWidth(), retryOnRow(), detachableNow()));
|
|
1639
|
+
dock.setBar(barFor(session, { tokPerSec: lastTokPerSec, tasks: taskCountsNow() }));
|
|
1562
1640
|
};
|
|
1563
1641
|
// W19: under plan the idle row makes the posture unmistakable — the W4
|
|
1564
1642
|
// parentheses idiom names the read-only constraint. The tier is the
|
|
1565
1643
|
// CALLER's word (the recovery flow passes the bare mode).
|
|
1566
1644
|
const paintIdle = () => {
|
|
1645
|
+
setTitleState("ready"); // §8.10 — a dock-less TTY has a title too
|
|
1567
1646
|
if (!dock.active)
|
|
1568
1647
|
return;
|
|
1569
|
-
//
|
|
1570
|
-
//
|
|
1571
|
-
//
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
tokPerSec: lastTokPerSec,
|
|
1576
|
-
},
|
|
1577
|
-
// DF-0330-F1: the row's budget. Without it the row is composed
|
|
1578
|
-
// blind and invariant ① cuts whatever sits last — which is how
|
|
1579
|
-
// a measured rate went missing at 100 columns.
|
|
1580
|
-
rowWidth(), !floorOn, taskCountsNow(true)));
|
|
1648
|
+
// Graphite §8.9: nothing is live; the bar carries the session and
|
|
1649
|
+
// its health. TUI2-R1 (E): the meter's fields are omitted while
|
|
1650
|
+
// unmeasured, so a session that has not called the model paints no
|
|
1651
|
+
// rate.
|
|
1652
|
+
dock.setLive(null);
|
|
1653
|
+
dock.setBar(barFor(session, { tokPerSec: lastTokPerSec, tasks: taskCountsNow(true) }));
|
|
1581
1654
|
};
|
|
1582
1655
|
// TUI2-R2 ⑥ — the BOOT status line. The row is the product's one
|
|
1583
1656
|
// persistent claim about itself (the tier, how to change it, the model,
|
|
@@ -1824,6 +1897,7 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
|
|
|
1824
1897
|
const ratio = autoCompactRatio(session); // A1a: the policy keeps the pre-A1a number — A1b decides if it moves
|
|
1825
1898
|
if (!Number.isFinite(ratio) || ratio < autoCompact.thresholdRatio)
|
|
1826
1899
|
return;
|
|
1900
|
+
markAutoCompact();
|
|
1827
1901
|
dispatch("/compact", dispatchCtx);
|
|
1828
1902
|
};
|
|
1829
1903
|
// CX-1 F5: a literal input (task-file mode) submits the turn directly —
|
|
@@ -1857,14 +1931,16 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
|
|
|
1857
1931
|
// The switch is the SAME live-extension flip /mode performs; the status
|
|
1858
1932
|
// row repaints at once with a one-line notice.
|
|
1859
1933
|
input.onModeCycle?.(() => {
|
|
1860
|
-
const
|
|
1934
|
+
const was = getMode();
|
|
1935
|
+
const next = OFFERED_MODES[(OFFERED_MODES.indexOf(was) + 1) % OFFERED_MODES.length];
|
|
1861
1936
|
const wasOn = getDontAsk();
|
|
1862
1937
|
setMode(next);
|
|
1863
1938
|
paintIdle();
|
|
1864
|
-
|
|
1939
|
+
// Graphite R3e: the MODE row on a terminal (the pipe keeps these words)
|
|
1940
|
+
body.modeNotice(`mode → ${MODE_LABEL[next]} (shift+tab cycles)`, MODE_LABEL[was], MODE_LABEL[next]);
|
|
1865
1941
|
// the switch came with the old dontAsk tier, and left with it
|
|
1866
1942
|
if (getDontAsk() !== wasOn)
|
|
1867
|
-
body.
|
|
1943
|
+
body.dontAskNotice("don't ask → off", false);
|
|
1868
1944
|
});
|
|
1869
1945
|
// Recovery first: a session with a dangling pause or uncertain
|
|
1870
1946
|
// executions must resolve them BEFORE the REPL accepts new turns —
|
|
@@ -1886,11 +1962,17 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
|
|
|
1886
1962
|
// before the run resumes: the run continues on the compacted projection.
|
|
1887
1963
|
const cold = !cancelled && process.stdin.isTTY ? coldResumeOffer(session) : null;
|
|
1888
1964
|
if (cold !== null) {
|
|
1965
|
+
// Graphite P1b (owner, 2026-09-30): either way the compaction row
|
|
1966
|
+
// says why it runs — `cold cache` — so on a dock dontAsk needs no line
|
|
1967
|
+
// of its own; off a dock the line is printed as it always was
|
|
1889
1968
|
if (getDontAsk()) {
|
|
1890
|
-
|
|
1969
|
+
if (!dock.active)
|
|
1970
|
+
body.notice(`[dontAsk] ${coldResumeLine(cold.tokens, cold.minutes)} — compacting first`);
|
|
1971
|
+
markCompactWhy("cold cache");
|
|
1891
1972
|
dispatch("/compact", dispatchCtx);
|
|
1892
1973
|
}
|
|
1893
1974
|
else if ((await askPanel(input, coldResumeView(cold.tokens, cold.minutes))).action === "allow") {
|
|
1975
|
+
markCompactWhy("cold cache");
|
|
1894
1976
|
dispatch("/compact", dispatchCtx);
|
|
1895
1977
|
}
|
|
1896
1978
|
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. */
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* Moved verbatim from index.ts's makeAgent (R1, 2026-09-23) and renamed;
|
|
8
8
|
* the body is not edited beyond the rename and activeStoreDir's setter.
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
10
|
+
import { onInRunSummary } from "./in-run-compaction.js";
|
|
11
|
+
import { activeStoreDir, childTurnBudget, setActiveStoreDir, body, bodyLog, dock, codingToolOptions, extensionsDir, floorOn, kisoHome, loadedExtensions, ownSessionsDir, projectRoot, protectedFiles, secretEnvNamesOf, sessionsDir, setAgentModel, setConfigModels, setConfiguredWindow, setCurrentAgentExtensions, setCurrentFaux, setCurrentModelName, setCurrentProfileName, setExtensionLists, setFloorOn, setMergedConfig, setModelChoice, setNeverInherited, setRetryShown, setSessionStore, setUserProtectedPaths, settingsLayers, workspaceRoot } from "./state.js";
|
|
11
12
|
import { claimProjectDir, projectLayoutActive } from "./projects.js";
|
|
12
13
|
import { migrationNotice, pendingLegacyIds, planMigration, runMigration } from "./session-migration.js";
|
|
13
14
|
import { askUi, resolveProjectTrust } from "./trust-ui.js";
|
|
@@ -202,7 +203,12 @@ export async function createCodingAgent(sessionId, input, modelFlag) {
|
|
|
202
203
|
setConfiguredWindow(resolveContextWindow(merged, resolved?.profile));
|
|
203
204
|
const model = resolved === null ? "faux" : resolved.profile.model;
|
|
204
205
|
if (resolved === null) {
|
|
205
|
-
|
|
206
|
+
// the last sweep (owner, 2026-10-06): on a dock this line was written
|
|
207
|
+
// into the frame and painted over at once — the opening's MODEL row
|
|
208
|
+
// says it instead (opening.ts). A pipe, and -p, keep it.
|
|
209
|
+
if (!dock.active) {
|
|
210
|
+
console.log("[faux mode — set ANTHROPIC_API_KEY or OPENAI_API_KEY, or configure models in ~/.kiso/config.json]\n");
|
|
211
|
+
}
|
|
206
212
|
setCurrentFaux(true);
|
|
207
213
|
setCurrentModelName("faux");
|
|
208
214
|
setCurrentProfileName(null);
|
|
@@ -302,6 +308,9 @@ export async function createCodingAgent(sessionId, input, modelFlag) {
|
|
|
302
308
|
isCheck: (command) => runsACheck(command, Object.values(merged.checks ?? {})),
|
|
303
309
|
// ADR-0055 Amendment 2: a discarded checkpoint says so, in sizes only.
|
|
304
310
|
onDiscard: (d) => body.notice(compactionDiscardedNotice(d)),
|
|
311
|
+
// the compaction round (owner, 2026-10-06): a summary inside a run
|
|
312
|
+
// is drawn while it runs — the running row reads it
|
|
313
|
+
onSummary: onInRunSummary,
|
|
305
314
|
// ADR-0055 Amendment 2 (decision 3): only a window someone stated
|
|
306
315
|
// arms the overflow belt — read from the LIVE binding, so it moves
|
|
307
316
|
// with /model and /resume; the fallback reads as null.
|
package/dist/dispatch.d.ts
CHANGED
|
@@ -69,4 +69,6 @@ export declare function parseBang(trimmed: string): {
|
|
|
69
69
|
readonly command: string;
|
|
70
70
|
readonly send: boolean;
|
|
71
71
|
} | null;
|
|
72
|
+
export declare function markCompactWhy(why: string): void;
|
|
73
|
+
export declare function markAutoCompact(): void;
|
|
72
74
|
export declare function dispatch(line: string, ctx: DispatchCtx): void;
|