stitchkit 0.103.10 → 0.103.11

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/agent-runtime/coding-shell-process.d.ts.map +1 -1
  3. package/dist/agent-runtime/sandbox-process-owner.d.ts.map +1 -1
  4. package/dist/agent-runtime/store-conformance-lifecycle.d.ts +10 -10
  5. package/dist/entrypoints/agent-runtime/coding-tools.js +2 -1
  6. package/dist/entrypoints/agent-runtime/sandbox.js +28 -14
  7. package/dist/entrypoints/application/diagnostic-journal.js +2 -2
  8. package/dist/entrypoints/files.js +8 -2
  9. package/dist/entrypoints/process.js +1 -1
  10. package/dist/index-32jkwkdm.js +565 -0
  11. package/dist/{index-a4ajxwmb.js → index-g4jxe7ve.js} +113 -131
  12. package/dist/{index-ga597cvy.js → index-ttf0y7b8.js} +85 -23
  13. package/dist/internal/exclusive-lock-read.d.ts +20 -0
  14. package/dist/internal/exclusive-lock-read.d.ts.map +1 -0
  15. package/dist/internal/exclusive-lock.d.ts +1 -0
  16. package/dist/internal/exclusive-lock.d.ts.map +1 -1
  17. package/dist/internal/with-exclusive-lock.d.ts.map +1 -1
  18. package/dist/process/command-owner.d.ts +7 -2
  19. package/dist/process/command-owner.d.ts.map +1 -1
  20. package/dist/process/contract.d.ts +12 -1
  21. package/dist/process/contract.d.ts.map +1 -1
  22. package/dist/process/group.d.ts +6 -1
  23. package/dist/process/group.d.ts.map +1 -1
  24. package/dist/process/launch.d.ts +65 -0
  25. package/dist/process/launch.d.ts.map +1 -0
  26. package/dist/process/leader-settlement.d.ts +2 -0
  27. package/dist/process/leader-settlement.d.ts.map +1 -1
  28. package/dist/process/start-failure.d.ts +7 -0
  29. package/dist/process/start-failure.d.ts.map +1 -0
  30. package/llms/agent-runtime.txt +7 -1
  31. package/llms/process.txt +42 -4
  32. package/llms-full.txt +49 -5
  33. package/llms.txt +2 -2
  34. package/package.json +2 -2
  35. package/dist/index-6m6a9ty4.js +0 -285
@@ -0,0 +1,65 @@
1
+ import { type ChildProcessWithoutNullStreams } from 'node:child_process';
2
+ import { type ParsedNativeCommandOptions } from './contract.js';
3
+ /** A structural host launcher may supply pipes without importing Node ambient types. */
4
+ export interface NativeCommandLaunchedProcess {
5
+ readonly pid?: number;
6
+ readonly exitCode: number | null;
7
+ readonly signalCode: string | null;
8
+ readonly stdout: NativeCommandLaunchedOutput;
9
+ readonly stderr: NativeCommandLaunchedOutput;
10
+ kill(signal: 'SIGKILL'): boolean;
11
+ on(event: 'error', listener: (error: Error) => void): unknown;
12
+ on(event: 'exit' | 'close', listener: (code: number | null, signal: string | null) => void): unknown;
13
+ }
14
+ interface NativeCommandLaunchedOutput {
15
+ readonly destroyed: boolean;
16
+ on(event: 'data', listener: (chunk: Uint8Array) => void): unknown;
17
+ destroy(): unknown;
18
+ }
19
+ /** Internal transport selection; application policy never enters the public command contract. */
20
+ export interface NativeCommandLaunchDriver {
21
+ launch?: () => NativeCommandLaunchedProcess;
22
+ maxBufferedBytes?: number;
23
+ group?: boolean;
24
+ force?: boolean;
25
+ cleanupTimeoutMs?: number;
26
+ timeoutMs?: number;
27
+ closeTimeoutMs?: number;
28
+ }
29
+ /** Driver overrides retain the same finite, safe-integer budget admission. */
30
+ export declare function nativeCommandBudgets(options: ParsedNativeCommandOptions, driver: NativeCommandLaunchDriver | undefined): {
31
+ cleanupTimeoutMs: number;
32
+ timeoutMs: number | undefined;
33
+ };
34
+ interface NativeCommandOwner {
35
+ result: Promise<unknown>;
36
+ stop(): Promise<void>;
37
+ terminate(): Promise<void>;
38
+ }
39
+ /** A launcher and its admission owner share the execution owner's shutdown barrier. */
40
+ export declare function registerNativeCommandOwner(child: object, owner: NativeCommandOwner): void;
41
+ export declare function nativeCommandOwner(child: object): NativeCommandOwner | undefined;
42
+ /** Install every observation before starting stdin or invoking an admission callback. */
43
+ export declare function launchNativeCommand(options: ParsedNativeCommandOptions, driver: NativeCommandLaunchDriver | undefined, onSpawn: ((child: ChildProcessWithoutNullStreams) => void) | undefined, onFailure: (error: Error) => void, onInputFailure: (error: Error) => void): {
44
+ child: NativeCommandLaunchedProcess;
45
+ stdout: AsyncIterable<Uint8Array<ArrayBufferLike>>;
46
+ stderr: AsyncIterable<Uint8Array<ArrayBufferLike>>;
47
+ leader: Promise<{
48
+ kind: "exit";
49
+ exitCode: number | null;
50
+ signal: string | null;
51
+ } | {
52
+ kind: "error";
53
+ cause: unknown;
54
+ }>;
55
+ closed: Promise<{
56
+ exitCode: number | null;
57
+ signal: string | null;
58
+ }>;
59
+ released: Promise<[boolean, ...boolean[]]>;
60
+ pendingHandles: Set<string>;
61
+ start(): void;
62
+ destroy(): void;
63
+ };
64
+ export {};
65
+ //# sourceMappingURL=launch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../../src/process/launch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,8BAA8B,EAAS,MAAM,oBAAoB,CAAC;AAEhF,OAAO,EAGL,KAAK,0BAA0B,EAChC,MAAM,YAAY,CAAC;AAEpB,wFAAwF;AACxF,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,2BAA2B,CAAC;IAC7C,QAAQ,CAAC,MAAM,EAAE,2BAA2B,CAAC;IAC7C,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IACjC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC;IAC9D,EAAE,CACA,KAAK,EAAE,MAAM,GAAG,OAAO,EACvB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAC7D,OAAO,CAAC;CACZ;AAED,UAAU,2BAA2B;IACnC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC;IAClE,OAAO,IAAI,OAAO,CAAC;CACpB;AAED,iGAAiG;AACjG,MAAM,WAAW,yBAAyB;IACxC,MAAM,CAAC,EAAE,MAAM,4BAA4B,CAAC;IAC5C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,8EAA8E;AAC9E,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,0BAA0B,EACnC,MAAM,EAAE,yBAAyB,GAAG,SAAS;;;EAY9C;AAED,UAAU,kBAAkB;IAC1B,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IACzB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAGD,uFAAuF;AACvF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAEzF;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAEhF;AA4ED,yFAAyF;AACzF,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,0BAA0B,EACnC,MAAM,EAAE,yBAAyB,GAAG,SAAS,EAC7C,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,8BAA8B,KAAK,IAAI,CAAC,GAAG,SAAS,EACtE,SAAS,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,EACjC,cAAc,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI;;;;;;;;;;;;;kBAuBC,MAAM,GAAG,IAAI;gBAAU,MAAM,GAAG,IAAI;;;;;;EA0D5E"}
@@ -1,4 +1,6 @@
1
1
  import type { NativeCommandSettlement, ParsedNativeCommandOptions } from './contract.js';
2
+ /** An internal caller may bound drain/close after leader settlement without changing generic drain. */
3
+ export declare function createLeaderCloseDeadline(leader: Promise<NativeCommandSettlement>, timeoutMs: number | undefined, controller: AbortController): () => void;
2
4
  /** One bounded external resource settlement, shared by observed exit and terminal failure. */
3
5
  export declare function createLeaderSettlement(options: ParsedNativeCommandOptions, signal: AbortSignal): (event: NativeCommandSettlement) => Promise<void>;
4
6
  //# sourceMappingURL=leader-settlement.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"leader-settlement.d.ts","sourceRoot":"","sources":["../../src/process/leader-settlement.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAGtF,8FAA8F;AAC9F,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,0BAA0B,EACnC,MAAM,EAAE,WAAW,WAGJ,uBAAuB,KAAG,OAAO,CAAC,IAAI,CAAC,CA+BvD"}
1
+ {"version":3,"file":"leader-settlement.d.ts","sourceRoot":"","sources":["../../src/process/leader-settlement.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAGtF,uGAAuG;AACvG,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,OAAO,CAAC,uBAAuB,CAAC,EACxC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,UAAU,EAAE,eAAe,cAmB5B;AAED,8FAA8F;AAC9F,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,0BAA0B,EACnC,MAAM,EAAE,WAAW,WAGJ,uBAAuB,KAAG,OAAO,CAAC,IAAI,CAAC,CAmCvD"}
@@ -0,0 +1,7 @@
1
+ import { type NativeCommandSettlement } from './contract.js';
2
+ /** A failed native admission still owns one bounded external-resource settlement. */
3
+ export declare function failedCommandStart(cause: unknown, settle: (event: NativeCommandSettlement) => Promise<void>): {
4
+ result: Promise<never>;
5
+ stop(): Promise<void>;
6
+ };
7
+ //# sourceMappingURL=start-failure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start-failure.d.ts","sourceRoot":"","sources":["../../src/process/start-failure.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAG9E,qFAAqF;AACrF,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC;;;EAyB1D"}
@@ -19,7 +19,7 @@ description: Configure Stitchkit's optional durable history, stream loop, run co
19
19
  type: architecture
20
20
  status: active
21
21
  created: 2026-08-22
22
- updated: 2026-09-28 18:39 +07:00
22
+ updated: 2026-10-02 16:28 +07:00
23
23
  ---
24
24
 
25
25
  # Agent application runtime
@@ -376,6 +376,12 @@ second host executable, so Windows kills the direct child, destroys retained pip
376
376
  deadline and makes no descendant-cleanup claim. A signal already aborted before execution returns
377
377
  `cancelled` without spawning.
378
378
 
379
+ Coding commands compose the shared native execution owner. Artifact retention and UTF-8
380
+ previews remain coding-tool policy; normal leader exit still forcibly stops remaining POSIX
381
+ group members before draining. A structural sandbox launcher uses the same settlement owner
382
+ with a finite output queue. Sandbox shutdown waits for actual owned completion and surfaces
383
+ cleanup refusal rather than treating it as successful teardown.
384
+
379
385
  One `createAgentHarnessFileResources` instance represents one immutable discovery generation:
380
386
  concurrent and repeated `load()` calls share it, so a direct resource read cannot cross into a
381
387
  new catalog unexpectedly. Discovery uses the same descriptor-anchored traversal as coding search;
package/llms/process.txt CHANGED
@@ -15,7 +15,7 @@ description: Native IO для Bun и Node, с явно выбранной durabi
15
15
  type: guide
16
16
  status: active
17
17
  created: 2026-10-01 20:44 +07:00
18
- updated: 2026-10-02 00:37 +07:00
18
+ updated: 2026-10-02 16:28 +07:00
19
19
  participants:
20
20
  - role: authored
21
21
  harness: Codex
@@ -131,6 +131,32 @@ await runNativeCommand({
131
131
  означает пустой environment; `ambient` явно разрешает наследование. Cwd/executable policy остаётся
132
132
  приложению; этот primitive запускает native executable и сам не создаёт shell.
133
133
 
134
+ Машинная классификация limit error использует `NativeCommandError.reason`: `deadline` при
135
+ истечении собственного `timeoutMs`, `output-budget` при превышении суммарных bytes stdout+stderr
136
+ в capture или streaming. Оба случая сохраняют `code: 'COMMAND_LIMIT'`; `message` служит
137
+ диагностике, не протоколу. У `COMMAND_UNAVAILABLE` и `COMMAND_CLEANUP` reason отсутствует;
138
+ caller abort и sink failure сохраняют исходную ошибку. При cleanup failure первоначальный
139
+ limit вместе с reason остаётся внутри `AggregateError` cause.
140
+
141
+ ```ts
142
+ import { NativeCommandError } from 'stitchkit/process'
143
+
144
+ try {
145
+ await runNativeCommand({ executable: '/usr/bin/git', args: ['status'], timeoutMs: 5000 })
146
+ } catch (error) {
147
+ if (error instanceof NativeCommandError && error.code === 'COMMAND_LIMIT') {
148
+ if (error.reason === 'deadline') console.error('Command timed out')
149
+ if (error.reason === 'output-budget') console.error('Command produced too many bytes')
150
+ }
151
+ throw error
152
+ }
153
+ ```
154
+
155
+ Конструктор сохраняет вызов `new NativeCommandError(code, message, { cause })` и принимает
156
+ `{ cause, reason: 'deadline' | 'output-budget' }` только для `COMMAND_LIMIT`. Owner всегда
157
+ заполняет reason для собственных limits. Созданная вызывающей стороной ошибка без reason
158
+ не получает выдуманную причину из текста; её reason остаётся `undefined`.
159
+
134
160
  Вывод читается с backpressure отдельно для stdout/stderr: один выполняющийся sink на канал плюс
135
161
  bounded native Readable buffer; накопления всей истории при streaming нет. Callback получает
136
162
  lifetime signal. Abort освобождает ожидание даже игнорирующего signal callback, но не способен
@@ -164,7 +190,11 @@ packed размера и dependency closure.
164
190
  close. A resource owner can stop its external scope there, allowing pipe drain to complete.
165
191
  The callback runs once: `event.kind === 'exit'` carries observed `exitCode`/`signal`; a terminal
166
192
  failure uses `kind: 'error'` and preserves its cause. Unavailable executables, output sink
167
- failure and caller cancellation settle through the same owner. `cleanupTimeoutMs` bounds
193
+ failure and caller cancellation settle through the same owner. Synchronous native launch
194
+ failures such as `E2BIG` also return a rejected result after this bounded settlement;
195
+ schema-invalid and already-aborted inputs refuse before native ownership and do not invoke
196
+ the hook. A failing hook preserves both the initial launch error and cleanup cause.
197
+ `cleanupTimeoutMs` bounds
168
198
  the callback after settlement starts; it never becomes a deadline for signal-only execution.
169
199
  Honor the settlement signal; arbitrary user promises cannot be forcibly cancelled.
170
200
 
@@ -184,6 +214,14 @@ caller established: `matched`, `different-boot`, `reused-pid`, `pid-gone`, `lega
184
214
  `unavailable`. Missing/partial/denied evidence is not death. Supervisor tree fencing remains
185
215
  the caller's job; a PID lifetime is not proof that its whole workload stopped.
186
216
 
217
+ Exclusive acquisition checks cancellation again before invoking the protected callback;
218
+ an aborted acquisition releases its newly owned lock without starting the callback.
219
+ Existing lock and reclaim-guard records are opened with nonblocking/no-follow flags,
220
+ must be regular files with one link, and are capped at 16 KiB. Descriptor metadata must
221
+ remain stable during the read. Unsafe records are not evidence of absence or permission
222
+ to reclaim. This bounds local record reads; it does not make a hung remote-filesystem
223
+ syscall interruptible or provide a snapshot against a hostile writer.
224
+
187
225
  An exclusive lock applies exact requested permissions through its creating descriptor,
188
226
  independent of umask. Default `0600` stays private. Shared `0640` requires a common group and
189
227
  traversable directories; another UID also needs directory write permission to reclaim a
@@ -234,9 +272,9 @@ Native POSIX Bun/Node one-shot command execution. See [native IO](../guide/nativ
234
272
  | `runNativeCommand` | function | caller-lifetime streaming or explicitly bounded binary capture |
235
273
  | `NativeCommandOptions` | type | executable/args, env policy, signal/deadline, bytes sink and finite cleanup limits |
236
274
  | `NativeCommandResult` | type | observed nullable exit code/signal and captured stdout/stderr bytes |
237
- | `NativeCommandError` | class | COMMAND_LIMIT, COMMAND_UNAVAILABLE or COMMAND_CLEANUP with internal cause |
275
+ | `NativeCommandError` | class | COMMAND_LIMIT with machine reason `deadline` or `output-budget`; COMMAND_UNAVAILABLE / COMMAND_CLEANUP have no limit reason. Preserves internal cause and existing constructor; caller-created limits without supplied reason remain unclassified |
238
276
 
239
- | `NativeCommandSettlement` | type | observed leader `exit` or terminal `error`; `onLeaderSettled` runs once before drain, bounded by cleanupTimeoutMs, with a cancellable settlement signal |
277
+ | `NativeCommandSettlement` | type | observed leader `exit` or terminal `error`, including synchronous native launch failure; `onLeaderSettled` runs once before drain, bounded by cleanupTimeoutMs, with a cancellable settlement signal; schema/pre-abort refusal creates no settlement obligation |
240
278
  | `observeProcessInstance` / `ProcessInstanceObservation` | function / type | Linux/Darwin lifetime observation; observed identity or unavailable with its original cause, never guessed absence |
241
279
  | `ProcessInstanceSchema` / `ProcessInstance` | schema / type | platform, boot ID, namespace and process start identity from the shared native reader |
242
280
  | `probeProcessOwner` / `ProcessOwnerEvidence` | function / type | compare lifetimes on one caller-established machine; matched, boot change, reused PID, gone, legacy or unavailable with cause |
package/llms-full.txt CHANGED
@@ -5119,7 +5119,7 @@ description: Configure Stitchkit's optional durable history, stream loop, run co
5119
5119
  type: architecture
5120
5120
  status: active
5121
5121
  created: 2026-08-22
5122
- updated: 2026-09-28 18:39 +07:00
5122
+ updated: 2026-10-02 16:28 +07:00
5123
5123
  ---
5124
5124
 
5125
5125
  # Agent application runtime
@@ -5476,6 +5476,12 @@ second host executable, so Windows kills the direct child, destroys retained pip
5476
5476
  deadline and makes no descendant-cleanup claim. A signal already aborted before execution returns
5477
5477
  `cancelled` without spawning.
5478
5478
 
5479
+ Coding commands compose the shared native execution owner. Artifact retention and UTF-8
5480
+ previews remain coding-tool policy; normal leader exit still forcibly stops remaining POSIX
5481
+ group members before draining. A structural sandbox launcher uses the same settlement owner
5482
+ with a finite output queue. Sandbox shutdown waits for actual owned completion and surfaces
5483
+ cleanup refusal rather than treating it as successful teardown.
5484
+
5479
5485
  One `createAgentHarnessFileResources` instance represents one immutable discovery generation:
5480
5486
  concurrent and repeated `load()` calls share it, so a direct resource read cannot cross into a
5481
5487
  new catalog unexpectedly. Discovery uses the same descriptor-anchored traversal as coding search;
@@ -8031,7 +8037,7 @@ description: Native IO для Bun и Node, с явно выбранной durabi
8031
8037
  type: guide
8032
8038
  status: active
8033
8039
  created: 2026-10-01 20:44 +07:00
8034
- updated: 2026-10-02 00:37 +07:00
8040
+ updated: 2026-10-02 16:28 +07:00
8035
8041
  participants:
8036
8042
  - role: authored
8037
8043
  harness: Codex
@@ -8147,6 +8153,32 @@ await runNativeCommand({
8147
8153
  означает пустой environment; `ambient` явно разрешает наследование. Cwd/executable policy остаётся
8148
8154
  приложению; этот primitive запускает native executable и сам не создаёт shell.
8149
8155
 
8156
+ Машинная классификация limit error использует `NativeCommandError.reason`: `deadline` при
8157
+ истечении собственного `timeoutMs`, `output-budget` при превышении суммарных bytes stdout+stderr
8158
+ в capture или streaming. Оба случая сохраняют `code: 'COMMAND_LIMIT'`; `message` служит
8159
+ диагностике, не протоколу. У `COMMAND_UNAVAILABLE` и `COMMAND_CLEANUP` reason отсутствует;
8160
+ caller abort и sink failure сохраняют исходную ошибку. При cleanup failure первоначальный
8161
+ limit вместе с reason остаётся внутри `AggregateError` cause.
8162
+
8163
+ ```ts
8164
+ import { NativeCommandError } from 'stitchkit/process'
8165
+
8166
+ try {
8167
+ await runNativeCommand({ executable: '/usr/bin/git', args: ['status'], timeoutMs: 5000 })
8168
+ } catch (error) {
8169
+ if (error instanceof NativeCommandError && error.code === 'COMMAND_LIMIT') {
8170
+ if (error.reason === 'deadline') console.error('Command timed out')
8171
+ if (error.reason === 'output-budget') console.error('Command produced too many bytes')
8172
+ }
8173
+ throw error
8174
+ }
8175
+ ```
8176
+
8177
+ Конструктор сохраняет вызов `new NativeCommandError(code, message, { cause })` и принимает
8178
+ `{ cause, reason: 'deadline' | 'output-budget' }` только для `COMMAND_LIMIT`. Owner всегда
8179
+ заполняет reason для собственных limits. Созданная вызывающей стороной ошибка без reason
8180
+ не получает выдуманную причину из текста; её reason остаётся `undefined`.
8181
+
8150
8182
  Вывод читается с backpressure отдельно для stdout/stderr: один выполняющийся sink на канал плюс
8151
8183
  bounded native Readable buffer; накопления всей истории при streaming нет. Callback получает
8152
8184
  lifetime signal. Abort освобождает ожидание даже игнорирующего signal callback, но не способен
@@ -8180,7 +8212,11 @@ packed размера и dependency closure.
8180
8212
  close. A resource owner can stop its external scope there, allowing pipe drain to complete.
8181
8213
  The callback runs once: `event.kind === 'exit'` carries observed `exitCode`/`signal`; a terminal
8182
8214
  failure uses `kind: 'error'` and preserves its cause. Unavailable executables, output sink
8183
- failure and caller cancellation settle through the same owner. `cleanupTimeoutMs` bounds
8215
+ failure and caller cancellation settle through the same owner. Synchronous native launch
8216
+ failures such as `E2BIG` also return a rejected result after this bounded settlement;
8217
+ schema-invalid and already-aborted inputs refuse before native ownership and do not invoke
8218
+ the hook. A failing hook preserves both the initial launch error and cleanup cause.
8219
+ `cleanupTimeoutMs` bounds
8184
8220
  the callback after settlement starts; it never becomes a deadline for signal-only execution.
8185
8221
  Honor the settlement signal; arbitrary user promises cannot be forcibly cancelled.
8186
8222
 
@@ -8200,6 +8236,14 @@ caller established: `matched`, `different-boot`, `reused-pid`, `pid-gone`, `lega
8200
8236
  `unavailable`. Missing/partial/denied evidence is not death. Supervisor tree fencing remains
8201
8237
  the caller's job; a PID lifetime is not proof that its whole workload stopped.
8202
8238
 
8239
+ Exclusive acquisition checks cancellation again before invoking the protected callback;
8240
+ an aborted acquisition releases its newly owned lock without starting the callback.
8241
+ Existing lock and reclaim-guard records are opened with nonblocking/no-follow flags,
8242
+ must be regular files with one link, and are capped at 16 KiB. Descriptor metadata must
8243
+ remain stable during the read. Unsafe records are not evidence of absence or permission
8244
+ to reclaim. This bounds local record reads; it does not make a hung remote-filesystem
8245
+ syscall interruptible or provide a snapshot against a hostile writer.
8246
+
8203
8247
  An exclusive lock applies exact requested permissions through its creating descriptor,
8204
8248
  independent of umask. Default `0600` stays private. Shared `0640` requires a common group and
8205
8249
  traversable directories; another UID also needs directory write permission to reclaim a
@@ -21191,9 +21235,9 @@ Native POSIX Bun/Node one-shot command execution. See [native IO](../guide/nativ
21191
21235
  | `runNativeCommand` | function | caller-lifetime streaming or explicitly bounded binary capture |
21192
21236
  | `NativeCommandOptions` | type | executable/args, env policy, signal/deadline, bytes sink and finite cleanup limits |
21193
21237
  | `NativeCommandResult` | type | observed nullable exit code/signal and captured stdout/stderr bytes |
21194
- | `NativeCommandError` | class | COMMAND_LIMIT, COMMAND_UNAVAILABLE or COMMAND_CLEANUP with internal cause |
21238
+ | `NativeCommandError` | class | COMMAND_LIMIT with machine reason `deadline` or `output-budget`; COMMAND_UNAVAILABLE / COMMAND_CLEANUP have no limit reason. Preserves internal cause and existing constructor; caller-created limits without supplied reason remain unclassified |
21195
21239
 
21196
- | `NativeCommandSettlement` | type | observed leader `exit` or terminal `error`; `onLeaderSettled` runs once before drain, bounded by cleanupTimeoutMs, with a cancellable settlement signal |
21240
+ | `NativeCommandSettlement` | type | observed leader `exit` or terminal `error`, including synchronous native launch failure; `onLeaderSettled` runs once before drain, bounded by cleanupTimeoutMs, with a cancellable settlement signal; schema/pre-abort refusal creates no settlement obligation |
21197
21241
  | `observeProcessInstance` / `ProcessInstanceObservation` | function / type | Linux/Darwin lifetime observation; observed identity or unavailable with its original cause, never guessed absence |
21198
21242
  | `ProcessInstanceSchema` / `ProcessInstance` | schema / type | platform, boot ID, namespace and process start identity from the shared native reader |
21199
21243
  | `probeProcessOwner` / `ProcessOwnerEvidence` | function / type | compare lifetimes on one caller-established machine; matched, boot change, reused PID, gone, legacy or unavailable with cause |
package/llms.txt CHANGED
@@ -23,7 +23,7 @@ Load the slice for each entrypoint your code imports — `import … from 'stitc
23
23
  - **stitchkit/primitives**: [llms/primitives.txt](llms/primitives.txt) 11.2 KB
24
24
  - **stitchkit/server**: [llms/server.txt](llms/server.txt) 45.3 KB → [llms/server.2.txt](llms/server.2.txt) 14.6 KB → [llms/server.3.txt](llms/server.3.txt) 45.6 KB → [llms/server.4.txt](llms/server.4.txt) 38.1 KB → [llms/server.5.txt](llms/server.5.txt) 21.8 KB
25
25
  - **stitchkit/observability**: [llms/observability.txt](llms/observability.txt) 44.8 KB
26
- - **stitchkit/agent-runtime**: [llms/agent-runtime.txt](llms/agent-runtime.txt) 36.8 KB → [llms/agent-runtime.2.txt](llms/agent-runtime.2.txt) 46.7 KB → [llms/agent-runtime.3.txt](llms/agent-runtime.3.txt) 34.3 KB
26
+ - **stitchkit/agent-runtime**: [llms/agent-runtime.txt](llms/agent-runtime.txt) 37.2 KB → [llms/agent-runtime.2.txt](llms/agent-runtime.2.txt) 46.7 KB → [llms/agent-runtime.3.txt](llms/agent-runtime.3.txt) 34.3 KB
27
27
  - **stitchkit/agent-runtime/testing**: [llms/agent-runtime-testing.txt](llms/agent-runtime-testing.txt) 2.6 KB
28
28
  - **stitchkit/agent-runtime/harness**: [llms/agent-runtime-harness.txt](llms/agent-runtime-harness.txt) 10.1 KB
29
29
  - **stitchkit/agent-runtime/coding-tools**: [llms/agent-runtime-coding-tools.txt](llms/agent-runtime-coding-tools.txt) 4.9 KB
@@ -55,7 +55,7 @@ Load the slice for each entrypoint your code imports — `import … from 'stitc
55
55
  - **stitchkit/agent-runtime/react**: [llms/agent-runtime-react.txt](llms/agent-runtime-react.txt) 1.0 KB
56
56
  - **stitchkit/agent-runtime/realtime**: [llms/agent-runtime-realtime.txt](llms/agent-runtime-realtime.txt) 1.8 KB
57
57
  - **stitchkit/agent-runtime/harness-tools**: [llms/agent-runtime-harness-tools.txt](llms/agent-runtime-harness-tools.txt) 1.3 KB
58
- - **stitchkit/process**: [llms/process.txt](llms/process.txt) 15.9 KB
58
+ - **stitchkit/process**: [llms/process.txt](llms/process.txt) 18.6 KB
59
59
 
60
60
  ## Upgrading slices
61
61
  - **Upgrading — how to move across versions**: [llms/upgrading.txt](llms/upgrading.txt) 10.3 KB
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.103.10",
3
+ "version": "0.103.11",
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",
@@ -250,7 +250,7 @@
250
250
  "build": "bun ../../scripts/package-build-lock.ts -- bun run build:unlocked",
251
251
  "build:unlocked": "rm -rf dist && cp ../../CHANGELOG.md ./CHANGELOG.md && bun run build:js && chmod +x dist/entrypoints/bin/upgrade-cli.js && bun run build:types && bun scripts/check-browser-clean.mjs && bun scripts/check-browser-executes.mjs && bun scripts/check-env-live.mjs && bun scripts/check-public-types.mjs && bun scripts/check-durability-port-standalone.mjs && bun scripts/check-declarations-strict.mjs",
252
252
  "dev": "bun run build:entrypoints -- --watch",
253
- "prepack": "cp ../../README.md ./README.md && bun ../../scripts/gen-llms.ts && bun run build",
253
+ "prepack": "bun ../../scripts/sync-package-readme.ts && bun ../../scripts/gen-llms.ts && bun run build",
254
254
  "test": "bun test",
255
255
  "smoke:node": "node scripts/node-smoke.mjs",
256
256
  "smoke:next-ssr": "node scripts/next-ssr-retry-smoke.mjs",
@@ -1,285 +0,0 @@
1
- import {
2
- raceAbort
3
- } from "./index-mbnnw3f2.js";
4
-
5
- // src/process/contract.ts
6
- import { z } from "zod";
7
- var NativeCommandSettlementSchema = z.discriminatedUnion("kind", [
8
- z.object({
9
- kind: z.literal("exit"),
10
- exitCode: z.number().int().nullable(),
11
- signal: z.string().nullable()
12
- }),
13
- z.object({ kind: z.literal("error"), cause: z.unknown() })
14
- ]);
15
- var NativeCommandOptionsSchema = z.strictObject({
16
- executable: z.string().min(1),
17
- args: z.array(z.string()).default([]),
18
- cwd: z.string().optional(),
19
- env: z.record(z.string(), z.string()).optional(),
20
- envPolicy: z.enum(["declared-only", "ambient"]).default("declared-only"),
21
- signal: z.custom((v) => v instanceof AbortSignal).optional(),
22
- timeoutMs: z.number().int().positive().max(2147483647).optional(),
23
- maxOutputBytes: z.number().int().positive().optional(),
24
- capture: z.boolean().default(false),
25
- stdin: z.custom((v) => v instanceof Uint8Array).optional(),
26
- maxStdinBytes: z.number().int().positive().default(1024 * 1024),
27
- onOutput: z.custom((v) => typeof v === "function").optional(),
28
- onLeaderSettled: z.custom((v) => typeof v === "function").optional(),
29
- killGraceMs: z.number().int().nonnegative().max(1e4).default(100),
30
- cleanupTimeoutMs: z.number().int().positive().max(30000).default(2000)
31
- }).superRefine((input, ctx) => {
32
- if (!input.signal && input.timeoutMs === undefined)
33
- ctx.addIssue({ code: "custom", message: "Declare a caller signal or a finite timeout" });
34
- if (input.capture && input.maxOutputBytes === undefined)
35
- ctx.addIssue({
36
- code: "custom",
37
- message: "Capture requires a finite combined output budget"
38
- });
39
- if (input.stdin && input.stdin.byteLength > input.maxStdinBytes)
40
- ctx.addIssue({ code: "custom", message: "stdin exceeds maxStdinBytes" });
41
- });
42
- var NativeCommandResultSchema = z.object({
43
- exitCode: z.number().int().nullable(),
44
- signal: z.string().nullable(),
45
- stdout: z.instanceof(Uint8Array),
46
- stderr: z.instanceof(Uint8Array)
47
- });
48
-
49
- class NativeCommandError2 extends Error {
50
- code;
51
- constructor(code, message, options) {
52
- super(message, options);
53
- this.code = code;
54
- this.name = "NativeCommandError";
55
- }
56
- }
57
-
58
- // src/process/command-owner.ts
59
- import { spawn } from "node:child_process";
60
-
61
- // src/process/group.ts
62
- function deniedOnDarwin(error) {
63
- return process.platform === "darwin" && error instanceof Error && "code" in error && error.code === "EPERM";
64
- }
65
- function signalGroup(pid, signal) {
66
- if (pid === undefined)
67
- return;
68
- try {
69
- process.kill(-pid, signal);
70
- } catch (error) {
71
- if (!(error instanceof Error && ("code" in error) && error.code === "ESRCH"))
72
- throw error;
73
- }
74
- }
75
- function exists(pid) {
76
- if (pid === undefined)
77
- return false;
78
- try {
79
- process.kill(-pid, 0);
80
- return true;
81
- } catch (error) {
82
- if (error instanceof Error && "code" in error && error.code === "ESRCH")
83
- return false;
84
- if (deniedOnDarwin(error))
85
- return true;
86
- throw error;
87
- }
88
- }
89
- async function signalUntilSettled(pid, signal, deadline) {
90
- for (;; ) {
91
- try {
92
- signalGroup(pid, signal);
93
- return;
94
- } catch (error) {
95
- if (!deniedOnDarwin(error) || Date.now() >= deadline)
96
- throw error;
97
- await new Promise((resolve) => setTimeout(resolve, Math.min(10, deadline - Date.now())));
98
- }
99
- }
100
- }
101
- async function stopCommandGroup(pid, graceMs, cleanupTimeoutMs = 2000) {
102
- const deadline = Date.now() + graceMs + cleanupTimeoutMs;
103
- await signalUntilSettled(pid, "SIGTERM", deadline);
104
- const until = Date.now() + graceMs;
105
- while (Date.now() < until && exists(pid))
106
- await new Promise((resolve) => setTimeout(resolve, Math.min(10, until - Date.now())));
107
- await signalUntilSettled(pid, "SIGKILL", deadline);
108
- }
109
- function commandCleanupError(cause) {
110
- return new NativeCommandError2("COMMAND_CLEANUP", "Command cleanup did not complete", {
111
- cause
112
- });
113
- }
114
- function waitForCommandClose(closed, timeoutMs) {
115
- return raceAbort(closed, AbortSignal.timeout(timeoutMs));
116
- }
117
-
118
- // src/process/leader-settlement.ts
119
- function createLeaderSettlement(options, signal) {
120
- let pending;
121
- return (event) => {
122
- if (pending)
123
- return pending;
124
- pending = (async () => {
125
- if (!options.onLeaderSettled)
126
- return;
127
- const controller = new AbortController;
128
- const timer = setTimeout(() => controller.abort(new Error("Leader settlement deadline exceeded")), options.cleanupTimeoutMs);
129
- const abort = () => controller.abort(signal.reason);
130
- if (!signal.aborted)
131
- signal.addEventListener("abort", abort, { once: true });
132
- try {
133
- await raceAbort(Promise.resolve(options.onLeaderSettled(event, controller.signal)), controller.signal);
134
- } catch (cause) {
135
- if (signal.aborted && cause === signal.reason)
136
- throw cause;
137
- throw commandCleanupError(new AggregateError([event, cause], "Leader settlement failed"));
138
- } finally {
139
- clearTimeout(timer);
140
- signal.removeEventListener("abort", abort);
141
- controller.abort(new Error("Leader settlement completed"));
142
- }
143
- })();
144
- pending.catch(() => {
145
- return;
146
- });
147
- return pending;
148
- };
149
- }
150
-
151
- // src/process/command-owner.ts
152
- function startNativeCommand(input, onSpawn) {
153
- const options = NativeCommandOptionsSchema.parse(input);
154
- options.signal?.throwIfAborted();
155
- if (process.platform === "win32")
156
- throw new NativeCommandError2("COMMAND_UNAVAILABLE", "POSIX process groups are required");
157
- const controller = new AbortController;
158
- const abort = () => controller.abort(options.signal?.reason);
159
- options.signal?.addEventListener("abort", abort, { once: true });
160
- if (options.signal?.aborted)
161
- abort();
162
- const timer = options.timeoutMs === undefined ? undefined : setTimeout(() => controller.abort(new NativeCommandError2("COMMAND_LIMIT", "Command deadline exceeded")), options.timeoutMs);
163
- let child;
164
- try {
165
- child = spawn(options.executable, options.args, {
166
- cwd: options.cwd,
167
- env: options.envPolicy === "ambient" ? { ...process.env, ...options.env } : options.env ?? {},
168
- detached: true,
169
- stdio: ["pipe", "pipe", "pipe"]
170
- });
171
- } catch (cause) {
172
- if (timer !== undefined)
173
- clearTimeout(timer);
174
- options.signal?.removeEventListener("abort", abort);
175
- throw new NativeCommandError2("COMMAND_UNAVAILABLE", "Command could not start", { cause });
176
- }
177
- const stdout = [];
178
- const stderr = [];
179
- let total = 0;
180
- let finished = false;
181
- let spawnFailure;
182
- const settle = createLeaderSettlement(options, controller.signal);
183
- const leader = new Promise((resolve) => {
184
- child.once("exit", (exitCode, signal) => resolve({ kind: "exit", exitCode, signal }));
185
- child.once("error", (cause) => resolve({ kind: "error", cause }));
186
- });
187
- const pendingHandles = new Set(["leader", "stdin", "stdout", "stderr"]);
188
- const released = Promise.all([
189
- leader.then(() => pendingHandles.delete("leader")),
190
- ...["stdin", "stdout", "stderr"].map((name) => new Promise((resolve) => child[name].once("close", () => {
191
- pendingHandles.delete(name);
192
- resolve();
193
- })))
194
- ]);
195
- const closed = new Promise((resolve) => {
196
- child.once("error", (error) => {
197
- spawnFailure = new NativeCommandError2("COMMAND_UNAVAILABLE", "Command could not start", {
198
- cause: error
199
- });
200
- controller.abort(spawnFailure);
201
- });
202
- child.once("close", (exitCode, signal) => resolve({ exitCode, signal }));
203
- });
204
- child.stdin.on("error", (error) => {
205
- if (!(("code" in error) && error.code === "EPIPE"))
206
- controller.abort(error);
207
- });
208
- const drain = async (stream, channel, captured) => {
209
- for await (const chunk of stream) {
210
- controller.signal.throwIfAborted();
211
- if (!(chunk instanceof Uint8Array))
212
- throw new TypeError("Expected binary command output");
213
- total += chunk.byteLength;
214
- if (options.maxOutputBytes !== undefined && total > options.maxOutputBytes)
215
- throw new NativeCommandError2("COMMAND_LIMIT", "Command output budget exceeded");
216
- if (options.capture)
217
- captured.push(Uint8Array.from(chunk));
218
- if (options.onOutput)
219
- await raceAbort(Promise.resolve().then(() => {
220
- controller.signal.throwIfAborted();
221
- return options.onOutput?.(chunk, channel, controller.signal);
222
- }), controller.signal);
223
- }
224
- };
225
- const result = (async () => {
226
- try {
227
- onSpawn?.(child);
228
- child.stdin.end(options.stdin);
229
- const drains = Promise.all([
230
- drain(child.stdout, "stdout", stdout),
231
- drain(child.stderr, "stderr", stderr)
232
- ]);
233
- await raceAbort(Promise.all([drains, leader.then(settle)]), controller.signal);
234
- const status = await raceAbort(closed, controller.signal);
235
- if (spawnFailure !== undefined)
236
- throw spawnFailure;
237
- return { ...status, stdout: Buffer.concat(stdout), stderr: Buffer.concat(stderr) };
238
- } catch (error) {
239
- controller.abort(error);
240
- child.stdin.destroy();
241
- child.stdout.destroy();
242
- child.stderr.destroy();
243
- const cleanup = await Promise.allSettled([
244
- settle({ kind: "error", cause: error }),
245
- stopCommandGroup(child.pid, options.killGraceMs, options.cleanupTimeoutMs).then(async () => {
246
- try {
247
- await waitForCommandClose(released, options.cleanupTimeoutMs);
248
- } catch (cause) {
249
- throw new Error(`Command handles remained open: ${[...pendingHandles].join(", ")}`, {
250
- cause
251
- });
252
- }
253
- })
254
- ]);
255
- const failures = cleanup.flatMap((item) => item.status === "rejected" && item.reason !== error ? [item.reason] : []);
256
- if (failures.length) {
257
- throw commandCleanupError(new AggregateError([error, ...failures], "Command failed and cleanup failed"));
258
- }
259
- throw error;
260
- } finally {
261
- finished = true;
262
- if (timer !== undefined)
263
- clearTimeout(timer);
264
- options.signal?.removeEventListener("abort", abort);
265
- controller.abort(new Error("Command completed"));
266
- }
267
- })();
268
- result.catch(() => {
269
- return;
270
- });
271
- return {
272
- result,
273
- async stop() {
274
- if (!finished)
275
- controller.abort(new DOMException("Command stopped", "AbortError"));
276
- await result.then(() => {
277
- return;
278
- }, () => {
279
- return;
280
- });
281
- }
282
- };
283
- }
284
-
285
- export { NativeCommandError2, startNativeCommand };