pi-ui-extend 1.0.40 → 1.0.42

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 (186) hide show
  1. package/README.md +10 -2
  2. package/dist/app/app.d.ts +3 -0
  3. package/dist/app/app.js +26 -0
  4. package/dist/app/commands/command-host.d.ts +1 -0
  5. package/dist/app/commands/command-model-actions.js +6 -1
  6. package/dist/app/commands/command-registry.js +3 -3
  7. package/dist/app/commands/command-session-actions.d.ts +0 -1
  8. package/dist/app/commands/command-session-actions.js +30 -15
  9. package/dist/app/commands/reload-context-inventory.d.ts +11 -0
  10. package/dist/app/commands/reload-context-inventory.js +59 -0
  11. package/dist/app/extensions/extension-actions-controller.d.ts +1 -0
  12. package/dist/app/extensions/extension-actions-controller.js +6 -0
  13. package/dist/app/extensions/subagent-catalog-state.d.ts +9 -0
  14. package/dist/app/extensions/subagent-catalog-state.js +23 -0
  15. package/dist/app/icons.d.ts +14 -0
  16. package/dist/app/icons.js +33 -0
  17. package/dist/app/rendering/conversation-tool-renderer.js +2 -2
  18. package/dist/app/rendering/dcp-stats.d.ts +6 -1
  19. package/dist/app/rendering/dcp-stats.js +214 -46
  20. package/dist/app/rendering/editor-panels.js +17 -8
  21. package/dist/app/session/lazy-session-manager.js +12 -1
  22. package/dist/app/session/session-lifecycle-controller.d.ts +1 -0
  23. package/dist/app/session/session-lifecycle-controller.js +6 -0
  24. package/dist/app/session/tabs-controller.d.ts +3 -5
  25. package/dist/app/session/tabs-controller.js +31 -22
  26. package/dist/app/subagents/subagents-files.js +60 -1
  27. package/dist/app/subagents/subagents-model.d.ts +15 -1
  28. package/dist/app/subagents/subagents-model.js +52 -17
  29. package/dist/app/types.d.ts +7 -0
  30. package/dist/bundled-extensions/session-title/config.js +1 -1
  31. package/dist/default-pix-config.js +10 -0
  32. package/dist/markdown-format.js +27 -9
  33. package/dist/schemas/index.d.ts +1 -0
  34. package/dist/schemas/index.js +1 -0
  35. package/dist/schemas/pi-tools-suite-schema.d.ts +106 -71
  36. package/dist/schemas/pi-tools-suite-schema.js +68 -88
  37. package/dist/schemas/pix-schema.d.ts +7 -0
  38. package/dist/schemas/pix-schema.js +14 -0
  39. package/dist/schemas/tasks-schema.d.ts +18 -0
  40. package/dist/schemas/tasks-schema.js +41 -0
  41. package/docs/concurrency.md +6 -0
  42. package/docs/desktop-mvp.md +12 -1
  43. package/docs/desktop-task-manager.md +93 -82
  44. package/external/pi-tools-suite/README.md +192 -72
  45. package/external/pi-tools-suite/docs/browser-qa-subagent.md +50 -21
  46. package/external/pi-tools-suite/docs/context-gateway-p00-adr.md +216 -0
  47. package/external/pi-tools-suite/docs/context-gateway-p01n-gate-review.md +122 -0
  48. package/external/pi-tools-suite/docs/context-gateway-p01n-measurement.md +133 -0
  49. package/external/pi-tools-suite/docs/context-gateway-p01r-ra-evidence.md +111 -0
  50. package/external/pi-tools-suite/docs/context-gateway-p01r-rb-evidence.md +100 -0
  51. package/external/pi-tools-suite/docs/context-gateway-p01r-rc-evidence.md +69 -0
  52. package/external/pi-tools-suite/docs/context-gateway-p01r-rd-evidence.md +100 -0
  53. package/external/pi-tools-suite/docs/context-gateway-p01r-re-evidence.md +74 -0
  54. package/external/pi-tools-suite/docs/context-gateway-p01r-rf-evidence.md +153 -0
  55. package/external/pi-tools-suite/docs/context-gateway-p01r-rg-evidence.md +235 -0
  56. package/external/pi-tools-suite/docs/evals.md +1 -1
  57. package/external/pi-tools-suite/docs/session-recovery.md +40 -14
  58. package/external/pi-tools-suite/docs/subagent-model-pools.md +120 -0
  59. package/external/pi-tools-suite/package.json +3 -0
  60. package/external/pi-tools-suite/src/async-subagents/{private-skills → agents}/browser-qa/scripts/browser-qa-runner.mjs +82 -1
  61. package/external/pi-tools-suite/src/async-subagents/{private-skills/browser-qa/SKILL.md → agents/browser-qa.md} +261 -12
  62. package/external/pi-tools-suite/src/async-subagents/agents/frontier-review.md +23 -0
  63. package/external/pi-tools-suite/src/async-subagents/agents/implement.md +20 -0
  64. package/external/pi-tools-suite/src/async-subagents/agents/oracle.md +16 -0
  65. package/external/pi-tools-suite/src/async-subagents/agents/presets.jsonc +16 -0
  66. package/external/pi-tools-suite/src/async-subagents/agents/research.md +20 -0
  67. package/external/pi-tools-suite/src/async-subagents/agents/verify.md +18 -0
  68. package/external/pi-tools-suite/src/async-subagents/commands.ts +20 -72
  69. package/external/pi-tools-suite/src/async-subagents/core/activity.ts +33 -0
  70. package/external/pi-tools-suite/src/async-subagents/core/agent-catalog.ts +42 -0
  71. package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +13 -93
  72. package/external/pi-tools-suite/src/async-subagents/core/agents-dir.ts +499 -0
  73. package/external/pi-tools-suite/src/async-subagents/core/browser-qa.ts +9 -0
  74. package/external/pi-tools-suite/src/async-subagents/core/config.ts +284 -417
  75. package/external/pi-tools-suite/src/async-subagents/core/model-fallback.ts +1 -1
  76. package/external/pi-tools-suite/src/async-subagents/core/model-selection.ts +54 -0
  77. package/external/pi-tools-suite/src/async-subagents/core/prompt.ts +7 -6
  78. package/external/pi-tools-suite/src/async-subagents/core/routing.ts +65 -45
  79. package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +18 -6
  80. package/external/pi-tools-suite/src/async-subagents/core/state.ts +40 -0
  81. package/external/pi-tools-suite/src/async-subagents/core/types.ts +7 -0
  82. package/external/pi-tools-suite/src/async-subagents/index.ts +77 -3
  83. package/external/pi-tools-suite/src/async-subagents/lib.ts +12 -9
  84. package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +54 -22
  85. package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +6 -4
  86. package/external/pi-tools-suite/src/async-subagents/types.ts +3 -0
  87. package/external/pi-tools-suite/src/config.ts +42 -2
  88. package/external/pi-tools-suite/src/context-gateway/accounting.ts +151 -0
  89. package/external/pi-tools-suite/src/context-gateway/config.ts +120 -0
  90. package/external/pi-tools-suite/src/context-gateway/enforcement.ts +102 -0
  91. package/external/pi-tools-suite/src/context-gateway/index.ts +214 -0
  92. package/external/pi-tools-suite/src/context-gateway/metadata-normalization.ts +88 -0
  93. package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +89 -0
  94. package/external/pi-tools-suite/src/context-gateway/telemetry.ts +502 -0
  95. package/external/pi-tools-suite/src/context-gateway/test-output-parser.ts +326 -0
  96. package/external/pi-tools-suite/src/context-gateway/types.ts +158 -0
  97. package/external/pi-tools-suite/src/context-inventory.ts +99 -0
  98. package/external/pi-tools-suite/src/dcp/auto-compress-budget.ts +106 -0
  99. package/external/pi-tools-suite/src/dcp/auto-compress.ts +892 -136
  100. package/external/pi-tools-suite/src/dcp/commands.ts +82 -139
  101. package/external/pi-tools-suite/src/dcp/compress-tool.ts +533 -74
  102. package/external/pi-tools-suite/src/dcp/compression-blocks.ts +488 -94
  103. package/external/pi-tools-suite/src/dcp/compression-preview.ts +122 -0
  104. package/external/pi-tools-suite/src/dcp/compression-progress.ts +70 -0
  105. package/external/pi-tools-suite/src/dcp/config.ts +86 -68
  106. package/external/pi-tools-suite/src/dcp/conversation-index.ts +421 -0
  107. package/external/pi-tools-suite/src/dcp/debug-log.ts +7 -5
  108. package/external/pi-tools-suite/src/dcp/index.ts +617 -203
  109. package/external/pi-tools-suite/src/dcp/journal.ts +566 -0
  110. package/external/pi-tools-suite/src/dcp/progress-controller.ts +244 -0
  111. package/external/pi-tools-suite/src/dcp/prompts.ts +83 -189
  112. package/external/pi-tools-suite/src/dcp/protected-continuity.ts +175 -0
  113. package/external/pi-tools-suite/src/dcp/provider-tool-results.ts +189 -0
  114. package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +299 -79
  115. package/external/pi-tools-suite/src/dcp/pruner-compression-blocks.ts +173 -281
  116. package/external/pi-tools-suite/src/dcp/pruner-emergency.ts +2 -4
  117. package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +23 -15
  118. package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +11 -1
  119. package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +30 -82
  120. package/external/pi-tools-suite/src/dcp/pruner-tools.ts +28 -136
  121. package/external/pi-tools-suite/src/dcp/pruner.ts +18 -33
  122. package/external/pi-tools-suite/src/dcp/recovery.ts +129 -0
  123. package/external/pi-tools-suite/src/dcp/shadow-plan.ts +127 -0
  124. package/external/pi-tools-suite/src/dcp/state-transaction.ts +102 -0
  125. package/external/pi-tools-suite/src/dcp/state.ts +158 -580
  126. package/external/pi-tools-suite/src/dcp/ui.ts +1 -0
  127. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +36 -214
  128. package/external/pi-tools-suite/src/index.ts +16 -1
  129. package/external/pi-tools-suite/src/model-tools/index.ts +76 -42
  130. package/external/pi-tools-suite/src/repo-discovery/index.ts +84 -18
  131. package/external/pi-tools-suite/src/repo-discovery/native-compact.ts +458 -0
  132. package/external/pi-tools-suite/src/resource-registry/index.ts +2290 -0
  133. package/external/pi-tools-suite/src/session-recovery/index.ts +189 -43
  134. package/external/pi-tools-suite/src/shell-command-policy.ts +219 -0
  135. package/external/pi-tools-suite/src/tool-descriptions.ts +41 -38
  136. package/external/pi-tools-suite/src/truncation-metadata-normalizer/index.ts +17 -0
  137. package/package.json +6 -3
  138. package/schemas/pi-tools-suite.json +540 -312
  139. package/schemas/pix.json +24 -0
  140. package/schemas/tasks.json +132 -0
  141. package/skills/spec-wiki/SKILL.md +55 -0
  142. package/skills/spec-wiki/evals/evals.json +132 -0
  143. package/skills/spec-wiki/evals/files/bootstrap/architecture/rfc-007.md +14 -0
  144. package/skills/spec-wiki/evals/files/bootstrap/docs/how-to-pay.md +4 -0
  145. package/skills/spec-wiki/evals/files/bootstrap/docs/overview.md +9 -0
  146. package/skills/spec-wiki/evals/files/bootstrap/requirements/payment-behavior.md +17 -0
  147. package/skills/spec-wiki/evals/files/bootstrap/src/payments.ts +12 -0
  148. package/skills/spec-wiki/evals/files/bootstrap/tests/payments.test.ts +11 -0
  149. package/skills/spec-wiki/evals/files/focused/src/session.ts +5 -0
  150. package/skills/spec-wiki/evals/files/focused/tests/session.test.ts +17 -0
  151. package/skills/spec-wiki/evals/files/maintenance/low-signal/incoming/capture.txt +2 -0
  152. package/skills/spec-wiki/evals/files/maintenance/low-signal/src/capture.ts +3 -0
  153. package/skills/spec-wiki/evals/files/maintenance/move/docs/old-job-contract.md +18 -0
  154. package/skills/spec-wiki/evals/files/maintenance/move/src/job.ts +5 -0
  155. package/skills/spec-wiki/evals/files/maintenance/new-relation/docs/session-refresh.md +24 -0
  156. package/skills/spec-wiki/evals/files/maintenance/new-relation/incoming/refresh-worker.ts +7 -0
  157. package/skills/spec-wiki/evals/files/maintenance/new-relation/src/session.ts +7 -0
  158. package/skills/spec-wiki/evals/files/maintenance/no-impact/docs/auth-contract.md +17 -0
  159. package/skills/spec-wiki/evals/files/maintenance/no-impact/incoming/telemetry-format.ts +3 -0
  160. package/skills/spec-wiki/evals/files/maintenance/no-impact/src/auth.ts +3 -0
  161. package/skills/spec-wiki/evals/files/query/docs/how-to-run-jobs.md +4 -0
  162. package/skills/spec-wiki/evals/files/query/docs/spec-overview.md +9 -0
  163. package/skills/spec-wiki/evals/files/query/features/jobs/cancellation-contract.md +15 -0
  164. package/skills/spec-wiki/evals/files/query/features/jobs/retry-spec.md +6 -0
  165. package/skills/spec-wiki/evals/maintenance_regression.py +311 -0
  166. package/skills/spec-wiki/evals/retrieval-eval.md +37 -0
  167. package/skills/spec-wiki/evals/retrieval-evals.json +37 -0
  168. package/skills/spec-wiki/evals/trigger-evals.json +94 -0
  169. package/skills/spec-wiki/references/bootstrap.md +100 -0
  170. package/skills/spec-wiki/references/focused-spec.md +117 -0
  171. package/skills/spec-wiki/references/index-schema.md +330 -0
  172. package/skills/spec-wiki/references/maintenance.md +134 -0
  173. package/skills/spec-wiki/references/query.md +74 -0
  174. package/skills/spec-wiki/scripts/spec_wiki.py +2076 -0
  175. package/docs/desktop-markdown-media.md +0 -77
  176. package/external/pi-tools-suite/docs/dcp-emergency-current-turn.md +0 -102
  177. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +0 -270
  178. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/auth-scaffold-spec.md +0 -78
  179. package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/qa-design.md +0 -223
  180. package/external/pi-tools-suite/src/dcp/state-persistence.ts +0 -195
  181. package/external/pi-tools-suite/src/skill-installer/index.ts +0 -333
  182. package/skills/spec-lite/SKILL.md +0 -140
  183. /package/external/pi-tools-suite/src/async-subagents/{private-skills/browser-qa/references → agents/browser-qa/examples}/qa-auth.example.jsonc +0 -0
  184. /package/external/pi-tools-suite/src/async-subagents/{private-skills/browser-qa/references → agents/browser-qa/examples}/qa-flow.example.jsonc +0 -0
  185. /package/external/pi-tools-suite/src/async-subagents/{private-skills → agents}/browser-qa/vendor/fflate.LICENSE +0 -0
  186. /package/external/pi-tools-suite/src/async-subagents/{private-skills → agents}/browser-qa/vendor/fflate.mjs +0 -0
@@ -39,7 +39,7 @@ const TerminalBellConfig = Type.Object({
39
39
  // ---------------------------------------------------------------------------
40
40
  const DcpManualModeConfig = Type.Object({
41
41
  enabled: Type.Optional(Type.Boolean({ description: "Enable manual DCP mode." })),
42
- automaticStrategies: Type.Optional(Type.Boolean({ description: "Run dedup/purge even in manual mode." })),
42
+ automaticStrategies: Type.Optional(Type.Never({ description: "Removed legacy key. Manual mode now has only the explicit enabled flag." })),
43
43
  }, { description: "Manual mode configuration." });
44
44
  const DcpAutoCandidatesConfig = Type.Object({
45
45
  enabled: Type.Optional(Type.Boolean({ description: "Enable auto candidate selection for compression." })),
@@ -56,11 +56,17 @@ const DcpMessageModeConfig = Type.Object({
56
56
  highTokens: Type.Optional(Type.Number({ description: "Token threshold for high-quality summary.", minimum: 0 })),
57
57
  maxSuggestions: Type.Optional(Type.Number({ description: "Maximum compression suggestions.", minimum: 0 })),
58
58
  }, { description: "Message-mode compression configuration." });
59
+ const DcpAutoCompressConfig = Type.Object({
60
+ enabled: Type.Optional(Type.Boolean({ description: "Allow autonomous compression after pressure/opportunity gates. Explicit compress calls can still use summarizerModel when summary is omitted." })),
61
+ patience: Type.Optional(Type.Number({ description: "Completed actionable opportunities allowed before automatic compression.", minimum: 0 })),
62
+ summarizerModel: Type.Optional(Type.Array(Type.String(), { description: "Ordered model refs used by auto-compress and explicit compress when summary is omitted; empty uses the deterministic extractive summary." })),
63
+ timeoutMs: Type.Optional(Type.Number({ description: "Per-summarizer deadline in milliseconds for automatic and explicit generated summaries.", minimum: 1 })),
64
+ }, { description: "Bounded automatic compression fallback." });
59
65
  const DcpCompressConfig = Type.Object({
60
66
  maxContextPercent: Type.Optional(Type.Union([Type.Number(), Type.String()], { description: "Maximum context percent (0–1 or '80%') before compression triggers." })),
61
67
  minContextPercent: Type.Optional(Type.Union([Type.Number(), Type.String()], { description: "Target context percent after compression." })),
62
- modelMaxContextPercent: Type.Optional(Type.Record(Type.String(), Type.Number(), { description: "Per-model max context percent overrides." })),
63
- modelMinContextPercent: Type.Optional(Type.Record(Type.String(), Type.Number(), { description: "Per-model min context percent overrides." })),
68
+ modelMaxContextPercent: Type.Optional(Type.Record(Type.String(), Type.Union([Type.Number(), Type.String()]), { description: "Per-model max context overrides using the same fraction/token/percent formats." })),
69
+ modelMinContextPercent: Type.Optional(Type.Record(Type.String(), Type.Union([Type.Number(), Type.String()]), { description: "Per-model min context overrides using the same fraction/token/percent formats." })),
64
70
  maxContextLimit: Type.Optional(Type.Union([Type.Number(), Type.String()], { description: "Absolute max context tokens or '200k'." })),
65
71
  minContextLimit: Type.Optional(Type.Union([Type.Number(), Type.String()], { description: "Absolute min context tokens." })),
66
72
  modelMaxContextLimits: Type.Optional(Type.Record(Type.String(), Type.Union([Type.Number(), Type.String()]), { description: "Per-model max context limit overrides." })),
@@ -74,29 +80,37 @@ const DcpCompressConfig = Type.Object({
74
80
  protectUserMessages: Type.Optional(Type.Boolean({ description: "Protect user messages from pruning." })),
75
81
  autoCandidates: Type.Optional(DcpAutoCandidatesConfig),
76
82
  messageMode: Type.Optional(DcpMessageModeConfig),
83
+ autoCompress: Type.Optional(DcpAutoCompressConfig),
77
84
  }, { description: "Compression trigger and behavior configuration." });
78
- const DcpDeduplicationConfig = Type.Object({
79
- enabled: Type.Optional(Type.Boolean({ description: "Enable duplicate content deduplication." })),
80
- protectedTools: Type.Optional(Type.Array(Type.String(), { description: "Tool outputs protected from dedup." })),
81
- }, { description: "Deduplication strategy configuration." });
82
- const DcpPurgeErrorsConfig = Type.Object({
83
- enabled: Type.Optional(Type.Boolean({ description: "Enable error input purging." })),
84
- turns: Type.Optional(Type.Number({ description: "Prune error inputs after N user turns.", minimum: 1 })),
85
- protectedTools: Type.Optional(Type.Array(Type.String(), { description: "Tool outputs protected from error purge." })),
86
- }, { description: "Error purging strategy configuration." });
87
- const DcpAutoToolPruningConfig = Type.Object({
88
- enabled: Type.Optional(Type.Boolean({ description: "Enable automatic tool output pruning." })),
89
- maxOutputTokens: Type.Optional(Type.Number({ description: "Maximum output tokens before truncation.", minimum: 0 })),
90
- keepRecentTurns: Type.Optional(Type.Number({ description: "Recent turns to keep.", minimum: 0 })),
91
- readLikeTools: Type.Optional(Type.Array(Type.String(), { description: "Tools treated as read-like (aggressively pruned)." })),
92
- readLikeTurns: Type.Optional(Type.Number({ description: "Turns threshold for read-like tool pruning.", minimum: 0 })),
93
- protectedTools: Type.Optional(Type.Array(Type.String(), { description: "Tool outputs protected from auto-pruning." })),
94
- }, { description: "Auto tool pruning strategy configuration." });
85
+ const DcpEmergencyCurrentTurnPruningConfig = Type.Object({
86
+ enabled: Type.Optional(Type.Boolean({ description: "Enable bounded same-turn emergency planning and last-resort output pruning." })),
87
+ hardContextPercent: Type.Optional(Type.Number({ description: "Context fraction that activates hard emergency pressure.", minimum: 0, maximum: 1 })),
88
+ targetContextPercent: Type.Optional(Type.Number({ description: "Context fraction the emergency path attempts to recover toward.", minimum: 0, maximum: 1 })),
89
+ patience: Type.Optional(Type.Number({ description: "Completed emergency opportunities allowed before last-resort pruning.", minimum: 0 })),
90
+ keepRecentToolPairs: Type.Optional(Type.Number({ description: "Newest complete tool-call/result pairs never selected by emergency cleanup.", minimum: 0 })),
91
+ minOutputTokens: Type.Optional(Type.Number({ description: "Minimum tool-result size eligible for emergency pruning.", minimum: 0 })),
92
+ maxSuggestions: Type.Optional(Type.Number({ description: "Maximum emergency message candidates shown in a reminder.", minimum: 0 })),
93
+ protectedTools: Type.Optional(Type.Array(Type.String(), { description: "Additional tools protected from emergency pruning and manual sweep." })),
94
+ }, { description: "Single bounded emergency cleanup strategy." });
95
95
  const DcpStrategiesConfig = Type.Object({
96
- deduplication: Type.Optional(DcpDeduplicationConfig),
97
- purgeErrors: Type.Optional(DcpPurgeErrorsConfig),
98
- autoToolPruning: Type.Optional(DcpAutoToolPruningConfig),
99
- }, { description: "DCP pruning strategies." });
96
+ emergencyCurrentTurnPruning: Type.Optional(DcpEmergencyCurrentTurnPruningConfig),
97
+ deduplication: Type.Optional(Type.Never({ description: "Removed legacy key; no automatic replacement policy maps to it." })),
98
+ purgeErrors: Type.Optional(Type.Never({ description: "Removed legacy key; no automatic replacement policy maps to it." })),
99
+ autoToolPruning: Type.Optional(Type.Never({ description: "Removed legacy key. Use explicit /dcp sweep or DCP compression/emergency policies instead." })),
100
+ }, { description: "Bounded DCP emergency strategy." });
101
+ const DcpConfigOverride = Type.Object({
102
+ enabled: Type.Optional(Type.Boolean()),
103
+ debug: Type.Optional(Type.Boolean()),
104
+ debugLog: Type.Optional(Type.Object({
105
+ maxBytes: Type.Optional(Type.Number({ minimum: 1024 })),
106
+ maxBackups: Type.Optional(Type.Number({ minimum: 1 })),
107
+ })),
108
+ manualMode: Type.Optional(DcpManualModeConfig),
109
+ compress: Type.Optional(DcpCompressConfig),
110
+ strategies: Type.Optional(DcpStrategiesConfig),
111
+ protectedFilePatterns: Type.Optional(Type.Array(Type.String())),
112
+ pruneNotification: Type.Optional(Type.Never({ description: "Removed legacy key; pruning notifications are no longer configurable." })),
113
+ }, { description: "Partial DCP override applied to a matching provider/model or bare-model key. Keys support * and ? wildcards." });
100
114
  const DcpConfig = Type.Object({
101
115
  enabled: Type.Optional(Type.Boolean({ description: "Enable DCP (Dynamic Context Pruning)." })),
102
116
  debug: Type.Optional(Type.Boolean({ description: "Enable DCP debug logging." })),
@@ -108,71 +122,10 @@ const DcpConfig = Type.Object({
108
122
  compress: Type.Optional(DcpCompressConfig),
109
123
  strategies: Type.Optional(DcpStrategiesConfig),
110
124
  protectedFilePatterns: Type.Optional(Type.Array(Type.String(), { description: "File path glob patterns whose content is protected from pruning." })),
111
- pruneNotification: Type.Optional(Type.Union([Type.Literal("off"), Type.Literal("minimal"), Type.Literal("detailed")], { description: "Notification level when pruning occurs." })),
125
+ modelOverrides: Type.Optional(Type.Record(Type.String(), DcpConfigOverride, { description: "Per-model partial DCP overrides. Exact keys take precedence over matching wildcards." })),
126
+ pruneNotification: Type.Optional(Type.Never({ description: "Removed legacy key; pruning notifications are no longer configurable." })),
112
127
  }, { description: "DCP (Dynamic Context Pruning) configuration." });
113
128
  // ---------------------------------------------------------------------------
114
- // Async subagents
115
- // ---------------------------------------------------------------------------
116
- const RetryConfig = Type.Object({
117
- maxRetries: Type.Optional(Type.Number({ description: "Maximum retry attempts.", minimum: 0 })),
118
- backoffMs: Type.Optional(Type.Number({ description: "Base delay in ms before first retry.", minimum: 0 })),
119
- retryableExitCodes: Type.Optional(Type.Array(Type.Number(), { description: "Exit codes eligible for retry. Empty array disables retry." })),
120
- }, { description: "Retry configuration for sub-agents." });
121
- const SubagentRoutingConfig = Type.Object({
122
- enabled: Type.Optional(Type.Boolean({ description: "Enable LLM-based automatic role routing." })),
123
- model: Type.Optional(Type.String({ description: "Router model in provider/model form." })),
124
- fallbackModels: Type.Optional(Type.Array(Type.String(), { uniqueItems: true, description: "Ordered router model fallbacks tried when the primary routing model is unavailable or fails. The current parent model is always tried last." })),
125
- maxTaskChars: Type.Optional(Type.Number({ description: "Max task/scope characters sent to router.", minimum: 100 })),
126
- maxTokens: Type.Optional(Type.Number({ description: "Max router response tokens.", minimum: 8 })),
127
- maxRetries: Type.Optional(Type.Number({ description: "Router request retries.", minimum: 0 })),
128
- timeoutMs: Type.Optional(Type.Number({ description: "Router request timeout in ms.", minimum: 1000 })),
129
- debug: Type.Optional(Type.Boolean({ description: "Show routing debug warnings." })),
130
- }, { description: "LLM-based role routing configuration." });
131
- const SubagentVisionConfig = Type.Object({
132
- blindModelPatterns: Type.Optional(Type.Array(Type.String(), { description: "Glob-like model refs treated as unable to inspect images." })),
133
- }, { description: "Vision capability overrides." });
134
- const SubagentPresetTypeOverride = Type.Object({
135
- model: Type.Optional(Type.String({ description: "Model override for this type within the preset." })),
136
- fallbackModels: Type.Optional(Type.Array(Type.String(), { description: "Per-role fallback models." })),
137
- thinking: Type.Optional(Type.String({ description: "Thinking level override." })),
138
- extraArgs: Type.Optional(Type.Array(Type.String(), { description: "Extra CLI arguments." })),
139
- timeoutMs: Type.Optional(Type.Number({ description: "Per-agent wall-clock timeout in ms.", minimum: 1 })),
140
- }, { description: "Per-type override within a preset." });
141
- const SubagentPreset = Type.Object({
142
- description: Type.Optional(Type.String({ description: "Preset description." })),
143
- model: Type.Optional(Type.String({ description: "Default model for this preset." })),
144
- fallbackModels: Type.Optional(Type.Array(Type.String(), { description: "Global fallback models for this preset." })),
145
- thinking: Type.Optional(Type.String({ description: "Default thinking level." })),
146
- extraArgs: Type.Optional(Type.Array(Type.String(), { description: "Extra CLI arguments." })),
147
- timeoutMs: Type.Optional(Type.Number({ description: "Per-agent wall-clock timeout in ms.", minimum: 1 })),
148
- types: Type.Optional(Type.Record(Type.String(), SubagentPresetTypeOverride, { description: "Per-type overrides." })),
149
- }, { description: "Named spawn preset." });
150
- const SubagentTypeConfig = Type.Object({
151
- description: Type.Optional(Type.String({ description: "Role description for routing." })),
152
- model: Type.Optional(Type.String({ description: "Model for this sub-agent type." })),
153
- fallbackModels: Type.Optional(Type.Array(Type.String(), { description: "Ordered model fallbacks." })),
154
- thinking: Type.Optional(Type.String({ description: "Thinking level." })),
155
- tools: Type.Optional(Type.Array(Type.String(), { description: "Enabled tools for this type." })),
156
- isolatedSkills: Type.Optional(Type.Array(Type.String(), { description: "Explicit skill files loaded after disabling normal skill discovery for this type." })),
157
- extraArgs: Type.Optional(Type.Array(Type.String(), { description: "Extra CLI arguments." })),
158
- promptAppend: Type.Optional(Type.String({ description: "Extra prompt text appended after generated prompt." })),
159
- promptOverride: Type.Optional(Type.String({ description: "Full prompt replacement." })),
160
- retry: Type.Optional(RetryConfig),
161
- maxResultBytes: Type.Optional(Type.Number({ description: "Max bytes in result summary.", minimum: 0 })),
162
- timeoutMs: Type.Optional(Type.Number({ description: "Per-agent wall-clock timeout in ms.", minimum: 1 })),
163
- }, { description: "Configuration for a sub-agent type/role." });
164
- const AsyncSubagentsConfig = Type.Object({
165
- defaultType: Type.Optional(Type.String({ description: "Default sub-agent type when not specified." })),
166
- routing: Type.Optional(SubagentRoutingConfig),
167
- vision: Type.Optional(SubagentVisionConfig),
168
- presets: Type.Optional(Type.Record(Type.String(), SubagentPreset, { description: "Named spawn presets." })),
169
- types: Type.Optional(Type.Record(Type.String(), SubagentTypeConfig, { description: "Sub-agent type definitions." })),
170
- maxConcurrent: Type.Optional(Type.Number({ description: "Max concurrent agents per spawn batch (0 = unlimited).", minimum: 0 })),
171
- retry: Type.Optional(RetryConfig),
172
- maxResultBytes: Type.Optional(Type.Number({ description: "Global max bytes in result summary.", minimum: 0 })),
173
- timeoutMs: Type.Optional(Type.Number({ description: "Global per-agent wall-clock timeout in ms.", minimum: 1 })),
174
- }, { description: "Async sub-agent configuration." });
175
- // ---------------------------------------------------------------------------
176
129
  // Prompt commands
177
130
  // ---------------------------------------------------------------------------
178
131
  const PromptCommand = Type.Object({
@@ -186,6 +139,29 @@ const SecretFirewallConfig = Type.Object({
186
139
  sessionHygiene: Type.Optional(Type.Boolean({ description: "Redact detected secret material from tool results and completed messages before it remains in session history." })),
187
140
  notify: Type.Optional(Type.Boolean({ description: "Show a warning when one or more secrets are redacted. Secret values are never included in notifications." })),
188
141
  }, { description: "Settings for the opt-in credential-firewall module." });
142
+ const CommentCheckerConfig = Type.Object({
143
+ enabled: Type.Optional(Type.Boolean({ description: "Enable or disable comment-checker without disabling the whole module." })),
144
+ strictness: Type.Optional(Type.Union([Type.Literal("conservative"), Type.Literal("balanced"), Type.Literal("aggressive")], { description: "Comment classification strictness. Defaults to balanced." })),
145
+ }, { description: "Settings for the comment-checker module." });
146
+ const ContextGatewayBudgetsConfig = Type.Object({
147
+ maxInlineBytes: Type.Optional(Type.Integer({ minimum: 1, maximum: 67108864, description: "Maximum inline size for a proven compact representation. Default 8192." })),
148
+ maxResultBytes: Type.Optional(Type.Integer({ minimum: 1, maximum: 67108864, description: "Budget for shell/other result classes. Selective enforce currently compacts only recognised complete simple test/build output. Default 8192." })),
149
+ maxExactReadBytes: Type.Optional(Type.Integer({ minimum: 1, maximum: 67108864, description: "Read-class observation budget. Reads remain passthrough because snapshot-safe recovery is not available. Default 32768." })),
150
+ maxSearchBytes: Type.Optional(Type.Integer({ minimum: 1, maximum: 67108864, description: "Repo-search/AST/structure observation budget. Gateway does not post-hoc truncate producer output. Default 8192." })),
151
+ maxSearchMatches: Type.Optional(Type.Integer({ minimum: 1, maximum: 1000, description: "Reserved search-match policy budget. Default 12." })),
152
+ }, { description: "Context Gateway class-specific observation and selective-enforcement budgets." });
153
+ const ContextGatewayConfig = Type.Object({
154
+ mode: Type.Optional(Type.Union([Type.Literal("off"), Type.Literal("observe"), Type.Literal("enforce")], { description: "Context Gateway mode. observe is passive; enforce only compacts proven complete simple test/build output and otherwise passes results through." })),
155
+ budgets: Type.Optional(ContextGatewayBudgetsConfig),
156
+ }, { description: "Context Gateway configuration. Default mode is off." });
157
+ const RepoDiscoveryConfig = Type.Object({
158
+ profile: Type.Optional(Type.Union([Type.Literal("baseline"), Type.Literal("native-compact")], { description: "repo_* runtime profile. baseline preserves historical argv/defaults; native-compact enables bounded native flags, cursors, validation, and an explicit per-call full override." })),
159
+ }, { description: "Repository discovery runtime policy. Default profile is baseline." });
160
+ const ResourceRegistryConfig = Type.Object({
161
+ remote: Type.Optional(Type.String({ description: "Git remote URL for the private registry. The repository contains reusable top-level skills/ and agents/ plus project-scoped state under projects/." })),
162
+ branch: Type.Optional(Type.String({ description: "Registry branch used for install/update/push/pull. Defaults to main." })),
163
+ projectKey: Type.Optional(Type.String({ description: "Optional explicit key for this project's projects/<key>/tasks.jsonc, plans/, and TODO.md state. Normally derived from the current Git origin." })),
164
+ }, { description: "Private Git-backed registry for reusable skills/agents and project-scoped tasks/plans state." });
189
165
  // ---------------------------------------------------------------------------
190
166
  // LSP
191
167
  // ---------------------------------------------------------------------------
@@ -234,12 +210,16 @@ export const PiToolsSuiteConfigSchema = Type.Object({
234
210
  ]), { description: "Force per-todo thinking for matching provider/model or bare-model keys. Keys support * and ? wildcards; null removes an inherited override." })),
235
211
  lookupModel: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Vision-capable provider/model used by GLM's lookup tool; unset or null disables lookup." })),
236
212
  terminalBell: Type.Optional(TerminalBellConfig),
213
+ commentChecker: Type.Optional(CommentCheckerConfig),
237
214
  dcp: Type.Optional(DcpConfig),
238
- asyncSubagents: Type.Optional(AsyncSubagentsConfig),
239
215
  toolRenderer: Type.Optional(ToolRendererConfig),
240
216
  promptCommands: Type.Optional(PromptCommandsConfig),
241
217
  secretFirewall: Type.Optional(SecretFirewallConfig),
218
+ contextGateway: Type.Optional(ContextGatewayConfig),
219
+ repoDiscovery: Type.Optional(RepoDiscoveryConfig),
220
+ resourceRegistry: Type.Optional(ResourceRegistryConfig),
242
221
  lsp: Type.Optional(LspConfig),
222
+ asyncSubagents: Type.Optional(Type.Never({ description: "Removed legacy key. Configure sub-agents in .pi/agents/*.md and model pools in .pi/agents/presets.jsonc." })),
243
223
  }, {
244
224
  $id: "https://unpkg.com/pi-ui-extend/schemas/pi-tools-suite.json",
245
225
  $schema: "https://json-schema.org/draft-07/schema#",
@@ -64,5 +64,12 @@ export declare const PixConfigSchema: Type.TObject<{
64
64
  label: Type.TString;
65
65
  }>>>;
66
66
  }>>;
67
+ desktop: Type.TOptional<Type.TObject<{
68
+ externalEditor: Type.TOptional<Type.TString>;
69
+ git: Type.TOptional<Type.TObject<{
70
+ reviewModelRef: Type.TOptional<Type.TString>;
71
+ commitMessageModelRef: Type.TOptional<Type.TString>;
72
+ }>>;
73
+ }>>;
67
74
  }>;
68
75
  export type PixConfigSchemaType = Static<typeof PixConfigSchema>;
@@ -69,6 +69,19 @@ const DictationConfig = Type.Object({
69
69
  language: Type.Optional(Type.String({ description: "Selected language code, e.g. 'en' or 'ru'." })),
70
70
  languages: Type.Optional(Type.Record(Type.String(), DictationLanguageModelConfig, { description: "Available language models keyed by language code." })),
71
71
  }, { description: "Voice dictation (Vosk) configuration." });
72
+ const DesktopConfig = Type.Object({
73
+ externalEditor: Type.Optional(Type.String({
74
+ description: "External editor used by Pix Desktop project explorer. Known aliases include zed, code/vscode, cursor, subl/sublime, idea/intellij, and webstorm; an executable path/name is also accepted.",
75
+ })),
76
+ git: Type.Optional(Type.Object({
77
+ reviewModelRef: Type.Optional(Type.String({
78
+ description: "Model reference used by Pix Desktop for LLM review of Git diffs, optionally with a :thinking suffix.",
79
+ })),
80
+ commitMessageModelRef: Type.Optional(Type.String({
81
+ description: "Model reference used by Pix Desktop to generate Git commit messages, optionally with a :thinking suffix.",
82
+ })),
83
+ }, { description: "Pix Desktop Git/Source Control LLM preferences." })),
84
+ }, { description: "Pix Desktop-specific preferences." });
72
85
  // ---------------------------------------------------------------------------
73
86
  // Top-level schema
74
87
  // ---------------------------------------------------------------------------
@@ -85,6 +98,7 @@ export const PixConfigSchema = Type.Object({
85
98
  modelColors: Type.Optional(ModelColorsConfig),
86
99
  iconTheme: Type.Optional(IconThemeConfig),
87
100
  dictation: Type.Optional(DictationConfig),
101
+ desktop: Type.Optional(DesktopConfig),
88
102
  }, {
89
103
  $id: "https://unpkg.com/pi-ui-extend/schemas/pix.json",
90
104
  $schema: "https://json-schema.org/draft-07/schema#",
@@ -0,0 +1,18 @@
1
+ /** JSON Schema for project-scoped Desktop tasks stored in <cwd>/.pi/tasks.jsonc. */
2
+ import { Static, Type } from "typebox";
3
+ export declare const ProjectTasksSchema: Type.TObject<{
4
+ $schema: Type.TOptional<Type.TString>;
5
+ version: Type.TLiteral<1>;
6
+ tasks: Type.TArray<Type.TObject<{
7
+ id: Type.TString;
8
+ title: Type.TString;
9
+ description: Type.TOptional<Type.TString>;
10
+ type: Type.TUnion<[Type.TLiteral<"bug">, Type.TLiteral<"feature">, Type.TLiteral<"improvement">]>;
11
+ status: Type.TUnion<[Type.TLiteral<"backlog">, Type.TLiteral<"todo">, Type.TLiteral<"in-progress">, Type.TLiteral<"done">]>;
12
+ priority: Type.TUnion<[Type.TLiteral<"low">, Type.TLiteral<"medium">, Type.TLiteral<"high">, Type.TLiteral<"urgent">]>;
13
+ sessionId: Type.TOptional<Type.TString>;
14
+ createdAt: Type.TString;
15
+ updatedAt: Type.TString;
16
+ }>>;
17
+ }>;
18
+ export type ProjectTasksSchemaType = Static<typeof ProjectTasksSchema>;
@@ -0,0 +1,41 @@
1
+ /** JSON Schema for project-scoped Desktop tasks stored in <cwd>/.pi/tasks.jsonc. */
2
+ import { Type } from "typebox";
3
+ const TaskType = Type.Union([
4
+ Type.Literal("bug"),
5
+ Type.Literal("feature"),
6
+ Type.Literal("improvement"),
7
+ ]);
8
+ const TaskStatus = Type.Union([
9
+ Type.Literal("backlog"),
10
+ Type.Literal("todo"),
11
+ Type.Literal("in-progress"),
12
+ Type.Literal("done"),
13
+ ]);
14
+ const TaskPriority = Type.Union([
15
+ Type.Literal("low"),
16
+ Type.Literal("medium"),
17
+ Type.Literal("high"),
18
+ Type.Literal("urgent"),
19
+ ]);
20
+ const ProjectTask = Type.Object({
21
+ id: Type.String({ minLength: 1, maxLength: 128, description: "Stable unique task id." }),
22
+ title: Type.String({ minLength: 1, maxLength: 200, description: "Short task title." }),
23
+ description: Type.Optional(Type.String({ maxLength: 10_000, description: "Optional detailed task description." })),
24
+ type: TaskType,
25
+ status: TaskStatus,
26
+ priority: TaskPriority,
27
+ sessionId: Type.Optional(Type.String({ minLength: 1, maxLength: 512, description: "Optional Pi session associated with this task." })),
28
+ createdAt: Type.String({ format: "date-time", description: "RFC 3339 creation timestamp." }),
29
+ updatedAt: Type.String({ format: "date-time", description: "RFC 3339 last-update timestamp." }),
30
+ }, { additionalProperties: false });
31
+ export const ProjectTasksSchema = Type.Object({
32
+ $schema: Type.Optional(Type.String({ description: "JSON Schema URL used by editors for validation and autocomplete." })),
33
+ version: Type.Literal(1, { description: "Task document format version." }),
34
+ tasks: Type.Array(ProjectTask, { maxItems: 10_000, description: "Project tasks. Task ids must be unique." }),
35
+ }, {
36
+ $id: "https://unpkg.com/pi-ui-extend/schemas/tasks.json",
37
+ $schema: "https://json-schema.org/draft-07/schema#",
38
+ title: "Pix Project Tasks",
39
+ description: "Project-scoped task document stored in <cwd>/.pi/tasks.jsonc.",
40
+ additionalProperties: false,
41
+ });
@@ -1,9 +1,15 @@
1
1
  # Spec: Application concurrency and lifecycle safety
2
2
 
3
+ <!-- markdownlint-disable MD013 -->
4
+
3
5
  ## Type
4
6
 
5
7
  Change
6
8
 
9
+ ## Lifecycle
10
+
11
+ Active implemented contract.
12
+
7
13
  ## Goal
8
14
 
9
15
  Keep tab, session, editor, extension UI, persistence, and shutdown work bound to
@@ -4,6 +4,13 @@
4
4
 
5
5
  Change
6
6
 
7
+ ## Lifecycle
8
+
9
+ Historical baseline. This document records the first Desktop vertical slice and
10
+ still contains useful process-host/ACP transport contracts, but it is no longer
11
+ the current feature-scope inventory. Later implemented Desktop behavior is
12
+ specified by the `specs/desktop-*.md` family.
13
+
7
14
  ## Goal
8
15
 
9
16
  Provide a lightweight desktop client for Pix using Tauri 2 and Svelte 5 +
@@ -21,7 +28,11 @@ updates, cancel a running turn, and answer extension elicitations.
21
28
  elicitations.
22
29
  - Development commands and a documented local setup path.
23
30
 
24
- ## Non-goals
31
+ ## MVP-era non-goals
32
+
33
+ The items below were intentionally out of scope for the first slice. Several
34
+ were implemented later and are governed by newer feature specs; they are not
35
+ current product non-goals.
25
36
 
26
37
  - Feature parity with `PiUiExtendApp`.
27
38
  - Voice input, an embedded terminal, custom widgets, image attachments,
@@ -2,123 +2,134 @@
2
2
 
3
3
  ## Type
4
4
 
5
- Change
5
+ As-is
6
+
7
+ ## Lifecycle
8
+
9
+ Active current contract.
6
10
 
7
11
  ## Goal
8
12
 
9
- Add a project-scoped task list to Pix Desktop in a persistent left sidebar and
10
- allow a saved task to start work immediately in a new session tab.
13
+ Provide a project-scoped task list in Pix Desktop and let a saved task start or
14
+ reopen work in a Desktop session without conflating project tasks with the
15
+ agent's session-local todo list.
11
16
 
12
17
  ## Scope
13
18
 
14
19
  - A collapsible, resizable left sidebar with `Tasks` and `Project` tabs.
15
- - A compact task list with create, edit, delete, filtering, and manual status
16
- changes.
17
- - Task type (`bug`, `feature`, `improvement`), status (`backlog`, `todo`,
18
- `in-progress`, `done`), and priority (`low`, `medium`, `high`, `urgent`).
19
- - Project-local persistence in `.pi/tasks.json` with a versioned schema.
20
- - Starting a task in a new ACP session, automatically sending a generated
21
- prompt, marking the task in progress, and storing the new session id.
22
- - Opening an already-linked session instead of creating a duplicate.
20
+ - Create, edit, delete, manual status changes, drag reordering, and moving tasks
21
+ between type groups.
22
+ - Task type (`bug`, `feature`, `improvement`) and status (`backlog`, `todo`,
23
+ `in-progress`, `done`).
24
+ - Persisted priority (`low`, `medium`, `high`, `urgent`). New tasks currently
25
+ receive `medium`; priority is not exposed by the current task editor or rows.
26
+ - Project-local persistence in `.pi/tasks.jsonc` with a versioned schema.
27
+ - One-time migration from the legacy strict-JSON `.pi/tasks.json` file.
28
+ - Starting an unlinked task in a new ACP session and reopening an already-linked
29
+ session instead of creating a duplicate.
30
+ - Task descriptions can contain attachment markers; those attachments are
31
+ resolved when the task prompt is built.
23
32
 
24
33
  ## Non-goals
25
34
 
26
- - Kanban, drag and drop, subtasks, dependencies, assignees, due dates,
27
- comments, or task history.
35
+ - Kanban columns, subtasks, dependencies, assignees, due dates, comments, or
36
+ task history.
37
+ - Task filtering controls in the current sidebar UI.
38
+ - Editing or displaying priority in the current sidebar UI.
28
39
  - Automatic transition to `done` when an agent turn finishes.
29
- - Synchronizing the project task file with the agent's session-local todo list.
40
+ - Synchronizing `.pi/tasks.jsonc` with the agent's session-local todo list.
30
41
  - Concurrent multi-window file merging.
31
42
 
32
43
  ## Behavior
33
44
 
34
- 1. The sidebar is visible below the title/session chrome, starts on `Tasks`,
35
- remembers its width and collapsed state locally, and preserves the main
36
- session workspace at narrow window sizes.
37
- 2. Task rows show a title, type, status, priority, and actions. Status and
38
- priority are communicated by a Lucide icon, text, and color so meaning never
39
- depends on color alone. `done` is green, `in-progress` is yellow, and idle
40
- states are neutral; all colors support light and dark themes.
41
- 3. Creating or editing validates a non-empty title. A description is optional.
42
- 4. Filters affect only the visible list and do not modify persisted ordering.
43
- 5. A missing `.pi/tasks.json` is treated as an empty version-1 document.
44
- 6. Starting an unlinked task creates and selects a new ACP session, persists
45
- its id and `in-progress` status, appends the generated user prompt to the
46
- transcript, and sends it immediately.
47
- 7. Starting a linked task loads that session. A missing/stale linked session is
48
- reported as a recoverable error and does not silently create another one.
49
- 8. Task completion remains manual.
45
+ 1. The sidebar starts on `Tasks`, remembers its width/collapsed state locally,
46
+ and keeps a compact tab rail available while collapsed.
47
+ 2. Tasks are grouped by type. Dragging a task reorders it; dropping it into a
48
+ different group also changes its type and updates `updatedAt`.
49
+ 3. Task rows show title, status, session/run action, edit, and delete controls.
50
+ Status uses icon, text, and color rather than color alone.
51
+ 4. Creating a task requires a non-empty title, starts with status `todo` and
52
+ priority `medium`, and may include description text and attachments.
53
+ 5. Editing changes title, description, and type. Status is changed separately.
54
+ Existing persisted priority is preserved.
55
+ 6. `.pi/tasks.jsonc` is authoritative. Missing `.pi`/task storage produces an
56
+ empty version-1 document. JSONC comments and trailing commas are accepted.
57
+ 7. If `.pi/tasks.jsonc` is absent and legacy `.pi/tasks.json` exists, Pix reads
58
+ and validates the legacy document, adds the schema reference when absent,
59
+ writes `.pi/tasks.jsonc`, deletes the legacy file, and returns the migrated
60
+ document. If JSONC already exists, it wins and the legacy file is ignored.
61
+ 8. Starting an unlinked task creates/selects a new ACP session, persists its
62
+ session id, changes any non-`done` task to `in-progress`, preserves `done`,
63
+ appends the generated task prompt, and sends it immediately.
64
+ 9. Starting a linked task opens that session. A stale/missing linked session is
65
+ a recoverable error and does not silently create another session.
66
+ 10. Task completion remains manual.
50
67
 
51
68
  ## Contracts
52
69
 
53
- - Project file: `.pi/tasks.json` containing `{ version: 1, tasks: [...] }`.
54
- - Every task has a unique id, title, type, status, priority, `createdAt`, and
55
- `updatedAt`; description and `sessionId` may be omitted.
56
- - Tauri exposes narrow read/write commands for this one file. Workspace paths
57
- are validated and `.pi` symlinks may not escape the workspace.
58
- - The webview sends a complete validated document on each mutation. Writes use
59
- a same-directory temporary file and replacement to avoid partial JSON.
70
+ - Project file: `.pi/tasks.jsonc`.
71
+ - Document shape: optional `$schema`, `version: 1`, and `tasks`.
72
+ - Each task has a unique id, title, type, status, priority, `createdAt`, and
73
+ `updatedAt`; description and `sessionId` are optional.
74
+ - Unknown fields, duplicate ids, unsupported enum values, empty titles,
75
+ malformed timestamps, unsupported versions, and oversized documents are
76
+ rejected.
77
+ - The Tauri backend confines task paths to the active workspace, rejects escape
78
+ through `.pi` symlinks, and caps the document at 1 MB.
79
+ - Writes validate the complete document, write a same-directory temporary file,
80
+ and replace the target to avoid partial JSONC files.
60
81
 
61
82
  ## Invariants
62
83
 
63
- - The task document version must be supported before it is displayed or saved.
64
- - Duplicate ids, unknown enum values, empty titles, and malformed timestamps
65
- are rejected.
66
- - A task is linked to at most one session in this MVP.
67
- - Starting a task is disabled while another prompt/session operation is active.
68
- - A failed save does not leave the UI claiming a task state that was not
69
- persisted.
84
+ - Failed validation never replaces the task file.
85
+ - Failed persistence restores the previous in-memory task document.
86
+ - A task is linked to at most one session.
87
+ - Running/reordering/editing is disabled while conflicting task/session work is
88
+ active.
89
+ - Dragging between groups changes only ordering/type; other task fields survive.
70
90
 
71
91
  ## Edge cases
72
92
 
73
- - Switching workspaces discards the previous workspace's in-memory task view
74
- and loads the new project file.
75
- - Empty and missing files have distinct behavior: missing means no tasks;
76
- malformed or empty JSON is an error.
77
- - Save, session creation, and prompt failures remain visible and retryable.
78
- - Collapsing the sidebar keeps its tab rail available; choosing a tab expands
79
- it.
93
+ - Switching workspaces discards the previous in-memory task view and loads the
94
+ new project's task document.
95
+ - Missing storage means no tasks; malformed or empty JSON/JSONC is an error.
96
+ - A legacy task file is migrated at most once because successful migration
97
+ removes `.pi/tasks.json`.
98
+ - Save, session creation, attachment preparation, and prompt failures remain
99
+ visible and retryable.
80
100
 
81
101
  ## Related files
82
102
 
83
103
  - `desktop/src/App.svelte`
84
- - `desktop/src/components/`
85
- - `desktop/src/lib/`
86
- - `desktop/src/styles.css`
104
+ - `desktop/src/components/WorkspaceSidebar.svelte`
105
+ - `desktop/src/lib/project-tasks.ts`
106
+ - `desktop/src/lib/project-tasks.test.ts`
87
107
  - `desktop/src-tauri/src/lib.rs`
108
+ - `src/schemas/tasks-schema.ts`
88
109
 
89
110
  ## Verification
90
111
 
91
- - Unit tests for task parsing, validation, filtering, prompt generation, and
92
- sidebar preference parsing.
93
- - Rust tests for missing/read/write/malformed files and workspace escape.
94
- - `npm --prefix desktop test`
95
- - `npm --prefix desktop run check`
96
- - `npm --prefix desktop run build:web`
97
- - `cargo test --manifest-path desktop/src-tauri/Cargo.toml`
112
+ - `desktop/src/lib/project-tasks.test.ts` covers parsing, prompt generation, and
113
+ drag/reorder semantics.
114
+ - Rust tests in `desktop/src-tauri/src/lib.rs` cover missing/read/write/malformed
115
+ JSONC, validation, workspace confinement, and legacy JSON migration.
116
+ - Run `npm --prefix desktop test`, `npm --prefix desktop run check`, and
117
+ `cargo test --manifest-path desktop/src-tauri/Cargo.toml`.
98
118
 
99
119
  ## Risks / unknowns
100
120
 
101
- - Whole-document writes assume one active desktop writer per project.
102
- - A linked session may later be removed outside Pix Desktop; the first version
103
- reports this rather than automatically unlinking it.
104
- - Windows replacement semantics may require a fallback around replacing an
105
- existing task file while retaining best-effort crash safety.
121
+ - Whole-document writes assume one active writer per project; multi-window merge
122
+ semantics remain out of scope.
123
+ - A linked session can be removed outside Pix Desktop and remains linked until
124
+ the user repairs or edits the task.
125
+ - Priority remains part of the persisted schema although the current UI does not
126
+ expose priority editing.
106
127
 
107
128
  ## Evidence
108
129
 
109
- - Confirmed by code: desktop session creation/loading and prompt submission are
110
- orchestrated in `desktop/src/App.svelte` through `AcpClient`.
111
- - Confirmed by code: Tauri already validates workspace-contained project file
112
- reads in `desktop/src-tauri/src/lib.rs`.
113
- - Confirmed by tests: session tab ordering and ACP request behavior have focused
114
- Vitest coverage under `desktop/src/lib/`.
115
- - Confirmed by docs: `DESIGN.md` specifies a compact persistent sidebar,
116
- semantic theme roles, Lucide icons, and non-color status cues.
117
- - Implemented: `.pi/tasks.json` now has an explicit version-1 contract and
118
- rejects unsupported versions rather than guessing a migration.
119
- - Verified by tests: all 82 desktop Vitest tests and all 8 Rust tests pass;
120
- Svelte/TypeScript checks report no errors or warnings.
121
- - Verified visually: browser QA covered clean preference state, resize/collapse,
122
- tabs, filters, and semantic colors in light/dark themes.
123
- - Verified natively: actual Tauri UI/backend CRUD, Run, persisted linkage,
124
- Open session without duplication, and delete cleanup all passed.
130
+ - Confirmed by code: `desktop/src/App.svelte`,
131
+ `desktop/src/components/WorkspaceSidebar.svelte`, and
132
+ `desktop/src/lib/project-tasks.ts` implement the current task lifecycle.
133
+ - Confirmed by code: `desktop/src-tauri/src/lib.rs` owns JSONC persistence,
134
+ validation, confinement, atomic replacement, and legacy migration.
135
+ - Confirmed by tests: project-task unit tests and Tauri task persistence tests.