@inflexa-ai/harness 0.44.0 → 0.45.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/conversation-agent.d.ts +10 -0
- package/dist/agents/conversation-agent.js +19 -1
- package/dist/agents/sandbox/shared.d.ts +23 -2
- package/dist/agents/sandbox/shared.js +51 -18
- package/dist/agents/sandbox/translational-safety-agent.js +26 -0
- package/dist/agents/sandbox/types.d.ts +24 -1
- package/dist/agents/sandbox-catalog.d.ts +14 -0
- package/dist/agents/sandbox-catalog.js +19 -0
- package/dist/app/approve-plan.d.ts +51 -0
- package/dist/app/approve-plan.js +84 -0
- package/dist/app/chat-turn.js +6 -3
- package/dist/app/data-profile-view.d.ts +1 -1
- package/dist/app/run-activity.js +4 -6
- package/dist/config/environment-stores.d.ts +4 -2
- package/dist/contracts/chat-events.d.ts +14 -2
- package/dist/contracts/chat-events.js +1 -1
- package/dist/contracts/chat-frame.d.ts +7 -0
- package/dist/contracts/chat-frame.js +24 -6
- package/dist/contracts/chat-parts.d.ts +73 -2
- package/dist/contracts/chat-parts.js +3 -1
- package/dist/contracts/index.d.ts +4 -2
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/part-registry.js +3 -0
- package/dist/contracts/schemas/chat-events.d.ts +38 -0
- package/dist/contracts/schemas/chat-events.js +8 -0
- package/dist/contracts/schemas/chat-parts.d.ts +239 -15
- package/dist/contracts/schemas/chat-parts.js +80 -12
- package/dist/contracts/schemas/index.d.ts +2 -2
- package/dist/contracts/schemas/index.js +2 -2
- package/dist/contracts/subagent-activity.d.ts +12 -0
- package/dist/contracts/subagent-activity.js +76 -0
- package/dist/execution/artifact-metadata.js +1 -3
- package/dist/execution/dbos-workflow-purger.d.ts +2 -0
- package/dist/execution/dbos-workflow-purger.js +21 -14
- package/dist/execution/run-canceler.d.ts +33 -11
- package/dist/execution/run-canceler.js +130 -28
- package/dist/execution/run-end-record.d.ts +30 -0
- package/dist/execution/run-end-record.js +27 -0
- package/dist/execution/run-event-parts.d.ts +6 -5
- package/dist/execution/run-event-parts.js +6 -5
- package/dist/execution/run-event-stream.d.ts +18 -6
- package/dist/execution/run-event-stream.js +67 -17
- package/dist/execution/run-synthesis.d.ts +21 -0
- package/dist/execution/run-synthesis.js +77 -3
- package/dist/index.d.ts +28 -10
- package/dist/index.js +26 -2
- package/dist/lib/hooks.d.ts +1 -1
- package/dist/lib/metrics.d.ts +2 -2
- package/dist/loop/call-guard.d.ts +50 -0
- package/dist/loop/call-guard.js +90 -0
- package/dist/loop/compaction.d.ts +5 -0
- package/dist/loop/compaction.js +17 -0
- package/dist/loop/run-agent.d.ts +9 -0
- package/dist/loop/run-agent.js +16 -1
- package/dist/loop/run-to-terminal.js +4 -1
- package/dist/loop/types.d.ts +8 -0
- package/dist/memory/card-builders.d.ts +11 -0
- package/dist/memory/card-builders.js +35 -21
- package/dist/memory/conversation-display-recorder.d.ts +2 -2
- package/dist/memory/conversation-display-recorder.js +38 -4
- package/dist/memory/conversation-display-storage.d.ts +9 -2
- package/dist/memory/conversation-display-storage.js +18 -3
- package/dist/memory/thread-history.d.ts +42 -8
- package/dist/memory/thread-history.js +164 -45
- package/dist/memory/thread-store.d.ts +13 -0
- package/dist/memory/thread-store.js +15 -1
- package/dist/prompts/briefing.d.ts +57 -3
- package/dist/prompts/briefing.js +129 -2
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +3 -1
- package/dist/prompts/planner.js +13 -2
- package/dist/provenance/seam.d.ts +39 -20
- package/dist/provenance/seam.js +7 -5
- package/dist/providers/ai-sdk.js +93 -11
- package/dist/providers/types.d.ts +6 -0
- package/dist/reference-data/catalog.js +141 -2
- package/dist/report-model/authoring-grammar.d.ts +26 -26
- package/dist/report-model/pin-snapshot.d.ts +1 -1
- package/dist/report-model/pin-snapshot.js +2 -50
- package/dist/runtime/assemble.d.ts +8 -1
- package/dist/runtime/assemble.js +11 -0
- package/dist/runtime/boot.d.ts +3 -0
- package/dist/runtime/boot.js +10 -1
- package/dist/sandbox/image-packages.d.ts +26 -4
- package/dist/sandbox/image-packages.js +18 -4
- package/dist/sandbox/sandbox-step-translate.d.ts +23 -0
- package/dist/sandbox/sandbox-step-translate.js +24 -0
- package/dist/schemas/artifact-manifest.d.ts +1 -1
- package/dist/schemas/artifact-manifest.js +11 -9
- package/dist/schemas/data-profile-schemas.d.ts +12 -12
- package/dist/schemas/plan-schemas.d.ts +75 -21
- package/dist/schemas/plan-schemas.js +1 -1
- package/dist/schemas/run-synthesis.d.ts +2 -2
- package/dist/schemas/workflow-state.d.ts +113 -0
- package/dist/schemas/workflow-state.js +54 -0
- package/dist/state/analyses-metrics.d.ts +79 -7
- package/dist/state/analyses-metrics.js +141 -6
- package/dist/state/artifacts.d.ts +66 -3
- package/dist/state/artifacts.js +85 -7
- package/dist/state/data-profile.d.ts +4 -2
- package/dist/state/data-profile.js +7 -5
- package/dist/state/index.d.ts +9 -8
- package/dist/state/index.js +5 -5
- package/dist/state/migrations/20261012120000_index_the_runs_by_start.d.ts +8 -0
- package/dist/state/migrations/20261012120000_index_the_runs_by_start.js +12 -0
- package/dist/state/migrations/20261013120000_index_the_ended_runs_and_the_recent_threads.d.ts +8 -0
- package/dist/state/migrations/20261013120000_index_the_ended_runs_and_the_recent_threads.js +13 -0
- package/dist/state/migrations/20261013180000_narrow_the_active_run_index.d.ts +8 -0
- package/dist/state/migrations/20261013180000_narrow_the_active_run_index.js +13 -0
- package/dist/state/migrations/20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.d.ts +8 -0
- package/dist/state/migrations/20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.js +13 -0
- package/dist/state/migrations/index.js +8 -0
- package/dist/state/plans.d.ts +21 -0
- package/dist/state/plans.js +25 -0
- package/dist/state/runs.d.ts +79 -14
- package/dist/state/runs.js +150 -33
- package/dist/state/schema.d.ts +2 -0
- package/dist/state/step-executions.d.ts +9 -0
- package/dist/state/step-executions.js +22 -0
- package/dist/tasks/data-profile-activity.d.ts +7 -1
- package/dist/tasks/data-profile-activity.js +18 -9
- package/dist/tasks/data-profile.d.ts +15 -1
- package/dist/tasks/data-profile.js +16 -11
- package/dist/tools/ad-hoc-router.d.ts +2 -0
- package/dist/tools/ad-hoc-router.js +18 -2
- package/dist/tools/approval/gateway.js +28 -5
- package/dist/tools/capability-data.d.ts +36 -0
- package/dist/tools/capability-data.js +73 -0
- package/dist/tools/display/show-user.d.ts +1 -1
- package/dist/tools/execute-analysis.d.ts +80 -1
- package/dist/tools/execute-analysis.js +352 -110
- package/dist/tools/knowledge/check.d.ts +60 -0
- package/dist/tools/knowledge/check.js +92 -0
- package/dist/tools/knowledge/client.d.ts +382 -0
- package/dist/tools/knowledge/client.js +302 -0
- package/dist/tools/knowledge/environment.d.ts +78 -0
- package/dist/tools/knowledge/environment.js +105 -0
- package/dist/tools/knowledge/index.d.ts +49 -0
- package/dist/tools/knowledge/index.js +70 -0
- package/dist/tools/knowledge/local-slots.d.ts +62 -0
- package/dist/tools/knowledge/local-slots.js +177 -0
- package/dist/tools/knowledge/pin.d.ts +14 -0
- package/dist/tools/knowledge/pin.js +17 -0
- package/dist/tools/knowledge/recommend.d.ts +92 -0
- package/dist/tools/knowledge/recommend.js +98 -0
- package/dist/tools/knowledge/situation.d.ts +143 -0
- package/dist/tools/knowledge/situation.js +164 -0
- package/dist/tools/knowledge/skeleton.d.ts +69 -0
- package/dist/tools/knowledge/skeleton.js +202 -0
- package/dist/tools/knowledge/template.d.ts +137 -0
- package/dist/tools/knowledge/template.js +267 -0
- package/dist/tools/research/generate-plan.d.ts +23 -0
- package/dist/tools/research/generate-plan.js +203 -12
- package/dist/tools/research/guarded-address.d.ts +20 -0
- package/dist/tools/research/guarded-address.js +148 -0
- package/dist/tools/research/inspect-data-profile.d.ts +1 -1
- package/dist/tools/research/inspect-run.d.ts +1 -1
- package/dist/tools/research/inspect-run.js +7 -7
- package/dist/tools/research/searxng-engine.d.ts +40 -0
- package/dist/tools/research/searxng-engine.js +120 -0
- package/dist/tools/research/web-fetch.d.ts +124 -0
- package/dist/tools/research/web-fetch.js +627 -0
- package/dist/tools/research/web-search.d.ts +75 -0
- package/dist/tools/research/web-search.js +112 -0
- package/dist/tools/sandbox/list-available-packages.d.ts +7 -4
- package/dist/tools/sandbox/list-available-packages.js +12 -4
- package/dist/tools/sandbox/list-available-refs.d.ts +21 -0
- package/dist/tools/sandbox/list-available-refs.js +60 -3
- package/dist/tools/sandbox/report-blocker.js +5 -1
- package/dist/tools/terminal-text.d.ts +11 -0
- package/dist/tools/terminal-text.js +19 -0
- package/dist/tools/workspace/decision-record.d.ts +81 -0
- package/dist/tools/workspace/decision-record.js +117 -0
- package/dist/tools/workspace/edit-file.d.ts +3 -1
- package/dist/tools/workspace/edit-file.js +20 -2
- package/dist/tools/workspace/execute-command.d.ts +17 -0
- package/dist/tools/workspace/execute-command.js +28 -1
- package/dist/tools/workspace/index.d.ts +1 -0
- package/dist/tools/workspace/index.js +1 -0
- package/dist/tools/workspace/mutator.d.ts +1 -1
- package/dist/workflows/execute-analysis.d.ts +62 -21
- package/dist/workflows/execute-analysis.js +307 -79
- package/dist/workflows/plan/graph.d.ts +16 -0
- package/dist/workflows/plan/graph.js +36 -0
- package/dist/workflows/sandbox-step.d.ts +12 -0
- package/dist/workflows/sandbox-step.js +14 -12
- package/package.json +5 -1
|
@@ -29,7 +29,7 @@ import { hasReportedUsage } from "../loop/metrics.js";
|
|
|
29
29
|
import { countTokens } from "./count-tokens.js";
|
|
30
30
|
import { conversationView, groupTurns, isGenuineUserStart } from "./conversation-view.js";
|
|
31
31
|
import { HARNESS_PROVIDER_NAMESPACE, SYNTHETIC_MESSAGE_KEY, envelopeMessage, parseStoredMessageEnvelope, syntheticRecordMessage, } from "./ai-sdk-message-storage.js";
|
|
32
|
-
import { envelopeDisplayMessages, parseStoredDisplayEnvelope } from "./conversation-display-storage.js";
|
|
32
|
+
import { conversationDisplayPart, envelopeDisplayMessages, parseStoredDisplayEnvelope, } from "./conversation-display-storage.js";
|
|
33
33
|
/** A resolved `ok(undefined)` ResultAsync — the empty/seed transaction step. */
|
|
34
34
|
function okVoid() {
|
|
35
35
|
return new ResultAsync(Promise.resolve(ok(undefined)));
|
|
@@ -44,11 +44,21 @@ function okVoid() {
|
|
|
44
44
|
* never shown. The harness owns the record-to-`system` mapping for the same
|
|
45
45
|
* reason it owns the marker — a host that hand-assembled either could produce a
|
|
46
46
|
* message the turn-boundary predicates fail to recognise.
|
|
47
|
+
*
|
|
48
|
+
* With `displayParts`, the display message holds those parts in place of the
|
|
49
|
+
* text part, and the model message is still the record of `text`. The text then
|
|
50
|
+
* stays for the model alone.
|
|
47
51
|
*/
|
|
48
|
-
export function conversationRecordTurn(text) {
|
|
52
|
+
export function conversationRecordTurn(text, displayParts) {
|
|
49
53
|
return {
|
|
50
54
|
modelMessages: [syntheticRecordMessage(text)],
|
|
51
|
-
displayMessages: [
|
|
55
|
+
displayMessages: [
|
|
56
|
+
{
|
|
57
|
+
id: randomUUID(),
|
|
58
|
+
role: "system",
|
|
59
|
+
parts: displayParts !== undefined && displayParts.length > 0 ? displayParts.map(conversationDisplayPart) : [{ type: "text", text, state: "done" }],
|
|
60
|
+
},
|
|
61
|
+
],
|
|
52
62
|
};
|
|
53
63
|
}
|
|
54
64
|
/**
|
|
@@ -214,6 +224,102 @@ function closeTurnRecord(client, threadId, startSeq, close) {
|
|
|
214
224
|
SET status = $3, reason = $4, reported_usage = $5::jsonb, turn_duration_ms = $6, closed_at = NOW()
|
|
215
225
|
WHERE thread_id = $1 AND start_seq = $2 AND status = 'open'`, [threadId, startSeq, close.status, close.reason === undefined ? null : stripNulCharacters(close.reason), rollup, close.turnDurationMs ?? null])).map(() => undefined);
|
|
216
226
|
}
|
|
227
|
+
/** The columns of a display-read row, and the join that the turn record needs. Both display reads select them. */
|
|
228
|
+
const STORED_ROW_COLUMNS = `messages.seq::text AS seq, messages.message_envelope, messages.display_envelope, messages.reported_usage,
|
|
229
|
+
messages.turn_duration_ms::text AS turn_duration_ms, messages.author, messages.created_at,
|
|
230
|
+
turns.status AS turn_status, turns.reason AS turn_reason, turns.reported_usage AS turn_usage,
|
|
231
|
+
turns.turn_duration_ms::text AS turn_record_duration_ms`;
|
|
232
|
+
function storedRowFrom(turnJoinBounds = "") {
|
|
233
|
+
return `FROM messages
|
|
234
|
+
LEFT JOIN cortex_thread_turns turns ON turns.thread_id = messages.thread_id AND turns.start_seq = messages.seq${turnJoinBounds}`;
|
|
235
|
+
}
|
|
236
|
+
/** The largest `bigint`, the bound of a cursor that the caller did not give. */
|
|
237
|
+
const MAX_SEQ = "9223372036854775807";
|
|
238
|
+
/** The smallest `bigint`, below every `seq`. */
|
|
239
|
+
const MIN_SEQ = "'-9223372036854775808'";
|
|
240
|
+
const PAGE_LOWER_BOUND = `COALESCE((SELECT seq FROM page_lower), ${MIN_SEQ}::bigint)`;
|
|
241
|
+
const PAGE_UPPER_BOUND = `COALESCE((SELECT seq FROM page_upper), ${MAX_SEQ}::bigint)`;
|
|
242
|
+
/**
|
|
243
|
+
* The statement of one page of the display read (`$1` thread, `$2` cursor or `NULL`, `$3` limit).
|
|
244
|
+
*
|
|
245
|
+
* One statement reads one snapshot, thus an append or a retract cannot add a turn between the
|
|
246
|
+
* read of the page edges and the read of the rows. The parts:
|
|
247
|
+
*
|
|
248
|
+
* - `page_walk`: a walk down from the cursor, one row for each step, that counts the turn starts
|
|
249
|
+
* and stops at the `$3`-th. Each step is a `LIMIT 1` read of the primary key `(thread_id, seq)`
|
|
250
|
+
* that evaluates the start predicate in the select list.
|
|
251
|
+
* - `page_lower`: the row where the walk reached `$3` starts, else `NULL`. A page that is not
|
|
252
|
+
* full reaches the start of the thread and holds the rows before the first turn start.
|
|
253
|
+
* - `page_climb` and `page_upper`: a walk up from the cursor to the first turn start at or above
|
|
254
|
+
* it, else `NULL`. For a cursor that a page gave this is the cursor row. For a cursor inside a
|
|
255
|
+
* turn it is the start of the next turn, thus the turn of the cursor stays whole.
|
|
256
|
+
* - `page_next`: `page_lower` when a row lies below it. The `EXISTS` reads no envelope.
|
|
257
|
+
*
|
|
258
|
+
* The start predicate is never a `WHERE` condition of a scan that has an `ORDER BY` and a `LIMIT`.
|
|
259
|
+
* The planner has no statistics for a JSON path, so it estimates that very few rows match. It then
|
|
260
|
+
* chooses a bitmap scan and a sort of each row of the thread, and it evaluates the predicate on the
|
|
261
|
+
* envelope of each row (a tool result can be many KB). A walk of `LIMIT 1` reads of the key does
|
|
262
|
+
* not depend on that estimate, and its work grows with the rows of the page.
|
|
263
|
+
*
|
|
264
|
+
* An absent cursor, `page_lower`, and `page_upper` each become a `bigint` extreme through
|
|
265
|
+
* `COALESCE`, never a `NULL` in a comparison: `seq >= NULL` matches no row, and a form such as
|
|
266
|
+
* `($2 IS NULL OR seq < $2)` is a filter, which would visit each newer row. Each bound is an
|
|
267
|
+
* index condition on `(thread_id, seq)`. The join to the turn records carries the same bounds,
|
|
268
|
+
* because the planner estimates a bounded range as a fixed fraction of the thread and then reads
|
|
269
|
+
* each turn record of the thread through a hash join.
|
|
270
|
+
*/
|
|
271
|
+
const PAGE_SQL = `WITH RECURSIVE page_walk(seq, starts) AS (
|
|
272
|
+
SELECT COALESCE($2::bigint, ${MAX_SEQ}::bigint), 0::bigint
|
|
273
|
+
UNION ALL
|
|
274
|
+
SELECT below.seq, page_walk.starts + below.is_start::int
|
|
275
|
+
FROM page_walk
|
|
276
|
+
CROSS JOIN LATERAL (
|
|
277
|
+
SELECT messages.seq, COALESCE(${GENUINE_USER_START_SQL}, false) AS is_start FROM messages
|
|
278
|
+
WHERE messages.thread_id = $1 AND messages.seq < page_walk.seq
|
|
279
|
+
ORDER BY messages.seq DESC
|
|
280
|
+
LIMIT 1
|
|
281
|
+
) below
|
|
282
|
+
WHERE page_walk.starts < $3::bigint
|
|
283
|
+
),
|
|
284
|
+
page_lower AS (
|
|
285
|
+
SELECT (SELECT page_walk.seq FROM page_walk WHERE page_walk.starts = $3::bigint) AS seq
|
|
286
|
+
),
|
|
287
|
+
page_climb(seq, is_start) AS (
|
|
288
|
+
SELECT above.seq, above.is_start FROM (
|
|
289
|
+
SELECT messages.seq, COALESCE(${GENUINE_USER_START_SQL}, false) AS is_start FROM messages
|
|
290
|
+
WHERE messages.thread_id = $1 AND messages.seq >= COALESCE($2::bigint, ${MAX_SEQ}::bigint)
|
|
291
|
+
ORDER BY messages.seq ASC
|
|
292
|
+
LIMIT 1
|
|
293
|
+
) above
|
|
294
|
+
UNION ALL
|
|
295
|
+
SELECT above.seq, above.is_start
|
|
296
|
+
FROM page_climb
|
|
297
|
+
CROSS JOIN LATERAL (
|
|
298
|
+
SELECT messages.seq, COALESCE(${GENUINE_USER_START_SQL}, false) AS is_start FROM messages
|
|
299
|
+
WHERE messages.thread_id = $1 AND messages.seq > page_climb.seq
|
|
300
|
+
ORDER BY messages.seq ASC
|
|
301
|
+
LIMIT 1
|
|
302
|
+
) above
|
|
303
|
+
WHERE NOT page_climb.is_start
|
|
304
|
+
),
|
|
305
|
+
page_upper AS (
|
|
306
|
+
SELECT page_climb.seq FROM page_climb WHERE page_climb.is_start
|
|
307
|
+
),
|
|
308
|
+
page_next AS (
|
|
309
|
+
SELECT page_lower.seq FROM page_lower
|
|
310
|
+
WHERE page_lower.seq IS NOT NULL
|
|
311
|
+
AND EXISTS (SELECT 1 FROM messages WHERE thread_id = $1 AND seq < page_lower.seq)
|
|
312
|
+
)
|
|
313
|
+
SELECT ${STORED_ROW_COLUMNS}, (SELECT seq::text FROM page_next) AS next_before
|
|
314
|
+
${storedRowFrom(` AND turns.start_seq >= ${PAGE_LOWER_BOUND} AND turns.start_seq < ${PAGE_UPPER_BOUND}`)}
|
|
315
|
+
WHERE messages.thread_id = $1
|
|
316
|
+
AND messages.seq >= ${PAGE_LOWER_BOUND}
|
|
317
|
+
AND messages.seq < ${PAGE_UPPER_BOUND}
|
|
318
|
+
ORDER BY messages.seq ASC`;
|
|
319
|
+
/** The limit that the page statement receives: an integer, at least 1, and a bigint-safe size. */
|
|
320
|
+
function pageLimit(limit) {
|
|
321
|
+
return Number.isFinite(limit) ? Math.min(Math.max(1, Math.trunc(limit)), Number.MAX_SAFE_INTEGER) : 1;
|
|
322
|
+
}
|
|
217
323
|
/**
|
|
218
324
|
* Create a `ThreadHistory` bound to a Postgres pool — a factory closure
|
|
219
325
|
* capturing `pool` (dependency injection per the harness-durable-runtime spec). The `messages` table is
|
|
@@ -286,59 +392,72 @@ export function createThreadHistory(pool, logger) {
|
|
|
286
392
|
return view.messages;
|
|
287
393
|
});
|
|
288
394
|
}
|
|
289
|
-
//
|
|
290
|
-
// turn is persisted as one row per step (plus its tool_result
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
//
|
|
395
|
+
// A display read is turn-bounded, not row-bounded. A serial-tool assistant
|
|
396
|
+
// turn is persisted as one row per step (plus its tool_result rows), so a
|
|
397
|
+
// row-windowed read could split a turn and truncate the trailing report
|
|
398
|
+
// card or text out of what the UI fetches. Both reads group rows into turns,
|
|
399
|
+
// so a turn always reloads intact.
|
|
400
|
+
// Spread rather than `usage: r.reported_usage ?? undefined`, so a row with
|
|
401
|
+
// no rollup carries no `usage` KEY at all — absent, not present-and-undefined.
|
|
402
|
+
// The duration and the author obey the same rule, each from its own column.
|
|
403
|
+
// The creation time is unconditional: its column is NOT NULL, thus it has no
|
|
404
|
+
// absence to represent.
|
|
405
|
+
async function toStoredMessage(threadId, r) {
|
|
406
|
+
const envelope = parseStoredMessageEnvelope(r.message_envelope, `${threadId}/${r.seq}`);
|
|
407
|
+
const displayEnvelope = r.display_envelope == null ? undefined : await parseStoredDisplayEnvelope(r.display_envelope, `${threadId}/${r.seq}/display`, logger);
|
|
408
|
+
const turn = r.turn_status === null
|
|
409
|
+
? undefined
|
|
410
|
+
: {
|
|
411
|
+
status: r.turn_status,
|
|
412
|
+
...(r.turn_reason === null ? {} : { reason: r.turn_reason }),
|
|
413
|
+
...(r.turn_usage === null ? {} : { usage: r.turn_usage }),
|
|
414
|
+
...(r.turn_record_duration_ms === null ? {} : { durationMs: Number(r.turn_record_duration_ms) }),
|
|
415
|
+
};
|
|
416
|
+
return {
|
|
417
|
+
seq: Number(r.seq),
|
|
418
|
+
envelope,
|
|
419
|
+
message: envelope.message,
|
|
420
|
+
...(displayEnvelope ? { displayEnvelope } : {}),
|
|
421
|
+
...(r.reported_usage === null ? {} : { usage: r.reported_usage }),
|
|
422
|
+
...(r.turn_duration_ms === null ? {} : { durationMs: Number(r.turn_duration_ms) }),
|
|
423
|
+
...(turn === undefined ? {} : { turn }),
|
|
424
|
+
...(r.author === null ? {} : { author: r.author }),
|
|
425
|
+
createdAt: r.created_at,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
async function toTurns(threadId, rows) {
|
|
429
|
+
const stored = await Promise.all(rows.map((row) => toStoredMessage(threadId, row)));
|
|
430
|
+
return groupTurns(stored, (row) => isGenuineUserStart(row.message));
|
|
431
|
+
}
|
|
295
432
|
async function readTurns(threadId) {
|
|
296
433
|
const { rows } = await pool.query(
|
|
297
434
|
// ORDER BY must qualify `messages.seq` — a bare `seq` would bind to the
|
|
298
435
|
// `seq::text AS seq` output alias (Postgres resolves an unqualified
|
|
299
436
|
// ORDER BY name to the output column), sorting the bigint as text:
|
|
300
437
|
// "10" before "2". The qualified name forces the bigint column.
|
|
301
|
-
`SELECT
|
|
302
|
-
|
|
303
|
-
turns.status AS turn_status, turns.reason AS turn_reason, turns.reported_usage AS turn_usage,
|
|
304
|
-
turns.turn_duration_ms::text AS turn_record_duration_ms
|
|
305
|
-
FROM messages
|
|
306
|
-
LEFT JOIN cortex_thread_turns turns ON turns.thread_id = messages.thread_id AND turns.start_seq = messages.seq
|
|
438
|
+
`SELECT ${STORED_ROW_COLUMNS}
|
|
439
|
+
${storedRowFrom()}
|
|
307
440
|
WHERE messages.thread_id = $1
|
|
308
441
|
ORDER BY messages.seq ASC`, [threadId]);
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
...(r.turn_reason === null ? {} : { reason: r.turn_reason }),
|
|
322
|
-
...(r.turn_usage === null ? {} : { usage: r.turn_usage }),
|
|
323
|
-
...(r.turn_record_duration_ms === null ? {} : { durationMs: Number(r.turn_record_duration_ms) }),
|
|
324
|
-
};
|
|
325
|
-
return {
|
|
326
|
-
seq: Number(r.seq),
|
|
327
|
-
envelope,
|
|
328
|
-
message: envelope.message,
|
|
329
|
-
...(displayEnvelope ? { displayEnvelope } : {}),
|
|
330
|
-
...(r.reported_usage === null ? {} : { usage: r.reported_usage }),
|
|
331
|
-
...(r.turn_duration_ms === null ? {} : { durationMs: Number(r.turn_duration_ms) }),
|
|
332
|
-
...(turn === undefined ? {} : { turn }),
|
|
333
|
-
...(r.author === null ? {} : { author: r.author }),
|
|
334
|
-
createdAt: r.created_at,
|
|
335
|
-
};
|
|
336
|
-
}));
|
|
337
|
-
return groupTurns(stored, (row) => isGenuineUserStart(row.message));
|
|
442
|
+
return toTurns(threadId, rows);
|
|
443
|
+
}
|
|
444
|
+
async function readPage(threadId, options) {
|
|
445
|
+
const { rows } = await pool.query(PAGE_SQL, [
|
|
446
|
+
threadId,
|
|
447
|
+
options.before === undefined ? null : String(options.before),
|
|
448
|
+
pageLimit(options.limit),
|
|
449
|
+
]);
|
|
450
|
+
// The cursor rides each row, and an empty page has no row. An empty page has no cursor either:
|
|
451
|
+
// `page_lower` is set only when its row is in the page.
|
|
452
|
+
const next = rows[0]?.next_before ?? null;
|
|
453
|
+
return { turns: await toTurns(threadId, rows), before: next === null ? null : Number(next) };
|
|
338
454
|
}
|
|
339
455
|
function loadAll(threadId) {
|
|
340
456
|
return tryQuery("thread-history.loadAll", () => readTurns(threadId));
|
|
341
457
|
}
|
|
458
|
+
function loadPage(threadId, options) {
|
|
459
|
+
return tryQuery("thread-history.loadPage", () => readPage(threadId, options));
|
|
460
|
+
}
|
|
342
461
|
function retractLastTurn(threadId) {
|
|
343
462
|
return withTransaction(pool, "thread-history.retractLastTurn", (client) =>
|
|
344
463
|
// Take the SAME per-thread advisory lock `appendTurn` takes, and take it
|
|
@@ -416,5 +535,5 @@ export function createThreadHistory(pool, logger) {
|
|
|
416
535
|
AND seq > $2::bigint
|
|
417
536
|
AND ${GENUINE_USER_START_SQL}`, [threadId, seq])).map(({ rows }) => Number(rows[0].turns));
|
|
418
537
|
}
|
|
419
|
-
return { appendTurn, writeTurn, loadRecent, loadAll, retractLastTurn, latestSeq, latestTurnAt, countUserTurnsAfter };
|
|
538
|
+
return { appendTurn, writeTurn, loadRecent, loadAll, loadPage, retractLastTurn, latestSeq, latestTurnAt, countUserTurnsAfter };
|
|
420
539
|
}
|
|
@@ -169,6 +169,12 @@ export interface ListThreadsInput {
|
|
|
169
169
|
/** Narrow to one thread's direct children, exact match. Same polarity as `type`: omitting it narrows nothing. */
|
|
170
170
|
readonly parentThreadId?: string;
|
|
171
171
|
}
|
|
172
|
+
export interface ListRecentThreadsInput {
|
|
173
|
+
/** The most threads to give. It clamps to 1 to 200, the bounds of a page of `listThreads`. */
|
|
174
|
+
readonly limit: number;
|
|
175
|
+
/** Narrow to one kind of session, exact match. Omitting it narrows nothing. */
|
|
176
|
+
readonly type?: ThreadType;
|
|
177
|
+
}
|
|
172
178
|
export interface ThreadPage {
|
|
173
179
|
readonly threads: Thread[];
|
|
174
180
|
readonly total: number;
|
|
@@ -247,6 +253,13 @@ export interface ThreadStore {
|
|
|
247
253
|
* always describe whichever set the page was drawn from.
|
|
248
254
|
*/
|
|
249
255
|
listThreads(input: ListThreadsInput): ResultAsync<ThreadPage, DbError>;
|
|
256
|
+
/**
|
|
257
|
+
* The newest live threads over every analysis, newest-updated first, with
|
|
258
|
+
* `thread_id` breaking a tie in ascending order. One statement that walks
|
|
259
|
+
* the live-row index on the update time. `type` narrows the result, and an
|
|
260
|
+
* archived thread is never in it.
|
|
261
|
+
*/
|
|
262
|
+
listRecentThreads(input: ListRecentThreadsInput): ResultAsync<Thread[], DbError>;
|
|
250
263
|
}
|
|
251
264
|
/**
|
|
252
265
|
* Create a `ThreadStore` bound to a Postgres pool — a factory closure
|
|
@@ -366,5 +366,19 @@ export function createThreadStore(pool) {
|
|
|
366
366
|
}));
|
|
367
367
|
});
|
|
368
368
|
}
|
|
369
|
-
|
|
369
|
+
function listRecentThreads(input) {
|
|
370
|
+
const limit = Math.min(Math.max(input.limit, 1), MAX_PER_PAGE);
|
|
371
|
+
const values = [limit];
|
|
372
|
+
let typePredicate = "";
|
|
373
|
+
if (input.type !== undefined) {
|
|
374
|
+
values.push(input.type);
|
|
375
|
+
typePredicate = " AND thread_type = $2";
|
|
376
|
+
}
|
|
377
|
+
return tryQuery("thread-store.listRecentThreads", () => pool.query(`SELECT ${THREAD_COLUMNS}
|
|
378
|
+
FROM cortex_analysis_threads
|
|
379
|
+
WHERE deleted_at IS NULL${typePredicate}
|
|
380
|
+
ORDER BY updated_at DESC, thread_id
|
|
381
|
+
LIMIT $1`, values)).map(({ rows }) => rows.map(toThread));
|
|
382
|
+
}
|
|
383
|
+
return { createThread, getThread, updateTitle, setAutoTitle, archiveThread, unarchiveThread, purgeThread, listThreads, listRecentThreads };
|
|
370
384
|
}
|
|
@@ -24,14 +24,20 @@
|
|
|
24
24
|
import { type WorkingMemory } from "../memory/working-memory.js";
|
|
25
25
|
import type { DataProfileResult } from "../state/data-profile.js";
|
|
26
26
|
import type { AnalysisStep } from "../schemas/workflow-state.js";
|
|
27
|
+
import type { TemplateParameter } from "../tools/knowledge/client.js";
|
|
28
|
+
import type { TemplateBindingValue } from "../tools/knowledge/template.js";
|
|
27
29
|
/** Completed dependencies rendered as upstream blocks; the tail is counted, not rendered. */
|
|
28
30
|
export declare const MAX_UPSTREAM_DEPS = 5;
|
|
29
31
|
/** Characters of each dependency's summary carried in the seed — the gist, not the document. */
|
|
30
32
|
export declare const UPSTREAM_SUMMARY_MAX_CHARS = 500;
|
|
31
33
|
/** Artifact paths listed per dependency; the rest are reachable from its output directory. */
|
|
32
34
|
export declare const MAX_UPSTREAM_ARTIFACTS = 8;
|
|
35
|
+
/** Adaptable slots rendered in the Template contract section; the tail is counted, not rendered. */
|
|
36
|
+
export declare const MAX_TEMPLATE_SLOTS = 24;
|
|
37
|
+
/** Characters of one slot description carried in the seed — the constraint, not the manual. */
|
|
38
|
+
export declare const TEMPLATE_SLOT_DESCRIPTION_MAX_CHARS = 160;
|
|
33
39
|
/** Fields whose content is agent-facing and MUST appear in the rendered task. */
|
|
34
|
-
export declare const STEP_TASK_FIELDS: readonly ["name", "question", "description", "context", "constraints", "acceptance_criteria", "caveats"];
|
|
40
|
+
export declare const STEP_TASK_FIELDS: readonly ["name", "question", "description", "context", "constraints", "acceptance_criteria", "caveats", "grounding"];
|
|
35
41
|
/**
|
|
36
42
|
* Fields deliberately excluded from the task section: identity/DAG structure
|
|
37
43
|
* threaded separately through the workflow input, execution knobs, and result
|
|
@@ -41,7 +47,7 @@ export declare const STEP_TASK_FIELDS: readonly ["name", "question", "descriptio
|
|
|
41
47
|
* {@link renderResources} section, because the budget is an execution bound,
|
|
42
48
|
* not a task instruction.
|
|
43
49
|
*/
|
|
44
|
-
export declare const STEP_NON_TASK_FIELDS: readonly ["id", "track", "step_type", "depends_on", "status", "resources", "agent", "timeout", "maxSteps", "summary", "artifactIds", "error", "packages"];
|
|
50
|
+
export declare const STEP_NON_TASK_FIELDS: readonly ["id", "track", "step_type", "depends_on", "status", "resources", "agent", "timeout", "maxSteps", "summary", "artifactIds", "error", "packages", "capability"];
|
|
45
51
|
/**
|
|
46
52
|
* The step's own instructions. Only `question` is guaranteed present; the
|
|
47
53
|
* remaining task fields are appended as dedicated sections when populated, so a
|
|
@@ -50,7 +56,7 @@ export declare const STEP_NON_TASK_FIELDS: readonly ["id", "track", "step_type",
|
|
|
50
56
|
* folded in here — otherwise the agent runs against the bare question and
|
|
51
57
|
* improvises the rest.
|
|
52
58
|
*/
|
|
53
|
-
export declare function renderTask(step: AnalysisStep): string;
|
|
59
|
+
export declare function renderTask(step: AnalysisStep, contract?: Pick<StepBriefing, "template" | "templateNotRetrieved">): string;
|
|
54
60
|
/**
|
|
55
61
|
* The heading of the analysis-memory section. The orient core of the sandbox
|
|
56
62
|
* prompt names this same text, thus a test pins the two together.
|
|
@@ -75,6 +81,46 @@ export declare function emptyStepMemory(): StepMemory;
|
|
|
75
81
|
* entry — it has no memory tool.
|
|
76
82
|
*/
|
|
77
83
|
export declare function renderMemory(memory: StepMemory): string;
|
|
84
|
+
/** A file the script of the template reads. `path` names the slot that gives it, for example `{{counts_path}}`. */
|
|
85
|
+
export interface TemplateBriefInput {
|
|
86
|
+
readonly name: string;
|
|
87
|
+
readonly path: string;
|
|
88
|
+
readonly description?: string;
|
|
89
|
+
}
|
|
90
|
+
/** One plan setting bound to an adaptable slot of the template. */
|
|
91
|
+
export interface TemplateBoundSetting {
|
|
92
|
+
readonly name: string;
|
|
93
|
+
readonly value: TemplateBindingValue;
|
|
94
|
+
/** The source of the value (a doi or a document), or `plan` when the procedure names none. */
|
|
95
|
+
readonly source: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The contract of the template a step renders, projected for the seed. The
|
|
99
|
+
* parent fetches it at dispatch and binds the plan settings to it; the
|
|
100
|
+
* section is what lets an agent that has never seen the template make one
|
|
101
|
+
* valid render call.
|
|
102
|
+
*/
|
|
103
|
+
export interface TemplateBrief {
|
|
104
|
+
/** The template reference of the plan step, with its version when the plan names one. */
|
|
105
|
+
readonly ref: string;
|
|
106
|
+
/** The version the service serves. Differs from the version of `ref` when the snapshot moved on. */
|
|
107
|
+
readonly version_served: string;
|
|
108
|
+
/** The adaptable slots of the contract, in the order the template declares them. A pinned slot is never rendered. */
|
|
109
|
+
readonly slots: readonly TemplateParameter[];
|
|
110
|
+
readonly inputs: readonly TemplateBriefInput[];
|
|
111
|
+
/** The plan settings bound to a slot, in plan order. Empty when the served version differs from the plan. */
|
|
112
|
+
readonly bound: readonly TemplateBoundSetting[];
|
|
113
|
+
/** The plan settings no bound slot carries, one line each with the reason. */
|
|
114
|
+
readonly unbound_settings: readonly string[];
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The Template contract section. Only the adaptable slots render, bounded by
|
|
118
|
+
* {@link MAX_TEMPLATE_SLOTS} and the description clamp, so a wide template
|
|
119
|
+
* cannot blow the seed. A served version that differs from the plan renders
|
|
120
|
+
* as a caveat: the render refuses the plan version, and the settings are not
|
|
121
|
+
* bound to a version the plan did not name.
|
|
122
|
+
*/
|
|
123
|
+
export declare function renderTemplateContract(brief: TemplateBrief | undefined): string;
|
|
78
124
|
/**
|
|
79
125
|
* What anchors every path the agent writes or reads: the id the tree is mounted
|
|
80
126
|
* at, and the step's writable directory.
|
|
@@ -136,6 +182,14 @@ export interface StepBriefing {
|
|
|
136
182
|
readonly upstream: readonly UpstreamHandoff[];
|
|
137
183
|
/** The goal and the constraints of the analysis, read from the working memory at dispatch. */
|
|
138
184
|
readonly memory: StepMemory;
|
|
185
|
+
/** The contract of the template the step renders, with the plan settings bound to it. Absent when none was retrieved. */
|
|
186
|
+
readonly template?: TemplateBrief;
|
|
187
|
+
/**
|
|
188
|
+
* Why the seed carries no template contract for a step whose grounding
|
|
189
|
+
* names one: no client is bound, the service did not answer, or the
|
|
190
|
+
* service does not hold the template. Read only when `template` is absent.
|
|
191
|
+
*/
|
|
192
|
+
readonly templateNotRetrieved?: string;
|
|
139
193
|
}
|
|
140
194
|
/**
|
|
141
195
|
* Compose the seed. Absent sections collapse out entirely — an independent step
|
package/dist/prompts/briefing.js
CHANGED
|
@@ -30,6 +30,10 @@ export const MAX_UPSTREAM_DEPS = 5;
|
|
|
30
30
|
export const UPSTREAM_SUMMARY_MAX_CHARS = 500;
|
|
31
31
|
/** Artifact paths listed per dependency; the rest are reachable from its output directory. */
|
|
32
32
|
export const MAX_UPSTREAM_ARTIFACTS = 8;
|
|
33
|
+
/** Adaptable slots rendered in the Template contract section; the tail is counted, not rendered. */
|
|
34
|
+
export const MAX_TEMPLATE_SLOTS = 24;
|
|
35
|
+
/** Characters of one slot description carried in the seed — the constraint, not the manual. */
|
|
36
|
+
export const TEMPLATE_SLOT_DESCRIPTION_MAX_CHARS = 160;
|
|
33
37
|
// ── Field partition (the coverage guard's two halves) ─────────────────
|
|
34
38
|
/** Fields whose content is agent-facing and MUST appear in the rendered task. */
|
|
35
39
|
export const STEP_TASK_FIELDS = [
|
|
@@ -40,6 +44,9 @@ export const STEP_TASK_FIELDS = [
|
|
|
40
44
|
"constraints",
|
|
41
45
|
"acceptance_criteria",
|
|
42
46
|
"caveats",
|
|
47
|
+
// The step agent reads the template id and the claim identifiers from the
|
|
48
|
+
// step, not from prose: the grounding renders beside the task fields.
|
|
49
|
+
"grounding",
|
|
43
50
|
];
|
|
44
51
|
/**
|
|
45
52
|
* Fields deliberately excluded from the task section: identity/DAG structure
|
|
@@ -70,6 +77,11 @@ export const STEP_NON_TASK_FIELDS = [
|
|
|
70
77
|
// the agent does need to know about them — a dropped entry, or a step that
|
|
71
78
|
// declares none — rides in `caveats`.
|
|
72
79
|
"packages",
|
|
80
|
+
// Withheld for the same reason as `packages`, one layer up: the phrase is how the router and
|
|
81
|
+
// the planner name what they routed on, and the launch has already resolved the reference data
|
|
82
|
+
// it declares. The step agent learns the work from its skill pack, which names each dataset by
|
|
83
|
+
// what it is. Handing it a routing phrase would invite it to treat that phrase as the method.
|
|
84
|
+
"capability",
|
|
73
85
|
];
|
|
74
86
|
// ── Formatting primitives ─────────────────────────────────────────────
|
|
75
87
|
function section(heading, body) {
|
|
@@ -95,7 +107,7 @@ function clamp(text, max) {
|
|
|
95
107
|
* folded in here — otherwise the agent runs against the bare question and
|
|
96
108
|
* improvises the rest.
|
|
97
109
|
*/
|
|
98
|
-
export function renderTask(step) {
|
|
110
|
+
export function renderTask(step, contract = {}) {
|
|
99
111
|
const parts = [`# ${step.name}`, section("Task", step.question)];
|
|
100
112
|
if (step.description?.trim()) {
|
|
101
113
|
parts.push(section("What this step produces", step.description.trim()));
|
|
@@ -112,8 +124,46 @@ export function renderTask(step) {
|
|
|
112
124
|
if (step.caveats && step.caveats.length > 0) {
|
|
113
125
|
parts.push(section("Caveats", bullets(step.caveats)));
|
|
114
126
|
}
|
|
127
|
+
if (step.grounding) {
|
|
128
|
+
parts.push(section("Grounding", renderGrounding(step.grounding, contract)));
|
|
129
|
+
}
|
|
115
130
|
return parts.join("\n\n");
|
|
116
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* The grounding as data lines. The template id is the one value the agent
|
|
134
|
+
* acts on (it names it to `knowledge_template`), thus it renders first after
|
|
135
|
+
* the status. The claim identifiers and the digest are for the record.
|
|
136
|
+
*
|
|
137
|
+
* A grounding that names a template promises a Template contract section.
|
|
138
|
+
* When the seed carries none, one line says so with the reason, thus the
|
|
139
|
+
* agent knows the contract was not fetched rather than guessing at slots.
|
|
140
|
+
*
|
|
141
|
+
* The settings of the procedure ride here as vetted values, not as
|
|
142
|
+
* constraints: the agent uses each one unless the data gives a reason for
|
|
143
|
+
* another value, and then it states the reason. They render only when the
|
|
144
|
+
* seed carries no template contract, because the bound and the unbound
|
|
145
|
+
* lists of the contract hold every setting already.
|
|
146
|
+
*/
|
|
147
|
+
function renderGrounding(grounding, contract) {
|
|
148
|
+
const lines = [`- Status: ${grounding.status}`];
|
|
149
|
+
if (grounding.template) {
|
|
150
|
+
lines.push(`- Template: \`${grounding.template}\``);
|
|
151
|
+
if (!contract.template)
|
|
152
|
+
lines.push(`- Template contract: not retrieved (${contract.templateNotRetrieved ?? "no contract was fetched for this step"})`);
|
|
153
|
+
}
|
|
154
|
+
lines.push(`- Snapshot: ${grounding.snapshot}`);
|
|
155
|
+
lines.push(`- Claims: ${grounding.claims.length > 0 ? grounding.claims.join(", ") : "none"}`);
|
|
156
|
+
lines.push(`- Reason: ${grounding.reason}`);
|
|
157
|
+
const settings = grounding.settings ?? [];
|
|
158
|
+
if (settings.length > 0 && !contract.template) {
|
|
159
|
+
lines.push("- Settings (the vetted values of the procedure; another value is permitted when your summary states the reason):");
|
|
160
|
+
for (const setting of settings) {
|
|
161
|
+
const value = Array.isArray(setting.value) ? setting.value.join(", ") : String(setting.value);
|
|
162
|
+
lines.push(` - ${setting.step}: ${setting.name} = ${value}${setting.source ? ` (${setting.source})` : ""}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return lines.join("\n");
|
|
166
|
+
}
|
|
117
167
|
// ── (1b) Analysis memory ──────────────────────────────────────────────
|
|
118
168
|
/**
|
|
119
169
|
* The heading of the analysis-memory section. The orient core of the sandbox
|
|
@@ -155,6 +205,82 @@ export function renderMemory(memory) {
|
|
|
155
205
|
}
|
|
156
206
|
return section(ANALYSIS_MEMORY_HEADING, parts.join("\n\n"));
|
|
157
207
|
}
|
|
208
|
+
/** A value as the render request carries it: JSON, thus `"apeglm"` and `10` cannot be confused. */
|
|
209
|
+
function slotValue(value) {
|
|
210
|
+
return JSON.stringify(value);
|
|
211
|
+
}
|
|
212
|
+
/** The version of a template reference (`tpl-x@1.0.0` gives `1.0.0`), or `undefined` when the reference has none. */
|
|
213
|
+
function versionOfRef(ref) {
|
|
214
|
+
const at = ref.indexOf("@");
|
|
215
|
+
return at < 0 ? undefined : ref.slice(at + 1);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* One adaptable slot as a bullet: the name, the type, the default with its
|
|
219
|
+
* source or the required state, the description, and the constraints the
|
|
220
|
+
* render enforces. A slot without a default is required unless the contract
|
|
221
|
+
* says otherwise, which is the rule of the renderer.
|
|
222
|
+
*/
|
|
223
|
+
function renderSlot(slot) {
|
|
224
|
+
const facts = [slot.type];
|
|
225
|
+
// A local slot is bound on this machine: the agent sends it as any other slot, and the value never leaves.
|
|
226
|
+
if (slot.local)
|
|
227
|
+
facts.push("local");
|
|
228
|
+
if (slot.default !== undefined) {
|
|
229
|
+
facts.push(`default ${slotValue(slot.default)}${slot.default_source ? ` [${slot.default_source}]` : ""}`);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
facts.push(slot.required === false ? "optional" : "required");
|
|
233
|
+
}
|
|
234
|
+
if (slot.minimum !== undefined)
|
|
235
|
+
facts.push(`min ${slot.minimum}`);
|
|
236
|
+
if (slot.maximum !== undefined)
|
|
237
|
+
facts.push(`max ${slot.maximum}`);
|
|
238
|
+
const tail = [];
|
|
239
|
+
if (slot.enum && slot.enum.length > 0)
|
|
240
|
+
tail.push(`Permitted: ${slot.enum.map(slotValue).join(", ")}.`);
|
|
241
|
+
if (slot.pattern)
|
|
242
|
+
tail.push(`Pattern: \`${slot.pattern}\`.`);
|
|
243
|
+
const description = clamp(slot.description.trim(), TEMPLATE_SLOT_DESCRIPTION_MAX_CHARS);
|
|
244
|
+
return `\`${slot.name}\` (${facts.join("; ")})${description ? `: ${description}` : ""}${tail.length > 0 ? ` ${tail.join(" ")}` : ""}`;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* The Template contract section. Only the adaptable slots render, bounded by
|
|
248
|
+
* {@link MAX_TEMPLATE_SLOTS} and the description clamp, so a wide template
|
|
249
|
+
* cannot blow the seed. A served version that differs from the plan renders
|
|
250
|
+
* as a caveat: the render refuses the plan version, and the settings are not
|
|
251
|
+
* bound to a version the plan did not name.
|
|
252
|
+
*/
|
|
253
|
+
export function renderTemplateContract(brief) {
|
|
254
|
+
if (!brief)
|
|
255
|
+
return "";
|
|
256
|
+
const refVersion = versionOfRef(brief.ref);
|
|
257
|
+
const lead = [
|
|
258
|
+
`\`${brief.ref}\` (the service serves version ${brief.version_served}). Send only the slots listed here to \`knowledge_template\`; a slot not listed is pinned or unknown, and the render refuses it.`,
|
|
259
|
+
];
|
|
260
|
+
if (refVersion !== undefined && refVersion !== brief.version_served) {
|
|
261
|
+
lead.push(`Caveat: the plan names version ${refVersion}, and the service serves version ${brief.version_served}. The plan settings are not bound to this render. Render the served version, and state each plan value yourself.`);
|
|
262
|
+
}
|
|
263
|
+
const blocks = [lead.join("\n")];
|
|
264
|
+
const adaptable = brief.slots.filter((slot) => slot.adaptable);
|
|
265
|
+
if (adaptable.length > 0) {
|
|
266
|
+
const shown = adaptable.slice(0, MAX_TEMPLATE_SLOTS);
|
|
267
|
+
const lines = [bullets(shown.map(renderSlot))];
|
|
268
|
+
const omitted = adaptable.length - shown.length;
|
|
269
|
+
if (omitted > 0)
|
|
270
|
+
lines.push(`(+${omitted} more adaptable ${omitted === 1 ? "slot" : "slots"} not listed. The render answer names a required one that is absent.)`);
|
|
271
|
+
blocks.push(`Slots:\n${lines.join("\n")}`);
|
|
272
|
+
}
|
|
273
|
+
if (brief.inputs.length > 0) {
|
|
274
|
+
blocks.push(`Inputs the script reads:\n${bullets(brief.inputs.map((input) => `${input.name}: \`${input.path}\`${input.description ? ` — ${input.description}` : ""}`))}`);
|
|
275
|
+
}
|
|
276
|
+
if (brief.bound.length > 0) {
|
|
277
|
+
blocks.push(`Bound by the plan (send each value as it is, or add an \`overrides\` entry with the slot and the reason for the change; a changed value without an override is refused):\n${bullets(brief.bound.map((setting) => `\`${setting.name}\` = ${slotValue(setting.value)} (${setting.source})`))}`);
|
|
278
|
+
}
|
|
279
|
+
if (brief.unbound_settings.length > 0) {
|
|
280
|
+
blocks.push(`Unbound settings (the plan states them, and no bound slot carries them):\n${bullets(brief.unbound_settings)}`);
|
|
281
|
+
}
|
|
282
|
+
return section("Template contract", blocks.join("\n\n"));
|
|
283
|
+
}
|
|
158
284
|
/** The in-sandbox analysis root of a frame — the read-only mount of the whole tree. */
|
|
159
285
|
export function analysisRootOf(frame) {
|
|
160
286
|
return `/${frame.analysisId}`;
|
|
@@ -240,7 +366,8 @@ function renderHandoff(handoff) {
|
|
|
240
366
|
*/
|
|
241
367
|
export function composeStepBriefing(briefing) {
|
|
242
368
|
return [
|
|
243
|
-
renderTask(briefing.step),
|
|
369
|
+
renderTask(briefing.step, briefing),
|
|
370
|
+
renderTemplateContract(briefing.template),
|
|
244
371
|
renderMemory(briefing.memory),
|
|
245
372
|
renderWorkspace(briefing.workspace),
|
|
246
373
|
renderResources(briefing.step.resources),
|