markform 0.1.24 → 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 +54 -31
- package/dist/ai-sdk.d.mts +1 -1
- package/dist/ai-sdk.mjs +2 -2
- package/dist/bin.mjs +1 -1
- package/dist/{cli-B1DhFYBS.mjs → cli-B1T8kMFt.mjs} +85 -40
- package/dist/cli-B1T8kMFt.mjs.map +1 -0
- package/dist/cli.mjs +1 -1
- package/dist/{coreTypes-GxzWNXap.d.mts → coreTypes-CxpqKpBA.d.mts} +45 -2
- package/dist/{coreTypes-CctFK6uE.mjs → coreTypes-DIv9Aabl.mjs} +19 -5
- package/dist/coreTypes-DIv9Aabl.mjs.map +1 -0
- package/dist/{fillRecord-DeqI2pQ5.d.mts → fillRecord-V3vlyobd.d.mts} +5 -1
- package/dist/{fillRecordRenderer-VBQ2vwPV.mjs → fillRecordRenderer-BqRPHPmE.mjs} +47 -15
- package/dist/fillRecordRenderer-BqRPHPmE.mjs.map +1 -0
- package/dist/index.d.mts +32 -4
- package/dist/index.mjs +4 -4
- package/dist/{prompts-BCnYaH4_.mjs → prompts-DaPKumGY.mjs} +114 -11
- package/dist/prompts-DaPKumGY.mjs.map +1 -0
- package/dist/render.d.mts +2 -2
- package/dist/render.mjs +1 -1
- package/dist/{session-BLjN3BkJ.mjs → session-BW9jtYNV.mjs} +2 -2
- package/dist/{session-BLjN3BkJ.mjs.map → session-BW9jtYNV.mjs.map} +1 -1
- package/dist/{session-D7C7IlEv.mjs → session-DHyTMP67.mjs} +1 -1
- package/dist/{shared-DtorFV21.mjs → shared-BLh342F5.mjs} +1 -1
- package/dist/{shared-CuSRYcIB.mjs → shared-BszoSkAO.mjs} +8 -8
- package/dist/{shared-CuSRYcIB.mjs.map → shared-BszoSkAO.mjs.map} +1 -1
- package/dist/{src-C5OWf1dL.mjs → src-DrXmaOWl.mjs} +155 -27
- package/dist/src-DrXmaOWl.mjs.map +1 -0
- package/docs/markform-apis.md +19 -7
- package/docs/markform-reference.md +247 -178
- package/docs/markform-spec.md +81 -33
- package/docs/skill/SKILL.md +62 -20
- package/examples/markform-demo-playbook.md +342 -0
- package/examples/parallel/parallel-research.form.md +2 -6
- package/examples/simple/simple-mock-filled.report.md +2 -2
- package/examples/simple/simple-skipped-filled.report.md +2 -2
- package/examples/twitter-thread/twitter-thread.form.md +5 -5
- package/package.json +1 -1
- package/dist/cli-B1DhFYBS.mjs.map +0 -1
- package/dist/coreTypes-CctFK6uE.mjs.map +0 -1
- package/dist/fillRecordRenderer-VBQ2vwPV.mjs.map +0 -1
- package/dist/prompts-BCnYaH4_.mjs.map +0 -1
- package/dist/src-C5OWf1dL.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
package/dist/render.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
import { At as ParsedForm } from "./coreTypes-
|
|
3
|
-
import { r as FillRecord } from "./fillRecord-
|
|
2
|
+
import { At as ParsedForm } from "./coreTypes-CxpqKpBA.mjs";
|
|
3
|
+
import { r as FillRecord } from "./fillRecord-V3vlyobd.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/render/renderUtils.d.ts
|
|
6
6
|
/**
|
package/dist/render.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
import { a as renderJsonContent, c as renderViewContent, d as formatDuration, f as formatTokens, l as renderYamlContent, n as FILL_RECORD_STYLES, o as renderMarkdownContent, r as renderFillRecordContent, s as renderSourceContent, t as FILL_RECORD_SCRIPTS, u as escapeHtml } from "./fillRecordRenderer-
|
|
2
|
+
import { a as renderJsonContent, c as renderViewContent, d as formatDuration, f as formatTokens, l as renderYamlContent, n as FILL_RECORD_STYLES, o as renderMarkdownContent, r as renderFillRecordContent, s as renderSourceContent, t as FILL_RECORD_SCRIPTS, u as escapeHtml } from "./fillRecordRenderer-BqRPHPmE.mjs";
|
|
3
3
|
|
|
4
4
|
export { FILL_RECORD_SCRIPTS, FILL_RECORD_STYLES, escapeHtml, formatDuration, formatTokens, renderFillRecordContent, renderJsonContent, renderMarkdownContent, renderSourceContent, renderViewContent, renderYamlContent };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { G as SessionTranscriptSchema } from "./coreTypes-
|
|
2
|
+
import { G as SessionTranscriptSchema } from "./coreTypes-DIv9Aabl.mjs";
|
|
3
3
|
import YAML from "yaml";
|
|
4
4
|
|
|
5
5
|
//#region src/engine/session.ts
|
|
@@ -113,4 +113,4 @@ function toSnakeCaseDeep(obj, preserveKeys = false) {
|
|
|
113
113
|
|
|
114
114
|
//#endregion
|
|
115
115
|
export { serializeSession as n, parseSession as t };
|
|
116
|
-
//# sourceMappingURL=session-
|
|
116
|
+
//# sourceMappingURL=session-BW9jtYNV.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-
|
|
1
|
+
{"version":3,"file":"session-BW9jtYNV.mjs","names":[],"sources":["../src/engine/session.ts"],"sourcesContent":["/**\n * Session module - parsing and serializing session transcripts.\n *\n * Session transcripts are used for golden testing and session replay.\n * They capture the full interaction between the harness and agent.\n */\nimport YAML from 'yaml';\nimport type { SessionTranscript } from './coreTypes';\nimport { SessionTranscriptSchema } from './coreTypes';\n\n/**\n * Parse a session transcript from YAML string.\n *\n * Converts snake_case keys to camelCase for TypeScript consumption.\n *\n * @param yaml - YAML string containing session transcript\n * @returns Parsed and validated SessionTranscript\n * @throws Error if YAML is invalid or doesn't match schema\n */\nexport function parseSession(yaml: string): SessionTranscript {\n // Parse YAML\n let raw: unknown;\n try {\n raw = YAML.parse(yaml);\n } catch (err) {\n throw new Error(\n `Failed to parse session YAML: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Convert snake_case to camelCase\n const converted = toCamelCaseDeep(raw);\n\n // Validate against schema\n const result = SessionTranscriptSchema.safeParse(converted);\n if (!result.success) {\n const errors = result.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join('; ');\n throw new Error(`Invalid session transcript: ${errors}`);\n }\n\n return result.data;\n}\n\n/**\n * Serialize a session transcript to YAML string.\n *\n * Converts camelCase keys to snake_case for YAML output.\n *\n * @param session - Session transcript to serialize\n * @returns YAML string\n */\nexport function serializeSession(session: SessionTranscript): string {\n // Convert camelCase to snake_case\n const snakeCased = toSnakeCaseDeep(session);\n\n // Serialize to YAML\n return YAML.stringify(snakeCased, {\n indent: 2,\n lineWidth: 0, // Don't wrap lines\n });\n}\n\n// =============================================================================\n// Key Case Conversion Helpers\n// =============================================================================\n\n/**\n * Convert a string from snake_case to camelCase.\n */\nfunction snakeToCamel(str: string): string {\n return str.replace(/_([a-z])/g, (_match, letter: string) => letter.toUpperCase());\n}\n\n/**\n * Convert a string from camelCase to snake_case.\n */\nfunction camelToSnake(str: string): string {\n return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\n/**\n * Recursively convert all object keys from snake_case to camelCase.\n *\n * Preserves keys that are user-defined identifiers (like option IDs in\n * checkboxes `values` objects).\n *\n * @param obj - Object to convert\n * @param preserveKeys - If true, don't convert keys in this object (but still recurse into values)\n */\nfunction toCamelCaseDeep(obj: unknown, preserveKeys = false): unknown {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n // Pass preserveKeys through to array items\n return obj.map((item) => toCamelCaseDeep(item, preserveKeys));\n }\n\n if (typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n const record = obj as Record<string, unknown>;\n\n for (const [key, value] of Object.entries(record)) {\n // Determine the key to use\n const resultKey = preserveKeys ? key : snakeToCamel(key);\n\n // Check if this is a \"value\" key in a set_checkboxes patch\n // The \"value\" object contains option IDs as keys which should be preserved\n const isCheckboxValues = key === 'value' && record.op === 'set_checkboxes';\n\n // Check if this is a \"value\" key in a set_table patch\n // The \"value\" array contains objects with column IDs as keys which should be preserved\n const isTableRows = key === 'value' && record.op === 'set_table';\n\n // Check if this is a \"tools\" key in a wire format request\n // Tool names are identifiers that should be preserved\n const isWireTools = key === 'tools';\n\n result[resultKey] = toCamelCaseDeep(value, isCheckboxValues || isTableRows || isWireTools);\n }\n return result;\n }\n\n return obj;\n}\n\n/**\n * Recursively convert all object keys from camelCase to snake_case.\n *\n * Preserves keys that are user-defined identifiers (like option IDs in\n * checkboxes `values` objects).\n *\n * @param obj - Object to convert\n * @param preserveKeys - If true, don't convert keys in this object (but still recurse into values)\n */\nfunction toSnakeCaseDeep(obj: unknown, preserveKeys = false): unknown {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n // Pass preserveKeys through to array items\n return obj.map((item) => toSnakeCaseDeep(item, preserveKeys));\n }\n\n if (typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n const record = obj as Record<string, unknown>;\n\n for (const [key, value] of Object.entries(record)) {\n // Determine the key to use\n const resultKey = preserveKeys ? key : camelToSnake(key);\n\n // Check if this is a \"value\" key in a set_checkboxes patch\n // The \"value\" object contains option IDs as keys which should be preserved\n const isCheckboxValues = key === 'value' && record.op === 'set_checkboxes';\n\n // Check if this is a \"value\" key in a set_table patch\n // The \"value\" array contains objects with column IDs as keys which should be preserved\n const isTableRows = key === 'value' && record.op === 'set_table';\n\n // Check if this is a \"tools\" key in a wire format request\n // Tool names are identifiers that should be preserved\n const isWireTools = key === 'tools';\n\n result[resultKey] = toSnakeCaseDeep(value, isCheckboxValues || isTableRows || isWireTools);\n }\n return result;\n }\n\n return obj;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,aAAa,MAAiC;CAE5D,IAAI;AACJ,KAAI;AACF,QAAM,KAAK,MAAM,KAAK;UACf,KAAK;AACZ,QAAM,IAAI,MACR,iCAAiC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAClF;;CAIH,MAAM,YAAY,gBAAgB,IAAI;CAGtC,MAAM,SAAS,wBAAwB,UAAU,UAAU;AAC3D,KAAI,CAAC,OAAO,SAAS;EACnB,MAAM,SAAS,OAAO,MAAM,OAAO,KAAK,MAAM,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,KAAK,KAAK;AAC7F,QAAM,IAAI,MAAM,+BAA+B,SAAS;;AAG1D,QAAO,OAAO;;;;;;;;;;AAWhB,SAAgB,iBAAiB,SAAoC;CAEnE,MAAM,aAAa,gBAAgB,QAAQ;AAG3C,QAAO,KAAK,UAAU,YAAY;EAChC,QAAQ;EACR,WAAW;EACZ,CAAC;;;;;AAUJ,SAAS,aAAa,KAAqB;AACzC,QAAO,IAAI,QAAQ,cAAc,QAAQ,WAAmB,OAAO,aAAa,CAAC;;;;;AAMnF,SAAS,aAAa,KAAqB;AACzC,QAAO,IAAI,QAAQ,WAAW,WAAW,IAAI,OAAO,aAAa,GAAG;;;;;;;;;;;AAYtE,SAAS,gBAAgB,KAAc,eAAe,OAAgB;AACpE,KAAI,QAAQ,QAAQ,QAAQ,OAC1B,QAAO;AAGT,KAAI,MAAM,QAAQ,IAAI,CAEpB,QAAO,IAAI,KAAK,SAAS,gBAAgB,MAAM,aAAa,CAAC;AAG/D,KAAI,OAAO,QAAQ,UAAU;EAC3B,MAAM,SAAkC,EAAE;EAC1C,MAAM,SAAS;AAEf,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,EAAE;GAEjD,MAAM,YAAY,eAAe,MAAM,aAAa,IAAI;GAIxD,MAAM,mBAAmB,QAAQ,WAAW,OAAO,OAAO;GAI1D,MAAM,cAAc,QAAQ,WAAW,OAAO,OAAO;AAMrD,UAAO,aAAa,gBAAgB,OAAO,oBAAoB,eAF3C,QAAQ,QAE8D;;AAE5F,SAAO;;AAGT,QAAO;;;;;;;;;;;AAYT,SAAS,gBAAgB,KAAc,eAAe,OAAgB;AACpE,KAAI,QAAQ,QAAQ,QAAQ,OAC1B,QAAO;AAGT,KAAI,MAAM,QAAQ,IAAI,CAEpB,QAAO,IAAI,KAAK,SAAS,gBAAgB,MAAM,aAAa,CAAC;AAG/D,KAAI,OAAO,QAAQ,UAAU;EAC3B,MAAM,SAAkC,EAAE;EAC1C,MAAM,SAAS;AAEf,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,EAAE;GAEjD,MAAM,YAAY,eAAe,MAAM,aAAa,IAAI;GAIxD,MAAM,mBAAmB,QAAQ,WAAW,OAAO,OAAO;GAI1D,MAAM,cAAc,QAAQ,WAAW,OAAO,OAAO;AAMrD,UAAO,aAAa,gBAAgB,OAAO,oBAAoB,eAF3C,QAAQ,QAE8D;;AAE5F,SAAO;;AAGT,QAAO"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
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, 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 { _ 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, n as createSpinner, o as getCommandContext, p as logWarn, r as ensureFormsDir, s as logDryRun, t as OUTPUT_FORMATS, u as logSuccess } from "./shared-BszoSkAO.mjs";
|
|
3
3
|
|
|
4
4
|
export { writeFile };
|
|
@@ -174,20 +174,20 @@ function formatOutput(ctx, data, consoleFormatter) {
|
|
|
174
174
|
* Log a dry-run message.
|
|
175
175
|
*/
|
|
176
176
|
function logDryRun(message, details) {
|
|
177
|
-
console.
|
|
178
|
-
if (details) console.
|
|
177
|
+
console.error(pc.yellow(`[DRY RUN] ${message}`));
|
|
178
|
+
if (details) console.error(pc.dim(JSON.stringify(details, null, 2)));
|
|
179
179
|
}
|
|
180
180
|
/**
|
|
181
181
|
* Log a verbose message (only shown if --verbose is set).
|
|
182
182
|
*/
|
|
183
183
|
function logVerbose(ctx, message) {
|
|
184
|
-
if (ctx.verbose) console.
|
|
184
|
+
if (ctx.verbose) console.error(pc.dim(message));
|
|
185
185
|
}
|
|
186
186
|
/**
|
|
187
187
|
* Log an info message (hidden if --quiet is set).
|
|
188
188
|
*/
|
|
189
189
|
function logInfo(ctx, message) {
|
|
190
|
-
if (!ctx.quiet) console.
|
|
190
|
+
if (!ctx.quiet) console.error(message);
|
|
191
191
|
}
|
|
192
192
|
/**
|
|
193
193
|
* Log an error message (always shown).
|
|
@@ -199,7 +199,7 @@ function logError(message) {
|
|
|
199
199
|
* Log a success message (hidden if --quiet is set).
|
|
200
200
|
*/
|
|
201
201
|
function logSuccess(ctx, message) {
|
|
202
|
-
if (!ctx.quiet) console.
|
|
202
|
+
if (!ctx.quiet) console.error(pc.green(message));
|
|
203
203
|
}
|
|
204
204
|
/**
|
|
205
205
|
* Log a timing message (hidden if --quiet is set).
|
|
@@ -207,14 +207,14 @@ function logSuccess(ctx, message) {
|
|
|
207
207
|
function logTiming(ctx, label, durationMs) {
|
|
208
208
|
if (!ctx.quiet) {
|
|
209
209
|
const seconds = (durationMs / 1e3).toFixed(1);
|
|
210
|
-
console.
|
|
210
|
+
console.error(pc.cyan(`⏰ ${label}: ${seconds}s`));
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
/**
|
|
214
214
|
* Log a warning message (hidden if --quiet is set).
|
|
215
215
|
*/
|
|
216
216
|
function logWarn(ctx, message) {
|
|
217
|
-
if (!ctx.quiet) console.
|
|
217
|
+
if (!ctx.quiet) console.error(pc.yellow(`⚠️ ${message}`));
|
|
218
218
|
}
|
|
219
219
|
/**
|
|
220
220
|
* Strip HTML comments from markdown content.
|
|
@@ -262,4 +262,4 @@ async function ensureFormsDir(formsDir) {
|
|
|
262
262
|
|
|
263
263
|
//#endregion
|
|
264
264
|
export { writeFile$1 as _, formatPath as a, logError as c, logTiming as d, logVerbose as f, stripHtmlComments as g, shouldUseColors as h, formatOutput as i, logInfo as l, readFile$1 as m, createSpinner as n, getCommandContext as o, logWarn as p, ensureFormsDir as r, logDryRun as s, OUTPUT_FORMATS as t, logSuccess as u };
|
|
265
|
-
//# sourceMappingURL=shared-
|
|
265
|
+
//# sourceMappingURL=shared-BszoSkAO.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared-CuSRYcIB.mjs","names":["readFile","writeFile"],"sources":["../src/cli/lib/naming.ts","../src/cli/lib/shared.ts"],"sourcesContent":["/**\n * Naming convention utilities for JSON/YAML output.\n *\n * Converts between camelCase (TypeScript internal) and snake_case (JSON/YAML output).\n */\n\n/**\n * Convert a camelCase string to snake_case.\n *\n * @example\n * toSnakeCase(\"fieldCount\") // \"field_count\"\n * toSnakeCase(\"parentFieldId\") // \"parent_field_id\"\n * toSnakeCase(\"already_snake\") // \"already_snake\"\n */\nexport function toSnakeCase(str: string): string {\n return str.replace(/([A-Z])/g, '_$1').toLowerCase();\n}\n\n/**\n * Convert a snake_case string to camelCase.\n *\n * @example\n * toCamelCase(\"field_count\") // \"fieldCount\"\n * toCamelCase(\"parent_field_id\") // \"parentFieldId\"\n * toCamelCase(\"alreadyCamel\") // \"alreadyCamel\"\n */\nexport function toCamelCase(str: string): string {\n return str.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase());\n}\n\n/**\n * Recursively convert all object keys from camelCase to snake_case.\n *\n * Handles nested objects and arrays. Primitives are returned unchanged.\n */\nexport function convertKeysToSnakeCase(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map(convertKeysToSnakeCase);\n }\n\n if (typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n const snakeKey = toSnakeCase(key);\n result[snakeKey] = convertKeysToSnakeCase(value);\n }\n return result;\n }\n\n return obj;\n}\n\n/**\n * Recursively convert all object keys from snake_case to camelCase.\n *\n * Handles nested objects and arrays. Primitives are returned unchanged.\n */\nexport function convertKeysToCamelCase(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map(convertKeysToCamelCase);\n }\n\n if (typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n const camelKey = toCamelCase(key);\n result[camelKey] = convertKeysToCamelCase(value);\n }\n return result;\n }\n\n return obj;\n}\n","/**\n * Shared CLI utilities for command context, debug, and dry-run helpers.\n */\n\nimport type { Command } from 'commander';\n\nimport { mkdir } from 'node:fs/promises';\nimport { relative } from 'node:path';\n\nimport * as p from '@clack/prompts';\nimport pc from 'picocolors';\nimport YAML from 'yaml';\n\nimport { convertKeysToSnakeCase } from './naming.js';\nimport type { CommandContext, OutputFormat } from './cliTypes.js';\n\n// =============================================================================\n// Spinner Utility Types\n// =============================================================================\n\n/**\n * Context type for spinner operations.\n * - 'api': For LLM/API calls (shows provider, model, turn info)\n * - 'compute': For local calculations\n */\nexport type SpinnerContextType = 'api' | 'compute';\n\n/**\n * API context for spinner - used when making LLM calls.\n */\nexport interface ApiSpinnerContext {\n type: 'api';\n provider: string;\n model: string;\n turnNumber?: number;\n}\n\n/**\n * Compute context for spinner - used for local calculations.\n */\nexport interface ComputeSpinnerContext {\n type: 'compute';\n operation: string;\n}\n\n/**\n * Union of spinner context types.\n */\nexport type SpinnerContext = ApiSpinnerContext | ComputeSpinnerContext;\n\n/**\n * Handle for controlling an active spinner.\n */\nexport interface SpinnerHandle {\n /** Update the spinner message. */\n message(msg: string): void;\n /** Update the spinner context (re-renders with elapsed time). */\n update(context: SpinnerContext): void;\n /** Stop the spinner with a success message. */\n stop(msg?: string): void;\n /** Stop the spinner with an error message. */\n error(msg: string): void;\n /** Get elapsed time in milliseconds since spinner started. */\n getElapsedMs(): number;\n}\n\n// Re-export types for backwards compatibility\nexport type { CommandContext, OutputFormat } from './cliTypes.js';\n\n// =============================================================================\n// Spinner Utility Functions\n// =============================================================================\n\n/**\n * Format elapsed time for display.\n */\nfunction formatElapsedTime(ms: number): string {\n const seconds = ms / 1000;\n if (seconds < 60) {\n return `${seconds.toFixed(1)}s`;\n }\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = seconds % 60;\n return `${minutes}m ${remainingSeconds.toFixed(0)}s`;\n}\n\n/**\n * Format spinner message based on context type.\n */\nfunction formatSpinnerMessage(context: SpinnerContext, elapsedMs: number): string {\n const elapsed = formatElapsedTime(elapsedMs);\n\n if (context.type === 'api') {\n const turnInfo = context.turnNumber !== undefined ? ` turn ${context.turnNumber}` : '';\n return `${context.provider}/${context.model}${turnInfo} ${pc.dim(`(${elapsed})`)}`;\n }\n\n return `${context.operation} ${pc.dim(`(${elapsed})`)}`;\n}\n\n/**\n * Create a context-aware spinner with elapsed time tracking.\n *\n * The spinner automatically updates its message with elapsed time.\n *\n * @example\n * ```ts\n * const spinner = createSpinner({\n * type: 'api',\n * provider: 'anthropic',\n * model: 'claude-sonnet-4',\n * turnNumber: 1,\n * });\n *\n * // Do async work...\n * const result = await agent.fillFormTool(...);\n *\n * spinner.stop('Done');\n * ```\n */\nexport function createSpinner(context: SpinnerContext): SpinnerHandle {\n const startTime = Date.now();\n const spinner = p.spinner();\n let currentContext = context;\n let intervalId: ReturnType<typeof setInterval> | null = null;\n\n // Start the spinner with initial message\n const initialMessage = formatSpinnerMessage(currentContext, 0);\n spinner.start(initialMessage);\n\n // Update elapsed time every second\n intervalId = setInterval(() => {\n const elapsed = Date.now() - startTime;\n spinner.message(formatSpinnerMessage(currentContext, elapsed));\n }, 1000);\n\n const cleanup = (): void => {\n if (intervalId) {\n clearInterval(intervalId);\n intervalId = null;\n }\n };\n\n return {\n message(msg: string): void {\n spinner.message(msg);\n },\n\n update(newContext: SpinnerContext): void {\n currentContext = newContext;\n const elapsed = Date.now() - startTime;\n spinner.message(formatSpinnerMessage(currentContext, elapsed));\n },\n\n stop(msg?: string): void {\n cleanup();\n const elapsed = Date.now() - startTime;\n const defaultMsg = formatSpinnerMessage(currentContext, elapsed);\n spinner.stop(msg ?? `✓ ${defaultMsg}`);\n },\n\n error(msg: string): void {\n cleanup();\n spinner.stop(pc.red(`✗ ${msg}`));\n },\n\n getElapsedMs(): number {\n return Date.now() - startTime;\n },\n };\n}\n\n/**\n * Create a no-op spinner handle for quiet mode or non-TTY environments.\n */\nexport function createNoOpSpinner(): SpinnerHandle {\n const startTime = Date.now();\n // Use explicit undefined returns to avoid empty-function lint errors\n const noop = (): void => undefined;\n return {\n message: noop,\n update: noop,\n stop: noop,\n error: noop,\n getElapsedMs: () => Date.now() - startTime,\n };\n}\n\n/**\n * Create a spinner if appropriate for the context.\n * Returns a no-op spinner in quiet mode or when stdout is not a TTY.\n */\nexport function createSpinnerIfTty(context: SpinnerContext, ctx: CommandContext): SpinnerHandle {\n if (ctx.quiet || !process.stdout.isTTY) {\n return createNoOpSpinner();\n }\n return createSpinner(context);\n}\n\n// =============================================================================\n// Output Format Utilities\n// =============================================================================\n\n/**\n * Valid format options for Commander choice validation.\n */\nexport const OUTPUT_FORMATS: OutputFormat[] = [\n 'console',\n 'plaintext',\n 'yaml',\n 'json',\n 'markform',\n 'markdown',\n];\n\n/**\n * Extract command context from Commander options.\n */\nexport function getCommandContext(command: Command): CommandContext {\n const opts = command.optsWithGlobals<{\n dryRun?: boolean;\n verbose?: boolean;\n quiet?: boolean;\n format?: OutputFormat;\n formsDir?: string;\n overwrite?: boolean;\n }>();\n return {\n dryRun: opts.dryRun ?? false,\n verbose: opts.verbose ?? false,\n quiet: opts.quiet ?? false,\n format: opts.format ?? 'console',\n formsDir: opts.formsDir,\n overwrite: opts.overwrite ?? false,\n };\n}\n\n/**\n * Check if output should use colors.\n * Returns true for console format when stdout is a TTY.\n */\nexport function shouldUseColors(ctx: CommandContext): boolean {\n if (ctx.format === 'plaintext' || ctx.format === 'yaml' || ctx.format === 'json') {\n return false;\n }\n // console format: use colors if stdout is a TTY and NO_COLOR is not set\n return process.stdout.isTTY && !process.env.NO_COLOR;\n}\n\n/**\n * Format structured data according to output format.\n *\n * JSON and YAML outputs are converted to snake_case keys for consistency.\n */\nexport function formatOutput(\n ctx: CommandContext,\n data: unknown,\n consoleFormatter?: (data: unknown, useColors: boolean) => string,\n): string {\n switch (ctx.format) {\n case 'json':\n return JSON.stringify(convertKeysToSnakeCase(data), null, 2);\n case 'yaml':\n return YAML.stringify(convertKeysToSnakeCase(data));\n case 'plaintext':\n case 'console':\n default:\n if (consoleFormatter) {\n return consoleFormatter(data, shouldUseColors(ctx));\n }\n // Default: use YAML for readable console output\n return YAML.stringify(convertKeysToSnakeCase(data));\n }\n}\n\n/**\n * Log a dry-run message.\n */\nexport function logDryRun(message: string, details?: unknown): void {\n console.log(pc.yellow(`[DRY RUN] ${message}`));\n if (details) {\n console.log(pc.dim(JSON.stringify(details, null, 2)));\n }\n}\n\n/**\n * Log a verbose message (only shown if --verbose is set).\n */\nexport function logVerbose(ctx: CommandContext, message: string): void {\n if (ctx.verbose) {\n console.log(pc.dim(message));\n }\n}\n\n/**\n * Log an info message (hidden if --quiet is set).\n */\nexport function logInfo(ctx: CommandContext, message: string): void {\n if (!ctx.quiet) {\n console.log(message);\n }\n}\n\n/**\n * Log an error message (always shown).\n */\nexport function logError(message: string): void {\n console.error(pc.red(`Error: ${message}`));\n}\n\n/**\n * Log a success message (hidden if --quiet is set).\n */\nexport function logSuccess(ctx: CommandContext, message: string): void {\n if (!ctx.quiet) {\n console.log(pc.green(message));\n }\n}\n\n/**\n * Log a timing message (hidden if --quiet is set).\n */\nexport function logTiming(ctx: CommandContext, label: string, durationMs: number): void {\n if (!ctx.quiet) {\n const seconds = (durationMs / 1000).toFixed(1);\n console.log(pc.cyan(`⏰ ${label}: ${seconds}s`));\n }\n}\n\n/**\n * Log a warning message (hidden if --quiet is set).\n */\nexport function logWarn(ctx: CommandContext, message: string): void {\n if (!ctx.quiet) {\n console.log(pc.yellow(`⚠️ ${message}`));\n }\n}\n\n/**\n * Strip HTML comments from markdown content.\n * Removes <!-- ... --> blocks (including multiline) and trims leading whitespace.\n */\nexport function stripHtmlComments(content: string): string {\n // Remove HTML comments (multiline-safe)\n const stripped = content.replace(/<!--[\\s\\S]*?-->/g, '');\n // Trim leading whitespace that may remain after comment removal\n return stripped.replace(/^\\s+/, '');\n}\n\n/**\n * Format a file path for display: relative to cwd, colored green.\n * If the path is within the cwd, shows as relative (e.g., \"./simple-filled1.form.md\")\n * If outside cwd, shows the absolute path.\n */\nexport function formatPath(absolutePath: string, cwd: string = process.cwd()): string {\n const relativePath = relative(cwd, absolutePath);\n // If the relative path doesn't start with \"..\", it's within cwd\n const displayPath = relativePath.startsWith('..') ? absolutePath : `./${relativePath}`;\n return pc.green(displayPath);\n}\n\n/**\n * Read a file and return its contents.\n */\nexport async function readFile(filePath: string): Promise<string> {\n const { readFile: fsReadFile } = await import('node:fs/promises');\n return fsReadFile(filePath, 'utf-8');\n}\n\n/**\n * Write contents to a file atomically.\n *\n * Uses the atomically library to prevent partial or corrupted files\n * if the process crashes mid-write.\n */\nexport async function writeFile(filePath: string, contents: string): Promise<void> {\n const { writeFile: atomicWriteFile } = await import('atomically');\n await atomicWriteFile(filePath, contents);\n}\n\n/**\n * Ensure the forms directory exists, creating it if necessary.\n * Uses recursive mkdir so parent directories are created as needed.\n *\n * @param formsDir Absolute path to the forms directory\n */\nexport async function ensureFormsDir(formsDir: string): Promise<void> {\n await mkdir(formsDir, { recursive: true });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"shared-BszoSkAO.mjs","names":["readFile","writeFile"],"sources":["../src/cli/lib/naming.ts","../src/cli/lib/shared.ts"],"sourcesContent":["/**\n * Naming convention utilities for JSON/YAML output.\n *\n * Converts between camelCase (TypeScript internal) and snake_case (JSON/YAML output).\n */\n\n/**\n * Convert a camelCase string to snake_case.\n *\n * @example\n * toSnakeCase(\"fieldCount\") // \"field_count\"\n * toSnakeCase(\"parentFieldId\") // \"parent_field_id\"\n * toSnakeCase(\"already_snake\") // \"already_snake\"\n */\nexport function toSnakeCase(str: string): string {\n return str.replace(/([A-Z])/g, '_$1').toLowerCase();\n}\n\n/**\n * Convert a snake_case string to camelCase.\n *\n * @example\n * toCamelCase(\"field_count\") // \"fieldCount\"\n * toCamelCase(\"parent_field_id\") // \"parentFieldId\"\n * toCamelCase(\"alreadyCamel\") // \"alreadyCamel\"\n */\nexport function toCamelCase(str: string): string {\n return str.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase());\n}\n\n/**\n * Recursively convert all object keys from camelCase to snake_case.\n *\n * Handles nested objects and arrays. Primitives are returned unchanged.\n */\nexport function convertKeysToSnakeCase(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map(convertKeysToSnakeCase);\n }\n\n if (typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n const snakeKey = toSnakeCase(key);\n result[snakeKey] = convertKeysToSnakeCase(value);\n }\n return result;\n }\n\n return obj;\n}\n\n/**\n * Recursively convert all object keys from snake_case to camelCase.\n *\n * Handles nested objects and arrays. Primitives are returned unchanged.\n */\nexport function convertKeysToCamelCase(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map(convertKeysToCamelCase);\n }\n\n if (typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n const camelKey = toCamelCase(key);\n result[camelKey] = convertKeysToCamelCase(value);\n }\n return result;\n }\n\n return obj;\n}\n","/**\n * Shared CLI utilities for command context, debug, and dry-run helpers.\n */\n\nimport type { Command } from 'commander';\n\nimport { mkdir } from 'node:fs/promises';\nimport { relative } from 'node:path';\n\nimport * as p from '@clack/prompts';\nimport pc from 'picocolors';\nimport YAML from 'yaml';\n\nimport { convertKeysToSnakeCase } from './naming.js';\nimport type { CommandContext, OutputFormat } from './cliTypes.js';\n\n// =============================================================================\n// Spinner Utility Types\n// =============================================================================\n\n/**\n * Context type for spinner operations.\n * - 'api': For LLM/API calls (shows provider, model, turn info)\n * - 'compute': For local calculations\n */\nexport type SpinnerContextType = 'api' | 'compute';\n\n/**\n * API context for spinner - used when making LLM calls.\n */\nexport interface ApiSpinnerContext {\n type: 'api';\n provider: string;\n model: string;\n turnNumber?: number;\n}\n\n/**\n * Compute context for spinner - used for local calculations.\n */\nexport interface ComputeSpinnerContext {\n type: 'compute';\n operation: string;\n}\n\n/**\n * Union of spinner context types.\n */\nexport type SpinnerContext = ApiSpinnerContext | ComputeSpinnerContext;\n\n/**\n * Handle for controlling an active spinner.\n */\nexport interface SpinnerHandle {\n /** Update the spinner message. */\n message(msg: string): void;\n /** Update the spinner context (re-renders with elapsed time). */\n update(context: SpinnerContext): void;\n /** Stop the spinner with a success message. */\n stop(msg?: string): void;\n /** Stop the spinner with an error message. */\n error(msg: string): void;\n /** Get elapsed time in milliseconds since spinner started. */\n getElapsedMs(): number;\n}\n\n// Re-export types for backwards compatibility\nexport type { CommandContext, OutputFormat } from './cliTypes.js';\n\n// =============================================================================\n// Spinner Utility Functions\n// =============================================================================\n\n/**\n * Format elapsed time for display.\n */\nfunction formatElapsedTime(ms: number): string {\n const seconds = ms / 1000;\n if (seconds < 60) {\n return `${seconds.toFixed(1)}s`;\n }\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = seconds % 60;\n return `${minutes}m ${remainingSeconds.toFixed(0)}s`;\n}\n\n/**\n * Format spinner message based on context type.\n */\nfunction formatSpinnerMessage(context: SpinnerContext, elapsedMs: number): string {\n const elapsed = formatElapsedTime(elapsedMs);\n\n if (context.type === 'api') {\n const turnInfo = context.turnNumber !== undefined ? ` turn ${context.turnNumber}` : '';\n return `${context.provider}/${context.model}${turnInfo} ${pc.dim(`(${elapsed})`)}`;\n }\n\n return `${context.operation} ${pc.dim(`(${elapsed})`)}`;\n}\n\n/**\n * Create a context-aware spinner with elapsed time tracking.\n *\n * The spinner automatically updates its message with elapsed time.\n *\n * @example\n * ```ts\n * const spinner = createSpinner({\n * type: 'api',\n * provider: 'anthropic',\n * model: 'claude-sonnet-4',\n * turnNumber: 1,\n * });\n *\n * // Do async work...\n * const result = await agent.fillFormTool(...);\n *\n * spinner.stop('Done');\n * ```\n */\nexport function createSpinner(context: SpinnerContext): SpinnerHandle {\n const startTime = Date.now();\n const spinner = p.spinner();\n let currentContext = context;\n let intervalId: ReturnType<typeof setInterval> | null = null;\n\n // Start the spinner with initial message\n const initialMessage = formatSpinnerMessage(currentContext, 0);\n spinner.start(initialMessage);\n\n // Update elapsed time every second\n intervalId = setInterval(() => {\n const elapsed = Date.now() - startTime;\n spinner.message(formatSpinnerMessage(currentContext, elapsed));\n }, 1000);\n\n const cleanup = (): void => {\n if (intervalId) {\n clearInterval(intervalId);\n intervalId = null;\n }\n };\n\n return {\n message(msg: string): void {\n spinner.message(msg);\n },\n\n update(newContext: SpinnerContext): void {\n currentContext = newContext;\n const elapsed = Date.now() - startTime;\n spinner.message(formatSpinnerMessage(currentContext, elapsed));\n },\n\n stop(msg?: string): void {\n cleanup();\n const elapsed = Date.now() - startTime;\n const defaultMsg = formatSpinnerMessage(currentContext, elapsed);\n spinner.stop(msg ?? `✓ ${defaultMsg}`);\n },\n\n error(msg: string): void {\n cleanup();\n spinner.stop(pc.red(`✗ ${msg}`));\n },\n\n getElapsedMs(): number {\n return Date.now() - startTime;\n },\n };\n}\n\n/**\n * Create a no-op spinner handle for quiet mode or non-TTY environments.\n */\nexport function createNoOpSpinner(): SpinnerHandle {\n const startTime = Date.now();\n // Use explicit undefined returns to avoid empty-function lint errors\n const noop = (): void => undefined;\n return {\n message: noop,\n update: noop,\n stop: noop,\n error: noop,\n getElapsedMs: () => Date.now() - startTime,\n };\n}\n\n/**\n * Create a spinner if appropriate for the context.\n * Returns a no-op spinner in quiet mode or when stdout is not a TTY.\n */\nexport function createSpinnerIfTty(context: SpinnerContext, ctx: CommandContext): SpinnerHandle {\n if (ctx.quiet || !process.stdout.isTTY) {\n return createNoOpSpinner();\n }\n return createSpinner(context);\n}\n\n// =============================================================================\n// Output Format Utilities\n// =============================================================================\n\n/**\n * Valid format options for Commander choice validation.\n */\nexport const OUTPUT_FORMATS: OutputFormat[] = [\n 'console',\n 'plaintext',\n 'yaml',\n 'json',\n 'markform',\n 'markdown',\n];\n\n/**\n * Extract command context from Commander options.\n */\nexport function getCommandContext(command: Command): CommandContext {\n const opts = command.optsWithGlobals<{\n dryRun?: boolean;\n verbose?: boolean;\n quiet?: boolean;\n format?: OutputFormat;\n formsDir?: string;\n overwrite?: boolean;\n }>();\n return {\n dryRun: opts.dryRun ?? false,\n verbose: opts.verbose ?? false,\n quiet: opts.quiet ?? false,\n format: opts.format ?? 'console',\n formsDir: opts.formsDir,\n overwrite: opts.overwrite ?? false,\n };\n}\n\n/**\n * Check if output should use colors.\n * Returns true for console format when stdout is a TTY.\n */\nexport function shouldUseColors(ctx: CommandContext): boolean {\n if (ctx.format === 'plaintext' || ctx.format === 'yaml' || ctx.format === 'json') {\n return false;\n }\n // console format: use colors if stdout is a TTY and NO_COLOR is not set\n return process.stdout.isTTY && !process.env.NO_COLOR;\n}\n\n/**\n * Format structured data according to output format.\n *\n * JSON and YAML outputs are converted to snake_case keys for consistency.\n */\nexport function formatOutput(\n ctx: CommandContext,\n data: unknown,\n consoleFormatter?: (data: unknown, useColors: boolean) => string,\n): string {\n switch (ctx.format) {\n case 'json':\n return JSON.stringify(convertKeysToSnakeCase(data), null, 2);\n case 'yaml':\n return YAML.stringify(convertKeysToSnakeCase(data));\n case 'plaintext':\n case 'console':\n default:\n if (consoleFormatter) {\n return consoleFormatter(data, shouldUseColors(ctx));\n }\n // Default: use YAML for readable console output\n return YAML.stringify(convertKeysToSnakeCase(data));\n }\n}\n\n/**\n * Log a dry-run message.\n */\nexport function logDryRun(message: string, details?: unknown): void {\n console.error(pc.yellow(`[DRY RUN] ${message}`));\n if (details) {\n console.error(pc.dim(JSON.stringify(details, null, 2)));\n }\n}\n\n/**\n * Log a verbose message (only shown if --verbose is set).\n */\nexport function logVerbose(ctx: CommandContext, message: string): void {\n if (ctx.verbose) {\n console.error(pc.dim(message));\n }\n}\n\n/**\n * Log an info message (hidden if --quiet is set).\n */\nexport function logInfo(ctx: CommandContext, message: string): void {\n if (!ctx.quiet) {\n console.error(message);\n }\n}\n\n/**\n * Log an error message (always shown).\n */\nexport function logError(message: string): void {\n console.error(pc.red(`Error: ${message}`));\n}\n\n/**\n * Log a success message (hidden if --quiet is set).\n */\nexport function logSuccess(ctx: CommandContext, message: string): void {\n if (!ctx.quiet) {\n console.error(pc.green(message));\n }\n}\n\n/**\n * Log a timing message (hidden if --quiet is set).\n */\nexport function logTiming(ctx: CommandContext, label: string, durationMs: number): void {\n if (!ctx.quiet) {\n const seconds = (durationMs / 1000).toFixed(1);\n console.error(pc.cyan(`⏰ ${label}: ${seconds}s`));\n }\n}\n\n/**\n * Log a warning message (hidden if --quiet is set).\n */\nexport function logWarn(ctx: CommandContext, message: string): void {\n if (!ctx.quiet) {\n console.error(pc.yellow(`⚠️ ${message}`));\n }\n}\n\n/**\n * Strip HTML comments from markdown content.\n * Removes <!-- ... --> blocks (including multiline) and trims leading whitespace.\n */\nexport function stripHtmlComments(content: string): string {\n // Remove HTML comments (multiline-safe)\n const stripped = content.replace(/<!--[\\s\\S]*?-->/g, '');\n // Trim leading whitespace that may remain after comment removal\n return stripped.replace(/^\\s+/, '');\n}\n\n/**\n * Format a file path for display: relative to cwd, colored green.\n * If the path is within the cwd, shows as relative (e.g., \"./simple-filled1.form.md\")\n * If outside cwd, shows the absolute path.\n */\nexport function formatPath(absolutePath: string, cwd: string = process.cwd()): string {\n const relativePath = relative(cwd, absolutePath);\n // If the relative path doesn't start with \"..\", it's within cwd\n const displayPath = relativePath.startsWith('..') ? absolutePath : `./${relativePath}`;\n return pc.green(displayPath);\n}\n\n/**\n * Read a file and return its contents.\n */\nexport async function readFile(filePath: string): Promise<string> {\n const { readFile: fsReadFile } = await import('node:fs/promises');\n return fsReadFile(filePath, 'utf-8');\n}\n\n/**\n * Write contents to a file atomically.\n *\n * Uses the atomically library to prevent partial or corrupted files\n * if the process crashes mid-write.\n */\nexport async function writeFile(filePath: string, contents: string): Promise<void> {\n const { writeFile: atomicWriteFile } = await import('atomically');\n await atomicWriteFile(filePath, contents);\n}\n\n/**\n * Ensure the forms directory exists, creating it if necessary.\n * Uses recursive mkdir so parent directories are created as needed.\n *\n * @param formsDir Absolute path to the forms directory\n */\nexport async function ensureFormsDir(formsDir: string): Promise<void> {\n await mkdir(formsDir, { recursive: true });\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
import { A as MarkformSectionInputSchema, B as ProgressCountsSchema, R as PatchSchema, ht as StructureSummarySchema, z as PatchWarningSchema } from "./coreTypes-
|
|
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-
|
|
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;
|
|
@@ -3009,6 +3085,7 @@ var FillRecordCollector = class {
|
|
|
3009
3085
|
failedCalls,
|
|
3010
3086
|
successRate: totalCalls > 0 ? successfulCalls / totalCalls * 100 : 0,
|
|
3011
3087
|
totalDurationMs,
|
|
3088
|
+
avgDurationMs: totalCalls > 0 ? totalDurationMs / totalCalls : 0,
|
|
3012
3089
|
byTool
|
|
3013
3090
|
};
|
|
3014
3091
|
}
|
|
@@ -3030,7 +3107,7 @@ var FillRecordCollector = class {
|
|
|
3030
3107
|
return Math.round(sorted[lower] * (1 - weight) + sorted[upper] * weight);
|
|
3031
3108
|
}
|
|
3032
3109
|
calculateTimingBreakdown(totalMs, llmTimeMs, toolTimeMs) {
|
|
3033
|
-
const overheadMs = Math.max(0, totalMs - llmTimeMs
|
|
3110
|
+
const overheadMs = Math.max(0, totalMs - llmTimeMs);
|
|
3034
3111
|
return {
|
|
3035
3112
|
totalMs,
|
|
3036
3113
|
llmTimeMs,
|
|
@@ -3055,7 +3132,8 @@ var FillRecordCollector = class {
|
|
|
3055
3132
|
ms: overheadMs,
|
|
3056
3133
|
percentage: totalMs > 0 ? overheadMs / totalMs * 100 : 0
|
|
3057
3134
|
}
|
|
3058
|
-
]
|
|
3135
|
+
],
|
|
3136
|
+
llmParallelism: totalMs > 0 ? llmTimeMs / totalMs : 0
|
|
3059
3137
|
};
|
|
3060
3138
|
}
|
|
3061
3139
|
buildExecutionMetadata(timeline) {
|
|
@@ -9426,17 +9504,25 @@ function mergeCallbacks(userCallbacks, collector) {
|
|
|
9426
9504
|
} catch (e) {
|
|
9427
9505
|
warnCallbackError("onWebSearch", e);
|
|
9428
9506
|
}
|
|
9429
|
-
}
|
|
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
|
|
9430
9515
|
};
|
|
9431
9516
|
}
|
|
9432
|
-
function buildErrorResult(form, errors, warnings, record) {
|
|
9517
|
+
function buildErrorResult(form, errors, warnings, record, sourceError) {
|
|
9433
9518
|
const values = {};
|
|
9434
9519
|
for (const [fieldId, response] of Object.entries(form.responsesByFieldId)) if (response.state === "answered" && response.value) values[fieldId] = response.value;
|
|
9435
9520
|
const result = {
|
|
9436
9521
|
status: {
|
|
9437
9522
|
ok: false,
|
|
9438
9523
|
reason: "error",
|
|
9439
|
-
message: errors.join("; ")
|
|
9524
|
+
message: errors.join("; "),
|
|
9525
|
+
error: sourceError
|
|
9440
9526
|
},
|
|
9441
9527
|
markdown: serializeForm(form),
|
|
9442
9528
|
values,
|
|
@@ -9515,7 +9601,8 @@ async function fillForm(options) {
|
|
|
9515
9601
|
status: {
|
|
9516
9602
|
ok: false,
|
|
9517
9603
|
reason: "error",
|
|
9518
|
-
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
|
|
9519
9606
|
},
|
|
9520
9607
|
markdown: typeof options.form === "string" ? options.form : "",
|
|
9521
9608
|
values: {},
|
|
@@ -9546,7 +9633,7 @@ async function fillForm(options) {
|
|
|
9546
9633
|
} else model = options.model;
|
|
9547
9634
|
} catch (error) {
|
|
9548
9635
|
const message = error instanceof Error ? error.message : String(error);
|
|
9549
|
-
return buildErrorResult(form, [`Model resolution error: ${message}`], []);
|
|
9636
|
+
return buildErrorResult(form, [`Model resolution error: ${message}`], [], void 0, error instanceof Error ? error : void 0);
|
|
9550
9637
|
}
|
|
9551
9638
|
else if (typeof options.model === "string" && options.model.includes("/")) provider = options.model.split("/")[0];
|
|
9552
9639
|
const modelString = typeof options.model === "string" ? options.model : "custom";
|
|
@@ -9639,15 +9726,22 @@ async function fillForm(options) {
|
|
|
9639
9726
|
response = await agent.fillFormTool(turnIssues, form, maxPatchesPerTurn, previousRejections);
|
|
9640
9727
|
} catch (error) {
|
|
9641
9728
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
9729
|
+
const errorObj = error instanceof Error ? error : void 0;
|
|
9642
9730
|
let record;
|
|
9643
9731
|
if (collector) {
|
|
9644
9732
|
collector.setStatus("failed", errorMessage);
|
|
9645
9733
|
record = collector.getRecord(getProgressCounts(form, targetRoles));
|
|
9646
9734
|
}
|
|
9735
|
+
if (errorObj && mergedCallbacks?.onError) try {
|
|
9736
|
+
mergedCallbacks.onError(errorObj, { turnNumber: turnCount + 1 });
|
|
9737
|
+
} catch (cbError) {
|
|
9738
|
+
warnCallbackError("onError", cbError);
|
|
9739
|
+
}
|
|
9647
9740
|
return buildResult(form, turnCount, totalPatches, {
|
|
9648
9741
|
ok: false,
|
|
9649
9742
|
reason: "error",
|
|
9650
|
-
message: errorMessage
|
|
9743
|
+
message: errorMessage,
|
|
9744
|
+
error: errorObj
|
|
9651
9745
|
}, inputContextWarnings, turnIssues, record);
|
|
9652
9746
|
}
|
|
9653
9747
|
const { patches, stats } = response;
|
|
@@ -9919,6 +10013,12 @@ async function runMultiTurnForItems(form, agent, items, targetRoles, maxPatchesP
|
|
|
9919
10013
|
response = await agent.fillFormTool(scopedIssues, form, maxPatchesPerTurn, previousRejections);
|
|
9920
10014
|
} catch (error) {
|
|
9921
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
|
+
}
|
|
9922
10022
|
return {
|
|
9923
10023
|
patchesApplied,
|
|
9924
10024
|
turnsUsed,
|
|
@@ -9926,7 +10026,8 @@ async function runMultiTurnForItems(form, agent, items, targetRoles, maxPatchesP
|
|
|
9926
10026
|
status: {
|
|
9927
10027
|
ok: false,
|
|
9928
10028
|
reason: "error",
|
|
9929
|
-
message: errorMessage
|
|
10029
|
+
message: errorMessage,
|
|
10030
|
+
error: errorObj
|
|
9930
10031
|
}
|
|
9931
10032
|
};
|
|
9932
10033
|
}
|
|
@@ -10001,12 +10102,24 @@ function formatNumber(n) {
|
|
|
10001
10102
|
* Format milliseconds as a human-readable duration.
|
|
10002
10103
|
*/
|
|
10003
10104
|
function formatDuration(ms) {
|
|
10004
|
-
if (ms < 1e3) return `${ms}ms`;
|
|
10105
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
10005
10106
|
const seconds = ms / 1e3;
|
|
10006
10107
|
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
10007
10108
|
return `${Math.floor(seconds / 60)}m ${(seconds % 60).toFixed(0)}s`;
|
|
10008
10109
|
}
|
|
10009
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
|
+
/**
|
|
10010
10123
|
* Format a percentage.
|
|
10011
10124
|
*/
|
|
10012
10125
|
function formatPercent(value, total) {
|
|
@@ -10033,7 +10146,11 @@ function formatFillRecordSummary(record, options = {}) {
|
|
|
10033
10146
|
const lines = [];
|
|
10034
10147
|
const statusText = record.status === "completed" ? "Fill completed" : "Fill incomplete";
|
|
10035
10148
|
const turnsText = `${record.execution.totalTurns} turn${record.execution.totalTurns !== 1 ? "s" : ""}`;
|
|
10036
|
-
|
|
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})`;
|
|
10037
10154
|
if (record.status !== "completed" && record.statusDetail) statusLine += ` - ${record.statusDetail}`;
|
|
10038
10155
|
lines.push(statusLine);
|
|
10039
10156
|
const timelineEmpty = record.timeline.length === 0;
|
|
@@ -10049,6 +10166,7 @@ function formatFillRecordSummary(record, options = {}) {
|
|
|
10049
10166
|
toolLine += ` (${formatNumber(toolSummary.successfulCalls)} succeeded`;
|
|
10050
10167
|
if (toolSummary.failedCalls > 0) toolLine += `, ${formatNumber(toolSummary.failedCalls)} failed`;
|
|
10051
10168
|
toolLine += ")";
|
|
10169
|
+
toolLine += `, avg ${formatDuration(toolSummary.avgDurationMs)} each`;
|
|
10052
10170
|
}
|
|
10053
10171
|
lines.push(toolLine);
|
|
10054
10172
|
if (verbose && toolSummary.byTool.length > 0) for (const tool of toolSummary.byTool) {
|
|
@@ -10057,14 +10175,22 @@ function formatFillRecordSummary(record, options = {}) {
|
|
|
10057
10175
|
if (tool.timing.p95Ms !== void 0 && tool.callCount > 1) toolDetail += `, p95 ${formatDuration(tool.timing.p95Ms)}`;
|
|
10058
10176
|
lines.push(toolDetail);
|
|
10059
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);
|
|
10060
10182
|
if (verbose) {
|
|
10061
|
-
lines.push("");
|
|
10062
|
-
const { timingBreakdown } = record;
|
|
10063
|
-
const llmPct = timingBreakdown.breakdown.find((b) => b.category === "llm")?.percentage ?? 0;
|
|
10064
|
-
const toolPct = timingBreakdown.breakdown.find((b) => b.category === "tools")?.percentage ?? 0;
|
|
10065
|
-
const overheadPct = timingBreakdown.breakdown.find((b) => b.category === "overhead")?.percentage ?? 0;
|
|
10066
10183
|
const timingLine = `Timing: ${llmPct}% LLM (${formatDuration(timingBreakdown.llmTimeMs)}) | ${toolPct}% tools (${formatDuration(timingBreakdown.toolTimeMs)}) | ${overheadPct}% overhead (${formatDuration(timingBreakdown.overheadMs)})`;
|
|
10067
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);
|
|
10068
10194
|
}
|
|
10069
10195
|
lines.push("");
|
|
10070
10196
|
const { formProgress } = record;
|
|
@@ -10169,7 +10295,8 @@ const TimingBreakdownSchema = z.object({
|
|
|
10169
10295
|
llmTimeMs: z.number().int().nonnegative(),
|
|
10170
10296
|
toolTimeMs: z.number().int().nonnegative(),
|
|
10171
10297
|
overheadMs: z.number().int().nonnegative(),
|
|
10172
|
-
breakdown: z.array(TimingBreakdownItemSchema)
|
|
10298
|
+
breakdown: z.array(TimingBreakdownItemSchema),
|
|
10299
|
+
llmParallelism: z.number().nonnegative()
|
|
10173
10300
|
});
|
|
10174
10301
|
/**
|
|
10175
10302
|
* Aggregated tool usage statistics.
|
|
@@ -10180,6 +10307,7 @@ const ToolSummarySchema = z.object({
|
|
|
10180
10307
|
failedCalls: z.number().int().nonnegative(),
|
|
10181
10308
|
successRate: z.number().nonnegative(),
|
|
10182
10309
|
totalDurationMs: z.number().int().nonnegative(),
|
|
10310
|
+
avgDurationMs: z.number().nonnegative(),
|
|
10183
10311
|
byTool: z.array(ToolStatsSchema)
|
|
10184
10312
|
});
|
|
10185
10313
|
/**
|
|
@@ -10399,8 +10527,8 @@ function validateResearchForm(form) {
|
|
|
10399
10527
|
//#endregion
|
|
10400
10528
|
//#region src/index.ts
|
|
10401
10529
|
/** Markform version (injected at build time). */
|
|
10402
|
-
const VERSION = "0.1.
|
|
10530
|
+
const VERSION = "0.1.25";
|
|
10403
10531
|
|
|
10404
10532
|
//#endregion
|
|
10405
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 };
|
|
10406
|
-
//# sourceMappingURL=src-
|
|
10534
|
+
//# sourceMappingURL=src-DrXmaOWl.mjs.map
|