@pencil-agent/nano-pencil 1.13.8 → 1.13.10
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.js +22 -0
- package/dist/core/config/settings-manager.d.ts +7 -0
- package/dist/core/runtime/pencil-agent.d.ts +28 -1
- package/dist/core/runtime/pencil-agent.js +56 -0
- package/dist/core/sub-agent/index.d.ts +1 -1
- package/dist/core/sub-agent/sub-agent-backend.js +78 -12
- package/dist/core/sub-agent/sub-agent-types.d.ts +44 -1
- package/dist/core/sub-agent/sub-agent-types.js +1 -1
- package/dist/extensions/defaults/AGENT.md +7 -2
- package/dist/extensions/defaults/CLAUDE.md +5 -4
- package/dist/extensions/defaults/diagnostics/diagnostic-buffer.d.ts +19 -0
- package/dist/extensions/defaults/diagnostics/diagnostic-buffer.js +125 -0
- package/dist/extensions/defaults/diagnostics/index.d.ts +8 -0
- package/dist/extensions/defaults/diagnostics/index.js +101 -0
- package/dist/extensions/defaults/diagnostics/redaction.d.ts +8 -0
- package/dist/extensions/defaults/diagnostics/redaction.js +45 -0
- package/dist/extensions/defaults/diagnostics/reporter.d.ts +17 -0
- package/dist/extensions/defaults/diagnostics/reporter.js +203 -0
- package/dist/extensions/defaults/diagnostics/types.d.ts +61 -0
- package/dist/extensions/defaults/diagnostics/types.js +7 -0
- package/dist/extensions/defaults/grub/grub-controller.d.ts +2 -0
- package/dist/extensions/defaults/grub/grub-controller.js +79 -24
- package/dist/extensions/defaults/grub/grub-i18n.d.ts +128 -0
- package/dist/extensions/defaults/grub/grub-i18n.js +167 -0
- package/dist/extensions/defaults/grub/grub-parser.d.ts +2 -1
- package/dist/extensions/defaults/grub/grub-parser.js +5 -3
- package/dist/extensions/defaults/grub/grub-types.d.ts +3 -0
- package/dist/extensions/defaults/grub/index.js +133 -78
- package/dist/extensions/defaults/idle-think/curiosity.d.ts +46 -0
- package/dist/extensions/defaults/idle-think/curiosity.js +137 -0
- package/dist/extensions/defaults/idle-think/index.d.ts +15 -0
- package/dist/extensions/defaults/idle-think/index.js +169 -0
- package/dist/extensions/defaults/idle-think/insights.d.ts +40 -0
- package/dist/extensions/defaults/idle-think/insights.js +123 -0
- package/dist/extensions/defaults/idle-think/thinker.d.ts +26 -0
- package/dist/extensions/defaults/idle-think/thinker.js +208 -0
- package/dist/extensions/defaults/presence/index.d.ts +1 -0
- package/dist/extensions/defaults/presence/index.js +83 -10
- package/dist/extensions/defaults/sal/eval/index.js +22 -3
- package/dist/extensions/defaults/sal/eval/insforge-sink.d.ts +3 -1
- package/dist/extensions/defaults/sal/eval/insforge-sink.js +46 -25
- package/dist/extensions/defaults/sal/eval/jsonl-sink.d.ts +2 -0
- package/dist/extensions/defaults/sal/eval/jsonl-sink.js +15 -2
- package/dist/extensions/defaults/sal/eval/types.d.ts +10 -0
- package/dist/extensions/defaults/sal/index.js +74 -18
- package/dist/extensions/defaults/team/CLAUDE.md +7 -6
- package/dist/extensions/defaults/team/index.d.ts +1 -0
- package/dist/extensions/defaults/team/index.js +92 -3
- package/dist/extensions/defaults/team/team-dashboard.js +6 -0
- package/dist/extensions/defaults/team/team-parser.d.ts +1 -1
- package/dist/extensions/defaults/team/team-parser.js +3 -2
- package/dist/extensions/defaults/team/team-presets.d.ts +14 -2
- package/dist/extensions/defaults/team/team-presets.js +124 -4
- package/dist/extensions/defaults/team/team-runtime.d.ts +15 -2
- package/dist/extensions/defaults/team/team-runtime.js +62 -1
- package/dist/extensions/defaults/team/team-types.d.ts +9 -0
- package/dist/modes/interactive/components/pencil-loader.d.ts +0 -2
- package/dist/modes/interactive/components/pencil-loader.js +1 -14
- package/dist/node_modules/@pencil-agent/ai/models.generated.d.ts +68 -17
- package/dist/node_modules/@pencil-agent/ai/models.generated.js +81 -30
- package/dist/packages/mem-core/consolidation.js +14 -1
- package/dist/packages/mem-core/diagnostics.d.ts +24 -0
- package/dist/packages/mem-core/diagnostics.js +62 -0
- package/dist/packages/mem-core/extension.js +48 -28
- package/dist/packages/mem-core/extraction.js +14 -1
- package/dist/packages/soul-core/diagnostics.d.ts +24 -0
- package/dist/packages/soul-core/diagnostics.js +62 -0
- package/dist/packages/soul-core/manager.js +13 -2
- package/dist/packages/soul-core/src/diagnostics.d.ts +23 -0
- package/dist/packages/soul-core/src/diagnostics.js +61 -0
- package/dist/packages/soul-core/src/manager.js +13 -2
- package/dist/utils/diagnostics.d.ts +38 -0
- package/dist/utils/diagnostics.js +89 -0
- package/package.json +1 -1
- package/docs/loop /351/207/215/346/236/204/345/256/214/346/210/220/346/200/273/347/273/223.md" +0 -251
- package/docs/loop /351/207/215/346/236/204/345/256/214/346/210/220/346/212/245/345/221/212.md" +0 -123
- package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210.md" +0 -1222
- package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210/345/256/236/347/216/260/346/212/245/345/221/212.md" +0 -158
- package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210/345/257/271/346/257/224/345/210/206/346/236/220.md" +0 -128
- package/docs/loop /351/207/215/346/236/204/350/256/241/345/210/222.md" +0 -321
- package/docs/loop-usage-examples.md +0 -215
- package/docs/planmode.md +0 -1987
package/dist/build-meta.json
CHANGED
|
@@ -20,10 +20,12 @@ const BUNDLED_PRESENCE_EXTENSION = join(__dirname, "extensions", "defaults", "pr
|
|
|
20
20
|
const BUNDLED_INTERVIEW_EXTENSION = join(__dirname, "extensions", "defaults", "interview", "index.js");
|
|
21
21
|
const BUNDLED_LOOP_EXTENSION = join(__dirname, "extensions", "defaults", "loop", "index.js");
|
|
22
22
|
const BUNDLED_PLAN_EXTENSION = join(__dirname, "extensions", "defaults", "plan", "index.js");
|
|
23
|
+
const BUNDLED_DIAGNOSTICS_EXTENSION = join(__dirname, "extensions", "defaults", "diagnostics", "index.js");
|
|
23
24
|
const BUNDLED_SAL_EXTENSION = join(__dirname, "extensions", "defaults", "sal", "index.js");
|
|
24
25
|
const BUNDLED_GRUB_EXTENSION = join(__dirname, "extensions", "defaults", "grub", "index.js");
|
|
25
26
|
const BUNDLED_SUBAGENT_EXTENSION = join(__dirname, "extensions", "defaults", "subagent", "index.js");
|
|
26
27
|
const BUNDLED_TEAM_EXTENSION = join(__dirname, "extensions", "defaults", "team", "index.js");
|
|
28
|
+
const BUNDLED_IDLE_THINK_EXTENSION = join(__dirname, "extensions", "defaults", "idle-think", "index.js");
|
|
27
29
|
const BUNDLED_BTW_EXTENSION = join(__dirname, "extensions", "defaults", "btw", "index.js");
|
|
28
30
|
const BUNDLED_DEBUG_EXTENSION = join(__dirname, "extensions", "defaults", "debug", "index.js");
|
|
29
31
|
const BUNDLED_MCP_EXTENSION = join(__dirname, "extensions", "defaults", "mcp", "index.js");
|
|
@@ -67,6 +69,17 @@ function findPackageRoot(startDir) {
|
|
|
67
69
|
*/
|
|
68
70
|
export function getBuiltinExtensionPaths() {
|
|
69
71
|
const paths = [];
|
|
72
|
+
// === Diagnostics extension (extension-owned issue buffer and reporting) ===
|
|
73
|
+
// Loaded first so it can subscribe to diagnostic:event before producer
|
|
74
|
+
// extensions such as SAL publish background failures.
|
|
75
|
+
if (existsSync(BUNDLED_DIAGNOSTICS_EXTENSION)) {
|
|
76
|
+
paths.push(BUNDLED_DIAGNOSTICS_EXTENSION);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const diagnosticsTs = join(__dirname, "extensions", "defaults", "diagnostics", "index.ts");
|
|
80
|
+
if (existsSync(diagnosticsTs))
|
|
81
|
+
paths.push(diagnosticsTs);
|
|
82
|
+
}
|
|
70
83
|
// === SAL extension (Structural Anchor Localization, default-on, experimental) ===
|
|
71
84
|
// Loaded ahead of NanoMem because turn-context producers must publish before
|
|
72
85
|
// turn-context consumers read. SAL is a producer of structuralAnchor; NanoMem
|
|
@@ -211,6 +224,15 @@ export function getBuiltinExtensionPaths() {
|
|
|
211
224
|
if (existsSync(teamTs))
|
|
212
225
|
paths.push(teamTs);
|
|
213
226
|
}
|
|
227
|
+
// === IdleThink extension (background code exploration during idle) ===
|
|
228
|
+
if (existsSync(BUNDLED_IDLE_THINK_EXTENSION)) {
|
|
229
|
+
paths.push(BUNDLED_IDLE_THINK_EXTENSION);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
const idleThinkTs = join(__dirname, "extensions", "defaults", "idle-think", "index.ts");
|
|
233
|
+
if (existsSync(idleThinkTs))
|
|
234
|
+
paths.push(idleThinkTs);
|
|
235
|
+
}
|
|
214
236
|
// === BTW extension (quick side question without interrupting) ===
|
|
215
237
|
if (existsSync(BUNDLED_BTW_EXTENSION)) {
|
|
216
238
|
paths.push(BUNDLED_BTW_EXTENSION);
|
|
@@ -105,6 +105,13 @@ export interface Settings {
|
|
|
105
105
|
presence?: {
|
|
106
106
|
enabled?: boolean;
|
|
107
107
|
};
|
|
108
|
+
/** IdleThink extension settings - background code exploration during idle */
|
|
109
|
+
idleThink?: {
|
|
110
|
+
enabled?: boolean;
|
|
111
|
+
idleMinutes?: number;
|
|
112
|
+
dailyBudget?: number;
|
|
113
|
+
maxDurationMinutes?: number;
|
|
114
|
+
};
|
|
108
115
|
/** Auto-update setting: 'always' = auto-update on startup, 'prompt' = ask user (default), 'never' = never check */
|
|
109
116
|
autoUpdate?: "always" | "prompt" | "never";
|
|
110
117
|
/** Last skipped version for update prompts */
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { type SDKLogger } from "./sdk.js";
|
|
8
8
|
import { type AgentSessionEvent } from "./agent-session.js";
|
|
9
|
+
import type { Api } from "@pencil-agent/ai";
|
|
9
10
|
import type { ThinkingLevel } from "@pencil-agent/agent-core";
|
|
10
11
|
/**
|
|
11
12
|
* Simplified options for PencilAgent wrapper.
|
|
@@ -13,10 +14,21 @@ import type { ThinkingLevel } from "@pencil-agent/agent-core";
|
|
|
13
14
|
export interface PencilAgentOptions {
|
|
14
15
|
/** API key for the provider. If omitted, uses environment variable. */
|
|
15
16
|
apiKey?: string;
|
|
16
|
-
/** Provider name: 'anthropic', 'openai', 'google',
|
|
17
|
+
/** Provider name: 'anthropic', 'openai', 'google', or any custom provider in models.json. */
|
|
17
18
|
provider?: string;
|
|
18
19
|
/** Model ID: 'claude-4-5-20250920', 'gpt-4o', etc. */
|
|
19
20
|
model?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Optional base URL when registering a custom provider on the fly.
|
|
23
|
+
* Required when `provider` + `model` is not already defined in
|
|
24
|
+
* ~/.nanopencil/agent/models.json. Ignored when the model is found.
|
|
25
|
+
*/
|
|
26
|
+
baseUrl?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Optional API protocol for the dynamically-registered provider.
|
|
29
|
+
* Defaults to "openai-completions". Ignored when the model is found.
|
|
30
|
+
*/
|
|
31
|
+
api?: Api;
|
|
20
32
|
/** Thinking level: 'off' | 'low' | 'medium' | 'high' */
|
|
21
33
|
thinkingLevel?: ThinkingLevel;
|
|
22
34
|
/** Working directory. Default: process.cwd() */
|
|
@@ -69,6 +81,21 @@ export declare class PencilAgent {
|
|
|
69
81
|
* Must be called before run/chat.
|
|
70
82
|
*/
|
|
71
83
|
init(): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Resolve constructor-provided provider/model into a Model<any>.
|
|
86
|
+
*
|
|
87
|
+
* Lookup order:
|
|
88
|
+
* 1. Existing entry in modelRegistry (e.g. ~/.nanopencil/agent/models.json
|
|
89
|
+
* already declares this provider/model — common case for users who ran
|
|
90
|
+
* /sal:setup or hand-edited models.json).
|
|
91
|
+
* 2. Dynamic registration when caller supplied baseUrl + apiKey — lets a
|
|
92
|
+
* one-line constructor call wire up a brand-new OpenAI-compatible
|
|
93
|
+
* endpoint without touching disk.
|
|
94
|
+
* 3. Otherwise return undefined and let createAgentSession fall back to
|
|
95
|
+
* findInitialModel() (built-in default). The logger surfaces a warning
|
|
96
|
+
* in this case so the caller knows their args were not honoured.
|
|
97
|
+
*/
|
|
98
|
+
private resolveRequestedModel;
|
|
72
99
|
/**
|
|
73
100
|
* Handle session events - collects text for run()
|
|
74
101
|
*/
|
|
@@ -63,6 +63,10 @@ export class PencilAgent {
|
|
|
63
63
|
key: this.options.apiKey,
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
|
+
// Resolve user-specified provider/model into a Model<any> for createAgentSession.
|
|
67
|
+
// Without this, createAgentSession falls back to findInitialModel() which
|
|
68
|
+
// picks the first available built-in — silently ignoring constructor args.
|
|
69
|
+
const resolvedModel = this.resolveRequestedModel(modelRegistry);
|
|
66
70
|
// Resolve tools
|
|
67
71
|
let tools = undefined;
|
|
68
72
|
if (this.options.tools && this.options.tools.length > 0) {
|
|
@@ -73,6 +77,7 @@ export class PencilAgent {
|
|
|
73
77
|
// Create session
|
|
74
78
|
this.sessionResult = await createAgentSession({
|
|
75
79
|
cwd: this.cwd,
|
|
80
|
+
model: resolvedModel,
|
|
76
81
|
thinkingLevel: this.options.thinkingLevel,
|
|
77
82
|
tools,
|
|
78
83
|
authStorage,
|
|
@@ -94,6 +99,57 @@ export class PencilAgent {
|
|
|
94
99
|
this.session.subscribe(this.handleEvent.bind(this));
|
|
95
100
|
this.initialized = true;
|
|
96
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Resolve constructor-provided provider/model into a Model<any>.
|
|
104
|
+
*
|
|
105
|
+
* Lookup order:
|
|
106
|
+
* 1. Existing entry in modelRegistry (e.g. ~/.nanopencil/agent/models.json
|
|
107
|
+
* already declares this provider/model — common case for users who ran
|
|
108
|
+
* /sal:setup or hand-edited models.json).
|
|
109
|
+
* 2. Dynamic registration when caller supplied baseUrl + apiKey — lets a
|
|
110
|
+
* one-line constructor call wire up a brand-new OpenAI-compatible
|
|
111
|
+
* endpoint without touching disk.
|
|
112
|
+
* 3. Otherwise return undefined and let createAgentSession fall back to
|
|
113
|
+
* findInitialModel() (built-in default). The logger surfaces a warning
|
|
114
|
+
* in this case so the caller knows their args were not honoured.
|
|
115
|
+
*/
|
|
116
|
+
resolveRequestedModel(registry) {
|
|
117
|
+
const provider = this.options.provider;
|
|
118
|
+
const modelId = this.options.model;
|
|
119
|
+
if (!provider || !modelId)
|
|
120
|
+
return undefined;
|
|
121
|
+
const existing = registry.find(provider, modelId);
|
|
122
|
+
if (existing)
|
|
123
|
+
return existing;
|
|
124
|
+
if (this.options.baseUrl) {
|
|
125
|
+
try {
|
|
126
|
+
registry.registerProvider(provider, {
|
|
127
|
+
api: this.options.api ?? "openai-completions",
|
|
128
|
+
baseUrl: this.options.baseUrl,
|
|
129
|
+
apiKey: this.options.apiKey,
|
|
130
|
+
models: [{
|
|
131
|
+
id: modelId,
|
|
132
|
+
name: modelId,
|
|
133
|
+
reasoning: false,
|
|
134
|
+
input: ["text"],
|
|
135
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
136
|
+
contextWindow: 128_000,
|
|
137
|
+
maxTokens: 8192,
|
|
138
|
+
}],
|
|
139
|
+
});
|
|
140
|
+
const registered = registry.find(provider, modelId);
|
|
141
|
+
if (registered)
|
|
142
|
+
return registered;
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
this.logger.warn(`[PencilAgent] dynamic provider registration failed for ${provider}/${modelId}: ${err.message}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
this.logger.warn(`[PencilAgent] model ${provider}/${modelId} not found in registry. ` +
|
|
149
|
+
`Either add it to ~/.nanopencil/agent/models.json or pass { baseUrl, apiKey } to register it dynamically. ` +
|
|
150
|
+
`Falling back to default model selection.`);
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
97
153
|
/**
|
|
98
154
|
* Handle session events - collects text for run()
|
|
99
155
|
*/
|
|
@@ -8,4 +8,4 @@ export { SubAgentRuntime, subAgentRuntime } from "./sub-agent-runtime.js";
|
|
|
8
8
|
export { InProcessSubAgentBackend } from "./sub-agent-backend.js";
|
|
9
9
|
export { SubprocessSubAgentBackend } from "./subprocess-backend.js";
|
|
10
10
|
export type { SubprocessBackendOptions } from "./subprocess-backend.js";
|
|
11
|
-
export type { SubAgentSpec, SubAgentResult, SubAgentHandle, SubAgentBackend, } from "./sub-agent-types.js";
|
|
11
|
+
export type { SubAgentSpec, SubAgentEvent, SubAgentResult, SubAgentHandle, SubAgentBackend, } from "./sub-agent-types.js";
|
|
@@ -32,6 +32,12 @@ export class InProcessSubAgentBackend {
|
|
|
32
32
|
model: spec.model,
|
|
33
33
|
};
|
|
34
34
|
const { session } = await createAgentSession(options);
|
|
35
|
+
const unsubscribe = session.subscribe((event) => {
|
|
36
|
+
const subAgentEvent = toSubAgentEvent(id, event);
|
|
37
|
+
if (subAgentEvent) {
|
|
38
|
+
spec.onEvent?.(subAgentEvent);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
35
41
|
const timeoutMs = spec.timeoutMs;
|
|
36
42
|
let status = "running";
|
|
37
43
|
let result;
|
|
@@ -44,21 +50,10 @@ export class InProcessSubAgentBackend {
|
|
|
44
50
|
}
|
|
45
51
|
}, timeoutMs);
|
|
46
52
|
}
|
|
47
|
-
// Extract text from assistant message content
|
|
48
|
-
const extractTextFromContent = (content) => {
|
|
49
|
-
if (typeof content === "string")
|
|
50
|
-
return content;
|
|
51
|
-
if (Array.isArray(content)) {
|
|
52
|
-
return content
|
|
53
|
-
.filter((part) => typeof part === "object" && part !== null && "type" in part && part.type === "text" && typeof part.text === "string")
|
|
54
|
-
.map((part) => part.text)
|
|
55
|
-
.join("\n");
|
|
56
|
-
}
|
|
57
|
-
return "";
|
|
58
|
-
};
|
|
59
53
|
// Start the prompt
|
|
60
54
|
const promptPromise = (async () => {
|
|
61
55
|
try {
|
|
56
|
+
spec.onEvent?.({ type: "agent_start", subAgentId: id, timestamp: Date.now() });
|
|
62
57
|
await session.prompt(prompt, {
|
|
63
58
|
images: spec.images,
|
|
64
59
|
});
|
|
@@ -107,6 +102,14 @@ export class InProcessSubAgentBackend {
|
|
|
107
102
|
}
|
|
108
103
|
// Clean up signal handler
|
|
109
104
|
spec.signal.removeEventListener("abort", signalHandler);
|
|
105
|
+
unsubscribe();
|
|
106
|
+
spec.onEvent?.({
|
|
107
|
+
type: "agent_end",
|
|
108
|
+
subAgentId: id,
|
|
109
|
+
timestamp: Date.now(),
|
|
110
|
+
success: result?.success ?? false,
|
|
111
|
+
error: result?.error,
|
|
112
|
+
});
|
|
110
113
|
}
|
|
111
114
|
})();
|
|
112
115
|
return {
|
|
@@ -132,6 +135,69 @@ export class InProcessSubAgentBackend {
|
|
|
132
135
|
};
|
|
133
136
|
}
|
|
134
137
|
}
|
|
138
|
+
function toSubAgentEvent(subAgentId, event) {
|
|
139
|
+
const timestamp = Date.now();
|
|
140
|
+
switch (event.type) {
|
|
141
|
+
case "message_update":
|
|
142
|
+
return {
|
|
143
|
+
type: "message_update",
|
|
144
|
+
subAgentId,
|
|
145
|
+
timestamp,
|
|
146
|
+
text: extractMessageText(event.message),
|
|
147
|
+
deltaType: event.assistantMessageEvent.type,
|
|
148
|
+
};
|
|
149
|
+
case "message_end":
|
|
150
|
+
return {
|
|
151
|
+
type: "message_end",
|
|
152
|
+
subAgentId,
|
|
153
|
+
timestamp,
|
|
154
|
+
text: extractMessageText(event.message),
|
|
155
|
+
};
|
|
156
|
+
case "tool_execution_start":
|
|
157
|
+
return {
|
|
158
|
+
type: "tool_start",
|
|
159
|
+
subAgentId,
|
|
160
|
+
timestamp,
|
|
161
|
+
toolName: event.toolName,
|
|
162
|
+
args: event.args,
|
|
163
|
+
};
|
|
164
|
+
case "tool_execution_update":
|
|
165
|
+
return {
|
|
166
|
+
type: "tool_update",
|
|
167
|
+
subAgentId,
|
|
168
|
+
timestamp,
|
|
169
|
+
toolName: event.toolName,
|
|
170
|
+
partialResult: event.partialResult,
|
|
171
|
+
};
|
|
172
|
+
case "tool_execution_end":
|
|
173
|
+
return {
|
|
174
|
+
type: "tool_end",
|
|
175
|
+
subAgentId,
|
|
176
|
+
timestamp,
|
|
177
|
+
toolName: event.toolName,
|
|
178
|
+
isError: event.isError,
|
|
179
|
+
};
|
|
180
|
+
default:
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function extractTextFromContent(content) {
|
|
185
|
+
if (typeof content === "string")
|
|
186
|
+
return content;
|
|
187
|
+
if (Array.isArray(content)) {
|
|
188
|
+
return content
|
|
189
|
+
.filter((part) => typeof part === "object" && part !== null && "type" in part && part.type === "text" && typeof part.text === "string")
|
|
190
|
+
.map((part) => part.text)
|
|
191
|
+
.join("\n");
|
|
192
|
+
}
|
|
193
|
+
return "";
|
|
194
|
+
}
|
|
195
|
+
function extractMessageText(message) {
|
|
196
|
+
if (typeof message !== "object" || message === null || !("content" in message)) {
|
|
197
|
+
return "";
|
|
198
|
+
}
|
|
199
|
+
return extractTextFromContent(message.content);
|
|
200
|
+
}
|
|
135
201
|
async function buildPromptWithContextFiles(spec) {
|
|
136
202
|
if (!spec.contextFiles?.length) {
|
|
137
203
|
return spec.prompt;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [WHO]: SubAgent types - SubAgentSpec, SubAgentHandle, SubAgentBackend, SubAgentResult
|
|
2
|
+
* [WHO]: SubAgent types - SubAgentSpec, SubAgentEvent, SubAgentHandle, SubAgentBackend, SubAgentResult
|
|
3
3
|
* [FROM]: Depends on @pencil-agent/agent-core, @pencil-agent/ai, core/tools
|
|
4
4
|
* [TO]: Consumed by ./sub-agent-runtime, ./sub-agent-backend, ./index.ts, extensions/defaults/subagent/*, extensions/defaults/team/*
|
|
5
5
|
* [HERE]: core/sub-agent/sub-agent-types.ts - SubAgent type definitions
|
|
@@ -7,6 +7,47 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { ImageContent, Model } from "@pencil-agent/ai";
|
|
9
9
|
import type { Tool } from "../tools/index.js";
|
|
10
|
+
/** Realtime lifecycle event emitted by a running SubAgent. */
|
|
11
|
+
export type SubAgentEvent = {
|
|
12
|
+
type: "agent_start";
|
|
13
|
+
subAgentId: string;
|
|
14
|
+
timestamp: number;
|
|
15
|
+
} | {
|
|
16
|
+
type: "message_update";
|
|
17
|
+
subAgentId: string;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
text: string;
|
|
20
|
+
deltaType?: string;
|
|
21
|
+
} | {
|
|
22
|
+
type: "message_end";
|
|
23
|
+
subAgentId: string;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
text: string;
|
|
26
|
+
} | {
|
|
27
|
+
type: "tool_start";
|
|
28
|
+
subAgentId: string;
|
|
29
|
+
timestamp: number;
|
|
30
|
+
toolName: string;
|
|
31
|
+
args: unknown;
|
|
32
|
+
} | {
|
|
33
|
+
type: "tool_update";
|
|
34
|
+
subAgentId: string;
|
|
35
|
+
timestamp: number;
|
|
36
|
+
toolName: string;
|
|
37
|
+
partialResult: unknown;
|
|
38
|
+
} | {
|
|
39
|
+
type: "tool_end";
|
|
40
|
+
subAgentId: string;
|
|
41
|
+
timestamp: number;
|
|
42
|
+
toolName: string;
|
|
43
|
+
isError: boolean;
|
|
44
|
+
} | {
|
|
45
|
+
type: "agent_end";
|
|
46
|
+
subAgentId: string;
|
|
47
|
+
timestamp: number;
|
|
48
|
+
success: boolean;
|
|
49
|
+
error?: string;
|
|
50
|
+
};
|
|
10
51
|
/**
|
|
11
52
|
* Specification for spawning a SubAgent.
|
|
12
53
|
*/
|
|
@@ -29,6 +70,8 @@ export interface SubAgentSpec {
|
|
|
29
70
|
contextFiles?: string[];
|
|
30
71
|
/** Optional callback invoked after the run result is available */
|
|
31
72
|
exitHook?: (result: SubAgentResult) => Promise<void> | void;
|
|
73
|
+
/** Optional realtime observer for TUI/status integrations */
|
|
74
|
+
onEvent?: (event: SubAgentEvent) => void;
|
|
32
75
|
}
|
|
33
76
|
/**
|
|
34
77
|
* Result from a completed SubAgent run.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [WHO]: SubAgent types - SubAgentSpec, SubAgentHandle, SubAgentBackend, SubAgentResult
|
|
2
|
+
* [WHO]: SubAgent types - SubAgentSpec, SubAgentEvent, SubAgentHandle, SubAgentBackend, SubAgentResult
|
|
3
3
|
* [FROM]: Depends on @pencil-agent/agent-core, @pencil-agent/ai, core/tools
|
|
4
4
|
* [TO]: Consumed by ./sub-agent-runtime, ./sub-agent-backend, ./index.ts, extensions/defaults/subagent/*, extensions/defaults/team/*
|
|
5
5
|
* [HERE]: core/sub-agent/sub-agent-types.ts - SubAgent type definitions
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
> P2 | Parent: ../AGENT.md
|
|
4
4
|
|
|
5
5
|
Member List
|
|
6
|
+
diagnostics/index.ts: Diagnostics extension entry, subscribes to diagnostic:event, buffers session-local diagnostic records, prompts only after threshold at agent_end, registers /report-issue
|
|
7
|
+
diagnostics/types.ts: Diagnostic event/report type contract and diagnostic:event channel name
|
|
8
|
+
diagnostics/diagnostic-buffer.ts: DiagnosticBuffer, event coercion, fingerprint dedupe, prompt gating
|
|
9
|
+
diagnostics/reporter.ts: User-approved InsForge pencil_issue_events reporter, configured via NANOPENCIL_ISSUE_* env vars
|
|
10
|
+
diagnostics/redaction.ts: Diagnostic message normalization and secret/path redaction helpers
|
|
6
11
|
link-world/index.ts: Internet access extension, provides internet-search Skill after setup
|
|
7
12
|
mcp/index.ts: MCP protocol integration extension, MCP guidance resources
|
|
8
13
|
presence/index.ts: AI-driven opening + idle presence lines, uses NanoMemEngine episodes/preferences/lessons + git/cwd snapshot, injects latest line into agent systemPrompt every turn for main-conversation perception, 30s debounce + idle in-flight lock, configurable via settings.presence.enabled, PRESENCE_MESSAGE_TYPE renderer
|
|
@@ -37,12 +42,12 @@ loop/scheduler-controller.ts: SchedulerController - in-memory recurring task sto
|
|
|
37
42
|
loop/scheduler-parser.ts: Loop command parsing with flags/subcommands, parseSchedulerCommand/parseDurationSpec/buildSchedulerHelp, --name/--max/--quiet
|
|
38
43
|
loop/scheduler-types.ts: Scheduled loop types, LoopPayloadKind/ScheduledLoopTask/LoopStartSpec/ParsedSchedulerCommand
|
|
39
44
|
loop/README.md: Loop extension documentation - recurring scheduler usage and flags
|
|
40
|
-
sal/index.ts: SAL extension entry, enabled by default, registers --nosal/--sal-ab/--sal-rebuild-terrain flags, /sal:coverage /sal:status /sal:setup commands, before_agent_start/tool_execution_start/agent_end hooks; /sal:setup writes ~/.memory-experiments/credentials.json with adapter inference (insforge/jsonl/noop); publishes structuralAnchor via core/runtime/turn-context (no SAL-specific globals); emits run_start/turn_anchor/run_end eval events through pluggable EvalSink with best-effort shutdown flushing; writes local .memory-experiments sidecar anchors only when --sal-ab or NANOPENCIL_SAL_AB=1 is enabled; runtime no-op when --nosal is set
|
|
45
|
+
sal/index.ts: SAL extension entry, enabled by default, registers --nosal/--sal-ab/--sal-rebuild-terrain flags, /sal:coverage /sal:status /sal:setup commands, before_agent_start/tool_execution_start/agent_end hooks; /sal:setup writes ~/.memory-experiments/credentials.json with adapter inference (insforge/jsonl/noop); publishes structuralAnchor via core/runtime/turn-context (no SAL-specific globals); emits run_start/turn_anchor/run_end eval events through pluggable EvalSink with best-effort shutdown flushing; publishes SAL eval background failures to diagnostic:event; writes local .memory-experiments sidecar anchors only when --sal-ab or NANOPENCIL_SAL_AB=1 is enabled; runtime no-op when --nosal is set
|
|
41
46
|
sal/terrain.ts: TerrainSnapshot/TerrainNode/TerrainEdge model, buildTerrainIndex(), checkDipCoverage(), isSnapshotStale(), moduleIdForPath(), parses P2 AGENT.md and P3 file headers
|
|
42
47
|
sal/anchors.ts: StructuralAnchor/AnchorResolution model, locateTask(), locateAction(), evidence-driven scoring with tunable SalWeights, CJK bigram tokenization
|
|
43
48
|
sal/weights.ts: SalWeights interface, SAL_DEFAULT_WEIGHTS, loadSalWeights() reads sal-config.json from workspace or .memory-experiments/sal/
|
|
44
49
|
sal/eval/index.ts: createEvalSink() factory + barrel re-exports; adapter selection via options.adapter or endpoint scheme inference (http(s)→insforge, file://|/|./|../→jsonl, missing→noop); ONLY entry point SAL imports from
|
|
45
|
-
sal/eval/types.ts: EvalSink interface, EvalEventEnvelope/EvalEventType (run_start/run_end/turn_anchor), EvalAdapterId ("insforge"|"jsonl"|"noop"), CreateEvalSinkOptions, createEvalEvent factory; zero-dependency type surface
|
|
50
|
+
sal/eval/types.ts: EvalSink interface, EvalEventEnvelope/EvalEventType (run_start/run_end/turn_anchor), EvalAdapterId ("insforge"|"jsonl"|"noop"), CreateEvalSinkOptions with optional onDiagnostic callback, createEvalEvent factory; zero-dependency type surface
|
|
46
51
|
sal/eval/noop-sink.ts: noopSink — silent EvalSink used when eval disabled or no adapter configured
|
|
47
52
|
sal/eval/insforge-sink.ts: InsForgeEvalSink — PostgREST adapter, routes run_start→eval_runs INSERT (merge-duplicates) with legacy-schema fallback, writes turn_anchor/tool_trace/memory_recalls/run_end only after parent run confirmation, tool_trace→eval_tool_traces with PGRST204 fallback, memory_recalls→eval_memory_recalls batch INSERT, run_end→eval_runs PATCH; allowSelfSigned TLS option logs only in development runtime, batching with default 2000ms interval
|
|
48
53
|
sal/eval/jsonl-sink.ts: JsonlEvalSink — append-only filesystem adapter, one JSON object per line, accepts file:// URLs or plain paths, auto-creates parent dir, batched writes
|
|
@@ -17,10 +17,11 @@ security-audit/engine/interceptor.ts: Request/response interception, Interceptor
|
|
|
17
17
|
security-audit/engine/logger.ts: Security event logging, JSON file audit trail
|
|
18
18
|
security-audit/engine/detector.ts: Vulnerability detection, pattern matching for dangerous commands
|
|
19
19
|
soul/index.ts: AI personality evolution extension, persistent personality across sessions
|
|
20
|
-
grub/index.ts: Grub extension entry - long-running autonomous harness, dual-phase system prompts (initializer/coding), /grub command (start/status/resume/stop) + grub renderer, session_start auto-adopt, git harness commit, pruneStale cleanup
|
|
21
|
-
grub/grub-controller.ts: GrubController - state machine for /grub iterations, durable persistState on every transition, adoptResumedTask for cross-session resume, validateCompletion downgrades premature complete when feature-list still has pending entries
|
|
22
|
-
grub/grub-parser.ts: Grub command parsing - parseGrubCommand/buildGrubHelp with resume subcommand, status --json, --max-iter/--max-fail flags
|
|
23
|
-
grub/grub-types.ts: Grub types - GrubStatus/GrubDecisionStatus/GrubDecision/GrubPhase/GrubTaskState/GrubTaskSnapshot/ParsedGrubCommand + FeatureItem/FeatureList (version 1 schema) + PersistedGrubState envelope
|
|
20
|
+
grub/index.ts: Grub extension entry - long-running autonomous harness, locale-aware dual-phase system prompts (initializer/coding), /grub command (start/status/resume/stop) + grub renderer, session_start auto-adopt, git harness commit, pruneStale cleanup
|
|
21
|
+
grub/grub-controller.ts: GrubController - state machine for /grub iterations, locale-persisted prompt generation, durable persistState on every transition, adoptResumedTask for cross-session resume, validateCompletion downgrades premature complete when feature-list still has pending entries
|
|
22
|
+
grub/grub-parser.ts: Grub command parsing - parseGrubCommand/buildGrubHelp with localized help, resume subcommand, status --json, --max-iter/--max-fail flags
|
|
23
|
+
grub/grub-types.ts: Grub types - GrubStatus/GrubDecisionStatus/GrubDecision/GrubPhase/GrubLocale/GrubTaskState/GrubTaskSnapshot/ParsedGrubCommand + FeatureItem/FeatureList (version 1 schema) + PersistedGrubState envelope
|
|
24
|
+
grub/grub-i18n.ts: Grub localization helper - detectGrubLocale(), grubText(), languageName(), English/Chinese TUI strings
|
|
24
25
|
grub/grub-feature-list.ts: feature-list.json IO - readFeatureList/writeFeatureList atomic write, validateFeatureListDiff enforces passes/evidence-only mutations, createInitialFeatureList placeholder, migrateChecklistToFeatureList legacy converter, countPassing/allPassing/firstPending helpers
|
|
25
26
|
grub/grub-persistence.ts: Cross-session persistence - persistState atomic JSON write to .grub/<id>/state.json, loadState shape-validated read, discoverActiveTasks scans .grub/ for running records, pruneStale removes terminal harnesses older than 30 days by default
|
|
26
27
|
grub/README.md: Grub extension documentation - long-running harness contract, feature-list.json schema, completion guard, cross-session resume, legacy migration
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [WHO]: DiagnosticBuffer, coerceDiagnosticEvent()
|
|
3
|
+
* [FROM]: Depends on ./types.js and ./redaction.js for event schema and privacy normalization
|
|
4
|
+
* [TO]: Consumed by extensions/defaults/diagnostics/index.ts
|
|
5
|
+
* [HERE]: extensions/defaults/diagnostics/diagnostic-buffer.ts - session-local dedupe and prompt gating state
|
|
6
|
+
*/
|
|
7
|
+
import { type DiagnosticEvent, type DiagnosticRecord } from "./types.js";
|
|
8
|
+
export declare class DiagnosticBuffer {
|
|
9
|
+
private records;
|
|
10
|
+
add(event: DiagnosticEvent): DiagnosticRecord;
|
|
11
|
+
all(): DiagnosticRecord[];
|
|
12
|
+
last(): DiagnosticRecord | undefined;
|
|
13
|
+
findPromptCandidate(): DiagnosticRecord | undefined;
|
|
14
|
+
findUnreported(): DiagnosticRecord[];
|
|
15
|
+
markPrompted(fingerprint: string): void;
|
|
16
|
+
markReported(fingerprint: string): void;
|
|
17
|
+
private trim;
|
|
18
|
+
}
|
|
19
|
+
export declare function coerceDiagnosticEvent(value: unknown): DiagnosticEvent | undefined;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [WHO]: DiagnosticBuffer, coerceDiagnosticEvent()
|
|
3
|
+
* [FROM]: Depends on ./types.js and ./redaction.js for event schema and privacy normalization
|
|
4
|
+
* [TO]: Consumed by extensions/defaults/diagnostics/index.ts
|
|
5
|
+
* [HERE]: extensions/defaults/diagnostics/diagnostic-buffer.ts - session-local dedupe and prompt gating state
|
|
6
|
+
*/
|
|
7
|
+
import { normalizeDiagnosticMessage, sanitizeDiagnosticValue } from "./redaction.js";
|
|
8
|
+
const MAX_RECORDS = 100;
|
|
9
|
+
export class DiagnosticBuffer {
|
|
10
|
+
records = new Map();
|
|
11
|
+
add(event) {
|
|
12
|
+
const now = event.created_at ?? new Date().toISOString();
|
|
13
|
+
const sanitized = {
|
|
14
|
+
source: event.source,
|
|
15
|
+
severity: event.severity,
|
|
16
|
+
category: event.category,
|
|
17
|
+
message: normalizeDiagnosticMessage(event.message),
|
|
18
|
+
detail: sanitizeDiagnosticValue(event.detail),
|
|
19
|
+
context: sanitizeDiagnosticValue(event.context),
|
|
20
|
+
created_at: now,
|
|
21
|
+
};
|
|
22
|
+
const fingerprint = event.fingerprint ?? buildFingerprint(sanitized);
|
|
23
|
+
const existing = this.records.get(fingerprint);
|
|
24
|
+
if (existing) {
|
|
25
|
+
existing.last_seen_at = now;
|
|
26
|
+
existing.occurrence_count += 1;
|
|
27
|
+
existing.severity = maxSeverity(existing.severity, sanitized.severity);
|
|
28
|
+
existing.detail = sanitized.detail;
|
|
29
|
+
existing.context = { ...(existing.context ?? {}), ...(sanitized.context ?? {}) };
|
|
30
|
+
// New occurrences invalidate a prior auto-report so the next
|
|
31
|
+
// agent_end batch uploads the updated count.
|
|
32
|
+
existing.reported = false;
|
|
33
|
+
return existing;
|
|
34
|
+
}
|
|
35
|
+
const record = {
|
|
36
|
+
...sanitized,
|
|
37
|
+
fingerprint,
|
|
38
|
+
first_seen_at: now,
|
|
39
|
+
last_seen_at: now,
|
|
40
|
+
occurrence_count: 1,
|
|
41
|
+
prompted: false,
|
|
42
|
+
reported: false,
|
|
43
|
+
};
|
|
44
|
+
this.records.set(fingerprint, record);
|
|
45
|
+
this.trim();
|
|
46
|
+
return record;
|
|
47
|
+
}
|
|
48
|
+
all() {
|
|
49
|
+
return Array.from(this.records.values()).sort((a, b) => b.last_seen_at.localeCompare(a.last_seen_at));
|
|
50
|
+
}
|
|
51
|
+
last() {
|
|
52
|
+
return this.all()[0];
|
|
53
|
+
}
|
|
54
|
+
findPromptCandidate() {
|
|
55
|
+
return this.all().find((record) => !record.prompted && shouldPrompt(record));
|
|
56
|
+
}
|
|
57
|
+
findUnreported() {
|
|
58
|
+
return this.all().filter((record) => !record.reported);
|
|
59
|
+
}
|
|
60
|
+
markPrompted(fingerprint) {
|
|
61
|
+
const record = this.records.get(fingerprint);
|
|
62
|
+
if (record)
|
|
63
|
+
record.prompted = true;
|
|
64
|
+
}
|
|
65
|
+
markReported(fingerprint) {
|
|
66
|
+
const record = this.records.get(fingerprint);
|
|
67
|
+
if (record)
|
|
68
|
+
record.reported = true;
|
|
69
|
+
}
|
|
70
|
+
trim() {
|
|
71
|
+
if (this.records.size <= MAX_RECORDS)
|
|
72
|
+
return;
|
|
73
|
+
const sorted = this.all();
|
|
74
|
+
for (const record of sorted.slice(MAX_RECORDS)) {
|
|
75
|
+
this.records.delete(record.fingerprint);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export function coerceDiagnosticEvent(value) {
|
|
80
|
+
if (!value || typeof value !== "object")
|
|
81
|
+
return undefined;
|
|
82
|
+
const input = value;
|
|
83
|
+
const source = typeof input.source === "string" ? input.source : undefined;
|
|
84
|
+
const severity = isSeverity(input.severity) ? input.severity : undefined;
|
|
85
|
+
const category = isCategory(input.category) ? input.category : "unknown";
|
|
86
|
+
const message = typeof input.message === "string" ? input.message : undefined;
|
|
87
|
+
if (!source || !severity || !message)
|
|
88
|
+
return undefined;
|
|
89
|
+
return {
|
|
90
|
+
source,
|
|
91
|
+
severity,
|
|
92
|
+
category,
|
|
93
|
+
message,
|
|
94
|
+
detail: input.detail,
|
|
95
|
+
fingerprint: typeof input.fingerprint === "string" ? input.fingerprint : undefined,
|
|
96
|
+
context: input.context && typeof input.context === "object" ? input.context : undefined,
|
|
97
|
+
created_at: typeof input.created_at === "string" ? input.created_at : undefined,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function shouldPrompt(record) {
|
|
101
|
+
if (record.severity === "error")
|
|
102
|
+
return record.occurrence_count >= 3;
|
|
103
|
+
if (record.severity === "warning")
|
|
104
|
+
return record.occurrence_count >= 5 || record.category === "fallback";
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
function buildFingerprint(event) {
|
|
108
|
+
return `${event.source}:${event.category}:${normalizeDiagnosticMessage(event.message).toLowerCase()}`;
|
|
109
|
+
}
|
|
110
|
+
function isSeverity(value) {
|
|
111
|
+
return value === "debug" || value === "info" || value === "warning" || value === "error";
|
|
112
|
+
}
|
|
113
|
+
function isCategory(value) {
|
|
114
|
+
return (value === "network" ||
|
|
115
|
+
value === "fallback" ||
|
|
116
|
+
value === "persistence" ||
|
|
117
|
+
value === "config" ||
|
|
118
|
+
value === "extension_timeout" ||
|
|
119
|
+
value === "schema" ||
|
|
120
|
+
value === "unknown");
|
|
121
|
+
}
|
|
122
|
+
function maxSeverity(a, b) {
|
|
123
|
+
const rank = { debug: 0, info: 1, warning: 2, error: 3 };
|
|
124
|
+
return rank[b] > rank[a] ? b : a;
|
|
125
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [WHO]: diagnosticsExtension - diagnostic:event listener, /report-issue command, silent auto-upload on agent_end
|
|
3
|
+
* [FROM]: Depends on core/extensions/types, @pencil-agent/tui, ./diagnostic-buffer, ./reporter, ./types
|
|
4
|
+
* [TO]: Auto-loaded by builtin-extensions.ts as a default extension before diagnostic producers
|
|
5
|
+
* [HERE]: extensions/defaults/diagnostics/index.ts - extension-owned diagnostic buffer; background failures auto-upload silently at agent_end, /report-issue stays for explicit user-initiated bundles
|
|
6
|
+
*/
|
|
7
|
+
import type { ExtensionAPI } from "../../../core/extensions/types.js";
|
|
8
|
+
export default function diagnosticsExtension(api: ExtensionAPI): Promise<void>;
|