@inflexa-ai/harness 0.24.0 → 0.26.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.
Files changed (85) hide show
  1. package/dist/agents/sandbox/__fixtures__/deps.js +6 -0
  2. package/dist/agents/sandbox/data-profiler.js +5 -4
  3. package/dist/agents/sandbox/shared.js +11 -0
  4. package/dist/agents/sandbox/types.d.ts +1 -1
  5. package/dist/app/data-profile-orientation.js +30 -1
  6. package/dist/contracts/data-profile.d.ts +166 -0
  7. package/dist/contracts/index.d.ts +1 -0
  8. package/dist/execution/input-signature.d.ts +33 -0
  9. package/dist/execution/input-signature.js +0 -0
  10. package/dist/index.d.ts +5 -2
  11. package/dist/index.js +10 -3
  12. package/dist/input-scan/coverage.d.ts +22 -0
  13. package/dist/input-scan/coverage.js +85 -0
  14. package/dist/input-scan/enrich.d.ts +41 -0
  15. package/dist/input-scan/enrich.js +227 -0
  16. package/dist/input-scan/formats.d.ts +41 -0
  17. package/dist/input-scan/formats.js +279 -0
  18. package/dist/input-scan/index.d.ts +12 -0
  19. package/dist/input-scan/index.js +11 -0
  20. package/dist/input-scan/scan.d.ts +50 -0
  21. package/dist/input-scan/scan.js +182 -0
  22. package/dist/input-scan/shapes.d.ts +46 -0
  23. package/dist/input-scan/shapes.js +0 -0
  24. package/dist/input-scan/types.d.ts +172 -0
  25. package/dist/input-scan/types.js +12 -0
  26. package/dist/lib/page-capture.d.ts +44 -15
  27. package/dist/lib/page-capture.js +78 -19
  28. package/dist/loop/run-agent.js +36 -19
  29. package/dist/memory/card-builders.d.ts +4 -6
  30. package/dist/memory/card-builders.js +4 -6
  31. package/dist/memory/conversation-display-replay.d.ts +7 -6
  32. package/dist/memory/conversation-display-replay.js +7 -6
  33. package/dist/memory/conversation-display-storage.d.ts +2 -10
  34. package/dist/memory/conversation-display-storage.js +79 -32
  35. package/dist/memory/thread-history.d.ts +31 -20
  36. package/dist/memory/thread-history.js +45 -45
  37. package/dist/prompts/report-session.d.ts +1 -1
  38. package/dist/prompts/report-session.js +22 -13
  39. package/dist/prompts/sandbox/data-profiler.d.ts +1 -1
  40. package/dist/prompts/sandbox/data-profiler.js +159 -275
  41. package/dist/provenance/exec-frame.js +9 -3
  42. package/dist/runtime/boot.d.ts +7 -8
  43. package/dist/runtime/boot.js +7 -19
  44. package/dist/sandbox/client.d.ts +7 -0
  45. package/dist/sandbox/create-sandbox.js +1 -0
  46. package/dist/sandbox/docker-client.d.ts +1 -0
  47. package/dist/sandbox/docker-client.js +20 -15
  48. package/dist/sandbox/k8s-client.d.ts +6 -5
  49. package/dist/sandbox/k8s-client.js +55 -39
  50. package/dist/sandbox/reaper.d.ts +24 -13
  51. package/dist/sandbox/reaper.js +47 -14
  52. package/dist/sandbox/types.d.ts +6 -5
  53. package/dist/schemas/data-profile-schemas.d.ts +62 -0
  54. package/dist/schemas/data-profile-schemas.js +68 -2
  55. package/dist/state/data-profile.d.ts +7 -88
  56. package/dist/state/index.d.ts +1 -1
  57. package/dist/tasks/data-profile-activity.d.ts +8 -0
  58. package/dist/tasks/data-profile-activity.js +1 -0
  59. package/dist/tasks/data-profile-index.d.ts +45 -0
  60. package/dist/tasks/data-profile-index.js +134 -0
  61. package/dist/tasks/data-profile.d.ts +8 -23
  62. package/dist/tasks/data-profile.js +150 -117
  63. package/dist/tools/define-tool.d.ts +19 -8
  64. package/dist/tools/define-tool.js +28 -12
  65. package/dist/tools/report-session/examine-page.d.ts +22 -13
  66. package/dist/tools/report-session/examine-page.js +15 -10
  67. package/dist/tools/report-session/index.d.ts +1 -1
  68. package/dist/tools/research/generate-plan.js +3 -8
  69. package/dist/tools/research/inspect-data-profile.d.ts +30 -4
  70. package/dist/tools/research/inspect-data-profile.js +75 -20
  71. package/dist/tools/workspace/scan-inputs.d.ts +38 -0
  72. package/dist/tools/workspace/scan-inputs.js +61 -0
  73. package/dist/tools/workspace/workspace-search.d.ts +1 -1
  74. package/dist/tools/workspace/workspace-search.js +12 -9
  75. package/dist/workspace/filesystem.d.ts +27 -0
  76. package/dist/workspace/filesystem.js +27 -0
  77. package/package.json +1 -1
  78. package/dist/app/data-profile-policy.d.ts +0 -36
  79. package/dist/app/data-profile-policy.js +0 -37
  80. package/dist/memory/content-to-cortex.d.ts +0 -75
  81. package/dist/memory/content-to-cortex.js +0 -201
  82. package/dist/memory/conversation-display-backfill.d.ts +0 -23
  83. package/dist/memory/conversation-display-backfill.js +0 -91
  84. package/dist/memory/reconstruct-cards.d.ts +0 -27
  85. package/dist/memory/reconstruct-cards.js +0 -57
@@ -1,201 +0,0 @@
1
- /**
2
- * The MIGRATION renderer: stored AI SDK model messages → `CortexMessage[]`.
3
- *
4
- * This is how a conversation written before display projections were persisted
5
- * is rendered ONCE, at startup, so it can be frozen into a display envelope
6
- * (`conversation-display-backfill.ts`). It is not a runtime read path — the
7
- * runtime reads the stored projection (`conversation-display-replay.ts`) and
8
- * never consults a tool name, a card builder, or the filesystem.
9
- *
10
- * Everything it does is inference from a transcript that was never meant to
11
- * carry display, which is why it belongs to the migration and nothing else:
12
- *
13
- * - text part → text part
14
- * - tool-call part → a reconstructed display card (`data-plan`,
15
- * `data-presentation`) when `resolveCard` recognises
16
- * the tool, else a generic tool-call part whose detail
17
- * `resolveDetail` recomputes from the persisted input
18
- * - everything else → dropped (reasoning, tool-result, etc.) — the UI
19
- * does not render them
20
- *
21
- * A `tool-result` block is not rendered, but it is not ignored either: it is the
22
- * only record of how its call ended. The blocks are indexed by `toolCallId`
23
- * before conversion so each tool-call part can report its own outcome — without
24
- * that pass every migrated call would report success, including the failed ones.
25
- *
26
- * A row that yields no renderable parts (e.g. a `tool`-role message carrying
27
- * only tool-result continuation parts) is omitted entirely, so the rendered
28
- * conversation has no empty bubbles. Storage is never mutated.
29
- */
30
- import { toolOutcomeForOutputType } from "../loop/tool-outcome.js";
31
- import { isInterruptedMessage, isSyntheticRecordMessage, isSyntheticUserMessage } from "./ai-sdk-message-storage.js";
32
- /**
33
- * How each stored call ended, keyed by tool-call id.
34
- *
35
- * A whole page is indexed before conversion because the pairing crosses rows: a
36
- * call rides an `assistant` row and its result rides the `tool` row after it.
37
- */
38
- function indexOutcomes(messages) {
39
- const outcomes = new Map();
40
- for (const { message } of messages) {
41
- const content = message.content;
42
- if (typeof content === "string")
43
- continue;
44
- for (const block of content) {
45
- if (block.type !== "tool-result")
46
- continue;
47
- outcomes.set(block.toolCallId, toolOutcomeForOutputType(block.output.type));
48
- }
49
- }
50
- return outcomes;
51
- }
52
- function genericToolCall(block, outcomes, resolveDetail) {
53
- // No paired result is the shape of a transcript whose `tool` row was never
54
- // appended — an aborted turn. That is not a success: the call was dispatched and
55
- // never completed, which is exactly `incomplete`. Reporting `ok` would claim a
56
- // result the tool never returned, and it is the one outcome the transcript
57
- // genuinely cannot supply, so it is named rather than guessed at.
58
- const outcome = outcomes.get(block.toolCallId) ?? "incomplete";
59
- const detail = resolveDetail?.(block.toolName, block.input);
60
- return {
61
- type: "tool-call",
62
- toolCallId: block.toolCallId,
63
- toolName: block.toolName,
64
- outcome,
65
- ...(detail === undefined ? {} : { detail }),
66
- };
67
- }
68
- async function blockToPart(block, outcomes, options) {
69
- if (block.type === "text")
70
- return { type: "text", text: block.text };
71
- if (block.type === "tool-call") {
72
- const card = options.resolveCard
73
- ? await options.resolveCard({ type: "tool_use", id: block.toolCallId, name: block.toolName, input: block.input })
74
- : null;
75
- return card ?? genericToolCall(block, outcomes, options.resolveDetail);
76
- }
77
- // reasoning, tool_result, file, and any other block — not rendered.
78
- return null;
79
- }
80
- async function rowToParts(message, outcomes, options) {
81
- const content = message.content;
82
- if (typeof content === "string") {
83
- return content.length > 0 ? [{ type: "text", text: content }] : [];
84
- }
85
- const parts = [];
86
- for (const block of content) {
87
- const part = await blockToPart(block, outcomes, options);
88
- if (part)
89
- parts.push(part);
90
- }
91
- return parts;
92
- }
93
- /**
94
- * Map stored messages (oldest-first) to `CortexMessage[]`. Rows that produce
95
- * no renderable parts are dropped, and consecutive assistant rows are coalesced
96
- * into one message. The first row's `seq` becomes the stable message id.
97
- * `options.resolveCard` reconstructs display cards from `tool_use` blocks and
98
- * `options.resolveDetail` reconstructs each call's one-line detail; both are
99
- * optional and each tool-call part reports its outcome either way.
100
- * A row carrying the interruption marker sets `interrupted: true` on the message
101
- * it lands in, including when coalesced into an assistant run; a row carrying a
102
- * stored turn rollup sets `usage` the same way. A row that carries a stored turn
103
- * duration sets `durationMs` the same way too. Thus a reloaded conversation shows
104
- * the cost and the time that the live turn showed, with no second query.
105
- *
106
- * Coalescing mirrors the live SSE shape. The agent loop persists each assistant
107
- * step (typically a single `tool_use`) as its own row, and the `tool_result`
108
- * `user` rows between them drop to zero renderable parts above — so one assistant
109
- * turn is stored as a run of adjacent assistant rows. The live chat stream
110
- * accumulates that whole turn into a single assistant message whose `parts` hold
111
- * every step; reconstructing it row-by-row instead would emit one message per
112
- * tool call, which the UI renders as a stack of single-tool boxes. Merging the
113
- * run restores the one-bubble-per-turn shape (and lets the UI collapse the tool
114
- * calls into one group) regardless of how the loop split the turn into rows.
115
- *
116
- * Coalescing is assistant-only. User rows never legitimately split into a run —
117
- * adjacent `user` rows arise only from turns that persisted no reply (an aborted
118
- * turn's lone user message followed by the next turn's), and merging them would
119
- * fabricate one bubble from two messages the user sent separately.
120
- */
121
- export async function contentToCortexMessages(messages, options = {}) {
122
- const outcomes = indexOutcomes(messages);
123
- const out = [];
124
- for (const message of messages) {
125
- // The loop's truncation nudge carries the `user` role only for the wire
126
- // format; rendering it would show words the user never typed, so drop it.
127
- // Skipping first is safe: a synthetic user message can never carry the
128
- // interruption marker — that rides assistant rows — so this cannot interact
129
- // with the marker-fold logic below. The assistant rows it separated then
130
- // become adjacent and coalesce into one bubble, which is the shape we want.
131
- //
132
- // A host-appended RECORD is the exception, and the distinction is exactly why the
133
- // two markers exist separately: it is equally synthetic for every turn-boundary
134
- // reader, but it is a fact the reader is entitled to see — the only trace in the
135
- // conversation that out-of-band work happened at all. Dropping it here would make
136
- // an embedder's durable record write successfully and then be invisible, with
137
- // nothing to indicate why.
138
- const isRecord = isSyntheticRecordMessage(message.message);
139
- if (isSyntheticUserMessage(message.message) && !isRecord)
140
- continue;
141
- const parts = await rowToParts(message.message, outcomes, options);
142
- // The marker is read before the zero-parts drop so the flag can never be lost to it:
143
- // a marked row carries an interruption fact regardless of whether it renders any parts.
144
- // When such a row contributes no bubble of its own, the interruption belongs to the
145
- // assistant run it trailed, so fold the flag onto the previous emitted assistant.
146
- const interrupted = isInterruptedMessage(message.message);
147
- if (parts.length === 0) {
148
- // The stored rollup of a row and its stored duration are facts about the turn,
149
- // and not about what the row renders. Thus the read of both comes before the
150
- // zero-parts drop, exactly as the read of the interruption marker does. The
151
- // fold then puts them on the assistant run that they trailed. Without the fold,
152
- // a turn whose last assistant row held unrendered blocks alone would lose the
153
- // two figures to the drop. That loss is the one that the stored figures end.
154
- const prev = out[out.length - 1];
155
- if (prev && prev.role === "assistant") {
156
- if (interrupted)
157
- prev.interrupted = true;
158
- if (message.usage)
159
- prev.usage = message.usage;
160
- if (message.durationMs !== undefined)
161
- prev.durationMs = message.durationMs;
162
- }
163
- continue;
164
- }
165
- // A record carries the `user` role for the wire format and for the turn-boundary
166
- // predicates, but nobody said it. Emitting it AS `user` would attribute
167
- // system-authored text to the reader — a lie about who spoke, and one that would
168
- // also mislead them about what they can retract. `system` is what it actually is.
169
- const role = isRecord
170
- ? "system"
171
- : message.message.role === "tool"
172
- ? "assistant"
173
- : message.message.role;
174
- const prev = out[out.length - 1];
175
- if (prev && prev.role === role && role === "assistant") {
176
- prev.parts.push(...parts);
177
- if (interrupted)
178
- prev.interrupted = true;
179
- // Coalescing rebuilds the one-bubble-per-turn shape, and the two figures ride
180
- // the LAST assistant row of the turn. Thus the run keeps the figures of
181
- // whichever row carried them. An assignment, and not a merge, is deliberate:
182
- // two rollups in one run would mean that coalescing merged two turns, and it
183
- // never does. The duration reads against `undefined`, because a measured zero
184
- // is a figure and an absent value alone means that nobody measured the turn.
185
- if (message.usage)
186
- prev.usage = message.usage;
187
- if (message.durationMs !== undefined)
188
- prev.durationMs = message.durationMs;
189
- continue;
190
- }
191
- const cortex = { id: String(message.seq), role, parts };
192
- if (interrupted)
193
- cortex.interrupted = true;
194
- if (message.usage)
195
- cortex.usage = message.usage;
196
- if (message.durationMs !== undefined)
197
- cortex.durationMs = message.durationMs;
198
- out.push(cortex);
199
- }
200
- return out;
201
- }
@@ -1,23 +0,0 @@
1
- import type { Pool } from "pg";
2
- import type { Tool } from "../tools/define-tool.js";
3
- export interface ConversationDisplayBackfillDeps {
4
- readonly pool: Pool;
5
- /**
6
- * The conversation agent's composed roster, used to recover each legacy call's
7
- * one-line detail from its persisted input. Tools contributed through the
8
- * host-tools seam are invisible to any map the harness holds on its own, so the
9
- * list has to come from the assembled runtime.
10
- */
11
- readonly tools: readonly Tool[];
12
- readonly batchSize?: number;
13
- }
14
- /**
15
- * Freeze the migration renderer's output into display envelopes, once per legacy
16
- * turn, so the runtime read path never has to reconstruct anything.
17
- *
18
- * Missing mutable resources degrade to a generic, cardless display — a plan
19
- * that is absent is normal historical absence, not a fault. Malformed stored rows
20
- * and database faults remain startup-fatal: a turn that cannot be migrated must
21
- * not be silently skipped and then read as if it had no display.
22
- */
23
- export declare function backfillConversationDisplayEnvelopes(deps: ConversationDisplayBackfillDeps): Promise<number>;
@@ -1,91 +0,0 @@
1
- import { createDetailResolver } from "../tools/detail-resolver.js";
2
- import { envelopeDisplayMessages, cortexMessagesToConversationUI, parseStoredDisplayEnvelope } from "./conversation-display-storage.js";
3
- import { parseStoredMessageEnvelope } from "./ai-sdk-message-storage.js";
4
- import { contentToCortexMessages } from "./content-to-cortex.js";
5
- import { createCardResolver } from "./reconstruct-cards.js";
6
- const BATCH_SIZE = 100;
7
- const GENUINE_USER_SQL = `m.message_envelope->'message'->>'role' = 'user'
8
- AND m.message_envelope->'message'->'providerOptions'->'cortex'->>'synthetic' IS DISTINCT FROM 'true'`;
9
- /**
10
- * Freeze the migration renderer's output into display envelopes, once per legacy
11
- * turn, so the runtime read path never has to reconstruct anything.
12
- *
13
- * Missing mutable resources degrade to a generic, cardless display — a plan
14
- * that is absent is normal historical absence, not a fault. Malformed stored rows
15
- * and database faults remain startup-fatal: a turn that cannot be migrated must
16
- * not be silently skipped and then read as if it had no display.
17
- */
18
- export async function backfillConversationDisplayEnvelopes(deps) {
19
- const batchSize = Math.max(1, deps.batchSize ?? BATCH_SIZE);
20
- const resolveDetail = createDetailResolver(deps.tools);
21
- let migrated = 0;
22
- let validationOffset = 0;
23
- for (;;) {
24
- const { rows } = await deps.pool.query(`SELECT thread_id, seq::text AS seq, display_envelope
25
- FROM messages
26
- WHERE display_envelope IS NOT NULL
27
- ORDER BY thread_id, seq
28
- LIMIT $1 OFFSET $2`, [batchSize, validationOffset]);
29
- await Promise.all(rows.map((row) => parseStoredDisplayEnvelope(row.display_envelope, `${row.thread_id}/${row.seq}/display`)));
30
- if (rows.length < batchSize)
31
- break;
32
- validationOffset += rows.length;
33
- }
34
- for (;;) {
35
- const { rows: heads } = await deps.pool.query(`SELECT m.thread_id, m.seq::text AS seq, t.analysis_id
36
- FROM messages m
37
- LEFT JOIN cortex_analysis_threads t ON t.thread_id = m.thread_id
38
- WHERE ${GENUINE_USER_SQL}
39
- AND m.display_envelope IS NULL
40
- ORDER BY m.thread_id, m.seq
41
- LIMIT $1`, [batchSize]);
42
- if (heads.length === 0)
43
- return migrated;
44
- for (const head of heads) {
45
- const { rows } = await deps.pool.query(`SELECT m.seq::text AS seq, m.message_envelope
46
- FROM messages m
47
- WHERE m.thread_id = $1
48
- AND m.seq >= $2::bigint
49
- AND m.seq < COALESCE(
50
- (
51
- SELECT MIN(next.seq)
52
- FROM messages next
53
- WHERE next.thread_id = m.thread_id
54
- AND next.seq > $2::bigint
55
- AND next.message_envelope->'message'->>'role' = 'user'
56
- AND next.message_envelope->'message'->'providerOptions'->'cortex'->>'synthetic' IS DISTINCT FROM 'true'
57
- ),
58
- 9223372036854775807
59
- )
60
- ORDER BY m.seq`, [head.thread_id, head.seq]);
61
- const stored = rows.map((row) => {
62
- const envelope = parseStoredMessageEnvelope(row.message_envelope, `${head.thread_id}/${row.seq}`);
63
- return { seq: Number(row.seq), envelope, message: envelope.message };
64
- });
65
- let resolveCard;
66
- if (head.analysis_id !== null) {
67
- resolveCard = createCardResolver(deps.pool, head.analysis_id);
68
- }
69
- const cortex = await contentToCortexMessages(stored, { resolveCard, resolveDetail });
70
- const uiMessages = cortexMessagesToConversationUI(cortex);
71
- // A genuine turn always contributes at least its user message. Keep a
72
- // defensive model-derived fallback so absence can never strand a NULL
73
- // envelope and make startup retry forever.
74
- const display = uiMessages.length > 0
75
- ? uiMessages
76
- : [
77
- {
78
- id: head.seq,
79
- role: "user",
80
- parts: [{ type: "text", text: "", state: "done" }],
81
- },
82
- ];
83
- const envelope = envelopeDisplayMessages(display);
84
- const result = await deps.pool.query(`UPDATE messages
85
- SET display_envelope = $1::jsonb
86
- WHERE thread_id = $2 AND seq = $3::bigint
87
- AND display_envelope IS NULL`, [JSON.stringify(envelope), head.thread_id, head.seq]);
88
- migrated += result.rowCount ?? 0;
89
- }
90
- }
91
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * @deprecated Compatibility-only reconstruction for pre-display-envelope
3
- * turns. New runtime reads use stored AI SDK UI messages. Retain this module
4
- * through the pre-v1 direct-upgrade window, then remove its runtime fallback
5
- * in a dedicated compatibility change.
6
- *
7
- * `show_plan` / `show_user` / `show_file` / `execute_analysis` emit
8
- * `data-plan` / `data-presentation` / `data-file-reference` / `data-run-card` cards
9
- * live over the chat SSE stream. On reload, `content-to-cortex` calls this
10
- * resolver for each AI SDK tool-call part: a recognised display tool yields its card (rebuilt via the
11
- * shared `card-builders`); anything else yields `null` and falls back to a
12
- * generic `tool-call` chip. Keyed by the analysis `Pool` + id (plan / run) so
13
- * the card can be re-loaded.
14
- */
15
- import type { CortexPart } from "@inflexa-ai/harness/contracts/message.js";
16
- import type { Pool } from "pg";
17
- /** @deprecated Used only by the legacy display backfill/fallback. */
18
- export interface StoredToolCallForCard {
19
- readonly type: "tool_use";
20
- readonly id: string;
21
- readonly name: string;
22
- readonly input: unknown;
23
- }
24
- /** @deprecated Used only by the legacy display backfill/fallback. */
25
- export type ToolCardResolver = (block: StoredToolCallForCard) => Promise<CortexPart | null>;
26
- /** @deprecated Use persisted conversation display envelopes for runtime reads. */
27
- export declare function createCardResolver(pool: Pool, analysisId: string): ToolCardResolver;
@@ -1,57 +0,0 @@
1
- /**
2
- * @deprecated Compatibility-only reconstruction for pre-display-envelope
3
- * turns. New runtime reads use stored AI SDK UI messages. Retain this module
4
- * through the pre-v1 direct-upgrade window, then remove its runtime fallback
5
- * in a dedicated compatibility change.
6
- *
7
- * `show_plan` / `show_user` / `show_file` / `execute_analysis` emit
8
- * `data-plan` / `data-presentation` / `data-file-reference` / `data-run-card` cards
9
- * live over the chat SSE stream. On reload, `content-to-cortex` calls this
10
- * resolver for each AI SDK tool-call part: a recognised display tool yields its card (rebuilt via the
11
- * shared `card-builders`); anything else yields `null` and falls back to a
12
- * generic `tool-call` chip. Keyed by the analysis `Pool` + id (plan / run) so
13
- * the card can be re-loaded.
14
- */
15
- import { unwrapOrThrow } from "../lib/result.js";
16
- import { adHocPlanId, adHocRunId } from "../tools/analysis-invocation.js";
17
- import { validatePath } from "../tools/lib/path-validation.js";
18
- import { buildFileReferenceCardData, buildPlanCardData, buildPresentationCardData, buildRunCardData } from "./card-builders.js";
19
- /** @deprecated Use persisted conversation display envelopes for runtime reads. */
20
- export function createCardResolver(pool, analysisId) {
21
- return async (block) => {
22
- if (block.type !== "tool_use")
23
- return null;
24
- const input = (block.input ?? {});
25
- if (block.name === "show_user") {
26
- // The live tool rejects a malformed/traversal echart `dataPath` and emits nothing; the
27
- // persisted tool_use still carries it, so the reload path MUST re-validate before building
28
- // the card — otherwise an unvalidated path is resurrected for the host to join into the
29
- // workspace root. Invalid → no card (chip fallback), matching the live "emitted nothing".
30
- if (typeof input.dataPath === "string" && validatePath(input.dataPath) !== null)
31
- return null;
32
- const card = buildPresentationCardData(input);
33
- return card ? { type: "data-presentation", ...card } : null;
34
- }
35
- if (block.name === "show_file") {
36
- const card = buildFileReferenceCardData(input);
37
- return card ? { type: "data-file-reference", ...card } : null;
38
- }
39
- if (block.name === "show_plan") {
40
- const planId = typeof input.planId === "string" ? input.planId : null;
41
- if (!planId)
42
- return null;
43
- const title = typeof input.title === "string" ? input.title : undefined;
44
- const card = unwrapOrThrow(await buildPlanCardData(pool, planId, analysisId, title));
45
- return card ? { type: "data-plan", ...card } : null;
46
- }
47
- if (block.name === "execute_analysis" && (input.mode === "plan" || input.mode === "adhoc")) {
48
- const planId = input.mode === "plan" ? (typeof input.planId === "string" ? input.planId : null) : adHocPlanId(analysisId, block.id);
49
- if (planId === null)
50
- return null;
51
- const runId = input.mode === "adhoc" ? adHocRunId(analysisId, block.id) : undefined;
52
- const card = unwrapOrThrow(await buildRunCardData(pool, { planId, analysisId, ...(runId !== undefined ? { runId } : {}) }));
53
- return card ? { type: "data-run-card", ...card } : null;
54
- }
55
- return null;
56
- };
57
- }