stitchkit 0.76.2 → 0.78.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-runtime-coding-tools.js +1 -1
- package/dist/agent-runtime-harness.js +6 -6
- package/dist/agent-runtime.js +11 -11
- package/dist/application/decisions.d.ts +89 -0
- package/dist/application/decisions.d.ts.map +1 -0
- package/dist/application/kernel.d.ts +35 -1
- package/dist/application/kernel.d.ts.map +1 -1
- package/dist/application/keyspace.d.ts +11 -1
- package/dist/application/keyspace.d.ts.map +1 -1
- package/dist/application/schedule.d.ts.map +1 -1
- package/dist/application/server-resource.d.ts.map +1 -1
- package/dist/application-diagnostic-journal.d.ts +21 -0
- package/dist/application-diagnostic-journal.d.ts.map +1 -0
- package/dist/application-diagnostic-journal.js +714 -0
- package/dist/application-opentelemetry.js +92 -5
- package/dist/application-schemas.d.ts +15 -0
- package/dist/application-schemas.d.ts.map +1 -0
- package/dist/application-schemas.js +26 -0
- package/dist/application.d.ts +3 -3
- package/dist/application.d.ts.map +1 -1
- package/dist/application.js +1832 -844
- package/dist/cli.js +7 -7
- package/dist/contract/index.js +9 -7
- package/dist/files/boundary.d.ts.map +1 -1
- package/dist/files.js +1 -1
- package/dist/{index-3z73fh2c.js → index-1ckavx4h.js} +49 -1
- package/dist/{index-s2rchahr.js → index-31k1ns1s.js} +4 -4
- package/dist/{index-m668wzyc.js → index-4ntp63ps.js} +4 -4
- package/dist/{index-1rxswfbv.js → index-56fm73re.js} +1 -1
- package/dist/{index-ezmn6ac6.js → index-5r0ak8fr.js} +3 -3
- package/dist/{index-f6n5n7nz.js → index-7z69kyrh.js} +10 -87
- package/dist/{index-k2zczx1g.js → index-93xp5tgg.js} +1 -1
- package/dist/{index-bfcpjw20.js → index-actkayzn.js} +7 -3
- package/dist/index-anyqnqcv.js +79 -0
- package/dist/{index-7qy2ex0m.js → index-cwp38wxn.js} +1 -1
- package/dist/{index-35z5h2ty.js → index-dmfn9m4x.js} +125 -4
- package/dist/index-h4pj6fta.js +87 -0
- package/dist/{index-jqtsc9mj.js → index-j6mfaw26.js} +2 -2
- package/dist/{index-58jzmnn4.js → index-k45qqrdh.js} +513 -39
- package/dist/{index-8eywc9zv.js → index-kw12fsbc.js} +0 -1
- package/dist/index-n1g2xm6a.js +203 -0
- package/dist/{index-t8qyvrvg.js → index-n7apa6sy.js} +12 -12
- package/dist/{index-s4c8wy8m.js → index-qaajjfp6.js} +8 -8
- package/dist/{index-nemjkxjp.js → index-s5tn4kdv.js} +14 -10
- package/dist/index-wfq8zerm.js +92 -0
- package/dist/index.js +53 -1215
- package/dist/internal/deadline.d.ts +17 -0
- package/dist/internal/deadline.d.ts.map +1 -0
- package/dist/internal/decision.d.ts +31 -0
- package/dist/internal/decision.d.ts.map +1 -0
- package/dist/live/events.d.ts +8 -13
- package/dist/live/events.d.ts.map +1 -1
- package/dist/live.d.ts +1 -1
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +12 -89
- package/dist/node.js +5 -6
- package/dist/observability/context.d.ts.map +1 -1
- package/dist/observability/index.js +3 -3
- package/dist/primitives.js +2 -1
- package/dist/remote.js +3 -6
- package/dist/server/event-bus.d.ts +3 -2
- package/dist/server/event-bus.d.ts.map +1 -1
- package/dist/server/index.js +31 -30
- package/dist/testing.js +1562 -87
- package/dist/tool-invoker.js +6 -6
- package/dist/tools.js +66 -23
- package/llms-full.txt +276 -6
- package/package.json +12 -4
- package/dist/index-3cwck0rm.js +0 -897
- package/dist/index-7b188kmz.js +0 -49
- package/dist/index-a916km3r.js +0 -45
- package/dist/index-hvftzz91.js +0 -721
- package/dist/{index-da1aqnhb.js → index-aewy3x5j.js} +3 -3
- package/dist/{index-nt1mp8km.js → index-efm026pw.js} +3 -3
package/dist/tool-invoker.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createToolInvoker
|
|
3
|
-
} from "./index-
|
|
4
|
-
import"./index-
|
|
5
|
-
import"./index-
|
|
6
|
-
import"./index-nemjkxjp.js";
|
|
3
|
+
} from "./index-j6mfaw26.js";
|
|
4
|
+
import"./index-cwp38wxn.js";
|
|
5
|
+
import"./index-qaajjfp6.js";
|
|
7
6
|
import"./index-22by16v6.js";
|
|
8
7
|
import"./index-cby4ar3v.js";
|
|
9
|
-
import"./index-
|
|
8
|
+
import"./index-s5tn4kdv.js";
|
|
9
|
+
import"./index-efm026pw.js";
|
|
10
10
|
import"./index-vj3vvpaa.js";
|
|
11
|
-
import"./index-0w9abg87.js";
|
|
12
11
|
import"./index-qyrqwr4c.js";
|
|
12
|
+
import"./index-0w9abg87.js";
|
|
13
13
|
import"./index-6k1937bx.js";
|
|
14
14
|
export {
|
|
15
15
|
createToolInvoker
|
package/dist/tools.js
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
redact
|
|
3
|
-
} from "./index-aczggrty.js";
|
|
4
1
|
import {
|
|
5
2
|
buildToolManifest,
|
|
6
3
|
mountAgent
|
|
7
|
-
} from "./index-
|
|
4
|
+
} from "./index-5r0ak8fr.js";
|
|
8
5
|
import"./index-3xnq72rz.js";
|
|
9
|
-
import {
|
|
10
|
-
argumentsDigest
|
|
11
|
-
} from "./index-a916km3r.js";
|
|
12
6
|
import {
|
|
13
7
|
signJwt,
|
|
14
8
|
verifyPkce
|
|
15
|
-
} from "./index-
|
|
9
|
+
} from "./index-aewy3x5j.js";
|
|
16
10
|
import {
|
|
17
11
|
DEFAULT_CORS_ALLOW_HEADERS,
|
|
18
12
|
DEFAULT_PROCESS_SIGNALS,
|
|
@@ -23,7 +17,7 @@ import {
|
|
|
23
17
|
} from "./index-vbf2p6me.js";
|
|
24
18
|
import {
|
|
25
19
|
createToolInvoker
|
|
26
|
-
} from "./index-
|
|
20
|
+
} from "./index-j6mfaw26.js";
|
|
27
21
|
import {
|
|
28
22
|
WaitTimeoutError,
|
|
29
23
|
createCli,
|
|
@@ -32,10 +26,10 @@ import {
|
|
|
32
26
|
fetchPinnedDocument,
|
|
33
27
|
readCapped,
|
|
34
28
|
runWaitOperation
|
|
35
|
-
} from "./index-
|
|
29
|
+
} from "./index-31k1ns1s.js";
|
|
36
30
|
import {
|
|
37
31
|
collectToolSurface
|
|
38
|
-
} from "./index-
|
|
32
|
+
} from "./index-93xp5tgg.js";
|
|
39
33
|
import {
|
|
40
34
|
createRuntimeToolFactory,
|
|
41
35
|
defineRuntimeTool
|
|
@@ -44,22 +38,17 @@ import {
|
|
|
44
38
|
collectTools,
|
|
45
39
|
createToolRunner,
|
|
46
40
|
formatToolError
|
|
47
|
-
} from "./index-
|
|
41
|
+
} from "./index-cwp38wxn.js";
|
|
48
42
|
import {
|
|
49
43
|
ToolExecutionControlError,
|
|
50
44
|
coerceJsonArgs,
|
|
51
45
|
executeToolMethod,
|
|
52
46
|
isToolExecutionControlError,
|
|
53
47
|
toolResultFromError
|
|
54
|
-
} from "./index-
|
|
55
|
-
import {
|
|
56
|
-
getRequestContext,
|
|
57
|
-
getTraceId,
|
|
58
|
-
runWithRequestContext
|
|
59
|
-
} from "./index-nemjkxjp.js";
|
|
48
|
+
} from "./index-qaajjfp6.js";
|
|
60
49
|
import {
|
|
61
50
|
ManagedFileError
|
|
62
|
-
} from "./index-
|
|
51
|
+
} from "./index-actkayzn.js";
|
|
63
52
|
import"./index-sbdmyz75.js";
|
|
64
53
|
import {
|
|
65
54
|
PORTABLE_JSON_SCHEMA_FORMATS,
|
|
@@ -73,20 +62,28 @@ import {
|
|
|
73
62
|
validateMcpRoundPolicy
|
|
74
63
|
} from "./index-22by16v6.js";
|
|
75
64
|
import"./index-cby4ar3v.js";
|
|
65
|
+
import {
|
|
66
|
+
redact
|
|
67
|
+
} from "./index-aczggrty.js";
|
|
68
|
+
import {
|
|
69
|
+
getRequestContext,
|
|
70
|
+
getTraceId,
|
|
71
|
+
runWithRequestContext
|
|
72
|
+
} from "./index-s5tn4kdv.js";
|
|
76
73
|
import {
|
|
77
74
|
normalizeError,
|
|
78
75
|
resolvePropagationContext
|
|
79
|
-
} from "./index-
|
|
76
|
+
} from "./index-efm026pw.js";
|
|
80
77
|
import {
|
|
81
78
|
defineContract
|
|
82
79
|
} from "./index-vj3vvpaa.js";
|
|
80
|
+
import {
|
|
81
|
+
isRecord
|
|
82
|
+
} from "./index-qyrqwr4c.js";
|
|
83
83
|
import {
|
|
84
84
|
AppError,
|
|
85
85
|
STITCH_ERROR_STATUS
|
|
86
86
|
} from "./index-0w9abg87.js";
|
|
87
|
-
import {
|
|
88
|
-
isRecord
|
|
89
|
-
} from "./index-qyrqwr4c.js";
|
|
90
87
|
import {
|
|
91
88
|
ManagedFilePathSchema,
|
|
92
89
|
ManagedFileRefSchema
|
|
@@ -1070,6 +1067,52 @@ import {
|
|
|
1070
1067
|
inputResponse,
|
|
1071
1068
|
PROTOCOL_VERSION_META_KEY
|
|
1072
1069
|
} from "@modelcontextprotocol/server";
|
|
1070
|
+
|
|
1071
|
+
// src/internal/stable-digest.ts
|
|
1072
|
+
function stableValue(value) {
|
|
1073
|
+
if (Array.isArray(value))
|
|
1074
|
+
return value.map(stableValue);
|
|
1075
|
+
if (value === null || typeof value !== "object")
|
|
1076
|
+
return value;
|
|
1077
|
+
const result = {};
|
|
1078
|
+
for (const key of Object.keys(value).sort()) {
|
|
1079
|
+
result[key] = stableValue(Reflect.get(value, key));
|
|
1080
|
+
}
|
|
1081
|
+
return result;
|
|
1082
|
+
}
|
|
1083
|
+
function mix128(input) {
|
|
1084
|
+
let a = 2654435769;
|
|
1085
|
+
let b = 2246822507;
|
|
1086
|
+
let c = 3266489909;
|
|
1087
|
+
let d = 668265263;
|
|
1088
|
+
for (let index = 0;index < input.length; index += 1) {
|
|
1089
|
+
const code = input.charCodeAt(index);
|
|
1090
|
+
a = Math.imul(a ^ code, 2246822519);
|
|
1091
|
+
b = Math.imul(b ^ code, 3266489917);
|
|
1092
|
+
c = Math.imul(c ^ code, 668265263);
|
|
1093
|
+
d = Math.imul(d ^ code, 374761393);
|
|
1094
|
+
a = a << 13 | a >>> 19;
|
|
1095
|
+
b = b << 17 | b >>> 15;
|
|
1096
|
+
c = c << 7 | c >>> 25;
|
|
1097
|
+
d = d << 11 | d >>> 21;
|
|
1098
|
+
}
|
|
1099
|
+
a ^= b >>> 15;
|
|
1100
|
+
b ^= c >>> 13;
|
|
1101
|
+
c ^= d >>> 11;
|
|
1102
|
+
d ^= a >>> 9;
|
|
1103
|
+
return [
|
|
1104
|
+
Math.imul(a ^ a >>> 16, 575075507) >>> 0,
|
|
1105
|
+
Math.imul(b ^ b >>> 13, 2654435761) >>> 0,
|
|
1106
|
+
Math.imul(c ^ c >>> 16, 2246822507) >>> 0,
|
|
1107
|
+
Math.imul(d ^ d >>> 15, 3266489909) >>> 0
|
|
1108
|
+
];
|
|
1109
|
+
}
|
|
1110
|
+
function argumentsDigest(args) {
|
|
1111
|
+
const lanes = mix128(JSON.stringify(stableValue(args)) ?? "undefined");
|
|
1112
|
+
return lanes.map((lane) => lane.toString(16).padStart(8, "0")).join("");
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// src/tools/mcp-round.ts
|
|
1073
1116
|
function transportContext(context, toolName, outcome, round) {
|
|
1074
1117
|
const protocolVersionValue = isRecord(context.mcpReq.envelope) ? Reflect.get(context.mcpReq.envelope, PROTOCOL_VERSION_META_KEY) : undefined;
|
|
1075
1118
|
const protocolVersion = typeof protocolVersionValue === "string" ? protocolVersionValue : undefined;
|
package/llms-full.txt
CHANGED
|
@@ -62,7 +62,7 @@ own, recorded as an ADR.
|
|
|
62
62
|
| `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, plus the store and managed-resource conformance kits |
|
|
63
63
|
| `stitchkit/declaration` | build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
|
|
64
64
|
| `stitchkit/react` | browser | stable | `createCursorQuery`, `createCacheBridge` |
|
|
65
|
-
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in 11 of the
|
|
65
|
+
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in 11 of the 23 minors since 0.56.2, most recently 0.75.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
|
|
66
66
|
| `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
|
|
67
67
|
| `stitchkit/agent-runtime/coding-tools` | server (Bun or Node) | evolving | bounded host-authorized direct file and shell tools; a root boundary, not an OS sandbox |
|
|
68
68
|
| `stitchkit/agent-runtime/openrouter` | server | evolving | isolated OpenRouter language-model adapter |
|
|
@@ -70,9 +70,11 @@ own, recorded as an ADR.
|
|
|
70
70
|
| `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
|
|
71
71
|
| `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
|
|
72
72
|
| `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
|
|
73
|
-
| `stitchkit/application` | server | evolving<br>_redefined in
|
|
73
|
+
| `stitchkit/application` | browser + server | evolving<br>_redefined in 5 of the 23 minors since 0.56.2, most recently 0.78.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
|
|
74
74
|
| `stitchkit/application/grammy` | server | evolving | isolated grammY polling and webhook lifecycle adapters |
|
|
75
75
|
| `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
|
|
76
|
+
| `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
|
|
77
|
+
| `stitchkit/application/diagnostic-journal` | server | evolving | the local diagnostic journal — the one part of the kernel that spawns, locks and writes files |
|
|
76
78
|
|
|
77
79
|
Rule of thumb: browser code imports `stitchkit` and `stitchkit/react`; server
|
|
78
80
|
code adds `stitchkit/server` (or `stitchkit/node` on Node) and opts into
|
|
@@ -5478,6 +5480,133 @@ Every instrument uses unit `1` and reports an absolute current/lifetime value:
|
|
|
5478
5480
|
| `stitchkit.application.schedule.accepting`, `.active`, `.queued`, `.runs_started`, `.runs_completed`, `.runs_failed`, `.ticks_skipped` | current schedule state and absolute run/tick counts |
|
|
5479
5481
|
| `stitchkit.application.activity.active`, `.queued`, `.completed`, `.failed` | absolute stage projections for declared activity sources |
|
|
5480
5482
|
|
|
5483
|
+
## Replacing part of the graph without stopping the process
|
|
5484
|
+
|
|
5485
|
+
`restart` takes down one resource **and everything that depends on it**, then
|
|
5486
|
+
brings that subtree back. Everything else keeps running, and the process epoch
|
|
5487
|
+
does not move.
|
|
5488
|
+
|
|
5489
|
+
```ts
|
|
5490
|
+
const result = await app.restart({ resourceId: 'database' })
|
|
5491
|
+
|
|
5492
|
+
result.outcome // 'restarted' | 'failed' | 'refused'
|
|
5493
|
+
result.affected // ['database', 'repository', 'api'] — in start order
|
|
5494
|
+
```
|
|
5495
|
+
|
|
5496
|
+
The dependants come down with it because they are holding what the resource
|
|
5497
|
+
published. A repository that kept running across a database replacement holds a
|
|
5498
|
+
pool that has been closed: it still typechecks, still has methods, and fails at
|
|
5499
|
+
whatever moment the first call happens. There is no version of this where a
|
|
5500
|
+
dependant keeps a live handle, so the subtree — not the resource — is the unit.
|
|
5501
|
+
|
|
5502
|
+
A leaf restart affects one resource, and an independent neighbour is never
|
|
5503
|
+
touched:
|
|
5504
|
+
|
|
5505
|
+
```ts
|
|
5506
|
+
await app.restart({ resourceId: 'cache' })
|
|
5507
|
+
// mailer and database were not stopped, not started, not activated
|
|
5508
|
+
```
|
|
5509
|
+
|
|
5510
|
+
**Refused is not failed.** An unknown id, a restart during shutdown, and a
|
|
5511
|
+
restart before the application is ready all return `refused` with a reason and
|
|
5512
|
+
touch nothing. `failed` means the subtree came down and the new generation did
|
|
5513
|
+
not come up — and the result agrees with the snapshot: if any affected resource
|
|
5514
|
+
ends the restart in `failed`, so does the restart, including an optional one
|
|
5515
|
+
whose failure the start loop does not re-throw.
|
|
5516
|
+
|
|
5517
|
+
**The close phase is bounded**, by the application's shutdown budget or by one
|
|
5518
|
+
this call names:
|
|
5519
|
+
|
|
5520
|
+
```ts
|
|
5521
|
+
await app.restart({ resourceId: 'database', gracePeriodMs: 5_000, forceTimeoutMs: 2_000 })
|
|
5522
|
+
```
|
|
5523
|
+
|
|
5524
|
+
**A resource has to be able to start twice.** The kernel calls `start` again, so
|
|
5525
|
+
a resource holding state across its own lifetime rebuilds it there. The ones this
|
|
5526
|
+
framework ships do — a schedule re-arms, a keyspace opens a new generation and
|
|
5527
|
+
re-loads, a managed server built from a factory gets a fresh server. A managed
|
|
5528
|
+
server given a server *instance* cannot, and says so by name rather than
|
|
5529
|
+
republishing something it has shut down. Give a keyspace a
|
|
5530
|
+
`backend: () => …` factory when its backend cannot be re-opened after `close()`.
|
|
5531
|
+
|
|
5532
|
+
→ [ADR 0157](../decisions/0157-a-restartable-resource-begins-a-generation.md).
|
|
5533
|
+
|
|
5534
|
+
Restarts of overlapping subtrees queue behind each other rather than being
|
|
5535
|
+
refused. Two callers asking at once is ordinary; two generations of one resource
|
|
5536
|
+
alive at once is the thing this must make impossible.
|
|
5537
|
+
|
|
5538
|
+
```ts
|
|
5539
|
+
// Both succeed, one complete pass after the other.
|
|
5540
|
+
await Promise.all([
|
|
5541
|
+
app.restart({ resourceId: 'database' }),
|
|
5542
|
+
app.restart({ resourceId: 'database' }),
|
|
5543
|
+
])
|
|
5544
|
+
```
|
|
5545
|
+
|
|
5546
|
+
What a restart is **not** is a process restart: it replaces resources, and does
|
|
5547
|
+
not re-read configuration the kernel captured when it was constructed.
|
|
5548
|
+
|
|
5549
|
+
→ [ADR 0154](../decisions/0154-the-unit-of-a-restart-is-the-subtree.md).
|
|
5550
|
+
|
|
5551
|
+
## Decisions a policy set makes together
|
|
5552
|
+
|
|
5553
|
+
`createDecisionPipeline` runs an ordered list of policies that each vote
|
|
5554
|
+
`allow`, `deny` (with a reason) or `defer`. The first terminal verdict wins and
|
|
5555
|
+
the rest do not run.
|
|
5556
|
+
|
|
5557
|
+
```ts
|
|
5558
|
+
import { createDecisionPipeline } from 'stitchkit/application'
|
|
5559
|
+
|
|
5560
|
+
const pipeline = createDecisionPipeline<{ userId: string; scope: string }>([
|
|
5561
|
+
{ id: 'banned', decide: (r) => (isBanned(r.userId)
|
|
5562
|
+
? { outcome: 'deny', reason: 'account suspended' }
|
|
5563
|
+
: { outcome: 'defer' }) },
|
|
5564
|
+
{ id: 'scope', decide: (r) => (r.scope === 'admin'
|
|
5565
|
+
? { outcome: 'allow' }
|
|
5566
|
+
: { outcome: 'defer' }) },
|
|
5567
|
+
{ id: 'default', decide: () => ({ outcome: 'deny', reason: 'no policy allowed this' }) },
|
|
5568
|
+
], { policyTimeoutMs: 2_000 })
|
|
5569
|
+
|
|
5570
|
+
const result = await pipeline.decide(request)
|
|
5571
|
+
result.outcome // 'allow' | 'deny' — never 'defer'
|
|
5572
|
+
result.trace // the policies that actually ran, in order
|
|
5573
|
+
```
|
|
5574
|
+
|
|
5575
|
+
Three things are deliberate.
|
|
5576
|
+
|
|
5577
|
+
**A deny carries a reason, by schema.** `{ outcome: 'deny' }` alone does not
|
|
5578
|
+
typecheck. A refusal whose cause exists only in the log of whoever refused is a
|
|
5579
|
+
support ticket.
|
|
5580
|
+
|
|
5581
|
+
**The trace is what ran, not what was declared.** It stops at the terminal
|
|
5582
|
+
verdict. When something was denied the question is which policy denied it and
|
|
5583
|
+
what the ones before it said, and a trace listing policies that never ran would
|
|
5584
|
+
answer that wrongly while looking complete.
|
|
5585
|
+
|
|
5586
|
+
**Every policy deferring raises.** `DecisionUndecidedError`, not a default —
|
|
5587
|
+
defaulting to `allow` turns an incomplete policy set into an open door, and
|
|
5588
|
+
defaulting to `deny` turns it into an outage whose cause reads as a legitimate
|
|
5589
|
+
refusal.
|
|
5590
|
+
|
|
5591
|
+
**A policy that does not answer raises too.** A non-decision, a throw, and running
|
|
5592
|
+
past `policyTimeoutMs` are one error — `DecisionPolicyError`, naming the policy
|
|
5593
|
+
and carrying the trace so far — because they are indistinguishable downstream and
|
|
5594
|
+
handling one but not the others is how a broken policy becomes a skipped one. The
|
|
5595
|
+
chain stops there rather than falling through to whatever the next policy would
|
|
5596
|
+
have said.
|
|
5597
|
+
|
|
5598
|
+
`policyTimeoutMs` is required and has no default. Pick a number your slowest
|
|
5599
|
+
policy comfortably beats. The framework will not choose it for you: a policy that
|
|
5600
|
+
never settles hangs every caller of the operation it guards, and a default here
|
|
5601
|
+
is a number nobody chose applied to code the framework has never seen.
|
|
5602
|
+
|
|
5603
|
+
The same `allow`/`deny`/`defer` type is what an event topic declared
|
|
5604
|
+
`mode: 'decision'` uses in `stitchkit/live` — one vocabulary, because a listener
|
|
5605
|
+
voting on an event and a policy voting on a request are answering the same
|
|
5606
|
+
question.
|
|
5607
|
+
|
|
5608
|
+
→ [ADR 0155](../decisions/0155-one-decision-vocabulary-and-an-unanswered-question-is-an-error.md).
|
|
5609
|
+
|
|
5481
5610
|
## Admission and graceful shutdown
|
|
5482
5611
|
|
|
5483
5612
|
Use the application operation lease for work that is not already counted by a
|
|
@@ -5571,8 +5700,14 @@ changing its revision or status contract. → ADR 0119.
|
|
|
5571
5700
|
Use `createDiagnosticJournal` when a process needs finite, ordered local metadata evidence and the
|
|
5572
5701
|
deployment log pipeline is not the right boundary:
|
|
5573
5702
|
|
|
5703
|
+
It lives at `stitchkit/application/diagnostic-journal`, not in the main barrel:
|
|
5704
|
+
it is the one part of the kernel that spawns, locks and writes files, and while it
|
|
5705
|
+
was exported from `stitchkit/application` that single line made the whole
|
|
5706
|
+
entrypoint unusable in a browser bundle. Its schemas stay in
|
|
5707
|
+
`stitchkit/application`.
|
|
5708
|
+
|
|
5574
5709
|
```ts
|
|
5575
|
-
import { createDiagnosticJournal } from 'stitchkit/application'
|
|
5710
|
+
import { createDiagnosticJournal } from 'stitchkit/application/diagnostic-journal'
|
|
5576
5711
|
import { z } from 'zod'
|
|
5577
5712
|
|
|
5578
5713
|
const journal = await createDiagnosticJournal({
|
|
@@ -10001,6 +10136,73 @@ makes one thing your job rather than the resolver's:
|
|
|
10001
10136
|
The mechanical part is identical either way. Only the *noticing* differs, and an
|
|
10002
10137
|
exact pin moves it onto you.
|
|
10003
10138
|
|
|
10139
|
+
## Released migration: 0.78.0
|
|
10140
|
+
|
|
10141
|
+
Two mechanical edits. Find them both:
|
|
10142
|
+
|
|
10143
|
+
```bash
|
|
10144
|
+
rg -n "createDecisionPipeline|createDiagnosticJournal" --glob '*.ts' --glob '*.tsx'
|
|
10145
|
+
```
|
|
10146
|
+
|
|
10147
|
+
**1. `createDecisionPipeline` takes a deadline.**
|
|
10148
|
+
|
|
10149
|
+
```ts
|
|
10150
|
+
// before
|
|
10151
|
+
createDecisionPipeline([policyA, policyB])
|
|
10152
|
+
// after
|
|
10153
|
+
createDecisionPipeline([policyA, policyB], { policyTimeoutMs: 2_000 })
|
|
10154
|
+
```
|
|
10155
|
+
|
|
10156
|
+
Pick a number your slowest policy comfortably beats. There is no default on
|
|
10157
|
+
purpose: a policy that never settles hangs every caller of the operation it
|
|
10158
|
+
guards, and the framework has no basis for choosing that number for you.
|
|
10159
|
+
|
|
10160
|
+
Past the deadline — and on a throw, and on a non-decision — the pipeline raises
|
|
10161
|
+
`DecisionPolicyError` naming the policy, with the trace so far. If you already
|
|
10162
|
+
catch that for a bad return value, you now catch two more cases with it.
|
|
10163
|
+
|
|
10164
|
+
**2. `createDiagnosticJournal` moved.**
|
|
10165
|
+
|
|
10166
|
+
```ts
|
|
10167
|
+
// before
|
|
10168
|
+
import { createDiagnosticJournal } from 'stitchkit/application'
|
|
10169
|
+
// after
|
|
10170
|
+
import { createDiagnosticJournal } from 'stitchkit/application/diagnostic-journal'
|
|
10171
|
+
```
|
|
10172
|
+
|
|
10173
|
+
Only the factory moved. `DiagnosticJournalConfig`, every
|
|
10174
|
+
`DiagnosticJournal*Schema` and `readDiagnosticJournalLockDiagnosis` stay where
|
|
10175
|
+
they were.
|
|
10176
|
+
|
|
10177
|
+
**If you restart resources**, nothing to change — but three things that did not
|
|
10178
|
+
work now do: a managed schedule can be restarted, a restarted keyspace accepts
|
|
10179
|
+
writes again, and a restarted managed server is actually shut down at exit. If
|
|
10180
|
+
you pass `managedServerResource` a server *instance*, a restart is now refused
|
|
10181
|
+
by name; pass a factory (`server: (context) => …`) to restart it.
|
|
10182
|
+
|
|
10183
|
+
## Released migration: 0.77.0
|
|
10184
|
+
|
|
10185
|
+
Two type renames, and only if you named them. Nothing runtime moved.
|
|
10186
|
+
|
|
10187
|
+
```bash
|
|
10188
|
+
rg -n "EventDecision|EventUndecided" --glob '*.ts' --glob '*.tsx'
|
|
10189
|
+
```
|
|
10190
|
+
|
|
10191
|
+
```ts
|
|
10192
|
+
// before
|
|
10193
|
+
import type { EventDecision, EventUndecided } from 'stitchkit/live'
|
|
10194
|
+
// after
|
|
10195
|
+
import type { PolicyDecision, UndecidedOutcome } from 'stitchkit/live'
|
|
10196
|
+
```
|
|
10197
|
+
|
|
10198
|
+
The shapes are identical — `{ outcome: 'allow' } | { outcome: 'deny', reason } | { outcome: 'defer' }`
|
|
10199
|
+
and `'allow' | 'deny'`. If you only ever *return* decisions from listeners and never named the
|
|
10200
|
+
type, the search above finds nothing and there is nothing to do.
|
|
10201
|
+
|
|
10202
|
+
They were renamed because `createDecisionPipeline` (new in this release) votes with exactly the
|
|
10203
|
+
same three outcomes, and two identical types under two names is the thing that makes a search for
|
|
10204
|
+
either one return half the truth.
|
|
10205
|
+
|
|
10004
10206
|
## Released migration: 0.76.0
|
|
10005
10207
|
|
|
10006
10208
|
One change, and only if you hand `createWatchClient` a transport you wrote yourself.
|
|
@@ -13150,7 +13352,8 @@ realtime contract from `stitchkit`, and the server halves live in `stitchkit/app
|
|
|
13150
13352
|
|--------|------|---------|
|
|
13151
13353
|
| `defineEvents` | function | declare topics: a wire name, one payload schema and how the topic is delivered to in-process listeners |
|
|
13152
13354
|
| `toRealtimeContract` | function | project a declaration onto `RealtimeContract`, so the existing validated socket carries it |
|
|
13153
|
-
| `EventDeliveryMode`
|
|
13355
|
+
| `EventDeliveryMode` | _type_ | `emit` / `serial` / `decision` — how a topic reaches its in-process listeners |
|
|
13356
|
+
| `PolicyDecision` / `UndecidedOutcome` | _types_ | one voter's `allow` / `deny` / `defer`, and what a run settles on when every voter defers — the same two types the decision pipeline in `stitchkit/application` uses, because a listener voting on an event and a policy voting on a request are answering the same question |
|
|
13154
13357
|
| `EventTopicDeclaration` / `EventTopicRegistry` / `EventsConfig` / `EventsDeclaration` | _types_ | one topic's schema, mode, `whenAllDefer` and `listenerTimeoutMs`, and the declaration they compose into |
|
|
13155
13358
|
| `EventPayloads` / `EventTopicsOfMode` / `WireTopic` | _types_ | payload map keyed by wire topic, the topics of one mode, and the `prefix.name` a topic is addressed by |
|
|
13156
13359
|
| `createWatchClient` | function | contract-shaped watch client: `watch.action(args)` returns a ref-counted handle sharing one subscription |
|
|
@@ -13444,7 +13647,11 @@ cutovers are covered by the executable
|
|
|
13444
13647
|
|
|
13445
13648
|
| Export | Kind | Summary |
|
|
13446
13649
|
|--------|------|---------|
|
|
13447
|
-
| `createApplication` | function | compose a validated resource DAG into one
|
|
13650
|
+
| `createApplication` | function | compose a validated resource DAG into one startup, readiness, admission and shutdown state machine |
|
|
13651
|
+
| `ApplicationHandle.restart` | method | replace one resource and everything that depends on it, leaving the rest of the graph running and the process epoch unchanged |
|
|
13652
|
+
| `ApplicationRestartInputSchema` / `ApplicationRestartInput` | schema / _type_ | the resource to replace, by id, and optionally `gracePeriodMs` / `forceTimeoutMs` for this restart — the application's own shutdown budget otherwise |
|
|
13653
|
+
| `ApplicationRestartResultSchema` / `ApplicationRestartResult` | schema / _type_ | the subtree that was actually taken down and brought back, in start order, with the outcome, the reason on anything but success, and how long it took |
|
|
13654
|
+
| `ApplicationRestartOutcomeSchema` / `ApplicationRestartOutcome` | schema / _type_ | `restarted`, `failed` or `refused` — a refusal (unknown id, shutting down, not yet ready) is not a failure and touches nothing |
|
|
13448
13655
|
| `ApplicationResourceFailure` | _type_ | one resource failure with the cause its phase label cannot carry — delivered to `onResourceFailure` |
|
|
13449
13656
|
| `ApplicationResourcePhase` | _type_ | the phase a managed resource failed in — the vocabulary of `ApplicationResourceShutdown.failures` |
|
|
13450
13657
|
| `ApplicationShutdownOptionsSchema` / `ApplicationShutdownOptions` | schema / _type_ | the two shutdown budgets and an abort signal — without the HTTP-only `retryAfterSeconds` |
|
|
@@ -13568,6 +13775,10 @@ and `ApplicationEventSinkConfig`.
|
|
|
13568
13775
|
|
|
13569
13776
|
### Canonical application records
|
|
13570
13777
|
|
|
13778
|
+
These are also published on their own as
|
|
13779
|
+
[`stitchkit/application/schemas`](#stitchkitapplicationschemas), which carries
|
|
13780
|
+
no server runtime and can therefore be imported from a browser bundle.
|
|
13781
|
+
|
|
13571
13782
|
The entrypoint exports each Zod schema beside its inferred type:
|
|
13572
13783
|
`ApplicationIdSchema` / `ApplicationId`, `ApplicationLifecycleSchema` /
|
|
13573
13784
|
`ApplicationLifecycle`, `ApplicationHealthSchema` / `ApplicationHealth`,
|
|
@@ -13581,13 +13792,28 @@ the function that derives it from a snapshot,
|
|
|
13581
13792
|
`ApplicationResourceShutdownSchema` / `ApplicationResourceShutdown`, and
|
|
13582
13793
|
`ApplicationShutdownResultSchema` / `ApplicationShutdownResult`.
|
|
13583
13794
|
|
|
13795
|
+
### Decisions
|
|
13796
|
+
|
|
13797
|
+
A pipeline of policies that each vote `allow`, `deny` or `defer`, sharing its
|
|
13798
|
+
vocabulary with an event topic declared `mode: 'decision'`.
|
|
13799
|
+
|
|
13800
|
+
| Export | Kind | Summary |
|
|
13801
|
+
|--------|------|---------|
|
|
13802
|
+
| `createDecisionPipeline` | function | run policies in declaration order; the first terminal verdict wins and the rest do not run |
|
|
13803
|
+
| `DecisionPipelineConfig` | _type_ | `policyTimeoutMs` — how long one policy has to answer. Required, no default: a policy that never settles hangs every caller of the operation it guards |
|
|
13804
|
+
| `PolicyDecisionSchema` / `PolicyDecision` | schema / _type_ | `allow`, `deny` with a reason, or `defer` — a deny cannot be silent |
|
|
13805
|
+
| `DecisionPolicy` / `DecisionPipeline` | _types_ | one named voter over the caller's subject, and the pipeline it composes into |
|
|
13806
|
+
| `DecisionResult` / `DecisionTraceEntry` | _types_ | the verdict, and the trace of what actually ran — not what was declared |
|
|
13807
|
+
| `DecisionUndecidedError` | class | every policy deferred and no undecided outcome was declared: an unanswered question, raised rather than guessed |
|
|
13808
|
+
| `DecisionPolicyError` | class | a policy returned a non-decision, threw, or ran past `policyTimeoutMs` — one error for all three, carrying the policy id and the trace so far |
|
|
13809
|
+
|
|
13584
13810
|
### Keyspace and watched reads
|
|
13585
13811
|
|
|
13586
13812
|
| Export | Kind | Summary |
|
|
13587
13813
|
|--------|------|---------|
|
|
13588
13814
|
| `defineKeyspace` / `keyspaceResource` | function | a named record set read synchronously from memory and written through one serialised chain; backend first, then memory, then the change event |
|
|
13589
13815
|
| `openKeyspace` / `OpenedKeyspace` | function / type | the same keyspace opened directly, for an application that owns its own lifecycle rather than declaring resources to a kernel |
|
|
13590
|
-
| `KeyspaceBackend` / `KeyspaceDeclaration` / `KeyspaceChange` / `KeyspaceResourceConfig` / `OpenKeyspace` | _types_ | the four-method durability port, the declaration, the announced change, the resource's options and the published handle |
|
|
13816
|
+
| `KeyspaceBackend` / `KeyspaceDeclaration` / `KeyspaceChange` / `KeyspaceResourceConfig` / `OpenKeyspace` | _types_ | the four-method durability port, the declaration, the announced change, the resource's options and the published handle. `backend` takes a value or a **factory** — a factory is called once per generation, so a restarted keyspace gets a backend that was never closed |
|
|
13591
13817
|
| `memoryKeyspaceBackend` / `sqliteKeyspaceBackend` / `SqliteKeyspaceBackendConfig` | function / type | a disposable in-process backend, and one table with a key and a JSON payload over a caller-owned database |
|
|
13592
13818
|
| `SqliteDatabase` / `SqliteStatement` / `SqliteValue` | _types_ | the minimal synchronous SQLite boundary the framework types against |
|
|
13593
13819
|
| `createWatchHub` / `watchKey` | function | one read per question however many are asking: single-flight per key, re-read on a declared topic, publish only what changed |
|
|
@@ -13624,6 +13850,50 @@ snapshots; the adapter owns no SDK lifecycle, polling or delta state.
|
|
|
13624
13850
|
| `ApplicationTelemetryMeter` | _type_ | minimal structural `Meter.createObservableGauge` boundary compatible with `@opentelemetry/api` |
|
|
13625
13851
|
| `ApplicationOpenTelemetryCollectionError` | _type_ | isolated instrument-name/error diagnostic without product/provider attributes |
|
|
13626
13852
|
|
|
13853
|
+
## `stitchkit/application/diagnostic-journal`
|
|
13854
|
+
|
|
13855
|
+
| Export | Kind | Summary |
|
|
13856
|
+
|--------|------|---------|
|
|
13857
|
+
| `createDiagnosticJournal` | function | the bounded local journal: a file lock, a spawn to diagnose a stale one, and framed writes |
|
|
13858
|
+
|
|
13859
|
+
Its own entrypoint because of what it reaches — `node:child_process`,
|
|
13860
|
+
`node:fs`, `node:os`, `node:util` — with `promisify(execFile)` evaluated while
|
|
13861
|
+
the module initialises. Exported from `stitchkit/application`, that one line made
|
|
13862
|
+
the entire barrel unusable in a browser bundle: not by failing at the call, but
|
|
13863
|
+
by throwing during module initialisation, on every route.
|
|
13864
|
+
|
|
13865
|
+
Its **contract** stays in `stitchkit/application` — every
|
|
13866
|
+
`DiagnosticJournal*Schema`, the states and the refusal reasons touch nothing but
|
|
13867
|
+
Zod, and a client reading a journal's status has as much right to them as the
|
|
13868
|
+
server writing it.
|
|
13869
|
+
|
|
13870
|
+
## `stitchkit/application/schemas`
|
|
13871
|
+
|
|
13872
|
+
The canonical application records — and nothing else. Every export here is also
|
|
13873
|
+
reachable from `stitchkit/application`; the difference is what comes with it.
|
|
13874
|
+
|
|
13875
|
+
`stitchkit/application` is the server runtime: it reaches `node:child_process`,
|
|
13876
|
+
`node:fs` and `node:crypto`. A browser bundler does not omit those, it
|
|
13877
|
+
substitutes stubs — so a module that merely *names* an application schema fails
|
|
13878
|
+
while it is initialising, and the page never mounts, on every route rather than
|
|
13879
|
+
the one that wanted the schema. A contract whose `output` is an application
|
|
13880
|
+
snapshot could be declared and never consumed, which is the one thing a contract
|
|
13881
|
+
is for.
|
|
13882
|
+
|
|
13883
|
+
So the same schemas ship a second way, from a module with nothing behind them:
|
|
13884
|
+
|
|
13885
|
+
```ts
|
|
13886
|
+
// browser and server alike
|
|
13887
|
+
import { ApplicationSnapshotSchema } from 'stitchkit/application/schemas';
|
|
13888
|
+
```
|
|
13889
|
+
|
|
13890
|
+
The exports are exactly those listed under
|
|
13891
|
+
[canonical application records](#canonical-application-records) above.
|
|
13892
|
+
`packages/core/scripts/check-browser-clean.mjs` holds the promise against the
|
|
13893
|
+
built artifact: it walks the browser lane's real `dist` output and refuses a
|
|
13894
|
+
`node:` import reachable from any of it, and refuses an entry built for the
|
|
13895
|
+
browser that no `exports` path leads to — which is how this one was missing.
|
|
13896
|
+
|
|
13627
13897
|
---
|
|
13628
13898
|
|
|
13629
13899
|
## `stitchkit/agent-runtime`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stitchkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.78.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",
|
|
@@ -129,6 +129,14 @@
|
|
|
129
129
|
"types": "./dist/application-opentelemetry.d.ts",
|
|
130
130
|
"import": "./dist/application-opentelemetry.js"
|
|
131
131
|
},
|
|
132
|
+
"./application/diagnostic-journal": {
|
|
133
|
+
"types": "./dist/application-diagnostic-journal.d.ts",
|
|
134
|
+
"import": "./dist/application-diagnostic-journal.js"
|
|
135
|
+
},
|
|
136
|
+
"./application/schemas": {
|
|
137
|
+
"types": "./dist/application-schemas.d.ts",
|
|
138
|
+
"import": "./dist/application-schemas.js"
|
|
139
|
+
},
|
|
132
140
|
"./testing": {
|
|
133
141
|
"types": "./dist/testing.d.ts",
|
|
134
142
|
"import": "./dist/testing.js"
|
|
@@ -156,11 +164,11 @@
|
|
|
156
164
|
"scripts": {
|
|
157
165
|
"build:native-contained-files": "node scripts/build-contained-files-native.mjs",
|
|
158
166
|
"check": "bun x tsc --noEmit",
|
|
159
|
-
"build:browser": "bun build src/index.ts src/live.ts src/react.ts src/contract/index.ts src/primitives.ts src/declaration.ts src/agent-runtime-browser.ts --outdir dist --target node --packages external --splitting --root src",
|
|
160
|
-
"build:server": "bun build src/server/index.ts src/node.ts src/tools.ts src/tool-invoker.ts src/cli.ts src/
|
|
167
|
+
"build:browser": "bun build src/index.ts src/live.ts src/react.ts src/contract/index.ts src/primitives.ts src/declaration.ts src/application-schemas.ts src/application.ts src/remote.ts src/agent-runtime-browser.ts --outdir dist --target node --packages external --splitting --root src",
|
|
168
|
+
"build:server": "bun build src/server/index.ts src/node.ts src/tools.ts src/tool-invoker.ts src/cli.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-diagnostic-journal.ts src/application-grammy.ts src/application-opentelemetry.ts src/telegram.ts --outdir dist --target node --packages external --splitting --root src",
|
|
161
169
|
"build:js": "bun run build:browser && bun run build:server && bun scripts/preserve-webpack-ignore.mjs",
|
|
162
170
|
"build:types": "bun x tsc -p tsconfig.build.json --emitDeclarationOnly && bun scripts/rewrite-declaration-specifiers.mjs",
|
|
163
|
-
"build": "rm -rf dist && bun run build:js && bun run build:types && bun scripts/check-browser-clean.mjs && bun scripts/check-env-live.mjs && bun scripts/check-public-types.mjs",
|
|
171
|
+
"build": "rm -rf dist && bun run build: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",
|
|
164
172
|
"dev": "bun run build:js -- --watch",
|
|
165
173
|
"prepack": "cp ../../README.md ./README.md && bun ../../scripts/gen-llms.ts && bun run build",
|
|
166
174
|
"test": "bun test",
|