@jetcrab/pi-desk 1.1.23-dev → 1.1.25
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/commands.md +4 -2
- package/docs/plugins.md +2 -0
- package/package.json +2 -2
- package/src/app/project-preview/page.tsx +13 -0
- package/src/client/l1_entry/pages/l1-project-preview-page.tsx +1 -5
- package/src/client/l2_biz/project-preview/hooks/l2-use-project-preview.ts +25 -0
- package/src/client/l2_biz/project-preview/l2-project-preview-storage.ts +7 -7
- package/src/client/l2_biz/project-preview/l2-project-preview.tsx +8 -5
- package/src/client/l2_biz/project-preview/runtime/l2-project-history-runtime.ts +14 -11
- package/src/client/l2_biz/project-preview/views/l2-project-branch-select.tsx +347 -0
- package/src/client/l2_biz/project-preview/views/l2-project-change-tree.tsx +28 -11
- package/src/client/l2_biz/project-preview/views/l2-project-history-list.tsx +35 -8
- package/src/client/l2_biz/project-preview/views/l2-project-history-view.tsx +46 -48
- package/src/client/l2_biz/workbench/file-workspace/l2-file-workspace-runtime.ts +2 -1
- package/src/client/l2_biz/workbench/file-workspace/l2-file-workspace-view.tsx +6 -2
- package/src/client/l3_modules/conversation/l3-conversation-viewport.tsx +18 -8
- package/src/client/l3_modules/project-files/l3-project-document-view.tsx +7 -4
- package/src/client/l3_modules/project-files/l3-project-file-runtime.ts +181 -89
- package/src/client/l3_modules/project-files/l3-project-file-tabs.tsx +80 -28
- package/src/common/l2_biz/project-preview/l2-project-history-messages.ts +4 -2
- package/src/common/l2_biz/project-preview/l2-project-preview-navigation.ts +14 -0
- package/src/server/l1_entry/api/l1-plugin-management-route.ts +0 -4
- package/src/server/l2_biz/plugin-management/l2-plugin-management.ts +4 -37
- package/src/server/l2_biz/plugin-management/l2-plugin-updates.ts +1 -4
- package/src/server/l2_biz/work-session/l2-work-session-chat-runtime.ts +4 -2
- package/src/server/l4_foundation/pi/l4-pi-chat-worker.ts +16 -1
- package/src/server/l4_foundation/pi/l4-pi-package-root-consistency.ts +3 -6
- package/src/server/l4_foundation/pi/l4-pi-package-root-gate.ts +1 -1
- package/src/server/l4_foundation/pi/l4-pi-plugin-owner-runtime.ts +4 -0
- package/src/server/l4_foundation/pi/l4-pi-plugin-sources.ts +5 -1
- package/src/server/l4_foundation/pi/l4-pi-session-initialization.ts +30 -34
- package/src/server/l4_foundation/pi/l4-pi-session-resources.ts +15 -14
- package/src/server/l4_foundation/pi/l4-pi-work-session-runtime.ts +1 -1
- package/src/server/l4_foundation/skills/runtime/l4-skill-catalog.ts +159 -166
- 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 +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- 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/api/plugins/add/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/apply/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/batch/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/del/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/enabled-set/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/get/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/list/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/reload/route.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/api/plugins/update/route.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 +2 -2
- package/temp/build/pi-desk/release/.next/server/app/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/project-preview/page.js +1 -1
- package/temp/build/pi-desk/release/.next/server/app/project-preview/page_client-reference-manifest.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/16438.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/18748.js +1 -0
- package/temp/build/pi-desk/release/.next/server/chunks/24557.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/25406.js +13 -14
- package/temp/build/pi-desk/release/.next/server/chunks/27344.js +15 -0
- package/temp/build/pi-desk/release/.next/server/chunks/35985.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/36361.js +2 -2
- package/temp/build/pi-desk/release/.next/server/chunks/50520.js +2 -2
- package/temp/build/pi-desk/release/.next/server/chunks/6917.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/80290.js +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/92386.js +15 -16
- package/temp/build/pi-desk/release/.next/server/instrumentation.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/15302-6e070e13fa65c863.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/19991.bcca96d396b83030.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/65232-ef4e5a4174f62faf.js +15 -0
- package/temp/build/pi-desk/release/.next/static/chunks/app/{page-54040b9fd54ee8b3.js → page-18d63fd3920dfae0.js} +3 -3
- package/temp/build/pi-desk/release/.next/static/chunks/app/project-preview/page-038b6c14e03ed878.js +1 -0
- package/temp/build/pi-desk/release/.next/static/chunks/{webpack-48eecbb66781c006.js → webpack-c0b805df46fcf8b3.js} +1 -1
- package/temp/build/pi-desk/release/.next/static/css/c43b04c244a64fb4.css +1 -0
- package/temp/build/pi-desk/release/.next/trace-build +1 -1
- package/temp/build/pi-desk/release/.next/server/chunks/20460.js +0 -15
- package/temp/build/pi-desk/release/.next/server/chunks/93049.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/15302-9c807afc6c60de0c.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/19991.8aa2e4b6df15fad6.js +0 -1
- package/temp/build/pi-desk/release/.next/static/chunks/9038-05fd2688407b39c1.js +0 -15
- package/temp/build/pi-desk/release/.next/static/chunks/app/project-preview/page-bc18765121950e65.js +0 -1
- package/temp/build/pi-desk/release/.next/static/css/10e063cfb3cda7bd.css +0 -1
- /package/temp/build/pi-desk/release/.next/static/{gAYTx4KJJR3vTtGYexj4J → 6a4eIEjmpW5l8Qlgtzzzg}/_buildManifest.js +0 -0
- /package/temp/build/pi-desk/release/.next/static/{gAYTx4KJJR3vTtGYexj4J → 6a4eIEjmpW5l8Qlgtzzzg}/_ssgManifest.js +0 -0
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
import type { L4Locale } from '@common/l4_foundation/locale/l4-locale'
|
|
2
|
+
import { L2_PROJECT_HISTORY_MESSAGES } from './l2-project-history-messages'
|
|
3
|
+
|
|
1
4
|
export const L2_PROJECT_PREVIEW_PATH = '/project-preview'
|
|
2
5
|
|
|
3
6
|
export function buildL2ProjectPreviewHref(cwd?: string | null): string {
|
|
4
7
|
return cwd ? `${L2_PROJECT_PREVIEW_PATH}?cwd=${encodeURIComponent(cwd)}` : L2_PROJECT_PREVIEW_PATH
|
|
5
8
|
}
|
|
9
|
+
|
|
10
|
+
export function buildL2ProjectPreviewTitle(
|
|
11
|
+
cwd: string | null | undefined,
|
|
12
|
+
locale: L4Locale
|
|
13
|
+
): string {
|
|
14
|
+
const directory = cwd?.trim() || null
|
|
15
|
+
const name = directory?.replaceAll('\\', '/').split('/').filter(Boolean).at(-1) ?? directory
|
|
16
|
+
return [name, L2_PROJECT_HISTORY_MESSAGES[locale].previewTitle, 'Pi Desk']
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
.join(' · ')
|
|
19
|
+
}
|
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
L2PluginManagementInvalidPackageError,
|
|
16
16
|
L2PluginManagementNotFoundError,
|
|
17
17
|
L2PluginManagementOperationConflictError,
|
|
18
|
-
L2PluginManagementPackageRootError,
|
|
19
18
|
L2PluginManagementRestartPendingError,
|
|
20
19
|
L2PluginManagementRestartUnavailableError
|
|
21
20
|
} from '@server/l2_biz/plugin-management/l2-plugin-management'
|
|
@@ -43,9 +42,6 @@ function pluginOperationError(error: unknown, action: string): Response {
|
|
|
43
42
|
) {
|
|
44
43
|
return createL4ApiErrorResponse(409, error.message)
|
|
45
44
|
}
|
|
46
|
-
if (error instanceof L2PluginManagementPackageRootError) {
|
|
47
|
-
return createL4ApiErrorResponse(409, error.message, error.rootError.i18n)
|
|
48
|
-
}
|
|
49
45
|
if (error instanceof L2PluginReloadBlockedError) {
|
|
50
46
|
return createL4ApiErrorResponse(409, error.message, {
|
|
51
47
|
key: 'errors:pluginReloadBlocked',
|
|
@@ -136,13 +136,6 @@ export class L2PluginManagementInvalidPackageError extends Error {
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
export class L2PluginManagementPackageRootError extends Error {
|
|
140
|
-
constructor(readonly rootError: L4PiPackageRootConsistencyError) {
|
|
141
|
-
super(rootError.message)
|
|
142
|
-
this.name = 'L2PluginManagementPackageRootError'
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
139
|
export class L2PluginManagementRestartPendingError extends Error {
|
|
147
140
|
constructor() {
|
|
148
141
|
super('已有插件操作需要重启完成')
|
|
@@ -845,12 +838,6 @@ export class L2PluginManagement {
|
|
|
845
838
|
let updateTag: string | null = null
|
|
846
839
|
let pluginName: string | null = null
|
|
847
840
|
if (mutation !== 'enable' && mutation !== 'disable') {
|
|
848
|
-
const packageRootError = await readL4PiPackageRootConsistencyError({
|
|
849
|
-
agentDir: this.agentDir,
|
|
850
|
-
configured,
|
|
851
|
-
repairSource: mutation === 'add' && reinstall ? operationSource : undefined
|
|
852
|
-
})
|
|
853
|
-
if (packageRootError) throw new L2PluginManagementPackageRootError(packageRootError)
|
|
854
841
|
if (mutation !== 'add' && !target) throw new L2PluginManagementNotFoundError(source)
|
|
855
842
|
this.setOperation(operationSource, 'checking', '正在确认插件版本和下载源')
|
|
856
843
|
const candidate = this.candidateSource(source, target?.installedPath)
|
|
@@ -899,29 +886,11 @@ export class L2PluginManagement {
|
|
|
899
886
|
setL4PiPluginUpdateTag(operationSource, updateTag, this.agentDir)
|
|
900
887
|
this.updates.clear(operationSource)
|
|
901
888
|
}
|
|
902
|
-
if (this.deferredMaintenance.size > 0) {
|
|
903
|
-
this.deferredMaintenance.set(operationSource, request)
|
|
904
|
-
waiting('等待重启后完成安装维护')
|
|
905
|
-
return
|
|
906
|
-
}
|
|
907
889
|
}
|
|
908
890
|
this.assertOpen()
|
|
909
891
|
await runtime.withSourceIdle(
|
|
910
892
|
operationSource,
|
|
911
893
|
async () => {
|
|
912
|
-
if (request) {
|
|
913
|
-
const current = this.createPackageManager()
|
|
914
|
-
this.throwSettingsErrors(current.settingsManager)
|
|
915
|
-
const configured = current.packageManager
|
|
916
|
-
.listConfiguredPackages()
|
|
917
|
-
.filter((item) => item.scope === 'user')
|
|
918
|
-
const rootError = await readL4PiPackageRootConsistencyError({
|
|
919
|
-
agentDir: this.agentDir,
|
|
920
|
-
configured,
|
|
921
|
-
repairSource: mutation === 'add' && input.reinstall ? operationSource : undefined
|
|
922
|
-
})
|
|
923
|
-
if (rootError) throw new L2PluginManagementPackageRootError(rootError)
|
|
924
|
-
}
|
|
925
894
|
this.assertOpen()
|
|
926
895
|
this.setOperation(
|
|
927
896
|
operationSource,
|
|
@@ -957,7 +926,8 @@ export class L2PluginManagement {
|
|
|
957
926
|
await runL4PiPackageMaintenance({
|
|
958
927
|
maintenance: request!,
|
|
959
928
|
agentDir: this.agentDir,
|
|
960
|
-
cwd: this.packageManagerCwd
|
|
929
|
+
cwd: this.packageManagerCwd,
|
|
930
|
+
signal: this.lifetime.signal
|
|
961
931
|
})
|
|
962
932
|
if (mutation !== 'del') await this.verifyInstalledPackage(request!.source)
|
|
963
933
|
})
|
|
@@ -1372,11 +1342,8 @@ export class L2PluginManagement {
|
|
|
1372
1342
|
if (!force && this.capabilityCache) return this.capabilityCache
|
|
1373
1343
|
|
|
1374
1344
|
try {
|
|
1375
|
-
this.
|
|
1376
|
-
|
|
1377
|
-
if (!force && this.capabilityCache) return Promise.resolve(this.capabilityCache)
|
|
1378
|
-
return runL4PiPluginCapabilityProbe(this.cwd, this.agentDir)
|
|
1379
|
-
})
|
|
1345
|
+
this.assertOpen()
|
|
1346
|
+
this.capabilityCache = await runL4PiPluginCapabilityProbe(this.cwd, this.agentDir)
|
|
1380
1347
|
this.publish()
|
|
1381
1348
|
console.info('[Pi Desk][PluginManagement] 插件能力盘点完成', {
|
|
1382
1349
|
packageCount: this.capabilityCache.packages.length,
|
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
resolveL4PluginInstall
|
|
21
21
|
} from '@server/l4_foundation/pi/l4-pi-plugin-registry'
|
|
22
22
|
import type { L4PiPluginSource } from '@server/l4_foundation/pi/l4-pi-plugin-sources'
|
|
23
|
-
import { runL4PiPackageRootExclusive } from '@server/l4_foundation/pi/l4-pi-package-root-gate'
|
|
24
23
|
|
|
25
24
|
const UPDATE_CACHE_TTL_MS = 5 * 60 * 1000
|
|
26
25
|
const UPDATE_CACHE_MAX_ENTRIES = 128
|
|
@@ -163,9 +162,7 @@ export class L2PluginUpdates {
|
|
|
163
162
|
cwd: this.cwd,
|
|
164
163
|
settingsManager
|
|
165
164
|
})
|
|
166
|
-
const updates = await
|
|
167
|
-
manager.checkForAvailableUpdates()
|
|
168
|
-
)
|
|
165
|
+
const updates = await manager.checkForAvailableUpdates()
|
|
169
166
|
const value: L2PluginUpdateStatus = {
|
|
170
167
|
...empty,
|
|
171
168
|
updateAvailable: updates.some((item) => item.scope === 'user')
|
|
@@ -359,17 +359,19 @@ export class L2WorkSessionChatRuntime {
|
|
|
359
359
|
|
|
360
360
|
prepareSource(sourceInput: L2ChatSource): Promise<void> {
|
|
361
361
|
const record = this.currentRecord(sourceInput)
|
|
362
|
-
|
|
362
|
+
// 快照读取不能排在重载、原生命令或插件初始化等修改操作后面。
|
|
363
|
+
return this.syncRecordRuntime(record)
|
|
363
364
|
}
|
|
364
365
|
|
|
365
366
|
private async syncRecordRuntime(record: L2WorkSessionChatRecord): Promise<void> {
|
|
366
367
|
const source = record.source
|
|
368
|
+
const version = record.sourceEventVersion
|
|
367
369
|
const runtime = await getL4PiWorkSessionRuntime(source.sessionId).readChatRuntime()
|
|
368
370
|
const nextRuntime = L2ChatRuntimeSchema.parse({
|
|
369
371
|
...runtime,
|
|
370
372
|
presentationMode: record.state.runtime.presentationMode
|
|
371
373
|
})
|
|
372
|
-
if (!this.isCurrentSource(source)) return
|
|
374
|
+
if (!this.isCurrentSource(source) || record.sourceEventVersion !== version) return
|
|
373
375
|
if (!sameRuntime(record.state.runtime, nextRuntime))
|
|
374
376
|
this.applySourceEvent(source, { type: 'runtime_update', runtime: nextRuntime })
|
|
375
377
|
}
|
|
@@ -769,7 +769,22 @@ export class L4PiChatWorker {
|
|
|
769
769
|
return () => this.listeners.delete(listener)
|
|
770
770
|
}
|
|
771
771
|
|
|
772
|
-
async getRuntime(): Promise<L4PiChatRuntimeSnapshot> {
|
|
772
|
+
async getRuntime(waitForInitialization = true): Promise<L4PiChatRuntimeSnapshot> {
|
|
773
|
+
if (!waitForInitialization) {
|
|
774
|
+
if (this.disposed) throw new Error('Pi chat worker has been disposed')
|
|
775
|
+
// 订阅/重连先交付当前快照;初始化结果沿已有 runtime_changed 独立推送。
|
|
776
|
+
if (!this.session && !this.sessionLifecycle) {
|
|
777
|
+
void this.ensureSession().catch((cause: unknown) => {
|
|
778
|
+
if (this.disposed || this.session) return
|
|
779
|
+
this.initializationError = (cause instanceof Error ? cause.message : String(cause)).slice(
|
|
780
|
+
0,
|
|
781
|
+
32_000
|
|
782
|
+
)
|
|
783
|
+
this.emit({ type: 'runtime_changed', runtime: this.runtimeSnapshot() })
|
|
784
|
+
})
|
|
785
|
+
}
|
|
786
|
+
return this.runtimeSnapshot()
|
|
787
|
+
}
|
|
773
788
|
const initialization = this.ensureSession()
|
|
774
789
|
let release = (): void => undefined
|
|
775
790
|
const interaction = new Promise<void>((resolve) => {
|
|
@@ -37,7 +37,6 @@ async function directoryExists(path: string): Promise<boolean> {
|
|
|
37
37
|
export async function readL4PiPackageRootConsistencyError(input: {
|
|
38
38
|
agentDir: string
|
|
39
39
|
configured: readonly L4PiConfiguredPackageLocation[]
|
|
40
|
-
repairSource?: string
|
|
41
40
|
}): Promise<L4PiPackageRootConsistencyError | null> {
|
|
42
41
|
const npmPackages = input.configured.flatMap((item) => {
|
|
43
42
|
const name = npmPackageName(item.source)
|
|
@@ -63,14 +62,12 @@ export async function readL4PiPackageRootConsistencyError(input: {
|
|
|
63
62
|
}
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
//
|
|
67
|
-
const
|
|
68
|
-
const requiredPackages = npmPackages.filter((item) => item.name !== repairName)
|
|
69
|
-
const expected = new Set(requiredPackages.map((item) => item.name))
|
|
65
|
+
// 只用于管理页诊断,不作为其他插件操作或聊天的前置条件。
|
|
66
|
+
const expected = new Set(npmPackages.map((item) => item.name))
|
|
70
67
|
const actual = new Set(Object.keys(dependencies))
|
|
71
68
|
const missing = [...expected].filter((name) => !actual.has(name))
|
|
72
69
|
const missingDirectories: string[] = []
|
|
73
|
-
for (const { name, item } of
|
|
70
|
+
for (const { name, item } of npmPackages) {
|
|
74
71
|
if (!item.installedPath || !(await directoryExists(item.installedPath))) {
|
|
75
72
|
missingDirectories.push(name)
|
|
76
73
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'server-only'
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/** 只串行安装目录写入;只读发现、聊天与扩展生命周期不得等待此锁。 */
|
|
4
4
|
export function runL4PiPackageRootExclusive<T>(operation: () => Promise<T>): Promise<T> {
|
|
5
5
|
const previous = globalThis.__piDeskPackageRootTail ?? Promise.resolve()
|
|
6
6
|
const result = previous.then(operation, operation)
|
|
@@ -411,8 +411,12 @@ export class L4PiPluginOwnerRuntime {
|
|
|
411
411
|
}
|
|
412
412
|
this.cancelRecovery(source)
|
|
413
413
|
this.maintainingSources.add(source)
|
|
414
|
+
const deadline = performance.now() + 15_000
|
|
414
415
|
try {
|
|
415
416
|
while (this.owners.some((owner) => owner.source === source && owner.activeCalls.size > 0)) {
|
|
417
|
+
if (performance.now() >= deadline) {
|
|
418
|
+
throw new Error(`插件 ${source} 的在途调用未在15秒内结束,本次维护失败,可稍后手动重试`)
|
|
419
|
+
}
|
|
416
420
|
onWaiting(`插件 ${source} 仍有调用正在执行`)
|
|
417
421
|
await delay(250, undefined, { signal: lifetime })
|
|
418
422
|
}
|
|
@@ -160,7 +160,11 @@ export async function discoverL4PiPluginSources(
|
|
|
160
160
|
|
|
161
161
|
try {
|
|
162
162
|
// 显式 skip,列清单不能触发缺失包安装、扩展导入或 AgentSession。
|
|
163
|
-
const resolved = await manager.resolve(async () =>
|
|
163
|
+
const resolved = await manager.resolve(async (source) => {
|
|
164
|
+
const item = sources.get(source)
|
|
165
|
+
if (item) item.error ??= '插件包未安装或版本不匹配'
|
|
166
|
+
return 'skip'
|
|
167
|
+
})
|
|
164
168
|
for (const item of sources.values()) item.hasPiResources = false
|
|
165
169
|
for (const resource of [
|
|
166
170
|
...resolved.extensions,
|
|
@@ -32,7 +32,7 @@ import { L4PiUiRuntime } from './l4-pi-ui-runtime'
|
|
|
32
32
|
const INITIALIZATION_TIMEOUT_MS = 60_000
|
|
33
33
|
const CLEANUP_TIMEOUT_MS = 1_500
|
|
34
34
|
const MAX_RETIRED_INITIALIZATIONS = 4
|
|
35
|
-
const retiredInitializations = new Set<Promise<unknown
|
|
35
|
+
const retiredInitializations = new WeakMap<L4PiSessionResources, Set<Promise<unknown>>>()
|
|
36
36
|
const STALE_MESSAGE = 'Pi 会话实例已失效,不能继续访问当前会话'
|
|
37
37
|
|
|
38
38
|
interface InitializationOptions {
|
|
@@ -57,6 +57,7 @@ export class L4PiSessionInitialization {
|
|
|
57
57
|
readonly ui: L4PiUiRuntime
|
|
58
58
|
private readonly manager: SessionManager
|
|
59
59
|
private readonly sessionId: string
|
|
60
|
+
private readonly retired: Set<Promise<unknown>>
|
|
60
61
|
private active = true
|
|
61
62
|
private accessRevoked = false
|
|
62
63
|
private session: AgentSession | null = null
|
|
@@ -68,6 +69,9 @@ export class L4PiSessionInitialization {
|
|
|
68
69
|
|
|
69
70
|
constructor(private readonly options: InitializationOptions) {
|
|
70
71
|
this.sessionId = options.sessionManager.getSessionId()
|
|
72
|
+
// 只限制同一工作会话的重复尝试,不能因其他会话的插件清理挂起而拒绝加载。
|
|
73
|
+
this.retired = retiredInitializations.get(options.resources) ?? new Set()
|
|
74
|
+
retiredInitializations.set(options.resources, this.retired)
|
|
71
75
|
this.ui = options.ui ?? new L4PiUiRuntime()
|
|
72
76
|
this.capabilities = new L4PiCapabilityRuntime(options.readCapabilities)
|
|
73
77
|
this.plugins = new L4PiSessionPluginRuntime({
|
|
@@ -116,29 +120,20 @@ export class L4PiSessionInitialization {
|
|
|
116
120
|
async reload(): Promise<void> {
|
|
117
121
|
this.assertActive()
|
|
118
122
|
const session = await this.start()
|
|
119
|
-
let failure: Error | null = null
|
|
120
|
-
this.onLifecycleError = (error) => {
|
|
121
|
-
failure ??= error
|
|
122
|
-
}
|
|
123
123
|
this.setStage('重载 Pi 配置')
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
this.assertActive()
|
|
138
|
-
if (failure) throw failure
|
|
139
|
-
} finally {
|
|
140
|
-
this.onLifecycleError = null
|
|
141
|
-
}
|
|
124
|
+
await session.reload({
|
|
125
|
+
beforeSessionStart: async () => {
|
|
126
|
+
this.assertActive()
|
|
127
|
+
this.validateResources(
|
|
128
|
+
session.resourceLoader,
|
|
129
|
+
session.settingsManager.drainErrors().map(({ error }) => ({
|
|
130
|
+
type: 'error',
|
|
131
|
+
message: error.message
|
|
132
|
+
}))
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
this.assertActive()
|
|
142
137
|
}
|
|
143
138
|
|
|
144
139
|
setStage(stage: string): void {
|
|
@@ -154,11 +149,8 @@ export class L4PiSessionInitialization {
|
|
|
154
149
|
private async initialize(refreshResources: boolean): Promise<AgentSession> {
|
|
155
150
|
const startedAt = performance.now()
|
|
156
151
|
this.assertActive()
|
|
157
|
-
if (
|
|
158
|
-
|
|
159
|
-
retiredInitializations.size >= MAX_RETIRED_INITIALIZATIONS
|
|
160
|
-
) {
|
|
161
|
-
throw new Error('尚未结束的插件初始化或关闭任务过多,请重启服务后再加载插件')
|
|
152
|
+
if (this.options.mode === 'normal' && this.retired.size >= MAX_RETIRED_INITIALIZATIONS) {
|
|
153
|
+
throw new Error('当前会话尚未结束的插件初始化或关闭任务过多,可先使用其他会话')
|
|
162
154
|
}
|
|
163
155
|
const failure = new Promise<never>((_, reject) => {
|
|
164
156
|
this.onLifecycleError = reject
|
|
@@ -244,7 +236,7 @@ export class L4PiSessionInitialization {
|
|
|
244
236
|
)
|
|
245
237
|
: Promise.resolve()
|
|
246
238
|
const cleanup = Promise.allSettled([this.execution, shutdown])
|
|
247
|
-
|
|
239
|
+
this.retired.add(cleanup)
|
|
248
240
|
const timer = setTimeout(() => {
|
|
249
241
|
console.warn('[Pi Desk][PiSession] 旧实例仍在后台清理,不阻塞基础聊天', {
|
|
250
242
|
sessionId: this.sessionId,
|
|
@@ -255,7 +247,7 @@ export class L4PiSessionInitialization {
|
|
|
255
247
|
timer.unref()
|
|
256
248
|
void cleanup.then(() => {
|
|
257
249
|
clearTimeout(timer)
|
|
258
|
-
|
|
250
|
+
this.retired.delete(cleanup)
|
|
259
251
|
})
|
|
260
252
|
}
|
|
261
253
|
|
|
@@ -271,9 +263,13 @@ export class L4PiSessionInitialization {
|
|
|
271
263
|
resourceLoader: ResourceLoader,
|
|
272
264
|
diagnostics: AgentSessionServices['diagnostics']
|
|
273
265
|
): void {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
266
|
+
// Pi 已将失败入口排除;保留局部诊断,不把其余可用扩展一起作废。
|
|
267
|
+
for (const { path, error } of resourceLoader.getExtensions().errors) {
|
|
268
|
+
const message = `${error}\n插件入口:${path}`
|
|
269
|
+
reportL4PiPluginNativeDiagnostic(this.options.cwd, message)
|
|
270
|
+
console.warn('[Pi Desk][PiSession] 已跳过失败扩展', { cwd: this.options.cwd, message })
|
|
271
|
+
}
|
|
272
|
+
const failures: string[] = []
|
|
277
273
|
for (const diagnostic of diagnostics) {
|
|
278
274
|
if (diagnostic.type === 'error') failures.push(diagnostic.message)
|
|
279
275
|
else console.info('[Pi Desk][PiSession] 初始化诊断', { cwd: this.options.cwd, ...diagnostic })
|
|
@@ -369,7 +365,7 @@ export class L4PiSessionInitialization {
|
|
|
369
365
|
sessionId: this.sessionId,
|
|
370
366
|
message: diagnostic
|
|
371
367
|
})
|
|
372
|
-
this.
|
|
368
|
+
reportL4PiPluginNativeDiagnostic(this.options.cwd, diagnostic)
|
|
373
369
|
if (error.event === 'session_tree' || error.event === 'session_before_tree') {
|
|
374
370
|
this.session?.extensionRunner
|
|
375
371
|
.getUIContext()
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
type SourceInfo
|
|
17
17
|
} from '@earendil-works/pi-coding-agent'
|
|
18
18
|
import { clearL4PiPluginCodeCache, loadL4PiDeskPluginEntry } from './l4-pi-plugin-module-loader'
|
|
19
|
-
import {
|
|
19
|
+
import { reportL4PiPluginNativeDiagnostic } from './l4-pi-plugin-native-diagnostic'
|
|
20
20
|
import { createL4PiPluginDisabledFilter } from './l4-pi-plugin-disabled'
|
|
21
21
|
|
|
22
22
|
type LoaderOptions = ConstructorParameters<typeof DefaultResourceLoader>[0]
|
|
@@ -93,15 +93,8 @@ export class L4PiSessionResources {
|
|
|
93
93
|
onProgress?.('等待已选择的插件资源准备完成')
|
|
94
94
|
return this.selection
|
|
95
95
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
this.selection = runL4PiPackageRootExclusive(() => {
|
|
99
|
-
const durationMs = Math.round(performance.now() - queuedAt)
|
|
100
|
-
if (durationMs >= 1000) {
|
|
101
|
-
console.info('[Pi Desk][PluginResources] 插件资源队列等待结束', { cwd, durationMs })
|
|
102
|
-
}
|
|
103
|
-
return this.discover(cwd, agentDir, settings, onProgress)
|
|
104
|
-
})
|
|
96
|
+
// 维护允许暂时缺包;只读发现不能排在 npm 网络操作后面。
|
|
97
|
+
this.selection = this.discover(cwd, agentDir, settings, onProgress)
|
|
105
98
|
return this.selection
|
|
106
99
|
}
|
|
107
100
|
|
|
@@ -122,7 +115,10 @@ export class L4PiSessionResources {
|
|
|
122
115
|
})
|
|
123
116
|
const paths = await runLoadStage('发现已配置的插件与资源', onProgress, () =>
|
|
124
117
|
manager.resolve(async (source) => {
|
|
125
|
-
|
|
118
|
+
const message = `插件包未安装或版本不匹配,已跳过:${source}`
|
|
119
|
+
reportL4PiPluginNativeDiagnostic(cwd, message)
|
|
120
|
+
console.warn('[Pi Desk][PluginResources]', { cwd, message })
|
|
121
|
+
return 'skip'
|
|
126
122
|
})
|
|
127
123
|
)
|
|
128
124
|
const disabled = await createL4PiPluginDisabledFilter(agentDir, manager, settings)
|
|
@@ -307,25 +303,30 @@ export class L4PiSessionResources {
|
|
|
307
303
|
}
|
|
308
304
|
})
|
|
309
305
|
const runtime = resourceLoader.getExtensions().runtime
|
|
306
|
+
const reportProviderError = (extensionPath: string, cause: unknown): void => {
|
|
307
|
+
const message = `${extensionPath}: ${String(cause)}`
|
|
308
|
+
reportL4PiPluginNativeDiagnostic(cwd, message)
|
|
309
|
+
diagnostics.push({ type: 'warning', message })
|
|
310
|
+
}
|
|
310
311
|
for (const { name, config, extensionPath } of runtime.pendingProviderRegistrations) {
|
|
311
312
|
try {
|
|
312
313
|
modelRuntime.registerProvider(name, config)
|
|
313
314
|
} catch (cause) {
|
|
314
|
-
|
|
315
|
+
reportProviderError(extensionPath, cause)
|
|
315
316
|
}
|
|
316
317
|
}
|
|
317
318
|
for (const { provider, extensionPath } of runtime.pendingNativeProviderRegistrations) {
|
|
318
319
|
try {
|
|
319
320
|
modelRuntime.registerNativeProvider(provider)
|
|
320
321
|
} catch (cause) {
|
|
321
|
-
|
|
322
|
+
reportProviderError(extensionPath, cause)
|
|
322
323
|
}
|
|
323
324
|
}
|
|
324
325
|
for (const { definition, extensionPath } of runtime.pendingVirtualModelRegistrations) {
|
|
325
326
|
try {
|
|
326
327
|
modelRuntime.registerVirtualModel(definition)
|
|
327
328
|
} catch (cause) {
|
|
328
|
-
|
|
329
|
+
reportProviderError(extensionPath, cause)
|
|
329
330
|
}
|
|
330
331
|
}
|
|
331
332
|
runtime.pendingProviderRegistrations = []
|
|
@@ -215,7 +215,7 @@ export class L4PiWorkSessionRuntime {
|
|
|
215
215
|
|
|
216
216
|
async readChatRuntime(): Promise<L4PiChatRuntimeSnapshot> {
|
|
217
217
|
this.assertActive()
|
|
218
|
-
return this.chatWorker.getRuntime()
|
|
218
|
+
return this.chatWorker.getRuntime(false)
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
async readModelContext(): Promise<L4PiModelContext> {
|