markform 0.1.23 → 0.1.25
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/README.md +69 -29
- package/dist/ai-sdk.d.mts +1 -1
- package/dist/ai-sdk.mjs +48 -6
- package/dist/ai-sdk.mjs.map +1 -1
- package/dist/bin.mjs +1 -1
- package/dist/{cli-ZcOC47KK.mjs → cli-B1T8kMFt.mjs} +709 -125
- package/dist/cli-B1T8kMFt.mjs.map +1 -0
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/{coreTypes-BlsJkU1w.d.mts → coreTypes-CxpqKpBA.d.mts} +181 -4
- package/dist/{coreTypes-CTLr-NGd.mjs → coreTypes-DIv9Aabl.mjs} +56 -6
- package/dist/coreTypes-DIv9Aabl.mjs.map +1 -0
- package/dist/{fillRecord-DTl5lnK0.d.mts → fillRecord-V3vlyobd.d.mts} +29 -1
- package/dist/{fillRecordRenderer-VBQ2vwPV.mjs → fillRecordRenderer-BqRPHPmE.mjs} +47 -15
- package/dist/fillRecordRenderer-BqRPHPmE.mjs.map +1 -0
- package/dist/index.d.mts +34 -30
- package/dist/index.mjs +4 -4
- package/dist/{apply-KzQztrDV.mjs → prompts-DaPKumGY.mjs} +1081 -17
- package/dist/prompts-DaPKumGY.mjs.map +1 -0
- package/dist/render.d.mts +2 -2
- package/dist/render.mjs +1 -1
- package/dist/{session-BCcltrLA.mjs → session-BW9jtYNV.mjs} +2 -2
- package/dist/{session-BCcltrLA.mjs.map → session-BW9jtYNV.mjs.map} +1 -1
- package/dist/{session-VeSkVrck.mjs → session-DHyTMP67.mjs} +1 -1
- package/dist/{shared-fb0nkzQi.mjs → shared-BLh342F5.mjs} +1 -1
- package/dist/{shared-CsdT2T7k.mjs → shared-BszoSkAO.mjs} +10 -10
- package/dist/shared-BszoSkAO.mjs.map +1 -0
- package/dist/{src-B2uFvGli.mjs → src-DrXmaOWl.mjs} +179 -840
- package/dist/src-DrXmaOWl.mjs.map +1 -0
- package/docs/markform-apis.md +25 -7
- package/docs/markform-reference.md +273 -179
- package/docs/markform-spec.md +92 -36
- package/docs/skill/SKILL.md +161 -0
- package/examples/markform-demo-playbook.md +342 -0
- package/examples/parallel/parallel-research.form.md +2 -6
- package/examples/rejection-test/rejection-test.session.yaml +52 -0
- package/examples/simple/simple-mock-filled.report.md +2 -2
- package/examples/simple/simple-skipped-filled.report.md +2 -2
- package/examples/simple/simple-with-skips.session.yaml +78 -0
- package/examples/simple/simple.session.yaml +78 -0
- package/examples/twitter-thread/twitter-thread.form.md +5 -5
- package/package.json +2 -2
- package/dist/apply-KzQztrDV.mjs.map +0 -1
- package/dist/cli-ZcOC47KK.mjs.map +0 -1
- package/dist/coreTypes-CTLr-NGd.mjs.map +0 -1
- package/dist/fillRecordRenderer-VBQ2vwPV.mjs.map +0 -1
- package/dist/shared-CsdT2T7k.mjs.map +0 -1
- package/dist/src-B2uFvGli.mjs.map +0 -1
- package/examples/startup-research/startup-research-mock-filled.form.md +0 -297
- package/examples/startup-research/startup-research.form.md +0 -181
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
import { A as MarkformSectionInputSchema, R as PatchSchema,
|
|
3
|
-
import {
|
|
2
|
+
import { A as MarkformSectionInputSchema, B as ProgressCountsSchema, R as PatchSchema, ht as StructureSummarySchema, z as PatchWarningSchema } from "./coreTypes-DIv9Aabl.mjs";
|
|
3
|
+
import { $ as DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN, A as tryParseSentinelResponse, B as isTagNode, D as detectSyntaxStyle, F as getBooleanAttr, G as DEFAULT_MAX_PARALLEL_AGENTS, H as AGENT_ROLE, I as getNumberAttr, J as DEFAULT_MAX_STEPS_PER_TURN, K as DEFAULT_MAX_PATCHES_PER_TURN, L as getStringArrayAttr, M as extractFenceValue, N as extractOptionItems, O as preprocessCommentSyntax, P as extractTableContent, Pt as wrapApiError, Q as DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN, R as getStringAttr, S as computeStructureSummary, St as MarkformParseError, V as parseOptionText, W as DEFAULT_MAX_ISSUES_PER_TURN, Y as DEFAULT_MAX_TURNS, Z as DEFAULT_PRIORITY, _ as inspect, a as WEB_SEARCH_INSTRUCTIONS, c as filterIssuesByOrder, d as coerceInputContext, dt as transformHarnessConfigToTs, et as DEFAULT_ROLES, g as getFieldsForRoles, ht as getWebSearchConfig, i as SECTION_HEADERS, j as CHECKBOX_MARKERS, l as filterIssuesByScope, m as applyPatches, n as GENERAL_INSTRUCTIONS, o as getIssuesIntro, q as DEFAULT_MAX_RETRIES, r as ISSUES_HEADER, s as getPatchFormatHint, t as DEFAULT_SYSTEM_PROMPT, tt as DEFAULT_ROLE_INSTRUCTIONS, w as serializeForm, x as computeProgressSummary, yt as MarkformConfigError, z as getValidateAttr } from "./prompts-DaPKumGY.mjs";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import Markdoc from "@markdoc/markdoc";
|
|
6
6
|
import YAML from "yaml";
|
|
@@ -80,10 +80,11 @@ function parseCellValue(rawValue, columnType) {
|
|
|
80
80
|
state: sentinel.type === "skip" ? "skipped" : "aborted",
|
|
81
81
|
reason: sentinel.reason
|
|
82
82
|
};
|
|
83
|
+
const unescaped = trimmed.replace(/\\[|]/g, "|").replace(/<br\s*\/?>/gi, "\n");
|
|
83
84
|
switch (columnType) {
|
|
84
85
|
case "string": return {
|
|
85
86
|
state: "answered",
|
|
86
|
-
value:
|
|
87
|
+
value: unescaped
|
|
87
88
|
};
|
|
88
89
|
case "number": {
|
|
89
90
|
const num = parseFloat(trimmed);
|
|
@@ -799,6 +800,13 @@ function parseColumnsFromAttributes(node, fieldId, tableHeaderLabels) {
|
|
|
799
800
|
const typeSpec = columnTypesRaw?.[i];
|
|
800
801
|
let type = "string";
|
|
801
802
|
let required = false;
|
|
803
|
+
let minLength;
|
|
804
|
+
let maxLength;
|
|
805
|
+
let pattern;
|
|
806
|
+
let enumValues;
|
|
807
|
+
let min;
|
|
808
|
+
let max;
|
|
809
|
+
let integer;
|
|
802
810
|
if (typeSpec !== void 0) {
|
|
803
811
|
if (typeof typeSpec === "string") {
|
|
804
812
|
if (!isValidColumnType(typeSpec)) throw new MarkformParseError(`table-field '${fieldId}' has invalid column type '${String(typeSpec)}' for column '${id}'. Valid types: string, number, url, date, year`);
|
|
@@ -808,17 +816,68 @@ function parseColumnsFromAttributes(node, fieldId, tableHeaderLabels) {
|
|
|
808
816
|
if (!isValidColumnType(typeObj.type)) throw new MarkformParseError(`table-field '${fieldId}' has invalid column type '${String(typeObj.type)}' for column '${id}'. Valid types: string, number, url, date, year`);
|
|
809
817
|
type = typeObj.type;
|
|
810
818
|
required = typeObj.required ?? false;
|
|
819
|
+
if (typeof typeObj.minLength === "number") minLength = typeObj.minLength;
|
|
820
|
+
if (typeof typeObj.maxLength === "number") maxLength = typeObj.maxLength;
|
|
821
|
+
if (typeof typeObj.pattern === "string") pattern = typeObj.pattern;
|
|
822
|
+
if (Array.isArray(typeObj.enum)) enumValues = typeObj.enum;
|
|
823
|
+
if (typeObj.min !== void 0) min = typeObj.min;
|
|
824
|
+
if (typeObj.max !== void 0) max = typeObj.max;
|
|
825
|
+
if (typeof typeObj.integer === "boolean") integer = typeObj.integer;
|
|
826
|
+
validateColumnConstraints(fieldId, id, type, typeObj);
|
|
811
827
|
}
|
|
812
828
|
}
|
|
813
|
-
|
|
829
|
+
const col = {
|
|
814
830
|
id,
|
|
815
831
|
label,
|
|
816
832
|
type,
|
|
817
833
|
required
|
|
818
|
-
}
|
|
834
|
+
};
|
|
835
|
+
if (minLength !== void 0) col.minLength = minLength;
|
|
836
|
+
if (maxLength !== void 0) col.maxLength = maxLength;
|
|
837
|
+
if (pattern !== void 0) col.pattern = pattern;
|
|
838
|
+
if (enumValues !== void 0) col.enum = enumValues;
|
|
839
|
+
if (min !== void 0) col.min = min;
|
|
840
|
+
if (max !== void 0) col.max = max;
|
|
841
|
+
if (integer !== void 0) col.integer = integer;
|
|
842
|
+
columns.push(col);
|
|
819
843
|
}
|
|
820
844
|
return columns;
|
|
821
845
|
}
|
|
846
|
+
/** String-only constraints. */
|
|
847
|
+
const STRING_ONLY_CONSTRAINTS = [
|
|
848
|
+
"minLength",
|
|
849
|
+
"maxLength",
|
|
850
|
+
"pattern",
|
|
851
|
+
"enum"
|
|
852
|
+
];
|
|
853
|
+
/** Number-only constraints. */
|
|
854
|
+
const NUMBER_ONLY_CONSTRAINTS = ["integer"];
|
|
855
|
+
/** Constraints valid per column type. */
|
|
856
|
+
const VALID_CONSTRAINTS_BY_TYPE = {
|
|
857
|
+
string: [...STRING_ONLY_CONSTRAINTS],
|
|
858
|
+
number: [
|
|
859
|
+
"min",
|
|
860
|
+
"max",
|
|
861
|
+
...NUMBER_ONLY_CONSTRAINTS
|
|
862
|
+
],
|
|
863
|
+
date: ["min", "max"],
|
|
864
|
+
year: ["min", "max"],
|
|
865
|
+
url: []
|
|
866
|
+
};
|
|
867
|
+
/**
|
|
868
|
+
* Validate that per-column constraints are appropriate for the column type.
|
|
869
|
+
* Throws MarkformParseError for mismatched constraints (e.g. minLength on a number column).
|
|
870
|
+
*/
|
|
871
|
+
function validateColumnConstraints(fieldId, columnId, type, typeObj) {
|
|
872
|
+
const allConstraintKeys = [
|
|
873
|
+
...STRING_ONLY_CONSTRAINTS,
|
|
874
|
+
...NUMBER_ONLY_CONSTRAINTS,
|
|
875
|
+
"min",
|
|
876
|
+
"max"
|
|
877
|
+
];
|
|
878
|
+
const validKeys = VALID_CONSTRAINTS_BY_TYPE[type];
|
|
879
|
+
for (const key of allConstraintKeys) if (typeObj[key] !== void 0 && !validKeys.includes(key)) throw new MarkformParseError(`table-field '${fieldId}' column '${columnId}': constraint '${key}' is not valid for type '${type}'. Valid constraints for '${type}': ${validKeys.length > 0 ? validKeys.join(", ") : "none"}`);
|
|
880
|
+
}
|
|
822
881
|
/**
|
|
823
882
|
* Parse a table-field tag.
|
|
824
883
|
*
|
|
@@ -1645,7 +1704,11 @@ function extractDocBlocks(ast, idIndex) {
|
|
|
1645
1704
|
else if (n.type === "softbreak" || n.type === "hardbreak") bodyMarkdown += "\n";
|
|
1646
1705
|
if (n.children && Array.isArray(n.children)) for (const c of n.children) extractText(c);
|
|
1647
1706
|
}
|
|
1648
|
-
if (node.children && Array.isArray(node.children)) for (
|
|
1707
|
+
if (node.children && Array.isArray(node.children)) for (let ci = 0; ci < node.children.length; ci++) {
|
|
1708
|
+
const child = node.children[ci];
|
|
1709
|
+
if (ci > 0 && child.type === "paragraph") bodyMarkdown += "\n\n";
|
|
1710
|
+
extractText(child);
|
|
1711
|
+
}
|
|
1649
1712
|
docs.push({
|
|
1650
1713
|
tag,
|
|
1651
1714
|
ref,
|
|
@@ -1900,7 +1963,7 @@ function tableFieldToJsonSchema(field, docs, options, groupId) {
|
|
|
1900
1963
|
const rowProperties = {};
|
|
1901
1964
|
const requiredColumns = [];
|
|
1902
1965
|
for (const col of field.columns) {
|
|
1903
|
-
rowProperties[col.id] = columnToJsonSchema(col);
|
|
1966
|
+
rowProperties[col.id] = columnToJsonSchema(col, options);
|
|
1904
1967
|
if (col.required) requiredColumns.push(col.id);
|
|
1905
1968
|
}
|
|
1906
1969
|
const rowSchema = {
|
|
@@ -1920,14 +1983,21 @@ function tableFieldToJsonSchema(field, docs, options, groupId) {
|
|
|
1920
1983
|
if (options.includeExtensions) schema["x-markform"] = buildFieldExtension(field, groupId);
|
|
1921
1984
|
return schema;
|
|
1922
1985
|
}
|
|
1923
|
-
function columnToJsonSchema(col) {
|
|
1986
|
+
function columnToJsonSchema(col, options) {
|
|
1924
1987
|
const schema = { title: col.label };
|
|
1925
1988
|
switch (col.type) {
|
|
1926
1989
|
case "string":
|
|
1927
1990
|
schema.type = "string";
|
|
1991
|
+
if (col.minLength !== void 0) schema.minLength = col.minLength;
|
|
1992
|
+
if (col.maxLength !== void 0) schema.maxLength = col.maxLength;
|
|
1993
|
+
if (col.pattern !== void 0) schema.pattern = col.pattern;
|
|
1994
|
+
if (col.enum !== void 0 && col.enum.length > 0) schema.enum = col.enum;
|
|
1928
1995
|
break;
|
|
1929
1996
|
case "number":
|
|
1930
1997
|
schema.type = "number";
|
|
1998
|
+
if (col.integer) schema.type = "integer";
|
|
1999
|
+
if (typeof col.min === "number") schema.minimum = col.min;
|
|
2000
|
+
if (typeof col.max === "number") schema.maximum = col.max;
|
|
1931
2001
|
break;
|
|
1932
2002
|
case "url":
|
|
1933
2003
|
schema.type = "string";
|
|
@@ -1936,9 +2006,15 @@ function columnToJsonSchema(col) {
|
|
|
1936
2006
|
case "date":
|
|
1937
2007
|
schema.type = "string";
|
|
1938
2008
|
schema.format = "date";
|
|
2009
|
+
if (options.draft !== "draft-07") {
|
|
2010
|
+
if (typeof col.min === "string") schema.formatMinimum = col.min;
|
|
2011
|
+
if (typeof col.max === "string") schema.formatMaximum = col.max;
|
|
2012
|
+
}
|
|
1939
2013
|
break;
|
|
1940
2014
|
case "year":
|
|
1941
2015
|
schema.type = "integer";
|
|
2016
|
+
if (typeof col.min === "number") schema.minimum = col.min;
|
|
2017
|
+
if (typeof col.max === "number") schema.maximum = col.max;
|
|
1942
2018
|
break;
|
|
1943
2019
|
}
|
|
1944
2020
|
return schema;
|
|
@@ -2154,549 +2230,6 @@ function getFieldId(ref) {
|
|
|
2154
2230
|
return ref.fieldId;
|
|
2155
2231
|
}
|
|
2156
2232
|
|
|
2157
|
-
//#endregion
|
|
2158
|
-
//#region src/engine/valueCoercion.ts
|
|
2159
|
-
/**
|
|
2160
|
-
* Find a field by ID.
|
|
2161
|
-
*
|
|
2162
|
-
* Uses idIndex for O(1) validation that the ID exists and is a field,
|
|
2163
|
-
* then retrieves the Field object from the schema.
|
|
2164
|
-
*/
|
|
2165
|
-
function findFieldById(form, fieldId) {
|
|
2166
|
-
if (form.idIndex.get(fieldId)?.nodeType !== "field") return;
|
|
2167
|
-
for (const group of form.schema.groups) for (const field of group.children) if (field.id === fieldId) return field;
|
|
2168
|
-
}
|
|
2169
|
-
function isPlainObject(value) {
|
|
2170
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2171
|
-
}
|
|
2172
|
-
function isStringArray(value) {
|
|
2173
|
-
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
2174
|
-
}
|
|
2175
|
-
function coerceToString(fieldId, rawValue) {
|
|
2176
|
-
if (rawValue === null) return {
|
|
2177
|
-
ok: true,
|
|
2178
|
-
patch: {
|
|
2179
|
-
op: "set_string",
|
|
2180
|
-
fieldId,
|
|
2181
|
-
value: null
|
|
2182
|
-
}
|
|
2183
|
-
};
|
|
2184
|
-
if (typeof rawValue === "string") return {
|
|
2185
|
-
ok: true,
|
|
2186
|
-
patch: {
|
|
2187
|
-
op: "set_string",
|
|
2188
|
-
fieldId,
|
|
2189
|
-
value: rawValue
|
|
2190
|
-
}
|
|
2191
|
-
};
|
|
2192
|
-
if (typeof rawValue === "number") return {
|
|
2193
|
-
ok: true,
|
|
2194
|
-
patch: {
|
|
2195
|
-
op: "set_string",
|
|
2196
|
-
fieldId,
|
|
2197
|
-
value: String(rawValue)
|
|
2198
|
-
},
|
|
2199
|
-
warning: `Coerced number ${rawValue} to string for field '${fieldId}'`
|
|
2200
|
-
};
|
|
2201
|
-
if (typeof rawValue === "boolean") return {
|
|
2202
|
-
ok: true,
|
|
2203
|
-
patch: {
|
|
2204
|
-
op: "set_string",
|
|
2205
|
-
fieldId,
|
|
2206
|
-
value: String(rawValue)
|
|
2207
|
-
},
|
|
2208
|
-
warning: `Coerced boolean ${rawValue} to string for field '${fieldId}'`
|
|
2209
|
-
};
|
|
2210
|
-
return {
|
|
2211
|
-
ok: false,
|
|
2212
|
-
error: `Cannot coerce ${typeof rawValue} to string for field '${fieldId}'`
|
|
2213
|
-
};
|
|
2214
|
-
}
|
|
2215
|
-
function coerceToNumber(fieldId, rawValue) {
|
|
2216
|
-
if (rawValue === null) return {
|
|
2217
|
-
ok: true,
|
|
2218
|
-
patch: {
|
|
2219
|
-
op: "set_number",
|
|
2220
|
-
fieldId,
|
|
2221
|
-
value: null
|
|
2222
|
-
}
|
|
2223
|
-
};
|
|
2224
|
-
if (typeof rawValue === "number") return {
|
|
2225
|
-
ok: true,
|
|
2226
|
-
patch: {
|
|
2227
|
-
op: "set_number",
|
|
2228
|
-
fieldId,
|
|
2229
|
-
value: rawValue
|
|
2230
|
-
}
|
|
2231
|
-
};
|
|
2232
|
-
if (typeof rawValue === "string") {
|
|
2233
|
-
const parsed = Number(rawValue);
|
|
2234
|
-
if (!Number.isNaN(parsed)) return {
|
|
2235
|
-
ok: true,
|
|
2236
|
-
patch: {
|
|
2237
|
-
op: "set_number",
|
|
2238
|
-
fieldId,
|
|
2239
|
-
value: parsed
|
|
2240
|
-
},
|
|
2241
|
-
warning: `Coerced string '${rawValue}' to number for field '${fieldId}'`
|
|
2242
|
-
};
|
|
2243
|
-
return {
|
|
2244
|
-
ok: false,
|
|
2245
|
-
error: `Cannot coerce non-numeric string '${rawValue}' to number for field '${fieldId}'`
|
|
2246
|
-
};
|
|
2247
|
-
}
|
|
2248
|
-
return {
|
|
2249
|
-
ok: false,
|
|
2250
|
-
error: `Cannot coerce ${typeof rawValue} to number for field '${fieldId}'`
|
|
2251
|
-
};
|
|
2252
|
-
}
|
|
2253
|
-
function coerceToStringList(fieldId, rawValue) {
|
|
2254
|
-
if (rawValue === null) return {
|
|
2255
|
-
ok: true,
|
|
2256
|
-
patch: {
|
|
2257
|
-
op: "set_string_list",
|
|
2258
|
-
fieldId,
|
|
2259
|
-
value: []
|
|
2260
|
-
}
|
|
2261
|
-
};
|
|
2262
|
-
if (isStringArray(rawValue)) return {
|
|
2263
|
-
ok: true,
|
|
2264
|
-
patch: {
|
|
2265
|
-
op: "set_string_list",
|
|
2266
|
-
fieldId,
|
|
2267
|
-
value: rawValue
|
|
2268
|
-
}
|
|
2269
|
-
};
|
|
2270
|
-
if (typeof rawValue === "string") return {
|
|
2271
|
-
ok: true,
|
|
2272
|
-
patch: {
|
|
2273
|
-
op: "set_string_list",
|
|
2274
|
-
fieldId,
|
|
2275
|
-
value: [rawValue]
|
|
2276
|
-
},
|
|
2277
|
-
warning: `Coerced single string to array for field '${fieldId}'`
|
|
2278
|
-
};
|
|
2279
|
-
if (Array.isArray(rawValue)) {
|
|
2280
|
-
const items = [];
|
|
2281
|
-
for (const item of rawValue) if (typeof item === "string") items.push(item);
|
|
2282
|
-
else if (typeof item === "number" || typeof item === "boolean") items.push(String(item));
|
|
2283
|
-
else return {
|
|
2284
|
-
ok: false,
|
|
2285
|
-
error: `Cannot coerce array with non-string items to string_list for field '${fieldId}'`
|
|
2286
|
-
};
|
|
2287
|
-
return {
|
|
2288
|
-
ok: true,
|
|
2289
|
-
patch: {
|
|
2290
|
-
op: "set_string_list",
|
|
2291
|
-
fieldId,
|
|
2292
|
-
value: items
|
|
2293
|
-
},
|
|
2294
|
-
warning: `Coerced array items to strings for field '${fieldId}'`
|
|
2295
|
-
};
|
|
2296
|
-
}
|
|
2297
|
-
return {
|
|
2298
|
-
ok: false,
|
|
2299
|
-
error: `Cannot coerce ${typeof rawValue} to string_list for field '${fieldId}'`
|
|
2300
|
-
};
|
|
2301
|
-
}
|
|
2302
|
-
function coerceToSingleSelect(field, rawValue) {
|
|
2303
|
-
if (field.kind !== "single_select") return {
|
|
2304
|
-
ok: false,
|
|
2305
|
-
error: `Field '${field.id}' is not a single_select field`
|
|
2306
|
-
};
|
|
2307
|
-
if (rawValue === null) return {
|
|
2308
|
-
ok: true,
|
|
2309
|
-
patch: {
|
|
2310
|
-
op: "set_single_select",
|
|
2311
|
-
fieldId: field.id,
|
|
2312
|
-
value: null
|
|
2313
|
-
}
|
|
2314
|
-
};
|
|
2315
|
-
if (typeof rawValue !== "string") return {
|
|
2316
|
-
ok: false,
|
|
2317
|
-
error: `single_select field '${field.id}' requires a string option ID, got ${typeof rawValue}`
|
|
2318
|
-
};
|
|
2319
|
-
const validOptions = new Set(field.options.map((o) => o.id));
|
|
2320
|
-
if (!validOptions.has(rawValue)) return {
|
|
2321
|
-
ok: false,
|
|
2322
|
-
error: `Invalid option '${rawValue}' for single_select field '${field.id}'. Valid options: ${Array.from(validOptions).join(", ")}`
|
|
2323
|
-
};
|
|
2324
|
-
return {
|
|
2325
|
-
ok: true,
|
|
2326
|
-
patch: {
|
|
2327
|
-
op: "set_single_select",
|
|
2328
|
-
fieldId: field.id,
|
|
2329
|
-
value: rawValue
|
|
2330
|
-
}
|
|
2331
|
-
};
|
|
2332
|
-
}
|
|
2333
|
-
function coerceToMultiSelect(field, rawValue) {
|
|
2334
|
-
if (field.kind !== "multi_select") return {
|
|
2335
|
-
ok: false,
|
|
2336
|
-
error: `Field '${field.id}' is not a multi_select field`
|
|
2337
|
-
};
|
|
2338
|
-
if (rawValue === null) return {
|
|
2339
|
-
ok: true,
|
|
2340
|
-
patch: {
|
|
2341
|
-
op: "set_multi_select",
|
|
2342
|
-
fieldId: field.id,
|
|
2343
|
-
value: []
|
|
2344
|
-
}
|
|
2345
|
-
};
|
|
2346
|
-
const validOptions = new Set(field.options.map((o) => o.id));
|
|
2347
|
-
let selected;
|
|
2348
|
-
let warning;
|
|
2349
|
-
if (typeof rawValue === "string") {
|
|
2350
|
-
selected = [rawValue];
|
|
2351
|
-
warning = `Coerced single string to array for multi_select field '${field.id}'`;
|
|
2352
|
-
} else if (isStringArray(rawValue)) selected = rawValue;
|
|
2353
|
-
else return {
|
|
2354
|
-
ok: false,
|
|
2355
|
-
error: `multi_select field '${field.id}' requires a string or string array, got ${typeof rawValue}`
|
|
2356
|
-
};
|
|
2357
|
-
for (const optId of selected) if (!validOptions.has(optId)) return {
|
|
2358
|
-
ok: false,
|
|
2359
|
-
error: `Invalid option '${optId}' for multi_select field '${field.id}'. Valid options: ${Array.from(validOptions).join(", ")}`
|
|
2360
|
-
};
|
|
2361
|
-
const patch = {
|
|
2362
|
-
op: "set_multi_select",
|
|
2363
|
-
fieldId: field.id,
|
|
2364
|
-
value: selected
|
|
2365
|
-
};
|
|
2366
|
-
return warning ? {
|
|
2367
|
-
ok: true,
|
|
2368
|
-
patch,
|
|
2369
|
-
warning
|
|
2370
|
-
} : {
|
|
2371
|
-
ok: true,
|
|
2372
|
-
patch
|
|
2373
|
-
};
|
|
2374
|
-
}
|
|
2375
|
-
function coerceToCheckboxes(field, rawValue) {
|
|
2376
|
-
if (field.kind !== "checkboxes") return {
|
|
2377
|
-
ok: false,
|
|
2378
|
-
error: `Field '${field.id}' is not a checkboxes field`
|
|
2379
|
-
};
|
|
2380
|
-
if (rawValue === null) return {
|
|
2381
|
-
ok: true,
|
|
2382
|
-
patch: {
|
|
2383
|
-
op: "set_checkboxes",
|
|
2384
|
-
fieldId: field.id,
|
|
2385
|
-
value: {}
|
|
2386
|
-
}
|
|
2387
|
-
};
|
|
2388
|
-
const validOptions = new Set(field.options.map((o) => o.id));
|
|
2389
|
-
const checkboxMode = field.checkboxMode;
|
|
2390
|
-
if (Array.isArray(rawValue)) {
|
|
2391
|
-
const defaultState = checkboxMode === "explicit" ? "yes" : "done";
|
|
2392
|
-
const values = {};
|
|
2393
|
-
for (const item of rawValue) {
|
|
2394
|
-
if (typeof item !== "string") return {
|
|
2395
|
-
ok: false,
|
|
2396
|
-
error: `Array items for checkboxes field '${field.id}' must be strings (option IDs), got ${typeof item}`
|
|
2397
|
-
};
|
|
2398
|
-
if (!validOptions.has(item)) return {
|
|
2399
|
-
ok: false,
|
|
2400
|
-
error: `Invalid option '${item}' for checkboxes field '${field.id}'. Valid options: ${Array.from(validOptions).join(", ")}`
|
|
2401
|
-
};
|
|
2402
|
-
values[item] = defaultState;
|
|
2403
|
-
}
|
|
2404
|
-
const patch = {
|
|
2405
|
-
op: "set_checkboxes",
|
|
2406
|
-
fieldId: field.id,
|
|
2407
|
-
value: values
|
|
2408
|
-
};
|
|
2409
|
-
if (rawValue.length === 0) return {
|
|
2410
|
-
ok: true,
|
|
2411
|
-
patch
|
|
2412
|
-
};
|
|
2413
|
-
return {
|
|
2414
|
-
ok: true,
|
|
2415
|
-
patch,
|
|
2416
|
-
warning: `Coerced array to checkboxes object with '${defaultState}' state for field '${field.id}'`
|
|
2417
|
-
};
|
|
2418
|
-
}
|
|
2419
|
-
if (!isPlainObject(rawValue)) return {
|
|
2420
|
-
ok: false,
|
|
2421
|
-
error: `checkboxes field '${field.id}' requires a Record<string, CheckboxValue> or array of option IDs, got ${typeof rawValue}`
|
|
2422
|
-
};
|
|
2423
|
-
const values = {};
|
|
2424
|
-
let hadBooleanCoercion = false;
|
|
2425
|
-
const validValues = new Set(checkboxMode === "explicit" ? [
|
|
2426
|
-
"unfilled",
|
|
2427
|
-
"yes",
|
|
2428
|
-
"no"
|
|
2429
|
-
] : checkboxMode === "simple" ? ["todo", "done"] : [
|
|
2430
|
-
"todo",
|
|
2431
|
-
"done",
|
|
2432
|
-
"incomplete",
|
|
2433
|
-
"active",
|
|
2434
|
-
"na"
|
|
2435
|
-
]);
|
|
2436
|
-
for (const [optId, value] of Object.entries(rawValue)) {
|
|
2437
|
-
if (!validOptions.has(optId)) return {
|
|
2438
|
-
ok: false,
|
|
2439
|
-
error: `Invalid option '${optId}' for checkboxes field '${field.id}'. Valid options: ${Array.from(validOptions).join(", ")}`
|
|
2440
|
-
};
|
|
2441
|
-
if (typeof value === "boolean") {
|
|
2442
|
-
hadBooleanCoercion = true;
|
|
2443
|
-
if (checkboxMode === "explicit") values[optId] = value ? "yes" : "no";
|
|
2444
|
-
else values[optId] = value ? "done" : "todo";
|
|
2445
|
-
continue;
|
|
2446
|
-
}
|
|
2447
|
-
if (typeof value !== "string" || !validValues.has(value)) return {
|
|
2448
|
-
ok: false,
|
|
2449
|
-
error: `Invalid checkbox value '${String(value)}' for option '${optId}' in field '${field.id}'. Valid values for ${checkboxMode} mode: ${Array.from(validValues).join(", ")} (or use true/false)`
|
|
2450
|
-
};
|
|
2451
|
-
values[optId] = value;
|
|
2452
|
-
}
|
|
2453
|
-
const patch = {
|
|
2454
|
-
op: "set_checkboxes",
|
|
2455
|
-
fieldId: field.id,
|
|
2456
|
-
value: values
|
|
2457
|
-
};
|
|
2458
|
-
if (hadBooleanCoercion) return {
|
|
2459
|
-
ok: true,
|
|
2460
|
-
patch,
|
|
2461
|
-
warning: `Coerced boolean values to checkbox strings for field '${field.id}'`
|
|
2462
|
-
};
|
|
2463
|
-
return {
|
|
2464
|
-
ok: true,
|
|
2465
|
-
patch
|
|
2466
|
-
};
|
|
2467
|
-
}
|
|
2468
|
-
function coerceToUrl(fieldId, rawValue) {
|
|
2469
|
-
if (rawValue === null) return {
|
|
2470
|
-
ok: true,
|
|
2471
|
-
patch: {
|
|
2472
|
-
op: "set_url",
|
|
2473
|
-
fieldId,
|
|
2474
|
-
value: null
|
|
2475
|
-
}
|
|
2476
|
-
};
|
|
2477
|
-
if (typeof rawValue === "string") return {
|
|
2478
|
-
ok: true,
|
|
2479
|
-
patch: {
|
|
2480
|
-
op: "set_url",
|
|
2481
|
-
fieldId,
|
|
2482
|
-
value: rawValue
|
|
2483
|
-
}
|
|
2484
|
-
};
|
|
2485
|
-
return {
|
|
2486
|
-
ok: false,
|
|
2487
|
-
error: `Cannot coerce ${typeof rawValue} to url for field '${fieldId}'`
|
|
2488
|
-
};
|
|
2489
|
-
}
|
|
2490
|
-
function coerceToUrlList(fieldId, rawValue) {
|
|
2491
|
-
if (rawValue === null) return {
|
|
2492
|
-
ok: true,
|
|
2493
|
-
patch: {
|
|
2494
|
-
op: "set_url_list",
|
|
2495
|
-
fieldId,
|
|
2496
|
-
value: []
|
|
2497
|
-
}
|
|
2498
|
-
};
|
|
2499
|
-
if (isStringArray(rawValue)) return {
|
|
2500
|
-
ok: true,
|
|
2501
|
-
patch: {
|
|
2502
|
-
op: "set_url_list",
|
|
2503
|
-
fieldId,
|
|
2504
|
-
value: rawValue
|
|
2505
|
-
}
|
|
2506
|
-
};
|
|
2507
|
-
if (typeof rawValue === "string") return {
|
|
2508
|
-
ok: true,
|
|
2509
|
-
patch: {
|
|
2510
|
-
op: "set_url_list",
|
|
2511
|
-
fieldId,
|
|
2512
|
-
value: [rawValue]
|
|
2513
|
-
},
|
|
2514
|
-
warning: `Coerced single string to array for field '${fieldId}'`
|
|
2515
|
-
};
|
|
2516
|
-
if (Array.isArray(rawValue)) {
|
|
2517
|
-
const items = [];
|
|
2518
|
-
for (const item of rawValue) if (typeof item === "string") items.push(item);
|
|
2519
|
-
else return {
|
|
2520
|
-
ok: false,
|
|
2521
|
-
error: `Cannot coerce array with non-string items to url_list for field '${fieldId}'`
|
|
2522
|
-
};
|
|
2523
|
-
return {
|
|
2524
|
-
ok: true,
|
|
2525
|
-
patch: {
|
|
2526
|
-
op: "set_url_list",
|
|
2527
|
-
fieldId,
|
|
2528
|
-
value: items
|
|
2529
|
-
}
|
|
2530
|
-
};
|
|
2531
|
-
}
|
|
2532
|
-
return {
|
|
2533
|
-
ok: false,
|
|
2534
|
-
error: `Cannot coerce ${typeof rawValue} to url_list for field '${fieldId}'`
|
|
2535
|
-
};
|
|
2536
|
-
}
|
|
2537
|
-
function coerceToDate(fieldId, rawValue) {
|
|
2538
|
-
if (rawValue === null) return {
|
|
2539
|
-
ok: true,
|
|
2540
|
-
patch: {
|
|
2541
|
-
op: "set_date",
|
|
2542
|
-
fieldId,
|
|
2543
|
-
value: null
|
|
2544
|
-
}
|
|
2545
|
-
};
|
|
2546
|
-
if (typeof rawValue === "string") return {
|
|
2547
|
-
ok: true,
|
|
2548
|
-
patch: {
|
|
2549
|
-
op: "set_date",
|
|
2550
|
-
fieldId,
|
|
2551
|
-
value: rawValue
|
|
2552
|
-
}
|
|
2553
|
-
};
|
|
2554
|
-
return {
|
|
2555
|
-
ok: false,
|
|
2556
|
-
error: `Cannot coerce ${typeof rawValue} to date for field '${fieldId}'`
|
|
2557
|
-
};
|
|
2558
|
-
}
|
|
2559
|
-
function coerceToYear(fieldId, rawValue) {
|
|
2560
|
-
if (rawValue === null) return {
|
|
2561
|
-
ok: true,
|
|
2562
|
-
patch: {
|
|
2563
|
-
op: "set_year",
|
|
2564
|
-
fieldId,
|
|
2565
|
-
value: null
|
|
2566
|
-
}
|
|
2567
|
-
};
|
|
2568
|
-
if (typeof rawValue === "number") {
|
|
2569
|
-
if (!Number.isInteger(rawValue)) return {
|
|
2570
|
-
ok: false,
|
|
2571
|
-
error: `Year must be an integer for field '${fieldId}', got ${rawValue}`
|
|
2572
|
-
};
|
|
2573
|
-
return {
|
|
2574
|
-
ok: true,
|
|
2575
|
-
patch: {
|
|
2576
|
-
op: "set_year",
|
|
2577
|
-
fieldId,
|
|
2578
|
-
value: rawValue
|
|
2579
|
-
}
|
|
2580
|
-
};
|
|
2581
|
-
}
|
|
2582
|
-
if (typeof rawValue === "string") {
|
|
2583
|
-
const parsed = Number.parseInt(rawValue, 10);
|
|
2584
|
-
if (Number.isNaN(parsed)) return {
|
|
2585
|
-
ok: false,
|
|
2586
|
-
error: `Cannot coerce non-numeric string '${rawValue}' to year for field '${fieldId}'`
|
|
2587
|
-
};
|
|
2588
|
-
return {
|
|
2589
|
-
ok: true,
|
|
2590
|
-
patch: {
|
|
2591
|
-
op: "set_year",
|
|
2592
|
-
fieldId,
|
|
2593
|
-
value: parsed
|
|
2594
|
-
},
|
|
2595
|
-
warning: `Coerced string '${rawValue}' to year for field '${fieldId}'`
|
|
2596
|
-
};
|
|
2597
|
-
}
|
|
2598
|
-
return {
|
|
2599
|
-
ok: false,
|
|
2600
|
-
error: `Cannot coerce ${typeof rawValue} to year for field '${fieldId}'`
|
|
2601
|
-
};
|
|
2602
|
-
}
|
|
2603
|
-
/**
|
|
2604
|
-
* Coerce raw value to SetTablePatch.
|
|
2605
|
-
* Accepts:
|
|
2606
|
-
* - Array of row objects: [{ col1: value1, col2: value2 }, ...]
|
|
2607
|
-
* - Empty array: [] (valid for optional tables or minRows=0)
|
|
2608
|
-
*/
|
|
2609
|
-
function coerceToTable(fieldId, rawValue) {
|
|
2610
|
-
if (rawValue === null) return {
|
|
2611
|
-
ok: true,
|
|
2612
|
-
patch: {
|
|
2613
|
-
op: "set_table",
|
|
2614
|
-
fieldId,
|
|
2615
|
-
value: []
|
|
2616
|
-
}
|
|
2617
|
-
};
|
|
2618
|
-
if (!Array.isArray(rawValue)) return {
|
|
2619
|
-
ok: false,
|
|
2620
|
-
error: `Table value for field '${fieldId}' must be an array of rows, got ${typeof rawValue}`
|
|
2621
|
-
};
|
|
2622
|
-
if (rawValue.length === 0) return {
|
|
2623
|
-
ok: true,
|
|
2624
|
-
patch: {
|
|
2625
|
-
op: "set_table",
|
|
2626
|
-
fieldId,
|
|
2627
|
-
value: []
|
|
2628
|
-
}
|
|
2629
|
-
};
|
|
2630
|
-
const rows = [];
|
|
2631
|
-
for (let i = 0; i < rawValue.length; i++) {
|
|
2632
|
-
const row = rawValue[i];
|
|
2633
|
-
if (typeof row !== "object" || row === null || Array.isArray(row)) return {
|
|
2634
|
-
ok: false,
|
|
2635
|
-
error: `Row ${i} for table field '${fieldId}' must be an object, got ${Array.isArray(row) ? "array" : typeof row}`
|
|
2636
|
-
};
|
|
2637
|
-
rows.push(row);
|
|
2638
|
-
}
|
|
2639
|
-
return {
|
|
2640
|
-
ok: true,
|
|
2641
|
-
patch: {
|
|
2642
|
-
op: "set_table",
|
|
2643
|
-
fieldId,
|
|
2644
|
-
value: rows
|
|
2645
|
-
}
|
|
2646
|
-
};
|
|
2647
|
-
}
|
|
2648
|
-
/**
|
|
2649
|
-
* Coerce a raw value to a Patch for a specific field.
|
|
2650
|
-
*/
|
|
2651
|
-
function coerceToFieldPatch(form, fieldId, rawValue) {
|
|
2652
|
-
const field = findFieldById(form, fieldId);
|
|
2653
|
-
if (!field) return {
|
|
2654
|
-
ok: false,
|
|
2655
|
-
error: `Field '${fieldId}' not found`
|
|
2656
|
-
};
|
|
2657
|
-
switch (field.kind) {
|
|
2658
|
-
case "string": return coerceToString(fieldId, rawValue);
|
|
2659
|
-
case "number": return coerceToNumber(fieldId, rawValue);
|
|
2660
|
-
case "string_list": return coerceToStringList(fieldId, rawValue);
|
|
2661
|
-
case "single_select": return coerceToSingleSelect(field, rawValue);
|
|
2662
|
-
case "multi_select": return coerceToMultiSelect(field, rawValue);
|
|
2663
|
-
case "checkboxes": return coerceToCheckboxes(field, rawValue);
|
|
2664
|
-
case "url": return coerceToUrl(fieldId, rawValue);
|
|
2665
|
-
case "url_list": return coerceToUrlList(fieldId, rawValue);
|
|
2666
|
-
case "date": return coerceToDate(fieldId, rawValue);
|
|
2667
|
-
case "year": return coerceToYear(fieldId, rawValue);
|
|
2668
|
-
case "table": return coerceToTable(fieldId, rawValue);
|
|
2669
|
-
default: {
|
|
2670
|
-
const _exhaustive = field;
|
|
2671
|
-
throw new Error(`Unhandled field kind: ${_exhaustive.kind}`);
|
|
2672
|
-
}
|
|
2673
|
-
}
|
|
2674
|
-
}
|
|
2675
|
-
/**
|
|
2676
|
-
* Coerce an entire InputContext to patches.
|
|
2677
|
-
*
|
|
2678
|
-
* Returns patches for valid entries, collects warnings for coercions,
|
|
2679
|
-
* and errors for invalid entries.
|
|
2680
|
-
*/
|
|
2681
|
-
function coerceInputContext(form, inputContext) {
|
|
2682
|
-
const patches = [];
|
|
2683
|
-
const warnings = [];
|
|
2684
|
-
const errors = [];
|
|
2685
|
-
for (const [fieldId, rawValue] of Object.entries(inputContext)) {
|
|
2686
|
-
if (rawValue === null) continue;
|
|
2687
|
-
const result = coerceToFieldPatch(form, fieldId, rawValue);
|
|
2688
|
-
if (result.ok) {
|
|
2689
|
-
patches.push(result.patch);
|
|
2690
|
-
if ("warning" in result && result.warning) warnings.push(result.warning);
|
|
2691
|
-
} else errors.push(result.error);
|
|
2692
|
-
}
|
|
2693
|
-
return {
|
|
2694
|
-
patches,
|
|
2695
|
-
warnings,
|
|
2696
|
-
errors
|
|
2697
|
-
};
|
|
2698
|
-
}
|
|
2699
|
-
|
|
2700
2233
|
//#endregion
|
|
2701
2234
|
//#region src/harness/harness.ts
|
|
2702
2235
|
/**
|
|
@@ -2818,6 +2351,7 @@ var FormHarness = class {
|
|
|
2818
2351
|
const stepResult = this.computeStepResult(result);
|
|
2819
2352
|
stepResult.patchesApplied = patchesActuallyApplied;
|
|
2820
2353
|
stepResult.rejectedPatches = applyResult.rejectedPatches;
|
|
2354
|
+
stepResult.coercionWarnings = applyResult.warnings;
|
|
2821
2355
|
this.recordTurn(issues, patches, result, llmStats, context, applyResult.rejectedPatches, applyResult.warnings, wire);
|
|
2822
2356
|
if (stepResult.issues.length === 0 || this.turnNumber >= this.config.maxTurns) this.state = "complete";
|
|
2823
2357
|
else this.state = "wait";
|
|
@@ -2828,8 +2362,7 @@ var FormHarness = class {
|
|
|
2828
2362
|
* Applies issue filtering and computes step budget.
|
|
2829
2363
|
*/
|
|
2830
2364
|
computeStepResult(result) {
|
|
2831
|
-
const
|
|
2832
|
-
const limitedIssues = this.filterIssuesByScope(orderFiltered).slice(0, this.config.maxIssuesPerTurn);
|
|
2365
|
+
const limitedIssues = filterIssuesByScope(filterIssuesByOrder(result.issues, this.form), this.form, this.config.maxFieldsPerTurn, this.config.maxGroupsPerTurn).slice(0, this.config.maxIssuesPerTurn);
|
|
2833
2366
|
const stepBudget = Math.min(this.config.maxPatchesPerTurn, limitedIssues.length);
|
|
2834
2367
|
return {
|
|
2835
2368
|
structureSummary: result.structureSummary,
|
|
@@ -2885,91 +2418,6 @@ var FormHarness = class {
|
|
|
2885
2418
|
return sha256(serializeForm(this.form));
|
|
2886
2419
|
}
|
|
2887
2420
|
/**
|
|
2888
|
-
* Filter issues based on maxFieldsPerTurn and maxGroupsPerTurn limits.
|
|
2889
|
-
*
|
|
2890
|
-
* Issues are processed in priority order. An issue is included if:
|
|
2891
|
-
* - Adding it doesn't exceed the field limit (for field/option scoped issues)
|
|
2892
|
-
* - Adding it doesn't exceed the group limit
|
|
2893
|
-
*
|
|
2894
|
-
* Form-level issues are always included.
|
|
2895
|
-
*/
|
|
2896
|
-
filterIssuesByScope(issues) {
|
|
2897
|
-
const maxFields = this.config.maxFieldsPerTurn;
|
|
2898
|
-
const maxGroups = this.config.maxGroupsPerTurn;
|
|
2899
|
-
if (maxFields === void 0 && maxGroups === void 0) return issues;
|
|
2900
|
-
const result = [];
|
|
2901
|
-
const seenFields = /* @__PURE__ */ new Set();
|
|
2902
|
-
const seenGroups = /* @__PURE__ */ new Set();
|
|
2903
|
-
for (const issue of issues) {
|
|
2904
|
-
if (issue.scope === "form") {
|
|
2905
|
-
result.push(issue);
|
|
2906
|
-
continue;
|
|
2907
|
-
}
|
|
2908
|
-
const fieldId = this.getFieldIdFromRef(issue.ref, issue.scope);
|
|
2909
|
-
const groupId = fieldId ? this.getGroupForField(fieldId) : void 0;
|
|
2910
|
-
if (maxFields !== void 0 && fieldId) {
|
|
2911
|
-
if (!seenFields.has(fieldId) && seenFields.size >= maxFields) continue;
|
|
2912
|
-
}
|
|
2913
|
-
if (maxGroups !== void 0 && groupId) {
|
|
2914
|
-
if (!seenGroups.has(groupId) && seenGroups.size >= maxGroups) continue;
|
|
2915
|
-
}
|
|
2916
|
-
result.push(issue);
|
|
2917
|
-
if (fieldId) seenFields.add(fieldId);
|
|
2918
|
-
if (groupId) seenGroups.add(groupId);
|
|
2919
|
-
}
|
|
2920
|
-
return result;
|
|
2921
|
-
}
|
|
2922
|
-
/**
|
|
2923
|
-
* Filter issues by order level.
|
|
2924
|
-
*
|
|
2925
|
-
* Only includes issues for fields at the current (lowest incomplete) order level.
|
|
2926
|
-
* Fields at higher order levels are deferred until all lower-order fields are complete.
|
|
2927
|
-
* If no order attributes are used, all issues pass through (all at order 0).
|
|
2928
|
-
*/
|
|
2929
|
-
filterIssuesByOrder(issues) {
|
|
2930
|
-
const fieldOrderMap = /* @__PURE__ */ new Map();
|
|
2931
|
-
for (const group of this.form.schema.groups) {
|
|
2932
|
-
const groupOrder = group.order ?? 0;
|
|
2933
|
-
for (const field of group.children) fieldOrderMap.set(field.id, field.order ?? groupOrder);
|
|
2934
|
-
}
|
|
2935
|
-
const openOrderLevels = /* @__PURE__ */ new Set();
|
|
2936
|
-
for (const issue of issues) {
|
|
2937
|
-
const fieldId = this.getFieldIdFromRef(issue.ref, issue.scope);
|
|
2938
|
-
if (fieldId) {
|
|
2939
|
-
const order = fieldOrderMap.get(fieldId) ?? 0;
|
|
2940
|
-
openOrderLevels.add(order);
|
|
2941
|
-
} else if (issue.scope === "form") openOrderLevels.add(0);
|
|
2942
|
-
}
|
|
2943
|
-
if (openOrderLevels.size <= 1) return issues;
|
|
2944
|
-
const currentOrder = Math.min(...openOrderLevels);
|
|
2945
|
-
return issues.filter((issue) => {
|
|
2946
|
-
if (issue.scope === "form") return true;
|
|
2947
|
-
const fieldId = this.getFieldIdFromRef(issue.ref, issue.scope);
|
|
2948
|
-
if (!fieldId) return true;
|
|
2949
|
-
return (fieldOrderMap.get(fieldId) ?? 0) === currentOrder;
|
|
2950
|
-
});
|
|
2951
|
-
}
|
|
2952
|
-
/**
|
|
2953
|
-
* Extract field ID from an issue ref.
|
|
2954
|
-
*/
|
|
2955
|
-
getFieldIdFromRef(ref, scope) {
|
|
2956
|
-
if (scope === "field") return ref;
|
|
2957
|
-
if (scope === "option") {
|
|
2958
|
-
const dotIndex = ref.indexOf(".");
|
|
2959
|
-
return dotIndex > 0 ? ref.slice(0, dotIndex) : void 0;
|
|
2960
|
-
}
|
|
2961
|
-
}
|
|
2962
|
-
/**
|
|
2963
|
-
* Get the parent group ID for a field.
|
|
2964
|
-
*/
|
|
2965
|
-
getGroupForField(fieldId) {
|
|
2966
|
-
const entry = this.form.idIndex.get(fieldId);
|
|
2967
|
-
if (!entry) return;
|
|
2968
|
-
if (entry.parentId) {
|
|
2969
|
-
if (this.form.idIndex.get(entry.parentId)?.nodeType === "group") return entry.parentId;
|
|
2970
|
-
}
|
|
2971
|
-
}
|
|
2972
|
-
/**
|
|
2973
2421
|
* Clear all fields that match the target roles.
|
|
2974
2422
|
* Used when fillMode='overwrite' to re-fill already-filled fields.
|
|
2975
2423
|
*/
|
|
@@ -3310,6 +2758,7 @@ var FillRecordCollector = class {
|
|
|
3310
2758
|
});
|
|
3311
2759
|
}
|
|
3312
2760
|
onTurnComplete(progress) {
|
|
2761
|
+
const warnings = progress.coercionWarnings;
|
|
3313
2762
|
this.events.push({
|
|
3314
2763
|
type: "turn_complete",
|
|
3315
2764
|
timestamp: currentTime(),
|
|
@@ -3317,6 +2766,7 @@ var FillRecordCollector = class {
|
|
|
3317
2766
|
patchesApplied: progress.patchesApplied,
|
|
3318
2767
|
patchesRejected: progress.rejectedPatches?.length ?? 0,
|
|
3319
2768
|
issuesAddressed: progress.issuesShown,
|
|
2769
|
+
...warnings && warnings.length > 0 && { coercionWarnings: warnings },
|
|
3320
2770
|
executionId: progress.executionId
|
|
3321
2771
|
});
|
|
3322
2772
|
}
|
|
@@ -3524,7 +2974,8 @@ var FillRecordCollector = class {
|
|
|
3524
2974
|
patchesApplied: completeEvent.patchesApplied,
|
|
3525
2975
|
patchesRejected: completeEvent.patchesRejected,
|
|
3526
2976
|
tokens,
|
|
3527
|
-
toolCalls
|
|
2977
|
+
toolCalls,
|
|
2978
|
+
...completeEvent.coercionWarnings && completeEvent.coercionWarnings.length > 0 && { coercionWarnings: completeEvent.coercionWarnings }
|
|
3528
2979
|
};
|
|
3529
2980
|
turns.set(key, entry);
|
|
3530
2981
|
}
|
|
@@ -3634,6 +3085,7 @@ var FillRecordCollector = class {
|
|
|
3634
3085
|
failedCalls,
|
|
3635
3086
|
successRate: totalCalls > 0 ? successfulCalls / totalCalls * 100 : 0,
|
|
3636
3087
|
totalDurationMs,
|
|
3088
|
+
avgDurationMs: totalCalls > 0 ? totalDurationMs / totalCalls : 0,
|
|
3637
3089
|
byTool
|
|
3638
3090
|
};
|
|
3639
3091
|
}
|
|
@@ -3655,7 +3107,7 @@ var FillRecordCollector = class {
|
|
|
3655
3107
|
return Math.round(sorted[lower] * (1 - weight) + sorted[upper] * weight);
|
|
3656
3108
|
}
|
|
3657
3109
|
calculateTimingBreakdown(totalMs, llmTimeMs, toolTimeMs) {
|
|
3658
|
-
const overheadMs = Math.max(0, totalMs - llmTimeMs
|
|
3110
|
+
const overheadMs = Math.max(0, totalMs - llmTimeMs);
|
|
3659
3111
|
return {
|
|
3660
3112
|
totalMs,
|
|
3661
3113
|
llmTimeMs,
|
|
@@ -3680,7 +3132,8 @@ var FillRecordCollector = class {
|
|
|
3680
3132
|
ms: overheadMs,
|
|
3681
3133
|
percentage: totalMs > 0 ? overheadMs / totalMs * 100 : 0
|
|
3682
3134
|
}
|
|
3683
|
-
]
|
|
3135
|
+
],
|
|
3136
|
+
llmParallelism: totalMs > 0 ? llmTimeMs / totalMs : 0
|
|
3684
3137
|
};
|
|
3685
3138
|
}
|
|
3686
3139
|
buildExecutionMetadata(timeline) {
|
|
@@ -8925,186 +8378,6 @@ function createAnthropic(options = {}) {
|
|
|
8925
8378
|
}
|
|
8926
8379
|
var anthropic = createAnthropic();
|
|
8927
8380
|
|
|
8928
|
-
//#endregion
|
|
8929
|
-
//#region src/harness/prompts.ts
|
|
8930
|
-
/**
|
|
8931
|
-
* Agent Prompts - Centralized prompt definitions for the live agent.
|
|
8932
|
-
*
|
|
8933
|
-
* All hardcoded prompts are defined here for easy review, modification,
|
|
8934
|
-
* and future configurability. This file serves as the single source of
|
|
8935
|
-
* truth for agent behavior instructions.
|
|
8936
|
-
*/
|
|
8937
|
-
/**
|
|
8938
|
-
* Default system prompt for the live agent.
|
|
8939
|
-
*
|
|
8940
|
-
* This is the base instruction set that defines the agent's core behavior
|
|
8941
|
-
* for form filling. It emphasizes accuracy over completeness and prohibits
|
|
8942
|
-
* fabrication of data.
|
|
8943
|
-
*/
|
|
8944
|
-
const DEFAULT_SYSTEM_PROMPT = `# Form Instructions
|
|
8945
|
-
|
|
8946
|
-
Research and fill the form fields using all available tools. Focus on accuracy over completeness.
|
|
8947
|
-
|
|
8948
|
-
## Guidelines
|
|
8949
|
-
1. Address required fields first (severity: "required"), then optional fields (severity: "recommended")
|
|
8950
|
-
2. NEVER fabricate or guess information - only use data you can verify
|
|
8951
|
-
3. If you cannot find verifiable information, use skip_field with a reason
|
|
8952
|
-
|
|
8953
|
-
## Patch Format Examples
|
|
8954
|
-
|
|
8955
|
-
Use the fill_form tool with patches in these formats:
|
|
8956
|
-
|
|
8957
|
-
| Type | Example |
|
|
8958
|
-
|------|---------|
|
|
8959
|
-
| string | \`{ op: "set_string", fieldId: "name", value: "Acme Corp" }\` |
|
|
8960
|
-
| number | \`{ op: "set_number", fieldId: "age", value: 32 }\` |
|
|
8961
|
-
| string_list | \`{ op: "set_string_list", fieldId: "tags", value: ["ai", "ml"] }\` |
|
|
8962
|
-
| url | \`{ op: "set_url", fieldId: "website", value: "https://example.com" }\` |
|
|
8963
|
-
| url_list | \`{ op: "set_url_list", fieldId: "sources", value: ["https://a.com", "https://b.com"] }\` |
|
|
8964
|
-
| date | \`{ op: "set_date", fieldId: "event_date", value: "2024-06-15" }\` |
|
|
8965
|
-
| year | \`{ op: "set_year", fieldId: "founded", value: 2024 }\` |
|
|
8966
|
-
| single_select | \`{ op: "set_single_select", fieldId: "priority", value: "high" }\` |
|
|
8967
|
-
| multi_select | \`{ op: "set_multi_select", fieldId: "categories", value: ["frontend", "backend"] }\` |
|
|
8968
|
-
| checkboxes | \`{ op: "set_checkboxes", fieldId: "tasks", value: { "task1": "done", "task2": "todo" } }\` |
|
|
8969
|
-
| table | \`{ op: "set_table", fieldId: "team", value: [{ "name": "Alice", "role": "Engineer" }] }\` |
|
|
8970
|
-
|
|
8971
|
-
## Important: checkboxes vs multi_select
|
|
8972
|
-
|
|
8973
|
-
These two types look similar but have DIFFERENT value formats:
|
|
8974
|
-
|
|
8975
|
-
- **multi_select** → array of option IDs: \`["opt1", "opt2"]\`
|
|
8976
|
-
- **checkboxes** → object mapping IDs to states: \`{ "opt1": "done", "opt2": "todo" }\`
|
|
8977
|
-
|
|
8978
|
-
**Checkbox states by mode:**
|
|
8979
|
-
- Mode "simple": \`"done"\` or \`"todo"\`
|
|
8980
|
-
- Mode "multi": \`"done"\`, \`"todo"\`, \`"incomplete"\`, \`"active"\`, or \`"na"\`
|
|
8981
|
-
- Mode "explicit": \`"yes"\` or \`"no"\` (if unknown, use abort_field)
|
|
8982
|
-
|
|
8983
|
-
**WRONG:** \`{ op: "set_checkboxes", value: ["task1", "task2"] }\`
|
|
8984
|
-
**RIGHT:** \`{ op: "set_checkboxes", value: { "task1": "done", "task2": "done" } }\`
|
|
8985
|
-
|
|
8986
|
-
## Skipping Fields
|
|
8987
|
-
|
|
8988
|
-
If you cannot find verifiable information:
|
|
8989
|
-
\`{ op: "skip_field", fieldId: "...", reason: "Could not find verified data" }\`
|
|
8990
|
-
`;
|
|
8991
|
-
/**
|
|
8992
|
-
* Web search instructions appended when web search tools are available.
|
|
8993
|
-
*
|
|
8994
|
-
* These instructions enforce that the agent must verify all information
|
|
8995
|
-
* through web search before filling fields.
|
|
8996
|
-
*/
|
|
8997
|
-
const WEB_SEARCH_INSTRUCTIONS = `# Web Search
|
|
8998
|
-
You have access to web search tools. You MUST use them to verify ALL information before filling fields.
|
|
8999
|
-
|
|
9000
|
-
Guidelines:
|
|
9001
|
-
1. Search for official sources (company websites, Crunchbase, LinkedIn, press releases)
|
|
9002
|
-
2. Cross-reference information across multiple sources when possible
|
|
9003
|
-
3. Only fill fields with data you found and verified through search
|
|
9004
|
-
4. If a search returns no results or uncertain information, use skip_field with a reason explaining what you searched for
|
|
9005
|
-
5. NEVER fill fields with guessed or assumed information
|
|
9006
|
-
`;
|
|
9007
|
-
/**
|
|
9008
|
-
* Header for the issues section in the context prompt.
|
|
9009
|
-
*/
|
|
9010
|
-
const ISSUES_HEADER = "# Current Form Issues";
|
|
9011
|
-
/**
|
|
9012
|
-
* Template for the issues intro text.
|
|
9013
|
-
* @param issueCount - Actual number of issues shown
|
|
9014
|
-
*/
|
|
9015
|
-
function getIssuesIntro(issueCount) {
|
|
9016
|
-
return `You need to address ${issueCount} issue${issueCount === 1 ? "" : "s"}. Here are the current issues:`;
|
|
9017
|
-
}
|
|
9018
|
-
/**
|
|
9019
|
-
* Patch format examples by field kind.
|
|
9020
|
-
*
|
|
9021
|
-
* This is the single source of truth for patch format documentation.
|
|
9022
|
-
* Used in PATCH_FORMAT_INSTRUCTIONS and rejection feedback hints.
|
|
9023
|
-
*/
|
|
9024
|
-
const PATCH_FORMATS = {
|
|
9025
|
-
string: "{ op: \"set_string\", fieldId: \"...\", value: \"text here\" }",
|
|
9026
|
-
number: "{ op: \"set_number\", fieldId: \"...\", value: 42 }",
|
|
9027
|
-
string_list: "{ op: \"set_string_list\", fieldId: \"...\", value: [\"item1\", \"item2\"] }",
|
|
9028
|
-
single_select: "{ op: \"set_single_select\", fieldId: \"...\", value: \"option_id\" }",
|
|
9029
|
-
multi_select: "{ op: \"set_multi_select\", fieldId: \"...\", value: [\"opt1\", \"opt2\"] }",
|
|
9030
|
-
checkboxes: "{ op: \"set_checkboxes\", fieldId: \"...\", value: { \"opt1\": \"done\", \"opt2\": \"todo\" } }",
|
|
9031
|
-
url: "{ op: \"set_url\", fieldId: \"...\", value: \"https://example.com\" }",
|
|
9032
|
-
url_list: "{ op: \"set_url_list\", fieldId: \"...\", value: [\"https://a.com\", \"https://b.com\"] }",
|
|
9033
|
-
date: "{ op: \"set_date\", fieldId: \"...\", value: \"2024-06-15\" }",
|
|
9034
|
-
year: "{ op: \"set_year\", fieldId: \"...\", value: 2024 }",
|
|
9035
|
-
table: "{ op: \"set_table\", fieldId: \"...\", value: [{ col1: \"val1\", col2: \"val2\" }] }"
|
|
9036
|
-
};
|
|
9037
|
-
/**
|
|
9038
|
-
* Get the correct patch format for a field kind.
|
|
9039
|
-
*
|
|
9040
|
-
* @param fieldKind - The field kind (e.g., "table", "string")
|
|
9041
|
-
* @param options - Optional configuration for the hint
|
|
9042
|
-
* @returns The patch format example string
|
|
9043
|
-
*/
|
|
9044
|
-
function getPatchFormatHint(fieldKind, fieldIdOrOptions, columnIds) {
|
|
9045
|
-
let options = {};
|
|
9046
|
-
if (typeof fieldIdOrOptions === "string") options = {
|
|
9047
|
-
fieldId: fieldIdOrOptions,
|
|
9048
|
-
columnIds
|
|
9049
|
-
};
|
|
9050
|
-
else if (fieldIdOrOptions) options = fieldIdOrOptions;
|
|
9051
|
-
let format = PATCH_FORMATS[fieldKind];
|
|
9052
|
-
if (!format) return `Use the correct set_${fieldKind} operation for this field type.`;
|
|
9053
|
-
if (options.fieldId) format = format.replace("fieldId: \"...\"", `fieldId: "${options.fieldId}"`);
|
|
9054
|
-
if (fieldKind === "checkboxes") {
|
|
9055
|
-
const mode = options.checkboxMode ?? "multi";
|
|
9056
|
-
const optIds = options.optionIds ?? ["opt1", "opt2"];
|
|
9057
|
-
const [state1, state2] = {
|
|
9058
|
-
simple: ["done", "todo"],
|
|
9059
|
-
multi: ["done", "todo"],
|
|
9060
|
-
explicit: ["yes", "no"]
|
|
9061
|
-
}[mode] ?? ["done", "todo"];
|
|
9062
|
-
const valueExample = optIds.length >= 2 ? `{ "${optIds[0]}": "${state1}", "${optIds[1]}": "${state2}" }` : optIds.length === 1 ? `{ "${optIds[0]}": "${state1}" }` : `{ "opt1": "${state1}", "opt2": "${state2}" }`;
|
|
9063
|
-
format = format.replace("{ \"opt1\": \"done\", \"opt2\": \"todo\" }", valueExample);
|
|
9064
|
-
}
|
|
9065
|
-
if (fieldKind === "table" && options.columnIds && options.columnIds.length > 0) {
|
|
9066
|
-
const colExample = options.columnIds.map((id) => `"${id}": "..."`).join(", ");
|
|
9067
|
-
format = format.replace("{ col1: \"val1\", col2: \"val2\" }", `{ ${colExample} }`);
|
|
9068
|
-
}
|
|
9069
|
-
return format;
|
|
9070
|
-
}
|
|
9071
|
-
/**
|
|
9072
|
-
* Instructions section for the context prompt.
|
|
9073
|
-
*
|
|
9074
|
-
* This explains the patch format for each field kind.
|
|
9075
|
-
* Generated from PATCH_FORMATS to ensure consistency.
|
|
9076
|
-
*/
|
|
9077
|
-
const PATCH_FORMAT_INSTRUCTIONS = `# Instructions
|
|
9078
|
-
|
|
9079
|
-
Use the fill_form tool to submit patches for the fields above.
|
|
9080
|
-
Each patch should match the field kind:
|
|
9081
|
-
${Object.entries(PATCH_FORMATS).map(([kind, format]) => `- ${kind}: ${format}`).join("\n")}
|
|
9082
|
-
|
|
9083
|
-
For table fields, use the column IDs shown in the field schema. Each row is an object with column ID keys.
|
|
9084
|
-
|
|
9085
|
-
If you cannot find verifiable information for a field, skip it:
|
|
9086
|
-
- skip: { op: "skip_field", fieldId: "...", reason: "Information not available" }`;
|
|
9087
|
-
/**
|
|
9088
|
-
* Simplified general instructions for use with inline field instructions.
|
|
9089
|
-
*
|
|
9090
|
-
* When inline field instructions are shown after each issue, we only need
|
|
9091
|
-
* general guidance about using the fill_form tool.
|
|
9092
|
-
*/
|
|
9093
|
-
const GENERAL_INSTRUCTIONS = `# General Instructions
|
|
9094
|
-
|
|
9095
|
-
Use the fill_form tool to submit patches for the fields above.
|
|
9096
|
-
For table fields, each row is an object with column ID keys.`;
|
|
9097
|
-
/**
|
|
9098
|
-
* Section headers used when building the composed system prompt.
|
|
9099
|
-
*/
|
|
9100
|
-
const SECTION_HEADERS = {
|
|
9101
|
-
formInstructions: "# Form Instructions",
|
|
9102
|
-
roleInstructions: (role) => `# Instructions for ${role} role`,
|
|
9103
|
-
roleGuidance: "# Role guidance",
|
|
9104
|
-
fieldInstructions: "# Field-specific instructions",
|
|
9105
|
-
additionalContext: "# Additional Context"
|
|
9106
|
-
};
|
|
9107
|
-
|
|
9108
8381
|
//#endregion
|
|
9109
8382
|
//#region src/harness/toolApi.ts
|
|
9110
8383
|
/**
|
|
@@ -9135,6 +8408,7 @@ var LiveAgent = class {
|
|
|
9135
8408
|
callbacks;
|
|
9136
8409
|
executionId;
|
|
9137
8410
|
toolChoice;
|
|
8411
|
+
maxRetries;
|
|
9138
8412
|
constructor(config) {
|
|
9139
8413
|
this.model = config.model;
|
|
9140
8414
|
this.maxStepsPerTurn = config.maxStepsPerTurn ?? DEFAULT_MAX_STEPS_PER_TURN;
|
|
@@ -9145,6 +8419,7 @@ var LiveAgent = class {
|
|
|
9145
8419
|
this.additionalTools = config.additionalTools ?? {};
|
|
9146
8420
|
this.callbacks = config.callbacks;
|
|
9147
8421
|
this.executionId = config.executionId ?? "0-serial";
|
|
8422
|
+
this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
9148
8423
|
this.toolChoice = config.toolChoice ?? "required";
|
|
9149
8424
|
if (this.enableWebSearch) {
|
|
9150
8425
|
if (config.providerTools) this.webSearchTools = config.providerTools;
|
|
@@ -9204,6 +8479,7 @@ var LiveAgent = class {
|
|
|
9204
8479
|
prompt: contextPrompt,
|
|
9205
8480
|
tools,
|
|
9206
8481
|
toolChoice: this.toolChoice,
|
|
8482
|
+
maxRetries: this.maxRetries,
|
|
9207
8483
|
stopWhen: stepCountIs(this.maxStepsPerTurn)
|
|
9208
8484
|
});
|
|
9209
8485
|
} catch (error) {
|
|
@@ -10228,17 +9504,25 @@ function mergeCallbacks(userCallbacks, collector) {
|
|
|
10228
9504
|
} catch (e) {
|
|
10229
9505
|
warnCallbackError("onWebSearch", e);
|
|
10230
9506
|
}
|
|
10231
|
-
}
|
|
9507
|
+
},
|
|
9508
|
+
onError: userCallbacks.onError ? (error, context) => {
|
|
9509
|
+
try {
|
|
9510
|
+
userCallbacks.onError?.(error, context);
|
|
9511
|
+
} catch (e) {
|
|
9512
|
+
warnCallbackError("onError", e);
|
|
9513
|
+
}
|
|
9514
|
+
} : void 0
|
|
10232
9515
|
};
|
|
10233
9516
|
}
|
|
10234
|
-
function buildErrorResult(form, errors, warnings, record) {
|
|
9517
|
+
function buildErrorResult(form, errors, warnings, record, sourceError) {
|
|
10235
9518
|
const values = {};
|
|
10236
9519
|
for (const [fieldId, response] of Object.entries(form.responsesByFieldId)) if (response.state === "answered" && response.value) values[fieldId] = response.value;
|
|
10237
9520
|
const result = {
|
|
10238
9521
|
status: {
|
|
10239
9522
|
ok: false,
|
|
10240
9523
|
reason: "error",
|
|
10241
|
-
message: errors.join("; ")
|
|
9524
|
+
message: errors.join("; "),
|
|
9525
|
+
error: sourceError
|
|
10242
9526
|
},
|
|
10243
9527
|
markdown: serializeForm(form),
|
|
10244
9528
|
values,
|
|
@@ -10317,7 +9601,8 @@ async function fillForm(options) {
|
|
|
10317
9601
|
status: {
|
|
10318
9602
|
ok: false,
|
|
10319
9603
|
reason: "error",
|
|
10320
|
-
message: `Form parse error: ${error instanceof Error ? error.message : String(error)}
|
|
9604
|
+
message: `Form parse error: ${error instanceof Error ? error.message : String(error)}`,
|
|
9605
|
+
error: error instanceof Error ? error : void 0
|
|
10321
9606
|
},
|
|
10322
9607
|
markdown: typeof options.form === "string" ? options.form : "",
|
|
10323
9608
|
values: {},
|
|
@@ -10348,7 +9633,7 @@ async function fillForm(options) {
|
|
|
10348
9633
|
} else model = options.model;
|
|
10349
9634
|
} catch (error) {
|
|
10350
9635
|
const message = error instanceof Error ? error.message : String(error);
|
|
10351
|
-
return buildErrorResult(form, [`Model resolution error: ${message}`], []);
|
|
9636
|
+
return buildErrorResult(form, [`Model resolution error: ${message}`], [], void 0, error instanceof Error ? error : void 0);
|
|
10352
9637
|
}
|
|
10353
9638
|
else if (typeof options.model === "string" && options.model.includes("/")) provider = options.model.split("/")[0];
|
|
10354
9639
|
const modelString = typeof options.model === "string" ? options.model : "custom";
|
|
@@ -10441,15 +9726,22 @@ async function fillForm(options) {
|
|
|
10441
9726
|
response = await agent.fillFormTool(turnIssues, form, maxPatchesPerTurn, previousRejections);
|
|
10442
9727
|
} catch (error) {
|
|
10443
9728
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
9729
|
+
const errorObj = error instanceof Error ? error : void 0;
|
|
10444
9730
|
let record;
|
|
10445
9731
|
if (collector) {
|
|
10446
9732
|
collector.setStatus("failed", errorMessage);
|
|
10447
9733
|
record = collector.getRecord(getProgressCounts(form, targetRoles));
|
|
10448
9734
|
}
|
|
9735
|
+
if (errorObj && mergedCallbacks?.onError) try {
|
|
9736
|
+
mergedCallbacks.onError(errorObj, { turnNumber: turnCount + 1 });
|
|
9737
|
+
} catch (cbError) {
|
|
9738
|
+
warnCallbackError("onError", cbError);
|
|
9739
|
+
}
|
|
10449
9740
|
return buildResult(form, turnCount, totalPatches, {
|
|
10450
9741
|
ok: false,
|
|
10451
9742
|
reason: "error",
|
|
10452
|
-
message: errorMessage
|
|
9743
|
+
message: errorMessage,
|
|
9744
|
+
error: errorObj
|
|
10453
9745
|
}, inputContextWarnings, turnIssues, record);
|
|
10454
9746
|
}
|
|
10455
9747
|
const { patches, stats } = response;
|
|
@@ -10503,6 +9795,7 @@ async function fillForm(options) {
|
|
|
10503
9795
|
issues: turnIssues,
|
|
10504
9796
|
patches,
|
|
10505
9797
|
rejectedPatches: stepResult.rejectedPatches ?? [],
|
|
9798
|
+
coercionWarnings: stepResult.coercionWarnings,
|
|
10506
9799
|
executionId: "0-serial"
|
|
10507
9800
|
});
|
|
10508
9801
|
} catch {}
|
|
@@ -10720,6 +10013,12 @@ async function runMultiTurnForItems(form, agent, items, targetRoles, maxPatchesP
|
|
|
10720
10013
|
response = await agent.fillFormTool(scopedIssues, form, maxPatchesPerTurn, previousRejections);
|
|
10721
10014
|
} catch (error) {
|
|
10722
10015
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
10016
|
+
const errorObj = error instanceof Error ? error : void 0;
|
|
10017
|
+
if (errorObj && mergedCallbacks?.onError) try {
|
|
10018
|
+
mergedCallbacks.onError(errorObj, { turnNumber: startTurn + turnsUsed + 1 });
|
|
10019
|
+
} catch (cbError) {
|
|
10020
|
+
warnCallbackError("onError", cbError);
|
|
10021
|
+
}
|
|
10723
10022
|
return {
|
|
10724
10023
|
patchesApplied,
|
|
10725
10024
|
turnsUsed,
|
|
@@ -10727,14 +10026,17 @@ async function runMultiTurnForItems(form, agent, items, targetRoles, maxPatchesP
|
|
|
10727
10026
|
status: {
|
|
10728
10027
|
ok: false,
|
|
10729
10028
|
reason: "error",
|
|
10730
|
-
message: errorMessage
|
|
10029
|
+
message: errorMessage,
|
|
10030
|
+
error: errorObj
|
|
10731
10031
|
}
|
|
10732
10032
|
};
|
|
10733
10033
|
}
|
|
10034
|
+
let lastCoercionWarnings;
|
|
10734
10035
|
if (response.patches.length > 0) {
|
|
10735
10036
|
const applyResult = applyPatches(form, response.patches);
|
|
10736
10037
|
patchesApplied += applyResult.appliedPatches.length;
|
|
10737
10038
|
previousRejections = applyResult.rejectedPatches;
|
|
10039
|
+
lastCoercionWarnings = applyResult.warnings;
|
|
10738
10040
|
} else previousRejections = void 0;
|
|
10739
10041
|
turnsUsed++;
|
|
10740
10042
|
try {
|
|
@@ -10750,6 +10052,7 @@ async function runMultiTurnForItems(form, agent, items, targetRoles, maxPatchesP
|
|
|
10750
10052
|
issues: scopedIssues,
|
|
10751
10053
|
patches: response.patches,
|
|
10752
10054
|
rejectedPatches: previousRejections ?? [],
|
|
10055
|
+
coercionWarnings: lastCoercionWarnings,
|
|
10753
10056
|
executionId
|
|
10754
10057
|
});
|
|
10755
10058
|
} catch {}
|
|
@@ -10799,12 +10102,24 @@ function formatNumber(n) {
|
|
|
10799
10102
|
* Format milliseconds as a human-readable duration.
|
|
10800
10103
|
*/
|
|
10801
10104
|
function formatDuration(ms) {
|
|
10802
|
-
if (ms < 1e3) return `${ms}ms`;
|
|
10105
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
10803
10106
|
const seconds = ms / 1e3;
|
|
10804
10107
|
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
10805
10108
|
return `${Math.floor(seconds / 60)}m ${(seconds % 60).toFixed(0)}s`;
|
|
10806
10109
|
}
|
|
10807
10110
|
/**
|
|
10111
|
+
* Format a rate value (like s/field or s/turn) with appropriate significant figures.
|
|
10112
|
+
* >= 10s: 1 decimal (e.g., 12.3s/field)
|
|
10113
|
+
* >= 1s: 2 decimals (e.g., 3.45s/field)
|
|
10114
|
+
* < 1s: show as ms (e.g., 450ms/field)
|
|
10115
|
+
*/
|
|
10116
|
+
function formatRate(ms, unit) {
|
|
10117
|
+
const seconds = ms / 1e3;
|
|
10118
|
+
if (seconds >= 10) return `${seconds.toFixed(1)}s/${unit}`;
|
|
10119
|
+
if (seconds >= 1) return `${seconds.toFixed(2)}s/${unit}`;
|
|
10120
|
+
return `${Math.round(ms)}ms/${unit}`;
|
|
10121
|
+
}
|
|
10122
|
+
/**
|
|
10808
10123
|
* Format a percentage.
|
|
10809
10124
|
*/
|
|
10810
10125
|
function formatPercent(value, total) {
|
|
@@ -10831,7 +10146,11 @@ function formatFillRecordSummary(record, options = {}) {
|
|
|
10831
10146
|
const lines = [];
|
|
10832
10147
|
const statusText = record.status === "completed" ? "Fill completed" : "Fill incomplete";
|
|
10833
10148
|
const turnsText = `${record.execution.totalTurns} turn${record.execution.totalTurns !== 1 ? "s" : ""}`;
|
|
10834
|
-
|
|
10149
|
+
const rateParts = [];
|
|
10150
|
+
if (record.execution.totalTurns > 0) rateParts.push(formatRate(record.durationMs / record.execution.totalTurns, "turn"));
|
|
10151
|
+
if (record.formProgress.answeredFields > 0) rateParts.push(formatRate(record.durationMs / record.formProgress.answeredFields, "field"));
|
|
10152
|
+
const ratesText = rateParts.length > 0 ? `, ${rateParts.join(", ")}` : "";
|
|
10153
|
+
let statusLine = `${statusText} in ${formatDuration(record.durationMs)} (${turnsText}${ratesText})`;
|
|
10835
10154
|
if (record.status !== "completed" && record.statusDetail) statusLine += ` - ${record.statusDetail}`;
|
|
10836
10155
|
lines.push(statusLine);
|
|
10837
10156
|
const timelineEmpty = record.timeline.length === 0;
|
|
@@ -10847,6 +10166,7 @@ function formatFillRecordSummary(record, options = {}) {
|
|
|
10847
10166
|
toolLine += ` (${formatNumber(toolSummary.successfulCalls)} succeeded`;
|
|
10848
10167
|
if (toolSummary.failedCalls > 0) toolLine += `, ${formatNumber(toolSummary.failedCalls)} failed`;
|
|
10849
10168
|
toolLine += ")";
|
|
10169
|
+
toolLine += `, avg ${formatDuration(toolSummary.avgDurationMs)} each`;
|
|
10850
10170
|
}
|
|
10851
10171
|
lines.push(toolLine);
|
|
10852
10172
|
if (verbose && toolSummary.byTool.length > 0) for (const tool of toolSummary.byTool) {
|
|
@@ -10855,14 +10175,22 @@ function formatFillRecordSummary(record, options = {}) {
|
|
|
10855
10175
|
if (tool.timing.p95Ms !== void 0 && tool.callCount > 1) toolDetail += `, p95 ${formatDuration(tool.timing.p95Ms)}`;
|
|
10856
10176
|
lines.push(toolDetail);
|
|
10857
10177
|
}
|
|
10178
|
+
const { timingBreakdown } = record;
|
|
10179
|
+
const llmPct = Math.round(timingBreakdown.breakdown.find((b) => b.category === "llm")?.percentage ?? 0);
|
|
10180
|
+
const toolPct = Math.round(timingBreakdown.breakdown.find((b) => b.category === "tools")?.percentage ?? 0);
|
|
10181
|
+
const overheadPct = Math.round(timingBreakdown.breakdown.find((b) => b.category === "overhead")?.percentage ?? 0);
|
|
10858
10182
|
if (verbose) {
|
|
10859
|
-
lines.push("");
|
|
10860
|
-
const { timingBreakdown } = record;
|
|
10861
|
-
const llmPct = timingBreakdown.breakdown.find((b) => b.category === "llm")?.percentage ?? 0;
|
|
10862
|
-
const toolPct = timingBreakdown.breakdown.find((b) => b.category === "tools")?.percentage ?? 0;
|
|
10863
|
-
const overheadPct = timingBreakdown.breakdown.find((b) => b.category === "overhead")?.percentage ?? 0;
|
|
10864
10183
|
const timingLine = `Timing: ${llmPct}% LLM (${formatDuration(timingBreakdown.llmTimeMs)}) | ${toolPct}% tools (${formatDuration(timingBreakdown.toolTimeMs)}) | ${overheadPct}% overhead (${formatDuration(timingBreakdown.overheadMs)})`;
|
|
10865
10184
|
lines.push(timingLine);
|
|
10185
|
+
const ep = timingBreakdown.llmParallelism;
|
|
10186
|
+
if (record.execution.parallelEnabled) {
|
|
10187
|
+
const threadCount = record.execution.executionThreads.length;
|
|
10188
|
+
const orderCount = record.execution.orderLevels.length;
|
|
10189
|
+
lines.push(` Effective parallelism: ${ep.toFixed(1)}x (${threadCount} threads, ${orderCount} order level${orderCount !== 1 ? "s" : ""})`);
|
|
10190
|
+
} else if (ep < .8) lines.push(` Effective parallelism: ${ep.toFixed(1)}x`);
|
|
10191
|
+
} else {
|
|
10192
|
+
const timingLine = `Timing: ${llmPct}% LLM | ${toolPct}% tools | ${overheadPct}% overhead`;
|
|
10193
|
+
lines.push(timingLine);
|
|
10866
10194
|
}
|
|
10867
10195
|
lines.push("");
|
|
10868
10196
|
const { formProgress } = record;
|
|
@@ -10943,7 +10271,8 @@ const TimelineEntrySchema = z.object({
|
|
|
10943
10271
|
input: z.number().int().nonnegative(),
|
|
10944
10272
|
output: z.number().int().nonnegative()
|
|
10945
10273
|
}),
|
|
10946
|
-
toolCalls: z.array(ToolCallRecordSchema)
|
|
10274
|
+
toolCalls: z.array(ToolCallRecordSchema),
|
|
10275
|
+
coercionWarnings: z.array(PatchWarningSchema).optional()
|
|
10947
10276
|
});
|
|
10948
10277
|
/**
|
|
10949
10278
|
* Breakdown item for visualization.
|
|
@@ -10966,7 +10295,8 @@ const TimingBreakdownSchema = z.object({
|
|
|
10966
10295
|
llmTimeMs: z.number().int().nonnegative(),
|
|
10967
10296
|
toolTimeMs: z.number().int().nonnegative(),
|
|
10968
10297
|
overheadMs: z.number().int().nonnegative(),
|
|
10969
|
-
breakdown: z.array(TimingBreakdownItemSchema)
|
|
10298
|
+
breakdown: z.array(TimingBreakdownItemSchema),
|
|
10299
|
+
llmParallelism: z.number().nonnegative()
|
|
10970
10300
|
});
|
|
10971
10301
|
/**
|
|
10972
10302
|
* Aggregated tool usage statistics.
|
|
@@ -10977,6 +10307,7 @@ const ToolSummarySchema = z.object({
|
|
|
10977
10307
|
failedCalls: z.number().int().nonnegative(),
|
|
10978
10308
|
successRate: z.number().nonnegative(),
|
|
10979
10309
|
totalDurationMs: z.number().int().nonnegative(),
|
|
10310
|
+
avgDurationMs: z.number().nonnegative(),
|
|
10980
10311
|
byTool: z.array(ToolStatsSchema)
|
|
10981
10312
|
});
|
|
10982
10313
|
/**
|
|
@@ -11045,6 +10376,14 @@ const FillRecordSchema = z.object({
|
|
|
11045
10376
|
* - toolSummary: call counts and success rates (without timing)
|
|
11046
10377
|
* - execution: turn counts, parallel settings (deterministic)
|
|
11047
10378
|
*/
|
|
10379
|
+
/**
|
|
10380
|
+
* Check if a fill record represents an empty session with no actual work.
|
|
10381
|
+
* Returns true if the timeline has zero entries (no turns were executed).
|
|
10382
|
+
* Used to skip writing .fill.json when no form-filling work was done.
|
|
10383
|
+
*/
|
|
10384
|
+
function isEmptyFillRecord(record) {
|
|
10385
|
+
return record.timeline.length === 0;
|
|
10386
|
+
}
|
|
11048
10387
|
function stripUnstableFillRecordFields(record) {
|
|
11049
10388
|
const stableByTool = record.toolSummary.byTool.map((toolStats) => {
|
|
11050
10389
|
const { timing, ...rest } = toolStats;
|
|
@@ -11188,8 +10527,8 @@ function validateResearchForm(form) {
|
|
|
11188
10527
|
//#endregion
|
|
11189
10528
|
//#region src/index.ts
|
|
11190
10529
|
/** Markform version (injected at build time). */
|
|
11191
|
-
const VERSION = "0.1.
|
|
10530
|
+
const VERSION = "0.1.25";
|
|
11192
10531
|
|
|
11193
10532
|
//#endregion
|
|
11194
|
-
export {
|
|
11195
|
-
//# sourceMappingURL=src-
|
|
10533
|
+
export { MockAgent as A, fieldToJsonSchema as B, getProviderInfo as C, createLiveAgent as D, buildMockWireFormat as E, isCellRef as F, injectHeaderIds as G, parseForm as H, isFieldRef as I, parseCellValue as J, findAllHeadings as K, isQualifiedRef as L, FormHarness as M, createHarness as N, FillRecordCollector as O, getFieldId as P, parseScopeRef as R, BUILT_IN_PROVIDERS as S, resolveModel as T, findAllCheckboxes as U, formToJsonSchema as V, injectCheckboxIds as W, parseRawTable as X, parseMarkdownTable as Y, resolveHarnessConfig as _, ExecutionMetadataSchema as a, createParallelHarness as b, TimelineEntrySchema as c, ToolCallRecordSchema as d, ToolStatsSchema as f, formatFillRecordSummary as g, stripUnstableFillRecordFields as h, runResearch as i, createMockAgent as j, computeExecutionPlan as k, TimingBreakdownItemSchema as l, isEmptyFillRecord as m, isResearchForm as n, FillRecordSchema as o, ToolSummarySchema as p, findEnclosingHeadings as q, validateResearchForm as r, FillRecordStatusSchema as s, VERSION as t, TimingBreakdownSchema as u, fillForm as v, getProviderNames as w, scopeIssuesForItem as x, ParallelHarness as y, serializeScopeRef as z };
|
|
10534
|
+
//# sourceMappingURL=src-DrXmaOWl.mjs.map
|