@pi-unipi/unipi 2.17.2 → 2.18.0
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/CHANGELOG.md +9 -0
- package/package.json +24 -24
- package/packages/ask-user/package.json +2 -2
- package/packages/autocomplete/package.json +1 -1
- package/packages/background-tasks/package.json +2 -2
- package/packages/btw/package.json +2 -2
- package/packages/compactor/package.json +3 -3
- package/packages/core/fusion-status.ts +6 -0
- package/packages/core/package.json +1 -1
- package/packages/footer/package.json +3 -3
- package/packages/footer/src/glance-editor.ts +22 -8
- package/packages/fusion/package.json +3 -3
- package/packages/fusion/src/index.ts +43 -6
- package/packages/fusion/src/nudge.ts +7 -0
- package/packages/fusion/src/prompts.ts +19 -3
- package/packages/fusion/src/sidekick-runtime.ts +83 -7
- package/packages/fusion/src/tools.ts +67 -14
- package/packages/fusion/src/transcript.ts +108 -0
- package/packages/image/package.json +2 -2
- package/packages/info-screen/package.json +2 -2
- package/packages/input-shortcuts/package.json +2 -2
- package/packages/kanboard/package.json +2 -2
- package/packages/mcp/package.json +2 -2
- package/packages/memory/package.json +3 -3
- package/packages/milestone/package.json +2 -2
- package/packages/notify/package.json +2 -2
- package/packages/ralph/package.json +3 -3
- package/packages/subagents/package.json +2 -2
- package/packages/trajectory/package.json +1 -1
- package/packages/unipi/bundled.js +283 -45
- package/packages/updater/package.json +2 -2
- package/packages/utility/package.json +2 -2
- package/packages/web-api/package.json +2 -2
- package/packages/workflow/package.json +2 -2
|
@@ -26381,8 +26381,8 @@ function buildOverlayTranscript(entries, theme) {
|
|
|
26381
26381
|
if (options.blankBefore !== false) {
|
|
26382
26382
|
pushBlankLine();
|
|
26383
26383
|
}
|
|
26384
|
-
const
|
|
26385
|
-
lines.push(`${header}${
|
|
26384
|
+
const firstLine4 = bodyLines.shift() ?? "";
|
|
26385
|
+
lines.push(`${header}${firstLine4 ? ` ${style(firstLine4)}` : ""}`);
|
|
26386
26386
|
for (const line of bodyLines) {
|
|
26387
26387
|
lines.push(`${blockIndent}${style(line)}`);
|
|
26388
26388
|
}
|
|
@@ -33186,34 +33186,34 @@ function registerAskUserTools(pi) {
|
|
|
33186
33186
|
};
|
|
33187
33187
|
}
|
|
33188
33188
|
const response = result2.response;
|
|
33189
|
-
let
|
|
33189
|
+
let contentText2;
|
|
33190
33190
|
switch (response.kind) {
|
|
33191
33191
|
case "selection": {
|
|
33192
33192
|
const selections = response.selections || [];
|
|
33193
|
-
|
|
33193
|
+
contentText2 = selections.length === 1 ? `User selected: ${selections[0]}` : `User selected: ${selections.join(", ")}`;
|
|
33194
33194
|
break;
|
|
33195
33195
|
}
|
|
33196
33196
|
case "freeform":
|
|
33197
|
-
|
|
33197
|
+
contentText2 = `User wrote: ${response.text}`;
|
|
33198
33198
|
break;
|
|
33199
33199
|
case "combined": {
|
|
33200
33200
|
const selections = response.selections || [];
|
|
33201
33201
|
const selText = selections.length === 1 ? selections[0] : selections.join(", ");
|
|
33202
|
-
|
|
33202
|
+
contentText2 = `User selected: ${selText} and wrote: ${response.text}`;
|
|
33203
33203
|
break;
|
|
33204
33204
|
}
|
|
33205
33205
|
case "end_turn":
|
|
33206
33206
|
ctx.abort();
|
|
33207
|
-
|
|
33207
|
+
contentText2 = "User chose to end the turn.";
|
|
33208
33208
|
break;
|
|
33209
33209
|
case "new_session":
|
|
33210
|
-
|
|
33210
|
+
contentText2 = response.prefill ? `User chose to hand off to: ${response.prefill}` : "User chose a handoff without a prefill.";
|
|
33211
33211
|
break;
|
|
33212
33212
|
case "timed_out":
|
|
33213
|
-
|
|
33213
|
+
contentText2 = "User did not respond (timed out)";
|
|
33214
33214
|
break;
|
|
33215
33215
|
default:
|
|
33216
|
-
|
|
33216
|
+
contentText2 = "No response";
|
|
33217
33217
|
}
|
|
33218
33218
|
if (response.kind === "new_session") {
|
|
33219
33219
|
const prefill = response.prefill || "";
|
|
@@ -33265,14 +33265,14 @@ Preparing for new task. Summarize previous work concisely, preserving only what'
|
|
|
33265
33265
|
}
|
|
33266
33266
|
const launchLabel = launcherResult.action === "compact" ? "compact" : "direct";
|
|
33267
33267
|
if (handoff.status === "editor_prefill") {
|
|
33268
|
-
|
|
33268
|
+
contentText2 = `Queued ${launchLabel} handoff fell back to editor prefill: ${handoff.prefill}`;
|
|
33269
33269
|
} else if (handoff.status === "failed") {
|
|
33270
|
-
|
|
33270
|
+
contentText2 = `Failed to queue ${launchLabel} handoff: ${handoff.prefill ?? prefill}`;
|
|
33271
33271
|
} else {
|
|
33272
|
-
|
|
33272
|
+
contentText2 = `Queued ${launchLabel} handoff: ${handoff.prefill ?? prefill}`;
|
|
33273
33273
|
}
|
|
33274
33274
|
return {
|
|
33275
|
-
content: [{ type: "text", text:
|
|
33275
|
+
content: [{ type: "text", text: contentText2 }],
|
|
33276
33276
|
details: {
|
|
33277
33277
|
...detailsBase,
|
|
33278
33278
|
response: {
|
|
@@ -33287,7 +33287,7 @@ Preparing for new task. Summarize previous work concisely, preserving only what'
|
|
|
33287
33287
|
};
|
|
33288
33288
|
}
|
|
33289
33289
|
return {
|
|
33290
|
-
content: [{ type: "text", text:
|
|
33290
|
+
content: [{ type: "text", text: contentText2 }],
|
|
33291
33291
|
details: {
|
|
33292
33292
|
...detailsBase,
|
|
33293
33293
|
response
|
|
@@ -44693,6 +44693,19 @@ var SEP = " \u2502 ";
|
|
|
44693
44693
|
var RESET2 = "\x1B[0m";
|
|
44694
44694
|
var LEAD_FG = "\x1B[1m\x1B[38;2;181;189;104m";
|
|
44695
44695
|
var DIM_FG = "\x1B[38;2;120;124;134m";
|
|
44696
|
+
function renderFusionStatus(fusion) {
|
|
44697
|
+
const lead = `${fusion.leadName}${fusion.leadEffort ? ` ${fusion.leadEffort}` : ""}`;
|
|
44698
|
+
const side = `${fusion.sidekickName}${fusion.sidekickEffort ? ` ${fusion.sidekickEffort}` : ""}`;
|
|
44699
|
+
const busy = fusion.busy === true;
|
|
44700
|
+
const leadCalls = fusion.leadToolCalls ?? 0;
|
|
44701
|
+
const sidekickCalls = fusion.sidekickToolCalls ?? 0;
|
|
44702
|
+
const totalCalls = leadCalls + sidekickCalls;
|
|
44703
|
+
const saved = fusion.savedUsd !== void 0 && fusion.savedUsd > 5e-3 ? ` \xB7 saved $${fusion.savedUsd.toFixed(2)}` : "";
|
|
44704
|
+
const split = totalCalls > 0 ? ` \xB7 ${String(sidekickCalls)}/${String(totalCalls)} calls` : "";
|
|
44705
|
+
const core = busy ? `${DIM_FG}Fusion \xB7 ${lead}${RESET2} ${LEAD_FG}\u25C6 ${side} \xB7 working${RESET2}` : `${LEAD_FG}Fusion \xB7 ${lead}${RESET2} ${DIM_FG}\u25C6 ${side}${RESET2}`;
|
|
44706
|
+
const tail = `${split}${saved}`;
|
|
44707
|
+
return tail ? `${core}${DIM_FG}${tail}${RESET2}` : core;
|
|
44708
|
+
}
|
|
44696
44709
|
var ANSI_RE = /\x1B(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1b\\)|_[^\x07]*(?:\x07|\x1b\\))/g;
|
|
44697
44710
|
function stripControls(text) {
|
|
44698
44711
|
return text.replace(ANSI_RE, "").replace(/[\r\n\t]/g, " ");
|
|
@@ -44789,11 +44802,7 @@ var GlanceEditor = class _GlanceEditor extends CustomEditor {
|
|
|
44789
44802
|
const winLabel = st.contextWindow > 0 ? `/${fmtTokens(st.contextWindow)}` : "";
|
|
44790
44803
|
rightParts.push(`${pctLabel}${winLabel}`);
|
|
44791
44804
|
if (st.fusion) {
|
|
44792
|
-
|
|
44793
|
-
const side = `${st.fusion.sidekickName}${st.fusion.sidekickEffort ? ` ${st.fusion.sidekickEffort}` : ""}`;
|
|
44794
|
-
rightParts.push(
|
|
44795
|
-
`${LEAD_FG}Fusion \xB7 ${lead}${RESET2} ${DIM_FG}\u25C6 ${side}${st.fusion.savedUsd !== void 0 && st.fusion.savedUsd > 5e-3 ? ` \xB7 saved $${st.fusion.savedUsd.toFixed(2)}` : ""}${RESET2}`
|
|
44796
|
-
);
|
|
44805
|
+
rightParts.push(renderFusionStatus(st.fusion));
|
|
44797
44806
|
} else {
|
|
44798
44807
|
if (st.modelName) rightParts.push(st.modelName);
|
|
44799
44808
|
if (st.thinkingLevel && st.thinkingLevel !== "off") {
|
|
@@ -51095,6 +51104,8 @@ import { mkdirSync as mkdirSync44, unlinkSync as unlinkSync12, writeFileSync as
|
|
|
51095
51104
|
import { tmpdir as tmpdir7 } from "node:os";
|
|
51096
51105
|
import { dirname as dirname49, join as join75 } from "node:path";
|
|
51097
51106
|
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
51107
|
+
var MAX_EVENTS = 300;
|
|
51108
|
+
var MAX_TOOL_OUTPUT = 4e3;
|
|
51098
51109
|
var emptyUsage = () => ({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 });
|
|
51099
51110
|
var SidekickRuntime = class {
|
|
51100
51111
|
cfg;
|
|
@@ -51119,6 +51130,37 @@ var SidekickRuntime = class {
|
|
|
51119
51130
|
isBusy() {
|
|
51120
51131
|
return this.pending !== void 0;
|
|
51121
51132
|
}
|
|
51133
|
+
totalToolCalls() {
|
|
51134
|
+
let total = this.pending?.progress.toolCalls ?? 0;
|
|
51135
|
+
for (const report of this.reports.values()) total += report.toolCalls;
|
|
51136
|
+
return total;
|
|
51137
|
+
}
|
|
51138
|
+
notifyProgress() {
|
|
51139
|
+
try {
|
|
51140
|
+
this.cfg.onProgress?.();
|
|
51141
|
+
} catch {
|
|
51142
|
+
}
|
|
51143
|
+
}
|
|
51144
|
+
appendEvent(event) {
|
|
51145
|
+
const progress = this.pending?.progress;
|
|
51146
|
+
if (!progress) return;
|
|
51147
|
+
progress.events.push(event);
|
|
51148
|
+
if (progress.events.length > MAX_EVENTS) {
|
|
51149
|
+
progress.events.shift();
|
|
51150
|
+
progress.droppedEvents += 1;
|
|
51151
|
+
}
|
|
51152
|
+
}
|
|
51153
|
+
closeOpenText() {
|
|
51154
|
+
const events = this.pending?.progress.events;
|
|
51155
|
+
const last = events?.at(-1);
|
|
51156
|
+
if (last?.kind === "text" && last.open) last.open = false;
|
|
51157
|
+
}
|
|
51158
|
+
toolOutput(result2) {
|
|
51159
|
+
if (typeof result2 === "object" && result2 !== null && Array.isArray(result2.content)) {
|
|
51160
|
+
return result2.content.map((part) => typeof part === "object" && part !== null && typeof part.text === "string" ? part.text : typeof part === "string" ? part : "").filter(Boolean).join("\n").slice(-MAX_TOOL_OUTPUT);
|
|
51161
|
+
}
|
|
51162
|
+
return String(result2 ?? "").slice(-MAX_TOOL_OUTPUT);
|
|
51163
|
+
}
|
|
51122
51164
|
send(value) {
|
|
51123
51165
|
if (!this.child?.stdin?.writable) throw new Error("Sidekick process is not writable");
|
|
51124
51166
|
this.child.stdin.write(`${JSON.stringify(value)}
|
|
@@ -51210,19 +51252,39 @@ var SidekickRuntime = class {
|
|
|
51210
51252
|
}
|
|
51211
51253
|
if (this.pending === void 0) return;
|
|
51212
51254
|
if (message.type === "tool_execution_start") {
|
|
51255
|
+
this.closeOpenText();
|
|
51213
51256
|
this.pending.progress.toolCalls += 1;
|
|
51214
|
-
const args = message.args
|
|
51215
|
-
const
|
|
51257
|
+
const args = message.args !== void 0 && typeof message.args === "object" && message.args !== null ? message.args : void 0;
|
|
51258
|
+
const argsText = args === void 0 ? "" : JSON.stringify(args).replace(/\s+/gu, " ");
|
|
51259
|
+
const summary = `${String(message.toolName ?? "tool")}(${argsText})`.slice(0, 40);
|
|
51216
51260
|
this.pending.progress.recentTools = [...this.pending.progress.recentTools, summary].slice(-6);
|
|
51261
|
+
this.appendEvent({ kind: "tool", toolCallId: String(message.toolCallId ?? ""), name: String(message.toolName ?? "tool"), args, output: "", isError: false, done: false, startedAt: Date.now() });
|
|
51262
|
+
this.notifyProgress();
|
|
51263
|
+
} else if (message.type === "tool_execution_end") {
|
|
51264
|
+
const toolCallId = String(message.toolCallId ?? "");
|
|
51265
|
+
const event = [...this.pending.progress.events].reverse().find((entry) => entry.kind === "tool" && entry.toolCallId === toolCallId);
|
|
51266
|
+
if (event) {
|
|
51267
|
+
event.done = true;
|
|
51268
|
+
event.endedAt = Date.now();
|
|
51269
|
+
event.isError = message.isError === true;
|
|
51270
|
+
event.output = this.toolOutput(message.result);
|
|
51271
|
+
}
|
|
51272
|
+
this.notifyProgress();
|
|
51217
51273
|
} else if (message.type === "message_update") {
|
|
51218
|
-
const
|
|
51219
|
-
if (
|
|
51220
|
-
const delta = typeof
|
|
51274
|
+
const streamEvent = message.assistantMessageEvent ?? message;
|
|
51275
|
+
if (streamEvent.type === "text_delta") {
|
|
51276
|
+
const delta = typeof streamEvent.delta === "string" ? streamEvent.delta : typeof streamEvent.text === "string" ? streamEvent.text : "";
|
|
51221
51277
|
this.pending.progress.textTail = `${this.pending.progress.textTail}${delta}`.slice(-400);
|
|
51278
|
+
const last = this.pending.progress.events.at(-1);
|
|
51279
|
+
if (last?.kind === "text" && last.open) last.text += delta;
|
|
51280
|
+
else this.appendEvent({ kind: "text", text: delta, open: true });
|
|
51281
|
+
this.notifyProgress();
|
|
51222
51282
|
}
|
|
51223
51283
|
} else if (message.type === "message_end") {
|
|
51224
51284
|
const msg = message.message;
|
|
51225
51285
|
if (msg?.role === "assistant") {
|
|
51286
|
+
this.closeOpenText();
|
|
51287
|
+
this.notifyProgress();
|
|
51226
51288
|
if (msg.stopReason === "error" && typeof msg.errorMessage === "string") this.pendingError = msg.errorMessage;
|
|
51227
51289
|
const usage2 = msg.usage;
|
|
51228
51290
|
if (usage2) {
|
|
@@ -51270,11 +51332,13 @@ var SidekickRuntime = class {
|
|
|
51270
51332
|
usage: { ...current.usage },
|
|
51271
51333
|
toolCalls: current.progress.toolCalls,
|
|
51272
51334
|
durationMs: Date.now() - current.startedAt,
|
|
51335
|
+
events: current.progress.events.map((event) => ({ ...event })),
|
|
51273
51336
|
...error === void 0 ? {} : { error }
|
|
51274
51337
|
};
|
|
51275
51338
|
this.reports.set(report.id, report);
|
|
51276
51339
|
if (this.latestHandoff?.id === report.id) this.latestHandoff.report = report;
|
|
51277
51340
|
current.resolve(report);
|
|
51341
|
+
this.notifyProgress();
|
|
51278
51342
|
this.abortRequested = false;
|
|
51279
51343
|
this.pendingError = void 0;
|
|
51280
51344
|
}
|
|
@@ -51297,7 +51361,7 @@ var SidekickRuntime = class {
|
|
|
51297
51361
|
id,
|
|
51298
51362
|
startedAt,
|
|
51299
51363
|
usage: emptyUsage(),
|
|
51300
|
-
progress: { toolCalls: 0, recentTools: [], textTail: "", startedAt },
|
|
51364
|
+
progress: { toolCalls: 0, recentTools: [], textTail: "", startedAt, events: [], droppedEvents: 0 },
|
|
51301
51365
|
resolve: resolve22,
|
|
51302
51366
|
reject
|
|
51303
51367
|
};
|
|
@@ -51310,7 +51374,9 @@ var SidekickRuntime = class {
|
|
|
51310
51374
|
return { id, done };
|
|
51311
51375
|
}
|
|
51312
51376
|
progress(id) {
|
|
51313
|
-
if (this.pending !== void 0 && (id === void 0 || id === this.pending.id))
|
|
51377
|
+
if (this.pending !== void 0 && (id === void 0 || id === this.pending.id)) {
|
|
51378
|
+
return { ...this.pending.progress, recentTools: [...this.pending.progress.recentTools], events: this.pending.progress.events.map((event) => ({ ...event })) };
|
|
51379
|
+
}
|
|
51314
51380
|
return void 0;
|
|
51315
51381
|
}
|
|
51316
51382
|
latest() {
|
|
@@ -51387,7 +51453,10 @@ You have a \`sidekick\` tool: a persistent subagent that works alongside you on
|
|
|
51387
51453
|
- **Premature implementation briefing.** Working solo you would catch a wrong assumption as you go; when delegating, the sidekick executes what you wrote, and changing course afterwards is expensive. Hold your plan to a higher confidence bar than you would need to start yourself. Every claim your plan depends on must either be verified or explicitly marked as a hypothesis for the sidekick to verify; verification instructions must cover every surface you discovered.
|
|
51388
51454
|
- **Promoting a delegated hypothesis to a confirmed conclusion.** When a report ranks candidate causes, the ranking is not a verdict. Present a cause as the root cause only if evidence shows its code path actually executes in the reported scenario; otherwise present it as the leading hypothesis and name the check that would settle it.`;
|
|
51389
51455
|
}
|
|
51390
|
-
var
|
|
51456
|
+
var EDIT_NUDGE = "<system_guidance>You made a direct edit yourself instead of delegating to the sidekick. This is a reminder that implementation and verification are to be delegated by default. ONLY implement a step yourself if it is trivially small (you can make the edit AND confirm it in 1-2 of your own turns, with nothing left to test afterwards) or correctness-critical (queries against shared data systems, eval/grading text, pipeline or threshold configuration \u2014 you author and check those regardless of size). For anything else, write a brief and hand it to `sidekick`: you design and review, it implements and verifies.</system_guidance>";
|
|
51457
|
+
function bashNudge(count) {
|
|
51458
|
+
return `<system_guidance>You have run ${String(count)} non-trivial shell commands yourself since the last handoff. Builds, test runs, installs, environment setup or repair, and any multi-step shell work are to be delegated to the \`sidekick\` by default; it runs on the same machine and remembers earlier handoffs, so a short brief with the goal, the exact commands or checks you want, and the done-criteria is enough. Keep running commands yourself only when a single read-only command answers a question you need right now, or when the user is waiting on an urgent deliverable.</system_guidance>`;
|
|
51459
|
+
}
|
|
51391
51460
|
function sidekickSystemPrompt(id) {
|
|
51392
51461
|
return `## Role: Fusion sidekick
|
|
51393
51462
|
You are the sidekick half of a Fusion pair. A lead agent (${id.leadName}) plans, talks to the user, and reviews; you do the hands-on work it hands you. You run on the same machine and repository as the lead, with your own shell sessions. Your conversation persists across handoffs: remember what you did, what you learned, and which processes you left running.
|
|
@@ -51411,9 +51480,97 @@ How you report (your final message is what the lead reads \u2014 make it the who
|
|
|
51411
51480
|
Prefer compact, idiomatic code that follows the repository's existing conventions; do not add or remove comments unless asked; do not create documentation files unless asked.`;
|
|
51412
51481
|
}
|
|
51413
51482
|
|
|
51483
|
+
// packages/fusion/src/nudge.ts
|
|
51484
|
+
var BASH_NUDGE_EVERY = 4;
|
|
51485
|
+
var TRIVIAL = /^\s*(?:cd\s+\S+\s*(?:&&|;)\s*)?(?:git\s+(?:status|log|diff|branch|show|remote|rev-parse)|ls|pwd|cat|head|tail|wc|echo|which|type|rg|grep|find|stat|file|du|df|env|printenv|date|whoami|tmux\s+capture-pane|npm\s+(?:view|whoami|ls))\b[^|;&]*$/;
|
|
51486
|
+
function isTrivialShell(command) {
|
|
51487
|
+
return TRIVIAL.test(command.trim());
|
|
51488
|
+
}
|
|
51489
|
+
|
|
51414
51490
|
// packages/fusion/src/tools.ts
|
|
51415
|
-
import {
|
|
51491
|
+
import { Markdown as Markdown2, Text as Text9 } from "@earendil-works/pi-tui";
|
|
51492
|
+
import { getMarkdownTheme as getMarkdownTheme2 } from "@earendil-works/pi-coding-agent";
|
|
51416
51493
|
import { Type as Type11 } from "typebox";
|
|
51494
|
+
|
|
51495
|
+
// packages/fusion/src/transcript.ts
|
|
51496
|
+
import { Box as Box3, Container as Container3, Text as Text8 } from "@earendil-works/pi-tui";
|
|
51497
|
+
var RailComponent = class {
|
|
51498
|
+
constructor(inner, rail) {
|
|
51499
|
+
this.inner = inner;
|
|
51500
|
+
this.rail = rail;
|
|
51501
|
+
}
|
|
51502
|
+
inner;
|
|
51503
|
+
rail;
|
|
51504
|
+
render(width) {
|
|
51505
|
+
return this.inner.render(Math.max(1, width - 2)).map((line) => `${this.rail} ${line}`);
|
|
51506
|
+
}
|
|
51507
|
+
invalidate() {
|
|
51508
|
+
this.inner.invalidate?.();
|
|
51509
|
+
}
|
|
51510
|
+
handleInput(data) {
|
|
51511
|
+
this.inner.handleInput?.(data);
|
|
51512
|
+
}
|
|
51513
|
+
};
|
|
51514
|
+
function frameSidekick(theme, status, content) {
|
|
51515
|
+
const railColor = status === "working" ? "accent" : status === "completed" ? "success" : "error";
|
|
51516
|
+
const rail = theme.fg(railColor, "\u258D");
|
|
51517
|
+
const boxed = new Box3(1, 0, (text) => theme.bg("customMessageBg", text));
|
|
51518
|
+
boxed.addChild(new RailComponent(content, rail));
|
|
51519
|
+
return boxed;
|
|
51520
|
+
}
|
|
51521
|
+
function firstLine2(value) {
|
|
51522
|
+
return value.split("\n", 1)[0] ?? "";
|
|
51523
|
+
}
|
|
51524
|
+
function truncate2(value, max) {
|
|
51525
|
+
return value.length > max ? `${value.slice(0, Math.max(0, max - 1))}\u2026` : value;
|
|
51526
|
+
}
|
|
51527
|
+
function primaryArg(name, args) {
|
|
51528
|
+
if (!args) return "";
|
|
51529
|
+
const value = name === "bash" ? args.command : name === "read" || name === "edit" || name === "write" ? args.path ?? args.file_path ?? args.filePath : name === "sidekick" ? args.message : Object.values(args).find((entry) => typeof entry === "string");
|
|
51530
|
+
return typeof value === "string" ? truncate2(firstLine2(value), 100) : "";
|
|
51531
|
+
}
|
|
51532
|
+
function toolComponent(theme, event, expanded) {
|
|
51533
|
+
const title = `${event.isError ? theme.fg("error", "\u2717 ") : ""}${theme.fg("toolTitle", theme.bold(event.name))}`;
|
|
51534
|
+
const argument = primaryArg(event.name, event.args);
|
|
51535
|
+
const lines = [`${title}${argument.length > 0 ? ` ${theme.fg("accent", argument)}` : ""}`];
|
|
51536
|
+
if (!event.done) {
|
|
51537
|
+
lines[0] += theme.fg("warning", " \u22EF running");
|
|
51538
|
+
} else if (event.output.length > 0) {
|
|
51539
|
+
const output = event.output.split("\n");
|
|
51540
|
+
const visible = expanded ? output.slice(-40) : output.slice(-3);
|
|
51541
|
+
lines.push(...visible.map((line) => theme.fg("toolOutput", truncate2(line, 160))));
|
|
51542
|
+
}
|
|
51543
|
+
return new Text8(lines.join("\n"), 0, 0);
|
|
51544
|
+
}
|
|
51545
|
+
function renderSidekickTranscript(theme, opts) {
|
|
51546
|
+
const box = new Container3();
|
|
51547
|
+
box.addChild(new Text8(opts.header, 0, 0));
|
|
51548
|
+
if (!opts.isPartial && !opts.expanded) {
|
|
51549
|
+
if (opts.report?.text) box.addChild(opts.renderText(opts.report.text));
|
|
51550
|
+
box.addChild(new Text8(theme.fg("dim", `${String(opts.events.length)} steps \xB7 expand to see the transcript`), 0, 0));
|
|
51551
|
+
return box;
|
|
51552
|
+
}
|
|
51553
|
+
const dropped = opts.droppedEvents ?? 0;
|
|
51554
|
+
const start = opts.expanded ? 0 : Math.max(0, opts.events.length - 8);
|
|
51555
|
+
if (opts.expanded && dropped > 0) {
|
|
51556
|
+
box.addChild(new Text8(theme.fg("dim", `\u2026 ${String(dropped)} earliest steps dropped`), 0, 0));
|
|
51557
|
+
} else if (start > 0) {
|
|
51558
|
+
box.addChild(new Text8(theme.fg("dim", `\u2026 ${String(start + dropped)} earlier steps`), 0, 0));
|
|
51559
|
+
}
|
|
51560
|
+
for (const event of opts.events.slice(start)) {
|
|
51561
|
+
if (event.kind === "tool") box.addChild(toolComponent(theme, event, opts.expanded));
|
|
51562
|
+
else if (event.text.trim()) box.addChild(opts.renderText(event.text.trim()));
|
|
51563
|
+
}
|
|
51564
|
+
if (!opts.isPartial && opts.expanded && opts.report?.text) {
|
|
51565
|
+
const last = opts.events.at(-1);
|
|
51566
|
+
if (last?.kind === "text" && last.text.trim() === opts.report.text.trim()) return box;
|
|
51567
|
+
box.addChild(new Text8(theme.fg("dim", "\u2500\u2500 report \u2500\u2500"), 0, 0));
|
|
51568
|
+
box.addChild(opts.renderText(opts.report.text));
|
|
51569
|
+
}
|
|
51570
|
+
return box;
|
|
51571
|
+
}
|
|
51572
|
+
|
|
51573
|
+
// packages/fusion/src/tools.ts
|
|
51417
51574
|
var SidekickParams = Type11.Object({
|
|
51418
51575
|
message: Type11.String({ description: "A concrete implementation or verification brief for the sidekick" }),
|
|
51419
51576
|
block: Type11.Optional(Type11.Boolean({ description: "Wait for completion (default true)" }))
|
|
@@ -51426,6 +51583,9 @@ var ReadSubagentParams = Type11.Object({
|
|
|
51426
51583
|
function duration(ms) {
|
|
51427
51584
|
return `${(ms / 1e3).toFixed(1)}s`;
|
|
51428
51585
|
}
|
|
51586
|
+
function firstLine3(value) {
|
|
51587
|
+
return value.split("\n", 1)[0] ?? "";
|
|
51588
|
+
}
|
|
51429
51589
|
function progressText(runtime, id) {
|
|
51430
51590
|
const progress = runtime.progress(id);
|
|
51431
51591
|
if (!progress) return "No active handoff progress.";
|
|
@@ -51438,7 +51598,9 @@ ${progress.recentTools.map((tool) => ` ${tool}`).join("\n")}` : "";
|
|
|
51438
51598
|
}
|
|
51439
51599
|
function progressKey(runtime, id) {
|
|
51440
51600
|
const progress = runtime.progress(id);
|
|
51441
|
-
|
|
51601
|
+
if (!progress) return "";
|
|
51602
|
+
const last = progress.events.at(-1);
|
|
51603
|
+
return `${String(progress.toolCalls)}|${progress.recentTools.join("|")}|${progress.textTail}|${String(progress.events.length)}|${last?.kind === "tool" ? `${String(last.output.length)}|${String(last.done)}` : last?.kind === "text" ? `${String(last.text.length)}|${String(last.open)}` : ""}`;
|
|
51442
51604
|
}
|
|
51443
51605
|
function reportText(report) {
|
|
51444
51606
|
return `${report.text}
|
|
@@ -51466,7 +51628,7 @@ async function waitForReport(runtime, id, done, signal, ctx, onUpdate, timeoutMs
|
|
|
51466
51628
|
const key = progressKey(runtime, id);
|
|
51467
51629
|
if (key !== lastProgressKey) {
|
|
51468
51630
|
lastProgressKey = key;
|
|
51469
|
-
onUpdate?.({ content: [{ type: "text", text: progress }] });
|
|
51631
|
+
onUpdate?.({ content: [{ type: "text", text: progress }], details: { progress: runtime.progress(id) } });
|
|
51470
51632
|
}
|
|
51471
51633
|
}
|
|
51472
51634
|
}
|
|
@@ -51480,13 +51642,46 @@ ${report.text}
|
|
|
51480
51642
|
details: report
|
|
51481
51643
|
};
|
|
51482
51644
|
}
|
|
51645
|
+
function transcriptTheme(theme) {
|
|
51646
|
+
return { fg: (color2, text) => theme.fg(color2, text), bold: (text) => theme.bold(text) };
|
|
51647
|
+
}
|
|
51648
|
+
function markdownText(markdown) {
|
|
51649
|
+
return new Markdown2(markdown, 0, 0, getMarkdownTheme2());
|
|
51650
|
+
}
|
|
51651
|
+
function contentText(content) {
|
|
51652
|
+
if (!Array.isArray(content)) return String(content ?? "");
|
|
51653
|
+
return content.map((part) => typeof part === "object" && part !== null && typeof part.text === "string" ? part.text : "").filter(Boolean).join("\n");
|
|
51654
|
+
}
|
|
51655
|
+
function renderToolTranscript(result2, options, theme, label) {
|
|
51656
|
+
const details = result2.details;
|
|
51657
|
+
const progress = details?.progress;
|
|
51658
|
+
const report = progress ? void 0 : details;
|
|
51659
|
+
const events = progress?.events ?? report?.events;
|
|
51660
|
+
const partial = progress !== void 0;
|
|
51661
|
+
const status = partial ? "working" : report?.status === "completed" && !result2.isError ? "completed" : "error";
|
|
51662
|
+
if (!events) return frameSidekick(theme, status, new Text9(contentText(result2.content), 0, 0));
|
|
51663
|
+
const header = partial ? `${theme.fg("accent", theme.bold(`\u25C6 ${label} working`))} ${theme.fg("dim", `\xB7 ${String(progress.toolCalls)} tool calls \xB7 ${duration(Date.now() - progress.startedAt)}`)}` : `${theme.fg(report?.status === "completed" ? "success" : "error", "\u25C6")} ${theme.fg("accent", theme.bold(`${label} ${report?.status ?? "done"}`))} ${theme.fg("dim", report ? `\xB7 ${report.id} \xB7 ${String(report.toolCalls)} tool calls \xB7 ${duration(report.durationMs)} \xB7 in ${String(report.usage.input)} / out ${String(report.usage.output)} tokens` : "")}`;
|
|
51664
|
+
return frameSidekick(theme, status, renderSidekickTranscript(transcriptTheme(theme), {
|
|
51665
|
+
events,
|
|
51666
|
+
droppedEvents: progress?.droppedEvents,
|
|
51667
|
+
header,
|
|
51668
|
+
expanded: options.expanded === true,
|
|
51669
|
+
isPartial: partial,
|
|
51670
|
+
report,
|
|
51671
|
+
renderText: markdownText
|
|
51672
|
+
}));
|
|
51673
|
+
}
|
|
51483
51674
|
function renderCompletionCard2(theme, report) {
|
|
51484
|
-
const
|
|
51485
|
-
|
|
51486
|
-
|
|
51487
|
-
|
|
51488
|
-
|
|
51489
|
-
|
|
51675
|
+
const status = report?.status === "completed" ? "completed" : "error";
|
|
51676
|
+
if (!report) return frameSidekick(theme, "error", new Text9(`${theme.fg("accent", "\u25C6")} ${theme.fg("accent", theme.bold("sidekick done"))}`, 0, 0));
|
|
51677
|
+
return frameSidekick(theme, status, renderSidekickTranscript(transcriptTheme(theme), {
|
|
51678
|
+
events: report.events ?? [],
|
|
51679
|
+
header: `${theme.fg(report.status === "completed" ? "success" : "error", "\u25C6")} ${theme.fg("accent", theme.bold(`sidekick ${report.status}`))} ${theme.fg("dim", `\xB7 ${report.id} \xB7 ${String(report.toolCalls)} tool calls \xB7 ${duration(report.durationMs)}`)}`,
|
|
51680
|
+
expanded: false,
|
|
51681
|
+
isPartial: false,
|
|
51682
|
+
report,
|
|
51683
|
+
renderText: markdownText
|
|
51684
|
+
}));
|
|
51490
51685
|
}
|
|
51491
51686
|
function registerFusionTools(pi, deps) {
|
|
51492
51687
|
pi.registerMessageRenderer("sidekick-completion", (message, _options, theme) => renderCompletionCard2(theme, message.details));
|
|
@@ -51495,10 +51690,15 @@ function registerFusionTools(pi, deps) {
|
|
|
51495
51690
|
label: "Sidekick",
|
|
51496
51691
|
description: "Hand off work to your persistent sidekick subagent (one per session; context and shells persist across handoffs; runs on the same machine). block:true (default) waits and returns the report. block:false returns immediately and the report arrives later as a <subagent_completion_notification>. Calling again while a handoff is running injects the message as an interrupt rather than starting a second sidekick.",
|
|
51497
51692
|
parameters: SidekickParams,
|
|
51693
|
+
renderShell: "self",
|
|
51694
|
+
renderCall: (args, theme) => frameSidekick(theme, "working", new Text9(`${theme.fg("toolTitle", theme.bold("\u25C6 sidekick"))} ${theme.fg("dim", firstLine3(String(args.message)).slice(0, 100))}`, 0, 0)),
|
|
51695
|
+
renderResult: (result2, options, theme) => renderToolTranscript(result2, options, theme, "sidekick"),
|
|
51498
51696
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
51499
51697
|
const runtime = deps.getRuntime(ctx);
|
|
51500
51698
|
if (!runtime) return result("Fusion is not active \u2014 pick a Fusion pair with /unipi:model.", void 0, true);
|
|
51699
|
+
const wasBusy = runtime.isBusy();
|
|
51501
51700
|
const handoff = runtime.handoff(params.message);
|
|
51701
|
+
if (!wasBusy) deps.onHandoffStart?.(ctx);
|
|
51502
51702
|
if (params.block === false) {
|
|
51503
51703
|
void handoff.done.then((report) => {
|
|
51504
51704
|
deps.onReport?.(ctx, report);
|
|
@@ -51524,6 +51724,9 @@ ${progressText(runtime, handoff.id)}`);
|
|
|
51524
51724
|
label: "Read Sidekick",
|
|
51525
51725
|
description: "Read a sidekick handoff report by agent_id (omit for the latest). block:true waits for completion (default timeout 2700s when omitted); block:false returns the current progress snapshot immediately.",
|
|
51526
51726
|
parameters: ReadSubagentParams,
|
|
51727
|
+
renderShell: "self",
|
|
51728
|
+
renderCall: (args, theme) => frameSidekick(theme, "working", new Text9(`${theme.fg("toolTitle", theme.bold("\u25C6 read_subagent"))} ${theme.fg("dim", args.agent_id ?? "latest")}`, 0, 0)),
|
|
51729
|
+
renderResult: (result2, options, theme) => renderToolTranscript(result2, options, theme, "read_subagent"),
|
|
51527
51730
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
51528
51731
|
const runtime = deps.getRuntime(ctx);
|
|
51529
51732
|
if (!runtime) return result("Fusion is not active \u2014 pick a Fusion pair with /unipi:model.", void 0, true);
|
|
@@ -51610,7 +51813,10 @@ function fusionExtension(pi) {
|
|
|
51610
51813
|
if (process.env.UNIPI_FUSION_CHILD === "1") return;
|
|
51611
51814
|
let active;
|
|
51612
51815
|
let runtime;
|
|
51613
|
-
let
|
|
51816
|
+
let lastCtx;
|
|
51817
|
+
let leadToolCalls = 0;
|
|
51818
|
+
let editNudgedThisTurn = false;
|
|
51819
|
+
let bashStreak = 0;
|
|
51614
51820
|
function identity(ctx) {
|
|
51615
51821
|
const reg = registryOf(ctx);
|
|
51616
51822
|
const names = (k) => findModel(reg, k)?.name || splitModelKey(k)?.id || k;
|
|
@@ -51630,6 +51836,7 @@ function fusionExtension(pi) {
|
|
|
51630
51836
|
return estimateSavings(runtime.usage, costOf(lead, preset2.prices[active.lead]), costOf(side, preset2.prices[active.sidekick])).savedUsd;
|
|
51631
51837
|
}
|
|
51632
51838
|
function publishStatus(ctx) {
|
|
51839
|
+
lastCtx = ctx;
|
|
51633
51840
|
const reg = registryOf(ctx);
|
|
51634
51841
|
const names = (k) => findModel(reg, k)?.name || splitModelKey(k)?.id || k;
|
|
51635
51842
|
if (active?.kind === "fusion") {
|
|
@@ -51638,12 +51845,18 @@ function fusionExtension(pi) {
|
|
|
51638
51845
|
leadEffort: active.leadEffort ?? "",
|
|
51639
51846
|
sidekickName: names(active.sidekick),
|
|
51640
51847
|
sidekickEffort: active.sidekickEffort ?? "",
|
|
51641
|
-
savedUsd: statusSavings(ctx)
|
|
51848
|
+
savedUsd: statusSavings(ctx),
|
|
51849
|
+
busy: runtime?.isBusy() ?? false,
|
|
51850
|
+
leadToolCalls,
|
|
51851
|
+
sidekickToolCalls: runtime?.totalToolCalls() ?? 0
|
|
51642
51852
|
});
|
|
51643
51853
|
} else {
|
|
51644
51854
|
setSharedFusionStatus(void 0);
|
|
51645
51855
|
}
|
|
51646
51856
|
}
|
|
51857
|
+
function publishStatusLater() {
|
|
51858
|
+
if (lastCtx) publishStatus(lastCtx);
|
|
51859
|
+
}
|
|
51647
51860
|
function leadSessionId(ctx) {
|
|
51648
51861
|
const manager2 = ctx.sessionManager;
|
|
51649
51862
|
return manager2?.getSessionId?.() ?? "default";
|
|
@@ -51656,7 +51869,8 @@ function fusionExtension(pi) {
|
|
|
51656
51869
|
model: active.sidekick,
|
|
51657
51870
|
thinking: active.sidekickEffort ?? "medium",
|
|
51658
51871
|
sessionFile: sidekickSessionPath(leadSessionId(ctx)),
|
|
51659
|
-
systemPrompt: sidekickSystemPrompt(identity(ctx))
|
|
51872
|
+
systemPrompt: sidekickSystemPrompt(identity(ctx)),
|
|
51873
|
+
onProgress: () => publishStatusLater()
|
|
51660
51874
|
});
|
|
51661
51875
|
}
|
|
51662
51876
|
return runtime;
|
|
@@ -51664,6 +51878,7 @@ function fusionExtension(pi) {
|
|
|
51664
51878
|
function stopRuntime() {
|
|
51665
51879
|
runtime?.kill();
|
|
51666
51880
|
runtime = void 0;
|
|
51881
|
+
leadToolCalls = 0;
|
|
51667
51882
|
}
|
|
51668
51883
|
function savingsStats(ctx) {
|
|
51669
51884
|
if (active?.kind !== "fusion" || runtime === void 0) return "Fusion is not active \u2014 pick a Fusion pair with /unipi:model.";
|
|
@@ -51679,7 +51894,8 @@ Handoffs: ${String(runtime.reports.size)} \xB7 runtime alive: ${String(runtime.i
|
|
|
51679
51894
|
}
|
|
51680
51895
|
registerFusionTools(pi, {
|
|
51681
51896
|
getRuntime,
|
|
51682
|
-
onReport: (ctx) => publishStatus(ctx)
|
|
51897
|
+
onReport: (ctx) => publishStatus(ctx),
|
|
51898
|
+
onHandoffStart: (ctx) => publishStatus(ctx)
|
|
51683
51899
|
});
|
|
51684
51900
|
pi.registerCommand("unipi:fusion-stats", {
|
|
51685
51901
|
description: "Estimated Fusion savings (sidekick tokens priced at lead rates)",
|
|
@@ -51688,10 +51904,31 @@ Handoffs: ${String(runtime.reports.size)} \xB7 runtime alive: ${String(runtime.i
|
|
|
51688
51904
|
pi.on("before_agent_start", (event, ctx) => active?.kind === "fusion" ? { systemPrompt: `${event.systemPrompt}
|
|
51689
51905
|
|
|
51690
51906
|
${leadPolicy(identity(ctx))}` } : void 0);
|
|
51907
|
+
pi.on("turn_start", () => {
|
|
51908
|
+
editNudgedThisTurn = false;
|
|
51909
|
+
});
|
|
51691
51910
|
pi.on("tool_result", (event) => {
|
|
51692
|
-
if (active?.kind !== "fusion"
|
|
51693
|
-
|
|
51694
|
-
|
|
51911
|
+
if (active?.kind !== "fusion") return;
|
|
51912
|
+
const toolName = event.toolName;
|
|
51913
|
+
if (toolName === "sidekick" || toolName === "read_subagent") {
|
|
51914
|
+
bashStreak = 0;
|
|
51915
|
+
return;
|
|
51916
|
+
}
|
|
51917
|
+
leadToolCalls += 1;
|
|
51918
|
+
publishStatusLater();
|
|
51919
|
+
if (toolName === "edit" || toolName === "write") {
|
|
51920
|
+
if (editNudgedThisTurn) return;
|
|
51921
|
+
editNudgedThisTurn = true;
|
|
51922
|
+
return { content: [...event.content, { type: "text", text: EDIT_NUDGE }] };
|
|
51923
|
+
}
|
|
51924
|
+
if (toolName !== "bash") return;
|
|
51925
|
+
const command = typeof event.input.command === "string" ? event.input.command : "";
|
|
51926
|
+
if (isTrivialShell(command)) return;
|
|
51927
|
+
bashStreak += 1;
|
|
51928
|
+
if (bashStreak < BASH_NUDGE_EVERY) return;
|
|
51929
|
+
const content = [...event.content, { type: "text", text: bashNudge(bashStreak) }];
|
|
51930
|
+
bashStreak = 0;
|
|
51931
|
+
return { content };
|
|
51695
51932
|
});
|
|
51696
51933
|
async function applyResult(ctx, result2, preset2, loaded) {
|
|
51697
51934
|
if (result2.type === "cancelled") return;
|
|
@@ -51824,7 +52061,8 @@ ${String(result2.curation.lead.length)} lead \xB7 ${String(result2.curation.side
|
|
|
51824
52061
|
});
|
|
51825
52062
|
pi.on("session_start", async (_e, ctx) => {
|
|
51826
52063
|
stopRuntime();
|
|
51827
|
-
|
|
52064
|
+
editNudgedThisTurn = false;
|
|
52065
|
+
bashStreak = 0;
|
|
51828
52066
|
modelBykey.clear();
|
|
51829
52067
|
active = loadPreset(ctx.cwd ?? process.cwd()).preset.active;
|
|
51830
52068
|
if (active?.kind === "fusion" && (!ctx.model || modelKey(ctx.model) !== active.lead)) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/updater",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.18.0",
|
|
4
4
|
"description": "Auto-updater, changelog browser, and readme browser for Unipi — checks npm registry, renders CHANGELOG.md and README.md files in TUI overlays",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@pi-unipi/core": "2.
|
|
37
|
+
"@pi-unipi/core": "2.18.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@earendil-works/pi-coding-agent": "^0.84.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/utility",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.18.0",
|
|
4
4
|
"description": "Utility commands and tools for Pi coding agent — lifecycle, diagnostics, cache, analytics, display, batch execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@pi-unipi/core": "2.
|
|
39
|
+
"@pi-unipi/core": "2.18.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {},
|
|
42
42
|
"peerDependencies": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/web-api",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.18.0",
|
|
4
4
|
"description": "Web search, read, and summarize tools with provider-based backend selection for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"README.md"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@pi-unipi/core": "2.
|
|
34
|
+
"@pi-unipi/core": "2.18.0",
|
|
35
35
|
"defuddle": "^0.18.1",
|
|
36
36
|
"linkedom": "^0.18.12",
|
|
37
37
|
"wreq-js": "^2.3.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/workflow",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.18.0",
|
|
4
4
|
"description": "Structured development workflow commands for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@pi-unipi/core": "2.
|
|
33
|
+
"@pi-unipi/core": "2.18.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@earendil-works/pi-ai": "^0.84.0",
|