@kontextmind/kxm 0.7.112 → 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/.kxm/agents/implementer.yaml +1 -1
- package/.kxm/roles/writer.yaml +1 -1
- package/docs/contributing/assignment-runner.md +3 -3
- 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/assignment-run.d.mts +7 -0
- package/scripts/assignment-run.mjs +128 -1
- package/scripts/demo-overlay.mjs +175 -0
- package/scripts/harness-run.mjs +2 -2
- package/scripts/native-critic.mjs +7 -7
package/.kxm/roles/writer.yaml
CHANGED
|
@@ -5,7 +5,7 @@ id: writer
|
|
|
5
5
|
# Effort default: medium for implementation (AGENTS.md). agy ids bake in the
|
|
6
6
|
# effort tier, so those entries carry no effort field.
|
|
7
7
|
roster:
|
|
8
|
-
- model: xai/grok-4.
|
|
8
|
+
- model: xai/grok-4.7
|
|
9
9
|
effort: medium
|
|
10
10
|
enabled: true
|
|
11
11
|
- model: openrouter/qwen/qwen3-coder-plus
|
|
@@ -33,7 +33,7 @@ permissions:
|
|
|
33
33
|
|
|
34
34
|
| Role | Admitted route (harness / model) | Vendor | Permission |
|
|
35
35
|
|---|---|---|---|
|
|
36
|
-
| `writer` | `grok` / `grok-4.
|
|
36
|
+
| `writer` | `grok` / `grok-4.7`; relief: `pi` / `openrouter/qwen/qwen3-coder-plus` | `xai`; `alibaba` | `edit` |
|
|
37
37
|
| `planner` | `claude` / `fable` | `anthropic` | `read-only` |
|
|
38
38
|
| `reviewer-arch` | `claude` / `fable` | `anthropic` | `read-only` |
|
|
39
39
|
| `reviewer-cli` | `codex` / `gpt-5.6-sol` | `openai` | `read-only` |
|
|
@@ -115,7 +115,7 @@ Write a closed `kxm.assignment.v1` manifest. Unknown keys are refused.
|
|
|
115
115
|
"assignment_id": "asg-writer-1",
|
|
116
116
|
"kind": "implement",
|
|
117
117
|
"harness": "grok",
|
|
118
|
-
"model": "grok-4.
|
|
118
|
+
"model": "grok-4.7",
|
|
119
119
|
"effort": "medium",
|
|
120
120
|
"permission": "edit",
|
|
121
121
|
"cwd": "/abs/kxm-fix-improve-sources",
|
|
@@ -247,7 +247,7 @@ then is that `BLOCK` resolved.
|
|
|
247
247
|
├── output-schema.json # the structured-output schema given to the harness
|
|
248
248
|
├── pre-dispatch.json # written before spawn (kxm.assignment-dispatch.v1)
|
|
249
249
|
├── completion.json # kxm.assignment-completion.v1, or refusal.json
|
|
250
|
-
├── routing-record.json #
|
|
250
|
+
├── routing-record.json # export with `node scripts/assignment-run.mjs routing-export --task-dir <abs>` and pass the JSONL to `kxm improve report --file` / `kxm routing report --file`
|
|
251
251
|
├── telemetry.jsonl # usage, latency and cost
|
|
252
252
|
├── runner-errors.jsonl # bounded failure codes
|
|
253
253
|
├── witness/ # latest.json and history/<receipt-id>.json
|
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,
|