@agimon-ai/doompi 0.0.1-alpha.72 → 0.0.1-alpha.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builders/cli/contextCatalog.cjs +15 -3
- package/dist/builders/cli/contextCatalog.cjs.map +1 -1
- package/dist/builders/cli/contextCatalog.mjs +15 -3
- package/dist/builders/cli/contextCatalog.mjs.map +1 -1
- package/dist/builders/cli/projectSettings.cjs +1 -1
- package/dist/builders/cli/projectSettings.cjs.map +1 -1
- package/dist/builders/cli/projectSettings.mjs +1 -1
- package/dist/builders/cli/projectSettings.mjs.map +1 -1
- package/dist/builders/server/runtime.cjs +60 -5
- package/dist/builders/server/runtime.cjs.map +1 -1
- package/dist/builders/server/runtime.mjs +61 -6
- package/dist/builders/server/runtime.mjs.map +1 -1
- package/dist/builders/web/bundle.cjs +1 -0
- package/dist/builders/web/bundle.cjs.map +1 -1
- package/dist/builders/web/bundle.mjs +1 -0
- package/dist/builders/web/bundle.mjs.map +1 -1
- package/dist/builders/web/webPluginGenerate.cjs +12 -5
- package/dist/builders/web/webPluginGenerate.cjs.map +1 -1
- package/dist/builders/web/webPluginGenerate.d.cts.map +1 -1
- package/dist/builders/web/webPluginGenerate.d.mts.map +1 -1
- package/dist/builders/web/webPluginGenerate.mjs +12 -5
- package/dist/builders/web/webPluginGenerate.mjs.map +1 -1
- package/dist/builders/web/webPluginVite.cjs +9 -0
- package/dist/builders/web/webPluginVite.cjs.map +1 -1
- package/dist/builders/web/webPluginVite.d.cts +9 -0
- package/dist/builders/web/webPluginVite.d.cts.map +1 -1
- package/dist/builders/web/webPluginVite.d.mts +9 -0
- package/dist/builders/web/webPluginVite.d.mts.map +1 -1
- package/dist/builders/web/webPluginVite.mjs +9 -0
- package/dist/builders/web/webPluginVite.mjs.map +1 -1
- package/dist/cli/server/run.cjs +2 -0
- package/dist/cli/server/run.cjs.map +1 -1
- package/dist/cli/server/run.mjs +2 -0
- package/dist/cli/server/run.mjs.map +1 -1
- package/dist/compiler/index.cjs +5 -5
- package/dist/compiler/index.cjs.map +1 -1
- package/dist/compiler/index.mjs +5 -5
- package/dist/compiler/index.mjs.map +1 -1
- package/dist/extensions/context-catalog.cjs +35 -4
- package/dist/extensions/context-catalog.cjs.map +1 -1
- package/dist/extensions/context-catalog.mjs +35 -4
- package/dist/extensions/context-catalog.mjs.map +1 -1
- package/dist/extensions/terminal-child-session.cjs +18 -0
- package/dist/extensions/terminal-child-session.cjs.map +1 -1
- package/dist/extensions/terminal-child-session.d.cts.map +1 -1
- package/dist/extensions/terminal-child-session.d.mts.map +1 -1
- package/dist/extensions/terminal-child-session.mjs +18 -0
- package/dist/extensions/terminal-child-session.mjs.map +1 -1
- package/docs/server/api-export.md +10 -0
- package/docs/server/api.md +5 -5
- package/docs/server/getting-started.md +4 -4
- package/docs/server/index.md +6 -6
- package/docs/server/ipc.md +28 -14
- package/docs/server/lifecycle.md +5 -5
- package/docs/server/security.md +4 -4
- package/package.json +25 -25
- package/src/prompts/doompi-author-extension/SKILL.md +4 -3
- package/src/prompts/doompi-author-extension/references/extension-contract.md +26 -26
|
@@ -57,6 +57,11 @@ function createContextPublisher(pi, cordis, options = {}) {
|
|
|
57
57
|
})).groups);
|
|
58
58
|
} catch {}
|
|
59
59
|
const countTokens = await (0, _agimon_ai_doompi_skill_catalog.counter)();
|
|
60
|
+
const prompt = options.readSystemPrompt?.();
|
|
61
|
+
const promptCost = prompt === void 0 ? void 0 : {
|
|
62
|
+
tokens: countTokens(prompt.text),
|
|
63
|
+
stage: prompt.stage
|
|
64
|
+
};
|
|
60
65
|
const projection = (0, _agimon_ai_doompi_core_context_projection.projectContext)({
|
|
61
66
|
revision: revision + 1,
|
|
62
67
|
majorMode: harness.majorMode,
|
|
@@ -68,11 +73,13 @@ function createContextPublisher(pi, cordis, options = {}) {
|
|
|
68
73
|
sources,
|
|
69
74
|
skills,
|
|
70
75
|
attribution: attributionFor(repoRoot, harness.domains, harness.pluginDirectories),
|
|
71
|
-
countTokens
|
|
76
|
+
countTokens,
|
|
77
|
+
...promptCost === void 0 ? {} : { systemPrompt: promptCost }
|
|
72
78
|
});
|
|
73
79
|
const serialized = JSON.stringify({
|
|
74
80
|
...projection,
|
|
75
|
-
revision: 0
|
|
81
|
+
revision: 0,
|
|
82
|
+
promptText: prompt?.text
|
|
76
83
|
});
|
|
77
84
|
if (serialized === published || disposed) return;
|
|
78
85
|
published = serialized;
|
|
@@ -83,7 +90,12 @@ function createContextPublisher(pi, cordis, options = {}) {
|
|
|
83
90
|
options.writeDetail(sessionId, revision, (0, _agimon_ai_doompi_core_context_detail.buildContextDetail)({
|
|
84
91
|
sources,
|
|
85
92
|
skills,
|
|
86
|
-
countTokens
|
|
93
|
+
countTokens,
|
|
94
|
+
...prompt === void 0 || promptCost === void 0 ? {} : { systemPrompt: {
|
|
95
|
+
text: prompt.text,
|
|
96
|
+
tokens: promptCost.tokens,
|
|
97
|
+
stage: prompt.stage
|
|
98
|
+
} }
|
|
87
99
|
}));
|
|
88
100
|
}
|
|
89
101
|
pi.appendEntry(_agimon_ai_doompi_core_context_projection.DOOM_CONTEXT_ENTRY_TYPE, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contextCatalog.cjs","names":["getHarnessState","resolvePluginEntries","readDoomMcpStatus","buildToolSources","extensionName","extensionPackageName","extensionToolSource","harnessRoot","buildSkillCatalog","readDoomSkillSourcesService","counter","projectContext","buildContextDetail","DOOM_CONTEXT_ENTRY_TYPE"],"sources":["../../../src/builders/cli/contextCatalog.ts"],"sourcesContent":["import { resolvePluginEntries } from '@agimon-ai/doompi-config/domains';\nimport { getHarnessState, harnessRoot } from '@agimon-ai/doompi-config/harnessStore';\nimport type { PackageAttribution } from '@agimon-ai/doompi-config/types';\nimport { buildContextDetail } from '@agimon-ai/doompi-core/context-detail';\nimport { DOOM_CONTEXT_ENTRY_TYPE, projectContext } from '@agimon-ai/doompi-core/context-projection';\nimport { readDoomMcpStatus } from '@agimon-ai/doompi-core/mcp-status';\nimport { readDoomSkillSourcesService } from '@agimon-ai/doompi-core/skills';\nimport type { ContextItemDetail } from '@agimon-ai/doompi-core/types-context-api';\nimport { buildSkillCatalog, counter, type SkillEntry } from '@agimon-ai/doompi-skill/catalog';\nimport { extensionName, extensionPackageName, extensionToolSource } from '@agimon-ai/doompi-ui/extensionName';\nimport { buildToolSources } from '@agimon-ai/doompi-ui/toolInventory';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\n\n/**\n * Publishes what the session is composed of, and what it costs.\n *\n * Read at publish time rather than accumulated, because the answer changes for\n * reasons this module does not see: a reconnected MCP server, a domain switch,\n * a plan-mode tool swap. Reading the live inventory is cheap next to being\n * wrong about it.\n */\nexport interface ContextPublisher {\n /** Never rejects: callers fire this and forget it. */\n publish: () => Promise<void>;\n dispose: () => void;\n}\n\n/** The active minor modes at publish time, read rather than remembered. */\nexport type ReadMinorModes = () => readonly { readonly id: string; readonly label: string }[];\n\nexport interface ContextPublisherOptions {\n readMinorModes?: ReadMinorModes;\n /**\n * The session the detail file is keyed by, read at publish time.\n *\n * Undefined until a session is bound, and undefined in a host that has no\n * session at all; the projection is still journaled either way, and only the\n * click-through detail goes unwritten.\n */\n readSessionId?: () => string | undefined;\n /**\n * Where the click-through detail is left for the session API to find.\n *\n * Injected because writing it is filesystem work and this is not the layer\n * that does filesystem work. A host that supplies neither still gets the\n * projection; only the detail behind a row goes unpublished.\n */\n writeDetail?: (sessionId: string, revision: number, items: readonly ContextItemDetail[]) => void;\n removeDetail?: (sessionId: string) => void;\n}\n\n/** Skills sit under owners, which is one level deeper than a flat list. */\nfunction flattenSkills(groups: Awaited<ReturnType<typeof buildSkillCatalog>>['groups']): SkillEntry[] {\n return groups.flatMap((group) => group.owners.flatMap((owner) => owner.skills));\n}\n\n/**\n * Package and plugin names to the mode that admitted them.\n *\n * Two halves meet here. Layer packages come from the harness, recorded when the\n * composition resolved. Domain plugins are re-resolved, because only the plugin\n * entry knows the domain that carried it.\n */\nfunction attributionFor(repoRoot: string, domains: readonly string[], pluginDirectories: readonly string[]) {\n const harness = getHarnessState();\n const attribution: Record<string, PackageAttribution> = { ...harness.packageAttribution };\n try {\n for (const entry of resolvePluginEntries(repoRoot, [...domains], [...pluginDirectories])) {\n if (entry.name && entry.domain) attribution[entry.name] = { kind: 'domain', mode: entry.domain };\n }\n } catch {\n // A domain that no longer resolves must not cost the reader the tool list.\n // Anything unmatched simply lands under core.\n }\n return attribution;\n}\n\nexport function createContextPublisher(\n pi: ExtensionAPI,\n cordis: Context,\n options: ContextPublisherOptions = {},\n): ContextPublisher {\n let published: string | undefined;\n let revision = 0;\n let disposed = false;\n /** Removed on disposal, so a session leaves nothing behind in the store. */\n let detailSessionId: string | undefined;\n\n const build = async (): Promise<void> => {\n if (disposed) return;\n const harness = getHarnessState();\n const mcpServers = readDoomMcpStatus(cordis)?.getSnapshot().servers;\n const sources = buildToolSources({\n tools: pi.getAllTools(),\n activeTools: pi.getActiveTools(),\n ...(mcpServers ? { mcpServers } : {}),\n resolveExtensionName: extensionName,\n resolveExtensionPackageName: extensionPackageName,\n resolveExtensionToolSource: (toolName) => extensionToolSource(pi, toolName),\n });\n\n const repoRoot = harnessRoot(harness);\n let skills: SkillEntry[] = [];\n try {\n const catalog = await buildSkillCatalog({\n repoRoot,\n activeSkillDirectories: harness.skillDirectories,\n extensionSources: readDoomSkillSourcesService(cordis)?.list() ?? [],\n });\n skills = flattenSkills(catalog.groups);\n } catch {\n // Tools are the larger cost and are already in hand; a skill walk that\n // fails should not take the whole figure down with it.\n }\n\n const countTokens = await counter();\n const projection = projectContext({\n revision: revision + 1,\n majorMode: harness.majorMode,\n groups: (options.readMinorModes?.() ?? []).map((mode) => ({ ...mode, kind: 'minor' as const })),\n domains: harness.domains,\n sources,\n skills,\n attribution: attributionFor(repoRoot, harness.domains, harness.pluginDirectories),\n countTokens,\n });\n\n // Revision is compared out, so a republish that changed nothing is silent\n // rather than a new journal entry saying the same thing.\n const serialized = JSON.stringify({ ...projection, revision: 0 });\n if (serialized === published || disposed) return;\n published = serialized;\n revision += 1;\n // The detail lands before the entry that invites a reader to ask for it, so\n // a click that follows the panel's update cannot outrun the file behind it.\n const sessionId = options.readSessionId?.();\n if (sessionId !== undefined && sessionId !== '' && options.writeDetail !== undefined) {\n detailSessionId = sessionId;\n options.writeDetail(sessionId, revision, buildContextDetail({ sources, skills, countTokens }));\n }\n pi.appendEntry(DOOM_CONTEXT_ENTRY_TYPE, { ...projection, revision });\n };\n\n /**\n * Reporting the composition is a convenience, so it fails quietly.\n *\n * Callers publish without awaiting, and an escaping rejection would surface\n * as an unhandled error in a live session. A panel that cannot say what the\n * toolbox costs is a far smaller problem than that.\n */\n const publish = async (): Promise<void> => {\n try {\n await build();\n } catch {\n // Left unreported on purpose: the next composition change tries again.\n }\n };\n\n return {\n publish,\n dispose: () => {\n disposed = true;\n if (detailSessionId !== undefined) options.removeDetail?.(detailSessionId);\n detailSessionId = undefined;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;AAqDA,SAAS,cAAc,QAA+E;CACpG,OAAO,OAAO,SAAS,UAAU,MAAM,OAAO,SAAS,UAAU,MAAM,MAAM,CAAC;AAChF;;;;;;;;AASA,SAAS,eAAe,UAAkB,SAA4B,mBAAsC;CAE1G,MAAM,cAAkD,EAAE,IAAA,GAD1CA,sCAAAA,gBAAAA,CACmD,CAAC,CAAC,mBAAmB;CACxF,IAAI;EACF,KAAK,MAAM,UAAA,GAASC,iCAAAA,qBAAAA,CAAqB,UAAU,CAAC,GAAG,OAAO,GAAG,CAAC,GAAG,iBAAiB,CAAC,GACrF,IAAI,MAAM,QAAQ,MAAM,QAAQ,YAAY,MAAM,QAAQ;GAAE,MAAM;GAAU,MAAM,MAAM;EAAO;CAEnG,QAAQ,CAGR;CACA,OAAO;AACT;AAEA,SAAgB,uBACd,IACA,QACA,UAAmC,CAAC,GAClB;CAClB,IAAI;CACJ,IAAI,WAAW;CACf,IAAI,WAAW;;CAEf,IAAI;CAEJ,MAAM,QAAQ,YAA2B;EACvC,IAAI,UAAU;EACd,MAAM,WAAA,GAAUD,sCAAAA,gBAAAA,CAAgB;EAChC,MAAM,cAAA,GAAaE,kCAAAA,kBAAAA,CAAkB,MAAM,CAAC,EAAE,YAAY,CAAC,CAAC;EAC5D,MAAM,WAAA,GAAUC,mCAAAA,iBAAAA,CAAiB;GAC/B,OAAO,GAAG,YAAY;GACtB,aAAa,GAAG,eAAe;GAC/B,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;GACnC,sBAAsBC,mCAAAA;GACtB,6BAA6BC,mCAAAA;GAC7B,6BAA6B,cAAA,GAAaC,mCAAAA,oBAAAA,CAAoB,IAAI,QAAQ;EAC5E,CAAC;EAED,MAAM,YAAA,GAAWC,sCAAAA,YAAAA,CAAY,OAAO;EACpC,IAAI,SAAuB,CAAC;EAC5B,IAAI;GAMF,SAAS,eAAc,OAAA,GALDC,gCAAAA,kBAAAA,CAAkB;IACtC;IACA,wBAAwB,QAAQ;IAChC,mBAAA,GAAkBC,8BAAAA,4BAAAA,CAA4B,MAAM,CAAC,EAAE,KAAK,KAAK,CAAC;GACpE,CAAC,EAAA,CAC8B,MAAM;EACvC,QAAQ,CAGR;EAEA,MAAM,cAAc,OAAA,GAAMC,gCAAAA,QAAAA,CAAQ;EAClC,MAAM,cAAA,GAAaC,0CAAAA,eAAAA,CAAe;GAChC,UAAU,WAAW;GACrB,WAAW,QAAQ;GACnB,SAAS,QAAQ,iBAAiB,KAAK,CAAC,EAAA,CAAG,KAAK,UAAU;IAAE,GAAG;IAAM,MAAM;GAAiB,EAAE;GAC9F,SAAS,QAAQ;GACjB;GACA;GACA,aAAa,eAAe,UAAU,QAAQ,SAAS,QAAQ,iBAAiB;GAChF;EACF,CAAC;EAID,MAAM,aAAa,KAAK,UAAU;GAAE,GAAG;GAAY,UAAU;EAAE,CAAC;EAChE,IAAI,eAAe,aAAa,UAAU;EAC1C,YAAY;EACZ,YAAY;EAGZ,MAAM,YAAY,QAAQ,gBAAgB;EAC1C,IAAI,cAAc,KAAA,KAAa,cAAc,MAAM,QAAQ,gBAAgB,KAAA,GAAW;GACpF,kBAAkB;GAClB,QAAQ,YAAY,WAAW,WAAA,GAAUC,sCAAAA,mBAAAA,CAAmB;IAAE;IAAS;IAAQ;GAAY,CAAC,CAAC;EAC/F;EACA,GAAG,YAAYC,0CAAAA,yBAAyB;GAAE,GAAG;GAAY;EAAS,CAAC;CACrE;;;;;;;;CASA,MAAM,UAAU,YAA2B;EACzC,IAAI;GACF,MAAM,MAAM;EACd,QAAQ,CAER;CACF;CAEA,OAAO;EACL;EACA,eAAe;GACb,WAAW;GACX,IAAI,oBAAoB,KAAA,GAAW,QAAQ,eAAe,eAAe;GACzE,kBAAkB,KAAA;EACpB;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"contextCatalog.cjs","names":["getHarnessState","resolvePluginEntries","readDoomMcpStatus","buildToolSources","extensionName","extensionPackageName","extensionToolSource","harnessRoot","buildSkillCatalog","readDoomSkillSourcesService","counter","projectContext","buildContextDetail","DOOM_CONTEXT_ENTRY_TYPE"],"sources":["../../../src/builders/cli/contextCatalog.ts"],"sourcesContent":["import { resolvePluginEntries } from '@agimon-ai/doompi-config/domains';\nimport { getHarnessState, harnessRoot } from '@agimon-ai/doompi-config/harnessStore';\nimport type { PackageAttribution } from '@agimon-ai/doompi-config/types';\nimport { buildContextDetail } from '@agimon-ai/doompi-core/context-detail';\nimport { DOOM_CONTEXT_ENTRY_TYPE, projectContext } from '@agimon-ai/doompi-core/context-projection';\nimport { readDoomMcpStatus } from '@agimon-ai/doompi-core/mcp-status';\nimport { readDoomSkillSourcesService } from '@agimon-ai/doompi-core/skills';\nimport type { ContextItemDetail, ContextPromptStage } from '@agimon-ai/doompi-core/types-context-api';\nimport { buildSkillCatalog, counter, type SkillEntry } from '@agimon-ai/doompi-skill/catalog';\nimport { extensionName, extensionPackageName, extensionToolSource } from '@agimon-ai/doompi-ui/extensionName';\nimport { buildToolSources } from '@agimon-ai/doompi-ui/toolInventory';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\n\n/**\n * Publishes what the session is composed of, and what it costs.\n *\n * Read at publish time rather than accumulated, because the answer changes for\n * reasons this module does not see: a reconnected MCP server, a domain switch,\n * a plan-mode tool swap. Reading the live inventory is cheap next to being\n * wrong about it.\n */\nexport interface ContextPublisher {\n /** Never rejects: callers fire this and forget it. */\n publish: () => Promise<void>;\n dispose: () => void;\n}\n\n/** The active minor modes at publish time, read rather than remembered. */\nexport type ReadMinorModes = () => readonly { readonly id: string; readonly label: string }[];\n\nexport interface ContextPublisherOptions {\n readMinorModes?: ReadMinorModes;\n /**\n * The session the detail file is keyed by, read at publish time.\n *\n * Undefined until a session is bound, and undefined in a host that has no\n * session at all; the projection is still journaled either way, and only the\n * click-through detail goes unwritten.\n */\n readSessionId?: () => string | undefined;\n /**\n * Where the click-through detail is left for the session API to find.\n *\n * Injected because writing it is filesystem work and this is not the layer\n * that does filesystem work. A host that supplies neither still gets the\n * projection; only the detail behind a row goes unpublished.\n */\n writeDetail?: (sessionId: string, revision: number, items: readonly ContextItemDetail[]) => void;\n removeDetail?: (sessionId: string) => void;\n /**\n * The system prompt as the session currently stands.\n *\n * Read rather than pushed, for the same reason the tool inventory is: the\n * prompt changes for reasons this module does not see. A host with no way to\n * answer returns undefined and the panel reports no prompt rather than a\n * stale one.\n */\n readSystemPrompt?: () => { readonly text: string; readonly stage: ContextPromptStage } | undefined;\n}\n\n/** Skills sit under owners, which is one level deeper than a flat list. */\nfunction flattenSkills(groups: Awaited<ReturnType<typeof buildSkillCatalog>>['groups']): SkillEntry[] {\n return groups.flatMap((group) => group.owners.flatMap((owner) => owner.skills));\n}\n\n/**\n * Package and plugin names to the mode that admitted them.\n *\n * Two halves meet here. Layer packages come from the harness, recorded when the\n * composition resolved. Domain plugins are re-resolved, because only the plugin\n * entry knows the domain that carried it.\n */\nfunction attributionFor(repoRoot: string, domains: readonly string[], pluginDirectories: readonly string[]) {\n const harness = getHarnessState();\n const attribution: Record<string, PackageAttribution> = { ...harness.packageAttribution };\n try {\n for (const entry of resolvePluginEntries(repoRoot, [...domains], [...pluginDirectories])) {\n if (entry.name && entry.domain) attribution[entry.name] = { kind: 'domain', mode: entry.domain };\n }\n } catch {\n // A domain that no longer resolves must not cost the reader the tool list.\n // Anything unmatched simply lands under core.\n }\n return attribution;\n}\n\nexport function createContextPublisher(\n pi: ExtensionAPI,\n cordis: Context,\n options: ContextPublisherOptions = {},\n): ContextPublisher {\n let published: string | undefined;\n let revision = 0;\n let disposed = false;\n /** Removed on disposal, so a session leaves nothing behind in the store. */\n let detailSessionId: string | undefined;\n\n const build = async (): Promise<void> => {\n if (disposed) return;\n const harness = getHarnessState();\n const mcpServers = readDoomMcpStatus(cordis)?.getSnapshot().servers;\n const sources = buildToolSources({\n tools: pi.getAllTools(),\n activeTools: pi.getActiveTools(),\n ...(mcpServers ? { mcpServers } : {}),\n resolveExtensionName: extensionName,\n resolveExtensionPackageName: extensionPackageName,\n resolveExtensionToolSource: (toolName) => extensionToolSource(pi, toolName),\n });\n\n const repoRoot = harnessRoot(harness);\n let skills: SkillEntry[] = [];\n try {\n const catalog = await buildSkillCatalog({\n repoRoot,\n activeSkillDirectories: harness.skillDirectories,\n extensionSources: readDoomSkillSourcesService(cordis)?.list() ?? [],\n });\n skills = flattenSkills(catalog.groups);\n } catch {\n // Tools are the larger cost and are already in hand; a skill walk that\n // fails should not take the whole figure down with it.\n }\n\n const countTokens = await counter();\n const prompt = options.readSystemPrompt?.();\n const promptCost = prompt === undefined ? undefined : { tokens: countTokens(prompt.text), stage: prompt.stage };\n const projection = projectContext({\n revision: revision + 1,\n majorMode: harness.majorMode,\n groups: (options.readMinorModes?.() ?? []).map((mode) => ({ ...mode, kind: 'minor' as const })),\n domains: harness.domains,\n sources,\n skills,\n attribution: attributionFor(repoRoot, harness.domains, harness.pluginDirectories),\n countTokens,\n ...(promptCost === undefined ? {} : { systemPrompt: promptCost }),\n });\n\n // Revision is compared out, so a republish that changed nothing is silent\n // rather than a new journal entry saying the same thing. The prompt joins by\n // its text: a reworded prompt of the same length is still a different answer.\n const serialized = JSON.stringify({ ...projection, revision: 0, promptText: prompt?.text });\n if (serialized === published || disposed) return;\n published = serialized;\n revision += 1;\n // The detail lands before the entry that invites a reader to ask for it, so\n // a click that follows the panel's update cannot outrun the file behind it.\n const sessionId = options.readSessionId?.();\n if (sessionId !== undefined && sessionId !== '' && options.writeDetail !== undefined) {\n detailSessionId = sessionId;\n options.writeDetail(\n sessionId,\n revision,\n buildContextDetail({\n sources,\n skills,\n countTokens,\n ...(prompt === undefined || promptCost === undefined\n ? {}\n : { systemPrompt: { text: prompt.text, tokens: promptCost.tokens, stage: prompt.stage } }),\n }),\n );\n }\n pi.appendEntry(DOOM_CONTEXT_ENTRY_TYPE, { ...projection, revision });\n };\n\n /**\n * Reporting the composition is a convenience, so it fails quietly.\n *\n * Callers publish without awaiting, and an escaping rejection would surface\n * as an unhandled error in a live session. A panel that cannot say what the\n * toolbox costs is a far smaller problem than that.\n */\n const publish = async (): Promise<void> => {\n try {\n await build();\n } catch {\n // Left unreported on purpose: the next composition change tries again.\n }\n };\n\n return {\n publish,\n dispose: () => {\n disposed = true;\n if (detailSessionId !== undefined) options.removeDetail?.(detailSessionId);\n detailSessionId = undefined;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -57,6 +57,11 @@ function createContextPublisher(pi, cordis, options = {}) {
|
|
|
57
57
|
})).groups);
|
|
58
58
|
} catch {}
|
|
59
59
|
const countTokens = await counter();
|
|
60
|
+
const prompt = options.readSystemPrompt?.();
|
|
61
|
+
const promptCost = prompt === void 0 ? void 0 : {
|
|
62
|
+
tokens: countTokens(prompt.text),
|
|
63
|
+
stage: prompt.stage
|
|
64
|
+
};
|
|
60
65
|
const projection = projectContext({
|
|
61
66
|
revision: revision + 1,
|
|
62
67
|
majorMode: harness.majorMode,
|
|
@@ -68,11 +73,13 @@ function createContextPublisher(pi, cordis, options = {}) {
|
|
|
68
73
|
sources,
|
|
69
74
|
skills,
|
|
70
75
|
attribution: attributionFor(repoRoot, harness.domains, harness.pluginDirectories),
|
|
71
|
-
countTokens
|
|
76
|
+
countTokens,
|
|
77
|
+
...promptCost === void 0 ? {} : { systemPrompt: promptCost }
|
|
72
78
|
});
|
|
73
79
|
const serialized = JSON.stringify({
|
|
74
80
|
...projection,
|
|
75
|
-
revision: 0
|
|
81
|
+
revision: 0,
|
|
82
|
+
promptText: prompt?.text
|
|
76
83
|
});
|
|
77
84
|
if (serialized === published || disposed) return;
|
|
78
85
|
published = serialized;
|
|
@@ -83,7 +90,12 @@ function createContextPublisher(pi, cordis, options = {}) {
|
|
|
83
90
|
options.writeDetail(sessionId, revision, buildContextDetail({
|
|
84
91
|
sources,
|
|
85
92
|
skills,
|
|
86
|
-
countTokens
|
|
93
|
+
countTokens,
|
|
94
|
+
...prompt === void 0 || promptCost === void 0 ? {} : { systemPrompt: {
|
|
95
|
+
text: prompt.text,
|
|
96
|
+
tokens: promptCost.tokens,
|
|
97
|
+
stage: prompt.stage
|
|
98
|
+
} }
|
|
87
99
|
}));
|
|
88
100
|
}
|
|
89
101
|
pi.appendEntry(DOOM_CONTEXT_ENTRY_TYPE, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contextCatalog.mjs","names":[],"sources":["../../../src/builders/cli/contextCatalog.ts"],"sourcesContent":["import { resolvePluginEntries } from '@agimon-ai/doompi-config/domains';\nimport { getHarnessState, harnessRoot } from '@agimon-ai/doompi-config/harnessStore';\nimport type { PackageAttribution } from '@agimon-ai/doompi-config/types';\nimport { buildContextDetail } from '@agimon-ai/doompi-core/context-detail';\nimport { DOOM_CONTEXT_ENTRY_TYPE, projectContext } from '@agimon-ai/doompi-core/context-projection';\nimport { readDoomMcpStatus } from '@agimon-ai/doompi-core/mcp-status';\nimport { readDoomSkillSourcesService } from '@agimon-ai/doompi-core/skills';\nimport type { ContextItemDetail } from '@agimon-ai/doompi-core/types-context-api';\nimport { buildSkillCatalog, counter, type SkillEntry } from '@agimon-ai/doompi-skill/catalog';\nimport { extensionName, extensionPackageName, extensionToolSource } from '@agimon-ai/doompi-ui/extensionName';\nimport { buildToolSources } from '@agimon-ai/doompi-ui/toolInventory';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\n\n/**\n * Publishes what the session is composed of, and what it costs.\n *\n * Read at publish time rather than accumulated, because the answer changes for\n * reasons this module does not see: a reconnected MCP server, a domain switch,\n * a plan-mode tool swap. Reading the live inventory is cheap next to being\n * wrong about it.\n */\nexport interface ContextPublisher {\n /** Never rejects: callers fire this and forget it. */\n publish: () => Promise<void>;\n dispose: () => void;\n}\n\n/** The active minor modes at publish time, read rather than remembered. */\nexport type ReadMinorModes = () => readonly { readonly id: string; readonly label: string }[];\n\nexport interface ContextPublisherOptions {\n readMinorModes?: ReadMinorModes;\n /**\n * The session the detail file is keyed by, read at publish time.\n *\n * Undefined until a session is bound, and undefined in a host that has no\n * session at all; the projection is still journaled either way, and only the\n * click-through detail goes unwritten.\n */\n readSessionId?: () => string | undefined;\n /**\n * Where the click-through detail is left for the session API to find.\n *\n * Injected because writing it is filesystem work and this is not the layer\n * that does filesystem work. A host that supplies neither still gets the\n * projection; only the detail behind a row goes unpublished.\n */\n writeDetail?: (sessionId: string, revision: number, items: readonly ContextItemDetail[]) => void;\n removeDetail?: (sessionId: string) => void;\n}\n\n/** Skills sit under owners, which is one level deeper than a flat list. */\nfunction flattenSkills(groups: Awaited<ReturnType<typeof buildSkillCatalog>>['groups']): SkillEntry[] {\n return groups.flatMap((group) => group.owners.flatMap((owner) => owner.skills));\n}\n\n/**\n * Package and plugin names to the mode that admitted them.\n *\n * Two halves meet here. Layer packages come from the harness, recorded when the\n * composition resolved. Domain plugins are re-resolved, because only the plugin\n * entry knows the domain that carried it.\n */\nfunction attributionFor(repoRoot: string, domains: readonly string[], pluginDirectories: readonly string[]) {\n const harness = getHarnessState();\n const attribution: Record<string, PackageAttribution> = { ...harness.packageAttribution };\n try {\n for (const entry of resolvePluginEntries(repoRoot, [...domains], [...pluginDirectories])) {\n if (entry.name && entry.domain) attribution[entry.name] = { kind: 'domain', mode: entry.domain };\n }\n } catch {\n // A domain that no longer resolves must not cost the reader the tool list.\n // Anything unmatched simply lands under core.\n }\n return attribution;\n}\n\nexport function createContextPublisher(\n pi: ExtensionAPI,\n cordis: Context,\n options: ContextPublisherOptions = {},\n): ContextPublisher {\n let published: string | undefined;\n let revision = 0;\n let disposed = false;\n /** Removed on disposal, so a session leaves nothing behind in the store. */\n let detailSessionId: string | undefined;\n\n const build = async (): Promise<void> => {\n if (disposed) return;\n const harness = getHarnessState();\n const mcpServers = readDoomMcpStatus(cordis)?.getSnapshot().servers;\n const sources = buildToolSources({\n tools: pi.getAllTools(),\n activeTools: pi.getActiveTools(),\n ...(mcpServers ? { mcpServers } : {}),\n resolveExtensionName: extensionName,\n resolveExtensionPackageName: extensionPackageName,\n resolveExtensionToolSource: (toolName) => extensionToolSource(pi, toolName),\n });\n\n const repoRoot = harnessRoot(harness);\n let skills: SkillEntry[] = [];\n try {\n const catalog = await buildSkillCatalog({\n repoRoot,\n activeSkillDirectories: harness.skillDirectories,\n extensionSources: readDoomSkillSourcesService(cordis)?.list() ?? [],\n });\n skills = flattenSkills(catalog.groups);\n } catch {\n // Tools are the larger cost and are already in hand; a skill walk that\n // fails should not take the whole figure down with it.\n }\n\n const countTokens = await counter();\n const projection = projectContext({\n revision: revision + 1,\n majorMode: harness.majorMode,\n groups: (options.readMinorModes?.() ?? []).map((mode) => ({ ...mode, kind: 'minor' as const })),\n domains: harness.domains,\n sources,\n skills,\n attribution: attributionFor(repoRoot, harness.domains, harness.pluginDirectories),\n countTokens,\n });\n\n // Revision is compared out, so a republish that changed nothing is silent\n // rather than a new journal entry saying the same thing.\n const serialized = JSON.stringify({ ...projection, revision: 0 });\n if (serialized === published || disposed) return;\n published = serialized;\n revision += 1;\n // The detail lands before the entry that invites a reader to ask for it, so\n // a click that follows the panel's update cannot outrun the file behind it.\n const sessionId = options.readSessionId?.();\n if (sessionId !== undefined && sessionId !== '' && options.writeDetail !== undefined) {\n detailSessionId = sessionId;\n options.writeDetail(sessionId, revision, buildContextDetail({ sources, skills, countTokens }));\n }\n pi.appendEntry(DOOM_CONTEXT_ENTRY_TYPE, { ...projection, revision });\n };\n\n /**\n * Reporting the composition is a convenience, so it fails quietly.\n *\n * Callers publish without awaiting, and an escaping rejection would surface\n * as an unhandled error in a live session. A panel that cannot say what the\n * toolbox costs is a far smaller problem than that.\n */\n const publish = async (): Promise<void> => {\n try {\n await build();\n } catch {\n // Left unreported on purpose: the next composition change tries again.\n }\n };\n\n return {\n publish,\n dispose: () => {\n disposed = true;\n if (detailSessionId !== undefined) options.removeDetail?.(detailSessionId);\n detailSessionId = undefined;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;AAqDA,SAAS,cAAc,QAA+E;CACpG,OAAO,OAAO,SAAS,UAAU,MAAM,OAAO,SAAS,UAAU,MAAM,MAAM,CAAC;AAChF;;;;;;;;AASA,SAAS,eAAe,UAAkB,SAA4B,mBAAsC;CAE1G,MAAM,cAAkD,EAAE,GAD1C,gBACmD,CAAC,CAAC,mBAAmB;CACxF,IAAI;EACF,KAAK,MAAM,SAAS,qBAAqB,UAAU,CAAC,GAAG,OAAO,GAAG,CAAC,GAAG,iBAAiB,CAAC,GACrF,IAAI,MAAM,QAAQ,MAAM,QAAQ,YAAY,MAAM,QAAQ;GAAE,MAAM;GAAU,MAAM,MAAM;EAAO;CAEnG,QAAQ,CAGR;CACA,OAAO;AACT;AAEA,SAAgB,uBACd,IACA,QACA,UAAmC,CAAC,GAClB;CAClB,IAAI;CACJ,IAAI,WAAW;CACf,IAAI,WAAW;;CAEf,IAAI;CAEJ,MAAM,QAAQ,YAA2B;EACvC,IAAI,UAAU;EACd,MAAM,UAAU,gBAAgB;EAChC,MAAM,aAAa,kBAAkB,MAAM,CAAC,EAAE,YAAY,CAAC,CAAC;EAC5D,MAAM,UAAU,iBAAiB;GAC/B,OAAO,GAAG,YAAY;GACtB,aAAa,GAAG,eAAe;GAC/B,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;GACnC,sBAAsB;GACtB,6BAA6B;GAC7B,6BAA6B,aAAa,oBAAoB,IAAI,QAAQ;EAC5E,CAAC;EAED,MAAM,WAAW,YAAY,OAAO;EACpC,IAAI,SAAuB,CAAC;EAC5B,IAAI;GAMF,SAAS,eAAc,MALD,kBAAkB;IACtC;IACA,wBAAwB,QAAQ;IAChC,kBAAkB,4BAA4B,MAAM,CAAC,EAAE,KAAK,KAAK,CAAC;GACpE,CAAC,EAAA,CAC8B,MAAM;EACvC,QAAQ,CAGR;EAEA,MAAM,cAAc,MAAM,QAAQ;EAClC,MAAM,aAAa,eAAe;GAChC,UAAU,WAAW;GACrB,WAAW,QAAQ;GACnB,SAAS,QAAQ,iBAAiB,KAAK,CAAC,EAAA,CAAG,KAAK,UAAU;IAAE,GAAG;IAAM,MAAM;GAAiB,EAAE;GAC9F,SAAS,QAAQ;GACjB;GACA;GACA,aAAa,eAAe,UAAU,QAAQ,SAAS,QAAQ,iBAAiB;GAChF;EACF,CAAC;EAID,MAAM,aAAa,KAAK,UAAU;GAAE,GAAG;GAAY,UAAU;EAAE,CAAC;EAChE,IAAI,eAAe,aAAa,UAAU;EAC1C,YAAY;EACZ,YAAY;EAGZ,MAAM,YAAY,QAAQ,gBAAgB;EAC1C,IAAI,cAAc,KAAA,KAAa,cAAc,MAAM,QAAQ,gBAAgB,KAAA,GAAW;GACpF,kBAAkB;GAClB,QAAQ,YAAY,WAAW,UAAU,mBAAmB;IAAE;IAAS;IAAQ;GAAY,CAAC,CAAC;EAC/F;EACA,GAAG,YAAY,yBAAyB;GAAE,GAAG;GAAY;EAAS,CAAC;CACrE;;;;;;;;CASA,MAAM,UAAU,YAA2B;EACzC,IAAI;GACF,MAAM,MAAM;EACd,QAAQ,CAER;CACF;CAEA,OAAO;EACL;EACA,eAAe;GACb,WAAW;GACX,IAAI,oBAAoB,KAAA,GAAW,QAAQ,eAAe,eAAe;GACzE,kBAAkB,KAAA;EACpB;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"contextCatalog.mjs","names":[],"sources":["../../../src/builders/cli/contextCatalog.ts"],"sourcesContent":["import { resolvePluginEntries } from '@agimon-ai/doompi-config/domains';\nimport { getHarnessState, harnessRoot } from '@agimon-ai/doompi-config/harnessStore';\nimport type { PackageAttribution } from '@agimon-ai/doompi-config/types';\nimport { buildContextDetail } from '@agimon-ai/doompi-core/context-detail';\nimport { DOOM_CONTEXT_ENTRY_TYPE, projectContext } from '@agimon-ai/doompi-core/context-projection';\nimport { readDoomMcpStatus } from '@agimon-ai/doompi-core/mcp-status';\nimport { readDoomSkillSourcesService } from '@agimon-ai/doompi-core/skills';\nimport type { ContextItemDetail, ContextPromptStage } from '@agimon-ai/doompi-core/types-context-api';\nimport { buildSkillCatalog, counter, type SkillEntry } from '@agimon-ai/doompi-skill/catalog';\nimport { extensionName, extensionPackageName, extensionToolSource } from '@agimon-ai/doompi-ui/extensionName';\nimport { buildToolSources } from '@agimon-ai/doompi-ui/toolInventory';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\n\n/**\n * Publishes what the session is composed of, and what it costs.\n *\n * Read at publish time rather than accumulated, because the answer changes for\n * reasons this module does not see: a reconnected MCP server, a domain switch,\n * a plan-mode tool swap. Reading the live inventory is cheap next to being\n * wrong about it.\n */\nexport interface ContextPublisher {\n /** Never rejects: callers fire this and forget it. */\n publish: () => Promise<void>;\n dispose: () => void;\n}\n\n/** The active minor modes at publish time, read rather than remembered. */\nexport type ReadMinorModes = () => readonly { readonly id: string; readonly label: string }[];\n\nexport interface ContextPublisherOptions {\n readMinorModes?: ReadMinorModes;\n /**\n * The session the detail file is keyed by, read at publish time.\n *\n * Undefined until a session is bound, and undefined in a host that has no\n * session at all; the projection is still journaled either way, and only the\n * click-through detail goes unwritten.\n */\n readSessionId?: () => string | undefined;\n /**\n * Where the click-through detail is left for the session API to find.\n *\n * Injected because writing it is filesystem work and this is not the layer\n * that does filesystem work. A host that supplies neither still gets the\n * projection; only the detail behind a row goes unpublished.\n */\n writeDetail?: (sessionId: string, revision: number, items: readonly ContextItemDetail[]) => void;\n removeDetail?: (sessionId: string) => void;\n /**\n * The system prompt as the session currently stands.\n *\n * Read rather than pushed, for the same reason the tool inventory is: the\n * prompt changes for reasons this module does not see. A host with no way to\n * answer returns undefined and the panel reports no prompt rather than a\n * stale one.\n */\n readSystemPrompt?: () => { readonly text: string; readonly stage: ContextPromptStage } | undefined;\n}\n\n/** Skills sit under owners, which is one level deeper than a flat list. */\nfunction flattenSkills(groups: Awaited<ReturnType<typeof buildSkillCatalog>>['groups']): SkillEntry[] {\n return groups.flatMap((group) => group.owners.flatMap((owner) => owner.skills));\n}\n\n/**\n * Package and plugin names to the mode that admitted them.\n *\n * Two halves meet here. Layer packages come from the harness, recorded when the\n * composition resolved. Domain plugins are re-resolved, because only the plugin\n * entry knows the domain that carried it.\n */\nfunction attributionFor(repoRoot: string, domains: readonly string[], pluginDirectories: readonly string[]) {\n const harness = getHarnessState();\n const attribution: Record<string, PackageAttribution> = { ...harness.packageAttribution };\n try {\n for (const entry of resolvePluginEntries(repoRoot, [...domains], [...pluginDirectories])) {\n if (entry.name && entry.domain) attribution[entry.name] = { kind: 'domain', mode: entry.domain };\n }\n } catch {\n // A domain that no longer resolves must not cost the reader the tool list.\n // Anything unmatched simply lands under core.\n }\n return attribution;\n}\n\nexport function createContextPublisher(\n pi: ExtensionAPI,\n cordis: Context,\n options: ContextPublisherOptions = {},\n): ContextPublisher {\n let published: string | undefined;\n let revision = 0;\n let disposed = false;\n /** Removed on disposal, so a session leaves nothing behind in the store. */\n let detailSessionId: string | undefined;\n\n const build = async (): Promise<void> => {\n if (disposed) return;\n const harness = getHarnessState();\n const mcpServers = readDoomMcpStatus(cordis)?.getSnapshot().servers;\n const sources = buildToolSources({\n tools: pi.getAllTools(),\n activeTools: pi.getActiveTools(),\n ...(mcpServers ? { mcpServers } : {}),\n resolveExtensionName: extensionName,\n resolveExtensionPackageName: extensionPackageName,\n resolveExtensionToolSource: (toolName) => extensionToolSource(pi, toolName),\n });\n\n const repoRoot = harnessRoot(harness);\n let skills: SkillEntry[] = [];\n try {\n const catalog = await buildSkillCatalog({\n repoRoot,\n activeSkillDirectories: harness.skillDirectories,\n extensionSources: readDoomSkillSourcesService(cordis)?.list() ?? [],\n });\n skills = flattenSkills(catalog.groups);\n } catch {\n // Tools are the larger cost and are already in hand; a skill walk that\n // fails should not take the whole figure down with it.\n }\n\n const countTokens = await counter();\n const prompt = options.readSystemPrompt?.();\n const promptCost = prompt === undefined ? undefined : { tokens: countTokens(prompt.text), stage: prompt.stage };\n const projection = projectContext({\n revision: revision + 1,\n majorMode: harness.majorMode,\n groups: (options.readMinorModes?.() ?? []).map((mode) => ({ ...mode, kind: 'minor' as const })),\n domains: harness.domains,\n sources,\n skills,\n attribution: attributionFor(repoRoot, harness.domains, harness.pluginDirectories),\n countTokens,\n ...(promptCost === undefined ? {} : { systemPrompt: promptCost }),\n });\n\n // Revision is compared out, so a republish that changed nothing is silent\n // rather than a new journal entry saying the same thing. The prompt joins by\n // its text: a reworded prompt of the same length is still a different answer.\n const serialized = JSON.stringify({ ...projection, revision: 0, promptText: prompt?.text });\n if (serialized === published || disposed) return;\n published = serialized;\n revision += 1;\n // The detail lands before the entry that invites a reader to ask for it, so\n // a click that follows the panel's update cannot outrun the file behind it.\n const sessionId = options.readSessionId?.();\n if (sessionId !== undefined && sessionId !== '' && options.writeDetail !== undefined) {\n detailSessionId = sessionId;\n options.writeDetail(\n sessionId,\n revision,\n buildContextDetail({\n sources,\n skills,\n countTokens,\n ...(prompt === undefined || promptCost === undefined\n ? {}\n : { systemPrompt: { text: prompt.text, tokens: promptCost.tokens, stage: prompt.stage } }),\n }),\n );\n }\n pi.appendEntry(DOOM_CONTEXT_ENTRY_TYPE, { ...projection, revision });\n };\n\n /**\n * Reporting the composition is a convenience, so it fails quietly.\n *\n * Callers publish without awaiting, and an escaping rejection would surface\n * as an unhandled error in a live session. A panel that cannot say what the\n * toolbox costs is a far smaller problem than that.\n */\n const publish = async (): Promise<void> => {\n try {\n await build();\n } catch {\n // Left unreported on purpose: the next composition change tries again.\n }\n };\n\n return {\n publish,\n dispose: () => {\n disposed = true;\n if (detailSessionId !== undefined) options.removeDetail?.(detailSessionId);\n detailSessionId = undefined;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -57,7 +57,7 @@ function resolveSettingsPath(value, baseDirectory, homeDirectory) {
|
|
|
57
57
|
*
|
|
58
58
|
* The bare package name is matched textually because that is the spelling sync
|
|
59
59
|
* writes into the user scope, and a resolved path is matched by walking to the
|
|
60
|
-
* package it belongs to, which is what catches `../packages/
|
|
60
|
+
* package it belongs to, which is what catches `../packages/cli/doompi` and a
|
|
61
61
|
* direct reference to a file inside it. Pattern entries select among already
|
|
62
62
|
* resolved paths rather than naming one, so they are left for the user to own.
|
|
63
63
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projectSettings.cjs","names":["path","DOOM_PACKAGE_NAME","isDoomPackagePath","isRecord","os","fs","readJson","piExtensionAliasPath"],"sources":["../../../src/builders/cli/projectSettings.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { DOOM_PACKAGE_NAME, isDoomPackagePath } from '@agimon-ai/doompi-core/doom-package';\nimport { isRecord, readJson, writeFileAtomic } from '@agimon-ai/doompi-core/runtime-json';\nimport type { JsonObject } from '@agimon-ai/doompi-core/runtime-json';\n\nimport { piExtensionAliasPath } from './piExtensionAlias';\n\n/**\n * Pi's project settings, as `doompi sync` reconciles them.\n *\n * Pi documents project settings as overriding user settings, but resource\n * resolution unions the two scopes and dedupes only by the resolved file's real\n * path. A repository that registers DoomPi alongside the stable user entry\n * therefore loads two installs of the same package against one home-scoped\n * worktree state. Sync removes the redundant project registration and leaves every\n * other key alone: the repository still overrides the user scope, through its own\n * `.doom` configuration and the runtime staged under `~/.pi/.doom/sync`.\n */\n\nconst PI_DIRECTORY = '.pi';\nconst SETTINGS_FILE = 'settings.json';\nconst EXTENSIONS_KEY = 'extensions';\nconst PACKAGES_KEY = 'packages';\nconst PACKAGE_SOURCE_KEY = 'source';\nconst HOME_ALIAS = '~';\nconst HOME_ALIAS_PREFIX = '~/';\n/** Pattern-form entries Pi filters with rather than resolves. */\nconst PATTERN_PREFIXES = ['!', '+', '-'] as const;\n\n/**\n * Reported by `sync --check` and `build`, and repaired by `sync`.\n *\n * Lives here rather than with either command because both report the same\n * finding about the same file, and the detector below is what decides it.\n */\nexport const DUPLICATE_REGISTRATION_DRIFT = 'duplicate DoomPi registration in .pi/settings.json';\n\n/** Pi's project configuration directory, the base its relative paths resolve against. */\nexport function projectPiDirectory(repoRoot: string): string {\n return path.join(repoRoot, PI_DIRECTORY);\n}\n\nexport function projectPiSettingsPath(repoRoot: string): string {\n return path.join(projectPiDirectory(repoRoot), SETTINGS_FILE);\n}\n\nfunction resolveSettingsPath(value: string, baseDirectory: string, homeDirectory: string): string {\n if (value === HOME_ALIAS) return homeDirectory;\n if (value.startsWith(HOME_ALIAS_PREFIX)) return path.join(homeDirectory, value.slice(HOME_ALIAS_PREFIX.length));\n return path.resolve(baseDirectory, value);\n}\n\n/**\n * Whether one settings entry names this package.\n *\n * The bare package name is matched textually because that is the spelling sync\n * writes into the user scope, and a resolved path is matched by walking to the\n * package it belongs to, which is what catches `../packages/
|
|
1
|
+
{"version":3,"file":"projectSettings.cjs","names":["path","DOOM_PACKAGE_NAME","isDoomPackagePath","isRecord","os","fs","readJson","piExtensionAliasPath"],"sources":["../../../src/builders/cli/projectSettings.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { DOOM_PACKAGE_NAME, isDoomPackagePath } from '@agimon-ai/doompi-core/doom-package';\nimport { isRecord, readJson, writeFileAtomic } from '@agimon-ai/doompi-core/runtime-json';\nimport type { JsonObject } from '@agimon-ai/doompi-core/runtime-json';\n\nimport { piExtensionAliasPath } from './piExtensionAlias';\n\n/**\n * Pi's project settings, as `doompi sync` reconciles them.\n *\n * Pi documents project settings as overriding user settings, but resource\n * resolution unions the two scopes and dedupes only by the resolved file's real\n * path. A repository that registers DoomPi alongside the stable user entry\n * therefore loads two installs of the same package against one home-scoped\n * worktree state. Sync removes the redundant project registration and leaves every\n * other key alone: the repository still overrides the user scope, through its own\n * `.doom` configuration and the runtime staged under `~/.pi/.doom/sync`.\n */\n\nconst PI_DIRECTORY = '.pi';\nconst SETTINGS_FILE = 'settings.json';\nconst EXTENSIONS_KEY = 'extensions';\nconst PACKAGES_KEY = 'packages';\nconst PACKAGE_SOURCE_KEY = 'source';\nconst HOME_ALIAS = '~';\nconst HOME_ALIAS_PREFIX = '~/';\n/** Pattern-form entries Pi filters with rather than resolves. */\nconst PATTERN_PREFIXES = ['!', '+', '-'] as const;\n\n/**\n * Reported by `sync --check` and `build`, and repaired by `sync`.\n *\n * Lives here rather than with either command because both report the same\n * finding about the same file, and the detector below is what decides it.\n */\nexport const DUPLICATE_REGISTRATION_DRIFT = 'duplicate DoomPi registration in .pi/settings.json';\n\n/** Pi's project configuration directory, the base its relative paths resolve against. */\nexport function projectPiDirectory(repoRoot: string): string {\n return path.join(repoRoot, PI_DIRECTORY);\n}\n\nexport function projectPiSettingsPath(repoRoot: string): string {\n return path.join(projectPiDirectory(repoRoot), SETTINGS_FILE);\n}\n\nfunction resolveSettingsPath(value: string, baseDirectory: string, homeDirectory: string): string {\n if (value === HOME_ALIAS) return homeDirectory;\n if (value.startsWith(HOME_ALIAS_PREFIX)) return path.join(homeDirectory, value.slice(HOME_ALIAS_PREFIX.length));\n return path.resolve(baseDirectory, value);\n}\n\n/**\n * Whether one settings entry names this package.\n *\n * The bare package name is matched textually because that is the spelling sync\n * writes into the user scope, and a resolved path is matched by walking to the\n * package it belongs to, which is what catches `../packages/cli/doompi` and a\n * direct reference to a file inside it. Pattern entries select among already\n * resolved paths rather than naming one, so they are left for the user to own.\n */\nfunction isDoomEntry(value: string, baseDirectory: string, homeDirectory: string): boolean {\n const normalized = value.replaceAll('\\\\', '/').trim();\n if (normalized === DOOM_PACKAGE_NAME) return true;\n if (PATTERN_PREFIXES.some((prefix) => normalized.startsWith(prefix))) return false;\n return isDoomPackagePath(resolveSettingsPath(normalized, baseDirectory, homeDirectory));\n}\n\nfunction packageSource(entry: unknown): string | undefined {\n if (typeof entry === 'string') return entry;\n if (isRecord(entry) && typeof entry[PACKAGE_SOURCE_KEY] === 'string') return entry[PACKAGE_SOURCE_KEY];\n return undefined;\n}\n\n/**\n * Drops DoomPi from one resource list, or reports that the key should go.\n *\n * A list that empties out is removed rather than left behind: an empty array is\n * indistinguishable from an absent one to Pi, and keeping it would leave the\n * repository looking like it still configures something.\n */\nfunction withoutDoomEntries(value: unknown, baseDirectory: string, homeDirectory: string): unknown {\n if (!Array.isArray(value)) return value;\n const kept = value.filter((entry) => {\n const source = packageSource(entry);\n return source === undefined || !isDoomEntry(source, baseDirectory, homeDirectory);\n });\n if (kept.length === value.length) return value;\n return kept.length > 0 ? kept : undefined;\n}\n\n/** Produces the project settings content for a reconciliation, without writing it. */\nexport function mergeProjectPiSettings(\n current: JsonObject,\n repoRoot: string,\n homeDirectory: string = os.homedir(),\n): JsonObject {\n const baseDirectory = projectPiDirectory(repoRoot);\n const merged: JsonObject = { ...current };\n for (const key of [EXTENSIONS_KEY, PACKAGES_KEY]) {\n if (!(key in merged)) continue;\n const next = withoutDoomEntries(merged[key], baseDirectory, homeDirectory);\n if (next === undefined) delete merged[key];\n else merged[key] = next;\n }\n return merged;\n}\n\n/** Reads Pi's project settings, or nothing when the repository declares none. */\nexport function readProjectPiSettings(repoRoot: string): JsonObject | undefined {\n const settingsPath = projectPiSettingsPath(repoRoot);\n if (!fs.existsSync(settingsPath)) return undefined;\n const settings = readJson(settingsPath);\n return isRecord(settings) ? settings : {};\n}\n\nexport function serializeProjectPiSettings(settings: JsonObject): string {\n return `${JSON.stringify(settings, null, 2)}\\n`;\n}\n\n/** True when the repository still registers DoomPi alongside the user scope. */\nexport function projectRegistersDoom(repoRoot: string, homeDirectory: string = os.homedir()): boolean {\n const settings = readProjectPiSettings(repoRoot);\n if (!settings) return false;\n return (\n serializeProjectPiSettings(mergeProjectPiSettings(settings, repoRoot, homeDirectory)) !==\n serializeProjectPiSettings(settings)\n );\n}\n\n/**\n * Removes the alias a repository-scoped registration needed.\n *\n * Only a symbolic link is removed, for the same reason sync refuses to replace\n * one: anything else at that path was not created here.\n */\nfunction removeProjectExtensionAlias(repoRoot: string): void {\n const aliasPath = piExtensionAliasPath(projectPiDirectory(repoRoot));\n if (fs.lstatSync(aliasPath, { throwIfNoEntry: false })?.isSymbolicLink()) {\n fs.rmSync(aliasPath, { force: true });\n }\n}\n\n/**\n * Reconciles the repository's Pi settings with the user-scope registration.\n *\n * The file is never created and never deleted, only rewritten: an absent one\n * means the repository registers nothing, and an existing one is a repository\n * root marker that other DoomPi code walks up to find.\n */\nexport function writeProjectPiSettings(repoRoot: string, homeDirectory: string = os.homedir()): string | undefined {\n const settings = readProjectPiSettings(repoRoot);\n removeProjectExtensionAlias(repoRoot);\n if (!settings) return undefined;\n\n const merged = mergeProjectPiSettings(settings, repoRoot, homeDirectory);\n const serialized = serializeProjectPiSettings(merged);\n if (serialized === serializeProjectPiSettings(settings)) return undefined;\n\n const settingsPath = projectPiSettingsPath(repoRoot);\n writeFileAtomic(settingsPath, serialized);\n return settingsPath;\n}\n"],"mappings":"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"}
|
|
@@ -53,7 +53,7 @@ function resolveSettingsPath(value, baseDirectory, homeDirectory) {
|
|
|
53
53
|
*
|
|
54
54
|
* The bare package name is matched textually because that is the spelling sync
|
|
55
55
|
* writes into the user scope, and a resolved path is matched by walking to the
|
|
56
|
-
* package it belongs to, which is what catches `../packages/
|
|
56
|
+
* package it belongs to, which is what catches `../packages/cli/doompi` and a
|
|
57
57
|
* direct reference to a file inside it. Pattern entries select among already
|
|
58
58
|
* resolved paths rather than naming one, so they are left for the user to own.
|
|
59
59
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projectSettings.mjs","names":[],"sources":["../../../src/builders/cli/projectSettings.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { DOOM_PACKAGE_NAME, isDoomPackagePath } from '@agimon-ai/doompi-core/doom-package';\nimport { isRecord, readJson, writeFileAtomic } from '@agimon-ai/doompi-core/runtime-json';\nimport type { JsonObject } from '@agimon-ai/doompi-core/runtime-json';\n\nimport { piExtensionAliasPath } from './piExtensionAlias';\n\n/**\n * Pi's project settings, as `doompi sync` reconciles them.\n *\n * Pi documents project settings as overriding user settings, but resource\n * resolution unions the two scopes and dedupes only by the resolved file's real\n * path. A repository that registers DoomPi alongside the stable user entry\n * therefore loads two installs of the same package against one home-scoped\n * worktree state. Sync removes the redundant project registration and leaves every\n * other key alone: the repository still overrides the user scope, through its own\n * `.doom` configuration and the runtime staged under `~/.pi/.doom/sync`.\n */\n\nconst PI_DIRECTORY = '.pi';\nconst SETTINGS_FILE = 'settings.json';\nconst EXTENSIONS_KEY = 'extensions';\nconst PACKAGES_KEY = 'packages';\nconst PACKAGE_SOURCE_KEY = 'source';\nconst HOME_ALIAS = '~';\nconst HOME_ALIAS_PREFIX = '~/';\n/** Pattern-form entries Pi filters with rather than resolves. */\nconst PATTERN_PREFIXES = ['!', '+', '-'] as const;\n\n/**\n * Reported by `sync --check` and `build`, and repaired by `sync`.\n *\n * Lives here rather than with either command because both report the same\n * finding about the same file, and the detector below is what decides it.\n */\nexport const DUPLICATE_REGISTRATION_DRIFT = 'duplicate DoomPi registration in .pi/settings.json';\n\n/** Pi's project configuration directory, the base its relative paths resolve against. */\nexport function projectPiDirectory(repoRoot: string): string {\n return path.join(repoRoot, PI_DIRECTORY);\n}\n\nexport function projectPiSettingsPath(repoRoot: string): string {\n return path.join(projectPiDirectory(repoRoot), SETTINGS_FILE);\n}\n\nfunction resolveSettingsPath(value: string, baseDirectory: string, homeDirectory: string): string {\n if (value === HOME_ALIAS) return homeDirectory;\n if (value.startsWith(HOME_ALIAS_PREFIX)) return path.join(homeDirectory, value.slice(HOME_ALIAS_PREFIX.length));\n return path.resolve(baseDirectory, value);\n}\n\n/**\n * Whether one settings entry names this package.\n *\n * The bare package name is matched textually because that is the spelling sync\n * writes into the user scope, and a resolved path is matched by walking to the\n * package it belongs to, which is what catches `../packages/
|
|
1
|
+
{"version":3,"file":"projectSettings.mjs","names":[],"sources":["../../../src/builders/cli/projectSettings.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { DOOM_PACKAGE_NAME, isDoomPackagePath } from '@agimon-ai/doompi-core/doom-package';\nimport { isRecord, readJson, writeFileAtomic } from '@agimon-ai/doompi-core/runtime-json';\nimport type { JsonObject } from '@agimon-ai/doompi-core/runtime-json';\n\nimport { piExtensionAliasPath } from './piExtensionAlias';\n\n/**\n * Pi's project settings, as `doompi sync` reconciles them.\n *\n * Pi documents project settings as overriding user settings, but resource\n * resolution unions the two scopes and dedupes only by the resolved file's real\n * path. A repository that registers DoomPi alongside the stable user entry\n * therefore loads two installs of the same package against one home-scoped\n * worktree state. Sync removes the redundant project registration and leaves every\n * other key alone: the repository still overrides the user scope, through its own\n * `.doom` configuration and the runtime staged under `~/.pi/.doom/sync`.\n */\n\nconst PI_DIRECTORY = '.pi';\nconst SETTINGS_FILE = 'settings.json';\nconst EXTENSIONS_KEY = 'extensions';\nconst PACKAGES_KEY = 'packages';\nconst PACKAGE_SOURCE_KEY = 'source';\nconst HOME_ALIAS = '~';\nconst HOME_ALIAS_PREFIX = '~/';\n/** Pattern-form entries Pi filters with rather than resolves. */\nconst PATTERN_PREFIXES = ['!', '+', '-'] as const;\n\n/**\n * Reported by `sync --check` and `build`, and repaired by `sync`.\n *\n * Lives here rather than with either command because both report the same\n * finding about the same file, and the detector below is what decides it.\n */\nexport const DUPLICATE_REGISTRATION_DRIFT = 'duplicate DoomPi registration in .pi/settings.json';\n\n/** Pi's project configuration directory, the base its relative paths resolve against. */\nexport function projectPiDirectory(repoRoot: string): string {\n return path.join(repoRoot, PI_DIRECTORY);\n}\n\nexport function projectPiSettingsPath(repoRoot: string): string {\n return path.join(projectPiDirectory(repoRoot), SETTINGS_FILE);\n}\n\nfunction resolveSettingsPath(value: string, baseDirectory: string, homeDirectory: string): string {\n if (value === HOME_ALIAS) return homeDirectory;\n if (value.startsWith(HOME_ALIAS_PREFIX)) return path.join(homeDirectory, value.slice(HOME_ALIAS_PREFIX.length));\n return path.resolve(baseDirectory, value);\n}\n\n/**\n * Whether one settings entry names this package.\n *\n * The bare package name is matched textually because that is the spelling sync\n * writes into the user scope, and a resolved path is matched by walking to the\n * package it belongs to, which is what catches `../packages/cli/doompi` and a\n * direct reference to a file inside it. Pattern entries select among already\n * resolved paths rather than naming one, so they are left for the user to own.\n */\nfunction isDoomEntry(value: string, baseDirectory: string, homeDirectory: string): boolean {\n const normalized = value.replaceAll('\\\\', '/').trim();\n if (normalized === DOOM_PACKAGE_NAME) return true;\n if (PATTERN_PREFIXES.some((prefix) => normalized.startsWith(prefix))) return false;\n return isDoomPackagePath(resolveSettingsPath(normalized, baseDirectory, homeDirectory));\n}\n\nfunction packageSource(entry: unknown): string | undefined {\n if (typeof entry === 'string') return entry;\n if (isRecord(entry) && typeof entry[PACKAGE_SOURCE_KEY] === 'string') return entry[PACKAGE_SOURCE_KEY];\n return undefined;\n}\n\n/**\n * Drops DoomPi from one resource list, or reports that the key should go.\n *\n * A list that empties out is removed rather than left behind: an empty array is\n * indistinguishable from an absent one to Pi, and keeping it would leave the\n * repository looking like it still configures something.\n */\nfunction withoutDoomEntries(value: unknown, baseDirectory: string, homeDirectory: string): unknown {\n if (!Array.isArray(value)) return value;\n const kept = value.filter((entry) => {\n const source = packageSource(entry);\n return source === undefined || !isDoomEntry(source, baseDirectory, homeDirectory);\n });\n if (kept.length === value.length) return value;\n return kept.length > 0 ? kept : undefined;\n}\n\n/** Produces the project settings content for a reconciliation, without writing it. */\nexport function mergeProjectPiSettings(\n current: JsonObject,\n repoRoot: string,\n homeDirectory: string = os.homedir(),\n): JsonObject {\n const baseDirectory = projectPiDirectory(repoRoot);\n const merged: JsonObject = { ...current };\n for (const key of [EXTENSIONS_KEY, PACKAGES_KEY]) {\n if (!(key in merged)) continue;\n const next = withoutDoomEntries(merged[key], baseDirectory, homeDirectory);\n if (next === undefined) delete merged[key];\n else merged[key] = next;\n }\n return merged;\n}\n\n/** Reads Pi's project settings, or nothing when the repository declares none. */\nexport function readProjectPiSettings(repoRoot: string): JsonObject | undefined {\n const settingsPath = projectPiSettingsPath(repoRoot);\n if (!fs.existsSync(settingsPath)) return undefined;\n const settings = readJson(settingsPath);\n return isRecord(settings) ? settings : {};\n}\n\nexport function serializeProjectPiSettings(settings: JsonObject): string {\n return `${JSON.stringify(settings, null, 2)}\\n`;\n}\n\n/** True when the repository still registers DoomPi alongside the user scope. */\nexport function projectRegistersDoom(repoRoot: string, homeDirectory: string = os.homedir()): boolean {\n const settings = readProjectPiSettings(repoRoot);\n if (!settings) return false;\n return (\n serializeProjectPiSettings(mergeProjectPiSettings(settings, repoRoot, homeDirectory)) !==\n serializeProjectPiSettings(settings)\n );\n}\n\n/**\n * Removes the alias a repository-scoped registration needed.\n *\n * Only a symbolic link is removed, for the same reason sync refuses to replace\n * one: anything else at that path was not created here.\n */\nfunction removeProjectExtensionAlias(repoRoot: string): void {\n const aliasPath = piExtensionAliasPath(projectPiDirectory(repoRoot));\n if (fs.lstatSync(aliasPath, { throwIfNoEntry: false })?.isSymbolicLink()) {\n fs.rmSync(aliasPath, { force: true });\n }\n}\n\n/**\n * Reconciles the repository's Pi settings with the user-scope registration.\n *\n * The file is never created and never deleted, only rewritten: an absent one\n * means the repository registers nothing, and an existing one is a repository\n * root marker that other DoomPi code walks up to find.\n */\nexport function writeProjectPiSettings(repoRoot: string, homeDirectory: string = os.homedir()): string | undefined {\n const settings = readProjectPiSettings(repoRoot);\n removeProjectExtensionAlias(repoRoot);\n if (!settings) return undefined;\n\n const merged = mergeProjectPiSettings(settings, repoRoot, homeDirectory);\n const serialized = serializeProjectPiSettings(merged);\n if (serialized === serializeProjectPiSettings(settings)) return undefined;\n\n const settingsPath = projectPiSettingsPath(repoRoot);\n writeFileAtomic(settingsPath, serialized);\n return settingsPath;\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
const require_runtime = require("../../_virtual/_rolldown/runtime.cjs");
|
|
2
2
|
require("../../composition/harnessState/index.cjs");
|
|
3
3
|
const require_index$1 = require("../../composition/repository/index.cjs");
|
|
4
|
+
const require_index$2 = require("../../composition/syncState/index.cjs");
|
|
4
5
|
const require_harnessContext = require("../cli/harnessContext.cjs");
|
|
6
|
+
const require_index$3 = require("../../composition/syncDrift/index.cjs");
|
|
5
7
|
const require_selectionStatus = require("./selectionStatus.cjs");
|
|
6
8
|
const require_sessionArguments = require("./sessionArguments.cjs");
|
|
7
9
|
let node_fs = require("node:fs");
|
|
@@ -53,6 +55,10 @@ async function runServerRuntime(options, runtime) {
|
|
|
53
55
|
env: baseEnvironment,
|
|
54
56
|
warn: notice
|
|
55
57
|
});
|
|
58
|
+
const openSessions = (0, _agimon_ai_doompi_core_history.createOpenSessionRegistry)({
|
|
59
|
+
directory: node_path.default.join((0, _agimon_ai_doompi_core_pi_settings.piAgentDirectory)(baseEnvironment), "server"),
|
|
60
|
+
onNotice: notice
|
|
61
|
+
});
|
|
56
62
|
let nextEventLoopTick = performance.now() + 1e3;
|
|
57
63
|
const eventLoopMonitor = setInterval(() => {
|
|
58
64
|
const now = performance.now();
|
|
@@ -78,7 +84,14 @@ async function runServerRuntime(options, runtime) {
|
|
|
78
84
|
const pendingSessions = /* @__PURE__ */ new Map();
|
|
79
85
|
const sessionArtifacts = /* @__PURE__ */ new Map();
|
|
80
86
|
let mountSessionApis;
|
|
87
|
+
/**
|
|
88
|
+
* Shutdown runs every session through the same close path a user-initiated
|
|
89
|
+
* close uses, so without this the registry would be emptied by the very event
|
|
90
|
+
* it exists to survive.
|
|
91
|
+
*/
|
|
92
|
+
let shuttingDown = false;
|
|
81
93
|
const closeManagedSession = async (sessionId) => {
|
|
94
|
+
if (!shuttingDown) openSessions.remove(sessionId);
|
|
82
95
|
const artifacts = sessionArtifacts.get(sessionId);
|
|
83
96
|
sessionArtifacts.delete(sessionId);
|
|
84
97
|
webCompositions?.remove({
|
|
@@ -155,6 +168,7 @@ async function runServerRuntime(options, runtime) {
|
|
|
155
168
|
}),
|
|
156
169
|
createSession: (request) => openSession(request),
|
|
157
170
|
onNotice: notice,
|
|
171
|
+
hubToken: () => attachToken,
|
|
158
172
|
requestSessionApi: (scope, request) => requestSessionApi(scope, request)
|
|
159
173
|
});
|
|
160
174
|
let harnessContext;
|
|
@@ -207,6 +221,20 @@ async function runServerRuntime(options, runtime) {
|
|
|
207
221
|
active: hub.snapshot().some((session) => session.workspaceId === workspace.id)
|
|
208
222
|
})),
|
|
209
223
|
resolveRepository: (id) => hub.workspaces().find((workspace) => workspace.id === id)?.root,
|
|
224
|
+
readRepositorySync: (id) => {
|
|
225
|
+
const root = hub.workspaces().find((workspace) => workspace.id === id)?.root;
|
|
226
|
+
if (!root) return void 0;
|
|
227
|
+
const drift = require_index$3.readSyncDrift({
|
|
228
|
+
repoRoot: root,
|
|
229
|
+
homeDirectory,
|
|
230
|
+
requireWebBundle: true
|
|
231
|
+
});
|
|
232
|
+
const state = require_index$2.readSyncState(root, homeDirectory);
|
|
233
|
+
return {
|
|
234
|
+
...drift,
|
|
235
|
+
mcpProjection: state?.fileState.mcpProjection
|
|
236
|
+
};
|
|
237
|
+
},
|
|
210
238
|
onNotice: notice
|
|
211
239
|
};
|
|
212
240
|
const globalBundle = await loadComposition(globalRoot, "global");
|
|
@@ -223,9 +251,9 @@ async function runServerRuntime(options, runtime) {
|
|
|
223
251
|
headers.set("x-doompi-token", attachToken);
|
|
224
252
|
return fetch(new URL(`${from.pathname}${from.search}`, cockpit.url), new Request(request, { headers }));
|
|
225
253
|
},
|
|
226
|
-
connectProtocol: () => {
|
|
254
|
+
connectProtocol: (pathname) => {
|
|
227
255
|
if (!cockpit || !attachToken) throw new Error("The headless protocol listener is not ready.");
|
|
228
|
-
const url = new URL(
|
|
256
|
+
const url = new URL(pathname, cockpit.url);
|
|
229
257
|
url.protocol = "ws:";
|
|
230
258
|
return new ws.default(url, { headers: { "x-doompi-token": attachToken } });
|
|
231
259
|
}
|
|
@@ -245,6 +273,12 @@ async function runServerRuntime(options, runtime) {
|
|
|
245
273
|
const pending = admissions.get(id);
|
|
246
274
|
if (pending) return pending;
|
|
247
275
|
const admission = (async () => {
|
|
276
|
+
const syncEnvironment = {
|
|
277
|
+
...baseEnvironment,
|
|
278
|
+
DOOMPI_ROOT: root
|
|
279
|
+
};
|
|
280
|
+
for (const key of [_agimon_ai_doompi_config_harnessStore.HARNESS_STATE_POINTER, ...Object.values(_agimon_ai_doompi_config_harnessState.HARNESS_STATE_KEYS)]) delete syncEnvironment[key];
|
|
281
|
+
await syncWorkspace(root, syncEnvironment);
|
|
248
282
|
const bundle = await loadComposition(root, "workspace");
|
|
249
283
|
await hub.mountFacets(bundle.facets, {
|
|
250
284
|
...sharedApiContext,
|
|
@@ -335,6 +369,7 @@ async function runServerRuntime(options, runtime) {
|
|
|
335
369
|
directEvents: hub.directEvents,
|
|
336
370
|
hubToken: token,
|
|
337
371
|
sessionService: hub.sessionService,
|
|
372
|
+
pluginRegistry: hub.pluginRegistry,
|
|
338
373
|
apis: [],
|
|
339
374
|
facets: pendingSessions.get(sessionOptions.sessionId)?.bundle.facets ?? [],
|
|
340
375
|
workspaceId: sessionOptions.workspaceId,
|
|
@@ -362,7 +397,7 @@ async function runServerRuntime(options, runtime) {
|
|
|
362
397
|
if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(request.basePath)) return Response.json({ error: "Invalid session API base path." }, { status: 400 });
|
|
363
398
|
if (!request.path.startsWith("/") || request.path.startsWith("//") || request.path.includes("#")) return Response.json({ error: "Invalid session API path." }, { status: 400 });
|
|
364
399
|
const body = request.body === null || request.body === void 0 ? void 0 : request.body;
|
|
365
|
-
return artifacts.apis.request(new Request(`http://doompi.local/api/
|
|
400
|
+
return artifacts.apis.request(new Request(`http://doompi.local/api/plugins/${request.basePath}${request.path}`, {
|
|
366
401
|
method: request.method,
|
|
367
402
|
headers: request.headers,
|
|
368
403
|
...body === void 0 ? {} : { body },
|
|
@@ -425,7 +460,16 @@ async function runServerRuntime(options, runtime) {
|
|
|
425
460
|
cleanup: () => childContext.cleanup(),
|
|
426
461
|
bundle
|
|
427
462
|
});
|
|
428
|
-
await hub.create(sessionHostOptions(childContext, bundle, identity));
|
|
463
|
+
const created = await hub.create(sessionHostOptions(childContext, bundle, identity));
|
|
464
|
+
if (created.workspaceId !== void 0) openSessions.add({
|
|
465
|
+
sessionId: created.id,
|
|
466
|
+
workspaceId: created.workspaceId,
|
|
467
|
+
cwd: created.cwd,
|
|
468
|
+
name: created.name,
|
|
469
|
+
createdAt: created.createdAt,
|
|
470
|
+
...created.parentSessionId === void 0 ? {} : { parentSessionId: created.parentSessionId },
|
|
471
|
+
...created.sessionProvenance === void 0 ? {} : { sessionProvenance: created.sessionProvenance }
|
|
472
|
+
});
|
|
429
473
|
return {
|
|
430
474
|
sessionId: identity.sessionId,
|
|
431
475
|
cwd: childContext.options.cwd
|
|
@@ -478,6 +522,16 @@ async function runServerRuntime(options, runtime) {
|
|
|
478
522
|
}, target.id);
|
|
479
523
|
return target.id;
|
|
480
524
|
},
|
|
525
|
+
dormantSessions: () => openSessions.list(),
|
|
526
|
+
removeDormantSession: (record) => openSessions.remove(record.sessionId),
|
|
527
|
+
reviveSession: async (record) => {
|
|
528
|
+
await openSession({
|
|
529
|
+
cwd: record.cwd,
|
|
530
|
+
name: record.name,
|
|
531
|
+
...record.parentSessionId === void 0 ? {} : { parentSessionId: record.parentSessionId },
|
|
532
|
+
...record.sessionProvenance === void 0 ? {} : { sessionProvenance: record.sessionProvenance }
|
|
533
|
+
}, record.sessionId);
|
|
534
|
+
},
|
|
481
535
|
requestAsset: (request) => webCompositions?.request(request) ?? Promise.resolve(void 0),
|
|
482
536
|
compositions: () => ({
|
|
483
537
|
global: webCompositions?.get({ scope: "global" }),
|
|
@@ -494,7 +548,7 @@ async function runServerRuntime(options, runtime) {
|
|
|
494
548
|
telemetry,
|
|
495
549
|
onNotice: notice
|
|
496
550
|
});
|
|
497
|
-
notice(`protocol on ${cockpit.url}/api/
|
|
551
|
+
notice(`protocol on ${cockpit.url}/api/ws`);
|
|
498
552
|
let resolveShutdown;
|
|
499
553
|
const shutdown = new Promise((resolve) => {
|
|
500
554
|
resolveShutdown = resolve;
|
|
@@ -508,6 +562,7 @@ async function runServerRuntime(options, runtime) {
|
|
|
508
562
|
signal.removeEventListener("abort", stop);
|
|
509
563
|
}
|
|
510
564
|
} finally {
|
|
565
|
+
shuttingDown = true;
|
|
511
566
|
clearInterval(eventLoopMonitor);
|
|
512
567
|
await bounded(telemetry.recordEvent("doompi_server.shutdown"), "shutdown telemetry", notice);
|
|
513
568
|
await Promise.allSettled([cockpit?.close()]);
|