pi-webdesk 0.1.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 +111 -0
- package/dist/apps/daemon/src/appearance-preferences.js +218 -0
- package/dist/apps/daemon/src/auth.js +88 -0
- package/dist/apps/daemon/src/bin.js +123 -0
- package/dist/apps/daemon/src/cli.js +48 -0
- package/dist/apps/daemon/src/event-hub.js +155 -0
- package/dist/apps/daemon/src/index.js +102 -0
- package/dist/apps/daemon/src/launcher-control.js +114 -0
- package/dist/apps/daemon/src/launcher.js +73 -0
- package/dist/apps/daemon/src/pi-auth.js +290 -0
- package/dist/apps/daemon/src/pi-resources.js +182 -0
- package/dist/apps/daemon/src/pi-runtime-factory.js +19 -0
- package/dist/apps/daemon/src/pi-sessions.js +265 -0
- package/dist/apps/daemon/src/runtime-process.js +241 -0
- package/dist/apps/daemon/src/secret.js +71 -0
- package/dist/apps/daemon/src/server.js +1662 -0
- package/dist/apps/daemon/src/session-projection.js +117 -0
- package/dist/apps/daemon/src/state-lock.js +31 -0
- package/dist/apps/daemon/src/static-web.js +53 -0
- package/dist/apps/daemon/src/task-archive.js +152 -0
- package/dist/apps/daemon/src/task-commit.js +503 -0
- package/dist/apps/daemon/src/task-merge.js +912 -0
- package/dist/apps/daemon/src/task-review.js +204 -0
- package/dist/apps/daemon/src/task-runtime.js +1124 -0
- package/dist/apps/daemon/src/task-validation.js +352 -0
- package/dist/apps/daemon/src/workspace-store.js +140 -0
- package/dist/apps/daemon/src/workspace.js +795 -0
- package/dist/extensions/webdesk.js +34 -0
- package/dist/packages/git/src/commit.js +675 -0
- package/dist/packages/git/src/errors.js +55 -0
- package/dist/packages/git/src/fingerprint.js +286 -0
- package/dist/packages/git/src/index.js +123 -0
- package/dist/packages/git/src/merge.js +1008 -0
- package/dist/packages/git/src/paths.js +58 -0
- package/dist/packages/git/src/repository.js +77 -0
- package/dist/packages/git/src/review.js +396 -0
- package/dist/packages/git/src/runner.js +110 -0
- package/dist/packages/git/src/validation.js +263 -0
- package/dist/packages/git/src/worktree.js +233 -0
- package/dist/packages/pi-bridge/extensions/pita-policy.js +117 -0
- package/dist/packages/pi-bridge/src/auth.js +80 -0
- package/dist/packages/pi-bridge/src/errors.js +19 -0
- package/dist/packages/pi-bridge/src/handshake.js +43 -0
- package/dist/packages/pi-bridge/src/index.js +76 -0
- package/dist/packages/pi-bridge/src/jsonl.js +105 -0
- package/dist/packages/pi-bridge/src/policy-approval.js +62 -0
- package/dist/packages/pi-bridge/src/resolve.js +59 -0
- package/dist/packages/pi-bridge/src/resources-child.mjs +23 -0
- package/dist/packages/pi-bridge/src/resources.js +481 -0
- package/dist/packages/pi-bridge/src/rpc/client.js +480 -0
- package/dist/packages/pi-bridge/src/rpc/runtime.js +496 -0
- package/dist/packages/pi-bridge/src/rpc/supervisor.mjs +129 -0
- package/dist/packages/pi-bridge/src/rpc/tool-events.js +78 -0
- package/dist/packages/pi-bridge/src/rpc/wire.js +263 -0
- package/dist/packages/pi-bridge/src/runtime.js +0 -0
- package/dist/packages/pi-bridge/src/sessions-child.mjs +38 -0
- package/dist/packages/pi-bridge/src/sessions.js +314 -0
- package/dist/packages/pi-bridge/src/tool-activity.js +56 -0
- package/dist/packages/protocol/src/index.js +1863 -0
- package/dist/web/assets/index-BOw_fhvO.css +2 -0
- package/dist/web/assets/index-oXs7yAAo.js +119 -0
- package/dist/web/index.html +14 -0
- package/package.json +69 -0
- package/scripts/prepare.mjs +7 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
// packages/pi-bridge/src/resources.ts
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { readFile, realpath, stat } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import {
|
|
7
|
+
DefaultPackageManager,
|
|
8
|
+
ProjectTrustStore,
|
|
9
|
+
SettingsManager,
|
|
10
|
+
VERSION,
|
|
11
|
+
getAgentDir,
|
|
12
|
+
hasTrustRequiringProjectResources,
|
|
13
|
+
parseFrontmatter
|
|
14
|
+
} from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import { z } from "zod";
|
|
16
|
+
import { PiBridgeError } from "./errors.js";
|
|
17
|
+
var INSPECTION_TIMEOUT_MS = 12e3;
|
|
18
|
+
var MAX_CHILD_OUTPUT_BYTES = 2 * 1024 * 1024;
|
|
19
|
+
var MAX_CHILD_ERROR_BYTES = 64 * 1024;
|
|
20
|
+
var MAX_RESOURCE_METADATA_BYTES = 256 * 1024;
|
|
21
|
+
var MAX_RESOURCES = 500;
|
|
22
|
+
var MAX_PACKAGES = 100;
|
|
23
|
+
var MAX_DIAGNOSTICS = 100;
|
|
24
|
+
var SETTING_DEFINITIONS = [
|
|
25
|
+
{ key: "defaultProvider", label: "Default provider", category: "model", path: ["defaultProvider"], effective: (manager) => manager.getDefaultProvider() },
|
|
26
|
+
{ key: "defaultModel", label: "Default model", category: "model", path: ["defaultModel"], effective: (manager) => manager.getDefaultModel() },
|
|
27
|
+
{ key: "defaultThinkingLevel", label: "Thinking level", category: "model", path: ["defaultThinkingLevel"], effective: (manager) => manager.getDefaultThinkingLevel() },
|
|
28
|
+
{ key: "theme", label: "Pi terminal theme", category: "display", path: ["theme"], effective: (manager) => manager.getThemeSetting() },
|
|
29
|
+
{ key: "steeringMode", label: "Steering delivery", category: "behavior", path: ["steeringMode"], effective: (manager) => manager.getSteeringMode() },
|
|
30
|
+
{ key: "followUpMode", label: "Follow-up delivery", category: "behavior", path: ["followUpMode"], effective: (manager) => manager.getFollowUpMode() },
|
|
31
|
+
{ key: "hideThinkingBlock", label: "Hide thinking blocks", category: "display", path: ["hideThinkingBlock"], effective: (manager) => manager.getHideThinkingBlock() },
|
|
32
|
+
{ key: "showCacheMissNotices", label: "Show cache notices", category: "display", path: ["showCacheMissNotices"], effective: (manager) => manager.getShowCacheMissNotices() },
|
|
33
|
+
{ key: "defaultProjectTrust", label: "Default project trust", category: "privacy", path: ["defaultProjectTrust"], effective: (manager) => manager.getDefaultProjectTrust() },
|
|
34
|
+
{ key: "enableSkillCommands", label: "Skill slash commands", category: "tools", path: ["enableSkillCommands"], effective: (manager) => manager.getEnableSkillCommands() },
|
|
35
|
+
{ key: "defaultTools", label: "Default tools", category: "tools", path: ["defaultTools"], effective: (manager) => manager.getDefaultTools() },
|
|
36
|
+
{ key: "enabledModels", label: "Enabled models", category: "model", path: ["enabledModels"], effective: (manager) => manager.getEnabledModels() },
|
|
37
|
+
{ key: "compaction.enabled", label: "Automatic compaction", category: "behavior", path: ["compaction", "enabled"], effective: (manager) => manager.getCompactionSettings().enabled },
|
|
38
|
+
{ key: "compaction.reserveTokens", label: "Compaction reserve", category: "behavior", path: ["compaction", "reserveTokens"], effective: (manager) => manager.getCompactionSettings().reserveTokens },
|
|
39
|
+
{ key: "compaction.keepRecentTokens", label: "Recent tokens retained", category: "behavior", path: ["compaction", "keepRecentTokens"], effective: (manager) => manager.getCompactionSettings().keepRecentTokens },
|
|
40
|
+
{ key: "retry.enabled", label: "Automatic retries", category: "behavior", path: ["retry", "enabled"], effective: (manager) => manager.getRetrySettings().enabled },
|
|
41
|
+
{ key: "retry.maxRetries", label: "Maximum retries", category: "behavior", path: ["retry", "maxRetries"], effective: (manager) => manager.getRetrySettings().maxRetries },
|
|
42
|
+
{ key: "quietStartup", label: "Quiet startup", category: "display", path: ["quietStartup"], effective: (manager) => manager.getQuietStartup() },
|
|
43
|
+
{ key: "externalEditor", label: "External editor", category: "tools", path: ["externalEditor"], effective: (manager) => manager.getExternalEditorCommand() },
|
|
44
|
+
{ key: "shellPath", label: "Shell", category: "tools", path: ["shellPath"], effective: (manager) => manager.getShellPath() },
|
|
45
|
+
{ key: "terminal.showImages", label: "Terminal images", category: "display", path: ["terminal", "showImages"], effective: (manager) => manager.getShowImages() },
|
|
46
|
+
{ key: "markdown.mermaid", label: "Mermaid rendering", category: "display", path: ["markdown", "mermaid"], effective: (manager) => manager.getMermaidRenderingMode() },
|
|
47
|
+
{ key: "enableAnalytics", label: "Pi analytics", category: "privacy", path: ["enableAnalytics"], effective: (manager) => manager.getEnableAnalytics() },
|
|
48
|
+
{ key: "enableInstallTelemetry", label: "Pi install telemetry", category: "privacy", path: ["enableInstallTelemetry"], effective: (manager) => manager.getEnableInstallTelemetry() }
|
|
49
|
+
];
|
|
50
|
+
var diagnosticSchema = z.object({
|
|
51
|
+
severity: z.enum(["warning", "error"]),
|
|
52
|
+
message: z.string(),
|
|
53
|
+
path: z.string().nullable()
|
|
54
|
+
}).strict();
|
|
55
|
+
var inspectedResourceSchema = z.object({
|
|
56
|
+
kind: z.enum(["extension", "skill", "prompt", "theme"]),
|
|
57
|
+
name: z.string(),
|
|
58
|
+
description: z.string().nullable(),
|
|
59
|
+
path: z.string(),
|
|
60
|
+
scope: z.enum(["user", "project", "temporary"]),
|
|
61
|
+
origin: z.enum(["package", "top-level"]),
|
|
62
|
+
source: z.string(),
|
|
63
|
+
enabled: z.boolean(),
|
|
64
|
+
disabledReason: z.enum(["filtered", "project-untrusted"]).nullable()
|
|
65
|
+
}).strict();
|
|
66
|
+
var inspectedPackageSchema = z.object({
|
|
67
|
+
source: z.string(),
|
|
68
|
+
scope: z.enum(["user", "project"]),
|
|
69
|
+
filtered: z.boolean(),
|
|
70
|
+
installed: z.boolean(),
|
|
71
|
+
installedPath: z.string().nullable(),
|
|
72
|
+
enabled: z.boolean()
|
|
73
|
+
}).strict();
|
|
74
|
+
var inspectedSettingSchema = z.object({
|
|
75
|
+
key: z.string(),
|
|
76
|
+
label: z.string(),
|
|
77
|
+
category: z.enum(["model", "behavior", "display", "tools", "privacy"]),
|
|
78
|
+
globalValue: z.string().nullable(),
|
|
79
|
+
projectValue: z.string().nullable(),
|
|
80
|
+
effectiveValue: z.string(),
|
|
81
|
+
effectiveScope: z.enum(["default", "user", "project"])
|
|
82
|
+
}).strict();
|
|
83
|
+
var piConfigurationInspectionSchema = z.object({
|
|
84
|
+
piVersion: z.string(),
|
|
85
|
+
cwd: z.string(),
|
|
86
|
+
agentDir: z.string(),
|
|
87
|
+
settingsPaths: z.object({ global: z.string(), project: z.string(), trust: z.string() }).strict(),
|
|
88
|
+
trust: z.object({
|
|
89
|
+
required: z.boolean(),
|
|
90
|
+
effective: z.boolean(),
|
|
91
|
+
savedDecision: z.boolean().nullable(),
|
|
92
|
+
savedAtPath: z.string().nullable(),
|
|
93
|
+
defaultPolicy: z.enum(["ask", "always", "never"])
|
|
94
|
+
}).strict(),
|
|
95
|
+
packages: z.array(inspectedPackageSchema),
|
|
96
|
+
resources: z.array(inspectedResourceSchema),
|
|
97
|
+
settings: z.array(inspectedSettingSchema),
|
|
98
|
+
diagnostics: z.array(diagnosticSchema),
|
|
99
|
+
omitted: z.object({ packages: z.number().int().nonnegative(), resources: z.number().int().nonnegative(), diagnostics: z.number().int().nonnegative() }).strict()
|
|
100
|
+
}).strict();
|
|
101
|
+
function rawSetting(settings, keys) {
|
|
102
|
+
let value = settings;
|
|
103
|
+
for (const key of keys) {
|
|
104
|
+
if (typeof value !== "object" || value === null || !(key in value)) return void 0;
|
|
105
|
+
value = value[key];
|
|
106
|
+
}
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
function formatSetting(value) {
|
|
110
|
+
if (value === void 0) return null;
|
|
111
|
+
if (value === null) return "None";
|
|
112
|
+
if (typeof value === "string") return value === "" ? "Empty" : value;
|
|
113
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
114
|
+
if (Array.isArray(value)) {
|
|
115
|
+
return value.length === 0 ? "None" : value.map((item) => String(item)).join(", ");
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
return JSON.stringify(value);
|
|
119
|
+
} catch {
|
|
120
|
+
return "Configured";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function resourceLabel(kind, resourcePath) {
|
|
124
|
+
const name = path.basename(resourcePath);
|
|
125
|
+
if (kind === "skill" && name === "SKILL.md") return path.basename(path.dirname(resourcePath));
|
|
126
|
+
return name.replace(/\.(?:ts|js|mjs|cjs|md|json)$/iu, "") || kind;
|
|
127
|
+
}
|
|
128
|
+
function isWithin(root, candidate) {
|
|
129
|
+
const relative = path.relative(root, candidate);
|
|
130
|
+
return relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== "..";
|
|
131
|
+
}
|
|
132
|
+
async function readMetadataFile(filePath, allowedRoot) {
|
|
133
|
+
let verifiedPath = filePath;
|
|
134
|
+
if (allowedRoot !== void 0) {
|
|
135
|
+
const [canonicalRoot, canonicalFile] = await Promise.all([
|
|
136
|
+
realpath(allowedRoot),
|
|
137
|
+
realpath(filePath)
|
|
138
|
+
]);
|
|
139
|
+
if (!isWithin(canonicalRoot, canonicalFile)) {
|
|
140
|
+
throw new Error("project resource metadata resolves outside the task worktree");
|
|
141
|
+
}
|
|
142
|
+
verifiedPath = canonicalFile;
|
|
143
|
+
}
|
|
144
|
+
const details = await stat(verifiedPath);
|
|
145
|
+
if (!details.isFile()) throw new Error("resource metadata path is not a file");
|
|
146
|
+
if (details.size > MAX_RESOURCE_METADATA_BYTES) {
|
|
147
|
+
throw new Error("resource metadata exceeds the read-only inspection limit");
|
|
148
|
+
}
|
|
149
|
+
return readFile(verifiedPath, "utf8");
|
|
150
|
+
}
|
|
151
|
+
async function resourceMetadata(kind, resourcePath, allowedRoot) {
|
|
152
|
+
if (kind === "extension") return {};
|
|
153
|
+
if (kind === "theme") {
|
|
154
|
+
const parsed2 = JSON.parse(await readMetadataFile(resourcePath, allowedRoot));
|
|
155
|
+
if (typeof parsed2 === "object" && parsed2 !== null) {
|
|
156
|
+
const name2 = parsed2["name"];
|
|
157
|
+
return typeof name2 === "string" ? { name: name2 } : {};
|
|
158
|
+
}
|
|
159
|
+
return {};
|
|
160
|
+
}
|
|
161
|
+
const metadataPath = kind === "skill" && path.basename(resourcePath) !== "SKILL.md" ? path.join(resourcePath, "SKILL.md") : resourcePath;
|
|
162
|
+
const parsed = parseFrontmatter(
|
|
163
|
+
await readMetadataFile(metadataPath, allowedRoot)
|
|
164
|
+
);
|
|
165
|
+
const name = parsed.frontmatter["name"];
|
|
166
|
+
const description = parsed.frontmatter["description"];
|
|
167
|
+
return {
|
|
168
|
+
...typeof name === "string" && name.trim() !== "" ? { name } : {},
|
|
169
|
+
...typeof description === "string" ? { description } : {}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function disabledState(resource, projectTrusted) {
|
|
173
|
+
if (resource.metadata.scope === "project" && !projectTrusted) {
|
|
174
|
+
return { enabled: false, disabledReason: "project-untrusted" };
|
|
175
|
+
}
|
|
176
|
+
if (!resource.enabled) return { enabled: false, disabledReason: "filtered" };
|
|
177
|
+
return { enabled: true, disabledReason: null };
|
|
178
|
+
}
|
|
179
|
+
function addDiagnostic(diagnostics, seen, diagnostic) {
|
|
180
|
+
const identity = `${diagnostic.severity}\0${diagnostic.path ?? ""}\0${diagnostic.message}`;
|
|
181
|
+
if (seen.has(identity)) return;
|
|
182
|
+
seen.add(identity);
|
|
183
|
+
diagnostics.push(diagnostic);
|
|
184
|
+
}
|
|
185
|
+
function settingsDiagnostics(manager, diagnostics, seen) {
|
|
186
|
+
for (const issue of manager.drainErrors()) {
|
|
187
|
+
addDiagnostic(diagnostics, seen, {
|
|
188
|
+
severity: "error",
|
|
189
|
+
message: `Pi could not read ${issue.scope} settings: ${issue.error.message}`,
|
|
190
|
+
path: null
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function packageEntries(settings, scope) {
|
|
195
|
+
return (settings.packages ?? []).map((entry) => ({
|
|
196
|
+
source: typeof entry === "string" ? entry : entry.source,
|
|
197
|
+
scope,
|
|
198
|
+
filtered: typeof entry === "object"
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
function bounded(items, maximum) {
|
|
202
|
+
return { items: items.slice(0, maximum), omitted: Math.max(0, items.length - maximum) };
|
|
203
|
+
}
|
|
204
|
+
async function inspectPiConfigurationInProcess(options) {
|
|
205
|
+
if (!path.isAbsolute(options.cwd)) {
|
|
206
|
+
throw new PiBridgeError("PI_INVALID_CONFIGURATION", "Pi inspection cwd must be absolute");
|
|
207
|
+
}
|
|
208
|
+
const cwd = path.normalize(options.cwd);
|
|
209
|
+
const agentDir = path.normalize(options.agentDir ?? getAgentDir());
|
|
210
|
+
const diagnostics = [];
|
|
211
|
+
const diagnosticSeen = /* @__PURE__ */ new Set();
|
|
212
|
+
const globalManager = SettingsManager.create(cwd, agentDir, { projectTrusted: false });
|
|
213
|
+
const inspectionManager = SettingsManager.create(cwd, agentDir, { projectTrusted: true });
|
|
214
|
+
const globalSettings = globalManager.getGlobalSettings();
|
|
215
|
+
const projectSettings = inspectionManager.getProjectSettings();
|
|
216
|
+
const defaultPolicy = globalManager.getDefaultProjectTrust();
|
|
217
|
+
const trustRequired = hasTrustRequiringProjectResources(cwd);
|
|
218
|
+
const savedTrust = new ProjectTrustStore(agentDir).getEntry(cwd);
|
|
219
|
+
const projectTrusted = !trustRequired || savedTrust?.decision === true || savedTrust === null && defaultPolicy === "always";
|
|
220
|
+
const effectiveManager = SettingsManager.create(cwd, agentDir, { projectTrusted });
|
|
221
|
+
settingsDiagnostics(globalManager, diagnostics, diagnosticSeen);
|
|
222
|
+
settingsDiagnostics(inspectionManager, diagnostics, diagnosticSeen);
|
|
223
|
+
settingsDiagnostics(effectiveManager, diagnostics, diagnosticSeen);
|
|
224
|
+
if (trustRequired && savedTrust === null && defaultPolicy === "ask") {
|
|
225
|
+
addDiagnostic(diagnostics, diagnosticSeen, {
|
|
226
|
+
severity: "warning",
|
|
227
|
+
message: "Project-local Pi resources are disabled in RPC mode until the project has a saved trust decision.",
|
|
228
|
+
path: path.join(cwd, ".pi")
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
const packageManager = new DefaultPackageManager({
|
|
232
|
+
cwd,
|
|
233
|
+
agentDir,
|
|
234
|
+
// Until trust is effective, Pi's manager receives no project settings at
|
|
235
|
+
// all. This prevents an untrusted repository from making discovery probe
|
|
236
|
+
// arbitrary configured package/resource paths merely to label them.
|
|
237
|
+
settingsManager: effectiveManager
|
|
238
|
+
});
|
|
239
|
+
const rawPackages = [
|
|
240
|
+
...packageEntries(globalSettings, "user"),
|
|
241
|
+
...packageEntries(projectSettings, "project")
|
|
242
|
+
];
|
|
243
|
+
const packages = rawPackages.map((entry) => {
|
|
244
|
+
if (entry.scope === "project" && !projectTrusted) {
|
|
245
|
+
return {
|
|
246
|
+
...entry,
|
|
247
|
+
installed: false,
|
|
248
|
+
installedPath: null,
|
|
249
|
+
enabled: false
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
const installedPath = packageManager.getInstalledPath(entry.source, entry.scope);
|
|
254
|
+
return {
|
|
255
|
+
...entry,
|
|
256
|
+
installed: installedPath !== void 0,
|
|
257
|
+
installedPath: installedPath ?? null,
|
|
258
|
+
enabled: (entry.scope !== "project" || projectTrusted) && installedPath !== void 0
|
|
259
|
+
};
|
|
260
|
+
} catch (error) {
|
|
261
|
+
addDiagnostic(diagnostics, diagnosticSeen, {
|
|
262
|
+
severity: "error",
|
|
263
|
+
message: `Pi could not inspect package ${entry.source}: ${error instanceof Error ? error.message : "invalid package source"}`,
|
|
264
|
+
path: null
|
|
265
|
+
});
|
|
266
|
+
return { ...entry, installed: false, installedPath: null, enabled: false };
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
const resources = [];
|
|
270
|
+
try {
|
|
271
|
+
const resolved = await packageManager.resolve(async () => "skip");
|
|
272
|
+
const groups = [
|
|
273
|
+
["extension", resolved.extensions],
|
|
274
|
+
["skill", resolved.skills],
|
|
275
|
+
["prompt", resolved.prompts],
|
|
276
|
+
["theme", resolved.themes]
|
|
277
|
+
];
|
|
278
|
+
for (const [kind, entries] of groups) {
|
|
279
|
+
for (const entry of entries) {
|
|
280
|
+
let metadata = {};
|
|
281
|
+
if (entry.metadata.scope !== "project" || projectTrusted) {
|
|
282
|
+
try {
|
|
283
|
+
metadata = await resourceMetadata(
|
|
284
|
+
kind,
|
|
285
|
+
entry.path,
|
|
286
|
+
entry.metadata.scope === "project" ? cwd : void 0
|
|
287
|
+
);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
addDiagnostic(diagnostics, diagnosticSeen, {
|
|
290
|
+
severity: "warning",
|
|
291
|
+
message: `${kind} metadata could not be read: ${error instanceof Error ? error.message : "unknown error"}`,
|
|
292
|
+
path: entry.path
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
resources.push({
|
|
297
|
+
kind,
|
|
298
|
+
name: metadata.name?.trim() || resourceLabel(kind, entry.path),
|
|
299
|
+
description: metadata.description?.trim() || null,
|
|
300
|
+
path: entry.path,
|
|
301
|
+
scope: entry.metadata.scope,
|
|
302
|
+
origin: entry.metadata.origin,
|
|
303
|
+
source: entry.metadata.source || "local",
|
|
304
|
+
...disabledState(entry, projectTrusted)
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
} catch (error) {
|
|
309
|
+
addDiagnostic(diagnostics, diagnosticSeen, {
|
|
310
|
+
severity: "error",
|
|
311
|
+
message: `Pi resource discovery failed: ${error instanceof Error ? error.message : "unknown error"}`,
|
|
312
|
+
path: null
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
const settings = SETTING_DEFINITIONS.map((definition) => {
|
|
316
|
+
const globalRaw = rawSetting(globalSettings, definition.path);
|
|
317
|
+
const projectRaw = rawSetting(projectSettings, definition.path);
|
|
318
|
+
const effectiveScope = projectTrusted && projectRaw !== void 0 ? "project" : globalRaw !== void 0 ? "user" : "default";
|
|
319
|
+
return {
|
|
320
|
+
key: definition.key,
|
|
321
|
+
label: definition.label,
|
|
322
|
+
category: definition.category,
|
|
323
|
+
globalValue: formatSetting(globalRaw),
|
|
324
|
+
projectValue: formatSetting(projectRaw),
|
|
325
|
+
effectiveValue: formatSetting(definition.effective(effectiveManager)) ?? "Not set",
|
|
326
|
+
effectiveScope
|
|
327
|
+
};
|
|
328
|
+
});
|
|
329
|
+
const boundedPackages = bounded(packages, MAX_PACKAGES);
|
|
330
|
+
const boundedResources = bounded(resources, MAX_RESOURCES);
|
|
331
|
+
const boundedDiagnostics = bounded(diagnostics, MAX_DIAGNOSTICS);
|
|
332
|
+
return {
|
|
333
|
+
piVersion: VERSION,
|
|
334
|
+
cwd,
|
|
335
|
+
agentDir,
|
|
336
|
+
settingsPaths: {
|
|
337
|
+
global: path.join(agentDir, "settings.json"),
|
|
338
|
+
project: path.join(cwd, ".pi", "settings.json"),
|
|
339
|
+
trust: path.join(agentDir, "trust.json")
|
|
340
|
+
},
|
|
341
|
+
trust: {
|
|
342
|
+
required: trustRequired,
|
|
343
|
+
effective: projectTrusted,
|
|
344
|
+
savedDecision: savedTrust?.decision ?? null,
|
|
345
|
+
savedAtPath: savedTrust?.path ?? null,
|
|
346
|
+
defaultPolicy
|
|
347
|
+
},
|
|
348
|
+
packages: boundedPackages.items,
|
|
349
|
+
resources: boundedResources.items,
|
|
350
|
+
settings,
|
|
351
|
+
diagnostics: boundedDiagnostics.items,
|
|
352
|
+
omitted: {
|
|
353
|
+
packages: boundedPackages.omitted,
|
|
354
|
+
resources: boundedResources.omitted,
|
|
355
|
+
diagnostics: boundedDiagnostics.omitted
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function killInspector(child) {
|
|
360
|
+
try {
|
|
361
|
+
child.kill("SIGKILL");
|
|
362
|
+
} catch {
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function createPiConfigurationInspector(options = {}) {
|
|
366
|
+
const timeoutMs = options.timeoutMs ?? INSPECTION_TIMEOUT_MS;
|
|
367
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 6e4) {
|
|
368
|
+
throw new PiBridgeError("PI_INVALID_CONFIGURATION", "Pi inspection timeout is invalid");
|
|
369
|
+
}
|
|
370
|
+
const spawnProcess = options.spawnProcess ?? spawn;
|
|
371
|
+
const childPath = options.childPath ?? fileURLToPath(new URL("./resources-child.mjs", import.meta.url));
|
|
372
|
+
return {
|
|
373
|
+
inspect(cwd) {
|
|
374
|
+
if (!path.isAbsolute(cwd)) {
|
|
375
|
+
return Promise.reject(
|
|
376
|
+
new PiBridgeError("PI_INVALID_CONFIGURATION", "Pi inspection cwd must be absolute")
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return new Promise((resolve, reject) => {
|
|
380
|
+
const child = spawnProcess(process.execPath, [childPath], {
|
|
381
|
+
cwd,
|
|
382
|
+
env: { ...options.env ?? process.env, PI_OFFLINE: "1" },
|
|
383
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
384
|
+
});
|
|
385
|
+
let stdout = Buffer.alloc(0);
|
|
386
|
+
let stderrBytes = 0;
|
|
387
|
+
let settled = false;
|
|
388
|
+
const finish = (operation) => {
|
|
389
|
+
if (settled) return;
|
|
390
|
+
settled = true;
|
|
391
|
+
clearTimeout(timer);
|
|
392
|
+
operation();
|
|
393
|
+
};
|
|
394
|
+
const timer = setTimeout(() => {
|
|
395
|
+
killInspector(child);
|
|
396
|
+
finish(
|
|
397
|
+
() => reject(
|
|
398
|
+
new PiBridgeError("PI_REQUEST_TIMEOUT", "Pi configuration inspection timed out")
|
|
399
|
+
)
|
|
400
|
+
);
|
|
401
|
+
}, timeoutMs);
|
|
402
|
+
timer.unref?.();
|
|
403
|
+
child.stdout.on("data", (chunk) => {
|
|
404
|
+
if (settled) return;
|
|
405
|
+
if (stdout.length + chunk.length > MAX_CHILD_OUTPUT_BYTES) {
|
|
406
|
+
killInspector(child);
|
|
407
|
+
finish(
|
|
408
|
+
() => reject(
|
|
409
|
+
new PiBridgeError(
|
|
410
|
+
"PI_PROTOCOL_VIOLATION",
|
|
411
|
+
"Pi configuration inspection exceeded its output bound"
|
|
412
|
+
)
|
|
413
|
+
)
|
|
414
|
+
);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
stdout = Buffer.concat([stdout, chunk]);
|
|
418
|
+
});
|
|
419
|
+
child.stderr.on("data", (chunk) => {
|
|
420
|
+
stderrBytes += chunk.length;
|
|
421
|
+
if (stderrBytes > MAX_CHILD_ERROR_BYTES) killInspector(child);
|
|
422
|
+
});
|
|
423
|
+
child.on("error", (error) => {
|
|
424
|
+
finish(
|
|
425
|
+
() => reject(
|
|
426
|
+
new PiBridgeError("PI_SPAWN_FAILED", "Pi configuration inspector could not start", {
|
|
427
|
+
cause: error
|
|
428
|
+
})
|
|
429
|
+
)
|
|
430
|
+
);
|
|
431
|
+
});
|
|
432
|
+
child.on("close", (code, signal) => {
|
|
433
|
+
finish(() => {
|
|
434
|
+
if (code !== 0 || signal !== null) {
|
|
435
|
+
reject(
|
|
436
|
+
new PiBridgeError(
|
|
437
|
+
"PI_RUNTIME_EXITED",
|
|
438
|
+
"Pi configuration inspection did not complete",
|
|
439
|
+
{ details: { code, signal } }
|
|
440
|
+
)
|
|
441
|
+
);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
let decoded;
|
|
445
|
+
try {
|
|
446
|
+
decoded = JSON.parse(stdout.toString("utf8"));
|
|
447
|
+
} catch (error) {
|
|
448
|
+
reject(
|
|
449
|
+
new PiBridgeError(
|
|
450
|
+
"PI_PROTOCOL_VIOLATION",
|
|
451
|
+
"Pi configuration inspector returned invalid JSON",
|
|
452
|
+
{ cause: error }
|
|
453
|
+
)
|
|
454
|
+
);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
const parsed = piConfigurationInspectionSchema.safeParse(decoded);
|
|
458
|
+
if (!parsed.success) {
|
|
459
|
+
reject(
|
|
460
|
+
new PiBridgeError(
|
|
461
|
+
"PI_PROTOCOL_VIOLATION",
|
|
462
|
+
"Pi configuration inspector returned incompatible data",
|
|
463
|
+
{ cause: parsed.error }
|
|
464
|
+
)
|
|
465
|
+
);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
resolve(parsed.data);
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
child.stdin.on("error", () => {
|
|
472
|
+
});
|
|
473
|
+
child.stdin.end(JSON.stringify({ cwd: path.normalize(cwd) }));
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
export {
|
|
479
|
+
createPiConfigurationInspector,
|
|
480
|
+
inspectPiConfigurationInProcess
|
|
481
|
+
};
|