@asterxsk/kiln 0.1.0 → 0.2.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/LICENSE +21 -21
- package/README.md +170 -170
- package/agent/AGENTS.md +67 -67
- package/agent/README.md +5 -5
- package/agent/extensions/AGENTS.md +68 -68
- package/agent/extensions/ask-user/index.ts +418 -418
- package/agent/extensions/ask-user/package-lock.json +769 -769
- package/agent/extensions/ask-user/package.json +19 -19
- package/agent/extensions/ask-user/prompt.ts +45 -45
- package/agent/extensions/ask-user/tsconfig.json +7 -7
- package/agent/extensions/background-terminals/docs/implementation-guide.md +942 -942
- package/agent/extensions/background-terminals/index.ts +627 -627
- package/agent/extensions/background-terminals/manager.test.ts +735 -735
- package/agent/extensions/background-terminals/output.test.ts +109 -109
- package/agent/extensions/background-terminals/package-lock.json +769 -769
- package/agent/extensions/background-terminals/package.json +17 -17
- package/agent/extensions/background-terminals/prompt.test.ts +125 -125
- package/agent/extensions/background-terminals/ps.test.ts +82 -82
- package/agent/extensions/background-terminals/result-delivery.test.ts +44 -44
- package/agent/extensions/background-terminals/src/domain.ts +87 -87
- package/agent/extensions/background-terminals/src/manager.ts +907 -907
- package/agent/extensions/background-terminals/src/output.ts +84 -84
- package/agent/extensions/background-terminals/src/prompt.ts +142 -142
- package/agent/extensions/background-terminals/src/result-delivery.ts +27 -27
- package/agent/extensions/background-terminals/src/runtime.ts +36 -36
- package/agent/extensions/background-terminals/src/ui/output-view.ts +79 -79
- package/agent/extensions/background-terminals/src/ui/ps.ts +621 -621
- package/agent/extensions/background-terminals/tsconfig.json +7 -7
- package/agent/extensions/file-search/index.spec.ts +443 -443
- package/agent/extensions/file-search/index.ts +459 -459
- package/agent/extensions/file-search/package-lock.json +2253 -2253
- package/agent/extensions/file-search/package.json +23 -23
- package/agent/extensions/file-search/src/args.ts +122 -122
- package/agent/extensions/file-search/src/binaries.ts +422 -422
- package/agent/extensions/file-search/src/output.ts +126 -126
- package/agent/extensions/file-search/src/process.ts +146 -146
- package/agent/extensions/file-search/src/prompt.ts +52 -52
- package/agent/extensions/file-search/tsconfig.json +7 -7
- package/agent/extensions/modelconf/PLAN.md +915 -915
- package/agent/extensions/modelconf/index.ts +296 -296
- package/agent/extensions/modelconf/src/ui/ModelConfView.ts +1101 -1101
- package/agent/extensions/pi-web-access/CHANGELOG.md +690 -690
- package/agent/extensions/pi-web-access/LICENSE +21 -21
- package/agent/extensions/pi-web-access/README.md +470 -470
- package/agent/extensions/pi-web-access/SECURITY.md +5 -5
- package/agent/extensions/pi-web-access/activity.ts +101 -101
- package/agent/extensions/pi-web-access/auth-fetch.ts +148 -148
- package/agent/extensions/pi-web-access/brightdata-unlocker.ts +272 -272
- package/agent/extensions/pi-web-access/chrome-cookies.ts +669 -669
- package/agent/extensions/pi-web-access/content-find.ts +139 -139
- package/agent/extensions/pi-web-access/credential-source.ts +191 -191
- package/agent/extensions/pi-web-access/data-uri-sanitize.ts +406 -406
- package/agent/extensions/pi-web-access/datalab-pdf-extract.ts +568 -568
- package/agent/extensions/pi-web-access/declared-web-links.ts +173 -173
- package/agent/extensions/pi-web-access/evidence/CONTRACT-EVIDENCE.md +496 -496
- package/agent/extensions/pi-web-access/evidence/contract-probe.mjs +140 -140
- package/agent/extensions/pi-web-access/exa.ts +526 -526
- package/agent/extensions/pi-web-access/extract.ts +1196 -1196
- package/agent/extensions/pi-web-access/feature-config.ts +29 -29
- package/agent/extensions/pi-web-access/fetch-params.ts +111 -111
- package/agent/extensions/pi-web-access/gemini-adc.ts +298 -298
- package/agent/extensions/pi-web-access/gemini-api.ts +353 -353
- package/agent/extensions/pi-web-access/gemini-pdf-extract.ts +108 -108
- package/agent/extensions/pi-web-access/gemini-url-context.ts +128 -128
- package/agent/extensions/pi-web-access/gemini-web-config.ts +101 -101
- package/agent/extensions/pi-web-access/gemini-web.ts +487 -487
- package/agent/extensions/pi-web-access/github-api.ts +197 -197
- package/agent/extensions/pi-web-access/github-extract.ts +746 -746
- package/agent/extensions/pi-web-access/github-issue-pr.ts +700 -700
- package/agent/extensions/pi-web-access/index.ts +1737 -1737
- package/agent/extensions/pi-web-access/package-lock.json +5808 -5808
- package/agent/extensions/pi-web-access/package.json +64 -64
- package/agent/extensions/pi-web-access/page-query.ts +96 -96
- package/agent/extensions/pi-web-access/pdf-extract.ts +409 -409
- package/agent/extensions/pi-web-access/promise-try.d.ts +7 -7
- package/agent/extensions/pi-web-access/query-rewrite.ts +51 -51
- package/agent/extensions/pi-web-access/render-search-error.ts +170 -170
- package/agent/extensions/pi-web-access/rsc-extract.ts +338 -338
- package/agent/extensions/pi-web-access/source-check.ts +282 -282
- package/agent/extensions/pi-web-access/ssrf-protection.ts +526 -526
- package/agent/extensions/pi-web-access/storage.ts +521 -521
- package/agent/extensions/pi-web-access/summary-model-scope.ts +125 -125
- package/agent/extensions/pi-web-access/test/auth-fetch.test.mjs +208 -208
- package/agent/extensions/pi-web-access/test/brightdata-unlocker.test.mjs +840 -840
- package/agent/extensions/pi-web-access/test/chrome-cookie-extraction.test.mjs +441 -441
- package/agent/extensions/pi-web-access/test/config-path.test.mjs +283 -283
- package/agent/extensions/pi-web-access/test/content-find.test.mjs +25 -25
- package/agent/extensions/pi-web-access/test/credential-source.test.mjs +118 -118
- package/agent/extensions/pi-web-access/test/data-uri-sanitize.test.mjs +210 -210
- package/agent/extensions/pi-web-access/test/datalab-pdf-extract.test.mjs +552 -552
- package/agent/extensions/pi-web-access/test/declared-web-links.test.mjs +212 -212
- package/agent/extensions/pi-web-access/test/fetch-answer-storage.test.mjs +40 -40
- package/agent/extensions/pi-web-access/test/fetch-cache-storage.test.mjs +334 -334
- package/agent/extensions/pi-web-access/test/fetch-content-domain-policy.test.mjs +95 -95
- package/agent/extensions/pi-web-access/test/fetch-modes.test.mjs +53 -53
- package/agent/extensions/pi-web-access/test/fetch-not-found-guidance.test.mjs +92 -92
- package/agent/extensions/pi-web-access/test/fetch-params.test.mjs +86 -86
- package/agent/extensions/pi-web-access/test/fetch-render-call.test.mjs +34 -34
- package/agent/extensions/pi-web-access/test/fetch-routing.test.mjs +173 -173
- package/agent/extensions/pi-web-access/test/gemini-adc-auth.test.mjs +257 -257
- package/agent/extensions/pi-web-access/test/gemini-api-transport.test.mjs +170 -170
- package/agent/extensions/pi-web-access/test/gemini-pdf-extract.test.mjs +133 -133
- package/agent/extensions/pi-web-access/test/gemini-web-cookie-opt-in.test.mjs +178 -178
- package/agent/extensions/pi-web-access/test/gemini-web-header-overflow.test.mjs +148 -148
- package/agent/extensions/pi-web-access/test/get-search-content.test.mjs +223 -223
- package/agent/extensions/pi-web-access/test/github-extract.test.mjs +378 -378
- package/agent/extensions/pi-web-access/test/github-issue-pr.test.mjs +565 -565
- package/agent/extensions/pi-web-access/test/inline-content-config.test.mjs +99 -99
- package/agent/extensions/pi-web-access/test/lazy-extract-load.test.mjs +118 -118
- package/agent/extensions/pi-web-access/test/local-video-oversize.test.mjs +52 -52
- package/agent/extensions/pi-web-access/test/package-typebox-dependency.test.mjs +50 -50
- package/agent/extensions/pi-web-access/test/page-query.test.mjs +51 -51
- package/agent/extensions/pi-web-access/test/pdf-config.test.mjs +140 -140
- package/agent/extensions/pi-web-access/test/pdf-extract.test.mjs +500 -500
- package/agent/extensions/pi-web-access/test/proxy-transport.test.mjs +286 -286
- package/agent/extensions/pi-web-access/test/query-rewrite.test.mjs +52 -52
- package/agent/extensions/pi-web-access/test/rsc-fallback.test.mjs +102 -102
- package/agent/extensions/pi-web-access/test/search-error-render.test.mjs +152 -152
- package/agent/extensions/pi-web-access/test/search-providers.test.mjs +274 -274
- package/agent/extensions/pi-web-access/test/source-check.test.mjs +179 -179
- package/agent/extensions/pi-web-access/test/ssrf-allow-ranges-config.test.mjs +205 -205
- package/agent/extensions/pi-web-access/test/ssrf-protection.test.mjs +456 -456
- package/agent/extensions/pi-web-access/test/tool-registration-config.test.mjs +182 -182
- package/agent/extensions/pi-web-access/test/youtube-extract-errors.test.mjs +64 -64
- package/agent/extensions/pi-web-access/tsconfig.json +11 -11
- package/agent/extensions/pi-web-access/utils.ts +451 -451
- package/agent/extensions/pi-web-access/video-extract.ts +392 -392
- package/agent/extensions/pi-web-access/youtube-extract.ts +328 -328
- package/agent/extensions/shared/activity-status.ts +31 -31
- package/agent/extensions/shared/child-session.test.ts +270 -270
- package/agent/extensions/shared/child-session.ts +148 -148
- package/agent/extensions/shared/context-utilization.test.ts +48 -48
- package/agent/extensions/shared/context-utilization.ts +47 -47
- package/agent/extensions/shared/dashboard-state.ts +99 -99
- package/agent/extensions/shared/tool-call-timeout.test.ts +117 -117
- package/agent/extensions/shared/tool-call-timeout.ts +104 -104
- package/agent/extensions/subagents/by-the-way.test.ts +29 -29
- package/agent/extensions/subagents/claude.test.ts +119 -119
- package/agent/extensions/subagents/codex.test.ts +102 -102
- package/agent/extensions/subagents/context-usage.test.ts +107 -107
- package/agent/extensions/subagents/docs/design-plan.md +568 -568
- package/agent/extensions/subagents/docs/effect-v4-extension-guide.md +354 -354
- package/agent/extensions/subagents/docs/effect-v4-notes.md +571 -571
- package/agent/extensions/subagents/index.ts +779 -779
- package/agent/extensions/subagents/manager.test.ts +276 -276
- package/agent/extensions/subagents/package-lock.json +2244 -2244
- package/agent/extensions/subagents/package.json +19 -19
- package/agent/extensions/subagents/result-delivery.test.ts +27 -27
- package/agent/extensions/subagents/src/backend.ts +73 -73
- package/agent/extensions/subagents/src/backends/claude.ts +701 -701
- package/agent/extensions/subagents/src/backends/codex.ts +1060 -1060
- package/agent/extensions/subagents/src/backends/pi.ts +575 -575
- package/agent/extensions/subagents/src/backends/stub.ts +300 -300
- package/agent/extensions/subagents/src/by-the-way.ts +21 -21
- package/agent/extensions/subagents/src/domain.ts +253 -253
- package/agent/extensions/subagents/src/format.ts +74 -74
- package/agent/extensions/subagents/src/manager.ts +736 -736
- package/agent/extensions/subagents/src/prompt.ts +92 -92
- package/agent/extensions/subagents/src/result-delivery.ts +20 -20
- package/agent/extensions/subagents/src/runtime.ts +53 -53
- package/agent/extensions/subagents/src/ui/takeover.ts +583 -583
- package/agent/extensions/subagents/src/ui/transcript.ts +201 -201
- package/agent/extensions/subagents/takeover.test.ts +29 -29
- package/agent/extensions/subagents/tsconfig.json +7 -7
- package/agent/extensions/todo/AGENTS.md +38 -38
- package/agent/extensions/todo/LICENSE +21 -21
- package/agent/extensions/todo/config.ts +55 -55
- package/agent/extensions/todo/index.ts +151 -151
- package/agent/extensions/todo/locales/de.json +17 -17
- package/agent/extensions/todo/locales/en.json +15 -15
- package/agent/extensions/todo/locales/es.json +17 -17
- package/agent/extensions/todo/locales/fr.json +17 -17
- package/agent/extensions/todo/locales/pt-BR.json +17 -17
- package/agent/extensions/todo/locales/pt.json +17 -17
- package/agent/extensions/todo/locales/ru.json +17 -17
- package/agent/extensions/todo/locales/uk.json +17 -17
- package/agent/extensions/todo/locales/zh.json +17 -17
- package/agent/extensions/todo/package-lock.json +3358 -3358
- package/agent/extensions/todo/package.json +67 -67
- package/agent/extensions/todo/state/i18n-bridge.ts +64 -64
- package/agent/extensions/todo/state/invariants.ts +20 -20
- package/agent/extensions/todo/state/replay.ts +38 -38
- package/agent/extensions/todo/state/selectors.ts +107 -107
- package/agent/extensions/todo/state/state-reducer.ts +326 -326
- package/agent/extensions/todo/state/state.ts +18 -18
- package/agent/extensions/todo/state/store.ts +82 -82
- package/agent/extensions/todo/state/task-graph.ts +57 -57
- package/agent/extensions/todo/todo-overlay.ts +200 -200
- package/agent/extensions/todo/todo.ts +155 -155
- package/agent/extensions/todo/tool/response-envelope.ts +109 -109
- package/agent/extensions/todo/tool/types.ts +206 -206
- package/agent/extensions/todo/view/format.ts +177 -177
- package/agent/install.ps1 +637 -527
- package/agent/install.sh +620 -511
- package/agent/keybindings.json +7 -7
- package/bin/kiln.js +124 -11
- package/package.json +8 -2
- package/agent/extensions/taste/index.ts +0 -443
- package/agent/extensions/taste/install.ps1 +0 -23
- package/agent/extensions/taste/install.sh +0 -21
- /package/agent/extensions/{status line → statusline}/index.ts +0 -0
- /package/agent/extensions/{status line → statusline}/install.ps1 +0 -0
- /package/agent/extensions/{status line → statusline}/install.sh +0 -0
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
|
|
3
|
-
type Theme = ExtensionContext["ui"]["theme"];
|
|
4
|
-
|
|
5
|
-
interface ActivityCounts {
|
|
6
|
-
running: number;
|
|
7
|
-
done: number;
|
|
8
|
-
failed: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const SQUARE = "■";
|
|
12
|
-
|
|
13
|
-
export function formatActivityStatus(
|
|
14
|
-
theme: Theme,
|
|
15
|
-
label: "subagents" | "workflows",
|
|
16
|
-
counts: ActivityCounts,
|
|
17
|
-
) {
|
|
18
|
-
const parts: string[] = [];
|
|
19
|
-
if (counts.running > 0) {
|
|
20
|
-
parts.push(theme.fg("warning", `${SQUARE} ${counts.running} running`));
|
|
21
|
-
}
|
|
22
|
-
if (counts.done > 0) {
|
|
23
|
-
parts.push(theme.fg("success", `${SQUARE} ${counts.done} done`));
|
|
24
|
-
}
|
|
25
|
-
if (counts.failed > 0) {
|
|
26
|
-
parts.push(theme.fg("error", `${SQUARE} ${counts.failed} failed`));
|
|
27
|
-
}
|
|
28
|
-
parts.push(theme.fg("accent", `/${label}`) + theme.fg("dim", " to view"));
|
|
29
|
-
|
|
30
|
-
return `${theme.fg("muted", `${label}:`)} ${parts.join(theme.fg("dim", " · "))}`;
|
|
31
|
-
}
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
type Theme = ExtensionContext["ui"]["theme"];
|
|
4
|
+
|
|
5
|
+
interface ActivityCounts {
|
|
6
|
+
running: number;
|
|
7
|
+
done: number;
|
|
8
|
+
failed: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const SQUARE = "■";
|
|
12
|
+
|
|
13
|
+
export function formatActivityStatus(
|
|
14
|
+
theme: Theme,
|
|
15
|
+
label: "subagents" | "workflows",
|
|
16
|
+
counts: ActivityCounts,
|
|
17
|
+
) {
|
|
18
|
+
const parts: string[] = [];
|
|
19
|
+
if (counts.running > 0) {
|
|
20
|
+
parts.push(theme.fg("warning", `${SQUARE} ${counts.running} running`));
|
|
21
|
+
}
|
|
22
|
+
if (counts.done > 0) {
|
|
23
|
+
parts.push(theme.fg("success", `${SQUARE} ${counts.done} done`));
|
|
24
|
+
}
|
|
25
|
+
if (counts.failed > 0) {
|
|
26
|
+
parts.push(theme.fg("error", `${SQUARE} ${counts.failed} failed`));
|
|
27
|
+
}
|
|
28
|
+
parts.push(theme.fg("accent", `/${label}`) + theme.fg("dim", " to view"));
|
|
29
|
+
|
|
30
|
+
return `${theme.fg("muted", `${label}:`)} ${parts.join(theme.fg("dim", " · "))}`;
|
|
31
|
+
}
|
|
@@ -1,270 +1,270 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import * as path from "node:path";
|
|
5
|
-
import test from "node:test";
|
|
6
|
-
import {
|
|
7
|
-
createAgentSession,
|
|
8
|
-
DefaultResourceLoader,
|
|
9
|
-
defineTool,
|
|
10
|
-
ProjectTrustStore,
|
|
11
|
-
SessionManager,
|
|
12
|
-
SettingsManager,
|
|
13
|
-
type SessionShutdownEvent,
|
|
14
|
-
} from "@earendil-works/pi-coding-agent";
|
|
15
|
-
import { Type } from "typebox";
|
|
16
|
-
import {
|
|
17
|
-
bindChildSessionExtensions,
|
|
18
|
-
CHILD_EXCLUDED_TOOL_NAMES,
|
|
19
|
-
childToolPolicy,
|
|
20
|
-
createChildResources,
|
|
21
|
-
resolveStandaloneChildProjectTrust,
|
|
22
|
-
shutdownAndDisposeChildSession,
|
|
23
|
-
type DisposableChildSession,
|
|
24
|
-
} from "./child-session.ts";
|
|
25
|
-
|
|
26
|
-
async function withTempDir(run: (directory: string) => Promise<void>) {
|
|
27
|
-
const directory = await mkdtemp(path.join(tmpdir(), "pi-child-policy-"));
|
|
28
|
-
try {
|
|
29
|
-
await run(directory);
|
|
30
|
-
} finally {
|
|
31
|
-
await rm(directory, { recursive: true, force: true });
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
test("child denylist keeps extension and workflow structured tools available", async () => {
|
|
36
|
-
await withTempDir(async (directory) => {
|
|
37
|
-
let starts = 0;
|
|
38
|
-
let shutdowns = 0;
|
|
39
|
-
const settingsManager = SettingsManager.inMemory(undefined, {
|
|
40
|
-
projectTrusted: false,
|
|
41
|
-
});
|
|
42
|
-
const inlineLoader = new DefaultResourceLoader({
|
|
43
|
-
cwd: directory,
|
|
44
|
-
agentDir: path.join(directory, "inline-agent"),
|
|
45
|
-
settingsManager,
|
|
46
|
-
extensionFactories: [
|
|
47
|
-
(pi) => {
|
|
48
|
-
pi.on("session_start", () => {
|
|
49
|
-
starts++;
|
|
50
|
-
});
|
|
51
|
-
pi.on("session_shutdown", () => {
|
|
52
|
-
shutdowns++;
|
|
53
|
-
});
|
|
54
|
-
for (const name of [
|
|
55
|
-
"fixture_extension_tool",
|
|
56
|
-
...CHILD_EXCLUDED_TOOL_NAMES,
|
|
57
|
-
]) {
|
|
58
|
-
pi.registerTool({
|
|
59
|
-
name,
|
|
60
|
-
label: name,
|
|
61
|
-
description: name,
|
|
62
|
-
parameters: Type.Object({}),
|
|
63
|
-
async execute() {
|
|
64
|
-
return {
|
|
65
|
-
content: [{ type: "text", text: "ok" }],
|
|
66
|
-
details: {},
|
|
67
|
-
};
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
});
|
|
74
|
-
await inlineLoader.reload();
|
|
75
|
-
|
|
76
|
-
const structuredOutput = defineTool({
|
|
77
|
-
name: "structured_output",
|
|
78
|
-
label: "Structured Output",
|
|
79
|
-
description: "fixture structured result",
|
|
80
|
-
parameters: Type.Object({ value: Type.String() }),
|
|
81
|
-
async execute(_id, params) {
|
|
82
|
-
return {
|
|
83
|
-
content: [{ type: "text", text: params.value }],
|
|
84
|
-
details: {},
|
|
85
|
-
};
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
const { session } = await createAgentSession({
|
|
89
|
-
cwd: directory,
|
|
90
|
-
agentDir: path.join(directory, "inline-agent"),
|
|
91
|
-
resourceLoader: inlineLoader,
|
|
92
|
-
settingsManager,
|
|
93
|
-
sessionManager: SessionManager.inMemory(directory),
|
|
94
|
-
customTools: [structuredOutput],
|
|
95
|
-
...childToolPolicy(),
|
|
96
|
-
});
|
|
97
|
-
await bindChildSessionExtensions(session);
|
|
98
|
-
|
|
99
|
-
assert.deepEqual(
|
|
100
|
-
[...CHILD_EXCLUDED_TOOL_NAMES],
|
|
101
|
-
[
|
|
102
|
-
"subagent_spawn",
|
|
103
|
-
"subagent_wait",
|
|
104
|
-
"subagent_cancel",
|
|
105
|
-
"subagent_check",
|
|
106
|
-
"subagent_list",
|
|
107
|
-
"workflow",
|
|
108
|
-
"ask_user",
|
|
109
|
-
],
|
|
110
|
-
);
|
|
111
|
-
const allTools = new Set(session.getAllTools().map((tool) => tool.name));
|
|
112
|
-
const activeTools = new Set(session.getActiveToolNames());
|
|
113
|
-
assert.equal(starts, 1);
|
|
114
|
-
assert.equal(allTools.has("fixture_extension_tool"), true);
|
|
115
|
-
assert.equal(activeTools.has("fixture_extension_tool"), true);
|
|
116
|
-
assert.equal(allTools.has("structured_output"), true);
|
|
117
|
-
assert.equal(activeTools.has("structured_output"), true);
|
|
118
|
-
for (const denied of CHILD_EXCLUDED_TOOL_NAMES) {
|
|
119
|
-
assert.equal(allTools.has(denied), false, `${denied} should be denied`);
|
|
120
|
-
assert.equal(
|
|
121
|
-
activeTools.has(denied),
|
|
122
|
-
false,
|
|
123
|
-
`${denied} should be inactive`,
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
for (const builtin of ["read", "bash", "edit", "write"]) {
|
|
127
|
-
assert.equal(
|
|
128
|
-
activeTools.has(builtin),
|
|
129
|
-
true,
|
|
130
|
-
`${builtin} should stay active`,
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
await Promise.all([
|
|
135
|
-
shutdownAndDisposeChildSession(session),
|
|
136
|
-
shutdownAndDisposeChildSession(session),
|
|
137
|
-
]);
|
|
138
|
-
assert.equal(shutdowns, 1);
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test("resource loading gates project extensions but retains global extensions", async () => {
|
|
143
|
-
await withTempDir(async (directory) => {
|
|
144
|
-
const cwd = path.join(directory, "project");
|
|
145
|
-
const agentDir = path.join(directory, "agent");
|
|
146
|
-
await mkdir(path.join(cwd, ".pi", "extensions"), { recursive: true });
|
|
147
|
-
await mkdir(path.join(agentDir, "extensions"), { recursive: true });
|
|
148
|
-
const extensionSource = (name: string) => `
|
|
149
|
-
export default function (pi) {
|
|
150
|
-
pi.registerTool({
|
|
151
|
-
name: ${JSON.stringify(name)}, label: ${JSON.stringify(name)},
|
|
152
|
-
description: "fixture", parameters: { type: "object", properties: {} },
|
|
153
|
-
async execute() { return { content: [{ type: "text", text: "ok" }] }; }
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
`;
|
|
157
|
-
await writeFile(
|
|
158
|
-
path.join(agentDir, "extensions", "global.ts"),
|
|
159
|
-
extensionSource("global_fixture"),
|
|
160
|
-
);
|
|
161
|
-
await writeFile(
|
|
162
|
-
path.join(cwd, ".pi", "extensions", "project.ts"),
|
|
163
|
-
extensionSource("project_fixture"),
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
const untrusted = await createChildResources({
|
|
167
|
-
cwd,
|
|
168
|
-
agentDir,
|
|
169
|
-
projectTrusted: false,
|
|
170
|
-
});
|
|
171
|
-
const untrustedTools = untrusted.loader
|
|
172
|
-
.getExtensions()
|
|
173
|
-
.extensions.flatMap((extension) => [...extension.tools.keys()]);
|
|
174
|
-
assert.equal(untrustedTools.includes("global_fixture"), true);
|
|
175
|
-
assert.equal(untrustedTools.includes("project_fixture"), false);
|
|
176
|
-
|
|
177
|
-
const trusted = await createChildResources({
|
|
178
|
-
cwd,
|
|
179
|
-
agentDir,
|
|
180
|
-
projectTrusted: true,
|
|
181
|
-
});
|
|
182
|
-
const trustedTools = trusted.loader
|
|
183
|
-
.getExtensions()
|
|
184
|
-
.extensions.flatMap((extension) => [...extension.tools.keys()]);
|
|
185
|
-
assert.equal(trustedTools.includes("global_fixture"), true);
|
|
186
|
-
assert.equal(trustedTools.includes("project_fixture"), true);
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test("alternate standalone cwd only uses explicit saved trust", async () => {
|
|
191
|
-
await withTempDir(async (directory) => {
|
|
192
|
-
const parentCwd = path.join(directory, "parent");
|
|
193
|
-
const childCwd = path.join(directory, "alternate");
|
|
194
|
-
const agentDir = path.join(directory, "agent");
|
|
195
|
-
await mkdir(parentCwd, { recursive: true });
|
|
196
|
-
await mkdir(childCwd, { recursive: true });
|
|
197
|
-
|
|
198
|
-
assert.equal(
|
|
199
|
-
resolveStandaloneChildProjectTrust({
|
|
200
|
-
parentCwd,
|
|
201
|
-
childCwd: parentCwd,
|
|
202
|
-
parentTrusted: true,
|
|
203
|
-
agentDir,
|
|
204
|
-
}),
|
|
205
|
-
true,
|
|
206
|
-
);
|
|
207
|
-
assert.equal(
|
|
208
|
-
resolveStandaloneChildProjectTrust({
|
|
209
|
-
parentCwd,
|
|
210
|
-
childCwd,
|
|
211
|
-
parentTrusted: true,
|
|
212
|
-
agentDir,
|
|
213
|
-
}),
|
|
214
|
-
false,
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
new ProjectTrustStore(agentDir).set(childCwd, true);
|
|
218
|
-
assert.equal(
|
|
219
|
-
resolveStandaloneChildProjectTrust({
|
|
220
|
-
parentCwd,
|
|
221
|
-
childCwd,
|
|
222
|
-
parentTrusted: false,
|
|
223
|
-
agentDir,
|
|
224
|
-
}),
|
|
225
|
-
true,
|
|
226
|
-
);
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
test("shutdown helper balances hooks and disposal despite errors", async () => {
|
|
231
|
-
let emits = 0;
|
|
232
|
-
let disposals = 0;
|
|
233
|
-
const session: DisposableChildSession = {
|
|
234
|
-
extensionRunner: {
|
|
235
|
-
hasHandlers: () => true,
|
|
236
|
-
async emit(event: SessionShutdownEvent) {
|
|
237
|
-
emits++;
|
|
238
|
-
assert.deepEqual(event, { type: "session_shutdown", reason: "quit" });
|
|
239
|
-
throw new Error("fixture shutdown failure");
|
|
240
|
-
},
|
|
241
|
-
},
|
|
242
|
-
dispose() {
|
|
243
|
-
disposals++;
|
|
244
|
-
},
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
await Promise.all([
|
|
248
|
-
shutdownAndDisposeChildSession(session),
|
|
249
|
-
shutdownAndDisposeChildSession(session),
|
|
250
|
-
shutdownAndDisposeChildSession(session),
|
|
251
|
-
]);
|
|
252
|
-
assert.equal(emits, 1);
|
|
253
|
-
assert.equal(disposals, 1);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
test("shutdown helper bounds a stuck hook before disposal", async () => {
|
|
257
|
-
let disposals = 0;
|
|
258
|
-
const session: DisposableChildSession = {
|
|
259
|
-
extensionRunner: {
|
|
260
|
-
hasHandlers: () => true,
|
|
261
|
-
emit: () => new Promise(() => {}),
|
|
262
|
-
},
|
|
263
|
-
dispose() {
|
|
264
|
-
disposals++;
|
|
265
|
-
},
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
await shutdownAndDisposeChildSession(session, { timeoutMs: 10 });
|
|
269
|
-
assert.equal(disposals, 1);
|
|
270
|
-
});
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import {
|
|
7
|
+
createAgentSession,
|
|
8
|
+
DefaultResourceLoader,
|
|
9
|
+
defineTool,
|
|
10
|
+
ProjectTrustStore,
|
|
11
|
+
SessionManager,
|
|
12
|
+
SettingsManager,
|
|
13
|
+
type SessionShutdownEvent,
|
|
14
|
+
} from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import { Type } from "typebox";
|
|
16
|
+
import {
|
|
17
|
+
bindChildSessionExtensions,
|
|
18
|
+
CHILD_EXCLUDED_TOOL_NAMES,
|
|
19
|
+
childToolPolicy,
|
|
20
|
+
createChildResources,
|
|
21
|
+
resolveStandaloneChildProjectTrust,
|
|
22
|
+
shutdownAndDisposeChildSession,
|
|
23
|
+
type DisposableChildSession,
|
|
24
|
+
} from "./child-session.ts";
|
|
25
|
+
|
|
26
|
+
async function withTempDir(run: (directory: string) => Promise<void>) {
|
|
27
|
+
const directory = await mkdtemp(path.join(tmpdir(), "pi-child-policy-"));
|
|
28
|
+
try {
|
|
29
|
+
await run(directory);
|
|
30
|
+
} finally {
|
|
31
|
+
await rm(directory, { recursive: true, force: true });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test("child denylist keeps extension and workflow structured tools available", async () => {
|
|
36
|
+
await withTempDir(async (directory) => {
|
|
37
|
+
let starts = 0;
|
|
38
|
+
let shutdowns = 0;
|
|
39
|
+
const settingsManager = SettingsManager.inMemory(undefined, {
|
|
40
|
+
projectTrusted: false,
|
|
41
|
+
});
|
|
42
|
+
const inlineLoader = new DefaultResourceLoader({
|
|
43
|
+
cwd: directory,
|
|
44
|
+
agentDir: path.join(directory, "inline-agent"),
|
|
45
|
+
settingsManager,
|
|
46
|
+
extensionFactories: [
|
|
47
|
+
(pi) => {
|
|
48
|
+
pi.on("session_start", () => {
|
|
49
|
+
starts++;
|
|
50
|
+
});
|
|
51
|
+
pi.on("session_shutdown", () => {
|
|
52
|
+
shutdowns++;
|
|
53
|
+
});
|
|
54
|
+
for (const name of [
|
|
55
|
+
"fixture_extension_tool",
|
|
56
|
+
...CHILD_EXCLUDED_TOOL_NAMES,
|
|
57
|
+
]) {
|
|
58
|
+
pi.registerTool({
|
|
59
|
+
name,
|
|
60
|
+
label: name,
|
|
61
|
+
description: name,
|
|
62
|
+
parameters: Type.Object({}),
|
|
63
|
+
async execute() {
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: "text", text: "ok" }],
|
|
66
|
+
details: {},
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
});
|
|
74
|
+
await inlineLoader.reload();
|
|
75
|
+
|
|
76
|
+
const structuredOutput = defineTool({
|
|
77
|
+
name: "structured_output",
|
|
78
|
+
label: "Structured Output",
|
|
79
|
+
description: "fixture structured result",
|
|
80
|
+
parameters: Type.Object({ value: Type.String() }),
|
|
81
|
+
async execute(_id, params) {
|
|
82
|
+
return {
|
|
83
|
+
content: [{ type: "text", text: params.value }],
|
|
84
|
+
details: {},
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
const { session } = await createAgentSession({
|
|
89
|
+
cwd: directory,
|
|
90
|
+
agentDir: path.join(directory, "inline-agent"),
|
|
91
|
+
resourceLoader: inlineLoader,
|
|
92
|
+
settingsManager,
|
|
93
|
+
sessionManager: SessionManager.inMemory(directory),
|
|
94
|
+
customTools: [structuredOutput],
|
|
95
|
+
...childToolPolicy(),
|
|
96
|
+
});
|
|
97
|
+
await bindChildSessionExtensions(session);
|
|
98
|
+
|
|
99
|
+
assert.deepEqual(
|
|
100
|
+
[...CHILD_EXCLUDED_TOOL_NAMES],
|
|
101
|
+
[
|
|
102
|
+
"subagent_spawn",
|
|
103
|
+
"subagent_wait",
|
|
104
|
+
"subagent_cancel",
|
|
105
|
+
"subagent_check",
|
|
106
|
+
"subagent_list",
|
|
107
|
+
"workflow",
|
|
108
|
+
"ask_user",
|
|
109
|
+
],
|
|
110
|
+
);
|
|
111
|
+
const allTools = new Set(session.getAllTools().map((tool) => tool.name));
|
|
112
|
+
const activeTools = new Set(session.getActiveToolNames());
|
|
113
|
+
assert.equal(starts, 1);
|
|
114
|
+
assert.equal(allTools.has("fixture_extension_tool"), true);
|
|
115
|
+
assert.equal(activeTools.has("fixture_extension_tool"), true);
|
|
116
|
+
assert.equal(allTools.has("structured_output"), true);
|
|
117
|
+
assert.equal(activeTools.has("structured_output"), true);
|
|
118
|
+
for (const denied of CHILD_EXCLUDED_TOOL_NAMES) {
|
|
119
|
+
assert.equal(allTools.has(denied), false, `${denied} should be denied`);
|
|
120
|
+
assert.equal(
|
|
121
|
+
activeTools.has(denied),
|
|
122
|
+
false,
|
|
123
|
+
`${denied} should be inactive`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
for (const builtin of ["read", "bash", "edit", "write"]) {
|
|
127
|
+
assert.equal(
|
|
128
|
+
activeTools.has(builtin),
|
|
129
|
+
true,
|
|
130
|
+
`${builtin} should stay active`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await Promise.all([
|
|
135
|
+
shutdownAndDisposeChildSession(session),
|
|
136
|
+
shutdownAndDisposeChildSession(session),
|
|
137
|
+
]);
|
|
138
|
+
assert.equal(shutdowns, 1);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("resource loading gates project extensions but retains global extensions", async () => {
|
|
143
|
+
await withTempDir(async (directory) => {
|
|
144
|
+
const cwd = path.join(directory, "project");
|
|
145
|
+
const agentDir = path.join(directory, "agent");
|
|
146
|
+
await mkdir(path.join(cwd, ".pi", "extensions"), { recursive: true });
|
|
147
|
+
await mkdir(path.join(agentDir, "extensions"), { recursive: true });
|
|
148
|
+
const extensionSource = (name: string) => `
|
|
149
|
+
export default function (pi) {
|
|
150
|
+
pi.registerTool({
|
|
151
|
+
name: ${JSON.stringify(name)}, label: ${JSON.stringify(name)},
|
|
152
|
+
description: "fixture", parameters: { type: "object", properties: {} },
|
|
153
|
+
async execute() { return { content: [{ type: "text", text: "ok" }] }; }
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
`;
|
|
157
|
+
await writeFile(
|
|
158
|
+
path.join(agentDir, "extensions", "global.ts"),
|
|
159
|
+
extensionSource("global_fixture"),
|
|
160
|
+
);
|
|
161
|
+
await writeFile(
|
|
162
|
+
path.join(cwd, ".pi", "extensions", "project.ts"),
|
|
163
|
+
extensionSource("project_fixture"),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const untrusted = await createChildResources({
|
|
167
|
+
cwd,
|
|
168
|
+
agentDir,
|
|
169
|
+
projectTrusted: false,
|
|
170
|
+
});
|
|
171
|
+
const untrustedTools = untrusted.loader
|
|
172
|
+
.getExtensions()
|
|
173
|
+
.extensions.flatMap((extension) => [...extension.tools.keys()]);
|
|
174
|
+
assert.equal(untrustedTools.includes("global_fixture"), true);
|
|
175
|
+
assert.equal(untrustedTools.includes("project_fixture"), false);
|
|
176
|
+
|
|
177
|
+
const trusted = await createChildResources({
|
|
178
|
+
cwd,
|
|
179
|
+
agentDir,
|
|
180
|
+
projectTrusted: true,
|
|
181
|
+
});
|
|
182
|
+
const trustedTools = trusted.loader
|
|
183
|
+
.getExtensions()
|
|
184
|
+
.extensions.flatMap((extension) => [...extension.tools.keys()]);
|
|
185
|
+
assert.equal(trustedTools.includes("global_fixture"), true);
|
|
186
|
+
assert.equal(trustedTools.includes("project_fixture"), true);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("alternate standalone cwd only uses explicit saved trust", async () => {
|
|
191
|
+
await withTempDir(async (directory) => {
|
|
192
|
+
const parentCwd = path.join(directory, "parent");
|
|
193
|
+
const childCwd = path.join(directory, "alternate");
|
|
194
|
+
const agentDir = path.join(directory, "agent");
|
|
195
|
+
await mkdir(parentCwd, { recursive: true });
|
|
196
|
+
await mkdir(childCwd, { recursive: true });
|
|
197
|
+
|
|
198
|
+
assert.equal(
|
|
199
|
+
resolveStandaloneChildProjectTrust({
|
|
200
|
+
parentCwd,
|
|
201
|
+
childCwd: parentCwd,
|
|
202
|
+
parentTrusted: true,
|
|
203
|
+
agentDir,
|
|
204
|
+
}),
|
|
205
|
+
true,
|
|
206
|
+
);
|
|
207
|
+
assert.equal(
|
|
208
|
+
resolveStandaloneChildProjectTrust({
|
|
209
|
+
parentCwd,
|
|
210
|
+
childCwd,
|
|
211
|
+
parentTrusted: true,
|
|
212
|
+
agentDir,
|
|
213
|
+
}),
|
|
214
|
+
false,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
new ProjectTrustStore(agentDir).set(childCwd, true);
|
|
218
|
+
assert.equal(
|
|
219
|
+
resolveStandaloneChildProjectTrust({
|
|
220
|
+
parentCwd,
|
|
221
|
+
childCwd,
|
|
222
|
+
parentTrusted: false,
|
|
223
|
+
agentDir,
|
|
224
|
+
}),
|
|
225
|
+
true,
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("shutdown helper balances hooks and disposal despite errors", async () => {
|
|
231
|
+
let emits = 0;
|
|
232
|
+
let disposals = 0;
|
|
233
|
+
const session: DisposableChildSession = {
|
|
234
|
+
extensionRunner: {
|
|
235
|
+
hasHandlers: () => true,
|
|
236
|
+
async emit(event: SessionShutdownEvent) {
|
|
237
|
+
emits++;
|
|
238
|
+
assert.deepEqual(event, { type: "session_shutdown", reason: "quit" });
|
|
239
|
+
throw new Error("fixture shutdown failure");
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
dispose() {
|
|
243
|
+
disposals++;
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
await Promise.all([
|
|
248
|
+
shutdownAndDisposeChildSession(session),
|
|
249
|
+
shutdownAndDisposeChildSession(session),
|
|
250
|
+
shutdownAndDisposeChildSession(session),
|
|
251
|
+
]);
|
|
252
|
+
assert.equal(emits, 1);
|
|
253
|
+
assert.equal(disposals, 1);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("shutdown helper bounds a stuck hook before disposal", async () => {
|
|
257
|
+
let disposals = 0;
|
|
258
|
+
const session: DisposableChildSession = {
|
|
259
|
+
extensionRunner: {
|
|
260
|
+
hasHandlers: () => true,
|
|
261
|
+
emit: () => new Promise(() => {}),
|
|
262
|
+
},
|
|
263
|
+
dispose() {
|
|
264
|
+
disposals++;
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
await shutdownAndDisposeChildSession(session, { timeoutMs: 10 });
|
|
269
|
+
assert.equal(disposals, 1);
|
|
270
|
+
});
|