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,19 @@
1
+ // packages/pi-bridge/src/errors.ts
2
+ var PiBridgeError = class extends Error {
3
+ code;
4
+ details;
5
+ constructor(code, message, options) {
6
+ super(message, options?.cause === void 0 ? void 0 : { cause: options.cause });
7
+ this.name = "PiBridgeError";
8
+ this.code = code;
9
+ this.details = options?.details ?? {};
10
+ }
11
+ };
12
+ function isPiBridgeError(value, code) {
13
+ if (!(value instanceof PiBridgeError)) return false;
14
+ return code === void 0 || value.code === code;
15
+ }
16
+ export {
17
+ PiBridgeError,
18
+ isPiBridgeError
19
+ };
@@ -0,0 +1,43 @@
1
+ // packages/pi-bridge/src/handshake.ts
2
+ import { z } from "zod";
3
+ var PITA_POLICY_STATUS_KEY = "pita-policy";
4
+ var PITA_POLICY_PROTOCOL = "pita-policy";
5
+ var PITA_POLICY_VERSION = 1;
6
+ var pitaRuntimeModeSchema = z.enum(["supervised", "trusted"]);
7
+ var pitaPolicyStatusSchema = z.looseObject({
8
+ protocol: z.literal(PITA_POLICY_PROTOCOL),
9
+ version: z.number().int().positive(),
10
+ mode: pitaRuntimeModeSchema
11
+ });
12
+ function isSupportedPolicyVersion(version) {
13
+ return version === PITA_POLICY_VERSION;
14
+ }
15
+ function encodePolicyStatus(status) {
16
+ return JSON.stringify(status);
17
+ }
18
+ function decodePolicyStatus(statusText) {
19
+ if (statusText === void 0) {
20
+ return { ok: false, reason: "handshake statusText is missing" };
21
+ }
22
+ let parsed;
23
+ try {
24
+ parsed = JSON.parse(statusText);
25
+ } catch {
26
+ return { ok: false, reason: `handshake statusText is not valid JSON: ${statusText}` };
27
+ }
28
+ const result = pitaPolicyStatusSchema.safeParse(parsed);
29
+ if (!result.success) {
30
+ return { ok: false, reason: `handshake payload is malformed: ${result.error.message}` };
31
+ }
32
+ return { ok: true, status: result.data };
33
+ }
34
+ export {
35
+ PITA_POLICY_PROTOCOL,
36
+ PITA_POLICY_STATUS_KEY,
37
+ PITA_POLICY_VERSION,
38
+ decodePolicyStatus,
39
+ encodePolicyStatus,
40
+ isSupportedPolicyVersion,
41
+ pitaPolicyStatusSchema,
42
+ pitaRuntimeModeSchema
43
+ };
@@ -0,0 +1,76 @@
1
+ // packages/pi-bridge/src/index.ts
2
+ import { PiBridgeError, isPiBridgeError } from "./errors.js";
3
+ import {
4
+ createPiAuthManager
5
+ } from "./auth.js";
6
+ import { JsonlDecoder } from "./jsonl.js";
7
+ import {
8
+ PITA_POLICY_APPROVAL_PROTOCOL,
9
+ PITA_POLICY_APPROVAL_TITLE,
10
+ MAX_POLICY_APPROVAL_SUMMARY_CHARS,
11
+ MAX_POLICY_APPROVAL_TIMEOUT_MS,
12
+ decodePolicyApproval,
13
+ encodePolicyApproval,
14
+ sanitizeApprovalDisplayText
15
+ } from "./policy-approval.js";
16
+ import {
17
+ PITA_POLICY_PROTOCOL,
18
+ PITA_POLICY_STATUS_KEY,
19
+ PITA_POLICY_VERSION,
20
+ decodePolicyStatus,
21
+ encodePolicyStatus,
22
+ isSupportedPolicyVersion,
23
+ pitaPolicyStatusSchema,
24
+ pitaRuntimeModeSchema
25
+ } from "./handshake.js";
26
+ import {
27
+ DEFAULT_POLICY_EXTENSION_PATH,
28
+ createPiRpcRuntime
29
+ } from "./rpc/runtime.js";
30
+ import {
31
+ PI_EXECUTABLE_ENV_VAR,
32
+ resolvePiInvocation
33
+ } from "./resolve.js";
34
+ import {
35
+ createPiConfigurationInspector,
36
+ inspectPiConfigurationInProcess
37
+ } from "./resources.js";
38
+ import {
39
+ MAX_PI_SESSION_CATALOG_ENTRIES,
40
+ PI_SESSION_DIRECTORY_ENV,
41
+ createPiSessionCatalog,
42
+ forkPiSessionInProcess,
43
+ inspectPiSessionsInProcess
44
+ } from "./sessions.js";
45
+ export {
46
+ DEFAULT_POLICY_EXTENSION_PATH,
47
+ JsonlDecoder,
48
+ MAX_PI_SESSION_CATALOG_ENTRIES,
49
+ MAX_POLICY_APPROVAL_SUMMARY_CHARS,
50
+ MAX_POLICY_APPROVAL_TIMEOUT_MS,
51
+ PITA_POLICY_APPROVAL_PROTOCOL,
52
+ PITA_POLICY_APPROVAL_TITLE,
53
+ PITA_POLICY_PROTOCOL,
54
+ PITA_POLICY_STATUS_KEY,
55
+ PITA_POLICY_VERSION,
56
+ PI_EXECUTABLE_ENV_VAR,
57
+ PI_SESSION_DIRECTORY_ENV,
58
+ PiBridgeError,
59
+ createPiAuthManager,
60
+ createPiConfigurationInspector,
61
+ createPiRpcRuntime,
62
+ createPiSessionCatalog,
63
+ decodePolicyApproval,
64
+ decodePolicyStatus,
65
+ encodePolicyApproval,
66
+ encodePolicyStatus,
67
+ forkPiSessionInProcess,
68
+ inspectPiConfigurationInProcess,
69
+ inspectPiSessionsInProcess,
70
+ isPiBridgeError,
71
+ isSupportedPolicyVersion,
72
+ pitaPolicyStatusSchema,
73
+ pitaRuntimeModeSchema,
74
+ resolvePiInvocation,
75
+ sanitizeApprovalDisplayText
76
+ };
@@ -0,0 +1,105 @@
1
+ // packages/pi-bridge/src/jsonl.ts
2
+ import { StringDecoder } from "node:string_decoder";
3
+ import { PiBridgeError } from "./errors.js";
4
+ var DEFAULT_MAX_RECORD_BYTES = 8 * 1024 * 1024;
5
+ function snippet(text) {
6
+ return text.length <= 160 ? text : `${text.slice(0, 160)}\u2026`;
7
+ }
8
+ var JsonlDecoder = class {
9
+ #utf8 = new StringDecoder("utf8");
10
+ #buffer = "";
11
+ #failed = false;
12
+ #maxRecordBytes;
13
+ constructor(options = {}) {
14
+ this.#maxRecordBytes = options.maxRecordBytes ?? DEFAULT_MAX_RECORD_BYTES;
15
+ }
16
+ /**
17
+ * Feeds a chunk and returns every complete record it finished. Throws a
18
+ * `PiBridgeError` on malformed or oversized records; after a throw the
19
+ * decoder is permanently failed and must not be reused.
20
+ */
21
+ push(chunk) {
22
+ this.#assertUsable();
23
+ this.#buffer += typeof chunk === "string" ? chunk : this.#utf8.write(chunk);
24
+ const records = [];
25
+ let newlineIndex = this.#buffer.indexOf("\n");
26
+ while (newlineIndex !== -1) {
27
+ let line = this.#buffer.slice(0, newlineIndex);
28
+ this.#buffer = this.#buffer.slice(newlineIndex + 1);
29
+ if (line.endsWith("\r")) line = line.slice(0, -1);
30
+ records.push(this.#parseRecord(line));
31
+ newlineIndex = this.#buffer.indexOf("\n");
32
+ }
33
+ if (Buffer.byteLength(this.#buffer, "utf8") > this.#maxRecordBytes) {
34
+ this.#fail(
35
+ new PiBridgeError(
36
+ "PI_JSONL_OVERSIZED_RECORD",
37
+ `JSONL record exceeds ${this.#maxRecordBytes} bytes without a newline`,
38
+ { details: { maxRecordBytes: this.#maxRecordBytes } }
39
+ )
40
+ );
41
+ }
42
+ return records;
43
+ }
44
+ /**
45
+ * Signals end of stream. Throws if a non-empty partial record was pending,
46
+ * which means the stream was truncated mid-record.
47
+ */
48
+ end() {
49
+ this.#assertUsable();
50
+ this.#buffer += this.#utf8.end();
51
+ if (this.#buffer.length > 0) {
52
+ const partial = this.#buffer;
53
+ this.#buffer = "";
54
+ this.#fail(
55
+ new PiBridgeError(
56
+ "PI_JSONL_TRUNCATED_STREAM",
57
+ `JSONL stream ended with a partial record: ${snippet(partial)}`,
58
+ { details: { partial: snippet(partial) } }
59
+ )
60
+ );
61
+ }
62
+ }
63
+ #parseRecord(line) {
64
+ if (Buffer.byteLength(line, "utf8") > this.#maxRecordBytes) {
65
+ this.#fail(
66
+ new PiBridgeError(
67
+ "PI_JSONL_OVERSIZED_RECORD",
68
+ `JSONL record exceeds ${this.#maxRecordBytes} bytes`,
69
+ { details: { maxRecordBytes: this.#maxRecordBytes } }
70
+ )
71
+ );
72
+ }
73
+ if (line.length === 0) {
74
+ this.#fail(
75
+ new PiBridgeError("PI_JSONL_MALFORMED_RECORD", "JSONL stream contained an empty record")
76
+ );
77
+ }
78
+ try {
79
+ return JSON.parse(line);
80
+ } catch (cause) {
81
+ this.#fail(
82
+ new PiBridgeError(
83
+ "PI_JSONL_MALFORMED_RECORD",
84
+ `JSONL record is not valid JSON: ${snippet(line)}`,
85
+ { cause, details: { record: snippet(line) } }
86
+ )
87
+ );
88
+ }
89
+ }
90
+ #assertUsable() {
91
+ if (this.#failed) {
92
+ throw new PiBridgeError(
93
+ "PI_PROTOCOL_VIOLATION",
94
+ "JsonlDecoder cannot be used after a decode failure"
95
+ );
96
+ }
97
+ }
98
+ #fail(error) {
99
+ this.#failed = true;
100
+ throw error;
101
+ }
102
+ };
103
+ export {
104
+ JsonlDecoder
105
+ };
@@ -0,0 +1,62 @@
1
+ // packages/pi-bridge/src/policy-approval.ts
2
+ import { z } from "zod";
3
+ var PITA_POLICY_APPROVAL_PROTOCOL = "pita-policy-approval-v1";
4
+ var PITA_POLICY_APPROVAL_TITLE = "Pita supervised approval";
5
+ var MAX_POLICY_APPROVAL_SUMMARY_CHARS = 1e5;
6
+ var MAX_POLICY_APPROVAL_TIMEOUT_MS = 10 * 6e4;
7
+ var unsafeDisplayCharacter = /[\p{Cc}\p{Cf}\p{Default_Ignorable_Code_Point}]/u;
8
+ function sanitizeApprovalDisplayText(value) {
9
+ let safe = "";
10
+ for (const character of value) {
11
+ if (character === "\n" || character === " ") {
12
+ safe += character;
13
+ } else if (unsafeDisplayCharacter.test(character)) {
14
+ safe += `\\u{${character.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}}`;
15
+ } else {
16
+ safe += character;
17
+ }
18
+ }
19
+ return safe;
20
+ }
21
+ var policyApprovalSchema = z.object({
22
+ protocol: z.literal(PITA_POLICY_APPROVAL_PROTOCOL),
23
+ toolCallId: z.string().min(1).max(500),
24
+ toolName: z.string().min(1).max(200),
25
+ summary: z.string().min(1).max(MAX_POLICY_APPROVAL_SUMMARY_CHARS),
26
+ requestedAtMs: z.number().int().nonnegative(),
27
+ timeoutMs: z.number().int().positive().max(MAX_POLICY_APPROVAL_TIMEOUT_MS)
28
+ }).strict();
29
+ function encodePolicyApproval(approval) {
30
+ return JSON.stringify(
31
+ policyApprovalSchema.parse({
32
+ protocol: PITA_POLICY_APPROVAL_PROTOCOL,
33
+ ...approval,
34
+ toolName: sanitizeApprovalDisplayText(approval.toolName),
35
+ summary: sanitizeApprovalDisplayText(approval.summary)
36
+ })
37
+ );
38
+ }
39
+ function decodePolicyApproval(title, message) {
40
+ if (title !== PITA_POLICY_APPROVAL_TITLE || message === void 0) return null;
41
+ try {
42
+ const parsed = policyApprovalSchema.safeParse(JSON.parse(message));
43
+ if (!parsed.success) return null;
44
+ const normalized = policyApprovalSchema.safeParse({
45
+ ...parsed.data,
46
+ toolName: sanitizeApprovalDisplayText(parsed.data.toolName),
47
+ summary: sanitizeApprovalDisplayText(parsed.data.summary)
48
+ });
49
+ return normalized.success ? normalized.data : null;
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+ export {
55
+ MAX_POLICY_APPROVAL_SUMMARY_CHARS,
56
+ MAX_POLICY_APPROVAL_TIMEOUT_MS,
57
+ PITA_POLICY_APPROVAL_PROTOCOL,
58
+ PITA_POLICY_APPROVAL_TITLE,
59
+ decodePolicyApproval,
60
+ encodePolicyApproval,
61
+ sanitizeApprovalDisplayText
62
+ };
@@ -0,0 +1,59 @@
1
+ // packages/pi-bridge/src/resolve.ts
2
+ import { existsSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { PiBridgeError } from "./errors.js";
6
+ var PI_EXECUTABLE_ENV_VAR = "PITA_PI_EXECUTABLE";
7
+ var PI_PACKAGE_NAME = "@earendil-works/pi-coding-agent";
8
+ function defaultResolveModulePath(specifier) {
9
+ return fileURLToPath(import.meta.resolve(specifier));
10
+ }
11
+ function resolvePiInvocation(options = {}) {
12
+ const env = options.env ?? process.env;
13
+ const fileExists = options.fileExists ?? existsSync;
14
+ const fromEnv = env[PI_EXECUTABLE_ENV_VAR];
15
+ if (fromEnv !== void 0 && fromEnv.trim() !== "") {
16
+ const executable = fromEnv.trim();
17
+ if (!fileExists(executable)) {
18
+ throw new PiBridgeError(
19
+ "PI_EXECUTABLE_NOT_FOUND",
20
+ `${PI_EXECUTABLE_ENV_VAR} points to ${executable}, but no file exists there.`,
21
+ { details: { executable } }
22
+ );
23
+ }
24
+ return {
25
+ executable,
26
+ prependArgs: [],
27
+ source: "env",
28
+ description: `${PI_EXECUTABLE_ENV_VAR} (${executable})`
29
+ };
30
+ }
31
+ const resolveModulePath = options.resolveModulePath ?? defaultResolveModulePath;
32
+ let entryPath = null;
33
+ try {
34
+ entryPath = resolveModulePath(PI_PACKAGE_NAME);
35
+ } catch {
36
+ entryPath = null;
37
+ }
38
+ if (entryPath !== null) {
39
+ const cliPath = path.join(path.dirname(entryPath), "cli.js");
40
+ if (fileExists(cliPath)) {
41
+ return {
42
+ executable: options.nodeExecutable ?? process.execPath,
43
+ prependArgs: [cliPath],
44
+ source: "workspace",
45
+ description: `workspace-local Pi (${cliPath})`
46
+ };
47
+ }
48
+ }
49
+ return {
50
+ executable: "pi",
51
+ prependArgs: [],
52
+ source: "path",
53
+ description: "pi on PATH"
54
+ };
55
+ }
56
+ export {
57
+ PI_EXECUTABLE_ENV_VAR,
58
+ resolvePiInvocation
59
+ };
@@ -0,0 +1,23 @@
1
+ // packages/pi-bridge/src/resources-child.mjs
2
+ import process from "node:process";
3
+ import { inspectPiConfigurationInProcess } from "./resources.js";
4
+ var MAX_REQUEST_BYTES = 16 * 1024;
5
+ var input = "";
6
+ process.stdin.setEncoding("utf8");
7
+ for await (const chunk of process.stdin) {
8
+ input += chunk;
9
+ if (Buffer.byteLength(input, "utf8") > MAX_REQUEST_BYTES) {
10
+ process.exitCode = 1;
11
+ break;
12
+ }
13
+ }
14
+ if (process.exitCode !== 1) {
15
+ try {
16
+ const request = JSON.parse(input);
17
+ if (typeof request?.cwd !== "string") throw new Error("missing cwd");
18
+ const result = await inspectPiConfigurationInProcess({ cwd: request.cwd });
19
+ process.stdout.write(JSON.stringify(result));
20
+ } catch {
21
+ process.exitCode = 1;
22
+ }
23
+ }