@agimon-ai/po-config 0.0.0-alpha.113 → 0.0.0-alpha.114
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/extensions/server.cjs +1 -1
- package/dist/extensions/server.mjs +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{settingsApi-C_rqLkZZ.cjs → settingsApi-CiYuZH5P.cjs} +23 -15
- package/dist/settingsApi-CiYuZH5P.cjs.map +1 -0
- package/dist/{settingsApi-r4lj6nnG.mjs → settingsApi-DQ0Kf7TF.mjs} +27 -19
- package/dist/settingsApi-DQ0Kf7TF.mjs.map +1 -0
- package/package.json +4 -4
- package/dist/settingsApi-C_rqLkZZ.cjs.map +0 -1
- package/dist/settingsApi-r4lj6nnG.mjs.map +0 -1
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_settingsApi = require("../settingsApi-
|
|
5
|
+
const require_settingsApi = require("../settingsApi-CiYuZH5P.cjs");
|
|
6
6
|
const require_sessionConfig = require("../sessionConfig-DDpiIhIL.cjs");
|
|
7
7
|
let _agimon_ai_po_core_platform = require("@agimon-ai/po-core/platform");
|
|
8
8
|
let _agimon_ai_po_core_mcpProjection = require("@agimon-ai/po-core/mcpProjection");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as createSettingsApi } from "../settingsApi-
|
|
1
|
+
import { t as createSettingsApi } from "../settingsApi-DQ0Kf7TF.mjs";
|
|
2
2
|
import { d as providePoConfigContext, o as createNativePoConfigContext } from "../sessionConfig-DB-c4mPl.mjs";
|
|
3
3
|
import { BACKGROUND_CONTEXT, requirePoFs, withAbortSignal } from "@agimon-ai/po-core/platform";
|
|
4
4
|
import { PO_MCP_PROJECTION_SERVICE, createDisabledPoMcpProjection, createPoMcpProjectionService, isPoMcpProjection } from "@agimon-ai/po-core/mcpProjection";
|
package/dist/index.cjs
CHANGED
|
@@ -9,7 +9,7 @@ const require_layeredConfig = require("./layeredConfig-BwWQJbk8.cjs");
|
|
|
9
9
|
const require_domains = require("./domains-Dg4YAym-.cjs");
|
|
10
10
|
const require_harnessState = require("./harnessState-CQBNDL1D.cjs");
|
|
11
11
|
const require_harnessStore = require("./harnessStore-Dgn3T4sR.cjs");
|
|
12
|
-
const require_settingsApi = require("./settingsApi-
|
|
12
|
+
const require_settingsApi = require("./settingsApi-CiYuZH5P.cjs");
|
|
13
13
|
const require_piConfig = require("./piConfig-cluCie5i.cjs");
|
|
14
14
|
const require_majorModes = require("./majorModes-BmRJ4RvP.cjs");
|
|
15
15
|
const require_profiles = require("./profiles-D_omhOvM.cjs");
|
package/dist/index.d.mts
CHANGED
|
@@ -19,7 +19,7 @@ import { PoApi } from "@agimon-ai/po-core/packageApi";
|
|
|
19
19
|
//#region src/services/settingsApi/index.d.ts
|
|
20
20
|
/**
|
|
21
21
|
* Config handlers are bound to their mount. Request paths cannot choose a different repository.
|
|
22
|
-
*
|
|
22
|
+
* Reads, writes and image settings all go through the host's FileSystem.
|
|
23
23
|
*/
|
|
24
24
|
export declare const createSettingsApi: (fs: FileSystem) => PoApi;
|
|
25
25
|
//#endregion
|
package/dist/index.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { r as PO_DIR } from "./layeredConfig-gg8cI8jj.mjs";
|
|
|
8
8
|
import { _ as findPluginManifestPathAsync, b as isRemotePluginSource, c as resolvePluginDirectories, g as expandDomainNames, h as domainCompletionPrefix, l as resolvePluginEntries, m as domainCompletionItems, o as listDomainNames, s as loadDomains, u as resolveSharedSkills, v as findPluginManifestPath, x as pluginDirectoryForSource, y as MARKETPLACE_MANIFEST_RELATIVE_PATHS } from "./domains-D5M1H3rX.mjs";
|
|
9
9
|
import { i as readHarnessState, n as HARNESS_STATE_KEYS, r as projectHarnessEnvironment, t as FILE_ONLY_STATE_FIELDS } from "./harnessState-ufE-npBa.mjs";
|
|
10
10
|
import { _ as HARNESS_STATE_POINTER, a as requireHarnessPaths, d as getHarnessState, f as getSessionHarnessState, g as updateHarnessState, h as snapshotHarnessState, l as createHarnessSession, m as restoreHarnessStateSnapshot, o as requireHarnessRoot, p as loadHarnessState, r as harnessRoot, u as disposeHarnessState, v as resetHarnessStore } from "./harnessStore-yqKUx-kP.mjs";
|
|
11
|
-
import { t as createSettingsApi } from "./settingsApi-
|
|
11
|
+
import { t as createSettingsApi } from "./settingsApi-DQ0Kf7TF.mjs";
|
|
12
12
|
import { a as loadPiConfigAsync, c as piConfigPaths, d as MIN_IMAGE_MAX_DIMENSION, f as clampImageMaxDimension, i as loadPiConfig, l as piImageSettingsPath, o as loadPiImageSettings, p as parsePiImageSettings, s as savePiImageSettings, u as DEFAULT_IMAGE_MAX_DIMENSION } from "./piConfig-Bq36PQFF.mjs";
|
|
13
13
|
import { a as loadMajorModesConfigLenient, c as layerEntries, i as loadMajorModesConfig, l as resolveLayers, r as readPackageConfigurations, s as isLocalPackageSpecifier, u as resolvePackageConfigurations } from "./majorModes-CBTXMJZb.mjs";
|
|
14
14
|
import { _ as replaceProfileEnvironment, f as loadProfiles, h as applyProfileEnvironment, l as buildPersonaPrompt, m as PERSONA_FILES, p as resolveProfile, u as listProfileNames } from "./profiles-lG_t2vKD.mjs";
|
|
@@ -139,6 +139,13 @@ async function writeDefaultPreset(fs, homeDirectory, environment, name) {
|
|
|
139
139
|
}
|
|
140
140
|
//#endregion
|
|
141
141
|
//#region src/services/settings/index.ts
|
|
142
|
+
/** The mount's admitted home and environment, so no reader falls back to the host's. */
|
|
143
|
+
function readOptions(homeDirectory, environment) {
|
|
144
|
+
return {
|
|
145
|
+
...homeDirectory === void 0 ? {} : { homeDirectory },
|
|
146
|
+
environment
|
|
147
|
+
};
|
|
148
|
+
}
|
|
142
149
|
function isRecord(value) {
|
|
143
150
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
144
151
|
}
|
|
@@ -191,12 +198,13 @@ function configuredSelectionValue(layers, key, effective) {
|
|
|
191
198
|
origin
|
|
192
199
|
};
|
|
193
200
|
}
|
|
194
|
-
function repositorySettingsView(repository, homeDirectory, environment) {
|
|
201
|
+
async function repositorySettingsView(fs, repository, homeDirectory, environment, io) {
|
|
195
202
|
const repoRoot = repository.path;
|
|
196
|
-
const
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
const
|
|
203
|
+
const options = readOptions(homeDirectory, environment);
|
|
204
|
+
const layers = await require_config.readPoConfigLayers(fs, repoRoot, io, options);
|
|
205
|
+
const modes = await require_majorModes.readMajorModesConfig(fs, repoRoot, io, options);
|
|
206
|
+
const domains = await require_domains.loadDomainsAsync(fs, repoRoot, io, homeDirectory);
|
|
207
|
+
const profiles = await require_profiles.loadProfilesAsync(fs, repoRoot, io, homeDirectory);
|
|
200
208
|
const configured = layers.effective.selection;
|
|
201
209
|
const effectiveMajorMode = configured?.majorMode ?? modes.defaultMajorMode;
|
|
202
210
|
const effectiveDomains = configured?.domains ?? require_domains.defaultDomainsForMajorMode(effectiveMajorMode, {}, domains.defaultDomains);
|
|
@@ -275,9 +283,9 @@ function selectionEdits(changes) {
|
|
|
275
283
|
...value === null ? {} : { value }
|
|
276
284
|
}));
|
|
277
285
|
}
|
|
278
|
-
function configView(context, keys) {
|
|
286
|
+
async function configView(fs, context, keys, io) {
|
|
279
287
|
const root = context.scope === "workspace" ? context.workspaceRoot : void 0;
|
|
280
|
-
const layers = require_config.
|
|
288
|
+
const layers = await require_config.readPoConfigLayers(fs, root, io, readOptions(context.homeDirectory, context.environment ?? {}));
|
|
281
289
|
const values = {};
|
|
282
290
|
for (const key of keys) {
|
|
283
291
|
const keyPath = key.split(".");
|
|
@@ -301,7 +309,7 @@ function configView(context, keys) {
|
|
|
301
309
|
//#region src/services/settingsApi/index.ts
|
|
302
310
|
/**
|
|
303
311
|
* Config handlers are bound to their mount. Request paths cannot choose a different repository.
|
|
304
|
-
*
|
|
312
|
+
* Reads, writes and image settings all go through the host's FileSystem.
|
|
305
313
|
*/
|
|
306
314
|
const createSettingsApi = (fs) => ({
|
|
307
315
|
basePath: "settings",
|
|
@@ -335,9 +343,9 @@ const createSettingsApi = (fs) => ({
|
|
|
335
343
|
*/
|
|
336
344
|
const addresses = (route) => url.pathname === route.path && request.method === route.method;
|
|
337
345
|
const currentRepository = repository();
|
|
338
|
-
if (addresses(apiRoutes_default.config)) return Response.json(configView(context, url.searchParams.getAll("key")));
|
|
346
|
+
if (addresses(apiRoutes_default.config)) return Response.json(await configView(fs, context, url.searchParams.getAll("key"), io));
|
|
339
347
|
if (addresses(apiRoutes_default.repositories)) return Response.json({ repositories: context.repositories?.() ?? [] });
|
|
340
|
-
if (addresses(apiRoutes_default.repository)) return currentRepository ? Response.json(repositorySettingsView(currentRepository, homeDirectory, environment)) : Response.json({ error: "Choose a workspace." }, { status: 404 });
|
|
348
|
+
if (addresses(apiRoutes_default.repository)) return currentRepository ? Response.json(await repositorySettingsView(fs, currentRepository, homeDirectory, environment, io)) : Response.json({ error: "Choose a workspace." }, { status: 404 });
|
|
341
349
|
if (url.pathname === apiRoutes_default.images.path && context.scope === "global") {
|
|
342
350
|
if (request.method === apiRoutes_default.images.method) return Response.json(await imageView());
|
|
343
351
|
if (request.method === apiRoutes_default.saveImages.method) {
|
|
@@ -367,7 +375,7 @@ const createSettingsApi = (fs) => ({
|
|
|
367
375
|
const body = parseSelectionWrite(await request.json());
|
|
368
376
|
if (!body) return Response.json({ error: "A selection write needs typed changes and a file hash." }, { status: 400 });
|
|
369
377
|
if (!currentRepository || body.repositoryId !== currentRepository.id) return Response.json({ error: "Workspace not found in this mount." }, { status: 404 });
|
|
370
|
-
const before = repositorySettingsView(currentRepository, homeDirectory, environment);
|
|
378
|
+
const before = await repositorySettingsView(fs, currentRepository, homeDirectory, environment, io);
|
|
371
379
|
if (before.hash !== body.expectedHash) return Response.json({
|
|
372
380
|
error: "The repository config changed since it was read.",
|
|
373
381
|
hash: before.hash
|
|
@@ -376,7 +384,7 @@ const createSettingsApi = (fs) => ({
|
|
|
376
384
|
if (invalid) return Response.json({ error: invalid }, { status: 422 });
|
|
377
385
|
await require_configWriter.writePoConfigValuesAsync(fs, require_core.repositoryPoConfigPath(currentRepository.path), selectionEdits(body.changes), io, { scope: "repository" });
|
|
378
386
|
context.configurationChanged?.();
|
|
379
|
-
return Response.json(repositorySettingsView(currentRepository, homeDirectory, environment));
|
|
387
|
+
return Response.json(await repositorySettingsView(fs, currentRepository, homeDirectory, environment, io));
|
|
380
388
|
}
|
|
381
389
|
if (addresses(apiRoutes_default.value)) {
|
|
382
390
|
const body = parseWrite(await request.json());
|
|
@@ -387,7 +395,7 @@ const createSettingsApi = (fs) => ({
|
|
|
387
395
|
const allowed = require_configPolicy.configScopeOf(edit.keyPath);
|
|
388
396
|
return allowed !== "both" && allowed !== scope;
|
|
389
397
|
})) return Response.json({ error: "This key cannot be set at this scope." }, { status: 409 });
|
|
390
|
-
const before = configView(context, []);
|
|
398
|
+
const before = await configView(fs, context, [], io);
|
|
391
399
|
if (before.hashes[scope] !== body.expectedHash) return Response.json({
|
|
392
400
|
error: "The config changed since it was read.",
|
|
393
401
|
hash: before.hashes[scope]
|
|
@@ -398,7 +406,7 @@ const createSettingsApi = (fs) => ({
|
|
|
398
406
|
...value === null ? {} : { value }
|
|
399
407
|
})), io, { scope });
|
|
400
408
|
context.configurationChanged?.();
|
|
401
|
-
return Response.json(configView(context, body.edits.map((edit) => edit.keyPath.join("."))));
|
|
409
|
+
return Response.json(await configView(fs, context, body.edits.map((edit) => edit.keyPath.join(".")), io));
|
|
402
410
|
}
|
|
403
411
|
return Response.json({ error: "Configuration route not found." }, { status: 404 });
|
|
404
412
|
};
|
|
@@ -430,4 +438,4 @@ Object.defineProperty(exports, "createSettingsApi", {
|
|
|
430
438
|
}
|
|
431
439
|
});
|
|
432
440
|
|
|
433
|
-
//# sourceMappingURL=settingsApi-
|
|
441
|
+
//# sourceMappingURL=settingsApi-CiYuZH5P.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settingsApi-CiYuZH5P.cjs","names":["defineApiRoutes","apiResponse","BACKGROUND_CONTEXT","readMajorModesConfig","path","globalPoConfigDirectory","MODES_FILE","readTextIfExists","parseDocument","isMap","writeFileAtomic","repositoryPoConfigPath","globalPoConfigPath","readPoConfigLayers","readMajorModesConfig","loadDomainsAsync","loadProfilesAsync","defaultDomainsForMajorMode","configScopeOf","BACKGROUND_CONTEXT","readPiImageSettings","DEFAULT_IMAGE_MAX_DIMENSION","routes","savePiImageSettingsAsync","writePoConfigValuesAsync","repositoryPoConfigPath","configScopeOf"],"sources":["../src/constants/settings.ts","../src/types/apiRoutes.ts","../src/services/presets/index.ts","../src/services/settings/index.ts","../src/services/settingsApi/index.ts"],"sourcesContent":["export const KEY_SEPARATOR = '.';\n\n/** The repeatable query parameter naming which keys a read is about. */\nexport const KEY_QUERY_PARAM = 'key';\n\nexport const SELECTION_KEYS = {\n majorMode: ['selection', 'majorMode'],\n domains: ['selection', 'domains'],\n profile: ['selection', 'profile'],\n} as const;\n","import { apiResponse, defineApiRoutes } from '@agimon-ai/po-core/web';\n\nimport { KEY_QUERY_PARAM } from '../constants/settings';\nimport type {\n RepositorySettingsView,\n SettingsConfigView,\n SettingsImagesView,\n SettingsPresetsView,\n SettingsRepository,\n} from './settings';\n\n/**\n * This package's routes, as data.\n *\n * No scope and no base path. The build reads both off\n * `src/extensions/(backend)/api/settings/`, so the mount is stated once, by the\n * folder that creates it. `settings` is the host's own API: it answers at\n * `/api/settings` and `/api/workspaces/<id>/settings`, beside `/plugins` rather\n * than under it, and the client knows that from the base path alone.\n *\n * This is the one file the mount's dispatcher and, once the client crosses the\n * package boundary, the cockpit both read, so a path cannot move on one side\n * alone.\n */\nexport default defineApiRoutes({\n /** The mount itself: the selected keys and the hash of each file they came from. */\n config: {\n method: 'GET',\n path: '/',\n /** Repeatable. The page also sends `repoRoot`, which the mount ignores: it serves one repository. */\n query: [KEY_QUERY_PARAM],\n response: apiResponse<SettingsConfigView>(),\n },\n value: {\n method: 'PUT',\n path: '/value',\n response: apiResponse<SettingsConfigView>(),\n },\n repositories: {\n method: 'GET',\n path: '/repositories',\n response: apiResponse<{ repositories: readonly SettingsRepository[] }>(),\n },\n /** The workspace this mount was bound to; a global mount has none and answers 404. */\n repository: {\n method: 'GET',\n path: '/repository',\n response: apiResponse<RepositorySettingsView>(),\n },\n selection: {\n method: 'PUT',\n path: '/repository/selection',\n response: apiResponse<RepositorySettingsView>(),\n },\n /** Pi's own image limits, served by the global mount only. */\n images: {\n method: 'GET',\n path: '/images',\n response: apiResponse<SettingsImagesView>(),\n },\n /** The same path as `images`; the method is the whole of the difference. */\n saveImages: {\n method: 'PUT',\n path: '/images',\n response: apiResponse<SettingsImagesView>(),\n },\n /** Your major modes from `~/.po/modes.yaml`, served by the global mount only. */\n presets: {\n method: 'GET',\n path: '/presets',\n response: apiResponse<SettingsPresetsView>(),\n },\n /** Sets `defaultMajorMode`; the method is the whole of the difference. */\n savePresets: {\n method: 'PUT',\n path: '/presets',\n response: apiResponse<SettingsPresetsView>(),\n },\n});\n","import path from 'node:path';\n\nimport {\n BACKGROUND_CONTEXT,\n getOrThrow,\n readTextIfExists,\n writeFileAtomic,\n type FileSystem,\n} from '@agimon-ai/po-core/platform';\nimport { isMap, parseDocument } from 'yaml';\n\nimport type { SettingsPresetsView } from '../../types/settings';\nimport { globalPoConfigDirectory } from '../config';\nimport { readMajorModesConfig, type LayerPackage } from '../majorModes';\nimport { MODES_FILE } from '../majorModes/core';\n\nconst io = BACKGROUND_CONTEXT;\n\nfunction packageName(entry: string | LayerPackage): string {\n return typeof entry === 'string' ? entry : entry.name;\n}\n\n/**\n * Settings > Presets: the major modes in `~/.po/modes.yaml`. Read with the home as the root, so\n * the page shows your presets, never one repository's.\n */\nexport async function readPresetsView(\n fs: FileSystem,\n homeDirectory: string,\n environment: NodeJS.ProcessEnv,\n): Promise<SettingsPresetsView> {\n const config = await readMajorModesConfig(fs, homeDirectory, io, { homeDirectory, environment });\n return {\n defaultPreset: config.defaultMajorMode,\n presets: Object.entries(config.majorMode).map(([name, mode]) => ({\n name,\n description: mode.description,\n layers: mode.layers.map((layer) => ({\n name: layer,\n packages: (config.layers[layer]?.packages ?? []).map(packageName),\n })),\n })),\n alwaysIncluded: (config.default?.packages ?? []).map(packageName),\n };\n}\n\n/**\n * Sets `defaultMajorMode` in `~/.po/modes.yaml`, keeping the file's comments. Answers an error\n * string, rather than writing, when the name is not one of your presets or the file is not YAML;\n * a result the loader refuses is put back as it was.\n */\nexport async function writeDefaultPreset(\n fs: FileSystem,\n homeDirectory: string,\n environment: NodeJS.ProcessEnv,\n name: string,\n): Promise<SettingsPresetsView | { error: string }> {\n const before = await readPresetsView(fs, homeDirectory, environment);\n if (!before.presets.some((preset) => preset.name === name)) return { error: `There is no preset named ${name}.` };\n const file = path.join(globalPoConfigDirectory(homeDirectory), MODES_FILE);\n const text = await readTextIfExists(fs, file, io);\n const document = parseDocument(text ?? '');\n if (document.errors.length > 0 || !isMap(document.contents)) {\n return { error: `Your ${MODES_FILE} could not be read as YAML.` };\n }\n document.set('defaultMajorMode', name);\n await writeFileAtomic(fs, file, document.toString(), io);\n try {\n return await readPresetsView(fs, homeDirectory, environment);\n } catch (error) {\n if (text === undefined) getOrThrow(await fs.remove(file, { force: true }, io));\n else await writeFileAtomic(fs, file, text, io);\n return { error: `Po left ${MODES_FILE} as it was: ${error instanceof Error ? error.message : String(error)}` };\n }\n}\n","import type { PoApiContext } from '@agimon-ai/po-core/packageApi';\nimport type { FileSystem } from '@agimon-ai/po-core/platform';\n\nimport { KEY_SEPARATOR, SELECTION_KEYS } from '../../constants/settings';\nimport type { PoConfigLayers } from '../../types/config';\nimport type {\n RepositoryCatalogOption,\n RepositorySelectionChanges,\n RepositorySelectionWriteRequest,\n RepositorySettingsView,\n SettingsConfigView,\n SettingsRepository,\n SettingsScope,\n SettingsValueView,\n SettingsBatchWriteRequest,\n} from '../../types/settings';\nimport { globalPoConfigPath, readPoConfigLayers, repositoryPoConfigPath, type ConfigEnvironment } from '../config';\nimport { configScopeOf } from '../configPolicy';\nimport { defaultDomainsForMajorMode, loadDomainsAsync } from '../domains';\nimport { readMajorModesConfig } from '../majorModes';\nimport { loadProfilesAsync } from '../profiles';\n\ntype SelectionKey = keyof typeof SELECTION_KEYS;\ntype IoContext = Parameters<FileSystem['readTextFile']>[1];\n\n/** The mount's admitted home and environment, so no reader falls back to the host's. */\nfunction readOptions(homeDirectory: string | undefined, environment: ConfigEnvironment) {\n return { ...(homeDirectory === undefined ? {} : { homeDirectory }), environment };\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport function describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** The display form of a config value; a record has no single-line form, so it has none. */\nexport function displayValue(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n if (Array.isArray(value)) return value.map((entry) => displayValue(entry) ?? '').join(', ');\n return undefined;\n}\n\nexport function pathFor(\n scope: SettingsScope,\n repoRoot: string,\n homeDirectory: string | undefined,\n environment: ConfigEnvironment,\n): string {\n return scope === 'repository' ? repositoryPoConfigPath(repoRoot) : globalPoConfigPath(homeDirectory, environment);\n}\n\nexport function parseWrite(body: unknown): SettingsBatchWriteRequest | undefined {\n if (!isRecord(body)) return undefined;\n const { repoRoot, scope, expectedHash } = body;\n if (typeof repoRoot !== 'string' || (scope !== 'global' && scope !== 'repository')) return undefined;\n if (scope === 'repository' && repoRoot === '') return undefined;\n if (typeof expectedHash !== 'string') return undefined;\n const edits = body.edits === undefined ? [{ keyPath: body.keyPath, value: body.value }] : body.edits;\n if (!Array.isArray(edits) || edits.length === 0 || edits.length > 100) return undefined;\n const validated: Array<SettingsBatchWriteRequest['edits'][number]> = [];\n for (const edit of edits) {\n if (!isRecord(edit)) return undefined;\n const { keyPath, value } = edit;\n if (!Array.isArray(keyPath) || keyPath.length === 0 || keyPath.some((part) => typeof part !== 'string'))\n return undefined;\n if (value !== null && typeof value !== 'string' && (typeof value !== 'number' || !Number.isFinite(value)))\n return undefined;\n validated.push({ keyPath: keyPath as string[], value });\n }\n return { repoRoot, scope, expectedHash, edits: validated };\n}\n\nexport function configuredSelectionValue<T>(\n layers: PoConfigLayers,\n key: SelectionKey,\n effective: T | undefined,\n): { effective?: T; repository?: T; origin: 'global' | 'repository' | 'default' } {\n const origin = layers.originOf(SELECTION_KEYS[key]);\n return {\n ...(effective === undefined ? {} : { effective }),\n ...(origin === 'repository' && effective !== undefined ? { repository: effective } : {}),\n origin,\n };\n}\n\nexport async function repositorySettingsView(\n fs: FileSystem,\n repository: SettingsRepository,\n homeDirectory: string | undefined,\n environment: ConfigEnvironment,\n io: IoContext,\n): Promise<RepositorySettingsView> {\n const repoRoot = repository.path;\n const options = readOptions(homeDirectory, environment);\n const layers = await readPoConfigLayers(fs, repoRoot, io, options);\n const modes = await readMajorModesConfig(fs, repoRoot, io, options);\n const domains = await loadDomainsAsync(fs, repoRoot, io, homeDirectory);\n const profiles = await loadProfilesAsync(fs, repoRoot, io, homeDirectory);\n const configured = layers.effective.selection;\n const effectiveMajorMode = configured?.majorMode ?? modes.defaultMajorMode;\n const effectiveDomains =\n configured?.domains ?? defaultDomainsForMajorMode(effectiveMajorMode, {}, domains.defaultDomains);\n const majorModes: RepositoryCatalogOption[] = Object.entries(modes.majorMode)\n .map(([name, definition]) => ({ name, description: definition.description, layers: definition.layers }))\n .sort((left, right) => left.name.localeCompare(right.name));\n const domainOptions: RepositoryCatalogOption[] = [\n ...Object.entries(domains.domains).map(([name, definition]) => ({\n name,\n ...(definition.description ? { description: definition.description } : {}),\n })),\n ...Object.entries(domains.aliases).map(([name, expandsTo]) => ({\n name,\n description: `Alias for ${expandsTo.join(', ') || 'no domains'}.`,\n expandsTo,\n })),\n ].sort((left, right) => left.name.localeCompare(right.name));\n\n return {\n repository,\n hash: layers.repositoryFile.hash,\n catalogs: {\n majorModes,\n domains: domainOptions,\n profiles: profiles.map((profile) => ({ name: profile.name })),\n },\n selection: {\n majorMode: configuredSelectionValue(layers, 'majorMode', effectiveMajorMode),\n domains: configuredSelectionValue(layers, 'domains', effectiveDomains),\n profile: configuredSelectionValue(layers, 'profile', configured?.profile),\n },\n };\n}\n\nexport function parseSelectionChanges(value: unknown): RepositorySelectionChanges | undefined {\n if (!isRecord(value)) return undefined;\n const keys = Object.keys(value);\n if (keys.length === 0 || keys.some((key) => !Object.hasOwn(SELECTION_KEYS, key))) return undefined;\n const changes: RepositorySelectionChanges = {};\n if (Object.hasOwn(value, 'majorMode')) {\n if (value.majorMode !== null && (typeof value.majorMode !== 'string' || value.majorMode.trim() === ''))\n return undefined;\n changes.majorMode = value.majorMode === null ? null : value.majorMode.trim();\n }\n if (Object.hasOwn(value, 'domains')) {\n if (\n value.domains !== null &&\n (!Array.isArray(value.domains) || value.domains.some((name) => typeof name !== 'string' || name.trim() === ''))\n ) {\n return undefined;\n }\n changes.domains =\n value.domains === null ? null : [...new Set((value.domains as string[]).map((name) => name.trim()))];\n }\n if (Object.hasOwn(value, 'profile')) {\n if (value.profile !== null && (typeof value.profile !== 'string' || value.profile.trim() === '')) return undefined;\n changes.profile = value.profile === null ? null : value.profile.trim();\n }\n return changes;\n}\n\nexport function parseSelectionWrite(value: unknown): RepositorySelectionWriteRequest | undefined {\n if (!isRecord(value) || typeof value.repositoryId !== 'string' || typeof value.expectedHash !== 'string')\n return undefined;\n const changes = parseSelectionChanges(value.changes);\n return changes === undefined\n ? undefined\n : { repositoryId: value.repositoryId, expectedHash: value.expectedHash, changes };\n}\n\nexport function validateSelectionChanges(\n view: RepositorySettingsView,\n changes: RepositorySelectionChanges,\n): string | undefined {\n if (changes.majorMode !== undefined && changes.majorMode !== null) {\n if (!view.catalogs.majorModes.some((mode) => mode.name === changes.majorMode)) {\n return `Unknown major mode '${changes.majorMode}'.`;\n }\n }\n if (changes.domains !== undefined && changes.domains !== null) {\n const known = new Set(view.catalogs.domains.map((domain) => domain.name));\n const unknown = changes.domains.find((domain) => !known.has(domain));\n if (unknown !== undefined) return `Unknown domain '${unknown}'.`;\n }\n if (changes.profile !== undefined && changes.profile !== null) {\n if (!view.catalogs.profiles.some((profile) => profile.name === changes.profile)) {\n return `Unknown profile '${changes.profile}'.`;\n }\n }\n return undefined;\n}\n\nexport function selectionEdits(changes: RepositorySelectionChanges) {\n return (Object.entries(changes) as Array<[SelectionKey, string | readonly string[] | null]>).map(([key, value]) => ({\n keyPath: SELECTION_KEYS[key],\n ...(value === null ? {} : { value }),\n }));\n}\n\nexport async function configView(\n fs: FileSystem,\n context: PoApiContext,\n keys: readonly string[],\n io: IoContext,\n): Promise<SettingsConfigView> {\n const root = context.scope === 'workspace' ? context.workspaceRoot : undefined;\n const layers = await readPoConfigLayers(fs, root, io, readOptions(context.homeDirectory, context.environment ?? {}));\n const values: Record<string, SettingsValueView> = {};\n for (const key of keys) {\n const keyPath = key.split(KEY_SEPARATOR);\n const value = displayValue(layers.valueAt(keyPath));\n values[key] = {\n ...(value === undefined ? {} : { value }),\n origin: layers.originOf(keyPath),\n scope: configScopeOf(keyPath),\n };\n }\n return {\n repoRoot: root ?? '',\n values,\n hashes: { global: layers.globalFile.hash, repository: layers.repositoryFile.hash },\n };\n}\n","import type { PoApi } from '@agimon-ai/po-core/packageApi';\nimport { BACKGROUND_CONTEXT, type FileSystem } from '@agimon-ai/po-core/platform';\n\nimport { KEY_QUERY_PARAM, KEY_SEPARATOR } from '../../constants/settings';\nimport routes from '../../types/apiRoutes';\nimport type { SettingsRepository } from '../../types/settings';\nimport { repositoryPoConfigPath } from '../config';\nimport { configScopeOf } from '../configPolicy';\nimport { writePoConfigValuesAsync } from '../configWriter';\nimport { DEFAULT_IMAGE_MAX_DIMENSION, MIN_IMAGE_MAX_DIMENSION } from '../imageSettings';\nimport { readPiImageSettings, savePiImageSettingsAsync } from '../piConfig';\nimport { readPresetsView, writeDefaultPreset } from '../presets';\nimport {\n configView,\n describe,\n isRecord,\n parseSelectionWrite,\n parseWrite,\n pathFor,\n repositorySettingsView,\n selectionEdits,\n validateSelectionChanges,\n} from '../settings';\n\n/** One declared route, as much of it as a comparison needs. */\ntype RouteSpec = { readonly method: string; readonly path: string };\n\n/**\n * Config handlers are bound to their mount. Request paths cannot choose a different repository.\n * Reads, writes and image settings all go through the host's FileSystem.\n */\nexport const createSettingsApi = (fs: FileSystem): PoApi => ({\n basePath: 'settings',\n start(context) {\n if (context.scope === 'session') throw new Error('Configuration settings require a global or workspace mount.');\n const { homeDirectory } = context;\n const environment = context.environment ?? {};\n const repository = (): SettingsRepository | undefined =>\n context.scope === 'workspace' && context.workspaceId && context.workspaceRoot\n ? {\n id: context.workspaceId,\n name: context.workspaceRoot.split('/').at(-1) ?? context.workspaceId,\n path: context.workspaceRoot,\n active: context.repositories?.().find((entry) => entry.id === context.workspaceId)?.active ?? false,\n }\n : undefined;\n const io = BACKGROUND_CONTEXT;\n const imageView = async () => ({\n ...(await readPiImageSettings(fs, io, homeDirectory)),\n minDimension: MIN_IMAGE_MAX_DIMENSION,\n maxAllowedDimension: DEFAULT_IMAGE_MAX_DIMENSION,\n });\n let writes: Promise<unknown> = Promise.resolve();\n let closed = false;\n const dispatch = async (request: Request): Promise<Response> => {\n if (closed) return Response.json({ error: 'Configuration mount is closed.' }, { status: 503 });\n const url = new URL(request.url);\n /**\n * Whether this request addresses one declared route.\n *\n * The table owns both halves of the comparison, so the path this mount\n * answers and the path the client asks for cannot drift apart: there is\n * one spelling of each, in `src/types/apiRoutes`.\n */\n const addresses = (route: RouteSpec): boolean => url.pathname === route.path && request.method === route.method;\n const currentRepository = repository();\n if (addresses(routes.config))\n return Response.json(await configView(fs, context, url.searchParams.getAll(KEY_QUERY_PARAM), io));\n if (addresses(routes.repositories)) return Response.json({ repositories: context.repositories?.() ?? [] });\n if (addresses(routes.repository)) {\n return currentRepository\n ? Response.json(await repositorySettingsView(fs, currentRepository, homeDirectory, environment, io))\n : Response.json({ error: 'Choose a workspace.' }, { status: 404 });\n }\n // One path, two methods, so the branch is on the path the pair share.\n if (url.pathname === routes.images.path && context.scope === 'global') {\n if (request.method === routes.images.method) return Response.json(await imageView());\n if (request.method === routes.saveImages.method) {\n const body: unknown = await request.json();\n if (\n !isRecord(body) ||\n (body.autoResize !== undefined && typeof body.autoResize !== 'boolean') ||\n (body.maxDimension !== undefined &&\n (typeof body.maxDimension !== 'number' || !Number.isFinite(body.maxDimension)))\n )\n return Response.json({ error: 'Invalid image settings.' }, { status: 400 });\n await savePiImageSettingsAsync(\n fs,\n {\n ...(typeof body.autoResize === 'boolean' ? { autoResize: body.autoResize } : {}),\n ...(typeof body.maxDimension === 'number' ? { maxDimension: body.maxDimension } : {}),\n },\n io,\n homeDirectory,\n );\n context.configurationChanged?.();\n return Response.json(await imageView());\n }\n }\n if (url.pathname === routes.presets.path && context.scope === 'global') {\n if (!homeDirectory) return Response.json({ error: 'Po has no home directory here.' }, { status: 503 });\n if (request.method === routes.presets.method) {\n return Response.json(await readPresetsView(fs, homeDirectory, environment));\n }\n if (request.method === routes.savePresets.method) {\n const body: unknown = await request.json();\n if (!isRecord(body) || typeof body.defaultPreset !== 'string' || !body.defaultPreset.trim())\n return Response.json({ error: 'Choose a preset.' }, { status: 400 });\n const result = await writeDefaultPreset(fs, homeDirectory, environment, body.defaultPreset.trim());\n if ('error' in result) return Response.json(result, { status: 422 });\n context.configurationChanged?.();\n return Response.json(result);\n }\n }\n if (addresses(routes.selection)) {\n const body = parseSelectionWrite(await request.json());\n if (!body)\n return Response.json({ error: 'A selection write needs typed changes and a file hash.' }, { status: 400 });\n if (!currentRepository || body.repositoryId !== currentRepository.id)\n return Response.json({ error: 'Workspace not found in this mount.' }, { status: 404 });\n const before = await repositorySettingsView(fs, currentRepository, homeDirectory, environment, io);\n if (before.hash !== body.expectedHash)\n return Response.json(\n { error: 'The repository config changed since it was read.', hash: before.hash },\n { status: 409 },\n );\n const invalid = validateSelectionChanges(before, body.changes);\n if (invalid) return Response.json({ error: invalid }, { status: 422 });\n await writePoConfigValuesAsync(\n fs,\n repositoryPoConfigPath(currentRepository.path),\n selectionEdits(body.changes),\n io,\n { scope: 'repository' },\n );\n context.configurationChanged?.();\n return Response.json(await repositorySettingsView(fs, currentRepository, homeDirectory, environment, io));\n }\n if (addresses(routes.value)) {\n const body = parseWrite(await request.json());\n if (!body)\n return Response.json({ error: 'A save requires a scope, key, value and file hash.' }, { status: 400 });\n const scope = context.scope === 'global' ? 'global' : 'repository';\n if (body.scope !== scope || (scope === 'repository' && body.repoRoot !== currentRepository?.path))\n return Response.json({ error: 'The write targets a different mount.' }, { status: 403 });\n if (\n body.edits.some((edit) => {\n const allowed = configScopeOf(edit.keyPath);\n return allowed !== 'both' && allowed !== scope;\n })\n )\n return Response.json({ error: 'This key cannot be set at this scope.' }, { status: 409 });\n const before = await configView(fs, context, [], io);\n if (before.hashes[scope] !== body.expectedHash)\n return Response.json(\n { error: 'The config changed since it was read.', hash: before.hashes[scope] },\n { status: 409 },\n );\n const target = pathFor(scope, currentRepository?.path ?? '', homeDirectory, environment);\n await writePoConfigValuesAsync(\n fs,\n target,\n body.edits.map(({ keyPath, value }) => ({ keyPath, ...(value === null ? {} : { value }) })),\n io,\n { scope },\n );\n context.configurationChanged?.();\n return Response.json(\n await configView(\n fs,\n context,\n body.edits.map((edit) => edit.keyPath.join(KEY_SEPARATOR)),\n io,\n ),\n );\n }\n return Response.json({ error: 'Configuration route not found.' }, { status: 404 });\n };\n const guarded = async (request: Request): Promise<Response> => {\n try {\n return await dispatch(request);\n } catch (error) {\n return Response.json({ error: describe(error) }, { status: error instanceof SyntaxError ? 400 : 422 });\n }\n };\n return {\n fetch(request) {\n if (request.method === 'GET' || request.method === 'HEAD') return guarded(request);\n const result = writes.then(() => guarded(request));\n writes = result.catch(() => undefined);\n return result;\n },\n close() {\n closed = true;\n },\n };\n },\n});\n"],"mappings":"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"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { c as configScopeOf } from "./configPolicy-DIYWpvHZ.mjs";
|
|
2
2
|
import { a as globalPoConfigDirectory, o as globalPoConfigPath, p as repositoryPoConfigPath } from "./core-DWO9z4du.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { n as readPoConfigLayers } from "./config-YZTd0uSD.mjs";
|
|
4
4
|
import { r as writePoConfigValuesAsync } from "./configWriter-B5K3aPLm.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { n as loadDomainsAsync, p as defaultDomainsForMajorMode } from "./domains-D5M1H3rX.mjs";
|
|
6
6
|
import { n as readPiImageSettings, r as savePiImageSettingsAsync, u as DEFAULT_IMAGE_MAX_DIMENSION } from "./piConfig-Bq36PQFF.mjs";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { o as MODES_FILE, t as readMajorModesConfig } from "./majorModes-CBTXMJZb.mjs";
|
|
8
|
+
import { o as loadProfilesAsync } from "./profiles-lG_t2vKD.mjs";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { BACKGROUND_CONTEXT, getOrThrow, readTextIfExists, writeFileAtomic } from "@agimon-ai/po-core/platform";
|
|
11
11
|
import { isMap, parseDocument } from "yaml";
|
|
@@ -137,6 +137,13 @@ async function writeDefaultPreset(fs, homeDirectory, environment, name) {
|
|
|
137
137
|
}
|
|
138
138
|
//#endregion
|
|
139
139
|
//#region src/services/settings/index.ts
|
|
140
|
+
/** The mount's admitted home and environment, so no reader falls back to the host's. */
|
|
141
|
+
function readOptions(homeDirectory, environment) {
|
|
142
|
+
return {
|
|
143
|
+
...homeDirectory === void 0 ? {} : { homeDirectory },
|
|
144
|
+
environment
|
|
145
|
+
};
|
|
146
|
+
}
|
|
140
147
|
function isRecord(value) {
|
|
141
148
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
142
149
|
}
|
|
@@ -189,12 +196,13 @@ function configuredSelectionValue(layers, key, effective) {
|
|
|
189
196
|
origin
|
|
190
197
|
};
|
|
191
198
|
}
|
|
192
|
-
function repositorySettingsView(repository, homeDirectory, environment) {
|
|
199
|
+
async function repositorySettingsView(fs, repository, homeDirectory, environment, io) {
|
|
193
200
|
const repoRoot = repository.path;
|
|
194
|
-
const
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
-
const
|
|
201
|
+
const options = readOptions(homeDirectory, environment);
|
|
202
|
+
const layers = await readPoConfigLayers(fs, repoRoot, io, options);
|
|
203
|
+
const modes = await readMajorModesConfig(fs, repoRoot, io, options);
|
|
204
|
+
const domains = await loadDomainsAsync(fs, repoRoot, io, homeDirectory);
|
|
205
|
+
const profiles = await loadProfilesAsync(fs, repoRoot, io, homeDirectory);
|
|
198
206
|
const configured = layers.effective.selection;
|
|
199
207
|
const effectiveMajorMode = configured?.majorMode ?? modes.defaultMajorMode;
|
|
200
208
|
const effectiveDomains = configured?.domains ?? defaultDomainsForMajorMode(effectiveMajorMode, {}, domains.defaultDomains);
|
|
@@ -273,9 +281,9 @@ function selectionEdits(changes) {
|
|
|
273
281
|
...value === null ? {} : { value }
|
|
274
282
|
}));
|
|
275
283
|
}
|
|
276
|
-
function configView(context, keys) {
|
|
284
|
+
async function configView(fs, context, keys, io) {
|
|
277
285
|
const root = context.scope === "workspace" ? context.workspaceRoot : void 0;
|
|
278
|
-
const layers =
|
|
286
|
+
const layers = await readPoConfigLayers(fs, root, io, readOptions(context.homeDirectory, context.environment ?? {}));
|
|
279
287
|
const values = {};
|
|
280
288
|
for (const key of keys) {
|
|
281
289
|
const keyPath = key.split(".");
|
|
@@ -299,7 +307,7 @@ function configView(context, keys) {
|
|
|
299
307
|
//#region src/services/settingsApi/index.ts
|
|
300
308
|
/**
|
|
301
309
|
* Config handlers are bound to their mount. Request paths cannot choose a different repository.
|
|
302
|
-
*
|
|
310
|
+
* Reads, writes and image settings all go through the host's FileSystem.
|
|
303
311
|
*/
|
|
304
312
|
const createSettingsApi = (fs) => ({
|
|
305
313
|
basePath: "settings",
|
|
@@ -333,9 +341,9 @@ const createSettingsApi = (fs) => ({
|
|
|
333
341
|
*/
|
|
334
342
|
const addresses = (route) => url.pathname === route.path && request.method === route.method;
|
|
335
343
|
const currentRepository = repository();
|
|
336
|
-
if (addresses(apiRoutes_default.config)) return Response.json(configView(context, url.searchParams.getAll("key")));
|
|
344
|
+
if (addresses(apiRoutes_default.config)) return Response.json(await configView(fs, context, url.searchParams.getAll("key"), io));
|
|
337
345
|
if (addresses(apiRoutes_default.repositories)) return Response.json({ repositories: context.repositories?.() ?? [] });
|
|
338
|
-
if (addresses(apiRoutes_default.repository)) return currentRepository ? Response.json(repositorySettingsView(currentRepository, homeDirectory, environment)) : Response.json({ error: "Choose a workspace." }, { status: 404 });
|
|
346
|
+
if (addresses(apiRoutes_default.repository)) return currentRepository ? Response.json(await repositorySettingsView(fs, currentRepository, homeDirectory, environment, io)) : Response.json({ error: "Choose a workspace." }, { status: 404 });
|
|
339
347
|
if (url.pathname === apiRoutes_default.images.path && context.scope === "global") {
|
|
340
348
|
if (request.method === apiRoutes_default.images.method) return Response.json(await imageView());
|
|
341
349
|
if (request.method === apiRoutes_default.saveImages.method) {
|
|
@@ -365,7 +373,7 @@ const createSettingsApi = (fs) => ({
|
|
|
365
373
|
const body = parseSelectionWrite(await request.json());
|
|
366
374
|
if (!body) return Response.json({ error: "A selection write needs typed changes and a file hash." }, { status: 400 });
|
|
367
375
|
if (!currentRepository || body.repositoryId !== currentRepository.id) return Response.json({ error: "Workspace not found in this mount." }, { status: 404 });
|
|
368
|
-
const before = repositorySettingsView(currentRepository, homeDirectory, environment);
|
|
376
|
+
const before = await repositorySettingsView(fs, currentRepository, homeDirectory, environment, io);
|
|
369
377
|
if (before.hash !== body.expectedHash) return Response.json({
|
|
370
378
|
error: "The repository config changed since it was read.",
|
|
371
379
|
hash: before.hash
|
|
@@ -374,7 +382,7 @@ const createSettingsApi = (fs) => ({
|
|
|
374
382
|
if (invalid) return Response.json({ error: invalid }, { status: 422 });
|
|
375
383
|
await writePoConfigValuesAsync(fs, repositoryPoConfigPath(currentRepository.path), selectionEdits(body.changes), io, { scope: "repository" });
|
|
376
384
|
context.configurationChanged?.();
|
|
377
|
-
return Response.json(repositorySettingsView(currentRepository, homeDirectory, environment));
|
|
385
|
+
return Response.json(await repositorySettingsView(fs, currentRepository, homeDirectory, environment, io));
|
|
378
386
|
}
|
|
379
387
|
if (addresses(apiRoutes_default.value)) {
|
|
380
388
|
const body = parseWrite(await request.json());
|
|
@@ -385,7 +393,7 @@ const createSettingsApi = (fs) => ({
|
|
|
385
393
|
const allowed = configScopeOf(edit.keyPath);
|
|
386
394
|
return allowed !== "both" && allowed !== scope;
|
|
387
395
|
})) return Response.json({ error: "This key cannot be set at this scope." }, { status: 409 });
|
|
388
|
-
const before = configView(context, []);
|
|
396
|
+
const before = await configView(fs, context, [], io);
|
|
389
397
|
if (before.hashes[scope] !== body.expectedHash) return Response.json({
|
|
390
398
|
error: "The config changed since it was read.",
|
|
391
399
|
hash: before.hashes[scope]
|
|
@@ -396,7 +404,7 @@ const createSettingsApi = (fs) => ({
|
|
|
396
404
|
...value === null ? {} : { value }
|
|
397
405
|
})), io, { scope });
|
|
398
406
|
context.configurationChanged?.();
|
|
399
|
-
return Response.json(configView(context, body.edits.map((edit) => edit.keyPath.join("."))));
|
|
407
|
+
return Response.json(await configView(fs, context, body.edits.map((edit) => edit.keyPath.join(".")), io));
|
|
400
408
|
}
|
|
401
409
|
return Response.json({ error: "Configuration route not found." }, { status: 404 });
|
|
402
410
|
};
|
|
@@ -423,4 +431,4 @@ const createSettingsApi = (fs) => ({
|
|
|
423
431
|
//#endregion
|
|
424
432
|
export { createSettingsApi as t };
|
|
425
433
|
|
|
426
|
-
//# sourceMappingURL=settingsApi-
|
|
434
|
+
//# sourceMappingURL=settingsApi-DQ0Kf7TF.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settingsApi-DQ0Kf7TF.mjs","names":["routes"],"sources":["../src/constants/settings.ts","../src/types/apiRoutes.ts","../src/services/presets/index.ts","../src/services/settings/index.ts","../src/services/settingsApi/index.ts"],"sourcesContent":["export const KEY_SEPARATOR = '.';\n\n/** The repeatable query parameter naming which keys a read is about. */\nexport const KEY_QUERY_PARAM = 'key';\n\nexport const SELECTION_KEYS = {\n majorMode: ['selection', 'majorMode'],\n domains: ['selection', 'domains'],\n profile: ['selection', 'profile'],\n} as const;\n","import { apiResponse, defineApiRoutes } from '@agimon-ai/po-core/web';\n\nimport { KEY_QUERY_PARAM } from '../constants/settings';\nimport type {\n RepositorySettingsView,\n SettingsConfigView,\n SettingsImagesView,\n SettingsPresetsView,\n SettingsRepository,\n} from './settings';\n\n/**\n * This package's routes, as data.\n *\n * No scope and no base path. The build reads both off\n * `src/extensions/(backend)/api/settings/`, so the mount is stated once, by the\n * folder that creates it. `settings` is the host's own API: it answers at\n * `/api/settings` and `/api/workspaces/<id>/settings`, beside `/plugins` rather\n * than under it, and the client knows that from the base path alone.\n *\n * This is the one file the mount's dispatcher and, once the client crosses the\n * package boundary, the cockpit both read, so a path cannot move on one side\n * alone.\n */\nexport default defineApiRoutes({\n /** The mount itself: the selected keys and the hash of each file they came from. */\n config: {\n method: 'GET',\n path: '/',\n /** Repeatable. The page also sends `repoRoot`, which the mount ignores: it serves one repository. */\n query: [KEY_QUERY_PARAM],\n response: apiResponse<SettingsConfigView>(),\n },\n value: {\n method: 'PUT',\n path: '/value',\n response: apiResponse<SettingsConfigView>(),\n },\n repositories: {\n method: 'GET',\n path: '/repositories',\n response: apiResponse<{ repositories: readonly SettingsRepository[] }>(),\n },\n /** The workspace this mount was bound to; a global mount has none and answers 404. */\n repository: {\n method: 'GET',\n path: '/repository',\n response: apiResponse<RepositorySettingsView>(),\n },\n selection: {\n method: 'PUT',\n path: '/repository/selection',\n response: apiResponse<RepositorySettingsView>(),\n },\n /** Pi's own image limits, served by the global mount only. */\n images: {\n method: 'GET',\n path: '/images',\n response: apiResponse<SettingsImagesView>(),\n },\n /** The same path as `images`; the method is the whole of the difference. */\n saveImages: {\n method: 'PUT',\n path: '/images',\n response: apiResponse<SettingsImagesView>(),\n },\n /** Your major modes from `~/.po/modes.yaml`, served by the global mount only. */\n presets: {\n method: 'GET',\n path: '/presets',\n response: apiResponse<SettingsPresetsView>(),\n },\n /** Sets `defaultMajorMode`; the method is the whole of the difference. */\n savePresets: {\n method: 'PUT',\n path: '/presets',\n response: apiResponse<SettingsPresetsView>(),\n },\n});\n","import path from 'node:path';\n\nimport {\n BACKGROUND_CONTEXT,\n getOrThrow,\n readTextIfExists,\n writeFileAtomic,\n type FileSystem,\n} from '@agimon-ai/po-core/platform';\nimport { isMap, parseDocument } from 'yaml';\n\nimport type { SettingsPresetsView } from '../../types/settings';\nimport { globalPoConfigDirectory } from '../config';\nimport { readMajorModesConfig, type LayerPackage } from '../majorModes';\nimport { MODES_FILE } from '../majorModes/core';\n\nconst io = BACKGROUND_CONTEXT;\n\nfunction packageName(entry: string | LayerPackage): string {\n return typeof entry === 'string' ? entry : entry.name;\n}\n\n/**\n * Settings > Presets: the major modes in `~/.po/modes.yaml`. Read with the home as the root, so\n * the page shows your presets, never one repository's.\n */\nexport async function readPresetsView(\n fs: FileSystem,\n homeDirectory: string,\n environment: NodeJS.ProcessEnv,\n): Promise<SettingsPresetsView> {\n const config = await readMajorModesConfig(fs, homeDirectory, io, { homeDirectory, environment });\n return {\n defaultPreset: config.defaultMajorMode,\n presets: Object.entries(config.majorMode).map(([name, mode]) => ({\n name,\n description: mode.description,\n layers: mode.layers.map((layer) => ({\n name: layer,\n packages: (config.layers[layer]?.packages ?? []).map(packageName),\n })),\n })),\n alwaysIncluded: (config.default?.packages ?? []).map(packageName),\n };\n}\n\n/**\n * Sets `defaultMajorMode` in `~/.po/modes.yaml`, keeping the file's comments. Answers an error\n * string, rather than writing, when the name is not one of your presets or the file is not YAML;\n * a result the loader refuses is put back as it was.\n */\nexport async function writeDefaultPreset(\n fs: FileSystem,\n homeDirectory: string,\n environment: NodeJS.ProcessEnv,\n name: string,\n): Promise<SettingsPresetsView | { error: string }> {\n const before = await readPresetsView(fs, homeDirectory, environment);\n if (!before.presets.some((preset) => preset.name === name)) return { error: `There is no preset named ${name}.` };\n const file = path.join(globalPoConfigDirectory(homeDirectory), MODES_FILE);\n const text = await readTextIfExists(fs, file, io);\n const document = parseDocument(text ?? '');\n if (document.errors.length > 0 || !isMap(document.contents)) {\n return { error: `Your ${MODES_FILE} could not be read as YAML.` };\n }\n document.set('defaultMajorMode', name);\n await writeFileAtomic(fs, file, document.toString(), io);\n try {\n return await readPresetsView(fs, homeDirectory, environment);\n } catch (error) {\n if (text === undefined) getOrThrow(await fs.remove(file, { force: true }, io));\n else await writeFileAtomic(fs, file, text, io);\n return { error: `Po left ${MODES_FILE} as it was: ${error instanceof Error ? error.message : String(error)}` };\n }\n}\n","import type { PoApiContext } from '@agimon-ai/po-core/packageApi';\nimport type { FileSystem } from '@agimon-ai/po-core/platform';\n\nimport { KEY_SEPARATOR, SELECTION_KEYS } from '../../constants/settings';\nimport type { PoConfigLayers } from '../../types/config';\nimport type {\n RepositoryCatalogOption,\n RepositorySelectionChanges,\n RepositorySelectionWriteRequest,\n RepositorySettingsView,\n SettingsConfigView,\n SettingsRepository,\n SettingsScope,\n SettingsValueView,\n SettingsBatchWriteRequest,\n} from '../../types/settings';\nimport { globalPoConfigPath, readPoConfigLayers, repositoryPoConfigPath, type ConfigEnvironment } from '../config';\nimport { configScopeOf } from '../configPolicy';\nimport { defaultDomainsForMajorMode, loadDomainsAsync } from '../domains';\nimport { readMajorModesConfig } from '../majorModes';\nimport { loadProfilesAsync } from '../profiles';\n\ntype SelectionKey = keyof typeof SELECTION_KEYS;\ntype IoContext = Parameters<FileSystem['readTextFile']>[1];\n\n/** The mount's admitted home and environment, so no reader falls back to the host's. */\nfunction readOptions(homeDirectory: string | undefined, environment: ConfigEnvironment) {\n return { ...(homeDirectory === undefined ? {} : { homeDirectory }), environment };\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport function describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** The display form of a config value; a record has no single-line form, so it has none. */\nexport function displayValue(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n if (Array.isArray(value)) return value.map((entry) => displayValue(entry) ?? '').join(', ');\n return undefined;\n}\n\nexport function pathFor(\n scope: SettingsScope,\n repoRoot: string,\n homeDirectory: string | undefined,\n environment: ConfigEnvironment,\n): string {\n return scope === 'repository' ? repositoryPoConfigPath(repoRoot) : globalPoConfigPath(homeDirectory, environment);\n}\n\nexport function parseWrite(body: unknown): SettingsBatchWriteRequest | undefined {\n if (!isRecord(body)) return undefined;\n const { repoRoot, scope, expectedHash } = body;\n if (typeof repoRoot !== 'string' || (scope !== 'global' && scope !== 'repository')) return undefined;\n if (scope === 'repository' && repoRoot === '') return undefined;\n if (typeof expectedHash !== 'string') return undefined;\n const edits = body.edits === undefined ? [{ keyPath: body.keyPath, value: body.value }] : body.edits;\n if (!Array.isArray(edits) || edits.length === 0 || edits.length > 100) return undefined;\n const validated: Array<SettingsBatchWriteRequest['edits'][number]> = [];\n for (const edit of edits) {\n if (!isRecord(edit)) return undefined;\n const { keyPath, value } = edit;\n if (!Array.isArray(keyPath) || keyPath.length === 0 || keyPath.some((part) => typeof part !== 'string'))\n return undefined;\n if (value !== null && typeof value !== 'string' && (typeof value !== 'number' || !Number.isFinite(value)))\n return undefined;\n validated.push({ keyPath: keyPath as string[], value });\n }\n return { repoRoot, scope, expectedHash, edits: validated };\n}\n\nexport function configuredSelectionValue<T>(\n layers: PoConfigLayers,\n key: SelectionKey,\n effective: T | undefined,\n): { effective?: T; repository?: T; origin: 'global' | 'repository' | 'default' } {\n const origin = layers.originOf(SELECTION_KEYS[key]);\n return {\n ...(effective === undefined ? {} : { effective }),\n ...(origin === 'repository' && effective !== undefined ? { repository: effective } : {}),\n origin,\n };\n}\n\nexport async function repositorySettingsView(\n fs: FileSystem,\n repository: SettingsRepository,\n homeDirectory: string | undefined,\n environment: ConfigEnvironment,\n io: IoContext,\n): Promise<RepositorySettingsView> {\n const repoRoot = repository.path;\n const options = readOptions(homeDirectory, environment);\n const layers = await readPoConfigLayers(fs, repoRoot, io, options);\n const modes = await readMajorModesConfig(fs, repoRoot, io, options);\n const domains = await loadDomainsAsync(fs, repoRoot, io, homeDirectory);\n const profiles = await loadProfilesAsync(fs, repoRoot, io, homeDirectory);\n const configured = layers.effective.selection;\n const effectiveMajorMode = configured?.majorMode ?? modes.defaultMajorMode;\n const effectiveDomains =\n configured?.domains ?? defaultDomainsForMajorMode(effectiveMajorMode, {}, domains.defaultDomains);\n const majorModes: RepositoryCatalogOption[] = Object.entries(modes.majorMode)\n .map(([name, definition]) => ({ name, description: definition.description, layers: definition.layers }))\n .sort((left, right) => left.name.localeCompare(right.name));\n const domainOptions: RepositoryCatalogOption[] = [\n ...Object.entries(domains.domains).map(([name, definition]) => ({\n name,\n ...(definition.description ? { description: definition.description } : {}),\n })),\n ...Object.entries(domains.aliases).map(([name, expandsTo]) => ({\n name,\n description: `Alias for ${expandsTo.join(', ') || 'no domains'}.`,\n expandsTo,\n })),\n ].sort((left, right) => left.name.localeCompare(right.name));\n\n return {\n repository,\n hash: layers.repositoryFile.hash,\n catalogs: {\n majorModes,\n domains: domainOptions,\n profiles: profiles.map((profile) => ({ name: profile.name })),\n },\n selection: {\n majorMode: configuredSelectionValue(layers, 'majorMode', effectiveMajorMode),\n domains: configuredSelectionValue(layers, 'domains', effectiveDomains),\n profile: configuredSelectionValue(layers, 'profile', configured?.profile),\n },\n };\n}\n\nexport function parseSelectionChanges(value: unknown): RepositorySelectionChanges | undefined {\n if (!isRecord(value)) return undefined;\n const keys = Object.keys(value);\n if (keys.length === 0 || keys.some((key) => !Object.hasOwn(SELECTION_KEYS, key))) return undefined;\n const changes: RepositorySelectionChanges = {};\n if (Object.hasOwn(value, 'majorMode')) {\n if (value.majorMode !== null && (typeof value.majorMode !== 'string' || value.majorMode.trim() === ''))\n return undefined;\n changes.majorMode = value.majorMode === null ? null : value.majorMode.trim();\n }\n if (Object.hasOwn(value, 'domains')) {\n if (\n value.domains !== null &&\n (!Array.isArray(value.domains) || value.domains.some((name) => typeof name !== 'string' || name.trim() === ''))\n ) {\n return undefined;\n }\n changes.domains =\n value.domains === null ? null : [...new Set((value.domains as string[]).map((name) => name.trim()))];\n }\n if (Object.hasOwn(value, 'profile')) {\n if (value.profile !== null && (typeof value.profile !== 'string' || value.profile.trim() === '')) return undefined;\n changes.profile = value.profile === null ? null : value.profile.trim();\n }\n return changes;\n}\n\nexport function parseSelectionWrite(value: unknown): RepositorySelectionWriteRequest | undefined {\n if (!isRecord(value) || typeof value.repositoryId !== 'string' || typeof value.expectedHash !== 'string')\n return undefined;\n const changes = parseSelectionChanges(value.changes);\n return changes === undefined\n ? undefined\n : { repositoryId: value.repositoryId, expectedHash: value.expectedHash, changes };\n}\n\nexport function validateSelectionChanges(\n view: RepositorySettingsView,\n changes: RepositorySelectionChanges,\n): string | undefined {\n if (changes.majorMode !== undefined && changes.majorMode !== null) {\n if (!view.catalogs.majorModes.some((mode) => mode.name === changes.majorMode)) {\n return `Unknown major mode '${changes.majorMode}'.`;\n }\n }\n if (changes.domains !== undefined && changes.domains !== null) {\n const known = new Set(view.catalogs.domains.map((domain) => domain.name));\n const unknown = changes.domains.find((domain) => !known.has(domain));\n if (unknown !== undefined) return `Unknown domain '${unknown}'.`;\n }\n if (changes.profile !== undefined && changes.profile !== null) {\n if (!view.catalogs.profiles.some((profile) => profile.name === changes.profile)) {\n return `Unknown profile '${changes.profile}'.`;\n }\n }\n return undefined;\n}\n\nexport function selectionEdits(changes: RepositorySelectionChanges) {\n return (Object.entries(changes) as Array<[SelectionKey, string | readonly string[] | null]>).map(([key, value]) => ({\n keyPath: SELECTION_KEYS[key],\n ...(value === null ? {} : { value }),\n }));\n}\n\nexport async function configView(\n fs: FileSystem,\n context: PoApiContext,\n keys: readonly string[],\n io: IoContext,\n): Promise<SettingsConfigView> {\n const root = context.scope === 'workspace' ? context.workspaceRoot : undefined;\n const layers = await readPoConfigLayers(fs, root, io, readOptions(context.homeDirectory, context.environment ?? {}));\n const values: Record<string, SettingsValueView> = {};\n for (const key of keys) {\n const keyPath = key.split(KEY_SEPARATOR);\n const value = displayValue(layers.valueAt(keyPath));\n values[key] = {\n ...(value === undefined ? {} : { value }),\n origin: layers.originOf(keyPath),\n scope: configScopeOf(keyPath),\n };\n }\n return {\n repoRoot: root ?? '',\n values,\n hashes: { global: layers.globalFile.hash, repository: layers.repositoryFile.hash },\n };\n}\n","import type { PoApi } from '@agimon-ai/po-core/packageApi';\nimport { BACKGROUND_CONTEXT, type FileSystem } from '@agimon-ai/po-core/platform';\n\nimport { KEY_QUERY_PARAM, KEY_SEPARATOR } from '../../constants/settings';\nimport routes from '../../types/apiRoutes';\nimport type { SettingsRepository } from '../../types/settings';\nimport { repositoryPoConfigPath } from '../config';\nimport { configScopeOf } from '../configPolicy';\nimport { writePoConfigValuesAsync } from '../configWriter';\nimport { DEFAULT_IMAGE_MAX_DIMENSION, MIN_IMAGE_MAX_DIMENSION } from '../imageSettings';\nimport { readPiImageSettings, savePiImageSettingsAsync } from '../piConfig';\nimport { readPresetsView, writeDefaultPreset } from '../presets';\nimport {\n configView,\n describe,\n isRecord,\n parseSelectionWrite,\n parseWrite,\n pathFor,\n repositorySettingsView,\n selectionEdits,\n validateSelectionChanges,\n} from '../settings';\n\n/** One declared route, as much of it as a comparison needs. */\ntype RouteSpec = { readonly method: string; readonly path: string };\n\n/**\n * Config handlers are bound to their mount. Request paths cannot choose a different repository.\n * Reads, writes and image settings all go through the host's FileSystem.\n */\nexport const createSettingsApi = (fs: FileSystem): PoApi => ({\n basePath: 'settings',\n start(context) {\n if (context.scope === 'session') throw new Error('Configuration settings require a global or workspace mount.');\n const { homeDirectory } = context;\n const environment = context.environment ?? {};\n const repository = (): SettingsRepository | undefined =>\n context.scope === 'workspace' && context.workspaceId && context.workspaceRoot\n ? {\n id: context.workspaceId,\n name: context.workspaceRoot.split('/').at(-1) ?? context.workspaceId,\n path: context.workspaceRoot,\n active: context.repositories?.().find((entry) => entry.id === context.workspaceId)?.active ?? false,\n }\n : undefined;\n const io = BACKGROUND_CONTEXT;\n const imageView = async () => ({\n ...(await readPiImageSettings(fs, io, homeDirectory)),\n minDimension: MIN_IMAGE_MAX_DIMENSION,\n maxAllowedDimension: DEFAULT_IMAGE_MAX_DIMENSION,\n });\n let writes: Promise<unknown> = Promise.resolve();\n let closed = false;\n const dispatch = async (request: Request): Promise<Response> => {\n if (closed) return Response.json({ error: 'Configuration mount is closed.' }, { status: 503 });\n const url = new URL(request.url);\n /**\n * Whether this request addresses one declared route.\n *\n * The table owns both halves of the comparison, so the path this mount\n * answers and the path the client asks for cannot drift apart: there is\n * one spelling of each, in `src/types/apiRoutes`.\n */\n const addresses = (route: RouteSpec): boolean => url.pathname === route.path && request.method === route.method;\n const currentRepository = repository();\n if (addresses(routes.config))\n return Response.json(await configView(fs, context, url.searchParams.getAll(KEY_QUERY_PARAM), io));\n if (addresses(routes.repositories)) return Response.json({ repositories: context.repositories?.() ?? [] });\n if (addresses(routes.repository)) {\n return currentRepository\n ? Response.json(await repositorySettingsView(fs, currentRepository, homeDirectory, environment, io))\n : Response.json({ error: 'Choose a workspace.' }, { status: 404 });\n }\n // One path, two methods, so the branch is on the path the pair share.\n if (url.pathname === routes.images.path && context.scope === 'global') {\n if (request.method === routes.images.method) return Response.json(await imageView());\n if (request.method === routes.saveImages.method) {\n const body: unknown = await request.json();\n if (\n !isRecord(body) ||\n (body.autoResize !== undefined && typeof body.autoResize !== 'boolean') ||\n (body.maxDimension !== undefined &&\n (typeof body.maxDimension !== 'number' || !Number.isFinite(body.maxDimension)))\n )\n return Response.json({ error: 'Invalid image settings.' }, { status: 400 });\n await savePiImageSettingsAsync(\n fs,\n {\n ...(typeof body.autoResize === 'boolean' ? { autoResize: body.autoResize } : {}),\n ...(typeof body.maxDimension === 'number' ? { maxDimension: body.maxDimension } : {}),\n },\n io,\n homeDirectory,\n );\n context.configurationChanged?.();\n return Response.json(await imageView());\n }\n }\n if (url.pathname === routes.presets.path && context.scope === 'global') {\n if (!homeDirectory) return Response.json({ error: 'Po has no home directory here.' }, { status: 503 });\n if (request.method === routes.presets.method) {\n return Response.json(await readPresetsView(fs, homeDirectory, environment));\n }\n if (request.method === routes.savePresets.method) {\n const body: unknown = await request.json();\n if (!isRecord(body) || typeof body.defaultPreset !== 'string' || !body.defaultPreset.trim())\n return Response.json({ error: 'Choose a preset.' }, { status: 400 });\n const result = await writeDefaultPreset(fs, homeDirectory, environment, body.defaultPreset.trim());\n if ('error' in result) return Response.json(result, { status: 422 });\n context.configurationChanged?.();\n return Response.json(result);\n }\n }\n if (addresses(routes.selection)) {\n const body = parseSelectionWrite(await request.json());\n if (!body)\n return Response.json({ error: 'A selection write needs typed changes and a file hash.' }, { status: 400 });\n if (!currentRepository || body.repositoryId !== currentRepository.id)\n return Response.json({ error: 'Workspace not found in this mount.' }, { status: 404 });\n const before = await repositorySettingsView(fs, currentRepository, homeDirectory, environment, io);\n if (before.hash !== body.expectedHash)\n return Response.json(\n { error: 'The repository config changed since it was read.', hash: before.hash },\n { status: 409 },\n );\n const invalid = validateSelectionChanges(before, body.changes);\n if (invalid) return Response.json({ error: invalid }, { status: 422 });\n await writePoConfigValuesAsync(\n fs,\n repositoryPoConfigPath(currentRepository.path),\n selectionEdits(body.changes),\n io,\n { scope: 'repository' },\n );\n context.configurationChanged?.();\n return Response.json(await repositorySettingsView(fs, currentRepository, homeDirectory, environment, io));\n }\n if (addresses(routes.value)) {\n const body = parseWrite(await request.json());\n if (!body)\n return Response.json({ error: 'A save requires a scope, key, value and file hash.' }, { status: 400 });\n const scope = context.scope === 'global' ? 'global' : 'repository';\n if (body.scope !== scope || (scope === 'repository' && body.repoRoot !== currentRepository?.path))\n return Response.json({ error: 'The write targets a different mount.' }, { status: 403 });\n if (\n body.edits.some((edit) => {\n const allowed = configScopeOf(edit.keyPath);\n return allowed !== 'both' && allowed !== scope;\n })\n )\n return Response.json({ error: 'This key cannot be set at this scope.' }, { status: 409 });\n const before = await configView(fs, context, [], io);\n if (before.hashes[scope] !== body.expectedHash)\n return Response.json(\n { error: 'The config changed since it was read.', hash: before.hashes[scope] },\n { status: 409 },\n );\n const target = pathFor(scope, currentRepository?.path ?? '', homeDirectory, environment);\n await writePoConfigValuesAsync(\n fs,\n target,\n body.edits.map(({ keyPath, value }) => ({ keyPath, ...(value === null ? {} : { value }) })),\n io,\n { scope },\n );\n context.configurationChanged?.();\n return Response.json(\n await configView(\n fs,\n context,\n body.edits.map((edit) => edit.keyPath.join(KEY_SEPARATOR)),\n io,\n ),\n );\n }\n return Response.json({ error: 'Configuration route not found.' }, { status: 404 });\n };\n const guarded = async (request: Request): Promise<Response> => {\n try {\n return await dispatch(request);\n } catch (error) {\n return Response.json({ error: describe(error) }, { status: error instanceof SyntaxError ? 400 : 422 });\n }\n };\n return {\n fetch(request) {\n if (request.method === 'GET' || request.method === 'HEAD') return guarded(request);\n const result = writes.then(() => guarded(request));\n writes = result.catch(() => undefined);\n return result;\n },\n close() {\n closed = true;\n },\n };\n },\n});\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/po-config",
|
|
3
|
-
"version": "0.0.0-alpha.
|
|
3
|
+
"version": "0.0.0-alpha.114",
|
|
4
4
|
"description": "Typed configuration loading, validation, and host services for composing Po sessions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-configuration",
|
|
@@ -139,14 +139,14 @@
|
|
|
139
139
|
"registry": "https://registry.npmjs.org/"
|
|
140
140
|
},
|
|
141
141
|
"dependencies": {
|
|
142
|
-
"@agimon-ai/po-core": "0.0.0-alpha.
|
|
143
|
-
"@agimon-ai/po-web-components": "0.0.0-alpha.
|
|
142
|
+
"@agimon-ai/po-core": "0.0.0-alpha.114",
|
|
143
|
+
"@agimon-ai/po-web-components": "0.0.0-alpha.114",
|
|
144
144
|
"@deepseek-ai/cordis": "4.0.5",
|
|
145
145
|
"typebox": "1.4.2",
|
|
146
146
|
"yaml": "2.9.1"
|
|
147
147
|
},
|
|
148
148
|
"devDependencies": {
|
|
149
|
-
"@agimon-ai/po-build": "0.0.0-alpha.
|
|
149
|
+
"@agimon-ai/po-build": "0.0.0-alpha.114",
|
|
150
150
|
"@earendil-works/pi-coding-agent": "1.5.0",
|
|
151
151
|
"@modelcontextprotocol/ext-apps": "2.0.3",
|
|
152
152
|
"@modelcontextprotocol/server": "2.3.1",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"settingsApi-C_rqLkZZ.cjs","names":["defineApiRoutes","apiResponse","BACKGROUND_CONTEXT","readMajorModesConfig","path","globalPoConfigDirectory","MODES_FILE","readTextIfExists","parseDocument","isMap","writeFileAtomic","repositoryPoConfigPath","globalPoConfigPath","loadPoConfigLayers","loadMajorModesConfig","loadDomains","loadProfiles","defaultDomainsForMajorMode","configScopeOf","BACKGROUND_CONTEXT","readPiImageSettings","DEFAULT_IMAGE_MAX_DIMENSION","routes","savePiImageSettingsAsync","writePoConfigValuesAsync","repositoryPoConfigPath","configScopeOf"],"sources":["../src/constants/settings.ts","../src/types/apiRoutes.ts","../src/services/presets/index.ts","../src/services/settings/index.ts","../src/services/settingsApi/index.ts"],"sourcesContent":["export const KEY_SEPARATOR = '.';\n\n/** The repeatable query parameter naming which keys a read is about. */\nexport const KEY_QUERY_PARAM = 'key';\n\nexport const SELECTION_KEYS = {\n majorMode: ['selection', 'majorMode'],\n domains: ['selection', 'domains'],\n profile: ['selection', 'profile'],\n} as const;\n","import { apiResponse, defineApiRoutes } from '@agimon-ai/po-core/web';\n\nimport { KEY_QUERY_PARAM } from '../constants/settings';\nimport type {\n RepositorySettingsView,\n SettingsConfigView,\n SettingsImagesView,\n SettingsPresetsView,\n SettingsRepository,\n} from './settings';\n\n/**\n * This package's routes, as data.\n *\n * No scope and no base path. The build reads both off\n * `src/extensions/(backend)/api/settings/`, so the mount is stated once, by the\n * folder that creates it. `settings` is the host's own API: it answers at\n * `/api/settings` and `/api/workspaces/<id>/settings`, beside `/plugins` rather\n * than under it, and the client knows that from the base path alone.\n *\n * This is the one file the mount's dispatcher and, once the client crosses the\n * package boundary, the cockpit both read, so a path cannot move on one side\n * alone.\n */\nexport default defineApiRoutes({\n /** The mount itself: the selected keys and the hash of each file they came from. */\n config: {\n method: 'GET',\n path: '/',\n /** Repeatable. The page also sends `repoRoot`, which the mount ignores: it serves one repository. */\n query: [KEY_QUERY_PARAM],\n response: apiResponse<SettingsConfigView>(),\n },\n value: {\n method: 'PUT',\n path: '/value',\n response: apiResponse<SettingsConfigView>(),\n },\n repositories: {\n method: 'GET',\n path: '/repositories',\n response: apiResponse<{ repositories: readonly SettingsRepository[] }>(),\n },\n /** The workspace this mount was bound to; a global mount has none and answers 404. */\n repository: {\n method: 'GET',\n path: '/repository',\n response: apiResponse<RepositorySettingsView>(),\n },\n selection: {\n method: 'PUT',\n path: '/repository/selection',\n response: apiResponse<RepositorySettingsView>(),\n },\n /** Pi's own image limits, served by the global mount only. */\n images: {\n method: 'GET',\n path: '/images',\n response: apiResponse<SettingsImagesView>(),\n },\n /** The same path as `images`; the method is the whole of the difference. */\n saveImages: {\n method: 'PUT',\n path: '/images',\n response: apiResponse<SettingsImagesView>(),\n },\n /** Your major modes from `~/.po/modes.yaml`, served by the global mount only. */\n presets: {\n method: 'GET',\n path: '/presets',\n response: apiResponse<SettingsPresetsView>(),\n },\n /** Sets `defaultMajorMode`; the method is the whole of the difference. */\n savePresets: {\n method: 'PUT',\n path: '/presets',\n response: apiResponse<SettingsPresetsView>(),\n },\n});\n","import path from 'node:path';\n\nimport {\n BACKGROUND_CONTEXT,\n getOrThrow,\n readTextIfExists,\n writeFileAtomic,\n type FileSystem,\n} from '@agimon-ai/po-core/platform';\nimport { isMap, parseDocument } from 'yaml';\n\nimport type { SettingsPresetsView } from '../../types/settings';\nimport { globalPoConfigDirectory } from '../config';\nimport { readMajorModesConfig, type LayerPackage } from '../majorModes';\nimport { MODES_FILE } from '../majorModes/core';\n\nconst io = BACKGROUND_CONTEXT;\n\nfunction packageName(entry: string | LayerPackage): string {\n return typeof entry === 'string' ? entry : entry.name;\n}\n\n/**\n * Settings > Presets: the major modes in `~/.po/modes.yaml`. Read with the home as the root, so\n * the page shows your presets, never one repository's.\n */\nexport async function readPresetsView(\n fs: FileSystem,\n homeDirectory: string,\n environment: NodeJS.ProcessEnv,\n): Promise<SettingsPresetsView> {\n const config = await readMajorModesConfig(fs, homeDirectory, io, { homeDirectory, environment });\n return {\n defaultPreset: config.defaultMajorMode,\n presets: Object.entries(config.majorMode).map(([name, mode]) => ({\n name,\n description: mode.description,\n layers: mode.layers.map((layer) => ({\n name: layer,\n packages: (config.layers[layer]?.packages ?? []).map(packageName),\n })),\n })),\n alwaysIncluded: (config.default?.packages ?? []).map(packageName),\n };\n}\n\n/**\n * Sets `defaultMajorMode` in `~/.po/modes.yaml`, keeping the file's comments. Answers an error\n * string, rather than writing, when the name is not one of your presets or the file is not YAML;\n * a result the loader refuses is put back as it was.\n */\nexport async function writeDefaultPreset(\n fs: FileSystem,\n homeDirectory: string,\n environment: NodeJS.ProcessEnv,\n name: string,\n): Promise<SettingsPresetsView | { error: string }> {\n const before = await readPresetsView(fs, homeDirectory, environment);\n if (!before.presets.some((preset) => preset.name === name)) return { error: `There is no preset named ${name}.` };\n const file = path.join(globalPoConfigDirectory(homeDirectory), MODES_FILE);\n const text = await readTextIfExists(fs, file, io);\n const document = parseDocument(text ?? '');\n if (document.errors.length > 0 || !isMap(document.contents)) {\n return { error: `Your ${MODES_FILE} could not be read as YAML.` };\n }\n document.set('defaultMajorMode', name);\n await writeFileAtomic(fs, file, document.toString(), io);\n try {\n return await readPresetsView(fs, homeDirectory, environment);\n } catch (error) {\n if (text === undefined) getOrThrow(await fs.remove(file, { force: true }, io));\n else await writeFileAtomic(fs, file, text, io);\n return { error: `Po left ${MODES_FILE} as it was: ${error instanceof Error ? error.message : String(error)}` };\n }\n}\n","import type { PoApiContext } from '@agimon-ai/po-core/packageApi';\n\nimport { KEY_SEPARATOR, SELECTION_KEYS } from '../../constants/settings';\nimport type {\n RepositoryCatalogOption,\n RepositorySelectionChanges,\n RepositorySelectionWriteRequest,\n RepositorySettingsView,\n SettingsConfigView,\n SettingsRepository,\n SettingsScope,\n SettingsValueView,\n SettingsBatchWriteRequest,\n} from '../../types/settings';\nimport { globalPoConfigPath, loadPoConfigLayers, repositoryPoConfigPath, type ConfigEnvironment } from '../config';\nimport { configScopeOf } from '../configPolicy';\nimport { defaultDomainsForMajorMode, loadDomains } from '../domains';\nimport { loadMajorModesConfig } from '../majorModes';\nimport { loadProfiles } from '../profiles';\n\ntype SelectionKey = keyof typeof SELECTION_KEYS;\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport function describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** The display form of a config value; a record has no single-line form, so it has none. */\nexport function displayValue(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n if (Array.isArray(value)) return value.map((entry) => displayValue(entry) ?? '').join(', ');\n return undefined;\n}\n\nexport function pathFor(\n scope: SettingsScope,\n repoRoot: string,\n homeDirectory: string | undefined,\n environment: ConfigEnvironment,\n): string {\n return scope === 'repository' ? repositoryPoConfigPath(repoRoot) : globalPoConfigPath(homeDirectory, environment);\n}\n\nexport function parseWrite(body: unknown): SettingsBatchWriteRequest | undefined {\n if (!isRecord(body)) return undefined;\n const { repoRoot, scope, expectedHash } = body;\n if (typeof repoRoot !== 'string' || (scope !== 'global' && scope !== 'repository')) return undefined;\n if (scope === 'repository' && repoRoot === '') return undefined;\n if (typeof expectedHash !== 'string') return undefined;\n const edits = body.edits === undefined ? [{ keyPath: body.keyPath, value: body.value }] : body.edits;\n if (!Array.isArray(edits) || edits.length === 0 || edits.length > 100) return undefined;\n const validated: Array<SettingsBatchWriteRequest['edits'][number]> = [];\n for (const edit of edits) {\n if (!isRecord(edit)) return undefined;\n const { keyPath, value } = edit;\n if (!Array.isArray(keyPath) || keyPath.length === 0 || keyPath.some((part) => typeof part !== 'string'))\n return undefined;\n if (value !== null && typeof value !== 'string' && (typeof value !== 'number' || !Number.isFinite(value)))\n return undefined;\n validated.push({ keyPath: keyPath as string[], value });\n }\n return { repoRoot, scope, expectedHash, edits: validated };\n}\n\nexport function configuredSelectionValue<T>(\n layers: ReturnType<typeof loadPoConfigLayers>,\n key: SelectionKey,\n effective: T | undefined,\n): { effective?: T; repository?: T; origin: 'global' | 'repository' | 'default' } {\n const origin = layers.originOf(SELECTION_KEYS[key]);\n return {\n ...(effective === undefined ? {} : { effective }),\n ...(origin === 'repository' && effective !== undefined ? { repository: effective } : {}),\n origin,\n };\n}\n\nexport function repositorySettingsView(\n repository: SettingsRepository,\n homeDirectory: string | undefined,\n environment: ConfigEnvironment,\n): RepositorySettingsView {\n const repoRoot = repository.path;\n const layers = loadPoConfigLayers(repoRoot, homeDirectory, environment);\n const modes = loadMajorModesConfig(repoRoot, homeDirectory, environment);\n const domains = loadDomains(repoRoot, homeDirectory);\n const profiles = loadProfiles(repoRoot, homeDirectory);\n const configured = layers.effective.selection;\n const effectiveMajorMode = configured?.majorMode ?? modes.defaultMajorMode;\n const effectiveDomains =\n configured?.domains ?? defaultDomainsForMajorMode(effectiveMajorMode, {}, domains.defaultDomains);\n const majorModes: RepositoryCatalogOption[] = Object.entries(modes.majorMode)\n .map(([name, definition]) => ({ name, description: definition.description, layers: definition.layers }))\n .sort((left, right) => left.name.localeCompare(right.name));\n const domainOptions: RepositoryCatalogOption[] = [\n ...Object.entries(domains.domains).map(([name, definition]) => ({\n name,\n ...(definition.description ? { description: definition.description } : {}),\n })),\n ...Object.entries(domains.aliases).map(([name, expandsTo]) => ({\n name,\n description: `Alias for ${expandsTo.join(', ') || 'no domains'}.`,\n expandsTo,\n })),\n ].sort((left, right) => left.name.localeCompare(right.name));\n\n return {\n repository,\n hash: layers.repositoryFile.hash,\n catalogs: {\n majorModes,\n domains: domainOptions,\n profiles: profiles.map((profile) => ({ name: profile.name })),\n },\n selection: {\n majorMode: configuredSelectionValue(layers, 'majorMode', effectiveMajorMode),\n domains: configuredSelectionValue(layers, 'domains', effectiveDomains),\n profile: configuredSelectionValue(layers, 'profile', configured?.profile),\n },\n };\n}\n\nexport function parseSelectionChanges(value: unknown): RepositorySelectionChanges | undefined {\n if (!isRecord(value)) return undefined;\n const keys = Object.keys(value);\n if (keys.length === 0 || keys.some((key) => !Object.hasOwn(SELECTION_KEYS, key))) return undefined;\n const changes: RepositorySelectionChanges = {};\n if (Object.hasOwn(value, 'majorMode')) {\n if (value.majorMode !== null && (typeof value.majorMode !== 'string' || value.majorMode.trim() === ''))\n return undefined;\n changes.majorMode = value.majorMode === null ? null : value.majorMode.trim();\n }\n if (Object.hasOwn(value, 'domains')) {\n if (\n value.domains !== null &&\n (!Array.isArray(value.domains) || value.domains.some((name) => typeof name !== 'string' || name.trim() === ''))\n ) {\n return undefined;\n }\n changes.domains =\n value.domains === null ? null : [...new Set((value.domains as string[]).map((name) => name.trim()))];\n }\n if (Object.hasOwn(value, 'profile')) {\n if (value.profile !== null && (typeof value.profile !== 'string' || value.profile.trim() === '')) return undefined;\n changes.profile = value.profile === null ? null : value.profile.trim();\n }\n return changes;\n}\n\nexport function parseSelectionWrite(value: unknown): RepositorySelectionWriteRequest | undefined {\n if (!isRecord(value) || typeof value.repositoryId !== 'string' || typeof value.expectedHash !== 'string')\n return undefined;\n const changes = parseSelectionChanges(value.changes);\n return changes === undefined\n ? undefined\n : { repositoryId: value.repositoryId, expectedHash: value.expectedHash, changes };\n}\n\nexport function validateSelectionChanges(\n view: RepositorySettingsView,\n changes: RepositorySelectionChanges,\n): string | undefined {\n if (changes.majorMode !== undefined && changes.majorMode !== null) {\n if (!view.catalogs.majorModes.some((mode) => mode.name === changes.majorMode)) {\n return `Unknown major mode '${changes.majorMode}'.`;\n }\n }\n if (changes.domains !== undefined && changes.domains !== null) {\n const known = new Set(view.catalogs.domains.map((domain) => domain.name));\n const unknown = changes.domains.find((domain) => !known.has(domain));\n if (unknown !== undefined) return `Unknown domain '${unknown}'.`;\n }\n if (changes.profile !== undefined && changes.profile !== null) {\n if (!view.catalogs.profiles.some((profile) => profile.name === changes.profile)) {\n return `Unknown profile '${changes.profile}'.`;\n }\n }\n return undefined;\n}\n\nexport function selectionEdits(changes: RepositorySelectionChanges) {\n return (Object.entries(changes) as Array<[SelectionKey, string | readonly string[] | null]>).map(([key, value]) => ({\n keyPath: SELECTION_KEYS[key],\n ...(value === null ? {} : { value }),\n }));\n}\n\nexport function configView(context: PoApiContext, keys: readonly string[]): SettingsConfigView {\n const root = context.scope === 'workspace' ? context.workspaceRoot : undefined;\n const layers = loadPoConfigLayers(root, context.homeDirectory, context.environment ?? {});\n const values: Record<string, SettingsValueView> = {};\n for (const key of keys) {\n const keyPath = key.split(KEY_SEPARATOR);\n const value = displayValue(layers.valueAt(keyPath));\n values[key] = {\n ...(value === undefined ? {} : { value }),\n origin: layers.originOf(keyPath),\n scope: configScopeOf(keyPath),\n };\n }\n return {\n repoRoot: root ?? '',\n values,\n hashes: { global: layers.globalFile.hash, repository: layers.repositoryFile.hash },\n };\n}\n","import type { PoApi } from '@agimon-ai/po-core/packageApi';\nimport { BACKGROUND_CONTEXT, type FileSystem } from '@agimon-ai/po-core/platform';\n\nimport { KEY_QUERY_PARAM, KEY_SEPARATOR } from '../../constants/settings';\nimport routes from '../../types/apiRoutes';\nimport type { SettingsRepository } from '../../types/settings';\nimport { repositoryPoConfigPath } from '../config';\nimport { configScopeOf } from '../configPolicy';\nimport { writePoConfigValuesAsync } from '../configWriter';\nimport { DEFAULT_IMAGE_MAX_DIMENSION, MIN_IMAGE_MAX_DIMENSION } from '../imageSettings';\nimport { readPiImageSettings, savePiImageSettingsAsync } from '../piConfig';\nimport { readPresetsView, writeDefaultPreset } from '../presets';\nimport {\n configView,\n describe,\n isRecord,\n parseSelectionWrite,\n parseWrite,\n pathFor,\n repositorySettingsView,\n selectionEdits,\n validateSelectionChanges,\n} from '../settings';\n\n/** One declared route, as much of it as a comparison needs. */\ntype RouteSpec = { readonly method: string; readonly path: string };\n\n/**\n * Config handlers are bound to their mount. Request paths cannot choose a different repository.\n * Writes and image settings go through the host's FileSystem.\n */\nexport const createSettingsApi = (fs: FileSystem): PoApi => ({\n basePath: 'settings',\n start(context) {\n if (context.scope === 'session') throw new Error('Configuration settings require a global or workspace mount.');\n const { homeDirectory } = context;\n const environment = context.environment ?? {};\n const repository = (): SettingsRepository | undefined =>\n context.scope === 'workspace' && context.workspaceId && context.workspaceRoot\n ? {\n id: context.workspaceId,\n name: context.workspaceRoot.split('/').at(-1) ?? context.workspaceId,\n path: context.workspaceRoot,\n active: context.repositories?.().find((entry) => entry.id === context.workspaceId)?.active ?? false,\n }\n : undefined;\n const io = BACKGROUND_CONTEXT;\n const imageView = async () => ({\n ...(await readPiImageSettings(fs, io, homeDirectory)),\n minDimension: MIN_IMAGE_MAX_DIMENSION,\n maxAllowedDimension: DEFAULT_IMAGE_MAX_DIMENSION,\n });\n let writes: Promise<unknown> = Promise.resolve();\n let closed = false;\n const dispatch = async (request: Request): Promise<Response> => {\n if (closed) return Response.json({ error: 'Configuration mount is closed.' }, { status: 503 });\n const url = new URL(request.url);\n /**\n * Whether this request addresses one declared route.\n *\n * The table owns both halves of the comparison, so the path this mount\n * answers and the path the client asks for cannot drift apart: there is\n * one spelling of each, in `src/types/apiRoutes`.\n */\n const addresses = (route: RouteSpec): boolean => url.pathname === route.path && request.method === route.method;\n const currentRepository = repository();\n if (addresses(routes.config)) return Response.json(configView(context, url.searchParams.getAll(KEY_QUERY_PARAM)));\n if (addresses(routes.repositories)) return Response.json({ repositories: context.repositories?.() ?? [] });\n if (addresses(routes.repository)) {\n return currentRepository\n ? Response.json(repositorySettingsView(currentRepository, homeDirectory, environment))\n : Response.json({ error: 'Choose a workspace.' }, { status: 404 });\n }\n // One path, two methods, so the branch is on the path the pair share.\n if (url.pathname === routes.images.path && context.scope === 'global') {\n if (request.method === routes.images.method) return Response.json(await imageView());\n if (request.method === routes.saveImages.method) {\n const body: unknown = await request.json();\n if (\n !isRecord(body) ||\n (body.autoResize !== undefined && typeof body.autoResize !== 'boolean') ||\n (body.maxDimension !== undefined &&\n (typeof body.maxDimension !== 'number' || !Number.isFinite(body.maxDimension)))\n )\n return Response.json({ error: 'Invalid image settings.' }, { status: 400 });\n await savePiImageSettingsAsync(\n fs,\n {\n ...(typeof body.autoResize === 'boolean' ? { autoResize: body.autoResize } : {}),\n ...(typeof body.maxDimension === 'number' ? { maxDimension: body.maxDimension } : {}),\n },\n io,\n homeDirectory,\n );\n context.configurationChanged?.();\n return Response.json(await imageView());\n }\n }\n if (url.pathname === routes.presets.path && context.scope === 'global') {\n if (!homeDirectory) return Response.json({ error: 'Po has no home directory here.' }, { status: 503 });\n if (request.method === routes.presets.method) {\n return Response.json(await readPresetsView(fs, homeDirectory, environment));\n }\n if (request.method === routes.savePresets.method) {\n const body: unknown = await request.json();\n if (!isRecord(body) || typeof body.defaultPreset !== 'string' || !body.defaultPreset.trim())\n return Response.json({ error: 'Choose a preset.' }, { status: 400 });\n const result = await writeDefaultPreset(fs, homeDirectory, environment, body.defaultPreset.trim());\n if ('error' in result) return Response.json(result, { status: 422 });\n context.configurationChanged?.();\n return Response.json(result);\n }\n }\n if (addresses(routes.selection)) {\n const body = parseSelectionWrite(await request.json());\n if (!body)\n return Response.json({ error: 'A selection write needs typed changes and a file hash.' }, { status: 400 });\n if (!currentRepository || body.repositoryId !== currentRepository.id)\n return Response.json({ error: 'Workspace not found in this mount.' }, { status: 404 });\n const before = repositorySettingsView(currentRepository, homeDirectory, environment);\n if (before.hash !== body.expectedHash)\n return Response.json(\n { error: 'The repository config changed since it was read.', hash: before.hash },\n { status: 409 },\n );\n const invalid = validateSelectionChanges(before, body.changes);\n if (invalid) return Response.json({ error: invalid }, { status: 422 });\n await writePoConfigValuesAsync(\n fs,\n repositoryPoConfigPath(currentRepository.path),\n selectionEdits(body.changes),\n io,\n { scope: 'repository' },\n );\n context.configurationChanged?.();\n return Response.json(repositorySettingsView(currentRepository, homeDirectory, environment));\n }\n if (addresses(routes.value)) {\n const body = parseWrite(await request.json());\n if (!body)\n return Response.json({ error: 'A save requires a scope, key, value and file hash.' }, { status: 400 });\n const scope = context.scope === 'global' ? 'global' : 'repository';\n if (body.scope !== scope || (scope === 'repository' && body.repoRoot !== currentRepository?.path))\n return Response.json({ error: 'The write targets a different mount.' }, { status: 403 });\n if (\n body.edits.some((edit) => {\n const allowed = configScopeOf(edit.keyPath);\n return allowed !== 'both' && allowed !== scope;\n })\n )\n return Response.json({ error: 'This key cannot be set at this scope.' }, { status: 409 });\n const before = configView(context, []);\n if (before.hashes[scope] !== body.expectedHash)\n return Response.json(\n { error: 'The config changed since it was read.', hash: before.hashes[scope] },\n { status: 409 },\n );\n const target = pathFor(scope, currentRepository?.path ?? '', homeDirectory, environment);\n await writePoConfigValuesAsync(\n fs,\n target,\n body.edits.map(({ keyPath, value }) => ({ keyPath, ...(value === null ? {} : { value }) })),\n io,\n { scope },\n );\n context.configurationChanged?.();\n return Response.json(\n configView(\n context,\n body.edits.map((edit) => edit.keyPath.join(KEY_SEPARATOR)),\n ),\n );\n }\n return Response.json({ error: 'Configuration route not found.' }, { status: 404 });\n };\n const guarded = async (request: Request): Promise<Response> => {\n try {\n return await dispatch(request);\n } catch (error) {\n return Response.json({ error: describe(error) }, { status: error instanceof SyntaxError ? 400 : 422 });\n }\n };\n return {\n fetch(request) {\n if (request.method === 'GET' || request.method === 'HEAD') return guarded(request);\n const result = writes.then(() => guarded(request));\n writes = result.catch(() => undefined);\n return result;\n },\n close() {\n closed = true;\n },\n };\n },\n});\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"settingsApi-r4lj6nnG.mjs","names":["routes"],"sources":["../src/constants/settings.ts","../src/types/apiRoutes.ts","../src/services/presets/index.ts","../src/services/settings/index.ts","../src/services/settingsApi/index.ts"],"sourcesContent":["export const KEY_SEPARATOR = '.';\n\n/** The repeatable query parameter naming which keys a read is about. */\nexport const KEY_QUERY_PARAM = 'key';\n\nexport const SELECTION_KEYS = {\n majorMode: ['selection', 'majorMode'],\n domains: ['selection', 'domains'],\n profile: ['selection', 'profile'],\n} as const;\n","import { apiResponse, defineApiRoutes } from '@agimon-ai/po-core/web';\n\nimport { KEY_QUERY_PARAM } from '../constants/settings';\nimport type {\n RepositorySettingsView,\n SettingsConfigView,\n SettingsImagesView,\n SettingsPresetsView,\n SettingsRepository,\n} from './settings';\n\n/**\n * This package's routes, as data.\n *\n * No scope and no base path. The build reads both off\n * `src/extensions/(backend)/api/settings/`, so the mount is stated once, by the\n * folder that creates it. `settings` is the host's own API: it answers at\n * `/api/settings` and `/api/workspaces/<id>/settings`, beside `/plugins` rather\n * than under it, and the client knows that from the base path alone.\n *\n * This is the one file the mount's dispatcher and, once the client crosses the\n * package boundary, the cockpit both read, so a path cannot move on one side\n * alone.\n */\nexport default defineApiRoutes({\n /** The mount itself: the selected keys and the hash of each file they came from. */\n config: {\n method: 'GET',\n path: '/',\n /** Repeatable. The page also sends `repoRoot`, which the mount ignores: it serves one repository. */\n query: [KEY_QUERY_PARAM],\n response: apiResponse<SettingsConfigView>(),\n },\n value: {\n method: 'PUT',\n path: '/value',\n response: apiResponse<SettingsConfigView>(),\n },\n repositories: {\n method: 'GET',\n path: '/repositories',\n response: apiResponse<{ repositories: readonly SettingsRepository[] }>(),\n },\n /** The workspace this mount was bound to; a global mount has none and answers 404. */\n repository: {\n method: 'GET',\n path: '/repository',\n response: apiResponse<RepositorySettingsView>(),\n },\n selection: {\n method: 'PUT',\n path: '/repository/selection',\n response: apiResponse<RepositorySettingsView>(),\n },\n /** Pi's own image limits, served by the global mount only. */\n images: {\n method: 'GET',\n path: '/images',\n response: apiResponse<SettingsImagesView>(),\n },\n /** The same path as `images`; the method is the whole of the difference. */\n saveImages: {\n method: 'PUT',\n path: '/images',\n response: apiResponse<SettingsImagesView>(),\n },\n /** Your major modes from `~/.po/modes.yaml`, served by the global mount only. */\n presets: {\n method: 'GET',\n path: '/presets',\n response: apiResponse<SettingsPresetsView>(),\n },\n /** Sets `defaultMajorMode`; the method is the whole of the difference. */\n savePresets: {\n method: 'PUT',\n path: '/presets',\n response: apiResponse<SettingsPresetsView>(),\n },\n});\n","import path from 'node:path';\n\nimport {\n BACKGROUND_CONTEXT,\n getOrThrow,\n readTextIfExists,\n writeFileAtomic,\n type FileSystem,\n} from '@agimon-ai/po-core/platform';\nimport { isMap, parseDocument } from 'yaml';\n\nimport type { SettingsPresetsView } from '../../types/settings';\nimport { globalPoConfigDirectory } from '../config';\nimport { readMajorModesConfig, type LayerPackage } from '../majorModes';\nimport { MODES_FILE } from '../majorModes/core';\n\nconst io = BACKGROUND_CONTEXT;\n\nfunction packageName(entry: string | LayerPackage): string {\n return typeof entry === 'string' ? entry : entry.name;\n}\n\n/**\n * Settings > Presets: the major modes in `~/.po/modes.yaml`. Read with the home as the root, so\n * the page shows your presets, never one repository's.\n */\nexport async function readPresetsView(\n fs: FileSystem,\n homeDirectory: string,\n environment: NodeJS.ProcessEnv,\n): Promise<SettingsPresetsView> {\n const config = await readMajorModesConfig(fs, homeDirectory, io, { homeDirectory, environment });\n return {\n defaultPreset: config.defaultMajorMode,\n presets: Object.entries(config.majorMode).map(([name, mode]) => ({\n name,\n description: mode.description,\n layers: mode.layers.map((layer) => ({\n name: layer,\n packages: (config.layers[layer]?.packages ?? []).map(packageName),\n })),\n })),\n alwaysIncluded: (config.default?.packages ?? []).map(packageName),\n };\n}\n\n/**\n * Sets `defaultMajorMode` in `~/.po/modes.yaml`, keeping the file's comments. Answers an error\n * string, rather than writing, when the name is not one of your presets or the file is not YAML;\n * a result the loader refuses is put back as it was.\n */\nexport async function writeDefaultPreset(\n fs: FileSystem,\n homeDirectory: string,\n environment: NodeJS.ProcessEnv,\n name: string,\n): Promise<SettingsPresetsView | { error: string }> {\n const before = await readPresetsView(fs, homeDirectory, environment);\n if (!before.presets.some((preset) => preset.name === name)) return { error: `There is no preset named ${name}.` };\n const file = path.join(globalPoConfigDirectory(homeDirectory), MODES_FILE);\n const text = await readTextIfExists(fs, file, io);\n const document = parseDocument(text ?? '');\n if (document.errors.length > 0 || !isMap(document.contents)) {\n return { error: `Your ${MODES_FILE} could not be read as YAML.` };\n }\n document.set('defaultMajorMode', name);\n await writeFileAtomic(fs, file, document.toString(), io);\n try {\n return await readPresetsView(fs, homeDirectory, environment);\n } catch (error) {\n if (text === undefined) getOrThrow(await fs.remove(file, { force: true }, io));\n else await writeFileAtomic(fs, file, text, io);\n return { error: `Po left ${MODES_FILE} as it was: ${error instanceof Error ? error.message : String(error)}` };\n }\n}\n","import type { PoApiContext } from '@agimon-ai/po-core/packageApi';\n\nimport { KEY_SEPARATOR, SELECTION_KEYS } from '../../constants/settings';\nimport type {\n RepositoryCatalogOption,\n RepositorySelectionChanges,\n RepositorySelectionWriteRequest,\n RepositorySettingsView,\n SettingsConfigView,\n SettingsRepository,\n SettingsScope,\n SettingsValueView,\n SettingsBatchWriteRequest,\n} from '../../types/settings';\nimport { globalPoConfigPath, loadPoConfigLayers, repositoryPoConfigPath, type ConfigEnvironment } from '../config';\nimport { configScopeOf } from '../configPolicy';\nimport { defaultDomainsForMajorMode, loadDomains } from '../domains';\nimport { loadMajorModesConfig } from '../majorModes';\nimport { loadProfiles } from '../profiles';\n\ntype SelectionKey = keyof typeof SELECTION_KEYS;\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport function describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** The display form of a config value; a record has no single-line form, so it has none. */\nexport function displayValue(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n if (Array.isArray(value)) return value.map((entry) => displayValue(entry) ?? '').join(', ');\n return undefined;\n}\n\nexport function pathFor(\n scope: SettingsScope,\n repoRoot: string,\n homeDirectory: string | undefined,\n environment: ConfigEnvironment,\n): string {\n return scope === 'repository' ? repositoryPoConfigPath(repoRoot) : globalPoConfigPath(homeDirectory, environment);\n}\n\nexport function parseWrite(body: unknown): SettingsBatchWriteRequest | undefined {\n if (!isRecord(body)) return undefined;\n const { repoRoot, scope, expectedHash } = body;\n if (typeof repoRoot !== 'string' || (scope !== 'global' && scope !== 'repository')) return undefined;\n if (scope === 'repository' && repoRoot === '') return undefined;\n if (typeof expectedHash !== 'string') return undefined;\n const edits = body.edits === undefined ? [{ keyPath: body.keyPath, value: body.value }] : body.edits;\n if (!Array.isArray(edits) || edits.length === 0 || edits.length > 100) return undefined;\n const validated: Array<SettingsBatchWriteRequest['edits'][number]> = [];\n for (const edit of edits) {\n if (!isRecord(edit)) return undefined;\n const { keyPath, value } = edit;\n if (!Array.isArray(keyPath) || keyPath.length === 0 || keyPath.some((part) => typeof part !== 'string'))\n return undefined;\n if (value !== null && typeof value !== 'string' && (typeof value !== 'number' || !Number.isFinite(value)))\n return undefined;\n validated.push({ keyPath: keyPath as string[], value });\n }\n return { repoRoot, scope, expectedHash, edits: validated };\n}\n\nexport function configuredSelectionValue<T>(\n layers: ReturnType<typeof loadPoConfigLayers>,\n key: SelectionKey,\n effective: T | undefined,\n): { effective?: T; repository?: T; origin: 'global' | 'repository' | 'default' } {\n const origin = layers.originOf(SELECTION_KEYS[key]);\n return {\n ...(effective === undefined ? {} : { effective }),\n ...(origin === 'repository' && effective !== undefined ? { repository: effective } : {}),\n origin,\n };\n}\n\nexport function repositorySettingsView(\n repository: SettingsRepository,\n homeDirectory: string | undefined,\n environment: ConfigEnvironment,\n): RepositorySettingsView {\n const repoRoot = repository.path;\n const layers = loadPoConfigLayers(repoRoot, homeDirectory, environment);\n const modes = loadMajorModesConfig(repoRoot, homeDirectory, environment);\n const domains = loadDomains(repoRoot, homeDirectory);\n const profiles = loadProfiles(repoRoot, homeDirectory);\n const configured = layers.effective.selection;\n const effectiveMajorMode = configured?.majorMode ?? modes.defaultMajorMode;\n const effectiveDomains =\n configured?.domains ?? defaultDomainsForMajorMode(effectiveMajorMode, {}, domains.defaultDomains);\n const majorModes: RepositoryCatalogOption[] = Object.entries(modes.majorMode)\n .map(([name, definition]) => ({ name, description: definition.description, layers: definition.layers }))\n .sort((left, right) => left.name.localeCompare(right.name));\n const domainOptions: RepositoryCatalogOption[] = [\n ...Object.entries(domains.domains).map(([name, definition]) => ({\n name,\n ...(definition.description ? { description: definition.description } : {}),\n })),\n ...Object.entries(domains.aliases).map(([name, expandsTo]) => ({\n name,\n description: `Alias for ${expandsTo.join(', ') || 'no domains'}.`,\n expandsTo,\n })),\n ].sort((left, right) => left.name.localeCompare(right.name));\n\n return {\n repository,\n hash: layers.repositoryFile.hash,\n catalogs: {\n majorModes,\n domains: domainOptions,\n profiles: profiles.map((profile) => ({ name: profile.name })),\n },\n selection: {\n majorMode: configuredSelectionValue(layers, 'majorMode', effectiveMajorMode),\n domains: configuredSelectionValue(layers, 'domains', effectiveDomains),\n profile: configuredSelectionValue(layers, 'profile', configured?.profile),\n },\n };\n}\n\nexport function parseSelectionChanges(value: unknown): RepositorySelectionChanges | undefined {\n if (!isRecord(value)) return undefined;\n const keys = Object.keys(value);\n if (keys.length === 0 || keys.some((key) => !Object.hasOwn(SELECTION_KEYS, key))) return undefined;\n const changes: RepositorySelectionChanges = {};\n if (Object.hasOwn(value, 'majorMode')) {\n if (value.majorMode !== null && (typeof value.majorMode !== 'string' || value.majorMode.trim() === ''))\n return undefined;\n changes.majorMode = value.majorMode === null ? null : value.majorMode.trim();\n }\n if (Object.hasOwn(value, 'domains')) {\n if (\n value.domains !== null &&\n (!Array.isArray(value.domains) || value.domains.some((name) => typeof name !== 'string' || name.trim() === ''))\n ) {\n return undefined;\n }\n changes.domains =\n value.domains === null ? null : [...new Set((value.domains as string[]).map((name) => name.trim()))];\n }\n if (Object.hasOwn(value, 'profile')) {\n if (value.profile !== null && (typeof value.profile !== 'string' || value.profile.trim() === '')) return undefined;\n changes.profile = value.profile === null ? null : value.profile.trim();\n }\n return changes;\n}\n\nexport function parseSelectionWrite(value: unknown): RepositorySelectionWriteRequest | undefined {\n if (!isRecord(value) || typeof value.repositoryId !== 'string' || typeof value.expectedHash !== 'string')\n return undefined;\n const changes = parseSelectionChanges(value.changes);\n return changes === undefined\n ? undefined\n : { repositoryId: value.repositoryId, expectedHash: value.expectedHash, changes };\n}\n\nexport function validateSelectionChanges(\n view: RepositorySettingsView,\n changes: RepositorySelectionChanges,\n): string | undefined {\n if (changes.majorMode !== undefined && changes.majorMode !== null) {\n if (!view.catalogs.majorModes.some((mode) => mode.name === changes.majorMode)) {\n return `Unknown major mode '${changes.majorMode}'.`;\n }\n }\n if (changes.domains !== undefined && changes.domains !== null) {\n const known = new Set(view.catalogs.domains.map((domain) => domain.name));\n const unknown = changes.domains.find((domain) => !known.has(domain));\n if (unknown !== undefined) return `Unknown domain '${unknown}'.`;\n }\n if (changes.profile !== undefined && changes.profile !== null) {\n if (!view.catalogs.profiles.some((profile) => profile.name === changes.profile)) {\n return `Unknown profile '${changes.profile}'.`;\n }\n }\n return undefined;\n}\n\nexport function selectionEdits(changes: RepositorySelectionChanges) {\n return (Object.entries(changes) as Array<[SelectionKey, string | readonly string[] | null]>).map(([key, value]) => ({\n keyPath: SELECTION_KEYS[key],\n ...(value === null ? {} : { value }),\n }));\n}\n\nexport function configView(context: PoApiContext, keys: readonly string[]): SettingsConfigView {\n const root = context.scope === 'workspace' ? context.workspaceRoot : undefined;\n const layers = loadPoConfigLayers(root, context.homeDirectory, context.environment ?? {});\n const values: Record<string, SettingsValueView> = {};\n for (const key of keys) {\n const keyPath = key.split(KEY_SEPARATOR);\n const value = displayValue(layers.valueAt(keyPath));\n values[key] = {\n ...(value === undefined ? {} : { value }),\n origin: layers.originOf(keyPath),\n scope: configScopeOf(keyPath),\n };\n }\n return {\n repoRoot: root ?? '',\n values,\n hashes: { global: layers.globalFile.hash, repository: layers.repositoryFile.hash },\n };\n}\n","import type { PoApi } from '@agimon-ai/po-core/packageApi';\nimport { BACKGROUND_CONTEXT, type FileSystem } from '@agimon-ai/po-core/platform';\n\nimport { KEY_QUERY_PARAM, KEY_SEPARATOR } from '../../constants/settings';\nimport routes from '../../types/apiRoutes';\nimport type { SettingsRepository } from '../../types/settings';\nimport { repositoryPoConfigPath } from '../config';\nimport { configScopeOf } from '../configPolicy';\nimport { writePoConfigValuesAsync } from '../configWriter';\nimport { DEFAULT_IMAGE_MAX_DIMENSION, MIN_IMAGE_MAX_DIMENSION } from '../imageSettings';\nimport { readPiImageSettings, savePiImageSettingsAsync } from '../piConfig';\nimport { readPresetsView, writeDefaultPreset } from '../presets';\nimport {\n configView,\n describe,\n isRecord,\n parseSelectionWrite,\n parseWrite,\n pathFor,\n repositorySettingsView,\n selectionEdits,\n validateSelectionChanges,\n} from '../settings';\n\n/** One declared route, as much of it as a comparison needs. */\ntype RouteSpec = { readonly method: string; readonly path: string };\n\n/**\n * Config handlers are bound to their mount. Request paths cannot choose a different repository.\n * Writes and image settings go through the host's FileSystem.\n */\nexport const createSettingsApi = (fs: FileSystem): PoApi => ({\n basePath: 'settings',\n start(context) {\n if (context.scope === 'session') throw new Error('Configuration settings require a global or workspace mount.');\n const { homeDirectory } = context;\n const environment = context.environment ?? {};\n const repository = (): SettingsRepository | undefined =>\n context.scope === 'workspace' && context.workspaceId && context.workspaceRoot\n ? {\n id: context.workspaceId,\n name: context.workspaceRoot.split('/').at(-1) ?? context.workspaceId,\n path: context.workspaceRoot,\n active: context.repositories?.().find((entry) => entry.id === context.workspaceId)?.active ?? false,\n }\n : undefined;\n const io = BACKGROUND_CONTEXT;\n const imageView = async () => ({\n ...(await readPiImageSettings(fs, io, homeDirectory)),\n minDimension: MIN_IMAGE_MAX_DIMENSION,\n maxAllowedDimension: DEFAULT_IMAGE_MAX_DIMENSION,\n });\n let writes: Promise<unknown> = Promise.resolve();\n let closed = false;\n const dispatch = async (request: Request): Promise<Response> => {\n if (closed) return Response.json({ error: 'Configuration mount is closed.' }, { status: 503 });\n const url = new URL(request.url);\n /**\n * Whether this request addresses one declared route.\n *\n * The table owns both halves of the comparison, so the path this mount\n * answers and the path the client asks for cannot drift apart: there is\n * one spelling of each, in `src/types/apiRoutes`.\n */\n const addresses = (route: RouteSpec): boolean => url.pathname === route.path && request.method === route.method;\n const currentRepository = repository();\n if (addresses(routes.config)) return Response.json(configView(context, url.searchParams.getAll(KEY_QUERY_PARAM)));\n if (addresses(routes.repositories)) return Response.json({ repositories: context.repositories?.() ?? [] });\n if (addresses(routes.repository)) {\n return currentRepository\n ? Response.json(repositorySettingsView(currentRepository, homeDirectory, environment))\n : Response.json({ error: 'Choose a workspace.' }, { status: 404 });\n }\n // One path, two methods, so the branch is on the path the pair share.\n if (url.pathname === routes.images.path && context.scope === 'global') {\n if (request.method === routes.images.method) return Response.json(await imageView());\n if (request.method === routes.saveImages.method) {\n const body: unknown = await request.json();\n if (\n !isRecord(body) ||\n (body.autoResize !== undefined && typeof body.autoResize !== 'boolean') ||\n (body.maxDimension !== undefined &&\n (typeof body.maxDimension !== 'number' || !Number.isFinite(body.maxDimension)))\n )\n return Response.json({ error: 'Invalid image settings.' }, { status: 400 });\n await savePiImageSettingsAsync(\n fs,\n {\n ...(typeof body.autoResize === 'boolean' ? { autoResize: body.autoResize } : {}),\n ...(typeof body.maxDimension === 'number' ? { maxDimension: body.maxDimension } : {}),\n },\n io,\n homeDirectory,\n );\n context.configurationChanged?.();\n return Response.json(await imageView());\n }\n }\n if (url.pathname === routes.presets.path && context.scope === 'global') {\n if (!homeDirectory) return Response.json({ error: 'Po has no home directory here.' }, { status: 503 });\n if (request.method === routes.presets.method) {\n return Response.json(await readPresetsView(fs, homeDirectory, environment));\n }\n if (request.method === routes.savePresets.method) {\n const body: unknown = await request.json();\n if (!isRecord(body) || typeof body.defaultPreset !== 'string' || !body.defaultPreset.trim())\n return Response.json({ error: 'Choose a preset.' }, { status: 400 });\n const result = await writeDefaultPreset(fs, homeDirectory, environment, body.defaultPreset.trim());\n if ('error' in result) return Response.json(result, { status: 422 });\n context.configurationChanged?.();\n return Response.json(result);\n }\n }\n if (addresses(routes.selection)) {\n const body = parseSelectionWrite(await request.json());\n if (!body)\n return Response.json({ error: 'A selection write needs typed changes and a file hash.' }, { status: 400 });\n if (!currentRepository || body.repositoryId !== currentRepository.id)\n return Response.json({ error: 'Workspace not found in this mount.' }, { status: 404 });\n const before = repositorySettingsView(currentRepository, homeDirectory, environment);\n if (before.hash !== body.expectedHash)\n return Response.json(\n { error: 'The repository config changed since it was read.', hash: before.hash },\n { status: 409 },\n );\n const invalid = validateSelectionChanges(before, body.changes);\n if (invalid) return Response.json({ error: invalid }, { status: 422 });\n await writePoConfigValuesAsync(\n fs,\n repositoryPoConfigPath(currentRepository.path),\n selectionEdits(body.changes),\n io,\n { scope: 'repository' },\n );\n context.configurationChanged?.();\n return Response.json(repositorySettingsView(currentRepository, homeDirectory, environment));\n }\n if (addresses(routes.value)) {\n const body = parseWrite(await request.json());\n if (!body)\n return Response.json({ error: 'A save requires a scope, key, value and file hash.' }, { status: 400 });\n const scope = context.scope === 'global' ? 'global' : 'repository';\n if (body.scope !== scope || (scope === 'repository' && body.repoRoot !== currentRepository?.path))\n return Response.json({ error: 'The write targets a different mount.' }, { status: 403 });\n if (\n body.edits.some((edit) => {\n const allowed = configScopeOf(edit.keyPath);\n return allowed !== 'both' && allowed !== scope;\n })\n )\n return Response.json({ error: 'This key cannot be set at this scope.' }, { status: 409 });\n const before = configView(context, []);\n if (before.hashes[scope] !== body.expectedHash)\n return Response.json(\n { error: 'The config changed since it was read.', hash: before.hashes[scope] },\n { status: 409 },\n );\n const target = pathFor(scope, currentRepository?.path ?? '', homeDirectory, environment);\n await writePoConfigValuesAsync(\n fs,\n target,\n body.edits.map(({ keyPath, value }) => ({ keyPath, ...(value === null ? {} : { value }) })),\n io,\n { scope },\n );\n context.configurationChanged?.();\n return Response.json(\n configView(\n context,\n body.edits.map((edit) => edit.keyPath.join(KEY_SEPARATOR)),\n ),\n );\n }\n return Response.json({ error: 'Configuration route not found.' }, { status: 404 });\n };\n const guarded = async (request: Request): Promise<Response> => {\n try {\n return await dispatch(request);\n } catch (error) {\n return Response.json({ error: describe(error) }, { status: error instanceof SyntaxError ? 400 : 422 });\n }\n };\n return {\n fetch(request) {\n if (request.method === 'GET' || request.method === 'HEAD') return guarded(request);\n const result = writes.then(() => guarded(request));\n writes = result.catch(() => undefined);\n return result;\n },\n close() {\n closed = true;\n },\n };\n },\n});\n"],"mappings":"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"}
|