okengine 0.22.0 → 0.23.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/AGENTS.md +7 -0
- package/manifest.v1.schema.json +44 -0
- package/package.json +32 -19
- package/site/content/docs/ai/mcp.mdx +13 -9
- package/site/content/docs/ai/skills.mdx +1 -1
- package/site/content/docs/elements/ai/agents.mdx +61 -13
- package/site/content/docs/elements/ai/decide.mdx +536 -0
- package/site/content/docs/elements/ai/events.mdx +373 -0
- package/site/content/docs/elements/ai/index.mdx +9 -3
- package/site/content/docs/elements/ai/meta.json +1 -1
- package/site/content/docs/elements/ai/prompts.mdx +4 -2
- package/site/content/docs/elements/gate/auth.mdx +4 -1
- package/site/content/docs/elements/store/index.mdx +1 -1
- package/site/content/docs/elements/store/kv.mdx +5 -5
- package/site/content/docs/recipes/dragonfly.mdx +5 -5
- package/site/content/docs/recipes/index.mdx +6 -6
- package/site/content/docs/recipes/postgres.mdx +12 -8
- package/site/content/docs/recipes/redis.mdx +4 -4
- package/site/content/docs/reference/cli.mdx +2 -1
- package/site/content/docs/reference/configuration.mdx +9 -7
- package/site/content/docs/reference/fx.mdx +7 -6
- package/src/auth/api-key-sql.test.ts +44 -0
- package/src/auth/api-key-sql.ts +1 -0
- package/src/auth/api-keys.ts +30 -12
- package/src/auth/config.ts +6 -2
- package/src/auth/gate-auth.test.ts +20 -0
- package/src/cli/agents-md.test.ts +25 -0
- package/src/cli/ask-vault-gaps.test.ts +38 -1
- package/src/cli/ask-vault-gaps.ts +35 -2
- package/src/cli/decide.ts +174 -0
- package/src/cli/decision-lock-watch.test.ts +45 -0
- package/src/cli/decision-lock-watch.ts +51 -0
- package/src/cli/dev-app-runner.ts +34 -8
- package/src/cli/dev-auth-secret.test.ts +49 -0
- package/src/cli/dev-auth-secret.ts +49 -0
- package/src/cli/dev-hot.test.ts +41 -0
- package/src/cli/dev-hot.ts +48 -0
- package/src/cli/dev.test.ts +1 -1
- package/src/cli/dev.ts +24 -6
- package/src/cli/eval.ts +142 -1
- package/src/cli/index.ts +5 -0
- package/src/cli/load-config.images.test.ts +2 -2
- package/src/cli/load-config.ts +1 -1
- package/src/cli/mcp-from-console.ts +48 -0
- package/src/cli/registry.ts +35 -1
- package/src/client/agent.ts +307 -0
- package/src/client/transport.test.ts +20 -0
- package/src/client/transport.ts +2 -1
- package/src/client/types.ts +1 -1
- package/src/client-react/index.ts +7 -1
- package/src/client-react/use-agent-run.test.ts +271 -0
- package/src/client-react/use-agent-run.ts +193 -0
- package/src/compiler/ai-approval.test.ts +36 -0
- package/src/compiler/ai-repair.test.ts +22 -0
- package/src/compiler/decisions.extract.test.ts +164 -0
- package/src/compiler/effects-infer.ts +27 -1
- package/src/compiler/extract.ts +268 -12
- package/src/compiler/response.ts +35 -2
- package/src/config/index.ts +21 -3
- package/src/console/server/ai-runs-flows.ts +269 -0
- package/src/console/server/ai-runs.test.ts +323 -0
- package/src/console/server/ai-runs.ts +562 -0
- package/src/console/server/ai.ts +12 -1
- package/src/console/server/app.ts +10 -1
- package/src/console/server/decisions-flows.ts +157 -0
- package/src/console/server/decisions.test.ts +66 -0
- package/src/console/server/decisions.ts +229 -0
- package/src/console/server/flows.ts +50 -2
- package/src/console/server/invoke-user-flow.ts +9 -0
- package/src/console/server/runs-ingest.ts +16 -0
- package/src/console/server/serve.ts +5 -0
- package/src/console/server/state.ts +25 -1
- package/src/console/server/store-stats.test.ts +47 -0
- package/src/console/server/store-stats.ts +92 -18
- package/src/console/ui-next/dist/assets/access-page-DDKkhT9v.js +4 -0
- package/src/console/ui-next/dist/assets/{agent-disclosure-Bkohc_8X.js → agent-disclosure-62Xts8Xi.js} +1 -1
- package/src/console/ui-next/dist/assets/{cache-glyph-rL0lHxat.js → cache-glyph-DyeoKHKA.js} +1 -1
- package/src/console/ui-next/dist/assets/{call-pii-button-Deo4PP18.js → call-pii-button-DvfpLXsU.js} +1 -1
- package/src/console/ui-next/dist/assets/{collapsible-CoJ6amHf.js → collapsible-BY03SeCg.js} +1 -1
- package/src/console/ui-next/dist/assets/confirm-sheet-DZfuJXP4.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-BTjPVjRT.js +1 -0
- package/src/console/ui-next/dist/assets/decisions-page-CgkKlA56.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-Dk5LipF6.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-CRGrj5JU.js +1 -0
- package/src/console/ui-next/dist/assets/duration-tone-Y6HLjyJ5.js +9 -0
- package/src/console/ui-next/dist/assets/element-icons-C6t8Vlml.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-chrome-Dslh2yE8.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-CIVg7Jgb.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-DLPfNy-_.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CPYBzDh0.js → highlighted-json-D7nNzpgB.js} +1 -1
- package/src/console/ui-next/dist/assets/{http-method-DTPevKmM.js → http-method-DdL19zzh.js} +1 -1
- package/src/console/ui-next/dist/assets/index-BlrN49Hs.css +2 -0
- package/src/console/ui-next/dist/assets/index-C0lc_s9d.js +58 -0
- package/src/console/ui-next/dist/assets/observability-page-CRlcyLCC.js +8 -0
- package/src/console/ui-next/dist/assets/{replica-lag-BM27lXld.js → replica-lag-Bb3FWUu9.js} +7 -7
- package/src/console/ui-next/dist/assets/request-meta-DYBMWhX8.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-DTE1sjTC.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-CcE-SXC8.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CKMSEQ4s.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-QOfk0M0H.js +55 -0
- package/src/console/ui-next/dist/assets/units-page-DKC1CDDd.js +1 -0
- package/src/console/ui-next/dist/assets/use-vault-list-BAyQG8Re.js +1 -0
- package/src/console/ui-next/dist/assets/vault-page-B54v4Yit.js +2 -0
- package/src/console/ui-next/dist/index.html +4 -3
- package/src/console/ui-next/seed-parked-approval.ts +126 -0
- package/src/console/ui-next/src/client.ts +224 -1
- package/src/console/ui-next/src/components/explorer/detail-header.tsx +9 -0
- package/src/console/ui-next/src/components/explorer/explorer-start-toggle.tsx +5 -1
- package/src/console/ui-next/src/components/ui/sheet-form.tsx +21 -12
- package/src/console/ui-next/src/features/flows/decisions/decisions-page.tsx +216 -0
- package/src/console/ui-next/src/features/flows/decisions/resolve.test.ts +48 -0
- package/src/console/ui-next/src/features/flows/decisions/resolve.ts +111 -0
- package/src/console/ui-next/src/features/flows/flows-page.tsx +10 -0
- package/src/console/ui-next/src/features/flows/graph/element-map.ts +1 -0
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +6 -0
- package/src/console/ui-next/src/features/flows/traces/effect-summary.test.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/http-status.ts +58 -0
- package/src/console/ui-next/src/features/flows/traces/request-client-card.tsx +355 -0
- package/src/console/ui-next/src/features/flows/traces/request-client.test.ts +114 -0
- package/src/console/ui-next/src/features/flows/traces/request-client.ts +423 -0
- package/src/console/ui-next/src/features/flows/traces/request-input-view.test.ts +52 -6
- package/src/console/ui-next/src/features/flows/traces/request-input-view.ts +85 -19
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +505 -80
- package/src/console/ui-next/src/features/flows/traces/trace-lanes.test.ts +61 -0
- package/src/console/ui-next/src/features/flows/traces/trace-lanes.ts +78 -0
- package/src/console/ui-next/src/features/flows/traces/trace-nav.test.ts +48 -0
- package/src/console/ui-next/src/features/flows/traces/trace-nav.ts +57 -0
- package/src/console/ui-next/src/features/flows/traces/trace-request-section.tsx +275 -96
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +16 -2
- package/src/console/ui-next/src/features/observability/detail/ai-runs.tsx +369 -0
- package/src/console/ui-next/src/features/observability/observability-page.tsx +31 -1
- package/src/console/ui-next/src/features/observability/state/observability-selection.ts +18 -3
- package/src/console/ui-next/src/features/store/performance/kv-performance-panel.tsx +12 -1
- package/src/console/ui-next/src/features/store/performance/performance-panel.tsx +14 -1
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +1 -1
- package/src/console/ui-next/src/features/units/detail/flow-contract-panel.tsx +6 -2
- package/src/console/ui-next/src/features/units/explorer/units-tree.tsx +73 -18
- package/src/console/ui-next/src/features/units/lib/unit-tree.test.ts +21 -0
- package/src/console/ui-next/src/features/units/lib/unit-tree.ts +82 -0
- package/src/console/ui-next/src/features/units/units-page.tsx +25 -7
- package/src/console/ui-next/src/router.tsx +17 -0
- package/src/console/ui-next/ui-next-seed-runs.ts +23 -0
- package/src/console/ui-next/vite-console-kernel-plugin.ts +43 -3
- package/src/console/ui-next/vite.config.ts +9 -0
- package/src/docker/recipes/dragonfly.ts +3 -2
- package/src/docker/recipes/redis.ts +1 -1
- package/src/drivers/ai-anthropic.ts +212 -21
- package/src/drivers/ai-mock.ts +34 -1
- package/src/drivers/ai-openai-compatible.ts +67 -26
- package/src/drivers/ai-preconnect.ts +18 -0
- package/src/drivers/ai-stream.test.ts +62 -0
- package/src/drivers/ai-types.ts +20 -0
- package/src/drivers/journal-postgres.ts +289 -2
- package/src/drivers/memory-ddl.test.ts +18 -0
- package/src/drivers/memory.ts +21 -1
- package/src/drivers/pg-rls.test.ts +31 -0
- package/src/drivers/pg-rls.ts +15 -0
- package/src/drivers/postgres.test.ts +9 -0
- package/src/drivers/postgres.ts +49 -11
- package/src/elements/ai/agent-event-slot.ts +27 -0
- package/src/elements/ai/approval-http.ts +207 -0
- package/src/elements/ai/approval.test.ts +741 -0
- package/src/elements/ai/approval.ts +228 -0
- package/src/elements/ai/decisions/bind.ts +163 -0
- package/src/elements/ai/decisions/certificate.test.ts +291 -0
- package/src/elements/ai/decisions/certificate.ts +377 -0
- package/src/elements/ai/decisions/certify.ts +314 -0
- package/src/elements/ai/decisions/decide.test.ts +953 -0
- package/src/elements/ai/decisions/e2e.test.ts +399 -0
- package/src/elements/ai/decisions/export.test.ts +120 -0
- package/src/elements/ai/decisions/export.ts +146 -0
- package/src/elements/ai/decisions/fixtures/openrouter-request.json +24 -0
- package/src/elements/ai/decisions/fixtures/openrouter-response.json +22 -0
- package/src/elements/ai/decisions/fixtures/typesafe-request.json +19 -0
- package/src/elements/ai/decisions/fixtures/typesafe-response.json +13 -0
- package/src/elements/ai/decisions/http.test.ts +154 -0
- package/src/elements/ai/decisions/http.ts +240 -0
- package/src/elements/ai/decisions/labels.test.ts +282 -0
- package/src/elements/ai/decisions/labels.ts +257 -0
- package/src/elements/ai/decisions/openrouter.live.test.ts +136 -0
- package/src/elements/ai/decisions/openrouter.ts +36 -0
- package/src/elements/ai/decisions/provider.ts +102 -0
- package/src/elements/ai/decisions/typesafe.ts +36 -0
- package/src/elements/ai/declare.ts +252 -1
- package/src/elements/ai/events.test.ts +379 -0
- package/src/elements/ai/events.ts +138 -0
- package/src/elements/ai/run-events.test.ts +461 -0
- package/src/elements/ai/run-events.ts +522 -0
- package/src/elements/ai/runtime.ts +1186 -105
- package/src/elements/ai/stream-turn.ts +214 -0
- package/src/elements/ai/stream.live.test.ts +76 -0
- package/src/elements/ai/subagent.test.ts +136 -0
- package/src/elements/ai.test.ts +497 -3
- package/src/elements/ai.ts +16 -1
- package/src/elements/clock/durable.ts +27 -1
- package/src/elements/store/sql-session.test.ts +51 -0
- package/src/elements/store/sql-session.ts +111 -1
- package/src/i18n/catalogs/ar.ts +5 -0
- package/src/i18n/catalogs/en.ts +5 -0
- package/src/index.ts +2 -0
- package/src/kernel/agent-event-store.ts +448 -0
- package/src/kernel/app.ts +103 -1
- package/src/kernel/boot-bind/ai.ts +3 -0
- package/src/kernel/boot-bind/store.test.ts +26 -0
- package/src/kernel/boot-bind/store.ts +82 -6
- package/src/kernel/boot.ts +15 -4
- package/src/kernel/builtin-errors.ts +2 -0
- package/src/kernel/capability.ts +3 -0
- package/src/kernel/decision-label-store.ts +441 -0
- package/src/kernel/effects.test.ts +2 -1
- package/src/kernel/effects.ts +13 -1
- package/src/kernel/element-registries.ts +3 -0
- package/src/kernel/errors-text.ts +4 -0
- package/src/kernel/errors.ts +2 -0
- package/src/kernel/flow.test.ts +2 -3
- package/src/kernel/fx-decide.ts +761 -0
- package/src/kernel/fx.test.ts +12 -5
- package/src/kernel/fx.ts +312 -29
- package/src/kernel/http-frame.test.ts +94 -0
- package/src/kernel/http-frame.ts +203 -0
- package/src/kernel/journal.ts +104 -0
- package/src/kernel/json-result.ts +10 -0
- package/src/kernel/pipeline.test.ts +45 -0
- package/src/kernel/sse-id.ts +32 -0
- package/src/manifest/diff.ts +1 -0
- package/src/manifest/types.ts +29 -2
- package/src/mcp/ai-tools.test.ts +202 -0
- package/src/mcp/authorization.ts +66 -0
- package/src/mcp/session.ts +3 -0
- package/src/mcp/tools.ts +168 -0
- package/src/release/http-graph.test.ts +2 -0
- package/src/release/http-graph.ts +5 -0
- package/src/release/measure.ts +39 -14
- package/src/runs/collect.ts +4 -1
- package/src/runs/parquet.test.ts +27 -1
- package/src/runs/parquet.ts +19 -0
- package/src/runs/types.ts +31 -0
- package/src/runtime/json-code-block.test.ts +106 -47
- package/src/runtime/json-code-block.ts +1209 -46
- package/src/term.test.ts +13 -0
- package/src/term.ts +81 -49
- package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +0 -1
- package/src/console/ui-next/dist/assets/access-page-Ze6ckjdd.js +0 -4
- package/src/console/ui-next/dist/assets/copy-inline-button-CWG5_ZOh.js +0 -1
- package/src/console/ui-next/dist/assets/detail-header-b_ZzIsmU.js +0 -1
- package/src/console/ui-next/dist/assets/dropdown-menu-nwXEO1Ac.js +0 -1
- package/src/console/ui-next/dist/assets/duration-tone-BbQ_8z50.js +0 -9
- package/src/console/ui-next/dist/assets/element-icons-CwyLpVXz.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-C8sSCqYT.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-D2E7BtKK.js +0 -1
- package/src/console/ui-next/dist/assets/index-VxoEz295.css +0 -2
- package/src/console/ui-next/dist/assets/index-vTuwmeQz.js +0 -58
- package/src/console/ui-next/dist/assets/observability-page-SJTSzHFJ.js +0 -4
- package/src/console/ui-next/dist/assets/request-meta-BjFT7DNl.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-CUNegEV4.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-Qj9ThRE-.js +0 -41
- package/src/console/ui-next/dist/assets/trace-detail-sheet-Cmitq3da.js +0 -2
- package/src/console/ui-next/dist/assets/tree-expand-toggle-CbDIB-7x.js +0 -55
- package/src/console/ui-next/dist/assets/units-page-_PuYqFty.js +0 -1
- package/src/console/ui-next/dist/assets/use-vault-list-Cs45Czkt.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-BOMr0go9.js +0 -2
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP wire frame stored on a wide event — query, headers, status.
|
|
3
|
+
*
|
|
4
|
+
* Credential headers are replaced before the row is written. The body stays
|
|
5
|
+
* on `input` / `output`; this frame is the rest of the message.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { RunHttpFrame } from "../runs/types.ts";
|
|
9
|
+
|
|
10
|
+
/** Replacement for credential header and query values. */
|
|
11
|
+
export const HTTP_FRAME_REDACTED = "[redacted]";
|
|
12
|
+
|
|
13
|
+
const EXACT_HEADERS = new Set([
|
|
14
|
+
"authorization",
|
|
15
|
+
"proxy-authorization",
|
|
16
|
+
"cookie",
|
|
17
|
+
"set-cookie",
|
|
18
|
+
"x-api-key",
|
|
19
|
+
"dpop",
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Build the Request a Console invoke would have sent for an HTTP flow.
|
|
24
|
+
*
|
|
25
|
+
* Call API runs `execute` directly, so there is no socket request. The frame
|
|
26
|
+
* still needs a method, path, query, and the headers the invoke actually
|
|
27
|
+
* uses (`accept`, `x-oke-client: console`, and `content-type` when there is a body).
|
|
28
|
+
* The operator's browser user-agent is not copied onto the flow.
|
|
29
|
+
*
|
|
30
|
+
* @param trigger - HTTP method and path template
|
|
31
|
+
* @param options - Path params and the assembled flow input
|
|
32
|
+
*/
|
|
33
|
+
export function requestForHttpInvoke(
|
|
34
|
+
trigger: { readonly method: string; readonly path: string },
|
|
35
|
+
options: {
|
|
36
|
+
readonly pathParams?: Readonly<Record<string, string>>;
|
|
37
|
+
readonly input?: unknown;
|
|
38
|
+
} = {},
|
|
39
|
+
): Request {
|
|
40
|
+
let path = trigger.path;
|
|
41
|
+
const params = options.pathParams ?? {};
|
|
42
|
+
for (const [key, value] of Object.entries(params)) {
|
|
43
|
+
path = path.replaceAll(`:${key}`, encodeURIComponent(value));
|
|
44
|
+
}
|
|
45
|
+
const url = new URL(path, "http://127.0.0.1");
|
|
46
|
+
const method = trigger.method.toUpperCase();
|
|
47
|
+
const headers = new Headers({ accept: "application/json", "x-oke-client": "console" });
|
|
48
|
+
if (!methodCarriesBody(method)) {
|
|
49
|
+
appendScalarQuery(url, options.input, new Set(Object.keys(params)));
|
|
50
|
+
return new Request(url.href, { method, headers });
|
|
51
|
+
}
|
|
52
|
+
headers.set("content-type", "application/json");
|
|
53
|
+
const body = options.input === undefined ? undefined : JSON.stringify(options.input);
|
|
54
|
+
return new Request(url.href, {
|
|
55
|
+
method,
|
|
56
|
+
headers,
|
|
57
|
+
...(body !== undefined ? { body } : {}),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Build the wire frame for an HTTP run.
|
|
63
|
+
*
|
|
64
|
+
* Does not read the body. Safe to call after the handler has consumed it,
|
|
65
|
+
* including streamed responses.
|
|
66
|
+
*
|
|
67
|
+
* @param request - Incoming request
|
|
68
|
+
* @param response - Final response after `onResponse`, when one exists
|
|
69
|
+
*/
|
|
70
|
+
export function captureHttpFrame(request: Request, response: Response | undefined): RunHttpFrame {
|
|
71
|
+
const url = new URL(request.url);
|
|
72
|
+
const query: Record<string, string> = {};
|
|
73
|
+
for (const [key, value] of url.searchParams) {
|
|
74
|
+
query[key] = sensitiveQuery(key) ? HTTP_FRAME_REDACTED : value;
|
|
75
|
+
}
|
|
76
|
+
return redactHttpFrame({
|
|
77
|
+
request: {
|
|
78
|
+
method: request.method,
|
|
79
|
+
path: url.pathname,
|
|
80
|
+
query,
|
|
81
|
+
headers: headerMap(request.headers),
|
|
82
|
+
},
|
|
83
|
+
...(response !== undefined
|
|
84
|
+
? { response: { status: response.status, headers: headerMap(response.headers) } }
|
|
85
|
+
: {}),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Mask credential headers and query values on a stored frame.
|
|
91
|
+
*
|
|
92
|
+
* Applied again at Console projection so a row that skipped capture still
|
|
93
|
+
* does not show secrets.
|
|
94
|
+
*
|
|
95
|
+
* @param frame - Stored HTTP frame
|
|
96
|
+
*/
|
|
97
|
+
export function redactHttpFrame(frame: RunHttpFrame): RunHttpFrame {
|
|
98
|
+
return {
|
|
99
|
+
request: {
|
|
100
|
+
method: frame.request.method,
|
|
101
|
+
path: frame.request.path,
|
|
102
|
+
query: redactQuery(frame.request.query),
|
|
103
|
+
headers: redactHeaders(frame.request.headers),
|
|
104
|
+
},
|
|
105
|
+
...(frame.response !== undefined
|
|
106
|
+
? {
|
|
107
|
+
response: {
|
|
108
|
+
status: frame.response.status,
|
|
109
|
+
headers: redactHeaders(frame.response.headers),
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
: {}),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* True when a header name carries a credential.
|
|
118
|
+
*
|
|
119
|
+
* @param name - Lower-case header name
|
|
120
|
+
*/
|
|
121
|
+
export function sensitiveHeader(name: string): boolean {
|
|
122
|
+
const key = name.toLowerCase();
|
|
123
|
+
if (EXACT_HEADERS.has(key)) return true;
|
|
124
|
+
if (key === "idempotency-key") return false;
|
|
125
|
+
return (
|
|
126
|
+
key.includes("token") ||
|
|
127
|
+
key.includes("secret") ||
|
|
128
|
+
key.includes("api-key") ||
|
|
129
|
+
key.endsWith("-key") ||
|
|
130
|
+
key.endsWith("_key")
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function methodCarriesBody(method: string): boolean {
|
|
135
|
+
switch (method) {
|
|
136
|
+
case "GET":
|
|
137
|
+
case "HEAD":
|
|
138
|
+
case "DELETE":
|
|
139
|
+
return false;
|
|
140
|
+
default:
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function appendScalarQuery(url: URL, input: unknown, skip: ReadonlySet<string>): void {
|
|
146
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) return;
|
|
147
|
+
for (const [key, value] of Object.entries(input as Record<string, unknown>)) {
|
|
148
|
+
if (skip.has(key)) continue;
|
|
149
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
150
|
+
url.searchParams.set(key, String(value));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function sensitiveQuery(name: string): boolean {
|
|
156
|
+
const key = name.toLowerCase();
|
|
157
|
+
return (
|
|
158
|
+
key === "token" ||
|
|
159
|
+
key.endsWith("_token") ||
|
|
160
|
+
key.endsWith("-token") ||
|
|
161
|
+
key === "api_key" ||
|
|
162
|
+
key === "apikey" ||
|
|
163
|
+
key === "api-key" ||
|
|
164
|
+
key === "secret" ||
|
|
165
|
+
key === "password" ||
|
|
166
|
+
key === "access_token" ||
|
|
167
|
+
key === "refresh_token"
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function headerMap(headers: Headers): Record<string, string> {
|
|
172
|
+
const out: Record<string, string> = {};
|
|
173
|
+
headers.forEach((value, key) => {
|
|
174
|
+
const name = key.toLowerCase();
|
|
175
|
+
if (sensitiveHeader(name)) {
|
|
176
|
+
out[name] = HTTP_FRAME_REDACTED;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const prev = out[name];
|
|
180
|
+
out[name] = prev === undefined ? value : `${prev}, ${value}`;
|
|
181
|
+
});
|
|
182
|
+
return sortRecord(out);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function redactHeaders(headers: Readonly<Record<string, string>>): Record<string, string> {
|
|
186
|
+
const out: Record<string, string> = {};
|
|
187
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
188
|
+
out[key.toLowerCase()] = sensitiveHeader(key) ? HTTP_FRAME_REDACTED : value;
|
|
189
|
+
}
|
|
190
|
+
return sortRecord(out);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function redactQuery(query: Readonly<Record<string, string>>): Record<string, string> {
|
|
194
|
+
const out: Record<string, string> = {};
|
|
195
|
+
for (const [key, value] of Object.entries(query)) {
|
|
196
|
+
out[key] = sensitiveQuery(key) ? HTTP_FRAME_REDACTED : value;
|
|
197
|
+
}
|
|
198
|
+
return sortRecord(out);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function sortRecord(record: Record<string, string>): Record<string, string> {
|
|
202
|
+
return Object.fromEntries(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)));
|
|
203
|
+
}
|
package/src/kernel/journal.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { mkdir } from "node:fs/promises";
|
|
11
11
|
import { dirname } from "node:path";
|
|
12
12
|
import { okid } from "../okid.ts";
|
|
13
|
+
import type { DecisionLabelStore } from "./decision-label-store.ts";
|
|
13
14
|
import type { IdempotencyStore } from "./idempotency-store.ts";
|
|
14
15
|
import { JournalSuspend } from "./journal-suspend.ts";
|
|
15
16
|
import { lazyRequire } from "./lazy-require.ts";
|
|
@@ -128,6 +129,24 @@ export interface JournalLeaseStore {
|
|
|
128
129
|
* @param leaseMs - Lease duration
|
|
129
130
|
*/
|
|
130
131
|
acquireLease(runId: string, instanceId: string, now: number, leaseMs: number): Promise<boolean>;
|
|
132
|
+
/**
|
|
133
|
+
* Take the lease and write `update(run)` in that same hold.
|
|
134
|
+
* `undefined` from `update` keeps the previous entries and still holds the lease.
|
|
135
|
+
* `"lease"` means another holder won. `"missing"` means no run.
|
|
136
|
+
*
|
|
137
|
+
* @param runId - Run id
|
|
138
|
+
* @param instanceId - Claimant instance
|
|
139
|
+
* @param now - Epoch-ms
|
|
140
|
+
* @param leaseMs - Lease duration
|
|
141
|
+
* @param update - Next snapshot, or undefined to leave entries
|
|
142
|
+
*/
|
|
143
|
+
cas(
|
|
144
|
+
runId: string,
|
|
145
|
+
instanceId: string,
|
|
146
|
+
now: number,
|
|
147
|
+
leaseMs: number,
|
|
148
|
+
update: (run: JournalRun) => JournalRun | undefined,
|
|
149
|
+
): Promise<"ok" | "missing" | { readonly lease: true; readonly leaseExpiresAt?: number }>;
|
|
131
150
|
/**
|
|
132
151
|
* Release a lease held by `instanceId` (no-op for other holders).
|
|
133
152
|
*
|
|
@@ -161,6 +180,20 @@ function loadIdempotencyStore(): typeof import("./idempotency-store.ts") {
|
|
|
161
180
|
return lazyRequire(import.meta.dir, ["idempotency", "store"].join("-"));
|
|
162
181
|
}
|
|
163
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Load the decision-label table without a static import.
|
|
185
|
+
*/
|
|
186
|
+
function loadDecisionLabelStore(): typeof import("./decision-label-store.ts") {
|
|
187
|
+
return lazyRequire(import.meta.dir, ["decision", "label", "store"].join("-"));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Load the agent-event table without a static import.
|
|
192
|
+
*/
|
|
193
|
+
function loadAgentEventStore(): typeof import("./agent-event-store.ts") {
|
|
194
|
+
return lazyRequire(import.meta.dir, ["agent", "event", "store"].join("-"));
|
|
195
|
+
}
|
|
196
|
+
|
|
164
197
|
/** Persistence backend for journal runs. */
|
|
165
198
|
export interface JournalStore extends Partial<JournalLeaseStore> {
|
|
166
199
|
/**
|
|
@@ -168,6 +201,16 @@ export interface JournalStore extends Partial<JournalLeaseStore> {
|
|
|
168
201
|
* only implements run `get` / `put`.
|
|
169
202
|
*/
|
|
170
203
|
readonly idempotency?: IdempotencyStore;
|
|
204
|
+
/**
|
|
205
|
+
* Decision labels and the drift flag on this same driver. Opened only when
|
|
206
|
+
* the app declares decisions.
|
|
207
|
+
*/
|
|
208
|
+
readonly decisions?: DecisionLabelStore;
|
|
209
|
+
/**
|
|
210
|
+
* Agent-run event log on this same driver. Followers resume from these rows
|
|
211
|
+
* after a restart and from another instance.
|
|
212
|
+
*/
|
|
213
|
+
readonly agentEvents?: import("./agent-event-store.ts").AgentEventStore;
|
|
171
214
|
/**
|
|
172
215
|
* Load a run by id.
|
|
173
216
|
*
|
|
@@ -234,6 +277,7 @@ function leaseMethods(
|
|
|
234
277
|
load: () => Promise<Map<string, JournalRun>>,
|
|
235
278
|
flush?: (map: Map<string, JournalRun>) => Promise<void>,
|
|
236
279
|
): JournalLeaseStore {
|
|
280
|
+
let gate: Promise<void> = Promise.resolve();
|
|
237
281
|
return {
|
|
238
282
|
async acquireLease(runId, instanceId, now, leaseMs) {
|
|
239
283
|
const map = await load();
|
|
@@ -244,6 +288,31 @@ function leaseMethods(
|
|
|
244
288
|
await flush?.(map);
|
|
245
289
|
return true;
|
|
246
290
|
},
|
|
291
|
+
async cas(runId, instanceId, now, leaseMs, update) {
|
|
292
|
+
let release!: () => void;
|
|
293
|
+
const slot = new Promise<void>((resolve) => {
|
|
294
|
+
release = resolve;
|
|
295
|
+
});
|
|
296
|
+
const prev = gate;
|
|
297
|
+
gate = slot;
|
|
298
|
+
await prev;
|
|
299
|
+
try {
|
|
300
|
+
const map = await load();
|
|
301
|
+
const run = map.get(runId);
|
|
302
|
+
if (!run) return "missing";
|
|
303
|
+
if (!claimable(run, instanceId, now)) {
|
|
304
|
+
return { lease: true, leaseExpiresAt: run.leaseExpiresAt };
|
|
305
|
+
}
|
|
306
|
+
const next = update(cloneRun(run)) ?? run;
|
|
307
|
+
next.lockedBy = instanceId;
|
|
308
|
+
next.leaseExpiresAt = now + leaseMs;
|
|
309
|
+
map.set(runId, next);
|
|
310
|
+
await flush?.(map);
|
|
311
|
+
return "ok";
|
|
312
|
+
} finally {
|
|
313
|
+
release();
|
|
314
|
+
}
|
|
315
|
+
},
|
|
247
316
|
async releaseLease(runId, instanceId) {
|
|
248
317
|
const map = await load();
|
|
249
318
|
const run = map.get(runId);
|
|
@@ -306,6 +375,16 @@ export function createMemoryJournalStore(seed?: readonly JournalRun[]): JournalS
|
|
|
306
375
|
Object.defineProperty(this, "idempotency", { value: created });
|
|
307
376
|
return created;
|
|
308
377
|
},
|
|
378
|
+
get decisions(): DecisionLabelStore {
|
|
379
|
+
const created = loadDecisionLabelStore().createMemoryDecisionLabelStore();
|
|
380
|
+
Object.defineProperty(this, "decisions", { value: created });
|
|
381
|
+
return created;
|
|
382
|
+
},
|
|
383
|
+
get agentEvents(): import("./agent-event-store.ts").AgentEventStore {
|
|
384
|
+
const created = loadAgentEventStore().createMemoryAgentEventStore();
|
|
385
|
+
Object.defineProperty(this, "agentEvents", { value: created });
|
|
386
|
+
return created;
|
|
387
|
+
},
|
|
309
388
|
};
|
|
310
389
|
}
|
|
311
390
|
|
|
@@ -359,6 +438,20 @@ export function createFileJournalStore(path: string): JournalStore {
|
|
|
359
438
|
Object.defineProperty(this, "idempotency", { value: created });
|
|
360
439
|
return created;
|
|
361
440
|
},
|
|
441
|
+
get decisions(): DecisionLabelStore {
|
|
442
|
+
const created = loadDecisionLabelStore().createFileDecisionLabelStore(
|
|
443
|
+
`${dirname(path)}/decision-labels.json`,
|
|
444
|
+
);
|
|
445
|
+
Object.defineProperty(this, "decisions", { value: created });
|
|
446
|
+
return created;
|
|
447
|
+
},
|
|
448
|
+
get agentEvents(): import("./agent-event-store.ts").AgentEventStore {
|
|
449
|
+
const created = loadAgentEventStore().createFileAgentEventStore(
|
|
450
|
+
`${dirname(path)}/agent-events`,
|
|
451
|
+
);
|
|
452
|
+
Object.defineProperty(this, "agentEvents", { value: created });
|
|
453
|
+
return created;
|
|
454
|
+
},
|
|
362
455
|
};
|
|
363
456
|
}
|
|
364
457
|
|
|
@@ -432,6 +525,11 @@ export interface JournalSession {
|
|
|
432
525
|
* instead of treating the cursor as past them. Clears the in-memory undo stack
|
|
433
526
|
* so frames re-bind on the next walk.
|
|
434
527
|
*/
|
|
528
|
+
/**
|
|
529
|
+
* Increments on {@link JournalSession.rewind}. Agent run ids key off this
|
|
530
|
+
* so `flow.retry` replays the same id.
|
|
531
|
+
*/
|
|
532
|
+
readonly epoch: number;
|
|
435
533
|
rewind(): void;
|
|
436
534
|
/** Registered per-step undos in persist/replay order (LIFO compensate). */
|
|
437
535
|
undoStack(): readonly JournalUndoFrame[];
|
|
@@ -471,6 +569,7 @@ export function createJournalSlot(): {
|
|
|
471
569
|
const session = slot.session;
|
|
472
570
|
if (!session) {
|
|
473
571
|
if (prop === "runId") return "";
|
|
572
|
+
if (prop === "epoch") return 0;
|
|
474
573
|
if (prop === "stampTenant") return async () => undefined;
|
|
475
574
|
if (prop === "rewind") return () => undefined;
|
|
476
575
|
return undefined;
|
|
@@ -516,6 +615,7 @@ export function createJournal(options: CreateJournalOptions): Journal {
|
|
|
516
615
|
function openSession(run: JournalRun, leased: boolean): JournalSession {
|
|
517
616
|
/** Next entry index to consume on replay. */
|
|
518
617
|
let cursor = 0;
|
|
618
|
+
let epoch = 0;
|
|
519
619
|
let leaseHeld = leased;
|
|
520
620
|
let registrationPass = false;
|
|
521
621
|
let undoExecution = false;
|
|
@@ -665,9 +765,13 @@ export function createJournal(options: CreateJournalOptions): Journal {
|
|
|
665
765
|
await persist();
|
|
666
766
|
return value;
|
|
667
767
|
},
|
|
768
|
+
get epoch() {
|
|
769
|
+
return epoch;
|
|
770
|
+
},
|
|
668
771
|
rewind() {
|
|
669
772
|
cursor = 0;
|
|
670
773
|
undos.length = 0;
|
|
774
|
+
epoch += 1;
|
|
671
775
|
},
|
|
672
776
|
undoStack() {
|
|
673
777
|
return undos;
|
|
@@ -28,6 +28,16 @@ export interface JsonStreamResult {
|
|
|
28
28
|
ready?: () => Promise<void>;
|
|
29
29
|
/** Set by the kernel to commit journal / Runs after the stream settles. */
|
|
30
30
|
finalize?: () => Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Agent result once a streamed `fx.run` settles.
|
|
33
|
+
* Absent for text and live streams.
|
|
34
|
+
*/
|
|
35
|
+
result?: Promise<unknown>;
|
|
36
|
+
/**
|
|
37
|
+
* Set when iteration parked a durable run, after `[DONE]` is queued.
|
|
38
|
+
* Finalize commits the journal as sleeping from this.
|
|
39
|
+
*/
|
|
40
|
+
parked?: { wakeAt: number; label: string };
|
|
31
41
|
}
|
|
32
42
|
|
|
33
43
|
/**
|
|
@@ -14,6 +14,7 @@ import { createClockRuntime } from "../elements/clock.ts";
|
|
|
14
14
|
import { gate } from "../elements/gate.ts";
|
|
15
15
|
import { createRunsRuntime, memoryRunsDriver } from "../runs/index.ts";
|
|
16
16
|
import { oke } from "./app.ts";
|
|
17
|
+
import { HTTP_FRAME_REDACTED } from "./http-frame.ts";
|
|
17
18
|
import { flow, resetFlowSeq } from "./flow.ts";
|
|
18
19
|
import { on, resetBindings } from "./on.ts";
|
|
19
20
|
import { http } from "./triggers.ts";
|
|
@@ -196,6 +197,50 @@ describe("pipeline — Unauthorized for anonymous", () => {
|
|
|
196
197
|
});
|
|
197
198
|
});
|
|
198
199
|
|
|
200
|
+
describe("pipeline — HTTP wire frame", () => {
|
|
201
|
+
test("fetch stores query, headers, and status beside the body", async () => {
|
|
202
|
+
resetBindings();
|
|
203
|
+
resetFlowSeq();
|
|
204
|
+
|
|
205
|
+
on(
|
|
206
|
+
http.get("/tasks", { out: z.object({ ok: z.literal(true) }) }).public(),
|
|
207
|
+
flow("tasks.list", {
|
|
208
|
+
do: () => ({ ok: true as const }),
|
|
209
|
+
}),
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const runs = createRunsRuntime({ driver: memoryRunsDriver });
|
|
213
|
+
await runs.open();
|
|
214
|
+
const app = oke({
|
|
215
|
+
name: "http-frame",
|
|
216
|
+
gate: { policies: [gate.public] },
|
|
217
|
+
runs,
|
|
218
|
+
env: "test",
|
|
219
|
+
});
|
|
220
|
+
await app.boot({ env: "test", gates: [gate.public], runs });
|
|
221
|
+
|
|
222
|
+
const res = await app.fetch(
|
|
223
|
+
new Request("http://localhost/tasks?limit=20", {
|
|
224
|
+
headers: { accept: "application/json", cookie: "sid=secret" },
|
|
225
|
+
}),
|
|
226
|
+
);
|
|
227
|
+
expect(res.status).toBe(200);
|
|
228
|
+
|
|
229
|
+
const event = (await runs.all()).find((row) => row.flow === "tasks.list");
|
|
230
|
+
expect(event?.input).toBeUndefined();
|
|
231
|
+
expect(event?.http).toMatchObject({
|
|
232
|
+
request: {
|
|
233
|
+
method: "GET",
|
|
234
|
+
path: "/tasks",
|
|
235
|
+
query: { limit: "20" },
|
|
236
|
+
headers: { cookie: HTTP_FRAME_REDACTED, accept: "application/json" },
|
|
237
|
+
},
|
|
238
|
+
response: { status: 200 },
|
|
239
|
+
});
|
|
240
|
+
await app.bootResult?.close();
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
199
244
|
describe("pipeline — evaluated gates on the run", () => {
|
|
200
245
|
test("recorded run contains evaluated chain, not just declared names", async () => {
|
|
201
246
|
resetBindings();
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE id carried on a live agent event without changing its JSON payload.
|
|
3
|
+
*
|
|
4
|
+
* The follow log stores a seq. The live `fx.run` frame for that row uses the
|
|
5
|
+
* same seq as `id:` so a client can resume with `Last-Event-ID`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const sseIdBrand: unique symbol = Symbol.for("oke.sse.id");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Copy `value` and attach an SSE id the encoder writes as `id:`.
|
|
12
|
+
* The id is not enumerable, so it is not part of the JSON payload.
|
|
13
|
+
*
|
|
14
|
+
* @param value - Event object
|
|
15
|
+
* @param id - SSE id (the stored log seq)
|
|
16
|
+
*/
|
|
17
|
+
export function withSseId<T extends object>(value: T, id: string): T {
|
|
18
|
+
const copy: T = { ...value };
|
|
19
|
+
Object.defineProperty(copy, sseIdBrand, { value: id, enumerable: false });
|
|
20
|
+
return copy;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* SSE id attached by {@link withSseId}, if any.
|
|
25
|
+
*
|
|
26
|
+
* @param value - Stream chunk
|
|
27
|
+
*/
|
|
28
|
+
export function readSseId(value: unknown): string | undefined {
|
|
29
|
+
if (typeof value !== "object" || value === null) return undefined;
|
|
30
|
+
const id = (value as { readonly [sseIdBrand]?: unknown })[sseIdBrand];
|
|
31
|
+
return typeof id === "string" ? id : undefined;
|
|
32
|
+
}
|
package/src/manifest/diff.ts
CHANGED
package/src/manifest/types.ts
CHANGED
|
@@ -58,6 +58,12 @@ export type AuthApiKeysResourceRef = "auth:api-keys";
|
|
|
58
58
|
*/
|
|
59
59
|
export type AuthTenantsResourceRef = "auth:tenants";
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Agent-run journal capability for approval routes.
|
|
63
|
+
* Not a store facet — the journal driver owns the rows.
|
|
64
|
+
*/
|
|
65
|
+
export type JournalRunsResourceRef = "journal:runs";
|
|
66
|
+
|
|
61
67
|
/**
|
|
62
68
|
* Dead-letter / live-stream read capability — not a store facet.
|
|
63
69
|
* Declare on `effects.reads` (never `writes`).
|
|
@@ -101,8 +107,10 @@ export interface Effects {
|
|
|
101
107
|
| AuthApiKeysResourceRef
|
|
102
108
|
| AuthTenantsResourceRef
|
|
103
109
|
>;
|
|
104
|
-
/** Store writes, plus `"auth:api-keys"` / `"auth:tenants"`
|
|
105
|
-
writes?: Array<
|
|
110
|
+
/** Store writes, plus `"auth:api-keys"` / `"auth:tenants"` and `"journal:runs"`. */
|
|
111
|
+
writes?: Array<
|
|
112
|
+
ResourceRef | AuthApiKeysResourceRef | AuthTenantsResourceRef | JournalRunsResourceRef
|
|
113
|
+
>;
|
|
106
114
|
/** Emitted signals. */
|
|
107
115
|
emits?: SignalRef[];
|
|
108
116
|
/** Channel template sends (irreversible). */
|
|
@@ -123,6 +131,8 @@ export interface Effects {
|
|
|
123
131
|
* Hostname only (e.g. `"api.stripe.com"`), never a full URL.
|
|
124
132
|
*/
|
|
125
133
|
fetches?: FetchHostRef[];
|
|
134
|
+
/** Decision refs from `fx.decide` (irreversible, nondeterministic). */
|
|
135
|
+
decides?: string[];
|
|
126
136
|
}
|
|
127
137
|
|
|
128
138
|
/** Embedding model ref for `effects.embeds` / `fx.embed` (model name). */
|
|
@@ -475,6 +485,8 @@ export interface AiPrompt {
|
|
|
475
485
|
version?: number;
|
|
476
486
|
evals?: string;
|
|
477
487
|
budget?: AiBudget;
|
|
488
|
+
/** `1` sends one schema-mismatch follow-up. Default `0`. */
|
|
489
|
+
repair?: 0 | 1;
|
|
478
490
|
/** Ordered recovery chain of logical model names. */
|
|
479
491
|
via?: string[];
|
|
480
492
|
/** Per-command deadline (`"30s"` or milliseconds). */
|
|
@@ -487,7 +499,10 @@ export interface AiPrompt {
|
|
|
487
499
|
/** Bounded agent whose tools are flows. */
|
|
488
500
|
export interface AiAgent {
|
|
489
501
|
tools?: FlowRef[];
|
|
502
|
+
/** Tools that park for human approval. The predicate stays in author code. */
|
|
503
|
+
approvals?: Record<string, true>;
|
|
490
504
|
maxSteps?: number;
|
|
505
|
+
maxDepth?: number;
|
|
491
506
|
model?: string;
|
|
492
507
|
budget?: AiBudget;
|
|
493
508
|
}
|
|
@@ -514,9 +529,21 @@ export interface Ai {
|
|
|
514
529
|
models?: Record<string, AiModel>;
|
|
515
530
|
prompts?: Record<string, AiPrompt>;
|
|
516
531
|
agents?: Record<string, AiAgent>;
|
|
532
|
+
decisions?: Record<string, AiDecision>;
|
|
517
533
|
mcpServers?: Record<string, AiMcpServer>;
|
|
518
534
|
}
|
|
519
535
|
|
|
536
|
+
/** A declared decision. The predicate and locale function stay in author code. */
|
|
537
|
+
export interface AiDecision {
|
|
538
|
+
mode: "review" | "abstain";
|
|
539
|
+
review?: string;
|
|
540
|
+
model?: string;
|
|
541
|
+
driverId?: "openrouter" | "typesafe";
|
|
542
|
+
questions: string[];
|
|
543
|
+
evals?: string;
|
|
544
|
+
autonomy?: { maxError: number; audit: number; risk?: number };
|
|
545
|
+
}
|
|
546
|
+
|
|
520
547
|
/** Per-table metadata on a Manifest {@link Plugin}. */
|
|
521
548
|
export interface PluginTable {
|
|
522
549
|
plane?: string;
|