markform 0.1.18 → 0.1.20
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 +27 -2
- package/dist/ai-sdk.d.mts +1 -2
- package/dist/ai-sdk.mjs +2 -2
- package/dist/ai-sdk.mjs.map +1 -1
- package/dist/{apply-BYgtU64w.mjs → apply-DIvm1b1s.mjs} +430 -31
- package/dist/apply-DIvm1b1s.mjs.map +1 -0
- package/dist/bin.mjs +20 -2
- package/dist/bin.mjs.map +1 -1
- package/dist/{cli-D9w0Bp4J.mjs → cli-FFMoEhFS.mjs} +1039 -89
- package/dist/cli-FFMoEhFS.mjs.map +1 -0
- package/dist/cli.mjs +1 -1
- package/dist/{coreTypes-SDB3KRRJ.mjs → coreTypes-CPKXf2dc.mjs} +1 -1
- package/dist/{coreTypes-SDB3KRRJ.mjs.map → coreTypes-CPKXf2dc.mjs.map} +1 -1
- package/dist/{coreTypes-BMEs8h_2.d.mts → coreTypes-CkxML8g2.d.mts} +4 -9
- package/dist/index.d.mts +515 -22
- package/dist/index.mjs +5 -5
- package/dist/{session-Ci4B0Pna.mjs → session-CK0x28RO.mjs} +2 -2
- package/dist/session-CK0x28RO.mjs.map +1 -0
- package/dist/{session-CW9AQw6i.mjs → session-ZHBi3LVQ.mjs} +1 -1
- package/dist/{shared-fUKfJ1UA.mjs → shared-BTR35aMz.mjs} +1 -1
- package/dist/{shared-CCq4haEV.mjs → shared-DwdyWmvE.mjs} +1 -3
- package/dist/shared-DwdyWmvE.mjs.map +1 -0
- package/dist/{src-DDxi-2ne.mjs → src-wR7GoftB.mjs} +1707 -645
- package/dist/src-wR7GoftB.mjs.map +1 -0
- package/docs/markform-apis.md +81 -0
- package/docs/markform-reference.md +15 -1
- package/package.json +17 -15
- package/dist/apply-BYgtU64w.mjs.map +0 -1
- package/dist/cli-D9w0Bp4J.mjs.map +0 -1
- package/dist/session-Ci4B0Pna.mjs.map +0 -1
- package/dist/shared-CCq4haEV.mjs.map +0 -1
- package/dist/src-DDxi-2ne.mjs.map +0 -1
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
|
|
2
|
-
import { L as PatchSchema } from "./coreTypes-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { n as serializeSession } from "./session-
|
|
6
|
-
import { _ as writeFile, a as formatPath, c as logError, d as logTiming, f as logVerbose, g as stripHtmlComments, h as shouldUseColors, i as formatOutput, l as logInfo, m as readFile$1, n as createSpinner, o as getCommandContext, p as logWarn, r as ensureFormsDir, s as logDryRun, t as OUTPUT_FORMATS, u as logSuccess } from "./shared-
|
|
2
|
+
import { L as PatchSchema } from "./coreTypes-CPKXf2dc.mjs";
|
|
3
|
+
import { $ as SUGGESTED_LLMS, B as DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN, F as DEFAULT_MAX_PATCHES_PER_TURN, G as REPORT_EXTENSION, J as deriveFillRecordPath, K as USER_ROLE, L as DEFAULT_MAX_TURNS, M as DEFAULT_FORMS_DIR, N as DEFAULT_MAX_ISSUES_PER_TURN, Q as parseRolesFlag, R as DEFAULT_PORT, V as DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN, W as MAX_FORMS_IN_MENU, X as deriveSchemaPath, Y as deriveReportPath, Z as detectFileType, c as computeProgressSummary, d as serializeForm, et as WEB_SEARCH_CONFIG, f as serializeRawMarkdown, h as friendlyUrlAbbrev, i as inspect, it as parseModelIdForDisplay, j as AGENT_ROLE, l as computeStructureSummary, m as formatBareUrlsAsHtmlLinks, n as getAllFields, p as serializeReport, q as deriveExportPath, rt as hasWebSearchSupport, t as applyPatches, tt as formatSuggestedLlms, v as validateSyntaxConsistency } from "./apply-DIvm1b1s.mjs";
|
|
4
|
+
import { C as resolveModel, D as computeExecutionPlan, E as FillRecordCollector, H as formToJsonSchema, S as getProviderNames, T as createLiveAgent, U as parseForm, _ as fillForm, g as resolveHarnessConfig, h as formatFillRecordSummary, i as runResearch, j as createHarness, k as createMockAgent, m as stripUnstableFillRecordFields, n as isResearchForm, t as VERSION, w as buildMockWireFormat, x as getProviderInfo } from "./src-wR7GoftB.mjs";
|
|
5
|
+
import { n as serializeSession } from "./session-CK0x28RO.mjs";
|
|
6
|
+
import { _ as writeFile, a as formatPath, c as logError, d as logTiming, f as logVerbose, g as stripHtmlComments, h as shouldUseColors, i as formatOutput, l as logInfo, m as readFile$1, n as createSpinner, o as getCommandContext, p as logWarn, r as ensureFormsDir, s as logDryRun, t as OUTPUT_FORMATS, u as logSuccess } from "./shared-DwdyWmvE.mjs";
|
|
7
7
|
import Markdoc from "@markdoc/markdoc";
|
|
8
8
|
import YAML from "yaml";
|
|
9
9
|
import { Command } from "commander";
|
|
10
10
|
import pc from "picocolors";
|
|
11
11
|
import { exec, execSync, spawn } from "node:child_process";
|
|
12
12
|
import { basename, dirname, join, resolve } from "node:path";
|
|
13
|
-
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
13
|
+
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { readFile } from "node:fs/promises";
|
|
16
16
|
import * as p from "@clack/prompts";
|
|
@@ -253,12 +253,12 @@ function registerApplyCommand(program) {
|
|
|
253
253
|
const patches = parsedJson;
|
|
254
254
|
const validatedPatches = [];
|
|
255
255
|
for (let i = 0; i < patches.length; i++) {
|
|
256
|
-
const result
|
|
257
|
-
if (!result
|
|
258
|
-
logError(`Invalid patch at index ${i}: ${result
|
|
256
|
+
const result = PatchSchema.safeParse(patches[i]);
|
|
257
|
+
if (!result.success) {
|
|
258
|
+
logError(`Invalid patch at index ${i}: ${result.error.issues[0]?.message ?? "Unknown error"}`);
|
|
259
259
|
process.exit(1);
|
|
260
260
|
}
|
|
261
|
-
validatedPatches.push(result
|
|
261
|
+
validatedPatches.push(result.data);
|
|
262
262
|
}
|
|
263
263
|
if (ctx.dryRun) {
|
|
264
264
|
logDryRun(`Would apply ${validatedPatches.length} patches to ${file}`, { patches: validatedPatches });
|
|
@@ -418,7 +418,7 @@ async function displayWithPager(content, title) {
|
|
|
418
418
|
return;
|
|
419
419
|
}
|
|
420
420
|
const header = `${pc.bgCyan(pc.black(` ${title} `))}`;
|
|
421
|
-
return new Promise((resolve
|
|
421
|
+
return new Promise((resolve) => {
|
|
422
422
|
const pager = spawn("less", [
|
|
423
423
|
"-R",
|
|
424
424
|
"-S",
|
|
@@ -435,10 +435,10 @@ async function displayWithPager(content, title) {
|
|
|
435
435
|
console.log("");
|
|
436
436
|
console.log(content);
|
|
437
437
|
console.log("");
|
|
438
|
-
resolve
|
|
438
|
+
resolve();
|
|
439
439
|
});
|
|
440
440
|
pager.on("close", () => {
|
|
441
|
-
resolve
|
|
441
|
+
resolve();
|
|
442
442
|
});
|
|
443
443
|
pager.stdin.write(header + "\n\n");
|
|
444
444
|
pager.stdin.write(content);
|
|
@@ -1353,13 +1353,13 @@ function parseVersionedPath(filePath) {
|
|
|
1353
1353
|
function generateVersionedPath(filePath) {
|
|
1354
1354
|
const parsed = parseVersionedPath(filePath);
|
|
1355
1355
|
if (!parsed) {
|
|
1356
|
-
let candidate
|
|
1357
|
-
let version
|
|
1358
|
-
while (existsSync(candidate
|
|
1359
|
-
version
|
|
1360
|
-
candidate
|
|
1356
|
+
let candidate = `${filePath}-filled1`;
|
|
1357
|
+
let version = 1;
|
|
1358
|
+
while (existsSync(candidate)) {
|
|
1359
|
+
version++;
|
|
1360
|
+
candidate = `${filePath}-filled${version}`;
|
|
1361
1361
|
}
|
|
1362
|
-
return candidate
|
|
1362
|
+
return candidate;
|
|
1363
1363
|
}
|
|
1364
1364
|
let version = parsed.version !== null ? parsed.version + 1 : 1;
|
|
1365
1365
|
let candidate = `${parsed.base}-filled${version}${parsed.extension}`;
|
|
@@ -1469,10 +1469,10 @@ async function promptForString(ctx) {
|
|
|
1469
1469
|
placeholder: placeholderText,
|
|
1470
1470
|
initialValue: currentVal ?? "",
|
|
1471
1471
|
validate: (value) => {
|
|
1472
|
-
if (field.required && !value
|
|
1473
|
-
if (field.minLength && value
|
|
1474
|
-
if (field.maxLength && value
|
|
1475
|
-
if (field.pattern && !new RegExp(field.pattern).test(value)) return `Must match pattern: ${field.pattern}`;
|
|
1472
|
+
if (field.required && !value?.trim()) return "This field is required";
|
|
1473
|
+
if (field.minLength && (value?.length ?? 0) < field.minLength) return `Minimum ${field.minLength} characters required`;
|
|
1474
|
+
if (field.maxLength && (value?.length ?? 0) > field.maxLength) return `Maximum ${field.maxLength} characters allowed`;
|
|
1475
|
+
if (field.pattern && value && !new RegExp(field.pattern).test(value)) return `Must match pattern: ${field.pattern}`;
|
|
1476
1476
|
}
|
|
1477
1477
|
});
|
|
1478
1478
|
if (p.isCancel(result)) return null;
|
|
@@ -1480,7 +1480,7 @@ async function promptForString(ctx) {
|
|
|
1480
1480
|
return {
|
|
1481
1481
|
op: "set_string",
|
|
1482
1482
|
fieldId: field.id,
|
|
1483
|
-
value: result
|
|
1483
|
+
value: result ?? null
|
|
1484
1484
|
};
|
|
1485
1485
|
}
|
|
1486
1486
|
/**
|
|
@@ -1495,8 +1495,8 @@ async function promptForNumber(ctx) {
|
|
|
1495
1495
|
placeholder: placeholderText,
|
|
1496
1496
|
initialValue: currentVal !== null ? String(currentVal) : "",
|
|
1497
1497
|
validate: (value) => {
|
|
1498
|
-
if (field.required && !value
|
|
1499
|
-
if (!value
|
|
1498
|
+
if (field.required && !value?.trim()) return "This field is required";
|
|
1499
|
+
if (!value?.trim()) return;
|
|
1500
1500
|
const num = Number(value);
|
|
1501
1501
|
if (isNaN(num)) return "Please enter a valid number";
|
|
1502
1502
|
if (field.integer && !Number.isInteger(num)) return "Please enter a whole number";
|
|
@@ -1527,14 +1527,14 @@ async function promptForStringList(ctx) {
|
|
|
1527
1527
|
placeholder: hint,
|
|
1528
1528
|
initialValue: currentItems.join("\n"),
|
|
1529
1529
|
validate: (value) => {
|
|
1530
|
-
const items
|
|
1531
|
-
if (field.required && items
|
|
1532
|
-
if (field.minItems && items
|
|
1533
|
-
if (field.maxItems && items
|
|
1530
|
+
const items = (value ?? "").split("\n").map((s) => s.trim()).filter(Boolean);
|
|
1531
|
+
if (field.required && items.length === 0) return "At least one item is required";
|
|
1532
|
+
if (field.minItems && items.length < field.minItems) return `Minimum ${field.minItems} items required`;
|
|
1533
|
+
if (field.maxItems && items.length > field.maxItems) return `Maximum ${field.maxItems} items allowed`;
|
|
1534
1534
|
}
|
|
1535
1535
|
});
|
|
1536
1536
|
if (p.isCancel(result)) return null;
|
|
1537
|
-
const items = result.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
1537
|
+
const items = (result ?? "").split("\n").map((s) => s.trim()).filter(Boolean);
|
|
1538
1538
|
if (items.length === 0 && !field.required) return null;
|
|
1539
1539
|
return {
|
|
1540
1540
|
op: "set_string_list",
|
|
@@ -1612,16 +1612,16 @@ async function promptForCheckboxes(ctx) {
|
|
|
1612
1612
|
});
|
|
1613
1613
|
if (p.isCancel(result)) return null;
|
|
1614
1614
|
const selected = result;
|
|
1615
|
-
const values
|
|
1616
|
-
for (const opt of field.options) values
|
|
1615
|
+
const values = {};
|
|
1616
|
+
for (const opt of field.options) values[opt.id] = selected.includes(opt.id) ? "done" : "todo";
|
|
1617
1617
|
return {
|
|
1618
1618
|
op: "set_checkboxes",
|
|
1619
1619
|
fieldId: field.id,
|
|
1620
|
-
value: values
|
|
1620
|
+
value: values
|
|
1621
1621
|
};
|
|
1622
1622
|
}
|
|
1623
1623
|
if (field.checkboxMode === "explicit") {
|
|
1624
|
-
const values
|
|
1624
|
+
const values = {};
|
|
1625
1625
|
for (const opt of field.options) {
|
|
1626
1626
|
const current = currentValues[opt.id];
|
|
1627
1627
|
const result = await p.select({
|
|
@@ -1643,12 +1643,12 @@ async function promptForCheckboxes(ctx) {
|
|
|
1643
1643
|
initialValue: current === "yes" || current === "no" ? current : "unfilled"
|
|
1644
1644
|
});
|
|
1645
1645
|
if (p.isCancel(result)) return null;
|
|
1646
|
-
values
|
|
1646
|
+
values[opt.id] = result;
|
|
1647
1647
|
}
|
|
1648
1648
|
return {
|
|
1649
1649
|
op: "set_checkboxes",
|
|
1650
1650
|
fieldId: field.id,
|
|
1651
|
-
value: values
|
|
1651
|
+
value: values
|
|
1652
1652
|
};
|
|
1653
1653
|
}
|
|
1654
1654
|
const values = {};
|
|
@@ -1701,8 +1701,8 @@ async function promptForUrl(ctx) {
|
|
|
1701
1701
|
placeholder: placeholderText,
|
|
1702
1702
|
initialValue: currentVal ?? "",
|
|
1703
1703
|
validate: (value) => {
|
|
1704
|
-
if (field.required && !value
|
|
1705
|
-
if (!value
|
|
1704
|
+
if (field.required && !value?.trim()) return "This field is required";
|
|
1705
|
+
if (!value?.trim()) return;
|
|
1706
1706
|
try {
|
|
1707
1707
|
new URL(value);
|
|
1708
1708
|
} catch {
|
|
@@ -1715,7 +1715,7 @@ async function promptForUrl(ctx) {
|
|
|
1715
1715
|
return {
|
|
1716
1716
|
op: "set_url",
|
|
1717
1717
|
fieldId: field.id,
|
|
1718
|
-
value: result
|
|
1718
|
+
value: result ?? null
|
|
1719
1719
|
};
|
|
1720
1720
|
}
|
|
1721
1721
|
/**
|
|
@@ -1743,8 +1743,8 @@ async function promptForDate(ctx) {
|
|
|
1743
1743
|
placeholder: currentVal ?? `YYYY-MM-DD${formatHint}`,
|
|
1744
1744
|
initialValue: currentVal ?? "",
|
|
1745
1745
|
validate: (value) => {
|
|
1746
|
-
if (field.required && !value
|
|
1747
|
-
if (!value
|
|
1746
|
+
if (field.required && !value?.trim()) return "This field is required";
|
|
1747
|
+
if (!value?.trim()) return;
|
|
1748
1748
|
if (!isValidDate(value)) return "Please enter a valid date in YYYY-MM-DD format";
|
|
1749
1749
|
if (field.min && value < field.min) return `Date must be on or after ${field.min}`;
|
|
1750
1750
|
if (field.max && value > field.max) return `Date must be on or before ${field.max}`;
|
|
@@ -1755,7 +1755,7 @@ async function promptForDate(ctx) {
|
|
|
1755
1755
|
return {
|
|
1756
1756
|
op: "set_date",
|
|
1757
1757
|
fieldId: field.id,
|
|
1758
|
-
value: result
|
|
1758
|
+
value: result ?? null
|
|
1759
1759
|
};
|
|
1760
1760
|
}
|
|
1761
1761
|
/** Default year range for validation */
|
|
@@ -1774,8 +1774,8 @@ async function promptForYear(ctx) {
|
|
|
1774
1774
|
placeholder: currentVal !== null ? String(currentVal) : `Year (${minYear}-${maxYear})`,
|
|
1775
1775
|
initialValue: currentVal !== null ? String(currentVal) : "",
|
|
1776
1776
|
validate: (value) => {
|
|
1777
|
-
if (field.required && !value
|
|
1778
|
-
if (!value
|
|
1777
|
+
if (field.required && !value?.trim()) return "This field is required";
|
|
1778
|
+
if (!value?.trim()) return;
|
|
1779
1779
|
const num = Number(value);
|
|
1780
1780
|
if (isNaN(num) || !Number.isInteger(num)) return "Please enter a valid year (e.g., 2025)";
|
|
1781
1781
|
if (num < minYear) return `Year must be ${minYear} or later`;
|
|
@@ -1805,11 +1805,11 @@ async function promptForUrlList(ctx) {
|
|
|
1805
1805
|
placeholder: hint,
|
|
1806
1806
|
initialValue: currentItems.join("\n"),
|
|
1807
1807
|
validate: (value) => {
|
|
1808
|
-
const items
|
|
1809
|
-
if (field.required && items
|
|
1810
|
-
if (field.minItems && items
|
|
1811
|
-
if (field.maxItems && items
|
|
1812
|
-
for (const item of items
|
|
1808
|
+
const items = (value ?? "").split("\n").map((s) => s.trim()).filter(Boolean);
|
|
1809
|
+
if (field.required && items.length === 0) return "At least one URL is required";
|
|
1810
|
+
if (field.minItems && items.length < field.minItems) return `Minimum ${field.minItems} URLs required`;
|
|
1811
|
+
if (field.maxItems && items.length > field.maxItems) return `Maximum ${field.maxItems} URLs allowed`;
|
|
1812
|
+
for (const item of items) try {
|
|
1813
1813
|
new URL(item);
|
|
1814
1814
|
} catch {
|
|
1815
1815
|
return `Invalid URL: ${item}`;
|
|
@@ -1817,7 +1817,7 @@ async function promptForUrlList(ctx) {
|
|
|
1817
1817
|
}
|
|
1818
1818
|
});
|
|
1819
1819
|
if (p.isCancel(result)) return null;
|
|
1820
|
-
const items = result.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
1820
|
+
const items = (result ?? "").split("\n").map((s) => s.trim()).filter(Boolean);
|
|
1821
1821
|
if (items.length === 0 && !field.required) return null;
|
|
1822
1822
|
return {
|
|
1823
1823
|
op: "set_url_list",
|
|
@@ -1947,7 +1947,7 @@ function truncate(value, maxLength = PATCH_VALUE_MAX_LENGTH) {
|
|
|
1947
1947
|
/**
|
|
1948
1948
|
* Format a patch value for display with truncation.
|
|
1949
1949
|
*/
|
|
1950
|
-
function formatPatchValue(patch) {
|
|
1950
|
+
function formatPatchValue$1(patch) {
|
|
1951
1951
|
switch (patch.op) {
|
|
1952
1952
|
case "set_string": return patch.value ? truncate(`"${patch.value}"`) : "(empty)";
|
|
1953
1953
|
case "set_number": return patch.value !== null ? String(patch.value) : "(empty)";
|
|
@@ -2052,7 +2052,7 @@ function createFillLoggingCallbacks(ctx, options = {}) {
|
|
|
2052
2052
|
logInfo(ctx, ` -> ${pc.yellow(String(patches.length))} patch(es):`);
|
|
2053
2053
|
for (const patch of patches) {
|
|
2054
2054
|
const typeName = formatPatchType(patch);
|
|
2055
|
-
const value = formatPatchValue(patch);
|
|
2055
|
+
const value = formatPatchValue$1(patch);
|
|
2056
2056
|
const fieldId = "fieldId" in patch ? patch.fieldId : patch.op === "add_note" ? patch.ref : "";
|
|
2057
2057
|
if (fieldId) logInfo(ctx, ` ${pc.cyan(fieldId)} ${pc.dim(`(${typeName})`)} = ${pc.green(value)}`);
|
|
2058
2058
|
else logInfo(ctx, ` ${pc.dim(`(${typeName})`)} = ${pc.green(value)}`);
|
|
@@ -2188,7 +2188,7 @@ async function promptForModel(webSearchRequired) {
|
|
|
2188
2188
|
message: "Model ID (provider/model-id):",
|
|
2189
2189
|
placeholder: "anthropic/claude-sonnet-4-20250514",
|
|
2190
2190
|
validate: (value) => {
|
|
2191
|
-
if (!value
|
|
2191
|
+
if (!value?.includes("/")) return "Format: provider/model-id (e.g., anthropic/claude-sonnet-4-20250514)";
|
|
2192
2192
|
}
|
|
2193
2193
|
});
|
|
2194
2194
|
if (p.isCancel(customModel)) return null;
|
|
@@ -2278,6 +2278,7 @@ async function runAgentFillWorkflow(form, modelId, formsDir, filePath, isResearc
|
|
|
2278
2278
|
fillMode: overwrite ? "overwrite" : "continue",
|
|
2279
2279
|
enableWebSearch: isResearch,
|
|
2280
2280
|
captureWireFormat: false,
|
|
2281
|
+
recordFill: false,
|
|
2281
2282
|
callbacks
|
|
2282
2283
|
});
|
|
2283
2284
|
if (result.status.ok) p.log.success(pc.green(`Form completed in ${result.turns} turn(s)`));
|
|
@@ -2768,12 +2769,15 @@ function formatConsoleSession(transcript, useColors) {
|
|
|
2768
2769
|
* Register the fill command.
|
|
2769
2770
|
*/
|
|
2770
2771
|
function registerFillCommand(program) {
|
|
2771
|
-
program.command("fill <file>").description("Run an agent to autonomously fill a form").option("--mock", "Use mock agent (requires --mock-source)").option("--model <id>", "Model ID for live agent (format: provider/model-id, e.g. openai/gpt-5-mini)").option("--mock-source <file>", "Path to completed form for mock agent").option("--record <file>", "Record session transcript to file").option("--max-turns <n>", `Maximum turns (default: ${DEFAULT_MAX_TURNS})`, String(DEFAULT_MAX_TURNS)).option("--max-patches <n>", `Maximum patches per turn (default: ${DEFAULT_MAX_PATCHES_PER_TURN})`, String(DEFAULT_MAX_PATCHES_PER_TURN)).option("--max-issues <n>", `Maximum issues shown per turn (default: ${DEFAULT_MAX_ISSUES_PER_TURN})`, String(DEFAULT_MAX_ISSUES_PER_TURN)).option("--max-fields <n>", "Maximum unique fields per turn (applied before --max-issues)").option("--max-groups <n>", "Maximum unique groups per turn (applied before --max-issues)").option("--roles <roles>", "Target roles to fill (comma-separated, or '*' for all; default: 'agent', or 'user' in --interactive mode)").option("--mode <mode>", "Fill mode: continue (skip filled fields) or overwrite (re-fill; default: continue)").option("-o, --output <file>", "Write final form to file").option("--prompt <file>", "Path to custom system prompt file (appends to default)").option("--instructions <text>", "Inline system prompt (appends to default; takes precedence over --prompt)").option("-i, --interactive", "Interactive mode: prompt user for field values (defaults to user role)").option("--normalize", "Regenerate form without preserving external content").action(async (file, options, cmd) => {
|
|
2772
|
+
program.command("fill <file>").description("Run an agent to autonomously fill a form").option("--mock", "Use mock agent (requires --mock-source)").option("--model <id>", "Model ID for live agent (format: provider/model-id, e.g. openai/gpt-5-mini)").option("--mock-source <file>", "Path to completed form for mock agent").option("--record <file>", "Record session transcript to file").option("--max-turns <n>", `Maximum turns (default: ${DEFAULT_MAX_TURNS})`, String(DEFAULT_MAX_TURNS)).option("--max-patches <n>", `Maximum patches per turn (default: ${DEFAULT_MAX_PATCHES_PER_TURN})`, String(DEFAULT_MAX_PATCHES_PER_TURN)).option("--max-issues <n>", `Maximum issues shown per turn (default: ${DEFAULT_MAX_ISSUES_PER_TURN})`, String(DEFAULT_MAX_ISSUES_PER_TURN)).option("--max-fields <n>", "Maximum unique fields per turn (applied before --max-issues)").option("--max-groups <n>", "Maximum unique groups per turn (applied before --max-issues)").option("--roles <roles>", "Target roles to fill (comma-separated, or '*' for all; default: 'agent', or 'user' in --interactive mode)").option("--mode <mode>", "Fill mode: continue (skip filled fields) or overwrite (re-fill; default: continue)").option("-o, --output <file>", "Write final form to file").option("--prompt <file>", "Path to custom system prompt file (appends to default)").option("--instructions <text>", "Inline system prompt (appends to default; takes precedence over --prompt)").option("-i, --interactive", "Interactive mode: prompt user for field values (defaults to user role)").option("--normalize", "Regenerate form without preserving external content").option("--record-fill", "Write fill record to sidecar .fill.json file").option("--record-fill-stable", "Write fill record without timestamps/durations (for golden tests)").action(async (file, options, cmd) => {
|
|
2772
2773
|
const ctx = getCommandContext(cmd);
|
|
2773
2774
|
const filePath = resolve(file);
|
|
2775
|
+
let harness;
|
|
2776
|
+
let collector;
|
|
2777
|
+
let targetRoles = [];
|
|
2778
|
+
let form;
|
|
2774
2779
|
try {
|
|
2775
2780
|
const startTime = Date.now();
|
|
2776
|
-
let targetRoles;
|
|
2777
2781
|
if (options.roles) try {
|
|
2778
2782
|
targetRoles = parseRolesFlag(options.roles);
|
|
2779
2783
|
} catch (error) {
|
|
@@ -2792,7 +2796,7 @@ function registerFillCommand(program) {
|
|
|
2792
2796
|
logVerbose(ctx, `Reading form: ${filePath}`);
|
|
2793
2797
|
const formContent = await readFile$1(filePath);
|
|
2794
2798
|
logVerbose(ctx, "Parsing form...");
|
|
2795
|
-
|
|
2799
|
+
form = parseForm(formContent);
|
|
2796
2800
|
if (options.interactive) {
|
|
2797
2801
|
if (options.mock) {
|
|
2798
2802
|
logError("--interactive cannot be used with --mock");
|
|
@@ -2817,19 +2821,19 @@ function registerFillCommand(program) {
|
|
|
2817
2821
|
process.exit(1);
|
|
2818
2822
|
}
|
|
2819
2823
|
if (patches.length > 0) applyPatches(form, patches);
|
|
2820
|
-
const durationMs
|
|
2821
|
-
let outputPath
|
|
2822
|
-
if (options.output) outputPath
|
|
2824
|
+
const durationMs = Date.now() - startTime;
|
|
2825
|
+
let outputPath;
|
|
2826
|
+
if (options.output) outputPath = resolve(options.output);
|
|
2823
2827
|
else {
|
|
2824
2828
|
const formsDir = getFormsDir(ctx.formsDir);
|
|
2825
2829
|
await ensureFormsDir(formsDir);
|
|
2826
|
-
outputPath
|
|
2830
|
+
outputPath = generateVersionedPathInFormsDir(filePath, formsDir);
|
|
2827
2831
|
}
|
|
2828
2832
|
if (ctx.dryRun) {
|
|
2829
|
-
logInfo(ctx, `[DRY RUN] Would write form to: ${outputPath
|
|
2833
|
+
logInfo(ctx, `[DRY RUN] Would write form to: ${outputPath}`);
|
|
2830
2834
|
showInteractiveOutro(patches.length, false);
|
|
2831
2835
|
} else {
|
|
2832
|
-
const { reportPath, yamlPath, formPath, schemaPath } = await exportMultiFormat(form, outputPath
|
|
2836
|
+
const { reportPath, yamlPath, formPath, schemaPath } = await exportMultiFormat(form, outputPath);
|
|
2833
2837
|
showInteractiveOutro(patches.length, false);
|
|
2834
2838
|
console.log("");
|
|
2835
2839
|
p.log.success("Outputs:");
|
|
@@ -2838,11 +2842,11 @@ function registerFillCommand(program) {
|
|
|
2838
2842
|
console.log(` ${formatPath(formPath)} ${pc.dim("(filled markform source)")}`);
|
|
2839
2843
|
console.log(` ${formatPath(schemaPath)} ${pc.dim("(JSON Schema)")}`);
|
|
2840
2844
|
}
|
|
2841
|
-
logTiming(ctx, "Fill time", durationMs
|
|
2845
|
+
logTiming(ctx, "Fill time", durationMs);
|
|
2842
2846
|
if (patches.length > 0) {
|
|
2843
2847
|
console.log("");
|
|
2844
2848
|
console.log("Next step: fill remaining fields with agent");
|
|
2845
|
-
console.log(` markform fill ${formatPath(outputPath
|
|
2849
|
+
console.log(` markform fill ${formatPath(outputPath)} --model=<provider/model>`);
|
|
2846
2850
|
}
|
|
2847
2851
|
process.exit(0);
|
|
2848
2852
|
}
|
|
@@ -2857,7 +2861,7 @@ function registerFillCommand(program) {
|
|
|
2857
2861
|
process.exit(1);
|
|
2858
2862
|
}
|
|
2859
2863
|
if (targetRoles.includes("*")) logWarn(ctx, "Warning: Filling all roles including user-designated fields");
|
|
2860
|
-
const
|
|
2864
|
+
const cliOptions = {
|
|
2861
2865
|
maxTurns: options.maxTurns ? parseInt(options.maxTurns, 10) : void 0,
|
|
2862
2866
|
maxPatchesPerTurn: options.maxPatches ? parseInt(options.maxPatches, 10) : void 0,
|
|
2863
2867
|
maxIssuesPerTurn: options.maxIssues ? parseInt(options.maxIssues, 10) : void 0,
|
|
@@ -2865,8 +2869,9 @@ function registerFillCommand(program) {
|
|
|
2865
2869
|
maxGroupsPerTurn: options.maxGroups ? parseInt(options.maxGroups, 10) : void 0,
|
|
2866
2870
|
targetRoles,
|
|
2867
2871
|
fillMode
|
|
2868
|
-
}
|
|
2869
|
-
const
|
|
2872
|
+
};
|
|
2873
|
+
const harnessConfig = resolveHarnessConfig(form, cliOptions);
|
|
2874
|
+
harness = createHarness(form, harnessConfig);
|
|
2870
2875
|
let agent;
|
|
2871
2876
|
let mockPath;
|
|
2872
2877
|
let agentProvider;
|
|
@@ -2877,12 +2882,36 @@ function registerFillCommand(program) {
|
|
|
2877
2882
|
mockPath = resolve(options.mockSource);
|
|
2878
2883
|
logVerbose(ctx, `Reading mock source: ${mockPath}`);
|
|
2879
2884
|
agent = createMockAgent(parseForm(await readFile$1(mockPath)));
|
|
2885
|
+
const structureSummary = computeStructureSummary(form.schema);
|
|
2886
|
+
collector = new FillRecordCollector({
|
|
2887
|
+
form: {
|
|
2888
|
+
id: form.schema.id,
|
|
2889
|
+
title: form.schema.title,
|
|
2890
|
+
description: form.schema.description,
|
|
2891
|
+
structure: structureSummary
|
|
2892
|
+
},
|
|
2893
|
+
provider: "mock",
|
|
2894
|
+
model: "mock",
|
|
2895
|
+
parallelEnabled: false
|
|
2896
|
+
});
|
|
2880
2897
|
} else {
|
|
2881
2898
|
const modelIdString = options.model;
|
|
2882
2899
|
logVerbose(ctx, `Resolving model: ${modelIdString}`);
|
|
2883
2900
|
const { model, provider, modelId } = await resolveModel(modelIdString);
|
|
2884
2901
|
agentProvider = provider;
|
|
2885
2902
|
agentModelName = modelId;
|
|
2903
|
+
const structureSummary = computeStructureSummary(form.schema);
|
|
2904
|
+
collector = new FillRecordCollector({
|
|
2905
|
+
form: {
|
|
2906
|
+
id: form.schema.id,
|
|
2907
|
+
title: form.schema.title,
|
|
2908
|
+
description: form.schema.description,
|
|
2909
|
+
structure: structureSummary
|
|
2910
|
+
},
|
|
2911
|
+
provider,
|
|
2912
|
+
model: modelIdString,
|
|
2913
|
+
parallelEnabled: false
|
|
2914
|
+
});
|
|
2886
2915
|
let systemPrompt;
|
|
2887
2916
|
if (options.instructions) {
|
|
2888
2917
|
systemPrompt = options.instructions;
|
|
@@ -2892,13 +2921,44 @@ function registerFillCommand(program) {
|
|
|
2892
2921
|
logVerbose(ctx, `Reading system prompt from: ${promptPath}`);
|
|
2893
2922
|
systemPrompt = await readFile$1(promptPath);
|
|
2894
2923
|
}
|
|
2895
|
-
const
|
|
2924
|
+
const cliCallbacks = createCliToolCallbacks({
|
|
2896
2925
|
message: (msg) => currentSpinner?.message(msg),
|
|
2897
2926
|
update: (context) => currentSpinner?.update(context),
|
|
2898
2927
|
stop: (msg) => currentSpinner?.stop(msg),
|
|
2899
2928
|
error: (msg) => currentSpinner?.error(msg),
|
|
2900
2929
|
getElapsedMs: () => currentSpinner?.getElapsedMs() ?? 0
|
|
2901
2930
|
}, ctx);
|
|
2931
|
+
const liveCollector = collector;
|
|
2932
|
+
const callbacks = {
|
|
2933
|
+
onTurnStart: (turn) => {
|
|
2934
|
+
liveCollector.onTurnStart({
|
|
2935
|
+
turnNumber: turn.turnNumber,
|
|
2936
|
+
issuesCount: turn.issuesCount,
|
|
2937
|
+
order: turn.order ?? 0,
|
|
2938
|
+
executionId: turn.executionId ?? "cli-serial"
|
|
2939
|
+
});
|
|
2940
|
+
},
|
|
2941
|
+
onTurnComplete: (progress) => {
|
|
2942
|
+
liveCollector.onTurnComplete(progress);
|
|
2943
|
+
},
|
|
2944
|
+
onToolStart: (call) => {
|
|
2945
|
+
cliCallbacks.onToolStart?.(call);
|
|
2946
|
+
liveCollector.onToolStart(call);
|
|
2947
|
+
},
|
|
2948
|
+
onToolEnd: (call) => {
|
|
2949
|
+
cliCallbacks.onToolEnd?.(call);
|
|
2950
|
+
liveCollector.onToolEnd(call);
|
|
2951
|
+
},
|
|
2952
|
+
onLlmCallStart: (call) => {
|
|
2953
|
+
liveCollector.onLlmCallStart(call);
|
|
2954
|
+
},
|
|
2955
|
+
onLlmCallEnd: (call) => {
|
|
2956
|
+
liveCollector.onLlmCallEnd(call);
|
|
2957
|
+
},
|
|
2958
|
+
onWebSearch: (info) => {
|
|
2959
|
+
liveCollector.onWebSearch(info);
|
|
2960
|
+
}
|
|
2961
|
+
};
|
|
2902
2962
|
targetRole = targetRoles[0] === "*" ? AGENT_ROLE : targetRoles[0] ?? AGENT_ROLE;
|
|
2903
2963
|
const liveAgent = createLiveAgent({
|
|
2904
2964
|
model,
|
|
@@ -2922,6 +2982,12 @@ function registerFillCommand(program) {
|
|
|
2922
2982
|
let stepResult = harness.step();
|
|
2923
2983
|
let previousRejections;
|
|
2924
2984
|
logInfo(ctx, `${pc.bold(`Turn ${stepResult.turnNumber}:`)} ${formatTurnIssues(stepResult.issues)}`);
|
|
2985
|
+
collector.onTurnStart({
|
|
2986
|
+
turnNumber: stepResult.turnNumber,
|
|
2987
|
+
issuesCount: stepResult.issues.length,
|
|
2988
|
+
order: 0,
|
|
2989
|
+
executionId: "cli-serial"
|
|
2990
|
+
});
|
|
2925
2991
|
while (!stepResult.isComplete && !harness.hasReachedMaxTurns()) {
|
|
2926
2992
|
let spinner = null;
|
|
2927
2993
|
if (!options.mock && agentProvider && agentModelName && process.stdout.isTTY && !ctx.quiet) {
|
|
@@ -2948,7 +3014,7 @@ function registerFillCommand(program) {
|
|
|
2948
3014
|
logInfo(ctx, ` → ${pc.yellow(String(patches.length))} patches${tokenSuffix}:`);
|
|
2949
3015
|
for (const patch of patches) {
|
|
2950
3016
|
const typeName = formatPatchType(patch);
|
|
2951
|
-
const value = formatPatchValue(patch);
|
|
3017
|
+
const value = formatPatchValue$1(patch);
|
|
2952
3018
|
const fieldId = "fieldId" in patch ? patch.fieldId : patch.op === "add_note" ? patch.ref : "";
|
|
2953
3019
|
if (fieldId) logInfo(ctx, ` ${pc.cyan(fieldId)} ${pc.dim(`(${typeName})`)} = ${pc.green(value)}`);
|
|
2954
3020
|
else logInfo(ctx, ` ${pc.dim(`(${typeName})`)} = ${pc.green(value)}`);
|
|
@@ -2987,12 +3053,31 @@ function registerFillCommand(program) {
|
|
|
2987
3053
|
contextPrompt: wire.request.prompt
|
|
2988
3054
|
};
|
|
2989
3055
|
}
|
|
3056
|
+
const prevTurnNumber = stepResult.turnNumber;
|
|
3057
|
+
const prevIssuesShown = stepResult.issues.length;
|
|
2990
3058
|
stepResult = harness.apply(patches, stepResult.issues, llmStats, context, wire);
|
|
3059
|
+
const rejectedPatches = stepResult.rejectedPatches ?? [];
|
|
3060
|
+
collector.onTurnComplete({
|
|
3061
|
+
turnNumber: prevTurnNumber,
|
|
3062
|
+
issuesShown: prevIssuesShown,
|
|
3063
|
+
patchesApplied: patches.length - rejectedPatches.length,
|
|
3064
|
+
requiredIssuesRemaining: stepResult.issues.filter((i) => i.severity === "required").length,
|
|
3065
|
+
isComplete: stepResult.isComplete,
|
|
3066
|
+
rejectedPatches,
|
|
3067
|
+
issues: stepResult.issues,
|
|
3068
|
+
patches
|
|
3069
|
+
});
|
|
2991
3070
|
previousRejections = stepResult.rejectedPatches;
|
|
2992
3071
|
if (stepResult.isComplete) logInfo(ctx, pc.green(` ✓ Complete`));
|
|
2993
3072
|
else if (!harness.hasReachedMaxTurns()) {
|
|
2994
3073
|
stepResult = harness.step();
|
|
2995
3074
|
logInfo(ctx, `${pc.bold(`Turn ${stepResult.turnNumber}:`)} ${formatTurnIssues(stepResult.issues)}`);
|
|
3075
|
+
collector.onTurnStart({
|
|
3076
|
+
turnNumber: stepResult.turnNumber,
|
|
3077
|
+
issuesCount: stepResult.issues.length,
|
|
3078
|
+
order: 0,
|
|
3079
|
+
executionId: "cli-serial"
|
|
3080
|
+
});
|
|
2996
3081
|
}
|
|
2997
3082
|
}
|
|
2998
3083
|
const durationMs = Date.now() - startTime;
|
|
@@ -3012,6 +3097,24 @@ function registerFillCommand(program) {
|
|
|
3012
3097
|
await writeFile(outputPath, formMarkdown);
|
|
3013
3098
|
logSuccess(ctx, `Form written to: ${outputPath}`);
|
|
3014
3099
|
}
|
|
3100
|
+
const finalInspect = inspect(harness.getForm(), { targetRoles });
|
|
3101
|
+
const progressSummary = computeProgressSummary(form.schema, harness.getForm().responsesByFieldId, harness.getForm().notes, finalInspect.issues);
|
|
3102
|
+
collector.setStatus(stepResult.isComplete ? "completed" : "partial", stepResult.isComplete ? void 0 : "max_turns");
|
|
3103
|
+
const fillRecord = collector.getRecord(progressSummary.counts);
|
|
3104
|
+
if (!ctx.quiet) {
|
|
3105
|
+
console.log("");
|
|
3106
|
+
const summary = formatFillRecordSummary(fillRecord, { verbose: ctx.verbose });
|
|
3107
|
+
console.error(summary);
|
|
3108
|
+
}
|
|
3109
|
+
if (options.recordFill || options.recordFillStable) {
|
|
3110
|
+
const sidecarPath = deriveFillRecordPath(outputPath);
|
|
3111
|
+
const recordToWrite = options.recordFillStable ? stripUnstableFillRecordFields(fillRecord) : fillRecord;
|
|
3112
|
+
if (ctx.dryRun) logInfo(ctx, `[DRY RUN] Would write fill record to: ${sidecarPath}`);
|
|
3113
|
+
else {
|
|
3114
|
+
writeFileSync(sidecarPath, JSON.stringify(recordToWrite, null, 2));
|
|
3115
|
+
logSuccess(ctx, `Fill record written to: ${sidecarPath}`);
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3015
3118
|
const transcript = buildSessionTranscript(filePath, options.mock ? "mock" : "live", mockPath, options.model, harnessConfig, harness.getTurns(), stepResult.isComplete, outputPath);
|
|
3016
3119
|
if (options.record) {
|
|
3017
3120
|
const recordPath = resolve(options.record);
|
|
@@ -3023,13 +3126,25 @@ function registerFillCommand(program) {
|
|
|
3023
3126
|
await writeFile(recordPath, yaml);
|
|
3024
3127
|
logSuccess(ctx, `Session recorded to: ${recordPath}`);
|
|
3025
3128
|
}
|
|
3026
|
-
} else {
|
|
3129
|
+
} else if (!ctx.quiet) {
|
|
3027
3130
|
const output = formatOutput(ctx, transcript, (data, useColors) => formatConsoleSession(data, useColors));
|
|
3028
3131
|
console.log(output);
|
|
3029
3132
|
}
|
|
3030
3133
|
process.exit(stepResult.isComplete ? 0 : 1);
|
|
3031
3134
|
} catch (error) {
|
|
3032
|
-
|
|
3135
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3136
|
+
logError(message);
|
|
3137
|
+
if ((options.recordFill || options.recordFillStable) && collector && harness && form && options.output) try {
|
|
3138
|
+
const currentForm = harness.getForm();
|
|
3139
|
+
const finalInspect = inspect(currentForm, { targetRoles });
|
|
3140
|
+
const progressSummary = computeProgressSummary(form.schema, currentForm.responsesByFieldId, currentForm.notes, finalInspect.issues);
|
|
3141
|
+
collector.setStatus("failed", message);
|
|
3142
|
+
const fillRecord = collector.getRecord(progressSummary.counts);
|
|
3143
|
+
const sidecarPath = deriveFillRecordPath(resolve(options.output));
|
|
3144
|
+
const recordToWrite = options.recordFillStable ? stripUnstableFillRecordFields(fillRecord) : fillRecord;
|
|
3145
|
+
writeFileSync(sidecarPath, JSON.stringify(recordToWrite, null, 2));
|
|
3146
|
+
logWarn(ctx, `Partial fill record written to: ${sidecarPath}`);
|
|
3147
|
+
} catch {}
|
|
3033
3148
|
process.exit(1);
|
|
3034
3149
|
}
|
|
3035
3150
|
});
|
|
@@ -3098,7 +3213,6 @@ function formatPriority$1(priority, useColors) {
|
|
|
3098
3213
|
case 2: return pc.yellow(label);
|
|
3099
3214
|
case 3: return pc.cyan(label);
|
|
3100
3215
|
case 4: return pc.blue(label);
|
|
3101
|
-
case 5:
|
|
3102
3216
|
default: return pc.dim(label);
|
|
3103
3217
|
}
|
|
3104
3218
|
}
|
|
@@ -3595,10 +3709,10 @@ function buildPlanItem(form, item) {
|
|
|
3595
3709
|
}
|
|
3596
3710
|
function formatConsolePlan(report, useColors) {
|
|
3597
3711
|
const lines = [];
|
|
3598
|
-
const bold = useColors ? pc.bold : (s
|
|
3599
|
-
const dim = useColors ? pc.dim : (s
|
|
3600
|
-
const cyan = useColors ? pc.cyan : (s
|
|
3601
|
-
const yellow = useColors ? pc.yellow : (s
|
|
3712
|
+
const bold = useColors ? pc.bold : (s) => s;
|
|
3713
|
+
const dim = useColors ? pc.dim : (s) => s;
|
|
3714
|
+
const cyan = useColors ? pc.cyan : (s) => s;
|
|
3715
|
+
const yellow = useColors ? pc.yellow : (s) => s;
|
|
3602
3716
|
const titlePart = report.title ? ` (${report.title})` : "";
|
|
3603
3717
|
lines.push(bold(cyan(`Plan: ${report.formId}${titlePart}`)));
|
|
3604
3718
|
lines.push("");
|
|
@@ -3738,10 +3852,10 @@ function openBrowser(url) {
|
|
|
3738
3852
|
/**
|
|
3739
3853
|
* Build tabs for a form file.
|
|
3740
3854
|
* All tabs are always present - content is generated dynamically from the form.
|
|
3741
|
-
* Tab order: View, Edit, Source, Report, Values, Schema
|
|
3855
|
+
* Tab order: View, Edit, Source, Report, Values, Schema, Fill Record (if sidecar exists)
|
|
3742
3856
|
*/
|
|
3743
3857
|
function buildFormTabs(formPath) {
|
|
3744
|
-
|
|
3858
|
+
const tabs = [
|
|
3745
3859
|
{
|
|
3746
3860
|
id: "view",
|
|
3747
3861
|
label: "View",
|
|
@@ -3773,6 +3887,13 @@ function buildFormTabs(formPath) {
|
|
|
3773
3887
|
path: null
|
|
3774
3888
|
}
|
|
3775
3889
|
];
|
|
3890
|
+
const sidecarPath = deriveFillRecordPath(formPath);
|
|
3891
|
+
if (existsSync(sidecarPath)) tabs.push({
|
|
3892
|
+
id: "fill-record",
|
|
3893
|
+
label: "Fill Record",
|
|
3894
|
+
path: sidecarPath
|
|
3895
|
+
});
|
|
3896
|
+
return tabs;
|
|
3776
3897
|
}
|
|
3777
3898
|
/**
|
|
3778
3899
|
* Register the serve command.
|
|
@@ -3862,6 +3983,18 @@ async function handleRequest(req, res, filePath, fileType, form, ctx, tabs, upda
|
|
|
3862
3983
|
res.end(html);
|
|
3863
3984
|
return;
|
|
3864
3985
|
}
|
|
3986
|
+
if (tabId === "fill-record" && tab?.path) try {
|
|
3987
|
+
const content = await readFile(tab.path, "utf-8");
|
|
3988
|
+
const html = renderFillRecordContent(JSON.parse(content));
|
|
3989
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
3990
|
+
res.end(html);
|
|
3991
|
+
return;
|
|
3992
|
+
} catch (error) {
|
|
3993
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3994
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
3995
|
+
res.end(`Error loading fill record: ${message}`);
|
|
3996
|
+
return;
|
|
3997
|
+
}
|
|
3865
3998
|
res.writeHead(404);
|
|
3866
3999
|
res.end("Tab not found");
|
|
3867
4000
|
return;
|
|
@@ -4543,6 +4676,34 @@ function renderFormHtml(form, tabs) {
|
|
|
4543
4676
|
</div>
|
|
4544
4677
|
${showTabs ? "<div id=\"tab-other\" class=\"tab-content\"><div class=\"loading\">Loading...</div></div>" : ""}
|
|
4545
4678
|
<script>
|
|
4679
|
+
// Copy YAML content handler for Fill Record tab (must be global for dynamically loaded content)
|
|
4680
|
+
function frCopyYaml(btn) {
|
|
4681
|
+
const pre = btn.parentElement.querySelector('pre');
|
|
4682
|
+
navigator.clipboard.writeText(pre.textContent).then(() => {
|
|
4683
|
+
const orig = btn.textContent;
|
|
4684
|
+
btn.textContent = 'Copied!';
|
|
4685
|
+
setTimeout(() => btn.textContent = orig, 1500);
|
|
4686
|
+
});
|
|
4687
|
+
}
|
|
4688
|
+
|
|
4689
|
+
// Tooltip handlers for Fill Record visualizations (must be global for dynamically loaded content)
|
|
4690
|
+
function frShowTip(el) {
|
|
4691
|
+
var tip = document.getElementById('fr-tooltip');
|
|
4692
|
+
if (tip && el.dataset.tooltip) {
|
|
4693
|
+
tip.textContent = el.dataset.tooltip;
|
|
4694
|
+
// Position tooltip centered above the element
|
|
4695
|
+
var rect = el.getBoundingClientRect();
|
|
4696
|
+
tip.style.left = (rect.left + rect.width / 2) + 'px';
|
|
4697
|
+
tip.style.top = (rect.top - 8) + 'px';
|
|
4698
|
+
tip.style.transform = 'translate(-50%, -100%)';
|
|
4699
|
+
tip.classList.add('visible');
|
|
4700
|
+
}
|
|
4701
|
+
}
|
|
4702
|
+
function frHideTip() {
|
|
4703
|
+
var tip = document.getElementById('fr-tooltip');
|
|
4704
|
+
if (tip) tip.classList.remove('visible');
|
|
4705
|
+
}
|
|
4706
|
+
|
|
4546
4707
|
// Track fields marked for skip
|
|
4547
4708
|
const skippedFields = new Set();
|
|
4548
4709
|
|
|
@@ -5315,7 +5476,7 @@ function renderViewFieldValue(field, value, isSkipped) {
|
|
|
5315
5476
|
case "string": {
|
|
5316
5477
|
const v = value.kind === "string" ? value.value : null;
|
|
5317
5478
|
if (v === null || v === "") return "<div class=\"view-field-empty\">(not filled)</div>";
|
|
5318
|
-
return `<div class="view-field-value">${
|
|
5479
|
+
return `<div class="view-field-value">${formatBareUrlsAsHtmlLinks(v, escapeHtml)}</div>`;
|
|
5319
5480
|
}
|
|
5320
5481
|
case "number": {
|
|
5321
5482
|
const v = value.kind === "number" ? value.value : null;
|
|
@@ -5325,7 +5486,7 @@ function renderViewFieldValue(field, value, isSkipped) {
|
|
|
5325
5486
|
case "string_list": {
|
|
5326
5487
|
const items = value.kind === "string_list" ? value.items : [];
|
|
5327
5488
|
if (items.length === 0) return "<div class=\"view-field-empty\">(not filled)</div>";
|
|
5328
|
-
return `<div class="view-field-value"><ul>${items.map((i) => `<li>${
|
|
5489
|
+
return `<div class="view-field-value"><ul>${items.map((i) => `<li>${formatBareUrlsAsHtmlLinks(i, escapeHtml)}</li>`).join("")}</ul></div>`;
|
|
5329
5490
|
}
|
|
5330
5491
|
case "single_select": {
|
|
5331
5492
|
const selected = value.kind === "single_select" ? value.selected : null;
|
|
@@ -5389,7 +5550,7 @@ function renderViewFieldValue(field, value, isSkipped) {
|
|
|
5389
5550
|
if (col.type === "url" && cellValue) {
|
|
5390
5551
|
const domain = friendlyUrlAbbrev(cellValue);
|
|
5391
5552
|
cellHtml = `<a href="${escapeHtml(cellValue)}" target="_blank" class="url-link" data-url="${escapeHtml(cellValue)}">${escapeHtml(domain)}</a>`;
|
|
5392
|
-
} else cellHtml =
|
|
5553
|
+
} else cellHtml = formatBareUrlsAsHtmlLinks(cellValue, escapeHtml);
|
|
5393
5554
|
}
|
|
5394
5555
|
tableHtml += `<td>${cellHtml}</td>`;
|
|
5395
5556
|
}
|
|
@@ -5600,6 +5761,7 @@ function renderMarkdownContent(content) {
|
|
|
5600
5761
|
}
|
|
5601
5762
|
/**
|
|
5602
5763
|
* Format inline markdown (bold, italic, code, links, checkboxes).
|
|
5764
|
+
* Also auto-links bare URLs for consistency.
|
|
5603
5765
|
*/
|
|
5604
5766
|
function formatInlineMarkdown(text) {
|
|
5605
5767
|
let result = escapeHtml(text);
|
|
@@ -5612,6 +5774,12 @@ function formatInlineMarkdown(text) {
|
|
|
5612
5774
|
const cleanUrl = url.replace(/&/g, "&");
|
|
5613
5775
|
return `<a href="${cleanUrl}" target="_blank" class="url-link" data-url="${cleanUrl}">${linkText}</a>`;
|
|
5614
5776
|
});
|
|
5777
|
+
result = result.replace(/(?<!href="|data-url="|">|\]\()(?:https?:\/\/|www\.)[^\s<>"]+(?<![.,;:!?'")])/g, (url) => {
|
|
5778
|
+
const cleanUrl = url.replace(/&/g, "&");
|
|
5779
|
+
const fullUrl = cleanUrl.startsWith("www.") ? `https://${cleanUrl}` : cleanUrl;
|
|
5780
|
+
const display = friendlyUrlAbbrev(fullUrl);
|
|
5781
|
+
return `<a href="${escapeHtml(fullUrl)}" target="_blank" class="url-link" data-url="${escapeHtml(fullUrl)}">${escapeHtml(display)}</a>`;
|
|
5782
|
+
});
|
|
5615
5783
|
return result;
|
|
5616
5784
|
}
|
|
5617
5785
|
/**
|
|
@@ -5656,6 +5824,789 @@ function renderJsonContent(content) {
|
|
|
5656
5824
|
}
|
|
5657
5825
|
return `<pre>${formatted.replace(/"([^"]+)":/g, "<span class=\"syn-key\">\"$1\"</span>:").replace(/: "([^"]*)"/g, ": <span class=\"syn-string\">\"$1\"</span>").replace(/: (-?\d+\.?\d*)/g, ": <span class=\"syn-number\">$1</span>").replace(/: (true|false)/g, ": <span class=\"syn-bool\">$1</span>").replace(/: (null)/g, ": <span class=\"syn-null\">$1</span>")}</pre>`;
|
|
5658
5826
|
}
|
|
5827
|
+
/**
|
|
5828
|
+
* Format milliseconds as human-readable duration.
|
|
5829
|
+
* @public Exported for reuse in other visualizations.
|
|
5830
|
+
*/
|
|
5831
|
+
function formatDuration(ms) {
|
|
5832
|
+
if (ms < 1e3) return `${ms.toFixed(0)}ms`;
|
|
5833
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
5834
|
+
return `${Math.floor(ms / 6e4)}m ${(ms % 6e4 / 1e3).toFixed(0)}s`;
|
|
5835
|
+
}
|
|
5836
|
+
/**
|
|
5837
|
+
* Format token count with K suffix for large numbers.
|
|
5838
|
+
* @public Exported for reuse in other visualizations.
|
|
5839
|
+
*/
|
|
5840
|
+
function formatTokens(count) {
|
|
5841
|
+
if (count >= 1e4) return `${(count / 1e3).toFixed(1)}k`;
|
|
5842
|
+
if (count >= 1e3) return `${(count / 1e3).toFixed(1)}k`;
|
|
5843
|
+
return count.toLocaleString();
|
|
5844
|
+
}
|
|
5845
|
+
/**
|
|
5846
|
+
* Format a patch value for display.
|
|
5847
|
+
* Shows full content - the container has max-height with scroll for long values.
|
|
5848
|
+
*/
|
|
5849
|
+
function formatPatchValue(value) {
|
|
5850
|
+
if (value === null || value === void 0) return "<em class=\"fr-turn__patch-value--clear\">(cleared)</em>";
|
|
5851
|
+
if (typeof value === "string") return escapeHtml(value);
|
|
5852
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
5853
|
+
return escapeHtml(JSON.stringify(value, null, 2));
|
|
5854
|
+
}
|
|
5855
|
+
/**
|
|
5856
|
+
* Render patches from a fill_form tool call input.
|
|
5857
|
+
* Returns HTML for the patch details section.
|
|
5858
|
+
*/
|
|
5859
|
+
function renderPatchDetails(input) {
|
|
5860
|
+
const patches = input.patches;
|
|
5861
|
+
if (!Array.isArray(patches) || patches.length === 0) return "";
|
|
5862
|
+
return `<div class="fr-turn__patches">${patches.map((patch) => {
|
|
5863
|
+
if (!patch || typeof patch !== "object") return "";
|
|
5864
|
+
const p = patch;
|
|
5865
|
+
const op = typeof p.op === "string" ? p.op : "unknown";
|
|
5866
|
+
const fieldId = typeof p.fieldId === "string" ? p.fieldId : typeof p.noteId === "string" ? p.noteId : "";
|
|
5867
|
+
const opLabel = op.replace(/_/g, " ");
|
|
5868
|
+
let valueHtml = "";
|
|
5869
|
+
if (op === "skip_field") valueHtml = "<em class=\"fr-turn__patch-value--skip\">(skipped)</em>";
|
|
5870
|
+
else if (op === "abort_field") valueHtml = "<em class=\"fr-turn__patch-value--skip\">(aborted)</em>";
|
|
5871
|
+
else if (op === "clear_field") valueHtml = "<em class=\"fr-turn__patch-value--clear\">(cleared)</em>";
|
|
5872
|
+
else if ("value" in p) valueHtml = formatPatchValue(p.value);
|
|
5873
|
+
else if ("values" in p) valueHtml = formatPatchValue(p.values);
|
|
5874
|
+
else if ("rows" in p) valueHtml = formatPatchValue(p.rows);
|
|
5875
|
+
return `
|
|
5876
|
+
<div class="fr-turn__patch">
|
|
5877
|
+
<span class="fr-turn__patch-field">${escapeHtml(fieldId)}</span>
|
|
5878
|
+
<span class="fr-turn__patch-op">${escapeHtml(opLabel)}</span>
|
|
5879
|
+
<span class="fr-turn__patch-value">${valueHtml}</span>
|
|
5880
|
+
</div>
|
|
5881
|
+
`;
|
|
5882
|
+
}).filter(Boolean).join("")}</div>`;
|
|
5883
|
+
}
|
|
5884
|
+
/**
|
|
5885
|
+
* Render a single tool call with enhanced details.
|
|
5886
|
+
* Shows query for web_search, patch details for fill_form.
|
|
5887
|
+
*/
|
|
5888
|
+
function renderToolCall(tc) {
|
|
5889
|
+
const hasError = !!tc.result?.error;
|
|
5890
|
+
const icon = tc.success ? "✓" : "✕";
|
|
5891
|
+
const errorClass = hasError ? " fr-turn__tool--error" : "";
|
|
5892
|
+
let resultSummary = "";
|
|
5893
|
+
if (hasError) resultSummary = `Error: ${escapeHtml(tc.result?.error ?? "")}`;
|
|
5894
|
+
else if (tc.result?.resultCount !== void 0) resultSummary = `${tc.result.resultCount} results`;
|
|
5895
|
+
else resultSummary = "OK";
|
|
5896
|
+
let detailHtml = "";
|
|
5897
|
+
if (tc.tool === "web_search" && typeof tc.input.query === "string") detailHtml = ` <span class="fr-turn__query">"${escapeHtml(tc.input.query)}"</span>`;
|
|
5898
|
+
const toolLine = `<li class="fr-turn__tool${errorClass}">${icon} <strong>${escapeHtml(tc.tool)}</strong>${detailHtml}: ${resultSummary} (${formatDuration(tc.durationMs)})</li>`;
|
|
5899
|
+
if (tc.tool === "fill_form" && tc.input.patches) {
|
|
5900
|
+
const patchDetails = renderPatchDetails(tc.input);
|
|
5901
|
+
if (patchDetails) return toolLine + patchDetails;
|
|
5902
|
+
}
|
|
5903
|
+
return toolLine;
|
|
5904
|
+
}
|
|
5905
|
+
/**
|
|
5906
|
+
* CSS styles for fill record visualization.
|
|
5907
|
+
* Uses CSS custom properties for theming (supports dark mode via prefers-color-scheme).
|
|
5908
|
+
* Designed to be lightweight, reusable, and embeddable.
|
|
5909
|
+
*/
|
|
5910
|
+
const FILL_RECORD_STYLES = `
|
|
5911
|
+
<style>
|
|
5912
|
+
.fr-dashboard {
|
|
5913
|
+
--fr-bg: #ffffff;
|
|
5914
|
+
--fr-bg-muted: #f9fafb;
|
|
5915
|
+
--fr-bg-subtle: #f3f4f6;
|
|
5916
|
+
--fr-border: #e5e7eb;
|
|
5917
|
+
--fr-text: #111827;
|
|
5918
|
+
--fr-text-muted: #6b7280;
|
|
5919
|
+
--fr-primary: #3b82f6;
|
|
5920
|
+
--fr-success: #22c55e;
|
|
5921
|
+
--fr-warning: #f59e0b;
|
|
5922
|
+
--fr-error: #ef4444;
|
|
5923
|
+
--fr-info: #6b7280;
|
|
5924
|
+
|
|
5925
|
+
/* Typography - consolidated to fewer sizes */
|
|
5926
|
+
--fr-font-sm: 13px;
|
|
5927
|
+
--fr-font-base: 14px;
|
|
5928
|
+
--fr-font-lg: 20px;
|
|
5929
|
+
|
|
5930
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
5931
|
+
padding: 20px;
|
|
5932
|
+
max-width: 900px;
|
|
5933
|
+
margin: 0 auto;
|
|
5934
|
+
color: var(--fr-text);
|
|
5935
|
+
line-height: 1.5;
|
|
5936
|
+
}
|
|
5937
|
+
|
|
5938
|
+
@media (prefers-color-scheme: dark) {
|
|
5939
|
+
.fr-dashboard {
|
|
5940
|
+
--fr-bg: #1f2937;
|
|
5941
|
+
--fr-bg-muted: #374151;
|
|
5942
|
+
--fr-bg-subtle: #4b5563;
|
|
5943
|
+
--fr-border: #4b5563;
|
|
5944
|
+
--fr-text: #f9fafb;
|
|
5945
|
+
--fr-text-muted: #9ca3af;
|
|
5946
|
+
}
|
|
5947
|
+
}
|
|
5948
|
+
|
|
5949
|
+
.fr-header {
|
|
5950
|
+
display: flex;
|
|
5951
|
+
justify-content: space-between;
|
|
5952
|
+
align-items: center;
|
|
5953
|
+
margin-bottom: 16px;
|
|
5954
|
+
padding-bottom: 12px;
|
|
5955
|
+
border-bottom: 1px solid var(--fr-border);
|
|
5956
|
+
}
|
|
5957
|
+
.fr-header__model {
|
|
5958
|
+
font-weight: 600;
|
|
5959
|
+
font-size: var(--fr-font-base);
|
|
5960
|
+
color: var(--fr-text);
|
|
5961
|
+
}
|
|
5962
|
+
.fr-header__time {
|
|
5963
|
+
font-weight: 600;
|
|
5964
|
+
font-size: var(--fr-font-base);
|
|
5965
|
+
color: var(--fr-text);
|
|
5966
|
+
}
|
|
5967
|
+
|
|
5968
|
+
.fr-banner {
|
|
5969
|
+
border-radius: 8px;
|
|
5970
|
+
padding: 12px 16px;
|
|
5971
|
+
margin-bottom: 20px;
|
|
5972
|
+
font-size: var(--fr-font-base);
|
|
5973
|
+
}
|
|
5974
|
+
.fr-banner--error {
|
|
5975
|
+
background: color-mix(in srgb, var(--fr-error) 10%, var(--fr-bg));
|
|
5976
|
+
border: 1px solid var(--fr-error);
|
|
5977
|
+
}
|
|
5978
|
+
.fr-banner--warning {
|
|
5979
|
+
background: color-mix(in srgb, var(--fr-warning) 10%, var(--fr-bg));
|
|
5980
|
+
border: 1px solid var(--fr-warning);
|
|
5981
|
+
}
|
|
5982
|
+
|
|
5983
|
+
.fr-cards {
|
|
5984
|
+
display: grid;
|
|
5985
|
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
5986
|
+
gap: 16px;
|
|
5987
|
+
margin-bottom: 24px;
|
|
5988
|
+
}
|
|
5989
|
+
|
|
5990
|
+
.fr-card {
|
|
5991
|
+
padding: 16px;
|
|
5992
|
+
background: var(--fr-bg-muted);
|
|
5993
|
+
border-radius: 8px;
|
|
5994
|
+
text-align: center;
|
|
5995
|
+
}
|
|
5996
|
+
.fr-card__label {
|
|
5997
|
+
font-size: var(--fr-font-sm);
|
|
5998
|
+
color: var(--fr-text-muted);
|
|
5999
|
+
margin-bottom: 4px;
|
|
6000
|
+
}
|
|
6001
|
+
.fr-card__value {
|
|
6002
|
+
font-size: var(--fr-font-lg);
|
|
6003
|
+
font-weight: 600;
|
|
6004
|
+
}
|
|
6005
|
+
.fr-card__sub {
|
|
6006
|
+
font-size: var(--fr-font-sm);
|
|
6007
|
+
color: var(--fr-text-muted);
|
|
6008
|
+
margin-top: 2px;
|
|
6009
|
+
}
|
|
6010
|
+
|
|
6011
|
+
.fr-badge {
|
|
6012
|
+
display: inline-flex;
|
|
6013
|
+
align-items: center;
|
|
6014
|
+
gap: 4px;
|
|
6015
|
+
padding: 4px 10px;
|
|
6016
|
+
border-radius: 4px;
|
|
6017
|
+
font-weight: 600;
|
|
6018
|
+
font-size: var(--fr-font-sm);
|
|
6019
|
+
}
|
|
6020
|
+
.fr-badge--completed { background: color-mix(in srgb, var(--fr-success) 15%, transparent); color: var(--fr-success); }
|
|
6021
|
+
.fr-badge--partial { background: color-mix(in srgb, var(--fr-warning) 15%, transparent); color: var(--fr-warning); }
|
|
6022
|
+
.fr-badge--cancelled { background: color-mix(in srgb, var(--fr-info) 15%, transparent); color: var(--fr-info); }
|
|
6023
|
+
.fr-badge--failed { background: color-mix(in srgb, var(--fr-error) 15%, transparent); color: var(--fr-error); }
|
|
6024
|
+
|
|
6025
|
+
.fr-section {
|
|
6026
|
+
margin-bottom: 24px;
|
|
6027
|
+
}
|
|
6028
|
+
.fr-section__title {
|
|
6029
|
+
font-size: var(--fr-font-base);
|
|
6030
|
+
font-weight: 500;
|
|
6031
|
+
color: var(--fr-text);
|
|
6032
|
+
margin-bottom: 8px;
|
|
6033
|
+
}
|
|
6034
|
+
|
|
6035
|
+
.fr-progress {
|
|
6036
|
+
background: var(--fr-border);
|
|
6037
|
+
border-radius: 4px;
|
|
6038
|
+
height: 20px;
|
|
6039
|
+
overflow: hidden;
|
|
6040
|
+
}
|
|
6041
|
+
.fr-progress__bar {
|
|
6042
|
+
background: var(--fr-primary);
|
|
6043
|
+
height: 100%;
|
|
6044
|
+
transition: width 0.3s ease;
|
|
6045
|
+
}
|
|
6046
|
+
.fr-progress__text {
|
|
6047
|
+
font-size: var(--fr-font-sm);
|
|
6048
|
+
color: var(--fr-text-muted);
|
|
6049
|
+
margin-top: 4px;
|
|
6050
|
+
}
|
|
6051
|
+
|
|
6052
|
+
.fr-progress__segments {
|
|
6053
|
+
display: flex;
|
|
6054
|
+
height: 100%;
|
|
6055
|
+
width: 100%;
|
|
6056
|
+
}
|
|
6057
|
+
.fr-progress-segment {
|
|
6058
|
+
height: 100%;
|
|
6059
|
+
min-width: 2px;
|
|
6060
|
+
border-right: 2px solid var(--fr-bg);
|
|
6061
|
+
cursor: pointer;
|
|
6062
|
+
}
|
|
6063
|
+
.fr-progress-segment:last-child {
|
|
6064
|
+
border-right: none;
|
|
6065
|
+
}
|
|
6066
|
+
.fr-progress-segment--filled {
|
|
6067
|
+
background: var(--fr-primary);
|
|
6068
|
+
}
|
|
6069
|
+
.fr-progress-segment--filled:hover {
|
|
6070
|
+
background: color-mix(in srgb, var(--fr-primary) 70%, white);
|
|
6071
|
+
}
|
|
6072
|
+
.fr-progress-segment--prefilled {
|
|
6073
|
+
background: #8b5cf6;
|
|
6074
|
+
}
|
|
6075
|
+
.fr-progress-segment--prefilled:hover {
|
|
6076
|
+
background: color-mix(in srgb, #8b5cf6 70%, white);
|
|
6077
|
+
}
|
|
6078
|
+
.fr-progress-segment--skipped {
|
|
6079
|
+
background: var(--fr-warning);
|
|
6080
|
+
}
|
|
6081
|
+
.fr-progress-segment--skipped:hover {
|
|
6082
|
+
background: color-mix(in srgb, var(--fr-warning) 70%, white);
|
|
6083
|
+
}
|
|
6084
|
+
.fr-progress-segment--empty {
|
|
6085
|
+
background: var(--fr-border);
|
|
6086
|
+
}
|
|
6087
|
+
|
|
6088
|
+
/* Gantt chart - each call on its own row */
|
|
6089
|
+
.fr-gantt {
|
|
6090
|
+
margin-bottom: 8px;
|
|
6091
|
+
}
|
|
6092
|
+
.fr-gantt__row {
|
|
6093
|
+
display: flex;
|
|
6094
|
+
align-items: center;
|
|
6095
|
+
height: 20px;
|
|
6096
|
+
margin-bottom: 3px;
|
|
6097
|
+
}
|
|
6098
|
+
.fr-gantt__label {
|
|
6099
|
+
width: 90px;
|
|
6100
|
+
flex-shrink: 0;
|
|
6101
|
+
font-size: 11px;
|
|
6102
|
+
color: var(--fr-text-muted);
|
|
6103
|
+
white-space: nowrap;
|
|
6104
|
+
overflow: hidden;
|
|
6105
|
+
text-overflow: ellipsis;
|
|
6106
|
+
padding-right: 8px;
|
|
6107
|
+
text-align: right;
|
|
6108
|
+
}
|
|
6109
|
+
.fr-gantt__track {
|
|
6110
|
+
flex: 1;
|
|
6111
|
+
background: var(--fr-bg-subtle);
|
|
6112
|
+
border-radius: 3px;
|
|
6113
|
+
height: 14px;
|
|
6114
|
+
position: relative;
|
|
6115
|
+
}
|
|
6116
|
+
.fr-gantt__bar {
|
|
6117
|
+
position: absolute;
|
|
6118
|
+
top: 2px;
|
|
6119
|
+
height: calc(100% - 4px);
|
|
6120
|
+
min-width: 6px;
|
|
6121
|
+
border-radius: 2px;
|
|
6122
|
+
cursor: pointer;
|
|
6123
|
+
}
|
|
6124
|
+
.fr-gantt__bar:hover {
|
|
6125
|
+
filter: brightness(1.15);
|
|
6126
|
+
}
|
|
6127
|
+
.fr-gantt__bar--llm {
|
|
6128
|
+
background: var(--fr-primary);
|
|
6129
|
+
}
|
|
6130
|
+
.fr-gantt__bar--tool {
|
|
6131
|
+
background: var(--fr-success);
|
|
6132
|
+
}
|
|
6133
|
+
.fr-gantt__legend {
|
|
6134
|
+
display: flex;
|
|
6135
|
+
gap: 16px;
|
|
6136
|
+
font-size: var(--fr-font-sm);
|
|
6137
|
+
color: var(--fr-text-muted);
|
|
6138
|
+
margin-top: 12px;
|
|
6139
|
+
padding-top: 8px;
|
|
6140
|
+
border-top: 1px solid var(--fr-border);
|
|
6141
|
+
}
|
|
6142
|
+
.fr-gantt__legend-item {
|
|
6143
|
+
display: flex;
|
|
6144
|
+
align-items: center;
|
|
6145
|
+
gap: 6px;
|
|
6146
|
+
}
|
|
6147
|
+
.fr-gantt__legend-dot {
|
|
6148
|
+
width: 10px;
|
|
6149
|
+
height: 10px;
|
|
6150
|
+
border-radius: 2px;
|
|
6151
|
+
}
|
|
6152
|
+
.fr-gantt__legend-dot--llm { background: var(--fr-primary); }
|
|
6153
|
+
.fr-gantt__legend-dot--tool { background: var(--fr-success); }
|
|
6154
|
+
|
|
6155
|
+
/* Tooltip container */
|
|
6156
|
+
.fr-tooltip {
|
|
6157
|
+
position: fixed;
|
|
6158
|
+
background: #1f2937;
|
|
6159
|
+
color: #f9fafb;
|
|
6160
|
+
padding: 8px 12px;
|
|
6161
|
+
border-radius: 4px;
|
|
6162
|
+
font-size: var(--fr-font-sm);
|
|
6163
|
+
white-space: pre-line;
|
|
6164
|
+
pointer-events: none;
|
|
6165
|
+
z-index: 1000;
|
|
6166
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
|
6167
|
+
opacity: 0;
|
|
6168
|
+
visibility: hidden;
|
|
6169
|
+
transition: opacity 0.05s ease-out, visibility 0.05s ease-out;
|
|
6170
|
+
}
|
|
6171
|
+
.fr-tooltip.visible {
|
|
6172
|
+
opacity: 1;
|
|
6173
|
+
visibility: visible;
|
|
6174
|
+
transition: opacity 0.2s ease-in, visibility 0.2s ease-in;
|
|
6175
|
+
}
|
|
6176
|
+
|
|
6177
|
+
.fr-table {
|
|
6178
|
+
width: 100%;
|
|
6179
|
+
border-collapse: collapse;
|
|
6180
|
+
font-size: var(--fr-font-sm);
|
|
6181
|
+
}
|
|
6182
|
+
.fr-table th {
|
|
6183
|
+
padding: 8px 12px;
|
|
6184
|
+
text-align: left;
|
|
6185
|
+
font-weight: 600;
|
|
6186
|
+
background: var(--fr-bg-subtle);
|
|
6187
|
+
}
|
|
6188
|
+
.fr-table th:not(:first-child) { text-align: center; }
|
|
6189
|
+
.fr-table td {
|
|
6190
|
+
padding: 8px 12px;
|
|
6191
|
+
border-bottom: 1px solid var(--fr-border);
|
|
6192
|
+
}
|
|
6193
|
+
.fr-table td:not(:first-child) { text-align: center; }
|
|
6194
|
+
|
|
6195
|
+
.fr-details {
|
|
6196
|
+
border: none;
|
|
6197
|
+
background: none;
|
|
6198
|
+
}
|
|
6199
|
+
.fr-details > summary {
|
|
6200
|
+
cursor: pointer;
|
|
6201
|
+
font-size: var(--fr-font-base);
|
|
6202
|
+
font-weight: 500;
|
|
6203
|
+
color: var(--fr-text);
|
|
6204
|
+
padding: 8px 0;
|
|
6205
|
+
list-style: none;
|
|
6206
|
+
}
|
|
6207
|
+
.fr-details > summary::-webkit-details-marker { display: none; }
|
|
6208
|
+
.fr-details > summary::before {
|
|
6209
|
+
content: '▶';
|
|
6210
|
+
display: inline-block;
|
|
6211
|
+
margin-right: 8px;
|
|
6212
|
+
transition: transform 0.2s;
|
|
6213
|
+
font-size: 11px;
|
|
6214
|
+
}
|
|
6215
|
+
.fr-details[open] > summary::before {
|
|
6216
|
+
transform: rotate(90deg);
|
|
6217
|
+
}
|
|
6218
|
+
.fr-details__content {
|
|
6219
|
+
background: var(--fr-bg-muted);
|
|
6220
|
+
border-radius: 8px;
|
|
6221
|
+
padding: 16px;
|
|
6222
|
+
margin-top: 8px;
|
|
6223
|
+
}
|
|
6224
|
+
|
|
6225
|
+
.fr-turn {
|
|
6226
|
+
margin-bottom: 8px;
|
|
6227
|
+
background: var(--fr-bg-muted);
|
|
6228
|
+
border-radius: 4px;
|
|
6229
|
+
}
|
|
6230
|
+
.fr-turn summary {
|
|
6231
|
+
cursor: pointer;
|
|
6232
|
+
padding: 12px;
|
|
6233
|
+
font-size: var(--fr-font-sm);
|
|
6234
|
+
list-style: none;
|
|
6235
|
+
}
|
|
6236
|
+
.fr-turn summary::-webkit-details-marker { display: none; }
|
|
6237
|
+
.fr-turn summary::before {
|
|
6238
|
+
content: '▶';
|
|
6239
|
+
display: inline-block;
|
|
6240
|
+
margin-right: 8px;
|
|
6241
|
+
transition: transform 0.2s;
|
|
6242
|
+
font-size: 11px;
|
|
6243
|
+
}
|
|
6244
|
+
.fr-turn[open] summary::before {
|
|
6245
|
+
transform: rotate(90deg);
|
|
6246
|
+
}
|
|
6247
|
+
.fr-turn__content {
|
|
6248
|
+
padding: 0 12px 12px;
|
|
6249
|
+
}
|
|
6250
|
+
.fr-turn__tools {
|
|
6251
|
+
margin: 0;
|
|
6252
|
+
padding-left: 20px;
|
|
6253
|
+
list-style: none;
|
|
6254
|
+
}
|
|
6255
|
+
.fr-turn__tool {
|
|
6256
|
+
margin: 4px 0;
|
|
6257
|
+
font-size: var(--fr-font-sm);
|
|
6258
|
+
color: var(--fr-text-muted);
|
|
6259
|
+
}
|
|
6260
|
+
.fr-turn__tool--error { color: var(--fr-error); }
|
|
6261
|
+
|
|
6262
|
+
.fr-turn__query {
|
|
6263
|
+
color: var(--fr-primary);
|
|
6264
|
+
font-style: italic;
|
|
6265
|
+
}
|
|
6266
|
+
|
|
6267
|
+
.fr-turn__patches {
|
|
6268
|
+
margin: 4px 0 8px 20px;
|
|
6269
|
+
padding: 8px 12px;
|
|
6270
|
+
background: var(--fr-bg-subtle);
|
|
6271
|
+
border-radius: 4px;
|
|
6272
|
+
font-size: var(--fr-font-sm);
|
|
6273
|
+
}
|
|
6274
|
+
.fr-turn__patch {
|
|
6275
|
+
margin: 4px 0;
|
|
6276
|
+
padding: 4px 0;
|
|
6277
|
+
border-bottom: 1px solid var(--fr-border);
|
|
6278
|
+
}
|
|
6279
|
+
.fr-turn__patch:last-child {
|
|
6280
|
+
border-bottom: none;
|
|
6281
|
+
margin-bottom: 0;
|
|
6282
|
+
padding-bottom: 0;
|
|
6283
|
+
}
|
|
6284
|
+
.fr-turn__patch-field {
|
|
6285
|
+
font-weight: 600;
|
|
6286
|
+
color: var(--fr-text);
|
|
6287
|
+
}
|
|
6288
|
+
.fr-turn__patch-op {
|
|
6289
|
+
font-size: 11px;
|
|
6290
|
+
padding: 1px 4px;
|
|
6291
|
+
border-radius: 2px;
|
|
6292
|
+
background: var(--fr-bg-muted);
|
|
6293
|
+
color: var(--fr-text-muted);
|
|
6294
|
+
margin-left: 6px;
|
|
6295
|
+
}
|
|
6296
|
+
.fr-turn__patch-value {
|
|
6297
|
+
display: block;
|
|
6298
|
+
margin-top: 2px;
|
|
6299
|
+
color: var(--fr-text-muted);
|
|
6300
|
+
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
|
|
6301
|
+
word-break: break-word;
|
|
6302
|
+
white-space: pre-wrap;
|
|
6303
|
+
max-height: 200px;
|
|
6304
|
+
overflow: auto;
|
|
6305
|
+
}
|
|
6306
|
+
.fr-turn__patch-value--skip {
|
|
6307
|
+
color: var(--fr-warning);
|
|
6308
|
+
font-style: italic;
|
|
6309
|
+
}
|
|
6310
|
+
.fr-turn__patch-value--clear {
|
|
6311
|
+
color: var(--fr-info);
|
|
6312
|
+
font-style: italic;
|
|
6313
|
+
}
|
|
6314
|
+
|
|
6315
|
+
.fr-raw {
|
|
6316
|
+
position: relative;
|
|
6317
|
+
}
|
|
6318
|
+
.fr-copy-btn {
|
|
6319
|
+
position: absolute;
|
|
6320
|
+
top: 8px;
|
|
6321
|
+
right: 8px;
|
|
6322
|
+
padding: 4px 8px;
|
|
6323
|
+
font-size: var(--fr-font-sm);
|
|
6324
|
+
background: var(--fr-bg-subtle);
|
|
6325
|
+
border: 1px solid var(--fr-border);
|
|
6326
|
+
border-radius: 4px;
|
|
6327
|
+
cursor: pointer;
|
|
6328
|
+
color: var(--fr-text-muted);
|
|
6329
|
+
transition: all 0.15s;
|
|
6330
|
+
}
|
|
6331
|
+
.fr-copy-btn:hover {
|
|
6332
|
+
background: var(--fr-border);
|
|
6333
|
+
color: var(--fr-text);
|
|
6334
|
+
}
|
|
6335
|
+
.fr-copy-btn:active {
|
|
6336
|
+
transform: scale(0.95);
|
|
6337
|
+
}
|
|
6338
|
+
|
|
6339
|
+
/* Scoped pre styles to override parent .tab-content pre */
|
|
6340
|
+
.fr-dashboard pre {
|
|
6341
|
+
background: var(--fr-bg-muted);
|
|
6342
|
+
color: var(--fr-text);
|
|
6343
|
+
padding: 1rem;
|
|
6344
|
+
border-radius: 6px;
|
|
6345
|
+
border: 1px solid var(--fr-border);
|
|
6346
|
+
overflow-x: auto;
|
|
6347
|
+
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
|
|
6348
|
+
font-size: 0.85rem;
|
|
6349
|
+
line-height: 1.5;
|
|
6350
|
+
margin: 0;
|
|
6351
|
+
}
|
|
6352
|
+
|
|
6353
|
+
/* Override syntax highlighting colors for dark mode compatibility */
|
|
6354
|
+
.fr-dashboard .syn-key { color: var(--fr-primary); }
|
|
6355
|
+
.fr-dashboard .syn-string { color: var(--fr-success); }
|
|
6356
|
+
.fr-dashboard .syn-number { color: var(--fr-primary); }
|
|
6357
|
+
.fr-dashboard .syn-bool { color: var(--fr-warning); }
|
|
6358
|
+
.fr-dashboard .syn-null { color: var(--fr-error); }
|
|
6359
|
+
|
|
6360
|
+
@media (max-width: 600px) {
|
|
6361
|
+
.fr-dashboard { padding: 12px; }
|
|
6362
|
+
.fr-cards { grid-template-columns: repeat(2, 1fr); gap: 12px; }
|
|
6363
|
+
.fr-card { padding: 12px; }
|
|
6364
|
+
.fr-card__value { font-size: 18px; }
|
|
6365
|
+
.fr-table { font-size: var(--fr-font-sm); }
|
|
6366
|
+
.fr-table th, .fr-table td { padding: 6px 8px; }
|
|
6367
|
+
}
|
|
6368
|
+
</style>
|
|
6369
|
+
`;
|
|
6370
|
+
/**
|
|
6371
|
+
* Render fill record content (dashboard-style visualization).
|
|
6372
|
+
* Uses CSS custom properties for theming with automatic dark mode support.
|
|
6373
|
+
* Mobile responsive with grid-based layout.
|
|
6374
|
+
*
|
|
6375
|
+
* @public Exported for testing and reuse.
|
|
6376
|
+
*/
|
|
6377
|
+
function renderFillRecordContent(record) {
|
|
6378
|
+
const { status, statusDetail, startedAt, durationMs, llm, formProgress, toolSummary, timeline } = record;
|
|
6379
|
+
const startDate = new Date(startedAt);
|
|
6380
|
+
const formattedDate = startDate.toLocaleDateString("en-US", {
|
|
6381
|
+
month: "short",
|
|
6382
|
+
day: "numeric",
|
|
6383
|
+
year: "numeric"
|
|
6384
|
+
});
|
|
6385
|
+
const formattedTime = startDate.toLocaleTimeString("en-US", {
|
|
6386
|
+
hour: "numeric",
|
|
6387
|
+
minute: "2-digit",
|
|
6388
|
+
hour12: true
|
|
6389
|
+
});
|
|
6390
|
+
const headerInfo = `
|
|
6391
|
+
<div class="fr-header">
|
|
6392
|
+
<div class="fr-header__model">${escapeHtml(llm.model)}</div>
|
|
6393
|
+
<div class="fr-header__time">${formattedDate} at ${formattedTime}</div>
|
|
6394
|
+
</div>
|
|
6395
|
+
`;
|
|
6396
|
+
let statusBanner = "";
|
|
6397
|
+
if (status !== "completed") {
|
|
6398
|
+
const bannerClass = status === "failed" ? "fr-banner--error" : "fr-banner--warning";
|
|
6399
|
+
const icon = status === "failed" ? "✕" : "⚠";
|
|
6400
|
+
const title = status === "failed" ? "FAILED" : status === "cancelled" ? "CANCELLED" : "PARTIAL";
|
|
6401
|
+
const msg = statusDetail ?? (status === "partial" ? "Did not complete all fields" : "");
|
|
6402
|
+
statusBanner = `<div class="fr-banner ${bannerClass}"><strong>${icon} ${title}${msg ? ":" : ""}</strong>${msg ? ` ${escapeHtml(msg)}` : ""}</div>`;
|
|
6403
|
+
}
|
|
6404
|
+
const totalTokens = llm.inputTokens + llm.outputTokens;
|
|
6405
|
+
const summaryCards = `
|
|
6406
|
+
<div class="fr-cards">
|
|
6407
|
+
<div class="fr-card">
|
|
6408
|
+
<div class="fr-card__label">Status</div>
|
|
6409
|
+
<div><span class="${`fr-badge fr-badge--${status}`}">${{
|
|
6410
|
+
completed: "✓",
|
|
6411
|
+
partial: "⚠",
|
|
6412
|
+
cancelled: "⊘",
|
|
6413
|
+
failed: "✕"
|
|
6414
|
+
}[status] ?? "?"} ${status.charAt(0).toUpperCase() + status.slice(1)}</span></div>
|
|
6415
|
+
</div>
|
|
6416
|
+
<div class="fr-card">
|
|
6417
|
+
<div class="fr-card__label">Duration</div>
|
|
6418
|
+
<div class="fr-card__value">${formatDuration(durationMs)}</div>
|
|
6419
|
+
</div>
|
|
6420
|
+
<div class="fr-card">
|
|
6421
|
+
<div class="fr-card__label">Turns</div>
|
|
6422
|
+
<div class="fr-card__value">${timeline.length}</div>
|
|
6423
|
+
</div>
|
|
6424
|
+
<div class="fr-card">
|
|
6425
|
+
<div class="fr-card__label">Tokens</div>
|
|
6426
|
+
<div class="fr-card__value">${formatTokens(totalTokens)}</div>
|
|
6427
|
+
<div class="fr-card__sub">${formatTokens(llm.inputTokens)} in / ${formatTokens(llm.outputTokens)} out</div>
|
|
6428
|
+
</div>
|
|
6429
|
+
</div>
|
|
6430
|
+
`;
|
|
6431
|
+
const fieldsMap = /* @__PURE__ */ new Map();
|
|
6432
|
+
for (const turn of timeline) for (const tc of turn.toolCalls) if (tc.tool === "fill_form" && tc.input.patches) {
|
|
6433
|
+
const patches = tc.input.patches;
|
|
6434
|
+
for (const patch of patches) if (patch.fieldId && patch.op) fieldsMap.set(patch.fieldId, {
|
|
6435
|
+
fieldId: patch.fieldId,
|
|
6436
|
+
op: patch.op,
|
|
6437
|
+
turnNumber: turn.turnNumber
|
|
6438
|
+
});
|
|
6439
|
+
}
|
|
6440
|
+
const fieldsFilled = Array.from(fieldsMap.values());
|
|
6441
|
+
const totalFields = formProgress.totalFields;
|
|
6442
|
+
const filledFields = formProgress.filledFields;
|
|
6443
|
+
const skippedFields = formProgress.skippedFields;
|
|
6444
|
+
const abortedFields = formProgress.abortedFields ?? 0;
|
|
6445
|
+
const progressPercent = totalFields > 0 ? Math.round(filledFields / totalFields * 100) : 0;
|
|
6446
|
+
const segmentWidth = totalFields > 0 ? 100 / totalFields : 0;
|
|
6447
|
+
const aiFilledFields = fieldsFilled.filter((f) => f.op !== "skip_field" && f.op !== "abort_field");
|
|
6448
|
+
const aiFilledSegmentsHtml = aiFilledFields.map((f) => {
|
|
6449
|
+
const opLabel = f.op.replace(/_/g, " ");
|
|
6450
|
+
return `<div class="fr-progress-segment fr-progress-segment--filled" style="width: ${segmentWidth}%" data-tooltip="${escapeHtml(`${f.fieldId}\n${opLabel}\nTurn ${f.turnNumber}`)}" onmouseenter="frShowTip(this)" onmouseleave="frHideTip()"></div>`;
|
|
6451
|
+
}).join("");
|
|
6452
|
+
const prefilledCount = Math.max(0, filledFields - aiFilledFields.length);
|
|
6453
|
+
const prefilledSegmentsHtml = prefilledCount > 0 ? `<div class="fr-progress-segment fr-progress-segment--prefilled" style="width: ${segmentWidth * prefilledCount}%" data-tooltip="Pre-filled (${prefilledCount} field${prefilledCount !== 1 ? "s" : ""})" onmouseenter="frShowTip(this)" onmouseleave="frHideTip()"></div>` : "";
|
|
6454
|
+
const skippedSegmentsHtml = fieldsFilled.filter((f) => f.op === "skip_field" || f.op === "abort_field").map((f) => {
|
|
6455
|
+
const opLabel = f.op === "skip_field" ? "skipped" : "aborted";
|
|
6456
|
+
return `<div class="fr-progress-segment fr-progress-segment--skipped" style="width: ${segmentWidth}%" data-tooltip="${escapeHtml(`${f.fieldId}\n${opLabel}\nTurn ${f.turnNumber}`)}" onmouseenter="frShowTip(this)" onmouseleave="frHideTip()"></div>`;
|
|
6457
|
+
}).join("");
|
|
6458
|
+
const unfilledCount = totalFields - filledFields - skippedFields - abortedFields;
|
|
6459
|
+
const unfilledSegmentsHtml = unfilledCount > 0 ? `<div class="fr-progress-segment fr-progress-segment--empty" style="width: ${segmentWidth * unfilledCount}%"></div>` : "";
|
|
6460
|
+
const progressDetails = [];
|
|
6461
|
+
if (prefilledCount > 0) progressDetails.push(`${prefilledCount} pre-filled`);
|
|
6462
|
+
if (skippedFields > 0) progressDetails.push(`${skippedFields} skipped`);
|
|
6463
|
+
const progressBar = `
|
|
6464
|
+
<div class="fr-section">
|
|
6465
|
+
<div class="fr-section__title">Progress</div>
|
|
6466
|
+
<div class="fr-progress">
|
|
6467
|
+
<div class="fr-progress__segments">
|
|
6468
|
+
${prefilledSegmentsHtml}${aiFilledSegmentsHtml}${skippedSegmentsHtml}${unfilledSegmentsHtml}
|
|
6469
|
+
</div>
|
|
6470
|
+
</div>
|
|
6471
|
+
<div class="fr-progress__text">
|
|
6472
|
+
${filledFields}/${totalFields} fields filled (${progressPercent}%)${progressDetails.length > 0 ? ` • ${progressDetails.join(" • ")}` : ""}
|
|
6473
|
+
</div>
|
|
6474
|
+
</div>
|
|
6475
|
+
`;
|
|
6476
|
+
const totalMs = durationMs;
|
|
6477
|
+
const llmCallCount = llm.totalCalls;
|
|
6478
|
+
const toolCallCount = toolSummary.totalCalls;
|
|
6479
|
+
const timelineEvents = [];
|
|
6480
|
+
let currentTime = 0;
|
|
6481
|
+
for (const turn of timeline) {
|
|
6482
|
+
const toolTimeInTurn = turn.toolCalls.reduce((sum, tc) => sum + tc.durationMs, 0);
|
|
6483
|
+
const llmTimeInTurn = Math.max(0, turn.durationMs - toolTimeInTurn);
|
|
6484
|
+
if (llmTimeInTurn > 0) timelineEvents.push({
|
|
6485
|
+
type: "llm",
|
|
6486
|
+
startMs: currentTime,
|
|
6487
|
+
durationMs: llmTimeInTurn,
|
|
6488
|
+
turnNumber: turn.turnNumber,
|
|
6489
|
+
label: `Turn ${turn.turnNumber}`,
|
|
6490
|
+
tokens: {
|
|
6491
|
+
input: turn.tokens.input,
|
|
6492
|
+
output: turn.tokens.output,
|
|
6493
|
+
total: turn.tokens.input + turn.tokens.output
|
|
6494
|
+
}
|
|
6495
|
+
});
|
|
6496
|
+
currentTime += llmTimeInTurn;
|
|
6497
|
+
for (const tc of turn.toolCalls) {
|
|
6498
|
+
timelineEvents.push({
|
|
6499
|
+
type: "tool",
|
|
6500
|
+
startMs: currentTime,
|
|
6501
|
+
durationMs: tc.durationMs,
|
|
6502
|
+
turnNumber: turn.turnNumber,
|
|
6503
|
+
label: tc.tool
|
|
6504
|
+
});
|
|
6505
|
+
currentTime += tc.durationMs;
|
|
6506
|
+
}
|
|
6507
|
+
}
|
|
6508
|
+
const ganttRowsHtml = timelineEvents.map((e) => {
|
|
6509
|
+
const leftPct = totalMs > 0 ? e.startMs / totalMs * 100 : 0;
|
|
6510
|
+
const widthPct = totalMs > 0 ? e.durationMs / totalMs * 100 : 0;
|
|
6511
|
+
const barClass = e.type === "llm" ? "fr-gantt__bar--llm" : "fr-gantt__bar--tool";
|
|
6512
|
+
const tooltip = e.type === "llm" ? `${e.label} ${formatDuration(e.durationMs)} ${formatTokens(e.tokens?.total ?? 0)} tokens (${formatTokens(e.tokens?.input ?? 0)} in / ${formatTokens(e.tokens?.output ?? 0)} out)` : `${e.label} ${formatDuration(e.durationMs)} Turn ${e.turnNumber}`;
|
|
6513
|
+
return `
|
|
6514
|
+
<div class="fr-gantt__row">
|
|
6515
|
+
<div class="fr-gantt__label">${escapeHtml(e.label)}</div>
|
|
6516
|
+
<div class="fr-gantt__track">
|
|
6517
|
+
<div class="fr-gantt__bar ${barClass}" style="left: ${leftPct}%; width: ${widthPct}%" data-tooltip="${tooltip}" onmouseenter="frShowTip(this)" onmouseleave="frHideTip()"></div>
|
|
6518
|
+
</div>
|
|
6519
|
+
</div>`;
|
|
6520
|
+
}).join("");
|
|
6521
|
+
const llmTotalMs = timelineEvents.filter((e) => e.type === "llm").reduce((sum, e) => sum + e.durationMs, 0);
|
|
6522
|
+
const toolTotalMs = timelineEvents.filter((e) => e.type === "tool").reduce((sum, e) => sum + e.durationMs, 0);
|
|
6523
|
+
const timingSection = `
|
|
6524
|
+
<details class="fr-details fr-section" open>
|
|
6525
|
+
<summary>Timeline (${formatDuration(totalMs)} total)</summary>
|
|
6526
|
+
<div class="fr-details__content">
|
|
6527
|
+
<div class="fr-gantt">
|
|
6528
|
+
${ganttRowsHtml}
|
|
6529
|
+
<div class="fr-gantt__legend">
|
|
6530
|
+
<div class="fr-gantt__legend-item">
|
|
6531
|
+
<div class="fr-gantt__legend-dot fr-gantt__legend-dot--llm"></div>
|
|
6532
|
+
<span>LLM (${llmCallCount} call${llmCallCount !== 1 ? "s" : ""}, ${formatDuration(llmTotalMs)})</span>
|
|
6533
|
+
</div>
|
|
6534
|
+
<div class="fr-gantt__legend-item">
|
|
6535
|
+
<div class="fr-gantt__legend-dot fr-gantt__legend-dot--tool"></div>
|
|
6536
|
+
<span>Tools (${toolCallCount} call${toolCallCount !== 1 ? "s" : ""}, ${formatDuration(toolTotalMs)})</span>
|
|
6537
|
+
</div>
|
|
6538
|
+
</div>
|
|
6539
|
+
</div>
|
|
6540
|
+
</div>
|
|
6541
|
+
</details>
|
|
6542
|
+
`;
|
|
6543
|
+
let toolSection = "";
|
|
6544
|
+
if (toolSummary.byTool.length > 0) toolSection = `
|
|
6545
|
+
<details class="fr-details fr-section" open>
|
|
6546
|
+
<summary>Tool Summary</summary>
|
|
6547
|
+
<div style="overflow-x: auto; margin-top: 8px;">
|
|
6548
|
+
<table class="fr-table">
|
|
6549
|
+
<thead><tr><th>Tool</th><th>Calls</th><th>Success</th><th>Avg</th><th>p95</th></tr></thead>
|
|
6550
|
+
<tbody>${toolSummary.byTool.map((t) => `
|
|
6551
|
+
<tr>
|
|
6552
|
+
<td>${escapeHtml(t.toolName)}</td>
|
|
6553
|
+
<td>${t.callCount}</td>
|
|
6554
|
+
<td>${t.successCount === t.callCount ? "100%" : `${Math.round(t.successCount / t.callCount * 100)}%`}</td>
|
|
6555
|
+
<td>${formatDuration(t.timing.avgMs)}</td>
|
|
6556
|
+
<td>${formatDuration(t.timing.p95Ms)}</td>
|
|
6557
|
+
</tr>
|
|
6558
|
+
`).join("")}</tbody>
|
|
6559
|
+
</table>
|
|
6560
|
+
</div>
|
|
6561
|
+
</details>
|
|
6562
|
+
`;
|
|
6563
|
+
let timelineSection = "";
|
|
6564
|
+
if (timeline.length > 0) {
|
|
6565
|
+
const timelineItems = timeline.map((turn) => {
|
|
6566
|
+
const turnTokens = turn.tokens.input + turn.tokens.output;
|
|
6567
|
+
const toolCallsList = turn.toolCalls.map((tc) => renderToolCall(tc)).join("");
|
|
6568
|
+
const patchInfo = turn.patchesApplied > 0 ? ` • ${turn.patchesApplied} patches` : "";
|
|
6569
|
+
const rejectedInfo = turn.patchesRejected > 0 ? ` <span style="color: var(--fr-error)">(${turn.patchesRejected} rejected)</span>` : "";
|
|
6570
|
+
return `
|
|
6571
|
+
<details class="fr-turn">
|
|
6572
|
+
<summary><strong>Turn ${turn.turnNumber}</strong> • Order ${turn.order} • ${formatDuration(turn.durationMs)} • ${formatTokens(turnTokens)} tokens${patchInfo}${rejectedInfo}</summary>
|
|
6573
|
+
<div class="fr-turn__content">
|
|
6574
|
+
${turn.toolCalls.length > 0 ? `<ul class="fr-turn__tools">${toolCallsList}</ul>` : "<span class=\"fr-turn__tool\">No tool calls</span>"}
|
|
6575
|
+
</div>
|
|
6576
|
+
</details>
|
|
6577
|
+
`;
|
|
6578
|
+
}).join("");
|
|
6579
|
+
timelineSection = `
|
|
6580
|
+
<details class="fr-details fr-section">
|
|
6581
|
+
<summary>Turn Details (${timeline.length} turns)</summary>
|
|
6582
|
+
<div style="margin-top: 8px;">${timelineItems}</div>
|
|
6583
|
+
</details>
|
|
6584
|
+
`;
|
|
6585
|
+
}
|
|
6586
|
+
const rawSection = `
|
|
6587
|
+
<details class="fr-details fr-section">
|
|
6588
|
+
<summary>Raw YAML</summary>
|
|
6589
|
+
<div class="fr-raw" style="margin-top: 8px;">
|
|
6590
|
+
<button class="fr-copy-btn" onclick="frCopyYaml(this)">Copy</button>
|
|
6591
|
+
${renderYamlContent(YAML.stringify(record, { lineWidth: 0 }))}
|
|
6592
|
+
</div>
|
|
6593
|
+
</details>
|
|
6594
|
+
`;
|
|
6595
|
+
return `
|
|
6596
|
+
${FILL_RECORD_STYLES}
|
|
6597
|
+
<div id="fr-tooltip" class="fr-tooltip"></div>
|
|
6598
|
+
<div class="fr-dashboard">
|
|
6599
|
+
${headerInfo}
|
|
6600
|
+
${statusBanner}
|
|
6601
|
+
${summaryCards}
|
|
6602
|
+
${progressBar}
|
|
6603
|
+
${timingSection}
|
|
6604
|
+
${toolSection}
|
|
6605
|
+
${timelineSection}
|
|
6606
|
+
${rawSection}
|
|
6607
|
+
</div>
|
|
6608
|
+
`;
|
|
6609
|
+
}
|
|
5659
6610
|
|
|
5660
6611
|
//#endregion
|
|
5661
6612
|
//#region src/cli/commands/render.ts
|
|
@@ -5779,7 +6730,7 @@ function registerResearchCommand(program) {
|
|
|
5779
6730
|
const modelId = options.model;
|
|
5780
6731
|
const { provider, model: modelName } = parseModelIdForDisplay(modelId);
|
|
5781
6732
|
if (!hasWebSearchSupport(provider)) {
|
|
5782
|
-
const webSearchProviders = Object.entries(WEB_SEARCH_CONFIG).filter(([, config]) => config.supported).map(([p
|
|
6733
|
+
const webSearchProviders = Object.entries(WEB_SEARCH_CONFIG).filter(([, config]) => config.supported).map(([p]) => p);
|
|
5783
6734
|
logError(`Model "${modelId}" does not support web search.`);
|
|
5784
6735
|
console.log("");
|
|
5785
6736
|
console.log(pc.yellow("Research forms require web search capabilities."));
|
|
@@ -5827,6 +6778,7 @@ function registerResearchCommand(program) {
|
|
|
5827
6778
|
model: modelId,
|
|
5828
6779
|
enableWebSearch: true,
|
|
5829
6780
|
captureWireFormat: false,
|
|
6781
|
+
recordFill: false,
|
|
5830
6782
|
maxTurnsTotal: maxTurns,
|
|
5831
6783
|
maxPatchesPerTurn,
|
|
5832
6784
|
maxIssuesPerTurn,
|
|
@@ -5849,10 +6801,10 @@ function registerResearchCommand(program) {
|
|
|
5849
6801
|
console.log(` ${formPath} ${pc.dim("(filled markform source)")}`);
|
|
5850
6802
|
console.log(` ${schemaPath} ${pc.dim("(JSON Schema)")}`);
|
|
5851
6803
|
if (options.transcript && result.transcript) {
|
|
5852
|
-
const { serializeSession
|
|
6804
|
+
const { serializeSession } = await import("./session-ZHBi3LVQ.mjs");
|
|
5853
6805
|
const transcriptPath = outputPath.replace(/\.form\.md$/, ".session.yaml");
|
|
5854
|
-
const { writeFile
|
|
5855
|
-
await writeFile
|
|
6806
|
+
const { writeFile } = await import("./shared-BTR35aMz.mjs");
|
|
6807
|
+
await writeFile(transcriptPath, serializeSession(result.transcript));
|
|
5856
6808
|
logInfo(ctx, `Transcript: ${transcriptPath}`);
|
|
5857
6809
|
}
|
|
5858
6810
|
logTiming(ctx, "Research fill", Date.now() - startTime);
|
|
@@ -5927,7 +6879,6 @@ function computeFieldStats(form, fields) {
|
|
|
5927
6879
|
case "aborted":
|
|
5928
6880
|
aborted++;
|
|
5929
6881
|
break;
|
|
5930
|
-
case "unanswered":
|
|
5931
6882
|
default:
|
|
5932
6883
|
unanswered++;
|
|
5933
6884
|
break;
|
|
@@ -6094,7 +7045,6 @@ function formatPriority(priority, useColors) {
|
|
|
6094
7045
|
case 2: return pc.yellow(label);
|
|
6095
7046
|
case 3: return pc.cyan(label);
|
|
6096
7047
|
case 4: return pc.blue(label);
|
|
6097
|
-
case 5:
|
|
6098
7048
|
default: return pc.dim(label);
|
|
6099
7049
|
}
|
|
6100
7050
|
}
|
|
@@ -6258,4 +7208,4 @@ async function runCli() {
|
|
|
6258
7208
|
|
|
6259
7209
|
//#endregion
|
|
6260
7210
|
export { runCli as t };
|
|
6261
|
-
//# sourceMappingURL=cli-
|
|
7211
|
+
//# sourceMappingURL=cli-FFMoEhFS.mjs.map
|