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.
- package/README.md +8 -1
- package/dist/api-docs.html +4 -3
- package/dist/assets/{CodeEditor-C6BtLdoC.js → CodeEditor-C8eMIGen.js} +1 -1
- package/dist/assets/{FileTree-CGxNFZda.js → FileTree-D52nAPO4.js} +1 -1
- package/dist/assets/{GitPanel-BV-IAyEC.js → GitPanel-DgeKbZsW.js} +1 -1
- package/dist/assets/{MarkdownPreview-DVoEzpoc.js → MarkdownPreview-B00VQTx6.js} +1 -1
- package/dist/assets/{Shell-CUTO-THd.js → Shell-twzi9t5B.js} +7 -7
- package/dist/assets/{StandaloneShell-BKM1qxUc.js → StandaloneShell-DH_GBJMj.js} +1 -1
- package/dist/assets/{TaskMasterPanel-DDDCVF6M.js → TaskMasterPanel-BY-pMPMl.js} +2 -2
- package/dist/assets/{abnfDiagram-N423BO3Z-Crubtav6.js → abnfDiagram-N423BO3Z-BwBdstkH.js} +1 -1
- package/dist/assets/{arc-h9xb7EWA.js → arc-C0IOpbFP.js} +1 -1
- package/dist/assets/{architectureDiagram-T3A2C74G-BmMQp4MC.js → architectureDiagram-T3A2C74G-C-Wkj_AY.js} +1 -1
- package/dist/assets/{blockDiagram-VBNYF7ZC-BtD-zDnJ.js → blockDiagram-VBNYF7ZC-_sXAZKku.js} +1 -1
- package/dist/assets/{c4Diagram-5PPSVZJV-D18s6J-v.js → c4Diagram-5PPSVZJV-C0DUsZv8.js} +1 -1
- package/dist/assets/channel-B6xiB58V.js +1 -0
- package/dist/assets/{chunk-2GRJ4B5K-BHIcCvB_.js → chunk-2GRJ4B5K-D2TFB_6A.js} +1 -1
- package/dist/assets/{chunk-2Q5K7J3B-DcX4AeRe.js → chunk-2Q5K7J3B-B_co_2bI.js} +1 -1
- package/dist/assets/{chunk-5RXB4S5H-CZwYDv9K.js → chunk-5RXB4S5H-BcUSLq4H.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-CR-F7R13.js → chunk-5VM5RSS4-0mQFBKSp.js} +1 -1
- package/dist/assets/{chunk-6Q2QTUOP-BPP216SB.js → chunk-6Q2QTUOP-BAkqmW1M.js} +1 -1
- package/dist/assets/{chunk-GF5L2VYU-CcFeSmHe.js → chunk-GF5L2VYU-Cyz0Xl7f.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-C5T_9-OI.js → chunk-JWPE2WC7-DYO47aT-.js} +1 -1
- package/dist/assets/{chunk-KBJHAD2P-g5eR5X5h.js → chunk-KBJHAD2P-DvXEzV8J.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-76WBDsoZ.js → chunk-RYQCIY6F-Beut8dYt.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-DGuy3LLn.js → chunk-XXDRQBXY-DLIy1GFl.js} +1 -1
- package/dist/assets/classDiagram-JCYQIIEL-C7xIPTll.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-C7xIPTll.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-Cg4R1M0H.js → cose-bilkent-JH36ORCC-DynbO4dj.js} +1 -1
- package/dist/assets/{cynefin-VYW2F7L2-Cbmals-o.js → cynefin-VYW2F7L2-CYTJsH3o.js} +1 -1
- package/dist/assets/{cynefinDiagram-MW4NZA55-CI79hpHc.js → cynefinDiagram-MW4NZA55-ftj-lAeE.js} +1 -1
- package/dist/assets/{dagre-VZM6K2ZE-r3Uz8Fhv.js → dagre-VZM6K2ZE-C1kSFboe.js} +1 -1
- package/dist/assets/{diagram-7IWD3JNH-ClRIkmQw.js → diagram-7IWD3JNH-CqXHTFrY.js} +1 -1
- package/dist/assets/{diagram-B4RE2ZJO-C71qkNlS.js → diagram-B4RE2ZJO-B84n3Zko.js} +1 -1
- package/dist/assets/{diagram-LBJQPF4R-CC7ShhCC.js → diagram-LBJQPF4R-B710SefS.js} +1 -1
- package/dist/assets/{diagram-Q27KOJAE-DPHL4vnP.js → diagram-Q27KOJAE-CmVPl9pq.js} +1 -1
- package/dist/assets/{diagram-UB23O5K3-CP_My5E9.js → diagram-UB23O5K3-LozbeaFL.js} +1 -1
- package/dist/assets/{ebnfDiagram-BXEA7PRR-BQe4AU9Z.js → ebnfDiagram-BXEA7PRR-B321UBUA.js} +1 -1
- package/dist/assets/{erDiagram-JOGREHBK-CFidQ-d5.js → erDiagram-JOGREHBK-CTbw676k.js} +1 -1
- package/dist/assets/feature-chat-pY0kOrz2.js +88 -0
- package/dist/assets/feature-workspace-FLomYkPq.js +39 -0
- package/dist/assets/{flowDiagram-UKHOOZJN-X5fPFxbp.js → flowDiagram-UKHOOZJN-BWVKTWlw.js} +1 -1
- package/dist/assets/{ganttDiagram-PKOTCBZU-CILFHjP8.js → ganttDiagram-PKOTCBZU-BAj9eeeX.js} +1 -1
- package/dist/assets/{gitGraphDiagram-DS77QQ5N-DCQCtC_g.js → gitGraphDiagram-DS77QQ5N-DcVMRU7N.js} +1 -1
- package/dist/assets/{index-cNARKAN2.js → index-BYFstHFT.js} +1 -1
- package/dist/assets/index-BbBSplOU.js +35 -0
- package/dist/assets/{index-D0vbG70l.js → index-BrWTp075.js} +1 -1
- package/dist/assets/index-CHukVVB2.css +1 -0
- package/dist/assets/{index-Bo9nFrtn.js → index-k9M0VaFg.js} +1 -1
- package/dist/assets/{infoDiagram-6WML65LV-Da145n1G.js → infoDiagram-6WML65LV-V6wz33B4.js} +1 -1
- package/dist/assets/{ishikawaDiagram-WSZJBQD7-DGUP61eq.js → ishikawaDiagram-WSZJBQD7-Dd-WNPyK.js} +1 -1
- package/dist/assets/{journeyDiagram-NVQOT4AX-BqfnKWDJ.js → journeyDiagram-NVQOT4AX-D9VCuCeA.js} +1 -1
- package/dist/assets/{kanban-definition-27J2QSJJ-US6pQfNy.js → kanban-definition-27J2QSJJ-CUtwDOl-.js} +1 -1
- package/dist/assets/{linear-d91wK5kZ.js → linear-D_XGf1jO.js} +1 -1
- package/dist/assets/{mermaid.core-BsWqrIaW.js → mermaid.core-DOnsXioB.js} +4 -4
- package/dist/assets/{mindmap-definition-FAOFIHXS-CXWD2UbS.js → mindmap-definition-FAOFIHXS-DAYNL5ZV.js} +1 -1
- package/dist/assets/{pegDiagram-VL7TDLO6-BVzfDR-8.js → pegDiagram-VL7TDLO6-B5FX3-E6.js} +1 -1
- package/dist/assets/{pieDiagram-7S7Q4E2Y-PNpHZnJ9.js → pieDiagram-7S7Q4E2Y-EbnDBVd0.js} +1 -1
- package/dist/assets/{quadrantDiagram-CIZ2JOQS-DxCI4i3K.js → quadrantDiagram-CIZ2JOQS-C1koOtwT.js} +1 -1
- package/dist/assets/{railroadDiagram-AXF67PYL-BYkvo3om.js → railroadDiagram-AXF67PYL-D52xmshj.js} +1 -1
- package/dist/assets/{requirementDiagram-LRYGKXZP-LUtvOYtV.js → requirementDiagram-LRYGKXZP-B7VbfNnW.js} +1 -1
- package/dist/assets/{sankeyDiagram-W5VNT64P-CVyI8YeM.js → sankeyDiagram-W5VNT64P-CeRG1vHN.js} +1 -1
- package/dist/assets/{sequenceDiagram-SI44F4Z6-1vuRFolM.js → sequenceDiagram-SI44F4Z6-CfKHnmSE.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-o4oVZXe8.js → sizeCapture-X5ZJPWSS-DJTzyd0S.js} +1 -1
- package/dist/assets/{stateDiagram-OKZ733FA-WedLIlXF.js → stateDiagram-OKZ733FA-DN0c7IYD.js} +1 -1
- package/dist/assets/stateDiagram-v2-UEYNNEHI-DbihlVOQ.js +1 -0
- package/dist/assets/{swimlanes-SLNWSIFB-CpeXuk67.js → swimlanes-SLNWSIFB-CaaJpBDA.js} +2 -2
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-BBRevuGP.js +8 -0
- package/dist/assets/{timeline-definition-Z64GVDOM-DFcPWe47.js → timeline-definition-Z64GVDOM-CrzHN7d2.js} +1 -1
- package/dist/assets/{vennDiagram-T6HMQDX7-BH8C1C49.js → vennDiagram-T6HMQDX7-BTldHIjo.js} +1 -1
- package/dist/assets/{wardleyDiagram-T6FBY63Y-K1Ew0uit.js → wardleyDiagram-T6FBY63Y-D9Y50OsZ.js} +1 -1
- package/dist/assets/{xychartDiagram-ELKLHX3M-CQHlqWY2.js → xychartDiagram-ELKLHX3M-DjxSJ3aF.js} +1 -1
- package/dist/favicon.png +0 -0
- package/dist/icons/icon-128x128.png +0 -0
- package/dist/icons/icon-144x144.png +0 -0
- package/dist/icons/icon-152x152.png +0 -0
- package/dist/icons/icon-180x180.png +0 -0
- package/dist/icons/icon-192x192.png +0 -0
- package/dist/icons/icon-384x384.png +0 -0
- package/dist/icons/icon-512x512.png +0 -0
- package/dist/icons/icon-72x72.png +0 -0
- package/dist/icons/icon-96x96.png +0 -0
- package/dist/icons/icon-maskable-192x192.png +0 -0
- package/dist/icons/icon-maskable-512x512.png +0 -0
- package/dist/icons/new.png +0 -0
- package/dist/index.html +4 -4
- package/dist/logo-128.png +0 -0
- package/dist/logo-256.png +0 -0
- package/dist/sw.js +1 -1
- package/dist-server/server/index.js +2 -0
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/agent/agent.routes.js +14 -2
- package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
- package/dist-server/server/modules/agent/tests/agent.routes.test.js +1 -0
- package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -1
- package/dist-server/server/modules/commands/commands.routes.js +2 -1
- package/dist-server/server/modules/commands/commands.routes.js.map +1 -1
- package/dist-server/server/modules/database/migrations.js +43 -0
- package/dist-server/server/modules/database/migrations.js.map +1 -1
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js +24 -1
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -1
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +2 -0
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +10 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/pi/index.js +5 -0
- package/dist-server/server/modules/providers/list/pi/index.js.map +1 -0
- package/dist-server/server/modules/providers/list/pi/pi-auth.provider.js +55 -0
- package/dist-server/server/modules/providers/list/pi/pi-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/pi/pi-mcp.provider.js +23 -0
- package/dist-server/server/modules/providers/list/pi/pi-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/pi/pi-models.provider.js +99 -0
- package/dist-server/server/modules/providers/list/pi/pi-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/pi/pi-runtime.provider.js +262 -0
- package/dist-server/server/modules/providers/list/pi/pi-runtime.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/pi/pi-session-synchronizer.provider.js +136 -0
- package/dist-server/server/modules/providers/list/pi/pi-session-synchronizer.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/pi/pi-sessions.provider.js +225 -0
- package/dist-server/server/modules/providers/list/pi/pi-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/pi/pi-skills.provider.js +31 -0
- package/dist-server/server/modules/providers/list/pi/pi-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/pi/pi.provider.js +22 -0
- package/dist-server/server/modules/providers/list/pi/pi.provider.js.map +1 -0
- package/dist-server/server/modules/providers/provider.registry.js +2 -0
- package/dist-server/server/modules/providers/provider.registry.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +2 -1
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js +26 -0
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js +1 -0
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js +5 -0
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
- package/dist-server/server/modules/providers/tests/mcp.test.js +4 -2
- package/dist-server/server/modules/providers/tests/mcp.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +56 -5
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/pi-provider.test.js +244 -0
- package/dist-server/server/modules/providers/tests/pi-provider.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js +9 -0
- package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js.map +1 -1
- package/dist-server/server/shared/utils.js +19 -0
- package/dist-server/server/shared/utils.js.map +1 -1
- package/package.json +1 -1
- package/public/api-docs.html +4 -3
- package/scripts/generate-pwa-icons.ps1 +1 -1
- package/server/index.ts +2 -0
- package/server/modules/agent/agent.module.ts +1 -1
- package/server/modules/agent/agent.routes.ts +15 -2
- package/server/modules/agent/tests/agent.routes.test.ts +1 -0
- package/server/modules/commands/commands.routes.ts +2 -1
- package/server/modules/database/migrations.ts +52 -0
- package/server/modules/database/tests/projects.db.integration.test.ts +28 -1
- package/server/modules/notifications/services/notification-orchestrator.service.js +2 -0
- package/server/modules/providers/README.md +5 -1
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +18 -0
- package/server/modules/providers/list/pi/index.ts +4 -0
- package/server/modules/providers/list/pi/pi-auth.provider.ts +58 -0
- package/server/modules/providers/list/pi/pi-mcp.provider.ts +33 -0
- package/server/modules/providers/list/pi/pi-models.provider.ts +114 -0
- package/server/modules/providers/list/pi/pi-runtime.provider.ts +271 -0
- package/server/modules/providers/list/pi/pi-session-synchronizer.provider.ts +134 -0
- package/server/modules/providers/list/pi/pi-sessions.provider.ts +206 -0
- package/server/modules/providers/list/pi/pi-skills.provider.ts +37 -0
- package/server/modules/providers/list/pi/pi.provider.ts +33 -0
- package/server/modules/providers/provider.registry.ts +2 -0
- package/server/modules/providers/provider.routes.ts +1 -0
- package/server/modules/providers/services/provider-capabilities.service.ts +32 -0
- package/server/modules/providers/services/session-synchronizer.service.ts +1 -0
- package/server/modules/providers/services/sessions-watcher.service.ts +5 -0
- package/server/modules/providers/tests/mcp.test.ts +4 -2
- package/server/modules/providers/tests/opencode-sessions.test.ts +81 -6
- package/server/modules/providers/tests/pi-provider.test.ts +244 -0
- package/server/modules/providers/tests/provider-runtime.service.test.ts +10 -0
- package/server/shared/types.ts +1 -1
- package/server/shared/utils.ts +23 -0
- package/dist/assets/channel-CuR3zGvy.js +0 -1
- package/dist/assets/classDiagram-JCYQIIEL-BWpl-4NG.js +0 -1
- package/dist/assets/classDiagram-v2-OCEON4UE-BWpl-4NG.js +0 -1
- package/dist/assets/feature-chat-CHKsDTs-.js +0 -88
- package/dist/assets/feature-workspace-BodovOw6.js +0 -39
- package/dist/assets/index-Bn37f1jp.js +0 -35
- package/dist/assets/index-Cr5MbHuj.css +0 -1
- package/dist/assets/stateDiagram-v2-UEYNNEHI-BqzvLVVB.js +0 -1
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-CeI6fByR.js +0 -8
- package/dist/icons/new-icon.png +0 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
|
|
3
|
+
import { sessionsDb } from '@/modules/database/index.js';
|
|
4
|
+
import { parseFilesInputTag } from '@/shared/image-attachments.js';
|
|
5
|
+
import type { AnyRecord, FetchHistoryOptions, FetchHistoryResult, NormalizedMessage } from '@/shared/types.js';
|
|
6
|
+
import type { IProviderSessions } from '@/shared/interfaces.js';
|
|
7
|
+
import {
|
|
8
|
+
createNormalizedMessage,
|
|
9
|
+
generateMessageId,
|
|
10
|
+
normalizeProviderTimestamp,
|
|
11
|
+
readObjectRecord,
|
|
12
|
+
readOptionalString,
|
|
13
|
+
sliceTailPage,
|
|
14
|
+
} from '@/shared/utils.js';
|
|
15
|
+
|
|
16
|
+
const textFromContent = (content: unknown, type = 'text'): string => {
|
|
17
|
+
if (typeof content === 'string') return content;
|
|
18
|
+
if (!Array.isArray(content)) return '';
|
|
19
|
+
return content.flatMap((part) => {
|
|
20
|
+
const record = readObjectRecord(part);
|
|
21
|
+
return record?.type === type ? [readOptionalString(record.text) ?? ''] : [];
|
|
22
|
+
}).join('');
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const formatToolResult = (result: unknown): string => {
|
|
26
|
+
if (typeof result === 'string') return result;
|
|
27
|
+
const record = readObjectRecord(result);
|
|
28
|
+
const content = record?.content;
|
|
29
|
+
const text = textFromContent(content);
|
|
30
|
+
if (text) return text;
|
|
31
|
+
try { return JSON.stringify(content ?? result, null, 2); } catch { return String(result ?? ''); }
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const tokenBudgetFromEntries = (entries: AnyRecord[]): AnyRecord | undefined => {
|
|
35
|
+
let input = 0;
|
|
36
|
+
let output = 0;
|
|
37
|
+
let cacheRead = 0;
|
|
38
|
+
let cacheWrite = 0;
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
const usage = readObjectRecord(readObjectRecord(entry.message)?.usage) ?? readObjectRecord(entry.usage);
|
|
41
|
+
if (!usage) continue;
|
|
42
|
+
input += Number(usage.input ?? 0);
|
|
43
|
+
output += Number(usage.output ?? 0);
|
|
44
|
+
cacheRead += Number(usage.cacheRead ?? 0);
|
|
45
|
+
cacheWrite += Number(usage.cacheWrite ?? 0);
|
|
46
|
+
}
|
|
47
|
+
const used = input + output + cacheRead + cacheWrite;
|
|
48
|
+
return used > 0 ? {
|
|
49
|
+
used,
|
|
50
|
+
inputTokens: input + cacheRead,
|
|
51
|
+
outputTokens: output,
|
|
52
|
+
breakdown: { input: input + cacheRead, output },
|
|
53
|
+
} : undefined;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const activeBranch = (entries: AnyRecord[]): AnyRecord[] => {
|
|
57
|
+
const withIds = entries.filter((entry) => readOptionalString(entry.id));
|
|
58
|
+
if (withIds.length === 0 || !withIds.some((entry) => readOptionalString(entry.parentId))) return entries;
|
|
59
|
+
const byId = new Map(withIds.map((entry) => [String(entry.id), entry]));
|
|
60
|
+
const branch: AnyRecord[] = [];
|
|
61
|
+
let current: AnyRecord | undefined = withIds[withIds.length - 1];
|
|
62
|
+
const seen = new Set<string>();
|
|
63
|
+
while (current) {
|
|
64
|
+
const id = readOptionalString(current.id);
|
|
65
|
+
if (!id || seen.has(id)) break;
|
|
66
|
+
seen.add(id);
|
|
67
|
+
branch.push(current);
|
|
68
|
+
const parentId = readOptionalString(current.parentId);
|
|
69
|
+
current = parentId ? byId.get(parentId) : undefined;
|
|
70
|
+
}
|
|
71
|
+
return branch.reverse();
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** Normalizes Pi RPC events and JSONL history into WindCli messages. */
|
|
75
|
+
export class PiSessionsProvider implements IProviderSessions {
|
|
76
|
+
normalizeMessage(rawMessage: unknown, sessionId: string | null): NormalizedMessage[] {
|
|
77
|
+
const raw = readObjectRecord(rawMessage);
|
|
78
|
+
if (!raw) return [];
|
|
79
|
+
const type = readOptionalString(raw.type);
|
|
80
|
+
const base = { provider: 'pi' as const, sessionId, timestamp: normalizeProviderTimestamp(raw.timestamp) };
|
|
81
|
+
if (type === 'message_update') {
|
|
82
|
+
const update = readObjectRecord(raw.assistantMessageEvent) ?? readObjectRecord(raw.event);
|
|
83
|
+
const delta = readOptionalString(update?.delta);
|
|
84
|
+
if (!delta) return [];
|
|
85
|
+
if (update?.type === 'text_delta') return [createNormalizedMessage({ ...base, kind: 'stream_delta', content: delta })];
|
|
86
|
+
if (update?.type === 'thinking_delta') return [createNormalizedMessage({ ...base, kind: 'thinking', content: delta })];
|
|
87
|
+
}
|
|
88
|
+
if (type === 'tool_execution_start') {
|
|
89
|
+
return [createNormalizedMessage({
|
|
90
|
+
...base,
|
|
91
|
+
kind: 'tool_use',
|
|
92
|
+
toolId: readOptionalString(raw.toolCallId) ?? generateMessageId('pi-tool'),
|
|
93
|
+
toolName: readOptionalString(raw.toolName) ?? 'Tool',
|
|
94
|
+
toolInput: raw.args ?? {},
|
|
95
|
+
})];
|
|
96
|
+
}
|
|
97
|
+
if (type === 'tool_execution_end') {
|
|
98
|
+
const toolId = readOptionalString(raw.toolCallId) ?? generateMessageId('pi-tool');
|
|
99
|
+
return [createNormalizedMessage({
|
|
100
|
+
...base,
|
|
101
|
+
kind: 'tool_result',
|
|
102
|
+
toolId,
|
|
103
|
+
toolName: readOptionalString(raw.toolName) ?? 'Tool',
|
|
104
|
+
toolResult: { content: formatToolResult(raw.result), isError: Boolean(raw.isError) },
|
|
105
|
+
})];
|
|
106
|
+
}
|
|
107
|
+
if (type === 'agent_end' && raw.willRetry !== true) {
|
|
108
|
+
return [createNormalizedMessage({ ...base, kind: 'stream_end' })];
|
|
109
|
+
}
|
|
110
|
+
if (type === 'error' || type === 'fault') {
|
|
111
|
+
return [createNormalizedMessage({
|
|
112
|
+
...base,
|
|
113
|
+
kind: 'error',
|
|
114
|
+
content: readOptionalString(raw.message) ?? readOptionalString(raw.error) ?? 'Unknown Pi error',
|
|
115
|
+
})];
|
|
116
|
+
}
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async fetchHistory(sessionId: string, options: FetchHistoryOptions = {}): Promise<FetchHistoryResult> {
|
|
121
|
+
const row = sessionsDb.getSessionById(sessionId);
|
|
122
|
+
if (!row?.jsonl_path) return { messages: [], total: 0, hasMore: false, offset: 0, limit: null };
|
|
123
|
+
try {
|
|
124
|
+
const entries = (await readFile(row.jsonl_path, 'utf8')).split(/\r?\n/).flatMap((line) => {
|
|
125
|
+
if (!line.trim()) return [];
|
|
126
|
+
try { return [JSON.parse(line) as AnyRecord]; } catch { return []; }
|
|
127
|
+
});
|
|
128
|
+
const branch = activeBranch(entries.filter((entry) => entry.type !== 'session'));
|
|
129
|
+
const messages = this.normalizeHistory(branch, sessionId);
|
|
130
|
+
const offset = Math.max(0, options.offset ?? 0);
|
|
131
|
+
const limit = options.limit === undefined ? null : options.limit === null ? null : Math.max(0, options.limit);
|
|
132
|
+
const { page, hasMore } = sliceTailPage(messages, limit, offset);
|
|
133
|
+
return {
|
|
134
|
+
messages: page,
|
|
135
|
+
total: messages.length,
|
|
136
|
+
hasMore,
|
|
137
|
+
offset,
|
|
138
|
+
limit,
|
|
139
|
+
tokenUsage: tokenBudgetFromEntries(branch),
|
|
140
|
+
};
|
|
141
|
+
} catch {
|
|
142
|
+
return { messages: [], total: 0, hasMore: false, offset: 0, limit: null };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private normalizeHistory(entries: AnyRecord[], sessionId: string): NormalizedMessage[] {
|
|
147
|
+
const messages: NormalizedMessage[] = [];
|
|
148
|
+
const tools = new Map<string, NormalizedMessage>();
|
|
149
|
+
for (const entry of entries) {
|
|
150
|
+
const id = readOptionalString(entry.id) ?? generateMessageId('pi-history');
|
|
151
|
+
const timestamp = normalizeProviderTimestamp(entry.timestamp);
|
|
152
|
+
if (entry.type === 'message') {
|
|
153
|
+
const message = readObjectRecord(entry.message);
|
|
154
|
+
const role = readOptionalString(message?.role);
|
|
155
|
+
if (role === 'user') {
|
|
156
|
+
const parsed = parseFilesInputTag(textFromContent(message?.content));
|
|
157
|
+
messages.push(createNormalizedMessage({
|
|
158
|
+
id, provider: 'pi', sessionId, timestamp, kind: 'text', role: 'user',
|
|
159
|
+
content: parsed.text, ...(parsed.attachments.length ? { files: parsed.attachments } : {}),
|
|
160
|
+
}));
|
|
161
|
+
} else if (role === 'assistant') {
|
|
162
|
+
const content = Array.isArray(message?.content) ? message.content : [];
|
|
163
|
+
content.forEach((part, index) => {
|
|
164
|
+
const block = readObjectRecord(part);
|
|
165
|
+
if (!block) return;
|
|
166
|
+
const blockId = `${id}:${index}`;
|
|
167
|
+
if (block.type === 'text' || block.type === 'thinking') {
|
|
168
|
+
const text = readOptionalString(block.text);
|
|
169
|
+
if (text) messages.push(createNormalizedMessage({
|
|
170
|
+
id: blockId, provider: 'pi', sessionId, timestamp,
|
|
171
|
+
kind: block.type === 'thinking' ? 'thinking' : 'text', role: 'assistant', content: text,
|
|
172
|
+
}));
|
|
173
|
+
} else if (block.type === 'toolCall' || block.type === 'tool_use') {
|
|
174
|
+
const toolId = readOptionalString(block.id) ?? blockId;
|
|
175
|
+
const normalized = createNormalizedMessage({
|
|
176
|
+
id: blockId, provider: 'pi', sessionId, timestamp, kind: 'tool_use',
|
|
177
|
+
toolId, toolName: readOptionalString(block.name) ?? 'Tool', toolInput: block.arguments ?? block.input ?? {},
|
|
178
|
+
});
|
|
179
|
+
tools.set(toolId, normalized);
|
|
180
|
+
messages.push(normalized);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
} else if (role === 'toolResult' || role === 'tool_result') {
|
|
184
|
+
const toolId = readOptionalString(message?.toolCallId) ?? readOptionalString(message?.tool_use_id);
|
|
185
|
+
const result = { content: formatToolResult(message), isError: Boolean(message?.isError ?? message?.is_error) };
|
|
186
|
+
const tool = toolId ? tools.get(toolId) : undefined;
|
|
187
|
+
if (tool) tool.toolResult = result;
|
|
188
|
+
else messages.push(createNormalizedMessage({
|
|
189
|
+
id, provider: 'pi', sessionId, timestamp, kind: 'tool_result', toolId, toolResult: result,
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
} else if (entry.type === 'compaction') {
|
|
193
|
+
const content = readOptionalString(entry.summary);
|
|
194
|
+
if (content) messages.push(createNormalizedMessage({
|
|
195
|
+
id, provider: 'pi', sessionId, timestamp, kind: 'text', role: 'assistant', content, isCompactSummary: true,
|
|
196
|
+
}));
|
|
197
|
+
} else if (entry.type === 'custom_message' && entry.display !== false) {
|
|
198
|
+
const content = textFromContent(entry.content);
|
|
199
|
+
if (content) messages.push(createNormalizedMessage({
|
|
200
|
+
id, provider: 'pi', sessionId, timestamp, kind: 'text', role: 'assistant', content,
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return messages;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { SkillsProvider } from '@/modules/providers/shared/skills/skills.provider.js';
|
|
5
|
+
import type { ProviderSkillSource } from '@/shared/types.js';
|
|
6
|
+
import { addUniqueProviderSkillSource, findTopmostGitRoot } from '@/shared/utils.js';
|
|
7
|
+
|
|
8
|
+
/** Discovers Pi-native and cross-agent skills using Pi's `/skill:<name>` syntax. */
|
|
9
|
+
export class PiSkillsProvider extends SkillsProvider {
|
|
10
|
+
constructor() {
|
|
11
|
+
super('pi');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
protected async getSkillSources(workspacePath: string): Promise<ProviderSkillSource[]> {
|
|
15
|
+
const sources: ProviderSkillSource[] = [];
|
|
16
|
+
const seen = new Set<string>();
|
|
17
|
+
const add = (scope: 'user' | 'project', rootDir: string) => addUniqueProviderSkillSource(
|
|
18
|
+
sources,
|
|
19
|
+
seen,
|
|
20
|
+
{ scope, rootDir, commandForSkill: (skillName) => `/skill:${skillName}` },
|
|
21
|
+
);
|
|
22
|
+
add('user', path.join(os.homedir(), '.pi', 'agent', 'skills'));
|
|
23
|
+
add('user', path.join(os.homedir(), '.agents', 'skills'));
|
|
24
|
+
add('project', path.join(workspacePath, '.pi', 'skills'));
|
|
25
|
+
|
|
26
|
+
const repoRoot = await findTopmostGitRoot(workspacePath);
|
|
27
|
+
let current = path.resolve(workspacePath);
|
|
28
|
+
while (true) {
|
|
29
|
+
add('project', path.join(current, '.agents', 'skills'));
|
|
30
|
+
if (!repoRoot || current === path.resolve(repoRoot)) break;
|
|
31
|
+
const parent = path.dirname(current);
|
|
32
|
+
if (parent === current) break;
|
|
33
|
+
current = parent;
|
|
34
|
+
}
|
|
35
|
+
return sources;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IProviderAuth,
|
|
3
|
+
IProviderMcp,
|
|
4
|
+
IProviderModels,
|
|
5
|
+
IProviderRuntime,
|
|
6
|
+
IProviderSessionSynchronizer,
|
|
7
|
+
IProviderSessions,
|
|
8
|
+
IProviderSkills,
|
|
9
|
+
} from '@/shared/interfaces.js';
|
|
10
|
+
import { AbstractProvider } from '@/modules/providers/shared/base/abstract.provider.js';
|
|
11
|
+
|
|
12
|
+
import { PiProviderAuth } from './pi-auth.provider.js';
|
|
13
|
+
import { PiMcpProvider } from './pi-mcp.provider.js';
|
|
14
|
+
import { PiProviderModels } from './pi-models.provider.js';
|
|
15
|
+
import { piRuntime } from './pi-runtime.provider.js';
|
|
16
|
+
import { PiSessionSynchronizer } from './pi-session-synchronizer.provider.js';
|
|
17
|
+
import { PiSessionsProvider } from './pi-sessions.provider.js';
|
|
18
|
+
import { PiSkillsProvider } from './pi-skills.provider.js';
|
|
19
|
+
|
|
20
|
+
/** Registry wrapper that composes all seven Pi provider facets. */
|
|
21
|
+
export class PiProvider extends AbstractProvider {
|
|
22
|
+
readonly runtime: IProviderRuntime = piRuntime;
|
|
23
|
+
readonly models: IProviderModels = new PiProviderModels();
|
|
24
|
+
readonly mcp: IProviderMcp = new PiMcpProvider();
|
|
25
|
+
readonly auth: IProviderAuth = new PiProviderAuth();
|
|
26
|
+
readonly skills: IProviderSkills = new PiSkillsProvider();
|
|
27
|
+
readonly sessions: IProviderSessions = new PiSessionsProvider();
|
|
28
|
+
readonly sessionSynchronizer: IProviderSessionSynchronizer = new PiSessionSynchronizer();
|
|
29
|
+
|
|
30
|
+
constructor() {
|
|
31
|
+
super('pi');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -2,6 +2,7 @@ import { ClaudeProvider } from '@/modules/providers/list/claude/claude.provider.
|
|
|
2
2
|
import { CodexProvider } from '@/modules/providers/list/codex/codex.provider.js';
|
|
3
3
|
import { CursorProvider } from '@/modules/providers/list/cursor/cursor.provider.js';
|
|
4
4
|
import { OpenCodeProvider } from '@/modules/providers/list/opencode/opencode.provider.js';
|
|
5
|
+
import { PiProvider } from '@/modules/providers/list/pi/index.js';
|
|
5
6
|
import type { IProvider } from '@/shared/interfaces.js';
|
|
6
7
|
import type { LLMProvider } from '@/shared/types.js';
|
|
7
8
|
import { AppError } from '@/shared/utils.js';
|
|
@@ -11,6 +12,7 @@ const providers: Record<LLMProvider, IProvider> = {
|
|
|
11
12
|
codex: new CodexProvider(),
|
|
12
13
|
cursor: new CursorProvider(),
|
|
13
14
|
opencode: new OpenCodeProvider(),
|
|
15
|
+
pi: new PiProvider(),
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -25,6 +25,12 @@ type ProviderCapabilities = {
|
|
|
25
25
|
supportsTokenUsage: boolean;
|
|
26
26
|
/** Whether the provider runtime can accept model-level reasoning effort. */
|
|
27
27
|
supportsEffort: boolean;
|
|
28
|
+
/** Whether the provider exposes configurable MCP servers. */
|
|
29
|
+
supportsMcp: boolean;
|
|
30
|
+
/** Whether the provider exposes discoverable/installable skills. */
|
|
31
|
+
supportsSkills: boolean;
|
|
32
|
+
/** Whether the Settings UI has provider permission controls. */
|
|
33
|
+
supportsPermissionSettings: boolean;
|
|
28
34
|
};
|
|
29
35
|
|
|
30
36
|
/**
|
|
@@ -44,6 +50,9 @@ const PROVIDER_CAPABILITIES: Record<LLMProvider, ProviderCapabilities> = {
|
|
|
44
50
|
supportsPermissionRequests: true,
|
|
45
51
|
supportsTokenUsage: true,
|
|
46
52
|
supportsEffort: true,
|
|
53
|
+
supportsMcp: true,
|
|
54
|
+
supportsSkills: true,
|
|
55
|
+
supportsPermissionSettings: true,
|
|
47
56
|
},
|
|
48
57
|
cursor: {
|
|
49
58
|
provider: 'cursor',
|
|
@@ -55,6 +64,9 @@ const PROVIDER_CAPABILITIES: Record<LLMProvider, ProviderCapabilities> = {
|
|
|
55
64
|
supportsPermissionRequests: false,
|
|
56
65
|
supportsTokenUsage: false,
|
|
57
66
|
supportsEffort: false,
|
|
67
|
+
supportsMcp: true,
|
|
68
|
+
supportsSkills: true,
|
|
69
|
+
supportsPermissionSettings: true,
|
|
58
70
|
},
|
|
59
71
|
codex: {
|
|
60
72
|
provider: 'codex',
|
|
@@ -66,6 +78,9 @@ const PROVIDER_CAPABILITIES: Record<LLMProvider, ProviderCapabilities> = {
|
|
|
66
78
|
supportsPermissionRequests: false,
|
|
67
79
|
supportsTokenUsage: true,
|
|
68
80
|
supportsEffort: true,
|
|
81
|
+
supportsMcp: true,
|
|
82
|
+
supportsSkills: true,
|
|
83
|
+
supportsPermissionSettings: true,
|
|
69
84
|
},
|
|
70
85
|
opencode: {
|
|
71
86
|
provider: 'opencode',
|
|
@@ -80,6 +95,23 @@ const PROVIDER_CAPABILITIES: Record<LLMProvider, ProviderCapabilities> = {
|
|
|
80
95
|
supportsPermissionRequests: false,
|
|
81
96
|
supportsTokenUsage: true,
|
|
82
97
|
supportsEffort: true,
|
|
98
|
+
supportsMcp: true,
|
|
99
|
+
supportsSkills: false,
|
|
100
|
+
supportsPermissionSettings: false,
|
|
101
|
+
},
|
|
102
|
+
pi: {
|
|
103
|
+
provider: 'pi',
|
|
104
|
+
permissionModes: ['default'],
|
|
105
|
+
defaultPermissionMode: 'default',
|
|
106
|
+
supportsImages: true,
|
|
107
|
+
supportsFiles: true,
|
|
108
|
+
supportsAbort: true,
|
|
109
|
+
supportsPermissionRequests: false,
|
|
110
|
+
supportsTokenUsage: true,
|
|
111
|
+
supportsEffort: true,
|
|
112
|
+
supportsMcp: false,
|
|
113
|
+
supportsSkills: true,
|
|
114
|
+
supportsPermissionSettings: false,
|
|
83
115
|
},
|
|
84
116
|
};
|
|
85
117
|
|
|
@@ -10,6 +10,7 @@ import { WS_OPEN_STATE, connectedClients } from '@/modules/websocket/index.js';
|
|
|
10
10
|
import type { LLMProvider } from '@/shared/types.js';
|
|
11
11
|
import { generateDisplayName } from '@/modules/projects/index.js';
|
|
12
12
|
import { SessionWatchScheduler } from '@/modules/providers/services/session-watch-scheduler.js';
|
|
13
|
+
import { getPiSessionDirectory } from '@/modules/providers/list/pi/index.js';
|
|
13
14
|
|
|
14
15
|
const PROVIDER_WATCH_PATHS: Array<{ provider: LLMProvider; rootPath: string }> = [
|
|
15
16
|
{
|
|
@@ -28,6 +29,10 @@ const PROVIDER_WATCH_PATHS: Array<{ provider: LLMProvider; rootPath: string }> =
|
|
|
28
29
|
provider: 'opencode',
|
|
29
30
|
rootPath: path.join(os.homedir(), '.local', 'share', 'opencode'),
|
|
30
31
|
},
|
|
32
|
+
{
|
|
33
|
+
provider: 'pi',
|
|
34
|
+
rootPath: getPiSessionDirectory(),
|
|
35
|
+
},
|
|
31
36
|
];
|
|
32
37
|
|
|
33
38
|
const WATCHER_IGNORED_DIRECTORY_NAMES = new Set([
|
|
@@ -313,8 +313,10 @@ test('providerMcpService global adder writes to all providers and rejects unsupp
|
|
|
313
313
|
workspacePath,
|
|
314
314
|
});
|
|
315
315
|
|
|
316
|
-
assert.equal(globalResult.length,
|
|
317
|
-
assert.ok(globalResult.every((entry) => entry.created === true));
|
|
316
|
+
assert.equal(globalResult.length, 5);
|
|
317
|
+
assert.ok(globalResult.filter((entry) => entry.provider !== 'pi').every((entry) => entry.created === true));
|
|
318
|
+
assert.equal(globalResult.find((entry) => entry.provider === 'pi')?.created, false);
|
|
319
|
+
assert.match(globalResult.find((entry) => entry.provider === 'pi')?.error ?? '', /does not provide native MCP/);
|
|
318
320
|
|
|
319
321
|
const claudeProject = await readJson(path.join(workspacePath, '.mcp.json'));
|
|
320
322
|
assert.ok((claudeProject.mcpServers as Record<string, unknown>)['global-http']);
|
|
@@ -6,7 +6,7 @@ import test from 'node:test';
|
|
|
6
6
|
|
|
7
7
|
import Database from 'better-sqlite3';
|
|
8
8
|
|
|
9
|
-
import { closeConnection, initializeDatabase, sessionsDb } from '@/modules/database/index.js';
|
|
9
|
+
import { closeConnection, initializeDatabase, projectsDb, sessionsDb } from '@/modules/database/index.js';
|
|
10
10
|
import { OpenCodeSessionSynchronizer } from '@/modules/providers/list/opencode/opencode-session-synchronizer.provider.js';
|
|
11
11
|
import { OpenCodeSessionsProvider } from '@/modules/providers/list/opencode/opencode-sessions.provider.js';
|
|
12
12
|
import { appendImagesInputTag } from '@/shared/image-attachments.js';
|
|
@@ -41,9 +41,15 @@ async function withIsolatedDatabase(runTest: () => void | Promise<void>): Promis
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const createOpenCodeDatabase = async (
|
|
44
|
+
const createOpenCodeDatabase = async (
|
|
45
|
+
homeDir: string,
|
|
46
|
+
workspacePath: string,
|
|
47
|
+
options: { projectId?: string; worktreePath?: string } = {},
|
|
48
|
+
): Promise<void> => {
|
|
45
49
|
const dataDir = path.join(homeDir, '.local', 'share', 'opencode');
|
|
46
50
|
await mkdir(dataDir, { recursive: true });
|
|
51
|
+
const projectId = options.projectId ?? 'project-1';
|
|
52
|
+
const worktreePath = options.worktreePath ?? workspacePath;
|
|
47
53
|
|
|
48
54
|
const db = new Database(path.join(dataDir, 'opencode.db'));
|
|
49
55
|
try {
|
|
@@ -123,8 +129,8 @@ const createOpenCodeDatabase = async (homeDir: string, workspacePath: string): P
|
|
|
123
129
|
db.prepare(
|
|
124
130
|
'INSERT INTO project (id, worktree, time_created, time_updated, sandboxes) VALUES (?, ?, ?, ?, ?)',
|
|
125
131
|
).run(
|
|
126
|
-
|
|
127
|
-
|
|
132
|
+
projectId,
|
|
133
|
+
worktreePath,
|
|
128
134
|
1_700_000_000_000,
|
|
129
135
|
1_700_000_001_000,
|
|
130
136
|
'[]',
|
|
@@ -137,7 +143,7 @@ const createOpenCodeDatabase = async (homeDir: string, workspacePath: string): P
|
|
|
137
143
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
138
144
|
`).run(
|
|
139
145
|
'open-session-1',
|
|
140
|
-
|
|
146
|
+
projectId,
|
|
141
147
|
'open-session-1',
|
|
142
148
|
workspacePath,
|
|
143
149
|
'OpenCode indexed title',
|
|
@@ -253,7 +259,10 @@ test('OpenCode session synchronizer indexes sqlite sessions without deletable tr
|
|
|
253
259
|
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
254
260
|
|
|
255
261
|
try {
|
|
256
|
-
await createOpenCodeDatabase(tempRoot, workspacePath
|
|
262
|
+
await createOpenCodeDatabase(tempRoot, workspacePath, {
|
|
263
|
+
projectId: 'global',
|
|
264
|
+
worktreePath: '/',
|
|
265
|
+
});
|
|
257
266
|
await withIsolatedDatabase(() => {
|
|
258
267
|
const synchronizer = new OpenCodeSessionSynchronizer();
|
|
259
268
|
const processed = synchronizer.synchronize();
|
|
@@ -273,6 +282,72 @@ test('OpenCode session synchronizer indexes sqlite sessions without deletable tr
|
|
|
273
282
|
}
|
|
274
283
|
});
|
|
275
284
|
|
|
285
|
+
test('OpenCode session synchronizer skips external Multica global workdirs', { concurrency: false }, async () => {
|
|
286
|
+
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'opencode-session-sync-multica-'));
|
|
287
|
+
const workspacePath = path.join(
|
|
288
|
+
tempRoot,
|
|
289
|
+
'multica_workspaces_desktop-api.example.test',
|
|
290
|
+
'workspace-id',
|
|
291
|
+
'deadbeef',
|
|
292
|
+
'workdir',
|
|
293
|
+
);
|
|
294
|
+
await mkdir(workspacePath, { recursive: true });
|
|
295
|
+
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
await createOpenCodeDatabase(tempRoot, workspacePath, {
|
|
299
|
+
projectId: 'global',
|
|
300
|
+
worktreePath: '/',
|
|
301
|
+
});
|
|
302
|
+
await withIsolatedDatabase(async () => {
|
|
303
|
+
const count = await new OpenCodeSessionSynchronizer().synchronize();
|
|
304
|
+
|
|
305
|
+
assert.equal(count, 0);
|
|
306
|
+
assert.equal(sessionsDb.getSessionById('open-session-1'), null);
|
|
307
|
+
assert.equal(projectsDb.getProjectPath(workspacePath), null);
|
|
308
|
+
});
|
|
309
|
+
} finally {
|
|
310
|
+
restoreHomeDir();
|
|
311
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test('OpenCode session synchronizer keeps WindCli sessions in Multica global workdirs', { concurrency: false }, async () => {
|
|
316
|
+
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'opencode-session-sync-multica-app-'));
|
|
317
|
+
const workspacePath = path.join(
|
|
318
|
+
tempRoot,
|
|
319
|
+
'multica_workspaces_desktop-api.example.test',
|
|
320
|
+
'workspace-id',
|
|
321
|
+
'deadbeef',
|
|
322
|
+
'workdir',
|
|
323
|
+
);
|
|
324
|
+
await mkdir(workspacePath, { recursive: true });
|
|
325
|
+
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
await createOpenCodeDatabase(tempRoot, workspacePath, {
|
|
329
|
+
projectId: 'global',
|
|
330
|
+
worktreePath: '/',
|
|
331
|
+
});
|
|
332
|
+
await withIsolatedDatabase(async () => {
|
|
333
|
+
sessionsDb.createAppSession('app-session-multica', 'opencode', workspacePath);
|
|
334
|
+
sessionsDb.assignProviderSessionId('app-session-multica', 'open-session-1');
|
|
335
|
+
|
|
336
|
+
const count = await new OpenCodeSessionSynchronizer().synchronize();
|
|
337
|
+
|
|
338
|
+
assert.equal(count, 1);
|
|
339
|
+
assert.equal(sessionsDb.getAllSessions().length, 1);
|
|
340
|
+
assert.equal(
|
|
341
|
+
sessionsDb.getSessionById('app-session-multica')?.provider_session_id,
|
|
342
|
+
'open-session-1',
|
|
343
|
+
);
|
|
344
|
+
});
|
|
345
|
+
} finally {
|
|
346
|
+
restoreHomeDir();
|
|
347
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
276
351
|
test('OpenCode session synchronizer returns the app session id once provider mapping exists', { concurrency: false }, async () => {
|
|
277
352
|
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'opencode-session-sync-mapped-'));
|
|
278
353
|
const workspacePath = path.join(tempRoot, 'workspace');
|