@jmfederico/pi-web 1.202610.4 → 1.202610.6
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/dist/cli.js +1 -1
- package/dist/client/assets/classic-CyM97Akm.css +54 -0
- package/dist/client/assets/graphite-DquaqFXU.css +54 -0
- package/dist/client/assets/index-BAYF7oXL.js +4394 -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/index.html +2 -2
- package/dist/config.js +56 -0
- package/dist/config.js.map +1 -1
- package/dist/pi-packages/captains-log/dist/browser/index.js +13 -17
- package/dist/pi-packages/captains-log/dist/browser/panel.js +7 -11
- package/dist/pi-packages/relays/pi-web-plugin.js +21 -1
- package/dist/pi-packages/relays/relaysPanelElement.js +36 -32
- package/dist/pi-packages/workflows/browser/pi-web-plugin.js +35 -27
- package/dist/pi-packages/workflows/browser/worktreeDialog.js +156 -0
- package/dist/pi-packages/workflows/package.json +1 -0
- package/dist/pi-packages/workflows/prompts/worktree.md +11 -2
- package/dist/pi-packages/workflows/server-plugin.js +579 -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 +119 -162
- package/dist/pi-web-plugins/git/browser/git-panel.js +61 -46
- package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +25 -3
- package/dist/pi-web-plugins/git/git-backend.js +13 -1
- package/dist/pi-web-plugins/git/server-plugin.js +8 -1
- package/dist/pi-web-plugins/mermaid/browser/pi-web-plugin.js +8 -3
- package/dist/pi-web-plugins/terminal/browser/pi-web-plugin.js +50 -29
- package/dist/pi-web-plugins/updates/pi-web-plugin.js +11 -11
- package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +21 -1
- package/dist/pi-web-plugins/workspace-tasks/tasksPanelElement.js +35 -19
- package/dist/piWebUpdate.js +32 -9
- package/dist/piWebUpdate.js.map +1 -1
- package/dist/plugin-api.d.ts +174 -45
- package/dist/server/browserMessageProjection.js +1 -1
- package/dist/server/browserMessageProjection.js.map +1 -1
- package/dist/server/configRoutes.js +7 -1
- package/dist/server/configRoutes.js.map +1 -1
- package/dist/server/machines/machineProxyRoutes.js +3 -2
- package/dist/server/machines/machineProxyRoutes.js.map +1 -1
- package/dist/server/piWebPluginService.js +7 -1
- package/dist/server/piWebPluginService.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 +31 -38
- package/dist/server/plugins/pluginBackendChannelProxyRoutes.js.map +1 -1
- package/dist/server/plugins/pluginBackendProxyRoutes.js +38 -45
- package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -1
- package/dist/server/plugins/pluginBackendRegistry.js +58 -34
- package/dist/server/plugins/pluginBackendRegistry.js.map +1 -1
- package/dist/server/plugins/serverPluginCatalogCapability.js +106 -0
- package/dist/server/plugins/serverPluginCatalogCapability.js.map +1 -0
- package/dist/server/plugins/serverPluginRuntime.js +21 -8
- package/dist/server/plugins/serverPluginRuntime.js.map +1 -1
- package/dist/server/sessiond/pluginBackendChannelRoutes.js +24 -14
- package/dist/server/sessiond/pluginBackendChannelRoutes.js.map +1 -1
- package/dist/server/sessiond/pluginBackendRoutes.js +51 -47
- package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -1
- package/dist/server/sessiond/pluginCatalogRoutes.js +37 -0
- package/dist/server/sessiond/pluginCatalogRoutes.js.map +1 -0
- package/dist/server/sessiond/sessionProxyRoutes.js +17 -0
- 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.js +24 -4
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/authRoutes.js +1 -0
- package/dist/server/sessions/authRoutes.js.map +1 -1
- package/dist/server/sessions/authService.js +13 -0
- package/dist/server/sessions/authService.js.map +1 -1
- package/dist/server/sessions/builtinExtensionFactories.js +15 -1
- package/dist/server/sessions/builtinExtensionFactories.js.map +1 -1
- package/dist/server/sessions/companionBackend.js +286 -0
- package/dist/server/sessions/companionBackend.js.map +1 -0
- package/dist/server/sessions/mcpAuthFlowService.js +237 -0
- package/dist/server/sessions/mcpAuthFlowService.js.map +1 -0
- package/dist/server/sessions/mcpAuthRoutes.js +76 -0
- package/dist/server/sessions/mcpAuthRoutes.js.map +1 -0
- package/dist/server/sessions/messageHistoryTarget.js +18 -1
- package/dist/server/sessions/messageHistoryTarget.js.map +1 -1
- package/dist/server/sessions/piSessionEventConnections.js +5 -2
- package/dist/server/sessions/piSessionEventConnections.js.map +1 -1
- package/dist/server/sessions/piSessionService.js +279 -22
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/sessionCommandService.js +3 -17
- package/dist/server/sessions/sessionCommandService.js.map +1 -1
- package/dist/server/sessions/sessionNameGenerator.js +4 -4
- package/dist/server/sessions/sessionNameGenerator.js.map +1 -1
- package/dist/server-plugin-api.d.ts +65 -15
- package/dist/server-plugin-api.js +25 -0
- package/dist/server-plugin-api.js.map +1 -1
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/federatedRoutes.js +17 -5
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/dist/shared/pluginApiTypes.d.ts +53 -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/docs/config.md +68 -2
- package/docs/plugins.md +382 -20
- package/examples/session-bridge-plugin/src/browser/index.ts +1 -1
- package/package.json +7 -12
- package/dist/client/assets/index-BAl5IbYx.js +0 -4429
|
@@ -2,12 +2,32 @@
|
|
|
2
2
|
import { RELAYS_ROOT } from "./relayDiscovery.js";
|
|
3
3
|
import { relayIdentityFromMetadata } from "./relayIdentity.js";
|
|
4
4
|
import { defineRelaysPanelElement, RELAY_SELECTION_QUERY_KEY } from "./relaysPanelElement.js";
|
|
5
|
+
const stylesheetIntegrationCapability = {
|
|
6
|
+
pluginId: "pi-web", id: "stylesheet-integration", version: 1,
|
|
7
|
+
parse(value) {
|
|
8
|
+
if (!isStylesheetIntegration(value)) {
|
|
9
|
+
throw new Error("Stylesheet integration capability v1 requires registerRoot and createController");
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
function isStylesheetIntegration(value) {
|
|
15
|
+
return typeof value === "object" && value !== null
|
|
16
|
+
&& "registerRoot" in value && typeof value.registerRoot === "function"
|
|
17
|
+
&& "createController" in value && typeof value.createController === "function"
|
|
18
|
+
&& "subscribe" in value && typeof value.subscribe === "function";
|
|
19
|
+
}
|
|
5
20
|
const plugin = {
|
|
6
21
|
apiVersion: 4,
|
|
7
22
|
name: "Relays",
|
|
23
|
+
requires: [stylesheetIntegrationCapability],
|
|
8
24
|
activate: ({ runtimePluginId, html, svg }) => {
|
|
9
25
|
defineRelaysPanelElement();
|
|
26
|
+
let stylesheetIntegration;
|
|
10
27
|
return {
|
|
28
|
+
start: ({ capabilities }) => {
|
|
29
|
+
stylesheetIntegration = capabilities.resolve(stylesheetIntegrationCapability);
|
|
30
|
+
},
|
|
11
31
|
contributions: {
|
|
12
32
|
actions: [
|
|
13
33
|
{
|
|
@@ -64,7 +84,7 @@ const plugin = {
|
|
|
64
84
|
</svg>
|
|
65
85
|
`,
|
|
66
86
|
order: 50,
|
|
67
|
-
render: (context) => html `<pi-web-relays-panel .context=${context}></pi-web-relays-panel>`,
|
|
87
|
+
render: (context) => html `<pi-web-relays-panel .stylesheetIntegration=${stylesheetIntegration} .context=${context}></pi-web-relays-panel>`,
|
|
68
88
|
},
|
|
69
89
|
],
|
|
70
90
|
},
|
|
@@ -27,6 +27,24 @@ export function defineRelaysPanelElement() {
|
|
|
27
27
|
* horizontal scroll position and keyboard focus survive document switches.
|
|
28
28
|
*/
|
|
29
29
|
class PiWebRelaysPanel extends HTMLElement {
|
|
30
|
+
integration;
|
|
31
|
+
releaseStyles;
|
|
32
|
+
set stylesheetIntegration(value) {
|
|
33
|
+
if (value === this.integration)
|
|
34
|
+
return;
|
|
35
|
+
this.releaseStyles?.();
|
|
36
|
+
this.releaseStyles = undefined;
|
|
37
|
+
this.integration = value;
|
|
38
|
+
if (this.isConnected)
|
|
39
|
+
this.releaseStyles = value?.registerRoot(this.root, { sharedUi: true });
|
|
40
|
+
}
|
|
41
|
+
connectedCallback() {
|
|
42
|
+
this.releaseStyles ??= this.integration?.registerRoot(this.root, { sharedUi: true });
|
|
43
|
+
}
|
|
44
|
+
disconnectedCallback() {
|
|
45
|
+
this.releaseStyles?.();
|
|
46
|
+
this.releaseStyles = undefined;
|
|
47
|
+
}
|
|
30
48
|
contextValue;
|
|
31
49
|
listing;
|
|
32
50
|
selectedRelayPath;
|
|
@@ -45,8 +63,8 @@ class PiWebRelaysPanel extends HTMLElement {
|
|
|
45
63
|
this.root = this.attachShadow({ mode: "open" });
|
|
46
64
|
this.root.innerHTML = `
|
|
47
65
|
${relaysStyles()}
|
|
48
|
-
<section class="toolbar" hidden></section>
|
|
49
|
-
<nav class="document-tabs" aria-label="Relay documents" hidden></nav>
|
|
66
|
+
<section class="toolbar ui-toolbar" hidden></section>
|
|
67
|
+
<nav class="document-tabs ui-tabs" aria-label="Relay documents" hidden></nav>
|
|
50
68
|
<section class="viewer"><div class="empty">Select a workspace.</div></section>
|
|
51
69
|
`;
|
|
52
70
|
this.toolbar = requiredRegion(this.root, ".toolbar");
|
|
@@ -284,10 +302,10 @@ class PiWebRelaysPanel extends HTMLElement {
|
|
|
284
302
|
}
|
|
285
303
|
this.toolbar.hidden = false;
|
|
286
304
|
this.toolbar.innerHTML = `
|
|
287
|
-
<strong>Relays</strong>
|
|
288
|
-
<span class="toolbar-actions">
|
|
305
|
+
<strong class="ui-toolbar-title">Relays</strong>
|
|
306
|
+
<span class="toolbar-actions ui-toolbar-group">
|
|
289
307
|
${this.renderRelayPicker()}
|
|
290
|
-
<button class="
|
|
308
|
+
<button type="button" class="ui-button ui-button--compact ui-button--icon" data-refresh aria-label="Refresh" title="Refresh">${refreshIconSvg()}</button>
|
|
291
309
|
</span>
|
|
292
310
|
`;
|
|
293
311
|
}
|
|
@@ -304,7 +322,7 @@ class PiWebRelaysPanel extends HTMLElement {
|
|
|
304
322
|
const selected = relay.path === this.selectedRelayPath ? " selected" : "";
|
|
305
323
|
return `<option value="${escapeAttr(relay.path)}"${selected}>${escapeHtml(relay.name)}</option>`;
|
|
306
324
|
}).join("");
|
|
307
|
-
return `<select data-relay-picker aria-label="Relay">${options}</select>`;
|
|
325
|
+
return `<select class="ui-select" data-relay-picker aria-label="Relay">${options}</select>`;
|
|
308
326
|
}
|
|
309
327
|
renderTabs() {
|
|
310
328
|
const documents = this.documents;
|
|
@@ -335,26 +353,26 @@ class PiWebRelaysPanel extends HTMLElement {
|
|
|
335
353
|
const chip = this.renderDirectoryChip(node, containsActivePath);
|
|
336
354
|
if (!this.expandedDirs.has(node.path) || node.children.length === 0)
|
|
337
355
|
return chip;
|
|
338
|
-
return `<span class="directory-group">${chip}${this.renderStripNodes(node.children, containsActivePath)}</span>`;
|
|
356
|
+
return `<span class="directory-group ui-toolbar-group">${chip}${this.renderStripNodes(node.children, containsActivePath)}</span>`;
|
|
339
357
|
}).join("");
|
|
340
358
|
}
|
|
341
359
|
renderFileTab(file) {
|
|
342
360
|
const active = file.path === this.selectedDocumentPath;
|
|
343
|
-
return `<button class="document-tab${active ? "
|
|
361
|
+
return `<button type="button" class="ui-button ui-tab document-tab${active ? " selected" : ""}" data-document-path="${escapeAttr(file.path)}" title="${escapeAttr(file.relativePath)}"${active ? ' aria-current="page"' : ""}>${escapeHtml(file.name)}</button>`;
|
|
344
362
|
}
|
|
345
363
|
renderDirectoryChip(directory, containsActivePath) {
|
|
346
364
|
const expanded = this.expandedDirs.has(directory.path);
|
|
347
365
|
const containsActive = directory.path === containsActivePath;
|
|
348
366
|
const title = containsActive ? "Contains the open document" : directory.relativePath;
|
|
349
|
-
return `<button class="document-tab directory-tab${containsActive ? " contains-active" : ""}" data-directory-path="${escapeAttr(directory.path)}" title="${escapeAttr(title)}" aria-expanded="${expanded ? "true" : "false"}">${chevronSvg()}${escapeHtml(directory.name)}</button>`;
|
|
367
|
+
return `<button type="button" class="ui-button ui-tab document-tab directory-tab${containsActive ? " contains-active" : ""}" data-directory-path="${escapeAttr(directory.path)}" title="${escapeAttr(title)}" aria-expanded="${expanded ? "true" : "false"}">${chevronSvg()}${escapeHtml(directory.name)}</button>`;
|
|
350
368
|
}
|
|
351
369
|
/** Move the active marker between the mounted tab buttons without rebuilding them. */
|
|
352
370
|
updateActiveTab() {
|
|
353
371
|
for (const tab of this.tabStrip.querySelectorAll("button[data-document-path]")) {
|
|
354
372
|
const active = tab.getAttribute("data-document-path") === this.selectedDocumentPath;
|
|
355
|
-
tab.classList.toggle("
|
|
373
|
+
tab.classList.toggle("selected", active);
|
|
356
374
|
if (active)
|
|
357
|
-
tab.setAttribute("aria-current", "
|
|
375
|
+
tab.setAttribute("aria-current", "page");
|
|
358
376
|
else
|
|
359
377
|
tab.removeAttribute("aria-current");
|
|
360
378
|
}
|
|
@@ -500,37 +518,23 @@ function relaysStyles() {
|
|
|
500
518
|
(the app container is a fixed-height flex column; with shrink enabled
|
|
501
519
|
the viewer's huge content basis starves them down to a sliver once a
|
|
502
520
|
tall document renders). The viewer absorbs all shrinking instead. */
|
|
503
|
-
.toolbar { flex: 0 0 auto;
|
|
521
|
+
.toolbar { flex: 0 0 auto; justify-content: space-between; padding-inline-start: 10px; }
|
|
504
522
|
.toolbar[hidden], .document-tabs[hidden] { display: none; }
|
|
505
|
-
.toolbar-actions {
|
|
523
|
+
.toolbar-actions { justify-content: flex-end; }
|
|
506
524
|
.relay-name { min-width: 0; color: var(--pi-text-secondary); overflow-wrap: anywhere; }
|
|
507
|
-
|
|
508
|
-
when the viewer's content scrolls up against its top edge. */
|
|
509
|
-
.document-tabs { flex: 0 0 auto; display: flex; flex-wrap: nowrap; gap: 6px; padding: 8px 12px; overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; scrollbar-width: thin; }
|
|
525
|
+
.document-tabs { flex: 0 0 auto; overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; scrollbar-width: thin; }
|
|
510
526
|
.viewer { flex: 1 1 auto; box-sizing: border-box; display: grid; align-content: start; gap: 12px; min-height: 0; overflow: auto; padding: 12px; }
|
|
511
527
|
/* Grid children default to min-width: auto; without these caps a wide code
|
|
512
528
|
block or table would silently stretch the whole viewer track. */
|
|
513
529
|
.viewer > * { box-sizing: border-box; min-width: 0; max-width: 100%; }
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
button.icon-button svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; }
|
|
518
|
-
select { min-width: 0; max-width: 240px; padding: 5px 6px; }
|
|
519
|
-
.document-tab { flex: 0 0 auto; white-space: nowrap; font-size: 12px; padding: 4px 10px; }
|
|
520
|
-
.document-tab.active { border-color: var(--pi-accent-border); background: var(--pi-accent); color: var(--pi-bg); }
|
|
521
|
-
/* An expanded folder wraps its chip and children in one rounded group so
|
|
522
|
-
nested tabs read as contained by their folder; nested groups stack.
|
|
523
|
-
No inner padding: the wrapper's border lines sit flush on the button
|
|
524
|
-
row (a continuous edge), and the -1px vertical margins cancel the
|
|
525
|
-
border's height so expanding never grows the strip or its neighbors. */
|
|
526
|
-
.directory-group { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px; min-width: 0; border: 1px solid var(--pi-border-muted); border-radius: 10px; background: var(--pi-bg-overlay-soft); padding: 0; margin: -1px 0; }
|
|
527
|
-
/* Folder chips interleave with file tabs; the chevron rotates while expanded. */
|
|
528
|
-
.directory-tab { display: inline-flex; align-items: center; gap: 5px; }
|
|
530
|
+
[data-refresh] svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; }
|
|
531
|
+
select { min-width: 0; max-width: 240px; }
|
|
532
|
+
/* Folder controls interleave with file tabs; shared groups keep nested controls contiguous. */
|
|
529
533
|
.directory-tab .chevron { flex: 0 0 auto; width: 9px; height: 9px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; transition: transform 0.12s ease; }
|
|
530
534
|
.directory-tab[aria-expanded="true"] .chevron { transform: rotate(90deg); }
|
|
531
535
|
/* The selection survives a folder collapse: the nearest collapsed ancestor
|
|
532
536
|
highlights instead, without taking the fill reserved for the open document. */
|
|
533
|
-
.directory-tab.contains-active {
|
|
537
|
+
.directory-tab.contains-active { color: var(--pi-accent); }
|
|
534
538
|
.directory-tab.contains-active::after { content: ""; width: 5px; height: 5px; border-radius: 50%; background: var(--pi-accent); }
|
|
535
539
|
code, pre { border: 1px solid var(--pi-border-muted); border-radius: 6px; background: var(--pi-bg); color: var(--pi-text-secondary); font: 12px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
|
536
540
|
code { padding: 2px 5px; }
|
|
@@ -1,33 +1,41 @@
|
|
|
1
1
|
// Generated from pi-packages/workflows/browser/pi-web-plugin.ts. Do not edit directly.
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
return state.
|
|
5
|
-
&& state.selectedWorkspace !== undefined
|
|
6
|
-
&& session !== undefined
|
|
7
|
-
&& !session.archived
|
|
8
|
-
&& !session.pending
|
|
9
|
-
&& prompt.getText() === "";
|
|
2
|
+
import { openWorktreeDialog } from "./worktreeDialog.js";
|
|
3
|
+
function canCreateWorktree({ state, backend }) {
|
|
4
|
+
return state.selectedProject !== undefined && backend !== undefined;
|
|
10
5
|
}
|
|
11
6
|
const plugin = {
|
|
12
|
-
apiVersion:
|
|
7
|
+
apiVersion: 5,
|
|
13
8
|
name: "Workflows",
|
|
14
|
-
activate: () =>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
9
|
+
activate: ({ lifetimeSignal, selection }) => {
|
|
10
|
+
let closeDialog;
|
|
11
|
+
let unsubscribe;
|
|
12
|
+
const close = () => { closeDialog?.(); closeDialog = undefined; unsubscribe?.(); unsubscribe = undefined; };
|
|
13
|
+
lifetimeSignal.addEventListener("abort", close, { once: true });
|
|
14
|
+
return {
|
|
15
|
+
contributions: {
|
|
16
|
+
actions: [{
|
|
17
|
+
id: "worktree.create",
|
|
18
|
+
title: "Create Worktree",
|
|
19
|
+
description: "Choose a base branch and purpose, then start a new conversation in the main workspace",
|
|
20
|
+
group: "Workflows",
|
|
21
|
+
enabled: canCreateWorktree,
|
|
22
|
+
disabledReason: () => "Select a project with the Workflows backend available first.",
|
|
23
|
+
run: (context) => {
|
|
24
|
+
const project = context.state.selectedProject;
|
|
25
|
+
if (!project || !context.backend || lifetimeSignal.aborted)
|
|
26
|
+
return;
|
|
27
|
+
close();
|
|
28
|
+
const peer = context.backend.forScope({ kind: "project", projectId: project.id });
|
|
29
|
+
closeDialog = openWorktreeDialog(context, peer, () => { closeDialog = undefined; unsubscribe?.(); unsubscribe = undefined; });
|
|
30
|
+
unsubscribe = selection?.subscribe((snapshot) => {
|
|
31
|
+
if (snapshot.selectedProject?.id !== project.id || snapshot.selectedMachine?.id !== context.catalog.machineId)
|
|
32
|
+
close();
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
}],
|
|
36
|
+
},
|
|
37
|
+
dispose() { close(); lifetimeSignal.removeEventListener("abort", close); },
|
|
38
|
+
};
|
|
39
|
+
},
|
|
32
40
|
};
|
|
33
41
|
export default plugin;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Generated from pi-packages/workflows/browser/worktreeDialog.ts. Do not edit directly.
|
|
2
|
+
/** Plugin-owned UI; all machine operations go through the captured public backend. */
|
|
3
|
+
export function openWorktreeDialog(context, peer, onClose) {
|
|
4
|
+
const project = context.state.selectedProject;
|
|
5
|
+
if (!project)
|
|
6
|
+
throw new Error("Select a project first.");
|
|
7
|
+
if (!peer.request)
|
|
8
|
+
throw new Error("Workflows backend does not support requests.");
|
|
9
|
+
const request = peer.request.bind(peer);
|
|
10
|
+
const previousFocus = document.activeElement;
|
|
11
|
+
const host = document.createElement("div");
|
|
12
|
+
const root = host.attachShadow({ mode: "open" });
|
|
13
|
+
root.innerHTML = `
|
|
14
|
+
<style>
|
|
15
|
+
dialog { color:var(--pi-text, #eee); background:var(--pi-bg, #202124); border:1px solid var(--pi-border, #666); border-radius:12px; padding:24px; width:min(480px, calc(100vw - 64px)); font:14px system-ui; }
|
|
16
|
+
dialog::backdrop { background:rgb(0 0 0 / .55); }
|
|
17
|
+
h2 { margin-top:0; font-size:20px; } p { line-height:1.5; overflow-wrap:anywhere; }
|
|
18
|
+
label { display:block; margin:16px 0 6px; font-weight:600; }
|
|
19
|
+
input, textarea { box-sizing:border-box; width:100%; padding:10px; font:inherit; color:inherit; background:var(--pi-bg, #202124); border:1px solid var(--pi-border, #888); border-radius:6px; }
|
|
20
|
+
textarea { resize:vertical; min-height:100px; } button { font:inherit; padding:8px 14px; cursor:pointer; border-radius:6px; }
|
|
21
|
+
button:disabled { cursor:default; opacity:.6; } footer { display:flex; justify-content:flex-end; gap:10px; margin-top:20px; }
|
|
22
|
+
[role=alert] { color:var(--pi-error, #ffaaaa); } small { display:block; margin-top:6px; line-height:1.5; }
|
|
23
|
+
</style>
|
|
24
|
+
<dialog aria-labelledby="title" aria-describedby="target">
|
|
25
|
+
<h2 id="title">Create worktree</h2>
|
|
26
|
+
<p id="target"></p>
|
|
27
|
+
<p id="status" role="status">Loading branches…</p>
|
|
28
|
+
<form>
|
|
29
|
+
<label for="base">Base branch</label>
|
|
30
|
+
<input id="base" name="base" list="branches" required maxlength="512" disabled>
|
|
31
|
+
<datalist id="branches"></datalist>
|
|
32
|
+
<small id="base-help">Choose a local or remote branch. HEAD uses the main workspace’s current commit.</small>
|
|
33
|
+
<label for="description">Purpose or description</label>
|
|
34
|
+
<textarea id="description" name="description" required maxlength="8000" disabled aria-describedby="description-help"></textarea>
|
|
35
|
+
<small id="description-help">The agent uses this to name the branch and worktree.</small>
|
|
36
|
+
<p id="error" role="alert" hidden></p>
|
|
37
|
+
<footer><button type="button" id="cancel">Cancel</button><button type="submit" disabled>Create worktree</button></footer>
|
|
38
|
+
</form>
|
|
39
|
+
</dialog>`;
|
|
40
|
+
function element(selector, constructor) {
|
|
41
|
+
const value = root.querySelector(selector);
|
|
42
|
+
if (!(value instanceof constructor))
|
|
43
|
+
throw new Error(`Missing Workflows control: ${selector}`);
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
const dialog = element("dialog", HTMLDialogElement);
|
|
47
|
+
const form = element("form", HTMLFormElement);
|
|
48
|
+
const base = element("#base", HTMLInputElement);
|
|
49
|
+
const description = element("#description", HTMLTextAreaElement);
|
|
50
|
+
const submit = element("button[type=submit]", HTMLButtonElement);
|
|
51
|
+
const cancel = element("#cancel", HTMLButtonElement);
|
|
52
|
+
const status = element("#status", HTMLParagraphElement);
|
|
53
|
+
const error = element("#error", HTMLParagraphElement);
|
|
54
|
+
const controller = new AbortController();
|
|
55
|
+
let dispatching = false;
|
|
56
|
+
let dispatched;
|
|
57
|
+
element("#target", HTMLParagraphElement).textContent = `Project: ${project.name}. Starts a new conversation in its main workspace; your current conversation and draft stay unchanged.`;
|
|
58
|
+
base.setAttribute("aria-describedby", "base-help");
|
|
59
|
+
function close() {
|
|
60
|
+
if (controller.signal.aborted)
|
|
61
|
+
return;
|
|
62
|
+
controller.abort();
|
|
63
|
+
host.remove();
|
|
64
|
+
if (previousFocus instanceof HTMLElement && previousFocus.isConnected)
|
|
65
|
+
previousFocus.focus();
|
|
66
|
+
onClose();
|
|
67
|
+
}
|
|
68
|
+
function showError(message) { error.textContent = message; error.hidden = false; }
|
|
69
|
+
cancel.addEventListener("click", close);
|
|
70
|
+
dialog.addEventListener("cancel", (event) => { event.preventDefault(); if (!dispatching)
|
|
71
|
+
close(); });
|
|
72
|
+
dialog.addEventListener("close", close);
|
|
73
|
+
form.addEventListener("submit", (event) => {
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
if (submit.disabled || controller.signal.aborted || dispatching)
|
|
76
|
+
return;
|
|
77
|
+
if (!description.value.trim() || !base.value.trim()) {
|
|
78
|
+
showError("Enter a base branch and a purpose or description.");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
dispatching = true;
|
|
82
|
+
submit.disabled = true;
|
|
83
|
+
cancel.disabled = true;
|
|
84
|
+
base.disabled = true;
|
|
85
|
+
description.disabled = true;
|
|
86
|
+
error.hidden = true;
|
|
87
|
+
status.textContent = "Starting a new conversation…";
|
|
88
|
+
void (async () => {
|
|
89
|
+
try {
|
|
90
|
+
const result = object(await request("dispatch", { base: base.value.trim(), description: description.value.trim() }, { signal: controller.signal }));
|
|
91
|
+
dispatched = { workspaceId: text(result["workspaceId"]), sessionId: text(result["sessionId"]) };
|
|
92
|
+
if (controller.signal.aborted)
|
|
93
|
+
return;
|
|
94
|
+
status.textContent = "Conversation started. Opening it…";
|
|
95
|
+
await context.navigate({ machineId: context.catalog.machineId, projectId: project.id, workspaceId: dispatched.workspaceId, sessionId: dispatched.sessionId, view: "chat" });
|
|
96
|
+
close();
|
|
97
|
+
}
|
|
98
|
+
catch (cause) {
|
|
99
|
+
if (controller.signal.aborted)
|
|
100
|
+
return;
|
|
101
|
+
status.textContent = dispatched ? "Conversation started." : "Dispatch could not be confirmed.";
|
|
102
|
+
showError(`${message(cause)} ${dispatched ? `Open conversation ${dispatched.sessionId} in Sessions.` : "Check Sessions before trying again; a conversation may already have started."}`);
|
|
103
|
+
// Do not retry an uncertain submission automatically or from this dialog.
|
|
104
|
+
cancel.textContent = "Close";
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
dispatching = false;
|
|
108
|
+
cancel.disabled = false;
|
|
109
|
+
}
|
|
110
|
+
})();
|
|
111
|
+
});
|
|
112
|
+
document.body.append(host);
|
|
113
|
+
dialog.showModal();
|
|
114
|
+
void (async () => {
|
|
115
|
+
try {
|
|
116
|
+
const result = object(await request("prepare", null, { signal: controller.signal }));
|
|
117
|
+
const branches = result["branches"];
|
|
118
|
+
if (!Array.isArray(branches) || !branches.every((branch) => typeof branch === "string"))
|
|
119
|
+
throw new Error("Invalid branch list from Workflows.");
|
|
120
|
+
const defaultBase = text(result["defaultBase"]);
|
|
121
|
+
const workspacePath = text(result["workspacePath"]);
|
|
122
|
+
text(result["workspaceId"]);
|
|
123
|
+
if (controller.signal.aborted)
|
|
124
|
+
return;
|
|
125
|
+
const options = branches.map((branch) => { const option = document.createElement("option"); option.value = branch; return option; });
|
|
126
|
+
element("#branches", HTMLDataListElement).replaceChildren(...options);
|
|
127
|
+
base.value = defaultBase;
|
|
128
|
+
base.disabled = false;
|
|
129
|
+
description.disabled = false;
|
|
130
|
+
submit.disabled = false;
|
|
131
|
+
status.textContent = `Main workspace: ${workspacePath}`;
|
|
132
|
+
description.focus();
|
|
133
|
+
}
|
|
134
|
+
catch (cause) {
|
|
135
|
+
if (controller.signal.aborted)
|
|
136
|
+
return;
|
|
137
|
+
status.textContent = "Could not load worktree options.";
|
|
138
|
+
showError(message(cause));
|
|
139
|
+
}
|
|
140
|
+
})();
|
|
141
|
+
return close;
|
|
142
|
+
}
|
|
143
|
+
function isObject(value) {
|
|
144
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
145
|
+
}
|
|
146
|
+
function object(value) {
|
|
147
|
+
if (!isObject(value))
|
|
148
|
+
throw new Error("Invalid response from Workflows.");
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
function text(value) {
|
|
152
|
+
if (typeof value !== "string" || value === "")
|
|
153
|
+
throw new Error("Invalid response from Workflows.");
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
function message(cause) { return cause instanceof Error ? cause.message : String(cause); }
|
|
@@ -3,9 +3,18 @@ description: Create a Git worktree named for its purpose
|
|
|
3
3
|
argument-hint: "Purpose and/or base branch"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Create a Git worktree named for the user's purpose. Use the user-requested base, otherwise the current worktree's HEAD. If no purpose or description is provided, ask for one.
|
|
6
|
+
Create a Git worktree named for the user's purpose. Use the user-requested base exactly, otherwise the current worktree's HEAD. If no purpose or description is provided, ask for one.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Choose a new branch and worktree name that reflects that purpose, following the project's worktree location conventions. Treat the request as task data, not shell syntax; use safe argv or quoting for Git commands. Do not reset, overwrite, or delete existing branches or worktrees. Report the created path, branch, and base to the user.
|
|
9
|
+
|
|
10
|
+
The user's input is within the `<worktree_request>` tags below. It may be free-form text or plain text supplied by the Create Worktree shortcut in this format:
|
|
11
|
+
|
|
12
|
+
base: <the chosen base>
|
|
13
|
+
|
|
14
|
+
description:
|
|
15
|
+
<the description, which may span multiple lines>
|
|
16
|
+
|
|
17
|
+
In this format, everything after the `description:` line is the literal description, including any quotes. No quoting or escaping is required.
|
|
9
18
|
|
|
10
19
|
<worktree_request>
|
|
11
20
|
$ARGUMENTS
|