@agimon-ai/doompi-web 0.0.1-alpha.10 → 0.0.1-alpha.13
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/src/adapters/bundledServer.cjs +1 -1
- package/dist/src/adapters/bundledServer.cjs.map +1 -1
- package/dist/src/adapters/bundledServer.mjs +1 -1
- package/dist/src/adapters/bundledServer.mjs.map +1 -1
- package/dist/src/adapters/httpServer.cjs +2 -2
- package/dist/src/adapters/httpServer.cjs.map +1 -1
- package/dist/src/adapters/httpServer.d.cts.map +1 -1
- package/dist/src/adapters/httpServer.d.mts.map +1 -1
- package/dist/src/adapters/httpServer.mjs +2 -2
- package/dist/src/adapters/httpServer.mjs.map +1 -1
- package/dist/src/adapters/promptImages.cjs +3 -0
- package/dist/src/adapters/promptImages.cjs.map +1 -0
- package/dist/src/adapters/promptImages.mjs +3 -0
- package/dist/src/adapters/promptImages.mjs.map +1 -0
- package/dist/src/adapters/sessionHub.cjs +1 -1
- package/dist/src/adapters/sessionHub.cjs.map +1 -1
- package/dist/src/adapters/sessionHub.mjs +1 -1
- package/dist/src/adapters/sessionHub.mjs.map +1 -1
- package/dist/src/adapters/settingsRoutes.cjs +1 -1
- package/dist/src/adapters/settingsRoutes.cjs.map +1 -1
- package/dist/src/adapters/settingsRoutes.mjs +1 -1
- package/dist/src/adapters/settingsRoutes.mjs.map +1 -1
- package/dist/src/adapters/syncGuard.cjs +1 -1
- package/dist/src/adapters/syncGuard.cjs.map +1 -1
- package/dist/src/adapters/syncGuard.mjs +1 -1
- package/dist/src/adapters/syncGuard.mjs.map +1 -1
- package/dist/src/adapters/threadJournals.cjs +1 -1
- package/dist/src/adapters/threadJournals.cjs.map +1 -1
- package/dist/src/adapters/threadJournals.mjs +1 -1
- package/dist/src/adapters/threadJournals.mjs.map +1 -1
- package/dist/src/adapters/webPluginGenerate.cjs +1 -1
- package/dist/src/adapters/webPluginGenerate.cjs.map +1 -1
- package/dist/src/adapters/webPluginGenerate.mjs +1 -1
- package/dist/src/adapters/webPluginGenerate.mjs.map +1 -1
- package/dist/src/adapters/webTelemetry.cjs +1 -1
- package/dist/src/adapters/webTelemetry.cjs.map +1 -1
- package/dist/src/adapters/webTelemetry.mjs +1 -1
- package/dist/src/adapters/webTelemetry.mjs.map +1 -1
- package/dist/src/types/settings.cjs +1 -1
- package/dist/src/types/settings.cjs.map +1 -1
- package/dist/src/types/settings.mjs +1 -1
- package/dist/src/types/settings.mjs.map +1 -1
- package/dist/web/assets/{CodeEditorView-Drbj5Jmq.js → CodeEditorView-Cv2_Pqe1.js} +3 -3
- package/dist/web/assets/{CodeEditorView-Drbj5Jmq.js.map → CodeEditorView-Cv2_Pqe1.js.map} +1 -1
- package/dist/web/assets/{dist-7iNxebeg.js → dist-1N2cDSZc.js} +2 -2
- package/dist/web/assets/{dist-7iNxebeg.js.map → dist-1N2cDSZc.js.map} +1 -1
- package/dist/web/assets/{dist-CyY9Rnb9.js → dist-BvJltgg9.js} +2 -2
- package/dist/web/assets/{dist-CyY9Rnb9.js.map → dist-BvJltgg9.js.map} +1 -1
- package/dist/web/assets/{dist-BCRyBbdR.js → dist-CMcoiIPx.js} +2 -2
- package/dist/web/assets/{dist-BCRyBbdR.js.map → dist-CMcoiIPx.js.map} +1 -1
- package/dist/web/assets/{dist-CM-9xFgG.js → dist-CSIKLWGg.js} +2 -2
- package/dist/web/assets/{dist-CM-9xFgG.js.map → dist-CSIKLWGg.js.map} +1 -1
- package/dist/web/assets/{dist-C2KhFiEs.js → dist-CVA2128k.js} +1 -1
- package/dist/web/assets/{dist-ClQi4oJ4.js → dist-CfQNkp46.js} +2 -2
- package/dist/web/assets/{dist-ClQi4oJ4.js.map → dist-CfQNkp46.js.map} +1 -1
- package/dist/web/assets/{dist-D_AIHDMX.js → dist-Dc0vEh5H.js} +2 -2
- package/dist/web/assets/{dist-D_AIHDMX.js.map → dist-Dc0vEh5H.js.map} +1 -1
- package/dist/web/assets/{dist-DjFwY_To.js → dist-DlhyAlgc.js} +2 -2
- package/dist/web/assets/{dist-DjFwY_To.js.map → dist-DlhyAlgc.js.map} +1 -1
- package/dist/web/assets/{dist-joNLZ1a7.js → dist-Dusr83oZ.js} +2 -2
- package/dist/web/assets/{dist-joNLZ1a7.js.map → dist-Dusr83oZ.js.map} +1 -1
- package/dist/web/assets/{dist-BiFP6LQz.js → dist-x765ui50.js} +2 -2
- package/dist/web/assets/{dist-BiFP6LQz.js.map → dist-x765ui50.js.map} +1 -1
- package/dist/web/assets/index-CdO0WgVA.js +101 -0
- package/dist/web/assets/index-CdO0WgVA.js.map +1 -0
- package/dist/web/assets/index-_BcdH66j.css +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +10 -10
- package/src/adapters/bundledServer.ts +13 -6
- package/src/adapters/httpServer.ts +47 -5
- package/src/adapters/promptImages.ts +80 -0
- package/src/adapters/sessionHub.ts +46 -3
- package/src/adapters/settingsRoutes.ts +51 -0
- package/src/adapters/syncGuard.ts +4 -0
- package/src/adapters/threadJournals.ts +69 -9
- package/src/adapters/webPluginGenerate.ts +3 -0
- package/src/adapters/webTelemetry.ts +109 -24
- package/src/types/settings.ts +21 -0
- package/src/web/features/activity/ActivityDock.tsx +16 -10
- package/src/web/features/activity/ContextItemDialog.tsx +204 -0
- package/src/web/features/activity/ContextPanel.tsx +27 -6
- package/src/web/features/leader/CommandPalette.tsx +11 -3
- package/src/web/features/sessions/SessionRail.tsx +4 -1
- package/src/web/features/settings/AppearanceSettings.tsx +6 -8
- package/src/web/features/settings/ContributedSettings.tsx +35 -22
- package/src/web/features/settings/ImageSettings.tsx +112 -0
- package/src/web/features/settings/NotificationSettings.tsx +6 -8
- package/src/web/features/settings/PluginSettings.tsx +9 -2
- package/src/web/features/settings/ProviderSettings.tsx +15 -13
- package/src/web/features/settings/RemoteControlSettings.tsx +6 -8
- package/src/web/features/settings/RepositorySettings.tsx +9 -10
- package/src/web/features/settings/RepositoryWorkspace.tsx +21 -18
- package/src/web/features/settings/SettingsMenu.tsx +6 -3
- package/src/web/features/settings/SettingsPanelHost.tsx +2 -4
- package/src/web/features/settings/SettingsSectionHeader.tsx +34 -0
- package/src/web/features/settings/TunnelSettings.tsx +8 -8
- package/src/web/lib/browserTelemetry.ts +112 -7
- package/src/web/lib/composition.ts +3 -3
- package/src/web/lib/contextDetailApi.ts +55 -0
- package/src/web/lib/settingsApi.ts +37 -0
- package/src/web/lib/settingsSections.ts +6 -0
- package/src/web/main.tsx +4 -0
- package/src/web/routes/SettingsPage.tsx +15 -9
- package/dist/web/assets/index-BuzTi9Gb.css +0 -2
- package/dist/web/assets/index-Ek5bNpFT.js +0 -101
- package/dist/web/assets/index-Ek5bNpFT.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);let r=require("node:url"),i=require("node:module");const a=`DOOMPI_AGENT_COMMAND`,o=`DOOMPI_WEB_MODULE`,s=[`node_modules`,`@agimon-ai`,`doompi`,`dist`,`bin`,`cli.mjs`];function c(e,t,r){let a=(0,i.createRequire)(r).resolve(`${e}/package.json`);return n.default.join(n.default.dirname(a),...t)}function l(e,r=t.default.existsSync){for(let t=n.default.resolve(e);;t=n.default.dirname(t)){let e=n.default.join(t,...s);if(r(e))return e;if(t===n.default.dirname(t))return}}function u(e,n,i=require("url").pathToFileURL(__filename).href,s=t.default.existsSync){let u=c(`@agimon-ai/doompi-server`,[`dist`,`bin`,`serve.mjs`],i),d=c(`@agimon-ai/doompi`,[`dist`,`bin`,`cli.mjs`],i),
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);let r=require("node:url"),i=require("node:module");const a=`DOOMPI_AGENT_COMMAND`,o=`DOOMPI_WEB_MODULE`,s=[`node_modules`,`@agimon-ai`,`doompi`,`dist`,`bin`,`cli.mjs`];function c(e,t,r){let a=(0,i.createRequire)(r).resolve(`${e}/package.json`);return n.default.join(n.default.dirname(a),...t)}function l(e,r=t.default.existsSync){for(let t=n.default.resolve(e);;t=n.default.dirname(t)){let e=n.default.join(t,...s);if(r(e))return e;if(t===n.default.dirname(t))return}}function u(e,n,i=require("url").pathToFileURL(__filename).href,s=t.default.existsSync){let u=n.DOOMPI_SERVER_COMMAND||c(`@agimon-ai/doompi-server`,[`dist`,`bin`,`serve.mjs`],i),d=n[a],f=l(e,s),p=d||f?{...n}:{...n,[a]:c(`@agimon-ai/doompi`,[`dist`,`bin`,`cli.mjs`],i)},m=n[o]||(0,r.pathToFileURL)(c(`@agimon-ai/doompi-web`,[`dist`,`index.mjs`],i)).href;return{command:process.execPath,args:[u],environment:{...p,[o]:m}}}exports.defaultServerLaunch=u,exports.repositoryDoomPiCli=l;
|
|
2
2
|
//# sourceMappingURL=bundledServer.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundledServer.cjs","names":["createRequire","path","fs","pathToFileURL"],"sources":["../../../src/adapters/bundledServer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nconst AGENT_COMMAND_ENV = 'DOOMPI_AGENT_COMMAND';\nconst WEB_MODULE_ENV = 'DOOMPI_WEB_MODULE';\nconst REPOSITORY_CLI = ['node_modules', '@agimon-ai', 'doompi', 'dist', 'bin', 'cli.mjs'];\n\nexport interface BundledServerLaunch {\n readonly command: string;\n /** Leading arguments the command needs; a caller appends its own after these. */\n readonly args: readonly string[];\n readonly environment: NodeJS.ProcessEnv;\n}\n\ntype FileExists = (file: string) => boolean;\n\nfunction packageFile(packageName: string, segments: readonly string[], parentUrl: string): string {\n const manifest = createRequire(parentUrl).resolve(`${packageName}/package.json`);\n return path.join(path.dirname(manifest), ...segments);\n}\n\n/** Finds the DoomPi CLI pinned by the repository containing the requested session. */\nexport function repositoryDoomPiCli(cwd: string, exists: FileExists = fs.existsSync): string | undefined {\n for (let directory = path.resolve(cwd); ; directory = path.dirname(directory)) {\n const cli = path.join(directory, ...REPOSITORY_CLI);\n if (exists(cli)) return cli;\n if (directory === path.dirname(directory)) return undefined;\n }\n}\n\n/**\n * Resolves the Server and fallback agent shipped by a DoomPi Web installation.\n *\n * This is what makes `doompi-web` need no flags: the hub launches the\n * doompi-server sitting in its own dependency tree rather than whichever one\n * PATH happens to name, and points it at the agent from the same install, so a\n * checkout runs its own build. A repository that pins its own DoomPi still\n * wins, since a session created there should run that repository's agent.\n */\nexport function defaultServerLaunch(\n cwd: string,\n environment: NodeJS.ProcessEnv,\n parentUrl: string = import.meta.url,\n exists: FileExists = fs.existsSync,\n): BundledServerLaunch {\n const server = packageFile('@agimon-ai/doompi-server', ['dist', 'bin', 'serve.mjs'], parentUrl);\n const
|
|
1
|
+
{"version":3,"file":"bundledServer.cjs","names":["createRequire","path","fs","pathToFileURL"],"sources":["../../../src/adapters/bundledServer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nconst AGENT_COMMAND_ENV = 'DOOMPI_AGENT_COMMAND';\nconst SERVER_COMMAND_ENV = 'DOOMPI_SERVER_COMMAND';\nconst WEB_MODULE_ENV = 'DOOMPI_WEB_MODULE';\nconst REPOSITORY_CLI = ['node_modules', '@agimon-ai', 'doompi', 'dist', 'bin', 'cli.mjs'];\n\nexport interface BundledServerLaunch {\n readonly command: string;\n /** Leading arguments the command needs; a caller appends its own after these. */\n readonly args: readonly string[];\n readonly environment: NodeJS.ProcessEnv;\n}\n\ntype FileExists = (file: string) => boolean;\n\nfunction packageFile(packageName: string, segments: readonly string[], parentUrl: string): string {\n const manifest = createRequire(parentUrl).resolve(`${packageName}/package.json`);\n return path.join(path.dirname(manifest), ...segments);\n}\n\n/** Finds the DoomPi CLI pinned by the repository containing the requested session. */\nexport function repositoryDoomPiCli(cwd: string, exists: FileExists = fs.existsSync): string | undefined {\n for (let directory = path.resolve(cwd); ; directory = path.dirname(directory)) {\n const cli = path.join(directory, ...REPOSITORY_CLI);\n if (exists(cli)) return cli;\n if (directory === path.dirname(directory)) return undefined;\n }\n}\n\n/**\n * Resolves the Server and fallback agent shipped by a DoomPi Web installation.\n *\n * This is what makes `doompi-web` need no flags: the hub launches the\n * doompi-server sitting in its own dependency tree rather than whichever one\n * PATH happens to name, and points it at the agent from the same install, so a\n * checkout runs its own build. A repository that pins its own DoomPi still\n * wins, since a session created there should run that repository's agent.\n */\nexport function defaultServerLaunch(\n cwd: string,\n environment: NodeJS.ProcessEnv,\n parentUrl: string = import.meta.url,\n exists: FileExists = fs.existsSync,\n): BundledServerLaunch {\n const configuredServer = environment[SERVER_COMMAND_ENV];\n const server = configuredServer || packageFile('@agimon-ai/doompi-server', ['dist', 'bin', 'serve.mjs'], parentUrl);\n const configuredAgent = environment[AGENT_COMMAND_ENV];\n const localAgent = repositoryDoomPiCli(cwd, exists);\n const agentEnvironment =\n configuredAgent || localAgent\n ? { ...environment }\n : {\n ...environment,\n [AGENT_COMMAND_ENV]: packageFile('@agimon-ai/doompi', ['dist', 'bin', 'cli.mjs'], parentUrl),\n };\n const webModule =\n environment[WEB_MODULE_ENV] ||\n pathToFileURL(packageFile('@agimon-ai/doompi-web', ['dist', 'index.mjs'], parentUrl)).href;\n return {\n command: process.execPath,\n args: [server],\n environment: {\n ...agentEnvironment,\n [WEB_MODULE_ENV]: webModule,\n },\n };\n}\n"],"mappings":"iMAKA,MAAM,EAAoB,uBAEpB,EAAiB,oBACjB,EAAiB,CAAC,eAAgB,aAAc,SAAU,OAAQ,MAAO,SAAS,EAWxF,SAAS,EAAY,EAAqB,EAA6B,EAA2B,CAChG,IAAM,GAAA,EAAWA,EAAAA,cAAAA,CAAc,CAAS,CAAC,CAAC,QAAQ,GAAG,EAAY,cAAc,EAC/E,OAAOC,EAAAA,QAAK,KAAKA,EAAAA,QAAK,QAAQ,CAAQ,EAAG,GAAG,CAAQ,CACtD,CAGA,SAAgB,EAAoB,EAAa,EAAqBC,EAAAA,QAAG,WAAgC,CACvG,IAAK,IAAI,EAAYD,EAAAA,QAAK,QAAQ,CAAG,GAAK,EAAYA,EAAAA,QAAK,QAAQ,CAAS,EAAG,CAC7E,IAAM,EAAMA,EAAAA,QAAK,KAAK,EAAW,GAAG,CAAc,EAClD,GAAI,EAAO,CAAG,EAAG,OAAO,EACxB,GAAI,IAAcA,EAAAA,QAAK,QAAQ,CAAS,EAAG,MAC7C,CACF,CAWA,SAAgB,EACd,EACA,EACA,EAAA,QAAA,KAAA,CAAA,CAAA,cAAA,UAAA,CAAA,CAAA,KACA,EAAqBC,EAAAA,QAAG,WACH,CAErB,IAAM,EADmB,EAAY,uBACF,EAAY,2BAA4B,CAAC,OAAQ,MAAO,WAAW,EAAG,CAAS,EAC5G,EAAkB,EAAY,GAC9B,EAAa,EAAoB,EAAK,CAAM,EAC5C,EACJ,GAAmB,EACf,CAAE,GAAG,CAAY,EACjB,CACE,GAAG,GACF,GAAoB,EAAY,oBAAqB,CAAC,OAAQ,MAAO,SAAS,EAAG,CAAS,CAC7F,EACA,EACJ,EAAY,KAAA,EACZC,EAAAA,cAAAA,CAAc,EAAY,wBAAyB,CAAC,OAAQ,WAAW,EAAG,CAAS,CAAC,CAAC,CAAC,KACxF,MAAO,CACL,QAAS,QAAQ,SACjB,KAAM,CAAC,CAAM,EACb,YAAa,CACX,GAAG,GACF,GAAiB,CACpB,CACF,CACF"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createRequire as e}from"node:module";import t from"node:fs";import n from"node:path";import{pathToFileURL as r}from"node:url";const i=`DOOMPI_AGENT_COMMAND`,a=`DOOMPI_WEB_MODULE`,o=[`node_modules`,`@agimon-ai`,`doompi`,`dist`,`bin`,`cli.mjs`];function s(t,r,i){let a=e(i).resolve(`${t}/package.json`);return n.join(n.dirname(a),...r)}function c(e,r=t.existsSync){for(let t=n.resolve(e);;t=n.dirname(t)){let e=n.join(t,...o);if(r(e))return e;if(t===n.dirname(t))return}}function l(e,n,o=import.meta.url,l=t.existsSync){let u=s(`@agimon-ai/doompi-server`,[`dist`,`bin`,`serve.mjs`],o),d=s(`@agimon-ai/doompi`,[`dist`,`bin`,`cli.mjs`],o),
|
|
1
|
+
import{createRequire as e}from"node:module";import t from"node:fs";import n from"node:path";import{pathToFileURL as r}from"node:url";const i=`DOOMPI_AGENT_COMMAND`,a=`DOOMPI_WEB_MODULE`,o=[`node_modules`,`@agimon-ai`,`doompi`,`dist`,`bin`,`cli.mjs`];function s(t,r,i){let a=e(i).resolve(`${t}/package.json`);return n.join(n.dirname(a),...r)}function c(e,r=t.existsSync){for(let t=n.resolve(e);;t=n.dirname(t)){let e=n.join(t,...o);if(r(e))return e;if(t===n.dirname(t))return}}function l(e,n,o=import.meta.url,l=t.existsSync){let u=n.DOOMPI_SERVER_COMMAND||s(`@agimon-ai/doompi-server`,[`dist`,`bin`,`serve.mjs`],o),d=n[i],f=c(e,l),p=d||f?{...n}:{...n,[i]:s(`@agimon-ai/doompi`,[`dist`,`bin`,`cli.mjs`],o)},m=n[a]||r(s(`@agimon-ai/doompi-web`,[`dist`,`index.mjs`],o)).href;return{command:process.execPath,args:[u],environment:{...p,[a]:m}}}export{l as defaultServerLaunch,c as repositoryDoomPiCli};
|
|
2
2
|
//# sourceMappingURL=bundledServer.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundledServer.mjs","names":[],"sources":["../../../src/adapters/bundledServer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nconst AGENT_COMMAND_ENV = 'DOOMPI_AGENT_COMMAND';\nconst WEB_MODULE_ENV = 'DOOMPI_WEB_MODULE';\nconst REPOSITORY_CLI = ['node_modules', '@agimon-ai', 'doompi', 'dist', 'bin', 'cli.mjs'];\n\nexport interface BundledServerLaunch {\n readonly command: string;\n /** Leading arguments the command needs; a caller appends its own after these. */\n readonly args: readonly string[];\n readonly environment: NodeJS.ProcessEnv;\n}\n\ntype FileExists = (file: string) => boolean;\n\nfunction packageFile(packageName: string, segments: readonly string[], parentUrl: string): string {\n const manifest = createRequire(parentUrl).resolve(`${packageName}/package.json`);\n return path.join(path.dirname(manifest), ...segments);\n}\n\n/** Finds the DoomPi CLI pinned by the repository containing the requested session. */\nexport function repositoryDoomPiCli(cwd: string, exists: FileExists = fs.existsSync): string | undefined {\n for (let directory = path.resolve(cwd); ; directory = path.dirname(directory)) {\n const cli = path.join(directory, ...REPOSITORY_CLI);\n if (exists(cli)) return cli;\n if (directory === path.dirname(directory)) return undefined;\n }\n}\n\n/**\n * Resolves the Server and fallback agent shipped by a DoomPi Web installation.\n *\n * This is what makes `doompi-web` need no flags: the hub launches the\n * doompi-server sitting in its own dependency tree rather than whichever one\n * PATH happens to name, and points it at the agent from the same install, so a\n * checkout runs its own build. A repository that pins its own DoomPi still\n * wins, since a session created there should run that repository's agent.\n */\nexport function defaultServerLaunch(\n cwd: string,\n environment: NodeJS.ProcessEnv,\n parentUrl: string = import.meta.url,\n exists: FileExists = fs.existsSync,\n): BundledServerLaunch {\n const server = packageFile('@agimon-ai/doompi-server', ['dist', 'bin', 'serve.mjs'], parentUrl);\n const
|
|
1
|
+
{"version":3,"file":"bundledServer.mjs","names":[],"sources":["../../../src/adapters/bundledServer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nconst AGENT_COMMAND_ENV = 'DOOMPI_AGENT_COMMAND';\nconst SERVER_COMMAND_ENV = 'DOOMPI_SERVER_COMMAND';\nconst WEB_MODULE_ENV = 'DOOMPI_WEB_MODULE';\nconst REPOSITORY_CLI = ['node_modules', '@agimon-ai', 'doompi', 'dist', 'bin', 'cli.mjs'];\n\nexport interface BundledServerLaunch {\n readonly command: string;\n /** Leading arguments the command needs; a caller appends its own after these. */\n readonly args: readonly string[];\n readonly environment: NodeJS.ProcessEnv;\n}\n\ntype FileExists = (file: string) => boolean;\n\nfunction packageFile(packageName: string, segments: readonly string[], parentUrl: string): string {\n const manifest = createRequire(parentUrl).resolve(`${packageName}/package.json`);\n return path.join(path.dirname(manifest), ...segments);\n}\n\n/** Finds the DoomPi CLI pinned by the repository containing the requested session. */\nexport function repositoryDoomPiCli(cwd: string, exists: FileExists = fs.existsSync): string | undefined {\n for (let directory = path.resolve(cwd); ; directory = path.dirname(directory)) {\n const cli = path.join(directory, ...REPOSITORY_CLI);\n if (exists(cli)) return cli;\n if (directory === path.dirname(directory)) return undefined;\n }\n}\n\n/**\n * Resolves the Server and fallback agent shipped by a DoomPi Web installation.\n *\n * This is what makes `doompi-web` need no flags: the hub launches the\n * doompi-server sitting in its own dependency tree rather than whichever one\n * PATH happens to name, and points it at the agent from the same install, so a\n * checkout runs its own build. A repository that pins its own DoomPi still\n * wins, since a session created there should run that repository's agent.\n */\nexport function defaultServerLaunch(\n cwd: string,\n environment: NodeJS.ProcessEnv,\n parentUrl: string = import.meta.url,\n exists: FileExists = fs.existsSync,\n): BundledServerLaunch {\n const configuredServer = environment[SERVER_COMMAND_ENV];\n const server = configuredServer || packageFile('@agimon-ai/doompi-server', ['dist', 'bin', 'serve.mjs'], parentUrl);\n const configuredAgent = environment[AGENT_COMMAND_ENV];\n const localAgent = repositoryDoomPiCli(cwd, exists);\n const agentEnvironment =\n configuredAgent || localAgent\n ? { ...environment }\n : {\n ...environment,\n [AGENT_COMMAND_ENV]: packageFile('@agimon-ai/doompi', ['dist', 'bin', 'cli.mjs'], parentUrl),\n };\n const webModule =\n environment[WEB_MODULE_ENV] ||\n pathToFileURL(packageFile('@agimon-ai/doompi-web', ['dist', 'index.mjs'], parentUrl)).href;\n return {\n command: process.execPath,\n args: [server],\n environment: {\n ...agentEnvironment,\n [WEB_MODULE_ENV]: webModule,\n },\n };\n}\n"],"mappings":"qIAKA,MAAM,EAAoB,uBAEpB,EAAiB,oBACjB,EAAiB,CAAC,eAAgB,aAAc,SAAU,OAAQ,MAAO,SAAS,EAWxF,SAAS,EAAY,EAAqB,EAA6B,EAA2B,CAChG,IAAM,EAAW,EAAc,CAAS,CAAC,CAAC,QAAQ,GAAG,EAAY,cAAc,EAC/E,OAAO,EAAK,KAAK,EAAK,QAAQ,CAAQ,EAAG,GAAG,CAAQ,CACtD,CAGA,SAAgB,EAAoB,EAAa,EAAqB,EAAG,WAAgC,CACvG,IAAK,IAAI,EAAY,EAAK,QAAQ,CAAG,GAAK,EAAY,EAAK,QAAQ,CAAS,EAAG,CAC7E,IAAM,EAAM,EAAK,KAAK,EAAW,GAAG,CAAc,EAClD,GAAI,EAAO,CAAG,EAAG,OAAO,EACxB,GAAI,IAAc,EAAK,QAAQ,CAAS,EAAG,MAC7C,CACF,CAWA,SAAgB,EACd,EACA,EACA,EAAoB,YAAY,IAChC,EAAqB,EAAG,WACH,CAErB,IAAM,EADmB,EAAY,uBACF,EAAY,2BAA4B,CAAC,OAAQ,MAAO,WAAW,EAAG,CAAS,EAC5G,EAAkB,EAAY,GAC9B,EAAa,EAAoB,EAAK,CAAM,EAC5C,EACJ,GAAmB,EACf,CAAE,GAAG,CAAY,EACjB,CACE,GAAG,GACF,GAAoB,EAAY,oBAAqB,CAAC,OAAQ,MAAO,SAAS,EAAG,CAAS,CAC7F,EACA,EACJ,EAAY,IACZ,EAAc,EAAY,wBAAyB,CAAC,OAAQ,WAAW,EAAG,CAAS,CAAC,CAAC,CAAC,KACxF,MAAO,CACL,QAAS,QAAQ,SACjB,KAAM,CAAC,CAAM,EACb,YAAa,CACX,GAAG,GACF,GAAiB,CACpB,CACF,CACF"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./piHubService.cjs"),n=require("./syncGuard.cjs"),r=require("./piWebSocketListener.cjs"),i=require("../types/media.cjs"),a=require("../services/fileMedia.cjs"),o=require("../services/staticAssets.cjs"),s=require("../types/hub.cjs"),c=require("../types/notification.cjs");require("../types/session.cjs");const l=require("./gitStatus.cjs"),u=require("./registryWatcher.cjs"),d=require("./serverSpawner.cjs"),f=require("./packageApiProxy.cjs"),p=require("./sessionHub.cjs"),m=require("../types/remoteAccess.cjs"),h=require("../services/remoteGuardPolicy.cjs"),g=require("../services/sessionMigration.cjs"),ee=require("./authRoutes.cjs"),te=require("./settingsRoutes.cjs"),ne=require("./providerAuth.cjs"),re=require("./remoteGuard.cjs"),ie=require("./remoteAccess.cjs"),ae=require("./bundlePublication.cjs"),oe=require("./webPush.cjs"),se=require("./remoteAccessStore.cjs"),ce=require("./remoteRoutes.cjs"),
|
|
2
|
-
`,1)[0]?.trim();return(e.name?.trim()||n||x.default.basename(t)||`untitled`).slice(0,80)}function I(e){return e instanceof Error?e.message:String(e)}function Ae(e,t){return`${e}\n${t}`}function je(e,t,n,r){return p.createSessionHub({source:u.watchRegistry(e.registryDir,t),spawner:d.createServerSpawner({registryDir:e.registryDir,command:e.spawnCommand,onNotice:t}),readGit:l.readGitStatus,channels:n,telemetry:r,onNotice:t})}function L(e){return`repo-${(0,pe.createHash)(`sha256`).update(e).digest(`base64url`).slice(0,24)}`}function Me(e){let t=new Map,n=()=>{for(let[e,n]of t)t.set(e,{...n,repository:{...n.repository,active:!1}});for(let n of e.records())try{let e=b.default.realpathSync((0,O.findRepositoryRoot)(n.cwd));t.set(e,{repository:{id:L(e),path:e,name:x.default.basename(e)||e,active:!0},seenAt:Date.now()})}catch{}let n=[...t.entries()].filter(([,e])=>!e.repository.active).sort((e,t)=>t[1].seenAt-e[1].seenAt);for(let[e]of n.slice(12))t.delete(e);return[...t.values()].sort((e,t)=>Number(t.repository.active)-Number(e.repository.active)||t.seenAt-e.seenAt||e.repository.path.localeCompare(t.repository.path)).map(e=>e.repository)};return{list:n,resolve(e){return n().find(t=>t.id===e)?.path}}}function Ne(e,t,n,r,i){let a=[];for(let o of t){let t=`${D.DOOM_API_ROUTE_PREFIX}/${o.basePath}`,s;try{s=o.start({scope:`hub`,onNotice:n,resolveRepository:r,readRepositorySync:i})}catch(e){n(`hub API '${o.basePath}' did not start (${I(e)}); its routes stay unmounted`);continue}a.push(s),e.all(`${t}/*`,async e=>{if(e.req.query(`session`)!==void 0)return e.notFound();let r=new URL(e.req.url);r.pathname=r.pathname.slice(t.length)||`/`;try{return await s.fetch(new Request(r,e.req.raw))}catch(t){return n(`hub API '${o.basePath}' failed on ${r.pathname} (${I(t)})`),e.json({error:`The '${o.basePath}' API failed.`},500)}})}return a}function Pe(e,t,n,r){e.all(`${D.DOOM_API_ROUTE_PREFIX}/*`,async e=>{let i=e.req.query(s.API_SESSION_QUERY_PARAM);if(i===void 0)return e.notFound();let a=t.snapshot().find(e=>e.id===i);if(a===void 0)return e.json({error:`No session ${i}.`},404);if(a.apiSocketPath===void 0)return e.json({error:`Session ${i} serves no package API.`},404);let o=new URL(e.req.url);o.searchParams.delete(s.API_SESSION_QUERY_PARAM);let c=y.readTraceContext(e.req.raw.headers);try{return await r.runInSpan(`web.package_proxy`,{"operation.name":`web.package_proxy`,"http.method":e.req.method},async t=>await f.proxyToSocket({socketPath:a.apiSocketPath,path:`${o.pathname}${o.search}`,method:e.req.method,headers:e.req.raw.headers,body:e.req.raw.body,trace:y.forwardedTraceContext(t,c)}),c)}catch(t){return n(`session ${i} API is unreachable (${I(t)})`),e.json({error:`Session ${i} is not answering.`},502)}})}async function R(e){let l=e.host??`127.0.0.1`,d=e.onNotice??(()=>{}),f=y.createWebTelemetry(),p=se.createRemoteAccessStore({stateDir:e.remoteStateDir,onNotice:d}),S=e=>{try{return b.default.realpathSync((0,O.findRepositoryRoot)(e))}catch{return}},D=e=>{let t=S(e);if(t!==void 0)return t;let n=(0,_e.globalDoomConfigDirectory)();try{return(0,C.readSyncRegistration)(n)?.root}catch(e){d(`global cockpit composition is unreadable (${I(e)})`);return}},A=e.compositionDir===void 0?void 0:S(e.compositionDir);if(e.compositionDir!==void 0&&A===void 0)throw Error(`Cockpit composition directory is not inside a repository: ${e.compositionDir}`);let j=u.readRegistryRecords(e.registryDir,d).map(e=>D(e.cwd)).filter(e=>e!==void 0),M=[...new Set(A===void 0?j:[A])].sort((e,t)=>e.localeCompare(t)),N=new Map;for(let e of M)N.set(e,n.createSyncGuard({repoRoot:e,onNotice:d}));await Promise.all([...N.values()].map(async e=>await e.ensureSynced()));let L=async()=>await fe.resolveWebComposition({repositoryRoots:M,stateDirectory:p.directory,onNotice:d}),R=await L(),z={assetsDir:Oe(e.assetsDir,R,d),apiDirectory:R?.apiDirectory},B=async e=>{let t=D(e);if(t===void 0)return;let r=N.get(t);if(r!==void 0){await r.ensureSynced();return}let i=n.createSyncGuard({repoRoot:t,onNotice:d});try{await i.ensureSynced()}finally{i.close()}},V=je(e,d,await de.loadHubChannels(z.assetsDir,d),f),H=ue.createThreadJournals({resolve:(e,t)=>V.threadJournal(e,t),onNotice:d}),U=new Set,W=(e,t)=>{for(let n of U)t&&!n.local||n.post(e)},G=new be.Hono,K=(0,he.createNodeWebSocket)({app:G}),Fe,q=Ee(),J=ae.createBundlePublication({assetsDir:()=>z.assetsDir,stateDir:p.directory,onNotice:d}),Y;_.reapStaleTunnel(p.directory,d);let Ie=e.onHandover===void 0?void 0:t=>{let n=g.planSessionMigration(V.snapshot().map(e=>({id:e.id,cwd:e.cwd,...e.name===void 0?{}:{name:e.name}})),t.sandbox.workspaces);for(let e of g.describeStranded(n.stranded))d(e);for(let e of n.migrate){let t=V.stop(e.id);t.ok||d(`could not stop ${e.name??e.id} before the move: ${t.error}`)}e.onHandover?.({settings:t,sessions:n.migrate})},X=ie.createRemoteAccess({store:p,launchTunnel:e.remoteAccess?.launchTunnel??_.createTunnelLauncher({stateDir:p.directory,...e.cloudflaredPath===void 0?{}:{cloudflaredPath:e.cloudflaredPath},onNotice:d,onExit:e=>{d(`remote access: ${e}`),X.disable()}}),bindListener:async()=>await new Promise((e,t)=>{let n=(0,me.serve)({fetch:G.fetch,port:0,hostname:`127.0.0.1`,serverOptions:{maxHeaderSize:16384,headersTimeout:5e3,requestTimeout:1e4}},t=>{K.injectWebSocket(n),e({port:t.port,close:async()=>await new Promise(e=>{n.closeAllConnections?.(),n.close(()=>e())})})}),r=n;r.maxConnections=64,r.maxRequestsPerSocket=100,n.once(`error`,t)}),bundleTrust:()=>J.trust(),onDeviceDropped:e=>Y?.remove(e),onNotice:d,...Ie===void 0?{}:{requestHandover:Ie},contained:(0,Se.insideSandbox)(process.env),broadcastLocal:e=>W(e,!0),broadcastAll:e=>W(e,!1),...e.remoteAccess?.now===void 0?{}:{now:e.remoteAccess.now}});Y=oe.createLiveWebPush({stateDir:p.directory,isConnected:e=>X.isDeviceConnected(e),onNotice:d});let Le=V.onEvent(e=>{e.kind===`frame`&&c.parseDoomNotificationEntry(e.frame)!==void 0&&Y?.notify()}),Z=re.createRemoteGuard({loopbackPort:()=>Fe,tunnelPolicy:()=>X.tunnelPolicy(),authorize:e=>X.authorize((0,T.getCookie)(e,m.DEVICE_COOKIE,`host`)),trustedDevice:e=>e.env?.sealedDeviceId,channelReady:(e,t)=>X.channelFor(e,t)!==void 0,stepUp:{required:e=>X.stepUpRequired(e),verify:async(e,t,n)=>{if(typeof n!=`object`||!n)return!1;let r=n.ceremonyId,i=n.response;if(typeof r!=`string`)return!1;let a=e.env?.sealedDeviceId??X.authorize((0,T.getCookie)(e,`doompi_device`,`host`)),o=a===void 0?`local`:`device:${a}`;return await X.passkeys().finishStepUp(r,o,t,i)}},extraOrigins:h.allowedOriginsFromEnv(process.env.DOOMPI_WEB_ALLOW_ORIGIN)});G.use(`*`,Z.middleware),G.use(`*`,async(e,t)=>{let n=new URL(e.req.url).pathname;if(n===m.REMOTE_HTTP_ROUTE)return t();let r=y.requestOperation(n);if(r===void 0)return t();let i=performance.now();return await f.runInSpan(r,{"operation.name":r,"http.method":e.req.method},async()=>{await t(),await f.recordEvent(`web.api.request`,{"operation.name":r,"http.method":e.req.method,"http.status_code":e.res.status,duration_ms:Math.max(0,Math.round(performance.now()-i))})},y.readTraceContext(e.req.raw.headers))}),G.use(`*`,async(e,t)=>{if(Z.listenerOf(e)===`local`)return t();let n=Number(e.req.header(`content-length`));return Number.isFinite(n)&&n>k?e.json({error:`The tunnel request body is too large.`},413):t()}),ce.registerRemoteRoutes(G,{remote:X,listenerOf:e=>Z.listenerOf(e)});let Re=(0,E.bodyLimit)({maxSize:k,onError:e=>e.json({error:`The tunnel request body is too large.`},413)});G.use(`*`,async(e,t)=>Z.listenerOf(e)===`local`?t():await Re(e,t));let ze=(0,E.bodyLimit)({maxSize:8192,onError:e=>e.json({error:`The telemetry batch is too large.`},413)}),Be=y.createBrowserTelemetryRateLimit();G.post(y.WEB_TELEMETRY_ROUTE,ze,async e=>{if(!Be())return e.json({error:`Too many telemetry batches.`},429);let t;try{t=await e.req.json()}catch{return e.json({error:`The telemetry batch must be JSON.`},400)}let n=y.parseBrowserPerformanceBatch(t);if(n===void 0)return e.json({error:`The telemetry batch is invalid.`},400);for(let e of n)await f.recordEvent(e.name,{...e.duration_ms===void 0?{}:{duration_ms:e.duration_ms},...e.count===void 0?{}:{count:e.count}});return e.body(null,204)}),G.post(m.REMOTE_HTTP_ROUTE,async e=>{let t=X.authorize((0,T.getCookie)(e,m.DEVICE_COOKIE,`host`));if(t===void 0)return e.json({error:`This device is not paired.`},401);let n=X.channelFor(t,`http`);if(n===void 0)return e.json({error:`This device has no sealed HTTP channel.`},401);let r;try{r=await e.req.json()}catch{return e.json({error:`The sealed request envelope was malformed.`},400)}let i=n.open(r);if(!i.ok)return e.json({error:`The sealed request was refused: ${i.failure}.`},400);let a;try{a=JSON.parse(new TextDecoder().decode(i.plaintext))}catch{return e.json({error:`The sealed HTTP request was malformed.`},400)}let o=Ce(a),s=o===void 0?void 0:we(o,e),c=o===void 0?void 0:Te(o.body);if(o===void 0||s===void 0||c===void 0)return e.json({error:`The sealed HTTP request was invalid.`},400);let l=new URL(o.target,new URL(e.req.url).origin),u=await G.request(l,{method:o.method,headers:s,...o.method===`GET`||o.method===`HEAD`?{}:{body:Uint8Array.from(c).buffer}},{...e.env,sealedDeviceId:t}),d=Buffer.from(await u.arrayBuffer()),f=n.seal(new TextEncoder().encode(JSON.stringify({v:1,status:u.status,headers:Array.from(u.headers.entries()),body:d.toString(`base64`)})));return f.ok?e.json(f.envelope):e.json({error:`The sealed response failed: ${f.failure}.`},503)});let Ve=(0,E.bodyLimit)({maxSize:8192,onError:e=>e.json({error:`The Push subscription is too large.`},413)}),Q=e=>e.env?.sealedDeviceId;G.get(m.REMOTE_PUSH_KEY_ROUTE,e=>Q(e)===void 0?e.json({error:`A sealed paired device is required.`},401):e.json({publicKey:Y?.publicKey()},200,{"Cache-Control":`no-store`})),G.post(m.REMOTE_PUSH_ROUTE,Ve,async e=>{let t=Q(e);if(t===void 0)return e.json({error:`A sealed paired device is required.`},401);let n;try{n=await e.req.json()}catch{return e.json({error:`The Push subscription must be JSON.`},400)}return Y?.subscribe(t,n)===!0?e.body(null,204):e.json({error:`The Push subscription is invalid.`},400)}),G.delete(m.REMOTE_PUSH_ROUTE,e=>{let t=Q(e);return t===void 0?e.json({error:`A sealed paired device is required.`},401):(Y?.remove(t),e.body(null,204))});let He=r.createPiWebSocketListener({onError:e=>d(`protocol: ${e.message}`)}),Ue=r.createPiWebSocketListener({onError:e=>d(`protocol: ${e.message}`)}),We=t.createPiHubService({records:()=>V.records(),spawn:e=>V.create(e),onNotice:d}),Ge={...We,createSession:async()=>{throw new w.PiServerError(`not_implemented`,`Remote protocol clients cannot create sessions`)}},Ke=new w.PiServer(We,{listeners:[He],onError:e=>d(`protocol: ${e.message}`)}),qe=new w.PiServer(Ge,{listeners:[Ue],onError:e=>d(`protocol: ${e.message}`)});await Promise.all([Ke.start(),qe.start()]);let $=ne.createProviderAuth({runtime:e.authRuntime,onNotice:d});ee.registerAuthRoutes(G,$);let Je=Me(V),Ye=()=>Je.list(),Xe=e=>Je.resolve(e),Ze=e=>{let t=Xe(e);if(t===void 0)return;let n=(0,C.readSyncDrift)({repoRoot:t}),r;try{r=(0,C.readSyncRegistration)(t)?(0,ve.readSyncState)(t):void 0}catch{r=void 0}return{fresh:n.fresh,reasons:[...n.reasons],...r===void 0?{}:{mcpProjection:r.fileState.mcpProjection}}};te.registerSettingsRoutes(G,{repositories:Ye,models:()=>$.listModels()});let Qe=z.apiDirectory,$e=Ne(G,Qe===void 0&&!process.env[xe.PACKAGE_API_DIR_ENV]?[]:await(0,xe.loadPackageApis)(`hub`,{apiDirectory:Qe,onNotice:d}),d,Xe,Ze);Pe(G,V,d,f),G.get(`/api/health`,e=>e.json({ok:!0,role:`hub`,protocol:1,sessions:V.snapshot().length,pid:process.pid,version:De()})),G.post(s.SESSIONS_API_ROUTE,async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:`The request body must be JSON.`},400)}if(!F(t)||typeof t.cwd!=`string`||t.cwd===``)return e.json({error:`A cwd string is required.`},400);let n=typeof t.name==`string`&&t.name!==``?t.name:void 0;await B(t.cwd);let r=await V.create({cwd:t.cwd,name:n,trace:y.readTraceContext(e.req.raw.headers)});return r.ok?e.json({sessionId:r.sessionId},201):e.json({error:r.error},r.code===`invalid_request`?400:502)}),G.post(`${s.SESSIONS_API_ROUTE}/:sessionId/restart`,async e=>{let t=e.req.param(`sessionId`),n=V.snapshot().find(e=>e.id===t);n!==void 0&&await B(n.cwd);let r=await V.restart(t,y.readTraceContext(e.req.raw.headers));return r.ok?e.json({sessionId:r.sessionId},202):e.json({error:r.error},r.code===`invalid_request`?404:502)}),G.get(`${s.SESSIONS_API_ROUTE}/:sessionId/history`,async e=>{let t=e.req.param(`sessionId`),n=V.snapshot().find(e=>e.id===t);if(!n)return e.json({error:`Unknown session.`},404);let r=await ge.SessionManager.list(n.cwd);return e.json({sessions:r.map(e=>({id:e.id,...e.name===void 0?{}:{name:e.name},firstMessage:e.firstMessage,createdAt:e.created.toISOString(),updatedAt:e.modified.toISOString(),messageCount:e.messageCount}))})}),G.post(`${s.SESSIONS_API_ROUTE}/:sessionId/resume`,async e=>{let t=e.req.param(`sessionId`),n=V.snapshot().find(e=>e.id===t);if(!n)return e.json({error:`Unknown session.`},404);let r;try{r=await e.req.json()}catch{return e.json({error:`The request body must be JSON.`},400)}if(!F(r)||typeof r.targetSessionId!=`string`||r.targetSessionId===``)return e.json({error:`A targetSessionId string is required.`},400);let i=(await ge.SessionManager.list(n.cwd)).find(e=>e.id===r.targetSessionId);if(!i)return e.json({error:`That Pi thread does not belong to this workspace.`},404);await B(n.cwd);let a=await V.resume(t,{sessionId:i.id,name:ke(i,n.cwd)},y.readTraceContext(e.req.raw.headers));return a.ok?e.json({sessionId:a.sessionId},202):e.json({error:a.error},a.code===`invalid_request`?409:502)}),G.delete(`${s.SESSIONS_API_ROUTE}/:sessionId`,e=>{let t=e.req.param(`sessionId`),n=V.stop(t);return n.ok?e.json({sessionId:t},202):e.json({error:n.error},n.code===`unknown`?404:n.code===`self`?409:502)}),G.get(`/api/sessions/:sessionId/files`,async e=>{let t=e.req.param(`sessionId`),n=V.snapshot().find(e=>e.id===t);if(!n)return e.json({error:`Unknown session.`},404);let r=e.req.query(`q`)??``,i=await v.listSessionFiles(n.cwd,r,20);return e.json({files:i})});let et=t=>{if(!(Z.listenerOf(t)===`local`||(0,Se.insideSandbox)(process.env)))return e.browseRoot??process.cwd()};G.get(s.DIRECTORIES_API_ROUTE,async e=>{let t=et(e),n=await le.suggestDirectories(e.req.query(`q`)??``,{limit:12,...t===void 0?{}:{root:t}});return e.json({directories:n})}),G.get(i.SESSION_FILE_ROUTE,async e=>{let t=e.req.param(`sessionId`),n=V.snapshot().find(e=>e.id===t);if(!n)return e.json({error:`Unknown session.`},404);let r=e.req.query(`path`)??``,o=await v.readSessionFile(n.cwd,r,i.MAX_SESSION_FILE_BYTES);return o.status===`forbidden`?e.json({error:`The path leaves the session directory.`},403):o.status===`not-found`?e.json({error:`No such file.`},404):o.status===`too-large`?e.json({error:`The file is too large to preview.`},413):e.body(new Uint8Array(o.body),200,a.sessionFileHeaders(r))}),G.get(m.SESSION_SOCKET_ROUTE,K.upgradeWebSocket(e=>{let t=Z.listenerOf(e)===`local`,n=t?void 0:X.authorize((0,T.getCookie)(e,m.DEVICE_COOKIE,`host`)),r=n===void 0?void 0:X.channelFor(n,`session`),i=new Set,a=(0,pe.randomUUID)(),o=new Map,l,u,f,p,h=e=>{for(let[t,n]of o)(e===void 0||n.sessionId===e)&&(o.delete(t),H.unsubscribe(n.sessionId,n.threadId))};return{onOpen(e,m){if(!t&&r===void 0){m.close(1008,`sealed session channel required`);return}let g=e=>{let n=JSON.stringify(e),i=n;if(!t){if(r===void 0)return;let e=r.seal(new TextEncoder().encode(n));if(!e.ok){m.close(1008,`sealed session channel failed`);return}i=JSON.stringify(e.envelope)}try{m.send(i)}catch{}};f={post:g,local:t},U.add(f),n!==void 0&&(p=X.trackSocket(n,(e,t)=>m.close(e,t))),g(s.hubHello(V.channelTypes())),g({type:s.SESSIONS_SNAPSHOT_TYPE,sessions:V.snapshot()}),l=V.onEvent(e=>{e.kind===`upsert`?g({type:s.SESSION_UPSERT_TYPE,session:e.session}):e.kind===`removed`?(i.delete(e.sessionId),h(e.sessionId),g({type:s.SESSION_REMOVED_TYPE,sessionId:e.sessionId})):e.kind===`channel`?(e.connectionId===a||e.connectionId===void 0&&i.has(e.sessionId))&&g({type:e.frameType,sessionId:e.sessionId,payload:e.payload}):(c.parseDoomNotificationEntry(e.frame)!==void 0||i.has(e.sessionId))&&g(s.sessionFrameEnvelope(e.sessionId,e.frame))}),u=H.onFrame(e=>{o.has(Ae(e.sessionId,e.threadId))&&g(s.threadFrameEnvelope(e.sessionId,e.threadId,e.frame))}),d(`browser attached`)},onMessage(e){if(typeof e.data!=`string`)return;let n;try{n=JSON.parse(e.data)}catch{return}if(!t){if(r===void 0)return;let e=r.open(n);if(!e.ok)return;try{n=JSON.parse(new TextDecoder().decode(e.plaintext))}catch{return}}if(!F(n)||typeof n.sessionId!=`string`)return;let c=n.sessionId;if(typeof n.type==`string`&&V.channelTypes().includes(n.type)&&(i.has(c)||V.channelReceivesWithoutSubscription(n.type))){V.receiveChannel(c,n.type,n.payload,a);return}if(n.type===`subscribe`){let e=V.backlog(c);if(!e)return;i.add(c),f?.post(e);for(let e of V.channelFrames(c))f?.post(e);return}if(n.type===`unsubscribe`){i.delete(c);return}if(n.type===`history_request`){let e=V.history(c,{...typeof n.before==`string`?{before:n.before}:{},...typeof n.limit==`number`?{limit:n.limit}:{}});if(!e)return;f?.post(e);return}if(n.type===`subscribe_thread`||n.type===`unsubscribe_thread`){if(typeof n.threadId!=`string`)return;let e=n.threadId,t=Ae(c,e);if(n.type===`unsubscribe_thread`){o.delete(t)&&H.unsubscribe(c,e);return}if(o.has(t)||V.backlog(c)===void 0)return;o.set(t,{sessionId:c,threadId:e}),f?.post(s.threadBacklog(c,e,H.subscribe(c,e)));return}if(n.type===`session_command`&&F(n.frame)){if(n.frame.type===`attach`)return;V.command(c,n.frame)}},onClose(){V.disconnectChannels(a),f&&U.delete(f),f=void 0,p?.(),p=void 0,l?.(),l=void 0,u?.(),u=void 0,i.clear(),h(),d(`browser detached`)}}})),G.get(m.PROTOCOL_SOCKET_ROUTE,K.upgradeWebSocket(e=>{let t=Z.listenerOf(e)===`local`,n=t?void 0:X.authorize((0,T.getCookie)(e,m.DEVICE_COOKIE,`host`)),r=n===void 0?void 0:X.channelFor(n,`protocol`),i,a;return{onOpen(e,o){if(!t&&r===void 0){o.close(1008,`sealed protocol channel required`);return}n!==void 0&&(a=X.trackSocket(n,(e,t)=>o.close(e,t))),i=(t?He:Ue).accept({send:e=>{if(t){o.send(e);return}if(r===void 0)return;let n=e instanceof Uint8Array?e:new Uint8Array(e),i=r.seal(n);if(!i.ok){o.close(1008,`sealed protocol channel failed`);return}o.send(new TextEncoder().encode(JSON.stringify(i.envelope)))},close:()=>o.close(),get readyState(){return o.readyState}})},async onMessage(e){let n=e.data;if(typeof n==`string`)return;let a=n instanceof Blob?new Uint8Array(await n.arrayBuffer()):(n instanceof ArrayBuffer,new Uint8Array(n));if(t){i?.onData(a);return}if(r===void 0)return;let o;try{o=JSON.parse(new TextDecoder().decode(a))}catch{return}let s=r.open(o);s.ok&&i?.onData(s.plaintext)},onClose(){a?.(),a=void 0,i?.onClose(),i=void 0},onError(e){i?.onError(e instanceof Error?e:Error(`The protocol socket failed`)),i=void 0}}})),G.get(`/manifest.webmanifest`,e=>{let t=P(x.default.join(q,`manifest.webmanifest`));return t===void 0?e.text(`The PWA manifest is unavailable.`,404):e.body(new Uint8Array(t),200,{"Cache-Control":`no-store`,"Content-Type":`application/manifest+json; charset=utf-8`})}),G.get(`/sw.js`,e=>{let t=P(x.default.join(q,`sw.js`));return t===void 0?e.text(`The trusted service worker is unavailable.`,404):e.body(new Uint8Array(t),200,{"Cache-Control":`no-cache`,"Content-Type":`text/javascript; charset=utf-8`,"Service-Worker-Allowed":`/`})}),G.get(`/pwa/*`,e=>{let t=new URL(e.req.url).pathname.slice(5),n=o.resolveAssetPath(q,`/${t}`),r=n===void 0?void 0:P(n);return n===void 0||r===void 0?e.text(`PWA asset not found.`,404):e.body(new Uint8Array(r),200,{"Cache-Control":`no-store`,"Content-Type":o.contentTypeFor(n),"X-Content-Type-Options":`nosniff`})}),G.get(ye.BUNDLE_MANIFEST_ROUTE,e=>{let t=J.current();return t===void 0?e.json({error:`No signed cockpit bundle is available.`},503):e.json(t.signed,200,{"Cache-Control":`no-store`})}),G.get(`/bundle-assets/*`,e=>{let t=new URL(e.req.url).pathname,n=/^\/bundle-assets\/([1-9][0-9]*)(\/.*)$/u.exec(t),r=n===null?void 0:Number(n[1]),i=J.current();if(n===null||!Number.isSafeInteger(r)||i===void 0||i.signed.manifest.revision!==r)return e.text(`That signed bundle revision is unavailable.`,404);let a=(0,ye.assetFor)(i.signed.manifest,n[2]??``),s=a===void 0?void 0:o.resolveAssetPath(i.assetsDir,a.path),c=s===void 0?void 0:P(s);return a===void 0||s===void 0||c===void 0?e.text(`Bundle asset not found.`,404):e.body(new Uint8Array(c),200,{"Cache-Control":`no-store`,"Content-Length":String(c.byteLength),"Content-Type":a.contentType,"X-Content-Type-Options":`nosniff`})}),G.get(`/`,e=>e.redirect(m.PAIRING_PAGE_ROUTE)),G.get(`*`,e=>{let t=new URL(e.req.url).pathname,n=e.req.header(`accept`)?.includes(`text/html`)===!0;return!t.startsWith(`/api/`)&&n?e.redirect(`/`):e.text(`Not found.`,404)});let tt=async()=>{try{let e=await L();e!==void 0&&e.webDirectory!==z.assetsDir&&(z={assetsDir:e.webDirectory,apiDirectory:e.apiDirectory},d(`serving the rebuilt cockpit bundle from ${z.assetsDir}`)),J.refresh(),W({type:s.HUB_RESYNCED_TYPE},!1)}catch(e){d(`the rebuilt cockpit composition is unavailable (${I(e)}); keeping the served bundle`)}};for(let e of N.values())e.watch(()=>void tt());return new Promise((t,n)=>{let r=(0,me.serve)({fetch:G.fetch,port:e.port,hostname:l},e=>{Fe=e.port,K.injectWebSocket(r);let n=`http://${l}:${e.port}`;d(`cockpit on ${n}`);let i,a=async()=>{await X.close(),await new Promise(e=>{H.close();for(let e of N.values())e.close();Ke.close(),qe.close(),Le(),Y?.close(),V.close(),$.close();for(let e of $e)e.close();for(let e of K.wss.clients)e.terminate();K.wss.close(),r.closeAllConnections?.(),r.close(()=>e())}),await y.shutdownWebTelemetry(f)};t({url:n,port:e.port,close:()=>i??=a()})});r.once(`error`,e=>{H.close(),Le(),Y?.close(),V.close(),$.close(),X.close();for(let e of $e)e.close();f.shutdown(),n(e)})})}exports.createSettingsRepositoryRegistry=Me,exports.packagedVersion=De,exports.serveWeb=R;
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./piHubService.cjs"),n=require("./syncGuard.cjs"),r=require("./piWebSocketListener.cjs"),i=require("../types/media.cjs"),a=require("../services/fileMedia.cjs"),o=require("../services/staticAssets.cjs"),s=require("../types/hub.cjs"),c=require("../types/notification.cjs");require("../types/session.cjs");const l=require("./gitStatus.cjs"),u=require("./registryWatcher.cjs"),d=require("./serverSpawner.cjs"),f=require("./packageApiProxy.cjs"),p=require("./sessionHub.cjs"),m=require("../types/remoteAccess.cjs"),h=require("../services/remoteGuardPolicy.cjs"),g=require("../services/sessionMigration.cjs"),ee=require("./authRoutes.cjs"),te=require("./settingsRoutes.cjs"),ne=require("./providerAuth.cjs"),re=require("./remoteGuard.cjs"),ie=require("./remoteAccess.cjs"),ae=require("./bundlePublication.cjs"),oe=require("./webPush.cjs"),se=require("./remoteAccessStore.cjs"),ce=require("./remoteRoutes.cjs"),le=require("./tunnelProcess.cjs"),ue=require("./directoryListing.cjs"),de=require("./sessionFiles.cjs"),fe=require("./threadJournals.cjs"),pe=require("./webHubPluginLoader.cjs"),_=require("./webTelemetry.cjs"),me=require("./webComposition.cjs");let v=require("node:fs");v=e.__toESM(v,1);let y=require("node:path");y=e.__toESM(y,1);let b=require("node:url"),x=require("@agimon-ai/doompi/services"),S=require("node:crypto"),he=require("@hono/node-server"),ge=require("@hono/node-ws"),C=require("@earendil-works/pi-coding-agent"),w=require("@earendil-works/pi-server"),_e=require("@agimon-ai/doompi-config"),ve=require("@agimon-ai/doompi/services/syncState"),ye=require("@agimon-ai/doompi-web-security"),be=require("hono"),T=require("hono/cookie"),E=require("hono/body-limit"),D=require("@agimon-ai/doompi-extension-contracts/package-api"),xe=require("@agimon-ai/doompi-extension-contracts/package-api-loader"),Se=require("@agimon-ai/doompi-extension-contracts/sandbox-harness"),O=require("@agimon-ai/doompi/utils/repository");const k=`DOOMPI_WEB_PACKAGE_ROOT`,Ce=8388608,we=new Set([`GET`,`HEAD`,`POST`,`PUT`,`PATCH`,`DELETE`]),A=new Set([`connection`,`content-length`,`cookie`,`forwarded`,`host`,`origin`,`transfer-encoding`,`upgrade`,`x-forwarded-for`,`x-forwarded-host`,`x-forwarded-proto`]);function Te(e){if(typeof e!=`object`||!e)return;let t=e;if(t.v!==1||typeof t.method!=`string`||!we.has(t.method)||typeof t.target!=`string`||!t.target.startsWith(`/`)||t.target.startsWith(`//`)||t.target.includes(`#`)||!Array.isArray(t.headers)||!t.headers.every(e=>Array.isArray(e)&&e.length===2&&e.every(e=>typeof e==`string`))||t.body!==void 0&&typeof t.body!=`string`)return;let n=new URL(t.target,`http://sealed.doompi.invalid`);if(n.pathname!==m.REMOTE_HTTP_ROUTE&&n.pathname!==m.REMOTE_CHANNEL_ROUTE)return t}function Ee(e,t){try{let n=new Headers;for(let[t,r]of e.headers){let e=t.toLowerCase();A.has(e)||e.startsWith(`sec-`)||n.append(t,r)}for(let e of[`cookie`,`host`,`origin`]){let r=t.req.header(e);r!==void 0&&n.set(e,r)}return n}catch{return}}function De(e){if(e===void 0)return Buffer.alloc(0);if(!/^(?:[A-Za-z\d+/]{4})*(?:[A-Za-z\d+/]{2}==|[A-Za-z\d+/]{3}=)?$/.test(e))return;let t=Buffer.from(e,`base64`);return t.toString(`base64`)===e?t:void 0}function j(e){let t=process.env[k];if(t!==void 0&&t!==``)return y.default.join(t,`dist`,e);let n=y.default.dirname((0,b.fileURLToPath)(require("url").pathToFileURL(__filename).href));for(;;){if(v.default.existsSync(y.default.join(n,`package.json`)))return y.default.join(n,`dist`,e);let t=y.default.dirname(n);if(t===n)throw Error(`doompi-web could not locate its own package root.`);n=t}}function M(){return j(`web`)}function Oe(){return j(`pwa`)}function ke(){let e=process.env[k],t=e!==void 0&&e!==``?e:y.default.dirname((0,b.fileURLToPath)(require("url").pathToFileURL(__filename).href));for(;;){let e=y.default.join(t,`package.json`);if(v.default.existsSync(e))try{let t=JSON.parse(v.default.readFileSync(e,`utf8`)),n=P(t)?t.version:void 0;return typeof n==`string`?n:`unknown`}catch{return`unknown`}let n=y.default.dirname(t);if(n===t)return`unknown`;t=n}}function Ae(e,t,n){if(e!==void 0)return e;let r=process.env.DOOMPI_WEB_DIST;if(r!==void 0&&r!==``)return r;let i=t?.webDirectory;return i!=null&&v.default.existsSync(y.default.join(i,`index.html`))?(n(`serving the synced cockpit bundle from ${i}`),i):M()}function N(e){try{return v.default.statSync(e).isFile()?v.default.readFileSync(e):void 0}catch{return}}function P(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function je(e,t){let n=e.firstMessage.split(`
|
|
2
|
+
`,1)[0]?.trim();return(e.name?.trim()||n||y.default.basename(t)||`untitled`).slice(0,80)}function F(e){return e instanceof Error?e.message:String(e)}function I(e,t){return`${e}\n${t}`}function Me(e,t,n,r){return p.createSessionHub({source:u.watchRegistry(e.registryDir,t),spawner:d.createServerSpawner({registryDir:e.registryDir,command:e.spawnCommand,onNotice:t}),readGit:l.readGitStatus,channels:n,telemetry:r,onNotice:t})}function L(e){return`repo-${(0,S.createHash)(`sha256`).update(e).digest(`base64url`).slice(0,24)}`}function R(e){let t=new Map,n=()=>{for(let[e,n]of t)t.set(e,{...n,repository:{...n.repository,active:!1}});for(let n of e.records())try{let e=v.default.realpathSync((0,O.findRepositoryRoot)(n.cwd));t.set(e,{repository:{id:L(e),path:e,name:y.default.basename(e)||e,active:!0},seenAt:Date.now()})}catch{}let n=[...t.entries()].filter(([,e])=>!e.repository.active).sort((e,t)=>t[1].seenAt-e[1].seenAt);for(let[e]of n.slice(12))t.delete(e);return[...t.values()].sort((e,t)=>Number(t.repository.active)-Number(e.repository.active)||t.seenAt-e.seenAt||e.repository.path.localeCompare(t.repository.path)).map(e=>e.repository)};return{list:n,resolve(e){return n().find(t=>t.id===e)?.path}}}function Ne(e,t,n,r,i){let a=[];for(let o of t){let t=`${D.DOOM_API_ROUTE_PREFIX}/${o.basePath}`,s;try{s=o.start({scope:`hub`,onNotice:n,resolveRepository:r,readRepositorySync:i})}catch(e){n(`hub API '${o.basePath}' did not start (${F(e)}); its routes stay unmounted`);continue}a.push(s),e.all(`${t}/*`,async e=>{if(e.req.query(`session`)!==void 0)return e.notFound();let r=new URL(e.req.url);r.pathname=r.pathname.slice(t.length)||`/`;try{return await s.fetch(new Request(r,e.req.raw))}catch(t){return n(`hub API '${o.basePath}' failed on ${r.pathname} (${F(t)})`),e.json({error:`The '${o.basePath}' API failed.`},500)}})}return a}function Pe(e,t,n,r){e.all(`${D.DOOM_API_ROUTE_PREFIX}/*`,async e=>{let i=e.req.query(s.API_SESSION_QUERY_PARAM);if(i===void 0)return e.notFound();let a=t.snapshot().find(e=>e.id===i);if(a===void 0)return e.json({error:`No session ${i}.`},404);if(a.apiSocketPath===void 0)return e.json({error:`Session ${i} serves no package API.`},404);let o=new URL(e.req.url);o.searchParams.delete(s.API_SESSION_QUERY_PARAM);let c=_.readTraceContext(e.req.raw.headers);try{return await r.runInSpan(`web.package_proxy`,{"operation.name":`web.package_proxy`,"http.method":e.req.method},async t=>await f.proxyToSocket({socketPath:a.apiSocketPath,path:`${o.pathname}${o.search}`,method:e.req.method,headers:e.req.raw.headers,body:e.req.raw.body,trace:_.forwardedTraceContext(t,c)}),c)}catch(t){return n(`session ${i} API is unreachable (${F(t)})`),e.json({error:`Session ${i} is not answering.`},502)}})}async function z(e){let l=e.host??`127.0.0.1`,d=e.onNotice??(()=>{}),f=_.createWebTelemetry(),p=se.createRemoteAccessStore({stateDir:e.remoteStateDir,onNotice:d}),b=e=>{try{return v.default.realpathSync((0,O.findRepositoryRoot)(e))}catch{return}},D=e=>{let t=b(e);if(t!==void 0)return t;let n=(0,_e.globalDoomConfigDirectory)();try{return(0,x.readSyncRegistration)(n)?.root}catch(e){d(`global cockpit composition is unreadable (${F(e)})`);return}},k=e.compositionDir===void 0?void 0:b(e.compositionDir);if(e.compositionDir!==void 0&&k===void 0)throw Error(`Cockpit composition directory is not inside a repository: ${e.compositionDir}`);let we=u.readRegistryRecords(e.registryDir,d).map(e=>D(e.cwd)).filter(e=>e!==void 0),A=[...new Set(k===void 0?we:[k])].sort((e,t)=>e.localeCompare(t)),j=new Map;for(let e of A)j.set(e,n.createSyncGuard({repoRoot:e,onNotice:d}));await Promise.all([...j.values()].map(async e=>await e.ensureSynced()));let M=async()=>await me.resolveWebComposition({repositoryRoots:A,stateDirectory:p.directory,onNotice:d}),L;try{L=await M()}catch(e){d(`the cockpit composition is unavailable (${F(e)}); serving the packaged bundle`),L=void 0}let z={assetsDir:Ae(e.assetsDir,L,d),apiDirectory:L?.apiDirectory},B=async e=>{let t=D(e);if(t===void 0)return;let r=j.get(t);if(r!==void 0){await r.ensureSynced();return}let i=n.createSyncGuard({repoRoot:t,onNotice:d});try{await i.ensureSynced()}finally{i.close()}},V=Me(e,d,await pe.loadHubChannels(z.assetsDir,d),f),H=fe.createThreadJournals({resolve:(e,t)=>V.threadJournal(e,t),onNotice:d}),U=new Set,W=(e,t)=>{for(let n of U)t&&!n.local||n.post(e)},G=new be.Hono,K=(0,ge.createNodeWebSocket)({app:G}),Fe,q=Oe(),J=ae.createBundlePublication({assetsDir:()=>z.assetsDir,stateDir:p.directory,onNotice:d}),Y;le.reapStaleTunnel(p.directory,d);let Ie=e.onHandover===void 0?void 0:t=>{let n=g.planSessionMigration(V.snapshot().map(e=>({id:e.id,cwd:e.cwd,...e.name===void 0?{}:{name:e.name}})),t.sandbox.workspaces);for(let e of g.describeStranded(n.stranded))d(e);for(let e of n.migrate){let t=V.stop(e.id);t.ok||d(`could not stop ${e.name??e.id} before the move: ${t.error}`)}e.onHandover?.({settings:t,sessions:n.migrate})},X=ie.createRemoteAccess({store:p,launchTunnel:e.remoteAccess?.launchTunnel??le.createTunnelLauncher({stateDir:p.directory,...e.cloudflaredPath===void 0?{}:{cloudflaredPath:e.cloudflaredPath},onNotice:d,onExit:e=>{d(`remote access: ${e}`),X.disable()}}),bindListener:async()=>await new Promise((e,t)=>{let n=(0,he.serve)({fetch:G.fetch,port:0,hostname:`127.0.0.1`,serverOptions:{maxHeaderSize:16384,headersTimeout:5e3,requestTimeout:1e4}},t=>{K.injectWebSocket(n),e({port:t.port,close:async()=>await new Promise(e=>{n.closeAllConnections?.(),n.close(()=>e())})})}),r=n;r.maxConnections=64,r.maxRequestsPerSocket=100,n.once(`error`,t)}),bundleTrust:()=>J.trust(),onDeviceDropped:e=>Y?.remove(e),onNotice:d,...Ie===void 0?{}:{requestHandover:Ie},contained:(0,Se.insideSandbox)(process.env),broadcastLocal:e=>W(e,!0),broadcastAll:e=>W(e,!1),...e.remoteAccess?.now===void 0?{}:{now:e.remoteAccess.now}});Y=oe.createLiveWebPush({stateDir:p.directory,isConnected:e=>X.isDeviceConnected(e),onNotice:d});let Le=V.onEvent(e=>{e.kind===`frame`&&c.parseDoomNotificationEntry(e.frame)!==void 0&&Y?.notify()}),Z=re.createRemoteGuard({loopbackPort:()=>Fe,tunnelPolicy:()=>X.tunnelPolicy(),authorize:e=>X.authorize((0,T.getCookie)(e,m.DEVICE_COOKIE,`host`)),trustedDevice:e=>e.env?.sealedDeviceId,channelReady:(e,t)=>X.channelFor(e,t)!==void 0,stepUp:{required:e=>X.stepUpRequired(e),verify:async(e,t,n)=>{if(typeof n!=`object`||!n)return!1;let r=n.ceremonyId,i=n.response;if(typeof r!=`string`)return!1;let a=e.env?.sealedDeviceId??X.authorize((0,T.getCookie)(e,`doompi_device`,`host`)),o=a===void 0?`local`:`device:${a}`;return await X.passkeys().finishStepUp(r,o,t,i)}},extraOrigins:h.allowedOriginsFromEnv(process.env.DOOMPI_WEB_ALLOW_ORIGIN)});G.use(`*`,Z.middleware),G.use(`*`,async(e,t)=>{let n=new URL(e.req.url).pathname;if(n===m.REMOTE_HTTP_ROUTE)return t();let r=_.requestOperation(n);if(r===void 0)return t();let i=performance.now();return await f.runInSpan(r,{"operation.name":r,"http.method":e.req.method},async()=>{await t(),await f.recordEvent(`web.api.request`,{"operation.name":r,"http.method":e.req.method,"http.status_code":e.res.status,duration_ms:Math.max(0,Math.round(performance.now()-i))})},_.readTraceContext(e.req.raw.headers))}),G.use(`*`,async(e,t)=>{if(Z.listenerOf(e)===`local`)return t();let n=Number(e.req.header(`content-length`));return Number.isFinite(n)&&n>Ce?e.json({error:`The tunnel request body is too large.`},413):t()}),ce.registerRemoteRoutes(G,{remote:X,listenerOf:e=>Z.listenerOf(e)});let Re=(0,E.bodyLimit)({maxSize:Ce,onError:e=>e.json({error:`The tunnel request body is too large.`},413)});G.use(`*`,async(e,t)=>Z.listenerOf(e)===`local`?t():await Re(e,t));let ze=(0,E.bodyLimit)({maxSize:32768,onError:e=>e.json({error:`The telemetry batch is too large.`},413)}),Be=_.createBrowserTelemetryRateLimit();G.post(_.WEB_TELEMETRY_ROUTE,ze,async e=>{if(!Be())return e.json({error:`Too many telemetry batches.`},429);let t;try{t=await e.req.json()}catch{return e.json({error:`The telemetry batch must be JSON.`},400)}let n=_.parseBrowserTelemetryBatch(t);if(n===void 0)return e.json({error:`The telemetry batch is invalid.`},400);for(let e of n){if(_.isBrowserErrorEvent(e)){await f.recordError(_.BROWSER_ERROR_EVENT,_.createBrowserError(e),{"browser.source":e.source,...e.session_id===void 0?{}:{"session.id":e.session_id},"failure.kind":`browser`},{includeException:!0});continue}await f.recordEvent(e.name,{...e.duration_ms===void 0?{}:{duration_ms:e.duration_ms},...e.count===void 0?{}:{count:e.count}})}return e.body(null,204)}),G.post(m.REMOTE_HTTP_ROUTE,async e=>{let t=X.authorize((0,T.getCookie)(e,m.DEVICE_COOKIE,`host`));if(t===void 0)return e.json({error:`This device is not paired.`},401);let n=X.channelFor(t,`http`);if(n===void 0)return e.json({error:`This device has no sealed HTTP channel.`},401);let r;try{r=await e.req.json()}catch{return e.json({error:`The sealed request envelope was malformed.`},400)}let i=n.open(r);if(!i.ok)return e.json({error:`The sealed request was refused: ${i.failure}.`},400);let a;try{a=JSON.parse(new TextDecoder().decode(i.plaintext))}catch{return e.json({error:`The sealed HTTP request was malformed.`},400)}let o=Te(a),s=o===void 0?void 0:Ee(o,e),c=o===void 0?void 0:De(o.body);if(o===void 0||s===void 0||c===void 0)return e.json({error:`The sealed HTTP request was invalid.`},400);let l=new URL(o.target,new URL(e.req.url).origin),u=await G.request(l,{method:o.method,headers:s,...o.method===`GET`||o.method===`HEAD`?{}:{body:Uint8Array.from(c).buffer}},{...e.env,sealedDeviceId:t}),d=Buffer.from(await u.arrayBuffer()),f=n.seal(new TextEncoder().encode(JSON.stringify({v:1,status:u.status,headers:Array.from(u.headers.entries()),body:d.toString(`base64`)})));return f.ok?e.json(f.envelope):e.json({error:`The sealed response failed: ${f.failure}.`},503)});let Ve=(0,E.bodyLimit)({maxSize:8192,onError:e=>e.json({error:`The Push subscription is too large.`},413)}),Q=e=>e.env?.sealedDeviceId;G.get(m.REMOTE_PUSH_KEY_ROUTE,e=>Q(e)===void 0?e.json({error:`A sealed paired device is required.`},401):e.json({publicKey:Y?.publicKey()},200,{"Cache-Control":`no-store`})),G.post(m.REMOTE_PUSH_ROUTE,Ve,async e=>{let t=Q(e);if(t===void 0)return e.json({error:`A sealed paired device is required.`},401);let n;try{n=await e.req.json()}catch{return e.json({error:`The Push subscription must be JSON.`},400)}return Y?.subscribe(t,n)===!0?e.body(null,204):e.json({error:`The Push subscription is invalid.`},400)}),G.delete(m.REMOTE_PUSH_ROUTE,e=>{let t=Q(e);return t===void 0?e.json({error:`A sealed paired device is required.`},401):(Y?.remove(t),e.body(null,204))});let He=r.createPiWebSocketListener({onError:e=>d(`protocol: ${e.message}`)}),Ue=r.createPiWebSocketListener({onError:e=>d(`protocol: ${e.message}`)}),We=t.createPiHubService({records:()=>V.records(),spawn:e=>V.create(e),onNotice:d}),Ge={...We,createSession:async()=>{throw new w.PiServerError(`not_implemented`,`Remote protocol clients cannot create sessions`)}},Ke=new w.PiServer(We,{listeners:[He],onError:e=>d(`protocol: ${e.message}`)}),qe=new w.PiServer(Ge,{listeners:[Ue],onError:e=>d(`protocol: ${e.message}`)});await Promise.all([Ke.start(),qe.start()]);let $=ne.createProviderAuth({runtime:e.authRuntime,onNotice:d});ee.registerAuthRoutes(G,$);let Je=R(V),Ye=()=>Je.list(),Xe=e=>Je.resolve(e),Ze=e=>{let t=Xe(e);if(t===void 0)return;let n=(0,x.readSyncDrift)({repoRoot:t}),r;try{r=(0,x.readSyncRegistration)(t)?(0,ve.readSyncState)(t):void 0}catch{r=void 0}return{fresh:n.fresh,reasons:[...n.reasons],...r===void 0?{}:{mcpProjection:r.fileState.mcpProjection}}};te.registerSettingsRoutes(G,{repositories:Ye,models:()=>$.listModels()});let Qe=z.apiDirectory,$e=Ne(G,Qe===void 0&&!process.env[xe.PACKAGE_API_DIR_ENV]?[]:await(0,xe.loadPackageApis)(`hub`,{apiDirectory:Qe,onNotice:d}),d,Xe,Ze);Pe(G,V,d,f),G.get(`/api/health`,e=>e.json({ok:!0,role:`hub`,protocol:1,sessions:V.snapshot().length,pid:process.pid,version:ke()})),G.post(s.SESSIONS_API_ROUTE,async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:`The request body must be JSON.`},400)}if(!P(t)||typeof t.cwd!=`string`||t.cwd===``)return e.json({error:`A cwd string is required.`},400);let n=typeof t.name==`string`&&t.name!==``?t.name:void 0;await B(t.cwd);let r=await V.create({cwd:t.cwd,name:n,trace:_.readTraceContext(e.req.raw.headers)});return r.ok?e.json({sessionId:r.sessionId},201):e.json({error:r.error},r.code===`invalid_request`?400:502)}),G.post(`${s.SESSIONS_API_ROUTE}/:sessionId/restart`,async e=>{let t=e.req.param(`sessionId`),n=V.snapshot().find(e=>e.id===t);n!==void 0&&await B(n.cwd);let r=await V.restart(t,_.readTraceContext(e.req.raw.headers));return r.ok?e.json({sessionId:r.sessionId},202):e.json({error:r.error},r.code===`invalid_request`?404:502)}),G.get(`${s.SESSIONS_API_ROUTE}/:sessionId/history`,async e=>{let t=e.req.param(`sessionId`),n=V.snapshot().find(e=>e.id===t);if(!n)return e.json({error:`Unknown session.`},404);let r=await C.SessionManager.list(n.cwd);return e.json({sessions:r.map(e=>({id:e.id,...e.name===void 0?{}:{name:e.name},firstMessage:e.firstMessage,createdAt:e.created.toISOString(),updatedAt:e.modified.toISOString(),messageCount:e.messageCount}))})}),G.post(`${s.SESSIONS_API_ROUTE}/:sessionId/resume`,async e=>{let t=e.req.param(`sessionId`),n=V.snapshot().find(e=>e.id===t);if(!n)return e.json({error:`Unknown session.`},404);let r;try{r=await e.req.json()}catch{return e.json({error:`The request body must be JSON.`},400)}if(!P(r)||typeof r.targetSessionId!=`string`||r.targetSessionId===``)return e.json({error:`A targetSessionId string is required.`},400);let i=(await C.SessionManager.list(n.cwd)).find(e=>e.id===r.targetSessionId);if(!i)return e.json({error:`That Pi thread does not belong to this workspace.`},404);await B(n.cwd);let a=await V.resume(t,{sessionId:i.id,name:je(i,n.cwd)},_.readTraceContext(e.req.raw.headers));return a.ok?e.json({sessionId:a.sessionId},202):e.json({error:a.error},a.code===`invalid_request`?409:502)}),G.delete(`${s.SESSIONS_API_ROUTE}/:sessionId`,e=>{let t=e.req.param(`sessionId`),n=V.stop(t);return n.ok?e.json({sessionId:t},202):e.json({error:n.error},n.code===`unknown`?404:n.code===`self`?409:502)}),G.get(`/api/sessions/:sessionId/files`,async e=>{let t=e.req.param(`sessionId`),n=V.snapshot().find(e=>e.id===t);if(!n)return e.json({error:`Unknown session.`},404);let r=e.req.query(`q`)??``,i=await de.listSessionFiles(n.cwd,r,20);return e.json({files:i})});let et=t=>{if(!(Z.listenerOf(t)===`local`||(0,Se.insideSandbox)(process.env)))return e.browseRoot??process.cwd()};G.get(s.DIRECTORIES_API_ROUTE,async e=>{let t=et(e),n=await ue.suggestDirectories(e.req.query(`q`)??``,{limit:12,...t===void 0?{}:{root:t}});return e.json({directories:n})}),G.get(i.SESSION_FILE_ROUTE,async e=>{let t=e.req.param(`sessionId`),n=V.snapshot().find(e=>e.id===t);if(!n)return e.json({error:`Unknown session.`},404);let r=e.req.query(`path`)??``,o=await de.readSessionFile(n.cwd,r,i.MAX_SESSION_FILE_BYTES);return o.status===`forbidden`?e.json({error:`The path leaves the session directory.`},403):o.status===`not-found`?e.json({error:`No such file.`},404):o.status===`too-large`?e.json({error:`The file is too large to preview.`},413):e.body(new Uint8Array(o.body),200,a.sessionFileHeaders(r))}),G.get(m.SESSION_SOCKET_ROUTE,K.upgradeWebSocket(e=>{let t=Z.listenerOf(e)===`local`,n=t?void 0:X.authorize((0,T.getCookie)(e,m.DEVICE_COOKIE,`host`)),r=n===void 0?void 0:X.channelFor(n,`session`),i=new Set,a=(0,S.randomUUID)(),o=new Map,l,u,f,p,h=e=>{for(let[t,n]of o)(e===void 0||n.sessionId===e)&&(o.delete(t),H.unsubscribe(n.sessionId,n.threadId))};return{onOpen(e,m){if(!t&&r===void 0){m.close(1008,`sealed session channel required`);return}let g=e=>{let n=JSON.stringify(e),i=n;if(!t){if(r===void 0)return;let e=r.seal(new TextEncoder().encode(n));if(!e.ok){m.close(1008,`sealed session channel failed`);return}i=JSON.stringify(e.envelope)}try{m.send(i)}catch{}};f={post:g,local:t},U.add(f),n!==void 0&&(p=X.trackSocket(n,(e,t)=>m.close(e,t))),g(s.hubHello(V.channelTypes())),g({type:s.SESSIONS_SNAPSHOT_TYPE,sessions:V.snapshot()}),l=V.onEvent(e=>{e.kind===`upsert`?g({type:s.SESSION_UPSERT_TYPE,session:e.session}):e.kind===`removed`?(i.delete(e.sessionId),h(e.sessionId),g({type:s.SESSION_REMOVED_TYPE,sessionId:e.sessionId})):e.kind===`channel`?(e.connectionId===a||e.connectionId===void 0&&i.has(e.sessionId))&&g({type:e.frameType,sessionId:e.sessionId,payload:e.payload}):(c.parseDoomNotificationEntry(e.frame)!==void 0||i.has(e.sessionId))&&g(s.sessionFrameEnvelope(e.sessionId,e.frame))}),u=H.onFrame(e=>{o.has(I(e.sessionId,e.threadId))&&g(s.threadFrameEnvelope(e.sessionId,e.threadId,e.frame))}),d(`browser attached`)},onMessage(e){if(typeof e.data!=`string`)return;let n;try{n=JSON.parse(e.data)}catch{return}if(!t){if(r===void 0)return;let e=r.open(n);if(!e.ok)return;try{n=JSON.parse(new TextDecoder().decode(e.plaintext))}catch{return}}if(!P(n)||typeof n.sessionId!=`string`)return;let c=n.sessionId;if(typeof n.type==`string`&&V.channelTypes().includes(n.type)&&(i.has(c)||V.channelReceivesWithoutSubscription(n.type))){V.receiveChannel(c,n.type,n.payload,a);return}if(n.type===`subscribe`){let e=V.backlog(c);if(!e)return;i.add(c),f?.post(e);for(let e of V.channelFrames(c))f?.post(e);return}if(n.type===`unsubscribe`){i.delete(c);return}if(n.type===`history_request`){let e=V.history(c,{...typeof n.before==`string`?{before:n.before}:{},...typeof n.limit==`number`?{limit:n.limit}:{}});if(!e)return;f?.post(e);return}if(n.type===`subscribe_thread`||n.type===`unsubscribe_thread`){if(typeof n.threadId!=`string`)return;let e=n.threadId,t=I(c,e);if(n.type===`unsubscribe_thread`){o.delete(t)&&H.unsubscribe(c,e);return}if(o.has(t)||V.backlog(c)===void 0)return;o.set(t,{sessionId:c,threadId:e}),f?.post(s.threadBacklog(c,e,H.subscribe(c,e)));return}if(n.type===`session_command`&&P(n.frame)){if(n.frame.type===`attach`)return;V.command(c,n.frame)}},onClose(){V.disconnectChannels(a),f&&U.delete(f),f=void 0,p?.(),p=void 0,l?.(),l=void 0,u?.(),u=void 0,i.clear(),h(),d(`browser detached`)}}})),G.get(m.PROTOCOL_SOCKET_ROUTE,K.upgradeWebSocket(e=>{let t=Z.listenerOf(e)===`local`,n=t?void 0:X.authorize((0,T.getCookie)(e,m.DEVICE_COOKIE,`host`)),r=n===void 0?void 0:X.channelFor(n,`protocol`),i,a;return{onOpen(e,o){if(!t&&r===void 0){o.close(1008,`sealed protocol channel required`);return}n!==void 0&&(a=X.trackSocket(n,(e,t)=>o.close(e,t))),i=(t?He:Ue).accept({send:e=>{if(t){o.send(e);return}if(r===void 0)return;let n=e instanceof Uint8Array?e:new Uint8Array(e),i=r.seal(n);if(!i.ok){o.close(1008,`sealed protocol channel failed`);return}o.send(new TextEncoder().encode(JSON.stringify(i.envelope)))},close:()=>o.close(),get readyState(){return o.readyState}})},async onMessage(e){let n=e.data;if(typeof n==`string`)return;let a=n instanceof Blob?new Uint8Array(await n.arrayBuffer()):(n instanceof ArrayBuffer,new Uint8Array(n));if(t){i?.onData(a);return}if(r===void 0)return;let o;try{o=JSON.parse(new TextDecoder().decode(a))}catch{return}let s=r.open(o);s.ok&&i?.onData(s.plaintext)},onClose(){a?.(),a=void 0,i?.onClose(),i=void 0},onError(e){i?.onError(e instanceof Error?e:Error(`The protocol socket failed`)),i=void 0}}})),G.get(`/manifest.webmanifest`,e=>{let t=N(y.default.join(q,`manifest.webmanifest`));return t===void 0?e.text(`The PWA manifest is unavailable.`,404):e.body(new Uint8Array(t),200,{"Cache-Control":`no-store`,"Content-Type":`application/manifest+json; charset=utf-8`})}),G.get(`/sw.js`,e=>{let t=N(y.default.join(q,`sw.js`));return t===void 0?e.text(`The trusted service worker is unavailable.`,404):e.body(new Uint8Array(t),200,{"Cache-Control":`no-cache`,"Content-Type":`text/javascript; charset=utf-8`,"Service-Worker-Allowed":`/`})}),G.get(`/pwa/*`,e=>{let t=new URL(e.req.url).pathname.slice(5),n=o.resolveAssetPath(q,`/${t}`),r=n===void 0?void 0:N(n);return n===void 0||r===void 0?e.text(`PWA asset not found.`,404):e.body(new Uint8Array(r),200,{"Cache-Control":`no-store`,"Content-Type":o.contentTypeFor(n),"X-Content-Type-Options":`nosniff`})}),G.get(ye.BUNDLE_MANIFEST_ROUTE,e=>{let t=J.current();return t===void 0?e.json({error:`No signed cockpit bundle is available.`},503):e.json(t.signed,200,{"Cache-Control":`no-store`})}),G.get(`/bundle-assets/*`,e=>{let t=new URL(e.req.url).pathname,n=/^\/bundle-assets\/([1-9][0-9]*)(\/.*)$/u.exec(t),r=n===null?void 0:Number(n[1]),i=J.current();if(n===null||!Number.isSafeInteger(r)||i===void 0||i.signed.manifest.revision!==r)return e.text(`That signed bundle revision is unavailable.`,404);let a=(0,ye.assetFor)(i.signed.manifest,n[2]??``),s=a===void 0?void 0:o.resolveAssetPath(i.assetsDir,a.path),c=s===void 0?void 0:N(s);return a===void 0||s===void 0||c===void 0?e.text(`Bundle asset not found.`,404):e.body(new Uint8Array(c),200,{"Cache-Control":`no-store`,"Content-Length":String(c.byteLength),"Content-Type":a.contentType,"X-Content-Type-Options":`nosniff`})}),G.get(`/`,e=>e.redirect(m.PAIRING_PAGE_ROUTE)),G.get(`*`,e=>{let t=new URL(e.req.url).pathname,n=e.req.header(`accept`)?.includes(`text/html`)===!0;return!t.startsWith(`/api/`)&&n?e.redirect(`/`):e.text(`Not found.`,404)});let tt=async()=>{try{let e=await M();e!==void 0&&e.webDirectory!==z.assetsDir&&(z={assetsDir:e.webDirectory,apiDirectory:e.apiDirectory},d(`serving the rebuilt cockpit bundle from ${z.assetsDir}`)),J.refresh(),W({type:s.HUB_RESYNCED_TYPE},!1)}catch(e){d(`the rebuilt cockpit composition is unavailable (${F(e)}); keeping the served bundle`)}};for(let e of j.values())e.watch(()=>void tt());return new Promise((t,n)=>{let r=(0,he.serve)({fetch:G.fetch,port:e.port,hostname:l},e=>{Fe=e.port,K.injectWebSocket(r);let n=`http://${l}:${e.port}`;d(`cockpit on ${n}`);let i,a=async()=>{await X.close(),await new Promise(e=>{H.close();for(let e of j.values())e.close();Ke.close(),qe.close(),Le(),Y?.close(),V.close(),$.close();for(let e of $e)e.close();for(let e of K.wss.clients)e.terminate();K.wss.close(),r.closeAllConnections?.(),r.close(()=>e())}),await _.shutdownWebTelemetry(f)};t({url:n,port:e.port,close:()=>i??=a()})});r.once(`error`,e=>{H.close(),Le(),Y?.close(),V.close(),$.close(),X.close();for(let e of $e)e.close();f.shutdown(),n(e)})})}exports.createSettingsRepositoryRegistry=R,exports.packagedVersion=ke,exports.serveWeb=z;
|
|
3
3
|
//# sourceMappingURL=httpServer.cjs.map
|