@kontextmind/kxm 0.7.115 → 0.7.118
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/.claude-plugin/marketplace.json +1 -1
- package/package.json +1 -1
- package/packages/core/tui/dist/index.js +478 -0
- package/packages/core/tui/src/adapters/claude.ts +131 -0
- package/packages/core/tui/src/adapters/historyExport.ts +35 -0
- package/packages/core/tui/src/adapters/omp.ts +174 -0
- package/packages/core/tui/src/adapters/pi.ts +34 -0
- package/packages/core/tui/src/exports/index.ts +8 -0
- package/packages/core/tui/src/tui/history.ts +106 -0
- package/packages/core/tui/src/tui/modelSelector.ts +43 -0
- package/packages/core/tui/src/tui/optimizer.ts +154 -0
- package/packages/core/tui/src/tui/queue.ts +90 -0
- package/packages/core/tui/src/tui/roleBudget.ts +104 -0
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +220 -22
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +36 -4
- package/plugins/kxm/dist/runtime.js +36 -4
- package/plugins/kxm/dist/server.js +22 -0
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +6 -0
- package/plugins/kxm/src/autocomplete.ts +3 -0
- package/plugins/kxm/src/cli/plugins.ts +201 -0
- package/plugins/kxm/src/cli.ts +14 -0
- package/plugins/kxm/src/harness.ts +36 -4
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/scripts/demo-overlay.mjs +175 -0
package/package.json
CHANGED
|
@@ -1129,6 +1129,268 @@ function createKxmTuiPanelComponent(options) {
|
|
|
1129
1129
|
return new KxmTuiPanelComponent(options);
|
|
1130
1130
|
}
|
|
1131
1131
|
|
|
1132
|
+
// packages/core/tui/src/tui/optimizer.ts
|
|
1133
|
+
function evaluatePlanOptimizations(tasks) {
|
|
1134
|
+
const proposals = [];
|
|
1135
|
+
const byStage = /* @__PURE__ */ new Map();
|
|
1136
|
+
for (const task of tasks) {
|
|
1137
|
+
const list = byStage.get(task.stageId) ?? [];
|
|
1138
|
+
list.push(task);
|
|
1139
|
+
byStage.set(task.stageId, list);
|
|
1140
|
+
}
|
|
1141
|
+
for (const [stageId, stageTasks] of byStage) {
|
|
1142
|
+
if (stageTasks.length >= 2) {
|
|
1143
|
+
const independent = stageTasks.filter((t) => t.dependencies.length === 0 || t.dependencies.every((dep) => !stageTasks.some((st) => st.id === dep)));
|
|
1144
|
+
if (independent.length >= 2) {
|
|
1145
|
+
proposals.push({
|
|
1146
|
+
id: `opt_fanout_${stageId}`,
|
|
1147
|
+
category: "concurrency",
|
|
1148
|
+
title: `Parallelize ${independent.length} tasks in stage "${stageId}"`,
|
|
1149
|
+
description: `Tasks [${independent.map((t) => t.id).join(", ")}] have zero mutual dependencies and can execute concurrently via kxm_fanout.`,
|
|
1150
|
+
targetTaskIds: independent.map((t) => t.id),
|
|
1151
|
+
projectedTimeSavingsSeconds: Math.round(independent.length * 15),
|
|
1152
|
+
projectedCostSavingsPercent: 0,
|
|
1153
|
+
selected: true
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
for (const task of tasks) {
|
|
1159
|
+
const isFrontier = /claude-3-5-sonnet|claude-3-opus|gpt-4o|gpt-5/i.test(task.model);
|
|
1160
|
+
if (isFrontier && (task.category === "docs" || /doc|readme|markdown/i.test(task.title))) {
|
|
1161
|
+
proposals.push({
|
|
1162
|
+
id: `opt_cost_${task.id}`,
|
|
1163
|
+
category: "cost",
|
|
1164
|
+
title: `Downscale model for documentation task "${task.title}"`,
|
|
1165
|
+
description: `Task "${task.id}" is documentation-focused. Routing to a high-throughput lightweight model saves significant token budget.`,
|
|
1166
|
+
targetTaskIds: [task.id],
|
|
1167
|
+
projectedTimeSavingsSeconds: 5,
|
|
1168
|
+
projectedCostSavingsPercent: 65,
|
|
1169
|
+
suggestedHarness: "pi",
|
|
1170
|
+
suggestedModel: "qwen/qwen3-coder-plus",
|
|
1171
|
+
selected: true
|
|
1172
|
+
});
|
|
1173
|
+
} else if (isFrontier && (task.category === "lint" || /lint|format|style/i.test(task.title))) {
|
|
1174
|
+
proposals.push({
|
|
1175
|
+
id: `opt_lint_${task.id}`,
|
|
1176
|
+
category: "cost",
|
|
1177
|
+
title: `Downscale model for formatting/lint task "${task.title}"`,
|
|
1178
|
+
description: `Task "${task.id}" performs deterministic formatting/linting. Use a fast local/tier-1 helper.`,
|
|
1179
|
+
targetTaskIds: [task.id],
|
|
1180
|
+
projectedTimeSavingsSeconds: 8,
|
|
1181
|
+
projectedCostSavingsPercent: 80,
|
|
1182
|
+
suggestedHarness: "pi",
|
|
1183
|
+
suggestedModel: "google/gemini-2.5-flash",
|
|
1184
|
+
selected: true
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
const totalTimeSavings = proposals.reduce((acc, p) => acc + p.projectedTimeSavingsSeconds, 0);
|
|
1189
|
+
const totalCostSavings = proposals.length > 0 ? Math.round(proposals.reduce((acc, p) => acc + p.projectedCostSavingsPercent, 0) / proposals.length) : 0;
|
|
1190
|
+
return {
|
|
1191
|
+
proposals,
|
|
1192
|
+
totalProjectedTimeSavingsSeconds: totalTimeSavings,
|
|
1193
|
+
totalProjectedCostSavingsPercent: totalCostSavings
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
function applyPlanOptimizations(tasks, proposals) {
|
|
1197
|
+
const selected = proposals.filter((p) => p.selected);
|
|
1198
|
+
if (selected.length === 0) return { tasks: [...tasks], appliedCount: 0 };
|
|
1199
|
+
const modelOverrides = /* @__PURE__ */ new Map();
|
|
1200
|
+
for (const proposal of selected) {
|
|
1201
|
+
if (proposal.category === "cost" && proposal.suggestedModel) {
|
|
1202
|
+
for (const targetId of proposal.targetTaskIds) {
|
|
1203
|
+
modelOverrides.set(targetId, {
|
|
1204
|
+
...proposal.suggestedHarness ? { harness: proposal.suggestedHarness } : {},
|
|
1205
|
+
model: proposal.suggestedModel
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
const updated = tasks.map((task) => {
|
|
1211
|
+
const override = modelOverrides.get(task.id);
|
|
1212
|
+
if (!override) return task;
|
|
1213
|
+
return {
|
|
1214
|
+
...task,
|
|
1215
|
+
...override.harness ? { harness: override.harness } : {},
|
|
1216
|
+
...override.model ? { model: override.model } : {}
|
|
1217
|
+
};
|
|
1218
|
+
});
|
|
1219
|
+
return {
|
|
1220
|
+
tasks: updated,
|
|
1221
|
+
appliedCount: selected.length
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// packages/core/tui/src/tui/history.ts
|
|
1226
|
+
function filterHistoryEvents(events, options = {}) {
|
|
1227
|
+
const category = options.category ?? "all";
|
|
1228
|
+
const query = options.query?.trim().toLowerCase();
|
|
1229
|
+
return events.filter((ev) => {
|
|
1230
|
+
if (category !== "all" && ev.category !== category) return false;
|
|
1231
|
+
if (!query) return true;
|
|
1232
|
+
const matchTarget = `${ev.id} ${ev.title} ${ev.stageId ?? ""} ${ev.taskId ?? ""} ${ev.role ?? ""} ${ev.model ?? ""} ${ev.journalSummary ?? ""}`.toLowerCase();
|
|
1233
|
+
return matchTarget.includes(query);
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
function renderWorkflowHistoryMarkdown(runId, events) {
|
|
1237
|
+
const totalSpend = events.reduce((acc, ev) => acc + (ev.costUsd ?? 0), 0);
|
|
1238
|
+
const passedCount = events.filter((ev) => ev.status === "passed").length;
|
|
1239
|
+
const failedCount = events.filter((ev) => ev.status === "failed").length;
|
|
1240
|
+
let mdContent = `# KXM Workflow Retrospective: ${runId}
|
|
1241
|
+
|
|
1242
|
+
`;
|
|
1243
|
+
mdContent += `**Total Events:** ${events.length} (Passed: ${passedCount}, Failed: ${failedCount})
|
|
1244
|
+
`;
|
|
1245
|
+
mdContent += `**Total Spend:** $${totalSpend.toFixed(4)} USD
|
|
1246
|
+
|
|
1247
|
+
`;
|
|
1248
|
+
mdContent += `## Chronological Event Journal
|
|
1249
|
+
|
|
1250
|
+
`;
|
|
1251
|
+
mdContent += `| Timestamp | Category | Title | Status | Role / Model | Duration | Spend |
|
|
1252
|
+
`;
|
|
1253
|
+
mdContent += `|---|---|---|---|---|---|---|
|
|
1254
|
+
`;
|
|
1255
|
+
for (const ev of events) {
|
|
1256
|
+
const elapsed = ev.durationMs !== void 0 ? `${(ev.durationMs / 1e3).toFixed(1)}s` : "-";
|
|
1257
|
+
const spend = ev.costUsd !== void 0 ? `$${ev.costUsd.toFixed(4)}` : "-";
|
|
1258
|
+
const roleModel = ev.role ? `${ev.role} (${ev.harness ?? "-"}/${ev.model ?? "-"})` : "-";
|
|
1259
|
+
mdContent += `| ${ev.timestamp} | ${ev.category} | ${ev.title} | ${ev.status} | ${roleModel} | ${elapsed} | ${spend} |
|
|
1260
|
+
`;
|
|
1261
|
+
}
|
|
1262
|
+
mdContent += `
|
|
1263
|
+
## Produced Artifacts & Proofs
|
|
1264
|
+
|
|
1265
|
+
`;
|
|
1266
|
+
for (const ev of events) {
|
|
1267
|
+
if (ev.artifacts && ev.artifacts.length > 0) {
|
|
1268
|
+
mdContent += `### ${ev.title} (${ev.id})
|
|
1269
|
+
`;
|
|
1270
|
+
for (const art of ev.artifacts) {
|
|
1271
|
+
mdContent += `- \`${art}\`
|
|
1272
|
+
`;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return mdContent;
|
|
1277
|
+
}
|
|
1278
|
+
function formatWorkflowHistoryJsonl(runId, events) {
|
|
1279
|
+
return events.map((ev) => JSON.stringify({
|
|
1280
|
+
schema: "kxm.workflow-history-event.v1",
|
|
1281
|
+
runId,
|
|
1282
|
+
...ev
|
|
1283
|
+
})).join("\n") + "\n";
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// packages/core/tui/src/tui/queue.ts
|
|
1287
|
+
function reorderQueuedTasks(tasks, fromIndex, toIndex) {
|
|
1288
|
+
if (fromIndex < 0 || fromIndex >= tasks.length || toIndex < 0 || toIndex >= tasks.length || fromIndex === toIndex) {
|
|
1289
|
+
return { success: false, tasks, warning: "invalid_indices" };
|
|
1290
|
+
}
|
|
1291
|
+
const target = tasks[fromIndex];
|
|
1292
|
+
if (target.status === "in_flight" || target.status === "completed") {
|
|
1293
|
+
return {
|
|
1294
|
+
success: false,
|
|
1295
|
+
tasks,
|
|
1296
|
+
warning: `cannot move ${target.status} task "${target.id}"`
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
const draft = [...tasks];
|
|
1300
|
+
const [removed] = draft.splice(fromIndex, 1);
|
|
1301
|
+
draft.splice(toIndex, 0, removed);
|
|
1302
|
+
const idToIndex = /* @__PURE__ */ new Map();
|
|
1303
|
+
for (let i = 0; i < draft.length; i++) {
|
|
1304
|
+
idToIndex.set(draft[i].id, i);
|
|
1305
|
+
}
|
|
1306
|
+
for (let i = 0; i < draft.length; i++) {
|
|
1307
|
+
const task = draft[i];
|
|
1308
|
+
for (const depId of task.dependencies) {
|
|
1309
|
+
const depIndex = idToIndex.get(depId);
|
|
1310
|
+
if (depIndex !== void 0 && depIndex > i) {
|
|
1311
|
+
return {
|
|
1312
|
+
success: false,
|
|
1313
|
+
tasks,
|
|
1314
|
+
warning: `Prerequisite violation: "${task.id}" depends on "${depId}", which is scheduled later at position ${depIndex + 1}`
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
return {
|
|
1320
|
+
success: true,
|
|
1321
|
+
tasks: draft
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
// packages/core/tui/src/tui/roleBudget.ts
|
|
1326
|
+
function evaluateRoleBudget(config, state, projectedCostUsd = 0) {
|
|
1327
|
+
const projectedRunSpend = state.currentRunSpendUsd + projectedCostUsd;
|
|
1328
|
+
if (config.runSpendCapUsd !== void 0 && projectedRunSpend > config.runSpendCapUsd) {
|
|
1329
|
+
if (config.onExhausted === "cascade_to_roster" && config.fallbackModel) {
|
|
1330
|
+
return {
|
|
1331
|
+
status: "exhausted",
|
|
1332
|
+
nextAction: "cascade",
|
|
1333
|
+
fallbackModel: config.fallbackModel,
|
|
1334
|
+
fallbackHarness: config.fallbackHarness ?? "pi",
|
|
1335
|
+
warning: `Role "${config.roleId}" reached run spend cap ($${config.runSpendCapUsd.toFixed(2)}); rolling over to ${config.fallbackModel}`
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
if (config.onExhausted === "borrow_from_pool" && (config.emergencyPoolLimitUsd ?? 0) > state.borrowedFromPoolUsd) {
|
|
1339
|
+
return {
|
|
1340
|
+
status: "exhausted",
|
|
1341
|
+
nextAction: "borrow",
|
|
1342
|
+
warning: `Role "${config.roleId}" borrowing from project emergency buffer`
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
return {
|
|
1346
|
+
status: "exhausted",
|
|
1347
|
+
nextAction: config.onExhausted === "pause_for_approval" ? "pause" : "fail_closed",
|
|
1348
|
+
warning: `Role "${config.roleId}" exhausted run spend cap ($${config.runSpendCapUsd.toFixed(2)})`
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
if (config.monthlySpendCapUsd !== void 0 && state.currentMonthlySpendUsd + projectedCostUsd > config.monthlySpendCapUsd) {
|
|
1352
|
+
if (config.onExhausted === "cascade_to_roster" && config.fallbackModel) {
|
|
1353
|
+
return {
|
|
1354
|
+
status: "exhausted",
|
|
1355
|
+
nextAction: "cascade",
|
|
1356
|
+
fallbackModel: config.fallbackModel,
|
|
1357
|
+
fallbackHarness: config.fallbackHarness ?? "pi",
|
|
1358
|
+
warning: `Role "${config.roleId}" reached monthly budget limit ($${config.monthlySpendCapUsd.toFixed(2)})`
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
return {
|
|
1362
|
+
status: "exhausted",
|
|
1363
|
+
nextAction: "pause",
|
|
1364
|
+
warning: `Role "${config.roleId}" reached monthly budget cap ($${config.monthlySpendCapUsd.toFixed(2)})`
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
if (config.runSpendCapUsd && projectedRunSpend / config.runSpendCapUsd >= 0.85) {
|
|
1368
|
+
return {
|
|
1369
|
+
status: "near_limit",
|
|
1370
|
+
nextAction: "proceed",
|
|
1371
|
+
warning: `Role "${config.roleId}" is at ${Math.round(projectedRunSpend / config.runSpendCapUsd * 100)}% of run budget`
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
return {
|
|
1375
|
+
status: "ok",
|
|
1376
|
+
nextAction: "proceed"
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
// packages/core/tui/src/tui/modelSelector.ts
|
|
1381
|
+
function filterModelOptions(models, query, providerFilter) {
|
|
1382
|
+
const q = query?.trim().toLowerCase();
|
|
1383
|
+
const prov = providerFilter?.trim().toLowerCase();
|
|
1384
|
+
return models.filter((m) => {
|
|
1385
|
+
if (prov && m.provider.toLowerCase() !== prov && m.harness.toLowerCase() !== prov) {
|
|
1386
|
+
return false;
|
|
1387
|
+
}
|
|
1388
|
+
if (!q) return true;
|
|
1389
|
+
const matchTarget = `${m.id} ${m.selector} ${m.name} ${m.provider} ${m.harness}`.toLowerCase();
|
|
1390
|
+
return matchTarget.includes(q);
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1132
1394
|
// packages/core/tui/src/services/registry.ts
|
|
1133
1395
|
function describe(error) {
|
|
1134
1396
|
if (error instanceof Error && error.message) return error.message.split(/\r?\n/u)[0].slice(0, KXM_TUI_LIMITS.detailMax);
|
|
@@ -1356,7 +1618,126 @@ async function runKxmTuiPanel(input) {
|
|
|
1356
1618
|
return stopped && input.abort?.aborted ? 130 : 0;
|
|
1357
1619
|
}
|
|
1358
1620
|
|
|
1621
|
+
// packages/core/tui/src/adapters/omp.ts
|
|
1622
|
+
function resolveKxmTheme(theme) {
|
|
1623
|
+
if ("headerBg" in theme && typeof theme.headerBg === "function") {
|
|
1624
|
+
return theme;
|
|
1625
|
+
}
|
|
1626
|
+
return createKxmTuiThemeFromPi(theme);
|
|
1627
|
+
}
|
|
1628
|
+
function createKxmTuiOmpOverlay(options) {
|
|
1629
|
+
let closed = false;
|
|
1630
|
+
let mode = options.initialMode ?? "expanded";
|
|
1631
|
+
let autoDispatch = Boolean(options.autoDispatch);
|
|
1632
|
+
const theme = resolveKxmTheme(options.theme);
|
|
1633
|
+
const panel = new KxmTuiPanelComponent({
|
|
1634
|
+
registry: options.registry,
|
|
1635
|
+
theme,
|
|
1636
|
+
title: options.title,
|
|
1637
|
+
...options.goal ? { breadcrumb: `Goal: ${options.goal}` } : {},
|
|
1638
|
+
helpLines: [
|
|
1639
|
+
"[Ctrl+O/F2] Toggle Fold [h] History [a] Toggle Auto [o] Optimize [Shift+\u2191/\u2193] Reorder"
|
|
1640
|
+
],
|
|
1641
|
+
requestRender: () => options.tui.requestRender(),
|
|
1642
|
+
onQuit: () => {
|
|
1643
|
+
if (closed) return;
|
|
1644
|
+
closed = true;
|
|
1645
|
+
panel.dispose();
|
|
1646
|
+
options.done?.(true);
|
|
1647
|
+
},
|
|
1648
|
+
...options.width === void 0 ? {} : { getWidth: options.width }
|
|
1649
|
+
});
|
|
1650
|
+
function renderCollapsed(width) {
|
|
1651
|
+
const goalText = options.goal ? `Goal: ${options.goal}` : options.title;
|
|
1652
|
+
const modeBadge = autoDispatch ? theme.accent("[AUTO: ON]") : theme.dim("[STEP MODE]");
|
|
1653
|
+
const hint = theme.dim("[Ctrl+O to Expand]");
|
|
1654
|
+
const content = ` [KXM] ${goalText} \u2500\u2500 ${modeBadge} \u2500\u2500 ${hint}`;
|
|
1655
|
+
const pad = Math.max(0, width - content.length);
|
|
1656
|
+
return [content + " ".repeat(pad)];
|
|
1657
|
+
}
|
|
1658
|
+
function handleInput(data) {
|
|
1659
|
+
if (closed) return;
|
|
1660
|
+
if (data === "" || data === "\x1BOQ" || data === "\x1B[12~") {
|
|
1661
|
+
mode = mode === "collapsed" ? "expanded" : "collapsed";
|
|
1662
|
+
options.tui.requestRender();
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
if (mode === "collapsed") {
|
|
1666
|
+
if (data === "\r" || data === " " || data === "o" || data === "O") {
|
|
1667
|
+
mode = "expanded";
|
|
1668
|
+
options.tui.requestRender();
|
|
1669
|
+
}
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
if (data === "a" || data === "A") {
|
|
1673
|
+
autoDispatch = !autoDispatch;
|
|
1674
|
+
options.onToggleAutoDispatch?.(autoDispatch);
|
|
1675
|
+
options.tui.requestRender();
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
if (data === "h" || data === "H") {
|
|
1679
|
+
mode = mode === "history" ? "expanded" : "history";
|
|
1680
|
+
options.tui.requestRender();
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
if (data === "o" || data === "O") {
|
|
1684
|
+
mode = "optimizer";
|
|
1685
|
+
options.onRunOptimizer?.();
|
|
1686
|
+
options.tui.requestRender();
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
if ((data === "x" || data === "X") && mode === "history") {
|
|
1690
|
+
options.onExportHistory?.("both");
|
|
1691
|
+
options.tui.requestRender();
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
if (data === "\x1B" && mode !== "expanded") {
|
|
1695
|
+
mode = "expanded";
|
|
1696
|
+
options.tui.requestRender();
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
panel.handleInput(data);
|
|
1700
|
+
options.tui.requestRender();
|
|
1701
|
+
}
|
|
1702
|
+
return {
|
|
1703
|
+
getMode: () => mode,
|
|
1704
|
+
setMode: (nextMode) => {
|
|
1705
|
+
mode = nextMode;
|
|
1706
|
+
options.tui.requestRender();
|
|
1707
|
+
},
|
|
1708
|
+
isAutoDispatch: () => autoDispatch,
|
|
1709
|
+
toggleAutoDispatch: () => {
|
|
1710
|
+
autoDispatch = !autoDispatch;
|
|
1711
|
+
options.onToggleAutoDispatch?.(autoDispatch);
|
|
1712
|
+
options.tui.requestRender();
|
|
1713
|
+
},
|
|
1714
|
+
render: (width) => {
|
|
1715
|
+
if (mode === "collapsed") {
|
|
1716
|
+
return renderCollapsed(width);
|
|
1717
|
+
}
|
|
1718
|
+
return panel.render(width);
|
|
1719
|
+
},
|
|
1720
|
+
handleInput,
|
|
1721
|
+
invalidate: () => panel.invalidate(),
|
|
1722
|
+
dispose: () => {
|
|
1723
|
+
if (closed) return;
|
|
1724
|
+
closed = true;
|
|
1725
|
+
panel.dispose();
|
|
1726
|
+
options.done?.(true);
|
|
1727
|
+
}
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1359
1731
|
// packages/core/tui/src/adapters/pi.ts
|
|
1732
|
+
var createKxmTuiPiOverlay = createKxmTuiOmpOverlay;
|
|
1733
|
+
function renderPiWorkflowWidget(options) {
|
|
1734
|
+
const filled = Math.min(10, Math.max(0, Math.round(options.progressPercent / 100 * 10)));
|
|
1735
|
+
const bar = `[${"\u2588".repeat(filled)}${"\u2591".repeat(10 - filled)}] ${options.progressPercent}%`;
|
|
1736
|
+
const mode = options.autoDispatch ? "AUTO" : "STEP";
|
|
1737
|
+
const line1 = `[KXM] Goal: ${options.goal} \u2500\u2500 Stage: ${options.stage} (${bar}) [${mode}]`;
|
|
1738
|
+
const line2 = options.activeTask ? ` Active: ${options.activeTask}` : ` Waiting for next dispatch (use /kxm progress)`;
|
|
1739
|
+
return [line1, line2];
|
|
1740
|
+
}
|
|
1360
1741
|
function createKxmTuiPiPanel(options) {
|
|
1361
1742
|
let closed = false;
|
|
1362
1743
|
const panel = new KxmTuiPanelComponent({
|
|
@@ -1384,6 +1765,86 @@ function createKxmTuiPiPanel(options) {
|
|
|
1384
1765
|
invalidate: () => panel.invalidate()
|
|
1385
1766
|
};
|
|
1386
1767
|
}
|
|
1768
|
+
|
|
1769
|
+
// packages/core/tui/src/adapters/claude.ts
|
|
1770
|
+
function formatClaudeTaskHud(options, compact = false) {
|
|
1771
|
+
const percent = options.totalTasks > 0 ? Math.round(options.completedTasks / options.totalTasks * 100) : 0;
|
|
1772
|
+
const filled = Math.round(percent / 100 * 10);
|
|
1773
|
+
const bar = `[${"\u2588".repeat(filled)}${"\u2591".repeat(10 - filled)}] ${percent}%`;
|
|
1774
|
+
const modeTag = options.autoDispatch ? "[AUTO]" : "[STEP]";
|
|
1775
|
+
const attemptTag = options.attempt && options.attempt > 1 ? ` (Attempt ${options.attempt}/${options.maxAttempts ?? 3})` : "";
|
|
1776
|
+
const timeTag = options.elapsedSeconds !== void 0 ? ` \u23F1 ${options.elapsedSeconds}s` : "";
|
|
1777
|
+
if (compact) {
|
|
1778
|
+
return `[KXM ${modeTag}] ${options.goal} \u2500\u2500 Stage ${options.stageIndex}/${options.totalStages} ${bar}${attemptTag}${timeTag}`;
|
|
1779
|
+
}
|
|
1780
|
+
const activeTask = options.activeTaskTitle ? `
|
|
1781
|
+
\u2502 Active Task: ${options.activeTaskTitle}${attemptTag} [${options.activeRole ?? "agent"} (${options.activeModel ?? "default"})]` : "";
|
|
1782
|
+
return [
|
|
1783
|
+
`\u250C\u2500\u2500 KXM TASK HUD \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510`,
|
|
1784
|
+
`\u2502 Goal: ${options.goal}`,
|
|
1785
|
+
`\u2502 Stage ${options.stageIndex}/${options.totalStages}: ${options.stageName} \u2502 Progress: ${bar}${timeTag} \u2502 Mode: ${modeTag}${activeTask}`,
|
|
1786
|
+
`\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518`
|
|
1787
|
+
].join("\n");
|
|
1788
|
+
}
|
|
1789
|
+
function formatClaudeUpcomingQueue(tasks) {
|
|
1790
|
+
if (tasks.length === 0) return "No upcoming tasks in queue.";
|
|
1791
|
+
const lines = ["### KXM Upcoming Task Queue:"];
|
|
1792
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
1793
|
+
const t = tasks[i];
|
|
1794
|
+
const deps = t.dependencies.length > 0 ? ` (Requires: ${t.dependencies.join(", ")})` : " (Ready)";
|
|
1795
|
+
lines.push(`${i + 1}. [${t.status.toUpperCase()}] **${t.title}** (\`${t.id}\`)`);
|
|
1796
|
+
lines.push(` Role: \`${t.role}\` [${t.harness}/${t.model}]${deps}`);
|
|
1797
|
+
}
|
|
1798
|
+
return lines.join("\n");
|
|
1799
|
+
}
|
|
1800
|
+
function formatClaudeOptimizerReport(result) {
|
|
1801
|
+
if (result.proposals.length === 0) {
|
|
1802
|
+
return "Plan Optimizer: No pending optimization proposals.";
|
|
1803
|
+
}
|
|
1804
|
+
const lines = [
|
|
1805
|
+
`### KXM Plan Optimizer Proposals:`,
|
|
1806
|
+
`Total Projected Savings: -${result.totalProjectedTimeSavingsSeconds}s execution time, -${result.totalProjectedCostSavingsPercent}% token cost`,
|
|
1807
|
+
""
|
|
1808
|
+
];
|
|
1809
|
+
for (const p of result.proposals) {
|
|
1810
|
+
const icon = p.category === "concurrency" ? "\u26A1" : "\u{1F4B0}";
|
|
1811
|
+
lines.push(`${icon} **${p.title}** (\`${p.id}\`)`);
|
|
1812
|
+
lines.push(` ${p.description}`);
|
|
1813
|
+
if (p.suggestedModel) {
|
|
1814
|
+
lines.push(` Suggested Routing: \`${p.suggestedHarness ?? "pi"}/${p.suggestedModel}\``);
|
|
1815
|
+
}
|
|
1816
|
+
lines.push("");
|
|
1817
|
+
}
|
|
1818
|
+
return lines.join("\n").trim();
|
|
1819
|
+
}
|
|
1820
|
+
function formatClaudeRoleBudget(config, state) {
|
|
1821
|
+
const runCap = config.runSpendCapUsd !== void 0 ? `$${config.runSpendCapUsd.toFixed(2)}` : "Unmetered";
|
|
1822
|
+
const monthlyCap = config.monthlySpendCapUsd !== void 0 ? `$${config.monthlySpendCapUsd.toFixed(2)}` : "Unmetered";
|
|
1823
|
+
const currentRun = `$${state.currentRunSpendUsd.toFixed(4)}`;
|
|
1824
|
+
const currentMonthly = `$${state.currentMonthlySpendUsd.toFixed(2)}`;
|
|
1825
|
+
return [
|
|
1826
|
+
`Role: ${config.roleId} (${config.type})`,
|
|
1827
|
+
`Run Spend: ${currentRun} / ${runCap}`,
|
|
1828
|
+
`Monthly Spend: ${currentMonthly} / ${monthlyCap}`,
|
|
1829
|
+
`On Exhausted: ${config.onExhausted}${config.fallbackModel ? ` (Fallback: ${config.fallbackModel})` : ""}`
|
|
1830
|
+
].join(" \u2502 ");
|
|
1831
|
+
}
|
|
1832
|
+
function formatClaudeWorkflowHistory(runId, events) {
|
|
1833
|
+
return renderWorkflowHistoryMarkdown(runId, events);
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
// packages/core/tui/src/adapters/historyExport.ts
|
|
1837
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
1838
|
+
import { join } from "node:path";
|
|
1839
|
+
function exportWorkflowHistory(runId, events, destinationDir) {
|
|
1840
|
+
mkdirSync(destinationDir, { recursive: true });
|
|
1841
|
+
const sanitizedRunId = runId.replaceAll(/[^a-zA-Z0-9_-]/g, "_");
|
|
1842
|
+
const mdPath = join(destinationDir, `retrospective-${sanitizedRunId}.md`);
|
|
1843
|
+
const jsonlPath = join(destinationDir, `history-${sanitizedRunId}.jsonl`);
|
|
1844
|
+
writeFileSync(mdPath, renderWorkflowHistoryMarkdown(runId, events), "utf8");
|
|
1845
|
+
writeFileSync(jsonlPath, formatWorkflowHistoryJsonl(runId, events), "utf8");
|
|
1846
|
+
return { mdPath, jsonlPath };
|
|
1847
|
+
}
|
|
1387
1848
|
export {
|
|
1388
1849
|
KXM_TUI_ABORT_ACTION,
|
|
1389
1850
|
KXM_TUI_CLEAR_ACTION,
|
|
@@ -1400,11 +1861,14 @@ export {
|
|
|
1400
1861
|
KXM_TUI_STEP_STATES,
|
|
1401
1862
|
KxmTuiPanelComponent,
|
|
1402
1863
|
alignRight,
|
|
1864
|
+
applyPlanOptimizations,
|
|
1403
1865
|
assertKxmTuiSurface,
|
|
1404
1866
|
checkedKxmTuiSections,
|
|
1405
1867
|
createDefaultKxmTuiTheme,
|
|
1406
1868
|
createKxmTuiAnsiTheme,
|
|
1869
|
+
createKxmTuiOmpOverlay,
|
|
1407
1870
|
createKxmTuiPanelComponent,
|
|
1871
|
+
createKxmTuiPiOverlay,
|
|
1408
1872
|
createKxmTuiPiPanel,
|
|
1409
1873
|
createKxmTuiRegistry,
|
|
1410
1874
|
createKxmTuiThemeFromPi,
|
|
@@ -1413,11 +1877,22 @@ export {
|
|
|
1413
1877
|
currentSection,
|
|
1414
1878
|
decodeKxmTuiInput,
|
|
1415
1879
|
deleteBackward,
|
|
1880
|
+
evaluatePlanOptimizations,
|
|
1881
|
+
evaluateRoleBudget,
|
|
1882
|
+
exportWorkflowHistory,
|
|
1416
1883
|
fieldIsEditable,
|
|
1884
|
+
filterHistoryEvents,
|
|
1885
|
+
filterModelOptions,
|
|
1417
1886
|
firstSelectableFieldIndex,
|
|
1418
1887
|
fitText,
|
|
1419
1888
|
focusedFieldIsEditable,
|
|
1420
1889
|
formatAge,
|
|
1890
|
+
formatClaudeOptimizerReport,
|
|
1891
|
+
formatClaudeRoleBudget,
|
|
1892
|
+
formatClaudeTaskHud,
|
|
1893
|
+
formatClaudeUpcomingQueue,
|
|
1894
|
+
formatClaudeWorkflowHistory,
|
|
1895
|
+
formatWorkflowHistoryJsonl,
|
|
1421
1896
|
groupedKxmTuiChoices,
|
|
1422
1897
|
initialKxmTuiPanelState,
|
|
1423
1898
|
insertAt,
|
|
@@ -1432,6 +1907,9 @@ export {
|
|
|
1432
1907
|
renderKxmTuiPanel,
|
|
1433
1908
|
renderKxmTuiPanelFrame,
|
|
1434
1909
|
renderKxmTuiPanelText,
|
|
1910
|
+
renderPiWorkflowWidget,
|
|
1911
|
+
renderWorkflowHistoryMarkdown,
|
|
1912
|
+
reorderQueuedTasks,
|
|
1435
1913
|
runKxmTuiPanel,
|
|
1436
1914
|
validateKxmTuiSurface,
|
|
1437
1915
|
visibleChoices,
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code TUI & Channel Adapter.
|
|
3
|
+
*
|
|
4
|
+
* Formats KXM goals, tasks, upcoming lookaheads, retries, and workflow
|
|
5
|
+
* histories for Claude Code's terminal output, hook contexts, and channel events.
|
|
6
|
+
*
|
|
7
|
+
* Supports:
|
|
8
|
+
* - Single-line and boxed HUD banners for terminal output
|
|
9
|
+
* - Lookahead upcoming queue with dependency annotations
|
|
10
|
+
* - Formatted history timeline for channel delivery and SessionStart hooks
|
|
11
|
+
* - Plan optimizer proposals formatting
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { type HistoryEventItem, renderWorkflowHistoryMarkdown } from "../tui/history.ts";
|
|
15
|
+
import { type QueuedTaskItem } from "../tui/queue.ts";
|
|
16
|
+
import { type OptimizerResult } from "../tui/optimizer.ts";
|
|
17
|
+
import { type RoleBudgetState, type RoleBudgetConfig } from "../tui/roleBudget.ts";
|
|
18
|
+
|
|
19
|
+
export interface ClaudeTaskHudOptions {
|
|
20
|
+
readonly goal: string;
|
|
21
|
+
readonly stageName: string;
|
|
22
|
+
readonly stageIndex: number;
|
|
23
|
+
readonly totalStages: number;
|
|
24
|
+
readonly completedTasks: number;
|
|
25
|
+
readonly totalTasks: number;
|
|
26
|
+
readonly activeTaskTitle?: string | undefined;
|
|
27
|
+
readonly activeRole?: string | undefined;
|
|
28
|
+
readonly activeModel?: string | undefined;
|
|
29
|
+
readonly attempt?: number | undefined;
|
|
30
|
+
readonly maxAttempts?: number | undefined;
|
|
31
|
+
readonly elapsedSeconds?: number | undefined;
|
|
32
|
+
readonly tokensIn?: number | undefined;
|
|
33
|
+
readonly tokensOut?: number | undefined;
|
|
34
|
+
readonly autoDispatch?: boolean | undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Format a high-visibility, single-line or framed HUD for Claude Code terminal output.
|
|
39
|
+
*/
|
|
40
|
+
export function formatClaudeTaskHud(options: ClaudeTaskHudOptions, compact = false): string {
|
|
41
|
+
const percent = options.totalTasks > 0
|
|
42
|
+
? Math.round((options.completedTasks / options.totalTasks) * 100)
|
|
43
|
+
: 0;
|
|
44
|
+
const filled = Math.round((percent / 100) * 10);
|
|
45
|
+
const bar = `[${"█".repeat(filled)}${"░".repeat(10 - filled)}] ${percent}%`;
|
|
46
|
+
const modeTag = options.autoDispatch ? "[AUTO]" : "[STEP]";
|
|
47
|
+
const attemptTag = options.attempt && options.attempt > 1 ? ` (Attempt ${options.attempt}/${options.maxAttempts ?? 3})` : "";
|
|
48
|
+
const timeTag = options.elapsedSeconds !== undefined ? ` ⏱ ${options.elapsedSeconds}s` : "";
|
|
49
|
+
|
|
50
|
+
if (compact) {
|
|
51
|
+
return `[KXM ${modeTag}] ${options.goal} ── Stage ${options.stageIndex}/${options.totalStages} ${bar}${attemptTag}${timeTag}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const activeTask = options.activeTaskTitle
|
|
55
|
+
? `\n│ Active Task: ${options.activeTaskTitle}${attemptTag} [${options.activeRole ?? "agent"} (${options.activeModel ?? "default"})]`
|
|
56
|
+
: "";
|
|
57
|
+
|
|
58
|
+
return [
|
|
59
|
+
`┌── KXM TASK HUD ─────────────────────────────────────────────────────────────┐`,
|
|
60
|
+
`│ Goal: ${options.goal}`,
|
|
61
|
+
`│ Stage ${options.stageIndex}/${options.totalStages}: ${options.stageName} │ Progress: ${bar}${timeTag} │ Mode: ${modeTag}${activeTask}`,
|
|
62
|
+
`└─────────────────────────────────────────────────────────────────────────────┘`,
|
|
63
|
+
].join("\n");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Format the upcoming lookahead queue for Claude Code context and channel streams.
|
|
68
|
+
*/
|
|
69
|
+
export function formatClaudeUpcomingQueue(tasks: readonly QueuedTaskItem[]): string {
|
|
70
|
+
if (tasks.length === 0) return "No upcoming tasks in queue.";
|
|
71
|
+
|
|
72
|
+
const lines = ["### KXM Upcoming Task Queue:"];
|
|
73
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
74
|
+
const t = tasks[i]!;
|
|
75
|
+
const deps = t.dependencies.length > 0 ? ` (Requires: ${t.dependencies.join(", ")})` : " (Ready)";
|
|
76
|
+
lines.push(`${i + 1}. [${t.status.toUpperCase()}] **${t.title}** (\`${t.id}\`)`);
|
|
77
|
+
lines.push(` Role: \`${t.role}\` [${t.harness}/${t.model}]${deps}`);
|
|
78
|
+
}
|
|
79
|
+
return lines.join("\n");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Format plan optimizer proposals for Claude Code review prompts.
|
|
84
|
+
*/
|
|
85
|
+
export function formatClaudeOptimizerReport(result: OptimizerResult): string {
|
|
86
|
+
if (result.proposals.length === 0) {
|
|
87
|
+
return "Plan Optimizer: No pending optimization proposals.";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const lines = [
|
|
91
|
+
`### KXM Plan Optimizer Proposals:`,
|
|
92
|
+
`Total Projected Savings: -${result.totalProjectedTimeSavingsSeconds}s execution time, -${result.totalProjectedCostSavingsPercent}% token cost`,
|
|
93
|
+
"",
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
for (const p of result.proposals) {
|
|
97
|
+
const icon = p.category === "concurrency" ? "⚡" : "💰";
|
|
98
|
+
lines.push(`${icon} **${p.title}** (\`${p.id}\`)`);
|
|
99
|
+
lines.push(` ${p.description}`);
|
|
100
|
+
if (p.suggestedModel) {
|
|
101
|
+
lines.push(` Suggested Routing: \`${p.suggestedHarness ?? "pi"}/${p.suggestedModel}\``);
|
|
102
|
+
}
|
|
103
|
+
lines.push("");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return lines.join("\n").trim();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Format role budget and subscription status for Claude Code channel info.
|
|
111
|
+
*/
|
|
112
|
+
export function formatClaudeRoleBudget(config: RoleBudgetConfig, state: RoleBudgetState): string {
|
|
113
|
+
const runCap = config.runSpendCapUsd !== undefined ? `$${config.runSpendCapUsd.toFixed(2)}` : "Unmetered";
|
|
114
|
+
const monthlyCap = config.monthlySpendCapUsd !== undefined ? `$${config.monthlySpendCapUsd.toFixed(2)}` : "Unmetered";
|
|
115
|
+
const currentRun = `$${state.currentRunSpendUsd.toFixed(4)}`;
|
|
116
|
+
const currentMonthly = `$${state.currentMonthlySpendUsd.toFixed(2)}`;
|
|
117
|
+
|
|
118
|
+
return [
|
|
119
|
+
`Role: ${config.roleId} (${config.type})`,
|
|
120
|
+
`Run Spend: ${currentRun} / ${runCap}`,
|
|
121
|
+
`Monthly Spend: ${currentMonthly} / ${monthlyCap}`,
|
|
122
|
+
`On Exhausted: ${config.onExhausted}${config.fallbackModel ? ` (Fallback: ${config.fallbackModel})` : ""}`,
|
|
123
|
+
].join(" │ ");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Format history for Claude Code channel updates.
|
|
128
|
+
*/
|
|
129
|
+
export function formatClaudeWorkflowHistory(runId: string, events: readonly HistoryEventItem[]): string {
|
|
130
|
+
return renderWorkflowHistoryMarkdown(runId, events);
|
|
131
|
+
}
|