@theokit/sdk-tools 0.27.0 → 0.27.2
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/CHANGELOG.md +233 -0
- package/LICENSE +2 -2
- package/README.md +15 -2
- package/dist/index.cjs +56 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +397 -8
- package/dist/index.d.ts +397 -8
- package/dist/index.js +57 -25
- package/dist/index.js.map +1 -1
- package/package.json +10 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,238 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.27.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 1a4bbcf: The declared `@theokit/sdk` peer range stops promising a version the package does not compile against.
|
|
8
|
+
|
|
9
|
+
It declared `>=4.19.3` and fails at exactly that floor, with `error TS2552: Cannot find name`. npm
|
|
10
|
+
resolves the combination with no `ERESOLVE` and no peer warning, and the break surfaces in the build,
|
|
11
|
+
far from the range that caused it.
|
|
12
|
+
|
|
13
|
+
The real floor is `4.54.0`, measured by bisecting the 116 stable 4.x releases with a real build as the
|
|
14
|
+
oracle: `4.53.1` fails and `4.54.0` passes. They are adjacent in the published list, so this is the
|
|
15
|
+
exact version rather than an interval.
|
|
16
|
+
|
|
17
|
+
This package had been excluded from the sibling finding (usetheokit/theokit-sdk#423) for declaring a
|
|
18
|
+
narrower range than the other four. A narrower range can still be false — it is just false over a
|
|
19
|
+
smaller interval (usetheokit/theokit-sdk#425).
|
|
20
|
+
|
|
21
|
+
## 0.27.1
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- e3f2a82: Public-API documentation reviewed file by file, and corrected wherever it disagreed
|
|
26
|
+
with the code. The docblocks ship in the `.d.ts`, so these read as behaviour changes
|
|
27
|
+
in an editor even though no behaviour changed.
|
|
28
|
+
|
|
29
|
+
The corrections that change what a caller would do:
|
|
30
|
+
|
|
31
|
+
- **`sdk-cache` documented its own premise backwards.** The header example labelled a
|
|
32
|
+
semantic hit as if it avoided the provider call. `asPlugin()` returns the cached
|
|
33
|
+
answer as `recalledContext`, which the agent loop injects as a `<memory-context>`
|
|
34
|
+
block _before_ the prompt — the request still goes to the provider. The two modes
|
|
35
|
+
are now labelled separately, with a table saying which one short-circuits and which
|
|
36
|
+
one seeds.
|
|
37
|
+
- **`sdk-handoff`'s five error classes said "throw".** Under the plugin wiring the
|
|
38
|
+
handler never throws; every failure becomes a tool result `{"ok":false,…}` handed
|
|
39
|
+
back to the model. Each class now says where it is actually observable. The header
|
|
40
|
+
also told readers to `import { Handoff } from "@theokit/sdk"`, from which it was
|
|
41
|
+
extracted.
|
|
42
|
+
- **`sdk-budget`'s `charge()` claimed idempotency across concurrent calls.** The mutex
|
|
43
|
+
serialises, it does not deduplicate: two identical calls record twice. Related, and
|
|
44
|
+
newly documented: with `maxUsd` set, a model missing from the pricing table denies
|
|
45
|
+
every request rather than passing it — and the table matches by exact string, so
|
|
46
|
+
`"openai/gpt-4o"` does not match `"gpt-4o"`.
|
|
47
|
+
- **The three `memory-*` adapters advertised an env-var fallback they do not read**,
|
|
48
|
+
and their peer dependencies are required rather than optional. Their behavioural
|
|
49
|
+
differences are now stated where they break the "interchangeable adapter"
|
|
50
|
+
assumption — honcho ignores `k` and always throws on `delete`; mem0 recalls across
|
|
51
|
+
sessions by design; supermemory ignores `sessionId` entirely.
|
|
52
|
+
- **`sdk-memory`'s `truncated` flag was documented as its own inverse**, and its
|
|
53
|
+
dreaming sweep claimed a mutex it never takes against the writer it names.
|
|
54
|
+
- **`sdk-tools`** corrected `run_vitest`'s unreachable `no_vitest` code, `truncation`'s
|
|
55
|
+
replacement-character claim, and two return shapes missing a live error code.
|
|
56
|
+
- **`acp`/`cli`** corrected sixteen statements including a named error class that is
|
|
57
|
+
not the one raised, a handler documented as calling `fork()` that refuses
|
|
58
|
+
unconditionally, handlers described as pure that mint ids and mutate a store, a
|
|
59
|
+
config loader credited to Zod in a package that does not import it, and a `--force`
|
|
60
|
+
scaffold described as atomic that deletes the destination before the rename.
|
|
61
|
+
|
|
62
|
+
Undocumented public symbols were documented across every package, with each claim
|
|
63
|
+
checked against the implementation rather than inferred from the name.
|
|
64
|
+
|
|
65
|
+
- e368fc1: Every published declaration file now compiles without `skipLibCheck` (#345). The
|
|
66
|
+
DTS rollup emitted symbols as a re-export from a chunk while omitting them from
|
|
67
|
+
that chunk's `import`, and dropped type-only imports from external packages —
|
|
68
|
+
leaving 51 unresolved references across ten of the twelve packages. Nothing broke
|
|
69
|
+
at runtime, and `tsc` stayed green for anyone with `skipLibCheck` on, but a
|
|
70
|
+
consumer running type-aware lint saw every type reached through one degrade to
|
|
71
|
+
`error`.
|
|
72
|
+
|
|
73
|
+
The declarations are repaired at build time from the compiler's own diagnostics.
|
|
74
|
+
No source or API change.
|
|
75
|
+
|
|
76
|
+
- 434b25f: `edit_file` wrote its backup to `<path>.bak` with `copyFile`, and the project-root guard that
|
|
77
|
+
validates `path` never looked at that second path. An attacker who could place a file inside the
|
|
78
|
+
workspace ahead of the edit — which is the ordinary situation for an agent working on a repository
|
|
79
|
+
it did not write — could plant a symlink there and have the backup written through it, anywhere on
|
|
80
|
+
the filesystem the process could reach.
|
|
81
|
+
|
|
82
|
+
Reproduced before the fix: a symlink at `<path>.bak` pointing outside the project root received the
|
|
83
|
+
file's contents, and `edit_file` reported success.
|
|
84
|
+
|
|
85
|
+
The backup is now opened with `O_NOFOLLOW`, so the kernel refuses a symlink outright rather than a
|
|
86
|
+
check deciding it is safe and the write happening a moment later. An existing **regular** `.bak` is
|
|
87
|
+
still overwritten, so the documented behaviour is unchanged for every legitimate edit.
|
|
88
|
+
|
|
89
|
+
When the path is refused, the tool returns `{ ok: false, error: "unsafe_backup_path" }` and **does
|
|
90
|
+
not perform the edit**. An edit that silently proceeded without the backup the caller asked for
|
|
91
|
+
would be the quieter bug.
|
|
92
|
+
|
|
93
|
+
- 9e1b0f7: `git_status` with an injected sandbox now asks the backend whether there is a repository, instead of
|
|
94
|
+
probing the host.
|
|
95
|
+
|
|
96
|
+
A session whose checkout lives inside the backend got `{ ok: false, error: "not_a_repo" }` for a
|
|
97
|
+
repository that was perfectly present where the command would have run — while `git_diff`,
|
|
98
|
+
configured identically in the same session, worked. `not_a_repo` exists so the model cannot read "no
|
|
99
|
+
repository" as "nothing changed", so being wrong about it is worse than being unavailable.
|
|
100
|
+
|
|
101
|
+
The path-scope check also moves ahead of the probe: a traversal attempt in a confined session used
|
|
102
|
+
to be answered `not_a_repo` rather than `path_traversal`.
|
|
103
|
+
|
|
104
|
+
- aadc9dd: Seventeen more negative-case tests now identify which failure they caught, and a registry test suite
|
|
105
|
+
stops sleeping to make timestamps differ.
|
|
106
|
+
|
|
107
|
+
Most of those assertions turned out to be under-asserting rather than untestable: twelve of them sat
|
|
108
|
+
on errors that were **already typed**, and simply checked that something threw. They now name the
|
|
109
|
+
class, the stable code and a message fragment — which means a change that swaps one failure for
|
|
110
|
+
another is caught, where before any error at all satisfied the test.
|
|
111
|
+
|
|
112
|
+
Four remain matched on a message fragment because the underlying error genuinely has no type yet, and
|
|
113
|
+
one of those is filed separately: a public entry point throwing a plain error gives callers nothing to
|
|
114
|
+
branch on but a string that changes whenever someone improves the wording.
|
|
115
|
+
|
|
116
|
+
Four more were reclassified out of scope after reading the source rather than the name: they raise
|
|
117
|
+
errors owned by Node, by the schema library, or by a database driver, and pinning a third-party class
|
|
118
|
+
buys little.
|
|
119
|
+
|
|
120
|
+
Separately, the live-agent-registry tests slept thirteen times — some to force last-used timestamps
|
|
121
|
+
apart so eviction ordering could be asserted, others to let fire-and-forget cleanup finish. Both are
|
|
122
|
+
now driven by the test clock, a mechanism this same file already used elsewhere and which needed no
|
|
123
|
+
production change. The file runs in a fraction of the time and no longer depends on how busy the
|
|
124
|
+
machine is.
|
|
125
|
+
|
|
126
|
+
- 8226bc6: Fourteen negative-case tests now identify which guard fired, and a scheduled job keeps test-order
|
|
127
|
+
independence honest.
|
|
128
|
+
|
|
129
|
+
Assertions that only checked "something threw" now assert the error class, its stable code and a
|
|
130
|
+
message substring — for concurrency validation, retry configuration, path traversal, filename
|
|
131
|
+
validation and credential loading. Each conversion was verified by mutating the production error's
|
|
132
|
+
code and watching the corresponding test fail, so the assertions are pinned to the real constants
|
|
133
|
+
rather than to a copy of them.
|
|
134
|
+
|
|
135
|
+
Forty-five remaining sites are deliberately left alone and grouped with reasons: ten raise validation
|
|
136
|
+
errors owned by a third-party schema library, thirteen surface Node's own errors, and twenty-two are
|
|
137
|
+
plain untyped errors in our code where there is no class or code to assert yet.
|
|
138
|
+
|
|
139
|
+
Separately, the suite runs one file at a time, and a comment in the configuration said that was
|
|
140
|
+
covering up a leak. Measured: with file-level parallelism restored the suite is fully green, twice
|
|
141
|
+
over — the two leaks that comment named have since been fixed. Restoring _within-file_ concurrency
|
|
142
|
+
plus randomised order does still fail, reproducibly, in one file that shares a mutable counter
|
|
143
|
+
between its cases; that is filed on its own and is not fixed here.
|
|
144
|
+
|
|
145
|
+
The default gate is unchanged. A separate weekly job runs the suite in shuffled order so the
|
|
146
|
+
remaining coupling keeps surfacing instead of staying suppressed by the serial default.
|
|
147
|
+
|
|
148
|
+
Also documented for contributors: what makes a wait trustworthy, and why a premise that justifies
|
|
149
|
+
deleting something needs checking in a way that a premise justifying keeping something does not.
|
|
150
|
+
|
|
151
|
+
- e699569: **The repository moved to the official `usetheokit` organization.** Every `repository`, `bugs` and `homepage` field now points there, along with the README, `CONTRIBUTING.md`, `SECURITY.md` and the issue templates. Existing clones and any URL already published keep working — GitHub redirects a transferred repository permanently — so this is a correctness fix for the metadata npm renders, not a break.
|
|
152
|
+
|
|
153
|
+
**The Apache-2.0 text every package ships was replaced with the official one.** The copy distributed until now had paragraph 4(d) truncated: it read "except as required for describing the origin of the Work and reproducing the content of the NOTICE file", dropping "reasonable and customary use" from the licensed clause. §4(d) governs what a redistributor must do with attribution notices, and the omission narrowed it.
|
|
154
|
+
|
|
155
|
+
That matters more than a typo would. The manifests declare the SPDX identifier `Apache-2.0`, which is an assertion that the terms are _the_ Apache-2.0 terms — a licence scanner resolves the identifier and never reads the file. A consumer's compliance review, which does read the file, would find a body that no longer matches the identifier and has no name of its own. Every `LICENSE` in this repository is now byte-identical to the canonical text, with the appendix filled in.
|
|
156
|
+
|
|
157
|
+
Nothing else about the terms changed: the licence is the same licence it has always been meant to be, and no package changes what it grants.
|
|
158
|
+
|
|
159
|
+
- f7311b0: When a tool's output exceeds its budget, the full untruncated output is written to an overflow
|
|
160
|
+
file. That file's name was `overflow-<timestamp>-<8 hex characters>.txt`: eight characters is 32
|
|
161
|
+
bits, and the rest of the name is a clock reading anyone can predict. The write itself followed
|
|
162
|
+
symlinks, so a guessed name planted ahead of time would have received the content — and by
|
|
163
|
+
construction that content is the largest thing the tool produced.
|
|
164
|
+
|
|
165
|
+
The name now carries a full UUID, and the file is created exclusively (`O_CREAT|O_EXCL`), which
|
|
166
|
+
POSIX requires to fail when the path already exists, symlinks included. There is no longer a window
|
|
167
|
+
between deciding a path is safe and writing to it.
|
|
168
|
+
|
|
169
|
+
No behaviour changes for any caller: the overflow path is still returned in the truncation trailer,
|
|
170
|
+
and it is still unique per truncation.
|
|
171
|
+
|
|
172
|
+
- f692988: The reference docs no longer ship inside the package. `node_modules/@theokit/sdk/docs/` is gone, along with the `harness-capability-map.md` and `error-codes.md` files it carried — the `docs` entry was removed from the published `files` list and the build step that generated it was removed with it.
|
|
173
|
+
|
|
174
|
+
The exported TypeScript types are now the only reference surface, and they remain the canonical contract: every public primitive carries its import path, signature and JSDoc example, surfaced by your editor. Nothing about the runtime API changed.
|
|
175
|
+
|
|
176
|
+
The scaffolded agent context still ships, unchanged, under `claude-template/`.
|
|
177
|
+
|
|
178
|
+
- 131b497: `run_vitest` now reports `no_vitest` when vitest is not installed, as its contract always said.
|
|
179
|
+
|
|
180
|
+
`npx --no-install` starts, complains on stderr and exits non-zero with nothing on stdout, so the
|
|
181
|
+
case reached `unparseable_output` — which reads as "vitest ran and printed something I could not
|
|
182
|
+
parse", sending a reader after a reporter or parser problem instead of a missing dependency.
|
|
183
|
+
`no_vitest` was reachable only when the `npx` binary itself could not be spawned.
|
|
184
|
+
|
|
185
|
+
The detection is text matching on npm's complaint, and npm's wording is not a contract: if it ever
|
|
186
|
+
changes, the case falls back to the old `unparseable_output` with the real reason in the payload
|
|
187
|
+
rather than to a wrong answer.
|
|
188
|
+
|
|
189
|
+
- 3ecc956: The barrel-export smoke test now asserts behaviour, not just that a name is a function.
|
|
190
|
+
|
|
191
|
+
It was flagged as redundant with four build gates that supposedly enforce the same public surface.
|
|
192
|
+
Measured, the four do not reach this package at all: two are filtered to a different package by name,
|
|
193
|
+
one hardcodes a path under that package, and the fourth's configuration declares only two workspaces,
|
|
194
|
+
neither of them this one. Proved by renaming an exported factory and running all four — every one
|
|
195
|
+
stayed green, and only this test noticed.
|
|
196
|
+
|
|
197
|
+
So deleting it would have removed the only coverage of that surface. It keeps its export checks and
|
|
198
|
+
gains a case that invokes a factory and asserts the tool descriptor it returns — name, description,
|
|
199
|
+
input schema and handler — which is the behavioural assertion no build gate can make.
|
|
200
|
+
|
|
201
|
+
- c7385d2: Test runs no longer claim every core on the host.
|
|
202
|
+
|
|
203
|
+
None of the package configs capped `maxWorkers`, so vitest's default applied: `os.availableParallelism()`,
|
|
204
|
+
one fork per core, each booting a full test environment. The repo's `test` script is
|
|
205
|
+
`turbo run test --filter='./packages/*'`, so that default is paid once per package _concurrently_ —
|
|
206
|
+
nproc forks times turbo's concurrency, on nproc cores. Measured on a 12-thread machine during an
|
|
207
|
+
unrelated investigation, two vitest pools alone were enough to reach load average 33.89 with the
|
|
208
|
+
desktop unusable; a full fan-out is several times that.
|
|
209
|
+
|
|
210
|
+
`@theokit/sdk` is the interesting case. B-104 recorded on 2026-08-19 that the `poolOptions.forks.*`
|
|
211
|
+
block was 100% dead in Vitest 4, deleted it, and noted that `fileParallelism: false` was forcing
|
|
212
|
+
`maxWorkers` to 1 unconditionally, so a fork-count knob could not act. B-059 then flipped
|
|
213
|
+
`fileParallelism` to `true` on 2026-08-20, which made the knob able to act again — and nothing
|
|
214
|
+
reintroduced one, so the package silently went back to the uncapped default. That comment has been
|
|
215
|
+
corrected along with the config; it claimed no knob existed, which is no longer true.
|
|
216
|
+
|
|
217
|
+
The cap leaves 4 cores free (`Math.max(2, cpus().length - 4)`), scaling with the runner rather than
|
|
218
|
+
hard-coding one machine's core count. It costs no wall-clock: measured in `theokit-ui`, the full
|
|
219
|
+
suite ran 73.96s at 4 workers against 74.36s at 12, so the parallelism above the cap was already
|
|
220
|
+
noise. Verified as resolved config rather than as file contents — `createVitest` reports
|
|
221
|
+
`maxWorkers: 8` on a 12-thread host, which is the formula, not the default.
|
|
222
|
+
|
|
223
|
+
This changes no published behaviour; it is test tooling only. Refs usetheokit/theokit-ui#51.
|
|
224
|
+
|
|
225
|
+
- e1fe586: `view_image` checked a file's size with one path lookup and read its bytes with another. Between
|
|
226
|
+
the two, the path could resolve to something else — so the size cap, which is the only thing
|
|
227
|
+
keeping an unbudgeted image out of an LLM's context, described a file that was not the one
|
|
228
|
+
returned.
|
|
229
|
+
|
|
230
|
+
It now opens the file once and both sizes and reads through that descriptor. A descriptor cannot
|
|
231
|
+
be swapped, so the two operations describe the same file by construction rather than by a check
|
|
232
|
+
that can be outrun.
|
|
233
|
+
|
|
234
|
+
The descriptor is closed on every path, including the early return when the image is over the cap.
|
|
235
|
+
|
|
3
236
|
## 0.27.0
|
|
4
237
|
|
|
5
238
|
### Minor Changes
|
package/LICENSE
CHANGED
|
@@ -137,8 +137,8 @@
|
|
|
137
137
|
|
|
138
138
|
6. Trademarks. This License does not grant permission to use the trade
|
|
139
139
|
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
-
except as required for
|
|
141
|
-
reproducing the content of the NOTICE file.
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
142
|
|
|
143
143
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
144
|
agreed to in writing, Licensor provides the Work (and each
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Built-in tools for `@theokit/sdk` agents. File system, git, subprocess, search-t
|
|
|
4
4
|
|
|
5
5
|
Extracted from `@theokit/sdk@1.7.0` as part of the SDK 2.0 package split.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
The exported TypeScript types cover every tool + guard primitive (`buildRepoMap`, `isBlockedIp`, `screenedFetch`, `catastrophicShellReason`, ...) with import paths and examples.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -57,7 +57,7 @@ Sub-shell tools (`subprocess`, `git-diff`, `run-vitest`) use timeout + AbortCont
|
|
|
57
57
|
|
|
58
58
|
## How it fits with `@theokit/sdk`
|
|
59
59
|
|
|
60
|
-
- **Foundation:** `
|
|
60
|
+
- **Foundation:** `Tool.create` and the `CustomTool` type come from `@theokit/sdk`.
|
|
61
61
|
- **No kernel coupling:** sdk-tools never imports from `@theokit/sdk/internal/runtime` or the agent loop.
|
|
62
62
|
- **Path-guard inline:** the small `isForbiddenPath` helper is inlined here (rather than importing from `@theokit/sdk/internal/security`) so sdk-tools is self-contained for the security-critical check.
|
|
63
63
|
|
|
@@ -77,6 +77,19 @@ import { createReadFileTool } from "@theokit/sdk-tools";
|
|
|
77
77
|
|
|
78
78
|
See the monorepo `CHANGELOG.md` for the 1.x → 2.0 package-split migration notes.
|
|
79
79
|
|
|
80
|
+
## API reference
|
|
81
|
+
|
|
82
|
+
Every symbol this package exports, with the exact specifier to import it from, is in the generated
|
|
83
|
+
capability map that ships inside `@theokit/sdk`:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
node_modules/@theokit/sdk/docs/harness-capability-map.md # symbol -> import specifier
|
|
87
|
+
node_modules/@theokit/sdk/docs/error-codes.md # every `code` an error can carry
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Both are generated from the built type declarations, so they describe the version you installed
|
|
91
|
+
rather than the version someone wrote a page about.
|
|
92
|
+
|
|
80
93
|
## License
|
|
81
94
|
|
|
82
95
|
Apache-2.0.
|
package/dist/index.cjs
CHANGED
|
@@ -6,8 +6,8 @@ var sdk = require('@theokit/sdk');
|
|
|
6
6
|
var zod = require('zod');
|
|
7
7
|
var pathSafety = require('@theokit/sdk/path-safety');
|
|
8
8
|
var persistence = require('@theokit/sdk/persistence');
|
|
9
|
-
var filesystem = require('@theokit/sdk/filesystem');
|
|
10
9
|
var fs = require('fs');
|
|
10
|
+
var filesystem = require('@theokit/sdk/filesystem');
|
|
11
11
|
var sandbox = require('@theokit/sdk/sandbox');
|
|
12
12
|
var child_process = require('child_process');
|
|
13
13
|
var interactive = require('@theokit/sdk/interactive');
|
|
@@ -591,7 +591,18 @@ async function editViaLocal(projectRoot, path, old_string, new_string) {
|
|
|
591
591
|
}
|
|
592
592
|
const outcome = computeEdit(content, old_string, new_string);
|
|
593
593
|
if (!outcome.ok) return JSON.stringify({ ok: false, error: "no_match", path });
|
|
594
|
-
|
|
594
|
+
try {
|
|
595
|
+
await promises.writeFile(`${absolutePath}.bak`, content, {
|
|
596
|
+
encoding: "utf-8",
|
|
597
|
+
flag: fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC | fs.constants.O_NOFOLLOW
|
|
598
|
+
});
|
|
599
|
+
} catch (err) {
|
|
600
|
+
const code = err.code;
|
|
601
|
+
if (code === "ELOOP" || code === "EEXIST") {
|
|
602
|
+
return JSON.stringify({ ok: false, error: "unsafe_backup_path", path });
|
|
603
|
+
}
|
|
604
|
+
throw err;
|
|
605
|
+
}
|
|
595
606
|
await promises.writeFile(absolutePath, outcome.result, "utf-8");
|
|
596
607
|
return JSON.stringify({ ok: true, replacements: 1 });
|
|
597
608
|
}
|
|
@@ -861,15 +872,15 @@ function createGitStatusTool(opts) {
|
|
|
861
872
|
path: zod.z.string().optional().describe("Optional project-relative path to scope the status report.")
|
|
862
873
|
}),
|
|
863
874
|
handler: async ({ path: path$1 }, ctx) => {
|
|
864
|
-
if (!fs.existsSync(path.join(projectRoot, ".git"))) {
|
|
865
|
-
return JSON.stringify({ ok: false, error: "not_a_repo" });
|
|
866
|
-
}
|
|
867
875
|
const scopeCheck = checkPathScope(path$1, projectRoot);
|
|
868
876
|
if (scopeCheck !== null) return scopeCheck;
|
|
869
877
|
const args = buildArgs(path$1, opts.includeBranch !== false);
|
|
870
878
|
if (opts.sandbox !== void 0) {
|
|
871
879
|
return statusViaSandbox(opts.sandbox, ctx, args, timeoutMs);
|
|
872
880
|
}
|
|
881
|
+
if (!fs.existsSync(path.join(projectRoot, ".git"))) {
|
|
882
|
+
return JSON.stringify({ ok: false, error: "not_a_repo" });
|
|
883
|
+
}
|
|
873
884
|
const result = await runGitProcess(projectRoot, args, timeoutMs, maxStdoutBytes);
|
|
874
885
|
return formatGitResult(result, timeoutMs);
|
|
875
886
|
}
|
|
@@ -2039,6 +2050,12 @@ function validateVitestScope(path, projectRoot) {
|
|
|
2039
2050
|
}
|
|
2040
2051
|
return checkPathScope(path, projectRoot);
|
|
2041
2052
|
}
|
|
2053
|
+
var VITEST_MISSING = [
|
|
2054
|
+
/npx canceled due to missing packages/i,
|
|
2055
|
+
/could not determine executable to run/i,
|
|
2056
|
+
/vitest: (?:command )?not found/i,
|
|
2057
|
+
/command not found: vitest/i
|
|
2058
|
+
];
|
|
2042
2059
|
function formatVitestResult(result, timeoutMs) {
|
|
2043
2060
|
if (result.kind === "timeout") {
|
|
2044
2061
|
return JSON.stringify({ ok: false, error: "timeout", timeoutMs });
|
|
@@ -2048,6 +2065,9 @@ function formatVitestResult(result, timeoutMs) {
|
|
|
2048
2065
|
}
|
|
2049
2066
|
const summary = extractTrailingJson(result.stdout);
|
|
2050
2067
|
if (summary === null) {
|
|
2068
|
+
if (VITEST_MISSING.some((pattern) => pattern.test(result.stderr))) {
|
|
2069
|
+
return JSON.stringify({ ok: false, error: "no_vitest", detail: result.stderr.slice(0, 500) });
|
|
2070
|
+
}
|
|
2051
2071
|
return JSON.stringify({
|
|
2052
2072
|
ok: false,
|
|
2053
2073
|
error: "unparseable_output",
|
|
@@ -2577,9 +2597,9 @@ function truncateOutput(output, opts) {
|
|
|
2577
2597
|
return { content: output, truncated: false, originalBytes };
|
|
2578
2598
|
}
|
|
2579
2599
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
2580
|
-
const filename = `overflow-${Date.now()}-${crypto.randomUUID()
|
|
2600
|
+
const filename = `overflow-${Date.now()}-${crypto.randomUUID()}.txt`;
|
|
2581
2601
|
const overflowPath = path.join(outputDir, filename);
|
|
2582
|
-
fs.writeFileSync(overflowPath, output, "utf-8");
|
|
2602
|
+
fs.writeFileSync(overflowPath, output, { encoding: "utf-8", flag: "wx" });
|
|
2583
2603
|
const buf = Buffer.from(output, "utf-8");
|
|
2584
2604
|
const trailer = `
|
|
2585
2605
|
|
|
@@ -2661,27 +2681,14 @@ function createViewImageTool(options) {
|
|
|
2661
2681
|
});
|
|
2662
2682
|
}
|
|
2663
2683
|
const absolute = pathSafety.safePathJoin(projectRoot, input.path);
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
bytes = fs.statSync(absolute).size;
|
|
2667
|
-
} catch {
|
|
2668
|
-
return json({ ok: false, error: "not_found", path: input.path });
|
|
2669
|
-
}
|
|
2670
|
-
if (bytes > maxBytes) {
|
|
2671
|
-
return json({
|
|
2672
|
-
ok: false,
|
|
2673
|
-
error: "image_too_large",
|
|
2674
|
-
path: input.path,
|
|
2675
|
-
bytes,
|
|
2676
|
-
limit_bytes: maxBytes
|
|
2677
|
-
});
|
|
2678
|
-
}
|
|
2684
|
+
const outcome = readWithinBudget(absolute, maxBytes);
|
|
2685
|
+
if (!outcome.ok) return json(failureFor(outcome, input.path, maxBytes));
|
|
2679
2686
|
return json({
|
|
2680
2687
|
ok: true,
|
|
2681
2688
|
path: input.path,
|
|
2682
2689
|
media_type: mediaType,
|
|
2683
|
-
bytes,
|
|
2684
|
-
data:
|
|
2690
|
+
bytes: outcome.bytes,
|
|
2691
|
+
data: outcome.data
|
|
2685
2692
|
});
|
|
2686
2693
|
},
|
|
2687
2694
|
/**
|
|
@@ -2707,6 +2714,31 @@ function createViewImageTool(options) {
|
|
|
2707
2714
|
}
|
|
2708
2715
|
});
|
|
2709
2716
|
}
|
|
2717
|
+
function readWithinBudget(absolute, maxBytes) {
|
|
2718
|
+
let fd;
|
|
2719
|
+
try {
|
|
2720
|
+
fd = fs.openSync(absolute, "r");
|
|
2721
|
+
} catch {
|
|
2722
|
+
return { ok: false, error: "not_found" };
|
|
2723
|
+
}
|
|
2724
|
+
try {
|
|
2725
|
+
const bytes = fs.fstatSync(fd).size;
|
|
2726
|
+
if (bytes > maxBytes) return { ok: false, error: "too_large", bytes };
|
|
2727
|
+
return { ok: true, bytes, data: fs.readFileSync(fd).toString("base64") };
|
|
2728
|
+
} finally {
|
|
2729
|
+
fs.closeSync(fd);
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
function failureFor(outcome, path, maxBytes) {
|
|
2733
|
+
if (outcome.error === "not_found") return { ok: false, error: "not_found", path };
|
|
2734
|
+
return {
|
|
2735
|
+
ok: false,
|
|
2736
|
+
error: "image_too_large",
|
|
2737
|
+
path,
|
|
2738
|
+
bytes: outcome.bytes,
|
|
2739
|
+
limit_bytes: maxBytes
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2710
2742
|
var DEFAULT_TIMEOUT_MS4 = 3e4;
|
|
2711
2743
|
var MAX_BODY_BYTES = 1 * 1024 * 1024;
|
|
2712
2744
|
function createWebFetchTool(opts) {
|