opencode-agent-skill 14.2.0-beta.1 → 15.0.0
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 +51 -18
- package/docs/PI-COMPAT.md +167 -3
- package/docs/V15-MANAGED-RUNTIME.md +128 -0
- package/global-config/agents/architect.md +63 -1
- package/global-config/agents/executor.md +7 -0
- package/global-config/agents/plan-checker.md +8 -1
- package/global-config/agents/verifier.md +3 -3
- package/global-config/skills/database-engineering/references/workflow.md +15 -0
- package/global-config/skills/engineering-orchestrator/references/long-horizon.md +21 -11
- package/lib/activity-deadline.mjs +70 -0
- package/lib/adaptive-context-budget.mjs +25 -5
- package/lib/affected-tests.mjs +1 -1
- package/lib/capability-fabric.mjs +40 -0
- package/lib/code-intelligence/edit-anchor.mjs +102 -0
- package/lib/code-intelligence/index.mjs +95 -0
- package/lib/code-intelligence/lsp-provider.mjs +189 -0
- package/lib/completion-auditor.mjs +102 -0
- package/lib/document-ingestion.mjs +60 -0
- package/lib/evidence-store.mjs +92 -8
- package/lib/execution-contract.mjs +579 -0
- package/lib/fast-verification-gate.mjs +69 -0
- package/lib/leaf-runtime-optimizer.mjs +76 -0
- package/lib/mcp-health.mjs +144 -0
- package/lib/mcp-tool-policy.mjs +19 -0
- package/lib/memory-engine.mjs +28 -0
- package/lib/pi-child-invocation.mjs +65 -0
- package/lib/pi-rpc-pool.mjs +61 -9
- package/lib/plan-salvage.mjs +124 -0
- package/lib/planning-speed-policy.mjs +131 -0
- package/lib/prompt-cache.mjs +26 -12
- package/lib/repo-graph.mjs +1 -1
- package/lib/repo-inspect.mjs +1 -0
- package/lib/reversible-context.mjs +49 -0
- package/lib/runtime-artifacts.mjs +42 -0
- package/lib/runtime-events.mjs +33 -1
- package/lib/safety.mjs +1 -0
- package/lib/semantic-index.mjs +1 -1
- package/lib/service-manager.mjs +526 -0
- package/lib/session-display.mjs +49 -0
- package/lib/task-engine.mjs +6 -10
- package/lib/task-graph.mjs +87 -0
- package/lib/task-policy.mjs +210 -8
- package/lib/trajectory.mjs +111 -1
- package/lib/turbo-fast-path.mjs +44 -0
- package/lib/workspace-fingerprint.mjs +2 -11
- package/lib/workspace-root.mjs +49 -0
- package/lib/worktree-sandbox.mjs +234 -11
- package/package.json +9 -6
- package/pi/extensions/ues-child-runtime.ts +300 -2
- package/pi/extensions/ues.ts +1887 -160
- package/scripts/check-release-consistency.mjs +25 -2
- package/scripts/check-source-integrity.mjs +532 -3
- package/scripts/eval-pi.mjs +76 -36
- package/scripts/smoke-pi-extension.mjs +1 -1
- package/pi/prompts/ues-run.md +0 -17
|
@@ -6,10 +6,30 @@ import { getEvidenceSelected } from "../../lib/evidence-store.mjs";
|
|
|
6
6
|
import { recordVerification } from "../../lib/verification-broker.mjs";
|
|
7
7
|
import { runtimeWorkspaceFingerprint } from "../../lib/workspace-fingerprint.mjs";
|
|
8
8
|
import { destructiveShellRisk } from "../../lib/safety.mjs";
|
|
9
|
+
import { crossToolTempPathRisk, isLocalEnvPath, localEnvWriteRisk } from "../../lib/execution-contract.mjs";
|
|
9
10
|
import {
|
|
10
11
|
canonicalVerificationCommand,
|
|
11
12
|
looksLikeVerificationCommand,
|
|
12
13
|
} from "../../lib/verification-command.mjs";
|
|
14
|
+
import {
|
|
15
|
+
applyAnchoredFileEdits,
|
|
16
|
+
diagnoseCode,
|
|
17
|
+
probeCodeIntelligence,
|
|
18
|
+
readAnchoredCode,
|
|
19
|
+
searchCodeIntelligence,
|
|
20
|
+
} from "../../lib/code-intelligence/index.mjs";
|
|
21
|
+
import { ingestDocument } from "../../lib/document-ingestion.mjs";
|
|
22
|
+
import { compactContext, expandContext, searchContext } from "../../lib/reversible-context.mjs";
|
|
23
|
+
import {
|
|
24
|
+
looksLikeLongRunningServiceCommand,
|
|
25
|
+
restartService,
|
|
26
|
+
serviceLogs,
|
|
27
|
+
serviceStatus,
|
|
28
|
+
startService,
|
|
29
|
+
stopAllServices,
|
|
30
|
+
stopService,
|
|
31
|
+
waitForService,
|
|
32
|
+
} from "../../lib/service-manager.mjs";
|
|
13
33
|
|
|
14
34
|
const toolExecutionState = new Map<string, {
|
|
15
35
|
startedAt: number;
|
|
@@ -72,15 +92,67 @@ async function capturedText(event: any, fallback: string) {
|
|
|
72
92
|
}
|
|
73
93
|
|
|
74
94
|
export default function (pi: ExtensionAPI) {
|
|
95
|
+
// Internal-only extension: the package host may discover this file alongside
|
|
96
|
+
// ues.ts, but only specialist child Pi processes should register its tools.
|
|
97
|
+
// Parent launch paths set UES_CHILD_PROCESS=1 for both CLI and RPC workers.
|
|
98
|
+
if (process.env.UES_CHILD_PROCESS !== "1") return;
|
|
99
|
+
|
|
75
100
|
const clearExecutionState = () => toolExecutionState.clear();
|
|
76
101
|
pi.on("session_start", clearExecutionState);
|
|
77
|
-
pi.on("session_shutdown",
|
|
102
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
103
|
+
clearExecutionState();
|
|
104
|
+
await stopAllServices(ctx.cwd).catch(() => []);
|
|
105
|
+
});
|
|
78
106
|
|
|
79
107
|
pi.on("tool_call", async (event, ctx) => {
|
|
80
108
|
const toolName = String(event.toolName || "");
|
|
109
|
+
const input: any = event.input || {};
|
|
110
|
+
const localEnvAllowed = String(process.env.UES_CHILD_ALLOW_LOCAL_ENV_WRITE || "") === "1";
|
|
111
|
+
const writeTool = ["edit", "write", "write_file", "apply_patch", "ues_code_edit"].includes(toolName);
|
|
112
|
+
const fileTool = ["read", "edit", "write", "write_file", "apply_patch", "ues_code", "ues_code_edit"].includes(toolName);
|
|
113
|
+
const fileCandidate = String(input.file || input.path || input.filePath || input.target || "");
|
|
114
|
+
const tempPathRisk = crossToolTempPathRisk(fileCandidate);
|
|
115
|
+
if (fileTool && tempPathRisk.risky) {
|
|
116
|
+
toolExecutionState.delete(String(event.toolCallId || ""));
|
|
117
|
+
return {
|
|
118
|
+
block: true,
|
|
119
|
+
reason:
|
|
120
|
+
"UES portable temp-path guard blocked " + fileCandidate +
|
|
121
|
+
". On Windows, /tmp and /var/tmp may resolve differently between Pi file tools and bash/MSYS. " +
|
|
122
|
+
"For transient transforms, keep creation/read in one shell pipeline; for cross-tool scratch use a repository-local ignored UES path such as .ues-cache/tmp after creating it.",
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (writeTool && isLocalEnvPath(fileCandidate) && !localEnvAllowed) {
|
|
126
|
+
toolExecutionState.delete(String(event.toolCallId || ""));
|
|
127
|
+
return {
|
|
128
|
+
block: true,
|
|
129
|
+
reason:
|
|
130
|
+
"UES local-env guard blocked a write to " + fileCandidate +
|
|
131
|
+
". .env/.env.* are local runtime inputs; update an example/template or report NEEDS_USER_ENV unless the user explicitly authorized this local env mutation.",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
81
135
|
if (!["bash", "powershell"].includes(toolName)) return undefined;
|
|
82
136
|
|
|
83
|
-
const command = String(
|
|
137
|
+
const command = String(input.command || "");
|
|
138
|
+
const envRisk = localEnvWriteRisk(command);
|
|
139
|
+
if (envRisk.risky && !localEnvAllowed) {
|
|
140
|
+
toolExecutionState.delete(String(event.toolCallId || ""));
|
|
141
|
+
return {
|
|
142
|
+
block: true,
|
|
143
|
+
reason:
|
|
144
|
+
"UES local-env guard blocked a shell write to .env/.env.*. Use .env.example/sample/template or report NEEDS_USER_ENV unless explicitly authorized.",
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (looksLikeLongRunningServiceCommand(command)) {
|
|
148
|
+
toolExecutionState.delete(String(event.toolCallId || ""));
|
|
149
|
+
return {
|
|
150
|
+
block: true,
|
|
151
|
+
reason:
|
|
152
|
+
"UES detected a likely long-running foreground service command. " +
|
|
153
|
+
"Use the ues_service tool (start -> wait-ready/logs -> stop) instead of bash/powershell so the agent does not stall.",
|
|
154
|
+
};
|
|
155
|
+
}
|
|
84
156
|
const risk = destructiveShellRisk(command);
|
|
85
157
|
if (risk.risky) {
|
|
86
158
|
toolExecutionState.delete(String(event.toolCallId || ""));
|
|
@@ -190,6 +262,232 @@ export default function (pi: ExtensionAPI) {
|
|
|
190
262
|
};
|
|
191
263
|
});
|
|
192
264
|
|
|
265
|
+
const AnchoredEdit = Type.Object({
|
|
266
|
+
anchor: Type.String({ minLength: 1 }),
|
|
267
|
+
endAnchor: Type.Optional(Type.String({ minLength: 1 })),
|
|
268
|
+
replacement: Type.String(),
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
pi.registerTool({
|
|
272
|
+
name: "ues_code",
|
|
273
|
+
label: "UES Code Intelligence",
|
|
274
|
+
description:
|
|
275
|
+
"Bounded code/document/context intelligence for weak models: semantic/AST search, hash-anchored reads, optional LSP diagnostics, optional MarkItDown ingestion, and reversible context recovery.",
|
|
276
|
+
parameters: Type.Object({
|
|
277
|
+
action: Type.Union([
|
|
278
|
+
Type.Literal("status"),
|
|
279
|
+
Type.Literal("search"),
|
|
280
|
+
Type.Literal("read"),
|
|
281
|
+
Type.Literal("diagnostics"),
|
|
282
|
+
Type.Literal("document"),
|
|
283
|
+
Type.Literal("context-expand"),
|
|
284
|
+
Type.Literal("context-search"),
|
|
285
|
+
]),
|
|
286
|
+
file: Type.Optional(Type.String()),
|
|
287
|
+
query: Type.Optional(Type.String()),
|
|
288
|
+
structuralPattern: Type.Optional(Type.String()),
|
|
289
|
+
language: Type.Optional(Type.String()),
|
|
290
|
+
startLine: Type.Optional(Type.Number({ minimum: 1 })),
|
|
291
|
+
endLine: Type.Optional(Type.Number({ minimum: 1 })),
|
|
292
|
+
ref: Type.Optional(Type.String()),
|
|
293
|
+
maxBytes: Type.Optional(Type.Number({ minimum: 1, maximum: 128000 })),
|
|
294
|
+
}),
|
|
295
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
296
|
+
try {
|
|
297
|
+
let result: any;
|
|
298
|
+
if (params.action === "status") {
|
|
299
|
+
result = probeCodeIntelligence(params.file || "");
|
|
300
|
+
} else if (params.action === "search") {
|
|
301
|
+
if (!params.query) throw new Error("ues_code search requires query");
|
|
302
|
+
result = await searchCodeIntelligence(ctx.cwd, params.query, {
|
|
303
|
+
structuralPattern: params.structuralPattern,
|
|
304
|
+
language: params.language,
|
|
305
|
+
file: params.file,
|
|
306
|
+
maxResults: 12,
|
|
307
|
+
});
|
|
308
|
+
} else if (params.action === "read") {
|
|
309
|
+
if (!params.file) throw new Error("ues_code read requires file");
|
|
310
|
+
const startLine = Math.max(1, Math.trunc(Number(params.startLine || 1)));
|
|
311
|
+
const endLine = Math.max(startLine, Math.min(startLine + 399, Math.trunc(Number(params.endLine || startLine + 199))));
|
|
312
|
+
result = await readAnchoredCode(ctx.cwd, params.file, { startLine, endLine });
|
|
313
|
+
return {
|
|
314
|
+
content: [{ type: "text", text: [
|
|
315
|
+
`file: ${result.file}; lines: ${result.startLine}-${result.endLine}/${result.lineCount}; sourceHash: ${result.sourceHash}`,
|
|
316
|
+
"",
|
|
317
|
+
result.text,
|
|
318
|
+
].join("\n") }],
|
|
319
|
+
details: { action: params.action, file: result.file, sourceHash: result.sourceHash, startLine: result.startLine, endLine: result.endLine },
|
|
320
|
+
};
|
|
321
|
+
} else if (params.action === "diagnostics") {
|
|
322
|
+
if (!params.file) throw new Error("ues_code diagnostics requires file");
|
|
323
|
+
result = await diagnoseCode(ctx.cwd, params.file, { timeoutMs: 3000, maxDiagnostics: 40 });
|
|
324
|
+
} else if (params.action === "document") {
|
|
325
|
+
if (!params.file) throw new Error("ues_code document requires file");
|
|
326
|
+
const document = await ingestDocument(ctx.cwd, params.file, { maxBytes: Math.min(Number(params.maxBytes || 4 * 1024 * 1024), 4 * 1024 * 1024) });
|
|
327
|
+
const block = await compactContext(ctx.cwd, document.markdown, {
|
|
328
|
+
kind: "document-ingestion",
|
|
329
|
+
source: document.file,
|
|
330
|
+
summary: `Normalized document from ${document.provider}`,
|
|
331
|
+
});
|
|
332
|
+
result = {
|
|
333
|
+
provider: document.provider,
|
|
334
|
+
file: document.file,
|
|
335
|
+
bytes: document.bytes,
|
|
336
|
+
optionalDependency: document.optionalDependency,
|
|
337
|
+
contextRef: block.ref,
|
|
338
|
+
originalChars: block.originalChars,
|
|
339
|
+
summary: block.levels.T1,
|
|
340
|
+
};
|
|
341
|
+
} else if (params.action === "context-expand") {
|
|
342
|
+
if (!params.ref) throw new Error("ues_code context-expand requires ref");
|
|
343
|
+
const expanded = await expandContext(ctx.cwd, params.ref, { maxBytes: params.maxBytes || 16000 });
|
|
344
|
+
return {
|
|
345
|
+
content: [{ type: "text", text: expanded.content }],
|
|
346
|
+
details: { action: params.action, ref: expanded.ref, start: expanded.start, returnedBytes: expanded.returnedBytes, truncated: expanded.truncated },
|
|
347
|
+
};
|
|
348
|
+
} else if (params.action === "context-search") {
|
|
349
|
+
if (!params.ref || !params.query) throw new Error("ues_code context-search requires ref and query");
|
|
350
|
+
result = await searchContext(ctx.cwd, params.ref, params.query, { maxBytes: params.maxBytes || 512000, maxMatches: 12 });
|
|
351
|
+
} else {
|
|
352
|
+
throw new Error("unsupported ues_code action");
|
|
353
|
+
}
|
|
354
|
+
const encoded = JSON.stringify(result, null, 2);
|
|
355
|
+
return {
|
|
356
|
+
content: [{ type: "text", text: encoded.length <= 32000 ? encoded : encoded.slice(0, 32000) + "\n...[bounded by ues_code]" }],
|
|
357
|
+
details: { action: params.action, bounded: encoded.length > 32000 },
|
|
358
|
+
};
|
|
359
|
+
} catch (error) {
|
|
360
|
+
return {
|
|
361
|
+
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
|
|
362
|
+
details: { action: params.action },
|
|
363
|
+
isError: true,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
pi.registerTool({
|
|
370
|
+
name: "ues_code_edit",
|
|
371
|
+
label: "UES Anchored Edit",
|
|
372
|
+
description:
|
|
373
|
+
"Apply fail-closed hash-anchored edits. A stale or mismatched anchor is rejected; re-read with ues_code instead of fuzzy retrying.",
|
|
374
|
+
parameters: Type.Object({
|
|
375
|
+
file: Type.String({ minLength: 1 }),
|
|
376
|
+
edits: Type.Array(AnchoredEdit, { minItems: 1, maxItems: 50 }),
|
|
377
|
+
dryRun: Type.Optional(Type.Boolean()),
|
|
378
|
+
diagnostics: Type.Optional(Type.Boolean()),
|
|
379
|
+
}),
|
|
380
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
381
|
+
try {
|
|
382
|
+
const edited = await applyAnchoredFileEdits(ctx.cwd, params.file, params.edits, { dryRun: params.dryRun === true });
|
|
383
|
+
const diagnostics = params.diagnostics === true && params.dryRun !== true
|
|
384
|
+
? await diagnoseCode(ctx.cwd, params.file, { timeoutMs: 3000, maxDiagnostics: 40 }).catch(() => null)
|
|
385
|
+
: null;
|
|
386
|
+
const result = {
|
|
387
|
+
file: edited.file,
|
|
388
|
+
applied: edited.applied,
|
|
389
|
+
dryRun: edited.dryRun,
|
|
390
|
+
sourceHash: edited.sourceHash,
|
|
391
|
+
outputHash: edited.outputHash,
|
|
392
|
+
diagnostics: diagnostics ? {
|
|
393
|
+
available: diagnostics.available,
|
|
394
|
+
provider: diagnostics.provider,
|
|
395
|
+
reason: diagnostics.reason,
|
|
396
|
+
count: diagnostics.diagnostics?.length || 0,
|
|
397
|
+
items: (diagnostics.diagnostics || []).slice(0, 40),
|
|
398
|
+
} : null,
|
|
399
|
+
};
|
|
400
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], details: result };
|
|
401
|
+
} catch (error) {
|
|
402
|
+
return {
|
|
403
|
+
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
|
|
404
|
+
details: { file: params.file },
|
|
405
|
+
isError: true,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
pi.registerTool({
|
|
412
|
+
name: "ues_service",
|
|
413
|
+
label: "UES Managed Service",
|
|
414
|
+
description:
|
|
415
|
+
"Manage long-running development servers/watchers without blocking the agent. Use start, wait-ready, status, logs, stop, or restart. For start/restart, command is the executable only (for example node or npm); put every argument in args. Services are bounded to the current workspace/runtime and are cleaned up on session shutdown.",
|
|
416
|
+
parameters: Type.Object({
|
|
417
|
+
action: Type.Union([
|
|
418
|
+
Type.Literal("start"),
|
|
419
|
+
Type.Literal("wait-ready"),
|
|
420
|
+
Type.Literal("status"),
|
|
421
|
+
Type.Literal("logs"),
|
|
422
|
+
Type.Literal("stop"),
|
|
423
|
+
Type.Literal("restart"),
|
|
424
|
+
]),
|
|
425
|
+
name: Type.String({ minLength: 1, maxLength: 64 }),
|
|
426
|
+
command: Type.Optional(Type.String({ minLength: 1 })),
|
|
427
|
+
args: Type.Optional(Type.Array(Type.String(), { maxItems: 128 })),
|
|
428
|
+
cwd: Type.Optional(Type.String()),
|
|
429
|
+
readyPort: Type.Optional(Type.Number({ minimum: 1, maximum: 65535 })),
|
|
430
|
+
readyHost: Type.Optional(Type.String({ maxLength: 255 })),
|
|
431
|
+
readyLog: Type.Optional(Type.String({ maxLength: 512 })),
|
|
432
|
+
timeoutMs: Type.Optional(Type.Number({ minimum: 100, maximum: 600000 })),
|
|
433
|
+
lifetimeMs: Type.Optional(Type.Number({ minimum: 10000, maximum: 7200000 })),
|
|
434
|
+
idleTimeoutMs: Type.Optional(Type.Number({ minimum: 5000, maximum: 3600000 })),
|
|
435
|
+
maxChars: Type.Optional(Type.Number({ minimum: 256, maximum: 128000 })),
|
|
436
|
+
}),
|
|
437
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
438
|
+
try {
|
|
439
|
+
let result: any;
|
|
440
|
+
if (params.action === "start") {
|
|
441
|
+
if (!params.command) throw new Error("ues_service start requires command");
|
|
442
|
+
const riskText = [params.command, ...(params.args || [])].join(" ");
|
|
443
|
+
const risk = destructiveShellRisk(riskText);
|
|
444
|
+
if (risk.risky) throw new Error(`UES service safety blocked ${risk.id || "destructive"} command`);
|
|
445
|
+
result = await startService(ctx.cwd, {
|
|
446
|
+
name: params.name,
|
|
447
|
+
command: params.command,
|
|
448
|
+
args: params.args || [],
|
|
449
|
+
cwd: params.cwd,
|
|
450
|
+
readyPort: params.readyPort,
|
|
451
|
+
readyHost: params.readyHost,
|
|
452
|
+
readyLog: params.readyLog,
|
|
453
|
+
timeoutMs: params.timeoutMs,
|
|
454
|
+
lifetimeMs: params.lifetimeMs,
|
|
455
|
+
idleTimeoutMs: params.idleTimeoutMs,
|
|
456
|
+
});
|
|
457
|
+
} else if (params.action === "wait-ready") {
|
|
458
|
+
result = await waitForService(ctx.cwd, params.name, { timeoutMs: params.timeoutMs });
|
|
459
|
+
} else if (params.action === "status") {
|
|
460
|
+
result = await serviceStatus(ctx.cwd, params.name);
|
|
461
|
+
} else if (params.action === "logs") {
|
|
462
|
+
result = await serviceLogs(ctx.cwd, params.name, { maxChars: params.maxChars, evidence: true });
|
|
463
|
+
} else if (params.action === "stop") {
|
|
464
|
+
result = await stopService(ctx.cwd, params.name, { timeoutMs: params.timeoutMs });
|
|
465
|
+
} else if (params.action === "restart") {
|
|
466
|
+
result = await restartService(ctx.cwd, params.name, {
|
|
467
|
+
timeoutMs: params.timeoutMs,
|
|
468
|
+
lifetimeMs: params.lifetimeMs,
|
|
469
|
+
idleTimeoutMs: params.idleTimeoutMs,
|
|
470
|
+
});
|
|
471
|
+
} else {
|
|
472
|
+
throw new Error("Unknown ues_service action");
|
|
473
|
+
}
|
|
474
|
+
const failed =
|
|
475
|
+
(params.action === "start" || params.action === "wait-ready") && result?.ready !== true;
|
|
476
|
+
return {
|
|
477
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
478
|
+
details: result,
|
|
479
|
+
isError: failed,
|
|
480
|
+
};
|
|
481
|
+
} catch (error) {
|
|
482
|
+
return {
|
|
483
|
+
content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
|
|
484
|
+
details: { action: params.action, name: params.name },
|
|
485
|
+
isError: true,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
|
|
193
491
|
pi.registerTool({
|
|
194
492
|
name: "ues_evidence_get",
|
|
195
493
|
label: "UES Evidence Get",
|