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,290 @@
|
|
|
1
|
+
// apps/daemon/src/pi-auth.ts
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import {
|
|
4
|
+
PROTOCOL_VERSION,
|
|
5
|
+
piAuthNoticeSchema,
|
|
6
|
+
piAuthOperationSchema,
|
|
7
|
+
piAuthPromptSchema,
|
|
8
|
+
piAuthSnapshotSchema
|
|
9
|
+
} from "../../../packages/protocol/src/index.js";
|
|
10
|
+
var PiAuthOperationError = class extends Error {
|
|
11
|
+
name = "PiAuthOperationError";
|
|
12
|
+
code;
|
|
13
|
+
status;
|
|
14
|
+
constructor(code, message, status, options) {
|
|
15
|
+
super(message, options);
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.status = status;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var TERMINAL_STATES = /* @__PURE__ */ new Set([
|
|
21
|
+
"succeeded",
|
|
22
|
+
"failed",
|
|
23
|
+
"cancelled"
|
|
24
|
+
]);
|
|
25
|
+
var MAX_RETAINED_OPERATIONS = 20;
|
|
26
|
+
function abortError(message = "Pi provider login was cancelled.") {
|
|
27
|
+
const error = new Error(message);
|
|
28
|
+
error.name = "AbortError";
|
|
29
|
+
return error;
|
|
30
|
+
}
|
|
31
|
+
function mapPrompt(id, prompt) {
|
|
32
|
+
switch (prompt.type) {
|
|
33
|
+
case "select":
|
|
34
|
+
return piAuthPromptSchema.parse({
|
|
35
|
+
id,
|
|
36
|
+
type: "select",
|
|
37
|
+
message: prompt.message,
|
|
38
|
+
options: prompt.options.map((option) => ({ ...option }))
|
|
39
|
+
});
|
|
40
|
+
case "text":
|
|
41
|
+
case "secret":
|
|
42
|
+
case "manual_code":
|
|
43
|
+
return piAuthPromptSchema.parse({
|
|
44
|
+
id,
|
|
45
|
+
type: prompt.type,
|
|
46
|
+
message: prompt.message,
|
|
47
|
+
...prompt.placeholder === void 0 ? {} : { placeholder: prompt.placeholder }
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function mapNotice(notice) {
|
|
52
|
+
switch (notice.type) {
|
|
53
|
+
case "info":
|
|
54
|
+
return piAuthNoticeSchema.parse({
|
|
55
|
+
type: "info",
|
|
56
|
+
message: notice.message,
|
|
57
|
+
...notice.links === void 0 ? {} : { links: notice.links.map((link) => ({ ...link })) }
|
|
58
|
+
});
|
|
59
|
+
case "auth_url":
|
|
60
|
+
return piAuthNoticeSchema.parse({
|
|
61
|
+
type: "auth_url",
|
|
62
|
+
url: notice.url,
|
|
63
|
+
...notice.instructions === void 0 ? {} : { instructions: notice.instructions }
|
|
64
|
+
});
|
|
65
|
+
case "device_code":
|
|
66
|
+
return piAuthNoticeSchema.parse({
|
|
67
|
+
type: "device_code",
|
|
68
|
+
userCode: notice.userCode,
|
|
69
|
+
verificationUri: notice.verificationUri,
|
|
70
|
+
...notice.intervalSeconds === void 0 ? {} : { intervalSeconds: notice.intervalSeconds },
|
|
71
|
+
...notice.expiresInSeconds === void 0 ? {} : { expiresInSeconds: notice.expiresInSeconds }
|
|
72
|
+
});
|
|
73
|
+
case "progress":
|
|
74
|
+
return piAuthNoticeSchema.parse({
|
|
75
|
+
type: "progress",
|
|
76
|
+
message: notice.message
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function createPiAuthCoordinator(options) {
|
|
81
|
+
const now = options.now ?? Date.now;
|
|
82
|
+
const operations = /* @__PURE__ */ new Map();
|
|
83
|
+
let disposed = false;
|
|
84
|
+
function requireActive() {
|
|
85
|
+
if (disposed) {
|
|
86
|
+
throw new PiAuthOperationError(
|
|
87
|
+
"auth-unavailable",
|
|
88
|
+
"The Pi authentication service is shutting down.",
|
|
89
|
+
503
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function result(entry) {
|
|
94
|
+
return piAuthOperationSchema.parse({
|
|
95
|
+
protocol: PROTOCOL_VERSION,
|
|
96
|
+
type: "pi-auth-operation",
|
|
97
|
+
id: entry.id,
|
|
98
|
+
providerId: entry.providerId,
|
|
99
|
+
method: entry.method,
|
|
100
|
+
state: entry.state,
|
|
101
|
+
prompt: entry.prompt,
|
|
102
|
+
notice: entry.notice,
|
|
103
|
+
...entry.error === null ? {} : { error: entry.error }
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function requireOperation(operationId) {
|
|
107
|
+
requireActive();
|
|
108
|
+
const entry = operations.get(operationId);
|
|
109
|
+
if (entry === void 0) {
|
|
110
|
+
throw new PiAuthOperationError(
|
|
111
|
+
"operation-not-found",
|
|
112
|
+
"That Pi login operation no longer exists.",
|
|
113
|
+
404
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return entry;
|
|
117
|
+
}
|
|
118
|
+
function trimOperations() {
|
|
119
|
+
if (operations.size <= MAX_RETAINED_OPERATIONS) return;
|
|
120
|
+
const terminal = [...operations.values()].filter((entry) => TERMINAL_STATES.has(entry.state)).sort((left, right) => left.createdAt - right.createdAt);
|
|
121
|
+
while (operations.size > MAX_RETAINED_OPERATIONS && terminal.length > 0) {
|
|
122
|
+
const oldest = terminal.shift();
|
|
123
|
+
if (oldest !== void 0) operations.delete(oldest.id);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function prompt(entry, request) {
|
|
127
|
+
if (entry.controller.signal.aborted) return Promise.reject(abortError());
|
|
128
|
+
if (entry.pending !== null) {
|
|
129
|
+
return Promise.reject(
|
|
130
|
+
new Error("Pi requested two authentication prompts at the same time.")
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const promptId = randomUUID();
|
|
134
|
+
entry.prompt = mapPrompt(promptId, request);
|
|
135
|
+
entry.state = "waiting";
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
const onAbort = () => {
|
|
138
|
+
if (entry.pending?.id !== promptId) return;
|
|
139
|
+
entry.pending = null;
|
|
140
|
+
entry.prompt = null;
|
|
141
|
+
reject(abortError());
|
|
142
|
+
};
|
|
143
|
+
const signals = [entry.controller.signal, request.signal].filter(
|
|
144
|
+
(signal) => signal !== void 0
|
|
145
|
+
);
|
|
146
|
+
for (const signal of signals) signal.addEventListener("abort", onAbort, { once: true });
|
|
147
|
+
entry.pending = {
|
|
148
|
+
id: promptId,
|
|
149
|
+
resolve,
|
|
150
|
+
reject,
|
|
151
|
+
removeAbortListener() {
|
|
152
|
+
for (const signal of signals) signal.removeEventListener("abort", onAbort);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
if (signals.some((signal) => signal.aborted)) onAbort();
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
function runLogin(entry) {
|
|
159
|
+
const interaction = {
|
|
160
|
+
signal: entry.controller.signal,
|
|
161
|
+
prompt: (request) => prompt(entry, request),
|
|
162
|
+
notify(notice) {
|
|
163
|
+
entry.notice = mapNotice(notice);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
void options.auth.login(entry.providerId, entry.method, interaction).then(
|
|
167
|
+
() => {
|
|
168
|
+
if (entry.state === "cancelled") return;
|
|
169
|
+
entry.pending?.removeAbortListener();
|
|
170
|
+
entry.pending = null;
|
|
171
|
+
entry.prompt = null;
|
|
172
|
+
entry.error = null;
|
|
173
|
+
entry.state = "succeeded";
|
|
174
|
+
trimOperations();
|
|
175
|
+
},
|
|
176
|
+
(error) => {
|
|
177
|
+
entry.pending?.removeAbortListener();
|
|
178
|
+
entry.pending = null;
|
|
179
|
+
entry.prompt = null;
|
|
180
|
+
if (entry.state === "cancelled" || entry.controller.signal.aborted) {
|
|
181
|
+
entry.state = "cancelled";
|
|
182
|
+
entry.error = null;
|
|
183
|
+
} else {
|
|
184
|
+
entry.state = "failed";
|
|
185
|
+
entry.error = "Pi provider login failed. Try again or check the provider configuration.";
|
|
186
|
+
}
|
|
187
|
+
trimOperations();
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
async snapshot() {
|
|
193
|
+
requireActive();
|
|
194
|
+
try {
|
|
195
|
+
const providers = await options.auth.listProviders();
|
|
196
|
+
const active = [...operations.values()].find(
|
|
197
|
+
(entry) => !TERMINAL_STATES.has(entry.state)
|
|
198
|
+
);
|
|
199
|
+
return piAuthSnapshotSchema.parse({
|
|
200
|
+
protocol: PROTOCOL_VERSION,
|
|
201
|
+
type: "pi-auth-snapshot",
|
|
202
|
+
providers,
|
|
203
|
+
activeOperation: active === void 0 ? null : result(active)
|
|
204
|
+
});
|
|
205
|
+
} catch (error) {
|
|
206
|
+
throw new PiAuthOperationError(
|
|
207
|
+
"auth-unavailable",
|
|
208
|
+
"Pi provider status is unavailable.",
|
|
209
|
+
503,
|
|
210
|
+
{ cause: error }
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
isAuthenticated(providerId) {
|
|
215
|
+
requireActive();
|
|
216
|
+
return options.auth.hasAuth(providerId);
|
|
217
|
+
},
|
|
218
|
+
startLogin(providerId, method) {
|
|
219
|
+
requireActive();
|
|
220
|
+
const active = [...operations.values()].find(
|
|
221
|
+
(entry2) => !TERMINAL_STATES.has(entry2.state)
|
|
222
|
+
);
|
|
223
|
+
if (active !== void 0) {
|
|
224
|
+
throw new PiAuthOperationError(
|
|
225
|
+
"operation-conflict",
|
|
226
|
+
`Finish or cancel the existing ${active.providerId} login first.`,
|
|
227
|
+
409
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
const entry = {
|
|
231
|
+
id: randomUUID(),
|
|
232
|
+
providerId,
|
|
233
|
+
method,
|
|
234
|
+
state: "running",
|
|
235
|
+
prompt: null,
|
|
236
|
+
notice: null,
|
|
237
|
+
error: null,
|
|
238
|
+
controller: new AbortController(),
|
|
239
|
+
pending: null,
|
|
240
|
+
createdAt: now()
|
|
241
|
+
};
|
|
242
|
+
operations.set(entry.id, entry);
|
|
243
|
+
runLogin(entry);
|
|
244
|
+
return result(entry);
|
|
245
|
+
},
|
|
246
|
+
operation(operationId) {
|
|
247
|
+
return result(requireOperation(operationId));
|
|
248
|
+
},
|
|
249
|
+
respond(operationId, promptId, value) {
|
|
250
|
+
const entry = requireOperation(operationId);
|
|
251
|
+
if (entry.state !== "waiting" || entry.pending?.id !== promptId) {
|
|
252
|
+
throw new PiAuthOperationError(
|
|
253
|
+
"operation-conflict",
|
|
254
|
+
"That Pi login prompt is no longer waiting for a response.",
|
|
255
|
+
409
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
const pending = entry.pending;
|
|
259
|
+
pending.removeAbortListener();
|
|
260
|
+
entry.pending = null;
|
|
261
|
+
entry.prompt = null;
|
|
262
|
+
entry.state = "running";
|
|
263
|
+
pending.resolve(value);
|
|
264
|
+
return result(entry);
|
|
265
|
+
},
|
|
266
|
+
cancel(operationId) {
|
|
267
|
+
const entry = requireOperation(operationId);
|
|
268
|
+
if (TERMINAL_STATES.has(entry.state)) return result(entry);
|
|
269
|
+
entry.state = "cancelled";
|
|
270
|
+
entry.prompt = null;
|
|
271
|
+
entry.error = null;
|
|
272
|
+
entry.controller.abort();
|
|
273
|
+
return result(entry);
|
|
274
|
+
},
|
|
275
|
+
dispose() {
|
|
276
|
+
if (disposed) return;
|
|
277
|
+
disposed = true;
|
|
278
|
+
for (const entry of operations.values()) {
|
|
279
|
+
if (!TERMINAL_STATES.has(entry.state)) {
|
|
280
|
+
entry.state = "cancelled";
|
|
281
|
+
entry.controller.abort();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
export {
|
|
288
|
+
PiAuthOperationError,
|
|
289
|
+
createPiAuthCoordinator
|
|
290
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// apps/daemon/src/pi-resources.ts
|
|
2
|
+
import {
|
|
3
|
+
PiBridgeError,
|
|
4
|
+
createPiConfigurationInspector,
|
|
5
|
+
sanitizeApprovalDisplayText
|
|
6
|
+
} from "../../../packages/pi-bridge/src/index.js";
|
|
7
|
+
import {
|
|
8
|
+
PI_CONFIGURATION_MAX_DIAGNOSTICS,
|
|
9
|
+
PI_CONFIGURATION_MAX_PACKAGES,
|
|
10
|
+
PI_CONFIGURATION_MAX_RESOURCES,
|
|
11
|
+
PROTOCOL_VERSION,
|
|
12
|
+
piConfigurationSnapshotSchema
|
|
13
|
+
} from "../../../packages/protocol/src/index.js";
|
|
14
|
+
var PiConfigurationOperationError = class extends Error {
|
|
15
|
+
name = "PiConfigurationOperationError";
|
|
16
|
+
code;
|
|
17
|
+
status;
|
|
18
|
+
constructor(code, message, status, options) {
|
|
19
|
+
super(message, options);
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.status = status;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
function display(text, maximum, fallback = "Unknown") {
|
|
25
|
+
const safe = sanitizeApprovalDisplayText(text.slice(0, maximum));
|
|
26
|
+
const bounded = safe.length <= maximum ? safe : safe.slice(0, maximum);
|
|
27
|
+
return bounded.trim() === "" ? fallback : bounded;
|
|
28
|
+
}
|
|
29
|
+
function redactCredentialText(text) {
|
|
30
|
+
return text.replace(/([a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/giu, "$1[redacted]@").replace(
|
|
31
|
+
/([?&](?:access[_-]?token|api[_-]?key|auth|credential|password|secret|token)=)[^&#\s]*/giu,
|
|
32
|
+
"$1[redacted]"
|
|
33
|
+
).replace(/\b(?:github_pat_|gh[pousr]_|sk-)[a-z0-9_-]{8,}\b/giu, "[redacted]");
|
|
34
|
+
}
|
|
35
|
+
function credentialSafeDisplay(text, maximum, fallback) {
|
|
36
|
+
return display(redactCredentialText(text), maximum, fallback);
|
|
37
|
+
}
|
|
38
|
+
function displayPath(text) {
|
|
39
|
+
return display(text, 4096, "(unavailable)");
|
|
40
|
+
}
|
|
41
|
+
function displayValue(value) {
|
|
42
|
+
return value === null ? null : display(value, 2e3, "Empty");
|
|
43
|
+
}
|
|
44
|
+
function mapInspection(taskId, canonicalWorktree, inspection) {
|
|
45
|
+
if (inspection.cwd !== canonicalWorktree) {
|
|
46
|
+
throw new PiConfigurationOperationError(
|
|
47
|
+
"inspection-failed",
|
|
48
|
+
"Pi configuration was returned for a different task directory.",
|
|
49
|
+
503
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
const packages = inspection.packages.slice(0, PI_CONFIGURATION_MAX_PACKAGES).map((entry) => ({
|
|
53
|
+
...entry,
|
|
54
|
+
source: credentialSafeDisplay(entry.source, 1e3, "local package"),
|
|
55
|
+
installedPath: entry.installedPath === null ? null : displayPath(entry.installedPath)
|
|
56
|
+
}));
|
|
57
|
+
const resources = inspection.resources.slice(0, PI_CONFIGURATION_MAX_RESOURCES).map((entry) => ({
|
|
58
|
+
...entry,
|
|
59
|
+
name: display(entry.name, 500, entry.kind),
|
|
60
|
+
description: entry.description === null ? null : credentialSafeDisplay(entry.description, 2e3, ""),
|
|
61
|
+
path: displayPath(entry.path),
|
|
62
|
+
source: credentialSafeDisplay(entry.source, 1e3, "local")
|
|
63
|
+
}));
|
|
64
|
+
const diagnostics = inspection.diagnostics.slice(0, PI_CONFIGURATION_MAX_DIAGNOSTICS).map((entry) => ({
|
|
65
|
+
...entry,
|
|
66
|
+
message: credentialSafeDisplay(
|
|
67
|
+
entry.message,
|
|
68
|
+
2e3,
|
|
69
|
+
"Pi reported a resource problem."
|
|
70
|
+
),
|
|
71
|
+
path: entry.path === null ? null : displayPath(entry.path)
|
|
72
|
+
}));
|
|
73
|
+
return piConfigurationSnapshotSchema.parse({
|
|
74
|
+
protocol: PROTOCOL_VERSION,
|
|
75
|
+
type: "pi-configuration-snapshot",
|
|
76
|
+
taskId,
|
|
77
|
+
piVersion: display(inspection.piVersion, 100),
|
|
78
|
+
cwd: displayPath(inspection.cwd),
|
|
79
|
+
agentDir: displayPath(inspection.agentDir),
|
|
80
|
+
settingsPaths: {
|
|
81
|
+
global: displayPath(inspection.settingsPaths.global),
|
|
82
|
+
project: displayPath(inspection.settingsPaths.project),
|
|
83
|
+
trust: displayPath(inspection.settingsPaths.trust)
|
|
84
|
+
},
|
|
85
|
+
trust: {
|
|
86
|
+
...inspection.trust,
|
|
87
|
+
savedAtPath: inspection.trust.savedAtPath === null ? null : displayPath(inspection.trust.savedAtPath)
|
|
88
|
+
},
|
|
89
|
+
packages,
|
|
90
|
+
resources,
|
|
91
|
+
settings: inspection.settings.map((entry) => ({
|
|
92
|
+
...entry,
|
|
93
|
+
key: display(entry.key, 200),
|
|
94
|
+
label: display(entry.label, 500),
|
|
95
|
+
globalValue: displayValue(entry.globalValue),
|
|
96
|
+
projectValue: displayValue(entry.projectValue),
|
|
97
|
+
effectiveValue: display(entry.effectiveValue, 2e3, "Not set")
|
|
98
|
+
})),
|
|
99
|
+
diagnostics,
|
|
100
|
+
omitted: {
|
|
101
|
+
packages: inspection.omitted.packages + Math.max(0, inspection.packages.length - PI_CONFIGURATION_MAX_PACKAGES),
|
|
102
|
+
resources: inspection.omitted.resources + Math.max(0, inspection.resources.length - PI_CONFIGURATION_MAX_RESOURCES),
|
|
103
|
+
diagnostics: inspection.omitted.diagnostics + Math.max(0, inspection.diagnostics.length - PI_CONFIGURATION_MAX_DIAGNOSTICS)
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function createPiConfigurationService(options) {
|
|
108
|
+
const inspector = options.inspector ?? createPiConfigurationInspector();
|
|
109
|
+
const activeInspections = /* @__PURE__ */ new Map();
|
|
110
|
+
async function inspectTask(taskId) {
|
|
111
|
+
let task;
|
|
112
|
+
try {
|
|
113
|
+
task = await options.getTask(taskId);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
throw new PiConfigurationOperationError(
|
|
116
|
+
"inspection-failed",
|
|
117
|
+
"The task could not be resolved from the local workspace.",
|
|
118
|
+
503,
|
|
119
|
+
{ cause: error }
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
if (task === null) {
|
|
123
|
+
throw new PiConfigurationOperationError(
|
|
124
|
+
"task-not-found",
|
|
125
|
+
"The selected task no longer exists.",
|
|
126
|
+
404
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
if (task.status !== "ready") {
|
|
130
|
+
throw new PiConfigurationOperationError(
|
|
131
|
+
"task-not-ready",
|
|
132
|
+
`Only a ready task has an inspectable Pi configuration; this task is ${task.status}.`,
|
|
133
|
+
409
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
let canonicalWorktree;
|
|
137
|
+
try {
|
|
138
|
+
canonicalWorktree = await options.verifyTaskWorktree(taskId);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
throw new PiConfigurationOperationError(
|
|
141
|
+
"task-not-ready",
|
|
142
|
+
"The task worktree could not be verified before reading Pi configuration.",
|
|
143
|
+
409,
|
|
144
|
+
{ cause: error }
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
return mapInspection(taskId, canonicalWorktree, await inspector.inspect(canonicalWorktree));
|
|
149
|
+
} catch (error) {
|
|
150
|
+
if (error instanceof PiConfigurationOperationError) throw error;
|
|
151
|
+
if (error instanceof PiBridgeError && error.code === "PI_REQUEST_TIMEOUT") {
|
|
152
|
+
throw new PiConfigurationOperationError(
|
|
153
|
+
"inspection-timeout",
|
|
154
|
+
"Pi configuration inspection took too long. Check unusually large resource directories and retry.",
|
|
155
|
+
504,
|
|
156
|
+
{ cause: error }
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
throw new PiConfigurationOperationError(
|
|
160
|
+
"inspection-failed",
|
|
161
|
+
"Pi configuration could not be inspected. Check the daemon output for details.",
|
|
162
|
+
503,
|
|
163
|
+
{ cause: error }
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
inspect(taskId) {
|
|
169
|
+
const existing = activeInspections.get(taskId);
|
|
170
|
+
if (existing !== void 0) return existing;
|
|
171
|
+
const operation = inspectTask(taskId).finally(() => {
|
|
172
|
+
if (activeInspections.get(taskId) === operation) activeInspections.delete(taskId);
|
|
173
|
+
});
|
|
174
|
+
activeInspections.set(taskId, operation);
|
|
175
|
+
return operation;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
export {
|
|
180
|
+
PiConfigurationOperationError,
|
|
181
|
+
createPiConfigurationService
|
|
182
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// apps/daemon/src/pi-runtime-factory.ts
|
|
2
|
+
import { createPiRpcRuntime, resolvePiInvocation } from "../../../packages/pi-bridge/src/index.js";
|
|
3
|
+
function createPiTaskRuntimeFactory(options = {}) {
|
|
4
|
+
return ({ worktreePath, sessionFile }) => {
|
|
5
|
+
const invocation = resolvePiInvocation();
|
|
6
|
+
return createPiRpcRuntime({
|
|
7
|
+
executable: invocation.executable,
|
|
8
|
+
prependArgs: invocation.prependArgs,
|
|
9
|
+
cwd: worktreePath,
|
|
10
|
+
mode: "supervised",
|
|
11
|
+
sessionMode: "persistent",
|
|
12
|
+
...options.supervisorIgnoreEofForTests === true ? { supervisorIgnoreEofForTests: true } : {},
|
|
13
|
+
...sessionFile === null ? {} : { sessionFile }
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
createPiTaskRuntimeFactory
|
|
19
|
+
};
|