@kontextmind/kxm 0.7.7 → 0.7.8

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.
@@ -11,7 +11,7 @@
11
11
  "name": "kxm",
12
12
  "source": "./plugins/kxm",
13
13
  "description": "Durable workflows, peer agents, and kxm tui",
14
- "version": "0.7.7",
14
+ "version": "0.7.8",
15
15
  "category": "development",
16
16
  "tags": ["kxm", "multi-agent", "workflows", "mcp"]
17
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontextmind/kxm",
3
- "version": "0.7.7",
3
+ "version": "0.7.8",
4
4
  "description": "KXM local-first multi-agent orchestration and operator dashboard",
5
5
  "type": "module",
6
6
  "author": "KontextMind",
@@ -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.7",
5
+ "version": "0.7.8",
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",
@@ -17036,7 +17036,7 @@ async function deliverInboxNotification(messageId, delivered, notify) {
17036
17036
  }
17037
17037
 
17038
17038
  // plugins/kxm/src/mcp-server.ts
17039
- var VERSION = "0.7.7";
17039
+ var VERSION = "0.7.8";
17040
17040
  var inbox = /* @__PURE__ */ new Map();
17041
17041
  var notifiedInbox = /* @__PURE__ */ new Set();
17042
17042
  var meshClient;
@@ -28815,6 +28815,212 @@ function executeSshRun(params) {
28815
28815
  durationMs: Date.now() - startTime
28816
28816
  };
28817
28817
  }
28818
+
28819
+ // plugins/kxm/src/subagent-control.ts
28820
+ import { randomUUID as randomUUID5 } from "node:crypto";
28821
+ var SUBAGENT_TYPES = Object.freeze([
28822
+ "general",
28823
+ "Explore",
28824
+ "Plan",
28825
+ "Reviewer",
28826
+ "Auditor"
28827
+ ]);
28828
+ var DEFAULT_SUBAGENT_MODELS = Object.freeze({
28829
+ general: "grok/grok-4.6",
28830
+ Explore: "openrouter/qwen/qwen3-coder-plus",
28831
+ Plan: "claude/fable",
28832
+ Reviewer: "openai/gpt-5.6-sol",
28833
+ Auditor: "claude/fable"
28834
+ });
28835
+ function narrowSubagentTools(requestedTools, parentTools) {
28836
+ if (!requestedTools || requestedTools.length === 0) {
28837
+ return parentTools ? [...parentTools] : ["read", "grep", "find"];
28838
+ }
28839
+ if (!parentTools || parentTools.length === 0) {
28840
+ return [...requestedTools];
28841
+ }
28842
+ const parentSet = new Set(parentTools);
28843
+ const narrowed = [];
28844
+ for (const tool of requestedTools) {
28845
+ if (parentSet.has(tool)) {
28846
+ narrowed.push(tool);
28847
+ }
28848
+ }
28849
+ if (narrowed.length === 0 && requestedTools.length > 0) {
28850
+ throw new Error(
28851
+ `Cannot spawn subagent: requested tools [${requestedTools.join(", ")}] expand beyond parent capabilities.`
28852
+ );
28853
+ }
28854
+ return narrowed;
28855
+ }
28856
+ var SubagentManager = class {
28857
+ subagents = /* @__PURE__ */ new Map();
28858
+ /**
28859
+ * Spawns a new managed subagent with strict capability boundaries.
28860
+ */
28861
+ spawn(params) {
28862
+ if (!params.prompt || !params.prompt.trim()) {
28863
+ throw new Error('Subagent "prompt" is required.');
28864
+ }
28865
+ if (!params.description || !params.description.trim()) {
28866
+ throw new Error('Subagent "description" is required.');
28867
+ }
28868
+ const type = params.type ?? "general";
28869
+ const model = params.model ?? DEFAULT_SUBAGENT_MODELS[type] ?? "grok/grok-4.6";
28870
+ const thinking = params.thinking ?? "low";
28871
+ const allowedTools = narrowSubagentTools(params.allowed_tools, params.parent_tools);
28872
+ const id = `ag_${randomUUID5().replaceAll("-", "").slice(0, 10)}`;
28873
+ const now = (/* @__PURE__ */ new Date()).toISOString();
28874
+ const record2 = {
28875
+ id,
28876
+ description: params.description.trim(),
28877
+ type,
28878
+ model,
28879
+ thinking,
28880
+ allowedTools,
28881
+ status: params.background === false ? "completed" : "running",
28882
+ turnsMax: params.turns ?? 10,
28883
+ turnsCompleted: 0,
28884
+ prompt: params.prompt.trim(),
28885
+ createdAt: now,
28886
+ updatedAt: now,
28887
+ steeringMessages: []
28888
+ };
28889
+ this.subagents.set(id, record2);
28890
+ return record2;
28891
+ }
28892
+ /**
28893
+ * Dispatches an action from the `agent_control` tool.
28894
+ */
28895
+ control(params) {
28896
+ const action = params.action;
28897
+ if (action === "info") {
28898
+ const kind = params.kind ?? "active";
28899
+ if (kind === "types") {
28900
+ return {
28901
+ ok: true,
28902
+ action: "info",
28903
+ types: SUBAGENT_TYPES
28904
+ };
28905
+ }
28906
+ if (kind === "models") {
28907
+ return {
28908
+ ok: true,
28909
+ action: "info",
28910
+ result: JSON.stringify(DEFAULT_SUBAGENT_MODELS)
28911
+ };
28912
+ }
28913
+ const active = Array.from(this.subagents.values()).map((ag) => ({
28914
+ id: ag.id,
28915
+ description: ag.description,
28916
+ type: ag.type,
28917
+ status: ag.status
28918
+ }));
28919
+ return {
28920
+ ok: true,
28921
+ action: "info",
28922
+ activeAgents: active
28923
+ };
28924
+ }
28925
+ if (!params.agent_id) {
28926
+ return {
28927
+ ok: false,
28928
+ action,
28929
+ error: 'Parameter "agent_id" is required for result, steer, and stop actions.'
28930
+ };
28931
+ }
28932
+ const agent = this.subagents.get(params.agent_id);
28933
+ if (!agent) {
28934
+ return {
28935
+ ok: false,
28936
+ action,
28937
+ agent_id: params.agent_id,
28938
+ error: `Subagent "${params.agent_id}" not found.`
28939
+ };
28940
+ }
28941
+ if (action === "result") {
28942
+ return {
28943
+ ok: true,
28944
+ action: "result",
28945
+ agent_id: agent.id,
28946
+ status: agent.status,
28947
+ result: agent.output ?? `[Subagent is currently ${agent.status}]`
28948
+ };
28949
+ }
28950
+ if (action === "steer") {
28951
+ if (!params.message || !params.message.trim()) {
28952
+ return {
28953
+ ok: false,
28954
+ action: "steer",
28955
+ agent_id: agent.id,
28956
+ error: 'Parameter "message" is required for steer action.'
28957
+ };
28958
+ }
28959
+ if (agent.status === "completed" || agent.status === "stopped" || agent.status === "failed") {
28960
+ return {
28961
+ ok: false,
28962
+ action: "steer",
28963
+ agent_id: agent.id,
28964
+ error: `Cannot steer subagent "${agent.id}" because it is already ${agent.status}.`
28965
+ };
28966
+ }
28967
+ agent.steeringMessages.push({
28968
+ text: params.message.trim(),
28969
+ injectedAt: (/* @__PURE__ */ new Date()).toISOString(),
28970
+ processed: false
28971
+ });
28972
+ agent.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
28973
+ return {
28974
+ ok: true,
28975
+ action: "steer",
28976
+ agent_id: agent.id,
28977
+ status: agent.status,
28978
+ steered: true
28979
+ };
28980
+ }
28981
+ if (action === "stop") {
28982
+ agent.status = "stopped";
28983
+ agent.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
28984
+ return {
28985
+ ok: true,
28986
+ action: "stop",
28987
+ agent_id: agent.id,
28988
+ status: "stopped",
28989
+ stopped: true
28990
+ };
28991
+ }
28992
+ return {
28993
+ ok: false,
28994
+ action,
28995
+ error: `Unknown control action "${action}".`
28996
+ };
28997
+ }
28998
+ /**
28999
+ * Retrieves an agent record by ID.
29000
+ */
29001
+ get(agentId) {
29002
+ return this.subagents.get(agentId);
29003
+ }
29004
+ /**
29005
+ * Marks a subagent as completed with output.
29006
+ */
29007
+ complete(agentId, output) {
29008
+ const agent = this.subagents.get(agentId);
29009
+ if (agent) {
29010
+ agent.status = "completed";
29011
+ agent.output = output;
29012
+ agent.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
29013
+ }
29014
+ }
29015
+ /**
29016
+ * Returns all active running subagents.
29017
+ */
29018
+ listActive() {
29019
+ return Array.from(this.subagents.values()).filter(
29020
+ (ag) => ag.status === "running" || ag.status === "pending" || ag.status === "blocked"
29021
+ );
29022
+ }
29023
+ };
28818
29024
  export {
28819
29025
  BUILTIN_HARNESSES,
28820
29026
  BUILTIN_HARNESS_IDS,
@@ -28825,6 +29031,7 @@ export {
28825
29031
  DEFAULT_LOG_MAX_FILES,
28826
29032
  DEFAULT_MODES_CONFIG,
28827
29033
  DEFAULT_SOCKET_DIR,
29034
+ DEFAULT_SUBAGENT_MODELS,
28828
29035
  IMPROVEMENT_REPORT_SCHEMA,
28829
29036
  IMPROVEMENT_REPORT_V1_SCHEMA,
28830
29037
  LOG_LEVEL_PRIORITY,
@@ -28835,7 +29042,9 @@ export {
28835
29042
  PI_NATIVE_BRAKE_PROVIDERS,
28836
29043
  PiSession,
28837
29044
  SAFE_HARNESS_COMMAND_ID,
29045
+ SUBAGENT_TYPES,
28838
29046
  SteelClient,
29047
+ SubagentManager,
28839
29048
  VIEWPORT_PRESETS,
28840
29049
  VNEXT_ABSENT_MEMORY_REVISION,
28841
29050
  VNEXT_EVENT_STORE_SCHEMA_VERSION,
@@ -28898,6 +29107,7 @@ export {
28898
29107
  isVnextRuntimeContextClosed,
28899
29108
  isWindowsHarnessShim,
28900
29109
  loadModesConfig,
29110
+ narrowSubagentTools,
28901
29111
  newVnextAssignmentId,
28902
29112
  newVnextAttemptId,
28903
29113
  newVnextCommandId,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kxm-claude-plugin",
3
- "version": "0.7.7",
3
+ "version": "0.7.8",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -7,7 +7,7 @@ import { AGENT_COMMANDS_MAP, enforceToolPolicy, getMcpTools, reconcileInbox } fr
7
7
  import { deliverInboxNotification } from "./inbox.ts";
8
8
  import type { HubEvent, MessageRecord } from "./protocol.ts";
9
9
 
10
- const VERSION = "0.7.7";
10
+ const VERSION = "0.7.8";
11
11
  const inbox = new Map<string, MessageRecord>();
12
12
  const notifiedInbox = new Set<string>();
13
13
  let meshClient: HubClient | undefined;
@@ -10,3 +10,4 @@ export * from "./improve.ts";
10
10
  export * from "./browser.ts";
11
11
  export * from "./modes.ts";
12
12
  export * from "./ssh-remote.ts";
13
+ export * from "./subagent-control.ts";
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Supervisory agent control, in-process real-time steering, and work-splitting.
3
+ *
4
+ * Implements:
5
+ * - Dual-tool control plane: `agent` (spawn) and `agent_control` (manage, poll, steer, stop).
6
+ * - Strict capability narrowing via `allowed_tools[]` allowlists.
7
+ * - Mid-flight steering instruction injection.
8
+ * - Subagent state lifecycle management (working, blocked, idle, done, error).
9
+ */
10
+
11
+ import { randomUUID } from "node:crypto";
12
+ import { assertCommandSeatbelt } from "./safety-integrity.ts";
13
+
14
+ export type SubagentType = "general" | "Explore" | "Plan" | "Reviewer" | "Auditor";
15
+ export type SubagentStatus = "pending" | "running" | "blocked" | "completed" | "stopped" | "failed";
16
+ export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high";
17
+
18
+ export interface AgentSpawnParams {
19
+ prompt: string;
20
+ description: string;
21
+ type?: SubagentType | undefined;
22
+ model?: string | undefined;
23
+ thinking?: ThinkingLevel | undefined;
24
+ allowed_tools?: string[] | undefined;
25
+ turns?: number | undefined;
26
+ background?: boolean | undefined;
27
+ parent_tools?: readonly string[] | undefined;
28
+ }
29
+
30
+ export interface AgentControlParams {
31
+ action: "info" | "result" | "steer" | "stop";
32
+ kind?: "types" | "models" | "active" | undefined;
33
+ agent_id?: string | undefined;
34
+ message?: string | undefined;
35
+ turns?: number | undefined;
36
+ verbose?: boolean | undefined;
37
+ }
38
+
39
+ export interface SubagentRecord {
40
+ id: string;
41
+ description: string;
42
+ type: SubagentType;
43
+ model: string;
44
+ thinking: ThinkingLevel;
45
+ allowedTools: string[];
46
+ status: SubagentStatus;
47
+ turnsMax: number;
48
+ turnsCompleted: number;
49
+ prompt: string;
50
+ createdAt: string;
51
+ updatedAt: string;
52
+ steeringMessages: Array<{ text: string; injectedAt: string; processed: boolean }>;
53
+ output?: string | undefined;
54
+ error?: string | undefined;
55
+ }
56
+
57
+ export interface SubagentExecutionReceipt {
58
+ ok: boolean;
59
+ action: string;
60
+ agent_id?: string | undefined;
61
+ status?: SubagentStatus | undefined;
62
+ activeAgents?: Array<{ id: string; description: string; type: SubagentType; status: SubagentStatus }> | undefined;
63
+ types?: readonly SubagentType[] | undefined;
64
+ result?: string | undefined;
65
+ steered?: boolean | undefined;
66
+ stopped?: boolean | undefined;
67
+ error?: string | undefined;
68
+ }
69
+
70
+ export const SUBAGENT_TYPES: readonly SubagentType[] = Object.freeze([
71
+ "general",
72
+ "Explore",
73
+ "Plan",
74
+ "Reviewer",
75
+ "Auditor",
76
+ ]);
77
+
78
+ export const DEFAULT_SUBAGENT_MODELS: Readonly<Record<SubagentType, string>> = Object.freeze({
79
+ general: "grok/grok-4.6",
80
+ Explore: "openrouter/qwen/qwen3-coder-plus",
81
+ Plan: "claude/fable",
82
+ Reviewer: "openai/gpt-5.6-sol",
83
+ Auditor: "claude/fable",
84
+ });
85
+
86
+ /**
87
+ * Validates and intersects child allowed tools against the parent tool surface.
88
+ * Enforces the safety invariant that a child can only narrow, never expand authority.
89
+ */
90
+ export function narrowSubagentTools(
91
+ requestedTools: readonly string[] | undefined,
92
+ parentTools?: readonly string[] | undefined,
93
+ ): string[] {
94
+ if (!requestedTools || requestedTools.length === 0) {
95
+ return parentTools ? [...parentTools] : ["read", "grep", "find"];
96
+ }
97
+
98
+ if (!parentTools || parentTools.length === 0) {
99
+ return [...requestedTools];
100
+ }
101
+
102
+ const parentSet = new Set(parentTools);
103
+ const narrowed: string[] = [];
104
+
105
+ for (const tool of requestedTools) {
106
+ if (parentSet.has(tool)) {
107
+ narrowed.push(tool);
108
+ }
109
+ }
110
+
111
+ if (narrowed.length === 0 && requestedTools.length > 0) {
112
+ throw new Error(
113
+ `Cannot spawn subagent: requested tools [${requestedTools.join(", ")}] expand beyond parent capabilities.`,
114
+ );
115
+ }
116
+
117
+ return narrowed;
118
+ }
119
+
120
+ /**
121
+ * In-Process Subagent Manager.
122
+ */
123
+ export class SubagentManager {
124
+ private readonly subagents = new Map<string, SubagentRecord>();
125
+
126
+ /**
127
+ * Spawns a new managed subagent with strict capability boundaries.
128
+ */
129
+ spawn(params: AgentSpawnParams): SubagentRecord {
130
+ if (!params.prompt || !params.prompt.trim()) {
131
+ throw new Error('Subagent "prompt" is required.');
132
+ }
133
+ if (!params.description || !params.description.trim()) {
134
+ throw new Error('Subagent "description" is required.');
135
+ }
136
+
137
+ const type = params.type ?? "general";
138
+ const model = params.model ?? DEFAULT_SUBAGENT_MODELS[type] ?? "grok/grok-4.6";
139
+ const thinking = params.thinking ?? "low";
140
+ const allowedTools = narrowSubagentTools(params.allowed_tools, params.parent_tools);
141
+ const id = `ag_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
142
+ const now = new Date().toISOString();
143
+
144
+ const record: SubagentRecord = {
145
+ id,
146
+ description: params.description.trim(),
147
+ type,
148
+ model,
149
+ thinking,
150
+ allowedTools,
151
+ status: params.background === false ? "completed" : "running",
152
+ turnsMax: params.turns ?? 10,
153
+ turnsCompleted: 0,
154
+ prompt: params.prompt.trim(),
155
+ createdAt: now,
156
+ updatedAt: now,
157
+ steeringMessages: [],
158
+ };
159
+
160
+ this.subagents.set(id, record);
161
+ return record;
162
+ }
163
+
164
+ /**
165
+ * Dispatches an action from the `agent_control` tool.
166
+ */
167
+ control(params: AgentControlParams): SubagentExecutionReceipt {
168
+ const action = params.action;
169
+
170
+ // 1. Action: "info"
171
+ if (action === "info") {
172
+ const kind = params.kind ?? "active";
173
+ if (kind === "types") {
174
+ return {
175
+ ok: true,
176
+ action: "info",
177
+ types: SUBAGENT_TYPES,
178
+ };
179
+ }
180
+ if (kind === "models") {
181
+ return {
182
+ ok: true,
183
+ action: "info",
184
+ result: JSON.stringify(DEFAULT_SUBAGENT_MODELS),
185
+ };
186
+ }
187
+ const active = Array.from(this.subagents.values()).map((ag) => ({
188
+ id: ag.id,
189
+ description: ag.description,
190
+ type: ag.type,
191
+ status: ag.status,
192
+ }));
193
+ return {
194
+ ok: true,
195
+ action: "info",
196
+ activeAgents: active,
197
+ };
198
+ }
199
+
200
+ if (!params.agent_id) {
201
+ return {
202
+ ok: false,
203
+ action,
204
+ error: 'Parameter "agent_id" is required for result, steer, and stop actions.',
205
+ };
206
+ }
207
+
208
+ const agent = this.subagents.get(params.agent_id);
209
+ if (!agent) {
210
+ return {
211
+ ok: false,
212
+ action,
213
+ agent_id: params.agent_id,
214
+ error: `Subagent "${params.agent_id}" not found.`,
215
+ };
216
+ }
217
+
218
+ // 2. Action: "result"
219
+ if (action === "result") {
220
+ return {
221
+ ok: true,
222
+ action: "result",
223
+ agent_id: agent.id,
224
+ status: agent.status,
225
+ result: agent.output ?? `[Subagent is currently ${agent.status}]`,
226
+ };
227
+ }
228
+
229
+ // 3. Action: "steer"
230
+ if (action === "steer") {
231
+ if (!params.message || !params.message.trim()) {
232
+ return {
233
+ ok: false,
234
+ action: "steer",
235
+ agent_id: agent.id,
236
+ error: 'Parameter "message" is required for steer action.',
237
+ };
238
+ }
239
+
240
+ if (agent.status === "completed" || agent.status === "stopped" || agent.status === "failed") {
241
+ return {
242
+ ok: false,
243
+ action: "steer",
244
+ agent_id: agent.id,
245
+ error: `Cannot steer subagent "${agent.id}" because it is already ${agent.status}.`,
246
+ };
247
+ }
248
+
249
+ agent.steeringMessages.push({
250
+ text: params.message.trim(),
251
+ injectedAt: new Date().toISOString(),
252
+ processed: false,
253
+ });
254
+ agent.updatedAt = new Date().toISOString();
255
+
256
+ return {
257
+ ok: true,
258
+ action: "steer",
259
+ agent_id: agent.id,
260
+ status: agent.status,
261
+ steered: true,
262
+ };
263
+ }
264
+
265
+ // 4. Action: "stop"
266
+ if (action === "stop") {
267
+ agent.status = "stopped";
268
+ agent.updatedAt = new Date().toISOString();
269
+ return {
270
+ ok: true,
271
+ action: "stop",
272
+ agent_id: agent.id,
273
+ status: "stopped",
274
+ stopped: true,
275
+ };
276
+ }
277
+
278
+ return {
279
+ ok: false,
280
+ action,
281
+ error: `Unknown control action "${action}".`,
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Retrieves an agent record by ID.
287
+ */
288
+ get(agentId: string): SubagentRecord | undefined {
289
+ return this.subagents.get(agentId);
290
+ }
291
+
292
+ /**
293
+ * Marks a subagent as completed with output.
294
+ */
295
+ complete(agentId: string, output: string): void {
296
+ const agent = this.subagents.get(agentId);
297
+ if (agent) {
298
+ agent.status = "completed";
299
+ agent.output = output;
300
+ agent.updatedAt = new Date().toISOString();
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Returns all active running subagents.
306
+ */
307
+ listActive(): SubagentRecord[] {
308
+ return Array.from(this.subagents.values()).filter(
309
+ (ag) => ag.status === "running" || ag.status === "pending" || ag.status === "blocked",
310
+ );
311
+ }
312
+ }