@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
|
@@ -1,32 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [WHO]: Provides InsForgeEvalSink (PostgREST-backed adapter)
|
|
3
|
-
* [FROM]: Depends on
|
|
2
|
+
* [WHO]: Provides InsForgeEvalSink (PostgREST-backed adapter for SAL)
|
|
3
|
+
* [FROM]: Depends on core/telemetry/* for InsforgeHttpClient + BatchingDispatcher + types; ./types.js for SAL event shape (EvalSink/EvalEventEnvelope/CreateEvalSinkOptions)
|
|
4
4
|
* [TO]: Constructed by eval/index.ts factory when adapter resolves to "insforge"
|
|
5
|
-
* [HERE]: extensions/defaults/sal/eval/insforge-sink.ts -
|
|
5
|
+
* [HERE]: extensions/defaults/sal/eval/insforge-sink.ts - SAL-specific event routing only: run_start→eval_runs, turn_anchor→eval_turns+eval_sal_anchors×2, tool_trace→eval_tool_traces (legacy fallback), memory_recalls→eval_memory_recalls, run_end→eval_runs PATCH. HTTP transport + batching come from core/telemetry/.
|
|
6
6
|
*
|
|
7
|
-
* Pluggable: nothing in this file may be imported from outside the eval/ directory.
|
|
7
|
+
* Pluggable: nothing in this file may be imported from outside the eval/ directory other than core/telemetry (the shared base layer).
|
|
8
8
|
* To add a new backend, write a sibling file with the same EvalSink interface.
|
|
9
9
|
*/
|
|
10
10
|
import type { CreateEvalSinkOptions, EvalEventEnvelope, EvalSink } from "./types.js";
|
|
11
11
|
export declare class InsForgeEvalSink implements EvalSink {
|
|
12
12
|
readonly enabled = true;
|
|
13
|
-
private
|
|
14
|
-
private
|
|
15
|
-
private allowSelfSigned;
|
|
16
|
-
private batchIntervalMs;
|
|
13
|
+
private http;
|
|
14
|
+
private dispatcher;
|
|
17
15
|
private onDiagnostic;
|
|
18
|
-
private pending;
|
|
19
|
-
private flushTimer;
|
|
20
|
-
private flushInFlight;
|
|
21
|
-
private closed;
|
|
22
16
|
private confirmedRuns;
|
|
23
17
|
private failedRuns;
|
|
24
18
|
constructor(options: CreateEvalSinkOptions);
|
|
25
19
|
sendEvent(event: EvalEventEnvelope): Promise<void>;
|
|
26
20
|
flush(): Promise<void>;
|
|
27
|
-
private doFlush;
|
|
28
21
|
close(): Promise<void>;
|
|
29
|
-
private scheduleFlush;
|
|
30
22
|
private routeEvent;
|
|
31
23
|
private handleRunStart;
|
|
32
24
|
private ensureRunExists;
|
|
@@ -34,8 +26,5 @@ export declare class InsForgeEvalSink implements EvalSink {
|
|
|
34
26
|
private handleRunEnd;
|
|
35
27
|
private handleMemoryRecalls;
|
|
36
28
|
private handleToolTrace;
|
|
37
|
-
private postJson;
|
|
38
|
-
private patchJson;
|
|
39
|
-
private httpJson;
|
|
40
29
|
private reportDiagnostic;
|
|
41
30
|
}
|
|
@@ -1,105 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* [WHO]: Provides InsForgeEvalSink (PostgREST-backed adapter)
|
|
3
|
-
* [FROM]: Depends on
|
|
2
|
+
* [WHO]: Provides InsForgeEvalSink (PostgREST-backed adapter for SAL)
|
|
3
|
+
* [FROM]: Depends on core/telemetry/* for InsforgeHttpClient + BatchingDispatcher + types; ./types.js for SAL event shape (EvalSink/EvalEventEnvelope/CreateEvalSinkOptions)
|
|
4
4
|
* [TO]: Constructed by eval/index.ts factory when adapter resolves to "insforge"
|
|
5
|
-
* [HERE]: extensions/defaults/sal/eval/insforge-sink.ts -
|
|
5
|
+
* [HERE]: extensions/defaults/sal/eval/insforge-sink.ts - SAL-specific event routing only: run_start→eval_runs, turn_anchor→eval_turns+eval_sal_anchors×2, tool_trace→eval_tool_traces (legacy fallback), memory_recalls→eval_memory_recalls, run_end→eval_runs PATCH. HTTP transport + batching come from core/telemetry/.
|
|
6
6
|
*
|
|
7
|
-
* Pluggable: nothing in this file may be imported from outside the eval/ directory.
|
|
7
|
+
* Pluggable: nothing in this file may be imported from outside the eval/ directory other than core/telemetry (the shared base layer).
|
|
8
8
|
* To add a new backend, write a sibling file with the same EvalSink interface.
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
11
|
-
import { request } from "node:https";
|
|
12
|
-
import { URL } from "node:url";
|
|
13
|
-
import { isDevRuntime } from "../../../../utils/diagnostics.js";
|
|
10
|
+
import { BatchingDispatcher, InsforgeHttpClient, safeHost } from "../../../../core/telemetry/index.js";
|
|
14
11
|
export class InsForgeEvalSink {
|
|
15
12
|
enabled = true;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
allowSelfSigned;
|
|
19
|
-
batchIntervalMs;
|
|
13
|
+
http;
|
|
14
|
+
dispatcher;
|
|
20
15
|
onDiagnostic;
|
|
21
|
-
pending = [];
|
|
22
|
-
flushTimer;
|
|
23
|
-
flushInFlight;
|
|
24
|
-
closed = false;
|
|
25
16
|
confirmedRuns = new Set();
|
|
26
17
|
failedRuns = new Set();
|
|
27
18
|
constructor(options) {
|
|
28
|
-
this.base = options.endpoint.replace(/\/+$/, "");
|
|
29
|
-
this.batchIntervalMs = options.batchIntervalMs ?? 2000;
|
|
30
19
|
this.onDiagnostic = options.onDiagnostic;
|
|
31
|
-
this.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
this.headers = h;
|
|
20
|
+
this.http = new InsforgeHttpClient({
|
|
21
|
+
endpoint: options.endpoint,
|
|
22
|
+
apiKey: options.apiKey,
|
|
23
|
+
anonKey: options.anonKey,
|
|
24
|
+
apiKeyHeader: options.apiKeyHeader,
|
|
25
|
+
extraHeaders: options.headers,
|
|
26
|
+
allowSelfSigned: options.allowSelfSigned,
|
|
27
|
+
source: "sal.eval",
|
|
28
|
+
onDiagnostic: this.onDiagnostic,
|
|
29
|
+
});
|
|
30
|
+
this.dispatcher = new BatchingDispatcher({
|
|
31
|
+
handler: (event) => this.routeEvent(event),
|
|
32
|
+
intervalMs: options.batchIntervalMs ?? 2000,
|
|
33
|
+
source: "sal.eval",
|
|
34
|
+
onDiagnostic: this.onDiagnostic,
|
|
35
|
+
});
|
|
50
36
|
}
|
|
51
37
|
async sendEvent(event) {
|
|
52
|
-
|
|
53
|
-
return;
|
|
54
|
-
this.pending.push(event);
|
|
55
|
-
this.scheduleFlush();
|
|
38
|
+
this.dispatcher.enqueue(event);
|
|
56
39
|
}
|
|
57
40
|
async flush() {
|
|
58
|
-
|
|
59
|
-
await this.flushInFlight.catch(() => { });
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
this.flushInFlight = this.doFlush();
|
|
63
|
-
try {
|
|
64
|
-
await this.flushInFlight;
|
|
65
|
-
}
|
|
66
|
-
catch (err) {
|
|
67
|
-
this.reportDiagnostic("persistence", "SAL eval flush failed.", err, "flush");
|
|
68
|
-
}
|
|
69
|
-
finally {
|
|
70
|
-
this.flushInFlight = undefined;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
async doFlush() {
|
|
74
|
-
if (this.flushTimer) {
|
|
75
|
-
clearTimeout(this.flushTimer);
|
|
76
|
-
this.flushTimer = undefined;
|
|
77
|
-
}
|
|
78
|
-
while (true) {
|
|
79
|
-
const toFlush = this.pending.splice(0);
|
|
80
|
-
if (toFlush.length === 0)
|
|
81
|
-
break;
|
|
82
|
-
for (const event of toFlush) {
|
|
83
|
-
await this.routeEvent(event);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
41
|
+
await this.dispatcher.flush();
|
|
86
42
|
}
|
|
87
43
|
async close() {
|
|
88
|
-
this.
|
|
89
|
-
await this.flush().catch((err) => {
|
|
90
|
-
this.reportDiagnostic("persistence", "SAL eval close flush failed.", err, "close-flush");
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
scheduleFlush() {
|
|
94
|
-
if (this.flushTimer)
|
|
95
|
-
return;
|
|
96
|
-
this.flushTimer = setTimeout(() => {
|
|
97
|
-
this.flushTimer = undefined;
|
|
98
|
-
void this.flush().catch(() => { });
|
|
99
|
-
}, this.batchIntervalMs);
|
|
44
|
+
await this.dispatcher.close();
|
|
100
45
|
}
|
|
101
46
|
// ------------------------------------------------------------------
|
|
102
|
-
//
|
|
47
|
+
// SAL-specific routing
|
|
103
48
|
// ------------------------------------------------------------------
|
|
104
49
|
async routeEvent(event) {
|
|
105
50
|
try {
|
|
@@ -146,8 +91,8 @@ export class InsForgeEvalSink {
|
|
|
146
91
|
workspace_root: strOrNull(p.workspace_root),
|
|
147
92
|
started_at: ev.ts,
|
|
148
93
|
};
|
|
149
|
-
const url = `${this.base}/api/database/records/eval_runs`;
|
|
150
|
-
const result = await this.postJson(url, [row], {
|
|
94
|
+
const url = `${this.http.base}/api/database/records/eval_runs`;
|
|
95
|
+
const result = await this.http.postJson(url, [row], {
|
|
151
96
|
prefer: "resolution=merge-duplicates",
|
|
152
97
|
quietErrorCodes: ["PGRST204"],
|
|
153
98
|
});
|
|
@@ -156,7 +101,7 @@ export class InsForgeEvalSink {
|
|
|
156
101
|
this.failedRuns.delete(ev.run_id);
|
|
157
102
|
return;
|
|
158
103
|
}
|
|
159
|
-
const fallback = await this.postJson(url, [toLegacyRunStartRow(row)], {
|
|
104
|
+
const fallback = await this.http.postJson(url, [toLegacyRunStartRow(row)], {
|
|
160
105
|
prefer: "resolution=merge-duplicates",
|
|
161
106
|
});
|
|
162
107
|
if (fallback.ok) {
|
|
@@ -201,7 +146,7 @@ export class InsForgeEvalSink {
|
|
|
201
146
|
// UNIQUE(run_id, turn_id) collision via ON CONFLICT DO UPDATE
|
|
202
147
|
// instead of returning 409/23505. ignore-duplicates only checks the
|
|
203
148
|
// primary key, which is auto-uuid here, so it never sees the conflict.
|
|
204
|
-
await this.postJson(`${this.base}/api/database/records/eval_turns?on_conflict=run_id,turn_id`, [{
|
|
149
|
+
await this.http.postJson(`${this.http.base}/api/database/records/eval_turns?on_conflict=run_id,turn_id`, [{
|
|
205
150
|
run_id: ev.run_id,
|
|
206
151
|
turn_id: turnId,
|
|
207
152
|
event_id: ev.event_id,
|
|
@@ -212,7 +157,7 @@ export class InsForgeEvalSink {
|
|
|
212
157
|
}], { prefer: "resolution=merge-duplicates" });
|
|
213
158
|
const taskAnchor = p.task_anchor;
|
|
214
159
|
if (taskAnchor) {
|
|
215
|
-
await this.postJson(`${this.base}/api/database/records/eval_sal_anchors?on_conflict=run_id,turn_id,anchor_type`, [{
|
|
160
|
+
await this.http.postJson(`${this.http.base}/api/database/records/eval_sal_anchors?on_conflict=run_id,turn_id,anchor_type`, [{
|
|
216
161
|
run_id: ev.run_id,
|
|
217
162
|
turn_id: turnId,
|
|
218
163
|
event_id: `${ev.event_id}-task`,
|
|
@@ -225,7 +170,7 @@ export class InsForgeEvalSink {
|
|
|
225
170
|
}], { prefer: "resolution=merge-duplicates" });
|
|
226
171
|
}
|
|
227
172
|
const actionAnchor = p.action_anchor;
|
|
228
|
-
await this.postJson(`${this.base}/api/database/records/eval_sal_anchors?on_conflict=run_id,turn_id,anchor_type`, [{
|
|
173
|
+
await this.http.postJson(`${this.http.base}/api/database/records/eval_sal_anchors?on_conflict=run_id,turn_id,anchor_type`, [{
|
|
229
174
|
run_id: ev.run_id,
|
|
230
175
|
turn_id: turnId,
|
|
231
176
|
event_id: `${ev.event_id}-action`,
|
|
@@ -244,7 +189,7 @@ export class InsForgeEvalSink {
|
|
|
244
189
|
const normalizedStatus = rawStatus === "success" ? "completed"
|
|
245
190
|
: rawStatus === "error" ? "failed"
|
|
246
191
|
: rawStatus ?? "completed";
|
|
247
|
-
await this.patchJson(`${this.base}/api/database/records/eval_runs?run_id=eq.${ev.run_id}`, {
|
|
192
|
+
await this.http.patchJson(`${this.http.base}/api/database/records/eval_runs?run_id=eq.${ev.run_id}`, {
|
|
248
193
|
status: normalizedStatus,
|
|
249
194
|
turn_count: numOrNull(p.turn_count),
|
|
250
195
|
total_duration_ms: numOrNull(p.total_duration_ms),
|
|
@@ -274,7 +219,7 @@ export class InsForgeEvalSink {
|
|
|
274
219
|
inject_rank: numOrNull(r.injectRank),
|
|
275
220
|
recorded_at: ev.ts,
|
|
276
221
|
}));
|
|
277
|
-
await this.postJson(`${this.base}/api/database/records/eval_memory_recalls`, rows, { prefer: "resolution=ignore-duplicates" });
|
|
222
|
+
await this.http.postJson(`${this.http.base}/api/database/records/eval_memory_recalls`, rows, { prefer: "resolution=ignore-duplicates" });
|
|
278
223
|
}
|
|
279
224
|
// INSERT into eval_tool_traces — one row per turn with tool usage summary
|
|
280
225
|
// InsForge columns are all TEXT; JSONB fields must be serialized to strings.
|
|
@@ -300,86 +245,15 @@ export class InsForgeEvalSink {
|
|
|
300
245
|
duration_ms: String(p.duration_ms ?? 0),
|
|
301
246
|
recorded_at: ev.ts,
|
|
302
247
|
};
|
|
303
|
-
const url = `${this.base}/api/database/records/eval_tool_traces`;
|
|
304
|
-
const result = await this.postJson(url, [row], {
|
|
248
|
+
const url = `${this.http.base}/api/database/records/eval_tool_traces`;
|
|
249
|
+
const result = await this.http.postJson(url, [row], {
|
|
305
250
|
prefer: "resolution=ignore-duplicates",
|
|
306
251
|
quietErrorCodes: ["PGRST204"],
|
|
307
252
|
});
|
|
308
253
|
if (!result.ok && result.errorCode === "PGRST204") {
|
|
309
|
-
await this.postJson(url, [toLegacyToolTraceRow(row)], { prefer: "resolution=ignore-duplicates" });
|
|
254
|
+
await this.http.postJson(url, [toLegacyToolTraceRow(row)], { prefer: "resolution=ignore-duplicates" });
|
|
310
255
|
}
|
|
311
256
|
}
|
|
312
|
-
// ------------------------------------------------------------------
|
|
313
|
-
// HTTP helpers
|
|
314
|
-
// ------------------------------------------------------------------
|
|
315
|
-
postJson(url, body, extra) {
|
|
316
|
-
const extraHeaders = {};
|
|
317
|
-
if (extra?.prefer)
|
|
318
|
-
extraHeaders["Prefer"] = extra.prefer;
|
|
319
|
-
return this.httpJson("POST", url, body, extraHeaders, extra?.quietErrorCodes);
|
|
320
|
-
}
|
|
321
|
-
patchJson(url, body) {
|
|
322
|
-
return this.httpJson("PATCH", url, body, {});
|
|
323
|
-
}
|
|
324
|
-
httpJson(method, url, body, extraHeaders, quietErrorCodes = []) {
|
|
325
|
-
return new Promise((resolve) => {
|
|
326
|
-
const payload = JSON.stringify(body);
|
|
327
|
-
let parsed;
|
|
328
|
-
try {
|
|
329
|
-
parsed = new URL(url);
|
|
330
|
-
}
|
|
331
|
-
catch {
|
|
332
|
-
this.reportDiagnostic("config", "SAL eval endpoint URL is invalid.", { url }, "invalid-url");
|
|
333
|
-
resolve({ ok: false });
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
const isHttps = parsed.protocol === "https:";
|
|
337
|
-
const requestFn = isHttps ? request : httpRequest;
|
|
338
|
-
const port = parsed.port ? Number(parsed.port) : (isHttps ? 443 : 80);
|
|
339
|
-
const req = requestFn({
|
|
340
|
-
hostname: parsed.hostname,
|
|
341
|
-
port,
|
|
342
|
-
path: parsed.pathname + parsed.search,
|
|
343
|
-
method,
|
|
344
|
-
headers: {
|
|
345
|
-
...this.headers,
|
|
346
|
-
...extraHeaders,
|
|
347
|
-
"Content-Length": Buffer.byteLength(payload),
|
|
348
|
-
},
|
|
349
|
-
timeout: 5000,
|
|
350
|
-
...(isHttps && this.allowSelfSigned ? { rejectUnauthorized: false } : {}),
|
|
351
|
-
}, (res) => {
|
|
352
|
-
let rawBody = "";
|
|
353
|
-
res.setEncoding("utf-8");
|
|
354
|
-
res.on("data", (chunk) => { rawBody += chunk; });
|
|
355
|
-
res.on("end", () => {
|
|
356
|
-
const ok = res.statusCode !== undefined && res.statusCode < 300;
|
|
357
|
-
const errorCode = parsePostgrestErrorCode(rawBody);
|
|
358
|
-
if (!ok && !quietErrorCodes.includes(errorCode ?? "")) {
|
|
359
|
-
this.reportDiagnostic(errorCode === "PGRST204" ? "schema" : "network", `SAL eval upload failed with HTTP ${res.statusCode}.`, { method, path: parsed.pathname, statusCode: res.statusCode, body: rawBody.slice(0, 300), errorCode }, `http-${res.statusCode ?? "unknown"}-${errorCode ?? "none"}`);
|
|
360
|
-
}
|
|
361
|
-
resolve({ ok, statusCode: res.statusCode, body: rawBody, errorCode });
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
req.on("error", (err) => {
|
|
365
|
-
this.reportDiagnostic("network", "SAL eval upload is failing due to a network connection error.", { host: parsed.hostname, error: err.message }, "network-error");
|
|
366
|
-
if (isDevRuntime()) {
|
|
367
|
-
console.error(`[sal][eval] network error → ${parsed.hostname}: ${err.message}`);
|
|
368
|
-
}
|
|
369
|
-
resolve({ ok: false });
|
|
370
|
-
});
|
|
371
|
-
req.on("timeout", () => {
|
|
372
|
-
this.reportDiagnostic("network", "SAL eval upload timed out.", { method, path: parsed.pathname, host: parsed.hostname }, "timeout");
|
|
373
|
-
if (isDevRuntime()) {
|
|
374
|
-
console.error(`[sal][eval] timeout ${method} ${parsed.pathname}`);
|
|
375
|
-
}
|
|
376
|
-
req.destroy();
|
|
377
|
-
resolve({ ok: false });
|
|
378
|
-
});
|
|
379
|
-
req.write(payload);
|
|
380
|
-
req.end();
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
257
|
reportDiagnostic(category, message, detail, fingerprintSuffix) {
|
|
384
258
|
this.onDiagnostic?.({
|
|
385
259
|
source: "sal.eval",
|
|
@@ -390,13 +264,13 @@ export class InsForgeEvalSink {
|
|
|
390
264
|
fingerprint: `sal.eval:${category}:${fingerprintSuffix}`,
|
|
391
265
|
context: {
|
|
392
266
|
adapter: "insforge",
|
|
393
|
-
endpoint_host: safeHost(this.base),
|
|
267
|
+
endpoint_host: safeHost(this.http.base),
|
|
394
268
|
},
|
|
395
269
|
});
|
|
396
270
|
}
|
|
397
271
|
}
|
|
398
272
|
// ----------------------------------------------------------------------------
|
|
399
|
-
//
|
|
273
|
+
// SAL-specific row helpers
|
|
400
274
|
// ----------------------------------------------------------------------------
|
|
401
275
|
function strOrNull(v, skipValue) {
|
|
402
276
|
if (v == null || v === "" || v === skipValue)
|
|
@@ -409,23 +283,6 @@ function numOrNull(v) {
|
|
|
409
283
|
const n = Number(v);
|
|
410
284
|
return isNaN(n) ? null : n;
|
|
411
285
|
}
|
|
412
|
-
function parsePostgrestErrorCode(rawBody) {
|
|
413
|
-
try {
|
|
414
|
-
const parsed = JSON.parse(rawBody);
|
|
415
|
-
return typeof parsed?.code === "string" ? parsed.code : undefined;
|
|
416
|
-
}
|
|
417
|
-
catch {
|
|
418
|
-
return undefined;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
function safeHost(value) {
|
|
422
|
-
try {
|
|
423
|
-
return new URL(value).hostname;
|
|
424
|
-
}
|
|
425
|
-
catch {
|
|
426
|
-
return undefined;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
286
|
function toLegacyRunStartRow(row) {
|
|
430
287
|
const { pencil_version: _pencilVersion, commit_hash: _commitHash, branch_name: _branchName, workspace_root: _workspaceRoot, ...legacyRow } = row;
|
|
431
288
|
return legacyRow;
|
|
@@ -19,6 +19,17 @@ const NOSAL_FLAG = "nosal";
|
|
|
19
19
|
const SAL_AB_FLAG = "sal-ab";
|
|
20
20
|
const SAL_REBUILD_FLAG = "sal-rebuild-terrain";
|
|
21
21
|
const DIAGNOSTIC_EVENT_CHANNEL = "diagnostic:event";
|
|
22
|
+
const SAL_COVERAGE_COMPLETIONS = [
|
|
23
|
+
{ value: "core/", label: "core/", description: "Check the core runtime files" },
|
|
24
|
+
{ value: "extensions/", label: "extensions/", description: "Check extension files" },
|
|
25
|
+
{ value: "modes/", label: "modes/", description: "Check interactive, print, and RPC mode files" },
|
|
26
|
+
{ value: "packages/", label: "packages/", description: "Check bundled package files" },
|
|
27
|
+
];
|
|
28
|
+
const SAL_SETUP_ENDPOINT_COMPLETIONS = [
|
|
29
|
+
{ value: "https://", label: "https://", description: "Send evaluation records to a hosted endpoint" },
|
|
30
|
+
{ value: "file://", label: "file://", description: "Write evaluation records to a local JSONL file" },
|
|
31
|
+
{ value: "./", label: "./", description: "Write evaluation records to a file in this workspace" },
|
|
32
|
+
];
|
|
22
33
|
async function ensureSnapshot(runtime, forceRebuild) {
|
|
23
34
|
if (runtime.snapshotErrored)
|
|
24
35
|
return runtime.snapshot;
|
|
@@ -181,17 +192,17 @@ async function cleanupStaleRuns(runtime) {
|
|
|
181
192
|
export default async function salExtension(api) {
|
|
182
193
|
api.registerFlag(NOSAL_FLAG, {
|
|
183
194
|
type: "boolean",
|
|
184
|
-
description: "
|
|
195
|
+
description: "Turn off SAL workspace guidance for this run",
|
|
185
196
|
default: false,
|
|
186
197
|
});
|
|
187
198
|
api.registerFlag(SAL_AB_FLAG, {
|
|
188
199
|
type: "boolean",
|
|
189
|
-
description: "
|
|
200
|
+
description: "Save local SAL comparison records under .memory-experiments",
|
|
190
201
|
default: false,
|
|
191
202
|
});
|
|
192
203
|
api.registerFlag(SAL_REBUILD_FLAG, {
|
|
193
204
|
type: "boolean",
|
|
194
|
-
description: "
|
|
205
|
+
description: "Refresh SAL's workspace map before the next turn",
|
|
195
206
|
default: false,
|
|
196
207
|
});
|
|
197
208
|
const workspaceRoot = api.cwd;
|
|
@@ -299,7 +310,13 @@ export default async function salExtension(api) {
|
|
|
299
310
|
const isEnabled = () => !api.getFlag(NOSAL_FLAG);
|
|
300
311
|
const isSalAbEnabled = () => resolveSalAbEnabled(api.getFlag(SAL_AB_FLAG));
|
|
301
312
|
api.registerCommand("sal:coverage", {
|
|
302
|
-
description: "
|
|
313
|
+
description: "Check whether folders have the file map headers SAL needs. Usage: /sal:coverage [folder ...]",
|
|
314
|
+
getArgumentCompletions: (argumentPrefix, context) => {
|
|
315
|
+
const used = new Set(context?.previousTokens ?? []);
|
|
316
|
+
const prefix = argumentPrefix.trim().toLowerCase();
|
|
317
|
+
const values = SAL_COVERAGE_COMPLETIONS.filter((item) => !used.has(item.value) && item.value.startsWith(prefix));
|
|
318
|
+
return values.length > 0 ? values : null;
|
|
319
|
+
},
|
|
303
320
|
handler: async (args, ctx) => {
|
|
304
321
|
const modules = (args ?? "")
|
|
305
322
|
.trim()
|
|
@@ -316,9 +333,15 @@ export default async function salExtension(api) {
|
|
|
316
333
|
},
|
|
317
334
|
});
|
|
318
335
|
api.registerCommand("sal:setup", {
|
|
319
|
-
description: "
|
|
320
|
-
"Usage: /sal:setup <endpoint> [api_key] [anon_key]
|
|
321
|
-
|
|
336
|
+
description: "Connect evaluation records to a hosted endpoint or local JSONL file. " +
|
|
337
|
+
"Usage: /sal:setup <endpoint> [api_key] [anon_key].",
|
|
338
|
+
getArgumentCompletions: (argumentPrefix, context) => {
|
|
339
|
+
if (context && context.tokenIndex > 0)
|
|
340
|
+
return null;
|
|
341
|
+
const prefix = argumentPrefix.trim().toLowerCase();
|
|
342
|
+
const values = SAL_SETUP_ENDPOINT_COMPLETIONS.filter((item) => item.value.startsWith(prefix));
|
|
343
|
+
return values.length > 0 ? values : null;
|
|
344
|
+
},
|
|
322
345
|
handler: async (args, ctx) => {
|
|
323
346
|
const tokens = (args ?? "").trim().split(/\s+/).filter((t) => t.length > 0);
|
|
324
347
|
const endpoint = tokens[0];
|
|
@@ -399,7 +422,7 @@ export default async function salExtension(api) {
|
|
|
399
422
|
},
|
|
400
423
|
});
|
|
401
424
|
api.registerCommand("sal:status", {
|
|
402
|
-
description: "Show
|
|
425
|
+
description: "Show whether SAL is active and where its records are going",
|
|
403
426
|
handler: async (_args, ctx) => {
|
|
404
427
|
const flagOn = isEnabled();
|
|
405
428
|
const snapshot = runtime.snapshot;
|
|
@@ -30,6 +30,11 @@ export interface EvalCredentials {
|
|
|
30
30
|
/** Adapter selector. When omitted, inferred from endpoint scheme (http -> insforge, file/path -> jsonl). */
|
|
31
31
|
adapter?: EvalAdapterId;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Delegate to the shared core/telemetry/build-meta loader so SAL eval, the
|
|
35
|
+
* ext-events sink, and any future telemetry consumer all stamp identical
|
|
36
|
+
* pencil_version / commit_hash on emitted rows.
|
|
37
|
+
*/
|
|
33
38
|
export declare function loadBuildMeta(): BuildMeta;
|
|
34
39
|
export declare const BUILD_META: BuildMeta;
|
|
35
40
|
export declare function resolveStaleCleanupEnabled(envValue: string | undefined, credentials: EvalCredentials | undefined): boolean;
|
|
@@ -4,10 +4,8 @@
|
|
|
4
4
|
* [TO]: Consumed by extensions/defaults/sal/index.ts and tests that validate SAL experiment/config behavior
|
|
5
5
|
* [HERE]: extensions/defaults/sal/sal-config.ts - configuration and path boundary for Structural Anchor Localization
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { dirname, join } from "node:path";
|
|
10
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { loadBuildMeta as loadBuildMetaShared, loadInsforgeCredentials } from "../../../core/telemetry/index.js";
|
|
11
9
|
export const SAL_AB_ENV = "NANOPENCIL_SAL_AB";
|
|
12
10
|
export const EVAL_ENABLED_ENV = "NANOPENCIL_EVAL_ENABLED";
|
|
13
11
|
export const EVAL_ENDPOINT_ENV = "NANOPENCIL_EVAL_ENDPOINT";
|
|
@@ -19,37 +17,13 @@ export const EVAL_API_KEY_HEADER_ENV = "NANOPENCIL_EVAL_API_KEY_HEADER";
|
|
|
19
17
|
export const EVAL_HEADERS_JSON_ENV = "NANOPENCIL_EVAL_HEADERS_JSON";
|
|
20
18
|
export const EVAL_CREDENTIALS_FILE_ENV = "NANOPENCIL_EVAL_CREDENTIALS_FILE";
|
|
21
19
|
export const EVAL_STALE_CLEANUP_ENV = "NANOPENCIL_EVAL_CLEANUP_STALE_RUNS";
|
|
20
|
+
/**
|
|
21
|
+
* Delegate to the shared core/telemetry/build-meta loader so SAL eval, the
|
|
22
|
+
* ext-events sink, and any future telemetry consumer all stamp identical
|
|
23
|
+
* pencil_version / commit_hash on emitted rows.
|
|
24
|
+
*/
|
|
22
25
|
export function loadBuildMeta() {
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
const thisFile = fileURLToPath(import.meta.url);
|
|
26
|
-
const thisDir = dirname(thisFile);
|
|
27
|
-
const distMeta = join(thisDir, "..", "..", "..", "build-meta.json");
|
|
28
|
-
if (existsSync(distMeta)) {
|
|
29
|
-
const parsed = JSON.parse(readFileSync(distMeta, "utf-8"));
|
|
30
|
-
return {
|
|
31
|
-
version: parsed.version ?? fallback.version,
|
|
32
|
-
commitHash: parsed.commitHash,
|
|
33
|
-
branch: parsed.branch,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
const pkgCandidates = [
|
|
37
|
-
join(thisDir, "..", "..", "..", "package.json"),
|
|
38
|
-
join(thisDir, "..", "..", "..", "..", "package.json"),
|
|
39
|
-
];
|
|
40
|
-
for (const p of pkgCandidates) {
|
|
41
|
-
if (existsSync(p)) {
|
|
42
|
-
const pkg = JSON.parse(readFileSync(p, "utf-8"));
|
|
43
|
-
if (pkg.name === "@pencil-agent/nano-pencil") {
|
|
44
|
-
return { version: pkg.version ?? fallback.version };
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
// Non-fatal: dev and tests can run without build metadata.
|
|
51
|
-
}
|
|
52
|
-
return fallback;
|
|
26
|
+
return loadBuildMetaShared();
|
|
53
27
|
}
|
|
54
28
|
export const BUILD_META = loadBuildMeta();
|
|
55
29
|
export function resolveStaleCleanupEnabled(envValue, credentials) {
|
|
@@ -92,55 +66,14 @@ export function parseHeadersJson(raw, reportDiagnostic) {
|
|
|
92
66
|
return {};
|
|
93
67
|
}
|
|
94
68
|
}
|
|
95
|
-
function resolveCredentialsFileCandidates(workspaceRoot) {
|
|
96
|
-
const envPath = process.env[EVAL_CREDENTIALS_FILE_ENV];
|
|
97
|
-
const workspacePath = join(workspaceRoot, ".memory-experiments", "credentials.json");
|
|
98
|
-
const userPath = join(homedir(), ".memory-experiments", "credentials.json");
|
|
99
|
-
return [envPath, workspacePath, userPath].filter((path) => Boolean(path));
|
|
100
|
-
}
|
|
101
|
-
function readCredentialsFromFile(filePath, reportDiagnostic) {
|
|
102
|
-
try {
|
|
103
|
-
if (!existsSync(filePath))
|
|
104
|
-
return undefined;
|
|
105
|
-
const raw = readFileSync(filePath, "utf-8");
|
|
106
|
-
const parsed = JSON.parse(raw);
|
|
107
|
-
if (!parsed || typeof parsed !== "object")
|
|
108
|
-
return undefined;
|
|
109
|
-
if (Array.isArray(parsed.credentials)) {
|
|
110
|
-
const entry = parsed.credentials.find((e) => e.id === "insforge" && e.enabled !== false);
|
|
111
|
-
if (!entry)
|
|
112
|
-
return undefined;
|
|
113
|
-
return {
|
|
114
|
-
endpoint: entry.endpoint ?? entry.insforge_url,
|
|
115
|
-
insforge_url: entry.insforge_url ?? entry.endpoint,
|
|
116
|
-
api_key: entry.api_key ?? entry.apiKey,
|
|
117
|
-
anon_key: entry.anon_key,
|
|
118
|
-
api_key_header: entry.api_key_header,
|
|
119
|
-
headers: entry.headers,
|
|
120
|
-
enabled: entry.enabled,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
return parsed;
|
|
124
|
-
}
|
|
125
|
-
catch (err) {
|
|
126
|
-
reportDiagnostic?.({
|
|
127
|
-
source: "sal.eval",
|
|
128
|
-
severity: "warning",
|
|
129
|
-
category: "config",
|
|
130
|
-
message: "SAL eval credentials file could not be read.",
|
|
131
|
-
detail: { filePath, error: err.message },
|
|
132
|
-
fingerprint: "sal.eval:config:credentials-read-failed",
|
|
133
|
-
});
|
|
134
|
-
return undefined;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
69
|
export function resolveEvalCredentials(workspaceRoot, reportDiagnostic) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
70
|
+
// Delegate file discovery + JSON parsing + format normalization to the
|
|
71
|
+
// shared telemetry base. SAL only needs to type-assert the result back to
|
|
72
|
+
// its own extended shape (EvalCredentials adds cleanup_stale_runs +
|
|
73
|
+
// adapter on top of InsforgeCredentialsBase).
|
|
74
|
+
const envPath = process.env[EVAL_CREDENTIALS_FILE_ENV];
|
|
75
|
+
const creds = loadInsforgeCredentials(workspaceRoot, "sal.eval", reportDiagnostic, envPath);
|
|
76
|
+
return creds ?? undefined;
|
|
144
77
|
}
|
|
145
78
|
export function normalizeExperimentId(experimentId) {
|
|
146
79
|
const raw = (experimentId ?? "").trim();
|