@chainingintention/pi-web-cn 1.202610.4 → 2.202610.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.js +31 -7
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/PiWebApp-BXSziMEg.js +4921 -0
- package/dist/client/assets/classic-CyM97Akm.css +54 -0
- package/dist/client/assets/external-B9K8duyP.js +2 -0
- package/dist/client/assets/graphite-DquaqFXU.css +54 -0
- package/dist/client/assets/index-cdtUOfOW.js +2 -0
- package/dist/client/assets/paper-CB1oLuRr.css +54 -0
- package/dist/client/assets/pi-web-dark-D_KtbM95.css +54 -0
- package/dist/client/assets/pi-web-light-DSNLMIBI.css +54 -0
- package/dist/client/assets/vendor-editor-core-BfnHL1Dg.js +10 -0
- package/dist/client/assets/{vendor-editor-languages-DOCw3vc9.js → vendor-editor-languages-bNquDJP0.js} +1 -1
- package/dist/client/index.html +3 -47
- package/dist/config.js +11 -2
- package/dist/config.js.map +1 -1
- package/dist/nativeServices/installedServiceDefinitions.js +10 -7
- package/dist/nativeServices/installedServiceDefinitions.js.map +1 -1
- package/dist/pi-packages/captains-log/dist/browser/index.js +43 -47
- package/dist/pi-packages/captains-log/dist/browser/panel.js +6 -10
- package/dist/pi-packages/captains-log/dist/companion.js +1 -1
- package/dist/pi-packages/captains-log/package.json +4 -4
- package/dist/pi-packages/relays/extensions/index.js +61 -0
- package/dist/pi-packages/relays/package.json +10 -1
- package/dist/pi-packages/relays/pi-web-plugin.js +52 -2
- package/dist/pi-packages/relays/prompts/relay-worktree.md +6 -16
- package/dist/pi-packages/relays/prompts/relay.md +6 -33
- package/dist/pi-packages/relays/relayDispatch.js +67 -0
- package/dist/pi-packages/relays/relayIdentity.js +37 -0
- package/dist/pi-packages/relays/relaysPanelElement.js +75 -37
- package/dist/pi-packages/relays/skills/relay/SKILL.md +24 -68
- package/dist/pi-packages/relays/skills/relay-runner/SKILL.md +40 -219
- package/dist/pi-packages/workflows/browser/pi-web-plugin.js +42 -0
- package/dist/pi-packages/workflows/browser/worktreeDialog.js +170 -0
- package/dist/pi-packages/workflows/package.json +23 -0
- package/dist/pi-packages/workflows/prompts/worktree.md +21 -0
- package/dist/pi-packages/workflows/server-plugin.js +586 -0
- package/dist/pi-packages/workflows/vite.config.mjs +17 -0
- package/dist/pi-web-plugins/code-highlight/browser/pi-web-plugin.js +22 -0
- package/dist/pi-web-plugins/code-highlight/package.json +13 -0
- package/dist/pi-web-plugins/files/browser/pi-web-plugin.js +115 -105
- package/dist/pi-web-plugins/git/browser/git-panel.js +60 -46
- package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +23 -3
- package/dist/pi-web-plugins/git/git-backend.js +20 -1
- package/dist/pi-web-plugins/git/server-plugin.js +20 -3
- package/dist/pi-web-plugins/git/workspace-discovery-notify.cjs +14 -0
- package/dist/pi-web-plugins/git/workspaceDiscoveryHooks.js +186 -0
- package/dist/pi-web-plugins/info/infoInternals.js +3 -3
- package/dist/pi-web-plugins/info/pi-web-plugin.js +1 -1
- package/dist/pi-web-plugins/mermaid/browser/mermaid-engine.js +1087 -422
- package/dist/pi-web-plugins/mermaid/browser/pi-web-plugin.js +8 -3
- package/dist/pi-web-plugins/terminal/browser/pi-web-plugin.js +39 -33
- package/dist/pi-web-plugins/updates/pi-web-plugin.js +13 -13
- package/dist/pi-web-plugins/updates/updatesLogic.js +4 -5
- package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +21 -1
- package/dist/pi-web-plugins/workspace-tasks/tasksPanelElement.js +39 -22
- package/dist/pi-web-plugins/zh-cn/bundled.json +44 -1
- package/dist/pi-web-plugins/zh-cn/cli.json +60 -1
- package/dist/pi-web-plugins/zh-cn/client.json +7 -1
- package/dist/pi-web-plugins/zh-cn/components.json +252 -2
- package/dist/pi-web-plugins/zh-cn/config-errors.json +17 -1
- package/dist/pi-web-plugins/zh-cn/files.json +10 -1
- package/dist/pi-web-plugins/zh-cn/main.json +1 -1
- package/dist/pi-web-plugins/zh-cn/provider-auth.json +2 -1
- package/dist/pi-web-plugins/zh-cn/server.json +83 -1
- package/dist/piAi.js +4 -0
- package/dist/piAi.js.map +1 -0
- package/dist/piRuntime.js +150 -0
- package/dist/piRuntime.js.map +1 -0
- package/dist/piSdk.js +5 -0
- package/dist/piSdk.js.map +1 -0
- package/dist/piWebUpdate.js +508 -0
- package/dist/piWebUpdate.js.map +1 -0
- package/dist/piWebVersionReport.js +43 -1
- package/dist/piWebVersionReport.js.map +1 -1
- package/dist/plugin-api.d.ts +393 -51
- package/dist/server/activity/workspaceActivityService.js +8 -1
- package/dist/server/activity/workspaceActivityService.js.map +1 -1
- package/dist/server/app.js +19 -4
- package/dist/server/app.js.map +1 -1
- package/dist/server/browserMessageProjection.js +39 -20
- package/dist/server/browserMessageProjection.js.map +1 -1
- package/dist/server/configRoutes.js +8 -0
- package/dist/server/configRoutes.js.map +1 -1
- package/dist/server/index.js +10 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/knownAutoInstallPiPackages.js +6 -0
- package/dist/server/knownAutoInstallPiPackages.js.map +1 -1
- package/dist/server/machines/machineProxyRoutes.js +37 -2
- package/dist/server/machines/machineProxyRoutes.js.map +1 -1
- package/dist/server/navigationPreferenceRoutes.js +59 -0
- package/dist/server/navigationPreferenceRoutes.js.map +1 -0
- package/dist/server/piPackageService.js +1 -1
- package/dist/server/piPackageService.js.map +1 -1
- package/dist/server/piWebPluginCatalog.js +1 -1
- package/dist/server/piWebPluginCatalog.js.map +1 -1
- package/dist/server/piWebPluginService.js +7 -1
- package/dist/server/piWebPluginService.js.map +1 -1
- package/dist/server/piWebStatus.js +17 -56
- package/dist/server/piWebStatus.js.map +1 -1
- package/dist/server/plugins/pluginBackendChannelProxyAdmission.js +1 -1
- package/dist/server/plugins/pluginBackendChannelProxyAdmission.js.map +1 -1
- package/dist/server/plugins/pluginBackendChannelProxyRoutes.js +40 -42
- package/dist/server/plugins/pluginBackendChannelProxyRoutes.js.map +1 -1
- package/dist/server/plugins/pluginBackendProxyRoutes.js +40 -44
- package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -1
- package/dist/server/plugins/pluginBackendRegistry.js +66 -35
- package/dist/server/plugins/pluginBackendRegistry.js.map +1 -1
- package/dist/server/plugins/projectPluginSettings.js +95 -0
- package/dist/server/plugins/projectPluginSettings.js.map +1 -0
- package/dist/server/plugins/serverPluginCatalogCapability.js +106 -0
- package/dist/server/plugins/serverPluginCatalogCapability.js.map +1 -0
- package/dist/server/plugins/serverPluginRuntime.js +47 -11
- package/dist/server/plugins/serverPluginRuntime.js.map +1 -1
- package/dist/server/projectTrustRoutes.js +1 -1
- package/dist/server/projectTrustRoutes.js.map +1 -1
- package/dist/server/projects/projectLifecycleService.js +17 -7
- package/dist/server/projects/projectLifecycleService.js.map +1 -1
- package/dist/server/realtime/sessionEventHub.js +22 -9
- package/dist/server/realtime/sessionEventHub.js.map +1 -1
- package/dist/server/sessiond/agentHttpDispatcher.js +1 -1
- package/dist/server/sessiond/agentHttpDispatcher.js.map +1 -1
- package/dist/server/sessiond/autoInstallPiPackages.js +3 -3
- package/dist/server/sessiond/pluginBackendChannelRoutes.js +27 -14
- package/dist/server/sessiond/pluginBackendChannelRoutes.js.map +1 -1
- package/dist/server/sessiond/pluginBackendRoutes.js +56 -49
- package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -1
- package/dist/server/sessiond/pluginCatalogRoutes.js +41 -0
- package/dist/server/sessiond/pluginCatalogRoutes.js.map +1 -0
- package/dist/server/sessiond/sessionProxyRoutes.js +46 -1
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
- package/dist/server/sessiond/sessionServiceDependencies.js +1 -0
- package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -1
- package/dist/server/sessiond/workspaceCatalogRoutes.js +6 -2
- package/dist/server/sessiond/workspaceCatalogRoutes.js.map +1 -1
- package/dist/server/sessiond/workspaceNotificationRoutes.js +55 -0
- package/dist/server/sessiond/workspaceNotificationRoutes.js.map +1 -0
- package/dist/server/sessiond.js +58 -8
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/askUserDomain.js +232 -0
- package/dist/server/sessions/askUserDomain.js.map +1 -0
- package/dist/server/sessions/askUserTool.js +18 -18
- package/dist/server/sessions/askUserTool.js.map +1 -1
- package/dist/server/sessions/attachmentService.js +1 -1
- package/dist/server/sessions/attachmentService.js.map +1 -1
- package/dist/server/sessions/authRoutes.js +12 -0
- package/dist/server/sessions/authRoutes.js.map +1 -1
- package/dist/server/sessions/authService.js +11 -1
- package/dist/server/sessions/authService.js.map +1 -1
- package/dist/server/sessions/builtinExtensionFactories.js +36 -0
- package/dist/server/sessions/builtinExtensionFactories.js.map +1 -0
- package/dist/server/sessions/companionBackend.js +286 -0
- package/dist/server/sessions/companionBackend.js.map +1 -0
- package/dist/server/sessions/dockerEnvironmentFacts.js +16 -1
- package/dist/server/sessions/dockerEnvironmentFacts.js.map +1 -1
- package/dist/server/sessions/extensionSessionDispatch.js +76 -0
- package/dist/server/sessions/extensionSessionDispatch.js.map +1 -0
- package/dist/server/sessions/globalProviderPolicy.js +88 -19
- package/dist/server/sessions/globalProviderPolicy.js.map +1 -1
- package/dist/server/sessions/managementAgentTools.js +2 -2
- package/dist/server/sessions/managementAgentTools.js.map +1 -1
- package/dist/server/sessions/mcpAuthFlowService.js +289 -0
- package/dist/server/sessions/mcpAuthFlowService.js.map +1 -0
- package/dist/server/sessions/mcpAuthRoutes.js +104 -0
- package/dist/server/sessions/mcpAuthRoutes.js.map +1 -0
- package/dist/server/sessions/mcpOAuthCredentialAdapter.js +92 -0
- package/dist/server/sessions/mcpOAuthCredentialAdapter.js.map +1 -0
- package/dist/server/sessions/messageHistoryTarget.js +79 -0
- package/dist/server/sessions/messageHistoryTarget.js.map +1 -0
- package/dist/server/sessions/piSessionEventConnections.js +5 -2
- package/dist/server/sessions/piSessionEventConnections.js.map +1 -1
- package/dist/server/sessions/piSessionManagerGateway.js +32 -27
- package/dist/server/sessions/piSessionManagerGateway.js.map +1 -1
- package/dist/server/sessions/piSessionService.js +1296 -341
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/plainTextTheme.js +1 -1
- package/dist/server/sessions/plainTextTheme.js.map +1 -1
- package/dist/server/sessions/projectAuthService.js +1 -1
- package/dist/server/sessions/projectAuthService.js.map +1 -1
- package/dist/server/sessions/projectSettingsScope.js +1 -1
- package/dist/server/sessions/projectSettingsScope.js.map +1 -1
- package/dist/server/sessions/projectTrust.js +1 -1
- package/dist/server/sessions/projectTrust.js.map +1 -1
- package/dist/server/sessions/queuedMessageMutation.js +170 -0
- package/dist/server/sessions/queuedMessageMutation.js.map +1 -0
- package/dist/server/sessions/relaySkill.js +57 -7
- package/dist/server/sessions/relaySkill.js.map +1 -1
- package/dist/server/sessions/sessionArchiveMapping.js +6 -0
- package/dist/server/sessions/sessionArchiveMapping.js.map +1 -1
- package/dist/server/sessions/sessionArchiveStore.js +5 -1
- package/dist/server/sessions/sessionArchiveStore.js.map +1 -1
- package/dist/server/sessions/sessionArchiveTree.js +2 -2
- package/dist/server/sessions/sessionArchiveTree.js.map +1 -1
- package/dist/server/sessions/sessionCommandService.js +11 -24
- package/dist/server/sessions/sessionCommandService.js.map +1 -1
- package/dist/server/sessions/sessionImageDimensions.js +31 -0
- package/dist/server/sessions/sessionImageDimensions.js.map +1 -0
- package/dist/server/sessions/sessionMediaIndex.js +191 -0
- package/dist/server/sessions/sessionMediaIndex.js.map +1 -0
- package/dist/server/sessions/sessionMetadata.js +51 -0
- package/dist/server/sessions/sessionMetadata.js.map +1 -0
- package/dist/server/sessions/sessionModelScope.js +2 -2
- package/dist/server/sessions/sessionModelScope.js.map +1 -1
- package/dist/server/sessions/sessionNameGenerator.js +13 -33
- package/dist/server/sessions/sessionNameGenerator.js.map +1 -1
- package/dist/server/sessions/sessionReadAccess.js +41 -0
- package/dist/server/sessions/sessionReadAccess.js.map +1 -0
- package/dist/server/sessions/sessionRoutes.js +61 -6
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/sessions/sessionSummaryScanner.js +62 -5
- package/dist/server/sessions/sessionSummaryScanner.js.map +1 -1
- package/dist/server/sessions/sessionUiEvents.js +17 -4
- package/dist/server/sessions/sessionUiEvents.js.map +1 -1
- package/dist/server/sessions/spawnSessionTool.js +11 -3
- package/dist/server/sessions/spawnSessionTool.js.map +1 -1
- package/dist/server/sessions/spawnSubsessionTool.js +35 -19
- package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
- package/dist/server/sessions/subsessionCompletionDelivery.js +15 -0
- package/dist/server/sessions/subsessionCompletionDelivery.js.map +1 -0
- package/dist/server/sessions/subsessionCompletionInbox.js +134 -0
- package/dist/server/sessions/subsessionCompletionInbox.js.map +1 -0
- package/dist/server/sessions/subsessionProvenance.js +27 -0
- package/dist/server/sessions/subsessionProvenance.js.map +1 -0
- package/dist/server/sessions/transcriptMarkdown.js +48 -0
- package/dist/server/sessions/transcriptMarkdown.js.map +1 -0
- package/dist/server/sessions/transcriptMessages.js +3 -0
- package/dist/server/sessions/transcriptMessages.js.map +1 -1
- package/dist/server/startupLogger.js +13 -0
- package/dist/server/startupLogger.js.map +1 -0
- package/dist/server/startupUrls.js +27 -0
- package/dist/server/startupUrls.js.map +1 -0
- package/dist/server/status/machineStatusService.js +15 -9
- package/dist/server/status/machineStatusService.js.map +1 -1
- package/dist/server/storage/navigationPreferenceStore.js +138 -0
- package/dist/server/storage/navigationPreferenceStore.js.map +1 -0
- package/dist/server/webSocketBridge.js +9 -0
- package/dist/server/webSocketBridge.js.map +1 -1
- package/dist/server/workbench/skillSync.js +1 -1
- package/dist/server/workbench/skillSync.js.map +1 -1
- package/dist/server/workbench/workbenchTools.js +2 -2
- package/dist/server/workbench/workbenchTools.js.map +1 -1
- package/dist/server/workspaces/filePreviewService.js +5 -2
- package/dist/server/workspaces/filePreviewService.js.map +1 -1
- package/dist/server/workspaces/projectPiWebConfig.js +87 -6
- package/dist/server/workspaces/projectPiWebConfig.js.map +1 -1
- package/dist/server/workspaces/workspaceChangeNotifier.js +12 -0
- package/dist/server/workspaces/workspaceChangeNotifier.js.map +1 -0
- package/dist/server/workspaces/workspaceNotificationCommand.js +64 -0
- package/dist/server/workspaces/workspaceNotificationCommand.js.map +1 -0
- package/dist/server/workspaces/workspaceProviderRegistry.js +53 -0
- package/dist/server/workspaces/workspaceProviderRegistry.js.map +1 -1
- package/dist/server-plugin-api.d.ts +83 -15
- package/dist/server-plugin-api.js +25 -0
- package/dist/server-plugin-api.js.map +1 -1
- package/dist/sessiond/sessionDaemonClient.js +27 -3
- package/dist/sessiond/sessionDaemonClient.js.map +1 -1
- package/dist/shared/apiTypes.js +3 -2
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/capabilities.js +3 -0
- package/dist/shared/capabilities.js.map +1 -1
- package/dist/shared/federatedRoutes.js +25 -5
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/dist/shared/machineStatus.js +11 -2
- package/dist/shared/machineStatus.js.map +1 -1
- package/dist/shared/navigationPreferences.js +6 -0
- package/dist/shared/navigationPreferences.js.map +1 -0
- package/dist/shared/piWebConfigParsing.js +54 -0
- package/dist/shared/piWebConfigParsing.js.map +1 -1
- package/dist/shared/pluginApiTypes.d.ts +55 -0
- package/dist/shared/pluginBackendProtocol.js +56 -1
- package/dist/shared/pluginBackendProtocol.js.map +1 -1
- package/dist/shared/pluginCatalogTypes.d.ts +49 -0
- package/dist/shared/pluginCatalogTypes.js +156 -0
- package/dist/shared/pluginCatalogTypes.js.map +1 -0
- package/dist/shared/sessionMedia.js +12 -0
- package/dist/shared/sessionMedia.js.map +1 -0
- package/dist/shared/streamingBehavior.js +8 -0
- package/dist/shared/streamingBehavior.js.map +1 -0
- package/dist/shared/themePreference.js +24 -0
- package/dist/shared/themePreference.js.map +1 -0
- package/dist/workspaceNotificationCli.js +95 -0
- package/dist/workspaceNotificationCli.js.map +1 -0
- package/docs/README.md +31 -0
- package/docs/config.md +77 -4
- package/docs/plugins.md +60 -11
- package/examples/session-bridge-plugin/package.json +2 -2
- package/examples/session-bridge-plugin/src/browser/index.ts +1 -1
- package/examples/session-bridge-plugin/src/companion.ts +1 -1
- package/extensions/pi-web.ts +3 -1
- package/package.json +46 -34
- package/skills/README.md +10 -9
- package/skills/relay/SKILL.md +24 -68
- package/skills/relay-runner/SKILL.md +40 -219
- package/dist/client/assets/PiWebApp-CN3g7SqA.js +0 -4362
- package/dist/client/assets/external-DUGCSDhZ.js +0 -2
- package/dist/client/assets/index-JduZlbIF.js +0 -2
- package/dist/client/assets/vendor-editor-core-YWog6mAR.js +0 -10
- package/dist/server/sessions/pendingAskStore.js +0 -275
- package/dist/server/sessions/pendingAskStore.js.map +0 -1
|
@@ -17,13 +17,13 @@ const GIT_WORKSPACE_STATE_LIMIT = 8;
|
|
|
17
17
|
const activityElementTag = "pi-web-git-panel-activity";
|
|
18
18
|
const EMPTY_LIST_MODEL = { submodules: [], files: [] };
|
|
19
19
|
const EMPTY_VIEW_STATE = { nodes: [], listModel: EMPTY_LIST_MODEL, expandablePaths: [] };
|
|
20
|
-
export function createGitBrowserContributions(sourcePluginId, runtimePluginId, html, svg) {
|
|
20
|
+
export function createGitBrowserContributions(sourcePluginId, runtimePluginId, html, svg, stylesheetIntegration = () => undefined) {
|
|
21
21
|
const panelId = `${runtimePluginId}:${GIT_PANEL_LOCAL_ID}`;
|
|
22
22
|
const controller = new GitUiController(sourcePluginId, createGitDiffRoute(panelId));
|
|
23
23
|
defineGitPanelActivityElement();
|
|
24
24
|
return {
|
|
25
25
|
actions: createGitActions(panelId, controller),
|
|
26
|
-
workspacePanels: [createGitPanel(html, svg, controller)],
|
|
26
|
+
workspacePanels: [createGitPanel(html, svg, controller, stylesheetIntegration)],
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
class GitUiController {
|
|
@@ -38,8 +38,8 @@ class GitUiController {
|
|
|
38
38
|
this.sourcePluginId = sourcePluginId;
|
|
39
39
|
this.route = route;
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
return workspace
|
|
41
|
+
canInspectWorkspace(workspace) {
|
|
42
|
+
return workspace !== undefined && (workspace.provider === undefined || workspace.provider.pluginId === this.sourcePluginId);
|
|
43
43
|
}
|
|
44
44
|
state(context) {
|
|
45
45
|
return this.stateFor(context);
|
|
@@ -83,7 +83,7 @@ class GitUiController {
|
|
|
83
83
|
void this.refresh(context, false);
|
|
84
84
|
}
|
|
85
85
|
invalidate(context) {
|
|
86
|
-
if (!this.
|
|
86
|
+
if (!this.canInspectWorkspace(context.workspace))
|
|
87
87
|
return Promise.resolve();
|
|
88
88
|
const state = this.stateFor(context);
|
|
89
89
|
state.stale = state.status !== undefined;
|
|
@@ -336,7 +336,7 @@ class GitUiController {
|
|
|
336
336
|
}
|
|
337
337
|
}
|
|
338
338
|
function createGitActions(panelId, controller) {
|
|
339
|
-
const hasGitWorkspace = (context) => controller.
|
|
339
|
+
const hasGitWorkspace = (context) => controller.canInspectWorkspace(context.state.selectedWorkspace);
|
|
340
340
|
return [
|
|
341
341
|
{
|
|
342
342
|
id: "view.git",
|
|
@@ -358,7 +358,7 @@ function createGitActions(panelId, controller) {
|
|
|
358
358
|
},
|
|
359
359
|
];
|
|
360
360
|
}
|
|
361
|
-
function createGitPanel(html, svg, controller) {
|
|
361
|
+
function createGitPanel(html, svg, controller, stylesheetIntegration) {
|
|
362
362
|
return {
|
|
363
363
|
id: GIT_PANEL_LOCAL_ID,
|
|
364
364
|
title: "Git",
|
|
@@ -375,9 +375,9 @@ function createGitPanel(html, svg, controller) {
|
|
|
375
375
|
order: 20,
|
|
376
376
|
routeAliases: ["git", "core:workspace.git"],
|
|
377
377
|
navigationAliases: ["core:workspace.git"],
|
|
378
|
-
visible: (context) => controller.
|
|
378
|
+
visible: (context) => controller.canInspectWorkspace(context.workspace),
|
|
379
379
|
onInvalidate: (context) => controller.invalidate(context),
|
|
380
|
-
render: (context) => renderGitPanel(html, controller, context),
|
|
380
|
+
render: (context) => renderGitPanel(html, controller, context, stylesheetIntegration()),
|
|
381
381
|
};
|
|
382
382
|
}
|
|
383
383
|
function requestGitBackend(context, operation, input) {
|
|
@@ -386,26 +386,26 @@ function requestGitBackend(context, operation, input) {
|
|
|
386
386
|
}
|
|
387
387
|
return context.peer.request(operation, input);
|
|
388
388
|
}
|
|
389
|
-
function renderGitPanel(html, controller, context) {
|
|
389
|
+
function renderGitPanel(html, controller, context, stylesheetIntegration) {
|
|
390
390
|
const state = controller.state(context);
|
|
391
391
|
const viewState = controller.viewState(state);
|
|
392
392
|
return html `
|
|
393
393
|
<section class="git-panel">
|
|
394
|
-
<style .textContent=${gitPanelStyles}></style>
|
|
395
|
-
<pi-web-git-panel-activity .controller=${controller} .context=${context}></pi-web-git-panel-activity>
|
|
396
|
-
<section class="git-toolbar">
|
|
394
|
+
<style data-git-styles .textContent=${gitPanelStyles}></style>
|
|
395
|
+
<pi-web-git-panel-activity .stylesheetIntegration=${stylesheetIntegration} .controller=${controller} .context=${context}></pi-web-git-panel-activity>
|
|
396
|
+
<section class="ui-toolbar git-toolbar">
|
|
397
397
|
<strong>Git</strong>
|
|
398
398
|
${state.stale ? html `<span class="git-stale">${t("ui.main.3468df3f1fe9", "stale")}</span>` : null}
|
|
399
|
-
<div class="git-toolbar-actions">
|
|
399
|
+
<div class="ui-toolbar-group git-toolbar-actions">
|
|
400
400
|
${viewState.expandablePaths.length === 0 ? null : renderExpandCollapseAll(html, controller, context, state, viewState.expandablePaths)}
|
|
401
401
|
${renderViewToggle(html, controller, context)}
|
|
402
|
-
<button type="button" ?disabled=${state.statusLoading} @click=${() => { void controller.refresh(context); }}>${t("bundled.relays.refresh.aee887", "Refresh")}</button>
|
|
402
|
+
<button class="ui-button" type="button" ?disabled=${state.statusLoading} @click=${() => { void controller.refresh(context); }}>${t("bundled.relays.refresh.aee887", "Refresh")}</button>
|
|
403
403
|
</div>
|
|
404
404
|
</section>
|
|
405
405
|
${state.error === undefined ? null : html `<div class="git-error" role="alert">${state.error}</div>`}
|
|
406
406
|
${state.status?.truncated === true ? html `<div class="git-warning" role="status">${t("ui.main.1634590c49b8", "Git status output was too large; the file list was truncated.")}</div>` : null}
|
|
407
407
|
<section class="git-split">
|
|
408
|
-
<div class="git-file-list">${renderFileList(html, controller, context, state, viewState)}</div>
|
|
408
|
+
<div class="ui-file-list git-file-list">${renderFileList(html, controller, context, state, viewState)}</div>
|
|
409
409
|
<div class="git-viewer">${renderDiffViewer(html, state)}</div>
|
|
410
410
|
</section>
|
|
411
411
|
</section>
|
|
@@ -413,7 +413,7 @@ function renderGitPanel(html, controller, context) {
|
|
|
413
413
|
}
|
|
414
414
|
function renderViewToggle(html, controller, context) {
|
|
415
415
|
return html `
|
|
416
|
-
<div class="git-view-toggle" role="group" aria-label="${t("ui.main.898449210b9d", "Changed files view")}">
|
|
416
|
+
<div class="ui-toolbar-group git-view-toggle" role="group" aria-label="${t("ui.main.898449210b9d", "Changed files view")}">
|
|
417
417
|
${renderViewToggleButton(html, controller, context, "list", t("ui.workspaceGitPanel.list", "List"))}
|
|
418
418
|
${renderViewToggleButton(html, controller, context, "tree", t("ui.main.7f32c63d2d35", "Tree"))}
|
|
419
419
|
</div>
|
|
@@ -421,11 +421,11 @@ function renderViewToggle(html, controller, context) {
|
|
|
421
421
|
}
|
|
422
422
|
function renderViewToggleButton(html, controller, context, view, label) {
|
|
423
423
|
const active = controller.currentView() === view;
|
|
424
|
-
return html `<button type="button" class
|
|
424
|
+
return html `<button type="button" class="ui-button" aria-pressed=${String(active)} @click=${() => { controller.setView(context, view); }}>${label}</button>`;
|
|
425
425
|
}
|
|
426
426
|
function renderExpandCollapseAll(html, controller, context, state, expandablePaths) {
|
|
427
427
|
const allExpanded = expandablePaths.every((path) => state.expandedDirectories.has(path));
|
|
428
|
-
return html `<button type="button" @click=${() => { controller.toggleExpandAll(context, expandablePaths, allExpanded); }}>${allExpanded ? t("ui.workspaceGitPanel.collapseAll", "Collapse all") : t("ui.workspaceGitPanel.expandAll", "Expand all")}</button>`;
|
|
428
|
+
return html `<button class="ui-button" type="button" @click=${() => { controller.toggleExpandAll(context, expandablePaths, allExpanded); }}>${allExpanded ? t("ui.workspaceGitPanel.collapseAll", "Collapse all") : t("ui.workspaceGitPanel.expandAll", "Expand all")}</button>`;
|
|
429
429
|
}
|
|
430
430
|
function renderFileList(html, controller, context, state, viewState) {
|
|
431
431
|
const status = state.status;
|
|
@@ -458,9 +458,9 @@ function renderSubmoduleGroup(html, controller, context, state, group) {
|
|
|
458
458
|
return html ``;
|
|
459
459
|
const expanded = state.expandedDirectories.has(group.path);
|
|
460
460
|
return html `
|
|
461
|
-
<button type="button" class="
|
|
462
|
-
<span class="
|
|
463
|
-
<span>${group.name}${submoduleBadge(html)}</span>
|
|
461
|
+
<button type="button" class="ui-button ui-file-row" style="--depth:0" title=${group.path} aria-expanded=${String(expanded)} @click=${() => { controller.toggleDirectory(context, group.path); }}>
|
|
462
|
+
<span class="ui-file-leading" aria-hidden="true">${expanded ? "▾" : "▸"}</span>
|
|
463
|
+
<span class="ui-file-label">${group.name}${submoduleBadge(html)}</span>
|
|
464
464
|
</button>
|
|
465
465
|
${expanded ? html `
|
|
466
466
|
${group.pointer === undefined ? null : renderSelectableRow(html, controller, context, state, group.path, group.pointer.name, group.pointer.file, 1)}
|
|
@@ -477,9 +477,9 @@ function renderTreeNode(html, controller, context, state, node, depth) {
|
|
|
477
477
|
return html ``;
|
|
478
478
|
const expanded = state.expandedDirectories.has(node.path);
|
|
479
479
|
return html `
|
|
480
|
-
<button type="button" class="
|
|
481
|
-
<span class="
|
|
482
|
-
<span>${node.name}${node.isSubmodule === true ? submoduleBadge(html) : null}</span>
|
|
480
|
+
<button type="button" class="ui-button ui-file-row" style=${`--depth:${String(depth)}`} title=${node.path} aria-expanded=${String(expanded)} @click=${() => { controller.toggleDirectory(context, node.path); }}>
|
|
481
|
+
<span class="ui-file-leading" aria-hidden="true">${expanded ? "▾" : "▸"}</span>
|
|
482
|
+
<span class="ui-file-label">${node.name}${node.isSubmodule === true ? submoduleBadge(html) : null}</span>
|
|
483
483
|
</button>
|
|
484
484
|
${expanded ? node.children.map((child) => renderTreeNode(html, controller, context, state, child, depth + 1)) : null}
|
|
485
485
|
`;
|
|
@@ -494,9 +494,10 @@ function renderSelectableRow(html, controller, context, state, path, label, file
|
|
|
494
494
|
return html ``;
|
|
495
495
|
const selected = state.selectedDiffPath === path;
|
|
496
496
|
return html `
|
|
497
|
-
<button type="button" class=${selected ? "
|
|
498
|
-
<span
|
|
499
|
-
<span>${label}</span>
|
|
497
|
+
<button type="button" class=${selected ? "ui-button ui-file-row selected" : "ui-button ui-file-row"} style=${`--depth:${String(depth)}`} title=${path} aria-current=${selected ? "page" : "false"} @click=${() => { controller.selectDiff(context, path); }}>
|
|
498
|
+
<span class="ui-file-leading" aria-hidden="true"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z"></path><path d="M14 2v6h6"></path></svg></span>
|
|
499
|
+
<span class="ui-file-label">${label}</span>
|
|
500
|
+
<span class="ui-file-meta">${stateLabel(file.index, file.workingTree)}</span>
|
|
500
501
|
</button>
|
|
501
502
|
`;
|
|
502
503
|
}
|
|
@@ -591,6 +592,24 @@ function defineGitPanelActivityElement() {
|
|
|
591
592
|
controllerValue;
|
|
592
593
|
contextValue;
|
|
593
594
|
pollTimer;
|
|
595
|
+
integration;
|
|
596
|
+
releaseStyles;
|
|
597
|
+
set stylesheetIntegration(value) {
|
|
598
|
+
if (this.integration === value)
|
|
599
|
+
return;
|
|
600
|
+
this.releaseStyles?.();
|
|
601
|
+
this.releaseStyles = undefined;
|
|
602
|
+
this.integration = value;
|
|
603
|
+
this.registerStyles();
|
|
604
|
+
}
|
|
605
|
+
registerStyles() {
|
|
606
|
+
if (!this.isConnected || this.releaseStyles !== undefined)
|
|
607
|
+
return;
|
|
608
|
+
const root = this.getRootNode();
|
|
609
|
+
// The activity element shares the host panel's root; it owns only this opt-in.
|
|
610
|
+
if (root instanceof ShadowRoot)
|
|
611
|
+
this.releaseStyles = this.integration?.registerRoot(root, { sharedUi: true });
|
|
612
|
+
}
|
|
594
613
|
set controller(value) {
|
|
595
614
|
if (this.controllerValue === value)
|
|
596
615
|
return;
|
|
@@ -604,11 +623,14 @@ function defineGitPanelActivityElement() {
|
|
|
604
623
|
this.restart();
|
|
605
624
|
}
|
|
606
625
|
connectedCallback() {
|
|
626
|
+
this.registerStyles();
|
|
607
627
|
window.addEventListener("popstate", this.onPopState);
|
|
608
628
|
document.addEventListener("visibilitychange", this.onVisibilityChange);
|
|
609
629
|
this.restart();
|
|
610
630
|
}
|
|
611
631
|
disconnectedCallback() {
|
|
632
|
+
this.releaseStyles?.();
|
|
633
|
+
this.releaseStyles = undefined;
|
|
612
634
|
window.removeEventListener("popstate", this.onPopState);
|
|
613
635
|
document.removeEventListener("visibilitychange", this.onVisibilityChange);
|
|
614
636
|
if (this.controllerValue !== undefined && this.contextValue !== undefined)
|
|
@@ -675,30 +697,22 @@ function errorMessage(error) {
|
|
|
675
697
|
return error instanceof Error ? error.message : String(error);
|
|
676
698
|
}
|
|
677
699
|
const gitPanelStyles = `
|
|
678
|
-
.git-panel { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; overflow: hidden; color: var(--pi-text); background: var(--pi-bg); font: 13px system-ui, sans-serif; }
|
|
700
|
+
.git-panel { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; overflow: hidden; color: var(--pi-text); background: var(--pi-bg); font: 13px var(--pi-font, system-ui, sans-serif); }
|
|
679
701
|
.git-panel ${activityElementTag} { display: none; }
|
|
680
|
-
.git-panel button { display: inline-flex; align-items: center; gap: 5px; border: 1px solid var(--pi-border); border-radius: 7px; background: var(--pi-surface); color: var(--pi-text); padding: 5px 7px; cursor: pointer; }
|
|
681
|
-
.git-panel button:disabled { cursor: wait; opacity: .65; }
|
|
682
702
|
.git-panel small, .git-panel .git-muted { color: var(--pi-muted); }
|
|
683
703
|
.git-panel p { margin: 10px; }
|
|
684
|
-
.git-panel .git-toolbar { flex: 0 0 auto;
|
|
685
|
-
.git-panel .git-toolbar-actions {
|
|
686
|
-
.git-panel .git-
|
|
687
|
-
.git-panel .git-
|
|
688
|
-
.git-panel .git-
|
|
689
|
-
.git-panel .git-
|
|
690
|
-
.git-panel .git-view-toggle button.is-selected { position: relative; z-index: 1; border-color: var(--pi-accent); background: var(--pi-selection-bg); }
|
|
691
|
-
.git-panel .git-stale { border: 1px solid var(--pi-warning-border); border-radius: 999px; color: var(--pi-warning); padding: 1px 6px; font-size: 12px; }
|
|
704
|
+
.git-panel .git-toolbar { flex: 0 0 auto; border-bottom: 1px solid var(--pi-border-muted); font-size: 12px; color: var(--pi-muted); }
|
|
705
|
+
.git-panel .git-toolbar-actions { margin-left: auto; }
|
|
706
|
+
.git-panel .git-toolbar > strong { display: flex; align-items: center; padding-inline: 10px; font-weight: 500; }
|
|
707
|
+
.git-panel .git-toolbar .ui-button { white-space: nowrap; }
|
|
708
|
+
.git-panel .git-toolbar .ui-button[aria-pressed="true"] { background: var(--pi-surface-hover); color: var(--pi-text); }
|
|
709
|
+
.git-panel .git-stale { border: 1px solid var(--pi-warning-border); border-radius: 999px; color: var(--pi-warning); padding: 1px 6px; font-size: 12px; align-self: center; }
|
|
692
710
|
.git-panel .git-error { flex: 0 0 auto; margin: 8px; border: 1px solid var(--pi-danger); border-radius: 7px; color: var(--pi-danger); padding: 8px; }
|
|
693
711
|
.git-panel .git-warning { flex: 0 0 auto; margin: 8px; border: 1px solid var(--pi-warning-border); border-radius: 7px; color: var(--pi-warning); padding: 8px; }
|
|
694
712
|
.git-panel .git-load-more { margin: 8px auto; }
|
|
695
713
|
.git-panel .git-split { flex: 1 1 auto; min-height: 0; display: grid; grid-template-rows: minmax(160px, 34%) minmax(0, 1fr); }
|
|
696
|
-
.git-panel .git-file-list {
|
|
697
|
-
.git-panel .git-
|
|
698
|
-
.git-panel .git-row:hover, .git-panel .git-row.is-selected { background: var(--pi-selection-bg); }
|
|
699
|
-
.git-panel .git-row span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
700
|
-
.git-panel .git-twisty { color: var(--pi-dim, var(--pi-muted)); }
|
|
701
|
-
.git-panel .git-summary { margin: 4px 6px 8px; color: var(--pi-muted); }
|
|
714
|
+
.git-panel .git-file-list { border-bottom: 1px solid var(--pi-border); }
|
|
715
|
+
.git-panel .git-summary { margin: 10px 12px 8px; color: var(--pi-muted); }
|
|
702
716
|
.git-panel .submodule-badge { display: inline-block; margin-left: 6px; border: 1px solid var(--pi-border); border-radius: 999px; color: var(--pi-muted); padding: 0 5px; font-size: 11px; font-weight: 400; vertical-align: baseline; }
|
|
703
717
|
.git-panel .git-viewer { min-height: 0; overflow: auto; display: flex; flex-direction: column; }
|
|
704
718
|
.git-panel .git-diffs { flex: 1 1 auto; min-height: 0; overflow: auto; display: grid; grid-template-rows: minmax(120px, 1fr) minmax(120px, 1fr); }
|
|
@@ -708,7 +722,7 @@ const gitPanelStyles = `
|
|
|
708
722
|
.git-panel .git-viewer-header { position: sticky; top: 0; display: flex; justify-content: space-between; gap: 8px; padding: 8px; border-bottom: 1px solid var(--pi-border-muted); background: var(--pi-bg); }
|
|
709
723
|
.git-panel .git-viewer-header strong { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
710
724
|
.git-panel .git-diff-scroller { flex: 1 1 auto; min-height: 0; overflow: auto; background: var(--pi-bg); }
|
|
711
|
-
.git-panel .git-diff-grid { display: grid; grid-template-columns: max-content max-content 2ch max-content; width: max-content; min-width: 100%; padding: 6px 0; font: 12px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; line-height: 1.45; }
|
|
725
|
+
.git-panel .git-diff-grid { display: grid; grid-template-columns: max-content max-content 2ch max-content; width: max-content; min-width: 100%; padding: 6px 0; font: 12px var(--pi-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace); line-height: 1.45; }
|
|
712
726
|
.git-panel .git-diff-line { display: contents; }
|
|
713
727
|
.git-panel .git-diff-cell { min-height: 1.45em; white-space: pre; }
|
|
714
728
|
.git-panel .git-line-number { min-width: 4ch; padding: 0 8px; border-right: 1px solid var(--pi-border-muted); color: var(--pi-dim); text-align: right; user-select: none; }
|
|
@@ -1,14 +1,34 @@
|
|
|
1
1
|
// Generated from pi-web-plugins/git/browser/pi-web-plugin.ts. Do not edit directly.
|
|
2
2
|
import { setTranslator } from "./i18n.js";
|
|
3
3
|
import { createGitBrowserContributions } from "./git-panel.js";
|
|
4
|
+
const stylesheetIntegrationCapability = {
|
|
5
|
+
pluginId: "pi-web", id: "stylesheet-integration", version: 1,
|
|
6
|
+
parse: (value) => {
|
|
7
|
+
if (!isStylesheetIntegration(value)) {
|
|
8
|
+
throw new Error("Invalid PI WEB stylesheet integration capability v1");
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
function isStylesheetIntegration(value) {
|
|
14
|
+
return typeof value === "object" && value !== null
|
|
15
|
+
&& "registerRoot" in value && typeof value.registerRoot === "function"
|
|
16
|
+
&& "subscribe" in value && typeof value.subscribe === "function"
|
|
17
|
+
&& "createController" in value && typeof value.createController === "function";
|
|
18
|
+
}
|
|
4
19
|
const plugin = {
|
|
5
20
|
apiVersion: 4,
|
|
6
21
|
name: "Git",
|
|
22
|
+
requires: [stylesheetIntegrationCapability],
|
|
7
23
|
activate: ({ pluginId, runtimePluginId, html, svg, translate }) => {
|
|
8
24
|
setTranslator(translate);
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
25
|
+
let stylesheetIntegration;
|
|
26
|
+
return {
|
|
27
|
+
start: ({ capabilities }) => {
|
|
28
|
+
stylesheetIntegration = capabilities.resolve(stylesheetIntegrationCapability);
|
|
29
|
+
},
|
|
30
|
+
contributions: createGitBrowserContributions(pluginId, runtimePluginId, html, svg, () => stylesheetIntegration),
|
|
31
|
+
};
|
|
12
32
|
},
|
|
13
33
|
};
|
|
14
34
|
export default plugin;
|
|
@@ -11,11 +11,19 @@ const statusCaches = new WeakMap();
|
|
|
11
11
|
const GIT_LOCAL_ENV_VARS = Object.freeze([
|
|
12
12
|
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
13
13
|
"GIT_COMMON_DIR",
|
|
14
|
+
"GIT_CONFIG",
|
|
15
|
+
"GIT_CONFIG_COUNT",
|
|
16
|
+
"GIT_CONFIG_PARAMETERS",
|
|
14
17
|
"GIT_DIR",
|
|
18
|
+
"GIT_GRAFT_FILE",
|
|
19
|
+
"GIT_IMPLICIT_WORK_TREE",
|
|
15
20
|
"GIT_INDEX_FILE",
|
|
21
|
+
"GIT_NO_REPLACE_OBJECTS",
|
|
16
22
|
"GIT_OBJECT_DIRECTORY",
|
|
17
23
|
"GIT_PREFIX",
|
|
18
24
|
"GIT_QUARANTINE_PATH",
|
|
25
|
+
"GIT_REPLACE_REF_BASE",
|
|
26
|
+
"GIT_SHALLOW_FILE",
|
|
19
27
|
"GIT_WORK_TREE",
|
|
20
28
|
]);
|
|
21
29
|
/** Dispatch the Git status/diff schema through the package peer request seam. */
|
|
@@ -44,7 +52,7 @@ async function gitStatusWithRunner(runGit, cwd) {
|
|
|
44
52
|
if (result.code !== 0)
|
|
45
53
|
return { isGitRepo: false, hash: hash(result.stdout + result.stderr), files: [], submodules: [], ...(result.truncated ? { truncated: true } : {}) };
|
|
46
54
|
const parsed = parseStatus(result.truncated ? result.stdout.slice(0, result.stdout.lastIndexOf("\0") + 1) : result.stdout, { deferSubmodules: true });
|
|
47
|
-
return expandSubmodules(runGit, cwd, parsed, result.stdout, result.truncated);
|
|
55
|
+
return expandSubmodules(runGit, await gitWorktreeRoot(runGit, cwd), parsed, result.stdout, result.truncated);
|
|
48
56
|
}
|
|
49
57
|
/**
|
|
50
58
|
* Merge each dirty submodule's own changes into the flat file list. A moved
|
|
@@ -136,6 +144,8 @@ async function gitDiffWithRunner(runGit, cwd, options) {
|
|
|
136
144
|
let path;
|
|
137
145
|
if (options.path !== undefined && options.path !== "")
|
|
138
146
|
path = normalizeRelativePath(options.path);
|
|
147
|
+
// Diff/status paths belong to the repository, not a folder project's session cwd.
|
|
148
|
+
cwd = await gitWorktreeRoot(runGit, cwd);
|
|
139
149
|
if (path !== undefined) {
|
|
140
150
|
const owner = await submoduleForPath(runGit, cwd, path);
|
|
141
151
|
if (owner !== undefined)
|
|
@@ -181,6 +191,15 @@ async function submoduleDiff(runGit, owner, path, staged) {
|
|
|
181
191
|
}
|
|
182
192
|
return { path, staged, hash: hash(result.stdout), diff: result.stdout, truncated: result.truncated };
|
|
183
193
|
}
|
|
194
|
+
async function gitWorktreeRoot(runGit, cwd) {
|
|
195
|
+
const result = await runGit(cwd, ["rev-parse", "--show-toplevel"]);
|
|
196
|
+
if (result.code !== 0)
|
|
197
|
+
throw new Error(result.stderr.trim() || t("git.cwdRootUnavailable", "Cannot resolve Git worktree root"));
|
|
198
|
+
const root = result.stdout.replace(/\r?\n$/u, "");
|
|
199
|
+
if (!isAbsolute(root))
|
|
200
|
+
throw new Error(t("git.cwdRootInvalid", "Git returned an invalid worktree root"));
|
|
201
|
+
return root;
|
|
202
|
+
}
|
|
184
203
|
async function isUntracked(runGit, cwd, path) {
|
|
185
204
|
const result = await runGit(cwd, ["ls-files", "--others", "--exclude-standard", "-z", "--", path]);
|
|
186
205
|
return result.code === 0 && result.stdout.split("\0").includes(path);
|
|
@@ -2,14 +2,23 @@
|
|
|
2
2
|
import { t, setTranslator } from "./i18n.js";
|
|
3
3
|
import { basename, resolve } from "node:path";
|
|
4
4
|
import { requestGitBackend } from "./git-backend.js";
|
|
5
|
+
import { createWorkspaceDiscoveryHooks } from "./workspaceDiscoveryHooks.js";
|
|
5
6
|
const GIT_LOCAL_ENV_VARS = Object.freeze([
|
|
6
7
|
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
7
8
|
"GIT_COMMON_DIR",
|
|
9
|
+
"GIT_CONFIG",
|
|
10
|
+
"GIT_CONFIG_COUNT",
|
|
11
|
+
"GIT_CONFIG_PARAMETERS",
|
|
8
12
|
"GIT_DIR",
|
|
13
|
+
"GIT_GRAFT_FILE",
|
|
14
|
+
"GIT_IMPLICIT_WORK_TREE",
|
|
9
15
|
"GIT_INDEX_FILE",
|
|
16
|
+
"GIT_NO_REPLACE_OBJECTS",
|
|
10
17
|
"GIT_OBJECT_DIRECTORY",
|
|
11
18
|
"GIT_PREFIX",
|
|
12
19
|
"GIT_QUARANTINE_PATH",
|
|
20
|
+
"GIT_REPLACE_REF_BASE",
|
|
21
|
+
"GIT_SHALLOW_FILE",
|
|
13
22
|
"GIT_WORK_TREE",
|
|
14
23
|
]);
|
|
15
24
|
const plugin = {
|
|
@@ -18,22 +27,29 @@ const plugin = {
|
|
|
18
27
|
activate(context) {
|
|
19
28
|
setTranslator(context.translate);
|
|
20
29
|
return {
|
|
21
|
-
workspaceProvider: createGitWorkspaceProvider(context),
|
|
30
|
+
workspaceProvider: createGitWorkspaceProvider(context, process.env["PI_WEB_INSTANCE_NAMESPACE"] ?? ""),
|
|
22
31
|
peer: { request: (request) => requestGitBackend(context, request) },
|
|
23
32
|
};
|
|
24
33
|
},
|
|
25
34
|
};
|
|
26
35
|
export default plugin;
|
|
27
|
-
export function createGitWorkspaceProvider(context) {
|
|
36
|
+
export function createGitWorkspaceProvider(context, instanceNamespace = "") {
|
|
28
37
|
return Object.freeze({
|
|
29
38
|
fallback: true,
|
|
39
|
+
integration: createWorkspaceDiscoveryHooks(context, (cwd, args, signal) => runGit(context, cwd, args, signal), process.execPath, instanceNamespace),
|
|
30
40
|
async probe(project, signal) {
|
|
31
41
|
const result = await runGit(context, project.path, ["rev-parse", "--is-inside-work-tree"], signal);
|
|
32
42
|
if (result.signal !== null)
|
|
33
43
|
throw new Error(t("ui.main.8919cbc9c0bd", "Git repository probe ended with signal {arg0}", { arg0: result.signal }));
|
|
34
44
|
if (result.exitCode !== 0)
|
|
35
45
|
return "pass";
|
|
36
|
-
|
|
46
|
+
if (result.stdout.trim() !== "true")
|
|
47
|
+
return "pass";
|
|
48
|
+
const root = await requireGit(runGit(context, project.path, ["rev-parse", "--show-toplevel"], signal), t("ui.main.49c35c822771", "resolve the Git worktree root"));
|
|
49
|
+
if (root.stdout.trim() === "")
|
|
50
|
+
throw new Error(t("ui.main.f1639c9e2fd7", "Git returned an empty worktree root"));
|
|
51
|
+
// A folder project keeps its cwd; only checkout roots own worktree discovery.
|
|
52
|
+
return resolve(root.stdout.trim()) === resolve(project.path) ? "claim" : "pass";
|
|
37
53
|
},
|
|
38
54
|
async list(project, signal) {
|
|
39
55
|
const rootResult = await requireGit(runGit(context, project.path, ["rev-parse", "--show-toplevel"], signal), t("ui.main.49c35c822771", "resolve the Git worktree root"));
|
|
@@ -163,6 +179,7 @@ async function runGit(context, cwd, args, signal) {
|
|
|
163
179
|
file: "git",
|
|
164
180
|
args: ["-C", cwd, ...args],
|
|
165
181
|
unsetEnv: GIT_LOCAL_ENV_VARS,
|
|
182
|
+
timeoutMs: 10_000,
|
|
166
183
|
signal,
|
|
167
184
|
});
|
|
168
185
|
if (result.stdoutTruncated || result.stderrTruncated) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Packaged with PI WEB: best-effort discovery hint, never Git authority.
|
|
2
|
+
const { spawnSync } = require("node:child_process");
|
|
3
|
+
const [notification, ...args] = process.argv.slice(2);
|
|
4
|
+
|
|
5
|
+
// Initial worktree checkout has a null previous OID and branch flag 1.
|
|
6
|
+
// --no-checkout skips this hook; ordinary catalog access still discovers it.
|
|
7
|
+
if (args.length === 3 && /^(?:0{40}|0{64})$/.test(args[0]) && args[2] === "1") {
|
|
8
|
+
try {
|
|
9
|
+
const command = JSON.parse(notification);
|
|
10
|
+
if (Array.isArray(command) && command.length > 0 && command.every((value) => typeof value === "string")) {
|
|
11
|
+
spawnSync(command[0], command.slice(1), { stdio: "ignore", timeout: 2000, killSignal: "SIGKILL" });
|
|
12
|
+
}
|
|
13
|
+
} catch { /* Invalid arguments, missing commands, and daemon failures must not affect Git. */ }
|
|
14
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// Generated from pi-web-plugins/git/workspaceDiscoveryHooks.ts. Do not edit directly.
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
const EVENT = "post-checkout";
|
|
6
|
+
/** Git owns the hook policy; the host owns settings snapshots and notification attribution/transport. */
|
|
7
|
+
export function createWorkspaceDiscoveryHooks(context, runGit, nodeExecutable = process.execPath, instanceNamespace = "") {
|
|
8
|
+
if (instanceNamespace !== "" && !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,31}$/u.test(instanceNamespace)) {
|
|
9
|
+
throw new Error("PI_WEB_INSTANCE_NAMESPACE must be 1–32 letters, digits, underscores or hyphens, starting with a letter or digit");
|
|
10
|
+
}
|
|
11
|
+
if (!isAbsolute(nodeExecutable))
|
|
12
|
+
throw new Error("Git hook runtime must be an absolute executable path");
|
|
13
|
+
// Serialize this activation's config edits, including registrations sharing a
|
|
14
|
+
// common Git config. Git itself locks writes from other processes.
|
|
15
|
+
let pending = Promise.resolve();
|
|
16
|
+
return Object.freeze({
|
|
17
|
+
reconcile(project, signal) {
|
|
18
|
+
const operation = pending.then(() => reconcile(project, signal));
|
|
19
|
+
pending = operation.catch(() => { });
|
|
20
|
+
return operation;
|
|
21
|
+
},
|
|
22
|
+
async acceptNotification(project, signal) {
|
|
23
|
+
return await enabled(project, signal) && (context.workspaceNotifications?.command(project.id).length ?? 0) > 0;
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
async function enabled(project, signal) {
|
|
27
|
+
signal.throwIfAborted();
|
|
28
|
+
const settings = context.resolveProjectSettings === undefined
|
|
29
|
+
? context.settings
|
|
30
|
+
: await context.resolveProjectSettings(project, signal);
|
|
31
|
+
const value = settings["workspaceDiscoveryHooks"];
|
|
32
|
+
if (value === undefined)
|
|
33
|
+
return true;
|
|
34
|
+
if (typeof value !== "boolean")
|
|
35
|
+
throw new Error("Git setting workspaceDiscoveryHooks must be a boolean");
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
async function reconcile(project, signal) {
|
|
39
|
+
const isEnabled = await enabled(project, signal);
|
|
40
|
+
// Separate projects/instances may share Git config. This identity belongs
|
|
41
|
+
// to the packaged-helper implementation; older generated helpers are untouched.
|
|
42
|
+
const identity = createHash("sha256")
|
|
43
|
+
.update(JSON.stringify(["packaged-helper", context.pluginId, resolve(context.dataDirectory), project.id]))
|
|
44
|
+
.digest("hex");
|
|
45
|
+
// Keep the unnamespaced production identity byte-for-byte compatible.
|
|
46
|
+
// The name also scopes ownership records; never adopt another instance's hook.
|
|
47
|
+
const prefix = instanceNamespace === "" ? "" : `${instanceNamespace}-`;
|
|
48
|
+
const name = `pi-web-workspace-discovery-${prefix}${identity}`;
|
|
49
|
+
const helper = join(context.packageRoot, "workspace-discovery-notify.cjs");
|
|
50
|
+
const registration = join(context.dataDirectory, "workspace-discovery", `${name}.registration.json`);
|
|
51
|
+
const notifier = context.workspaceNotifications?.command(project.id);
|
|
52
|
+
const command = registrationCommand(helper, nodeExecutable, notifier ?? []);
|
|
53
|
+
const ownedCommands = new Set([command, ...await readRegistrationCommands(registration, signal)]);
|
|
54
|
+
const install = isEnabled && notifier !== undefined && notifier.length > 0;
|
|
55
|
+
if (install && !await supportsNamedHooks(project, signal))
|
|
56
|
+
return;
|
|
57
|
+
const commandKey = `hook.${name}.command`;
|
|
58
|
+
const eventKey = `hook.${name}.event`;
|
|
59
|
+
const commands = await readConfig(project, commandKey, signal);
|
|
60
|
+
const events = await readConfig(project, eventKey, signal);
|
|
61
|
+
if (commands.some((entry) => entry.scope !== "local" || !ownedCommands.has(entry.value))
|
|
62
|
+
|| events.some((entry) => entry.scope !== "local" || (entry.value !== EVENT && commands.length === 0))) {
|
|
63
|
+
// We only install local keys. A same-name global/worktree registration is
|
|
64
|
+
// external even if its values happen to match ours; never override it or
|
|
65
|
+
// remove the local command on which its inherited event might depend.
|
|
66
|
+
throw new Error(`Git workspace discovery hook conflict: ${name} has unexpected configuration`);
|
|
67
|
+
}
|
|
68
|
+
if (!install) {
|
|
69
|
+
// Disconnect the event first: an event without a command makes Git fail.
|
|
70
|
+
// Installation uses the reverse order for the same reason.
|
|
71
|
+
await unsetLocal(project, eventKey, events, signal);
|
|
72
|
+
await unsetLocal(project, commandKey, commands, signal);
|
|
73
|
+
signal.throwIfAborted();
|
|
74
|
+
await rm(registration, { force: true });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// Routing/runtime arguments live in Git config, not generated executable
|
|
78
|
+
// files. Preserve exact ownership before changing config, including either
|
|
79
|
+
// side of a failed update, then compact after installation succeeds.
|
|
80
|
+
await writeRegistration([...new Set([...commands.map((entry) => entry.value), command])]);
|
|
81
|
+
await setLocal(project, commandKey, command, commands, signal);
|
|
82
|
+
await setLocal(project, eventKey, EVENT, events, signal);
|
|
83
|
+
await writeRegistration([command]);
|
|
84
|
+
async function writeRegistration(owned) {
|
|
85
|
+
await writeOwnedFileIfChanged(registration, `${JSON.stringify({ version: 1, commands: owned })}\n`, signal);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function supportsNamedHooks(project, signal) {
|
|
89
|
+
// Detect Git's advertised named-config facility, not its version. `hook
|
|
90
|
+
// list` is unsuitable here: a missing command in an existing registration
|
|
91
|
+
// makes it fail, preventing precisely the repair we need to perform.
|
|
92
|
+
const result = await runGit(project.path, ["help", "--config"], signal);
|
|
93
|
+
if (result.signal !== null)
|
|
94
|
+
throw new Error(`Git named-hook detection ended from signal ${result.signal}`);
|
|
95
|
+
const keys = result.stdout.split(/\r?\n/u);
|
|
96
|
+
const supported = result.exitCode === 0
|
|
97
|
+
&& keys.includes("hook.<friendly-name>.command") && keys.includes("hook.<friendly-name>.event");
|
|
98
|
+
if (!supported)
|
|
99
|
+
context.logger.debug("Git named configuration hooks unavailable; workspace discovery remains on catalog access", { projectId: project.id });
|
|
100
|
+
return supported;
|
|
101
|
+
}
|
|
102
|
+
async function readConfig(project, key, signal) {
|
|
103
|
+
const result = await runGit(project.path, ["config", "--null", "--show-scope", "--get-all", key], signal);
|
|
104
|
+
if (result.signal === null && result.exitCode === 1)
|
|
105
|
+
return [];
|
|
106
|
+
requireSuccess(result, `read ${key}`);
|
|
107
|
+
const fields = result.stdout.split("\0");
|
|
108
|
+
if (fields.pop() !== "" || fields.length % 2 !== 0)
|
|
109
|
+
throw new Error(`Malformed Git config output for ${key}`);
|
|
110
|
+
const entries = [];
|
|
111
|
+
for (let index = 0; index < fields.length; index += 2) {
|
|
112
|
+
entries.push({ scope: fields[index] ?? "", value: fields[index + 1] ?? "" });
|
|
113
|
+
}
|
|
114
|
+
return entries;
|
|
115
|
+
}
|
|
116
|
+
async function setLocal(project, key, value, entries, signal) {
|
|
117
|
+
const local = entries.filter((entry) => entry.scope === "local");
|
|
118
|
+
if (local.length === 1 && local[0]?.value === value)
|
|
119
|
+
return;
|
|
120
|
+
requireSuccess(await runGit(project.path, ["config", "--local", "--replace-all", key, value], signal), `set ${key}`);
|
|
121
|
+
}
|
|
122
|
+
async function unsetLocal(project, key, entries, signal) {
|
|
123
|
+
if (!entries.some((entry) => entry.scope === "local"))
|
|
124
|
+
return;
|
|
125
|
+
requireSuccess(await runGit(project.path, ["config", "--local", "--unset-all", key], signal), `remove ${key}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function requireSuccess(result, action) {
|
|
129
|
+
if (result.signal === null && result.exitCode === 0)
|
|
130
|
+
return;
|
|
131
|
+
const outcome = result.signal === null ? `exit ${String(result.exitCode)}` : `signal ${result.signal}`;
|
|
132
|
+
throw new Error(`Unable to ${action} (${outcome}): ${result.stderr.trim()}`);
|
|
133
|
+
}
|
|
134
|
+
function shellQuote(value) {
|
|
135
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
136
|
+
}
|
|
137
|
+
function registrationCommand(helper, nodeExecutable, notificationCommand) {
|
|
138
|
+
// Git itself supplies the shell and appends quoted hook argv to the function
|
|
139
|
+
// call. No extra `sh` executable is required on the user's PATH. The final
|
|
140
|
+
// return preserves best-effort behavior even when our helper/runtime is gone.
|
|
141
|
+
const shellHelper = process.platform === "win32" ? helper.replaceAll("\\", "/") : helper;
|
|
142
|
+
const shellNode = process.platform === "win32" ? nodeExecutable.replaceAll("\\", "/") : nodeExecutable;
|
|
143
|
+
return `pi_web_workspace_notify () { if test -r ${shellQuote(shellHelper)}; then ${shellQuote(shellNode)} ${shellQuote(shellHelper)} ${shellQuote(JSON.stringify(notificationCommand))} "$@" >/dev/null 2>&1; fi; return 0; }; pi_web_workspace_notify`;
|
|
144
|
+
}
|
|
145
|
+
async function readRegistrationCommands(path, signal) {
|
|
146
|
+
let source;
|
|
147
|
+
try {
|
|
148
|
+
source = await readFile(path, { encoding: "utf8", signal });
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
152
|
+
return [];
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
const value = JSON.parse(source);
|
|
156
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)
|
|
157
|
+
|| Reflect.get(value, "version") !== 1)
|
|
158
|
+
throw new Error(`Invalid PI WEB Git hook registration record: ${path}`);
|
|
159
|
+
const commands = Reflect.get(value, "commands");
|
|
160
|
+
if (!Array.isArray(commands) || !commands.every((command) => typeof command === "string" && command.length > 0)) {
|
|
161
|
+
throw new Error(`Invalid PI WEB Git hook command record: ${path}`);
|
|
162
|
+
}
|
|
163
|
+
return commands;
|
|
164
|
+
}
|
|
165
|
+
async function writeOwnedFileIfChanged(path, source, signal) {
|
|
166
|
+
signal.throwIfAborted();
|
|
167
|
+
try {
|
|
168
|
+
if (await readFile(path, { encoding: "utf8", signal }) === source)
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT")
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
await mkdir(dirname(path), { recursive: true });
|
|
176
|
+
// Atomic per-project ownership records never contain executable code.
|
|
177
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
178
|
+
try {
|
|
179
|
+
await writeFile(temporary, source, { flag: "wx", mode: 0o600, signal });
|
|
180
|
+
signal.throwIfAborted();
|
|
181
|
+
await rename(temporary, path);
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
await rm(temporary, { force: true });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -210,7 +210,7 @@ function renderWorkspaceSection(html, workspace) {
|
|
|
210
210
|
</section>
|
|
211
211
|
`;
|
|
212
212
|
}
|
|
213
|
-
/** Panel body:
|
|
213
|
+
/** Panel body: machine status is useful even without a selected workspace. */
|
|
214
214
|
export function renderInfoPanel(html, context) {
|
|
215
215
|
return html `
|
|
216
216
|
<style>
|
|
@@ -226,9 +226,9 @@ export function renderInfoPanel(html, context) {
|
|
|
226
226
|
</style>
|
|
227
227
|
<section class="toolbar"><strong>${t("bundled.info.info.e70286", "Info")}</strong></section>
|
|
228
228
|
<section class="viewer info-status">
|
|
229
|
-
${renderStatusSection(html, context.state
|
|
229
|
+
${renderStatusSection(html, context.state.piWebStatus)}
|
|
230
230
|
${renderMachineSection(html, context.machine)}
|
|
231
|
-
${renderWorkspaceSection(html, context.workspace)}
|
|
231
|
+
${context.workspace === undefined ? null : renderWorkspaceSection(html, context.workspace)}
|
|
232
232
|
</section>
|
|
233
233
|
`;
|
|
234
234
|
}
|
|
@@ -24,7 +24,7 @@ const plugin = {
|
|
|
24
24
|
items: (context) => [{ type: "text", text: context.workspace.provider?.pluginId ?? (context.workspace.isGitRepo === true ? "Git" : t("bundled.info.folder.7c7802", "Folder")), title: context.workspace.path }],
|
|
25
25
|
},
|
|
26
26
|
],
|
|
27
|
-
|
|
27
|
+
applicationPanels: [
|
|
28
28
|
{
|
|
29
29
|
id: "workspace.info",
|
|
30
30
|
title: t("bundled.info.info.e70286", "Info"),
|