@agimon-ai/doompi-config 0.0.1-alpha.37 → 0.0.1-alpha.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/adapters/harnessStore.cjs +1 -1
- package/dist/adapters/harnessStore.cjs.map +1 -1
- package/dist/adapters/harnessStore.d.cts.map +1 -1
- package/dist/adapters/harnessStore.d.mts.map +1 -1
- package/dist/adapters/harnessStore.mjs +1 -1
- package/dist/adapters/harnessStore.mjs.map +1 -1
- package/dist/adapters/init.cjs +21 -0
- package/dist/adapters/init.cjs.map +1 -1
- package/dist/adapters/init.d.cts.map +1 -1
- package/dist/adapters/init.d.mts.map +1 -1
- package/dist/adapters/init.mjs +21 -0
- package/dist/adapters/init.mjs.map +1 -1
- package/dist/services/configPolicy.cjs +1 -1
- package/dist/services/configPolicy.cjs.map +1 -1
- package/dist/services/configPolicy.d.cts.map +1 -1
- package/dist/services/configPolicy.d.mts.map +1 -1
- package/dist/services/configPolicy.mjs +1 -1
- package/dist/services/configPolicy.mjs.map +1 -1
- package/dist/types/config.d.cts +10 -0
- package/dist/types/config.d.cts.map +1 -1
- package/dist/types/config.d.mts +10 -0
- package/dist/types/config.d.mts.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./atomicJson.cjs"),n=require("./harnessState.cjs");let r=require("node:fs");r=e.__toESM(r,1);let i=require("node:path");i=e.__toESM(i,1);let a=require("node:os");a=e.__toESM(a,1);const o=`DOOMPI_STATE`;let s;function c(){s=void 0}function l(e,t){let n=JSON.parse(e);if(n.version!==1)throw Error(`Harness state at ${t} has version ${String(n.version)}, expected 1`);if(typeof n.state!=`object`||n.state===null)throw Error(`Harness state at ${t} has no state object`);return{version:1,owner:{pid:n.owner?.pid??-1,startedAt:n.owner?.startedAt??``},state:n.state}}function
|
|
1
|
+
const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./atomicJson.cjs"),n=require("./harnessState.cjs");let r=require("node:fs");r=e.__toESM(r,1);let i=require("node:path");i=e.__toESM(i,1);let a=require("node:os");a=e.__toESM(a,1);const o=`DOOMPI_STATE`;let s;function c(){s=void 0}function l(e){try{return r.default.statSync(e).mtimeMs}catch{return}}function u(e,t){let n=t[o];if(e.filePath!==n)return!1;if(!e.filePath)return!0;try{return r.default.statSync(e.filePath).mtimeMs===e.mtimeMs}catch{return!1}}function d(e,t){let n=JSON.parse(e);if(n.version!==1)throw Error(`Harness state at ${t} has version ${String(n.version)}, expected 1`);if(typeof n.state!=`object`||n.state===null)throw Error(`Harness state at ${t} has no state object`);return{version:1,owner:{pid:n.owner?.pid??-1,startedAt:n.owner?.startedAt??``},state:n.state}}function f(e=process.env,t){let i=e===process.env;if(i&&s&&u(s,e))return s;let a=e[o],c;if(a&&r.default.existsSync(a))try{let e=d(r.default.readFileSync(a,`utf8`),a);c={filePath:a,owned:e.owner.pid===process.pid||e.owner.pid===0,state:e.state,mtimeMs:l(a)}}catch(e){t?.(o,e)}return c??={owned:!1,state:n.readHarnessState(e,t)},i&&(s=c),c}function p(e=process.env){let t=f(e),r={[o]:e[o]};for(let t of Object.values(n.HARNESS_STATE_KEYS))r[t]=e[t];return{filePath:t.filePath,owned:t.owned,state:structuredClone(t.state),environment:r}}function m(e,t=process.env){let n=f(t);if(e.filePath&&e.owned){if(!n.owned||n.filePath!==e.filePath)throw Error(`Harness state ownership changed; refusing to restore an older snapshot.`);v(e.filePath,e.state)}else if(!e.filePath){if(n.filePath&&!n.owned)throw Error(`Harness state is no longer owned by this process; refusing to restore an older snapshot.`);n.filePath&&n.owned&&r.default.rmSync(n.filePath,{force:!0}),c()}else if(n.filePath!==e.filePath)throw Error(`Harness state pointer changed; refusing to restore an older snapshot.`);for(let[n,r]of Object.entries(e.environment))r===void 0?delete t[n]:t[n]=r;t===process.env&&(s=e.filePath?{filePath:e.filePath,owned:e.owned,state:e.state,mtimeMs:l(e.filePath)}:void 0)}function h(e){return f(process.env,e).state}function g(e){return i.default.join(e,`harness-state.json`)}function _(){return i.default.join(a.default.tmpdir(),`doom-harness`,`${String(process.pid)}.json`)}function v(e,n,r=process.pid){t.writePrivateAtomicJson(e,{version:1,owner:{pid:r,startedAt:new Date().toISOString()},state:n})}function y(e,t){let r=g(t.directory);return v(r,e,t.unclaimed?0:process.pid),t.environment[o]=r,n.projectHarnessEnvironment(e,t.environment),!t.unclaimed&&t.environment===process.env&&(s={filePath:r,owned:!0,state:e,mtimeMs:l(r)}),r}function b(e,t=process.env){let r=f(t),i={...r.state,...e},a=r.owned&&r.filePath?r.filePath:_();return v(a,i),t[o]=a,t===process.env&&(s={filePath:a,owned:!0,state:i,mtimeMs:l(a)}),n.projectHarnessEnvironment(e,t),i}function x(e=process.env){let t=s;c(),!(!t?.owned||!t.filePath)&&(r.default.rmSync(t.filePath,{force:!0}),delete e[o])}function S(e=h()){return e.root??process.cwd()}function C(e=h()){if(!e.root)throw Error(`${n.HARNESS_STATE_KEYS.root} is not set`);return e.root}function w(e=h()){let t=C(e);if(!e.temporaryDirectory)throw Error(`${n.HARNESS_STATE_KEYS.temporaryDirectory} is not set`);return{root:t,temporaryDirectory:e.temporaryDirectory}}exports.HARNESS_STATE_POINTER=o,exports.createHarnessSession=y,exports.disposeHarnessState=x,exports.getHarnessState=h,exports.harnessRoot=S,exports.loadHarnessState=f,exports.requireHarnessPaths=w,exports.requireHarnessRoot=C,exports.resetHarnessStore=c,exports.restoreHarnessStateSnapshot=m,exports.snapshotHarnessState=p,exports.updateHarnessState=b;
|
|
2
2
|
//# sourceMappingURL=harnessStore.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harnessStore.cjs","names":["fs","readHarnessState","HARNESS_STATE_KEYS","path","os"],"sources":["../../src/adapters/harnessStore.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport type { HarnessState } from '../types/config.ts';\nimport { writePrivateAtomicJson } from './atomicJson.ts';\nimport {\n HARNESS_STATE_KEYS,\n type HarnessStateParseReporter,\n projectHarnessEnvironment,\n readHarnessState,\n} from './harnessState.ts';\n\n/**\n * Who owns the harness state, and where it lives.\n *\n * The state used to be the environment: twenty variables that every reader\n * re-parsed and every writer mutated globally. The file is the authority now,\n * and the environment is a projection published for the readers that can only\n * see an environment, which are bash hooks, the shell launchers, packages that\n * do not depend on this one, and any process spawned by any of them.\n *\n * OWNERSHIP IS PER PROCESS, AND THAT IS THE WHOLE DESIGN:\n * Doom Team subagents run detached, in their own process group, from an\n * environment snapshot taken at spawn. A child can outlive its parent. If it\n * shared the parent's file it would corrupt a session it does not own, and if\n * that file sat in the parent's run directory the parent's shutdown sweep would\n * delete it from under a live run. So every process writes a file it owns:\n * `owner.pid` records who, and a process that finds someone else's file copies\n * it before its first write.\n *\n * AVOID:\n * - Treating the file as shared mutable state between processes. Atomic writes\n * prevent torn reads, not lost updates\n * - Reading the environment directly for anything this exposes\n */\n\n/** Points at the state file. The one variable that is not derived. */\nexport const HARNESS_STATE_POINTER = 'DOOMPI_STATE';\n\nconst STATE_FILE_VERSION = 1;\nconst STATE_FILE_NAME = 'harness-state.json';\nconst ADOPTED_DIRECTORY = 'doom-harness';\n/**\n * Written for a process that does not exist yet, so the first one to read it\n * owns it. A spawner cannot know its child's pid, and a child that inherited\n * its parent's file would lose it the moment the parent cleaned up.\n */\nconst UNCLAIMED_OWNER = 0;\n\nexport interface HarnessStateFile {\n version: number;\n owner: { pid: number; startedAt: string };\n state: HarnessState;\n}\n\nexport interface HarnessStateTransactionSnapshot {\n readonly filePath?: string;\n readonly owned: boolean;\n readonly state: HarnessState;\n readonly environment: Readonly<Record<string, string | undefined>>;\n}\n\nexport interface LoadedHarnessState {\n /** Undefined when this process is running off the environment fallback. */\n filePath?: string;\n owned: boolean;\n state: HarnessState;\n}\n\nlet loaded: LoadedHarnessState | undefined;\n\n/** Drops the cached state. For tests, and for a process changing identity. */\nexport function resetHarnessStore(): void {\n loaded = undefined;\n}\n\nfunction parseStateFile(source: string, filePath: string): HarnessStateFile {\n const parsed = JSON.parse(source) as Partial<HarnessStateFile>;\n if (parsed.version !== STATE_FILE_VERSION) {\n throw new Error(\n `Harness state at ${filePath} has version ${String(parsed.version)}, expected ${STATE_FILE_VERSION}`,\n );\n }\n if (typeof parsed.state !== 'object' || parsed.state === null) {\n throw new Error(`Harness state at ${filePath} has no state object`);\n }\n return {\n version: STATE_FILE_VERSION,\n owner: { pid: parsed.owner?.pid ?? -1, startedAt: parsed.owner?.startedAt ?? '' },\n state: parsed.state as HarnessState,\n };\n}\n\n/**\n * Reads the state for this process, once.\n *\n * A missing pointer means nobody wrote a file for this process, so the\n * environment is all there is: a nested run, a third-party spawn, or a caller\n * older than the store. A pointer that cannot be read is reported and then\n * treated the same way, because a session that starts degraded beats a session\n * that does not start at all.\n */\nexport function loadHarnessState(\n environment: NodeJS.ProcessEnv = process.env,\n report?: HarnessStateParseReporter,\n): LoadedHarnessState {\n // The cache describes this process. Asking about another environment is a\n // question about another process, so it reads through rather than answering\n // from what this one happens to hold.\n const cacheable = environment === process.env;\n if (cacheable && loaded) return loaded;\n\n const filePath = environment[HARNESS_STATE_POINTER];\n let result: LoadedHarnessState | undefined;\n if (filePath && fs.existsSync(filePath)) {\n try {\n const file = parseStateFile(fs.readFileSync(filePath, 'utf8'), filePath);\n const owned = file.owner.pid === process.pid || file.owner.pid === UNCLAIMED_OWNER;\n result = { filePath, owned, state: file.state };\n } catch (error) {\n report?.(HARNESS_STATE_POINTER, error);\n }\n }\n result ??= { owned: false, state: readHarnessState(environment, report) };\n if (cacheable) loaded = result;\n return result;\n}\n\n/** Captures the owned state pointer and every projected harness variable. */\nexport function snapshotHarnessState(environment: NodeJS.ProcessEnv = process.env): HarnessStateTransactionSnapshot {\n const loadedState = loadHarnessState(environment);\n const environmentSnapshot: Record<string, string | undefined> = {\n [HARNESS_STATE_POINTER]: environment[HARNESS_STATE_POINTER],\n };\n for (const key of Object.values(HARNESS_STATE_KEYS)) environmentSnapshot[key] = environment[key];\n return {\n filePath: loadedState.filePath,\n owned: loadedState.owned,\n state: structuredClone(loadedState.state),\n environment: environmentSnapshot,\n };\n}\n\n/** Restores a snapshot only while the current process still owns its state pointer. */\nexport function restoreHarnessStateSnapshot(\n snapshot: HarnessStateTransactionSnapshot,\n environment: NodeJS.ProcessEnv = process.env,\n): void {\n const current = loadHarnessState(environment);\n if (snapshot.filePath && snapshot.owned) {\n if (!current.owned || current.filePath !== snapshot.filePath) {\n throw new Error('Harness state ownership changed; refusing to restore an older snapshot.');\n }\n persist(snapshot.filePath, snapshot.state);\n } else if (!snapshot.filePath) {\n if (current.filePath && !current.owned) {\n throw new Error('Harness state is no longer owned by this process; refusing to restore an older snapshot.');\n }\n if (current.filePath && current.owned) fs.rmSync(current.filePath, { force: true });\n resetHarnessStore();\n } else if (current.filePath !== snapshot.filePath) {\n throw new Error('Harness state pointer changed; refusing to restore an older snapshot.');\n }\n for (const [key, value] of Object.entries(snapshot.environment)) {\n if (value === undefined) delete environment[key];\n else environment[key] = value;\n }\n if (environment === process.env) {\n loaded = snapshot.filePath\n ? { filePath: snapshot.filePath, owned: snapshot.owned, state: snapshot.state }\n : undefined;\n }\n}\n\nexport function getHarnessState(report?: HarnessStateParseReporter): HarnessState {\n return loadHarnessState(process.env, report).state;\n}\n\nfunction stateFilePath(directory: string): string {\n return path.join(directory, STATE_FILE_NAME);\n}\n\n/** Where a process writes state when it was not given a file of its own. */\nfunction adoptedStateFilePath(): string {\n return path.join(os.tmpdir(), ADOPTED_DIRECTORY, `${String(process.pid)}.json`);\n}\n\nfunction persist(filePath: string, state: HarnessState, ownerPid = process.pid): void {\n writePrivateAtomicJson(filePath, {\n version: STATE_FILE_VERSION,\n owner: { pid: ownerPid, startedAt: new Date().toISOString() },\n state,\n } satisfies HarnessStateFile);\n}\n\n/**\n * Starts a session: writes the file and publishes the derived environment.\n *\n * The environment is a parameter because the caller is usually describing a\n * process other than itself. The launcher builds the environment for the Pi it\n * is about to spawn, and Doom Team builds the snapshot for a detached child, so\n * neither should touch its own `process.env` to do it.\n */\nexport function createHarnessSession(\n state: HarnessState,\n options: {\n directory: string;\n environment: NodeJS.ProcessEnv;\n /**\n * Leave the file for a process that does not exist yet to claim.\n *\n * What a spawner wants: the child owns its state from its first read, in a\n * directory the spawner manages, so the parent cleaning up its own session\n * cannot pull the file out from under a detached run.\n */\n unclaimed?: boolean;\n },\n): string {\n const filePath = stateFilePath(options.directory);\n persist(filePath, state, options.unclaimed ? UNCLAIMED_OWNER : process.pid);\n options.environment[HARNESS_STATE_POINTER] = filePath;\n projectHarnessEnvironment(state, options.environment);\n // Only prime the cache when this process is the one being described.\n if (!options.unclaimed && options.environment === process.env) loaded = { filePath, owned: true, state };\n return filePath;\n}\n\n/**\n * Applies a patch to this process's state.\n *\n * Copy on write: a process holding a file it does not own writes its own copy\n * and repoints itself at it, so a child can never rewrite its parent's session.\n */\nexport function updateHarnessState(\n patch: Partial<HarnessState>,\n environment: NodeJS.ProcessEnv = process.env,\n): HarnessState {\n const current = loadHarnessState(environment);\n const state = { ...current.state, ...patch };\n const filePath = current.owned && current.filePath ? current.filePath : adoptedStateFilePath();\n persist(filePath, state);\n environment[HARNESS_STATE_POINTER] = filePath;\n if (environment === process.env) loaded = { filePath, owned: true, state };\n projectHarnessEnvironment(patch, environment);\n return state;\n}\n\n/** Removes this process's own state file. Safe when there is none. */\nexport function disposeHarnessState(environment: NodeJS.ProcessEnv = process.env): void {\n const current = loaded;\n resetHarnessStore();\n if (!current?.owned || !current.filePath) return;\n fs.rmSync(current.filePath, { force: true });\n delete environment[HARNESS_STATE_POINTER];\n}\n\nexport function harnessRoot(state: Pick<HarnessState, 'root'> = getHarnessState()): string {\n return state.root ?? process.cwd();\n}\n\n/**\n * The repository root, or a hard error.\n *\n * Separate from `requireHarnessPaths` because most callers only need the root,\n * and separate from `harnessRoot` because falling back to `process.cwd()` would\n * quietly operate on whatever directory the session happens to sit in. A caller\n * that asks for the root wants the session's repository or nothing.\n */\nexport function requireHarnessRoot(state: Pick<HarnessState, 'root'> = getHarnessState()): string {\n if (!state.root) throw new Error(`${HARNESS_STATE_KEYS.root} is not set`);\n return state.root;\n}\n\n/** The root and the run's scratch directory, for callers that write to disk. */\nexport function requireHarnessPaths(state: Pick<HarnessState, 'root' | 'temporaryDirectory'> = getHarnessState()): {\n root: string;\n temporaryDirectory: string;\n} {\n const root = requireHarnessRoot(state);\n if (!state.temporaryDirectory) throw new Error(`${HARNESS_STATE_KEYS.temporaryDirectory} is not set`);\n return { root, temporaryDirectory: state.temporaryDirectory };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"harnessStore.cjs","names":["fs","readHarnessState","HARNESS_STATE_KEYS","path","os"],"sources":["../../src/adapters/harnessStore.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport type { HarnessState } from '../types/config.ts';\nimport { writePrivateAtomicJson } from './atomicJson.ts';\nimport {\n HARNESS_STATE_KEYS,\n type HarnessStateParseReporter,\n projectHarnessEnvironment,\n readHarnessState,\n} from './harnessState.ts';\n\n/**\n * Who owns the harness state, and where it lives.\n *\n * The state used to be the environment: twenty variables that every reader\n * re-parsed and every writer mutated globally. The file is the authority now,\n * and the environment is a projection published for the readers that can only\n * see an environment, which are bash hooks, the shell launchers, packages that\n * do not depend on this one, and any process spawned by any of them.\n *\n * OWNERSHIP IS PER PROCESS, AND THAT IS THE WHOLE DESIGN:\n * Doom Team subagents run detached, in their own process group, from an\n * environment snapshot taken at spawn. A child can outlive its parent. If it\n * shared the parent's file it would corrupt a session it does not own, and if\n * that file sat in the parent's run directory the parent's shutdown sweep would\n * delete it from under a live run. So every process writes a file it owns:\n * `owner.pid` records who, and a process that finds someone else's file copies\n * it before its first write.\n *\n * AVOID:\n * - Treating the file as shared mutable state between processes. Atomic writes\n * prevent torn reads, not lost updates\n * - Reading the environment directly for anything this exposes\n */\n\n/** Points at the state file. The one variable that is not derived. */\nexport const HARNESS_STATE_POINTER = 'DOOMPI_STATE';\n\nconst STATE_FILE_VERSION = 1;\nconst STATE_FILE_NAME = 'harness-state.json';\nconst ADOPTED_DIRECTORY = 'doom-harness';\n/**\n * Written for a process that does not exist yet, so the first one to read it\n * owns it. A spawner cannot know its child's pid, and a child that inherited\n * its parent's file would lose it the moment the parent cleaned up.\n */\nconst UNCLAIMED_OWNER = 0;\n\nexport interface HarnessStateFile {\n version: number;\n owner: { pid: number; startedAt: string };\n state: HarnessState;\n}\n\nexport interface HarnessStateTransactionSnapshot {\n readonly filePath?: string;\n readonly owned: boolean;\n readonly state: HarnessState;\n readonly environment: Readonly<Record<string, string | undefined>>;\n}\n\nexport interface LoadedHarnessState {\n /** Undefined when this process is running off the environment fallback. */\n filePath?: string;\n owned: boolean;\n state: HarnessState;\n}\n\ninterface CachedHarnessState extends LoadedHarnessState {\n /** The file's mtime when it was read, so a rewrite by another copy is noticed. */\n mtimeMs?: number;\n}\n\nlet loaded: CachedHarnessState | undefined;\n\n/** Drops the cached state. For tests, and for a process changing identity. */\nexport function resetHarnessStore(): void {\n loaded = undefined;\n}\n\nfunction fileMtime(filePath: string): number | undefined {\n try {\n return fs.statSync(filePath).mtimeMs;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Whether the cache still describes what the process points at.\n *\n * A session can load more than one copy of this package (a layer package\n * installed under .pi/npm brings its own), and each copy caches on its own.\n * The pointer and the file are shared, so a switch persisted through one copy\n * moves the pointer or rewrites the file, and every other copy must notice\n * rather than answer from memory.\n */\nfunction cacheCurrent(cache: CachedHarnessState, environment: NodeJS.ProcessEnv): boolean {\n const pointer = environment[HARNESS_STATE_POINTER];\n if (cache.filePath !== pointer) return false;\n if (!cache.filePath) return true;\n try {\n return fs.statSync(cache.filePath).mtimeMs === cache.mtimeMs;\n } catch {\n return false;\n }\n}\n\nfunction parseStateFile(source: string, filePath: string): HarnessStateFile {\n const parsed = JSON.parse(source) as Partial<HarnessStateFile>;\n if (parsed.version !== STATE_FILE_VERSION) {\n throw new Error(\n `Harness state at ${filePath} has version ${String(parsed.version)}, expected ${STATE_FILE_VERSION}`,\n );\n }\n if (typeof parsed.state !== 'object' || parsed.state === null) {\n throw new Error(`Harness state at ${filePath} has no state object`);\n }\n return {\n version: STATE_FILE_VERSION,\n owner: { pid: parsed.owner?.pid ?? -1, startedAt: parsed.owner?.startedAt ?? '' },\n state: parsed.state as HarnessState,\n };\n}\n\n/**\n * Reads the state for this process, once.\n *\n * A missing pointer means nobody wrote a file for this process, so the\n * environment is all there is: a nested run, a third-party spawn, or a caller\n * older than the store. A pointer that cannot be read is reported and then\n * treated the same way, because a session that starts degraded beats a session\n * that does not start at all.\n */\nexport function loadHarnessState(\n environment: NodeJS.ProcessEnv = process.env,\n report?: HarnessStateParseReporter,\n): LoadedHarnessState {\n // The cache describes this process. Asking about another environment is a\n // question about another process, so it reads through rather than answering\n // from what this one happens to hold.\n const cacheable = environment === process.env;\n if (cacheable && loaded && cacheCurrent(loaded, environment)) return loaded;\n\n const filePath = environment[HARNESS_STATE_POINTER];\n let result: CachedHarnessState | undefined;\n if (filePath && fs.existsSync(filePath)) {\n try {\n const file = parseStateFile(fs.readFileSync(filePath, 'utf8'), filePath);\n const owned = file.owner.pid === process.pid || file.owner.pid === UNCLAIMED_OWNER;\n result = { filePath, owned, state: file.state, mtimeMs: fileMtime(filePath) };\n } catch (error) {\n report?.(HARNESS_STATE_POINTER, error);\n }\n }\n result ??= { owned: false, state: readHarnessState(environment, report) };\n if (cacheable) loaded = result;\n return result;\n}\n\n/** Captures the owned state pointer and every projected harness variable. */\nexport function snapshotHarnessState(environment: NodeJS.ProcessEnv = process.env): HarnessStateTransactionSnapshot {\n const loadedState = loadHarnessState(environment);\n const environmentSnapshot: Record<string, string | undefined> = {\n [HARNESS_STATE_POINTER]: environment[HARNESS_STATE_POINTER],\n };\n for (const key of Object.values(HARNESS_STATE_KEYS)) environmentSnapshot[key] = environment[key];\n return {\n filePath: loadedState.filePath,\n owned: loadedState.owned,\n state: structuredClone(loadedState.state),\n environment: environmentSnapshot,\n };\n}\n\n/** Restores a snapshot only while the current process still owns its state pointer. */\nexport function restoreHarnessStateSnapshot(\n snapshot: HarnessStateTransactionSnapshot,\n environment: NodeJS.ProcessEnv = process.env,\n): void {\n const current = loadHarnessState(environment);\n if (snapshot.filePath && snapshot.owned) {\n if (!current.owned || current.filePath !== snapshot.filePath) {\n throw new Error('Harness state ownership changed; refusing to restore an older snapshot.');\n }\n persist(snapshot.filePath, snapshot.state);\n } else if (!snapshot.filePath) {\n if (current.filePath && !current.owned) {\n throw new Error('Harness state is no longer owned by this process; refusing to restore an older snapshot.');\n }\n if (current.filePath && current.owned) fs.rmSync(current.filePath, { force: true });\n resetHarnessStore();\n } else if (current.filePath !== snapshot.filePath) {\n throw new Error('Harness state pointer changed; refusing to restore an older snapshot.');\n }\n for (const [key, value] of Object.entries(snapshot.environment)) {\n if (value === undefined) delete environment[key];\n else environment[key] = value;\n }\n if (environment === process.env) {\n loaded = snapshot.filePath\n ? {\n filePath: snapshot.filePath,\n owned: snapshot.owned,\n state: snapshot.state,\n mtimeMs: fileMtime(snapshot.filePath),\n }\n : undefined;\n }\n}\n\nexport function getHarnessState(report?: HarnessStateParseReporter): HarnessState {\n return loadHarnessState(process.env, report).state;\n}\n\nfunction stateFilePath(directory: string): string {\n return path.join(directory, STATE_FILE_NAME);\n}\n\n/** Where a process writes state when it was not given a file of its own. */\nfunction adoptedStateFilePath(): string {\n return path.join(os.tmpdir(), ADOPTED_DIRECTORY, `${String(process.pid)}.json`);\n}\n\nfunction persist(filePath: string, state: HarnessState, ownerPid = process.pid): void {\n writePrivateAtomicJson(filePath, {\n version: STATE_FILE_VERSION,\n owner: { pid: ownerPid, startedAt: new Date().toISOString() },\n state,\n } satisfies HarnessStateFile);\n}\n\n/**\n * Starts a session: writes the file and publishes the derived environment.\n *\n * The environment is a parameter because the caller is usually describing a\n * process other than itself. The launcher builds the environment for the Pi it\n * is about to spawn, and Doom Team builds the snapshot for a detached child, so\n * neither should touch its own `process.env` to do it.\n */\nexport function createHarnessSession(\n state: HarnessState,\n options: {\n directory: string;\n environment: NodeJS.ProcessEnv;\n /**\n * Leave the file for a process that does not exist yet to claim.\n *\n * What a spawner wants: the child owns its state from its first read, in a\n * directory the spawner manages, so the parent cleaning up its own session\n * cannot pull the file out from under a detached run.\n */\n unclaimed?: boolean;\n },\n): string {\n const filePath = stateFilePath(options.directory);\n persist(filePath, state, options.unclaimed ? UNCLAIMED_OWNER : process.pid);\n options.environment[HARNESS_STATE_POINTER] = filePath;\n projectHarnessEnvironment(state, options.environment);\n // Only prime the cache when this process is the one being described.\n if (!options.unclaimed && options.environment === process.env)\n loaded = { filePath, owned: true, state, mtimeMs: fileMtime(filePath) };\n return filePath;\n}\n\n/**\n * Applies a patch to this process's state.\n *\n * Copy on write: a process holding a file it does not own writes its own copy\n * and repoints itself at it, so a child can never rewrite its parent's session.\n */\nexport function updateHarnessState(\n patch: Partial<HarnessState>,\n environment: NodeJS.ProcessEnv = process.env,\n): HarnessState {\n const current = loadHarnessState(environment);\n const state = { ...current.state, ...patch };\n const filePath = current.owned && current.filePath ? current.filePath : adoptedStateFilePath();\n persist(filePath, state);\n environment[HARNESS_STATE_POINTER] = filePath;\n if (environment === process.env) loaded = { filePath, owned: true, state, mtimeMs: fileMtime(filePath) };\n projectHarnessEnvironment(patch, environment);\n return state;\n}\n\n/** Removes this process's own state file. Safe when there is none. */\nexport function disposeHarnessState(environment: NodeJS.ProcessEnv = process.env): void {\n const current = loaded;\n resetHarnessStore();\n if (!current?.owned || !current.filePath) return;\n fs.rmSync(current.filePath, { force: true });\n delete environment[HARNESS_STATE_POINTER];\n}\n\nexport function harnessRoot(state: Pick<HarnessState, 'root'> = getHarnessState()): string {\n return state.root ?? process.cwd();\n}\n\n/**\n * The repository root, or a hard error.\n *\n * Separate from `requireHarnessPaths` because most callers only need the root,\n * and separate from `harnessRoot` because falling back to `process.cwd()` would\n * quietly operate on whatever directory the session happens to sit in. A caller\n * that asks for the root wants the session's repository or nothing.\n */\nexport function requireHarnessRoot(state: Pick<HarnessState, 'root'> = getHarnessState()): string {\n if (!state.root) throw new Error(`${HARNESS_STATE_KEYS.root} is not set`);\n return state.root;\n}\n\n/** The root and the run's scratch directory, for callers that write to disk. */\nexport function requireHarnessPaths(state: Pick<HarnessState, 'root' | 'temporaryDirectory'> = getHarnessState()): {\n root: string;\n temporaryDirectory: string;\n} {\n const root = requireHarnessRoot(state);\n if (!state.temporaryDirectory) throw new Error(`${HARNESS_STATE_KEYS.temporaryDirectory} is not set`);\n return { root, temporaryDirectory: state.temporaryDirectory };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harnessStore.d.cts","names":[],"sources":["../../src/adapters/harnessStore.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;cAqCa;UAYI;EACf;EACA;IAAS;IAAa;;EACtB,OAAO;;UAGQ;WACN;WACA;WACA,OAAO;WACP,aAAa,SAAS;;UAGhB;;EAEf;EACA;EACA,OAAO;;;
|
|
1
|
+
{"version":3,"file":"harnessStore.d.cts","names":[],"sources":["../../src/adapters/harnessStore.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;cAqCa;UAYI;EACf;EACA;IAAS;IAAa;;EACtB,OAAO;;UAGQ;WACN;WACA;WACA,OAAO;WACP,aAAa,SAAS;;UAGhB;;EAEf;EACA;EACA,OAAO;;;iBAWO;;;;;;;;;;iBA0DA,iBACd,cAAa,OAAO,YACpB,SAAS,4BACR;;iBAwBa,qBAAqB,cAAa,OAAO,aAA2B;;iBAepE,4BACd,UAAU,iCACV,cAAa,OAAO;iBAiCN,gBAAgB,SAAS,4BAA4B;;;;;;;;;iBA6BrD,qBACd,OAAO,cACP;EACE;EACA,aAAa,OAAO;;;;;;;;EAQpB;;;;;;;;iBAmBY,mBACd,OAAO,QAAQ,eACf,cAAa,OAAO,aACnB;;iBAYa,oBAAoB,cAAa,OAAO;iBAQxC,YAAY,QAAO,KAAK;;;;;;;;;iBAYxB,mBAAmB,QAAO,KAAK;;iBAM/B,oBAAoB,QAAO,KAAK;EAC9C;EACA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harnessStore.d.mts","names":[],"sources":["../../src/adapters/harnessStore.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;cAqCa;UAYI;EACf;EACA;IAAS;IAAa;;EACtB,OAAO;;UAGQ;WACN;WACA;WACA,OAAO;WACP,aAAa,SAAS;;UAGhB;;EAEf;EACA;EACA,OAAO;;;
|
|
1
|
+
{"version":3,"file":"harnessStore.d.mts","names":[],"sources":["../../src/adapters/harnessStore.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;cAqCa;UAYI;EACf;EACA;IAAS;IAAa;;EACtB,OAAO;;UAGQ;WACN;WACA;WACA,OAAO;WACP,aAAa,SAAS;;UAGhB;;EAEf;EACA;EACA,OAAO;;;iBAWO;;;;;;;;;;iBA0DA,iBACd,cAAa,OAAO,YACpB,SAAS,4BACR;;iBAwBa,qBAAqB,cAAa,OAAO,aAA2B;;iBAepE,4BACd,UAAU,iCACV,cAAa,OAAO;iBAiCN,gBAAgB,SAAS,4BAA4B;;;;;;;;;iBA6BrD,qBACd,OAAO,cACP;EACE;EACA,aAAa,OAAO;;;;;;;;EAQpB;;;;;;;;iBAmBY,mBACd,OAAO,QAAQ,eACf,cAAa,OAAO,aACnB;;iBAYa,oBAAoB,cAAa,OAAO;iBAQxC,YAAY,QAAO,KAAK;;;;;;;;;iBAYxB,mBAAmB,QAAO,KAAK;;iBAM/B,oBAAoB,QAAO,KAAK;EAC9C;EACA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{writePrivateAtomicJson as e}from"./atomicJson.mjs";import{HARNESS_STATE_KEYS as t,projectHarnessEnvironment as n,readHarnessState as r}from"./harnessState.mjs";import i from"node:fs";import a from"node:path";import o from"node:os";const s=`DOOMPI_STATE`;let c;function l(){c=void 0}function u(e,t){let n=JSON.parse(e);if(n.version!==1)throw Error(`Harness state at ${t} has version ${String(n.version)}, expected 1`);if(typeof n.state!=`object`||n.state===null)throw Error(`Harness state at ${t} has no state object`);return{version:1,owner:{pid:n.owner?.pid??-1,startedAt:n.owner?.startedAt??``},state:n.state}}function
|
|
1
|
+
import{writePrivateAtomicJson as e}from"./atomicJson.mjs";import{HARNESS_STATE_KEYS as t,projectHarnessEnvironment as n,readHarnessState as r}from"./harnessState.mjs";import i from"node:fs";import a from"node:path";import o from"node:os";const s=`DOOMPI_STATE`;let c;function l(){c=void 0}function u(e){try{return i.statSync(e).mtimeMs}catch{return}}function d(e,t){let n=t[s];if(e.filePath!==n)return!1;if(!e.filePath)return!0;try{return i.statSync(e.filePath).mtimeMs===e.mtimeMs}catch{return!1}}function f(e,t){let n=JSON.parse(e);if(n.version!==1)throw Error(`Harness state at ${t} has version ${String(n.version)}, expected 1`);if(typeof n.state!=`object`||n.state===null)throw Error(`Harness state at ${t} has no state object`);return{version:1,owner:{pid:n.owner?.pid??-1,startedAt:n.owner?.startedAt??``},state:n.state}}function p(e=process.env,t){let n=e===process.env;if(n&&c&&d(c,e))return c;let a=e[s],o;if(a&&i.existsSync(a))try{let e=f(i.readFileSync(a,`utf8`),a);o={filePath:a,owned:e.owner.pid===process.pid||e.owner.pid===0,state:e.state,mtimeMs:u(a)}}catch(e){t?.(s,e)}return o??={owned:!1,state:r(e,t)},n&&(c=o),o}function m(e=process.env){let n=p(e),r={[s]:e[s]};for(let n of Object.values(t))r[n]=e[n];return{filePath:n.filePath,owned:n.owned,state:structuredClone(n.state),environment:r}}function h(e,t=process.env){let n=p(t);if(e.filePath&&e.owned){if(!n.owned||n.filePath!==e.filePath)throw Error(`Harness state ownership changed; refusing to restore an older snapshot.`);y(e.filePath,e.state)}else if(!e.filePath){if(n.filePath&&!n.owned)throw Error(`Harness state is no longer owned by this process; refusing to restore an older snapshot.`);n.filePath&&n.owned&&i.rmSync(n.filePath,{force:!0}),l()}else if(n.filePath!==e.filePath)throw Error(`Harness state pointer changed; refusing to restore an older snapshot.`);for(let[n,r]of Object.entries(e.environment))r===void 0?delete t[n]:t[n]=r;t===process.env&&(c=e.filePath?{filePath:e.filePath,owned:e.owned,state:e.state,mtimeMs:u(e.filePath)}:void 0)}function g(e){return p(process.env,e).state}function _(e){return a.join(e,`harness-state.json`)}function v(){return a.join(o.tmpdir(),`doom-harness`,`${String(process.pid)}.json`)}function y(t,n,r=process.pid){e(t,{version:1,owner:{pid:r,startedAt:new Date().toISOString()},state:n})}function b(e,t){let r=_(t.directory);return y(r,e,t.unclaimed?0:process.pid),t.environment[s]=r,n(e,t.environment),!t.unclaimed&&t.environment===process.env&&(c={filePath:r,owned:!0,state:e,mtimeMs:u(r)}),r}function x(e,t=process.env){let r=p(t),i={...r.state,...e},a=r.owned&&r.filePath?r.filePath:v();return y(a,i),t[s]=a,t===process.env&&(c={filePath:a,owned:!0,state:i,mtimeMs:u(a)}),n(e,t),i}function S(e=process.env){let t=c;l(),!(!t?.owned||!t.filePath)&&(i.rmSync(t.filePath,{force:!0}),delete e[s])}function C(e=g()){return e.root??process.cwd()}function w(e=g()){if(!e.root)throw Error(`${t.root} is not set`);return e.root}function T(e=g()){let n=w(e);if(!e.temporaryDirectory)throw Error(`${t.temporaryDirectory} is not set`);return{root:n,temporaryDirectory:e.temporaryDirectory}}export{s as HARNESS_STATE_POINTER,b as createHarnessSession,S as disposeHarnessState,g as getHarnessState,C as harnessRoot,p as loadHarnessState,T as requireHarnessPaths,w as requireHarnessRoot,l as resetHarnessStore,h as restoreHarnessStateSnapshot,m as snapshotHarnessState,x as updateHarnessState};
|
|
2
2
|
//# sourceMappingURL=harnessStore.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harnessStore.mjs","names":[],"sources":["../../src/adapters/harnessStore.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport type { HarnessState } from '../types/config.ts';\nimport { writePrivateAtomicJson } from './atomicJson.ts';\nimport {\n HARNESS_STATE_KEYS,\n type HarnessStateParseReporter,\n projectHarnessEnvironment,\n readHarnessState,\n} from './harnessState.ts';\n\n/**\n * Who owns the harness state, and where it lives.\n *\n * The state used to be the environment: twenty variables that every reader\n * re-parsed and every writer mutated globally. The file is the authority now,\n * and the environment is a projection published for the readers that can only\n * see an environment, which are bash hooks, the shell launchers, packages that\n * do not depend on this one, and any process spawned by any of them.\n *\n * OWNERSHIP IS PER PROCESS, AND THAT IS THE WHOLE DESIGN:\n * Doom Team subagents run detached, in their own process group, from an\n * environment snapshot taken at spawn. A child can outlive its parent. If it\n * shared the parent's file it would corrupt a session it does not own, and if\n * that file sat in the parent's run directory the parent's shutdown sweep would\n * delete it from under a live run. So every process writes a file it owns:\n * `owner.pid` records who, and a process that finds someone else's file copies\n * it before its first write.\n *\n * AVOID:\n * - Treating the file as shared mutable state between processes. Atomic writes\n * prevent torn reads, not lost updates\n * - Reading the environment directly for anything this exposes\n */\n\n/** Points at the state file. The one variable that is not derived. */\nexport const HARNESS_STATE_POINTER = 'DOOMPI_STATE';\n\nconst STATE_FILE_VERSION = 1;\nconst STATE_FILE_NAME = 'harness-state.json';\nconst ADOPTED_DIRECTORY = 'doom-harness';\n/**\n * Written for a process that does not exist yet, so the first one to read it\n * owns it. A spawner cannot know its child's pid, and a child that inherited\n * its parent's file would lose it the moment the parent cleaned up.\n */\nconst UNCLAIMED_OWNER = 0;\n\nexport interface HarnessStateFile {\n version: number;\n owner: { pid: number; startedAt: string };\n state: HarnessState;\n}\n\nexport interface HarnessStateTransactionSnapshot {\n readonly filePath?: string;\n readonly owned: boolean;\n readonly state: HarnessState;\n readonly environment: Readonly<Record<string, string | undefined>>;\n}\n\nexport interface LoadedHarnessState {\n /** Undefined when this process is running off the environment fallback. */\n filePath?: string;\n owned: boolean;\n state: HarnessState;\n}\n\nlet loaded: LoadedHarnessState | undefined;\n\n/** Drops the cached state. For tests, and for a process changing identity. */\nexport function resetHarnessStore(): void {\n loaded = undefined;\n}\n\nfunction parseStateFile(source: string, filePath: string): HarnessStateFile {\n const parsed = JSON.parse(source) as Partial<HarnessStateFile>;\n if (parsed.version !== STATE_FILE_VERSION) {\n throw new Error(\n `Harness state at ${filePath} has version ${String(parsed.version)}, expected ${STATE_FILE_VERSION}`,\n );\n }\n if (typeof parsed.state !== 'object' || parsed.state === null) {\n throw new Error(`Harness state at ${filePath} has no state object`);\n }\n return {\n version: STATE_FILE_VERSION,\n owner: { pid: parsed.owner?.pid ?? -1, startedAt: parsed.owner?.startedAt ?? '' },\n state: parsed.state as HarnessState,\n };\n}\n\n/**\n * Reads the state for this process, once.\n *\n * A missing pointer means nobody wrote a file for this process, so the\n * environment is all there is: a nested run, a third-party spawn, or a caller\n * older than the store. A pointer that cannot be read is reported and then\n * treated the same way, because a session that starts degraded beats a session\n * that does not start at all.\n */\nexport function loadHarnessState(\n environment: NodeJS.ProcessEnv = process.env,\n report?: HarnessStateParseReporter,\n): LoadedHarnessState {\n // The cache describes this process. Asking about another environment is a\n // question about another process, so it reads through rather than answering\n // from what this one happens to hold.\n const cacheable = environment === process.env;\n if (cacheable && loaded) return loaded;\n\n const filePath = environment[HARNESS_STATE_POINTER];\n let result: LoadedHarnessState | undefined;\n if (filePath && fs.existsSync(filePath)) {\n try {\n const file = parseStateFile(fs.readFileSync(filePath, 'utf8'), filePath);\n const owned = file.owner.pid === process.pid || file.owner.pid === UNCLAIMED_OWNER;\n result = { filePath, owned, state: file.state };\n } catch (error) {\n report?.(HARNESS_STATE_POINTER, error);\n }\n }\n result ??= { owned: false, state: readHarnessState(environment, report) };\n if (cacheable) loaded = result;\n return result;\n}\n\n/** Captures the owned state pointer and every projected harness variable. */\nexport function snapshotHarnessState(environment: NodeJS.ProcessEnv = process.env): HarnessStateTransactionSnapshot {\n const loadedState = loadHarnessState(environment);\n const environmentSnapshot: Record<string, string | undefined> = {\n [HARNESS_STATE_POINTER]: environment[HARNESS_STATE_POINTER],\n };\n for (const key of Object.values(HARNESS_STATE_KEYS)) environmentSnapshot[key] = environment[key];\n return {\n filePath: loadedState.filePath,\n owned: loadedState.owned,\n state: structuredClone(loadedState.state),\n environment: environmentSnapshot,\n };\n}\n\n/** Restores a snapshot only while the current process still owns its state pointer. */\nexport function restoreHarnessStateSnapshot(\n snapshot: HarnessStateTransactionSnapshot,\n environment: NodeJS.ProcessEnv = process.env,\n): void {\n const current = loadHarnessState(environment);\n if (snapshot.filePath && snapshot.owned) {\n if (!current.owned || current.filePath !== snapshot.filePath) {\n throw new Error('Harness state ownership changed; refusing to restore an older snapshot.');\n }\n persist(snapshot.filePath, snapshot.state);\n } else if (!snapshot.filePath) {\n if (current.filePath && !current.owned) {\n throw new Error('Harness state is no longer owned by this process; refusing to restore an older snapshot.');\n }\n if (current.filePath && current.owned) fs.rmSync(current.filePath, { force: true });\n resetHarnessStore();\n } else if (current.filePath !== snapshot.filePath) {\n throw new Error('Harness state pointer changed; refusing to restore an older snapshot.');\n }\n for (const [key, value] of Object.entries(snapshot.environment)) {\n if (value === undefined) delete environment[key];\n else environment[key] = value;\n }\n if (environment === process.env) {\n loaded = snapshot.filePath\n ? { filePath: snapshot.filePath, owned: snapshot.owned, state: snapshot.state }\n : undefined;\n }\n}\n\nexport function getHarnessState(report?: HarnessStateParseReporter): HarnessState {\n return loadHarnessState(process.env, report).state;\n}\n\nfunction stateFilePath(directory: string): string {\n return path.join(directory, STATE_FILE_NAME);\n}\n\n/** Where a process writes state when it was not given a file of its own. */\nfunction adoptedStateFilePath(): string {\n return path.join(os.tmpdir(), ADOPTED_DIRECTORY, `${String(process.pid)}.json`);\n}\n\nfunction persist(filePath: string, state: HarnessState, ownerPid = process.pid): void {\n writePrivateAtomicJson(filePath, {\n version: STATE_FILE_VERSION,\n owner: { pid: ownerPid, startedAt: new Date().toISOString() },\n state,\n } satisfies HarnessStateFile);\n}\n\n/**\n * Starts a session: writes the file and publishes the derived environment.\n *\n * The environment is a parameter because the caller is usually describing a\n * process other than itself. The launcher builds the environment for the Pi it\n * is about to spawn, and Doom Team builds the snapshot for a detached child, so\n * neither should touch its own `process.env` to do it.\n */\nexport function createHarnessSession(\n state: HarnessState,\n options: {\n directory: string;\n environment: NodeJS.ProcessEnv;\n /**\n * Leave the file for a process that does not exist yet to claim.\n *\n * What a spawner wants: the child owns its state from its first read, in a\n * directory the spawner manages, so the parent cleaning up its own session\n * cannot pull the file out from under a detached run.\n */\n unclaimed?: boolean;\n },\n): string {\n const filePath = stateFilePath(options.directory);\n persist(filePath, state, options.unclaimed ? UNCLAIMED_OWNER : process.pid);\n options.environment[HARNESS_STATE_POINTER] = filePath;\n projectHarnessEnvironment(state, options.environment);\n // Only prime the cache when this process is the one being described.\n if (!options.unclaimed && options.environment === process.env) loaded = { filePath, owned: true, state };\n return filePath;\n}\n\n/**\n * Applies a patch to this process's state.\n *\n * Copy on write: a process holding a file it does not own writes its own copy\n * and repoints itself at it, so a child can never rewrite its parent's session.\n */\nexport function updateHarnessState(\n patch: Partial<HarnessState>,\n environment: NodeJS.ProcessEnv = process.env,\n): HarnessState {\n const current = loadHarnessState(environment);\n const state = { ...current.state, ...patch };\n const filePath = current.owned && current.filePath ? current.filePath : adoptedStateFilePath();\n persist(filePath, state);\n environment[HARNESS_STATE_POINTER] = filePath;\n if (environment === process.env) loaded = { filePath, owned: true, state };\n projectHarnessEnvironment(patch, environment);\n return state;\n}\n\n/** Removes this process's own state file. Safe when there is none. */\nexport function disposeHarnessState(environment: NodeJS.ProcessEnv = process.env): void {\n const current = loaded;\n resetHarnessStore();\n if (!current?.owned || !current.filePath) return;\n fs.rmSync(current.filePath, { force: true });\n delete environment[HARNESS_STATE_POINTER];\n}\n\nexport function harnessRoot(state: Pick<HarnessState, 'root'> = getHarnessState()): string {\n return state.root ?? process.cwd();\n}\n\n/**\n * The repository root, or a hard error.\n *\n * Separate from `requireHarnessPaths` because most callers only need the root,\n * and separate from `harnessRoot` because falling back to `process.cwd()` would\n * quietly operate on whatever directory the session happens to sit in. A caller\n * that asks for the root wants the session's repository or nothing.\n */\nexport function requireHarnessRoot(state: Pick<HarnessState, 'root'> = getHarnessState()): string {\n if (!state.root) throw new Error(`${HARNESS_STATE_KEYS.root} is not set`);\n return state.root;\n}\n\n/** The root and the run's scratch directory, for callers that write to disk. */\nexport function requireHarnessPaths(state: Pick<HarnessState, 'root' | 'temporaryDirectory'> = getHarnessState()): {\n root: string;\n temporaryDirectory: string;\n} {\n const root = requireHarnessRoot(state);\n if (!state.temporaryDirectory) throw new Error(`${HARNESS_STATE_KEYS.temporaryDirectory} is not set`);\n return { root, temporaryDirectory: state.temporaryDirectory };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"harnessStore.mjs","names":[],"sources":["../../src/adapters/harnessStore.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport type { HarnessState } from '../types/config.ts';\nimport { writePrivateAtomicJson } from './atomicJson.ts';\nimport {\n HARNESS_STATE_KEYS,\n type HarnessStateParseReporter,\n projectHarnessEnvironment,\n readHarnessState,\n} from './harnessState.ts';\n\n/**\n * Who owns the harness state, and where it lives.\n *\n * The state used to be the environment: twenty variables that every reader\n * re-parsed and every writer mutated globally. The file is the authority now,\n * and the environment is a projection published for the readers that can only\n * see an environment, which are bash hooks, the shell launchers, packages that\n * do not depend on this one, and any process spawned by any of them.\n *\n * OWNERSHIP IS PER PROCESS, AND THAT IS THE WHOLE DESIGN:\n * Doom Team subagents run detached, in their own process group, from an\n * environment snapshot taken at spawn. A child can outlive its parent. If it\n * shared the parent's file it would corrupt a session it does not own, and if\n * that file sat in the parent's run directory the parent's shutdown sweep would\n * delete it from under a live run. So every process writes a file it owns:\n * `owner.pid` records who, and a process that finds someone else's file copies\n * it before its first write.\n *\n * AVOID:\n * - Treating the file as shared mutable state between processes. Atomic writes\n * prevent torn reads, not lost updates\n * - Reading the environment directly for anything this exposes\n */\n\n/** Points at the state file. The one variable that is not derived. */\nexport const HARNESS_STATE_POINTER = 'DOOMPI_STATE';\n\nconst STATE_FILE_VERSION = 1;\nconst STATE_FILE_NAME = 'harness-state.json';\nconst ADOPTED_DIRECTORY = 'doom-harness';\n/**\n * Written for a process that does not exist yet, so the first one to read it\n * owns it. A spawner cannot know its child's pid, and a child that inherited\n * its parent's file would lose it the moment the parent cleaned up.\n */\nconst UNCLAIMED_OWNER = 0;\n\nexport interface HarnessStateFile {\n version: number;\n owner: { pid: number; startedAt: string };\n state: HarnessState;\n}\n\nexport interface HarnessStateTransactionSnapshot {\n readonly filePath?: string;\n readonly owned: boolean;\n readonly state: HarnessState;\n readonly environment: Readonly<Record<string, string | undefined>>;\n}\n\nexport interface LoadedHarnessState {\n /** Undefined when this process is running off the environment fallback. */\n filePath?: string;\n owned: boolean;\n state: HarnessState;\n}\n\ninterface CachedHarnessState extends LoadedHarnessState {\n /** The file's mtime when it was read, so a rewrite by another copy is noticed. */\n mtimeMs?: number;\n}\n\nlet loaded: CachedHarnessState | undefined;\n\n/** Drops the cached state. For tests, and for a process changing identity. */\nexport function resetHarnessStore(): void {\n loaded = undefined;\n}\n\nfunction fileMtime(filePath: string): number | undefined {\n try {\n return fs.statSync(filePath).mtimeMs;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Whether the cache still describes what the process points at.\n *\n * A session can load more than one copy of this package (a layer package\n * installed under .pi/npm brings its own), and each copy caches on its own.\n * The pointer and the file are shared, so a switch persisted through one copy\n * moves the pointer or rewrites the file, and every other copy must notice\n * rather than answer from memory.\n */\nfunction cacheCurrent(cache: CachedHarnessState, environment: NodeJS.ProcessEnv): boolean {\n const pointer = environment[HARNESS_STATE_POINTER];\n if (cache.filePath !== pointer) return false;\n if (!cache.filePath) return true;\n try {\n return fs.statSync(cache.filePath).mtimeMs === cache.mtimeMs;\n } catch {\n return false;\n }\n}\n\nfunction parseStateFile(source: string, filePath: string): HarnessStateFile {\n const parsed = JSON.parse(source) as Partial<HarnessStateFile>;\n if (parsed.version !== STATE_FILE_VERSION) {\n throw new Error(\n `Harness state at ${filePath} has version ${String(parsed.version)}, expected ${STATE_FILE_VERSION}`,\n );\n }\n if (typeof parsed.state !== 'object' || parsed.state === null) {\n throw new Error(`Harness state at ${filePath} has no state object`);\n }\n return {\n version: STATE_FILE_VERSION,\n owner: { pid: parsed.owner?.pid ?? -1, startedAt: parsed.owner?.startedAt ?? '' },\n state: parsed.state as HarnessState,\n };\n}\n\n/**\n * Reads the state for this process, once.\n *\n * A missing pointer means nobody wrote a file for this process, so the\n * environment is all there is: a nested run, a third-party spawn, or a caller\n * older than the store. A pointer that cannot be read is reported and then\n * treated the same way, because a session that starts degraded beats a session\n * that does not start at all.\n */\nexport function loadHarnessState(\n environment: NodeJS.ProcessEnv = process.env,\n report?: HarnessStateParseReporter,\n): LoadedHarnessState {\n // The cache describes this process. Asking about another environment is a\n // question about another process, so it reads through rather than answering\n // from what this one happens to hold.\n const cacheable = environment === process.env;\n if (cacheable && loaded && cacheCurrent(loaded, environment)) return loaded;\n\n const filePath = environment[HARNESS_STATE_POINTER];\n let result: CachedHarnessState | undefined;\n if (filePath && fs.existsSync(filePath)) {\n try {\n const file = parseStateFile(fs.readFileSync(filePath, 'utf8'), filePath);\n const owned = file.owner.pid === process.pid || file.owner.pid === UNCLAIMED_OWNER;\n result = { filePath, owned, state: file.state, mtimeMs: fileMtime(filePath) };\n } catch (error) {\n report?.(HARNESS_STATE_POINTER, error);\n }\n }\n result ??= { owned: false, state: readHarnessState(environment, report) };\n if (cacheable) loaded = result;\n return result;\n}\n\n/** Captures the owned state pointer and every projected harness variable. */\nexport function snapshotHarnessState(environment: NodeJS.ProcessEnv = process.env): HarnessStateTransactionSnapshot {\n const loadedState = loadHarnessState(environment);\n const environmentSnapshot: Record<string, string | undefined> = {\n [HARNESS_STATE_POINTER]: environment[HARNESS_STATE_POINTER],\n };\n for (const key of Object.values(HARNESS_STATE_KEYS)) environmentSnapshot[key] = environment[key];\n return {\n filePath: loadedState.filePath,\n owned: loadedState.owned,\n state: structuredClone(loadedState.state),\n environment: environmentSnapshot,\n };\n}\n\n/** Restores a snapshot only while the current process still owns its state pointer. */\nexport function restoreHarnessStateSnapshot(\n snapshot: HarnessStateTransactionSnapshot,\n environment: NodeJS.ProcessEnv = process.env,\n): void {\n const current = loadHarnessState(environment);\n if (snapshot.filePath && snapshot.owned) {\n if (!current.owned || current.filePath !== snapshot.filePath) {\n throw new Error('Harness state ownership changed; refusing to restore an older snapshot.');\n }\n persist(snapshot.filePath, snapshot.state);\n } else if (!snapshot.filePath) {\n if (current.filePath && !current.owned) {\n throw new Error('Harness state is no longer owned by this process; refusing to restore an older snapshot.');\n }\n if (current.filePath && current.owned) fs.rmSync(current.filePath, { force: true });\n resetHarnessStore();\n } else if (current.filePath !== snapshot.filePath) {\n throw new Error('Harness state pointer changed; refusing to restore an older snapshot.');\n }\n for (const [key, value] of Object.entries(snapshot.environment)) {\n if (value === undefined) delete environment[key];\n else environment[key] = value;\n }\n if (environment === process.env) {\n loaded = snapshot.filePath\n ? {\n filePath: snapshot.filePath,\n owned: snapshot.owned,\n state: snapshot.state,\n mtimeMs: fileMtime(snapshot.filePath),\n }\n : undefined;\n }\n}\n\nexport function getHarnessState(report?: HarnessStateParseReporter): HarnessState {\n return loadHarnessState(process.env, report).state;\n}\n\nfunction stateFilePath(directory: string): string {\n return path.join(directory, STATE_FILE_NAME);\n}\n\n/** Where a process writes state when it was not given a file of its own. */\nfunction adoptedStateFilePath(): string {\n return path.join(os.tmpdir(), ADOPTED_DIRECTORY, `${String(process.pid)}.json`);\n}\n\nfunction persist(filePath: string, state: HarnessState, ownerPid = process.pid): void {\n writePrivateAtomicJson(filePath, {\n version: STATE_FILE_VERSION,\n owner: { pid: ownerPid, startedAt: new Date().toISOString() },\n state,\n } satisfies HarnessStateFile);\n}\n\n/**\n * Starts a session: writes the file and publishes the derived environment.\n *\n * The environment is a parameter because the caller is usually describing a\n * process other than itself. The launcher builds the environment for the Pi it\n * is about to spawn, and Doom Team builds the snapshot for a detached child, so\n * neither should touch its own `process.env` to do it.\n */\nexport function createHarnessSession(\n state: HarnessState,\n options: {\n directory: string;\n environment: NodeJS.ProcessEnv;\n /**\n * Leave the file for a process that does not exist yet to claim.\n *\n * What a spawner wants: the child owns its state from its first read, in a\n * directory the spawner manages, so the parent cleaning up its own session\n * cannot pull the file out from under a detached run.\n */\n unclaimed?: boolean;\n },\n): string {\n const filePath = stateFilePath(options.directory);\n persist(filePath, state, options.unclaimed ? UNCLAIMED_OWNER : process.pid);\n options.environment[HARNESS_STATE_POINTER] = filePath;\n projectHarnessEnvironment(state, options.environment);\n // Only prime the cache when this process is the one being described.\n if (!options.unclaimed && options.environment === process.env)\n loaded = { filePath, owned: true, state, mtimeMs: fileMtime(filePath) };\n return filePath;\n}\n\n/**\n * Applies a patch to this process's state.\n *\n * Copy on write: a process holding a file it does not own writes its own copy\n * and repoints itself at it, so a child can never rewrite its parent's session.\n */\nexport function updateHarnessState(\n patch: Partial<HarnessState>,\n environment: NodeJS.ProcessEnv = process.env,\n): HarnessState {\n const current = loadHarnessState(environment);\n const state = { ...current.state, ...patch };\n const filePath = current.owned && current.filePath ? current.filePath : adoptedStateFilePath();\n persist(filePath, state);\n environment[HARNESS_STATE_POINTER] = filePath;\n if (environment === process.env) loaded = { filePath, owned: true, state, mtimeMs: fileMtime(filePath) };\n projectHarnessEnvironment(patch, environment);\n return state;\n}\n\n/** Removes this process's own state file. Safe when there is none. */\nexport function disposeHarnessState(environment: NodeJS.ProcessEnv = process.env): void {\n const current = loaded;\n resetHarnessStore();\n if (!current?.owned || !current.filePath) return;\n fs.rmSync(current.filePath, { force: true });\n delete environment[HARNESS_STATE_POINTER];\n}\n\nexport function harnessRoot(state: Pick<HarnessState, 'root'> = getHarnessState()): string {\n return state.root ?? process.cwd();\n}\n\n/**\n * The repository root, or a hard error.\n *\n * Separate from `requireHarnessPaths` because most callers only need the root,\n * and separate from `harnessRoot` because falling back to `process.cwd()` would\n * quietly operate on whatever directory the session happens to sit in. A caller\n * that asks for the root wants the session's repository or nothing.\n */\nexport function requireHarnessRoot(state: Pick<HarnessState, 'root'> = getHarnessState()): string {\n if (!state.root) throw new Error(`${HARNESS_STATE_KEYS.root} is not set`);\n return state.root;\n}\n\n/** The root and the run's scratch directory, for callers that write to disk. */\nexport function requireHarnessPaths(state: Pick<HarnessState, 'root' | 'temporaryDirectory'> = getHarnessState()): {\n root: string;\n temporaryDirectory: string;\n} {\n const root = requireHarnessRoot(state);\n if (!state.temporaryDirectory) throw new Error(`${HARNESS_STATE_KEYS.temporaryDirectory} is not set`);\n return { root, temporaryDirectory: state.temporaryDirectory };\n}\n"],"mappings":"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"}
|
package/dist/adapters/init.cjs
CHANGED
|
@@ -9,6 +9,18 @@ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./config.cjs");l
|
|
|
9
9
|
# stopPhrases:
|
|
10
10
|
# - stop speaking
|
|
11
11
|
# utteranceIdleMs: 3000
|
|
12
|
+
# # Long prompts only: open a draft, keep talking, then send it in one piece.
|
|
13
|
+
# # Short utterances are delivered as you finish them and need no phrase.
|
|
14
|
+
# composeOpenPhrases:
|
|
15
|
+
# - hey doom
|
|
16
|
+
# composeSendPhrases:
|
|
17
|
+
# - that's it
|
|
18
|
+
# composeCancelPhrases:
|
|
19
|
+
# - scratch that
|
|
20
|
+
# # Endpoint window while a draft collects, so a short command lands on its own.
|
|
21
|
+
# composeUtteranceIdleMs: 1200
|
|
22
|
+
# # Silence before Voice reminds you a draft is open. 0 turns the reminder off.
|
|
23
|
+
# composeNudgeMs: 10000
|
|
12
24
|
# tts:
|
|
13
25
|
# engine: macos-say
|
|
14
26
|
# voice: Samantha
|
|
@@ -127,6 +139,15 @@ domains:
|
|
|
127
139
|
# mcp: true
|
|
128
140
|
# # Set false only when this domain should omit shared .claude/skills.
|
|
129
141
|
# sharedSkills: false
|
|
142
|
+
# # Domain mcp is an allowlist over servers configured elsewhere, not a
|
|
143
|
+
# # request to start new ones. Listing servers here drops the tool schemas
|
|
144
|
+
# # of the ones left out, usually the largest context saving a domain can
|
|
145
|
+
# # make. Run doompi --explain to see the token cost before and after.
|
|
146
|
+
# # Filtering applies only when every selected domain declares mcp, so a
|
|
147
|
+
# # selection mixing this with a domain that omits it stays unfiltered.
|
|
148
|
+
# mcp:
|
|
149
|
+
# servers: [code-intel]
|
|
150
|
+
# proxy: [repository-search]
|
|
130
151
|
# Select it with doompi --domains development, or add it to defaultDomains.
|
|
131
152
|
#
|
|
132
153
|
# Aliases let one selection enable multiple domains. Replace the empty mapping
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.cjs","names":["fs","path","randomUUID","os","globalDoomConfigDirectory"],"sources":["../../src/adapters/init.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from './config.ts';\n\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\nconst REPOSITORY_DIRECTORY_MODE = 0o755;\nconst REPOSITORY_FILE_MODE = 0o644;\nconst DOOM_DIRECTORY = '.doom';\nconst UTF8_ENCODING = 'utf8';\nconst FILE_ALREADY_EXISTS = 'EEXIST';\nconst FILE_NOT_FOUND = 'ENOENT';\n\n/** The global seed files are processed in this fixed order. */\nexport const GLOBAL_DOOM_SEED_FILES = ['config.yaml', 'modes.yaml', 'domains.yaml', 'profiles.yaml'] as const;\n\ntype DoomSeedFile = (typeof GLOBAL_DOOM_SEED_FILES)[number];\n\n/** Portable defaults for a new global Doom configuration directory. */\nexport const DOOM_CONFIG_TEMPLATES: Readonly<Record<DoomSeedFile, string>> = {\n 'config.yaml': `projectTrust: ask\n\n# Autonomous voice is opt-in and uses an explicitly configured Pi model.\n# voice:\n# autoCapture:\n# model: provider/model-id\n# startPhrases:\n# - hey doom\n# stopPhrases:\n# - stop speaking\n# utteranceIdleMs: 3000\n# tts:\n# engine: macos-say\n# voice: Samantha\n# rate: 190\n`,\n 'modes.yaml': `# A layer is a reusable named bundle of packages, built-in or local extensions,\n# and hook groups. Major modes below choose which layers load together for a session.\n#\n# Layers and modes defined here apply to every repository. A repository's own\n# .doom/modes.yaml is read after this file and replaces any layer or mode of the\n# same name, so a repository can override one entry without restating the rest.\n#\n# Add a layer by giving it a unique name under layers. Local Pi scripts belong\n# under extensions, while installed Pi packages belong under packages:\n# review-tools:\n# extensions:\n# - './extensions/local-review.ts'\n# packages:\n# - '@scope/review-extension'\n# A bare extension name selects a DoomPi built-in. A path beginning with ./ or\n# ../ loads a custom script or extension directory. A bare package loads its\n# ordered package.json pi.extensions entries, while an exported subpath selects\n# one adapter. Relative paths resolve beside this file here (~/.pi/.doom), or\n# from the repository root in .doom/modes.yaml.\n#\n# Use a package mapping when that package accepts configuration. Configuration\n# belongs under the package entry that consumes it, not beside packages.\n# The top-level default bundle loads before every major mode. Replace this whole\n# packages list to customize the distribution defaults. DoomPi core packages\n# remain active independently of this list.\ndefault:\n packages:\n - '@agimon-ai/doompi-help'\n - '@agimon-ai/doompi-hook'\n - '@agimon-ai/doompi-goal'\n - '@agimon-ai/doompi-voice'\n - '@agimon-ai/doompi-runner'\n - '@agimon-ai/doompi-read'\n - '@agimon-ai/doompi-grep'\n - '@agimon-ai/doompi-edit'\n - '@agimon-ai/doompi-file-edit'\n - '@agimon-ai/doompi-autocompact'\n - '@agimon-ai/doompi-loop'\n - '@agimon-ai/doompi-plan'\n - '@agimon-ai/doompi-workflow'\n - '@agimon-ai/doompi-log'\n - '@agimon-ai/doompi-mcp'\nlayers:\n team:\n packages:\n - name: '@agimon-ai/doompi-team'\n # Optional Team configuration. Uncomment and choose models available to Pi.\n # config:\n # models:\n # - model: openai-codex/gpt-5.6-luna\n # thinking: xhigh\n # # Tool names excluded from subagents launched by this package. These\n # # remove model-visible tools while preserving hidden team transport.\n # excludeTools: [ask_user_question, intercom, subagent]\n ask-user:\n packages:\n - '@agimon-ai/doompi-user-feedback'\n task:\n packages:\n - '@agimon-ai/doompi-task'\n# A major mode explains when it should be used and names its ordered layers.\n# Add another entry under majorMode, then select it with\n# doompi --major-mode <name>. Change defaultMajorMode to make it the default.\ndefaultMajorMode: copilot\nmajorMode:\n minimal:\n description: Lean mode with Team delegation and persistent tasks.\n layers: [team, task]\n copilot:\n description: General-purpose coding mode with Team delegation, persistent tasks, supervised commands, and structured user feedback.\n layers: [team, ask-user, task]\n # review:\n # description: Focused review mode for inspecting changes and reporting risks.\n # layers: [team, task, ask-user]\n`,\n 'domains.yaml': `# Plugins are named once in this catalog, then selected by one or more domains.\n# DoomPi automatically inspects personal and repository Codex marketplaces.\n# Each configured root may also be a folder whose direct children are plugins;\n# use roots: [plugins] to discover a repository plugin folder without one entry\n# per plugin. Discovery never scans recursively.\ndefaultDomains: [default]\nplugins:\n roots: []\n entries: {}\n # Local paths resolve beside this file in ~/.pi/.doom. Repository paths in a\n # repository .doom/domains.yaml resolve from that repository root.\n # entries:\n # development:\n # source: local\n # path: plugins/development\n # remote-review:\n # source: url\n # url: https://github.com/acme/review-plugin.git\n # ref: v1.2.0\n # published-review:\n # source: npm\n # package: '@acme/review-plugin'\n # version: 1.2.0\n# Domains carry descriptions and refer to catalog names, not filesystem paths.\ndomains:\n default:\n description: Shared skills and repository MCP only.\n plugins: []\n # development:\n # description: TypeScript implementation and review tools.\n # plugins:\n # - development\n # # Mapping form can load only selected resources from a shared plugin.\n # - name: remote-review\n # skills: [typescript]\n # agents: [reviewer]\n # hooks: false\n # mcp: true\n # # Set false only when this domain should omit shared .claude/skills.\n # sharedSkills: false\n# Select it with doompi --domains development, or add it to defaultDomains.\n#\n# Aliases let one selection enable multiple domains. Replace the empty mapping\n# below with entries like the commented example.\naliases: {}\n# aliases:\n# work: [default, development]\n`,\n 'profiles.yaml': `# Profiles select a persona and optional environment defaults for a session.\n# Each configured root may itself be a profile or contain direct-child profile\n# folders. A folder is discovered when it contains profile.md, SOUL.md, or\n# AGENTS.md; discovery never scans recursively.\nprofiles:\n roots: []\n entries: {}\n # Roots resolve beside this file in ~/.pi/.doom. Repository roots in a\n # repository .doom/profiles.yaml resolve from that repository root.\n # roots: [agents/acme]\n # Explicit entries override an automatically discovered folder of the same\n # name. Environment values must be strings; already exported values win.\n # entries:\n # writer:\n # persona: agents/special/writer\n # env:\n # BRAND: acme\n # TONE: concise\n# Doom Pi concatenates profile.md, SOUL.md, then AGENTS.md for the persona.\n# Select one with: doompi --profile writer\n`,\n};\n\n/** Standalone defaults for a repository that is trying DoomPi through DPI. */\nexport const REPOSITORY_DOOM_CONFIG_TEMPLATES: Readonly<Record<DoomSeedFile, string>> = {\n 'config.yaml': DOOM_CONFIG_TEMPLATES['config.yaml'],\n 'modes.yaml': `# Repository-local layers and major modes. These entries override personal\n# entries with the same name from ~/.pi/.doom/modes.yaml.\n# This repository-local default replaces the personal top-level default as one\n# whole package list. Edit it to customize the standalone package baseline.\ndefault:\n packages:\n - '@agimon-ai/doompi-help'\n - '@agimon-ai/doompi-hook'\n - '@agimon-ai/doompi-goal'\n - '@agimon-ai/doompi-voice'\n - '@agimon-ai/doompi-runner'\n - '@agimon-ai/doompi-read'\n - '@agimon-ai/doompi-grep'\n - '@agimon-ai/doompi-edit'\n - '@agimon-ai/doompi-file-edit'\n - '@agimon-ai/doompi-autocompact'\n - '@agimon-ai/doompi-loop'\n - '@agimon-ai/doompi-plan'\n - '@agimon-ai/doompi-workflow'\n - '@agimon-ai/doompi-log'\n - '@agimon-ai/doompi-mcp'\nlayers:\n team:\n packages:\n - name: '@agimon-ai/doompi-team'\n ask-user:\n packages:\n - '@agimon-ai/doompi-user-feedback'\n task:\n packages:\n - '@agimon-ai/doompi-task'\ndefaultMajorMode: copilot\nmajorMode:\n minimal:\n description: Lean mode with Team delegation and persistent tasks.\n layers: [team, task]\n copilot:\n description: General-purpose coding mode with delegation, persistent tasks, supervised commands, and structured user feedback.\n layers: [team, ask-user, task]\n`,\n 'domains.yaml': `# Repository-local plugin catalog and domain selection.\ndefaultDomains: [default]\nplugins:\n roots: []\n entries: {}\ndomains:\n default:\n description: Shared skills and repository MCP only.\n plugins: []\naliases: {}\n`,\n 'profiles.yaml': `# Repository-local persona roots and environment defaults.\nprofiles:\n roots: []\n entries: {}\n`,\n};\n\n/** Result of filling a Doom seed directory. File arrays contain names in seed order. */\nexport interface GlobalDoomInitResult {\n directory: string;\n created: string[];\n preserved: string[];\n /** Existing files overwritten from the template. Only ever non-empty under `force`. */\n replaced: string[];\n}\n\nexport interface GlobalDoomInitOptions {\n /**\n * Overwrite existing seed files instead of preserving them.\n *\n * Off by default: init is safe to re-run, and hand-edited config is the\n * normal case rather than the exception.\n */\n force?: boolean;\n}\n\nexport type RepositoryDoomInitOptions = GlobalDoomInitOptions;\nexport type RepositoryDoomInitResult = GlobalDoomInitResult;\n\nfunction errorCode(error: unknown): string | undefined {\n if (!(error instanceof Error)) return undefined;\n const code = (error as NodeJS.ErrnoException).code;\n return typeof code === 'string' ? code : undefined;\n}\n\nfunction ensureDirectory(directory: string, mode: number): void {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(directory);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n try {\n fs.mkdirSync(directory, { mode });\n stats = fs.lstatSync(directory);\n } catch (mkdirError) {\n if (errorCode(mkdirError) !== FILE_ALREADY_EXISTS) throw mkdirError;\n stats = fs.lstatSync(directory);\n }\n }\n\n if (stats.isSymbolicLink()) {\n throw new Error(`Refusing to initialize through symlinked directory: ${directory}`);\n }\n if (!stats.isDirectory()) {\n throw new Error(`Doom config path is not a directory: ${directory}`);\n }\n}\n\nfunction ensureDoomDirectory(directory: string, mode: number): void {\n ensureDirectory(path.dirname(directory), mode);\n ensureDirectory(directory, mode);\n}\n\nfunction classifyTarget(targetPath: string): 'missing' | 'file' {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(targetPath);\n } catch (error) {\n if (errorCode(error) === FILE_NOT_FOUND) return 'missing';\n throw error;\n }\n\n if (stats.isFile()) return 'file';\n if (stats.isSymbolicLink()) {\n try {\n if (fs.statSync(targetPath).isFile()) return 'file';\n } catch (error) {\n throw new Error(`Doom config seed symlink is not file-like: ${targetPath}`, { cause: error });\n }\n }\n throw new Error(`Doom config seed path is not file-like: ${targetPath}`);\n}\n\nfunction cleanupTemporaryFile(temporaryPath: string): void {\n try {\n fs.unlinkSync(temporaryPath);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n }\n}\n\nfunction publishExclusively(targetPath: string, content: string, mode: number): boolean {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', mode);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, mode);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n try {\n fs.linkSync(temporaryPath, targetPath);\n return true;\n } catch (error) {\n if (errorCode(error) === FILE_ALREADY_EXISTS && classifyTarget(targetPath) === 'file') return false;\n throw error;\n }\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\n/**\n * Replaces a seed file with the template, atomically.\n *\n * The rename swaps the directory entry in one step, so a reader either sees the\n * whole old file or the whole new one. Renaming onto a symlinked seed replaces\n * the link rather than writing through it.\n */\nfunction publishForcefully(targetPath: string, content: string, mode: number): void {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', mode);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, mode);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n fs.renameSync(temporaryPath, targetPath);\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\nfunction initializeDoomConfig(\n directory: string,\n templates: Readonly<Record<DoomSeedFile, string>>,\n options: GlobalDoomInitOptions,\n directoryMode: number,\n fileMode: number,\n): GlobalDoomInitResult {\n ensureDoomDirectory(directory, directoryMode);\n\n const created: string[] = [];\n const preserved: string[] = [];\n const replaced: string[] = [];\n for (const fileName of GLOBAL_DOOM_SEED_FILES) {\n const targetPath = path.join(directory, fileName);\n const existed = classifyTarget(targetPath) === 'file';\n\n if (options.force) {\n publishForcefully(targetPath, templates[fileName], fileMode);\n (existed ? replaced : created).push(fileName);\n continue;\n }\n if (existed) {\n preserved.push(fileName);\n continue;\n }\n if (publishExclusively(targetPath, templates[fileName], fileMode)) created.push(fileName);\n else preserved.push(fileName);\n }\n\n return { directory, created, preserved, replaced };\n}\n\n/**\n * Fills missing global seed files.\n *\n * Existing files are left alone so that re-running init never discards\n * hand-edited config; pass `force` to overwrite them from the template.\n */\nexport function initializeGlobalDoomConfig(\n homeDirectory = os.homedir(),\n options: GlobalDoomInitOptions = {},\n): GlobalDoomInitResult {\n return initializeDoomConfig(\n globalDoomConfigDirectory(homeDirectory),\n DOOM_CONFIG_TEMPLATES,\n options,\n PRIVATE_DIRECTORY_MODE,\n PRIVATE_FILE_MODE,\n );\n}\n\n/** Seeds repository-local configuration without creating or changing Pi settings. */\nexport function initializeRepositoryDoomConfig(\n repositoryRoot = process.cwd(),\n options: RepositoryDoomInitOptions = {},\n): RepositoryDoomInitResult {\n return initializeDoomConfig(\n path.join(path.resolve(repositoryRoot), DOOM_DIRECTORY),\n REPOSITORY_DOOM_CONFIG_TEMPLATES,\n options,\n REPOSITORY_DIRECTORY_MODE,\n REPOSITORY_FILE_MODE,\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"init.cjs","names":["fs","path","randomUUID","os","globalDoomConfigDirectory"],"sources":["../../src/adapters/init.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from './config.ts';\n\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\nconst REPOSITORY_DIRECTORY_MODE = 0o755;\nconst REPOSITORY_FILE_MODE = 0o644;\nconst DOOM_DIRECTORY = '.doom';\nconst UTF8_ENCODING = 'utf8';\nconst FILE_ALREADY_EXISTS = 'EEXIST';\nconst FILE_NOT_FOUND = 'ENOENT';\n\n/** The global seed files are processed in this fixed order. */\nexport const GLOBAL_DOOM_SEED_FILES = ['config.yaml', 'modes.yaml', 'domains.yaml', 'profiles.yaml'] as const;\n\ntype DoomSeedFile = (typeof GLOBAL_DOOM_SEED_FILES)[number];\n\n/** Portable defaults for a new global Doom configuration directory. */\nexport const DOOM_CONFIG_TEMPLATES: Readonly<Record<DoomSeedFile, string>> = {\n 'config.yaml': `projectTrust: ask\n\n# Autonomous voice is opt-in and uses an explicitly configured Pi model.\n# voice:\n# autoCapture:\n# model: provider/model-id\n# startPhrases:\n# - hey doom\n# stopPhrases:\n# - stop speaking\n# utteranceIdleMs: 3000\n# # Long prompts only: open a draft, keep talking, then send it in one piece.\n# # Short utterances are delivered as you finish them and need no phrase.\n# composeOpenPhrases:\n# - hey doom\n# composeSendPhrases:\n# - that's it\n# composeCancelPhrases:\n# - scratch that\n# # Endpoint window while a draft collects, so a short command lands on its own.\n# composeUtteranceIdleMs: 1200\n# # Silence before Voice reminds you a draft is open. 0 turns the reminder off.\n# composeNudgeMs: 10000\n# tts:\n# engine: macos-say\n# voice: Samantha\n# rate: 190\n`,\n 'modes.yaml': `# A layer is a reusable named bundle of packages, built-in or local extensions,\n# and hook groups. Major modes below choose which layers load together for a session.\n#\n# Layers and modes defined here apply to every repository. A repository's own\n# .doom/modes.yaml is read after this file and replaces any layer or mode of the\n# same name, so a repository can override one entry without restating the rest.\n#\n# Add a layer by giving it a unique name under layers. Local Pi scripts belong\n# under extensions, while installed Pi packages belong under packages:\n# review-tools:\n# extensions:\n# - './extensions/local-review.ts'\n# packages:\n# - '@scope/review-extension'\n# A bare extension name selects a DoomPi built-in. A path beginning with ./ or\n# ../ loads a custom script or extension directory. A bare package loads its\n# ordered package.json pi.extensions entries, while an exported subpath selects\n# one adapter. Relative paths resolve beside this file here (~/.pi/.doom), or\n# from the repository root in .doom/modes.yaml.\n#\n# Use a package mapping when that package accepts configuration. Configuration\n# belongs under the package entry that consumes it, not beside packages.\n# The top-level default bundle loads before every major mode. Replace this whole\n# packages list to customize the distribution defaults. DoomPi core packages\n# remain active independently of this list.\ndefault:\n packages:\n - '@agimon-ai/doompi-help'\n - '@agimon-ai/doompi-hook'\n - '@agimon-ai/doompi-goal'\n - '@agimon-ai/doompi-voice'\n - '@agimon-ai/doompi-runner'\n - '@agimon-ai/doompi-read'\n - '@agimon-ai/doompi-grep'\n - '@agimon-ai/doompi-edit'\n - '@agimon-ai/doompi-file-edit'\n - '@agimon-ai/doompi-autocompact'\n - '@agimon-ai/doompi-loop'\n - '@agimon-ai/doompi-plan'\n - '@agimon-ai/doompi-workflow'\n - '@agimon-ai/doompi-log'\n - '@agimon-ai/doompi-mcp'\nlayers:\n team:\n packages:\n - name: '@agimon-ai/doompi-team'\n # Optional Team configuration. Uncomment and choose models available to Pi.\n # config:\n # models:\n # - model: openai-codex/gpt-5.6-luna\n # thinking: xhigh\n # # Tool names excluded from subagents launched by this package. These\n # # remove model-visible tools while preserving hidden team transport.\n # excludeTools: [ask_user_question, intercom, subagent]\n ask-user:\n packages:\n - '@agimon-ai/doompi-user-feedback'\n task:\n packages:\n - '@agimon-ai/doompi-task'\n# A major mode explains when it should be used and names its ordered layers.\n# Add another entry under majorMode, then select it with\n# doompi --major-mode <name>. Change defaultMajorMode to make it the default.\ndefaultMajorMode: copilot\nmajorMode:\n minimal:\n description: Lean mode with Team delegation and persistent tasks.\n layers: [team, task]\n copilot:\n description: General-purpose coding mode with Team delegation, persistent tasks, supervised commands, and structured user feedback.\n layers: [team, ask-user, task]\n # review:\n # description: Focused review mode for inspecting changes and reporting risks.\n # layers: [team, task, ask-user]\n`,\n 'domains.yaml': `# Plugins are named once in this catalog, then selected by one or more domains.\n# DoomPi automatically inspects personal and repository Codex marketplaces.\n# Each configured root may also be a folder whose direct children are plugins;\n# use roots: [plugins] to discover a repository plugin folder without one entry\n# per plugin. Discovery never scans recursively.\ndefaultDomains: [default]\nplugins:\n roots: []\n entries: {}\n # Local paths resolve beside this file in ~/.pi/.doom. Repository paths in a\n # repository .doom/domains.yaml resolve from that repository root.\n # entries:\n # development:\n # source: local\n # path: plugins/development\n # remote-review:\n # source: url\n # url: https://github.com/acme/review-plugin.git\n # ref: v1.2.0\n # published-review:\n # source: npm\n # package: '@acme/review-plugin'\n # version: 1.2.0\n# Domains carry descriptions and refer to catalog names, not filesystem paths.\ndomains:\n default:\n description: Shared skills and repository MCP only.\n plugins: []\n # development:\n # description: TypeScript implementation and review tools.\n # plugins:\n # - development\n # # Mapping form can load only selected resources from a shared plugin.\n # - name: remote-review\n # skills: [typescript]\n # agents: [reviewer]\n # hooks: false\n # mcp: true\n # # Set false only when this domain should omit shared .claude/skills.\n # sharedSkills: false\n # # Domain mcp is an allowlist over servers configured elsewhere, not a\n # # request to start new ones. Listing servers here drops the tool schemas\n # # of the ones left out, usually the largest context saving a domain can\n # # make. Run doompi --explain to see the token cost before and after.\n # # Filtering applies only when every selected domain declares mcp, so a\n # # selection mixing this with a domain that omits it stays unfiltered.\n # mcp:\n # servers: [code-intel]\n # proxy: [repository-search]\n# Select it with doompi --domains development, or add it to defaultDomains.\n#\n# Aliases let one selection enable multiple domains. Replace the empty mapping\n# below with entries like the commented example.\naliases: {}\n# aliases:\n# work: [default, development]\n`,\n 'profiles.yaml': `# Profiles select a persona and optional environment defaults for a session.\n# Each configured root may itself be a profile or contain direct-child profile\n# folders. A folder is discovered when it contains profile.md, SOUL.md, or\n# AGENTS.md; discovery never scans recursively.\nprofiles:\n roots: []\n entries: {}\n # Roots resolve beside this file in ~/.pi/.doom. Repository roots in a\n # repository .doom/profiles.yaml resolve from that repository root.\n # roots: [agents/acme]\n # Explicit entries override an automatically discovered folder of the same\n # name. Environment values must be strings; already exported values win.\n # entries:\n # writer:\n # persona: agents/special/writer\n # env:\n # BRAND: acme\n # TONE: concise\n# Doom Pi concatenates profile.md, SOUL.md, then AGENTS.md for the persona.\n# Select one with: doompi --profile writer\n`,\n};\n\n/** Standalone defaults for a repository that is trying DoomPi through DPI. */\nexport const REPOSITORY_DOOM_CONFIG_TEMPLATES: Readonly<Record<DoomSeedFile, string>> = {\n 'config.yaml': DOOM_CONFIG_TEMPLATES['config.yaml'],\n 'modes.yaml': `# Repository-local layers and major modes. These entries override personal\n# entries with the same name from ~/.pi/.doom/modes.yaml.\n# This repository-local default replaces the personal top-level default as one\n# whole package list. Edit it to customize the standalone package baseline.\ndefault:\n packages:\n - '@agimon-ai/doompi-help'\n - '@agimon-ai/doompi-hook'\n - '@agimon-ai/doompi-goal'\n - '@agimon-ai/doompi-voice'\n - '@agimon-ai/doompi-runner'\n - '@agimon-ai/doompi-read'\n - '@agimon-ai/doompi-grep'\n - '@agimon-ai/doompi-edit'\n - '@agimon-ai/doompi-file-edit'\n - '@agimon-ai/doompi-autocompact'\n - '@agimon-ai/doompi-loop'\n - '@agimon-ai/doompi-plan'\n - '@agimon-ai/doompi-workflow'\n - '@agimon-ai/doompi-log'\n - '@agimon-ai/doompi-mcp'\nlayers:\n team:\n packages:\n - name: '@agimon-ai/doompi-team'\n ask-user:\n packages:\n - '@agimon-ai/doompi-user-feedback'\n task:\n packages:\n - '@agimon-ai/doompi-task'\ndefaultMajorMode: copilot\nmajorMode:\n minimal:\n description: Lean mode with Team delegation and persistent tasks.\n layers: [team, task]\n copilot:\n description: General-purpose coding mode with delegation, persistent tasks, supervised commands, and structured user feedback.\n layers: [team, ask-user, task]\n`,\n 'domains.yaml': `# Repository-local plugin catalog and domain selection.\ndefaultDomains: [default]\nplugins:\n roots: []\n entries: {}\ndomains:\n default:\n description: Shared skills and repository MCP only.\n plugins: []\naliases: {}\n`,\n 'profiles.yaml': `# Repository-local persona roots and environment defaults.\nprofiles:\n roots: []\n entries: {}\n`,\n};\n\n/** Result of filling a Doom seed directory. File arrays contain names in seed order. */\nexport interface GlobalDoomInitResult {\n directory: string;\n created: string[];\n preserved: string[];\n /** Existing files overwritten from the template. Only ever non-empty under `force`. */\n replaced: string[];\n}\n\nexport interface GlobalDoomInitOptions {\n /**\n * Overwrite existing seed files instead of preserving them.\n *\n * Off by default: init is safe to re-run, and hand-edited config is the\n * normal case rather than the exception.\n */\n force?: boolean;\n}\n\nexport type RepositoryDoomInitOptions = GlobalDoomInitOptions;\nexport type RepositoryDoomInitResult = GlobalDoomInitResult;\n\nfunction errorCode(error: unknown): string | undefined {\n if (!(error instanceof Error)) return undefined;\n const code = (error as NodeJS.ErrnoException).code;\n return typeof code === 'string' ? code : undefined;\n}\n\nfunction ensureDirectory(directory: string, mode: number): void {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(directory);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n try {\n fs.mkdirSync(directory, { mode });\n stats = fs.lstatSync(directory);\n } catch (mkdirError) {\n if (errorCode(mkdirError) !== FILE_ALREADY_EXISTS) throw mkdirError;\n stats = fs.lstatSync(directory);\n }\n }\n\n if (stats.isSymbolicLink()) {\n throw new Error(`Refusing to initialize through symlinked directory: ${directory}`);\n }\n if (!stats.isDirectory()) {\n throw new Error(`Doom config path is not a directory: ${directory}`);\n }\n}\n\nfunction ensureDoomDirectory(directory: string, mode: number): void {\n ensureDirectory(path.dirname(directory), mode);\n ensureDirectory(directory, mode);\n}\n\nfunction classifyTarget(targetPath: string): 'missing' | 'file' {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(targetPath);\n } catch (error) {\n if (errorCode(error) === FILE_NOT_FOUND) return 'missing';\n throw error;\n }\n\n if (stats.isFile()) return 'file';\n if (stats.isSymbolicLink()) {\n try {\n if (fs.statSync(targetPath).isFile()) return 'file';\n } catch (error) {\n throw new Error(`Doom config seed symlink is not file-like: ${targetPath}`, { cause: error });\n }\n }\n throw new Error(`Doom config seed path is not file-like: ${targetPath}`);\n}\n\nfunction cleanupTemporaryFile(temporaryPath: string): void {\n try {\n fs.unlinkSync(temporaryPath);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n }\n}\n\nfunction publishExclusively(targetPath: string, content: string, mode: number): boolean {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', mode);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, mode);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n try {\n fs.linkSync(temporaryPath, targetPath);\n return true;\n } catch (error) {\n if (errorCode(error) === FILE_ALREADY_EXISTS && classifyTarget(targetPath) === 'file') return false;\n throw error;\n }\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\n/**\n * Replaces a seed file with the template, atomically.\n *\n * The rename swaps the directory entry in one step, so a reader either sees the\n * whole old file or the whole new one. Renaming onto a symlinked seed replaces\n * the link rather than writing through it.\n */\nfunction publishForcefully(targetPath: string, content: string, mode: number): void {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', mode);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, mode);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n fs.renameSync(temporaryPath, targetPath);\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\nfunction initializeDoomConfig(\n directory: string,\n templates: Readonly<Record<DoomSeedFile, string>>,\n options: GlobalDoomInitOptions,\n directoryMode: number,\n fileMode: number,\n): GlobalDoomInitResult {\n ensureDoomDirectory(directory, directoryMode);\n\n const created: string[] = [];\n const preserved: string[] = [];\n const replaced: string[] = [];\n for (const fileName of GLOBAL_DOOM_SEED_FILES) {\n const targetPath = path.join(directory, fileName);\n const existed = classifyTarget(targetPath) === 'file';\n\n if (options.force) {\n publishForcefully(targetPath, templates[fileName], fileMode);\n (existed ? replaced : created).push(fileName);\n continue;\n }\n if (existed) {\n preserved.push(fileName);\n continue;\n }\n if (publishExclusively(targetPath, templates[fileName], fileMode)) created.push(fileName);\n else preserved.push(fileName);\n }\n\n return { directory, created, preserved, replaced };\n}\n\n/**\n * Fills missing global seed files.\n *\n * Existing files are left alone so that re-running init never discards\n * hand-edited config; pass `force` to overwrite them from the template.\n */\nexport function initializeGlobalDoomConfig(\n homeDirectory = os.homedir(),\n options: GlobalDoomInitOptions = {},\n): GlobalDoomInitResult {\n return initializeDoomConfig(\n globalDoomConfigDirectory(homeDirectory),\n DOOM_CONFIG_TEMPLATES,\n options,\n PRIVATE_DIRECTORY_MODE,\n PRIVATE_FILE_MODE,\n );\n}\n\n/** Seeds repository-local configuration without creating or changing Pi settings. */\nexport function initializeRepositoryDoomConfig(\n repositoryRoot = process.cwd(),\n options: RepositoryDoomInitOptions = {},\n): RepositoryDoomInitResult {\n return initializeDoomConfig(\n path.join(path.resolve(repositoryRoot), DOOM_DIRECTORY),\n REPOSITORY_DOOM_CONFIG_TEMPLATES,\n options,\n REPOSITORY_DIRECTORY_MODE,\n REPOSITORY_FILE_MODE,\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.cts","names":[],"sources":["../../src/adapters/init.ts"],"mappings":";;cAiBa;KAER,uBAAuB;;cAGf,uBAAuB,SAAS,OAAO;;
|
|
1
|
+
{"version":3,"file":"init.d.cts","names":[],"sources":["../../src/adapters/init.ts"],"mappings":";;cAiBa;KAER,uBAAuB;;cAGf,uBAAuB,SAAS,OAAO;;cAyLvC,kCAAkC,SAAS,OAAO;;UA6D9C;EACf;EACA;EACA;;EAEA;;UAGe;;;;;;;EAOf;;KAGU,4BAA4B;KAC5B,2BAA2B;;;;;;;iBAiKvB,2BACd,wBACA,UAAS,wBACR;;iBAWa,+BACd,yBACA,UAAS,4BACR"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.mts","names":[],"sources":["../../src/adapters/init.ts"],"mappings":";;cAiBa;KAER,uBAAuB;;cAGf,uBAAuB,SAAS,OAAO;;
|
|
1
|
+
{"version":3,"file":"init.d.mts","names":[],"sources":["../../src/adapters/init.ts"],"mappings":";;cAiBa;KAER,uBAAuB;;cAGf,uBAAuB,SAAS,OAAO;;cAyLvC,kCAAkC,SAAS,OAAO;;UA6D9C;EACf;EACA;EACA;;EAEA;;UAGe;;;;;;;EAOf;;KAGU,4BAA4B;KAC5B,2BAA2B;;;;;;;iBAiKvB,2BACd,wBACA,UAAS,wBACR;;iBAWa,+BACd,yBACA,UAAS,4BACR"}
|
package/dist/adapters/init.mjs
CHANGED
|
@@ -9,6 +9,18 @@ import{globalDoomConfigDirectory as e}from"./config.mjs";import{randomUUID as t}
|
|
|
9
9
|
# stopPhrases:
|
|
10
10
|
# - stop speaking
|
|
11
11
|
# utteranceIdleMs: 3000
|
|
12
|
+
# # Long prompts only: open a draft, keep talking, then send it in one piece.
|
|
13
|
+
# # Short utterances are delivered as you finish them and need no phrase.
|
|
14
|
+
# composeOpenPhrases:
|
|
15
|
+
# - hey doom
|
|
16
|
+
# composeSendPhrases:
|
|
17
|
+
# - that's it
|
|
18
|
+
# composeCancelPhrases:
|
|
19
|
+
# - scratch that
|
|
20
|
+
# # Endpoint window while a draft collects, so a short command lands on its own.
|
|
21
|
+
# composeUtteranceIdleMs: 1200
|
|
22
|
+
# # Silence before Voice reminds you a draft is open. 0 turns the reminder off.
|
|
23
|
+
# composeNudgeMs: 10000
|
|
12
24
|
# tts:
|
|
13
25
|
# engine: macos-say
|
|
14
26
|
# voice: Samantha
|
|
@@ -127,6 +139,15 @@ domains:
|
|
|
127
139
|
# mcp: true
|
|
128
140
|
# # Set false only when this domain should omit shared .claude/skills.
|
|
129
141
|
# sharedSkills: false
|
|
142
|
+
# # Domain mcp is an allowlist over servers configured elsewhere, not a
|
|
143
|
+
# # request to start new ones. Listing servers here drops the tool schemas
|
|
144
|
+
# # of the ones left out, usually the largest context saving a domain can
|
|
145
|
+
# # make. Run doompi --explain to see the token cost before and after.
|
|
146
|
+
# # Filtering applies only when every selected domain declares mcp, so a
|
|
147
|
+
# # selection mixing this with a domain that omits it stays unfiltered.
|
|
148
|
+
# mcp:
|
|
149
|
+
# servers: [code-intel]
|
|
150
|
+
# proxy: [repository-search]
|
|
130
151
|
# Select it with doompi --domains development, or add it to defaultDomains.
|
|
131
152
|
#
|
|
132
153
|
# Aliases let one selection enable multiple domains. Replace the empty mapping
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.mjs","names":[],"sources":["../../src/adapters/init.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from './config.ts';\n\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\nconst REPOSITORY_DIRECTORY_MODE = 0o755;\nconst REPOSITORY_FILE_MODE = 0o644;\nconst DOOM_DIRECTORY = '.doom';\nconst UTF8_ENCODING = 'utf8';\nconst FILE_ALREADY_EXISTS = 'EEXIST';\nconst FILE_NOT_FOUND = 'ENOENT';\n\n/** The global seed files are processed in this fixed order. */\nexport const GLOBAL_DOOM_SEED_FILES = ['config.yaml', 'modes.yaml', 'domains.yaml', 'profiles.yaml'] as const;\n\ntype DoomSeedFile = (typeof GLOBAL_DOOM_SEED_FILES)[number];\n\n/** Portable defaults for a new global Doom configuration directory. */\nexport const DOOM_CONFIG_TEMPLATES: Readonly<Record<DoomSeedFile, string>> = {\n 'config.yaml': `projectTrust: ask\n\n# Autonomous voice is opt-in and uses an explicitly configured Pi model.\n# voice:\n# autoCapture:\n# model: provider/model-id\n# startPhrases:\n# - hey doom\n# stopPhrases:\n# - stop speaking\n# utteranceIdleMs: 3000\n# tts:\n# engine: macos-say\n# voice: Samantha\n# rate: 190\n`,\n 'modes.yaml': `# A layer is a reusable named bundle of packages, built-in or local extensions,\n# and hook groups. Major modes below choose which layers load together for a session.\n#\n# Layers and modes defined here apply to every repository. A repository's own\n# .doom/modes.yaml is read after this file and replaces any layer or mode of the\n# same name, so a repository can override one entry without restating the rest.\n#\n# Add a layer by giving it a unique name under layers. Local Pi scripts belong\n# under extensions, while installed Pi packages belong under packages:\n# review-tools:\n# extensions:\n# - './extensions/local-review.ts'\n# packages:\n# - '@scope/review-extension'\n# A bare extension name selects a DoomPi built-in. A path beginning with ./ or\n# ../ loads a custom script or extension directory. A bare package loads its\n# ordered package.json pi.extensions entries, while an exported subpath selects\n# one adapter. Relative paths resolve beside this file here (~/.pi/.doom), or\n# from the repository root in .doom/modes.yaml.\n#\n# Use a package mapping when that package accepts configuration. Configuration\n# belongs under the package entry that consumes it, not beside packages.\n# The top-level default bundle loads before every major mode. Replace this whole\n# packages list to customize the distribution defaults. DoomPi core packages\n# remain active independently of this list.\ndefault:\n packages:\n - '@agimon-ai/doompi-help'\n - '@agimon-ai/doompi-hook'\n - '@agimon-ai/doompi-goal'\n - '@agimon-ai/doompi-voice'\n - '@agimon-ai/doompi-runner'\n - '@agimon-ai/doompi-read'\n - '@agimon-ai/doompi-grep'\n - '@agimon-ai/doompi-edit'\n - '@agimon-ai/doompi-file-edit'\n - '@agimon-ai/doompi-autocompact'\n - '@agimon-ai/doompi-loop'\n - '@agimon-ai/doompi-plan'\n - '@agimon-ai/doompi-workflow'\n - '@agimon-ai/doompi-log'\n - '@agimon-ai/doompi-mcp'\nlayers:\n team:\n packages:\n - name: '@agimon-ai/doompi-team'\n # Optional Team configuration. Uncomment and choose models available to Pi.\n # config:\n # models:\n # - model: openai-codex/gpt-5.6-luna\n # thinking: xhigh\n # # Tool names excluded from subagents launched by this package. These\n # # remove model-visible tools while preserving hidden team transport.\n # excludeTools: [ask_user_question, intercom, subagent]\n ask-user:\n packages:\n - '@agimon-ai/doompi-user-feedback'\n task:\n packages:\n - '@agimon-ai/doompi-task'\n# A major mode explains when it should be used and names its ordered layers.\n# Add another entry under majorMode, then select it with\n# doompi --major-mode <name>. Change defaultMajorMode to make it the default.\ndefaultMajorMode: copilot\nmajorMode:\n minimal:\n description: Lean mode with Team delegation and persistent tasks.\n layers: [team, task]\n copilot:\n description: General-purpose coding mode with Team delegation, persistent tasks, supervised commands, and structured user feedback.\n layers: [team, ask-user, task]\n # review:\n # description: Focused review mode for inspecting changes and reporting risks.\n # layers: [team, task, ask-user]\n`,\n 'domains.yaml': `# Plugins are named once in this catalog, then selected by one or more domains.\n# DoomPi automatically inspects personal and repository Codex marketplaces.\n# Each configured root may also be a folder whose direct children are plugins;\n# use roots: [plugins] to discover a repository plugin folder without one entry\n# per plugin. Discovery never scans recursively.\ndefaultDomains: [default]\nplugins:\n roots: []\n entries: {}\n # Local paths resolve beside this file in ~/.pi/.doom. Repository paths in a\n # repository .doom/domains.yaml resolve from that repository root.\n # entries:\n # development:\n # source: local\n # path: plugins/development\n # remote-review:\n # source: url\n # url: https://github.com/acme/review-plugin.git\n # ref: v1.2.0\n # published-review:\n # source: npm\n # package: '@acme/review-plugin'\n # version: 1.2.0\n# Domains carry descriptions and refer to catalog names, not filesystem paths.\ndomains:\n default:\n description: Shared skills and repository MCP only.\n plugins: []\n # development:\n # description: TypeScript implementation and review tools.\n # plugins:\n # - development\n # # Mapping form can load only selected resources from a shared plugin.\n # - name: remote-review\n # skills: [typescript]\n # agents: [reviewer]\n # hooks: false\n # mcp: true\n # # Set false only when this domain should omit shared .claude/skills.\n # sharedSkills: false\n# Select it with doompi --domains development, or add it to defaultDomains.\n#\n# Aliases let one selection enable multiple domains. Replace the empty mapping\n# below with entries like the commented example.\naliases: {}\n# aliases:\n# work: [default, development]\n`,\n 'profiles.yaml': `# Profiles select a persona and optional environment defaults for a session.\n# Each configured root may itself be a profile or contain direct-child profile\n# folders. A folder is discovered when it contains profile.md, SOUL.md, or\n# AGENTS.md; discovery never scans recursively.\nprofiles:\n roots: []\n entries: {}\n # Roots resolve beside this file in ~/.pi/.doom. Repository roots in a\n # repository .doom/profiles.yaml resolve from that repository root.\n # roots: [agents/acme]\n # Explicit entries override an automatically discovered folder of the same\n # name. Environment values must be strings; already exported values win.\n # entries:\n # writer:\n # persona: agents/special/writer\n # env:\n # BRAND: acme\n # TONE: concise\n# Doom Pi concatenates profile.md, SOUL.md, then AGENTS.md for the persona.\n# Select one with: doompi --profile writer\n`,\n};\n\n/** Standalone defaults for a repository that is trying DoomPi through DPI. */\nexport const REPOSITORY_DOOM_CONFIG_TEMPLATES: Readonly<Record<DoomSeedFile, string>> = {\n 'config.yaml': DOOM_CONFIG_TEMPLATES['config.yaml'],\n 'modes.yaml': `# Repository-local layers and major modes. These entries override personal\n# entries with the same name from ~/.pi/.doom/modes.yaml.\n# This repository-local default replaces the personal top-level default as one\n# whole package list. Edit it to customize the standalone package baseline.\ndefault:\n packages:\n - '@agimon-ai/doompi-help'\n - '@agimon-ai/doompi-hook'\n - '@agimon-ai/doompi-goal'\n - '@agimon-ai/doompi-voice'\n - '@agimon-ai/doompi-runner'\n - '@agimon-ai/doompi-read'\n - '@agimon-ai/doompi-grep'\n - '@agimon-ai/doompi-edit'\n - '@agimon-ai/doompi-file-edit'\n - '@agimon-ai/doompi-autocompact'\n - '@agimon-ai/doompi-loop'\n - '@agimon-ai/doompi-plan'\n - '@agimon-ai/doompi-workflow'\n - '@agimon-ai/doompi-log'\n - '@agimon-ai/doompi-mcp'\nlayers:\n team:\n packages:\n - name: '@agimon-ai/doompi-team'\n ask-user:\n packages:\n - '@agimon-ai/doompi-user-feedback'\n task:\n packages:\n - '@agimon-ai/doompi-task'\ndefaultMajorMode: copilot\nmajorMode:\n minimal:\n description: Lean mode with Team delegation and persistent tasks.\n layers: [team, task]\n copilot:\n description: General-purpose coding mode with delegation, persistent tasks, supervised commands, and structured user feedback.\n layers: [team, ask-user, task]\n`,\n 'domains.yaml': `# Repository-local plugin catalog and domain selection.\ndefaultDomains: [default]\nplugins:\n roots: []\n entries: {}\ndomains:\n default:\n description: Shared skills and repository MCP only.\n plugins: []\naliases: {}\n`,\n 'profiles.yaml': `# Repository-local persona roots and environment defaults.\nprofiles:\n roots: []\n entries: {}\n`,\n};\n\n/** Result of filling a Doom seed directory. File arrays contain names in seed order. */\nexport interface GlobalDoomInitResult {\n directory: string;\n created: string[];\n preserved: string[];\n /** Existing files overwritten from the template. Only ever non-empty under `force`. */\n replaced: string[];\n}\n\nexport interface GlobalDoomInitOptions {\n /**\n * Overwrite existing seed files instead of preserving them.\n *\n * Off by default: init is safe to re-run, and hand-edited config is the\n * normal case rather than the exception.\n */\n force?: boolean;\n}\n\nexport type RepositoryDoomInitOptions = GlobalDoomInitOptions;\nexport type RepositoryDoomInitResult = GlobalDoomInitResult;\n\nfunction errorCode(error: unknown): string | undefined {\n if (!(error instanceof Error)) return undefined;\n const code = (error as NodeJS.ErrnoException).code;\n return typeof code === 'string' ? code : undefined;\n}\n\nfunction ensureDirectory(directory: string, mode: number): void {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(directory);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n try {\n fs.mkdirSync(directory, { mode });\n stats = fs.lstatSync(directory);\n } catch (mkdirError) {\n if (errorCode(mkdirError) !== FILE_ALREADY_EXISTS) throw mkdirError;\n stats = fs.lstatSync(directory);\n }\n }\n\n if (stats.isSymbolicLink()) {\n throw new Error(`Refusing to initialize through symlinked directory: ${directory}`);\n }\n if (!stats.isDirectory()) {\n throw new Error(`Doom config path is not a directory: ${directory}`);\n }\n}\n\nfunction ensureDoomDirectory(directory: string, mode: number): void {\n ensureDirectory(path.dirname(directory), mode);\n ensureDirectory(directory, mode);\n}\n\nfunction classifyTarget(targetPath: string): 'missing' | 'file' {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(targetPath);\n } catch (error) {\n if (errorCode(error) === FILE_NOT_FOUND) return 'missing';\n throw error;\n }\n\n if (stats.isFile()) return 'file';\n if (stats.isSymbolicLink()) {\n try {\n if (fs.statSync(targetPath).isFile()) return 'file';\n } catch (error) {\n throw new Error(`Doom config seed symlink is not file-like: ${targetPath}`, { cause: error });\n }\n }\n throw new Error(`Doom config seed path is not file-like: ${targetPath}`);\n}\n\nfunction cleanupTemporaryFile(temporaryPath: string): void {\n try {\n fs.unlinkSync(temporaryPath);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n }\n}\n\nfunction publishExclusively(targetPath: string, content: string, mode: number): boolean {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', mode);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, mode);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n try {\n fs.linkSync(temporaryPath, targetPath);\n return true;\n } catch (error) {\n if (errorCode(error) === FILE_ALREADY_EXISTS && classifyTarget(targetPath) === 'file') return false;\n throw error;\n }\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\n/**\n * Replaces a seed file with the template, atomically.\n *\n * The rename swaps the directory entry in one step, so a reader either sees the\n * whole old file or the whole new one. Renaming onto a symlinked seed replaces\n * the link rather than writing through it.\n */\nfunction publishForcefully(targetPath: string, content: string, mode: number): void {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', mode);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, mode);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n fs.renameSync(temporaryPath, targetPath);\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\nfunction initializeDoomConfig(\n directory: string,\n templates: Readonly<Record<DoomSeedFile, string>>,\n options: GlobalDoomInitOptions,\n directoryMode: number,\n fileMode: number,\n): GlobalDoomInitResult {\n ensureDoomDirectory(directory, directoryMode);\n\n const created: string[] = [];\n const preserved: string[] = [];\n const replaced: string[] = [];\n for (const fileName of GLOBAL_DOOM_SEED_FILES) {\n const targetPath = path.join(directory, fileName);\n const existed = classifyTarget(targetPath) === 'file';\n\n if (options.force) {\n publishForcefully(targetPath, templates[fileName], fileMode);\n (existed ? replaced : created).push(fileName);\n continue;\n }\n if (existed) {\n preserved.push(fileName);\n continue;\n }\n if (publishExclusively(targetPath, templates[fileName], fileMode)) created.push(fileName);\n else preserved.push(fileName);\n }\n\n return { directory, created, preserved, replaced };\n}\n\n/**\n * Fills missing global seed files.\n *\n * Existing files are left alone so that re-running init never discards\n * hand-edited config; pass `force` to overwrite them from the template.\n */\nexport function initializeGlobalDoomConfig(\n homeDirectory = os.homedir(),\n options: GlobalDoomInitOptions = {},\n): GlobalDoomInitResult {\n return initializeDoomConfig(\n globalDoomConfigDirectory(homeDirectory),\n DOOM_CONFIG_TEMPLATES,\n options,\n PRIVATE_DIRECTORY_MODE,\n PRIVATE_FILE_MODE,\n );\n}\n\n/** Seeds repository-local configuration without creating or changing Pi settings. */\nexport function initializeRepositoryDoomConfig(\n repositoryRoot = process.cwd(),\n options: RepositoryDoomInitOptions = {},\n): RepositoryDoomInitResult {\n return initializeDoomConfig(\n path.join(path.resolve(repositoryRoot), DOOM_DIRECTORY),\n REPOSITORY_DOOM_CONFIG_TEMPLATES,\n options,\n REPOSITORY_DIRECTORY_MODE,\n REPOSITORY_FILE_MODE,\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"init.mjs","names":[],"sources":["../../src/adapters/init.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from './config.ts';\n\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\nconst REPOSITORY_DIRECTORY_MODE = 0o755;\nconst REPOSITORY_FILE_MODE = 0o644;\nconst DOOM_DIRECTORY = '.doom';\nconst UTF8_ENCODING = 'utf8';\nconst FILE_ALREADY_EXISTS = 'EEXIST';\nconst FILE_NOT_FOUND = 'ENOENT';\n\n/** The global seed files are processed in this fixed order. */\nexport const GLOBAL_DOOM_SEED_FILES = ['config.yaml', 'modes.yaml', 'domains.yaml', 'profiles.yaml'] as const;\n\ntype DoomSeedFile = (typeof GLOBAL_DOOM_SEED_FILES)[number];\n\n/** Portable defaults for a new global Doom configuration directory. */\nexport const DOOM_CONFIG_TEMPLATES: Readonly<Record<DoomSeedFile, string>> = {\n 'config.yaml': `projectTrust: ask\n\n# Autonomous voice is opt-in and uses an explicitly configured Pi model.\n# voice:\n# autoCapture:\n# model: provider/model-id\n# startPhrases:\n# - hey doom\n# stopPhrases:\n# - stop speaking\n# utteranceIdleMs: 3000\n# # Long prompts only: open a draft, keep talking, then send it in one piece.\n# # Short utterances are delivered as you finish them and need no phrase.\n# composeOpenPhrases:\n# - hey doom\n# composeSendPhrases:\n# - that's it\n# composeCancelPhrases:\n# - scratch that\n# # Endpoint window while a draft collects, so a short command lands on its own.\n# composeUtteranceIdleMs: 1200\n# # Silence before Voice reminds you a draft is open. 0 turns the reminder off.\n# composeNudgeMs: 10000\n# tts:\n# engine: macos-say\n# voice: Samantha\n# rate: 190\n`,\n 'modes.yaml': `# A layer is a reusable named bundle of packages, built-in or local extensions,\n# and hook groups. Major modes below choose which layers load together for a session.\n#\n# Layers and modes defined here apply to every repository. A repository's own\n# .doom/modes.yaml is read after this file and replaces any layer or mode of the\n# same name, so a repository can override one entry without restating the rest.\n#\n# Add a layer by giving it a unique name under layers. Local Pi scripts belong\n# under extensions, while installed Pi packages belong under packages:\n# review-tools:\n# extensions:\n# - './extensions/local-review.ts'\n# packages:\n# - '@scope/review-extension'\n# A bare extension name selects a DoomPi built-in. A path beginning with ./ or\n# ../ loads a custom script or extension directory. A bare package loads its\n# ordered package.json pi.extensions entries, while an exported subpath selects\n# one adapter. Relative paths resolve beside this file here (~/.pi/.doom), or\n# from the repository root in .doom/modes.yaml.\n#\n# Use a package mapping when that package accepts configuration. Configuration\n# belongs under the package entry that consumes it, not beside packages.\n# The top-level default bundle loads before every major mode. Replace this whole\n# packages list to customize the distribution defaults. DoomPi core packages\n# remain active independently of this list.\ndefault:\n packages:\n - '@agimon-ai/doompi-help'\n - '@agimon-ai/doompi-hook'\n - '@agimon-ai/doompi-goal'\n - '@agimon-ai/doompi-voice'\n - '@agimon-ai/doompi-runner'\n - '@agimon-ai/doompi-read'\n - '@agimon-ai/doompi-grep'\n - '@agimon-ai/doompi-edit'\n - '@agimon-ai/doompi-file-edit'\n - '@agimon-ai/doompi-autocompact'\n - '@agimon-ai/doompi-loop'\n - '@agimon-ai/doompi-plan'\n - '@agimon-ai/doompi-workflow'\n - '@agimon-ai/doompi-log'\n - '@agimon-ai/doompi-mcp'\nlayers:\n team:\n packages:\n - name: '@agimon-ai/doompi-team'\n # Optional Team configuration. Uncomment and choose models available to Pi.\n # config:\n # models:\n # - model: openai-codex/gpt-5.6-luna\n # thinking: xhigh\n # # Tool names excluded from subagents launched by this package. These\n # # remove model-visible tools while preserving hidden team transport.\n # excludeTools: [ask_user_question, intercom, subagent]\n ask-user:\n packages:\n - '@agimon-ai/doompi-user-feedback'\n task:\n packages:\n - '@agimon-ai/doompi-task'\n# A major mode explains when it should be used and names its ordered layers.\n# Add another entry under majorMode, then select it with\n# doompi --major-mode <name>. Change defaultMajorMode to make it the default.\ndefaultMajorMode: copilot\nmajorMode:\n minimal:\n description: Lean mode with Team delegation and persistent tasks.\n layers: [team, task]\n copilot:\n description: General-purpose coding mode with Team delegation, persistent tasks, supervised commands, and structured user feedback.\n layers: [team, ask-user, task]\n # review:\n # description: Focused review mode for inspecting changes and reporting risks.\n # layers: [team, task, ask-user]\n`,\n 'domains.yaml': `# Plugins are named once in this catalog, then selected by one or more domains.\n# DoomPi automatically inspects personal and repository Codex marketplaces.\n# Each configured root may also be a folder whose direct children are plugins;\n# use roots: [plugins] to discover a repository plugin folder without one entry\n# per plugin. Discovery never scans recursively.\ndefaultDomains: [default]\nplugins:\n roots: []\n entries: {}\n # Local paths resolve beside this file in ~/.pi/.doom. Repository paths in a\n # repository .doom/domains.yaml resolve from that repository root.\n # entries:\n # development:\n # source: local\n # path: plugins/development\n # remote-review:\n # source: url\n # url: https://github.com/acme/review-plugin.git\n # ref: v1.2.0\n # published-review:\n # source: npm\n # package: '@acme/review-plugin'\n # version: 1.2.0\n# Domains carry descriptions and refer to catalog names, not filesystem paths.\ndomains:\n default:\n description: Shared skills and repository MCP only.\n plugins: []\n # development:\n # description: TypeScript implementation and review tools.\n # plugins:\n # - development\n # # Mapping form can load only selected resources from a shared plugin.\n # - name: remote-review\n # skills: [typescript]\n # agents: [reviewer]\n # hooks: false\n # mcp: true\n # # Set false only when this domain should omit shared .claude/skills.\n # sharedSkills: false\n # # Domain mcp is an allowlist over servers configured elsewhere, not a\n # # request to start new ones. Listing servers here drops the tool schemas\n # # of the ones left out, usually the largest context saving a domain can\n # # make. Run doompi --explain to see the token cost before and after.\n # # Filtering applies only when every selected domain declares mcp, so a\n # # selection mixing this with a domain that omits it stays unfiltered.\n # mcp:\n # servers: [code-intel]\n # proxy: [repository-search]\n# Select it with doompi --domains development, or add it to defaultDomains.\n#\n# Aliases let one selection enable multiple domains. Replace the empty mapping\n# below with entries like the commented example.\naliases: {}\n# aliases:\n# work: [default, development]\n`,\n 'profiles.yaml': `# Profiles select a persona and optional environment defaults for a session.\n# Each configured root may itself be a profile or contain direct-child profile\n# folders. A folder is discovered when it contains profile.md, SOUL.md, or\n# AGENTS.md; discovery never scans recursively.\nprofiles:\n roots: []\n entries: {}\n # Roots resolve beside this file in ~/.pi/.doom. Repository roots in a\n # repository .doom/profiles.yaml resolve from that repository root.\n # roots: [agents/acme]\n # Explicit entries override an automatically discovered folder of the same\n # name. Environment values must be strings; already exported values win.\n # entries:\n # writer:\n # persona: agents/special/writer\n # env:\n # BRAND: acme\n # TONE: concise\n# Doom Pi concatenates profile.md, SOUL.md, then AGENTS.md for the persona.\n# Select one with: doompi --profile writer\n`,\n};\n\n/** Standalone defaults for a repository that is trying DoomPi through DPI. */\nexport const REPOSITORY_DOOM_CONFIG_TEMPLATES: Readonly<Record<DoomSeedFile, string>> = {\n 'config.yaml': DOOM_CONFIG_TEMPLATES['config.yaml'],\n 'modes.yaml': `# Repository-local layers and major modes. These entries override personal\n# entries with the same name from ~/.pi/.doom/modes.yaml.\n# This repository-local default replaces the personal top-level default as one\n# whole package list. Edit it to customize the standalone package baseline.\ndefault:\n packages:\n - '@agimon-ai/doompi-help'\n - '@agimon-ai/doompi-hook'\n - '@agimon-ai/doompi-goal'\n - '@agimon-ai/doompi-voice'\n - '@agimon-ai/doompi-runner'\n - '@agimon-ai/doompi-read'\n - '@agimon-ai/doompi-grep'\n - '@agimon-ai/doompi-edit'\n - '@agimon-ai/doompi-file-edit'\n - '@agimon-ai/doompi-autocompact'\n - '@agimon-ai/doompi-loop'\n - '@agimon-ai/doompi-plan'\n - '@agimon-ai/doompi-workflow'\n - '@agimon-ai/doompi-log'\n - '@agimon-ai/doompi-mcp'\nlayers:\n team:\n packages:\n - name: '@agimon-ai/doompi-team'\n ask-user:\n packages:\n - '@agimon-ai/doompi-user-feedback'\n task:\n packages:\n - '@agimon-ai/doompi-task'\ndefaultMajorMode: copilot\nmajorMode:\n minimal:\n description: Lean mode with Team delegation and persistent tasks.\n layers: [team, task]\n copilot:\n description: General-purpose coding mode with delegation, persistent tasks, supervised commands, and structured user feedback.\n layers: [team, ask-user, task]\n`,\n 'domains.yaml': `# Repository-local plugin catalog and domain selection.\ndefaultDomains: [default]\nplugins:\n roots: []\n entries: {}\ndomains:\n default:\n description: Shared skills and repository MCP only.\n plugins: []\naliases: {}\n`,\n 'profiles.yaml': `# Repository-local persona roots and environment defaults.\nprofiles:\n roots: []\n entries: {}\n`,\n};\n\n/** Result of filling a Doom seed directory. File arrays contain names in seed order. */\nexport interface GlobalDoomInitResult {\n directory: string;\n created: string[];\n preserved: string[];\n /** Existing files overwritten from the template. Only ever non-empty under `force`. */\n replaced: string[];\n}\n\nexport interface GlobalDoomInitOptions {\n /**\n * Overwrite existing seed files instead of preserving them.\n *\n * Off by default: init is safe to re-run, and hand-edited config is the\n * normal case rather than the exception.\n */\n force?: boolean;\n}\n\nexport type RepositoryDoomInitOptions = GlobalDoomInitOptions;\nexport type RepositoryDoomInitResult = GlobalDoomInitResult;\n\nfunction errorCode(error: unknown): string | undefined {\n if (!(error instanceof Error)) return undefined;\n const code = (error as NodeJS.ErrnoException).code;\n return typeof code === 'string' ? code : undefined;\n}\n\nfunction ensureDirectory(directory: string, mode: number): void {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(directory);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n try {\n fs.mkdirSync(directory, { mode });\n stats = fs.lstatSync(directory);\n } catch (mkdirError) {\n if (errorCode(mkdirError) !== FILE_ALREADY_EXISTS) throw mkdirError;\n stats = fs.lstatSync(directory);\n }\n }\n\n if (stats.isSymbolicLink()) {\n throw new Error(`Refusing to initialize through symlinked directory: ${directory}`);\n }\n if (!stats.isDirectory()) {\n throw new Error(`Doom config path is not a directory: ${directory}`);\n }\n}\n\nfunction ensureDoomDirectory(directory: string, mode: number): void {\n ensureDirectory(path.dirname(directory), mode);\n ensureDirectory(directory, mode);\n}\n\nfunction classifyTarget(targetPath: string): 'missing' | 'file' {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(targetPath);\n } catch (error) {\n if (errorCode(error) === FILE_NOT_FOUND) return 'missing';\n throw error;\n }\n\n if (stats.isFile()) return 'file';\n if (stats.isSymbolicLink()) {\n try {\n if (fs.statSync(targetPath).isFile()) return 'file';\n } catch (error) {\n throw new Error(`Doom config seed symlink is not file-like: ${targetPath}`, { cause: error });\n }\n }\n throw new Error(`Doom config seed path is not file-like: ${targetPath}`);\n}\n\nfunction cleanupTemporaryFile(temporaryPath: string): void {\n try {\n fs.unlinkSync(temporaryPath);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n }\n}\n\nfunction publishExclusively(targetPath: string, content: string, mode: number): boolean {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', mode);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, mode);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n try {\n fs.linkSync(temporaryPath, targetPath);\n return true;\n } catch (error) {\n if (errorCode(error) === FILE_ALREADY_EXISTS && classifyTarget(targetPath) === 'file') return false;\n throw error;\n }\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\n/**\n * Replaces a seed file with the template, atomically.\n *\n * The rename swaps the directory entry in one step, so a reader either sees the\n * whole old file or the whole new one. Renaming onto a symlinked seed replaces\n * the link rather than writing through it.\n */\nfunction publishForcefully(targetPath: string, content: string, mode: number): void {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', mode);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, mode);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n fs.renameSync(temporaryPath, targetPath);\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\nfunction initializeDoomConfig(\n directory: string,\n templates: Readonly<Record<DoomSeedFile, string>>,\n options: GlobalDoomInitOptions,\n directoryMode: number,\n fileMode: number,\n): GlobalDoomInitResult {\n ensureDoomDirectory(directory, directoryMode);\n\n const created: string[] = [];\n const preserved: string[] = [];\n const replaced: string[] = [];\n for (const fileName of GLOBAL_DOOM_SEED_FILES) {\n const targetPath = path.join(directory, fileName);\n const existed = classifyTarget(targetPath) === 'file';\n\n if (options.force) {\n publishForcefully(targetPath, templates[fileName], fileMode);\n (existed ? replaced : created).push(fileName);\n continue;\n }\n if (existed) {\n preserved.push(fileName);\n continue;\n }\n if (publishExclusively(targetPath, templates[fileName], fileMode)) created.push(fileName);\n else preserved.push(fileName);\n }\n\n return { directory, created, preserved, replaced };\n}\n\n/**\n * Fills missing global seed files.\n *\n * Existing files are left alone so that re-running init never discards\n * hand-edited config; pass `force` to overwrite them from the template.\n */\nexport function initializeGlobalDoomConfig(\n homeDirectory = os.homedir(),\n options: GlobalDoomInitOptions = {},\n): GlobalDoomInitResult {\n return initializeDoomConfig(\n globalDoomConfigDirectory(homeDirectory),\n DOOM_CONFIG_TEMPLATES,\n options,\n PRIVATE_DIRECTORY_MODE,\n PRIVATE_FILE_MODE,\n );\n}\n\n/** Seeds repository-local configuration without creating or changing Pi settings. */\nexport function initializeRepositoryDoomConfig(\n repositoryRoot = process.cwd(),\n options: RepositoryDoomInitOptions = {},\n): RepositoryDoomInitResult {\n return initializeDoomConfig(\n path.join(path.resolve(repositoryRoot), DOOM_DIRECTORY),\n REPOSITORY_DOOM_CONFIG_TEMPLATES,\n options,\n REPOSITORY_DIRECTORY_MODE,\n REPOSITORY_FILE_MODE,\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
let e=require("yaml");const t=`auto`,n=[`modes`,`projectTrust`,`editor`,`voice`,`selection`],r=[`majorMode`,`domains`,`profile`],i=[`planning`],a=[`main`,`subagents`,`plansDirectory`],o=[`model`,`thinking`],s=[`command`],c=[`engine`,`language`,`recorder`,`adapters`,`autoCapture`],l=[`binary`,`device`],u=`whisper-cpp`,d=`openai-whisper`,f=`mlx-whisper`,p=[u,d,f],m=[`binary`,`model`],h=[`path`,`id`],g=[`model`,`startPhrases`,`stopPhrases`,`utteranceIdleMs`,`transcriptionTimeoutMs`,`tts`],_=[`engine`,`voice`,`rate`],v=1500,y=1e4,b=1e3,x=12e4,S=/^[^/\s]+\/[^/\s]+(?:\/[^/\s]+)*$/u,C=/[\p{P}\p{S}]+/gu,w=[`off`,`minimal`,`low`,`medium`,`high`,`xhigh`,`max`],T=[t,u,d,f],E=[`macos-say`],D=new Set(w),O=new Set([`ask`,`always`,`never`]),k=new Set(T),A=new Set(E);function j(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function M(e,t,n,r){let i=Object.keys(e).filter(e=>!t.includes(e));if(i.length>0)throw Error(`Doom config at ${r} has unsupported ${n} field(s): ${i.join(`, `)}`)}function N(e,t,n){if(e!==void 0){if(typeof e!=`string`||e.trim().length===0)throw Error(`Doom config at ${n} requires ${t} to be a non-empty string`);return e.trim()}}function P(e){for(let t of e){let e=t.codePointAt(0);if(e!==void 0&&(e<=31||e===127))return!0}return!1}function F(e,t,n,r){let i=N(e,t,n);if(i){if(i.length>r)throw Error(`Doom config at ${n} requires ${t} to be at most ${r} characters`);if(P(i))throw Error(`Doom config at ${n} does not allow control characters in ${t}`);return i}}function I(e,t){let n=`voice.autoCapture.model`,r=N(e,n,t);if(!r||!S.test(r))throw Error(`Doom config at ${t} requires ${n} in provider/model-id form`);return r}function L(e){return e.normalize(`NFKC`).toLocaleLowerCase(`en-US`).replace(C,` `).replace(/\s+/gu,` `).trim()}function R(e,t,n){if(e===void 0)return;if(!Array.isArray(e)||e.some(e=>typeof e!=`string`))throw Error(`Doom config at ${n} requires ${t} to be an array of strings`);if(e.length>16)throw Error(`Doom config at ${n} requires ${t} to contain at most 16 phrases`);let r=e.map(e=>e.trim()),i=new Set;for(let e of r){if(e.length===0)throw Error(`Doom config at ${n} requires ${t} to contain non-empty strings`);if(e.length>64)throw Error(`Doom config at ${n} requires ${t} phrases to be at most 64 characters`);if(P(e))throw Error(`Doom config at ${n} does not allow control characters in ${t}`);let r=L(e);if(!r)throw Error(`Doom config at ${n} requires ${t} to contain words`);if(i.has(r))throw Error(`Doom config at ${n} does not allow normalized duplicates in ${t}`);i.add(r)}return r}function z(e,t){let n=`voice.autoCapture.tts`;if(!j(e))throw Error(`Doom config at ${t} requires ${n} to be an object`);if(M(e,_,n,t),typeof e.engine!=`string`||!A.has(e.engine))throw Error(`Doom config at ${t} requires ${n}.engine to be one of: ${[...A].join(`, `)}`);let r=F(e.voice,`${n}.voice`,t,64),i=e.rate;if(i!==void 0&&(typeof i!=`number`||!Number.isInteger(i)||i<80||i>500))throw Error(`Doom config at ${t} requires ${n}.rate to be an integer from 80 to 500`);return{engine:e.engine,...r?{voice:r}:{},...i===void 0?{}:{rate:i}}}function B(e,t){if(e===void 0)return;let n=`voice.autoCapture`;if(!j(e))throw Error(`Doom config at ${t} requires ${n} to be an object`);M(e,g,n,t);let r=R(e.startPhrases,`${n}.startPhrases`,t),i=R(e.stopPhrases,`${n}.stopPhrases`,t),a=e.utteranceIdleMs;if(a!==void 0&&(typeof a!=`number`||!Number.isInteger(a)||a<v||a>y))throw Error(`Doom config at ${t} requires ${n}.utteranceIdleMs to be an integer from ${v} to ${y}`);let o=e.transcriptionTimeoutMs;if(o!==void 0&&(typeof o!=`number`||!Number.isInteger(o)||o<b||o>x))throw Error(`Doom config at ${t} requires ${n}.transcriptionTimeoutMs to be an integer from ${b} to ${x}`);let s=new Set((r??[]).map(L));if((i??[]).some(e=>s.has(L(e))))throw Error(`Doom config at ${t} does not allow a phrase in both startPhrases and stopPhrases`);return{model:I(e.model,t),...r?{startPhrases:r}:{},...i?{stopPhrases:i}:{},...a===void 0?{}:{utteranceIdleMs:a},...o===void 0?{}:{transcriptionTimeoutMs:o},tts:z(e.tts,t)}}function V(e,t,n){if(e===void 0)return;if(!j(e))throw Error(`Doom config at ${n} requires ${t} to be an object`);M(e,o,t,n);let r=e.thinking;if(r!==void 0&&(typeof r!=`string`||!D.has(r)))throw Error(`Doom config at ${n} requires ${t}.thinking to be one of: ${[...D].join(`, `)}`);let i=N(e.model,`${t}.model`,n);return{...i?{model:i}:{},...r?{thinking:r}:{}}}function H(e,t,n=`modes.planning`){if(!j(e))throw Error(`Doom config at ${t} requires ${n} to be an object`);return M(e,a,n,t),{main:V(e.main,`${n}.main`,t),subagents:V(e.subagents,`${n}.subagents`,t),plansDirectory:N(e.plansDirectory,`${n}.plansDirectory`,t)}}function U(e,t,n,r){if(!j(e))throw Error(`Doom config at ${n} requires ${t} to be an object`);M(e,h,t,n);let i=N(e.path,`${t}.path`,n),a=N(e.id,`${t}.id`,n);if(!!i==!!a)throw Error(`Doom config at ${n} requires exactly one of ${t}.path or ${t}.id`);if(a&&!r)throw Error(`Doom config at ${n} does not support ${t}.id for whisper-cpp`);return{...i?{path:i}:{},...a?{id:a}:{}}}function W(e,t,n,r){if(e===void 0)return;if(!j(e))throw Error(`Doom config at ${n} requires ${t} to be an object`);M(e,m,t,n);let i=N(e.binary,`${t}.binary`,n);return{...i?{binary:i}:{},model:U(e.model,`${t}.model`,n,r)}}function G(e,t){if(e===void 0)return;if(!j(e))throw Error(`Doom config at ${t} requires voice to be an object`);M(e,c,`voice`,t);let n={};if(e.engine!==void 0){if(typeof e.engine!=`string`||!k.has(e.engine))throw Error(`Doom config at ${t} requires voice.engine to be one of: ${[...k].join(`, `)}`);n.engine=e.engine}let r=N(e.language,`voice.language`,t);if(r&&(n.language=r),e.recorder!==void 0){if(!j(e.recorder))throw Error(`Doom config at ${t} requires voice.recorder to be an object`);M(e.recorder,l,`voice.recorder`,t),n.recorder={};let r=N(e.recorder.binary,`voice.recorder.binary`,t),i=N(e.recorder.device,`voice.recorder.device`,t);r&&(n.recorder.binary=r),i&&(n.recorder.device=i)}if(e.adapters!==void 0){if(!j(e.adapters))throw Error(`Doom config at ${t} requires voice.adapters to be an object`);M(e.adapters,p,`voice.adapters`,t),n.adapters={[u]:W(e.adapters[u],`voice.adapters.${u}`,t,!1),[d]:W(e.adapters[d],`voice.adapters.${d}`,t,!0),[f]:W(e.adapters[f],`voice.adapters.${f}`,t,!0)}}let i=B(e.autoCapture,t);return i&&(n.autoCapture=i),n}function K(e,t){if(e===void 0)return;if(!j(e))throw Error(`Doom config at ${t} requires selection to be an object`);M(e,r,`selection`,t);let n=e.domains;if(n!==void 0&&(!Array.isArray(n)||n.some(e=>typeof e!=`string`)))throw Error(`Doom config at ${t} requires selection.domains to be an array of strings`);return{majorMode:N(e.majorMode,`selection.majorMode`,t),domains:n,profile:N(e.profile,`selection.profile`,t)}}function q(e,t){if(e!==void 0){if(!j(e))throw Error(`Doom config at ${t} requires editor to be an object`);return M(e,s,`editor`,t),{command:N(e.command,`editor.command`,t)}}}function J(t,r){let a;try{a=(0,e.parse)(t)??{}}catch(e){throw Error(`Could not parse Doom config at ${r}`,{cause:e})}if(!j(a))throw Error(`Doom config at ${r} must be a YAML object`);M(a,n,`root`,r);let o;if(a.modes!==void 0){if(!j(a.modes))throw Error(`Doom config at ${r} requires modes to be an object`);M(a.modes,i,`modes`,r),o=a.modes.planning===void 0?{}:{planning:H(a.modes.planning,r)}}let s=a.projectTrust===void 0?`ask`:a.projectTrust;if(typeof s!=`string`||!O.has(s))throw Error(`Doom config at ${r} projectTrust must be ask, always, or never`);return{modes:o,projectTrust:s,editor:q(a.editor,r),voice:G(a.voice,r),selection:K(a.selection,r)}}function Y(e,t){return e||t?{...e,...t}:void 0}function X(e,t){if(t.voice?.autoCapture)throw Error(`Repository Doom config voice.autoCapture is global-only; configure it in ~/.pi/.doom/config.yaml`);let n=e.modes?.planning,r=t.modes?.planning,i=n||r?{main:Y(n?.main,r?.main),subagents:Y(n?.subagents,r?.subagents),plansDirectory:r?.plansDirectory??n?.plansDirectory}:void 0,a=Q(e.voice,t.voice);return{modes:i?{planning:i}:void 0,projectTrust:t.projectTrust,editor:e.editor,voice:a,selection:e.selection||t.selection?{...e.selection,...t.selection}:void 0}}function Z(e,t){if(!(!e&&!t))return{...e,...t,model:t?.model??e.model}}function Q(e,t){if(!(!e&&!t))return{...e,...t,recorder:e?.recorder||t?.recorder?{...e?.recorder,...t?.recorder}:void 0,adapters:e?.adapters||t?.adapters?{[u]:Z(e?.adapters?.[u],t?.adapters?.[u]),[d]:Z(e?.adapters?.[d],t?.adapters?.[d]),[f]:Z(e?.adapters?.[f],t?.adapters?.[f])}:void 0,...e?.autoCapture?{autoCapture:e.autoCapture}:{}}}function $(e){return{engine:e.engine??t,language:e.language??`auto`,recorder:{...e.recorder,device:e.recorder?.device??`none:default`},adapters:e.adapters??{},...e.autoCapture?{autoCapture:{...e.autoCapture,startPhrases:[...e.autoCapture.startPhrases??[]],stopPhrases:[...e.autoCapture.stopPhrases??[]],utteranceIdleMs:e.autoCapture.utteranceIdleMs??3e3,transcriptionTimeoutMs:e.autoCapture.transcriptionTimeoutMs??15e3,tts:{...e.autoCapture.tts}}}:{}}}exports.DOOM_PLANNING_THINKING_LEVELS=w,exports.DOOM_VOICE_ENGINES=T,exports.DOOM_VOICE_TTS_ENGINES=E,exports.mergeDoomConfigs=X,exports.parseDoomConfig=J,exports.parsePlanningModeConfig=H,exports.resolveVoiceConfig=$;
|
|
1
|
+
let e=require("yaml");const t=`auto`,n=[`modes`,`projectTrust`,`editor`,`voice`,`selection`],r=[`majorMode`,`domains`,`profile`],i=[`planning`],a=[`main`,`subagents`,`plansDirectory`],o=[`model`,`thinking`],s=[`command`],c=[`engine`,`language`,`recorder`,`adapters`,`autoCapture`],l=[`binary`,`device`],u=`whisper-cpp`,d=`openai-whisper`,f=`mlx-whisper`,p=[u,d,f],m=[`binary`,`model`],h=[`path`,`id`],g=[`model`,`startPhrases`,`stopPhrases`,`composeOpenPhrases`,`composeSendPhrases`,`composeCancelPhrases`,`utteranceIdleMs`,`composeUtteranceIdleMs`,`composeNudgeMs`,`transcriptionTimeoutMs`,`tts`],_=[`engine`,`voice`,`rate`],v=[`hey doom`,`doom prompt`],y=[`that's it`,`doom send`],b=[`doom cancel`,`scratch that`],x=/^[^/\s]+\/[^/\s]+(?:\/[^/\s]+)*$/u,S=/[\p{P}\p{S}]+/gu,C=[`off`,`minimal`,`low`,`medium`,`high`,`xhigh`,`max`],w=[t,u,d,f],T=[`macos-say`],E=new Set(C),D=new Set([`ask`,`always`,`never`]),O=new Set(w),k=new Set(T);function A(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function j(e,t,n,r){let i=Object.keys(e).filter(e=>!t.includes(e));if(i.length>0)throw Error(`Doom config at ${r} has unsupported ${n} field(s): ${i.join(`, `)}`)}function M(e,t,n){if(e!==void 0){if(typeof e!=`string`||e.trim().length===0)throw Error(`Doom config at ${n} requires ${t} to be a non-empty string`);return e.trim()}}function N(e){for(let t of e){let e=t.codePointAt(0);if(e!==void 0&&(e<=31||e===127))return!0}return!1}function P(e,t,n,r){let i=M(e,t,n);if(i){if(i.length>r)throw Error(`Doom config at ${n} requires ${t} to be at most ${r} characters`);if(N(i))throw Error(`Doom config at ${n} does not allow control characters in ${t}`);return i}}function F(e,t){let n=`voice.autoCapture.model`,r=M(e,n,t);if(!r||!x.test(r))throw Error(`Doom config at ${t} requires ${n} in provider/model-id form`);return r}function I(e){return e.normalize(`NFKC`).toLocaleLowerCase(`en-US`).replace(S,` `).replace(/\s+/gu,` `).trim()}function L(e,t,n){if(e===void 0)return;if(!Array.isArray(e)||e.some(e=>typeof e!=`string`))throw Error(`Doom config at ${n} requires ${t} to be an array of strings`);if(e.length>16)throw Error(`Doom config at ${n} requires ${t} to contain at most 16 phrases`);let r=e.map(e=>e.trim()),i=new Set;for(let e of r){if(e.length===0)throw Error(`Doom config at ${n} requires ${t} to contain non-empty strings`);if(e.length>64)throw Error(`Doom config at ${n} requires ${t} phrases to be at most 64 characters`);if(N(e))throw Error(`Doom config at ${n} does not allow control characters in ${t}`);let r=I(e);if(!r)throw Error(`Doom config at ${n} requires ${t} to contain words`);if(i.has(r))throw Error(`Doom config at ${n} does not allow normalized duplicates in ${t}`);i.add(r)}return r}function R(e,t,n,r,i){if(e!==void 0){if(typeof e!=`number`||!Number.isInteger(e)||e<r||e>i)throw Error(`Doom config at ${n} requires ${t} to be an integer from ${r} to ${i}`);return e}}function z(e,t,n,r,i){let a=new Set((e??[]).map(I));if((t??[]).some(e=>a.has(I(e))))throw Error(`Doom config at ${i} does not allow a phrase in both ${n} and ${r}`)}function B(e,t){let n=`voice.autoCapture.tts`;if(!A(e))throw Error(`Doom config at ${t} requires ${n} to be an object`);if(j(e,_,n,t),typeof e.engine!=`string`||!k.has(e.engine))throw Error(`Doom config at ${t} requires ${n}.engine to be one of: ${[...k].join(`, `)}`);let r=P(e.voice,`${n}.voice`,t,64),i=e.rate;if(i!==void 0&&(typeof i!=`number`||!Number.isInteger(i)||i<80||i>500))throw Error(`Doom config at ${t} requires ${n}.rate to be an integer from 80 to 500`);return{engine:e.engine,...r?{voice:r}:{},...i===void 0?{}:{rate:i}}}function V(e,t){if(e===void 0)return;let n=`voice.autoCapture`;if(!A(e))throw Error(`Doom config at ${t} requires ${n} to be an object`);j(e,g,n,t);let r=L(e.startPhrases,`${n}.startPhrases`,t),i=L(e.stopPhrases,`${n}.stopPhrases`,t),a=L(e.composeOpenPhrases,`${n}.composeOpenPhrases`,t),o=L(e.composeSendPhrases,`${n}.composeSendPhrases`,t),s=L(e.composeCancelPhrases,`${n}.composeCancelPhrases`,t),c=R(e.utteranceIdleMs,`${n}.utteranceIdleMs`,t,1500,1e4),l=R(e.composeUtteranceIdleMs,`${n}.composeUtteranceIdleMs`,t,800,3e3),u=e.composeNudgeMs===0?0:R(e.composeNudgeMs,`${n}.composeNudgeMs`,t,5e3,6e4),d=R(e.transcriptionTimeoutMs,`${n}.transcriptionTimeoutMs`,t,1e3,12e4);return z(r,i,`startPhrases`,`stopPhrases`,t),z(o,s,`composeSendPhrases`,`composeCancelPhrases`,t),z(a,o,`composeOpenPhrases`,`composeSendPhrases`,t),z(a,s,`composeOpenPhrases`,`composeCancelPhrases`,t),z(i,o,`stopPhrases`,`composeSendPhrases`,t),z(i,s,`stopPhrases`,`composeCancelPhrases`,t),{model:F(e.model,t),...r?{startPhrases:r}:{},...i?{stopPhrases:i}:{},...a?{composeOpenPhrases:a}:{},...o?{composeSendPhrases:o}:{},...s?{composeCancelPhrases:s}:{},...c===void 0?{}:{utteranceIdleMs:c},...l===void 0?{}:{composeUtteranceIdleMs:l},...u===void 0?{}:{composeNudgeMs:u},...d===void 0?{}:{transcriptionTimeoutMs:d},tts:B(e.tts,t)}}function H(e,t,n){if(e===void 0)return;if(!A(e))throw Error(`Doom config at ${n} requires ${t} to be an object`);j(e,o,t,n);let r=e.thinking;if(r!==void 0&&(typeof r!=`string`||!E.has(r)))throw Error(`Doom config at ${n} requires ${t}.thinking to be one of: ${[...E].join(`, `)}`);let i=M(e.model,`${t}.model`,n);return{...i?{model:i}:{},...r?{thinking:r}:{}}}function U(e,t,n=`modes.planning`){if(!A(e))throw Error(`Doom config at ${t} requires ${n} to be an object`);return j(e,a,n,t),{main:H(e.main,`${n}.main`,t),subagents:H(e.subagents,`${n}.subagents`,t),plansDirectory:M(e.plansDirectory,`${n}.plansDirectory`,t)}}function W(e,t,n,r){if(!A(e))throw Error(`Doom config at ${n} requires ${t} to be an object`);j(e,h,t,n);let i=M(e.path,`${t}.path`,n),a=M(e.id,`${t}.id`,n);if(!!i==!!a)throw Error(`Doom config at ${n} requires exactly one of ${t}.path or ${t}.id`);if(a&&!r)throw Error(`Doom config at ${n} does not support ${t}.id for whisper-cpp`);return{...i?{path:i}:{},...a?{id:a}:{}}}function G(e,t,n,r){if(e===void 0)return;if(!A(e))throw Error(`Doom config at ${n} requires ${t} to be an object`);j(e,m,t,n);let i=M(e.binary,`${t}.binary`,n);return{...i?{binary:i}:{},model:W(e.model,`${t}.model`,n,r)}}function K(e,t){if(e===void 0)return;if(!A(e))throw Error(`Doom config at ${t} requires voice to be an object`);j(e,c,`voice`,t);let n={};if(e.engine!==void 0){if(typeof e.engine!=`string`||!O.has(e.engine))throw Error(`Doom config at ${t} requires voice.engine to be one of: ${[...O].join(`, `)}`);n.engine=e.engine}let r=M(e.language,`voice.language`,t);if(r&&(n.language=r),e.recorder!==void 0){if(!A(e.recorder))throw Error(`Doom config at ${t} requires voice.recorder to be an object`);j(e.recorder,l,`voice.recorder`,t),n.recorder={};let r=M(e.recorder.binary,`voice.recorder.binary`,t),i=M(e.recorder.device,`voice.recorder.device`,t);r&&(n.recorder.binary=r),i&&(n.recorder.device=i)}if(e.adapters!==void 0){if(!A(e.adapters))throw Error(`Doom config at ${t} requires voice.adapters to be an object`);j(e.adapters,p,`voice.adapters`,t),n.adapters={[u]:G(e.adapters[u],`voice.adapters.${u}`,t,!1),[d]:G(e.adapters[d],`voice.adapters.${d}`,t,!0),[f]:G(e.adapters[f],`voice.adapters.${f}`,t,!0)}}let i=V(e.autoCapture,t);return i&&(n.autoCapture=i),n}function q(e,t){if(e===void 0)return;if(!A(e))throw Error(`Doom config at ${t} requires selection to be an object`);j(e,r,`selection`,t);let n=e.domains;if(n!==void 0&&(!Array.isArray(n)||n.some(e=>typeof e!=`string`)))throw Error(`Doom config at ${t} requires selection.domains to be an array of strings`);return{majorMode:M(e.majorMode,`selection.majorMode`,t),domains:n,profile:M(e.profile,`selection.profile`,t)}}function J(e,t){if(e!==void 0){if(!A(e))throw Error(`Doom config at ${t} requires editor to be an object`);return j(e,s,`editor`,t),{command:M(e.command,`editor.command`,t)}}}function Y(t,r){let a;try{a=(0,e.parse)(t)??{}}catch(e){throw Error(`Could not parse Doom config at ${r}`,{cause:e})}if(!A(a))throw Error(`Doom config at ${r} must be a YAML object`);j(a,n,`root`,r);let o;if(a.modes!==void 0){if(!A(a.modes))throw Error(`Doom config at ${r} requires modes to be an object`);j(a.modes,i,`modes`,r),o=a.modes.planning===void 0?{}:{planning:U(a.modes.planning,r)}}let s=a.projectTrust===void 0?`ask`:a.projectTrust;if(typeof s!=`string`||!D.has(s))throw Error(`Doom config at ${r} projectTrust must be ask, always, or never`);return{modes:o,projectTrust:s,editor:J(a.editor,r),voice:K(a.voice,r),selection:q(a.selection,r)}}function X(e,t){return e||t?{...e,...t}:void 0}function Z(e,t){if(t.voice?.autoCapture)throw Error(`Repository Doom config voice.autoCapture is global-only; configure it in ~/.pi/.doom/config.yaml`);let n=e.modes?.planning,r=t.modes?.planning,i=n||r?{main:X(n?.main,r?.main),subagents:X(n?.subagents,r?.subagents),plansDirectory:r?.plansDirectory??n?.plansDirectory}:void 0,a=$(e.voice,t.voice);return{modes:i?{planning:i}:void 0,projectTrust:t.projectTrust,editor:e.editor,voice:a,selection:e.selection||t.selection?{...e.selection,...t.selection}:void 0}}function Q(e,t){if(!(!e&&!t))return{...e,...t,model:t?.model??e.model}}function $(e,t){if(!(!e&&!t))return{...e,...t,recorder:e?.recorder||t?.recorder?{...e?.recorder,...t?.recorder}:void 0,adapters:e?.adapters||t?.adapters?{[u]:Q(e?.adapters?.[u],t?.adapters?.[u]),[d]:Q(e?.adapters?.[d],t?.adapters?.[d]),[f]:Q(e?.adapters?.[f],t?.adapters?.[f])}:void 0,...e?.autoCapture?{autoCapture:e.autoCapture}:{}}}function ee(e){return{engine:e.engine??t,language:e.language??`auto`,recorder:{...e.recorder,device:e.recorder?.device??`none:default`},adapters:e.adapters??{},...e.autoCapture?{autoCapture:{...e.autoCapture,startPhrases:[...e.autoCapture.startPhrases??[]],stopPhrases:[...e.autoCapture.stopPhrases??[]],composeOpenPhrases:[...e.autoCapture.composeOpenPhrases??v],composeSendPhrases:[...e.autoCapture.composeSendPhrases??y],composeCancelPhrases:[...e.autoCapture.composeCancelPhrases??b],utteranceIdleMs:e.autoCapture.utteranceIdleMs??3e3,composeUtteranceIdleMs:e.autoCapture.composeUtteranceIdleMs??1200,composeNudgeMs:e.autoCapture.composeNudgeMs??1e4,transcriptionTimeoutMs:e.autoCapture.transcriptionTimeoutMs??15e3,tts:{...e.autoCapture.tts}}}:{}}}exports.DOOM_PLANNING_THINKING_LEVELS=C,exports.DOOM_VOICE_ENGINES=w,exports.DOOM_VOICE_TTS_ENGINES=T,exports.mergeDoomConfigs=Z,exports.parseDoomConfig=Y,exports.parsePlanningModeConfig=U,exports.resolveVoiceConfig=ee;
|
|
2
2
|
//# sourceMappingURL=configPolicy.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configPolicy.cjs","names":["parseYaml"],"sources":["../../src/services/configPolicy.ts"],"sourcesContent":["import { parse as parseYaml } from 'yaml';\nimport type {\n DoomConfig,\n DoomSelectionConfig,\n EditorConfig,\n PlanningAgentConfig,\n PlanningModeConfig,\n PlanningThinkingLevel,\n ProjectTrust,\n ResolvedVoiceConfig,\n VoiceAdapterConfig,\n VoiceAutoCaptureConfig,\n VoiceConfig,\n VoiceEngine,\n VoiceModelConfig,\n VoiceTtsConfig,\n VoiceTtsEngine,\n} from '../types/config.ts';\n\nconst DEFAULT_PROJECT_TRUST: ProjectTrust = 'ask';\nconst DEFAULT_VOICE_ENGINE: VoiceEngine = 'auto';\nconst DEFAULT_VOICE_LANGUAGE = 'auto';\nconst DEFAULT_RECORDER_DEVICE = 'none:default';\nconst ROOT_KEYS = ['modes', 'projectTrust', 'editor', 'voice', 'selection'] as const;\nconst SELECTION_KEYS = ['majorMode', 'domains', 'profile'] as const;\nconst MODE_KEYS = ['planning'] as const;\nconst PLANNING_KEYS = ['main', 'subagents', 'plansDirectory'] as const;\nconst AGENT_KEYS = ['model', 'thinking'] as const;\nconst EDITOR_KEYS = ['command'] as const;\nconst VOICE_KEYS = ['engine', 'language', 'recorder', 'adapters', 'autoCapture'] as const;\nconst RECORDER_KEYS = ['binary', 'device'] as const;\nconst WHISPER_CPP_ENGINE = 'whisper-cpp' as const;\nconst OPENAI_WHISPER_ENGINE = 'openai-whisper' as const;\nconst MLX_WHISPER_ENGINE = 'mlx-whisper' as const;\nconst MACOS_SAY_TTS_ENGINE = 'macos-say' as const;\nconst ADAPTERS_KEYS = [WHISPER_CPP_ENGINE, OPENAI_WHISPER_ENGINE, MLX_WHISPER_ENGINE] as const;\nconst ADAPTER_KEYS = ['binary', 'model'] as const;\nconst MODEL_KEYS = ['path', 'id'] as const;\nconst AUTO_CAPTURE_KEYS = [\n 'model',\n 'startPhrases',\n 'stopPhrases',\n 'utteranceIdleMs',\n 'transcriptionTimeoutMs',\n 'tts',\n] as const;\nconst TTS_KEYS = ['engine', 'voice', 'rate'] as const;\nconst DEFAULT_UTTERANCE_IDLE_MS = 3_000;\nconst MIN_UTTERANCE_IDLE_MS = 1_500;\nconst MAX_UTTERANCE_IDLE_MS = 10_000;\nconst DEFAULT_TRANSCRIPTION_TIMEOUT_MS = 15_000;\nconst MIN_TRANSCRIPTION_TIMEOUT_MS = 1_000;\nconst MAX_TRANSCRIPTION_TIMEOUT_MS = 120_000;\nconst MAX_CONTROL_PHRASES = 16;\nconst MAX_CONTROL_PHRASE_LENGTH = 64;\nconst MAX_TTS_VOICE_LENGTH = 64;\nconst MIN_TTS_RATE = 80;\nconst MAX_TTS_RATE = 500;\nconst MODEL_REFERENCE_PATTERN = /^[^/\\s]+\\/[^/\\s]+(?:\\/[^/\\s]+)*$/u;\nconst PHRASE_PUNCTUATION_PATTERN = /[\\p{P}\\p{S}]+/gu;\n/**\n * The accepted values, in the order a picker should offer them.\n *\n * Exported because the config UI has to offer exactly what the parser accepts,\n * and a second hand-written list in the contributing package would drift. The\n * validating Sets below are built from these for the same reason.\n */\nexport const DOOM_PLANNING_THINKING_LEVELS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'] as const;\nexport const DOOM_VOICE_ENGINES = [\n DEFAULT_VOICE_ENGINE,\n WHISPER_CPP_ENGINE,\n OPENAI_WHISPER_ENGINE,\n MLX_WHISPER_ENGINE,\n] as const;\nexport const DOOM_VOICE_TTS_ENGINES = [MACOS_SAY_TTS_ENGINE] as const;\n\nconst THINKING_VALUES = new Set<PlanningThinkingLevel>(DOOM_PLANNING_THINKING_LEVELS);\nconst TRUST_VALUES = new Set<ProjectTrust>(['ask', 'always', 'never']);\nconst ENGINE_VALUES = new Set<VoiceEngine>(DOOM_VOICE_ENGINES);\nconst TTS_ENGINE_VALUES = new Set<VoiceTtsEngine>(DOOM_VOICE_TTS_ENGINES);\ntype ConfigObject = Record<string, unknown>;\n\nfunction isObject(value: unknown): value is ConfigObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\nfunction assertKeys(value: ConfigObject, allowed: readonly string[], location: string, filePath: string): void {\n const unknown = Object.keys(value).filter((key) => !allowed.includes(key));\n if (unknown.length > 0)\n throw new Error(`Doom config at ${filePath} has unsupported ${location} field(s): ${unknown.join(', ')}`);\n}\nfunction optionalString(value: unknown, location: string, filePath: string): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== 'string' || value.trim().length === 0)\n throw new Error(`Doom config at ${filePath} requires ${location} to be a non-empty string`);\n return value.trim();\n}\nfunction hasControlCharacters(value: string): boolean {\n for (const character of value) {\n const codePoint = character.codePointAt(0);\n if (codePoint !== undefined && (codePoint <= 0x1f || codePoint === 0x7f)) return true;\n }\n return false;\n}\nfunction boundedOptionalString(\n value: unknown,\n location: string,\n filePath: string,\n maximumLength: number,\n): string | undefined {\n const text = optionalString(value, location, filePath);\n if (!text) return undefined;\n if (text.length > maximumLength)\n throw new Error(`Doom config at ${filePath} requires ${location} to be at most ${maximumLength} characters`);\n if (hasControlCharacters(text))\n throw new Error(`Doom config at ${filePath} does not allow control characters in ${location}`);\n return text;\n}\nfunction parseModelReference(value: unknown, filePath: string): string {\n const location = 'voice.autoCapture.model';\n const model = optionalString(value, location, filePath);\n if (!model || !MODEL_REFERENCE_PATTERN.test(model))\n throw new Error(`Doom config at ${filePath} requires ${location} in provider/model-id form`);\n return model;\n}\nfunction normalizeControlPhrase(value: string): string {\n return value\n .normalize('NFKC')\n .toLocaleLowerCase('en-US')\n .replace(PHRASE_PUNCTUATION_PATTERN, ' ')\n .replace(/\\s+/gu, ' ')\n .trim();\n}\nfunction parseControlPhrases(value: unknown, location: string, filePath: string): string[] | undefined {\n if (value === undefined) return undefined;\n if (!Array.isArray(value) || value.some((phrase) => typeof phrase !== 'string'))\n throw new Error(`Doom config at ${filePath} requires ${location} to be an array of strings`);\n if (value.length > MAX_CONTROL_PHRASES)\n throw new Error(\n `Doom config at ${filePath} requires ${location} to contain at most ${MAX_CONTROL_PHRASES} phrases`,\n );\n const phrases = value.map((phrase) => phrase.trim());\n const normalized = new Set<string>();\n for (const phrase of phrases) {\n if (phrase.length === 0)\n throw new Error(`Doom config at ${filePath} requires ${location} to contain non-empty strings`);\n if (phrase.length > MAX_CONTROL_PHRASE_LENGTH)\n throw new Error(\n `Doom config at ${filePath} requires ${location} phrases to be at most ${MAX_CONTROL_PHRASE_LENGTH} characters`,\n );\n if (hasControlCharacters(phrase))\n throw new Error(`Doom config at ${filePath} does not allow control characters in ${location}`);\n const key = normalizeControlPhrase(phrase);\n if (!key) throw new Error(`Doom config at ${filePath} requires ${location} to contain words`);\n if (normalized.has(key))\n throw new Error(`Doom config at ${filePath} does not allow normalized duplicates in ${location}`);\n normalized.add(key);\n }\n return phrases;\n}\nfunction parseTtsConfig(value: unknown, filePath: string): VoiceTtsConfig {\n const location = 'voice.autoCapture.tts';\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, TTS_KEYS, location, filePath);\n if (typeof value.engine !== 'string' || !TTS_ENGINE_VALUES.has(value.engine as VoiceTtsEngine))\n throw new Error(\n `Doom config at ${filePath} requires ${location}.engine to be one of: ${[...TTS_ENGINE_VALUES].join(', ')}`,\n );\n const voice = boundedOptionalString(value.voice, `${location}.voice`, filePath, MAX_TTS_VOICE_LENGTH);\n const rate = value.rate;\n if (\n rate !== undefined &&\n (typeof rate !== 'number' || !Number.isInteger(rate) || rate < MIN_TTS_RATE || rate > MAX_TTS_RATE)\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.rate to be an integer from ${MIN_TTS_RATE} to ${MAX_TTS_RATE}`,\n );\n }\n return {\n engine: value.engine as VoiceTtsEngine,\n ...(voice ? { voice } : {}),\n ...(rate === undefined ? {} : { rate }),\n };\n}\nfunction parseAutoCapture(value: unknown, filePath: string): VoiceAutoCaptureConfig | undefined {\n if (value === undefined) return undefined;\n const location = 'voice.autoCapture';\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, AUTO_CAPTURE_KEYS, location, filePath);\n const startPhrases = parseControlPhrases(value.startPhrases, `${location}.startPhrases`, filePath);\n const stopPhrases = parseControlPhrases(value.stopPhrases, `${location}.stopPhrases`, filePath);\n const utteranceIdleMs = value.utteranceIdleMs;\n if (\n utteranceIdleMs !== undefined &&\n (typeof utteranceIdleMs !== 'number' ||\n !Number.isInteger(utteranceIdleMs) ||\n utteranceIdleMs < MIN_UTTERANCE_IDLE_MS ||\n utteranceIdleMs > MAX_UTTERANCE_IDLE_MS)\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.utteranceIdleMs to be an integer from ${MIN_UTTERANCE_IDLE_MS} to ${MAX_UTTERANCE_IDLE_MS}`,\n );\n }\n const transcriptionTimeoutMs = value.transcriptionTimeoutMs;\n if (\n transcriptionTimeoutMs !== undefined &&\n (typeof transcriptionTimeoutMs !== 'number' ||\n !Number.isInteger(transcriptionTimeoutMs) ||\n transcriptionTimeoutMs < MIN_TRANSCRIPTION_TIMEOUT_MS ||\n transcriptionTimeoutMs > MAX_TRANSCRIPTION_TIMEOUT_MS)\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.transcriptionTimeoutMs to be an integer from ${MIN_TRANSCRIPTION_TIMEOUT_MS} to ${MAX_TRANSCRIPTION_TIMEOUT_MS}`,\n );\n }\n const startKeys = new Set((startPhrases ?? []).map(normalizeControlPhrase));\n if ((stopPhrases ?? []).some((phrase) => startKeys.has(normalizeControlPhrase(phrase))))\n throw new Error(`Doom config at ${filePath} does not allow a phrase in both startPhrases and stopPhrases`);\n return {\n model: parseModelReference(value.model, filePath),\n ...(startPhrases ? { startPhrases } : {}),\n ...(stopPhrases ? { stopPhrases } : {}),\n ...(utteranceIdleMs === undefined ? {} : { utteranceIdleMs }),\n ...(transcriptionTimeoutMs === undefined ? {} : { transcriptionTimeoutMs }),\n tts: parseTtsConfig(value.tts, filePath),\n };\n}\nfunction parseAgent(value: unknown, location: string, filePath: string): PlanningAgentConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, AGENT_KEYS, location, filePath);\n const thinking = value.thinking;\n if (\n thinking !== undefined &&\n (typeof thinking !== 'string' || !THINKING_VALUES.has(thinking as PlanningThinkingLevel))\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.thinking to be one of: ${[...THINKING_VALUES].join(', ')}`,\n );\n }\n const model = optionalString(value.model, `${location}.model`, filePath);\n return { ...(model ? { model } : {}), ...(thinking ? { thinking: thinking as PlanningThinkingLevel } : {}) };\n}\nexport function parsePlanningModeConfig(\n value: unknown,\n filePath: string,\n location = 'modes.planning',\n): PlanningModeConfig {\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, PLANNING_KEYS, location, filePath);\n return {\n main: parseAgent(value.main, `${location}.main`, filePath),\n subagents: parseAgent(value.subagents, `${location}.subagents`, filePath),\n plansDirectory: optionalString(value.plansDirectory, `${location}.plansDirectory`, filePath),\n };\n}\nfunction parseModel(value: unknown, location: string, filePath: string, allowId: boolean): VoiceModelConfig {\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, MODEL_KEYS, location, filePath);\n const modelPath = optionalString(value.path, `${location}.path`, filePath);\n const id = optionalString(value.id, `${location}.id`, filePath);\n if (Boolean(modelPath) === Boolean(id))\n throw new Error(`Doom config at ${filePath} requires exactly one of ${location}.path or ${location}.id`);\n if (id && !allowId) throw new Error(`Doom config at ${filePath} does not support ${location}.id for whisper-cpp`);\n return { ...(modelPath ? { path: modelPath } : {}), ...(id ? { id } : {}) };\n}\nfunction parseAdapter(\n value: unknown,\n location: string,\n filePath: string,\n allowId: boolean,\n): VoiceAdapterConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, ADAPTER_KEYS, location, filePath);\n const binary = optionalString(value.binary, `${location}.binary`, filePath);\n return { ...(binary ? { binary } : {}), model: parseModel(value.model, `${location}.model`, filePath, allowId) };\n}\nfunction parseVoice(value: unknown, filePath: string): VoiceConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires voice to be an object`);\n assertKeys(value, VOICE_KEYS, 'voice', filePath);\n const result: VoiceConfig = {};\n if (value.engine !== undefined) {\n if (typeof value.engine !== 'string' || !ENGINE_VALUES.has(value.engine as VoiceEngine))\n throw new Error(\n `Doom config at ${filePath} requires voice.engine to be one of: ${[...ENGINE_VALUES].join(', ')}`,\n );\n result.engine = value.engine as VoiceEngine;\n }\n const language = optionalString(value.language, 'voice.language', filePath);\n if (language) result.language = language;\n if (value.recorder !== undefined) {\n if (!isObject(value.recorder))\n throw new Error(`Doom config at ${filePath} requires voice.recorder to be an object`);\n assertKeys(value.recorder, RECORDER_KEYS, 'voice.recorder', filePath);\n result.recorder = {};\n const binary = optionalString(value.recorder.binary, 'voice.recorder.binary', filePath);\n const device = optionalString(value.recorder.device, 'voice.recorder.device', filePath);\n if (binary) result.recorder.binary = binary;\n if (device) result.recorder.device = device;\n }\n if (value.adapters !== undefined) {\n if (!isObject(value.adapters))\n throw new Error(`Doom config at ${filePath} requires voice.adapters to be an object`);\n assertKeys(value.adapters, ADAPTERS_KEYS, 'voice.adapters', filePath);\n result.adapters = {\n [WHISPER_CPP_ENGINE]: parseAdapter(\n value.adapters[WHISPER_CPP_ENGINE],\n `voice.adapters.${WHISPER_CPP_ENGINE}`,\n filePath,\n false,\n ),\n [OPENAI_WHISPER_ENGINE]: parseAdapter(\n value.adapters[OPENAI_WHISPER_ENGINE],\n `voice.adapters.${OPENAI_WHISPER_ENGINE}`,\n filePath,\n true,\n ),\n [MLX_WHISPER_ENGINE]: parseAdapter(\n value.adapters[MLX_WHISPER_ENGINE],\n `voice.adapters.${MLX_WHISPER_ENGINE}`,\n filePath,\n true,\n ),\n };\n }\n const autoCapture = parseAutoCapture(value.autoCapture, filePath);\n if (autoCapture) result.autoCapture = autoCapture;\n return result;\n}\nfunction parseSelection(value: unknown, filePath: string): DoomSelectionConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires selection to be an object`);\n assertKeys(value, SELECTION_KEYS, 'selection', filePath);\n const domains = value.domains;\n if (domains !== undefined && (!Array.isArray(domains) || domains.some((name) => typeof name !== 'string'))) {\n throw new Error(`Doom config at ${filePath} requires selection.domains to be an array of strings`);\n }\n return {\n majorMode: optionalString(value.majorMode, 'selection.majorMode', filePath),\n // An empty list is meaningful: it selects no content domains at all.\n domains: domains as string[] | undefined,\n profile: optionalString(value.profile, 'selection.profile', filePath),\n };\n}\nfunction parseEditor(value: unknown, filePath: string): EditorConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires editor to be an object`);\n assertKeys(value, EDITOR_KEYS, 'editor', filePath);\n return { command: optionalString(value.command, 'editor.command', filePath) };\n}\nexport function parseDoomConfig(content: string, filePath: string): DoomConfig {\n let parsed: unknown;\n try {\n parsed = parseYaml(content) ?? {};\n } catch (error) {\n throw new Error(`Could not parse Doom config at ${filePath}`, { cause: error });\n }\n if (!isObject(parsed)) throw new Error(`Doom config at ${filePath} must be a YAML object`);\n assertKeys(parsed, ROOT_KEYS, 'root', filePath);\n let modes: DoomConfig['modes'];\n if (parsed.modes !== undefined) {\n if (!isObject(parsed.modes)) throw new Error(`Doom config at ${filePath} requires modes to be an object`);\n assertKeys(parsed.modes, MODE_KEYS, 'modes', filePath);\n modes =\n parsed.modes.planning === undefined ? {} : { planning: parsePlanningModeConfig(parsed.modes.planning, filePath) };\n }\n const trust = parsed.projectTrust === undefined ? DEFAULT_PROJECT_TRUST : parsed.projectTrust;\n if (typeof trust !== 'string' || !TRUST_VALUES.has(trust as ProjectTrust))\n throw new Error(`Doom config at ${filePath} projectTrust must be ask, always, or never`);\n return {\n modes,\n projectTrust: trust as ProjectTrust,\n editor: parseEditor(parsed.editor, filePath),\n voice: parseVoice(parsed.voice, filePath),\n selection: parseSelection(parsed.selection, filePath),\n };\n}\nfunction mergeAgent(\n globalValue?: PlanningAgentConfig,\n repositoryValue?: PlanningAgentConfig,\n): PlanningAgentConfig | undefined {\n return globalValue || repositoryValue ? { ...globalValue, ...repositoryValue } : undefined;\n}\nexport function mergeDoomConfigs(globalConfig: DoomConfig, repositoryConfig: DoomConfig): DoomConfig {\n if (repositoryConfig.voice?.autoCapture)\n throw new Error('Repository Doom config voice.autoCapture is global-only; configure it in ~/.pi/.doom/config.yaml');\n const globalPlanning = globalConfig.modes?.planning;\n const repositoryPlanning = repositoryConfig.modes?.planning;\n const planning =\n globalPlanning || repositoryPlanning\n ? {\n main: mergeAgent(globalPlanning?.main, repositoryPlanning?.main),\n subagents: mergeAgent(globalPlanning?.subagents, repositoryPlanning?.subagents),\n plansDirectory: repositoryPlanning?.plansDirectory ?? globalPlanning?.plansDirectory,\n }\n : undefined;\n const voice = mergeVoice(globalConfig.voice, repositoryConfig.voice);\n return {\n modes: planning ? { planning } : undefined,\n projectTrust: repositoryConfig.projectTrust,\n editor: globalConfig.editor,\n voice,\n // Per key, so a repository can pin one axis and inherit the rest.\n selection:\n globalConfig.selection || repositoryConfig.selection\n ? { ...globalConfig.selection, ...repositoryConfig.selection }\n : undefined,\n };\n}\nfunction mergeAdapter(\n globalValue?: VoiceAdapterConfig,\n repositoryValue?: VoiceAdapterConfig,\n): VoiceAdapterConfig | undefined {\n if (!globalValue && !repositoryValue) return undefined;\n return { ...globalValue, ...repositoryValue, model: repositoryValue?.model ?? globalValue!.model };\n}\nfunction mergeVoice(globalValue?: VoiceConfig, repositoryValue?: VoiceConfig): VoiceConfig | undefined {\n if (!globalValue && !repositoryValue) return undefined;\n return {\n ...globalValue,\n ...repositoryValue,\n recorder:\n globalValue?.recorder || repositoryValue?.recorder\n ? { ...globalValue?.recorder, ...repositoryValue?.recorder }\n : undefined,\n adapters:\n globalValue?.adapters || repositoryValue?.adapters\n ? {\n [WHISPER_CPP_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[WHISPER_CPP_ENGINE],\n repositoryValue?.adapters?.[WHISPER_CPP_ENGINE],\n ),\n [OPENAI_WHISPER_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[OPENAI_WHISPER_ENGINE],\n repositoryValue?.adapters?.[OPENAI_WHISPER_ENGINE],\n ),\n [MLX_WHISPER_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[MLX_WHISPER_ENGINE],\n repositoryValue?.adapters?.[MLX_WHISPER_ENGINE],\n ),\n }\n : undefined,\n ...(globalValue?.autoCapture ? { autoCapture: globalValue.autoCapture } : {}),\n };\n}\nexport function resolveVoiceConfig(config: VoiceConfig): ResolvedVoiceConfig {\n return {\n engine: config.engine ?? DEFAULT_VOICE_ENGINE,\n language: config.language ?? DEFAULT_VOICE_LANGUAGE,\n recorder: { ...config.recorder, device: config.recorder?.device ?? DEFAULT_RECORDER_DEVICE },\n adapters: config.adapters ?? {},\n ...(config.autoCapture\n ? {\n autoCapture: {\n ...config.autoCapture,\n startPhrases: [...(config.autoCapture.startPhrases ?? [])],\n stopPhrases: [...(config.autoCapture.stopPhrases ?? [])],\n utteranceIdleMs: config.autoCapture.utteranceIdleMs ?? DEFAULT_UTTERANCE_IDLE_MS,\n transcriptionTimeoutMs: config.autoCapture.transcriptionTimeoutMs ?? DEFAULT_TRANSCRIPTION_TIMEOUT_MS,\n tts: { ...config.autoCapture.tts },\n },\n }\n : {}),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"configPolicy.cjs","names":["parseYaml"],"sources":["../../src/services/configPolicy.ts"],"sourcesContent":["import { parse as parseYaml } from 'yaml';\nimport type {\n DoomConfig,\n DoomSelectionConfig,\n EditorConfig,\n PlanningAgentConfig,\n PlanningModeConfig,\n PlanningThinkingLevel,\n ProjectTrust,\n ResolvedVoiceConfig,\n VoiceAdapterConfig,\n VoiceAutoCaptureConfig,\n VoiceConfig,\n VoiceEngine,\n VoiceModelConfig,\n VoiceTtsConfig,\n VoiceTtsEngine,\n} from '../types/config.ts';\n\nconst DEFAULT_PROJECT_TRUST: ProjectTrust = 'ask';\nconst DEFAULT_VOICE_ENGINE: VoiceEngine = 'auto';\nconst DEFAULT_VOICE_LANGUAGE = 'auto';\nconst DEFAULT_RECORDER_DEVICE = 'none:default';\nconst ROOT_KEYS = ['modes', 'projectTrust', 'editor', 'voice', 'selection'] as const;\nconst SELECTION_KEYS = ['majorMode', 'domains', 'profile'] as const;\nconst MODE_KEYS = ['planning'] as const;\nconst PLANNING_KEYS = ['main', 'subagents', 'plansDirectory'] as const;\nconst AGENT_KEYS = ['model', 'thinking'] as const;\nconst EDITOR_KEYS = ['command'] as const;\nconst VOICE_KEYS = ['engine', 'language', 'recorder', 'adapters', 'autoCapture'] as const;\nconst RECORDER_KEYS = ['binary', 'device'] as const;\nconst WHISPER_CPP_ENGINE = 'whisper-cpp' as const;\nconst OPENAI_WHISPER_ENGINE = 'openai-whisper' as const;\nconst MLX_WHISPER_ENGINE = 'mlx-whisper' as const;\nconst MACOS_SAY_TTS_ENGINE = 'macos-say' as const;\nconst ADAPTERS_KEYS = [WHISPER_CPP_ENGINE, OPENAI_WHISPER_ENGINE, MLX_WHISPER_ENGINE] as const;\nconst ADAPTER_KEYS = ['binary', 'model'] as const;\nconst MODEL_KEYS = ['path', 'id'] as const;\nconst AUTO_CAPTURE_KEYS = [\n 'model',\n 'startPhrases',\n 'stopPhrases',\n 'composeOpenPhrases',\n 'composeSendPhrases',\n 'composeCancelPhrases',\n 'utteranceIdleMs',\n 'composeUtteranceIdleMs',\n 'composeNudgeMs',\n 'transcriptionTimeoutMs',\n 'tts',\n] as const;\nconst TTS_KEYS = ['engine', 'voice', 'rate'] as const;\nconst DEFAULT_UTTERANCE_IDLE_MS = 3_000;\nconst MIN_UTTERANCE_IDLE_MS = 1_500;\nconst MAX_UTTERANCE_IDLE_MS = 10_000;\n/**\n * The endpoint window used while a composition draft is collecting.\n *\n * Shorter than `utteranceIdleMs` on purpose. A turn produces exactly one transcript, so\n * with the ordinary ~3 s window a short command spoken after a brief pause arrives glued\n * to the end of the preceding sentence and can never be read as a command. Roughly twice\n * the VAD's own 600 ms trailing-silence window: long enough that ordinary speech rhythm\n * does not split a sentence, short enough that a deliberate pause does. Over-splitting is\n * harmless here and only here, because draft segments are joined back together with a\n * space.\n */\nconst DEFAULT_COMPOSE_UTTERANCE_IDLE_MS = 1_200;\nconst MIN_COMPOSE_UTTERANCE_IDLE_MS = 800;\nconst MAX_COMPOSE_UTTERANCE_IDLE_MS = 3_000;\n/** Silence before Voice reminds the user a draft is still open. `0` disables it. */\nconst DEFAULT_COMPOSE_NUDGE_MS = 10_000;\nconst MIN_COMPOSE_NUDGE_MS = 5_000;\nconst MAX_COMPOSE_NUDGE_MS = 60_000;\n/**\n * Defaults kept as lists so the phrases that shipped first keep working.\n *\n * `hey doom` and `that's it` read as speech; `doom prompt` and `doom send` are what\n * existing users have in their fingers. Dropping the originals would break them for no\n * gain, and the cost of carrying both is two extra comparisons per command candidate.\n */\nconst DEFAULT_COMPOSE_OPEN_PHRASES = ['hey doom', 'doom prompt'] as const;\nconst DEFAULT_COMPOSE_SEND_PHRASES = [\"that's it\", 'doom send'] as const;\nconst DEFAULT_COMPOSE_CANCEL_PHRASES = ['doom cancel', 'scratch that'] as const;\nconst DEFAULT_TRANSCRIPTION_TIMEOUT_MS = 15_000;\nconst MIN_TRANSCRIPTION_TIMEOUT_MS = 1_000;\nconst MAX_TRANSCRIPTION_TIMEOUT_MS = 120_000;\nconst MAX_CONTROL_PHRASES = 16;\nconst MAX_CONTROL_PHRASE_LENGTH = 64;\nconst MAX_TTS_VOICE_LENGTH = 64;\nconst MIN_TTS_RATE = 80;\nconst MAX_TTS_RATE = 500;\nconst MODEL_REFERENCE_PATTERN = /^[^/\\s]+\\/[^/\\s]+(?:\\/[^/\\s]+)*$/u;\nconst PHRASE_PUNCTUATION_PATTERN = /[\\p{P}\\p{S}]+/gu;\n/**\n * The accepted values, in the order a picker should offer them.\n *\n * Exported because the config UI has to offer exactly what the parser accepts,\n * and a second hand-written list in the contributing package would drift. The\n * validating Sets below are built from these for the same reason.\n */\nexport const DOOM_PLANNING_THINKING_LEVELS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'] as const;\nexport const DOOM_VOICE_ENGINES = [\n DEFAULT_VOICE_ENGINE,\n WHISPER_CPP_ENGINE,\n OPENAI_WHISPER_ENGINE,\n MLX_WHISPER_ENGINE,\n] as const;\nexport const DOOM_VOICE_TTS_ENGINES = [MACOS_SAY_TTS_ENGINE] as const;\n\nconst THINKING_VALUES = new Set<PlanningThinkingLevel>(DOOM_PLANNING_THINKING_LEVELS);\nconst TRUST_VALUES = new Set<ProjectTrust>(['ask', 'always', 'never']);\nconst ENGINE_VALUES = new Set<VoiceEngine>(DOOM_VOICE_ENGINES);\nconst TTS_ENGINE_VALUES = new Set<VoiceTtsEngine>(DOOM_VOICE_TTS_ENGINES);\ntype ConfigObject = Record<string, unknown>;\n\nfunction isObject(value: unknown): value is ConfigObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\nfunction assertKeys(value: ConfigObject, allowed: readonly string[], location: string, filePath: string): void {\n const unknown = Object.keys(value).filter((key) => !allowed.includes(key));\n if (unknown.length > 0)\n throw new Error(`Doom config at ${filePath} has unsupported ${location} field(s): ${unknown.join(', ')}`);\n}\nfunction optionalString(value: unknown, location: string, filePath: string): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== 'string' || value.trim().length === 0)\n throw new Error(`Doom config at ${filePath} requires ${location} to be a non-empty string`);\n return value.trim();\n}\nfunction hasControlCharacters(value: string): boolean {\n for (const character of value) {\n const codePoint = character.codePointAt(0);\n if (codePoint !== undefined && (codePoint <= 0x1f || codePoint === 0x7f)) return true;\n }\n return false;\n}\nfunction boundedOptionalString(\n value: unknown,\n location: string,\n filePath: string,\n maximumLength: number,\n): string | undefined {\n const text = optionalString(value, location, filePath);\n if (!text) return undefined;\n if (text.length > maximumLength)\n throw new Error(`Doom config at ${filePath} requires ${location} to be at most ${maximumLength} characters`);\n if (hasControlCharacters(text))\n throw new Error(`Doom config at ${filePath} does not allow control characters in ${location}`);\n return text;\n}\nfunction parseModelReference(value: unknown, filePath: string): string {\n const location = 'voice.autoCapture.model';\n const model = optionalString(value, location, filePath);\n if (!model || !MODEL_REFERENCE_PATTERN.test(model))\n throw new Error(`Doom config at ${filePath} requires ${location} in provider/model-id form`);\n return model;\n}\nfunction normalizeControlPhrase(value: string): string {\n return value\n .normalize('NFKC')\n .toLocaleLowerCase('en-US')\n .replace(PHRASE_PUNCTUATION_PATTERN, ' ')\n .replace(/\\s+/gu, ' ')\n .trim();\n}\nfunction parseControlPhrases(value: unknown, location: string, filePath: string): string[] | undefined {\n if (value === undefined) return undefined;\n if (!Array.isArray(value) || value.some((phrase) => typeof phrase !== 'string'))\n throw new Error(`Doom config at ${filePath} requires ${location} to be an array of strings`);\n if (value.length > MAX_CONTROL_PHRASES)\n throw new Error(\n `Doom config at ${filePath} requires ${location} to contain at most ${MAX_CONTROL_PHRASES} phrases`,\n );\n const phrases = value.map((phrase) => phrase.trim());\n const normalized = new Set<string>();\n for (const phrase of phrases) {\n if (phrase.length === 0)\n throw new Error(`Doom config at ${filePath} requires ${location} to contain non-empty strings`);\n if (phrase.length > MAX_CONTROL_PHRASE_LENGTH)\n throw new Error(\n `Doom config at ${filePath} requires ${location} phrases to be at most ${MAX_CONTROL_PHRASE_LENGTH} characters`,\n );\n if (hasControlCharacters(phrase))\n throw new Error(`Doom config at ${filePath} does not allow control characters in ${location}`);\n const key = normalizeControlPhrase(phrase);\n if (!key) throw new Error(`Doom config at ${filePath} requires ${location} to contain words`);\n if (normalized.has(key))\n throw new Error(`Doom config at ${filePath} does not allow normalized duplicates in ${location}`);\n normalized.add(key);\n }\n return phrases;\n}\nfunction parseBoundedInteger(\n value: unknown,\n location: string,\n filePath: string,\n minimum: number,\n maximum: number,\n): number | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== 'number' || !Number.isInteger(value) || value < minimum || value > maximum)\n throw new Error(`Doom config at ${filePath} requires ${location} to be an integer from ${minimum} to ${maximum}`);\n return value;\n}\n/**\n * Rejects a phrase claimed by two control roles at once.\n *\n * Composition commands are adjudicated before stop phrases and before leading\n * start-phrase removal, so a phrase shared with a later role is not ambiguous, it is\n * silently unreachable. Without a screen there is nothing to show which role won.\n */\nfunction assertDisjointPhrases(\n left: readonly string[] | undefined,\n right: readonly string[] | undefined,\n leftName: string,\n rightName: string,\n filePath: string,\n): void {\n const keys = new Set((left ?? []).map(normalizeControlPhrase));\n if ((right ?? []).some((phrase) => keys.has(normalizeControlPhrase(phrase))))\n throw new Error(`Doom config at ${filePath} does not allow a phrase in both ${leftName} and ${rightName}`);\n}\nfunction parseTtsConfig(value: unknown, filePath: string): VoiceTtsConfig {\n const location = 'voice.autoCapture.tts';\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, TTS_KEYS, location, filePath);\n if (typeof value.engine !== 'string' || !TTS_ENGINE_VALUES.has(value.engine as VoiceTtsEngine))\n throw new Error(\n `Doom config at ${filePath} requires ${location}.engine to be one of: ${[...TTS_ENGINE_VALUES].join(', ')}`,\n );\n const voice = boundedOptionalString(value.voice, `${location}.voice`, filePath, MAX_TTS_VOICE_LENGTH);\n const rate = value.rate;\n if (\n rate !== undefined &&\n (typeof rate !== 'number' || !Number.isInteger(rate) || rate < MIN_TTS_RATE || rate > MAX_TTS_RATE)\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.rate to be an integer from ${MIN_TTS_RATE} to ${MAX_TTS_RATE}`,\n );\n }\n return {\n engine: value.engine as VoiceTtsEngine,\n ...(voice ? { voice } : {}),\n ...(rate === undefined ? {} : { rate }),\n };\n}\nfunction parseAutoCapture(value: unknown, filePath: string): VoiceAutoCaptureConfig | undefined {\n if (value === undefined) return undefined;\n const location = 'voice.autoCapture';\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, AUTO_CAPTURE_KEYS, location, filePath);\n const startPhrases = parseControlPhrases(value.startPhrases, `${location}.startPhrases`, filePath);\n const stopPhrases = parseControlPhrases(value.stopPhrases, `${location}.stopPhrases`, filePath);\n const composeOpenPhrases = parseControlPhrases(value.composeOpenPhrases, `${location}.composeOpenPhrases`, filePath);\n const composeSendPhrases = parseControlPhrases(value.composeSendPhrases, `${location}.composeSendPhrases`, filePath);\n const composeCancelPhrases = parseControlPhrases(\n value.composeCancelPhrases,\n `${location}.composeCancelPhrases`,\n filePath,\n );\n const utteranceIdleMs = parseBoundedInteger(\n value.utteranceIdleMs,\n `${location}.utteranceIdleMs`,\n filePath,\n MIN_UTTERANCE_IDLE_MS,\n MAX_UTTERANCE_IDLE_MS,\n );\n const composeUtteranceIdleMs = parseBoundedInteger(\n value.composeUtteranceIdleMs,\n `${location}.composeUtteranceIdleMs`,\n filePath,\n MIN_COMPOSE_UTTERANCE_IDLE_MS,\n MAX_COMPOSE_UTTERANCE_IDLE_MS,\n );\n // Zero is the documented off switch, so it is accepted outside the range rather than\n // forcing a separate boolean that could disagree with the interval.\n const composeNudgeMs =\n value.composeNudgeMs === 0\n ? 0\n : parseBoundedInteger(\n value.composeNudgeMs,\n `${location}.composeNudgeMs`,\n filePath,\n MIN_COMPOSE_NUDGE_MS,\n MAX_COMPOSE_NUDGE_MS,\n );\n const transcriptionTimeoutMs = parseBoundedInteger(\n value.transcriptionTimeoutMs,\n `${location}.transcriptionTimeoutMs`,\n filePath,\n MIN_TRANSCRIPTION_TIMEOUT_MS,\n MAX_TRANSCRIPTION_TIMEOUT_MS,\n );\n assertDisjointPhrases(startPhrases, stopPhrases, 'startPhrases', 'stopPhrases', filePath);\n // `composeOpenPhrases` may overlap `startPhrases`: addressing the agent and opening a\n // draft are the same gesture, composition is adjudicated first, and the shipped\n // defaults deliberately share `hey doom`.\n assertDisjointPhrases(\n composeSendPhrases,\n composeCancelPhrases,\n 'composeSendPhrases',\n 'composeCancelPhrases',\n filePath,\n );\n assertDisjointPhrases(composeOpenPhrases, composeSendPhrases, 'composeOpenPhrases', 'composeSendPhrases', filePath);\n assertDisjointPhrases(\n composeOpenPhrases,\n composeCancelPhrases,\n 'composeOpenPhrases',\n 'composeCancelPhrases',\n filePath,\n );\n assertDisjointPhrases(stopPhrases, composeSendPhrases, 'stopPhrases', 'composeSendPhrases', filePath);\n assertDisjointPhrases(stopPhrases, composeCancelPhrases, 'stopPhrases', 'composeCancelPhrases', filePath);\n return {\n model: parseModelReference(value.model, filePath),\n ...(startPhrases ? { startPhrases } : {}),\n ...(stopPhrases ? { stopPhrases } : {}),\n ...(composeOpenPhrases ? { composeOpenPhrases } : {}),\n ...(composeSendPhrases ? { composeSendPhrases } : {}),\n ...(composeCancelPhrases ? { composeCancelPhrases } : {}),\n ...(utteranceIdleMs === undefined ? {} : { utteranceIdleMs }),\n ...(composeUtteranceIdleMs === undefined ? {} : { composeUtteranceIdleMs }),\n ...(composeNudgeMs === undefined ? {} : { composeNudgeMs }),\n ...(transcriptionTimeoutMs === undefined ? {} : { transcriptionTimeoutMs }),\n tts: parseTtsConfig(value.tts, filePath),\n };\n}\nfunction parseAgent(value: unknown, location: string, filePath: string): PlanningAgentConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, AGENT_KEYS, location, filePath);\n const thinking = value.thinking;\n if (\n thinking !== undefined &&\n (typeof thinking !== 'string' || !THINKING_VALUES.has(thinking as PlanningThinkingLevel))\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.thinking to be one of: ${[...THINKING_VALUES].join(', ')}`,\n );\n }\n const model = optionalString(value.model, `${location}.model`, filePath);\n return { ...(model ? { model } : {}), ...(thinking ? { thinking: thinking as PlanningThinkingLevel } : {}) };\n}\nexport function parsePlanningModeConfig(\n value: unknown,\n filePath: string,\n location = 'modes.planning',\n): PlanningModeConfig {\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, PLANNING_KEYS, location, filePath);\n return {\n main: parseAgent(value.main, `${location}.main`, filePath),\n subagents: parseAgent(value.subagents, `${location}.subagents`, filePath),\n plansDirectory: optionalString(value.plansDirectory, `${location}.plansDirectory`, filePath),\n };\n}\nfunction parseModel(value: unknown, location: string, filePath: string, allowId: boolean): VoiceModelConfig {\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, MODEL_KEYS, location, filePath);\n const modelPath = optionalString(value.path, `${location}.path`, filePath);\n const id = optionalString(value.id, `${location}.id`, filePath);\n if (Boolean(modelPath) === Boolean(id))\n throw new Error(`Doom config at ${filePath} requires exactly one of ${location}.path or ${location}.id`);\n if (id && !allowId) throw new Error(`Doom config at ${filePath} does not support ${location}.id for whisper-cpp`);\n return { ...(modelPath ? { path: modelPath } : {}), ...(id ? { id } : {}) };\n}\nfunction parseAdapter(\n value: unknown,\n location: string,\n filePath: string,\n allowId: boolean,\n): VoiceAdapterConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, ADAPTER_KEYS, location, filePath);\n const binary = optionalString(value.binary, `${location}.binary`, filePath);\n return { ...(binary ? { binary } : {}), model: parseModel(value.model, `${location}.model`, filePath, allowId) };\n}\nfunction parseVoice(value: unknown, filePath: string): VoiceConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires voice to be an object`);\n assertKeys(value, VOICE_KEYS, 'voice', filePath);\n const result: VoiceConfig = {};\n if (value.engine !== undefined) {\n if (typeof value.engine !== 'string' || !ENGINE_VALUES.has(value.engine as VoiceEngine))\n throw new Error(\n `Doom config at ${filePath} requires voice.engine to be one of: ${[...ENGINE_VALUES].join(', ')}`,\n );\n result.engine = value.engine as VoiceEngine;\n }\n const language = optionalString(value.language, 'voice.language', filePath);\n if (language) result.language = language;\n if (value.recorder !== undefined) {\n if (!isObject(value.recorder))\n throw new Error(`Doom config at ${filePath} requires voice.recorder to be an object`);\n assertKeys(value.recorder, RECORDER_KEYS, 'voice.recorder', filePath);\n result.recorder = {};\n const binary = optionalString(value.recorder.binary, 'voice.recorder.binary', filePath);\n const device = optionalString(value.recorder.device, 'voice.recorder.device', filePath);\n if (binary) result.recorder.binary = binary;\n if (device) result.recorder.device = device;\n }\n if (value.adapters !== undefined) {\n if (!isObject(value.adapters))\n throw new Error(`Doom config at ${filePath} requires voice.adapters to be an object`);\n assertKeys(value.adapters, ADAPTERS_KEYS, 'voice.adapters', filePath);\n result.adapters = {\n [WHISPER_CPP_ENGINE]: parseAdapter(\n value.adapters[WHISPER_CPP_ENGINE],\n `voice.adapters.${WHISPER_CPP_ENGINE}`,\n filePath,\n false,\n ),\n [OPENAI_WHISPER_ENGINE]: parseAdapter(\n value.adapters[OPENAI_WHISPER_ENGINE],\n `voice.adapters.${OPENAI_WHISPER_ENGINE}`,\n filePath,\n true,\n ),\n [MLX_WHISPER_ENGINE]: parseAdapter(\n value.adapters[MLX_WHISPER_ENGINE],\n `voice.adapters.${MLX_WHISPER_ENGINE}`,\n filePath,\n true,\n ),\n };\n }\n const autoCapture = parseAutoCapture(value.autoCapture, filePath);\n if (autoCapture) result.autoCapture = autoCapture;\n return result;\n}\nfunction parseSelection(value: unknown, filePath: string): DoomSelectionConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires selection to be an object`);\n assertKeys(value, SELECTION_KEYS, 'selection', filePath);\n const domains = value.domains;\n if (domains !== undefined && (!Array.isArray(domains) || domains.some((name) => typeof name !== 'string'))) {\n throw new Error(`Doom config at ${filePath} requires selection.domains to be an array of strings`);\n }\n return {\n majorMode: optionalString(value.majorMode, 'selection.majorMode', filePath),\n // An empty list is meaningful: it selects no content domains at all.\n domains: domains as string[] | undefined,\n profile: optionalString(value.profile, 'selection.profile', filePath),\n };\n}\nfunction parseEditor(value: unknown, filePath: string): EditorConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires editor to be an object`);\n assertKeys(value, EDITOR_KEYS, 'editor', filePath);\n return { command: optionalString(value.command, 'editor.command', filePath) };\n}\nexport function parseDoomConfig(content: string, filePath: string): DoomConfig {\n let parsed: unknown;\n try {\n parsed = parseYaml(content) ?? {};\n } catch (error) {\n throw new Error(`Could not parse Doom config at ${filePath}`, { cause: error });\n }\n if (!isObject(parsed)) throw new Error(`Doom config at ${filePath} must be a YAML object`);\n assertKeys(parsed, ROOT_KEYS, 'root', filePath);\n let modes: DoomConfig['modes'];\n if (parsed.modes !== undefined) {\n if (!isObject(parsed.modes)) throw new Error(`Doom config at ${filePath} requires modes to be an object`);\n assertKeys(parsed.modes, MODE_KEYS, 'modes', filePath);\n modes =\n parsed.modes.planning === undefined ? {} : { planning: parsePlanningModeConfig(parsed.modes.planning, filePath) };\n }\n const trust = parsed.projectTrust === undefined ? DEFAULT_PROJECT_TRUST : parsed.projectTrust;\n if (typeof trust !== 'string' || !TRUST_VALUES.has(trust as ProjectTrust))\n throw new Error(`Doom config at ${filePath} projectTrust must be ask, always, or never`);\n return {\n modes,\n projectTrust: trust as ProjectTrust,\n editor: parseEditor(parsed.editor, filePath),\n voice: parseVoice(parsed.voice, filePath),\n selection: parseSelection(parsed.selection, filePath),\n };\n}\nfunction mergeAgent(\n globalValue?: PlanningAgentConfig,\n repositoryValue?: PlanningAgentConfig,\n): PlanningAgentConfig | undefined {\n return globalValue || repositoryValue ? { ...globalValue, ...repositoryValue } : undefined;\n}\nexport function mergeDoomConfigs(globalConfig: DoomConfig, repositoryConfig: DoomConfig): DoomConfig {\n if (repositoryConfig.voice?.autoCapture)\n throw new Error('Repository Doom config voice.autoCapture is global-only; configure it in ~/.pi/.doom/config.yaml');\n const globalPlanning = globalConfig.modes?.planning;\n const repositoryPlanning = repositoryConfig.modes?.planning;\n const planning =\n globalPlanning || repositoryPlanning\n ? {\n main: mergeAgent(globalPlanning?.main, repositoryPlanning?.main),\n subagents: mergeAgent(globalPlanning?.subagents, repositoryPlanning?.subagents),\n plansDirectory: repositoryPlanning?.plansDirectory ?? globalPlanning?.plansDirectory,\n }\n : undefined;\n const voice = mergeVoice(globalConfig.voice, repositoryConfig.voice);\n return {\n modes: planning ? { planning } : undefined,\n projectTrust: repositoryConfig.projectTrust,\n editor: globalConfig.editor,\n voice,\n // Per key, so a repository can pin one axis and inherit the rest.\n selection:\n globalConfig.selection || repositoryConfig.selection\n ? { ...globalConfig.selection, ...repositoryConfig.selection }\n : undefined,\n };\n}\nfunction mergeAdapter(\n globalValue?: VoiceAdapterConfig,\n repositoryValue?: VoiceAdapterConfig,\n): VoiceAdapterConfig | undefined {\n if (!globalValue && !repositoryValue) return undefined;\n return { ...globalValue, ...repositoryValue, model: repositoryValue?.model ?? globalValue!.model };\n}\nfunction mergeVoice(globalValue?: VoiceConfig, repositoryValue?: VoiceConfig): VoiceConfig | undefined {\n if (!globalValue && !repositoryValue) return undefined;\n return {\n ...globalValue,\n ...repositoryValue,\n recorder:\n globalValue?.recorder || repositoryValue?.recorder\n ? { ...globalValue?.recorder, ...repositoryValue?.recorder }\n : undefined,\n adapters:\n globalValue?.adapters || repositoryValue?.adapters\n ? {\n [WHISPER_CPP_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[WHISPER_CPP_ENGINE],\n repositoryValue?.adapters?.[WHISPER_CPP_ENGINE],\n ),\n [OPENAI_WHISPER_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[OPENAI_WHISPER_ENGINE],\n repositoryValue?.adapters?.[OPENAI_WHISPER_ENGINE],\n ),\n [MLX_WHISPER_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[MLX_WHISPER_ENGINE],\n repositoryValue?.adapters?.[MLX_WHISPER_ENGINE],\n ),\n }\n : undefined,\n ...(globalValue?.autoCapture ? { autoCapture: globalValue.autoCapture } : {}),\n };\n}\nexport function resolveVoiceConfig(config: VoiceConfig): ResolvedVoiceConfig {\n return {\n engine: config.engine ?? DEFAULT_VOICE_ENGINE,\n language: config.language ?? DEFAULT_VOICE_LANGUAGE,\n recorder: { ...config.recorder, device: config.recorder?.device ?? DEFAULT_RECORDER_DEVICE },\n adapters: config.adapters ?? {},\n ...(config.autoCapture\n ? {\n autoCapture: {\n ...config.autoCapture,\n startPhrases: [...(config.autoCapture.startPhrases ?? [])],\n stopPhrases: [...(config.autoCapture.stopPhrases ?? [])],\n // Unlike start and stop phrases, these default to a non-empty list: without\n // a send phrase a draft could be opened and never submitted.\n composeOpenPhrases: [...(config.autoCapture.composeOpenPhrases ?? DEFAULT_COMPOSE_OPEN_PHRASES)],\n composeSendPhrases: [...(config.autoCapture.composeSendPhrases ?? DEFAULT_COMPOSE_SEND_PHRASES)],\n composeCancelPhrases: [...(config.autoCapture.composeCancelPhrases ?? DEFAULT_COMPOSE_CANCEL_PHRASES)],\n utteranceIdleMs: config.autoCapture.utteranceIdleMs ?? DEFAULT_UTTERANCE_IDLE_MS,\n composeUtteranceIdleMs: config.autoCapture.composeUtteranceIdleMs ?? DEFAULT_COMPOSE_UTTERANCE_IDLE_MS,\n composeNudgeMs: config.autoCapture.composeNudgeMs ?? DEFAULT_COMPOSE_NUDGE_MS,\n transcriptionTimeoutMs: config.autoCapture.transcriptionTimeoutMs ?? DEFAULT_TRANSCRIPTION_TIMEOUT_MS,\n tts: { ...config.autoCapture.tts },\n },\n }\n : {}),\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configPolicy.d.cts","names":[],"sources":["../../src/services/configPolicy.ts"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"configPolicy.d.cts","names":[],"sources":["../../src/services/configPolicy.ts"],"mappings":";;;;;;;;;cAoGa;cACA;cAMA;iBA6OG,wBACd,gBACA,kBACA,oBACC;iBAyGa,gBAAgB,iBAAiB,mBAAmB;iBAiCpD,iBAAiB,cAAc,YAAY,kBAAkB,aAAa;iBA8D1E,mBAAmB,QAAQ,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configPolicy.d.mts","names":[],"sources":["../../src/services/configPolicy.ts"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"configPolicy.d.mts","names":[],"sources":["../../src/services/configPolicy.ts"],"mappings":";;;;;;;;;cAoGa;cACA;cAMA;iBA6OG,wBACd,gBACA,kBACA,oBACC;iBAyGa,gBAAgB,iBAAiB,mBAAmB;iBAiCpD,iBAAiB,cAAc,YAAY,kBAAkB,aAAa;iBA8D1E,mBAAmB,QAAQ,cAAc"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{parse as e}from"yaml";const t=`auto`,n=[`modes`,`projectTrust`,`editor`,`voice`,`selection`],r=[`majorMode`,`domains`,`profile`],i=[`planning`],a=[`main`,`subagents`,`plansDirectory`],o=[`model`,`thinking`],s=[`command`],c=[`engine`,`language`,`recorder`,`adapters`,`autoCapture`],l=[`binary`,`device`],u=`whisper-cpp`,d=`openai-whisper`,f=`mlx-whisper`,p=[u,d,f],m=[`binary`,`model`],h=[`path`,`id`],g=[`model`,`startPhrases`,`stopPhrases`,`utteranceIdleMs`,`transcriptionTimeoutMs`,`tts`],_=[`engine`,`voice`,`rate`],v=1500,y=1e4,b=1e3,x=12e4,S=/^[^/\s]+\/[^/\s]+(?:\/[^/\s]+)*$/u,C=/[\p{P}\p{S}]+/gu,w=[`off`,`minimal`,`low`,`medium`,`high`,`xhigh`,`max`],T=[t,u,d,f],E=[`macos-say`],D=new Set(w),O=new Set([`ask`,`always`,`never`]),k=new Set(T),A=new Set(E);function j(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function M(e,t,n,r){let i=Object.keys(e).filter(e=>!t.includes(e));if(i.length>0)throw Error(`Doom config at ${r} has unsupported ${n} field(s): ${i.join(`, `)}`)}function N(e,t,n){if(e!==void 0){if(typeof e!=`string`||e.trim().length===0)throw Error(`Doom config at ${n} requires ${t} to be a non-empty string`);return e.trim()}}function P(e){for(let t of e){let e=t.codePointAt(0);if(e!==void 0&&(e<=31||e===127))return!0}return!1}function F(e,t,n,r){let i=N(e,t,n);if(i){if(i.length>r)throw Error(`Doom config at ${n} requires ${t} to be at most ${r} characters`);if(P(i))throw Error(`Doom config at ${n} does not allow control characters in ${t}`);return i}}function I(e,t){let n=`voice.autoCapture.model`,r=N(e,n,t);if(!r||!S.test(r))throw Error(`Doom config at ${t} requires ${n} in provider/model-id form`);return r}function L(e){return e.normalize(`NFKC`).toLocaleLowerCase(`en-US`).replace(C,` `).replace(/\s+/gu,` `).trim()}function R(e,t,n){if(e===void 0)return;if(!Array.isArray(e)||e.some(e=>typeof e!=`string`))throw Error(`Doom config at ${n} requires ${t} to be an array of strings`);if(e.length>16)throw Error(`Doom config at ${n} requires ${t} to contain at most 16 phrases`);let r=e.map(e=>e.trim()),i=new Set;for(let e of r){if(e.length===0)throw Error(`Doom config at ${n} requires ${t} to contain non-empty strings`);if(e.length>64)throw Error(`Doom config at ${n} requires ${t} phrases to be at most 64 characters`);if(P(e))throw Error(`Doom config at ${n} does not allow control characters in ${t}`);let r=L(e);if(!r)throw Error(`Doom config at ${n} requires ${t} to contain words`);if(i.has(r))throw Error(`Doom config at ${n} does not allow normalized duplicates in ${t}`);i.add(r)}return r}function z(e,t){let n=`voice.autoCapture.tts`;if(!j(e))throw Error(`Doom config at ${t} requires ${n} to be an object`);if(M(e,_,n,t),typeof e.engine!=`string`||!A.has(e.engine))throw Error(`Doom config at ${t} requires ${n}.engine to be one of: ${[...A].join(`, `)}`);let r=F(e.voice,`${n}.voice`,t,64),i=e.rate;if(i!==void 0&&(typeof i!=`number`||!Number.isInteger(i)||i<80||i>500))throw Error(`Doom config at ${t} requires ${n}.rate to be an integer from 80 to 500`);return{engine:e.engine,...r?{voice:r}:{},...i===void 0?{}:{rate:i}}}function B(e,t){if(e===void 0)return;let n=`voice.autoCapture`;if(!j(e))throw Error(`Doom config at ${t} requires ${n} to be an object`);M(e,g,n,t);let r=R(e.startPhrases,`${n}.startPhrases`,t),i=R(e.stopPhrases,`${n}.stopPhrases`,t),a=e.utteranceIdleMs;if(a!==void 0&&(typeof a!=`number`||!Number.isInteger(a)||a<v||a>y))throw Error(`Doom config at ${t} requires ${n}.utteranceIdleMs to be an integer from ${v} to ${y}`);let o=e.transcriptionTimeoutMs;if(o!==void 0&&(typeof o!=`number`||!Number.isInteger(o)||o<b||o>x))throw Error(`Doom config at ${t} requires ${n}.transcriptionTimeoutMs to be an integer from ${b} to ${x}`);let s=new Set((r??[]).map(L));if((i??[]).some(e=>s.has(L(e))))throw Error(`Doom config at ${t} does not allow a phrase in both startPhrases and stopPhrases`);return{model:I(e.model,t),...r?{startPhrases:r}:{},...i?{stopPhrases:i}:{},...a===void 0?{}:{utteranceIdleMs:a},...o===void 0?{}:{transcriptionTimeoutMs:o},tts:z(e.tts,t)}}function V(e,t,n){if(e===void 0)return;if(!j(e))throw Error(`Doom config at ${n} requires ${t} to be an object`);M(e,o,t,n);let r=e.thinking;if(r!==void 0&&(typeof r!=`string`||!D.has(r)))throw Error(`Doom config at ${n} requires ${t}.thinking to be one of: ${[...D].join(`, `)}`);let i=N(e.model,`${t}.model`,n);return{...i?{model:i}:{},...r?{thinking:r}:{}}}function H(e,t,n=`modes.planning`){if(!j(e))throw Error(`Doom config at ${t} requires ${n} to be an object`);return M(e,a,n,t),{main:V(e.main,`${n}.main`,t),subagents:V(e.subagents,`${n}.subagents`,t),plansDirectory:N(e.plansDirectory,`${n}.plansDirectory`,t)}}function U(e,t,n,r){if(!j(e))throw Error(`Doom config at ${n} requires ${t} to be an object`);M(e,h,t,n);let i=N(e.path,`${t}.path`,n),a=N(e.id,`${t}.id`,n);if(!!i==!!a)throw Error(`Doom config at ${n} requires exactly one of ${t}.path or ${t}.id`);if(a&&!r)throw Error(`Doom config at ${n} does not support ${t}.id for whisper-cpp`);return{...i?{path:i}:{},...a?{id:a}:{}}}function W(e,t,n,r){if(e===void 0)return;if(!j(e))throw Error(`Doom config at ${n} requires ${t} to be an object`);M(e,m,t,n);let i=N(e.binary,`${t}.binary`,n);return{...i?{binary:i}:{},model:U(e.model,`${t}.model`,n,r)}}function G(e,t){if(e===void 0)return;if(!j(e))throw Error(`Doom config at ${t} requires voice to be an object`);M(e,c,`voice`,t);let n={};if(e.engine!==void 0){if(typeof e.engine!=`string`||!k.has(e.engine))throw Error(`Doom config at ${t} requires voice.engine to be one of: ${[...k].join(`, `)}`);n.engine=e.engine}let r=N(e.language,`voice.language`,t);if(r&&(n.language=r),e.recorder!==void 0){if(!j(e.recorder))throw Error(`Doom config at ${t} requires voice.recorder to be an object`);M(e.recorder,l,`voice.recorder`,t),n.recorder={};let r=N(e.recorder.binary,`voice.recorder.binary`,t),i=N(e.recorder.device,`voice.recorder.device`,t);r&&(n.recorder.binary=r),i&&(n.recorder.device=i)}if(e.adapters!==void 0){if(!j(e.adapters))throw Error(`Doom config at ${t} requires voice.adapters to be an object`);M(e.adapters,p,`voice.adapters`,t),n.adapters={[u]:W(e.adapters[u],`voice.adapters.${u}`,t,!1),[d]:W(e.adapters[d],`voice.adapters.${d}`,t,!0),[f]:W(e.adapters[f],`voice.adapters.${f}`,t,!0)}}let i=B(e.autoCapture,t);return i&&(n.autoCapture=i),n}function K(e,t){if(e===void 0)return;if(!j(e))throw Error(`Doom config at ${t} requires selection to be an object`);M(e,r,`selection`,t);let n=e.domains;if(n!==void 0&&(!Array.isArray(n)||n.some(e=>typeof e!=`string`)))throw Error(`Doom config at ${t} requires selection.domains to be an array of strings`);return{majorMode:N(e.majorMode,`selection.majorMode`,t),domains:n,profile:N(e.profile,`selection.profile`,t)}}function q(e,t){if(e!==void 0){if(!j(e))throw Error(`Doom config at ${t} requires editor to be an object`);return M(e,s,`editor`,t),{command:N(e.command,`editor.command`,t)}}}function J(t,r){let a;try{a=e(t)??{}}catch(e){throw Error(`Could not parse Doom config at ${r}`,{cause:e})}if(!j(a))throw Error(`Doom config at ${r} must be a YAML object`);M(a,n,`root`,r);let o;if(a.modes!==void 0){if(!j(a.modes))throw Error(`Doom config at ${r} requires modes to be an object`);M(a.modes,i,`modes`,r),o=a.modes.planning===void 0?{}:{planning:H(a.modes.planning,r)}}let s=a.projectTrust===void 0?`ask`:a.projectTrust;if(typeof s!=`string`||!O.has(s))throw Error(`Doom config at ${r} projectTrust must be ask, always, or never`);return{modes:o,projectTrust:s,editor:q(a.editor,r),voice:G(a.voice,r),selection:K(a.selection,r)}}function Y(e,t){return e||t?{...e,...t}:void 0}function X(e,t){if(t.voice?.autoCapture)throw Error(`Repository Doom config voice.autoCapture is global-only; configure it in ~/.pi/.doom/config.yaml`);let n=e.modes?.planning,r=t.modes?.planning,i=n||r?{main:Y(n?.main,r?.main),subagents:Y(n?.subagents,r?.subagents),plansDirectory:r?.plansDirectory??n?.plansDirectory}:void 0,a=Q(e.voice,t.voice);return{modes:i?{planning:i}:void 0,projectTrust:t.projectTrust,editor:e.editor,voice:a,selection:e.selection||t.selection?{...e.selection,...t.selection}:void 0}}function Z(e,t){if(!(!e&&!t))return{...e,...t,model:t?.model??e.model}}function Q(e,t){if(!(!e&&!t))return{...e,...t,recorder:e?.recorder||t?.recorder?{...e?.recorder,...t?.recorder}:void 0,adapters:e?.adapters||t?.adapters?{[u]:Z(e?.adapters?.[u],t?.adapters?.[u]),[d]:Z(e?.adapters?.[d],t?.adapters?.[d]),[f]:Z(e?.adapters?.[f],t?.adapters?.[f])}:void 0,...e?.autoCapture?{autoCapture:e.autoCapture}:{}}}function $(e){return{engine:e.engine??t,language:e.language??`auto`,recorder:{...e.recorder,device:e.recorder?.device??`none:default`},adapters:e.adapters??{},...e.autoCapture?{autoCapture:{...e.autoCapture,startPhrases:[...e.autoCapture.startPhrases??[]],stopPhrases:[...e.autoCapture.stopPhrases??[]],utteranceIdleMs:e.autoCapture.utteranceIdleMs??3e3,transcriptionTimeoutMs:e.autoCapture.transcriptionTimeoutMs??15e3,tts:{...e.autoCapture.tts}}}:{}}}export{w as DOOM_PLANNING_THINKING_LEVELS,T as DOOM_VOICE_ENGINES,E as DOOM_VOICE_TTS_ENGINES,X as mergeDoomConfigs,J as parseDoomConfig,H as parsePlanningModeConfig,$ as resolveVoiceConfig};
|
|
1
|
+
import{parse as e}from"yaml";const t=`auto`,n=[`modes`,`projectTrust`,`editor`,`voice`,`selection`],r=[`majorMode`,`domains`,`profile`],i=[`planning`],a=[`main`,`subagents`,`plansDirectory`],o=[`model`,`thinking`],s=[`command`],c=[`engine`,`language`,`recorder`,`adapters`,`autoCapture`],l=[`binary`,`device`],u=`whisper-cpp`,d=`openai-whisper`,f=`mlx-whisper`,p=[u,d,f],m=[`binary`,`model`],h=[`path`,`id`],g=[`model`,`startPhrases`,`stopPhrases`,`composeOpenPhrases`,`composeSendPhrases`,`composeCancelPhrases`,`utteranceIdleMs`,`composeUtteranceIdleMs`,`composeNudgeMs`,`transcriptionTimeoutMs`,`tts`],_=[`engine`,`voice`,`rate`],v=[`hey doom`,`doom prompt`],y=[`that's it`,`doom send`],b=[`doom cancel`,`scratch that`],x=/^[^/\s]+\/[^/\s]+(?:\/[^/\s]+)*$/u,S=/[\p{P}\p{S}]+/gu,C=[`off`,`minimal`,`low`,`medium`,`high`,`xhigh`,`max`],w=[t,u,d,f],T=[`macos-say`],E=new Set(C),D=new Set([`ask`,`always`,`never`]),O=new Set(w),k=new Set(T);function A(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function j(e,t,n,r){let i=Object.keys(e).filter(e=>!t.includes(e));if(i.length>0)throw Error(`Doom config at ${r} has unsupported ${n} field(s): ${i.join(`, `)}`)}function M(e,t,n){if(e!==void 0){if(typeof e!=`string`||e.trim().length===0)throw Error(`Doom config at ${n} requires ${t} to be a non-empty string`);return e.trim()}}function N(e){for(let t of e){let e=t.codePointAt(0);if(e!==void 0&&(e<=31||e===127))return!0}return!1}function P(e,t,n,r){let i=M(e,t,n);if(i){if(i.length>r)throw Error(`Doom config at ${n} requires ${t} to be at most ${r} characters`);if(N(i))throw Error(`Doom config at ${n} does not allow control characters in ${t}`);return i}}function F(e,t){let n=`voice.autoCapture.model`,r=M(e,n,t);if(!r||!x.test(r))throw Error(`Doom config at ${t} requires ${n} in provider/model-id form`);return r}function I(e){return e.normalize(`NFKC`).toLocaleLowerCase(`en-US`).replace(S,` `).replace(/\s+/gu,` `).trim()}function L(e,t,n){if(e===void 0)return;if(!Array.isArray(e)||e.some(e=>typeof e!=`string`))throw Error(`Doom config at ${n} requires ${t} to be an array of strings`);if(e.length>16)throw Error(`Doom config at ${n} requires ${t} to contain at most 16 phrases`);let r=e.map(e=>e.trim()),i=new Set;for(let e of r){if(e.length===0)throw Error(`Doom config at ${n} requires ${t} to contain non-empty strings`);if(e.length>64)throw Error(`Doom config at ${n} requires ${t} phrases to be at most 64 characters`);if(N(e))throw Error(`Doom config at ${n} does not allow control characters in ${t}`);let r=I(e);if(!r)throw Error(`Doom config at ${n} requires ${t} to contain words`);if(i.has(r))throw Error(`Doom config at ${n} does not allow normalized duplicates in ${t}`);i.add(r)}return r}function R(e,t,n,r,i){if(e!==void 0){if(typeof e!=`number`||!Number.isInteger(e)||e<r||e>i)throw Error(`Doom config at ${n} requires ${t} to be an integer from ${r} to ${i}`);return e}}function z(e,t,n,r,i){let a=new Set((e??[]).map(I));if((t??[]).some(e=>a.has(I(e))))throw Error(`Doom config at ${i} does not allow a phrase in both ${n} and ${r}`)}function B(e,t){let n=`voice.autoCapture.tts`;if(!A(e))throw Error(`Doom config at ${t} requires ${n} to be an object`);if(j(e,_,n,t),typeof e.engine!=`string`||!k.has(e.engine))throw Error(`Doom config at ${t} requires ${n}.engine to be one of: ${[...k].join(`, `)}`);let r=P(e.voice,`${n}.voice`,t,64),i=e.rate;if(i!==void 0&&(typeof i!=`number`||!Number.isInteger(i)||i<80||i>500))throw Error(`Doom config at ${t} requires ${n}.rate to be an integer from 80 to 500`);return{engine:e.engine,...r?{voice:r}:{},...i===void 0?{}:{rate:i}}}function V(e,t){if(e===void 0)return;let n=`voice.autoCapture`;if(!A(e))throw Error(`Doom config at ${t} requires ${n} to be an object`);j(e,g,n,t);let r=L(e.startPhrases,`${n}.startPhrases`,t),i=L(e.stopPhrases,`${n}.stopPhrases`,t),a=L(e.composeOpenPhrases,`${n}.composeOpenPhrases`,t),o=L(e.composeSendPhrases,`${n}.composeSendPhrases`,t),s=L(e.composeCancelPhrases,`${n}.composeCancelPhrases`,t),c=R(e.utteranceIdleMs,`${n}.utteranceIdleMs`,t,1500,1e4),l=R(e.composeUtteranceIdleMs,`${n}.composeUtteranceIdleMs`,t,800,3e3),u=e.composeNudgeMs===0?0:R(e.composeNudgeMs,`${n}.composeNudgeMs`,t,5e3,6e4),d=R(e.transcriptionTimeoutMs,`${n}.transcriptionTimeoutMs`,t,1e3,12e4);return z(r,i,`startPhrases`,`stopPhrases`,t),z(o,s,`composeSendPhrases`,`composeCancelPhrases`,t),z(a,o,`composeOpenPhrases`,`composeSendPhrases`,t),z(a,s,`composeOpenPhrases`,`composeCancelPhrases`,t),z(i,o,`stopPhrases`,`composeSendPhrases`,t),z(i,s,`stopPhrases`,`composeCancelPhrases`,t),{model:F(e.model,t),...r?{startPhrases:r}:{},...i?{stopPhrases:i}:{},...a?{composeOpenPhrases:a}:{},...o?{composeSendPhrases:o}:{},...s?{composeCancelPhrases:s}:{},...c===void 0?{}:{utteranceIdleMs:c},...l===void 0?{}:{composeUtteranceIdleMs:l},...u===void 0?{}:{composeNudgeMs:u},...d===void 0?{}:{transcriptionTimeoutMs:d},tts:B(e.tts,t)}}function H(e,t,n){if(e===void 0)return;if(!A(e))throw Error(`Doom config at ${n} requires ${t} to be an object`);j(e,o,t,n);let r=e.thinking;if(r!==void 0&&(typeof r!=`string`||!E.has(r)))throw Error(`Doom config at ${n} requires ${t}.thinking to be one of: ${[...E].join(`, `)}`);let i=M(e.model,`${t}.model`,n);return{...i?{model:i}:{},...r?{thinking:r}:{}}}function U(e,t,n=`modes.planning`){if(!A(e))throw Error(`Doom config at ${t} requires ${n} to be an object`);return j(e,a,n,t),{main:H(e.main,`${n}.main`,t),subagents:H(e.subagents,`${n}.subagents`,t),plansDirectory:M(e.plansDirectory,`${n}.plansDirectory`,t)}}function W(e,t,n,r){if(!A(e))throw Error(`Doom config at ${n} requires ${t} to be an object`);j(e,h,t,n);let i=M(e.path,`${t}.path`,n),a=M(e.id,`${t}.id`,n);if(!!i==!!a)throw Error(`Doom config at ${n} requires exactly one of ${t}.path or ${t}.id`);if(a&&!r)throw Error(`Doom config at ${n} does not support ${t}.id for whisper-cpp`);return{...i?{path:i}:{},...a?{id:a}:{}}}function G(e,t,n,r){if(e===void 0)return;if(!A(e))throw Error(`Doom config at ${n} requires ${t} to be an object`);j(e,m,t,n);let i=M(e.binary,`${t}.binary`,n);return{...i?{binary:i}:{},model:W(e.model,`${t}.model`,n,r)}}function K(e,t){if(e===void 0)return;if(!A(e))throw Error(`Doom config at ${t} requires voice to be an object`);j(e,c,`voice`,t);let n={};if(e.engine!==void 0){if(typeof e.engine!=`string`||!O.has(e.engine))throw Error(`Doom config at ${t} requires voice.engine to be one of: ${[...O].join(`, `)}`);n.engine=e.engine}let r=M(e.language,`voice.language`,t);if(r&&(n.language=r),e.recorder!==void 0){if(!A(e.recorder))throw Error(`Doom config at ${t} requires voice.recorder to be an object`);j(e.recorder,l,`voice.recorder`,t),n.recorder={};let r=M(e.recorder.binary,`voice.recorder.binary`,t),i=M(e.recorder.device,`voice.recorder.device`,t);r&&(n.recorder.binary=r),i&&(n.recorder.device=i)}if(e.adapters!==void 0){if(!A(e.adapters))throw Error(`Doom config at ${t} requires voice.adapters to be an object`);j(e.adapters,p,`voice.adapters`,t),n.adapters={[u]:G(e.adapters[u],`voice.adapters.${u}`,t,!1),[d]:G(e.adapters[d],`voice.adapters.${d}`,t,!0),[f]:G(e.adapters[f],`voice.adapters.${f}`,t,!0)}}let i=V(e.autoCapture,t);return i&&(n.autoCapture=i),n}function q(e,t){if(e===void 0)return;if(!A(e))throw Error(`Doom config at ${t} requires selection to be an object`);j(e,r,`selection`,t);let n=e.domains;if(n!==void 0&&(!Array.isArray(n)||n.some(e=>typeof e!=`string`)))throw Error(`Doom config at ${t} requires selection.domains to be an array of strings`);return{majorMode:M(e.majorMode,`selection.majorMode`,t),domains:n,profile:M(e.profile,`selection.profile`,t)}}function J(e,t){if(e!==void 0){if(!A(e))throw Error(`Doom config at ${t} requires editor to be an object`);return j(e,s,`editor`,t),{command:M(e.command,`editor.command`,t)}}}function Y(t,r){let a;try{a=e(t)??{}}catch(e){throw Error(`Could not parse Doom config at ${r}`,{cause:e})}if(!A(a))throw Error(`Doom config at ${r} must be a YAML object`);j(a,n,`root`,r);let o;if(a.modes!==void 0){if(!A(a.modes))throw Error(`Doom config at ${r} requires modes to be an object`);j(a.modes,i,`modes`,r),o=a.modes.planning===void 0?{}:{planning:U(a.modes.planning,r)}}let s=a.projectTrust===void 0?`ask`:a.projectTrust;if(typeof s!=`string`||!D.has(s))throw Error(`Doom config at ${r} projectTrust must be ask, always, or never`);return{modes:o,projectTrust:s,editor:J(a.editor,r),voice:K(a.voice,r),selection:q(a.selection,r)}}function X(e,t){return e||t?{...e,...t}:void 0}function Z(e,t){if(t.voice?.autoCapture)throw Error(`Repository Doom config voice.autoCapture is global-only; configure it in ~/.pi/.doom/config.yaml`);let n=e.modes?.planning,r=t.modes?.planning,i=n||r?{main:X(n?.main,r?.main),subagents:X(n?.subagents,r?.subagents),plansDirectory:r?.plansDirectory??n?.plansDirectory}:void 0,a=$(e.voice,t.voice);return{modes:i?{planning:i}:void 0,projectTrust:t.projectTrust,editor:e.editor,voice:a,selection:e.selection||t.selection?{...e.selection,...t.selection}:void 0}}function Q(e,t){if(!(!e&&!t))return{...e,...t,model:t?.model??e.model}}function $(e,t){if(!(!e&&!t))return{...e,...t,recorder:e?.recorder||t?.recorder?{...e?.recorder,...t?.recorder}:void 0,adapters:e?.adapters||t?.adapters?{[u]:Q(e?.adapters?.[u],t?.adapters?.[u]),[d]:Q(e?.adapters?.[d],t?.adapters?.[d]),[f]:Q(e?.adapters?.[f],t?.adapters?.[f])}:void 0,...e?.autoCapture?{autoCapture:e.autoCapture}:{}}}function ee(e){return{engine:e.engine??t,language:e.language??`auto`,recorder:{...e.recorder,device:e.recorder?.device??`none:default`},adapters:e.adapters??{},...e.autoCapture?{autoCapture:{...e.autoCapture,startPhrases:[...e.autoCapture.startPhrases??[]],stopPhrases:[...e.autoCapture.stopPhrases??[]],composeOpenPhrases:[...e.autoCapture.composeOpenPhrases??v],composeSendPhrases:[...e.autoCapture.composeSendPhrases??y],composeCancelPhrases:[...e.autoCapture.composeCancelPhrases??b],utteranceIdleMs:e.autoCapture.utteranceIdleMs??3e3,composeUtteranceIdleMs:e.autoCapture.composeUtteranceIdleMs??1200,composeNudgeMs:e.autoCapture.composeNudgeMs??1e4,transcriptionTimeoutMs:e.autoCapture.transcriptionTimeoutMs??15e3,tts:{...e.autoCapture.tts}}}:{}}}export{C as DOOM_PLANNING_THINKING_LEVELS,w as DOOM_VOICE_ENGINES,T as DOOM_VOICE_TTS_ENGINES,Z as mergeDoomConfigs,Y as parseDoomConfig,U as parsePlanningModeConfig,ee as resolveVoiceConfig};
|
|
2
2
|
//# sourceMappingURL=configPolicy.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configPolicy.mjs","names":["parseYaml"],"sources":["../../src/services/configPolicy.ts"],"sourcesContent":["import { parse as parseYaml } from 'yaml';\nimport type {\n DoomConfig,\n DoomSelectionConfig,\n EditorConfig,\n PlanningAgentConfig,\n PlanningModeConfig,\n PlanningThinkingLevel,\n ProjectTrust,\n ResolvedVoiceConfig,\n VoiceAdapterConfig,\n VoiceAutoCaptureConfig,\n VoiceConfig,\n VoiceEngine,\n VoiceModelConfig,\n VoiceTtsConfig,\n VoiceTtsEngine,\n} from '../types/config.ts';\n\nconst DEFAULT_PROJECT_TRUST: ProjectTrust = 'ask';\nconst DEFAULT_VOICE_ENGINE: VoiceEngine = 'auto';\nconst DEFAULT_VOICE_LANGUAGE = 'auto';\nconst DEFAULT_RECORDER_DEVICE = 'none:default';\nconst ROOT_KEYS = ['modes', 'projectTrust', 'editor', 'voice', 'selection'] as const;\nconst SELECTION_KEYS = ['majorMode', 'domains', 'profile'] as const;\nconst MODE_KEYS = ['planning'] as const;\nconst PLANNING_KEYS = ['main', 'subagents', 'plansDirectory'] as const;\nconst AGENT_KEYS = ['model', 'thinking'] as const;\nconst EDITOR_KEYS = ['command'] as const;\nconst VOICE_KEYS = ['engine', 'language', 'recorder', 'adapters', 'autoCapture'] as const;\nconst RECORDER_KEYS = ['binary', 'device'] as const;\nconst WHISPER_CPP_ENGINE = 'whisper-cpp' as const;\nconst OPENAI_WHISPER_ENGINE = 'openai-whisper' as const;\nconst MLX_WHISPER_ENGINE = 'mlx-whisper' as const;\nconst MACOS_SAY_TTS_ENGINE = 'macos-say' as const;\nconst ADAPTERS_KEYS = [WHISPER_CPP_ENGINE, OPENAI_WHISPER_ENGINE, MLX_WHISPER_ENGINE] as const;\nconst ADAPTER_KEYS = ['binary', 'model'] as const;\nconst MODEL_KEYS = ['path', 'id'] as const;\nconst AUTO_CAPTURE_KEYS = [\n 'model',\n 'startPhrases',\n 'stopPhrases',\n 'utteranceIdleMs',\n 'transcriptionTimeoutMs',\n 'tts',\n] as const;\nconst TTS_KEYS = ['engine', 'voice', 'rate'] as const;\nconst DEFAULT_UTTERANCE_IDLE_MS = 3_000;\nconst MIN_UTTERANCE_IDLE_MS = 1_500;\nconst MAX_UTTERANCE_IDLE_MS = 10_000;\nconst DEFAULT_TRANSCRIPTION_TIMEOUT_MS = 15_000;\nconst MIN_TRANSCRIPTION_TIMEOUT_MS = 1_000;\nconst MAX_TRANSCRIPTION_TIMEOUT_MS = 120_000;\nconst MAX_CONTROL_PHRASES = 16;\nconst MAX_CONTROL_PHRASE_LENGTH = 64;\nconst MAX_TTS_VOICE_LENGTH = 64;\nconst MIN_TTS_RATE = 80;\nconst MAX_TTS_RATE = 500;\nconst MODEL_REFERENCE_PATTERN = /^[^/\\s]+\\/[^/\\s]+(?:\\/[^/\\s]+)*$/u;\nconst PHRASE_PUNCTUATION_PATTERN = /[\\p{P}\\p{S}]+/gu;\n/**\n * The accepted values, in the order a picker should offer them.\n *\n * Exported because the config UI has to offer exactly what the parser accepts,\n * and a second hand-written list in the contributing package would drift. The\n * validating Sets below are built from these for the same reason.\n */\nexport const DOOM_PLANNING_THINKING_LEVELS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'] as const;\nexport const DOOM_VOICE_ENGINES = [\n DEFAULT_VOICE_ENGINE,\n WHISPER_CPP_ENGINE,\n OPENAI_WHISPER_ENGINE,\n MLX_WHISPER_ENGINE,\n] as const;\nexport const DOOM_VOICE_TTS_ENGINES = [MACOS_SAY_TTS_ENGINE] as const;\n\nconst THINKING_VALUES = new Set<PlanningThinkingLevel>(DOOM_PLANNING_THINKING_LEVELS);\nconst TRUST_VALUES = new Set<ProjectTrust>(['ask', 'always', 'never']);\nconst ENGINE_VALUES = new Set<VoiceEngine>(DOOM_VOICE_ENGINES);\nconst TTS_ENGINE_VALUES = new Set<VoiceTtsEngine>(DOOM_VOICE_TTS_ENGINES);\ntype ConfigObject = Record<string, unknown>;\n\nfunction isObject(value: unknown): value is ConfigObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\nfunction assertKeys(value: ConfigObject, allowed: readonly string[], location: string, filePath: string): void {\n const unknown = Object.keys(value).filter((key) => !allowed.includes(key));\n if (unknown.length > 0)\n throw new Error(`Doom config at ${filePath} has unsupported ${location} field(s): ${unknown.join(', ')}`);\n}\nfunction optionalString(value: unknown, location: string, filePath: string): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== 'string' || value.trim().length === 0)\n throw new Error(`Doom config at ${filePath} requires ${location} to be a non-empty string`);\n return value.trim();\n}\nfunction hasControlCharacters(value: string): boolean {\n for (const character of value) {\n const codePoint = character.codePointAt(0);\n if (codePoint !== undefined && (codePoint <= 0x1f || codePoint === 0x7f)) return true;\n }\n return false;\n}\nfunction boundedOptionalString(\n value: unknown,\n location: string,\n filePath: string,\n maximumLength: number,\n): string | undefined {\n const text = optionalString(value, location, filePath);\n if (!text) return undefined;\n if (text.length > maximumLength)\n throw new Error(`Doom config at ${filePath} requires ${location} to be at most ${maximumLength} characters`);\n if (hasControlCharacters(text))\n throw new Error(`Doom config at ${filePath} does not allow control characters in ${location}`);\n return text;\n}\nfunction parseModelReference(value: unknown, filePath: string): string {\n const location = 'voice.autoCapture.model';\n const model = optionalString(value, location, filePath);\n if (!model || !MODEL_REFERENCE_PATTERN.test(model))\n throw new Error(`Doom config at ${filePath} requires ${location} in provider/model-id form`);\n return model;\n}\nfunction normalizeControlPhrase(value: string): string {\n return value\n .normalize('NFKC')\n .toLocaleLowerCase('en-US')\n .replace(PHRASE_PUNCTUATION_PATTERN, ' ')\n .replace(/\\s+/gu, ' ')\n .trim();\n}\nfunction parseControlPhrases(value: unknown, location: string, filePath: string): string[] | undefined {\n if (value === undefined) return undefined;\n if (!Array.isArray(value) || value.some((phrase) => typeof phrase !== 'string'))\n throw new Error(`Doom config at ${filePath} requires ${location} to be an array of strings`);\n if (value.length > MAX_CONTROL_PHRASES)\n throw new Error(\n `Doom config at ${filePath} requires ${location} to contain at most ${MAX_CONTROL_PHRASES} phrases`,\n );\n const phrases = value.map((phrase) => phrase.trim());\n const normalized = new Set<string>();\n for (const phrase of phrases) {\n if (phrase.length === 0)\n throw new Error(`Doom config at ${filePath} requires ${location} to contain non-empty strings`);\n if (phrase.length > MAX_CONTROL_PHRASE_LENGTH)\n throw new Error(\n `Doom config at ${filePath} requires ${location} phrases to be at most ${MAX_CONTROL_PHRASE_LENGTH} characters`,\n );\n if (hasControlCharacters(phrase))\n throw new Error(`Doom config at ${filePath} does not allow control characters in ${location}`);\n const key = normalizeControlPhrase(phrase);\n if (!key) throw new Error(`Doom config at ${filePath} requires ${location} to contain words`);\n if (normalized.has(key))\n throw new Error(`Doom config at ${filePath} does not allow normalized duplicates in ${location}`);\n normalized.add(key);\n }\n return phrases;\n}\nfunction parseTtsConfig(value: unknown, filePath: string): VoiceTtsConfig {\n const location = 'voice.autoCapture.tts';\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, TTS_KEYS, location, filePath);\n if (typeof value.engine !== 'string' || !TTS_ENGINE_VALUES.has(value.engine as VoiceTtsEngine))\n throw new Error(\n `Doom config at ${filePath} requires ${location}.engine to be one of: ${[...TTS_ENGINE_VALUES].join(', ')}`,\n );\n const voice = boundedOptionalString(value.voice, `${location}.voice`, filePath, MAX_TTS_VOICE_LENGTH);\n const rate = value.rate;\n if (\n rate !== undefined &&\n (typeof rate !== 'number' || !Number.isInteger(rate) || rate < MIN_TTS_RATE || rate > MAX_TTS_RATE)\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.rate to be an integer from ${MIN_TTS_RATE} to ${MAX_TTS_RATE}`,\n );\n }\n return {\n engine: value.engine as VoiceTtsEngine,\n ...(voice ? { voice } : {}),\n ...(rate === undefined ? {} : { rate }),\n };\n}\nfunction parseAutoCapture(value: unknown, filePath: string): VoiceAutoCaptureConfig | undefined {\n if (value === undefined) return undefined;\n const location = 'voice.autoCapture';\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, AUTO_CAPTURE_KEYS, location, filePath);\n const startPhrases = parseControlPhrases(value.startPhrases, `${location}.startPhrases`, filePath);\n const stopPhrases = parseControlPhrases(value.stopPhrases, `${location}.stopPhrases`, filePath);\n const utteranceIdleMs = value.utteranceIdleMs;\n if (\n utteranceIdleMs !== undefined &&\n (typeof utteranceIdleMs !== 'number' ||\n !Number.isInteger(utteranceIdleMs) ||\n utteranceIdleMs < MIN_UTTERANCE_IDLE_MS ||\n utteranceIdleMs > MAX_UTTERANCE_IDLE_MS)\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.utteranceIdleMs to be an integer from ${MIN_UTTERANCE_IDLE_MS} to ${MAX_UTTERANCE_IDLE_MS}`,\n );\n }\n const transcriptionTimeoutMs = value.transcriptionTimeoutMs;\n if (\n transcriptionTimeoutMs !== undefined &&\n (typeof transcriptionTimeoutMs !== 'number' ||\n !Number.isInteger(transcriptionTimeoutMs) ||\n transcriptionTimeoutMs < MIN_TRANSCRIPTION_TIMEOUT_MS ||\n transcriptionTimeoutMs > MAX_TRANSCRIPTION_TIMEOUT_MS)\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.transcriptionTimeoutMs to be an integer from ${MIN_TRANSCRIPTION_TIMEOUT_MS} to ${MAX_TRANSCRIPTION_TIMEOUT_MS}`,\n );\n }\n const startKeys = new Set((startPhrases ?? []).map(normalizeControlPhrase));\n if ((stopPhrases ?? []).some((phrase) => startKeys.has(normalizeControlPhrase(phrase))))\n throw new Error(`Doom config at ${filePath} does not allow a phrase in both startPhrases and stopPhrases`);\n return {\n model: parseModelReference(value.model, filePath),\n ...(startPhrases ? { startPhrases } : {}),\n ...(stopPhrases ? { stopPhrases } : {}),\n ...(utteranceIdleMs === undefined ? {} : { utteranceIdleMs }),\n ...(transcriptionTimeoutMs === undefined ? {} : { transcriptionTimeoutMs }),\n tts: parseTtsConfig(value.tts, filePath),\n };\n}\nfunction parseAgent(value: unknown, location: string, filePath: string): PlanningAgentConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, AGENT_KEYS, location, filePath);\n const thinking = value.thinking;\n if (\n thinking !== undefined &&\n (typeof thinking !== 'string' || !THINKING_VALUES.has(thinking as PlanningThinkingLevel))\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.thinking to be one of: ${[...THINKING_VALUES].join(', ')}`,\n );\n }\n const model = optionalString(value.model, `${location}.model`, filePath);\n return { ...(model ? { model } : {}), ...(thinking ? { thinking: thinking as PlanningThinkingLevel } : {}) };\n}\nexport function parsePlanningModeConfig(\n value: unknown,\n filePath: string,\n location = 'modes.planning',\n): PlanningModeConfig {\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, PLANNING_KEYS, location, filePath);\n return {\n main: parseAgent(value.main, `${location}.main`, filePath),\n subagents: parseAgent(value.subagents, `${location}.subagents`, filePath),\n plansDirectory: optionalString(value.plansDirectory, `${location}.plansDirectory`, filePath),\n };\n}\nfunction parseModel(value: unknown, location: string, filePath: string, allowId: boolean): VoiceModelConfig {\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, MODEL_KEYS, location, filePath);\n const modelPath = optionalString(value.path, `${location}.path`, filePath);\n const id = optionalString(value.id, `${location}.id`, filePath);\n if (Boolean(modelPath) === Boolean(id))\n throw new Error(`Doom config at ${filePath} requires exactly one of ${location}.path or ${location}.id`);\n if (id && !allowId) throw new Error(`Doom config at ${filePath} does not support ${location}.id for whisper-cpp`);\n return { ...(modelPath ? { path: modelPath } : {}), ...(id ? { id } : {}) };\n}\nfunction parseAdapter(\n value: unknown,\n location: string,\n filePath: string,\n allowId: boolean,\n): VoiceAdapterConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, ADAPTER_KEYS, location, filePath);\n const binary = optionalString(value.binary, `${location}.binary`, filePath);\n return { ...(binary ? { binary } : {}), model: parseModel(value.model, `${location}.model`, filePath, allowId) };\n}\nfunction parseVoice(value: unknown, filePath: string): VoiceConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires voice to be an object`);\n assertKeys(value, VOICE_KEYS, 'voice', filePath);\n const result: VoiceConfig = {};\n if (value.engine !== undefined) {\n if (typeof value.engine !== 'string' || !ENGINE_VALUES.has(value.engine as VoiceEngine))\n throw new Error(\n `Doom config at ${filePath} requires voice.engine to be one of: ${[...ENGINE_VALUES].join(', ')}`,\n );\n result.engine = value.engine as VoiceEngine;\n }\n const language = optionalString(value.language, 'voice.language', filePath);\n if (language) result.language = language;\n if (value.recorder !== undefined) {\n if (!isObject(value.recorder))\n throw new Error(`Doom config at ${filePath} requires voice.recorder to be an object`);\n assertKeys(value.recorder, RECORDER_KEYS, 'voice.recorder', filePath);\n result.recorder = {};\n const binary = optionalString(value.recorder.binary, 'voice.recorder.binary', filePath);\n const device = optionalString(value.recorder.device, 'voice.recorder.device', filePath);\n if (binary) result.recorder.binary = binary;\n if (device) result.recorder.device = device;\n }\n if (value.adapters !== undefined) {\n if (!isObject(value.adapters))\n throw new Error(`Doom config at ${filePath} requires voice.adapters to be an object`);\n assertKeys(value.adapters, ADAPTERS_KEYS, 'voice.adapters', filePath);\n result.adapters = {\n [WHISPER_CPP_ENGINE]: parseAdapter(\n value.adapters[WHISPER_CPP_ENGINE],\n `voice.adapters.${WHISPER_CPP_ENGINE}`,\n filePath,\n false,\n ),\n [OPENAI_WHISPER_ENGINE]: parseAdapter(\n value.adapters[OPENAI_WHISPER_ENGINE],\n `voice.adapters.${OPENAI_WHISPER_ENGINE}`,\n filePath,\n true,\n ),\n [MLX_WHISPER_ENGINE]: parseAdapter(\n value.adapters[MLX_WHISPER_ENGINE],\n `voice.adapters.${MLX_WHISPER_ENGINE}`,\n filePath,\n true,\n ),\n };\n }\n const autoCapture = parseAutoCapture(value.autoCapture, filePath);\n if (autoCapture) result.autoCapture = autoCapture;\n return result;\n}\nfunction parseSelection(value: unknown, filePath: string): DoomSelectionConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires selection to be an object`);\n assertKeys(value, SELECTION_KEYS, 'selection', filePath);\n const domains = value.domains;\n if (domains !== undefined && (!Array.isArray(domains) || domains.some((name) => typeof name !== 'string'))) {\n throw new Error(`Doom config at ${filePath} requires selection.domains to be an array of strings`);\n }\n return {\n majorMode: optionalString(value.majorMode, 'selection.majorMode', filePath),\n // An empty list is meaningful: it selects no content domains at all.\n domains: domains as string[] | undefined,\n profile: optionalString(value.profile, 'selection.profile', filePath),\n };\n}\nfunction parseEditor(value: unknown, filePath: string): EditorConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires editor to be an object`);\n assertKeys(value, EDITOR_KEYS, 'editor', filePath);\n return { command: optionalString(value.command, 'editor.command', filePath) };\n}\nexport function parseDoomConfig(content: string, filePath: string): DoomConfig {\n let parsed: unknown;\n try {\n parsed = parseYaml(content) ?? {};\n } catch (error) {\n throw new Error(`Could not parse Doom config at ${filePath}`, { cause: error });\n }\n if (!isObject(parsed)) throw new Error(`Doom config at ${filePath} must be a YAML object`);\n assertKeys(parsed, ROOT_KEYS, 'root', filePath);\n let modes: DoomConfig['modes'];\n if (parsed.modes !== undefined) {\n if (!isObject(parsed.modes)) throw new Error(`Doom config at ${filePath} requires modes to be an object`);\n assertKeys(parsed.modes, MODE_KEYS, 'modes', filePath);\n modes =\n parsed.modes.planning === undefined ? {} : { planning: parsePlanningModeConfig(parsed.modes.planning, filePath) };\n }\n const trust = parsed.projectTrust === undefined ? DEFAULT_PROJECT_TRUST : parsed.projectTrust;\n if (typeof trust !== 'string' || !TRUST_VALUES.has(trust as ProjectTrust))\n throw new Error(`Doom config at ${filePath} projectTrust must be ask, always, or never`);\n return {\n modes,\n projectTrust: trust as ProjectTrust,\n editor: parseEditor(parsed.editor, filePath),\n voice: parseVoice(parsed.voice, filePath),\n selection: parseSelection(parsed.selection, filePath),\n };\n}\nfunction mergeAgent(\n globalValue?: PlanningAgentConfig,\n repositoryValue?: PlanningAgentConfig,\n): PlanningAgentConfig | undefined {\n return globalValue || repositoryValue ? { ...globalValue, ...repositoryValue } : undefined;\n}\nexport function mergeDoomConfigs(globalConfig: DoomConfig, repositoryConfig: DoomConfig): DoomConfig {\n if (repositoryConfig.voice?.autoCapture)\n throw new Error('Repository Doom config voice.autoCapture is global-only; configure it in ~/.pi/.doom/config.yaml');\n const globalPlanning = globalConfig.modes?.planning;\n const repositoryPlanning = repositoryConfig.modes?.planning;\n const planning =\n globalPlanning || repositoryPlanning\n ? {\n main: mergeAgent(globalPlanning?.main, repositoryPlanning?.main),\n subagents: mergeAgent(globalPlanning?.subagents, repositoryPlanning?.subagents),\n plansDirectory: repositoryPlanning?.plansDirectory ?? globalPlanning?.plansDirectory,\n }\n : undefined;\n const voice = mergeVoice(globalConfig.voice, repositoryConfig.voice);\n return {\n modes: planning ? { planning } : undefined,\n projectTrust: repositoryConfig.projectTrust,\n editor: globalConfig.editor,\n voice,\n // Per key, so a repository can pin one axis and inherit the rest.\n selection:\n globalConfig.selection || repositoryConfig.selection\n ? { ...globalConfig.selection, ...repositoryConfig.selection }\n : undefined,\n };\n}\nfunction mergeAdapter(\n globalValue?: VoiceAdapterConfig,\n repositoryValue?: VoiceAdapterConfig,\n): VoiceAdapterConfig | undefined {\n if (!globalValue && !repositoryValue) return undefined;\n return { ...globalValue, ...repositoryValue, model: repositoryValue?.model ?? globalValue!.model };\n}\nfunction mergeVoice(globalValue?: VoiceConfig, repositoryValue?: VoiceConfig): VoiceConfig | undefined {\n if (!globalValue && !repositoryValue) return undefined;\n return {\n ...globalValue,\n ...repositoryValue,\n recorder:\n globalValue?.recorder || repositoryValue?.recorder\n ? { ...globalValue?.recorder, ...repositoryValue?.recorder }\n : undefined,\n adapters:\n globalValue?.adapters || repositoryValue?.adapters\n ? {\n [WHISPER_CPP_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[WHISPER_CPP_ENGINE],\n repositoryValue?.adapters?.[WHISPER_CPP_ENGINE],\n ),\n [OPENAI_WHISPER_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[OPENAI_WHISPER_ENGINE],\n repositoryValue?.adapters?.[OPENAI_WHISPER_ENGINE],\n ),\n [MLX_WHISPER_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[MLX_WHISPER_ENGINE],\n repositoryValue?.adapters?.[MLX_WHISPER_ENGINE],\n ),\n }\n : undefined,\n ...(globalValue?.autoCapture ? { autoCapture: globalValue.autoCapture } : {}),\n };\n}\nexport function resolveVoiceConfig(config: VoiceConfig): ResolvedVoiceConfig {\n return {\n engine: config.engine ?? DEFAULT_VOICE_ENGINE,\n language: config.language ?? DEFAULT_VOICE_LANGUAGE,\n recorder: { ...config.recorder, device: config.recorder?.device ?? DEFAULT_RECORDER_DEVICE },\n adapters: config.adapters ?? {},\n ...(config.autoCapture\n ? {\n autoCapture: {\n ...config.autoCapture,\n startPhrases: [...(config.autoCapture.startPhrases ?? [])],\n stopPhrases: [...(config.autoCapture.stopPhrases ?? [])],\n utteranceIdleMs: config.autoCapture.utteranceIdleMs ?? DEFAULT_UTTERANCE_IDLE_MS,\n transcriptionTimeoutMs: config.autoCapture.transcriptionTimeoutMs ?? DEFAULT_TRANSCRIPTION_TIMEOUT_MS,\n tts: { ...config.autoCapture.tts },\n },\n }\n : {}),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"configPolicy.mjs","names":["parseYaml"],"sources":["../../src/services/configPolicy.ts"],"sourcesContent":["import { parse as parseYaml } from 'yaml';\nimport type {\n DoomConfig,\n DoomSelectionConfig,\n EditorConfig,\n PlanningAgentConfig,\n PlanningModeConfig,\n PlanningThinkingLevel,\n ProjectTrust,\n ResolvedVoiceConfig,\n VoiceAdapterConfig,\n VoiceAutoCaptureConfig,\n VoiceConfig,\n VoiceEngine,\n VoiceModelConfig,\n VoiceTtsConfig,\n VoiceTtsEngine,\n} from '../types/config.ts';\n\nconst DEFAULT_PROJECT_TRUST: ProjectTrust = 'ask';\nconst DEFAULT_VOICE_ENGINE: VoiceEngine = 'auto';\nconst DEFAULT_VOICE_LANGUAGE = 'auto';\nconst DEFAULT_RECORDER_DEVICE = 'none:default';\nconst ROOT_KEYS = ['modes', 'projectTrust', 'editor', 'voice', 'selection'] as const;\nconst SELECTION_KEYS = ['majorMode', 'domains', 'profile'] as const;\nconst MODE_KEYS = ['planning'] as const;\nconst PLANNING_KEYS = ['main', 'subagents', 'plansDirectory'] as const;\nconst AGENT_KEYS = ['model', 'thinking'] as const;\nconst EDITOR_KEYS = ['command'] as const;\nconst VOICE_KEYS = ['engine', 'language', 'recorder', 'adapters', 'autoCapture'] as const;\nconst RECORDER_KEYS = ['binary', 'device'] as const;\nconst WHISPER_CPP_ENGINE = 'whisper-cpp' as const;\nconst OPENAI_WHISPER_ENGINE = 'openai-whisper' as const;\nconst MLX_WHISPER_ENGINE = 'mlx-whisper' as const;\nconst MACOS_SAY_TTS_ENGINE = 'macos-say' as const;\nconst ADAPTERS_KEYS = [WHISPER_CPP_ENGINE, OPENAI_WHISPER_ENGINE, MLX_WHISPER_ENGINE] as const;\nconst ADAPTER_KEYS = ['binary', 'model'] as const;\nconst MODEL_KEYS = ['path', 'id'] as const;\nconst AUTO_CAPTURE_KEYS = [\n 'model',\n 'startPhrases',\n 'stopPhrases',\n 'composeOpenPhrases',\n 'composeSendPhrases',\n 'composeCancelPhrases',\n 'utteranceIdleMs',\n 'composeUtteranceIdleMs',\n 'composeNudgeMs',\n 'transcriptionTimeoutMs',\n 'tts',\n] as const;\nconst TTS_KEYS = ['engine', 'voice', 'rate'] as const;\nconst DEFAULT_UTTERANCE_IDLE_MS = 3_000;\nconst MIN_UTTERANCE_IDLE_MS = 1_500;\nconst MAX_UTTERANCE_IDLE_MS = 10_000;\n/**\n * The endpoint window used while a composition draft is collecting.\n *\n * Shorter than `utteranceIdleMs` on purpose. A turn produces exactly one transcript, so\n * with the ordinary ~3 s window a short command spoken after a brief pause arrives glued\n * to the end of the preceding sentence and can never be read as a command. Roughly twice\n * the VAD's own 600 ms trailing-silence window: long enough that ordinary speech rhythm\n * does not split a sentence, short enough that a deliberate pause does. Over-splitting is\n * harmless here and only here, because draft segments are joined back together with a\n * space.\n */\nconst DEFAULT_COMPOSE_UTTERANCE_IDLE_MS = 1_200;\nconst MIN_COMPOSE_UTTERANCE_IDLE_MS = 800;\nconst MAX_COMPOSE_UTTERANCE_IDLE_MS = 3_000;\n/** Silence before Voice reminds the user a draft is still open. `0` disables it. */\nconst DEFAULT_COMPOSE_NUDGE_MS = 10_000;\nconst MIN_COMPOSE_NUDGE_MS = 5_000;\nconst MAX_COMPOSE_NUDGE_MS = 60_000;\n/**\n * Defaults kept as lists so the phrases that shipped first keep working.\n *\n * `hey doom` and `that's it` read as speech; `doom prompt` and `doom send` are what\n * existing users have in their fingers. Dropping the originals would break them for no\n * gain, and the cost of carrying both is two extra comparisons per command candidate.\n */\nconst DEFAULT_COMPOSE_OPEN_PHRASES = ['hey doom', 'doom prompt'] as const;\nconst DEFAULT_COMPOSE_SEND_PHRASES = [\"that's it\", 'doom send'] as const;\nconst DEFAULT_COMPOSE_CANCEL_PHRASES = ['doom cancel', 'scratch that'] as const;\nconst DEFAULT_TRANSCRIPTION_TIMEOUT_MS = 15_000;\nconst MIN_TRANSCRIPTION_TIMEOUT_MS = 1_000;\nconst MAX_TRANSCRIPTION_TIMEOUT_MS = 120_000;\nconst MAX_CONTROL_PHRASES = 16;\nconst MAX_CONTROL_PHRASE_LENGTH = 64;\nconst MAX_TTS_VOICE_LENGTH = 64;\nconst MIN_TTS_RATE = 80;\nconst MAX_TTS_RATE = 500;\nconst MODEL_REFERENCE_PATTERN = /^[^/\\s]+\\/[^/\\s]+(?:\\/[^/\\s]+)*$/u;\nconst PHRASE_PUNCTUATION_PATTERN = /[\\p{P}\\p{S}]+/gu;\n/**\n * The accepted values, in the order a picker should offer them.\n *\n * Exported because the config UI has to offer exactly what the parser accepts,\n * and a second hand-written list in the contributing package would drift. The\n * validating Sets below are built from these for the same reason.\n */\nexport const DOOM_PLANNING_THINKING_LEVELS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'] as const;\nexport const DOOM_VOICE_ENGINES = [\n DEFAULT_VOICE_ENGINE,\n WHISPER_CPP_ENGINE,\n OPENAI_WHISPER_ENGINE,\n MLX_WHISPER_ENGINE,\n] as const;\nexport const DOOM_VOICE_TTS_ENGINES = [MACOS_SAY_TTS_ENGINE] as const;\n\nconst THINKING_VALUES = new Set<PlanningThinkingLevel>(DOOM_PLANNING_THINKING_LEVELS);\nconst TRUST_VALUES = new Set<ProjectTrust>(['ask', 'always', 'never']);\nconst ENGINE_VALUES = new Set<VoiceEngine>(DOOM_VOICE_ENGINES);\nconst TTS_ENGINE_VALUES = new Set<VoiceTtsEngine>(DOOM_VOICE_TTS_ENGINES);\ntype ConfigObject = Record<string, unknown>;\n\nfunction isObject(value: unknown): value is ConfigObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\nfunction assertKeys(value: ConfigObject, allowed: readonly string[], location: string, filePath: string): void {\n const unknown = Object.keys(value).filter((key) => !allowed.includes(key));\n if (unknown.length > 0)\n throw new Error(`Doom config at ${filePath} has unsupported ${location} field(s): ${unknown.join(', ')}`);\n}\nfunction optionalString(value: unknown, location: string, filePath: string): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== 'string' || value.trim().length === 0)\n throw new Error(`Doom config at ${filePath} requires ${location} to be a non-empty string`);\n return value.trim();\n}\nfunction hasControlCharacters(value: string): boolean {\n for (const character of value) {\n const codePoint = character.codePointAt(0);\n if (codePoint !== undefined && (codePoint <= 0x1f || codePoint === 0x7f)) return true;\n }\n return false;\n}\nfunction boundedOptionalString(\n value: unknown,\n location: string,\n filePath: string,\n maximumLength: number,\n): string | undefined {\n const text = optionalString(value, location, filePath);\n if (!text) return undefined;\n if (text.length > maximumLength)\n throw new Error(`Doom config at ${filePath} requires ${location} to be at most ${maximumLength} characters`);\n if (hasControlCharacters(text))\n throw new Error(`Doom config at ${filePath} does not allow control characters in ${location}`);\n return text;\n}\nfunction parseModelReference(value: unknown, filePath: string): string {\n const location = 'voice.autoCapture.model';\n const model = optionalString(value, location, filePath);\n if (!model || !MODEL_REFERENCE_PATTERN.test(model))\n throw new Error(`Doom config at ${filePath} requires ${location} in provider/model-id form`);\n return model;\n}\nfunction normalizeControlPhrase(value: string): string {\n return value\n .normalize('NFKC')\n .toLocaleLowerCase('en-US')\n .replace(PHRASE_PUNCTUATION_PATTERN, ' ')\n .replace(/\\s+/gu, ' ')\n .trim();\n}\nfunction parseControlPhrases(value: unknown, location: string, filePath: string): string[] | undefined {\n if (value === undefined) return undefined;\n if (!Array.isArray(value) || value.some((phrase) => typeof phrase !== 'string'))\n throw new Error(`Doom config at ${filePath} requires ${location} to be an array of strings`);\n if (value.length > MAX_CONTROL_PHRASES)\n throw new Error(\n `Doom config at ${filePath} requires ${location} to contain at most ${MAX_CONTROL_PHRASES} phrases`,\n );\n const phrases = value.map((phrase) => phrase.trim());\n const normalized = new Set<string>();\n for (const phrase of phrases) {\n if (phrase.length === 0)\n throw new Error(`Doom config at ${filePath} requires ${location} to contain non-empty strings`);\n if (phrase.length > MAX_CONTROL_PHRASE_LENGTH)\n throw new Error(\n `Doom config at ${filePath} requires ${location} phrases to be at most ${MAX_CONTROL_PHRASE_LENGTH} characters`,\n );\n if (hasControlCharacters(phrase))\n throw new Error(`Doom config at ${filePath} does not allow control characters in ${location}`);\n const key = normalizeControlPhrase(phrase);\n if (!key) throw new Error(`Doom config at ${filePath} requires ${location} to contain words`);\n if (normalized.has(key))\n throw new Error(`Doom config at ${filePath} does not allow normalized duplicates in ${location}`);\n normalized.add(key);\n }\n return phrases;\n}\nfunction parseBoundedInteger(\n value: unknown,\n location: string,\n filePath: string,\n minimum: number,\n maximum: number,\n): number | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== 'number' || !Number.isInteger(value) || value < minimum || value > maximum)\n throw new Error(`Doom config at ${filePath} requires ${location} to be an integer from ${minimum} to ${maximum}`);\n return value;\n}\n/**\n * Rejects a phrase claimed by two control roles at once.\n *\n * Composition commands are adjudicated before stop phrases and before leading\n * start-phrase removal, so a phrase shared with a later role is not ambiguous, it is\n * silently unreachable. Without a screen there is nothing to show which role won.\n */\nfunction assertDisjointPhrases(\n left: readonly string[] | undefined,\n right: readonly string[] | undefined,\n leftName: string,\n rightName: string,\n filePath: string,\n): void {\n const keys = new Set((left ?? []).map(normalizeControlPhrase));\n if ((right ?? []).some((phrase) => keys.has(normalizeControlPhrase(phrase))))\n throw new Error(`Doom config at ${filePath} does not allow a phrase in both ${leftName} and ${rightName}`);\n}\nfunction parseTtsConfig(value: unknown, filePath: string): VoiceTtsConfig {\n const location = 'voice.autoCapture.tts';\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, TTS_KEYS, location, filePath);\n if (typeof value.engine !== 'string' || !TTS_ENGINE_VALUES.has(value.engine as VoiceTtsEngine))\n throw new Error(\n `Doom config at ${filePath} requires ${location}.engine to be one of: ${[...TTS_ENGINE_VALUES].join(', ')}`,\n );\n const voice = boundedOptionalString(value.voice, `${location}.voice`, filePath, MAX_TTS_VOICE_LENGTH);\n const rate = value.rate;\n if (\n rate !== undefined &&\n (typeof rate !== 'number' || !Number.isInteger(rate) || rate < MIN_TTS_RATE || rate > MAX_TTS_RATE)\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.rate to be an integer from ${MIN_TTS_RATE} to ${MAX_TTS_RATE}`,\n );\n }\n return {\n engine: value.engine as VoiceTtsEngine,\n ...(voice ? { voice } : {}),\n ...(rate === undefined ? {} : { rate }),\n };\n}\nfunction parseAutoCapture(value: unknown, filePath: string): VoiceAutoCaptureConfig | undefined {\n if (value === undefined) return undefined;\n const location = 'voice.autoCapture';\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, AUTO_CAPTURE_KEYS, location, filePath);\n const startPhrases = parseControlPhrases(value.startPhrases, `${location}.startPhrases`, filePath);\n const stopPhrases = parseControlPhrases(value.stopPhrases, `${location}.stopPhrases`, filePath);\n const composeOpenPhrases = parseControlPhrases(value.composeOpenPhrases, `${location}.composeOpenPhrases`, filePath);\n const composeSendPhrases = parseControlPhrases(value.composeSendPhrases, `${location}.composeSendPhrases`, filePath);\n const composeCancelPhrases = parseControlPhrases(\n value.composeCancelPhrases,\n `${location}.composeCancelPhrases`,\n filePath,\n );\n const utteranceIdleMs = parseBoundedInteger(\n value.utteranceIdleMs,\n `${location}.utteranceIdleMs`,\n filePath,\n MIN_UTTERANCE_IDLE_MS,\n MAX_UTTERANCE_IDLE_MS,\n );\n const composeUtteranceIdleMs = parseBoundedInteger(\n value.composeUtteranceIdleMs,\n `${location}.composeUtteranceIdleMs`,\n filePath,\n MIN_COMPOSE_UTTERANCE_IDLE_MS,\n MAX_COMPOSE_UTTERANCE_IDLE_MS,\n );\n // Zero is the documented off switch, so it is accepted outside the range rather than\n // forcing a separate boolean that could disagree with the interval.\n const composeNudgeMs =\n value.composeNudgeMs === 0\n ? 0\n : parseBoundedInteger(\n value.composeNudgeMs,\n `${location}.composeNudgeMs`,\n filePath,\n MIN_COMPOSE_NUDGE_MS,\n MAX_COMPOSE_NUDGE_MS,\n );\n const transcriptionTimeoutMs = parseBoundedInteger(\n value.transcriptionTimeoutMs,\n `${location}.transcriptionTimeoutMs`,\n filePath,\n MIN_TRANSCRIPTION_TIMEOUT_MS,\n MAX_TRANSCRIPTION_TIMEOUT_MS,\n );\n assertDisjointPhrases(startPhrases, stopPhrases, 'startPhrases', 'stopPhrases', filePath);\n // `composeOpenPhrases` may overlap `startPhrases`: addressing the agent and opening a\n // draft are the same gesture, composition is adjudicated first, and the shipped\n // defaults deliberately share `hey doom`.\n assertDisjointPhrases(\n composeSendPhrases,\n composeCancelPhrases,\n 'composeSendPhrases',\n 'composeCancelPhrases',\n filePath,\n );\n assertDisjointPhrases(composeOpenPhrases, composeSendPhrases, 'composeOpenPhrases', 'composeSendPhrases', filePath);\n assertDisjointPhrases(\n composeOpenPhrases,\n composeCancelPhrases,\n 'composeOpenPhrases',\n 'composeCancelPhrases',\n filePath,\n );\n assertDisjointPhrases(stopPhrases, composeSendPhrases, 'stopPhrases', 'composeSendPhrases', filePath);\n assertDisjointPhrases(stopPhrases, composeCancelPhrases, 'stopPhrases', 'composeCancelPhrases', filePath);\n return {\n model: parseModelReference(value.model, filePath),\n ...(startPhrases ? { startPhrases } : {}),\n ...(stopPhrases ? { stopPhrases } : {}),\n ...(composeOpenPhrases ? { composeOpenPhrases } : {}),\n ...(composeSendPhrases ? { composeSendPhrases } : {}),\n ...(composeCancelPhrases ? { composeCancelPhrases } : {}),\n ...(utteranceIdleMs === undefined ? {} : { utteranceIdleMs }),\n ...(composeUtteranceIdleMs === undefined ? {} : { composeUtteranceIdleMs }),\n ...(composeNudgeMs === undefined ? {} : { composeNudgeMs }),\n ...(transcriptionTimeoutMs === undefined ? {} : { transcriptionTimeoutMs }),\n tts: parseTtsConfig(value.tts, filePath),\n };\n}\nfunction parseAgent(value: unknown, location: string, filePath: string): PlanningAgentConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, AGENT_KEYS, location, filePath);\n const thinking = value.thinking;\n if (\n thinking !== undefined &&\n (typeof thinking !== 'string' || !THINKING_VALUES.has(thinking as PlanningThinkingLevel))\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.thinking to be one of: ${[...THINKING_VALUES].join(', ')}`,\n );\n }\n const model = optionalString(value.model, `${location}.model`, filePath);\n return { ...(model ? { model } : {}), ...(thinking ? { thinking: thinking as PlanningThinkingLevel } : {}) };\n}\nexport function parsePlanningModeConfig(\n value: unknown,\n filePath: string,\n location = 'modes.planning',\n): PlanningModeConfig {\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, PLANNING_KEYS, location, filePath);\n return {\n main: parseAgent(value.main, `${location}.main`, filePath),\n subagents: parseAgent(value.subagents, `${location}.subagents`, filePath),\n plansDirectory: optionalString(value.plansDirectory, `${location}.plansDirectory`, filePath),\n };\n}\nfunction parseModel(value: unknown, location: string, filePath: string, allowId: boolean): VoiceModelConfig {\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, MODEL_KEYS, location, filePath);\n const modelPath = optionalString(value.path, `${location}.path`, filePath);\n const id = optionalString(value.id, `${location}.id`, filePath);\n if (Boolean(modelPath) === Boolean(id))\n throw new Error(`Doom config at ${filePath} requires exactly one of ${location}.path or ${location}.id`);\n if (id && !allowId) throw new Error(`Doom config at ${filePath} does not support ${location}.id for whisper-cpp`);\n return { ...(modelPath ? { path: modelPath } : {}), ...(id ? { id } : {}) };\n}\nfunction parseAdapter(\n value: unknown,\n location: string,\n filePath: string,\n allowId: boolean,\n): VoiceAdapterConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, ADAPTER_KEYS, location, filePath);\n const binary = optionalString(value.binary, `${location}.binary`, filePath);\n return { ...(binary ? { binary } : {}), model: parseModel(value.model, `${location}.model`, filePath, allowId) };\n}\nfunction parseVoice(value: unknown, filePath: string): VoiceConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires voice to be an object`);\n assertKeys(value, VOICE_KEYS, 'voice', filePath);\n const result: VoiceConfig = {};\n if (value.engine !== undefined) {\n if (typeof value.engine !== 'string' || !ENGINE_VALUES.has(value.engine as VoiceEngine))\n throw new Error(\n `Doom config at ${filePath} requires voice.engine to be one of: ${[...ENGINE_VALUES].join(', ')}`,\n );\n result.engine = value.engine as VoiceEngine;\n }\n const language = optionalString(value.language, 'voice.language', filePath);\n if (language) result.language = language;\n if (value.recorder !== undefined) {\n if (!isObject(value.recorder))\n throw new Error(`Doom config at ${filePath} requires voice.recorder to be an object`);\n assertKeys(value.recorder, RECORDER_KEYS, 'voice.recorder', filePath);\n result.recorder = {};\n const binary = optionalString(value.recorder.binary, 'voice.recorder.binary', filePath);\n const device = optionalString(value.recorder.device, 'voice.recorder.device', filePath);\n if (binary) result.recorder.binary = binary;\n if (device) result.recorder.device = device;\n }\n if (value.adapters !== undefined) {\n if (!isObject(value.adapters))\n throw new Error(`Doom config at ${filePath} requires voice.adapters to be an object`);\n assertKeys(value.adapters, ADAPTERS_KEYS, 'voice.adapters', filePath);\n result.adapters = {\n [WHISPER_CPP_ENGINE]: parseAdapter(\n value.adapters[WHISPER_CPP_ENGINE],\n `voice.adapters.${WHISPER_CPP_ENGINE}`,\n filePath,\n false,\n ),\n [OPENAI_WHISPER_ENGINE]: parseAdapter(\n value.adapters[OPENAI_WHISPER_ENGINE],\n `voice.adapters.${OPENAI_WHISPER_ENGINE}`,\n filePath,\n true,\n ),\n [MLX_WHISPER_ENGINE]: parseAdapter(\n value.adapters[MLX_WHISPER_ENGINE],\n `voice.adapters.${MLX_WHISPER_ENGINE}`,\n filePath,\n true,\n ),\n };\n }\n const autoCapture = parseAutoCapture(value.autoCapture, filePath);\n if (autoCapture) result.autoCapture = autoCapture;\n return result;\n}\nfunction parseSelection(value: unknown, filePath: string): DoomSelectionConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires selection to be an object`);\n assertKeys(value, SELECTION_KEYS, 'selection', filePath);\n const domains = value.domains;\n if (domains !== undefined && (!Array.isArray(domains) || domains.some((name) => typeof name !== 'string'))) {\n throw new Error(`Doom config at ${filePath} requires selection.domains to be an array of strings`);\n }\n return {\n majorMode: optionalString(value.majorMode, 'selection.majorMode', filePath),\n // An empty list is meaningful: it selects no content domains at all.\n domains: domains as string[] | undefined,\n profile: optionalString(value.profile, 'selection.profile', filePath),\n };\n}\nfunction parseEditor(value: unknown, filePath: string): EditorConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires editor to be an object`);\n assertKeys(value, EDITOR_KEYS, 'editor', filePath);\n return { command: optionalString(value.command, 'editor.command', filePath) };\n}\nexport function parseDoomConfig(content: string, filePath: string): DoomConfig {\n let parsed: unknown;\n try {\n parsed = parseYaml(content) ?? {};\n } catch (error) {\n throw new Error(`Could not parse Doom config at ${filePath}`, { cause: error });\n }\n if (!isObject(parsed)) throw new Error(`Doom config at ${filePath} must be a YAML object`);\n assertKeys(parsed, ROOT_KEYS, 'root', filePath);\n let modes: DoomConfig['modes'];\n if (parsed.modes !== undefined) {\n if (!isObject(parsed.modes)) throw new Error(`Doom config at ${filePath} requires modes to be an object`);\n assertKeys(parsed.modes, MODE_KEYS, 'modes', filePath);\n modes =\n parsed.modes.planning === undefined ? {} : { planning: parsePlanningModeConfig(parsed.modes.planning, filePath) };\n }\n const trust = parsed.projectTrust === undefined ? DEFAULT_PROJECT_TRUST : parsed.projectTrust;\n if (typeof trust !== 'string' || !TRUST_VALUES.has(trust as ProjectTrust))\n throw new Error(`Doom config at ${filePath} projectTrust must be ask, always, or never`);\n return {\n modes,\n projectTrust: trust as ProjectTrust,\n editor: parseEditor(parsed.editor, filePath),\n voice: parseVoice(parsed.voice, filePath),\n selection: parseSelection(parsed.selection, filePath),\n };\n}\nfunction mergeAgent(\n globalValue?: PlanningAgentConfig,\n repositoryValue?: PlanningAgentConfig,\n): PlanningAgentConfig | undefined {\n return globalValue || repositoryValue ? { ...globalValue, ...repositoryValue } : undefined;\n}\nexport function mergeDoomConfigs(globalConfig: DoomConfig, repositoryConfig: DoomConfig): DoomConfig {\n if (repositoryConfig.voice?.autoCapture)\n throw new Error('Repository Doom config voice.autoCapture is global-only; configure it in ~/.pi/.doom/config.yaml');\n const globalPlanning = globalConfig.modes?.planning;\n const repositoryPlanning = repositoryConfig.modes?.planning;\n const planning =\n globalPlanning || repositoryPlanning\n ? {\n main: mergeAgent(globalPlanning?.main, repositoryPlanning?.main),\n subagents: mergeAgent(globalPlanning?.subagents, repositoryPlanning?.subagents),\n plansDirectory: repositoryPlanning?.plansDirectory ?? globalPlanning?.plansDirectory,\n }\n : undefined;\n const voice = mergeVoice(globalConfig.voice, repositoryConfig.voice);\n return {\n modes: planning ? { planning } : undefined,\n projectTrust: repositoryConfig.projectTrust,\n editor: globalConfig.editor,\n voice,\n // Per key, so a repository can pin one axis and inherit the rest.\n selection:\n globalConfig.selection || repositoryConfig.selection\n ? { ...globalConfig.selection, ...repositoryConfig.selection }\n : undefined,\n };\n}\nfunction mergeAdapter(\n globalValue?: VoiceAdapterConfig,\n repositoryValue?: VoiceAdapterConfig,\n): VoiceAdapterConfig | undefined {\n if (!globalValue && !repositoryValue) return undefined;\n return { ...globalValue, ...repositoryValue, model: repositoryValue?.model ?? globalValue!.model };\n}\nfunction mergeVoice(globalValue?: VoiceConfig, repositoryValue?: VoiceConfig): VoiceConfig | undefined {\n if (!globalValue && !repositoryValue) return undefined;\n return {\n ...globalValue,\n ...repositoryValue,\n recorder:\n globalValue?.recorder || repositoryValue?.recorder\n ? { ...globalValue?.recorder, ...repositoryValue?.recorder }\n : undefined,\n adapters:\n globalValue?.adapters || repositoryValue?.adapters\n ? {\n [WHISPER_CPP_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[WHISPER_CPP_ENGINE],\n repositoryValue?.adapters?.[WHISPER_CPP_ENGINE],\n ),\n [OPENAI_WHISPER_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[OPENAI_WHISPER_ENGINE],\n repositoryValue?.adapters?.[OPENAI_WHISPER_ENGINE],\n ),\n [MLX_WHISPER_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[MLX_WHISPER_ENGINE],\n repositoryValue?.adapters?.[MLX_WHISPER_ENGINE],\n ),\n }\n : undefined,\n ...(globalValue?.autoCapture ? { autoCapture: globalValue.autoCapture } : {}),\n };\n}\nexport function resolveVoiceConfig(config: VoiceConfig): ResolvedVoiceConfig {\n return {\n engine: config.engine ?? DEFAULT_VOICE_ENGINE,\n language: config.language ?? DEFAULT_VOICE_LANGUAGE,\n recorder: { ...config.recorder, device: config.recorder?.device ?? DEFAULT_RECORDER_DEVICE },\n adapters: config.adapters ?? {},\n ...(config.autoCapture\n ? {\n autoCapture: {\n ...config.autoCapture,\n startPhrases: [...(config.autoCapture.startPhrases ?? [])],\n stopPhrases: [...(config.autoCapture.stopPhrases ?? [])],\n // Unlike start and stop phrases, these default to a non-empty list: without\n // a send phrase a draft could be opened and never submitted.\n composeOpenPhrases: [...(config.autoCapture.composeOpenPhrases ?? DEFAULT_COMPOSE_OPEN_PHRASES)],\n composeSendPhrases: [...(config.autoCapture.composeSendPhrases ?? DEFAULT_COMPOSE_SEND_PHRASES)],\n composeCancelPhrases: [...(config.autoCapture.composeCancelPhrases ?? DEFAULT_COMPOSE_CANCEL_PHRASES)],\n utteranceIdleMs: config.autoCapture.utteranceIdleMs ?? DEFAULT_UTTERANCE_IDLE_MS,\n composeUtteranceIdleMs: config.autoCapture.composeUtteranceIdleMs ?? DEFAULT_COMPOSE_UTTERANCE_IDLE_MS,\n composeNudgeMs: config.autoCapture.composeNudgeMs ?? DEFAULT_COMPOSE_NUDGE_MS,\n transcriptionTimeoutMs: config.autoCapture.transcriptionTimeoutMs ?? DEFAULT_TRANSCRIPTION_TIMEOUT_MS,\n tts: { ...config.autoCapture.tts },\n },\n }\n : {}),\n };\n}\n"],"mappings":"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"}
|
package/dist/types/config.d.cts
CHANGED
|
@@ -34,7 +34,12 @@ interface VoiceAutoCaptureConfig {
|
|
|
34
34
|
model: string;
|
|
35
35
|
startPhrases?: string[];
|
|
36
36
|
stopPhrases?: string[];
|
|
37
|
+
composeOpenPhrases?: string[];
|
|
38
|
+
composeSendPhrases?: string[];
|
|
39
|
+
composeCancelPhrases?: string[];
|
|
37
40
|
utteranceIdleMs?: number;
|
|
41
|
+
composeUtteranceIdleMs?: number;
|
|
42
|
+
composeNudgeMs?: number;
|
|
38
43
|
transcriptionTimeoutMs?: number;
|
|
39
44
|
tts: VoiceTtsConfig;
|
|
40
45
|
}
|
|
@@ -42,7 +47,12 @@ interface ResolvedVoiceAutoCaptureConfig {
|
|
|
42
47
|
model: string;
|
|
43
48
|
startPhrases: string[];
|
|
44
49
|
stopPhrases: string[];
|
|
50
|
+
composeOpenPhrases: string[];
|
|
51
|
+
composeSendPhrases: string[];
|
|
52
|
+
composeCancelPhrases: string[];
|
|
45
53
|
utteranceIdleMs: number;
|
|
54
|
+
composeUtteranceIdleMs: number;
|
|
55
|
+
composeNudgeMs: number;
|
|
46
56
|
transcriptionTimeoutMs?: number;
|
|
47
57
|
tts: VoiceTtsConfig;
|
|
48
58
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.cts","names":[],"sources":["../../src/types/config.ts"],"mappings":";;;KAKY;KACA;KACA;KACA;UAEK;EACf;EACA,WAAW;;UAEI;EACf,OAAO;EACP,YAAY;EACZ;;UAEe;EACf;;UAEe;EACf;EACA;;UAEe;EACf;EACA,OAAO;;UAEQ;EACf,QAAQ;EACR;EACA;;UAEe;EACf;EACA;EACA;EACA;EACA;EACA,KAAK;;UAEU;EACf;EACA;EACA;EACA;EACA;EACA,KAAK;;UAEU;EACf,SAAS;EACT;EACA;IAAa;IAAiB;;EAC9B;IACE,gBAAgB;IAChB,mBAAmB;IACnB,gBAAgB;;EAElB,cAAc;;UAEC;EACf,QAAQ;EACR;EACA;IAAY;IAAiB;;EAC7B,UAAU,YAAY;EACtB,cAAc;;;;;;;;;UASC;EACf;EACA;EACA;;;UAIe;WACN;WACA;WACA;WACA;;WAEA;;KAGC;KACA;UAEK;WACN;WACA;WACA,QAAQ;WACR,QAAQ;WACR,UAAU;WACV,OAAO;;;KAIN,6BAA6B;WAAwC;;UAEhE;EACf;IAAU,WAAW;;EACrB,cAAc;EACd,SAAS;EACT,QAAQ;EACR,YAAY;;UAGG;EACf;EACA;;UAGe;EACf;;EAEA;EACA;EACA;;EAEA;;EAEA;EACA;EACA,oBAAoB;EACpB;EACA;EACA;EACA;EACA;EACA;EACA,aAAa;;EAEb,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA;;KAGU,aAAa,UAAU,oBAAmB,6BAClD,SACA,+BAA+B,oBACpB,aAAa,WACtB,oCACc,cAAc,SAAS,aAAa,OAAO,YACvD;KAEI,qBAAqB,aAAa;UAE7B;WACN,UAAU,aAAa;WACvB,SAAS;;WAET,mBAAmB,aAAa;;WAEhC;;;UAIM;EACf,KAAK,kBAAkB,yBAAyB;;;UAIjC,6BAA2B,mBAA0B,mBAAmB;;YAG7E;IACR,eAAe"}
|
|
1
|
+
{"version":3,"file":"config.d.cts","names":[],"sources":["../../src/types/config.ts"],"mappings":";;;KAKY;KACA;KACA;KACA;UAEK;EACf;EACA,WAAW;;UAEI;EACf,OAAO;EACP,YAAY;EACZ;;UAEe;EACf;;UAEe;EACf;EACA;;UAEe;EACf;EACA,OAAO;;UAEQ;EACf,QAAQ;EACR;EACA;;UAEe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,KAAK;;UAEU;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,KAAK;;UAEU;EACf,SAAS;EACT;EACA;IAAa;IAAiB;;EAC9B;IACE,gBAAgB;IAChB,mBAAmB;IACnB,gBAAgB;;EAElB,cAAc;;UAEC;EACf,QAAQ;EACR;EACA;IAAY;IAAiB;;EAC7B,UAAU,YAAY;EACtB,cAAc;;;;;;;;;UASC;EACf;EACA;EACA;;;UAIe;WACN;WACA;WACA;WACA;;WAEA;;KAGC;KACA;UAEK;WACN;WACA;WACA,QAAQ;WACR,QAAQ;WACR,UAAU;WACV,OAAO;;;KAIN,6BAA6B;WAAwC;;UAEhE;EACf;IAAU,WAAW;;EACrB,cAAc;EACd,SAAS;EACT,QAAQ;EACR,YAAY;;UAGG;EACf;EACA;;UAGe;EACf;;EAEA;EACA;EACA;;EAEA;;EAEA;EACA;EACA,oBAAoB;EACpB;EACA;EACA;EACA;EACA;EACA;EACA,aAAa;;EAEb,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA;;KAGU,aAAa,UAAU,oBAAmB,6BAClD,SACA,+BAA+B,oBACpB,aAAa,WACtB,oCACc,cAAc,SAAS,aAAa,OAAO,YACvD;KAEI,qBAAqB,aAAa;UAE7B;WACN,UAAU,aAAa;WACvB,SAAS;;WAET,mBAAmB,aAAa;;WAEhC;;;UAIM;EACf,KAAK,kBAAkB,yBAAyB;;;UAIjC,6BAA2B,mBAA0B,mBAAmB;;YAG7E;IACR,eAAe"}
|
package/dist/types/config.d.mts
CHANGED
|
@@ -34,7 +34,12 @@ interface VoiceAutoCaptureConfig {
|
|
|
34
34
|
model: string;
|
|
35
35
|
startPhrases?: string[];
|
|
36
36
|
stopPhrases?: string[];
|
|
37
|
+
composeOpenPhrases?: string[];
|
|
38
|
+
composeSendPhrases?: string[];
|
|
39
|
+
composeCancelPhrases?: string[];
|
|
37
40
|
utteranceIdleMs?: number;
|
|
41
|
+
composeUtteranceIdleMs?: number;
|
|
42
|
+
composeNudgeMs?: number;
|
|
38
43
|
transcriptionTimeoutMs?: number;
|
|
39
44
|
tts: VoiceTtsConfig;
|
|
40
45
|
}
|
|
@@ -42,7 +47,12 @@ interface ResolvedVoiceAutoCaptureConfig {
|
|
|
42
47
|
model: string;
|
|
43
48
|
startPhrases: string[];
|
|
44
49
|
stopPhrases: string[];
|
|
50
|
+
composeOpenPhrases: string[];
|
|
51
|
+
composeSendPhrases: string[];
|
|
52
|
+
composeCancelPhrases: string[];
|
|
45
53
|
utteranceIdleMs: number;
|
|
54
|
+
composeUtteranceIdleMs: number;
|
|
55
|
+
composeNudgeMs: number;
|
|
46
56
|
transcriptionTimeoutMs?: number;
|
|
47
57
|
tts: VoiceTtsConfig;
|
|
48
58
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.mts","names":[],"sources":["../../src/types/config.ts"],"mappings":";;;KAKY;KACA;KACA;KACA;UAEK;EACf;EACA,WAAW;;UAEI;EACf,OAAO;EACP,YAAY;EACZ;;UAEe;EACf;;UAEe;EACf;EACA;;UAEe;EACf;EACA,OAAO;;UAEQ;EACf,QAAQ;EACR;EACA;;UAEe;EACf;EACA;EACA;EACA;EACA;EACA,KAAK;;UAEU;EACf;EACA;EACA;EACA;EACA;EACA,KAAK;;UAEU;EACf,SAAS;EACT;EACA;IAAa;IAAiB;;EAC9B;IACE,gBAAgB;IAChB,mBAAmB;IACnB,gBAAgB;;EAElB,cAAc;;UAEC;EACf,QAAQ;EACR;EACA;IAAY;IAAiB;;EAC7B,UAAU,YAAY;EACtB,cAAc;;;;;;;;;UASC;EACf;EACA;EACA;;;UAIe;WACN;WACA;WACA;WACA;;WAEA;;KAGC;KACA;UAEK;WACN;WACA;WACA,QAAQ;WACR,QAAQ;WACR,UAAU;WACV,OAAO;;;KAIN,6BAA6B;WAAwC;;UAEhE;EACf;IAAU,WAAW;;EACrB,cAAc;EACd,SAAS;EACT,QAAQ;EACR,YAAY;;UAGG;EACf;EACA;;UAGe;EACf;;EAEA;EACA;EACA;;EAEA;;EAEA;EACA;EACA,oBAAoB;EACpB;EACA;EACA;EACA;EACA;EACA;EACA,aAAa;;EAEb,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA;;KAGU,aAAa,UAAU,oBAAmB,6BAClD,SACA,+BAA+B,oBACpB,aAAa,WACtB,oCACc,cAAc,SAAS,aAAa,OAAO,YACvD;KAEI,qBAAqB,aAAa;UAE7B;WACN,UAAU,aAAa;WACvB,SAAS;;WAET,mBAAmB,aAAa;;WAEhC;;;UAIM;EACf,KAAK,kBAAkB,yBAAyB;;;UAIjC,6BAA2B,mBAA0B,mBAAmB;;YAG7E;IACR,eAAe"}
|
|
1
|
+
{"version":3,"file":"config.d.mts","names":[],"sources":["../../src/types/config.ts"],"mappings":";;;KAKY;KACA;KACA;KACA;UAEK;EACf;EACA,WAAW;;UAEI;EACf,OAAO;EACP,YAAY;EACZ;;UAEe;EACf;;UAEe;EACf;EACA;;UAEe;EACf;EACA,OAAO;;UAEQ;EACf,QAAQ;EACR;EACA;;UAEe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,KAAK;;UAEU;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,KAAK;;UAEU;EACf,SAAS;EACT;EACA;IAAa;IAAiB;;EAC9B;IACE,gBAAgB;IAChB,mBAAmB;IACnB,gBAAgB;;EAElB,cAAc;;UAEC;EACf,QAAQ;EACR;EACA;IAAY;IAAiB;;EAC7B,UAAU,YAAY;EACtB,cAAc;;;;;;;;;UASC;EACf;EACA;EACA;;;UAIe;WACN;WACA;WACA;WACA;;WAEA;;KAGC;KACA;UAEK;WACN;WACA;WACA,QAAQ;WACR,QAAQ;WACR,UAAU;WACV,OAAO;;;KAIN,6BAA6B;WAAwC;;UAEhE;EACf;IAAU,WAAW;;EACrB,cAAc;EACd,SAAS;EACT,QAAQ;EACR,YAAY;;UAGG;EACf;EACA;;UAGe;EACf;;EAEA;EACA;EACA;;EAEA;;EAEA;EACA;EACA,oBAAoB;EACpB;EACA;EACA;EACA;EACA;EACA;EACA,aAAa;;EAEb,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA;;KAGU,aAAa,UAAU,oBAAmB,6BAClD,SACA,+BAA+B,oBACpB,aAAa,WACtB,oCACc,cAAc,SAAS,aAAa,OAAO,YACvD;KAEI,qBAAqB,aAAa;UAE7B;WACN,UAAU,aAAa;WACvB,SAAS;;WAET,mBAAmB,aAAa;;WAEhC;;;UAIM;EACf,KAAK,kBAAkB,yBAAyB;;;UAIjC,6BAA2B,mBAA0B,mBAAmB;;YAG7E;IACR,eAAe"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/doompi-config",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.38",
|
|
4
4
|
"description": "Typed configuration loading, validation, and host adapters for composing DoomPi sessions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-configuration",
|
|
@@ -136,11 +136,11 @@
|
|
|
136
136
|
"dependencies": {
|
|
137
137
|
"@deepseek-ai/cordis": "4.0.1",
|
|
138
138
|
"yaml": "2.9.0",
|
|
139
|
-
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.
|
|
140
|
-
"@agimon-ai/doompi-telemetry": "0.0.1-alpha.
|
|
139
|
+
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.38",
|
|
140
|
+
"@agimon-ai/doompi-telemetry": "0.0.1-alpha.38"
|
|
141
141
|
},
|
|
142
142
|
"devDependencies": {
|
|
143
|
-
"@earendil-works/pi-coding-agent": "0.84.
|
|
143
|
+
"@earendil-works/pi-coding-agent": "0.84.3",
|
|
144
144
|
"@types/node": "26.2.0",
|
|
145
145
|
"@vitest/coverage-v8": "4.1.11",
|
|
146
146
|
"tsdown": "0.22.14",
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
"vitest": "4.1.11"
|
|
149
149
|
},
|
|
150
150
|
"peerDependencies": {
|
|
151
|
-
"@earendil-works/pi-coding-agent": "0.84.
|
|
151
|
+
"@earendil-works/pi-coding-agent": "0.84.3"
|
|
152
152
|
},
|
|
153
153
|
"engines": {
|
|
154
154
|
"node": ">=22.19.0"
|