@kontextmind/kxm 0.7.174 → 0.7.176
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/CHANGELOG.md +13 -0
- package/docs/contributing/decisions.md +22 -1
- package/docs/contributing/test-matrix.md +1 -1
- package/docs/operations/upgrade.md +5 -5
- package/docs/reference/cli-reference.md +8 -7
- package/docs/reference/config-reference.md +2 -2
- package/docs/start/install.md +3 -3
- package/package.json +6 -6
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +128 -52
- package/plugins/kxm/dist/extension.js +74 -11
- package/plugins/kxm/dist/mcp-server.js +33 -10
- package/plugins/kxm/dist/runtime.js +44 -27
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +8 -3
- package/plugins/kxm/src/cli/system.ts +74 -20
- package/plugins/kxm/src/cli.ts +4 -2
- package/plugins/kxm/src/harness.ts +50 -29
- package/plugins/kxm/src/kxm-install-kind.ts +6 -0
- package/plugins/kxm/src/kxm-update-config.ts +2 -2
- package/plugins/kxm/src/kxm-update.ts +11 -4
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/scripts/docker-install-smoke.mjs +2 -1
- package/scripts/npm-pack-json.d.mts +4 -0
- package/scripts/npm-pack-json.mjs +15 -0
- package/scripts/pr-land.mjs +45 -3
|
@@ -33758,6 +33758,9 @@ var Protocol = class {
|
|
|
33758
33758
|
this.setRequestHandler(GetTaskPayloadRequestSchema, async (request, extra) => {
|
|
33759
33759
|
const handleTaskResult = async () => {
|
|
33760
33760
|
const taskId = request.params.taskId;
|
|
33761
|
+
if (!await this._taskStore.getTask(taskId, extra.sessionId)) {
|
|
33762
|
+
throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
|
|
33763
|
+
}
|
|
33761
33764
|
if (this._taskMessageQueue) {
|
|
33762
33765
|
let queuedMessage;
|
|
33763
33766
|
while (queuedMessage = await this._taskMessageQueue.dequeue(taskId, extra.sessionId)) {
|
|
@@ -33788,12 +33791,12 @@ var Protocol = class {
|
|
|
33788
33791
|
throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
|
|
33789
33792
|
}
|
|
33790
33793
|
if (!isTerminal(task.status)) {
|
|
33791
|
-
await this._waitForTaskUpdate(taskId, extra.signal);
|
|
33794
|
+
await this._waitForTaskUpdate(taskId, extra.signal, extra.sessionId);
|
|
33792
33795
|
return await handleTaskResult();
|
|
33793
33796
|
}
|
|
33794
33797
|
if (isTerminal(task.status)) {
|
|
33795
33798
|
const result2 = await this._taskStore.getTaskResult(taskId, extra.sessionId);
|
|
33796
|
-
this._clearTaskQueue(taskId);
|
|
33799
|
+
this._clearTaskQueue(taskId, extra.sessionId);
|
|
33797
33800
|
return {
|
|
33798
33801
|
...result2,
|
|
33799
33802
|
_meta: {
|
|
@@ -33830,7 +33833,7 @@ var Protocol = class {
|
|
|
33830
33833
|
throw new McpError(ErrorCode.InvalidParams, `Cannot cancel task in terminal status: ${task.status}`);
|
|
33831
33834
|
}
|
|
33832
33835
|
await this._taskStore.updateTaskStatus(request.params.taskId, "cancelled", "Client cancelled task execution.", extra.sessionId);
|
|
33833
|
-
this._clearTaskQueue(request.params.taskId);
|
|
33836
|
+
this._clearTaskQueue(request.params.taskId, extra.sessionId);
|
|
33834
33837
|
const cancelledTask = await this._taskStore.getTask(request.params.taskId, extra.sessionId);
|
|
33835
33838
|
if (!cancelledTask) {
|
|
33836
33839
|
throw new McpError(ErrorCode.InvalidParams, `Task not found after cancellation: ${request.params.taskId}`);
|
|
@@ -33958,6 +33961,19 @@ var Protocol = class {
|
|
|
33958
33961
|
const handler = this._requestHandlers.get(request.method) ?? this.fallbackRequestHandler;
|
|
33959
33962
|
const capturedTransport = this._transport;
|
|
33960
33963
|
const relatedTaskId = request.params?._meta?.[RELATED_TASK_META_KEY]?.taskId;
|
|
33964
|
+
const sessionId = capturedTransport?.sessionId;
|
|
33965
|
+
const store = this._taskStore;
|
|
33966
|
+
let relatedTaskFound = true;
|
|
33967
|
+
let relatedTaskLookup;
|
|
33968
|
+
if (relatedTaskId && store && this._taskMessageQueue && sessionId !== void 0) {
|
|
33969
|
+
relatedTaskFound = false;
|
|
33970
|
+
relatedTaskLookup = (async () => {
|
|
33971
|
+
if (!await store.getTask(relatedTaskId, sessionId)) {
|
|
33972
|
+
throw new McpError(ErrorCode.InvalidParams, `Task not found: ${relatedTaskId}`);
|
|
33973
|
+
}
|
|
33974
|
+
relatedTaskFound = true;
|
|
33975
|
+
})();
|
|
33976
|
+
}
|
|
33961
33977
|
if (handler === void 0) {
|
|
33962
33978
|
const errorResponse = {
|
|
33963
33979
|
jsonrpc: "2.0",
|
|
@@ -33967,7 +33983,10 @@ var Protocol = class {
|
|
|
33967
33983
|
message: "Method not found"
|
|
33968
33984
|
}
|
|
33969
33985
|
};
|
|
33970
|
-
if (relatedTaskId &&
|
|
33986
|
+
if (relatedTaskId && relatedTaskLookup) {
|
|
33987
|
+
const queuedError = { type: "error", message: errorResponse, timestamp: Date.now() };
|
|
33988
|
+
relatedTaskLookup.then(() => this._enqueueTaskMessage(relatedTaskId, queuedError, sessionId), () => capturedTransport?.send(errorResponse)).catch((error2) => this._onerror(new Error(`Failed to send an error response: ${error2}`)));
|
|
33989
|
+
} else if (relatedTaskId && this._taskMessageQueue) {
|
|
33971
33990
|
this._enqueueTaskMessage(relatedTaskId, {
|
|
33972
33991
|
type: "error",
|
|
33973
33992
|
message: errorResponse,
|
|
@@ -34018,7 +34037,10 @@ var Protocol = class {
|
|
|
34018
34037
|
closeSSEStream: extra?.closeSSEStream,
|
|
34019
34038
|
closeStandaloneSSEStream: extra?.closeStandaloneSSEStream
|
|
34020
34039
|
};
|
|
34021
|
-
Promise.resolve().then(() => {
|
|
34040
|
+
(relatedTaskLookup ?? Promise.resolve()).then(() => {
|
|
34041
|
+
if (relatedTaskLookup && abortController.signal.aborted) {
|
|
34042
|
+
throw new McpError(ErrorCode.ConnectionClosed, "Request was cancelled");
|
|
34043
|
+
}
|
|
34022
34044
|
if (taskCreationParams) {
|
|
34023
34045
|
this.assertTaskHandlerCapability(request.method);
|
|
34024
34046
|
}
|
|
@@ -34053,7 +34075,7 @@ var Protocol = class {
|
|
|
34053
34075
|
...error2["data"] !== void 0 && { data: error2["data"] }
|
|
34054
34076
|
}
|
|
34055
34077
|
};
|
|
34056
|
-
if (relatedTaskId && this._taskMessageQueue) {
|
|
34078
|
+
if (relatedTaskId && this._taskMessageQueue && relatedTaskFound) {
|
|
34057
34079
|
await this._enqueueTaskMessage(relatedTaskId, {
|
|
34058
34080
|
type: "error",
|
|
34059
34081
|
message: errorResponse,
|
|
@@ -34533,7 +34555,7 @@ var Protocol = class {
|
|
|
34533
34555
|
throw new Error("Cannot enqueue task message: taskStore and taskMessageQueue are not configured");
|
|
34534
34556
|
}
|
|
34535
34557
|
const maxQueueSize = this._options?.maxTaskQueueSize;
|
|
34536
|
-
await this._taskMessageQueue.enqueue(taskId, message, sessionId, maxQueueSize);
|
|
34558
|
+
await this._taskMessageQueue.enqueue(taskId, message, sessionId ?? this._transport?.sessionId, maxQueueSize);
|
|
34537
34559
|
}
|
|
34538
34560
|
/**
|
|
34539
34561
|
* Clears the message queue for a task and rejects any pending request resolvers.
|
|
@@ -34562,12 +34584,13 @@ var Protocol = class {
|
|
|
34562
34584
|
* Uses polling to check for updates at the task's configured poll interval.
|
|
34563
34585
|
* @param taskId The task ID to wait for
|
|
34564
34586
|
* @param signal Abort signal to cancel the wait
|
|
34587
|
+
* @param sessionId Session of the request that waits, passed to the task store
|
|
34565
34588
|
* @returns Promise that resolves when an update occurs or rejects if aborted
|
|
34566
34589
|
*/
|
|
34567
|
-
async _waitForTaskUpdate(taskId, signal) {
|
|
34590
|
+
async _waitForTaskUpdate(taskId, signal, sessionId) {
|
|
34568
34591
|
let interval = this._options?.defaultTaskPollInterval ?? 1e3;
|
|
34569
34592
|
try {
|
|
34570
|
-
const task = await this._taskStore?.getTask(taskId);
|
|
34593
|
+
const task = await this._taskStore?.getTask(taskId, sessionId);
|
|
34571
34594
|
if (task?.pollInterval) {
|
|
34572
34595
|
interval = task.pollInterval;
|
|
34573
34596
|
}
|
|
@@ -35446,6 +35469,42 @@ var ExperimentalMcpServerTasks = class {
|
|
|
35446
35469
|
};
|
|
35447
35470
|
|
|
35448
35471
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
|
|
35472
|
+
function toolInputElementCount(value, max) {
|
|
35473
|
+
let count = 0;
|
|
35474
|
+
const stack = [value];
|
|
35475
|
+
while (stack.length > 0) {
|
|
35476
|
+
const node = stack.pop();
|
|
35477
|
+
if (node === null || typeof node !== "object")
|
|
35478
|
+
continue;
|
|
35479
|
+
if (Array.isArray(node)) {
|
|
35480
|
+
for (const child of node) {
|
|
35481
|
+
if (++count > max)
|
|
35482
|
+
return count;
|
|
35483
|
+
if (child !== null && typeof child === "object")
|
|
35484
|
+
stack.push(child);
|
|
35485
|
+
}
|
|
35486
|
+
} else {
|
|
35487
|
+
for (const key in node) {
|
|
35488
|
+
if (!Object.prototype.hasOwnProperty.call(node, key))
|
|
35489
|
+
continue;
|
|
35490
|
+
if (++count > max)
|
|
35491
|
+
return count;
|
|
35492
|
+
const child = node[key];
|
|
35493
|
+
if (child !== null && typeof child === "object")
|
|
35494
|
+
stack.push(child);
|
|
35495
|
+
}
|
|
35496
|
+
}
|
|
35497
|
+
}
|
|
35498
|
+
return count;
|
|
35499
|
+
}
|
|
35500
|
+
function resolveMaxToolInputElements(value) {
|
|
35501
|
+
if (value === void 0 || value === Infinity)
|
|
35502
|
+
return void 0;
|
|
35503
|
+
if (typeof value !== "number" || Number.isNaN(value) || value < 1) {
|
|
35504
|
+
throw new RangeError(`maxToolInputElements must be a number of at least 1, or Infinity, got ${String(value)}`);
|
|
35505
|
+
}
|
|
35506
|
+
return value;
|
|
35507
|
+
}
|
|
35449
35508
|
var McpServer = class {
|
|
35450
35509
|
constructor(serverInfo, options) {
|
|
35451
35510
|
this._registeredResources = {};
|
|
@@ -35457,6 +35516,7 @@ var McpServer = class {
|
|
|
35457
35516
|
this._resourceHandlersInitialized = false;
|
|
35458
35517
|
this._promptHandlersInitialized = false;
|
|
35459
35518
|
this.server = new Server(serverInfo, options);
|
|
35519
|
+
this._maxToolInputElements = resolveMaxToolInputElements(options?.maxToolInputElements);
|
|
35460
35520
|
}
|
|
35461
35521
|
/**
|
|
35462
35522
|
* Access experimental features.
|
|
@@ -35587,12 +35647,15 @@ var McpServer = class {
|
|
|
35587
35647
|
* Validates tool input arguments against the tool's input schema.
|
|
35588
35648
|
*/
|
|
35589
35649
|
async validateToolInput(tool, args, toolName) {
|
|
35650
|
+
if (this._maxToolInputElements !== void 0 && toolInputElementCount(args, this._maxToolInputElements) > this._maxToolInputElements) {
|
|
35651
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for tool ${toolName}: arguments contain more than the maximum of ${this._maxToolInputElements} elements`);
|
|
35652
|
+
}
|
|
35590
35653
|
if (!tool.inputSchema) {
|
|
35591
35654
|
return void 0;
|
|
35592
35655
|
}
|
|
35593
35656
|
const inputObj = normalizeObjectSchema(tool.inputSchema);
|
|
35594
35657
|
const schemaToParse = inputObj ?? tool.inputSchema;
|
|
35595
|
-
const parseResult = await safeParseAsync2(schemaToParse, args);
|
|
35658
|
+
const parseResult = await safeParseAsync2(schemaToParse, args ?? {});
|
|
35596
35659
|
if (!parseResult.success) {
|
|
35597
35660
|
const error2 = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
35598
35661
|
const errorMessage = getParseErrorMessage(error2);
|
|
@@ -35830,7 +35893,7 @@ var McpServer = class {
|
|
|
35830
35893
|
}
|
|
35831
35894
|
if (prompt.argsSchema) {
|
|
35832
35895
|
const argsObj = normalizeObjectSchema(prompt.argsSchema);
|
|
35833
|
-
const parseResult = await safeParseAsync2(argsObj, request.params.arguments);
|
|
35896
|
+
const parseResult = await safeParseAsync2(argsObj, request.params.arguments ?? {});
|
|
35834
35897
|
if (!parseResult.success) {
|
|
35835
35898
|
const error2 = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
35836
35899
|
const errorMessage = getParseErrorMessage(error2);
|
|
@@ -21387,6 +21387,9 @@ var Protocol = class {
|
|
|
21387
21387
|
this.setRequestHandler(GetTaskPayloadRequestSchema, async (request, extra) => {
|
|
21388
21388
|
const handleTaskResult = async () => {
|
|
21389
21389
|
const taskId = request.params.taskId;
|
|
21390
|
+
if (!await this._taskStore.getTask(taskId, extra.sessionId)) {
|
|
21391
|
+
throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
|
|
21392
|
+
}
|
|
21390
21393
|
if (this._taskMessageQueue) {
|
|
21391
21394
|
let queuedMessage;
|
|
21392
21395
|
while (queuedMessage = await this._taskMessageQueue.dequeue(taskId, extra.sessionId)) {
|
|
@@ -21417,12 +21420,12 @@ var Protocol = class {
|
|
|
21417
21420
|
throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
|
|
21418
21421
|
}
|
|
21419
21422
|
if (!isTerminal(task.status)) {
|
|
21420
|
-
await this._waitForTaskUpdate(taskId, extra.signal);
|
|
21423
|
+
await this._waitForTaskUpdate(taskId, extra.signal, extra.sessionId);
|
|
21421
21424
|
return await handleTaskResult();
|
|
21422
21425
|
}
|
|
21423
21426
|
if (isTerminal(task.status)) {
|
|
21424
21427
|
const result = await this._taskStore.getTaskResult(taskId, extra.sessionId);
|
|
21425
|
-
this._clearTaskQueue(taskId);
|
|
21428
|
+
this._clearTaskQueue(taskId, extra.sessionId);
|
|
21426
21429
|
return {
|
|
21427
21430
|
...result,
|
|
21428
21431
|
_meta: {
|
|
@@ -21459,7 +21462,7 @@ var Protocol = class {
|
|
|
21459
21462
|
throw new McpError(ErrorCode.InvalidParams, `Cannot cancel task in terminal status: ${task.status}`);
|
|
21460
21463
|
}
|
|
21461
21464
|
await this._taskStore.updateTaskStatus(request.params.taskId, "cancelled", "Client cancelled task execution.", extra.sessionId);
|
|
21462
|
-
this._clearTaskQueue(request.params.taskId);
|
|
21465
|
+
this._clearTaskQueue(request.params.taskId, extra.sessionId);
|
|
21463
21466
|
const cancelledTask = await this._taskStore.getTask(request.params.taskId, extra.sessionId);
|
|
21464
21467
|
if (!cancelledTask) {
|
|
21465
21468
|
throw new McpError(ErrorCode.InvalidParams, `Task not found after cancellation: ${request.params.taskId}`);
|
|
@@ -21587,6 +21590,19 @@ var Protocol = class {
|
|
|
21587
21590
|
const handler = this._requestHandlers.get(request.method) ?? this.fallbackRequestHandler;
|
|
21588
21591
|
const capturedTransport = this._transport;
|
|
21589
21592
|
const relatedTaskId = request.params?._meta?.[RELATED_TASK_META_KEY]?.taskId;
|
|
21593
|
+
const sessionId = capturedTransport?.sessionId;
|
|
21594
|
+
const store = this._taskStore;
|
|
21595
|
+
let relatedTaskFound = true;
|
|
21596
|
+
let relatedTaskLookup;
|
|
21597
|
+
if (relatedTaskId && store && this._taskMessageQueue && sessionId !== void 0) {
|
|
21598
|
+
relatedTaskFound = false;
|
|
21599
|
+
relatedTaskLookup = (async () => {
|
|
21600
|
+
if (!await store.getTask(relatedTaskId, sessionId)) {
|
|
21601
|
+
throw new McpError(ErrorCode.InvalidParams, `Task not found: ${relatedTaskId}`);
|
|
21602
|
+
}
|
|
21603
|
+
relatedTaskFound = true;
|
|
21604
|
+
})();
|
|
21605
|
+
}
|
|
21590
21606
|
if (handler === void 0) {
|
|
21591
21607
|
const errorResponse = {
|
|
21592
21608
|
jsonrpc: "2.0",
|
|
@@ -21596,7 +21612,10 @@ var Protocol = class {
|
|
|
21596
21612
|
message: "Method not found"
|
|
21597
21613
|
}
|
|
21598
21614
|
};
|
|
21599
|
-
if (relatedTaskId &&
|
|
21615
|
+
if (relatedTaskId && relatedTaskLookup) {
|
|
21616
|
+
const queuedError = { type: "error", message: errorResponse, timestamp: Date.now() };
|
|
21617
|
+
relatedTaskLookup.then(() => this._enqueueTaskMessage(relatedTaskId, queuedError, sessionId), () => capturedTransport?.send(errorResponse)).catch((error2) => this._onerror(new Error(`Failed to send an error response: ${error2}`)));
|
|
21618
|
+
} else if (relatedTaskId && this._taskMessageQueue) {
|
|
21600
21619
|
this._enqueueTaskMessage(relatedTaskId, {
|
|
21601
21620
|
type: "error",
|
|
21602
21621
|
message: errorResponse,
|
|
@@ -21647,7 +21666,10 @@ var Protocol = class {
|
|
|
21647
21666
|
closeSSEStream: extra?.closeSSEStream,
|
|
21648
21667
|
closeStandaloneSSEStream: extra?.closeStandaloneSSEStream
|
|
21649
21668
|
};
|
|
21650
|
-
Promise.resolve().then(() => {
|
|
21669
|
+
(relatedTaskLookup ?? Promise.resolve()).then(() => {
|
|
21670
|
+
if (relatedTaskLookup && abortController.signal.aborted) {
|
|
21671
|
+
throw new McpError(ErrorCode.ConnectionClosed, "Request was cancelled");
|
|
21672
|
+
}
|
|
21651
21673
|
if (taskCreationParams) {
|
|
21652
21674
|
this.assertTaskHandlerCapability(request.method);
|
|
21653
21675
|
}
|
|
@@ -21682,7 +21704,7 @@ var Protocol = class {
|
|
|
21682
21704
|
...error2["data"] !== void 0 && { data: error2["data"] }
|
|
21683
21705
|
}
|
|
21684
21706
|
};
|
|
21685
|
-
if (relatedTaskId && this._taskMessageQueue) {
|
|
21707
|
+
if (relatedTaskId && this._taskMessageQueue && relatedTaskFound) {
|
|
21686
21708
|
await this._enqueueTaskMessage(relatedTaskId, {
|
|
21687
21709
|
type: "error",
|
|
21688
21710
|
message: errorResponse,
|
|
@@ -22162,7 +22184,7 @@ var Protocol = class {
|
|
|
22162
22184
|
throw new Error("Cannot enqueue task message: taskStore and taskMessageQueue are not configured");
|
|
22163
22185
|
}
|
|
22164
22186
|
const maxQueueSize = this._options?.maxTaskQueueSize;
|
|
22165
|
-
await this._taskMessageQueue.enqueue(taskId, message, sessionId, maxQueueSize);
|
|
22187
|
+
await this._taskMessageQueue.enqueue(taskId, message, sessionId ?? this._transport?.sessionId, maxQueueSize);
|
|
22166
22188
|
}
|
|
22167
22189
|
/**
|
|
22168
22190
|
* Clears the message queue for a task and rejects any pending request resolvers.
|
|
@@ -22191,12 +22213,13 @@ var Protocol = class {
|
|
|
22191
22213
|
* Uses polling to check for updates at the task's configured poll interval.
|
|
22192
22214
|
* @param taskId The task ID to wait for
|
|
22193
22215
|
* @param signal Abort signal to cancel the wait
|
|
22216
|
+
* @param sessionId Session of the request that waits, passed to the task store
|
|
22194
22217
|
* @returns Promise that resolves when an update occurs or rejects if aborted
|
|
22195
22218
|
*/
|
|
22196
|
-
async _waitForTaskUpdate(taskId, signal) {
|
|
22219
|
+
async _waitForTaskUpdate(taskId, signal, sessionId) {
|
|
22197
22220
|
let interval = this._options?.defaultTaskPollInterval ?? 1e3;
|
|
22198
22221
|
try {
|
|
22199
|
-
const task = await this._taskStore?.getTask(taskId);
|
|
22222
|
+
const task = await this._taskStore?.getTask(taskId, sessionId);
|
|
22200
22223
|
if (task?.pollInterval) {
|
|
22201
22224
|
interval = task.pollInterval;
|
|
22202
22225
|
}
|
|
@@ -25438,7 +25461,7 @@ function sessionTokenFixHint(policy) {
|
|
|
25438
25461
|
}
|
|
25439
25462
|
|
|
25440
25463
|
// plugins/kxm/src/mcp-server.ts
|
|
25441
|
-
var VERSION = "0.7.
|
|
25464
|
+
var VERSION = "0.7.176";
|
|
25442
25465
|
var CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
|
|
25443
25466
|
var inbox = /* @__PURE__ */ new Map();
|
|
25444
25467
|
var notifiedInbox = /* @__PURE__ */ new Set();
|
|
@@ -17298,8 +17298,17 @@ function argsFor(entry, scope) {
|
|
|
17298
17298
|
if (scope === "extensions") return entry.update.extensions;
|
|
17299
17299
|
return entry.update.models;
|
|
17300
17300
|
}
|
|
17301
|
+
function detectedHarnesses(inventory, requestedHarness) {
|
|
17302
|
+
if (requestedHarness) return inventory.harnesses.filter((entry) => entry.id === requestedHarness);
|
|
17303
|
+
const detected = inventory.harnesses.filter((entry) => entry.detected);
|
|
17304
|
+
const preferred = inventory.defaultHarness;
|
|
17305
|
+
return [
|
|
17306
|
+
...detected.filter((entry) => entry.id === preferred),
|
|
17307
|
+
...detected.filter((entry) => entry.id !== preferred)
|
|
17308
|
+
];
|
|
17309
|
+
}
|
|
17301
17310
|
function planHarnessUpdate(inventory, requested) {
|
|
17302
|
-
const selected =
|
|
17311
|
+
const selected = detectedHarnesses(inventory, requested.harness);
|
|
17303
17312
|
if (requested.harness && selected.length === 0) {
|
|
17304
17313
|
return [{
|
|
17305
17314
|
harness: requested.harness,
|
|
@@ -17310,36 +17319,44 @@ function planHarnessUpdate(inventory, requested) {
|
|
|
17310
17319
|
detail: isKnownHarnessId(requested.harness) ? "not_detected" : "unknown_harness"
|
|
17311
17320
|
}];
|
|
17312
17321
|
}
|
|
17322
|
+
const waves = requested.scope === "all" ? ["self", "extensions", "models"] : [requested.scope];
|
|
17313
17323
|
const steps = [];
|
|
17314
|
-
for (const
|
|
17315
|
-
const
|
|
17316
|
-
|
|
17317
|
-
|
|
17318
|
-
|
|
17319
|
-
|
|
17320
|
-
|
|
17321
|
-
|
|
17322
|
-
|
|
17323
|
-
|
|
17324
|
-
|
|
17325
|
-
|
|
17326
|
-
|
|
17327
|
-
|
|
17328
|
-
|
|
17329
|
-
|
|
17324
|
+
for (const scope of waves) {
|
|
17325
|
+
for (const status of selected) {
|
|
17326
|
+
const entry = BUILTIN_HARNESSES.find((candidate) => candidate.id === status.id);
|
|
17327
|
+
if (!entry) continue;
|
|
17328
|
+
const supported = scopesFor(requested.scope === "all" ? "all" : scope, entry);
|
|
17329
|
+
if (!supported.includes(scope)) {
|
|
17330
|
+
if (requested.scope !== "all") {
|
|
17331
|
+
steps.push({
|
|
17332
|
+
harness: status.id,
|
|
17333
|
+
scope,
|
|
17334
|
+
command: status.command ?? entry.commands[0],
|
|
17335
|
+
args: [],
|
|
17336
|
+
outcome: "skipped",
|
|
17337
|
+
detail: "no_updater"
|
|
17338
|
+
});
|
|
17339
|
+
}
|
|
17340
|
+
continue;
|
|
17341
|
+
}
|
|
17342
|
+
if (!status.detected || !status.command) {
|
|
17343
|
+
steps.push({
|
|
17344
|
+
harness: status.id,
|
|
17345
|
+
scope,
|
|
17346
|
+
command: entry.commands[0],
|
|
17347
|
+
args: argsFor(entry, scope) ?? [],
|
|
17348
|
+
outcome: "skipped",
|
|
17349
|
+
detail: "not_detected"
|
|
17350
|
+
});
|
|
17351
|
+
continue;
|
|
17352
|
+
}
|
|
17330
17353
|
steps.push({
|
|
17331
17354
|
harness: status.id,
|
|
17332
|
-
scope
|
|
17333
|
-
command:
|
|
17334
|
-
args: argsFor(entry,
|
|
17335
|
-
outcome: "
|
|
17336
|
-
detail: "not_detected"
|
|
17355
|
+
scope,
|
|
17356
|
+
command: status.command,
|
|
17357
|
+
args: argsFor(entry, scope) ?? [],
|
|
17358
|
+
outcome: "would"
|
|
17337
17359
|
});
|
|
17338
|
-
continue;
|
|
17339
|
-
}
|
|
17340
|
-
for (const scope of scopes) {
|
|
17341
|
-
const args = argsFor(entry, scope) ?? [];
|
|
17342
|
-
steps.push({ harness: status.id, scope, command: status.command, args, outcome: "would" });
|
|
17343
17360
|
}
|
|
17344
17361
|
}
|
|
17345
17362
|
return steps;
|
package/plugins/kxm/package.json
CHANGED
|
@@ -34,15 +34,20 @@ still blocks the tools.
|
|
|
34
34
|
| Command | Purpose |
|
|
35
35
|
|---|---|
|
|
36
36
|
| `kxm update --check` | Check for a kxm package update without applying it |
|
|
37
|
-
| `kxm update
|
|
37
|
+
| `kxm update` / `--all` | Harness `--self` first, then kxm, extensions, and models |
|
|
38
|
+
| `kxm update --kxm` | Apply only the kxm package update |
|
|
38
39
|
| `kxm update [harness] --self` | Update only the harness CLI |
|
|
39
40
|
| `kxm update [harness] --extensions` | Update only extensions and plugins (Pi packages, the Claude kxm plugin) |
|
|
40
41
|
| `kxm update [harness] --models` | Refresh model catalogs where the harness supports it |
|
|
41
42
|
|
|
42
|
-
- `kxm update
|
|
43
|
-
|
|
43
|
+
- Bare `kxm update` (and `--all`) updates detected harness CLIs first, then kxm,
|
|
44
|
+
then extensions and models. `--kxm` is kxm-only. kxm installs from npm by default;
|
|
45
|
+
it needs an authenticated `gh` only when `update.yaml` sets `source: github`.
|
|
46
|
+
It uses `gh` when that CLI answers `gh --version`, and otherwise curl with the same
|
|
44
47
|
sha256 check. A host with neither can follow the curl commands in
|
|
45
48
|
docs/operations/upgrade.md.
|
|
49
|
+
Inside the kxm repository it installs the local files. Detected harness updater
|
|
50
|
+
failures on `--all` are degraded, not fatal.
|
|
46
51
|
- `kxm update claude --extensions` updates only a user-scope plugin install;
|
|
47
52
|
its dry run prints `claude extensions: would claude plugin update kxm -y`.
|
|
48
53
|
A project-scope install is the operator's `claude plugin update kxm@kxm --scope project`.
|
|
@@ -68,6 +68,7 @@ import {
|
|
|
68
68
|
} from "../init-guide-setup.ts";
|
|
69
69
|
import {
|
|
70
70
|
classifyInstallRoot,
|
|
71
|
+
kxmSourceCheckoutRoot,
|
|
71
72
|
resolveInstallKind,
|
|
72
73
|
type InstallKindReport,
|
|
73
74
|
type InstallProbe,
|
|
@@ -161,7 +162,7 @@ export function applyKxmPackageUpdate(runtime: Runtime, notice: KxmUpdateNotice)
|
|
|
161
162
|
const releaseDir = mkdtempSync(join(tmpdir(), "kxm-pkg-update-"));
|
|
162
163
|
try {
|
|
163
164
|
const downloader = notice.source === "github" && !ghReleaseDownloadAvailable(runtime.env) ? "curl" : "gh";
|
|
164
|
-
const planned = planKxmPackageUpdate(notice.source, notice.latest, releaseDir, notice.asset, downloader);
|
|
165
|
+
const planned = planKxmPackageUpdate(notice.source, notice.latest, releaseDir, notice.asset, downloader, notice.localRoot);
|
|
165
166
|
if (runtime.dryRun) {
|
|
166
167
|
return { ok: true, detail: planned.map(formatPackageUpdateStep).join(" && ") };
|
|
167
168
|
}
|
|
@@ -309,14 +310,15 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
|
|
|
309
310
|
models?: boolean | undefined;
|
|
310
311
|
check?: boolean | undefined;
|
|
311
312
|
kxm?: boolean | undefined;
|
|
313
|
+
all?: boolean | undefined;
|
|
312
314
|
}): Promise<number> {
|
|
313
|
-
if (options.check && (options.kxm || options.self || options.extensions || options.models || harness)) {
|
|
315
|
+
if (options.check && (options.kxm || options.self || options.extensions || options.models || options.all || harness)) {
|
|
314
316
|
print(runtime.io, runtime.json, { ok: false, command: "update", error: "scope_conflict" }, "--check cannot be combined with other update flags");
|
|
315
317
|
return 2;
|
|
316
318
|
}
|
|
317
|
-
const selected = [options.self, options.extensions, options.models].filter(Boolean).length;
|
|
319
|
+
const selected = [options.self, options.extensions, options.models, options.all].filter(Boolean).length;
|
|
318
320
|
if (selected > 1) {
|
|
319
|
-
print(runtime.io, runtime.json, { ok: false, command: "update", error: "scope_conflict" }, "specify at most one of --self, --extensions, or --models");
|
|
321
|
+
print(runtime.io, runtime.json, { ok: false, command: "update", error: "scope_conflict" }, "specify at most one of --all, --self, --extensions, or --models");
|
|
320
322
|
return 2;
|
|
321
323
|
}
|
|
322
324
|
warnIgnoredProjectUpdateYaml(runtime);
|
|
@@ -328,10 +330,24 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
|
|
|
328
330
|
// `cd ~ && kxm update --kxm` into an uncaught ENOENT on `$HOME/package.json`,
|
|
329
331
|
// and would happily compare a stranger project's version wherever one did
|
|
330
332
|
// exist. Unreadable is now a stated refusal.
|
|
333
|
+
// Exception: a cwd that *is* this repository (`@kontextmind/kxm` plus `.git`)
|
|
334
|
+
// is a local-files apply, not a stranger version compare.
|
|
331
335
|
const current = installedKxmVersion(classified.root) ?? installedKxmVersion(findKxmRepoRoot(import.meta.url));
|
|
336
|
+
const localRoot = kxmSourceCheckoutRoot(runtime.dirs.workdir) ?? kxmSourceCheckoutRoot(runtime.cwd);
|
|
332
337
|
let notice: KxmUpdateNotice;
|
|
333
338
|
let kindReport = classified;
|
|
334
|
-
if (
|
|
339
|
+
if (localRoot) {
|
|
340
|
+
const localVersion = installedKxmVersion(localRoot) ?? current ?? "unknown";
|
|
341
|
+
notice = {
|
|
342
|
+
current: current ?? localVersion,
|
|
343
|
+
latest: localVersion,
|
|
344
|
+
available: true,
|
|
345
|
+
auto: false,
|
|
346
|
+
source: "local",
|
|
347
|
+
message: `kxm ${current ?? localVersion} (local ${localRoot})`,
|
|
348
|
+
localRoot,
|
|
349
|
+
};
|
|
350
|
+
} else if (classified.kind === "source") {
|
|
335
351
|
if (options.check) {
|
|
336
352
|
const message = `kxm ${current ?? "unknown"} (running from source at ${classified.root})`;
|
|
337
353
|
print(runtime.io, runtime.json, {
|
|
@@ -340,7 +356,7 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
|
|
|
340
356
|
current: current ?? "unknown",
|
|
341
357
|
available: false,
|
|
342
358
|
auto: false,
|
|
343
|
-
source: "
|
|
359
|
+
source: "npm",
|
|
344
360
|
installKind: "source",
|
|
345
361
|
root: classified.root,
|
|
346
362
|
message,
|
|
@@ -362,7 +378,7 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
|
|
|
362
378
|
current: current ?? "unknown",
|
|
363
379
|
available: false,
|
|
364
380
|
auto: false,
|
|
365
|
-
source: "
|
|
381
|
+
source: "npm",
|
|
366
382
|
message: `kxm ${current ?? "unknown"} (running from source)`,
|
|
367
383
|
};
|
|
368
384
|
} else {
|
|
@@ -385,13 +401,30 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
|
|
|
385
401
|
}, notice.message);
|
|
386
402
|
return 0;
|
|
387
403
|
}
|
|
388
|
-
const
|
|
404
|
+
const fullUpdate = Boolean(options.all) || (selected === 0 && !options.kxm && !harness);
|
|
405
|
+
const applyKxm = Boolean(options.kxm || fullUpdate || notice.auto);
|
|
389
406
|
let kxmApply: { ok: boolean; detail: string; error?: string } | undefined;
|
|
390
|
-
|
|
407
|
+
const applyKxmPackage = (requireSuccess: boolean): number | undefined => {
|
|
408
|
+
if (!applyKxm) return undefined;
|
|
409
|
+
if (notice.source === "local" && notice.localRoot) {
|
|
410
|
+
kxmApply = applyKxmPackageUpdate(runtime, notice);
|
|
411
|
+
if (!kxmApply.ok && requireSuccess) {
|
|
412
|
+
print(runtime.io, runtime.json, {
|
|
413
|
+
ok: false,
|
|
414
|
+
command: "update",
|
|
415
|
+
error: kxmApply.error,
|
|
416
|
+
kxm: kxmApply,
|
|
417
|
+
notice,
|
|
418
|
+
...installKindPayload(classified),
|
|
419
|
+
}, kxmApply.detail);
|
|
420
|
+
return 1;
|
|
421
|
+
}
|
|
422
|
+
return undefined;
|
|
423
|
+
}
|
|
391
424
|
const resolved = resolveInstallKind(probe, npmGlobalRootFn(runtime));
|
|
392
425
|
kindReport = resolved;
|
|
393
426
|
if (resolved.kind !== "npm-global") {
|
|
394
|
-
if (
|
|
427
|
+
if (requireSuccess) {
|
|
395
428
|
print(runtime.io, runtime.json, {
|
|
396
429
|
ok: false,
|
|
397
430
|
command: "update",
|
|
@@ -404,9 +437,11 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
|
|
|
404
437
|
return 2;
|
|
405
438
|
}
|
|
406
439
|
if (notice.available) runtime.io.stderr(`kxm: ${resolved.instruction}\n`);
|
|
407
|
-
|
|
440
|
+
return undefined;
|
|
441
|
+
}
|
|
442
|
+
if (notice.available) {
|
|
408
443
|
kxmApply = applyKxmPackageUpdate(runtime, notice);
|
|
409
|
-
if (!kxmApply.ok &&
|
|
444
|
+
if (!kxmApply.ok && requireSuccess) {
|
|
410
445
|
print(runtime.io, runtime.json, {
|
|
411
446
|
ok: false,
|
|
412
447
|
command: "update",
|
|
@@ -418,9 +453,12 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
|
|
|
418
453
|
return 1;
|
|
419
454
|
}
|
|
420
455
|
}
|
|
421
|
-
|
|
422
|
-
|
|
456
|
+
return undefined;
|
|
457
|
+
};
|
|
458
|
+
const skipHarness = Boolean(options.kxm && !fullUpdate && selected === 0 && !harness && !notice.auto);
|
|
423
459
|
if (skipHarness) {
|
|
460
|
+
const aborted = applyKxmPackage(true);
|
|
461
|
+
if (aborted !== undefined) return aborted;
|
|
424
462
|
print(
|
|
425
463
|
runtime.io,
|
|
426
464
|
runtime.json,
|
|
@@ -431,23 +469,39 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
|
|
|
431
469
|
}
|
|
432
470
|
const scope: HarnessUpdateScope = options.self ? "self" : options.extensions ? "extensions" : options.models ? "models" : "all";
|
|
433
471
|
const inventory = await probeHarnessesAsync({ env: runtime.env });
|
|
434
|
-
const planned = planHarnessUpdate(inventory, { ...(harness ? { harness } : {}), scope });
|
|
435
|
-
const
|
|
436
|
-
|
|
472
|
+
const planned = planHarnessUpdate(inventory, { ...(harness ? { harness } : {}), scope: fullUpdate ? "all" : scope });
|
|
473
|
+
const runOpts = { env: runtime.env, dryRun: runtime.dryRun };
|
|
474
|
+
let steps;
|
|
475
|
+
if (fullUpdate) {
|
|
476
|
+
const selfSteps = runHarnessUpdate(planned.filter((step) => step.scope === "self"), runOpts);
|
|
477
|
+
applyKxmPackage(false);
|
|
478
|
+
const restSteps = runHarnessUpdate(planned.filter((step) => step.scope !== "self"), runOpts);
|
|
479
|
+
steps = [...selfSteps, ...restSteps];
|
|
480
|
+
} else {
|
|
481
|
+
const aborted = applyKxmPackage(Boolean(options.kxm));
|
|
482
|
+
if (aborted !== undefined) return aborted;
|
|
483
|
+
steps = runHarnessUpdate(planned, runOpts);
|
|
484
|
+
}
|
|
485
|
+
const kxmFailed = kxmApply?.ok === false;
|
|
437
486
|
const skippedUnknown = steps.some((step) => step.detail === "unknown_harness");
|
|
487
|
+
const harnessFailed = steps.some((step) => step.outcome === "failed");
|
|
438
488
|
const text = [notice.available ? notice.message : undefined, kxmApply?.detail, formatHarnessUpdate(steps)].filter(Boolean).join("\n");
|
|
439
489
|
print(runtime.io, runtime.json, {
|
|
440
|
-
ok: !
|
|
490
|
+
ok: !kxmFailed && !skippedUnknown,
|
|
441
491
|
command: "update",
|
|
442
492
|
dryRun: runtime.dryRun,
|
|
443
|
-
scope,
|
|
493
|
+
scope: fullUpdate ? "all" : scope,
|
|
494
|
+
defaultHarness: inventory.defaultHarness,
|
|
444
495
|
notice,
|
|
445
496
|
...(kxmApply ? { kxm: kxmApply } : {}),
|
|
446
497
|
steps,
|
|
498
|
+
...(harnessFailed ? { degraded: true } : {}),
|
|
447
499
|
...installKindPayload(kindReport),
|
|
448
500
|
}, text);
|
|
449
501
|
if (skippedUnknown) return 2;
|
|
450
|
-
|
|
502
|
+
if (kxmFailed) return 1;
|
|
503
|
+
if (harnessFailed && !fullUpdate) return 1;
|
|
504
|
+
return 0;
|
|
451
505
|
}
|
|
452
506
|
|
|
453
507
|
export async function cmdValidate(runtime: Runtime, fileFlag?: string | undefined): Promise<number> {
|
package/plugins/kxm/src/cli.ts
CHANGED
|
@@ -788,8 +788,9 @@ function createProgram(ctx: CliContext, result: { code: number }, argv: readonly
|
|
|
788
788
|
addGlobalOptions(program.command("update").description("Update kxm, harness CLIs, extensions, plugins, and model catalogs")
|
|
789
789
|
.argument("[harness]", "Harness id (default: every detected harness)")
|
|
790
790
|
.option("--check", "Check for a kxm package update without applying")
|
|
791
|
-
.option("--
|
|
792
|
-
.option("--
|
|
791
|
+
.option("--all", "Update harness CLIs first (--self), then kxm, extensions, and models (default)")
|
|
792
|
+
.option("--kxm", "Apply only the kxm operator package update")
|
|
793
|
+
.option("--self", "Update only harness CLIs")
|
|
793
794
|
.option("--extensions", "Update only extensions/plugins (Pi packages, Claude kxm)")
|
|
794
795
|
.option("--models", "Refresh model catalogs where the harness supports it"))
|
|
795
796
|
.action(async function updateAction(this: Command, harness: string | undefined, options: {
|
|
@@ -798,6 +799,7 @@ function createProgram(ctx: CliContext, result: { code: number }, argv: readonly
|
|
|
798
799
|
models?: boolean;
|
|
799
800
|
check?: boolean;
|
|
800
801
|
kxm?: boolean;
|
|
802
|
+
all?: boolean;
|
|
801
803
|
}) {
|
|
802
804
|
result.code = await cmdUpdate(runtimeFrom(ctx, this), harness, options);
|
|
803
805
|
});
|