@gaberrb/polypus 0.4.11 → 0.4.13
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/dist/index.js +204 -59
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -205,6 +205,7 @@ var en = {
|
|
|
205
205
|
"swarm.needsAgents": "Swarm mode needs at least {min} configured agents (you have {have}). Add more with `polypus add-agent`, or use `polypus run` for a single agent.",
|
|
206
206
|
"swarm.status": "swarm agents=[{agents}] workspace={workspace}",
|
|
207
207
|
"swarm.bypassNote": "Workers run in bypass mode inside isolated git worktrees; branches are merged at the end.",
|
|
208
|
+
"swarm.cancelling": "cancelling swarm \u2014 finishing in-flight workers, then merging what committed\u2026",
|
|
208
209
|
"swarm.decomposed": "Decomposed into {n} subtask(s):",
|
|
209
210
|
"swarm.workerStart": "\u25B6 {id} started by {agent}",
|
|
210
211
|
"swarm.workerDone": "\u2713 {id} done",
|
|
@@ -464,6 +465,7 @@ var ptBR = {
|
|
|
464
465
|
"swarm.needsAgents": "O modo swarm precisa de pelo menos {min} agentes configurados (voc\xEA tem {have}). Adicione mais com `polypus add-agent`, ou use `polypus run` para um agente s\xF3.",
|
|
465
466
|
"swarm.status": "swarm agentes=[{agents}] workspace={workspace}",
|
|
466
467
|
"swarm.bypassNote": "Os workers rodam em modo bypass dentro de git worktrees isoladas; os branches s\xE3o mesclados no final.",
|
|
468
|
+
"swarm.cancelling": "cancelando o swarm \u2014 encerrando os workers em andamento e mesclando o que commitou\u2026",
|
|
467
469
|
"swarm.decomposed": "Dividido em {n} subtarefa(s):",
|
|
468
470
|
"swarm.workerStart": "\u25B6 {id} iniciada por {agent}",
|
|
469
471
|
"swarm.workerDone": "\u2713 {id} conclu\xEDda",
|
|
@@ -908,15 +910,33 @@ var OpenAICompatibleProvider = class {
|
|
|
908
910
|
parameters: t2.parameters
|
|
909
911
|
}
|
|
910
912
|
}));
|
|
913
|
+
const base = {
|
|
914
|
+
model: this.model,
|
|
915
|
+
messages,
|
|
916
|
+
...tools && tools.length > 0 ? { tools } : {},
|
|
917
|
+
temperature: req.params?.temperature,
|
|
918
|
+
// Generous default so large files aren't truncated mid tool-call.
|
|
919
|
+
max_tokens: req.params?.maxTokens ?? 8192
|
|
920
|
+
};
|
|
921
|
+
if (req.onDelta) {
|
|
922
|
+
const stream = await this.client.chat.completions.create(
|
|
923
|
+
{ ...base, stream: true, stream_options: { include_usage: true } },
|
|
924
|
+
{ signal: req.signal }
|
|
925
|
+
);
|
|
926
|
+
const agg = await aggregateStream(stream, req.onDelta);
|
|
927
|
+
return {
|
|
928
|
+
content: agg.content,
|
|
929
|
+
toolCalls: agg.toolCalls.map((tc, i) => ({
|
|
930
|
+
id: tc.id || `call_${i}`,
|
|
931
|
+
name: tc.name,
|
|
932
|
+
arguments: safeParseArgs(tc.arguments)
|
|
933
|
+
})),
|
|
934
|
+
finishReason: agg.finishReason || "stop",
|
|
935
|
+
usage: agg.usage
|
|
936
|
+
};
|
|
937
|
+
}
|
|
911
938
|
const completion = await this.client.chat.completions.create(
|
|
912
|
-
{
|
|
913
|
-
model: this.model,
|
|
914
|
-
messages,
|
|
915
|
-
...tools && tools.length > 0 ? { tools } : {},
|
|
916
|
-
temperature: req.params?.temperature,
|
|
917
|
-
// Generous default so large files aren't truncated mid tool-call.
|
|
918
|
-
max_tokens: req.params?.maxTokens ?? 8192
|
|
919
|
-
},
|
|
939
|
+
{ ...base },
|
|
920
940
|
{ signal: req.signal }
|
|
921
941
|
);
|
|
922
942
|
const choice = completion.choices[0];
|
|
@@ -937,6 +957,34 @@ var OpenAICompatibleProvider = class {
|
|
|
937
957
|
};
|
|
938
958
|
}
|
|
939
959
|
};
|
|
960
|
+
async function aggregateStream(stream, onDelta) {
|
|
961
|
+
let content = "";
|
|
962
|
+
let finishReason = "";
|
|
963
|
+
let usage2;
|
|
964
|
+
const toolAcc = [];
|
|
965
|
+
for await (const chunk of stream) {
|
|
966
|
+
const choice = chunk.choices?.[0];
|
|
967
|
+
const delta = choice?.delta;
|
|
968
|
+
if (delta?.content) {
|
|
969
|
+
content += delta.content;
|
|
970
|
+
onDelta?.(delta.content);
|
|
971
|
+
}
|
|
972
|
+
for (const tc of delta?.tool_calls ?? []) {
|
|
973
|
+
const slot = toolAcc[tc.index] ??= { id: "", name: "", arguments: "" };
|
|
974
|
+
if (tc.id) slot.id = tc.id;
|
|
975
|
+
if (tc.function?.name) slot.name = tc.function.name;
|
|
976
|
+
if (tc.function?.arguments) slot.arguments += tc.function.arguments;
|
|
977
|
+
}
|
|
978
|
+
if (choice?.finish_reason) finishReason = choice.finish_reason;
|
|
979
|
+
if (chunk.usage) {
|
|
980
|
+
usage2 = {
|
|
981
|
+
promptTokens: chunk.usage.prompt_tokens,
|
|
982
|
+
completionTokens: chunk.usage.completion_tokens
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return { content, finishReason, usage: usage2, toolCalls: toolAcc.filter(Boolean) };
|
|
987
|
+
}
|
|
940
988
|
function toOpenAIMessage(m) {
|
|
941
989
|
switch (m.role) {
|
|
942
990
|
case "tool":
|
|
@@ -2282,13 +2330,15 @@ async function runAgent(opts) {
|
|
|
2282
2330
|
}
|
|
2283
2331
|
}
|
|
2284
2332
|
}
|
|
2333
|
+
const wantDelta = driver.kind === "native" && typeof events?.onAssistantDelta === "function";
|
|
2285
2334
|
let response;
|
|
2286
2335
|
try {
|
|
2287
2336
|
response = await agent.provider.chat({
|
|
2288
2337
|
messages,
|
|
2289
2338
|
tools: driver.providerTools(),
|
|
2290
2339
|
params: opts.params,
|
|
2291
|
-
signal: opts.signal
|
|
2340
|
+
signal: opts.signal,
|
|
2341
|
+
onDelta: wantDelta ? (chunk) => events.onAssistantDelta(chunk) : void 0
|
|
2292
2342
|
});
|
|
2293
2343
|
} catch (err) {
|
|
2294
2344
|
if (opts.signal?.aborted) return { finished: false, reason: "cancelled", steps: step, messages, usage: usage2 };
|
|
@@ -3632,7 +3682,7 @@ async function removeWorktree(git, wt) {
|
|
|
3632
3682
|
}
|
|
3633
3683
|
|
|
3634
3684
|
// src/core/agent/worker.ts
|
|
3635
|
-
async function runWorker(subtask, agent, wt, allow, deny, events) {
|
|
3685
|
+
async function runWorker(subtask, agent, wt, allow, deny, events, signal) {
|
|
3636
3686
|
const permissions = new PermissionEngine({
|
|
3637
3687
|
mode: "bypass",
|
|
3638
3688
|
policy: { workspace: wt.path, allow, deny },
|
|
@@ -3644,9 +3694,10 @@ async function runWorker(subtask, agent, wt, allow, deny, events) {
|
|
|
3644
3694
|
agent,
|
|
3645
3695
|
permissions,
|
|
3646
3696
|
promptContext: { workspace: wt.path, mode: "bypass", allow, briefing: subtask.brief },
|
|
3647
|
-
events
|
|
3697
|
+
events,
|
|
3698
|
+
signal
|
|
3648
3699
|
});
|
|
3649
|
-
const committed = await commitWorktree(wt, `polypus(${subtask.id}): ${subtask.title}`);
|
|
3700
|
+
const committed = result.reason === "cancelled" ? false : await commitWorktree(wt, `polypus(${subtask.id}): ${subtask.title}`);
|
|
3650
3701
|
return {
|
|
3651
3702
|
subtask,
|
|
3652
3703
|
agentName: agent.config.name,
|
|
@@ -3663,30 +3714,23 @@ async function runSwarm(opts) {
|
|
|
3663
3714
|
const lead = opts.agents[0];
|
|
3664
3715
|
if (!lead) throw new Error("Swarm requires at least one agent.");
|
|
3665
3716
|
const maxSubtasks = opts.maxSubtasks ?? Math.max(opts.agents.length, 2);
|
|
3717
|
+
const concurrency = Math.max(1, opts.concurrency ?? opts.agents.length);
|
|
3718
|
+
const idleTimeoutMs2 = opts.idleTimeoutMs ?? 0;
|
|
3666
3719
|
const git = await ensureRepo(opts.workspace);
|
|
3667
|
-
const subtasks = await decompose(lead, opts.task, maxSubtasks);
|
|
3720
|
+
const subtasks = await decompose(lead, opts.task, maxSubtasks, opts.signal);
|
|
3668
3721
|
opts.events?.onDecomposed?.(subtasks);
|
|
3669
3722
|
const worktrees = [];
|
|
3670
3723
|
for (const subtask of subtasks) {
|
|
3671
3724
|
worktrees.push(await createWorktree(git, subtask.id));
|
|
3672
3725
|
}
|
|
3673
|
-
const
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
wt,
|
|
3682
|
-
opts.allow,
|
|
3683
|
-
opts.deny,
|
|
3684
|
-
opts.events?.workerEvents?.(subtask)
|
|
3685
|
-
);
|
|
3686
|
-
opts.events?.onWorkerDone?.(outcome);
|
|
3687
|
-
return outcome;
|
|
3688
|
-
})
|
|
3689
|
-
);
|
|
3726
|
+
const runOne = (subtask, i) => guardedWorker(subtask, opts.agents[i % opts.agents.length], worktrees[i], {
|
|
3727
|
+
allow: opts.allow,
|
|
3728
|
+
deny: opts.deny,
|
|
3729
|
+
idleTimeoutMs: idleTimeoutMs2,
|
|
3730
|
+
signal: opts.signal,
|
|
3731
|
+
events: opts.events
|
|
3732
|
+
});
|
|
3733
|
+
const outcomes = await runPool(subtasks, concurrency, runOne);
|
|
3690
3734
|
const merges = [];
|
|
3691
3735
|
for (const outcome of outcomes) {
|
|
3692
3736
|
if (!outcome.committed) continue;
|
|
@@ -3704,13 +3748,69 @@ async function runSwarm(opts) {
|
|
|
3704
3748
|
}
|
|
3705
3749
|
return { subtasks, outcomes, merges };
|
|
3706
3750
|
}
|
|
3751
|
+
async function guardedWorker(subtask, agent, wt, opts) {
|
|
3752
|
+
const ac = new AbortController();
|
|
3753
|
+
const onAbort = () => ac.abort();
|
|
3754
|
+
if (opts.signal) {
|
|
3755
|
+
if (opts.signal.aborted) ac.abort();
|
|
3756
|
+
else opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
3757
|
+
}
|
|
3758
|
+
let idleTimer;
|
|
3759
|
+
const resetIdle = () => {
|
|
3760
|
+
if (opts.idleTimeoutMs <= 0) return;
|
|
3761
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
3762
|
+
idleTimer = setTimeout(() => ac.abort(), opts.idleTimeoutMs);
|
|
3763
|
+
idleTimer.unref?.();
|
|
3764
|
+
};
|
|
3765
|
+
const base = opts.events?.workerEvents?.(subtask);
|
|
3766
|
+
const events = {
|
|
3767
|
+
...base,
|
|
3768
|
+
onStep: (n) => {
|
|
3769
|
+
resetIdle();
|
|
3770
|
+
base?.onStep?.(n);
|
|
3771
|
+
}
|
|
3772
|
+
};
|
|
3773
|
+
opts.events?.onWorkerStart?.(subtask, agent.config.name);
|
|
3774
|
+
resetIdle();
|
|
3775
|
+
let outcome;
|
|
3776
|
+
try {
|
|
3777
|
+
outcome = await runWorker(subtask, agent, wt, opts.allow, opts.deny, events, ac.signal);
|
|
3778
|
+
} catch {
|
|
3779
|
+
outcome = {
|
|
3780
|
+
subtask,
|
|
3781
|
+
agentName: agent.config.name,
|
|
3782
|
+
branch: wt.branch,
|
|
3783
|
+
finished: false,
|
|
3784
|
+
committed: false,
|
|
3785
|
+
steps: 0
|
|
3786
|
+
};
|
|
3787
|
+
} finally {
|
|
3788
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
3789
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
3790
|
+
}
|
|
3791
|
+
opts.events?.onWorkerDone?.(outcome);
|
|
3792
|
+
return outcome;
|
|
3793
|
+
}
|
|
3794
|
+
async function runPool(items, limit, fn) {
|
|
3795
|
+
const results = new Array(items.length);
|
|
3796
|
+
let next = 0;
|
|
3797
|
+
const worker = async () => {
|
|
3798
|
+
for (; ; ) {
|
|
3799
|
+
const i = next++;
|
|
3800
|
+
if (i >= items.length) return;
|
|
3801
|
+
results[i] = await fn(items[i], i);
|
|
3802
|
+
}
|
|
3803
|
+
};
|
|
3804
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
3805
|
+
return results;
|
|
3806
|
+
}
|
|
3707
3807
|
var DECOMPOSE_SYSTEM = [
|
|
3708
3808
|
"You are a tech lead splitting a coding task into independent subtasks that can be done in parallel.",
|
|
3709
3809
|
'Return ONLY a JSON array. Each item: {"title": string, "brief": string}.',
|
|
3710
3810
|
"Make subtasks touch DIFFERENT files/areas to minimize merge conflicts.",
|
|
3711
3811
|
"Keep the list small (prefer 2-4 items). Each brief must be self-contained and actionable."
|
|
3712
3812
|
].join("\n");
|
|
3713
|
-
async function decompose(lead, task, maxSubtasks) {
|
|
3813
|
+
async function decompose(lead, task, maxSubtasks, signal) {
|
|
3714
3814
|
try {
|
|
3715
3815
|
const res = await lead.provider.chat({
|
|
3716
3816
|
messages: [
|
|
@@ -3720,7 +3820,8 @@ ${task}
|
|
|
3720
3820
|
|
|
3721
3821
|
Return at most ${maxSubtasks} subtasks as a JSON array.` }
|
|
3722
3822
|
],
|
|
3723
|
-
params: { temperature: 0 }
|
|
3823
|
+
params: { temperature: 0 },
|
|
3824
|
+
signal
|
|
3724
3825
|
});
|
|
3725
3826
|
const parsed = extractJsonArray(res.content);
|
|
3726
3827
|
if (parsed && parsed.length > 0) {
|
|
@@ -3746,6 +3847,28 @@ function extractJsonArray(text2) {
|
|
|
3746
3847
|
}
|
|
3747
3848
|
}
|
|
3748
3849
|
|
|
3850
|
+
// src/core/agent/concurrency.ts
|
|
3851
|
+
var OLLAMA_ENDPOINT_CONCURRENCY = 2;
|
|
3852
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 3e5;
|
|
3853
|
+
function endpointKey(agent) {
|
|
3854
|
+
return `${agent.config.provider}:${agent.config.baseUrl ?? "default"}`;
|
|
3855
|
+
}
|
|
3856
|
+
function recommendConcurrency(agents) {
|
|
3857
|
+
const perEndpoint = /* @__PURE__ */ new Map();
|
|
3858
|
+
for (const a of agents) {
|
|
3859
|
+
perEndpoint.set(endpointKey(a), (perEndpoint.get(endpointKey(a)) ?? 0) + 1);
|
|
3860
|
+
}
|
|
3861
|
+
let total = 0;
|
|
3862
|
+
for (const [key, count] of perEndpoint) {
|
|
3863
|
+
total += key.startsWith("ollama:") ? Math.min(count, OLLAMA_ENDPOINT_CONCURRENCY) : count;
|
|
3864
|
+
}
|
|
3865
|
+
return Math.max(1, total);
|
|
3866
|
+
}
|
|
3867
|
+
function idleTimeoutMs() {
|
|
3868
|
+
const raw = Number(process.env.POLYPUS_SWARM_IDLE_TIMEOUT_MS);
|
|
3869
|
+
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_IDLE_TIMEOUT_MS;
|
|
3870
|
+
}
|
|
3871
|
+
|
|
3749
3872
|
// src/ui/swarm-view.ts
|
|
3750
3873
|
var RESET2 = "\x1B[0m";
|
|
3751
3874
|
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
@@ -3909,6 +4032,35 @@ function pad(s, n) {
|
|
|
3909
4032
|
return s.length >= n ? s : s + " ".repeat(n - s.length);
|
|
3910
4033
|
}
|
|
3911
4034
|
|
|
4035
|
+
// src/ui/cancel.ts
|
|
4036
|
+
function listenForCancel(controller) {
|
|
4037
|
+
const stdin2 = process.stdin;
|
|
4038
|
+
if (!stdin2.isTTY) return { pause() {
|
|
4039
|
+
}, resume() {
|
|
4040
|
+
}, dispose() {
|
|
4041
|
+
} };
|
|
4042
|
+
const onData = (buf) => {
|
|
4043
|
+
if (buf.length === 1 && (buf[0] === 27 || buf[0] === 3)) controller.abort();
|
|
4044
|
+
};
|
|
4045
|
+
let active = false;
|
|
4046
|
+
const attach = () => {
|
|
4047
|
+
if (active) return;
|
|
4048
|
+
stdin2.setRawMode(true);
|
|
4049
|
+
stdin2.resume();
|
|
4050
|
+
stdin2.on("data", onData);
|
|
4051
|
+
active = true;
|
|
4052
|
+
};
|
|
4053
|
+
const detach = () => {
|
|
4054
|
+
if (!active) return;
|
|
4055
|
+
stdin2.off("data", onData);
|
|
4056
|
+
stdin2.setRawMode(false);
|
|
4057
|
+
stdin2.pause();
|
|
4058
|
+
active = false;
|
|
4059
|
+
};
|
|
4060
|
+
attach();
|
|
4061
|
+
return { pause: detach, resume: attach, dispose: detach };
|
|
4062
|
+
}
|
|
4063
|
+
|
|
3912
4064
|
// src/cli/commands/swarm.ts
|
|
3913
4065
|
var MIN_SWARM_AGENTS = 3;
|
|
3914
4066
|
function canSwarm(agentCount) {
|
|
@@ -3932,6 +4084,11 @@ async function runSwarmSession(task, config, opts = {}) {
|
|
|
3932
4084
|
pc7.dim(t("swarm.status", { agents: resolved.map((a) => a.config.name).join(", "), workspace }))
|
|
3933
4085
|
);
|
|
3934
4086
|
console.log(pc7.yellow(t("swarm.bypassNote") + "\n"));
|
|
4087
|
+
const controller = new AbortController();
|
|
4088
|
+
const cancel2 = listenForCancel(controller);
|
|
4089
|
+
controller.signal.addEventListener("abort", () => console.log(pc7.dim("\n" + t("swarm.cancelling"))), {
|
|
4090
|
+
once: true
|
|
4091
|
+
});
|
|
3935
4092
|
const view = new SwarmView(resolved[0].config.name);
|
|
3936
4093
|
view.start();
|
|
3937
4094
|
let result;
|
|
@@ -3943,6 +4100,9 @@ async function runSwarmSession(task, config, opts = {}) {
|
|
|
3943
4100
|
allow: config.permissions.allow,
|
|
3944
4101
|
deny: config.permissions.deny,
|
|
3945
4102
|
maxSubtasks: opts.maxSubtasks,
|
|
4103
|
+
concurrency: recommendConcurrency(resolved),
|
|
4104
|
+
idleTimeoutMs: idleTimeoutMs(),
|
|
4105
|
+
signal: controller.signal,
|
|
3946
4106
|
events: {
|
|
3947
4107
|
onDecomposed: (subtasks) => view.setSubtasks(subtasks),
|
|
3948
4108
|
onWorkerStart: (subtask, agentName) => view.workerStart(subtask.id, agentName),
|
|
@@ -3956,6 +4116,7 @@ async function runSwarmSession(task, config, opts = {}) {
|
|
|
3956
4116
|
});
|
|
3957
4117
|
} finally {
|
|
3958
4118
|
view.stop();
|
|
4119
|
+
cancel2.dispose();
|
|
3959
4120
|
}
|
|
3960
4121
|
console.log("");
|
|
3961
4122
|
console.log(pc7.bold("\n" + t("swarm.summary")));
|
|
@@ -4228,33 +4389,6 @@ async function executeTask(task, resolved, workspace, session, json = false, ver
|
|
|
4228
4389
|
function fmtTokens(n) {
|
|
4229
4390
|
return n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : String(n);
|
|
4230
4391
|
}
|
|
4231
|
-
function listenForCancel(controller) {
|
|
4232
|
-
const stdin2 = process.stdin;
|
|
4233
|
-
if (!stdin2.isTTY) return { pause() {
|
|
4234
|
-
}, resume() {
|
|
4235
|
-
}, dispose() {
|
|
4236
|
-
} };
|
|
4237
|
-
const onData = (buf) => {
|
|
4238
|
-
if (buf.length === 1 && (buf[0] === 27 || buf[0] === 3)) controller.abort();
|
|
4239
|
-
};
|
|
4240
|
-
let active = false;
|
|
4241
|
-
const attach = () => {
|
|
4242
|
-
if (active) return;
|
|
4243
|
-
stdin2.setRawMode(true);
|
|
4244
|
-
stdin2.resume();
|
|
4245
|
-
stdin2.on("data", onData);
|
|
4246
|
-
active = true;
|
|
4247
|
-
};
|
|
4248
|
-
const detach = () => {
|
|
4249
|
-
if (!active) return;
|
|
4250
|
-
stdin2.off("data", onData);
|
|
4251
|
-
stdin2.setRawMode(false);
|
|
4252
|
-
stdin2.pause();
|
|
4253
|
-
active = false;
|
|
4254
|
-
};
|
|
4255
|
-
attach();
|
|
4256
|
-
return { pause: detach, resume: attach, dispose: detach };
|
|
4257
|
-
}
|
|
4258
4392
|
async function confirmAction(req) {
|
|
4259
4393
|
if (req.kind === "write" && req.hunks && req.hunks.length > 0) {
|
|
4260
4394
|
renderDiff(req.hunks);
|
|
@@ -4320,16 +4454,27 @@ function renderDiff(hunks) {
|
|
|
4320
4454
|
}
|
|
4321
4455
|
}
|
|
4322
4456
|
function renderEvents(spinner3) {
|
|
4457
|
+
let streamed = false;
|
|
4323
4458
|
return {
|
|
4324
4459
|
onStep() {
|
|
4460
|
+
streamed = false;
|
|
4325
4461
|
spinner3.start(t("ui.thinking"));
|
|
4326
4462
|
},
|
|
4327
4463
|
onUsage(usage2) {
|
|
4328
4464
|
const total = usage2.promptTokens + usage2.completionTokens;
|
|
4329
4465
|
if (total > 0) spinner3.setSuffix(t("ui.tokensShort", { total: fmtTokens(total) }));
|
|
4330
4466
|
},
|
|
4467
|
+
onAssistantDelta(chunk) {
|
|
4468
|
+
spinner3.stop();
|
|
4469
|
+
process.stdout.write(pc8.cyan(chunk));
|
|
4470
|
+
streamed = true;
|
|
4471
|
+
},
|
|
4331
4472
|
onAssistantText(text2) {
|
|
4332
4473
|
spinner3.stop();
|
|
4474
|
+
if (streamed) {
|
|
4475
|
+
process.stdout.write("\n");
|
|
4476
|
+
return;
|
|
4477
|
+
}
|
|
4333
4478
|
if (text2.trim()) console.log(pc8.cyan(text2.trim()));
|
|
4334
4479
|
},
|
|
4335
4480
|
onToolCall(call) {
|