perchai-cli 2.4.26 → 2.4.29
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.
- package/README.md +1 -0
- package/dist/perch.mjs +530 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ Run `perch`, then use these commands inside the terminal chat:
|
|
|
19
19
|
|
|
20
20
|
- `/help` — show commands.
|
|
21
21
|
- `/status` — show cwd, auth, mode, persona, permission, and thread.
|
|
22
|
+
- `/usage` — show tokens and estimated cost for this session.
|
|
22
23
|
- `/cwd [dir]` — show or change the working directory.
|
|
23
24
|
- `/permission [default|auto_review|take_the_wheel|plan]` — show or change permission mode.
|
|
24
25
|
- `/permissions [default|auto_review|take_the_wheel|plan]` — alias for `/permission`.
|
package/dist/perch.mjs
CHANGED
|
@@ -75566,6 +75566,7 @@ var init_payroll = __esm({
|
|
|
75566
75566
|
// lib/perchBusinessTools/index.ts
|
|
75567
75567
|
var init_perchBusinessTools = __esm({
|
|
75568
75568
|
"lib/perchBusinessTools/index.ts"() {
|
|
75569
|
+
"use strict";
|
|
75569
75570
|
init_generateAPAuditPacket();
|
|
75570
75571
|
init_inventoryFolder();
|
|
75571
75572
|
init_loadBusinessTables();
|
|
@@ -75917,6 +75918,7 @@ function isTurnAbortedError(error) {
|
|
|
75917
75918
|
var TURN_STOPPED_BY_USER_MESSAGE;
|
|
75918
75919
|
var init_turnAbort = __esm({
|
|
75919
75920
|
"features/perchTerminal/runtime/turnAbort.ts"() {
|
|
75921
|
+
"use strict";
|
|
75920
75922
|
TURN_STOPPED_BY_USER_MESSAGE = "Turn stopped by user.";
|
|
75921
75923
|
}
|
|
75922
75924
|
});
|
|
@@ -76921,6 +76923,8 @@ function buildTranscriptSegments(state) {
|
|
|
76921
76923
|
const terminalSegmentIdx = /* @__PURE__ */ new Map();
|
|
76922
76924
|
const workerRunIdx = /* @__PURE__ */ new Map();
|
|
76923
76925
|
const flockRunIdx = /* @__PURE__ */ new Map();
|
|
76926
|
+
const flockIdByWorkerId = /* @__PURE__ */ new Map();
|
|
76927
|
+
const flockWorkerIdByToolCallId = /* @__PURE__ */ new Map();
|
|
76924
76928
|
const diagnosticIdxByKey = /* @__PURE__ */ new Map();
|
|
76925
76929
|
const capabilityIdx = /* @__PURE__ */ new Map();
|
|
76926
76930
|
const liveCardIdx = /* @__PURE__ */ new Map();
|
|
@@ -76944,6 +76948,32 @@ function buildTranscriptSegments(state) {
|
|
|
76944
76948
|
if (runId) sandboxRunIdx.set(runId, idx);
|
|
76945
76949
|
if (toolCallId) sandboxRunIdx.set(toolCallId, idx);
|
|
76946
76950
|
}
|
|
76951
|
+
function updateFlockWorkerByWorkerId(workerId, updater) {
|
|
76952
|
+
if (!workerId) return false;
|
|
76953
|
+
const flockId = flockIdByWorkerId.get(workerId);
|
|
76954
|
+
if (!flockId) return false;
|
|
76955
|
+
const idx = flockRunIdx.get(flockId);
|
|
76956
|
+
if (idx === void 0) return true;
|
|
76957
|
+
const seg = segments[idx];
|
|
76958
|
+
if (seg?.kind !== "flock_run") return true;
|
|
76959
|
+
segments[idx] = {
|
|
76960
|
+
...seg,
|
|
76961
|
+
workers: seg.workers.map(
|
|
76962
|
+
(worker) => worker.workerId === workerId ? updater(worker) : worker
|
|
76963
|
+
)
|
|
76964
|
+
};
|
|
76965
|
+
return true;
|
|
76966
|
+
}
|
|
76967
|
+
function isFlockOwnedToolEvent(event) {
|
|
76968
|
+
return Boolean(
|
|
76969
|
+
event.workerId && flockIdByWorkerId.has(event.workerId) || event.toolCallId && flockWorkerIdByToolCallId.has(event.toolCallId)
|
|
76970
|
+
);
|
|
76971
|
+
}
|
|
76972
|
+
function isQuietFlockDiagnostic(event) {
|
|
76973
|
+
if (!event.workerId || !flockIdByWorkerId.has(event.workerId)) return false;
|
|
76974
|
+
if (event.code === "tool_call_budget_exhausted") return false;
|
|
76975
|
+
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);
|
|
76976
|
+
}
|
|
76947
76977
|
function flushReasoning() {
|
|
76948
76978
|
if (!pendingReasoning) return;
|
|
76949
76979
|
segments.push({ kind: "reasoning", text: pendingReasoning, seq: seq++, timestamp: pendingReasoningTs });
|
|
@@ -77037,6 +77067,15 @@ function buildTranscriptSegments(state) {
|
|
|
77037
77067
|
ev.toolCallId ? [ev.toolCallId] : [],
|
|
77038
77068
|
ev.ts
|
|
77039
77069
|
);
|
|
77070
|
+
if (ev.workerId && flockIdByWorkerId.has(ev.workerId)) {
|
|
77071
|
+
if (ev.toolCallId) flockWorkerIdByToolCallId.set(ev.toolCallId, ev.workerId);
|
|
77072
|
+
updateFlockWorkerByWorkerId(ev.workerId, (worker) => ({
|
|
77073
|
+
...worker,
|
|
77074
|
+
toolCalls: (worker.toolCalls ?? 0) + 1,
|
|
77075
|
+
lastToolName: ev.toolName
|
|
77076
|
+
}));
|
|
77077
|
+
break;
|
|
77078
|
+
}
|
|
77040
77079
|
if (ev.workerId) {
|
|
77041
77080
|
const idx = segments.length;
|
|
77042
77081
|
segments.push({
|
|
@@ -77091,6 +77130,7 @@ function buildTranscriptSegments(state) {
|
|
|
77091
77130
|
}
|
|
77092
77131
|
case "tool_call_completed": {
|
|
77093
77132
|
flushReasoning();
|
|
77133
|
+
if (isFlockOwnedToolEvent(ev)) break;
|
|
77094
77134
|
const id = ev.toolCallId ?? ev.toolName;
|
|
77095
77135
|
const idx = toolCallIdx.get(id);
|
|
77096
77136
|
if (idx !== void 0) {
|
|
@@ -77107,6 +77147,7 @@ function buildTranscriptSegments(state) {
|
|
|
77107
77147
|
}
|
|
77108
77148
|
case "tool_result": {
|
|
77109
77149
|
flushReasoning();
|
|
77150
|
+
if (isFlockOwnedToolEvent(ev)) break;
|
|
77110
77151
|
const id = ev.toolCallId ?? ev.toolName;
|
|
77111
77152
|
if (ev.toolName === "run_sandbox_code") {
|
|
77112
77153
|
updateSandboxRun(ev.toolCallId, null, (seg) => ({ ...seg, resultText: ev.text }));
|
|
@@ -77135,6 +77176,7 @@ function buildTranscriptSegments(state) {
|
|
|
77135
77176
|
case "tool_output_delta": {
|
|
77136
77177
|
flushReasoning();
|
|
77137
77178
|
if (!ev.text) break;
|
|
77179
|
+
if (isFlockOwnedToolEvent(ev)) break;
|
|
77138
77180
|
const id = ev.toolCallId ?? ev.toolName;
|
|
77139
77181
|
if (ev.toolName === "run_sandbox_code") {
|
|
77140
77182
|
updateSandboxRun(ev.toolCallId, null, (seg) => ({ ...seg, resultText: `${seg.resultText ?? ""}${ev.text}` }));
|
|
@@ -77543,6 +77585,7 @@ function buildTranscriptSegments(state) {
|
|
|
77543
77585
|
case "diagnostic": {
|
|
77544
77586
|
flushReasoning();
|
|
77545
77587
|
if (!ev.message.trim()) break;
|
|
77588
|
+
if (isQuietFlockDiagnostic(ev)) break;
|
|
77546
77589
|
if (ev.code === "tool_call_budget_exhausted") {
|
|
77547
77590
|
let attributed = false;
|
|
77548
77591
|
if (ev.workerId) {
|
|
@@ -77694,6 +77737,7 @@ function buildTranscriptSegments(state) {
|
|
|
77694
77737
|
}
|
|
77695
77738
|
case "tool_call_failed": {
|
|
77696
77739
|
flushReasoning();
|
|
77740
|
+
if (isFlockOwnedToolEvent(ev)) break;
|
|
77697
77741
|
const id = ev.toolCallId ?? ev.toolName;
|
|
77698
77742
|
const idx = toolCallIdx.get(id);
|
|
77699
77743
|
if (idx !== void 0) {
|
|
@@ -77808,6 +77852,12 @@ function buildTranscriptSegments(state) {
|
|
|
77808
77852
|
}
|
|
77809
77853
|
case "worker_run_started": {
|
|
77810
77854
|
flushReasoning();
|
|
77855
|
+
if (updateFlockWorkerByWorkerId(ev.workerId, (worker) => ({
|
|
77856
|
+
...worker,
|
|
77857
|
+
status: worker.status === "queued" ? "running" : worker.status
|
|
77858
|
+
}))) {
|
|
77859
|
+
break;
|
|
77860
|
+
}
|
|
77811
77861
|
const idx = segments.length;
|
|
77812
77862
|
segments.push({
|
|
77813
77863
|
kind: "worker_run",
|
|
@@ -77824,6 +77874,12 @@ function buildTranscriptSegments(state) {
|
|
|
77824
77874
|
}
|
|
77825
77875
|
case "worker_run_completed": {
|
|
77826
77876
|
flushReasoning();
|
|
77877
|
+
if (updateFlockWorkerByWorkerId(ev.workerId, (worker) => ({
|
|
77878
|
+
...worker,
|
|
77879
|
+
status: ev.ok ? "done" : "failed"
|
|
77880
|
+
}))) {
|
|
77881
|
+
break;
|
|
77882
|
+
}
|
|
77827
77883
|
const runIdx = workerRunIdx.get(ev.workerId);
|
|
77828
77884
|
if (runIdx !== void 0) {
|
|
77829
77885
|
const seg = segments[runIdx];
|
|
@@ -77876,6 +77932,11 @@ function buildTranscriptSegments(state) {
|
|
|
77876
77932
|
if (idx === void 0) break;
|
|
77877
77933
|
const seg = segments[idx];
|
|
77878
77934
|
if (seg?.kind !== "flock_run") break;
|
|
77935
|
+
if (ev.accepted) {
|
|
77936
|
+
for (const worker of ev.workers) {
|
|
77937
|
+
flockIdByWorkerId.set(worker.workerId, ev.flockId);
|
|
77938
|
+
}
|
|
77939
|
+
}
|
|
77879
77940
|
segments[idx] = ev.accepted ? {
|
|
77880
77941
|
...seg,
|
|
77881
77942
|
status: "running",
|
|
@@ -77897,7 +77958,11 @@ function buildTranscriptSegments(state) {
|
|
|
77897
77958
|
if (seg?.kind !== "flock_run") break;
|
|
77898
77959
|
const known = seg.workers.some((worker) => worker.flockWorkerId === ev.flockWorkerId);
|
|
77899
77960
|
const workers = known ? seg.workers.map(
|
|
77900
|
-
(worker) => worker.flockWorkerId === ev.flockWorkerId ? {
|
|
77961
|
+
(worker) => worker.flockWorkerId === ev.flockWorkerId ? {
|
|
77962
|
+
...worker,
|
|
77963
|
+
status: ev.status,
|
|
77964
|
+
note: ev.detail ?? worker.note
|
|
77965
|
+
} : worker
|
|
77901
77966
|
) : [
|
|
77902
77967
|
...seg.workers,
|
|
77903
77968
|
{
|
|
@@ -77905,7 +77970,8 @@ function buildTranscriptSegments(state) {
|
|
|
77905
77970
|
workerId: ev.workerId,
|
|
77906
77971
|
displayName: ev.displayName,
|
|
77907
77972
|
nickname: ev.nickname,
|
|
77908
|
-
status: ev.status
|
|
77973
|
+
status: ev.status,
|
|
77974
|
+
note: ev.detail
|
|
77909
77975
|
}
|
|
77910
77976
|
];
|
|
77911
77977
|
segments[idx] = { ...seg, workers };
|
|
@@ -78308,9 +78374,13 @@ function segmentToCompact(seg) {
|
|
|
78308
78374
|
summary: seg.summary?.slice(0, 300),
|
|
78309
78375
|
flockWorkers: seg.workers.map((worker) => ({
|
|
78310
78376
|
flockWorkerId: worker.flockWorkerId,
|
|
78377
|
+
workerId: worker.workerId,
|
|
78311
78378
|
displayName: worker.displayName,
|
|
78312
78379
|
nickname: worker.nickname,
|
|
78313
|
-
status: worker.status
|
|
78380
|
+
status: worker.status,
|
|
78381
|
+
note: worker.note,
|
|
78382
|
+
toolCalls: worker.toolCalls,
|
|
78383
|
+
lastToolName: worker.lastToolName
|
|
78314
78384
|
}))
|
|
78315
78385
|
};
|
|
78316
78386
|
case "worker_tool_call":
|
|
@@ -86622,6 +86692,7 @@ function truncateHistoryLine(value, max2) {
|
|
|
86622
86692
|
}
|
|
86623
86693
|
var init_operatorTruth = __esm({
|
|
86624
86694
|
"features/perchTerminal/runtime/operatorTruth.ts"() {
|
|
86695
|
+
"use strict";
|
|
86625
86696
|
}
|
|
86626
86697
|
});
|
|
86627
86698
|
|
|
@@ -91587,7 +91658,6 @@ function listFinancialPlaybooks() {
|
|
|
91587
91658
|
var AP_AUDIT_PACKET_DEF, PLAYBOOKS;
|
|
91588
91659
|
var init_registry2 = __esm({
|
|
91589
91660
|
"features/perchTerminal/runtime/financialPlaybooks/registry.ts"() {
|
|
91590
|
-
"use strict";
|
|
91591
91661
|
init_managedWorkflowRegistry2();
|
|
91592
91662
|
init_toolNames();
|
|
91593
91663
|
AP_AUDIT_PACKET_DEF = {
|
|
@@ -220709,11 +220779,11 @@ function flockTaskGate(rawTask) {
|
|
|
220709
220779
|
if (!task) {
|
|
220710
220780
|
return { ok: false, task, reason: "No task given. Usage: /flock <task>." };
|
|
220711
220781
|
}
|
|
220712
|
-
if (
|
|
220782
|
+
if (/(^|[\s([{])\/flock(?:\s|$)/i.test(task)) {
|
|
220713
220783
|
return {
|
|
220714
220784
|
ok: false,
|
|
220715
220785
|
task,
|
|
220716
|
-
reason: "
|
|
220786
|
+
reason: "Subagents cannot start another /flock run from inside a /flock request."
|
|
220717
220787
|
};
|
|
220718
220788
|
}
|
|
220719
220789
|
const wordCount = task.split(/\s+/).length;
|
|
@@ -221475,15 +221545,30 @@ function buildSpawnContext(input, flockId, signal, emit, worker) {
|
|
|
221475
221545
|
};
|
|
221476
221546
|
}
|
|
221477
221547
|
function buildFlockSummary(plan, outcomes, status, toolCallsUsed, wallTimeHit) {
|
|
221478
|
-
const header = status === "completed" ? `
|
|
221548
|
+
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
221549
|
const lines = outcomes.map((outcome) => {
|
|
221480
221550
|
const mark = outcome.status === "done" ? "done" : outcome.status === "failed" ? "failed" : outcome.status;
|
|
221481
|
-
const
|
|
221551
|
+
const detailText = friendlyOutcomeDetail(outcome.detail);
|
|
221552
|
+
const detail = detailText ? ` \u2014 ${clampLine(detailText, 160)}` : "";
|
|
221482
221553
|
return `- ${outcome.worker.displayName} (${outcome.worker.nickname}): ${mark}${detail}`;
|
|
221483
221554
|
});
|
|
221484
221555
|
const footer = `Used ${toolCallsUsed}/${plan.caps.maxTotalToolCalls} tool calls across ${outcomes.length} workers.`;
|
|
221485
221556
|
return [header, ...lines, footer].join("\n");
|
|
221486
221557
|
}
|
|
221558
|
+
function friendlyOutcomeDetail(detail) {
|
|
221559
|
+
const clean = clampLine(detail, 220);
|
|
221560
|
+
if (!clean) return "";
|
|
221561
|
+
if (/Reached maximum \d+ tool-call iterations/i.test(clean)) {
|
|
221562
|
+
return "finished from gathered evidence";
|
|
221563
|
+
}
|
|
221564
|
+
if (/source-read budget|read-only source|source-analysis/i.test(clean)) {
|
|
221565
|
+
return "used gathered evidence";
|
|
221566
|
+
}
|
|
221567
|
+
if (/tool-call budget exhausted|Tool budget reached/i.test(clean)) {
|
|
221568
|
+
return "tool limit reached; finished from evidence";
|
|
221569
|
+
}
|
|
221570
|
+
return clean;
|
|
221571
|
+
}
|
|
221487
221572
|
function clampLine(text, max2) {
|
|
221488
221573
|
const clean = (text ?? "").replace(/\s+/g, " ").trim();
|
|
221489
221574
|
return clean.length > max2 ? `${clean.slice(0, max2 - 1)}\u2026` : clean;
|
|
@@ -221510,13 +221595,282 @@ var init_runFlockTurn = __esm({
|
|
|
221510
221595
|
}
|
|
221511
221596
|
});
|
|
221512
221597
|
|
|
221598
|
+
// features/perchTerminal/runtime/usage/usageCommand.ts
|
|
221599
|
+
function parseUsageCommand(raw) {
|
|
221600
|
+
const trimmed = raw.trim();
|
|
221601
|
+
if (!/^\/usage(?:\s|$)/i.test(trimmed)) return null;
|
|
221602
|
+
const task = trimmed.slice("/usage".length).trim();
|
|
221603
|
+
return task ? { kind: "usage", task } : { kind: "usage" };
|
|
221604
|
+
}
|
|
221605
|
+
var init_usageCommand = __esm({
|
|
221606
|
+
"features/perchTerminal/runtime/usage/usageCommand.ts"() {
|
|
221607
|
+
"use strict";
|
|
221608
|
+
}
|
|
221609
|
+
});
|
|
221610
|
+
|
|
221611
|
+
// lib/perch-ai/access.ts
|
|
221612
|
+
function hasPrivilegedPerchAiRole(memberships) {
|
|
221613
|
+
return memberships.some(
|
|
221614
|
+
(membership) => membership.status === "active" && (membership.role === "admin" || membership.role === "internal")
|
|
221615
|
+
);
|
|
221616
|
+
}
|
|
221617
|
+
function canShowPerchDevTools(membershipRole) {
|
|
221618
|
+
if (!membershipRole) return false;
|
|
221619
|
+
return hasPrivilegedPerchAiRole([
|
|
221620
|
+
{
|
|
221621
|
+
role: membershipRole,
|
|
221622
|
+
status: "active"
|
|
221623
|
+
}
|
|
221624
|
+
]);
|
|
221625
|
+
}
|
|
221626
|
+
var init_access = __esm({
|
|
221627
|
+
"lib/perch-ai/access.ts"() {
|
|
221628
|
+
}
|
|
221629
|
+
});
|
|
221630
|
+
|
|
221631
|
+
// features/perchTerminal/runtime/usage/usageSummary.ts
|
|
221632
|
+
function collectUsageRunIds(input) {
|
|
221633
|
+
const runIds = /* @__PURE__ */ new Set();
|
|
221634
|
+
for (const runId of input.usageRunIds ?? []) {
|
|
221635
|
+
const cleaned = runId.trim();
|
|
221636
|
+
if (cleaned) runIds.add(cleaned);
|
|
221637
|
+
}
|
|
221638
|
+
for (const message of input.recentMessages ?? []) {
|
|
221639
|
+
if (message.kind !== "text" || message.role !== "assistant") continue;
|
|
221640
|
+
const stateRunId = message.operatorState?.runId?.trim();
|
|
221641
|
+
if (stateRunId) runIds.add(stateRunId);
|
|
221642
|
+
for (const event of message.operatorState?.events ?? []) {
|
|
221643
|
+
if (event.type === "turn_started" && event.runId.trim()) runIds.add(event.runId.trim());
|
|
221644
|
+
}
|
|
221645
|
+
for (const event of message.transcriptEvents ?? []) {
|
|
221646
|
+
if (event.type === "turn_started" && event.runId.trim()) runIds.add(event.runId.trim());
|
|
221647
|
+
}
|
|
221648
|
+
}
|
|
221649
|
+
return Array.from(runIds).slice(-250);
|
|
221650
|
+
}
|
|
221651
|
+
function summarizeInferenceUsageRows(rows, options = {}) {
|
|
221652
|
+
const promptTokens = sumRows(rows, "prompt_tokens");
|
|
221653
|
+
const completionTokens = sumRows(rows, "completion_tokens");
|
|
221654
|
+
const cachedTokens = sumRows(rows, "cached_tokens");
|
|
221655
|
+
const totalTokens = sumRows(rows, "total_tokens");
|
|
221656
|
+
const estimatedCostUsd = rows.reduce((sum, row) => sum + readNumber(row.estimated_cost_usd), 0);
|
|
221657
|
+
const created = rows.map((row) => row.created_at).filter((value) => typeof value === "string" && value.length > 0).sort();
|
|
221658
|
+
const summary = {
|
|
221659
|
+
ok: true,
|
|
221660
|
+
calls: rows.length,
|
|
221661
|
+
promptTokens,
|
|
221662
|
+
completionTokens,
|
|
221663
|
+
cachedTokens,
|
|
221664
|
+
totalTokens,
|
|
221665
|
+
estimatedCostUsd,
|
|
221666
|
+
runCount: new Set(options.runIds?.length ? options.runIds : rows.map((row) => row.run_id).filter(Boolean)).size,
|
|
221667
|
+
since: created[0] ?? null,
|
|
221668
|
+
through: created[created.length - 1] ?? null,
|
|
221669
|
+
showModelDetails: Boolean(options.showModelDetails)
|
|
221670
|
+
};
|
|
221671
|
+
if (summary.showModelDetails) {
|
|
221672
|
+
summary.modelBreakdown = buildModelBreakdown(rows);
|
|
221673
|
+
}
|
|
221674
|
+
return summary;
|
|
221675
|
+
}
|
|
221676
|
+
function formatUsageSummaryText(summary) {
|
|
221677
|
+
const lines = [
|
|
221678
|
+
"Usage this session",
|
|
221679
|
+
`- Calls: ${formatInteger(summary.calls)}`,
|
|
221680
|
+
`- Tokens: ${formatInteger(summary.totalTokens)} total (${formatInteger(summary.promptTokens)} input \xB7 ${formatInteger(summary.completionTokens)} output${summary.cachedTokens ? ` \xB7 ${formatInteger(summary.cachedTokens)} cached` : ""})`,
|
|
221681
|
+
`- Estimated cost: ${formatUsd(summary.estimatedCostUsd)}`
|
|
221682
|
+
];
|
|
221683
|
+
if (summary.calls === 0) {
|
|
221684
|
+
lines.push("- Nothing has been recorded for this session yet.");
|
|
221685
|
+
}
|
|
221686
|
+
if (summary.showModelDetails && summary.modelBreakdown?.length) {
|
|
221687
|
+
lines.push("", "Internal detail:");
|
|
221688
|
+
for (const row of summary.modelBreakdown.slice(0, 8)) {
|
|
221689
|
+
lines.push(
|
|
221690
|
+
`- ${row.label}: ${formatInteger(row.totalTokens)} tokens \xB7 ${formatUsd(row.estimatedCostUsd)} \xB7 ${formatInteger(row.calls)} calls`
|
|
221691
|
+
);
|
|
221692
|
+
}
|
|
221693
|
+
}
|
|
221694
|
+
return lines.join("\n");
|
|
221695
|
+
}
|
|
221696
|
+
function buildModelBreakdown(rows) {
|
|
221697
|
+
const byModel = /* @__PURE__ */ new Map();
|
|
221698
|
+
for (const row of rows) {
|
|
221699
|
+
const label = [row.model_option_id, row.provider, row.model].filter((value) => typeof value === "string" && value.trim()).join(" \xB7 ") || "unknown model";
|
|
221700
|
+
const current = byModel.get(label) ?? {
|
|
221701
|
+
label,
|
|
221702
|
+
calls: 0,
|
|
221703
|
+
promptTokens: 0,
|
|
221704
|
+
completionTokens: 0,
|
|
221705
|
+
cachedTokens: 0,
|
|
221706
|
+
totalTokens: 0,
|
|
221707
|
+
estimatedCostUsd: 0
|
|
221708
|
+
};
|
|
221709
|
+
current.calls += 1;
|
|
221710
|
+
current.promptTokens += readNumber(row.prompt_tokens);
|
|
221711
|
+
current.completionTokens += readNumber(row.completion_tokens);
|
|
221712
|
+
current.cachedTokens += readNumber(row.cached_tokens);
|
|
221713
|
+
current.totalTokens += readNumber(row.total_tokens);
|
|
221714
|
+
current.estimatedCostUsd += readNumber(row.estimated_cost_usd);
|
|
221715
|
+
byModel.set(label, current);
|
|
221716
|
+
}
|
|
221717
|
+
return Array.from(byModel.values()).sort((a, b2) => b2.estimatedCostUsd - a.estimatedCostUsd);
|
|
221718
|
+
}
|
|
221719
|
+
function sumRows(rows, key) {
|
|
221720
|
+
return rows.reduce((sum, row) => sum + readNumber(row[key]), 0);
|
|
221721
|
+
}
|
|
221722
|
+
function readNumber(value) {
|
|
221723
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
221724
|
+
if (typeof value === "string") {
|
|
221725
|
+
const parsed = Number(value);
|
|
221726
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
221727
|
+
}
|
|
221728
|
+
return 0;
|
|
221729
|
+
}
|
|
221730
|
+
function formatInteger(value) {
|
|
221731
|
+
return Math.max(0, Math.round(value)).toLocaleString("en-US");
|
|
221732
|
+
}
|
|
221733
|
+
function formatUsd(value) {
|
|
221734
|
+
if (!Number.isFinite(value) || value <= 0) return "$0.00";
|
|
221735
|
+
return value < 0.01 ? `$${value.toFixed(4)}` : `$${value.toFixed(2)}`;
|
|
221736
|
+
}
|
|
221737
|
+
var init_usageSummary = __esm({
|
|
221738
|
+
"features/perchTerminal/runtime/usage/usageSummary.ts"() {
|
|
221739
|
+
"use strict";
|
|
221740
|
+
}
|
|
221741
|
+
});
|
|
221742
|
+
|
|
221743
|
+
// features/perchTerminal/runtime/usage/runUsageTurn.ts
|
|
221744
|
+
async function runUsageTurn(input, deps) {
|
|
221745
|
+
const startedAt = Date.now();
|
|
221746
|
+
const runId = input.clientRunId ?? `usage-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
221747
|
+
const now14 = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
221748
|
+
const events = [];
|
|
221749
|
+
const emit = (event) => {
|
|
221750
|
+
events.push(event);
|
|
221751
|
+
deps.onEvent?.(event);
|
|
221752
|
+
};
|
|
221753
|
+
emit({ type: "turn_started", runId, ts: now14(), isSystemNotification: false });
|
|
221754
|
+
let userMessageId = null;
|
|
221755
|
+
if (input.workspaceId && input.skipUserMessagePersistence !== true) {
|
|
221756
|
+
userMessageId = await deps.persistUserMessage({
|
|
221757
|
+
workspaceId: input.workspaceId,
|
|
221758
|
+
threadId: input.threadId,
|
|
221759
|
+
role: "user",
|
|
221760
|
+
mode: input.chatMode,
|
|
221761
|
+
content: input.trimmedInput
|
|
221762
|
+
});
|
|
221763
|
+
if (userMessageId) {
|
|
221764
|
+
emit({ type: "user_message_persisted", messageId: userMessageId, ts: now14() });
|
|
221765
|
+
}
|
|
221766
|
+
}
|
|
221767
|
+
const runIds = collectUsageRunIds({
|
|
221768
|
+
usageRunIds: input.usageRunIds,
|
|
221769
|
+
recentMessages: input.recentMessages
|
|
221770
|
+
});
|
|
221771
|
+
const showModelDetails = canShowPerchDevTools(input.sessionContext.membershipRole);
|
|
221772
|
+
const summary = await resolveUsageSummary(input, runIds, showModelDetails);
|
|
221773
|
+
const assistantText = formatUsageSummaryText(summary);
|
|
221774
|
+
let assistantMessageId = null;
|
|
221775
|
+
if (input.workspaceId) {
|
|
221776
|
+
assistantMessageId = await deps.persistAssistantMessage({
|
|
221777
|
+
workspaceId: input.workspaceId,
|
|
221778
|
+
threadId: input.threadId,
|
|
221779
|
+
role: "assistant",
|
|
221780
|
+
mode: input.chatMode,
|
|
221781
|
+
content: assistantText,
|
|
221782
|
+
contentJson: {
|
|
221783
|
+
slashCommand: "usage",
|
|
221784
|
+
turnSummary: assistantText,
|
|
221785
|
+
usageSummary: summary,
|
|
221786
|
+
transcriptEvents: events
|
|
221787
|
+
}
|
|
221788
|
+
});
|
|
221789
|
+
}
|
|
221790
|
+
emit({
|
|
221791
|
+
type: "assistant_response",
|
|
221792
|
+
text: assistantText,
|
|
221793
|
+
messageId: assistantMessageId ?? runId,
|
|
221794
|
+
ts: now14()
|
|
221795
|
+
});
|
|
221796
|
+
const durationMs = Date.now() - startedAt;
|
|
221797
|
+
emit({ type: "turn_completed", runId, durationMs, status: "completed", ts: now14() });
|
|
221798
|
+
return {
|
|
221799
|
+
status: "completed",
|
|
221800
|
+
assistantText,
|
|
221801
|
+
messageId: assistantMessageId ?? runId,
|
|
221802
|
+
runId,
|
|
221803
|
+
durationMs
|
|
221804
|
+
};
|
|
221805
|
+
}
|
|
221806
|
+
async function resolveUsageSummary(input, runIds, showModelDetails) {
|
|
221807
|
+
if (runIds.length === 0) {
|
|
221808
|
+
return summarizeInferenceUsageRows([], { showModelDetails, runIds });
|
|
221809
|
+
}
|
|
221810
|
+
if (input.supabase && input.userId && input.workspaceId) {
|
|
221811
|
+
const rows = await fetchUsageRows(input.supabase, {
|
|
221812
|
+
userId: input.userId,
|
|
221813
|
+
workspaceId: input.workspaceId,
|
|
221814
|
+
runIds
|
|
221815
|
+
});
|
|
221816
|
+
return summarizeInferenceUsageRows(rows, { showModelDetails, runIds });
|
|
221817
|
+
}
|
|
221818
|
+
const viaServer = await fetchUsageSummaryFromServer(input, runIds);
|
|
221819
|
+
if (viaServer) return viaServer;
|
|
221820
|
+
return summarizeInferenceUsageRows([], { showModelDetails, runIds });
|
|
221821
|
+
}
|
|
221822
|
+
async function fetchUsageRows(supabase, input) {
|
|
221823
|
+
const { data, error } = await supabase.from("perch_ai_inference_usage").select("*").eq("user_id", input.userId).eq("workspace_id", input.workspaceId).in("run_id", input.runIds).order("created_at", { ascending: true }).limit(1e3);
|
|
221824
|
+
if (error) throw error;
|
|
221825
|
+
return data ?? [];
|
|
221826
|
+
}
|
|
221827
|
+
async function fetchUsageSummaryFromServer(input, runIds) {
|
|
221828
|
+
const appUrl = input.cliServerAppUrl?.trim();
|
|
221829
|
+
const token = input.cliServerAccessToken?.trim();
|
|
221830
|
+
if (!appUrl || !token || typeof fetch !== "function") return null;
|
|
221831
|
+
const controller = new AbortController();
|
|
221832
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
221833
|
+
try {
|
|
221834
|
+
const response = await fetch(`${appUrl.replace(/\/+$/, "")}/api/perch-terminal/usage`, {
|
|
221835
|
+
method: "POST",
|
|
221836
|
+
headers: {
|
|
221837
|
+
Accept: "application/json",
|
|
221838
|
+
"Content-Type": "application/json",
|
|
221839
|
+
Authorization: `Bearer ${token}`
|
|
221840
|
+
},
|
|
221841
|
+
body: JSON.stringify({ runIds }),
|
|
221842
|
+
signal: controller.signal
|
|
221843
|
+
});
|
|
221844
|
+
if (!response.ok) return null;
|
|
221845
|
+
const payload = await response.json();
|
|
221846
|
+
return payload.ok ? payload.summary : null;
|
|
221847
|
+
} catch {
|
|
221848
|
+
return null;
|
|
221849
|
+
} finally {
|
|
221850
|
+
clearTimeout(timeout);
|
|
221851
|
+
}
|
|
221852
|
+
}
|
|
221853
|
+
var init_runUsageTurn = __esm({
|
|
221854
|
+
"features/perchTerminal/runtime/usage/runUsageTurn.ts"() {
|
|
221855
|
+
"use strict";
|
|
221856
|
+
init_access();
|
|
221857
|
+
init_usageSummary();
|
|
221858
|
+
}
|
|
221859
|
+
});
|
|
221860
|
+
|
|
221513
221861
|
// features/perchTerminal/runtime/commands/sharedSlashCommands.ts
|
|
221514
221862
|
function parseSharedSlashCommand(raw) {
|
|
221515
|
-
return parseFlockCommand(raw);
|
|
221863
|
+
return parseUsageCommand(raw) ?? parseFlockCommand(raw);
|
|
221864
|
+
}
|
|
221865
|
+
function sharedSlashCommandRunsAsTurn(command) {
|
|
221866
|
+
if (!command) return false;
|
|
221867
|
+
if (command.kind === "usage") return true;
|
|
221868
|
+
return Boolean(command.task);
|
|
221516
221869
|
}
|
|
221517
221870
|
function resolveTurnRunnerForPrompt(raw) {
|
|
221518
221871
|
const parsed = parseSharedSlashCommand(raw);
|
|
221519
221872
|
if (!parsed) return null;
|
|
221873
|
+
if (parsed.kind === "usage") return runUsageTurn;
|
|
221520
221874
|
return runFlockTurn;
|
|
221521
221875
|
}
|
|
221522
221876
|
var init_sharedSlashCommands = __esm({
|
|
@@ -221524,6 +221878,8 @@ var init_sharedSlashCommands = __esm({
|
|
|
221524
221878
|
"use strict";
|
|
221525
221879
|
init_flockCommand();
|
|
221526
221880
|
init_runFlockTurn();
|
|
221881
|
+
init_usageCommand();
|
|
221882
|
+
init_runUsageTurn();
|
|
221527
221883
|
}
|
|
221528
221884
|
});
|
|
221529
221885
|
|
|
@@ -230123,6 +230479,7 @@ function buildCliTurnInput(input, resolved) {
|
|
|
230123
230479
|
userId,
|
|
230124
230480
|
selectedSourceId: input.selectedSourceId ?? null,
|
|
230125
230481
|
recentMessages: input.recentMessages ?? [],
|
|
230482
|
+
usageRunIds: input.usageRunIds ?? [],
|
|
230126
230483
|
sessionContext: emptyPerchTerminalSessionContext({
|
|
230127
230484
|
userId,
|
|
230128
230485
|
workspaceId
|
|
@@ -230793,6 +231150,10 @@ var init_cliStandaloneOAuth = __esm({
|
|
|
230793
231150
|
});
|
|
230794
231151
|
|
|
230795
231152
|
// features/perchTerminal/runtime/contextMeterDisplay.ts
|
|
231153
|
+
function positiveInt3(value) {
|
|
231154
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return null;
|
|
231155
|
+
return Math.floor(value);
|
|
231156
|
+
}
|
|
230796
231157
|
function formatTokenK(n) {
|
|
230797
231158
|
if (n < 1e3) return `${Math.max(0, Math.round(n))}`;
|
|
230798
231159
|
return `${(n / 1e3).toFixed(n < 1e4 ? 1 : 0)}k`;
|
|
@@ -230811,12 +231172,17 @@ function resolvePrimaryContextTokens(snapshot) {
|
|
|
230811
231172
|
if (rowTokens > 0) return rowTokens;
|
|
230812
231173
|
return 0;
|
|
230813
231174
|
}
|
|
230814
|
-
function resolveContextMeterDisplay(snapshot) {
|
|
230815
|
-
const
|
|
231175
|
+
function resolveContextMeterDisplay(snapshot, activeModel) {
|
|
231176
|
+
const activeRawLimitTokens = positiveInt3(activeModel?.contextWindow) ?? positiveInt3(activeModel?.contextWindowTokens);
|
|
231177
|
+
const activeEffectiveLimitTokens = activeRawLimitTokens ? getEffectiveContextWindow({
|
|
231178
|
+
contextWindow: activeRawLimitTokens,
|
|
231179
|
+
maxOutputTokens: activeModel?.maxOutputTokens ?? null
|
|
231180
|
+
}) : null;
|
|
231181
|
+
const limitTokens = activeRawLimitTokens ?? snapshot?.rawContextWindowTokens ?? activeEffectiveLimitTokens ?? snapshot?.effectiveLimitTokens ?? snapshot?.contextLimitTokens ?? getEffectiveContextWindow();
|
|
230816
231182
|
const threadTokens = snapshot?.threadContextTokens ?? snapshot?.totalContextTokens ?? snapshot?.latestSendTokens ?? 0;
|
|
230817
231183
|
const primaryTokens = resolvePrimaryContextTokens(snapshot);
|
|
230818
231184
|
const rawLimitTokens = Math.max(1, limitTokens);
|
|
230819
|
-
const effectiveLimitTokens = snapshot?.effectiveLimitTokens ?? snapshot?.contextLimitTokens ?? rawLimitTokens;
|
|
231185
|
+
const effectiveLimitTokens = activeEffectiveLimitTokens ?? snapshot?.effectiveLimitTokens ?? snapshot?.contextLimitTokens ?? rawLimitTokens;
|
|
230820
231186
|
const reservedOutputTokens = Math.max(0, rawLimitTokens - effectiveLimitTokens);
|
|
230821
231187
|
const committedTokens = Math.min(rawLimitTokens, primaryTokens + reservedOutputTokens);
|
|
230822
231188
|
const workerUsageTokens = snapshot?.workerUsage?.reduce((sum, worker) => sum + Math.max(0, worker.totalTokens), 0) ?? 0;
|
|
@@ -230836,6 +231202,8 @@ function resolveContextMeterDisplay(snapshot) {
|
|
|
230836
231202
|
workerTokens,
|
|
230837
231203
|
jobTotalTokens: job,
|
|
230838
231204
|
limitTokens,
|
|
231205
|
+
effectiveLimitTokens,
|
|
231206
|
+
reservedOutputTokens,
|
|
230839
231207
|
composerLabel,
|
|
230840
231208
|
hasWorkers
|
|
230841
231209
|
};
|
|
@@ -285305,7 +285673,8 @@ ${HELP_TEXT}`);
|
|
|
285305
285673
|
trimRecentMessages(nextRecentMessages);
|
|
285306
285674
|
writeCliTurnResult(result3, parsed.json, writer, {
|
|
285307
285675
|
personaId: parsed.personaId,
|
|
285308
|
-
color: shouldUseCliColor()
|
|
285676
|
+
color: shouldUseCliColor(),
|
|
285677
|
+
activeContextModel: resolveCliActiveContextModel(connection)
|
|
285309
285678
|
});
|
|
285310
285679
|
await admitCliLearningMemory(connection, {
|
|
285311
285680
|
prompt: parsed.prompt,
|
|
@@ -285610,7 +285979,7 @@ async function runReadlineInteractivePerchCli(writer, deps, options) {
|
|
|
285610
285979
|
if (!prompt) continue;
|
|
285611
285980
|
if (prompt === "/exit" || prompt === "exit" || prompt === "quit") break;
|
|
285612
285981
|
const sharedCommand = parseSharedSlashCommand(prompt);
|
|
285613
|
-
const runsAsSharedTurn =
|
|
285982
|
+
const runsAsSharedTurn = sharedSlashCommandRunsAsTurn(sharedCommand);
|
|
285614
285983
|
if (prompt.startsWith("/") && !runsAsSharedTurn) {
|
|
285615
285984
|
try {
|
|
285616
285985
|
const commandResult = await runInteractiveSlashCommand({
|
|
@@ -285648,6 +286017,7 @@ async function runReadlineInteractivePerchCli(writer, deps, options) {
|
|
|
285648
286017
|
permissionMode: state.permissionMode,
|
|
285649
286018
|
threadId: state.threadId,
|
|
285650
286019
|
recentMessages: state.recentMessages,
|
|
286020
|
+
usageRunIds: state.usageRunIds,
|
|
285651
286021
|
userId: hostedContext.userId,
|
|
285652
286022
|
workspaceId: hostedContext.workspaceId,
|
|
285653
286023
|
permanentMemories: hostedContext.permanentMemories,
|
|
@@ -285658,13 +286028,15 @@ async function runReadlineInteractivePerchCli(writer, deps, options) {
|
|
|
285658
286028
|
});
|
|
285659
286029
|
writeCliTurnResult(result2, false, writer, {
|
|
285660
286030
|
personaId: state.personaId,
|
|
285661
|
-
color: shouldUseCliColor()
|
|
286031
|
+
color: shouldUseCliColor(),
|
|
286032
|
+
activeContextModel: resolveCliActiveContextModel(connection)
|
|
285662
286033
|
});
|
|
285663
286034
|
appendRecentMessage(state.recentMessages, "user", prompt);
|
|
285664
286035
|
if (result2.assistantText.trim()) {
|
|
285665
286036
|
appendRecentMessage(state.recentMessages, "assistant", result2.assistantText.trim());
|
|
285666
286037
|
}
|
|
285667
286038
|
trimRecentMessages(state.recentMessages);
|
|
286039
|
+
recordCliUsageRunId(state, result2.runId);
|
|
285668
286040
|
state.contextSnapshot = result2.contextSnapshot ?? state.contextSnapshot;
|
|
285669
286041
|
const admitted = await admitCliLearningMemory(connection, {
|
|
285670
286042
|
prompt,
|
|
@@ -285803,7 +286175,7 @@ async function runInkInteractivePerchCli(writer, deps, options) {
|
|
|
285803
286175
|
return;
|
|
285804
286176
|
}
|
|
285805
286177
|
const sharedTurnCommand = parseSharedSlashCommand(prompt);
|
|
285806
|
-
if (prompt.startsWith("/") && !sharedTurnCommand
|
|
286178
|
+
if (prompt.startsWith("/") && !sharedSlashCommandRunsAsTurn(sharedTurnCommand)) {
|
|
285807
286179
|
const commandWriter = {
|
|
285808
286180
|
stdout: (text) => {
|
|
285809
286181
|
const clean = text.trim();
|
|
@@ -285844,6 +286216,8 @@ async function runInkInteractivePerchCli(writer, deps, options) {
|
|
|
285844
286216
|
const toolNamesById = /* @__PURE__ */ new Map();
|
|
285845
286217
|
const flockWorkerNames = /* @__PURE__ */ new Map();
|
|
285846
286218
|
const flockLimitMeta = /* @__PURE__ */ new Map();
|
|
286219
|
+
const aggregateToolIds = /* @__PURE__ */ new Map();
|
|
286220
|
+
const aggregateToolMeta = /* @__PURE__ */ new Map();
|
|
285847
286221
|
const clientRunId = createCliRunId();
|
|
285848
286222
|
const externalController = new AbortController();
|
|
285849
286223
|
const runtimeRun = registerRuntimeRun({
|
|
@@ -285875,6 +286249,7 @@ async function runInkInteractivePerchCli(writer, deps, options) {
|
|
|
285875
286249
|
permissionMode: state.permissionMode,
|
|
285876
286250
|
threadId: state.threadId,
|
|
285877
286251
|
recentMessages: state.recentMessages,
|
|
286252
|
+
usageRunIds: state.usageRunIds,
|
|
285878
286253
|
userId: hostedContext.userId,
|
|
285879
286254
|
workspaceId: hostedContext.workspaceId,
|
|
285880
286255
|
permanentMemories: hostedContext.permanentMemories,
|
|
@@ -285907,11 +286282,28 @@ async function runInkInteractivePerchCli(writer, deps, options) {
|
|
|
285907
286282
|
case "tool_call_started": {
|
|
285908
286283
|
const toolId = event.toolCallId ?? `${event.toolName}-${Date.now()}`;
|
|
285909
286284
|
const name = humanizeCliToolName(event.toolName);
|
|
286285
|
+
const aggregateKind = cliToolAggregateKind(event.toolName);
|
|
285910
286286
|
toolInputsById.current.set(toolId, {
|
|
285911
286287
|
toolName: event.toolName,
|
|
285912
286288
|
input: event.input ?? {}
|
|
285913
286289
|
});
|
|
285914
286290
|
toolNamesById.set(toolId, name);
|
|
286291
|
+
if (aggregateKind) {
|
|
286292
|
+
aggregateToolIds.set(toolId, aggregateKind);
|
|
286293
|
+
const meta2 = getCliToolAggregateMeta(aggregateToolMeta, aggregateKind);
|
|
286294
|
+
meta2.started += 1;
|
|
286295
|
+
meta2.pending += 1;
|
|
286296
|
+
appendCliToolAggregateDetail(meta2, event.toolName, event.input ?? {}, "started");
|
|
286297
|
+
updateToolItem(cliToolAggregateItemId(aggregateKind), {
|
|
286298
|
+
label: "tool",
|
|
286299
|
+
text: renderCliToolAggregateText(aggregateKind, meta2),
|
|
286300
|
+
tone: "muted",
|
|
286301
|
+
detailLines: meta2.detailLines,
|
|
286302
|
+
expanded: false
|
|
286303
|
+
});
|
|
286304
|
+
setWorkingText(cliToolAggregateWorkingText(aggregateKind));
|
|
286305
|
+
break;
|
|
286306
|
+
}
|
|
285915
286307
|
const fileStart = buildFileToolDisplay(event.toolName, event.input ?? {}, "running");
|
|
285916
286308
|
if (fileStart) richToolIds.current.add(toolId);
|
|
285917
286309
|
updateToolItem(toolId, {
|
|
@@ -285927,6 +286319,22 @@ async function runInkInteractivePerchCli(writer, deps, options) {
|
|
|
285927
286319
|
case "tool_call_completed": {
|
|
285928
286320
|
const toolId = event.toolCallId ?? `${event.toolName}-${Date.now()}`;
|
|
285929
286321
|
const stored = toolInputsById.current.get(toolId);
|
|
286322
|
+
const aggregateKind = aggregateToolIds.get(toolId);
|
|
286323
|
+
if (aggregateKind) {
|
|
286324
|
+
const meta2 = getCliToolAggregateMeta(aggregateToolMeta, aggregateKind);
|
|
286325
|
+
meta2.pending = Math.max(0, meta2.pending - 1);
|
|
286326
|
+
meta2.done += 1;
|
|
286327
|
+
appendCliToolAggregateDetail(meta2, event.toolName, stored?.input ?? {}, "done");
|
|
286328
|
+
updateToolItem(cliToolAggregateItemId(aggregateKind), {
|
|
286329
|
+
label: "tool",
|
|
286330
|
+
text: renderCliToolAggregateText(aggregateKind, meta2),
|
|
286331
|
+
tone: meta2.failed > 0 ? "danger" : "success",
|
|
286332
|
+
detailLines: meta2.detailLines,
|
|
286333
|
+
expanded: false
|
|
286334
|
+
});
|
|
286335
|
+
setWorkingText("thinking");
|
|
286336
|
+
break;
|
|
286337
|
+
}
|
|
285930
286338
|
const fileDone = buildFileToolDisplay(event.toolName, stored?.input ?? {}, "done");
|
|
285931
286339
|
if (fileDone) {
|
|
285932
286340
|
richToolIds.current.add(toolId);
|
|
@@ -285956,6 +286364,22 @@ async function runInkInteractivePerchCli(writer, deps, options) {
|
|
|
285956
286364
|
case "tool_call_failed": {
|
|
285957
286365
|
const toolId = event.toolCallId ?? `${event.toolName}-${Date.now()}`;
|
|
285958
286366
|
const name = toolNamesById.get(toolId) ?? humanizeCliToolName(event.toolName);
|
|
286367
|
+
const stored = toolInputsById.current.get(toolId);
|
|
286368
|
+
const aggregateKind = aggregateToolIds.get(toolId);
|
|
286369
|
+
if (aggregateKind) {
|
|
286370
|
+
const meta2 = getCliToolAggregateMeta(aggregateToolMeta, aggregateKind);
|
|
286371
|
+
meta2.pending = Math.max(0, meta2.pending - 1);
|
|
286372
|
+
meta2.failed += 1;
|
|
286373
|
+
appendCliToolAggregateDetail(meta2, event.toolName, stored?.input ?? {}, "failed", event.error);
|
|
286374
|
+
updateToolItem(cliToolAggregateItemId(aggregateKind), {
|
|
286375
|
+
label: "tool",
|
|
286376
|
+
text: renderCliToolAggregateText(aggregateKind, meta2),
|
|
286377
|
+
tone: "danger",
|
|
286378
|
+
detailLines: meta2.detailLines,
|
|
286379
|
+
expanded: false
|
|
286380
|
+
});
|
|
286381
|
+
break;
|
|
286382
|
+
}
|
|
285959
286383
|
updateToolItem(toolId, {
|
|
285960
286384
|
label: "need",
|
|
285961
286385
|
text: `${name} \xB7 ${event.error ?? "needs attention"}`,
|
|
@@ -286202,6 +286626,7 @@ async function runInkInteractivePerchCli(writer, deps, options) {
|
|
|
286202
286626
|
appendRecentMessage(state.recentMessages, "user", prompt);
|
|
286203
286627
|
if (assistantText) appendRecentMessage(state.recentMessages, "assistant", assistantText);
|
|
286204
286628
|
trimRecentMessages(state.recentMessages);
|
|
286629
|
+
recordCliUsageRunId(state, result2.runId);
|
|
286205
286630
|
state.contextSnapshot = result2.contextSnapshot ?? state.contextSnapshot;
|
|
286206
286631
|
const admitted = await admitCliLearningMemory(connection, {
|
|
286207
286632
|
prompt,
|
|
@@ -286515,7 +286940,10 @@ async function runInteractiveSlashCommand(input) {
|
|
|
286515
286940
|
`);
|
|
286516
286941
|
return "continue";
|
|
286517
286942
|
case "context":
|
|
286518
|
-
input.writer.stdout(renderCliContextDetails(
|
|
286943
|
+
input.writer.stdout(renderCliContextDetails(
|
|
286944
|
+
input.state.contextSnapshot,
|
|
286945
|
+
resolveCliActiveContextModel(input.getConnection?.() ?? null)
|
|
286946
|
+
));
|
|
286519
286947
|
return "continue";
|
|
286520
286948
|
case "memoryAudit": {
|
|
286521
286949
|
const audit = explainLearningAdmission({
|
|
@@ -286590,6 +287018,7 @@ async function runInteractiveSlashCommand(input) {
|
|
|
286590
287018
|
}
|
|
286591
287019
|
input.state.threadId = parsed.value === "new" ? `cli-${Date.now()}` : parsed.value;
|
|
286592
287020
|
input.state.recentMessages = [];
|
|
287021
|
+
input.state.usageRunIds = [];
|
|
286593
287022
|
input.state.contextSnapshot = null;
|
|
286594
287023
|
input.state.persistedThreadUpdatedAt = null;
|
|
286595
287024
|
await hydrateInteractiveCliState(input.state);
|
|
@@ -286597,6 +287026,7 @@ async function runInteractiveSlashCommand(input) {
|
|
|
286597
287026
|
return "continue";
|
|
286598
287027
|
case "clear":
|
|
286599
287028
|
input.state.recentMessages = [];
|
|
287029
|
+
input.state.usageRunIds = [];
|
|
286600
287030
|
input.state.contextSnapshot = null;
|
|
286601
287031
|
input.state.persistedThreadUpdatedAt = null;
|
|
286602
287032
|
clearThreadSession(input.state.threadId);
|
|
@@ -286685,6 +287115,7 @@ function renderInteractiveStatus(state, connection, session, workspaceId) {
|
|
|
286685
287115
|
const signedIn = session === void 0 ? isCliModelConnectionReady(connection) : isStoredCliAuthSessionUsable(session);
|
|
286686
287116
|
const connectionStatus = isCliModelConnectionReady(connection) ? "connected" : "locked \xB7 run /login";
|
|
286687
287117
|
const color = shouldUseCliColor();
|
|
287118
|
+
const activeContextModel = resolveCliActiveContextModel(connection);
|
|
286688
287119
|
const lines = [
|
|
286689
287120
|
["version", CLI_PACKAGE_VERSION],
|
|
286690
287121
|
["cwd", state.cwd],
|
|
@@ -286697,7 +287128,7 @@ function renderInteractiveStatus(state, connection, session, workspaceId) {
|
|
|
286697
287128
|
["mode", state.chatMode],
|
|
286698
287129
|
["persona", state.personaId],
|
|
286699
287130
|
["thread", state.threadId],
|
|
286700
|
-
["context", renderCliContextMeter(state.contextSnapshot)],
|
|
287131
|
+
["context", renderCliContextMeter(state.contextSnapshot, activeContextModel)],
|
|
286701
287132
|
["saved", state.persistedThreadUpdatedAt ? new Date(state.persistedThreadUpdatedAt).toLocaleString() : "(new thread)"],
|
|
286702
287133
|
["local-tools", state.cliLocalTools ? "on" : "off"]
|
|
286703
287134
|
];
|
|
@@ -286846,6 +287277,7 @@ function createInteractiveCliState(options) {
|
|
|
286846
287277
|
cliLocalTools: options.cliLocalTools ?? true,
|
|
286847
287278
|
appUrl: resolveCliAppUrl(options.appUrl ?? null, null),
|
|
286848
287279
|
recentMessages: [],
|
|
287280
|
+
usageRunIds: [],
|
|
286849
287281
|
contextSnapshot: null,
|
|
286850
287282
|
persistedThreadUpdatedAt: null
|
|
286851
287283
|
};
|
|
@@ -286862,6 +287294,7 @@ async function syncInteractiveCliThreadScope(state, connection, options = {}) {
|
|
|
286862
287294
|
if (!options.force && state.threadScopeKey === nextScopeKey) return;
|
|
286863
287295
|
state.threadScopeKey = nextScopeKey;
|
|
286864
287296
|
state.recentMessages = [];
|
|
287297
|
+
state.usageRunIds = [];
|
|
286865
287298
|
state.contextSnapshot = null;
|
|
286866
287299
|
state.persistedThreadUpdatedAt = null;
|
|
286867
287300
|
clearThreadSession(state.threadId);
|
|
@@ -287045,21 +287478,21 @@ function isPermanentMemoryLike2(value) {
|
|
|
287045
287478
|
const memory = value;
|
|
287046
287479
|
return typeof memory.id === "string" && typeof memory.title === "string" && typeof memory.body === "string";
|
|
287047
287480
|
}
|
|
287048
|
-
function renderCliContextMeter(snapshot) {
|
|
287481
|
+
function renderCliContextMeter(snapshot, activeModel) {
|
|
287049
287482
|
if (!snapshot) return "Context";
|
|
287050
|
-
const meter = resolveContextMeterDisplay(snapshot);
|
|
287483
|
+
const meter = resolveContextMeterDisplay(snapshot, activeModel);
|
|
287051
287484
|
const percent2 = snapshot.contextPercentage ?? Math.round(meter.committedTokens / Math.max(1, meter.limitTokens) * 100);
|
|
287052
287485
|
const parts = [`${meter.composerLabel}`, `${Math.max(0, Math.min(100, percent2))}%`];
|
|
287053
287486
|
const compacted = snapshot.compactedRowCount ?? 0;
|
|
287054
287487
|
if (snapshot.compacted || compacted > 0) parts.push(`compacted ${compacted}`);
|
|
287055
287488
|
return parts.join(" \xB7 ");
|
|
287056
287489
|
}
|
|
287057
|
-
function renderCliContextDetails(snapshot) {
|
|
287490
|
+
function renderCliContextDetails(snapshot, activeModel) {
|
|
287058
287491
|
if (!snapshot) return "context no meter yet\n";
|
|
287059
|
-
const meter = resolveContextMeterDisplay(snapshot);
|
|
287492
|
+
const meter = resolveContextMeterDisplay(snapshot, activeModel);
|
|
287060
287493
|
const percent2 = snapshot.contextPercentage ?? Math.round(meter.committedTokens / Math.max(1, meter.limitTokens) * 100);
|
|
287061
287494
|
const lines = [
|
|
287062
|
-
["context", renderCliContextMeter(snapshot)],
|
|
287495
|
+
["context", renderCliContextMeter(snapshot, activeModel)],
|
|
287063
287496
|
["thread", `${formatCliTokenCount(meter.threadTokens)} tokens`],
|
|
287064
287497
|
["limit", `${formatCliTokenCount(meter.limitTokens)} tokens`],
|
|
287065
287498
|
["fill", `${Math.max(0, Math.min(100, percent2))}%`],
|
|
@@ -287079,6 +287512,10 @@ function humanizeCliToolName(name) {
|
|
|
287079
287512
|
function isCliModelConnectionReady(connection) {
|
|
287080
287513
|
return Boolean(connection?.authenticated && connection.founderModelSelection);
|
|
287081
287514
|
}
|
|
287515
|
+
function resolveCliActiveContextModel(connection) {
|
|
287516
|
+
if (!connection?.founderModelSelection) return null;
|
|
287517
|
+
return resolveModelForLane(connection.founderModelSelection, "chat");
|
|
287518
|
+
}
|
|
287082
287519
|
function renderCliAuthSummary(connection) {
|
|
287083
287520
|
if (isCliModelConnectionReady(connection)) {
|
|
287084
287521
|
return `signed in${connection.email ? ` as ${connection.email}` : ""}`;
|
|
@@ -287415,6 +287852,67 @@ function buildFileToolDisplay(toolName, input, phase, summary) {
|
|
|
287415
287852
|
]
|
|
287416
287853
|
};
|
|
287417
287854
|
}
|
|
287855
|
+
function cliToolAggregateKind(toolName) {
|
|
287856
|
+
const normalized = toolName.toLowerCase();
|
|
287857
|
+
if (normalized === "readlocalfile" || normalized === "readlocalsourcefile") return "read";
|
|
287858
|
+
if (normalized === "glob" || normalized === "grep" || normalized === "listlocalsources" || normalized === "statpath" || normalized === "validateworkspaceroot") return "search";
|
|
287859
|
+
return null;
|
|
287860
|
+
}
|
|
287861
|
+
function getCliToolAggregateMeta(store, kind) {
|
|
287862
|
+
const existing = store.get(kind);
|
|
287863
|
+
if (existing) return existing;
|
|
287864
|
+
const created = {
|
|
287865
|
+
started: 0,
|
|
287866
|
+
done: 0,
|
|
287867
|
+
failed: 0,
|
|
287868
|
+
pending: 0,
|
|
287869
|
+
detailLines: []
|
|
287870
|
+
};
|
|
287871
|
+
store.set(kind, created);
|
|
287872
|
+
return created;
|
|
287873
|
+
}
|
|
287874
|
+
function cliToolAggregateItemId(kind) {
|
|
287875
|
+
return `tool-aggregate-${kind}`;
|
|
287876
|
+
}
|
|
287877
|
+
function cliToolAggregateWorkingText(kind) {
|
|
287878
|
+
return kind === "read" ? "reading files" : "searching";
|
|
287879
|
+
}
|
|
287880
|
+
function renderCliToolAggregateText(kind, meta) {
|
|
287881
|
+
const completed = meta.done + meta.failed;
|
|
287882
|
+
const count = Math.max(completed, meta.started);
|
|
287883
|
+
const noun = kind === "read" ? `file${count === 1 ? "" : "s"}` : `path${count === 1 ? "" : "s"}`;
|
|
287884
|
+
const verb = kind === "read" ? "read" : "searched";
|
|
287885
|
+
const status = meta.pending > 0 ? `${meta.pending} running` : "done";
|
|
287886
|
+
const failed = meta.failed > 0 ? ` \xB7 ${meta.failed} failed` : "";
|
|
287887
|
+
return `${verb} ${count} ${noun} \xB7 ${status}${failed} \xB7 ctrl-e`;
|
|
287888
|
+
}
|
|
287889
|
+
function appendCliToolAggregateDetail(meta, toolName, input, status, error) {
|
|
287890
|
+
const label = humanizeCliToolName(toolName);
|
|
287891
|
+
const target = describeCliToolAggregateInput(toolName, input);
|
|
287892
|
+
const tail = error ? ` \xB7 ${error}` : "";
|
|
287893
|
+
meta.detailLines.push({
|
|
287894
|
+
tone: "meta",
|
|
287895
|
+
text: `${label}${target ? ` \xB7 ${target}` : ""} \xB7 ${status}${tail}`
|
|
287896
|
+
});
|
|
287897
|
+
if (meta.detailLines.length > INK_DETAIL_LINE_LIMIT) {
|
|
287898
|
+
meta.detailLines = meta.detailLines.slice(-INK_DETAIL_LINE_LIMIT);
|
|
287899
|
+
}
|
|
287900
|
+
}
|
|
287901
|
+
function describeCliToolAggregateInput(toolName, input) {
|
|
287902
|
+
const normalized = toolName.toLowerCase();
|
|
287903
|
+
if (normalized === "glob") {
|
|
287904
|
+
const pattern = stringValue8(input.pattern) ?? stringValue8(input.glob) ?? "*";
|
|
287905
|
+
const base = stringValue8(input.path) ?? stringValue8(input.cwd) ?? ".";
|
|
287906
|
+
return `${pattern} in ${shortFilePath(base)}`;
|
|
287907
|
+
}
|
|
287908
|
+
if (normalized === "grep") {
|
|
287909
|
+
const pattern = stringValue8(input.pattern) ?? stringValue8(input.query) ?? "";
|
|
287910
|
+
const base = stringValue8(input.path) ?? stringValue8(input.cwd) ?? ".";
|
|
287911
|
+
return `${pattern || "pattern"} in ${shortFilePath(base)}`;
|
|
287912
|
+
}
|
|
287913
|
+
const filePath = stringValue8(input.path) ?? stringValue8(input.filePath) ?? stringValue8(input.localSourceId) ?? stringValue8(input.cwd);
|
|
287914
|
+
return filePath ? shortFilePath(filePath) : null;
|
|
287915
|
+
}
|
|
287418
287916
|
function describeChangeSummary(summary) {
|
|
287419
287917
|
const kind = summary.changeKind ?? "changed";
|
|
287420
287918
|
const added = summary.linesAdded ?? 0;
|
|
@@ -287535,6 +288033,11 @@ function trimRecentMessages(messages) {
|
|
|
287535
288033
|
messages.splice(0, messages.length - 30);
|
|
287536
288034
|
}
|
|
287537
288035
|
}
|
|
288036
|
+
function recordCliUsageRunId(state, runId) {
|
|
288037
|
+
const cleaned = runId?.trim();
|
|
288038
|
+
if (!cleaned) return;
|
|
288039
|
+
state.usageRunIds = [.../* @__PURE__ */ new Set([...state.usageRunIds, cleaned])].slice(-250);
|
|
288040
|
+
}
|
|
287538
288041
|
function createCliRunId() {
|
|
287539
288042
|
return `cli-turn-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
287540
288043
|
}
|
|
@@ -287657,7 +288160,7 @@ function writeCliTurnResult(result2, json, writer, options = {}) {
|
|
|
287657
288160
|
color: options.color,
|
|
287658
288161
|
streamLabels: true
|
|
287659
288162
|
});
|
|
287660
|
-
const contextLine = `${paint("context".padEnd(12), "muted", options.color === true)} ${renderCliContextMeter(result2.contextSnapshot)}
|
|
288163
|
+
const contextLine = `${paint("context".padEnd(12), "muted", options.color === true)} ${renderCliContextMeter(result2.contextSnapshot, options.activeContextModel)}
|
|
287661
288164
|
`;
|
|
287662
288165
|
writer.stdout(`${rendered}${rendered.endsWith("\n") ? "" : "\n"}${contextLine}`);
|
|
287663
288166
|
}
|
|
@@ -287813,6 +288316,7 @@ var init_perch_cli = __esm({
|
|
|
287813
288316
|
init_cliAuthSession();
|
|
287814
288317
|
init_cliStandaloneOAuth();
|
|
287815
288318
|
init_contextMeterDisplay();
|
|
288319
|
+
init_modelRegistry();
|
|
287816
288320
|
init_threadSession();
|
|
287817
288321
|
init_runRegistry();
|
|
287818
288322
|
init_learningMemory();
|
|
@@ -287875,6 +288379,7 @@ Commands:
|
|
|
287875
288379
|
/help Show this list.
|
|
287876
288380
|
/status Show cwd, auth, mode, persona, permission, and thread.
|
|
287877
288381
|
/skills [name] List Perch core skills, or show one skill body.
|
|
288382
|
+
/usage Show tokens and estimated cost for this session.
|
|
287878
288383
|
/cwd [dir] Show or change the working directory.
|
|
287879
288384
|
/permission [mode] Show or set default, auto_review, take_the_wheel, or plan.
|
|
287880
288385
|
/permissions [mode] Alias for /permission.
|
|
@@ -287934,6 +288439,7 @@ Commands:
|
|
|
287934
288439
|
];
|
|
287935
288440
|
PERCH_SPLASH_COMMANDS = [
|
|
287936
288441
|
["/status", "show auth, route, tools, and thread"],
|
|
288442
|
+
["/usage", "show session tokens and estimated cost"],
|
|
287937
288443
|
["/skills", "show reusable operator skills"],
|
|
287938
288444
|
["/persona", "swap saffron or quill"],
|
|
287939
288445
|
["/permission", "change autonomy for the next turns"],
|