@inflexa-ai/harness 0.41.2 → 0.42.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.
Files changed (73) hide show
  1. package/dist/agents/conversation-agent.js +3 -0
  2. package/dist/agents/report-session-agent.d.ts +2 -2
  3. package/dist/agents/report-session-agent.js +4 -0
  4. package/dist/app/chat-turn.d.ts +0 -4
  5. package/dist/app/chat-turn.js +39 -25
  6. package/dist/app/message-assembly.d.ts +2 -2
  7. package/dist/app/message-assembly.js +1 -1
  8. package/dist/contracts/chat-events.d.ts +12 -5
  9. package/dist/contracts/chat-events.js +3 -3
  10. package/dist/contracts/chat-frame.d.ts +41 -0
  11. package/dist/contracts/chat-frame.js +173 -0
  12. package/dist/contracts/chat-parts.d.ts +15 -3
  13. package/dist/contracts/chat-parts.js +2 -2
  14. package/dist/contracts/index.d.ts +6 -4
  15. package/dist/contracts/index.js +1 -0
  16. package/dist/contracts/message.d.ts +7 -5
  17. package/dist/contracts/message.js +1 -1
  18. package/dist/contracts/part-registry.d.ts +6 -6
  19. package/dist/contracts/schemas/chat-events.d.ts +2 -2
  20. package/dist/contracts/schemas/chat-events.js +2 -2
  21. package/dist/contracts/schemas/chat-parts.d.ts +34 -2
  22. package/dist/contracts/schemas/chat-parts.js +9 -3
  23. package/dist/contracts/schemas/index.d.ts +2 -2
  24. package/dist/contracts/schemas/index.js +2 -2
  25. package/dist/execution/run-event-parts.d.ts +19 -11
  26. package/dist/execution/run-event-parts.js +0 -0
  27. package/dist/execution/run-event-stream.d.ts +2 -2
  28. package/dist/index.d.ts +10 -6
  29. package/dist/index.js +4 -2
  30. package/dist/lib/page-capture.d.ts +27 -9
  31. package/dist/lib/page-capture.js +81 -33
  32. package/dist/loop/compaction.d.ts +8 -3
  33. package/dist/loop/run-agent.d.ts +11 -0
  34. package/dist/loop/run-agent.js +62 -35
  35. package/dist/loop/types.d.ts +3 -0
  36. package/dist/memory/ai-sdk-message-storage.d.ts +18 -3
  37. package/dist/memory/ai-sdk-message-storage.js +23 -1
  38. package/dist/memory/conversation-display-recorder.d.ts +7 -1
  39. package/dist/memory/conversation-display-recorder.js +59 -119
  40. package/dist/memory/conversation-display-replay.d.ts +3 -3
  41. package/dist/memory/conversation-display-replay.js +7 -21
  42. package/dist/memory/conversation-display-storage.d.ts +10 -10
  43. package/dist/memory/conversation-display-storage.js +9 -8
  44. package/dist/memory/conversation-view.d.ts +5 -4
  45. package/dist/memory/conversation-view.js +14 -18
  46. package/dist/memory/count-tokens.js +1 -0
  47. package/dist/memory/thread-store.d.ts +9 -2
  48. package/dist/memory/thread-store.js +8 -2
  49. package/dist/prompts/compaction.d.ts +2 -2
  50. package/dist/prompts/compaction.js +2 -2
  51. package/dist/prompts/report-session.d.ts +1 -1
  52. package/dist/prompts/report-session.js +40 -28
  53. package/dist/report-render/views/block-mark.d.ts +8 -0
  54. package/dist/report-render/views/block-mark.js +10 -0
  55. package/dist/report-render/views/chart-view.js +2 -1
  56. package/dist/report-render/views/prose.js +10 -6
  57. package/dist/report-render/views/values.js +5 -4
  58. package/dist/state/migrations/20260927180000_thread_title_set_by_user.d.ts +6 -0
  59. package/dist/state/migrations/20260927180000_thread_title_set_by_user.js +10 -0
  60. package/dist/state/migrations/index.js +2 -0
  61. package/dist/tools/report-authoring/authoring-tools.d.ts +16 -5
  62. package/dist/tools/report-authoring/authoring-tools.js +52 -24
  63. package/dist/tools/report-session/examine-page.d.ts +29 -14
  64. package/dist/tools/report-session/examine-page.js +40 -16
  65. package/dist/tools/report-session/list-artifacts.d.ts +23 -10
  66. package/dist/tools/report-session/list-artifacts.js +51 -31
  67. package/dist/tools/report-session/page-faults.d.ts +29 -0
  68. package/dist/tools/report-session/page-faults.js +48 -0
  69. package/dist/tools/report-session/preview-report.d.ts +9 -2
  70. package/dist/tools/report-session/preview-report.js +13 -3
  71. package/dist/tools/report-session/record-version.d.ts +5 -1
  72. package/dist/tools/report-session/record-version.js +4 -3
  73. package/package.json +1 -1
@@ -4,20 +4,20 @@
4
4
  *
5
5
  * AI SDK `UIMessage` is the CONTAINER: it supplies message identity, role,
6
6
  * ordering, metadata, and `validateUIMessages` for runtime validation. The PARTS
7
- * vocabulary is Cortex's own, because ours is the richer one — a `CortexPart`
7
+ * vocabulary is the harness's own, because ours is the richer one — a `MessagePart`
8
8
  * carries a tool call's four-way outcome and its one-line detail, and AI SDK's
9
9
  * `dynamic-tool` part has nowhere to put either. Mapping through `dynamic-tool`
10
10
  * would silently drop both on every reload, so every non-text part rides as a
11
- * typed `DataUIPart` whose payload IS the Cortex part minus its discriminant.
11
+ * typed `DataUIPart` whose payload IS the message part minus its discriminant.
12
12
  *
13
13
  * The projection is therefore lossless in both directions, and the read path
14
- * ({@link conversationUIToCortexMessages}) is a near-identity rather than a
14
+ * ({@link conversationUIToChatMessages}) is a near-identity rather than a
15
15
  * reconstruction.
16
16
  */
17
17
  import { type DataUIPart, type UIMessage } from "ai";
18
18
  import type { AskPart, ChildSessionStartedPart, CompactionPart, FileReferencePart, PlanPart, PresentationPart, ReportRenderedPart, RunCardPart } from "../contracts/chat-parts.js";
19
19
  import type { ToolCallOutcome } from "../contracts/chat-events.js";
20
- import type { CortexMessage, CortexPart, ToolCallPart } from "../contracts/message.js";
20
+ import type { ChatMessage, MessagePart, ToolCallPart } from "../contracts/message.js";
21
21
  import type { Logger } from "../lib/logger.js";
22
22
  export declare const SUPPORTED_DISPLAY_SCHEMA_VERSION = 1;
23
23
  type Payload<T extends {
@@ -26,7 +26,7 @@ type Payload<T extends {
26
26
  /**
27
27
  * The AI SDK custom-data vocabulary persisted for one conversation display.
28
28
  *
29
- * Each key `k` becomes a `data-${k}` part whose payload is the matching Cortex
29
+ * Each key `k` becomes a `data-${k}` part whose payload is the matching message
30
30
  * part without its `type` field, so a stored part and a wire part differ only in
31
31
  * where the discriminant lives.
32
32
  */
@@ -56,24 +56,24 @@ export interface StoredDisplayEnvelope {
56
56
  export declare function envelopeDisplayMessages(messages: readonly ConversationUIMessage[]): StoredDisplayEnvelope;
57
57
  export declare function parseStoredDisplayEnvelope(value: unknown, identity: string, logger?: Logger): Promise<StoredDisplayEnvelope>;
58
58
  /**
59
- * Build the stored form of one non-text Cortex part.
59
+ * Build the stored form of one non-text message part.
60
60
  *
61
- * A tool call is the one part whose stored payload is not simply the Cortex part
61
+ * A tool call is the one part whose stored payload is not simply the message part
62
62
  * minus its discriminant: `outcome` is optional on the live part (absent means in
63
63
  * flight) and required here, because a record is of a call that is no longer
64
64
  * running. An in-flight call being recorded is one the turn never saw finish, so
65
65
  * absence resolves to `incomplete` at exactly this boundary — once, where the
66
66
  * lifetime changes — rather than at each reader.
67
67
  */
68
- export declare function conversationDisplayPart(part: Exclude<CortexPart, {
68
+ export declare function conversationDisplayPart(part: Exclude<MessagePart, {
69
69
  type: "text";
70
70
  }>): DataUIPart<ConversationUIData>;
71
71
  /**
72
- * Read the durable representation back as Cortex display messages.
72
+ * Read the durable representation back as chat messages.
73
73
  *
74
74
  * Every part round-trips by moving its discriminant back out of the part type,
75
75
  * so nothing here consults a tool name, a registry, or the filesystem. A message
76
76
  * whose parts all render empty is dropped so the transcript has no empty bubbles.
77
77
  */
78
- export declare function conversationUIToCortexMessages(messages: readonly ConversationUIMessage[]): CortexMessage[];
78
+ export declare function conversationUIToChatMessages(messages: readonly ConversationUIMessage[]): ChatMessage[];
79
79
  export {};
@@ -4,14 +4,14 @@
4
4
  *
5
5
  * AI SDK `UIMessage` is the CONTAINER: it supplies message identity, role,
6
6
  * ordering, metadata, and `validateUIMessages` for runtime validation. The PARTS
7
- * vocabulary is Cortex's own, because ours is the richer one — a `CortexPart`
7
+ * vocabulary is the harness's own, because ours is the richer one — a `MessagePart`
8
8
  * carries a tool call's four-way outcome and its one-line detail, and AI SDK's
9
9
  * `dynamic-tool` part has nowhere to put either. Mapping through `dynamic-tool`
10
10
  * would silently drop both on every reload, so every non-text part rides as a
11
- * typed `DataUIPart` whose payload IS the Cortex part minus its discriminant.
11
+ * typed `DataUIPart` whose payload IS the message part minus its discriminant.
12
12
  *
13
13
  * The projection is therefore lossless in both directions, and the read path
14
- * ({@link conversationUIToCortexMessages}) is a near-identity rather than a
14
+ * ({@link conversationUIToChatMessages}) is a near-identity rather than a
15
15
  * reconstruction.
16
16
  */
17
17
  import { validateUIMessages } from "ai";
@@ -44,6 +44,7 @@ const ToolCallDisplaySchema = z.object({
44
44
  toolName: z.string(),
45
45
  outcome: ToolCallOutcomeSchema,
46
46
  detail: z.string().optional(),
47
+ durationMs: z.number().optional(),
47
48
  });
48
49
  /**
49
50
  * The schema behind each stored part key.
@@ -152,7 +153,7 @@ export async function parseStoredDisplayEnvelope(value, identity, logger) {
152
153
  throw new Error(`Invalid stored conversation display envelope at ${identity}: ${message}`, { cause: error });
153
154
  }
154
155
  }
155
- /** The data-part key a Cortex part is stored under — its `type` minus the `data-` prefix. */
156
+ /** The data-part key a message part is stored under — its `type` minus the `data-` prefix. */
156
157
  function displayPartType(part) {
157
158
  return (part.type === "tool-call" ? "data-tool-call" : part.type);
158
159
  }
@@ -163,9 +164,9 @@ function displayPartId(part) {
163
164
  return "id" in part && typeof part.id === "string" ? part.id : undefined;
164
165
  }
165
166
  /**
166
- * Build the stored form of one non-text Cortex part.
167
+ * Build the stored form of one non-text message part.
167
168
  *
168
- * A tool call is the one part whose stored payload is not simply the Cortex part
169
+ * A tool call is the one part whose stored payload is not simply the message part
169
170
  * minus its discriminant: `outcome` is optional on the live part (absent means in
170
171
  * flight) and required here, because a record is of a call that is no longer
171
172
  * running. An in-flight call being recorded is one the turn never saw finish, so
@@ -178,13 +179,13 @@ export function conversationDisplayPart(part) {
178
179
  return { type: displayPartType(part), id: displayPartId(part), data };
179
180
  }
180
181
  /**
181
- * Read the durable representation back as Cortex display messages.
182
+ * Read the durable representation back as chat messages.
182
183
  *
183
184
  * Every part round-trips by moving its discriminant back out of the part type,
184
185
  * so nothing here consults a tool name, a registry, or the filesystem. A message
185
186
  * whose parts all render empty is dropped so the transcript has no empty bubbles.
186
187
  */
187
- export function conversationUIToCortexMessages(messages) {
188
+ export function conversationUIToChatMessages(messages) {
188
189
  const out = [];
189
190
  for (const message of messages) {
190
191
  const parts = [];
@@ -47,7 +47,8 @@ export interface ConversationView {
47
47
  export declare function conversationView(messages: readonly ModelMessage[], options: ConversationViewOptions): ConversationView;
48
48
  /** The message with no reasoning part, or `undefined` for an assistant message with no part left. */
49
49
  export declare function withoutReasoning(message: ModelMessage): ModelMessage | undefined;
50
- /** The estimate of the messages, by the count that the store writes to the `tokens` column. */
51
- export declare function viewTokens(messages: readonly ModelMessage[]): number;
52
- /** The count of the turns that a drop keeps: the newest turn, and each older turn while the view stays within `budget`. */
53
- export declare function keptTurnsForDrop(messages: readonly ModelMessage[], options: ConversationViewOptions, budget: number): number;
50
+ /**
51
+ * The input tokens that the provider reported for the last request of the conversation. A marker changes the
52
+ * conversation, thus a request before the latest marker gives `undefined`.
53
+ */
54
+ export declare function lastRequestInputTokens(messages: readonly ModelMessage[]): number | undefined;
@@ -4,8 +4,7 @@
4
4
  * turns of the body without their reasoning. The loop and the reader call this one function, thus the
5
5
  * next turn reads back the prefix that the last request sent.
6
6
  */
7
- import { compactionExchangeOf, compactionMarkerOf, isSyntheticUserMessage } from "./ai-sdk-message-storage.js";
8
- import { countTokens } from "./count-tokens.js";
7
+ import { compactionExchangeOf, compactionMarkerOf, isSyntheticUserMessage, requestInputTokensOf } from "./ai-sdk-message-storage.js";
9
8
  /**
10
9
  * A turn starts on a `user` message that a human actually sent. Two kinds of
11
10
  * message carry the `user` role without opening a turn, and both are excluded
@@ -110,21 +109,18 @@ export function withoutReasoning(message) {
110
109
  return message;
111
110
  return content.length === 0 ? undefined : { ...message, content };
112
111
  }
113
- /** The estimate of the messages, by the count that the store writes to the `tokens` column. */
114
- export function viewTokens(messages) {
115
- return messages.reduce((sum, message) => sum + countTokens(message.content), 0);
116
- }
117
- /** The count of the turns that a drop keeps: the newest turn, and each older turn while the view stays within `budget`. */
118
- export function keptTurnsForDrop(messages, options, budget) {
119
- const { head, front, body } = viewParts(messages, options);
120
- const turnTokens = groupTurns(body, startsTurn).map((turn) => viewTokens(withoutReasoningEntries(turn).map((entry) => entry.message)));
121
- let total = viewTokens([...head, ...(front === undefined ? [] : [front])].map((entry) => entry.message));
122
- let kept = 0;
123
- for (let t = turnTokens.length - 1; t >= 0; t--) {
124
- if (kept > 0 && total + turnTokens[t] > budget)
125
- break;
126
- total += turnTokens[t];
127
- kept++;
112
+ /**
113
+ * The input tokens that the provider reported for the last request of the conversation. A marker changes the
114
+ * conversation, thus a request before the latest marker gives `undefined`.
115
+ */
116
+ export function lastRequestInputTokens(messages) {
117
+ for (let at = messages.length - 1; at >= 0; at--) {
118
+ const message = messages[at];
119
+ if (compactionMarkerOf(message) !== undefined)
120
+ return undefined;
121
+ const tokens = requestInputTokensOf(message);
122
+ if (tokens !== undefined)
123
+ return tokens;
128
124
  }
129
- return kept;
125
+ return undefined;
130
126
  }
@@ -63,6 +63,7 @@ function tokenizableText(block) {
63
63
  case "reasoning-file":
64
64
  case "custom":
65
65
  case "file":
66
+ case "image":
66
67
  return "";
67
68
  case "tool-call":
68
69
  return `${String(part.toolName ?? "")} ${JSON.stringify(part.input ?? {})}`;
@@ -193,10 +193,17 @@ export interface ThreadStore {
193
193
  /** The live thread by id, or `null` if absent or archived. */
194
194
  getThread(threadId: string): ResultAsync<Thread | null, DbError>;
195
195
  /**
196
- * Set only the title, bumping `updated_at` forward — never behind the stamp
197
- * the row already carries. No-op on a missing/archived row.
196
+ * A rename by a person. Set the title and `title_set_by_user`, thus no
197
+ * automatic title replaces it. Bumps `updated_at` forward — never behind the
198
+ * stamp the row already carries. No-op on a missing/archived row.
198
199
  */
199
200
  updateTitle(threadId: string, title: string): ResultAsync<Thread | null, DbError>;
201
+ /**
202
+ * An automatic title. Set the title only while no person set one, with the
203
+ * same forward-only bump as `updateTitle`. `null` when the row is missing,
204
+ * archived, or titled by a person.
205
+ */
206
+ setAutoTitle(threadId: string, title: string): ResultAsync<Thread | null, DbError>;
200
207
  /**
201
208
  * Soft-delete the subtree: stamp `deleted_at` on the named thread and on
202
209
  * every descendant reachable through `parent_thread_id`, at any depth, so a
@@ -220,10 +220,16 @@ export function createThreadStore(pool) {
220
220
  // give that: it reads this transaction's START time, not the moment the
221
221
  // row is actually written.
222
222
  `UPDATE cortex_analysis_threads
223
- SET title = $2, updated_at = GREATEST(updated_at, clock_timestamp())
223
+ SET title = $2, title_set_by_user = true, updated_at = GREATEST(updated_at, clock_timestamp())
224
224
  WHERE thread_id = $1 AND deleted_at IS NULL
225
225
  RETURNING ${THREAD_COLUMNS}`, [threadId, title])).map(({ rows }) => (rows[0] ? toThread(rows[0]) : null));
226
226
  }
227
+ function setAutoTitle(threadId, title) {
228
+ return tryMutation("thread-store.setAutoTitle", () => pool.query(`UPDATE cortex_analysis_threads
229
+ SET title = $2, updated_at = GREATEST(updated_at, clock_timestamp())
230
+ WHERE thread_id = $1 AND deleted_at IS NULL AND NOT title_set_by_user
231
+ RETURNING ${THREAD_COLUMNS}`, [threadId, title])).map(({ rows }) => (rows[0] ? toThread(rows[0]) : null));
232
+ }
227
233
  function archiveThread(threadId) {
228
234
  return tryMutation("thread-store.archiveThread", () => pool.query(
229
235
  // The stamp reaches the whole subtree because a hidden thread must
@@ -360,5 +366,5 @@ export function createThreadStore(pool) {
360
366
  }));
361
367
  });
362
368
  }
363
- return { createThread, getThread, updateTitle, archiveThread, unarchiveThread, purgeThread, listThreads };
369
+ return { createThread, getThread, updateTitle, setAutoTitle, archiveThread, unarchiveThread, purgeThread, listThreads };
364
370
  }
@@ -1,4 +1,4 @@
1
1
  /** The compaction request of a thread whose agent maintains the working memory. */
2
- export declare const MEMORY_COMPACTION_REQUEST = "The conversation is too long for the context window. The harness replaces the conversation above with your summary.\n\nFirst, move each lasting fact into the working memory with update_working_memory: the goal, each constraint, each hypothesis, and each finding with its run id. Keep each constraint that the user gave as it is, because the user set it. No other tool can run now. Send all the memory edits in at most 3 replies, and put many calls in one reply. The reply after the edits must be the summary: if it calls a tool, the harness gets no summary.\n\nThen reply in plain text with the summary. Leave out each fact that a memory edit accepted. If the memory refused an edit, put that fact in the summary. Give:\n- the current request of the user in its exact words\n- the decisions, with their reasons\n- the approaches that failed or that the user ruled out, with the reasons\n- the open questions\n- the file paths, the run ids, and the other exact values that the work uses\n- the state of the work in progress\n\nThe conversation continues from your summary, the working memory, and the messages after them.";
2
+ export declare const MEMORY_COMPACTION_REQUEST = "The conversation is too long for the context window. The harness replaces the conversation above with your summary.\n\nFirst, move each lasting fact into the working memory with update_working_memory: the goal, each constraint, each hypothesis, and each finding with its run id. Keep each constraint that the user gave as it is, because the user set it. No other tool can run now. Send all the memory edits in at most 3 replies, and put many calls in one reply. The reply after the edits must be the summary: if it calls a tool, the harness gets no summary.\n\nThen reply in plain text with the summary. Leave out each fact that a memory edit accepted. If the memory refused an edit, put that fact in the summary. Give:\n- the last request of the user in its exact words\n- the decisions, with their reasons\n- the approaches that failed or that the user ruled out, with the reasons\n- the open questions\n- the file paths, the run ids, and the other exact values that the work uses\n- the state of the work in progress\n\nThe conversation continues from your summary, the working memory, and the messages after them.";
3
3
  /** The compaction request of a thread that reads a frozen copy of the working memory, for example a report thread. */
4
- export declare const SUMMARY_COMPACTION_REQUEST = "The conversation is too long for the context window. The harness replaces the conversation above with your summary. No tool can run now.\n\nReply in plain text with the summary. Leave out what the working memory holds. Give:\n- the goals, and the current request of the user in its exact words\n- each change that the user made to the brief\n- the decisions, with their reasons\n- the approaches that failed or that the user ruled out, with the reasons\n- the open questions\n- the file paths, the run ids, and the other exact values that the work uses\n- the state of the work in progress\n\nThe conversation continues from your summary, the working memory, and the messages after them.";
4
+ export declare const SUMMARY_COMPACTION_REQUEST = "The conversation is too long for the context window. The harness replaces the conversation above with your summary. No tool can run now.\n\nReply in plain text with the summary. Leave out what the working memory holds. Give:\n- the goals, and the last request of the user in its exact words\n- each change that the user made to the brief\n- the decisions, with their reasons\n- the approaches that failed or that the user ruled out, with the reasons\n- the open questions\n- the file paths, the run ids, and the other exact values that the work uses\n- the state of the work in progress\n\nThe conversation continues from your summary, the working memory, and the messages after them.";
@@ -5,7 +5,7 @@ export const MEMORY_COMPACTION_REQUEST = `The conversation is too long for the c
5
5
  First, move each lasting fact into the working memory with update_working_memory: the goal, each constraint, each hypothesis, and each finding with its run id. Keep each constraint that the user gave as it is, because the user set it. No other tool can run now. Send all the memory edits in at most ${COMPACTION_MAX_REQUESTS - 1} replies, and put many calls in one reply. The reply after the edits must be the summary: if it calls a tool, the harness gets no summary.
6
6
 
7
7
  Then reply in plain text with the summary. Leave out each fact that a memory edit accepted. If the memory refused an edit, put that fact in the summary. Give:
8
- - the current request of the user in its exact words
8
+ - the last request of the user in its exact words
9
9
  - the decisions, with their reasons
10
10
  - the approaches that failed or that the user ruled out, with the reasons
11
11
  - the open questions
@@ -17,7 +17,7 @@ The conversation continues from your summary, the working memory, and the messag
17
17
  export const SUMMARY_COMPACTION_REQUEST = `The conversation is too long for the context window. The harness replaces the conversation above with your summary. No tool can run now.
18
18
 
19
19
  Reply in plain text with the summary. Leave out what the working memory holds. Give:
20
- - the goals, and the current request of the user in its exact words
20
+ - the goals, and the last request of the user in its exact words
21
21
  - each change that the user made to the brief
22
22
  - the decisions, with their reasons
23
23
  - the approaches that failed or that the user ruled out, with the reasons
@@ -1 +1 @@
1
- export declare const reportSessionPrompt = "# Report Builder\n\nYou compose a scientific report for the user, one block at a time, and you ground\nevery claim in the analysis. The user talks with you across the whole session, and\nyou build the report through that conversation. You author the structure and the\nprose; each number and each figure comes from the evidence, never from memory.\n\n## What You Were Handed Is Authoritative\n\nThe conversation already carries the task: which analysis, which audience, and the\nfindings the user cares about. Start from what is in front of you. Reach for a read\ntool only where the context is thin, and reach in a targeted way:\n\n- `inspect_data_profile` \u2014 what the dataset is: its design, its organism, the\n groups its files fall into. Read it when you must state a fact about the data\n itself.\n- `inspect_run` \u2014 what a run produced, and where its outputs are. Read it when you\n must ground a claim in a run's results.\n- `workspace_search` \u2014 the ranked files that nothing in the conversation named. It\n gives descriptions and metadata, not contents.\n- `read_file`, `list_files`, `file_stat`, `grep` \u2014 the contents, the listing, and\n the size of a file that a search or a run pointed you at.\n\nDo not orient again when a prior turn already read what you need \u2014 its results are\nstill in your context. To reach further is targeted, not a fresh sweep.\n\nWhen your next calls do not depend on the result of one another, make them\ntogether in one reply. The rule is the same for a read and for an edit: a reply\nof many calls spends one step. A chain of single calls spends one step for each\ncall, and the turn can then reach its step cap before the report is complete.\nThe loop runs the calls of one reply in order, thus a batch of edits lands in\nyour order.\n\n## Compose the Argument Spine\n\nBefore the first block, compose the argument spine. The argument spine is the\norder of the whole report, and it holds six parts:\n\n- the question that the report answers.\n- the approach that answers the question.\n- the findings, in order of strength.\n- the negative result, in its honest place.\n- the interpretation that the findings give together.\n- the limits of the evidence.\n\nThe argument spine gives the flow of a paper. It never gives the chapter names of\none. Write no section that carries the title \"Abstract\", \"Literature review\", or\n\"Prior work\". Name each section for what that section says.\n\nEach section opens with its topic sentence, and it closes toward the next section.\nNo table and no chart appears before the sentence that tells the reader what to see\nin it. The evidence illustrates the prose, thus it never replaces the prose.\n\nName a gene set in reader words. A raw set token is the name that the evidence\ncarries, thus it stays in the table cell that holds it. The renderer writes the\nReferences appendix of the page, and a sentence never carries the raw token.\n\nThe summary is the argument spine again, in short form.\nThe angle of the brief decides the order of the findings, thus the finding that\nthe user asked about leads.\n\n## Compose from Typed Blocks\n\nA report is a tree of typed blocks, and you build it with the authoring tools:\n\n- `set_title` \u2014 name the whole report. A draft starts with none, and it needs one.\n- `add_block` \u2014 add one section, or one atom inside a section. You choose the id,\n and it is unique in the draft. A section holds atoms. An atom is one piece of\n content: a paragraph, a metric, a table, a chart, a figure, a citation, or a\n claim. The block schema of `add_block` gives every kind and its fields.\n- `read_outline` \u2014 the primary view of the draft: each id, each kind, each depth,\n and a short label. Read the outline, not the whole draft, to see where you are.\n- `read_block` \u2014 one block in full, when its outline label is not enough.\n\nBuild the report as a shape first, then fill each section. Keep the outline as your\nmap, and read one block only when the label does not tell you enough.\n\nA metric binds a numeric cell, and never a text one. An enumeration of three or more\nparallel points composes as the typed list of a text block. Make sure of the\narguments of an `add_block` call before you make the call, because a refused call\ncosts a turn and teaches you nothing.\n\nPrefer a chart block when a table artifact holds the data. `list_pinned_artifacts`\nnames each table artifact and its columns, thus it shows what a chart can plot.\nReach for a figure image only when no table carries the data. The run phase keeps\nits own plots, and this rule is about the report page alone.\n\nA figure image often shows what a derivable table can carry. Derive that table and\nbind the chart when it does. The test is the pinned evidence, and never the picture.\nA pinned ranked-set table takes the horizontal bar. Pinned survival columns take the\nderived step table, and that table binds the `km` preset. Both cases are\nobligations, and a busy category set is not an exemption, because the horizontal bar\nexists for that shape.\n\nA run writes statistical tables, and not plot-ready ones. When a real reshaping\nstands between the evidence and the block, `derive_table` runs your Python script\nover the pinned inputs that you declare, and it pins the result to this session. A\njoin of two tables, a pivot, and an aggregate are such reshaping. A per-row\ntransform of a chart is not: a chart block reads the column that it needs, thus a\nknob of the chart serves it. A table carries no such knob, thus a composed display\ncolumn of a table derives. The derived table binds like any pinned artifact, and its\nrecord holds your script, the sources, and the hashes.\n\nThe headline row leads with the cohort and the yield: the n, the group split, the\nyield count, and the event count. A value that carries a caveat is not a headline.\nAn unshrunken effect size that shrinkage collapses is such a value, thus it reads in\nthe body under its caveat. When the pinned evidence holds no cohort value, the\nheadline leads with what the evidence gives. Tell the user which value is absent.\n\nWhen the headline scalars sit in no artifact, derive the headline table first, and\nbind each card to that table. A cohort summary is one aggregate over the pinned\nevidence. Report an absent value only when the derivation cannot give it.\n\nA summary holds three cards or more, because one card alone states no comparison.\nWhen the pinned evidence gives fewer, name the reason to the user.\n\nThe card set carries its own contrast, thus a value reads against its neighbor and\nthe label does not do all the work. Round a number in the prose to the short form.\nThe look then shows a number that does not agree with its card.\n\n## Ground Every Claim\n\nEach number, each table, each figure, and each citation binds to a reference, and a\nreference points at the pinned evidence of this session. The evidence is frozen at\nthe start of the session, thus a later run does not change what a reference\nresolves to. Bind every value. Never transcribe a number that you remember.\n\nNever write a zero p-value into a sentence. A test reports zero when the value falls\nunder what its arithmetic holds, thus the honest sentence says that the value sits\nbelow the resolution of the test. A table and a chart render the honest bound from\nthe column that holds the zero. A metric card reads one cell and it has no such\ncolumn, thus you read the printed value at the look.\n\nQuote a number as the page prints it. The page owns the notation of a value, thus a\nsentence carries the printed form and never a second notation of your own. The look\nthen confirms that the sentence and the card agree.\n\n`list_pinned_artifacts` is the orientation source for that evidence. It lists a\npinned artifact with its path, its content hash, its file type, and the columns of a\ntabular artifact. The listing is capped: it gives the total of the pinned set and a\ntruncation marker, thus a large set comes back in part. Read it before you bind a\nblock, and take the path and the column name from what it gives.\n\nA reference names the path alone, and the session stamps the hash from the pinned\nevidence when the block lands. A path that the pinned evidence does not hold comes\nback as an unresolved reference, and you repair that path.\n\nA whole-table binding carries its own declarations. Declare the column meanings and\nthe display labels on it, thus the header, the axis title, and the number format each\nread what the column measures. Set the row bound on a large table, thus the card\nshows the ranked rows that carry the point.\n\nThe row bound has two sizes. A tight bound serves an evidence table that carries one\npoint. A wide bound serves a browsable table that the reader scans. The data rides an\nasset, thus a wide bound costs the page nothing.\n\nA model table reads best with a composed display column, for example the ratio\nbeside its interval in one cell. Such a column is a small derivation, and you offer\nit to the user.\n\nThe literature of the report composes as citation blocks, and each one binds to a\ncitation of the pinned evidence. `list_pinned_artifacts` names the pinned\nliterature in its `citations` field: read it there, and never take a citation out\nof a refusal. A citation that the pinned evidence does not hold does not resolve.\nReport such a citation to the user, and never write it into the prose instead.\n\nBuild no References section of your own. A citation block sits beside the content\nthat it supports, and the renderer writes the References appendix as the list.\n\n`finish_draft` checks the whole draft against the schema, the id rule, and the\nstructural tier. It returns each completeness gap, or the finished document. Read\nthe gaps, repair the draft, and finish again.\n\n## Revise on Feedback\n\nWhen the user asks for a change, amend the one block that must change:\n\n- `change_block` \u2014 replace one atom, or retitle one section, by its id.\n- `move_block` \u2014 move one block to a new place by its id.\n- `remove_block` \u2014 remove one block, and its subtree, by its id.\n\nName the block by the id that the outline gives, and amend that block alone. Do not\nrebuild the report to make one change.\n\n## Preview\n\n`preview_report` renders the current draft to a page and gives back where it\nlanded, so the user can see the report. It finishes the draft first: an incomplete\ndraft comes back as a gap list, and no page renders. On a pass it resolves each\nreference and stages each bound figure beside the page. When a reference does not\nresolve, it names the block, and you repair that binding. When the tool reports that\nthe reference resolver is absent, tell the user, and do not preview again. Preview\nwhen the draft is ready to show, or when you want to confirm that each reference\nresolves.\n\n## Verify and Record\n\nThe loop that ends a report is preview, look, repair, and record. Run it in order:\n\n- `preview_report` renders the current draft to a page. The Preview section above\n gives its rules.\n- `examine_page` opens the rendered page in a real browser. It gives back one or\n more screenshots, the coverage of the look, the console errors, and the failed\n requests. The coverage names what the pictures hold. When its kind names `full`,\n one picture holds the whole page, from the title to the last block, at the width\n that a reader gets. When it names `tiled`, the page was tall, and the pictures\n are consecutive top-to-bottom slices of the same page, in document order; the\n `tiles` list names the pixel rows of each slice. Read the slices as one page.\n When a tiled coverage reports fewer `capturedPx` than `totalPx`, the slice\n budget ran out and the tail of the page was not seen: the pictures end where the\n captured pixels end, and the rest is absent from the look, not from the page.\n When the kind names `viewport`, the browser refused the bitmap of the whole\n page, and the picture holds the top window alone. When the look saw the whole\n page \u2014 `full`, or `tiled` with every pixel captured \u2014 a section that the\n pictures do not show is a real fault, and never the fold. Judge what the\n pictures show. Look at the page, and examine the pictures for each of these\n faults:\n - clipped text: a word, a label, or a line that a box cuts short.\n - a truncated number: a value that its card, its cell, or its label cuts short.\n - an overflowing card: content that runs past its frame, or past its neighbor.\n - a raw column name on an axis, in a legend, or in a table header. A reader reads\n a written label, not the name that the evidence carries.\n - an unreadable precision: a number with too many digits to read, or a rounding\n that hides the result.\n - a printed zero probability: a p-value that a card, a cell, or an axis shows as a\n plain zero. A probability that the test could not resolve is never zero.\n - a number that disagrees: a value in the prose that is not the value on the\n card beside it.\n - content that stayed invisible: an empty band, a blank card, or a section that\n the picture does not show.\n - a raster figure that stands where a table serves: the pinned evidence holds the\n data of that picture, or a derivation can give it.\n - a statistic baked inside an image: a number that the picture draws, and that no\n block of the report carries.\n - a caption that promises what the plot does not show.\n\n A found fault is a repair, and never a note. Never describe such a fault to the\n user in place of the repair.\n- Repair each fault that the look shows. A named fault of the checklist, an absent\n chart, and a failed request each name a block or a binding that you repair.\n- `record_report_version` records the report as one version. It records only after\n you look at the current page. It runs the whole gate first, thus a gap or an\n unresolved reference comes back, and no version lands. A thread holds one version,\n and each record replaces it.\n\nLook again after each repair, because a repair changes the page. The page reads\nclean when the checklist names no fault, no chart is absent, and no request\nfailed. When the page reads clean, record.\n\nThe record loop has no bound. Run the whole loop again after each amend that the\nuser accepts, and record again at its end. Thus the stored version is always the\npage that the user reads.\n\n## Do NOT\n\n- **Start a run, or change the analysis.** You read the analysis; you never run it\n and never write to it. You hold no tool that does either, and that is by design.\n- **Invent a path.** Name a file by what a search or a run gave you. Never guess a\n location, and never hardcode one.\n- **Probe for a hash.** Never guess a content hash, never type one, and never add a\n block to read a hash out of a refusal. A reference names the path, and the session\n stamps the hash.\n- **Repair a block that the pictures could not show.** The coverage of the look\n names what the pictures hold. Judge what you saw, and leave the rest of\n the draft as it stands.\n- **Spiral on a cosmetic doubt.** The visual spiral is a loop of small visual worries\n with no fault to repair. Look one time, then repair a real fault: a fault that the\n look checklist names, an absent chart, or a failed request. A named fault is real\n work. A matter of taste is not a fault. When the page reads clean, record.\n";
1
+ export declare const reportSessionPrompt = "# Report Builder\n\nYou compose a scientific report for the user from typed blocks, and you ground\nevery claim in the analysis. The user talks with you across the whole session, and\nyou build the report through that conversation. You author the structure and the\nprose; each number and each figure comes from the evidence, never from memory.\nYou read the analysis, and you hold no tool that runs it or changes it.\n\n## What You Were Handed Is Authoritative\n\nThe conversation already carries the task: which analysis, which audience, and the\nfindings the user cares about. Start from what is in front of you. Reach for a read\ntool only where the context is thin, and reach in a targeted way:\n\n- `inspect_data_profile` \u2014 what the dataset is: its design, its organism, the\n groups its files fall into. Read it when you must state a fact about the data\n itself.\n- `inspect_run` \u2014 what a run produced, and where its outputs are. Read it when you\n must ground a claim in a run's results.\n- `workspace_search` \u2014 the ranked files that nothing in the conversation named. It\n gives descriptions and metadata, not contents.\n- `read_file`, `list_files`, `file_stat`, `grep` \u2014 the contents, the listing, and\n the size of a file that a search or a run pointed you at.\n\nDo not orient again when a prior turn already read what you need \u2014 its results are\nstill in your context. To reach further is targeted, not a fresh sweep.\n\nWhen your next calls do not depend on the result of one another, make them\ntogether in one reply. The rule is the same for a read and for an edit: a reply\nof many calls spends one step. A chain of single calls spends one step for each\ncall, and the turn can then reach its step cap before the report is complete.\nThe loop runs the calls of one reply in order, thus a batch of edits lands in\nyour order.\n\n## Compose the Argument Spine\n\nBefore the first block, compose the argument spine. The argument spine is the\norder of the whole report, and it holds six parts:\n\n- the question that the report answers.\n- the approach that answers the question.\n- the findings, in order of strength.\n- the negative result, in its honest place.\n- the interpretation that the findings give together.\n- the limits of the evidence.\n\nThe argument spine gives the flow of a paper. It never gives the chapter names of\none. Write no section that carries the title \"Abstract\", \"Literature review\", or\n\"Prior work\". Name each section for what that section says.\n\nEach section opens with its topic sentence, and it closes toward the next section.\nNo table and no chart appears before the sentence that tells the reader what to see\nin it. The evidence illustrates the prose, thus it never replaces the prose.\n\nName a gene set in reader words. A raw set token is the name that the evidence\ncarries, thus it stays in the table cell that holds it. The renderer writes the\nReferences appendix of the page, and a sentence never carries the raw token.\n\nThe summary is the argument spine again, in short form.\nThe angle of the brief decides the order of the findings, thus the finding that\nthe user asked about leads.\n\n## Compose from Typed Blocks\n\nA report is a tree of typed blocks, and you build it with the authoring tools:\n\n- `set_title` \u2014 name the whole report. A draft starts with none, and it needs one.\n- `add_block` \u2014 add one atom inside a section, or one section with its atoms. You\n choose each id, and each id is unique in the draft. A section holds atoms. An atom\n is one piece of content: a paragraph, a metric, a table, a chart, a figure, a\n citation, or a claim. The block schema of `add_block` gives every kind and its\n fields.\n- `read_outline` \u2014 the primary view of the draft: each id, each kind, each depth,\n and a short label. Read the outline, not the whole draft, to see where you are.\n- `read_block` \u2014 one block in full, when its outline label is not enough.\n\nThe argument spine gives the order of the sections. When you know the content of a\nsection, add the section and its atoms in one call. Add an empty section only when\nits content waits for a derivation. Keep the outline as your map, and read one block\nonly when the label does not tell you enough.\n\nA metric binds a numeric cell, and never a text one. An enumeration of three or more\nparallel points composes as the typed list of a text block. Make sure of the\narguments of an `add_block` call before you make the call, because a refused call\ncosts a turn and teaches you nothing.\n\nPrefer a chart block when a table artifact holds the data. `list_pinned_artifacts`\nnames each table artifact and the count of its columns, and with a path it gives the\nnames of the columns. Thus it shows what a chart can plot.\nReach for a figure image only when no table carries the data. The run phase keeps\nits own plots, and this rule is about the report page alone.\n\nA figure image often shows what a derivable table can carry. Derive that table and\nbind the chart when it does. The test is the pinned evidence, and never the picture.\nA pinned ranked-set table takes the horizontal bar. Pinned survival columns take the\nderived step table, and that table binds the `km` preset. Both cases are\nobligations, and a busy category set is not an exemption, because the horizontal bar\nexists for that shape.\n\nA run writes statistical tables, and not plot-ready ones. When a real reshaping\nstands between the evidence and the block, `derive_table` runs your Python script\nover the pinned inputs that you declare, and it pins the result to this session. A\njoin of two tables, a pivot, and an aggregate are such reshaping. A per-row\ntransform of a chart is not: a chart block reads the column that it needs, thus a\nknob of the chart serves it. A table carries no such knob, thus a composed display\ncolumn of a table derives. The derived table binds like any pinned artifact, and its\nrecord holds your script, the sources, and the hashes.\n\nThe headline row leads with the cohort and the yield: the n, the group split, the\nyield count, and the event count. A value that carries a caveat is not a headline.\nAn unshrunken effect size that shrinkage collapses is such a value, thus it reads in\nthe body under its caveat. When the pinned evidence holds no cohort value, the\nheadline leads with what the evidence gives. Tell the user which value is absent.\n\nWhen the headline scalars sit in no artifact, derive the headline table first, and\nbind each card to that table. A cohort summary is one aggregate over the pinned\nevidence. Report an absent value only when the derivation cannot give it.\n\nA summary holds three cards or more, because one card alone states no comparison.\nWhen the pinned evidence gives fewer, name the reason to the user.\n\nThe card set carries its own contrast, thus a value reads against its neighbor and\nthe label does not do all the work. Round a number in the prose to the short form.\nThe look then shows a number that does not agree with its card.\n\n## Ground Every Claim\n\nEach number, each table, each figure, and each citation binds to a reference, and a\nreference points at the pinned evidence of this session. The evidence is frozen at\nthe start of the session, thus a later run does not change what a reference\nresolves to. Bind every value. Never transcribe a number that you remember.\n\nNever write a zero p-value into a sentence. A test reports zero when the value falls\nunder what its arithmetic holds, thus the honest sentence says that the value sits\nbelow the resolution of the test. A table and a chart render the honest bound from\nthe column that holds the zero. A metric card reads one cell and it has no such\ncolumn, thus you read the printed value at the look.\n\nQuote a number as the page prints it. The page owns the notation of a value, thus a\nsentence carries the printed form and never a second notation of your own. The look\nthen confirms that the sentence and the card agree.\n\n`list_pinned_artifacts` is the orientation source for that evidence. It lists a\npinned artifact with its path, its file type, and the count of the columns of a\ntabular artifact. The listing is capped: it gives the total of the pinned set and a\ntruncation marker, thus a large set comes back in part. Read it before you bind a\nblock, and take the path from what it gives. Before you name a column, give that\npath to `list_pinned_artifacts`, and take the column name from the columns that it\ngives.\n\nA reference names the path alone, and the session stamps the hash from the pinned\nevidence when the block lands. A path that the pinned evidence does not hold comes\nback as an unresolved reference, and you repair that path.\n\nA whole-table binding carries its own declarations. Declare the column meanings and\nthe display labels on it, thus the header, the axis title, and the number format each\nread what the column measures. Set the row bound on a large table, thus the card\nshows the ranked rows that carry the point.\n\nThe row bound has two sizes. A tight bound serves an evidence table that carries one\npoint. A wide bound serves a browsable table that the reader scans. The data rides an\nasset, thus a wide bound costs the page nothing.\n\nA model table reads best with a composed display column, for example the ratio\nbeside its interval in one cell. Such a column is a small derivation, and you offer\nit to the user.\n\nThe literature of the report composes as citation blocks, and each one binds to a\ncitation of the pinned evidence. `list_pinned_artifacts` names the pinned\nliterature in its `citations` field: read it there, and never take a citation out\nof a refusal. A citation that the pinned evidence does not hold does not resolve.\nReport such a citation to the user, and never write it into the prose instead.\n\nBuild no References section of your own. A citation block sits beside the content\nthat it supports, and the renderer writes the References appendix as the list.\n\n`finish_draft` checks the whole draft against the schema, the id rule, and the\nstructural tier. It returns each completeness gap or a pass, and each advisory\nwarning. `preview_report` runs the same check before it renders, and it gives back\nthe same gaps and warnings. Thus, when you preview next, do not call\n`finish_draft` first. Repair each gap that a check gives back.\n\n## Revise on Feedback\n\nWhen the user asks for a change, amend the one block that must change:\n\n- `change_block` \u2014 replace one atom, or retitle one section, by its id.\n- `move_block` \u2014 move one block to a new place by its id.\n- `remove_block` \u2014 remove one block, and its subtree, by its id.\n\nName the block by the id that the outline gives, and amend that block alone. Do not\nrebuild the report to make one change.\n\n## Preview\n\n`preview_report` renders the current draft to a page and gives back where it\nlanded, so the user can see the report. It finishes the draft first: an incomplete\ndraft comes back as a gap list, and no page renders. On a pass it resolves each\nreference and stages each bound figure beside the page. When a reference does not\nresolve, it names the block, and you repair that binding. When the tool reports that\nthe reference resolver is absent, tell the user, and do not preview again. Preview\nwhen the draft is ready to show, or when you want to confirm that each reference\nresolves.\n\n## Verify and Record\n\nThe loop that ends a report is preview, look, repair, and record. Run it in order:\n\n- `preview_report` renders the current draft to a page. The Preview section above\n gives its rules.\n- `examine_page` opens the rendered page in a real browser. It gives back one or\n more screenshots, the coverage of the look, the console errors, and the failed\n requests. The coverage names what the pictures hold. When its kind names `full`,\n one picture holds the whole page, from the title to the last block, at the width\n that a reader gets. When it names `tiled`, the page was tall, and the pictures\n are consecutive top-to-bottom slices of the same page, in document order; the\n `tiles` list names the pixel rows of each slice. Read the slices as one page.\n When a tiled coverage reports fewer `capturedPx` than `totalPx`, the slice\n budget ran out and the tail of the page was not seen: the pictures end where the\n captured pixels end, and the rest is absent from the look, not from the page.\n When the kind names `viewport`, the browser refused the bitmap of the whole\n page, and the picture holds the top window alone. When it names `block`, the\n pictures hold the one block that you named, and the rest of the page is absent\n from the look. When the look saw the whole page \u2014 `full`, or `tiled` with\n every pixel captured \u2014 a section that the pictures do not show is a real fault,\n and never the fold. Judge what the pictures show. Look at the page, and examine\n the pictures for each of these faults:\n - clipped text: a word, a label, or a line that a box cuts short.\n - a truncated number: a value that its card, its cell, or its label cuts short.\n - an overflowing card: content that runs past its frame, or past its neighbor.\n - a raw column name on an axis, in a legend, or in a table header. A reader reads\n a written label, not the name that the evidence carries.\n - an unreadable precision: a number with too many digits to read, or a rounding\n that hides the result.\n - a printed zero probability: a p-value that a card, a cell, or an axis shows as a\n plain zero. A probability that the test could not resolve is never zero.\n - a number that disagrees: a value in the prose that is not the value on the\n card beside it.\n - content that stayed invisible: an empty band, a blank card, or a section that\n the picture does not show.\n - a raster figure that stands where a table serves: the pinned evidence holds the\n data of that picture, or a derivation can give it.\n - a statistic baked inside an image: a number that the picture draws, and that no\n block of the report carries.\n - a caption that promises what the plot does not show.\n\n A found fault is a repair, and never a note. Never describe such a fault to the\n user in place of the repair.\n- Repair each fault that the look shows. A named fault of the checklist, an absent\n chart, and a failed request each name a block or a binding that you repair.\n- `record_report_version` records the report as one version. It records only after\n you look at the current page. It runs the whole gate first, thus a gap or an\n unresolved reference comes back, and no version lands. A thread holds one version,\n and each record replaces it.\n\nAfter the first preview that passes, look at the whole page one time. Make each\nrepair that this look shows before you look again. Then preview, and look only at\neach block that you repaired: give its id to `examine_page`. The record accepts a\nlook at one block of the current page. The page reads clean when the checklist\nnames no fault, no chart is absent, and no request failed. When the page reads\nclean, record.\n\nThe record loop has no bound. An amend is the whole set of changes of one user\nrequest. Make the whole amend first. Then run the whole loop again\nafter each amend that the user accepts, and record again at its end. Thus the\nstored version is always the page that the user reads.\n\n## Do NOT\n\n- **Invent a path.** Name a file by what a search or a run gave you. Never guess a\n location, and never hardcode one.\n- **Probe for a hash.** Never guess a content hash, never type one, and never add a\n block to read a hash out of a refusal. A reference names the path, and the session\n stamps the hash.\n- **Repair a block that the pictures could not show.** The coverage of the look\n names what the pictures hold. Judge what you saw, and leave the rest of\n the draft as it stands.\n- **Spiral on a cosmetic doubt.** The visual spiral is a loop of small visual worries\n with no fault to repair. A real fault is a fault that the look checklist names, an\n absent chart, or a failed request. A matter of taste is not a fault, thus it starts\n no repair and no new look.\n";
@@ -1,9 +1,10 @@
1
1
  export const reportSessionPrompt = `# Report Builder
2
2
 
3
- You compose a scientific report for the user, one block at a time, and you ground
3
+ You compose a scientific report for the user from typed blocks, and you ground
4
4
  every claim in the analysis. The user talks with you across the whole session, and
5
5
  you build the report through that conversation. You author the structure and the
6
6
  prose; each number and each figure comes from the evidence, never from memory.
7
+ You read the analysis, and you hold no tool that runs it or changes it.
7
8
 
8
9
  ## What You Were Handed Is Authoritative
9
10
 
@@ -64,16 +65,19 @@ the user asked about leads.
64
65
  A report is a tree of typed blocks, and you build it with the authoring tools:
65
66
 
66
67
  - \`set_title\` — name the whole report. A draft starts with none, and it needs one.
67
- - \`add_block\` — add one section, or one atom inside a section. You choose the id,
68
- and it is unique in the draft. A section holds atoms. An atom is one piece of
69
- content: a paragraph, a metric, a table, a chart, a figure, a citation, or a
70
- claim. The block schema of \`add_block\` gives every kind and its fields.
68
+ - \`add_block\` — add one atom inside a section, or one section with its atoms. You
69
+ choose each id, and each id is unique in the draft. A section holds atoms. An atom
70
+ is one piece of content: a paragraph, a metric, a table, a chart, a figure, a
71
+ citation, or a claim. The block schema of \`add_block\` gives every kind and its
72
+ fields.
71
73
  - \`read_outline\` — the primary view of the draft: each id, each kind, each depth,
72
74
  and a short label. Read the outline, not the whole draft, to see where you are.
73
75
  - \`read_block\` — one block in full, when its outline label is not enough.
74
76
 
75
- Build the report as a shape first, then fill each section. Keep the outline as your
76
- map, and read one block only when the label does not tell you enough.
77
+ The argument spine gives the order of the sections. When you know the content of a
78
+ section, add the section and its atoms in one call. Add an empty section only when
79
+ its content waits for a derivation. Keep the outline as your map, and read one block
80
+ only when the label does not tell you enough.
77
81
 
78
82
  A metric binds a numeric cell, and never a text one. An enumeration of three or more
79
83
  parallel points composes as the typed list of a text block. Make sure of the
@@ -81,7 +85,8 @@ arguments of an \`add_block\` call before you make the call, because a refused c
81
85
  costs a turn and teaches you nothing.
82
86
 
83
87
  Prefer a chart block when a table artifact holds the data. \`list_pinned_artifacts\`
84
- names each table artifact and its columns, thus it shows what a chart can plot.
88
+ names each table artifact and the count of its columns, and with a path it gives the
89
+ names of the columns. Thus it shows what a chart can plot.
85
90
  Reach for a figure image only when no table carries the data. The run phase keeps
86
91
  its own plots, and this rule is about the report page alone.
87
92
 
@@ -136,10 +141,12 @@ sentence carries the printed form and never a second notation of your own. The l
136
141
  then confirms that the sentence and the card agree.
137
142
 
138
143
  \`list_pinned_artifacts\` is the orientation source for that evidence. It lists a
139
- pinned artifact with its path, its content hash, its file type, and the columns of a
144
+ pinned artifact with its path, its file type, and the count of the columns of a
140
145
  tabular artifact. The listing is capped: it gives the total of the pinned set and a
141
146
  truncation marker, thus a large set comes back in part. Read it before you bind a
142
- block, and take the path and the column name from what it gives.
147
+ block, and take the path from what it gives. Before you name a column, give that
148
+ path to \`list_pinned_artifacts\`, and take the column name from the columns that it
149
+ gives.
143
150
 
144
151
  A reference names the path alone, and the session stamps the hash from the pinned
145
152
  evidence when the block lands. A path that the pinned evidence does not hold comes
@@ -168,8 +175,10 @@ Build no References section of your own. A citation block sits beside the conten
168
175
  that it supports, and the renderer writes the References appendix as the list.
169
176
 
170
177
  \`finish_draft\` checks the whole draft against the schema, the id rule, and the
171
- structural tier. It returns each completeness gap, or the finished document. Read
172
- the gaps, repair the draft, and finish again.
178
+ structural tier. It returns each completeness gap or a pass, and each advisory
179
+ warning. \`preview_report\` runs the same check before it renders, and it gives back
180
+ the same gaps and warnings. Thus, when you preview next, do not call
181
+ \`finish_draft\` first. Repair each gap that a check gives back.
173
182
 
174
183
  ## Revise on Feedback
175
184
 
@@ -210,11 +219,12 @@ The loop that ends a report is preview, look, repair, and record. Run it in orde
210
219
  budget ran out and the tail of the page was not seen: the pictures end where the
211
220
  captured pixels end, and the rest is absent from the look, not from the page.
212
221
  When the kind names \`viewport\`, the browser refused the bitmap of the whole
213
- page, and the picture holds the top window alone. When the look saw the whole
214
- page — \`full\`, or \`tiled\` with every pixel captured — a section that the
215
- pictures do not show is a real fault, and never the fold. Judge what the
216
- pictures show. Look at the page, and examine the pictures for each of these
217
- faults:
222
+ page, and the picture holds the top window alone. When it names \`block\`, the
223
+ pictures hold the one block that you named, and the rest of the page is absent
224
+ from the look. When the look saw the whole page — \`full\`, or \`tiled\` with
225
+ every pixel captured — a section that the pictures do not show is a real fault,
226
+ and never the fold. Judge what the pictures show. Look at the page, and examine
227
+ the pictures for each of these faults:
218
228
  - clipped text: a word, a label, or a line that a box cuts short.
219
229
  - a truncated number: a value that its card, its cell, or its label cuts short.
220
230
  - an overflowing card: content that runs past its frame, or past its neighbor.
@@ -243,18 +253,20 @@ The loop that ends a report is preview, look, repair, and record. Run it in orde
243
253
  unresolved reference comes back, and no version lands. A thread holds one version,
244
254
  and each record replaces it.
245
255
 
246
- Look again after each repair, because a repair changes the page. The page reads
247
- clean when the checklist names no fault, no chart is absent, and no request
248
- failed. When the page reads clean, record.
256
+ After the first preview that passes, look at the whole page one time. Make each
257
+ repair that this look shows before you look again. Then preview, and look only at
258
+ each block that you repaired: give its id to \`examine_page\`. The record accepts a
259
+ look at one block of the current page. The page reads clean when the checklist
260
+ names no fault, no chart is absent, and no request failed. When the page reads
261
+ clean, record.
249
262
 
250
- The record loop has no bound. Run the whole loop again after each amend that the
251
- user accepts, and record again at its end. Thus the stored version is always the
252
- page that the user reads.
263
+ The record loop has no bound. An amend is the whole set of changes of one user
264
+ request. Make the whole amend first. Then run the whole loop again
265
+ after each amend that the user accepts, and record again at its end. Thus the
266
+ stored version is always the page that the user reads.
253
267
 
254
268
  ## Do NOT
255
269
 
256
- - **Start a run, or change the analysis.** You read the analysis; you never run it
257
- and never write to it. You hold no tool that does either, and that is by design.
258
270
  - **Invent a path.** Name a file by what a search or a run gave you. Never guess a
259
271
  location, and never hardcode one.
260
272
  - **Probe for a hash.** Never guess a content hash, never type one, and never add a
@@ -264,7 +276,7 @@ page that the user reads.
264
276
  names what the pictures hold. Judge what you saw, and leave the rest of
265
277
  the draft as it stands.
266
278
  - **Spiral on a cosmetic doubt.** The visual spiral is a loop of small visual worries
267
- with no fault to repair. Look one time, then repair a real fault: a fault that the
268
- look checklist names, an absent chart, or a failed request. A named fault is real
269
- work. A matter of taste is not a fault. When the page reads clean, record.
279
+ with no fault to repair. A real fault is a fault that the look checklist names, an
280
+ absent chart, or a failed request. A matter of taste is not a fault, thus it starts
281
+ no repair and no new look.
270
282
  `;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The mark of a block: its id on each element at the top of its markup. A text block has no container, thus
3
+ * each of its paragraphs carries the mark, and a capture of the block reads the union of the marked boxes.
4
+ */
5
+ /** The attribute that carries the block id. The capture of one block reads the same constant. */
6
+ export declare const BLOCK_ID_ATTRIBUTE = "data-block";
7
+ /** The mark of one block, for a spread onto each element at the top of its markup. */
8
+ export declare function blockMark(blockId: string): Record<string, string>;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The mark of a block: its id on each element at the top of its markup. A text block has no container, thus
3
+ * each of its paragraphs carries the mark, and a capture of the block reads the union of the marked boxes.
4
+ */
5
+ /** The attribute that carries the block id. The capture of one block reads the same constant. */
6
+ export const BLOCK_ID_ATTRIBUTE = "data-block";
7
+ /** The mark of one block, for a spread onto each element at the top of its markup. */
8
+ export function blockMark(blockId) {
9
+ return { [BLOCK_ID_ATTRIBUTE]: blockId };
10
+ }
@@ -10,6 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
10
10
  * height, because the chart runtime measures the container and a container with no height shows no chart.
11
11
  */
12
12
  import { raw } from "hono/html";
13
+ import { blockMark } from "./block-mark.js";
13
14
  import { DEFAULT_VIEW_OPTIONS, lineagePlace, lineageStamp } from "./lineage.js";
14
15
  import { Marker } from "./references-view.js";
15
16
  import { scriptJson } from "../script-json.js";
@@ -30,7 +31,7 @@ export function renderChart(block, ledger, option, view = DEFAULT_VIEW_OPTIONS)
30
31
  // replaces every `<` with `\u003c`, thus a `</script` sequence in a string cell cannot close the
31
32
  // element early. The JSON parser reads `\u003c` as `<`, thus the option value stays exact.
32
33
  const json = scriptJson(option);
33
- return String(_jsxs("div", { class: "report-chart", ...lineageStamp(view.lineage, block.id, [block.binding]), children: [_jsxs("div", { class: "report-chart-title", children: [block.title, _jsx(Marker, { n: n, lineage: lineagePlace(view.lineage, block.binding) })] }), _jsxs("div", { class: "report-chart-card corner-accents", children: [_jsx("div", { id: containerId, "data-echarts-id": block.id, class: "chart-container" }), _jsx("script", { type: "application/json", children: raw(json) })] }), block.caption !== undefined ? _jsx("p", { class: "report-caption", children: block.caption }) : null] }));
34
+ return String(_jsxs("div", { class: "report-chart", ...blockMark(block.id), ...lineageStamp(view.lineage, block.id, [block.binding]), children: [_jsxs("div", { class: "report-chart-title", children: [block.title, _jsx(Marker, { n: n, lineage: lineagePlace(view.lineage, block.binding) })] }), _jsxs("div", { class: "report-chart-card corner-accents", children: [_jsx("div", { id: containerId, "data-echarts-id": block.id, class: "chart-container" }), _jsx("script", { type: "application/json", children: raw(json) })] }), block.caption !== undefined ? _jsx("p", { class: "report-caption", children: block.caption }) : null] }));
34
35
  }
35
36
  /** The container id derives from the block id, thus the id stays stable across renders. */
36
37
  function chartContainerId(blockId) {
@@ -10,6 +10,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "hono/jsx/jsx-
10
10
  * item takes the same escape as a paragraph, thus no item reaches the page as markup.
11
11
  */
12
12
  import { raw } from "hono/html";
13
+ import { blockMark } from "./block-mark.js";
13
14
  import { DEFAULT_VIEW_OPTIONS, lineagePlace, lineageStamp } from "./lineage.js";
14
15
  import { Marker } from "./references-view.js";
15
16
  /** The paragraph class of a text block and a claim block. The paragraph fills the content column. */
@@ -37,9 +38,9 @@ function splitParagraphs(prose) {
37
38
  * One typed list as list markup. The flag selects the element: an ordered list numbers its items, and an
38
39
  * unordered one bullets them. Each item is one inline line, thus no item holds a list of its own.
39
40
  */
40
- function TextListView({ list }) {
41
+ function TextListView({ list, mark }) {
41
42
  const items = list.items.map((item) => _jsx("li", { class: "report-list-item", children: item }));
42
- return list.ordered ? _jsx("ol", { class: LIST_CLASS, children: items }) : _jsx("ul", { class: LIST_CLASS, children: items });
43
+ return list.ordered ? (_jsx("ol", { class: LIST_CLASS, ...mark, children: items })) : (_jsx("ul", { class: LIST_CLASS, ...mark, children: items }));
43
44
  }
44
45
  /**
45
46
  * Render a text block as escaped paragraphs, and then its list.
@@ -51,7 +52,8 @@ function TextListView({ list }) {
51
52
  export function renderText(block) {
52
53
  const paragraphs = splitParagraphs(block.content.prose);
53
54
  const list = block.content.list;
54
- return String(_jsxs(_Fragment, { children: [paragraphs.map((paragraph) => (_jsx("p", { class: PARAGRAPH_CLASS, children: paragraph }))), list !== undefined ? _jsx(TextListView, { list: list }) : null] }));
55
+ const mark = blockMark(block.id);
56
+ return String(_jsxs(_Fragment, { children: [paragraphs.map((paragraph) => (_jsx("p", { class: PARAGRAPH_CLASS, ...mark, children: paragraph }))), list !== undefined ? _jsx(TextListView, { list: list, mark: mark }) : null] }));
55
57
  }
56
58
  /**
57
59
  * Render a claim block as escaped paragraphs plus evidence markers. The markers attach to the last
@@ -68,8 +70,10 @@ export function renderClaim(block, ledger, view = DEFAULT_VIEW_OPTIONS) {
68
70
  const markers = block.bindings.map((reference, index) => _jsx(Marker, { n: ledger.mark(reference), lineage: lineagePlace(view.lineage, reference, index) }));
69
71
  const paragraphs = splitParagraphs(block.content.prose);
70
72
  const last = paragraphs.length - 1;
71
- const body = paragraphs.length === 0 ? (_jsx("p", { class: PARAGRAPH_CLASS, children: markers })) : (_jsx(_Fragment, { children: paragraphs.map((paragraph, index) => (_jsxs("p", { class: PARAGRAPH_CLASS, children: [paragraph, index === last ? markers : null] }))) }));
72
- return String(view.lineage ? _jsx("div", { ...lineageStamp(view.lineage, block.id, block.bindings), children: body }) : body);
73
+ // Under the lineage a wrapper holds the paragraphs, thus the wrapper alone carries the mark.
74
+ const mark = view.lineage ? {} : blockMark(block.id);
75
+ const body = paragraphs.length === 0 ? (_jsx("p", { class: PARAGRAPH_CLASS, ...mark, children: markers })) : (_jsx(_Fragment, { children: paragraphs.map((paragraph, index) => (_jsxs("p", { class: PARAGRAPH_CLASS, ...mark, children: [paragraph, index === last ? markers : null] }))) }));
76
+ return String(view.lineage ? (_jsx("div", { ...blockMark(block.id), ...lineageStamp(view.lineage, block.id, block.bindings), children: body })) : (body));
73
77
  }
74
78
  /** The heading class of a section, by heading level. A heading uses the sans family, never the mono family. */
75
79
  function headingClass(level) {
@@ -83,7 +87,7 @@ function headingClass(level) {
83
87
  export function renderSection(section, depth, childrenHtml) {
84
88
  const level = Math.min(depth + 2, 4);
85
89
  const Heading = `h${level}`;
86
- return String(_jsxs("section", { id: section.id, class: "report-section", children: [_jsx(Heading, { class: headingClass(level), children: section.title }), raw(childrenHtml)] }));
90
+ return String(_jsxs("section", { id: section.id, class: "report-section", ...blockMark(section.id), children: [_jsx(Heading, { class: headingClass(level), children: section.title }), raw(childrenHtml)] }));
87
91
  }
88
92
  /**
89
93
  * Render the fixed left navigation. Each anchor targets one top-level section by its block id, with a