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,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observability AI pane — agent runs, the approval queue, and a live follow.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useCallback, useEffect, useState, type JSX } from "react";
|
|
6
|
+
import {
|
|
7
|
+
agentApprovalApprove,
|
|
8
|
+
agentApprovalDeny,
|
|
9
|
+
agentApprovalsList,
|
|
10
|
+
agentRunsList,
|
|
11
|
+
type AgentApprovalRow,
|
|
12
|
+
type AgentRunRow,
|
|
13
|
+
} from "@/client.ts";
|
|
14
|
+
import {
|
|
15
|
+
EXPLORER_COUNT_CLASS,
|
|
16
|
+
EXPLORER_ROW_CLASS,
|
|
17
|
+
EXPLORER_STRIP_CLASS,
|
|
18
|
+
SECTION_HEAD_CLASS,
|
|
19
|
+
} from "@/components/explorer/explorer-chrome.ts";
|
|
20
|
+
|
|
21
|
+
/** One SSE frame from `GET /agent/runs/:runId/events`. */
|
|
22
|
+
export interface AgentFollowFrame {
|
|
23
|
+
readonly id?: string;
|
|
24
|
+
readonly data: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Split an SSE buffer into frames. The remainder stays for the next chunk.
|
|
29
|
+
*
|
|
30
|
+
* @param buffer - Text received so far
|
|
31
|
+
*/
|
|
32
|
+
export function parseAgentEventFrames(buffer: string): {
|
|
33
|
+
readonly frames: readonly AgentFollowFrame[];
|
|
34
|
+
readonly rest: string;
|
|
35
|
+
} {
|
|
36
|
+
const frames: AgentFollowFrame[] = [];
|
|
37
|
+
let rest = buffer;
|
|
38
|
+
for (;;) {
|
|
39
|
+
const split = rest.indexOf("\n\n");
|
|
40
|
+
if (split < 0) break;
|
|
41
|
+
const block = rest.slice(0, split);
|
|
42
|
+
rest = rest.slice(split + 2);
|
|
43
|
+
let id: string | undefined;
|
|
44
|
+
const data: string[] = [];
|
|
45
|
+
for (const line of block.split("\n")) {
|
|
46
|
+
if (line.startsWith("id:")) id = line.slice(3).trim();
|
|
47
|
+
else if (line.startsWith("data:")) data.push(line.slice(5).trim());
|
|
48
|
+
}
|
|
49
|
+
if (data.length === 0) continue;
|
|
50
|
+
frames.push({ ...(id !== undefined ? { id } : {}), data: data.join("\n") });
|
|
51
|
+
}
|
|
52
|
+
return { frames, rest };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Edited args. Empty means "leave the original". Any other text must be JSON.
|
|
57
|
+
*
|
|
58
|
+
* @param text - Textarea value
|
|
59
|
+
*/
|
|
60
|
+
export function parseApprovalArgs(
|
|
61
|
+
text: string,
|
|
62
|
+
): { readonly ok: true; readonly value?: unknown } | { readonly ok: false } {
|
|
63
|
+
const trimmed = text.trim();
|
|
64
|
+
if (trimmed.length === 0) return { ok: true };
|
|
65
|
+
try {
|
|
66
|
+
return { ok: true, value: JSON.parse(trimmed) as unknown };
|
|
67
|
+
} catch {
|
|
68
|
+
return { ok: false };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Format a pending age in seconds or minutes.
|
|
74
|
+
*
|
|
75
|
+
* @param ageMs - Milliseconds since the approval was requested
|
|
76
|
+
*/
|
|
77
|
+
function formatAge(ageMs: number): string {
|
|
78
|
+
const seconds = Math.max(0, Math.round(ageMs / 1000));
|
|
79
|
+
if (seconds < 60) return `${seconds}s`;
|
|
80
|
+
return `${Math.floor(seconds / 60)}m`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function tokenLabel(run: AgentRunRow): string {
|
|
84
|
+
if (run.inputTokens === undefined && run.outputTokens === undefined) return "";
|
|
85
|
+
return `${run.inputTokens ?? 0}/${run.outputTokens ?? 0}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Agent runs, run detail, follow, and the approvals queue.
|
|
90
|
+
*
|
|
91
|
+
* @param props - Selected run from the URL
|
|
92
|
+
*/
|
|
93
|
+
export function AiRunsPanel({
|
|
94
|
+
agentRunId,
|
|
95
|
+
query,
|
|
96
|
+
onSelectRun,
|
|
97
|
+
}: {
|
|
98
|
+
readonly agentRunId: string | null;
|
|
99
|
+
readonly query: string;
|
|
100
|
+
readonly onSelectRun: (id: string | null) => void;
|
|
101
|
+
}): JSX.Element {
|
|
102
|
+
const [runs, setRuns] = useState<readonly AgentRunRow[]>([]);
|
|
103
|
+
const [rows, setRows] = useState<readonly AgentApprovalRow[]>([]);
|
|
104
|
+
const [notice, setNotice] = useState<string | null>(null);
|
|
105
|
+
const [argsText, setArgsText] = useState<Record<string, string>>({});
|
|
106
|
+
const [reasons, setReasons] = useState<Record<string, string>>({});
|
|
107
|
+
const [follow, setFollow] = useState<readonly string[]>([]);
|
|
108
|
+
const [following, setFollowing] = useState(false);
|
|
109
|
+
|
|
110
|
+
const reload = useCallback(async () => {
|
|
111
|
+
const list = await agentRunsList();
|
|
112
|
+
if (list.data) setRuns(list.data.runs);
|
|
113
|
+
const queue = await agentApprovalsList();
|
|
114
|
+
if (queue.data) setRows(queue.data.rows);
|
|
115
|
+
}, []);
|
|
116
|
+
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
void reload();
|
|
119
|
+
}, [reload]);
|
|
120
|
+
|
|
121
|
+
const selected = runs.find((run) => run.id === agentRunId) ?? null;
|
|
122
|
+
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (!following || !agentRunId) return;
|
|
125
|
+
const controller = new AbortController();
|
|
126
|
+
let lastId = "0";
|
|
127
|
+
let cancelled = false;
|
|
128
|
+
const run = async (): Promise<void> => {
|
|
129
|
+
const res = await fetch(`/agent/runs/${agentRunId}/events`, {
|
|
130
|
+
headers: { "last-event-id": lastId, accept: "text/event-stream" },
|
|
131
|
+
signal: controller.signal,
|
|
132
|
+
});
|
|
133
|
+
if (!res.ok || !res.body) {
|
|
134
|
+
setNotice(String(res.status));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const reader = res.body.getReader();
|
|
138
|
+
const decoder = new TextDecoder();
|
|
139
|
+
let buffer = "";
|
|
140
|
+
for (;;) {
|
|
141
|
+
const chunk = await reader.read();
|
|
142
|
+
if (chunk.done || cancelled) return;
|
|
143
|
+
buffer += decoder.decode(chunk.value, { stream: true });
|
|
144
|
+
const parsed = parseAgentEventFrames(buffer);
|
|
145
|
+
buffer = parsed.rest;
|
|
146
|
+
for (const frame of parsed.frames) {
|
|
147
|
+
if (frame.id) lastId = frame.id;
|
|
148
|
+
setFollow((current) => [...current, frame.data]);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
void run().catch((err: unknown) => {
|
|
153
|
+
if (!cancelled) setNotice(err instanceof Error ? err.message : String(err));
|
|
154
|
+
});
|
|
155
|
+
return () => {
|
|
156
|
+
cancelled = true;
|
|
157
|
+
controller.abort();
|
|
158
|
+
};
|
|
159
|
+
}, [following, agentRunId]);
|
|
160
|
+
|
|
161
|
+
async function approve(row: AgentApprovalRow): Promise<void> {
|
|
162
|
+
const parsed = parseApprovalArgs(argsText[row.id] ?? "");
|
|
163
|
+
if (!parsed.ok) {
|
|
164
|
+
setNotice("ValidationError");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const result = await agentApprovalApprove({
|
|
168
|
+
id: row.id,
|
|
169
|
+
...(parsed.value !== undefined ? { args: parsed.value } : {}),
|
|
170
|
+
});
|
|
171
|
+
if (result.error?.code === "Conflict") setNotice("Conflict");
|
|
172
|
+
else if (result.error?.code === "JournalLeaseBusy") setNotice("JournalLeaseBusy");
|
|
173
|
+
else if (result.error) setNotice(result.error.code);
|
|
174
|
+
else setNotice(null);
|
|
175
|
+
await reload();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function deny(row: AgentApprovalRow): Promise<void> {
|
|
179
|
+
const result = await agentApprovalDeny({
|
|
180
|
+
id: row.id,
|
|
181
|
+
...(reasons[row.id] ? { reason: reasons[row.id] } : {}),
|
|
182
|
+
});
|
|
183
|
+
if (result.error?.code === "Conflict") setNotice("Conflict");
|
|
184
|
+
else if (result.error?.code === "JournalLeaseBusy") setNotice("JournalLeaseBusy");
|
|
185
|
+
else if (result.error) setNotice(result.error.code);
|
|
186
|
+
else setNotice(null);
|
|
187
|
+
await reload();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<div className="flex min-h-0 flex-1 flex-col overflow-auto" data-slot="observability-ai-runs">
|
|
192
|
+
<div className={EXPLORER_STRIP_CLASS}>
|
|
193
|
+
<span className={`${SECTION_HEAD_CLASS} px-2.5`}>Agent runs</span>
|
|
194
|
+
<span className={EXPLORER_COUNT_CLASS}>{runs.length}</span>
|
|
195
|
+
</div>
|
|
196
|
+
<ul>
|
|
197
|
+
{runs
|
|
198
|
+
.filter(
|
|
199
|
+
(run) => query.length === 0 || run.agent.includes(query) || run.id.includes(query),
|
|
200
|
+
)
|
|
201
|
+
.map((run) => (
|
|
202
|
+
<li key={run.id}>
|
|
203
|
+
<button
|
|
204
|
+
type="button"
|
|
205
|
+
className={EXPLORER_ROW_CLASS}
|
|
206
|
+
data-slot="agent-run-row"
|
|
207
|
+
data-status={run.status}
|
|
208
|
+
data-selected={run.id === agentRunId ? "true" : "false"}
|
|
209
|
+
onClick={() => onSelectRun(run.id === agentRunId ? null : run.id)}
|
|
210
|
+
>
|
|
211
|
+
<span className="min-w-0 flex-1 truncate">{run.agent}</span>
|
|
212
|
+
<span className={EXPLORER_COUNT_CLASS}>{run.status}</span>
|
|
213
|
+
<span className={EXPLORER_COUNT_CLASS} data-slot="agent-run-stop">
|
|
214
|
+
{run.stopReason ?? "—"}
|
|
215
|
+
{run.error ? ` · ${run.error}` : ""}
|
|
216
|
+
</span>
|
|
217
|
+
<span className={EXPLORER_COUNT_CLASS}>{run.steps}</span>
|
|
218
|
+
<span className={EXPLORER_COUNT_CLASS}>{run.cost}</span>
|
|
219
|
+
<span className={EXPLORER_COUNT_CLASS}>{tokenLabel(run)}</span>
|
|
220
|
+
<span className="truncate font-mono text-[10px] text-muted-foreground">
|
|
221
|
+
{run.threadId ?? ""}
|
|
222
|
+
</span>
|
|
223
|
+
</button>
|
|
224
|
+
</li>
|
|
225
|
+
))}
|
|
226
|
+
</ul>
|
|
227
|
+
{selected ? (
|
|
228
|
+
<div data-slot="agent-run-detail">
|
|
229
|
+
<div className={EXPLORER_STRIP_CLASS}>
|
|
230
|
+
<span className={`${SECTION_HEAD_CLASS} px-2.5`}>{selected.agent}</span>
|
|
231
|
+
<button
|
|
232
|
+
type="button"
|
|
233
|
+
className={`${SECTION_HEAD_CLASS} px-2.5`}
|
|
234
|
+
data-slot="agent-run-follow"
|
|
235
|
+
onClick={() => {
|
|
236
|
+
setFollow([]);
|
|
237
|
+
setFollowing(true);
|
|
238
|
+
}}
|
|
239
|
+
>
|
|
240
|
+
Follow
|
|
241
|
+
</button>
|
|
242
|
+
<a href="#agent-approvals" className="px-2.5 text-xs underline">
|
|
243
|
+
Approvals
|
|
244
|
+
</a>
|
|
245
|
+
</div>
|
|
246
|
+
<ul>
|
|
247
|
+
{selected.trail.map((step, index) => (
|
|
248
|
+
<li
|
|
249
|
+
key={`${step.tool}:${index}`}
|
|
250
|
+
className={EXPLORER_ROW_CLASS}
|
|
251
|
+
data-slot="agent-trail-step"
|
|
252
|
+
>
|
|
253
|
+
<span className="min-w-0 flex-1 truncate">{step.tool}</span>
|
|
254
|
+
<span className={EXPLORER_COUNT_CLASS}>{step.status}</span>
|
|
255
|
+
{step.approver ? (
|
|
256
|
+
<span className={EXPLORER_COUNT_CLASS} data-slot="agent-trail-approver">
|
|
257
|
+
{step.approver}
|
|
258
|
+
</span>
|
|
259
|
+
) : null}
|
|
260
|
+
{step.denial ? (
|
|
261
|
+
<span className={EXPLORER_COUNT_CLASS}>{step.denial.reason}</span>
|
|
262
|
+
) : null}
|
|
263
|
+
</li>
|
|
264
|
+
))}
|
|
265
|
+
</ul>
|
|
266
|
+
{selected.repairs.length > 0 ? (
|
|
267
|
+
<ul>
|
|
268
|
+
{selected.repairs.map((repair) => (
|
|
269
|
+
<li
|
|
270
|
+
key={`${repair.prompt}:${repair.at}`}
|
|
271
|
+
className={EXPLORER_ROW_CLASS}
|
|
272
|
+
data-slot="agent-repair"
|
|
273
|
+
>
|
|
274
|
+
<span className="min-w-0 flex-1 truncate">{repair.prompt}</span>
|
|
275
|
+
<span className={EXPLORER_COUNT_CLASS}>{repair.attempts}</span>
|
|
276
|
+
</li>
|
|
277
|
+
))}
|
|
278
|
+
</ul>
|
|
279
|
+
) : null}
|
|
280
|
+
{selected.children.length > 0 ? (
|
|
281
|
+
<ul data-slot="agent-subagents">
|
|
282
|
+
{selected.children.map((child) => (
|
|
283
|
+
<li key={child.id}>
|
|
284
|
+
<button
|
|
285
|
+
type="button"
|
|
286
|
+
className={EXPLORER_ROW_CLASS}
|
|
287
|
+
data-parent={child.parentRunId ?? ""}
|
|
288
|
+
onClick={() => onSelectRun(child.id)}
|
|
289
|
+
>
|
|
290
|
+
<span className="min-w-0 flex-1 truncate">{child.agent}</span>
|
|
291
|
+
<span className={EXPLORER_COUNT_CLASS}>{child.status}</span>
|
|
292
|
+
</button>
|
|
293
|
+
</li>
|
|
294
|
+
))}
|
|
295
|
+
</ul>
|
|
296
|
+
) : null}
|
|
297
|
+
{following ? (
|
|
298
|
+
<ul data-slot="agent-follow">
|
|
299
|
+
{follow.map((line, index) => (
|
|
300
|
+
<li key={index} className={EXPLORER_ROW_CLASS} data-slot="agent-follow-event">
|
|
301
|
+
<span className="min-w-0 flex-1 truncate font-mono text-[10px]">{line}</span>
|
|
302
|
+
</li>
|
|
303
|
+
))}
|
|
304
|
+
</ul>
|
|
305
|
+
) : null}
|
|
306
|
+
</div>
|
|
307
|
+
) : null}
|
|
308
|
+
<div className={EXPLORER_STRIP_CLASS} id="agent-approvals">
|
|
309
|
+
<span className={`${SECTION_HEAD_CLASS} px-2.5`}>Approvals</span>
|
|
310
|
+
</div>
|
|
311
|
+
{notice ? (
|
|
312
|
+
<p className="px-2.5 py-1 text-xs" data-slot="approval-notice">
|
|
313
|
+
{notice}
|
|
314
|
+
</p>
|
|
315
|
+
) : null}
|
|
316
|
+
<ul>
|
|
317
|
+
{rows.map((row) => (
|
|
318
|
+
<li key={row.id} className={EXPLORER_ROW_CLASS} data-slot="approval-row">
|
|
319
|
+
<span className="min-w-0 flex-1 truncate">
|
|
320
|
+
{row.agent} · {row.tool}
|
|
321
|
+
</span>
|
|
322
|
+
<span className={EXPLORER_COUNT_CLASS} data-slot="approval-tenant">
|
|
323
|
+
{row.tenant ?? "—"}
|
|
324
|
+
</span>
|
|
325
|
+
<span className={EXPLORER_COUNT_CLASS} data-slot="approval-age">
|
|
326
|
+
{formatAge(row.ageMs)}
|
|
327
|
+
</span>
|
|
328
|
+
<span className={EXPLORER_COUNT_CLASS}>{row.gate}</span>
|
|
329
|
+
<form
|
|
330
|
+
className="flex items-center gap-2"
|
|
331
|
+
onSubmit={(event) => {
|
|
332
|
+
event.preventDefault();
|
|
333
|
+
void approve(row);
|
|
334
|
+
}}
|
|
335
|
+
>
|
|
336
|
+
<input
|
|
337
|
+
aria-label="Edited args"
|
|
338
|
+
value={argsText[row.id] ?? ""}
|
|
339
|
+
placeholder={JSON.stringify(row.args)}
|
|
340
|
+
onChange={(event) => {
|
|
341
|
+
const next = event.target.value;
|
|
342
|
+
setArgsText((current) => ({ ...current, [row.id]: next }));
|
|
343
|
+
}}
|
|
344
|
+
/>
|
|
345
|
+
<button type="submit">Approve</button>
|
|
346
|
+
</form>
|
|
347
|
+
<form
|
|
348
|
+
className="flex items-center gap-2"
|
|
349
|
+
onSubmit={(event) => {
|
|
350
|
+
event.preventDefault();
|
|
351
|
+
void deny(row);
|
|
352
|
+
}}
|
|
353
|
+
>
|
|
354
|
+
<input
|
|
355
|
+
aria-label="Deny reason"
|
|
356
|
+
value={reasons[row.id] ?? ""}
|
|
357
|
+
onChange={(event) => {
|
|
358
|
+
const next = event.target.value;
|
|
359
|
+
setReasons((current) => ({ ...current, [row.id]: next }));
|
|
360
|
+
}}
|
|
361
|
+
/>
|
|
362
|
+
<button type="submit">Deny</button>
|
|
363
|
+
</form>
|
|
364
|
+
</li>
|
|
365
|
+
))}
|
|
366
|
+
</ul>
|
|
367
|
+
</div>
|
|
368
|
+
);
|
|
369
|
+
}
|
|
@@ -26,6 +26,7 @@ import { useClockList } from "./data/use-clock-list.ts";
|
|
|
26
26
|
import { useInstancesList } from "./data/use-instances-list.ts";
|
|
27
27
|
import { useSignalsList } from "./data/use-signals-list.ts";
|
|
28
28
|
import { AiRail } from "./detail/ai-rail.tsx";
|
|
29
|
+
import { AiRunsPanel } from "./detail/ai-runs.tsx";
|
|
29
30
|
import { InstanceFleetSheet } from "./detail/instance-fleet-sheet.tsx";
|
|
30
31
|
import { MetricsPanel } from "./detail/metrics-panel.tsx";
|
|
31
32
|
import { RunsQueryPanel } from "./query/runs-query-panel.tsx";
|
|
@@ -57,11 +58,13 @@ export function ObservabilityPage(): JSX.Element {
|
|
|
57
58
|
selectedErrorKey,
|
|
58
59
|
query,
|
|
59
60
|
view,
|
|
61
|
+
agentRunId,
|
|
60
62
|
setSelectedRun,
|
|
61
63
|
setWindow,
|
|
62
64
|
setSelectedError,
|
|
63
65
|
setQuery,
|
|
64
66
|
setView,
|
|
67
|
+
setAgentRun,
|
|
65
68
|
} = useObservabilitySelection();
|
|
66
69
|
const [fleetOpen, setFleetOpen] = useState(false);
|
|
67
70
|
const start = useExplorerStartPanel();
|
|
@@ -122,6 +125,10 @@ export function ObservabilityPage(): JSX.Element {
|
|
|
122
125
|
() => (selectedRunId ? (buffer.find((run) => run.id === selectedRunId) ?? null) : null),
|
|
123
126
|
[buffer, selectedRunId],
|
|
124
127
|
);
|
|
128
|
+
const navigationRuns = useMemo(
|
|
129
|
+
() => [...buffer].sort((a, b) => b.startedAt - a.startedAt || a.id.localeCompare(b.id)),
|
|
130
|
+
[buffer],
|
|
131
|
+
);
|
|
125
132
|
|
|
126
133
|
return (
|
|
127
134
|
<div className={EXPLORER_PAGE_CLASS} data-slot="observability-page">
|
|
@@ -198,9 +205,27 @@ export function ObservabilityPage(): JSX.Element {
|
|
|
198
205
|
>
|
|
199
206
|
SQL
|
|
200
207
|
</button>
|
|
208
|
+
<button
|
|
209
|
+
type="button"
|
|
210
|
+
role="tab"
|
|
211
|
+
aria-selected={view === "ai"}
|
|
212
|
+
className={cn(
|
|
213
|
+
EXPLORER_STRIP_TOKEN_CLASS,
|
|
214
|
+
"font-semibold tracking-[0.08em] uppercase",
|
|
215
|
+
view === "ai"
|
|
216
|
+
? EXPLORER_STRIP_TOKEN_ACTIVE_CLASS
|
|
217
|
+
: EXPLORER_STRIP_TOKEN_IDLE_CLASS,
|
|
218
|
+
)}
|
|
219
|
+
onClick={() => setView("ai")}
|
|
220
|
+
data-slot="observability-view-ai"
|
|
221
|
+
>
|
|
222
|
+
AI
|
|
223
|
+
</button>
|
|
201
224
|
</div>
|
|
202
225
|
{view === "query" ? (
|
|
203
226
|
<RunsQueryPanel />
|
|
227
|
+
) : view === "ai" ? (
|
|
228
|
+
<AiRunsPanel agentRunId={agentRunId} query={query} onSelectRun={setAgentRun} />
|
|
204
229
|
) : (
|
|
205
230
|
<>
|
|
206
231
|
<AiRail ai={ai.data} asks={asks} tokens={tokens} />
|
|
@@ -210,7 +235,12 @@ export function ObservabilityPage(): JSX.Element {
|
|
|
210
235
|
</div>
|
|
211
236
|
</ResizablePanel>
|
|
212
237
|
</ResizablePanelGroup>
|
|
213
|
-
<TraceDetailSheet
|
|
238
|
+
<TraceDetailSheet
|
|
239
|
+
run={selectedRun}
|
|
240
|
+
onClose={() => setSelectedRun(null)}
|
|
241
|
+
navigationRuns={navigationRuns}
|
|
242
|
+
onSelectRun={setSelectedRun}
|
|
243
|
+
/>
|
|
214
244
|
<InstanceFleetSheet fleet={fleet.data} open={fleetOpen} onClose={() => setFleetOpen(false)} />
|
|
215
245
|
</div>
|
|
216
246
|
);
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from "../lib/window-stats.ts";
|
|
12
12
|
|
|
13
13
|
/** Right-pane mode on `/observability`. */
|
|
14
|
-
export type ObservabilityView = "metrics" | "query";
|
|
14
|
+
export type ObservabilityView = "metrics" | "query" | "ai";
|
|
15
15
|
|
|
16
16
|
/** Search params for `/observability`. */
|
|
17
17
|
export interface ObservabilitySearch {
|
|
@@ -20,6 +20,7 @@ export interface ObservabilitySearch {
|
|
|
20
20
|
readonly error?: string;
|
|
21
21
|
readonly q?: string;
|
|
22
22
|
readonly view?: ObservabilityView;
|
|
23
|
+
readonly agentRun?: string;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -33,13 +34,16 @@ export function validateObservabilitySearch(search: Record<string, unknown>): Ob
|
|
|
33
34
|
const error =
|
|
34
35
|
typeof search.error === "string" && search.error.length > 0 ? search.error : undefined;
|
|
35
36
|
const q = typeof search.q === "string" && search.q.length > 0 ? search.q : undefined;
|
|
36
|
-
const view = search.view === "query"
|
|
37
|
+
const view = search.view === "query" || search.view === "ai" ? search.view : undefined;
|
|
38
|
+
const agentRun =
|
|
39
|
+
typeof search.agentRun === "string" && search.agentRun.length > 0 ? search.agentRun : undefined;
|
|
37
40
|
return {
|
|
38
41
|
...(run !== undefined ? { run } : {}),
|
|
39
42
|
...(window !== DEFAULT_OBSERVABILITY_WINDOW ? { window } : {}),
|
|
40
43
|
...(error !== undefined ? { error } : {}),
|
|
41
44
|
...(q !== undefined ? { q } : {}),
|
|
42
45
|
...(view !== undefined ? { view } : {}),
|
|
46
|
+
...(agentRun !== undefined ? { agentRun } : {}),
|
|
43
47
|
};
|
|
44
48
|
}
|
|
45
49
|
|
|
@@ -54,7 +58,9 @@ export function useObservabilitySelection() {
|
|
|
54
58
|
const window = parseObservabilityWindow(search.window);
|
|
55
59
|
const selectedErrorKey = typeof search.error === "string" ? search.error : null;
|
|
56
60
|
const query = typeof search.q === "string" ? search.q : "";
|
|
57
|
-
const view: ObservabilityView =
|
|
61
|
+
const view: ObservabilityView =
|
|
62
|
+
search.view === "query" || search.view === "ai" ? search.view : "metrics";
|
|
63
|
+
const agentRunId = typeof search.agentRun === "string" ? search.agentRun : null;
|
|
58
64
|
|
|
59
65
|
const patch = useCallback(
|
|
60
66
|
(next: Partial<ObservabilitySearch>) => {
|
|
@@ -106,16 +112,25 @@ export function useObservabilitySelection() {
|
|
|
106
112
|
[patch],
|
|
107
113
|
);
|
|
108
114
|
|
|
115
|
+
const setAgentRun = useCallback(
|
|
116
|
+
(run: string | null) => {
|
|
117
|
+
patch({ agentRun: run ?? undefined, view: "ai" });
|
|
118
|
+
},
|
|
119
|
+
[patch],
|
|
120
|
+
);
|
|
121
|
+
|
|
109
122
|
return {
|
|
110
123
|
selectedRunId,
|
|
111
124
|
window,
|
|
112
125
|
selectedErrorKey,
|
|
113
126
|
query,
|
|
114
127
|
view,
|
|
128
|
+
agentRunId,
|
|
115
129
|
setSelectedRun,
|
|
116
130
|
setWindow,
|
|
117
131
|
setSelectedError,
|
|
118
132
|
setQuery,
|
|
119
133
|
setView,
|
|
134
|
+
setAgentRun,
|
|
120
135
|
};
|
|
121
136
|
}
|
|
@@ -214,7 +214,18 @@ function formatPct(value: number | null): string {
|
|
|
214
214
|
|
|
215
215
|
function errorMessage(error: unknown): string | null {
|
|
216
216
|
if (!error) return null;
|
|
217
|
-
|
|
217
|
+
const text =
|
|
218
|
+
error instanceof Error
|
|
219
|
+
? error.message
|
|
220
|
+
: typeof error === "object" && error !== null && "message" in error
|
|
221
|
+
? typeof (error as { message?: unknown }).message === "string"
|
|
222
|
+
? (error as { message: string }).message
|
|
223
|
+
: null
|
|
224
|
+
: typeof error === "string"
|
|
225
|
+
? error
|
|
226
|
+
: null;
|
|
227
|
+
if (!text || text.trim().length === 0 || text === "[object Object]") return null;
|
|
228
|
+
return text;
|
|
218
229
|
}
|
|
219
230
|
|
|
220
231
|
function errorCode(error: unknown): string | null {
|
|
@@ -433,7 +433,20 @@ function fmtCost(value: unknown): string {
|
|
|
433
433
|
|
|
434
434
|
function errorMessage(error: unknown): string | null {
|
|
435
435
|
if (!error) return null;
|
|
436
|
-
|
|
436
|
+
const text =
|
|
437
|
+
error instanceof Error
|
|
438
|
+
? error.message
|
|
439
|
+
: typeof error === "object" && error !== null && "message" in error
|
|
440
|
+
? stringField(error.message)
|
|
441
|
+
: typeof error === "string"
|
|
442
|
+
? error
|
|
443
|
+
: null;
|
|
444
|
+
if (!text || text.trim().length === 0 || text === "[object Object]") return null;
|
|
445
|
+
return text;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function stringField(value: unknown): string | null {
|
|
449
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
437
450
|
}
|
|
438
451
|
|
|
439
452
|
function errorCode(error: unknown): string | null {
|
|
@@ -48,18 +48,21 @@ export interface FlowContractPanelProps {
|
|
|
48
48
|
readonly runs?: readonly RunRow[];
|
|
49
49
|
/** Start-panel collapse control for the Flows catalog. */
|
|
50
50
|
readonly leading?: ReactNode;
|
|
51
|
+
/** End-rail collapse control for Call API. */
|
|
52
|
+
readonly trailing?: ReactNode;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
/**
|
|
54
|
-
*
|
|
56
|
+
* Contract pane for a selected flow — Call API sits in the rail beside it.
|
|
55
57
|
*
|
|
56
|
-
* @param props - Flow row + Manifest + optional runs buffer +
|
|
58
|
+
* @param props - Flow row + Manifest + optional runs buffer + panel toggles
|
|
57
59
|
*/
|
|
58
60
|
export function FlowContractPanel({
|
|
59
61
|
row,
|
|
60
62
|
manifest,
|
|
61
63
|
runs,
|
|
62
64
|
leading,
|
|
65
|
+
trailing,
|
|
63
66
|
}: FlowContractPanelProps): JSX.Element {
|
|
64
67
|
const kind = flowTriggerKind(row.flow.trigger);
|
|
65
68
|
const trigger = flowTriggerSpec(row.flow.trigger);
|
|
@@ -95,6 +98,7 @@ export function FlowContractPanel({
|
|
|
95
98
|
<DetailHeader
|
|
96
99
|
dataSlot="endpoint-header"
|
|
97
100
|
leading={leading}
|
|
101
|
+
trailing={trailing}
|
|
98
102
|
icon={
|
|
99
103
|
<span data-slot="trigger-kind-icon" data-kind={kind}>
|
|
100
104
|
<HugeiconsIcon icon={trigger.icon} className="size-4" />
|