@pencil-agent/nano-pencil 1.11.15 → 1.11.16
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.
|
@@ -222,6 +222,11 @@ export declare class AgentSession {
|
|
|
222
222
|
* Includes built-in commands, extension commands, prompt templates, and skills.
|
|
223
223
|
*/
|
|
224
224
|
getSlashCommands(): SessionSlashCommandDescriptor[];
|
|
225
|
+
/**
|
|
226
|
+
* Try to execute an extension slash command directly.
|
|
227
|
+
* Returns true when a matching extension command was found, even if it failed internally.
|
|
228
|
+
*/
|
|
229
|
+
tryExecuteExtensionCommand(text: string): Promise<boolean>;
|
|
225
230
|
/** Emit an event to all listeners */
|
|
226
231
|
private _emit;
|
|
227
232
|
private _lastAssistantMessage;
|
|
@@ -257,6 +257,13 @@ export class AgentSession {
|
|
|
257
257
|
...skillCommands,
|
|
258
258
|
];
|
|
259
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* Try to execute an extension slash command directly.
|
|
262
|
+
* Returns true when a matching extension command was found, even if it failed internally.
|
|
263
|
+
*/
|
|
264
|
+
async tryExecuteExtensionCommand(text) {
|
|
265
|
+
return this._tryExecuteExtensionCommand(text);
|
|
266
|
+
}
|
|
260
267
|
// =========================================================================
|
|
261
268
|
// Event Subscription
|
|
262
269
|
// =========================================================================
|
|
@@ -39,6 +39,20 @@ function recordLoopEvent(pi, message) {
|
|
|
39
39
|
timestamp: Date.now(),
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
|
+
function publishLoopUpdate(pi, bus, message, type = "info") {
|
|
43
|
+
recordLoopEvent(pi, message);
|
|
44
|
+
notify(bus, message, type);
|
|
45
|
+
pi.sendMessage({
|
|
46
|
+
customType: LOOP_CUSTOM_TYPE,
|
|
47
|
+
content: message,
|
|
48
|
+
display: true,
|
|
49
|
+
details: {
|
|
50
|
+
message,
|
|
51
|
+
level: type,
|
|
52
|
+
timestamp: Date.now(),
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
42
56
|
function notify(bus, message, type = "info") {
|
|
43
57
|
notifyByBus.get(bus)?.(message, type);
|
|
44
58
|
}
|
|
@@ -243,7 +257,7 @@ function dispatchNextIteration(pi, bus, controller) {
|
|
|
243
257
|
}
|
|
244
258
|
const prompt = controller.buildPrompt();
|
|
245
259
|
controller.markDispatched();
|
|
246
|
-
|
|
260
|
+
publishLoopUpdate(pi, bus, `[Loop] Starting iteration ${task.currentIteration} for ${task.id}.`, "info");
|
|
247
261
|
pi.sendUserMessage(prompt, { deliverAs: "followUp" });
|
|
248
262
|
}
|
|
249
263
|
export default async function loopExtension(pi) {
|
|
@@ -294,11 +308,10 @@ export default async function loopExtension(pi) {
|
|
|
294
308
|
const failure = controller.recordFailure("Loop run ended without an assistant message.");
|
|
295
309
|
if (failure.action === "stop") {
|
|
296
310
|
const message = describeTerminalSnapshot(failure.snapshot);
|
|
297
|
-
|
|
298
|
-
notify(bus, message, "warning");
|
|
311
|
+
publishLoopUpdate(pi, bus, message, "warning");
|
|
299
312
|
return;
|
|
300
313
|
}
|
|
301
|
-
|
|
314
|
+
publishLoopUpdate(pi, bus, `[Loop] Iteration failed. Retrying iteration ${failure.task?.currentIteration}.`, "warning");
|
|
302
315
|
dispatchNextIteration(pi, bus, controller);
|
|
303
316
|
return;
|
|
304
317
|
}
|
|
@@ -307,20 +320,18 @@ export default async function loopExtension(pi) {
|
|
|
307
320
|
const failure = controller.recordFailure("Assistant response did not include a valid <loop-state> block.");
|
|
308
321
|
if (failure.action === "stop") {
|
|
309
322
|
const message = describeTerminalSnapshot(failure.snapshot);
|
|
310
|
-
|
|
311
|
-
notify(bus, message, "warning");
|
|
323
|
+
publishLoopUpdate(pi, bus, message, "warning");
|
|
312
324
|
return;
|
|
313
325
|
}
|
|
314
|
-
|
|
326
|
+
publishLoopUpdate(pi, bus, `[Loop] Missing or invalid loop-state block. Retrying iteration ${failure.task?.currentIteration}.`, "warning");
|
|
315
327
|
dispatchNextIteration(pi, bus, controller);
|
|
316
328
|
return;
|
|
317
329
|
}
|
|
318
|
-
|
|
330
|
+
publishLoopUpdate(pi, bus, describeDecision(decision), "info");
|
|
319
331
|
const next = controller.finishTurn(decision);
|
|
320
332
|
if (next.action === "stop") {
|
|
321
333
|
const message = describeTerminalSnapshot(next.snapshot);
|
|
322
|
-
|
|
323
|
-
notify(bus, message, decision.status === "complete" ? "info" : "warning");
|
|
334
|
+
publishLoopUpdate(pi, bus, message, decision.status === "complete" ? "info" : "warning");
|
|
324
335
|
return;
|
|
325
336
|
}
|
|
326
337
|
dispatchNextIteration(pi, bus, controller);
|
|
@@ -335,8 +346,7 @@ export default async function loopExtension(pi) {
|
|
|
335
346
|
if (parsed.type === "help") {
|
|
336
347
|
const reason = parsed.reason === "empty" ? "Missing loop goal." : undefined;
|
|
337
348
|
const help = buildHelp(reason);
|
|
338
|
-
|
|
339
|
-
notify(bus, help, "warning");
|
|
349
|
+
publishLoopUpdate(pi, bus, help, "warning");
|
|
340
350
|
return;
|
|
341
351
|
}
|
|
342
352
|
if (parsed.type === "status") {
|
|
@@ -346,16 +356,14 @@ export default async function loopExtension(pi) {
|
|
|
346
356
|
: state.lastTerminal
|
|
347
357
|
? formatSnapshot(state.lastTerminal)
|
|
348
358
|
: "[Loop] No loop task has been started in this session.";
|
|
349
|
-
|
|
350
|
-
notify(bus, message, "info");
|
|
359
|
+
publishLoopUpdate(pi, bus, message, "info");
|
|
351
360
|
return;
|
|
352
361
|
}
|
|
353
362
|
if (parsed.type === "stop") {
|
|
354
363
|
const activeTask = controller.getActiveTask();
|
|
355
364
|
if (!activeTask) {
|
|
356
365
|
const message = "[Loop] No active loop is running.";
|
|
357
|
-
|
|
358
|
-
notify(bus, message, "warning");
|
|
366
|
+
publishLoopUpdate(pi, bus, message, "warning");
|
|
359
367
|
return;
|
|
360
368
|
}
|
|
361
369
|
controller.stop("Stopped by user request.", "stopped");
|
|
@@ -363,8 +371,7 @@ export default async function loopExtension(pi) {
|
|
|
363
371
|
ctx.abort();
|
|
364
372
|
}
|
|
365
373
|
const message = `[Loop] Stopped loop ${activeTask.id}.`;
|
|
366
|
-
|
|
367
|
-
notify(bus, message, "info");
|
|
374
|
+
publishLoopUpdate(pi, bus, message, "info");
|
|
368
375
|
return;
|
|
369
376
|
}
|
|
370
377
|
try {
|
|
@@ -374,15 +381,13 @@ export default async function loopExtension(pi) {
|
|
|
374
381
|
`Goal: ${task.goal}`,
|
|
375
382
|
`Safety limits: ${task.maxIterations} iterations, ${task.maxConsecutiveFailures} consecutive failures.`,
|
|
376
383
|
].join("\n");
|
|
377
|
-
|
|
378
|
-
notify(bus, `[Loop] Started ${task.id}: ${summarizeGoal(task.goal)}`, "info");
|
|
384
|
+
publishLoopUpdate(pi, bus, message, "info");
|
|
379
385
|
dispatchNextIteration(pi, bus, controller);
|
|
380
386
|
}
|
|
381
387
|
catch (error) {
|
|
382
388
|
const message = error instanceof Error ? error.message : String(error);
|
|
383
389
|
const output = `[Loop] ${message}`;
|
|
384
|
-
|
|
385
|
-
notify(bus, output, "error");
|
|
390
|
+
publishLoopUpdate(pi, bus, output, "error");
|
|
386
391
|
}
|
|
387
392
|
},
|
|
388
393
|
});
|
|
@@ -349,6 +349,49 @@ function formatState(state) {
|
|
|
349
349
|
}
|
|
350
350
|
return lines.join("\n");
|
|
351
351
|
}
|
|
352
|
+
function createProgressReport(state) {
|
|
353
|
+
return {
|
|
354
|
+
id: state.id,
|
|
355
|
+
goal: state.goal,
|
|
356
|
+
mode: state.mode,
|
|
357
|
+
status: state.status,
|
|
358
|
+
startedAt: state.startedAt,
|
|
359
|
+
finishedAt: state.updatedAt,
|
|
360
|
+
plan: state.plan ?? createFallbackPlan(state.goal, state.mode),
|
|
361
|
+
results: [...state.results],
|
|
362
|
+
finalSummary: state.lastWorkerSummary ?? "",
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function buildProgressUpdate(state, message) {
|
|
366
|
+
const lines = [
|
|
367
|
+
`Team run ${state.id} is in stage "${state.stage}".`,
|
|
368
|
+
`Goal: ${summarizeGoal(state.goal, 120)}`,
|
|
369
|
+
];
|
|
370
|
+
if (message) {
|
|
371
|
+
lines.push(`Progress: ${message}`);
|
|
372
|
+
}
|
|
373
|
+
if (state.plan?.summary) {
|
|
374
|
+
lines.push(`Plan: ${state.plan.summary}`);
|
|
375
|
+
}
|
|
376
|
+
if (state.results.length > 0) {
|
|
377
|
+
lines.push(`Completed workers: ${state.results.length}`);
|
|
378
|
+
}
|
|
379
|
+
if (state.lastWorkerSummary) {
|
|
380
|
+
lines.push(`Latest result: ${state.lastWorkerSummary}`);
|
|
381
|
+
}
|
|
382
|
+
if (state.lastError) {
|
|
383
|
+
lines.push(`Last error: ${state.lastError}`);
|
|
384
|
+
}
|
|
385
|
+
return lines.join("\n");
|
|
386
|
+
}
|
|
387
|
+
function emitProgressUpdate(onUpdate, state, message) {
|
|
388
|
+
if (!onUpdate || !state)
|
|
389
|
+
return;
|
|
390
|
+
onUpdate({
|
|
391
|
+
content: [{ type: "text", text: buildProgressUpdate(state, message) }],
|
|
392
|
+
details: createProgressReport(state),
|
|
393
|
+
});
|
|
394
|
+
}
|
|
352
395
|
function formatReport(report) {
|
|
353
396
|
const lines = [
|
|
354
397
|
`[Team] Run ${report.id}`,
|
|
@@ -733,7 +776,7 @@ async function runWorker(pi, ctx, goal, plan, worker, previousResults) {
|
|
|
733
776
|
}
|
|
734
777
|
return parsed;
|
|
735
778
|
}
|
|
736
|
-
async function orchestrateTeamRun(pi, ctx, goal, mode) {
|
|
779
|
+
async function orchestrateTeamRun(pi, ctx, goal, mode, onUpdate) {
|
|
737
780
|
const controller = getController(pi);
|
|
738
781
|
const active = controller.getActive();
|
|
739
782
|
if (!active) {
|
|
@@ -742,16 +785,21 @@ async function orchestrateTeamRun(pi, ctx, goal, mode) {
|
|
|
742
785
|
controller.update({ stage: "planning" });
|
|
743
786
|
persistState(pi, controller.getActive());
|
|
744
787
|
syncRunUi(ctx, controller.getActive());
|
|
788
|
+
emitProgressUpdate(onUpdate, controller.getActive(), "Creating the team plan.");
|
|
745
789
|
const plan = await createPlan(pi, ctx, goal, mode);
|
|
746
790
|
controller.update({ plan, stage: "parallel research" });
|
|
747
791
|
persistState(pi, controller.getActive());
|
|
748
792
|
syncRunUi(ctx, controller.getActive());
|
|
749
|
-
|
|
750
|
-
|
|
793
|
+
emitProgressUpdate(onUpdate, controller.getActive(), `Plan ready. Launching ${plan.researchWorkers.length} research worker${plan.researchWorkers.length === 1 ? "" : "s"}.`);
|
|
794
|
+
const researchResults = await Promise.all(plan.researchWorkers.map(async (worker) => {
|
|
795
|
+
emitProgressUpdate(onUpdate, controller.getActive(), `Started ${worker.role} (${worker.id}).`);
|
|
796
|
+
const result = await runWorker(pi, ctx, goal, plan, worker, []);
|
|
751
797
|
controller.appendResult(result);
|
|
752
798
|
persistState(pi, controller.getActive());
|
|
753
799
|
syncRunUi(ctx, controller.getActive());
|
|
754
|
-
|
|
800
|
+
emitProgressUpdate(onUpdate, controller.getActive(), `${worker.role} finished with status ${result.status}.`);
|
|
801
|
+
return result;
|
|
802
|
+
}));
|
|
755
803
|
const allResults = [...researchResults];
|
|
756
804
|
const executionMode = mode === "research" ? "research_only" : plan.executionMode;
|
|
757
805
|
if (executionMode === "implement_and_review") {
|
|
@@ -765,10 +813,12 @@ async function orchestrateTeamRun(pi, ctx, goal, mode) {
|
|
|
765
813
|
controller.update({ stage: "implementation" });
|
|
766
814
|
persistState(pi, controller.getActive());
|
|
767
815
|
syncRunUi(ctx, controller.getActive());
|
|
816
|
+
emitProgressUpdate(onUpdate, controller.getActive(), "Starting the implementation worker.");
|
|
768
817
|
const implementationResult = await runWorker(pi, ctx, goal, plan, implementationWorker, allResults);
|
|
769
818
|
controller.appendResult(implementationResult);
|
|
770
819
|
persistState(pi, controller.getActive());
|
|
771
820
|
syncRunUi(ctx, controller.getActive());
|
|
821
|
+
emitProgressUpdate(onUpdate, controller.getActive(), `Implementation worker finished with status ${implementationResult.status}.`);
|
|
772
822
|
allResults.push(implementationResult);
|
|
773
823
|
const reviewWorker = {
|
|
774
824
|
id: "reviewer",
|
|
@@ -780,10 +830,12 @@ async function orchestrateTeamRun(pi, ctx, goal, mode) {
|
|
|
780
830
|
controller.update({ stage: "review" });
|
|
781
831
|
persistState(pi, controller.getActive());
|
|
782
832
|
syncRunUi(ctx, controller.getActive());
|
|
833
|
+
emitProgressUpdate(onUpdate, controller.getActive(), "Starting the review worker.");
|
|
783
834
|
const reviewResult = await runWorker(pi, ctx, goal, plan, reviewWorker, allResults);
|
|
784
835
|
controller.appendResult(reviewResult);
|
|
785
836
|
persistState(pi, controller.getActive());
|
|
786
837
|
syncRunUi(ctx, controller.getActive());
|
|
838
|
+
emitProgressUpdate(onUpdate, controller.getActive(), `Review worker finished with status ${reviewResult.status}.`);
|
|
787
839
|
allResults.push(reviewResult);
|
|
788
840
|
}
|
|
789
841
|
const status = allResults.some((result) => result.status === "failed")
|
|
@@ -803,6 +855,14 @@ async function orchestrateTeamRun(pi, ctx, goal, mode) {
|
|
|
803
855
|
finalSummary: "",
|
|
804
856
|
};
|
|
805
857
|
const finalSummary = buildFinalSummary(provisionalReport);
|
|
858
|
+
controller.update({
|
|
859
|
+
stage: "finished",
|
|
860
|
+
status,
|
|
861
|
+
lastWorkerSummary: finalSummary,
|
|
862
|
+
});
|
|
863
|
+
persistState(pi, controller.getActive());
|
|
864
|
+
syncRunUi(ctx, controller.getActive());
|
|
865
|
+
emitProgressUpdate(onUpdate, controller.getActive(), `Team run finished with status ${status}.`);
|
|
806
866
|
const report = controller.finish(status, finalSummary);
|
|
807
867
|
if (!report) {
|
|
808
868
|
return { ...provisionalReport, finalSummary };
|
|
@@ -887,13 +947,14 @@ export default async function teamExtension(pi) {
|
|
|
887
947
|
description: "Delegate a task to a coordinated team of workers, but only when the user explicitly asked for Agent team or multi-agent execution.",
|
|
888
948
|
guidance: "Only use team_run when the user explicitly requested Agent team, multi-agent, or subagent execution. Do not call it proactively.",
|
|
889
949
|
parameters: TEAM_TOOL_PARAMS,
|
|
890
|
-
execute: async (_toolCallId, params, _signal,
|
|
950
|
+
execute: async (_toolCallId, params, _signal, onUpdate, ctx) => {
|
|
891
951
|
ensureExplicitTeamTrigger(ctx, params.goal);
|
|
892
952
|
const controller = getController(pi);
|
|
893
953
|
const active = controller.start(params.goal, params.mode ?? "auto");
|
|
894
954
|
persistState(pi, active);
|
|
955
|
+
emitProgressUpdate(onUpdate, active, "Team run started.");
|
|
895
956
|
try {
|
|
896
|
-
const report = await orchestrateTeamRun(pi, ctx, params.goal, params.mode ?? "auto");
|
|
957
|
+
const report = await orchestrateTeamRun(pi, ctx, params.goal, params.mode ?? "auto", onUpdate);
|
|
897
958
|
report.artifactPath = writeReportArtifact(report, ctx.cwd);
|
|
898
959
|
persistReport(pi, report);
|
|
899
960
|
return {
|
|
@@ -904,6 +965,7 @@ export default async function teamExtension(pi) {
|
|
|
904
965
|
catch (error) {
|
|
905
966
|
const message = error instanceof Error ? error.message : String(error);
|
|
906
967
|
controller.update({ lastError: message, stage: "failed" });
|
|
968
|
+
emitProgressUpdate(onUpdate, controller.getActive(), message);
|
|
907
969
|
const report = controller.finish("failed", message) ?? {
|
|
908
970
|
id: active.id,
|
|
909
971
|
goal: params.goal,
|
|
@@ -66,6 +66,21 @@ function asText(value) {
|
|
|
66
66
|
return String(value);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
+
function toolPayloadToText(value) {
|
|
70
|
+
if (value &&
|
|
71
|
+
typeof value === "object" &&
|
|
72
|
+
"content" in value &&
|
|
73
|
+
Array.isArray(value.content)) {
|
|
74
|
+
const text = value.content
|
|
75
|
+
.filter((block) => block?.type === "text" && typeof block.text === "string")
|
|
76
|
+
.map((block) => block.text)
|
|
77
|
+
.join("\n");
|
|
78
|
+
if (text.trim()) {
|
|
79
|
+
return text;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return asText(value);
|
|
83
|
+
}
|
|
69
84
|
function createSlashCommandsUpdate(session) {
|
|
70
85
|
const commands = session.getSlashCommands();
|
|
71
86
|
const seen = new Set();
|
|
@@ -98,6 +113,14 @@ function getMessageText(message) {
|
|
|
98
113
|
.filter((part) => part.length > 0)
|
|
99
114
|
.join("\n");
|
|
100
115
|
}
|
|
116
|
+
function isVisibleCustomMessage(message) {
|
|
117
|
+
return (typeof message === "object" &&
|
|
118
|
+
message !== null &&
|
|
119
|
+
"role" in message &&
|
|
120
|
+
message.role === "custom" &&
|
|
121
|
+
"display" in message &&
|
|
122
|
+
message.display === true);
|
|
123
|
+
}
|
|
101
124
|
function isMutatingTool(tool) {
|
|
102
125
|
if (MUTATING_TOOL_NAMES.has(tool.name))
|
|
103
126
|
return true;
|
|
@@ -328,6 +351,11 @@ class NanoPencilAgent {
|
|
|
328
351
|
this.mapEventToAcp(params.sessionId, event);
|
|
329
352
|
});
|
|
330
353
|
try {
|
|
354
|
+
const extensionHandled = await this.session.tryExecuteExtensionCommand(userText);
|
|
355
|
+
if (extensionHandled) {
|
|
356
|
+
await this.emitSessionMetadata(sessionState);
|
|
357
|
+
return { stopReason: "end_turn" };
|
|
358
|
+
}
|
|
331
359
|
// @ts-expect-error - source is for internal use
|
|
332
360
|
await this.session.prompt(userText, { source: "acp" });
|
|
333
361
|
await this.emitSessionMetadata(sessionState);
|
|
@@ -814,6 +842,21 @@ class NanoPencilAgent {
|
|
|
814
842
|
}
|
|
815
843
|
break;
|
|
816
844
|
}
|
|
845
|
+
case "message_end":
|
|
846
|
+
if (isVisibleCustomMessage(event.message)) {
|
|
847
|
+
const text = getMessageText(event.message);
|
|
848
|
+
if (text.trim()) {
|
|
849
|
+
void this.connection.sessionUpdate({
|
|
850
|
+
sessionId,
|
|
851
|
+
update: {
|
|
852
|
+
sessionUpdate: "agent_message_chunk",
|
|
853
|
+
content: textToContent(text),
|
|
854
|
+
messageId: createMessageId(),
|
|
855
|
+
},
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
break;
|
|
817
860
|
case "tool_execution_start":
|
|
818
861
|
void this.connection.sessionUpdate({
|
|
819
862
|
sessionId,
|
|
@@ -828,6 +871,26 @@ class NanoPencilAgent {
|
|
|
828
871
|
},
|
|
829
872
|
});
|
|
830
873
|
break;
|
|
874
|
+
case "tool_execution_update":
|
|
875
|
+
void this.connection.sessionUpdate({
|
|
876
|
+
sessionId,
|
|
877
|
+
update: {
|
|
878
|
+
sessionUpdate: "tool_call_update",
|
|
879
|
+
toolCallId: event.toolCallId,
|
|
880
|
+
status: "pending",
|
|
881
|
+
content: [
|
|
882
|
+
{
|
|
883
|
+
type: "content",
|
|
884
|
+
content: {
|
|
885
|
+
type: "text",
|
|
886
|
+
text: toolPayloadToText(event.partialResult),
|
|
887
|
+
},
|
|
888
|
+
},
|
|
889
|
+
],
|
|
890
|
+
rawOutput: event.partialResult,
|
|
891
|
+
},
|
|
892
|
+
});
|
|
893
|
+
break;
|
|
831
894
|
case "tool_execution_end":
|
|
832
895
|
void this.connection.sessionUpdate({
|
|
833
896
|
sessionId,
|
|
@@ -840,7 +903,7 @@ class NanoPencilAgent {
|
|
|
840
903
|
type: "content",
|
|
841
904
|
content: {
|
|
842
905
|
type: "text",
|
|
843
|
-
text:
|
|
906
|
+
text: toolPayloadToText(event.result),
|
|
844
907
|
},
|
|
845
908
|
},
|
|
846
909
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pencil-agent/nano-pencil",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.16",
|
|
4
4
|
"description": "CLI writing agent with read, bash, edit, write tools and session management. Based on pi; supports DashScope Coding Plan. Soul enabled by default for AI personality evolution.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|