wind-agent-cli 1.38.2 → 1.39.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 (185) hide show
  1. package/README.md +8 -1
  2. package/dist/api-docs.html +4 -3
  3. package/dist/assets/{CodeEditor-C6BtLdoC.js → CodeEditor-C8eMIGen.js} +1 -1
  4. package/dist/assets/{FileTree-CGxNFZda.js → FileTree-D52nAPO4.js} +1 -1
  5. package/dist/assets/{GitPanel-BV-IAyEC.js → GitPanel-DgeKbZsW.js} +1 -1
  6. package/dist/assets/{MarkdownPreview-DVoEzpoc.js → MarkdownPreview-B00VQTx6.js} +1 -1
  7. package/dist/assets/{Shell-CUTO-THd.js → Shell-twzi9t5B.js} +7 -7
  8. package/dist/assets/{StandaloneShell-BKM1qxUc.js → StandaloneShell-DH_GBJMj.js} +1 -1
  9. package/dist/assets/{TaskMasterPanel-DDDCVF6M.js → TaskMasterPanel-BY-pMPMl.js} +2 -2
  10. package/dist/assets/{abnfDiagram-N423BO3Z-Crubtav6.js → abnfDiagram-N423BO3Z-BwBdstkH.js} +1 -1
  11. package/dist/assets/{arc-h9xb7EWA.js → arc-C0IOpbFP.js} +1 -1
  12. package/dist/assets/{architectureDiagram-T3A2C74G-BmMQp4MC.js → architectureDiagram-T3A2C74G-C-Wkj_AY.js} +1 -1
  13. package/dist/assets/{blockDiagram-VBNYF7ZC-BtD-zDnJ.js → blockDiagram-VBNYF7ZC-_sXAZKku.js} +1 -1
  14. package/dist/assets/{c4Diagram-5PPSVZJV-D18s6J-v.js → c4Diagram-5PPSVZJV-C0DUsZv8.js} +1 -1
  15. package/dist/assets/channel-B6xiB58V.js +1 -0
  16. package/dist/assets/{chunk-2GRJ4B5K-BHIcCvB_.js → chunk-2GRJ4B5K-D2TFB_6A.js} +1 -1
  17. package/dist/assets/{chunk-2Q5K7J3B-DcX4AeRe.js → chunk-2Q5K7J3B-B_co_2bI.js} +1 -1
  18. package/dist/assets/{chunk-5RXB4S5H-CZwYDv9K.js → chunk-5RXB4S5H-BcUSLq4H.js} +1 -1
  19. package/dist/assets/{chunk-5VM5RSS4-CR-F7R13.js → chunk-5VM5RSS4-0mQFBKSp.js} +1 -1
  20. package/dist/assets/{chunk-6Q2QTUOP-BPP216SB.js → chunk-6Q2QTUOP-BAkqmW1M.js} +1 -1
  21. package/dist/assets/{chunk-GF5L2VYU-CcFeSmHe.js → chunk-GF5L2VYU-Cyz0Xl7f.js} +1 -1
  22. package/dist/assets/{chunk-JWPE2WC7-C5T_9-OI.js → chunk-JWPE2WC7-DYO47aT-.js} +1 -1
  23. package/dist/assets/{chunk-KBJHAD2P-g5eR5X5h.js → chunk-KBJHAD2P-DvXEzV8J.js} +1 -1
  24. package/dist/assets/{chunk-RYQCIY6F-76WBDsoZ.js → chunk-RYQCIY6F-Beut8dYt.js} +1 -1
  25. package/dist/assets/{chunk-XXDRQBXY-DGuy3LLn.js → chunk-XXDRQBXY-DLIy1GFl.js} +1 -1
  26. package/dist/assets/classDiagram-JCYQIIEL-C7xIPTll.js +1 -0
  27. package/dist/assets/classDiagram-v2-OCEON4UE-C7xIPTll.js +1 -0
  28. package/dist/assets/{cose-bilkent-JH36ORCC-Cg4R1M0H.js → cose-bilkent-JH36ORCC-DynbO4dj.js} +1 -1
  29. package/dist/assets/{cynefin-VYW2F7L2-Cbmals-o.js → cynefin-VYW2F7L2-CYTJsH3o.js} +1 -1
  30. package/dist/assets/{cynefinDiagram-MW4NZA55-CI79hpHc.js → cynefinDiagram-MW4NZA55-ftj-lAeE.js} +1 -1
  31. package/dist/assets/{dagre-VZM6K2ZE-r3Uz8Fhv.js → dagre-VZM6K2ZE-C1kSFboe.js} +1 -1
  32. package/dist/assets/{diagram-7IWD3JNH-ClRIkmQw.js → diagram-7IWD3JNH-CqXHTFrY.js} +1 -1
  33. package/dist/assets/{diagram-B4RE2ZJO-C71qkNlS.js → diagram-B4RE2ZJO-B84n3Zko.js} +1 -1
  34. package/dist/assets/{diagram-LBJQPF4R-CC7ShhCC.js → diagram-LBJQPF4R-B710SefS.js} +1 -1
  35. package/dist/assets/{diagram-Q27KOJAE-DPHL4vnP.js → diagram-Q27KOJAE-CmVPl9pq.js} +1 -1
  36. package/dist/assets/{diagram-UB23O5K3-CP_My5E9.js → diagram-UB23O5K3-LozbeaFL.js} +1 -1
  37. package/dist/assets/{ebnfDiagram-BXEA7PRR-BQe4AU9Z.js → ebnfDiagram-BXEA7PRR-B321UBUA.js} +1 -1
  38. package/dist/assets/{erDiagram-JOGREHBK-CFidQ-d5.js → erDiagram-JOGREHBK-CTbw676k.js} +1 -1
  39. package/dist/assets/feature-chat-pY0kOrz2.js +88 -0
  40. package/dist/assets/feature-workspace-FLomYkPq.js +39 -0
  41. package/dist/assets/{flowDiagram-UKHOOZJN-X5fPFxbp.js → flowDiagram-UKHOOZJN-BWVKTWlw.js} +1 -1
  42. package/dist/assets/{ganttDiagram-PKOTCBZU-CILFHjP8.js → ganttDiagram-PKOTCBZU-BAj9eeeX.js} +1 -1
  43. package/dist/assets/{gitGraphDiagram-DS77QQ5N-DCQCtC_g.js → gitGraphDiagram-DS77QQ5N-DcVMRU7N.js} +1 -1
  44. package/dist/assets/{index-cNARKAN2.js → index-BYFstHFT.js} +1 -1
  45. package/dist/assets/index-BbBSplOU.js +35 -0
  46. package/dist/assets/{index-D0vbG70l.js → index-BrWTp075.js} +1 -1
  47. package/dist/assets/index-CHukVVB2.css +1 -0
  48. package/dist/assets/{index-Bo9nFrtn.js → index-k9M0VaFg.js} +1 -1
  49. package/dist/assets/{infoDiagram-6WML65LV-Da145n1G.js → infoDiagram-6WML65LV-V6wz33B4.js} +1 -1
  50. package/dist/assets/{ishikawaDiagram-WSZJBQD7-DGUP61eq.js → ishikawaDiagram-WSZJBQD7-Dd-WNPyK.js} +1 -1
  51. package/dist/assets/{journeyDiagram-NVQOT4AX-BqfnKWDJ.js → journeyDiagram-NVQOT4AX-D9VCuCeA.js} +1 -1
  52. package/dist/assets/{kanban-definition-27J2QSJJ-US6pQfNy.js → kanban-definition-27J2QSJJ-CUtwDOl-.js} +1 -1
  53. package/dist/assets/{linear-d91wK5kZ.js → linear-D_XGf1jO.js} +1 -1
  54. package/dist/assets/{mermaid.core-BsWqrIaW.js → mermaid.core-DOnsXioB.js} +4 -4
  55. package/dist/assets/{mindmap-definition-FAOFIHXS-CXWD2UbS.js → mindmap-definition-FAOFIHXS-DAYNL5ZV.js} +1 -1
  56. package/dist/assets/{pegDiagram-VL7TDLO6-BVzfDR-8.js → pegDiagram-VL7TDLO6-B5FX3-E6.js} +1 -1
  57. package/dist/assets/{pieDiagram-7S7Q4E2Y-PNpHZnJ9.js → pieDiagram-7S7Q4E2Y-EbnDBVd0.js} +1 -1
  58. package/dist/assets/{quadrantDiagram-CIZ2JOQS-DxCI4i3K.js → quadrantDiagram-CIZ2JOQS-C1koOtwT.js} +1 -1
  59. package/dist/assets/{railroadDiagram-AXF67PYL-BYkvo3om.js → railroadDiagram-AXF67PYL-D52xmshj.js} +1 -1
  60. package/dist/assets/{requirementDiagram-LRYGKXZP-LUtvOYtV.js → requirementDiagram-LRYGKXZP-B7VbfNnW.js} +1 -1
  61. package/dist/assets/{sankeyDiagram-W5VNT64P-CVyI8YeM.js → sankeyDiagram-W5VNT64P-CeRG1vHN.js} +1 -1
  62. package/dist/assets/{sequenceDiagram-SI44F4Z6-1vuRFolM.js → sequenceDiagram-SI44F4Z6-CfKHnmSE.js} +1 -1
  63. package/dist/assets/{sizeCapture-X5ZJPWSS-o4oVZXe8.js → sizeCapture-X5ZJPWSS-DJTzyd0S.js} +1 -1
  64. package/dist/assets/{stateDiagram-OKZ733FA-WedLIlXF.js → stateDiagram-OKZ733FA-DN0c7IYD.js} +1 -1
  65. package/dist/assets/stateDiagram-v2-UEYNNEHI-DbihlVOQ.js +1 -0
  66. package/dist/assets/{swimlanes-SLNWSIFB-CpeXuk67.js → swimlanes-SLNWSIFB-CaaJpBDA.js} +2 -2
  67. package/dist/assets/swimlanesDiagram-ULZ7WXOC-BBRevuGP.js +8 -0
  68. package/dist/assets/{timeline-definition-Z64GVDOM-DFcPWe47.js → timeline-definition-Z64GVDOM-CrzHN7d2.js} +1 -1
  69. package/dist/assets/{vennDiagram-T6HMQDX7-BH8C1C49.js → vennDiagram-T6HMQDX7-BTldHIjo.js} +1 -1
  70. package/dist/assets/{wardleyDiagram-T6FBY63Y-K1Ew0uit.js → wardleyDiagram-T6FBY63Y-D9Y50OsZ.js} +1 -1
  71. package/dist/assets/{xychartDiagram-ELKLHX3M-CQHlqWY2.js → xychartDiagram-ELKLHX3M-DjxSJ3aF.js} +1 -1
  72. package/dist/favicon.png +0 -0
  73. package/dist/icons/icon-128x128.png +0 -0
  74. package/dist/icons/icon-144x144.png +0 -0
  75. package/dist/icons/icon-152x152.png +0 -0
  76. package/dist/icons/icon-180x180.png +0 -0
  77. package/dist/icons/icon-192x192.png +0 -0
  78. package/dist/icons/icon-384x384.png +0 -0
  79. package/dist/icons/icon-512x512.png +0 -0
  80. package/dist/icons/icon-72x72.png +0 -0
  81. package/dist/icons/icon-96x96.png +0 -0
  82. package/dist/icons/icon-maskable-192x192.png +0 -0
  83. package/dist/icons/icon-maskable-512x512.png +0 -0
  84. package/dist/icons/new.png +0 -0
  85. package/dist/index.html +4 -4
  86. package/dist/logo-128.png +0 -0
  87. package/dist/logo-256.png +0 -0
  88. package/dist/sw.js +1 -1
  89. package/dist-server/server/index.js +2 -0
  90. package/dist-server/server/index.js.map +1 -1
  91. package/dist-server/server/modules/agent/agent.routes.js +14 -2
  92. package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
  93. package/dist-server/server/modules/agent/tests/agent.routes.test.js +1 -0
  94. package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -1
  95. package/dist-server/server/modules/commands/commands.routes.js +2 -1
  96. package/dist-server/server/modules/commands/commands.routes.js.map +1 -1
  97. package/dist-server/server/modules/database/migrations.js +43 -0
  98. package/dist-server/server/modules/database/migrations.js.map +1 -1
  99. package/dist-server/server/modules/database/tests/projects.db.integration.test.js +24 -1
  100. package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -1
  101. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +2 -0
  102. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
  103. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +10 -1
  104. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
  105. package/dist-server/server/modules/providers/list/pi/index.js +5 -0
  106. package/dist-server/server/modules/providers/list/pi/index.js.map +1 -0
  107. package/dist-server/server/modules/providers/list/pi/pi-auth.provider.js +55 -0
  108. package/dist-server/server/modules/providers/list/pi/pi-auth.provider.js.map +1 -0
  109. package/dist-server/server/modules/providers/list/pi/pi-mcp.provider.js +23 -0
  110. package/dist-server/server/modules/providers/list/pi/pi-mcp.provider.js.map +1 -0
  111. package/dist-server/server/modules/providers/list/pi/pi-models.provider.js +99 -0
  112. package/dist-server/server/modules/providers/list/pi/pi-models.provider.js.map +1 -0
  113. package/dist-server/server/modules/providers/list/pi/pi-runtime.provider.js +262 -0
  114. package/dist-server/server/modules/providers/list/pi/pi-runtime.provider.js.map +1 -0
  115. package/dist-server/server/modules/providers/list/pi/pi-session-synchronizer.provider.js +136 -0
  116. package/dist-server/server/modules/providers/list/pi/pi-session-synchronizer.provider.js.map +1 -0
  117. package/dist-server/server/modules/providers/list/pi/pi-sessions.provider.js +225 -0
  118. package/dist-server/server/modules/providers/list/pi/pi-sessions.provider.js.map +1 -0
  119. package/dist-server/server/modules/providers/list/pi/pi-skills.provider.js +31 -0
  120. package/dist-server/server/modules/providers/list/pi/pi-skills.provider.js.map +1 -0
  121. package/dist-server/server/modules/providers/list/pi/pi.provider.js +22 -0
  122. package/dist-server/server/modules/providers/list/pi/pi.provider.js.map +1 -0
  123. package/dist-server/server/modules/providers/provider.registry.js +2 -0
  124. package/dist-server/server/modules/providers/provider.registry.js.map +1 -1
  125. package/dist-server/server/modules/providers/provider.routes.js +2 -1
  126. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  127. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +26 -0
  128. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
  129. package/dist-server/server/modules/providers/services/session-synchronizer.service.js +1 -0
  130. package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
  131. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +5 -0
  132. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  133. package/dist-server/server/modules/providers/tests/mcp.test.js +4 -2
  134. package/dist-server/server/modules/providers/tests/mcp.test.js.map +1 -1
  135. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +56 -5
  136. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
  137. package/dist-server/server/modules/providers/tests/pi-provider.test.js +244 -0
  138. package/dist-server/server/modules/providers/tests/pi-provider.test.js.map +1 -0
  139. package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js +9 -0
  140. package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js.map +1 -1
  141. package/dist-server/server/shared/utils.js +19 -0
  142. package/dist-server/server/shared/utils.js.map +1 -1
  143. package/package.json +1 -1
  144. package/public/api-docs.html +4 -3
  145. package/scripts/generate-pwa-icons.ps1 +1 -1
  146. package/server/index.ts +2 -0
  147. package/server/modules/agent/agent.module.ts +1 -1
  148. package/server/modules/agent/agent.routes.ts +15 -2
  149. package/server/modules/agent/tests/agent.routes.test.ts +1 -0
  150. package/server/modules/commands/commands.routes.ts +2 -1
  151. package/server/modules/database/migrations.ts +52 -0
  152. package/server/modules/database/tests/projects.db.integration.test.ts +28 -1
  153. package/server/modules/notifications/services/notification-orchestrator.service.js +2 -0
  154. package/server/modules/providers/README.md +5 -1
  155. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +18 -0
  156. package/server/modules/providers/list/pi/index.ts +4 -0
  157. package/server/modules/providers/list/pi/pi-auth.provider.ts +58 -0
  158. package/server/modules/providers/list/pi/pi-mcp.provider.ts +33 -0
  159. package/server/modules/providers/list/pi/pi-models.provider.ts +114 -0
  160. package/server/modules/providers/list/pi/pi-runtime.provider.ts +271 -0
  161. package/server/modules/providers/list/pi/pi-session-synchronizer.provider.ts +134 -0
  162. package/server/modules/providers/list/pi/pi-sessions.provider.ts +206 -0
  163. package/server/modules/providers/list/pi/pi-skills.provider.ts +37 -0
  164. package/server/modules/providers/list/pi/pi.provider.ts +33 -0
  165. package/server/modules/providers/provider.registry.ts +2 -0
  166. package/server/modules/providers/provider.routes.ts +1 -0
  167. package/server/modules/providers/services/provider-capabilities.service.ts +32 -0
  168. package/server/modules/providers/services/session-synchronizer.service.ts +1 -0
  169. package/server/modules/providers/services/sessions-watcher.service.ts +5 -0
  170. package/server/modules/providers/tests/mcp.test.ts +4 -2
  171. package/server/modules/providers/tests/opencode-sessions.test.ts +81 -6
  172. package/server/modules/providers/tests/pi-provider.test.ts +244 -0
  173. package/server/modules/providers/tests/provider-runtime.service.test.ts +10 -0
  174. package/server/shared/types.ts +1 -1
  175. package/server/shared/utils.ts +23 -0
  176. package/dist/assets/channel-CuR3zGvy.js +0 -1
  177. package/dist/assets/classDiagram-JCYQIIEL-BWpl-4NG.js +0 -1
  178. package/dist/assets/classDiagram-v2-OCEON4UE-BWpl-4NG.js +0 -1
  179. package/dist/assets/feature-chat-CHKsDTs-.js +0 -88
  180. package/dist/assets/feature-workspace-BodovOw6.js +0 -39
  181. package/dist/assets/index-Bn37f1jp.js +0 -35
  182. package/dist/assets/index-Cr5MbHuj.css +0 -1
  183. package/dist/assets/stateDiagram-v2-UEYNNEHI-BqzvLVVB.js +0 -1
  184. package/dist/assets/swimlanesDiagram-ULZ7WXOC-CeI6fByR.js +0 -8
  185. package/dist/icons/new-icon.png +0 -0
@@ -0,0 +1,244 @@
1
+ import assert from 'node:assert/strict';
2
+ import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import test from 'node:test';
6
+
7
+ import type { AnyRecord, ProviderRuntimeContext } from '@/shared/types.js';
8
+ import { PiMcpProvider } from '@/modules/providers/list/pi/pi-mcp.provider.js';
9
+ import { parsePiModelsTable } from '@/modules/providers/list/pi/pi-models.provider.js';
10
+ import { abortPiSession, runPi } from '@/modules/providers/list/pi/pi-runtime.provider.js';
11
+ import { PiSessionsProvider } from '@/modules/providers/list/pi/pi-sessions.provider.js';
12
+ import { PiSessionSynchronizer } from '@/modules/providers/list/pi/pi-session-synchronizer.provider.js';
13
+ import { closeConnection, initializeDatabase, sessionsDb } from '@/modules/database/index.js';
14
+ import { AppError } from '@/shared/utils.js';
15
+
16
+ test('Pi model discovery parses fixed columns and preserves provider/model ids', () => {
17
+ const models = parsePiModelsTable([
18
+ 'provider model context max-out thinking images',
19
+ 'anthropic claude/sonnet-special 200K 64K yes yes',
20
+ 'openai gpt-4o 128K 16K no yes',
21
+ ].join('\r\n'));
22
+
23
+ assert.deepEqual(models.map((model) => model.value), [
24
+ 'anthropic/claude/sonnet-special',
25
+ 'openai/gpt-4o',
26
+ ]);
27
+ assert.deepEqual(models[0].effort?.values.map((entry) => entry.value), [
28
+ 'off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max',
29
+ ]);
30
+ assert.equal(models[1].effort, undefined);
31
+ assert.match(models[0].description ?? '', /Images/);
32
+ });
33
+
34
+ test('Pi sessions normalize RPC deltas and tool lifecycle events', () => {
35
+ const sessions = new PiSessionsProvider();
36
+ const text = sessions.normalizeMessage({
37
+ type: 'message_update',
38
+ assistantMessageEvent: { type: 'text_delta', delta: 'hello' },
39
+ }, 'session-1');
40
+ const thinking = sessions.normalizeMessage({
41
+ type: 'message_update',
42
+ assistantMessageEvent: { type: 'thinking_delta', delta: 'reasoning' },
43
+ }, 'session-1');
44
+ const start = sessions.normalizeMessage({
45
+ type: 'tool_execution_start', toolCallId: 'tool-1', toolName: 'read', args: { path: 'a.ts' },
46
+ }, 'session-1');
47
+ const end = sessions.normalizeMessage({
48
+ type: 'tool_execution_end', toolCallId: 'tool-1', toolName: 'read', result: { content: 'done' }, isError: false,
49
+ }, 'session-1');
50
+
51
+ assert.equal(text[0].kind, 'stream_delta');
52
+ assert.equal(text[0].content, 'hello');
53
+ assert.equal(thinking[0].kind, 'thinking');
54
+ assert.equal(start[0].toolId, 'tool-1');
55
+ assert.equal(end[0].kind, 'tool_result');
56
+ assert.equal(end[0].toolResult?.content, 'done');
57
+ });
58
+
59
+ test('Pi synchronizer and history reader restore only the active JSONL branch', { concurrency: false }, async () => {
60
+ const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'pi-history-'));
61
+ const previousDatabasePath = process.env.DATABASE_PATH;
62
+ const previousSessionDir = process.env.PI_CODING_AGENT_SESSION_DIR;
63
+ try {
64
+ const sessionRoot = path.join(tempRoot, 'sessions');
65
+ const workspace = path.join(tempRoot, 'workspace');
66
+ const transcript = path.join(sessionRoot, 'session.jsonl');
67
+ await mkdir(sessionRoot, { recursive: true });
68
+ await mkdir(workspace, { recursive: true });
69
+ const entries = [
70
+ { type: 'session', version: 3, id: 'pi-session-1', cwd: workspace, timestamp: '2026-01-01T00:00:00.000Z' },
71
+ { type: 'message', id: 'u1', parentId: null, timestamp: '2026-01-01T00:00:01.000Z', message: { role: 'user', content: 'root prompt' } },
72
+ { type: 'message', id: 'a-old', parentId: 'u1', timestamp: '2026-01-01T00:00:02.000Z', message: { role: 'assistant', content: [{ type: 'text', text: 'abandoned answer' }] } },
73
+ { type: 'message', id: 'u2', parentId: 'u1', timestamp: '2026-01-01T00:00:03.000Z', message: { role: 'user', content: 'active fork' } },
74
+ { type: 'message', id: 'a2', parentId: 'u2', timestamp: '2026-01-01T00:00:04.000Z', message: {
75
+ role: 'assistant',
76
+ content: [{ type: 'thinking', text: 'considering' }, { type: 'text', text: 'active answer' }],
77
+ usage: { input: 5, output: 4, cacheRead: 1, cacheWrite: 0 },
78
+ } },
79
+ { type: 'session_info', name: 'Active Pi branch' },
80
+ ];
81
+ await writeFile(transcript, `${entries.map((entry) => JSON.stringify(entry)).join('\n')}\n{partial`, 'utf8');
82
+
83
+ closeConnection();
84
+ process.env.DATABASE_PATH = path.join(tempRoot, 'windcli.db');
85
+ process.env.PI_CODING_AGENT_SESSION_DIR = sessionRoot;
86
+ await initializeDatabase();
87
+ assert.equal(await new PiSessionSynchronizer().synchronize(), 1);
88
+ assert.equal(sessionsDb.getSessionById('pi-session-1')?.custom_name, 'Active Pi branch');
89
+
90
+ const history = await new PiSessionsProvider().fetchHistory('pi-session-1', { limit: 2, offset: 0 });
91
+ assert.equal(history.total, 4);
92
+ assert.equal(history.hasMore, true);
93
+ assert.deepEqual(history.messages.map((message) => message.content), ['considering', 'active answer']);
94
+ assert.equal((history.tokenUsage as AnyRecord).used, 10);
95
+ assert.ok(!history.messages.some((message) => message.content === 'abandoned answer'));
96
+ } finally {
97
+ closeConnection();
98
+ if (previousDatabasePath === undefined) delete process.env.DATABASE_PATH;
99
+ else process.env.DATABASE_PATH = previousDatabasePath;
100
+ if (previousSessionDir === undefined) delete process.env.PI_CODING_AGENT_SESSION_DIR;
101
+ else process.env.PI_CODING_AGENT_SESSION_DIR = previousSessionDir;
102
+ await rm(tempRoot, { recursive: true, force: true });
103
+ }
104
+ });
105
+
106
+ test('Pi MCP facet lists no servers and rejects mutation with a stable error', async () => {
107
+ const mcp = new PiMcpProvider();
108
+ assert.deepEqual(await mcp.listServers(), { user: [], local: [], project: [] });
109
+ await assert.rejects(
110
+ () => mcp.upsertServer({ name: 'test', scope: 'user', transport: 'stdio', command: 'test' }),
111
+ (error: unknown) => error instanceof AppError && error.code === 'MCP_NOT_SUPPORTED',
112
+ );
113
+ });
114
+
115
+ const waitForFile = async (filePath: string): Promise<void> => {
116
+ for (let attempt = 0; attempt < 50; attempt += 1) {
117
+ try { await readFile(filePath); return; } catch { await new Promise((resolve) => setTimeout(resolve, 20)); }
118
+ }
119
+ throw new Error(`Timed out waiting for ${filePath}`);
120
+ };
121
+
122
+ test('Pi runtime uses JSONL RPC arguments, streams events, and emits one terminal state', { concurrency: false }, async () => {
123
+ const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'pi-runtime-'));
124
+ const originalPath = process.env.PATH;
125
+ const originalCapture = process.env.PI_FAKE_CAPTURE;
126
+ try {
127
+ const workspace = path.join(tempRoot, 'workspace');
128
+ const capture = path.join(tempRoot, 'capture.json');
129
+ const scriptPath = path.join(tempRoot, 'fake-pi.cjs');
130
+ await mkdir(path.join(workspace, '.pi', 'skills'), { recursive: true });
131
+ await writeFile(path.join(workspace, 'image.png'), 'fake image', 'utf8');
132
+ await writeFile(scriptPath, `
133
+ const fs = require('node:fs');
134
+ const readline = require('node:readline');
135
+ const capture = process.env.PI_FAKE_CAPTURE;
136
+ const seen = [];
137
+ fs.writeFileSync(capture, JSON.stringify({ args: process.argv.slice(2), cwd: process.cwd(), seen }));
138
+ const rl = readline.createInterface({ input: process.stdin });
139
+ rl.on('line', (line) => {
140
+ seen.push(JSON.parse(line));
141
+ fs.writeFileSync(capture, JSON.stringify({ args: process.argv.slice(2), cwd: process.cwd(), seen }));
142
+ const write = (event) => process.stdout.write(JSON.stringify(event) + '\\r\\n');
143
+ write({ type: 'response', command: 'prompt', success: true });
144
+ write({ type: 'message_update', assistantMessageEvent: { type: 'text_delta', delta: 'Hello' } });
145
+ write({ type: 'message_update', assistantMessageEvent: { type: 'thinking_delta', delta: 'Think' } });
146
+ write({ type: 'tool_execution_start', toolCallId: 't1', toolName: 'read', args: { path: 'x' } });
147
+ write({ type: 'tool_execution_end', toolCallId: 't1', toolName: 'read', result: { content: 'ok' }, isError: false });
148
+ write({ type: 'agent_end', messages: [{ role: 'assistant', usage: { input: 2, output: 3, cacheRead: 1, cacheWrite: 0 } }] });
149
+ write({ type: 'agent_end', messages: [] });
150
+ rl.close();
151
+ });
152
+ `, 'utf8');
153
+ await writeFile(path.join(tempRoot, 'pi.cmd'), '@echo off\r\nnode "%~dp0fake-pi.cjs" %*\r\n', 'utf8');
154
+ const unixShim = path.join(tempRoot, 'pi');
155
+ await writeFile(unixShim, `#!/bin/sh\nexec node "${scriptPath.replace(/\\/g, '/')}" "$@"\n`, 'utf8');
156
+ await chmod(unixShim, 0o755);
157
+ process.env.PATH = `${tempRoot}${path.delimiter}${originalPath ?? ''}`;
158
+ process.env.PI_FAKE_CAPTURE = capture;
159
+
160
+ const sent: AnyRecord[] = [];
161
+ const context: ProviderRuntimeContext = {
162
+ resolveProviderSessionId: () => null,
163
+ resolveResumeModel: async () => 'anthropic/claude-sonnet-4-5',
164
+ getProviderModels: async () => ({ OPTIONS: [], DEFAULT: '' }),
165
+ normalizeMessage: (event, sessionId) => new PiSessionsProvider().normalizeMessage(event, sessionId),
166
+ isProviderInstalled: async () => true,
167
+ };
168
+ await runPi('Inspect', {
169
+ sessionId: '11111111-1111-4111-8111-111111111111',
170
+ cwd: workspace,
171
+ model: 'anthropic/claude-sonnet-4-5',
172
+ effort: 'high',
173
+ images: [{ path: path.join(workspace, 'image.png'), mimeType: 'image/png' }],
174
+ files: [{ path: path.join(workspace, 'notes.txt') }],
175
+ }, { send: (message) => sent.push(message as AnyRecord) }, context);
176
+
177
+ const captured = JSON.parse(await readFile(capture, 'utf8')) as { args: string[]; cwd: string; seen: AnyRecord[] };
178
+ assert.equal(captured.cwd, workspace);
179
+ assert.deepEqual(captured.args.slice(0, 3), ['--mode', 'rpc', '--no-approve']);
180
+ assert.ok(captured.args.includes('anthropic/claude-sonnet-4-5'));
181
+ assert.ok(captured.args.includes('--session-id'));
182
+ assert.ok(captured.args.includes('--thinking'));
183
+ assert.ok(captured.args.includes('--skill'));
184
+ assert.equal(captured.seen[0].type, 'prompt');
185
+ assert.equal((captured.seen[0].images as unknown[]).length, 1);
186
+ assert.match(String(captured.seen[0].message), /<files_input>/);
187
+ assert.ok(sent.some((message) => message.kind === 'stream_delta' && message.content === 'Hello'));
188
+ assert.ok(sent.some((message) => message.kind === 'thinking'));
189
+ assert.ok(sent.some((message) => message.kind === 'tool_use'));
190
+ assert.ok(sent.some((message) => message.kind === 'tool_result'));
191
+ assert.ok(sent.some((message) => message.kind === 'status' && (message.tokenBudget as AnyRecord).used === 6));
192
+ assert.equal(sent.filter((message) => message.kind === 'complete').length, 1);
193
+ } finally {
194
+ process.env.PATH = originalPath;
195
+ if (originalCapture === undefined) delete process.env.PI_FAKE_CAPTURE;
196
+ else process.env.PI_FAKE_CAPTURE = originalCapture;
197
+ await rm(tempRoot, { recursive: true, force: true });
198
+ }
199
+ });
200
+
201
+ test('Pi abort sends RPC before stopping the child', { concurrency: false }, async () => {
202
+ const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'pi-abort-'));
203
+ const originalPath = process.env.PATH;
204
+ const originalCapture = process.env.PI_FAKE_CAPTURE;
205
+ try {
206
+ const capture = path.join(tempRoot, 'abort.json');
207
+ const scriptPath = path.join(tempRoot, 'fake-pi.cjs');
208
+ await writeFile(scriptPath, `
209
+ const fs = require('node:fs');
210
+ const readline = require('node:readline');
211
+ const seen = [];
212
+ const save = () => fs.writeFileSync(process.env.PI_FAKE_CAPTURE, JSON.stringify(seen));
213
+ readline.createInterface({ input: process.stdin }).on('line', (line) => {
214
+ seen.push(JSON.parse(line)); save();
215
+ if (seen.at(-1).type === 'abort') process.exit(0);
216
+ });
217
+ `, 'utf8');
218
+ await writeFile(path.join(tempRoot, 'pi.cmd'), '@echo off\r\nnode "%~dp0fake-pi.cjs" %*\r\n', 'utf8');
219
+ const unixShim = path.join(tempRoot, 'pi');
220
+ await writeFile(unixShim, `#!/bin/sh\nexec node "${scriptPath.replace(/\\/g, '/')}" "$@"\n`, 'utf8');
221
+ await chmod(unixShim, 0o755);
222
+ process.env.PATH = `${tempRoot}${path.delimiter}${originalPath ?? ''}`;
223
+ process.env.PI_FAKE_CAPTURE = capture;
224
+ const sessionId = '22222222-2222-4222-8222-222222222222';
225
+ const context: ProviderRuntimeContext = {
226
+ resolveProviderSessionId: () => null,
227
+ resolveResumeModel: async () => undefined,
228
+ getProviderModels: async () => ({ OPTIONS: [], DEFAULT: '' }),
229
+ normalizeMessage: () => [],
230
+ isProviderInstalled: async () => true,
231
+ };
232
+ const running = runPi('Wait', { sessionId, cwd: tempRoot }, { send: () => undefined }, context);
233
+ await waitForFile(capture);
234
+ assert.equal(abortPiSession(sessionId), true);
235
+ await running;
236
+ const seen = JSON.parse(await readFile(capture, 'utf8')) as AnyRecord[];
237
+ assert.deepEqual(seen.map((entry) => entry.type), ['prompt', 'abort']);
238
+ } finally {
239
+ process.env.PATH = originalPath;
240
+ if (originalCapture === undefined) delete process.env.PI_FAKE_CAPTURE;
241
+ else process.env.PI_FAKE_CAPTURE = originalCapture;
242
+ await rm(tempRoot, { recursive: true, force: true });
243
+ }
244
+ });
@@ -2,6 +2,7 @@ import assert from 'node:assert/strict';
2
2
  import test from 'node:test';
3
3
 
4
4
  import { providerRegistry } from '@/modules/providers/provider.registry.js';
5
+ import { providerCapabilitiesService } from '@/modules/providers/services/provider-capabilities.service.js';
5
6
  import { createProviderRuntimeService } from '@/modules/providers/services/provider-runtime.service.js';
6
7
  import type { IProvider, IProviderRuntime } from '@/shared/interfaces.js';
7
8
  import type { LLMProvider } from '@/shared/types.js';
@@ -76,11 +77,20 @@ test('providerRegistry owns one runtime for every registered provider', () => {
76
77
  'codex',
77
78
  'cursor',
78
79
  'opencode',
80
+ 'pi',
79
81
  ]);
80
82
  assert.equal(providers.every((provider) => typeof provider.runtime.run === 'function'), true);
81
83
  assert.equal(providers.every((provider) => typeof provider.runtime.abort === 'function'), true);
82
84
  });
83
85
 
86
+ test('Pi capabilities expose skills while hiding MCP and permission settings', () => {
87
+ const capabilities = providerCapabilitiesService.getProviderCapabilities('pi');
88
+ assert.equal(capabilities.supportsSkills, true);
89
+ assert.equal(capabilities.supportsMcp, false);
90
+ assert.equal(capabilities.supportsPermissionSettings, false);
91
+ assert.equal(capabilities.supportsPermissionRequests, false);
92
+ });
93
+
84
94
  test('dispatches runs and aborts through the runtime owned by providerRegistry', async () => {
85
95
  const calls: unknown[][] = [];
86
96
  const runtime = createRuntime({
@@ -66,7 +66,7 @@ export type AuthenticatedWebSocketRequest = IncomingMessage & {
66
66
  * Use this as the source of truth whenever a function or payload needs to identify
67
67
  * a specific LLM integration.
68
68
  */
69
- export type LLMProvider = 'claude' | 'codex' | 'cursor' | 'opencode';
69
+ export type LLMProvider = 'claude' | 'codex' | 'cursor' | 'opencode' | 'pi';
70
70
 
71
71
  /**
72
72
  * One selectable model row in a provider model catalog.
@@ -211,6 +211,29 @@ export function normalizeProjectPath(inputPath: string): string {
211
211
  return normalized.replace(/[\\/]+$/, '');
212
212
  }
213
213
 
214
+ /**
215
+ * Identifies disposable Multica task directories that OpenCode records under
216
+ * its global project. The Providers module uses this to avoid indexing external
217
+ * automation runs, while the Database module uses the same rule to remove old
218
+ * WindCli index rows. A normal project named `workdir` does not match unless its
219
+ * trailing structure is `multica_workspaces_* / <workspace> / <8 hex> / workdir`.
220
+ */
221
+ export function isTransientMulticaWorkdirPath(inputPath: string): boolean {
222
+ const normalizedPath = normalizeProjectPath(inputPath);
223
+ const segments = normalizedPath.split(/[\\/]+/).filter(Boolean);
224
+ if (segments.length < 4) {
225
+ return false;
226
+ }
227
+
228
+ const [multicaRoot, workspaceId, runId, directoryName] = segments.slice(-4);
229
+ return (
230
+ /^multica_workspaces_.+/i.test(multicaRoot)
231
+ && workspaceId.length > 0
232
+ && /^[0-9a-f]{8}$/i.test(runId)
233
+ && directoryName.toLowerCase() === 'workdir'
234
+ );
235
+ }
236
+
214
237
  /**
215
238
  * Validates that a user-supplied workspace path is safe to use.
216
239
  *
@@ -1 +0,0 @@
1
- import{U as a,O as n}from"./mermaid.core-BsWqrIaW.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as t,C as e}from"./chunk-GF5L2VYU-CcFeSmHe.js";import{_ as i}from"./mermaid.core-BsWqrIaW.js";import"./chunk-5VM5RSS4-CR-F7R13.js";import"./chunk-XXDRQBXY-DGuy3LLn.js";import"./chunk-KBJHAD2P-g5eR5X5h.js";import"./chunk-2GRJ4B5K-BHIcCvB_.js";import"./vendor-data-DsEZeNPr.js";import"./vendor-react-C2GTiUCy.js";import"./feature-workspace-BodovOw6.js";import"./vendor-ui-CAUHSABu.js";import"./vendor-i18n-B6HjLCnN.js";import"./vendor-markdown-DkITEES2.js";var w={parser:t,get db(){return new e},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{w as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as t,C as e}from"./chunk-GF5L2VYU-CcFeSmHe.js";import{_ as i}from"./mermaid.core-BsWqrIaW.js";import"./chunk-5VM5RSS4-CR-F7R13.js";import"./chunk-XXDRQBXY-DGuy3LLn.js";import"./chunk-KBJHAD2P-g5eR5X5h.js";import"./chunk-2GRJ4B5K-BHIcCvB_.js";import"./vendor-data-DsEZeNPr.js";import"./vendor-react-C2GTiUCy.js";import"./feature-workspace-BodovOw6.js";import"./vendor-ui-CAUHSABu.js";import"./vendor-i18n-B6HjLCnN.js";import"./vendor-markdown-DkITEES2.js";var w={parser:t,get db(){return new e},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{w as diagram};