@mrciphersmith/keryx 0.2.140 → 0.2.143
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.md +10 -0
- package/dist/cli.js +687 -67
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -248,6 +248,16 @@ What is in it today:
|
|
|
248
248
|
session that keeps its ancestry, without editing a transcript by hand.
|
|
249
249
|
`/status` is the session inspector (identity, context window and limits when
|
|
250
250
|
the provider reported them); `/session-info` and `/info` are not aliases.
|
|
251
|
+
- **Plans that survive the turn.** For multi-step work, the agent can keep a
|
|
252
|
+
structured execution plan in the session. The TUI shows pending, active,
|
|
253
|
+
completed, blocked, and skipped steps in the sidebar, and restored sessions
|
|
254
|
+
continue from the same plan instead of rebuilding it from transcript text.
|
|
255
|
+
- **Theme-aware rich transcripts.** Assistant prose, headings, emphasis,
|
|
256
|
+
inline code, fenced code, diffs, and GFM pipe tables use semantic colors
|
|
257
|
+
derived from the selected `/theme`. Use a language fence such as
|
|
258
|
+
```` ```typescript ```` for syntax color, ```` ```diff ```` for addition and
|
|
259
|
+
deletion rows, or ```` ```text ````/```` ```txt ```` when content must remain
|
|
260
|
+
literal, including terminal output and ASCII diagrams.
|
|
251
261
|
- **Responsive busy-turn UX.** A running main turn can be interrupted with
|
|
252
262
|
`/interrupt`; additional prompts are queued and answered as read-only side workers
|
|
253
263
|
(`side-1`) in the TUI so the shell stays usable under long-running turns.
|
package/dist/cli.js
CHANGED
|
@@ -23987,7 +23987,7 @@ async function openSlateAtomic(dir, mintAttemptId, build) {
|
|
|
23987
23987
|
if (existing !== undefined) {
|
|
23988
23988
|
await archiveIfExistsLocked(dir, mintAttemptId());
|
|
23989
23989
|
}
|
|
23990
|
-
const next = build();
|
|
23990
|
+
const next = build(existing);
|
|
23991
23991
|
await writeFileAtomic(slatePath(dir), `${JSON.stringify(next, null, 2)}
|
|
23992
23992
|
`);
|
|
23993
23993
|
await rm7(path93.join(dir, "terminal-state.json"), { force: true });
|
|
@@ -24089,6 +24089,123 @@ var init_slate = __esm(() => {
|
|
|
24089
24089
|
];
|
|
24090
24090
|
});
|
|
24091
24091
|
|
|
24092
|
+
// src/session/execution-plan.ts
|
|
24093
|
+
function clonePlan(plan) {
|
|
24094
|
+
return { revision: plan.revision, items: plan.items.map((item) => ({ ...item })) };
|
|
24095
|
+
}
|
|
24096
|
+
function validateItems(items) {
|
|
24097
|
+
const ids = new Set;
|
|
24098
|
+
let active = 0;
|
|
24099
|
+
return items.map((item) => {
|
|
24100
|
+
const id = item.id.trim();
|
|
24101
|
+
const title = item.title.trim();
|
|
24102
|
+
if (id.length === 0)
|
|
24103
|
+
throw new ExecutionPlanValidationError("item id must be non-empty");
|
|
24104
|
+
if (ids.has(id))
|
|
24105
|
+
throw new ExecutionPlanValidationError(`duplicate item id: ${id}`);
|
|
24106
|
+
ids.add(id);
|
|
24107
|
+
if (title.length === 0)
|
|
24108
|
+
throw new ExecutionPlanValidationError(`item ${id} has an empty title`);
|
|
24109
|
+
if (!EXECUTION_PLAN_STATUSES.includes(item.status)) {
|
|
24110
|
+
throw new ExecutionPlanValidationError(`item ${id} has unknown status ${JSON.stringify(item.status)}`);
|
|
24111
|
+
}
|
|
24112
|
+
if (item.status === "in_progress" && ++active > 1) {
|
|
24113
|
+
throw new ExecutionPlanValidationError("at most one item may be in_progress");
|
|
24114
|
+
}
|
|
24115
|
+
return { id, title, status: item.status };
|
|
24116
|
+
});
|
|
24117
|
+
}
|
|
24118
|
+
function notify(dir, plan) {
|
|
24119
|
+
for (const listener of listeners) {
|
|
24120
|
+
try {
|
|
24121
|
+
listener(dir, clonePlan(plan));
|
|
24122
|
+
} catch {}
|
|
24123
|
+
}
|
|
24124
|
+
}
|
|
24125
|
+
function subscribeExecutionPlans(listener) {
|
|
24126
|
+
listeners.add(listener);
|
|
24127
|
+
return () => listeners.delete(listener);
|
|
24128
|
+
}
|
|
24129
|
+
async function getExecutionPlan(dir) {
|
|
24130
|
+
const plan = (await readSlate(dir))?.executionPlan;
|
|
24131
|
+
return plan === undefined ? undefined : clonePlan(plan);
|
|
24132
|
+
}
|
|
24133
|
+
async function setExecutionPlan(dir, input) {
|
|
24134
|
+
const items = validateItems(input.items);
|
|
24135
|
+
let result;
|
|
24136
|
+
await writeSlate(dir, (prev) => {
|
|
24137
|
+
if (prev === undefined)
|
|
24138
|
+
throw new ExecutionPlanValidationError("no open Slate");
|
|
24139
|
+
const actual = prev.executionPlan?.revision ?? 0;
|
|
24140
|
+
if (input.expectedRevision !== actual)
|
|
24141
|
+
throw new ExecutionPlanConflictError(input.expectedRevision, actual);
|
|
24142
|
+
result = { revision: actual + 1, items };
|
|
24143
|
+
return { ...prev, executionPlan: result };
|
|
24144
|
+
});
|
|
24145
|
+
const plan = clonePlan(result);
|
|
24146
|
+
notify(dir, plan);
|
|
24147
|
+
return plan;
|
|
24148
|
+
}
|
|
24149
|
+
async function updateExecutionPlan(dir, input) {
|
|
24150
|
+
let result;
|
|
24151
|
+
await writeSlate(dir, (prev) => {
|
|
24152
|
+
if (prev === undefined)
|
|
24153
|
+
throw new ExecutionPlanValidationError("no open Slate");
|
|
24154
|
+
const current = prev.executionPlan;
|
|
24155
|
+
const actual = current?.revision ?? 0;
|
|
24156
|
+
if (input.expectedRevision !== actual)
|
|
24157
|
+
throw new ExecutionPlanConflictError(input.expectedRevision, actual);
|
|
24158
|
+
if (current === undefined)
|
|
24159
|
+
throw new ExecutionPlanValidationError("no execution plan exists");
|
|
24160
|
+
if (!EXECUTION_PLAN_STATUSES.includes(input.status)) {
|
|
24161
|
+
throw new ExecutionPlanValidationError(`unknown status ${JSON.stringify(input.status)}`);
|
|
24162
|
+
}
|
|
24163
|
+
if (!current.items.some((item) => item.id === input.itemId)) {
|
|
24164
|
+
throw new ExecutionPlanValidationError(`unknown item id: ${input.itemId}`);
|
|
24165
|
+
}
|
|
24166
|
+
const items = validateItems(current.items.map((item) => item.id === input.itemId ? { ...item, status: input.status } : item));
|
|
24167
|
+
result = { revision: actual + 1, items };
|
|
24168
|
+
return { ...prev, executionPlan: result };
|
|
24169
|
+
});
|
|
24170
|
+
const plan = clonePlan(result);
|
|
24171
|
+
notify(dir, plan);
|
|
24172
|
+
return plan;
|
|
24173
|
+
}
|
|
24174
|
+
function hasActionableExecutionPlanItems(plan) {
|
|
24175
|
+
return plan?.items.some((item) => item.status === "pending" || item.status === "in_progress") ?? false;
|
|
24176
|
+
}
|
|
24177
|
+
function renderExecutionPlanSnapshot(plan, maxItems = 7) {
|
|
24178
|
+
if (plan === undefined || plan.items.length === 0)
|
|
24179
|
+
return;
|
|
24180
|
+
const rows = plan.items.slice(0, Math.max(0, maxItems)).map((item) => {
|
|
24181
|
+
const title = item.title.length > 120 ? `${item.title.slice(0, 119)}\u2026` : item.title;
|
|
24182
|
+
return `- ${item.id} [${item.status}]: ${title}`;
|
|
24183
|
+
});
|
|
24184
|
+
if (plan.items.length > rows.length)
|
|
24185
|
+
rows.push(`- \u2026 ${plan.items.length - rows.length} more item(s)`);
|
|
24186
|
+
return `Current execution plan (revision ${plan.revision}):
|
|
24187
|
+
${rows.join(`
|
|
24188
|
+
`)}`;
|
|
24189
|
+
}
|
|
24190
|
+
var EXECUTION_PLAN_STATUSES, listeners, ExecutionPlanConflictError, ExecutionPlanValidationError;
|
|
24191
|
+
var init_execution_plan = __esm(() => {
|
|
24192
|
+
init_slate();
|
|
24193
|
+
EXECUTION_PLAN_STATUSES = ["pending", "in_progress", "completed", "blocked", "skipped"];
|
|
24194
|
+
listeners = new Set;
|
|
24195
|
+
ExecutionPlanConflictError = class ExecutionPlanConflictError extends Error {
|
|
24196
|
+
constructor(expected, actual) {
|
|
24197
|
+
super(`execution plan revision conflict: expected ${expected}, current ${actual}`);
|
|
24198
|
+
this.name = "ExecutionPlanConflictError";
|
|
24199
|
+
}
|
|
24200
|
+
};
|
|
24201
|
+
ExecutionPlanValidationError = class ExecutionPlanValidationError extends Error {
|
|
24202
|
+
constructor(message) {
|
|
24203
|
+
super(`invalid execution plan: ${message}`);
|
|
24204
|
+
this.name = "ExecutionPlanValidationError";
|
|
24205
|
+
}
|
|
24206
|
+
};
|
|
24207
|
+
});
|
|
24208
|
+
|
|
24092
24209
|
// src/flow/store.ts
|
|
24093
24210
|
import { createHash as createHash16 } from "crypto";
|
|
24094
24211
|
import { appendFile as appendFile3, mkdir as mkdir32, readFile as readFile49, readdir as readdir12 } from "fs/promises";
|
|
@@ -28849,7 +28966,12 @@ async function openSlate(opts) {
|
|
|
28849
28966
|
cwd: opts.cwd,
|
|
28850
28967
|
...opts.runtime !== undefined ? { runtime: opts.runtime } : {}
|
|
28851
28968
|
});
|
|
28852
|
-
return openSlateAtomic(opts.dir, opts.mintAttemptId, () => ({
|
|
28969
|
+
return openSlateAtomic(opts.dir, opts.mintAttemptId, (existing) => ({
|
|
28970
|
+
anchors,
|
|
28971
|
+
course: {},
|
|
28972
|
+
seeds: [],
|
|
28973
|
+
...existing?.executionPlan === undefined ? {} : { executionPlan: existing.executionPlan }
|
|
28974
|
+
}));
|
|
28853
28975
|
}
|
|
28854
28976
|
async function closeSlate(dir, mintAttemptId) {
|
|
28855
28977
|
await archiveSlate(dir, mintAttemptId());
|
|
@@ -28891,7 +29013,12 @@ async function ensureSlateOpened(ref, mintAttemptId, runtime) {
|
|
|
28891
29013
|
if (isSlateSessionDetached(ref)) {
|
|
28892
29014
|
return;
|
|
28893
29015
|
}
|
|
28894
|
-
await openSlateAtomic(ref.dir, mintAttemptId, () => ({
|
|
29016
|
+
await openSlateAtomic(ref.dir, mintAttemptId, (existing) => ({
|
|
29017
|
+
anchors,
|
|
29018
|
+
course: {},
|
|
29019
|
+
seeds: [],
|
|
29020
|
+
...existing?.executionPlan === undefined ? {} : { executionPlan: existing.executionPlan }
|
|
29021
|
+
}));
|
|
28895
29022
|
ref.opened = true;
|
|
28896
29023
|
}
|
|
28897
29024
|
async function closeSlateSession(ref, mintAttemptId) {
|
|
@@ -29360,7 +29487,7 @@ function buildAgentSystemInstruction(orient, ctx = {}) {
|
|
|
29360
29487
|
` + locateRule + "- read_file returns a bounded window starting at `start_line` (default 1). A large file " + "is read by paging: its truncation notice names the start_line to continue from, and a " + `line number from ${hasGraph ? "search_code or graph_symbol" : "search_code"} can be read directly \u2014 do not re-read the ` + `same head hoping for more.
|
|
29361
29488
|
` + "- Prefer ONE correct shell_exec over many exploratory tool calls when the user asks " + `to run a known keryx workflow.
|
|
29362
29489
|
` + "- Tool-call budget: shell_exec, file-mutating shell, workspace_create/workspace_propose, and spawn_subagent " + "all share ONE small per-turn pool (distinct non-read actions), separate from the much larger read-tool pool. " + (hasGraph ? "search_code/graph_*/memory_search/read_wiki/wiki_*/test_related/health_status/flow_status/repomap/read_file/" : "search_code/read_file/") + "list_dir do NOT touch it. Conserve the small pool: batch multiple shell steps into ONE call with `&&` instead " + "of issuing them one at a time, get a command's arguments right the first time instead of trying variants, and " + "for any check covered by a read tool above, use that tool instead of shelling out to the equivalent `keryx \u2026` " + `CLI command.
|
|
29363
|
-
` + "- This session has its own Slate (working-set scratch, not project knowledge): " + "**slate_read** shows the Course (if a Flow is bound) and Seeds recorded so far \u2014 nothing " + "here is auto-injected, so call it if you want to see it. **slate_write_seed** with " + "`{ text, kind? }` records a draft hypothesis/decision/follow-up worth a later human " + "review. WRITE A SEED when you: found a root cause or a bug worth remembering; changed or " + "added code (summarize WHAT changed and WHY); took a design/architecture decision; " + "identified a risk; or discovered a constraint/lesson. Use the `kind` that fits: " + "`decision` (a choice made), `wiki-update` (something a wiki page should say), " + "`memory-entry` (a lesson/constraint), `follow-up` (a TODO for a later session), " + "`risk`, or `contract-change`. Keep each Seed to 2-3 sentences, concrete and specific. " + "Do NOT write Seeds for routine progress notes, one-shot operational requests (e.g. " + '"run git pull", "count files"), or trivia \u2014 those need no workspace and no proposal. ' + "Seeds are the ONLY input wrap-up proposes from: a session whose Slate has zero Seeds " + `produces zero proposals. A Seed is never accepted knowledge by itself.
|
|
29490
|
+
` + "- For multi-step work, use **plan_set** to publish a structured plan, **plan_update** after each real status " + "change, and **plan_get** before resolving a revision conflict. Keep stable item ids, at most one " + "`in_progress` item, and do not mark work complete before verification. These tools update session metadata " + "only; `/plan` remains the operator's separate read-only permission mode.\n" + "- This session has its own Slate (working-set scratch, not project knowledge): " + "**slate_read** shows the Course (if a Flow is bound) and Seeds recorded so far \u2014 nothing " + "here is auto-injected, so call it if you want to see it. **slate_write_seed** with " + "`{ text, kind? }` records a draft hypothesis/decision/follow-up worth a later human " + "review. WRITE A SEED when you: found a root cause or a bug worth remembering; changed or " + "added code (summarize WHAT changed and WHY); took a design/architecture decision; " + "identified a risk; or discovered a constraint/lesson. Use the `kind` that fits: " + "`decision` (a choice made), `wiki-update` (something a wiki page should say), " + "`memory-entry` (a lesson/constraint), `follow-up` (a TODO for a later session), " + "`risk`, or `contract-change`. Keep each Seed to 2-3 sentences, concrete and specific. " + "Do NOT write Seeds for routine progress notes, one-shot operational requests (e.g. " + '"run git pull", "count files"), or trivia \u2014 those need no workspace and no proposal. ' + "Seeds are the ONLY input wrap-up proposes from: a session whose Slate has zero Seeds " + `produces zero proposals. A Seed is never accepted knowledge by itself.
|
|
29364
29491
|
` + "- Shared Agent Context (SAC) workspaces hold accepted, evidence-backed project context " + "beyond this codebase. **workspace_list** with `{ includeArchived? }` shows every workspace " + "visible to you \u2014 call it first when the user references a shared team workspace or accepted " + "project context, or before creating a new workspace, to judge whether an existing one " + "already fits the current topic. **workspace_show** with `{ workspaceId }` shows one " + "workspace's manifest. **workspace_overview** with `{ workspaceId }`, then **workspace_read** " + "with `{ workspaceId, itemId }` for one specific item, reads its accepted Facts/Work/Know-how. " + "**workspace_create** with `{ title, component? }` creates a new workspace AND binds it to " + "this session's slate (wrap-up then proposes into it) \u2014 only when workspace_list found no " + "fitting one and the session has real, durable results worth persisting; a workspace is " + "meant to persist across sessions, so prefer an existing one over creating another for the " + "same topic, and do NOT create one for one-shot operational requests. **workspace_propose** with " + "`{ workspaceId, kind, sessionId?, note? }` (sessionId defaults to this session) proposes a decision/wiki-update/memory-entry/" + "follow-up/contract-change/risk from this session for later human review \u2014 it never accepts " + "anything by itself; accepting always requires a human running `keryx workspace review` at a " + `real terminal, never this tool.
|
|
29365
29492
|
` + "- When you need a decision, interview step, or clarification: use **ask_user** with " + "2\u20136 options `{ id, label, description, recommended? }` (mark one recommended). " + `Do not dump long prose questions without options.
|
|
29366
29493
|
` + "- For a focused independent subtask (investigate X, review Y, research Z): use " + "**spawn_subagent** with `{ task, mode?: 'read_only'|'general', label? }`. " + "Default mode is read_only (no shell). Prefer spawn for work that can finish " + `without your intermediate turns; do not spawn for trivial one-line answers.
|
|
@@ -29657,6 +29784,7 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
|
|
|
29657
29784
|
return "stop";
|
|
29658
29785
|
};
|
|
29659
29786
|
let toollessReprompts = 0;
|
|
29787
|
+
let planFollowThroughUsed = false;
|
|
29660
29788
|
let lastToollessText;
|
|
29661
29789
|
for (;; ) {
|
|
29662
29790
|
if (roundState.round >= roundState.maxRounds) {
|
|
@@ -29666,7 +29794,20 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
|
|
|
29666
29794
|
return { finishReason: "budget" };
|
|
29667
29795
|
}
|
|
29668
29796
|
roundState.round += 1;
|
|
29669
|
-
|
|
29797
|
+
let currentPlan;
|
|
29798
|
+
if (options.slateSession?.opened === true) {
|
|
29799
|
+
try {
|
|
29800
|
+
currentPlan = await getExecutionPlan(options.slateSession.dir);
|
|
29801
|
+
} catch (cause) {
|
|
29802
|
+
io.onSystem?.(`execution plan read failed (ignored): ${cause instanceof Error ? cause.message : String(cause)}
|
|
29803
|
+
`);
|
|
29804
|
+
}
|
|
29805
|
+
}
|
|
29806
|
+
const planSnapshot = renderExecutionPlanSnapshot(currentPlan);
|
|
29807
|
+
const roundSystemInstruction = planSnapshot === undefined ? deps.systemInstruction : `${deps.systemInstruction}
|
|
29808
|
+
|
|
29809
|
+
${planSnapshot}`;
|
|
29810
|
+
const preRequestEstimate = estimateRequestTokens(history, roundSystemInstruction, toolDefs);
|
|
29670
29811
|
if (needsCompaction(preRequestEstimate, deps.contextWindow)) {
|
|
29671
29812
|
const compacted = compactMessages(history, { keepLastUserTurns: 3 });
|
|
29672
29813
|
if (!compacted.noop) {
|
|
@@ -29681,7 +29822,7 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
|
|
|
29681
29822
|
const baseRequest = {
|
|
29682
29823
|
providerId: deps.providerId,
|
|
29683
29824
|
modelId: deps.modelId,
|
|
29684
|
-
systemInstruction:
|
|
29825
|
+
systemInstruction: roundSystemInstruction,
|
|
29685
29826
|
messages: [...history],
|
|
29686
29827
|
tools: toolDefs,
|
|
29687
29828
|
budget: { maxOutputTokens, runReservation: maxOutputTokens },
|
|
@@ -29911,6 +30052,32 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
|
|
|
29911
30052
|
}
|
|
29912
30053
|
continue;
|
|
29913
30054
|
}
|
|
30055
|
+
if (!planFollowThroughUsed && hasActionableExecutionPlanItems(currentPlan)) {
|
|
30056
|
+
planFollowThroughUsed = true;
|
|
30057
|
+
history.push({
|
|
30058
|
+
role: "user",
|
|
30059
|
+
content: "[system] The current execution plan still has actionable items remaining. Continue the work now. " + "Do not give another final reply until the plan is complete or genuinely blocked.",
|
|
30060
|
+
provenance: "project",
|
|
30061
|
+
ts: now()
|
|
30062
|
+
});
|
|
30063
|
+
io.onHistoryChange?.("tool");
|
|
30064
|
+
continue;
|
|
30065
|
+
}
|
|
30066
|
+
if (planFollowThroughUsed && hasActionableExecutionPlanItems(currentPlan)) {
|
|
30067
|
+
const actionable = currentPlan?.items.filter((item) => item.status === "pending" || item.status === "in_progress") ?? [];
|
|
30068
|
+
const shown = actionable.slice(0, 7).map((item) => {
|
|
30069
|
+
const title = item.title.length > 120 ? `${item.title.slice(0, 119)}\u2026` : item.title;
|
|
30070
|
+
return `- ${item.id} [${item.status}]: ${title}`;
|
|
30071
|
+
});
|
|
30072
|
+
if (actionable.length > shown.length) {
|
|
30073
|
+
shown.push(`- \u2026 ${actionable.length - shown.length} more actionable item(s)`);
|
|
30074
|
+
}
|
|
30075
|
+
system(`
|
|
30076
|
+
[plan] Actionable items remain after the single follow-through:
|
|
30077
|
+
${shown.join(`
|
|
30078
|
+
`)}
|
|
30079
|
+
`);
|
|
30080
|
+
}
|
|
29914
30081
|
return {};
|
|
29915
30082
|
}
|
|
29916
30083
|
if (isAborted()) {
|
|
@@ -30481,6 +30648,7 @@ var init_agent = __esm(() => {
|
|
|
30481
30648
|
init_peer_notification();
|
|
30482
30649
|
init_scheduler();
|
|
30483
30650
|
init_slate();
|
|
30651
|
+
init_execution_plan();
|
|
30484
30652
|
init_slate_course();
|
|
30485
30653
|
init_machine_wrap_up();
|
|
30486
30654
|
init_slate_lifecycle();
|
|
@@ -44782,7 +44950,7 @@ import { stdout } from "process";
|
|
|
44782
44950
|
// src/lib/md-blocks.ts
|
|
44783
44951
|
var FENCE_LINE = /^[ \t]{0,3}(```|~~~)(.*)\r?$/;
|
|
44784
44952
|
var HUNK_HEADER = /^@@ -\d+(?:,\d+)? \+\d+(?:,\d+)? @@/;
|
|
44785
|
-
var MARKDOWN_LANGS = new Set(["md", "markdown", "prompt"
|
|
44953
|
+
var MARKDOWN_LANGS = new Set(["md", "markdown", "prompt"]);
|
|
44786
44954
|
var DIFF_LANGS = new Set(["diff", "patch"]);
|
|
44787
44955
|
function stripTrailingCr(line) {
|
|
44788
44956
|
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
@@ -44791,6 +44959,101 @@ function splitLines(text) {
|
|
|
44791
44959
|
return text.split(`
|
|
44792
44960
|
`).map(stripTrailingCr);
|
|
44793
44961
|
}
|
|
44962
|
+
function markdownTableCells(line) {
|
|
44963
|
+
const source = line.trim();
|
|
44964
|
+
if (!source.includes("|")) {
|
|
44965
|
+
return;
|
|
44966
|
+
}
|
|
44967
|
+
const cells = [];
|
|
44968
|
+
let cell = "";
|
|
44969
|
+
let inCode = false;
|
|
44970
|
+
for (let index = 0;index < source.length; index += 1) {
|
|
44971
|
+
const char = source[index] ?? "";
|
|
44972
|
+
if (char === "\\" && source[index + 1] === "|") {
|
|
44973
|
+
cell += "|";
|
|
44974
|
+
index += 1;
|
|
44975
|
+
continue;
|
|
44976
|
+
}
|
|
44977
|
+
if (char === "`") {
|
|
44978
|
+
inCode = !inCode;
|
|
44979
|
+
cell += char;
|
|
44980
|
+
continue;
|
|
44981
|
+
}
|
|
44982
|
+
if (char === "|" && !inCode) {
|
|
44983
|
+
cells.push(cell.trim());
|
|
44984
|
+
cell = "";
|
|
44985
|
+
continue;
|
|
44986
|
+
}
|
|
44987
|
+
cell += char;
|
|
44988
|
+
}
|
|
44989
|
+
cells.push(cell.trim());
|
|
44990
|
+
if (source.startsWith("|")) {
|
|
44991
|
+
cells.shift();
|
|
44992
|
+
}
|
|
44993
|
+
if (source.endsWith("|")) {
|
|
44994
|
+
cells.pop();
|
|
44995
|
+
}
|
|
44996
|
+
return cells.length >= 2 ? cells : undefined;
|
|
44997
|
+
}
|
|
44998
|
+
function tableAlignments(line, columns) {
|
|
44999
|
+
const cells = markdownTableCells(line);
|
|
45000
|
+
if (cells === undefined || cells.length !== columns) {
|
|
45001
|
+
return;
|
|
45002
|
+
}
|
|
45003
|
+
const alignments = [];
|
|
45004
|
+
for (const cell of cells) {
|
|
45005
|
+
const marker = cell.trim();
|
|
45006
|
+
if (!/^:?-{3,}:?$/.test(marker)) {
|
|
45007
|
+
return;
|
|
45008
|
+
}
|
|
45009
|
+
alignments.push(marker.startsWith(":") && marker.endsWith(":") ? "center" : marker.endsWith(":") ? "right" : "left");
|
|
45010
|
+
}
|
|
45011
|
+
return alignments;
|
|
45012
|
+
}
|
|
45013
|
+
function parseMarkdownTables(text) {
|
|
45014
|
+
if (text.length === 0) {
|
|
45015
|
+
return [];
|
|
45016
|
+
}
|
|
45017
|
+
const lines = splitLines(text);
|
|
45018
|
+
const parts = [];
|
|
45019
|
+
let proseStart = 0;
|
|
45020
|
+
const flushProse = (end) => {
|
|
45021
|
+
if (end <= proseStart) {
|
|
45022
|
+
return;
|
|
45023
|
+
}
|
|
45024
|
+
const prose = lines.slice(proseStart, end).join(`
|
|
45025
|
+
`);
|
|
45026
|
+
if (prose.length > 0) {
|
|
45027
|
+
parts.push({ kind: "prose", text: prose });
|
|
45028
|
+
}
|
|
45029
|
+
};
|
|
45030
|
+
for (let index = 0;index + 1 < lines.length; index += 1) {
|
|
45031
|
+
const headers = markdownTableCells(lines[index] ?? "");
|
|
45032
|
+
if (headers === undefined) {
|
|
45033
|
+
continue;
|
|
45034
|
+
}
|
|
45035
|
+
const alignments = tableAlignments(lines[index + 1] ?? "", headers.length);
|
|
45036
|
+
if (alignments === undefined) {
|
|
45037
|
+
continue;
|
|
45038
|
+
}
|
|
45039
|
+
flushProse(index);
|
|
45040
|
+
const rows = [];
|
|
45041
|
+
let cursor = index + 2;
|
|
45042
|
+
while (cursor < lines.length) {
|
|
45043
|
+
const row = markdownTableCells(lines[cursor] ?? "");
|
|
45044
|
+
if (row === undefined || row.length !== headers.length) {
|
|
45045
|
+
break;
|
|
45046
|
+
}
|
|
45047
|
+
rows.push(row);
|
|
45048
|
+
cursor += 1;
|
|
45049
|
+
}
|
|
45050
|
+
parts.push({ kind: "table", table: { headers, alignments, rows } });
|
|
45051
|
+
proseStart = cursor;
|
|
45052
|
+
index = cursor - 1;
|
|
45053
|
+
}
|
|
45054
|
+
flushProse(lines.length);
|
|
45055
|
+
return parts.length > 0 ? parts : [{ kind: "prose", text }];
|
|
45056
|
+
}
|
|
44794
45057
|
function fenceInfo(line) {
|
|
44795
45058
|
const match = FENCE_LINE.exec(line);
|
|
44796
45059
|
if (match === null) {
|
|
@@ -51141,7 +51404,7 @@ import path47 from "path";
|
|
|
51141
51404
|
// package.json
|
|
51142
51405
|
var package_default = {
|
|
51143
51406
|
name: "@mrciphersmith/keryx",
|
|
51144
|
-
version: "0.2.
|
|
51407
|
+
version: "0.2.143",
|
|
51145
51408
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
51146
51409
|
private: false,
|
|
51147
51410
|
publishConfig: {
|
|
@@ -86842,6 +87105,107 @@ shell_exec: no output for ${info.idleTimeoutMs}ms, so the command was killed (ra
|
|
|
86842
87105
|
};
|
|
86843
87106
|
}
|
|
86844
87107
|
|
|
87108
|
+
// src/harness/tool/builtin/execution-plan-tool.ts
|
|
87109
|
+
init_execution_plan();
|
|
87110
|
+
var statusSchema = { type: "string", enum: [...EXECUTION_PLAN_STATUSES] };
|
|
87111
|
+
function noSession(name) {
|
|
87112
|
+
return { output: `${name}: no active session in this run`, isError: true };
|
|
87113
|
+
}
|
|
87114
|
+
function failed(name, cause) {
|
|
87115
|
+
return { output: `${name} failed: ${cause instanceof Error ? cause.message : String(cause)}`, isError: true };
|
|
87116
|
+
}
|
|
87117
|
+
function executionPlanTools(getSessionDir) {
|
|
87118
|
+
const get = {
|
|
87119
|
+
definition: {
|
|
87120
|
+
name: "plan_get",
|
|
87121
|
+
description: "Read the current structured execution plan. Input: {}.",
|
|
87122
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
87123
|
+
risk: "read"
|
|
87124
|
+
},
|
|
87125
|
+
invoke: async () => {
|
|
87126
|
+
const dir = getSessionDir();
|
|
87127
|
+
if (dir === undefined)
|
|
87128
|
+
return noSession("plan_get");
|
|
87129
|
+
try {
|
|
87130
|
+
return { output: JSON.stringify(await getExecutionPlan(dir) ?? null, null, 2), isError: false };
|
|
87131
|
+
} catch (cause) {
|
|
87132
|
+
return failed("plan_get", cause);
|
|
87133
|
+
}
|
|
87134
|
+
}
|
|
87135
|
+
};
|
|
87136
|
+
const set = {
|
|
87137
|
+
definition: {
|
|
87138
|
+
name: "plan_set",
|
|
87139
|
+
description: "Replace the structured execution plan using optimistic revision control.",
|
|
87140
|
+
inputSchema: {
|
|
87141
|
+
type: "object",
|
|
87142
|
+
properties: {
|
|
87143
|
+
expectedRevision: { type: "integer", minimum: 0 },
|
|
87144
|
+
items: {
|
|
87145
|
+
type: "array",
|
|
87146
|
+
items: {
|
|
87147
|
+
type: "object",
|
|
87148
|
+
properties: { id: { type: "string", minLength: 1 }, title: { type: "string", minLength: 1 }, status: statusSchema },
|
|
87149
|
+
required: ["id", "title", "status"],
|
|
87150
|
+
additionalProperties: false
|
|
87151
|
+
}
|
|
87152
|
+
}
|
|
87153
|
+
},
|
|
87154
|
+
required: ["expectedRevision", "items"],
|
|
87155
|
+
additionalProperties: false
|
|
87156
|
+
},
|
|
87157
|
+
risk: "read"
|
|
87158
|
+
},
|
|
87159
|
+
invoke: async (input) => {
|
|
87160
|
+
const dir = getSessionDir();
|
|
87161
|
+
if (dir === undefined)
|
|
87162
|
+
return noSession("plan_set");
|
|
87163
|
+
try {
|
|
87164
|
+
const plan = await setExecutionPlan(dir, {
|
|
87165
|
+
expectedRevision: input.expectedRevision,
|
|
87166
|
+
items: input.items
|
|
87167
|
+
});
|
|
87168
|
+
return { output: JSON.stringify(plan, null, 2), isError: false };
|
|
87169
|
+
} catch (cause) {
|
|
87170
|
+
return failed("plan_set", cause);
|
|
87171
|
+
}
|
|
87172
|
+
}
|
|
87173
|
+
};
|
|
87174
|
+
const update = {
|
|
87175
|
+
definition: {
|
|
87176
|
+
name: "plan_update",
|
|
87177
|
+
description: "Update one execution-plan item's status using optimistic revision control.",
|
|
87178
|
+
inputSchema: {
|
|
87179
|
+
type: "object",
|
|
87180
|
+
properties: {
|
|
87181
|
+
expectedRevision: { type: "integer", minimum: 0 },
|
|
87182
|
+
itemId: { type: "string", minLength: 1 },
|
|
87183
|
+
status: statusSchema
|
|
87184
|
+
},
|
|
87185
|
+
required: ["expectedRevision", "itemId", "status"],
|
|
87186
|
+
additionalProperties: false
|
|
87187
|
+
},
|
|
87188
|
+
risk: "read"
|
|
87189
|
+
},
|
|
87190
|
+
invoke: async (input) => {
|
|
87191
|
+
const dir = getSessionDir();
|
|
87192
|
+
if (dir === undefined)
|
|
87193
|
+
return noSession("plan_update");
|
|
87194
|
+
try {
|
|
87195
|
+
const plan = await updateExecutionPlan(dir, {
|
|
87196
|
+
expectedRevision: input.expectedRevision,
|
|
87197
|
+
itemId: input.itemId,
|
|
87198
|
+
status: input.status
|
|
87199
|
+
});
|
|
87200
|
+
return { output: JSON.stringify(plan, null, 2), isError: false };
|
|
87201
|
+
} catch (cause) {
|
|
87202
|
+
return failed("plan_update", cause);
|
|
87203
|
+
}
|
|
87204
|
+
}
|
|
87205
|
+
};
|
|
87206
|
+
return [set, update, get];
|
|
87207
|
+
}
|
|
87208
|
+
|
|
86845
87209
|
// src/harness/tool/builtin/slate-tool.ts
|
|
86846
87210
|
init_slate();
|
|
86847
87211
|
init_slate_course();
|
|
@@ -87760,6 +88124,7 @@ function buildInteractiveAgentTools(input) {
|
|
|
87760
88124
|
createAskUserTool(invokeAskUserHost),
|
|
87761
88125
|
slateReadTool(input.cwd, getSessionDir),
|
|
87762
88126
|
slateWriteSeedTool(getSessionDir, idSeq, clock),
|
|
88127
|
+
...executionPlanTools(getSessionDir),
|
|
87763
88128
|
...input.bus?.client() === undefined ? [] : buildBusTools(input.bus.client),
|
|
87764
88129
|
...input.mcp === undefined ? [] : createMcpInteractiveTools({
|
|
87765
88130
|
catalog: input.mcp.catalog,
|
|
@@ -91315,6 +91680,64 @@ class LiveMarkdownBlock {
|
|
|
91315
91680
|
init_config_dir();
|
|
91316
91681
|
import { existsSync as existsSync33 } from "fs";
|
|
91317
91682
|
import path192 from "path";
|
|
91683
|
+
function hexChannels(hex) {
|
|
91684
|
+
return [1, 3, 5].map((offset) => Number.parseInt(hex.slice(offset, offset + 2), 16));
|
|
91685
|
+
}
|
|
91686
|
+
function mixHex(base, accent, accentWeight) {
|
|
91687
|
+
const a = hexChannels(base);
|
|
91688
|
+
const b = hexChannels(accent);
|
|
91689
|
+
const channel = (index) => Math.round((a[index] ?? 0) * (1 - accentWeight) + (b[index] ?? 0) * accentWeight).toString(16).padStart(2, "0");
|
|
91690
|
+
return `#${channel(0)}${channel(1)}${channel(2)}`;
|
|
91691
|
+
}
|
|
91692
|
+
function relativeLuminance(hex) {
|
|
91693
|
+
const linear = hexChannels(hex).map((channel) => {
|
|
91694
|
+
const value = channel / 255;
|
|
91695
|
+
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
91696
|
+
});
|
|
91697
|
+
return 0.2126 * (linear[0] ?? 0) + 0.7152 * (linear[1] ?? 0) + 0.0722 * (linear[2] ?? 0);
|
|
91698
|
+
}
|
|
91699
|
+
function contrastRatio(a, b) {
|
|
91700
|
+
const [light, dark] = [relativeLuminance(a), relativeLuminance(b)].sort((x, y) => y - x);
|
|
91701
|
+
return ((light ?? 0) + 0.05) / ((dark ?? 0) + 0.05);
|
|
91702
|
+
}
|
|
91703
|
+
function transcriptAccent(theme, accent) {
|
|
91704
|
+
for (let weight = 0.5;weight >= 0; weight -= 0.05) {
|
|
91705
|
+
const candidate = mixHex(theme.text, accent, weight);
|
|
91706
|
+
if (contrastRatio(candidate, theme.bg) >= 3 && contrastRatio(candidate, theme.panel) >= 3) {
|
|
91707
|
+
return candidate;
|
|
91708
|
+
}
|
|
91709
|
+
}
|
|
91710
|
+
return theme.text;
|
|
91711
|
+
}
|
|
91712
|
+
function deriveTranscriptPalette(theme) {
|
|
91713
|
+
const heading = transcriptAccent(theme, theme.assistant);
|
|
91714
|
+
const muted = transcriptAccent(theme, theme.muted);
|
|
91715
|
+
const positive = transcriptAccent(theme, theme.ok);
|
|
91716
|
+
const negative = transcriptAccent(theme, theme.error);
|
|
91717
|
+
const focus = transcriptAccent(theme, theme.focus);
|
|
91718
|
+
return {
|
|
91719
|
+
prose: theme.text,
|
|
91720
|
+
heading,
|
|
91721
|
+
emphasis: theme.text,
|
|
91722
|
+
inlineCode: focus,
|
|
91723
|
+
inlineCodeBackground: theme.highlight,
|
|
91724
|
+
blockMeta: theme.muted,
|
|
91725
|
+
tableHeader: heading,
|
|
91726
|
+
tableBorder: theme.border,
|
|
91727
|
+
codePlain: theme.text,
|
|
91728
|
+
codeComment: muted,
|
|
91729
|
+
codeString: positive,
|
|
91730
|
+
codeNumber: focus,
|
|
91731
|
+
codeKeyword: heading,
|
|
91732
|
+
diffContext: theme.text,
|
|
91733
|
+
diffMeta: muted,
|
|
91734
|
+
diffHunk: heading,
|
|
91735
|
+
diffAdd: positive,
|
|
91736
|
+
diffDelete: negative,
|
|
91737
|
+
diffAddBackground: mixHex(theme.panel, theme.ok, 0.12),
|
|
91738
|
+
diffDeleteBackground: mixHex(theme.panel, theme.error, 0.12)
|
|
91739
|
+
};
|
|
91740
|
+
}
|
|
91318
91741
|
var THEME_NAMES = [
|
|
91319
91742
|
"groknight",
|
|
91320
91743
|
"tokyonight",
|
|
@@ -91528,7 +91951,7 @@ var THEMES = {
|
|
|
91528
91951
|
};
|
|
91529
91952
|
var kind = DEFAULT_THEME_ID;
|
|
91530
91953
|
var resolved = THEMES.groknight;
|
|
91531
|
-
var
|
|
91954
|
+
var listeners2 = new Set;
|
|
91532
91955
|
function parseThemeId(raw) {
|
|
91533
91956
|
const key = raw.trim().toLowerCase();
|
|
91534
91957
|
if (key === "auto" || key === "system") {
|
|
@@ -91568,15 +91991,15 @@ function themeLabel(id) {
|
|
|
91568
91991
|
function applyThemeId(id, mode = null) {
|
|
91569
91992
|
kind = id;
|
|
91570
91993
|
resolved = resolveTheme(id, mode);
|
|
91571
|
-
for (const listener of
|
|
91994
|
+
for (const listener of listeners2) {
|
|
91572
91995
|
listener(resolved, kind);
|
|
91573
91996
|
}
|
|
91574
91997
|
return resolved;
|
|
91575
91998
|
}
|
|
91576
91999
|
function onThemeChange(listener) {
|
|
91577
|
-
|
|
92000
|
+
listeners2.add(listener);
|
|
91578
92001
|
return () => {
|
|
91579
|
-
|
|
92002
|
+
listeners2.delete(listener);
|
|
91580
92003
|
};
|
|
91581
92004
|
}
|
|
91582
92005
|
function tuiConfigPath(dir) {
|
|
@@ -92305,11 +92728,15 @@ function clipBody(text, maxLines = MAX_BODY_LINES) {
|
|
|
92305
92728
|
return [...lines.slice(0, maxLines), `\u2026 (${hidden} more line${hidden === 1 ? "" : "s"} not shown)`].join(`
|
|
92306
92729
|
`);
|
|
92307
92730
|
}
|
|
92308
|
-
function
|
|
92731
|
+
function themedChunk(otui, text, fg, bg) {
|
|
92732
|
+
const foreground = otui.fg(fg)(text);
|
|
92733
|
+
return bg === undefined ? foreground : otui.bg(bg)(foreground);
|
|
92734
|
+
}
|
|
92735
|
+
function markdownToChunks(otui, md, palette = deriveTranscriptPalette(getTheme())) {
|
|
92309
92736
|
const out = [];
|
|
92310
92737
|
const plain = (s) => {
|
|
92311
92738
|
if (s.length > 0) {
|
|
92312
|
-
out.push(
|
|
92739
|
+
out.push(themedChunk(otui, s, palette.prose));
|
|
92313
92740
|
}
|
|
92314
92741
|
};
|
|
92315
92742
|
const inline = (text) => {
|
|
@@ -92319,9 +92746,9 @@ function markdownToChunks(otui, md) {
|
|
|
92319
92746
|
while ((m = re.exec(text)) !== null) {
|
|
92320
92747
|
plain(text.slice(last, m.index));
|
|
92321
92748
|
if (m[1] !== undefined) {
|
|
92322
|
-
out.push(otui
|
|
92749
|
+
out.push(themedChunk(otui, m[1].slice(1, -1), palette.inlineCode, palette.inlineCodeBackground));
|
|
92323
92750
|
} else if (m[2] !== undefined) {
|
|
92324
|
-
out.push(otui.bold(m[2].slice(2, -2)));
|
|
92751
|
+
out.push(otui.bold(themedChunk(otui, m[2].slice(2, -2), palette.emphasis)));
|
|
92325
92752
|
}
|
|
92326
92753
|
last = m.index + m[0].length;
|
|
92327
92754
|
}
|
|
@@ -92340,12 +92767,12 @@ function markdownToChunks(otui, md) {
|
|
|
92340
92767
|
`);
|
|
92341
92768
|
}
|
|
92342
92769
|
if (inCode) {
|
|
92343
|
-
out.push(otui.
|
|
92770
|
+
out.push(themedChunk(otui, line, palette.codePlain));
|
|
92344
92771
|
continue;
|
|
92345
92772
|
}
|
|
92346
92773
|
const heading = /^#{1,6}\s+(.*)$/.exec(line);
|
|
92347
92774
|
if (heading !== null) {
|
|
92348
|
-
out.push(otui.
|
|
92775
|
+
out.push(otui.bold(themedChunk(otui, heading[1] ?? "", palette.heading)));
|
|
92349
92776
|
continue;
|
|
92350
92777
|
}
|
|
92351
92778
|
const bullet = /^(\s*)[-*]\s+(.*)$/.exec(line);
|
|
@@ -92358,109 +92785,150 @@ function markdownToChunks(otui, md) {
|
|
|
92358
92785
|
}
|
|
92359
92786
|
return out;
|
|
92360
92787
|
}
|
|
92361
|
-
function diffChunks(otui, text) {
|
|
92788
|
+
function diffChunks(otui, text, palette = deriveTranscriptPalette(getTheme())) {
|
|
92362
92789
|
const out = [];
|
|
92363
92790
|
const lines = splitLines(text);
|
|
92364
92791
|
for (const [index, line] of lines.entries()) {
|
|
92365
92792
|
if (index > 0) {
|
|
92366
|
-
out.push(
|
|
92367
|
-
|
|
92793
|
+
out.push(themedChunk(otui, `
|
|
92794
|
+
`, palette.diffContext));
|
|
92368
92795
|
}
|
|
92369
92796
|
switch (classifyDiffLine(line)) {
|
|
92370
92797
|
case "add":
|
|
92371
|
-
out.push(otui.
|
|
92798
|
+
out.push(themedChunk(otui, line, palette.diffAdd, palette.diffAddBackground));
|
|
92372
92799
|
break;
|
|
92373
92800
|
case "del":
|
|
92374
|
-
out.push(otui.
|
|
92801
|
+
out.push(themedChunk(otui, line, palette.diffDelete, palette.diffDeleteBackground));
|
|
92375
92802
|
break;
|
|
92376
92803
|
case "hunk":
|
|
92377
|
-
out.push(otui.
|
|
92804
|
+
out.push(themedChunk(otui, line, palette.diffHunk));
|
|
92378
92805
|
break;
|
|
92379
92806
|
case "meta":
|
|
92380
|
-
out.push(otui.
|
|
92807
|
+
out.push(themedChunk(otui, line, palette.diffMeta));
|
|
92381
92808
|
break;
|
|
92382
92809
|
default:
|
|
92383
|
-
out.push(
|
|
92810
|
+
out.push(themedChunk(otui, line, palette.diffContext));
|
|
92384
92811
|
}
|
|
92385
92812
|
}
|
|
92386
92813
|
return out;
|
|
92387
92814
|
}
|
|
92388
|
-
function flatDimChunks(otui, text) {
|
|
92815
|
+
function flatDimChunks(otui, text, palette = deriveTranscriptPalette(getTheme())) {
|
|
92389
92816
|
const out = [];
|
|
92390
92817
|
for (const [index, line] of splitLines(text).entries()) {
|
|
92391
92818
|
if (index > 0) {
|
|
92392
|
-
out.push(
|
|
92393
|
-
|
|
92819
|
+
out.push(otui.dim(themedChunk(otui, `
|
|
92820
|
+
`, palette.blockMeta)));
|
|
92394
92821
|
}
|
|
92395
|
-
out.push(otui.dim(line));
|
|
92822
|
+
out.push(otui.dim(themedChunk(otui, line, palette.blockMeta)));
|
|
92396
92823
|
}
|
|
92397
92824
|
return out;
|
|
92398
92825
|
}
|
|
92399
|
-
function codeChunks(otui, text, lang) {
|
|
92826
|
+
function codeChunks(otui, text, lang, palette) {
|
|
92400
92827
|
const out = [];
|
|
92401
92828
|
for (const [index, line] of splitLines(text).entries()) {
|
|
92402
92829
|
if (index > 0) {
|
|
92403
|
-
out.push(
|
|
92404
|
-
|
|
92830
|
+
out.push(themedChunk(otui, `
|
|
92831
|
+
`, palette.codePlain));
|
|
92405
92832
|
}
|
|
92406
92833
|
for (const token of tokenizeCodeLine(line, lang)) {
|
|
92407
92834
|
switch (token.kind) {
|
|
92408
92835
|
case "comment":
|
|
92409
|
-
out.push(otui
|
|
92836
|
+
out.push(themedChunk(otui, token.text, palette.codeComment));
|
|
92410
92837
|
break;
|
|
92411
92838
|
case "string":
|
|
92412
|
-
out.push(otui
|
|
92839
|
+
out.push(themedChunk(otui, token.text, palette.codeString));
|
|
92413
92840
|
break;
|
|
92414
92841
|
case "number":
|
|
92415
|
-
out.push(otui
|
|
92842
|
+
out.push(themedChunk(otui, token.text, palette.codeNumber));
|
|
92416
92843
|
break;
|
|
92417
92844
|
case "keyword":
|
|
92418
|
-
out.push(otui
|
|
92845
|
+
out.push(themedChunk(otui, token.text, palette.codeKeyword));
|
|
92419
92846
|
break;
|
|
92420
92847
|
default:
|
|
92421
|
-
out.push(
|
|
92848
|
+
out.push(themedChunk(otui, token.text, palette.codePlain));
|
|
92422
92849
|
}
|
|
92423
92850
|
}
|
|
92424
92851
|
}
|
|
92425
92852
|
return out;
|
|
92426
92853
|
}
|
|
92427
|
-
function payloadChunks(otui, text, lang = "") {
|
|
92854
|
+
function payloadChunks(otui, text, lang = "", palette = deriveTranscriptPalette(getTheme())) {
|
|
92428
92855
|
const kind = payloadKind(lang, lineCountOf(text));
|
|
92429
92856
|
if (kind === "diff" || looksLikeUnifiedDiff(text)) {
|
|
92430
|
-
return diffChunks(otui, text);
|
|
92857
|
+
return diffChunks(otui, text, palette);
|
|
92431
92858
|
}
|
|
92432
92859
|
if (kind === "markdown" || lang.length === 0) {
|
|
92433
|
-
return markdownToChunks(otui, text);
|
|
92860
|
+
return markdownToChunks(otui, text, palette);
|
|
92434
92861
|
}
|
|
92435
|
-
return codeChunks(otui, text, lang);
|
|
92862
|
+
return codeChunks(otui, text, lang, palette);
|
|
92436
92863
|
}
|
|
92437
92864
|
function createSegmentView(otui, renderer, parent, segment) {
|
|
92438
92865
|
viewSeq += 1;
|
|
92439
92866
|
const id = `seg${viewSeq}`;
|
|
92440
92867
|
if (segment.kind === "text") {
|
|
92441
|
-
|
|
92442
|
-
|
|
92443
|
-
|
|
92444
|
-
|
|
92445
|
-
|
|
92868
|
+
let mounted = [];
|
|
92869
|
+
const paint = (source) => {
|
|
92870
|
+
for (const child of mounted) {
|
|
92871
|
+
try {
|
|
92872
|
+
parent.remove(child);
|
|
92873
|
+
child.destroyRecursively();
|
|
92874
|
+
} catch {}
|
|
92875
|
+
}
|
|
92876
|
+
mounted = [];
|
|
92877
|
+
const palette = deriveTranscriptPalette(getTheme());
|
|
92878
|
+
for (const [index, part] of parseMarkdownTables(source).entries()) {
|
|
92879
|
+
if (part.kind === "prose") {
|
|
92880
|
+
const text = new otui.TextRenderable(renderer, {
|
|
92881
|
+
id: `${id}-prose-${index}`,
|
|
92882
|
+
content: new otui.StyledText(markdownToChunks(otui, part.text, palette))
|
|
92883
|
+
});
|
|
92884
|
+
parent.add(text);
|
|
92885
|
+
mounted.push(text);
|
|
92886
|
+
continue;
|
|
92887
|
+
}
|
|
92888
|
+
const header = part.table.headers.map((cell) => [otui.bold(themedChunk(otui, cell, palette.tableHeader))]);
|
|
92889
|
+
const rows = part.table.rows.map((row) => row.map((cell) => markdownToChunks(otui, cell, palette)));
|
|
92890
|
+
const table = new otui.TextTableRenderable(renderer, {
|
|
92891
|
+
id: `${id}-table-${index}`,
|
|
92892
|
+
content: [header, ...rows],
|
|
92893
|
+
width: "100%",
|
|
92894
|
+
wrapMode: "word",
|
|
92895
|
+
columnWidthMode: "full",
|
|
92896
|
+
columnFitter: "balanced",
|
|
92897
|
+
cellPaddingX: 1,
|
|
92898
|
+
cellPaddingY: 0,
|
|
92899
|
+
showBorders: true,
|
|
92900
|
+
outerBorder: true,
|
|
92901
|
+
borderStyle: "rounded",
|
|
92902
|
+
borderColor: palette.tableBorder,
|
|
92903
|
+
fg: palette.prose,
|
|
92904
|
+
backgroundColor: getTheme().panel
|
|
92905
|
+
});
|
|
92906
|
+
parent.add(table);
|
|
92907
|
+
mounted.push(table);
|
|
92908
|
+
}
|
|
92909
|
+
};
|
|
92910
|
+
paint(segment.text);
|
|
92446
92911
|
return {
|
|
92447
92912
|
kind: "text",
|
|
92448
92913
|
update: (next) => {
|
|
92449
92914
|
if (next.kind === "text") {
|
|
92450
|
-
|
|
92915
|
+
paint(next.text);
|
|
92451
92916
|
}
|
|
92452
92917
|
},
|
|
92453
92918
|
destroy: () => {
|
|
92454
|
-
|
|
92455
|
-
|
|
92456
|
-
|
|
92457
|
-
|
|
92919
|
+
for (const child of mounted) {
|
|
92920
|
+
try {
|
|
92921
|
+
parent.remove(child);
|
|
92922
|
+
child.destroyRecursively();
|
|
92923
|
+
} catch {}
|
|
92924
|
+
}
|
|
92925
|
+
mounted = [];
|
|
92458
92926
|
}
|
|
92459
92927
|
};
|
|
92460
92928
|
}
|
|
92461
92929
|
const tag = (lang, body) => {
|
|
92462
92930
|
const n = lineCountOf(body);
|
|
92463
|
-
return `${lang.length > 0 ? lang : "text"} \xB7 ${n} ${n === 1 ? "line" : "lines"} \xB7 y copy`;
|
|
92931
|
+
return `${lang.length > 0 ? lang : "text"} \xB7 ${n} ${n === 1 ? "line" : "lines"} \xB7 ctrl+o then y copy`;
|
|
92464
92932
|
};
|
|
92465
92933
|
const frameWidth = (lang, body) => Math.max(hugWidth(body, FRAME_CHROME), hugWidth(tag(lang, body), FRAME_CHROME));
|
|
92466
92934
|
const frame = new otui.BoxRenderable(renderer, {
|
|
@@ -92471,12 +92939,15 @@ function createSegmentView(otui, renderer, parent, segment) {
|
|
|
92471
92939
|
borderStyle: "rounded",
|
|
92472
92940
|
border: true,
|
|
92473
92941
|
borderColor: frameColor(),
|
|
92942
|
+
backgroundColor: getTheme().panel,
|
|
92474
92943
|
paddingLeft: 1,
|
|
92475
92944
|
paddingRight: 1
|
|
92476
92945
|
});
|
|
92477
92946
|
const header = new otui.TextRenderable(renderer, {
|
|
92478
92947
|
id: `${id}-tag`,
|
|
92479
|
-
content: otui.
|
|
92948
|
+
content: new otui.StyledText([
|
|
92949
|
+
themedChunk(otui, tag(segment.lang, segment.body), deriveTranscriptPalette(getTheme()).blockMeta)
|
|
92950
|
+
])
|
|
92480
92951
|
});
|
|
92481
92952
|
const body = new otui.TextRenderable(renderer, {
|
|
92482
92953
|
id: `${id}-body`,
|
|
@@ -92491,7 +92962,9 @@ function createSegmentView(otui, renderer, parent, segment) {
|
|
|
92491
92962
|
if (next.kind !== "code") {
|
|
92492
92963
|
return;
|
|
92493
92964
|
}
|
|
92494
|
-
header.content = otui.
|
|
92965
|
+
header.content = new otui.StyledText([
|
|
92966
|
+
themedChunk(otui, tag(next.lang, next.body), deriveTranscriptPalette(getTheme()).blockMeta)
|
|
92967
|
+
]);
|
|
92495
92968
|
body.content = new otui.StyledText(payloadChunks(otui, next.body, next.lang));
|
|
92496
92969
|
frame.maxWidth = frameWidth(next.lang, next.body);
|
|
92497
92970
|
},
|
|
@@ -95107,12 +95580,50 @@ function themeColorRemap(from, to) {
|
|
|
95107
95580
|
remap.set(oldColor, newColor);
|
|
95108
95581
|
}
|
|
95109
95582
|
}
|
|
95583
|
+
const fromTranscript = deriveTranscriptPalette(from);
|
|
95584
|
+
const toTranscript = deriveTranscriptPalette(to);
|
|
95585
|
+
for (const slot of Object.keys(fromTranscript)) {
|
|
95586
|
+
const oldColor = fromTranscript[slot];
|
|
95587
|
+
const newColor = toTranscript[slot];
|
|
95588
|
+
if (oldColor !== newColor) {
|
|
95589
|
+
remap.set(oldColor, newColor);
|
|
95590
|
+
}
|
|
95591
|
+
}
|
|
95110
95592
|
return remap;
|
|
95111
95593
|
}
|
|
95112
|
-
function
|
|
95594
|
+
function recolorContent(value, remap, color) {
|
|
95595
|
+
if (Array.isArray(value)) {
|
|
95596
|
+
let changed = false;
|
|
95597
|
+
for (const item of value) {
|
|
95598
|
+
changed = recolorContent(item, remap, color) || changed;
|
|
95599
|
+
}
|
|
95600
|
+
return changed;
|
|
95601
|
+
}
|
|
95602
|
+
if (value === null || typeof value !== "object") {
|
|
95603
|
+
return false;
|
|
95604
|
+
}
|
|
95605
|
+
const target = value;
|
|
95606
|
+
let changed = false;
|
|
95607
|
+
if (typeof target.text === "string") {
|
|
95608
|
+
for (const prop of ["fg", "bg"]) {
|
|
95609
|
+
const hex = themeColorToHex(target[prop]);
|
|
95610
|
+
const next = hex === undefined ? undefined : remap.get(hex);
|
|
95611
|
+
if (next !== undefined) {
|
|
95612
|
+
target[prop] = color(next);
|
|
95613
|
+
changed = true;
|
|
95614
|
+
}
|
|
95615
|
+
}
|
|
95616
|
+
}
|
|
95617
|
+
if (target.chunks !== undefined) {
|
|
95618
|
+
changed = recolorContent(target.chunks, remap, color) || changed;
|
|
95619
|
+
}
|
|
95620
|
+
return changed;
|
|
95621
|
+
}
|
|
95622
|
+
function recolorThemeTree(node, fromTheme, toTheme, color = (hex) => hex) {
|
|
95113
95623
|
if (node === null || node === undefined) {
|
|
95114
95624
|
return;
|
|
95115
95625
|
}
|
|
95626
|
+
const remap = themeColorRemap(fromTheme, toTheme);
|
|
95116
95627
|
const target = node;
|
|
95117
95628
|
for (const prop of ["borderColor", "backgroundColor", "fg"]) {
|
|
95118
95629
|
const hex = themeColorToHex(target[prop]);
|
|
@@ -95123,9 +95634,13 @@ function recolorThemeTree(node, remap) {
|
|
|
95123
95634
|
}
|
|
95124
95635
|
}
|
|
95125
95636
|
}
|
|
95637
|
+
const content = target.content;
|
|
95638
|
+
if (content !== undefined && recolorContent(content, remap, color)) {
|
|
95639
|
+
target.content = content;
|
|
95640
|
+
}
|
|
95126
95641
|
if (typeof target.getChildren === "function") {
|
|
95127
95642
|
for (const child of target.getChildren()) {
|
|
95128
|
-
recolorThemeTree(child,
|
|
95643
|
+
recolorThemeTree(child, fromTheme, toTheme, color);
|
|
95129
95644
|
}
|
|
95130
95645
|
}
|
|
95131
95646
|
}
|
|
@@ -95745,7 +96260,6 @@ async function createShellChrome(otui, renderer, opts) {
|
|
|
95745
96260
|
}
|
|
95746
96261
|
});
|
|
95747
96262
|
const applyTheme = (theme = getTheme()) => {
|
|
95748
|
-
const remap = themeColorRemap(appliedTheme, theme);
|
|
95749
96263
|
try {
|
|
95750
96264
|
r.setBackgroundColor(theme.bg);
|
|
95751
96265
|
} catch {}
|
|
@@ -95770,14 +96284,15 @@ async function createShellChrome(otui, renderer, opts) {
|
|
|
95770
96284
|
menu.selectedTextColor = theme.focus;
|
|
95771
96285
|
menu.descriptionColor = theme.muted;
|
|
95772
96286
|
menu.selectedDescriptionColor = theme.muted;
|
|
95773
|
-
|
|
95774
|
-
recolorThemeTree(
|
|
95775
|
-
recolorThemeTree(
|
|
95776
|
-
recolorThemeTree(
|
|
95777
|
-
recolorThemeTree(
|
|
95778
|
-
recolorThemeTree(
|
|
95779
|
-
recolorThemeTree(
|
|
95780
|
-
recolorThemeTree(
|
|
96287
|
+
const chunkColor = (hex) => otui.RGBA.fromHex(hex);
|
|
96288
|
+
recolorThemeTree(transcript, appliedTheme, theme, chunkColor);
|
|
96289
|
+
recolorThemeTree(dock, appliedTheme, theme, chunkColor);
|
|
96290
|
+
recolorThemeTree(queueDock, appliedTheme, theme, chunkColor);
|
|
96291
|
+
recolorThemeTree(sidebarTop, appliedTheme, theme, chunkColor);
|
|
96292
|
+
recolorThemeTree(menu, appliedTheme, theme, chunkColor);
|
|
96293
|
+
recolorThemeTree(composer, appliedTheme, theme, chunkColor);
|
|
96294
|
+
recolorThemeTree(header, appliedTheme, theme, chunkColor);
|
|
96295
|
+
recolorThemeTree(footer, appliedTheme, theme, chunkColor);
|
|
95781
96296
|
appliedTheme = theme;
|
|
95782
96297
|
};
|
|
95783
96298
|
const unsubTheme = onThemeChange((theme) => applyTheme(theme));
|
|
@@ -102363,6 +102878,96 @@ function paintBackgroundJobSidebar(otui, renderer, parent, entries, options) {
|
|
|
102363
102878
|
}
|
|
102364
102879
|
}
|
|
102365
102880
|
|
|
102881
|
+
// src/tui/execution-plan-panel.ts
|
|
102882
|
+
init_execution_plan();
|
|
102883
|
+
var GLYPHS = {
|
|
102884
|
+
completed: "\u2713",
|
|
102885
|
+
in_progress: "\u25B6",
|
|
102886
|
+
pending: "\u25CB",
|
|
102887
|
+
blocked: "!",
|
|
102888
|
+
skipped: "\u2212"
|
|
102889
|
+
};
|
|
102890
|
+
function truncate7(text, width) {
|
|
102891
|
+
if (width <= 0)
|
|
102892
|
+
return "";
|
|
102893
|
+
if (displayWidth(text) <= width)
|
|
102894
|
+
return text;
|
|
102895
|
+
if (width === 1)
|
|
102896
|
+
return "\u2026";
|
|
102897
|
+
let prefix = "";
|
|
102898
|
+
for (const character of text) {
|
|
102899
|
+
if (displayWidth(prefix + character) > width - 1)
|
|
102900
|
+
break;
|
|
102901
|
+
prefix += character;
|
|
102902
|
+
}
|
|
102903
|
+
return `${prefix}\u2026`;
|
|
102904
|
+
}
|
|
102905
|
+
function projectExecutionPlanPanel(plan, options) {
|
|
102906
|
+
if (plan === undefined || plan.items.length === 0) {
|
|
102907
|
+
return { visible: false, rows: [], revision: undefined };
|
|
102908
|
+
}
|
|
102909
|
+
const size = Math.max(1, Math.min(options.maxRows ?? 7, 7, plan.items.length));
|
|
102910
|
+
const active = Math.max(0, plan.items.findIndex((item) => item.status === "in_progress"));
|
|
102911
|
+
const start = Math.max(0, Math.min(active - Math.floor(size / 2), plan.items.length - size));
|
|
102912
|
+
const rows = plan.items.slice(start, start + size).map((item) => {
|
|
102913
|
+
const glyph = GLYPHS[item.status];
|
|
102914
|
+
return { id: item.id, status: item.status, glyph, text: truncate7(`${glyph} ${item.title}`, options.width) };
|
|
102915
|
+
});
|
|
102916
|
+
return { visible: true, rows, revision: plan.revision };
|
|
102917
|
+
}
|
|
102918
|
+
function mountExecutionPlanPanel(otui, renderer, parent, options) {
|
|
102919
|
+
const box = parent;
|
|
102920
|
+
let paintedDir;
|
|
102921
|
+
let paintedRevision;
|
|
102922
|
+
let refreshGeneration = 0;
|
|
102923
|
+
let disposed = false;
|
|
102924
|
+
const paint = (plan, dir) => {
|
|
102925
|
+
const projected = projectExecutionPlanPanel(plan, {
|
|
102926
|
+
width: options.width,
|
|
102927
|
+
...options.maxRows === undefined ? {} : { maxRows: options.maxRows }
|
|
102928
|
+
});
|
|
102929
|
+
if (paintedDir === dir && paintedRevision === projected.revision)
|
|
102930
|
+
return;
|
|
102931
|
+
paintedDir = dir;
|
|
102932
|
+
paintedRevision = projected.revision;
|
|
102933
|
+
if (box.getChildren !== undefined && box.remove !== undefined) {
|
|
102934
|
+
for (const child of [...box.getChildren()])
|
|
102935
|
+
box.remove(child);
|
|
102936
|
+
}
|
|
102937
|
+
const Ctor = otui.TextRenderable;
|
|
102938
|
+
if (!projected.visible || box.add === undefined || Ctor === undefined)
|
|
102939
|
+
return;
|
|
102940
|
+
box.add(new Ctor(renderer, { id: "sb-plan-h", content: "Plan", marginTop: 1 }));
|
|
102941
|
+
for (const row of projected.rows)
|
|
102942
|
+
box.add(new Ctor(renderer, { id: `sb-plan-${row.id}`, content: row.text }));
|
|
102943
|
+
};
|
|
102944
|
+
const refresh = async () => {
|
|
102945
|
+
const dir = options.getSessionDir();
|
|
102946
|
+
const generation = ++refreshGeneration;
|
|
102947
|
+
const plan = dir === undefined ? undefined : await getExecutionPlan(dir).catch(() => {
|
|
102948
|
+
return;
|
|
102949
|
+
});
|
|
102950
|
+
if (disposed || generation !== refreshGeneration || dir !== options.getSessionDir())
|
|
102951
|
+
return;
|
|
102952
|
+
paint(plan, dir);
|
|
102953
|
+
};
|
|
102954
|
+
const unsubscribe = subscribeExecutionPlans((dir, plan) => {
|
|
102955
|
+
if (dir === options.getSessionDir()) {
|
|
102956
|
+
refreshGeneration += 1;
|
|
102957
|
+
paint(plan, dir);
|
|
102958
|
+
}
|
|
102959
|
+
});
|
|
102960
|
+
refresh();
|
|
102961
|
+
return {
|
|
102962
|
+
refresh,
|
|
102963
|
+
dispose: () => {
|
|
102964
|
+
disposed = true;
|
|
102965
|
+
refreshGeneration += 1;
|
|
102966
|
+
unsubscribe();
|
|
102967
|
+
}
|
|
102968
|
+
};
|
|
102969
|
+
}
|
|
102970
|
+
|
|
102366
102971
|
// src/tui/bus-command.ts
|
|
102367
102972
|
var SEND_USAGE = "usage: /bus send @<name> <text> (or /bus @<name> <text>)";
|
|
102368
102973
|
var ASK_USAGE = "usage: /bus ask @<name> <text>";
|
|
@@ -104346,6 +104951,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
104346
104951
|
let mountedChrome;
|
|
104347
104952
|
let liveDeps;
|
|
104348
104953
|
let liveJobs;
|
|
104954
|
+
let disposeExecutionPlanPanel;
|
|
104349
104955
|
let detachExternal;
|
|
104350
104956
|
let liveBus;
|
|
104351
104957
|
let destroyed = false;
|
|
@@ -104368,6 +104974,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
104368
104974
|
try {
|
|
104369
104975
|
const r = renderer = await createShellRenderer(otui, {
|
|
104370
104976
|
onDestroy: () => {
|
|
104977
|
+
disposeExecutionPlanPanel?.();
|
|
104371
104978
|
destroyed = true;
|
|
104372
104979
|
foregroundOperation.cancel("renderer destroyed");
|
|
104373
104980
|
foregroundOperation.dispose();
|
|
@@ -104407,6 +105014,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
104407
105014
|
saveShellConfig(sel.baseUrl === undefined ? { provider: sel.provider, model: sel.model } : { provider: sel.provider, model: sel.model, baseUrl: sel.baseUrl });
|
|
104408
105015
|
let currentSel = sel;
|
|
104409
105016
|
const suggestionGate = new NextStepSuggestionGate;
|
|
105017
|
+
let slateSession;
|
|
104410
105018
|
const liveSlateSession = () => whilePersisting(slateSession, () => sessionLease.canPersist());
|
|
104411
105019
|
let deps = await opts.makeAgentDeps(sel, liveSlateSession, busClientRef);
|
|
104412
105020
|
liveDeps = deps;
|
|
@@ -104550,6 +105158,18 @@ async function launchTuiAgentShell(opts) {
|
|
|
104550
105158
|
marginTop: 1
|
|
104551
105159
|
});
|
|
104552
105160
|
sidebar.add(sbSubagents);
|
|
105161
|
+
const sbPlan = new otui.BoxRenderable(r, {
|
|
105162
|
+
id: "sb-plan",
|
|
105163
|
+
flexDirection: "column",
|
|
105164
|
+
flexShrink: 0
|
|
105165
|
+
});
|
|
105166
|
+
sidebar.add(sbPlan);
|
|
105167
|
+
const executionPlanPanel = mountExecutionPlanPanel(otui, r, sbPlan, {
|
|
105168
|
+
getSessionDir: () => liveSlateSession()?.dir,
|
|
105169
|
+
width: SIDEBAR_TEXT_WIDTH,
|
|
105170
|
+
maxRows: 7
|
|
105171
|
+
});
|
|
105172
|
+
disposeExecutionPlanPanel = executionPlanPanel.dispose;
|
|
104553
105173
|
const sbJobs = new otui.BoxRenderable(r, {
|
|
104554
105174
|
id: "sb-jobs",
|
|
104555
105175
|
flexDirection: "column",
|
|
@@ -105134,9 +105754,9 @@ async function launchTuiAgentShell(opts) {
|
|
|
105134
105754
|
let archive = [];
|
|
105135
105755
|
let nextArchiveIndex = 0;
|
|
105136
105756
|
let sessionPersistTimer;
|
|
105137
|
-
let slateSession;
|
|
105138
105757
|
const bindSlateToLiveSession = () => {
|
|
105139
105758
|
slateSession = freshSlateSessionRef(liveSession.dir, sessionCwd);
|
|
105759
|
+
executionPlanPanel.refresh();
|
|
105140
105760
|
};
|
|
105141
105761
|
const onContextCompaction = (r) => {
|
|
105142
105762
|
if (!sessionLease.canPersist()) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.143",
|
|
4
4
|
"description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|