@pencil-agent/nano-pencil 1.14.3 → 1.14.5
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/cli/args.d.ts +11 -1
- package/dist/cli/args.js +130 -0
- package/dist/core/config/settings-manager.d.ts +8 -1
- package/dist/core/config/settings-manager.js +9 -0
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/runner.d.ts +70 -4
- package/dist/core/extensions/runner.js +188 -8
- package/dist/core/extensions/types.d.ts +15 -3
- package/dist/core/i18n/slash-commands.d.ts +1 -0
- package/dist/core/i18n/slash-commands.js +1 -0
- package/dist/core/i18n/slash-commands.zh.d.ts +1 -0
- package/dist/core/i18n/slash-commands.zh.js +1 -0
- package/dist/core/runtime/agent-session.d.ts +10 -2
- package/dist/core/runtime/agent-session.js +117 -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.d.ts +21 -1
- package/dist/core/runtime/sdk.js +12 -2
- package/dist/core/slash-commands.d.ts +3 -0
- package/dist/core/slash-commands.js +6 -2
- 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/AGENT.md +3 -3
- package/dist/extensions/defaults/CLAUDE.md +1 -1
- package/dist/extensions/defaults/browser/index.js +13 -5
- package/dist/extensions/defaults/btw/index.js +2 -2
- package/dist/extensions/defaults/debug/index.js +18 -8
- 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 +13 -5
- 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.d.ts +2 -2
- package/dist/extensions/defaults/sal/index.js +47 -10
- 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/sal/sal-runtime.d.ts +3 -1
- package/dist/extensions/defaults/sal/sal-runtime.js +1 -1
- package/dist/extensions/defaults/sal/sal-trace.d.ts +1 -1
- package/dist/extensions/defaults/sal/sal-trace.js +13 -2
- package/dist/extensions/defaults/security-audit/index.js +138 -80
- 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/token-save/index.js +11 -5
- package/dist/extensions/optional/export-html/index.js +19 -5
- package/dist/extensions/optional/simplify/index.js +11 -5
- package/dist/main.js +11 -2
- package/dist/modes/acp/acp-mode.d.ts +3 -0
- package/dist/modes/acp/acp-mode.js +40 -1
- package/dist/modes/agent-loop-result-format.d.ts +10 -0
- package/dist/modes/agent-loop-result-format.js +69 -0
- package/dist/modes/interactive/agent-loop-status.d.ts +8 -0
- package/dist/modes/interactive/agent-loop-status.js +41 -0
- package/dist/modes/interactive/interactive-mode.d.ts +2 -1
- package/dist/modes/interactive/interactive-mode.js +75 -25
- package/dist/modes/interactive/slash-command-arguments.d.ts +28 -0
- package/dist/modes/interactive/slash-command-arguments.js +126 -0
- package/dist/modes/print-mode.d.ts +17 -5
- package/dist/modes/print-mode.js +77 -5
- package/dist/modes/rpc/rpc-client.d.ts +5 -1
- package/dist/modes/rpc/rpc-client.js +6 -0
- package/dist/modes/rpc/rpc-mode.d.ts +8 -1
- package/dist/modes/rpc/rpc-mode.js +92 -48
- package/dist/modes/rpc/rpc-types.d.ts +24 -1
- package/dist/node_modules/@pencil-agent/agent-core/agent-loop-continuations.d.ts +17 -0
- package/dist/node_modules/@pencil-agent/agent-core/agent-loop-continuations.js +60 -0
- package/dist/node_modules/@pencil-agent/agent-core/agent-loop-tool-results.d.ts +10 -0
- package/dist/node_modules/@pencil-agent/agent-core/agent-loop-tool-results.js +137 -0
- package/dist/node_modules/@pencil-agent/agent-core/agent-loop-tool-summaries.d.ts +22 -0
- package/dist/node_modules/@pencil-agent/agent-core/agent-loop-tool-summaries.js +64 -0
- package/dist/node_modules/@pencil-agent/agent-core/agent-loop.d.ts +4 -4
- package/dist/node_modules/@pencil-agent/agent-core/agent-loop.js +440 -39
- package/dist/node_modules/@pencil-agent/agent-core/agent-run-result.d.ts +9 -0
- package/dist/node_modules/@pencil-agent/agent-core/agent-run-result.js +32 -0
- package/dist/node_modules/@pencil-agent/agent-core/agent.d.ts +42 -2
- package/dist/node_modules/@pencil-agent/agent-core/agent.js +86 -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 +2 -2
- package/dist/node_modules/@pencil-agent/agent-core/structured-adaptive-agent-loop.js +164 -43
- 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 +139 -19
- package/dist/node_modules/@pencil-agent/agent-core/types.js +1 -1
- package/dist/node_modules/@pencil-agent/ai/models.generated.d.ts +34 -34
- package/dist/node_modules/@pencil-agent/ai/models.generated.js +63 -63
- package/dist/node_modules/@pencil-agent/ai/providers/transform-messages.d.ts +3 -3
- package/dist/node_modules/@pencil-agent/ai/providers/transform-messages.js +42 -27
- package/dist/node_modules/@pencil-agent/ai/stream.d.ts +2 -2
- package/dist/node_modules/@pencil-agent/ai/stream.js +70 -14
- package/dist/node_modules/@pencil-agent/ai/utils/event-stream.d.ts +5 -1
- package/dist/node_modules/@pencil-agent/ai/utils/event-stream.js +15 -3
- package/dist/node_modules/@pencil-agent/tui/autocomplete.d.ts +8 -1
- package/dist/node_modules/@pencil-agent/tui/autocomplete.js +12 -1
- package/dist/node_modules/@pencil-agent/tui/index.d.ts +1 -1
- package/dist/node_modules/@pencil-agent/tui/tui.d.ts +3 -8
- package/dist/node_modules/@pencil-agent/tui/tui.js +33 -67
- package/dist/packages/mem-core/extension.js +6 -2
- 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/docs/agent-loop-frameworks.md +26 -3
- package/package.json +2 -2
package/dist/build-meta.json
CHANGED
package/dist/cli/args.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* [TO]: Consumed by main.ts, core/model-resolver.ts
|
|
5
5
|
* [HERE]: cli/args.ts - CLI argument parsing and help display
|
|
6
6
|
*/
|
|
7
|
-
import type
|
|
7
|
+
import { type AgentLoopFrameworkInput, type AgentLoopPolicyOptions, type ThinkingLevel } from "@pencil-agent/agent-core";
|
|
8
8
|
import { type ToolName } from "../core/tools/index.js";
|
|
9
9
|
export type Mode = "text" | "json" | "rpc";
|
|
10
10
|
export interface Args {
|
|
@@ -29,6 +29,16 @@ export interface Args {
|
|
|
29
29
|
extensions?: string[];
|
|
30
30
|
noExtensions?: boolean;
|
|
31
31
|
print?: boolean;
|
|
32
|
+
/** In text print mode, emit the final agent loop result as stderr JSON. */
|
|
33
|
+
printLoopResult?: boolean;
|
|
34
|
+
/** In print mode, exit non-zero when the final agent result is an error. */
|
|
35
|
+
failOnAgentError?: boolean;
|
|
36
|
+
/** In print mode, exit non-zero when any tool permission denial occurred. */
|
|
37
|
+
failOnToolDenial?: boolean;
|
|
38
|
+
/** Non-persistent agent loop framework override for this process/session. */
|
|
39
|
+
agentLoopFramework?: AgentLoopFrameworkInput;
|
|
40
|
+
/** Non-persistent loop policy overrides for this process/session. */
|
|
41
|
+
loopPolicy?: Pick<AgentLoopPolicyOptions, "maxTurnsPerPrompt" | "maxToolCallsPerPrompt" | "maxToolConcurrency" | "maxToolResultBatchSizeChars" | "outputTokenBudget" | "maxOutputTokenRecoveryAttempts" | "maxModelErrorRecoveryAttempts" | "maxStopHookContinuations">;
|
|
32
42
|
export?: string;
|
|
33
43
|
noSkills?: boolean;
|
|
34
44
|
skills?: string[];
|
package/dist/cli/args.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [WHO]: Args, Mode, parseArgs(), printHelp()
|
|
3
|
+
* [FROM]: Depends on agent-core, chalk, config.ts, core/tools
|
|
4
|
+
* [TO]: Consumed by main.ts, core/model-resolver.ts
|
|
5
|
+
* [HERE]: cli/args.ts - CLI argument parsing and help display
|
|
6
|
+
*/
|
|
7
|
+
import { normalizeAgentLoopFramework, } from "@pencil-agent/agent-core";
|
|
1
8
|
import chalk from "chalk";
|
|
2
9
|
import { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR } from "../config.js";
|
|
3
10
|
import { allTools } from "../core/tools/index.js";
|
|
@@ -5,12 +12,45 @@ const VALID_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh
|
|
|
5
12
|
export function isValidThinkingLevel(level) {
|
|
6
13
|
return VALID_THINKING_LEVELS.includes(level);
|
|
7
14
|
}
|
|
15
|
+
function parseAgentLoopFramework(value) {
|
|
16
|
+
const normalized = normalizeAgentLoopFramework(value);
|
|
17
|
+
if (normalized === "standard" || normalized === "weak-model-compatible") {
|
|
18
|
+
return normalized;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
function parsePositiveIntegerOption(flag, value) {
|
|
23
|
+
const parsed = Number(value);
|
|
24
|
+
if (Number.isInteger(parsed) && parsed > 0)
|
|
25
|
+
return parsed;
|
|
26
|
+
console.error(chalk.yellow(`Warning: Invalid ${flag} value "${value}". Expected a positive integer.`));
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
function parseNonNegativeIntegerOption(flag, value) {
|
|
30
|
+
const parsed = Number(value);
|
|
31
|
+
if (Number.isInteger(parsed) && parsed >= 0)
|
|
32
|
+
return parsed;
|
|
33
|
+
console.error(chalk.yellow(`Warning: Invalid ${flag} value "${value}". Expected a non-negative integer.`));
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
function parseUnitIntervalOption(flag, value) {
|
|
37
|
+
const parsed = Number(value);
|
|
38
|
+
if (Number.isFinite(parsed) && parsed > 0 && parsed <= 1)
|
|
39
|
+
return parsed;
|
|
40
|
+
console.error(chalk.yellow(`Warning: Invalid ${flag} value "${value}". Expected a number in (0, 1].`));
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
function setLoopPolicyOption(result, key, value) {
|
|
44
|
+
result.loopPolicy = result.loopPolicy ?? {};
|
|
45
|
+
result.loopPolicy[key] = value;
|
|
46
|
+
}
|
|
8
47
|
export function parseArgs(args, extensionFlags) {
|
|
9
48
|
const result = {
|
|
10
49
|
messages: [],
|
|
11
50
|
fileArgs: [],
|
|
12
51
|
unknownFlags: new Map(),
|
|
13
52
|
};
|
|
53
|
+
const outputTokenBudget = {};
|
|
14
54
|
for (let i = 0; i < args.length; i++) {
|
|
15
55
|
const arg = args[i];
|
|
16
56
|
if (arg === "--help" || arg === "-h") {
|
|
@@ -89,6 +129,75 @@ export function parseArgs(args, extensionFlags) {
|
|
|
89
129
|
else if (arg === "--print" || arg === "-p") {
|
|
90
130
|
result.print = true;
|
|
91
131
|
}
|
|
132
|
+
else if (arg === "--print-loop-result") {
|
|
133
|
+
result.printLoopResult = true;
|
|
134
|
+
}
|
|
135
|
+
else if (arg === "--fail-on-agent-error") {
|
|
136
|
+
result.failOnAgentError = true;
|
|
137
|
+
}
|
|
138
|
+
else if (arg === "--fail-on-tool-denial") {
|
|
139
|
+
result.failOnToolDenial = true;
|
|
140
|
+
}
|
|
141
|
+
else if (arg === "--agent-loop" && i + 1 < args.length) {
|
|
142
|
+
const framework = args[++i];
|
|
143
|
+
const normalized = parseAgentLoopFramework(framework);
|
|
144
|
+
if (normalized) {
|
|
145
|
+
result.agentLoopFramework = normalized;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
console.error(chalk.yellow(`Warning: Invalid agent loop framework "${framework}". Valid values: standard, weak-model-compatible.`));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else if (arg === "--max-turns-per-prompt" && i + 1 < args.length) {
|
|
152
|
+
const value = parsePositiveIntegerOption(arg, args[++i]);
|
|
153
|
+
if (value !== undefined)
|
|
154
|
+
setLoopPolicyOption(result, "maxTurnsPerPrompt", value);
|
|
155
|
+
}
|
|
156
|
+
else if (arg === "--max-tool-calls-per-prompt" && i + 1 < args.length) {
|
|
157
|
+
const value = parsePositiveIntegerOption(arg, args[++i]);
|
|
158
|
+
if (value !== undefined)
|
|
159
|
+
setLoopPolicyOption(result, "maxToolCallsPerPrompt", value);
|
|
160
|
+
}
|
|
161
|
+
else if (arg === "--max-tool-concurrency" && i + 1 < args.length) {
|
|
162
|
+
const value = parsePositiveIntegerOption(arg, args[++i]);
|
|
163
|
+
if (value !== undefined)
|
|
164
|
+
setLoopPolicyOption(result, "maxToolConcurrency", value);
|
|
165
|
+
}
|
|
166
|
+
else if (arg === "--max-tool-result-batch-size-chars" && i + 1 < args.length) {
|
|
167
|
+
const value = parsePositiveIntegerOption(arg, args[++i]);
|
|
168
|
+
if (value !== undefined)
|
|
169
|
+
setLoopPolicyOption(result, "maxToolResultBatchSizeChars", value);
|
|
170
|
+
}
|
|
171
|
+
else if (arg === "--output-token-budget" && i + 1 < args.length) {
|
|
172
|
+
const value = parsePositiveIntegerOption(arg, args[++i]);
|
|
173
|
+
if (value !== undefined)
|
|
174
|
+
outputTokenBudget.targetTokens = value;
|
|
175
|
+
}
|
|
176
|
+
else if (arg === "--output-token-budget-threshold" && i + 1 < args.length) {
|
|
177
|
+
const value = parseUnitIntervalOption(arg, args[++i]);
|
|
178
|
+
if (value !== undefined)
|
|
179
|
+
outputTokenBudget.thresholdPct = value;
|
|
180
|
+
}
|
|
181
|
+
else if (arg === "--output-token-budget-continuations" && i + 1 < args.length) {
|
|
182
|
+
const value = parseNonNegativeIntegerOption(arg, args[++i]);
|
|
183
|
+
if (value !== undefined)
|
|
184
|
+
outputTokenBudget.maxContinuations = value;
|
|
185
|
+
}
|
|
186
|
+
else if (arg === "--max-output-token-recovery-attempts" && i + 1 < args.length) {
|
|
187
|
+
const value = parseNonNegativeIntegerOption(arg, args[++i]);
|
|
188
|
+
if (value !== undefined)
|
|
189
|
+
setLoopPolicyOption(result, "maxOutputTokenRecoveryAttempts", value);
|
|
190
|
+
}
|
|
191
|
+
else if (arg === "--max-model-error-recovery-attempts" && i + 1 < args.length) {
|
|
192
|
+
const value = parseNonNegativeIntegerOption(arg, args[++i]);
|
|
193
|
+
if (value !== undefined)
|
|
194
|
+
setLoopPolicyOption(result, "maxModelErrorRecoveryAttempts", value);
|
|
195
|
+
}
|
|
196
|
+
else if (arg === "--max-stop-hook-continuations" && i + 1 < args.length) {
|
|
197
|
+
const value = parseNonNegativeIntegerOption(arg, args[++i]);
|
|
198
|
+
if (value !== undefined)
|
|
199
|
+
setLoopPolicyOption(result, "maxStopHookContinuations", value);
|
|
200
|
+
}
|
|
92
201
|
else if (arg === "--export" && i + 1 < args.length) {
|
|
93
202
|
result.export = args[++i];
|
|
94
203
|
}
|
|
@@ -168,6 +277,13 @@ export function parseArgs(args, extensionFlags) {
|
|
|
168
277
|
result.messages.push(arg);
|
|
169
278
|
}
|
|
170
279
|
}
|
|
280
|
+
if (outputTokenBudget.targetTokens !== undefined) {
|
|
281
|
+
setLoopPolicyOption(result, "outputTokenBudget", {
|
|
282
|
+
targetTokens: outputTokenBudget.targetTokens,
|
|
283
|
+
thresholdPct: outputTokenBudget.thresholdPct,
|
|
284
|
+
maxContinuations: outputTokenBudget.maxContinuations,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
171
287
|
return result;
|
|
172
288
|
}
|
|
173
289
|
export function printHelp() {
|
|
@@ -194,6 +310,20 @@ ${chalk.bold("Options:")}
|
|
|
194
310
|
--append-system-prompt <text> Append text or file contents to the system prompt
|
|
195
311
|
--mode <mode> Output mode: text (default), json, or rpc
|
|
196
312
|
--print, -p Non-interactive mode: process prompt and exit
|
|
313
|
+
--print-loop-result In text print mode, write final loop result JSON to stderr
|
|
314
|
+
--fail-on-agent-error In print mode, exit non-zero when final loop result is an error
|
|
315
|
+
--fail-on-tool-denial In print mode, exit non-zero when tools were denied
|
|
316
|
+
--agent-loop <framework> Override loop framework: standard or weak-model-compatible
|
|
317
|
+
--max-turns-per-prompt <n> Stop a prompt after n assistant turns
|
|
318
|
+
--max-tool-calls-per-prompt <n> Stop a prompt after n tool calls
|
|
319
|
+
--max-tool-concurrency <n> Max concurrent safe tool calls in compatible loop
|
|
320
|
+
--max-tool-result-batch-size-chars <n> Max aggregate tool result chars per turn
|
|
321
|
+
--output-token-budget <n> Continue when final output is below n tokens
|
|
322
|
+
--output-token-budget-threshold <n> Continuation threshold ratio in (0,1], default loop policy
|
|
323
|
+
--output-token-budget-continuations <n> Max output-budget continuations
|
|
324
|
+
--max-output-token-recovery-attempts <n> Max recovery turns after output-token stops
|
|
325
|
+
--max-model-error-recovery-attempts <n> Max in-loop model error recoveries
|
|
326
|
+
--max-stop-hook-continuations <n> Max stop-hook validation continuations
|
|
197
327
|
--continue, -c Continue previous session
|
|
198
328
|
--resume, -r Select a session to resume
|
|
199
329
|
--session <path> Use specific session file
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [WHO]: SettingsManager class, two-tier settings (global + project-local)
|
|
2
|
+
* [WHO]: SettingsManager class, two-tier settings (global + project-local), agent loop defaults
|
|
3
3
|
* [FROM]: Depends on ai, node:fs, proper-lockfile, config.ts
|
|
4
4
|
* [TO]: Consumed by index.ts, main.ts, core/runtime/sdk.ts, cli/config-selector.ts, extensions/defaults/team/index.ts, modes/interactive/components/model-selector.ts, modes/interactive/components/config-selector.ts, and test files
|
|
5
5
|
* [HERE]: core/config/settings-manager.ts - user preferences aggregation
|
|
@@ -20,6 +20,9 @@ export interface RetrySettings {
|
|
|
20
20
|
baseDelayMs?: number;
|
|
21
21
|
maxDelayMs?: number;
|
|
22
22
|
}
|
|
23
|
+
export interface AgentLoopSettings {
|
|
24
|
+
maxToolResultBatchSizeChars?: number;
|
|
25
|
+
}
|
|
23
26
|
export interface TerminalSettings {
|
|
24
27
|
showImages?: boolean;
|
|
25
28
|
clearOnShrink?: boolean;
|
|
@@ -65,6 +68,7 @@ export interface Settings {
|
|
|
65
68
|
defaultModel?: string;
|
|
66
69
|
defaultThinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
67
70
|
agentLoopFramework?: AgentLoopFrameworkSettingInput;
|
|
71
|
+
agentLoop?: AgentLoopSettings;
|
|
68
72
|
transport?: TransportSetting;
|
|
69
73
|
steeringMode?: "all" | "one-at-a-time";
|
|
70
74
|
followUpMode?: "all" | "one-at-a-time";
|
|
@@ -218,6 +222,9 @@ export declare class SettingsManager {
|
|
|
218
222
|
getDefaultThinkingLevel(): "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | undefined;
|
|
219
223
|
setDefaultThinkingLevel(level: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"): void;
|
|
220
224
|
getAgentLoopFramework(): AgentLoopFrameworkSetting | undefined;
|
|
225
|
+
getAgentLoopSettings(): {
|
|
226
|
+
maxToolResultBatchSizeChars: number;
|
|
227
|
+
};
|
|
221
228
|
setAgentLoopFramework(framework: AgentLoopFrameworkSettingInput | undefined): void;
|
|
222
229
|
getTransport(): TransportSetting;
|
|
223
230
|
setTransport(transport: TransportSetting): void;
|
|
@@ -4,6 +4,7 @@ import lockfile from "proper-lockfile";
|
|
|
4
4
|
import { APP_NAME, CONFIG_DIR_NAME } from "../../config.js";
|
|
5
5
|
import { defaultAgentDirContext } from "../agent-dir/agent-dir-context.js";
|
|
6
6
|
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
7
|
+
const DEFAULT_MAX_TOOL_RESULT_BATCH_SIZE_CHARS = 200_000;
|
|
7
8
|
export function normalizeAgentLoopFrameworkSetting(value) {
|
|
8
9
|
if (value === "high-intelligence")
|
|
9
10
|
return "standard";
|
|
@@ -574,6 +575,14 @@ export class SettingsManager {
|
|
|
574
575
|
getAgentLoopFramework() {
|
|
575
576
|
return normalizeAgentLoopFrameworkSetting(this.settings.agentLoopFramework);
|
|
576
577
|
}
|
|
578
|
+
getAgentLoopSettings() {
|
|
579
|
+
const configured = this.settings.agentLoop?.maxToolResultBatchSizeChars;
|
|
580
|
+
return {
|
|
581
|
+
maxToolResultBatchSizeChars: typeof configured === "number" && Number.isFinite(configured) && configured > 0
|
|
582
|
+
? Math.floor(configured)
|
|
583
|
+
: DEFAULT_MAX_TOOL_RESULT_BATCH_SIZE_CHARS,
|
|
584
|
+
};
|
|
585
|
+
}
|
|
577
586
|
setAgentLoopFramework(framework) {
|
|
578
587
|
const normalized = normalizeAgentLoopFrameworkSetting(framework);
|
|
579
588
|
if (normalized === undefined) {
|
|
@@ -8,6 +8,6 @@ export type { SlashCommandInfo, SlashCommandLocation, SlashCommandSource } from
|
|
|
8
8
|
export { createExtensionRuntime, discoverAndLoadExtensions, loadExtensionFromFactory, loadExtensions, } from "./loader.js";
|
|
9
9
|
export type { ExtensionErrorListener, ForkHandler, NavigateTreeHandler, NewSessionHandler, ShutdownHandler, SwitchSessionHandler, } from "./runner.js";
|
|
10
10
|
export { ExtensionRunner } from "./runner.js";
|
|
11
|
-
export type { AgentEndEvent, AgentStartEvent, AgentToolResult, AgentToolUpdateCallback, AppAction, AppendEntryHandler, BashToolCallEvent, BashToolResultEvent, BeforeAgentStartEvent, BeforeAgentStartEventResult, CompactOptions, ContextEvent, ContextEventResult, ContextUsage, CustomToolCallEvent, CustomToolResultEvent, EditToolCallEvent, EditToolResultEvent, ExecOptions, ExecResult, Extension, ExtensionActions, ExtensionAPI, ExtensionCommandContext, ExtensionCommandContextActions, ExtensionContext, ExtensionContextActions, ExtensionError, ExtensionEvent, ExtensionFactory, ExtensionFlag, ExtensionHandler, ExtensionRuntime, ExtensionShortcut, ExtensionUIContext, ExtensionUIDialogOptions, ExtensionWidgetOptions, FindToolCallEvent, FindToolResultEvent, GetActiveToolsHandler, GetAllToolsHandler, GetCommandsHandler, GetThinkingLevelHandler, GrepToolCallEvent, GrepToolResultEvent, InputEvent, InputEventResult, InputSource, KeybindingsManager, LoadExtensionsResult, LsToolCallEvent, LsToolResultEvent, MessageEndEvent, MessageRenderer, MessageRenderOptions, MessageStartEvent, MessageUpdateEvent, ModelSelectEvent, ModelSelectSource, ProviderConfig, ProviderModelConfig, ReadToolCallEvent, ReadToolResultEvent, RegisteredCommand, RegisteredTool, ResourcesDiscoverEvent, ResourcesDiscoverResult, SendMessageHandler, SendUserMessageHandler, SessionBeforeCompactEvent, SessionBeforeCompactResult, SessionBeforeForkEvent, SessionBeforeForkResult, SessionBeforeSwitchEvent, SessionBeforeSwitchResult, SessionBeforeTreeEvent, SessionBeforeTreeResult, SessionCompactEvent, SessionEvent, SessionForkEvent, SessionShutdownEvent, SessionStartEvent, SessionSwitchEvent, SessionTreeEvent, SetActiveToolsHandler, SetLabelHandler, SetModelHandler, SetThinkingLevelHandler, TerminalInputHandler, ToolCallEvent, ToolCallEventResult, ToolDefinition, ToolExecutionEndEvent, ToolExecutionStartEvent, ToolExecutionUpdateEvent, ToolInfo, ToolRenderResultOptions, ToolResultEvent, ToolResultEventResult, TreePreparation, TurnEndEvent, TurnStartEvent, UserBashEvent, UserBashEventResult, WidgetPlacement, WriteToolCallEvent, WriteToolResultEvent, } from "./types.js";
|
|
11
|
+
export type { AgentEndEvent, AgentResultEvent, AgentStartEvent, AgentToolResult, AgentToolUpdateCallback, AppAction, AppendEntryHandler, BashToolCallEvent, BashToolResultEvent, BeforeAgentStartEvent, BeforeAgentStartEventResult, CompactOptions, ContextEvent, ContextEventResult, ContextUsage, CustomToolCallEvent, CustomToolResultEvent, EditToolCallEvent, EditToolResultEvent, ExecOptions, ExecResult, Extension, ExtensionActions, ExtensionAPI, ExtensionCommandContext, ExtensionCommandContextActions, ExtensionContext, ExtensionContextActions, ExtensionError, ExtensionEvent, ExtensionFactory, ExtensionFlag, ExtensionHandler, ExtensionRuntime, ExtensionShortcut, ExtensionUIContext, ExtensionUIDialogOptions, ExtensionWidgetOptions, FindToolCallEvent, FindToolResultEvent, GetActiveToolsHandler, GetAllToolsHandler, GetCommandsHandler, GetThinkingLevelHandler, GrepToolCallEvent, GrepToolResultEvent, InputEvent, InputEventResult, InputSource, KeybindingsManager, LoadExtensionsResult, LsToolCallEvent, LsToolResultEvent, MessageEndEvent, MessageRenderer, MessageRenderOptions, MessageStartEvent, MessageUpdateEvent, ModelSelectEvent, ModelSelectSource, ProviderConfig, ProviderModelConfig, ReadToolCallEvent, ReadToolResultEvent, RegisteredCommand, RegisteredTool, ResourcesDiscoverEvent, ResourcesDiscoverResult, SendMessageHandler, SendUserMessageHandler, SessionBeforeCompactEvent, SessionBeforeCompactResult, SessionBeforeForkEvent, SessionBeforeForkResult, SessionBeforeSwitchEvent, SessionBeforeSwitchResult, SessionBeforeTreeEvent, SessionBeforeTreeResult, SessionCompactEvent, SessionEvent, SessionForkEvent, SessionShutdownEvent, SessionStartEvent, SessionSwitchEvent, SessionTreeEvent, SetActiveToolsHandler, SetLabelHandler, SetModelHandler, SetThinkingLevelHandler, TerminalInputHandler, ToolCallEvent, ToolCallEventResult, ToolDefinition, ToolExecutionEndEvent, ToolExecutionStartEvent, ToolExecutionUpdateEvent, ToolInfo, ToolRenderResultOptions, ToolResultEvent, ToolResultEventResult, TreePreparation, TurnEndEvent, TurnStartEvent, UserBashEvent, UserBashEventResult, WidgetPlacement, WriteToolCallEvent, WriteToolResultEvent, } from "./types.js";
|
|
12
12
|
export { isBashToolResult, isEditToolResult, isFindToolResult, isGrepToolResult, isLsToolResult, isReadToolResult, isToolCallEventType, isWriteToolResult, } from "./types.js";
|
|
13
13
|
export { wrapRegisteredTool, wrapRegisteredTools, wrapToolsWithExtensions, wrapToolWithExtensions, } from "./wrapper.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [WHO]: ExtensionRunner class, lifecycle management, event emission
|
|
3
|
-
* [FROM]: Depends on agent-core, ai, tui, modes/theme, session-manager, types.ts
|
|
4
|
-
* [TO]: Consumed by core/extensions/index.ts, core/extensions/wrapper.ts
|
|
5
|
-
* [HERE]: core/extensions/runner.ts - extension execution and lifecycle management
|
|
2
|
+
* [WHO]: ExtensionRunner class, lifecycle management, event emission, slash-command dispatch chokepoint (invokeCommand), telemetry sink wiring (setTelemetrySink)
|
|
3
|
+
* [FROM]: Depends on agent-core, ai, tui, modes/theme, session-manager, types.ts, core/telemetry (ExtensionTelemetrySink + classifyArgsSignature for the P1 ext_command_events writer)
|
|
4
|
+
* [TO]: Consumed by core/extensions/index.ts, core/extensions/wrapper.ts, core/runtime/agent-session.ts (delegates command dispatch via invokeCommand)
|
|
5
|
+
* [HERE]: core/extensions/runner.ts - extension execution and lifecycle management; owns the single try/catch around command.handler so telemetry can wrap every invocation regardless of caller mode
|
|
6
6
|
*/
|
|
7
7
|
import type { AgentMessage } from "@pencil-agent/agent-core";
|
|
8
8
|
import type { ImageContent } from "@pencil-agent/ai";
|
|
@@ -11,6 +11,7 @@ import type { ResourceDiagnostic } from "../diagnostics.js";
|
|
|
11
11
|
import type { KeybindingsConfig } from "../keybindings.js";
|
|
12
12
|
import type { ModelRegistry } from "../model-registry.js";
|
|
13
13
|
import type { SessionManager } from "../session/session-manager.js";
|
|
14
|
+
import { type ExtensionTelemetrySink, type LlmCallEventInput } from "../telemetry/index.js";
|
|
14
15
|
import type { BeforeAgentStartEvent, BeforeAgentStartEventResult, ContextEvent, Extension, ExtensionActions, ExtensionCommandContext, ExtensionCommandContextActions, ExtensionContext, ExtensionContextActions, ExtensionError, ExtensionEvent, ExtensionFlag, ExtensionRuntime, ExtensionShortcut, ExtensionUIContext, InputEvent, InputEventResult, InputSource, MessageRenderer, RegisteredCommand, RegisteredTool, ResourcesDiscoverEvent, SessionBeforeCompactResult, SessionBeforeForkResult, SessionBeforeSwitchResult, SessionBeforeTreeResult, ToolCallEvent, ToolCallEventResult, ToolResultEvent, ToolResultEventResult, UserBashEvent, UserBashEventResult } from "./types.js";
|
|
15
16
|
/** Combined result from all before_agent_start handlers */
|
|
16
17
|
interface BeforeAgentStartCombinedResult {
|
|
@@ -90,6 +91,7 @@ export declare class ExtensionRunner {
|
|
|
90
91
|
private shutdownHandler;
|
|
91
92
|
private shortcutDiagnostics;
|
|
92
93
|
private commandDiagnostics;
|
|
94
|
+
private telemetrySink?;
|
|
93
95
|
private _beforeAgentStartTimeoutMs;
|
|
94
96
|
private get beforeAgentStartTimeoutMs();
|
|
95
97
|
private readonly beforeAgentStartTimeoutSentinel;
|
|
@@ -127,6 +129,70 @@ export declare class ExtensionRunner {
|
|
|
127
129
|
extensionPath: string;
|
|
128
130
|
}>;
|
|
129
131
|
getCommand(name: string): RegisteredCommand | undefined;
|
|
132
|
+
/**
|
|
133
|
+
* Returns the owning Extension for a given slash command name. Used by the
|
|
134
|
+
* telemetry middleware to stamp `extension_name` on each ext_command_events
|
|
135
|
+
* row. Falls back to undefined when no extension claims the command.
|
|
136
|
+
*/
|
|
137
|
+
private findCommandOwner;
|
|
138
|
+
/**
|
|
139
|
+
* Derive a short, stable extension name from an Extension record. For
|
|
140
|
+
* built-ins (extensions/defaults/<name>) and most user extensions
|
|
141
|
+
* (packages/<name>) the directory basename is the right answer.
|
|
142
|
+
*/
|
|
143
|
+
private deriveExtensionName;
|
|
144
|
+
/**
|
|
145
|
+
* Attach (or replace) the extension telemetry sink. The runner owns sink
|
|
146
|
+
* lifecycle from this point — invokeCommand() will fire-and-forget one
|
|
147
|
+
* `ext_command_events` row per invocation, and writeLlmCallEvent() (called
|
|
148
|
+
* from extension-core-bindings) writes one `ext_llm_calls` row per
|
|
149
|
+
* extension-initiated LLM call. Passing the noop sink (the factory's
|
|
150
|
+
* default when no insforge credentials exist) is the safe way to disable
|
|
151
|
+
* telemetry without scattering null-checks at the call sites.
|
|
152
|
+
*/
|
|
153
|
+
setTelemetrySink(sink: ExtensionTelemetrySink): void;
|
|
154
|
+
/**
|
|
155
|
+
* Passthrough used by core/runtime/extension-core-bindings.ts after each
|
|
156
|
+
* extension-initiated LLM call. The runner owns the sink; the binding
|
|
157
|
+
* doesn't import it directly to keep its concerns scoped to LLM plumbing.
|
|
158
|
+
*/
|
|
159
|
+
writeLlmCallEvent(input: LlmCallEventInput): void;
|
|
160
|
+
/**
|
|
161
|
+
* Wrap an extension hook handler invocation in three layers:
|
|
162
|
+
*
|
|
163
|
+
* 1. AsyncLocalStorage frame so any LLM call placed by the handler gets
|
|
164
|
+
* attributed to this extension + hook + isUserInitiated=false in
|
|
165
|
+
* ext_llm_calls.
|
|
166
|
+
* 2. Wall-clock timing measurement (only when the sample roll passes).
|
|
167
|
+
* 3. One fire-and-forget ext_hook_events row per sampled invocation,
|
|
168
|
+
* capturing duration_ms + ok + error_code + sample_rate.
|
|
169
|
+
*
|
|
170
|
+
* High-frequency hooks (tool_*) are sampled at 10% so the table doesn't
|
|
171
|
+
* drown in tool-execution rows; the sample_rate column on each row lets
|
|
172
|
+
* dashboards extrapolate with `count(*) * 1/sample_rate`. Sampling decision
|
|
173
|
+
* sits inside the AsyncLocalStorage frame so even skipped-emit hooks still
|
|
174
|
+
* attribute their LLM calls.
|
|
175
|
+
*/
|
|
176
|
+
private invokeHookHandler;
|
|
177
|
+
/**
|
|
178
|
+
* Single chokepoint for slash command dispatch. All modes (interactive /
|
|
179
|
+
* print / rpc / acp) funnel through agent-session._tryExecuteExtensionCommand,
|
|
180
|
+
* which calls this method. The wrapper measures wall-clock duration,
|
|
181
|
+
* captures outcome (ok / error / cancelled), and emits one telemetry row
|
|
182
|
+
* per invocation. Errors are still routed through emitError() so existing
|
|
183
|
+
* UI surfaces (toasts, logs) keep working unchanged.
|
|
184
|
+
*
|
|
185
|
+
* Returns `{ found: false }` when no extension owns the command, letting
|
|
186
|
+
* the caller fall through to built-in command handling without emitting a
|
|
187
|
+
* telemetry row for an unknown command.
|
|
188
|
+
*/
|
|
189
|
+
invokeCommand(commandName: string, args: string, ctx: ExtensionCommandContext, metadata?: {
|
|
190
|
+
sessionId?: string | null;
|
|
191
|
+
runId?: string | null;
|
|
192
|
+
variant?: string | null;
|
|
193
|
+
}): Promise<{
|
|
194
|
+
found: boolean;
|
|
195
|
+
}>;
|
|
130
196
|
/**
|
|
131
197
|
* Request a graceful shutdown. Called by extension tools and event handlers.
|
|
132
198
|
* The actual shutdown behavior is provided by the mode via bindExtensions().
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { theme } from "../../modes/interactive/theme/theme.js";
|
|
2
|
+
import { classifyArgsSignature, HOOK_SAMPLE_RATES, runWithExtCallerContext, } from "../telemetry/index.js";
|
|
2
3
|
// Keybindings for these actions cannot be overridden by extensions
|
|
3
4
|
const RESERVED_ACTIONS_FOR_EXTENSION_CONFLICTS = [
|
|
4
5
|
"interrupt",
|
|
@@ -108,6 +109,7 @@ export class ExtensionRunner {
|
|
|
108
109
|
shutdownHandler = () => { };
|
|
109
110
|
shortcutDiagnostics = [];
|
|
110
111
|
commandDiagnostics = [];
|
|
112
|
+
telemetrySink;
|
|
111
113
|
_beforeAgentStartTimeoutMs = 1500;
|
|
112
114
|
get beforeAgentStartTimeoutMs() { return this._beforeAgentStartTimeoutMs ?? 1500; }
|
|
113
115
|
beforeAgentStartTimeoutSentinel = Symbol("before_agent_start_timeout");
|
|
@@ -378,6 +380,184 @@ export class ExtensionRunner {
|
|
|
378
380
|
}
|
|
379
381
|
return undefined;
|
|
380
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* Returns the owning Extension for a given slash command name. Used by the
|
|
385
|
+
* telemetry middleware to stamp `extension_name` on each ext_command_events
|
|
386
|
+
* row. Falls back to undefined when no extension claims the command.
|
|
387
|
+
*/
|
|
388
|
+
findCommandOwner(name) {
|
|
389
|
+
for (const ext of this.extensions) {
|
|
390
|
+
if (ext.commands.has(name))
|
|
391
|
+
return ext;
|
|
392
|
+
}
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Derive a short, stable extension name from an Extension record. For
|
|
397
|
+
* built-ins (extensions/defaults/<name>) and most user extensions
|
|
398
|
+
* (packages/<name>) the directory basename is the right answer.
|
|
399
|
+
*/
|
|
400
|
+
deriveExtensionName(ext) {
|
|
401
|
+
const path = ext.path || ext.resolvedPath || "";
|
|
402
|
+
const segments = path.replace(/\/+$/, "").split(/[\\/]/);
|
|
403
|
+
return segments[segments.length - 1] || "unknown";
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Attach (or replace) the extension telemetry sink. The runner owns sink
|
|
407
|
+
* lifecycle from this point — invokeCommand() will fire-and-forget one
|
|
408
|
+
* `ext_command_events` row per invocation, and writeLlmCallEvent() (called
|
|
409
|
+
* from extension-core-bindings) writes one `ext_llm_calls` row per
|
|
410
|
+
* extension-initiated LLM call. Passing the noop sink (the factory's
|
|
411
|
+
* default when no insforge credentials exist) is the safe way to disable
|
|
412
|
+
* telemetry without scattering null-checks at the call sites.
|
|
413
|
+
*/
|
|
414
|
+
setTelemetrySink(sink) {
|
|
415
|
+
this.telemetrySink = sink;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Passthrough used by core/runtime/extension-core-bindings.ts after each
|
|
419
|
+
* extension-initiated LLM call. The runner owns the sink; the binding
|
|
420
|
+
* doesn't import it directly to keep its concerns scoped to LLM plumbing.
|
|
421
|
+
*/
|
|
422
|
+
writeLlmCallEvent(input) {
|
|
423
|
+
try {
|
|
424
|
+
this.telemetrySink?.writeLlmCallEvent(input);
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
// Telemetry must never destabilize the LLM call path.
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Wrap an extension hook handler invocation in three layers:
|
|
432
|
+
*
|
|
433
|
+
* 1. AsyncLocalStorage frame so any LLM call placed by the handler gets
|
|
434
|
+
* attributed to this extension + hook + isUserInitiated=false in
|
|
435
|
+
* ext_llm_calls.
|
|
436
|
+
* 2. Wall-clock timing measurement (only when the sample roll passes).
|
|
437
|
+
* 3. One fire-and-forget ext_hook_events row per sampled invocation,
|
|
438
|
+
* capturing duration_ms + ok + error_code + sample_rate.
|
|
439
|
+
*
|
|
440
|
+
* High-frequency hooks (tool_*) are sampled at 10% so the table doesn't
|
|
441
|
+
* drown in tool-execution rows; the sample_rate column on each row lets
|
|
442
|
+
* dashboards extrapolate with `count(*) * 1/sample_rate`. Sampling decision
|
|
443
|
+
* sits inside the AsyncLocalStorage frame so even skipped-emit hooks still
|
|
444
|
+
* attribute their LLM calls.
|
|
445
|
+
*/
|
|
446
|
+
invokeHookHandler(ext, hookName, fn) {
|
|
447
|
+
const extensionName = this.deriveExtensionName(ext);
|
|
448
|
+
const ctx = {
|
|
449
|
+
extensionName,
|
|
450
|
+
callerContext: `hook:${hookName}`,
|
|
451
|
+
isUserInitiated: false,
|
|
452
|
+
};
|
|
453
|
+
return runWithExtCallerContext(ctx, async () => {
|
|
454
|
+
const sampleRate = HOOK_SAMPLE_RATES[hookName] ?? 1.0;
|
|
455
|
+
if (sampleRate < 1.0 && Math.random() >= sampleRate) {
|
|
456
|
+
return await fn();
|
|
457
|
+
}
|
|
458
|
+
const recordedAt = new Date();
|
|
459
|
+
const startPerf = performance.now();
|
|
460
|
+
let ok = true;
|
|
461
|
+
let errorCode = null;
|
|
462
|
+
try {
|
|
463
|
+
return await fn();
|
|
464
|
+
}
|
|
465
|
+
catch (err) {
|
|
466
|
+
ok = false;
|
|
467
|
+
errorCode = err instanceof Error ? err.constructor.name : "unknown";
|
|
468
|
+
throw err;
|
|
469
|
+
}
|
|
470
|
+
finally {
|
|
471
|
+
try {
|
|
472
|
+
this.telemetrySink?.writeHookEvent({
|
|
473
|
+
extensionName,
|
|
474
|
+
hookName,
|
|
475
|
+
durationMs: Math.round(performance.now() - startPerf),
|
|
476
|
+
ok,
|
|
477
|
+
errorCode,
|
|
478
|
+
sampleRate,
|
|
479
|
+
recordedAt,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
catch {
|
|
483
|
+
// Telemetry never destabilizes hook dispatch.
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Single chokepoint for slash command dispatch. All modes (interactive /
|
|
490
|
+
* print / rpc / acp) funnel through agent-session._tryExecuteExtensionCommand,
|
|
491
|
+
* which calls this method. The wrapper measures wall-clock duration,
|
|
492
|
+
* captures outcome (ok / error / cancelled), and emits one telemetry row
|
|
493
|
+
* per invocation. Errors are still routed through emitError() so existing
|
|
494
|
+
* UI surfaces (toasts, logs) keep working unchanged.
|
|
495
|
+
*
|
|
496
|
+
* Returns `{ found: false }` when no extension owns the command, letting
|
|
497
|
+
* the caller fall through to built-in command handling without emitting a
|
|
498
|
+
* telemetry row for an unknown command.
|
|
499
|
+
*/
|
|
500
|
+
async invokeCommand(commandName, args, ctx, metadata) {
|
|
501
|
+
const command = this.getCommand(commandName);
|
|
502
|
+
if (!command)
|
|
503
|
+
return { found: false };
|
|
504
|
+
const ownerExt = this.findCommandOwner(commandName);
|
|
505
|
+
const extensionName = ownerExt ? this.deriveExtensionName(ownerExt) : "unknown";
|
|
506
|
+
const startedAt = new Date();
|
|
507
|
+
const startPerf = performance.now();
|
|
508
|
+
let outcome = "ok";
|
|
509
|
+
let errorCode = null;
|
|
510
|
+
let thrown;
|
|
511
|
+
const argsSig = classifyArgsSignature(args);
|
|
512
|
+
const callerCtx = {
|
|
513
|
+
extensionName,
|
|
514
|
+
callerContext: argsSig === "no-args" ? `command:/${commandName}` : `command:/${commandName} ${argsSig}`,
|
|
515
|
+
isUserInitiated: true,
|
|
516
|
+
sessionId: metadata?.sessionId ?? null,
|
|
517
|
+
runId: metadata?.runId ?? null,
|
|
518
|
+
variant: metadata?.variant ?? null,
|
|
519
|
+
};
|
|
520
|
+
try {
|
|
521
|
+
await runWithExtCallerContext(callerCtx, () => command.handler(args, ctx));
|
|
522
|
+
}
|
|
523
|
+
catch (err) {
|
|
524
|
+
outcome = "error";
|
|
525
|
+
errorCode = err instanceof Error ? err.constructor.name : "unknown";
|
|
526
|
+
thrown = err;
|
|
527
|
+
this.emitError({
|
|
528
|
+
extensionPath: `command:${commandName}`,
|
|
529
|
+
event: "command",
|
|
530
|
+
error: err instanceof Error ? err.message : String(err),
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
const durationMs = Math.round(performance.now() - startPerf);
|
|
534
|
+
const endedAt = new Date();
|
|
535
|
+
try {
|
|
536
|
+
this.telemetrySink?.writeCommandEvent({
|
|
537
|
+
extensionName,
|
|
538
|
+
commandName,
|
|
539
|
+
argsSignature: argsSig,
|
|
540
|
+
argsLength: args.length,
|
|
541
|
+
outcome,
|
|
542
|
+
errorCode,
|
|
543
|
+
durationMs,
|
|
544
|
+
startedAt,
|
|
545
|
+
endedAt,
|
|
546
|
+
sessionId: metadata?.sessionId ?? null,
|
|
547
|
+
runId: metadata?.runId ?? null,
|
|
548
|
+
variant: metadata?.variant ?? null,
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
catch {
|
|
552
|
+
// Sink emission must never bring down command dispatch.
|
|
553
|
+
}
|
|
554
|
+
// Even though we logged the error to telemetry + emitError, callers
|
|
555
|
+
// expecting the thrown error (e.g. AgentSession previously re-caught it
|
|
556
|
+
// silently) won't see it. This matches the original behaviour: the old
|
|
557
|
+
// _tryExecuteExtensionCommand swallowed the error after emitError too.
|
|
558
|
+
void thrown;
|
|
559
|
+
return { found: true };
|
|
560
|
+
}
|
|
381
561
|
/**
|
|
382
562
|
* Request a graceful shutdown. Called by extension tools and event handlers.
|
|
383
563
|
* The actual shutdown behavior is provided by the mode via bindExtensions().
|
|
@@ -444,7 +624,7 @@ export class ExtensionRunner {
|
|
|
444
624
|
continue;
|
|
445
625
|
for (const handler of handlers) {
|
|
446
626
|
try {
|
|
447
|
-
const handlerResult = await handler(event, ctx);
|
|
627
|
+
const handlerResult = await this.invokeHookHandler(ext, event.type, () => handler(event, ctx));
|
|
448
628
|
if (this.isSessionBeforeEvent(event) && handlerResult) {
|
|
449
629
|
result = handlerResult;
|
|
450
630
|
if (result.cancel) {
|
|
@@ -476,7 +656,7 @@ export class ExtensionRunner {
|
|
|
476
656
|
continue;
|
|
477
657
|
for (const handler of handlers) {
|
|
478
658
|
try {
|
|
479
|
-
const handlerResult = (await handler(currentEvent, ctx));
|
|
659
|
+
const handlerResult = (await this.invokeHookHandler(ext, "tool_result", () => handler(currentEvent, ctx)));
|
|
480
660
|
if (!handlerResult)
|
|
481
661
|
continue;
|
|
482
662
|
if (handlerResult.content !== undefined) {
|
|
@@ -521,7 +701,7 @@ export class ExtensionRunner {
|
|
|
521
701
|
if (!handlers || handlers.length === 0)
|
|
522
702
|
continue;
|
|
523
703
|
for (const handler of handlers) {
|
|
524
|
-
const handlerResult = await handler(event, ctx);
|
|
704
|
+
const handlerResult = await this.invokeHookHandler(ext, "tool_call", () => handler(event, ctx));
|
|
525
705
|
if (handlerResult) {
|
|
526
706
|
result = handlerResult;
|
|
527
707
|
if (result.block) {
|
|
@@ -540,7 +720,7 @@ export class ExtensionRunner {
|
|
|
540
720
|
continue;
|
|
541
721
|
for (const handler of handlers) {
|
|
542
722
|
try {
|
|
543
|
-
const handlerResult = await handler(event, ctx);
|
|
723
|
+
const handlerResult = await this.invokeHookHandler(ext, "user_bash", () => handler(event, ctx));
|
|
544
724
|
if (handlerResult) {
|
|
545
725
|
return handlerResult;
|
|
546
726
|
}
|
|
@@ -569,7 +749,7 @@ export class ExtensionRunner {
|
|
|
569
749
|
for (const handler of handlers) {
|
|
570
750
|
try {
|
|
571
751
|
const event = { type: "context", messages: currentMessages };
|
|
572
|
-
const handlerResult = await handler(event, ctx);
|
|
752
|
+
const handlerResult = await this.invokeHookHandler(ext, "context", () => handler(event, ctx));
|
|
573
753
|
if (handlerResult && handlerResult.messages) {
|
|
574
754
|
currentMessages = handlerResult.messages;
|
|
575
755
|
}
|
|
@@ -605,7 +785,7 @@ export class ExtensionRunner {
|
|
|
605
785
|
images,
|
|
606
786
|
systemPrompt: currentSystemPrompt,
|
|
607
787
|
};
|
|
608
|
-
const handlerResult = await this.withTimeout(handler(event, ctx), this.beforeAgentStartTimeoutMs);
|
|
788
|
+
const handlerResult = await this.withTimeout(this.invokeHookHandler(ext, "before_agent_start", () => handler(event, ctx)), this.beforeAgentStartTimeoutMs);
|
|
609
789
|
if (handlerResult === this.beforeAgentStartTimeoutSentinel) {
|
|
610
790
|
this.reportBeforeAgentStartTimeout(ext.path);
|
|
611
791
|
continue;
|
|
@@ -663,7 +843,7 @@ export class ExtensionRunner {
|
|
|
663
843
|
for (const handler of handlers) {
|
|
664
844
|
try {
|
|
665
845
|
const event = { type: "resources_discover", cwd, reason };
|
|
666
|
-
const handlerResult = await handler(event, ctx);
|
|
846
|
+
const handlerResult = await this.invokeHookHandler(ext, "resources_discover", () => handler(event, ctx));
|
|
667
847
|
const result = handlerResult;
|
|
668
848
|
if (result?.skillPaths?.length) {
|
|
669
849
|
skillPaths.push(...result.skillPaths.map((path) => ({ path, extensionPath: ext.path })));
|
|
@@ -698,7 +878,7 @@ export class ExtensionRunner {
|
|
|
698
878
|
for (const handler of ext.handlers.get("input") ?? []) {
|
|
699
879
|
try {
|
|
700
880
|
const event = { type: "input", text: currentText, images: currentImages, source };
|
|
701
|
-
const result = (await handler(event, ctx));
|
|
881
|
+
const result = (await this.invokeHookHandler(ext, "input", () => handler(event, ctx)));
|
|
702
882
|
if (result?.action === "handled")
|
|
703
883
|
return result;
|
|
704
884
|
if (result?.action === "transform") {
|