@pencil-agent/nano-pencil 1.14.2 → 1.14.4
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/build-meta.json +3 -3
- package/dist/builtin-extensions.d.ts +4 -0
- package/dist/builtin-extensions.js +16 -16
- package/dist/core/config/settings-manager.d.ts +8 -1
- package/dist/core/config/settings-manager.js +9 -0
- package/dist/core/extensions/runner.d.ts +70 -4
- package/dist/core/extensions/runner.js +188 -8
- package/dist/core/extensions/types.d.ts +8 -1
- package/dist/core/i18n/slash-commands.d.ts +12 -0
- package/dist/core/i18n/slash-commands.js +16 -4
- package/dist/core/i18n/slash-commands.zh.d.ts +12 -0
- package/dist/core/i18n/slash-commands.zh.js +16 -4
- package/dist/core/runtime/agent-session.d.ts +5 -0
- package/dist/core/runtime/agent-session.js +85 -27
- package/dist/core/runtime/extension-core-bindings.d.ts +3 -3
- package/dist/core/runtime/extension-core-bindings.js +73 -20
- package/dist/core/runtime/retry-coordinator.d.ts +10 -1
- package/dist/core/runtime/retry-coordinator.js +20 -4
- package/dist/core/runtime/sdk.js +2 -1
- package/dist/core/runtime/slash-command-catalog.d.ts +2 -1
- package/dist/core/runtime/slash-command-catalog.js +9 -1
- package/dist/core/slash-commands.d.ts +12 -1
- package/dist/core/slash-commands.js +81 -32
- package/dist/core/telemetry/batching-dispatcher.d.ts +41 -0
- package/dist/core/telemetry/batching-dispatcher.js +89 -0
- package/dist/core/telemetry/build-meta.d.ts +12 -0
- package/dist/core/telemetry/build-meta.js +57 -0
- package/dist/core/telemetry/caller-context.d.ts +32 -0
- package/dist/core/telemetry/caller-context.js +19 -0
- package/dist/core/telemetry/credentials.d.ts +27 -0
- package/dist/core/telemetry/credentials.js +87 -0
- package/dist/core/telemetry/ext-events.d.ts +89 -0
- package/dist/core/telemetry/ext-events.js +189 -0
- package/dist/core/telemetry/index.d.ts +13 -0
- package/dist/core/telemetry/index.js +6 -0
- package/dist/core/telemetry/insforge-base.d.ts +37 -0
- package/dist/core/telemetry/insforge-base.js +160 -0
- package/dist/core/telemetry/types.d.ts +33 -0
- package/dist/core/telemetry/types.js +7 -0
- package/dist/extensions/defaults/browser/index.js +14 -6
- package/dist/extensions/defaults/btw/index.js +2 -2
- package/dist/extensions/defaults/debug/index.js +38 -3
- package/dist/extensions/defaults/diagnostics/index.js +12 -0
- package/dist/extensions/defaults/grub/grub-parser.d.ts +15 -1
- package/dist/extensions/defaults/grub/grub-parser.js +31 -1
- package/dist/extensions/defaults/grub/index.d.ts +1 -1
- package/dist/extensions/defaults/grub/index.js +4 -3
- package/dist/extensions/defaults/interview/index.js +2 -2
- package/dist/extensions/defaults/link-world/index.js +14 -6
- package/dist/extensions/defaults/loop/cron/cron-scheduler.js +19 -0
- package/dist/extensions/defaults/loop/index.js +35 -0
- package/dist/extensions/defaults/mcp/index.js +18 -0
- package/dist/extensions/defaults/plan/index.js +29 -11
- package/dist/extensions/defaults/presence/index.d.ts +12 -2
- package/dist/extensions/defaults/presence/index.js +77 -23
- package/dist/extensions/defaults/presence/presence-memory.d.ts +2 -1
- package/dist/extensions/defaults/presence/presence-memory.js +37 -1
- package/dist/extensions/defaults/recap/index.js +12 -0
- package/dist/extensions/defaults/sal/eval/insforge-sink.d.ts +6 -17
- package/dist/extensions/defaults/sal/eval/insforge-sink.js +40 -183
- package/dist/extensions/defaults/sal/index.js +31 -8
- package/dist/extensions/defaults/sal/sal-config.d.ts +5 -0
- package/dist/extensions/defaults/sal/sal-config.js +15 -82
- package/dist/extensions/defaults/security-audit/index.js +141 -83
- package/dist/extensions/defaults/subagent/index.js +29 -5
- package/dist/extensions/defaults/team/index.js +10 -9
- package/dist/extensions/defaults/team/team-parser.d.ts +18 -0
- package/dist/extensions/defaults/team/team-parser.js +91 -3
- package/dist/extensions/defaults/team/team-ui.js +4 -14
- package/dist/extensions/defaults/token-save/index.js +14 -1
- package/dist/extensions/optional/export-html/index.js +19 -5
- package/dist/extensions/optional/simplify/index.js +11 -5
- package/dist/modes/interactive/interactive-mode.d.ts +2 -1
- package/dist/modes/interactive/interactive-mode.js +68 -19
- package/dist/modes/interactive/slash-command-arguments.d.ts +16 -0
- package/dist/modes/interactive/slash-command-arguments.js +97 -0
- package/dist/modes/rpc/rpc-mode.d.ts +3 -0
- package/dist/modes/rpc/rpc-mode.js +40 -31
- package/dist/modes/rpc/rpc-types.d.ts +3 -0
- package/dist/node_modules/@pencil-agent/agent-core/agent.d.ts +15 -1
- package/dist/node_modules/@pencil-agent/agent-core/agent.js +13 -1
- package/dist/node_modules/@pencil-agent/agent-core/index.d.ts +2 -1
- package/dist/node_modules/@pencil-agent/agent-core/index.js +2 -1
- package/dist/node_modules/@pencil-agent/agent-core/structured-adaptive-agent-loop.d.ts +1 -1
- package/dist/node_modules/@pencil-agent/agent-core/structured-adaptive-agent-loop.js +293 -20
- package/dist/node_modules/@pencil-agent/agent-core/structured-adaptive-streaming-tool-executor.d.ts +33 -0
- package/dist/node_modules/@pencil-agent/agent-core/structured-adaptive-streaming-tool-executor.js +189 -0
- package/dist/node_modules/@pencil-agent/agent-core/structured-adaptive-tool-orchestration.d.ts +9 -0
- package/dist/node_modules/@pencil-agent/agent-core/structured-adaptive-tool-orchestration.js +30 -5
- package/dist/node_modules/@pencil-agent/agent-core/types.d.ts +90 -3
- package/dist/node_modules/@pencil-agent/agent-core/types.js +1 -1
- package/dist/node_modules/@pencil-agent/ai/models.generated.d.ts +0 -17
- package/dist/node_modules/@pencil-agent/ai/models.generated.js +29 -46
- package/dist/node_modules/@pencil-agent/tui/autocomplete.d.ts +8 -1
- package/dist/node_modules/@pencil-agent/tui/autocomplete.js +18 -2
- package/dist/node_modules/@pencil-agent/tui/index.d.ts +1 -1
- package/dist/node_modules/@pencil-agent/tui/tui.d.ts +8 -3
- package/dist/node_modules/@pencil-agent/tui/tui.js +68 -33
- package/dist/packages/mem-core/extension.js +154 -83
- package/dist/packages/mem-core/full-insights-sections.d.ts +48 -0
- package/dist/packages/mem-core/full-insights-sections.js +231 -0
- package/dist/packages/mem-core/full-insights.d.ts +1 -1
- package/dist/packages/mem-core/full-insights.js +102 -42
- package/package.json +2 -2
|
@@ -5,16 +5,27 @@
|
|
|
5
5
|
* [HERE]: core/i18n/slash-commands.zh.ts - Chinese slash command translations
|
|
6
6
|
*/
|
|
7
7
|
export const slashCommands = {
|
|
8
|
+
categories: {
|
|
9
|
+
core: "核心",
|
|
10
|
+
model: "模型",
|
|
11
|
+
memory: "记忆",
|
|
12
|
+
session: "会话",
|
|
13
|
+
workflow: "工作流",
|
|
14
|
+
agents: "Agent",
|
|
15
|
+
tools: "工具",
|
|
16
|
+
admin: "管理",
|
|
17
|
+
},
|
|
8
18
|
settings: "打开设置菜单",
|
|
9
19
|
model: "选择模型(打开选择器界面)",
|
|
10
|
-
|
|
11
|
-
"
|
|
20
|
+
thinking: "选择当前模型的推理深度",
|
|
21
|
+
"agent-loop": "选择 agent 推进任务的方式",
|
|
22
|
+
"scoped-models": "选择快速切换里出现的模型",
|
|
12
23
|
apikey: "更新当前提供商的 API 密钥",
|
|
13
24
|
mcp: "管理 MCP 服务器(列出、启用、禁用)",
|
|
14
25
|
soul: "显示 AI 人格和统计(灵魂)",
|
|
15
26
|
persona: "切换 AI 人格/个性包",
|
|
16
27
|
memory: "显示项目记忆和知识(纳米记忆)",
|
|
17
|
-
dream: "
|
|
28
|
+
dream: "刷新长期项目记忆(纳米记忆)",
|
|
18
29
|
export: "将会话导出为 HTML 文件",
|
|
19
30
|
share: "将会话分享为保密的 GitHub gist",
|
|
20
31
|
copy: "复制上一条 AI 消息到剪贴板",
|
|
@@ -24,6 +35,7 @@ export const slashCommands = {
|
|
|
24
35
|
usage: "显示 token 使用量和费用统计",
|
|
25
36
|
changelog: "显示更新日志条目",
|
|
26
37
|
hotkeys: "显示所有键盘快捷键",
|
|
38
|
+
resources: "显示已加载的扩展、提示、技能和主题",
|
|
27
39
|
fork: "从上一条消息创建新分支",
|
|
28
40
|
tree: "导航会话树(切换分支)",
|
|
29
41
|
login: "通过 OAuth 提供商登录",
|
|
@@ -34,7 +46,7 @@ export const slashCommands = {
|
|
|
34
46
|
compact: "手动压缩会话上下文",
|
|
35
47
|
resume: "恢复其他会话",
|
|
36
48
|
reload: "重新加载扩展、技能、提示和主题",
|
|
37
|
-
"link-world": "
|
|
49
|
+
"link-world": "设置联网访问工具",
|
|
38
50
|
quit: "退出 NanoPencil",
|
|
39
51
|
language: "切换语言(English/中文)",
|
|
40
52
|
};
|
|
@@ -342,6 +342,10 @@ export declare class AgentSession {
|
|
|
342
342
|
prompt(text: string, options?: PromptOptions): Promise<void>;
|
|
343
343
|
/**
|
|
344
344
|
* Try to execute an extension command. Returns true if command was found and executed.
|
|
345
|
+
*
|
|
346
|
+
* Delegates to ExtensionRunner.invokeCommand() so command dispatch, error
|
|
347
|
+
* routing (emitError), and telemetry (ext_command_events) all happen in one
|
|
348
|
+
* place rather than being scattered across modes.
|
|
345
349
|
*/
|
|
346
350
|
private _tryExecuteExtensionCommand;
|
|
347
351
|
/**
|
|
@@ -516,6 +520,7 @@ export declare class AgentSession {
|
|
|
516
520
|
* @param skipAbortedCheck If false, include aborted messages (for pre-prompt check). Default: true
|
|
517
521
|
*/
|
|
518
522
|
private _checkCompaction;
|
|
523
|
+
private _recoverModelErrorInLoop;
|
|
519
524
|
/**
|
|
520
525
|
* Internal: Run auto-compaction with events.
|
|
521
526
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [WHO]: AgentSession class, session lifecycle, event emission
|
|
2
|
+
* [WHO]: AgentSession class, session lifecycle, event emission, in-loop context-overflow recovery adapter
|
|
3
3
|
* [FROM]: Depends on agent-core, ai, core/tools/*, core/session/*, core/config/*
|
|
4
4
|
* [TO]: Consumed by core/index.ts, core/runtime/sdk.ts, modes/interactive/interactive-mode.ts, modes/print-mode.ts, modes/rpc/rpc-mode.ts, modes/acp/acp-mode.ts, modes/rpc/rpc-types.ts, modes/rpc/rpc-client.ts, modes/interactive/components/footer.ts, modes/interactive/components/skill-invocation-message.ts
|
|
5
5
|
* [HERE]: Central runtime hub; all modes delegate to this class
|
|
@@ -29,6 +29,7 @@ import { CompactionCoordinator } from "../session/compaction/compaction-coordina
|
|
|
29
29
|
import { ToolOrchestrator } from "../tools/orchestrator.js";
|
|
30
30
|
import { ModelSwitcher } from "../model/index.js";
|
|
31
31
|
import { DEFAULT_THINKING_LEVEL } from "../defaults.js";
|
|
32
|
+
import { createExtensionTelemetrySink } from "../telemetry/index.js";
|
|
32
33
|
import { exportSessionToHtml, } from "../export-html/index.js";
|
|
33
34
|
import { createToolHtmlRenderer } from "../export-html/tool-renderer.js";
|
|
34
35
|
import { ExtensionRunner, wrapRegisteredTools, wrapToolsWithExtensions, } from "../extensions/index.js";
|
|
@@ -158,6 +159,7 @@ export class AgentSession {
|
|
|
158
159
|
this._soulManager = config.soulManager;
|
|
159
160
|
this._initialActiveToolNames = config.initialActiveToolNames;
|
|
160
161
|
this._baseToolsOverride = config.baseToolsOverride;
|
|
162
|
+
this.agent.setModelErrorRecovery((event) => this._recoverModelErrorInLoop(event));
|
|
161
163
|
// Always subscribe to agent events for internal handling
|
|
162
164
|
// (session persistence, extensions, auto-compaction, retry logic)
|
|
163
165
|
this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
|
|
@@ -844,32 +846,22 @@ export class AgentSession {
|
|
|
844
846
|
}
|
|
845
847
|
/**
|
|
846
848
|
* Try to execute an extension command. Returns true if command was found and executed.
|
|
849
|
+
*
|
|
850
|
+
* Delegates to ExtensionRunner.invokeCommand() so command dispatch, error
|
|
851
|
+
* routing (emitError), and telemetry (ext_command_events) all happen in one
|
|
852
|
+
* place rather than being scattered across modes.
|
|
847
853
|
*/
|
|
848
854
|
async _tryExecuteExtensionCommand(text) {
|
|
849
855
|
if (!this._extensionRunner)
|
|
850
856
|
return false;
|
|
851
|
-
// Parse command name and args
|
|
852
857
|
const spaceIndex = text.indexOf(" ");
|
|
853
858
|
const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
|
|
854
859
|
const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1);
|
|
855
|
-
const command = this._extensionRunner.getCommand(commandName);
|
|
856
|
-
if (!command)
|
|
857
|
-
return false;
|
|
858
|
-
// Get command context from extension runner (includes session control methods)
|
|
859
860
|
const ctx = this._extensionRunner.createCommandContext();
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
catch (err) {
|
|
865
|
-
// Emit error via extension runner
|
|
866
|
-
this._extensionRunner.emitError({
|
|
867
|
-
extensionPath: `command:${commandName}`,
|
|
868
|
-
event: "command",
|
|
869
|
-
error: err instanceof Error ? err.message : String(err),
|
|
870
|
-
});
|
|
871
|
-
return true;
|
|
872
|
-
}
|
|
861
|
+
const result = await this._extensionRunner.invokeCommand(commandName, args, ctx, {
|
|
862
|
+
sessionId: this.sessionManager.getSessionId(),
|
|
863
|
+
});
|
|
864
|
+
return result.found;
|
|
873
865
|
}
|
|
874
866
|
/**
|
|
875
867
|
* Expand skill commands (/skill:name args) to their full content.
|
|
@@ -1585,12 +1577,72 @@ export class AgentSession {
|
|
|
1585
1577
|
await this._runAutoCompaction("threshold", false);
|
|
1586
1578
|
}
|
|
1587
1579
|
}
|
|
1580
|
+
async _recoverModelErrorInLoop(event) {
|
|
1581
|
+
const settings = this.settingsManager.getCompactionSettings();
|
|
1582
|
+
if (event.message.role !== "assistant")
|
|
1583
|
+
return { action: "stop" };
|
|
1584
|
+
const assistantMessage = event.message;
|
|
1585
|
+
if (event.errorSubtype !== "context_overflow") {
|
|
1586
|
+
if (!this._retryCoordinator.isRetryableError(assistantMessage)) {
|
|
1587
|
+
return { action: "stop" };
|
|
1588
|
+
}
|
|
1589
|
+
const shouldRetry = await this._retryCoordinator.handleErrorInLoop(assistantMessage);
|
|
1590
|
+
if (!shouldRetry)
|
|
1591
|
+
return { action: "stop" };
|
|
1592
|
+
const messages = this.agent.state.messages;
|
|
1593
|
+
const retryMessages = messages.at(-1)?.role === "assistant" ? messages.slice(0, -1) : messages;
|
|
1594
|
+
this.agent.replaceMessages(retryMessages);
|
|
1595
|
+
return {
|
|
1596
|
+
action: "retry",
|
|
1597
|
+
messages: retryMessages,
|
|
1598
|
+
transition: {
|
|
1599
|
+
reason: "model_error_recovery",
|
|
1600
|
+
subtype: event.errorSubtype,
|
|
1601
|
+
attempt: event.attempt,
|
|
1602
|
+
},
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
if (!settings.enabled)
|
|
1606
|
+
return { action: "stop" };
|
|
1607
|
+
const contextWindow = this.model?.contextWindow ?? 0;
|
|
1608
|
+
const sameModel = this.model &&
|
|
1609
|
+
assistantMessage.provider === this.model.provider &&
|
|
1610
|
+
assistantMessage.model === this.model.id;
|
|
1611
|
+
if (!sameModel || !isContextOverflow(assistantMessage, contextWindow)) {
|
|
1612
|
+
return { action: "stop" };
|
|
1613
|
+
}
|
|
1614
|
+
const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
|
|
1615
|
+
const errorIsFromBeforeCompaction = compactionEntry !== null &&
|
|
1616
|
+
assistantMessage.timestamp < new Date(compactionEntry.timestamp).getTime();
|
|
1617
|
+
if (errorIsFromBeforeCompaction)
|
|
1618
|
+
return { action: "stop" };
|
|
1619
|
+
const messages = this.agent.state.messages;
|
|
1620
|
+
if (messages.length > 0 &&
|
|
1621
|
+
messages[messages.length - 1].role === "assistant") {
|
|
1622
|
+
this.agent.replaceMessages(messages.slice(0, -1));
|
|
1623
|
+
}
|
|
1624
|
+
const recoveredMessages = await this._runAutoCompaction("overflow", true, {
|
|
1625
|
+
triggerContinue: false,
|
|
1626
|
+
});
|
|
1627
|
+
if (!recoveredMessages)
|
|
1628
|
+
return { action: "stop" };
|
|
1629
|
+
return {
|
|
1630
|
+
action: "retry",
|
|
1631
|
+
messages: recoveredMessages,
|
|
1632
|
+
transition: {
|
|
1633
|
+
reason: "model_error_recovery",
|
|
1634
|
+
subtype: event.errorSubtype,
|
|
1635
|
+
attempt: event.attempt,
|
|
1636
|
+
},
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1588
1639
|
/**
|
|
1589
1640
|
* Internal: Run auto-compaction with events.
|
|
1590
1641
|
*/
|
|
1591
|
-
async _runAutoCompaction(reason, willRetry) {
|
|
1642
|
+
async _runAutoCompaction(reason, willRetry, options) {
|
|
1592
1643
|
this._logger.info("Auto-compaction triggered", { reason, willRetry });
|
|
1593
1644
|
const settings = this.settingsManager.getCompactionSettings();
|
|
1645
|
+
const triggerContinue = options?.triggerContinue ?? true;
|
|
1594
1646
|
this._emit({ type: "auto_compaction_start", reason });
|
|
1595
1647
|
this._autoCompactionAbortController = new AbortController();
|
|
1596
1648
|
try {
|
|
@@ -1601,7 +1653,7 @@ export class AgentSession {
|
|
|
1601
1653
|
aborted: false,
|
|
1602
1654
|
willRetry: false,
|
|
1603
1655
|
});
|
|
1604
|
-
return;
|
|
1656
|
+
return undefined;
|
|
1605
1657
|
}
|
|
1606
1658
|
const apiKey = await this._modelRegistry.getApiKey(this.model);
|
|
1607
1659
|
if (!apiKey) {
|
|
@@ -1611,7 +1663,7 @@ export class AgentSession {
|
|
|
1611
1663
|
aborted: false,
|
|
1612
1664
|
willRetry: false,
|
|
1613
1665
|
});
|
|
1614
|
-
return;
|
|
1666
|
+
return undefined;
|
|
1615
1667
|
}
|
|
1616
1668
|
const pathEntries = this.sessionManager.getBranch();
|
|
1617
1669
|
const preparation = prepareCompaction(pathEntries, settings);
|
|
@@ -1622,7 +1674,7 @@ export class AgentSession {
|
|
|
1622
1674
|
aborted: false,
|
|
1623
1675
|
willRetry: false,
|
|
1624
1676
|
});
|
|
1625
|
-
return;
|
|
1677
|
+
return undefined;
|
|
1626
1678
|
}
|
|
1627
1679
|
let extensionCompaction;
|
|
1628
1680
|
let fromExtension = false;
|
|
@@ -1641,7 +1693,7 @@ export class AgentSession {
|
|
|
1641
1693
|
aborted: true,
|
|
1642
1694
|
willRetry: false,
|
|
1643
1695
|
});
|
|
1644
|
-
return;
|
|
1696
|
+
return undefined;
|
|
1645
1697
|
}
|
|
1646
1698
|
if (extensionResult?.compaction) {
|
|
1647
1699
|
extensionCompaction = extensionResult.compaction;
|
|
@@ -1674,7 +1726,7 @@ export class AgentSession {
|
|
|
1674
1726
|
aborted: true,
|
|
1675
1727
|
willRetry: false,
|
|
1676
1728
|
});
|
|
1677
|
-
return;
|
|
1729
|
+
return undefined;
|
|
1678
1730
|
}
|
|
1679
1731
|
this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
|
|
1680
1732
|
const newEntries = this.sessionManager.getEntries();
|
|
@@ -1701,7 +1753,7 @@ export class AgentSession {
|
|
|
1701
1753
|
aborted: false,
|
|
1702
1754
|
willRetry,
|
|
1703
1755
|
});
|
|
1704
|
-
if (willRetry) {
|
|
1756
|
+
if (willRetry && triggerContinue) {
|
|
1705
1757
|
const messages = this.agent.state.messages;
|
|
1706
1758
|
const lastMsg = messages[messages.length - 1];
|
|
1707
1759
|
if (lastMsg?.role === "assistant" &&
|
|
@@ -1712,13 +1764,14 @@ export class AgentSession {
|
|
|
1712
1764
|
this.agent.continue().catch(() => { });
|
|
1713
1765
|
}, 100);
|
|
1714
1766
|
}
|
|
1715
|
-
else if (this.agent.hasQueuedMessages()) {
|
|
1767
|
+
else if (!willRetry && this.agent.hasQueuedMessages()) {
|
|
1716
1768
|
// Auto-compaction can complete while follow-up/steering/custom messages are waiting.
|
|
1717
1769
|
// Kick the loop so queued messages are actually delivered.
|
|
1718
1770
|
setTimeout(() => {
|
|
1719
1771
|
this.agent.continue().catch(() => { });
|
|
1720
1772
|
}, 100);
|
|
1721
1773
|
}
|
|
1774
|
+
return sessionContext.messages;
|
|
1722
1775
|
}
|
|
1723
1776
|
catch (error) {
|
|
1724
1777
|
const errorMessage = error instanceof Error ? error.message : "compaction failed";
|
|
@@ -1731,6 +1784,7 @@ export class AgentSession {
|
|
|
1731
1784
|
? `Context overflow recovery failed: ${errorMessage}`
|
|
1732
1785
|
: `Auto-compaction failed: ${errorMessage}`,
|
|
1733
1786
|
});
|
|
1787
|
+
return undefined;
|
|
1734
1788
|
}
|
|
1735
1789
|
finally {
|
|
1736
1790
|
this._autoCompactionAbortController = undefined;
|
|
@@ -1881,6 +1935,10 @@ export class AgentSession {
|
|
|
1881
1935
|
if (this._extensionRunner) {
|
|
1882
1936
|
this._bindExtensionCore(this._extensionRunner);
|
|
1883
1937
|
this._applyExtensionBindings(this._extensionRunner);
|
|
1938
|
+
// P1 extension telemetry: every /command invocation writes one row to
|
|
1939
|
+
// ext_command_events. Returns a noop sink when no insforge credentials
|
|
1940
|
+
// are configured, so this is zero-cost for users not opted in.
|
|
1941
|
+
this._extensionRunner.setTelemetrySink(createExtensionTelemetrySink({ workspaceRoot: this._cwd }));
|
|
1884
1942
|
}
|
|
1885
1943
|
const registeredTools = this._extensionRunner?.getAllRegisteredTools() ?? [];
|
|
1886
1944
|
const allCustomTools = [
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [WHO]: Provides bindExtensionCore()
|
|
3
|
-
* [FROM]: Depends on ExtensionRunner, model/session/resource abstractions, slash command metadata
|
|
2
|
+
* [WHO]: Provides bindExtensionCore() — wires AgentSession host methods to ExtensionRunner APIs, including LLM call telemetry (every ctx.completeSimple / completeSimpleWithUsage / completeJson emits one ext_llm_calls row)
|
|
3
|
+
* [FROM]: Depends on ExtensionRunner, model/session/resource abstractions, slash command metadata, core/telemetry (getExtCallerContext for caller attribution + LlmCallEventInput shape)
|
|
4
4
|
* [TO]: Consumed by AgentSession when initializing extension runtime capabilities
|
|
5
|
-
* [HERE]: core/runtime/extension-core-bindings.ts -
|
|
5
|
+
* [HERE]: core/runtime/extension-core-bindings.ts - the LLM-call instrumentation point; reads AsyncLocalStorage context pushed by runner.invokeCommand (user-initiated=true) or runner.invokeHookHandler (user-initiated=false) to attribute each call. is_user_initiated=false rows grouped by extension_name + caller_context are the idle-thinking bug detector.
|
|
6
6
|
*/
|
|
7
7
|
import type { Model } from "@pencil-agent/ai";
|
|
8
8
|
import type { ThinkingLevel } from "@pencil-agent/agent-core";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { completeSimple } from "@pencil-agent/ai";
|
|
2
|
+
import { getExtCallerContext } from "../telemetry/index.js";
|
|
2
3
|
import { buildExtensionSlashCommands } from "./slash-command-catalog.js";
|
|
3
4
|
function getStructuredToolChoice(model, toolName) {
|
|
4
5
|
switch (model.api) {
|
|
@@ -21,33 +22,73 @@ function isTextContent(block) {
|
|
|
21
22
|
function isNamedToolCall(block, toolName) {
|
|
22
23
|
return block.type === "toolCall" && "name" in block && block.name === toolName;
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Build the LlmCallEventInput row for one extension-initiated LLM call. Reads
|
|
27
|
+
* the AsyncLocalStorage caller context pushed by ExtensionRunner — that's how
|
|
28
|
+
* we know which extension and whether the path was user-initiated. Falls back
|
|
29
|
+
* to `extension_name="unknown"` + `is_user_initiated=false` when no context is
|
|
30
|
+
* set (defensive: that combination shows up in dashboards as "unattributed",
|
|
31
|
+
* a useful signal in itself).
|
|
32
|
+
*/
|
|
33
|
+
function buildLlmCallEvent(args) {
|
|
34
|
+
const callerCtx = getExtCallerContext();
|
|
35
|
+
return {
|
|
36
|
+
extensionName: callerCtx?.extensionName ?? "unknown",
|
|
37
|
+
callerContext: callerCtx?.callerContext ?? "unknown",
|
|
38
|
+
isUserInitiated: callerCtx?.isUserInitiated ?? false,
|
|
39
|
+
modelId: args.model?.id ?? null,
|
|
40
|
+
tokensIn: args.usage?.input ?? null,
|
|
41
|
+
tokensOut: args.usage?.output ?? null,
|
|
42
|
+
costTotal: args.usage?.cost?.total ?? null,
|
|
43
|
+
durationMs: Math.round(performance.now() - args.startPerf),
|
|
44
|
+
ok: args.ok,
|
|
45
|
+
errorCode: args.errorCode,
|
|
46
|
+
startedAt: args.startedAt,
|
|
47
|
+
endedAt: new Date(),
|
|
48
|
+
sessionId: callerCtx?.sessionId ?? args.host.sessionManager.getSessionId(),
|
|
49
|
+
runId: callerCtx?.runId ?? null,
|
|
50
|
+
variant: callerCtx?.variant ?? null,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async function completeTextWithCurrentModel(runner, host, systemPrompt, userMessage) {
|
|
54
|
+
const result = await completeTextWithUsage(runner, host, systemPrompt, userMessage);
|
|
26
55
|
return result?.text;
|
|
27
56
|
}
|
|
28
|
-
async function completeTextWithUsage(host, systemPrompt, userMessage) {
|
|
57
|
+
async function completeTextWithUsage(runner, host, systemPrompt, userMessage) {
|
|
29
58
|
const model = host.model;
|
|
30
59
|
if (!model)
|
|
31
60
|
return undefined;
|
|
32
61
|
const apiKey = await host.modelRegistry.getApiKey(model);
|
|
33
62
|
if (!apiKey)
|
|
34
63
|
return undefined;
|
|
64
|
+
const startedAt = new Date();
|
|
65
|
+
const startPerf = performance.now();
|
|
66
|
+
let ok = true;
|
|
67
|
+
let errorCode = null;
|
|
68
|
+
let usage;
|
|
69
|
+
let text;
|
|
35
70
|
try {
|
|
36
71
|
const response = await completeSimple(model, {
|
|
37
72
|
systemPrompt,
|
|
38
73
|
messages: [{ role: "user", content: userMessage, timestamp: Date.now() }],
|
|
39
74
|
}, { maxTokens: 1500, temperature: 0.2, apiKey });
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
.
|
|
43
|
-
|
|
44
|
-
|
|
75
|
+
usage = response.usage;
|
|
76
|
+
text =
|
|
77
|
+
response.content
|
|
78
|
+
?.filter(isTextContent)
|
|
79
|
+
.map((block) => block.text ?? "")
|
|
80
|
+
.join("") ?? "";
|
|
45
81
|
}
|
|
46
|
-
catch {
|
|
47
|
-
|
|
82
|
+
catch (err) {
|
|
83
|
+
ok = false;
|
|
84
|
+
errorCode = err instanceof Error ? err.constructor.name : "unknown";
|
|
48
85
|
}
|
|
86
|
+
runner.writeLlmCallEvent(buildLlmCallEvent({ host, model, startedAt, startPerf, ok, errorCode, usage }));
|
|
87
|
+
if (!ok || text === undefined || usage === undefined)
|
|
88
|
+
return undefined;
|
|
89
|
+
return { text, usage };
|
|
49
90
|
}
|
|
50
|
-
async function completeJsonWithCurrentModel(host, systemPrompt, userMessage, schema, options) {
|
|
91
|
+
async function completeJsonWithCurrentModel(runner, host, systemPrompt, userMessage, schema, options) {
|
|
51
92
|
const model = host.model;
|
|
52
93
|
if (!model)
|
|
53
94
|
return undefined;
|
|
@@ -55,6 +96,12 @@ async function completeJsonWithCurrentModel(host, systemPrompt, userMessage, sch
|
|
|
55
96
|
if (!apiKey)
|
|
56
97
|
return undefined;
|
|
57
98
|
const toolName = options?.toolName || "submit_json";
|
|
99
|
+
const startedAt = new Date();
|
|
100
|
+
const startPerf = performance.now();
|
|
101
|
+
let ok = true;
|
|
102
|
+
let errorCode = null;
|
|
103
|
+
let usage;
|
|
104
|
+
let payloadString;
|
|
58
105
|
try {
|
|
59
106
|
const toolSchema = schema;
|
|
60
107
|
const completionOptions = {
|
|
@@ -74,15 +121,21 @@ async function completeJsonWithCurrentModel(host, systemPrompt, userMessage, sch
|
|
|
74
121
|
},
|
|
75
122
|
],
|
|
76
123
|
}, completionOptions);
|
|
124
|
+
usage = response.usage;
|
|
77
125
|
const toolCall = response.content?.find((block) => isNamedToolCall(block, toolName));
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
126
|
+
if (toolCall) {
|
|
127
|
+
const payload = options?.resultKey ? toolCall.arguments?.[options.resultKey] : toolCall.arguments;
|
|
128
|
+
payloadString = JSON.stringify(payload);
|
|
129
|
+
}
|
|
82
130
|
}
|
|
83
|
-
catch {
|
|
84
|
-
|
|
131
|
+
catch (err) {
|
|
132
|
+
ok = false;
|
|
133
|
+
errorCode = err instanceof Error ? err.constructor.name : "unknown";
|
|
85
134
|
}
|
|
135
|
+
runner.writeLlmCallEvent(buildLlmCallEvent({ host, model, startedAt, startPerf, ok, errorCode, usage }));
|
|
136
|
+
if (!ok)
|
|
137
|
+
return undefined;
|
|
138
|
+
return payloadString;
|
|
86
139
|
}
|
|
87
140
|
export function bindExtensionCore(runner, host) {
|
|
88
141
|
runner.bindCore({
|
|
@@ -146,9 +199,9 @@ export function bindExtensionCore(runner, host) {
|
|
|
146
199
|
setThinkingLevel: (level) => host.setThinkingLevel(level),
|
|
147
200
|
}, {
|
|
148
201
|
getModel: () => host.model,
|
|
149
|
-
completeSimple: (systemPrompt, userMessage) => completeTextWithCurrentModel(host, systemPrompt, userMessage),
|
|
150
|
-
completeSimpleWithUsage: (systemPrompt, userMessage) => completeTextWithUsage(host, systemPrompt, userMessage),
|
|
151
|
-
completeJson: (systemPrompt, userMessage, schema, options) => completeJsonWithCurrentModel(host, systemPrompt, userMessage, schema, options),
|
|
202
|
+
completeSimple: (systemPrompt, userMessage) => completeTextWithCurrentModel(runner, host, systemPrompt, userMessage),
|
|
203
|
+
completeSimpleWithUsage: (systemPrompt, userMessage) => completeTextWithUsage(runner, host, systemPrompt, userMessage),
|
|
204
|
+
completeJson: (systemPrompt, userMessage, schema, options) => completeJsonWithCurrentModel(runner, host, systemPrompt, userMessage, schema, options),
|
|
152
205
|
getSettings: () => host.settingsManager.getSettings(),
|
|
153
206
|
isIdle: () => !host.isStreaming,
|
|
154
207
|
abort: () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [WHO]: RetryCoordinator class, auto-retry with exponential backoff
|
|
2
|
+
* [WHO]: RetryCoordinator class, auto-retry with exponential backoff, in-loop retry preparation
|
|
3
3
|
* [FROM]: Depends on agent-core (AssistantMessage), ai (isContextOverflow), core/runtime/utils/sleep
|
|
4
4
|
* [TO]: Consumed by core/runtime/agent-session.ts
|
|
5
5
|
* [HERE]: core/runtime/retry-coordinator.ts - retry orchestration extracted from AgentSession
|
|
@@ -62,6 +62,15 @@ export declare class RetryCoordinator {
|
|
|
62
62
|
* @returns true if retry was initiated, false if max retries exceeded or disabled
|
|
63
63
|
*/
|
|
64
64
|
handleError(message: AssistantMessage): Promise<boolean>;
|
|
65
|
+
/**
|
|
66
|
+
* Handle a retryable error inside an active agent loop.
|
|
67
|
+
*
|
|
68
|
+
* Unlike handleError(), this does not mutate host messages and does not
|
|
69
|
+
* schedule agent.continue(); the caller remains responsible for replacing
|
|
70
|
+
* loop context and continuing in-place.
|
|
71
|
+
*/
|
|
72
|
+
handleErrorInLoop(message: AssistantMessage): Promise<boolean>;
|
|
73
|
+
private _prepareRetry;
|
|
65
74
|
/**
|
|
66
75
|
* Called when an assistant response succeeds — resets retry counter
|
|
67
76
|
* and resolves the pending retry promise.
|
|
@@ -44,6 +44,26 @@ export class RetryCoordinator {
|
|
|
44
44
|
* @returns true if retry was initiated, false if max retries exceeded or disabled
|
|
45
45
|
*/
|
|
46
46
|
async handleError(message) {
|
|
47
|
+
const shouldRetry = await this._prepareRetry(message);
|
|
48
|
+
if (!shouldRetry)
|
|
49
|
+
return false;
|
|
50
|
+
// Remove error message from agent state
|
|
51
|
+
this._host.removeLastAssistantMessage();
|
|
52
|
+
// Trigger retry via continue()
|
|
53
|
+
this._host.triggerContinue();
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Handle a retryable error inside an active agent loop.
|
|
58
|
+
*
|
|
59
|
+
* Unlike handleError(), this does not mutate host messages and does not
|
|
60
|
+
* schedule agent.continue(); the caller remains responsible for replacing
|
|
61
|
+
* loop context and continuing in-place.
|
|
62
|
+
*/
|
|
63
|
+
async handleErrorInLoop(message) {
|
|
64
|
+
return this._prepareRetry(message);
|
|
65
|
+
}
|
|
66
|
+
async _prepareRetry(message) {
|
|
47
67
|
const settings = this._host.getRetrySettings();
|
|
48
68
|
if (!settings.enabled)
|
|
49
69
|
return false;
|
|
@@ -73,8 +93,6 @@ export class RetryCoordinator {
|
|
|
73
93
|
delayMs,
|
|
74
94
|
errorMessage: message.errorMessage || "Unknown error",
|
|
75
95
|
});
|
|
76
|
-
// Remove error message from agent state
|
|
77
|
-
this._host.removeLastAssistantMessage();
|
|
78
96
|
// Wait with exponential backoff (abortable)
|
|
79
97
|
this._retryAbortController = new AbortController();
|
|
80
98
|
try {
|
|
@@ -94,8 +112,6 @@ export class RetryCoordinator {
|
|
|
94
112
|
return false;
|
|
95
113
|
}
|
|
96
114
|
this._retryAbortController = undefined;
|
|
97
|
-
// Trigger retry via continue()
|
|
98
|
-
this._host.triggerContinue();
|
|
99
115
|
return true;
|
|
100
116
|
}
|
|
101
117
|
/**
|
package/dist/core/runtime/sdk.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [WHO]: createAgentSession(options) → AgentSession + load results
|
|
2
|
+
* [WHO]: createAgentSession(options) → AgentSession + load results, runtime loop setting wiring
|
|
3
3
|
* [FROM]: Depends on agent-core, ai, core/config/*, core/tools/*, core/session/*, core/mcp-*, i18n/*
|
|
4
4
|
* [TO]: Consumed by index.ts, main.ts, test/presence-opening.test.ts, extensions/defaults/team/index.ts
|
|
5
5
|
* [HERE]: SDK factory; creates all services with DI, wires up extensions
|
|
@@ -250,6 +250,7 @@ export async function createAgentSession(options = {}) {
|
|
|
250
250
|
agentLoopFramework: settingsManager.getAgentLoopFramework(),
|
|
251
251
|
thinkingBudgets: settingsManager.getThinkingBudgets(),
|
|
252
252
|
maxRetryDelayMs: settingsManager.getRetrySettings().maxDelayMs,
|
|
253
|
+
maxToolResultBatchSizeChars: settingsManager.getAgentLoopSettings().maxToolResultBatchSizeChars,
|
|
253
254
|
getApiKey: async (provider) => {
|
|
254
255
|
// Use the provider argument from the in-flight request;
|
|
255
256
|
// agent.state.model may already be switched mid-turn.
|
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
import type { ResourceLoader } from "../config/resource-loader.js";
|
|
8
8
|
import type { ExtensionRunner } from "../extensions/index.js";
|
|
9
9
|
import type { PromptTemplate } from "../prompt/prompt-templates.js";
|
|
10
|
-
import { getLocalizedCommands, type SlashCommandInfo } from "../slash-commands.js";
|
|
10
|
+
import { getLocalizedCommands, type SlashCommandInfo, type SlashCommandCategory } from "../slash-commands.js";
|
|
11
11
|
export interface SessionSlashCommandDescriptor {
|
|
12
12
|
name: string;
|
|
13
13
|
description?: string;
|
|
14
14
|
source: "builtin" | SlashCommandInfo["source"];
|
|
15
|
+
category?: SlashCommandCategory;
|
|
15
16
|
}
|
|
16
17
|
type Translate = Parameters<typeof getLocalizedCommands>[0];
|
|
17
18
|
export interface SlashCommandCatalogSource {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BUILTIN_SLASH_COMMANDS, getLocalizedCommands, } from "../slash-commands.js";
|
|
1
|
+
import { BUILTIN_SLASH_COMMANDS, getLocalizedCommands, inferSlashCommandCategory, } from "../slash-commands.js";
|
|
2
2
|
function normalizeLocation(source) {
|
|
3
3
|
if (source === "user" || source === "project" || source === "path") {
|
|
4
4
|
return source;
|
|
@@ -16,6 +16,7 @@ function getExtensionCommands(runner, reservedBuiltins) {
|
|
|
16
16
|
name: command.name,
|
|
17
17
|
description: command.description,
|
|
18
18
|
path: extensionPath,
|
|
19
|
+
category: inferSlashCommandCategory(command.name, "extension"),
|
|
19
20
|
})) ?? []);
|
|
20
21
|
}
|
|
21
22
|
export function buildSessionSlashCommands(source, translate) {
|
|
@@ -23,22 +24,26 @@ export function buildSessionSlashCommands(source, translate) {
|
|
|
23
24
|
name: command.name,
|
|
24
25
|
description: command.description,
|
|
25
26
|
source: "builtin",
|
|
27
|
+
category: command.category,
|
|
26
28
|
}));
|
|
27
29
|
const reservedBuiltins = getReservedBuiltinNames();
|
|
28
30
|
const extensionCommands = getExtensionCommands(source.extensionRunner, reservedBuiltins).map((command) => ({
|
|
29
31
|
name: command.name,
|
|
30
32
|
description: command.description,
|
|
31
33
|
source: "extension",
|
|
34
|
+
category: command.category,
|
|
32
35
|
}));
|
|
33
36
|
const promptCommands = source.promptTemplates.map((template) => ({
|
|
34
37
|
name: template.name,
|
|
35
38
|
description: template.description,
|
|
36
39
|
source: "prompt",
|
|
40
|
+
category: inferSlashCommandCategory(template.name, "prompt"),
|
|
37
41
|
}));
|
|
38
42
|
const skillCommands = source.resourceLoader.getSkills().skills.map((skill) => ({
|
|
39
43
|
name: `skill:${skill.name}`,
|
|
40
44
|
description: skill.description,
|
|
41
45
|
source: "skill",
|
|
46
|
+
category: inferSlashCommandCategory(skill.name, "skill"),
|
|
42
47
|
}));
|
|
43
48
|
return [
|
|
44
49
|
...builtins,
|
|
@@ -53,12 +58,14 @@ export function buildExtensionSlashCommands(source) {
|
|
|
53
58
|
name: command.name,
|
|
54
59
|
description: command.description,
|
|
55
60
|
source: "extension",
|
|
61
|
+
category: command.category,
|
|
56
62
|
path: command.path,
|
|
57
63
|
}));
|
|
58
64
|
const templates = source.promptTemplates.map((template) => ({
|
|
59
65
|
name: template.name,
|
|
60
66
|
description: template.description,
|
|
61
67
|
source: "prompt",
|
|
68
|
+
category: inferSlashCommandCategory(template.name, "prompt"),
|
|
62
69
|
location: normalizeLocation(template.source),
|
|
63
70
|
path: template.filePath,
|
|
64
71
|
}));
|
|
@@ -68,6 +75,7 @@ export function buildExtensionSlashCommands(source) {
|
|
|
68
75
|
name: `skill:${skill.name}`,
|
|
69
76
|
description: skill.description,
|
|
70
77
|
source: "skill",
|
|
78
|
+
category: inferSlashCommandCategory(skill.name, "skill"),
|
|
71
79
|
location: normalizeLocation(skill.source),
|
|
72
80
|
path: skill.filePath,
|
|
73
81
|
}));
|
|
@@ -1,25 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [WHO]: SlashCommandInfo, BuiltinSlashCommand, slashCommand definitions, getLocalizedCommands()
|
|
2
|
+
* [WHO]: SlashCommandInfo, BuiltinSlashCommand, slashCommand definitions, category helpers, getLocalizedCommands()
|
|
3
3
|
* [FROM]: No external dependencies
|
|
4
4
|
* [TO]: Consumed by modes/interactive/interactive-mode.ts, modes/acp/acp-mode.ts
|
|
5
5
|
* [HERE]: core/slash-commands.ts - slash command types and registry
|
|
6
6
|
*/
|
|
7
7
|
export type SlashCommandSource = "extension" | "prompt" | "skill";
|
|
8
8
|
export type SlashCommandLocation = "user" | "project" | "path";
|
|
9
|
+
export type SlashCommandCategory = "core" | "model" | "memory" | "session" | "workflow" | "agents" | "tools" | "admin";
|
|
10
|
+
export type SlashCommandImplementation = "core" | "extension";
|
|
9
11
|
export interface SlashCommandInfo {
|
|
10
12
|
name: string;
|
|
11
13
|
description?: string;
|
|
12
14
|
source: SlashCommandSource;
|
|
15
|
+
category?: SlashCommandCategory;
|
|
13
16
|
location?: SlashCommandLocation;
|
|
14
17
|
path?: string;
|
|
15
18
|
}
|
|
16
19
|
export interface BuiltinSlashCommand {
|
|
17
20
|
name: string;
|
|
18
21
|
descriptionKey: string;
|
|
22
|
+
category: SlashCommandCategory;
|
|
23
|
+
implementation?: SlashCommandImplementation;
|
|
19
24
|
}
|
|
20
25
|
export declare const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand>;
|
|
26
|
+
export declare function getExtensionBackedBuiltinCommandNames(): Set<string>;
|
|
27
|
+
export declare function inferSlashCommandCategory(name: string, source?: SlashCommandSource): SlashCommandCategory;
|
|
28
|
+
export declare function getSlashCommandCategoryLabel(category: SlashCommandCategory, t: (key: string) => string): string;
|
|
29
|
+
export declare function formatSlashCommandDescription(description: string | undefined, category: SlashCommandCategory | undefined, t: (key: string) => string): string | undefined;
|
|
21
30
|
export interface LocalizedSlashCommand {
|
|
22
31
|
name: string;
|
|
23
32
|
description: string;
|
|
33
|
+
category: SlashCommandCategory;
|
|
34
|
+
categoryLabel: string;
|
|
24
35
|
}
|
|
25
36
|
export declare function getLocalizedCommands(t: (key: string) => string): LocalizedSlashCommand[];
|