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,204 @@
|
|
|
1
|
+
// apps/daemon/src/task-review.ts
|
|
2
|
+
import { realpath } from "node:fs/promises";
|
|
3
|
+
import {
|
|
4
|
+
isGitServiceError,
|
|
5
|
+
reviewWorktree
|
|
6
|
+
} from "../../../packages/git/src/index.js";
|
|
7
|
+
import { sanitizeApprovalDisplayText } from "../../../packages/pi-bridge/src/index.js";
|
|
8
|
+
import {
|
|
9
|
+
PROTOCOL_VERSION,
|
|
10
|
+
TASK_REVIEW_MAX_FILE_DIFF_CHARS,
|
|
11
|
+
TASK_REVIEW_MAX_FILES,
|
|
12
|
+
TASK_REVIEW_MAX_TOTAL_DIFF_CHARS,
|
|
13
|
+
taskReviewSchema
|
|
14
|
+
} from "../../../packages/protocol/src/index.js";
|
|
15
|
+
var TaskReviewOperationError = class extends Error {
|
|
16
|
+
name = "TaskReviewOperationError";
|
|
17
|
+
code;
|
|
18
|
+
status;
|
|
19
|
+
constructor(code, message, status, options) {
|
|
20
|
+
super(message, options);
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.status = status;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var MAX_PATH_DISPLAY_CHARS = 4096;
|
|
26
|
+
var WORKTREE_UNAVAILABLE_CODES = /* @__PURE__ */ new Set([
|
|
27
|
+
"GIT_PATH_NOT_FOUND",
|
|
28
|
+
"GIT_PATH_NOT_DIRECTORY",
|
|
29
|
+
"GIT_NOT_A_REPOSITORY",
|
|
30
|
+
"GIT_BARE_REPOSITORY",
|
|
31
|
+
"GIT_NOT_A_WORK_TREE",
|
|
32
|
+
"GIT_HEAD_UNRESOLVED"
|
|
33
|
+
]);
|
|
34
|
+
var WORKTREE_UNAVAILABLE_MESSAGE = "The task worktree is missing or is no longer a healthy Git worktree. Recover it before reviewing changes.";
|
|
35
|
+
function boundedDisplay(text, maxChars) {
|
|
36
|
+
const clippedInput = text.length > maxChars;
|
|
37
|
+
const safe = sanitizeApprovalDisplayText(text.slice(0, maxChars));
|
|
38
|
+
if (safe.length <= maxChars) return { text: safe, clipped: clippedInput };
|
|
39
|
+
return { text: safe.slice(0, maxChars), clipped: true };
|
|
40
|
+
}
|
|
41
|
+
function boundedPathDisplay(text) {
|
|
42
|
+
const bounded = boundedDisplay(text, MAX_PATH_DISPLAY_CHARS);
|
|
43
|
+
if (!bounded.clipped) return bounded.text;
|
|
44
|
+
return `${bounded.text.slice(0, MAX_PATH_DISPLAY_CHARS - 1)}\u2026`;
|
|
45
|
+
}
|
|
46
|
+
function createTaskReviewService(options) {
|
|
47
|
+
const deriveReview = options.reviewWorktree ?? reviewWorktree;
|
|
48
|
+
const now = options.now ?? Date.now;
|
|
49
|
+
async function resolveTask(taskId) {
|
|
50
|
+
let task;
|
|
51
|
+
try {
|
|
52
|
+
task = await options.getTask(taskId);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
throw new TaskReviewOperationError(
|
|
55
|
+
"review-failed",
|
|
56
|
+
"The task could not be resolved from the local workspace.",
|
|
57
|
+
503,
|
|
58
|
+
{ cause: error }
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (task === null) {
|
|
62
|
+
throw new TaskReviewOperationError(
|
|
63
|
+
"task-not-found",
|
|
64
|
+
"The selected task no longer exists.",
|
|
65
|
+
404
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
if (task.status !== "ready") {
|
|
69
|
+
throw new TaskReviewOperationError(
|
|
70
|
+
"task-not-ready",
|
|
71
|
+
`Only a ready task can be reviewed; this task is ${task.status}.`,
|
|
72
|
+
409
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return task;
|
|
76
|
+
}
|
|
77
|
+
async function resolveRepository(task) {
|
|
78
|
+
let repository;
|
|
79
|
+
try {
|
|
80
|
+
repository = await options.getRepository(task.repositoryId);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
throw new TaskReviewOperationError(
|
|
83
|
+
"review-failed",
|
|
84
|
+
"The task's repository could not be resolved from the local workspace.",
|
|
85
|
+
503,
|
|
86
|
+
{ cause: error }
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
if (repository === null) {
|
|
90
|
+
throw new TaskReviewOperationError(
|
|
91
|
+
"task-not-ready",
|
|
92
|
+
"The task's repository is no longer registered.",
|
|
93
|
+
409
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return repository;
|
|
97
|
+
}
|
|
98
|
+
async function review(taskId) {
|
|
99
|
+
const task = await resolveTask(taskId);
|
|
100
|
+
const repository = await resolveRepository(task);
|
|
101
|
+
let canonicalWorktree;
|
|
102
|
+
try {
|
|
103
|
+
canonicalWorktree = await realpath(task.worktreePath);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
throw new TaskReviewOperationError(
|
|
106
|
+
"task-not-ready",
|
|
107
|
+
WORKTREE_UNAVAILABLE_MESSAGE,
|
|
108
|
+
409,
|
|
109
|
+
{ cause: error }
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
let derived;
|
|
113
|
+
try {
|
|
114
|
+
derived = await deriveReview({
|
|
115
|
+
worktreePath: canonicalWorktree,
|
|
116
|
+
maxFiles: TASK_REVIEW_MAX_FILES,
|
|
117
|
+
maxDiffCharsPerFile: TASK_REVIEW_MAX_FILE_DIFF_CHARS,
|
|
118
|
+
maxTotalDiffChars: TASK_REVIEW_MAX_TOTAL_DIFF_CHARS
|
|
119
|
+
});
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (isGitServiceError(error) && WORKTREE_UNAVAILABLE_CODES.has(error.code)) {
|
|
122
|
+
throw new TaskReviewOperationError(
|
|
123
|
+
"task-not-ready",
|
|
124
|
+
WORKTREE_UNAVAILABLE_MESSAGE,
|
|
125
|
+
409,
|
|
126
|
+
{ cause: error }
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
if (isGitServiceError(error, "GIT_REPOSITORY_CHANGED_DURING_REVIEW")) {
|
|
130
|
+
throw new TaskReviewOperationError(
|
|
131
|
+
"review-failed",
|
|
132
|
+
"The task branch or HEAD changed while Webdesk was capturing the review. Refresh to try again.",
|
|
133
|
+
409,
|
|
134
|
+
{ cause: error }
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
throw new TaskReviewOperationError(
|
|
138
|
+
"review-failed",
|
|
139
|
+
"Git could not derive review facts for this worktree. Check the daemon output for details.",
|
|
140
|
+
502,
|
|
141
|
+
{ cause: error }
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
if (derived.worktree.root !== canonicalWorktree || derived.worktree.commonDir !== repository.commonDir || derived.worktree.branch !== task.branch) {
|
|
145
|
+
throw new TaskReviewOperationError(
|
|
146
|
+
"task-not-ready",
|
|
147
|
+
"The recorded worktree no longer matches the task's repository. Recover it before reviewing changes.",
|
|
148
|
+
409
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
let remainingChars = TASK_REVIEW_MAX_TOTAL_DIFF_CHARS;
|
|
152
|
+
let totalDiffTruncated = derived.totalDiffTruncated;
|
|
153
|
+
const files = derived.files.map((file) => {
|
|
154
|
+
let diff = null;
|
|
155
|
+
let diffTruncated = file.diffTruncated;
|
|
156
|
+
let diffOmittedReason = file.diffOmittedReason;
|
|
157
|
+
if (file.diff !== null) {
|
|
158
|
+
if (remainingChars <= 0) {
|
|
159
|
+
diffOmittedReason = "total-budget";
|
|
160
|
+
totalDiffTruncated = true;
|
|
161
|
+
} else {
|
|
162
|
+
const perFileLimit = Math.min(TASK_REVIEW_MAX_FILE_DIFF_CHARS, remainingChars);
|
|
163
|
+
const bounded = boundedDisplay(file.diff, perFileLimit);
|
|
164
|
+
diff = bounded.text;
|
|
165
|
+
if (bounded.clipped) {
|
|
166
|
+
diffTruncated = true;
|
|
167
|
+
if (perFileLimit < TASK_REVIEW_MAX_FILE_DIFF_CHARS) totalDiffTruncated = true;
|
|
168
|
+
}
|
|
169
|
+
remainingChars -= diff.length;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
entryId: file.entryId,
|
|
174
|
+
path: boundedPathDisplay(file.path),
|
|
175
|
+
previousPath: file.previousPath === null ? null : boundedPathDisplay(file.previousPath),
|
|
176
|
+
kind: file.kind,
|
|
177
|
+
staged: file.staged,
|
|
178
|
+
unstaged: file.unstaged,
|
|
179
|
+
binary: file.binary,
|
|
180
|
+
diff,
|
|
181
|
+
diffTruncated,
|
|
182
|
+
diffOmittedReason
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
return taskReviewSchema.parse({
|
|
186
|
+
protocol: PROTOCOL_VERSION,
|
|
187
|
+
type: "task-review",
|
|
188
|
+
taskId: task.id,
|
|
189
|
+
headCommit: derived.worktree.headCommit,
|
|
190
|
+
branch: derived.worktree.branch,
|
|
191
|
+
clean: derived.clean,
|
|
192
|
+
files,
|
|
193
|
+
totalChangedFiles: derived.totalChangedFiles,
|
|
194
|
+
omittedFiles: derived.omittedFiles,
|
|
195
|
+
totalDiffTruncated,
|
|
196
|
+
capturedAtMs: now()
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return { review };
|
|
200
|
+
}
|
|
201
|
+
export {
|
|
202
|
+
TaskReviewOperationError,
|
|
203
|
+
createTaskReviewService
|
|
204
|
+
};
|