stitchkit 0.70.0 → 0.70.1
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/dist/agent-runtime/coding-tool-files.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-paths.d.ts +0 -1
- package/dist/agent-runtime/coding-tool-paths.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-search-patch.d.ts.map +1 -1
- package/dist/agent-runtime/contained-files.d.ts +30 -7
- package/dist/agent-runtime/contained-files.d.ts.map +1 -1
- package/dist/agent-runtime-coding-tools.js +29 -32
- package/dist/agent-runtime-harness.js +1 -1
- package/dist/application/server-resource.d.ts +1 -0
- package/dist/application/server-resource.d.ts.map +1 -1
- package/dist/application.js +5 -2
- package/dist/index-1z5xxfst.js +397 -0
- package/dist/{index-38hs3a58.js → index-4g196jmf.js} +4 -1
- package/dist/{index-dk6e56g0.js → index-7wpgrqa8.js} +37 -2
- package/dist/{index-6wzr93cg.js → index-w0445741.js} +1 -1
- package/dist/node.js +22 -2
- package/dist/server/bun.d.ts.map +1 -1
- package/dist/server/index.js +10 -2
- package/dist/server/node.d.ts.map +1 -1
- package/dist/server/process-signals.d.ts.map +1 -1
- package/dist/server/shutdown.d.ts +2 -0
- package/dist/server/shutdown.d.ts.map +1 -1
- package/dist/testing.js +2 -2
- package/llms-full.txt +22 -13
- package/native/darwin-arm64.node +0 -0
- package/native/darwin-x64.node +0 -0
- package/package.json +3 -1
- package/dist/index-xbtcszs7.js +0 -202
package/llms-full.txt
CHANGED
|
@@ -953,21 +953,26 @@ const server = createServer({ services, socket })
|
|
|
953
953
|
|
|
954
954
|
const result = await server.shutdown({
|
|
955
955
|
gracePeriodMs: 30_000,
|
|
956
|
+
realtimeCloseTimeoutMs: 1_000,
|
|
956
957
|
forceTimeoutMs: 5_000,
|
|
957
958
|
retryAfterSeconds: 5,
|
|
958
959
|
signal: shutdownController.signal,
|
|
959
960
|
})
|
|
960
961
|
```
|
|
961
962
|
|
|
962
|
-
The first call closes HTTP and Socket.IO admission, then gives the
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
963
|
+
The first call closes HTTP and Socket.IO admission, then gives admitted HTTP/application work the
|
|
964
|
+
full `gracePeriodMs` budget. Once that work drains, `realtimeCloseTimeoutMs` (default `1_000`)
|
|
965
|
+
bounds WebSocket close handshakes inside the same outer deadline. Any upgraded sockets still open
|
|
966
|
+
at that boundary are terminated without shortening HTTP grace, and graceful runtime shutdown
|
|
967
|
+
continues. If the outer grace budget or external signal forces destructive teardown,
|
|
968
|
+
`forceTimeoutMs` bounds physical completion separately. Repeated calls return the same Promise;
|
|
966
969
|
the first options win. New
|
|
967
970
|
ordinary HTTP work receives `503`, `Retry-After` and `Connection: close` outside
|
|
968
971
|
`wrapFetch`. `result.outcome` is `clean` or `forced`; a forced result preserves
|
|
969
972
|
the pending snapshot and reason while final pending counters describe the
|
|
970
|
-
post-close transport state.
|
|
973
|
+
post-close transport state. `forcedWebSockets` counts both sockets terminated at the dedicated
|
|
974
|
+
realtime bound and sockets terminated by outer force; `pendingWebSocketsAtForce` is only the latter
|
|
975
|
+
snapshot, so a clean result can truthfully report a bounded realtime termination. A graceful phase error still runs forced cleanup and
|
|
971
976
|
then rejects with the original error; a forced transport that cannot confirm
|
|
972
977
|
completion before `forceTimeoutMs` rejects instead of reporting a false zero.
|
|
973
978
|
`runtime` is a diagnostics escape hatch, not a second canonical stop path.
|
|
@@ -4069,7 +4074,9 @@ required host authorization callback. File paths are relative, bounded and conta
|
|
|
4069
4074
|
descriptor-relative resolution: each ancestor is opened without following symlinks and remains
|
|
4070
4075
|
pinned through authorization and the filesystem effect. Reads revalidate the pinned file identity;
|
|
4071
4076
|
writes and patches revalidate the pinned parent identity; search and resource discovery descend
|
|
4072
|
-
only through opened directories. Linux
|
|
4077
|
+
only through opened directories. Linux traverses `/proc/self/fd`; macOS uses the package's
|
|
4078
|
+
architecture-specific Node-API `openat` backend. Unsupported platforms, including FreeBSD until a
|
|
4079
|
+
real backend is qualified, fail closed. The package needs no install-time compiler or script.
|
|
4073
4080
|
Other platforms fail these filesystem operations closed because Node exposes no equivalent
|
|
4074
4081
|
portable directory-handle-relative API. Writes/edits reject symlink targets, content is strict
|
|
4075
4082
|
UTF-8 and retained bytes are finite. Shell accepts a finite alias mapped by the host to an absolute executable plus an
|
|
@@ -8899,11 +8906,13 @@ current one *up to* your target, and apply each snippet.
|
|
|
8899
8906
|
### Descriptor-backed Agent filesystem containment
|
|
8900
8907
|
|
|
8901
8908
|
Built-in coding file/search tools and `createAgentHarnessFileResources` now require a runtime
|
|
8902
|
-
that can address an opened directory descriptor: Linux `/proc/self/fd`, or macOS
|
|
8903
|
-
|
|
8909
|
+
that can address an opened directory descriptor: Linux `/proc/self/fd`, or the packaged macOS
|
|
8910
|
+
Node-API backend added in 0.70.1. This is what keeps a mutable parent rename or outside-symlink replacement from changing
|
|
8904
8911
|
the authorized target between validation and the actual effect.
|
|
8905
8912
|
|
|
8906
|
-
No call-site change is needed on those platforms.
|
|
8913
|
+
No call-site change is needed on those platforms. The 0.70.0 notes incorrectly inferred macOS and
|
|
8914
|
+
FreeBSD support from `/dev/fd`; macOS is restored by 0.70.1 and FreeBSD remains unsupported. On
|
|
8915
|
+
Windows or another platform without that
|
|
8907
8916
|
boundary, move the built-in filesystem operations to a supported worker or replace them with
|
|
8908
8917
|
application-owned tools backed by an equivalent native handle API. They fail closed rather than
|
|
8909
8918
|
falling back to path spelling. `run_command` remains separately available under its explicit
|
|
@@ -11697,9 +11706,9 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
11697
11706
|
| `UnixListenConfig` | _type_ | unix listener — a socket path, or `{ path, mode }` to tighten the file mode after listen |
|
|
11698
11707
|
| `BunServerHandle` | _type_ | managed Bun handle (`url`, `port`, `runtime`, `status`, `shutdown`) |
|
|
11699
11708
|
| `ManagedServerHandle` | _type_ | shared lifecycle shape generic over the runtime escape hatch |
|
|
11700
|
-
| `ShutdownOptionsSchema` / `ShutdownOptions` | schema / _type_ |
|
|
11709
|
+
| `ShutdownOptionsSchema` / `ShutdownOptions` | schema / _type_ | HTTP/application grace, a separate WebSocket close-handshake bound, bounded forced-completion timeout, retry hint and optional external abort signal |
|
|
11701
11710
|
| `ShutdownStatusSchema` / `ShutdownStatus` | schema / _type_ | live state and request/WebSocket counters |
|
|
11702
|
-
| `ShutdownResultSchema` / `ShutdownResult` | schema / _type_ | clean/forced result with final counters
|
|
11711
|
+
| `ShutdownResultSchema` / `ShutdownResult` | schema / _type_ | clean/forced result with final counters, outer-force snapshots and `forcedWebSockets` including bounded realtime terminations |
|
|
11703
11712
|
| `ShutdownStateSchema` / `ShutdownState` | schema / _type_ | managed lifecycle state machine |
|
|
11704
11713
|
| `ServiceDef` | _type_ | the result of `implement` |
|
|
11705
11714
|
| `MethodDef` | _type_ | one resolved endpoint inside a service |
|
|
@@ -11887,7 +11896,7 @@ cutovers are covered by the executable
|
|
|
11887
11896
|
| `ManagedResourceDependency` | _type_ | a dependency named by id or given as the resource itself — the second form is what `context.use(...)` can type |
|
|
11888
11897
|
| `ManagedResourcePublished` | _type_ | the value type `context.use(resource)` returns, recovered from that resource's own `start` |
|
|
11889
11898
|
| `ManagedResourcePublishesNoValue` | _type_ | what `context.use(...)` returns for a resource that publishes nothing — a branded refusal rather than `never`, so reading it does not silently compile |
|
|
11890
|
-
| `ManagedServerResourceConfig` | _type_ | the server or a sync/async factory receiving `ManagedResourceContext`, plus stable ID, dependencies and shutdown policy
|
|
11899
|
+
| `ManagedServerResourceConfig` | _type_ | the server or a sync/async factory receiving `ManagedResourceContext`, plus stable ID, dependencies and server shutdown policy (`retryAfterSeconds`, `realtimeCloseTimeoutMs`) |
|
|
11891
11900
|
| `ManagedServerResource` | _type_ | the resource `managedServerResource` returns, whose `start` publishes the `ManagedServerHandle` |
|
|
11892
11901
|
| `ApplicationHealthHandlerOptions` / `ApplicationHealthHandlerOptionsSchema` | _type_ / schema | liveness/readiness selection and sanitized `Retry-After` policy |
|
|
11893
11902
|
| `ApplicationOperationalHandlers` / `ApplicationOperationalHandlersOptions` / `ApplicationOperationalHandlersOptionsSchema` | _type_ / schema | conventional status/readiness/liveness route surface and shared retry policy |
|
|
@@ -12244,7 +12253,7 @@ artifact store is supplied, `read_output`.
|
|
|
12244
12253
|
|
|
12245
12254
|
| Export | Kind | Summary |
|
|
12246
12255
|
|--------|------|---------|
|
|
12247
|
-
| `createAgentCodingTools` | function | construct direct host-authorized bounded file, search, guarded patch, shell and artifact runtime-tool definitions; filesystem operations
|
|
12256
|
+
| `createAgentCodingTools` | function | construct direct host-authorized bounded file, search, guarded patch, shell and artifact runtime-tool definitions; filesystem operations use Linux `/proc/self/fd` or the packaged macOS Node-API backend and otherwise fail closed |
|
|
12248
12257
|
| `AgentCodingToolDefinition` | _type_ | peer-free structural direct-tool shape accepted by the canonical runtime-tool surface |
|
|
12249
12258
|
| `AgentCodingToolConfig` | _type_ | absolute root, required authorization callback, finite executable alias map, exact child environment and optional limits |
|
|
12250
12259
|
| `AgentCodingToolAuthorizationSchema` / `AgentCodingToolAuthorization` | schema / _type_ | discriminated read/write/search/patch/shell/artifact decision presented to host policy before effect |
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stitchkit",
|
|
3
|
-
"version": "0.70.
|
|
3
|
+
"version": "0.70.1",
|
|
4
4
|
"description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"types": "./dist/index.d.ts",
|
|
39
39
|
"files": [
|
|
40
40
|
"dist",
|
|
41
|
+
"native/*.node",
|
|
41
42
|
"llms.txt",
|
|
42
43
|
"llms-full.txt",
|
|
43
44
|
"README.md",
|
|
@@ -141,6 +142,7 @@
|
|
|
141
142
|
"access": "public"
|
|
142
143
|
},
|
|
143
144
|
"scripts": {
|
|
145
|
+
"build:native-contained-files": "node scripts/build-contained-files-native.mjs",
|
|
144
146
|
"check": "bun x tsc --noEmit",
|
|
145
147
|
"build:browser": "bun build src/index.ts src/react.ts src/contract/index.ts src/declaration.ts src/agent-runtime-browser.ts --outdir dist --target node --packages external --splitting --root src",
|
|
146
148
|
"build:server": "bun build src/server/index.ts src/node.ts src/tools.ts src/tool-invoker.ts src/cli.ts src/remote.ts src/files.ts src/testing.ts src/observability/index.ts src/agent-runtime.ts src/agent-runtime-harness.ts src/agent-runtime-coding-tools.ts src/agent-runtime-openrouter.ts src/agent-runtime-sqlite-bun.ts src/agent-runtime-sqlite-node.ts src/application.ts src/application-grammy.ts src/application-opentelemetry.ts --outdir dist --target node --packages external --splitting --root src",
|
package/dist/index-xbtcszs7.js
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
// src/agent-runtime/contained-files.ts
|
|
2
|
-
import { constants } from "node:fs";
|
|
3
|
-
import { open, readdir, realpath } from "node:fs/promises";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
function descriptorPath(handle) {
|
|
6
|
-
if (process.platform === "linux")
|
|
7
|
-
return `/proc/self/fd/${handle.fd}`;
|
|
8
|
-
if (process.platform === "darwin" || process.platform === "freebsd") {
|
|
9
|
-
return `/dev/fd/${handle.fd}`;
|
|
10
|
-
}
|
|
11
|
-
throw new Error("Contained filesystem operations require descriptor paths on this platform");
|
|
12
|
-
}
|
|
13
|
-
async function openPinnedDirectory(absolute) {
|
|
14
|
-
const expected = await realpath(absolute);
|
|
15
|
-
const handle = await open(expected, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
16
|
-
try {
|
|
17
|
-
if (await realpath(descriptorPath(handle)) !== expected) {
|
|
18
|
-
throw new Error("Contained directory identity changed while opening");
|
|
19
|
-
}
|
|
20
|
-
return handle;
|
|
21
|
-
} catch (error) {
|
|
22
|
-
await handle.close();
|
|
23
|
-
throw error;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function safeSegments(relative) {
|
|
27
|
-
if (path.isAbsolute(relative))
|
|
28
|
-
throw new Error("Contained paths must be relative");
|
|
29
|
-
const segments = relative.split(/[\\/]/u);
|
|
30
|
-
if (segments.length === 0 || segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
31
|
-
throw new Error("Contained path has invalid segments");
|
|
32
|
-
}
|
|
33
|
-
return segments;
|
|
34
|
-
}
|
|
35
|
-
async function openContainedParent(root, relative) {
|
|
36
|
-
const segments = safeSegments(relative);
|
|
37
|
-
const basename = segments.pop();
|
|
38
|
-
if (!basename)
|
|
39
|
-
throw new Error("Contained path is missing a basename");
|
|
40
|
-
let current = await openPinnedDirectory(root);
|
|
41
|
-
try {
|
|
42
|
-
for (const segment of segments) {
|
|
43
|
-
const next = await open(path.join(descriptorPath(current), segment), constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
44
|
-
const metadata = await next.stat();
|
|
45
|
-
if (!metadata.isDirectory()) {
|
|
46
|
-
await next.close();
|
|
47
|
-
throw new Error("Contained ancestor is not a directory");
|
|
48
|
-
}
|
|
49
|
-
await current.close();
|
|
50
|
-
current = next;
|
|
51
|
-
}
|
|
52
|
-
return { handle: current, path: descriptorPath(current), basename };
|
|
53
|
-
} catch (error) {
|
|
54
|
-
await current.close();
|
|
55
|
-
throw error;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
async function openContainedFile(root, relative) {
|
|
59
|
-
const parent = await openContainedParent(root, relative);
|
|
60
|
-
try {
|
|
61
|
-
const handle = await open(path.join(parent.path, parent.basename), constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
62
|
-
const metadata = await handle.stat();
|
|
63
|
-
if (!metadata.isFile()) {
|
|
64
|
-
await handle.close();
|
|
65
|
-
throw new Error("Contained path is not a regular file");
|
|
66
|
-
}
|
|
67
|
-
return handle;
|
|
68
|
-
} finally {
|
|
69
|
-
await parent.handle.close();
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
function sameIdentity(left, right) {
|
|
73
|
-
return left.dev === right.dev && left.ino === right.ino;
|
|
74
|
-
}
|
|
75
|
-
async function assertContainedFileCurrent(root, relative, expected) {
|
|
76
|
-
const current = await openContainedFile(root, relative);
|
|
77
|
-
try {
|
|
78
|
-
if (!sameIdentity(await expected.stat(), await current.stat())) {
|
|
79
|
-
throw new Error("Contained file identity changed during authorization");
|
|
80
|
-
}
|
|
81
|
-
} finally {
|
|
82
|
-
await current.close();
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
async function assertContainedParentCurrent(root, relative, expected) {
|
|
86
|
-
const current = await openContainedParent(root, relative);
|
|
87
|
-
try {
|
|
88
|
-
if (!sameIdentity(await expected.stat(), await current.handle.stat())) {
|
|
89
|
-
throw new Error("Contained parent identity changed during authorization");
|
|
90
|
-
}
|
|
91
|
-
} finally {
|
|
92
|
-
await current.handle.close();
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
async function readContainedUtf8Handle(handle, maxBytes) {
|
|
96
|
-
const metadata = await handle.stat();
|
|
97
|
-
if (!metadata.isFile())
|
|
98
|
-
throw new Error("Contained path is not a regular file");
|
|
99
|
-
if (metadata.size > maxBytes)
|
|
100
|
-
throw new Error("Contained file exceeds byte budget");
|
|
101
|
-
const buffer = Buffer.alloc(metadata.size);
|
|
102
|
-
let offset = 0;
|
|
103
|
-
while (offset < buffer.byteLength) {
|
|
104
|
-
const { bytesRead } = await handle.read(buffer, offset, buffer.byteLength - offset, offset);
|
|
105
|
-
if (bytesRead === 0)
|
|
106
|
-
break;
|
|
107
|
-
offset += bytesRead;
|
|
108
|
-
}
|
|
109
|
-
if (offset !== metadata.size)
|
|
110
|
-
throw new Error("Contained file changed while being read");
|
|
111
|
-
return {
|
|
112
|
-
text: new TextDecoder("utf-8", { fatal: true }).decode(buffer),
|
|
113
|
-
bytes: offset,
|
|
114
|
-
mode: metadata.mode
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
async function walkContainedFiles(input) {
|
|
118
|
-
const scan = await scanContainedFiles({ ...input, symlinks: "refuse" });
|
|
119
|
-
if (scan.truncated)
|
|
120
|
-
throw new Error("Contained file traversal exceeded its bounds");
|
|
121
|
-
return scan.files;
|
|
122
|
-
}
|
|
123
|
-
async function scanContainedFiles(input) {
|
|
124
|
-
const files = [];
|
|
125
|
-
let truncated = false;
|
|
126
|
-
let skippedDirectories = 0;
|
|
127
|
-
let skippedSymlinks = 0;
|
|
128
|
-
const root = await openPinnedDirectory(input.root);
|
|
129
|
-
const visit = async (directory, relativeDirectory, depth) => {
|
|
130
|
-
if (truncated)
|
|
131
|
-
return;
|
|
132
|
-
if (depth > input.maxDepth) {
|
|
133
|
-
truncated = true;
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
const entries = await readdir(descriptorPath(directory), { withFileTypes: true });
|
|
137
|
-
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
138
|
-
for (const entry of entries) {
|
|
139
|
-
const relative = relativeDirectory ? path.join(relativeDirectory, entry.name) : entry.name;
|
|
140
|
-
const anchored = path.join(descriptorPath(directory), entry.name);
|
|
141
|
-
if (entry.isSymbolicLink()) {
|
|
142
|
-
if (input.symlinks === "refuse") {
|
|
143
|
-
throw new Error(`Contained file traversal refuses symlink: ${relative}`);
|
|
144
|
-
}
|
|
145
|
-
skippedSymlinks += 1;
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
if (entry.isDirectory()) {
|
|
149
|
-
if (input.excludeDirectory?.(relative)) {
|
|
150
|
-
skippedDirectories += 1;
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
let child;
|
|
154
|
-
try {
|
|
155
|
-
child = await open(anchored, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
156
|
-
} catch (error) {
|
|
157
|
-
if (input.symlinks === "skip") {
|
|
158
|
-
skippedSymlinks += 1;
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
throw error;
|
|
162
|
-
}
|
|
163
|
-
try {
|
|
164
|
-
await visit(child, relative, depth + 1);
|
|
165
|
-
} finally {
|
|
166
|
-
await child.close();
|
|
167
|
-
}
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
if (!entry.isFile())
|
|
171
|
-
continue;
|
|
172
|
-
if (files.length >= input.maxFiles) {
|
|
173
|
-
truncated = true;
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
let content;
|
|
177
|
-
if (input.readMaxBytes !== undefined) {
|
|
178
|
-
try {
|
|
179
|
-
const handle = await open(anchored, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
180
|
-
try {
|
|
181
|
-
content = await readContainedUtf8Handle(handle, input.readMaxBytes);
|
|
182
|
-
} finally {
|
|
183
|
-
await handle.close();
|
|
184
|
-
}
|
|
185
|
-
} catch (error) {
|
|
186
|
-
if (input.skipUnreadable)
|
|
187
|
-
continue;
|
|
188
|
-
throw error;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
files.push({ absolute: relative, relative, ...content && { content } });
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
try {
|
|
195
|
-
await visit(root, "", 0);
|
|
196
|
-
} finally {
|
|
197
|
-
await root.close();
|
|
198
|
-
}
|
|
199
|
-
return { files, truncated, skippedDirectories, skippedSymlinks };
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export { openContainedParent, openContainedFile, assertContainedFileCurrent, assertContainedParentCurrent, readContainedUtf8Handle, walkContainedFiles, scanContainedFiles };
|