@inflexa-ai/harness 0.25.0 → 0.26.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/dist/agents/report-session-agent.d.ts +5 -4
- package/dist/agents/report-session-agent.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -3
- package/dist/lib/page-capture.d.ts +44 -15
- package/dist/lib/page-capture.js +78 -19
- package/dist/lib/zod-issues.d.ts +9 -4
- package/dist/lib/zod-issues.js +35 -4
- package/dist/loop/run-agent.js +36 -19
- package/dist/memory/card-builders.d.ts +4 -6
- package/dist/memory/card-builders.js +4 -6
- package/dist/memory/conversation-display-replay.d.ts +7 -6
- package/dist/memory/conversation-display-replay.js +7 -6
- package/dist/memory/conversation-display-storage.d.ts +2 -10
- package/dist/memory/conversation-display-storage.js +79 -32
- package/dist/memory/thread-history.d.ts +31 -20
- package/dist/memory/thread-history.js +45 -45
- package/dist/prompts/report-session.d.ts +1 -1
- package/dist/prompts/report-session.js +22 -13
- package/dist/runtime/assemble.d.ts +3 -0
- package/dist/runtime/assemble.js +9 -1
- package/dist/runtime/boot.d.ts +7 -8
- package/dist/runtime/boot.js +7 -19
- package/dist/sandbox/client.d.ts +9 -13
- package/dist/sandbox/client.js +8 -3
- package/dist/sandbox/create-sandbox.js +0 -1
- package/dist/tasks/derive-table-exec.d.ts +50 -0
- package/dist/tasks/derive-table-exec.js +84 -0
- package/dist/tools/define-tool.d.ts +19 -8
- package/dist/tools/define-tool.js +28 -12
- package/dist/tools/report-session/derive-table.d.ts +39 -7
- package/dist/tools/report-session/derive-table.js +24 -83
- package/dist/tools/report-session/examine-page.d.ts +22 -13
- package/dist/tools/report-session/examine-page.js +15 -10
- package/dist/tools/report-session/index.d.ts +1 -1
- package/dist/tools/research/generate-plan.js +3 -8
- package/dist/tools/research/inspect-data-profile.js +10 -11
- package/dist/tools/start-report-session.js +6 -2
- package/package.json +1 -1
- package/dist/app/data-profile-policy.d.ts +0 -62
- package/dist/app/data-profile-policy.js +0 -59
- package/dist/memory/content-to-cortex.d.ts +0 -75
- package/dist/memory/content-to-cortex.js +0 -201
- package/dist/memory/conversation-display-backfill.d.ts +0 -23
- package/dist/memory/conversation-display-backfill.js +0 -91
- package/dist/memory/reconstruct-cards.d.ts +0 -27
- 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
|
-
}
|