@kontextmind/kxm 0.7.6 → 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.
- package/.claude-plugin/marketplace.json +1 -1
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +43389 -42846
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime.js +645 -25
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/cli/context-skills.ts +373 -0
- package/plugins/kxm/src/cli/hub.ts +614 -0
- package/plugins/kxm/src/cli/roles.ts +615 -0
- package/plugins/kxm/src/cli/system.ts +906 -0
- package/plugins/kxm/src/cli/tasks.ts +364 -0
- package/plugins/kxm/src/cli/types.ts +270 -0
- package/plugins/kxm/src/cli/vnext.ts +698 -0
- package/plugins/kxm/src/cli/workflows.ts +699 -0
- package/plugins/kxm/src/cli.ts +195 -4285
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/runtime.ts +2 -0
- package/plugins/kxm/src/ssh-remote.ts +560 -0
- package/plugins/kxm/src/subagent-control.ts +312 -0
|
@@ -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
|
+
}
|