@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.
Files changed (34) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.kxm/agents/implementer.yaml +1 -1
  3. package/.kxm/roles/writer.yaml +1 -1
  4. package/docs/contributing/assignment-runner.md +3 -3
  5. package/package.json +1 -1
  6. package/packages/core/tui/dist/index.js +478 -0
  7. package/packages/core/tui/src/adapters/claude.ts +131 -0
  8. package/packages/core/tui/src/adapters/historyExport.ts +35 -0
  9. package/packages/core/tui/src/adapters/omp.ts +174 -0
  10. package/packages/core/tui/src/adapters/pi.ts +34 -0
  11. package/packages/core/tui/src/exports/index.ts +8 -0
  12. package/packages/core/tui/src/tui/history.ts +106 -0
  13. package/packages/core/tui/src/tui/modelSelector.ts +43 -0
  14. package/packages/core/tui/src/tui/optimizer.ts +154 -0
  15. package/packages/core/tui/src/tui/queue.ts +90 -0
  16. package/packages/core/tui/src/tui/roleBudget.ts +104 -0
  17. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  18. package/plugins/kxm/dist/cli.js +220 -22
  19. package/plugins/kxm/dist/mcp-server.js +1 -1
  20. package/plugins/kxm/dist/runtime-supervisor.js +36 -4
  21. package/plugins/kxm/dist/runtime.js +36 -4
  22. package/plugins/kxm/dist/server.js +22 -0
  23. package/plugins/kxm/package.json +1 -1
  24. package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +6 -0
  25. package/plugins/kxm/src/autocomplete.ts +3 -0
  26. package/plugins/kxm/src/cli/plugins.ts +201 -0
  27. package/plugins/kxm/src/cli.ts +14 -0
  28. package/plugins/kxm/src/harness.ts +36 -4
  29. package/plugins/kxm/src/mcp-server.ts +1 -1
  30. package/scripts/assignment-run.d.mts +7 -0
  31. package/scripts/assignment-run.mjs +128 -1
  32. package/scripts/demo-overlay.mjs +175 -0
  33. package/scripts/harness-run.mjs +2 -2
  34. package/scripts/native-critic.mjs +7 -7
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Plan Optimizer Engine.
3
+ *
4
+ * Analyzes upcoming tasks against dependency graphs, token footprints,
5
+ * and harness/model capabilities to generate concrete proposals for:
6
+ * 1. Concurrency (parallel fanout for independent tasks)
7
+ * 2. Cost (downscaling lightweight tasks to faster/cheaper models)
8
+ * 3. Reordering (failing fast on syntax/lint before heavier passes)
9
+ */
10
+
11
+ export interface OptimizerTaskInput {
12
+ readonly id: string;
13
+ readonly title: string;
14
+ readonly stageId: string;
15
+ readonly role: string;
16
+ readonly harness: string;
17
+ readonly model: string;
18
+ readonly dependencies: readonly string[];
19
+ readonly estimatedTokens?: number | undefined;
20
+ readonly category?: "implementation" | "test" | "docs" | "lint" | "security" | undefined;
21
+ }
22
+
23
+ export interface OptimizationProposal {
24
+ readonly id: string;
25
+ readonly category: "concurrency" | "cost" | "reorder" | "slice";
26
+ readonly title: string;
27
+ readonly description: string;
28
+ readonly targetTaskIds: readonly string[];
29
+ readonly projectedTimeSavingsSeconds: number;
30
+ readonly projectedCostSavingsPercent: number;
31
+ readonly suggestedModel?: string | undefined;
32
+ readonly suggestedHarness?: string | undefined;
33
+ readonly selected: boolean;
34
+ }
35
+
36
+ export interface OptimizerResult {
37
+ readonly proposals: readonly OptimizationProposal[];
38
+ readonly totalProjectedTimeSavingsSeconds: number;
39
+ readonly totalProjectedCostSavingsPercent: number;
40
+ }
41
+
42
+ /**
43
+ * Analyze a task queue and generate optimization proposals.
44
+ */
45
+ export function evaluatePlanOptimizations(tasks: readonly OptimizerTaskInput[]): OptimizerResult {
46
+ const proposals: OptimizationProposal[] = [];
47
+
48
+ // 1. Detect Parallel Fanout Opportunities (tasks with no mutual dependencies in the same stage)
49
+ const byStage = new Map<string, OptimizerTaskInput[]>();
50
+ for (const task of tasks) {
51
+ const list = byStage.get(task.stageId) ?? [];
52
+ list.push(task);
53
+ byStage.set(task.stageId, list);
54
+ }
55
+
56
+ for (const [stageId, stageTasks] of byStage) {
57
+ if (stageTasks.length >= 2) {
58
+ const independent = stageTasks.filter((t) => t.dependencies.length === 0 || t.dependencies.every((dep) => !stageTasks.some((st) => st.id === dep)));
59
+ if (independent.length >= 2) {
60
+ proposals.push({
61
+ id: `opt_fanout_${stageId}`,
62
+ category: "concurrency",
63
+ title: `Parallelize ${independent.length} tasks in stage "${stageId}"`,
64
+ description: `Tasks [${independent.map((t) => t.id).join(", ")}] have zero mutual dependencies and can execute concurrently via kxm_fanout.`,
65
+ targetTaskIds: independent.map((t) => t.id),
66
+ projectedTimeSavingsSeconds: Math.round(independent.length * 15),
67
+ projectedCostSavingsPercent: 0,
68
+ selected: true,
69
+ });
70
+ }
71
+ }
72
+ }
73
+
74
+ // 2. Detect Cost/Model Downscaling Opportunities (Docs or Lint on top-tier frontier models)
75
+ for (const task of tasks) {
76
+ const isFrontier = /claude-3-5-sonnet|claude-3-opus|gpt-4o|gpt-5/i.test(task.model);
77
+ if (isFrontier && (task.category === "docs" || /doc|readme|markdown/i.test(task.title))) {
78
+ proposals.push({
79
+ id: `opt_cost_${task.id}`,
80
+ category: "cost",
81
+ title: `Downscale model for documentation task "${task.title}"`,
82
+ description: `Task "${task.id}" is documentation-focused. Routing to a high-throughput lightweight model saves significant token budget.`,
83
+ targetTaskIds: [task.id],
84
+ projectedTimeSavingsSeconds: 5,
85
+ projectedCostSavingsPercent: 65,
86
+ suggestedHarness: "pi",
87
+ suggestedModel: "qwen/qwen3-coder-plus",
88
+ selected: true,
89
+ });
90
+ } else if (isFrontier && (task.category === "lint" || /lint|format|style/i.test(task.title))) {
91
+ proposals.push({
92
+ id: `opt_lint_${task.id}`,
93
+ category: "cost",
94
+ title: `Downscale model for formatting/lint task "${task.title}"`,
95
+ description: `Task "${task.id}" performs deterministic formatting/linting. Use a fast local/tier-1 helper.`,
96
+ targetTaskIds: [task.id],
97
+ projectedTimeSavingsSeconds: 8,
98
+ projectedCostSavingsPercent: 80,
99
+ suggestedHarness: "pi",
100
+ suggestedModel: "google/gemini-2.5-flash",
101
+ selected: true,
102
+ });
103
+ }
104
+ }
105
+
106
+ const totalTimeSavings = proposals.reduce((acc, p) => acc + p.projectedTimeSavingsSeconds, 0);
107
+ const totalCostSavings = proposals.length > 0
108
+ ? Math.round(proposals.reduce((acc, p) => acc + p.projectedCostSavingsPercent, 0) / proposals.length)
109
+ : 0;
110
+
111
+ return {
112
+ proposals,
113
+ totalProjectedTimeSavingsSeconds: totalTimeSavings,
114
+ totalProjectedCostSavingsPercent: totalCostSavings,
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Apply selected proposals to the task queue.
120
+ */
121
+ export function applyPlanOptimizations<T extends OptimizerTaskInput>(
122
+ tasks: readonly T[],
123
+ proposals: readonly OptimizationProposal[],
124
+ ): { tasks: T[]; appliedCount: number } {
125
+ const selected = proposals.filter((p) => p.selected);
126
+ if (selected.length === 0) return { tasks: [...tasks], appliedCount: 0 };
127
+
128
+ const modelOverrides = new Map<string, { harness?: string; model?: string }>();
129
+ for (const proposal of selected) {
130
+ if (proposal.category === "cost" && proposal.suggestedModel) {
131
+ for (const targetId of proposal.targetTaskIds) {
132
+ modelOverrides.set(targetId, {
133
+ ...(proposal.suggestedHarness ? { harness: proposal.suggestedHarness } : {}),
134
+ model: proposal.suggestedModel,
135
+ });
136
+ }
137
+ }
138
+ }
139
+
140
+ const updated = tasks.map((task) => {
141
+ const override = modelOverrides.get(task.id);
142
+ if (!override) return task;
143
+ return {
144
+ ...task,
145
+ ...(override.harness ? { harness: override.harness } : {}),
146
+ ...(override.model ? { model: override.model } : {}),
147
+ };
148
+ });
149
+
150
+ return {
151
+ tasks: updated,
152
+ appliedCount: selected.length,
153
+ };
154
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Task Queue & Topological Reordering Engine.
3
+ *
4
+ * Manages upcoming tasks in the execution queue with safety guards:
5
+ * - Detects prerequisite violations when moving a task before its dependencies
6
+ * - Prevents circular dependency cycles
7
+ * - Shifts tasks safely up or down
8
+ */
9
+
10
+ export interface QueuedTaskItem {
11
+ readonly id: string;
12
+ readonly title: string;
13
+ readonly stageId: string;
14
+ readonly role: string;
15
+ readonly harness: string;
16
+ readonly model: string;
17
+ readonly status: "completed" | "in_flight" | "pending" | "blocked" | "failed";
18
+ readonly dependencies: readonly string[];
19
+ readonly attempt: number;
20
+ readonly maxAttempts: number;
21
+ readonly estimatedDurationSec?: number | undefined;
22
+ readonly estimatedCostUsd?: number | undefined;
23
+ }
24
+
25
+ export interface ReorderResult {
26
+ readonly success: boolean;
27
+ readonly tasks: readonly QueuedTaskItem[];
28
+ readonly warning?: string | undefined;
29
+ }
30
+
31
+ /**
32
+ * Reorder a task in the upcoming queue with dependency validation.
33
+ */
34
+ export function reorderQueuedTasks(
35
+ tasks: readonly QueuedTaskItem[],
36
+ fromIndex: number,
37
+ toIndex: number,
38
+ ): ReorderResult {
39
+ if (
40
+ fromIndex < 0 ||
41
+ fromIndex >= tasks.length ||
42
+ toIndex < 0 ||
43
+ toIndex >= tasks.length ||
44
+ fromIndex === toIndex
45
+ ) {
46
+ return { success: false, tasks, warning: "invalid_indices" };
47
+ }
48
+
49
+ const target = tasks[fromIndex]!;
50
+
51
+ // Cannot reorder in-flight or completed tasks
52
+ if (target.status === "in_flight" || target.status === "completed") {
53
+ return {
54
+ success: false,
55
+ tasks,
56
+ warning: `cannot move ${target.status} task "${target.id}"`,
57
+ };
58
+ }
59
+
60
+ // Create mutable copy and move
61
+ const draft = [...tasks];
62
+ const [removed] = draft.splice(fromIndex, 1);
63
+ draft.splice(toIndex, 0, removed!);
64
+
65
+ // Validate dependencies:
66
+ // For every task at index i, none of its dependencies can appear at index j > i.
67
+ const idToIndex = new Map<string, number>();
68
+ for (let i = 0; i < draft.length; i++) {
69
+ idToIndex.set(draft[i]!.id, i);
70
+ }
71
+
72
+ for (let i = 0; i < draft.length; i++) {
73
+ const task = draft[i]!;
74
+ for (const depId of task.dependencies) {
75
+ const depIndex = idToIndex.get(depId);
76
+ if (depIndex !== undefined && depIndex > i) {
77
+ return {
78
+ success: false,
79
+ tasks,
80
+ warning: `Prerequisite violation: "${task.id}" depends on "${depId}", which is scheduled later at position ${depIndex + 1}`,
81
+ };
82
+ }
83
+ }
84
+ }
85
+
86
+ return {
87
+ success: true,
88
+ tasks: draft,
89
+ };
90
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Role Subscription Budget Limits & Rollover Engine.
3
+ *
4
+ * Tracks per-role subscription quotas, metered run spend caps,
5
+ * and executes rollover cascades when limits are exhausted.
6
+ */
7
+
8
+ export interface RoleBudgetConfig {
9
+ readonly roleId: string;
10
+ readonly type: "subscription" | "metered" | "hybrid";
11
+ readonly runSpendCapUsd?: number | undefined;
12
+ readonly monthlySpendCapUsd?: number | undefined;
13
+ readonly monthlyTokenQuota?: number | undefined;
14
+ readonly rolloverPercent?: number | undefined; // 0 - 100
15
+ readonly onExhausted: "cascade_to_roster" | "borrow_from_pool" | "pause_for_approval" | "fail_closed";
16
+ readonly fallbackModel?: string | undefined;
17
+ readonly fallbackHarness?: string | undefined;
18
+ readonly emergencyPoolLimitUsd?: number | undefined;
19
+ }
20
+
21
+ export interface RoleBudgetState {
22
+ readonly roleId: string;
23
+ readonly currentRunSpendUsd: number;
24
+ readonly currentMonthlySpendUsd: number;
25
+ readonly usedTokens: number;
26
+ readonly rolledOverTokens: number;
27
+ readonly borrowedFromPoolUsd: number;
28
+ }
29
+
30
+ export interface BudgetEvaluationResult {
31
+ readonly status: "ok" | "near_limit" | "exhausted";
32
+ readonly nextAction: "proceed" | "cascade" | "borrow" | "pause" | "fail_closed";
33
+ readonly fallbackModel?: string | undefined;
34
+ readonly fallbackHarness?: string | undefined;
35
+ readonly warning?: string | undefined;
36
+ }
37
+
38
+ /**
39
+ * Evaluate if a role execution can proceed under its budget caps or needs rollover.
40
+ */
41
+ export function evaluateRoleBudget(
42
+ config: RoleBudgetConfig,
43
+ state: RoleBudgetState,
44
+ projectedCostUsd = 0,
45
+ ): BudgetEvaluationResult {
46
+ const projectedRunSpend = state.currentRunSpendUsd + projectedCostUsd;
47
+
48
+ // 1. Check run spend cap
49
+ if (config.runSpendCapUsd !== undefined && projectedRunSpend > config.runSpendCapUsd) {
50
+ if (config.onExhausted === "cascade_to_roster" && config.fallbackModel) {
51
+ return {
52
+ status: "exhausted",
53
+ nextAction: "cascade",
54
+ fallbackModel: config.fallbackModel,
55
+ fallbackHarness: config.fallbackHarness ?? "pi",
56
+ warning: `Role "${config.roleId}" reached run spend cap ($${config.runSpendCapUsd.toFixed(2)}); rolling over to ${config.fallbackModel}`,
57
+ };
58
+ }
59
+ if (config.onExhausted === "borrow_from_pool" && (config.emergencyPoolLimitUsd ?? 0) > state.borrowedFromPoolUsd) {
60
+ return {
61
+ status: "exhausted",
62
+ nextAction: "borrow",
63
+ warning: `Role "${config.roleId}" borrowing from project emergency buffer`,
64
+ };
65
+ }
66
+ return {
67
+ status: "exhausted",
68
+ nextAction: config.onExhausted === "pause_for_approval" ? "pause" : "fail_closed",
69
+ warning: `Role "${config.roleId}" exhausted run spend cap ($${config.runSpendCapUsd.toFixed(2)})`,
70
+ };
71
+ }
72
+
73
+ // 2. Check monthly spend cap
74
+ if (config.monthlySpendCapUsd !== undefined && (state.currentMonthlySpendUsd + projectedCostUsd) > config.monthlySpendCapUsd) {
75
+ if (config.onExhausted === "cascade_to_roster" && config.fallbackModel) {
76
+ return {
77
+ status: "exhausted",
78
+ nextAction: "cascade",
79
+ fallbackModel: config.fallbackModel,
80
+ fallbackHarness: config.fallbackHarness ?? "pi",
81
+ warning: `Role "${config.roleId}" reached monthly budget limit ($${config.monthlySpendCapUsd.toFixed(2)})`,
82
+ };
83
+ }
84
+ return {
85
+ status: "exhausted",
86
+ nextAction: "pause",
87
+ warning: `Role "${config.roleId}" reached monthly budget cap ($${config.monthlySpendCapUsd.toFixed(2)})`,
88
+ };
89
+ }
90
+
91
+ // 3. Near limit alert (>= 85% of budget)
92
+ if (config.runSpendCapUsd && (projectedRunSpend / config.runSpendCapUsd) >= 0.85) {
93
+ return {
94
+ status: "near_limit",
95
+ nextAction: "proceed",
96
+ warning: `Role "${config.roleId}" is at ${Math.round((projectedRunSpend / config.runSpendCapUsd) * 100)}% of run budget`,
97
+ };
98
+ }
99
+
100
+ return {
101
+ status: "ok",
102
+ nextAction: "proceed",
103
+ };
104
+ }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "kxm",
4
4
  "displayName": "KXM",
5
- "version": "0.7.112",
5
+ "version": "0.7.118",
6
6
  "description": "Headless multi-agent orchestration, durable workflows, and a live operator dashboard for Pi and Claude Code",
7
7
  "author": {
8
8
  "name": "KontextMind",