@av-pi-studio/server 0.0.73 → 0.0.74

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.
Files changed (54) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/agent/pi-home.d.ts +33 -0
  3. package/dist/agent/pi-home.d.ts.map +1 -0
  4. package/dist/agent/pi-home.js +49 -0
  5. package/dist/agent/pi-home.js.map +1 -0
  6. package/dist/agent/provider-registry.d.ts +6 -0
  7. package/dist/agent/provider-registry.d.ts.map +1 -1
  8. package/dist/agent/provider-registry.js +7 -14
  9. package/dist/agent/provider-registry.js.map +1 -1
  10. package/dist/config/daemon-config.d.ts +39 -0
  11. package/dist/config/daemon-config.d.ts.map +1 -1
  12. package/dist/config/daemon-config.js +59 -1
  13. package/dist/config/daemon-config.js.map +1 -1
  14. package/dist/daemon/bootstrap.d.ts +4 -0
  15. package/dist/daemon/bootstrap.d.ts.map +1 -1
  16. package/dist/daemon/bootstrap.js +20 -0
  17. package/dist/daemon/bootstrap.js.map +1 -1
  18. package/dist/extensions/curated-packs.d.ts +70 -0
  19. package/dist/extensions/curated-packs.d.ts.map +1 -0
  20. package/dist/extensions/curated-packs.js +206 -0
  21. package/dist/extensions/curated-packs.js.map +1 -0
  22. package/dist/extensions/extensions-rpc.d.ts +15 -0
  23. package/dist/extensions/extensions-rpc.d.ts.map +1 -0
  24. package/dist/extensions/extensions-rpc.js +76 -0
  25. package/dist/extensions/extensions-rpc.js.map +1 -0
  26. package/dist/extensions/extensions-service.d.ts +60 -0
  27. package/dist/extensions/extensions-service.d.ts.map +1 -0
  28. package/dist/extensions/extensions-service.js +145 -0
  29. package/dist/extensions/extensions-service.js.map +1 -0
  30. package/dist/extensions/extensions-state.d.ts +490 -0
  31. package/dist/extensions/extensions-state.d.ts.map +1 -0
  32. package/dist/extensions/extensions-state.js +114 -0
  33. package/dist/extensions/extensions-state.js.map +1 -0
  34. package/dist/extensions/index.d.ts +5 -0
  35. package/dist/extensions/index.d.ts.map +1 -0
  36. package/dist/extensions/index.js +10 -0
  37. package/dist/extensions/index.js.map +1 -0
  38. package/dist/extensions/sync-executor.d.ts +55 -0
  39. package/dist/extensions/sync-executor.d.ts.map +1 -0
  40. package/dist/extensions/sync-executor.js +158 -0
  41. package/dist/extensions/sync-executor.js.map +1 -0
  42. package/dist/extensions/sync-planner.d.ts +62 -0
  43. package/dist/extensions/sync-planner.d.ts.map +1 -0
  44. package/dist/extensions/sync-planner.js +103 -0
  45. package/dist/extensions/sync-planner.js.map +1 -0
  46. package/dist/extensions/wire.d.ts +7 -0
  47. package/dist/extensions/wire.d.ts.map +1 -0
  48. package/dist/extensions/wire.js +51 -0
  49. package/dist/extensions/wire.js.map +1 -0
  50. package/dist/index.d.ts +1 -0
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +3 -0
  53. package/dist/index.js.map +1 -1
  54. package/package.json +4 -4
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The curated-packs manifest — single source of truth for which Pi extensions Pi-Studio
3
+ * recommends (swe/features/preinstalled-extensions.md § The manifest).
4
+ *
5
+ * Pure data plus two tiny pure helpers: no filesystem, no process, no daemon dependency. Sources
6
+ * are deliberately **unpinned** (design tenet 3) — `pi update` skips pinned npm specs
7
+ * (`updateConfiguredSources`: `if (!parsed.pinned)`, pi 0.84.1 `package-manager.js:840`), so a pin
8
+ * here would permanently exclude that extension from the user's own updater. The guard test below
9
+ * makes that invariant mechanically unbreakable.
10
+ */
11
+ /** One curated entry: an unpinned pi source spec, the version it was added in, and an optional
12
+ * deprecation tombstone. Entries are never deleted — retire with `deprecated: true`. */
13
+ export interface CuratedEntry {
14
+ /** Unpinned pi spec: `npm:<name>` or `git:<url>` — no `@version`/`@ref` suffix. */
15
+ source: string;
16
+ /** Aligned workspace version on disk when the entry was added (docs/UI/audit only). */
17
+ addedIn: string;
18
+ /** Tombstone: never offered anew; existing installs are left untouched. */
19
+ deprecated?: boolean;
20
+ }
21
+ export interface CuratedPack {
22
+ title: string;
23
+ description: string;
24
+ packages: CuratedEntry[];
25
+ }
26
+ export type CuratedPackCatalog = Record<string, CuratedPack>;
27
+ export declare const CURATED_PACKS: {
28
+ core: {
29
+ title: string;
30
+ description: string;
31
+ packages: {
32
+ source: string;
33
+ addedIn: string;
34
+ }[];
35
+ };
36
+ };
37
+ export declare const SERVER_VERSION: string;
38
+ export interface ParsedSource {
39
+ kind: "npm" | "git";
40
+ /** Pi's own dedup key: npm package name, or git URL without ref. */
41
+ identity: string;
42
+ pinned: boolean;
43
+ }
44
+ /** Parse a pi source spec (`npm:<name>[@<version>]` or `git:<url>[@<ref>]`). Throws on any other
45
+ * scheme — every manifest entry must be one of the two pi understands. */
46
+ export declare function parseSource(source: string): ParsedSource;
47
+ /** `identityOf(source) === parseSource(source).identity` — pi's dedup key, ref/version stripped. */
48
+ export declare function identityOf(source: string): string;
49
+ /** `core` + the given slugs (deduped, order-stable, `core` always first), with unknown slugs
50
+ * reported for a caller-side `warn` rather than thrown. Entries are deduped by identity so a
51
+ * hypothetical identity reachable via two selected packs is never offered twice. */
52
+ export declare function selectEntries(catalog: CuratedPackCatalog, packs: readonly string[]): {
53
+ entries: {
54
+ pack: string;
55
+ entry: CuratedEntry;
56
+ }[];
57
+ unknownSlugs: string[];
58
+ };
59
+ export interface CatalogViolation {
60
+ pack: string;
61
+ source?: string;
62
+ message: string;
63
+ }
64
+ /** The manifest invariants from § The manifest: every source parses and is unpinned, no identity
65
+ * is shared across packs, pack keys are stable slugs with `core` present, every `addedIn` is
66
+ * valid semver `<=` `currentVersion`, and no source carries a placeholder. Returns every
67
+ * violation found (empty = a clean catalog) rather than throwing on the first one, so a guard
68
+ * test can assert the full violation set against a deliberately broken fixture. */
69
+ export declare function checkCatalogInvariants(catalog: CuratedPackCatalog, currentVersion?: string): CatalogViolation[];
70
+ //# sourceMappingURL=curated-packs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"curated-packs.d.ts","sourceRoot":"","sources":["../../src/extensions/curated-packs.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH;yFACyF;AACzF,MAAM,WAAW,YAAY;IAC3B,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;IACf,uFAAuF;IACvF,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAI7D,eAAO,MAAM,aAAa;;;;;;;;;CAiBI,CAAC;AAK/B,eAAO,MAAM,cAAc,EAAE,MACT,CAAC;AAErB,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;IACpB,oEAAoE;IACpE,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;CACjB;AAsCD;2EAC2E;AAC3E,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAgBxD;AAED,oGAAoG;AACpG,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;qFAEqF;AACrF,wBAAgB,aAAa,CAC3B,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB;IAAE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,YAAY,CAAA;KAAE,EAAE,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAAE,CAoB9E;AAuBD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;oFAIoF;AACpF,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,kBAAkB,EAC3B,cAAc,GAAE,MAAuB,GACtC,gBAAgB,EAAE,CAuEpB"}
@@ -0,0 +1,206 @@
1
+ import { createRequire } from "node:module";
2
+ // core is implicit and always selected; swe/science/data are future audience packs, added as pure
3
+ // data edits over time.
4
+ export const CURATED_PACKS = {
5
+ core: {
6
+ title: "Baseline",
7
+ description: "Recommended for everyone",
8
+ packages: [
9
+ // Background commands + attachable PTY/TUI sessions — long-running work without blocking a turn.
10
+ { source: "npm:@99percentpeople/pi-background-tasks", addedIn: "0.0.73" },
11
+ // Automatic memory-context injection: load, search, persist knowledge across sessions.
12
+ { source: "npm:pi-memctx", addedIn: "0.0.73" },
13
+ // Todo list for the model, rendered as a live overlay that survives /reload.
14
+ { source: "npm:@juicesharp/rpiv-todo", addedIn: "0.0.73" },
15
+ // Web search, URL fetch, repo cloning, PDF/YouTube extraction.
16
+ { source: "npm:pi-web-access", addedIn: "0.0.73" },
17
+ // Powerline-style status bar.
18
+ { source: "npm:pi-powerline-footer", addedIn: "0.0.73" },
19
+ ],
20
+ },
21
+ };
22
+ // Aligned workspace version, read from packages/server/package.json (never the root one — the
23
+ // root package.json deliberately carries no "version" field). This module sits two levels below
24
+ // the package root in both `src/` and compiled `dist/`, so the specifier is "../../package.json".
25
+ export const SERVER_VERSION = createRequire(import.meta.url)("../../package.json")
26
+ .version;
27
+ /** Split a pi git source's `repo[@ref]` shape (mirrors pi's own `splitRef`,
28
+ * `@earendil-works/pi-coding-agent/dist/utils/git.js`), so `pinned` is correct for scp-like
29
+ * (`git@host:path@ref`), URL (`https://host/path@ref`), and bare (`host/path@ref`) forms. */
30
+ function splitGitRef(url) {
31
+ const scpMatch = /^git@([^:]+):(.+)$/.exec(url);
32
+ if (scpMatch) {
33
+ const host = scpMatch[1] ?? "";
34
+ const pathWithMaybeRef = scpMatch[2] ?? "";
35
+ const at = pathWithMaybeRef.indexOf("@");
36
+ if (at <= 0)
37
+ return { repo: url };
38
+ const repoPath = pathWithMaybeRef.slice(0, at);
39
+ const ref = pathWithMaybeRef.slice(at + 1);
40
+ if (!repoPath || !ref)
41
+ return { repo: url };
42
+ return { repo: `git@${host}:${repoPath}`, ref };
43
+ }
44
+ if (url.includes("://")) {
45
+ try {
46
+ const parsed = new URL(url);
47
+ const pathWithMaybeRef = parsed.pathname.replace(/^\/+/, "");
48
+ const at = pathWithMaybeRef.indexOf("@");
49
+ if (at <= 0)
50
+ return { repo: url };
51
+ const repoPath = pathWithMaybeRef.slice(0, at);
52
+ const ref = pathWithMaybeRef.slice(at + 1);
53
+ if (!repoPath || !ref)
54
+ return { repo: url };
55
+ parsed.pathname = `/${repoPath}`;
56
+ return { repo: parsed.toString().replace(/\/$/, ""), ref };
57
+ }
58
+ catch {
59
+ return { repo: url };
60
+ }
61
+ }
62
+ // Bare host/path form (`pi install git:github.com/user/repo[@ref]`).
63
+ const at = url.lastIndexOf("@");
64
+ if (at > 0)
65
+ return { repo: url.slice(0, at), ref: url.slice(at + 1) };
66
+ return { repo: url };
67
+ }
68
+ /** Parse a pi source spec (`npm:<name>[@<version>]` or `git:<url>[@<ref>]`). Throws on any other
69
+ * scheme — every manifest entry must be one of the two pi understands. */
70
+ export function parseSource(source) {
71
+ if (source.startsWith("npm:")) {
72
+ const spec = source.slice("npm:".length);
73
+ // A scoped package's leading `@scope/` must not be mistaken for a version separator — only an
74
+ // `@` at index > 0 introduces a version.
75
+ const at = spec.lastIndexOf("@");
76
+ const hasVersion = at > 0;
77
+ const name = hasVersion ? spec.slice(0, at) : spec;
78
+ const version = hasVersion ? spec.slice(at + 1) : undefined;
79
+ return { kind: "npm", identity: name, pinned: Boolean(version) };
80
+ }
81
+ if (source.startsWith("git:")) {
82
+ const { repo, ref } = splitGitRef(source.slice("git:".length));
83
+ return { kind: "git", identity: repo, pinned: Boolean(ref) };
84
+ }
85
+ throw new Error(`unrecognized pi source spec (must start with "npm:" or "git:"): ${source}`);
86
+ }
87
+ /** `identityOf(source) === parseSource(source).identity` — pi's dedup key, ref/version stripped. */
88
+ export function identityOf(source) {
89
+ return parseSource(source).identity;
90
+ }
91
+ /** `core` + the given slugs (deduped, order-stable, `core` always first), with unknown slugs
92
+ * reported for a caller-side `warn` rather than thrown. Entries are deduped by identity so a
93
+ * hypothetical identity reachable via two selected packs is never offered twice. */
94
+ export function selectEntries(catalog, packs) {
95
+ const slugs = ["core", ...new Set(packs.filter((slug) => slug !== "core"))];
96
+ const entries = [];
97
+ const unknownSlugs = [];
98
+ const seenIdentities = new Set();
99
+ for (const slug of slugs) {
100
+ const pack = catalog[slug];
101
+ if (!pack) {
102
+ if (slug !== "core")
103
+ unknownSlugs.push(slug);
104
+ continue;
105
+ }
106
+ for (const entry of pack.packages) {
107
+ const identity = identityOf(entry.source);
108
+ if (seenIdentities.has(identity))
109
+ continue;
110
+ seenIdentities.add(identity);
111
+ entries.push({ pack: slug, entry });
112
+ }
113
+ }
114
+ return { entries, unknownSlugs };
115
+ }
116
+ // ---------------------------------------------------------------------------
117
+ // Manifest invariants (curated-packs.test.ts is the guard test that enforces these)
118
+ // ---------------------------------------------------------------------------
119
+ /** `"1.2.3"`-shaped, no pre-release/build metadata — the only form the manifest ever needs. */
120
+ const SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
121
+ /** `a <= b` for two `SEMVER_PATTERN`-shaped strings, compared numerically per segment. */
122
+ function semverLte(a, b) {
123
+ const pa = a.split(".").map(Number);
124
+ const pb = b.split(".").map(Number);
125
+ for (let i = 0; i < 3; i++) {
126
+ const da = pa[i] ?? 0;
127
+ const db = pb[i] ?? 0;
128
+ if (da !== db)
129
+ return da < db;
130
+ }
131
+ return true;
132
+ }
133
+ const PLACEHOLDER_PATTERN = /<(ref|version|pkg)>/;
134
+ /** The manifest invariants from § The manifest: every source parses and is unpinned, no identity
135
+ * is shared across packs, pack keys are stable slugs with `core` present, every `addedIn` is
136
+ * valid semver `<=` `currentVersion`, and no source carries a placeholder. Returns every
137
+ * violation found (empty = a clean catalog) rather than throwing on the first one, so a guard
138
+ * test can assert the full violation set against a deliberately broken fixture. */
139
+ export function checkCatalogInvariants(catalog, currentVersion = SERVER_VERSION) {
140
+ const violations = [];
141
+ const identityOwner = new Map();
142
+ if (!("core" in catalog)) {
143
+ violations.push({ pack: "core", message: "manifest must define a 'core' pack" });
144
+ }
145
+ for (const [packKey, pack] of Object.entries(catalog)) {
146
+ if (!/^[a-z][a-z0-9]*$/.test(packKey)) {
147
+ violations.push({ pack: packKey, message: `pack key "${packKey}" is not a stable slug` });
148
+ }
149
+ for (const entry of pack.packages) {
150
+ if (PLACEHOLDER_PATTERN.test(entry.source)) {
151
+ violations.push({
152
+ pack: packKey,
153
+ source: entry.source,
154
+ message: "source contains a placeholder (<ref>/<version>/<pkg>)",
155
+ });
156
+ continue;
157
+ }
158
+ let parsed;
159
+ try {
160
+ parsed = parseSource(entry.source);
161
+ }
162
+ catch (error) {
163
+ violations.push({
164
+ pack: packKey,
165
+ source: entry.source,
166
+ message: error instanceof Error ? error.message : String(error),
167
+ });
168
+ continue;
169
+ }
170
+ if (parsed.pinned) {
171
+ violations.push({
172
+ pack: packKey,
173
+ source: entry.source,
174
+ message: "source carries a version/ref pin — sources must stay unpinned",
175
+ });
176
+ }
177
+ const owner = identityOwner.get(parsed.identity);
178
+ if (owner && owner !== packKey) {
179
+ violations.push({
180
+ pack: packKey,
181
+ source: entry.source,
182
+ message: `identity "${parsed.identity}" also appears in pack "${owner}" (identities must be disjoint across packs)`,
183
+ });
184
+ }
185
+ else if (!owner) {
186
+ identityOwner.set(parsed.identity, packKey);
187
+ }
188
+ if (!SEMVER_PATTERN.test(entry.addedIn)) {
189
+ violations.push({
190
+ pack: packKey,
191
+ source: entry.source,
192
+ message: `addedIn "${entry.addedIn}" is not valid semver`,
193
+ });
194
+ }
195
+ else if (!semverLte(entry.addedIn, currentVersion)) {
196
+ violations.push({
197
+ pack: packKey,
198
+ source: entry.source,
199
+ message: `addedIn "${entry.addedIn}" is newer than the current version "${currentVersion}"`,
200
+ });
201
+ }
202
+ }
203
+ }
204
+ return violations;
205
+ }
206
+ //# sourceMappingURL=curated-packs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"curated-packs.js","sourceRoot":"","sources":["../../src/extensions/curated-packs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { Logger } from "../logging/logger.js";
2
+ import type { HandlerRegistry } from "../ws/router.js";
3
+ import type { ExtensionsService } from "./extensions-service.js";
4
+ /**
5
+ * `extension_packs_list_request`/`extension_packs_set_request` (swe/features/
6
+ * preinstalled-extensions.md § RPC surface). Thin orchestration only — every decision (statuses,
7
+ * installs) already lives in the planner/executor behind `ExtensionsService`; this module maps
8
+ * server types to wire types and nothing else.
9
+ */
10
+ export interface ExtensionsRpcDeps {
11
+ service: ExtensionsService;
12
+ logger?: Logger;
13
+ }
14
+ export declare function registerExtensionsHandlers(registry: HandlerRegistry, deps: ExtensionsRpcDeps): void;
15
+ //# sourceMappingURL=extensions-rpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensions-rpc.d.ts","sourceRoot":"","sources":["../../src/extensions/extensions-rpc.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAGrF;;;;;GAKG;AAEH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAsBD,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,iBAAiB,GACtB,IAAI,CAuEN"}
@@ -0,0 +1,76 @@
1
+ import { CURATED_PACKS, selectEntries } from "./curated-packs.js";
2
+ import { toExtensionPackInfoList } from "./wire.js";
3
+ /** The list-response fields, shared verbatim by both the list response and the set response
4
+ * (which carries them plus `ok`/`error`/`report`) — computed fresh from `describe()` so a `set`
5
+ * response reflects state *after* the sync it triggered completes, never a stale pre-sync view. */
6
+ function toListFields(described) {
7
+ return {
8
+ autoSync: described.autoSync,
9
+ selected: described.selected,
10
+ packs: toExtensionPackInfoList(CURATED_PACKS, described.entries),
11
+ ...(described.lastSync ? { lastSync: described.lastSync } : {}),
12
+ };
13
+ }
14
+ /** `ctx.message` is an unvalidated `Record<string, unknown>` (`ws/router.ts`), so the handler must
15
+ * check `packs`'s shape itself rather than trust the wire schema a conforming client used. */
16
+ function isSlugArray(value) {
17
+ return Array.isArray(value) && value.every((slug) => typeof slug === "string");
18
+ }
19
+ export function registerExtensionsHandlers(registry, deps) {
20
+ const { service, logger } = deps;
21
+ registry.register("extension_packs_list_request", async () => {
22
+ const described = await service.describe();
23
+ return { type: "extension_packs_list_response", ...toListFields(described) };
24
+ });
25
+ registry.register("extension_packs_set_request", async (ctx) => {
26
+ const packs = ctx.message.packs;
27
+ // Absent `packs` ⇒ the manual-sync trigger: no validation, no persistence, ungated sync.
28
+ if (packs === undefined) {
29
+ const report = await service.sync("manual");
30
+ const described = await service.describe();
31
+ return {
32
+ type: "extension_packs_set_response",
33
+ ...toListFields(described),
34
+ ok: true,
35
+ report,
36
+ };
37
+ }
38
+ // A malformed `packs` is a domain failure, NOT a silent "deselect everything": coercing it
39
+ // would persist an empty selection and sync over it. Only a client bypassing the protocol
40
+ // schema (`packs: z.array(z.string()).optional()`) can send this, so this is
41
+ // defense-in-depth — but the daemon must never act on a shape it cannot read.
42
+ if (!isSlugArray(packs)) {
43
+ logger?.warn({ packs }, "extension_packs_set_request: rejecting malformed packs");
44
+ const described = await service.describe();
45
+ return {
46
+ type: "extension_packs_set_response",
47
+ ...toListFields(described),
48
+ ok: false,
49
+ error: "invalid packs: expected an array of pack slugs",
50
+ };
51
+ }
52
+ const { unknownSlugs } = selectEntries(CURATED_PACKS, packs);
53
+ if (unknownSlugs.length > 0) {
54
+ logger?.warn({ unknownSlugs }, "extension_packs_set_request: rejecting unknown pack slug(s)");
55
+ // Domain failure ⇒ `ok: false` + `error`, never `rpc_error` (transport-level only). No
56
+ // persistence, no sync — the current (unchanged) state is still echoed back.
57
+ const described = await service.describe();
58
+ return {
59
+ type: "extension_packs_set_response",
60
+ ...toListFields(described),
61
+ ok: false,
62
+ error: `unknown pack: ${unknownSlugs.join(", ")}`,
63
+ };
64
+ }
65
+ await service.setSelectedPacks(packs);
66
+ const report = await service.sync("selection");
67
+ const described = await service.describe();
68
+ return {
69
+ type: "extension_packs_set_response",
70
+ ...toListFields(described),
71
+ ok: true,
72
+ report,
73
+ };
74
+ });
75
+ }
76
+ //# sourceMappingURL=extensions-rpc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensions-rpc.js","sourceRoot":"","sources":["../../src/extensions/extensions-rpc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import { type PersistedConfig } from "../config/daemon-config.js";
2
+ import type { Logger } from "../logging/logger.js";
3
+ import { type DescribedEntry } from "./sync-planner.js";
4
+ import { type InstallSpawn, type SyncReport } from "./sync-executor.js";
5
+ /**
6
+ * Orchestration only: reads `settings.json` + state, hands them to the pure planner, runs the plan
7
+ * through the executor, persists the `lastSync` summary, and logs the loud-but-not-alarming
8
+ * first-sync report (swe/features/preinstalled-extensions.md § Lifecycle, § Concurrency).
9
+ * Every real decision lives in `sync-planner.ts`/`sync-executor.ts` — this class never decides
10
+ * *what* to install, only *when* and *whether*.
11
+ */
12
+ export type SyncReason = "bootstrap" | "manual" | "selection";
13
+ export type { DescribedEntry };
14
+ export interface ExtensionsDescribe {
15
+ autoSync: boolean;
16
+ selected: string[];
17
+ entries: DescribedEntry[];
18
+ /** `outcome` is read back as a plain `string`, not the narrow `SyncReport["outcome"]` union —
19
+ * the persisted field is forward-compat (a future daemon's new outcome value must round-trip
20
+ * through an older one without a type mismatch). */
21
+ lastSync?: {
22
+ at: string;
23
+ outcome: string;
24
+ };
25
+ }
26
+ export declare class ExtensionsService {
27
+ private readonly home;
28
+ private readonly config;
29
+ private readonly logger;
30
+ private readonly spawn;
31
+ /** Where `setSelectedPacks` persists — defaults to `<home>/config.json`, mirroring bootstrap's
32
+ * own default, so tests that don't care about persistence never need to pass one. */
33
+ private readonly configPath;
34
+ private readonly mutex;
35
+ constructor(deps: {
36
+ home: string;
37
+ config: PersistedConfig;
38
+ logger: Logger;
39
+ spawn?: InstallSpawn;
40
+ configPath?: string;
41
+ });
42
+ sync(reason: SyncReason): Promise<SyncReport>;
43
+ /**
44
+ * Updates the service's own selection view *and* persists it, in that order — an
45
+ * in-memory-correct daemon must survive a disk write failure (logged, never thrown; the running
46
+ * daemon keeps serving the new selection even if the write failed). `core` is never stored (it
47
+ * is implicit); dedupes and preserves caller order otherwise.
48
+ */
49
+ setSelectedPacks(packs: readonly string[]): Promise<void>;
50
+ /** Dry-run: the same planner, no writes. One code path for "what we'd do" and "what we
51
+ * report" — this must never drift from `sync()`'s own plan computation. */
52
+ describe(): Promise<ExtensionsDescribe>;
53
+ /** Persists only the `{ at, outcome }` summary (never the full report) so a freshly-booted
54
+ * daemon that hasn't synced yet can still report the previous run via `describe()`. Re-reads
55
+ * state first so this never clobbers what `executePlan` just wrote per-action; silently no-ops
56
+ * if the state file is unreadable (never rewrite a corrupt file, even for this). */
57
+ private recordLastSync;
58
+ private runSync;
59
+ }
60
+ //# sourceMappingURL=extensions-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensions-service.d.ts","sourceRoot":"","sources":["../../src/extensions/extensions-service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,4BAA4B,CAAC;AACzF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAUnD,OAAO,EAA8B,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,EAAe,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErF;;;;;;GAMG;AAEH,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE9D,YAAY,EAAE,cAAc,EAAE,CAAC;AAE/B,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B;;yDAEqD;IACrD,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5C;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA2B;IACjD;0FACsF;IACtF,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAGpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAE1C,YAAY,IAAI,EAAE;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,eAAe,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,YAAY,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,EAMA;IAED,IAAI,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAE5C;IAED;;;;;OAKG;IACG,gBAAgB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAW9D;IAED;gFAC4E;IACtE,QAAQ,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAqB5C;IAED;;;yFAGqF;YACvE,cAAc;YAYd,OAAO;CA2EtB"}
@@ -0,0 +1,145 @@
1
+ import { join } from "node:path";
2
+ import { persistExtensionPacks } from "../config/daemon-config.js";
3
+ import { createLimiter } from "../util/concurrency.js";
4
+ import { CURATED_PACKS, selectEntries } from "./curated-packs.js";
5
+ import { effectivePiHomeKey, loadExtensionsState, readPiSettingsPackages, saveExtensionsState, } from "./extensions-state.js";
6
+ import { attachLastErrors, planSync } from "./sync-planner.js";
7
+ import { executePlan } from "./sync-executor.js";
8
+ export class ExtensionsService {
9
+ home;
10
+ config;
11
+ logger;
12
+ spawn;
13
+ /** Where `setSelectedPacks` persists — defaults to `<home>/config.json`, mirroring bootstrap's
14
+ * own default, so tests that don't care about persistence never need to pass one. */
15
+ configPath;
16
+ // In-process mutex (concurrency-1 limiter): a sync requested while one runs waits for it, then
17
+ // gets its own fresh run — never the same report, since config may have changed in between.
18
+ mutex = createLimiter(1);
19
+ constructor(deps) {
20
+ this.home = deps.home;
21
+ this.config = deps.config;
22
+ this.logger = deps.logger;
23
+ this.spawn = deps.spawn;
24
+ this.configPath = deps.configPath ?? join(deps.home, "config.json");
25
+ }
26
+ sync(reason) {
27
+ return this.mutex(() => this.runSync(reason));
28
+ }
29
+ /**
30
+ * Updates the service's own selection view *and* persists it, in that order — an
31
+ * in-memory-correct daemon must survive a disk write failure (logged, never thrown; the running
32
+ * daemon keeps serving the new selection even if the write failed). `core` is never stored (it
33
+ * is implicit); dedupes and preserves caller order otherwise.
34
+ */
35
+ async setSelectedPacks(packs) {
36
+ const deduped = [...new Set(packs.filter((p) => p !== "core"))];
37
+ this.config.daemon.extensions.packs = deduped;
38
+ try {
39
+ await persistExtensionPacks(this.configPath, deduped);
40
+ }
41
+ catch (err) {
42
+ this.logger.error({ err: err?.message ?? String(err) }, "failed to persist extension pack selection to config.json");
43
+ }
44
+ }
45
+ /** Dry-run: the same planner, no writes. One code path for "what we'd do" and "what we
46
+ * report" — this must never drift from `sync()`'s own plan computation. */
47
+ async describe() {
48
+ const piHomeKey = effectivePiHomeKey(this.config);
49
+ const [state, settings] = await Promise.all([
50
+ loadExtensionsState(this.home),
51
+ readPiSettingsPackages(piHomeKey),
52
+ ]);
53
+ const piHomeState = state === "unreadable" ? "unreadable" : (state.piHomes[piHomeKey] ?? emptyPiHomeState());
54
+ const packs = this.config.daemon.extensions.packs;
55
+ const { entries } = planSync({
56
+ catalog: CURATED_PACKS,
57
+ packs,
58
+ state: piHomeState,
59
+ settingsPackages: settings.packages,
60
+ });
61
+ return {
62
+ autoSync: this.config.daemon.extensions.autoSync,
63
+ selected: [...packs],
64
+ entries: attachLastErrors(entries, piHomeState),
65
+ lastSync: state === "unreadable" ? undefined : state.piHomes[piHomeKey]?.lastSync,
66
+ };
67
+ }
68
+ /** Persists only the `{ at, outcome }` summary (never the full report) so a freshly-booted
69
+ * daemon that hasn't synced yet can still report the previous run via `describe()`. Re-reads
70
+ * state first so this never clobbers what `executePlan` just wrote per-action; silently no-ops
71
+ * if the state file is unreadable (never rewrite a corrupt file, even for this). */
72
+ async recordLastSync(piHomeKey, summary) {
73
+ const loaded = await loadExtensionsState(this.home);
74
+ if (loaded === "unreadable")
75
+ return;
76
+ const slice = loaded.piHomes[piHomeKey] ?? emptyPiHomeState();
77
+ slice.lastSync = summary;
78
+ loaded.piHomes[piHomeKey] = slice;
79
+ await saveExtensionsState(this.home, loaded);
80
+ }
81
+ async runSync(reason) {
82
+ // `autoSync: false` gates bootstrap/selection entirely — Pi-Studio never touches pi's settings.
83
+ // `manual` is the deliberate escape hatch (`extensions sync` must work even with the switch
84
+ // off); no plan is even computed here, so no lastSync write either — nothing was attempted.
85
+ if (!this.config.daemon.extensions.autoSync && reason !== "manual") {
86
+ this.logger.debug({ reason }, "extensions sync skipped — daemon.extensions.autoSync is false");
87
+ return { at: new Date().toISOString(), outcome: "noop", installed: [], failures: [] };
88
+ }
89
+ const piHomeKey = effectivePiHomeKey(this.config);
90
+ const settings = await readPiSettingsPackages(piHomeKey);
91
+ if (!settings.ok) {
92
+ this.logger.warn({ piHomeKey }, "settings.json is malformed — extensions sync skipped for this pi-home");
93
+ const report = {
94
+ at: new Date().toISOString(),
95
+ outcome: "skipped",
96
+ installed: [],
97
+ failures: [],
98
+ };
99
+ await this.recordLastSync(piHomeKey, { at: report.at, outcome: report.outcome });
100
+ return report;
101
+ }
102
+ const state = await loadExtensionsState(this.home);
103
+ if (state === "unreadable") {
104
+ this.logger.error({ piHomeKey }, "extensions-state.json is unreadable — extensions sync skipped");
105
+ // Never rewrite the corrupt file — not even to record this attempt as lastSync.
106
+ return { at: new Date().toISOString(), outcome: "skipped", installed: [], failures: [] };
107
+ }
108
+ const packs = this.config.daemon.extensions.packs;
109
+ const { unknownSlugs } = selectEntries(CURATED_PACKS, packs);
110
+ if (unknownSlugs.length > 0) {
111
+ this.logger.warn({ unknownSlugs }, "ignoring unknown extension pack slug(s) in config");
112
+ }
113
+ const piHomeState = state.piHomes[piHomeKey] ?? emptyPiHomeState();
114
+ const plan = planSync({
115
+ catalog: CURATED_PACKS,
116
+ packs,
117
+ state: piHomeState,
118
+ settingsPackages: settings.packages,
119
+ });
120
+ const report = await executePlan(plan, {
121
+ home: this.home,
122
+ piHomeKey,
123
+ config: this.config,
124
+ spawn: this.spawn,
125
+ logger: this.logger,
126
+ });
127
+ if (plan.actions.length > 0) {
128
+ const failedSources = report.failures.map((f) => f.source).join(", ");
129
+ const summary = `installed ${report.installed.length} of ${plan.actions.length} recommended extensions ` +
130
+ `into ${piHomeKey}; ${report.failures.length} failed` +
131
+ (report.failures.length > 0 ? `: ${failedSources} (will retry on next start)` : "") +
132
+ " — disable via daemon.extensions.autoSync=false / PI_STUDIO_EXTENSIONS_AUTOSYNC=false";
133
+ if (report.outcome === "partial" || report.outcome === "failed")
134
+ this.logger.warn(summary);
135
+ else
136
+ this.logger.info(summary);
137
+ }
138
+ await this.recordLastSync(piHomeKey, { at: report.at, outcome: report.outcome });
139
+ return report;
140
+ }
141
+ }
142
+ function emptyPiHomeState() {
143
+ return { offered: {}, failures: {} };
144
+ }
145
+ //# sourceMappingURL=extensions-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensions-service.js","sourceRoot":"","sources":["../../src/extensions/extensions-service.ts"],"names":[],"mappings":"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"}