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,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many of the log's most recent records the line describes.
|
|
3
|
+
*
|
|
4
|
+
* The bar answers "what is happening now"; "what has ever happened" is `--mcp-report`'s
|
|
5
|
+
* question and it reads both generations to answer it. A fixed window also bounds the two
|
|
6
|
+
* counts on the line, so the rendered string cannot grow with the size of somebody's log.
|
|
7
|
+
*/
|
|
8
|
+
export declare const WINDOW = 50;
|
|
9
|
+
/**
|
|
10
|
+
* The most bytes read off the end of the log, ever.
|
|
11
|
+
*
|
|
12
|
+
* The log rotates itself at 1 MiB, so under bantamkit's own default this never fires. It
|
|
13
|
+
* fires when `BANTAMKIT_EVENT_LOG` names a LITERAL path — an operator can point that at
|
|
14
|
+
* anything, and a redraw path may not be at the mercy of how big that anything is. A
|
|
15
|
+
* partial first line is discarded, so the tail parses identically either way.
|
|
16
|
+
*
|
|
17
|
+
* The rotated generation `<path>.1` is NOT read. It is history, and history is the
|
|
18
|
+
* analyst's job.
|
|
19
|
+
*/
|
|
20
|
+
export declare const TAIL_BYTES = 262144;
|
|
21
|
+
/** Every `outcome` `docs/eventlog.md` defines, and nothing else is renderable. */
|
|
22
|
+
export declare const OUTCOMES: ReadonlySet<string>;
|
|
23
|
+
/**
|
|
24
|
+
* The three outcomes that mean BANTAMKIT is in trouble, rather than the caller's input.
|
|
25
|
+
*
|
|
26
|
+
* `raised` is a handler that fell over. `refused-budget` is the store's index budget
|
|
27
|
+
* stopping a save — `docs/status.md`'s `index-budget-low`, one step too late.
|
|
28
|
+
* `empty-unreadable-layer` is its `memory-layer-unreadable`, observed from outside.
|
|
29
|
+
*
|
|
30
|
+
* Deliberately absent: `refused-validation`, `error`, `escalate`, `invalid` and the other
|
|
31
|
+
* `empty-*` values. Every one of those is the tool working on input it was right to
|
|
32
|
+
* refuse, and a bar that turned orange for them is a bar the operator learns to ignore.
|
|
33
|
+
*/
|
|
34
|
+
export declare const ADVERSE: readonly string[];
|
|
35
|
+
export declare const ACTIVE = "bantamkit Active \uD83D\uDFE2";
|
|
36
|
+
export declare const DEGRADED = "bantamkit Degraded \uD83D\uDFE0";
|
|
37
|
+
export declare const UNKNOWN = "bantamkit Unknown \u26AA";
|
|
38
|
+
/** One space, U+00B7, one space. */
|
|
39
|
+
export declare const SEP = " \u00B7 ";
|
|
40
|
+
export declare const OFF = "event log off";
|
|
41
|
+
export declare const ABSENT = "no event log yet";
|
|
42
|
+
export declare const UNREADABLE = "event log unreadable";
|
|
43
|
+
export declare const EMPTY = "event log empty";
|
|
44
|
+
export declare const SURPRISE = "state unavailable";
|
|
45
|
+
/** What one look at the log found. Integers and closed-vocabulary tokens only. */
|
|
46
|
+
export interface Reading {
|
|
47
|
+
state: 'active' | 'degraded' | 'unknown';
|
|
48
|
+
events: number;
|
|
49
|
+
problems: number;
|
|
50
|
+
tool: string;
|
|
51
|
+
outcome: string;
|
|
52
|
+
reason: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The last `TAIL_BYTES` of `path`, with a partial leading line discarded.
|
|
56
|
+
*
|
|
57
|
+
* Read as BYTES and never decoded as a whole: a truncated tail can start mid-codepoint,
|
|
58
|
+
* and decoding the buffer in one go would replace that byte and leave every intact record
|
|
59
|
+
* behind it fine — but the reference decodes per line, so this does too, and one bad line
|
|
60
|
+
* costs one record on both sides.
|
|
61
|
+
*
|
|
62
|
+
* Throws the underlying filesystem error; the caller turns it into a rendered state.
|
|
63
|
+
*/
|
|
64
|
+
export declare function readTail(path: string): Buffer;
|
|
65
|
+
/**
|
|
66
|
+
* `[tool, outcome]` for every line that is a record this module is willing to render.
|
|
67
|
+
*
|
|
68
|
+
* THIS IS THE LEAK GUARD AND IT IS STRUCTURAL, NOT A FILTER. What comes back is a list of
|
|
69
|
+
* pairs of validated tokens; there is no field here that can hold a path, a memory body, a
|
|
70
|
+
* query or a `detail` value, so no later code CAN print one. A record with an unknown
|
|
71
|
+
* outcome, a tool that is not `^[a-z0-9_]{1,64}$`, a non-object body, or unparseable JSON
|
|
72
|
+
* is dropped whole — never rendered under a fallback spelling, because a fallback spelling
|
|
73
|
+
* is a way for the file's own bytes to reach the bar.
|
|
74
|
+
*/
|
|
75
|
+
export declare function parse(data: Buffer): Array<[string, string]>;
|
|
76
|
+
/**
|
|
77
|
+
* The window's state: degraded if it holds an `ADVERSE` outcome, else active.
|
|
78
|
+
*
|
|
79
|
+
* The record the line NAMES is the most recent adverse one when degraded, and the most
|
|
80
|
+
* recent one when active. Naming the last record while degraded would hide the fault
|
|
81
|
+
* behind whatever happened to run after it.
|
|
82
|
+
*/
|
|
83
|
+
export declare function summarise(pairs: ReadonlyArray<readonly [string, string]>): Reading;
|
|
84
|
+
/**
|
|
85
|
+
* Resolve the log the same way the server would, read it, and summarise it.
|
|
86
|
+
*
|
|
87
|
+
* `resolveEventLogPath` is reused rather than reimplemented so this runtime has ONE
|
|
88
|
+
* vocabulary for `BANTAMKIT_EVENT_LOG`. It DESIGNATES a store path without creating one,
|
|
89
|
+
* so drawing a status bar never brings a memory store into existence.
|
|
90
|
+
*/
|
|
91
|
+
export declare function probe(env: Readonly<Record<string, string | undefined>>, store?: string | null, start?: string | null): Reading;
|
|
92
|
+
/**
|
|
93
|
+
* The one line, without its newline. Never empty, never multi-line.
|
|
94
|
+
*
|
|
95
|
+
* An empty line would make the host's bar flicker between one row and none, which is worse
|
|
96
|
+
* than any wording; that is why the unknown state has a rendering at all.
|
|
97
|
+
*/
|
|
98
|
+
export declare function render(reading: Reading): string;
|
|
99
|
+
/**
|
|
100
|
+
* The total entry point: a string for every input, including inputs that are wrong.
|
|
101
|
+
*
|
|
102
|
+
* The bare `catch` is not laziness, it is the contract. This runs on somebody's redraw path
|
|
103
|
+
* with no operator watching; the failure it must not have is a traceback where a status bar
|
|
104
|
+
* should be.
|
|
105
|
+
*/
|
|
106
|
+
export declare function statusLine(env: Readonly<Record<string, string | undefined>>, store?: string | null, start?: string | null): string;
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One line for a host's status bar, rendered from what a finished session left on disk.
|
|
3
|
+
*
|
|
4
|
+
* The contract is `docs/statusline.md` and the reference is
|
|
5
|
+
* `runtime-py/src/bantamkit/statusline.py`; `tools/conformance/suites/statusline.mjs`
|
|
6
|
+
* runs both as PROCESSES over one synthetic fixture set and compares stdout, stderr and
|
|
7
|
+
* the exit code. This file implements the document, not the Python — but every constant
|
|
8
|
+
* below has a named counterpart there, so a reader can put the two side by side.
|
|
9
|
+
*
|
|
10
|
+
* WHY THIS IS NOT A SIXTH HOST. `bantamkit_status` and its prompt (`docs/status.md`) are
|
|
11
|
+
* rendered ON DEMAND. This one is rendered WITHOUT ANYONE ASKING, on the host's redraw
|
|
12
|
+
* path, and `statusLine` is a key in `~/.claude/settings.json` — **Claude Code only**. It
|
|
13
|
+
* has no analogue in Claude Desktop, in the Copilot CLI, or in either VS Code extension.
|
|
14
|
+
*
|
|
15
|
+
* THREE PROPERTIES, EACH THE OPPOSITE OF A DEFECT JOB39 MEASURED BY CONNECTING.
|
|
16
|
+
*
|
|
17
|
+
* * **It never starts a server.** Nothing here opens a transport or builds a `Memory`.
|
|
18
|
+
* It reads at most one file.
|
|
19
|
+
* * **It never fails loudly.** `statusLine()` is total: every path returns a string,
|
|
20
|
+
* and the outermost `catch` renders the UNKNOWN state. The packaged `bin` dies with a
|
|
21
|
+
* raw Node stack trace on a bad `cwd`; a redraw path may not reproduce that. Unknown
|
|
22
|
+
* is a state to render, not an error to raise.
|
|
23
|
+
* * **It reads what is already true.** No clock, no heartbeat, no timer.
|
|
24
|
+
*
|
|
25
|
+
* WHY THE EVENT LOG AND NOT `--mcp-report`. `--mcp-report` reads source B too, but only
|
|
26
|
+
* after source A. Measured on this machine 2026-08-24, 25 runs each: bare `node -e 0` p50
|
|
27
|
+
* 18.1 ms, `--statusline` p50 69.4 ms, `--mcp-report` p50 88.6 ms over 22 slug directories,
|
|
28
|
+
* 112 files and 1674 records. The 19 ms is the smaller half of the reason: source A is
|
|
29
|
+
* UNBOUNDED and bantamkit does not own it — the host never rotates it, and it grew from
|
|
30
|
+
* 1533 records to 1674 in five days. Source B is bounded by its own rotation rule and
|
|
31
|
+
* bounded again here by `TAIL_BYTES`. The third reason is shape: `--mcp-report` prints
|
|
32
|
+
* forty lines and a bar takes one, so an adapter over it would have to PARSE the report.
|
|
33
|
+
*
|
|
34
|
+
* METADATA ONLY, AND HERE IT IS STRUCTURAL. `parse` keeps exactly two strings per record:
|
|
35
|
+
* a `tool` matching `^[a-z0-9_]{1,64}$` and an `outcome` from `OUTCOMES`. `detail`, `ts`
|
|
36
|
+
* and `v` are never read. No parsed record has a field that could hold borrowed text, so
|
|
37
|
+
* no code path CAN print one.
|
|
38
|
+
*/
|
|
39
|
+
import { closeSync, fstatSync, openSync, readSync } from 'node:fs';
|
|
40
|
+
import { resolveEventLogPath } from './mcpreport.js';
|
|
41
|
+
/**
|
|
42
|
+
* How many of the log's most recent records the line describes.
|
|
43
|
+
*
|
|
44
|
+
* The bar answers "what is happening now"; "what has ever happened" is `--mcp-report`'s
|
|
45
|
+
* question and it reads both generations to answer it. A fixed window also bounds the two
|
|
46
|
+
* counts on the line, so the rendered string cannot grow with the size of somebody's log.
|
|
47
|
+
*/
|
|
48
|
+
export const WINDOW = 50;
|
|
49
|
+
/**
|
|
50
|
+
* The most bytes read off the end of the log, ever.
|
|
51
|
+
*
|
|
52
|
+
* The log rotates itself at 1 MiB, so under bantamkit's own default this never fires. It
|
|
53
|
+
* fires when `BANTAMKIT_EVENT_LOG` names a LITERAL path — an operator can point that at
|
|
54
|
+
* anything, and a redraw path may not be at the mercy of how big that anything is. A
|
|
55
|
+
* partial first line is discarded, so the tail parses identically either way.
|
|
56
|
+
*
|
|
57
|
+
* The rotated generation `<path>.1` is NOT read. It is history, and history is the
|
|
58
|
+
* analyst's job.
|
|
59
|
+
*/
|
|
60
|
+
export const TAIL_BYTES = 262144;
|
|
61
|
+
/** Every `outcome` `docs/eventlog.md` defines, and nothing else is renderable. */
|
|
62
|
+
export const OUTCOMES = new Set([
|
|
63
|
+
// memory_save
|
|
64
|
+
'saved',
|
|
65
|
+
'duplicate',
|
|
66
|
+
'refused-validation',
|
|
67
|
+
'refused-budget',
|
|
68
|
+
// memory_recall
|
|
69
|
+
'answered',
|
|
70
|
+
'empty-no-match',
|
|
71
|
+
'empty-unreadable-layer',
|
|
72
|
+
'empty-nothing-saved',
|
|
73
|
+
// validate_json
|
|
74
|
+
'valid',
|
|
75
|
+
'invalid',
|
|
76
|
+
// shiftwork_clock_in
|
|
77
|
+
'brief',
|
|
78
|
+
'escalate',
|
|
79
|
+
'success',
|
|
80
|
+
'error',
|
|
81
|
+
// shiftwork_clock_out / shiftwork_status
|
|
82
|
+
'ok',
|
|
83
|
+
'status',
|
|
84
|
+
// build_identity
|
|
85
|
+
'complete',
|
|
86
|
+
'partial',
|
|
87
|
+
// any handler that fell over
|
|
88
|
+
'raised',
|
|
89
|
+
]);
|
|
90
|
+
/**
|
|
91
|
+
* The three outcomes that mean BANTAMKIT is in trouble, rather than the caller's input.
|
|
92
|
+
*
|
|
93
|
+
* `raised` is a handler that fell over. `refused-budget` is the store's index budget
|
|
94
|
+
* stopping a save — `docs/status.md`'s `index-budget-low`, one step too late.
|
|
95
|
+
* `empty-unreadable-layer` is its `memory-layer-unreadable`, observed from outside.
|
|
96
|
+
*
|
|
97
|
+
* Deliberately absent: `refused-validation`, `error`, `escalate`, `invalid` and the other
|
|
98
|
+
* `empty-*` values. Every one of those is the tool working on input it was right to
|
|
99
|
+
* refuse, and a bar that turned orange for them is a bar the operator learns to ignore.
|
|
100
|
+
*/
|
|
101
|
+
export const ADVERSE = ['raised', 'refused-budget', 'empty-unreadable-layer'];
|
|
102
|
+
/** The host's log validates tool names with exactly this, and so does `mcpreport.ts`. */
|
|
103
|
+
const TOOL = /^[a-z0-9_]{1,64}$/;
|
|
104
|
+
export const ACTIVE = 'bantamkit Active 🟢';
|
|
105
|
+
export const DEGRADED = 'bantamkit Degraded 🟠';
|
|
106
|
+
export const UNKNOWN = 'bantamkit Unknown ⚪';
|
|
107
|
+
/** One space, U+00B7, one space. */
|
|
108
|
+
export const SEP = ' · ';
|
|
109
|
+
export const OFF = 'event log off';
|
|
110
|
+
export const ABSENT = 'no event log yet';
|
|
111
|
+
export const UNREADABLE = 'event log unreadable';
|
|
112
|
+
export const EMPTY = 'event log empty';
|
|
113
|
+
export const SURPRISE = 'state unavailable';
|
|
114
|
+
const unknown = (reason) => ({
|
|
115
|
+
state: 'unknown',
|
|
116
|
+
events: 0,
|
|
117
|
+
problems: 0,
|
|
118
|
+
tool: '',
|
|
119
|
+
outcome: '',
|
|
120
|
+
reason,
|
|
121
|
+
});
|
|
122
|
+
/**
|
|
123
|
+
* The last `TAIL_BYTES` of `path`, with a partial leading line discarded.
|
|
124
|
+
*
|
|
125
|
+
* Read as BYTES and never decoded as a whole: a truncated tail can start mid-codepoint,
|
|
126
|
+
* and decoding the buffer in one go would replace that byte and leave every intact record
|
|
127
|
+
* behind it fine — but the reference decodes per line, so this does too, and one bad line
|
|
128
|
+
* costs one record on both sides.
|
|
129
|
+
*
|
|
130
|
+
* Throws the underlying filesystem error; the caller turns it into a rendered state.
|
|
131
|
+
*/
|
|
132
|
+
export function readTail(path) {
|
|
133
|
+
const fd = openSync(path, 'r');
|
|
134
|
+
let data;
|
|
135
|
+
try {
|
|
136
|
+
const size = fstatSync(fd).size;
|
|
137
|
+
const start = size > TAIL_BYTES ? size - TAIL_BYTES : 0;
|
|
138
|
+
const length = size - start;
|
|
139
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
140
|
+
let filled = 0;
|
|
141
|
+
while (filled < length) {
|
|
142
|
+
const got = readSync(fd, buffer, filled, length - filled, start + filled);
|
|
143
|
+
if (got === 0)
|
|
144
|
+
break;
|
|
145
|
+
filled += got;
|
|
146
|
+
}
|
|
147
|
+
data = buffer.subarray(0, filled);
|
|
148
|
+
if (start > 0) {
|
|
149
|
+
const cut = data.indexOf(0x0a);
|
|
150
|
+
data = cut < 0 ? Buffer.alloc(0) : data.subarray(cut + 1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
closeSync(fd);
|
|
155
|
+
}
|
|
156
|
+
return data;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* `[tool, outcome]` for every line that is a record this module is willing to render.
|
|
160
|
+
*
|
|
161
|
+
* THIS IS THE LEAK GUARD AND IT IS STRUCTURAL, NOT A FILTER. What comes back is a list of
|
|
162
|
+
* pairs of validated tokens; there is no field here that can hold a path, a memory body, a
|
|
163
|
+
* query or a `detail` value, so no later code CAN print one. A record with an unknown
|
|
164
|
+
* outcome, a tool that is not `^[a-z0-9_]{1,64}$`, a non-object body, or unparseable JSON
|
|
165
|
+
* is dropped whole — never rendered under a fallback spelling, because a fallback spelling
|
|
166
|
+
* is a way for the file's own bytes to reach the bar.
|
|
167
|
+
*/
|
|
168
|
+
export function parse(data) {
|
|
169
|
+
// `fatal: true` is the reference's `bytes.decode('utf-8')`, which RAISES. Node's default
|
|
170
|
+
// decoder substitutes U+FFFD instead, and a record whose `detail` held an invalid byte
|
|
171
|
+
// would then survive here and be dropped there — a divergence in a compared line, bought
|
|
172
|
+
// for nothing. One bad line costs one record on both sides.
|
|
173
|
+
const utf8 = new TextDecoder('utf-8', { fatal: true });
|
|
174
|
+
const pairs = [];
|
|
175
|
+
let cursor = 0;
|
|
176
|
+
while (cursor <= data.length) {
|
|
177
|
+
let stop = data.indexOf(0x0a, cursor);
|
|
178
|
+
if (stop < 0)
|
|
179
|
+
stop = data.length;
|
|
180
|
+
const line = data.subarray(cursor, stop);
|
|
181
|
+
cursor = stop + 1;
|
|
182
|
+
if (line.length === 0)
|
|
183
|
+
continue;
|
|
184
|
+
let record;
|
|
185
|
+
try {
|
|
186
|
+
record = JSON.parse(utf8.decode(line));
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (record === null || typeof record !== 'object' || Array.isArray(record))
|
|
192
|
+
continue;
|
|
193
|
+
const { tool, outcome } = record;
|
|
194
|
+
if (typeof tool !== 'string' || typeof outcome !== 'string')
|
|
195
|
+
continue;
|
|
196
|
+
if (!TOOL.test(tool) || !OUTCOMES.has(outcome))
|
|
197
|
+
continue;
|
|
198
|
+
pairs.push([tool, outcome]);
|
|
199
|
+
}
|
|
200
|
+
return pairs;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* The window's state: degraded if it holds an `ADVERSE` outcome, else active.
|
|
204
|
+
*
|
|
205
|
+
* The record the line NAMES is the most recent adverse one when degraded, and the most
|
|
206
|
+
* recent one when active. Naming the last record while degraded would hide the fault
|
|
207
|
+
* behind whatever happened to run after it.
|
|
208
|
+
*/
|
|
209
|
+
export function summarise(pairs) {
|
|
210
|
+
const window = pairs.slice(Math.max(0, pairs.length - WINDOW));
|
|
211
|
+
if (window.length === 0)
|
|
212
|
+
return unknown(EMPTY);
|
|
213
|
+
const problems = window.filter(([, outcome]) => ADVERSE.includes(outcome));
|
|
214
|
+
const [tool, outcome] = problems.length > 0 ? problems[problems.length - 1] : window[window.length - 1];
|
|
215
|
+
return {
|
|
216
|
+
state: problems.length > 0 ? 'degraded' : 'active',
|
|
217
|
+
events: window.length,
|
|
218
|
+
problems: problems.length,
|
|
219
|
+
tool,
|
|
220
|
+
outcome,
|
|
221
|
+
reason: '',
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Resolve the log the same way the server would, read it, and summarise it.
|
|
226
|
+
*
|
|
227
|
+
* `resolveEventLogPath` is reused rather than reimplemented so this runtime has ONE
|
|
228
|
+
* vocabulary for `BANTAMKIT_EVENT_LOG`. It DESIGNATES a store path without creating one,
|
|
229
|
+
* so drawing a status bar never brings a memory store into existence.
|
|
230
|
+
*/
|
|
231
|
+
export function probe(env, store, start) {
|
|
232
|
+
let path;
|
|
233
|
+
try {
|
|
234
|
+
path = resolveEventLogPath(env, store, start);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return unknown(UNREADABLE);
|
|
238
|
+
}
|
|
239
|
+
if (path === null)
|
|
240
|
+
return unknown(OFF);
|
|
241
|
+
let data;
|
|
242
|
+
try {
|
|
243
|
+
data = readTail(path);
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
return unknown(error.code === 'ENOENT' ? ABSENT : UNREADABLE);
|
|
247
|
+
}
|
|
248
|
+
return summarise(parse(data));
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* The one line, without its newline. Never empty, never multi-line.
|
|
252
|
+
*
|
|
253
|
+
* An empty line would make the host's bar flicker between one row and none, which is worse
|
|
254
|
+
* than any wording; that is why the unknown state has a rendering at all.
|
|
255
|
+
*/
|
|
256
|
+
export function render(reading) {
|
|
257
|
+
if (reading.state === 'unknown')
|
|
258
|
+
return `${UNKNOWN}${SEP}${reading.reason}`;
|
|
259
|
+
const events = `${reading.events} event${reading.events === 1 ? '' : 's'}`;
|
|
260
|
+
const named = `${reading.tool} ${reading.outcome}`;
|
|
261
|
+
if (reading.state === 'degraded') {
|
|
262
|
+
const problems = `${reading.problems} problem${reading.problems === 1 ? '' : 's'}`;
|
|
263
|
+
return `${DEGRADED}${SEP}${events}${SEP}${problems}${SEP}${named}`;
|
|
264
|
+
}
|
|
265
|
+
return `${ACTIVE}${SEP}${events}${SEP}${named}`;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* The total entry point: a string for every input, including inputs that are wrong.
|
|
269
|
+
*
|
|
270
|
+
* The bare `catch` is not laziness, it is the contract. This runs on somebody's redraw path
|
|
271
|
+
* with no operator watching; the failure it must not have is a traceback where a status bar
|
|
272
|
+
* should be.
|
|
273
|
+
*/
|
|
274
|
+
export function statusLine(env, store, start) {
|
|
275
|
+
try {
|
|
276
|
+
return render(probe(env, store, start));
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return `${UNKNOWN}${SEP}${SURPRISE}`;
|
|
280
|
+
}
|
|
281
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bantamkit-mcp",
|
|
3
|
+
"version": "0.26.0",
|
|
4
|
+
"description": "bantamkit MCP server — pure Node, runnable with npx",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"bantamkit-mcp": "dist/cli.js",
|
|
8
|
+
"bantamkit-memory": "dist/memory/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"assets"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc -p tsconfig.json",
|
|
27
|
+
"sync-assets": "node scripts/sync-assets.mjs",
|
|
28
|
+
"prepack": "node scripts/sync-assets.mjs",
|
|
29
|
+
"pretest": "npm run build",
|
|
30
|
+
"test": "node scripts/run-tests.mjs"
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/Ink01101011/bantamkit.git",
|
|
36
|
+
"directory": "runtime-ts"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^22.10.2",
|
|
40
|
+
"typescript": "^5.7.2"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@modelcontextprotocol/sdk": "1.30.0"
|
|
44
|
+
}
|
|
45
|
+
}
|