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,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator routes for the decisions list and the review queue.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { journalLeaseBusyResponse } from "../../elements/ai/approval.ts";
|
|
7
|
+
import { resolveDecisionReview } from "../../kernel/fx-decide.ts";
|
|
8
|
+
import { fail, flow, http, type Binding } from "../../kernel/index.ts";
|
|
9
|
+
import { bindHttp } from "./bind.ts";
|
|
10
|
+
import type { ConsoleState } from "./state.ts";
|
|
11
|
+
import { loadDecisionQueue, projectDecisionList, candidateMetrics } from "./decisions.ts";
|
|
12
|
+
|
|
13
|
+
const AuthFailed = z.object({});
|
|
14
|
+
|
|
15
|
+
const DecisionListOut = z.object({
|
|
16
|
+
decisions: z.array(
|
|
17
|
+
z.object({
|
|
18
|
+
name: z.string(),
|
|
19
|
+
state: z.enum(["learning", "candidate", "certified", "suspended"]),
|
|
20
|
+
mode: z.enum(["review", "abstain"]),
|
|
21
|
+
model: z.string().optional(),
|
|
22
|
+
metrics: z.record(z.string(), z.number()).optional(),
|
|
23
|
+
promote: z.string().optional(),
|
|
24
|
+
}),
|
|
25
|
+
),
|
|
26
|
+
suspended: z.array(z.string()),
|
|
27
|
+
failures: z.array(
|
|
28
|
+
z.object({
|
|
29
|
+
decision: z.string(),
|
|
30
|
+
question: z.string(),
|
|
31
|
+
message: z.string(),
|
|
32
|
+
at: z.number(),
|
|
33
|
+
}),
|
|
34
|
+
),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const DecisionQueueOut = z.object({
|
|
38
|
+
rows: z.array(
|
|
39
|
+
z.object({
|
|
40
|
+
id: z.string(),
|
|
41
|
+
decision: z.string(),
|
|
42
|
+
requestedAt: z.number(),
|
|
43
|
+
ageMs: z.number(),
|
|
44
|
+
labelOnly: z.boolean(),
|
|
45
|
+
status: z.enum(["pending", "reviewed"]),
|
|
46
|
+
questions: z.array(
|
|
47
|
+
z.object({
|
|
48
|
+
id: z.string(),
|
|
49
|
+
kind: z.enum(["boolean", "choice", "score"]),
|
|
50
|
+
options: z.array(z.string()).optional(),
|
|
51
|
+
levels: z.array(z.string()).optional(),
|
|
52
|
+
}),
|
|
53
|
+
),
|
|
54
|
+
}),
|
|
55
|
+
),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const DecisionResolveIn = z.object({
|
|
59
|
+
id: z.string().min(1),
|
|
60
|
+
values: z.record(z.string(), z.unknown()),
|
|
61
|
+
labelOnly: z.boolean().optional(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const DecisionResolveOut = z.object({ ok: z.literal(true) });
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* List, queue, and resolve bindings.
|
|
68
|
+
*
|
|
69
|
+
* @param state - Console state
|
|
70
|
+
*/
|
|
71
|
+
export function decisionConsoleBindings(state: ConsoleState): Binding[] {
|
|
72
|
+
const list = flow("console.decisions.list", {
|
|
73
|
+
plane: "operator",
|
|
74
|
+
do: async (_input, fx) => {
|
|
75
|
+
if (!fx.operator.id) return fail("AuthFailed", {});
|
|
76
|
+
const { decisionDriftNames, getDecisionLock } =
|
|
77
|
+
await import("../../elements/ai/decisions/certificate.ts");
|
|
78
|
+
const { decisionLabelWriteFailures, listDecisionCandidates, loadDecisionCandidate } =
|
|
79
|
+
await import("../../elements/ai/decisions/labels.ts");
|
|
80
|
+
const names = await listDecisionCandidates();
|
|
81
|
+
const fitted: Record<string, Record<string, number>> = {};
|
|
82
|
+
for (const name of names) {
|
|
83
|
+
const metrics = candidateMetrics(await loadDecisionCandidate(name));
|
|
84
|
+
if (metrics) fitted[name] = metrics;
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
decisions: projectDecisionList(state.manifest, getDecisionLock(), new Set(names), fitted),
|
|
88
|
+
suspended: decisionDriftNames(),
|
|
89
|
+
failures: decisionLabelWriteFailures(),
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
const queue = flow("console.decisions.queue", {
|
|
94
|
+
plane: "operator",
|
|
95
|
+
do: async (_input, fx) => {
|
|
96
|
+
if (!fx.operator.id) return fail("AuthFailed", {});
|
|
97
|
+
return { rows: await loadDecisionQueue(state.journalStore, Date.now()) };
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
const resolve = flow("console.decisions.resolve", {
|
|
101
|
+
plane: "operator",
|
|
102
|
+
do: async (input: { id: string; values: Record<string, unknown>; labelOnly?: boolean }, fx) => {
|
|
103
|
+
const reviewer = fx.operator.id;
|
|
104
|
+
if (!reviewer) return fail("Unauthorized", {});
|
|
105
|
+
const store = state.journalStore;
|
|
106
|
+
if (!store) return fx.fail.notFound();
|
|
107
|
+
const result = await resolveDecisionReview(
|
|
108
|
+
store,
|
|
109
|
+
input.id,
|
|
110
|
+
{
|
|
111
|
+
values: input.values,
|
|
112
|
+
reviewer,
|
|
113
|
+
tenantId: fx.tenant.id,
|
|
114
|
+
plane: "operator",
|
|
115
|
+
auth: {
|
|
116
|
+
userId: fx.auth.userId,
|
|
117
|
+
scopes: fx.auth.scopes,
|
|
118
|
+
verified: fx.auth.verified,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
Date.now,
|
|
122
|
+
input.labelOnly === true,
|
|
123
|
+
);
|
|
124
|
+
if (result.ok) return { ok: true as const };
|
|
125
|
+
if (result.status === 409 && result.reason === "lease") {
|
|
126
|
+
return journalLeaseBusyResponse(result.retryAfterSeconds);
|
|
127
|
+
}
|
|
128
|
+
if (result.status === 422) {
|
|
129
|
+
return fail("ValidationError", {
|
|
130
|
+
issues: [{ message: "review values do not match the open questions", path: ["values"] }],
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
if (result.status === 503) return fx.fail.serviceUnavailable();
|
|
134
|
+
if (result.status === 403) return fx.fail.forbidden();
|
|
135
|
+
if (result.status === 409) return fx.fail.conflict();
|
|
136
|
+
return fx.fail.notFound();
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
return [
|
|
140
|
+
bindHttp(
|
|
141
|
+
http.get("/console/decisions", { out: DecisionListOut, errors: { AuthFailed } }),
|
|
142
|
+
list,
|
|
143
|
+
),
|
|
144
|
+
bindHttp(
|
|
145
|
+
http.get("/console/decisions/queue", { out: DecisionQueueOut, errors: { AuthFailed } }),
|
|
146
|
+
queue,
|
|
147
|
+
),
|
|
148
|
+
bindHttp(
|
|
149
|
+
http.post("/console/decisions/resolve", {
|
|
150
|
+
in: DecisionResolveIn,
|
|
151
|
+
out: DecisionResolveOut,
|
|
152
|
+
errors: { AuthFailed },
|
|
153
|
+
}),
|
|
154
|
+
resolve,
|
|
155
|
+
),
|
|
156
|
+
];
|
|
157
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decision list state and pending age.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import type { JournalRun } from "../../kernel/journal.ts";
|
|
7
|
+
import { projectDecisionList, projectDecisionQueue } from "./decisions.ts";
|
|
8
|
+
import {
|
|
9
|
+
setDecisionDrift,
|
|
10
|
+
setDecisionLock,
|
|
11
|
+
resetDecisionCertificates,
|
|
12
|
+
} from "../../elements/ai/decisions/certificate.ts";
|
|
13
|
+
import type { Manifest } from "../../manifest/types.ts";
|
|
14
|
+
|
|
15
|
+
const manifest = {
|
|
16
|
+
ai: {
|
|
17
|
+
decisions: {
|
|
18
|
+
triage: { mode: "review" as const, questions: ["team"] },
|
|
19
|
+
route: { mode: "review" as const, questions: ["team"] },
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
} as unknown as Manifest;
|
|
23
|
+
|
|
24
|
+
describe("decision console projection", () => {
|
|
25
|
+
test("lockfile and drift set the list state", () => {
|
|
26
|
+
resetDecisionCertificates();
|
|
27
|
+
expect(projectDecisionList(manifest)[0]?.state).toBe("learning");
|
|
28
|
+
setDecisionLock({
|
|
29
|
+
decisions: {
|
|
30
|
+
triage: { model: "typesafe/jev-1.13.0", questions: {} },
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
expect(projectDecisionList(manifest).find((row) => row.name === "triage")?.state).toBe(
|
|
34
|
+
"certified",
|
|
35
|
+
);
|
|
36
|
+
setDecisionDrift("triage", true);
|
|
37
|
+
const rows = projectDecisionList(manifest, undefined, new Set(["route"]), {
|
|
38
|
+
route: { labels: 12, errors: 0 },
|
|
39
|
+
});
|
|
40
|
+
expect(rows.find((row) => row.name === "triage")?.state).toBe("suspended");
|
|
41
|
+
const route = rows.find((row) => row.name === "route");
|
|
42
|
+
expect(route?.state).toBe("candidate");
|
|
43
|
+
expect(route?.metrics).toEqual({ labels: 12, errors: 0 });
|
|
44
|
+
expect(route?.promote).toBe("oke decide promote route");
|
|
45
|
+
resetDecisionCertificates();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("queue age is now minus requestedAt", () => {
|
|
49
|
+
const run = {
|
|
50
|
+
id: "run",
|
|
51
|
+
flow: "run",
|
|
52
|
+
status: "sleeping",
|
|
53
|
+
entries: [
|
|
54
|
+
{
|
|
55
|
+
kind: "step" as const,
|
|
56
|
+
name: "ai-decision:abc",
|
|
57
|
+
at: 1_000,
|
|
58
|
+
value: { status: "pending", requestedAt: 1_000 },
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
} as JournalRun;
|
|
62
|
+
const [row] = projectDecisionQueue([run], 61_000);
|
|
63
|
+
expect(row?.ageMs).toBe(60_000);
|
|
64
|
+
expect(row?.labelOnly).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console projection for decisions: lockfile state and the review queue.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { JournalRun, JournalStore } from "../../kernel/journal.ts";
|
|
6
|
+
import type { Manifest } from "../../manifest/types.ts";
|
|
7
|
+
import { aiDecisionRegistry } from "../../kernel/element-registries.ts";
|
|
8
|
+
import {
|
|
9
|
+
decisionDriftNames,
|
|
10
|
+
decisionDriftSuspended,
|
|
11
|
+
getDecisionLock,
|
|
12
|
+
type DecisionCandidate,
|
|
13
|
+
type DecisionLockfile,
|
|
14
|
+
} from "../../elements/ai/decisions/certificate.ts";
|
|
15
|
+
|
|
16
|
+
/** List state for one decision. */
|
|
17
|
+
export type DecisionListState = "learning" | "candidate" | "certified" | "suspended";
|
|
18
|
+
|
|
19
|
+
/** One decision row. */
|
|
20
|
+
export interface DecisionListRow {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly state: DecisionListState;
|
|
23
|
+
readonly mode: "review" | "abstain";
|
|
24
|
+
readonly model?: string;
|
|
25
|
+
/** Candidate fit, when one is stored and the decision is not certified. */
|
|
26
|
+
readonly metrics?: Readonly<Record<string, number>>;
|
|
27
|
+
/** Command that writes this decision's candidate into the lockfile. */
|
|
28
|
+
readonly promote?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** One question the resolve form can answer. */
|
|
32
|
+
export interface DecisionFormQuestion {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly kind: "boolean" | "choice" | "score";
|
|
35
|
+
readonly options?: readonly string[];
|
|
36
|
+
readonly levels?: readonly string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** One parked or audit review. */
|
|
40
|
+
export interface DecisionQueueRow {
|
|
41
|
+
readonly id: string;
|
|
42
|
+
readonly decision: string;
|
|
43
|
+
readonly requestedAt: number;
|
|
44
|
+
readonly ageMs: number;
|
|
45
|
+
readonly labelOnly: boolean;
|
|
46
|
+
readonly status: "pending" | "reviewed";
|
|
47
|
+
readonly questions: readonly DecisionFormQuestion[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* List state from the lockfile, the candidate, and the app drift flag.
|
|
52
|
+
*
|
|
53
|
+
* @param manifest - App manifest
|
|
54
|
+
* @param lock - Lockfile, when one is loaded
|
|
55
|
+
*/
|
|
56
|
+
export function projectDecisionList(
|
|
57
|
+
manifest: Manifest | null | undefined,
|
|
58
|
+
lock: DecisionLockfile | undefined = getDecisionLock(),
|
|
59
|
+
candidates: ReadonlySet<string> = new Set(),
|
|
60
|
+
metrics: Readonly<Record<string, Readonly<Record<string, number>>>> = {},
|
|
61
|
+
): DecisionListRow[] {
|
|
62
|
+
const decisions = manifest?.ai?.decisions ?? {};
|
|
63
|
+
return Object.entries(decisions).map(([name, decision]) => {
|
|
64
|
+
const certified = lock?.decisions[name] !== undefined;
|
|
65
|
+
const candidate = candidates.has(name);
|
|
66
|
+
const suspended = decisionDriftSuspended(name);
|
|
67
|
+
const state: DecisionListState = suspended
|
|
68
|
+
? "suspended"
|
|
69
|
+
: certified
|
|
70
|
+
? "certified"
|
|
71
|
+
: candidate
|
|
72
|
+
? "candidate"
|
|
73
|
+
: "learning";
|
|
74
|
+
const fitted = metrics[name];
|
|
75
|
+
return {
|
|
76
|
+
name,
|
|
77
|
+
state,
|
|
78
|
+
mode: decision.mode,
|
|
79
|
+
...(decision.model !== undefined ? { model: decision.model } : {}),
|
|
80
|
+
...(state === "candidate" && fitted ? { metrics: fitted } : {}),
|
|
81
|
+
...(state === "candidate" ? { promote: `oke decide promote ${name}` } : {}),
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* First slice metrics on a stored candidate.
|
|
88
|
+
*
|
|
89
|
+
* @param entry - Candidate body
|
|
90
|
+
*/
|
|
91
|
+
export function candidateMetrics(
|
|
92
|
+
entry: DecisionCandidate | undefined,
|
|
93
|
+
): Record<string, number> | undefined {
|
|
94
|
+
if (!entry) return undefined;
|
|
95
|
+
for (const slices of Object.values(entry.questions)) {
|
|
96
|
+
for (const slice of Object.values(slices)) {
|
|
97
|
+
if (slice.metrics) return { ...slice.metrics };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Pending and reviewed decision steps, newest age first.
|
|
105
|
+
*
|
|
106
|
+
* @param runs - Journal runs
|
|
107
|
+
* @param now - Clock
|
|
108
|
+
*/
|
|
109
|
+
export function projectDecisionQueue(
|
|
110
|
+
runs: readonly JournalRun[],
|
|
111
|
+
now: number,
|
|
112
|
+
tenantId?: string | null,
|
|
113
|
+
): DecisionQueueRow[] {
|
|
114
|
+
const rows: DecisionQueueRow[] = [];
|
|
115
|
+
for (const run of runs) {
|
|
116
|
+
for (const entry of run.entries) {
|
|
117
|
+
if (entry.kind !== "step") continue;
|
|
118
|
+
const labelOnly = entry.name.startsWith("ai-decision-label:");
|
|
119
|
+
const parked = entry.name.startsWith("ai-decision:");
|
|
120
|
+
if (!labelOnly && !parked) continue;
|
|
121
|
+
const id = entry.name.slice(labelOnly ? "ai-decision-label:".length : "ai-decision:".length);
|
|
122
|
+
const value = entry.value as {
|
|
123
|
+
status?: "pending" | "reviewed";
|
|
124
|
+
requestedAt?: number;
|
|
125
|
+
tenant?: string | null;
|
|
126
|
+
open?: readonly string[];
|
|
127
|
+
};
|
|
128
|
+
if (tenantId !== undefined && (value.tenant ?? null) !== tenantId) continue;
|
|
129
|
+
const requestedAt = value.requestedAt ?? entry.at;
|
|
130
|
+
rows.push({
|
|
131
|
+
id,
|
|
132
|
+
decision: decisionNameFromId(id),
|
|
133
|
+
requestedAt,
|
|
134
|
+
ageMs: Math.max(0, now - requestedAt),
|
|
135
|
+
labelOnly,
|
|
136
|
+
status: value.status ?? "pending",
|
|
137
|
+
questions: formQuestions(decisionNameFromId(id), value.open),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return rows.sort((a, b) => b.ageMs - a.ageMs);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** One decision on the read-only MCP list. */
|
|
145
|
+
export interface McpDecisionRow {
|
|
146
|
+
readonly name: string;
|
|
147
|
+
readonly state: DecisionListState;
|
|
148
|
+
readonly pending: number;
|
|
149
|
+
readonly metrics?: Readonly<Record<string, number>>;
|
|
150
|
+
readonly drift: boolean;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Decision rows for `oke.decisions.list`: lockfile state, pending reviews,
|
|
155
|
+
* certificate or candidate metrics, and drift.
|
|
156
|
+
*
|
|
157
|
+
* @param manifest - App manifest
|
|
158
|
+
* @param pendingByName - Pending review counts
|
|
159
|
+
* @param metrics - Candidate or certificate metrics
|
|
160
|
+
*/
|
|
161
|
+
export function projectMcpDecisions(
|
|
162
|
+
manifest: Manifest | null | undefined,
|
|
163
|
+
pendingByName: Readonly<Record<string, number>> = {},
|
|
164
|
+
metrics: Readonly<Record<string, Readonly<Record<string, number>>>> = {},
|
|
165
|
+
): McpDecisionRow[] {
|
|
166
|
+
const drifted = new Set(decisionDriftNames());
|
|
167
|
+
return projectDecisionList(
|
|
168
|
+
manifest,
|
|
169
|
+
getDecisionLock(),
|
|
170
|
+
new Set(Object.keys(metrics)),
|
|
171
|
+
metrics,
|
|
172
|
+
).map((row) => ({
|
|
173
|
+
name: row.name,
|
|
174
|
+
state: row.state,
|
|
175
|
+
pending: pendingByName[row.name] ?? 0,
|
|
176
|
+
...((row.metrics ?? metrics[row.name]) ? { metrics: row.metrics ?? metrics[row.name] } : {}),
|
|
177
|
+
drift: drifted.has(row.name) || row.state === "suspended",
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Queue from a journal store.
|
|
183
|
+
*
|
|
184
|
+
* @param store - Durable journal, when the console has one
|
|
185
|
+
* @param now - Clock
|
|
186
|
+
*/
|
|
187
|
+
export async function loadDecisionQueue(
|
|
188
|
+
store: JournalStore | null | undefined,
|
|
189
|
+
now: number,
|
|
190
|
+
tenantId?: string | null,
|
|
191
|
+
): Promise<DecisionQueueRow[]> {
|
|
192
|
+
if (!store) return [];
|
|
193
|
+
return projectDecisionQueue(await store.list(), now, tenantId);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function decisionNameFromId(id: string): string {
|
|
197
|
+
try {
|
|
198
|
+
const decoded = Buffer.from(id, "base64url").toString("utf8");
|
|
199
|
+
const first = decoded.indexOf(".");
|
|
200
|
+
const second = first >= 0 ? decoded.indexOf(".", first + 1) : -1;
|
|
201
|
+
if (second > first) return decoded.slice(second + 1);
|
|
202
|
+
return first > 0 ? decoded.slice(first + 1) : id;
|
|
203
|
+
} catch {
|
|
204
|
+
return id;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function formQuestions(name: string, open: readonly string[] | undefined): DecisionFormQuestion[] {
|
|
209
|
+
const decl = aiDecisionRegistry.find((item) => item.name === name);
|
|
210
|
+
if (!decl) return [];
|
|
211
|
+
const ids = open && open.length > 0 ? open : Object.keys(decl.ask);
|
|
212
|
+
const questions: DecisionFormQuestion[] = [];
|
|
213
|
+
for (const id of ids) {
|
|
214
|
+
const question = decl.ask[id];
|
|
215
|
+
if (!question) continue;
|
|
216
|
+
if (question.kind === "choice") {
|
|
217
|
+
questions.push({
|
|
218
|
+
id,
|
|
219
|
+
kind: "choice",
|
|
220
|
+
options: [...Object.keys(question.options), "none_of_these"],
|
|
221
|
+
});
|
|
222
|
+
} else if (question.kind === "score") {
|
|
223
|
+
questions.push({ id, kind: "score", levels: [...question.levels] });
|
|
224
|
+
} else {
|
|
225
|
+
questions.push({ id, kind: "boolean" });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return questions;
|
|
229
|
+
}
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
} from "../../auth/index.ts";
|
|
17
17
|
import { DryRunWriteIsolationError } from "../../kernel/dry-run.ts";
|
|
18
18
|
import { fail, flow, http, type AnyFlowDef, type Binding } from "../../kernel/index.ts";
|
|
19
|
+
import { redactHttpFrame } from "../../kernel/http-frame.ts";
|
|
20
|
+
import { lazyRequire } from "../../kernel/lazy-require.ts";
|
|
19
21
|
import type { Flow as ManifestFlow, ResourceRef } from "../../manifest/types.ts";
|
|
20
22
|
import { eventHasIrreversible, runReplay } from "../../cli/replay.ts";
|
|
21
23
|
import { createRunsRuntime } from "../../runs/runtime.ts";
|
|
@@ -44,6 +46,7 @@ import {
|
|
|
44
46
|
STORE_SQL_LOCKS_POLL_MS,
|
|
45
47
|
STORE_SQL_STATS_PII_GAP,
|
|
46
48
|
StoreSqlStatsError,
|
|
49
|
+
engineErrorText,
|
|
47
50
|
} from "./store-stats.ts";
|
|
48
51
|
import {
|
|
49
52
|
STORE_KV_STATS_SERVER_WIDE_GAP,
|
|
@@ -92,7 +95,18 @@ const ManifestOut = z.object({
|
|
|
92
95
|
});
|
|
93
96
|
|
|
94
97
|
const EffectEntryOut = z.object({
|
|
95
|
-
kind: z.enum([
|
|
98
|
+
kind: z.enum([
|
|
99
|
+
"read",
|
|
100
|
+
"write",
|
|
101
|
+
"emit",
|
|
102
|
+
"send",
|
|
103
|
+
"ask",
|
|
104
|
+
"embed",
|
|
105
|
+
"secret",
|
|
106
|
+
"call",
|
|
107
|
+
"fetch",
|
|
108
|
+
"decide",
|
|
109
|
+
]),
|
|
96
110
|
resource: z.string(),
|
|
97
111
|
timestamp: z.number(),
|
|
98
112
|
duration: z.number(),
|
|
@@ -104,6 +118,7 @@ const EffectEntryOut = z.object({
|
|
|
104
118
|
kind: z.enum(["third-party", "infrastructure"]).optional(),
|
|
105
119
|
})
|
|
106
120
|
.optional(),
|
|
121
|
+
agentRunId: z.string().optional(),
|
|
107
122
|
});
|
|
108
123
|
|
|
109
124
|
const LogLineOut = z.object({
|
|
@@ -150,6 +165,26 @@ const RunsListOut = z.object({
|
|
|
150
165
|
input: z.unknown().nullable(),
|
|
151
166
|
/** Flow return value snapshot — null when absent (failures / legacy). */
|
|
152
167
|
output: z.unknown().nullable(),
|
|
168
|
+
/**
|
|
169
|
+
* HTTP wire frame — null for non-HTTP runs and rows recorded before
|
|
170
|
+
* the frame existed.
|
|
171
|
+
*/
|
|
172
|
+
http: z
|
|
173
|
+
.object({
|
|
174
|
+
request: z.object({
|
|
175
|
+
method: z.string(),
|
|
176
|
+
path: z.string(),
|
|
177
|
+
query: z.record(z.string(), z.string()),
|
|
178
|
+
headers: z.record(z.string(), z.string()),
|
|
179
|
+
}),
|
|
180
|
+
response: z
|
|
181
|
+
.object({
|
|
182
|
+
status: z.number(),
|
|
183
|
+
headers: z.record(z.string(), z.string()),
|
|
184
|
+
})
|
|
185
|
+
.optional(),
|
|
186
|
+
})
|
|
187
|
+
.nullable(),
|
|
153
188
|
}),
|
|
154
189
|
),
|
|
155
190
|
});
|
|
@@ -891,6 +926,8 @@ const AiListOut = z.object({
|
|
|
891
926
|
agent: z.string(),
|
|
892
927
|
message: z.string(),
|
|
893
928
|
ok: z.boolean(),
|
|
929
|
+
stopReason: z.enum(["completed", "max_steps", "budget", "denied", "aborted", "error"]),
|
|
930
|
+
error: z.string().optional(),
|
|
894
931
|
steps: z.number(),
|
|
895
932
|
cost: z.number(),
|
|
896
933
|
at: z.number(),
|
|
@@ -910,6 +947,7 @@ const AiListOut = z.object({
|
|
|
910
947
|
"secret",
|
|
911
948
|
"call",
|
|
912
949
|
"fetch",
|
|
950
|
+
"decide",
|
|
913
951
|
]),
|
|
914
952
|
resource: z.string(),
|
|
915
953
|
}),
|
|
@@ -2356,6 +2394,14 @@ export function createConsoleBindings(state: ConsoleState): {
|
|
|
2356
2394
|
}),
|
|
2357
2395
|
channelSendTest,
|
|
2358
2396
|
),
|
|
2397
|
+
...lazyRequire<typeof import("./decisions-flows.ts")>(
|
|
2398
|
+
import.meta.dir,
|
|
2399
|
+
["decisions", "flows"].join("-"),
|
|
2400
|
+
).decisionConsoleBindings(state),
|
|
2401
|
+
...lazyRequire<typeof import("./ai-runs-flows.ts")>(
|
|
2402
|
+
import.meta.dir,
|
|
2403
|
+
["ai", "runs", "flows"].join("-"),
|
|
2404
|
+
).agentConsoleBindings(state),
|
|
2359
2405
|
];
|
|
2360
2406
|
|
|
2361
2407
|
return {
|
|
@@ -2657,6 +2703,7 @@ export function projectRun(r: WideEvent, piiFields: ReadonlySet<string> = new Se
|
|
|
2657
2703
|
duration: e.duration,
|
|
2658
2704
|
reversibility: e.reversibility,
|
|
2659
2705
|
...(e.external !== undefined ? { external: e.external } : {}),
|
|
2706
|
+
...(e.agentRunId !== undefined ? { agentRunId: e.agentRunId } : {}),
|
|
2660
2707
|
})),
|
|
2661
2708
|
logs: masked.logs.map((line) => ({
|
|
2662
2709
|
level: line.level,
|
|
@@ -2667,6 +2714,7 @@ export function projectRun(r: WideEvent, piiFields: ReadonlySet<string> = new Se
|
|
|
2667
2714
|
dimensions,
|
|
2668
2715
|
input: masked.input === undefined ? null : masked.input,
|
|
2669
2716
|
output: masked.output === undefined ? null : masked.output,
|
|
2717
|
+
http: r.http ? redactHttpFrame(r.http) : null,
|
|
2670
2718
|
};
|
|
2671
2719
|
}
|
|
2672
2720
|
|
|
@@ -3489,7 +3537,7 @@ function failStoreSqlStats(err: unknown) {
|
|
|
3489
3537
|
return fail("PgStatStatementsUnsupported", { reason: err.message });
|
|
3490
3538
|
}
|
|
3491
3539
|
return fail("PgStatStatementsUnsupported", {
|
|
3492
|
-
reason:
|
|
3540
|
+
reason: engineErrorText(err),
|
|
3493
3541
|
});
|
|
3494
3542
|
}
|
|
3495
3543
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { userPrincipal, type UserPrincipal } from "../../auth/planes.ts";
|
|
10
10
|
import { assembleInput } from "../../compiler/http-parse.ts";
|
|
11
|
+
import { requestForHttpInvoke } from "../../kernel/http-frame.ts";
|
|
11
12
|
import type { ExecuteResult, OkeApp } from "../../kernel/app.ts";
|
|
12
13
|
import { httpStatusForFailure } from "../../kernel/builtin-errors.ts";
|
|
13
14
|
import { fail, OkeError } from "../../kernel/errors.ts";
|
|
@@ -357,9 +358,17 @@ export function bindHostInvokeUserFlow(app: InvokeHostApp): ConsoleInvokeUserFlo
|
|
|
357
358
|
});
|
|
358
359
|
|
|
359
360
|
const runId = okid();
|
|
361
|
+
const request =
|
|
362
|
+
trigger.kind === "http"
|
|
363
|
+
? requestForHttpInvoke(trigger, {
|
|
364
|
+
...(input.pathParams !== undefined ? { pathParams: input.pathParams } : {}),
|
|
365
|
+
input: assembled,
|
|
366
|
+
})
|
|
367
|
+
: undefined;
|
|
360
368
|
const result = await app.execute(flowDef, assembled, trigger, {
|
|
361
369
|
trustedInvoke: true,
|
|
362
370
|
runId,
|
|
371
|
+
...(request !== undefined ? { request } : {}),
|
|
363
372
|
...(input.bypassGates === true ? { bypassGates: true } : {}),
|
|
364
373
|
...(input.revealPii === true ? { revealPii: true } : {}),
|
|
365
374
|
...(input.rls ? { rls: input.rls } : {}),
|
|
@@ -45,6 +45,22 @@ const WideEventIngestSchema = z
|
|
|
45
45
|
.optional(),
|
|
46
46
|
input: z.unknown().optional(),
|
|
47
47
|
output: z.unknown().optional(),
|
|
48
|
+
http: z
|
|
49
|
+
.object({
|
|
50
|
+
request: z.object({
|
|
51
|
+
method: z.string(),
|
|
52
|
+
path: z.string(),
|
|
53
|
+
query: z.record(z.string(), z.string()),
|
|
54
|
+
headers: z.record(z.string(), z.string()),
|
|
55
|
+
}),
|
|
56
|
+
response: z
|
|
57
|
+
.object({
|
|
58
|
+
status: z.number(),
|
|
59
|
+
headers: z.record(z.string(), z.string()),
|
|
60
|
+
})
|
|
61
|
+
.optional(),
|
|
62
|
+
})
|
|
63
|
+
.optional(),
|
|
48
64
|
effects: z.array(z.unknown()),
|
|
49
65
|
logs: z.array(z.unknown()),
|
|
50
66
|
durationMs: z.number(),
|
|
@@ -42,6 +42,7 @@ export const CONSOLE_SPA_PATHS = [
|
|
|
42
42
|
"/",
|
|
43
43
|
"/overview",
|
|
44
44
|
"/flows",
|
|
45
|
+
"/flows/decisions",
|
|
45
46
|
"/store",
|
|
46
47
|
"/vault",
|
|
47
48
|
"/observability",
|
|
@@ -201,6 +202,10 @@ export async function serveConsole(
|
|
|
201
202
|
|
|
202
203
|
const authed = withCookieAuth(request);
|
|
203
204
|
|
|
205
|
+
if (url.pathname.startsWith("/agent/") && handle.state.forwardAgent) {
|
|
206
|
+
return handle.state.forwardAgent(request);
|
|
207
|
+
}
|
|
208
|
+
|
|
204
209
|
if (url.pathname.startsWith("/console/")) {
|
|
205
210
|
const response = await runWithDevSurface("Console", () => handle.app.fetch(authed));
|
|
206
211
|
const withCookies = await attachSessionCookies(authed, response);
|
|
@@ -349,6 +349,15 @@ export interface ConsoleState {
|
|
|
349
349
|
vaultLayerSeed: VaultLayerSeed | null;
|
|
350
350
|
/** Durable journal for rotation blast radius + Clock waiting-on. */
|
|
351
351
|
journalStore: JournalStore | null;
|
|
352
|
+
/**
|
|
353
|
+
* Called after a tool approval is written. The seeded app resumes the
|
|
354
|
+
* parked run so the follow log can emit `RUN_FINISHED`.
|
|
355
|
+
*/
|
|
356
|
+
afterAgentApproval?: () => Promise<void>;
|
|
357
|
+
/**
|
|
358
|
+
* Forwards `GET /agent/runs/:runId/events` to the app that owns the log.
|
|
359
|
+
*/
|
|
360
|
+
forwardAgent?: (request: Request) => Promise<Response>;
|
|
352
361
|
/** Host fleet registry (read-only). Null until bound or confirmed absent. */
|
|
353
362
|
instanceStore: InstanceStore | null;
|
|
354
363
|
/** Host `oke_crons` for fleet lease join (not Console's memory clock). */
|
|
@@ -525,7 +534,17 @@ export interface ConsoleLiveRun {
|
|
|
525
534
|
|
|
526
535
|
/** Effect entry on a live run row. */
|
|
527
536
|
export interface ConsoleLiveRunEffect {
|
|
528
|
-
readonly kind:
|
|
537
|
+
readonly kind:
|
|
538
|
+
| "read"
|
|
539
|
+
| "write"
|
|
540
|
+
| "emit"
|
|
541
|
+
| "send"
|
|
542
|
+
| "ask"
|
|
543
|
+
| "embed"
|
|
544
|
+
| "secret"
|
|
545
|
+
| "call"
|
|
546
|
+
| "fetch"
|
|
547
|
+
| "decide";
|
|
529
548
|
readonly resource: string;
|
|
530
549
|
readonly timestamp: number;
|
|
531
550
|
readonly duration: number;
|
|
@@ -686,6 +705,11 @@ export function createConsoleState(options: CreateConsoleStateOptions = {}): Con
|
|
|
686
705
|
seedAccessDemoData(state.apiKeys, state.invites, state.now);
|
|
687
706
|
accessSeeded = true;
|
|
688
707
|
}
|
|
708
|
+
try {
|
|
709
|
+
await state.apiKeys.reload?.();
|
|
710
|
+
} catch {
|
|
711
|
+
// Keep the in-memory keys when the table read fails.
|
|
712
|
+
}
|
|
689
713
|
return access.projectAccessPanel({
|
|
690
714
|
manifest: state.manifest,
|
|
691
715
|
roles: state.roles,
|