@kontextmind/kxm 0.7.118 → 0.7.120

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.
@@ -415,28 +415,31 @@ export function resolveKxmRunTarget(
415
415
  runtime: Runtime,
416
416
  workflow: string | undefined,
417
417
  forTask = false,
418
+ envelope: Record<string, unknown> = {},
419
+ allowDefaultWorkflow = false,
418
420
  ): { projectRoot: string; workflowId: string; configRevision: string; defaultHarness: string; prerequisites: KxmRunHandoff[] } | number {
419
421
  if (runtime.workspaceFlag !== undefined) {
420
422
  print(runtime.io, runtime.json, {
421
423
  ok: false,
422
424
  command: "run",
423
425
  error: "workspace_option_unsupported",
426
+ ...envelope,
424
427
  }, "kxm run discovers the authoritative project from the current directory; --workspace is not supported");
425
428
  return 2;
426
429
  }
427
- if (!workflow && !forTask) {
428
- print(runtime.io, runtime.json, { ok: false, command: "run", error: "workflow_required" }, "usage: kxm run <workflow> [prompt]");
430
+ if (!workflow && !forTask && !allowDefaultWorkflow) {
431
+ print(runtime.io, runtime.json, { ok: false, command: "run", error: "workflow_required", ...envelope }, "usage: kxm run <workflow> [prompt]");
429
432
  return 2;
430
433
  }
431
434
  const projectRoot = discoverKxmProjectRoot(runtime.cwd);
432
435
  if (!projectRoot) {
433
- print(runtime.io, runtime.json, { ok: false, command: "run", error: "project_required" }, "kxm run requires a KXM project (run kxm init first)");
436
+ print(runtime.io, runtime.json, { ok: false, command: "run", error: "project_required", ...envelope }, "kxm run requires a KXM project (run kxm init first)");
434
437
  return 1;
435
438
  }
436
439
  const bundle = loadKxmProject(projectRoot, {});
437
440
  const workflowId = workflow ?? String(bundle.project.value.defaultWorkflow ?? "default");
438
441
  if (!bundle.workflows.has(workflowId)) {
439
- print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_workflow_unknown", workflow: workflowId }, `workflow ${workflowId} does not exist in this project`);
442
+ print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_workflow_unknown", workflow: workflowId, ...envelope }, `workflow ${workflowId} does not exist in this project`);
440
443
  return 1;
441
444
  }
442
445
  const defaultHarness = String(bundle.project.value.defaultHarness ?? "pi");
@@ -451,9 +454,16 @@ export function resolveKxmRunTarget(
451
454
  return { projectRoot, workflowId, configRevision: bundle.configRevision, defaultHarness, prerequisites };
452
455
  }
453
456
 
454
- export async function cmdKxmRun(runtime: Runtime, workflow: string | undefined, promptParts: string[], forTask = false): Promise<number> {
457
+ export async function cmdKxmRun(
458
+ runtime: Runtime,
459
+ workflow: string | undefined,
460
+ promptParts: string[],
461
+ forTask = false,
462
+ call?: { brief?: string; prompt?: string; runIdOut?: { runId?: string }; allowDefaultWorkflow?: boolean },
463
+ ): Promise<number> {
464
+ const envelope = call?.brief !== undefined ? { brief: call.brief } : {};
455
465
  try {
456
- const target = resolveKxmRunTarget(runtime, workflow, forTask);
466
+ const target = resolveKxmRunTarget(runtime, workflow, forTask, envelope, call?.allowDefaultWorkflow === true);
457
467
  if (typeof target === "number") return target;
458
468
  const { projectRoot } = target;
459
469
  if (runtime.dryRun) {
@@ -461,21 +471,24 @@ export async function cmdKxmRun(runtime: Runtime, workflow: string | undefined,
461
471
  ok: true,
462
472
  command: "run",
463
473
  dryRun: true,
474
+ ...envelope,
464
475
  ...target,
465
476
  }, `run plan: workflow ${target.workflowId} at ${target.configRevision.slice(0, 19)}… (no run created)`);
466
477
  return 0;
467
478
  }
468
479
  const supervisor = await (kxmDriveCliSeams.ensureSupervisor ?? ensureKxmSupervisor)({ env: runtime.env });
469
- const prompt = promptParts.join(" ").trim();
480
+ const prompt = call?.prompt !== undefined ? call.prompt : promptParts.join(" ").trim();
470
481
  const acceptance = await (kxmDriveCliSeams.runtimeRequest ?? kxmRuntimeRequest)(supervisor, "POST", "/v1/runs", {
471
482
  projectRoot,
472
483
  workflowId: target.workflowId,
473
484
  prompt,
474
485
  });
475
486
  const run = acceptance.run as { runId: string; homeRuntimeId: string; status: string; configRevision: string };
487
+ if (call?.runIdOut) call.runIdOut.runId = run.runId;
476
488
  print(runtime.io, runtime.json, {
477
489
  ok: true,
478
490
  command: "run",
491
+ ...envelope,
479
492
  execution: {
480
493
  status: "not_started",
481
494
  mode: "live",
@@ -496,10 +509,10 @@ export async function cmdKxmRun(runtime: Runtime, workflow: string | undefined,
496
509
  return 0;
497
510
  } catch (error) {
498
511
  if (error instanceof KxmConfigError) {
499
- print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_failed", issues: error.issues }, `run failed: ${error.message}`);
512
+ print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_failed", issues: error.issues, ...envelope }, `run failed: ${error.message}`);
500
513
  return 1;
501
514
  }
502
- print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_io_failed" }, "run failed because a local operation did not complete");
515
+ print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_io_failed", ...envelope }, "run failed because a local operation did not complete");
503
516
  return 1;
504
517
  }
505
518
  }
@@ -104,7 +104,6 @@ import {
104
104
  cmdBackup,
105
105
  cmdRestore,
106
106
  cmdKxmTrust,
107
- cmdKxmRun,
108
107
  cmdKxmRunStatus,
109
108
  cmdKxmRunDrive,
110
109
  cmdKxmRunReceipt,
@@ -122,6 +121,16 @@ cmdBackup,
122
121
 
123
122
  import { cmdPluginInstall } from "./cli/plugins.ts";
124
123
 
124
+ import {
125
+ applyLane,
126
+ cmdKxmRunCli,
127
+ cmdLaneCreate,
128
+ cmdLaneDrop,
129
+ cmdLaneList,
130
+ cmdLaneRun,
131
+ cmdLaneStatus,
132
+ } from "./cli/lanes.ts";
133
+
125
134
  import {
126
135
  cmdStatus,
127
136
  cmdDash,
@@ -187,6 +196,7 @@ const USAGE_ERROR_CODES = new Set([
187
196
  */
188
197
  const DRY_RUN_COMMANDS: ReadonlySet<string> = new Set([
189
198
  "init", "backup", "restore", "run", "explain", "suggest", "update", "dash", "completion", "completion install",
199
+ "lane create", "lane list", "lane status", "lane drop", "lane run",
190
200
  "plugin install", "plugin",
191
201
  "runs status", "runs drive", "runs receipt", "runs cancel", "runs list",
192
202
  "tenant status", "models inventory-refresh", "harness list", "auth token",
@@ -435,26 +445,76 @@ function createProgram(ctx: CliContext, result: { code: number }): Command {
435
445
  result.code = await cmdRestore(runtimeFrom(ctx, this), manifest, options);
436
446
  });
437
447
 
448
+ const laneCmd = addGlobalOptions(program.command("lane").description("Manage one git worktree per unit of work"));
449
+ laneCmd.helpCommand("help", "Show lane help");
450
+ addGlobalOptions(laneCmd.command("create").description("Create a worktree lane from a base ref (default origin/main)"))
451
+ .argument("<unit>", "Lane unit name")
452
+ .option("--base <ref>", "Base ref to resolve before adding the worktree (default origin/main)")
453
+ .action(async function laneCreateAction(this: Command, unit: string, options: { base?: string }) {
454
+ result.code = await cmdLaneCreate(runtimeFrom(ctx, this), unit, options);
455
+ });
456
+ addGlobalOptions(laneCmd.command("list").description("List lanes with dirty, ahead, and behind counts"))
457
+ .action(async function laneListAction(this: Command) {
458
+ result.code = await cmdLaneList(runtimeFrom(ctx, this));
459
+ });
460
+ addGlobalOptions(laneCmd.command("status").description("Show one lane and the projected status of its last run"))
461
+ .argument("<unit>", "Lane unit name")
462
+ .action(async function laneStatusAction(this: Command, unit: string) {
463
+ result.code = await cmdLaneStatus(runtimeFrom(ctx, this), unit);
464
+ });
465
+ addGlobalOptions(laneCmd.command("drop").description("Remove a lane worktree and its record without deleting the branch"))
466
+ .argument("<unit>", "Lane unit name")
467
+ .option("--force", "Remove a dirty lane or one whose last run is still open")
468
+ .action(async function laneDropAction(this: Command, unit: string, options: { force?: boolean }) {
469
+ result.code = await cmdLaneDrop(runtimeFrom(ctx, this), unit, { force: options.force === true });
470
+ });
471
+ addGlobalOptions(laneCmd.command("run").description("Create the lane if needed, start a run from a brief, and drive it"))
472
+ .argument("<unit>", "Lane unit name")
473
+ .requiredOption("--brief <file>", "Read the run prompt from a file")
474
+ .option("--workflow <id>", "Workflow id (default: the project default)")
475
+ .option("--base <ref>", "Base ref when the lane does not exist yet (default origin/main)")
476
+ .option("--wait", "Wait until a drive receipt is recorded")
477
+ .option("--timeout-ms <n>", "Wait timeout in milliseconds (default 60000, max 600000)")
478
+ .action(async function laneRunAction(this: Command, unit: string, options: { brief?: string; workflow?: string; base?: string; wait?: boolean; timeoutMs?: string }) {
479
+ result.code = await cmdLaneRun(runtimeFrom(ctx, this), unit, {
480
+ ...(options.brief !== undefined ? { brief: options.brief } : {}),
481
+ ...(options.workflow !== undefined ? { workflow: options.workflow } : {}),
482
+ ...(options.base !== undefined ? { base: options.base } : {}),
483
+ wait: options.wait === true,
484
+ ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
485
+ });
486
+ });
487
+
438
488
  addGlobalOptions(program.command("run").description("Create a KXM run without executing steps; follow its prerequisites, then kxm runs drive <runId> --wait")
439
489
  .argument("[workflow]", "Workflow id to run")
440
490
  .argument("[prompt...]", "Run prompt (events keep its hash; the full text is kept in a local 0600 sidecar file)")
441
- .action(async function runAction(this: Command, workflow: string | undefined, promptParts: string[]) {
442
- result.code = await cmdKxmRun(runtimeFrom(ctx, this), workflow, promptParts);
491
+ .option("--brief <file>", "Read the run prompt from a file instead of the positional prompt")
492
+ .option("--lane <unit>", "Discover the project from this lane's worktree")
493
+ .action(async function runAction(this: Command, workflow: string | undefined, promptParts: string[], options: { brief?: string; lane?: string }) {
494
+ result.code = await cmdKxmRunCli(runtimeFrom(ctx, this), workflow, promptParts, options);
443
495
  }));
444
496
  const runCmd = addGlobalOptions(program.command("runs").description("Inspect KXM runs"));
445
497
  runCmd.helpCommand("help", "Show runs help");
446
498
  addGlobalOptions(runCmd.command("status").description("Show the projected status of a run, including durable drive receipt state (open / receipt verified / unsettled / orphaned)"))
447
499
  .argument("<runId>", "Run id")
448
- .action(async function runStatusAction(this: Command, runId: string) {
449
- result.code = await cmdKxmRunStatus(runtimeFrom(ctx, this), runId);
500
+ .option("--lane <unit>", "Discover the project from this lane's worktree")
501
+ .action(async function runStatusAction(this: Command, runId: string, options: { lane?: string }) {
502
+ const runtime = applyLane(runtimeFrom(ctx, this), options.lane, "runs status");
503
+ result.code = typeof runtime === "number" ? runtime : await cmdKxmRunStatus(runtime, runId);
450
504
  });
451
505
  addGlobalOptions(runCmd.command("drive").description("Drive a run with live harness calls, or with the model-free simulation when --simulated is passed"))
452
506
  .argument("<runId>", "Run id")
453
507
  .option("--simulated", "Use the model-free simulation producer instead of a live model")
454
508
  .option("--wait", "Wait until a drive receipt is recorded; exits 0 only for a VERIFIED COMPLETED settlement")
455
509
  .option("--timeout-ms <n>", "Wait timeout in milliseconds (default 60000, max 600000)")
456
- .action(async function runDriveAction(this: Command, runId: string, options: { simulated?: boolean; wait?: boolean; timeoutMs?: string }) {
457
- result.code = await cmdKxmRunDrive(runtimeFrom(ctx, this), runId, options.simulated === true, {
510
+ .option("--lane <unit>", "Discover the project from this lane's worktree")
511
+ .action(async function runDriveAction(this: Command, runId: string, options: { simulated?: boolean; wait?: boolean; timeoutMs?: string; lane?: string }) {
512
+ const runtime = applyLane(runtimeFrom(ctx, this), options.lane, "runs drive");
513
+ if (typeof runtime === "number") {
514
+ result.code = runtime;
515
+ return;
516
+ }
517
+ result.code = await cmdKxmRunDrive(runtime, runId, options.simulated === true, {
458
518
  wait: options.wait === true,
459
519
  ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
460
520
  });
@@ -462,13 +522,17 @@ function createProgram(ctx: CliContext, result: { code: number }): Command {
462
522
  addGlobalOptions(runCmd.command("receipt").description("Print the newest drive receipt for a run"))
463
523
  .argument("<runId>", "Run id")
464
524
  .option("--all", "Print the capped receipt list for the run")
465
- .action(async function runReceiptAction(this: Command, runId: string, options: { all?: boolean }) {
466
- result.code = await cmdKxmRunReceipt(runtimeFrom(ctx, this), runId, { all: options.all === true });
525
+ .option("--lane <unit>", "Discover the project from this lane's worktree")
526
+ .action(async function runReceiptAction(this: Command, runId: string, options: { all?: boolean; lane?: string }) {
527
+ const runtime = applyLane(runtimeFrom(ctx, this), options.lane, "runs receipt");
528
+ result.code = typeof runtime === "number" ? runtime : await cmdKxmRunReceipt(runtime, runId, { all: options.all === true });
467
529
  });
468
530
  addGlobalOptions(runCmd.command("cancel").description("Durably request cancellation of a run"))
469
531
  .argument("<runId>", "Run id")
470
- .action(async function runCancelAction(this: Command, runId: string) {
471
- result.code = await cmdKxmRunCancel(runtimeFrom(ctx, this), runId);
532
+ .option("--lane <unit>", "Discover the project from this lane's worktree")
533
+ .action(async function runCancelAction(this: Command, runId: string, options: { lane?: string }) {
534
+ const runtime = applyLane(runtimeFrom(ctx, this), options.lane, "runs cancel");
535
+ result.code = typeof runtime === "number" ? runtime : await cmdKxmRunCancel(runtime, runId);
472
536
  });
473
537
  addGlobalOptions(runCmd.command("list").description("List recent runs for the current project"))
474
538
  .action(async function runListAction(this: Command) {
@@ -11,7 +11,7 @@ import { deliverInboxNotification } from "./inbox.ts";
11
11
  import type { HubEvent, MessageRecord } from "./protocol.ts";
12
12
  import { sessionTokenFixHint } from "./session-token-hint.ts";
13
13
 
14
- const VERSION = "0.7.118";
14
+ const VERSION = "0.7.120";
15
15
  const CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
16
16
  const inbox = new Map<string, MessageRecord>();
17
17
  const notifiedInbox = new Set<string>();
@@ -92,6 +92,12 @@ export function claudeBridgeRegistrationNotice(
92
92
  report: ClaudeBridgeRegistration | undefined,
93
93
  pi?: object,
94
94
  ): { message: string; type: "warning" } | undefined {
95
+ if (report?.warning) {
96
+ return {
97
+ message: boundedRegistrationWarning(report.warning),
98
+ type: "warning",
99
+ };
100
+ }
95
101
  const conflict = Boolean(report?.conflict) || (pi ? claudeBridgeStandaloneToolsPresent(pi) : false);
96
102
  if (!conflict) return undefined;
97
103
  return {
@@ -112,13 +118,23 @@ export function registerClaudeBridgeProvider(pi: ExtensionAPI): ClaudeBridgeRegi
112
118
  return { registered: false, conflict: false };
113
119
  }
114
120
 
115
- pi.registerProvider(PROVIDER_ID, {
116
- name: PROVIDER_NAME,
117
- baseUrl: PROVIDER_ID,
118
- api: CLAUDE_BRIDGE_API,
119
- models: registeredClaudeBridgeModels(),
120
- streamSimple: streamClaudeBridge,
121
- } as unknown as Parameters<ExtensionAPI["registerProvider"]>[1]);
122
-
123
- return { registered: true, conflict: false };
121
+ try {
122
+ pi.registerProvider(PROVIDER_ID, {
123
+ name: PROVIDER_NAME,
124
+ baseUrl: PROVIDER_ID,
125
+ apiKey: PROVIDER_ID,
126
+ api: CLAUDE_BRIDGE_API,
127
+ models: registeredClaudeBridgeModels(),
128
+ streamSimple: streamClaudeBridge,
129
+ } as unknown as Parameters<ExtensionAPI["registerProvider"]>[1]);
130
+
131
+ return { registered: true, conflict: false };
132
+ } catch (error) {
133
+ const reason = error instanceof Error ? error.message : String(error);
134
+ return {
135
+ registered: false,
136
+ conflict: false,
137
+ warning: `kxm: claude-bridge provider registration failed (${reason}). Configure apiKey or ensure provider auth is valid.`,
138
+ };
139
+ }
124
140
  }
package/schemas/README.md CHANGED
@@ -13,6 +13,7 @@ Runtime-local records, and JSON events/results for the planned KXM contract.
13
13
  | `workflow.schema.json` | `kxm.workflow.v1` |
14
14
  | `template-provenance.schema.json` | `kxm.template-provenance.v1` |
15
15
  | `local-repository-bindings.schema.json` | `kxm.local-repository-bindings.v1` |
16
+ | `lanes.schema.json` | `kxm.lanes.v1` |
16
17
  | `init-operation.schema.json` | `kxm.init-operation.v1` |
17
18
  | `permission-diff.schema.json` | `kxm.permission-diff.v1` |
18
19
  | `run-event.schema.json` | `kxm.run-event.v1` |
@@ -0,0 +1,61 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.kxm.dev/lanes.schema.json",
4
+ "title": "KXM worktree lanes",
5
+ "type": "object",
6
+ "required": ["schema", "lanes"],
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "schema": {
10
+ "const": "kxm.lanes.v1"
11
+ },
12
+ "lanes": {
13
+ "type": "object",
14
+ "propertyNames": {
15
+ "type": "string",
16
+ "minLength": 1,
17
+ "maxLength": 64,
18
+ "pattern": "^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$"
19
+ },
20
+ "patternProperties": {
21
+ "^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$": {
22
+ "$ref": "#/$defs/lane"
23
+ }
24
+ },
25
+ "additionalProperties": false
26
+ }
27
+ },
28
+ "$defs": {
29
+ "lane": {
30
+ "type": "object",
31
+ "required": ["path", "branch", "baseRef", "baseSha", "createdAt"],
32
+ "additionalProperties": false,
33
+ "properties": {
34
+ "path": {
35
+ "type": "string",
36
+ "minLength": 1
37
+ },
38
+ "branch": {
39
+ "type": "string",
40
+ "minLength": 1
41
+ },
42
+ "baseRef": {
43
+ "type": "string",
44
+ "minLength": 1
45
+ },
46
+ "baseSha": {
47
+ "type": "string",
48
+ "pattern": "^[a-f0-9]{40,64}$"
49
+ },
50
+ "createdAt": {
51
+ "type": "string",
52
+ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{1,9})?Z$"
53
+ },
54
+ "lastRunId": {
55
+ "type": "string",
56
+ "minLength": 1
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }