dsh-code 0.8.0 → 0.9.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/README.en.md +19 -3
- package/README.md +19 -3
- package/lib/index.mjs +1175 -227
- package/lib/types/app.d.ts +13 -0
- package/lib/types/approval.d.ts +3 -1
- package/lib/types/kernel-panels.d.ts +58 -8
- package/lib/types/models.d.ts +15 -1
- package/lib/types/render/animations.d.ts +14 -2
- package/lib/types/render/projection.d.ts +2 -0
- package/lib/types/render/tool-preview.d.ts +10 -0
- package/lib/types/session-directory.d.ts +46 -2
- package/lib/types/subagents.d.ts +60 -0
- package/package.json +25 -1
- package/src/app.ts +400 -103
- package/src/approval.ts +161 -135
- package/src/index.ts +175 -8
- package/src/kernel-panels.ts +310 -30
- package/src/models.ts +26 -0
- package/src/render/animations.ts +49 -5
- package/src/render/lines.ts +236 -233
- package/src/render/projection.ts +5 -1
- package/src/render/tool-preview.ts +77 -50
- package/src/session-directory.ts +128 -6
- package/src/subagents.ts +165 -0
package/lib/index.mjs
CHANGED
|
@@ -2,9 +2,9 @@ import { n as __require, r as __toESM, t as __commonJSMin } from "./rolldown-run
|
|
|
2
2
|
import { a as getPalette, c as parseThemeName, i as error, l as setTheme, n as brand, o as getTheme, r as dim, s as inkColor, u as chalk } from "./theme-BEi4i_aN.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
|
-
import { readFileSync } from "node:fs";
|
|
5
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
6
6
|
import os, { homedir } from "node:os";
|
|
7
|
-
import { mkdir, readdir, writeFile } from "node:fs/promises";
|
|
7
|
+
import { mkdir, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
8
8
|
import { basename, dirname, join, resolve } from "node:path";
|
|
9
9
|
import z from "@deepseek-ai/schemastery";
|
|
10
10
|
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
@@ -24633,6 +24633,30 @@ function toolArgumentsPreview(args, toolName) {
|
|
|
24633
24633
|
} catch {}
|
|
24634
24634
|
return boundedRawPreview(args);
|
|
24635
24635
|
}
|
|
24636
|
+
/** Visible budget for one delegation prompt row on the tool card. */
|
|
24637
|
+
const MAX_PROMPT_CHARS = 160;
|
|
24638
|
+
/**
|
|
24639
|
+
* Bounded prompt preview for delegation-style tools (`subagent`): the
|
|
24640
|
+
* `prompt` argument rendered as the card's second row, so the transcript
|
|
24641
|
+
* shows what the child agent was asked — not just its description label —
|
|
24642
|
+
* while it runs (Codex's SpawnAgent card preview). Anything else returns ''.
|
|
24643
|
+
* @param toolName - the tool the arguments belong to.
|
|
24644
|
+
* @param args - raw JSON arguments string as the model produced it.
|
|
24645
|
+
* @returns the one-line prompt preview, or '' when none applies.
|
|
24646
|
+
*/
|
|
24647
|
+
function toolPromptPreview(toolName, args) {
|
|
24648
|
+
if (toolName !== "subagent" || args === "" || args.length > MAX_PARSE_CHARS) return "";
|
|
24649
|
+
try {
|
|
24650
|
+
const parsed = JSON.parse(args);
|
|
24651
|
+
if (parsed === null || typeof parsed !== "object") return "";
|
|
24652
|
+
const prompt = parsed["prompt"];
|
|
24653
|
+
if (typeof prompt !== "string" || prompt === "") return "";
|
|
24654
|
+
const flat = prompt.replace(/\s+/gu, " ").trim();
|
|
24655
|
+
return flat.length > MAX_PROMPT_CHARS ? `${flat.slice(0, 159)}…` : flat;
|
|
24656
|
+
} catch {
|
|
24657
|
+
return "";
|
|
24658
|
+
}
|
|
24659
|
+
}
|
|
24636
24660
|
//#endregion
|
|
24637
24661
|
//#region src/render/tool-detail.ts
|
|
24638
24662
|
/**
|
|
@@ -25091,6 +25115,7 @@ function projectEvent(view, event) {
|
|
|
25091
25115
|
name: data.name,
|
|
25092
25116
|
arguments: data.arguments,
|
|
25093
25117
|
preview: toolArgumentsPreview(data.arguments, data.name),
|
|
25118
|
+
prompt: toolPromptPreview(data.name, data.arguments),
|
|
25094
25119
|
state: "running",
|
|
25095
25120
|
summary: "",
|
|
25096
25121
|
detail: void 0
|
|
@@ -25632,6 +25657,7 @@ function replayProjectEvent(acc, event) {
|
|
|
25632
25657
|
name: data.name,
|
|
25633
25658
|
arguments: data.arguments,
|
|
25634
25659
|
preview: toolArgumentsPreview(data.arguments, data.name),
|
|
25660
|
+
prompt: toolPromptPreview(data.name, data.arguments),
|
|
25635
25661
|
state: "running",
|
|
25636
25662
|
summary: "",
|
|
25637
25663
|
detail: void 0
|
|
@@ -26518,6 +26544,15 @@ const DEEPSEEK_WAVE_BANDS = {
|
|
|
26518
26544
|
.35,
|
|
26519
26545
|
.55,
|
|
26520
26546
|
1
|
|
26547
|
+
]],
|
|
26548
|
+
unknown: [[
|
|
26549
|
+
.1,
|
|
26550
|
+
.7,
|
|
26551
|
+
1
|
|
26552
|
+
], [
|
|
26553
|
+
.35,
|
|
26554
|
+
.55,
|
|
26555
|
+
1
|
|
26521
26556
|
]]
|
|
26522
26557
|
},
|
|
26523
26558
|
aurora: {
|
|
@@ -26546,6 +26581,23 @@ const DEEPSEEK_WAVE_BANDS = {
|
|
|
26546
26581
|
.35,
|
|
26547
26582
|
2
|
|
26548
26583
|
]
|
|
26584
|
+
],
|
|
26585
|
+
unknown: [
|
|
26586
|
+
[
|
|
26587
|
+
.35,
|
|
26588
|
+
.15,
|
|
26589
|
+
0
|
|
26590
|
+
],
|
|
26591
|
+
[
|
|
26592
|
+
-.5,
|
|
26593
|
+
.6,
|
|
26594
|
+
1
|
|
26595
|
+
],
|
|
26596
|
+
[
|
|
26597
|
+
.75,
|
|
26598
|
+
.35,
|
|
26599
|
+
2
|
|
26600
|
+
]
|
|
26549
26601
|
]
|
|
26550
26602
|
},
|
|
26551
26603
|
pulse: {
|
|
@@ -26562,15 +26614,25 @@ const DEEPSEEK_WAVE_BANDS = {
|
|
|
26562
26614
|
.45,
|
|
26563
26615
|
.55,
|
|
26564
26616
|
1.1
|
|
26617
|
+
]],
|
|
26618
|
+
unknown: [[
|
|
26619
|
+
.1,
|
|
26620
|
+
.55,
|
|
26621
|
+
.8
|
|
26622
|
+
], [
|
|
26623
|
+
.45,
|
|
26624
|
+
.55,
|
|
26625
|
+
1.1
|
|
26565
26626
|
]]
|
|
26566
26627
|
}
|
|
26567
26628
|
};
|
|
26568
26629
|
/** Original Codex duration used as the animation's sampling timeline. */
|
|
26569
26630
|
function deepseekWaveBaseDuration(tier, style) {
|
|
26631
|
+
const pro = tier === "deepseek" || tier === "unknown";
|
|
26570
26632
|
switch (style) {
|
|
26571
|
-
case "aurora": return
|
|
26572
|
-
case "pulse": return
|
|
26573
|
-
case "wave": return
|
|
26633
|
+
case "aurora": return pro ? 1600 : 1300;
|
|
26634
|
+
case "pulse": return pro ? 1250 : 900;
|
|
26635
|
+
case "wave": return pro ? 1300 : 1e3;
|
|
26574
26636
|
}
|
|
26575
26637
|
}
|
|
26576
26638
|
/**
|
|
@@ -26822,6 +26884,206 @@ function isOfficialDeepSeekLabel(label) {
|
|
|
26822
26884
|
const model = slash < 0 ? "" : label.slice(slash + 1);
|
|
26823
26885
|
return provider.toLowerCase().includes("deepseek") || model.toLowerCase().includes("deepseek");
|
|
26824
26886
|
}
|
|
26887
|
+
/**
|
|
26888
|
+
* Known reasoning-effort ranks in ascending order. Effort ids are opaque
|
|
26889
|
+
* adapter-owned strings, so the rank table covers the conventional names
|
|
26890
|
+
* (off → low → medium → high → xhigh → max/ultra); an unrecognized id
|
|
26891
|
+
* ranks as unknown (0), which never triggers the high-effort wave.
|
|
26892
|
+
*/
|
|
26893
|
+
const EFFORT_RANK = {
|
|
26894
|
+
off: 0,
|
|
26895
|
+
none: 0,
|
|
26896
|
+
low: 1,
|
|
26897
|
+
medium: 2,
|
|
26898
|
+
med: 2,
|
|
26899
|
+
high: 3,
|
|
26900
|
+
xhigh: 4,
|
|
26901
|
+
"x-high": 4,
|
|
26902
|
+
"very-high": 4,
|
|
26903
|
+
max: 5,
|
|
26904
|
+
maximum: 5,
|
|
26905
|
+
ultra: 5
|
|
26906
|
+
};
|
|
26907
|
+
/**
|
|
26908
|
+
* True when an effective reasoning effort is STRICTLY above `high` — the
|
|
26909
|
+
* trigger gate for the "Into the Unknown" wave on non-DeepSeek routes.
|
|
26910
|
+
* Absent efforts and unrecognized ids never qualify.
|
|
26911
|
+
* @param effort - the effective reasoning-effort id ('' or undefined when none).
|
|
26912
|
+
* @returns whether the effort ranks above high.
|
|
26913
|
+
*/
|
|
26914
|
+
function effortAboveHigh(effort) {
|
|
26915
|
+
if (effort === void 0 || effort === "") return false;
|
|
26916
|
+
const rank = EFFORT_RANK[effort.trim().toLowerCase()];
|
|
26917
|
+
return rank !== void 0 && rank > 3;
|
|
26918
|
+
}
|
|
26919
|
+
//#endregion
|
|
26920
|
+
//#region src/session-directory.ts
|
|
26921
|
+
/** Lightweight session-directory projection for the /resume picker. */
|
|
26922
|
+
/** Case-insensitive filesystems (Windows, macOS) compare paths by lowercased form. */
|
|
26923
|
+
const CASE_INSENSITIVE_FS = process.platform === "win32" || process.platform === "darwin";
|
|
26924
|
+
/** True when the header describes a subagent conversation (durable lineage). */
|
|
26925
|
+
function isSubagentSession(header) {
|
|
26926
|
+
return header.origin === "subagent" || header.parentSession !== void 0;
|
|
26927
|
+
}
|
|
26928
|
+
function comparablePath(value) {
|
|
26929
|
+
const resolved = resolve(value);
|
|
26930
|
+
const fold = (path) => CASE_INSENSITIVE_FS ? path.toLowerCase() : path;
|
|
26931
|
+
try {
|
|
26932
|
+
return fold(realpathSync(resolved));
|
|
26933
|
+
} catch {
|
|
26934
|
+
return fold(resolved);
|
|
26935
|
+
}
|
|
26936
|
+
}
|
|
26937
|
+
/** Platform-consistent path equality for session cwd comparisons. */
|
|
26938
|
+
function samePath(left, right) {
|
|
26939
|
+
if (left === void 0) return false;
|
|
26940
|
+
return comparablePath(left) === comparablePath(right);
|
|
26941
|
+
}
|
|
26942
|
+
/**
|
|
26943
|
+
* Unique header match by exact id or unique id prefix (root and subagent
|
|
26944
|
+
* headers alike); the caller applies any lineage gate.
|
|
26945
|
+
* @param headers - the persisted headers.
|
|
26946
|
+
* @param wanted - the id or id prefix.
|
|
26947
|
+
* @returns the uniquely matched header.
|
|
26948
|
+
* @throws when nothing matches or the prefix is ambiguous.
|
|
26949
|
+
*/
|
|
26950
|
+
function matchSessionId(headers, wanted) {
|
|
26951
|
+
const exact = headers.filter((header) => header.id === wanted);
|
|
26952
|
+
const matches = exact.length > 0 ? exact : headers.filter((header) => header.id.startsWith(wanted));
|
|
26953
|
+
if (matches.length === 0) throw new Error(`no persisted session matches "${wanted}"`);
|
|
26954
|
+
if (matches.length > 1) throw new Error(`session prefix "${wanted}" is ambiguous (${matches.length} matches): use more of the id`);
|
|
26955
|
+
return matches[0];
|
|
26956
|
+
}
|
|
26957
|
+
/** The newest persisted ROOT session pinned to this cwd, or undefined. */
|
|
26958
|
+
function newestRootForCwd(headers, cwd) {
|
|
26959
|
+
return headers.filter((header) => !isSubagentSession(header) && samePath(header.cwd, cwd)).sort((left, right) => right.createdAt - left.createdAt)[0];
|
|
26960
|
+
}
|
|
26961
|
+
/**
|
|
26962
|
+
* Filter/sort header-only records. No session log is loaded here. Sorting is
|
|
26963
|
+
* by LAST ACTIVITY (`updated` — artifact mtime when the caller resolved one,
|
|
26964
|
+
* else createdAt), matching the codex resume picker's default UpdatedAt
|
|
26965
|
+
* ordering: a session you kept talking in outranks one created later but idle.
|
|
26966
|
+
* @param records - the header-only records.
|
|
26967
|
+
* @param options - filter/sort options.
|
|
26968
|
+
* @param updated - per-session last-activity timestamps, when resolved.
|
|
26969
|
+
*/
|
|
26970
|
+
function projectSessionRows(records, options, updated) {
|
|
26971
|
+
const needle = options.query.trim().toLowerCase();
|
|
26972
|
+
return records.filter((record) => options.sessions === "all" || !isSubagentSession(record.header)).filter((record) => options.cwd === "all" || samePath(record.header.cwd, options.currentCwd)).map((record) => {
|
|
26973
|
+
const cwd = record.header.cwd ?? "";
|
|
26974
|
+
const subagent = isSubagentSession(record.header);
|
|
26975
|
+
const activity = updated?.get(record.header.id);
|
|
26976
|
+
return {
|
|
26977
|
+
id: record.header.id,
|
|
26978
|
+
createdAt: record.header.createdAt,
|
|
26979
|
+
updatedAt: activity === void 0 || !Number.isFinite(activity) || activity < record.header.createdAt ? record.header.createdAt : activity,
|
|
26980
|
+
cwd,
|
|
26981
|
+
workspace: cwd === "" ? "(no workspace)" : basename(cwd),
|
|
26982
|
+
parent: record.header.parentSession,
|
|
26983
|
+
subagent,
|
|
26984
|
+
resumable: !subagent,
|
|
26985
|
+
live: record.live,
|
|
26986
|
+
persisted: record.persisted,
|
|
26987
|
+
preset: record.header.agentPreset ?? "standard"
|
|
26988
|
+
};
|
|
26989
|
+
}).filter((row) => needle === "" || `${row.id} ${row.cwd} ${row.workspace} ${row.preset}`.toLowerCase().includes(needle)).sort((left, right) => options.sort === "newest" ? right.updatedAt - left.updatedAt || right.createdAt - left.createdAt : left.updatedAt - right.updatedAt || left.createdAt - right.createdAt);
|
|
26990
|
+
}
|
|
26991
|
+
/** Merge page-local title observations without disturbing directory order. */
|
|
26992
|
+
function mergeSessionTitles(rows, observations) {
|
|
26993
|
+
const titles = /* @__PURE__ */ new Map();
|
|
26994
|
+
for (const observation of observations) {
|
|
26995
|
+
if (observation.status !== "fulfilled") continue;
|
|
26996
|
+
const title = observation.value?.title?.title ?? observation.value?.title?.text;
|
|
26997
|
+
if (title !== void 0 && title.trim() !== "") titles.set(observation.sessionId, title);
|
|
26998
|
+
}
|
|
26999
|
+
return rows.map((row) => titles.has(row.id) ? {
|
|
27000
|
+
...row,
|
|
27001
|
+
title: titles.get(row.id)
|
|
27002
|
+
} : row);
|
|
27003
|
+
}
|
|
27004
|
+
/**
|
|
27005
|
+
* Encode a session id the way the JSONL backend does for its on-disk layout
|
|
27006
|
+
* (`encodeSegment`: safe units literal, everything else `~XXXX`). Used ONLY to
|
|
27007
|
+
* validate that a `locate()` path really is this session's directory before
|
|
27008
|
+
* any deletion touches the filesystem — a local copy of the pure upstream
|
|
27009
|
+
* contract, kept in sync with `session-persistence-jsonl/src/format.ts`.
|
|
27010
|
+
*/
|
|
27011
|
+
function encodeSessionSegment(raw) {
|
|
27012
|
+
if (raw.length === 0) throw new Error("cannot encode an empty path segment");
|
|
27013
|
+
if (raw === ".") return "~002E";
|
|
27014
|
+
if (raw === "..") return "~002E~002E";
|
|
27015
|
+
let out = "";
|
|
27016
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
27017
|
+
const code = raw.charCodeAt(i);
|
|
27018
|
+
const ch = String.fromCharCode(code);
|
|
27019
|
+
if (ch !== "~" && /^[A-Za-z0-9._-]$/u.test(ch)) out += ch;
|
|
27020
|
+
else out += `~${code.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
27021
|
+
}
|
|
27022
|
+
return out;
|
|
27023
|
+
}
|
|
27024
|
+
/** The session-log artifact names the JSONL backend may create. */
|
|
27025
|
+
const SESSION_ARTIFACT_NAMES = ["session.jsonl", "session.jsonl.zstd"];
|
|
27026
|
+
/**
|
|
27027
|
+
* Guard one `locate()` artifact path before deletion (codex's scoped-path
|
|
27028
|
+
* check, adapted to the JSONL layout): the file must be a `session.jsonl`
|
|
27029
|
+
* artifact sitting in the directory named exactly `encodeSegment(id)`.
|
|
27030
|
+
* @param artifact - the path the persistence backend located.
|
|
27031
|
+
* @param id - the session id the artifact claims to belong to.
|
|
27032
|
+
* @returns the owning session directory, or undefined when the layout is unexpected.
|
|
27033
|
+
*/
|
|
27034
|
+
function sessionArtifactDirectory(artifact, id) {
|
|
27035
|
+
if (basename(artifact) !== "session.jsonl" && basename(artifact) !== "session.jsonl.zstd") return void 0;
|
|
27036
|
+
const dir = dirname(artifact);
|
|
27037
|
+
if (basename(dir) !== encodeSessionSegment(id)) return void 0;
|
|
27038
|
+
return dir;
|
|
27039
|
+
}
|
|
27040
|
+
/**
|
|
27041
|
+
* Collect one session's deletion subtree: the id plus every record whose
|
|
27042
|
+
* parent chain leads to it (codex deletes subagent threads with their root).
|
|
27043
|
+
* @param records - the full directory listing.
|
|
27044
|
+
* @param id - the root session id to delete.
|
|
27045
|
+
* @returns the ids to delete, root first.
|
|
27046
|
+
*/
|
|
27047
|
+
function collectDeletionSubtree(records, id) {
|
|
27048
|
+
const parentOf = /* @__PURE__ */ new Map();
|
|
27049
|
+
for (const record of records) parentOf.set(record.header.id, record.header.parentSession);
|
|
27050
|
+
const doomed = /* @__PURE__ */ new Set([id]);
|
|
27051
|
+
for (let pass = 0; pass < 2; pass += 1) for (const candidate of parentOf.keys()) {
|
|
27052
|
+
if (doomed.has(candidate)) continue;
|
|
27053
|
+
let ancestor = parentOf.get(candidate);
|
|
27054
|
+
let depth = 0;
|
|
27055
|
+
while (ancestor !== void 0 && depth < 64) {
|
|
27056
|
+
if (doomed.has(ancestor)) {
|
|
27057
|
+
doomed.add(candidate);
|
|
27058
|
+
break;
|
|
27059
|
+
}
|
|
27060
|
+
ancestor = parentOf.get(ancestor);
|
|
27061
|
+
depth += 1;
|
|
27062
|
+
}
|
|
27063
|
+
}
|
|
27064
|
+
return [...doomed];
|
|
27065
|
+
}
|
|
27066
|
+
/**
|
|
27067
|
+
* Codex-style relative time for session rows ("now", "5m ago", "3h ago",
|
|
27068
|
+
* "2d ago"; older than a week falls back to the local date).
|
|
27069
|
+
* @param timestamp - epoch milliseconds of the last activity.
|
|
27070
|
+
* @param now - the pinned reference clock (one value per list render).
|
|
27071
|
+
*/
|
|
27072
|
+
function formatRelativeTime(timestamp, now) {
|
|
27073
|
+
const seconds = Math.round((now - timestamp) / 1e3);
|
|
27074
|
+
if (seconds < 0) return "now";
|
|
27075
|
+
if (seconds < 60) return "now";
|
|
27076
|
+
const minutes = Math.round(seconds / 60);
|
|
27077
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
27078
|
+
const hours = Math.round(minutes / 60);
|
|
27079
|
+
if (hours < 24) return `${hours}h ago`;
|
|
27080
|
+
const days = Math.round(hours / 24);
|
|
27081
|
+
if (days < 7) return `${days}d ago`;
|
|
27082
|
+
const date = new Date(timestamp);
|
|
27083
|
+
const month = `${date.getMonth() + 1}`.padStart(2, "0");
|
|
27084
|
+
const day = `${date.getDate()}`.padStart(2, "0");
|
|
27085
|
+
return `${date.getFullYear()}-${month}-${day}`;
|
|
27086
|
+
}
|
|
26825
27087
|
//#endregion
|
|
26826
27088
|
//#region src/render/status.ts
|
|
26827
27089
|
/**
|
|
@@ -27506,6 +27768,7 @@ function transcriptEntryLines(entry, columns) {
|
|
|
27506
27768
|
lineSegment(entry.name, "brand"),
|
|
27507
27769
|
lineSegment(entry.preview === "" ? "" : ` ${entry.preview}`, "dim")
|
|
27508
27770
|
], width),
|
|
27771
|
+
...entry.prompt === "" ? [] : textLines(` └ ${entry.prompt}`, width, "dim"),
|
|
27509
27772
|
...entry.summary === "" ? [] : textLines(` ⎿ ${entry.summary}`, width, entry.state === "error" ? "error" : "dim"),
|
|
27510
27773
|
...entry.detail === void 0 ? [] : toolDetailLines(entry.detail, width)
|
|
27511
27774
|
];
|
|
@@ -27530,6 +27793,17 @@ function transcriptEntryLines(entry, columns) {
|
|
|
27530
27793
|
//#endregion
|
|
27531
27794
|
//#region src/kernel-panels.ts
|
|
27532
27795
|
/** Bounded, composer-safe panels for preset, session, and plugin kernel views. */
|
|
27796
|
+
/** True for the Ctrl+F search-focus toggle. */
|
|
27797
|
+
function isSearchToggle(input, key) {
|
|
27798
|
+
return key.ctrl === true && input === "f";
|
|
27799
|
+
}
|
|
27800
|
+
/** The search-state line: gated panels show only an ACTIVE filter (the ctrl+f
|
|
27801
|
+
* toggle lives in the footer), direct-typing panels keep the plain prompt. */
|
|
27802
|
+
function searchLine(searching, query) {
|
|
27803
|
+
if (searching === true) return `search: ${query === "" ? "type to filter · esc stops" : query}`;
|
|
27804
|
+
if (searching === false) return query === "" ? "" : `search: ${query}`;
|
|
27805
|
+
return `search: ${query === "" ? "type to filter" : query}`;
|
|
27806
|
+
}
|
|
27533
27807
|
function ListFrame(props) {
|
|
27534
27808
|
const stdout = useStdout().stdout;
|
|
27535
27809
|
const viewport = panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30);
|
|
@@ -27560,7 +27834,7 @@ function ListFrame(props) {
|
|
|
27560
27834
|
}, truncateColumns(singleLineText(props.title), viewport.contentColumns)), (0, import_react.createElement)(Text, {
|
|
27561
27835
|
dimColor: true,
|
|
27562
27836
|
wrap: "truncate-end"
|
|
27563
|
-
}, truncateColumns(singleLineText(
|
|
27837
|
+
}, truncateColumns(singleLineText(searchLine(props.searching, props.query)), viewport.contentColumns)), ...visible.map((row, index) => {
|
|
27564
27838
|
const absolute = offset + index;
|
|
27565
27839
|
const selected = !props.loading && props.error === void 0 && props.rows.length > 0 && absolute === props.cursor;
|
|
27566
27840
|
return (0, import_react.createElement)(Text, {
|
|
@@ -27701,10 +27975,10 @@ function PluginPanel({ load, close, initialQuery = "" }) {
|
|
|
27701
27975
|
footer: "↑↓ inspect · enter details · r refresh · esc close"
|
|
27702
27976
|
});
|
|
27703
27977
|
}
|
|
27704
|
-
function ResumePanel({ currentCwd, load, readTranscript, select, close }) {
|
|
27978
|
+
function ResumePanel({ currentCwd, load, readTranscript, select, requestDelete, deleteConfirmId, reloadToken = 0, deleteMode = false, close }) {
|
|
27705
27979
|
const [options, setOptions] = (0, import_react.useState)({
|
|
27706
27980
|
sessions: "roots",
|
|
27707
|
-
cwd: "
|
|
27981
|
+
cwd: "current",
|
|
27708
27982
|
sort: "newest",
|
|
27709
27983
|
currentCwd,
|
|
27710
27984
|
query: ""
|
|
@@ -27717,6 +27991,10 @@ function ResumePanel({ currentCwd, load, readTranscript, select, close }) {
|
|
|
27717
27991
|
const [error, setError] = (0, import_react.useState)();
|
|
27718
27992
|
const [expanded, setExpanded] = (0, import_react.useState)();
|
|
27719
27993
|
const [transcript, setTranscript] = (0, import_react.useState)();
|
|
27994
|
+
/** Ctrl+F-gated search: typing filters only while searching (codex). */
|
|
27995
|
+
const [searching, setSearching] = (0, import_react.useState)(false);
|
|
27996
|
+
/** Reference clock pinned per row render, so relative times never drift mid-list. */
|
|
27997
|
+
const now = (0, import_react.useMemo)(() => Date.now(), [rows, options]);
|
|
27720
27998
|
const transcriptLoad = (0, import_react.useRef)();
|
|
27721
27999
|
(0, import_react.useEffect)(() => () => transcriptLoad.current?.abort(), []);
|
|
27722
28000
|
(0, import_react.useEffect)(() => {
|
|
@@ -27735,7 +28013,7 @@ function ResumePanel({ currentCwd, load, readTranscript, select, close }) {
|
|
|
27735
28013
|
}
|
|
27736
28014
|
});
|
|
27737
28015
|
return () => controller.abort();
|
|
27738
|
-
}, [options]);
|
|
28016
|
+
}, [options, reloadToken]);
|
|
27739
28017
|
(0, import_react.useEffect)(() => setCursor((value) => Math.min(value, Math.max(0, rows.length - 1))), [rows.length]);
|
|
27740
28018
|
const cycle = () => {
|
|
27741
28019
|
if (focus === 3) {
|
|
@@ -27758,7 +28036,34 @@ function ResumePanel({ currentCwd, load, readTranscript, select, close }) {
|
|
|
27758
28036
|
});
|
|
27759
28037
|
};
|
|
27760
28038
|
useInput((input, key) => {
|
|
27761
|
-
if (
|
|
28039
|
+
if (deleteConfirmId !== void 0) return;
|
|
28040
|
+
if (key.escape) {
|
|
28041
|
+
if (searching) {
|
|
28042
|
+
setSearching(false);
|
|
28043
|
+
return;
|
|
28044
|
+
}
|
|
28045
|
+
return close();
|
|
28046
|
+
}
|
|
28047
|
+
if (isSearchToggle(input, key)) {
|
|
28048
|
+
setSearching((current) => !current);
|
|
28049
|
+
return;
|
|
28050
|
+
}
|
|
28051
|
+
if (searching) {
|
|
28052
|
+
if (key.return) {
|
|
28053
|
+
setSearching(false);
|
|
28054
|
+
return;
|
|
28055
|
+
}
|
|
28056
|
+
const next = editQuery(options.query, input, key);
|
|
28057
|
+
if (next !== void 0) {
|
|
28058
|
+
setOptions((value) => ({
|
|
28059
|
+
...value,
|
|
28060
|
+
query: next
|
|
28061
|
+
}));
|
|
28062
|
+
setCursor(0);
|
|
28063
|
+
}
|
|
28064
|
+
return;
|
|
28065
|
+
}
|
|
28066
|
+
if (input === "q") return close();
|
|
27762
28067
|
if (key.tab) return setFocus((value) => (value + (key.shift ? 3 : 1)) % 4);
|
|
27763
28068
|
if (key.leftArrow) return cycle();
|
|
27764
28069
|
if (key.rightArrow) return cycle();
|
|
@@ -27768,7 +28073,7 @@ function ResumePanel({ currentCwd, load, readTranscript, select, close }) {
|
|
|
27768
28073
|
if (key.pageDown) return setCursor((value) => Math.min(rows.length - 1, value + 8));
|
|
27769
28074
|
if (input === "g") return setCursor(0);
|
|
27770
28075
|
if (input === "G") return setCursor(Math.max(0, rows.length - 1));
|
|
27771
|
-
if (input === "d"
|
|
28076
|
+
if (input === "d" && rows[cursor] !== void 0 && requestDelete !== void 0) return requestDelete(rows[cursor]);
|
|
27772
28077
|
if (input === "e" && rows[cursor] !== void 0) return setExpanded((value) => value === rows[cursor].id ? void 0 : rows[cursor].id);
|
|
27773
28078
|
if (input === "t" && rows[cursor] !== void 0) {
|
|
27774
28079
|
const row = rows[cursor];
|
|
@@ -27790,14 +28095,6 @@ function ResumePanel({ currentCwd, load, readTranscript, select, close }) {
|
|
|
27790
28095
|
return;
|
|
27791
28096
|
}
|
|
27792
28097
|
if (key.return && rows[cursor]?.resumable === true) return select(rows[cursor]);
|
|
27793
|
-
const next = editQuery(options.query, input, key);
|
|
27794
|
-
if (next !== void 0) {
|
|
27795
|
-
setOptions((value) => ({
|
|
27796
|
-
...value,
|
|
27797
|
-
query: next
|
|
27798
|
-
}));
|
|
27799
|
-
setCursor(0);
|
|
27800
|
-
}
|
|
27801
28098
|
}, { isActive: transcript === void 0 });
|
|
27802
28099
|
if (transcript !== void 0) return (0, import_react.createElement)(DocumentPanel, {
|
|
27803
28100
|
title: `transcript · ${transcript.id}`,
|
|
@@ -27808,19 +28105,21 @@ function ResumePanel({ currentCwd, load, readTranscript, select, close }) {
|
|
|
27808
28105
|
setTranscript(void 0);
|
|
27809
28106
|
}
|
|
27810
28107
|
});
|
|
28108
|
+
const pendingRow = deleteConfirmId === void 0 ? void 0 : rows.find((row) => row.id === deleteConfirmId);
|
|
27811
28109
|
const toolbar = `[${focus === 0 ? ">" : ""}${options.sessions}] [${focus === 1 ? ">" : ""}${options.cwd} cwd] [${focus === 2 ? ">" : ""}${options.sort}] [${focus === 3 ? ">" : ""}${density}]`;
|
|
27812
28110
|
return (0, import_react.createElement)(ListFrame, {
|
|
27813
|
-
title: `/resume · ${toolbar}`,
|
|
28111
|
+
title: deleteConfirmId === void 0 ? `/resume${deleteMode ? " — delete mode" : ""}${searching ? " — searching" : ""} · ${toolbar}` : `permanently delete ${pendingRow === void 0 ? deleteConfirmId.slice(-12) : pendingRow.title ?? pendingRow.id}? this cannot be undone · subagent threads go too`,
|
|
27814
28112
|
rows: rows.map((row) => ({
|
|
27815
28113
|
key: row.id,
|
|
27816
28114
|
disabled: !row.resumable,
|
|
27817
|
-
text: `${row.subagent ? "↳" : "○"} ${row.title ?? row.id.slice(-12)}${density === "comfortable" ? ` · ${row.workspace} · ${row.preset}` : ""}${row.live ? " · live" : ""}${expanded === row.id ? ` · ${row.id} · ${row.cwd}${row.parent === void 0 ? "" : ` · parent ${row.parent}`}` : ""}`
|
|
28115
|
+
text: `${row.subagent ? "↳" : "○"} ${row.title ?? row.id.slice(-12)}${density === "comfortable" ? ` · ${formatRelativeTime(row.updatedAt ?? row.createdAt, now)} · ${row.workspace} · ${row.preset}` : ""}${row.live ? " · live" : ""}${expanded === row.id ? ` · ${row.id} · ${row.cwd}${row.parent === void 0 ? "" : ` · parent ${row.parent}`}` : ""}`
|
|
27818
28116
|
})),
|
|
27819
28117
|
cursor,
|
|
27820
28118
|
loading,
|
|
27821
28119
|
error,
|
|
27822
28120
|
query: options.query,
|
|
27823
|
-
|
|
28121
|
+
searching,
|
|
28122
|
+
footer: "tab/←→ filters · ↑↓/pg navigate · ctrl+f search · e details · t transcript · d delete · enter resume"
|
|
27824
28123
|
});
|
|
27825
28124
|
}
|
|
27826
28125
|
function DocumentPanel({ title, text, error, close }) {
|
|
@@ -28007,21 +28306,31 @@ function StatuslinePanel({ enabled, change, close }) {
|
|
|
28007
28306
|
/**
|
|
28008
28307
|
* The `/model` reasoning-effort stage (the Codex model → reasoning popup
|
|
28009
28308
|
* contract): one bounded list over the selected model's adapter-advertised
|
|
28010
|
-
* effort levels
|
|
28011
|
-
*
|
|
28012
|
-
*
|
|
28013
|
-
*
|
|
28014
|
-
*
|
|
28015
|
-
* model
|
|
28309
|
+
* effort levels — in the adapter's own display order, ids verbatim (the
|
|
28310
|
+
* kernel treats them as opaque and rejects anything else) — with the
|
|
28311
|
+
* effective effort and the model default marked. A model WITHOUT an
|
|
28312
|
+
* adapter-declared default leads with a "Default" (provider-default) row —
|
|
28313
|
+
* the web effort pane's first entry — so the user can clear a picked level
|
|
28314
|
+
* back to provider behavior. A model advertising no levels opens the same
|
|
28315
|
+
* stage with an explicit empty state (the web pane's "no levels" copy)
|
|
28316
|
+
* instead of a bare failure notice. Enter applies one level; Esc returns to
|
|
28317
|
+
* the model list without applying.
|
|
28016
28318
|
*/
|
|
28017
28319
|
function EffortPanel({ row, current, select, back }) {
|
|
28018
|
-
const
|
|
28019
|
-
const
|
|
28020
|
-
const
|
|
28320
|
+
const advertised = row.reasoning?.efforts ?? [];
|
|
28321
|
+
const empty = row.reasoning === void 0 || advertised.length === 0;
|
|
28322
|
+
const hasDefaultRow = row.reasoning !== void 0 && row.reasoning.defaultEffort === void 0;
|
|
28323
|
+
const rows = empty ? [{
|
|
28324
|
+
id: "",
|
|
28325
|
+
name: ""
|
|
28326
|
+
}] : hasDefaultRow ? [{
|
|
28021
28327
|
id: "",
|
|
28022
28328
|
name: "Default"
|
|
28023
|
-
}, ...
|
|
28329
|
+
}, ...advertised] : advertised;
|
|
28024
28330
|
const effective = current === void 0 || current === "" ? "" : current;
|
|
28331
|
+
const wanted = effective === "" ? row.reasoning?.defaultEffort ?? "" : effective;
|
|
28332
|
+
const initialCursor = Math.max(0, rows.findIndex((effort) => effort.id === wanted));
|
|
28333
|
+
const [cursor, setCursor] = (0, import_react.useState)(initialCursor);
|
|
28025
28334
|
(0, import_react.useEffect)(() => {
|
|
28026
28335
|
if (rows.length === 0) {
|
|
28027
28336
|
if (cursor !== 0) setCursor(0);
|
|
@@ -28031,7 +28340,15 @@ function EffortPanel({ row, current, select, back }) {
|
|
|
28031
28340
|
}, [rows.length, cursor]);
|
|
28032
28341
|
useInput((input, key) => {
|
|
28033
28342
|
if (key.escape || input === "q") return back();
|
|
28034
|
-
if (
|
|
28343
|
+
if (empty) return;
|
|
28344
|
+
if (input === "g") {
|
|
28345
|
+
setCursor(0);
|
|
28346
|
+
return;
|
|
28347
|
+
}
|
|
28348
|
+
if (input === "G") {
|
|
28349
|
+
setCursor(rows.length - 1);
|
|
28350
|
+
return;
|
|
28351
|
+
}
|
|
28035
28352
|
if (key.upArrow) {
|
|
28036
28353
|
setCursor(cursor > 0 ? cursor - 1 : rows.length - 1);
|
|
28037
28354
|
return;
|
|
@@ -28044,14 +28361,190 @@ function EffortPanel({ row, current, select, back }) {
|
|
|
28044
28361
|
});
|
|
28045
28362
|
return (0, import_react.createElement)(ListFrame, {
|
|
28046
28363
|
title: `/model — effort for ${row.providerName} · ${row.modelName}`,
|
|
28047
|
-
rows:
|
|
28364
|
+
rows: empty ? [{
|
|
28365
|
+
key: "empty",
|
|
28366
|
+
disabled: true,
|
|
28367
|
+
text: "this model advertises no reasoning effort levels — the provider default applies"
|
|
28368
|
+
}] : rows.map((effort) => ({
|
|
28048
28369
|
key: effort.id,
|
|
28049
28370
|
text: `${effort.id === effective ? "●" : "○"} ${effort.name}${effort.id === row.reasoning?.defaultEffort ? " · default" : ""}${effort.description === void 0 ? "" : ` · ${effort.description}`}`
|
|
28050
28371
|
})),
|
|
28051
28372
|
cursor,
|
|
28052
28373
|
loading: false,
|
|
28053
28374
|
query: "",
|
|
28054
|
-
footer: "↑↓ choose · enter apply · esc/q back"
|
|
28375
|
+
footer: empty ? "esc back" : "↑↓ choose · enter apply · esc/q back"
|
|
28376
|
+
});
|
|
28377
|
+
}
|
|
28378
|
+
/**
|
|
28379
|
+
* The /agents panel (the Codex agent-picker contract, read-only): this
|
|
28380
|
+
* conversation's subagent conversations — live rows from the activity feed
|
|
28381
|
+
* first, persisted children the feed has not seen this process after — with
|
|
28382
|
+
* Enter/t opening the child's full transcript in the shared read-only
|
|
28383
|
+
* document view (the same projection the exporter uses).
|
|
28384
|
+
*/
|
|
28385
|
+
function AgentsPanel({ live, load, readTranscript, close }) {
|
|
28386
|
+
const [dirRows, setDirRows] = (0, import_react.useState)(void 0);
|
|
28387
|
+
const [error, setError] = (0, import_react.useState)();
|
|
28388
|
+
const [loading, setLoading] = (0, import_react.useState)(true);
|
|
28389
|
+
const [cursor, setCursor] = (0, import_react.useState)(0);
|
|
28390
|
+
const [transcript, setTranscript] = (0, import_react.useState)();
|
|
28391
|
+
const transcriptLoad = (0, import_react.useRef)();
|
|
28392
|
+
(0, import_react.useEffect)(() => () => transcriptLoad.current?.abort(), []);
|
|
28393
|
+
const refresh = () => {
|
|
28394
|
+
setLoading(true);
|
|
28395
|
+
setError(void 0);
|
|
28396
|
+
Promise.resolve().then(load).then((value) => {
|
|
28397
|
+
setDirRows(value);
|
|
28398
|
+
setLoading(false);
|
|
28399
|
+
}, (reason) => {
|
|
28400
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
28401
|
+
setLoading(false);
|
|
28402
|
+
});
|
|
28403
|
+
};
|
|
28404
|
+
(0, import_react.useEffect)(refresh, []);
|
|
28405
|
+
const rows = (0, import_react.useMemo)(() => {
|
|
28406
|
+
const seen = new Set(live.map((row) => row.id));
|
|
28407
|
+
const feedRows = live.map((row) => ({
|
|
28408
|
+
id: row.id,
|
|
28409
|
+
label: row.label,
|
|
28410
|
+
activity: row.activity,
|
|
28411
|
+
running: row.state === "running",
|
|
28412
|
+
done: row.state === "done",
|
|
28413
|
+
live: true
|
|
28414
|
+
}));
|
|
28415
|
+
const persisted = (dirRows ?? []).filter((row) => !seen.has(row.id)).map((row) => ({
|
|
28416
|
+
id: row.id,
|
|
28417
|
+
label: row.title ?? row.id.slice(-12),
|
|
28418
|
+
activity: row.workspace,
|
|
28419
|
+
running: false,
|
|
28420
|
+
done: !row.live,
|
|
28421
|
+
live: row.live
|
|
28422
|
+
}));
|
|
28423
|
+
return [...feedRows, ...persisted];
|
|
28424
|
+
}, [live, dirRows]);
|
|
28425
|
+
(0, import_react.useEffect)(() => setCursor((value) => Math.min(value, Math.max(0, rows.length - 1))), [rows.length]);
|
|
28426
|
+
const openTranscript = () => {
|
|
28427
|
+
const row = rows[cursor];
|
|
28428
|
+
if (row === void 0) return;
|
|
28429
|
+
transcriptLoad.current?.abort();
|
|
28430
|
+
setTranscript({ id: row.id });
|
|
28431
|
+
const controller = new AbortController();
|
|
28432
|
+
transcriptLoad.current = controller;
|
|
28433
|
+
Promise.resolve().then(() => readTranscript(row.id, controller.signal)).then((text) => {
|
|
28434
|
+
if (!controller.signal.aborted) setTranscript({
|
|
28435
|
+
id: row.id,
|
|
28436
|
+
text
|
|
28437
|
+
});
|
|
28438
|
+
}, (reason) => {
|
|
28439
|
+
if (!controller.signal.aborted) setTranscript({
|
|
28440
|
+
id: row.id,
|
|
28441
|
+
error: reason instanceof Error ? reason.message : String(reason)
|
|
28442
|
+
});
|
|
28443
|
+
});
|
|
28444
|
+
};
|
|
28445
|
+
useInput((input, key) => {
|
|
28446
|
+
if (key.escape || input === "q") return close();
|
|
28447
|
+
if (input === "r") return refresh();
|
|
28448
|
+
if (key.upArrow) return setCursor((value) => rows.length === 0 ? 0 : (value + rows.length - 1) % rows.length);
|
|
28449
|
+
if (key.downArrow) return setCursor((value) => rows.length === 0 ? 0 : (value + 1) % rows.length);
|
|
28450
|
+
if ((key.return || input === "t") && rows[cursor] !== void 0) return openTranscript();
|
|
28451
|
+
}, { isActive: transcript === void 0 });
|
|
28452
|
+
if (transcript !== void 0) return (0, import_react.createElement)(DocumentPanel, {
|
|
28453
|
+
title: `subagent · ${transcript.id.slice(-12)}`,
|
|
28454
|
+
text: transcript.text,
|
|
28455
|
+
error: transcript.error,
|
|
28456
|
+
close: () => {
|
|
28457
|
+
transcriptLoad.current?.abort();
|
|
28458
|
+
setTranscript(void 0);
|
|
28459
|
+
}
|
|
28460
|
+
});
|
|
28461
|
+
return (0, import_react.createElement)(ListFrame, {
|
|
28462
|
+
title: `/agents · ${live.length} live · ${rows.length} total`,
|
|
28463
|
+
rows: rows.map((row) => ({
|
|
28464
|
+
key: row.id,
|
|
28465
|
+
text: `${row.running ? "●" : row.done ? "✓" : row.live ? "⏸" : "○"} ${row.label} · ${row.activity}${row.live ? " · live" : ""}`
|
|
28466
|
+
})),
|
|
28467
|
+
cursor,
|
|
28468
|
+
loading,
|
|
28469
|
+
...error === void 0 ? {} : { error },
|
|
28470
|
+
query: "",
|
|
28471
|
+
footer: "↑↓ choose · enter/t transcript · r refresh · esc close"
|
|
28472
|
+
});
|
|
28473
|
+
}
|
|
28474
|
+
/**
|
|
28475
|
+
* The /subagent model panel: which model configuration delegated subagents
|
|
28476
|
+
* run on. The kernel seeds child agents from the parent's CREATE-TIME
|
|
28477
|
+
* AgentOptions, so a mid-session /model switch would otherwise leave them on
|
|
28478
|
+
* the launch-time route; the TUI mirrors the selection onto subagent-origin
|
|
28479
|
+
* requests (or an explicit override picked here) via an agent/request
|
|
28480
|
+
* listener. The leading "inherit" row restores follow-the-current-model
|
|
28481
|
+
* behavior; picking a model with several advertised efforts opens the same
|
|
28482
|
+
* effort stage /model uses. Effort overrides are not offered separately —
|
|
28483
|
+
* the kernel's AgentOptions has no effort channel for children, so the level
|
|
28484
|
+
* rides the selected model exactly as /model applies it.
|
|
28485
|
+
*/
|
|
28486
|
+
function SubagentPanel({ current, load, pick, inherit, close }) {
|
|
28487
|
+
const [directory, setDirectory] = (0, import_react.useState)(void 0);
|
|
28488
|
+
const [error, setError] = (0, import_react.useState)();
|
|
28489
|
+
const [loading, setLoading] = (0, import_react.useState)(true);
|
|
28490
|
+
const [cursor, setCursor] = (0, import_react.useState)(0);
|
|
28491
|
+
const [effortFor, setEffortFor] = (0, import_react.useState)(void 0);
|
|
28492
|
+
const refresh = () => {
|
|
28493
|
+
setLoading(true);
|
|
28494
|
+
setError(void 0);
|
|
28495
|
+
Promise.resolve().then(load).then((value) => {
|
|
28496
|
+
setDirectory(value);
|
|
28497
|
+
setLoading(false);
|
|
28498
|
+
}, (reason) => {
|
|
28499
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
28500
|
+
setLoading(false);
|
|
28501
|
+
});
|
|
28502
|
+
};
|
|
28503
|
+
(0, import_react.useEffect)(refresh, []);
|
|
28504
|
+
const rows = (0, import_react.useMemo)(() => directory?.rows ?? [], [directory]);
|
|
28505
|
+
(0, import_react.useEffect)(() => {
|
|
28506
|
+
if (current === "" || rows.length === 0) return;
|
|
28507
|
+
const index = rows.findIndex((row) => current.startsWith(`${row.provider}/${row.model}`));
|
|
28508
|
+
if (index >= 0) setCursor(index + 1);
|
|
28509
|
+
}, [rows, current]);
|
|
28510
|
+
(0, import_react.useEffect)(() => setCursor((value) => Math.min(value, rows.length)), [rows.length]);
|
|
28511
|
+
useInput((input, key) => {
|
|
28512
|
+
if (effortFor !== void 0) return;
|
|
28513
|
+
if (key.escape || input === "q") return close();
|
|
28514
|
+
if (input === "r" && !loading) return refresh();
|
|
28515
|
+
if (key.upArrow) return setCursor((value) => (value + rows.length) % (rows.length + 1));
|
|
28516
|
+
if (key.downArrow) return setCursor((value) => (value + 1) % (rows.length + 1));
|
|
28517
|
+
if (key.return) {
|
|
28518
|
+
if (cursor === 0) return inherit();
|
|
28519
|
+
const row = rows[cursor - 1];
|
|
28520
|
+
if (row === void 0) return;
|
|
28521
|
+
if (row.reasoning !== void 0 && row.reasoning.efforts.length > 1) {
|
|
28522
|
+
setEffortFor(row);
|
|
28523
|
+
return;
|
|
28524
|
+
}
|
|
28525
|
+
pick(row, row.reasoning?.efforts.length === 1 ? row.reasoning.efforts[0].id : void 0);
|
|
28526
|
+
}
|
|
28527
|
+
});
|
|
28528
|
+
if (effortFor !== void 0) return (0, import_react.createElement)(EffortPanel, {
|
|
28529
|
+
row: effortFor,
|
|
28530
|
+
current: current === "" ? void 0 : current.split("@")[1],
|
|
28531
|
+
select: (effortId) => pick(effortFor, effortId),
|
|
28532
|
+
back: () => setEffortFor(void 0)
|
|
28533
|
+
});
|
|
28534
|
+
return (0, import_react.createElement)(ListFrame, {
|
|
28535
|
+
title: `/subagent — model for delegated agents${current === "" ? "" : ` · override ${current}`}`,
|
|
28536
|
+
rows: [{
|
|
28537
|
+
key: "__inherit__",
|
|
28538
|
+
text: `${current === "" ? "●" : "○"} inherit — follow the current model (/model switches apply)`
|
|
28539
|
+
}, ...rows.map((row) => ({
|
|
28540
|
+
key: `${row.provider}/${row.model}`,
|
|
28541
|
+
text: `${current.startsWith(`${row.provider}/${row.model}`) ? "●" : "○"} ${row.providerName} · ${row.modelName}`
|
|
28542
|
+
}))],
|
|
28543
|
+
cursor,
|
|
28544
|
+
loading,
|
|
28545
|
+
...error === void 0 ? {} : { error },
|
|
28546
|
+
query: "",
|
|
28547
|
+
footer: "↑↓ choose · enter apply · r refresh · esc close"
|
|
28055
28548
|
});
|
|
28056
28549
|
}
|
|
28057
28550
|
/** Encode one entry for the history file (JSON keeps multi-line drafts intact). */
|
|
@@ -28588,6 +29081,23 @@ function Header({ resumed }) {
|
|
|
28588
29081
|
function todoMark(status) {
|
|
28589
29082
|
return status === "completed" ? "✓" : status === "in_progress" ? "●" : "○";
|
|
28590
29083
|
}
|
|
29084
|
+
/**
|
|
29085
|
+
* One-row live subagent summary (the Codex agent status feed, compressed to
|
|
29086
|
+
* the transcript's budget): running count, total, and the most recently
|
|
29087
|
+
* active child's current activity. One line, never more — the full view is
|
|
29088
|
+
* the /agents panel.
|
|
29089
|
+
*/
|
|
29090
|
+
function AgentsLine({ rows }) {
|
|
29091
|
+
if (rows.length === 0) return void 0;
|
|
29092
|
+
const running = rows.filter((row) => row.state !== "done").length;
|
|
29093
|
+
const newest = [...rows].sort((left, right) => right.updatedAt - left.updatedAt)[0];
|
|
29094
|
+
const mark = newest.state === "done" ? "✓" : newest.state === "idle" ? "⏸" : "●";
|
|
29095
|
+
return (0, import_react.createElement)(Box, { paddingX: 1 }, (0, import_react.createElement)(Text, {
|
|
29096
|
+
color: inkColor(getPalette().brand),
|
|
29097
|
+
bold: true,
|
|
29098
|
+
wrap: "truncate-end"
|
|
29099
|
+
}, `agents ${running} live`, (0, import_react.createElement)(Text, { color: inkColor(getPalette().dim) }, ` · ${rows.length} total · /agents`), (0, import_react.createElement)(Text, { color: inkColor(getPalette().text) }, ` · ${mark} ${newest.label} ${newest.activity}`)));
|
|
29100
|
+
}
|
|
28591
29101
|
/** One-row todo summary: task count cannot grow the live Ink tree. */
|
|
28592
29102
|
function TodoPanel({ todos }) {
|
|
28593
29103
|
if (todos.length === 0) return void 0;
|
|
@@ -28692,9 +29202,10 @@ function statusToneProps(tone) {
|
|
|
28692
29202
|
*/
|
|
28693
29203
|
/** Theme anchors for the one-shot composer wave, read from the active palette
|
|
28694
29204
|
* so the wave stays coordinated in both themes. The flash tier runs the
|
|
28695
|
-
* brand blues; the deepseek
|
|
28696
|
-
* richer mix
|
|
28697
|
-
*
|
|
29205
|
+
* brand blues; the deepseek AND unknown tiers swap in the code sky-blue for
|
|
29206
|
+
* a brighter, richer mix (the unknown tier reuses the pro palette). Codex's
|
|
29207
|
+
* Wave bands carry no hue index (only hues[0] tints the row), so the accent
|
|
29208
|
+
* the prompt keeps is always hues[0]. */
|
|
28698
29209
|
function deepseekWaveHues(tier) {
|
|
28699
29210
|
const palette = getPalette();
|
|
28700
29211
|
return tier === "flash" ? [
|
|
@@ -28764,50 +29275,96 @@ function NoticeLine({ text, tone, columns }) {
|
|
|
28764
29275
|
wrap: "truncate-end"
|
|
28765
29276
|
}, truncateColumns(`${mark} ${singleLineText(text)}`, Math.max(1, columns - 2))));
|
|
28766
29277
|
}
|
|
28767
|
-
/** The
|
|
28768
|
-
|
|
29278
|
+
/** The fixed decision list; answers stay in the binary answerer vocabulary. */
|
|
29279
|
+
const APPROVAL_OPTIONS = [
|
|
29280
|
+
{
|
|
29281
|
+
key: "allow",
|
|
29282
|
+
label: "Yes, proceed",
|
|
29283
|
+
hotkey: "y"
|
|
29284
|
+
},
|
|
29285
|
+
{
|
|
29286
|
+
key: "reject-note",
|
|
29287
|
+
label: "No, and tell it what to do differently",
|
|
29288
|
+
hotkey: "n"
|
|
29289
|
+
},
|
|
29290
|
+
{
|
|
29291
|
+
key: "reject",
|
|
29292
|
+
label: "No, continue without running it",
|
|
29293
|
+
hotkey: "d"
|
|
29294
|
+
}
|
|
29295
|
+
];
|
|
29296
|
+
/**
|
|
29297
|
+
* The approval dialog (Codex ApprovalOverlay contract): a bold question
|
|
29298
|
+
* header, the bounded command body with an explicit overflow marker, a
|
|
29299
|
+
* numbered option list with a `›` cursor, single-key shortcuts, and digits
|
|
29300
|
+
* for direct selection. Askers queue FIFO — the count rides the header.
|
|
29301
|
+
* The upstream answerer vocabulary stays binary (`allowed-once` /
|
|
29302
|
+
* `rejected`): "tell it what to do differently" rejects and hands the
|
|
29303
|
+
* composer back with a hint notice, exactly Codex's decline-then-type flow.
|
|
29304
|
+
*/
|
|
29305
|
+
function ApprovalBar({ snapshot, locked, notify }) {
|
|
28769
29306
|
const stdout = useStdout().stdout;
|
|
28770
29307
|
const viewport = panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30);
|
|
28771
|
-
const [
|
|
29308
|
+
const [cursor, setCursor] = (0, import_react.useState)(0);
|
|
28772
29309
|
const pending = snapshot.pending;
|
|
28773
|
-
const active = !locked &&
|
|
28774
|
-
const
|
|
28775
|
-
const visibleScroll = clampScroll(scroll, content.length, viewport.bodyRows);
|
|
29310
|
+
const active = !locked && pending !== void 0 && !snapshot.answered;
|
|
29311
|
+
const body = (0, import_react.useMemo)(() => pending === void 0 || pending.command === "" ? [] : textLines(pending.command, viewport.contentColumns, "dim"), [pending, viewport.contentColumns]);
|
|
28776
29312
|
(0, import_react.useEffect)(() => {
|
|
28777
|
-
|
|
29313
|
+
setCursor(0);
|
|
28778
29314
|
}, [pending]);
|
|
28779
|
-
|
|
28780
|
-
|
|
28781
|
-
|
|
29315
|
+
const decide = (option) => {
|
|
29316
|
+
const ask = snapshot.pending;
|
|
29317
|
+
if (ask === void 0 || snapshot.answered) return;
|
|
29318
|
+
if (option.key === "allow") {
|
|
29319
|
+
ask.answer("allowed-once");
|
|
29320
|
+
return;
|
|
29321
|
+
}
|
|
29322
|
+
ask.answer("rejected");
|
|
29323
|
+
if (option.key === "reject-note") notify("rejected — type below what it should do differently (it steers the next step)", "warning");
|
|
29324
|
+
};
|
|
28782
29325
|
useInput((input, key) => {
|
|
28783
|
-
if (snapshot.pending === void 0) return;
|
|
29326
|
+
if (snapshot.pending === void 0 || snapshot.answered) return;
|
|
28784
29327
|
if (key.upArrow) {
|
|
28785
|
-
|
|
29328
|
+
setCursor((current) => (current + APPROVAL_OPTIONS.length - 1) % APPROVAL_OPTIONS.length);
|
|
28786
29329
|
return;
|
|
28787
29330
|
}
|
|
28788
29331
|
if (key.downArrow) {
|
|
28789
|
-
|
|
29332
|
+
setCursor((current) => (current + 1) % APPROVAL_OPTIONS.length);
|
|
28790
29333
|
return;
|
|
28791
29334
|
}
|
|
28792
|
-
if (key.
|
|
28793
|
-
|
|
29335
|
+
if (key.return) {
|
|
29336
|
+
decide(APPROVAL_OPTIONS[cursor]);
|
|
28794
29337
|
return;
|
|
28795
29338
|
}
|
|
28796
|
-
if (key.
|
|
28797
|
-
|
|
29339
|
+
if (key.escape) {
|
|
29340
|
+
decide(APPROVAL_OPTIONS[2]);
|
|
28798
29341
|
return;
|
|
28799
29342
|
}
|
|
28800
|
-
if (snapshot.answered) return;
|
|
28801
29343
|
if (input === "y" || input === "Y") {
|
|
28802
|
-
|
|
29344
|
+
decide(APPROVAL_OPTIONS[0]);
|
|
29345
|
+
return;
|
|
29346
|
+
}
|
|
29347
|
+
if (input === "n" || input === "N") {
|
|
29348
|
+
decide(APPROVAL_OPTIONS[1]);
|
|
29349
|
+
return;
|
|
29350
|
+
}
|
|
29351
|
+
if (input === "d" || input === "D") {
|
|
29352
|
+
decide(APPROVAL_OPTIONS[2]);
|
|
28803
29353
|
return;
|
|
28804
29354
|
}
|
|
28805
|
-
if (input
|
|
29355
|
+
if (/^[1-9]$/u.test(input)) {
|
|
29356
|
+
const index = Number(input) - 1;
|
|
29357
|
+
if (index < APPROVAL_OPTIONS.length) decide(APPROVAL_OPTIONS[index]);
|
|
29358
|
+
}
|
|
28806
29359
|
}, { isActive: active });
|
|
28807
|
-
if (
|
|
29360
|
+
if (pending === void 0) return void 0;
|
|
28808
29361
|
if (viewport.maxHeight === 0) return (0, import_react.createElement)(Box, { display: "none" });
|
|
28809
|
-
|
|
28810
|
-
|
|
29362
|
+
const queuedSuffix = snapshot.queued > 0 ? ` · +${snapshot.queued} queued` : "";
|
|
29363
|
+
if (viewport.compact) return (0, import_react.createElement)(Text, { wrap: "truncate-end" }, truncateColumns(`approval${queuedSuffix} · enter/y allow · esc/n reject`, viewport.contentColumns));
|
|
29364
|
+
const reservedRows = 3 + APPROVAL_OPTIONS.length;
|
|
29365
|
+
const bodyBudget = Math.max(1, viewport.bodyRows - reservedRows);
|
|
29366
|
+
const visibleBody = body.slice(0, bodyBudget);
|
|
29367
|
+
const overflow = body.length - visibleBody.length;
|
|
28811
29368
|
return (0, import_react.createElement)(Box, {
|
|
28812
29369
|
flexDirection: "column",
|
|
28813
29370
|
width: viewport.outerColumns,
|
|
@@ -28818,10 +29375,25 @@ function ApprovalBar({ snapshot, locked }) {
|
|
|
28818
29375
|
color: inkColor(getPalette().warn),
|
|
28819
29376
|
bold: true,
|
|
28820
29377
|
wrap: "truncate-end"
|
|
28821
|
-
}, truncateColumns(
|
|
28822
|
-
|
|
29378
|
+
}, truncateColumns(`${pending.headline}${queuedSuffix}`, viewport.contentColumns)), (0, import_react.createElement)(PanelGap, { visible: viewport.gapRows > 0 && body.length > 0 }), ...visibleBody.map((line, index) => (0, import_react.createElement)(StyledRows, {
|
|
29379
|
+
key: `body-${index}`,
|
|
29380
|
+
lines: [line]
|
|
29381
|
+
})), ...overflow > 0 ? [(0, import_react.createElement)(Text, {
|
|
29382
|
+
key: "overflow",
|
|
29383
|
+
color: inkColor(getPalette().dim),
|
|
29384
|
+
wrap: "truncate-end"
|
|
29385
|
+
}, truncateColumns(`… +${overflow} more lines · ctrl+o shows the full call in the transcript`, viewport.contentColumns))] : [], ...body.length > 0 ? [(0, import_react.createElement)(PanelGap, { visible: viewport.gapRows > 0 })] : [], ...APPROVAL_OPTIONS.map((option, index) => {
|
|
29386
|
+
const selected = !snapshot.answered && index === cursor;
|
|
29387
|
+
return (0, import_react.createElement)(Text, {
|
|
29388
|
+
key: option.key,
|
|
29389
|
+
color: selected ? inkColor(getPalette().brandBright) : inkColor(getPalette().text),
|
|
29390
|
+
bold: selected || void 0,
|
|
29391
|
+
wrap: "truncate-end"
|
|
29392
|
+
}, truncateColumns(`${selected ? "›" : " "} ${index + 1}. ${option.label} (${option.hotkey})`, viewport.contentColumns));
|
|
29393
|
+
}), (0, import_react.createElement)(Text, {
|
|
29394
|
+
color: inkColor(getPalette().dim),
|
|
28823
29395
|
wrap: "truncate-end"
|
|
28824
|
-
},
|
|
29396
|
+
}, truncateColumns(snapshot.answered ? "submitted…" : "↑↓ choose · enter confirm · y/n/d quick · esc reject", viewport.contentColumns)));
|
|
28825
29397
|
}
|
|
28826
29398
|
/**
|
|
28827
29399
|
* The ask_user_question bar: walks one request question by question,
|
|
@@ -29067,18 +29639,31 @@ function QuestionBar({ store, snapshot, locked }) {
|
|
|
29067
29639
|
}, dim(truncateColumns(footer, viewport.contentColumns))));
|
|
29068
29640
|
}
|
|
29069
29641
|
/** The /model panel: a scrolling list over the advisory model directory. */
|
|
29070
|
-
function ModelPanel({ directory, error, onSelect, onProviders, onRetry, onClose }) {
|
|
29642
|
+
function ModelPanel({ directory, error, current, onSelect, onProviders, onRetry, onClose }) {
|
|
29071
29643
|
const [cursor, setCursor] = (0, import_react.useState)(0);
|
|
29072
29644
|
const stdout = useStdout().stdout;
|
|
29073
29645
|
const viewport = panelViewport(stdout?.columns ?? 80, stdout?.rows ?? 30);
|
|
29074
29646
|
const rows = directory?.rows ?? [];
|
|
29647
|
+
const positioned = (0, import_react.useRef)(false);
|
|
29075
29648
|
(0, import_react.useEffect)(() => {
|
|
29076
|
-
if (rows.length === 0) {
|
|
29077
|
-
if (
|
|
29649
|
+
if (positioned.current || rows.length === 0 || current === void 0) {
|
|
29650
|
+
if (rows.length === 0) {
|
|
29651
|
+
if (cursor !== 0) setCursor(0);
|
|
29652
|
+
return;
|
|
29653
|
+
}
|
|
29654
|
+
if (cursor >= rows.length) setCursor(rows.length - 1);
|
|
29078
29655
|
return;
|
|
29079
29656
|
}
|
|
29080
|
-
|
|
29081
|
-
|
|
29657
|
+
const index = rows.findIndex((row) => `${row.provider}/${row.model}` === current);
|
|
29658
|
+
if (index >= 0) {
|
|
29659
|
+
positioned.current = true;
|
|
29660
|
+
setCursor(index);
|
|
29661
|
+
} else if (cursor >= rows.length) setCursor(Math.max(0, rows.length - 1));
|
|
29662
|
+
}, [
|
|
29663
|
+
rows,
|
|
29664
|
+
cursor,
|
|
29665
|
+
current
|
|
29666
|
+
]);
|
|
29082
29667
|
useInput((input, key) => {
|
|
29083
29668
|
if (key.escape || input === "q") {
|
|
29084
29669
|
onClose();
|
|
@@ -29518,6 +30103,9 @@ function HelpPanel({ descriptors, skills, commandError, skillError, onClose }) {
|
|
|
29518
30103
|
(0, import_react.createElement)(Box, { key: "local-statusline" }, row("/statusline", "customize the status line items")),
|
|
29519
30104
|
(0, import_react.createElement)(Box, { key: "local-theme" }, row("/theme", "switch the color theme")),
|
|
29520
30105
|
(0, import_react.createElement)(Box, { key: "local-history" }, row("/history", "search and recall past prompts")),
|
|
30106
|
+
(0, import_react.createElement)(Box, { key: "local-agents" }, row("/agents", "inspect subagent sessions of this conversation")),
|
|
30107
|
+
(0, import_react.createElement)(Box, { key: "local-subagent" }, row("/subagent", "choose the model delegated subagents run on")),
|
|
30108
|
+
(0, import_react.createElement)(Box, { key: "local-delete" }, row("/delete", "delete a session and its subagent threads")),
|
|
29521
30109
|
(0, import_react.createElement)(Box, { key: "local-clear" }, row("/clear", "clear the screen")),
|
|
29522
30110
|
(0, import_react.createElement)(Box, { key: "local-export" }, row("/export", "export the transcript to markdown (/export [path])")),
|
|
29523
30111
|
(0, import_react.createElement)(Box, { key: "local-title" }, row("/title", "rename this session (/title <text>)")),
|
|
@@ -29819,6 +30407,21 @@ function completionCandidates(value, descriptors, skills) {
|
|
|
29819
30407
|
description: "search and recall past prompts",
|
|
29820
30408
|
origin: "command"
|
|
29821
30409
|
},
|
|
30410
|
+
{
|
|
30411
|
+
label: "/agents",
|
|
30412
|
+
description: "inspect subagent sessions of this conversation",
|
|
30413
|
+
origin: "command"
|
|
30414
|
+
},
|
|
30415
|
+
{
|
|
30416
|
+
label: "/subagent",
|
|
30417
|
+
description: "choose the model delegated subagents run on",
|
|
30418
|
+
origin: "command"
|
|
30419
|
+
},
|
|
30420
|
+
{
|
|
30421
|
+
label: "/delete",
|
|
30422
|
+
description: "delete a session and its subagent threads",
|
|
30423
|
+
origin: "command"
|
|
30424
|
+
},
|
|
29822
30425
|
{
|
|
29823
30426
|
label: "/clear",
|
|
29824
30427
|
description: "clear the screen",
|
|
@@ -29920,7 +30523,7 @@ function CompletionMenu({ active, mention, index, rows }) {
|
|
|
29920
30523
|
* While a modal (approval / question / model panel) owns the keys, the
|
|
29921
30524
|
* box passes every key through untouched.
|
|
29922
30525
|
*/
|
|
29923
|
-
function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, interrupt, quit, openModel, openEffort, openHelp, openMode, openPermission, openResume, openPlugin, openStatusline, openTheme, openHistory, createSession, cancelSessionSwitch, notify, hasNotice, dismissNotice, toggleReasoning, openVerbose, clearView, refresh, loadMentions, cyclePermission, exportTranscript, renameTitle, recallSpace, recordLocal, recordHistory, queued, cancelQueued, historyFill, historyConsumed, waveTier, waveStyle }) {
|
|
30526
|
+
function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, interrupt, quit, openModel, openEffort, openHelp, openMode, openPermission, openResume, openPlugin, openStatusline, openTheme, openHistory, openAgents, openSubagent, openDelete, deleteConfirm, confirmDelete, cancelDelete, createSession, cancelSessionSwitch, notify, hasNotice, dismissNotice, toggleReasoning, openVerbose, clearView, refresh, loadMentions, cyclePermission, exportTranscript, renameTitle, recallSpace, recordLocal, recordHistory, queued, cancelQueued, historyFill, historyConsumed, waveTier, waveStyle }) {
|
|
29924
30527
|
const columns = useStdout().stdout?.columns ?? 80;
|
|
29925
30528
|
const [value, setValue] = (0, import_react.useState)("");
|
|
29926
30529
|
const [cursor, setCursor] = (0, import_react.useState)(0);
|
|
@@ -30015,8 +30618,39 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30015
30618
|
description: row.description,
|
|
30016
30619
|
origin: "path"
|
|
30017
30620
|
})) : candidates;
|
|
30621
|
+
/** Accept the highlighted completion-menu candidate into the draft. */
|
|
30622
|
+
const acceptMenuCandidate = () => {
|
|
30623
|
+
if (mentionActive && mentionToken !== void 0) {
|
|
30624
|
+
const row = mentionRows[completionIndex % mentionRows.length];
|
|
30625
|
+
if (row !== void 0) {
|
|
30626
|
+
const insertion = row.label.startsWith("@") ? row.label : `@${row.label}${row.kind === "directory" ? "/" : ""}`;
|
|
30627
|
+
setValue(value.slice(0, mentionToken.start) + insertion + value.slice(cursor));
|
|
30628
|
+
setCursor(mentionToken.start + insertion.length);
|
|
30629
|
+
}
|
|
30630
|
+
} else if (pathActive) {
|
|
30631
|
+
const row = pathRows[completionIndex % Math.max(1, pathRows.length)];
|
|
30632
|
+
if (row !== void 0) {
|
|
30633
|
+
const insertion = row.kind === "directory" ? `${row.label}/` : row.label;
|
|
30634
|
+
setValue(value.slice(0, pathTokenStart) + insertion + value.slice(cursor));
|
|
30635
|
+
setCursor(pathTokenStart + insertion.length);
|
|
30636
|
+
}
|
|
30637
|
+
} else {
|
|
30638
|
+
const candidate = candidates[completionIndex % candidates.length];
|
|
30639
|
+
if (candidate !== void 0) {
|
|
30640
|
+
setValue(`${candidate.label} `);
|
|
30641
|
+
setCursor(candidate.label.length + 1);
|
|
30642
|
+
}
|
|
30643
|
+
}
|
|
30644
|
+
setCompletionIndex(0);
|
|
30645
|
+
setDismissedMenuValue(void 0);
|
|
30646
|
+
};
|
|
30018
30647
|
useInput((input, key) => {
|
|
30019
30648
|
if (!active) return;
|
|
30649
|
+
if (deleteConfirm !== void 0) {
|
|
30650
|
+
if (input === "y" || input === "Y") confirmDelete();
|
|
30651
|
+
else cancelDelete();
|
|
30652
|
+
return;
|
|
30653
|
+
}
|
|
30020
30654
|
if (key.tab && key.shift) {
|
|
30021
30655
|
try {
|
|
30022
30656
|
const next = cyclePermission();
|
|
@@ -30072,6 +30706,12 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30072
30706
|
setDismissedMenuValue(void 0);
|
|
30073
30707
|
return;
|
|
30074
30708
|
}
|
|
30709
|
+
if (menuActive) {
|
|
30710
|
+
if (!(!mentionActive && !pathActive && candidates.some((candidate) => candidate.label === value))) {
|
|
30711
|
+
acceptMenuCandidate();
|
|
30712
|
+
return;
|
|
30713
|
+
}
|
|
30714
|
+
}
|
|
30075
30715
|
const text = value.trim();
|
|
30076
30716
|
setValue("");
|
|
30077
30717
|
setCursor(0);
|
|
@@ -30162,6 +30802,18 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30162
30802
|
openHistory();
|
|
30163
30803
|
return;
|
|
30164
30804
|
}
|
|
30805
|
+
if (text === "/agents") {
|
|
30806
|
+
openAgents();
|
|
30807
|
+
return;
|
|
30808
|
+
}
|
|
30809
|
+
if (text === "/subagent") {
|
|
30810
|
+
openSubagent();
|
|
30811
|
+
return;
|
|
30812
|
+
}
|
|
30813
|
+
if (text === "/delete" || text.startsWith("/delete ")) {
|
|
30814
|
+
openDelete(text.slice(7).trim());
|
|
30815
|
+
return;
|
|
30816
|
+
}
|
|
30165
30817
|
if (busy && !text.startsWith("/")) {
|
|
30166
30818
|
steer(text);
|
|
30167
30819
|
return;
|
|
@@ -30200,29 +30852,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30200
30852
|
return;
|
|
30201
30853
|
}
|
|
30202
30854
|
if (key.tab && menuActive) {
|
|
30203
|
-
|
|
30204
|
-
const row = mentionRows[completionIndex % mentionRows.length];
|
|
30205
|
-
if (row !== void 0) {
|
|
30206
|
-
const insertion = row.label.startsWith("@") ? row.label : `@${row.label}${row.kind === "directory" ? "/" : ""}`;
|
|
30207
|
-
setValue(value.slice(0, mentionToken.start) + insertion + value.slice(cursor));
|
|
30208
|
-
setCursor(mentionToken.start + insertion.length);
|
|
30209
|
-
}
|
|
30210
|
-
} else if (pathActive) {
|
|
30211
|
-
const row = pathRows[completionIndex % Math.max(1, pathRows.length)];
|
|
30212
|
-
if (row !== void 0) {
|
|
30213
|
-
const insertion = row.kind === "directory" ? `${row.label}/` : row.label;
|
|
30214
|
-
setValue(value.slice(0, pathTokenStart) + insertion + value.slice(cursor));
|
|
30215
|
-
setCursor(pathTokenStart + insertion.length);
|
|
30216
|
-
}
|
|
30217
|
-
} else {
|
|
30218
|
-
const candidate = candidates[completionIndex % candidates.length];
|
|
30219
|
-
if (candidate !== void 0) {
|
|
30220
|
-
setValue(`${candidate.label} `);
|
|
30221
|
-
setCursor(candidate.label.length + 1);
|
|
30222
|
-
}
|
|
30223
|
-
}
|
|
30224
|
-
setCompletionIndex(0);
|
|
30225
|
-
setDismissedMenuValue(void 0);
|
|
30855
|
+
acceptMenuCandidate();
|
|
30226
30856
|
return;
|
|
30227
30857
|
}
|
|
30228
30858
|
if (key.backspace || key.delete) {
|
|
@@ -30311,6 +30941,18 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30311
30941
|
const promptColor = tierHues === null ? inkColor(getPalette().brand) : inkColor(tierHues[0]);
|
|
30312
30942
|
const promptGlyph = waveTier === "flash" ? "›" : waveTier === "deepseek" ? "»" : "❯";
|
|
30313
30943
|
if (frozen) {
|
|
30944
|
+
if (deleteConfirm !== void 0) return (0, import_react.createElement)(Box, {
|
|
30945
|
+
width: Math.max(1, columns - 1),
|
|
30946
|
+
borderStyle: "round",
|
|
30947
|
+
borderColor: inkColor(getPalette().warn),
|
|
30948
|
+
paddingX: 1
|
|
30949
|
+
}, (0, import_react.createElement)(Text, { wrap: "truncate-end" }, (0, import_react.createElement)(Text, {
|
|
30950
|
+
color: inkColor(getPalette().warn),
|
|
30951
|
+
bold: true
|
|
30952
|
+
}, "❯ "), (0, import_react.createElement)(Text, {
|
|
30953
|
+
color: inkColor(getPalette().warn),
|
|
30954
|
+
bold: true
|
|
30955
|
+
}, "y delete · any other key cancels")));
|
|
30314
30956
|
const frozen = value === "" ? "type a message" : verboseLine(value, Math.max(1, columns - 6));
|
|
30315
30957
|
return (0, import_react.createElement)(Box, {
|
|
30316
30958
|
width: Math.max(1, columns - 1),
|
|
@@ -30384,17 +31026,17 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30384
31026
|
backgroundColor: waveBg(cells.length)
|
|
30385
31027
|
});
|
|
30386
31028
|
if (deepseekWaveWordVisible(waveTick, waveTier, style)) {
|
|
30387
|
-
const word = "deepseek";
|
|
30388
|
-
const start = Math.max(2, Math.floor((contentWidth -
|
|
31029
|
+
const word = waveTier === "unknown" ? "Into the Unknown" : "deepseek";
|
|
31030
|
+
const start = Math.max(2, Math.floor((contentWidth - word.length) / 2));
|
|
30389
31031
|
let clear = true;
|
|
30390
|
-
for (let at = 0; at <
|
|
31032
|
+
for (let at = 0; at < word.length; at += 1) {
|
|
30391
31033
|
const cell = cells[start + at];
|
|
30392
31034
|
if (cell === void 0 || cell.char !== " " && cell.dim !== true) {
|
|
30393
31035
|
clear = false;
|
|
30394
31036
|
break;
|
|
30395
31037
|
}
|
|
30396
31038
|
}
|
|
30397
|
-
if (clear) for (let at = 0; at <
|
|
31039
|
+
if (clear) for (let at = 0; at < word.length; at += 1) {
|
|
30398
31040
|
const cell = cells[start + at];
|
|
30399
31041
|
cell.char = word[at];
|
|
30400
31042
|
cell.color = inkColor(deepseekWaveWordHue(at, hues));
|
|
@@ -30402,7 +31044,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
30402
31044
|
cell.dim = false;
|
|
30403
31045
|
}
|
|
30404
31046
|
}
|
|
30405
|
-
if (waveTier === "deepseek" && style === "wave") {
|
|
31047
|
+
if ((waveTier === "deepseek" || waveTier === "unknown") && style === "wave") {
|
|
30406
31048
|
const spark = deepseekWaveSpark(waveTick);
|
|
30407
31049
|
if (spark !== null) {
|
|
30408
31050
|
const last = cells[cells.length - 1];
|
|
@@ -30573,16 +31215,18 @@ function App(props) {
|
|
|
30573
31215
|
const [effortFor, setEffortFor] = (0, import_react.useState)(void 0);
|
|
30574
31216
|
/** Effective reasoning effort, shown in the /model picker and switch notice. */
|
|
30575
31217
|
const [effortLabel, setEffortLabel] = (0, import_react.useState)(props.effort);
|
|
30576
|
-
/** DeepSeek easter egg: switching INTO an official DeepSeek route
|
|
30577
|
-
*
|
|
30578
|
-
*
|
|
30579
|
-
*
|
|
30580
|
-
*
|
|
30581
|
-
*
|
|
30582
|
-
*
|
|
30583
|
-
*
|
|
30584
|
-
*
|
|
30585
|
-
*
|
|
31218
|
+
/** DeepSeek easter egg: switching INTO an official DeepSeek route — or
|
|
31219
|
+
* onto a NON-DeepSeek model running a reasoning effort strictly above
|
|
31220
|
+
* high — plays one of Codex's three ignition styles (Wave / Aurora /
|
|
31221
|
+
* Pulse, picked at random without repeating) across the composer's
|
|
31222
|
+
* padded band (33ms tick, per-style durations), then the band returns
|
|
31223
|
+
* to static while the prompt marker keeps the tier accent. The trigger
|
|
31224
|
+
* follows the applied model label (what the status bar actually shows),
|
|
31225
|
+
* never the initial paint, and the tier is derived from the label and
|
|
31226
|
+
* cached at the switch. The 33ms tick itself lives inside Input, so the
|
|
31227
|
+
* sweep re-renders only the composer row, not the whole tree, at 30fps;
|
|
31228
|
+
* App owns the rarely-changing tier/style and Input starts the sweep
|
|
31229
|
+
* whenever that pair changes. */
|
|
30586
31230
|
const [waveTier, setWaveTier] = (0, import_react.useState)(null);
|
|
30587
31231
|
const [waveStyle, setWaveStyle] = (0, import_react.useState)(null);
|
|
30588
31232
|
const previousModel = (0, import_react.useRef)(void 0);
|
|
@@ -30594,13 +31238,15 @@ function App(props) {
|
|
|
30594
31238
|
const effortChanged = previousEffort.current !== effortLabel;
|
|
30595
31239
|
previousEffort.current = effortLabel;
|
|
30596
31240
|
const modelChanged = previous !== void 0 && previous !== modelLabel;
|
|
30597
|
-
|
|
31241
|
+
const official = isOfficialDeepSeekLabel(modelLabel);
|
|
31242
|
+
const unknownTrigger = !official && effortAboveHigh(effortLabel);
|
|
31243
|
+
if (!official && !unknownTrigger) {
|
|
30598
31244
|
setWaveTier(null);
|
|
30599
31245
|
setWaveStyle(null);
|
|
30600
31246
|
return;
|
|
30601
31247
|
}
|
|
30602
31248
|
if (modelChanged || effortChanged) {
|
|
30603
|
-
setWaveTier(deepseekWaveTier(modelLabel));
|
|
31249
|
+
setWaveTier(official ? deepseekWaveTier(modelLabel) : "unknown");
|
|
30604
31250
|
const nextStyle = deepseekWaveStyleRandom(previousStyle.current);
|
|
30605
31251
|
previousStyle.current = nextStyle;
|
|
30606
31252
|
setWaveStyle(nextStyle);
|
|
@@ -30679,6 +31325,36 @@ function App(props) {
|
|
|
30679
31325
|
const [statuslineItems, setStatuslineItems] = (0, import_react.useState)(() => parseStatuslineItems(props.statusline));
|
|
30680
31326
|
const [themeOpen, setThemeOpen] = (0, import_react.useState)(false);
|
|
30681
31327
|
const [historyOpen, setHistoryOpen] = (0, import_react.useState)(false);
|
|
31328
|
+
const [agentsOpen, setAgentsOpen] = (0, import_react.useState)(false);
|
|
31329
|
+
const [subagentOpen, setSubagentOpen] = (0, import_react.useState)(false);
|
|
31330
|
+
/** /delete state: delete-mode hint plus an optional pre-armed row id. */
|
|
31331
|
+
const [resumeDelete, setResumeDelete] = (0, import_react.useState)({ mode: false });
|
|
31332
|
+
/** The row id awaiting y/n in the COMPOSER (codex delete confirm): the
|
|
31333
|
+
* composer takes the keys, the resume panel yields until it settles. */
|
|
31334
|
+
const [deleteConfirmId, setDeleteConfirmId] = (0, import_react.useState)(void 0);
|
|
31335
|
+
/** Bumped after a deletion so the /resume listing reloads immediately. */
|
|
31336
|
+
const [deleteReloadToken, setDeleteReloadToken] = (0, import_react.useState)(0);
|
|
31337
|
+
const requestDelete = (0, import_react.useCallback)((row) => {
|
|
31338
|
+
setDeleteConfirmId(row.id);
|
|
31339
|
+
}, []);
|
|
31340
|
+
const cancelDelete = (0, import_react.useCallback)(() => {
|
|
31341
|
+
setDeleteConfirmId(void 0);
|
|
31342
|
+
}, []);
|
|
31343
|
+
const confirmDelete = (0, import_react.useCallback)(() => {
|
|
31344
|
+
const id = deleteConfirmId;
|
|
31345
|
+
if (id === void 0) return;
|
|
31346
|
+
setDeleteConfirmId(void 0);
|
|
31347
|
+
props.deleteSession(id).then((outcome) => {
|
|
31348
|
+
notify(outcome);
|
|
31349
|
+
setDeleteReloadToken((token) => token + 1);
|
|
31350
|
+
}, (reason) => {
|
|
31351
|
+
notify(`delete failed: ${reason instanceof Error ? reason.message : String(reason)}`, "error");
|
|
31352
|
+
});
|
|
31353
|
+
}, [
|
|
31354
|
+
deleteConfirmId,
|
|
31355
|
+
props.deleteSession,
|
|
31356
|
+
notify
|
|
31357
|
+
]);
|
|
30682
31358
|
/** The /history panel's accepted entry: text plus its recall-space index. */
|
|
30683
31359
|
const [historyFill, setHistoryFill] = (0, import_react.useState)(void 0);
|
|
30684
31360
|
/** Submissions recorded in this process (Codex local history; persistent file stays in the runner). */
|
|
@@ -30712,9 +31388,10 @@ function App(props) {
|
|
|
30712
31388
|
const [refreshEpoch, setRefreshEpoch] = (0, import_react.useState)(0);
|
|
30713
31389
|
const approvalSnapshot = (0, import_react.useSyncExternalStore)(props.approval.subscribe, props.approval.getSnapshot);
|
|
30714
31390
|
const questionSnapshot = (0, import_react.useSyncExternalStore)(props.questions.subscribe, props.questions.getSnapshot);
|
|
31391
|
+
const agentRows = (0, import_react.useSyncExternalStore)(props.subagents.subscribe, props.subagents.getSnapshot);
|
|
30715
31392
|
const approvalPending = approvalSnapshot.pending !== void 0;
|
|
30716
31393
|
const questionPending = questionSnapshot.pending !== void 0;
|
|
30717
|
-
const inputActive = !modelOpen && !helpOpen && !modeOpen && !permissionOpen && !resumeOpen && !pluginOpen && !statuslineOpen && !themeOpen && !historyOpen && !verboseOpen && !approvalPending && !questionPending;
|
|
31394
|
+
const inputActive = deleteConfirmId !== void 0 ? !approvalPending && !questionPending : !modelOpen && !helpOpen && !modeOpen && !permissionOpen && !resumeOpen && !pluginOpen && !statuslineOpen && !themeOpen && !historyOpen && !agentsOpen && !subagentOpen && !verboseOpen && !approvalPending && !questionPending;
|
|
30718
31395
|
(0, import_react.useEffect)(() => {
|
|
30719
31396
|
if (!approvalPending && !questionPending) return;
|
|
30720
31397
|
setModelOpen(false);
|
|
@@ -30729,6 +31406,9 @@ function App(props) {
|
|
|
30729
31406
|
setStatuslineOpen(false);
|
|
30730
31407
|
setThemeOpen(false);
|
|
30731
31408
|
setHistoryOpen(false);
|
|
31409
|
+
setAgentsOpen(false);
|
|
31410
|
+
setSubagentOpen(false);
|
|
31411
|
+
setDeleteConfirmId(void 0);
|
|
30732
31412
|
setVerboseOpen(false);
|
|
30733
31413
|
}, [approvalPending, questionPending]);
|
|
30734
31414
|
const settledRowsCache = (0, import_react.useRef)(void 0);
|
|
@@ -30788,8 +31468,8 @@ function App(props) {
|
|
|
30788
31468
|
const streamRows = Math.max(1, dynamicRows - visibleLiveLines.length);
|
|
30789
31469
|
const reasoningRows = view.streamingReasoning === "" ? 0 : view.streaming === "" ? streamRows : streamRows <= 1 ? 0 : showReasoning ? Math.max(1, Math.floor(streamRows / 3)) : 1;
|
|
30790
31470
|
const answerRows = view.streaming === "" ? 0 : Math.max(1, streamRows - reasoningRows);
|
|
30791
|
-
const transcriptVisible = !modelOpen && !helpOpen && !modeOpen && !permissionOpen && !resumeOpen && !pluginOpen && !statuslineOpen && !themeOpen && !historyOpen && !verboseOpen && !approvalPending && !questionPending;
|
|
30792
|
-
const modalVisible = modelOpen || helpOpen || modeOpen || permissionOpen || resumeOpen || pluginOpen || statuslineOpen || themeOpen || historyOpen || verboseOpen && !approvalPending && !questionPending || approvalPending || questionPending;
|
|
31471
|
+
const transcriptVisible = !modelOpen && !helpOpen && !modeOpen && !permissionOpen && !resumeOpen && !pluginOpen && !statuslineOpen && !themeOpen && !historyOpen && !agentsOpen && !subagentOpen && !verboseOpen && !approvalPending && !questionPending;
|
|
31472
|
+
const modalVisible = modelOpen || helpOpen || modeOpen || permissionOpen || resumeOpen || pluginOpen || statuslineOpen || themeOpen || historyOpen || agentsOpen || subagentOpen || verboseOpen && !approvalPending && !questionPending || approvalPending || questionPending;
|
|
30793
31473
|
const closeInspector = (0, import_react.useCallback)(() => {
|
|
30794
31474
|
setVerboseOpen(false);
|
|
30795
31475
|
}, []);
|
|
@@ -30898,6 +31578,7 @@ function App(props) {
|
|
|
30898
31578
|
onBack: () => setProviderOpen(false)
|
|
30899
31579
|
});
|
|
30900
31580
|
else if (effortFor !== void 0) modelSurface = (0, import_react.createElement)(EffortPanel, {
|
|
31581
|
+
key: `${effortFor.provider}/${effortFor.model}`,
|
|
30901
31582
|
row: effortFor,
|
|
30902
31583
|
current: effortLabel,
|
|
30903
31584
|
select: (effortId) => applyModel(effortFor, effortId),
|
|
@@ -30906,6 +31587,7 @@ function App(props) {
|
|
|
30906
31587
|
else modelSurface = (0, import_react.createElement)(ModelPanel, {
|
|
30907
31588
|
directory,
|
|
30908
31589
|
error: modelError,
|
|
31590
|
+
current: modelLabel,
|
|
30909
31591
|
onSelect: (row) => {
|
|
30910
31592
|
if (row.reasoning !== void 0 && row.reasoning.efforts.length > 1) {
|
|
30911
31593
|
setEffortFor(row);
|
|
@@ -30936,13 +31618,14 @@ function App(props) {
|
|
|
30936
31618
|
dim: false,
|
|
30937
31619
|
maxRows: answerRows,
|
|
30938
31620
|
prefix: " "
|
|
30939
|
-
}, busy ? (0, import_react.createElement)(Caret) : void 0) : void 0, deepDivingVisible ? (0, import_react.createElement)(DeepDivingLine, { since: view.busySince }) : void 0) : void 0, transcriptVisible ? (0, import_react.createElement)(TodoPanel, { todos: view.todos }) : void 0, (0, import_react.createElement)(QuestionBar, {
|
|
31621
|
+
}, busy ? (0, import_react.createElement)(Caret) : void 0) : void 0, deepDivingVisible ? (0, import_react.createElement)(DeepDivingLine, { since: view.busySince }) : void 0) : void 0, transcriptVisible ? (0, import_react.createElement)(TodoPanel, { todos: view.todos }) : void 0, transcriptVisible ? (0, import_react.createElement)(AgentsLine, { rows: agentRows }) : void 0, (0, import_react.createElement)(QuestionBar, {
|
|
30940
31622
|
store: props.questions,
|
|
30941
31623
|
snapshot: questionSnapshot,
|
|
30942
31624
|
locked: false
|
|
30943
31625
|
}), (0, import_react.createElement)(ApprovalBar, {
|
|
30944
31626
|
snapshot: approvalSnapshot,
|
|
30945
|
-
locked: questionPending
|
|
31627
|
+
locked: questionPending,
|
|
31628
|
+
notify
|
|
30946
31629
|
}), modelSurface, helpOpen && !approvalPending && !questionPending ? (0, import_react.createElement)(HelpPanel, {
|
|
30947
31630
|
descriptors,
|
|
30948
31631
|
skills,
|
|
@@ -30981,6 +31664,10 @@ function App(props) {
|
|
|
30981
31664
|
currentCwd: props.workspaceRoot,
|
|
30982
31665
|
load: props.loadSessions,
|
|
30983
31666
|
readTranscript: props.loadSessionTranscript,
|
|
31667
|
+
requestDelete,
|
|
31668
|
+
deleteConfirmId,
|
|
31669
|
+
reloadToken: deleteReloadToken,
|
|
31670
|
+
deleteMode: resumeDelete.mode,
|
|
30984
31671
|
select: (row) => {
|
|
30985
31672
|
props.switchSession(row);
|
|
30986
31673
|
setResumeOpen(false);
|
|
@@ -31016,6 +31703,29 @@ function App(props) {
|
|
|
31016
31703
|
setHistoryOpen(false);
|
|
31017
31704
|
},
|
|
31018
31705
|
close: () => setHistoryOpen(false)
|
|
31706
|
+
}) : void 0, agentsOpen && !approvalPending && !questionPending ? (0, import_react.createElement)(AgentsPanel, {
|
|
31707
|
+
live: agentRows,
|
|
31708
|
+
load: props.loadSubagents,
|
|
31709
|
+
readTranscript: props.loadSessionTranscript,
|
|
31710
|
+
close: () => setAgentsOpen(false)
|
|
31711
|
+
}) : void 0, subagentOpen && !approvalPending && !questionPending ? (0, import_react.createElement)(SubagentPanel, {
|
|
31712
|
+
current: props.subagentModel,
|
|
31713
|
+
load: props.loadModels,
|
|
31714
|
+
pick: (row, effortId) => {
|
|
31715
|
+
try {
|
|
31716
|
+
const label = props.setSubagentModel(row, effortId);
|
|
31717
|
+
notify(`subagents → ${label}`);
|
|
31718
|
+
setSubagentOpen(false);
|
|
31719
|
+
} catch (reason) {
|
|
31720
|
+
notify(`subagent model change failed: ${reason instanceof Error ? reason.message : String(reason)}`, "error");
|
|
31721
|
+
}
|
|
31722
|
+
},
|
|
31723
|
+
inherit: () => {
|
|
31724
|
+
props.clearSubagentModel();
|
|
31725
|
+
notify("subagents → inherit current model");
|
|
31726
|
+
setSubagentOpen(false);
|
|
31727
|
+
},
|
|
31728
|
+
close: () => setSubagentOpen(false)
|
|
31019
31729
|
}) : void 0, notice === void 0 ? void 0 : (0, import_react.createElement)(NoticeLine, {
|
|
31020
31730
|
text: notice.text,
|
|
31021
31731
|
tone: notice.tone,
|
|
@@ -31057,7 +31767,8 @@ function App(props) {
|
|
|
31057
31767
|
return;
|
|
31058
31768
|
}
|
|
31059
31769
|
if (row.reasoning === void 0 || row.reasoning.efforts.length === 0) {
|
|
31060
|
-
|
|
31770
|
+
setEffortFor(row);
|
|
31771
|
+
setModelOpen(true);
|
|
31061
31772
|
return;
|
|
31062
31773
|
}
|
|
31063
31774
|
setEffortFor(row);
|
|
@@ -31071,7 +31782,10 @@ function App(props) {
|
|
|
31071
31782
|
},
|
|
31072
31783
|
openMode: () => setModeOpen(true),
|
|
31073
31784
|
openPermission: () => setPermissionOpen(true),
|
|
31074
|
-
openResume: () =>
|
|
31785
|
+
openResume: () => {
|
|
31786
|
+
setResumeDelete({ mode: false });
|
|
31787
|
+
setResumeOpen(true);
|
|
31788
|
+
},
|
|
31075
31789
|
openPlugin: (query = "") => {
|
|
31076
31790
|
setPluginQuery(query);
|
|
31077
31791
|
setPluginOpen(true);
|
|
@@ -31079,6 +31793,20 @@ function App(props) {
|
|
|
31079
31793
|
openStatusline: () => setStatuslineOpen(true),
|
|
31080
31794
|
openTheme: () => setThemeOpen(true),
|
|
31081
31795
|
openHistory: () => setHistoryOpen(true),
|
|
31796
|
+
openAgents: () => setAgentsOpen(true),
|
|
31797
|
+
openSubagent: () => setSubagentOpen(true),
|
|
31798
|
+
openDelete: (id) => {
|
|
31799
|
+
const armed = id === void 0 || id === "" ? void 0 : id;
|
|
31800
|
+
setResumeDelete({
|
|
31801
|
+
mode: true,
|
|
31802
|
+
...armed === void 0 ? {} : { id: armed }
|
|
31803
|
+
});
|
|
31804
|
+
setDeleteConfirmId(armed);
|
|
31805
|
+
setResumeOpen(true);
|
|
31806
|
+
},
|
|
31807
|
+
deleteConfirm: deleteConfirmId,
|
|
31808
|
+
confirmDelete,
|
|
31809
|
+
cancelDelete,
|
|
31082
31810
|
createSession: props.createSession,
|
|
31083
31811
|
cancelSessionSwitch: props.cancelSessionSwitch,
|
|
31084
31812
|
notify,
|
|
@@ -31149,15 +31877,26 @@ function App(props) {
|
|
|
31149
31877
|
* @returns the store the renderer subscribes to.
|
|
31150
31878
|
*/
|
|
31151
31879
|
function mountApprovalAnswerer(ctx, owns, preview) {
|
|
31880
|
+
const queue = [];
|
|
31152
31881
|
let snapshot = {
|
|
31153
31882
|
pending: void 0,
|
|
31154
|
-
answered: false
|
|
31883
|
+
answered: false,
|
|
31884
|
+
queued: 0
|
|
31155
31885
|
};
|
|
31156
31886
|
const listeners = /* @__PURE__ */ new Set();
|
|
31157
|
-
const
|
|
31158
|
-
|
|
31887
|
+
const publish = () => {
|
|
31888
|
+
const head = queue[0];
|
|
31889
|
+
snapshot = {
|
|
31890
|
+
pending: head === void 0 ? void 0 : head.pending,
|
|
31891
|
+
answered: head !== void 0 && head.answered,
|
|
31892
|
+
queued: Math.max(0, queue.length - 1)
|
|
31893
|
+
};
|
|
31159
31894
|
for (const listener of listeners) listener();
|
|
31160
31895
|
};
|
|
31896
|
+
const removeSlot = (slot) => {
|
|
31897
|
+
const at = queue.indexOf(slot);
|
|
31898
|
+
if (at !== -1) queue.splice(at, 1);
|
|
31899
|
+
};
|
|
31161
31900
|
ctx.on("approval/request", (request, next) => {
|
|
31162
31901
|
if (!owns(request.agent)) return next();
|
|
31163
31902
|
if (request.signal?.aborted === true) return Promise.resolve("cancelled");
|
|
@@ -31172,39 +31911,36 @@ function mountApprovalAnswerer(ctx, owns, preview) {
|
|
|
31172
31911
|
if (resolved) return;
|
|
31173
31912
|
resolved = true;
|
|
31174
31913
|
detachAbort();
|
|
31175
|
-
|
|
31176
|
-
|
|
31177
|
-
answered: false
|
|
31178
|
-
});
|
|
31914
|
+
removeSlot(slot);
|
|
31915
|
+
publish();
|
|
31179
31916
|
settle("cancelled");
|
|
31180
31917
|
};
|
|
31181
31918
|
if (signal !== void 0) signal.addEventListener("abort", onAbort, { once: true });
|
|
31182
|
-
const
|
|
31183
|
-
|
|
31184
|
-
|
|
31185
|
-
|
|
31186
|
-
|
|
31187
|
-
|
|
31188
|
-
|
|
31189
|
-
|
|
31190
|
-
|
|
31191
|
-
|
|
31192
|
-
answered
|
|
31193
|
-
|
|
31194
|
-
|
|
31919
|
+
const slot = {
|
|
31920
|
+
answered: false,
|
|
31921
|
+
pending: {
|
|
31922
|
+
headline: request.reason ?? `tool ${request.toolName} asks for your approval`,
|
|
31923
|
+
toolName: request.toolName,
|
|
31924
|
+
command: preview(request),
|
|
31925
|
+
answer: (outcome) => {
|
|
31926
|
+
if (resolved) return;
|
|
31927
|
+
resolved = true;
|
|
31928
|
+
detachAbort();
|
|
31929
|
+
slot.answered = true;
|
|
31930
|
+
publish();
|
|
31931
|
+
settle(outcome);
|
|
31932
|
+
}
|
|
31195
31933
|
}
|
|
31196
31934
|
};
|
|
31197
|
-
|
|
31198
|
-
|
|
31199
|
-
answered: false
|
|
31200
|
-
});
|
|
31935
|
+
queue.push(slot);
|
|
31936
|
+
publish();
|
|
31201
31937
|
return new Promise((resolve) => {
|
|
31202
31938
|
settle = resolve;
|
|
31203
31939
|
}).then((outcome) => {
|
|
31204
|
-
if (outcome !== "cancelled")
|
|
31205
|
-
|
|
31206
|
-
|
|
31207
|
-
}
|
|
31940
|
+
if (outcome !== "cancelled") {
|
|
31941
|
+
removeSlot(slot);
|
|
31942
|
+
publish();
|
|
31943
|
+
}
|
|
31208
31944
|
return outcome;
|
|
31209
31945
|
});
|
|
31210
31946
|
});
|
|
@@ -31352,6 +32088,32 @@ function buildModelSelection(row, effortId) {
|
|
|
31352
32088
|
...selected === void 0 ? {} : { reasoningEffort: ReasoningEffortId(selected) }
|
|
31353
32089
|
};
|
|
31354
32090
|
}
|
|
32091
|
+
/** Display label for one applied selection: `provider/model` or `provider/model@effort`. */
|
|
32092
|
+
function modelSelectionLabel(selection) {
|
|
32093
|
+
return selection.reasoningEffort === void 0 ? `${selection.provider}/${selection.model}` : `${selection.provider}/${selection.model}@${selection.reasoningEffort}`;
|
|
32094
|
+
}
|
|
32095
|
+
/**
|
|
32096
|
+
* Apply one model selection onto a resolved request config — the exact
|
|
32097
|
+
* semantics of the kernel's `installModelSelection` request listener,
|
|
32098
|
+
* extracted so the TUI can mirror it for subagent-origin requests: children
|
|
32099
|
+
* spawned by the subagent tool inherit the parent's CREATE-TIME AgentOptions,
|
|
32100
|
+
* which a mid-session /model switch never touches, so delegated work would
|
|
32101
|
+
* otherwise keep running on the launch-time route. An absent effort strips
|
|
32102
|
+
* any inherited effort (restoring the selected model's provider default),
|
|
32103
|
+
* matching the kernel listener field-for-field.
|
|
32104
|
+
* @param resolved - the config the inner chain produced.
|
|
32105
|
+
* @param selection - the selection to enforce.
|
|
32106
|
+
* @returns the overridden config.
|
|
32107
|
+
*/
|
|
32108
|
+
function applyModelSelectionToConfig(resolved, selection) {
|
|
32109
|
+
const { reasoningEffort: _inheritedEffort, ...withoutInheritedEffort } = resolved;
|
|
32110
|
+
return {
|
|
32111
|
+
...withoutInheritedEffort,
|
|
32112
|
+
provider: selection.provider,
|
|
32113
|
+
model: selection.model,
|
|
32114
|
+
...selection.reasoningEffort === void 0 ? {} : { reasoningEffort: selection.reasoningEffort }
|
|
32115
|
+
};
|
|
32116
|
+
}
|
|
31355
32117
|
/**
|
|
31356
32118
|
* Load the selectable model directory from the live `ctx.llm` registry.
|
|
31357
32119
|
* Providers are listed synchronously; each provider's models are discovered
|
|
@@ -32000,6 +32762,153 @@ function createTranscriptStore(replay) {
|
|
|
32000
32762
|
}
|
|
32001
32763
|
};
|
|
32002
32764
|
}
|
|
32765
|
+
/** Bounded last-activity text (plain characters, display-sliced later). */
|
|
32766
|
+
const MAX_ACTIVITY_CHARS = 80;
|
|
32767
|
+
/** Single-line bounded preview of an assembled message's text content. */
|
|
32768
|
+
function messagePreview(content) {
|
|
32769
|
+
if (!Array.isArray(content)) return "replied";
|
|
32770
|
+
const texts = [];
|
|
32771
|
+
for (const block of content) {
|
|
32772
|
+
if (texts.join(" ").length >= MAX_ACTIVITY_CHARS) break;
|
|
32773
|
+
if (typeof block === "object" && block !== null) {
|
|
32774
|
+
const { type, text } = block;
|
|
32775
|
+
if (type === "text" && typeof text === "string" && text !== "") texts.push(text);
|
|
32776
|
+
}
|
|
32777
|
+
}
|
|
32778
|
+
const joined = texts.join(" ").replace(/\s+/gu, " ").trim();
|
|
32779
|
+
return joined === "" ? "replied" : bound(joined);
|
|
32780
|
+
}
|
|
32781
|
+
/** Bound one activity string to the display budget. */
|
|
32782
|
+
function bound(text) {
|
|
32783
|
+
const flat = text.replace(/\s+/gu, " ").trim();
|
|
32784
|
+
return flat.length > MAX_ACTIVITY_CHARS ? `${flat.slice(0, 79)}…` : flat;
|
|
32785
|
+
}
|
|
32786
|
+
/**
|
|
32787
|
+
* Fold one child-session event into its feed row (pure).
|
|
32788
|
+
* Unknown event kinds leave the row untouched.
|
|
32789
|
+
* @param previous - the row's current state, when any.
|
|
32790
|
+
* @param sessionId - the child session id.
|
|
32791
|
+
* @param event - the child session event.
|
|
32792
|
+
* @returns the next row state.
|
|
32793
|
+
*/
|
|
32794
|
+
function foldSubagentRow(previous, sessionId, event) {
|
|
32795
|
+
const base = previous ?? {
|
|
32796
|
+
id: sessionId,
|
|
32797
|
+
label: `agent ${sessionId.slice(-6)}`,
|
|
32798
|
+
state: "running",
|
|
32799
|
+
activity: "starting…",
|
|
32800
|
+
updatedAt: event.time
|
|
32801
|
+
};
|
|
32802
|
+
const data = event.data;
|
|
32803
|
+
switch (event.type) {
|
|
32804
|
+
case "session/title": {
|
|
32805
|
+
const title = data["title"];
|
|
32806
|
+
const text = typeof title === "string" && title.trim() !== "" ? title : void 0;
|
|
32807
|
+
return text === void 0 || text === base.label ? base : {
|
|
32808
|
+
...base,
|
|
32809
|
+
label: bound(text),
|
|
32810
|
+
updatedAt: event.time
|
|
32811
|
+
};
|
|
32812
|
+
}
|
|
32813
|
+
case "request/header": return {
|
|
32814
|
+
...base,
|
|
32815
|
+
state: "running",
|
|
32816
|
+
activity: "working…",
|
|
32817
|
+
updatedAt: event.time
|
|
32818
|
+
};
|
|
32819
|
+
case "user/message": return {
|
|
32820
|
+
...base,
|
|
32821
|
+
state: "running",
|
|
32822
|
+
activity: "prompted",
|
|
32823
|
+
updatedAt: event.time
|
|
32824
|
+
};
|
|
32825
|
+
case "assistant/chunk": return {
|
|
32826
|
+
...base,
|
|
32827
|
+
state: "running",
|
|
32828
|
+
activity: "thinking…",
|
|
32829
|
+
updatedAt: event.time
|
|
32830
|
+
};
|
|
32831
|
+
case "assistant/message": return {
|
|
32832
|
+
...base,
|
|
32833
|
+
state: "idle",
|
|
32834
|
+
activity: messagePreview(data["message"] === void 0 ? void 0 : data["message"].content),
|
|
32835
|
+
updatedAt: event.time
|
|
32836
|
+
};
|
|
32837
|
+
case "tool/call": {
|
|
32838
|
+
const name = typeof data["name"] === "string" ? data["name"] : "tool";
|
|
32839
|
+
return {
|
|
32840
|
+
...base,
|
|
32841
|
+
state: "running",
|
|
32842
|
+
activity: `tool ${name}`,
|
|
32843
|
+
updatedAt: event.time
|
|
32844
|
+
};
|
|
32845
|
+
}
|
|
32846
|
+
case "tool/result": return {
|
|
32847
|
+
...base,
|
|
32848
|
+
state: "running",
|
|
32849
|
+
activity: "tool done",
|
|
32850
|
+
updatedAt: event.time
|
|
32851
|
+
};
|
|
32852
|
+
case "turn/start": return {
|
|
32853
|
+
...base,
|
|
32854
|
+
state: "running",
|
|
32855
|
+
activity: base.activity === "starting…" ? "working…" : base.activity,
|
|
32856
|
+
updatedAt: event.time
|
|
32857
|
+
};
|
|
32858
|
+
case "turn/end": return {
|
|
32859
|
+
...base,
|
|
32860
|
+
state: "done",
|
|
32861
|
+
activity: "finished",
|
|
32862
|
+
updatedAt: event.time
|
|
32863
|
+
};
|
|
32864
|
+
default: return base;
|
|
32865
|
+
}
|
|
32866
|
+
}
|
|
32867
|
+
/**
|
|
32868
|
+
* Create one subagent feed. `apply` folds a child event (the caller gates
|
|
32869
|
+
* which sessions are children); `reset` clears on a session switch. Row
|
|
32870
|
+
* order is first-seen; the snapshot array is frozen and only replaced when
|
|
32871
|
+
* a row actually changed.
|
|
32872
|
+
* @returns the mutable feed handle plus its `SubagentFeedView`.
|
|
32873
|
+
*/
|
|
32874
|
+
function createSubagentFeed() {
|
|
32875
|
+
let rows = Object.freeze([]);
|
|
32876
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
32877
|
+
let scheduled = false;
|
|
32878
|
+
const notify = () => {
|
|
32879
|
+
if (scheduled) return;
|
|
32880
|
+
scheduled = true;
|
|
32881
|
+
queueMicrotask(() => {
|
|
32882
|
+
scheduled = false;
|
|
32883
|
+
for (const listener of listeners) listener();
|
|
32884
|
+
});
|
|
32885
|
+
};
|
|
32886
|
+
return {
|
|
32887
|
+
apply(sessionId, event) {
|
|
32888
|
+
const index = rows.findIndex((row) => row.id === sessionId);
|
|
32889
|
+
const previous = index === -1 ? void 0 : rows[index];
|
|
32890
|
+
const next = foldSubagentRow(previous, sessionId, event);
|
|
32891
|
+
if (next === previous) return;
|
|
32892
|
+
if (index === -1 && rows.length >= 8) return;
|
|
32893
|
+
rows = Object.freeze(index === -1 ? [...rows, next] : rows.map((row, at) => at === index ? next : row));
|
|
32894
|
+
notify();
|
|
32895
|
+
},
|
|
32896
|
+
reset() {
|
|
32897
|
+
if (rows.length === 0) return;
|
|
32898
|
+
rows = Object.freeze([]);
|
|
32899
|
+
notify();
|
|
32900
|
+
},
|
|
32901
|
+
subscribe(listener) {
|
|
32902
|
+
listeners.add(listener);
|
|
32903
|
+
return () => {
|
|
32904
|
+
listeners.delete(listener);
|
|
32905
|
+
};
|
|
32906
|
+
},
|
|
32907
|
+
getSnapshot() {
|
|
32908
|
+
return rows;
|
|
32909
|
+
}
|
|
32910
|
+
};
|
|
32911
|
+
}
|
|
32003
32912
|
//#endregion
|
|
32004
32913
|
//#region src/skills.ts
|
|
32005
32914
|
function toRows(skills) {
|
|
@@ -32287,76 +33196,6 @@ function listPluginRows(ctx) {
|
|
|
32287
33196
|
return rows;
|
|
32288
33197
|
}
|
|
32289
33198
|
//#endregion
|
|
32290
|
-
//#region src/session-directory.ts
|
|
32291
|
-
/** Lightweight session-directory projection for the /resume picker. */
|
|
32292
|
-
/** Case-insensitive filesystems (Windows, macOS) compare paths by lowercased form. */
|
|
32293
|
-
const CASE_INSENSITIVE_FS = process.platform === "win32" || process.platform === "darwin";
|
|
32294
|
-
/** True when the header describes a subagent conversation (durable lineage). */
|
|
32295
|
-
function isSubagentSession(header) {
|
|
32296
|
-
return header.origin === "subagent" || header.parentSession !== void 0;
|
|
32297
|
-
}
|
|
32298
|
-
function comparablePath(value) {
|
|
32299
|
-
const resolved = resolve(value);
|
|
32300
|
-
return CASE_INSENSITIVE_FS ? resolved.toLowerCase() : resolved;
|
|
32301
|
-
}
|
|
32302
|
-
/** Platform-consistent path equality for session cwd comparisons. */
|
|
32303
|
-
function samePath(left, right) {
|
|
32304
|
-
if (left === void 0) return false;
|
|
32305
|
-
return comparablePath(left) === comparablePath(right);
|
|
32306
|
-
}
|
|
32307
|
-
/**
|
|
32308
|
-
* Unique header match by exact id or unique id prefix (root and subagent
|
|
32309
|
-
* headers alike); the caller applies any lineage gate.
|
|
32310
|
-
* @param headers - the persisted headers.
|
|
32311
|
-
* @param wanted - the id or id prefix.
|
|
32312
|
-
* @returns the uniquely matched header.
|
|
32313
|
-
* @throws when nothing matches or the prefix is ambiguous.
|
|
32314
|
-
*/
|
|
32315
|
-
function matchSessionId(headers, wanted) {
|
|
32316
|
-
const exact = headers.filter((header) => header.id === wanted);
|
|
32317
|
-
const matches = exact.length > 0 ? exact : headers.filter((header) => header.id.startsWith(wanted));
|
|
32318
|
-
if (matches.length === 0) throw new Error(`no persisted session matches "${wanted}"`);
|
|
32319
|
-
if (matches.length > 1) throw new Error(`session prefix "${wanted}" is ambiguous (${matches.length} matches): use more of the id`);
|
|
32320
|
-
return matches[0];
|
|
32321
|
-
}
|
|
32322
|
-
/** The newest persisted ROOT session pinned to this cwd, or undefined. */
|
|
32323
|
-
function newestRootForCwd(headers, cwd) {
|
|
32324
|
-
return headers.filter((header) => !isSubagentSession(header) && samePath(header.cwd, cwd)).sort((left, right) => right.createdAt - left.createdAt)[0];
|
|
32325
|
-
}
|
|
32326
|
-
/** Filter/sort header-only records. No session log is loaded here. */
|
|
32327
|
-
function projectSessionRows(records, options) {
|
|
32328
|
-
const needle = options.query.trim().toLowerCase();
|
|
32329
|
-
return records.filter((record) => options.sessions === "all" || !isSubagentSession(record.header)).filter((record) => options.cwd === "all" || samePath(record.header.cwd, options.currentCwd)).map((record) => {
|
|
32330
|
-
const cwd = record.header.cwd ?? "";
|
|
32331
|
-
const subagent = isSubagentSession(record.header);
|
|
32332
|
-
return {
|
|
32333
|
-
id: record.header.id,
|
|
32334
|
-
createdAt: record.header.createdAt,
|
|
32335
|
-
cwd,
|
|
32336
|
-
workspace: cwd === "" ? "(no workspace)" : basename(cwd),
|
|
32337
|
-
parent: record.header.parentSession,
|
|
32338
|
-
subagent,
|
|
32339
|
-
resumable: !subagent,
|
|
32340
|
-
live: record.live,
|
|
32341
|
-
persisted: record.persisted,
|
|
32342
|
-
preset: record.header.agentPreset ?? "standard"
|
|
32343
|
-
};
|
|
32344
|
-
}).filter((row) => needle === "" || `${row.id} ${row.cwd} ${row.workspace} ${row.preset}`.toLowerCase().includes(needle)).sort((left, right) => options.sort === "newest" ? right.createdAt - left.createdAt : left.createdAt - right.createdAt);
|
|
32345
|
-
}
|
|
32346
|
-
/** Merge page-local title observations without disturbing directory order. */
|
|
32347
|
-
function mergeSessionTitles(rows, observations) {
|
|
32348
|
-
const titles = /* @__PURE__ */ new Map();
|
|
32349
|
-
for (const observation of observations) {
|
|
32350
|
-
if (observation.status !== "fulfilled") continue;
|
|
32351
|
-
const title = observation.value?.title?.title ?? observation.value?.title?.text;
|
|
32352
|
-
if (title !== void 0 && title.trim() !== "") titles.set(observation.sessionId, title);
|
|
32353
|
-
}
|
|
32354
|
-
return rows.map((row) => titles.has(row.id) ? {
|
|
32355
|
-
...row,
|
|
32356
|
-
title: titles.get(row.id)
|
|
32357
|
-
} : row);
|
|
32358
|
-
}
|
|
32359
|
-
//#endregion
|
|
32360
33199
|
//#region src/index.ts
|
|
32361
33200
|
/**
|
|
32362
33201
|
* @deepseek-ai/dsh-code — the interactive terminal driver. The bundle patch
|
|
@@ -32512,7 +33351,7 @@ async function run(ctx, startup, io) {
|
|
|
32512
33351
|
const sessionQuery = ctx.get("sessionQuery");
|
|
32513
33352
|
if (agents === void 0 || defaultModel === void 0 || sessions === void 0) return;
|
|
32514
33353
|
const cwd = process.cwd();
|
|
32515
|
-
const
|
|
33354
|
+
const currentDefaults = () => defaultModel.currentSelection();
|
|
32516
33355
|
const presets = agentPresetsFrom(ctx);
|
|
32517
33356
|
if (presets === void 0) throw new Error("agent preset service is unavailable; check the dsh-code bundle patch");
|
|
32518
33357
|
const permissionPresets = permissionPresetsFrom(ctx);
|
|
@@ -32528,7 +33367,7 @@ async function run(ctx, startup, io) {
|
|
|
32528
33367
|
mode = (await presets.mount(agentCtx, sessionPreset)).id;
|
|
32529
33368
|
installModelSelection(agentCtx, {
|
|
32530
33369
|
get current() {
|
|
32531
|
-
return resolveEffectiveSelection(selectionState.picked, agentCtx.agent?.session.requestHeader()?.config,
|
|
33370
|
+
return resolveEffectiveSelection(selectionState.picked, agentCtx.agent?.session.requestHeader()?.config, currentDefaults());
|
|
32532
33371
|
},
|
|
32533
33372
|
set current(value) {
|
|
32534
33373
|
selectionState.picked = value;
|
|
@@ -32536,12 +33375,16 @@ async function run(ctx, startup, io) {
|
|
|
32536
33375
|
assembled: void 0
|
|
32537
33376
|
});
|
|
32538
33377
|
};
|
|
33378
|
+
const seedOptions = pendingSelection === void 0 ? {
|
|
33379
|
+
provider: currentDefaults().provider,
|
|
33380
|
+
model: currentDefaults().model
|
|
33381
|
+
} : {
|
|
33382
|
+
provider: pendingSelection.provider,
|
|
33383
|
+
model: pendingSelection.model
|
|
33384
|
+
};
|
|
32539
33385
|
const handle = next.resume ? await agents.resume({
|
|
32540
33386
|
resumeSessionId: SessionId(next.sessionId),
|
|
32541
|
-
agentOptions:
|
|
32542
|
-
provider: defaults.provider,
|
|
32543
|
-
model: defaults.model
|
|
32544
|
-
},
|
|
33387
|
+
agentOptions: seedOptions,
|
|
32545
33388
|
signal: quitAbort.signal,
|
|
32546
33389
|
setup
|
|
32547
33390
|
}) : await agents.create({
|
|
@@ -32550,10 +33393,7 @@ async function run(ctx, startup, io) {
|
|
|
32550
33393
|
cwd: nextCwd,
|
|
32551
33394
|
agentPreset: mode
|
|
32552
33395
|
},
|
|
32553
|
-
agentOptions:
|
|
32554
|
-
provider: defaults.provider,
|
|
32555
|
-
model: defaults.model
|
|
32556
|
-
},
|
|
33396
|
+
agentOptions: seedOptions,
|
|
32557
33397
|
signal: quitAbort.signal,
|
|
32558
33398
|
setup
|
|
32559
33399
|
});
|
|
@@ -32575,6 +33415,7 @@ async function run(ctx, startup, io) {
|
|
|
32575
33415
|
let agent;
|
|
32576
33416
|
let session;
|
|
32577
33417
|
let store = createTranscriptStore();
|
|
33418
|
+
const subagents = createSubagentFeed();
|
|
32578
33419
|
let mentions = createMentions(ctx, void 0, cwd);
|
|
32579
33420
|
/** Explicit model pick made before any session exists (a bare launch). */
|
|
32580
33421
|
let pendingSelection;
|
|
@@ -32625,13 +33466,26 @@ async function run(ctx, startup, io) {
|
|
|
32625
33466
|
mentions = prepared.mentions;
|
|
32626
33467
|
}
|
|
32627
33468
|
const off = ctx.on("session/event", (subject, event) => {
|
|
32628
|
-
if (session
|
|
33469
|
+
if (session === void 0) return;
|
|
33470
|
+
if (subject.id === session.id) {
|
|
33471
|
+
store.apply(event);
|
|
33472
|
+
return;
|
|
33473
|
+
}
|
|
33474
|
+
if (subject.header.parentSession === session.id) subagents.apply(subject.id, event);
|
|
32629
33475
|
});
|
|
32630
33476
|
const commands = watchCommands(ctx);
|
|
32631
33477
|
if (agent !== void 0) commands.setAgent(agent);
|
|
32632
33478
|
const skills = watchSkills(ctx);
|
|
32633
33479
|
if (agent !== void 0) skills.setAgent(agent);
|
|
32634
33480
|
const approval = mountApprovalAnswerer(ctx, (candidate) => agent !== void 0 && candidate.id === agent.id, (request) => approvalCommandPreview(store.getView().entries, request.callId, request.toolName));
|
|
33481
|
+
let subagentOverride;
|
|
33482
|
+
ctx.on("agent/request", (payload, next) => {
|
|
33483
|
+
const subject = payload.agent;
|
|
33484
|
+
const header = subject.session.header;
|
|
33485
|
+
if (header.parentSession === void 0 && header.origin !== "subagent") return next();
|
|
33486
|
+
const picked = subagentOverride ?? resolveEffectiveSelection(active?.selection.picked ?? pendingSelection, subject.session.requestHeader()?.config, currentDefaults());
|
|
33487
|
+
return next().then((resolved) => applyModelSelectionToConfig(resolved, picked));
|
|
33488
|
+
});
|
|
32635
33489
|
const questions = mountQuestionProvider(ctx);
|
|
32636
33490
|
const bridge = { notify: () => {} };
|
|
32637
33491
|
const statuslinePath = join(homedir(), ".dsh", "dsh-code", "statusline.json");
|
|
@@ -32838,6 +33692,7 @@ async function run(ctx, startup, io) {
|
|
|
32838
33692
|
session = next.session;
|
|
32839
33693
|
store = next.store;
|
|
32840
33694
|
mentions = next.mentions;
|
|
33695
|
+
subagents.reset();
|
|
32841
33696
|
pendingMode = void 0;
|
|
32842
33697
|
pendingPermission = void 0;
|
|
32843
33698
|
commands.setAgent(agent);
|
|
@@ -32950,8 +33805,33 @@ async function run(ctx, startup, io) {
|
|
|
32950
33805
|
const selection = buildModelSelection(row, effortId);
|
|
32951
33806
|
if (active === void 0) pendingSelection = selection;
|
|
32952
33807
|
else active.selection.picked = selection;
|
|
33808
|
+
defaultModel.saveSelection(selection).catch((error) => {
|
|
33809
|
+
bridge.notify(`model switch applies to this session but was not saved as the default: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
33810
|
+
});
|
|
33811
|
+
const llm = ctx.get("llm");
|
|
33812
|
+
const resolveCallConfig = llm?.resolveCallConfig;
|
|
33813
|
+
if (llm !== void 0 && typeof resolveCallConfig === "function") Promise.resolve(resolveCallConfig.call(llm, {
|
|
33814
|
+
provider: selection.provider,
|
|
33815
|
+
model: selection.model,
|
|
33816
|
+
...selection.reasoningEffort === void 0 ? {} : { reasoningEffort: selection.reasoningEffort }
|
|
33817
|
+
})).catch((error) => {
|
|
33818
|
+
bridge.notify(`model selection rejected: ${error instanceof Error ? error.message : String(error)} — reopen /model to pick again`, "error");
|
|
33819
|
+
});
|
|
32953
33820
|
return `${row.provider}/${row.model}`;
|
|
32954
33821
|
};
|
|
33822
|
+
/** The /subagent override label, '' when delegated agents follow the current model. */
|
|
33823
|
+
const subagentModelLabel = () => subagentOverride === void 0 ? "" : modelSelectionLabel(subagentOverride);
|
|
33824
|
+
/** Apply one /subagent model pick; returns the override label. */
|
|
33825
|
+
const setSubagentModel = (row, effortId) => {
|
|
33826
|
+
subagentOverride = buildModelSelection(row, effortId);
|
|
33827
|
+
renderCurrent();
|
|
33828
|
+
return modelSelectionLabel(subagentOverride);
|
|
33829
|
+
};
|
|
33830
|
+
/** Drop the /subagent override: delegated agents follow the current model again. */
|
|
33831
|
+
const clearSubagentModel = () => {
|
|
33832
|
+
subagentOverride = void 0;
|
|
33833
|
+
renderCurrent();
|
|
33834
|
+
};
|
|
32955
33835
|
/**
|
|
32956
33836
|
* Export the folded transcript to a markdown file (/export). The default
|
|
32957
33837
|
* target sits beside the session's cwd so the file lands in the user's
|
|
@@ -32994,11 +33874,61 @@ async function run(ctx, startup, io) {
|
|
|
32994
33874
|
};
|
|
32995
33875
|
const loadSessions = async (options, signal) => {
|
|
32996
33876
|
if (sessionQuery === void 0) throw new Error("session query is unavailable in this profile");
|
|
32997
|
-
const
|
|
33877
|
+
const records = await sessionQuery.listSessions(signal);
|
|
33878
|
+
const updated = /* @__PURE__ */ new Map();
|
|
33879
|
+
for (const record of records) {
|
|
33880
|
+
const location = persistence?.locate(record.header);
|
|
33881
|
+
if (location === void 0) continue;
|
|
33882
|
+
try {
|
|
33883
|
+
updated.set(record.header.id, (await stat(location.path)).mtimeMs);
|
|
33884
|
+
} catch {}
|
|
33885
|
+
}
|
|
33886
|
+
const projected = projectSessionRows(records, options, updated);
|
|
32998
33887
|
const page = projected.slice(0, 32);
|
|
32999
33888
|
if (page.length === 0) return projected;
|
|
33000
33889
|
return mergeSessionTitles(projected, await sessionQuery.readTitleSnapshots(page.map((row) => row.id), signal));
|
|
33001
33890
|
};
|
|
33891
|
+
/**
|
|
33892
|
+
* Delete one session subtree (/delete, codex semantics: subagent threads go
|
|
33893
|
+
* with their root). The kernel persistence seam has NO deletion API by
|
|
33894
|
+
* design — logs accumulate "until removed externally" — so this is the
|
|
33895
|
+
* controlled external removal: guards (live/current refusal, subtree
|
|
33896
|
+
* collection, and the JSONL layout check `encodeSegment(id)/session.jsonl`)
|
|
33897
|
+
* run before any filesystem touch, and only the backend-located artifacts
|
|
33898
|
+
* are removed. Backends without a locatable artifact (SQLite) are refused.
|
|
33899
|
+
* @param id - the root session id to delete.
|
|
33900
|
+
* @returns the outcome line for the panel/notice.
|
|
33901
|
+
*/
|
|
33902
|
+
const deleteSession = async (id) => {
|
|
33903
|
+
if (sessionQuery === void 0) return "session query is unavailable in this profile";
|
|
33904
|
+
if (session !== void 0 && session.id === id) return "cannot delete the session you are using — switch or /new first";
|
|
33905
|
+
const records = await sessionQuery.listSessions();
|
|
33906
|
+
const target = records.find((record) => record.header.id === id);
|
|
33907
|
+
if (target === void 0) return `no persisted session matches "${id}"`;
|
|
33908
|
+
if (target.live) return "cannot delete a live session — it is open in this or another process";
|
|
33909
|
+
const doomed = collectDeletionSubtree(records, id);
|
|
33910
|
+
const byId = new Map(records.map((record) => [record.header.id, record]));
|
|
33911
|
+
let removed = 0;
|
|
33912
|
+
for (const candidate of doomed) {
|
|
33913
|
+
const record = byId.get(candidate);
|
|
33914
|
+
if (record === void 0 || record.live) continue;
|
|
33915
|
+
const location = persistence?.locate(record.header);
|
|
33916
|
+
if (location === void 0) return `session backend exposes no deletable artifact for ${candidate.slice(-12)} (deletion is unsupported on this backend)`;
|
|
33917
|
+
const dir = sessionArtifactDirectory(location.path, candidate);
|
|
33918
|
+
if (dir === void 0) return `refusing to delete: unexpected artifact layout at ${location.path}`;
|
|
33919
|
+
try {
|
|
33920
|
+
for (const name of SESSION_ARTIFACT_NAMES) await rm(join(dir, name), { force: true });
|
|
33921
|
+
await rm(dir, {
|
|
33922
|
+
force: true,
|
|
33923
|
+
recursive: false
|
|
33924
|
+
}).catch(() => {});
|
|
33925
|
+
removed += 1;
|
|
33926
|
+
} catch (error) {
|
|
33927
|
+
return `delete failed for ${candidate.slice(-12)}: ${error instanceof Error ? error.message : String(error)}`;
|
|
33928
|
+
}
|
|
33929
|
+
}
|
|
33930
|
+
return `deleted ${removed} session${removed === 1 ? "" : "s"}`;
|
|
33931
|
+
};
|
|
33002
33932
|
const loadSessionTranscript = async (id, signal) => {
|
|
33003
33933
|
if (sessionQuery === void 0) throw new Error("session query is unavailable in this profile");
|
|
33004
33934
|
const snapshot = await sessionQuery.readSession(id, signal);
|
|
@@ -33041,6 +33971,7 @@ async function run(ctx, startup, io) {
|
|
|
33041
33971
|
session = next.session;
|
|
33042
33972
|
store = next.store;
|
|
33043
33973
|
mentions = next.mentions;
|
|
33974
|
+
subagents.reset();
|
|
33044
33975
|
pendingMode = void 0;
|
|
33045
33976
|
pendingPermission = void 0;
|
|
33046
33977
|
commands.setAgent(agent);
|
|
@@ -33156,6 +34087,7 @@ async function run(ctx, startup, io) {
|
|
|
33156
34087
|
const appElement = () => {
|
|
33157
34088
|
const sessionCwd = session?.header.cwd ?? cwd;
|
|
33158
34089
|
const currentView = store.getView();
|
|
34090
|
+
const defaults = currentDefaults();
|
|
33159
34091
|
const model = currentView.model !== "" ? currentView.model : pendingSelection !== void 0 ? `${pendingSelection.provider}/${pendingSelection.model}` : `${defaults.provider}/${defaults.model}`;
|
|
33160
34092
|
const effort = resolveEffectiveSelection(active?.selection.picked ?? pendingSelection, session?.requestHeader()?.config, defaults).reasoningEffort;
|
|
33161
34093
|
const permission = permissionPresets === void 0 ? currentView.permission : effectivePermission(permissionPresets, session, pendingPermission);
|
|
@@ -33164,6 +34096,7 @@ async function run(ctx, startup, io) {
|
|
|
33164
34096
|
store,
|
|
33165
34097
|
approval,
|
|
33166
34098
|
questions,
|
|
34099
|
+
subagents,
|
|
33167
34100
|
commands,
|
|
33168
34101
|
skills,
|
|
33169
34102
|
model,
|
|
@@ -33189,6 +34122,10 @@ async function run(ctx, startup, io) {
|
|
|
33189
34122
|
cyclePermission: cyclePermission$1,
|
|
33190
34123
|
setPermission: setPermissionAction,
|
|
33191
34124
|
selectModel,
|
|
34125
|
+
subagentModel: subagentModelLabel(),
|
|
34126
|
+
setSubagentModel,
|
|
34127
|
+
clearSubagentModel,
|
|
34128
|
+
deleteSession,
|
|
33192
34129
|
exportTranscript,
|
|
33193
34130
|
renameTitle,
|
|
33194
34131
|
loadPresets: () => presets.list(),
|
|
@@ -33197,6 +34134,17 @@ async function run(ctx, startup, io) {
|
|
|
33197
34134
|
createSession,
|
|
33198
34135
|
loadSessions,
|
|
33199
34136
|
loadSessionTranscript,
|
|
34137
|
+
loadSubagents: () => {
|
|
34138
|
+
const current = session;
|
|
34139
|
+
if (current === void 0 || sessionQuery === void 0) return Promise.resolve([]);
|
|
34140
|
+
return loadSessions({
|
|
34141
|
+
sessions: "all",
|
|
34142
|
+
cwd: "all",
|
|
34143
|
+
sort: "newest",
|
|
34144
|
+
currentCwd: current.header.cwd ?? cwd,
|
|
34145
|
+
query: ""
|
|
34146
|
+
}).then((rows) => rows.filter((row) => row.parent === current.id));
|
|
34147
|
+
},
|
|
33200
34148
|
switchSession,
|
|
33201
34149
|
cancelSessionSwitch,
|
|
33202
34150
|
loadPlugins: () => listPluginRows(ctx),
|