@rexvane/piweb 0.3.12 → 0.3.14

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 (177) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +8 -7
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +3 -3
  5. package/.next/react-loadable-manifest.json +2 -2
  6. package/.next/routes-manifest.json +6 -0
  7. package/.next/server/app/_global-error/page.js +2 -2
  8. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/server/app/_global-error.html +1 -1
  10. package/.next/server/app/_global-error.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/server/app/_not-found/page.js +1 -1
  15. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/server/app/_not-found.html +1 -1
  17. package/.next/server/app/_not-found.rsc +1 -1
  18. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  20. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  21. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  22. package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
  23. package/.next/server/app/api/agent/[id]/route.js +1 -1
  24. package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
  25. package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
  26. package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
  27. package/.next/server/app/api/agent/new/route.js +1 -1
  28. package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
  29. package/.next/server/app/api/config/route.js +1 -1
  30. package/.next/server/app/api/files/route.js +1 -1
  31. package/.next/server/app/api/git/route.js +2 -2
  32. package/.next/server/app/api/growth/route.js +1 -1
  33. package/.next/server/app/api/growth/route.js.nft.json +1 -1
  34. package/.next/server/app/api/health/route.js +1 -1
  35. package/.next/server/app/api/models/route.js +1 -1
  36. package/.next/server/app/api/models/route.js.nft.json +1 -1
  37. package/.next/server/app/api/pi-settings/route.js +1 -1
  38. package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
  39. package/.next/server/app/api/plugins/route.js +1 -1
  40. package/.next/server/app/api/plugins/route.js.nft.json +1 -1
  41. package/.next/server/app/api/prompts/route.js +1 -1
  42. package/.next/server/app/api/prompts/route.js.nft.json +1 -1
  43. package/.next/server/app/api/security/route.js +1 -1
  44. package/.next/server/app/api/security/route.js.nft.json +1 -1
  45. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  46. package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
  47. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  48. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  49. package/.next/server/app/api/sessions/import/route.js +6 -0
  50. package/.next/server/app/api/sessions/import/route.js.nft.json +1 -0
  51. package/.next/server/app/api/sessions/import/route_client-reference-manifest.js +1 -0
  52. package/.next/server/app/api/sessions/route.js +1 -1
  53. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  54. package/.next/server/app/api/skills/route.js +2 -2
  55. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  56. package/.next/server/app/api/update/route.js +2 -2
  57. package/.next/server/app/api/update/route.js.nft.json +1 -1
  58. package/.next/server/app/api/version/route.js +1 -1
  59. package/.next/server/app/api/web-auth/route.js +1 -1
  60. package/.next/server/app/api/workspaces/route.js +1 -1
  61. package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  62. package/.next/server/app/icon.svg/route.js +1 -1
  63. package/.next/server/app/index.html +1 -1
  64. package/.next/server/app/index.rsc +3 -3
  65. package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
  66. package/.next/server/app/index.segments/_full.segment.rsc +3 -3
  67. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  68. package/.next/server/app/login/page.js +1 -1
  69. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  70. package/.next/server/app/login.html +1 -1
  71. package/.next/server/app/login.rsc +2 -2
  72. package/.next/server/app/login.segments/_full.segment.rsc +2 -2
  73. package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
  74. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
  75. package/.next/server/app/page.js +1 -1
  76. package/.next/server/app/page_client-reference-manifest.js +1 -1
  77. package/.next/server/app-paths-manifest.json +8 -7
  78. package/.next/server/chunks/496.js +1 -1
  79. package/.next/server/chunks/532.js +1 -0
  80. package/.next/server/chunks/686.js +2 -2
  81. package/.next/server/middleware-build-manifest.js +1 -1
  82. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  83. package/.next/server/pages/404.html +1 -1
  84. package/.next/server/pages/500.html +1 -1
  85. package/.next/server/server-reference-manifest.json +1 -1
  86. package/.next/static/LOFl9bqqMV2goBV8erESO/_buildManifest.js +1 -0
  87. package/.next/static/chunks/4632.112d2e893d48d188.js +1 -0
  88. package/.next/static/chunks/4686-7d694def920ff093.js +1 -0
  89. package/.next/static/chunks/app/_global-error/page-f77e9ede9808b18e.js +1 -0
  90. package/.next/static/chunks/app/api/agent/[id]/events/route-f77e9ede9808b18e.js +1 -0
  91. package/.next/static/chunks/app/api/agent/[id]/route-f77e9ede9808b18e.js +1 -0
  92. package/.next/static/chunks/app/api/agent/[id]/tree/route-f77e9ede9808b18e.js +1 -0
  93. package/.next/static/chunks/app/api/agent/new/route-f77e9ede9808b18e.js +1 -0
  94. package/.next/static/chunks/app/api/config/route-f77e9ede9808b18e.js +1 -0
  95. package/.next/static/chunks/app/api/files/route-f77e9ede9808b18e.js +1 -0
  96. package/.next/static/chunks/app/api/git/route-f77e9ede9808b18e.js +1 -0
  97. package/.next/static/chunks/app/api/growth/route-f77e9ede9808b18e.js +1 -0
  98. package/.next/static/chunks/app/api/health/route-f77e9ede9808b18e.js +1 -0
  99. package/.next/static/chunks/app/api/models/route-f77e9ede9808b18e.js +1 -0
  100. package/.next/static/chunks/app/api/pi-settings/route-f77e9ede9808b18e.js +1 -0
  101. package/.next/static/chunks/app/api/plugins/route-f77e9ede9808b18e.js +1 -0
  102. package/.next/static/chunks/app/api/prompts/route-f77e9ede9808b18e.js +1 -0
  103. package/.next/static/chunks/app/api/security/route-f77e9ede9808b18e.js +1 -0
  104. package/.next/static/chunks/app/api/sessions/[id]/export/route-f77e9ede9808b18e.js +1 -0
  105. package/.next/static/chunks/app/api/sessions/[id]/route-f77e9ede9808b18e.js +1 -0
  106. package/.next/static/chunks/app/api/sessions/import/route-f77e9ede9808b18e.js +1 -0
  107. package/.next/static/chunks/app/api/sessions/route-f77e9ede9808b18e.js +1 -0
  108. package/.next/static/chunks/app/api/skills/route-f77e9ede9808b18e.js +1 -0
  109. package/.next/static/chunks/app/api/update/route-f77e9ede9808b18e.js +1 -0
  110. package/.next/static/chunks/app/api/version/route-f77e9ede9808b18e.js +1 -0
  111. package/.next/static/chunks/app/api/web-auth/route-f77e9ede9808b18e.js +1 -0
  112. package/.next/static/chunks/app/api/workspaces/route-f77e9ede9808b18e.js +1 -0
  113. package/.next/static/chunks/app/page-9a5440047c5a415c.js +1 -0
  114. package/.next/static/chunks/next/dist/client/components/builtin/app-error-f77e9ede9808b18e.js +1 -0
  115. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f77e9ede9808b18e.js +1 -0
  116. package/.next/static/chunks/next/dist/client/components/builtin/not-found-f77e9ede9808b18e.js +1 -0
  117. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f77e9ede9808b18e.js +1 -0
  118. package/.next/static/chunks/{webpack-05a9e85529a18b93.js → webpack-d28e132e1b8c100c.js} +1 -1
  119. package/.next/types/app/api/sessions/import/route.ts +353 -0
  120. package/.next/types/routes.d.ts +2 -1
  121. package/.next/types/validator.ts +9 -0
  122. package/README.md +6 -2
  123. package/package.json +1 -1
  124. package/src/app/api/sessions/import/route.ts +74 -0
  125. package/src/components/PromptPanel.tsx +33 -31
  126. package/src/components/SessionImportSection.tsx +370 -0
  127. package/src/components/SettingsPanel.tsx +4 -1
  128. package/src/i18n.tsx +68 -6
  129. package/src/lib/prompt-sources.ts +29 -1
  130. package/src/lib/session-import/claude.ts +229 -0
  131. package/src/lib/session-import/codex.ts +223 -0
  132. package/src/lib/session-import/dsh-zstd.ts +69 -0
  133. package/src/lib/session-import/dsh.ts +259 -0
  134. package/src/lib/session-import/files.ts +84 -0
  135. package/src/lib/session-import/grok.ts +169 -0
  136. package/src/lib/session-import/index.ts +229 -0
  137. package/src/lib/session-import/oc-family.ts +275 -0
  138. package/src/lib/session-import/opencode.ts +7 -0
  139. package/src/lib/session-import/paths.ts +37 -0
  140. package/src/lib/session-import/sqlite.ts +89 -0
  141. package/src/lib/session-import/types.ts +171 -0
  142. package/src/lib/session-import/writer.ts +259 -0
  143. package/src/lib/session-import/zcode.ts +8 -0
  144. package/src/lib/skills-service.ts +51 -14
  145. package/.next/server/chunks/118.js +0 -1
  146. package/.next/static/2kt2UiWjv_viXpNlfjvBT/_buildManifest.js +0 -1
  147. package/.next/static/chunks/4127.dff5c3b33c6bfd1b.js +0 -1
  148. package/.next/static/chunks/4686-5bbee099126af797.js +0 -1
  149. package/.next/static/chunks/app/_global-error/page-961c055baebb3872.js +0 -1
  150. package/.next/static/chunks/app/api/agent/[id]/events/route-961c055baebb3872.js +0 -1
  151. package/.next/static/chunks/app/api/agent/[id]/route-961c055baebb3872.js +0 -1
  152. package/.next/static/chunks/app/api/agent/[id]/tree/route-961c055baebb3872.js +0 -1
  153. package/.next/static/chunks/app/api/agent/new/route-961c055baebb3872.js +0 -1
  154. package/.next/static/chunks/app/api/config/route-961c055baebb3872.js +0 -1
  155. package/.next/static/chunks/app/api/files/route-961c055baebb3872.js +0 -1
  156. package/.next/static/chunks/app/api/git/route-961c055baebb3872.js +0 -1
  157. package/.next/static/chunks/app/api/growth/route-961c055baebb3872.js +0 -1
  158. package/.next/static/chunks/app/api/health/route-961c055baebb3872.js +0 -1
  159. package/.next/static/chunks/app/api/models/route-961c055baebb3872.js +0 -1
  160. package/.next/static/chunks/app/api/pi-settings/route-961c055baebb3872.js +0 -1
  161. package/.next/static/chunks/app/api/plugins/route-961c055baebb3872.js +0 -1
  162. package/.next/static/chunks/app/api/prompts/route-961c055baebb3872.js +0 -1
  163. package/.next/static/chunks/app/api/security/route-961c055baebb3872.js +0 -1
  164. package/.next/static/chunks/app/api/sessions/[id]/export/route-961c055baebb3872.js +0 -1
  165. package/.next/static/chunks/app/api/sessions/[id]/route-961c055baebb3872.js +0 -1
  166. package/.next/static/chunks/app/api/sessions/route-961c055baebb3872.js +0 -1
  167. package/.next/static/chunks/app/api/skills/route-961c055baebb3872.js +0 -1
  168. package/.next/static/chunks/app/api/update/route-961c055baebb3872.js +0 -1
  169. package/.next/static/chunks/app/api/version/route-961c055baebb3872.js +0 -1
  170. package/.next/static/chunks/app/api/web-auth/route-961c055baebb3872.js +0 -1
  171. package/.next/static/chunks/app/api/workspaces/route-961c055baebb3872.js +0 -1
  172. package/.next/static/chunks/app/page-00c8ebf40ec5eaca.js +0 -1
  173. package/.next/static/chunks/next/dist/client/components/builtin/app-error-961c055baebb3872.js +0 -1
  174. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-961c055baebb3872.js +0 -1
  175. package/.next/static/chunks/next/dist/client/components/builtin/not-found-961c055baebb3872.js +0 -1
  176. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-961c055baebb3872.js +0 -1
  177. /package/.next/static/{2kt2UiWjv_viXpNlfjvBT → LOFl9bqqMV2goBV8erESO}/_ssgManifest.js +0 -0
@@ -0,0 +1,171 @@
1
+ /**
2
+ * 本地会话导入:外部工具的对话 → pi 会话。
3
+ *
4
+ * 这里定义"中间层"的形状——它刻意贴着 pi 的会话 schema(角色、content 块、
5
+ * 时间戳、用量),而不是拍平成纯文本:思考、工具调用/结果、图片、用量都要能带过去。
6
+ * 每个来源只负责把自己的格式解析成这些类型,写入合法性由 writer 统一保证。
7
+ */
8
+
9
+ /** 支持导入的来源 */
10
+ export type ImportSource = "claude" | "codex" | "grok" | "zcode" | "dsh" | "opencode";
11
+
12
+ export const IMPORT_SOURCES: ImportSource[] = ["claude", "codex", "grok", "zcode", "dsh", "opencode"];
13
+
14
+ export interface ImportedUsage {
15
+ input?: number;
16
+ output?: number;
17
+ cacheRead?: number;
18
+ cacheWrite?: number;
19
+ reasoning?: number;
20
+ totalTokens?: number;
21
+ }
22
+
23
+ /** 与 pi 的 content 块对齐(够用即可:其余类型在读取时被忽略) */
24
+ export type ImportedBlock =
25
+ | { type: "text"; text: string }
26
+ | { type: "thinking"; thinking: string }
27
+ | { type: "toolCall"; id: string; name: string; arguments?: unknown }
28
+ | { type: "image"; data: string; mimeType: string };
29
+
30
+ export type ImportedMessage =
31
+ | { role: "user"; content: ImportedBlock[]; timestamp: number }
32
+ | {
33
+ role: "assistant";
34
+ content: ImportedBlock[];
35
+ timestamp: number;
36
+ api?: string;
37
+ provider?: string;
38
+ model?: string;
39
+ usage?: ImportedUsage;
40
+ stopReason?: string;
41
+ errorMessage?: string;
42
+ }
43
+ | {
44
+ role: "toolResult";
45
+ toolCallId: string;
46
+ toolName?: string;
47
+ content: ImportedBlock[];
48
+ isError?: boolean;
49
+ timestamp: number;
50
+ };
51
+
52
+ export type ImportedEntry =
53
+ | { type: "message"; message: ImportedMessage }
54
+ | { type: "session_info"; name: string }
55
+ | { type: "model_change"; provider: string; modelId: string };
56
+
57
+ /** 扫描结果里的一行(会发给前端;location 属服务端内部信息,路由层要剥掉) */
58
+ export interface ExternalSessionSummary {
59
+ source: ImportSource;
60
+ /** 源里的稳定标识(文件名/uuid/DB 行 id):幂等键的一半 */
61
+ externalId: string;
62
+ title?: string;
63
+ /** 源会话原本的项目目录(本机存在就导入回该工作区) */
64
+ projectPath?: string;
65
+ provider?: string;
66
+ model?: string;
67
+ createdAt?: number;
68
+ updatedAt?: number;
69
+ /** 未统计时为 undefined(界面显示 —) */
70
+ messageCount?: number;
71
+ /** 服务端内部定位信息(文件路径 / 库+id),不下发 */
72
+ location: string;
73
+ }
74
+
75
+ export interface ImportedSession {
76
+ summary: ExternalSessionSummary;
77
+ entries: ImportedEntry[];
78
+ /** 跳过内容的原因(侧链、合成消息等),用于导入报告 */
79
+ skipped?: string[];
80
+ }
81
+
82
+ export interface ScanOptions {
83
+ /**
84
+ * 每个来源最多返回多少条(按最近活动倒序)。界面只列最近的若干条,所以扫描阶段就该停手:
85
+ * 文件类的来源按 mtime 排序后只读前若干个头,SQLite 类的直接 LIMIT,不把上千个会话全读一遍。
86
+ * 传 0 或省略表示不限(导入时按 id 反查需要看到全部)。
87
+ */
88
+ limit?: number;
89
+ }
90
+
91
+ export interface ImportSourceModule {
92
+ id: ImportSource;
93
+ /** 列出该来源在本机可导入的会话(只读扫描) */
94
+ scan(options?: ScanOptions): Promise<ExternalSessionSummary[]>;
95
+ /** 读取单个会话并转成 pi 条目;读不到返回 null */
96
+ read(summary: ExternalSessionSummary): Promise<ImportedSession | null>;
97
+ }
98
+
99
+ /** 时间戳容错:非法值不回落到"现在"(那会伪造历史),由调用方决定兜底值 */
100
+ export function toEpochMs(value: unknown): number | undefined {
101
+ if (typeof value === "number" && Number.isFinite(value)) {
102
+ // 秒级时间戳(如 1789273099)按秒理解
103
+ return value < 1e12 ? Math.round(value * 1000) : Math.round(value);
104
+ }
105
+ if (typeof value === "string" && value.trim()) {
106
+ const parsed = Date.parse(value);
107
+ if (Number.isFinite(parsed)) return parsed;
108
+ const numeric = Number(value);
109
+ if (Number.isFinite(numeric)) return toEpochMs(numeric);
110
+ }
111
+ return undefined;
112
+ }
113
+
114
+ /** 文本块:空串直接丢掉,避免产出空 content */
115
+ export function textBlock(value: unknown): ImportedBlock | null {
116
+ if (typeof value !== "string") return null;
117
+ return value.trim() ? { type: "text", text: value } : null;
118
+ }
119
+
120
+ export function compactBlocks(blocks: Array<ImportedBlock | null | undefined>): ImportedBlock[] {
121
+ return blocks.filter((block): block is ImportedBlock => Boolean(block));
122
+ }
123
+
124
+ /** 工具返回值可能是字符串、对象或数组,统一成内容块 */
125
+ export function outputBlocks(value: unknown): ImportedBlock[] {
126
+ if (typeof value === "string") return compactBlocks([textBlock(value)]);
127
+ if (Array.isArray(value)) return compactBlocks(value.flatMap((item) => outputBlocks(item)));
128
+ if (value && typeof value === "object") {
129
+ const record = value as Record<string, unknown>;
130
+ if (typeof record.text === "string") return compactBlocks([textBlock(record.text)]);
131
+ if (typeof record.content === "string") return compactBlocks([textBlock(record.content)]);
132
+ try {
133
+ return compactBlocks([textBlock(JSON.stringify(value, null, 2))]);
134
+ } catch {
135
+ return [];
136
+ }
137
+ }
138
+ return [];
139
+ }
140
+
141
+ /** 参数可能是 JSON 字符串(codex/dsh 就是),尽量还原成对象 */
142
+ export function parseArguments(value: unknown): unknown {
143
+ if (typeof value !== "string") return value ?? {};
144
+ try {
145
+ return JSON.parse(value);
146
+ } catch {
147
+ return value;
148
+ }
149
+ }
150
+
151
+ /** 标题候选:源标题 → 首条用户消息前 48 字 */
152
+ export function fallbackTitle(entries: ImportedEntry[], explicit?: string): string {
153
+ const trimmed = explicit?.replace(/\s+/g, " ").trim();
154
+ if (trimmed) return trimmed.slice(0, 120);
155
+ for (const entry of entries) {
156
+ if (entry.type !== "message" || entry.message.role !== "user") continue;
157
+ const text = entry.message.content.map((block) => (block.type === "text" ? block.text : "")).join(" ").replace(/\s+/g, " ").trim();
158
+ if (text) return text.slice(0, 48);
159
+ }
160
+ return "";
161
+ }
162
+
163
+ /** 标题上限:列表只用来辨认会话,几百字的提示词原样塞进接口响应只是浪费带宽 */
164
+ export const TITLE_LIMIT = 120;
165
+
166
+ /** 把任意来源的标题字段压成一行、限长;空串按"没有标题"处理 */
167
+ export function cleanTitle(value: unknown): string | undefined {
168
+ if (typeof value !== "string") return undefined;
169
+ const flat = value.replace(/\s+/g, " ").trim();
170
+ return flat ? flat.slice(0, TITLE_LIMIT) : undefined;
171
+ }
@@ -0,0 +1,259 @@
1
+ /**
2
+ * 把导入的条目写成**合法**的 pi 会话文件。
3
+ *
4
+ * 这个文件的每条规则都对应 SDK 的一处硬要求,写错就会出现"列表里有、打开报错"或
5
+ * "模型请求 400"这类问题:
6
+ * 1. 首行 header 且 version=3(小于 3 会被 SDK 迁移,条目 id 全被重写)
7
+ * 2. 条目 parentId 串成单链,首条为 null
8
+ * 3. 每个 toolResult.toolCallId 必须能对上前面某条 assistant 的 toolCall.id,
9
+ * 源里缺调用记录时补一条占位 toolCall(否则压缩/请求阶段会挂)
10
+ * 4. content 永远是数组;message.timestamp 是毫秒数
11
+ * 5. 文件落在 <agentDir>/sessions/<编码 cwd>/ 下,header.cwd 必须是真实存在的目录
12
+ */
13
+ import fs from "node:fs/promises";
14
+ import path from "node:path";
15
+ import { randomUUID } from "node:crypto";
16
+ import type { ImportedBlock, ImportedEntry, ImportedMessage, ImportedSession, ImportSource } from "./types";
17
+ import { fallbackTitle } from "./types";
18
+
19
+ /** 来源前缀:标题里标出来源,侧栏一眼能看出是导入的 */
20
+ const SOURCE_LABEL: Record<ImportSource, string> = {
21
+ claude: "Claude",
22
+ codex: "Codex",
23
+ grok: "Grok",
24
+ zcode: "ZCode",
25
+ dsh: "dsh",
26
+ opencode: "opencode",
27
+ };
28
+
29
+ const PROVENANCE_TYPE = "piweb-import";
30
+
31
+ const entryId = () => randomUUID().replace(/-/g, "").slice(0, 8);
32
+
33
+ /** 会话目录编码:与 SDK 的 SessionManager 完全一致(--D--AIApp-PiWeb--) */
34
+ export function encodeSessionDir(cwd: string): string {
35
+ return `--${path.resolve(cwd).replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
36
+ }
37
+
38
+ function safeId(value: string): string {
39
+ const cleaned = value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
40
+ return cleaned.slice(0, 80) || "session";
41
+ }
42
+
43
+ /** 文件名里带来源与外部 id → "是否导入过"只需看文件名,O(1) 且不依赖任何数据库 */
44
+ export function importedFileName(source: ImportSource, externalId: string, createdAt?: number): string {
45
+ const stamp = new Date(createdAt ?? Date.now()).toISOString().replace(/[:.]/g, "-");
46
+ return `${stamp}_import-${source}-${safeId(externalId)}.jsonl`;
47
+ }
48
+
49
+ /** 幂等检查:本机是否已经导入过这条外部会话 */
50
+ export async function findImported(agentDir: string, source: ImportSource, externalId: string): Promise<string | null> {
51
+ const suffix = `_import-${source}-${safeId(externalId)}.jsonl`;
52
+ const root = path.join(agentDir, "sessions");
53
+ let dirs: string[] = [];
54
+ try {
55
+ dirs = await fs.readdir(root);
56
+ } catch {
57
+ return null;
58
+ }
59
+ for (const dir of dirs) {
60
+ // 文件名带时间戳前缀,只能列目录后按后缀匹配
61
+ let files: string[] = [];
62
+ try {
63
+ files = await fs.readdir(path.join(root, dir));
64
+ } catch {
65
+ continue;
66
+ }
67
+ const hit = files.find((name) => name.endsWith(suffix));
68
+ if (hit) return path.join(root, dir, hit);
69
+ }
70
+ return null;
71
+ }
72
+
73
+ /**
74
+ * 一次列目录得到所有已导入的 `source:externalId`:扫描几百条会话时逐条 findImported 要把
75
+ * sessions 目录翻几百遍(每次 O(目录数)),这里聚合一次给界面标记"已导入"。
76
+ */
77
+ export async function listImportedKeys(agentDir: string): Promise<Set<string>> {
78
+ const keys = new Set<string>();
79
+ const root = path.join(agentDir, "sessions");
80
+ let dirs: string[] = [];
81
+ try {
82
+ dirs = await fs.readdir(root);
83
+ } catch {
84
+ return keys;
85
+ }
86
+ for (const dir of dirs) {
87
+ let files: string[] = [];
88
+ try {
89
+ files = await fs.readdir(path.join(root, dir));
90
+ } catch {
91
+ continue;
92
+ }
93
+ for (const name of files) {
94
+ const match = /_import-([A-Za-z0-9]+)-(.+)\.jsonl$/.exec(name);
95
+ if (match) keys.add(`${match[1]}:${match[2]}`);
96
+ }
97
+ }
98
+ return keys;
99
+ }
100
+
101
+ /** 与 findImported / importedFileName 共用同一个键(避免两边规则不一致导致重复导入) */
102
+ export function importedKey(source: ImportSource, externalId: string): string {
103
+ return `${source}:${safeId(externalId)}`;
104
+ }
105
+
106
+ /** 内容块清洗:toolCall 必须有 id/name;toolResult 文本转块 */
107
+ function cleanBlocks(blocks: ImportedBlock[]): ImportedBlock[] {
108
+ const out: ImportedBlock[] = [];
109
+ for (const block of blocks) {
110
+ if (!block) continue;
111
+ if (block.type === "text") {
112
+ if (block.text) out.push(block);
113
+ } else if (block.type === "thinking") {
114
+ if (block.thinking) out.push(block);
115
+ } else if (block.type === "toolCall") {
116
+ if (block.id && block.name) out.push({ ...block, arguments: block.arguments ?? {} });
117
+ } else if (block.type === "image") {
118
+ if (block.data && block.mimeType) out.push(block);
119
+ }
120
+ }
121
+ return out;
122
+ }
123
+
124
+ /**
125
+ * 保证 toolResult 有对应的 toolCall:源里常见"只留了结果、调用被截断"的情况,
126
+ * 缺了就补一条占位 assistant 消息,否则 pi 侧组装请求时会因为孤立 toolResult 报错。
127
+ */
128
+ function repairToolPairing(messages: ImportedMessage[]): { messages: ImportedMessage[]; repaired: number } {
129
+ const known = new Set<string>();
130
+ const out: ImportedMessage[] = [];
131
+ let repaired = 0;
132
+ for (const message of messages) {
133
+ if (message.role === "assistant") {
134
+ for (const block of message.content) if (block.type === "toolCall") known.add(block.id);
135
+ out.push(message);
136
+ continue;
137
+ }
138
+ if (message.role !== "toolResult" || known.has(message.toolCallId)) {
139
+ out.push(message);
140
+ continue;
141
+ }
142
+ known.add(message.toolCallId);
143
+ repaired += 1;
144
+ out.push({
145
+ role: "assistant",
146
+ content: [{ type: "toolCall", id: message.toolCallId, name: message.toolName || "tool", arguments: {} }],
147
+ timestamp: Math.max(0, message.timestamp - 1),
148
+ stopReason: "toolUse",
149
+ });
150
+ out.push(message);
151
+ }
152
+ return { messages: out, repaired };
153
+ }
154
+
155
+ export interface WriteImportedOptions {
156
+ /** 目标工作区目录(必须真实存在——调用方负责校验与兜底) */
157
+ cwd: string;
158
+ agentDir: string;
159
+ now?: number;
160
+ }
161
+
162
+ export interface WriteImportedResult {
163
+ path: string;
164
+ messageCount: number;
165
+ skipped: string[];
166
+ }
167
+
168
+ /** 写一个导入会话;调用方需先确认该外部会话没有导入过 */
169
+ export async function writeImportedSession(session: ImportedSession, options: WriteImportedOptions): Promise<WriteImportedResult> {
170
+ const { summary, entries } = session;
171
+ const now = options.now ?? Date.now();
172
+ const createdAt = summary.createdAt ?? now;
173
+
174
+ const messages = repairToolPairing(entries.filter((entry): entry is { type: "message"; message: ImportedMessage } => entry.type === "message").map((entry) => entry.message));
175
+ const skipped = [...(session.skipped ?? [])];
176
+ if (messages.repaired > 0) skipped.push(`补齐 ${messages.repaired} 条缺失的工具调用记录`);
177
+
178
+ const lines: string[] = [];
179
+ lines.push(JSON.stringify({ type: "session", version: 3, id: randomUUID(), timestamp: new Date(createdAt).toISOString(), cwd: path.resolve(options.cwd) }));
180
+
181
+ let parentId: string | null = null;
182
+ const push = (body: Record<string, unknown>, at: number) => {
183
+ const id = entryId();
184
+ lines.push(JSON.stringify({ ...body, id, parentId, timestamp: new Date(at).toISOString() }));
185
+ parentId = id;
186
+ };
187
+
188
+ if (summary.provider && summary.model) push({ type: "model_change", provider: summary.provider, modelId: summary.model }, createdAt);
189
+
190
+ let lastAt = createdAt;
191
+ for (const message of messages.messages) {
192
+ const at = Number.isFinite(message.timestamp) ? message.timestamp : lastAt;
193
+ lastAt = at;
194
+ const content = cleanBlocks(message.content);
195
+ if (!content.length && message.role !== "toolResult") continue;
196
+ if (message.role === "assistant") {
197
+ push({
198
+ type: "message",
199
+ message: {
200
+ role: "assistant",
201
+ content,
202
+ timestamp: at,
203
+ stopReason: message.stopReason ?? "stop",
204
+ ...(message.api ? { api: message.api } : {}),
205
+ ...(message.provider ? { provider: message.provider } : summary.provider ? { provider: summary.provider } : {}),
206
+ ...(message.model ? { model: message.model } : summary.model ? { model: summary.model } : {}),
207
+ ...(message.usage ? { usage: normalizeUsage(message.usage) } : {}),
208
+ ...(message.errorMessage ? { errorMessage: message.errorMessage } : {}),
209
+ },
210
+ }, at);
211
+ continue;
212
+ }
213
+ if (message.role === "toolResult") {
214
+ push({
215
+ type: "message",
216
+ message: {
217
+ role: "toolResult",
218
+ toolCallId: message.toolCallId,
219
+ ...(message.toolName ? { toolName: message.toolName } : {}),
220
+ content,
221
+ isError: Boolean(message.isError),
222
+ timestamp: at,
223
+ },
224
+ }, at);
225
+ continue;
226
+ }
227
+ push({ type: "message", message: { role: "user", content, timestamp: at } }, at);
228
+ }
229
+
230
+ const title = fallbackTitle(entries, summary.title);
231
+ if (title) push({ type: "session_info", name: `【${SOURCE_LABEL[summary.source]}】${title}` }, lastAt);
232
+ // 来源凭据:两边都会忽略未知 custom 条目,不影响上下文
233
+ push({ type: "custom", customType: PROVENANCE_TYPE, data: { source: summary.source, externalId: summary.externalId, sourcePath: summary.location, projectPath: summary.projectPath ?? null, importedAt: new Date(now).toISOString() } }, lastAt);
234
+
235
+ const dir = path.join(options.agentDir, "sessions", encodeSessionDir(options.cwd));
236
+ await fs.mkdir(dir, { recursive: true });
237
+ const target = path.join(dir, importedFileName(summary.source, summary.externalId, createdAt));
238
+ const temporary = `${target}.${process.pid}.tmp`;
239
+ await fs.writeFile(temporary, `${lines.join("\n")}\n`, "utf8");
240
+ await fs.rename(temporary, target);
241
+ return { path: target, messageCount: messages.messages.length, skipped };
242
+ }
243
+
244
+ /** pi 的 usage 形状:缺的字段补 0,cost 恒为 0(导入的历史没有真实计费) */
245
+ function normalizeUsage(usage: { input?: number; output?: number; cacheRead?: number; cacheWrite?: number; reasoning?: number; totalTokens?: number }) {
246
+ const input = usage.input ?? 0;
247
+ const output = usage.output ?? 0;
248
+ const cacheRead = usage.cacheRead ?? 0;
249
+ const cacheWrite = usage.cacheWrite ?? 0;
250
+ return {
251
+ input,
252
+ output,
253
+ cacheRead,
254
+ cacheWrite,
255
+ ...(usage.reasoning ? { reasoning: usage.reasoning } : {}),
256
+ totalTokens: usage.totalTokens ?? input + output + cacheRead + cacheWrite,
257
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
258
+ };
259
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * ZCode:`~/.zcode/cli/db/db.sqlite`(session / message / part 三表)。
3
+ * 与 opencode 共用适配器,差异只在库路径与排序字段。
4
+ */
5
+ import { zcodeDb } from "./paths";
6
+ import { createOcFamilySource } from "./oc-family";
7
+
8
+ export const zcodeSource = createOcFamilySource("zcode", zcodeDb);
@@ -18,13 +18,48 @@ export interface SkillView {
18
18
  baseDir: string;
19
19
  }
20
20
 
21
- function classifyScope(filePath: string, cwd?: string): SkillView["scope"] {
22
- const norm = filePath.replace(/\\/g, "/").toLowerCase();
23
- const agentDir = getAgentDir().replace(/\\/g, "/").toLowerCase();
24
- const home = os.homedir().replace(/\\/g, "/").toLowerCase();
25
- if (norm.startsWith(`${agentDir}/`) || norm.startsWith(`${home}/.agents/`) || norm.startsWith(`${home}/.pi/`))
21
+ /**
22
+ * 路径比较用的规范形式:展开真实路径 → 统一分隔符 → 去掉结尾斜杠 → Windows 下大小写不敏感。
23
+ *
24
+ * realpath 这一步是必须的:同一个目录可以有多种写法——Windows 的 8.3 短名(`C:\Users\RUNNER~1`)、
25
+ * 含 `..` 的路径、junction / symlink。删技能、开关技能时 `resolveDiscoveredPath` 交出的是 **realpath 形式**,
26
+ * 而 agentDir 是配置里的原样字符串,两边直接比字符串就会把"本来就在 agent 目录下"的全局技能
27
+ * 判成 package 技能而拒绝删除(CI 的 Windows runner 上就是这样红的,真实用户只要路径里带短名也会中招)。
28
+ */
29
+ async function toComparablePath(target: string): Promise<string> {
30
+ let resolved: string;
31
+ try {
32
+ resolved = await fs.realpath(target);
33
+ } catch {
34
+ resolved = path.resolve(target); // 路径还不存在(如待创建的技能目录):退化成普通规范化
35
+ }
36
+ return resolved.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
37
+ }
38
+
39
+ interface ScopeContext {
40
+ /** 全部已展开成可比较形式 */
41
+ agentDir: string;
42
+ home: string;
43
+ cwd?: string;
44
+ }
45
+
46
+ /** 每次调用只展开一次来源路径,避免逐个技能重复 realpath */
47
+ async function scopeContext(cwd?: string): Promise<ScopeContext> {
48
+ return {
49
+ agentDir: await toComparablePath(getAgentDir()),
50
+ home: await toComparablePath(os.homedir()),
51
+ cwd: cwd ? await toComparablePath(cwd) : undefined,
52
+ };
53
+ }
54
+
55
+ /** 严格判断"在某个目录里面":必须落在分隔符之后,避免 /ws 误匹配 /ws-other */
56
+ const within = (target: string, root: string) => target.startsWith(`${root}/`);
57
+
58
+ async function classifyScope(filePath: string, context: ScopeContext): Promise<SkillView["scope"]> {
59
+ const norm = await toComparablePath(filePath);
60
+ if (within(norm, context.agentDir) || within(norm, `${context.home}/.agents`) || within(norm, `${context.home}/.pi`))
26
61
  return "global";
27
- if (cwd && norm.startsWith(path.resolve(cwd).replace(/\\/g, "/").toLowerCase())) return "project";
62
+ if (context.cwd && within(norm, context.cwd)) return "project";
28
63
  if (norm.includes("/node_modules/")) return "package";
29
64
  return "package";
30
65
  }
@@ -37,6 +72,7 @@ export async function listSkills(cwd?: string): Promise<SkillView[]> {
37
72
  await resourceLoaderReady(dir);
38
73
  const loader = getResourceLoader(dir);
39
74
  const { skills } = loader.getSkills();
75
+ const context = await scopeContext(dir);
40
76
  for (const s of skills) {
41
77
  if (seen.has(s.filePath)) continue;
42
78
  seen.add(s.filePath);
@@ -45,7 +81,7 @@ export async function listSkills(cwd?: string): Promise<SkillView[]> {
45
81
  description: s.description,
46
82
  filePath: s.filePath,
47
83
  disabled: s.disableModelInvocation,
48
- scope: classifyScope(s.filePath, dir),
84
+ scope: await classifyScope(s.filePath, context),
49
85
  baseDir: s.baseDir,
50
86
  });
51
87
  }
@@ -85,7 +121,8 @@ export async function setSkillDisabled(filePath: string, disabled: boolean, cwd?
85
121
  await fs.writeFile(authorized, next, "utf8");
86
122
  // 就地重载加载器并让活跃会话重建系统提示(技能列表在系统提示里);全局技能影响所有目录
87
123
  await reloadAllLoaders();
88
- const scope = classifyScope(authorized, cwd);
124
+ const context = await scopeContext(cwd);
125
+ const scope = await classifyScope(authorized, context);
89
126
  await reloadSessionsForCwd(scope === "project" ? cwd : undefined);
90
127
  return { changed: true };
91
128
  }
@@ -100,21 +137,21 @@ export async function readSkillFile(filePath: string, cwd?: string): Promise<str
100
137
  export async function deleteSkill(filePath: string, cwd?: string): Promise<{ removed: string }> {
101
138
  const skills = await listSkills(cwd);
102
139
  const authorized = await resolveDiscoveredPath(filePath, skills.map((skill) => skill.filePath));
103
- if (classifyScope(authorized, cwd) === "package") {
140
+ const context = await scopeContext(cwd);
141
+ if ((await classifyScope(authorized, context)) === "package") {
104
142
  throw new Error("package-managed skill: disable it instead (it is reinstalled with the package)");
105
143
  }
106
144
  const skillDir = path.dirname(authorized);
107
145
  // 只删技能目录本身,不能误删父目录(全局技能目录 ~/.pi/agent/skills、项目 .agents/skills 等)
108
- const agentDir = getAgentDir().replace(/\\/g, "/");
109
- const normalizedSkillDir = skillDir.replace(/\\/g, "/");
110
- const knownRoots = [`${agentDir}/skills`, `${agentDir}/web-skills`];
111
- if (!knownRoots.some((root) => normalizedSkillDir.toLowerCase() === root.toLowerCase())) {
146
+ const normalizedSkillDir = await toComparablePath(skillDir);
147
+ const knownRoots = [`${context.agentDir}/skills`, `${context.agentDir}/web-skills`];
148
+ if (!knownRoots.includes(normalizedSkillDir)) {
112
149
  // 非已知根:项目内技能目录,至少确认 SKILL.md 直接位于其下且目录名非空
113
150
  if (path.basename(skillDir) === "") throw new Error("refusing to delete workspace root");
114
151
  }
115
152
  await fs.rm(skillDir, { recursive: true, force: true });
116
153
  await reloadAllLoaders();
117
- const scope = classifyScope(authorized, cwd);
154
+ const scope = await classifyScope(authorized, context);
118
155
  await reloadSessionsForCwd(scope === "project" ? cwd : undefined);
119
156
  return { removed: skillDir };
120
157
  }
@@ -1 +0,0 @@
1
- exports.id=118,exports.ids=[118],exports.modules={49904:(a,b,c)=>{"use strict";c.d(b,{Ax:()=>k,Jt:()=>m,_i:()=>l});var d=c(51455),e=c.n(d),f=c(76760),g=c.n(f),h=c(80974),i=c.n(h);let j=new Map;function k(a,b){let c=g().resolve(a),d="win32"===process.platform?c.toLowerCase():c,e=(j.get(d)??Promise.resolve()).then(b,b),f=e.then(()=>void 0,()=>void 0);return j.set(d,f),f.then(()=>{j.get(d)===f&&j.delete(d)}),e}async function l(a,b){await e().mkdir(g().dirname(a),{recursive:!0});let c=await i().lock(a,{realpath:!1,retries:{retries:20,minTimeout:20,maxTimeout:100}});try{return await b()}finally{await c()}}async function m(a){await a.flush();let b=a.drainErrors();if(b.length)throw Error(`Pi settings failed: ${b.map(({scope:a,error:b})=>`${a}: ${b.message}`).join("; ")}`,{cause:b[0].error})}},75554:(a,b,c)=>{"use strict";c.a(a,async(a,d)=>{try{c.d(b,{DY:()=>p,Gh:()=>u,J0:()=>o});var e=c(51455),f=c.n(e),g=c(76760),h=c.n(g),i=c(77598),j=c(87982),k=c(49904),l=a([j]);j=(l.then?(await l)():l)[0];let q=16384,r=2e4,s=3,t=2e3;async function m(){let a;try{a=await f().readFile(h().join((0,j.$M)(),"settings.json"),"utf8")}catch(a){if("ENOENT"===a.code)return{};throw a}let b=JSON.parse(a.replace(/^\uFEFF/,""));if(!b||"object"!=typeof b||Array.isArray(b))throw Error("settings.json must contain a JSON object");return b}function n(a,b){let c=a[b];return c&&"object"==typeof c&&!Array.isArray(c)?c:{}}async function o(){let a=await m(),b=a.compaction??{},c=a.retry??{},d=(a,b)=>"number"==typeof a&&Number.isSafeInteger(a)&&a>=0?a:b;return{compaction:{enabled:!1!==b.enabled,reserveTokens:d(b.reserveTokens,q),keepRecentTokens:d(b.keepRecentTokens,r)},retry:{enabled:!1!==c.enabled,maxRetries:d(c.maxRetries,s),baseDelayMs:d(c.baseDelayMs,t)}}}class u extends Error{}async function p(a){let b=function(a){if(!a||"object"!=typeof a||Array.isArray(a))throw new u("invalid settings patch");if(Object.keys(a).some(a=>"compaction"!==a&&"retry"!==a))throw new u("unknown settings field");let b={};for(let c of["compaction","retry"]){if(void 0===a[c])continue;let d=a[c];if(!d||"object"!=typeof d||Array.isArray(d))throw new u(`invalid ${c} settings`);let e="compaction"===c?["enabled","reserveTokens","keepRecentTokens"]:["enabled","maxRetries","baseDelayMs"];for(let[a,b]of Object.entries(d)){if(!e.includes(a))throw new u(`unknown ${c} field: ${a}`);if("enabled"===a){if("boolean"!=typeof b)throw new u(`${c}.${a} must be boolean`)}else if("number"!=typeof b||!Number.isSafeInteger(b)||b<0||b>1e9)throw new u(`${c}.${a} must be a non-negative integer`)}"compaction"===c?b.compaction=d:b.retry=d}return b}(a),c=h().join((0,j.$M)(),"settings.json");await (0,k.Ax)(c,()=>(0,k._i)(c,async()=>{let a=await m();b.compaction&&(a.compaction={...n(a,"compaction"),...b.compaction}),b.retry&&(a.retry={...n(a,"retry"),...b.retry}),await f().mkdir(h().dirname(c),{recursive:!0});let d=`${c}.${process.pid}.${(0,i.randomUUID)()}.tmp`;try{await f().writeFile(d,JSON.stringify(a,null," "),"utf8"),await f().rename(d,c)}finally{await f().rm(d,{force:!0}).catch(()=>void 0)}})),await (0,j.BW)()}d()}catch(a){d(a)}})},78335:()=>{},87982:(a,b,c)=>{"use strict";c.a(a,async(a,d)=>{try{c.d(b,{$M:()=>i.getAgentDir,Ax:()=>Q,BW:()=>D,Cv:()=>i.SessionManager,Dc:()=>z,Df:()=>x,Ec:()=>w,G1:()=>J,Gg:()=>H,QR:()=>F,Sd:()=>I,X1:()=>E,YW:()=>r,Yb:()=>t,af:()=>n,az:()=>i.createAgentSession,fm:()=>p,jz:()=>A,rC:()=>l,uU:()=>k,v9:()=>B,vT:()=>q,wK:()=>o,wj:()=>G,xD:()=>C,ye:()=>u});var e=c(76760),f=c.n(e),g=c(73024),h=c.n(g),i=c(75430),j=a([i]);i=(j.then?(await j)():j)[0],process.env.PYTHONUTF8??="1",process.env.PYTHONIOENCODING??="utf-8";let K=null;function k(){return K||(K=i.ModelRuntime.create().catch(a=>{throw K=null,a})),K}function l(){K=null}let L=new Map,M=null;function m(){return M||(M=new i.ProjectTrustStore((0,i.getAgentDir)())),M}function n(a){let b=f().resolve(a),c=!1;try{c=(0,i.hasTrustRequiringProjectResources)(b)}catch{c=!1}if(!c)return{required:!1,trusted:!0,source:"not-required"};let d=m().get(b);if(null!==d)return{required:!0,trusted:d,source:"remembered"};let e=function(){try{let a=JSON.parse(h().readFileSync(f().join((0,i.getAgentDir)(),"settings.json"),"utf8"));return"always"===a.defaultProjectTrust||"never"===a.defaultProjectTrust?a.defaultProjectTrust:"ask"}catch{return"ask"}}();return"always"===e?{required:!0,trusted:!0,source:"default-always"}:"never"===e?{required:!0,trusted:!1,source:"default-never"}:{required:!0,trusted:!1,source:"undecided"}}function o(a,b){let c=f().resolve(a);m().set(c,b);let d=n(c),e="win32"===process.platform?c.toLowerCase():c,g=L.get(e);return g&&g.setProjectTrusted(d.trusted),d}function p(){for(let[a,b]of(M=null,L))try{b.setProjectTrusted(n(a).trusted)}catch{}}function q(a){let b=f().resolve(a),c="win32"===process.platform?b.toLowerCase():b,d=L.get(c);return d||(d=i.SettingsManager.create(b,(0,i.getAgentDir)(),{projectTrusted:n(b).trusted}),L.set(c,d)),d}let N=new Map,O=Promise.resolve();function r(a){let b=O.then(a,a);return O=b.then(()=>void 0,()=>void 0),b}function s(a){let b=f().resolve(a);return"win32"===process.platform?b.toLowerCase():b}async function t(a){let b=q(a);b.setProjectTrusted(n(a).trusted);let c=new i.DefaultResourceLoader({cwd:f().resolve(a),agentDir:(0,i.getAgentDir)(),settingsManager:b}),d=c.reload.bind(c);c.reload=async c=>{b.setProjectTrusted(n(a).trusted),await d(c)};try{return await r(()=>c.reload()),c}catch(a){throw c.getExtensions().runtime.invalidate(),a}}function u(a){let b=s(a),c=N.get(b);return c||(c={loader:new i.DefaultResourceLoader({cwd:f().resolve(a),agentDir:(0,i.getAgentDir)(),settingsManager:q(a)}),ready:Promise.resolve(),version:0,fingerprint:""},N.set(b,c),v(a,c)),c.loader}function v(a,b){let c=r(async()=>{q(a).setProjectTrusted(n(a).trusted),b.loader.getExtensions().runtime.invalidate();let c=y(a);await b.loader.reload(),b.fingerprint=c,b.version+=1});return b.ready=c,c.catch(()=>void 0),c}function w(a){return N.get(s(a))?.version??0}async function x(a){u(a),await N.get(s(a)).ready}function y(a){let b,c,d=[String(n(a).trusted)],e=(a,b)=>{try{let c=h().statSync(a);if(d.push(`${a}:${c.mtimeMs}:${c.ctimeMs}:${c.size}`),c.isDirectory()&&b<2)for(let c of h().readdirSync(a,{withFileTypes:!0}).sort((a,b)=>a.name.localeCompare(b.name)))"node_modules"!==c.name&&e(f().join(a,c.name),b+1)}catch{d.push(`${a}:missing`)}};for(let d of(b=(0,i.getAgentDir)(),c=f().join(f().resolve(a),".pi"),[f().join(b,"prompts"),f().join(b,"skills"),f().join(b,"extensions"),f().join(c,"prompts"),f().join(c,"skills"),f().join(c,"extensions")]))e(d,0);for(let b of[f().join((0,i.getAgentDir)(),"settings.json"),f().join(a,".pi","settings.json")])e(b,2);return d.join("\n")}function z(a){let b=N.get(s(a));return!b||y(a)!==b.fingerprint}async function A(a){await x(a);let b=N.get(s(a));return!!b&&y(a)!==b.fingerprint&&(await C(a),!0)}function B(a){return N.get(s(a))?.loader}async function C(a){let b=N.get(s(a));b?await v(a,b):await x(a)}async function D(){for(let a of L.values())try{await a.reload()}catch{}}async function E(){for(let a of L.values())try{await a.reload()}catch{}P.clear();let a=[];for(let[b,c]of N)try{await v(b,c),a.push(b)}catch{}return a}let P=new Map;function F(a){let b=f().resolve(a),c="win32"===process.platform?b.toLowerCase():b,d=P.get(c);return d||(d=new i.DefaultPackageManager({cwd:b,agentDir:(0,i.getAgentDir)(),settingsManager:q(b)}),P.set(c,d)),d}function G(a){return Buffer.from(a,"utf8").toString("base64url")}function H(a){return Buffer.from(a,"base64url").toString("utf8")}function I(a){return/^[A-Za-z0-9_-]+$/.test(a)&&a.length>8}function J(a){return i.SessionManager.open(a)}let Q={readonly:["read","grep","find","ls"],standard:["read","grep","find","ls","bash","edit","write"],full:[]};d()}catch(a){d(a)}})},96487:()=>{}};
@@ -1 +0,0 @@
1
- self.__BUILD_MANIFEST={__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},__routerFilterStatic:{numItems:22,errorRate:1e-4,numBits:422,numHashes:14,bitArray:[0,1,1,1,1,1,1,1,1,0,1,1,0,0,0,0,1,1,1,1,1,0,1,1,0,0,0,0,0,1,1,1,0,1,1,0,1,0,0,0,0,0,0,0,0,1,1,0,1,1,1,0,1,1,0,1,1,1,0,1,1,1,0,1,0,1,0,0,1,0,1,0,1,1,0,0,0,1,0,0,1,1,1,1,1,0,0,1,0,0,0,1,1,0,1,0,1,1,1,0,1,1,0,0,0,0,1,0,1,0,0,0,0,1,0,0,1,0,1,1,0,1,0,1,0,0,0,1,0,1,1,1,1,0,1,1,0,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,1,1,0,1,0,0,1,1,0,1,1,1,1,0,0,1,1,1,0,0,1,0,1,1,1,1,0,0,1,1,0,0,0,1,1,0,1,1,0,1,1,1,0,1,0,0,1,1,0,0,0,1,1,0,1,1,1,1,0,0,1,1,1,0,0,1,1,1,1,0,0,0,1,0,0,0,1,1,1,1,1,0,0,1,0,1,0,1,1,1,0,1,0,1,0,0,1,1,1,1,0,1,0,0,0,1,0,1,1,1,0,0,0,1,1,0,0,1,0,1,1,0,0,0,1,0,1,1,1,0,0,0,1,1,1,1,1,0,1,1,1,1,1,1,0,0,0,1,1,0,0,0,0,0,1,0,1,0,1,0,0,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,1,0,1,0,0,1,0,1,0,0,0,0,0,1,0,0,1,1,0,1,0,1,1,1,1,0,0,0,0,1,0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,1,0,1,1,0,1,0,0,1,0,1,0,1,0,0,0,1,0,0,1,1,1,1,1,1,0,0,0,0,1,0,1,0,1,0,0,1,1,0,1,1,1,1,1]},__routerFilterDynamic:{numItems:2,errorRate:1e-4,numBits:39,numHashes:14,bitArray:[1,0,1,0,0,0,0,0,1,1,0,1,1,1,1,1,0,0,1,0,0,1,0,0,0,1,0,1,1,0,1,1,0,1,0,1,1,0,0]},sortedPages:["/_app"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();