@inflexa-ai/harness 0.18.0 → 0.19.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 (83) hide show
  1. package/dist/agents/report-session-agent.d.ts +72 -0
  2. package/dist/agents/report-session-agent.js +87 -0
  3. package/dist/app/report-session-runtime.d.ts +72 -0
  4. package/dist/app/report-session-runtime.js +183 -0
  5. package/dist/app/spawn-report-session.d.ts +32 -5
  6. package/dist/app/spawn-report-session.js +19 -0
  7. package/dist/config/environment-stores.d.ts +12 -7
  8. package/dist/config/resource-limits.d.ts +6 -10
  9. package/dist/config/resource-limits.js +6 -44
  10. package/dist/contracts/report-reference.d.ts +4 -0
  11. package/dist/contracts/report-reference.js +3 -0
  12. package/dist/index.d.ts +2 -1
  13. package/dist/lib/chrome.d.ts +14 -0
  14. package/dist/lib/chrome.js +7 -0
  15. package/dist/lib/page-capture.d.ts +47 -0
  16. package/dist/lib/page-capture.js +83 -0
  17. package/dist/loop/run-agent.js +104 -15
  18. package/dist/memory/ai-sdk-message-storage.d.ts +9 -4
  19. package/dist/memory/ai-sdk-message-storage.js +9 -4
  20. package/dist/memory/count-tokens.js +34 -5
  21. package/dist/memory/thread-store.d.ts +4 -4
  22. package/dist/memory/thread-store.js +18 -9
  23. package/dist/prompts/report-session.d.ts +1 -0
  24. package/dist/prompts/report-session.js +110 -0
  25. package/dist/providers/ai-sdk.d.ts +158 -3
  26. package/dist/providers/ai-sdk.js +234 -23
  27. package/dist/providers/errors.d.ts +14 -0
  28. package/dist/providers/errors.js +103 -18
  29. package/dist/providers/types.d.ts +33 -0
  30. package/dist/providers/types.js +10 -1
  31. package/dist/report-model/assert-rules.d.ts +44 -0
  32. package/dist/report-model/assert-rules.js +97 -0
  33. package/dist/report-model/draft-hash.d.ts +14 -0
  34. package/dist/report-model/draft-hash.js +36 -0
  35. package/dist/report-model/fixture-resolver.js +2 -71
  36. package/dist/report-model/{mint-snapshot.d.ts → pin-snapshot.d.ts} +7 -7
  37. package/dist/report-model/{mint-snapshot.js → pin-snapshot.js} +7 -7
  38. package/dist/report-model/production-resolver.d.ts +124 -0
  39. package/dist/report-model/production-resolver.js +801 -0
  40. package/dist/report-model/reference-resolver.d.ts +7 -1
  41. package/dist/report-model/validate.d.ts +34 -2
  42. package/dist/report-model/validate.js +37 -17
  43. package/dist/report-render/page.d.ts +23 -0
  44. package/dist/report-render/page.js +36 -3
  45. package/dist/report-render/value-bridge.d.ts +62 -0
  46. package/dist/report-render/value-bridge.js +82 -0
  47. package/dist/runtime/assemble.d.ts +39 -0
  48. package/dist/runtime/assemble.js +67 -6
  49. package/dist/sandbox/mount-plan.d.ts +7 -0
  50. package/dist/sandbox/mount-plan.js +7 -2
  51. package/dist/state/init.js +36 -0
  52. package/dist/state/purge-analysis.js +1 -0
  53. package/dist/state/report-session-state.d.ts +139 -0
  54. package/dist/state/report-session-state.js +177 -0
  55. package/dist/state/report-versions.d.ts +3 -6
  56. package/dist/state/report-versions.js +11 -38
  57. package/dist/state/snapshot-parse.d.ts +34 -0
  58. package/dist/state/snapshot-parse.js +82 -0
  59. package/dist/tasks/extract-values-script.d.ts +64 -0
  60. package/dist/tasks/extract-values-script.js +180 -0
  61. package/dist/tasks/extract-values.d.ts +124 -0
  62. package/dist/tasks/extract-values.js +193 -0
  63. package/dist/tools/ad-hoc-router.d.ts +7 -2
  64. package/dist/tools/ad-hoc-router.js +2 -1
  65. package/dist/tools/define-tool.d.ts +29 -0
  66. package/dist/tools/define-tool.js +26 -0
  67. package/dist/tools/report/preview-snapshot.d.ts +1 -1
  68. package/dist/tools/report/preview-snapshot.js +11 -50
  69. package/dist/tools/report-authoring/authoring-tools.d.ts +181 -24
  70. package/dist/tools/report-authoring/authoring-tools.js +152 -44
  71. package/dist/tools/report-session/examine-page.d.ts +90 -0
  72. package/dist/tools/report-session/examine-page.js +136 -0
  73. package/dist/tools/report-session/index.d.ts +10 -0
  74. package/dist/tools/report-session/index.js +10 -0
  75. package/dist/tools/report-session/preview-report.d.ts +105 -0
  76. package/dist/tools/report-session/preview-report.js +269 -0
  77. package/dist/tools/report-session/record-version.d.ts +102 -0
  78. package/dist/tools/report-session/record-version.js +139 -0
  79. package/dist/tools/research/generate-plan.js +4 -2
  80. package/dist/tools/sandbox/list-available-packages.js +2 -1
  81. package/dist/tools/sandbox/list-available-refs.d.ts +5 -1
  82. package/dist/tools/sandbox/list-available-refs.js +13 -6
  83. package/package.json +10 -8
@@ -20,6 +20,32 @@ import { z } from "zod";
20
20
  export function isToolError(value) {
21
21
  return typeof value === "object" && value !== null && typeof value.error === "string" && typeof value.retryable === "boolean";
22
22
  }
23
+ /**
24
+ * The reserved key that carries a picture on a tool ok value. It is a symbol,
25
+ * thus it never collides with a data field. `JSON.stringify` also omits a symbol
26
+ * key, thus the bytes never reach the JSON text by accident.
27
+ */
28
+ export const toolResultImageKey = Symbol("toolResultImage");
29
+ /**
30
+ * Attach a picture to a tool ok value. The value keeps its own fields, and the
31
+ * picture rides under the reserved key. The loop reads the key, and it moves the
32
+ * bytes into an image content block on the tool result.
33
+ */
34
+ export function withToolResultImage(value, image) {
35
+ return { ...value, [toolResultImageKey]: image };
36
+ }
37
+ /** Read the picture that a tool ok value carries, or undefined when it carries none. */
38
+ export function readToolResultImage(value) {
39
+ if (typeof value !== "object" || value === null)
40
+ return undefined;
41
+ const image = value[toolResultImageKey];
42
+ if (typeof image !== "object" || image === null)
43
+ return undefined;
44
+ const { base64, mediaType } = image;
45
+ if (typeof base64 !== "string" || typeof mediaType !== "string")
46
+ return undefined;
47
+ return { base64, mediaType };
48
+ }
23
49
  /**
24
50
  * Package a `Tool`. The emitted JSON Schema must have a top-level
25
51
  * `"type": "object"` — model tool calling rejects anything else. A `z.discriminatedUnion`
@@ -17,7 +17,7 @@
17
17
  * has to be able to see without reading the model transcript.
18
18
  */
19
19
  import { type Tool } from "../define-tool.js";
20
- import { type ChromeConfig } from "../../lib/chrome.js";
20
+ import type { ChromeConfig } from "../../lib/chrome.js";
21
21
  import type { Logger } from "../../lib/logger.js";
22
22
  import { type PreviewPublisher } from "./preview-publisher.js";
23
23
  import type { PreviewUrlCell } from "./mint-preview-url.js";
@@ -19,11 +19,9 @@
19
19
  import { ok } from "neverthrow";
20
20
  import { z } from "zod";
21
21
  import { defineTool } from "../define-tool.js";
22
- import { withPage } from "../../lib/chrome.js";
22
+ import { capturePage } from "../../lib/page-capture.js";
23
23
  import { createNoopLogger } from "../../lib/console-logger.js";
24
24
  import { describeMintFailure } from "./preview-publisher.js";
25
- const NAV_TIMEOUT_MS = 20_000;
26
- const READY_TIMEOUT_MS = 8_000;
27
25
  const WAIT_MS_CAP = 30_000;
28
26
  export function createPreviewSnapshotTool(state) {
29
27
  const logger = (state.logger ?? createNoopLogger()).named("preview-snapshot").with({ previewId: state.previewId });
@@ -75,53 +73,16 @@ export function createPreviewSnapshotTool(state) {
75
73
  state.urlCell.expiresAt = result.data.expiresAt;
76
74
  }
77
75
  try {
78
- return ok(await withPage(state.chrome, async (page) => {
79
- const consoleErrors = [];
80
- const failedRequests = [];
81
- page.on("console", (msg) => {
82
- if (msg.type() === "error")
83
- consoleErrors.push(msg.text());
84
- });
85
- page.on("pageerror", (err) => consoleErrors.push(`pageerror: ${err.message}`));
86
- page.on("requestfailed", (req) => {
87
- failedRequests.push({
88
- url: req.url(),
89
- reason: req.failure()?.errorText ?? "unknown",
90
- });
91
- });
92
- await page.goto(url, {
93
- waitUntil: "networkidle2",
94
- timeout: NAV_TIMEOUT_MS,
95
- });
96
- // Wait for the inflexa-theme-ready event (fired after echarts theme
97
- // registers). Check the `__inflexaThemeReady` sentinel first because
98
- // the page already deferred its dispatch — by the time we get here
99
- // via networkidle2, the event has likely already fired and a plain
100
- // addEventListener would block forever. The callback runs in the
101
- // browser context.
102
- await page
103
- .evaluate(new Function("timeout", "return new Promise(function(resolve){if(window.__inflexaThemeReady){resolve();return;}var t=setTimeout(resolve,timeout);document.addEventListener('inflexa-theme-ready',function(){clearTimeout(t);resolve();},{once:true});});"), READY_TIMEOUT_MS)
104
- .catch(() => {
105
- /* fall through — capture state as-is */
106
- });
107
- if (input.waitForSelector) {
108
- await page.waitForSelector(input.waitForSelector, { timeout: 5_000 }).catch(() => {
109
- /* not fatal */
110
- });
111
- }
112
- if (input.waitMs)
113
- await new Promise((r) => setTimeout(r, input.waitMs));
114
- const screenshot = await page.screenshot({
115
- encoding: "base64",
116
- fullPage: false,
117
- });
118
- return {
119
- ok: true,
120
- screenshotBase64: typeof screenshot === "string" ? screenshot : Buffer.from(screenshot).toString("base64"),
121
- consoleErrors,
122
- failedRequests,
123
- };
124
- }));
76
+ const captured = await capturePage(state.chrome, url, {
77
+ ...(input.waitForSelector !== undefined ? { waitForSelector: input.waitForSelector } : {}),
78
+ ...(input.waitMs !== undefined ? { waitMs: input.waitMs } : {}),
79
+ });
80
+ return ok({
81
+ ok: true,
82
+ screenshotBase64: captured.screenshotBase64,
83
+ consoleErrors: captured.consoleErrors,
84
+ failedRequests: captured.failedRequests,
85
+ });
125
86
  }
126
87
  catch (err) {
127
88
  return ok({
@@ -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,155 @@ 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;
143
+ };
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;
107
147
  };
108
- /** The state that the factory closes over. */
109
- export interface ReportAuthoringState {
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, the
161
+ * concurrency token, and the seen-document hash of the look-before-record rule. `absent` and `wrong-type`
162
+ * are permanent conditions, and `failed` is a transient fault that names its cause.
163
+ *
164
+ * `seenDocumentHash` is the hash that the last eyes capture saw, or `null` before the first look. The record
165
+ * tool compares it against the hash of the current draft, thus a never-seen page and a stale look each
166
+ * refuse.
167
+ */
168
+ export type SessionStateLoad = {
169
+ outcome: "found";
170
+ state: ReportSessionState;
171
+ analysisId: string;
172
+ token: SessionStateToken;
173
+ seenDocumentHash: string | null;
174
+ } | {
175
+ outcome: "absent";
176
+ } | {
177
+ outcome: "wrong-type";
178
+ detail: string;
179
+ } | {
180
+ outcome: "failed";
181
+ detail: string;
182
+ };
183
+ /** The outcome of a gateway persist. `conflict` means that a concurrent turn landed first. */
184
+ export type SessionStatePersist = {
185
+ outcome: "persisted";
186
+ } | {
187
+ outcome: "conflict";
188
+ } | {
189
+ outcome: "failed";
190
+ detail: string;
191
+ };
192
+ /**
193
+ * The outcome of a gateway stamp. `stamped` wrote the marker. `absent` means that no row holds the thread.
194
+ * `failed` is a transient store fault that names its cause. Each arm is plain data, thus a stamp never
195
+ * throws for one of them.
196
+ */
197
+ export type StampResult = {
198
+ outcome: "stamped";
199
+ } | {
200
+ outcome: "absent";
201
+ } | {
202
+ outcome: "failed";
203
+ detail: string;
204
+ };
205
+ /**
206
+ * The outcome of the seen stamp through the gateway. `stamped` copied a rendered hash onto the seen hash.
207
+ * `no-rendered` means that the row holds no rendered hash to copy, thus no preview stamped one. `absent`
208
+ * means that no row holds the thread. `failed` is a transient store fault that names its cause. Each arm is
209
+ * plain data, thus a stamp never throws for one of them.
210
+ */
211
+ export type SeenStampResult = {
212
+ outcome: "stamped";
213
+ } | {
214
+ outcome: "no-rendered";
215
+ } | {
216
+ outcome: "absent";
217
+ } | {
218
+ outcome: "failed";
219
+ detail: string;
220
+ };
221
+ /**
222
+ * The session-state gateway that the tools read and write.
223
+ *
224
+ * The tool layer is the one consumer, thus the interface lives here. The interface speaks a plain
225
+ * discriminated value, not a `Result`, because the tools read the outcome as data. The runtime realizes
226
+ * the gateway over the durable store. A load gives the state, a typed absence, or a typed failure. A
227
+ * persist gives success, or a typed failure.
228
+ */
229
+ export interface ReportSessionStateGateway {
230
+ load(threadId: string): Promise<SessionStateLoad>;
231
+ persist(threadId: string, document: DraftDocument, expected: SessionStateToken): Promise<SessionStatePersist>;
232
+ /**
233
+ * Stamp the hash of the rendered draft on the session state. The preview calls it when the page lands,
234
+ * thus the runtime knows which draft the page shows.
235
+ */
236
+ stampRendered(threadId: string, hash: string): Promise<StampResult>;
237
+ /**
238
+ * Copy the rendered hash onto the seen hash. The eyes call it after a capture, thus the seen hash holds
239
+ * the hash of the draft that the picture shows, and never the current one. The outcome names whether a
240
+ * rendered hash existed to copy, thus the eyes tell a real stamp from a missed one.
241
+ */
242
+ stampSeen(threadId: string): Promise<SeenStampResult>;
243
+ }
244
+ /**
245
+ * The thread of a call, its analysis, the loaded state, the concurrency token that the persist compares
246
+ * against, and the seen-document hash of the look-before-record rule.
247
+ */
248
+ export interface OpenedThread {
249
+ readonly threadId: string;
250
+ readonly analysisId: string;
251
+ readonly state: ReportSessionState;
252
+ readonly token: SessionStateToken;
253
+ /** The hash that the last eyes capture saw, or `null` before the first look. */
254
+ readonly seenDocumentHash: string | null;
255
+ }
256
+ /**
257
+ * Resolve the report thread of a call, and load its state through the gateway.
258
+ *
259
+ * The report thread and its analysis ride on the analysis scope. The thread id becomes one segment of a
260
+ * session directory, thus the safe-id check sits here beside the shape check: a scope whose id carries a
261
+ * separator or a traversal segment names no thread that a tool can write under.
262
+ *
263
+ * The stored analysis of the thread must match the scope. A mismatch reads one analysis's draft and writes
264
+ * into another analysis's workspace, thus the resolution refuses it with a permanent reason that names the
265
+ * two ids. A scope of a different kind, an unsafe id, an absent thread, a wrong thread type, and a gateway
266
+ * fault each refuse as a typed `SessionRefusal` too.
267
+ *
268
+ * The authoring tools and the preview tool share this one resolution, thus the two surfaces accept the
269
+ * same thread id and map a load outcome onto a refusal the same way.
270
+ */
271
+ export declare function openReportThread(gateway: ReportSessionStateGateway, scope: Scope): Promise<Result<OpenedThread, SessionRefusal>>;
272
+ /** The eight authoring tools. */
115
273
  export interface ReportAuthoringTools {
116
274
  readonly add_block: Tool<AddBlockInput, MutationResult>;
117
275
  readonly change_block: Tool<ChangeBlockInput, MutationResult>;
@@ -120,15 +278,14 @@ export interface ReportAuthoringTools {
120
278
  readonly set_title: Tool<SetTitleInput, MutationResult>;
121
279
  readonly read_outline: Tool<ReadOutlineInput, OutlineResult>;
122
280
  readonly read_block: Tool<ReadBlockInput, ReadBlockResult>;
123
- readonly finish_draft: Tool<FinishDraftInput, FinishResult>;
124
- currentDraft(): DraftDocument;
281
+ readonly finish_draft: Tool<FinishDraftInput, FinishToolResult>;
125
282
  }
126
283
  /**
127
- * Make the eight authoring tools over one draft.
284
+ * Make the eight authoring tools over a session-state gateway.
128
285
  *
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.
286
+ * Each tool reads the thread id from the scope of the call, and it loads the state of that thread through
287
+ * the gateway. Thus one factory serves every thread, and two threads never share one draft. A mutation
288
+ * persists the new document before it reports `applied: true`, thus a reported landing is never lost.
132
289
  */
133
- export declare function createReportAuthoringTools(state: ReportAuthoringState): ReportAuthoringTools;
290
+ export declare function createReportAuthoringTools(gateway: ReportSessionStateGateway): ReportAuthoringTools;
134
291
  export {};