@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.
- package/dist/agents/report-session-agent.d.ts +56 -0
- package/dist/agents/report-session-agent.js +72 -0
- package/dist/app/report-session-runtime.d.ts +72 -0
- package/dist/app/report-session-runtime.js +157 -0
- package/dist/memory/thread-store.d.ts +4 -4
- package/dist/memory/thread-store.js +18 -9
- package/dist/prompts/report-session.d.ts +1 -0
- package/dist/prompts/report-session.js +89 -0
- package/dist/providers/ai-sdk.d.ts +77 -0
- package/dist/providers/ai-sdk.js +149 -19
- package/dist/providers/errors.d.ts +14 -0
- package/dist/providers/errors.js +103 -18
- package/dist/providers/types.d.ts +16 -0
- package/dist/providers/types.js +10 -1
- package/dist/report-model/{mint-snapshot.d.ts → pin-snapshot.d.ts} +7 -7
- package/dist/report-model/{mint-snapshot.js → pin-snapshot.js} +7 -7
- package/dist/report-model/reference-resolver.d.ts +1 -1
- package/dist/report-model/validate.d.ts +34 -2
- package/dist/report-model/validate.js +32 -17
- package/dist/report-render/value-bridge.d.ts +62 -0
- package/dist/report-render/value-bridge.js +82 -0
- package/dist/runtime/assemble.d.ts +15 -0
- package/dist/runtime/assemble.js +28 -6
- package/dist/state/init.js +23 -0
- package/dist/state/purge-analysis.js +1 -0
- package/dist/state/report-session-state.d.ts +112 -0
- package/dist/state/report-session-state.js +150 -0
- package/dist/state/report-versions.d.ts +3 -6
- package/dist/state/report-versions.js +11 -38
- package/dist/state/snapshot-parse.d.ts +34 -0
- package/dist/state/snapshot-parse.js +82 -0
- package/dist/tools/ad-hoc-router.d.ts +7 -2
- package/dist/tools/ad-hoc-router.js +2 -1
- package/dist/tools/report-authoring/authoring-tools.d.ts +131 -24
- package/dist/tools/report-authoring/authoring-tools.js +152 -44
- package/dist/tools/report-session/index.d.ts +8 -0
- package/dist/tools/report-session/index.js +8 -0
- package/dist/tools/report-session/preview-report.d.ts +90 -0
- package/dist/tools/report-session/preview-report.js +245 -0
- package/dist/tools/research/generate-plan.js +4 -2
- 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
|
|
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 =
|
|
82
|
-
if (
|
|
83
|
-
return err({ type: "corrupt_version", op: "report-versions.read", versionId: row.version_id, part: "snapshot", issues:
|
|
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.
|
|
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
|
|
130
|
-
//
|
|
131
|
-
const parsedSnapshot =
|
|
132
|
-
if (
|
|
133
|
-
return errAsync({ type: "malformed_snapshot", op: "report-versions.record", issues:
|
|
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
|
|
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
|
-
/**
|
|
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.
|
|
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
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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.
|
|
9
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
109
|
-
export
|
|
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
|
-
/**
|
|
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,
|
|
124
|
-
currentDraft(): DraftDocument;
|
|
231
|
+
readonly finish_draft: Tool<FinishDraftInput, FinishToolResult>;
|
|
125
232
|
}
|
|
126
233
|
/**
|
|
127
|
-
* Make the eight authoring tools over
|
|
234
|
+
* Make the eight authoring tools over a session-state gateway.
|
|
128
235
|
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
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(
|
|
240
|
+
export declare function createReportAuthoringTools(gateway: ReportSessionStateGateway): ReportAuthoringTools;
|
|
134
241
|
export {};
|