markform 0.1.20 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +44 -12
  2. package/dist/ai-sdk.d.mts +1 -1
  3. package/dist/ai-sdk.mjs +2 -2
  4. package/dist/{apply-DIvm1b1s.mjs → apply-C7mO7VkZ.mjs} +158 -95
  5. package/dist/apply-C7mO7VkZ.mjs.map +1 -0
  6. package/dist/bin.mjs +1 -1
  7. package/dist/{cli-FFMoEhFS.mjs → cli-C8F9yDsv.mjs} +102 -1225
  8. package/dist/cli-C8F9yDsv.mjs.map +1 -0
  9. package/dist/cli.mjs +1 -1
  10. package/dist/{coreTypes-CkxML8g2.d.mts → coreTypes-BlsJkU1w.d.mts} +28 -2
  11. package/dist/{coreTypes-CPKXf2dc.mjs → coreTypes-CTLr-NGd.mjs} +24 -3
  12. package/dist/coreTypes-CTLr-NGd.mjs.map +1 -0
  13. package/dist/fillRecord-DTl5lnK0.d.mts +345 -0
  14. package/dist/fillRecordRenderer-CruJrLkj.mjs +1256 -0
  15. package/dist/fillRecordRenderer-CruJrLkj.mjs.map +1 -0
  16. package/dist/index.d.mts +22 -342
  17. package/dist/index.mjs +5 -5
  18. package/dist/render.d.mts +74 -0
  19. package/dist/render.mjs +4 -0
  20. package/dist/{session-CK0x28RO.mjs → session-BCcltrLA.mjs} +2 -2
  21. package/dist/{session-CK0x28RO.mjs.map → session-BCcltrLA.mjs.map} +1 -1
  22. package/dist/{session-ZHBi3LVQ.mjs → session-VeSkVrck.mjs} +1 -1
  23. package/dist/{shared-DwdyWmvE.mjs → shared-CsdT2T7k.mjs} +1 -1
  24. package/dist/{shared-DwdyWmvE.mjs.map → shared-CsdT2T7k.mjs.map} +1 -1
  25. package/dist/{shared-BTR35aMz.mjs → shared-fb0nkzQi.mjs} +1 -1
  26. package/dist/{src-wR7GoftB.mjs → src-CbRnGzMK.mjs} +205 -138
  27. package/dist/src-CbRnGzMK.mjs.map +1 -0
  28. package/dist/urlFormat-lls7CsEP.mjs +71 -0
  29. package/dist/urlFormat-lls7CsEP.mjs.map +1 -0
  30. package/docs/markform-apis.md +53 -0
  31. package/examples/movie-research/movie-deep-research-mock-filled.form.md +320 -343
  32. package/examples/movie-research/movie-deep-research.form.md +273 -308
  33. package/examples/movie-research/movie-research-demo.form.md +27 -41
  34. package/examples/parallel/parallel-research.form.md +33 -29
  35. package/examples/parallel/parallel-research.mock.filled.form.md +88 -0
  36. package/examples/rejection-test/rejection-test-mock-filled.form.md +21 -16
  37. package/examples/rejection-test/rejection-test-mock-filled.schema.json +1 -1
  38. package/examples/rejection-test/rejection-test.form.md +17 -15
  39. package/examples/rejection-test/rejection-test.session.yaml +88 -60
  40. package/examples/simple/simple-mock-filled.form.md +113 -126
  41. package/examples/simple/simple-mock-filled.schema.json +2 -3
  42. package/examples/simple/simple-skipped-filled.form.md +112 -129
  43. package/examples/simple/simple-skipped-filled.report.md +8 -8
  44. package/examples/simple/simple-skipped-filled.schema.json +2 -3
  45. package/examples/simple/simple-tags-syntax.form.md +32 -0
  46. package/examples/simple/simple-with-skips.session.yaml +663 -627
  47. package/examples/simple/simple.form.md +97 -113
  48. package/examples/simple/simple.schema.json +2 -3
  49. package/examples/simple/simple.session.yaml +663 -627
  50. package/examples/startup-deep-research/startup-deep-research.form.md +191 -235
  51. package/examples/startup-research/startup-research-mock-filled.form.md +128 -147
  52. package/examples/startup-research/startup-research.form.md +90 -129
  53. package/examples/twitter-thread/twitter-thread.form.md +373 -0
  54. package/package.json +5 -1
  55. package/dist/apply-DIvm1b1s.mjs.map +0 -1
  56. package/dist/cli-FFMoEhFS.mjs.map +0 -1
  57. package/dist/coreTypes-CPKXf2dc.mjs.map +0 -1
  58. package/dist/src-wR7GoftB.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
 
2
- import { L as PatchSchema, R as ProgressCountsSchema, V as RunModeSchema, pt as StructureSummarySchema } from "./coreTypes-CPKXf2dc.mjs";
3
- import { A as parseOptionText, B as DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN, C as extractTableContent, D as getStringAttr, E as getStringArrayAttr, F as DEFAULT_MAX_PATCHES_PER_TURN, H as DEFAULT_ROLES, I as DEFAULT_MAX_STEPS_PER_TURN, L as DEFAULT_MAX_TURNS, N as DEFAULT_MAX_ISSUES_PER_TURN, O as getValidateAttr, P as DEFAULT_MAX_PARALLEL_AGENTS, S as extractOptionItems, T as getNumberAttr, U as DEFAULT_ROLE_INSTRUCTIONS, V as DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN, _ as preprocessCommentSyntax, b as CHECKBOX_MARKERS, c as computeProgressSummary, d as serializeForm, g as detectSyntaxStyle, i as inspect, j as AGENT_ROLE, k as isTagNode, l as computeStructureSummary, lt as MarkformParseError, nt as getWebSearchConfig, ot as MarkformConfigError, r as getFieldsForRoles, t as applyPatches, w as getBooleanAttr, x as extractFenceValue, y as tryParseSentinelResponse, z as DEFAULT_PRIORITY } from "./apply-DIvm1b1s.mjs";
2
+ import { A as MarkformSectionInputSchema, R as PatchSchema, mt as StructureSummarySchema, z as ProgressCountsSchema } from "./coreTypes-CTLr-NGd.mjs";
3
+ import { B as DEFAULT_ROLES, C as getNumberAttr, D as isTagNode, E as getValidateAttr, F as DEFAULT_MAX_TURNS, L as DEFAULT_PRIORITY, M as DEFAULT_MAX_PARALLEL_AGENTS, N as DEFAULT_MAX_PATCHES_PER_TURN, O as parseOptionText, P as DEFAULT_MAX_STEPS_PER_TURN, R as DEFAULT_RESEARCH_MAX_ISSUES_PER_TURN, S as getBooleanAttr, T as getStringAttr, V as DEFAULT_ROLE_INSTRUCTIONS, Z as transformHarnessConfigToTs, _ as tryParseSentinelResponse, at as MarkformConfigError, b as extractOptionItems, bt as wrapApiError, c as computeProgressSummary, ct as MarkformParseError, d as serializeForm, h as preprocessCommentSyntax, i as inspect, j as DEFAULT_MAX_ISSUES_PER_TURN, k as AGENT_ROLE, l as computeStructureSummary, m as detectSyntaxStyle, r as getFieldsForRoles, t as applyPatches, tt as getWebSearchConfig, v as CHECKBOX_MARKERS, w as getStringArrayAttr, x as extractTableContent, y as extractFenceValue, z as DEFAULT_RESEARCH_MAX_PATCHES_PER_TURN } from "./apply-C7mO7VkZ.mjs";
4
4
  import { z } from "zod";
5
5
  import Markdoc from "@markdoc/markdoc";
6
6
  import YAML from "yaml";
@@ -1214,58 +1214,42 @@ const VALID_FORM_TAGS = new Set([
1214
1214
  "documentation"
1215
1215
  ]);
1216
1216
  /**
1217
- * Parse harness configuration from frontmatter.
1218
- * YAML keys must be snake_case; they are mapped to camelCase internally.
1219
- * Unrecognized keys produce a parse error.
1217
+ * Format Zod validation errors for user-friendly display.
1220
1218
  */
1221
- function parseHarnessConfig(raw) {
1222
- if (!raw || typeof raw !== "object") return;
1223
- const config = raw;
1224
- const result = {};
1225
- const keyMap = {
1226
- max_turns: "maxTurns",
1227
- max_patches_per_turn: "maxPatchesPerTurn",
1228
- max_issues_per_turn: "maxIssuesPerTurn",
1229
- max_parallel_agents: "maxParallelAgents"
1230
- };
1231
- for (const [key, value] of Object.entries(config)) {
1232
- const camelKey = keyMap[key];
1233
- if (!camelKey) throw new MarkformParseError(`Unknown harness config key '${key}'. Valid keys: ${Object.keys(keyMap).join(", ")}`);
1234
- if (typeof value !== "number") throw new MarkformParseError(`Harness config key '${key}' must be a number, got ${typeof value}`);
1235
- result[camelKey] = value;
1236
- }
1237
- return Object.keys(result).length > 0 ? result : void 0;
1219
+ function formatZodError(error) {
1220
+ return error.issues.map((issue) => {
1221
+ const path = issue.path.map(String).join(".");
1222
+ return path ? `${path}: ${issue.message}` : issue.message;
1223
+ }).join("; ");
1238
1224
  }
1239
1225
  /**
1240
1226
  * Extract YAML frontmatter from Markdoc AST.
1241
1227
  * Uses Markdoc's native frontmatter extraction and parses the YAML.
1228
+ * Validates the markform section using Zod schema.
1242
1229
  */
1243
1230
  function extractFrontmatter(ast) {
1244
1231
  const rawFrontmatter = ast.attributes.frontmatter;
1245
1232
  if (!rawFrontmatter) return { frontmatter: {} };
1246
1233
  try {
1247
1234
  const frontmatter = YAML.parse(rawFrontmatter) ?? {};
1248
- const markformSection = frontmatter.markform;
1249
- if (!markformSection) return { frontmatter };
1250
- const harnessConfig = parseHarnessConfig(markformSection.harness);
1251
- let runMode;
1252
- const rawRunMode = markformSection.run_mode;
1253
- if (rawRunMode !== void 0) {
1254
- const parsed = RunModeSchema.safeParse(rawRunMode);
1255
- if (!parsed.success) throw new MarkformParseError(`Invalid run_mode: '${typeof rawRunMode === "string" ? rawRunMode : JSON.stringify(rawRunMode)}'. Must be one of: interactive, fill, research`);
1256
- runMode = parsed.data;
1257
- }
1258
- const description = typeof markformSection.description === "string" ? markformSection.description : void 0;
1235
+ const rawMarkformSection = frontmatter.markform;
1236
+ if (!rawMarkformSection) return { frontmatter };
1237
+ const validationResult = MarkformSectionInputSchema.safeParse(rawMarkformSection);
1238
+ if (!validationResult.success) throw new MarkformParseError(`Invalid markform frontmatter: ${formatZodError(validationResult.error)}`);
1239
+ const markformSection = validationResult.data;
1240
+ const harnessConfig = markformSection.harness && Object.keys(markformSection.harness).length > 0 ? transformHarnessConfigToTs(markformSection.harness) : void 0;
1259
1241
  return {
1260
1242
  frontmatter,
1261
1243
  metadata: {
1262
1244
  markformVersion: markformSection.spec ?? "MF/0.1",
1263
- roles: frontmatter.roles ?? [...DEFAULT_ROLES],
1264
- roleInstructions: frontmatter.role_instructions ?? DEFAULT_ROLE_INSTRUCTIONS,
1245
+ ...markformSection.title && { title: markformSection.title },
1246
+ ...markformSection.description && { description: markformSection.description },
1247
+ roles: markformSection.roles ?? frontmatter.roles ?? [...DEFAULT_ROLES],
1248
+ roleInstructions: markformSection.role_instructions ?? frontmatter.role_instructions ?? DEFAULT_ROLE_INSTRUCTIONS,
1265
1249
  ...harnessConfig && { harnessConfig },
1266
- ...runMode && { runMode }
1250
+ ...markformSection.run_mode && { runMode: markformSection.run_mode }
1267
1251
  },
1268
- description
1252
+ description: markformSection.description
1269
1253
  };
1270
1254
  } catch (error) {
1271
1255
  if (error instanceof MarkformParseError) throw error;
@@ -3285,10 +3269,11 @@ function generateSessionId() {
3285
3269
  //#endregion
3286
3270
  //#region src/harness/fillRecordCollector.ts
3287
3271
  /**
3288
- * Thread-safe collector for FillRecord data.
3272
+ * Collector for FillRecord data from async form fill operations.
3289
3273
  *
3290
- * Uses an append-only event log pattern for thread safety.
3291
- * Events are aggregated when getRecord() is called.
3274
+ * Uses an append-only event log pattern that safely handles interleaved
3275
+ * async callbacks from parallel execution. Events are aggregated when
3276
+ * getRecord() is called.
3292
3277
  */
3293
3278
  var FillRecordCollector = class {
3294
3279
  startedAt;
@@ -3331,7 +3316,8 @@ var FillRecordCollector = class {
3331
3316
  turnNumber: progress.turnNumber,
3332
3317
  patchesApplied: progress.patchesApplied,
3333
3318
  patchesRejected: progress.rejectedPatches?.length ?? 0,
3334
- issuesAddressed: progress.issuesShown
3319
+ issuesAddressed: progress.issuesShown,
3320
+ executionId: progress.executionId
3335
3321
  });
3336
3322
  }
3337
3323
  onLlmCallStart(call) {
@@ -3439,93 +3425,120 @@ var FillRecordCollector = class {
3439
3425
  };
3440
3426
  }
3441
3427
  buildTimeline() {
3428
+ const turnKey = (execId, turnNum) => `${execId}:${turnNum}`;
3429
+ const fillStartMs = new Date(this.startedAt).getTime();
3442
3430
  const turns = /* @__PURE__ */ new Map();
3443
3431
  const turnStartEvents = /* @__PURE__ */ new Map();
3444
3432
  const turnToolCalls = /* @__PURE__ */ new Map();
3445
3433
  const turnTokens = /* @__PURE__ */ new Map();
3446
3434
  for (const event of this.events) if (event.type === "turn_start") {
3447
- turnStartEvents.set(event.turnNumber, event);
3448
- turnToolCalls.set(event.turnNumber, []);
3449
- turnTokens.set(event.turnNumber, {
3435
+ const key = turnKey(event.executionId, event.turnNumber);
3436
+ turnStartEvents.set(key, event);
3437
+ turnToolCalls.set(key, []);
3438
+ turnTokens.set(key, {
3450
3439
  input: 0,
3451
3440
  output: 0
3452
3441
  });
3453
3442
  }
3454
3443
  const activeToolsByTurn = /* @__PURE__ */ new Map();
3455
- let currentTurnNumber = 0;
3444
+ const currentTurnKeyByExecutionId = /* @__PURE__ */ new Map();
3456
3445
  for (const event of this.events) if (event.type === "turn_start") {
3457
- currentTurnNumber = event.turnNumber;
3458
- activeToolsByTurn.set(currentTurnNumber, /* @__PURE__ */ new Map());
3446
+ const key = turnKey(event.executionId, event.turnNumber);
3447
+ currentTurnKeyByExecutionId.set(event.executionId, key);
3448
+ activeToolsByTurn.set(key, /* @__PURE__ */ new Map());
3459
3449
  } else if (event.type === "tool_start") {
3460
- const activeTurn = this.findTurnForExecutionId(event.executionId, turnStartEvents) ?? currentTurnNumber;
3461
- const activeTools = activeToolsByTurn.get(activeTurn);
3462
- if (activeTools) {
3463
- const key = `${event.executionId}:${event.name}`;
3464
- activeTools.set(key, {
3465
- start: event,
3466
- turnNumber: activeTurn
3467
- });
3450
+ const activeTurnKey = currentTurnKeyByExecutionId.get(event.executionId);
3451
+ if (activeTurnKey) {
3452
+ const activeTools = activeToolsByTurn.get(activeTurnKey);
3453
+ if (activeTools) {
3454
+ const toolKey = `${event.executionId}:${event.name}`;
3455
+ activeTools.set(toolKey, {
3456
+ start: event,
3457
+ turnKey: activeTurnKey
3458
+ });
3459
+ }
3468
3460
  }
3469
3461
  } else if (event.type === "tool_end") {
3470
- const key = `${event.executionId}:${event.name}`;
3471
- let foundTurn;
3472
- for (const [turnNum, activeTools] of activeToolsByTurn) if (activeTools.has(key)) {
3473
- foundTurn = turnNum;
3462
+ const toolKey = `${event.executionId}:${event.name}`;
3463
+ let foundTurnKey;
3464
+ for (const [tk, activeTools] of activeToolsByTurn) if (activeTools.has(toolKey)) {
3465
+ foundTurnKey = tk;
3474
3466
  break;
3475
3467
  }
3476
- if (foundTurn !== void 0) {
3477
- const activeTools = activeToolsByTurn.get(foundTurn);
3478
- const startInfo = activeTools.get(key);
3468
+ if (foundTurnKey !== void 0) {
3469
+ const activeTools = activeToolsByTurn.get(foundTurnKey);
3470
+ const startInfo = activeTools.get(toolKey);
3479
3471
  if (startInfo) {
3480
3472
  const toolCall = {
3481
3473
  tool: event.name,
3482
3474
  startedAt: startInfo.start.timestamp,
3483
3475
  completedAt: event.timestamp,
3476
+ startMs: new Date(startInfo.start.timestamp).getTime() - fillStartMs,
3484
3477
  durationMs: event.durationMs,
3485
3478
  success: !event.error,
3486
3479
  input: this.normalizeInput(startInfo.start.input),
3487
3480
  result: event.error ? { error: event.error } : this.extractResultCount(event.output)
3488
3481
  };
3489
- turnToolCalls.get(foundTurn)?.push(toolCall);
3490
- activeTools.delete(key);
3482
+ turnToolCalls.get(foundTurnKey)?.push(toolCall);
3483
+ activeTools.delete(toolKey);
3491
3484
  }
3492
3485
  }
3493
3486
  } else if (event.type === "llm_call_end") {
3494
- const turnNum = this.findTurnForExecutionId(event.executionId, turnStartEvents) ?? currentTurnNumber;
3495
- const tokens = turnTokens.get(turnNum);
3496
- if (tokens) {
3497
- tokens.input += event.inputTokens;
3498
- tokens.output += event.outputTokens;
3487
+ const tk = currentTurnKeyByExecutionId.get(event.executionId);
3488
+ if (tk) {
3489
+ const tokens = turnTokens.get(tk);
3490
+ if (tokens) {
3491
+ tokens.input += event.inputTokens;
3492
+ tokens.output += event.outputTokens;
3493
+ }
3499
3494
  }
3500
3495
  }
3496
+ const turnCompleteByKey = /* @__PURE__ */ new Map();
3501
3497
  for (const event of this.events) if (event.type === "turn_complete") {
3502
- const startEvent = turnStartEvents.get(event.turnNumber);
3503
- if (startEvent) {
3504
- const tokens = turnTokens.get(event.turnNumber) ?? {
3498
+ if (event.executionId) {
3499
+ const key = turnKey(event.executionId, event.turnNumber);
3500
+ turnCompleteByKey.set(key, event);
3501
+ } else for (const [key, startEvent] of turnStartEvents) if (startEvent.turnNumber === event.turnNumber && !turnCompleteByKey.has(key)) {
3502
+ turnCompleteByKey.set(key, event);
3503
+ break;
3504
+ }
3505
+ }
3506
+ for (const [key, startEvent] of turnStartEvents) {
3507
+ const completeEvent = turnCompleteByKey.get(key);
3508
+ if (completeEvent) {
3509
+ const tokens = turnTokens.get(key) ?? {
3505
3510
  input: 0,
3506
3511
  output: 0
3507
3512
  };
3508
- const toolCalls = turnToolCalls.get(event.turnNumber) ?? [];
3513
+ const toolCalls = turnToolCalls.get(key) ?? [];
3514
+ const turnStartMs = new Date(startEvent.timestamp).getTime();
3509
3515
  const entry = {
3510
- turnNumber: event.turnNumber,
3516
+ turnNumber: startEvent.turnNumber,
3511
3517
  order: startEvent.order,
3512
3518
  executionId: startEvent.executionId,
3513
3519
  startedAt: startEvent.timestamp,
3514
- completedAt: event.timestamp,
3515
- durationMs: new Date(event.timestamp).getTime() - new Date(startEvent.timestamp).getTime(),
3516
- issuesAddressed: event.issuesAddressed,
3517
- patchesApplied: event.patchesApplied,
3518
- patchesRejected: event.patchesRejected,
3520
+ completedAt: completeEvent.timestamp,
3521
+ startMs: turnStartMs - fillStartMs,
3522
+ durationMs: new Date(completeEvent.timestamp).getTime() - turnStartMs,
3523
+ issuesAddressed: completeEvent.issuesAddressed,
3524
+ patchesApplied: completeEvent.patchesApplied,
3525
+ patchesRejected: completeEvent.patchesRejected,
3519
3526
  tokens,
3520
3527
  toolCalls
3521
3528
  };
3522
- turns.set(event.turnNumber, entry);
3529
+ turns.set(key, entry);
3523
3530
  }
3524
3531
  }
3525
- return Array.from(turns.values()).sort((a, b) => a.turnNumber - b.turnNumber);
3532
+ return Array.from(turns.values()).sort((a, b) => new Date(a.startedAt).getTime() - new Date(b.startedAt).getTime());
3526
3533
  }
3527
- findTurnForExecutionId(executionId, turnStartEvents) {
3528
- for (const [turnNumber, event] of turnStartEvents) if (event.executionId === executionId) return turnNumber;
3534
+ findTurnKeyForExecutionId(executionId, turnStartEvents) {
3535
+ let latestKey;
3536
+ let latestTime = "";
3537
+ for (const [key, event] of turnStartEvents) if (event.executionId === executionId && event.timestamp > latestTime) {
3538
+ latestKey = key;
3539
+ latestTime = event.timestamp;
3540
+ }
3541
+ return latestKey;
3529
3542
  }
3530
3543
  normalizeInput(input) {
3531
3544
  if (input && typeof input === "object" && !Array.isArray(input)) return input;
@@ -9120,6 +9133,8 @@ var LiveAgent = class {
9120
9133
  webSearchTools = null;
9121
9134
  additionalTools;
9122
9135
  callbacks;
9136
+ executionId;
9137
+ toolChoice;
9123
9138
  constructor(config) {
9124
9139
  this.model = config.model;
9125
9140
  this.maxStepsPerTurn = config.maxStepsPerTurn ?? DEFAULT_MAX_STEPS_PER_TURN;
@@ -9129,6 +9144,8 @@ var LiveAgent = class {
9129
9144
  this.enableWebSearch = config.enableWebSearch;
9130
9145
  this.additionalTools = config.additionalTools ?? {};
9131
9146
  this.callbacks = config.callbacks;
9147
+ this.executionId = config.executionId ?? "0-serial";
9148
+ this.toolChoice = config.toolChoice ?? "required";
9132
9149
  if (this.enableWebSearch && this.provider) this.webSearchTools = loadWebSearchTools(this.provider);
9133
9150
  }
9134
9151
  /**
@@ -9168,32 +9185,37 @@ var LiveAgent = class {
9168
9185
  ...this.webSearchTools,
9169
9186
  ...this.additionalTools
9170
9187
  };
9171
- const tools = wrapToolsWithCallbacks(rawTools, this.callbacks, this.provider);
9188
+ const tools = wrapToolsWithCallbacks(rawTools, this.callbacks, this.provider, this.executionId);
9172
9189
  const modelId = this.model.modelId ?? "unknown";
9173
9190
  if (this.callbacks?.onLlmCallStart) try {
9174
9191
  this.callbacks.onLlmCallStart({
9175
9192
  model: modelId,
9176
- executionId: "0-serial"
9193
+ executionId: this.executionId
9177
9194
  });
9178
9195
  } catch {}
9179
- const result = await generateText({
9180
- model: this.model,
9181
- system: systemPrompt,
9182
- prompt: contextPrompt,
9183
- tools,
9184
- stopWhen: stepCountIs(this.maxStepsPerTurn)
9185
- });
9196
+ let result;
9197
+ try {
9198
+ result = await generateText({
9199
+ model: this.model,
9200
+ system: systemPrompt,
9201
+ prompt: contextPrompt,
9202
+ tools,
9203
+ toolChoice: this.toolChoice,
9204
+ stopWhen: stepCountIs(this.maxStepsPerTurn)
9205
+ });
9206
+ } catch (error) {
9207
+ throw wrapApiError(error, this.provider ?? "unknown", modelId);
9208
+ }
9186
9209
  if (this.callbacks?.onLlmCallEnd) try {
9187
9210
  this.callbacks.onLlmCallEnd({
9188
9211
  model: modelId,
9189
9212
  inputTokens: result.usage?.inputTokens ?? 0,
9190
9213
  outputTokens: result.usage?.outputTokens ?? 0,
9191
- executionId: "0-serial"
9214
+ executionId: this.executionId
9192
9215
  });
9193
9216
  } catch {}
9194
9217
  const patches = [];
9195
9218
  const toolCallCounts = /* @__PURE__ */ new Map();
9196
- const executionId = "0-serial";
9197
9219
  for (const step of result.steps) for (const toolCall of step.toolCalls) {
9198
9220
  const count = toolCallCounts.get(toolCall.toolName) ?? 0;
9199
9221
  toolCallCounts.set(toolCall.toolName, count + 1);
@@ -9204,7 +9226,7 @@ var LiveAgent = class {
9204
9226
  this.callbacks.onToolStart({
9205
9227
  name: FILL_FORM_TOOL_NAME,
9206
9228
  input,
9207
- executionId
9229
+ executionId: this.executionId
9208
9230
  });
9209
9231
  } catch {}
9210
9232
  patches.push(...input.patches);
@@ -9213,7 +9235,7 @@ var LiveAgent = class {
9213
9235
  name: FILL_FORM_TOOL_NAME,
9214
9236
  output: { patchCount: input.patches.length },
9215
9237
  durationMs: Date.now() - startTime,
9216
- executionId,
9238
+ executionId: this.executionId,
9217
9239
  error: void 0
9218
9240
  });
9219
9241
  } catch {}
@@ -9458,12 +9480,12 @@ function findField(form, fieldId) {
9458
9480
  * Only wraps tools that have an execute function.
9459
9481
  * Declarative tools (schema only) are passed through unchanged.
9460
9482
  */
9461
- function wrapToolsWithCallbacks(tools, callbacks, provider) {
9483
+ function wrapToolsWithCallbacks(tools, callbacks, provider, executionId = "0-serial") {
9462
9484
  if (!callbacks?.onToolStart && !callbacks?.onToolEnd && !callbacks?.onWebSearch) return tools;
9463
9485
  const wrapped = {};
9464
9486
  for (const [name, tool] of Object.entries(tools)) {
9465
9487
  const execute = tool.execute;
9466
- if (typeof execute === "function") wrapped[name] = wrapTool(name, tool, execute, callbacks, provider);
9488
+ if (typeof execute === "function") wrapped[name] = wrapTool(name, tool, execute, callbacks, provider, executionId);
9467
9489
  else wrapped[name] = tool;
9468
9490
  }
9469
9491
  return wrapped;
@@ -9471,12 +9493,11 @@ function wrapToolsWithCallbacks(tools, callbacks, provider) {
9471
9493
  /**
9472
9494
  * Wrap a single tool with callbacks.
9473
9495
  */
9474
- function wrapTool(name, tool, originalExecute, callbacks, provider) {
9496
+ function wrapTool(name, tool, originalExecute, callbacks, provider, executionId) {
9475
9497
  return {
9476
9498
  ...tool,
9477
9499
  execute: async (input) => {
9478
9500
  const startTime = Date.now();
9479
- const executionId = "0-serial";
9480
9501
  if (callbacks.onToolStart) try {
9481
9502
  callbacks.onToolStart({
9482
9503
  name,
@@ -9853,7 +9874,7 @@ var ParallelHarness = class {
9853
9874
  this.config.onBatchStart?.(batch.batchId);
9854
9875
  const batchIssues = inspect(this.form).issues;
9855
9876
  const maxConcurrent = this.config.maxParallelAgents ?? DEFAULT_MAX_PARALLEL_AGENTS;
9856
- const agentPromises = [];
9877
+ const agentFactories = [];
9857
9878
  const itemFieldIds = /* @__PURE__ */ new Map();
9858
9879
  for (let i = 0; i < batchItems.length; i++) {
9859
9880
  const item = batchItems[i];
@@ -9861,7 +9882,7 @@ var ParallelHarness = class {
9861
9882
  const targetFieldIds = getFieldIdsForItem(this.form, item);
9862
9883
  itemFieldIds.set(i, targetFieldIds);
9863
9884
  if (scopedIssues.length === 0) {
9864
- agentPromises.push(Promise.resolve({ patches: [] }));
9885
+ agentFactories.push(() => Promise.resolve({ patches: [] }));
9865
9886
  continue;
9866
9887
  }
9867
9888
  const request = {
@@ -9871,9 +9892,9 @@ var ParallelHarness = class {
9871
9892
  issues: scopedIssues
9872
9893
  };
9873
9894
  const agent = this.config.agentFactory ? this.config.agentFactory(request) : primaryAgent;
9874
- agentPromises.push(agent.fillFormTool(scopedIssues, this.form, maxPatches));
9895
+ agentFactories.push(() => agent.fillFormTool(scopedIssues, this.form, maxPatches));
9875
9896
  }
9876
- const results = await runWithConcurrency(agentPromises, maxConcurrent);
9897
+ const results = await runWithConcurrency(agentFactories, maxConcurrent);
9877
9898
  const allPatches = [];
9878
9899
  for (const result of results) if (result.status === "fulfilled") allPatches.push(...result.value.patches);
9879
9900
  else errors.push(`Parallel agent error: ${result.reason}`);
@@ -9912,20 +9933,25 @@ var ParallelHarness = class {
9912
9933
  }
9913
9934
  };
9914
9935
  /**
9915
- * Run promises with a concurrency limit.
9936
+ * Run promise factories with a concurrency limit.
9937
+ * Takes an array of functions that create promises (factories), not already-started promises.
9938
+ * This ensures concurrency limiting actually works - promises are started lazily.
9916
9939
  * Returns results in the same order as input.
9917
9940
  */
9918
- async function runWithConcurrency(promises, maxConcurrent) {
9919
- if (maxConcurrent >= promises.length) return Promise.allSettled(promises);
9920
- const results = new Array(promises.length);
9941
+ async function runWithConcurrency(factories, maxConcurrent) {
9942
+ if (factories.length === 0) return [];
9943
+ if (maxConcurrent >= factories.length) return Promise.allSettled(factories.map(async (f) => f()));
9944
+ const results = new Array(factories.length);
9921
9945
  let nextIndex = 0;
9922
9946
  async function runNext() {
9923
- while (nextIndex < promises.length) {
9947
+ while (nextIndex < factories.length) {
9924
9948
  const idx = nextIndex++;
9949
+ const factory = factories[idx];
9950
+ if (!factory) continue;
9925
9951
  try {
9926
9952
  results[idx] = {
9927
9953
  status: "fulfilled",
9928
- value: await promises[idx]
9954
+ value: await factory()
9929
9955
  };
9930
9956
  } catch (reason) {
9931
9957
  results[idx] = {
@@ -9935,7 +9961,7 @@ async function runWithConcurrency(promises, maxConcurrent) {
9935
9961
  }
9936
9962
  }
9937
9963
  }
9938
- const workers = Array.from({ length: Math.min(maxConcurrent, promises.length) }, () => runNext());
9964
+ const workers = Array.from({ length: Math.min(maxConcurrent, factories.length) }, () => runNext());
9939
9965
  await Promise.all(workers);
9940
9966
  return results;
9941
9967
  }
@@ -9949,6 +9975,43 @@ function createParallelHarness(form, config) {
9949
9975
  //#endregion
9950
9976
  //#region src/harness/programmaticFill.ts
9951
9977
  /**
9978
+ * Execution ID format for tracking parallel and serial execution threads.
9979
+ *
9980
+ * Format: `eid:{type}:o{order}[:{batchId}:i{index}]`
9981
+ *
9982
+ * Components:
9983
+ * - `eid:` - Prefix identifying this as an execution ID
9984
+ * - `{type}` - Either `serial` or `batch`
9985
+ * - `o{order}` - Order level (e.g., `o0`, `o10`)
9986
+ * - For batch only: `:{batchId}:i{index}` - Batch name and item index
9987
+ *
9988
+ * Examples:
9989
+ * - `eid:serial:o0` - Serial execution at order level 0
9990
+ * - `eid:batch:o0:research:i0` - First item in "research" batch at order 0
9991
+ * - `eid:batch:o0:research:i3` - Fourth item in "research" batch at order 0
9992
+ * - `eid:serial:o10` - Serial execution at order level 10
9993
+ *
9994
+ * Design principles:
9995
+ * - Self-identifying: Clear `eid:` prefix
9996
+ * - Structured: Type comes first for easy filtering
9997
+ * - Explicit: Each component has a prefix (`o` for order, `i` for index)
9998
+ * - Parseable: Can be split on `:` to extract components
9999
+ */
10000
+ /** Prefix for all execution IDs */
10001
+ const EXECUTION_ID_PREFIX = "eid";
10002
+ /**
10003
+ * Create an execution ID for serial execution at an order level.
10004
+ */
10005
+ function serialExecutionId(order) {
10006
+ return `${EXECUTION_ID_PREFIX}:serial:o${order}`;
10007
+ }
10008
+ /**
10009
+ * Create an execution ID for a batch item at an order level.
10010
+ */
10011
+ function batchExecutionId(order, batchId, itemIndex) {
10012
+ return `${EXECUTION_ID_PREFIX}:batch:o${order}:${batchId}:i${itemIndex}`;
10013
+ }
10014
+ /**
9952
10015
  * Get current progress counts for a form.
9953
10016
  * Runs inspect to get issues, then computes progress summary.
9954
10017
  */
@@ -10250,7 +10313,8 @@ async function fillForm(options) {
10250
10313
  enableWebSearch: options.enableWebSearch,
10251
10314
  additionalTools: options.additionalTools,
10252
10315
  callbacks: mergedCallbacks,
10253
- maxStepsPerTurn: options.maxStepsPerTurn
10316
+ maxStepsPerTurn: options.maxStepsPerTurn,
10317
+ toolChoice: options.toolChoice
10254
10318
  });
10255
10319
  let turnCount = startingTurnNumber;
10256
10320
  let turnsThisCall = 0;
@@ -10361,7 +10425,8 @@ async function fillForm(options) {
10361
10425
  stats,
10362
10426
  issues: turnIssues,
10363
10427
  patches,
10364
- rejectedPatches: stepResult.rejectedPatches ?? []
10428
+ rejectedPatches: stepResult.rejectedPatches ?? [],
10429
+ executionId: "0-serial"
10365
10430
  });
10366
10431
  } catch {}
10367
10432
  if (!stepResult.isComplete && !harness.hasReachedMaxTurns()) stepResult = harness.step();
@@ -10402,16 +10467,20 @@ async function fillFormParallel(form, model, provider, options, initialPatches,
10402
10467
  const targetRoles = options.targetRoles ?? [AGENT_ROLE];
10403
10468
  let totalPatches = initialPatches;
10404
10469
  let turnCount = startingTurnNumber;
10405
- const primaryAgent = options._testAgent ?? createLiveAgent({
10406
- model,
10407
- systemPromptAddition: options.systemPromptAddition,
10408
- targetRole: targetRoles[0] ?? AGENT_ROLE,
10409
- provider,
10410
- enableWebSearch: options.enableWebSearch,
10411
- additionalTools: options.additionalTools,
10412
- callbacks: mergedCallbacks,
10413
- maxStepsPerTurn: options.maxStepsPerTurn
10414
- });
10470
+ const createAgentForExecution = (executionId) => {
10471
+ return options._testAgent ?? createLiveAgent({
10472
+ model,
10473
+ systemPromptAddition: options.systemPromptAddition,
10474
+ targetRole: targetRoles[0] ?? AGENT_ROLE,
10475
+ provider,
10476
+ enableWebSearch: options.enableWebSearch,
10477
+ additionalTools: options.additionalTools,
10478
+ callbacks: mergedCallbacks,
10479
+ maxStepsPerTurn: options.maxStepsPerTurn,
10480
+ executionId,
10481
+ toolChoice: options.toolChoice
10482
+ });
10483
+ };
10415
10484
  for (const order of plan.orderLevels) {
10416
10485
  if (options.signal?.aborted) {
10417
10486
  let record;
@@ -10441,7 +10510,8 @@ async function fillFormParallel(form, model, provider, options, initialPatches,
10441
10510
  } catch {}
10442
10511
  const serialItems = plan.looseSerial.filter((i) => i.order === order);
10443
10512
  if (serialItems.length > 0) {
10444
- const result = await runMultiTurnForItems(form, primaryAgent, serialItems, targetRoles, maxPatchesPerTurn, maxIssuesPerTurn, maxTurnsTotal, turnCount, options, order, `${order}-serial`, mergedCallbacks);
10513
+ const serialExecId = serialExecutionId(order);
10514
+ const result = await runMultiTurnForItems(form, createAgentForExecution(serialExecId), serialItems, targetRoles, maxPatchesPerTurn, maxIssuesPerTurn, maxTurnsTotal, turnCount, options, order, serialExecId, mergedCallbacks);
10445
10515
  totalPatches += result.patchesApplied;
10446
10516
  turnCount += result.turnsUsed;
10447
10517
  if (result.aborted && result.status) {
@@ -10466,17 +10536,11 @@ async function fillFormParallel(form, model, provider, options, initialPatches,
10466
10536
  });
10467
10537
  } catch {}
10468
10538
  const results = await runWithConcurrency(batchItems.map((item, itemIndex) => {
10469
- return runMultiTurnForItems(form, options._testAgent ?? createLiveAgent({
10470
- model,
10471
- systemPromptAddition: options.systemPromptAddition,
10472
- targetRole: targetRoles[0] ?? AGENT_ROLE,
10473
- provider,
10474
- enableWebSearch: options.enableWebSearch,
10475
- additionalTools: options.additionalTools,
10476
- callbacks: mergedCallbacks,
10477
- maxStepsPerTurn: options.maxStepsPerTurn
10478
- }), [item], targetRoles, maxPatchesPerTurn, maxIssuesPerTurn, maxTurnsTotal, turnCount, options, order, `${order}-batch-${batch.batchId}-${itemIndex}`, mergedCallbacks);
10479
- }).map((p) => p), maxParallelAgents);
10539
+ return () => {
10540
+ const batchExecId = batchExecutionId(order, batch.batchId, itemIndex);
10541
+ return runMultiTurnForItems(form, createAgentForExecution(batchExecId), [item], targetRoles, maxPatchesPerTurn, maxIssuesPerTurn, maxTurnsTotal, turnCount, options, order, batchExecId, mergedCallbacks);
10542
+ };
10543
+ }), maxParallelAgents);
10480
10544
  let batchPatches = 0;
10481
10545
  const batchErrors = [];
10482
10546
  for (const result of results) if (result.status === "fulfilled") {
@@ -10608,7 +10672,8 @@ async function runMultiTurnForItems(form, agent, items, targetRoles, maxPatchesP
10608
10672
  stats: response.stats,
10609
10673
  issues: scopedIssues,
10610
10674
  patches: response.patches,
10611
- rejectedPatches: previousRejections ?? []
10675
+ rejectedPatches: previousRejections ?? [],
10676
+ executionId
10612
10677
  });
10613
10678
  } catch {}
10614
10679
  }
@@ -10774,6 +10839,7 @@ const ToolCallRecordSchema = z.object({
10774
10839
  tool: z.string(),
10775
10840
  startedAt: z.string().datetime(),
10776
10841
  completedAt: z.string().datetime(),
10842
+ startMs: z.number().int().nonnegative(),
10777
10843
  durationMs: z.number().int().nonnegative(),
10778
10844
  success: z.boolean(),
10779
10845
  input: z.record(z.string(), z.unknown()),
@@ -10791,6 +10857,7 @@ const TimelineEntrySchema = z.object({
10791
10857
  executionId: z.string(),
10792
10858
  startedAt: z.string().datetime(),
10793
10859
  completedAt: z.string().datetime(),
10860
+ startMs: z.number().int().nonnegative(),
10794
10861
  durationMs: z.number().int().nonnegative(),
10795
10862
  issuesAddressed: z.number().int().nonnegative(),
10796
10863
  patchesApplied: z.number().int().nonnegative(),
@@ -11044,8 +11111,8 @@ function validateResearchForm(form) {
11044
11111
  //#endregion
11045
11112
  //#region src/index.ts
11046
11113
  /** Markform version (injected at build time). */
11047
- const VERSION = "0.1.20";
11114
+ const VERSION = "0.1.22";
11048
11115
 
11049
11116
  //#endregion
11050
11117
  export { FormHarness as A, serializeScopeRef as B, resolveModel as C, computeExecutionPlan as D, FillRecordCollector as E, getFieldId as F, injectCheckboxIds as G, formToJsonSchema as H, isCellRef as I, findEnclosingHeadings as J, injectHeaderIds as K, isFieldRef as L, coerceInputContext as M, coerceToFieldPatch as N, MockAgent as O, findFieldById as P, isQualifiedRef as R, getProviderNames as S, createLiveAgent as T, parseForm as U, fieldToJsonSchema as V, findAllCheckboxes as W, parseMarkdownTable as X, parseCellValue as Y, parseRawTable as Z, fillForm as _, ExecutionMetadataSchema as a, scopeIssuesForItem as b, TimelineEntrySchema as c, ToolCallRecordSchema as d, ToolStatsSchema as f, resolveHarnessConfig as g, formatFillRecordSummary as h, runResearch as i, createHarness as j, createMockAgent as k, TimingBreakdownItemSchema as l, stripUnstableFillRecordFields as m, isResearchForm as n, FillRecordSchema as o, ToolSummarySchema as p, findAllHeadings as q, validateResearchForm as r, FillRecordStatusSchema as s, VERSION as t, TimingBreakdownSchema as u, ParallelHarness as v, buildMockWireFormat as w, getProviderInfo as x, createParallelHarness as y, parseScopeRef as z };
11051
- //# sourceMappingURL=src-wR7GoftB.mjs.map
11118
+ //# sourceMappingURL=src-CbRnGzMK.mjs.map