@jetcrab/pi-desk 1.1.1 → 1.1.5-dev
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/docs/pi-desk/README.md +47 -0
- package/docs/pi-desk/commands.md +76 -0
- package/docs/pi-desk/examples/local-application/browser/application.js +106 -0
- package/docs/pi-desk/examples/local-application/browser/entry.js +9 -0
- package/docs/pi-desk/examples/local-application/package.json +9 -0
- package/docs/pi-desk/examples/local-application/pi-desk.ts +11 -0
- package/docs/pi-desk/plugins.md +119 -0
- package/package.json +3 -2
- package/src/client/l2_biz/pi-settings/hooks/l2-use-pi-settings.ts +37 -21
- package/src/client/l2_biz/pi-settings/l2-pi-settings.tsx +37 -43
- package/src/client/l2_biz/pi-settings/views/l2-mcp-settings-editor.tsx +3 -1
- package/src/client/l2_biz/pi-settings/views/l2-mcp-settings-list.tsx +1 -1
- package/src/client/l2_biz/plugin-host/hooks/l2-use-plugin-catalog.ts +3 -1
- package/src/client/l2_biz/plugin-host/hooks/l2-use-plugin-download-source.ts +5 -1
- package/src/client/l2_biz/plugin-host/l2-plugin-capability-details.tsx +57 -41
- package/src/client/l2_biz/plugin-host/l2-plugin-management-controls.tsx +89 -116
- package/src/client/l2_biz/plugin-host/l2-plugin-management.tsx +492 -338
- package/src/client/l2_biz/plugin-host/l2-use-plugin-management.ts +72 -22
- package/src/client/l2_biz/plugin-host/views/l2-plugin-catalog-detail.tsx +104 -76
- package/src/client/l2_biz/plugin-host/views/l2-plugin-catalog-search.tsx +101 -56
- package/src/client/l2_biz/plugin-host/views/l2-plugin-direct-install.tsx +91 -59
- package/src/client/l2_biz/plugin-host/views/l2-plugin-download-source.tsx +36 -27
- package/src/client/l2_biz/plugin-host/views/l2-plugin-installed-list.tsx +218 -208
- package/src/client/l2_biz/plugin-host/views/l2-plugin-management-channel.tsx +12 -5
- package/src/client/l2_biz/plugin-host/views/l2-plugin-management-dialog.tsx +84 -106
- package/src/client/l2_biz/plugin-host/views/l2-plugin-readme.tsx +29 -15
- package/src/client/l2_biz/settings/hooks/l2-use-project-model-default-settings.ts +22 -11
- package/src/client/l2_biz/settings/l2-settings.tsx +6 -11
- package/src/client/l2_biz/settings/views/l2-project-model-default-settings.tsx +20 -14
- package/src/client/l2_biz/terminal/l2-terminal-workspace.tsx +35 -1
- package/src/client/l2_biz/workbench/file-workspace/l2-work-session-file-preview.tsx +6 -2
- package/src/client/l2_biz/workbench/hooks/l2-workbench-chat-viewport.ts +14 -1
- package/src/client/l2_biz/workbench/l2-workbench-branch-picker.tsx +33 -5
- package/src/client/l2_biz/workbench/l2-workbench-sidebar-layout.tsx +3 -1
- package/src/client/l2_biz/workbench/l2-workbench.tsx +20 -3
- package/src/client/l3_modules/conversation/l3-conversation-call-summary.tsx +0 -3
- package/src/client/l3_modules/project-files/l3-file-context-menu-content.tsx +1 -0
- package/src/client/l4_foundation/ui/l4-app-dialog.tsx +3 -1
- package/src/client/l4_foundation/ui/l4-image-viewer.tsx +21 -0
- package/src/client/l4_foundation/ui/shadcn/checkbox.tsx +27 -0
- package/src/client/l4_foundation/ui/shadcn/context-menu.tsx +8 -1
- package/src/client/l4_foundation/ui/shadcn/dropdown-menu.tsx +5 -0
- package/src/client/l4_foundation/ui/shadcn/popover.tsx +2 -0
- package/src/client/l4_foundation/ui/shadcn/select.tsx +2 -0
- package/src/client/l4_foundation/ui/shadcn/switch.tsx +31 -0
- package/src/client/l4_foundation/ui/shadcn/tabs.tsx +74 -0
- package/src/common/l2_biz/plugin/l2-plugin-management-messages.ts +52 -22
- package/src/common/l3_modules/conversation/l3-conversation-locale-messages.ts +2 -2
- package/src/common/l3_modules/conversation/l3-conversation-pidesk.ts +1 -1
- package/src/common/l4_foundation/plugin/l4-plugin-package.ts +1 -0
- package/src/server/l2_biz/plugin-management/l2-plugin-catalog.ts +16 -5
- package/src/server/l2_biz/plugin-management/l2-plugin-commands.ts +21 -1
- package/src/server/l2_biz/plugin-management/l2-plugin-management.ts +21 -2
- package/src/server/l2_biz/plugin-management/l2-plugin-updates.ts +17 -3
- package/src/server/l4_foundation/pi/l4-pi-basic-session-services.ts +3 -3
- package/src/server/l4_foundation/pi/l4-pi-package-maintenance-runner.mts +4 -11
- package/src/server/l4_foundation/pi/l4-pi-plugin-registry-config.ts +52 -34
- package/src/server/l4_foundation/pi/l4-pi-plugin-registry-network.ts +5 -3
- package/src/server/l4_foundation/pi/l4-pi-plugin-registry.ts +23 -8
- package/src/server/l4_foundation/pi/l4-pi-session-initialization.ts +2 -5
- package/src/server/l4_foundation/pidesk/l4-pidesk-command.ts +8 -1
- package/src/server/l4_foundation/pidesk/l4-pidesk-runtime.ts +24 -38
- package/src/server/l4_foundation/pidesk/l4-pidesk-system-prompt.ts +11 -0
- package/temp/build/pi-desk/release/.next/BUILD_ID +1 -1
- package/temp/build/pi-desk/release/.next/build-manifest.json +3 -3
- package/temp/build/pi-desk/release/.next/prerender-manifest.json +3 -3
- package/temp/build/pi-desk/release/.next/react-loadable-manifest.json +1 -1
- package/temp/build/pi-desk/release/.next/required-server-files.js +3 -3
- package/temp/build/pi-desk/release/.next/required-server-files.json +3 -3
- package/temp/build/pi-desk/release/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_global-error.html +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_global-error.rsc +3 -3
- package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +3 -3
- package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +3 -3
- package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/health/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/login/page.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/page.js +79 -79
- package/temp/build/pi-desk/release/.next/server/app/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/25406.js +8 -77
- package/temp/build/pi-desk/release/.next/server/chunks/{81107.js → 35348.js} +2 -2
- package/temp/build/pi-desk/release/.next/server/chunks/35985.js +4 -4
- package/temp/build/pi-desk/release/.next/server/chunks/36361.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/47757.js +6 -75
- package/temp/build/pi-desk/release/.next/server/chunks/50520.js +4 -4
- package/temp/build/pi-desk/release/.next/server/chunks/58330.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/84231.js +2 -2
- package/temp/build/pi-desk/release/.next/server/chunks/89236.js +2 -2
- package/temp/build/pi-desk/release/.next/server/chunks/92386.js +5 -5
- package/temp/build/pi-desk/release/.next/server/chunks/94518.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/9874.js +1 -1
- package/temp/build/pi-desk/release/.next/server/middleware-build-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/pages/500.html +1 -1
- package/temp/build/pi-desk/release/.next/server/server-reference-manifest.json +1 -1
- package/temp/build/pi-desk/release/.next/static/chunks/14197-3d3ddd426ac06c9c.js +3 -0
- package/temp/build/pi-desk/release/.next/static/chunks/34903-e4205c0becabea76.js +318 -0
- package/temp/build/pi-desk/release/.next/static/chunks/{54206.7c0792096df8a5cf.js → 54206.82a63ab971911eda.js} +3 -3
- package/temp/build/pi-desk/release/.next/static/chunks/74132-876f256c7a6101af.js +13 -0
- package/temp/build/pi-desk/release/.next/static/chunks/78923-17a9c337b29dcecd.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/96995-8f8313ea2ac3abd7.js +62 -0
- package/temp/build/pi-desk/release/.next/static/chunks/app/{layout-7c5409cced9c2501.js → layout-4faf3a52d219296d.js} +2 -2
- package/temp/build/pi-desk/release/.next/static/chunks/app/login/{page-8c87fb9e6c918848.js → page-bf893718bb151f3c.js} +1 -1
- package/temp/build/pi-desk/release/.next/static/chunks/app/page-28e1d77d6b884657.js +158 -0
- package/temp/build/pi-desk/release/.next/static/chunks/{webpack-45424d28099bbefa.js → webpack-7bfbf627379fb4c1.js} +1 -1
- package/temp/build/pi-desk/release/.next/static/css/3213ee28336bddc2.css +1 -0
- package/temp/build/pi-desk/release/.next/trace-build +1 -1
- package/src/server/l4_foundation/pidesk/l4-pidesk-skill-template.ts +0 -87
- package/temp/build/pi-desk/release/.next/static/chunks/27491-8b2d3e345b9142ea.js +0 -318
- package/temp/build/pi-desk/release/.next/static/chunks/7089-600a8f5593b7846c.js +0 -3
- package/temp/build/pi-desk/release/.next/static/chunks/74132-0884df6a6f11861a.js +0 -13
- package/temp/build/pi-desk/release/.next/static/chunks/78923-539a8b5f7af3c54a.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/96995-ae32167974365050.js +0 -62
- package/temp/build/pi-desk/release/.next/static/chunks/app/page-39b146293b6eb488.js +0 -158
- package/temp/build/pi-desk/release/.next/static/css/56486914a8ba0a1b.css +0 -1
- /package/temp/build/pi-desk/release/.next/static/{STVrBHA3z9KPtvBE4WcE2 → IgHJ40fniC07-ioYUKobL}/_buildManifest.js +0 -0
- /package/temp/build/pi-desk/release/.next/static/{STVrBHA3z9KPtvBE4WcE2 → IgHJ40fniC07-ioYUKobL}/_ssgManifest.js +0 -0
|
@@ -25,6 +25,7 @@ export const L4PluginRegistrySchema = z
|
|
|
25
25
|
}, '插件源必须是 HTTP 或 HTTPS 仓库地址')
|
|
26
26
|
export const L4PluginDownloadSourceSchema = z.discriminatedUnion('mode', [
|
|
27
27
|
z.object({ mode: z.literal('auto') }).strict(),
|
|
28
|
+
z.object({ mode: z.literal('local') }).strict(),
|
|
28
29
|
z.object({ mode: z.literal('official') }).strict(),
|
|
29
30
|
z.object({ mode: z.literal('domestic') }).strict(),
|
|
30
31
|
z.object({ mode: z.literal('custom'), registry: L4PluginRegistrySchema }).strict()
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
L4_PLUGIN_OFFICIAL_NAMES,
|
|
18
18
|
L4PluginRegistryError,
|
|
19
19
|
searchL4PluginCatalog,
|
|
20
|
+
readL4PluginInstallRegistry,
|
|
20
21
|
resolveL4PluginInstall,
|
|
21
22
|
type L4RegistryPackage
|
|
22
23
|
} from '@server/l4_foundation/pi/l4-pi-plugin-registry'
|
|
@@ -181,15 +182,21 @@ export class L2PluginCatalog {
|
|
|
181
182
|
}
|
|
182
183
|
|
|
183
184
|
async get(request: L2PluginCatalogGetRequest): Promise<L2PluginCatalogDetail> {
|
|
184
|
-
const
|
|
185
|
+
const downloadSource = readL4PiPluginPreferences().downloadSource
|
|
186
|
+
const registry =
|
|
187
|
+
downloadSource.mode === 'local'
|
|
188
|
+
? (await readL4PluginInstallRegistry(request.name, { downloadSource })).registry
|
|
189
|
+
: request.registry
|
|
190
|
+
const resolved = registry
|
|
185
191
|
? null
|
|
186
192
|
: await resolveL4PluginInstall(
|
|
187
|
-
`npm:${request.name}${request.version ? `@${request.version}` : ''}
|
|
193
|
+
`npm:${request.name}${request.version ? `@${request.version}` : ''}`,
|
|
194
|
+
{ downloadSource }
|
|
188
195
|
)
|
|
189
196
|
const detail = await getL4PluginCatalogDetail(
|
|
190
197
|
request.name,
|
|
191
198
|
resolved ? resolved.source.slice(resolved.source.lastIndexOf('@') + 1) : request.version,
|
|
192
|
-
|
|
199
|
+
registry ?? resolved?.registry
|
|
193
200
|
)
|
|
194
201
|
const manifest = detail.manifest
|
|
195
202
|
return {
|
|
@@ -211,9 +218,13 @@ export class L2PluginCatalog {
|
|
|
211
218
|
}
|
|
212
219
|
|
|
213
220
|
async getSettings(): Promise<L2PluginCatalogSettings> {
|
|
221
|
+
const downloadSource = readL4PiPluginPreferences().downloadSource
|
|
214
222
|
return {
|
|
215
|
-
downloadSource
|
|
216
|
-
recommendedRegistry:
|
|
223
|
+
downloadSource,
|
|
224
|
+
recommendedRegistry:
|
|
225
|
+
downloadSource.mode === 'local'
|
|
226
|
+
? L4_PLUGIN_OFFICIAL_REGISTRY
|
|
227
|
+
: await getL4PluginRecommendedRegistry()
|
|
217
228
|
}
|
|
218
229
|
}
|
|
219
230
|
|
|
@@ -65,6 +65,12 @@ async function resolveTargets(
|
|
|
65
65
|
const targets = new Map<string, CommandTarget>()
|
|
66
66
|
for (const name of names) {
|
|
67
67
|
const existing = findL2PluginCommandTarget(snapshot.plugins, name, agentDir)
|
|
68
|
+
if (command.action === 'reload') {
|
|
69
|
+
if (!existing) throw new Error(`插件不存在:${name}`)
|
|
70
|
+
if (existing.status === 'disabled') throw new Error(`插件已禁用,请先启用:${name}`)
|
|
71
|
+
targets.set(existing.source, { name, source: existing.source, local: false })
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
68
74
|
if (command.action === 'remove') {
|
|
69
75
|
if (!existing) throw new Error(`插件不存在:${name}`)
|
|
70
76
|
if (existing.kind !== 'package') {
|
|
@@ -102,6 +108,18 @@ async function acceptTargets(
|
|
|
102
108
|
targets: CommandTarget[],
|
|
103
109
|
management: CommandManagement
|
|
104
110
|
): Promise<CommandAcceptance[]> {
|
|
111
|
+
if (command.action === 'reload') {
|
|
112
|
+
const acceptances: CommandAcceptance[] = []
|
|
113
|
+
for (const target of targets) {
|
|
114
|
+
try {
|
|
115
|
+
await management.apply([target.source])
|
|
116
|
+
acceptances.push({ target, error: null })
|
|
117
|
+
} catch (error) {
|
|
118
|
+
acceptances.push({ target, error: failureMessage(error) })
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return acceptances
|
|
122
|
+
}
|
|
105
123
|
const results = new Map<string, string | null>()
|
|
106
124
|
const packages = targets.filter((target) => !target.local)
|
|
107
125
|
const locals = targets.filter((target) => target.local)
|
|
@@ -170,6 +188,7 @@ function completedResult(
|
|
|
170
188
|
if (command.version && item.version !== command.version) {
|
|
171
189
|
throw new Error(`实际版本 ${item.version ?? '未知'} 与目标版本 ${command.version} 不一致`)
|
|
172
190
|
}
|
|
191
|
+
if (command.action === 'reload') return '成功:已重新加载本机代码,已有 Pi 会话保持不变'
|
|
173
192
|
return target.local
|
|
174
193
|
? '成功:已加载当前代码,服务与界面已更新'
|
|
175
194
|
: `成功:已安装${item.version ? ` ${item.version}` : ''}并完成宿主加载`
|
|
@@ -303,7 +322,8 @@ export async function executeL2PluginCommand(
|
|
|
303
322
|
if (acceptedCount === 0) {
|
|
304
323
|
throw new Error(acceptances.map(({ target, error }) => `${target.name}:${error}`).join('\n'))
|
|
305
324
|
}
|
|
306
|
-
const action =
|
|
325
|
+
const action =
|
|
326
|
+
command.action === 'remove' ? '卸载' : command.action === 'reload' ? '重载' : '安装'
|
|
307
327
|
const description = `${action} ${targets.map((target) => target.name).join('、')}${command.version ? `@${command.version}` : command.tag ? `(渠道 ${command.tag})` : ''}`
|
|
308
328
|
console.info('[Pi Desk][Commands] 插件命令已接纳', {
|
|
309
329
|
operation: description,
|
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
} from '@common/l4_foundation/locale/l4-localized-text'
|
|
10
10
|
|
|
11
11
|
import { createHash, randomUUID } from 'node:crypto'
|
|
12
|
-
import { mkdir, open, readFile, realpath, rm, stat, writeFile } from 'node:fs/promises'
|
|
13
|
-
import { isAbsolute, join, relative, resolve } from 'node:path'
|
|
12
|
+
import { copyFile, mkdir, open, readFile, realpath, rm, stat, writeFile } from 'node:fs/promises'
|
|
13
|
+
import { dirname, isAbsolute, join, relative, resolve } from 'node:path'
|
|
14
14
|
import {
|
|
15
15
|
DefaultPackageManager,
|
|
16
16
|
getAgentDir,
|
|
@@ -1009,6 +1009,25 @@ export class L2PluginManagement {
|
|
|
1009
1009
|
|
|
1010
1010
|
console.info('[Pi Desk][PluginManagement] 开始在隔离 AgentDir 预检候选插件', { source })
|
|
1011
1011
|
try {
|
|
1012
|
+
// npm 解析结果没有 registry 表示本机模式,预检需保留完整项目配置而非单个源地址。
|
|
1013
|
+
if (!registry && l2PluginNpmName(source)) {
|
|
1014
|
+
for (const configPath of ['.npmrc', join('etc', 'npmrc')]) {
|
|
1015
|
+
const target = join(stagingAgentDir, 'npm', configPath)
|
|
1016
|
+
await mkdir(dirname(target), { recursive: true })
|
|
1017
|
+
try {
|
|
1018
|
+
await copyFile(join(this.agentDir, 'npm', configPath), target)
|
|
1019
|
+
console.info('[Pi Desk][PluginManagement] 已为隔离预检继承本机 npm 配置', {
|
|
1020
|
+
source,
|
|
1021
|
+
configPath
|
|
1022
|
+
})
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT')
|
|
1025
|
+
throw new L2PluginManagementInvalidPackageError(
|
|
1026
|
+
`无法继承本机 npm 配置(${configPath}):${errorMessage(error)}`
|
|
1027
|
+
)
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1012
1031
|
await runL4PiPackageMaintenance({
|
|
1013
1032
|
maintenance: { action: 'install', source, ...(registry ? { registry } : {}) },
|
|
1014
1033
|
agentDir: stagingAgentDir,
|
|
@@ -11,7 +11,10 @@ import {
|
|
|
11
11
|
l4PluginNpmName,
|
|
12
12
|
l4PluginSourceIdentity
|
|
13
13
|
} from '@common/l4_foundation/plugin/l4-plugin-package'
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
readL4PiPluginPreferences,
|
|
16
|
+
readL4PiPluginUpdateTag
|
|
17
|
+
} from '@server/l4_foundation/pi/l4-pi-plugin-preferences'
|
|
15
18
|
import {
|
|
16
19
|
readL4PluginInstallRegistry,
|
|
17
20
|
resolveL4PluginInstall
|
|
@@ -109,12 +112,23 @@ export class L2PluginUpdates {
|
|
|
109
112
|
continue
|
|
110
113
|
let key: string | null = null
|
|
111
114
|
try {
|
|
112
|
-
const
|
|
113
|
-
|
|
115
|
+
const downloadSource = readL4PiPluginPreferences(this.agentDir).downloadSource
|
|
116
|
+
const policy = await readL4PluginInstallRegistry(name, {
|
|
117
|
+
agentDir: this.agentDir,
|
|
118
|
+
downloadSource
|
|
119
|
+
})
|
|
120
|
+
key = JSON.stringify([
|
|
121
|
+
identity,
|
|
122
|
+
tag,
|
|
123
|
+
downloadSource.mode,
|
|
124
|
+
policy.registry,
|
|
125
|
+
policy.fallback
|
|
126
|
+
])
|
|
114
127
|
let value = this.cache.get(key)?.value
|
|
115
128
|
if (check) {
|
|
116
129
|
if (!source.path || !source.version) throw new Error('无法读取插件已安装版本')
|
|
117
130
|
const resolved = await resolveL4PluginInstall(`npm:${name}@${tag}`, {
|
|
131
|
+
downloadSource,
|
|
118
132
|
agentDir: this.agentDir,
|
|
119
133
|
signal: this.signal,
|
|
120
134
|
cache: false
|
|
@@ -2,7 +2,7 @@ import 'server-only'
|
|
|
2
2
|
|
|
3
3
|
import { existsSync, readFileSync } from 'node:fs'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
|
-
import {
|
|
5
|
+
import { appendL4PiDeskSystemPrompt } from '@server/l4_foundation/pidesk/l4-pidesk-runtime'
|
|
6
6
|
import {
|
|
7
7
|
CONFIG_DIR_NAME,
|
|
8
8
|
createExtensionRuntime,
|
|
@@ -38,7 +38,7 @@ class BasicResourceLoader implements ResourceLoader {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
getSkills(): ReturnType<ResourceLoader['getSkills']> {
|
|
41
|
-
return { skills:
|
|
41
|
+
return { skills: [], diagnostics: [] }
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
getPrompts(): ReturnType<ResourceLoader['getPrompts']> {
|
|
@@ -62,7 +62,7 @@ class BasicResourceLoader implements ResourceLoader {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
getAppendSystemPrompt(): string[] {
|
|
65
|
-
return this.appendPrompt ? [this.appendPrompt.content] : []
|
|
65
|
+
return appendL4PiDeskSystemPrompt(this.appendPrompt ? [this.appendPrompt.content] : [])
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
getAppendSystemPromptSources(): Array<{ path: string }> {
|
|
@@ -64,23 +64,16 @@ async function main(): Promise<void> {
|
|
|
64
64
|
if (!source || source.length > 2048) throw new Error('插件维护来源无效')
|
|
65
65
|
const registry = process.argv[4] ? L4PluginRegistrySchema.parse(process.argv[4]) : undefined
|
|
66
66
|
const scope = /^npm:(@[^/]+)\//.exec(source)?.[1]
|
|
67
|
+
// scoped 源只覆盖对应 scope,公共依赖继续使用原有默认源。
|
|
68
|
+
const registryKey = scope ? `npm_config_${scope}:registry` : 'npm_config_registry'
|
|
67
69
|
const inheritedEnvironment = { ...process.env }
|
|
68
70
|
if (registry) {
|
|
69
71
|
// Windows 子进程的环境名不区分大小写,不能同时保留两种拼写。
|
|
70
|
-
const replaced = new Set([
|
|
71
|
-
'npm_config_registry',
|
|
72
|
-
...(scope ? [`npm_config_${scope}:registry`.toLowerCase()] : [])
|
|
73
|
-
])
|
|
74
72
|
for (const key of Object.keys(inheritedEnvironment)) {
|
|
75
|
-
if (
|
|
73
|
+
if (key.toLowerCase() === registryKey.toLowerCase()) delete inheritedEnvironment[key]
|
|
76
74
|
}
|
|
77
75
|
}
|
|
78
|
-
const registryEnvironment: Record<string, string> = registry
|
|
79
|
-
? {
|
|
80
|
-
npm_config_registry: registry,
|
|
81
|
-
...(scope ? { [`npm_config_${scope}:registry`]: registry } : {})
|
|
82
|
-
}
|
|
83
|
-
: {}
|
|
76
|
+
const registryEnvironment: Record<string, string> = registry ? { [registryKey]: registry } : {}
|
|
84
77
|
|
|
85
78
|
const configuredAgentDir = process.env.PI_CODING_AGENT_DIR
|
|
86
79
|
const agentDir =
|
|
@@ -2,14 +2,15 @@ import 'server-only'
|
|
|
2
2
|
|
|
3
3
|
import { execFile } from 'node:child_process'
|
|
4
4
|
import { existsSync } from 'node:fs'
|
|
5
|
-
import { readFile } from 'node:fs/promises'
|
|
5
|
+
import { mkdir, readFile } from 'node:fs/promises'
|
|
6
6
|
import { dirname, join, resolve } from 'node:path'
|
|
7
7
|
import { homedir } from 'node:os'
|
|
8
8
|
import { promisify } from 'node:util'
|
|
9
|
+
import { getAgentDir } from '@earendil-works/pi-coding-agent'
|
|
9
10
|
|
|
10
11
|
const execute = promisify(execFile)
|
|
11
12
|
type NpmConfig = Record<string, string>
|
|
12
|
-
let cached: { cwd: string; expires: number; value: Promise<NpmConfig> } | undefined
|
|
13
|
+
let cached: { cwd: string; prefix: string; expires: number; value: Promise<NpmConfig> } | undefined
|
|
13
14
|
|
|
14
15
|
function npmCli(): string | undefined {
|
|
15
16
|
const directories = [
|
|
@@ -49,17 +50,33 @@ async function readNpmrc(path: string, config: NpmConfig): Promise<void> {
|
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
function npmConfigPath(path: string, cwd: string): string {
|
|
54
|
+
const homePattern = process.platform === 'win32' ? /^~[\\/]/ : /^~\//
|
|
55
|
+
return homePattern.test(path) ? resolve(homedir(), path.slice(2)) : resolve(cwd, path)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function loadConfig(cwd: string, installRoot: string): Promise<NpmConfig> {
|
|
59
|
+
const environment: NpmConfig = {}
|
|
60
|
+
// 与 npm loadEnv 一致:大小写统一后按环境枚举顺序覆盖,空值不参与配置。
|
|
61
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
62
|
+
if (value !== undefined && value !== '' && /^npm_config_/i.test(key)) {
|
|
63
|
+
const original = key.slice(11)
|
|
64
|
+
const name = original.startsWith('//')
|
|
65
|
+
? original
|
|
66
|
+
: original.replace(/(?!^)_/g, '-').toLowerCase()
|
|
67
|
+
environment[name] = expand(value)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
53
70
|
const cli = npmCli()
|
|
54
71
|
let effective: Record<string, unknown> = {}
|
|
55
72
|
if (cli) {
|
|
56
73
|
try {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
windowsHide: true
|
|
62
|
-
|
|
74
|
+
await mkdir(cwd, { recursive: true })
|
|
75
|
+
const result = await execute(
|
|
76
|
+
process.execPath,
|
|
77
|
+
[cli, 'config', 'list', '--json', '--prefix', installRoot],
|
|
78
|
+
{ cwd, timeout: 4000, maxBuffer: 512 * 1024, windowsHide: true }
|
|
79
|
+
)
|
|
63
80
|
effective = JSON.parse(result.stdout) as Record<string, unknown>
|
|
64
81
|
} catch {
|
|
65
82
|
// 不输出 npm 的 stderr:其中可能包含注册表鉴权信息。
|
|
@@ -68,18 +85,25 @@ async function loadConfig(): Promise<NpmConfig> {
|
|
|
68
85
|
}
|
|
69
86
|
const config: NpmConfig = {}
|
|
70
87
|
if (cli) await readNpmrc(resolve(dirname(cli), '../npmrc'), config)
|
|
71
|
-
|
|
88
|
+
// npm 默认 globalconfig 同样受 CLI --prefix 影响,不能回退到 Node 安装目录。
|
|
72
89
|
const globalPath =
|
|
73
|
-
typeof effective.globalconfig === 'string'
|
|
90
|
+
typeof effective.globalconfig === 'string'
|
|
91
|
+
? effective.globalconfig
|
|
92
|
+
: (environment.globalconfig ?? join(installRoot, 'etc/npmrc'))
|
|
74
93
|
const userPath =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
await readNpmrc(globalPath, config)
|
|
79
|
-
await readNpmrc(userPath, config)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
94
|
+
typeof effective.userconfig === 'string'
|
|
95
|
+
? effective.userconfig
|
|
96
|
+
: (environment.userconfig ?? join(homedir(), '.npmrc'))
|
|
97
|
+
await readNpmrc(npmConfigPath(globalPath, cwd), config)
|
|
98
|
+
await readNpmrc(npmConfigPath(userPath, cwd), config)
|
|
99
|
+
// Pi user-scope 安装传入 --prefix,项目 npmrc 属于安装根,不属于应用或维护 cwd。
|
|
100
|
+
if (
|
|
101
|
+
effective.global !== true &&
|
|
102
|
+
environment.global !== 'true' &&
|
|
103
|
+
effective.location !== 'global' &&
|
|
104
|
+
environment.location !== 'global'
|
|
105
|
+
)
|
|
106
|
+
await readNpmrc(join(installRoot, '.npmrc'), config)
|
|
83
107
|
for (const [key, value] of Object.entries(effective)) {
|
|
84
108
|
if (
|
|
85
109
|
typeof value === 'string' &&
|
|
@@ -88,23 +112,17 @@ async function loadConfig(): Promise<NpmConfig> {
|
|
|
88
112
|
)
|
|
89
113
|
config[key] = value
|
|
90
114
|
}
|
|
91
|
-
|
|
92
|
-
if (value !== undefined && /^npm_config_/i.test(key)) {
|
|
93
|
-
const original = key.slice(11)
|
|
94
|
-
const name = original.startsWith('//')
|
|
95
|
-
? original
|
|
96
|
-
: original.replace(/(?!^)_/g, '-').toLowerCase()
|
|
97
|
-
config[name] = expand(value)
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return config
|
|
115
|
+
return { ...config, ...environment }
|
|
101
116
|
}
|
|
102
117
|
|
|
103
|
-
export function readRegistryNpmConfig(): Promise<NpmConfig> {
|
|
104
|
-
const cwd =
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
export function readRegistryNpmConfig(agentDir = getAgentDir()): Promise<NpmConfig> {
|
|
119
|
+
const cwd =
|
|
120
|
+
process.env.PI_DESK_PACKAGE_MAINTENANCE_CWD ??
|
|
121
|
+
join(agentDir, 'pi-desk', 'plugin-package-manager')
|
|
122
|
+
const prefix = join(agentDir, 'npm')
|
|
123
|
+
if (!cached || cached.cwd !== cwd || cached.prefix !== prefix || cached.expires <= Date.now()) {
|
|
124
|
+
const value = loadConfig(cwd, prefix)
|
|
125
|
+
cached = { cwd, prefix, expires: Date.now() + 30_000, value }
|
|
108
126
|
void value.catch(() => {
|
|
109
127
|
if (cached?.value === value) cached = undefined
|
|
110
128
|
})
|
|
@@ -26,10 +26,11 @@ export async function fetchRegistryBytes(
|
|
|
26
26
|
timeout?: number
|
|
27
27
|
cache?: boolean
|
|
28
28
|
auth?: boolean
|
|
29
|
+
agentDir?: string
|
|
29
30
|
}
|
|
30
31
|
): Promise<Buffer> {
|
|
31
32
|
options.signal?.throwIfAborted()
|
|
32
|
-
const config = options.auth === false ? {} : await readRegistryNpmConfig()
|
|
33
|
+
const config = options.auth === false ? {} : await readRegistryNpmConfig(options.agentDir)
|
|
33
34
|
const authorization = registryAuth(url, config)
|
|
34
35
|
const key = `${url.href}:${options.limit}:${createHash('sha256')
|
|
35
36
|
.update(authorization ?? '')
|
|
@@ -133,9 +134,10 @@ export async function fetchRegistryBytes(
|
|
|
133
134
|
|
|
134
135
|
export async function fetchRegistryJson(
|
|
135
136
|
url: URL,
|
|
136
|
-
signal?: AbortSignal
|
|
137
|
+
signal?: AbortSignal,
|
|
138
|
+
agentDir?: string
|
|
137
139
|
): Promise<Record<string, unknown>> {
|
|
138
|
-
const body = await fetchRegistryBytes(url, { limit: 8 * 1024 * 1024, signal })
|
|
140
|
+
const body = await fetchRegistryBytes(url, { limit: 8 * 1024 * 1024, signal, agentDir })
|
|
139
141
|
try {
|
|
140
142
|
const parsed: unknown = JSON.parse(body.toString('utf8'))
|
|
141
143
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('invalid')
|
|
@@ -102,16 +102,25 @@ export async function readL4PluginInstallRegistry(
|
|
|
102
102
|
name: string,
|
|
103
103
|
options: { downloadSource?: L4PluginDownloadSource; registry?: string; agentDir?: string }
|
|
104
104
|
): Promise<{ registry: string; fallback: boolean }> {
|
|
105
|
+
const preferences = readL4PiPluginPreferences(options.agentDir)
|
|
106
|
+
const source = options.downloadSource ?? preferences.downloadSource
|
|
107
|
+
if (source.mode === 'local') {
|
|
108
|
+
const config = await readRegistryNpmConfig(options.agentDir)
|
|
109
|
+
const scoped = name.startsWith('@') ? config[`${name.split('/')[0]}:registry`] : undefined
|
|
110
|
+
return {
|
|
111
|
+
registry: normalizeRegistry(scoped || config.registry || L4_PLUGIN_OFFICIAL_REGISTRY),
|
|
112
|
+
fallback: false
|
|
113
|
+
}
|
|
114
|
+
}
|
|
105
115
|
if (options.registry) {
|
|
106
116
|
const registry = normalizeRegistry(options.registry)
|
|
107
117
|
if (![L4_PLUGIN_OFFICIAL_REGISTRY, L4_PLUGIN_DOMESTIC_REGISTRY].includes(registry))
|
|
108
118
|
return { registry, fallback: false }
|
|
109
119
|
}
|
|
110
|
-
const preferences = readL4PiPluginPreferences(options.agentDir)
|
|
111
120
|
const saved = preferences.registries[`npm:${name}`]
|
|
112
121
|
if (saved) return { registry: normalizeRegistry(saved), fallback: false }
|
|
113
122
|
if (name.startsWith('@')) {
|
|
114
|
-
const config = await readRegistryNpmConfig()
|
|
123
|
+
const config = await readRegistryNpmConfig(options.agentDir)
|
|
115
124
|
const scoped = config[`${name.split('/')[0]}:registry`]
|
|
116
125
|
if (scoped) {
|
|
117
126
|
const registry = normalizeRegistry(scoped)
|
|
@@ -119,7 +128,6 @@ export async function readL4PluginInstallRegistry(
|
|
|
119
128
|
return { registry, fallback: false }
|
|
120
129
|
}
|
|
121
130
|
}
|
|
122
|
-
const source = options.downloadSource ?? preferences.downloadSource
|
|
123
131
|
if (source.mode === 'custom')
|
|
124
132
|
return { registry: normalizeRegistry(source.registry), fallback: false }
|
|
125
133
|
if (source.mode === 'official') return { registry: L4_PLUGIN_OFFICIAL_REGISTRY, fallback: false }
|
|
@@ -145,7 +153,9 @@ export async function resolveL4PluginInstall(
|
|
|
145
153
|
const selector = match[2] ?? 'latest'
|
|
146
154
|
if (selector.length > 128 || !/^[a-zA-Z0-9._+-]+$/.test(selector))
|
|
147
155
|
throw new L4PluginRegistryError('npm 插件版本或标签无效', 400)
|
|
148
|
-
const
|
|
156
|
+
const downloadSource =
|
|
157
|
+
options.downloadSource ?? readL4PiPluginPreferences(options.agentDir).downloadSource
|
|
158
|
+
const selected = await readL4PluginInstallRegistry(name, { ...options, downloadSource })
|
|
149
159
|
let registry = selected.registry
|
|
150
160
|
let target = selector
|
|
151
161
|
let version: string
|
|
@@ -172,18 +182,23 @@ export async function resolveL4PluginInstall(
|
|
|
172
182
|
version = chooseVersion(metadata, target)
|
|
173
183
|
validManifest(registryObject(metadata.versions)[version], name, version)
|
|
174
184
|
}
|
|
175
|
-
|
|
185
|
+
// 本机源仅供版本查询,安装必须继续沿用 npm 配置,也不能保存为逐包仓库归属。
|
|
186
|
+
return {
|
|
187
|
+
source: `npm:${name}@${version}`,
|
|
188
|
+
...(downloadSource.mode === 'local' ? {} : { registry })
|
|
189
|
+
}
|
|
176
190
|
}
|
|
177
191
|
|
|
178
192
|
async function readInstallMetadata(
|
|
179
193
|
url: URL,
|
|
180
|
-
options: { signal?: AbortSignal; cache?: boolean }
|
|
194
|
+
options: { signal?: AbortSignal; cache?: boolean; agentDir?: string }
|
|
181
195
|
): Promise<Record<string, unknown>> {
|
|
182
|
-
if (options.cache !== false) return fetchRegistryJson(url, options.signal)
|
|
196
|
+
if (options.cache !== false) return fetchRegistryJson(url, options.signal, options.agentDir)
|
|
183
197
|
const body = await fetchRegistryBytes(url, {
|
|
184
198
|
limit: 8 * 1024 * 1024,
|
|
185
199
|
signal: options.signal,
|
|
186
|
-
cache: false
|
|
200
|
+
cache: false,
|
|
201
|
+
agentDir: options.agentDir
|
|
187
202
|
})
|
|
188
203
|
try {
|
|
189
204
|
const parsed: unknown = JSON.parse(body.toString('utf8'))
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import type { CapabilityMode } from '@jetcrab/pi-desk-sdk/capabilities'
|
|
16
16
|
import {
|
|
17
17
|
createL4PiDeskTool,
|
|
18
|
-
|
|
18
|
+
appendL4PiDeskSystemPrompt,
|
|
19
19
|
type L4PiDeskCommandResult
|
|
20
20
|
} from '@server/l4_foundation/pidesk/l4-pidesk-runtime'
|
|
21
21
|
import { L4PiCapabilityRuntime } from './l4-pi-capability-runtime'
|
|
@@ -313,10 +313,7 @@ export class L4PiSessionInitialization {
|
|
|
313
313
|
: await this.options.resources.createServices(
|
|
314
314
|
this.options.cwd,
|
|
315
315
|
{
|
|
316
|
-
|
|
317
|
-
...loaded,
|
|
318
|
-
skills: readL4PiDeskSkills(loaded.skills)
|
|
319
|
-
}),
|
|
316
|
+
appendSystemPromptOverride: appendL4PiDeskSystemPrompt,
|
|
320
317
|
eventBus: this.capabilities.eventBus(this.plugins.eventBus),
|
|
321
318
|
extensionsOverride: (loaded) => {
|
|
322
319
|
if (!this.active) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'server-only'
|
|
2
2
|
|
|
3
|
-
export type L4PiDeskPluginAction = 'list' | 'show' | 'install' | 'remove'
|
|
3
|
+
export type L4PiDeskPluginAction = 'list' | 'show' | 'install' | 'reload' | 'remove'
|
|
4
4
|
|
|
5
5
|
export type L4PiDeskCommand =
|
|
6
6
|
| { kind: 'help'; message: string }
|
|
@@ -54,6 +54,13 @@ const definitions: readonly CommandDefinition[] = [
|
|
|
54
54
|
version: true,
|
|
55
55
|
tag: true
|
|
56
56
|
},
|
|
57
|
+
{
|
|
58
|
+
path: ['plugins', 'reload'],
|
|
59
|
+
description:
|
|
60
|
+
'重新加载一个或多个已安装插件;不下载、不重启宿主,不重载已有 Pi 会话;完成后向本会话汇总结果',
|
|
61
|
+
name: 'multiple',
|
|
62
|
+
scope: 'required'
|
|
63
|
+
},
|
|
57
64
|
{
|
|
58
65
|
path: ['plugins', 'remove'],
|
|
59
66
|
description: '卸载一个或多个包来源;完成后向本会话汇总结果',
|
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
import 'server-only'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { access, readFile, rm } from 'node:fs/promises'
|
|
4
4
|
import { dirname, join } from 'node:path'
|
|
5
|
-
import {
|
|
6
|
-
defineTool,
|
|
7
|
-
getAgentDir,
|
|
8
|
-
getDocsPath,
|
|
9
|
-
loadSkillsFromDir,
|
|
10
|
-
truncateHead,
|
|
11
|
-
type Skill
|
|
12
|
-
} from '@earendil-works/pi-coding-agent'
|
|
5
|
+
import { defineTool, getAgentDir, truncateHead } from '@earendil-works/pi-coding-agent'
|
|
13
6
|
import { Type } from 'typebox'
|
|
14
7
|
import type { AgentToolResult } from '@earendil-works/pi-agent-core'
|
|
15
8
|
import { getL4PiDeskDataDir } from '@server/l4_foundation/pi/l4-pi-desk-data-dir'
|
|
16
9
|
import { isL4PiDeskSafeMode } from '@server/l4_foundation/pi/l4-pi-desk-mode'
|
|
17
10
|
import { parseL4PiDeskCommand, type L4PiDeskCommand } from './l4-pidesk-command'
|
|
18
|
-
import {
|
|
11
|
+
import { renderL4PiDeskSystemPrompt } from './l4-pidesk-system-prompt'
|
|
19
12
|
|
|
20
13
|
export interface L4PiDeskCommandResult {
|
|
21
14
|
mode: 'sync' | 'async'
|
|
@@ -36,7 +29,8 @@ export type L4PiDeskPluginCommandHandler = (
|
|
|
36
29
|
|
|
37
30
|
interface CommandRuntime {
|
|
38
31
|
handler: L4PiDeskPluginCommandHandler
|
|
39
|
-
|
|
32
|
+
systemPrompt: string
|
|
33
|
+
docsDirectory: string
|
|
40
34
|
version: string
|
|
41
35
|
development: boolean
|
|
42
36
|
agentDir: string
|
|
@@ -66,45 +60,37 @@ export async function initializeL4PiDeskCommands(
|
|
|
66
60
|
const sdkRoot = dirname(
|
|
67
61
|
dirname(createRequire(join(hostRoot, 'package.json')).resolve('@jetcrab/pi-desk-sdk'))
|
|
68
62
|
)
|
|
69
|
-
const
|
|
70
|
-
await
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}),
|
|
79
|
-
'utf8'
|
|
80
|
-
),
|
|
81
|
-
writeFile(
|
|
82
|
-
join(skillRoot, 'references', 'plugins.md'),
|
|
83
|
-
renderL4PiDeskPluginGuide(portable(agentDir)),
|
|
84
|
-
'utf8'
|
|
85
|
-
)
|
|
86
|
-
])
|
|
87
|
-
const loaded = loadSkillsFromDir({ dir: skillRoot, source: 'pidesk' })
|
|
88
|
-
const skill = loaded.skills.find((item) => item.name === 'pi-desk')
|
|
89
|
-
if (!skill) throw new Error('Pi Desk Skill 生成后无法加载')
|
|
63
|
+
const docsDirectory = join(hostRoot, 'docs', 'pi-desk')
|
|
64
|
+
await Promise.all(
|
|
65
|
+
['README.md', 'commands.md', 'plugins.md'].map((file) => access(join(docsDirectory, file)))
|
|
66
|
+
)
|
|
67
|
+
// 只清理当前环境曾生成的宿主 Skill,用户 Skills 和其他宿主环境保持不变。
|
|
68
|
+
await rm(join(getL4PiDeskDataDir(), development ? 'dev-skills' : 'skills', 'pi-desk'), {
|
|
69
|
+
recursive: true,
|
|
70
|
+
force: true
|
|
71
|
+
})
|
|
90
72
|
const runtime: CommandRuntime = {
|
|
91
73
|
handler,
|
|
92
|
-
|
|
74
|
+
docsDirectory,
|
|
75
|
+
systemPrompt: renderL4PiDeskSystemPrompt({
|
|
76
|
+
docsDirectory: portable(docsDirectory),
|
|
77
|
+
sdkRoot: portable(sdkRoot)
|
|
78
|
+
}),
|
|
93
79
|
version: manifest.version,
|
|
94
80
|
development,
|
|
95
81
|
agentDir
|
|
96
82
|
}
|
|
97
83
|
globalThis.__piDeskCommandRuntime = runtime
|
|
98
|
-
console.info('[Pi Desk][Commands]
|
|
84
|
+
console.info('[Pi Desk][Commands] 命令入口与系统资料导航已准备完成', { docsDirectory })
|
|
99
85
|
}
|
|
100
86
|
|
|
101
87
|
export function disposeL4PiDeskCommands(): void {
|
|
102
88
|
globalThis.__piDeskCommandRuntime = undefined
|
|
103
89
|
}
|
|
104
90
|
|
|
105
|
-
export function
|
|
106
|
-
const
|
|
107
|
-
return
|
|
91
|
+
export function appendL4PiDeskSystemPrompt(prompts: readonly string[] = []): string[] {
|
|
92
|
+
const prompt = globalThis.__piDeskCommandRuntime?.systemPrompt
|
|
93
|
+
return prompt ? [...prompts, prompt] : [...prompts]
|
|
108
94
|
}
|
|
109
95
|
|
|
110
96
|
export async function executeL4PiDeskCommand(
|
|
@@ -126,7 +112,7 @@ export async function executeL4PiDeskCommand(
|
|
|
126
112
|
mode: isL4PiDeskSafeMode() ? 'basic' : 'normal',
|
|
127
113
|
cwd: context.cwd,
|
|
128
114
|
agentDir: runtime.agentDir,
|
|
129
|
-
|
|
115
|
+
docsDirectory: runtime.docsDirectory
|
|
130
116
|
})
|
|
131
117
|
}
|
|
132
118
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import 'server-only'
|
|
2
|
+
|
|
3
|
+
export function renderL4PiDeskSystemPrompt(paths: {
|
|
4
|
+
docsDirectory: string
|
|
5
|
+
sdkRoot: string
|
|
6
|
+
}): string {
|
|
7
|
+
return `Pi Desk 资料(仅在使用、管理 Pi Desk 或开发其插件时读取):
|
|
8
|
+
- 文档入口:${paths.docsDirectory}/README.md
|
|
9
|
+
- SDK 公开类型声明:${paths.sdkRoot}/dist/
|
|
10
|
+
从入口选择并完整阅读对应文档及引用示例;原生 Pi 能力按入口引用查阅官方资料。相对路径从文档所在目录解析。`
|
|
11
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
IgHJ40fniC07-ioYUKobL
|