perchai-cli 2.4.32 → 2.4.34

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 (2) hide show
  1. package/dist/perch.mjs +598 -90
  2. package/package.json +1 -1
package/dist/perch.mjs CHANGED
@@ -76219,6 +76219,7 @@ function getToolDisplayName(toolName) {
76219
76219
  var NON_MODULE_TOOL_OWNERS, TOOL_RISK, TOOL_DISPLAY_NAMES;
76220
76220
  var init_catalog = __esm({
76221
76221
  "features/perchTerminal/runtime/toolSystem/catalog.ts"() {
76222
+ "use strict";
76222
76223
  init_toolNames();
76223
76224
  NON_MODULE_TOOL_OWNERS = {
76224
76225
  [TOOL_NAMES.listSources]: "lane",
@@ -83126,7 +83127,6 @@ function listFinancialRoleIds() {
83126
83127
  var FINANCIAL_ROLE_REGISTRY, evidenceScoutManifest;
83127
83128
  var init_financialRoles = __esm({
83128
83129
  "features/perchTerminal/agentPlatform/financialRoles.ts"() {
83129
- "use strict";
83130
83130
  FINANCIAL_ROLE_REGISTRY = /* @__PURE__ */ new Map();
83131
83131
  evidenceScoutManifest = {
83132
83132
  workerId: "evidence_scout",
@@ -86724,6 +86724,7 @@ function truncateHistoryLine(value, max2) {
86724
86724
  }
86725
86725
  var init_operatorTruth = __esm({
86726
86726
  "features/perchTerminal/runtime/operatorTruth.ts"() {
86727
+ "use strict";
86727
86728
  }
86728
86729
  });
86729
86730
 
@@ -91010,7 +91011,6 @@ Final answers lead with findings, name artifacts or delivery status, and give on
91010
91011
  var MARKET_DESK_TOOL_NAMES;
91011
91012
  var init_marketDeskAccess = __esm({
91012
91013
  "features/perchTerminal/runtime/marketDesk/marketDeskAccess.ts"() {
91013
- "use strict";
91014
91014
  init_toolNames();
91015
91015
  MARKET_DESK_TOOL_NAMES = /* @__PURE__ */ new Set([
91016
91016
  TOOL_NAMES.getMarketSignal,
@@ -135930,13 +135930,38 @@ function getDesktopToolDefinitions() {
135930
135930
  type: "function",
135931
135931
  function: {
135932
135932
  name: TOOL_NAMES.spawnWorker,
135933
- description: "Spawn one bounded specialist sub-agent. Use simple capability-shaped workers when possible: source_reader for scoped local-file evidence, math_checker for exact sandbox totals/reconciliation, contradiction_verifier for conflict checks, financial_writer/general_writer for narrative from provided facts. Best for independent work where the main loop only needs the conclusion. Do not use context-only managed analysts as raw-folder readers; give them pre-read JSON. The main loop remains owner of decisions, writing, and real actions. Prefer dispatch_agent for parallel batches. Do not repeat source_reader after it returns a DONE/PARTIAL manifest; advance to math_checker or verification.",
135933
+ description: "Spawn one bounded specialist sub-agent. Use simple capability-shaped workers when possible: source_reader for scoped local-file evidence, math_checker for exact sandbox totals/reconciliation, contradiction_verifier for conflict checks, financial_writer/general_writer for narrative from provided facts. When no registered worker fits, pass custom{} instead of workerId to spawn a one-off ad-hoc worker with its own name and minimal tool scope. Best for independent work where the main loop only needs the conclusion. Do not use context-only managed analysts as raw-folder readers; give them pre-read JSON. The main loop remains owner of decisions, writing, and real actions. Prefer dispatch_agent for parallel batches. Do not repeat source_reader after it returns a DONE/PARTIAL manifest; advance to math_checker or verification.",
135934
135934
  parameters: {
135935
135935
  type: "object",
135936
135936
  properties: {
135937
135937
  workerId: {
135938
135938
  type: "string",
135939
- description: "Registered worker or role id. Simple roles: source_reader, math_checker, contradiction_verifier, financial_writer, general_writer. Quill roles: legal_source_scout, legal_case_scout, source_verifier, doc_writer, email_sender, calendar_scheduler."
135939
+ description: "Registered worker or role id. Simple roles: source_reader, math_checker, contradiction_verifier, financial_writer, general_writer. Quill roles: legal_source_scout, legal_case_scout, source_verifier, doc_writer, email_sender, calendar_scheduler. Omit when passing custom."
135940
+ },
135941
+ custom: {
135942
+ type: "object",
135943
+ description: "Inline ad-hoc worker spec, used INSTEAD of workerId when no registered worker fits. The worker is run-scoped: registered for this spawn, unregistered after. Orchestration tools are never granted; write tools require writesWorkspace=true.",
135944
+ properties: {
135945
+ displayName: {
135946
+ type: "string",
135947
+ description: 'Short task-specific role name, e.g. "Changelog Summarizer".'
135948
+ },
135949
+ allowedTools: {
135950
+ type: "array",
135951
+ items: { type: "string" },
135952
+ description: "Minimal tool names this worker needs. Unknown/forbidden tools are dropped."
135953
+ },
135954
+ writesWorkspace: {
135955
+ type: "boolean",
135956
+ description: "True only if the worker must edit files inside the workspace root."
135957
+ },
135958
+ outputContract: {
135959
+ type: "string",
135960
+ description: "Short contract for the worker's structured output."
135961
+ }
135962
+ },
135963
+ required: ["displayName"],
135964
+ additionalProperties: false
135940
135965
  },
135941
135966
  objective: {
135942
135967
  type: "string",
@@ -135965,7 +135990,7 @@ function getDesktopToolDefinitions() {
135965
135990
  description: "Optional child loop cap. Defaults to the worker manifest cap."
135966
135991
  }
135967
135992
  },
135968
- required: ["workerId", "objective"],
135993
+ required: ["objective"],
135969
135994
  additionalProperties: false
135970
135995
  }
135971
135996
  }
@@ -205268,7 +205293,159 @@ var init_sendWorkerMessage2 = __esm({
205268
205293
  }
205269
205294
  });
205270
205295
 
205296
+ // features/perchTerminal/runtime/workers/adhocManifest.ts
205297
+ function buildAdhocWorkerManifest(spec) {
205298
+ const displayName = spec.displayName.replace(/\s+/g, " ").trim().slice(0, MAX_NAME_CHARS) || "Ad-hoc Worker";
205299
+ const writesWorkspace = spec.writesWorkspace === true;
205300
+ const outputContract = (spec.outputContract ?? "").replace(/\s+/g, " ").trim().slice(0, MAX_CONTRACT_CHARS) || DEFAULT_OUTPUT_CONTRACT;
205301
+ const requested = [...new Set(spec.allowedTools ?? [])].filter(
205302
+ (tool) => typeof tool === "string" && tool.trim().length > 0
205303
+ );
205304
+ const droppedTools = [];
205305
+ const allowedTools = requested.filter((tool) => {
205306
+ if (ADHOC_FORBIDDEN_TOOLS.includes(tool)) {
205307
+ droppedTools.push(tool);
205308
+ return false;
205309
+ }
205310
+ if (!writesWorkspace && ADHOC_WRITE_TOOLS.has(tool)) {
205311
+ droppedTools.push(tool);
205312
+ return false;
205313
+ }
205314
+ return true;
205315
+ });
205316
+ const maxIterations = clampInt(spec.maxIterations, 1, ADHOC_MAX_ITERATIONS, 6);
205317
+ const workerId = uniqueAdhocWorkerId(displayName);
205318
+ const manifest = {
205319
+ workerId,
205320
+ name: displayName,
205321
+ description: `Ad-hoc worker (run-scoped): ${displayName}.`,
205322
+ lane: writesWorkspace ? "code_worker" : "fast_worker",
205323
+ systemPrompt: [
205324
+ `You are ${displayName}, one bounded ad-hoc worker spawned for a single objective.`,
205325
+ "Work only on the objective you are given. Never delegate, never spawn other workers, never start suites.",
205326
+ `Respect the output contract exactly: ${outputContract}.`,
205327
+ writesWorkspace ? "You may edit files, but only inside the active workspace root, and only edits the objective requires." : "You are read-only: never write, move, or delete files."
205328
+ ].join("\n"),
205329
+ allowedTools,
205330
+ maxIterations,
205331
+ callableAgents: [],
205332
+ outputContract
205333
+ };
205334
+ return { manifest, droppedTools };
205335
+ }
205336
+ function uniqueAdhocWorkerId(displayName) {
205337
+ const slug = displayName.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 24) || "worker";
205338
+ const base = `adhoc_${Date.now().toString(36)}_${slug}`;
205339
+ let candidate = base;
205340
+ let bump = 0;
205341
+ while (getWorkerManifest(candidate)) {
205342
+ bump += 1;
205343
+ candidate = `${base}_${bump}`;
205344
+ }
205345
+ return candidate;
205346
+ }
205347
+ function clampInt(value, min2, max2, fallback) {
205348
+ const num = typeof value === "number" && Number.isFinite(value) ? Math.floor(value) : fallback;
205349
+ return Math.min(Math.max(num, min2), max2);
205350
+ }
205351
+ var ADHOC_FORBIDDEN_TOOLS, ADHOC_WRITE_TOOLS, ADHOC_MAX_ITERATIONS, MAX_NAME_CHARS, MAX_CONTRACT_CHARS, DEFAULT_OUTPUT_CONTRACT;
205352
+ var init_adhocManifest = __esm({
205353
+ "features/perchTerminal/runtime/workers/adhocManifest.ts"() {
205354
+ "use strict";
205355
+ init_toolNames();
205356
+ init_registry();
205357
+ init_types();
205358
+ ADHOC_FORBIDDEN_TOOLS = [
205359
+ ...WORKER_DISALLOWED_TOOLS,
205360
+ TOOL_NAMES.dispatchAgent,
205361
+ TOOL_NAMES.sendWorkerMessage,
205362
+ TOOL_NAMES.taskStop,
205363
+ TOOL_NAMES.runSuite,
205364
+ TOOL_NAMES.listSuiteCatalog,
205365
+ TOOL_NAMES.proposeSuitePlan,
205366
+ TOOL_NAMES.executeSuitePlan,
205367
+ TOOL_NAMES.proposeWork,
205368
+ TOOL_NAMES.executeWork,
205369
+ TOOL_NAMES.proposePlan
205370
+ ];
205371
+ ADHOC_WRITE_TOOLS = /* @__PURE__ */ new Set([
205372
+ TOOL_NAMES.writeLocalFile,
205373
+ TOOL_NAMES.editLocalFile,
205374
+ TOOL_NAMES.deleteLocalFile,
205375
+ TOOL_NAMES.moveLocalFile,
205376
+ TOOL_NAMES.copyLocalFile,
205377
+ TOOL_NAMES.createDirectory
205378
+ ]);
205379
+ ADHOC_MAX_ITERATIONS = 10;
205380
+ MAX_NAME_CHARS = 40;
205381
+ MAX_CONTRACT_CHARS = 240;
205382
+ DEFAULT_OUTPUT_CONTRACT = "Return a short structured summary of what was found or done.";
205383
+ }
205384
+ });
205385
+
205386
+ // features/perchTerminal/runtime/flock/flockNicknames.ts
205387
+ function seedFromFlockId(flockId) {
205388
+ let hash = 2166136261;
205389
+ for (let i = 0; i < flockId.length; i++) {
205390
+ hash ^= flockId.charCodeAt(i);
205391
+ hash = Math.imul(hash, 16777619);
205392
+ }
205393
+ return hash >>> 0;
205394
+ }
205395
+ function flockNicknameRotation(flockId) {
205396
+ const names = [...FLOCK_NICKNAMES];
205397
+ let state = seedFromFlockId(flockId) || 1;
205398
+ const nextRandom = () => {
205399
+ state = Math.imul(state, 1664525) + 1013904223 >>> 0;
205400
+ return state / 4294967296;
205401
+ };
205402
+ for (let i = names.length - 1; i > 0; i--) {
205403
+ const j = Math.floor(nextRandom() * (i + 1));
205404
+ [names[i], names[j]] = [names[j], names[i]];
205405
+ }
205406
+ return names;
205407
+ }
205408
+ function flockNicknameFor(flockId, index) {
205409
+ const rotation = flockNicknameRotation(flockId);
205410
+ return rotation[index % rotation.length];
205411
+ }
205412
+ var FLOCK_NICKNAMES;
205413
+ var init_flockNicknames = __esm({
205414
+ "features/perchTerminal/runtime/flock/flockNicknames.ts"() {
205415
+ "use strict";
205416
+ FLOCK_NICKNAMES = [
205417
+ "Little Dum Dum",
205418
+ "Molly",
205419
+ "Boomer",
205420
+ "Shawarma",
205421
+ "Curie",
205422
+ "Eagle",
205423
+ "Noether",
205424
+ "Wonky",
205425
+ "Lovelace",
205426
+ "Bell",
205427
+ "Turing",
205428
+ "Faraday",
205429
+ "Biscuit",
205430
+ "Ice Cream",
205431
+ "Minsky"
205432
+ ];
205433
+ }
205434
+ });
205435
+
205271
205436
  // features/perchTerminal/runtime/toolSystem/tools/workers/spawnWorker.ts
205437
+ function parseAdhocSpec(raw) {
205438
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
205439
+ const record = raw;
205440
+ if (typeof record.displayName !== "string" || !record.displayName.trim()) return null;
205441
+ return {
205442
+ displayName: record.displayName,
205443
+ allowedTools: Array.isArray(record.allowedTools) ? record.allowedTools.filter((tool) => typeof tool === "string") : void 0,
205444
+ writesWorkspace: record.writesWorkspace === true,
205445
+ maxIterations: typeof record.maxIterations === "number" ? record.maxIterations : void 0,
205446
+ outputContract: typeof record.outputContract === "string" ? record.outputContract : void 0
205447
+ };
205448
+ }
205272
205449
  var spawnWorkerTool;
205273
205450
  var init_spawnWorker = __esm({
205274
205451
  "features/perchTerminal/runtime/toolSystem/tools/workers/spawnWorker.ts"() {
@@ -205278,12 +205455,49 @@ var init_spawnWorker = __esm({
205278
205455
  init_agentDispatch();
205279
205456
  init_localScope();
205280
205457
  init_toolNames();
205458
+ init_adhocManifest();
205459
+ init_registry();
205460
+ init_flockNicknames();
205281
205461
  spawnWorkerTool = {
205282
205462
  name: TOOL_NAMES.spawnWorker,
205283
205463
  classification: { native: false },
205284
205464
  handler: async (args, ctx) => {
205285
- const workerId = String(args.workerId ?? "");
205465
+ let workerId = String(args.workerId ?? "");
205286
205466
  const objective = String(args.objective ?? "");
205467
+ let adhocManifestId = null;
205468
+ let adhocNickname = null;
205469
+ const customSpec = parseAdhocSpec(args.custom);
205470
+ if (!workerId && args.custom !== void 0 && !customSpec) {
205471
+ return {
205472
+ ok: false,
205473
+ workerId: "",
205474
+ summary: "spawn_worker custom spec is invalid: custom.displayName (non-empty string) is required.",
205475
+ errorCode: "adhoc_spec_invalid"
205476
+ };
205477
+ }
205478
+ if (!workerId && customSpec) {
205479
+ const { manifest, droppedTools } = buildAdhocWorkerManifest(customSpec);
205480
+ registerWorkerManifest(manifest);
205481
+ adhocManifestId = manifest.workerId;
205482
+ adhocNickname = flockNicknameFor(manifest.workerId, 0);
205483
+ workerId = manifest.workerId;
205484
+ if (droppedTools.length > 0 && ctx.onEvent) {
205485
+ ctx.onEvent({
205486
+ type: "diagnostic",
205487
+ code: "adhoc_worker_tools_dropped",
205488
+ message: `Ad-hoc worker "${manifest.name}" was not granted: ${droppedTools.join(", ")} (orchestration or write tools outside its scope).`,
205489
+ ts: (/* @__PURE__ */ new Date()).toISOString()
205490
+ });
205491
+ }
205492
+ }
205493
+ if (!workerId) {
205494
+ return {
205495
+ ok: false,
205496
+ workerId: "",
205497
+ summary: "spawn_worker requires either workerId (registered worker) or custom { displayName, allowedTools, ... } for an ad-hoc worker.",
205498
+ errorCode: "worker_id_missing"
205499
+ };
205500
+ }
205287
205501
  const context = typeof args.context === "string" || typeof args.context === "object" && args.context !== null && !Array.isArray(args.context) ? args.context : void 0;
205288
205502
  const lane = typeof args.lane === "string" ? args.lane : void 0;
205289
205503
  const maxIterations = typeof args.maxIterations === "number" ? args.maxIterations : void 0;
@@ -205314,26 +205528,31 @@ var init_spawnWorker = __esm({
205314
205528
  Proceed with the available context. If a missing fact is essential, ask the user for that fact instead of stopping.` : "",
205315
205529
  capabilityNote ? `Capability note: ${capabilityNote}` : ""
205316
205530
  ].filter(Boolean).join("\n\n");
205317
- return spawnWorker(
205318
- { workerId, objective: workerObjective, context: enrichedContext, lane, maxIterations },
205319
- {
205320
- workspaceRoot: effectiveWorkspaceRoot(ctx),
205321
- desktopConnected: ctx.desktopConnected,
205322
- activeRootPath: ctx.activeRootPath,
205323
- permissionMode: ctx.permissionMode,
205324
- workspaceId: ctx.workspaceId,
205325
- threadId: ctx.threadId,
205326
- selectedSourceId: ctx.selectedSourceId,
205327
- supabaseConfigured: ctx.supabaseConfigured,
205328
- supabase: ctx.supabase,
205329
- runId: ctx.runId,
205330
- founderModelSelection: ctx.founderModelSelection ?? null,
205331
- onEvent: ctx.onEvent,
205332
- signal: ctx.signal,
205333
- parentToolCallId: ctx.parentToolCallId,
205334
- mcpTools: ctx.mcpTools ?? []
205335
- }
205336
- );
205531
+ try {
205532
+ const result2 = await spawnWorker(
205533
+ { workerId, objective: workerObjective, context: enrichedContext, lane, maxIterations },
205534
+ {
205535
+ workspaceRoot: effectiveWorkspaceRoot(ctx),
205536
+ desktopConnected: ctx.desktopConnected,
205537
+ activeRootPath: ctx.activeRootPath,
205538
+ permissionMode: ctx.permissionMode,
205539
+ workspaceId: ctx.workspaceId,
205540
+ threadId: ctx.threadId,
205541
+ selectedSourceId: ctx.selectedSourceId,
205542
+ supabaseConfigured: ctx.supabaseConfigured,
205543
+ supabase: ctx.supabase,
205544
+ runId: ctx.runId,
205545
+ founderModelSelection: ctx.founderModelSelection ?? null,
205546
+ onEvent: ctx.onEvent,
205547
+ signal: ctx.signal,
205548
+ parentToolCallId: ctx.parentToolCallId,
205549
+ mcpTools: ctx.mcpTools ?? []
205550
+ }
205551
+ );
205552
+ return adhocNickname ? { ...result2, nickname: adhocNickname } : result2;
205553
+ } finally {
205554
+ if (adhocManifestId) unregisterWorkerManifest(adhocManifestId);
205555
+ }
205337
205556
  }
205338
205557
  };
205339
205558
  }
@@ -221147,56 +221366,6 @@ var init_flockModelHints = __esm({
221147
221366
  }
221148
221367
  });
221149
221368
 
221150
- // features/perchTerminal/runtime/flock/flockNicknames.ts
221151
- function seedFromFlockId(flockId) {
221152
- let hash = 2166136261;
221153
- for (let i = 0; i < flockId.length; i++) {
221154
- hash ^= flockId.charCodeAt(i);
221155
- hash = Math.imul(hash, 16777619);
221156
- }
221157
- return hash >>> 0;
221158
- }
221159
- function flockNicknameRotation(flockId) {
221160
- const names = [...FLOCK_NICKNAMES];
221161
- let state = seedFromFlockId(flockId) || 1;
221162
- const nextRandom = () => {
221163
- state = Math.imul(state, 1664525) + 1013904223 >>> 0;
221164
- return state / 4294967296;
221165
- };
221166
- for (let i = names.length - 1; i > 0; i--) {
221167
- const j = Math.floor(nextRandom() * (i + 1));
221168
- [names[i], names[j]] = [names[j], names[i]];
221169
- }
221170
- return names;
221171
- }
221172
- function flockNicknameFor(flockId, index) {
221173
- const rotation = flockNicknameRotation(flockId);
221174
- return rotation[index % rotation.length];
221175
- }
221176
- var FLOCK_NICKNAMES;
221177
- var init_flockNicknames = __esm({
221178
- "features/perchTerminal/runtime/flock/flockNicknames.ts"() {
221179
- "use strict";
221180
- FLOCK_NICKNAMES = [
221181
- "Little Dum Dum",
221182
- "Molly",
221183
- "Boomer",
221184
- "Shawarma",
221185
- "Curie",
221186
- "Eagle",
221187
- "Noether",
221188
- "Wonky",
221189
- "Lovelace",
221190
- "Bell",
221191
- "Turing",
221192
- "Faraday",
221193
- "Biscuit",
221194
- "Ice Cream",
221195
- "Minsky"
221196
- ];
221197
- }
221198
- });
221199
-
221200
221369
  // features/perchTerminal/runtime/flock/flockRoles.ts
221201
221370
  function listFlockRoleSpecs() {
221202
221371
  return Object.values(FLOCK_ROLES);
@@ -221429,6 +221598,74 @@ var init_flockRoles = __esm({
221429
221598
  "Return JSON with verificationStatus (pass|partial|fail), verifiedClaims, unsupportedClaims, warnings."
221430
221599
  )
221431
221600
  },
221601
+ consistency_checker: {
221602
+ roleId: "consistency_checker",
221603
+ workerId: "flock_consistency_checker",
221604
+ displayName: "Consistency Checker",
221605
+ role: "verifier",
221606
+ lane: "verifier",
221607
+ allowedTools: [
221608
+ TOOL_NAMES.searchKnowledge,
221609
+ TOOL_NAMES.readLocalFile,
221610
+ TOOL_NAMES.listLocalSources,
221611
+ TOOL_NAMES.readLocalSourceFile
221612
+ ],
221613
+ writesWorkspace: false,
221614
+ maxIterations: 8,
221615
+ outputContract: "JSON { continuityStatus: 'pass'|'partial'|'fail', contradictions, styleIssues, warnings }",
221616
+ objectiveTemplate: (task) => bounded(
221617
+ task,
221618
+ "Review the provided draft for internal consistency and craft: continuity of characters, names, timeline, and established facts; tone and voice drift; pacing; clich\xE9s. Do not fact-check against external sources.",
221619
+ "Return JSON with continuityStatus (pass|partial|fail), contradictions (string[]), styleIssues (string[]), warnings (string[])."
221620
+ )
221621
+ },
221622
+ legal_drafter: {
221623
+ roleId: "legal_drafter",
221624
+ workerId: "flock_legal_drafter",
221625
+ displayName: "Legal Drafter",
221626
+ role: "worker",
221627
+ lane: "writer",
221628
+ allowedTools: [
221629
+ TOOL_NAMES.searchKnowledge,
221630
+ TOOL_NAMES.glob,
221631
+ TOOL_NAMES.grep,
221632
+ TOOL_NAMES.readLocalFile,
221633
+ TOOL_NAMES.listLocalSources,
221634
+ TOOL_NAMES.readLocalSourceFile,
221635
+ TOOL_NAMES.writeLocalFile
221636
+ ],
221637
+ writesWorkspace: true,
221638
+ maxIterations: 10,
221639
+ outputContract: "JSON { title, markdown, irac: { issue, rules, application, conclusion }, authoritiesCited, needsVerification, notes }",
221640
+ objectiveTemplate: (task) => bounded(
221641
+ task,
221642
+ "Draft a legal memo in strict IRAC form \u2014 Issue, Rule, Application, Conclusion, every section present and labeled. First call searchKnowledge for IRAC memo exemplars or templates and imitate their structure and register when any exist. Closed universe: cite ONLY authorities found in the provided case packet (local sources), with pin cites; never invent or import outside authority. Propositions you cannot support from the packet go in needsVerification, never in the prose as settled law.",
221643
+ "Return JSON with title, markdown, irac {issue, rules, application, conclusion}, authoritiesCited (string[]), needsVerification (string[]), notes (string[])."
221644
+ )
221645
+ },
221646
+ legal_citation_checker: {
221647
+ roleId: "legal_citation_checker",
221648
+ workerId: "flock_legal_citation_checker",
221649
+ displayName: "Legal Citation Checker",
221650
+ role: "verifier",
221651
+ lane: "verifier",
221652
+ // Closed universe by construction: no web or global knowledge tools — the packet is the law.
221653
+ allowedTools: [
221654
+ TOOL_NAMES.glob,
221655
+ TOOL_NAMES.grep,
221656
+ TOOL_NAMES.readLocalFile,
221657
+ TOOL_NAMES.listLocalSources,
221658
+ TOOL_NAMES.readLocalSourceFile
221659
+ ],
221660
+ writesWorkspace: false,
221661
+ maxIterations: 10,
221662
+ outputContract: "JSON { verificationStatus: 'pass'|'partial'|'fail', verifiedCitations, unsupportedCitations: [{ citation, claim, reason }], missingFromPacket, warnings }",
221663
+ objectiveTemplate: (task) => bounded(
221664
+ task,
221665
+ "Closed-universe citation check: for every authority cited in the provided draft or memo, locate it in the provided case packet (local sources via listLocalSources/grep/readLocalFile). Verify the authority exists in the packet, quoted language appears in it, and each pin cite supports the stated proposition. Mark pass only when the packet supports the citation. Never consult outside sources \u2014 an authority absent from the packet is reported in missingFromPacket, not assumed valid.",
221666
+ "Return JSON with verificationStatus (pass|partial|fail), verifiedCitations, unsupportedCitations ({citation, claim, reason}[]), missingFromPacket (string[]), warnings (string[])."
221667
+ )
221668
+ },
221432
221669
  browser_operator: {
221433
221670
  roleId: "browser_operator",
221434
221671
  // Reuses the roster manifest from workers/registry — its system prompt and
@@ -221500,10 +221737,35 @@ function planFlock(rawTask, options = {}) {
221500
221737
  if ((options.surface ?? detectFlockSurface()) === "cli") {
221501
221738
  selected.delete("browser_operator");
221502
221739
  }
221740
+ if (selected.has("legal_drafter")) {
221741
+ if (!/\b(write|draft|compose|prepare)\b/i.test(task)) {
221742
+ selected.delete("legal_drafter");
221743
+ selected.add("legal_citation_checker");
221744
+ }
221745
+ selected.delete("writer");
221746
+ selected.delete("researcher");
221747
+ selected.delete("citation_checker");
221748
+ selected.delete("consistency_checker");
221749
+ selected.delete("source_verifier");
221750
+ }
221751
+ if (selected.has("legal_drafter")) {
221752
+ selected.add("legal_citation_checker");
221753
+ } else if (selected.has("legal_citation_checker")) {
221754
+ selected.add("workspace_scout");
221755
+ }
221503
221756
  if (selected.has("patch_worker") || selected.has("test_runner") || selected.has("ui_reviewer")) {
221504
221757
  selected.add("workspace_scout");
221505
221758
  }
221506
- if (selected.has("writer")) selected.add("researcher");
221759
+ if (selected.has("writer")) {
221760
+ const grounded = selected.has("researcher") || selected.has("citation_checker") || selected.has("source_verifier");
221761
+ if (grounded) {
221762
+ selected.add("researcher");
221763
+ selected.add("citation_checker");
221764
+ selected.delete("consistency_checker");
221765
+ } else {
221766
+ selected.add("consistency_checker");
221767
+ }
221768
+ }
221507
221769
  if (selected.size === 0) {
221508
221770
  selected.add("workspace_scout");
221509
221771
  selected.add("evidence_reviewer");
@@ -221563,6 +221825,8 @@ var init_flockPlanner = __esm({
221563
221825
  { roleId: "researcher", pattern: /\b(research|sources?|find|look up|references?|citations?|academic|papers?|literature|background)\b/i },
221564
221826
  { roleId: "writer", pattern: /\b(write|draft|compose|essay|memo|report|article|document|copy)\b/i },
221565
221827
  { roleId: "citation_checker", pattern: /\b(citations?|verify|check sources?|fact.?check|references?)\b/i },
221828
+ { roleId: "consistency_checker", pattern: /\b(fiction|stor(?:y|ies)|novel|chapters?|poems?|screenplay|scripts?|lyrics)\b/i },
221829
+ { roleId: "legal_drafter", pattern: /\b(irac|memorandum|legal (?:memo|brief|writing|citations?)|case ?packet|bluebook|pin ?cites?)\b/i },
221566
221830
  { roleId: "browser_operator", pattern: /\b(browser|open|navigate|click|fill|submit|login|webpage|url|site)\b/i }
221567
221831
  ];
221568
221832
  ROLE_TRIM_PRIORITY = [
@@ -221570,8 +221834,11 @@ var init_flockPlanner = __esm({
221570
221834
  "writer",
221571
221835
  "test_runner",
221572
221836
  "workspace_scout",
221837
+ "legal_drafter",
221838
+ "legal_citation_checker",
221573
221839
  "researcher",
221574
221840
  "citation_checker",
221841
+ "consistency_checker",
221575
221842
  "evidence_reviewer",
221576
221843
  "source_verifier",
221577
221844
  "ui_reviewer",
@@ -221609,7 +221876,9 @@ function buildFlockPlannerPrompts(ctx) {
221609
221876
  `- At most ${writeCap} workers with writesWorkspace=true; each writer needs a disjoint, clearly scoped responsibility, and writeJustification is required for more than ${resolveFlockWriteWorkerCap({ permissionMode: null, planJustifiesMoreWriters: false })}.`,
221610
221877
  `- maxIterations: 1-${caps.maxIterationsPerWorker}.`,
221611
221878
  "- dependsOn: ids of earlier workers whose output this worker needs. No cycles. You decide the graph: parallel where independent, chained where not. Final verifier/reducer workers usually benefit from depending on the workers they check \u2014 guidance, not a requirement.",
221612
- "- baseWorkerId: set ONLY to reuse an id from existingWorkers; otherwise null.",
221879
+ "- baseWorkerId: set ONLY to reuse an id from existingWorkers; otherwise null. Prefer reusing existingWorkers ids when a role matches instead of inventing a near-duplicate.",
221880
+ "- Writing tasks: pair the draft with exactly ONE draft verifier \u2014 reuse flock_citation_checker (together with flock_researcher) when the piece makes factual or source-backed claims, or flock_consistency_checker for creative/fiction drafts (continuity, voice, pacing \u2014 no researcher, never demand citations from fiction).",
221881
+ "- Legal writing or citation-checking tasks (IRAC memos, briefs, case packets, pin cites): reuse flock_legal_drafter and flock_legal_citation_checker instead of the generic writing roles. Closed universe \u2014 verification runs ONLY against the user's provided case packet/local sources, never the open web; a legal draft must include flock_legal_citation_checker.",
221613
221882
  "- Decline (accepted=false, with reason) tasks that are trivial, conversational, or need no fanout."
221614
221883
  ].join("\n");
221615
221884
  ensureFlockWorkersRegistered();
@@ -221715,7 +221984,7 @@ function validateLlmFlockPlan(record, ctx) {
221715
221984
  const objective = sanitizeText(worker.objective, MAX_OBJECTIVE_CHARS);
221716
221985
  if (!displayName || !objective) return null;
221717
221986
  const role = VALID_ROLES.has(worker.phase) ? worker.phase : "worker";
221718
- const outputContract = sanitizeLine(worker.outputContract, MAX_CONTRACT_CHARS) || "Return a short structured summary of what was found or done.";
221987
+ const outputContract = sanitizeLine(worker.outputContract, MAX_CONTRACT_CHARS2) || "Return a short structured summary of what was found or done.";
221719
221988
  const requestedTools = Array.isArray(worker.allowedTools) ? worker.allowedTools.filter((tool) => typeof tool === "string") : [];
221720
221989
  let allowedTools = [...new Set(requestedTools)].filter(
221721
221990
  (tool) => available.has(tool) && !FLOCK_FORBIDDEN_TOOLS.includes(tool)
@@ -221726,7 +221995,7 @@ function validateLlmFlockPlan(record, ctx) {
221726
221995
  if (!writesWorkspace) {
221727
221996
  allowedTools = allowedTools.filter((tool) => !WRITE_TOOLS.has(tool));
221728
221997
  }
221729
- const maxIterations = clampInt(worker.maxIterations, 1, caps.maxIterationsPerWorker, 6);
221998
+ const maxIterations = clampInt2(worker.maxIterations, 1, caps.maxIterationsPerWorker, 6);
221730
221999
  const baseWorkerId = typeof worker.baseWorkerId === "string" && worker.baseWorkerId.trim() ? worker.baseWorkerId.trim() : null;
221731
222000
  const reusedManifest = baseWorkerId ? resolveWorkerManifest(baseWorkerId) : null;
221732
222001
  const flockWorkerId = `fw${index + 1}_${slugify(displayName)}`;
@@ -221792,6 +222061,74 @@ function validateLlmFlockPlan(record, ctx) {
221792
222061
  }
221793
222062
  }
221794
222063
  }
222064
+ const legalDrafterWorker = surfacedWorkers.find(
222065
+ (worker) => worker.workerId === FLOCK_ROLES.legal_drafter.workerId
222066
+ );
222067
+ if (legalDrafterWorker && !surfacedWorkers.some((worker) => worker.workerId === FLOCK_ROLES.legal_citation_checker.workerId)) {
222068
+ const spec = FLOCK_ROLES.legal_citation_checker;
222069
+ const genericVerifierIndex = surfacedWorkers.findIndex(
222070
+ (worker) => worker.workerId === FLOCK_ROLES.citation_checker.workerId || worker.workerId === FLOCK_ROLES.consistency_checker.workerId
222071
+ );
222072
+ if (genericVerifierIndex >= 0) {
222073
+ const existing = surfacedWorkers[genericVerifierIndex];
222074
+ surfacedWorkers[genericVerifierIndex] = {
222075
+ ...existing,
222076
+ workerId: spec.workerId,
222077
+ displayName: spec.displayName,
222078
+ role: spec.role,
222079
+ objective: spec.objectiveTemplate(ctx.task),
222080
+ maxIterations: Math.min(spec.maxIterations, caps.maxIterationsPerWorker),
222081
+ allowedTools: [...spec.allowedTools],
222082
+ writesWorkspace: false,
222083
+ writeScope: null,
222084
+ outputContract: spec.outputContract,
222085
+ dependsOn: [legalDrafterWorker.flockWorkerId],
222086
+ dynamicManifest: null
222087
+ };
222088
+ } else if (surfacedWorkers.length < FLOCK_MAX_WORKERS) {
222089
+ const index = surfacedWorkers.length;
222090
+ surfacedWorkers.push({
222091
+ flockWorkerId: `fw${index + 1}_${spec.roleId}`,
222092
+ workerId: spec.workerId,
222093
+ displayName: spec.displayName,
222094
+ nickname: flockNicknameFor(ctx.flockId, index),
222095
+ role: spec.role,
222096
+ objective: spec.objectiveTemplate(ctx.task),
222097
+ maxIterations: Math.min(spec.maxIterations, caps.maxIterationsPerWorker),
222098
+ allowedTools: [...spec.allowedTools],
222099
+ writesWorkspace: false,
222100
+ writeScope: null,
222101
+ outputContract: spec.outputContract,
222102
+ dependsOn: [legalDrafterWorker.flockWorkerId],
222103
+ modelOverride: null,
222104
+ dynamicManifest: null
222105
+ });
222106
+ }
222107
+ }
222108
+ const writerWorker = legalDrafterWorker ?? surfacedWorkers.find((worker) => worker.workerId === FLOCK_ROLES.writer.workerId);
222109
+ if (writerWorker && !surfacedWorkers.some((worker) => worker.role === "verifier") && surfacedWorkers.length < FLOCK_MAX_WORKERS) {
222110
+ const hasResearch = surfacedWorkers.some(
222111
+ (worker) => worker.workerId === FLOCK_ROLES.researcher.workerId
222112
+ );
222113
+ const spec = legalDrafterWorker ? FLOCK_ROLES.legal_citation_checker : hasResearch ? FLOCK_ROLES.citation_checker : FLOCK_ROLES.consistency_checker;
222114
+ const index = surfacedWorkers.length;
222115
+ surfacedWorkers.push({
222116
+ flockWorkerId: `fw${index + 1}_${spec.roleId}`,
222117
+ workerId: spec.workerId,
222118
+ displayName: spec.displayName,
222119
+ nickname: flockNicknameFor(ctx.flockId, index),
222120
+ role: spec.role,
222121
+ objective: spec.objectiveTemplate(ctx.task),
222122
+ maxIterations: Math.min(spec.maxIterations, caps.maxIterationsPerWorker),
222123
+ allowedTools: [...spec.allowedTools],
222124
+ writesWorkspace: false,
222125
+ writeScope: null,
222126
+ outputContract: spec.outputContract,
222127
+ dependsOn: [writerWorker.flockWorkerId],
222128
+ modelOverride: null,
222129
+ dynamicManifest: null
222130
+ });
222131
+ }
221795
222132
  surfacedWorkers.sort((a, b2) => FLOCK_ROLE_ORDER[a.role] - FLOCK_ROLE_ORDER[b2.role]);
221796
222133
  const summary = sanitizeLine(record.summary, 200) || `${surfacedWorkers.length} workers: ${surfacedWorkers.map((worker) => worker.displayName).join(", ")}`;
221797
222134
  return {
@@ -221843,11 +222180,11 @@ function sanitizeText(value, max2) {
221843
222180
  if (typeof value !== "string") return "";
221844
222181
  return value.trim().slice(0, max2);
221845
222182
  }
221846
- function clampInt(value, min2, max2, fallback) {
222183
+ function clampInt2(value, min2, max2, fallback) {
221847
222184
  const num = typeof value === "number" && Number.isFinite(value) ? Math.floor(value) : fallback;
221848
222185
  return Math.min(Math.max(num, min2), max2);
221849
222186
  }
221850
- var FLOCK_FORBIDDEN_TOOLS, WRITE_TOOLS, GUI_ONLY_WORKER_IDS, GUI_ONLY_TOOLS, VALID_ROLES, MAX_DISPLAY_NAME_CHARS, MAX_OBJECTIVE_CHARS, MAX_CONTRACT_CHARS, MAX_ROSTER_IN_PROMPT, MAX_TOOLS_IN_PROMPT;
222187
+ var FLOCK_FORBIDDEN_TOOLS, WRITE_TOOLS, GUI_ONLY_WORKER_IDS, GUI_ONLY_TOOLS, VALID_ROLES, MAX_DISPLAY_NAME_CHARS, MAX_OBJECTIVE_CHARS, MAX_CONTRACT_CHARS2, MAX_ROSTER_IN_PROMPT, MAX_TOOLS_IN_PROMPT;
221851
222188
  var init_flockLlmPlanner = __esm({
221852
222189
  "features/perchTerminal/runtime/flock/flockLlmPlanner.ts"() {
221853
222190
  "use strict";
@@ -221888,7 +222225,7 @@ var init_flockLlmPlanner = __esm({
221888
222225
  VALID_ROLES = /* @__PURE__ */ new Set(["scout", "worker", "verifier", "reducer"]);
221889
222226
  MAX_DISPLAY_NAME_CHARS = 40;
221890
222227
  MAX_OBJECTIVE_CHARS = 1200;
221891
- MAX_CONTRACT_CHARS = 240;
222228
+ MAX_CONTRACT_CHARS2 = 240;
221892
222229
  MAX_ROSTER_IN_PROMPT = 24;
221893
222230
  MAX_TOOLS_IN_PROMPT = 80;
221894
222231
  }
@@ -222031,14 +222368,19 @@ async function runFlockTurn(input, deps, options = {}) {
222031
222368
  }
222032
222369
  const spawnFn = options.spawnWorkerFn ?? spawnWorker;
222033
222370
  const outcomes = [];
222371
+ const revisionOutcomes = [];
222372
+ let revisionReport = null;
222034
222373
  const outputByFlockWorkerId = /* @__PURE__ */ new Map();
222035
- const sharedContext = { task };
222374
+ const sharedContext = {
222375
+ task,
222376
+ currentDate: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
222377
+ };
222036
222378
  let toolCallsUsed = 0;
222037
222379
  let toolCallsReserved = 0;
222038
222380
  let workersAwaitingLaunch = plan.workers.length;
222039
222381
  try {
222040
222382
  const phases = [...new Set(plan.workers.map((worker) => FLOCK_ROLE_ORDER[worker.role]))].sort((a, b2) => a - b2);
222041
- const runReadyWorker = async (worker) => {
222383
+ const runReadyWorker = async (worker, contextOverride) => {
222042
222384
  const workersLeftIncludingThis = Math.max(1, workersAwaitingLaunch);
222043
222385
  workersAwaitingLaunch = Math.max(0, workersAwaitingLaunch - 1);
222044
222386
  if (flockRun.controller.signal.aborted) {
@@ -222073,11 +222415,14 @@ async function runFlockTurn(input, deps, options = {}) {
222073
222415
  toolCallsReserved += reservedToolCalls;
222074
222416
  emitWorkerUpdate(emit, plan, worker, "running");
222075
222417
  try {
222418
+ const context = contextOverride ?? buildWorkerContext(worker, sharedContext, outputByFlockWorkerId, plan);
222419
+ const objective = worker.role === "worker" && contextContainsSources(context) ? `${worker.objective}
222420
+ ${FLOCK_GROUNDING_CONTRACT}` : worker.objective;
222076
222421
  const result2 = await spawnFn(
222077
222422
  {
222078
222423
  workerId: worker.workerId,
222079
- objective: worker.objective,
222080
- context: buildWorkerContext(worker, sharedContext, outputByFlockWorkerId, plan),
222424
+ objective,
222425
+ context,
222081
222426
  maxIterations: worker.maxIterations,
222082
222427
  maxToolCalls: reservedToolCalls
222083
222428
  },
@@ -222134,7 +222479,9 @@ async function runFlockTurn(input, deps, options = {}) {
222134
222479
  break;
222135
222480
  }
222136
222481
  for (const worker of readyWorkers) pending.delete(worker.flockWorkerId);
222137
- const batchOutcomes = await Promise.all(readyWorkers.map(runReadyWorker));
222482
+ const batchOutcomes = await Promise.all(
222483
+ readyWorkers.map((worker) => runReadyWorker(worker))
222484
+ );
222138
222485
  for (const outcome of batchOutcomes) {
222139
222486
  outcomes.push(outcome);
222140
222487
  if (!outcome.result) continue;
@@ -222144,6 +222491,43 @@ async function runFlockTurn(input, deps, options = {}) {
222144
222491
  }
222145
222492
  }
222146
222493
  }
222494
+ const candidate = findRevisionCandidate(plan, outcomes, outputByFlockWorkerId);
222495
+ if (candidate && !flockRun.controller.signal.aborted) {
222496
+ revisionReport = candidate;
222497
+ const revisionWorker = {
222498
+ ...candidate.draftWorker,
222499
+ flockWorkerId: `${candidate.draftWorker.flockWorkerId}_r1`,
222500
+ displayName: `${candidate.draftWorker.displayName} (revision)`,
222501
+ objective: buildRevisionObjective(task, candidate),
222502
+ dependsOn: []
222503
+ };
222504
+ const revisionOutcome = await runReadyWorker(revisionWorker, {
222505
+ task,
222506
+ currentDate: sharedContext.currentDate,
222507
+ draft: candidate.draftOutput,
222508
+ verifierFindings: candidate.findings.raw
222509
+ });
222510
+ revisionOutcomes.push(revisionOutcome);
222511
+ candidate.revised = revisionOutcome.status === "done";
222512
+ if (revisionOutcome.result) {
222513
+ const revisedOutput = revisionOutcome.result.structuredOutput ?? clampLine(revisionOutcome.result.summary, 1200);
222514
+ sharedContext[revisionWorker.displayName] = revisedOutput;
222515
+ outputByFlockWorkerId.set(revisionWorker.flockWorkerId, revisedOutput);
222516
+ const recheckWorker = {
222517
+ ...candidate.verifierWorker,
222518
+ flockWorkerId: `${candidate.verifierWorker.flockWorkerId}_r1`,
222519
+ displayName: `${candidate.verifierWorker.displayName} (recheck)`,
222520
+ dependsOn: []
222521
+ };
222522
+ const recheckOutcome = await runReadyWorker(recheckWorker, {
222523
+ task,
222524
+ currentDate: sharedContext.currentDate,
222525
+ dependencies: { [revisionWorker.displayName]: revisedOutput }
222526
+ });
222527
+ revisionOutcomes.push(recheckOutcome);
222528
+ candidate.recheckFindings = recheckOutcome.result ? extractVerifierFindings(recheckOutcome.result.structuredOutput) : null;
222529
+ }
222530
+ }
222147
222531
  } finally {
222148
222532
  clearTimeout(wallTimer);
222149
222533
  finishRuntimeRun(
@@ -222156,7 +222540,14 @@ async function runFlockTurn(input, deps, options = {}) {
222156
222540
  const workersFailed = outcomes.filter((outcome) => outcome.status === "failed").length;
222157
222541
  const flockStatus = userCancelled || wallTimeHit && workersDone === 0 ? "cancelled" : workersFailed === 0 && workersDone === plan.workers.length ? "completed" : workersDone > 0 ? "partial" : "failed";
222158
222542
  const assistantText = [
222159
- buildFlockSummary(plan, outcomes, flockStatus, toolCallsUsed, wallTimeHit),
222543
+ buildFlockSummary(
222544
+ plan,
222545
+ [...outcomes, ...revisionOutcomes],
222546
+ flockStatus,
222547
+ toolCallsUsed,
222548
+ wallTimeHit
222549
+ ),
222550
+ ...buildVerificationSection(revisionReport),
222160
222551
  ...modelOverrides.applied.map(
222161
222552
  (override) => `Model override: ${override.displayName} ran on ${override.label}.`
222162
222553
  ),
@@ -222243,7 +222634,117 @@ function buildWorkerContext(worker, sharedContext, outputByFlockWorkerId, plan)
222243
222634
  const depWorker = plan.workers.find((candidate) => candidate.flockWorkerId === dep);
222244
222635
  dependencies[depWorker?.displayName ?? dep] = output;
222245
222636
  }
222246
- return { task: sharedContext.task, dependencies };
222637
+ return { task: sharedContext.task, currentDate: sharedContext.currentDate, dependencies };
222638
+ }
222639
+ function contextContainsSources(context) {
222640
+ const scan = (value, depth) => {
222641
+ if (!value || typeof value !== "object" || Array.isArray(value) || depth > 3) return false;
222642
+ const record = value;
222643
+ const sources = record.sources;
222644
+ if (Array.isArray(sources) && sources.some(
222645
+ (source) => source && typeof source === "object" && typeof source.url === "string"
222646
+ )) {
222647
+ return true;
222648
+ }
222649
+ return Object.values(record).some((nested) => scan(nested, depth + 1));
222650
+ };
222651
+ return scan(context, 0);
222652
+ }
222653
+ function extractVerifierFindings(output) {
222654
+ if (!output || typeof output !== "object" || Array.isArray(output)) return null;
222655
+ const record = output;
222656
+ const verdictRaw = record.verificationStatus ?? record.continuityStatus ?? record.verdict;
222657
+ const verdict = typeof verdictRaw === "string" ? verdictRaw.trim().toLowerCase() : null;
222658
+ const issues = [];
222659
+ for (const key of [
222660
+ "unsupportedClaims",
222661
+ "unsupportedCitations",
222662
+ "missingFromPacket",
222663
+ "contradictions",
222664
+ "styleIssues",
222665
+ "unsupported"
222666
+ ]) {
222667
+ const value = record[key];
222668
+ if (!Array.isArray(value)) continue;
222669
+ for (const item of value) {
222670
+ const text = typeof item === "string" ? item : JSON.stringify(item);
222671
+ const clean = clampLine(text, 200);
222672
+ if (clean && clean !== "{}") issues.push(clean);
222673
+ }
222674
+ }
222675
+ return { verdict, issues, raw: record };
222676
+ }
222677
+ function verifierFindingsNeedRevision(findings) {
222678
+ if (!findings) return false;
222679
+ return findings.issues.length > 0 || findings.verdict === "fail" || findings.verdict === "partial";
222680
+ }
222681
+ function findRevisionCandidate(plan, outcomes, outputByFlockWorkerId) {
222682
+ for (const outcome of outcomes) {
222683
+ if (outcome.worker.role !== "verifier" || outcome.status !== "done" || !outcome.result) {
222684
+ continue;
222685
+ }
222686
+ const findings = extractVerifierFindings(outcome.result.structuredOutput);
222687
+ if (!verifierFindingsNeedRevision(findings)) continue;
222688
+ const draftWorker = resolveDraftWorkerForVerifier(plan, outcome.worker, outcomes);
222689
+ if (!draftWorker) continue;
222690
+ const draftOutput = outputByFlockWorkerId.get(draftWorker.flockWorkerId);
222691
+ if (draftOutput === void 0) continue;
222692
+ return {
222693
+ verifierWorker: outcome.worker,
222694
+ draftWorker,
222695
+ draftOutput,
222696
+ findings,
222697
+ revised: false,
222698
+ recheckFindings: null
222699
+ };
222700
+ }
222701
+ return null;
222702
+ }
222703
+ function resolveDraftWorkerForVerifier(plan, verifier, outcomes) {
222704
+ for (const dep of verifier.dependsOn) {
222705
+ const worker = plan.workers.find((candidate) => candidate.flockWorkerId === dep);
222706
+ if (worker?.role === "worker") return worker;
222707
+ }
222708
+ const doneWorkers = outcomes.filter((outcome) => outcome.status === "done" && outcome.worker.role === "worker").map((outcome) => outcome.worker);
222709
+ const writer = doneWorkers.find(
222710
+ (worker) => worker.workerId === "flock_legal_drafter" || worker.workerId === "flock_writer"
222711
+ );
222712
+ if (writer) return writer;
222713
+ const writingWorkers = doneWorkers.filter((worker) => worker.writesWorkspace);
222714
+ if (writingWorkers.length === 1) return writingWorkers[0];
222715
+ return doneWorkers.length === 1 ? doneWorkers[0] : null;
222716
+ }
222717
+ function buildRevisionObjective(task, candidate) {
222718
+ return [
222719
+ "Bounded Flock revision \u2014 stay strictly inside this scope.",
222720
+ `Task: ${task}`,
222721
+ `A verifier (${candidate.verifierWorker.displayName}) reviewed your draft (context key "draft") and reported findings (context key "verifierFindings").`,
222722
+ "Revise the draft: fix what the findings support fixing; remove or explicitly flag any claim you cannot support. Keep the draft's purpose, format, and output contract. Do not start over.",
222723
+ `Output contract: ${candidate.draftWorker.outputContract}`,
222724
+ "Do not delegate, spawn workers, or start suites. Stop when the findings are addressed."
222725
+ ].join("\n");
222726
+ }
222727
+ function buildVerificationSection(report) {
222728
+ if (!report) return [];
222729
+ const lines = [];
222730
+ lines.push(
222731
+ `Verification (${report.verifierWorker.displayName}): ${report.findings.verdict ?? "issues found"} \u2014 ${report.findings.issues.length} finding(s).`
222732
+ );
222733
+ const shown = report.findings.issues.slice(0, 6);
222734
+ for (const issue of shown) lines.push(`- ${issue}`);
222735
+ if (report.findings.issues.length > shown.length) {
222736
+ lines.push(`- \u2026and ${report.findings.issues.length - shown.length} more.`);
222737
+ }
222738
+ if (report.revised) {
222739
+ const recheck = report.recheckFindings;
222740
+ lines.push(
222741
+ recheck ? `The draft was revised once; recheck verdict: ${recheck.verdict ?? "no structured verdict"}${recheck.issues.length ? ` (${recheck.issues.length} remaining finding(s))` : ""}.` : "The draft was revised once; the recheck returned no structured verdict."
222742
+ );
222743
+ } else {
222744
+ lines.push("No revision pass ran \u2014 the findings above stand as reported.");
222745
+ }
222746
+ lines.push("Ask about any finding to discuss it, or rerun /flock to retry with adjustments.");
222747
+ return lines;
222247
222748
  }
222248
222749
  function buildSpawnContext(input, flockId, signal, emit, worker) {
222249
222750
  return {
@@ -222308,6 +222809,7 @@ function makeFlockRunId() {
222308
222809
  function now11() {
222309
222810
  return (/* @__PURE__ */ new Date()).toISOString();
222310
222811
  }
222812
+ var FLOCK_GROUNDING_CONTRACT;
222311
222813
  var init_runFlockTurn = __esm({
222312
222814
  "features/perchTerminal/runtime/flock/runFlockTurn.ts"() {
222313
222815
  "use strict";
@@ -222321,6 +222823,12 @@ var init_runFlockTurn = __esm({
222321
222823
  init_flockLlmPlanner();
222322
222824
  init_flockPlanner();
222323
222825
  init_flockRoles();
222826
+ FLOCK_GROUNDING_CONTRACT = [
222827
+ "Grounding contract: your context includes sources[] gathered by research.",
222828
+ "Every nontrivial factual claim in the draft must carry an inline [n] marker mapping to one of those sources, with a numbered source list at the end.",
222829
+ "Claims you cannot support from sources[] belong in needsVerification, never in the prose as fact.",
222830
+ "Do not cite anything that is not in sources[]."
222831
+ ].join(" ");
222324
222832
  }
222325
222833
  });
222326
222834
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perchai-cli",
3
- "version": "2.4.32",
3
+ "version": "2.4.34",
4
4
  "description": "Perch AI command-line interface",
5
5
  "bin": {
6
6
  "perch": "bin/perch"