perchai-cli 2.4.24 → 2.4.26

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 +305 -30
  2. package/package.json +1 -1
package/dist/perch.mjs CHANGED
@@ -76921,6 +76921,7 @@ function buildTranscriptSegments(state) {
76921
76921
  const terminalSegmentIdx = /* @__PURE__ */ new Map();
76922
76922
  const workerRunIdx = /* @__PURE__ */ new Map();
76923
76923
  const flockRunIdx = /* @__PURE__ */ new Map();
76924
+ const diagnosticIdxByKey = /* @__PURE__ */ new Map();
76924
76925
  const capabilityIdx = /* @__PURE__ */ new Map();
76925
76926
  const liveCardIdx = /* @__PURE__ */ new Map();
76926
76927
  const workflowCardIdx = /* @__PURE__ */ new Map();
@@ -77542,6 +77543,38 @@ function buildTranscriptSegments(state) {
77542
77543
  case "diagnostic": {
77543
77544
  flushReasoning();
77544
77545
  if (!ev.message.trim()) break;
77546
+ if (ev.code === "tool_call_budget_exhausted") {
77547
+ let attributed = false;
77548
+ if (ev.workerId) {
77549
+ for (const [flockId, idx] of flockRunIdx) {
77550
+ const seg = segments[idx];
77551
+ if (seg?.kind !== "flock_run") continue;
77552
+ if (!seg.workers.some((worker) => worker.workerId === ev.workerId)) continue;
77553
+ segments[idx] = {
77554
+ ...seg,
77555
+ workers: seg.workers.map(
77556
+ (worker) => worker.workerId === ev.workerId ? { ...worker, note: "tool limit \u2014 finishing from gathered evidence" } : worker
77557
+ )
77558
+ };
77559
+ attributed = Boolean(flockId);
77560
+ break;
77561
+ }
77562
+ }
77563
+ if (!attributed) {
77564
+ const key = `tool-budget-${ev.workerId ?? "turn"}`;
77565
+ const existingIdx = diagnosticIdxByKey.get(key);
77566
+ if (existingIdx !== void 0 && segments[existingIdx]?.kind === "diagnostic") {
77567
+ segments[existingIdx] = {
77568
+ ...segments[existingIdx],
77569
+ message: ev.message
77570
+ };
77571
+ } else {
77572
+ diagnosticIdxByKey.set(key, segments.length);
77573
+ segments.push({ kind: "diagnostic", message: ev.message, seq: seq++, timestamp: ev.ts });
77574
+ }
77575
+ }
77576
+ break;
77577
+ }
77545
77578
  segments.push({
77546
77579
  kind: "diagnostic",
77547
77580
  message: ev.message,
@@ -77849,6 +77882,7 @@ function buildTranscriptSegments(state) {
77849
77882
  plannerSource: ev.plannerSource,
77850
77883
  workers: ev.workers.map((worker) => ({
77851
77884
  flockWorkerId: worker.flockWorkerId,
77885
+ workerId: worker.workerId,
77852
77886
  displayName: worker.displayName,
77853
77887
  nickname: worker.nickname,
77854
77888
  status: "queued"
@@ -77868,6 +77902,7 @@ function buildTranscriptSegments(state) {
77868
77902
  ...seg.workers,
77869
77903
  {
77870
77904
  flockWorkerId: ev.flockWorkerId,
77905
+ workerId: ev.workerId,
77871
77906
  displayName: ev.displayName,
77872
77907
  nickname: ev.nickname,
77873
77908
  status: ev.status
@@ -83022,7 +83057,6 @@ function listFinancialRoleIds() {
83022
83057
  var FINANCIAL_ROLE_REGISTRY, evidenceScoutManifest;
83023
83058
  var init_financialRoles = __esm({
83024
83059
  "features/perchTerminal/agentPlatform/financialRoles.ts"() {
83025
- "use strict";
83026
83060
  FINANCIAL_ROLE_REGISTRY = /* @__PURE__ */ new Map();
83027
83061
  evidenceScoutManifest = {
83028
83062
  workerId: "evidence_scout",
@@ -86588,7 +86622,6 @@ function truncateHistoryLine(value, max2) {
86588
86622
  }
86589
86623
  var init_operatorTruth = __esm({
86590
86624
  "features/perchTerminal/runtime/operatorTruth.ts"() {
86591
- "use strict";
86592
86625
  }
86593
86626
  });
86594
86627
 
@@ -91554,6 +91587,7 @@ function listFinancialPlaybooks() {
91554
91587
  var AP_AUDIT_PACKET_DEF, PLAYBOOKS;
91555
91588
  var init_registry2 = __esm({
91556
91589
  "features/perchTerminal/runtime/financialPlaybooks/registry.ts"() {
91590
+ "use strict";
91557
91591
  init_managedWorkflowRegistry2();
91558
91592
  init_toolNames();
91559
91593
  AP_AUDIT_PACKET_DEF = {
@@ -217708,10 +217742,18 @@ async function runModelToolLoop(input) {
217708
217742
  batch.slice(remainingRealToolCalls).map((toolCall) => toolCall.id)
217709
217743
  );
217710
217744
  if (skippedForToolBudget.size > 0) {
217745
+ const skippedToolNames = [
217746
+ ...new Set(
217747
+ batch.filter((toolCall) => skippedForToolBudget.has(toolCall.id)).map((toolCall) => toolCall.name)
217748
+ )
217749
+ ];
217711
217750
  onEvent({
217712
217751
  type: "diagnostic",
217713
217752
  code: "tool_call_budget_exhausted",
217714
- message: `Tool-call budget exhausted after ${realToolCallsStarted}/${maxToolCalls ?? 0} real tool call(s); suppressing ${skippedForToolBudget.size} requested call(s).`,
217753
+ message: `Tool budget reached after ${realToolCallsStarted} call(s) \u2014 finishing from gathered evidence.`,
217754
+ toolNames: skippedToolNames,
217755
+ skippedCount: skippedForToolBudget.size,
217756
+ maxToolCalls: maxToolCalls ?? 0,
217715
217757
  ts: now5()
217716
217758
  });
217717
217759
  }
@@ -217725,6 +217767,7 @@ async function runModelToolLoop(input) {
217725
217767
  firstPartyToolIntentUsed = true;
217726
217768
  }
217727
217769
  const batchTerminalIds = batch.map((toolCall) => {
217770
+ if (skippedForToolBudget.has(toolCall.id)) return null;
217728
217771
  onEvent({
217729
217772
  type: "tool_call_requested",
217730
217773
  toolName: toolCall.name,
@@ -217896,17 +217939,20 @@ async function runModelToolLoop(input) {
217896
217939
  consecutiveRequiredArgFailures,
217897
217940
  execution
217898
217941
  );
217899
- onEvent({
217900
- type: execution.ok ? "tool_call_completed" : "tool_call_failed",
217901
- toolName: toolCall.name,
217902
- toolCallId: toolCall.id,
217903
- riskLevel: execution.riskLevel,
217904
- ok: execution.ok,
217905
- errorCode: execution.errorCode,
217906
- error: execution.error,
217907
- metadata: browserPerceptionMetadata(execution),
217908
- ts: now5()
217909
- });
217942
+ const isToolBudgetSuppressed = execution.errorCode === "tool_call_budget_exhausted";
217943
+ if (!isToolBudgetSuppressed) {
217944
+ onEvent({
217945
+ type: execution.ok ? "tool_call_completed" : "tool_call_failed",
217946
+ toolName: toolCall.name,
217947
+ toolCallId: toolCall.id,
217948
+ riskLevel: execution.riskLevel,
217949
+ ok: execution.ok,
217950
+ errorCode: execution.errorCode,
217951
+ error: execution.error,
217952
+ metadata: browserPerceptionMetadata(execution),
217953
+ ts: now5()
217954
+ });
217955
+ }
217910
217956
  if (execution.ok && (toolCall.name === "writeLocalFile" || toolCall.name === "editLocalFile")) {
217911
217957
  const out = typeof execution.output === "object" && execution.output !== null ? execution.output : null;
217912
217958
  const filePath = typeof out?.relativePath === "string" ? out.relativePath : null;
@@ -217965,7 +218011,7 @@ async function runModelToolLoop(input) {
217965
218011
  });
217966
218012
  }
217967
218013
  const toolResultText = execution.outputSummary?.trim() ? execution.outputSummary : execution.result.slice(0, 1500);
217968
- if (toolResultText.trim() && !isTerminalTool) {
218014
+ if (toolResultText.trim() && !isTerminalTool && !isToolBudgetSuppressed) {
217969
218015
  onEvent({
217970
218016
  type: "tool_output_delta",
217971
218017
  toolName: toolCall.name,
@@ -217982,7 +218028,7 @@ async function runModelToolLoop(input) {
217982
218028
  });
217983
218029
  }
217984
218030
  const persistOption = autoRouterActive && lastAutoHopModelId ? getFounderModelOption(lastAutoHopModelId) ?? option : option;
217985
- if (persistToolCall && !isSyntheticReadSuppressionExecution(execution)) {
218031
+ if (persistToolCall && !isSyntheticReadSuppressionExecution(execution) && !isToolBudgetSuppressed) {
217986
218032
  await persistToolCall({
217987
218033
  toolName: execution.toolName,
217988
218034
  toolCallId: execution.toolCallId,
@@ -219469,7 +219515,7 @@ ${contextBlock}
219469
219515
  return;
219470
219516
  }
219471
219517
  if (event.type === "diagnostic") {
219472
- ctx.onEvent(event);
219518
+ ctx.onEvent({ ...event, workerId: args.workerId });
219473
219519
  return;
219474
219520
  }
219475
219521
  if (event.type === "model_call_failed") {
@@ -220309,7 +220355,7 @@ function flockCaps() {
220309
220355
  function resolveFlockWriteWorkerCap(input) {
220310
220356
  return input.permissionMode === "take_the_wheel" && input.planJustifiesMoreWriters ? FLOCK_MAX_WRITE_WORKERS_TAKE_THE_WHEEL : FLOCK_MAX_WRITE_WORKERS;
220311
220357
  }
220312
- var FLOCK_MIN_WORKERS, FLOCK_MAX_WORKERS, FLOCK_PREFERRED_WORKERS, FLOCK_DEFAULT_WALL_MS, FLOCK_MAX_WALL_MS, FLOCK_MAX_TOTAL_TOOL_CALLS, FLOCK_MAX_WORKER_ITERATIONS, FLOCK_MAX_WRITE_WORKERS, FLOCK_MAX_WRITE_WORKERS_TAKE_THE_WHEEL, FLOCK_MIN_TASK_CHARS, FLOCK_MIN_TASK_WORDS, FLOCK_MAX_TASK_CHARS;
220358
+ var FLOCK_MIN_WORKERS, FLOCK_MAX_WORKERS, FLOCK_PREFERRED_WORKERS, FLOCK_DEFAULT_WALL_MS, FLOCK_MAX_WALL_MS, FLOCK_MAX_TOTAL_TOOL_CALLS, FLOCK_MIN_WORKER_TOOL_CALLS, FLOCK_MAX_WORKER_TOOL_CALLS, FLOCK_MAX_WORKER_ITERATIONS, FLOCK_MAX_WRITE_WORKERS, FLOCK_MAX_WRITE_WORKERS_TAKE_THE_WHEEL, FLOCK_MIN_TASK_CHARS, FLOCK_MIN_TASK_WORDS, FLOCK_MAX_TASK_CHARS;
220313
220359
  var init_flockLimits = __esm({
220314
220360
  "features/perchTerminal/runtime/flock/flockLimits.ts"() {
220315
220361
  "use strict";
@@ -220319,7 +220365,9 @@ var init_flockLimits = __esm({
220319
220365
  FLOCK_PREFERRED_WORKERS = { min: 3, max: 6 };
220320
220366
  FLOCK_DEFAULT_WALL_MS = 8 * 6e4;
220321
220367
  FLOCK_MAX_WALL_MS = 10 * 6e4;
220322
- FLOCK_MAX_TOTAL_TOOL_CALLS = 80;
220368
+ FLOCK_MAX_TOTAL_TOOL_CALLS = 160;
220369
+ FLOCK_MIN_WORKER_TOOL_CALLS = 12;
220370
+ FLOCK_MAX_WORKER_TOOL_CALLS = 40;
220323
220371
  FLOCK_MAX_WORKER_ITERATIONS = Math.min(10, MAX_WORKER_ITERATIONS);
220324
220372
  FLOCK_MAX_WRITE_WORKERS = 2;
220325
220373
  FLOCK_MAX_WRITE_WORKERS_TAKE_THE_WHEEL = 4;
@@ -220329,6 +220377,118 @@ var init_flockLimits = __esm({
220329
220377
  }
220330
220378
  });
220331
220379
 
220380
+ // features/perchTerminal/runtime/flock/flockModelHints.ts
220381
+ function parseFlockModelHints(task) {
220382
+ const hints = [];
220383
+ const seen = /* @__PURE__ */ new Set();
220384
+ const push2 = (modelText, roleText, explicit) => {
220385
+ const model = modelText.trim().replace(/\s+/g, " ");
220386
+ const role = roleText.trim().toLowerCase();
220387
+ if (!model || !(role in ROLE_WORD_MAP)) return;
220388
+ const key = `${model.toLowerCase()}\u2192${role}`;
220389
+ if (seen.has(key)) return;
220390
+ seen.add(key);
220391
+ hints.push({ modelText: model, roleText: role, explicit });
220392
+ };
220393
+ const usePattern = new RegExp(
220394
+ `\\buse\\s+([A-Za-z0-9][A-Za-z0-9 ._/-]{0,30}?)\\s+(?:as|for)\\s+(?:an?\\s+|the\\s+)?(${ROLE_WORDS})\\b`,
220395
+ "gi"
220396
+ );
220397
+ for (const match of task.matchAll(usePattern)) {
220398
+ push2(match[1], match[2], true);
220399
+ }
220400
+ const pairPattern = new RegExp(
220401
+ `\\b([A-Z][A-Za-z0-9._-]{1,24})\\s+(${ROLE_WORDS})\\b`,
220402
+ "g"
220403
+ );
220404
+ for (const match of task.matchAll(pairPattern)) {
220405
+ if (/^(use|using|the|a|an|as|and|with|for)$/i.test(match[1])) continue;
220406
+ push2(match[1], match[2], false);
220407
+ }
220408
+ return hints;
220409
+ }
220410
+ function resolveFlockModelOption(modelText) {
220411
+ const needle = modelText.trim().toLowerCase();
220412
+ if (needle.length < 2) return null;
220413
+ const usable = FOUNDER_MODEL_OPTIONS.filter(
220414
+ (option) => isOptionAllowedForLane(option, "fast_worker") || isOptionAllowedForLane(option, "code_worker") || isOptionAllowedForLane(option, "verifier")
220415
+ );
220416
+ const haystacks = (option) => [option.label, option.vendor ?? "", option.family ?? "", option.modelId, option.id].join(" ").toLowerCase();
220417
+ const ranked = [...usable].sort(
220418
+ (a, b2) => Number(b2.userFacing === true) - Number(a.userFacing === true)
220419
+ );
220420
+ return ranked.find((option) => haystacks(option).includes(needle)) ?? null;
220421
+ }
220422
+ function applyFlockModelOverrides(plan, task) {
220423
+ const report = { applied: [], unavailable: [] };
220424
+ const hints = parseFlockModelHints(task);
220425
+ for (const hint of hints) {
220426
+ const option = resolveFlockModelOption(hint.modelText);
220427
+ if (!option) {
220428
+ if (hint.explicit) report.unavailable.push(hint);
220429
+ continue;
220430
+ }
220431
+ const roles = new Set(ROLE_WORD_MAP[hint.roleText] ?? []);
220432
+ const targets = plan.workers.filter(
220433
+ (worker) => roles.has(worker.role) || worker.displayName.toLowerCase().includes(hint.roleText)
220434
+ );
220435
+ if (targets.length === 0) continue;
220436
+ for (const worker of targets) {
220437
+ if (worker.modelOverride) continue;
220438
+ worker.modelOverride = { optionId: option.id, label: option.label };
220439
+ report.applied.push({
220440
+ flockWorkerId: worker.flockWorkerId,
220441
+ displayName: worker.displayName,
220442
+ modelText: hint.modelText,
220443
+ label: option.label
220444
+ });
220445
+ }
220446
+ }
220447
+ return report;
220448
+ }
220449
+ function founderSelectionWithModelOverride(base, optionId) {
220450
+ const selection = base ?? DEFAULT_FOUNDER_MODEL_SELECTION;
220451
+ return {
220452
+ ...selection,
220453
+ chatModelId: optionId,
220454
+ supervisorModelId: optionId,
220455
+ fastWorkerModelId: optionId,
220456
+ codeWorkerModelId: optionId,
220457
+ dataWorkerModelId: optionId,
220458
+ writerModelId: optionId,
220459
+ verifierModelId: optionId
220460
+ };
220461
+ }
220462
+ var ROLE_WORD_MAP, ROLE_WORDS;
220463
+ var init_flockModelHints = __esm({
220464
+ "features/perchTerminal/runtime/flock/flockModelHints.ts"() {
220465
+ "use strict";
220466
+ init_modelRegistry();
220467
+ ROLE_WORD_MAP = {
220468
+ scout: ["scout"],
220469
+ scouts: ["scout"],
220470
+ explorer: ["scout"],
220471
+ worker: ["worker"],
220472
+ workers: ["worker"],
220473
+ coder: ["worker"],
220474
+ patcher: ["worker"],
220475
+ builder: ["worker"],
220476
+ reviewer: ["verifier", "reducer"],
220477
+ reviewers: ["verifier", "reducer"],
220478
+ verifier: ["verifier"],
220479
+ verifiers: ["verifier"],
220480
+ tester: ["verifier"],
220481
+ checker: ["verifier"],
220482
+ reducer: ["reducer"],
220483
+ synthesizer: ["reducer"],
220484
+ synthesiser: ["reducer"],
220485
+ synth: ["reducer"],
220486
+ summarizer: ["reducer"]
220487
+ };
220488
+ ROLE_WORDS = Object.keys(ROLE_WORD_MAP).join("|");
220489
+ }
220490
+ });
220491
+
220332
220492
  // features/perchTerminal/runtime/flock/flockNicknames.ts
220333
220493
  function seedFromFlockId(flockId) {
220334
220494
  let hash = 2166136261;
@@ -220622,6 +220782,7 @@ function buildPlanWorker(spec, index, flockId, task, writeScope, caps) {
220622
220782
  writeScope: spec.writesWorkspace ? writeScope : null,
220623
220783
  outputContract: spec.outputContract,
220624
220784
  dependsOn: [],
220785
+ modelOverride: null,
220625
220786
  dynamicManifest: null
220626
220787
  };
220627
220788
  }
@@ -220685,7 +220846,7 @@ function buildFlockPlannerPrompts(ctx) {
220685
220846
  "- allowedTools: choose ONLY from availableTools in the input. Never request orchestration or delegation tools.",
220686
220847
  `- 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 })}.`,
220687
220848
  `- maxIterations: 1-${caps.maxIterationsPerWorker}.`,
220688
- "- dependsOn: ids of earlier workers whose output this worker needs. No cycles.",
220849
+ "- 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.",
220689
220850
  "- baseWorkerId: set ONLY to reuse an id from existingWorkers; otherwise null.",
220690
220851
  "- Decline (accepted=false, with reason) tasks that are trivial, conversational, or need no fanout."
220691
220852
  ].join("\n");
@@ -220814,6 +220975,8 @@ function validateLlmFlockPlan(record, ctx) {
220814
220975
  outputContract,
220815
220976
  dependsOn: [],
220816
220977
  // resolved below once all ids are known
220978
+ modelOverride: null,
220979
+ // applied later from explicit task hints only
220817
220980
  dynamicManifest: reusedManifest ? null : buildDynamicManifest({
220818
220981
  workerId: dynamicWorkerId,
220819
220982
  displayName,
@@ -221024,6 +221187,13 @@ async function runFlockTurn(input, deps, options = {}) {
221024
221187
  flockId
221025
221188
  });
221026
221189
  }
221190
+ if (plan.accepted && typeof options.totalToolCallBudget === "number") {
221191
+ plan.caps.maxTotalToolCalls = Math.max(
221192
+ 1,
221193
+ Math.min(Math.floor(options.totalToolCallBudget), plan.caps.maxTotalToolCalls)
221194
+ );
221195
+ }
221196
+ const modelOverrides = plan.accepted ? applyFlockModelOverrides(plan, task) : { applied: [], unavailable: [] };
221027
221197
  emitPlanEvent(emit, plan);
221028
221198
  if (!plan.accepted) {
221029
221199
  emit({
@@ -221070,9 +221240,12 @@ async function runFlockTurn(input, deps, options = {}) {
221070
221240
  const sharedContext = { task };
221071
221241
  let toolCallsUsed = 0;
221072
221242
  let toolCallsReserved = 0;
221243
+ let workersAwaitingLaunch = plan.workers.length;
221073
221244
  try {
221074
221245
  const phases = [...new Set(plan.workers.map((worker) => FLOCK_ROLE_ORDER[worker.role]))].sort((a, b2) => a - b2);
221075
221246
  const runReadyWorker = async (worker) => {
221247
+ const workersLeftIncludingThis = Math.max(1, workersAwaitingLaunch);
221248
+ workersAwaitingLaunch = Math.max(0, workersAwaitingLaunch - 1);
221076
221249
  if (flockRun.controller.signal.aborted) {
221077
221250
  const outcome = {
221078
221251
  worker,
@@ -221094,9 +221267,13 @@ async function runFlockTurn(input, deps, options = {}) {
221094
221267
  emitWorkerUpdate(emit, plan, worker, "skipped", outcome.detail);
221095
221268
  return outcome;
221096
221269
  }
221097
- const reservedToolCalls = Math.max(
221098
- 1,
221099
- Math.min(worker.maxIterations, remainingToolCalls)
221270
+ const fairShare = Math.floor(remainingToolCalls / workersLeftIncludingThis);
221271
+ const reservedToolCalls = Math.min(
221272
+ remainingToolCalls,
221273
+ Math.max(
221274
+ FLOCK_MIN_WORKER_TOOL_CALLS,
221275
+ Math.min(FLOCK_MAX_WORKER_TOOL_CALLS, fairShare)
221276
+ )
221100
221277
  );
221101
221278
  toolCallsReserved += reservedToolCalls;
221102
221279
  emitWorkerUpdate(emit, plan, worker, "running");
@@ -221109,7 +221286,7 @@ async function runFlockTurn(input, deps, options = {}) {
221109
221286
  maxIterations: worker.maxIterations,
221110
221287
  maxToolCalls: reservedToolCalls
221111
221288
  },
221112
- buildSpawnContext(input, plan.flockId, flockRun.controller.signal, emit)
221289
+ buildSpawnContext(input, plan.flockId, flockRun.controller.signal, emit, worker)
221113
221290
  );
221114
221291
  toolCallsReserved -= reservedToolCalls;
221115
221292
  toolCallsUsed += Math.min(result2.toolCalls ?? 0, reservedToolCalls);
@@ -221183,7 +221360,15 @@ async function runFlockTurn(input, deps, options = {}) {
221183
221360
  const workersDone = outcomes.filter((outcome) => outcome.status === "done").length;
221184
221361
  const workersFailed = outcomes.filter((outcome) => outcome.status === "failed").length;
221185
221362
  const flockStatus = userCancelled || wallTimeHit && workersDone === 0 ? "cancelled" : workersFailed === 0 && workersDone === plan.workers.length ? "completed" : workersDone > 0 ? "partial" : "failed";
221186
- const assistantText = buildFlockSummary(plan, outcomes, flockStatus, toolCallsUsed, wallTimeHit);
221363
+ const assistantText = [
221364
+ buildFlockSummary(plan, outcomes, flockStatus, toolCallsUsed, wallTimeHit),
221365
+ ...modelOverrides.applied.map(
221366
+ (override) => `Model override: ${override.displayName} ran on ${override.label}.`
221367
+ ),
221368
+ ...modelOverrides.unavailable.map(
221369
+ (override) => `Requested model "${override.modelText}"${override.roleText ? ` for ${override.roleText}` : ""} is not available \u2014 that worker stayed on the default model path.`
221370
+ )
221371
+ ].join("\n");
221187
221372
  emit({
221188
221373
  type: "flock_run_completed",
221189
221374
  flockId: plan.flockId,
@@ -221234,7 +221419,8 @@ function emitPlanEvent(emit, plan) {
221234
221419
  maxIterations: worker.maxIterations,
221235
221420
  allowedTools: worker.allowedTools,
221236
221421
  writeScope: worker.writeScope,
221237
- outputContract: worker.outputContract
221422
+ outputContract: worker.outputContract,
221423
+ modelOverride: worker.modelOverride?.label ?? null
221238
221424
  })) : [],
221239
221425
  caps: plan.accepted ? plan.caps : { maxWorkers: 0, maxWallMs: 0, maxTotalToolCalls: 0, maxIterationsPerWorker: 0 },
221240
221426
  ts: now6()
@@ -221264,7 +221450,7 @@ function buildWorkerContext(worker, sharedContext, outputByFlockWorkerId, plan)
221264
221450
  }
221265
221451
  return { task: sharedContext.task, dependencies };
221266
221452
  }
221267
- function buildSpawnContext(input, flockId, signal, emit) {
221453
+ function buildSpawnContext(input, flockId, signal, emit, worker) {
221268
221454
  return {
221269
221455
  workspaceRoot: input.activeRootPath ?? "",
221270
221456
  desktopConnected: input.desktopConnected,
@@ -221277,7 +221463,12 @@ function buildSpawnContext(input, flockId, signal, emit) {
221277
221463
  supabaseConfigured: input.supabaseConfigured,
221278
221464
  supabase: input.supabase ?? null,
221279
221465
  runId: flockId,
221280
- founderModelSelection: input.founderModelSelection ?? null,
221466
+ // Default: the currently selected Perch model path. Only an explicit
221467
+ // "use <model> as <role>" request in the task pins this worker elsewhere.
221468
+ founderModelSelection: worker.modelOverride ? founderSelectionWithModelOverride(
221469
+ input.founderModelSelection ?? null,
221470
+ worker.modelOverride.optionId
221471
+ ) : input.founderModelSelection ?? null,
221281
221472
  onEvent: emit,
221282
221473
  signal,
221283
221474
  mcpTools: input.mcpTools ?? []
@@ -221312,6 +221503,7 @@ var init_runFlockTurn = __esm({
221312
221503
  init_registry();
221313
221504
  init_flockCommand();
221314
221505
  init_flockLimits();
221506
+ init_flockModelHints();
221315
221507
  init_flockLlmPlanner();
221316
221508
  init_flockPlanner();
221317
221509
  init_flockRoles();
@@ -285013,10 +285205,12 @@ var init_build2 = __esm({
285013
285205
  // scripts/perch-cli.ts
285014
285206
  var perch_cli_exports = {};
285015
285207
  __export(perch_cli_exports, {
285208
+ FLOCK_NICKNAME_ACCENTS: () => FLOCK_NICKNAME_ACCENTS,
285016
285209
  HELP_TEXT: () => HELP_TEXT,
285017
285210
  INTERACTIVE_HELP_TEXT: () => INTERACTIVE_HELP_TEXT,
285018
285211
  PERCH_SPLASH_COMMANDS: () => PERCH_SPLASH_COMMANDS,
285019
285212
  flockEventToCliRow: () => flockEventToCliRow,
285213
+ flockNicknameAccentColor: () => flockNicknameAccentColor,
285020
285214
  parseInteractiveSlashCommand: () => parseInteractiveSlashCommand,
285021
285215
  parsePerchCli: () => parsePerchCli,
285022
285216
  runPerchCli: () => runPerchCli
@@ -285648,6 +285842,8 @@ async function runInkInteractivePerchCli(writer, deps, options) {
285648
285842
  setLiveText("");
285649
285843
  liveTextRef.current = "";
285650
285844
  const toolNamesById = /* @__PURE__ */ new Map();
285845
+ const flockWorkerNames = /* @__PURE__ */ new Map();
285846
+ const flockLimitMeta = /* @__PURE__ */ new Map();
285651
285847
  const clientRunId = createCliRunId();
285652
285848
  const externalController = new AbortController();
285653
285849
  const runtimeRun = registerRuntimeRun({
@@ -285939,10 +286135,40 @@ async function runInkInteractivePerchCli(writer, deps, options) {
285939
286135
  case "executor_waiting_for_user":
285940
286136
  addItem({ label: "wait", text: event.prompt, tone: "muted" });
285941
286137
  break;
286138
+ case "diagnostic": {
286139
+ if (event.code !== "tool_call_budget_exhausted") break;
286140
+ const limitKey = `limit-${event.workerId ?? "turn"}`;
286141
+ const meta2 = flockLimitMeta.get(limitKey) ?? { skipped: 0, tools: /* @__PURE__ */ new Set() };
286142
+ meta2.skipped += event.skippedCount ?? 0;
286143
+ for (const toolName of event.toolNames ?? []) meta2.tools.add(toolName);
286144
+ flockLimitMeta.set(limitKey, meta2);
286145
+ const who = event.workerId ? flockWorkerNames.get(event.workerId) ?? "A worker" : "The model";
286146
+ updateToolItem(limitKey, {
286147
+ label: "flock",
286148
+ text: `${who} hit its tool limit and is finishing from gathered evidence.`,
286149
+ tone: "muted",
286150
+ detailLines: [
286151
+ { tone: "meta", text: `cap tool-call budget \xB7 ${event.maxToolCalls ?? "?"} real calls allowed` },
286152
+ {
286153
+ tone: "meta",
286154
+ text: `skipped ${meta2.skipped} call(s)${meta2.tools.size ? `: ${[...meta2.tools].join(", ")}` : ""}`
286155
+ }
286156
+ ],
286157
+ expanded: false
286158
+ });
286159
+ break;
286160
+ }
285942
286161
  case "flock_run_started":
285943
286162
  case "flock_plan_ready":
285944
286163
  case "flock_worker_update":
285945
286164
  case "flock_run_completed": {
286165
+ if (event.type === "flock_plan_ready" && event.accepted) {
286166
+ for (const worker of event.workers) {
286167
+ flockWorkerNames.set(worker.workerId, worker.displayName);
286168
+ }
286169
+ } else if (event.type === "flock_worker_update") {
286170
+ flockWorkerNames.set(event.workerId, event.displayName);
286171
+ }
285946
286172
  const row = flockEventToCliRow(event);
285947
286173
  if (!row) break;
285948
286174
  if (row.id) {
@@ -286102,6 +286328,43 @@ async function runInkInteractivePerchCli(writer, deps, options) {
286102
286328
  renderInkDetailContent(React11, Ink2, line)
286103
286329
  )
286104
286330
  );
286331
+ const renderFlockRow = (key, line, tone, showLabel) => {
286332
+ const dotParts = line.split(" \xB7 ");
286333
+ const limitMatch = dotParts.length < 2 ? line.match(/^(.+?) (hit its tool limit .*)$/) : null;
286334
+ if (dotParts.length < 2 && !limitMatch) {
286335
+ return renderTranscriptRow(key, "flock", line, tone, showLabel);
286336
+ }
286337
+ if (limitMatch) {
286338
+ return renderTranscriptRow(key, "flock", line, tone, showLabel);
286339
+ }
286340
+ const name = dotParts[0];
286341
+ const nickname = dotParts[1] ?? "";
286342
+ const restText = dotParts.length > 2 ? ` \xB7 ${dotParts.slice(2).join(" \xB7 ")}` : "";
286343
+ return React11.createElement(
286344
+ Ink2.Box,
286345
+ { key },
286346
+ React11.createElement(
286347
+ Ink2.Box,
286348
+ { width: INK_LABEL_WIDTH, flexShrink: 0 },
286349
+ React11.createElement(
286350
+ Ink2.Text,
286351
+ { color: colorForInkTone(tone) },
286352
+ showLabel ? renderInkSpeakerLabel("flock") : ""
286353
+ )
286354
+ ),
286355
+ React11.createElement(
286356
+ Ink2.Box,
286357
+ { flexGrow: 1 },
286358
+ React11.createElement(
286359
+ Ink2.Text,
286360
+ null,
286361
+ React11.createElement(Ink2.Text, { color: bodyColorForInkTone(tone) }, `${name} \xB7 `),
286362
+ React11.createElement(Ink2.Text, { color: flockNicknameAccentColor(nickname), bold: true }, nickname),
286363
+ React11.createElement(Ink2.Text, { color: bodyColorForInkTone(tone) }, restText)
286364
+ )
286365
+ )
286366
+ );
286367
+ };
286105
286368
  const renderTranscriptItem = (item, index) => {
286106
286369
  const lines = item.text.split(/\r?\n/);
286107
286370
  const previous = items[index - 1];
@@ -286115,7 +286378,7 @@ async function runInkInteractivePerchCli(writer, deps, options) {
286115
286378
  React11.createElement(Ink2.Box, { width: INK_LABEL_WIDTH, flexShrink: 0 }),
286116
286379
  React11.createElement(Ink2.Text, { color: CLI_BRAND.divider }, INK_DIVIDER)
286117
286380
  ) : null,
286118
- lines.map((line, lineIndex) => renderTranscriptRow(
286381
+ lines.map((line, lineIndex) => item.label === "flock" ? renderFlockRow(`${item.id}-${lineIndex}`, line, item.tone, lineIndex === 0) : renderTranscriptRow(
286119
286382
  `${item.id}-${lineIndex}`,
286120
286383
  item.label,
286121
286384
  line,
@@ -286851,6 +287114,13 @@ function colorForInkTone(tone) {
286851
287114
  return CLI_BRAND.patina;
286852
287115
  }
286853
287116
  }
287117
+ function flockNicknameAccentColor(name) {
287118
+ let hash = 0;
287119
+ for (let i = 0; i < name.length; i++) {
287120
+ hash = hash * 31 + name.charCodeAt(i) >>> 0;
287121
+ }
287122
+ return FLOCK_NICKNAME_ACCENTS[hash % FLOCK_NICKNAME_ACCENTS.length];
287123
+ }
286854
287124
  function bodyColorForInkTone(tone) {
286855
287125
  switch (tone) {
286856
287126
  case "danger":
@@ -287533,7 +287803,7 @@ function defaultWriter() {
287533
287803
  stderr: (text) => process.stderr.write(text)
287534
287804
  };
287535
287805
  }
287536
- var execFileAsync3, DEFAULT_CLI_LOGIN_APP_URL, CLI_PACKAGE_VERSION, CLI_BRAND, ANSI2, HELP_TEXT, INTERACTIVE_HELP_TEXT, FLOCK_STATUS_LABELS, INK_LABEL_WIDTH, INK_ROW_LIMIT, INK_DETAIL_LINE_LIMIT, INK_DIVIDER, PERCH_MOTION_FRAMES, PERCH_SPLASH_WIDTH, PERCH_SPLASH_SCENE, PERCH_SPLASH_COMMANDS;
287806
+ var execFileAsync3, DEFAULT_CLI_LOGIN_APP_URL, CLI_PACKAGE_VERSION, CLI_BRAND, ANSI2, HELP_TEXT, INTERACTIVE_HELP_TEXT, FLOCK_STATUS_LABELS, INK_LABEL_WIDTH, INK_ROW_LIMIT, INK_DETAIL_LINE_LIMIT, INK_DIVIDER, PERCH_MOTION_FRAMES, PERCH_SPLASH_WIDTH, PERCH_SPLASH_SCENE, PERCH_SPLASH_COMMANDS, FLOCK_NICKNAME_ACCENTS;
287537
287807
  var init_perch_cli = __esm({
287538
287808
  "scripts/perch-cli.ts"() {
287539
287809
  "use strict";
@@ -287669,6 +287939,11 @@ Commands:
287669
287939
  ["/permission", "change autonomy for the next turns"],
287670
287940
  ["/login", "connect your Perch account"]
287671
287941
  ];
287942
+ FLOCK_NICKNAME_ACCENTS = [
287943
+ CLI_BRAND.patinaActive,
287944
+ CLI_BRAND.bronze,
287945
+ CLI_BRAND.bronzeGlint
287946
+ ];
287672
287947
  if (!process.env.PERCH_CLI_BUNDLE_ENTRY && process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
287673
287948
  runPerchCli(process.argv.slice(2)).then((code) => {
287674
287949
  process.exitCode = code;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perchai-cli",
3
- "version": "2.4.24",
3
+ "version": "2.4.26",
4
4
  "description": "Perch AI command-line interface",
5
5
  "bin": {
6
6
  "perch": "bin/perch"