pi-supernova 0.8.2 → 0.9.1
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/README.md +188 -51
- package/docs/CHANGELOG.md +86 -1
- package/docs/TOKEN_COSTS.md +38 -0
- package/index.js +10 -175
- package/package.json +2 -1
- package/src/adapters/bash.js +14 -30
- package/src/adapters/errors.js +1 -9
- package/src/adapters/read-focus.js +98 -0
- package/src/adapters/read-image.js +51 -0
- package/src/adapters/read-json.js +42 -0
- package/src/adapters/read-text.js +71 -0
- package/src/adapters/read.js +66 -635
- package/src/bridge/catalog.js +3 -2
- package/src/bridge/host-bridge.js +35 -167
- package/src/bridge/tool-registry.js +104 -0
- package/src/bridge/trace.js +41 -0
- package/src/context/evidence-graph.js +249 -0
- package/src/context/evidence-rank.js +153 -0
- package/src/context/evidence.js +10 -424
- package/src/context/fuzzy.js +116 -43
- package/src/context/query.js +80 -0
- package/src/context/repo-index.js +23 -166
- package/src/context/search-files.js +19 -0
- package/src/context/search.js +2 -24
- package/src/context/snap-search.js +203 -0
- package/src/context/snap.js +5 -266
- package/src/context/source-entry.js +112 -0
- package/src/contract/bash.js +6 -1
- package/src/contract/program.js +36 -0
- package/src/contract/read.js +8 -53
- package/src/fs/check.js +1 -1
- package/src/fs/commit.js +161 -0
- package/src/fs/diff.js +11 -15
- package/src/fs/directory.js +79 -0
- package/src/fs/file-io.js +100 -0
- package/src/fs/glob.js +54 -0
- package/src/fs/json-size.js +54 -0
- package/src/fs/lines.js +117 -0
- package/src/fs/read-window.js +74 -0
- package/src/fs/session-resource.js +50 -0
- package/src/fs/text-ops.js +7 -227
- package/src/fs/vfs.js +5 -239
- package/src/fs/workspace.js +2 -1
- package/src/output/bottleneck.js +13 -67
- package/src/output/final.js +114 -0
- package/src/output/format.js +94 -5
- package/src/output/outcome.js +91 -0
- package/src/runtime/batch-input.js +68 -0
- package/src/runtime/guest-api.js +281 -0
- package/src/runtime/guest-worker.js +62 -333
- package/src/runtime/parallel.js +41 -39
- package/src/runtime/program-batch.js +21 -75
- package/src/runtime/program-file.js +3 -11
- package/src/runtime/program.js +141 -0
- package/src/runtime/reference.js +6 -5
- package/src/runtime/runtime.js +77 -253
- package/src/runtime/worker-pool.js +91 -0
- package/src/shared/decode.js +22 -8
- package/src/shared/image-worker.js +30 -0
- package/src/shared/image.js +78 -0
- package/src/shared/png.js +57 -0
- package/src/shared/result.js +77 -0
- package/src/shared/syntax-context.js +61 -3
- package/src/ui/host-render.js +104 -0
- package/src/ui/progress.js +51 -0
- package/src/ui/render.js +21 -421
- package/src/ui/trace.js +277 -0
package/src/bridge/catalog.js
CHANGED
|
@@ -2,10 +2,11 @@ import { isString } from "../shared/decode.js";
|
|
|
2
2
|
|
|
3
3
|
/** Optimal string alignment distance: insert/delete/substitute/adjacent-transpose cost 1. */
|
|
4
4
|
function osaCell(a, b, rows, i, j) {
|
|
5
|
-
const cost = a[i - 1]
|
|
5
|
+
const cost = Number(a[i - 1] !== b[j - 1]);
|
|
6
6
|
let best = Math.min(rows[i - 1][j] + 1, rows[i][j - 1] + 1, rows[i - 1][j - 1] + cost);
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
// At a boundary, the missing character cannot equal an in-range character.
|
|
9
|
+
if (a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) best = Math.min(best, rows[i - 2][j - 2] + 1);
|
|
9
10
|
|
|
10
11
|
return best;
|
|
11
12
|
}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
import {createToolRegistry} from './tool-registry.js';
|
|
2
|
+
import {traceArgs,finishRecord} from './trace.js';
|
|
1
3
|
import * as fs from "node:fs/promises";
|
|
2
4
|
import * as path from "node:path";
|
|
3
|
-
import { packageHostResult
|
|
4
|
-
|
|
5
|
-
import { isString, isFunction
|
|
5
|
+
import { packageHostResult } from "../output/bottleneck.js";
|
|
6
|
+
|
|
7
|
+
import { errorMessage, isString, isFunction } from "../shared/decode.js";
|
|
6
8
|
import { isMutatingTool, runParallelWave, createNativeScheduler } from "../runtime/parallel.js";
|
|
7
9
|
import { unknownToolMessage } from "./catalog.js";
|
|
8
|
-
import {
|
|
10
|
+
import { resolveInvokeTarget } from "./invoke.js";
|
|
9
11
|
import { buildWriteDiff } from "../fs/diff.js";
|
|
10
12
|
import { WorkspaceIndex } from "../context/repo-index.js";
|
|
11
13
|
import { SeenLedger } from "../context/ledger.js";
|
|
12
|
-
import { CausalVfs } from "../fs/vfs.js";
|
|
14
|
+
import { CausalVfs, resolveCommitTarget } from "../fs/vfs.js";
|
|
13
15
|
import { resolveWorkspacePath, runCommand, clearPathCache, relativeSlash } from "../fs/workspace.js";
|
|
14
16
|
import { createNativeAdapters } from "../adapters/index.js";
|
|
15
17
|
import { resultDiff, boundedWriteDiff, writeSnapshot } from "../fs/text-ops.js";
|
|
@@ -31,8 +33,6 @@ export function createHostBridge({ pi, config, getCwd, registry, ledger: runLedg
|
|
|
31
33
|
const natives = createNativeAdapters(getCwd, vfs, config, index, ledger, hooks);
|
|
32
34
|
let callCount = 0;
|
|
33
35
|
let activeCtx = null;
|
|
34
|
-
let hostSession = null;
|
|
35
|
-
let boundSessionId;
|
|
36
36
|
let activeSignal = undefined;
|
|
37
37
|
let trace = [];
|
|
38
38
|
let callListener = null;
|
|
@@ -72,109 +72,16 @@ export function createHostBridge({ pi, config, getCwd, registry, ledger: runLedg
|
|
|
72
72
|
return env;
|
|
73
73
|
};
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function wrapHostRegister() {
|
|
80
|
-
if (registry || !pi || !isFunction(pi.registerTool)) return;
|
|
81
|
-
const original = pi.registerTool.bind(pi);
|
|
82
|
-
const excluded = new Set(config.excludeTools || []);
|
|
83
|
-
pi.registerTool = (tool) => {
|
|
84
|
-
if (captureHostTool(tool, excluded)) {
|
|
85
|
-
executors.set(tool.name, tool.execute.bind(tool));
|
|
86
|
-
definitions.set(tool.name, tool);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return original(tool);
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
wrapHostRegister();
|
|
75
|
+
const tools = createToolRegistry({pi,config,registry,natives,executors,definitions});
|
|
76
|
+
const {refreshTools,isCallable,externalNames,hostTool,evalToolNames} = tools;
|
|
94
77
|
|
|
95
78
|
function bindCallContext(ctx, signal) {
|
|
96
79
|
activeCtx = ctx || null;
|
|
97
|
-
|
|
98
|
-
boundSessionId = sessionId;
|
|
99
|
-
const registry = pi?.pi?.AgentRegistry?.global?.();
|
|
100
|
-
let sessions = [];
|
|
101
|
-
|
|
102
|
-
try { sessions = registry?.list?.() ?? []; } catch {}
|
|
103
|
-
if (!Array.isArray(sessions)) sessions = [];
|
|
104
|
-
hostSession = sessionId
|
|
105
|
-
? sessions.map(ref => ref.session).find(session => !session?.isDisposed && session?.sessionManager?.getSessionId?.() === sessionId) ?? null
|
|
106
|
-
: null;
|
|
80
|
+
tools.bindSession(ctx);
|
|
107
81
|
activeSignal = signal;
|
|
108
82
|
vfs.signal = signal;
|
|
109
83
|
}
|
|
110
84
|
|
|
111
|
-
function evalToolNames() {
|
|
112
|
-
try { return hostSession?.getEvalBridgeToolNames?.() ?? []; }
|
|
113
|
-
catch { return []; }
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function hostTool(name) {
|
|
117
|
-
if (!hostSession) return undefined;
|
|
118
|
-
const metadata = definitions.get(name);
|
|
119
|
-
|
|
120
|
-
// Keep Supernova's transactional adapters for ordinary built-ins. Respect overrides.
|
|
121
|
-
if (Object.hasOwn(natives, name) && metadata?.sourceInfo?.source === "builtin") return undefined;
|
|
122
|
-
|
|
123
|
-
try { return hostSession.getToolForEvalBridge?.(name); }
|
|
124
|
-
catch { return undefined; }
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function callableEnv() {
|
|
128
|
-
return {
|
|
129
|
-
excluded: new Set(config.excludeTools || []),
|
|
130
|
-
hostSession,
|
|
131
|
-
natives,
|
|
132
|
-
executors,
|
|
133
|
-
sessionInvalid: () => hostSession && (hostSession.isDisposed || hostSession.sessionManager?.getSessionId?.() !== boundSessionId),
|
|
134
|
-
nativeOwned: name => Object.hasOwn(natives, name) && !executors.has(name)
|
|
135
|
-
&& (!hostSession || !definitions.has(name) || definitions.get(name).sourceInfo?.source === "builtin"),
|
|
136
|
-
evalAllows: name => {
|
|
137
|
-
if (!evalToolNames().includes(name) && definitions.has(name)) return false;
|
|
138
|
-
|
|
139
|
-
return !!hostTool(name) || (Object.hasOwn(natives, name) && (!definitions.has(name) || definitions.get(name).sourceInfo?.source === "builtin"));
|
|
140
|
-
},
|
|
141
|
-
listed: name => {
|
|
142
|
-
let activeTools;
|
|
143
|
-
|
|
144
|
-
try { activeTools = isFunction(pi?.getActiveTools) ? pi.getActiveTools() : undefined; } catch {}
|
|
145
|
-
|
|
146
|
-
if (definitions.has(name) && Array.isArray(activeTools) && !activeTools.includes(name)) return false;
|
|
147
|
-
|
|
148
|
-
return executors.has(name) || Object.hasOwn(natives, name);
|
|
149
|
-
},
|
|
150
|
-
hostTool,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function isCallable(name) {
|
|
155
|
-
return toolIsCallable(name, callableEnv());
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function refreshTools() {
|
|
159
|
-
let listed = [];
|
|
160
|
-
|
|
161
|
-
try { listed = pi?.getAllTools?.() ?? []; } catch {}
|
|
162
|
-
const tools = Array.isArray(listed) ? listed : [];
|
|
163
|
-
|
|
164
|
-
for (const tool of tools) {
|
|
165
|
-
if (!isString(tool?.name)) continue;
|
|
166
|
-
definitions.set(tool.name, { ...definitions.get(tool.name), ...tool });
|
|
167
|
-
|
|
168
|
-
if (!hostSession && isFunction(tool.execute)) executors.set(tool.name, tool.execute.bind(tool));
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return [...definitions.values()].filter(tool => isCallable(tool.name));
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function externalNames() {
|
|
175
|
-
return [...definitions.keys()].filter(name => !!hostTool(name) || executors.has(name));
|
|
176
|
-
}
|
|
177
|
-
|
|
178
85
|
function resetCallBudget() {
|
|
179
86
|
closed = false;
|
|
180
87
|
vfs.closed = false;
|
|
@@ -185,26 +92,6 @@ export function createHostBridge({ pi, config, getCwd, registry, ledger: runLedg
|
|
|
185
92
|
clearPathCache();
|
|
186
93
|
}
|
|
187
94
|
|
|
188
|
-
function getTrace() {
|
|
189
|
-
return [...trace];
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function setCallListener(fn) {
|
|
193
|
-
callListener = isFunction(fn) ? fn : null;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function beginSpeculation() {
|
|
197
|
-
return vfs.begin();
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
async function commitSpeculation() {
|
|
201
|
-
return await vfs.commit();
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function rollbackSpeculation() {
|
|
205
|
-
return vfs.rollback();
|
|
206
|
-
}
|
|
207
|
-
|
|
208
95
|
function notifyCall(record) {
|
|
209
96
|
if (!callListener) return;
|
|
210
97
|
|
|
@@ -261,25 +148,6 @@ export function createHostBridge({ pi, config, getCwd, registry, ledger: runLedg
|
|
|
261
148
|
notifyCall(record);
|
|
262
149
|
}
|
|
263
150
|
|
|
264
|
-
function traceArgs(args) {
|
|
265
|
-
if (!isObject(args)) return {};
|
|
266
|
-
const out = {};
|
|
267
|
-
|
|
268
|
-
for (const key of ["path", "target", "query", "pattern", "command", "cwd", "glob", "action", "op"]) {
|
|
269
|
-
const value = args[key];
|
|
270
|
-
|
|
271
|
-
if (isString(value)) out[key] = truncateChars(value, 240, "trace").text;
|
|
272
|
-
// eslint-disable-next-line anti-slop/no-runtime-typeof -- display-only label: the value is already handled, this names its kind for the trace.
|
|
273
|
-
else if (Array.isArray(value)) out[key] = value.slice(0, 128).map(item => isString(item) ? truncateChars(item, 240, "trace").text : typeof item);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (isString(args.content)) out.content = args.content.length + " chars";
|
|
277
|
-
if (Array.isArray(args.edits)) out.edits = args.edits.length + " edits";
|
|
278
|
-
if (Array.isArray(args.args)) out.args = args.args.length + " argv";
|
|
279
|
-
|
|
280
|
-
return out;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
151
|
function assertOwnedOverride(name, args) {
|
|
284
152
|
if (name === "read" && (args?.json !== undefined || /^(agent|artifact):\/\/.*\?/i.test(String(args?.path)))) throw new Error("JSON projection requires the Supernova-owned read adapter, not an external override");
|
|
285
153
|
if (name === "write" && args?.append === true) throw new Error("append requires the Supernova-owned write adapter, not an external override");
|
|
@@ -296,7 +164,7 @@ export function createHostBridge({ pi, config, getCwd, registry, ledger: runLedg
|
|
|
296
164
|
|
|
297
165
|
try {
|
|
298
166
|
const res = await target.exec(`supernova:${name}:${callId}`, args || {}, activeSignal, undefined, target.delegated
|
|
299
|
-
? { ...activeCtx, settings:
|
|
167
|
+
? { ...activeCtx, settings: tools.session.settings, toolNames: evalToolNames(), autoApprove: false }
|
|
300
168
|
: activeCtx);
|
|
301
169
|
|
|
302
170
|
completeRecord(record, res, fallbackDiff);
|
|
@@ -307,8 +175,8 @@ export function createHostBridge({ pi, config, getCwd, registry, ledger: runLedg
|
|
|
307
175
|
}
|
|
308
176
|
}
|
|
309
177
|
|
|
310
|
-
async function invokeNative(target, args, record) {
|
|
311
|
-
const res = await target.native(target.argvOwned ? { ...args, args: args.args.map(String) } : args || {}, activeSignal);
|
|
178
|
+
async function invokeNative(target, args, record, onItem) {
|
|
179
|
+
const res = await target.native(target.argvOwned ? { ...args, args: args.args.map(String) } : args || {}, activeSignal, onItem);
|
|
312
180
|
completeRecord(record, res);
|
|
313
181
|
|
|
314
182
|
return res;
|
|
@@ -317,11 +185,11 @@ export function createHostBridge({ pi, config, getCwd, registry, ledger: runLedg
|
|
|
317
185
|
function failRecord(record, error) {
|
|
318
186
|
record.ok = false;
|
|
319
187
|
record.ms = Date.now() - record.time;
|
|
320
|
-
record.error =
|
|
188
|
+
record.error = errorMessage(error);
|
|
321
189
|
notifyCall(record);
|
|
322
190
|
}
|
|
323
191
|
|
|
324
|
-
async function invokeRaw(name, args) {
|
|
192
|
+
async function invokeRaw(name, args, onItem) {
|
|
325
193
|
assertRunOpen(name);
|
|
326
194
|
const callId = ++sharedRegistry.callSeq;
|
|
327
195
|
assertCallableTarget(name);
|
|
@@ -336,10 +204,10 @@ export function createHostBridge({ pi, config, getCwd, registry, ledger: runLedg
|
|
|
336
204
|
notifyCall(record);
|
|
337
205
|
|
|
338
206
|
try {
|
|
339
|
-
const target = resolveInvokeTarget(name, args, { hostTool, hostSession, executors, natives });
|
|
207
|
+
const target = resolveInvokeTarget(name, args, { hostTool, hostSession: tools.session, executors, natives });
|
|
340
208
|
|
|
341
209
|
if (target.kind === "override") return await invokeOverride(target, name, args, record, callId);
|
|
342
|
-
if (target.kind === "native") return await invokeNative(target, args, record);
|
|
210
|
+
if (target.kind === "native") return await invokeNative(target, args, record, onItem);
|
|
343
211
|
|
|
344
212
|
throw new Error(unknownToolMessage(name, [...executors.keys(), ...Object.keys(natives)]));
|
|
345
213
|
} catch (error) {
|
|
@@ -348,25 +216,25 @@ export function createHostBridge({ pi, config, getCwd, registry, ledger: runLedg
|
|
|
348
216
|
}
|
|
349
217
|
}
|
|
350
218
|
|
|
351
|
-
function
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (Number.isInteger(exitCode) && exitCode !== 0) record.exitCode = exitCode;
|
|
357
|
-
const text = isObject(res) && Array.isArray(res.content)
|
|
358
|
-
? res.content.filter(part => part?.type === "text" && isString(part.text)).map(part => part.text).join("\n")
|
|
359
|
-
: undefined;
|
|
219
|
+
function fileMutationKey(name, args) {
|
|
220
|
+
if (!["edit", "write", "apply_patch"].includes(name) || !isString(args?.path)) return;
|
|
221
|
+
// Overrides can mutate more than their declared path: keep them global.
|
|
222
|
+
if (hostTool(name) || executors.has(name)) return;
|
|
360
223
|
|
|
361
|
-
|
|
224
|
+
return async () => {
|
|
225
|
+
const target = await resolveWorkspacePath(getCwd(), args.path, name, false, true);
|
|
226
|
+
// Share the commit identity, including symlinks and not-yet-created files.
|
|
227
|
+
return (await resolveCommitTarget(target)).target;
|
|
228
|
+
};
|
|
362
229
|
}
|
|
363
230
|
|
|
364
|
-
async function call(name, args) {
|
|
231
|
+
async function call(name, args, onItem) {
|
|
365
232
|
if (!isString(name) || !name) throw new Error("nova.call requires a tool name");
|
|
366
|
-
const
|
|
233
|
+
const deliver = onItem ? (index, raw) => onItem(index, packageHostResult(raw, config)) : undefined;
|
|
234
|
+
const invoke = async () => packageHostResult(await invokeRaw(name, args, deliver), config);
|
|
367
235
|
const kind = isMutatingTool(name, config, args, definitions.get(name)) ? "write" : "read";
|
|
368
236
|
|
|
369
|
-
return scheduler.schedule(kind, invoke, activeSignal);
|
|
237
|
+
return scheduler.schedule(kind, invoke, activeSignal, fileMutationKey(name, args));
|
|
370
238
|
}
|
|
371
239
|
|
|
372
240
|
async function callMany(calls) {
|
|
@@ -433,13 +301,13 @@ export function createHostBridge({ pi, config, getCwd, registry, ledger: runLedg
|
|
|
433
301
|
close() { closed = true; vfs.closed = true; },
|
|
434
302
|
bindCallContext,
|
|
435
303
|
resetCallBudget,
|
|
436
|
-
getTrace,
|
|
304
|
+
getTrace: () => [...trace],
|
|
437
305
|
getMutations: () => ({ ...vfs.mutations }),
|
|
438
|
-
setCallListener,
|
|
306
|
+
setCallListener: fn => { callListener = isFunction(fn) ? fn : null; },
|
|
439
307
|
barrier: run => scheduler.schedule("write", run, activeSignal),
|
|
440
|
-
beginSpeculation,
|
|
441
|
-
commitSpeculation,
|
|
442
|
-
rollbackSpeculation,
|
|
308
|
+
beginSpeculation: () => vfs.begin(),
|
|
309
|
+
commitSpeculation: async () => await vfs.commit(),
|
|
310
|
+
rollbackSpeculation: () => vfs.rollback(),
|
|
443
311
|
getOverlayDepth: () => vfs.getOverlayDepth(),
|
|
444
312
|
call,
|
|
445
313
|
callMany,
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {isString,isFunction} from '../shared/decode.js';
|
|
2
|
+
import {toolIsCallable} from './invoke.js';
|
|
3
|
+
|
|
4
|
+
export function createToolRegistry({pi,config,registry,natives,executors,definitions}) {
|
|
5
|
+
let hostSession = null, boundSessionId;
|
|
6
|
+
function captureHostTool(tool, excluded) {
|
|
7
|
+
return tool && isString(tool.name) && isFunction(tool.execute) && tool.name !== "supernova" && !excluded.has(tool.name);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function wrapHostRegister() {
|
|
11
|
+
if (registry || !pi || !isFunction(pi.registerTool)) return;
|
|
12
|
+
const original = pi.registerTool.bind(pi);
|
|
13
|
+
const excluded = new Set(config.excludeTools || []);
|
|
14
|
+
pi.registerTool = (tool) => {
|
|
15
|
+
if (captureHostTool(tool, excluded)) {
|
|
16
|
+
executors.set(tool.name, tool.execute.bind(tool));
|
|
17
|
+
definitions.set(tool.name, tool);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return original(tool);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function evalToolNames() {
|
|
25
|
+
try { return hostSession?.getEvalBridgeToolNames?.() ?? []; }
|
|
26
|
+
catch { return []; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function hostTool(name) {
|
|
30
|
+
if (!hostSession) return undefined;
|
|
31
|
+
const metadata = definitions.get(name);
|
|
32
|
+
|
|
33
|
+
// Keep Supernova's transactional adapters for ordinary built-ins. Respect overrides.
|
|
34
|
+
if (Object.hasOwn(natives, name) && metadata?.sourceInfo?.source === "builtin") return undefined;
|
|
35
|
+
|
|
36
|
+
try { return hostSession.getToolForEvalBridge?.(name); }
|
|
37
|
+
catch { return undefined; }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function callableEnv() {
|
|
41
|
+
return {
|
|
42
|
+
excluded: new Set(config.excludeTools || []),
|
|
43
|
+
hostSession,
|
|
44
|
+
natives,
|
|
45
|
+
executors,
|
|
46
|
+
sessionInvalid: () => hostSession && (hostSession.isDisposed || hostSession.sessionManager?.getSessionId?.() !== boundSessionId),
|
|
47
|
+
nativeOwned: name => Object.hasOwn(natives, name) && !executors.has(name)
|
|
48
|
+
&& (!hostSession || !definitions.has(name) || definitions.get(name).sourceInfo?.source === "builtin"),
|
|
49
|
+
evalAllows: name => {
|
|
50
|
+
if (!evalToolNames().includes(name) && definitions.has(name)) return false;
|
|
51
|
+
|
|
52
|
+
return !!hostTool(name) || (Object.hasOwn(natives, name) && (!definitions.has(name) || definitions.get(name).sourceInfo?.source === "builtin"));
|
|
53
|
+
},
|
|
54
|
+
listed: name => {
|
|
55
|
+
let activeTools;
|
|
56
|
+
|
|
57
|
+
try { activeTools = isFunction(pi?.getActiveTools) ? pi.getActiveTools() : undefined; } catch {}
|
|
58
|
+
|
|
59
|
+
if (definitions.has(name) && Array.isArray(activeTools) && !activeTools.includes(name)) return false;
|
|
60
|
+
|
|
61
|
+
return executors.has(name) || Object.hasOwn(natives, name);
|
|
62
|
+
},
|
|
63
|
+
hostTool,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isCallable(name) {
|
|
68
|
+
return toolIsCallable(name, callableEnv());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function refreshTools() {
|
|
72
|
+
let listed = [];
|
|
73
|
+
|
|
74
|
+
try { listed = pi?.getAllTools?.() ?? []; } catch {}
|
|
75
|
+
const tools = Array.isArray(listed) ? listed : [];
|
|
76
|
+
|
|
77
|
+
for (const tool of tools) {
|
|
78
|
+
if (!isString(tool?.name)) continue;
|
|
79
|
+
definitions.set(tool.name, { ...definitions.get(tool.name), ...tool });
|
|
80
|
+
|
|
81
|
+
if (!hostSession && isFunction(tool.execute)) executors.set(tool.name, tool.execute.bind(tool));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return [...definitions.values()].filter(tool => isCallable(tool.name));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function externalNames() {
|
|
88
|
+
return [...definitions.keys()].filter(name => !!hostTool(name) || executors.has(name));
|
|
89
|
+
}
|
|
90
|
+
function bindSession(ctx) {
|
|
91
|
+
const sessionId = ctx?.sessionManager?.getSessionId?.();
|
|
92
|
+
boundSessionId = sessionId;
|
|
93
|
+
const registry = pi?.pi?.AgentRegistry?.global?.();
|
|
94
|
+
let sessions = [];
|
|
95
|
+
|
|
96
|
+
try { sessions = registry?.list?.() ?? []; } catch {}
|
|
97
|
+
if (!Array.isArray(sessions)) sessions = [];
|
|
98
|
+
hostSession = sessionId
|
|
99
|
+
? sessions.map(ref => ref.session).find(session => !session?.isDisposed && session?.sessionManager?.getSessionId?.() === sessionId) ?? null
|
|
100
|
+
: null;
|
|
101
|
+
}
|
|
102
|
+
wrapHostRegister();
|
|
103
|
+
return {refreshTools,isCallable,externalNames,hostTool,evalToolNames,bindSession,get session(){return hostSession;}};
|
|
104
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {READ_PREVIEW} from "../shared/result.js";
|
|
2
|
+
import {isObject,isString} from '../shared/decode.js';
|
|
3
|
+
import {truncateChars} from '../output/format.js';
|
|
4
|
+
import {hostResultFailed} from '../output/bottleneck.js';
|
|
5
|
+
|
|
6
|
+
function traceArgs(args) {
|
|
7
|
+
if (!isObject(args)) return {};
|
|
8
|
+
const out = {};
|
|
9
|
+
|
|
10
|
+
for (const key of ["path", "target", "query", "pattern", "command", "cwd", "glob", "action", "op"]) {
|
|
11
|
+
const value = args[key];
|
|
12
|
+
|
|
13
|
+
if (isString(value)) out[key] = truncateChars(value, 240, "trace").text;
|
|
14
|
+
// eslint-disable-next-line anti-slop/no-runtime-typeof -- display-only label: the value is already handled, this names its kind for the trace.
|
|
15
|
+
else if (Array.isArray(value)) out[key] = value.slice(0, 128).map(item => isString(item) ? truncateChars(item, 240, "trace").text : typeof item);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (isString(args.content)) out.content = args.content.length + " chars";
|
|
19
|
+
if (Array.isArray(args.edits)) out.edits = args.edits.length + " edits";
|
|
20
|
+
if (Array.isArray(args.args)) out.args = args.args.length + " argv";
|
|
21
|
+
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resultText(res) {
|
|
26
|
+
return isObject(res) && Array.isArray(res.content)
|
|
27
|
+
? res.content.filter(part => part?.type === "text" && isString(part.text)).map(part => part.text).join("\n")
|
|
28
|
+
: undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function finishRecord(record, res) {
|
|
32
|
+
record.ms = Date.now() - record.time;
|
|
33
|
+
record.ok = !hostResultFailed(res);
|
|
34
|
+
const exitCode = isObject(res?.details) ? res.details.exitCode : undefined;
|
|
35
|
+
|
|
36
|
+
if (Number.isInteger(exitCode) && exitCode !== 0) record.exitCode = exitCode;
|
|
37
|
+
const text = isObject(res) && Object.hasOwn(res,READ_PREVIEW) ? res[READ_PREVIEW] : resultText(res);
|
|
38
|
+
|
|
39
|
+
if (text) record.resultText = truncateChars(text, 4096, "trace").text;
|
|
40
|
+
}
|
|
41
|
+
export { traceArgs, finishRecord };
|