bantamkit-mcp 0.26.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/LICENSE +21 -0
- package/README.md +398 -0
- package/assets/contracts/default.yaml +90 -0
- package/assets/evals/devteam/manifest.yaml +351 -0
- package/assets/evals/devteam/repo/HISTORY.md +18 -0
- package/assets/evals/devteam/repo/README.md +12 -0
- package/assets/evals/devteam/repo/docs/architecture.md +17 -0
- package/assets/evals/devteam/repo/docs/runbook.md +10 -0
- package/assets/evals/devteam/repo/issues/142-settlement-timeout.md +23 -0
- package/assets/evals/devteam/repo/patches/0009-retry-budget.patch +38 -0
- package/assets/evals/devteam/repo/src/ledger/__init__.py +3 -0
- package/assets/evals/devteam/repo/src/ledger/config.py +35 -0
- package/assets/evals/devteam/repo/src/ledger/errors.py +13 -0
- package/assets/evals/devteam/repo/src/ledger/posting.py +12 -0
- package/assets/evals/devteam/repo/src/ledger/registry.py +7 -0
- package/assets/evals/devteam/repo/src/ledger/report.py +9 -0
- package/assets/evals/devteam/repo/src/ledger/retry.py +17 -0
- package/assets/evals/devteam/repo/src/ledger/settle.py +16 -0
- package/assets/evals/devteam/repo/src/ledger/validate.py +14 -0
- package/assets/evals/devteam/repo/tests/test_posting.py +13 -0
- package/assets/evals/devteam/repo/tests/test_settle.py +9 -0
- package/assets/evals/devteam/tasks/dt-error-contract.yaml +186 -0
- package/assets/evals/devteam/tasks/dt-handler-map.yaml +183 -0
- package/assets/evals/devteam/tasks/dt-patch-before-after.yaml +182 -0
- package/assets/evals/devteam/tasks/dt-retry-attempts.yaml +181 -0
- package/assets/evals/devteam/tasks/dt-settlement-config.yaml +185 -0
- package/assets/evals/devteam/tasks/dt-symbol-home.yaml +181 -0
- package/assets/evals/devteam/tasks/dt-trace-blame.yaml +182 -0
- package/assets/evals/devteam/tasks/dt-unread-key.yaml +180 -0
- package/assets/evals/document/tasks/doc-large-in-137.yaml +38 -0
- package/assets/evals/document/tasks/doc-large-in-359.yaml +44 -0
- package/assets/evals/document/tasks/doc-large-in-372.yaml +38 -0
- package/assets/evals/document/tasks/doc-large-out-11764.yaml +37 -0
- package/assets/evals/document/tasks/doc-large-out-4137.yaml +37 -0
- package/assets/evals/document/tasks/doc-large-out-8022.yaml +37 -0
- package/assets/evals/document/tasks/doc-small-137.yaml +37 -0
- package/assets/evals/document/tasks/doc-small-261.yaml +37 -0
- package/assets/evals/document/tasks/doc-small-388.yaml +37 -0
- package/assets/evals/fixtures/.gitkeep +0 -0
- package/assets/evals/fixtures/catalog.json +6 -0
- package/assets/evals/perturbations/task-completion.yaml +576 -0
- package/assets/evals/tasks/.gitkeep +0 -0
- package/assets/evals/tasks/extract-contact.yaml +14 -0
- package/assets/evals/tasks/extract-invoice.yaml +14 -0
- package/assets/evals/tasks/extract-order.yaml +15 -0
- package/assets/evals/tasks/extract-schedule.yaml +14 -0
- package/assets/evals/tasks/extract-versions.yaml +17 -0
- package/assets/evals/tasks/nav-prod-port.yaml +84 -0
- package/assets/evals/tasks/nav-release-bundle.yaml +87 -0
- package/assets/evals/tasks/recall-audit-retention.yaml +17 -0
- package/assets/evals/tasks/recall-cache-ttl.yaml +13 -0
- package/assets/evals/tasks/recall-db-port.yaml +17 -0
- package/assets/evals/tasks/recall-deploy.yaml +13 -0
- package/assets/evals/tasks/recall-env-endpoint.yaml +18 -0
- package/assets/evals/tasks/recall-oncall-rotation.yaml +21 -0
- package/assets/evals/tasks/recall-oncall.yaml +13 -0
- package/assets/evals/tasks/recall-org-quota.yaml +18 -0
- package/assets/evals/tasks/recall-owner.yaml +13 -0
- package/assets/evals/tasks/shop-basket-total.yaml +10 -0
- package/assets/evals/tasks/shop-cheapest.yaml +9 -0
- package/assets/evals/tasks/shop-compare.yaml +9 -0
- package/assets/evals/tasks/shop-gadget-value.yaml +9 -0
- package/assets/evals/tasks/shop-stock-total.yaml +9 -0
- package/assets/evals/tasks/shop-total.yaml +9 -0
- package/assets/profiles/default.yaml +31 -0
- package/assets/profiles/patient.yaml +31 -0
- package/assets/rubrics/.gitkeep +0 -0
- package/assets/rubrics/code-quality.yaml +20 -0
- package/assets/rubrics/grounded-completion.yaml +37 -0
- package/assets/rubrics/task-completion.yaml +28 -0
- package/assets/schemas/shiftwork-checkpoint.json +188 -0
- package/assets/skills/.gitkeep +0 -0
- package/assets/skills/file-graph.md +7 -0
- package/assets/skills/memory.md +35 -0
- package/assets/tools/.gitkeep +0 -0
- package/assets/tools/bantamkit_read.json +48 -0
- package/assets/tools/bantamkit_status.json +25 -0
- package/assets/tools/build_identity.json +17 -0
- package/assets/tools/document_list.json +12 -0
- package/assets/tools/document_read.json +31 -0
- package/assets/tools/file_graph.json +12 -0
- package/assets/tools/memory_compact.json +31 -0
- package/assets/tools/memory_recall.json +38 -0
- package/assets/tools/memory_save.json +61 -0
- package/assets/tools/shiftwork_clock_in.json +25 -0
- package/assets/tools/shiftwork_clock_out.json +60 -0
- package/assets/tools/shiftwork_status.json +25 -0
- package/assets/tools/skill_audit.json +70 -0
- package/assets/tools/validate_json.json +31 -0
- package/dist/assets.d.ts +48 -0
- package/dist/assets.js +109 -0
- package/dist/charsets.d.ts +17 -0
- package/dist/charsets.js +580 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +280 -0
- package/dist/contract.d.ts +145 -0
- package/dist/contract.js +608 -0
- package/dist/docread.d.ts +357 -0
- package/dist/docread.js +3315 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +14 -0
- package/dist/eventlog.d.ts +137 -0
- package/dist/eventlog.js +294 -0
- package/dist/htmlentities.d.ts +2 -0
- package/dist/htmlentities.js +16 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +13 -0
- package/dist/mcp/identity.d.ts +25 -0
- package/dist/mcp/identity.js +275 -0
- package/dist/mcp/pyargs.d.ts +80 -0
- package/dist/mcp/pyargs.js +362 -0
- package/dist/mcp/sdkjson.d.ts +56 -0
- package/dist/mcp/sdkjson.js +91 -0
- package/dist/mcp/server.d.ts +45 -0
- package/dist/mcp/server.js +740 -0
- package/dist/mcp/status.d.ts +210 -0
- package/dist/mcp/status.js +357 -0
- package/dist/mcp/transport.d.ts +68 -0
- package/dist/mcp/transport.js +111 -0
- package/dist/mcpreport.d.ts +214 -0
- package/dist/mcpreport.js +758 -0
- package/dist/memory/cli.d.ts +2 -0
- package/dist/memory/cli.js +396 -0
- package/dist/memory/component.d.ts +256 -0
- package/dist/memory/component.js +497 -0
- package/dist/memory/factfile.d.ts +120 -0
- package/dist/memory/factfile.js +406 -0
- package/dist/memory/layers.d.ts +92 -0
- package/dist/memory/layers.js +410 -0
- package/dist/memory/pyfs.d.ts +521 -0
- package/dist/memory/pyfs.js +1608 -0
- package/dist/memory/pyyaml.d.ts +155 -0
- package/dist/memory/pyyaml.js +868 -0
- package/dist/memory/store.d.ts +478 -0
- package/dist/memory/store.js +1034 -0
- package/dist/pyargparse.d.ts +141 -0
- package/dist/pyargparse.js +813 -0
- package/dist/pyjson.d.ts +110 -0
- package/dist/pyjson.js +524 -0
- package/dist/pyjsonschema.d.ts +50 -0
- package/dist/pyjsonschema.js +951 -0
- package/dist/shiftwork.d.ts +60 -0
- package/dist/shiftwork.js +324 -0
- package/dist/skillaudit.d.ts +172 -0
- package/dist/skillaudit.js +914 -0
- package/dist/statusline.d.ts +106 -0
- package/dist/statusline.js +281 -0
- package/package.json +45 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import type { EventLog } from '../eventlog.js';
|
|
2
|
+
import { Memory } from '../memory/component.js';
|
|
3
|
+
/**
|
|
4
|
+
* The tool AND the prompt answer to this one name. Deliberately the same word: the operator
|
|
5
|
+
* who read `bantamkit_status` in a footer must find it in their prompt menu without
|
|
6
|
+
* translating, and a model that read it in a prompt must find the tool.
|
|
7
|
+
*/
|
|
8
|
+
export declare const STATUS_NAME = "bantamkit_status";
|
|
9
|
+
/** The prompt's own title, as `prompts/list` advertises it. */
|
|
10
|
+
export declare const STATUS_PROMPT_TITLE = "bantamkit status";
|
|
11
|
+
/**
|
|
12
|
+
* The prompt's description — the sentence a person reads in the host's own command menu,
|
|
13
|
+
* which is why it is addressed to them and not to a model.
|
|
14
|
+
*/
|
|
15
|
+
export declare const STATUS_PROMPT_DESCRIPTION: string;
|
|
16
|
+
/**
|
|
17
|
+
* What the prompt says after the report. The report is the payload; this is the one line
|
|
18
|
+
* that tells the model what the person wanted with it, and it asks for a RELAY rather than
|
|
19
|
+
* an interpretation — the operator invoked this to read the server's own words.
|
|
20
|
+
*/
|
|
21
|
+
export declare const STATUS_PROMPT_TAIL: string;
|
|
22
|
+
/**
|
|
23
|
+
* The prompt and resource-template counts `statusReport` prints.
|
|
24
|
+
*
|
|
25
|
+
* Named here for the same reason the reference names them: neither SDK offers a public count
|
|
26
|
+
* of its own registrations, and reaching into a manager's private map is the habit
|
|
27
|
+
* `fromManifest` exists to have ended. They are PINNED AGAINST THE WIRE — the `advertisement`
|
|
28
|
+
* wire session drives a real `prompts/list` and `resources/templates/list` against both
|
|
29
|
+
* runtimes, so a registration added or removed without moving these fails the suite.
|
|
30
|
+
*/
|
|
31
|
+
export declare const SERVED_PROMPTS = 1;
|
|
32
|
+
export declare const SERVED_RESOURCE_TEMPLATES = 2;
|
|
33
|
+
/**
|
|
34
|
+
* Percent of the index budget that has to be SPENT before the store is called degraded.
|
|
35
|
+
*
|
|
36
|
+
* 90 and not 100 because the useful moment is before the refusal, not after it: at 100% the
|
|
37
|
+
* next `memory_save` has already failed and the operator has already seen the error. An
|
|
38
|
+
* INTEGER percent, compared by cross-multiplication below, so the two runtimes cannot land
|
|
39
|
+
* on opposite sides of the line through a float they rounded differently.
|
|
40
|
+
*/
|
|
41
|
+
export declare const INDEX_PRESSURE_PERCENT = 90;
|
|
42
|
+
/**
|
|
43
|
+
* One thing that is wrong, carried in the two forms the two surfaces need.
|
|
44
|
+
*
|
|
45
|
+
* `key` is an ASCII token from a closed set, which is the ONLY form that may reach the event
|
|
46
|
+
* log (`docs/eventlog.md`'s metadata-only rule); nothing here writes one today, and the field
|
|
47
|
+
* exists so that a future record cannot be tempted to log the prose. `sentence` is what a
|
|
48
|
+
* person reads. It never carries a tool argument, a memory body, a validated output, or a
|
|
49
|
+
* grant NAME — a layer's KIND is reportable, its name is not.
|
|
50
|
+
*/
|
|
51
|
+
export interface Condition {
|
|
52
|
+
readonly key: string;
|
|
53
|
+
readonly sentence: string;
|
|
54
|
+
}
|
|
55
|
+
/** `1 prompt` / `2 prompts`. Spelled once so the two runtimes cannot disagree twice. */
|
|
56
|
+
export declare function plural(count: number, word: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* The size of the writable store's `index.md` on disk, or `null` if it cannot be read.
|
|
59
|
+
*
|
|
60
|
+
* THE STAT, NOT THE PARSE, and the difference is the reason this can run on every tool call.
|
|
61
|
+
* `Memory.indexAccounting()` re-derives the index by reading every fact file — its own
|
|
62
|
+
* docstring says so, and the event log only calls it when a log is actually enabled. A footer
|
|
63
|
+
* that has to decide on EVERY call cannot pay that, so this reads the rendered artefact the
|
|
64
|
+
* store itself keeps up to date (`MemoryStore.rebuildIndex` writes it on every save) with one
|
|
65
|
+
* `stat`.
|
|
66
|
+
*
|
|
67
|
+
* What that trade costs, said plainly: a store whose facts were edited on disk behind the
|
|
68
|
+
* server's back has a stale `index.md`, and this reports the stale size. The condition is a
|
|
69
|
+
* WARNING that the budget is nearly spent, not the budget check itself —
|
|
70
|
+
* `MemoryStore.checkIndexBudget` is still the thing that refuses a save, and it still
|
|
71
|
+
* measures the parse. The two cannot disagree about a store only bantamkit has written.
|
|
72
|
+
*
|
|
73
|
+
* ABSENT IS 0 AND UNREADABLE IS `null`, split on the errno rather than collapsed. A store
|
|
74
|
+
* that has never been saved to has no `index.md` and really does spend nothing of its budget;
|
|
75
|
+
* a store whose directory refuses a `stat` has an unknown index, and reporting that as 0 would
|
|
76
|
+
* claim infinite headroom at exactly the moment there may be none.
|
|
77
|
+
*/
|
|
78
|
+
export declare function indexBytes(memory: Memory): number | null;
|
|
79
|
+
/**
|
|
80
|
+
* Is the pack this build loads still there?
|
|
81
|
+
*
|
|
82
|
+
* `buildServer` reads every manifest eagerly at startup, so a server that is RUNNING resolved
|
|
83
|
+
* its pack once. It can still lose it afterwards — an upgrade that replaces the directory, a
|
|
84
|
+
* `BANTAMKIT_ASSETS` pointed at a scratch tree that gets cleaned up — and the failure is
|
|
85
|
+
* quiet: the tool descriptions were read at startup and keep being served, while
|
|
86
|
+
* `resources/read` and every later `loadSkill` have nothing behind them.
|
|
87
|
+
*
|
|
88
|
+
* Deliberately no path in the sentence. `assetsRoot()` resolves to DIFFERENT paths in the two
|
|
89
|
+
* runtimes by construction, so a path here would be the one line of this surface that cannot
|
|
90
|
+
* be compared across them, to buy what `--assets-root` prints on demand.
|
|
91
|
+
*/
|
|
92
|
+
export declare function assetPackCondition(): Condition | null;
|
|
93
|
+
/**
|
|
94
|
+
* A memory layer that could not be LISTED — which is not a layer that held nothing.
|
|
95
|
+
*
|
|
96
|
+
* This is the sharpest of the four, because it is the one whose damage is a wrong ANSWER
|
|
97
|
+
* rather than a missing one: `Memory.nothingToReport` already refuses to say "nothing is
|
|
98
|
+
* saved" when a layer is unreadable, but that sentence only reaches a person who happened to
|
|
99
|
+
* run an empty recall. The footer says it on every call.
|
|
100
|
+
*
|
|
101
|
+
* KIND, NEVER NAME. A layer's label is `project`, `extra:<grant name>` or `profile`, and the
|
|
102
|
+
* grant name is the operator's own words for somebody's directory. The kind is the part that
|
|
103
|
+
* is reportable; the split is here rather than at the call site so there is exactly one place
|
|
104
|
+
* it can be got wrong.
|
|
105
|
+
*
|
|
106
|
+
* It reaches `Memory`'s private `layers` and `factCount` through element access on purpose,
|
|
107
|
+
* exactly as the reference reads `_layers` and `_fact_count`. A public accessor would be the
|
|
108
|
+
* right shape and would be a change to the MEMORY layer, which this unit is not authorised to
|
|
109
|
+
* make. Nothing is re-implemented: `factCount` is the same counter `unreadableLayers` uses, so
|
|
110
|
+
* "unreadable" means here exactly what it means to recall.
|
|
111
|
+
*/
|
|
112
|
+
export declare function unreadableLayerCondition(memory: Memory): Condition | null;
|
|
113
|
+
/**
|
|
114
|
+
* The index is nearly as big as the budget that has to hold it.
|
|
115
|
+
*
|
|
116
|
+
* THE REMEDY NAMES THE COMMAND *THIS* INSTALL PROVIDES, and that is the one place this
|
|
117
|
+
* sentence is allowed to differ from `mcpserver.py`'s. The reference spells
|
|
118
|
+
* `python -m bantamkit.memory compact`; a pure-npm install has no `bantamkit.memory` module
|
|
119
|
+
* and no interpreter to run it with, so printing that here would hand the operator a command
|
|
120
|
+
* that cannot run. `runtime-ts` ships the same lifecycle as the `bantamkit-memory` bin
|
|
121
|
+
* (`src/memory/cli.ts`), so that is what is named. Ruled in `docs/porting.md`'s divergence
|
|
122
|
+
* table and pinned on both sides by `tools/conformance/suites/wire.mjs`.
|
|
123
|
+
*
|
|
124
|
+
* IT IS A LITERAL AND NOT AN IMPORT, exactly as the reference's is. `memory/cli.ts`'s `PROG`
|
|
125
|
+
* is the CLI's own name for itself and the MCP server does not import that module — the
|
|
126
|
+
* coupling is held from the OUTSIDE instead, by `runtime-ts/test/server.test.mjs` here and by
|
|
127
|
+
* `tests/test_status_surface.py`'s
|
|
128
|
+
* `test_the_index_remedy_names_the_command_this_install_actually_provides` there.
|
|
129
|
+
*
|
|
130
|
+
* WHAT THE REMEDY DOES NOT PROMISE. `index-budget-low` fires at >= 90% of budget, while
|
|
131
|
+
* `compact` archives only while the index is above `budget - reserve` and `reserve` defaults
|
|
132
|
+
* to the largest surviving index line. So across most of the band that prints this sentence
|
|
133
|
+
* the command exits 0 having archived nothing. That is the SAME arithmetic in both runtimes
|
|
134
|
+
* (`INDEX_PRESSURE_PERCENT` here, `MemoryStore.compact`'s default `reserve` there), so it is
|
|
135
|
+
* not a divergence and is not fixed here; it is registered in `docs/porting.md`. The sentence
|
|
136
|
+
* says "archive or shorten facts", which is what the operator has to do either way.
|
|
137
|
+
*
|
|
138
|
+
* THIS SENTENCE IS FOR THE OPERATOR; THE MODEL HAS ITS OWN REMEDY. Since job42 the model
|
|
139
|
+
* can call `memory_compact` (the ninth served tool) when a save is actually refused, and
|
|
140
|
+
* the refused-budget reply in `memory/component.ts` names it. That does not change this
|
|
141
|
+
* line: it is read in a status report, by a person, and the command it names is the one
|
|
142
|
+
* this install provides. The two remedies run the same `MemoryStore.compact`.
|
|
143
|
+
*/
|
|
144
|
+
export declare function indexPressureCondition(memory: Memory): Condition | null;
|
|
145
|
+
/**
|
|
146
|
+
* The log was asked for and a record has already been lost.
|
|
147
|
+
*
|
|
148
|
+
* THE `enabled` GUARD IS LOAD-BEARING, not decorative. `writeFailed` is set inside `record`'s
|
|
149
|
+
* `catch` and is never cleared, so a log that was switched on, failed, and was switched off
|
|
150
|
+
* again would still be carrying the flag — and a server whose operator never asked for a log
|
|
151
|
+
* would then report itself degraded for a diagnostic that is not running. Only a log that is
|
|
152
|
+
* BOTH on and known to have lost a record is a problem anyone can act on.
|
|
153
|
+
*/
|
|
154
|
+
export declare function eventLogCondition(log: EventLog): Condition | null;
|
|
155
|
+
/**
|
|
156
|
+
* Everything wrong right now, worst first. An empty array means healthy.
|
|
157
|
+
*
|
|
158
|
+
* ORDER IS SEVERITY AND IT IS LOAD-BEARING, because the footer shows the first one: a pack
|
|
159
|
+
* that vanished breaks every asset-backed surface; an unreadable layer makes recall ANSWER
|
|
160
|
+
* WRONGLY rather than fail; a full index refuses the next save; a broken event log costs
|
|
161
|
+
* diagnostics only.
|
|
162
|
+
*
|
|
163
|
+
* EVERY CONDITION IS OBSERVED, NOT INFERRED — no heartbeat, no timer, no last-seen timestamp.
|
|
164
|
+
* Each one is a state a test can construct and then watch this report: delete the pack, make a
|
|
165
|
+
* layer's `facts/` a file, build a store whose index already exceeds nine tenths of its
|
|
166
|
+
* budget, point the log at an unwritable path. A condition that cannot be constructed is not
|
|
167
|
+
* claimed.
|
|
168
|
+
*
|
|
169
|
+
* THE COST, because this runs on every tool call: one `stat` for the index, one `stat` for the
|
|
170
|
+
* pack, one directory listing per memory layer (two to four), and a field read for the log. No
|
|
171
|
+
* fact file is opened and no index is parsed.
|
|
172
|
+
*/
|
|
173
|
+
export declare function degradedConditions(memory: Memory, log: EventLog): Condition[];
|
|
174
|
+
/**
|
|
175
|
+
* The one line other tools' results carry when something is wrong. `''` when nothing is.
|
|
176
|
+
*
|
|
177
|
+
* THE EMPTY STRING FOR A HEALTHY SERVER IS THE WHOLE POINT, and it is checked in a pair: the
|
|
178
|
+
* notice must be present on a degraded call AND absent on a healthy one, because only the pair
|
|
179
|
+
* proves it is conditional. A footer on every result is noise, and noise trains a reader to
|
|
180
|
+
* skip it — at which point the one time it matters it is invisible.
|
|
181
|
+
*
|
|
182
|
+
* ONE SHAPE, ALWAYS, including for a single condition — a count of 1 reads fine, and a second
|
|
183
|
+
* shape is a second thing for the port to get right. The worst condition is spelt out because
|
|
184
|
+
* a bare count is not actionable; the rest are a number and a pointer, because this is a
|
|
185
|
+
* FOOTER on somebody else's answer and has no licence to become the answer.
|
|
186
|
+
*/
|
|
187
|
+
export declare function degradedNotice(conditions: readonly Condition[]): string;
|
|
188
|
+
/**
|
|
189
|
+
* The whole answer `bantamkit_status` returns — prose, for a person, in a transcript.
|
|
190
|
+
*
|
|
191
|
+
* THE FIRST LINE IS THE ANSWER and it is the line the operator asked for by name:
|
|
192
|
+
* `bantamkit Active 🟢`, or `bantamkit Degraded 🟠` when `conditions` is non-empty. A reader
|
|
193
|
+
* who stops after eight characters has still learned the thing they came for.
|
|
194
|
+
*
|
|
195
|
+
* THE ONE FIELD THAT IS NOT COMPARABLE ACROSS RUNTIMES is `build`. It is `build_id`, a
|
|
196
|
+
* fingerprint of the running source, and the two runtimes fingerprint two different trees by
|
|
197
|
+
* construction — `docs/porting.md`'s divergence table already rules exactly this. It is here
|
|
198
|
+
* anyway because "which bantamkit" is half the question this tool exists to answer: two
|
|
199
|
+
* endpoints registered under one name is the situation RB-P84 filed, and a version string
|
|
200
|
+
* cannot tell them apart.
|
|
201
|
+
*
|
|
202
|
+
* `identity` ARRIVES AS AN ARGUMENT rather than being built here, which is the one shape
|
|
203
|
+
* difference from the reference. `buildIdentity` needs the package version and the SDK version
|
|
204
|
+
* — both of which the server resolves and neither of which this module can see — so the
|
|
205
|
+
* alternative was a cycle between this file and `server.ts`. Nothing about the report changes:
|
|
206
|
+
* it reads the same two fields off the same object the `build_identity` tool answers with.
|
|
207
|
+
*
|
|
208
|
+
* NO ARGUMENT VALUE CAN REACH THIS. The tool takes none, and every input above is server state.
|
|
209
|
+
*/
|
|
210
|
+
export declare function statusReport(memory: Memory, log: EventLog, conditions: readonly Condition[], identity: ReadonlyMap<string, unknown>, tools: number, prompts: number, templates: number): string;
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bantamkit_status`, the degraded footer, and the conditions both are built from.
|
|
3
|
+
*
|
|
4
|
+
* A port of the block of the same name in `runtime-py/src/bantamkit/mcpserver.py`. The
|
|
5
|
+
* contract both runtimes emit is `docs/status.md`, and `tools/conformance/suites/wire.mjs`
|
|
6
|
+
* compares the two reports with line 2's build digest masked — the one value
|
|
7
|
+
* `docs/porting.md` already rules divergent, because it fingerprints the executing tree and
|
|
8
|
+
* these are two trees. If you find yourself needing a SECOND mask, something is diverging
|
|
9
|
+
* that the contract says should not.
|
|
10
|
+
*
|
|
11
|
+
* WHY A TOOL RESULT AND NOT A NOTIFICATION, MEASURED BEFORE ANY OF THIS WAS WRITTEN.
|
|
12
|
+
* The request this answers is "let me SEE that the server is alive", and the obvious
|
|
13
|
+
* implementation — push a line at the host — does not exist on any of the five hosts
|
|
14
|
+
* bantamkit targets. Claude Code's own binary carries the sentence `modern protocol
|
|
15
|
+
* revision with no unsolicited notification path`, and its proprietary `claude/channel`
|
|
16
|
+
* capability is gated six ways, one of which is `provider !== "firstParty"` — that rules
|
|
17
|
+
* out Copilot on its own. bantamkit's host log says the same from the running side:
|
|
18
|
+
* `"protocolEra":"modern"` in every `Connection established` record, and ten records of
|
|
19
|
+
* `Channel notifications skipped: server did not declare claude/channel capability`. So the
|
|
20
|
+
* only surface every host is guaranteed to render is a TOOL RESULT, and that is what all
|
|
21
|
+
* three pieces are. Nothing here pushes and nothing here declares `claude/channel`.
|
|
22
|
+
*
|
|
23
|
+
* THE THREE PIECES AND WHO EACH ONE IS FOR:
|
|
24
|
+
* * `bantamkit_status` — a tool. The MODEL can call it, so an agent mid-conversation can
|
|
25
|
+
* answer "is this thing working" without the operator leaving the transcript.
|
|
26
|
+
* * a same-named PROMPT — `prompts/list` was empty on both runtimes while both advertised
|
|
27
|
+
* `prompts: {listChanged: false}`. A prompt is what a PERSON invokes.
|
|
28
|
+
* * the FOOTER — one line appended to the OTHER tools' results, and only when something is
|
|
29
|
+
* wrong. Never on a healthy call: a footer on every result is noise, noise trains the
|
|
30
|
+
* reader to stop reading, and the one time it matters it is then invisible.
|
|
31
|
+
*/
|
|
32
|
+
import { statSync } from 'node:fs';
|
|
33
|
+
import { join } from 'node:path';
|
|
34
|
+
import { AssetNotFound, assetsRoot } from '../assets.js';
|
|
35
|
+
import { Memory } from '../memory/component.js';
|
|
36
|
+
/**
|
|
37
|
+
* The tool AND the prompt answer to this one name. Deliberately the same word: the operator
|
|
38
|
+
* who read `bantamkit_status` in a footer must find it in their prompt menu without
|
|
39
|
+
* translating, and a model that read it in a prompt must find the tool.
|
|
40
|
+
*/
|
|
41
|
+
export const STATUS_NAME = 'bantamkit_status';
|
|
42
|
+
/** The prompt's own title, as `prompts/list` advertises it. */
|
|
43
|
+
export const STATUS_PROMPT_TITLE = 'bantamkit status';
|
|
44
|
+
/**
|
|
45
|
+
* The prompt's description — the sentence a person reads in the host's own command menu,
|
|
46
|
+
* which is why it is addressed to them and not to a model.
|
|
47
|
+
*/
|
|
48
|
+
export const STATUS_PROMPT_DESCRIPTION = "Is bantamkit actually working? Inserts the running server's own status report — active " +
|
|
49
|
+
'or degraded, its version and build fingerprint, the memory store it is bound to, whether ' +
|
|
50
|
+
'the event log is on, and every degraded condition in full. Invoke it when you want the ' +
|
|
51
|
+
'server to answer for itself rather than ask a model to go and check.';
|
|
52
|
+
/**
|
|
53
|
+
* What the prompt says after the report. The report is the payload; this is the one line
|
|
54
|
+
* that tells the model what the person wanted with it, and it asks for a RELAY rather than
|
|
55
|
+
* an interpretation — the operator invoked this to read the server's own words.
|
|
56
|
+
*/
|
|
57
|
+
export const STATUS_PROMPT_TAIL = 'Show me that report as it stands. If it says Degraded, tell me which of the problems ' +
|
|
58
|
+
'above you would deal with first and why; if it says Active, say so in one line and stop.';
|
|
59
|
+
/**
|
|
60
|
+
* The prompt and resource-template counts `statusReport` prints.
|
|
61
|
+
*
|
|
62
|
+
* Named here for the same reason the reference names them: neither SDK offers a public count
|
|
63
|
+
* of its own registrations, and reaching into a manager's private map is the habit
|
|
64
|
+
* `fromManifest` exists to have ended. They are PINNED AGAINST THE WIRE — the `advertisement`
|
|
65
|
+
* wire session drives a real `prompts/list` and `resources/templates/list` against both
|
|
66
|
+
* runtimes, so a registration added or removed without moving these fails the suite.
|
|
67
|
+
*/
|
|
68
|
+
export const SERVED_PROMPTS = 1;
|
|
69
|
+
export const SERVED_RESOURCE_TEMPLATES = 2;
|
|
70
|
+
/**
|
|
71
|
+
* Percent of the index budget that has to be SPENT before the store is called degraded.
|
|
72
|
+
*
|
|
73
|
+
* 90 and not 100 because the useful moment is before the refusal, not after it: at 100% the
|
|
74
|
+
* next `memory_save` has already failed and the operator has already seen the error. An
|
|
75
|
+
* INTEGER percent, compared by cross-multiplication below, so the two runtimes cannot land
|
|
76
|
+
* on opposite sides of the line through a float they rounded differently.
|
|
77
|
+
*/
|
|
78
|
+
export const INDEX_PRESSURE_PERCENT = 90;
|
|
79
|
+
/** `1 prompt` / `2 prompts`. Spelled once so the two runtimes cannot disagree twice. */
|
|
80
|
+
export function plural(count, word) {
|
|
81
|
+
return count === 1 ? `${count} ${word}` : `${count} ${word}s`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The size of the writable store's `index.md` on disk, or `null` if it cannot be read.
|
|
85
|
+
*
|
|
86
|
+
* THE STAT, NOT THE PARSE, and the difference is the reason this can run on every tool call.
|
|
87
|
+
* `Memory.indexAccounting()` re-derives the index by reading every fact file — its own
|
|
88
|
+
* docstring says so, and the event log only calls it when a log is actually enabled. A footer
|
|
89
|
+
* that has to decide on EVERY call cannot pay that, so this reads the rendered artefact the
|
|
90
|
+
* store itself keeps up to date (`MemoryStore.rebuildIndex` writes it on every save) with one
|
|
91
|
+
* `stat`.
|
|
92
|
+
*
|
|
93
|
+
* What that trade costs, said plainly: a store whose facts were edited on disk behind the
|
|
94
|
+
* server's back has a stale `index.md`, and this reports the stale size. The condition is a
|
|
95
|
+
* WARNING that the budget is nearly spent, not the budget check itself —
|
|
96
|
+
* `MemoryStore.checkIndexBudget` is still the thing that refuses a save, and it still
|
|
97
|
+
* measures the parse. The two cannot disagree about a store only bantamkit has written.
|
|
98
|
+
*
|
|
99
|
+
* ABSENT IS 0 AND UNREADABLE IS `null`, split on the errno rather than collapsed. A store
|
|
100
|
+
* that has never been saved to has no `index.md` and really does spend nothing of its budget;
|
|
101
|
+
* a store whose directory refuses a `stat` has an unknown index, and reporting that as 0 would
|
|
102
|
+
* claim infinite headroom at exactly the moment there may be none.
|
|
103
|
+
*/
|
|
104
|
+
export function indexBytes(memory) {
|
|
105
|
+
try {
|
|
106
|
+
return statSync(join(memory.store.root, 'index.md')).size;
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
// `ENOENT` is the reference's `FileNotFoundError`; every other system error is its bare
|
|
110
|
+
// `OSError`. A non-system throw is a bug here and is not swallowed.
|
|
111
|
+
const code = e.code;
|
|
112
|
+
if (code === undefined)
|
|
113
|
+
throw e;
|
|
114
|
+
return code === 'ENOENT' ? 0 : null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Is the pack this build loads still there?
|
|
119
|
+
*
|
|
120
|
+
* `buildServer` reads every manifest eagerly at startup, so a server that is RUNNING resolved
|
|
121
|
+
* its pack once. It can still lose it afterwards — an upgrade that replaces the directory, a
|
|
122
|
+
* `BANTAMKIT_ASSETS` pointed at a scratch tree that gets cleaned up — and the failure is
|
|
123
|
+
* quiet: the tool descriptions were read at startup and keep being served, while
|
|
124
|
+
* `resources/read` and every later `loadSkill` have nothing behind them.
|
|
125
|
+
*
|
|
126
|
+
* Deliberately no path in the sentence. `assetsRoot()` resolves to DIFFERENT paths in the two
|
|
127
|
+
* runtimes by construction, so a path here would be the one line of this surface that cannot
|
|
128
|
+
* be compared across them, to buy what `--assets-root` prints on demand.
|
|
129
|
+
*/
|
|
130
|
+
export function assetPackCondition() {
|
|
131
|
+
let root;
|
|
132
|
+
try {
|
|
133
|
+
root = assetsRoot();
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
if (!(e instanceof AssetNotFound))
|
|
137
|
+
throw e;
|
|
138
|
+
return {
|
|
139
|
+
key: 'asset-pack-missing',
|
|
140
|
+
sentence: 'the asset pack cannot be resolved at all, so skills, rubrics and tool ' +
|
|
141
|
+
'descriptions have nothing behind them — reinstall the package, or point ' +
|
|
142
|
+
'BANTAMKIT_ASSETS at a real pack and restart the server.',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (!isDir(root)) {
|
|
146
|
+
return {
|
|
147
|
+
key: 'asset-pack-missing',
|
|
148
|
+
sentence: 'the asset pack is gone from where this server resolved it, so skills, ' +
|
|
149
|
+
'rubrics and tool descriptions can no longer be re-read — run ' +
|
|
150
|
+
'`bantamkit-mcp --assets-root` to see where it is looking, then restart.',
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
/** `Path.is_dir()`: false for a missing path and for anything that is not a directory. */
|
|
156
|
+
function isDir(path) {
|
|
157
|
+
try {
|
|
158
|
+
return statSync(path).isDirectory();
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
if (e.code === undefined)
|
|
162
|
+
throw e;
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* A memory layer that could not be LISTED — which is not a layer that held nothing.
|
|
168
|
+
*
|
|
169
|
+
* This is the sharpest of the four, because it is the one whose damage is a wrong ANSWER
|
|
170
|
+
* rather than a missing one: `Memory.nothingToReport` already refuses to say "nothing is
|
|
171
|
+
* saved" when a layer is unreadable, but that sentence only reaches a person who happened to
|
|
172
|
+
* run an empty recall. The footer says it on every call.
|
|
173
|
+
*
|
|
174
|
+
* KIND, NEVER NAME. A layer's label is `project`, `extra:<grant name>` or `profile`, and the
|
|
175
|
+
* grant name is the operator's own words for somebody's directory. The kind is the part that
|
|
176
|
+
* is reportable; the split is here rather than at the call site so there is exactly one place
|
|
177
|
+
* it can be got wrong.
|
|
178
|
+
*
|
|
179
|
+
* It reaches `Memory`'s private `layers` and `factCount` through element access on purpose,
|
|
180
|
+
* exactly as the reference reads `_layers` and `_fact_count`. A public accessor would be the
|
|
181
|
+
* right shape and would be a change to the MEMORY layer, which this unit is not authorised to
|
|
182
|
+
* make. Nothing is re-implemented: `factCount` is the same counter `unreadableLayers` uses, so
|
|
183
|
+
* "unreadable" means here exactly what it means to recall.
|
|
184
|
+
*/
|
|
185
|
+
export function unreadableLayerCondition(memory) {
|
|
186
|
+
const layers = memory['layers'];
|
|
187
|
+
const unreadable = layers.filter(([, store]) => Memory['factCount'](store.root) === null);
|
|
188
|
+
if (unreadable.length === 0)
|
|
189
|
+
return null;
|
|
190
|
+
const kinds = [...new Set(unreadable.map(([label]) => label.split(':')[0]))].sort();
|
|
191
|
+
return {
|
|
192
|
+
key: 'memory-layer-unreadable',
|
|
193
|
+
sentence: `${plural(unreadable.length, 'memory layer')} could not be read ` +
|
|
194
|
+
`(${plural(kinds.length, 'kind')}: ${kinds.join(', ')}), so an empty recall is not ` +
|
|
195
|
+
'evidence that nothing is saved — check that those store directories exist and ' +
|
|
196
|
+
'are readable.',
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* The index is nearly as big as the budget that has to hold it.
|
|
201
|
+
*
|
|
202
|
+
* THE REMEDY NAMES THE COMMAND *THIS* INSTALL PROVIDES, and that is the one place this
|
|
203
|
+
* sentence is allowed to differ from `mcpserver.py`'s. The reference spells
|
|
204
|
+
* `python -m bantamkit.memory compact`; a pure-npm install has no `bantamkit.memory` module
|
|
205
|
+
* and no interpreter to run it with, so printing that here would hand the operator a command
|
|
206
|
+
* that cannot run. `runtime-ts` ships the same lifecycle as the `bantamkit-memory` bin
|
|
207
|
+
* (`src/memory/cli.ts`), so that is what is named. Ruled in `docs/porting.md`'s divergence
|
|
208
|
+
* table and pinned on both sides by `tools/conformance/suites/wire.mjs`.
|
|
209
|
+
*
|
|
210
|
+
* IT IS A LITERAL AND NOT AN IMPORT, exactly as the reference's is. `memory/cli.ts`'s `PROG`
|
|
211
|
+
* is the CLI's own name for itself and the MCP server does not import that module — the
|
|
212
|
+
* coupling is held from the OUTSIDE instead, by `runtime-ts/test/server.test.mjs` here and by
|
|
213
|
+
* `tests/test_status_surface.py`'s
|
|
214
|
+
* `test_the_index_remedy_names_the_command_this_install_actually_provides` there.
|
|
215
|
+
*
|
|
216
|
+
* WHAT THE REMEDY DOES NOT PROMISE. `index-budget-low` fires at >= 90% of budget, while
|
|
217
|
+
* `compact` archives only while the index is above `budget - reserve` and `reserve` defaults
|
|
218
|
+
* to the largest surviving index line. So across most of the band that prints this sentence
|
|
219
|
+
* the command exits 0 having archived nothing. That is the SAME arithmetic in both runtimes
|
|
220
|
+
* (`INDEX_PRESSURE_PERCENT` here, `MemoryStore.compact`'s default `reserve` there), so it is
|
|
221
|
+
* not a divergence and is not fixed here; it is registered in `docs/porting.md`. The sentence
|
|
222
|
+
* says "archive or shorten facts", which is what the operator has to do either way.
|
|
223
|
+
*
|
|
224
|
+
* THIS SENTENCE IS FOR THE OPERATOR; THE MODEL HAS ITS OWN REMEDY. Since job42 the model
|
|
225
|
+
* can call `memory_compact` (the ninth served tool) when a save is actually refused, and
|
|
226
|
+
* the refused-budget reply in `memory/component.ts` names it. That does not change this
|
|
227
|
+
* line: it is read in a status report, by a person, and the command it names is the one
|
|
228
|
+
* this install provides. The two remedies run the same `MemoryStore.compact`.
|
|
229
|
+
*/
|
|
230
|
+
export function indexPressureCondition(memory) {
|
|
231
|
+
const size = indexBytes(memory);
|
|
232
|
+
const budget = memory.store.indexBudget;
|
|
233
|
+
// INTEGER CROSS-MULTIPLICATION, never `size / budget >= 0.9`. Two runtimes that each
|
|
234
|
+
// rounded their own float could land on opposite sides of the line for the same store,
|
|
235
|
+
// and this comparison is the whole reason they cannot.
|
|
236
|
+
if (size === null || size * 100 < INDEX_PRESSURE_PERCENT * budget)
|
|
237
|
+
return null;
|
|
238
|
+
return {
|
|
239
|
+
key: 'index-budget-low',
|
|
240
|
+
sentence: `the memory index is ${size} bytes of a ${budget}-byte budget, so the next save ` +
|
|
241
|
+
'is close to being refused — archive or shorten facts with ' +
|
|
242
|
+
'`bantamkit-memory compact`.',
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* The log was asked for and a record has already been lost.
|
|
247
|
+
*
|
|
248
|
+
* THE `enabled` GUARD IS LOAD-BEARING, not decorative. `writeFailed` is set inside `record`'s
|
|
249
|
+
* `catch` and is never cleared, so a log that was switched on, failed, and was switched off
|
|
250
|
+
* again would still be carrying the flag — and a server whose operator never asked for a log
|
|
251
|
+
* would then report itself degraded for a diagnostic that is not running. Only a log that is
|
|
252
|
+
* BOTH on and known to have lost a record is a problem anyone can act on.
|
|
253
|
+
*/
|
|
254
|
+
export function eventLogCondition(log) {
|
|
255
|
+
if (!(log.enabled && log.writeFailed))
|
|
256
|
+
return null;
|
|
257
|
+
return {
|
|
258
|
+
key: 'event-log-unwritable',
|
|
259
|
+
sentence: 'the event log is switched on but a write to it has already failed, so tool ' +
|
|
260
|
+
'outcomes are going unrecorded — check the path in BANTAMKIT_EVENT_LOG and ' +
|
|
261
|
+
'whether its directory is writable.',
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Everything wrong right now, worst first. An empty array means healthy.
|
|
266
|
+
*
|
|
267
|
+
* ORDER IS SEVERITY AND IT IS LOAD-BEARING, because the footer shows the first one: a pack
|
|
268
|
+
* that vanished breaks every asset-backed surface; an unreadable layer makes recall ANSWER
|
|
269
|
+
* WRONGLY rather than fail; a full index refuses the next save; a broken event log costs
|
|
270
|
+
* diagnostics only.
|
|
271
|
+
*
|
|
272
|
+
* EVERY CONDITION IS OBSERVED, NOT INFERRED — no heartbeat, no timer, no last-seen timestamp.
|
|
273
|
+
* Each one is a state a test can construct and then watch this report: delete the pack, make a
|
|
274
|
+
* layer's `facts/` a file, build a store whose index already exceeds nine tenths of its
|
|
275
|
+
* budget, point the log at an unwritable path. A condition that cannot be constructed is not
|
|
276
|
+
* claimed.
|
|
277
|
+
*
|
|
278
|
+
* THE COST, because this runs on every tool call: one `stat` for the index, one `stat` for the
|
|
279
|
+
* pack, one directory listing per memory layer (two to four), and a field read for the log. No
|
|
280
|
+
* fact file is opened and no index is parsed.
|
|
281
|
+
*/
|
|
282
|
+
export function degradedConditions(memory, log) {
|
|
283
|
+
return [
|
|
284
|
+
assetPackCondition(),
|
|
285
|
+
unreadableLayerCondition(memory),
|
|
286
|
+
indexPressureCondition(memory),
|
|
287
|
+
eventLogCondition(log),
|
|
288
|
+
].filter((condition) => condition !== null);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* The one line other tools' results carry when something is wrong. `''` when nothing is.
|
|
292
|
+
*
|
|
293
|
+
* THE EMPTY STRING FOR A HEALTHY SERVER IS THE WHOLE POINT, and it is checked in a pair: the
|
|
294
|
+
* notice must be present on a degraded call AND absent on a healthy one, because only the pair
|
|
295
|
+
* proves it is conditional. A footer on every result is noise, and noise trains a reader to
|
|
296
|
+
* skip it — at which point the one time it matters it is invisible.
|
|
297
|
+
*
|
|
298
|
+
* ONE SHAPE, ALWAYS, including for a single condition — a count of 1 reads fine, and a second
|
|
299
|
+
* shape is a second thing for the port to get right. The worst condition is spelt out because
|
|
300
|
+
* a bare count is not actionable; the rest are a number and a pointer, because this is a
|
|
301
|
+
* FOOTER on somebody else's answer and has no licence to become the answer.
|
|
302
|
+
*/
|
|
303
|
+
export function degradedNotice(conditions) {
|
|
304
|
+
if (conditions.length === 0)
|
|
305
|
+
return '';
|
|
306
|
+
return (`⚠️ bantamkit degraded (${conditions.length}): ${conditions[0].sentence} ` +
|
|
307
|
+
'Call `bantamkit_status` for the full report.');
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* The whole answer `bantamkit_status` returns — prose, for a person, in a transcript.
|
|
311
|
+
*
|
|
312
|
+
* THE FIRST LINE IS THE ANSWER and it is the line the operator asked for by name:
|
|
313
|
+
* `bantamkit Active 🟢`, or `bantamkit Degraded 🟠` when `conditions` is non-empty. A reader
|
|
314
|
+
* who stops after eight characters has still learned the thing they came for.
|
|
315
|
+
*
|
|
316
|
+
* THE ONE FIELD THAT IS NOT COMPARABLE ACROSS RUNTIMES is `build`. It is `build_id`, a
|
|
317
|
+
* fingerprint of the running source, and the two runtimes fingerprint two different trees by
|
|
318
|
+
* construction — `docs/porting.md`'s divergence table already rules exactly this. It is here
|
|
319
|
+
* anyway because "which bantamkit" is half the question this tool exists to answer: two
|
|
320
|
+
* endpoints registered under one name is the situation RB-P84 filed, and a version string
|
|
321
|
+
* cannot tell them apart.
|
|
322
|
+
*
|
|
323
|
+
* `identity` ARRIVES AS AN ARGUMENT rather than being built here, which is the one shape
|
|
324
|
+
* difference from the reference. `buildIdentity` needs the package version and the SDK version
|
|
325
|
+
* — both of which the server resolves and neither of which this module can see — so the
|
|
326
|
+
* alternative was a cycle between this file and `server.ts`. Nothing about the report changes:
|
|
327
|
+
* it reads the same two fields off the same object the `build_identity` tool answers with.
|
|
328
|
+
*
|
|
329
|
+
* NO ARGUMENT VALUE CAN REACH THIS. The tool takes none, and every input above is server state.
|
|
330
|
+
*/
|
|
331
|
+
export function statusReport(memory, log, conditions, identity, tools, prompts, templates) {
|
|
332
|
+
const build = identity.get('build_id');
|
|
333
|
+
const facts = Memory['factCount'](memory.store.root);
|
|
334
|
+
const size = indexBytes(memory);
|
|
335
|
+
const budget = memory.store.indexBudget;
|
|
336
|
+
const lines = [
|
|
337
|
+
`bantamkit ${conditions.length > 0 ? 'Degraded 🟠' : 'Active 🟢'}`,
|
|
338
|
+
`version ${identity.get('version')}, build ` +
|
|
339
|
+
(typeof build === 'string' ? build : 'unavailable'),
|
|
340
|
+
`serving ${plural(tools, 'tool')}, ${plural(prompts, 'prompt')}, ` +
|
|
341
|
+
`${plural(templates, 'resource template')}`,
|
|
342
|
+
'memory: ' +
|
|
343
|
+
(facts === null
|
|
344
|
+
? 'the project store could not be read'
|
|
345
|
+
: `${plural(facts, 'fact')} in the project store`) +
|
|
346
|
+
', index ' +
|
|
347
|
+
(size === null ? 'unreadable' : String(size)) +
|
|
348
|
+
` of ${budget} bytes`,
|
|
349
|
+
`event log: ${log.enabled ? 'on' : 'off'}`,
|
|
350
|
+
];
|
|
351
|
+
if (conditions.length > 0) {
|
|
352
|
+
lines.push(`${plural(conditions.length, 'problem')}:`);
|
|
353
|
+
for (const condition of conditions)
|
|
354
|
+
lines.push(`- ${condition.sentence}`);
|
|
355
|
+
}
|
|
356
|
+
return lines.join('\n');
|
|
357
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stdio transport, with the two things the SDK's own one cannot give this port.
|
|
3
|
+
*
|
|
4
|
+
* 1. THE RAW ARGUMENT BYTES. `StdioServerTransport` hands the server `JSON.parse(line)`, and
|
|
5
|
+
* N6 measured what that costs: a `5.0` that has been through `JSON.parse` is
|
|
6
|
+
* indistinguishable from `5`, so an accounting field arrives as an integer and the
|
|
7
|
+
* `.log.jsonl` line Python writes as `5.0` gets written as `5`. Nothing errors. The file
|
|
8
|
+
* just stops being diffable, silently, from the first successful clock-out.
|
|
9
|
+
*
|
|
10
|
+
* The SDK exposes no hook for this — `ReadBuffer.readMessage` parses inside the buffer and
|
|
11
|
+
* the buffer is private — so the buffering is done here instead. It is thirty lines
|
|
12
|
+
* (`indexOf('\n')`, strip a trailing `\r`) and it keeps the LINE next to the parsed
|
|
13
|
+
* message, keyed by JSON-RPC id. A handler then re-reads its own arguments with
|
|
14
|
+
* `pyjson.parseJson`, which tags `5.0` as a float and keeps an integer past 2^53 exact.
|
|
15
|
+
*
|
|
16
|
+
* 2. THE OUTGOING NUMBER LITERALS AND THE ENVELOPE ORDER. `serializeMessage` is
|
|
17
|
+
* `JSON.stringify(message) + '\n'`, which un-does the work above on the way out: the same
|
|
18
|
+
* `5.0` in a `structuredContent` leaves as `5`. So a tool handler registers the EXACT text
|
|
19
|
+
* of its result here, keyed by request id, and `send` splices it in. Everything else still
|
|
20
|
+
* goes through `JSON.stringify`, which is byte-for-byte what pydantic-core writes for
|
|
21
|
+
* strings, bools, nulls and safe integers.
|
|
22
|
+
*
|
|
23
|
+
* While the envelope is being built by hand anyway, its keys are written in the reference's
|
|
24
|
+
* order (`jsonrpc`, `id`, `result`) rather than the SDK's (`result`, `jsonrpc`, `id`). Key
|
|
25
|
+
* order is not observable to a conforming client, but it costs nothing here and it removes
|
|
26
|
+
* a difference from the list rather than adding a ruling to it.
|
|
27
|
+
*
|
|
28
|
+
* NOTHING IN THIS FILE WRITES TO STDOUT EXCEPT `send`. That is the invariant the whole
|
|
29
|
+
* package is under, and it is checked by `test/server.test.mjs`, which asserts that every
|
|
30
|
+
* line a real session produced parses as a frame and that stdout never ends mid-line.
|
|
31
|
+
*/
|
|
32
|
+
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
33
|
+
import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
|
|
34
|
+
type Readable = NodeJS.ReadableStream;
|
|
35
|
+
type Writable = NodeJS.WritableStream;
|
|
36
|
+
export declare class RawStdioTransport implements Transport {
|
|
37
|
+
private readonly input;
|
|
38
|
+
private readonly output;
|
|
39
|
+
onclose?: () => void;
|
|
40
|
+
onerror?: (error: Error) => void;
|
|
41
|
+
onmessage?: (message: JSONRPCMessage) => void;
|
|
42
|
+
sessionId?: string;
|
|
43
|
+
private buffer;
|
|
44
|
+
private started;
|
|
45
|
+
private readonly rawLines;
|
|
46
|
+
private readonly exactResults;
|
|
47
|
+
private readonly ondata;
|
|
48
|
+
private readonly onstreamerror;
|
|
49
|
+
constructor(input?: Readable, output?: Writable);
|
|
50
|
+
/** The verbatim request line that carried this id, or `undefined` if it is gone. */
|
|
51
|
+
rawLineFor(id: unknown): string | undefined;
|
|
52
|
+
/** Register the exact JSON text to send as this request's `result`. */
|
|
53
|
+
setExactResult(id: unknown, json: string): void;
|
|
54
|
+
start(): Promise<void>;
|
|
55
|
+
close(): Promise<void>;
|
|
56
|
+
send(message: JSONRPCMessage): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* One frame, in the reference's key order, with an exact `result` when one was registered.
|
|
59
|
+
*
|
|
60
|
+
* The raw line and the exact text are both dropped here rather than in the handler: the
|
|
61
|
+
* handler cannot know whether the protocol will actually send its answer (a cancelled
|
|
62
|
+
* request never reaches `send`), and a map that only ever grows is a leak in a process
|
|
63
|
+
* that is meant to run for a session.
|
|
64
|
+
*/
|
|
65
|
+
private frame;
|
|
66
|
+
private consume;
|
|
67
|
+
}
|
|
68
|
+
export {};
|