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,1863 @@
1
+ // packages/protocol/src/index.ts
2
+ import { z } from "zod";
3
+ var PROTOCOL_VERSION = "protocol-v1";
4
+ var WORKSPACE_SCHEMA_VERSION = 5;
5
+ var taskIdSchema = z.string().min(1);
6
+ var nonNegativeInt = z.number().int().min(0);
7
+ var runtimeModelInfoSchema = z.object({
8
+ provider: z.string().min(1).max(200),
9
+ id: z.string().min(1).max(500),
10
+ name: z.string().min(1).max(500)
11
+ }).strict();
12
+ var runtimeStatusPayloadSchema = z.object({
13
+ state: z.enum(["starting", "ready", "needs_auth", "exited", "failed"]),
14
+ model: runtimeModelInfoSchema.nullable().optional(),
15
+ detail: z.string().optional()
16
+ });
17
+ var taskApprovalSchema = z.object({
18
+ approvalId: z.uuid(),
19
+ /** Pi's identity for the exact intercepted call this decision covers. */
20
+ toolCallId: z.string().min(1).max(500),
21
+ toolName: z.string().min(1).max(200),
22
+ summary: z.string().min(1).max(1e5),
23
+ requestedAtMs: z.number().int().nonnegative(),
24
+ expiresAtMs: z.number().int().positive()
25
+ }).strict();
26
+ var approvalRequestedPayloadSchema = taskApprovalSchema;
27
+ var approvalResolvedPayloadSchema = z.object({
28
+ approvalId: z.uuid(),
29
+ resolution: z.enum(["approved", "denied", "timeout", "superseded"])
30
+ });
31
+ var agentActivityPayloadSchema = z.object({
32
+ phase: z.enum(["agent-start", "agent-end", "agent-settled", "turn-start", "turn-end"])
33
+ });
34
+ var logPayloadSchema = z.object({
35
+ level: z.enum(["info", "warning", "error"]),
36
+ message: z.string()
37
+ });
38
+ var assistantDeltaPayloadSchema = z.object({
39
+ text: z.string()
40
+ });
41
+ var toolActivitySchema = z.object({
42
+ toolCallId: z.string().min(1).max(500),
43
+ name: z.string().min(1).max(200),
44
+ status: z.enum(["queued", "running", "succeeded", "failed", "incomplete"]),
45
+ input: z.string().max(2e4),
46
+ output: z.string().max(2e4),
47
+ inputOmittedChars: nonNegativeInt,
48
+ outputOmittedChars: nonNegativeInt,
49
+ path: z.string().max(4096).optional()
50
+ }).strict();
51
+ var envelopeBase = {
52
+ protocol: z.literal(PROTOCOL_VERSION),
53
+ taskId: taskIdSchema,
54
+ runtimeGeneration: nonNegativeInt,
55
+ sequence: nonNegativeInt,
56
+ timestamp: nonNegativeInt
57
+ };
58
+ var daemonEventSchema = z.discriminatedUnion("type", [
59
+ z.object({
60
+ ...envelopeBase,
61
+ type: z.literal("runtime-status"),
62
+ payload: runtimeStatusPayloadSchema
63
+ }),
64
+ z.object({
65
+ ...envelopeBase,
66
+ type: z.literal("approval-requested"),
67
+ payload: approvalRequestedPayloadSchema
68
+ }),
69
+ z.object({
70
+ ...envelopeBase,
71
+ type: z.literal("approval-resolved"),
72
+ payload: approvalResolvedPayloadSchema
73
+ }),
74
+ z.object({
75
+ ...envelopeBase,
76
+ type: z.literal("agent-activity"),
77
+ payload: agentActivityPayloadSchema
78
+ }),
79
+ z.object({
80
+ ...envelopeBase,
81
+ type: z.literal("log"),
82
+ payload: logPayloadSchema
83
+ }),
84
+ z.object({
85
+ ...envelopeBase,
86
+ type: z.literal("assistant-delta"),
87
+ payload: assistantDeltaPayloadSchema
88
+ }),
89
+ z.object({
90
+ ...envelopeBase,
91
+ type: z.literal("tool-activity"),
92
+ payload: toolActivitySchema
93
+ })
94
+ ]);
95
+ function parseDaemonEvent(value) {
96
+ return daemonEventSchema.parse(value);
97
+ }
98
+ function safeParseDaemonEvent(value) {
99
+ return daemonEventSchema.safeParse(value);
100
+ }
101
+ function createDaemonEventFactory(init) {
102
+ const now = init.now ?? Date.now;
103
+ let sequence = 0;
104
+ return (type, payload) => parseDaemonEvent({
105
+ protocol: PROTOCOL_VERSION,
106
+ taskId: init.taskId,
107
+ runtimeGeneration: init.runtimeGeneration,
108
+ sequence: sequence++,
109
+ timestamp: now(),
110
+ type,
111
+ payload
112
+ });
113
+ }
114
+ var serviceSessionSchema = z.object({
115
+ protocol: z.literal(PROTOCOL_VERSION),
116
+ type: z.literal("service-session"),
117
+ authenticated: z.literal(true),
118
+ /** Unix epoch milliseconds after which the session is no longer valid. */
119
+ sessionExpiresAt: nonNegativeInt
120
+ });
121
+ function parseServiceSession(value) {
122
+ return serviceSessionSchema.parse(value);
123
+ }
124
+ function safeParseServiceSession(value) {
125
+ return serviceSessionSchema.safeParse(value);
126
+ }
127
+ var serverControlMessageSchema = z.discriminatedUnion("type", [
128
+ z.object({
129
+ protocol: z.literal(PROTOCOL_VERSION),
130
+ type: z.literal("service-ready"),
131
+ /** Unix epoch milliseconds at which the daemon accepted the channel. */
132
+ timestamp: nonNegativeInt,
133
+ /** Expiry of the session that authenticated this channel. */
134
+ sessionExpiresAt: nonNegativeInt
135
+ })
136
+ ]);
137
+ function parseServerControlMessage(value) {
138
+ return serverControlMessageSchema.parse(value);
139
+ }
140
+ function safeParseServerControlMessage(value) {
141
+ return serverControlMessageSchema.safeParse(value);
142
+ }
143
+ var sessionEntryIdSchema = z.string().min(1).max(500);
144
+ var MAX_SESSION_TRANSCRIPT_ITEMS = 200;
145
+ var MAX_SESSION_TRANSCRIPT_TEXT_CHARS = 2e4;
146
+ var textTranscriptItemSchema = z.object({
147
+ /** Pi session entry id backing this item. */
148
+ id: sessionEntryIdSchema,
149
+ role: z.enum(["user", "assistant"]),
150
+ text: z.string().max(MAX_SESSION_TRANSCRIPT_TEXT_CHARS),
151
+ /** Characters omitted from the tail to keep reconnect payloads bounded. */
152
+ omittedChars: nonNegativeInt
153
+ }).strict();
154
+ var sessionTranscriptItemSchema = z.union([
155
+ textTranscriptItemSchema,
156
+ z.object({
157
+ id: sessionEntryIdSchema,
158
+ role: z.literal("tool"),
159
+ /** Pi tool-call identity backs this item, with its result attached. */
160
+ tool: toolActivitySchema
161
+ }).strict()
162
+ ]);
163
+ var sessionTreeNodeSchema = z.lazy(
164
+ () => z.object({
165
+ id: sessionEntryIdSchema,
166
+ parentId: sessionEntryIdSchema.nullable(),
167
+ kind: z.string().min(1).max(100),
168
+ role: z.string().min(1).max(100).optional(),
169
+ label: z.string().max(500).optional(),
170
+ preview: z.string().max(200).optional(),
171
+ onActivePath: z.boolean(),
172
+ children: z.array(sessionTreeNodeSchema)
173
+ }).strict()
174
+ );
175
+ var sessionTreeSchema = z.object({
176
+ roots: z.array(sessionTreeNodeSchema),
177
+ leafId: sessionEntryIdSchema.nullable(),
178
+ /** The full tree remains in Pi; this projection hit its node/depth cap. */
179
+ truncated: z.boolean()
180
+ }).strict();
181
+ var taskRuntimeSnapshotSchema = z.object({
182
+ protocol: z.literal(PROTOCOL_VERSION),
183
+ type: z.literal("task-runtime-snapshot"),
184
+ taskId: taskIdSchema,
185
+ runtimeGeneration: nonNegativeInt,
186
+ /**
187
+ * Daemon event cursor at capture time. Live delivery resumes with the
188
+ * next sequence; null when this generation has produced no events.
189
+ */
190
+ lastSequence: nonNegativeInt.nullable(),
191
+ state: z.enum(["idle", "starting", "ready", "needs_auth", "exited", "failed"]),
192
+ working: z.boolean(),
193
+ approval: taskApprovalSchema.nullable(),
194
+ model: runtimeModelInfoSchema.nullable(),
195
+ detail: z.string().max(1e3).optional(),
196
+ /**
197
+ * Canonical conversation along Pi's active branch; null when Pi could not
198
+ * be consulted (no live runtime, or the runtime request failed).
199
+ */
200
+ transcript: z.array(sessionTranscriptItemSchema).max(MAX_SESSION_TRANSCRIPT_ITEMS).nullable(),
201
+ transcriptUnavailableReason: z.enum(["runtime-not-live", "pi-request-failed"]).optional(),
202
+ /** Older transcript items omitted from this bounded snapshot. */
203
+ omittedTranscriptItems: nonNegativeInt,
204
+ sessionTree: sessionTreeSchema.nullable(),
205
+ /**
206
+ * True when non-canonical in-flight output (a still-streaming turn) could
207
+ * not be reconstructed; the UI must surface an explicit gap marker rather
208
+ * than silently joining partial streams.
209
+ */
210
+ inFlightGap: z.boolean()
211
+ }).strict();
212
+ function parseTaskRuntimeSnapshot(value) {
213
+ return taskRuntimeSnapshotSchema.parse(value);
214
+ }
215
+ var taskEventsReplaySchema = z.object({
216
+ protocol: z.literal(PROTOCOL_VERSION),
217
+ type: z.literal("task-events-replay"),
218
+ taskId: taskIdSchema,
219
+ runtimeGeneration: nonNegativeInt,
220
+ /** The client's presented cursor; replayed events begin after it. */
221
+ afterSequence: nonNegativeInt,
222
+ /** The daemon's cursor; equal to afterSequence when nothing was missed. */
223
+ lastSequence: nonNegativeInt
224
+ }).strict();
225
+ var taskEventsErrorSchema = z.object({
226
+ protocol: z.literal(PROTOCOL_VERSION),
227
+ type: z.literal("task-events-error"),
228
+ taskId: taskIdSchema,
229
+ code: z.enum(["task-not-found", "snapshot-failed"]),
230
+ message: z.string().min(1).max(500)
231
+ }).strict();
232
+ var taskSubscribeMessageSchema = z.object({
233
+ protocol: z.literal(PROTOCOL_VERSION),
234
+ type: z.literal("task-subscribe"),
235
+ taskId: z.uuid(),
236
+ cursor: z.object({
237
+ runtimeGeneration: nonNegativeInt,
238
+ lastSequence: nonNegativeInt
239
+ }).strict().nullable()
240
+ }).strict();
241
+ var clientMessageSchema = z.discriminatedUnion("type", [taskSubscribeMessageSchema]);
242
+ function safeParseClientMessage(value) {
243
+ return clientMessageSchema.safeParse(value);
244
+ }
245
+ var serverMessageSchema = z.union([
246
+ serverControlMessageSchema,
247
+ daemonEventSchema,
248
+ taskRuntimeSnapshotSchema,
249
+ taskEventsReplaySchema,
250
+ taskEventsErrorSchema
251
+ ]);
252
+ function parseServerMessage(value) {
253
+ return serverMessageSchema.parse(value);
254
+ }
255
+ function safeParseServerMessage(value) {
256
+ return serverMessageSchema.safeParse(value);
257
+ }
258
+ var absolutePathSchema = z.string().min(1).max(4096).startsWith("/");
259
+ var gitCommitSchema = z.string().regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/);
260
+ var entityIdSchema = z.uuid();
261
+ var TASK_VALIDATION_MAX_COMMAND_CHARS = 4096;
262
+ var TASK_VALIDATION_MAX_OUTPUT_CHARS = 1e5;
263
+ var TASK_VALIDATION_MAX_DURATION_MS = 6e5;
264
+ var taskValidationCommandSchema = z.string().min(1).max(TASK_VALIDATION_MAX_COMMAND_CHARS).refine(
265
+ (value) => value.trim().length > 0,
266
+ "Validation commands must contain a non-whitespace character."
267
+ ).refine(
268
+ // Newlines and tabs are legitimate in multi-line commands. Reject every
269
+ // other Unicode control/format character, including bidi overrides, so
270
+ // the exact runnable command remains visually trustworthy without being
271
+ // trimmed, escaped, or otherwise rewritten.
272
+ (value) => [...value].every(
273
+ (character) => character === "\n" || character === " " || !/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}\p{Cs}\p{Default_Ignorable_Code_Point}]/u.test(character) && (!new RegExp("\\p{Zs}", "u").test(character) || character === " ") && !/[\u115F\u1160\u2800\u3164\uFFA0]/u.test(character)
274
+ ),
275
+ "Validation commands must not contain invisible control, formatting, separator, or filler characters other than newlines, tabs, and ordinary spaces."
276
+ );
277
+ var runTaskValidationRequestSchema = z.object({ command: taskValidationCommandSchema }).strict();
278
+ var worktreeStateFingerprintSchema = z.string().regex(/^[0-9a-f]{64}$/);
279
+ var taskValidationResultSchema = z.object({
280
+ /** Exact validated user-authored command, preserved verbatim. */
281
+ command: taskValidationCommandSchema,
282
+ outcome: z.enum(["passed", "failed", "timed-out", "error"]),
283
+ /** Shell exit code, when the process exited on its own. */
284
+ exitCode: z.number().int().min(0).max(255).nullable(),
285
+ /** Terminating signal name, when the process was killed. */
286
+ signal: z.string().min(1).max(20).nullable(),
287
+ /** Combined stdout/stderr with control characters visibly escaped. */
288
+ output: z.string().max(TASK_VALIDATION_MAX_OUTPUT_CHARS),
289
+ /** True when captured output was cut at the display bound. */
290
+ outputTruncated: z.boolean(),
291
+ /** Characters dropped from the output tail (reported, never silent). */
292
+ omittedOutputChars: nonNegativeInt,
293
+ startedAtMs: nonNegativeInt,
294
+ finishedAtMs: nonNegativeInt,
295
+ /** Worktree-state fingerprint captured immediately before the run. */
296
+ stateFingerprint: worktreeStateFingerprintSchema
297
+ }).strict().superRefine((result, context) => {
298
+ if (result.finishedAtMs < result.startedAtMs) {
299
+ context.addIssue({
300
+ code: "custom",
301
+ path: ["finishedAtMs"],
302
+ message: "A validation cannot finish before it started."
303
+ });
304
+ }
305
+ if (result.outcome === "passed" && (result.exitCode !== 0 || result.signal !== null)) {
306
+ context.addIssue({
307
+ code: "custom",
308
+ path: ["outcome"],
309
+ message: "A passed validation requires exit code 0 and no signal."
310
+ });
311
+ }
312
+ if (result.outcome === "failed" && (result.exitCode === null || result.exitCode === 0) && result.signal === null) {
313
+ context.addIssue({
314
+ code: "custom",
315
+ path: ["outcome"],
316
+ message: "A failed validation requires a nonzero exit code or a signal."
317
+ });
318
+ }
319
+ if (result.outcome === "error" && (result.exitCode !== null || result.signal !== null)) {
320
+ context.addIssue({
321
+ code: "custom",
322
+ path: ["outcome"],
323
+ message: "An error outcome carries no exit code or signal."
324
+ });
325
+ }
326
+ if (result.exitCode !== null && result.signal !== null) {
327
+ context.addIssue({
328
+ code: "custom",
329
+ path: ["signal"],
330
+ message: "A process exits with either a code or a signal, never both."
331
+ });
332
+ }
333
+ if (result.outputTruncated !== result.omittedOutputChars > 0) {
334
+ context.addIssue({
335
+ code: "custom",
336
+ path: ["outputTruncated"],
337
+ message: "Truncation state and omitted character count must agree."
338
+ });
339
+ }
340
+ });
341
+ var taskValidationSchema = z.object({
342
+ protocol: z.literal(PROTOCOL_VERSION),
343
+ type: z.literal("task-validation"),
344
+ taskId: entityIdSchema,
345
+ result: taskValidationResultSchema.nullable(),
346
+ /** True while a validation process is executing for this task. */
347
+ running: z.boolean(),
348
+ /**
349
+ * True when the result's pre-run fingerprint no longer matches the live
350
+ * worktree state (or that state could not be captured). A stale result
351
+ * must never be presented as current.
352
+ */
353
+ stale: z.boolean(),
354
+ /** Unix epoch milliseconds when the daemon derived this status. */
355
+ capturedAtMs: nonNegativeInt
356
+ }).strict().superRefine((validation, context) => {
357
+ if (validation.result === null && validation.stale) {
358
+ context.addIssue({
359
+ code: "custom",
360
+ path: ["stale"],
361
+ message: "Only a recorded result can be stale."
362
+ });
363
+ }
364
+ });
365
+ var taskValidationErrorSchema = z.object({
366
+ protocol: z.literal(PROTOCOL_VERSION),
367
+ type: z.literal("task-validation-error"),
368
+ code: z.enum([
369
+ "invalid-request",
370
+ "task-not-found",
371
+ "task-not-ready",
372
+ "validation-running",
373
+ "commit-in-progress",
374
+ "merge-in-progress",
375
+ "archive-in-progress",
376
+ "agent-working",
377
+ "validation-failed",
378
+ "internal-error"
379
+ ]),
380
+ message: z.string().min(1).max(500)
381
+ }).strict();
382
+ function parseTaskValidation(value) {
383
+ return taskValidationSchema.parse(value);
384
+ }
385
+ function safeParseTaskValidation(value) {
386
+ return taskValidationSchema.safeParse(value);
387
+ }
388
+ var taskMergeModeSchema = z.enum(["fast-forward", "merge-commit"]);
389
+ var repositoryPendingMergeSchema = z.object({
390
+ taskId: entityIdSchema,
391
+ preflightId: entityIdSchema,
392
+ mode: taskMergeModeSchema,
393
+ targetBranch: z.string().min(1).max(1024),
394
+ previousTargetHead: gitCommitSchema,
395
+ newTargetHead: gitCommitSchema,
396
+ recordedAtMs: nonNegativeInt
397
+ }).strict().refine((record) => record.previousTargetHead !== record.newTargetHead, {
398
+ message: "A pending merge must advance the target branch.",
399
+ path: ["newTargetHead"]
400
+ });
401
+ var repositoryRecordSchema = z.object({
402
+ id: entityIdSchema,
403
+ rootPath: absolutePathSchema,
404
+ commonDir: absolutePathSchema,
405
+ displayName: z.string().min(1).max(255),
406
+ headCommit: gitCommitSchema,
407
+ branch: z.string().min(1).max(1024).nullable(),
408
+ createdAtMs: nonNegativeInt,
409
+ /** Interrupted-merge intent awaiting explicit recovery; schema v4. */
410
+ pendingMerge: repositoryPendingMergeSchema.optional()
411
+ }).strict();
412
+ var worktreeRecoveryFactsSchema = z.object({
413
+ branch: z.string().min(1).max(1024),
414
+ worktreePath: absolutePathSchema,
415
+ baseCommit: gitCommitSchema,
416
+ branchExists: z.boolean().nullable(),
417
+ targetRegisteredAsWorktree: z.boolean().nullable(),
418
+ targetExistsOnDisk: z.boolean().nullable()
419
+ }).strict();
420
+ var taskRecordBase = {
421
+ id: entityIdSchema,
422
+ repositoryId: entityIdSchema,
423
+ title: z.string().min(1).max(200),
424
+ branch: z.string().min(1).max(1024),
425
+ worktreePath: absolutePathSchema,
426
+ baseCommit: gitCommitSchema,
427
+ createdAtMs: nonNegativeInt
428
+ };
429
+ var taskReadyMetadata = {
430
+ /** Exact Pi-owned session file associated with this task. */
431
+ piSessionFile: absolutePathSchema.optional(),
432
+ /** Durable floor for runtime generations; never decreases or reuses. */
433
+ runtimeGeneration: nonNegativeInt.optional(),
434
+ /** Latest completed validation run; running records are never persisted. */
435
+ validation: taskValidationResultSchema.optional()
436
+ };
437
+ var taskFailureSchema = z.object({
438
+ code: z.string().min(1).max(100),
439
+ message: z.string().min(1).max(500),
440
+ recoveryFacts: worktreeRecoveryFactsSchema.optional()
441
+ }).strict();
442
+ var runtimeProcessRecordSchema = z.object({
443
+ runtimeGeneration: z.number().int().positive(),
444
+ pid: z.number().int().positive(),
445
+ processGroupId: z.number().int().positive(),
446
+ sessionId: z.number().int().positive(),
447
+ /** Boot identity stored separately so a reboot can safely retire a stale lease. */
448
+ bootFingerprint: z.string().regex(/^[0-9a-f]{64}$/).optional(),
449
+ startFingerprint: z.string().regex(/^[0-9a-f]{64}$/),
450
+ recordedAtMs: nonNegativeInt
451
+ }).strict().refine((record) => record.pid === record.processGroupId && record.pid === record.sessionId, {
452
+ message: "The supervised Pi process must lead its dedicated process group and session.",
453
+ path: ["processGroupId"]
454
+ });
455
+ var taskRecordSchema = z.discriminatedUnion("status", [
456
+ z.object({ ...taskRecordBase, status: z.literal("provisioning") }).strict(),
457
+ z.object({
458
+ ...taskRecordBase,
459
+ status: z.literal("ready"),
460
+ ...taskReadyMetadata,
461
+ /** Crash-recovery lease for the currently supervised Pi process. */
462
+ runtimeProcess: runtimeProcessRecordSchema.optional()
463
+ }).strict().refine(
464
+ (task) => task.runtimeProcess === void 0 || task.runtimeProcess.runtimeGeneration === task.runtimeGeneration,
465
+ {
466
+ message: "Runtime process ownership must match the task's current generation.",
467
+ path: ["runtimeProcess", "runtimeGeneration"]
468
+ }
469
+ ),
470
+ z.object({
471
+ ...taskRecordBase,
472
+ status: z.literal("failed"),
473
+ failure: taskFailureSchema
474
+ }).strict(),
475
+ z.object({
476
+ ...taskRecordBase,
477
+ status: z.literal("archived"),
478
+ ...taskReadyMetadata,
479
+ archivedAtMs: nonNegativeInt
480
+ }).strict()
481
+ ]);
482
+ var workspaceStateSchema = z.object({
483
+ schemaVersion: z.literal(WORKSPACE_SCHEMA_VERSION),
484
+ repositories: z.array(repositoryRecordSchema),
485
+ tasks: z.array(taskRecordSchema)
486
+ }).strict().superRefine((state, context) => {
487
+ const repositoryIds = /* @__PURE__ */ new Set();
488
+ const repositoryRoots = /* @__PURE__ */ new Set();
489
+ const repositoryCommonDirs = /* @__PURE__ */ new Set();
490
+ for (const [index, repository] of state.repositories.entries()) {
491
+ if (repositoryIds.has(repository.id)) {
492
+ context.addIssue({
493
+ code: "custom",
494
+ message: "Repository ids must be unique.",
495
+ path: ["repositories", index, "id"]
496
+ });
497
+ }
498
+ repositoryIds.add(repository.id);
499
+ if (repositoryRoots.has(repository.rootPath)) {
500
+ context.addIssue({
501
+ code: "custom",
502
+ message: "Repository roots must be unique.",
503
+ path: ["repositories", index, "rootPath"]
504
+ });
505
+ }
506
+ repositoryRoots.add(repository.rootPath);
507
+ if (repositoryCommonDirs.has(repository.commonDir)) {
508
+ context.addIssue({
509
+ code: "custom",
510
+ message: "Repository common Git directories must be unique.",
511
+ path: ["repositories", index, "commonDir"]
512
+ });
513
+ }
514
+ repositoryCommonDirs.add(repository.commonDir);
515
+ }
516
+ const taskIds = /* @__PURE__ */ new Set();
517
+ const tasksById = /* @__PURE__ */ new Map();
518
+ const taskWorktreePaths = /* @__PURE__ */ new Set();
519
+ const taskBranches = /* @__PURE__ */ new Set();
520
+ for (const [index, task] of state.tasks.entries()) {
521
+ if (taskIds.has(task.id)) {
522
+ context.addIssue({
523
+ code: "custom",
524
+ message: "Task ids must be unique.",
525
+ path: ["tasks", index, "id"]
526
+ });
527
+ }
528
+ taskIds.add(task.id);
529
+ tasksById.set(task.id, task);
530
+ if (!repositoryIds.has(task.repositoryId)) {
531
+ context.addIssue({
532
+ code: "custom",
533
+ message: "Every task must reference a registered repository.",
534
+ path: ["tasks", index, "repositoryId"]
535
+ });
536
+ }
537
+ if (taskWorktreePaths.has(task.worktreePath)) {
538
+ context.addIssue({
539
+ code: "custom",
540
+ message: "Task worktree paths must be unique.",
541
+ path: ["tasks", index, "worktreePath"]
542
+ });
543
+ }
544
+ taskWorktreePaths.add(task.worktreePath);
545
+ const branchKey = `${task.repositoryId}\0${task.branch}`;
546
+ if (taskBranches.has(branchKey)) {
547
+ context.addIssue({
548
+ code: "custom",
549
+ message: "Task branches must be unique within a repository.",
550
+ path: ["tasks", index, "branch"]
551
+ });
552
+ }
553
+ taskBranches.add(branchKey);
554
+ }
555
+ for (const [index, repository] of state.repositories.entries()) {
556
+ const pendingTaskId = repository.pendingMerge?.taskId;
557
+ if (pendingTaskId === void 0) continue;
558
+ const pendingTask = tasksById.get(pendingTaskId);
559
+ if (pendingTask === void 0 || pendingTask.repositoryId !== repository.id || pendingTask.status !== "ready") {
560
+ context.addIssue({
561
+ code: "custom",
562
+ message: "A pending merge must reference a ready task in its repository.",
563
+ path: ["repositories", index, "pendingMerge", "taskId"]
564
+ });
565
+ }
566
+ }
567
+ });
568
+ var preMergeRepositorySchema = repositoryRecordSchema.refine(
569
+ (repository) => repository.pendingMerge === void 0,
570
+ "Workspace schemas before v4 cannot contain pending merge records."
571
+ );
572
+ var preArchiveTaskSchema = taskRecordSchema.refine(
573
+ (task) => task.status !== "archived",
574
+ "Workspace schemas before v5 cannot contain archived tasks."
575
+ );
576
+ var workspaceStateV1Schema = z.object({
577
+ schemaVersion: z.literal(1),
578
+ repositories: z.array(preMergeRepositorySchema),
579
+ tasks: z.array(
580
+ preArchiveTaskSchema.refine(
581
+ (task) => task.status !== "ready" || task.validation === void 0,
582
+ "Workspace schema v1 cannot contain validation results."
583
+ )
584
+ )
585
+ }).strict();
586
+ var workspaceStateV2Schema = z.object({
587
+ schemaVersion: z.literal(2),
588
+ repositories: z.array(preMergeRepositorySchema),
589
+ tasks: z.array(
590
+ preArchiveTaskSchema.refine(
591
+ (task) => task.status !== "ready" || task.validation === void 0,
592
+ "Workspace schema v2 cannot contain validation results."
593
+ )
594
+ )
595
+ }).strict();
596
+ var workspaceStateV3Schema = z.object({
597
+ schemaVersion: z.literal(3),
598
+ repositories: z.array(preMergeRepositorySchema),
599
+ tasks: z.array(preArchiveTaskSchema)
600
+ }).strict();
601
+ var workspaceStateV4Schema = z.object({
602
+ schemaVersion: z.literal(4),
603
+ repositories: z.array(repositoryRecordSchema),
604
+ tasks: z.array(preArchiveTaskSchema)
605
+ }).strict();
606
+ var workspaceSnapshotSchema = z.object({
607
+ protocol: z.literal(PROTOCOL_VERSION),
608
+ type: z.literal("workspace-snapshot"),
609
+ state: workspaceStateSchema
610
+ }).strict();
611
+ var registerRepositoryRequestSchema = z.object({ path: absolutePathSchema }).strict();
612
+ var createTaskRequestSchema = z.object({
613
+ repositoryId: entityIdSchema,
614
+ title: z.string().trim().min(1).max(200),
615
+ baseRef: z.string().trim().min(1).max(1024)
616
+ }).strict();
617
+ var taskArchiveRequestSchema = z.object({}).strict();
618
+ var workspaceErrorSchema = z.object({
619
+ protocol: z.literal(PROTOCOL_VERSION),
620
+ type: z.literal("workspace-error"),
621
+ code: z.enum([
622
+ "invalid-request",
623
+ "repository-already-registered",
624
+ "repository-not-found",
625
+ "task-not-found",
626
+ "task-not-ready",
627
+ "task-busy",
628
+ "task-recovery-required",
629
+ "git-preflight-failed",
630
+ "git-conflict",
631
+ "git-incomplete",
632
+ "workspace-unavailable",
633
+ "internal-error"
634
+ ]),
635
+ message: z.string().min(1).max(500)
636
+ }).strict();
637
+ function parseWorkspaceSnapshot(value) {
638
+ return workspaceSnapshotSchema.parse(value);
639
+ }
640
+ function safeParseWorkspaceSnapshot(value) {
641
+ return workspaceSnapshotSchema.safeParse(value);
642
+ }
643
+ var TASK_REVIEW_MAX_FILES = 200;
644
+ var TASK_REVIEW_MAX_FILE_DIFF_CHARS = 2e4;
645
+ var TASK_REVIEW_MAX_TOTAL_DIFF_CHARS = 2e5;
646
+ var reviewPathSchema = z.string().min(1).max(4096);
647
+ var taskReviewFileSchema = z.object({
648
+ /** Opaque stable identity from the unsanitized Git status record. */
649
+ entryId: z.string().regex(/^[0-9a-f]{64}$/),
650
+ /** Repository-relative path with control characters visibly escaped. */
651
+ path: reviewPathSchema,
652
+ /** Rename/copy source path, when Git recorded one. */
653
+ previousPath: reviewPathSchema.nullable(),
654
+ kind: z.enum([
655
+ "added",
656
+ "modified",
657
+ "deleted",
658
+ "renamed",
659
+ "copied",
660
+ "type-changed",
661
+ "untracked",
662
+ "conflicted"
663
+ ]),
664
+ /** The index differs from HEAD for this path. */
665
+ staged: z.boolean(),
666
+ /** The working tree differs from the index for this path. */
667
+ unstaged: z.boolean(),
668
+ /** Git identified binary content; no text diff exists. */
669
+ binary: z.boolean(),
670
+ /**
671
+ * Bounded unified diff text, or null when `diffOmittedReason` explains
672
+ * its absence. May be empty when index and worktree states cancel out.
673
+ */
674
+ diff: z.string().max(TASK_REVIEW_MAX_FILE_DIFF_CHARS).nullable(),
675
+ /** True when the diff was cut at a per-file or total bound. */
676
+ diffTruncated: z.boolean(),
677
+ diffOmittedReason: z.enum(["binary", "too-large", "total-budget", "review-budget", "diff-failed"]).nullable()
678
+ }).strict().superRefine((file, context) => {
679
+ if (file.diff === null === (file.diffOmittedReason === null)) {
680
+ context.addIssue({
681
+ code: "custom",
682
+ path: ["diff"],
683
+ message: "A missing diff requires exactly one omission reason."
684
+ });
685
+ }
686
+ if (file.binary !== (file.diffOmittedReason === "binary")) {
687
+ context.addIssue({
688
+ code: "custom",
689
+ path: ["binary"],
690
+ message: "Binary state and binary omission reason must agree."
691
+ });
692
+ }
693
+ if (file.diffTruncated && file.diff === null) {
694
+ context.addIssue({
695
+ code: "custom",
696
+ path: ["diffTruncated"],
697
+ message: "Only a present text diff can be truncated."
698
+ });
699
+ }
700
+ if ((file.kind === "renamed" || file.kind === "copied") !== (file.previousPath !== null)) {
701
+ context.addIssue({
702
+ code: "custom",
703
+ path: ["previousPath"],
704
+ message: "Only rename and copy records carry a previous path."
705
+ });
706
+ }
707
+ });
708
+ var taskReviewSchema = z.object({
709
+ protocol: z.literal(PROTOCOL_VERSION),
710
+ type: z.literal("task-review"),
711
+ taskId: entityIdSchema,
712
+ /** Exact worktree HEAD commit the review was derived against. */
713
+ headCommit: gitCommitSchema,
714
+ /** Current worktree branch, or null when HEAD is detached. */
715
+ branch: z.string().min(1).max(1024).nullable(),
716
+ /** True when Git reported no staged, unstaged, or untracked changes. */
717
+ clean: z.boolean(),
718
+ files: z.array(taskReviewFileSchema).max(TASK_REVIEW_MAX_FILES),
719
+ /** Total Git status entries, including any beyond the file cap. */
720
+ totalChangedFiles: nonNegativeInt,
721
+ /** Git status entries dropped by the file cap (reported, never silent). */
722
+ omittedFiles: nonNegativeInt,
723
+ /** True when the total diff budget cut or suppressed at least one diff. */
724
+ totalDiffTruncated: z.boolean(),
725
+ /** Unix epoch milliseconds when the daemon derived these facts. */
726
+ capturedAtMs: nonNegativeInt
727
+ }).strict().superRefine((review, context) => {
728
+ if (review.totalChangedFiles !== review.files.length + review.omittedFiles) {
729
+ context.addIssue({
730
+ code: "custom",
731
+ path: ["totalChangedFiles"],
732
+ message: "Listed plus omitted entries must equal the total status entries."
733
+ });
734
+ }
735
+ if (review.clean !== (review.totalChangedFiles === 0)) {
736
+ context.addIssue({
737
+ code: "custom",
738
+ path: ["clean"],
739
+ message: "Clean state must exactly match an empty changed-file set."
740
+ });
741
+ }
742
+ const entryIds = /* @__PURE__ */ new Set();
743
+ for (const [index, file] of review.files.entries()) {
744
+ if (entryIds.has(file.entryId)) {
745
+ context.addIssue({
746
+ code: "custom",
747
+ path: ["files", index, "entryId"],
748
+ message: "Review entry identifiers must be unique."
749
+ });
750
+ }
751
+ entryIds.add(file.entryId);
752
+ }
753
+ let totalDiffChars = 0;
754
+ for (const file of review.files) totalDiffChars += file.diff?.length ?? 0;
755
+ if (totalDiffChars > TASK_REVIEW_MAX_TOTAL_DIFF_CHARS) {
756
+ context.addIssue({
757
+ code: "custom",
758
+ path: ["files"],
759
+ message: "Total diff content exceeds the task review payload bound."
760
+ });
761
+ }
762
+ const exhaustedReviewBudget = review.files.some(
763
+ (file) => file.diffOmittedReason === "total-budget" || file.diffOmittedReason === "review-budget"
764
+ );
765
+ if (exhaustedReviewBudget && !review.totalDiffTruncated) {
766
+ context.addIssue({
767
+ code: "custom",
768
+ path: ["totalDiffTruncated"],
769
+ message: "A review-level omission must mark the total diff truncated."
770
+ });
771
+ }
772
+ });
773
+ var taskReviewErrorSchema = z.object({
774
+ protocol: z.literal(PROTOCOL_VERSION),
775
+ type: z.literal("task-review-error"),
776
+ code: z.enum([
777
+ "invalid-request",
778
+ "task-not-found",
779
+ "task-not-ready",
780
+ "review-failed",
781
+ "internal-error"
782
+ ]),
783
+ message: z.string().min(1).max(500)
784
+ }).strict();
785
+ function parseTaskReview(value) {
786
+ return taskReviewSchema.parse(value);
787
+ }
788
+ function safeParseTaskReview(value) {
789
+ return taskReviewSchema.safeParse(value);
790
+ }
791
+ var TASK_COMMIT_MAX_MESSAGE_CHARS = 5e3;
792
+ var TASK_COMMIT_MAX_FILES = TASK_REVIEW_MAX_FILES;
793
+ var TASK_COMMIT_MAX_FILE_DIFF_CHARS = TASK_REVIEW_MAX_FILE_DIFF_CHARS;
794
+ var TASK_COMMIT_MAX_TOTAL_DIFF_CHARS = TASK_REVIEW_MAX_TOTAL_DIFF_CHARS;
795
+ var taskCommitMessageSchema = z.string().min(1).max(TASK_COMMIT_MAX_MESSAGE_CHARS).refine(
796
+ (value) => value.trim().length > 0,
797
+ "Commit messages must contain a non-whitespace character."
798
+ ).refine(
799
+ // Same visual-trust rule as validation commands: newlines and tabs are
800
+ // legitimate, every other invisible control/format/separator/filler
801
+ // character is rejected so the exact recorded message is trustworthy.
802
+ (value) => [...value].every(
803
+ (character) => character === "\n" || character === " " || !/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}\p{Cs}\p{Default_Ignorable_Code_Point}]/u.test(character) && (!new RegExp("\\p{Zs}", "u").test(character) || character === " ") && !/[\u115F\u1160\u2800\u3164\uFFA0]/u.test(character)
804
+ ),
805
+ "Commit messages must not contain invisible control, formatting, separator, or filler characters other than newlines, tabs, and ordinary spaces."
806
+ );
807
+ var taskCommitPreflightRequestSchema = z.object({ message: taskCommitMessageSchema }).strict();
808
+ var taskCommitFileSchema = z.object({
809
+ /** Opaque stable identity from the raw parent-vs-tree diff record. */
810
+ entryId: z.string().regex(/^[0-9a-f]{64}$/),
811
+ /** Repository-relative path with control characters visibly escaped. */
812
+ path: reviewPathSchema,
813
+ /** Rename/copy source path, when Git recorded one. */
814
+ previousPath: reviewPathSchema.nullable(),
815
+ kind: z.enum(["added", "modified", "deleted", "renamed", "copied", "type-changed"]),
816
+ /** Git identified binary content; no text diff exists. */
817
+ binary: z.boolean(),
818
+ /** Bounded unified diff between parent commit and frozen tree, or null. */
819
+ diff: z.string().max(TASK_COMMIT_MAX_FILE_DIFF_CHARS).nullable(),
820
+ diffTruncated: z.boolean(),
821
+ diffOmittedReason: z.enum(["binary", "too-large", "total-budget", "review-budget", "diff-failed"]).nullable()
822
+ }).strict().superRefine((file, context) => {
823
+ if (file.diff === null === (file.diffOmittedReason === null)) {
824
+ context.addIssue({
825
+ code: "custom",
826
+ path: ["diff"],
827
+ message: "A missing diff requires exactly one omission reason."
828
+ });
829
+ }
830
+ if (file.binary !== (file.diffOmittedReason === "binary")) {
831
+ context.addIssue({
832
+ code: "custom",
833
+ path: ["binary"],
834
+ message: "Binary state and binary omission reason must agree."
835
+ });
836
+ }
837
+ if (file.diffTruncated && file.diff === null) {
838
+ context.addIssue({
839
+ code: "custom",
840
+ path: ["diffTruncated"],
841
+ message: "Only a present text diff can be truncated."
842
+ });
843
+ }
844
+ if ((file.kind === "renamed" || file.kind === "copied") !== (file.previousPath !== null)) {
845
+ context.addIssue({
846
+ code: "custom",
847
+ path: ["previousPath"],
848
+ message: "Only rename and copy records carry a previous path."
849
+ });
850
+ }
851
+ });
852
+ var taskCommitValidationVerdictSchema = z.enum([
853
+ "current-passed",
854
+ "current-failed",
855
+ "stale",
856
+ "none"
857
+ ]);
858
+ var taskCommitPreflightSchema = z.object({
859
+ protocol: z.literal(PROTOCOL_VERSION),
860
+ type: z.literal("task-commit-preflight"),
861
+ taskId: entityIdSchema,
862
+ /** One-shot daemon-memory token; invalid after expiry, supersession, use, restart, or any state change. */
863
+ preflightId: entityIdSchema,
864
+ branch: z.string().min(1).max(1024),
865
+ /** Exact parent commit the frozen tree will be committed on top of. */
866
+ parentCommit: gitCommitSchema,
867
+ /** Frozen tree object; the published commit carries exactly this tree. */
868
+ treeOid: gitCommitSchema,
869
+ /** Exact user-authored commit message this approval covers. */
870
+ message: taskCommitMessageSchema,
871
+ /** Post-filter included changes, derived strictly from parent and tree. */
872
+ files: z.array(taskCommitFileSchema).max(TASK_COMMIT_MAX_FILES),
873
+ totalFiles: z.number().int().min(1),
874
+ omittedFiles: nonNegativeInt,
875
+ totalDiffTruncated: z.boolean(),
876
+ /** True when the commit includes content beyond the staged index. */
877
+ stagedDivergence: z.boolean(),
878
+ validationVerdict: taskCommitValidationVerdictSchema,
879
+ /** True for every verdict except current-passed. */
880
+ acknowledgementRequired: z.boolean(),
881
+ /** Ordinary commit hooks are always bypassed; fixed by design. */
882
+ hooksBypassed: z.literal(true),
883
+ createdAtMs: nonNegativeInt,
884
+ /** Unix epoch milliseconds after which this preflight cannot execute. */
885
+ expiresAtMs: z.number().int().positive()
886
+ }).strict().superRefine((preflight, context) => {
887
+ if (preflight.totalFiles !== preflight.files.length + preflight.omittedFiles) {
888
+ context.addIssue({
889
+ code: "custom",
890
+ path: ["totalFiles"],
891
+ message: "Listed plus omitted entries must equal the total change entries."
892
+ });
893
+ }
894
+ if (preflight.acknowledgementRequired !== (preflight.validationVerdict !== "current-passed")) {
895
+ context.addIssue({
896
+ code: "custom",
897
+ path: ["acknowledgementRequired"],
898
+ message: "Every verdict except current-passed requires acknowledgement."
899
+ });
900
+ }
901
+ if (preflight.expiresAtMs <= preflight.createdAtMs) {
902
+ context.addIssue({
903
+ code: "custom",
904
+ path: ["expiresAtMs"],
905
+ message: "A preflight must expire after it was created."
906
+ });
907
+ }
908
+ const entryIds = /* @__PURE__ */ new Set();
909
+ for (const [index, file] of preflight.files.entries()) {
910
+ if (entryIds.has(file.entryId)) {
911
+ context.addIssue({
912
+ code: "custom",
913
+ path: ["files", index, "entryId"],
914
+ message: "Commit entry identifiers must be unique."
915
+ });
916
+ }
917
+ entryIds.add(file.entryId);
918
+ }
919
+ let totalDiffChars = 0;
920
+ for (const file of preflight.files) totalDiffChars += file.diff?.length ?? 0;
921
+ if (totalDiffChars > TASK_COMMIT_MAX_TOTAL_DIFF_CHARS) {
922
+ context.addIssue({
923
+ code: "custom",
924
+ path: ["files"],
925
+ message: "Total diff content exceeds the commit preflight payload bound."
926
+ });
927
+ }
928
+ });
929
+ var executeTaskCommitRequestSchema = z.object({
930
+ preflightId: entityIdSchema,
931
+ /**
932
+ * Dedicated, explicit acknowledgement that the validation verdict shown
933
+ * at preflight was seen. Must be true for every verdict except
934
+ * current-passed; never implied by the approval click alone.
935
+ */
936
+ acknowledgeValidation: z.boolean()
937
+ }).strict();
938
+ var taskCommitResultSchema = z.object({
939
+ protocol: z.literal(PROTOCOL_VERSION),
940
+ type: z.literal("task-commit-result"),
941
+ taskId: entityIdSchema,
942
+ branch: z.string().min(1).max(1024),
943
+ /** Published commit; its tree provably equals the approved treeOid. */
944
+ commit: gitCommitSchema,
945
+ treeOid: gitCommitSchema,
946
+ parentCommit: gitCommitSchema,
947
+ /** True when commit.gpgSign was enabled and the commit was signed. */
948
+ signed: z.boolean(),
949
+ /**
950
+ * Honest post-publication facts: the commit exists on the branch even
951
+ * when these are false; nothing is rolled back or repaired destructively.
952
+ */
953
+ indexSynced: z.boolean(),
954
+ headVerified: z.boolean(),
955
+ capturedAtMs: nonNegativeInt
956
+ }).strict();
957
+ var taskCommitErrorSchema = z.object({
958
+ protocol: z.literal(PROTOCOL_VERSION),
959
+ type: z.literal("task-commit-error"),
960
+ code: z.enum([
961
+ "invalid-request",
962
+ "task-not-found",
963
+ "task-not-ready",
964
+ "commit-in-progress",
965
+ "merge-in-progress",
966
+ "archive-in-progress",
967
+ "validation-running",
968
+ "agent-working",
969
+ "commit-conflicted",
970
+ "nothing-to-commit",
971
+ "identity-missing",
972
+ "state-unsupported",
973
+ "preflight-not-found",
974
+ "preflight-outdated",
975
+ "acknowledgement-required",
976
+ "branch-moved",
977
+ "signing-failed",
978
+ "commit-failed",
979
+ "internal-error"
980
+ ]),
981
+ message: z.string().min(1).max(500)
982
+ }).strict();
983
+ function parseTaskCommitPreflight(value) {
984
+ return taskCommitPreflightSchema.parse(value);
985
+ }
986
+ function safeParseTaskCommitPreflight(value) {
987
+ return taskCommitPreflightSchema.safeParse(value);
988
+ }
989
+ function parseTaskCommitResult(value) {
990
+ return taskCommitResultSchema.parse(value);
991
+ }
992
+ function safeParseTaskCommitResult(value) {
993
+ return taskCommitResultSchema.safeParse(value);
994
+ }
995
+ var TASK_MERGE_MAX_FILES = TASK_REVIEW_MAX_FILES;
996
+ var TASK_MERGE_MAX_FILE_DIFF_CHARS = TASK_REVIEW_MAX_FILE_DIFF_CHARS;
997
+ var TASK_MERGE_MAX_TOTAL_DIFF_CHARS = TASK_REVIEW_MAX_TOTAL_DIFF_CHARS;
998
+ var TASK_MERGE_MAX_COMMIT_SUBJECTS = 20;
999
+ var TASK_MERGE_MAX_COMMIT_SUBJECT_CHARS = 200;
1000
+ var TASK_MERGE_MAX_CONFLICT_PATHS = 20;
1001
+ var branchNameSchema = z.string().min(1).max(1024);
1002
+ var mergeApprovalBranchSchema = branchNameSchema.refine(
1003
+ (value) => [...value].every(
1004
+ (character) => !/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}\p{Cs}\p{Default_Ignorable_Code_Point}]/u.test(character) && (!new RegExp("\\p{Zs}", "u").test(character) || character === " ") && !/[\u115F\u1160\u2800\u3164\uFFA0]/u.test(character)
1005
+ ),
1006
+ "Merge approval branch names must not contain invisible or formatting characters."
1007
+ );
1008
+ var taskMergePreflightRequestSchema = z.object({
1009
+ /** Optional merge-commit message; ignored for a fast-forward. */
1010
+ message: taskCommitMessageSchema.optional()
1011
+ }).strict();
1012
+ var taskMergePreflightSchema = z.object({
1013
+ protocol: z.literal(PROTOCOL_VERSION),
1014
+ type: z.literal("task-merge-preflight"),
1015
+ taskId: entityIdSchema,
1016
+ /** One-shot daemon-memory token; invalid after expiry, supersession, use, restart, or any state change on either side. */
1017
+ preflightId: entityIdSchema,
1018
+ mode: taskMergeModeSchema,
1019
+ taskBranch: mergeApprovalBranchSchema,
1020
+ /** Fixed registered target branch; never chosen by the browser. */
1021
+ targetBranch: mergeApprovalBranchSchema,
1022
+ /** Exact task-branch head this approval covers. */
1023
+ taskHead: gitCommitSchema,
1024
+ /** Exact target-branch head this approval covers. */
1025
+ targetHead: gitCommitSchema,
1026
+ /** Exact merge-result tree; the published target tree equals this OID. */
1027
+ mergedTree: gitCommitSchema,
1028
+ /** Effective merge-commit message; null exactly for a fast-forward. */
1029
+ mergeMessage: taskCommitMessageSchema.nullable(),
1030
+ /** Changes the merge publishes, derived strictly from targetHead → mergedTree. */
1031
+ files: z.array(taskCommitFileSchema).max(TASK_MERGE_MAX_FILES),
1032
+ totalFiles: nonNegativeInt,
1033
+ omittedFiles: nonNegativeInt,
1034
+ totalDiffTruncated: z.boolean(),
1035
+ /** Task commits not yet on the target branch. */
1036
+ commitCount: z.number().int().min(1),
1037
+ /** Escaped first-line subjects of the newest merged commits, bounded. */
1038
+ commitSubjects: z.array(z.string().min(1).max(TASK_MERGE_MAX_COMMIT_SUBJECT_CHARS)).max(TASK_MERGE_MAX_COMMIT_SUBJECTS),
1039
+ validationVerdict: taskCommitValidationVerdictSchema,
1040
+ /**
1041
+ * True when the published tree is exactly the validated task snapshot
1042
+ * (fast-forward). A merge commit's result tree was never validated, so
1043
+ * this is false for every merge commit.
1044
+ */
1045
+ validationCoversResult: z.boolean(),
1046
+ /** True unless a fast-forward is covered by a current passing validation. */
1047
+ acknowledgementRequired: z.boolean(),
1048
+ /** Ordinary merge hooks are always bypassed; fixed by design. */
1049
+ hooksBypassed: z.literal(true),
1050
+ createdAtMs: nonNegativeInt,
1051
+ /** Unix epoch milliseconds after which this preflight cannot execute. */
1052
+ expiresAtMs: z.number().int().positive()
1053
+ }).strict().superRefine((preflight, context) => {
1054
+ if (preflight.taskBranch === preflight.targetBranch) {
1055
+ context.addIssue({
1056
+ code: "custom",
1057
+ path: ["targetBranch"],
1058
+ message: "A task can never merge into its own branch."
1059
+ });
1060
+ }
1061
+ if (preflight.totalFiles !== preflight.files.length + preflight.omittedFiles) {
1062
+ context.addIssue({
1063
+ code: "custom",
1064
+ path: ["totalFiles"],
1065
+ message: "Listed plus omitted entries must equal the total change entries."
1066
+ });
1067
+ }
1068
+ if (preflight.mode === "fast-forward" !== (preflight.mergeMessage === null)) {
1069
+ context.addIssue({
1070
+ code: "custom",
1071
+ path: ["mergeMessage"],
1072
+ message: "Exactly the merge-commit mode carries an effective merge message."
1073
+ });
1074
+ }
1075
+ if (preflight.validationCoversResult !== (preflight.mode === "fast-forward")) {
1076
+ context.addIssue({
1077
+ code: "custom",
1078
+ path: ["validationCoversResult"],
1079
+ message: "Only a fast-forward publishes the validated task snapshot itself."
1080
+ });
1081
+ }
1082
+ if (preflight.acknowledgementRequired !== (preflight.validationVerdict !== "current-passed" || preflight.mode === "merge-commit")) {
1083
+ context.addIssue({
1084
+ code: "custom",
1085
+ path: ["acknowledgementRequired"],
1086
+ message: "Acknowledgement is required unless a fast-forward is covered by a current passing validation."
1087
+ });
1088
+ }
1089
+ if (preflight.commitSubjects.length > preflight.commitCount) {
1090
+ context.addIssue({
1091
+ code: "custom",
1092
+ path: ["commitSubjects"],
1093
+ message: "Listed commit subjects cannot exceed the merged commit count."
1094
+ });
1095
+ }
1096
+ if (preflight.expiresAtMs <= preflight.createdAtMs) {
1097
+ context.addIssue({
1098
+ code: "custom",
1099
+ path: ["expiresAtMs"],
1100
+ message: "A preflight must expire after it was created."
1101
+ });
1102
+ }
1103
+ const entryIds = /* @__PURE__ */ new Set();
1104
+ for (const [index, file] of preflight.files.entries()) {
1105
+ if (entryIds.has(file.entryId)) {
1106
+ context.addIssue({
1107
+ code: "custom",
1108
+ path: ["files", index, "entryId"],
1109
+ message: "Merge entry identifiers must be unique."
1110
+ });
1111
+ }
1112
+ entryIds.add(file.entryId);
1113
+ }
1114
+ let totalDiffChars = 0;
1115
+ for (const file of preflight.files) totalDiffChars += file.diff?.length ?? 0;
1116
+ if (totalDiffChars > TASK_MERGE_MAX_TOTAL_DIFF_CHARS) {
1117
+ context.addIssue({
1118
+ code: "custom",
1119
+ path: ["files"],
1120
+ message: "Total diff content exceeds the merge preflight payload bound."
1121
+ });
1122
+ }
1123
+ });
1124
+ var executeTaskMergeRequestSchema = z.object({
1125
+ preflightId: entityIdSchema,
1126
+ /**
1127
+ * Dedicated, explicit acknowledgement of the validation coverage shown at
1128
+ * preflight. Required for every preflight whose acknowledgementRequired
1129
+ * is true; never implied by the approval click alone.
1130
+ */
1131
+ acknowledgeValidation: z.boolean()
1132
+ }).strict();
1133
+ var taskMergeResultSchema = z.object({
1134
+ protocol: z.literal(PROTOCOL_VERSION),
1135
+ type: z.literal("task-merge-result"),
1136
+ taskId: entityIdSchema,
1137
+ mode: taskMergeModeSchema,
1138
+ taskBranch: mergeApprovalBranchSchema,
1139
+ targetBranch: mergeApprovalBranchSchema,
1140
+ /** Exact source tip whose commits and merge result were approved. */
1141
+ taskHead: gitCommitSchema,
1142
+ previousTargetHead: gitCommitSchema,
1143
+ /** Published target tip; its tree provably equals the approved mergedTree. */
1144
+ newTargetHead: gitCommitSchema,
1145
+ /** Created merge commit; null exactly for a fast-forward. */
1146
+ mergeCommit: gitCommitSchema.nullable(),
1147
+ treeOid: gitCommitSchema,
1148
+ /** True when commit.gpgSign was enabled and the merge commit was signed. */
1149
+ signed: z.boolean(),
1150
+ /**
1151
+ * Honest post-publication facts: the merge is on the target branch even
1152
+ * when these are false; nothing is rolled back or repaired destructively.
1153
+ * A false targetSynced leaves a durable pending-sync recovery state.
1154
+ */
1155
+ targetSynced: z.boolean(),
1156
+ targetHeadVerified: z.boolean(),
1157
+ capturedAtMs: nonNegativeInt
1158
+ }).strict().superRefine((result, context) => {
1159
+ if (result.mode === "merge-commit" !== (result.mergeCommit !== null)) {
1160
+ context.addIssue({
1161
+ code: "custom",
1162
+ path: ["mergeCommit"],
1163
+ message: "Exactly the merge-commit mode creates a new merge commit."
1164
+ });
1165
+ }
1166
+ if (result.mergeCommit !== null && result.mergeCommit !== result.newTargetHead) {
1167
+ context.addIssue({
1168
+ code: "custom",
1169
+ path: ["newTargetHead"],
1170
+ message: "A published merge commit is the new target tip."
1171
+ });
1172
+ }
1173
+ if (result.mode === "fast-forward" && result.signed) {
1174
+ context.addIssue({
1175
+ code: "custom",
1176
+ path: ["signed"],
1177
+ message: "A fast-forward creates no new object and is never signed by Webdesk."
1178
+ });
1179
+ }
1180
+ if (result.mode === "fast-forward" && result.newTargetHead !== result.taskHead) {
1181
+ context.addIssue({
1182
+ code: "custom",
1183
+ path: ["newTargetHead"],
1184
+ message: "A fast-forward publishes the exact approved task head."
1185
+ });
1186
+ }
1187
+ if (result.targetSynced && !result.targetHeadVerified) {
1188
+ context.addIssue({
1189
+ code: "custom",
1190
+ path: ["targetHeadVerified"],
1191
+ message: "A synced target checkout must also have its published head verified."
1192
+ });
1193
+ }
1194
+ });
1195
+ var taskMergeErrorSchema = z.object({
1196
+ protocol: z.literal(PROTOCOL_VERSION),
1197
+ type: z.literal("task-merge-error"),
1198
+ code: z.enum([
1199
+ "invalid-request",
1200
+ "task-not-found",
1201
+ "task-not-ready",
1202
+ "repository-not-found",
1203
+ "merge-in-progress",
1204
+ "commit-in-progress",
1205
+ "archive-in-progress",
1206
+ "validation-running",
1207
+ "agent-working",
1208
+ "task-dirty",
1209
+ "target-dirty",
1210
+ "target-unavailable",
1211
+ "target-mismatched",
1212
+ "wrong-base",
1213
+ "unrelated-histories",
1214
+ "already-merged",
1215
+ "nothing-to-merge",
1216
+ "merge-conflicted",
1217
+ "state-unsupported",
1218
+ "target-path-occupied",
1219
+ "identity-missing",
1220
+ "preflight-not-found",
1221
+ "preflight-outdated",
1222
+ "acknowledgement-required",
1223
+ "ref-cas-failed",
1224
+ "publication-unknown",
1225
+ "signing-failed",
1226
+ "sync-pending",
1227
+ "sync-not-pending",
1228
+ "git-version-unsupported",
1229
+ "merge-failed",
1230
+ "internal-error"
1231
+ ]),
1232
+ message: z.string().min(1).max(500),
1233
+ /** Escaped conflicted paths, bounded; only for merge-conflicted. */
1234
+ conflictPaths: z.array(reviewPathSchema).max(TASK_MERGE_MAX_CONFLICT_PATHS).optional(),
1235
+ /** Conflicted paths beyond the display bound (reported, never silent). */
1236
+ omittedConflictPaths: nonNegativeInt.optional()
1237
+ }).strict().superRefine((error, context) => {
1238
+ if (error.code !== "merge-conflicted" && (error.conflictPaths !== void 0 || error.omittedConflictPaths !== void 0)) {
1239
+ context.addIssue({
1240
+ code: "custom",
1241
+ path: ["conflictPaths"],
1242
+ message: "Only a merge conflict carries conflicted-path facts."
1243
+ });
1244
+ }
1245
+ if (error.omittedConflictPaths !== void 0 && error.conflictPaths === void 0) {
1246
+ context.addIssue({
1247
+ code: "custom",
1248
+ path: ["omittedConflictPaths"],
1249
+ message: "An omitted conflict count requires the retained conflicted paths."
1250
+ });
1251
+ }
1252
+ });
1253
+ var repositoryMergeSyncResultSchema = z.object({
1254
+ protocol: z.literal(PROTOCOL_VERSION),
1255
+ type: z.literal("repository-merge-sync-result"),
1256
+ repositoryId: entityIdSchema,
1257
+ outcome: z.enum([
1258
+ "synced",
1259
+ "publication-unconfirmed",
1260
+ "publication-unconfirmed-cleared",
1261
+ "advanced-integrated",
1262
+ "target-moved",
1263
+ "target-moved-cleared"
1264
+ ]),
1265
+ taskId: entityIdSchema,
1266
+ mode: taskMergeModeSchema,
1267
+ targetBranch: mergeApprovalBranchSchema,
1268
+ previousTargetHead: gitCommitSchema,
1269
+ newTargetHead: gitCommitSchema,
1270
+ /** Foreign target tip observed when the ref moved externally. */
1271
+ currentTargetHead: gitCommitSchema.nullable(),
1272
+ pendingMergeCleared: z.boolean(),
1273
+ capturedAtMs: nonNegativeInt
1274
+ }).strict().superRefine((result, context) => {
1275
+ const retainsPending = ["publication-unconfirmed", "target-moved"].includes(result.outcome);
1276
+ if (result.pendingMergeCleared !== !retainsPending) {
1277
+ context.addIssue({
1278
+ code: "custom",
1279
+ path: ["pendingMergeCleared"],
1280
+ message: "An unconfirmed or externally moved target retains the pending merge record."
1281
+ });
1282
+ }
1283
+ const reportsCurrentHead = [
1284
+ "publication-unconfirmed",
1285
+ "publication-unconfirmed-cleared",
1286
+ "advanced-integrated",
1287
+ "target-moved",
1288
+ "target-moved-cleared"
1289
+ ].includes(result.outcome);
1290
+ if (reportsCurrentHead !== (result.currentTargetHead !== null)) {
1291
+ context.addIssue({
1292
+ code: "custom",
1293
+ path: ["currentTargetHead"],
1294
+ message: "Unconfirmed, advanced, or externally moved outcomes report the observed tip."
1295
+ });
1296
+ }
1297
+ });
1298
+ var repositoryMergeSyncRequestSchema = z.object({
1299
+ /** Explicit dismissal after Webdesk first returned the exact recovery head for inspection. */
1300
+ acknowledgeRecoveryDismissal: z.boolean().optional().default(false)
1301
+ }).strict();
1302
+ function parseTaskMergePreflight(value) {
1303
+ return taskMergePreflightSchema.parse(value);
1304
+ }
1305
+ function safeParseTaskMergePreflight(value) {
1306
+ return taskMergePreflightSchema.safeParse(value);
1307
+ }
1308
+ function parseTaskMergeResult(value) {
1309
+ return taskMergeResultSchema.parse(value);
1310
+ }
1311
+ function safeParseTaskMergeResult(value) {
1312
+ return taskMergeResultSchema.safeParse(value);
1313
+ }
1314
+ function safeParseRepositoryMergeSyncResult(value) {
1315
+ return repositoryMergeSyncResultSchema.safeParse(value);
1316
+ }
1317
+ var taskRuntimeStateSchema = z.enum([
1318
+ "idle",
1319
+ "starting",
1320
+ "ready",
1321
+ "needs_auth",
1322
+ "exited",
1323
+ "failed"
1324
+ ]);
1325
+ var taskRuntimeModelSchema = runtimeModelInfoSchema;
1326
+ var taskRuntimeStatusSchema = z.object({
1327
+ protocol: z.literal(PROTOCOL_VERSION),
1328
+ type: z.literal("task-runtime"),
1329
+ taskId: entityIdSchema,
1330
+ runtimeGeneration: nonNegativeInt,
1331
+ /** Last daemon event included in this status snapshot. */
1332
+ lastSequence: nonNegativeInt.nullable(),
1333
+ state: taskRuntimeStateSchema,
1334
+ working: z.boolean(),
1335
+ /** Current approval, if any; included so reconnect does not lose it. */
1336
+ approval: taskApprovalSchema.nullable().default(null),
1337
+ model: taskRuntimeModelSchema.nullable().optional(),
1338
+ detail: z.string().max(1e3).optional()
1339
+ }).strict().superRefine((status, context) => {
1340
+ const hasLiveCursor = status.lastSequence !== null;
1341
+ if (status.state === "idle" === hasLiveCursor) {
1342
+ context.addIssue({
1343
+ code: "custom",
1344
+ path: ["lastSequence"],
1345
+ message: "Idle runtimes have no cursor; all other states require one."
1346
+ });
1347
+ }
1348
+ });
1349
+ var promptTaskRequestSchema = z.object({
1350
+ message: z.string().trim().min(1).max(2e4)
1351
+ }).strict();
1352
+ var steerTaskRequestSchema = promptTaskRequestSchema;
1353
+ var taskSessionTreeSchema = z.object({
1354
+ protocol: z.literal(PROTOCOL_VERSION),
1355
+ type: z.literal("task-session-tree"),
1356
+ taskId: taskIdSchema,
1357
+ runtimeGeneration: nonNegativeInt,
1358
+ sessionTree: sessionTreeSchema
1359
+ }).strict();
1360
+ function safeParseTaskSessionTree(value) {
1361
+ return taskSessionTreeSchema.safeParse(value);
1362
+ }
1363
+ var selectTaskModelRequestSchema = z.object({
1364
+ provider: z.string().min(1).max(200),
1365
+ modelId: z.string().min(1).max(500)
1366
+ }).strict();
1367
+ var resolveTaskApprovalRequestSchema = z.object({
1368
+ approvalId: z.uuid(),
1369
+ decision: z.enum(["approve", "deny"])
1370
+ }).strict();
1371
+ var taskRuntimeErrorSchema = z.object({
1372
+ protocol: z.literal(PROTOCOL_VERSION),
1373
+ type: z.literal("task-runtime-error"),
1374
+ code: z.enum([
1375
+ "invalid-request",
1376
+ "task-not-found",
1377
+ "task-not-ready",
1378
+ "runtime-not-ready",
1379
+ "runtime-not-working",
1380
+ "runtime-auth-required",
1381
+ "runtime-busy",
1382
+ "runtime-recovery-required",
1383
+ "runtime-start-failed",
1384
+ "runtime-request-failed",
1385
+ "runtime-unavailable",
1386
+ "approval-not-pending",
1387
+ "internal-error"
1388
+ ]),
1389
+ message: z.string().min(1).max(500)
1390
+ }).strict();
1391
+ function parseTaskRuntimeStatus(value) {
1392
+ return taskRuntimeStatusSchema.parse(value);
1393
+ }
1394
+ function safeParseTaskRuntimeStatus(value) {
1395
+ return taskRuntimeStatusSchema.safeParse(value);
1396
+ }
1397
+ var piAuthMethodTypeSchema = z.enum(["api_key", "oauth"]);
1398
+ var piAuthExternalUrlSchema = z.url().max(4096).refine((value) => {
1399
+ const url = new URL(value);
1400
+ return url.protocol === "https:" || url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]");
1401
+ }, "Pi authentication links must use HTTPS, except for loopback HTTP callbacks");
1402
+ var piAuthMethodSchema = z.object({
1403
+ type: piAuthMethodTypeSchema,
1404
+ label: z.string().min(1).max(200)
1405
+ }).strict();
1406
+ var piAuthModelSchema = z.object({
1407
+ id: z.string().min(1).max(500),
1408
+ provider: z.string().min(1).max(200),
1409
+ name: z.string().min(1).max(500),
1410
+ reasoning: z.boolean()
1411
+ }).strict();
1412
+ var piAuthProviderSchema = z.object({
1413
+ id: z.string().min(1).max(200),
1414
+ name: z.string().min(1).max(200),
1415
+ authenticated: z.boolean(),
1416
+ source: z.string().min(1).max(200).optional(),
1417
+ authType: piAuthMethodTypeSchema.optional(),
1418
+ methods: z.array(piAuthMethodSchema),
1419
+ models: z.array(piAuthModelSchema)
1420
+ }).strict();
1421
+ var piAuthPromptBase = {
1422
+ id: entityIdSchema,
1423
+ message: z.string().min(1).max(2e3)
1424
+ };
1425
+ var piAuthPromptSchema = z.discriminatedUnion("type", [
1426
+ z.object({
1427
+ ...piAuthPromptBase,
1428
+ type: z.literal("text"),
1429
+ placeholder: z.string().max(500).optional()
1430
+ }).strict(),
1431
+ z.object({
1432
+ ...piAuthPromptBase,
1433
+ type: z.literal("secret"),
1434
+ placeholder: z.string().max(500).optional()
1435
+ }).strict(),
1436
+ z.object({
1437
+ ...piAuthPromptBase,
1438
+ type: z.literal("manual_code"),
1439
+ placeholder: z.string().max(500).optional()
1440
+ }).strict(),
1441
+ z.object({
1442
+ ...piAuthPromptBase,
1443
+ type: z.literal("select"),
1444
+ options: z.array(
1445
+ z.object({
1446
+ id: z.string().min(1).max(500),
1447
+ label: z.string().min(1).max(500),
1448
+ description: z.string().max(1e3).optional()
1449
+ }).strict()
1450
+ )
1451
+ }).strict()
1452
+ ]);
1453
+ var piAuthNoticeSchema = z.discriminatedUnion("type", [
1454
+ z.object({
1455
+ type: z.literal("info"),
1456
+ message: z.string().min(1).max(2e3),
1457
+ links: z.array(
1458
+ z.object({
1459
+ url: piAuthExternalUrlSchema,
1460
+ label: z.string().max(500).optional()
1461
+ }).strict()
1462
+ ).optional()
1463
+ }).strict(),
1464
+ z.object({
1465
+ type: z.literal("auth_url"),
1466
+ url: piAuthExternalUrlSchema,
1467
+ instructions: z.string().max(2e3).optional()
1468
+ }).strict(),
1469
+ z.object({
1470
+ type: z.literal("device_code"),
1471
+ userCode: z.string().min(1).max(500),
1472
+ verificationUri: piAuthExternalUrlSchema,
1473
+ intervalSeconds: nonNegativeInt.optional(),
1474
+ expiresInSeconds: nonNegativeInt.optional()
1475
+ }).strict(),
1476
+ z.object({
1477
+ type: z.literal("progress"),
1478
+ message: z.string().min(1).max(2e3)
1479
+ }).strict()
1480
+ ]);
1481
+ var piAuthOperationSchema = z.object({
1482
+ protocol: z.literal(PROTOCOL_VERSION),
1483
+ type: z.literal("pi-auth-operation"),
1484
+ id: entityIdSchema,
1485
+ providerId: z.string().min(1).max(200),
1486
+ method: piAuthMethodTypeSchema,
1487
+ state: z.enum(["running", "waiting", "succeeded", "failed", "cancelled"]),
1488
+ prompt: piAuthPromptSchema.nullable(),
1489
+ notice: piAuthNoticeSchema.nullable(),
1490
+ error: z.string().min(1).max(1e3).optional()
1491
+ }).strict();
1492
+ var piAuthSnapshotSchema = z.object({
1493
+ protocol: z.literal(PROTOCOL_VERSION),
1494
+ type: z.literal("pi-auth-snapshot"),
1495
+ providers: z.array(piAuthProviderSchema),
1496
+ /** Optional so a current client can accept an older protocol-v1 daemon snapshot. */
1497
+ activeOperation: piAuthOperationSchema.nullable().optional()
1498
+ }).strict();
1499
+ var startPiAuthRequestSchema = z.object({
1500
+ providerId: z.string().min(1).max(200),
1501
+ method: piAuthMethodTypeSchema
1502
+ }).strict();
1503
+ var respondPiAuthRequestSchema = z.object({
1504
+ promptId: entityIdSchema,
1505
+ value: z.string().min(1).max(2e4)
1506
+ }).strict();
1507
+ var piAuthErrorSchema = z.object({
1508
+ protocol: z.literal(PROTOCOL_VERSION),
1509
+ type: z.literal("pi-auth-error"),
1510
+ code: z.enum([
1511
+ "invalid-request",
1512
+ "operation-not-found",
1513
+ "operation-conflict",
1514
+ "provider-unavailable",
1515
+ "auth-unavailable",
1516
+ "internal-error"
1517
+ ]),
1518
+ message: z.string().min(1).max(500)
1519
+ }).strict();
1520
+ var PI_CONFIGURATION_MAX_RESOURCES = 500;
1521
+ var PI_CONFIGURATION_MAX_PACKAGES = 100;
1522
+ var PI_CONFIGURATION_MAX_SETTINGS = 64;
1523
+ var PI_CONFIGURATION_MAX_DIAGNOSTICS = 100;
1524
+ var piConfigurationDisplayPathSchema = z.string().max(4096);
1525
+ var piConfigurationValueSchema = z.string().max(2e3).nullable();
1526
+ var piResourceSchema = z.object({
1527
+ kind: z.enum(["extension", "skill", "prompt", "theme"]),
1528
+ name: z.string().min(1).max(500),
1529
+ description: z.string().max(2e3).nullable(),
1530
+ path: piConfigurationDisplayPathSchema,
1531
+ scope: z.enum(["user", "project", "temporary"]),
1532
+ origin: z.enum(["package", "top-level"]),
1533
+ source: z.string().min(1).max(1e3),
1534
+ enabled: z.boolean(),
1535
+ disabledReason: z.enum(["filtered", "project-untrusted"]).nullable()
1536
+ }).strict();
1537
+ var piPackageSchema = z.object({
1538
+ source: z.string().min(1).max(1e3),
1539
+ scope: z.enum(["user", "project"]),
1540
+ filtered: z.boolean(),
1541
+ installed: z.boolean(),
1542
+ installedPath: piConfigurationDisplayPathSchema.nullable(),
1543
+ enabled: z.boolean()
1544
+ }).strict();
1545
+ var piSettingProjectionSchema = z.object({
1546
+ key: z.string().min(1).max(200),
1547
+ label: z.string().min(1).max(500),
1548
+ category: z.enum(["model", "behavior", "display", "tools", "privacy"]),
1549
+ globalValue: piConfigurationValueSchema,
1550
+ projectValue: piConfigurationValueSchema,
1551
+ effectiveValue: z.string().min(1).max(2e3),
1552
+ effectiveScope: z.enum(["default", "user", "project"])
1553
+ }).strict();
1554
+ var piResourceDiagnosticSchema = z.object({
1555
+ severity: z.enum(["warning", "error"]),
1556
+ message: z.string().min(1).max(2e3),
1557
+ path: piConfigurationDisplayPathSchema.nullable()
1558
+ }).strict();
1559
+ var piConfigurationSnapshotSchema = z.object({
1560
+ protocol: z.literal(PROTOCOL_VERSION),
1561
+ type: z.literal("pi-configuration-snapshot"),
1562
+ taskId: entityIdSchema,
1563
+ piVersion: z.string().min(1).max(100),
1564
+ cwd: piConfigurationDisplayPathSchema,
1565
+ agentDir: piConfigurationDisplayPathSchema,
1566
+ settingsPaths: z.object({
1567
+ global: piConfigurationDisplayPathSchema,
1568
+ project: piConfigurationDisplayPathSchema,
1569
+ trust: piConfigurationDisplayPathSchema
1570
+ }).strict(),
1571
+ trust: z.object({
1572
+ required: z.boolean(),
1573
+ effective: z.boolean(),
1574
+ savedDecision: z.boolean().nullable(),
1575
+ savedAtPath: piConfigurationDisplayPathSchema.nullable(),
1576
+ defaultPolicy: z.enum(["ask", "always", "never"])
1577
+ }).strict(),
1578
+ packages: z.array(piPackageSchema).max(PI_CONFIGURATION_MAX_PACKAGES),
1579
+ resources: z.array(piResourceSchema).max(PI_CONFIGURATION_MAX_RESOURCES),
1580
+ settings: z.array(piSettingProjectionSchema).max(PI_CONFIGURATION_MAX_SETTINGS),
1581
+ diagnostics: z.array(piResourceDiagnosticSchema).max(PI_CONFIGURATION_MAX_DIAGNOSTICS),
1582
+ omitted: z.object({
1583
+ packages: nonNegativeInt,
1584
+ resources: nonNegativeInt,
1585
+ diagnostics: nonNegativeInt
1586
+ }).strict()
1587
+ }).strict();
1588
+ var piConfigurationErrorSchema = z.object({
1589
+ protocol: z.literal(PROTOCOL_VERSION),
1590
+ type: z.literal("pi-configuration-error"),
1591
+ code: z.enum([
1592
+ "invalid-request",
1593
+ "task-not-found",
1594
+ "task-not-ready",
1595
+ "inspection-timeout",
1596
+ "inspection-failed"
1597
+ ]),
1598
+ message: z.string().min(1).max(500)
1599
+ }).strict();
1600
+ var PI_SESSION_CATALOG_MAX_ENTRIES = 200;
1601
+ var piSessionCatalogItemSchema = z.object({
1602
+ selectionId: entityIdSchema,
1603
+ sessionId: z.string().min(1).max(500),
1604
+ name: z.string().min(1).max(200).nullable(),
1605
+ cwd: absolutePathSchema,
1606
+ firstMessage: z.string().max(500),
1607
+ createdAtMs: nonNegativeInt,
1608
+ modifiedAtMs: nonNegativeInt,
1609
+ messageCount: nonNegativeInt
1610
+ }).strict();
1611
+ var piSessionCatalogSnapshotSchema = z.object({
1612
+ protocol: z.literal(PROTOCOL_VERSION),
1613
+ type: z.literal("pi-session-catalog"),
1614
+ sessions: z.array(piSessionCatalogItemSchema).max(PI_SESSION_CATALOG_MAX_ENTRIES),
1615
+ omitted: nonNegativeInt,
1616
+ expiresAtMs: nonNegativeInt
1617
+ }).strict();
1618
+ var continuePiSessionRequestSchema = z.object({
1619
+ selectionId: entityIdSchema,
1620
+ repositoryId: entityIdSchema,
1621
+ title: z.string().trim().min(1).max(200),
1622
+ baseRef: z.string().trim().min(1).max(1024)
1623
+ }).strict();
1624
+ var continuePiSessionResultSchema = z.object({
1625
+ protocol: z.literal(PROTOCOL_VERSION),
1626
+ type: z.literal("pi-session-continuation-result"),
1627
+ taskId: entityIdSchema,
1628
+ sourceSessionId: z.string().min(1).max(500),
1629
+ continuation: z.discriminatedUnion("status", [
1630
+ z.object({ status: z.literal("continued") }).strict(),
1631
+ z.object({
1632
+ status: z.literal("failed"),
1633
+ message: z.string().min(1).max(500)
1634
+ }).strict()
1635
+ ]),
1636
+ snapshot: workspaceSnapshotSchema
1637
+ }).strict();
1638
+ var piSessionCatalogErrorSchema = z.object({
1639
+ protocol: z.literal(PROTOCOL_VERSION),
1640
+ type: z.literal("pi-session-catalog-error"),
1641
+ code: z.enum([
1642
+ "invalid-request",
1643
+ "selection-expired",
1644
+ "session-unavailable",
1645
+ "repository-mismatch",
1646
+ "continuation-unavailable"
1647
+ ]),
1648
+ message: z.string().min(1).max(500)
1649
+ }).strict();
1650
+ var PITA_PREFERENCES_SCHEMA_VERSION = 1;
1651
+ var pitaPaletteSchema = z.enum([
1652
+ "warm-sand",
1653
+ "cool-slate",
1654
+ "moss",
1655
+ "graphite",
1656
+ "amethyst",
1657
+ "ocean"
1658
+ ]);
1659
+ var pitaAppearancePreferencesSchema = z.object({
1660
+ palette: pitaPaletteSchema,
1661
+ mode: z.enum(["light", "dark", "system"]),
1662
+ density: z.enum(["comfortable", "compact"]),
1663
+ sidebarWidth: z.number().int().min(200).max(360),
1664
+ sidebarVisible: z.boolean(),
1665
+ contextPanel: z.enum(["right", "left", "hidden"])
1666
+ }).strict();
1667
+ var pitaPreferencesSnapshotSchema = z.object({
1668
+ protocol: z.literal(PROTOCOL_VERSION),
1669
+ type: z.literal("pita-preferences-snapshot"),
1670
+ schemaVersion: z.literal(PITA_PREFERENCES_SCHEMA_VERSION),
1671
+ repositoryId: entityIdSchema.nullable(),
1672
+ global: pitaAppearancePreferencesSchema,
1673
+ repository: pitaAppearancePreferencesSchema.nullable(),
1674
+ effective: pitaAppearancePreferencesSchema
1675
+ }).strict();
1676
+ var updatePitaPreferencesRequestSchema = z.object({
1677
+ scope: z.enum(["global", "repository"]),
1678
+ repositoryId: entityIdSchema.nullable(),
1679
+ action: z.enum(["apply", "reset"]),
1680
+ preferences: pitaAppearancePreferencesSchema.nullable()
1681
+ }).strict().superRefine((request, context) => {
1682
+ if (request.scope === "repository" && request.repositoryId === null) {
1683
+ context.addIssue({
1684
+ code: "custom",
1685
+ path: ["repositoryId"],
1686
+ message: "Repository preferences require a repository id."
1687
+ });
1688
+ }
1689
+ if (request.action === "apply" && request.preferences === null) {
1690
+ context.addIssue({
1691
+ code: "custom",
1692
+ path: ["preferences"],
1693
+ message: "Applying preferences requires a complete preference value."
1694
+ });
1695
+ }
1696
+ if (request.action === "reset" && request.preferences !== null) {
1697
+ context.addIssue({
1698
+ code: "custom",
1699
+ path: ["preferences"],
1700
+ message: "Reset requests cannot carry preference values."
1701
+ });
1702
+ }
1703
+ });
1704
+ var pitaPreferencesErrorSchema = z.object({
1705
+ protocol: z.literal(PROTOCOL_VERSION),
1706
+ type: z.literal("pita-preferences-error"),
1707
+ code: z.enum(["invalid-request", "repository-not-found", "preferences-unavailable"]),
1708
+ message: z.string().min(1).max(500)
1709
+ }).strict();
1710
+ export {
1711
+ MAX_SESSION_TRANSCRIPT_ITEMS,
1712
+ MAX_SESSION_TRANSCRIPT_TEXT_CHARS,
1713
+ PITA_PREFERENCES_SCHEMA_VERSION,
1714
+ PI_CONFIGURATION_MAX_DIAGNOSTICS,
1715
+ PI_CONFIGURATION_MAX_PACKAGES,
1716
+ PI_CONFIGURATION_MAX_RESOURCES,
1717
+ PI_CONFIGURATION_MAX_SETTINGS,
1718
+ PI_SESSION_CATALOG_MAX_ENTRIES,
1719
+ PROTOCOL_VERSION,
1720
+ TASK_COMMIT_MAX_FILES,
1721
+ TASK_COMMIT_MAX_FILE_DIFF_CHARS,
1722
+ TASK_COMMIT_MAX_MESSAGE_CHARS,
1723
+ TASK_COMMIT_MAX_TOTAL_DIFF_CHARS,
1724
+ TASK_MERGE_MAX_COMMIT_SUBJECTS,
1725
+ TASK_MERGE_MAX_COMMIT_SUBJECT_CHARS,
1726
+ TASK_MERGE_MAX_CONFLICT_PATHS,
1727
+ TASK_MERGE_MAX_FILES,
1728
+ TASK_MERGE_MAX_FILE_DIFF_CHARS,
1729
+ TASK_MERGE_MAX_TOTAL_DIFF_CHARS,
1730
+ TASK_REVIEW_MAX_FILES,
1731
+ TASK_REVIEW_MAX_FILE_DIFF_CHARS,
1732
+ TASK_REVIEW_MAX_TOTAL_DIFF_CHARS,
1733
+ TASK_VALIDATION_MAX_COMMAND_CHARS,
1734
+ TASK_VALIDATION_MAX_DURATION_MS,
1735
+ TASK_VALIDATION_MAX_OUTPUT_CHARS,
1736
+ WORKSPACE_SCHEMA_VERSION,
1737
+ agentActivityPayloadSchema,
1738
+ approvalRequestedPayloadSchema,
1739
+ approvalResolvedPayloadSchema,
1740
+ assistantDeltaPayloadSchema,
1741
+ clientMessageSchema,
1742
+ continuePiSessionRequestSchema,
1743
+ continuePiSessionResultSchema,
1744
+ createDaemonEventFactory,
1745
+ createTaskRequestSchema,
1746
+ daemonEventSchema,
1747
+ executeTaskCommitRequestSchema,
1748
+ executeTaskMergeRequestSchema,
1749
+ logPayloadSchema,
1750
+ parseDaemonEvent,
1751
+ parseServerControlMessage,
1752
+ parseServerMessage,
1753
+ parseServiceSession,
1754
+ parseTaskCommitPreflight,
1755
+ parseTaskCommitResult,
1756
+ parseTaskMergePreflight,
1757
+ parseTaskMergeResult,
1758
+ parseTaskReview,
1759
+ parseTaskRuntimeSnapshot,
1760
+ parseTaskRuntimeStatus,
1761
+ parseTaskValidation,
1762
+ parseWorkspaceSnapshot,
1763
+ piAuthErrorSchema,
1764
+ piAuthMethodSchema,
1765
+ piAuthMethodTypeSchema,
1766
+ piAuthModelSchema,
1767
+ piAuthNoticeSchema,
1768
+ piAuthOperationSchema,
1769
+ piAuthPromptSchema,
1770
+ piAuthProviderSchema,
1771
+ piAuthSnapshotSchema,
1772
+ piConfigurationErrorSchema,
1773
+ piConfigurationSnapshotSchema,
1774
+ piPackageSchema,
1775
+ piResourceDiagnosticSchema,
1776
+ piResourceSchema,
1777
+ piSessionCatalogErrorSchema,
1778
+ piSessionCatalogItemSchema,
1779
+ piSessionCatalogSnapshotSchema,
1780
+ piSettingProjectionSchema,
1781
+ pitaAppearancePreferencesSchema,
1782
+ pitaPaletteSchema,
1783
+ pitaPreferencesErrorSchema,
1784
+ pitaPreferencesSnapshotSchema,
1785
+ promptTaskRequestSchema,
1786
+ registerRepositoryRequestSchema,
1787
+ repositoryMergeSyncRequestSchema,
1788
+ repositoryMergeSyncResultSchema,
1789
+ repositoryPendingMergeSchema,
1790
+ repositoryRecordSchema,
1791
+ resolveTaskApprovalRequestSchema,
1792
+ respondPiAuthRequestSchema,
1793
+ runTaskValidationRequestSchema,
1794
+ runtimeModelInfoSchema,
1795
+ runtimeProcessRecordSchema,
1796
+ runtimeStatusPayloadSchema,
1797
+ safeParseClientMessage,
1798
+ safeParseDaemonEvent,
1799
+ safeParseRepositoryMergeSyncResult,
1800
+ safeParseServerControlMessage,
1801
+ safeParseServerMessage,
1802
+ safeParseServiceSession,
1803
+ safeParseTaskCommitPreflight,
1804
+ safeParseTaskCommitResult,
1805
+ safeParseTaskMergePreflight,
1806
+ safeParseTaskMergeResult,
1807
+ safeParseTaskReview,
1808
+ safeParseTaskRuntimeStatus,
1809
+ safeParseTaskSessionTree,
1810
+ safeParseTaskValidation,
1811
+ safeParseWorkspaceSnapshot,
1812
+ selectTaskModelRequestSchema,
1813
+ serverControlMessageSchema,
1814
+ serverMessageSchema,
1815
+ serviceSessionSchema,
1816
+ sessionTranscriptItemSchema,
1817
+ sessionTreeNodeSchema,
1818
+ sessionTreeSchema,
1819
+ startPiAuthRequestSchema,
1820
+ steerTaskRequestSchema,
1821
+ taskApprovalSchema,
1822
+ taskArchiveRequestSchema,
1823
+ taskCommitErrorSchema,
1824
+ taskCommitFileSchema,
1825
+ taskCommitMessageSchema,
1826
+ taskCommitPreflightRequestSchema,
1827
+ taskCommitPreflightSchema,
1828
+ taskCommitResultSchema,
1829
+ taskCommitValidationVerdictSchema,
1830
+ taskEventsErrorSchema,
1831
+ taskEventsReplaySchema,
1832
+ taskIdSchema,
1833
+ taskMergeErrorSchema,
1834
+ taskMergeModeSchema,
1835
+ taskMergePreflightRequestSchema,
1836
+ taskMergePreflightSchema,
1837
+ taskMergeResultSchema,
1838
+ taskRecordSchema,
1839
+ taskReviewErrorSchema,
1840
+ taskReviewFileSchema,
1841
+ taskReviewSchema,
1842
+ taskRuntimeErrorSchema,
1843
+ taskRuntimeModelSchema,
1844
+ taskRuntimeSnapshotSchema,
1845
+ taskRuntimeStateSchema,
1846
+ taskRuntimeStatusSchema,
1847
+ taskSessionTreeSchema,
1848
+ taskSubscribeMessageSchema,
1849
+ taskValidationCommandSchema,
1850
+ taskValidationErrorSchema,
1851
+ taskValidationResultSchema,
1852
+ taskValidationSchema,
1853
+ toolActivitySchema,
1854
+ updatePitaPreferencesRequestSchema,
1855
+ workspaceErrorSchema,
1856
+ workspaceSnapshotSchema,
1857
+ workspaceStateSchema,
1858
+ workspaceStateV1Schema,
1859
+ workspaceStateV2Schema,
1860
+ workspaceStateV3Schema,
1861
+ workspaceStateV4Schema,
1862
+ worktreeRecoveryFactsSchema
1863
+ };