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.
Files changed (64) hide show
  1. package/README.md +111 -0
  2. package/dist/apps/daemon/src/appearance-preferences.js +218 -0
  3. package/dist/apps/daemon/src/auth.js +88 -0
  4. package/dist/apps/daemon/src/bin.js +123 -0
  5. package/dist/apps/daemon/src/cli.js +48 -0
  6. package/dist/apps/daemon/src/event-hub.js +155 -0
  7. package/dist/apps/daemon/src/index.js +102 -0
  8. package/dist/apps/daemon/src/launcher-control.js +114 -0
  9. package/dist/apps/daemon/src/launcher.js +73 -0
  10. package/dist/apps/daemon/src/pi-auth.js +290 -0
  11. package/dist/apps/daemon/src/pi-resources.js +182 -0
  12. package/dist/apps/daemon/src/pi-runtime-factory.js +19 -0
  13. package/dist/apps/daemon/src/pi-sessions.js +265 -0
  14. package/dist/apps/daemon/src/runtime-process.js +241 -0
  15. package/dist/apps/daemon/src/secret.js +71 -0
  16. package/dist/apps/daemon/src/server.js +1662 -0
  17. package/dist/apps/daemon/src/session-projection.js +117 -0
  18. package/dist/apps/daemon/src/state-lock.js +31 -0
  19. package/dist/apps/daemon/src/static-web.js +53 -0
  20. package/dist/apps/daemon/src/task-archive.js +152 -0
  21. package/dist/apps/daemon/src/task-commit.js +503 -0
  22. package/dist/apps/daemon/src/task-merge.js +912 -0
  23. package/dist/apps/daemon/src/task-review.js +204 -0
  24. package/dist/apps/daemon/src/task-runtime.js +1124 -0
  25. package/dist/apps/daemon/src/task-validation.js +352 -0
  26. package/dist/apps/daemon/src/workspace-store.js +140 -0
  27. package/dist/apps/daemon/src/workspace.js +795 -0
  28. package/dist/extensions/webdesk.js +34 -0
  29. package/dist/packages/git/src/commit.js +675 -0
  30. package/dist/packages/git/src/errors.js +55 -0
  31. package/dist/packages/git/src/fingerprint.js +286 -0
  32. package/dist/packages/git/src/index.js +123 -0
  33. package/dist/packages/git/src/merge.js +1008 -0
  34. package/dist/packages/git/src/paths.js +58 -0
  35. package/dist/packages/git/src/repository.js +77 -0
  36. package/dist/packages/git/src/review.js +396 -0
  37. package/dist/packages/git/src/runner.js +110 -0
  38. package/dist/packages/git/src/validation.js +263 -0
  39. package/dist/packages/git/src/worktree.js +233 -0
  40. package/dist/packages/pi-bridge/extensions/pita-policy.js +117 -0
  41. package/dist/packages/pi-bridge/src/auth.js +80 -0
  42. package/dist/packages/pi-bridge/src/errors.js +19 -0
  43. package/dist/packages/pi-bridge/src/handshake.js +43 -0
  44. package/dist/packages/pi-bridge/src/index.js +76 -0
  45. package/dist/packages/pi-bridge/src/jsonl.js +105 -0
  46. package/dist/packages/pi-bridge/src/policy-approval.js +62 -0
  47. package/dist/packages/pi-bridge/src/resolve.js +59 -0
  48. package/dist/packages/pi-bridge/src/resources-child.mjs +23 -0
  49. package/dist/packages/pi-bridge/src/resources.js +481 -0
  50. package/dist/packages/pi-bridge/src/rpc/client.js +480 -0
  51. package/dist/packages/pi-bridge/src/rpc/runtime.js +496 -0
  52. package/dist/packages/pi-bridge/src/rpc/supervisor.mjs +129 -0
  53. package/dist/packages/pi-bridge/src/rpc/tool-events.js +78 -0
  54. package/dist/packages/pi-bridge/src/rpc/wire.js +263 -0
  55. package/dist/packages/pi-bridge/src/runtime.js +0 -0
  56. package/dist/packages/pi-bridge/src/sessions-child.mjs +38 -0
  57. package/dist/packages/pi-bridge/src/sessions.js +314 -0
  58. package/dist/packages/pi-bridge/src/tool-activity.js +56 -0
  59. package/dist/packages/protocol/src/index.js +1863 -0
  60. package/dist/web/assets/index-BOw_fhvO.css +2 -0
  61. package/dist/web/assets/index-oXs7yAAo.js +119 -0
  62. package/dist/web/index.html +14 -0
  63. package/package.json +69 -0
  64. 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
+ };