borgmcp 5.4.0 → 5.5.0
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/README.md +12 -0
- package/dist/assimilate-cmd.d.ts +8 -1
- package/dist/assimilate-cmd.d.ts.map +1 -1
- package/dist/assimilate-cmd.js +54 -21
- package/dist/assimilate-cmd.js.map +1 -1
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +22 -0
- package/dist/claude.js.map +1 -1
- package/dist/cli-help.d.ts +1 -0
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +42 -0
- package/dist/cli-help.js.map +1 -1
- package/dist/docs-sections.d.ts.map +1 -1
- package/dist/docs-sections.js +8 -0
- package/dist/docs-sections.js.map +1 -1
- package/dist/local-server-cursor.d.ts +1 -1
- package/dist/local-server-cursor.d.ts.map +1 -1
- package/dist/local-server-cursor.js +14 -4
- package/dist/local-server-cursor.js.map +1 -1
- package/dist/remote-client.d.ts +14 -0
- package/dist/remote-client.d.ts.map +1 -1
- package/dist/remote-client.js +30 -14
- package/dist/remote-client.js.map +1 -1
- package/dist/representative-cmd.d.ts +87 -0
- package/dist/representative-cmd.d.ts.map +1 -0
- package/dist/representative-cmd.js +286 -0
- package/dist/representative-cmd.js.map +1 -0
- package/dist/representative-core.d.ts +197 -0
- package/dist/representative-core.d.ts.map +1 -0
- package/dist/representative-core.js +493 -0
- package/dist/representative-core.js.map +1 -0
- package/dist/representative-mcp.d.ts +30 -0
- package/dist/representative-mcp.d.ts.map +1 -0
- package/dist/representative-mcp.js +182 -0
- package/dist/representative-mcp.js.map +1 -0
- package/dist/representative-owner.d.ts +10 -0
- package/dist/representative-owner.d.ts.map +1 -0
- package/dist/representative-owner.js +107 -0
- package/dist/representative-owner.js.map +1 -0
- package/dist/representative-store.d.ts +61 -0
- package/dist/representative-store.d.ts.map +1 -0
- package/dist/representative-store.js +158 -0
- package/dist/representative-store.js.map +1 -0
- package/dist/seat-store.d.ts +13 -0
- package/dist/seat-store.d.ts.map +1 -1
- package/dist/seat-store.js +55 -10
- package/dist/seat-store.js.map +1 -1
- package/dist/stream-owner.d.ts +10 -0
- package/dist/stream-owner.d.ts.map +1 -1
- package/dist/stream-owner.js +98 -19
- package/dist/stream-owner.js.map +1 -1
- package/dist/unknown-subcommand.d.ts +1 -1
- package/dist/unknown-subcommand.d.ts.map +1 -1
- package/dist/unknown-subcommand.js +1 -0
- package/dist/unknown-subcommand.js.map +1 -1
- package/docs/HUMAN_REPRESENTATIVE.md +269 -0
- package/docs/RELEASING.md +2 -2
- package/package.json +2 -2
- package/src/assimilate-cmd.ts +73 -22
- package/src/claude.ts +22 -0
- package/src/cli-help.ts +45 -0
- package/src/docs-sections.ts +8 -0
- package/src/local-server-cursor.ts +11 -3
- package/src/remote-client.ts +45 -12
- package/src/representative-cmd.ts +363 -0
- package/src/representative-core.ts +699 -0
- package/src/representative-mcp.ts +209 -0
- package/src/representative-owner.ts +105 -0
- package/src/representative-store.ts +208 -0
- package/src/seat-store.ts +61 -10
- package/src/stream-owner.ts +96 -19
- package/src/unknown-subcommand.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unknown-subcommand.js","sourceRoot":"","sources":["../src/unknown-subcommand.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,OAAO;IACP,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,OAAO;IACP,YAAY;IACZ,YAAY;IACZ,wBAAwB;IACxB,oBAAoB;IACpB,OAAO;IACP,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,YAAY;IACZ,QAAQ;CACA,CAAC;AAEX;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAyB;IACzD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,CAAC,cAAc;IACpD,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,8BAA8B;IACtE,IAAK,iBAAuC,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1E,OAAO,KAAK,CAAC,CAAC,wCAAwC;AACxD,CAAC"}
|
|
1
|
+
{"version":3,"file":"unknown-subcommand.js","sourceRoot":"","sources":["../src/unknown-subcommand.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,OAAO;IACP,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,OAAO;IACP,YAAY;IACZ,YAAY;IACZ,wBAAwB;IACxB,oBAAoB;IACpB,OAAO;IACP,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,YAAY;IACZ,gBAAgB;IAChB,QAAQ;CACA,CAAC;AAEX;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAyB;IACzD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,CAAC,cAAc;IACpD,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,8BAA8B;IACtE,IAAK,iBAAuC,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1E,OAAO,KAAK,CAAC,CAAC,wCAAwC;AACxD,CAAC"}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# Human Representative
|
|
2
|
+
|
|
3
|
+
`borg representative` lets a standard MCP host — for example Hermes — speak
|
|
4
|
+
**for the human** to one existing Coordinator drone and read its replies. It is
|
|
5
|
+
a client-side feature: it uses the ordinary cube log, addressing,
|
|
6
|
+
acknowledgement and saved-connection mechanisms. The Borg server has no special
|
|
7
|
+
"representative" concept and enforces nothing extra for it.
|
|
8
|
+
|
|
9
|
+
## Vocabulary
|
|
10
|
+
|
|
11
|
+
- **Cube**: one repository's shared coordination space on your Borg server.
|
|
12
|
+
- **Drone**: one connected agent session in a cube. Its **role** defines how it
|
|
13
|
+
works.
|
|
14
|
+
- **Human seat**: the one role in a cube that speaks with the human's
|
|
15
|
+
authority.
|
|
16
|
+
- **Coordinator**: the drone holding the human seat. It owns the coordination
|
|
17
|
+
playbook and dispatches the other drones.
|
|
18
|
+
- **Human representative**: a *separate* automated drone under its own
|
|
19
|
+
non-human-seat role. It relays the human's requests, questions and decisions
|
|
20
|
+
to that one Coordinator and reads the Coordinator's replies. It is not the
|
|
21
|
+
human, never takes or replaces the human seat, carries none of the
|
|
22
|
+
Coordinator's playbook, and cannot address other drones or broadcast.
|
|
23
|
+
- **Binding**: the saved selection of one server, one cube, one representative
|
|
24
|
+
drone and one Coordinator drone for one worktree.
|
|
25
|
+
|
|
26
|
+
## Lifecycle
|
|
27
|
+
|
|
28
|
+
### 1. Create the representative role once
|
|
29
|
+
|
|
30
|
+
The representative needs an existing role that is **not** the human seat and
|
|
31
|
+
not a coordinating (queen-class) role. The default name is
|
|
32
|
+
`hermes-representative`. Create it with the cube's normal role management (for
|
|
33
|
+
example ask the Coordinator to run `borg_create-role`). Keep its text short,
|
|
34
|
+
for example: *"Relays the human's requests, questions and decisions to the
|
|
35
|
+
Coordinator and reports the Coordinator's replies back. Does not plan, dispatch
|
|
36
|
+
or review work."* Do not copy the Coordinator role into it.
|
|
37
|
+
|
|
38
|
+
### 2. Prepare the connection
|
|
39
|
+
|
|
40
|
+
From the repository whose cube you want, name the exact Coordinator drone
|
|
41
|
+
(`borg drones` lists labels):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
borg representative prepare --host <host:port> --coordinator <coordinator-drone-label> --worktree hermes
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Replace `<host:port>` with your existing Borg server's address. The bare
|
|
48
|
+
`host:port` form is accepted (for example `127.0.0.1:7091`) and defaults to HTTPS.
|
|
49
|
+
Always pass `--host` for scripted or non-interactive runs. In an interactive
|
|
50
|
+
terminal, omitting it makes `prepare` attempt server detection and ask you to
|
|
51
|
+
confirm the detected server or enter its address.
|
|
52
|
+
|
|
53
|
+
This creates the representative's own drone in a new linked worktree through
|
|
54
|
+
the same path as `borg assimilate --worktree`, but **launches no agent CLI** and
|
|
55
|
+
does not touch any other drone. It then verifies against the live cube that:
|
|
56
|
+
|
|
57
|
+
- the new drone's role is the requested one, and is neither the human seat nor
|
|
58
|
+
a coordinating role;
|
|
59
|
+
- exactly one active drone has the given label, it is not the representative
|
|
60
|
+
itself, and it holds the human seat.
|
|
61
|
+
|
|
62
|
+
A missing, evicted, duplicated or non-human-seat Coordinator fails with a named
|
|
63
|
+
error. Another drone is never chosen instead. On success the binding is saved
|
|
64
|
+
in Borg's private configuration directory (`representative.json`, mode 0600).
|
|
65
|
+
That file holds identifiers and a request ledger only — no credential and no
|
|
66
|
+
message text. The drone's credential stays in Borg's existing private
|
|
67
|
+
connection store.
|
|
68
|
+
|
|
69
|
+
To resume later, run `borg representative prepare --coordinator <coordinator-drone-label> --role <your-representative-role>` from inside the representative worktree (without `--worktree`), substituting
|
|
70
|
+
your saved labels. Recovery errors for a bound connection print that complete
|
|
71
|
+
command with its actual labels and worktree. Changing the cube or Coordinator
|
|
72
|
+
is refused unless you
|
|
73
|
+
pass `--rebind`; a rebind also discards the old request ledger. A running
|
|
74
|
+
`borg representative mcp` process never picks up a rebind: its calls fail
|
|
75
|
+
closed until the MCP host restarts it.
|
|
76
|
+
|
|
77
|
+
`prepare` reuses Borg's connection and worktree preparation, including its
|
|
78
|
+
private-state initialization and saved-connection checks. It does not require
|
|
79
|
+
Claude Code, Codex or OpenCode, write their configuration or preferences,
|
|
80
|
+
provision their launch access, report an agent identity, or install a session
|
|
81
|
+
hook. Nothing is started. An explicit `--host` that conflicts with the saved
|
|
82
|
+
connection is refused before preparation.
|
|
83
|
+
|
|
84
|
+
A confirmed evicted drone uses the ordinary connection recovery path. Revoked,
|
|
85
|
+
superseded, unreachable and changed-trust connections remain distinct refusals;
|
|
86
|
+
they are not treated as eviction. After recovery changes the drone identity,
|
|
87
|
+
confirm the new selection with the printed `--rebind` command.
|
|
88
|
+
|
|
89
|
+
First-time role creation and actual linked-worktree provisioning have not yet
|
|
90
|
+
been exercised live for this interface. The current evidence uses controlled
|
|
91
|
+
backends; this is not a claim of live onboarding verification.
|
|
92
|
+
|
|
93
|
+
### 3. Configure the MCP host
|
|
94
|
+
|
|
95
|
+
The Borg server speaks pinned-TLS HTTPS, not MCP, so the host starts a local
|
|
96
|
+
stdio MCP process. A generic configuration (shown in Hermes-style YAML; adapt
|
|
97
|
+
the keys to your host):
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
mcp_servers:
|
|
101
|
+
borg-representative:
|
|
102
|
+
command: borg
|
|
103
|
+
args: ["representative", "mcp", "--worktree", "/absolute/path/to/representative/worktree"]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
No token, key or URL belongs in this configuration. If the worktree is not
|
|
107
|
+
prepared, or its saved connection no longer matches the binding, the process
|
|
108
|
+
exits with an error on stderr and writes nothing to stdout.
|
|
109
|
+
|
|
110
|
+
Check a connection at any time with
|
|
111
|
+
`borg representative status --worktree <path>`.
|
|
112
|
+
|
|
113
|
+
### 4. Tools
|
|
114
|
+
|
|
115
|
+
| Tool | Purpose |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
| `borg_representative-status` | Bound cube, representative, Coordinator; live re-check; unresolved sends; limits. |
|
|
118
|
+
| `borg_representative-send` | Relay one `request`, `question` or `decision` to the bound Coordinator. |
|
|
119
|
+
| `borg_representative-read` | Unread replies from the bound Coordinator addressed to the representative. Drains everything it fetches. |
|
|
120
|
+
| `borg_representative-ack` | Signal to the Coordinator that one direct reply was received. Nothing more. |
|
|
121
|
+
|
|
122
|
+
There is no tool for logging to other drones, broadcasting, role or drone
|
|
123
|
+
management, grants, eviction, release, regeneration or server lifecycle, and no
|
|
124
|
+
generic dispatcher. `send` rejects any recipient field.
|
|
125
|
+
|
|
126
|
+
New network operations re-verify the live cube first: the connection must still be the
|
|
127
|
+
bound drone in the bound cube, still under a permitted role, and the bound
|
|
128
|
+
Coordinator must still be an active human-seat drone. Otherwise the call fails
|
|
129
|
+
and nothing is sent. A cached sent retry returns its historical receipt without
|
|
130
|
+
a live re-check; use `borg_representative-status` to check the current connection.
|
|
131
|
+
|
|
132
|
+
## Attribution and authority
|
|
133
|
+
|
|
134
|
+
Each relayed message starts with a fixed header:
|
|
135
|
+
|
|
136
|
+
```text
|
|
137
|
+
[HUMAN-REPRESENTATIVE · automated relay via <representative-label> · not typed by the human]
|
|
138
|
+
request_id: <uuid>
|
|
139
|
+
kind: request | question | decision
|
|
140
|
+
authorization: user-authorized — … | model-advice — …
|
|
141
|
+
reply: direct to <representative-label>, quoting the request_id.
|
|
142
|
+
---
|
|
143
|
+
<message>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
- `user_authorized` means the representative asserts the human explicitly
|
|
147
|
+
authorized that exact text. `model_advice` marks the model's own suggestion.
|
|
148
|
+
A `decision` is refused unless it is `user_authorized`.
|
|
149
|
+
- This label is the representative's own statement. **The Borg server does not
|
|
150
|
+
verify or enforce it.** On the server these are ordinary posts from the
|
|
151
|
+
representative drone. A relayed decision authorizes only its own text; it is
|
|
152
|
+
not proof of broader human approval, and the Coordinator should treat it that
|
|
153
|
+
way.
|
|
154
|
+
|
|
155
|
+
## Request identity, retries and ambiguous sends
|
|
156
|
+
|
|
157
|
+
`send` returns a `request_id`, which is also the protocol `post_id`
|
|
158
|
+
idempotency key of the log append.
|
|
159
|
+
|
|
160
|
+
- The lookup, the conflict decision, the id allocation and the `pending`
|
|
161
|
+
reservation happen in one locked ledger transaction, before any network use.
|
|
162
|
+
Two overlapping sends of the same content without a `request_id` therefore
|
|
163
|
+
cannot both go out: the second is refused (`AMBIGUOUS_SEND_UNRESOLVED`) and
|
|
164
|
+
names the first one's `request_id`. This is not a content filter: once a
|
|
165
|
+
request is settled, sending identical content again is a new, legitimate
|
|
166
|
+
request.
|
|
167
|
+
- Within the same server database, cube and representative drone, re-sending
|
|
168
|
+
the same `request_id` with identical content never creates a second message:
|
|
169
|
+
a request already recorded as sent is answered from the local
|
|
170
|
+
ledger, and otherwise the server deduplicates on `post_id`. The same
|
|
171
|
+
`request_id` with different content is refused (`REQUEST_ID_CONFLICT`). The local
|
|
172
|
+
ledger retains only the newest 200 settled requests; unresolved requests are
|
|
173
|
+
retained. Older settled retries depend on server deduplication. Changing the
|
|
174
|
+
database, cube or drone is outside that guarantee.
|
|
175
|
+
- Invalid input, and a payload the protocol would refuse, fail before anything
|
|
176
|
+
is reserved. The live cube is verified before posting; if that check fails,
|
|
177
|
+
nothing was sent and the reservation is released.
|
|
178
|
+
- The representative makes exactly **one** transport attempt per call (the
|
|
179
|
+
client's usual automatic retry after a connection reset is switched off for
|
|
180
|
+
it). Only because of that, a typed refusal the server returns to that attempt
|
|
181
|
+
is reported as `SEND_REJECTED` — *this attempt was not stored*: a rejected,
|
|
182
|
+
revoked or superseded credential, an evicted drone or deleted cube, or an HTTP
|
|
183
|
+
4xx answer. The error carries the underlying cause code and a recovery step
|
|
184
|
+
(for example: restore the connection with `prepare`; wait out a rate limit
|
|
185
|
+
and retry the same `request_id`; a `POST_ID_CONFLICT` goes to the operator).
|
|
186
|
+
That is a statement about the one attempt, not about the server's internals:
|
|
187
|
+
it assumes a server that answers 4xx instead of storing.
|
|
188
|
+
- Everything else is reported as `outcome: "ambiguous"`: no answer or a
|
|
189
|
+
timeout, an HTTP 5xx, a response that cannot be read or violates the protocol
|
|
190
|
+
(which can happen *after* the server stored the message), a TLS trust failure,
|
|
191
|
+
or any unrecognised error. The result names a bounded, sanitized `cause` and
|
|
192
|
+
a matching recovery hint. Nothing is re-sent across calls. The request stays
|
|
193
|
+
under `unresolved_requests` — across restarts — until a retry with the same
|
|
194
|
+
`request_id` settles it, and the same content under a new id is refused
|
|
195
|
+
meanwhile. There is deliberately no "forget it and resend under a new id"
|
|
196
|
+
operation.
|
|
197
|
+
- If an earlier attempt under a `request_id` was ambiguous, a later definite
|
|
198
|
+
refusal of a retry does not clear it: the request stays unresolved, because
|
|
199
|
+
the earlier attempt may have been stored.
|
|
200
|
+
|
|
201
|
+
Exactly-once delivery is therefore not claimed; at-most-once per `request_id`
|
|
202
|
+
relies on the server honouring `post_id` deduplication as the shared protocol
|
|
203
|
+
specifies, and on the single-process rule below.
|
|
204
|
+
|
|
205
|
+
## Reading, cursors and wake limits
|
|
206
|
+
|
|
207
|
+
- `read` drains only the representative drone's **own** unread cursor. Other
|
|
208
|
+
drones' cursors are separate client-owned state and are never touched.
|
|
209
|
+
- A read **consumes everything it fetched**, not only what it returns: the
|
|
210
|
+
Coordinator's replies, and equally the entries it ignores (other drones'
|
|
211
|
+
entries are counted in `ignored_entries` and never returned; the
|
|
212
|
+
Coordinator's broadcasts are returned only with `include_broadcast`). None
|
|
213
|
+
of them appear unread again.
|
|
214
|
+
- If the MCP host stops between reading a reply and relaying it to the human,
|
|
215
|
+
that reply is gone from the unread view. It still exists in the cube log, but
|
|
216
|
+
this version offers no tool to list past replies again. Persist the read result
|
|
217
|
+
in the host before relaying it.
|
|
218
|
+
- Replies preserve document citations (id, title and state). Document bodies are
|
|
219
|
+
not included and cannot be fetched through this connection. Ask the Coordinator
|
|
220
|
+
to provide the content through a supported channel.
|
|
221
|
+
- `limit` is a page-size hint, not a hard cap: when the unread backlog is
|
|
222
|
+
large the client's digest mode fetches, and drains, more than `limit`.
|
|
223
|
+
- `ack` is only a signal to the Coordinator that a direct reply was received.
|
|
224
|
+
It does not make delivery reliable, and it neither advances nor restores the
|
|
225
|
+
unread cursor.
|
|
226
|
+
- Exclusive process ownership is enforced for each representative drone. Processes
|
|
227
|
+
may start idle; the first `send`, `read` or `ack` takes the lease. Other processes
|
|
228
|
+
receive `REPRESENTATIVE_OWNERSHIP_REQUIRED` before any ledger reservation or
|
|
229
|
+
write, cursor access, or network call. The refusal names the owner's PID and
|
|
230
|
+
start time. Use that host, or wait for it to exit before using another.
|
|
231
|
+
- `status` is allowed in every process, is read-only, and takes no lease. Its
|
|
232
|
+
`ownership` field reports the state, PID, start time, and heartbeat age in
|
|
233
|
+
milliseconds (`ageMs`). A clean exit releases ownership; a dead PID or a
|
|
234
|
+
heartbeat older than 70 seconds permits takeover without manual cleanup.
|
|
235
|
+
A process that loses its lease refuses further activity until restarted.
|
|
236
|
+
An already in-flight network operation cannot be cancelled by a local lease;
|
|
237
|
+
same-request retries across takeover still use the existing ledger and server
|
|
238
|
+
deduplication. Retry an ambiguous send with its original `request_id`.
|
|
239
|
+
- `in_reply_to` is a textual match of a known `request_id` quoted in the reply.
|
|
240
|
+
It is a convenience, not a protocol guarantee.
|
|
241
|
+
- **There is no background wake.** A generic MCP host receives nothing
|
|
242
|
+
unsolicited: replies are seen only when the host calls
|
|
243
|
+
`borg_representative-read`. This version provides explicit send/read round
|
|
244
|
+
trips only and makes no claim of automatic ongoing coordination. The
|
|
245
|
+
Coordinator is woken by the direct message through its own normal wake path.
|
|
246
|
+
|
|
247
|
+
### Host conversation routing
|
|
248
|
+
|
|
249
|
+
The lease selects one consuming process, not a conversation within that host.
|
|
250
|
+
The host must record which conversation owns each `request_id`, persist every
|
|
251
|
+
read result before relaying it, and route replies using `in_reply_to`. Hold
|
|
252
|
+
replies with an unknown or missing request ID for the human instead of dropping
|
|
253
|
+
them. Borg cannot enforce these duties inside the host; it provides neither a
|
|
254
|
+
durable inbox nor a separate unread cursor for each conversation.
|
|
255
|
+
|
|
256
|
+
## Recovery
|
|
257
|
+
|
|
258
|
+
Run `borg` with the Node installation that owns the global `borgmcp` install;
|
|
259
|
+
a different Node prefix can fail the local server-installation check even when
|
|
260
|
+
the server is installed under the original prefix.
|
|
261
|
+
|
|
262
|
+
| Error | Meaning and action |
|
|
263
|
+
| --- | --- |
|
|
264
|
+
| `NOT_PREPARED` | No binding for that worktree. Follow the initial preparation command above with an explicitly chosen Coordinator. |
|
|
265
|
+
| `SEAT_UNAVAILABLE` | The representative drone's saved connection is gone or rejected. Run the complete recovery command printed in the error; it includes the worktree, Coordinator and role. |
|
|
266
|
+
| `BINDING_MISMATCH` | The worktree's connection is not the bound server/cube/drone, or the binding changed under a running process. Run the printed command to confirm the rebind, then restart the MCP process. |
|
|
267
|
+
| `COORDINATOR_UNAVAILABLE` | The bound Coordinator was evicted, released or reassigned. Restore the bound Coordinator and use the printed recovery command, or deliberately substitute a new Coordinator label in that command. |
|
|
268
|
+
| `REPRESENTATIVE_OWNERSHIP_REQUIRED` with a directory-permission refusal | Check that the named path is a real directory you own and not a symlink, then set it to 0700 and retry. Restart a process that had already lost ownership. Do not change permissions through a symlink. |
|
|
269
|
+
| `REPRESENTATIVE_ROLE_NOT_PERMITTED` | The representative drone holds a human-seat or coordinating role. Give it its own worker role. |
|
package/docs/RELEASING.md
CHANGED
|
@@ -21,9 +21,9 @@ Before creating the release tag, independently verify all of these conditions:
|
|
|
21
21
|
- the extraction review confirms no private backend secrets, deployment
|
|
22
22
|
configuration, customer data, local state, or duplicated shared contracts
|
|
23
23
|
entered the public package;
|
|
24
|
-
- the exact audited registry dependency `borgmcp-shared@2.
|
|
24
|
+
- the exact audited registry dependency `borgmcp-shared@2.2.0` remains locked to
|
|
25
25
|
its canonical tarball and integrity
|
|
26
|
-
`sha512-
|
|
26
|
+
`sha512-sKCKCMBvJWHAVYtFAeBLfpuNZqSJgLqEy7/k1isefgJFlHTMJJSy53bOVj6W+pLWrRSKbhhFkxJheTmFey/cIg==`;
|
|
27
27
|
- the current published server and the client candidate use the same exact
|
|
28
28
|
`borgmcp-shared` version; publish a compatible server before tagging the
|
|
29
29
|
client when that pin changes;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "borgmcp",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.5.0",
|
|
4
4
|
"description": "Coordinate AI coding agents in shared cubes. Works with Claude Code, Codex, and OpenCode.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
74
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
75
|
-
"borgmcp-shared": "2.
|
|
75
|
+
"borgmcp-shared": "2.2.0",
|
|
76
76
|
"chalk": "^5.3.0",
|
|
77
77
|
"prompts": "^2.4.2",
|
|
78
78
|
"which": "^4.0.0"
|
package/src/assimilate-cmd.ts
CHANGED
|
@@ -441,6 +441,7 @@ async function selectAssimilationAuthority(
|
|
|
441
441
|
flags: AssimilateFlags,
|
|
442
442
|
deps: AuthorityResolutionDeps,
|
|
443
443
|
mode: 'assimilate' | 'cube-init',
|
|
444
|
+
authoritySelectionCommand?: string,
|
|
444
445
|
): Promise<AssimilationAuthority | null> {
|
|
445
446
|
if (flags.server !== undefined) {
|
|
446
447
|
try {
|
|
@@ -460,7 +461,7 @@ async function selectAssimilationAuthority(
|
|
|
460
461
|
}
|
|
461
462
|
if (!deps.isTTY() || flags.yes) {
|
|
462
463
|
if (deps.defaultAuthority) return deps.defaultAuthority;
|
|
463
|
-
const command = mode === 'cube-init'
|
|
464
|
+
const command = authoritySelectionCommand ? `\`${authoritySelectionCommand}\`` : mode === 'cube-init'
|
|
464
465
|
? '`borg server cube init --host <host>`'
|
|
465
466
|
: '`borg assimilate --host <host> --here`';
|
|
466
467
|
deps.stderr(`No local server selected. Use ${command} to select a local server.\n`);
|
|
@@ -1132,11 +1133,11 @@ export interface CubeRoleResolutionOutcome {
|
|
|
1132
1133
|
cli: BorgCli;
|
|
1133
1134
|
}
|
|
1134
1135
|
|
|
1135
|
-
|
|
1136
|
+
function resolveConnectionRole(
|
|
1136
1137
|
input: CubeRoleResolutionInput,
|
|
1137
1138
|
deps: CubeRoleResolutionDeps,
|
|
1138
|
-
):
|
|
1139
|
-
const { requestedRole,
|
|
1139
|
+
): AssimilationPhaseOutcome<{ resolvedRole: Role }> {
|
|
1140
|
+
const { requestedRole, cubeDetail, isFirstDrone, savedLocalRole, apiUrl } = input;
|
|
1140
1141
|
let resolvedRole: Role | undefined;
|
|
1141
1142
|
if (savedLocalRole) {
|
|
1142
1143
|
resolvedRole = savedLocalRole;
|
|
@@ -1166,6 +1167,17 @@ export async function resolveAssimilationCubeRole(
|
|
|
1166
1167
|
}
|
|
1167
1168
|
}
|
|
1168
1169
|
|
|
1170
|
+
return continueAssimilation({ resolvedRole });
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
export async function resolveAssimilationCubeRole(
|
|
1174
|
+
input: CubeRoleResolutionInput,
|
|
1175
|
+
deps: CubeRoleResolutionDeps,
|
|
1176
|
+
): Promise<AssimilationPhaseOutcome<CubeRoleResolutionOutcome>> {
|
|
1177
|
+
const role = resolveConnectionRole(input, deps);
|
|
1178
|
+
if (role.kind === 'stop') return role;
|
|
1179
|
+
const { resolvedRole } = role.value;
|
|
1180
|
+
const { flags, apiUrl } = input;
|
|
1169
1181
|
const effectiveModel: string | null = flags.model ?? null;
|
|
1170
1182
|
const cli = await deps.resolveCli(flags.cli);
|
|
1171
1183
|
try {
|
|
@@ -1192,7 +1204,7 @@ export interface SeatPreparationInput {
|
|
|
1192
1204
|
serverTrustIdentity: string;
|
|
1193
1205
|
cubeDetail: CubeDetail;
|
|
1194
1206
|
resolvedRole: Role;
|
|
1195
|
-
cli
|
|
1207
|
+
cli?: BorgCli;
|
|
1196
1208
|
effectiveModel: string | null;
|
|
1197
1209
|
projectRoot: string;
|
|
1198
1210
|
existing: CanonicalActiveCube | null;
|
|
@@ -1267,8 +1279,7 @@ export async function prepareAssimilationSeat(
|
|
|
1267
1279
|
cube_id: cubeDetail.id,
|
|
1268
1280
|
role_id: resolvedRole.id,
|
|
1269
1281
|
hostname: deps.getHostname(),
|
|
1270
|
-
agent_kind: cli,
|
|
1271
|
-
model: effectiveModel,
|
|
1282
|
+
...(cli !== undefined ? { agent_kind: cli, model: effectiveModel } : {}),
|
|
1272
1283
|
working_repo: resolveWorkingRepo(projectRoot),
|
|
1273
1284
|
...(reattachPriorId ? { prior_drone_id: reattachPriorId } : {}),
|
|
1274
1285
|
...(remintInvalidPrior ? { remint_invalid_prior: true } : {}),
|
|
@@ -1471,6 +1482,7 @@ export interface AuthorityResolutionInput {
|
|
|
1471
1482
|
args: AssimilateArgs;
|
|
1472
1483
|
mode: 'assimilate' | 'cube-init';
|
|
1473
1484
|
repositoryContext: GitRepositoryContext;
|
|
1485
|
+
authoritySelectionCommand?: string;
|
|
1474
1486
|
}
|
|
1475
1487
|
|
|
1476
1488
|
export interface AuthorityResolutionOutcome {
|
|
@@ -1493,6 +1505,13 @@ export async function resolveAssimilationAuthority(
|
|
|
1493
1505
|
deps: AuthorityResolutionDeps,
|
|
1494
1506
|
): Promise<AssimilationPhaseOutcome<AuthorityResolutionOutcome>> {
|
|
1495
1507
|
const { args, mode, repositoryContext } = input;
|
|
1508
|
+
// Representative retries must refuse before private-state initialization or
|
|
1509
|
+
// installation checks can mutate anything when no authority was selected.
|
|
1510
|
+
if (input.authoritySelectionCommand && args.flags.server === undefined &&
|
|
1511
|
+
deps.defaultAuthority === undefined && !args.flags.enroll && (!deps.isTTY() || args.flags.yes)) {
|
|
1512
|
+
await selectAssimilationAuthority(args.flags, deps, mode, input.authoritySelectionCommand);
|
|
1513
|
+
return { kind: 'stop', code: 1 };
|
|
1514
|
+
}
|
|
1496
1515
|
const hostlessEnrollment = args.flags.enroll === true &&
|
|
1497
1516
|
args.flags.server === undefined && deps.defaultAuthority === undefined;
|
|
1498
1517
|
const artifactOnlyEnrollment = hostlessEnrollment && deps.isTTY();
|
|
@@ -1624,7 +1643,7 @@ export async function resolveAssimilationAuthority(
|
|
|
1624
1643
|
localSeatReadError = error;
|
|
1625
1644
|
}
|
|
1626
1645
|
|
|
1627
|
-
const selectedAuthority = await selectAssimilationAuthority(args.flags, deps, mode);
|
|
1646
|
+
const selectedAuthority = await selectAssimilationAuthority(args.flags, deps, mode, input.authoritySelectionCommand);
|
|
1628
1647
|
if (!selectedAuthority) return { kind: 'stop', code: 1 };
|
|
1629
1648
|
let authority = selectedAuthority;
|
|
1630
1649
|
if (localSeatReadError !== undefined) {
|
|
@@ -1750,12 +1769,31 @@ export async function runAssimilate(
|
|
|
1750
1769
|
args: AssimilateArgs,
|
|
1751
1770
|
deps: AssimilateDeps,
|
|
1752
1771
|
options: RunAssimilateOptions = {},
|
|
1772
|
+
): Promise<number> {
|
|
1773
|
+
return runAssimilationFlow(args, deps, options);
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
/** Prepare a host-neutral connection using the same durable assimilation lifecycle. */
|
|
1777
|
+
export async function prepareConnection(
|
|
1778
|
+
args: AssimilateArgs,
|
|
1779
|
+
deps: AssimilateDeps,
|
|
1780
|
+
options: { validateRole: (role: Role) => void; onPrepared: (prepared: PreparedAssimilation) => void; authoritySelectionCommand?: string },
|
|
1781
|
+
): Promise<number> {
|
|
1782
|
+
return runAssimilationFlow(args, deps, { launch: false, ...options }, options.validateRole, options.authoritySelectionCommand);
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
async function runAssimilationFlow(
|
|
1786
|
+
args: AssimilateArgs,
|
|
1787
|
+
deps: AssimilateDeps,
|
|
1788
|
+
options: RunAssimilateOptions,
|
|
1789
|
+
validateConnectionRole?: (role: Role) => void,
|
|
1790
|
+
authoritySelectionCommand?: string,
|
|
1753
1791
|
): Promise<number> {
|
|
1754
1792
|
const repository = await resolveAssimilationRepository(args, deps);
|
|
1755
1793
|
if (repository.kind === 'stop') return repository.code;
|
|
1756
1794
|
const { mode, repositoryContext } = repository.value;
|
|
1757
1795
|
|
|
1758
|
-
const authorityResolution = await resolveAssimilationAuthority({ args, mode, repositoryContext }, deps);
|
|
1796
|
+
const authorityResolution = await resolveAssimilationAuthority({ args, mode, repositoryContext, authoritySelectionCommand }, deps);
|
|
1759
1797
|
if (authorityResolution.kind === 'stop') return authorityResolution.code;
|
|
1760
1798
|
const {
|
|
1761
1799
|
authority,
|
|
@@ -2172,16 +2210,18 @@ export async function runAssimilate(
|
|
|
2172
2210
|
}
|
|
2173
2211
|
}
|
|
2174
2212
|
|
|
2175
|
-
const
|
|
2176
|
-
requestedRole: args.role,
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
}, deps);
|
|
2213
|
+
const roleInput = {
|
|
2214
|
+
requestedRole: args.role, flags: args.flags, cubeDetail, isFirstDrone,
|
|
2215
|
+
savedLocalRole, apiUrl: authority.apiUrl,
|
|
2216
|
+
};
|
|
2217
|
+
const cubeRole = validateConnectionRole
|
|
2218
|
+
? resolveConnectionRole(roleInput, deps)
|
|
2219
|
+
: await resolveAssimilationCubeRole(roleInput, deps);
|
|
2183
2220
|
if (cubeRole.kind === 'stop') return cubeRole.code;
|
|
2184
|
-
const { resolvedRole
|
|
2221
|
+
const { resolvedRole } = cubeRole.value;
|
|
2222
|
+
validateConnectionRole?.(resolvedRole);
|
|
2223
|
+
const cli = 'cli' in cubeRole.value ? cubeRole.value.cli as BorgCli : undefined;
|
|
2224
|
+
const effectiveModel = 'effectiveModel' in cubeRole.value ? cubeRole.value.effectiveModel as string | null : null;
|
|
2185
2225
|
|
|
2186
2226
|
const seat = await prepareAssimilationSeat({
|
|
2187
2227
|
apiUrl: auth.apiUrl,
|
|
@@ -2202,6 +2242,7 @@ export async function runAssimilate(
|
|
|
2202
2242
|
}, deps);
|
|
2203
2243
|
if (seat.kind === 'stop') return seat.code;
|
|
2204
2244
|
const { result, assignedRole, sessionExpected } = seat.value;
|
|
2245
|
+
validateConnectionRole?.(assignedRole);
|
|
2205
2246
|
|
|
2206
2247
|
const worktree = await prepareAssimilationWorktree({
|
|
2207
2248
|
flags: args.flags,
|
|
@@ -2266,7 +2307,7 @@ export async function runAssimilate(
|
|
|
2266
2307
|
// of that request. The resolver therefore saved the preference against the
|
|
2267
2308
|
// invoking checkout. Once a sibling exists, save the same choice under its
|
|
2268
2309
|
// own project key so a later --here launch in that worktree can read it.
|
|
2269
|
-
if (spawnedWorktreePath) {
|
|
2310
|
+
if (spawnedWorktreePath && cli !== undefined) {
|
|
2270
2311
|
try {
|
|
2271
2312
|
await deps.setCliPreferenceForWorktree(cli, spawnedWorktreePath);
|
|
2272
2313
|
} catch (err) {
|
|
@@ -2281,8 +2322,10 @@ export async function runAssimilate(
|
|
|
2281
2322
|
}
|
|
2282
2323
|
|
|
2283
2324
|
try {
|
|
2284
|
-
|
|
2285
|
-
|
|
2325
|
+
if (cli !== undefined) {
|
|
2326
|
+
deps.mkdirp(scratchRoot);
|
|
2327
|
+
deps.provisionLaunchAccess?.(cli, seatWorktree, launchAccessPaths);
|
|
2328
|
+
}
|
|
2286
2329
|
} catch (err) {
|
|
2287
2330
|
const message = err instanceof Error ? err.message : String(err);
|
|
2288
2331
|
deps.stderr(
|
|
@@ -2320,6 +2363,14 @@ export async function runAssimilate(
|
|
|
2320
2363
|
}
|
|
2321
2364
|
}
|
|
2322
2365
|
|
|
2366
|
+
if (validateConnectionRole) {
|
|
2367
|
+
options.onPrepared?.({
|
|
2368
|
+
cubeId: result.cube_id, cubeName: cubeDetail.name, droneId: result.drone_id,
|
|
2369
|
+
droneLabel: result.drone_label, roleName: assignedRole.name, worktree: seatWorktree,
|
|
2370
|
+
});
|
|
2371
|
+
return 0;
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2323
2374
|
// gh#793: best-effort GC of orphaned inbox files (evicted/dead drones) in the
|
|
2324
2375
|
// cube just joined — lazy-on-assimilate, no cron/new command. NEVER blocks or
|
|
2325
2376
|
// fails the assimilate (whole call swallowed). Local-only signal (CubeDetail
|
|
@@ -2371,7 +2422,7 @@ export async function runAssimilate(
|
|
|
2371
2422
|
cubeDetail,
|
|
2372
2423
|
assignedRole,
|
|
2373
2424
|
apiUrl: auth.apiUrl,
|
|
2374
|
-
cli
|
|
2425
|
+
cli: cli!,
|
|
2375
2426
|
effectiveModel,
|
|
2376
2427
|
agentCwd,
|
|
2377
2428
|
seatWorktree,
|
package/src/claude.ts
CHANGED
|
@@ -390,6 +390,28 @@ async function main() {
|
|
|
390
390
|
buildDefaultSeatCommandDeps(),
|
|
391
391
|
));
|
|
392
392
|
}
|
|
393
|
+
if (process.argv[2] === 'representative') {
|
|
394
|
+
// Loaded on demand: the representative facade is unrelated to agent launch.
|
|
395
|
+
const representative = await import('./representative-cmd.js');
|
|
396
|
+
const parsed = representative.parseRepresentativeArgs(process.argv.slice(3));
|
|
397
|
+
if (!parsed.ok) {
|
|
398
|
+
process.stderr.write(chalk.red(`${consolePrefix()}◼ borg representative: ${parsed.error}\n`));
|
|
399
|
+
process.stderr.write(`Run \`borg representative --help\` for usage.\n`);
|
|
400
|
+
process.exit(1);
|
|
401
|
+
}
|
|
402
|
+
const deps = await representative.buildDefaultRepresentativeDeps();
|
|
403
|
+
if (parsed.command.action === 'prepare') {
|
|
404
|
+
process.exit(await representative.runRepresentativePrepare(parsed.command, deps));
|
|
405
|
+
}
|
|
406
|
+
if (parsed.command.action === 'status') {
|
|
407
|
+
process.exit(await representative.runRepresentativeStatus(parsed.command, deps));
|
|
408
|
+
}
|
|
409
|
+
const { pinMcpSeatIdentity } = await import('./cubes.js');
|
|
410
|
+
process.exit(await representative.runRepresentativeMcp(parsed.command, deps, {
|
|
411
|
+
version: getPackageVersion(),
|
|
412
|
+
pinSeat: pinMcpSeatIdentity,
|
|
413
|
+
}));
|
|
414
|
+
}
|
|
393
415
|
if (process.argv[2] === 'launch-all') {
|
|
394
416
|
const parsed = parseLaunchAllArgs(process.argv.slice(3));
|
|
395
417
|
if (!parsed.ok) {
|
package/src/cli-help.ts
CHANGED
|
@@ -113,6 +113,49 @@ export function launchSeatHelpText(version: string): string {
|
|
|
113
113
|
);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
export function representativeHelpText(version: string): string {
|
|
117
|
+
return (
|
|
118
|
+
`borg representative (borgmcp ${version}) — connect a human representative (e.g. Hermes) to one Coordinator\n\n` +
|
|
119
|
+
`Vocabulary:\n` +
|
|
120
|
+
` cube One repository's shared coordination space on your Borg server.\n` +
|
|
121
|
+
` drone One connected agent session in a cube; its role defines how it works.\n` +
|
|
122
|
+
` human seat The one role in a cube that speaks with the human's authority.\n` +
|
|
123
|
+
` Coordinator The drone holding the human seat. It plans the work and dispatches the other drones.\n` +
|
|
124
|
+
` human representative A SEPARATE automated drone, under its own non-human-seat role, that relays the\n` +
|
|
125
|
+
` human's requests, questions and decisions to that one Coordinator and reads its\n` +
|
|
126
|
+
` replies. It is not the human, never takes the human seat, and never addresses\n` +
|
|
127
|
+
` other drones or broadcasts.\n\n` +
|
|
128
|
+
`Usage:\n` +
|
|
129
|
+
` borg representative prepare --coordinator <drone-label> [--role <name>] [--worktree <name>] [--host <host>] [--rebind]\n` +
|
|
130
|
+
` borg representative status [--worktree <path>]\n` +
|
|
131
|
+
` borg representative mcp [--worktree <path>]\n` +
|
|
132
|
+
` borg representative --help\n\n` +
|
|
133
|
+
`Commands:\n` +
|
|
134
|
+
` prepare Create or resume the representative's own drone in this repository's cube and bind it to\n` +
|
|
135
|
+
` exactly the named Coordinator drone. Launches no agent CLI and changes no other drone.\n` +
|
|
136
|
+
` Fails if that Coordinator is missing, evicted, duplicated, or not in the human seat;\n` +
|
|
137
|
+
` another drone is never chosen instead.\n` +
|
|
138
|
+
` status Show the saved binding, re-check it against the live cube, and list unresolved sends.\n` +
|
|
139
|
+
` mcp Serve the restricted stdio MCP tools (status, send, read, ack) for a generic MCP host.\n\n` +
|
|
140
|
+
`Options:\n` +
|
|
141
|
+
` --coordinator <drone-label> Exact label of the Coordinator drone (see \`borg drones\`). Required for prepare.\n` +
|
|
142
|
+
` --role <name> Existing non-human-seat role for the representative (default: hermes-representative)\n` +
|
|
143
|
+
` --worktree <name> prepare: create the drone in a new linked worktree of that name\n` +
|
|
144
|
+
` --worktree <path> status/mcp: absolute path of the prepared representative worktree\n` +
|
|
145
|
+
` --host <host> prepare: explicit Borg server, as in \`borg assimilate --host\`\n` +
|
|
146
|
+
` --rebind prepare: explicitly replace the saved cube/Coordinator selection\n` +
|
|
147
|
+
` --help, -h Show this help\n\n` +
|
|
148
|
+
`Limits: explicit send/read round trips only — there is no background wake or push to the MCP host.\n` +
|
|
149
|
+
`Reading drains everything it fetches, so relay replies at once; ack is only a signal to the Coordinator.\n` +
|
|
150
|
+
`Run exactly one MCP host process per representative worktree (not enforced).\n` +
|
|
151
|
+
`A retried send reuses its request id so the server stores it once; an unknown outcome is reported as\n` +
|
|
152
|
+
`ambiguous, with its cause, and never re-sent automatically. "User-authorized" is the representative's own label:\n` +
|
|
153
|
+
`the Borg server does not verify it, and one relayed decision is not broader human approval.\n` +
|
|
154
|
+
`No command takes a credential; the drone's saved connection stays in Borg's private store.\n` +
|
|
155
|
+
`Details: docs/HUMAN_REPRESENTATIVE.md\n`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
116
159
|
export function doctorHelpText(version: string): string {
|
|
117
160
|
return (
|
|
118
161
|
`borg doctor (borgmcp ${version}) — inspect Borg agent integrations without changing them\n\n` +
|
|
@@ -139,6 +182,7 @@ export function clientSubcommandHelpText(
|
|
|
139
182
|
case 'drones': return seatsHelpText(version);
|
|
140
183
|
case 'launch': return launchSeatHelpText(version);
|
|
141
184
|
case 'launch-all': return launchAllHelpText(version);
|
|
185
|
+
case 'representative': return representativeHelpText(version);
|
|
142
186
|
case 'doctor': return doctorHelpText(version);
|
|
143
187
|
default: return null;
|
|
144
188
|
}
|
|
@@ -186,6 +230,7 @@ export function topLevelHelpText(version: string): string {
|
|
|
186
230
|
` borg drones List this machine's registered drones and worktrees\n` +
|
|
187
231
|
` borg launch <drone-label-or-id-prefix> Reopen one registered drone from its worktree\n` +
|
|
188
232
|
` borg launch-all [cube] Launch all drone worktrees of a cube (default: active cube)\n` +
|
|
233
|
+
` borg representative prepare|status|mcp Let an MCP host (e.g. Hermes) speak for you to one Coordinator drone\n` +
|
|
189
234
|
` borg server <command> [arguments]\n` +
|
|
190
235
|
` borg --cli claude|codex|opencode Launch that agent CLI directly\n` +
|
|
191
236
|
` borg --version Show installed version\n\n` +
|
package/src/docs-sections.ts
CHANGED
|
@@ -15,6 +15,7 @@ const REPOSITORY_URL = "https://github.com/Byte-Ventures/borg-mcp-client";
|
|
|
15
15
|
const LOCAL_SERVER_URL = `${REPOSITORY_URL}/blob/main/docs/LOCAL_SERVER.md`;
|
|
16
16
|
const SEAT_LIFECYCLE_URL = `${REPOSITORY_URL}/blob/main/docs/SEAT_LIFECYCLE.md`;
|
|
17
17
|
const DOCUMENTS_URL = `${REPOSITORY_URL}/blob/main/docs/DOCUMENTS.md`;
|
|
18
|
+
const HUMAN_REPRESENTATIVE_URL = `${REPOSITORY_URL}/blob/main/docs/HUMAN_REPRESENTATIVE.md`;
|
|
18
19
|
|
|
19
20
|
export interface DocsSection {
|
|
20
21
|
/** logical topic key */
|
|
@@ -91,6 +92,13 @@ export const DOCS_SECTIONS: DocsSection[] = [
|
|
|
91
92
|
summary: "Immutable cube-local Markdown or plain text, revisions, removal, and structured activity-log citations.",
|
|
92
93
|
keywords: ["document", "documents", "citation", "cite", "durable content", "supersede", "revision", "borg_put-document", "borg_get-document"],
|
|
93
94
|
},
|
|
95
|
+
{
|
|
96
|
+
slug: "human-representative",
|
|
97
|
+
title: "Human representative",
|
|
98
|
+
url: HUMAN_REPRESENTATIVE_URL,
|
|
99
|
+
summary: "A separate non-human-seat drone that relays the human's requests and decisions to one bound Coordinator over a restricted stdio MCP facade: prepare, host configuration, idempotent sends, no background wake.",
|
|
100
|
+
keywords: ["representative", "hermes", "human representative", "delegate", "proxy", "borg representative", "borg_representative-send", "mcp host", "request_id", "ambiguous"],
|
|
101
|
+
},
|
|
94
102
|
{
|
|
95
103
|
slug: "tools",
|
|
96
104
|
title: "Tool reference",
|