@kontextmind/kxm 0.7.98 → 0.7.99
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/guides/peer-messaging.md +2 -2
- package/docs/reference/cli-reference.md +6 -4
- package/docs/reference/http-api.md +1 -0
- package/docs/reference/tools.md +3 -3
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/claude-hook.js +4 -1
- package/plugins/kxm/dist/cli.js +14 -2
- package/plugins/kxm/dist/client.js +9 -0
- package/plugins/kxm/dist/core.js +4 -1
- package/plugins/kxm/dist/extension.js +13 -1
- package/plugins/kxm/dist/mcp-server.js +14 -2
- package/plugins/kxm/dist/server.js +12 -1
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm-peer/SKILL.md +5 -3
- package/plugins/kxm/src/cli.ts +3 -1
- package/plugins/kxm/src/client.ts +10 -0
- package/plugins/kxm/src/commands.ts +10 -1
- package/plugins/kxm/src/extension.ts +1 -0
- package/plugins/kxm/src/hub.ts +12 -0
- package/plugins/kxm/src/mcp-server.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -365,6 +365,13 @@ All notable user-facing changes are documented here. The project follows [Semant
|
|
|
365
365
|
- **Workflow prompts no longer point agents at `.kxm/config`**, a path KXM refuses.
|
|
366
366
|
- **`kxm gate signal` and `kxm workflow wait` inside a KXM project reach the hub for hub
|
|
367
367
|
runs.** They go to the local Runtime only for a run its store holds.
|
|
368
|
+
- **`kxm peer inbox` lists the requests waiting for a named CLI agent.** It returned
|
|
369
|
+
`{"messages":[]}` every time. The hub now serves `GET /v1/agents/:id/inbox`
|
|
370
|
+
(agent-authenticated, project-scoped): the caller's queued and delivered requests,
|
|
371
|
+
oldest first, acknowledging nothing. Run with a stable `KXM_AGENT_NAME` (for example
|
|
372
|
+
`codex`) to list requests peers queued for it while it was offline, then answer them
|
|
373
|
+
with `kxm peer reply`. The Pi extension's `kxm_inbox` tool now refuses instead of
|
|
374
|
+
returning an empty list, because Pi activates each inbound request as a turn itself.
|
|
368
375
|
|
|
369
376
|
- **The Claude plugin's SessionStart hook is one bundled, read-only, project-scoped
|
|
370
377
|
script.** The two shell hooks it replaces (`kxm session brief --status` and
|
|
@@ -210,7 +210,7 @@ CLI (run with the recipient's `KXM_AGENT_NAME`):
|
|
|
210
210
|
kxm peer reply msg_779f5e0f22e04ac1af6078589a874971 "The plan is sound. Add a test for a zero discount."
|
|
211
211
|
```
|
|
212
212
|
|
|
213
|
-
Only the recipient can reply, and only once. From the CLI, `kxm peer inbox`
|
|
213
|
+
Only the recipient can reply, and only once. From the CLI, `kxm peer inbox` lists the requests still waiting for the agent named by `KXM_AGENT_NAME`, including ones queued while it was offline, without acknowledging them. The default `cli-<pid>` name is a new agent on every call, so its list is empty.
|
|
214
214
|
|
|
215
215
|
## Choose a delivery mode
|
|
216
216
|
|
|
@@ -291,7 +291,7 @@ Errors about `workflowContext` are covered in [Peer provenance and quorum gates]
|
|
|
291
291
|
| A request stays `delivered` | The recipient's turn, tool, or provider call is still running, or a Claude Code session restarted after acknowledging it. | Wait, or cancel and send it again with a new idempotency key. |
|
|
292
292
|
| `kxm_fanout` returns `pending` | The local wait ended before a reply. | Use the returned message IDs with `kxm_get`, or repeat the exact call. |
|
|
293
293
|
| Claude Code never sees requests | Channel mode is off or blocked by policy. | Use `kxm_inbox` and `kxm_reply`. |
|
|
294
|
-
| `kxm peer inbox` is always empty |
|
|
294
|
+
| `kxm peer inbox` is always empty | `KXM_AGENT_NAME` is unset, so each call registers a new `cli-<pid>` agent that nobody has addressed. | Set `KXM_AGENT_NAME` to the name peers send to. |
|
|
295
295
|
|
|
296
296
|
For hub-level problems, see [Troubleshoot KXM](../operations/troubleshooting.md).
|
|
297
297
|
|
|
@@ -2397,18 +2397,21 @@ kxm peer fanout --targets reviewer critic --content "Independent review of PR 42
|
|
|
2397
2397
|
kxm peer inbox
|
|
2398
2398
|
```
|
|
2399
2399
|
|
|
2400
|
-
List inbound peer requests
|
|
2400
|
+
List the inbound peer requests addressed to this agent that still need a reply, oldest first, read from the hub (`GET /v1/agents/<agentId>/inbox`). Only a stable `KXM_AGENT_NAME` has an inbox: a registered name that is offline keeps its agent ID, so a later call as the same name lists what peers queued for it with `peer send --allow-offline`. The default `cli-<pid>` is a new agent on every call, and its list is always empty. Answer each request with `peer reply` under the same name.
|
|
2401
2401
|
|
|
2402
2402
|
| Option | Argument | Default | Description |
|
|
2403
2403
|
|---|---|---|---|
|
|
2404
2404
|
| `--payload` | `<json>` | none | JSON payload |
|
|
2405
2405
|
|
|
2406
|
+
- Reads only. Listing acknowledges nothing, so a request stays `queued` for push delivery. Output key: `messages`, each a full message record (`id`, `fromName`, `content`, `status` `queued` or `delivered`, `expiresAt`).
|
|
2407
|
+
- The call registers the name while it runs, so it exits 1 (`command_failed`, `agent name already active in project`) when another session holds that name online.
|
|
2408
|
+
|
|
2406
2409
|
```bash
|
|
2407
|
-
kxm peer inbox --json
|
|
2410
|
+
KXM_AGENT_NAME=codex kxm peer inbox --json
|
|
2408
2411
|
```
|
|
2409
2412
|
|
|
2410
2413
|
```text
|
|
2411
|
-
{"schema":"kxm.cli-result.v1","messages":[]}
|
|
2414
|
+
{"schema":"kxm.cli-result.v1","messages":[{"id":"msg_1f0c…","project":"kxm","from":"agt_9d2e…","fromName":"reviewer","to":"agt_4b7a…","toName":"codex","content":"Review the retry loop in src/sync.ts","delivery":"followUp","hops":0,"maxHops":5,"seq":1,"createdAt":"2026-09-23T21:40:00.000Z","expiresAt":"2026-09-24T21:40:00.000Z","status":"queued"}]}
|
|
2412
2415
|
```
|
|
2413
2416
|
|
|
2414
2417
|
### `kxm peer reply`
|
|
@@ -3590,7 +3593,6 @@ These are behaviors of the current build that differ from what the help text or
|
|
|
3590
3593
|
|
|
3591
3594
|
- `kxm routing benchmark` prints constant placeholder figures.
|
|
3592
3595
|
- `kxm task sync` does not contact GitHub or Jira.
|
|
3593
|
-
- `kxm peer inbox` always returns an empty list from the CLI.
|
|
3594
3596
|
- `kxm context` subcommands crash with a stack trace when the hub is unreachable, and they ignore the persisted hub credential.
|
|
3595
3597
|
- `kxm completion <shell>` generates a command list that includes a nonexistent `plan` command, omits `models`, `routes`, `explain`, and `ssh`, lists a nonexistent `goal get`, and omits `runs drive`, `runs receipt`, `runtime sync-retry`, and `improve report`.
|
|
3596
3598
|
- `kxm backup` JSON shows `manifestSha256` redacted.
|
|
@@ -49,6 +49,7 @@ Operations routes carry no message bodies. `kxm dash` and `kxm tenant status` us
|
|
|
49
49
|
| `POST` | `/v1/agents/register` | Project | Register or resume `name` in `project` with `purpose`, optional `model` and `host`. Returns the agent and a new `agentKey` (201 new, 200 resumed) |
|
|
50
50
|
| `GET` | `/v1/agents` | Agent | Agents in your project; `?includeOffline=true` adds registered offline agents |
|
|
51
51
|
| `POST` | `/v1/agents/<agentId>/heartbeat` | Agent | Renew your presence lease (clients send one every 10 seconds) |
|
|
52
|
+
| `GET` | `/v1/agents/<agentId>/inbox` | Agent | Your open inbound requests (`queued` or `delivered`), oldest first, as `{ "messages": [...] }`. Expires overdue requests first; acknowledges nothing and moves no delivery cursor |
|
|
52
53
|
| `DELETE` | `/v1/agents/<agentId>` | Agent | Mark yourself offline (204) |
|
|
53
54
|
|
|
54
55
|
Key errors: `duplicate_agent_name` (409, the name is online in the project), `invalid_agent_identity` (401). Presence (`online`, `stale`, `offline`) is computed from the hub's clock with a 30-second lease; agents never report their own presence.
|
package/docs/reference/tools.md
CHANGED
|
@@ -24,7 +24,7 @@ flowchart LR
|
|
|
24
24
|
| `kxm_fanout` | Peers | Yes | `kxm peer fanout` |
|
|
25
25
|
| `kxm_await` | Peers | No | `kxm peer await` |
|
|
26
26
|
| `kxm_cancel` | Peers | Yes | `kxm peer cancel` |
|
|
27
|
-
| `kxm_inbox` | Peers | No | `kxm peer inbox` (
|
|
27
|
+
| `kxm_inbox` | Peers | No | `kxm peer inbox` (lists a named CLI agent's open requests; refused in Pi) |
|
|
28
28
|
| `kxm_reply` | Peers | Yes | `kxm peer reply` |
|
|
29
29
|
| `kxm_workflow_list` | Workflows | No | None; `kxm workflow list` reads the local database instead |
|
|
30
30
|
| `kxm_workflow_get` | Workflows | No | None; `kxm workflow get` reads the local database instead |
|
|
@@ -162,8 +162,8 @@ Returns the message record with `status: "cancelled"`. Cancelling an already can
|
|
|
162
162
|
Lists inbound requests that still need a reply. It takes no parameters.
|
|
163
163
|
|
|
164
164
|
- **Claude Code:** returns `{ "messages": [...] }` from the session's inbox, which fills from the hub's event stream while the session is registered. Before returning, it re-reads each request and drops those already answered, cancelled or expired. This is pull mode; see [Pushed channel mode and pull mode](../../plugins/kxm/README.md#pushed-channel-mode-and-pull-mode).
|
|
165
|
-
- **Pi:**
|
|
166
|
-
- **CLI:**
|
|
165
|
+
- **Pi:** refuses. The extension turns each inbound request into a model turn itself, and that turn's final response is the reply, so listing would offer requests its own queue is about to activate.
|
|
166
|
+
- **CLI:** `kxm peer inbox` reads the agent's open requests from the hub (`GET /v1/agents/<agentId>/inbox`), acknowledging nothing. Run it with a stable `KXM_AGENT_NAME` to see requests queued for that name while it was offline; the default `cli-<pid>` is a new agent on every call, so its list is empty.
|
|
167
167
|
|
|
168
168
|
### `kxm_reply`
|
|
169
169
|
|
package/package.json
CHANGED
|
@@ -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.
|
|
5
|
+
"version": "0.7.99",
|
|
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",
|
|
@@ -9508,7 +9508,10 @@ var init_commands = __esm({
|
|
|
9508
9508
|
await reconcileInbox(client, context.inbox, context.notifiedInbox);
|
|
9509
9509
|
return { messages: [...context.inbox.values()] };
|
|
9510
9510
|
}
|
|
9511
|
-
return { messages:
|
|
9511
|
+
if (context?.hubInbox) return { messages: await client.listInbox() };
|
|
9512
|
+
throw new Error(
|
|
9513
|
+
"kxm_inbox is not available in this session: it activates each inbound request as a turn, and that turn's final response is the reply"
|
|
9514
|
+
);
|
|
9512
9515
|
}
|
|
9513
9516
|
},
|
|
9514
9517
|
{
|
package/plugins/kxm/dist/cli.js
CHANGED
|
@@ -19626,6 +19626,15 @@ var HubClient = class {
|
|
|
19626
19626
|
}
|
|
19627
19627
|
}));
|
|
19628
19628
|
}
|
|
19629
|
+
/** This agent's open inbound requests (queued or delivered), oldest first. A read: nothing
|
|
19630
|
+
* is acknowledged. */
|
|
19631
|
+
async listInbox() {
|
|
19632
|
+
if (!this.agent) throw new Error("hub client is not registered");
|
|
19633
|
+
const result = await this.request(
|
|
19634
|
+
`/v1/agents/${encodeURIComponent(this.agent.id)}/inbox`
|
|
19635
|
+
);
|
|
19636
|
+
return result.messages;
|
|
19637
|
+
}
|
|
19629
19638
|
async getMessage(messageId) {
|
|
19630
19639
|
const result = await this.request(`/v1/messages/${encodeURIComponent(messageId)}`);
|
|
19631
19640
|
return result.message;
|
|
@@ -20322,7 +20331,10 @@ var AGENT_COMMANDS = [
|
|
|
20322
20331
|
await reconcileInbox(client, context.inbox, context.notifiedInbox);
|
|
20323
20332
|
return { messages: [...context.inbox.values()] };
|
|
20324
20333
|
}
|
|
20325
|
-
return { messages:
|
|
20334
|
+
if (context?.hubInbox) return { messages: await client.listInbox() };
|
|
20335
|
+
throw new Error(
|
|
20336
|
+
"kxm_inbox is not available in this session: it activates each inbound request as a turn, and that turn's final response is the reply"
|
|
20337
|
+
);
|
|
20326
20338
|
}
|
|
20327
20339
|
},
|
|
20328
20340
|
{
|
|
@@ -49161,7 +49173,7 @@ async function dispatchAgentCliCommand(runtime, toolName, rawArgs) {
|
|
|
49161
49173
|
let client;
|
|
49162
49174
|
try {
|
|
49163
49175
|
client = await ensureCliClient(runtime);
|
|
49164
|
-
const output = await cmd.execute(client, args);
|
|
49176
|
+
const output = await cmd.execute(client, args, { hubInbox: true });
|
|
49165
49177
|
const payload = output && typeof output === "object" ? output : { result: output };
|
|
49166
49178
|
print(runtime.io, runtime.json, payload, JSON.stringify(output, null, 2));
|
|
49167
49179
|
return 0;
|
|
@@ -262,6 +262,15 @@ var HubClient = class {
|
|
|
262
262
|
}
|
|
263
263
|
}));
|
|
264
264
|
}
|
|
265
|
+
/** This agent's open inbound requests (queued or delivered), oldest first. A read: nothing
|
|
266
|
+
* is acknowledged. */
|
|
267
|
+
async listInbox() {
|
|
268
|
+
if (!this.agent) throw new Error("hub client is not registered");
|
|
269
|
+
const result = await this.request(
|
|
270
|
+
`/v1/agents/${encodeURIComponent(this.agent.id)}/inbox`
|
|
271
|
+
);
|
|
272
|
+
return result.messages;
|
|
273
|
+
}
|
|
265
274
|
async getMessage(messageId) {
|
|
266
275
|
const result = await this.request(`/v1/messages/${encodeURIComponent(messageId)}`);
|
|
267
276
|
return result.message;
|
package/plugins/kxm/dist/core.js
CHANGED
|
@@ -1284,7 +1284,10 @@ var AGENT_COMMANDS = [
|
|
|
1284
1284
|
await reconcileInbox(client, context.inbox, context.notifiedInbox);
|
|
1285
1285
|
return { messages: [...context.inbox.values()] };
|
|
1286
1286
|
}
|
|
1287
|
-
return { messages:
|
|
1287
|
+
if (context?.hubInbox) return { messages: await client.listInbox() };
|
|
1288
|
+
throw new Error(
|
|
1289
|
+
"kxm_inbox is not available in this session: it activates each inbound request as a turn, and that turn's final response is the reply"
|
|
1290
|
+
);
|
|
1288
1291
|
}
|
|
1289
1292
|
},
|
|
1290
1293
|
{
|
|
@@ -14842,6 +14842,15 @@ var HubClient = class {
|
|
|
14842
14842
|
}
|
|
14843
14843
|
}));
|
|
14844
14844
|
}
|
|
14845
|
+
/** This agent's open inbound requests (queued or delivered), oldest first. A read: nothing
|
|
14846
|
+
* is acknowledged. */
|
|
14847
|
+
async listInbox() {
|
|
14848
|
+
if (!this.agent) throw new Error("hub client is not registered");
|
|
14849
|
+
const result2 = await this.request(
|
|
14850
|
+
`/v1/agents/${encodeURIComponent(this.agent.id)}/inbox`
|
|
14851
|
+
);
|
|
14852
|
+
return result2.messages;
|
|
14853
|
+
}
|
|
14845
14854
|
async getMessage(messageId) {
|
|
14846
14855
|
const result2 = await this.request(`/v1/messages/${encodeURIComponent(messageId)}`);
|
|
14847
14856
|
return result2.message;
|
|
@@ -15408,7 +15417,10 @@ var AGENT_COMMANDS = [
|
|
|
15408
15417
|
await reconcileInbox(client, context.inbox, context.notifiedInbox);
|
|
15409
15418
|
return { messages: [...context.inbox.values()] };
|
|
15410
15419
|
}
|
|
15411
|
-
return { messages:
|
|
15420
|
+
if (context?.hubInbox) return { messages: await client.listInbox() };
|
|
15421
|
+
throw new Error(
|
|
15422
|
+
"kxm_inbox is not available in this session: it activates each inbound request as a turn, and that turn's final response is the reply"
|
|
15423
|
+
);
|
|
15412
15424
|
}
|
|
15413
15425
|
},
|
|
15414
15426
|
{
|
|
@@ -16042,6 +16042,15 @@ var HubClient = class {
|
|
|
16042
16042
|
}
|
|
16043
16043
|
}));
|
|
16044
16044
|
}
|
|
16045
|
+
/** This agent's open inbound requests (queued or delivered), oldest first. A read: nothing
|
|
16046
|
+
* is acknowledged. */
|
|
16047
|
+
async listInbox() {
|
|
16048
|
+
if (!this.agent) throw new Error("hub client is not registered");
|
|
16049
|
+
const result = await this.request(
|
|
16050
|
+
`/v1/agents/${encodeURIComponent(this.agent.id)}/inbox`
|
|
16051
|
+
);
|
|
16052
|
+
return result.messages;
|
|
16053
|
+
}
|
|
16045
16054
|
async getMessage(messageId) {
|
|
16046
16055
|
const result = await this.request(`/v1/messages/${encodeURIComponent(messageId)}`);
|
|
16047
16056
|
return result.message;
|
|
@@ -16700,7 +16709,10 @@ var AGENT_COMMANDS = [
|
|
|
16700
16709
|
await reconcileInbox(client, context.inbox, context.notifiedInbox);
|
|
16701
16710
|
return { messages: [...context.inbox.values()] };
|
|
16702
16711
|
}
|
|
16703
|
-
return { messages:
|
|
16712
|
+
if (context?.hubInbox) return { messages: await client.listInbox() };
|
|
16713
|
+
throw new Error(
|
|
16714
|
+
"kxm_inbox is not available in this session: it activates each inbound request as a turn, and that turn's final response is the reply"
|
|
16715
|
+
);
|
|
16704
16716
|
}
|
|
16705
16717
|
},
|
|
16706
16718
|
{
|
|
@@ -17296,7 +17308,7 @@ function sessionTokenFixHint(policy) {
|
|
|
17296
17308
|
}
|
|
17297
17309
|
|
|
17298
17310
|
// plugins/kxm/src/mcp-server.ts
|
|
17299
|
-
var VERSION = "0.7.
|
|
17311
|
+
var VERSION = "0.7.99";
|
|
17300
17312
|
var CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
|
|
17301
17313
|
var inbox = /* @__PURE__ */ new Map();
|
|
17302
17314
|
var notifiedInbox = /* @__PURE__ */ new Set();
|
|
@@ -17223,7 +17223,10 @@ var AGENT_COMMANDS = [
|
|
|
17223
17223
|
await reconcileInbox(client, context.inbox, context.notifiedInbox);
|
|
17224
17224
|
return { messages: [...context.inbox.values()] };
|
|
17225
17225
|
}
|
|
17226
|
-
return { messages:
|
|
17226
|
+
if (context?.hubInbox) return { messages: await client.listInbox() };
|
|
17227
|
+
throw new Error(
|
|
17228
|
+
"kxm_inbox is not available in this session: it activates each inbound request as a turn, and that turn's final response is the reply"
|
|
17229
|
+
);
|
|
17227
17230
|
}
|
|
17228
17231
|
},
|
|
17229
17232
|
{
|
|
@@ -22526,6 +22529,14 @@ data: ${JSON.stringify({ type: "ops", project, topic: "agents", at: nowIso() })}
|
|
|
22526
22529
|
json(response, 200, { agent: publicAgent(current, staleAfterMs) });
|
|
22527
22530
|
return;
|
|
22528
22531
|
}
|
|
22532
|
+
const inboxMatch = url.pathname.match(/^\/v1\/agents\/([^/]+)\/inbox$/);
|
|
22533
|
+
if (method === "GET" && inboxMatch) {
|
|
22534
|
+
const current = requireAgent(request, decodeURIComponent(inboxMatch[1]));
|
|
22535
|
+
requireProjectAuth(request, current.project);
|
|
22536
|
+
expireMessages();
|
|
22537
|
+
json(response, 200, { messages: store.getPendingMessages(current.id) });
|
|
22538
|
+
return;
|
|
22539
|
+
}
|
|
22529
22540
|
const agentMatch = url.pathname.match(/^\/v1\/agents\/([^/]+)$/);
|
|
22530
22541
|
if (method === "DELETE" && agentMatch) {
|
|
22531
22542
|
const current = requireAgent(request, decodeURIComponent(agentMatch[1]));
|
package/plugins/kxm/package.json
CHANGED
|
@@ -42,9 +42,11 @@ reading of its heartbeat lease: `online` holds the lease, `stale` has passed
|
|
|
42
42
|
peer the hub has retired. Offline peers are listed only with
|
|
43
43
|
`--include-offline`. The host label is a reading aid, never a permission.
|
|
44
44
|
|
|
45
|
-
`kxm peer inbox`
|
|
46
|
-
|
|
47
|
-
that
|
|
45
|
+
`kxm peer inbox` lists the requests addressed to the CLI agent's own name, so
|
|
46
|
+
run it with a stable `KXM_AGENT_NAME` and answer each with `kxm peer reply`
|
|
47
|
+
under that name. The default `cli-<pid>` is a new agent on every call, and its
|
|
48
|
+
inbox is always empty. In Claude Code, `kxm_inbox` lists the session's inbox; a
|
|
49
|
+
Pi session receives each request as a turn instead, and its `kxm_inbox` refuses.
|
|
48
50
|
|
|
49
51
|
## Examples
|
|
50
52
|
|
package/plugins/kxm/src/cli.ts
CHANGED
|
@@ -349,7 +349,9 @@ async function dispatchAgentCliCommand(
|
|
|
349
349
|
let client: HubClient | undefined;
|
|
350
350
|
try {
|
|
351
351
|
client = await ensureCliClient(runtime);
|
|
352
|
-
|
|
352
|
+
// A one-shot call keeps no inbox, so `peer inbox` reads this agent's open requests from
|
|
353
|
+
// the hub; with a stable KXM_AGENT_NAME those include ones queued while it was offline.
|
|
354
|
+
const output = await cmd.execute(client, args, { hubInbox: true });
|
|
353
355
|
const payload = (output && typeof output === "object" ? output : { result: output }) as object;
|
|
354
356
|
print(runtime.io, runtime.json, payload, JSON.stringify(output, null, 2));
|
|
355
357
|
return 0;
|
|
@@ -325,6 +325,16 @@ export class HubClient {
|
|
|
325
325
|
}));
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
+
/** This agent's open inbound requests (queued or delivered), oldest first. A read: nothing
|
|
329
|
+
* is acknowledged. */
|
|
330
|
+
async listInbox(): Promise<MessageRecord[]> {
|
|
331
|
+
if (!this.agent) throw new Error("hub client is not registered");
|
|
332
|
+
const result = await this.request<{ messages: MessageRecord[] }>(
|
|
333
|
+
`/v1/agents/${encodeURIComponent(this.agent.id)}/inbox`,
|
|
334
|
+
);
|
|
335
|
+
return result.messages;
|
|
336
|
+
}
|
|
337
|
+
|
|
328
338
|
async getMessage(messageId: string): Promise<MessageRecord> {
|
|
329
339
|
const result = await this.request<{ message: MessageRecord }>(`/v1/messages/${encodeURIComponent(messageId)}`);
|
|
330
340
|
return result.message;
|
|
@@ -72,8 +72,12 @@ export interface SessionTokenPayload {
|
|
|
72
72
|
|
|
73
73
|
export interface CommandExecutionContext {
|
|
74
74
|
signal?: AbortSignal | undefined;
|
|
75
|
+
/** An event-fed inbox the caller keeps (the MCP server); `kxm_inbox` reconciles it. */
|
|
75
76
|
inbox?: Map<string, MessageRecord> | undefined;
|
|
76
77
|
notifiedInbox?: Set<string> | undefined;
|
|
78
|
+
/** The caller keeps no inbox and reads its open requests from the hub (a one-shot CLI
|
|
79
|
+
* call). A caller with neither this nor `inbox` activates inbound requests itself (Pi). */
|
|
80
|
+
hubInbox?: boolean | undefined;
|
|
77
81
|
/** Inbound requests this session is handling: the Pi extension's active request, the MCP
|
|
78
82
|
* server's open inbox. A request sent meanwhile is one more hop along their chain. */
|
|
79
83
|
handling?: readonly Pick<MessageRecord, "hops" | "maxHops">[] | undefined;
|
|
@@ -452,7 +456,12 @@ export const AGENT_COMMANDS: readonly AgentCommand[] = [
|
|
|
452
456
|
await reconcileInbox(client, context.inbox, context.notifiedInbox);
|
|
453
457
|
return { messages: [...context.inbox.values()] };
|
|
454
458
|
}
|
|
455
|
-
return { messages:
|
|
459
|
+
if (context?.hubInbox) return { messages: await client.listInbox() };
|
|
460
|
+
// Listing would offer requests this session's own activation queue is about to hand
|
|
461
|
+
// it as turns, and an empty list would hide them, so it refuses.
|
|
462
|
+
throw new Error(
|
|
463
|
+
"kxm_inbox is not available in this session: it activates each inbound request as a turn, and that turn's final response is the reply",
|
|
464
|
+
);
|
|
456
465
|
},
|
|
457
466
|
},
|
|
458
467
|
{
|
|
@@ -898,6 +898,7 @@ export default function piMeshExtension(pi: ExtensionAPI): void | Promise<void>
|
|
|
898
898
|
throw new Error(`tool_policy_denied: ${policy.detail ?? policy.error}`);
|
|
899
899
|
}
|
|
900
900
|
// A request sent while handling the active inbound request continues its hop chain.
|
|
901
|
+
// No inbox and no hubInbox: `pending` owns activation, so kxm_inbox refuses here.
|
|
901
902
|
const handling = activeInbound ? [activeInbound] : [];
|
|
902
903
|
return result(
|
|
903
904
|
await workflowCall(() =>
|
package/plugins/kxm/src/hub.ts
CHANGED
|
@@ -2520,6 +2520,18 @@ export function createMeshHub(options: MeshHubOptions = {}): MeshHub {
|
|
|
2520
2520
|
return;
|
|
2521
2521
|
}
|
|
2522
2522
|
|
|
2523
|
+
// An agent's open inbound requests, queued or delivered, in delivery order. A read
|
|
2524
|
+
// for a caller that holds no event-fed inbox of its own (a one-shot CLI call): it
|
|
2525
|
+
// acknowledges nothing and moves no cursor, so push delivery is unchanged.
|
|
2526
|
+
const inboxMatch = url.pathname.match(/^\/v1\/agents\/([^/]+)\/inbox$/);
|
|
2527
|
+
if (method === "GET" && inboxMatch) {
|
|
2528
|
+
const current = requireAgent(request, decodeURIComponent(inboxMatch[1]!));
|
|
2529
|
+
requireProjectAuth(request, current.project);
|
|
2530
|
+
expireMessages();
|
|
2531
|
+
json(response, 200, { messages: store.getPendingMessages(current.id) });
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2523
2535
|
const agentMatch = url.pathname.match(/^\/v1\/agents\/([^/]+)$/);
|
|
2524
2536
|
if (method === "DELETE" && agentMatch) {
|
|
2525
2537
|
const current = requireAgent(request, decodeURIComponent(agentMatch[1]!));
|
|
@@ -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.
|
|
14
|
+
const VERSION = "0.7.99";
|
|
15
15
|
const CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
|
|
16
16
|
const inbox = new Map<string, MessageRecord>();
|
|
17
17
|
const notifiedInbox = new Set<string>();
|