@gang-of-beads/pi-web-plugin-api 0.1.0-alpha.2 → 0.1.0-alpha.4

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 CHANGED
@@ -5,7 +5,7 @@ Lightweight browser and server plugin contracts for [PI WEB](https://github.com/
5
5
  ## Install
6
6
 
7
7
  ```sh
8
- npm install --save-dev @gang-of-beads/pi-web-plugin-api@alpha
8
+ npm install --save-dev @gang-of-beads/pi-web-plugin-api
9
9
  ```
10
10
 
11
11
  Browser plugins also install `lit@^3`: the browser declarations use Lit's template and style types, including when rendering through the host's `context.html`. Server-only plugins do not need Lit; their declaration dependencies are included.
@@ -28,7 +28,7 @@ Bundle runtime imports into each plugin half: PI WEB loads plugin modules as the
28
28
 
29
29
  ## Compatibility
30
30
 
31
- `0.1.0-alpha.2` contains the declarations shipped by PI WEB `2.202610.11`, with the shape and typography tokens (`--pi-font-*`, `--pi-radius-*`) accepted in `ThemeTokens`. Browser plugins declare `apiVersion: 2`; server plugins declare `apiVersion: 1`. The existing `@gang-of-beads/pi-web/plugin-api` and `@gang-of-beads/pi-web/server-plugin-api` entry points remain available.
31
+ `0.1.0-alpha.4` contains the declarations shipped by PI WEB `2.202610.13`: a server plugin receives its private settings as `context.serverSettings`, and the config types carry `serverSettings`; a composer contribution's context carries a `signal` that aborts when the composer leaves its session; and a browser plugin can list and add project folders on the selected machine through `projectFolders`. Browser plugins declare `apiVersion: 2`; server plugins declare `apiVersion: 1`. The existing `@gang-of-beads/pi-web/plugin-api` and `@gang-of-beads/pi-web/server-plugin-api` entry points remain available.
32
32
 
33
33
  This is an alpha release. Host-internal members marked unstable are not a supported third-party contract.
34
34
 
@@ -1,7 +1,7 @@
1
1
  import type { CSSResultGroup, TemplateResult } from "lit";
2
- import type { FileSuggestion, ForegroundToken, PluginListModel, PluginReadFailure, LegacyThemeToken, SemanticSurfaceToken, ShapeTypographyToken, TerminalCommandRun, TerminalInfo, DeleteWorkspaceFileResponse, FileContentResponse, FileTreeResponse, JsonValue, MachineKind, MoveWorkspaceFileOptions, MoveWorkspaceFileResponse, PiWebStatusResponse, TerminalCommandRunHandle, WorkspaceProviderMetadata, WorkspaceRemovalPresentation, WorkspaceUploadBatchProgress, WorkspaceUploadCancelHandle, WriteWorkspaceFileOptions, WriteWorkspaceFileResponse, MachineConnection } from "./shared/pluginApiTypes.js";
2
+ import type { FileSuggestion, ProjectFolderListing, ProjectFolderListingRequest, ForegroundToken, MessageRoleToken, PluginListModel, PluginReadFailure, LegacyThemeToken, SemanticSurfaceToken, ShapeTypographyToken, TerminalCommandRun, TerminalInfo, DeleteWorkspaceFileResponse, FileContentResponse, FileTreeResponse, JsonValue, MachineKind, MoveWorkspaceFileOptions, MoveWorkspaceFileResponse, PiWebStatusResponse, TerminalCommandRunHandle, WorkspaceProviderMetadata, WorkspaceRemovalPresentation, WorkspaceUploadBatchProgress, WorkspaceUploadCancelHandle, WriteWorkspaceFileOptions, WriteWorkspaceFileResponse, MachineConnection } from "./shared/pluginApiTypes.js";
3
3
  export type { ThemeToken } from "./shared/pluginApiTypes.js";
4
- export type { FileSuggestion, LegacyThemeToken, SemanticSurfaceToken, TerminalInfo, FileContentMediaType, FileContentResponse, FileTreeEntry, FileTreeResponse, JsonObject, JsonPrimitive, JsonValue, MachineConnection, MachineKind, PiWebComponentStatus, PiWebDockerMode, PluginListAction, PluginListGroup, PluginListModel, PluginListRead, PluginListRow, PluginListStatus, PluginListTone, PluginListWords, PluginReadFailure, PiWebInstallationInfo, PiWebInstallationKind, PiWebPackageManager, PiWebReleaseStatus, PiWebServiceComponent, PiWebStatusMessage, PiWebStatusResponse, PiWebStatusSeverity, PiWebVersionResponse, TerminalCommandRun, TerminalCommandRunHandle, TerminalCommandRunStatus, WorkspaceProviderCapabilities, WorkspaceProviderMetadata, WorkspaceRemovalPresentation, WorkspaceFileUploadProgress, WorkspaceUploadBatchFileProgress, WorkspaceUploadBatchProgress, WorkspaceUploadCancelHandle, WriteWorkspaceFileOptions, WriteWorkspaceFileResponse, DeleteWorkspaceFileResponse, MoveWorkspaceFileOptions, MoveWorkspaceFileResponse, } from "./shared/pluginApiTypes.js";
4
+ export type { FileSuggestion, ProjectFolderContents, ProjectFolderCrumb, ProjectFolderEntry, ProjectFolderListing, ProjectFolderListingRequest, ProjectFolderUnreadableCause, LegacyThemeToken, SemanticSurfaceToken, TerminalInfo, FileContentMediaType, FileContentResponse, FileTreeEntry, FileTreeResponse, JsonObject, JsonPrimitive, JsonValue, MachineConnection, MachineKind, PiWebComponentStatus, PiWebDockerMode, PluginListAction, PluginListGroup, PluginListModel, PluginListRead, PluginListRow, PluginListStatus, PluginListTone, PluginListWords, PluginReadFailure, PiWebInstallationInfo, PiWebInstallationKind, PiWebPackageManager, PiWebReleaseStatus, PiWebServiceComponent, PiWebStatusMessage, PiWebStatusResponse, PiWebStatusSeverity, PiWebVersionResponse, TerminalCommandRun, TerminalCommandRunHandle, TerminalCommandRunStatus, WorkspaceProviderCapabilities, WorkspaceProviderMetadata, WorkspaceRemovalPresentation, WorkspaceFileUploadProgress, WorkspaceUploadBatchFileProgress, WorkspaceUploadBatchProgress, WorkspaceUploadCancelHandle, WriteWorkspaceFileOptions, WriteWorkspaceFileResponse, DeleteWorkspaceFileResponse, MoveWorkspaceFileOptions, MoveWorkspaceFileResponse, } from "./shared/pluginApiTypes.js";
5
5
  export { CORE_STATUS_FLAGS } from "./shared/machineStatus.js";
6
6
  export { workspaceFileRefusalOf, type WorkspaceFileRefusal } from "./shared/apiTypes.js";
7
7
  export type PluginId = string;
@@ -492,12 +492,44 @@ export interface CodeFenceRendererContribution {
492
492
  render: (source: string) => Node | Promise<Node>;
493
493
  }
494
494
  export type ComposerSlot = "leading" | "trailing";
495
+ /**
496
+ * What a composer control sees, bound to the machine and session the composer
497
+ * showed when the context was made. The composer is reused across sessions and
498
+ * machines, so work that outlives the click (a dictation still streaming) stays
499
+ * with the session it started in.
500
+ */
495
501
  export interface ComposerRuntimeContext {
502
+ /**
503
+ * The session the context is bound to. A context made while a new session
504
+ * was still starting reports the session's real id once it has one: the
505
+ * same session under its lasting id, so read it when you need it rather
506
+ * than keeping a copy. It changes at most that once, and only then.
507
+ */
496
508
  sessionId: string | undefined;
497
509
  machineId: string | undefined;
498
510
  draft: string;
499
511
  busy: boolean;
512
+ /**
513
+ * Aborted when the composer leaves this context's session: the reader
514
+ * switches session or machine, or the composer unmounts. A new session
515
+ * getting its real id is not leaving it. Already aborted if that happened
516
+ * before the context reached the plugin. Stop work bound to the session when
517
+ * it fires. Absent on hosts older than this member: there, nothing tells the
518
+ * plugin the composer left.
519
+ */
520
+ signal?: AbortSignal;
521
+ /**
522
+ * Append text to this session's draft. Once the composer has left the
523
+ * session, the text joins that session's stored draft instead of the one on
524
+ * screen. A context made before the composer had a session has no stored
525
+ * draft, so its writes after the composer left are discarded.
526
+ */
500
527
  insertText: (text: string) => void;
528
+ /**
529
+ * Replace this session's draft. Once the composer has left the session,
530
+ * the stored draft is replaced instead of the one on screen, or the write is
531
+ * discarded if the context was made before the composer had a session.
532
+ */
501
533
  replaceDraft: (text: string) => void;
502
534
  notify: (message: string, severity: "info" | "warning" | "error") => void;
503
535
  /** Ask the composer to redraw after state only the plugin can see changed. */
@@ -581,6 +613,41 @@ export interface PluginProjectTrustRead {
581
613
  readonly decision: boolean | null;
582
614
  readonly trusted: boolean;
583
615
  }
616
+ /**
617
+ * The add-project dialog's access to one machine's folders, captured when the dialog opens: every
618
+ * folder listing, suggestion, trust read and the final add made through it go to `machine`,
619
+ * whatever is selected afterwards.
620
+ */
621
+ export interface PluginProjectFolders {
622
+ readonly machine: {
623
+ readonly id: string;
624
+ readonly name: string | undefined;
625
+ };
626
+ listFolder: (request: ProjectFolderListingRequest, signal: AbortSignal) => Promise<PluginProjectFolderReply>;
627
+ directories: (query: string, signal: AbortSignal) => Promise<FileSuggestion[]>;
628
+ trust: (path: string) => Promise<PluginProjectTrustRead>;
629
+ create: (input: PluginProjectCreateInput) => Promise<string | undefined>;
630
+ }
631
+ /**
632
+ * How one folder listing came back, naming the machine that was asked: the listing; a new folder
633
+ * name that machine refuses, with its reason; a machine too old to list folders; or no answer.
634
+ */
635
+ export type PluginProjectFolderReply = {
636
+ readonly kind: "listed";
637
+ readonly machineId: string;
638
+ readonly listing: ProjectFolderListing;
639
+ } | {
640
+ readonly kind: "name-refused";
641
+ readonly machineId: string;
642
+ readonly reason: string;
643
+ } | {
644
+ readonly kind: "unsupported";
645
+ readonly machineId: string;
646
+ } | {
647
+ readonly kind: "unanswered";
648
+ readonly machineId: string;
649
+ readonly reason: string;
650
+ };
584
651
  export interface PluginPromptEditor {
585
652
  /** Insert text at the current cursor position. Replaces any selection.
586
653
  * If the editor is not focused, focuses it first.
@@ -627,6 +694,8 @@ export interface PluginRuntimeContext {
627
694
  projectDirectories: (query: string, signal: AbortSignal) => Promise<FileSuggestion[]>;
628
695
  /** Server-resolved existing trust for a raw path, on the selected machine. */
629
696
  projectTrust: (path: string) => Promise<PluginProjectTrustRead>;
697
+ /** Folder access bound to the machine selected now, for the add-project dialog. Absent on hosts without the folder browser. */
698
+ projectFolders?: () => PluginProjectFolders;
630
699
  configureAuth: () => void | Promise<void>;
631
700
  logoutAuth: () => void | Promise<void>;
632
701
  openThemePicker: () => void;
@@ -903,9 +972,11 @@ export interface WorkspaceLabelContribution {
903
972
  items: (context: WorkspaceLabelContext) => WorkspaceLabelItem[];
904
973
  }
905
974
  export type ThemeColorScheme = "dark" | "light";
906
- export type ThemeTokens = Record<LegacyThemeToken, string> & Partial<Record<SemanticSurfaceToken | ForegroundToken | ShapeTypographyToken, string>>;
975
+ export type ThemeTokens = Record<LegacyThemeToken, string> & Partial<Record<SemanticSurfaceToken | ForegroundToken | ShapeTypographyToken | MessageRoleToken, string>>;
907
976
  export interface ThemeContribution {
908
977
  id: LocalContributionId;
978
+ /** Prefer this available concrete theme; tokens remain the fallback for other hosts. */
979
+ replacedBy?: QualifiedContributionId;
909
980
  name: string;
910
981
  description?: string;
911
982
  order?: number;
@@ -20,6 +20,14 @@ export interface ServerPluginActivationContext {
20
20
  readonly packageRoot: string;
21
21
  readonly logger: ServerPluginLogger;
22
22
  readonly settings: JsonObject;
23
+ /**
24
+ * A JSON copy of this plugin's own private `plugins.<id>.serverSettings` block, taken at
25
+ * activation; `{}` when none is configured. No browser receives it, and no other plugin's block
26
+ * is ever here. Undefined means the host is older and does not supply private settings at all,
27
+ * which is not the same as none configured: a plugin that needs a key should report that host
28
+ * as unable to provide it rather than as unconfigured.
29
+ */
30
+ readonly serverSettings?: JsonObject;
23
31
  /**
24
32
  * Durable per-plugin storage. Missing is not empty: `read` answers
25
33
  * undefined for a key that was never written, and a corrupt document reads
@@ -61,8 +61,17 @@ export type PiWebPluginConfigMap = Record<string, PiWebPluginConfig>;
61
61
  export interface PiWebPluginConfig {
62
62
  enabled?: boolean;
63
63
  settings?: PiWebPluginSettings;
64
+ /** Raw file/server configuration only; public responses omit this block. */
65
+ serverSettings?: PiWebPluginSettings;
64
66
  [key: string]: unknown;
65
67
  }
68
+ /** Explicit private-settings operations by plugin id; a plugin the map omits keeps its stored block. */
69
+ export type PiWebServerSettingsUpdates = Record<string, {
70
+ operation: "replace";
71
+ value: PiWebPluginSettings;
72
+ } | {
73
+ operation: "clear";
74
+ }>;
66
75
  export interface PiWebPathAccessConfig {
67
76
  allowedPaths?: string[];
68
77
  }
@@ -100,6 +109,11 @@ export interface PiWebListTilesConfig {
100
109
  phone?: ListTilesPerRow;
101
110
  desktop?: ListTilesPerRow;
102
111
  }
112
+ /** One theme choice, as a browser keeps it locally: a qualified theme id and whether it follows the system. */
113
+ export interface PiWebSharedThemeConfig {
114
+ themeId: `${string}:${string}`;
115
+ auto: boolean;
116
+ }
103
117
  export interface PiWebConfigValues {
104
118
  host?: string;
105
119
  port?: number;
@@ -122,6 +136,11 @@ export interface PiWebConfigValues {
122
136
  updateCommand?: string;
123
137
  /** Tiles per row in the Navigate lists, per layout; see PiWebListTilesConfig. */
124
138
  listTiles?: PiWebListTilesConfig;
139
+ /**
140
+ * The theme every browser of this PI WEB shares (owner, 2026-10-16); absent, each browser keeps
141
+ * its own. Written only through `PUT /api/config/theme`, so a general config save keeps it.
142
+ */
143
+ theme?: PiWebSharedThemeConfig;
125
144
  /**
126
145
  * When true, PI WEB appends environment facts to session system prompts:
127
146
  * the pi-web session nesting every session runs in, plus container facts in
@@ -259,6 +278,10 @@ export interface PiWebConfigResponse {
259
278
  config: PiWebConfigValues;
260
279
  effectiveConfig: PiWebConfigValues;
261
280
  envOverrides: PiWebConfigEnvOverrides;
281
+ /** Advertised only by the target that implements private settings preservation and explicit updates. */
282
+ serverSettingsProtocol?: 1;
283
+ /** Which plugins hold a `serverSettings` block (an empty block counts); presence only, never validity or a value. */
284
+ serverSettingsConfigured?: Record<string, boolean>;
262
285
  }
263
286
  export interface Project {
264
287
  id: string;
@@ -343,6 +366,8 @@ export declare const SESSION_UNREAD_COMPLETED_AT_MAX_LENGTH = 64;
343
366
  export declare const SESSION_NOT_FOUND_CODE = "session-not-found";
344
367
  /** The code the web and the daemon answer with for a route they do not have: the machine is older than the page (B16). */
345
368
  export declare const ROUTE_MISSING_CODE = "route-missing";
369
+ /** The code the project-folder listing answers with for a new folder name the machine cannot use. */
370
+ export declare const PROJECT_FOLDER_NAME_REFUSED_CODE = "folder-name-refused";
346
371
  /**
347
372
  * What a failed answer says about the link (B16), in the `transport` field of its error body: the
348
373
  * web process found nothing listening where its session daemon should be (the daemon is restarting,
@@ -686,6 +711,13 @@ export interface QueuedSessionMessage {
686
711
  * existed, by another client, or by a non-browser caller.
687
712
  */
688
713
  clientMessageId?: string;
714
+ /**
715
+ * How many images the message carries. A device handed the message back (Stop, Clear queue,
716
+ * Take back) without its own copy of them can say so instead of returning the words as if
717
+ * that were all. Absent when the daemon does not know: one from before this field existed, or
718
+ * an entry an extension put in pi's queue directly.
719
+ */
720
+ imageCount?: number;
689
721
  }
690
722
  /** Length bound for extension-dialog ids. */
691
723
  export declare const EXTENSION_DIALOG_ID_MAX_LENGTH = 128;
@@ -1272,7 +1304,7 @@ export interface SlashCommand {
1272
1304
  description?: string;
1273
1305
  source: "extension" | "prompt" | "skill" | "builtin";
1274
1306
  }
1275
- export type { FileSuggestion } from "./pluginApiTypes.js";
1307
+ export type { FileSuggestion, ProjectFolderContents, ProjectFolderCrumb, ProjectFolderEntry, ProjectFolderListing, ProjectFolderListingRequest, ProjectFolderUnreadableCause } from "./pluginApiTypes.js";
1276
1308
  export interface TerminalInfo {
1277
1309
  id: string;
1278
1310
  cwd: string;
@@ -18,6 +18,8 @@ export const SESSION_UNREAD_COMPLETED_AT_MAX_LENGTH = 64;
18
18
  export const SESSION_NOT_FOUND_CODE = "session-not-found";
19
19
  /** The code the web and the daemon answer with for a route they do not have: the machine is older than the page (B16). */
20
20
  export const ROUTE_MISSING_CODE = "route-missing";
21
+ /** The code the project-folder listing answers with for a new folder name the machine cannot use. */
22
+ export const PROJECT_FOLDER_NAME_REFUSED_CODE = "folder-name-refused";
21
23
  /**
22
24
  * What a failed answer says about the link (B16), in the `transport` field of its error body: the
23
25
  * web process found nothing listening where its session daemon should be (the daemon is restarting,
@@ -50,6 +50,60 @@ export interface FileSuggestion {
50
50
  path: string;
51
51
  kind: "tracked" | "untracked" | "other";
52
52
  }
53
+ /**
54
+ * One folder to list for the add-project folder browser: a path as the reader entered or chose it
55
+ * (empty means the machine's home folder), or, with `child`, a new folder's name under that path.
56
+ * The machine resolves and joins it with its own path rules; the page never splits or joins paths.
57
+ */
58
+ export interface ProjectFolderListingRequest {
59
+ readonly path: string;
60
+ readonly child?: string;
61
+ }
62
+ /** One step of an absolute path, root first, spelled the way the machine spells it. */
63
+ export interface ProjectFolderCrumb {
64
+ readonly name: string;
65
+ readonly path: string;
66
+ }
67
+ /** A subfolder the folder browser can enter. */
68
+ export interface ProjectFolderEntry {
69
+ readonly name: string;
70
+ readonly path: string;
71
+ }
72
+ /** Why a folder that exists, or might, could not be listed. None of these means the folder is empty. */
73
+ export type ProjectFolderUnreadableCause = "not-a-folder" | "denied" | "timed-out" | "failed";
74
+ /**
75
+ * What one listing found. `complete` is every subfolder; `truncated` is the first `folders` of
76
+ * `total`; `incomplete` stopped before every linked folder was checked; `missing` is a folder that
77
+ * does not exist yet, which adding the project creates; `unreadable` names why it could not be read.
78
+ */
79
+ export type ProjectFolderContents = {
80
+ readonly kind: "complete";
81
+ readonly folders: readonly ProjectFolderEntry[];
82
+ } | {
83
+ readonly kind: "truncated";
84
+ readonly folders: readonly ProjectFolderEntry[];
85
+ readonly total: number;
86
+ } | {
87
+ readonly kind: "incomplete";
88
+ readonly folders: readonly ProjectFolderEntry[];
89
+ } | {
90
+ readonly kind: "missing";
91
+ } | {
92
+ readonly kind: "unreadable";
93
+ readonly cause: ProjectFolderUnreadableCause;
94
+ readonly detail?: string;
95
+ };
96
+ /**
97
+ * The machine's answer for one folder: its absolute path, its parent (null at a filesystem root),
98
+ * the path's steps root first, the machine's path separator, and what is inside.
99
+ */
100
+ export interface ProjectFolderListing {
101
+ readonly path: string;
102
+ readonly parent: string | null;
103
+ readonly crumbs: readonly ProjectFolderCrumb[];
104
+ readonly separator: string;
105
+ readonly contents: ProjectFolderContents;
106
+ }
53
107
  export interface FileContentResponse {
54
108
  path: string;
55
109
  language?: string;
@@ -214,13 +268,20 @@ export interface PiWebStatusResponse extends PiWebVersionResponse {
214
268
  */
215
269
  /**
216
270
  * Shape and typography stops a theme may pin. Optional by design: a theme
217
- * that omits them inherits the core's current shape - which since the pro
218
- * redesign is the flat mono TUI look, so a soft theme MUST set these to keep
219
- * its rounded sans rendering. Radii follow the published scale; there is no
271
+ * that omits them inherits the core's current shape - the rounded sans
272
+ * Material scale - so a flat or mono theme MUST set these to keep its look. Radii follow the published scale; there is no
220
273
  * density or border-width token yet.
221
274
  */
222
275
  export type ShapeTypographyToken = "--pi-font-ui" | "--pi-font-display" | "--pi-font-mono" | "--pi-radius-xs" | "--pi-radius-sm" | "--pi-radius-md" | "--pi-radius-lg" | "--pi-radius-xl";
223
- export type ThemeToken = LegacyThemeToken | SemanticSurfaceToken | ForegroundToken | ShapeTypographyToken;
276
+ export type ThemeToken = LegacyThemeToken | SemanticSurfaceToken | ForegroundToken | ShapeTypographyToken | MessageRoleToken;
277
+ /**
278
+ * Conversation message colours by who wrote the message. Optional: a theme that omits one gets it
279
+ * derived from its own tokens when it is applied (user from the selection colour and accent border,
280
+ * assistant from the surface, system from the purple family), so packs written before these tokens
281
+ * keep their user and assistant look and still get a system message that reads apart from the
282
+ * assistant's (owner, 2026-10-16).
283
+ */
284
+ export type MessageRoleToken = "--pi-msg-user-bg" | "--pi-msg-user-border" | "--pi-msg-user-header-bg" | "--pi-msg-assistant-bg" | "--pi-msg-assistant-border" | "--pi-msg-assistant-header-bg" | "--pi-msg-system-bg" | "--pi-msg-system-border" | "--pi-msg-system-header-bg";
224
285
  /**
225
286
  * Foregrounds a theme may name for a filled surface it owns. Optional by
226
287
  * design: a theme that omits one keeps the previous fallback, which is why
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gang-of-beads/pi-web-plugin-api",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-alpha.4",
4
4
  "description": "Lightweight PI WEB browser and server plugin contracts, with TypeScript declarations and public runtime helpers.",
5
5
  "license": "MIT",
6
6
  "author": "Gang of Beads",