@pencil-agent/nano-pencil 1.8.2 → 1.9.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/dist/core/agent-session.js +6 -2
- package/dist/core/extensions/runner.d.ts +2 -0
- package/dist/core/extensions/runner.js +24 -1
- package/dist/core/soul-integration.js +2 -0
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.js +44 -4
- package/dist/pencil-mem-integration.js +11 -6
- package/package.json +1 -1
|
@@ -260,10 +260,14 @@ export class AgentSession {
|
|
|
260
260
|
await this._extensionRunner.emit({ type: "agent_start" });
|
|
261
261
|
}
|
|
262
262
|
else if (event.type === "agent_end") {
|
|
263
|
-
|
|
263
|
+
// Do not block the main turn lifecycle on extension post-processing.
|
|
264
|
+
// Slow hooks (e.g. memory extraction) should not delay the next prompt.
|
|
265
|
+
void this._extensionRunner
|
|
266
|
+
.emit({
|
|
264
267
|
type: "agent_end",
|
|
265
268
|
messages: event.messages,
|
|
266
|
-
})
|
|
269
|
+
})
|
|
270
|
+
.catch(() => { });
|
|
267
271
|
}
|
|
268
272
|
else if (event.type === "turn_start") {
|
|
269
273
|
const extensionEvent = {
|
|
@@ -81,7 +81,9 @@ export declare class ExtensionRunner {
|
|
|
81
81
|
private shutdownHandler;
|
|
82
82
|
private shortcutDiagnostics;
|
|
83
83
|
private commandDiagnostics;
|
|
84
|
+
private readonly beforeAgentStartTimeoutMs;
|
|
84
85
|
constructor(extensions: Extension[], runtime: ExtensionRuntime, cwd: string, sessionManager: SessionManager, modelRegistry: ModelRegistry);
|
|
86
|
+
private withTimeout;
|
|
85
87
|
bindCore(actions: ExtensionActions, contextActions: ExtensionContextActions): void;
|
|
86
88
|
bindCommandContext(actions?: ExtensionCommandContextActions): void;
|
|
87
89
|
setUIContext(uiContext?: ExtensionUIContext): void;
|
|
@@ -104,6 +104,7 @@ export class ExtensionRunner {
|
|
|
104
104
|
shutdownHandler = () => { };
|
|
105
105
|
shortcutDiagnostics = [];
|
|
106
106
|
commandDiagnostics = [];
|
|
107
|
+
beforeAgentStartTimeoutMs = 1500;
|
|
107
108
|
constructor(extensions, runtime, cwd, sessionManager, modelRegistry) {
|
|
108
109
|
this.extensions = extensions;
|
|
109
110
|
this.runtime = runtime;
|
|
@@ -112,6 +113,20 @@ export class ExtensionRunner {
|
|
|
112
113
|
this.sessionManager = sessionManager;
|
|
113
114
|
this.modelRegistry = modelRegistry;
|
|
114
115
|
}
|
|
116
|
+
async withTimeout(promise, timeoutMs) {
|
|
117
|
+
return new Promise((resolve) => {
|
|
118
|
+
const timer = setTimeout(() => resolve(undefined), timeoutMs);
|
|
119
|
+
promise
|
|
120
|
+
.then((value) => {
|
|
121
|
+
clearTimeout(timer);
|
|
122
|
+
resolve(value);
|
|
123
|
+
})
|
|
124
|
+
.catch(() => {
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
resolve(undefined);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
115
130
|
bindCore(actions, contextActions) {
|
|
116
131
|
// Copy actions into the shared runtime (all extension APIs reference this)
|
|
117
132
|
this.runtime.sendMessage = actions.sendMessage;
|
|
@@ -540,7 +555,15 @@ export class ExtensionRunner {
|
|
|
540
555
|
images,
|
|
541
556
|
systemPrompt: currentSystemPrompt,
|
|
542
557
|
};
|
|
543
|
-
const handlerResult = await handler(event, ctx);
|
|
558
|
+
const handlerResult = await this.withTimeout(handler(event, ctx), this.beforeAgentStartTimeoutMs);
|
|
559
|
+
if (handlerResult === undefined) {
|
|
560
|
+
this.emitError({
|
|
561
|
+
extensionPath: ext.path,
|
|
562
|
+
event: "before_agent_start",
|
|
563
|
+
error: `handler timed out after ${this.beforeAgentStartTimeoutMs}ms`,
|
|
564
|
+
});
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
544
567
|
if (handlerResult) {
|
|
545
568
|
const result = handlerResult;
|
|
546
569
|
if (result.message) {
|
|
@@ -8,7 +8,9 @@ import { getAgentDir } from "../config.js";
|
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { dirname } from "node:path";
|
|
11
|
+
import { createRequire } from "node:module";
|
|
11
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
12
14
|
// Try to load from bundled packages first (dist/packages/nanosoul)
|
|
13
15
|
const BUNDLED_SOUL = join(__dirname, "packages", "nanosoul");
|
|
14
16
|
/**
|
|
@@ -14,6 +14,7 @@ import { parseSkillBlock, } from "../../core/agent-session.js";
|
|
|
14
14
|
import { FooterDataProvider, } from "../../core/footer-data-provider.js";
|
|
15
15
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
16
16
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
17
|
+
import { listMCPServers, setMCPServerEnabled } from "../../core/mcp/mcp-config.js";
|
|
17
18
|
import { resolveModelScope } from "../../core/model-resolver.js";
|
|
18
19
|
import { SessionManager, } from "../../core/session-manager.js";
|
|
19
20
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
@@ -1548,6 +1549,11 @@ export class InteractiveMode {
|
|
|
1548
1549
|
await this.handleModelCommand(searchTerm);
|
|
1549
1550
|
return;
|
|
1550
1551
|
}
|
|
1552
|
+
if (text === "/mcp" || text.startsWith("/mcp ")) {
|
|
1553
|
+
await this.handleMcpCommand(text);
|
|
1554
|
+
this.editor.setText("");
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1551
1557
|
if (text.startsWith("/export")) {
|
|
1552
1558
|
await this.handleExportCommand(text);
|
|
1553
1559
|
this.editor.setText("");
|
|
@@ -1780,10 +1786,7 @@ export class InteractiveMode {
|
|
|
1780
1786
|
this.ui.requestRender();
|
|
1781
1787
|
}
|
|
1782
1788
|
else if (event.message.role === "user") {
|
|
1783
|
-
|
|
1784
|
-
if (userText &&
|
|
1785
|
-
this.optimisticUserMessages.length > 0 &&
|
|
1786
|
-
this.optimisticUserMessages[0] === userText) {
|
|
1789
|
+
if (this.optimisticUserMessages.length > 0) {
|
|
1787
1790
|
this.optimisticUserMessages.shift();
|
|
1788
1791
|
this.updatePendingMessagesDisplay();
|
|
1789
1792
|
this.ui.requestRender();
|
|
@@ -3991,6 +3994,43 @@ export class InteractiveMode {
|
|
|
3991
3994
|
this.chatContainer.addChild(new Text(lines.join("\n"), 1, 0));
|
|
3992
3995
|
this.ui.requestRender();
|
|
3993
3996
|
}
|
|
3997
|
+
async handleMcpCommand(text) {
|
|
3998
|
+
const parts = text.trim().split(/\s+/);
|
|
3999
|
+
const action = (parts[1] || "list").toLowerCase();
|
|
4000
|
+
const target = parts[2];
|
|
4001
|
+
if (action === "list") {
|
|
4002
|
+
const servers = listMCPServers();
|
|
4003
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
4004
|
+
if (servers.length === 0) {
|
|
4005
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", "No MCP servers configured."), 1, 0));
|
|
4006
|
+
}
|
|
4007
|
+
else {
|
|
4008
|
+
const lines = [
|
|
4009
|
+
theme.bold("MCP Servers"),
|
|
4010
|
+
"",
|
|
4011
|
+
...servers.map((s) => {
|
|
4012
|
+
const status = s.enabled === false ? "disabled" : "enabled";
|
|
4013
|
+
return `- ${s.id} (${s.name}) [${status}]`;
|
|
4014
|
+
}),
|
|
4015
|
+
"",
|
|
4016
|
+
theme.fg("dim", "Use: /mcp enable <id> or /mcp disable <id>"),
|
|
4017
|
+
];
|
|
4018
|
+
this.chatContainer.addChild(new Text(lines.join("\n"), 1, 0));
|
|
4019
|
+
}
|
|
4020
|
+
this.ui.requestRender();
|
|
4021
|
+
return;
|
|
4022
|
+
}
|
|
4023
|
+
if ((action === "enable" || action === "disable") && target) {
|
|
4024
|
+
setMCPServerEnabled(target, action === "enable");
|
|
4025
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
4026
|
+
this.chatContainer.addChild(new Text(`${target} ${action === "enable" ? "enabled" : "disabled"}. Run /reload to apply changes.`, 1, 0));
|
|
4027
|
+
this.ui.requestRender();
|
|
4028
|
+
return;
|
|
4029
|
+
}
|
|
4030
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
4031
|
+
this.chatContainer.addChild(new Text("Usage: /mcp [list|enable <id>|disable <id>]", 1, 0));
|
|
4032
|
+
this.ui.requestRender();
|
|
4033
|
+
}
|
|
3994
4034
|
async handleUpdateCommand() {
|
|
3995
4035
|
this.chatContainer.addChild(new Spacer(1));
|
|
3996
4036
|
this.chatContainer.addChild(new Text(theme.fg("accent", "🔍 Checking for updates..."), 1, 0));
|
|
@@ -12,7 +12,8 @@ import { fileURLToPath } from "node:url";
|
|
|
12
12
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
const require = createRequire(import.meta.url);
|
|
14
14
|
/** 内置扩展路径 */
|
|
15
|
-
const
|
|
15
|
+
const BUNDLED_NANOMEM_EXTENSION_PACKAGES = join(__dirname, "packages", "nanomem", "extension.js");
|
|
16
|
+
const BUNDLED_NANOMEM_EXTENSION_LEGACY = join(__dirname, "extensions", "nanomem", "extension.js");
|
|
16
17
|
const BUNDLED_SIMPLIFY_EXTENSION = join(__dirname, "extensions", "simplify", "index.js");
|
|
17
18
|
/** 从当前模块位置向上查找包根(含 package.json 且 name 为 nano-pencil 相关) */
|
|
18
19
|
function findPackageRoot(startDir) {
|
|
@@ -40,19 +41,23 @@ function findPackageRoot(startDir) {
|
|
|
40
41
|
export function getNanopencilDefaultExtensionPaths() {
|
|
41
42
|
const paths = [];
|
|
42
43
|
// === NanoMem 扩展 ===
|
|
43
|
-
// 1) 优先使用 build
|
|
44
|
-
if (existsSync(
|
|
45
|
-
paths.push(
|
|
44
|
+
// 1) 优先使用 build 时打包到 dist/packages 的扩展
|
|
45
|
+
if (existsSync(BUNDLED_NANOMEM_EXTENSION_PACKAGES)) {
|
|
46
|
+
paths.push(BUNDLED_NANOMEM_EXTENSION_PACKAGES);
|
|
47
|
+
}
|
|
48
|
+
else if (existsSync(BUNDLED_NANOMEM_EXTENSION_LEGACY)) {
|
|
49
|
+
// 2) 兼容旧版 dist/extensions 路径
|
|
50
|
+
paths.push(BUNDLED_NANOMEM_EXTENSION_LEGACY);
|
|
46
51
|
}
|
|
47
52
|
else {
|
|
48
|
-
//
|
|
53
|
+
// 3) require.resolve:开发/本地安装时 node_modules 中的 nanomem
|
|
49
54
|
try {
|
|
50
55
|
const extPath = require.resolve("nanomem/extension.js");
|
|
51
56
|
if (existsSync(extPath))
|
|
52
57
|
paths.push(extPath);
|
|
53
58
|
}
|
|
54
59
|
catch {
|
|
55
|
-
//
|
|
60
|
+
// 4) 按包根 + node_modules/nanomem/dist/extension.js 查找
|
|
56
61
|
const packageRoot = findPackageRoot(__dirname);
|
|
57
62
|
if (packageRoot) {
|
|
58
63
|
const candidate = join(packageRoot, "node_modules", "nanomem", "dist", "extension.js");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pencil-agent/nano-pencil",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
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": {
|