perchai-cli 2.4.31 → 2.4.33

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 +152 -8
  2. package/package.json +1 -1
package/dist/perch.mjs CHANGED
@@ -75917,6 +75917,7 @@ function isTurnAbortedError(error) {
75917
75917
  var TURN_STOPPED_BY_USER_MESSAGE;
75918
75918
  var init_turnAbort = __esm({
75919
75919
  "features/perchTerminal/runtime/turnAbort.ts"() {
75920
+ "use strict";
75920
75921
  TURN_STOPPED_BY_USER_MESSAGE = "Turn stopped by user.";
75921
75922
  }
75922
75923
  });
@@ -76217,7 +76218,6 @@ function getToolDisplayName(toolName) {
76217
76218
  var NON_MODULE_TOOL_OWNERS, TOOL_RISK, TOOL_DISPLAY_NAMES;
76218
76219
  var init_catalog = __esm({
76219
76220
  "features/perchTerminal/runtime/toolSystem/catalog.ts"() {
76220
- "use strict";
76221
76221
  init_toolNames();
76222
76222
  NON_MODULE_TOOL_OWNERS = {
76223
76223
  [TOOL_NAMES.listSources]: "lane",
@@ -83125,6 +83125,7 @@ function listFinancialRoleIds() {
83125
83125
  var FINANCIAL_ROLE_REGISTRY, evidenceScoutManifest;
83126
83126
  var init_financialRoles = __esm({
83127
83127
  "features/perchTerminal/agentPlatform/financialRoles.ts"() {
83128
+ "use strict";
83128
83129
  FINANCIAL_ROLE_REGISTRY = /* @__PURE__ */ new Map();
83129
83130
  evidenceScoutManifest = {
83130
83131
  workerId: "evidence_scout",
@@ -91687,7 +91688,6 @@ function listFinancialPlaybooks() {
91687
91688
  var AP_AUDIT_PACKET_DEF, PLAYBOOKS;
91688
91689
  var init_registry2 = __esm({
91689
91690
  "features/perchTerminal/runtime/financialPlaybooks/registry.ts"() {
91690
- "use strict";
91691
91691
  init_managedWorkflowRegistry2();
91692
91692
  init_toolNames();
91693
91693
  AP_AUDIT_PACKET_DEF = {
@@ -196813,6 +196813,7 @@ function recordDispatchBatchResult(tasks, result2, ctx, opts = {}) {
196813
196813
  var lifecycleByRun, MAX_LIFECYCLE_RUNS;
196814
196814
  var init_workerLifecycle = __esm({
196815
196815
  "features/perchTerminal/runtime/workers/workerLifecycle.ts"() {
196816
+ "use strict";
196816
196817
  init_workerManifest();
196817
196818
  lifecycleByRun = /* @__PURE__ */ new Map();
196818
196819
  MAX_LIFECYCLE_RUNS = 200;
@@ -216864,6 +216865,58 @@ function createSourceReadBudgetState() {
216864
216865
  syntheticResultIssued: false
216865
216866
  };
216866
216867
  }
216868
+ function createFailedReadBreakerState() {
216869
+ return { consecutiveFailedReads: 0, tripped: false };
216870
+ }
216871
+ function buildFailedReadBreakerSuppressionExecution(toolCall, state) {
216872
+ if (!state.tripped) return null;
216873
+ if (!FAILED_READ_BREAKER_TOOLS.has(toolCall.name)) return null;
216874
+ const timestamp = now5();
216875
+ return {
216876
+ toolCallId: toolCall.id,
216877
+ toolName: toolCall.name,
216878
+ toolKind: "read",
216879
+ riskLevel: "read",
216880
+ input: sanitizeToolInput(toolCall.arguments),
216881
+ ok: true,
216882
+ result: FAILED_READ_BREAKER_MESSAGE,
216883
+ output: {
216884
+ ok: true,
216885
+ suppressedFailedReadBreaker: true,
216886
+ consecutiveFailedReads: state.consecutiveFailedReads,
216887
+ message: FAILED_READ_BREAKER_MESSAGE
216888
+ },
216889
+ outputSummary: FAILED_READ_BREAKER_MESSAGE,
216890
+ durationMs: 0,
216891
+ startedAt: timestamp,
216892
+ completedAt: timestamp,
216893
+ desktopRequired: false,
216894
+ resultTruncated: false,
216895
+ evidence: []
216896
+ };
216897
+ }
216898
+ function recordFailedReadBreakerExecution(state, execution) {
216899
+ if (!FAILED_READ_BREAKER_TOOLS.has(execution.toolName)) return false;
216900
+ if (isSyntheticReadSuppressionExecution(execution) || execution.errorCode === "tool_call_budget_exhausted" || isFailedReadBreakerSuppressionExecution(execution)) {
216901
+ return false;
216902
+ }
216903
+ if (execution.ok) {
216904
+ state.consecutiveFailedReads = 0;
216905
+ return false;
216906
+ }
216907
+ state.consecutiveFailedReads += 1;
216908
+ if (!state.tripped && state.consecutiveFailedReads >= FAILED_READ_BREAKER_LIMIT) {
216909
+ state.tripped = true;
216910
+ return true;
216911
+ }
216912
+ return false;
216913
+ }
216914
+ function isFailedReadBreakerSuppressionExecution(execution) {
216915
+ const output = execution.output;
216916
+ return Boolean(
216917
+ output && typeof output === "object" && output.suppressedFailedReadBreaker === true
216918
+ );
216919
+ }
216867
216920
  function buildSourceReadBudgetSuppressionExecution(toolCall, budget, opts) {
216868
216921
  const limits = resolveSourceReadBudgetLimits(opts);
216869
216922
  if (!limits) return null;
@@ -217242,7 +217295,7 @@ function sanitizeMaxIterations(value, cap = MAX_ITERATIONS) {
217242
217295
  function now5() {
217243
217296
  return (/* @__PURE__ */ new Date()).toISOString();
217244
217297
  }
217245
- var MAX_ITERATIONS, MAX_WORKER_ITERATIONS, SOURCE_ANALYSIS_MAX_ITERATIONS, SUBSTANTIAL_FINAL_TEXT_MIN_CHARS, SUBSTANTIAL_FINAL_TEXT_MIN_WORDS, CHAT_SOURCE_READ_TOTAL_BUDGET, CHAT_SOURCE_READ_FAMILY_BUDGET, SOURCE_ANALYSIS_SOURCE_READ_TOTAL_BUDGET, SOURCE_ANALYSIS_SOURCE_READ_FAMILY_BUDGET, SOURCE_READ_BUDGET_MESSAGE, FINAL_TEXT_SUPPRESSIBLE_READ_TOOLS, DUPLICATE_READ_SUPPRESSIBLE_TOOLS, SOURCE_READ_BUDGET_COUNTED_TOOLS, SOURCE_READ_BUDGET_SUPPRESSIBLE_TOOLS, POST_BUDGET_SOURCE_ANALYSIS_FINISHER_TOOLS;
217298
+ var MAX_ITERATIONS, MAX_WORKER_ITERATIONS, SOURCE_ANALYSIS_MAX_ITERATIONS, SUBSTANTIAL_FINAL_TEXT_MIN_CHARS, SUBSTANTIAL_FINAL_TEXT_MIN_WORDS, CHAT_SOURCE_READ_TOTAL_BUDGET, CHAT_SOURCE_READ_FAMILY_BUDGET, SOURCE_ANALYSIS_SOURCE_READ_TOTAL_BUDGET, SOURCE_ANALYSIS_SOURCE_READ_FAMILY_BUDGET, SOURCE_READ_BUDGET_MESSAGE, FINAL_TEXT_SUPPRESSIBLE_READ_TOOLS, DUPLICATE_READ_SUPPRESSIBLE_TOOLS, SOURCE_READ_BUDGET_COUNTED_TOOLS, SOURCE_READ_BUDGET_SUPPRESSIBLE_TOOLS, POST_BUDGET_SOURCE_ANALYSIS_FINISHER_TOOLS, FAILED_READ_BREAKER_TOOLS, FAILED_READ_BREAKER_LIMIT, FAILED_READ_BREAKER_MESSAGE;
217246
217299
  var init_sourceReadPolicy = __esm({
217247
217300
  "features/perchTerminal/runtime/toolLoop/sourceReadPolicy.ts"() {
217248
217301
  "use strict";
@@ -217315,6 +217368,13 @@ var init_sourceReadPolicy = __esm({
217315
217368
  TOOL_NAMES.prepareSandboxInputs,
217316
217369
  TOOL_NAMES.runSandboxCode
217317
217370
  ]);
217371
+ FAILED_READ_BREAKER_TOOLS = /* @__PURE__ */ new Set([
217372
+ TOOL_NAMES.readLocalFile,
217373
+ TOOL_NAMES.readLocalSourceFile,
217374
+ TOOL_NAMES.printFile
217375
+ ]);
217376
+ FAILED_READ_BREAKER_LIMIT = 6;
217377
+ FAILED_READ_BREAKER_MESSAGE = `Stopped file reads after ${FAILED_READ_BREAKER_LIMIT} consecutive misses \u2014 the paths being guessed do not exist. Do not probe further paths. Use the context you were given, or finish from gathered evidence now.`;
217318
217378
  }
217319
217379
  });
217320
217380
 
@@ -217423,6 +217483,14 @@ async function executeToolBatch(toolCalls, ctx) {
217423
217483
  });
217424
217484
  return budgetSuppression;
217425
217485
  }
217486
+ const failedReadSuppression = buildFailedReadBreakerSuppressionExecution(
217487
+ toolCall,
217488
+ ctx.failedReadBreaker
217489
+ );
217490
+ if (failedReadSuppression) {
217491
+ ctx.sourceReadBudget.syntheticResultIssued = true;
217492
+ return failedReadSuppression;
217493
+ }
217426
217494
  const duplicateRead = buildDuplicateReadSuppressionExecution(
217427
217495
  toolCall,
217428
217496
  ctx.successfulReadToolSignatures
@@ -217521,6 +217589,14 @@ async function executeToolBatch(toolCalls, ctx) {
217521
217589
  ctx.untrustedContextAvailable = true;
217522
217590
  rememberSuccessfulReadExecution(ctx.successfulReadToolSignatures, execution);
217523
217591
  recordSuccessfulSourceReadExecution(ctx.sourceReadBudget, execution);
217592
+ if (recordFailedReadBreakerExecution(ctx.failedReadBreaker, execution)) {
217593
+ ctx.onEvent({
217594
+ type: "diagnostic",
217595
+ code: "failed_read_breaker_tripped",
217596
+ message: `${FAILED_READ_BREAKER_LIMIT} consecutive file reads failed \u2014 suppressing further file reads this run and finishing from gathered evidence.`,
217597
+ ts: now6()
217598
+ });
217599
+ }
217524
217600
  const updatedMode = permissionModeFromExecution(execution);
217525
217601
  if (updatedMode) ctx.permissionMode = updatedMode;
217526
217602
  const consecutiveArgFailureCount = updateConsecutiveRequiredArgFailures(
@@ -219281,6 +219357,7 @@ async function runModelToolLoop(input) {
219281
219357
  const successfulReadToolSignatures = /* @__PURE__ */ new Map();
219282
219358
  const sourceReadBudget = createSourceReadBudgetState();
219283
219359
  const consecutiveRequiredArgFailures = /* @__PURE__ */ new Map();
219360
+ const failedReadBreaker = createFailedReadBreakerState();
219284
219361
  const startMs = Date.now();
219285
219362
  let iterations = 0;
219286
219363
  let lastProvider = "unknown";
@@ -219573,6 +219650,7 @@ async function runModelToolLoop(input) {
219573
219650
  sourceReadBudget,
219574
219651
  successfulReadToolSignatures,
219575
219652
  consecutiveRequiredArgFailures,
219653
+ failedReadBreaker,
219576
219654
  permissionMode: effectivePermissionMode,
219577
219655
  realToolCallsStarted,
219578
219656
  firstPartyToolIntentUsed,
@@ -221349,6 +221427,27 @@ var init_flockRoles = __esm({
221349
221427
  "Return JSON with verificationStatus (pass|partial|fail), verifiedClaims, unsupportedClaims, warnings."
221350
221428
  )
221351
221429
  },
221430
+ consistency_checker: {
221431
+ roleId: "consistency_checker",
221432
+ workerId: "flock_consistency_checker",
221433
+ displayName: "Consistency Checker",
221434
+ role: "verifier",
221435
+ lane: "verifier",
221436
+ allowedTools: [
221437
+ TOOL_NAMES.searchKnowledge,
221438
+ TOOL_NAMES.readLocalFile,
221439
+ TOOL_NAMES.listLocalSources,
221440
+ TOOL_NAMES.readLocalSourceFile
221441
+ ],
221442
+ writesWorkspace: false,
221443
+ maxIterations: 8,
221444
+ outputContract: "JSON { continuityStatus: 'pass'|'partial'|'fail', contradictions, styleIssues, warnings }",
221445
+ objectiveTemplate: (task) => bounded(
221446
+ task,
221447
+ "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.",
221448
+ "Return JSON with continuityStatus (pass|partial|fail), contradictions (string[]), styleIssues (string[]), warnings (string[])."
221449
+ )
221450
+ },
221352
221451
  browser_operator: {
221353
221452
  roleId: "browser_operator",
221354
221453
  // Reuses the roster manifest from workers/registry — its system prompt and
@@ -221423,7 +221522,16 @@ function planFlock(rawTask, options = {}) {
221423
221522
  if (selected.has("patch_worker") || selected.has("test_runner") || selected.has("ui_reviewer")) {
221424
221523
  selected.add("workspace_scout");
221425
221524
  }
221426
- if (selected.has("writer")) selected.add("researcher");
221525
+ if (selected.has("writer")) {
221526
+ const grounded = selected.has("researcher") || selected.has("citation_checker") || selected.has("source_verifier");
221527
+ if (grounded) {
221528
+ selected.add("researcher");
221529
+ selected.add("citation_checker");
221530
+ selected.delete("consistency_checker");
221531
+ } else {
221532
+ selected.add("consistency_checker");
221533
+ }
221534
+ }
221427
221535
  if (selected.size === 0) {
221428
221536
  selected.add("workspace_scout");
221429
221537
  selected.add("evidence_reviewer");
@@ -221483,6 +221591,7 @@ var init_flockPlanner = __esm({
221483
221591
  { roleId: "researcher", pattern: /\b(research|sources?|find|look up|references?|citations?|academic|papers?|literature|background)\b/i },
221484
221592
  { roleId: "writer", pattern: /\b(write|draft|compose|essay|memo|report|article|document|copy)\b/i },
221485
221593
  { roleId: "citation_checker", pattern: /\b(citations?|verify|check sources?|fact.?check|references?)\b/i },
221594
+ { roleId: "consistency_checker", pattern: /\b(fiction|stor(?:y|ies)|novel|chapters?|poems?|screenplay|scripts?|lyrics)\b/i },
221486
221595
  { roleId: "browser_operator", pattern: /\b(browser|open|navigate|click|fill|submit|login|webpage|url|site)\b/i }
221487
221596
  ];
221488
221597
  ROLE_TRIM_PRIORITY = [
@@ -221492,6 +221601,7 @@ var init_flockPlanner = __esm({
221492
221601
  "workspace_scout",
221493
221602
  "researcher",
221494
221603
  "citation_checker",
221604
+ "consistency_checker",
221495
221605
  "evidence_reviewer",
221496
221606
  "source_verifier",
221497
221607
  "ui_reviewer",
@@ -221529,12 +221639,19 @@ function buildFlockPlannerPrompts(ctx) {
221529
221639
  `- 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 })}.`,
221530
221640
  `- maxIterations: 1-${caps.maxIterationsPerWorker}.`,
221531
221641
  "- 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.",
221532
- "- baseWorkerId: set ONLY to reuse an id from existingWorkers; otherwise null.",
221642
+ "- 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.",
221643
+ "- 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).",
221533
221644
  "- Decline (accepted=false, with reason) tasks that are trivial, conversational, or need no fanout."
221534
221645
  ].join("\n");
221535
- const roster = listWorkerContracts().filter(
221646
+ ensureFlockWorkersRegistered();
221647
+ const archetypeIds = new Set(listFlockRoleSpecs().map((spec) => spec.workerId));
221648
+ const surfacedContracts = listWorkerContracts().filter(
221536
221649
  (contract) => ctx.surface !== "cli" || !GUI_ONLY_WORKER_IDS.includes(contract.workerId)
221537
- ).slice(0, MAX_ROSTER_IN_PROMPT).map((contract) => ({ id: contract.workerId, name: contract.name }));
221650
+ );
221651
+ const roster = [
221652
+ ...surfacedContracts.filter((contract) => archetypeIds.has(contract.workerId)),
221653
+ ...surfacedContracts.filter((contract) => !archetypeIds.has(contract.workerId))
221654
+ ].slice(0, MAX_ROSTER_IN_PROMPT).map((contract) => ({ id: contract.workerId, name: contract.name }));
221538
221655
  const userPrompt = JSON.stringify({
221539
221656
  task: ctx.task,
221540
221657
  surface: ctx.surface,
@@ -221706,6 +221823,32 @@ function validateLlmFlockPlan(record, ctx) {
221706
221823
  }
221707
221824
  }
221708
221825
  }
221826
+ const writerWorker = surfacedWorkers.find(
221827
+ (worker) => worker.workerId === FLOCK_ROLES.writer.workerId
221828
+ );
221829
+ if (writerWorker && !surfacedWorkers.some((worker) => worker.role === "verifier") && surfacedWorkers.length < FLOCK_MAX_WORKERS) {
221830
+ const hasResearch = surfacedWorkers.some(
221831
+ (worker) => worker.workerId === FLOCK_ROLES.researcher.workerId
221832
+ );
221833
+ const spec = hasResearch ? FLOCK_ROLES.citation_checker : FLOCK_ROLES.consistency_checker;
221834
+ const index = surfacedWorkers.length;
221835
+ surfacedWorkers.push({
221836
+ flockWorkerId: `fw${index + 1}_${spec.roleId}`,
221837
+ workerId: spec.workerId,
221838
+ displayName: spec.displayName,
221839
+ nickname: flockNicknameFor(ctx.flockId, index),
221840
+ role: spec.role,
221841
+ objective: spec.objectiveTemplate(ctx.task),
221842
+ maxIterations: Math.min(spec.maxIterations, caps.maxIterationsPerWorker),
221843
+ allowedTools: [...spec.allowedTools],
221844
+ writesWorkspace: false,
221845
+ writeScope: null,
221846
+ outputContract: spec.outputContract,
221847
+ dependsOn: [writerWorker.flockWorkerId],
221848
+ modelOverride: null,
221849
+ dynamicManifest: null
221850
+ });
221851
+ }
221709
221852
  surfacedWorkers.sort((a, b2) => FLOCK_ROLE_ORDER[a.role] - FLOCK_ROLE_ORDER[b2.role]);
221710
221853
  const summary = sanitizeLine(record.summary, 200) || `${surfacedWorkers.length} workers: ${surfacedWorkers.map((worker) => worker.displayName).join(", ")}`;
221711
221854
  return {
@@ -221774,6 +221917,7 @@ var init_flockLlmPlanner = __esm({
221774
221917
  init_flockLimits();
221775
221918
  init_flockNicknames();
221776
221919
  init_flockPlanner();
221920
+ init_flockRoles();
221777
221921
  init_flockPlanner();
221778
221922
  FLOCK_FORBIDDEN_TOOLS = [
221779
221923
  ...WORKER_DISALLOWED_TOOLS,
@@ -221878,6 +222022,7 @@ async function runFlockTurn(input, deps, options = {}) {
221878
222022
  task: task.slice(0, 300),
221879
222023
  ts: now11()
221880
222024
  });
222025
+ ensureFlockWorkersRegistered();
221881
222026
  let plan = null;
221882
222027
  if (options.plannerModelCall !== null) {
221883
222028
  const llmCtx = {
@@ -221917,7 +222062,6 @@ async function runFlockTurn(input, deps, options = {}) {
221917
222062
  });
221918
222063
  return await finishTurn(plan.reason);
221919
222064
  }
221920
- ensureFlockWorkersRegistered();
221921
222065
  for (const worker of plan.workers) {
221922
222066
  if (!worker.dynamicManifest) continue;
221923
222067
  registerWorkerManifest(worker.dynamicManifest);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perchai-cli",
3
- "version": "2.4.31",
3
+ "version": "2.4.33",
4
4
  "description": "Perch AI command-line interface",
5
5
  "bin": {
6
6
  "perch": "bin/perch"