@kontextmind/kxm 0.7.69 → 0.7.71
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 +7 -0
- package/docs/test-matrix.md +1 -0
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +50 -20
- package/plugins/kxm/dist/client.js +16 -3
- package/plugins/kxm/dist/core.js +31 -6
- package/plugins/kxm/dist/extension.js +33 -9
- package/plugins/kxm/dist/mcp-server.js +34 -10
- package/plugins/kxm/dist/server.js +65 -22
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm/references/protocol.md +6 -0
- package/plugins/kxm/skills/kxm-peer/SKILL.md +5 -2
- package/plugins/kxm/src/cli.ts +3 -1
- package/plugins/kxm/src/client.ts +24 -2
- package/plugins/kxm/src/commands.ts +16 -4
- package/plugins/kxm/src/hub.ts +55 -17
- package/plugins/kxm/src/local-snapshot.ts +10 -2
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/protocol.ts +42 -1
- package/plugins/kxm/src/store.ts +5 -2
- package/plugins/kxm/src/tui.ts +20 -4
- package/scripts/emit-codex-artifacts.mjs +1 -1
|
@@ -12,21 +12,62 @@ export const DEFAULT_RATE_LIMIT_MAX = 600;
|
|
|
12
12
|
export const DEFAULT_RATE_LIMIT_WINDOW_MS = 60_000;
|
|
13
13
|
export const MAX_BODY_BYTES = 256 * 1024;
|
|
14
14
|
export const MAX_CONTENT_CHARS = 32_000;
|
|
15
|
+
export const MAX_AGENT_HOST_CHARS = 64;
|
|
15
16
|
|
|
16
17
|
export type DeliveryMode = "steer" | "followUp" | "nextTurn";
|
|
17
18
|
export type MessageStatus = "queued" | "delivered" | "replied" | "cancelled" | "expired" | "error";
|
|
19
|
+
export type AgentPresence = "online" | "stale" | "offline";
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
/** The durable half of an agent: exactly the fields the hub persists in the
|
|
22
|
+
* agents record JSON. Presence is never stored, so a restored database can
|
|
23
|
+
* never claim an agent was alive. */
|
|
24
|
+
export interface AgentIdentity {
|
|
20
25
|
id: string;
|
|
21
26
|
name: string;
|
|
22
27
|
purpose: string;
|
|
23
28
|
project: string;
|
|
24
29
|
model?: string;
|
|
30
|
+
/** Client-declared label for the box this agent runs on. It exists so a
|
|
31
|
+
* reader can tell two boxes apart; the hub never reads it for
|
|
32
|
+
* authorization, project scope, or target resolution. */
|
|
33
|
+
host?: string;
|
|
25
34
|
connectedAt: string;
|
|
26
35
|
lastSeenAt: string;
|
|
27
36
|
online: boolean;
|
|
28
37
|
}
|
|
29
38
|
|
|
39
|
+
/** The public projection of an agent: durable identity plus presence derived
|
|
40
|
+
* from the hub clock at read time. */
|
|
41
|
+
export interface AgentRecord extends AgentIdentity {
|
|
42
|
+
/** `lastSeenAt + staleAfterMs`: the heartbeat lease the stale sweep enforces.
|
|
43
|
+
* Absent when projected from the local store without the hub's clock or lease. */
|
|
44
|
+
leaseExpiresAt?: string;
|
|
45
|
+
/** Hub-clocked presence. Absent when the reader has no hub projection. */
|
|
46
|
+
presence?: AgentPresence;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Presence is hub-clocked and never client-reported. An agent holds its lease
|
|
50
|
+
* until `lastSeenAt + staleAfterMs`; between lease expiry and the sweep that
|
|
51
|
+
* retires it, it reads `stale`; once retired it reads `offline`. An
|
|
52
|
+
* unparseable `lastSeenAt` leaves the lease at the epoch, which reads as an
|
|
53
|
+
* expired lease rather than as a live agent. */
|
|
54
|
+
export function agentPresenceView(
|
|
55
|
+
agent: AgentIdentity,
|
|
56
|
+
staleAfterMs: number = DEFAULT_STALE_AFTER_MS,
|
|
57
|
+
now: number = Date.now(),
|
|
58
|
+
): { leaseExpiresAt: string; presence: AgentPresence } {
|
|
59
|
+
const lastSeenMs = Date.parse(agent.lastSeenAt);
|
|
60
|
+
const leaseExpiresAtMs = (Number.isFinite(lastSeenMs) ? lastSeenMs : 0) + staleAfterMs;
|
|
61
|
+
const leaseExpiresAt = new Date(leaseExpiresAtMs).toISOString();
|
|
62
|
+
if (!agent.online) return { leaseExpiresAt, presence: "offline" };
|
|
63
|
+
return { leaseExpiresAt, presence: now < leaseExpiresAtMs ? "online" : "stale" };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Project a durable identity onto the wire shape readers consume. */
|
|
67
|
+
export function toAgentRecord(agent: AgentIdentity, staleAfterMs?: number, now?: number): AgentRecord {
|
|
68
|
+
return { ...agent, ...agentPresenceView(agent, staleAfterMs, now) };
|
|
69
|
+
}
|
|
70
|
+
|
|
30
71
|
export interface MessageReply {
|
|
31
72
|
content: string;
|
|
32
73
|
createdAt: string;
|
package/plugins/kxm/src/store.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import type { DatabaseSync } from "./sqlite.ts";
|
|
3
|
-
import type {
|
|
3
|
+
import type { AgentIdentity, MessageRecord } from "./protocol.ts";
|
|
4
4
|
import type { ContextItem } from "./context.ts";
|
|
5
5
|
import type { WorkflowJournalEntry, WorkflowRun } from "./workflow.ts";
|
|
6
6
|
import {
|
|
@@ -78,7 +78,10 @@ export const HUB_STORE_SCHEMA_SPEC: DatabaseSchemaSpec = Object.freeze({
|
|
|
78
78
|
tables: HUB_STORE_TABLES,
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
/** The stored shape stays the durable identity plus the agent key: presence
|
|
82
|
+
* and the lease are derived on read, so the agents record JSON grows only by
|
|
83
|
+
* the additive `host` label. */
|
|
84
|
+
export interface StoredAgent extends AgentIdentity {
|
|
82
85
|
key: string;
|
|
83
86
|
}
|
|
84
87
|
|
package/plugins/kxm/src/tui.ts
CHANGED
|
@@ -310,6 +310,19 @@ function visibleAgents(snapshot: MeshTuiSnapshot): AgentRecord[] {
|
|
|
310
310
|
return snapshot.agents.filter((agent) => agent.model !== "tui");
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
+
/** Presence comes from the hub clock, so the dash colours it rather than
|
|
314
|
+
* recomputing it: green holds a lease, amber has lost it but has not been
|
|
315
|
+
* swept, dim is a registered peer that is gone. */
|
|
316
|
+
function presenceCell(agent: AgentRecord, theme: MeshTuiTheme, width = 7): string {
|
|
317
|
+
// Local-store projections carry no hub-clocked presence; the dash shows "n/a"
|
|
318
|
+
// rather than inventing a lease from the reader's clock.
|
|
319
|
+
const presence = agent.presence ?? "n/a";
|
|
320
|
+
const cell = pad(presence, width);
|
|
321
|
+
if (presence === "online") return theme.success(cell);
|
|
322
|
+
if (presence === "stale") return theme.warning(cell);
|
|
323
|
+
return theme.dim(cell);
|
|
324
|
+
}
|
|
325
|
+
|
|
313
326
|
function panelMetric(snapshot: MeshTuiSnapshot, panel: MeshTuiPanel): string {
|
|
314
327
|
if (panel === "agents") {
|
|
315
328
|
const agents = visibleAgents(snapshot);
|
|
@@ -441,7 +454,7 @@ function listLines(snapshot: MeshTuiSnapshot, view: MeshTuiView, theme: MeshTuiT
|
|
|
441
454
|
const mark = (index: number) => cursor(theme, index === view.selected, "list", view.pane);
|
|
442
455
|
if (view.tab === "agents") {
|
|
443
456
|
return visibleAgents(snapshot).map((agent, index) => (
|
|
444
|
-
`${mark(index)}${pad(agent.name, 14)} ${agent
|
|
457
|
+
`${mark(index)}${pad(agent.name, 14)} ${presenceCell(agent, theme)} ${pad(agent.host ?? "-", 12)} ${pad(agent.model ?? "-", 14)} ${pad(age(agent.lastSeenAt, now), 4)}`
|
|
445
458
|
));
|
|
446
459
|
}
|
|
447
460
|
if (view.tab === "tasks" || view.tab === "workflows") {
|
|
@@ -489,9 +502,10 @@ function detailLines(snapshot: MeshTuiSnapshot, view: MeshTuiView, theme: MeshTu
|
|
|
489
502
|
const related = snapshot.openMessages.filter((message) => message.fromName === agent.name || message.toName === agent.name);
|
|
490
503
|
return [
|
|
491
504
|
theme.accent(agent.name),
|
|
492
|
-
`${agent
|
|
505
|
+
`${presenceCell(agent, theme, (agent.presence ?? "n/a").length)} ${agent.model ?? "-"}`,
|
|
506
|
+
`host ${agent.host ?? "-"}`,
|
|
493
507
|
agent.purpose,
|
|
494
|
-
`seen ${age(agent.lastSeenAt, now)} ago`,
|
|
508
|
+
`seen ${age(agent.lastSeenAt, now)} ago · lease ${agent.leaseExpiresAt?.slice(11, 19) ?? "n/a"} UTC`,
|
|
495
509
|
"",
|
|
496
510
|
theme.dim("Open work"),
|
|
497
511
|
...(related.length === 0 ? [theme.dim("none")] : related.map((message) => `${message.status} ${message.fromName} → ${message.toName} ${age(message.createdAt, now)}`)),
|
|
@@ -875,7 +889,9 @@ export async function runMeshTui(input: {
|
|
|
875
889
|
}
|
|
876
890
|
if (!useOpsStream && identity) {
|
|
877
891
|
try {
|
|
878
|
-
|
|
892
|
+
// Offline members too: the ops snapshot lists the whole project, and
|
|
893
|
+
// the legacy path is a fallback for the same screen, not a narrower one.
|
|
894
|
+
const listed = await input.fetchImpl(`${base}/v1/agents?includeOffline=true`, { headers: headers(identity) });
|
|
879
895
|
if (listed.ok) {
|
|
880
896
|
const body = await readJson<{ agents: AgentRecord[] }>(listed);
|
|
881
897
|
const byId = new Map(local.agents.map((agent) => [agent.id, agent]));
|
|
@@ -30,7 +30,7 @@ The \`kxm\` CLI is the unified agent surface for peer collaboration and workflow
|
|
|
30
30
|
| Command | Purpose | Key options |
|
|
31
31
|
|---|---|---|
|
|
32
32
|
| \`kxm peer list\` | List online peer agents and purposes | \`--json\` |
|
|
33
|
-
| \`kxm peer send [target] [content]\` | Send a focused request to a peer | \`--target\`, \`--content\`, \`--delivery <steer\\|followUp\\|nextTurn>\`, \`--correlation-id\`, \`--idempotency-key\`, \`--workflow-context <json>\`, \`--ttl-ms\` |
|
|
33
|
+
| \`kxm peer send [target] [content]\` | Send a focused request to a peer | \`--target\`, \`--content\`, \`--delivery <steer\\|followUp\\|nextTurn>\`, \`--correlation-id\`, \`--idempotency-key\`, \`--workflow-context <json>\`, \`--ttl-ms\`, \`--allow-offline\` |
|
|
34
34
|
| \`kxm peer get [messageId]\` | Check request status without blocking | \`--message-id\` |
|
|
35
35
|
| \`kxm peer await [messageId]\` | Wait for reply (capped at 60 seconds) | \`--message-id\`, \`--timeout-ms\` (max 60000) |
|
|
36
36
|
| \`kxm peer cancel [messageId]\` | Cancel a queued or delivered request | \`--message-id\` |
|