@wrenai/genbi 0.0.1
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/LICENSE +201 -0
- package/README.md +141 -0
- package/bin/genbi.mjs +31 -0
- package/dist/assets/echarts-LTFC-ysd.js +40 -0
- package/dist/assets/index-Bo--QMUN.js +441 -0
- package/dist/assets/index-Xs4q7pNo.css +1 -0
- package/dist/index.html +14 -0
- package/dist/wrenai_logo.png +0 -0
- package/dist/wrenai_logo_white.png +0 -0
- package/dist-server/harness/auth/detect.d.ts +18 -0
- package/dist-server/harness/auth/detect.js +37 -0
- package/dist-server/harness/auth/index.d.ts +4 -0
- package/dist-server/harness/auth/index.js +2 -0
- package/dist-server/harness/auth/probe.d.ts +18 -0
- package/dist-server/harness/auth/probe.js +52 -0
- package/dist-server/harness/auth/types.d.ts +42 -0
- package/dist-server/harness/auth/types.js +9 -0
- package/dist-server/harness/bundle/loader.d.ts +32 -0
- package/dist-server/harness/bundle/loader.js +43 -0
- package/dist-server/harness/bundle/schema.d.ts +199 -0
- package/dist-server/harness/bundle/schema.js +127 -0
- package/dist-server/harness/bundle/version.d.ts +10 -0
- package/dist-server/harness/bundle/version.js +43 -0
- package/dist-server/harness/capability/gate.d.ts +6 -0
- package/dist-server/harness/capability/gate.js +29 -0
- package/dist-server/harness/capability/registry.d.ts +6 -0
- package/dist-server/harness/capability/registry.js +17 -0
- package/dist-server/harness/cli-args.d.ts +123 -0
- package/dist-server/harness/cli-args.js +247 -0
- package/dist-server/harness/cli.d.ts +2 -0
- package/dist-server/harness/cli.js +151 -0
- package/dist-server/harness/compile/cache.d.ts +23 -0
- package/dist-server/harness/compile/cache.js +109 -0
- package/dist-server/harness/compile/compose-profile.d.ts +34 -0
- package/dist-server/harness/compile/compose-profile.js +95 -0
- package/dist-server/harness/compile/errors.d.ts +22 -0
- package/dist-server/harness/compile/errors.js +41 -0
- package/dist-server/harness/compile/fingerprint.d.ts +22 -0
- package/dist-server/harness/compile/fingerprint.js +84 -0
- package/dist-server/harness/compile/index.d.ts +8 -0
- package/dist-server/harness/compile/index.js +7 -0
- package/dist-server/harness/compile/pipeline.d.ts +66 -0
- package/dist-server/harness/compile/pipeline.js +176 -0
- package/dist-server/harness/compile/resolve-binary.d.ts +55 -0
- package/dist-server/harness/compile/resolve-binary.js +133 -0
- package/dist-server/harness/compile/types.d.ts +107 -0
- package/dist-server/harness/compile/types.js +1 -0
- package/dist-server/harness/compile/warble-identity.d.ts +12 -0
- package/dist-server/harness/compile/warble-identity.js +33 -0
- package/dist-server/harness/compliance/gate.d.ts +35 -0
- package/dist-server/harness/compliance/gate.js +48 -0
- package/dist-server/harness/compliance/index.d.ts +2 -0
- package/dist-server/harness/compliance/index.js +1 -0
- package/dist-server/harness/compliance/types.d.ts +16 -0
- package/dist-server/harness/compliance/types.js +1 -0
- package/dist-server/harness/events/emitter.d.ts +34 -0
- package/dist-server/harness/events/emitter.js +25 -0
- package/dist-server/harness/events/index.d.ts +4 -0
- package/dist-server/harness/events/index.js +2 -0
- package/dist-server/harness/events/sse.d.ts +20 -0
- package/dist-server/harness/events/sse.js +48 -0
- package/dist-server/harness/events/types.d.ts +131 -0
- package/dist-server/harness/events/types.js +1 -0
- package/dist-server/harness/exec/errors.d.ts +16 -0
- package/dist-server/harness/exec/errors.js +29 -0
- package/dist-server/harness/exec/index.d.ts +5 -0
- package/dist-server/harness/exec/index.js +2 -0
- package/dist-server/harness/exec/local.d.ts +32 -0
- package/dist-server/harness/exec/local.js +134 -0
- package/dist-server/harness/exec/policy.d.ts +12 -0
- package/dist-server/harness/exec/policy.js +1 -0
- package/dist-server/harness/exec/types.d.ts +63 -0
- package/dist-server/harness/exec/types.js +1 -0
- package/dist-server/harness/guardrails/index.d.ts +2 -0
- package/dist-server/harness/guardrails/index.js +1 -0
- package/dist-server/harness/guardrails/policy.d.ts +45 -0
- package/dist-server/harness/guardrails/policy.js +57 -0
- package/dist-server/harness/index.d.ts +37 -0
- package/dist-server/harness/index.js +19 -0
- package/dist-server/harness/loop/errors.d.ts +53 -0
- package/dist-server/harness/loop/errors.js +72 -0
- package/dist-server/harness/loop/executor.d.ts +123 -0
- package/dist-server/harness/loop/executor.js +380 -0
- package/dist-server/harness/loop/guard.d.ts +16 -0
- package/dist-server/harness/loop/guard.js +1 -0
- package/dist-server/harness/loop/index.d.ts +5 -0
- package/dist-server/harness/loop/index.js +3 -0
- package/dist-server/harness/native-producer/index.d.ts +109 -0
- package/dist-server/harness/native-producer/index.js +302 -0
- package/dist-server/harness/npm-package-resolve.d.ts +28 -0
- package/dist-server/harness/npm-package-resolve.js +47 -0
- package/dist-server/harness/providers/adapters/anthropic.d.ts +7 -0
- package/dist-server/harness/providers/adapters/anthropic.js +6 -0
- package/dist-server/harness/providers/adapters/mock.d.ts +4 -0
- package/dist-server/harness/providers/adapters/mock.js +5 -0
- package/dist-server/harness/providers/adapters/openai-compatible.d.ts +10 -0
- package/dist-server/harness/providers/adapters/openai-compatible.js +10 -0
- package/dist-server/harness/providers/binding.d.ts +23 -0
- package/dist-server/harness/providers/binding.js +13 -0
- package/dist-server/harness/providers/errors.d.ts +6 -0
- package/dist-server/harness/providers/errors.js +12 -0
- package/dist-server/harness/providers/index.d.ts +18 -0
- package/dist-server/harness/providers/index.js +22 -0
- package/dist-server/harness/providers/registry.d.ts +16 -0
- package/dist-server/harness/providers/registry.js +24 -0
- package/dist-server/harness/render/envelope.d.ts +145 -0
- package/dist-server/harness/render/envelope.js +428 -0
- package/dist-server/harness/render/errors.d.ts +33 -0
- package/dist-server/harness/render/errors.js +46 -0
- package/dist-server/harness/render/index.d.ts +5 -0
- package/dist-server/harness/render/index.js +3 -0
- package/dist-server/harness/render/validate.d.ts +13 -0
- package/dist-server/harness/render/validate.js +88 -0
- package/dist-server/harness/route/adapter-spec.d.ts +40 -0
- package/dist-server/harness/route/adapter-spec.js +61 -0
- package/dist-server/harness/route/agent-sdk-cli.d.ts +30 -0
- package/dist-server/harness/route/agent-sdk-cli.js +86 -0
- package/dist-server/harness/route/agent-sdk-manifest.d.ts +31 -0
- package/dist-server/harness/route/agent-sdk-manifest.js +56 -0
- package/dist-server/harness/route/artifact-content.d.ts +53 -0
- package/dist-server/harness/route/artifact-content.js +83 -0
- package/dist-server/harness/route/chat-event-mapper.d.ts +95 -0
- package/dist-server/harness/route/chat-event-mapper.js +164 -0
- package/dist-server/harness/route/codex-ask-events.d.ts +25 -0
- package/dist-server/harness/route/codex-ask-events.js +395 -0
- package/dist-server/harness/route/codex-ask.d.ts +15 -0
- package/dist-server/harness/route/codex-ask.js +242 -0
- package/dist-server/harness/route/codex-local-cli.d.ts +20 -0
- package/dist-server/harness/route/codex-local-cli.js +78 -0
- package/dist-server/harness/route/codex-local-manifest.d.ts +22 -0
- package/dist-server/harness/route/codex-local-manifest.js +180 -0
- package/dist-server/harness/route/describe.d.ts +54 -0
- package/dist-server/harness/route/describe.js +84 -0
- package/dist-server/harness/route/dispatched.d.ts +138 -0
- package/dist-server/harness/route/dispatched.js +339 -0
- package/dist-server/harness/route/in-process.d.ts +43 -0
- package/dist-server/harness/route/in-process.js +106 -0
- package/dist-server/harness/route/index.d.ts +19 -0
- package/dist-server/harness/route/index.js +12 -0
- package/dist-server/harness/route/profile-source.d.ts +37 -0
- package/dist-server/harness/route/profile-source.js +76 -0
- package/dist-server/harness/route/route.d.ts +33 -0
- package/dist-server/harness/route/route.js +114 -0
- package/dist-server/harness/route/tier-binding.d.ts +36 -0
- package/dist-server/harness/route/tier-binding.js +63 -0
- package/dist-server/harness/route/types.d.ts +286 -0
- package/dist-server/harness/route/types.js +1 -0
- package/dist-server/harness/session/errors.d.ts +4 -0
- package/dist-server/harness/session/errors.js +7 -0
- package/dist-server/harness/session/gate.d.ts +11 -0
- package/dist-server/harness/session/gate.js +13 -0
- package/dist-server/harness/session/index.d.ts +5 -0
- package/dist-server/harness/session/index.js +3 -0
- package/dist-server/harness/session/run.d.ts +60 -0
- package/dist-server/harness/session/run.js +333 -0
- package/dist-server/harness/session/types.d.ts +29 -0
- package/dist-server/harness/session/types.js +1 -0
- package/dist-server/harness/setup/codex-events.d.ts +12 -0
- package/dist-server/harness/setup/codex-events.js +108 -0
- package/dist-server/harness/setup/runner.d.ts +497 -0
- package/dist-server/harness/setup/runner.js +1314 -0
- package/dist-server/harness/tools/errors.d.ts +107 -0
- package/dist-server/harness/tools/errors.js +141 -0
- package/dist-server/harness/tools/index.d.ts +13 -0
- package/dist-server/harness/tools/index.js +8 -0
- package/dist-server/harness/tools/mcp.d.ts +26 -0
- package/dist-server/harness/tools/mcp.js +15 -0
- package/dist-server/harness/tools/native.d.ts +179 -0
- package/dist-server/harness/tools/native.js +322 -0
- package/dist-server/harness/tools/parse.d.ts +14 -0
- package/dist-server/harness/tools/parse.js +18 -0
- package/dist-server/harness/tools/resolve-wren-binary.d.ts +12 -0
- package/dist-server/harness/tools/resolve-wren-binary.js +21 -0
- package/dist-server/harness/tools/resolve.d.ts +51 -0
- package/dist-server/harness/tools/resolve.js +79 -0
- package/dist-server/harness/tools/setup-native.d.ts +84 -0
- package/dist-server/harness/tools/setup-native.js +353 -0
- package/dist-server/harness/tools/wiring.d.ts +10 -0
- package/dist-server/harness/tools/wiring.js +16 -0
- package/dist-server/providers/setup.provider.yaml +31 -0
- package/dist-server/providers/wren.provider.yaml +33 -0
- package/dist-server/server/adopt.d.ts +87 -0
- package/dist-server/server/adopt.js +263 -0
- package/dist-server/server/app.d.ts +6 -0
- package/dist-server/server/app.js +2521 -0
- package/dist-server/server/auth-choice.d.ts +16 -0
- package/dist-server/server/auth-choice.js +36 -0
- package/dist-server/server/bin.d.ts +2 -0
- package/dist-server/server/bin.js +601 -0
- package/dist-server/server/bootstrap-project-binding.d.ts +31 -0
- package/dist-server/server/bootstrap-project-binding.js +71 -0
- package/dist-server/server/clarify.d.ts +10 -0
- package/dist-server/server/clarify.js +17 -0
- package/dist-server/server/codex-setup-mcp.d.ts +2 -0
- package/dist-server/server/codex-setup-mcp.js +147 -0
- package/dist-server/server/compose.d.ts +89 -0
- package/dist-server/server/compose.js +271 -0
- package/dist-server/server/conn-config.d.ts +30 -0
- package/dist-server/server/conn-config.js +253 -0
- package/dist-server/server/context-files.d.ts +12 -0
- package/dist-server/server/context-files.js +117 -0
- package/dist-server/server/context-map.d.ts +30 -0
- package/dist-server/server/context-map.js +95 -0
- package/dist-server/server/context-source.d.ts +68 -0
- package/dist-server/server/context-source.js +85 -0
- package/dist-server/server/db.d.ts +862 -0
- package/dist-server/server/db.js +1839 -0
- package/dist-server/server/enrichment-runner.d.ts +184 -0
- package/dist-server/server/enrichment-runner.js +213 -0
- package/dist-server/server/enrichment-verify.d.ts +73 -0
- package/dist-server/server/enrichment-verify.js +290 -0
- package/dist-server/server/enrichment.d.ts +232 -0
- package/dist-server/server/enrichment.js +199 -0
- package/dist-server/server/env-detect.d.ts +8 -0
- package/dist-server/server/env-detect.js +17 -0
- package/dist-server/server/env-field-hints.d.ts +46 -0
- package/dist-server/server/env-field-hints.js +48 -0
- package/dist-server/server/fold.d.ts +133 -0
- package/dist-server/server/fold.js +407 -0
- package/dist-server/server/harness-profile-sources.d.ts +3 -0
- package/dist-server/server/harness-profile-sources.js +24 -0
- package/dist-server/server/harness.d.ts +19 -0
- package/dist-server/server/harness.js +439 -0
- package/dist-server/server/impact.d.ts +18 -0
- package/dist-server/server/impact.js +85 -0
- package/dist-server/server/interactive-terminal.d.ts +175 -0
- package/dist-server/server/interactive-terminal.js +336 -0
- package/dist-server/server/native-artifacts.d.ts +1004 -0
- package/dist-server/server/native-artifacts.js +564 -0
- package/dist-server/server/native-dispatch-registry.d.ts +42 -0
- package/dist-server/server/native-dispatch-registry.js +45 -0
- package/dist-server/server/native-producer-fence.d.ts +14 -0
- package/dist-server/server/native-producer-fence.js +48 -0
- package/dist-server/server/native-session-resume.d.ts +6 -0
- package/dist-server/server/native-session-resume.js +103 -0
- package/dist-server/server/native-session-workspace.d.ts +40 -0
- package/dist-server/server/native-session-workspace.js +175 -0
- package/dist-server/server/native-sessions.d.ts +374 -0
- package/dist-server/server/native-sessions.js +1517 -0
- package/dist-server/server/native-wren-runtime.d.ts +20 -0
- package/dist-server/server/native-wren-runtime.js +259 -0
- package/dist-server/server/node-pty-host.d.ts +31 -0
- package/dist-server/server/node-pty-host.js +100 -0
- package/dist-server/server/route-intent.d.ts +40 -0
- package/dist-server/server/route-intent.js +23 -0
- package/dist-server/server/runtime-binding.d.ts +52 -0
- package/dist-server/server/runtime-binding.js +258 -0
- package/dist-server/server/source-catalog.d.ts +84 -0
- package/dist-server/server/source-catalog.js +221 -0
- package/dist-server/server/source-driver.d.ts +35 -0
- package/dist-server/server/source-driver.js +124 -0
- package/dist-server/server/spa.d.ts +2 -0
- package/dist-server/server/spa.js +51 -0
- package/dist-server/server/subscription-model-catalog.d.ts +27 -0
- package/dist-server/server/subscription-model-catalog.js +203 -0
- package/dist-server/server/turn.d.ts +249 -0
- package/dist-server/server/turn.js +1074 -0
- package/dist-server/server/wire-types.d.ts +799 -0
- package/dist-server/server/wire-types.js +1 -0
- package/dist-server/server/wren-home.d.ts +19 -0
- package/dist-server/server/wren-home.js +33 -0
- package/dist-server/server/wren-profiles.d.ts +43 -0
- package/dist-server/server/wren-profiles.js +123 -0
- package/package.json +93 -0
- package/profiles/genbi-default/context/binding.yml +9 -0
- package/profiles/genbi-default/ir.golden.json +506 -0
- package/profiles/genbi-default/profile.yml +16 -0
- package/profiles/genbi-enrich-context/components/apply_enrichment/component.yml +42 -0
- package/profiles/genbi-enrich-context/components/draft_enrichment/component.yml +30 -0
- package/profiles/genbi-enrich-context/components/draft_enrichment/steps/draft.md +34 -0
- package/profiles/genbi-enrich-context/components/inspect_context/component.yml +32 -0
- package/profiles/genbi-enrich-context/components/inspect_context/steps/inspect.md +13 -0
- package/profiles/genbi-enrich-context/context/binding.yml +4 -0
- package/profiles/genbi-enrich-context/ir.golden.json +302 -0
- package/profiles/genbi-enrich-context/profile.yml +15 -0
- package/profiles/genbi-monitor/context/binding.yml +7 -0
- package/profiles/genbi-monitor/ir.golden.json +188 -0
- package/profiles/genbi-monitor/profile.yml +27 -0
- package/profiles/genbi-setup/components/build_context/component.yml +44 -0
- package/profiles/genbi-setup/components/build_context/steps/build.md +14 -0
- package/profiles/genbi-setup/components/connect_source/component.yml +51 -0
- package/profiles/genbi-setup/components/connect_source/steps/connect.md +23 -0
- package/profiles/genbi-setup/context/binding.yml +8 -0
- package/profiles/genbi-setup/ir.golden.json +140 -0
- package/profiles/genbi-setup/profile.yml +18 -0
- package/profiles/genbi-setup/raw/schema.json +5 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { assertCapabilities } from "../capability/gate.js";
|
|
2
|
+
import { createAgentEventEmitter } from "../events/index.js";
|
|
3
|
+
import { createLocalExecutionEnv } from "../exec/index.js";
|
|
4
|
+
import { deriveEnforcement } from "../guardrails/index.js";
|
|
5
|
+
import { executeAgent, summarizeToolOutput } from "../loop/index.js";
|
|
6
|
+
import { getAllowedBlockTypes, renderEnvelope } from "../render/index.js";
|
|
7
|
+
import { BUILD_DASHBOARD_TOOL_NAME, WREN_QUERY_TOOL_NAME, withResolvedTools } from "../tools/index.js";
|
|
8
|
+
import { UnknownAgentError } from "./errors.js";
|
|
9
|
+
import { findLockedGatedCheck } from "./gate.js";
|
|
10
|
+
/**
|
|
11
|
+
* The full `answer_query` walking-skeleton entry point: capability gate
|
|
12
|
+
* (whole-bundle) -> resolve `agentId`'s tools -> run its dataflow
|
|
13
|
+
* steps with repair-fold -> synthesize the two-stage render
|
|
14
|
+
* envelope -> enforce any locked `gated_check` guardrail -> an
|
|
15
|
+
* explicit `AnswerResult | RefusalResult`.
|
|
16
|
+
*
|
|
17
|
+
* MCP clients opened while resolving `agentId`'s tools are always torn down
|
|
18
|
+
* (`withResolvedTools`'s `finally`), whether the run succeeds, the loop
|
|
19
|
+
* throws (e.g. `RepairExhaustedError`), or the render stage throws (e.g. a
|
|
20
|
+
* malformed model response failing `output_schema` validation) — in every
|
|
21
|
+
* failure case the error propagates out of `runAgent` rather than being
|
|
22
|
+
* swallowed into a refusal; only a *rendered but unverified* envelope
|
|
23
|
+
* becomes a `RefusalResult`.
|
|
24
|
+
*/
|
|
25
|
+
export async function runAgent(bundle, agentId, userInput, ctx) {
|
|
26
|
+
assertCapabilities(bundle, ctx.capabilityRegistry);
|
|
27
|
+
const agent = bundle.agents.find((candidate) => candidate.id === agentId);
|
|
28
|
+
if (!agent) {
|
|
29
|
+
throw new UnknownAgentError(agentId);
|
|
30
|
+
}
|
|
31
|
+
const emitter = createAgentEventEmitter(ctx.onEvent);
|
|
32
|
+
emitter.emit({ kind: "run.start", mode: "A", agentId });
|
|
33
|
+
try {
|
|
34
|
+
const enforcementPolicy = {
|
|
35
|
+
...deriveEnforcement(agent),
|
|
36
|
+
...(ctx.allowedHosts !== undefined ? { allowedHosts: ctx.allowedHosts } : {}),
|
|
37
|
+
};
|
|
38
|
+
const executionEnv = ctx.executionEnv ?? createLocalExecutionEnv();
|
|
39
|
+
const result = await withResolvedTools(agent, {
|
|
40
|
+
...(ctx.mcpServers !== undefined ? { mcpServers: ctx.mcpServers } : {}),
|
|
41
|
+
...(ctx.nativeTools !== undefined ? { nativeTools: ctx.nativeTools } : {}),
|
|
42
|
+
executionEnv,
|
|
43
|
+
enforcementPolicy,
|
|
44
|
+
}, async (tools) => {
|
|
45
|
+
// Track whether the run had at least one *successful*
|
|
46
|
+
// data-access tool call, independent of anything the render LLM
|
|
47
|
+
// claims about itself. `requiresDataAccess` is capability-aware — an
|
|
48
|
+
// agent that never declares a `sql_execution:*` capability (a
|
|
49
|
+
// pure-render/no-data component) never sets this, so it can never be
|
|
50
|
+
// forced to a refusal by this check.
|
|
51
|
+
let sawSuccessfulDataAccessCall = false;
|
|
52
|
+
const requiresDataAccess = agent.capabilities.some((entry) => entry.capability.startsWith("sql_execution:"));
|
|
53
|
+
// Deterministic table capture: the most recent *successful* `query`
|
|
54
|
+
// tool result, kept as a flat table payload (columns + positional
|
|
55
|
+
// rows + the SQL that produced it). This is the agent's real data —
|
|
56
|
+
// `renderEnvelope` builds the table/definition blocks straight from
|
|
57
|
+
// it instead of asking the render model to re-emit structure it tends
|
|
58
|
+
// to summarize away (leaving a bare `{type:"table"}`). The last
|
|
59
|
+
// success wins because, in the answer_query flow, it is the query
|
|
60
|
+
// whose rows are the answer.
|
|
61
|
+
let toolTable;
|
|
62
|
+
// Deterministic dashboard-envelope capture: the most recent
|
|
63
|
+
// *successful* `build_dashboard` tool result, kept as-is (it's
|
|
64
|
+
// already a complete, ordered `{blocks}` envelope — see
|
|
65
|
+
// `assembleDashboardBlocks`). Mirrors `toolTable`'s capture above;
|
|
66
|
+
// the last success wins for the same reason. `renderEnvelope` prefers
|
|
67
|
+
// this over `toolTable` (a dashboard's `{blocks}` result is strictly
|
|
68
|
+
// more complete than a single incidental `query` call's flat table).
|
|
69
|
+
let dashboardSeed;
|
|
70
|
+
// The most recent data-access tool error (`query` or
|
|
71
|
+
// `build_dashboard`), kept as a bounded summary so a non-gated
|
|
72
|
+
// data-block agent (e.g. `generate_dashboard`) that never grounded
|
|
73
|
+
// its answer can surface WHY, instead of rendering an empty,
|
|
74
|
+
// ungrounded "verified" envelope. Last error wins.
|
|
75
|
+
let lastDataAccessError;
|
|
76
|
+
// The FLOOR: accumulated straight from the same
|
|
77
|
+
// `onToolCallOutcome` firings the deterministic gate already consumes, zero new
|
|
78
|
+
// emission infrastructure. Terminal-only, flat, ordered by fire order.
|
|
79
|
+
const traceSteps = [];
|
|
80
|
+
let ordinal = 0;
|
|
81
|
+
const artifacts = await executeAgent(agent, {
|
|
82
|
+
binding: ctx.binding,
|
|
83
|
+
registry: ctx.registry,
|
|
84
|
+
tools,
|
|
85
|
+
userInput,
|
|
86
|
+
...(ctx.evaluateGuard !== undefined ? { evaluateGuard: ctx.evaluateGuard } : {}),
|
|
87
|
+
onToolCallOutcome: (outcome) => {
|
|
88
|
+
// Carry the tool's input and a compact detail (success
|
|
89
|
+
// summary, or the error message) so the UI's work-log can expand
|
|
90
|
+
// a step — especially useful for the SQL-repair retries, where
|
|
91
|
+
// an errored `query` step should show the SQL it tried and why
|
|
92
|
+
// it failed.
|
|
93
|
+
// Both success + error detail go through summarizeToolOutput so the
|
|
94
|
+
// persisted trace_json detail is bounded (200 chars) either way — an
|
|
95
|
+
// error string is returned as-is (no quoting) then truncated, matching
|
|
96
|
+
// the success path's cap rather than persisting an unbounded message.
|
|
97
|
+
const detail = outcome.outcome === "success"
|
|
98
|
+
? summarizeToolOutput(outcome.output)
|
|
99
|
+
: summarizeToolOutput(outcome.error);
|
|
100
|
+
traceSteps.push({
|
|
101
|
+
id: outcome.callId ?? `${outcome.tool}-${ordinal}`,
|
|
102
|
+
tool: outcome.tool,
|
|
103
|
+
outcome: outcome.outcome,
|
|
104
|
+
ordinal,
|
|
105
|
+
...(outcome.input !== undefined ? { input: outcome.input } : {}),
|
|
106
|
+
...(detail !== undefined ? { detail } : {}),
|
|
107
|
+
});
|
|
108
|
+
ordinal += 1;
|
|
109
|
+
if (outcome.tool === WREN_QUERY_TOOL_NAME && outcome.outcome === "success") {
|
|
110
|
+
sawSuccessfulDataAccessCall = true;
|
|
111
|
+
const seed = toToolTableSeed(outcome.output, outcome.input);
|
|
112
|
+
if (seed !== undefined)
|
|
113
|
+
toolTable = seed;
|
|
114
|
+
}
|
|
115
|
+
if (outcome.tool === BUILD_DASHBOARD_TOOL_NAME && outcome.outcome === "success") {
|
|
116
|
+
const seed = toDashboardSeed(outcome.output);
|
|
117
|
+
if (seed !== undefined)
|
|
118
|
+
dashboardSeed = seed;
|
|
119
|
+
}
|
|
120
|
+
if (outcome.outcome === "error" &&
|
|
121
|
+
(outcome.tool === WREN_QUERY_TOOL_NAME || outcome.tool === BUILD_DASHBOARD_TOOL_NAME)) {
|
|
122
|
+
lastDataAccessError = detail;
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
onEvent: emitter.emit,
|
|
126
|
+
});
|
|
127
|
+
const envelope = await renderEnvelope(agent, artifacts, {
|
|
128
|
+
binding: ctx.binding,
|
|
129
|
+
registry: ctx.registry,
|
|
130
|
+
userInput,
|
|
131
|
+
...(ctx.renderTier !== undefined ? { tier: ctx.renderTier } : {}),
|
|
132
|
+
...(toolTable !== undefined ? { toolTable } : {}),
|
|
133
|
+
...(dashboardSeed !== undefined ? { dashboardSeed } : {}),
|
|
134
|
+
});
|
|
135
|
+
const trace = { steps: traceSteps };
|
|
136
|
+
const gatedCheck = findLockedGatedCheck(agent);
|
|
137
|
+
// Earn `verified` from a real
|
|
138
|
+
// successful data-access instead of relying solely on the render LLM's
|
|
139
|
+
// self-attestation. A successful, non-erroring `query` (or, for a
|
|
140
|
+
// dashboard, `build_dashboard`) execution IS the evidence the
|
|
141
|
+
// deterministic gate requires; the render LLM (used for narrative-only
|
|
142
|
+
// agents like `explain_change`) does not reliably self-attest
|
|
143
|
+
// `verified: true`, so without this, a genuine successful query still
|
|
144
|
+
// produced a spurious refusal. Agents with no successful data-access
|
|
145
|
+
// call are unaffected — they still depend on (and can still fail) the
|
|
146
|
+
// render LLM's self-attestation below.
|
|
147
|
+
//
|
|
148
|
+
// CAVEAT (accepted trade-off, single-query agents only, see
|
|
149
|
+
// `isDashboardShaped` below): this is scoped to "the run had ANY
|
|
150
|
+
// successful `query` call", so it overrides a render LLM's
|
|
151
|
+
// `verified: false` regardless of WHY the model set it — including a
|
|
152
|
+
// legitimate self-doubt that the answer doesn't match the data, not
|
|
153
|
+
// only fabrication. The product meaning of `verified` here is
|
|
154
|
+
// therefore "grounded in a real, successful data-access", not "the
|
|
155
|
+
// model certifies its own interpretation".
|
|
156
|
+
//
|
|
157
|
+
// A DASHBOARD-SHAPED agent (`kpi_card`/`chart` permitted by
|
|
158
|
+
// `output_schema` — i.e. `generate_dashboard`'s multi-panel,
|
|
159
|
+
// `build_dashboard`-mediated assembly) is different in kind from a
|
|
160
|
+
// single-query agent: it fans out to several independent panel
|
|
161
|
+
// queries, so "any successful query somewhere in the run" says
|
|
162
|
+
// nothing about whether the OTHER panels — or the dashboard assembly
|
|
163
|
+
// itself — actually succeeded. A run where every panel query failed
|
|
164
|
+
// but one incidental prior query succeeded must not be marked
|
|
165
|
+
// verified with an empty/fabricated dashboard. Grounding for a
|
|
166
|
+
// dashboard-shaped answer therefore requires the seed that actually
|
|
167
|
+
// IS the answer: `dashboardSeed` (paired with a real successful
|
|
168
|
+
// query, so a `build_dashboard` call carrying only fabricated panel
|
|
169
|
+
// data doesn't count).
|
|
170
|
+
//
|
|
171
|
+
// Every other shape (table-only `answer_query`, narrative
|
|
172
|
+
// `explain_change`, unconstrained `explore_model`) has exactly one
|
|
173
|
+
// data-producing step, not several independent ones a dashboard fans
|
|
174
|
+
// out to — so for those, "any real successful data-access call" is
|
|
175
|
+
// sufficient evidence and keeps the original grounding rule.
|
|
176
|
+
//
|
|
177
|
+
// NOTE (deviation from the literal spec): the spec's literal
|
|
178
|
+
// `seedBackedVerified` formula additionally credited "table-only
|
|
179
|
+
// schema AND a captured `toolTable` seed". `toolTable` is populated
|
|
180
|
+
// from `query`'s raw tool-result `output`, but `query` is resolved as
|
|
181
|
+
// an MCP tool, whose tool-result `output` is the full MCP
|
|
182
|
+
// `CallToolResult` (`{content, structuredContent, isError}`) — not the
|
|
183
|
+
// flat `{columns, rows}` shape `toToolTableSeed` expects — so
|
|
184
|
+
// `toolTable` is always `undefined` in practice for any real
|
|
185
|
+
// MCP-backed `query` call, making that clause dead code today. Fixing
|
|
186
|
+
// the unwrap was tried, but `toolTable` is *also* fed straight into
|
|
187
|
+
// `renderEnvelope`'s own seed precedence (ranked above the
|
|
188
|
+
// direct-artifact scan), so making it populate changed `answer_query`'s
|
|
189
|
+
// rendered *content* (the raw, un-curated query dump winning over
|
|
190
|
+
// `generate_sql`'s actual curated answer) — a regression outside this
|
|
191
|
+
// fix's scope. The `isDashboardShaped` split below achieves the
|
|
192
|
+
// same fix intent (stop trusting an incidental query for a
|
|
193
|
+
// multi-panel dashboard) without that side effect.
|
|
194
|
+
const outputSchema = agent.output_schema;
|
|
195
|
+
const DATA_BLOCK_TYPES = new Set(["table", "kpi_card", "chart"]);
|
|
196
|
+
const DASHBOARD_ONLY_BLOCK_TYPES = new Set(["kpi_card", "chart"]);
|
|
197
|
+
const allowedBlockTypes = getAllowedBlockTypes(outputSchema);
|
|
198
|
+
const producesDataBlocks = allowedBlockTypes !== undefined && [...allowedBlockTypes].some((t) => DATA_BLOCK_TYPES.has(t));
|
|
199
|
+
const isDashboardShaped = allowedBlockTypes !== undefined && [...allowedBlockTypes].some((t) => DASHBOARD_ONLY_BLOCK_TYPES.has(t));
|
|
200
|
+
const seedBackedVerified = isDashboardShaped
|
|
201
|
+
? dashboardSeed !== undefined && sawSuccessfulDataAccessCall
|
|
202
|
+
: requiresDataAccess && sawSuccessfulDataAccessCall;
|
|
203
|
+
const earnedVerified = seedBackedVerified;
|
|
204
|
+
// Only ever ADD verified:true when earned — never force verified:false
|
|
205
|
+
// onto an unearned envelope. The deterministic gates below (both the
|
|
206
|
+
// pre-existing `gatedCheck` checks and the dashboard-grounding branch) decide
|
|
207
|
+
// solely from `seedBackedVerified`/`sawSuccessfulDataAccessCall`, not
|
|
208
|
+
// from `effectiveEnvelope.verified` — so forcing it false here would
|
|
209
|
+
// only rob a refusal's returned envelope of the model's own (possibly
|
|
210
|
+
// fabricated) self-attestation, which the test explicitly wants
|
|
211
|
+
// preserved as evidence the gate doesn't trust it.
|
|
212
|
+
const effectiveEnvelope = earnedVerified ? { ...envelope, verified: true } : envelope;
|
|
213
|
+
if (gatedCheck && effectiveEnvelope.verified !== true) {
|
|
214
|
+
return {
|
|
215
|
+
kind: "refusal",
|
|
216
|
+
reason: `agent "${agentId}" has a locked gated_check guardrail, but the rendered envelope's ` +
|
|
217
|
+
`"verified" field was ${JSON.stringify(effectiveEnvelope.verified)} (must be exactly true)`,
|
|
218
|
+
envelope: effectiveEnvelope,
|
|
219
|
+
trace,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
// Deterministic gate invariant: a locked `gated_check`
|
|
223
|
+
// guardrail must never be satisfied by the render LLM's self-attested
|
|
224
|
+
// `verified: true` alone when the agent declares a data-access
|
|
225
|
+
// capability (`sql_execution:*`, i.e. it needs the `query` tool to
|
|
226
|
+
// answer honestly). A fabricating or confused model can assert
|
|
227
|
+
// `verified: true` on a run where the `query` tool was never called,
|
|
228
|
+
// or was called and only ever errored (including a
|
|
229
|
+
// missing `wren` binary — every call throws, so zero successes are
|
|
230
|
+
// recorded and this branch correctly forces a refusal instead of
|
|
231
|
+
// trusting a possibly-fabricated `verified: true`). This check is
|
|
232
|
+
// purely additive to the one above: honest runs that have a real
|
|
233
|
+
// successful `query` call keep behaving exactly as before.
|
|
234
|
+
if (gatedCheck && requiresDataAccess && !sawSuccessfulDataAccessCall) {
|
|
235
|
+
return {
|
|
236
|
+
kind: "refusal",
|
|
237
|
+
reason: `agent "${agentId}" has a locked gated_check guardrail and declares a data-access ` +
|
|
238
|
+
`capability ("sql_execution:*"), but the run had zero successful "${WREN_QUERY_TOOL_NAME}" ` +
|
|
239
|
+
`tool calls — a self-attested "verified: true" cannot stand without at least one real, ` +
|
|
240
|
+
`non-erroring data-access call (deterministic gate)`,
|
|
241
|
+
envelope: effectiveEnvelope,
|
|
242
|
+
trace,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
// A NON-gated data-block agent (e.g. generate_dashboard)
|
|
246
|
+
// that requires data access but produced no real answer-data seed
|
|
247
|
+
// never actually grounded its answer (build_dashboard was never
|
|
248
|
+
// assembled, or every panel query failed). Rather than present an
|
|
249
|
+
// empty, ungrounded envelope as an "answer", surface WHY — the last
|
|
250
|
+
// data-access error — as a refusal. Gated agents (answer_query,
|
|
251
|
+
// explain_change) are unaffected: their two refusal blocks above
|
|
252
|
+
// already handle the unverified case.
|
|
253
|
+
if (!gatedCheck && producesDataBlocks && requiresDataAccess && !seedBackedVerified) {
|
|
254
|
+
return {
|
|
255
|
+
kind: "refusal",
|
|
256
|
+
reason: lastDataAccessError
|
|
257
|
+
? `Couldn't build a grounded result — the data query failed: ${lastDataAccessError}`
|
|
258
|
+
: `Couldn't build a grounded result — the queried data was not assembled into an answer`,
|
|
259
|
+
envelope: effectiveEnvelope,
|
|
260
|
+
trace,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
return { kind: "answer", envelope: effectiveEnvelope, trace };
|
|
264
|
+
});
|
|
265
|
+
if (result.kind === "answer") {
|
|
266
|
+
emitter.emit({ kind: "answer", envelope: result.envelope });
|
|
267
|
+
emitter.emit({ kind: "run.finish", status: "answer" });
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
emitter.emit({ kind: "refusal", reason: result.reason, envelope: result.envelope });
|
|
271
|
+
emitter.emit({ kind: "run.finish", status: "refusal" });
|
|
272
|
+
}
|
|
273
|
+
return result;
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
emitter.emit({ kind: "error", message: describeRunError(error) });
|
|
277
|
+
emitter.emit({ kind: "run.finish", status: "error" });
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function describeRunError(error) {
|
|
282
|
+
if (error instanceof Error)
|
|
283
|
+
return error.message;
|
|
284
|
+
return typeof error === "string" ? error : JSON.stringify(error);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Reshapes a successful `query` tool call into a {@link ToolTableSeed}: the
|
|
288
|
+
* tool returns `{columns, rows}` with rows as column-keyed objects, but the
|
|
289
|
+
* render table block wants positional rows aligned to `columns`. Returns
|
|
290
|
+
* `undefined` for any output that isn't that shape (so a non-table tool, or a
|
|
291
|
+
* malformed result, simply falls back to the normal render path). The SQL, if
|
|
292
|
+
* the tool call's input carried it, becomes the definition block's `sql`.
|
|
293
|
+
*/
|
|
294
|
+
function toToolTableSeed(output, input) {
|
|
295
|
+
if (typeof output !== "object" || output === null)
|
|
296
|
+
return undefined;
|
|
297
|
+
const { columns, rows } = output;
|
|
298
|
+
if (!Array.isArray(columns) || !Array.isArray(rows))
|
|
299
|
+
return undefined;
|
|
300
|
+
if (!columns.every((c) => typeof c === "string"))
|
|
301
|
+
return undefined;
|
|
302
|
+
const positionalRows = rows.map((row) => row !== null && typeof row === "object"
|
|
303
|
+
? columns.map((col) => row[col])
|
|
304
|
+
: [row]);
|
|
305
|
+
const sql = typeof input === "object" && input !== null && typeof input.sql === "string"
|
|
306
|
+
? input.sql
|
|
307
|
+
: undefined;
|
|
308
|
+
return {
|
|
309
|
+
columns: columns,
|
|
310
|
+
rows: positionalRows,
|
|
311
|
+
...(sql !== undefined ? { sql } : {}),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Reshapes a successful `build_dashboard` tool call into a
|
|
316
|
+
* {@link DashboardSeed}: the tool already returns `{blocks, summary?,
|
|
317
|
+
* verified}` (see `assembleDashboardBlocks`), so this just narrows/validates
|
|
318
|
+
* that shape defensively. Returns `undefined` for any output that isn't at
|
|
319
|
+
* least `{blocks: array}` (so a malformed result simply falls back to the
|
|
320
|
+
* normal render path instead of seeding garbage).
|
|
321
|
+
*/
|
|
322
|
+
function toDashboardSeed(output) {
|
|
323
|
+
if (typeof output !== "object" || output === null)
|
|
324
|
+
return undefined;
|
|
325
|
+
const { blocks, summary, verified } = output;
|
|
326
|
+
if (!Array.isArray(blocks))
|
|
327
|
+
return undefined;
|
|
328
|
+
return {
|
|
329
|
+
blocks,
|
|
330
|
+
...(typeof summary === "string" ? { summary } : {}),
|
|
331
|
+
...(typeof verified === "boolean" ? { verified } : {}),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { StepTrace } from "../events/index.js";
|
|
2
|
+
import type { RenderEnvelope } from "../render/index.js";
|
|
3
|
+
/** A normal answer: the envelope satisfied every `gated_check` guardrail (or the agent has none). */
|
|
4
|
+
export interface AnswerResult {
|
|
5
|
+
readonly kind: "answer";
|
|
6
|
+
readonly envelope: RenderEnvelope;
|
|
7
|
+
/**
|
|
8
|
+
* The FLOOR. Always present for a in-process run (`runAgent`
|
|
9
|
+
* always accumulates one, even with no `onEvent` sink attached). Dispatched
|
|
10
|
+
* carries no trace at all — it isn't a `RunAgentResult`, so this field
|
|
11
|
+
* doesn't apply there (see `route/dispatched.ts`'s doc comment for the gap).
|
|
12
|
+
*/
|
|
13
|
+
readonly trace?: StepTrace;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A refusal: the agent has a `locked` `gated_check` guardrail and the
|
|
17
|
+
* rendered envelope's `verified` field was not `true`. The envelope is
|
|
18
|
+
* still attached (it's often useful for diagnostics), but callers must
|
|
19
|
+
* branch on `kind` before treating it as a deliverable answer.
|
|
20
|
+
*/
|
|
21
|
+
export interface RefusalResult {
|
|
22
|
+
readonly kind: "refusal";
|
|
23
|
+
readonly reason: string;
|
|
24
|
+
readonly envelope: RenderEnvelope;
|
|
25
|
+
/** See {@link AnswerResult.trace}. */
|
|
26
|
+
readonly trace?: StepTrace;
|
|
27
|
+
}
|
|
28
|
+
/** `runAgent`'s result: model refusal vs. answer as an explicit, exhaustive union. */
|
|
29
|
+
export type RunAgentResult = AnswerResult | RefusalResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AgentEventInput } from "../events/index.js";
|
|
2
|
+
export declare class CodexSetupEventMapper {
|
|
3
|
+
private readonly tracePath?;
|
|
4
|
+
private readonly tools;
|
|
5
|
+
private activeStep;
|
|
6
|
+
private finalText;
|
|
7
|
+
private traceCursor;
|
|
8
|
+
constructor(tracePath?: string | undefined);
|
|
9
|
+
nextLine(line: string): AgentEventInput | undefined;
|
|
10
|
+
result(): string;
|
|
11
|
+
private nextTraceRecord;
|
|
12
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
export class CodexSetupEventMapper {
|
|
3
|
+
tracePath;
|
|
4
|
+
tools = new Map();
|
|
5
|
+
activeStep = "";
|
|
6
|
+
finalText;
|
|
7
|
+
traceCursor = 0;
|
|
8
|
+
constructor(tracePath) {
|
|
9
|
+
this.tracePath = tracePath;
|
|
10
|
+
}
|
|
11
|
+
nextLine(line) {
|
|
12
|
+
let event;
|
|
13
|
+
try {
|
|
14
|
+
event = JSON.parse(line);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
throw new Error("warble-codex-local emitted non-JSON output");
|
|
18
|
+
}
|
|
19
|
+
switch (event.t) {
|
|
20
|
+
case "step_start":
|
|
21
|
+
this.activeStep = event.id;
|
|
22
|
+
return { kind: "step.start", stepId: event.id, name: event.name, tier: "strong", depth: 0 };
|
|
23
|
+
case "tool_call":
|
|
24
|
+
if (!this.activeStep)
|
|
25
|
+
throw new Error("Codex tool call arrived before step start");
|
|
26
|
+
this.tools.set(event.id, { stepId: this.activeStep, name: event.name });
|
|
27
|
+
return {
|
|
28
|
+
kind: "tool.call",
|
|
29
|
+
stepId: this.activeStep,
|
|
30
|
+
callId: event.id,
|
|
31
|
+
tool: event.name,
|
|
32
|
+
depth: 0,
|
|
33
|
+
status: "running",
|
|
34
|
+
};
|
|
35
|
+
case "tool_result": {
|
|
36
|
+
const tool = this.tools.get(event.id);
|
|
37
|
+
if (!tool)
|
|
38
|
+
throw new Error("Codex tool result arrived without a matching call");
|
|
39
|
+
this.tools.delete(event.id);
|
|
40
|
+
const trace = this.nextTraceRecord();
|
|
41
|
+
return {
|
|
42
|
+
kind: "tool.result",
|
|
43
|
+
stepId: tool.stepId,
|
|
44
|
+
callId: event.id,
|
|
45
|
+
tool: tool.name,
|
|
46
|
+
status: event.ok ? "success" : "error",
|
|
47
|
+
...(trace ? { input: trace.input } : {}),
|
|
48
|
+
...(event.ok && trace ? { summary: trace.detail } : {}),
|
|
49
|
+
...(event.error ? { error: event.error } : {}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
case "answer":
|
|
53
|
+
this.finalText = event.text;
|
|
54
|
+
return { kind: "answer", text: event.text };
|
|
55
|
+
case "step_finish":
|
|
56
|
+
return {
|
|
57
|
+
kind: "step.finish",
|
|
58
|
+
stepId: event.id,
|
|
59
|
+
name: event.id,
|
|
60
|
+
status: event.ok ? "ok" : "error",
|
|
61
|
+
...(event.detail ? { detail: event.detail } : {}),
|
|
62
|
+
};
|
|
63
|
+
default:
|
|
64
|
+
throw new Error("warble-codex-local emitted an unknown event");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
result() {
|
|
68
|
+
if (this.tools.size > 0)
|
|
69
|
+
throw new Error("warble-codex-local ended with an unfinished tool call");
|
|
70
|
+
if (this.finalText === undefined || this.finalText.trim().length === 0) {
|
|
71
|
+
throw new Error("warble-codex-local ended without a final answer");
|
|
72
|
+
}
|
|
73
|
+
return this.finalText;
|
|
74
|
+
}
|
|
75
|
+
nextTraceRecord() {
|
|
76
|
+
if (!this.tracePath)
|
|
77
|
+
return undefined;
|
|
78
|
+
const lines = readFileSync(this.tracePath, "utf8").split("\n").filter((line) => line.trim().length > 0);
|
|
79
|
+
const line = lines[this.traceCursor];
|
|
80
|
+
if (line === undefined)
|
|
81
|
+
throw new Error("Codex setup MCP completed without a matching bridge trace record");
|
|
82
|
+
this.traceCursor += 1;
|
|
83
|
+
let value;
|
|
84
|
+
try {
|
|
85
|
+
value = JSON.parse(line);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
throw new Error("Codex setup MCP wrote an invalid bridge trace record");
|
|
89
|
+
}
|
|
90
|
+
if (!isTraceRecord(value))
|
|
91
|
+
throw new Error("Codex setup MCP wrote a malformed bridge trace record");
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function isTraceRecord(value) {
|
|
96
|
+
if (typeof value !== "object" || value === null)
|
|
97
|
+
return false;
|
|
98
|
+
const record = value;
|
|
99
|
+
if (typeof record["detail"] !== "string")
|
|
100
|
+
return false;
|
|
101
|
+
const input = record["input"];
|
|
102
|
+
if (typeof input !== "object" || input === null)
|
|
103
|
+
return false;
|
|
104
|
+
const fields = input;
|
|
105
|
+
if (fields["action"] !== "exec" && fields["action"] !== "write")
|
|
106
|
+
return false;
|
|
107
|
+
return ["command", "cwd", "path"].every((key) => fields[key] === undefined || typeof fields[key] === "string");
|
|
108
|
+
}
|