@inflexa-ai/harness 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/agents/report-session-agent.d.ts +56 -0
  2. package/dist/agents/report-session-agent.js +72 -0
  3. package/dist/app/report-session-runtime.d.ts +72 -0
  4. package/dist/app/report-session-runtime.js +157 -0
  5. package/dist/memory/thread-store.d.ts +4 -4
  6. package/dist/memory/thread-store.js +18 -9
  7. package/dist/prompts/report-session.d.ts +1 -0
  8. package/dist/prompts/report-session.js +89 -0
  9. package/dist/providers/ai-sdk.d.ts +77 -0
  10. package/dist/providers/ai-sdk.js +149 -19
  11. package/dist/providers/errors.d.ts +14 -0
  12. package/dist/providers/errors.js +103 -18
  13. package/dist/providers/types.d.ts +16 -0
  14. package/dist/providers/types.js +10 -1
  15. package/dist/report-model/{mint-snapshot.d.ts → pin-snapshot.d.ts} +7 -7
  16. package/dist/report-model/{mint-snapshot.js → pin-snapshot.js} +7 -7
  17. package/dist/report-model/reference-resolver.d.ts +1 -1
  18. package/dist/report-model/validate.d.ts +34 -2
  19. package/dist/report-model/validate.js +32 -17
  20. package/dist/report-render/value-bridge.d.ts +62 -0
  21. package/dist/report-render/value-bridge.js +82 -0
  22. package/dist/runtime/assemble.d.ts +15 -0
  23. package/dist/runtime/assemble.js +28 -6
  24. package/dist/state/init.js +23 -0
  25. package/dist/state/purge-analysis.js +1 -0
  26. package/dist/state/report-session-state.d.ts +112 -0
  27. package/dist/state/report-session-state.js +150 -0
  28. package/dist/state/report-versions.d.ts +3 -6
  29. package/dist/state/report-versions.js +11 -38
  30. package/dist/state/snapshot-parse.d.ts +34 -0
  31. package/dist/state/snapshot-parse.js +82 -0
  32. package/dist/tools/ad-hoc-router.d.ts +7 -2
  33. package/dist/tools/ad-hoc-router.js +2 -1
  34. package/dist/tools/report-authoring/authoring-tools.d.ts +131 -24
  35. package/dist/tools/report-authoring/authoring-tools.js +152 -44
  36. package/dist/tools/report-session/index.d.ts +8 -0
  37. package/dist/tools/report-session/index.js +8 -0
  38. package/dist/tools/report-session/preview-report.d.ts +90 -0
  39. package/dist/tools/report-session/preview-report.js +245 -0
  40. package/dist/tools/research/generate-plan.js +4 -2
  41. package/package.json +1 -1
@@ -10,7 +10,7 @@
10
10
  * one thread trips the constraint, and the store maps that trip to the typed
11
11
  * refusal thread_already_holds_version.
12
12
  *
13
- * The store keeps the snapshot and the anchor as given, and it never mints a
13
+ * The store keeps the snapshot and the anchor as given, and it never pins a
14
14
  * snapshot. A read parses the stored document and the stored snapshot with the
15
15
  * current schemas. A row that fails a parse reads as a typed error, and it does
16
16
  * not crash. An absent row reads as a normal absence.
@@ -21,42 +21,15 @@
21
21
  */
22
22
  import { randomUUID } from "node:crypto";
23
23
  import { err, errAsync, ok } from "neverthrow";
24
- import { z } from "zod";
25
24
  import { ReportDocumentSchema } from "../contracts/report-blocks.js";
26
25
  import { tryMutation, tryQuery } from "../lib/db-result.js";
26
+ import { parseSnapshot, reduceIssues } from "./snapshot-parse.js";
27
27
  /**
28
28
  * The name of the unique constraint on thread_id. Postgres reports it on a
29
29
  * duplicate-thread refusal, and the record matches it to map the trip to the
30
30
  * thread_already_holds_version refusal.
31
31
  */
32
32
  const THREAD_UNIQUE_CONSTRAINT = "cortex_report_versions_one_per_thread";
33
- /**
34
- * One pinned artifact of a stored snapshot. The value carries the content hash,
35
- * an optional file type, and optional rows. The `z.ZodType<ReportSnapshot>`
36
- * annotation below keeps this schema and the `ArtifactSnapshot` type of the
37
- * reference model in step, thus a change to one shows as a compile error there.
38
- */
39
- const ArtifactSnapshotSchema = z.object({
40
- hash: z.string(),
41
- fileType: z.string().nullable().optional(),
42
- rows: z.array(z.record(z.string(), z.union([z.string(), z.number()]))).optional(),
43
- });
44
- /**
45
- * The stored-snapshot schema. The annotation ties the parse output to the
46
- * `ReportSnapshot` type of the reference model, thus the store and the structural
47
- * validation never disagree about the snapshot shape. The mint never fills `rows`,
48
- * but the schema admits what the type admits.
49
- */
50
- const ReportSnapshotSchema = z.object({
51
- artifacts: z.record(z.string(), ArtifactSnapshotSchema),
52
- citations: z.array(z.string()).optional(),
53
- });
54
- function reduceIssues(error) {
55
- return error.issues.map((issue) => ({
56
- path: issue.path.map((segment) => String(segment)).join("."),
57
- message: issue.message,
58
- }));
59
- }
60
33
  const _assertRecordError = true;
61
34
  const _assertReadError = true;
62
35
  /**
@@ -78,9 +51,9 @@ function rowToVersion(row) {
78
51
  if (!document.success) {
79
52
  return err({ type: "corrupt_version", op: "report-versions.read", versionId: row.version_id, part: "document", issues: reduceIssues(document.error) });
80
53
  }
81
- const snapshot = ReportSnapshotSchema.safeParse(row.snapshot);
82
- if (!snapshot.success) {
83
- return err({ type: "corrupt_version", op: "report-versions.read", versionId: row.version_id, part: "snapshot", issues: reduceIssues(snapshot.error) });
54
+ const snapshot = parseSnapshot(row.snapshot);
55
+ if (snapshot.isErr()) {
56
+ return err({ type: "corrupt_version", op: "report-versions.read", versionId: row.version_id, part: "snapshot", issues: snapshot.error });
84
57
  }
85
58
  return ok({
86
59
  versionId: row.version_id,
@@ -90,7 +63,7 @@ function rowToVersion(row) {
90
63
  parentSeq: row.parent_seq === null ? null : Number(row.parent_seq),
91
64
  parentVersionId: row.parent_version_id,
92
65
  document: document.data,
93
- snapshot: snapshot.data,
66
+ snapshot: snapshot.value,
94
67
  createdAt: row.created_at,
95
68
  });
96
69
  }
@@ -126,11 +99,11 @@ export function createReportVersionStore({ pool }) {
126
99
  return errAsync({ type: "malformed_document", op: "report-versions.record", issues: reduceIssues(parsed.error) });
127
100
  }
128
101
  const document = parsed.data;
129
- // The parse guards the row against a caller bug through a cast. The store
130
- // stores the given value, thus the parse changes no value.
131
- const parsedSnapshot = ReportSnapshotSchema.safeParse(input.snapshot);
132
- if (!parsedSnapshot.success) {
133
- return errAsync({ type: "malformed_snapshot", op: "report-versions.record", issues: reduceIssues(parsedSnapshot.error) });
102
+ // The parse guards the row against a caller bug. The store stores the given
103
+ // value, thus the parse changes no value.
104
+ const parsedSnapshot = parseSnapshot(input.snapshot);
105
+ if (parsedSnapshot.isErr()) {
106
+ return errAsync({ type: "malformed_snapshot", op: "report-versions.record", issues: parsedSnapshot.error });
134
107
  }
135
108
  const insert = () => {
136
109
  const versionId = randomUUID();
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The one parse of a stored report snapshot, shared by the two stores.
3
+ *
4
+ * The session-state store and the version store each hold a snapshot column, and
5
+ * each reads it back the same way. A path such as `__proto__` is an ordinary
6
+ * artifact key of the ledger, and the pin keeps it as an own member of a
7
+ * null-prototype map (`report-model/pin-snapshot.ts`). Each object-shaped schema
8
+ * of zod drops such a key, thus a store that parsed the map through one would
9
+ * resolve a reference to that artifact before a reload and refuse it as absent
10
+ * after one. The walk reads the own keys of the stored map, and it parses each
11
+ * value on its own, thus the two stores agree on the key set.
12
+ */
13
+ import { type Result } from "neverthrow";
14
+ import { z } from "zod";
15
+ import type { ReportSnapshot } from "../report-model/reference-resolver.js";
16
+ /** One reduced schema issue -- the dotted path and the message, without the rest. */
17
+ export interface SchemaIssue {
18
+ readonly path: string;
19
+ readonly message: string;
20
+ }
21
+ /** Reduce a zod error to the dotted path and the message of each issue. */
22
+ export declare function reduceIssues(error: z.ZodError): SchemaIssue[];
23
+ /**
24
+ * Parse a stored snapshot.
25
+ *
26
+ * The artifacts map never goes through a zod object schema, for the reason that
27
+ * the module header gives: a zod object drops a `__proto__` key and the pin keeps
28
+ * it. The walk reads the own keys of the stored map, and it parses each value on
29
+ * its own.
30
+ *
31
+ * The `ReportSnapshot` return type ties this parse to the type of the reference
32
+ * model, thus a change to one shows as a compile error here.
33
+ */
34
+ export declare function parseSnapshot(stored: unknown): Result<ReportSnapshot, SchemaIssue[]>;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The one parse of a stored report snapshot, shared by the two stores.
3
+ *
4
+ * The session-state store and the version store each hold a snapshot column, and
5
+ * each reads it back the same way. A path such as `__proto__` is an ordinary
6
+ * artifact key of the ledger, and the pin keeps it as an own member of a
7
+ * null-prototype map (`report-model/pin-snapshot.ts`). Each object-shaped schema
8
+ * of zod drops such a key, thus a store that parsed the map through one would
9
+ * resolve a reference to that artifact before a reload and refuse it as absent
10
+ * after one. The walk reads the own keys of the stored map, and it parses each
11
+ * value on its own, thus the two stores agree on the key set.
12
+ */
13
+ import { err, ok } from "neverthrow";
14
+ import { z } from "zod";
15
+ /**
16
+ * One pinned artifact of a stored snapshot. The value carries the content hash, an
17
+ * optional file type, and optional rows.
18
+ */
19
+ const ArtifactSnapshotSchema = z.object({
20
+ hash: z.string(),
21
+ fileType: z.string().nullable().optional(),
22
+ rows: z.array(z.record(z.string(), z.union([z.string(), z.number()]))).optional(),
23
+ });
24
+ /** The citation list of a stored snapshot. A snapshot with no list reads as an absent field. */
25
+ const CitationsSchema = z.array(z.string()).optional();
26
+ /** Reduce a zod error to the dotted path and the message of each issue. */
27
+ export function reduceIssues(error) {
28
+ return error.issues.map((issue) => ({
29
+ path: issue.path.map((segment) => String(segment)).join("."),
30
+ message: issue.message,
31
+ }));
32
+ }
33
+ /** Put a prefix before the path of each issue, thus a nested parse names the key that failed. */
34
+ function prefixIssues(prefix, issues) {
35
+ return issues.map((issue) => ({
36
+ path: issue.path === "" ? prefix : `${prefix}.${issue.path}`,
37
+ message: issue.message,
38
+ }));
39
+ }
40
+ /** A stored map is a plain object. An array and a null carry no own key that a map read can use. */
41
+ function isPlainRecord(value) {
42
+ return typeof value === "object" && value !== null && !Array.isArray(value);
43
+ }
44
+ /**
45
+ * Parse a stored snapshot.
46
+ *
47
+ * The artifacts map never goes through a zod object schema, for the reason that
48
+ * the module header gives: a zod object drops a `__proto__` key and the pin keeps
49
+ * it. The walk reads the own keys of the stored map, and it parses each value on
50
+ * its own.
51
+ *
52
+ * The `ReportSnapshot` return type ties this parse to the type of the reference
53
+ * model, thus a change to one shows as a compile error here.
54
+ */
55
+ export function parseSnapshot(stored) {
56
+ if (!isPlainRecord(stored)) {
57
+ return err([{ path: "", message: "the stored snapshot is not an object" }]);
58
+ }
59
+ if (!isPlainRecord(stored.artifacts)) {
60
+ return err([{ path: "artifacts", message: "the artifacts map is not an object" }]);
61
+ }
62
+ const citations = CitationsSchema.safeParse(stored.citations);
63
+ if (!citations.success) {
64
+ return err(prefixIssues("citations", reduceIssues(citations.error)));
65
+ }
66
+ // The map takes a null prototype, the same as the pin, thus a path such as
67
+ // `__proto__` stays an ordinary entry and never reaches a prototype slot.
68
+ const artifacts = Object.create(null);
69
+ const issues = [];
70
+ for (const [path, value] of Object.entries(stored.artifacts)) {
71
+ const parsed = ArtifactSnapshotSchema.safeParse(value);
72
+ if (!parsed.success) {
73
+ issues.push(...prefixIssues(`artifacts.${path}`, reduceIssues(parsed.error)));
74
+ continue;
75
+ }
76
+ artifacts[path] = parsed.data;
77
+ }
78
+ if (issues.length > 0) {
79
+ return err(issues);
80
+ }
81
+ return citations.data === undefined ? ok({ artifacts }) : ok({ artifacts, citations: citations.data });
82
+ }
@@ -2,7 +2,7 @@ import type { Pool } from "pg";
2
2
  import { type AgentSession } from "../auth/types.js";
3
3
  import type { ResourcePolicy, ResourceSpec } from "../config/resource-limits.js";
4
4
  import type { Logger } from "../lib/logger.js";
5
- import type { ChatProvider } from "../providers/types.js";
5
+ import { type ChatProvider } from "../providers/types.js";
6
6
  export declare const AD_HOC_ROUTER_AGENT_ID = "adhoc-router";
7
7
  export declare const AD_HOC_ROUTER_TIMEOUT_MS = 10000;
8
8
  export declare const AD_HOC_FALLBACK_AGENT_ID = "scientific-executor";
@@ -18,7 +18,12 @@ export interface AdHocRouterDeps {
18
18
  readonly pool: Pool;
19
19
  readonly resourcePolicy?: ResourcePolicy;
20
20
  readonly logger?: Logger;
21
- /** Test seam; production always uses {@link AD_HOC_ROUTER_TIMEOUT_MS}. */
21
+ /**
22
+ * Test seam that sets an explicit deadline. An explicit value overrides the
23
+ * derived deadline. The derived deadline is the maximum of
24
+ * {@link AD_HOC_ROUTER_TIMEOUT_MS} and the request-timeout limit that the
25
+ * provider advertises.
26
+ */
22
27
  readonly timeoutMs?: number;
23
28
  }
24
29
  export declare function defaultAdHocResources(policy?: ResourcePolicy): ResourceSpec;
@@ -6,6 +6,7 @@ import { DATA_PROFILE_ORIENTATION_MAX_CHARS, buildDataProfileOrientation } from
6
6
  import { createNoopLogger } from "../lib/console-logger.js";
7
7
  import { unwrapOrThrow } from "../lib/result.js";
8
8
  import { resourceEstimationSection } from "../prompts/planner.js";
9
+ import { effectiveDeadlineMs } from "../providers/types.js";
9
10
  import { loadDataProfileStatus } from "../state/index.js";
10
11
  export const AD_HOC_ROUTER_AGENT_ID = "adhoc-router";
11
12
  export const AD_HOC_ROUTER_TIMEOUT_MS = 10_000;
@@ -72,7 +73,7 @@ export async function routeAdHocRequest(deps, input) {
72
73
  const catalog = PLANNABLE_AGENT_CATALOG.map((agent) => `- ${agent.id}: capabilities [${agent.capabilities.join(", ")}]; suitable for [${agent.suitableFor.join(", ")}]`).join("\n");
73
74
  const orientation = await profileOrientation(deps.pool, input.analysisId, logger);
74
75
  const controller = new AbortController();
75
- const timer = setTimeout(() => controller.abort(new Error("ad hoc routing timed out")), deps.timeoutMs ?? AD_HOC_ROUTER_TIMEOUT_MS);
76
+ const timer = setTimeout(() => controller.abort(new Error("ad hoc routing timed out")), effectiveDeadlineMs(deps.provider, AD_HOC_ROUTER_TIMEOUT_MS, deps.timeoutMs));
76
77
  const signal = AbortSignal.any([input.signal, controller.signal]);
77
78
  let raw;
78
79
  let failure;
@@ -1,18 +1,24 @@
1
1
  /**
2
2
  * The authoring tool surface over the draft operations.
3
3
  *
4
- * The factory closes over a private draft holder and the pinned snapshot. It gives eight tools and a
5
- * read of the current draft. The tools read no ambient state, thus two factories hold two independent
6
- * drafts.
4
+ * The factory takes a session-state gateway, and it holds no draft. Each tool reads the thread id from the
5
+ * scope of the call, loads the state of that thread, applies the pure operation, and persists the new
6
+ * document. Thus two threads never share one draft, and a landed document outlives a host restart.
7
7
  *
8
- * Each tool maps its flat input onto a core operation, and it applies the pure operation. The holder
9
- * swaps only on a landed operation. A refused operation returns typed data in the ok channel, and the
10
- * error channel stays for an unexpected failure.
8
+ * Each tool maps its flat input onto a core operation, and it applies the pure operation. A landed
9
+ * document persists before the tool reports `applied: true`. A refused operation returns typed data in the
10
+ * ok channel, and the error channel stays for an unexpected failure.
11
+ *
12
+ * A call with no report thread in its scope, a thread with no stored state, and a gateway fault each
13
+ * refuse as typed data too. That refusal sits beside the core refusal, and it names the absent thread
14
+ * scope or the absent state.
11
15
  *
12
16
  * The primitive admits a flat object input only. Thus a destination is four optional fields, and not a
13
17
  * nested union. A conflict between the flat fields refuses before the core runs.
14
18
  */
19
+ import { type Result } from "neverthrow";
15
20
  import { z } from "zod";
21
+ import type { Scope } from "../../auth/types.js";
16
22
  import { type DraftRefusal } from "../../report-model/draft-operations.js";
17
23
  import { type OutlineEntry, type ReadableBlock } from "../../report-model/draft-read.js";
18
24
  import { type FinishResult } from "../../report-model/draft-finish.js";
@@ -77,7 +83,30 @@ export interface ChangedContainer {
77
83
  children: OutlineEntry[];
78
84
  }
79
85
  /**
80
- * The result of a mutation tool. A refusal carries the typed reason.
86
+ * The closed set of tool-layer refusal reasons.
87
+ *
88
+ * `no-thread-scope` means that the scope of the call names no report thread. `absent-state` means that no
89
+ * report session exists for the thread, and it is permanent. `wrong-thread-type` means that the thread is
90
+ * not a report thread, and it is permanent. `scope-analysis-mismatch` means that the scope names one
91
+ * analysis and the thread belongs to another, and it is permanent. `stale-state` means that a concurrent
92
+ * turn landed first, and the agent must read the state again. `state-unavailable` means that the gateway
93
+ * cannot serve or store the state, and it is transient. The set is disjoint from the core
94
+ * `DraftRefusalReason`, thus a reader tells a core refusal from a tool-layer refusal by the reason alone.
95
+ */
96
+ export type SessionRefusalReason = "no-thread-scope" | "absent-state" | "wrong-thread-type" | "scope-analysis-mismatch" | "stale-state" | "state-unavailable";
97
+ /**
98
+ * A refusal that the tool layer raises before the core runs.
99
+ *
100
+ * The core refuses a bad edit. This layer refuses a call that names no thread, a thread with no stored
101
+ * state, or a gateway that cannot give the state. It sits beside the core `DraftRefusal`, and it never
102
+ * widens the closed reason set of the core.
103
+ */
104
+ export interface SessionRefusal {
105
+ reason: SessionRefusalReason;
106
+ detail: string;
107
+ }
108
+ /**
109
+ * The result of a mutation tool. A refusal carries the typed reason, from the core or from the tool layer.
81
110
  *
82
111
  * A landing carries the child order of each container that it changed, and not the whole outline. A whole
83
112
  * outline costs the size of the draft on every landing, thus composing a report of n blocks would spend
@@ -92,26 +121,105 @@ export type MutationResult = {
92
121
  changed: ChangedContainer[];
93
122
  } | {
94
123
  applied: false;
95
- refusal: DraftRefusal;
124
+ refusal: DraftRefusal | SessionRefusal;
96
125
  };
97
- /** The result of `read_outline`. */
98
- export interface OutlineResult {
126
+ /** The result of `read_outline`. A load that gives no state refuses instead of an outline. */
127
+ export type OutlineResult = {
99
128
  outline: OutlineEntry[];
100
- }
101
- /** The result of `read_block`. An absent block is an expected outcome, thus it stays in the ok channel. */
129
+ } | {
130
+ refused: SessionRefusal;
131
+ };
132
+ /**
133
+ * The result of `read_block`. An absent block is an expected outcome, thus it stays in the ok channel. A
134
+ * load that gives no state refuses instead of a block.
135
+ */
102
136
  export type ReadBlockResult = {
103
137
  found: true;
104
138
  block: ReadableBlock;
105
139
  } | {
106
140
  found: false;
141
+ } | {
142
+ refused: SessionRefusal;
107
143
  };
108
- /** The state that the factory closes over. */
109
- export interface ReportAuthoringState {
144
+ /** The result of `finish_draft`. A load that gives no state refuses instead of a finish outcome. */
145
+ export type FinishToolResult = FinishResult | {
146
+ refused: SessionRefusal;
147
+ };
148
+ /** The state of one report thread: the document under composition, and the frozen snapshot. */
149
+ export interface ReportSessionState {
150
+ readonly document: DraftDocument;
110
151
  readonly snapshot: ReportSnapshot;
111
- /** The draft to start from. The default is an empty draft. */
112
- readonly initialDraft?: DraftDocument;
113
152
  }
114
- /** The eight authoring tools, plus a read of the current draft. */
153
+ /**
154
+ * The concurrency token that a load hands out and a persist compares against. It is the prior document
155
+ * that the load read, or `null` before the first document lands. The persist lands only when the row still
156
+ * holds it, thus a concurrent turn that landed first turns the next persist into a conflict.
157
+ */
158
+ export type SessionStateToken = DraftDocument | null;
159
+ /**
160
+ * The outcome of a gateway load. `found` carries the state, the analysis that owns the thread, and the
161
+ * concurrency token. `absent` and `wrong-type` are permanent conditions, and `failed` is a transient fault
162
+ * that names its cause.
163
+ */
164
+ export type SessionStateLoad = {
165
+ outcome: "found";
166
+ state: ReportSessionState;
167
+ analysisId: string;
168
+ token: SessionStateToken;
169
+ } | {
170
+ outcome: "absent";
171
+ } | {
172
+ outcome: "wrong-type";
173
+ detail: string;
174
+ } | {
175
+ outcome: "failed";
176
+ detail: string;
177
+ };
178
+ /** The outcome of a gateway persist. `conflict` means that a concurrent turn landed first. */
179
+ export type SessionStatePersist = {
180
+ outcome: "persisted";
181
+ } | {
182
+ outcome: "conflict";
183
+ } | {
184
+ outcome: "failed";
185
+ detail: string;
186
+ };
187
+ /**
188
+ * The session-state gateway that the tools read and write.
189
+ *
190
+ * The tool layer is the one consumer, thus the interface lives here. The interface speaks a plain
191
+ * discriminated value, not a `Result`, because the tools read the outcome as data. The runtime realizes
192
+ * the gateway over the durable store. A load gives the state, a typed absence, or a typed failure. A
193
+ * persist gives success, or a typed failure.
194
+ */
195
+ export interface ReportSessionStateGateway {
196
+ load(threadId: string): Promise<SessionStateLoad>;
197
+ persist(threadId: string, document: DraftDocument, expected: SessionStateToken): Promise<SessionStatePersist>;
198
+ }
199
+ /** The thread of a call, its analysis, the loaded state, and the concurrency token that the persist compares against. */
200
+ export interface OpenedThread {
201
+ readonly threadId: string;
202
+ readonly analysisId: string;
203
+ readonly state: ReportSessionState;
204
+ readonly token: SessionStateToken;
205
+ }
206
+ /**
207
+ * Resolve the report thread of a call, and load its state through the gateway.
208
+ *
209
+ * The report thread and its analysis ride on the analysis scope. The thread id becomes one segment of a
210
+ * session directory, thus the safe-id check sits here beside the shape check: a scope whose id carries a
211
+ * separator or a traversal segment names no thread that a tool can write under.
212
+ *
213
+ * The stored analysis of the thread must match the scope. A mismatch reads one analysis's draft and writes
214
+ * into another analysis's workspace, thus the resolution refuses it with a permanent reason that names the
215
+ * two ids. A scope of a different kind, an unsafe id, an absent thread, a wrong thread type, and a gateway
216
+ * fault each refuse as a typed `SessionRefusal` too.
217
+ *
218
+ * The authoring tools and the preview tool share this one resolution, thus the two surfaces accept the
219
+ * same thread id and map a load outcome onto a refusal the same way.
220
+ */
221
+ export declare function openReportThread(gateway: ReportSessionStateGateway, scope: Scope): Promise<Result<OpenedThread, SessionRefusal>>;
222
+ /** The eight authoring tools. */
115
223
  export interface ReportAuthoringTools {
116
224
  readonly add_block: Tool<AddBlockInput, MutationResult>;
117
225
  readonly change_block: Tool<ChangeBlockInput, MutationResult>;
@@ -120,15 +228,14 @@ export interface ReportAuthoringTools {
120
228
  readonly set_title: Tool<SetTitleInput, MutationResult>;
121
229
  readonly read_outline: Tool<ReadOutlineInput, OutlineResult>;
122
230
  readonly read_block: Tool<ReadBlockInput, ReadBlockResult>;
123
- readonly finish_draft: Tool<FinishDraftInput, FinishResult>;
124
- currentDraft(): DraftDocument;
231
+ readonly finish_draft: Tool<FinishDraftInput, FinishToolResult>;
125
232
  }
126
233
  /**
127
- * Make the eight authoring tools over one draft.
234
+ * Make the eight authoring tools over a session-state gateway.
128
235
  *
129
- * The holder is a private `let`. Each tool reads the holder at call time, thus a landing through one tool
130
- * shows in the next call of every tool. The holder swaps only inside `land`, thus a refused operation
131
- * leaves the draft as it was.
236
+ * Each tool reads the thread id from the scope of the call, and it loads the state of that thread through
237
+ * the gateway. Thus one factory serves every thread, and two threads never share one draft. A mutation
238
+ * persists the new document before it reports `applied: true`, thus a reported landing is never lost.
132
239
  */
133
- export declare function createReportAuthoringTools(state: ReportAuthoringState): ReportAuthoringTools;
240
+ export declare function createReportAuthoringTools(gateway: ReportSessionStateGateway): ReportAuthoringTools;
134
241
  export {};