perchai-cli 2.4.25 → 2.4.28

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 +253 -31
  2. package/package.json +1 -1
package/dist/perch.mjs CHANGED
@@ -75918,7 +75918,6 @@ function isTurnAbortedError(error) {
75918
75918
  var TURN_STOPPED_BY_USER_MESSAGE;
75919
75919
  var init_turnAbort = __esm({
75920
75920
  "features/perchTerminal/runtime/turnAbort.ts"() {
75921
- "use strict";
75922
75921
  TURN_STOPPED_BY_USER_MESSAGE = "Turn stopped by user.";
75923
75922
  }
75924
75923
  });
@@ -76219,6 +76218,7 @@ function getToolDisplayName(toolName) {
76219
76218
  var NON_MODULE_TOOL_OWNERS, TOOL_RISK, TOOL_DISPLAY_NAMES;
76220
76219
  var init_catalog = __esm({
76221
76220
  "features/perchTerminal/runtime/toolSystem/catalog.ts"() {
76221
+ "use strict";
76222
76222
  init_toolNames();
76223
76223
  NON_MODULE_TOOL_OWNERS = {
76224
76224
  [TOOL_NAMES.listSources]: "lane",
@@ -76922,6 +76922,8 @@ function buildTranscriptSegments(state) {
76922
76922
  const terminalSegmentIdx = /* @__PURE__ */ new Map();
76923
76923
  const workerRunIdx = /* @__PURE__ */ new Map();
76924
76924
  const flockRunIdx = /* @__PURE__ */ new Map();
76925
+ const flockIdByWorkerId = /* @__PURE__ */ new Map();
76926
+ const flockWorkerIdByToolCallId = /* @__PURE__ */ new Map();
76925
76927
  const diagnosticIdxByKey = /* @__PURE__ */ new Map();
76926
76928
  const capabilityIdx = /* @__PURE__ */ new Map();
76927
76929
  const liveCardIdx = /* @__PURE__ */ new Map();
@@ -76945,6 +76947,32 @@ function buildTranscriptSegments(state) {
76945
76947
  if (runId) sandboxRunIdx.set(runId, idx);
76946
76948
  if (toolCallId) sandboxRunIdx.set(toolCallId, idx);
76947
76949
  }
76950
+ function updateFlockWorkerByWorkerId(workerId, updater) {
76951
+ if (!workerId) return false;
76952
+ const flockId = flockIdByWorkerId.get(workerId);
76953
+ if (!flockId) return false;
76954
+ const idx = flockRunIdx.get(flockId);
76955
+ if (idx === void 0) return true;
76956
+ const seg = segments[idx];
76957
+ if (seg?.kind !== "flock_run") return true;
76958
+ segments[idx] = {
76959
+ ...seg,
76960
+ workers: seg.workers.map(
76961
+ (worker) => worker.workerId === workerId ? updater(worker) : worker
76962
+ )
76963
+ };
76964
+ return true;
76965
+ }
76966
+ function isFlockOwnedToolEvent(event) {
76967
+ return Boolean(
76968
+ event.workerId && flockIdByWorkerId.has(event.workerId) || event.toolCallId && flockWorkerIdByToolCallId.has(event.toolCallId)
76969
+ );
76970
+ }
76971
+ function isQuietFlockDiagnostic(event) {
76972
+ if (!event.workerId || !flockIdByWorkerId.has(event.workerId)) return false;
76973
+ if (event.code === "tool_call_budget_exhausted") return false;
76974
+ return event.code === "source_read_budget_reached" || event.code === "source_analysis_max_iteration_closing_attempted" || event.code === "source_analysis_max_iteration_synthesized" || event.code === "source_analysis_max_iteration_closing_fallback" || event.code === "source_analysis_max_iteration_incomplete" || /source-read budget|source-analysis|read-only source/i.test(event.message);
76975
+ }
76948
76976
  function flushReasoning() {
76949
76977
  if (!pendingReasoning) return;
76950
76978
  segments.push({ kind: "reasoning", text: pendingReasoning, seq: seq++, timestamp: pendingReasoningTs });
@@ -77038,6 +77066,15 @@ function buildTranscriptSegments(state) {
77038
77066
  ev.toolCallId ? [ev.toolCallId] : [],
77039
77067
  ev.ts
77040
77068
  );
77069
+ if (ev.workerId && flockIdByWorkerId.has(ev.workerId)) {
77070
+ if (ev.toolCallId) flockWorkerIdByToolCallId.set(ev.toolCallId, ev.workerId);
77071
+ updateFlockWorkerByWorkerId(ev.workerId, (worker) => ({
77072
+ ...worker,
77073
+ toolCalls: (worker.toolCalls ?? 0) + 1,
77074
+ lastToolName: ev.toolName
77075
+ }));
77076
+ break;
77077
+ }
77041
77078
  if (ev.workerId) {
77042
77079
  const idx = segments.length;
77043
77080
  segments.push({
@@ -77092,6 +77129,7 @@ function buildTranscriptSegments(state) {
77092
77129
  }
77093
77130
  case "tool_call_completed": {
77094
77131
  flushReasoning();
77132
+ if (isFlockOwnedToolEvent(ev)) break;
77095
77133
  const id = ev.toolCallId ?? ev.toolName;
77096
77134
  const idx = toolCallIdx.get(id);
77097
77135
  if (idx !== void 0) {
@@ -77108,6 +77146,7 @@ function buildTranscriptSegments(state) {
77108
77146
  }
77109
77147
  case "tool_result": {
77110
77148
  flushReasoning();
77149
+ if (isFlockOwnedToolEvent(ev)) break;
77111
77150
  const id = ev.toolCallId ?? ev.toolName;
77112
77151
  if (ev.toolName === "run_sandbox_code") {
77113
77152
  updateSandboxRun(ev.toolCallId, null, (seg) => ({ ...seg, resultText: ev.text }));
@@ -77136,6 +77175,7 @@ function buildTranscriptSegments(state) {
77136
77175
  case "tool_output_delta": {
77137
77176
  flushReasoning();
77138
77177
  if (!ev.text) break;
77178
+ if (isFlockOwnedToolEvent(ev)) break;
77139
77179
  const id = ev.toolCallId ?? ev.toolName;
77140
77180
  if (ev.toolName === "run_sandbox_code") {
77141
77181
  updateSandboxRun(ev.toolCallId, null, (seg) => ({ ...seg, resultText: `${seg.resultText ?? ""}${ev.text}` }));
@@ -77544,6 +77584,7 @@ function buildTranscriptSegments(state) {
77544
77584
  case "diagnostic": {
77545
77585
  flushReasoning();
77546
77586
  if (!ev.message.trim()) break;
77587
+ if (isQuietFlockDiagnostic(ev)) break;
77547
77588
  if (ev.code === "tool_call_budget_exhausted") {
77548
77589
  let attributed = false;
77549
77590
  if (ev.workerId) {
@@ -77695,6 +77736,7 @@ function buildTranscriptSegments(state) {
77695
77736
  }
77696
77737
  case "tool_call_failed": {
77697
77738
  flushReasoning();
77739
+ if (isFlockOwnedToolEvent(ev)) break;
77698
77740
  const id = ev.toolCallId ?? ev.toolName;
77699
77741
  const idx = toolCallIdx.get(id);
77700
77742
  if (idx !== void 0) {
@@ -77809,6 +77851,12 @@ function buildTranscriptSegments(state) {
77809
77851
  }
77810
77852
  case "worker_run_started": {
77811
77853
  flushReasoning();
77854
+ if (updateFlockWorkerByWorkerId(ev.workerId, (worker) => ({
77855
+ ...worker,
77856
+ status: worker.status === "queued" ? "running" : worker.status
77857
+ }))) {
77858
+ break;
77859
+ }
77812
77860
  const idx = segments.length;
77813
77861
  segments.push({
77814
77862
  kind: "worker_run",
@@ -77825,6 +77873,12 @@ function buildTranscriptSegments(state) {
77825
77873
  }
77826
77874
  case "worker_run_completed": {
77827
77875
  flushReasoning();
77876
+ if (updateFlockWorkerByWorkerId(ev.workerId, (worker) => ({
77877
+ ...worker,
77878
+ status: ev.ok ? "done" : "failed"
77879
+ }))) {
77880
+ break;
77881
+ }
77828
77882
  const runIdx = workerRunIdx.get(ev.workerId);
77829
77883
  if (runIdx !== void 0) {
77830
77884
  const seg = segments[runIdx];
@@ -77877,6 +77931,11 @@ function buildTranscriptSegments(state) {
77877
77931
  if (idx === void 0) break;
77878
77932
  const seg = segments[idx];
77879
77933
  if (seg?.kind !== "flock_run") break;
77934
+ if (ev.accepted) {
77935
+ for (const worker of ev.workers) {
77936
+ flockIdByWorkerId.set(worker.workerId, ev.flockId);
77937
+ }
77938
+ }
77880
77939
  segments[idx] = ev.accepted ? {
77881
77940
  ...seg,
77882
77941
  status: "running",
@@ -77898,7 +77957,11 @@ function buildTranscriptSegments(state) {
77898
77957
  if (seg?.kind !== "flock_run") break;
77899
77958
  const known = seg.workers.some((worker) => worker.flockWorkerId === ev.flockWorkerId);
77900
77959
  const workers = known ? seg.workers.map(
77901
- (worker) => worker.flockWorkerId === ev.flockWorkerId ? { ...worker, status: ev.status } : worker
77960
+ (worker) => worker.flockWorkerId === ev.flockWorkerId ? {
77961
+ ...worker,
77962
+ status: ev.status,
77963
+ note: ev.detail ?? worker.note
77964
+ } : worker
77902
77965
  ) : [
77903
77966
  ...seg.workers,
77904
77967
  {
@@ -77906,7 +77969,8 @@ function buildTranscriptSegments(state) {
77906
77969
  workerId: ev.workerId,
77907
77970
  displayName: ev.displayName,
77908
77971
  nickname: ev.nickname,
77909
- status: ev.status
77972
+ status: ev.status,
77973
+ note: ev.detail
77910
77974
  }
77911
77975
  ];
77912
77976
  segments[idx] = { ...seg, workers };
@@ -78309,9 +78373,13 @@ function segmentToCompact(seg) {
78309
78373
  summary: seg.summary?.slice(0, 300),
78310
78374
  flockWorkers: seg.workers.map((worker) => ({
78311
78375
  flockWorkerId: worker.flockWorkerId,
78376
+ workerId: worker.workerId,
78312
78377
  displayName: worker.displayName,
78313
78378
  nickname: worker.nickname,
78314
- status: worker.status
78379
+ status: worker.status,
78380
+ note: worker.note,
78381
+ toolCalls: worker.toolCalls,
78382
+ lastToolName: worker.lastToolName
78315
78383
  }))
78316
78384
  };
78317
78385
  case "worker_tool_call":
@@ -90909,7 +90977,6 @@ Final answers lead with findings, name artifacts or delivery status, and give on
90909
90977
  var MARKET_DESK_TOOL_NAMES;
90910
90978
  var init_marketDeskAccess = __esm({
90911
90979
  "features/perchTerminal/runtime/marketDesk/marketDeskAccess.ts"() {
90912
- "use strict";
90913
90980
  init_toolNames();
90914
90981
  MARKET_DESK_TOOL_NAMES = /* @__PURE__ */ new Set([
90915
90982
  TOOL_NAMES.getMarketSignal,
@@ -200071,6 +200138,7 @@ function recordDispatchBatchResult(tasks, result2, ctx, opts = {}) {
200071
200138
  var lifecycleByRun, MAX_LIFECYCLE_RUNS;
200072
200139
  var init_workerLifecycle = __esm({
200073
200140
  "features/perchTerminal/runtime/workers/workerLifecycle.ts"() {
200141
+ "use strict";
200074
200142
  init_workerManifest();
200075
200143
  lifecycleByRun = /* @__PURE__ */ new Map();
200076
200144
  MAX_LIFECYCLE_RUNS = 200;
@@ -220709,11 +220777,11 @@ function flockTaskGate(rawTask) {
220709
220777
  if (!task) {
220710
220778
  return { ok: false, task, reason: "No task given. Usage: /flock <task>." };
220711
220779
  }
220712
- if (/\/flock\b/i.test(task)) {
220780
+ if (/(^|[\s([{])\/flock(?:\s|$)/i.test(task)) {
220713
220781
  return {
220714
220782
  ok: false,
220715
220783
  task,
220716
- reason: "A flock cannot gather another flock \u2014 recursive /flock is not allowed."
220784
+ reason: "Subagents cannot start another /flock run from inside a /flock request."
220717
220785
  };
220718
220786
  }
220719
220787
  const wordCount = task.split(/\s+/).length;
@@ -221475,15 +221543,30 @@ function buildSpawnContext(input, flockId, signal, emit, worker) {
221475
221543
  };
221476
221544
  }
221477
221545
  function buildFlockSummary(plan, outcomes, status, toolCallsUsed, wallTimeHit) {
221478
- const header = status === "completed" ? `Flock finished: ${plan.summary}.` : status === "partial" ? `Flock finished with partial results (${plan.summary}).` : status === "cancelled" ? `Flock stopped early${wallTimeHit ? " \u2014 wall-time cap reached" : ""}.` : `Flock failed \u2014 no worker produced a usable result.`;
221546
+ const header = status === "completed" ? `Subagents finished: ${plan.summary}.` : status === "partial" ? `Subagents finished with partial results (${plan.summary}).` : status === "cancelled" ? `Subagents stopped early${wallTimeHit ? " \u2014 wall-time cap reached" : ""}.` : `Subagents failed \u2014 no worker produced a usable result.`;
221479
221547
  const lines = outcomes.map((outcome) => {
221480
221548
  const mark = outcome.status === "done" ? "done" : outcome.status === "failed" ? "failed" : outcome.status;
221481
- const detail = outcome.detail ? ` \u2014 ${clampLine(outcome.detail, 160)}` : "";
221549
+ const detailText = friendlyOutcomeDetail(outcome.detail);
221550
+ const detail = detailText ? ` \u2014 ${clampLine(detailText, 160)}` : "";
221482
221551
  return `- ${outcome.worker.displayName} (${outcome.worker.nickname}): ${mark}${detail}`;
221483
221552
  });
221484
221553
  const footer = `Used ${toolCallsUsed}/${plan.caps.maxTotalToolCalls} tool calls across ${outcomes.length} workers.`;
221485
221554
  return [header, ...lines, footer].join("\n");
221486
221555
  }
221556
+ function friendlyOutcomeDetail(detail) {
221557
+ const clean = clampLine(detail, 220);
221558
+ if (!clean) return "";
221559
+ if (/Reached maximum \d+ tool-call iterations/i.test(clean)) {
221560
+ return "finished from gathered evidence";
221561
+ }
221562
+ if (/source-read budget|read-only source|source-analysis/i.test(clean)) {
221563
+ return "used gathered evidence";
221564
+ }
221565
+ if (/tool-call budget exhausted|Tool budget reached/i.test(clean)) {
221566
+ return "tool limit reached; finished from evidence";
221567
+ }
221568
+ return clean;
221569
+ }
221487
221570
  function clampLine(text, max2) {
221488
221571
  const clean = (text ?? "").replace(/\s+/g, " ").trim();
221489
221572
  return clean.length > max2 ? `${clean.slice(0, max2 - 1)}\u2026` : clean;
@@ -230793,6 +230876,10 @@ var init_cliStandaloneOAuth = __esm({
230793
230876
  });
230794
230877
 
230795
230878
  // features/perchTerminal/runtime/contextMeterDisplay.ts
230879
+ function positiveInt3(value) {
230880
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return null;
230881
+ return Math.floor(value);
230882
+ }
230796
230883
  function formatTokenK(n) {
230797
230884
  if (n < 1e3) return `${Math.max(0, Math.round(n))}`;
230798
230885
  return `${(n / 1e3).toFixed(n < 1e4 ? 1 : 0)}k`;
@@ -230811,12 +230898,17 @@ function resolvePrimaryContextTokens(snapshot) {
230811
230898
  if (rowTokens > 0) return rowTokens;
230812
230899
  return 0;
230813
230900
  }
230814
- function resolveContextMeterDisplay(snapshot) {
230815
- const limitTokens = snapshot?.rawContextWindowTokens ?? snapshot?.effectiveLimitTokens ?? snapshot?.contextLimitTokens ?? getEffectiveContextWindow();
230901
+ function resolveContextMeterDisplay(snapshot, activeModel) {
230902
+ const activeRawLimitTokens = positiveInt3(activeModel?.contextWindow) ?? positiveInt3(activeModel?.contextWindowTokens);
230903
+ const activeEffectiveLimitTokens = activeRawLimitTokens ? getEffectiveContextWindow({
230904
+ contextWindow: activeRawLimitTokens,
230905
+ maxOutputTokens: activeModel?.maxOutputTokens ?? null
230906
+ }) : null;
230907
+ const limitTokens = activeRawLimitTokens ?? snapshot?.rawContextWindowTokens ?? activeEffectiveLimitTokens ?? snapshot?.effectiveLimitTokens ?? snapshot?.contextLimitTokens ?? getEffectiveContextWindow();
230816
230908
  const threadTokens = snapshot?.threadContextTokens ?? snapshot?.totalContextTokens ?? snapshot?.latestSendTokens ?? 0;
230817
230909
  const primaryTokens = resolvePrimaryContextTokens(snapshot);
230818
230910
  const rawLimitTokens = Math.max(1, limitTokens);
230819
- const effectiveLimitTokens = snapshot?.effectiveLimitTokens ?? snapshot?.contextLimitTokens ?? rawLimitTokens;
230911
+ const effectiveLimitTokens = activeEffectiveLimitTokens ?? snapshot?.effectiveLimitTokens ?? snapshot?.contextLimitTokens ?? rawLimitTokens;
230820
230912
  const reservedOutputTokens = Math.max(0, rawLimitTokens - effectiveLimitTokens);
230821
230913
  const committedTokens = Math.min(rawLimitTokens, primaryTokens + reservedOutputTokens);
230822
230914
  const workerUsageTokens = snapshot?.workerUsage?.reduce((sum, worker) => sum + Math.max(0, worker.totalTokens), 0) ?? 0;
@@ -230836,6 +230928,8 @@ function resolveContextMeterDisplay(snapshot) {
230836
230928
  workerTokens,
230837
230929
  jobTotalTokens: job,
230838
230930
  limitTokens,
230931
+ effectiveLimitTokens,
230932
+ reservedOutputTokens,
230839
230933
  composerLabel,
230840
230934
  hasWorkers
230841
230935
  };
@@ -285205,12 +285299,12 @@ var init_build2 = __esm({
285205
285299
  // scripts/perch-cli.ts
285206
285300
  var perch_cli_exports = {};
285207
285301
  __export(perch_cli_exports, {
285208
- FLOCK_NAME_ACCENTS: () => FLOCK_NAME_ACCENTS,
285302
+ FLOCK_NICKNAME_ACCENTS: () => FLOCK_NICKNAME_ACCENTS,
285209
285303
  HELP_TEXT: () => HELP_TEXT,
285210
285304
  INTERACTIVE_HELP_TEXT: () => INTERACTIVE_HELP_TEXT,
285211
285305
  PERCH_SPLASH_COMMANDS: () => PERCH_SPLASH_COMMANDS,
285212
285306
  flockEventToCliRow: () => flockEventToCliRow,
285213
- flockNameAccentColor: () => flockNameAccentColor,
285307
+ flockNicknameAccentColor: () => flockNicknameAccentColor,
285214
285308
  parseInteractiveSlashCommand: () => parseInteractiveSlashCommand,
285215
285309
  parsePerchCli: () => parsePerchCli,
285216
285310
  runPerchCli: () => runPerchCli
@@ -285305,7 +285399,8 @@ ${HELP_TEXT}`);
285305
285399
  trimRecentMessages(nextRecentMessages);
285306
285400
  writeCliTurnResult(result3, parsed.json, writer, {
285307
285401
  personaId: parsed.personaId,
285308
- color: shouldUseCliColor()
285402
+ color: shouldUseCliColor(),
285403
+ activeContextModel: resolveCliActiveContextModel(connection)
285309
285404
  });
285310
285405
  await admitCliLearningMemory(connection, {
285311
285406
  prompt: parsed.prompt,
@@ -285658,7 +285753,8 @@ async function runReadlineInteractivePerchCli(writer, deps, options) {
285658
285753
  });
285659
285754
  writeCliTurnResult(result2, false, writer, {
285660
285755
  personaId: state.personaId,
285661
- color: shouldUseCliColor()
285756
+ color: shouldUseCliColor(),
285757
+ activeContextModel: resolveCliActiveContextModel(connection)
285662
285758
  });
285663
285759
  appendRecentMessage(state.recentMessages, "user", prompt);
285664
285760
  if (result2.assistantText.trim()) {
@@ -285844,6 +285940,8 @@ async function runInkInteractivePerchCli(writer, deps, options) {
285844
285940
  const toolNamesById = /* @__PURE__ */ new Map();
285845
285941
  const flockWorkerNames = /* @__PURE__ */ new Map();
285846
285942
  const flockLimitMeta = /* @__PURE__ */ new Map();
285943
+ const aggregateToolIds = /* @__PURE__ */ new Map();
285944
+ const aggregateToolMeta = /* @__PURE__ */ new Map();
285847
285945
  const clientRunId = createCliRunId();
285848
285946
  const externalController = new AbortController();
285849
285947
  const runtimeRun = registerRuntimeRun({
@@ -285907,11 +286005,28 @@ async function runInkInteractivePerchCli(writer, deps, options) {
285907
286005
  case "tool_call_started": {
285908
286006
  const toolId = event.toolCallId ?? `${event.toolName}-${Date.now()}`;
285909
286007
  const name = humanizeCliToolName(event.toolName);
286008
+ const aggregateKind = cliToolAggregateKind(event.toolName);
285910
286009
  toolInputsById.current.set(toolId, {
285911
286010
  toolName: event.toolName,
285912
286011
  input: event.input ?? {}
285913
286012
  });
285914
286013
  toolNamesById.set(toolId, name);
286014
+ if (aggregateKind) {
286015
+ aggregateToolIds.set(toolId, aggregateKind);
286016
+ const meta2 = getCliToolAggregateMeta(aggregateToolMeta, aggregateKind);
286017
+ meta2.started += 1;
286018
+ meta2.pending += 1;
286019
+ appendCliToolAggregateDetail(meta2, event.toolName, event.input ?? {}, "started");
286020
+ updateToolItem(cliToolAggregateItemId(aggregateKind), {
286021
+ label: "tool",
286022
+ text: renderCliToolAggregateText(aggregateKind, meta2),
286023
+ tone: "muted",
286024
+ detailLines: meta2.detailLines,
286025
+ expanded: false
286026
+ });
286027
+ setWorkingText(cliToolAggregateWorkingText(aggregateKind));
286028
+ break;
286029
+ }
285915
286030
  const fileStart = buildFileToolDisplay(event.toolName, event.input ?? {}, "running");
285916
286031
  if (fileStart) richToolIds.current.add(toolId);
285917
286032
  updateToolItem(toolId, {
@@ -285927,6 +286042,22 @@ async function runInkInteractivePerchCli(writer, deps, options) {
285927
286042
  case "tool_call_completed": {
285928
286043
  const toolId = event.toolCallId ?? `${event.toolName}-${Date.now()}`;
285929
286044
  const stored = toolInputsById.current.get(toolId);
286045
+ const aggregateKind = aggregateToolIds.get(toolId);
286046
+ if (aggregateKind) {
286047
+ const meta2 = getCliToolAggregateMeta(aggregateToolMeta, aggregateKind);
286048
+ meta2.pending = Math.max(0, meta2.pending - 1);
286049
+ meta2.done += 1;
286050
+ appendCliToolAggregateDetail(meta2, event.toolName, stored?.input ?? {}, "done");
286051
+ updateToolItem(cliToolAggregateItemId(aggregateKind), {
286052
+ label: "tool",
286053
+ text: renderCliToolAggregateText(aggregateKind, meta2),
286054
+ tone: meta2.failed > 0 ? "danger" : "success",
286055
+ detailLines: meta2.detailLines,
286056
+ expanded: false
286057
+ });
286058
+ setWorkingText("thinking");
286059
+ break;
286060
+ }
285930
286061
  const fileDone = buildFileToolDisplay(event.toolName, stored?.input ?? {}, "done");
285931
286062
  if (fileDone) {
285932
286063
  richToolIds.current.add(toolId);
@@ -285956,6 +286087,22 @@ async function runInkInteractivePerchCli(writer, deps, options) {
285956
286087
  case "tool_call_failed": {
285957
286088
  const toolId = event.toolCallId ?? `${event.toolName}-${Date.now()}`;
285958
286089
  const name = toolNamesById.get(toolId) ?? humanizeCliToolName(event.toolName);
286090
+ const stored = toolInputsById.current.get(toolId);
286091
+ const aggregateKind = aggregateToolIds.get(toolId);
286092
+ if (aggregateKind) {
286093
+ const meta2 = getCliToolAggregateMeta(aggregateToolMeta, aggregateKind);
286094
+ meta2.pending = Math.max(0, meta2.pending - 1);
286095
+ meta2.failed += 1;
286096
+ appendCliToolAggregateDetail(meta2, event.toolName, stored?.input ?? {}, "failed", event.error);
286097
+ updateToolItem(cliToolAggregateItemId(aggregateKind), {
286098
+ label: "tool",
286099
+ text: renderCliToolAggregateText(aggregateKind, meta2),
286100
+ tone: "danger",
286101
+ detailLines: meta2.detailLines,
286102
+ expanded: false
286103
+ });
286104
+ break;
286105
+ }
285959
286106
  updateToolItem(toolId, {
285960
286107
  label: "need",
285961
286108
  text: `${name} \xB7 ${event.error ?? "needs attention"}`,
@@ -286334,8 +286481,12 @@ async function runInkInteractivePerchCli(writer, deps, options) {
286334
286481
  if (dotParts.length < 2 && !limitMatch) {
286335
286482
  return renderTranscriptRow(key, "flock", line, tone, showLabel);
286336
286483
  }
286337
- const name = limitMatch ? limitMatch[1] : dotParts[0];
286338
- const restText = limitMatch ? ` ${limitMatch[2]}` : ` \xB7 ${dotParts.slice(1).join(" \xB7 ")}`;
286484
+ if (limitMatch) {
286485
+ return renderTranscriptRow(key, "flock", line, tone, showLabel);
286486
+ }
286487
+ const name = dotParts[0];
286488
+ const nickname = dotParts[1] ?? "";
286489
+ const restText = dotParts.length > 2 ? ` \xB7 ${dotParts.slice(2).join(" \xB7 ")}` : "";
286339
286490
  return React11.createElement(
286340
286491
  Ink2.Box,
286341
286492
  { key },
@@ -286354,7 +286505,8 @@ async function runInkInteractivePerchCli(writer, deps, options) {
286354
286505
  React11.createElement(
286355
286506
  Ink2.Text,
286356
286507
  null,
286357
- React11.createElement(Ink2.Text, { color: flockNameAccentColor(name), bold: true }, name),
286508
+ React11.createElement(Ink2.Text, { color: bodyColorForInkTone(tone) }, `${name} \xB7 `),
286509
+ React11.createElement(Ink2.Text, { color: flockNicknameAccentColor(nickname), bold: true }, nickname),
286358
286510
  React11.createElement(Ink2.Text, { color: bodyColorForInkTone(tone) }, restText)
286359
286511
  )
286360
286512
  )
@@ -286510,7 +286662,10 @@ async function runInteractiveSlashCommand(input) {
286510
286662
  `);
286511
286663
  return "continue";
286512
286664
  case "context":
286513
- input.writer.stdout(renderCliContextDetails(input.state.contextSnapshot));
286665
+ input.writer.stdout(renderCliContextDetails(
286666
+ input.state.contextSnapshot,
286667
+ resolveCliActiveContextModel(input.getConnection?.() ?? null)
286668
+ ));
286514
286669
  return "continue";
286515
286670
  case "memoryAudit": {
286516
286671
  const audit = explainLearningAdmission({
@@ -286680,6 +286835,7 @@ function renderInteractiveStatus(state, connection, session, workspaceId) {
286680
286835
  const signedIn = session === void 0 ? isCliModelConnectionReady(connection) : isStoredCliAuthSessionUsable(session);
286681
286836
  const connectionStatus = isCliModelConnectionReady(connection) ? "connected" : "locked \xB7 run /login";
286682
286837
  const color = shouldUseCliColor();
286838
+ const activeContextModel = resolveCliActiveContextModel(connection);
286683
286839
  const lines = [
286684
286840
  ["version", CLI_PACKAGE_VERSION],
286685
286841
  ["cwd", state.cwd],
@@ -286692,7 +286848,7 @@ function renderInteractiveStatus(state, connection, session, workspaceId) {
286692
286848
  ["mode", state.chatMode],
286693
286849
  ["persona", state.personaId],
286694
286850
  ["thread", state.threadId],
286695
- ["context", renderCliContextMeter(state.contextSnapshot)],
286851
+ ["context", renderCliContextMeter(state.contextSnapshot, activeContextModel)],
286696
286852
  ["saved", state.persistedThreadUpdatedAt ? new Date(state.persistedThreadUpdatedAt).toLocaleString() : "(new thread)"],
286697
286853
  ["local-tools", state.cliLocalTools ? "on" : "off"]
286698
286854
  ];
@@ -287040,21 +287196,21 @@ function isPermanentMemoryLike2(value) {
287040
287196
  const memory = value;
287041
287197
  return typeof memory.id === "string" && typeof memory.title === "string" && typeof memory.body === "string";
287042
287198
  }
287043
- function renderCliContextMeter(snapshot) {
287199
+ function renderCliContextMeter(snapshot, activeModel) {
287044
287200
  if (!snapshot) return "Context";
287045
- const meter = resolveContextMeterDisplay(snapshot);
287201
+ const meter = resolveContextMeterDisplay(snapshot, activeModel);
287046
287202
  const percent2 = snapshot.contextPercentage ?? Math.round(meter.committedTokens / Math.max(1, meter.limitTokens) * 100);
287047
287203
  const parts = [`${meter.composerLabel}`, `${Math.max(0, Math.min(100, percent2))}%`];
287048
287204
  const compacted = snapshot.compactedRowCount ?? 0;
287049
287205
  if (snapshot.compacted || compacted > 0) parts.push(`compacted ${compacted}`);
287050
287206
  return parts.join(" \xB7 ");
287051
287207
  }
287052
- function renderCliContextDetails(snapshot) {
287208
+ function renderCliContextDetails(snapshot, activeModel) {
287053
287209
  if (!snapshot) return "context no meter yet\n";
287054
- const meter = resolveContextMeterDisplay(snapshot);
287210
+ const meter = resolveContextMeterDisplay(snapshot, activeModel);
287055
287211
  const percent2 = snapshot.contextPercentage ?? Math.round(meter.committedTokens / Math.max(1, meter.limitTokens) * 100);
287056
287212
  const lines = [
287057
- ["context", renderCliContextMeter(snapshot)],
287213
+ ["context", renderCliContextMeter(snapshot, activeModel)],
287058
287214
  ["thread", `${formatCliTokenCount(meter.threadTokens)} tokens`],
287059
287215
  ["limit", `${formatCliTokenCount(meter.limitTokens)} tokens`],
287060
287216
  ["fill", `${Math.max(0, Math.min(100, percent2))}%`],
@@ -287074,6 +287230,10 @@ function humanizeCliToolName(name) {
287074
287230
  function isCliModelConnectionReady(connection) {
287075
287231
  return Boolean(connection?.authenticated && connection.founderModelSelection);
287076
287232
  }
287233
+ function resolveCliActiveContextModel(connection) {
287234
+ if (!connection?.founderModelSelection) return null;
287235
+ return resolveModelForLane(connection.founderModelSelection, "chat");
287236
+ }
287077
287237
  function renderCliAuthSummary(connection) {
287078
287238
  if (isCliModelConnectionReady(connection)) {
287079
287239
  return `signed in${connection.email ? ` as ${connection.email}` : ""}`;
@@ -287109,12 +287269,12 @@ function colorForInkTone(tone) {
287109
287269
  return CLI_BRAND.patina;
287110
287270
  }
287111
287271
  }
287112
- function flockNameAccentColor(name) {
287272
+ function flockNicknameAccentColor(name) {
287113
287273
  let hash = 0;
287114
287274
  for (let i = 0; i < name.length; i++) {
287115
287275
  hash = hash * 31 + name.charCodeAt(i) >>> 0;
287116
287276
  }
287117
- return FLOCK_NAME_ACCENTS[hash % FLOCK_NAME_ACCENTS.length];
287277
+ return FLOCK_NICKNAME_ACCENTS[hash % FLOCK_NICKNAME_ACCENTS.length];
287118
287278
  }
287119
287279
  function bodyColorForInkTone(tone) {
287120
287280
  switch (tone) {
@@ -287410,6 +287570,67 @@ function buildFileToolDisplay(toolName, input, phase, summary) {
287410
287570
  ]
287411
287571
  };
287412
287572
  }
287573
+ function cliToolAggregateKind(toolName) {
287574
+ const normalized = toolName.toLowerCase();
287575
+ if (normalized === "readlocalfile" || normalized === "readlocalsourcefile") return "read";
287576
+ if (normalized === "glob" || normalized === "grep" || normalized === "listlocalsources" || normalized === "statpath" || normalized === "validateworkspaceroot") return "search";
287577
+ return null;
287578
+ }
287579
+ function getCliToolAggregateMeta(store, kind) {
287580
+ const existing = store.get(kind);
287581
+ if (existing) return existing;
287582
+ const created = {
287583
+ started: 0,
287584
+ done: 0,
287585
+ failed: 0,
287586
+ pending: 0,
287587
+ detailLines: []
287588
+ };
287589
+ store.set(kind, created);
287590
+ return created;
287591
+ }
287592
+ function cliToolAggregateItemId(kind) {
287593
+ return `tool-aggregate-${kind}`;
287594
+ }
287595
+ function cliToolAggregateWorkingText(kind) {
287596
+ return kind === "read" ? "reading files" : "searching";
287597
+ }
287598
+ function renderCliToolAggregateText(kind, meta) {
287599
+ const completed = meta.done + meta.failed;
287600
+ const count = Math.max(completed, meta.started);
287601
+ const noun = kind === "read" ? `file${count === 1 ? "" : "s"}` : `path${count === 1 ? "" : "s"}`;
287602
+ const verb = kind === "read" ? "read" : "searched";
287603
+ const status = meta.pending > 0 ? `${meta.pending} running` : "done";
287604
+ const failed = meta.failed > 0 ? ` \xB7 ${meta.failed} failed` : "";
287605
+ return `${verb} ${count} ${noun} \xB7 ${status}${failed} \xB7 ctrl-e`;
287606
+ }
287607
+ function appendCliToolAggregateDetail(meta, toolName, input, status, error) {
287608
+ const label = humanizeCliToolName(toolName);
287609
+ const target = describeCliToolAggregateInput(toolName, input);
287610
+ const tail = error ? ` \xB7 ${error}` : "";
287611
+ meta.detailLines.push({
287612
+ tone: "meta",
287613
+ text: `${label}${target ? ` \xB7 ${target}` : ""} \xB7 ${status}${tail}`
287614
+ });
287615
+ if (meta.detailLines.length > INK_DETAIL_LINE_LIMIT) {
287616
+ meta.detailLines = meta.detailLines.slice(-INK_DETAIL_LINE_LIMIT);
287617
+ }
287618
+ }
287619
+ function describeCliToolAggregateInput(toolName, input) {
287620
+ const normalized = toolName.toLowerCase();
287621
+ if (normalized === "glob") {
287622
+ const pattern = stringValue8(input.pattern) ?? stringValue8(input.glob) ?? "*";
287623
+ const base = stringValue8(input.path) ?? stringValue8(input.cwd) ?? ".";
287624
+ return `${pattern} in ${shortFilePath(base)}`;
287625
+ }
287626
+ if (normalized === "grep") {
287627
+ const pattern = stringValue8(input.pattern) ?? stringValue8(input.query) ?? "";
287628
+ const base = stringValue8(input.path) ?? stringValue8(input.cwd) ?? ".";
287629
+ return `${pattern || "pattern"} in ${shortFilePath(base)}`;
287630
+ }
287631
+ const filePath = stringValue8(input.path) ?? stringValue8(input.filePath) ?? stringValue8(input.localSourceId) ?? stringValue8(input.cwd);
287632
+ return filePath ? shortFilePath(filePath) : null;
287633
+ }
287413
287634
  function describeChangeSummary(summary) {
287414
287635
  const kind = summary.changeKind ?? "changed";
287415
287636
  const added = summary.linesAdded ?? 0;
@@ -287652,7 +287873,7 @@ function writeCliTurnResult(result2, json, writer, options = {}) {
287652
287873
  color: options.color,
287653
287874
  streamLabels: true
287654
287875
  });
287655
- const contextLine = `${paint("context".padEnd(12), "muted", options.color === true)} ${renderCliContextMeter(result2.contextSnapshot)}
287876
+ const contextLine = `${paint("context".padEnd(12), "muted", options.color === true)} ${renderCliContextMeter(result2.contextSnapshot, options.activeContextModel)}
287656
287877
  `;
287657
287878
  writer.stdout(`${rendered}${rendered.endsWith("\n") ? "" : "\n"}${contextLine}`);
287658
287879
  }
@@ -287798,7 +288019,7 @@ function defaultWriter() {
287798
288019
  stderr: (text) => process.stderr.write(text)
287799
288020
  };
287800
288021
  }
287801
- 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_NAME_ACCENTS;
288022
+ 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;
287802
288023
  var init_perch_cli = __esm({
287803
288024
  "scripts/perch-cli.ts"() {
287804
288025
  "use strict";
@@ -287808,6 +288029,7 @@ var init_perch_cli = __esm({
287808
288029
  init_cliAuthSession();
287809
288030
  init_cliStandaloneOAuth();
287810
288031
  init_contextMeterDisplay();
288032
+ init_modelRegistry();
287811
288033
  init_threadSession();
287812
288034
  init_runRegistry();
287813
288035
  init_learningMemory();
@@ -287934,7 +288156,7 @@ Commands:
287934
288156
  ["/permission", "change autonomy for the next turns"],
287935
288157
  ["/login", "connect your Perch account"]
287936
288158
  ];
287937
- FLOCK_NAME_ACCENTS = [
288159
+ FLOCK_NICKNAME_ACCENTS = [
287938
288160
  CLI_BRAND.patinaActive,
287939
288161
  CLI_BRAND.bronze,
287940
288162
  CLI_BRAND.bronzeGlint
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perchai-cli",
3
- "version": "2.4.25",
3
+ "version": "2.4.28",
4
4
  "description": "Perch AI command-line interface",
5
5
  "bin": {
6
6
  "perch": "bin/perch"