@bli-cockpit/mcp 0.1.114 → 0.1.116
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 +11 -1
- package/dist/approvals-tools.d.ts +4 -0
- package/dist/approvals-tools.js +191 -0
- package/dist/memory-chain-tools.d.ts +13 -0
- package/dist/memory-chain-tools.js +44 -0
- package/dist/memory-consolidation-tools.js +19 -1
- package/dist/memory-rules-tools.js +40 -13
- package/dist/server.js +6 -0
- package/dist/tool-annotations.d.ts +1 -1
- package/dist/tool-annotations.js +1 -0
- package/dist/tool-catalog.d.ts +2 -2
- package/dist/tool-catalog.js +13 -0
- package/dist/verb-census-spellings.js +2 -0
- package/dist/verb-census.js +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -109,7 +109,7 @@ about what an agent can reach.
|
|
|
109
109
|
|
|
110
110
|
<!-- BEGIN GENERATED verb census — `npm run mcp:readme` -->
|
|
111
111
|
|
|
112
|
-
**
|
|
112
|
+
**169 of 174 Tower verbs have an MCP twin.**
|
|
113
113
|
Each tool goes through the SAME door its CLI verb calls, with the same
|
|
114
114
|
collector device token — never a second route and never a service-role
|
|
115
115
|
reader. `src/verb-census.test.ts` fails when a verb is in none of the
|
|
@@ -117,6 +117,15 @@ three tables below.
|
|
|
117
117
|
|
|
118
118
|
| CLI verb | MCP tool | Door |
|
|
119
119
|
| --- | --- | --- |
|
|
120
|
+
| `cockpit approvals approve` | `approvals_approve` | `POST /api/approvals/[id]/approve` |
|
|
121
|
+
| `cockpit approvals create` | `approvals_create` | `POST /api/approvals` |
|
|
122
|
+
| `cockpit approvals edit` | `approvals_edit` | `PATCH /api/approvals/[id]` |
|
|
123
|
+
| `cockpit approvals list` | `approvals_list` | `GET /api/approvals` |
|
|
124
|
+
| `cockpit approvals reject` | `approvals_reject` | `POST /api/approvals/[id]/reject` |
|
|
125
|
+
| `cockpit approvals rules` | `approvals_rules` | `GET /api/approvals/rules` |
|
|
126
|
+
| `cockpit approvals rules create` | `approvals_rules_create` | `POST /api/approvals/rules` |
|
|
127
|
+
| `cockpit approvals rules revoke` | `approvals_rules_revoke` | `POST /api/approvals/rules/[id]/revoke` |
|
|
128
|
+
| `cockpit approvals show` | `approvals_show` | `GET /api/approvals/[id]` |
|
|
120
129
|
| `cockpit brain retry` | `brain_retry` | `POST /api/brain/incidents/retry` |
|
|
121
130
|
| `cockpit brain status` | `brain_status` | `GET /api/brain/incidents` |
|
|
122
131
|
| `cockpit brief done` | `brief_done` | `POST /api/jarvis/ticks` |
|
|
@@ -195,6 +204,7 @@ three tables below.
|
|
|
195
204
|
| `cockpit mail send` | `mail_send` | `POST /api/mail/send` |
|
|
196
205
|
| `cockpit mail sync` | `mail_sync` | `POST /api/mail/accounts/[id]/sync` |
|
|
197
206
|
| `cockpit mail sync-status` | `mail_sync_status` | `GET /api/mail/sync-status` |
|
|
207
|
+
| `cockpit memory chain` | `memory_chain` | `GET /api/memory/chain` |
|
|
198
208
|
| `cockpit memory consolidation` | `memory_consolidation` | `GET /api/memory/consolidation` |
|
|
199
209
|
| `cockpit memory forget` | `forget_memory` (`bli-memory`) | `POST /api/memory/forget` |
|
|
200
210
|
| `cockpit memory knowledge` | `memory_knowledge` | `GET /api/memory/knowledge` |
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The approvals inbox over MCP (BLI-16945): the nine twins of `cockpit approvals`.
|
|
3
|
+
*
|
|
4
|
+
* Plain words: an agent PREPARES an outward act (a mail, a message) as a card; the person
|
|
5
|
+
* approves it, edits it and approves it, or rejects it. After a few approvals of one kind the
|
|
6
|
+
* person can turn it into a standing rule ("send without asking"). Every run leaves a receipt.
|
|
7
|
+
* These tools ride the SAME `/api/approvals` doors the browser page and the CLI call, on this
|
|
8
|
+
* machine's device token, never a second route.
|
|
9
|
+
*
|
|
10
|
+
* Three of them are deliberate acts and refuse without `confirm: true`, the same gate as
|
|
11
|
+
* `mail_send` and the CLI's `--yes`: `approvals_approve` (the guarded act runs and cannot be
|
|
12
|
+
* recalled), `approvals_rules_create` (future cards send without asking) and
|
|
13
|
+
* `approvals_rules_revoke` (a standing rule stops). Creating a card, editing one and rejecting one
|
|
14
|
+
* reach nobody, so they need none.
|
|
15
|
+
*
|
|
16
|
+
* A refused send answers 502 with the door's reason and its "Not sent" sentence; this relays both
|
|
17
|
+
* verbatim, as an error, and approving again retries.
|
|
18
|
+
*/
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
import { callAgentDoor } from "./agent-door.js";
|
|
21
|
+
import { CONFIRM_INPUT, EMAIL_INPUT, dataResult, doorFailureText, errorResult, queryString, registrarFor, unconfirmed, withSession, } from "./tool-result.js";
|
|
22
|
+
const LIST_STATUSES = ["open", "done", "pending", "executing", "executed", "failed", "rejected"];
|
|
23
|
+
const SOURCE_KINDS = ["session", "meeting", "ticket", "message", "mail", "jarvis", "schedule", "manual"];
|
|
24
|
+
const CARD_ID = z.string().uuid().describe("The card id, from approvals_list.");
|
|
25
|
+
const ADDRESS = z.object({
|
|
26
|
+
name: z.string().max(200).optional().describe("The person's display name."),
|
|
27
|
+
address: EMAIL_INPUT.describe("The email address."),
|
|
28
|
+
});
|
|
29
|
+
/** Every field a card's `payload` can carry today (mail.send and msg.send); the door checks the fields of the kind named. */
|
|
30
|
+
const PAYLOAD = z.looseObject({
|
|
31
|
+
account: z.string().max(100).optional().describe("mail.send: the exact connected mailbox label to send from. Omit for the default."),
|
|
32
|
+
account_id: z.string().uuid().optional().describe("mail.send: the mailbox id to send from, instead of a label."),
|
|
33
|
+
to: z.array(ADDRESS).max(50).optional().describe("mail.send: recipients as { name?, address }."),
|
|
34
|
+
cc: z.array(ADDRESS).max(50).optional().describe("mail.send: copied recipients as { name?, address }."),
|
|
35
|
+
subject: z.string().max(998).optional().describe("mail.send: the subject line."),
|
|
36
|
+
text: z.string().max(500_000).optional().describe("mail.send: the plain-text body."),
|
|
37
|
+
in_reply_to_message_id: z.string().uuid().optional().describe("mail.send: a message id from mail_read, to send as a reply in that thread."),
|
|
38
|
+
channel_id: z.string().uuid().optional().describe("msg.send: the channel id from msg_channels."),
|
|
39
|
+
content: z.string().max(8000).optional().describe("msg.send: the message text."),
|
|
40
|
+
thread_parent_id: z.string().uuid().optional().describe("msg.send: a message id to reply under."),
|
|
41
|
+
});
|
|
42
|
+
const EDIT = z.looseObject({
|
|
43
|
+
subject: z.string().max(998).optional().describe("mail.send: the new subject line."),
|
|
44
|
+
text: z.string().max(500_000).optional().describe("mail.send: the new plain-text body."),
|
|
45
|
+
content: z.string().max(8000).optional().describe("msg.send: the new message text."),
|
|
46
|
+
});
|
|
47
|
+
export function registerApprovalsTools(server, deps) {
|
|
48
|
+
const register = registrarFor(server);
|
|
49
|
+
/** One door call, relayed: the data on success, the door's own reason and sentence on a refusal. */
|
|
50
|
+
async function ask(tool, method, path, body, headline) {
|
|
51
|
+
return withSession(deps, async (session) => {
|
|
52
|
+
const response = await callAgentDoor(session, deps.fetchImpl, method, path, body);
|
|
53
|
+
if (!response.ok)
|
|
54
|
+
return errorResult(doorFailureText(tool, response));
|
|
55
|
+
return dataResult(headline(response.body), response.body);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const titleOf = (answer) => {
|
|
59
|
+
const card = answer.card;
|
|
60
|
+
return typeof card?.title === "string" ? card.title : "that card";
|
|
61
|
+
};
|
|
62
|
+
register("approvals_list", {
|
|
63
|
+
title: "List approval cards",
|
|
64
|
+
description: "The approvals inbox: outward acts (a mail, a message) that an agent prepared and a person has yet to decide. `status` open (default) is everything still waiting "
|
|
65
|
+
+ "(pending, executing, failed); done is everything decided; or name one status. Each card has its title, who it goes to, why, where it came from, the exact content and its "
|
|
66
|
+
+ "latest receipt. Newest first; `has_more` and `next_before` page backwards. Read-only. `cockpit approvals`, same door.",
|
|
67
|
+
inputSchema: {
|
|
68
|
+
status: z.enum(LIST_STATUSES).optional().describe("open (default), done, or one of pending, executing, executed, failed, rejected."),
|
|
69
|
+
limit: z.number().int().min(1).max(100).optional().describe("How many cards, 1 to 100. Default 50."),
|
|
70
|
+
before: z.string().min(1).max(100).optional().describe("The next_before cursor of the previous page, to read older cards."),
|
|
71
|
+
},
|
|
72
|
+
}, async (args) => {
|
|
73
|
+
const query = new URLSearchParams();
|
|
74
|
+
if (typeof args.status === "string")
|
|
75
|
+
query.set("status", args.status);
|
|
76
|
+
if (typeof args.limit === "number")
|
|
77
|
+
query.set("limit", String(args.limit));
|
|
78
|
+
if (typeof args.before === "string")
|
|
79
|
+
query.set("before", args.before);
|
|
80
|
+
return ask("approvals_list", "GET", `/api/approvals${queryString(query)}`, undefined, (answer) => `${String(answer.returned ?? 0)} card(s) shown, ${String(answer.waiting ?? 0)} waiting for a decision.${answer.has_more === true ? " More exist: pass next_before as before." : ""}`);
|
|
81
|
+
});
|
|
82
|
+
register("approvals_show", {
|
|
83
|
+
title: "Read one approval card",
|
|
84
|
+
description: "One card in full: who it goes to, why, where it came from, the exact content that will be (or was) sent, every receipt with the content the act door was given, "
|
|
85
|
+
+ "and whether a standing rule is worth offering. Read-only. `cockpit approvals show`, same door.",
|
|
86
|
+
inputSchema: { id: CARD_ID },
|
|
87
|
+
}, async (args) => ask("approvals_show", "GET", `/api/approvals/${encodeURIComponent(String(args.id))}`, undefined, (answer) => `${titleOf(answer)}.`));
|
|
88
|
+
register("approvals_create", {
|
|
89
|
+
title: "Prepare an approval card",
|
|
90
|
+
description: "Prepares an outward act as a card for the person to approve; nothing is sent. `act_kind` is mail.send (payload: to, subject, text, and optionally account, cc, in_reply_to_message_id) "
|
|
91
|
+
+ "or msg.send (payload: channel_id, content, optionally thread_parent_id). `reason` is why, in a sentence the person reads. `dedupe_key` makes a retry return the same card. "
|
|
92
|
+
+ "If a standing rule already covers the act it is sent at once and the answer says so. `cockpit approvals create`, same door.",
|
|
93
|
+
inputSchema: {
|
|
94
|
+
act_kind: z.enum(["mail.send", "msg.send"]).describe("Which kind of act: mail.send or msg.send."),
|
|
95
|
+
payload: PAYLOAD.describe("The act's exact content; the fields of the kind named."),
|
|
96
|
+
reason: z.string().trim().min(1).max(2000).describe("Why this should go out, in a sentence the person reads."),
|
|
97
|
+
kind_label: z.string().trim().min(1).max(120).optional().describe("A short name for this sort of card, like Client follow-up email."),
|
|
98
|
+
source_kind: z.enum(SOURCE_KINDS).optional().describe("Where the act came from. Default manual."),
|
|
99
|
+
source_ref: z.string().trim().min(1).max(200).optional().describe("An id or link for the source, like a ticket identifier."),
|
|
100
|
+
source_label: z.string().trim().min(1).max(300).optional().describe("A short human label for the source, shown on the card."),
|
|
101
|
+
prepared_by: z.string().trim().min(1).max(80).optional().describe("Who prepared it, like Claude. Default an agent on a paired machine."),
|
|
102
|
+
dedupe_key: z.string().trim().min(1).max(200).optional().describe("A stable key so preparing the same card twice returns the first."),
|
|
103
|
+
},
|
|
104
|
+
}, async (args) => ask("approvals_create", "POST", "/api/approvals", args, (answer) => {
|
|
105
|
+
if (answer.deduped === true)
|
|
106
|
+
return `${titleOf(answer)} was already prepared. Nothing new was created.`;
|
|
107
|
+
if (answer.executed_by_rule === true)
|
|
108
|
+
return `${titleOf(answer)}: a standing rule sent it without asking.`;
|
|
109
|
+
return `${titleOf(answer)} is prepared and waiting for the person's decision.`;
|
|
110
|
+
}));
|
|
111
|
+
register("approvals_approve", {
|
|
112
|
+
title: "Approve an approval card",
|
|
113
|
+
description: "Approves a card: the guarded act RUNS (the mail goes out, the message posts) and cannot be taken back, so confirm must be true. `edit` ({ subject, text } or { content }) "
|
|
114
|
+
+ "is applied first, in one step. If the send is refused the answer is an error carrying the door's reason and its \"Not sent\" sentence, the card is marked failed, and approving again retries. "
|
|
115
|
+
+ "A card already decided is refused with already_decided. `cockpit approvals approve`, same door.",
|
|
116
|
+
inputSchema: {
|
|
117
|
+
id: CARD_ID,
|
|
118
|
+
edit: EDIT.optional().describe("An edit to apply before approving: { subject, text } for mail, { content } for a message."),
|
|
119
|
+
confirm: CONFIRM_INPUT,
|
|
120
|
+
},
|
|
121
|
+
}, async (args) => {
|
|
122
|
+
const refusal = unconfirmed(args, "Approving lets the guarded act run, and a sent mail or message cannot be recalled.");
|
|
123
|
+
if (refusal)
|
|
124
|
+
return refusal;
|
|
125
|
+
return ask("approvals_approve", "POST", `/api/approvals/${encodeURIComponent(String(args.id))}/approve`, args.edit ? { edit: args.edit } : {}, (answer) => {
|
|
126
|
+
const receipt = answer.receipt;
|
|
127
|
+
return `Approved. ${typeof receipt?.summary === "string" ? receipt.summary : "Done."}`;
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
register("approvals_edit", {
|
|
131
|
+
title: "Edit an approval card",
|
|
132
|
+
description: "Saves an edit to a waiting card ({ subject, text } for mail, { content } for a message). The card stays waiting and nothing is sent; to edit and send in one step, call "
|
|
133
|
+
+ "approvals_approve with `edit`. Recipients cannot be edited (field_not_editable). `cockpit approvals edit`, same door.",
|
|
134
|
+
inputSchema: { id: CARD_ID, edit: EDIT.describe("The edit: { subject, text } for mail, { content } for a message.") },
|
|
135
|
+
}, async (args) => ask("approvals_edit", "PATCH", `/api/approvals/${encodeURIComponent(String(args.id))}`, { edit: args.edit }, (answer) => `Saved your edit to ${titleOf(answer)}. It is still waiting for a decision.`));
|
|
136
|
+
register("approvals_reject", {
|
|
137
|
+
title: "Reject an approval card",
|
|
138
|
+
description: "Declines a waiting card. Nothing is sent and the card is closed; a card already decided is refused with already_decided. `cockpit approvals reject`, same door.",
|
|
139
|
+
inputSchema: { id: CARD_ID },
|
|
140
|
+
}, async (args) => ask("approvals_reject", "POST", `/api/approvals/${encodeURIComponent(String(args.id))}/reject`, {}, (answer) => `Rejected ${titleOf(answer)}. Nothing was sent.`));
|
|
141
|
+
register("approvals_rules", {
|
|
142
|
+
title: "List standing approval rules",
|
|
143
|
+
description: "The person's standing rules: kinds of card that send without asking, who they cover, how often each has fired and whether it is still active. Read-only. `cockpit approvals rules`, same door.",
|
|
144
|
+
inputSchema: {
|
|
145
|
+
active: z.boolean().optional().describe("true for rules still in force, false for revoked ones. Omit for both."),
|
|
146
|
+
limit: z.number().int().min(1).max(100).optional().describe("How many rules, 1 to 100. Default 50."),
|
|
147
|
+
},
|
|
148
|
+
}, async (args) => {
|
|
149
|
+
const query = new URLSearchParams();
|
|
150
|
+
if (typeof args.active === "boolean")
|
|
151
|
+
query.set("active", String(args.active));
|
|
152
|
+
if (typeof args.limit === "number")
|
|
153
|
+
query.set("limit", String(args.limit));
|
|
154
|
+
return ask("approvals_rules", "GET", `/api/approvals/rules${queryString(query)}`, undefined, (answer) => `${String(answer.returned ?? 0)} standing rule(s).`);
|
|
155
|
+
});
|
|
156
|
+
register("approvals_rules_create", {
|
|
157
|
+
title: "Create a standing approval rule",
|
|
158
|
+
description: "Turns a card the person approved into a standing rule: from now on this kind of card sends WITHOUT asking, so confirm must be true. `audience` same (default) covers the same people; "
|
|
159
|
+
+ "any covers every audience of this kind. Refused unless the person approved the card themselves (card_not_approved) and when a rule already exists (rule_exists). `cockpit approvals rules create`, same door.",
|
|
160
|
+
inputSchema: {
|
|
161
|
+
card_id: z.string().uuid().describe("The id of a card the person approved."),
|
|
162
|
+
audience: z.enum(["same", "any"]).optional().describe("same (default): the same people. any: every audience of this kind."),
|
|
163
|
+
label: z.string().trim().min(1).max(400).optional().describe("A name for the rule, shown in the rules list."),
|
|
164
|
+
confirm: CONFIRM_INPUT,
|
|
165
|
+
},
|
|
166
|
+
}, async (args) => {
|
|
167
|
+
const refusal = unconfirmed(args, "A standing rule makes this kind of card send without asking from now on.");
|
|
168
|
+
if (refusal)
|
|
169
|
+
return refusal;
|
|
170
|
+
const { confirm: _confirm, ...body } = args;
|
|
171
|
+
return ask("approvals_rules_create", "POST", "/api/approvals/rules", body, (answer) => {
|
|
172
|
+
const rule = answer.rule;
|
|
173
|
+
return `Standing rule created: ${typeof rule?.label === "string" ? rule.label : "see the rule"}.`;
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
register("approvals_rules_revoke", {
|
|
177
|
+
title: "Revoke a standing approval rule",
|
|
178
|
+
description: "Takes a standing rule back: matching cards wait for a decision again. The rule stays in the list as revoked, with the reason. Confirm must be true. "
|
|
179
|
+
+ "`cockpit approvals rules revoke`, same door.",
|
|
180
|
+
inputSchema: {
|
|
181
|
+
id: z.string().uuid().describe("The rule id, from approvals_rules."),
|
|
182
|
+
reason: z.string().trim().min(1).max(300).optional().describe("Why the rule is taken back."),
|
|
183
|
+
confirm: CONFIRM_INPUT,
|
|
184
|
+
},
|
|
185
|
+
}, async (args) => {
|
|
186
|
+
const refusal = unconfirmed(args, "Revoking a standing rule changes what Tower sends without asking.");
|
|
187
|
+
if (refusal)
|
|
188
|
+
return refusal;
|
|
189
|
+
return ask("approvals_rules_revoke", "POST", `/api/approvals/rules/${encodeURIComponent(String(args.id))}/revoke`, args.reason ? { reason: args.reason } : {}, () => "Standing rule revoked.");
|
|
190
|
+
});
|
|
191
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `memory_chain` (BLI-13376) - the MCP twin of `cockpit memory chain <id>`.
|
|
3
|
+
*
|
|
4
|
+
* Same door, same device token: `GET /api/memory/chain?id=<memory id>`. For a
|
|
5
|
+
* memory the nightly consolidation wrote, it lists the memories that were
|
|
6
|
+
* folded into it (date, project, text, who saved it). The door applies the
|
|
7
|
+
* privacy rule: a private original shows only to its owner or a super_admin,
|
|
8
|
+
* and the rest are counted, never shown. Read-only.
|
|
9
|
+
*/
|
|
10
|
+
import { type ToolDeps } from "./tool-result.js";
|
|
11
|
+
export declare function registerMemoryChainTools(server: {
|
|
12
|
+
registerTool: (...args: never[]) => unknown;
|
|
13
|
+
}, deps: ToolDeps): void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `memory_chain` (BLI-13376) - the MCP twin of `cockpit memory chain <id>`.
|
|
3
|
+
*
|
|
4
|
+
* Same door, same device token: `GET /api/memory/chain?id=<memory id>`. For a
|
|
5
|
+
* memory the nightly consolidation wrote, it lists the memories that were
|
|
6
|
+
* folded into it (date, project, text, who saved it). The door applies the
|
|
7
|
+
* privacy rule: a private original shows only to its owner or a super_admin,
|
|
8
|
+
* and the rest are counted, never shown. Read-only.
|
|
9
|
+
*/
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { callAgentDoor } from "./agent-door.js";
|
|
12
|
+
import { doorFailureText, errorResult, queryString, registrarFor, textResult, withSession } from "./tool-result.js";
|
|
13
|
+
export function registerMemoryChainTools(server, deps) {
|
|
14
|
+
registrarFor(server)("memory_chain", {
|
|
15
|
+
title: "Read the originals behind a consolidated memory",
|
|
16
|
+
description: "For a memory the nightly consolidation wrote (it says 'from N memories by Name, Name' in search and on the Memory page), the memories that were folded into it: date, project, text and who saved each. A private original is shown only to its owner or a super_admin; the rest are counted ('1 private original not shown'). An id the night never wrote answers consolidated=false. Read-only.",
|
|
17
|
+
inputSchema: { id: z.string().min(1).describe("The memory id from search_memory, memory_rules or memory_knowledge.") },
|
|
18
|
+
}, async (args) => withSession(deps, async (session) => {
|
|
19
|
+
const params = new URLSearchParams();
|
|
20
|
+
params.set("id", String(args.id));
|
|
21
|
+
const response = await callAgentDoor(session, deps.fetchImpl, "GET", `/api/memory/chain${queryString(params)}`);
|
|
22
|
+
if (!response.ok)
|
|
23
|
+
return errorResult(doorFailureText("memory_chain", response));
|
|
24
|
+
const body = response.body;
|
|
25
|
+
if (!body.consolidated) {
|
|
26
|
+
return textResult(`Memory ${String(args.id)} was not written by the nightly consolidation, so it has no originals.`, response.body);
|
|
27
|
+
}
|
|
28
|
+
const originals = body.originals ?? [];
|
|
29
|
+
const count = body.count ?? originals.length;
|
|
30
|
+
const lines = [`Memory ${String(args.id)} is from ${count} ${count === 1 ? "memory" : "memories"}${(body.people ?? []).length ? ` by ${(body.people ?? []).join(", ")}` : ""}.`];
|
|
31
|
+
for (const original of originals) {
|
|
32
|
+
lines.push(`[${(original.createdAt ?? "").slice(0, 10)}]${original.project ? ` [${original.project}]` : ""}${original.savedBy ? ` ${original.savedBy}:` : ""} ${original.text ?? ""}`);
|
|
33
|
+
}
|
|
34
|
+
const hiddenPrivate = body.hiddenPrivate ?? 0;
|
|
35
|
+
if (hiddenPrivate > 0)
|
|
36
|
+
lines.push(`${hiddenPrivate} private ${hiddenPrivate === 1 ? "original" : "originals"} not shown.`);
|
|
37
|
+
const gone = (body.hiddenForgotten ?? 0) + (body.missing ?? 0);
|
|
38
|
+
if (gone > 0)
|
|
39
|
+
lines.push(`${gone} ${gone === 1 ? "original is" : "originals are"} no longer available.`);
|
|
40
|
+
if (body.truncated)
|
|
41
|
+
lines.push("Only the first originals are listed.");
|
|
42
|
+
return textResult(lines.join("\n"), response.body);
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
@@ -23,6 +23,17 @@ function counts(value) {
|
|
|
23
23
|
const entries = Object.entries(value);
|
|
24
24
|
return entries.length === 0 ? "none" : entries.map(([key, count]) => `${key} ${String(count)}`).join(", ");
|
|
25
25
|
}
|
|
26
|
+
/** One scope of last night, as the door computed it (BLI-13376). */
|
|
27
|
+
function scopeLine(scope) {
|
|
28
|
+
const refused = Array.isArray(scope.topRefused)
|
|
29
|
+
? scope.topRefused.map((item) => `${String(item.label)} ${String(item.count)}`).join(", ")
|
|
30
|
+
: "";
|
|
31
|
+
const cost = typeof scope.costUsd === "number" ? `$${scope.costUsd.toFixed(4)}` : "$?";
|
|
32
|
+
return `${String(scope.label ?? scope.containerTag ?? "?")} ${String(scope.mode ?? "?")} clusters ${String(scope.clusters ?? 0)}; `
|
|
33
|
+
+ `merged ${String(scope.merged ?? 0)}; rewritten ${String(scope.rewritten ?? 0)}; corrected ${String(scope.corrected ?? 0)}; `
|
|
34
|
+
+ `kept ${String(scope.kept ?? 0)}; refused ${String(scope.refused ?? 0)}${refused ? ` (${refused})` : ""}; `
|
|
35
|
+
+ `cross-person ${String(scope.crossPersonClusters ?? 0)}; ${cost}`;
|
|
36
|
+
}
|
|
26
37
|
function runLine(run) {
|
|
27
38
|
const cost = typeof run.costUsd === "number" ? `$${run.costUsd.toFixed(4)}` : "$?";
|
|
28
39
|
return `${String(run.startedAt ?? "?")} ${String(run.containerTag ?? "?")} ${String(run.mode ?? "?")} `
|
|
@@ -33,7 +44,7 @@ function runLine(run) {
|
|
|
33
44
|
export function registerMemoryConsolidationTools(server, deps) {
|
|
34
45
|
registrarFor(server)("memory_consolidation", {
|
|
35
46
|
title: "Read the nightly memory consolidation runs",
|
|
36
|
-
description: "What the nightly BLI Memory consolidation pass did: clusters,
|
|
47
|
+
description: "What the nightly BLI Memory consolidation pass did, LAST NIGHT first: a one-line night total (runs, finished vs unfinished, cost) and per container or scope the clusters, merged, rewritten, corrected, kept, refused (with top labels), cross-person clusters and cost; the structured half is the full door body plus a computed `night` object. With a container tag it answers that space's recent runs; without one, the newest run per container. Read-only — the pass never deletes a memory and this tool never starts one. An empty list means no run is visible to this account (the receipts are super_admin-readable), not an outage.",
|
|
37
48
|
inputSchema: {
|
|
38
49
|
container: z
|
|
39
50
|
.string()
|
|
@@ -63,6 +74,13 @@ export function registerMemoryConsolidationTools(server, deps) {
|
|
|
63
74
|
// One line per run in the text half. It used to carry only the count,
|
|
64
75
|
// so a Claude-family client never saw a run (BLI-10432).
|
|
65
76
|
const lines = runs.map(runLine).join("\n");
|
|
77
|
+
// Last night first (BLI-13376): the door's one-line total, then a line per scope.
|
|
78
|
+
const nightLine = typeof response.body.nightLine === "string" ? response.body.nightLine : null;
|
|
79
|
+
const scopes = (Array.isArray(response.body.nightScopes) ? response.body.nightScopes : []);
|
|
80
|
+
if (nightLine) {
|
|
81
|
+
return textResult(`${nightLine}\n${scopes.map(scopeLine).join("\n")}\n`
|
|
82
|
+
+ "merged/rewritten/corrected are what an apply run wrote or what a report run only proposed (mode); kept is always a proposal; nothing is ever deleted.", response.body);
|
|
83
|
+
}
|
|
66
84
|
return textResult(runs.length === 0
|
|
67
85
|
? `No consolidation runs are visible (${reason}). The pass is a nightly cron and its receipts are readable by a super_admin; this is the access rule answering, not an outage.`
|
|
68
86
|
: `${runs.length} consolidation run${runs.length === 1 ? "" : "s"}. report = proposals only, nothing on the shelf changed; apply = accepted edits written as supersessions, which never delete anything.`
|
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { callAgentDoor } from "./agent-door.js";
|
|
3
3
|
import { doorFailureText, errorResult, queryString, registrarFor, textResult, withSession } from "./tool-result.js";
|
|
4
|
+
/** The plain sentence, or the machine wording marked as the original while no plain one is written yet. Same rule as `cockpit memory`. */
|
|
5
|
+
function sentence(idea) {
|
|
6
|
+
const c = idea.consolidated;
|
|
7
|
+
// "(from 4 memories by Edward, Vee)" marks a memory the nightly pass wrote (BLI-13376).
|
|
8
|
+
const origin = c ? ` (from ${c.count} ${c.count === 1 ? "memory" : "memories"}${c.people.length ? ` by ${c.people.join(", ")}` : ""})` : "";
|
|
9
|
+
return (idea.human_text ? idea.human_text : `${idea.text} (original wording)`) + origin;
|
|
10
|
+
}
|
|
11
|
+
/** The text half of the answer: the same header words the page and the CLI print, then each project's ideas as plain sentences. */
|
|
12
|
+
function answerText(body, action) {
|
|
13
|
+
const lines = [body.words?.summary, body.words?.ranking, body.freshness?.words, ...(body.notes ?? [])].filter((line) => Boolean(line));
|
|
14
|
+
if (body.projectGroups?.length)
|
|
15
|
+
for (const group of body.projectGroups) {
|
|
16
|
+
lines.push("", `${group.project}, ${group.count.toLocaleString("en-US")} memories`);
|
|
17
|
+
const topics = group.topics ?? [];
|
|
18
|
+
const headed = Boolean(group.ranked) && topics.length > 1;
|
|
19
|
+
if (topics.length)
|
|
20
|
+
for (const topic of topics)
|
|
21
|
+
lines.push(...(headed ? [` ${topic.title}`] : []), ...topic.memories.map(idea => `${headed ? " " : " "}${sentence(idea)}`));
|
|
22
|
+
else
|
|
23
|
+
lines.push(...(group.memories ?? []).map(idea => ` ${sentence(idea)}`));
|
|
24
|
+
}
|
|
25
|
+
else
|
|
26
|
+
for (const group of body.groups ?? []) {
|
|
27
|
+
lines.push(group.owner, ...group.rules.map(rule => ` [${rule.created_at.slice(0, 10)}] ${rule.project ? `[${rule.project}] ` : ""}${sentence(rule)}`));
|
|
28
|
+
}
|
|
29
|
+
if (!body.returned)
|
|
30
|
+
lines.push(`No ${action === "rules" ? "ground rules" : "project knowledge"} matched.`);
|
|
31
|
+
if (body.capped)
|
|
32
|
+
lines.push("Result capped; narrow by person or project, or raise limit (max 1000) or per_project (max 100).");
|
|
33
|
+
return lines.join("\n");
|
|
34
|
+
}
|
|
4
35
|
export function registerMemoryRulesTools(server, deps) {
|
|
5
36
|
registrarFor(server)("memory_map", {
|
|
6
37
|
title: "Read the memory map",
|
|
@@ -18,25 +49,21 @@ export function registerMemoryRulesTools(server, deps) {
|
|
|
18
49
|
for (const action of ["rules", "knowledge"])
|
|
19
50
|
registrarFor(server)(`memory_${action}`, {
|
|
20
51
|
title: `Read BLI agent ${action}`,
|
|
21
|
-
description: `Memories grouped by project
|
|
22
|
-
inputSchema: {
|
|
52
|
+
description: `Memories grouped by project, each project with its own quota of ideas, ranked by importance, repetition, then recency. The text is plain header words (what the list covers, how it is ordered, how fresh it is, any problems) then one plain sentence per idea; quote those sentences, never ids or scores. structuredContent is the whole answer, with both wordings per idea: human_text for people, text for agents. Reads only memories visible to you, over the same door as cockpit memory ${action}. Default 15 ideas per project, 1000 in all; capped means narrow the filters or raise a bound.`,
|
|
53
|
+
inputSchema: {
|
|
54
|
+
person: z.string().min(1).optional().describe("Exact roster name, or unattributed for memories with no author."),
|
|
55
|
+
project: z.string().min(1).optional().describe("Case-insensitive project name or canonical slug, for example Pearl Portal or pearl-portal."),
|
|
56
|
+
limit: z.number().int().min(1).max(1000).optional().describe("Maximum ideas in all, default 1000, maximum 1000."),
|
|
57
|
+
per_project: z.number().int().min(1).max(100).optional().describe("Ideas shown from each project, default 15, maximum 100."),
|
|
58
|
+
},
|
|
23
59
|
}, async (args) => withSession(deps, async (session) => {
|
|
24
60
|
const params = new URLSearchParams();
|
|
25
|
-
for (const key of ["person", "project", "limit"])
|
|
61
|
+
for (const key of ["person", "project", "limit", "per_project"])
|
|
26
62
|
if (args[key] !== undefined)
|
|
27
63
|
params.set(key, String(args[key]));
|
|
28
64
|
const response = await callAgentDoor(session, deps.fetchImpl, "GET", `/api/memory/${action}${queryString(params)}`);
|
|
29
65
|
if (!response.ok)
|
|
30
66
|
return errorResult(doorFailureText(`memory_${action}`, response));
|
|
31
|
-
|
|
32
|
-
const lines = body.projectGroups ? body.projectGroups.flatMap(group => [
|
|
33
|
-
`${group.project} (${group.count}): ${group.headline.split("\n")[0]}`,
|
|
34
|
-
...group.topics.flatMap(topic => [topic.title, ...topic.memories.map(memory => `${memory.text}${memory.repetition > 1 ? ` (said ${memory.repetition} times)` : ""}${memory.importance !== null ? ` [importance ${memory.importance}/100]` : ""}`)]),
|
|
35
|
-
]) : (body.groups ?? []).flatMap((group) => [group.owner, ...group.rules.map((rule) => `[${rule.created_at.slice(0, 10)}] ${rule.project ? `[${rule.project}] ` : ""}${rule.text}${group.owner === "unattributed" ? ` (space: ${rule.container})` : ""}`)]);
|
|
36
|
-
if (!lines.length)
|
|
37
|
-
lines.push(`No ${action === "rules" ? "ground rules" : "project knowledge"} matched.`);
|
|
38
|
-
if (body.capped)
|
|
39
|
-
lines.push("Result capped; narrow by person/project or increase limit (max 1000).");
|
|
40
|
-
return textResult(lines.join("\n"), response.body);
|
|
67
|
+
return textResult(answerText(response.body, action), response.body);
|
|
41
68
|
}));
|
|
42
69
|
}
|
package/dist/server.js
CHANGED
|
@@ -20,6 +20,7 @@ import { registerNotesTools } from "./notes-tools.js";
|
|
|
20
20
|
import { registerNotesBotUpcomingTool } from "./notes-bot-upcoming-tool.js";
|
|
21
21
|
import { registerNotesGranolaStatusTool } from "./notes-granola-status-tool.js";
|
|
22
22
|
import { registerNotesWriteTools } from "./notes-write-tools.js";
|
|
23
|
+
import { registerMemoryChainTools } from "./memory-chain-tools.js";
|
|
23
24
|
import { registerMemoryConsolidationTools } from "./memory-consolidation-tools.js";
|
|
24
25
|
import { registerMemoryExperienceTools } from "./memory-experience-tools.js";
|
|
25
26
|
import { registerMemoryRulesTools } from "./memory-rules-tools.js";
|
|
@@ -41,6 +42,7 @@ import { registerSpeedTools } from "./speed-tools.js";
|
|
|
41
42
|
import { registerUsageTools } from "./usage-tools.js";
|
|
42
43
|
import { registerLogsTools } from "./logs-tools.js";
|
|
43
44
|
import { registerBrainTools } from "./brain-tools.js";
|
|
45
|
+
import { registerApprovalsTools } from "./approvals-tools.js";
|
|
44
46
|
import { registerSourcesTools } from "./sources-tools.js";
|
|
45
47
|
import { TOWER_SERVER_ICONS } from "./server-icons.js";
|
|
46
48
|
import { installToolRegistration } from "./tool-registration.js";
|
|
@@ -487,6 +489,8 @@ export function createServer(deps, options = {}) {
|
|
|
487
489
|
// BLI-4534: the nightly consolidation receipts, twin of `cockpit memory
|
|
488
490
|
// consolidation`. Read-only, and the pass it reports on never deletes.
|
|
489
491
|
registerMemoryConsolidationTools(server, { fetchImpl: deps.fetchImpl });
|
|
492
|
+
// BLI-13376: the originals behind a consolidated memory, twin of `cockpit memory chain`.
|
|
493
|
+
registerMemoryChainTools(server, { fetchImpl: deps.fetchImpl });
|
|
490
494
|
// BLI-4539: putting a window of refused extractions back, the twin of
|
|
491
495
|
// `cockpit memory requeue`. Dry run by default, super_admin at the door.
|
|
492
496
|
registerMemoryRequeueTools(server, { fetchImpl: deps.fetchImpl });
|
|
@@ -519,5 +523,7 @@ export function createServer(deps, options = {}) {
|
|
|
519
523
|
// BLI-12859: Tower's incident queue (one incident, one issue, per recurring log problem), over
|
|
520
524
|
// the same GET /api/brain/incidents door `cockpit brain status` reads. Read-only.
|
|
521
525
|
registerBrainTools(server, { fetchImpl: deps.fetchImpl });
|
|
526
|
+
// BLI-16945: the approvals inbox, over the same /api/approvals doors the page and `cockpit approvals` call.
|
|
527
|
+
registerApprovalsTools(server, { fetchImpl: deps.fetchImpl });
|
|
522
528
|
return server;
|
|
523
529
|
}
|
|
@@ -17,7 +17,7 @@ export interface TowerToolAnnotations {
|
|
|
17
17
|
* Writes a person cannot take back by writing again: deletes, revokes,
|
|
18
18
|
* detaches and a merge. These also carry `destructiveHint: true`.
|
|
19
19
|
*/
|
|
20
|
-
export declare const DESTRUCTIVE_TOOLS: readonly ["docs_delete", "cal_delete", "cal_detach", "mail_detach", "notes_detach", "notes_rmdir", "glossary_remove", "settings_delete", "team_device_revoke", "github_merge"];
|
|
20
|
+
export declare const DESTRUCTIVE_TOOLS: readonly ["docs_delete", "cal_delete", "cal_detach", "mail_detach", "notes_detach", "notes_rmdir", "glossary_remove", "settings_delete", "team_device_revoke", "github_merge", "approvals_rules_revoke"];
|
|
21
21
|
/**
|
|
22
22
|
* Tools the HOSTED server leaves out, each with the reason. A hosted process
|
|
23
23
|
* is one machine answering many people: a tool that reads that machine's
|
package/dist/tool-annotations.js
CHANGED
package/dist/tool-catalog.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* from `src/` under Node's type stripping, so it may import nothing.
|
|
11
11
|
*/
|
|
12
12
|
/** Tools that change nothing. The smoke calls every one. */
|
|
13
|
-
export declare const READ_TOOLS: readonly ["get_ticket_timeline", "get_active_tickets", "docs_list", "docs_tree", "docs_read", "docs_sync_status", "msg_channels", "msg_read", "msg_thread", "work_board", "work_list_issues", "work_get_issue", "work_issue_history", "work_list_projects", "careers_list", "careers_show", "careers_resume_url", "careers_takehome_show", "careers_settings_show", "rename_pending", "github_pr", "setup_read", "speed_read", "speed_seen", "usage_people", "usage_sessions", "mail_accounts", "mail_inbox", "mail_read", "mail_search", "mail_attachment", "mail_sync_status", "cal_today", "cal_week", "cal_next", "cal_find", "cal_calendars", "cal_sync_status", "jarvis_ask", "jarvis_trace", "jarvis_check", "models_list", "models_card", "models_compare", "brief_read", "brief_history", "brief_status", "brief_missed", "notes_folders", "notes_list", "glossary_list", "notes_show", "notes_shelf", "notes_shelves", "notes_place", "notes_accounts", "notes_recordings", "notes_recording", "notes_sync_status", "notes_granola_status", "notes_bot_upcoming", "ops_status", "slack_coverage", "slack_connect", "slack_disconnect", "slack_read", "memory_map", "memory_rules", "memory_knowledge", "memory_profile", "memory_consolidation", "settings_show", "settings_list", "model_show", "team_members", "team_github_login_add", "team_github_login_remove", "team_github_login_list", "team_device_list", "scout_board", "workbook_read", "tower_search", "sources_check", "logs_digest", "brain_status"];
|
|
13
|
+
export declare const READ_TOOLS: readonly ["get_ticket_timeline", "get_active_tickets", "docs_list", "docs_tree", "docs_read", "docs_sync_status", "msg_channels", "msg_read", "msg_thread", "work_board", "work_list_issues", "work_get_issue", "work_issue_history", "work_list_projects", "careers_list", "careers_show", "careers_resume_url", "careers_takehome_show", "careers_settings_show", "rename_pending", "github_pr", "setup_read", "speed_read", "speed_seen", "usage_people", "usage_sessions", "mail_accounts", "mail_inbox", "mail_read", "mail_search", "mail_attachment", "mail_sync_status", "cal_today", "cal_week", "cal_next", "cal_find", "cal_calendars", "cal_sync_status", "jarvis_ask", "jarvis_trace", "jarvis_check", "models_list", "models_card", "models_compare", "brief_read", "brief_history", "brief_status", "brief_missed", "notes_folders", "notes_list", "glossary_list", "notes_show", "notes_shelf", "notes_shelves", "notes_place", "notes_accounts", "notes_recordings", "notes_recording", "notes_sync_status", "notes_granola_status", "notes_bot_upcoming", "ops_status", "slack_coverage", "slack_connect", "slack_disconnect", "slack_read", "memory_map", "memory_rules", "memory_knowledge", "memory_profile", "memory_consolidation", "memory_chain", "settings_show", "settings_list", "model_show", "team_members", "team_github_login_add", "team_github_login_remove", "team_github_login_list", "team_device_list", "scout_board", "workbook_read", "tower_search", "sources_check", "logs_digest", "brain_status", "approvals_list", "approvals_show", "approvals_rules"];
|
|
14
14
|
/**
|
|
15
15
|
* Tools that write, but answer a dry run that writes nothing. The smoke calls
|
|
16
16
|
* each one ONLY with its dry-run flag set (`scripts/smoke.ts` says which).
|
|
@@ -21,7 +21,7 @@ export declare const DRY_RUN_TOOLS: readonly ["ops_recompile", "memory_requeue"]
|
|
|
21
21
|
* candidates, share calendars. Never called by the smoke; covered by the unit
|
|
22
22
|
* suites (`write-tools.test.ts` and each family's own).
|
|
23
23
|
*/
|
|
24
|
-
export declare const WRITE_TOOLS: readonly ["logs_key_mint", "brain_retry", "emit_event", "docs_create", "docs_update", "docs_delete", "docs_link", "msg_create_channel", "msg_dm", "msg_send", "work_create_issue", "work_update_issue", "work_move_issue", "work_comment_issue", "careers_rescreen", "careers_send_takehome", "careers_invite", "careers_grade", "careers_decide", "careers_followup", "careers_interview", "careers_takehome_set", "careers_settings_set", "rename_resolve", "github_merge", "github_retitle", "github_rerun", "mail_send", "mail_draft", "mail_rename", "mail_enable", "mail_disable", "mail_default", "mail_remove", "cal_change", "mail_sync", "mail_detach", "cal_create", "cal_share", "cal_sync", "cal_detach", "cal_delete", "jarvis_dispatch", "glossary_add", "glossary_remove", "notes_mkdir", "notes_rmdir", "notes_rename", "notes_upload", "notes_paste", "notes_share", "notes_unshare", "notes_move", "notes_sync", "notes_detach", "memory_experience", "scout_start", "scout_dismiss", "scout_undo", "brief_rewrite", "brief_correct", "brief_done", "brief_undone", "settings_set", "settings_delete", "model_set", "team_invite", "team_role", "team_grant", "team_revoke", "team_device_revoke", "team_device_doctor"];
|
|
24
|
+
export declare const WRITE_TOOLS: readonly ["approvals_create", "approvals_approve", "approvals_edit", "approvals_reject", "approvals_rules_create", "approvals_rules_revoke", "logs_key_mint", "brain_retry", "emit_event", "docs_create", "docs_update", "docs_delete", "docs_link", "msg_create_channel", "msg_dm", "msg_send", "work_create_issue", "work_update_issue", "work_move_issue", "work_comment_issue", "careers_rescreen", "careers_send_takehome", "careers_invite", "careers_grade", "careers_decide", "careers_followup", "careers_interview", "careers_takehome_set", "careers_settings_set", "rename_resolve", "github_merge", "github_retitle", "github_rerun", "mail_send", "mail_draft", "mail_rename", "mail_enable", "mail_disable", "mail_default", "mail_remove", "cal_change", "mail_sync", "mail_detach", "cal_create", "cal_share", "cal_sync", "cal_detach", "cal_delete", "jarvis_dispatch", "glossary_add", "glossary_remove", "notes_mkdir", "notes_rmdir", "notes_rename", "notes_upload", "notes_paste", "notes_share", "notes_unshare", "notes_move", "notes_sync", "notes_detach", "memory_experience", "scout_start", "scout_dismiss", "scout_undo", "brief_rewrite", "brief_correct", "brief_done", "brief_undone", "settings_set", "settings_delete", "model_set", "team_invite", "team_role", "team_grant", "team_revoke", "team_device_revoke", "team_device_doctor"];
|
|
25
25
|
/** Reads that spend a model turn. The smoke's `--no-model` leaves these out. */
|
|
26
26
|
export declare const MODEL_SPENDING_TOOLS: readonly ["jarvis_ask", "jarvis_check"];
|
|
27
27
|
/**
|
package/dist/tool-catalog.js
CHANGED
|
@@ -83,6 +83,8 @@ export const READ_TOOLS = [
|
|
|
83
83
|
"memory_knowledge",
|
|
84
84
|
"memory_profile",
|
|
85
85
|
"memory_consolidation",
|
|
86
|
+
// BLI-13376: a read of the originals behind a consolidated memory.
|
|
87
|
+
"memory_chain",
|
|
86
88
|
"settings_show",
|
|
87
89
|
"settings_list",
|
|
88
90
|
"model_show",
|
|
@@ -99,6 +101,10 @@ export const READ_TOOLS = [
|
|
|
99
101
|
"logs_digest",
|
|
100
102
|
// BLI-12859: Tower's incident queue, per project. Reads only.
|
|
101
103
|
"brain_status",
|
|
104
|
+
// BLI-16945: the approvals inbox and its standing rules. Reads only.
|
|
105
|
+
"approvals_list",
|
|
106
|
+
"approvals_show",
|
|
107
|
+
"approvals_rules",
|
|
102
108
|
];
|
|
103
109
|
/**
|
|
104
110
|
* Tools that write, but answer a dry run that writes nothing. The smoke calls
|
|
@@ -111,6 +117,13 @@ export const DRY_RUN_TOOLS = ["ops_recompile", "memory_requeue"];
|
|
|
111
117
|
* suites (`write-tools.test.ts` and each family's own).
|
|
112
118
|
*/
|
|
113
119
|
export const WRITE_TOOLS = [
|
|
120
|
+
// BLI-16945: approve runs the guarded act; a rule sends without asking. Never smoked.
|
|
121
|
+
"approvals_create",
|
|
122
|
+
"approvals_approve",
|
|
123
|
+
"approvals_edit",
|
|
124
|
+
"approvals_reject",
|
|
125
|
+
"approvals_rules_create",
|
|
126
|
+
"approvals_rules_revoke",
|
|
114
127
|
"logs_key_mint",
|
|
115
128
|
"brain_retry",
|
|
116
129
|
"emit_event",
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
export const TYPED_SPELLING = {
|
|
23
23
|
// The action is `invite`; the button, the help wall and the docs all say
|
|
24
24
|
// send-takehome, so that is what a stranger is told to type.
|
|
25
|
+
"approvals rules-create": "approvals rules create",
|
|
26
|
+
"approvals rules-revoke": "approvals rules revoke",
|
|
25
27
|
"careers invite": "careers send-takehome",
|
|
26
28
|
"careers takehome-show": "careers takehome show",
|
|
27
29
|
"careers takehome-set": "careers takehome set",
|
package/dist/verb-census.js
CHANGED
|
@@ -244,6 +244,15 @@ export const MCP_TWINS = {
|
|
|
244
244
|
"logs": { tool: "logs_digest", door: "LOCAL @bli-cockpit/telemetry-core/log-digest (this machine's logs); `project` reads GET /api/logs (the stored digests)" },
|
|
245
245
|
"brain retry": { tool: "brain_retry", door: "POST /api/brain/incidents/retry" },
|
|
246
246
|
"brain status": { tool: "brain_status", door: "GET /api/brain/incidents" },
|
|
247
|
+
"approvals list": { tool: "approvals_list", door: "GET /api/approvals" },
|
|
248
|
+
"approvals show": { tool: "approvals_show", door: "GET /api/approvals/[id]" },
|
|
249
|
+
"approvals create": { tool: "approvals_create", door: "POST /api/approvals" },
|
|
250
|
+
"approvals approve": { tool: "approvals_approve", door: "POST /api/approvals/[id]/approve" },
|
|
251
|
+
"approvals edit": { tool: "approvals_edit", door: "PATCH /api/approvals/[id]" },
|
|
252
|
+
"approvals reject": { tool: "approvals_reject", door: "POST /api/approvals/[id]/reject" },
|
|
253
|
+
"approvals rules": { tool: "approvals_rules", door: "GET /api/approvals/rules" },
|
|
254
|
+
"approvals rules-create": { tool: "approvals_rules_create", door: "POST /api/approvals/rules" },
|
|
255
|
+
"approvals rules-revoke": { tool: "approvals_rules_revoke", door: "POST /api/approvals/rules/[id]/revoke" },
|
|
247
256
|
"usage sessions": { tool: "usage_sessions", door: "GET /api/usage/sessions" },
|
|
248
257
|
"setup": { tool: "setup_read", door: "GET /api/setup" },
|
|
249
258
|
// BLI-13033: times the data door of every rail page; the twin runs the same GETs.
|
|
@@ -354,6 +363,9 @@ export const MCP_TWINS = {
|
|
|
354
363
|
// `bli-tower` rather than `bli-memory`, because it is a read of a JOB s
|
|
355
364
|
// counts and not a read or write of the store itself.
|
|
356
365
|
"memory consolidation": { tool: "memory_consolidation", door: "GET /api/memory/consolidation" },
|
|
366
|
+
// BLI-13376: the originals behind a consolidated memory. Also on `bli-tower`:
|
|
367
|
+
// the door filters private and forgotten originals before anything leaves.
|
|
368
|
+
"memory chain": { tool: "memory_chain", door: "GET /api/memory/chain" },
|
|
357
369
|
// BLI-4539: the recovery verb, after a window of extractions whose writes
|
|
358
370
|
// the store refused. Also on `bli-tower`, for the same reason
|
|
359
371
|
// `memory profile` is: it is ours, not the vendor's four.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.116",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "bli-tower: an MCP server over BLI Cockpit's agent doors: JARVIS (jarvis_*), documents (docs_*), channels (msg_*), issues (work_*), the daily page (brief_*), meeting notes (notes_*), the ops board (ops_status/slack_*), settings/team/model, Scout and the workbook, plus the legacy event-stream tools (emit_event, get_ticket_timeline, get_active_tickets).",
|
|
6
6
|
"type": "module",
|