stitchkit 0.105.2 → 0.106.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/agent-runtime/coding-shell-process.d.ts.map +1 -1
  3. package/dist/agent-runtime/store-conformance-lifecycle.d.ts +10 -10
  4. package/dist/agent-runtime/store-conformance-support.d.ts +5 -5
  5. package/dist/entrypoints/agent-runtime/coding-tools.js +2 -2
  6. package/dist/entrypoints/agent-runtime/sandbox.js +2 -2
  7. package/dist/entrypoints/process.js +1 -1
  8. package/dist/{index-3s86a7kf.js → index-4q9jvqg7.js} +216 -51
  9. package/dist/{index-sevbwsnn.js → index-9bskshe3.js} +2 -4
  10. package/dist/process/cleanup.d.ts.map +1 -1
  11. package/dist/process/command-owner.d.ts.map +1 -1
  12. package/dist/process/contract.d.ts +80 -7
  13. package/dist/process/contract.d.ts.map +1 -1
  14. package/dist/process/group.d.ts +12 -0
  15. package/dist/process/group.d.ts.map +1 -1
  16. package/dist/process/launch.d.ts +5 -0
  17. package/dist/process/launch.d.ts.map +1 -1
  18. package/dist/process/output-drain.d.ts +14 -0
  19. package/dist/process/output-drain.d.ts.map +1 -0
  20. package/dist/process/owned-child.d.ts +9 -0
  21. package/dist/process/owned-child.d.ts.map +1 -1
  22. package/dist/process/terminate.d.ts +2 -0
  23. package/dist/process/terminate.d.ts.map +1 -1
  24. package/dist/process/transport.d.ts +5 -0
  25. package/dist/process/transport.d.ts.map +1 -1
  26. package/llms/process.txt +62 -17
  27. package/llms/stitchkit.txt +3 -3
  28. package/llms/{upgrading-0.37.0-0.50.0.txt → upgrading-0.37.0-0.53.0.txt} +25 -3
  29. package/llms/{upgrading-0.53.0-0.67.0.txt → upgrading-0.55.0-0.68.0.txt} +53 -25
  30. package/llms/{upgrading-0.68.0-0.94.0.txt → upgrading-0.69.0-0.95.0.txt} +45 -53
  31. package/llms/{upgrading-0.95.0-0.105.0.txt → upgrading-0.96.0-0.106.0.txt} +40 -45
  32. package/llms/upgrading.txt +4 -4
  33. package/llms-full.txt +102 -20
  34. package/llms.txt +5 -5
  35. package/package.json +1 -1
@@ -1,14 +1,56 @@
1
- # Upgrading 0.68.0 → 0.94.0 — stitchkit docs slice
1
+ # Upgrading 0.69.0 → 0.95.0 — stitchkit docs slice
2
2
 
3
3
  > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
4
  > `llms.txt` in this package.
5
5
 
6
- Contains: Guide: Upgrading, 0.68.0 → 0.94.0 (docs/guide/upgrading.md)
6
+ Contains: Guide: Upgrading, 0.69.0 → 0.95.0 (docs/guide/upgrading.md)
7
7
 
8
8
  ==============================================================================
9
- # Guide: Upgrading, 0.68.0 → 0.94.0 (docs/guide/upgrading.md)
9
+ # Guide: Upgrading, 0.69.0 → 0.95.0 (docs/guide/upgrading.md)
10
10
  ==============================================================================
11
11
 
12
+ ## Released migration: 0.95.0
13
+
14
+ One breaking change in `stitchkit/application`: the process lifecycle ledger
15
+ counts downtime as the window nobody answered in, and gains the `draining`
16
+ phase and the `startup-failed` outcome. A ledger file written by an earlier
17
+ version loads unchanged — the new `unavailableAt` reads as `null`.
18
+
19
+ 1. **Read downtime from the ready fact.** **Who must act:** a project that
20
+ reads `downtimeMs` from a `started` fact. It is gone from `StartFact`; the
21
+ number it carried is `processGapMs`. The downtime now arrives with the new
22
+ run's readiness:
23
+
24
+ ```ts
25
+ // before
26
+ ledger.subscribe((fact) => {
27
+ if (fact.type === 'started') report(fact.previousExit, fact.downtimeMs);
28
+ });
29
+ // after
30
+ ledger.subscribe((fact) => {
31
+ if (fact.type === 'started') previousExit = fact.previousExit;
32
+ if (fact.type === 'ready') report(previousExit, fact.downtimeMs);
33
+ });
34
+ ```
35
+
36
+ 2. **Handle the new values.** **Who must act:** a project that switches
37
+ exhaustively over `LifecycleTermination` (new: `draining`,
38
+ `startup-failed`), `PreviousExit` (new: `startup-failed`) or
39
+ `ProcessLifecycleFact` (new: `DrainingFact`, `type: 'draining'`). The type
40
+ checker names each switch. An open run is `active` or `draining`; a check
41
+ written as `termination === 'active'` to mean "still running" becomes
42
+ `termination === 'active' || termination === 'draining'`.
43
+ `TransitionReadyInput` is renamed `TransitionRunInput` — the same fields,
44
+ now also taken by `transitionProcessDraining`; rename the import.
45
+
46
+ 3. **Record the drain and the readiness when driving the ledger by hand.**
47
+ **Who must act:** only a project that calls `createProcessLifecycleLedger`
48
+ methods itself instead of mounting `lifecycleLedgerResource`. Call
49
+ `recordDraining()` where the application stops admitting work, before it
50
+ drains; without it downtime is counted from the recorded stop. Call
51
+ `recordReady()` when the run serves; a run stopped without it now ends
52
+ `startup-failed`, not `clean`.
53
+
12
54
  ## Released migration: 0.94.0
13
55
 
14
56
  One breaking minor that consolidates: tool options move into one group, and
@@ -1107,53 +1149,3 @@ else renderExistingRole(message)
1107
1149
 
1108
1150
  No data migration is needed. The schema still accepts all earlier records, and applications that
1109
1151
  do not exhaustively branch over these unions compile unchanged.
1110
-
1111
- ## Released migration: 0.68.0
1112
-
1113
- ### Make Unix transport selection explicit outside Bun
1114
-
1115
- The legacy `unix` option remains a Bun convenience, but on Node or another
1116
- runtime it now fails before dispatch. Replace it with the owned adapter and close
1117
- that adapter with the application:
1118
-
1119
- ```ts
1120
- // before — unsafe outside Bun: an unsupported fetch could dial baseUrl over TCP
1121
- const http = createHttpClient({ baseUrl, unix: '/run/service.sock' })
1122
-
1123
- // after — Bun and Node; every dispatch and redirect stays on the socket
1124
- import { createUnixClientTransport } from 'stitchkit/server' // or stitchkit/node
1125
- const transport = createUnixClientTransport({ socketPath: '/run/service.sock' })
1126
- const http = createHttpClient({ baseUrl, fetch: transport.fetch })
1127
- // during shutdown
1128
- await transport.close()
1129
- ```
1130
-
1131
- Do not automatically replay `possibly-dispatched`: a timeout or connection loss
1132
- after bytes left the process does not prove that a write did not happen.
1133
-
1134
- ### Choose tolerant stream parsing explicitly
1135
-
1136
- `parseNDJSON` and `parseSSE` now throw on malformed JSON, invalid UTF-8 and an
1137
- over-limit line. The default line ceiling is 1 MiB. If a feed deliberately skips
1138
- bad records, retain that policy explicitly:
1139
-
1140
- ```ts
1141
- // before — malformed input disappeared implicitly
1142
- parseNDJSON(response)
1143
-
1144
- // after — ordinary fail-closed path
1145
- parseNDJSON(response, { maxLineBytes: 256 * 1024 })
1146
-
1147
- // after — deliberately tolerant path
1148
- parseNDJSON(response, { onParseError: (raw, error) => report(raw, error) })
1149
- ```
1150
-
1151
- ### Extend exhaustive framework-error handling
1152
-
1153
- If a switch makes `StitchErrorCode` exhaustive, add
1154
- `STREAM_ITEM_INVALID`, `STREAM_FRAME_TOO_LARGE`,
1155
- `STREAM_TERMINAL_MISSING` and `STREAM_LIFETIME_EXCEEDED`. A partial application
1156
- status map needs no change (ADR 0105).
1157
-
1158
- The admission/channel APIs and endpoint `stream` descriptor are additive; raw
1159
- responses and raw `streamingRoute` remain supported.
@@ -1,14 +1,51 @@
1
- # Upgrading 0.95.0 → 0.105.0 — stitchkit docs slice
1
+ # Upgrading 0.96.0 → 0.106.0 — stitchkit docs slice
2
2
 
3
3
  > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
4
  > `llms.txt` in this package.
5
5
 
6
- Contains: Guide: Upgrading, 0.95.0 → 0.105.0 (docs/guide/upgrading.md)
6
+ Contains: Guide: Upgrading, 0.96.0 → 0.106.0 (docs/guide/upgrading.md)
7
7
 
8
8
  ==============================================================================
9
- # Guide: Upgrading, 0.95.0 → 0.105.0 (docs/guide/upgrading.md)
9
+ # Guide: Upgrading, 0.96.0 → 0.106.0 (docs/guide/upgrading.md)
10
10
  ==============================================================================
11
11
 
12
+ ## Released migration: 0.106.0
13
+
14
+ ### a stopped leader settles as stopped
15
+
16
+ **Who must act:** code passed to `onLeaderSettled` of `runNativeCommand` (`stitchkit/process`) that
17
+ reads `kind: 'error'` as "the command was stopped". Code that only handles `kind: 'exit'`, or does
18
+ not pass `onLeaderSettled`, needs no change.
19
+
20
+ A command stopped by a caller abort, its `timeoutMs`, an exceeded output budget or a failing sink
21
+ used to settle at once as `{ kind: 'error', cause }`. It now waits for the stop signals and settles
22
+ as `{ kind: 'stopped', cause, exitCode, signal }`, with how the kernel reported the leader's end
23
+ ([ADR 0248](../decisions/0248-a-stopped-leader-settles-with-its-exit-and-a-command-may-join-the-callers-group.md)).
24
+ `'error'` is left for a leader whose exit was never observed.
25
+
26
+ 1. Where the hook handled a stop under `'error'`, handle `'stopped'` there, and take the exit from
27
+ the event instead of reading the child process:
28
+
29
+ ```ts
30
+ // before
31
+ onLeaderSettled: (event) => {
32
+ if (event.kind === 'exit') record(event.exitCode, event.signal)
33
+ else stopped(event.cause)
34
+ }
35
+ // after
36
+ onLeaderSettled: (event) => {
37
+ if (event.kind === 'error') return failed(event.cause)
38
+ record(event.exitCode, event.signal)
39
+ if (event.kind === 'stopped') stopped(event.cause)
40
+ }
41
+ ```
42
+
43
+ Search pattern: `onLeaderSettled` together with `kind === 'error'` or `kind !== 'exit'`.
44
+ 2. Type-check: an exhaustive `switch (event.kind)` without a `'stopped'` case fails on the new kind.
45
+ 3. A hook that stops an external scope on a stopped command now runs once the leader is gone,
46
+ still bounded by `cleanupTimeoutMs`; no change is needed unless the leader waited for that scope
47
+ to exit (it gets KILL when the grace ends either way).
48
+
12
49
  ## Released migration: 0.105.0
13
50
 
14
51
  ### CLI publication has its own entrypoint
@@ -895,45 +932,3 @@ it; only a context built by hand needs it.
895
932
  hand before `bot.stop()`. A project that wrote its own `getUpdates`
896
933
  transformer for this (waiting on `app.admission` before returning a batch)
897
934
  deletes it; keeping both waits twice and is harmless.
898
-
899
- ## Released migration: 0.95.0
900
-
901
- One breaking change in `stitchkit/application`: the process lifecycle ledger
902
- counts downtime as the window nobody answered in, and gains the `draining`
903
- phase and the `startup-failed` outcome. A ledger file written by an earlier
904
- version loads unchanged — the new `unavailableAt` reads as `null`.
905
-
906
- 1. **Read downtime from the ready fact.** **Who must act:** a project that
907
- reads `downtimeMs` from a `started` fact. It is gone from `StartFact`; the
908
- number it carried is `processGapMs`. The downtime now arrives with the new
909
- run's readiness:
910
-
911
- ```ts
912
- // before
913
- ledger.subscribe((fact) => {
914
- if (fact.type === 'started') report(fact.previousExit, fact.downtimeMs);
915
- });
916
- // after
917
- ledger.subscribe((fact) => {
918
- if (fact.type === 'started') previousExit = fact.previousExit;
919
- if (fact.type === 'ready') report(previousExit, fact.downtimeMs);
920
- });
921
- ```
922
-
923
- 2. **Handle the new values.** **Who must act:** a project that switches
924
- exhaustively over `LifecycleTermination` (new: `draining`,
925
- `startup-failed`), `PreviousExit` (new: `startup-failed`) or
926
- `ProcessLifecycleFact` (new: `DrainingFact`, `type: 'draining'`). The type
927
- checker names each switch. An open run is `active` or `draining`; a check
928
- written as `termination === 'active'` to mean "still running" becomes
929
- `termination === 'active' || termination === 'draining'`.
930
- `TransitionReadyInput` is renamed `TransitionRunInput` — the same fields,
931
- now also taken by `transitionProcessDraining`; rename the import.
932
-
933
- 3. **Record the drain and the readiness when driving the ledger by hand.**
934
- **Who must act:** only a project that calls `createProcessLifecycleLedger`
935
- methods itself instead of mounting `lifecycleLedgerResource`. Call
936
- `recordDraining()` where the application stops admitting work, before it
937
- drains; without it downtime is counted from the recorded stop. Call
938
- `recordReady()` when the run serves; a run stopped without it now ends
939
- `startup-failed`, not `clean`.
@@ -11,10 +11,10 @@ Contains: Guide: Upgrading, general (docs/guide/upgrading.md)
11
11
 
12
12
  Load this slice, then every range slice between your installed version and the target:
13
13
 
14
- - 0.95.0 → 0.105.0: `llms/upgrading-0.95.0-0.105.0.txt`
15
- - 0.68.0 → 0.94.0: `llms/upgrading-0.68.0-0.94.0.txt`
16
- - 0.53.0 → 0.67.0: `llms/upgrading-0.53.0-0.67.0.txt`
17
- - 0.37.0 → 0.50.0: `llms/upgrading-0.37.0-0.50.0.txt`
14
+ - 0.96.0 → 0.106.0: `llms/upgrading-0.96.0-0.106.0.txt`
15
+ - 0.69.0 → 0.95.0: `llms/upgrading-0.69.0-0.95.0.txt`
16
+ - 0.55.0 → 0.68.0: `llms/upgrading-0.55.0-0.68.0.txt`
17
+ - 0.37.0 → 0.53.0: `llms/upgrading-0.37.0-0.53.0.txt`
18
18
 
19
19
  # Upgrading stitchkit
20
20
 
package/llms-full.txt CHANGED
@@ -74,12 +74,12 @@ own, recorded as an ADR.
74
74
  | `stitchkit/geo` | server (Bun or Node) | evolving | managed GeoIP reader generations, last-known-good reload and the optional MaxMind adapter |
75
75
  | `stitchkit/oauth` | browser **and** server | evolving | provider-neutral Authorization Code + PKCE request and one-shot callback transaction mechanics |
76
76
  | `stitchkit/google` | server (Bun or Node) | evolving | optional Google code exchange and verified OIDC identity adapter |
77
- | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 50 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
77
+ | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 51 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
78
78
  | `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, plus the managed-resource conformance kit |
79
79
  | `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
80
80
  | `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
81
81
  | `stitchkit/react/keyboard` | browser | evolving | which part of a screen gets a key — ordered layers, one Escape per level, list zones with real focus; see [keyboard guide](keyboard.md) |
82
- | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 23 of the 50 minors since 0.56.2, most recently 0.105.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
82
+ | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 23 of the 51 minors since 0.56.2, most recently 0.105.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
83
83
  | `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
84
84
  | `stitchkit/agent-runtime/testing` | tests on Bun or Node | evolving | credential-free replay, scripted provider faults, deterministic race controls and the agent-store conformance kit |
85
85
  | `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
@@ -91,7 +91,7 @@ own, recorded as an ADR.
91
91
  | `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
92
92
  | `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
93
93
  | `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
94
- | `stitchkit/application` | browser + server | evolving<br>_redefined in 13 of the 50 minors since 0.56.2, most recently 0.105.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
94
+ | `stitchkit/application` | browser + server | evolving<br>_redefined in 13 of the 51 minors since 0.56.2, most recently 0.105.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
95
95
  | `stitchkit/application/grammy` | server | evolving | a grammY bot as application resources — configuration, then polling with updates admitted by batch — and the webhook adapter |
96
96
  | `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
97
97
  | `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
@@ -8355,7 +8355,9 @@ command the same way, by its `stop` policy:
8355
8355
  | `graceMs` | How long the leader (or the group) has to leave before the whole group gets KILL. 0 to 3 600 000 (one hour). Required. |
8356
8356
  | `killOn` | Optional `AbortSignal`. Its abort stops the command with KILL to the whole group at once, also in the middle of a grace. |
8357
8357
 
8358
- Omitting `stop` means `{ target: 'group', signal: 'SIGTERM', graceMs: 100 }`.
8358
+ Omitting `stop` means `{ target: 'group', signal: 'SIGTERM', graceMs: 100 }`; a command in the
8359
+ caller's group (`group: 'caller'`) stops its leader alone, so its default is `target: 'leader'`
8360
+ and a group target is refused (see [Terminal commands](#terminal-commands-inherited-stdio-and-the-callers-group)).
8359
8361
 
8360
8362
  ```ts
8361
8363
  // Cooperative cancellation: the leader rolls its work back, its helpers are not asked.
@@ -8426,7 +8428,8 @@ await runNativeCommand({
8426
8428
  })
8427
8429
  ```
8428
8430
 
8429
- A command whose group this package did not create (a structural
8431
+ A command in the caller's group (`group: 'caller'`) has no group of its own: `descendants` is
8432
+ refused there and nothing is stopped after its leader exits. A command whose group this package did not create (a structural
8430
8433
  launcher supplied by a host) has no group to stop, and `descendants` has nothing to act on there.
8431
8434
 
8432
8435
 
@@ -8442,26 +8445,68 @@ bounded tar or capture commands get their own explicit limits.
8442
8445
  A child the caller holds until it exits is the same call. A guardian that forwards its own SIGTERM
8443
8446
  passes an abort signal and `stop: { target: 'leader', signal: 'SIGTERM', graceMs }`. It records the
8444
8447
  pid in `onLeaderStarted({ pid })`, which runs once right after the leader exists (a throw stops the
8445
- command with that error), reads the exit code and signal in `onLeaderSettled` before the pipes
8446
- drain, and reads stderr through `onOutput` or `capture` with `maxOutputBytes`. `drainTimeoutMs`
8447
- bounds how long the output pipes may stay open after the leader exited: a holder that left the
8448
- group with `setsid` keeps them open, and without the option the command waits for it until
8449
- `timeoutMs` or the signal; with it the command ends with `COMMAND_CLEANUP` (a holder inside the
8450
- group is already stopped by `descendants`). A worker whose whole group must stop on abort is the
8451
- default `target: 'group'`. A child that inherits the caller's terminal is outside this
8452
- capability: every command runs in a group of its own, and a background group that reads the
8453
- terminal is stopped by the kernel (`SIGTTIN`), so an interactive re-execution stays a plain
8454
- spawn in the caller's group. Importing one leaf does not make
8448
+ command with that error), and the leader's exit code and signal in `onLeaderSettled`: `'exit'`
8449
+ when the leader ended on its own, `'stopped'` with the same fields when the guardian stopped it.
8450
+ It reads stderr through `onOutput` or `capture` with `maxOutputBytes`. `drainTimeoutMs` bounds how
8451
+ long the output pipes may stay open after the leader exited: a holder that left the group with
8452
+ `setsid` keeps them open, and without the option the command waits for it until `timeoutMs` or
8453
+ the signal; with it the command ends with `COMMAND_CLEANUP` (a holder inside the group is already
8454
+ stopped by `descendants`). A worker whose whole group must stop on abort is the default
8455
+ `target: 'group'`. A command that uses the caller's terminal is the next section. Importing one leaf does not make
8455
8456
  installing the whole npm package smaller; choose a dependency after measuring packed size and its
8456
8457
  dependency closure.
8457
8458
 
8459
+ ## Terminal commands: inherited stdio and the caller's group
8460
+
8461
+ An operator CLI that re-runs itself, or any command that prompts or prints in colour, needs the
8462
+ caller's terminal itself rather than pipes. Two options give it that:
8463
+
8464
+ ```ts
8465
+ const { exitCode } = await runNativeCommand({
8466
+ executable: process.execPath, args: [cliPath, ...argv], envPolicy: 'ambient',
8467
+ signal: shutdown.signal,
8468
+ stdio: 'inherit', // the command's stdin, stdout and stderr are the caller's: a TTY stays a TTY
8469
+ group: 'caller', // it joins the caller's process group, so a Ctrl-C at the terminal reaches it
8470
+ })
8471
+ process.exitCode = exitCode ?? 1
8472
+ ```
8473
+
8474
+ `stdio: 'inherit'` hands the caller's three descriptors to the command; its bytes never pass
8475
+ through this package, so `capture`, `onOutput`, `stdin`, `maxOutputBytes` and `drainTimeoutMs` are
8476
+ refused by name with it, and the result's buffers stay empty.
8477
+
8478
+ `group: 'caller'` keeps the command in the caller's process group instead of a group of its own.
8479
+ That is what an interactive command needs: the terminal sends Ctrl-C to its foreground group,
8480
+ and a command in a group of its own is a background group there, which the kernel stops
8481
+ (`SIGTTIN`) as soon as it reads the terminal. In the caller's group the command has no group of
8482
+ its own, so:
8483
+
8484
+ - a stop signals the leader alone: `stop.target` is `'leader'` (the default here is
8485
+ `{ target: 'leader', signal: 'SIGTERM', graceMs: 100 }`), and `target: 'group'` is refused,
8486
+ because a group signal would reach the caller;
8487
+ - `descendants` does not apply and is refused: what the leader leaves behind belongs to the
8488
+ caller's group, and `descendantsStopped` is always `false`;
8489
+ - a Ctrl-C reaches the caller too; handle `SIGINT` in the caller when it must outlive the command.
8490
+
8491
+ The two options are independent: `stdio: 'inherit'` alone keeps colour and `isatty` for a command
8492
+ that never reads the terminal, and `group: 'caller'` alone forwards Ctrl-C to a piped command.
8493
+
8458
8494
  ## Resource-scoped launchers and shared owners
8459
8495
 
8460
8496
  `onLeaderSettled(event, signal)` observes leader exit before inherited stdout/stderr pipes
8461
8497
  close. A resource owner can stop its external scope there, allowing pipe drain to complete.
8462
- The callback runs once: `event.kind === 'exit'` carries observed `exitCode`/`signal`; a terminal
8463
- failure uses `kind: 'error'` and preserves its cause. Unavailable executables, output sink
8464
- failure and caller cancellation settle through the same owner. Synchronous native launch
8498
+ The callback runs once, with one of three events:
8499
+
8500
+ | `event.kind` | when | carries |
8501
+ |---|---|---|
8502
+ | `'exit'` | the leader ended on its own | `exitCode`, `signal` as the kernel reported them |
8503
+ | `'stopped'` | the command was stopped (caller abort, `timeoutMs`, output budget, failing sink) and the leader's exit after the stop signals was observed | the stop's `cause`, and the leader's `exitCode` and `signal` (for example `SIGTERM`, or `SIGKILL` once the grace ended) |
8504
+ | `'error'` | no exit of the leader was observed: it could not start, or its exit did not arrive within `cleanupTimeoutMs` | the `cause` |
8505
+
8506
+ A leader that ended on its own settles before the pipes drain; a stopped one settles after the
8507
+ stop sequence, once its exit is known. A stop that comes after the leader already exited (a
8508
+ deadline while a helper holds the pipe) does not change its `'exit'` event. Unavailable
8509
+ executables, output sink failure and caller cancellation settle through the same owner. Synchronous native launch
8465
8510
  failures such as `E2BIG` also return a rejected result after this bounded settlement;
8466
8511
  schema-invalid and already-aborted inputs refuse before native ownership and do not invoke
8467
8512
  the hook. A failing hook preserves both the initial launch error and cleanup cause.
@@ -15533,6 +15578,43 @@ analytics policy belong to the application.
15533
15578
 
15534
15579
  # Upgrading stitchkit
15535
15580
 
15581
+ ## Released migration: 0.106.0
15582
+
15583
+ ### a stopped leader settles as stopped
15584
+
15585
+ **Who must act:** code passed to `onLeaderSettled` of `runNativeCommand` (`stitchkit/process`) that
15586
+ reads `kind: 'error'` as "the command was stopped". Code that only handles `kind: 'exit'`, or does
15587
+ not pass `onLeaderSettled`, needs no change.
15588
+
15589
+ A command stopped by a caller abort, its `timeoutMs`, an exceeded output budget or a failing sink
15590
+ used to settle at once as `{ kind: 'error', cause }`. It now waits for the stop signals and settles
15591
+ as `{ kind: 'stopped', cause, exitCode, signal }`, with how the kernel reported the leader's end
15592
+ ([ADR 0248](../decisions/0248-a-stopped-leader-settles-with-its-exit-and-a-command-may-join-the-callers-group.md)).
15593
+ `'error'` is left for a leader whose exit was never observed.
15594
+
15595
+ 1. Where the hook handled a stop under `'error'`, handle `'stopped'` there, and take the exit from
15596
+ the event instead of reading the child process:
15597
+
15598
+ ```ts
15599
+ // before
15600
+ onLeaderSettled: (event) => {
15601
+ if (event.kind === 'exit') record(event.exitCode, event.signal)
15602
+ else stopped(event.cause)
15603
+ }
15604
+ // after
15605
+ onLeaderSettled: (event) => {
15606
+ if (event.kind === 'error') return failed(event.cause)
15607
+ record(event.exitCode, event.signal)
15608
+ if (event.kind === 'stopped') stopped(event.cause)
15609
+ }
15610
+ ```
15611
+
15612
+ Search pattern: `onLeaderSettled` together with `kind === 'error'` or `kind !== 'exit'`.
15613
+ 2. Type-check: an exhaustive `switch (event.kind)` without a `'stopped'` case fails on the new kind.
15614
+ 3. A hook that stops an external scope on a stopped command now runs once the leader is gone,
15615
+ still bounded by `cleanupTimeoutMs`; no change is needed unless the leader waited for that scope
15616
+ to exit (it gets KILL when the grace ends either way).
15617
+
15536
15618
  ## Released migration: 0.105.0
15537
15619
 
15538
15620
  ### CLI publication has its own entrypoint
@@ -22646,12 +22728,12 @@ Native POSIX Bun/Node one-shot command execution. See [native IO](../guide/nativ
22646
22728
  | Export | Kind | Purpose |
22647
22729
  |---|---|---|
22648
22730
  | `runNativeCommand` | function | caller-lifetime streaming or explicitly bounded binary capture |
22649
- | `NativeCommandOptions` | type | executable/args, env policy, signal/deadline, bytes sink, finite cleanup limits, the `stop` policy and `descendants` (`'terminate-after-leader'`, the default: stop the group after the leader exits, or `'leave'`); `onLeaderStarted({ pid })` (once, after the leader exists), `drainTimeoutMs` (bound on the pipes staying open after the leader exited, `COMMAND_CLEANUP`); an unknown key is refused by name, a removed one (`killGraceMs`) with its replacement |
22731
+ | `NativeCommandOptions` | type | executable/args, env policy, signal/deadline, bytes sink, finite cleanup limits, the `stop` policy and `descendants` (`'terminate-after-leader'`, the default: stop the group after the leader exits, or `'leave'`); `stdio` (`'pipe'`, or `'inherit'` for the caller's descriptors, refusing the pipe-only options) and `group` (`'own'`, or `'caller'`: joins the caller's process group, stops only the leader, refuses `descendants` and a group stop); `onLeaderStarted({ pid })` (once, after the leader exists), `drainTimeoutMs` (bound on the pipes staying open after the leader exited, `COMMAND_CLEANUP`); an unknown key is refused by name, a removed one (`killGraceMs`) with its replacement |
22650
22732
  | `NativeCommandStopPolicy` | type | how a cancelled command stops: `target` `'group'` (signal every member) or `'leader'` (signal only the leader; its exit ends the grace), `signal` (default `SIGTERM`), `graceMs` (at most one hour) before KILL to the group, and `killOn`, an `AbortSignal` that skips the grace; default `{ target: 'group', signal: 'SIGTERM', graceMs: 100 }` |
22651
22733
  | `NativeCommandResult` | type | observed nullable exit code/signal, `descendantsStopped` (members left in the group after the leader exited were stopped) and captured stdout/stderr bytes |
22652
22734
  | `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 |
22653
22735
 
22654
- | `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 |
22736
+ | `NativeCommandSettlement` | type | `exit` (the leader ended on its own), `stopped` (the command was stopped and the leader's exit after the stop observed: the stop's `cause` with `exitCode`/`signal`) or `error` (no exit observed), 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 |
22655
22737
  | `observeProcessInstance` / `ProcessInstanceObservation` | function / type | Linux/Darwin lifetime observation; observed identity or unavailable with its original cause, never guessed absence |
22656
22738
  | `ProcessInstanceSchema` / `ProcessInstance` | schema / type | platform, boot ID, namespace and process start identity from the shared native reader |
22657
22739
  | `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
@@ -56,15 +56,15 @@ Load the slice for each entrypoint your code imports — `import … from 'stitc
56
56
  - **stitchkit/node**: [llms/node.txt](llms/node.txt) 3.1 KB
57
57
  - **stitchkit/agent-runtime/react**: [llms/agent-runtime-react.txt](llms/agent-runtime-react.txt) 1.0 KB
58
58
  - **stitchkit/agent-runtime/realtime**: [llms/agent-runtime-realtime.txt](llms/agent-runtime-realtime.txt) 2.6 KB
59
- - **stitchkit/process**: [llms/process.txt](llms/process.txt) 36.2 KB
59
+ - **stitchkit/process**: [llms/process.txt](llms/process.txt) 39.4 KB
60
60
  - **stitchkit/files/packaging**: [llms/files-packaging.txt](llms/files-packaging.txt) 2.8 KB
61
61
 
62
62
  ## Upgrading slices
63
63
  - **Upgrading — how to move across versions**: [llms/upgrading.txt](llms/upgrading.txt) 10.3 KB
64
- - **Upgrading 0.95.0 → 0.105.0**: [llms/upgrading-0.95.0-0.105.0.txt](llms/upgrading-0.95.0-0.105.0.txt) 46.8 KB
65
- - **Upgrading 0.68.0 → 0.94.0**: [llms/upgrading-0.68.0-0.94.0.txt](llms/upgrading-0.68.0-0.94.0.txt) 46.5 KB
66
- - **Upgrading 0.53.0 → 0.67.0**: [llms/upgrading-0.53.0-0.67.0.txt](llms/upgrading-0.53.0-0.67.0.txt) 46.1 KB
67
- - **Upgrading 0.37.0 → 0.50.0**: [llms/upgrading-0.37.0-0.50.0.txt](llms/upgrading-0.37.0-0.50.0.txt) 31.6 KB
64
+ - **Upgrading 0.96.0 → 0.106.0**: [llms/upgrading-0.96.0-0.106.0.txt](llms/upgrading-0.96.0-0.106.0.txt) 46.6 KB
65
+ - **Upgrading 0.69.0 → 0.95.0**: [llms/upgrading-0.69.0-0.95.0.txt](llms/upgrading-0.69.0-0.95.0.txt) 46.6 KB
66
+ - **Upgrading 0.55.0 → 0.68.0**: [llms/upgrading-0.55.0-0.68.0.txt](llms/upgrading-0.55.0-0.68.0.txt) 47.0 KB
67
+ - **Upgrading 0.37.0 → 0.53.0**: [llms/upgrading-0.37.0-0.53.0.txt](llms/upgrading-0.37.0-0.53.0.txt) 32.6 KB
68
68
 
69
69
  ## Guide (web)
70
70
  - [Getting started](https://github.com/max-listov/stitchkit/blob/master/docs/guide/getting-started.md): install, entrypoints, and a first contract → server → client app
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.105.2",
3
+ "version": "0.106.0",
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",