hoi4-agent-tools 0.0.0-bootstrap.1 → 0.1.3
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/CHANGELOG.md +50 -0
- package/LICENSE +201 -0
- package/README.md +140 -2
- package/SECURITY.md +17 -0
- package/dist/bin/http.d.ts +3 -0
- package/dist/bin/http.d.ts.map +1 -0
- package/dist/bin/http.js +27 -0
- package/dist/bin/http.js.map +1 -0
- package/dist/bin/setup.d.ts +3 -0
- package/dist/bin/setup.d.ts.map +1 -0
- package/dist/bin/setup.js +188 -0
- package/dist/bin/setup.js.map +1 -0
- package/dist/bin/stdio.d.ts +3 -0
- package/dist/bin/stdio.d.ts.map +1 -0
- package/dist/bin/stdio.js +36 -0
- package/dist/bin/stdio.js.map +1 -0
- package/dist/hoi4_agent_tools/core/artifacts.d.ts +117 -0
- package/dist/hoi4_agent_tools/core/artifacts.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/artifacts.js +1047 -0
- package/dist/hoi4_agent_tools/core/artifacts.js.map +1 -0
- package/dist/hoi4_agent_tools/core/canonical.d.ts +13 -0
- package/dist/hoi4_agent_tools/core/canonical.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/canonical.js +41 -0
- package/dist/hoi4_agent_tools/core/canonical.js.map +1 -0
- package/dist/hoi4_agent_tools/core/configuration.d.ts +137 -0
- package/dist/hoi4_agent_tools/core/configuration.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/configuration.js +383 -0
- package/dist/hoi4_agent_tools/core/configuration.js.map +1 -0
- package/dist/hoi4_agent_tools/core/diagnostic-collector.d.ts +18 -0
- package/dist/hoi4_agent_tools/core/diagnostic-collector.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/diagnostic-collector.js +45 -0
- package/dist/hoi4_agent_tools/core/diagnostic-collector.js.map +1 -0
- package/dist/hoi4_agent_tools/core/diagnostics.d.ts +26 -0
- package/dist/hoi4_agent_tools/core/diagnostics.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/diagnostics.js +25 -0
- package/dist/hoi4_agent_tools/core/diagnostics.js.map +1 -0
- package/dist/hoi4_agent_tools/core/diff.d.ts +22 -0
- package/dist/hoi4_agent_tools/core/diff.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/diff.js +93 -0
- package/dist/hoi4_agent_tools/core/diff.js.map +1 -0
- package/dist/hoi4_agent_tools/core/engine.d.ts +56 -0
- package/dist/hoi4_agent_tools/core/engine.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/engine.js +319 -0
- package/dist/hoi4_agent_tools/core/engine.js.map +1 -0
- package/dist/hoi4_agent_tools/core/image-diff.d.ts +14 -0
- package/dist/hoi4_agent_tools/core/image-diff.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/image-diff.js +121 -0
- package/dist/hoi4_agent_tools/core/image-diff.js.map +1 -0
- package/dist/hoi4_agent_tools/core/index.d.ts +64 -0
- package/dist/hoi4_agent_tools/core/index.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/index.js +963 -0
- package/dist/hoi4_agent_tools/core/index.js.map +1 -0
- package/dist/hoi4_agent_tools/core/index.public.d.ts +16 -0
- package/dist/hoi4_agent_tools/core/index.public.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/index.public.js +16 -0
- package/dist/hoi4_agent_tools/core/index.public.js.map +1 -0
- package/dist/hoi4_agent_tools/core/render-budget.d.ts +51 -0
- package/dist/hoi4_agent_tools/core/render-budget.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/render-budget.js +109 -0
- package/dist/hoi4_agent_tools/core/render-budget.js.map +1 -0
- package/dist/hoi4_agent_tools/core/result.d.ts +45 -0
- package/dist/hoi4_agent_tools/core/result.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/result.js +26 -0
- package/dist/hoi4_agent_tools/core/result.js.map +1 -0
- package/dist/hoi4_agent_tools/core/scanner.d.ts +28 -0
- package/dist/hoi4_agent_tools/core/scanner.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/scanner.js +136 -0
- package/dist/hoi4_agent_tools/core/scanner.js.map +1 -0
- package/dist/hoi4_agent_tools/core/server-state.d.ts +31 -0
- package/dist/hoi4_agent_tools/core/server-state.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/server-state.js +413 -0
- package/dist/hoi4_agent_tools/core/server-state.js.map +1 -0
- package/dist/hoi4_agent_tools/core/source/encoding.d.ts +10 -0
- package/dist/hoi4_agent_tools/core/source/encoding.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/source/encoding.js +54 -0
- package/dist/hoi4_agent_tools/core/source/encoding.js.map +1 -0
- package/dist/hoi4_agent_tools/core/source/index.d.ts +7 -0
- package/dist/hoi4_agent_tools/core/source/index.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/source/index.js +7 -0
- package/dist/hoi4_agent_tools/core/source/index.js.map +1 -0
- package/dist/hoi4_agent_tools/core/source/lexer.d.ts +30 -0
- package/dist/hoi4_agent_tools/core/source/lexer.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/source/lexer.js +266 -0
- package/dist/hoi4_agent_tools/core/source/lexer.js.map +1 -0
- package/dist/hoi4_agent_tools/core/source/limits.d.ts +21 -0
- package/dist/hoi4_agent_tools/core/source/limits.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/source/limits.js +49 -0
- package/dist/hoi4_agent_tools/core/source/limits.js.map +1 -0
- package/dist/hoi4_agent_tools/core/source/localisation.d.ts +20 -0
- package/dist/hoi4_agent_tools/core/source/localisation.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/source/localisation.js +163 -0
- package/dist/hoi4_agent_tools/core/source/localisation.js.map +1 -0
- package/dist/hoi4_agent_tools/core/source/parser.d.ts +44 -0
- package/dist/hoi4_agent_tools/core/source/parser.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/source/parser.js +334 -0
- package/dist/hoi4_agent_tools/core/source/parser.js.map +1 -0
- package/dist/hoi4_agent_tools/core/source/rewrite.d.ts +10 -0
- package/dist/hoi4_agent_tools/core/source/rewrite.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/source/rewrite.js +28 -0
- package/dist/hoi4_agent_tools/core/source/rewrite.js.map +1 -0
- package/dist/hoi4_agent_tools/core/svg-text.d.ts +34 -0
- package/dist/hoi4_agent_tools/core/svg-text.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/svg-text.js +127 -0
- package/dist/hoi4_agent_tools/core/svg-text.js.map +1 -0
- package/dist/hoi4_agent_tools/core/transaction-limits.d.ts +9 -0
- package/dist/hoi4_agent_tools/core/transaction-limits.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/transaction-limits.js +9 -0
- package/dist/hoi4_agent_tools/core/transaction-limits.js.map +1 -0
- package/dist/hoi4_agent_tools/core/transactions.d.ts +162 -0
- package/dist/hoi4_agent_tools/core/transactions.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/transactions.js +1181 -0
- package/dist/hoi4_agent_tools/core/transactions.js.map +1 -0
- package/dist/hoi4_agent_tools/core/workspace.d.ts +68 -0
- package/dist/hoi4_agent_tools/core/workspace.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/core/workspace.js +794 -0
- package/dist/hoi4_agent_tools/core/workspace.js.map +1 -0
- package/dist/hoi4_agent_tools/focus/compiler.d.ts +9 -0
- package/dist/hoi4_agent_tools/focus/compiler.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/focus/compiler.js +273 -0
- package/dist/hoi4_agent_tools/focus/compiler.js.map +1 -0
- package/dist/hoi4_agent_tools/focus/continuous-render.d.ts +32 -0
- package/dist/hoi4_agent_tools/focus/continuous-render.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/focus/continuous-render.js +179 -0
- package/dist/hoi4_agent_tools/focus/continuous-render.js.map +1 -0
- package/dist/hoi4_agent_tools/focus/drift.d.ts +22 -0
- package/dist/hoi4_agent_tools/focus/drift.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/focus/drift.js +124 -0
- package/dist/hoi4_agent_tools/focus/drift.js.map +1 -0
- package/dist/hoi4_agent_tools/focus/geometry.d.ts +17 -0
- package/dist/hoi4_agent_tools/focus/geometry.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/focus/geometry.js +35 -0
- package/dist/hoi4_agent_tools/focus/geometry.js.map +1 -0
- package/dist/hoi4_agent_tools/focus/importer.d.ts +9 -0
- package/dist/hoi4_agent_tools/focus/importer.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/focus/importer.js +732 -0
- package/dist/hoi4_agent_tools/focus/importer.js.map +1 -0
- package/dist/hoi4_agent_tools/focus/index.d.ts +14 -0
- package/dist/hoi4_agent_tools/focus/index.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/focus/index.js +14 -0
- package/dist/hoi4_agent_tools/focus/index.js.map +1 -0
- package/dist/hoi4_agent_tools/focus/layout.d.ts +5 -0
- package/dist/hoi4_agent_tools/focus/layout.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/focus/layout.js +653 -0
- package/dist/hoi4_agent_tools/focus/layout.js.map +1 -0
- package/dist/hoi4_agent_tools/focus/limits.d.ts +9 -0
- package/dist/hoi4_agent_tools/focus/limits.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/focus/limits.js +9 -0
- package/dist/hoi4_agent_tools/focus/limits.js.map +1 -0
- package/dist/hoi4_agent_tools/focus/lint.d.ts +14 -0
- package/dist/hoi4_agent_tools/focus/lint.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/focus/lint.js +839 -0
- package/dist/hoi4_agent_tools/focus/lint.js.map +1 -0
- package/dist/hoi4_agent_tools/focus/model.d.ts +303 -0
- package/dist/hoi4_agent_tools/focus/model.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/focus/model.js +20 -0
- package/dist/hoi4_agent_tools/focus/model.js.map +1 -0
- package/dist/hoi4_agent_tools/focus/native-effects.d.ts +9 -0
- package/dist/hoi4_agent_tools/focus/native-effects.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/focus/native-effects.js +563 -0
- package/dist/hoi4_agent_tools/focus/native-effects.js.map +1 -0
- package/dist/hoi4_agent_tools/focus/planning.d.ts +15 -0
- package/dist/hoi4_agent_tools/focus/planning.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/focus/planning.js +241 -0
- package/dist/hoi4_agent_tools/focus/planning.js.map +1 -0
- package/dist/hoi4_agent_tools/focus/presentation.d.ts +24 -0
- package/dist/hoi4_agent_tools/focus/presentation.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/focus/presentation.js +204 -0
- package/dist/hoi4_agent_tools/focus/presentation.js.map +1 -0
- package/dist/hoi4_agent_tools/focus/render.d.ts +35 -0
- package/dist/hoi4_agent_tools/focus/render.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/focus/render.js +491 -0
- package/dist/hoi4_agent_tools/focus/render.js.map +1 -0
- package/dist/hoi4_agent_tools/focus/service.d.ts +82 -0
- package/dist/hoi4_agent_tools/focus/service.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/focus/service.js +492 -0
- package/dist/hoi4_agent_tools/focus/service.js.map +1 -0
- package/dist/hoi4_agent_tools/focus/source-update.d.ts +6 -0
- package/dist/hoi4_agent_tools/focus/source-update.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/focus/source-update.js +328 -0
- package/dist/hoi4_agent_tools/focus/source-update.js.map +1 -0
- package/dist/hoi4_agent_tools/gui/animation-manifest.d.ts +37 -0
- package/dist/hoi4_agent_tools/gui/animation-manifest.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/gui/animation-manifest.js +66 -0
- package/dist/hoi4_agent_tools/gui/animation-manifest.js.map +1 -0
- package/dist/hoi4_agent_tools/gui/assets.d.ts +71 -0
- package/dist/hoi4_agent_tools/gui/assets.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/gui/assets.js +629 -0
- package/dist/hoi4_agent_tools/gui/assets.js.map +1 -0
- package/dist/hoi4_agent_tools/gui/dds.d.ts +17 -0
- package/dist/hoi4_agent_tools/gui/dds.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/gui/dds.js +245 -0
- package/dist/hoi4_agent_tools/gui/dds.js.map +1 -0
- package/dist/hoi4_agent_tools/gui/helpers.d.ts +101 -0
- package/dist/hoi4_agent_tools/gui/helpers.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/gui/helpers.js +1072 -0
- package/dist/hoi4_agent_tools/gui/helpers.js.map +1 -0
- package/dist/hoi4_agent_tools/gui/index.d.ts +15 -0
- package/dist/hoi4_agent_tools/gui/index.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/gui/index.js +15 -0
- package/dist/hoi4_agent_tools/gui/index.js.map +1 -0
- package/dist/hoi4_agent_tools/gui/layout.d.ts +6 -0
- package/dist/hoi4_agent_tools/gui/layout.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/gui/layout.js +753 -0
- package/dist/hoi4_agent_tools/gui/layout.js.map +1 -0
- package/dist/hoi4_agent_tools/gui/limits.d.ts +25 -0
- package/dist/hoi4_agent_tools/gui/limits.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/gui/limits.js +27 -0
- package/dist/hoi4_agent_tools/gui/limits.js.map +1 -0
- package/dist/hoi4_agent_tools/gui/renderer.d.ts +18 -0
- package/dist/hoi4_agent_tools/gui/renderer.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/gui/renderer.js +388 -0
- package/dist/hoi4_agent_tools/gui/renderer.js.map +1 -0
- package/dist/hoi4_agent_tools/gui/scenario.d.ts +66 -0
- package/dist/hoi4_agent_tools/gui/scenario.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/gui/scenario.js +191 -0
- package/dist/hoi4_agent_tools/gui/scenario.js.map +1 -0
- package/dist/hoi4_agent_tools/gui/source-graph.d.ts +7 -0
- package/dist/hoi4_agent_tools/gui/source-graph.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/gui/source-graph.js +1074 -0
- package/dist/hoi4_agent_tools/gui/source-graph.js.map +1 -0
- package/dist/hoi4_agent_tools/gui/source-patch.d.ts +13 -0
- package/dist/hoi4_agent_tools/gui/source-patch.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/gui/source-patch.js +90 -0
- package/dist/hoi4_agent_tools/gui/source-patch.js.map +1 -0
- package/dist/hoi4_agent_tools/gui/studio.d.ts +86 -0
- package/dist/hoi4_agent_tools/gui/studio.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/gui/studio.js +830 -0
- package/dist/hoi4_agent_tools/gui/studio.js.map +1 -0
- package/dist/hoi4_agent_tools/gui/tga.d.ts +16 -0
- package/dist/hoi4_agent_tools/gui/tga.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/gui/tga.js +157 -0
- package/dist/hoi4_agent_tools/gui/tga.js.map +1 -0
- package/dist/hoi4_agent_tools/gui/types.d.ts +322 -0
- package/dist/hoi4_agent_tools/gui/types.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/gui/types.js +19 -0
- package/dist/hoi4_agent_tools/gui/types.js.map +1 -0
- package/dist/hoi4_agent_tools/gui/validators.d.ts +8 -0
- package/dist/hoi4_agent_tools/gui/validators.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/gui/validators.js +951 -0
- package/dist/hoi4_agent_tools/gui/validators.js.map +1 -0
- package/dist/hoi4_agent_tools/map/bmp.d.ts +62 -0
- package/dist/hoi4_agent_tools/map/bmp.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/map/bmp.js +363 -0
- package/dist/hoi4_agent_tools/map/bmp.js.map +1 -0
- package/dist/hoi4_agent_tools/map/diagnostic-limit.d.ts +5 -0
- package/dist/hoi4_agent_tools/map/diagnostic-limit.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/map/diagnostic-limit.js +21 -0
- package/dist/hoi4_agent_tools/map/diagnostic-limit.js.map +1 -0
- package/dist/hoi4_agent_tools/map/index.d.ts +8 -0
- package/dist/hoi4_agent_tools/map/index.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/map/index.js +8 -0
- package/dist/hoi4_agent_tools/map/index.js.map +1 -0
- package/dist/hoi4_agent_tools/map/limits.d.ts +13 -0
- package/dist/hoi4_agent_tools/map/limits.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/map/limits.js +13 -0
- package/dist/hoi4_agent_tools/map/limits.js.map +1 -0
- package/dist/hoi4_agent_tools/map/model.d.ts +269 -0
- package/dist/hoi4_agent_tools/map/model.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/map/model.js +1114 -0
- package/dist/hoi4_agent_tools/map/model.js.map +1 -0
- package/dist/hoi4_agent_tools/map/operations.d.ts +401 -0
- package/dist/hoi4_agent_tools/map/operations.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/map/operations.js +2717 -0
- package/dist/hoi4_agent_tools/map/operations.js.map +1 -0
- package/dist/hoi4_agent_tools/map/render.d.ts +90 -0
- package/dist/hoi4_agent_tools/map/render.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/map/render.js +759 -0
- package/dist/hoi4_agent_tools/map/render.js.map +1 -0
- package/dist/hoi4_agent_tools/map/service.d.ts +81 -0
- package/dist/hoi4_agent_tools/map/service.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/map/service.js +695 -0
- package/dist/hoi4_agent_tools/map/service.js.map +1 -0
- package/dist/hoi4_agent_tools/map/validation.d.ts +23 -0
- package/dist/hoi4_agent_tools/map/validation.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/map/validation.js +1177 -0
- package/dist/hoi4_agent_tools/map/validation.js.map +1 -0
- package/dist/hoi4_agent_tools/mcp/prompts/register.d.ts +3 -0
- package/dist/hoi4_agent_tools/mcp/prompts/register.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/mcp/prompts/register.js +50 -0
- package/dist/hoi4_agent_tools/mcp/prompts/register.js.map +1 -0
- package/dist/hoi4_agent_tools/mcp/registry/metadata.d.ts +9 -0
- package/dist/hoi4_agent_tools/mcp/registry/metadata.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/mcp/registry/metadata.js +10 -0
- package/dist/hoi4_agent_tools/mcp/registry/metadata.js.map +1 -0
- package/dist/hoi4_agent_tools/mcp/resources/register.d.ts +5 -0
- package/dist/hoi4_agent_tools/mcp/resources/register.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/mcp/resources/register.js +278 -0
- package/dist/hoi4_agent_tools/mcp/resources/register.js.map +1 -0
- package/dist/hoi4_agent_tools/mcp/security/auth.d.ts +24 -0
- package/dist/hoi4_agent_tools/mcp/security/auth.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/mcp/security/auth.js +234 -0
- package/dist/hoi4_agent_tools/mcp/security/auth.js.map +1 -0
- package/dist/hoi4_agent_tools/mcp/server/base-tools.d.ts +35 -0
- package/dist/hoi4_agent_tools/mcp/server/base-tools.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/mcp/server/base-tools.js +835 -0
- package/dist/hoi4_agent_tools/mcp/server/base-tools.js.map +1 -0
- package/dist/hoi4_agent_tools/mcp/server/create.d.ts +5 -0
- package/dist/hoi4_agent_tools/mcp/server/create.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/mcp/server/create.js +26 -0
- package/dist/hoi4_agent_tools/mcp/server/create.js.map +1 -0
- package/dist/hoi4_agent_tools/mcp/server/output-schemas.d.ts +133 -0
- package/dist/hoi4_agent_tools/mcp/server/output-schemas.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/mcp/server/output-schemas.js +127 -0
- package/dist/hoi4_agent_tools/mcp/server/output-schemas.js.map +1 -0
- package/dist/hoi4_agent_tools/mcp/server/progress.d.ts +8 -0
- package/dist/hoi4_agent_tools/mcp/server/progress.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/mcp/server/progress.js +21 -0
- package/dist/hoi4_agent_tools/mcp/server/progress.js.map +1 -0
- package/dist/hoi4_agent_tools/mcp/server/result.d.ts +232 -0
- package/dist/hoi4_agent_tools/mcp/server/result.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/mcp/server/result.js +353 -0
- package/dist/hoi4_agent_tools/mcp/server/result.js.map +1 -0
- package/dist/hoi4_agent_tools/mcp/tools/focus.d.ts +5 -0
- package/dist/hoi4_agent_tools/mcp/tools/focus.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/mcp/tools/focus.js +1059 -0
- package/dist/hoi4_agent_tools/mcp/tools/focus.js.map +1 -0
- package/dist/hoi4_agent_tools/mcp/tools/gui.d.ts +111 -0
- package/dist/hoi4_agent_tools/mcp/tools/gui.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +598 -0
- package/dist/hoi4_agent_tools/mcp/tools/gui.js.map +1 -0
- package/dist/hoi4_agent_tools/mcp/tools/map.d.ts +5 -0
- package/dist/hoi4_agent_tools/mcp/tools/map.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/mcp/tools/map.js +685 -0
- package/dist/hoi4_agent_tools/mcp/tools/map.js.map +1 -0
- package/dist/hoi4_agent_tools/mcp/transports/bounded-stdio.d.ts +58 -0
- package/dist/hoi4_agent_tools/mcp/transports/bounded-stdio.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/mcp/transports/bounded-stdio.js +181 -0
- package/dist/hoi4_agent_tools/mcp/transports/bounded-stdio.js.map +1 -0
- package/dist/hoi4_agent_tools/mcp/transports/event-store.d.ts +26 -0
- package/dist/hoi4_agent_tools/mcp/transports/event-store.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/mcp/transports/event-store.js +86 -0
- package/dist/hoi4_agent_tools/mcp/transports/event-store.js.map +1 -0
- package/dist/hoi4_agent_tools/mcp/transports/http.d.ts +28 -0
- package/dist/hoi4_agent_tools/mcp/transports/http.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/mcp/transports/http.js +744 -0
- package/dist/hoi4_agent_tools/mcp/transports/http.js.map +1 -0
- package/dist/hoi4_agent_tools/mcp/transports/protocol-gate.d.ts +15 -0
- package/dist/hoi4_agent_tools/mcp/transports/protocol-gate.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/mcp/transports/protocol-gate.js +51 -0
- package/dist/hoi4_agent_tools/mcp/transports/protocol-gate.js.map +1 -0
- package/dist/hoi4_agent_tools/runtime.d.ts +4 -0
- package/dist/hoi4_agent_tools/runtime.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/runtime.js +27 -0
- package/dist/hoi4_agent_tools/runtime.js.map +1 -0
- package/dist/hoi4_agent_tools/schemas/common.d.ts +74 -0
- package/dist/hoi4_agent_tools/schemas/common.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/schemas/common.js +51 -0
- package/dist/hoi4_agent_tools/schemas/common.js.map +1 -0
- package/dist/hoi4_agent_tools/schemas/focus.d.ts +784 -0
- package/dist/hoi4_agent_tools/schemas/focus.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/schemas/focus.js +330 -0
- package/dist/hoi4_agent_tools/schemas/focus.js.map +1 -0
- package/dist/hoi4_agent_tools/schemas/index.d.ts +5 -0
- package/dist/hoi4_agent_tools/schemas/index.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/schemas/index.js +5 -0
- package/dist/hoi4_agent_tools/schemas/index.js.map +1 -0
- package/dist/hoi4_agent_tools/schemas/map.d.ts +654 -0
- package/dist/hoi4_agent_tools/schemas/map.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/schemas/map.js +570 -0
- package/dist/hoi4_agent_tools/schemas/map.js.map +1 -0
- package/dist/hoi4_agent_tools/schemas/transaction.d.ts +259 -0
- package/dist/hoi4_agent_tools/schemas/transaction.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/schemas/transaction.js +104 -0
- package/dist/hoi4_agent_tools/schemas/transaction.js.map +1 -0
- package/dist/hoi4_agent_tools/version.d.ts +6 -0
- package/dist/hoi4_agent_tools/version.d.ts.map +1 -0
- package/dist/hoi4_agent_tools/version.js +7 -0
- package/dist/hoi4_agent_tools/version.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/docs/README.md +22 -0
- package/docs/adr/0001-runtime-and-language.md +16 -0
- package/docs/adr/0002-lossless-source-model.md +18 -0
- package/docs/adr/0003-image-and-font-codecs.md +18 -0
- package/docs/adr/0004-focus-layout.md +16 -0
- package/docs/adr/0005-offline-rendering.md +18 -0
- package/docs/adr/0006-mcp-sdk-and-protocol.md +16 -0
- package/docs/adr/0007-storage-and-transactions.md +24 -0
- package/docs/adr/0008-test-strategy.md +18 -0
- package/docs/adr/0009-packaging-and-registry.md +16 -0
- package/docs/adr/0010-unsupported-and-refusal-policy.md +18 -0
- package/docs/adr/0011-http-security.md +16 -0
- package/docs/adr/README.md +13 -0
- package/docs/architecture.md +60 -0
- package/docs/artifacts.md +133 -0
- package/docs/compatibility.md +33 -0
- package/docs/completion-report.md +69 -0
- package/docs/configuration.md +285 -0
- package/docs/focus-workflow.md +68 -0
- package/docs/gui-workflow.md +121 -0
- package/docs/limitations.md +30 -0
- package/docs/map-workflow.md +89 -0
- package/docs/performance.md +117 -0
- package/docs/release.md +200 -0
- package/docs/research/mcp-2026-07-10.md +46 -0
- package/docs/research/source-ledger.md +68 -0
- package/docs/security.md +139 -0
- package/docs/self-hosting.md +168 -0
- package/docs/testing.md +43 -0
- package/docs/third-party-notices.md +11 -0
- package/docs/transactions.md +162 -0
- package/docs/troubleshooting.md +46 -0
- package/package.json +98 -5
- package/schemas/configuration.schema.json +794 -0
- package/schemas/continuous-focus-palette.schema.json +325 -0
- package/schemas/focus-plan.schema.json +902 -0
- package/schemas/focus-planning-sidecar.schema.json +364 -0
- package/schemas/gui-animation-source.schema.json +176 -0
- package/schemas/gui-helper.schema.json +301 -0
- package/schemas/gui-scenario.schema.json +298 -0
- package/schemas/map-operation.schema.json +2284 -0
- package/schemas/operation-result.schema.json +344 -0
- package/schemas/transaction-manifest.schema.json +685 -0
- package/server.json +32 -0
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
import { link, lstat, mkdir, open, readFile, realpath, unlink } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { z } from 'zod/v4';
|
|
4
|
+
import { WORKSPACE_MAX_REGISTRATIONS, } from './configuration.js';
|
|
5
|
+
import { compareCodeUnits, canonicalJson, hashCanonical, secureId, sha256Bytes, } from './canonical.js';
|
|
6
|
+
import { ServiceError } from './result.js';
|
|
7
|
+
import { ServerState } from './server-state.js';
|
|
8
|
+
const runtimeRegistrationClaimFile = '.runtime-registration-owner.json';
|
|
9
|
+
const sha256Schema = z.string().regex(/^[a-f0-9]{64}$/u);
|
|
10
|
+
const runtimeRegistrationClaimSchema = z
|
|
11
|
+
.object({
|
|
12
|
+
version: z.literal(1),
|
|
13
|
+
workspaceIdentity: sha256Schema,
|
|
14
|
+
ownerIdentity: sha256Schema,
|
|
15
|
+
claimHash: sha256Schema,
|
|
16
|
+
})
|
|
17
|
+
.strict();
|
|
18
|
+
async function exists(value, signal) {
|
|
19
|
+
signal?.throwIfAborted();
|
|
20
|
+
try {
|
|
21
|
+
await lstat(value);
|
|
22
|
+
signal?.throwIfAborted();
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error.name === 'AbortError')
|
|
27
|
+
throw error;
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export async function canonicalPath(input, signal) {
|
|
32
|
+
signal?.throwIfAborted();
|
|
33
|
+
const absolute = path.resolve(input);
|
|
34
|
+
if (await exists(absolute, signal)) {
|
|
35
|
+
const canonical = path.normalize(await realpath(absolute));
|
|
36
|
+
signal?.throwIfAborted();
|
|
37
|
+
return canonical;
|
|
38
|
+
}
|
|
39
|
+
const suffix = [];
|
|
40
|
+
let cursor = absolute;
|
|
41
|
+
while (!(await exists(cursor, signal))) {
|
|
42
|
+
signal?.throwIfAborted();
|
|
43
|
+
const parent = path.dirname(cursor);
|
|
44
|
+
if (parent === cursor) {
|
|
45
|
+
throw new ServiceError('PATH_NO_EXISTING_ANCESTOR', `Path has no existing ancestor: ${input}`);
|
|
46
|
+
}
|
|
47
|
+
suffix.unshift(path.basename(cursor));
|
|
48
|
+
cursor = parent;
|
|
49
|
+
}
|
|
50
|
+
const canonical = path.normalize(path.join(await realpath(cursor), ...suffix));
|
|
51
|
+
signal?.throwIfAborted();
|
|
52
|
+
return canonical;
|
|
53
|
+
}
|
|
54
|
+
export function isWithin(root, candidate) {
|
|
55
|
+
const relative = path.relative(root, candidate);
|
|
56
|
+
return (relative === '' ||
|
|
57
|
+
(!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative)));
|
|
58
|
+
}
|
|
59
|
+
function pathsOverlap(left, right) {
|
|
60
|
+
return isWithin(left, right) || isWithin(right, left);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Hash the resolved root topology without persisting canonical host paths. Canonicalization happens
|
|
64
|
+
* before this function is called, so filesystem aliases converge on the same stable identity.
|
|
65
|
+
*/
|
|
66
|
+
export function resolvedWorkspaceIdentity(workspace) {
|
|
67
|
+
return hashCanonical({
|
|
68
|
+
id: workspace.id,
|
|
69
|
+
roots: workspace.roots.map(({ kind, path: rootPath, loadOrder, writable, replacePaths }) => ({
|
|
70
|
+
kind,
|
|
71
|
+
rootHash: sha256Bytes(process.platform === 'win32' ? rootPath.toLowerCase() : rootPath),
|
|
72
|
+
loadOrder,
|
|
73
|
+
writable,
|
|
74
|
+
replacePaths,
|
|
75
|
+
})),
|
|
76
|
+
behavior: {
|
|
77
|
+
kind: workspace.registration.kind,
|
|
78
|
+
relativeRoots: workspace.registration.roots,
|
|
79
|
+
replacePaths: [...workspace.registration.replacePaths].sort((left, right) => compareCodeUnits(left, right)),
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function configuredOwnerIdentity(workspaceIdentity) {
|
|
84
|
+
return hashCanonical({
|
|
85
|
+
domain: 'hoi4-agent-configured-workspace-owner-v1',
|
|
86
|
+
workspaceIdentity,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function runtimeOwnerIdentity(workspaceIdentity, principal) {
|
|
90
|
+
return hashCanonical({
|
|
91
|
+
domain: 'hoi4-agent-runtime-workspace-owner-v1',
|
|
92
|
+
workspaceIdentity,
|
|
93
|
+
principal: principal ?? null,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function runtimeClaimHash(claim) {
|
|
97
|
+
return hashCanonical(claim);
|
|
98
|
+
}
|
|
99
|
+
function registrationClaimConflict() {
|
|
100
|
+
return new ServiceError('WORKSPACE_REGISTRATION_CONFLICT', 'Runtime workspace registration conflicts with an existing registration');
|
|
101
|
+
}
|
|
102
|
+
async function readRuntimeRegistrationClaim(claimPath, signal) {
|
|
103
|
+
signal?.throwIfAborted();
|
|
104
|
+
let claimStatus;
|
|
105
|
+
try {
|
|
106
|
+
claimStatus = await lstat(claimPath);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
if (error.code === 'ENOENT')
|
|
110
|
+
return undefined;
|
|
111
|
+
throw registrationClaimConflict();
|
|
112
|
+
}
|
|
113
|
+
signal?.throwIfAborted();
|
|
114
|
+
if (claimStatus.isSymbolicLink() || !claimStatus.isFile() || claimStatus.size > 4_096) {
|
|
115
|
+
throw registrationClaimConflict();
|
|
116
|
+
}
|
|
117
|
+
let parsed;
|
|
118
|
+
try {
|
|
119
|
+
const text = signal === undefined
|
|
120
|
+
? await readFile(claimPath, 'utf8')
|
|
121
|
+
: await readFile(claimPath, { encoding: 'utf8', signal });
|
|
122
|
+
signal?.throwIfAborted();
|
|
123
|
+
parsed = JSON.parse(text);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error.name === 'AbortError')
|
|
127
|
+
throw error;
|
|
128
|
+
throw registrationClaimConflict();
|
|
129
|
+
}
|
|
130
|
+
signal?.throwIfAborted();
|
|
131
|
+
const validated = runtimeRegistrationClaimSchema.safeParse(parsed);
|
|
132
|
+
if (!validated.success)
|
|
133
|
+
throw registrationClaimConflict();
|
|
134
|
+
const claim = validated.data;
|
|
135
|
+
if (runtimeClaimHash({
|
|
136
|
+
version: claim.version,
|
|
137
|
+
workspaceIdentity: claim.workspaceIdentity,
|
|
138
|
+
ownerIdentity: claim.ownerIdentity,
|
|
139
|
+
}) !== claim.claimHash) {
|
|
140
|
+
throw registrationClaimConflict();
|
|
141
|
+
}
|
|
142
|
+
return claim;
|
|
143
|
+
}
|
|
144
|
+
async function assertRuntimeRegistrationClaim(workspace, signal) {
|
|
145
|
+
signal?.throwIfAborted();
|
|
146
|
+
const claimRoot = await containedGeneratedPath(workspace.artifactRoot);
|
|
147
|
+
signal?.throwIfAborted();
|
|
148
|
+
const claimPath = path.join(claimRoot, runtimeRegistrationClaimFile);
|
|
149
|
+
const expectedWithoutHash = {
|
|
150
|
+
version: 1,
|
|
151
|
+
workspaceIdentity: workspace.workspaceIdentity,
|
|
152
|
+
ownerIdentity: workspace.ownerIdentity,
|
|
153
|
+
};
|
|
154
|
+
const expected = {
|
|
155
|
+
...expectedWithoutHash,
|
|
156
|
+
claimHash: runtimeClaimHash(expectedWithoutHash),
|
|
157
|
+
};
|
|
158
|
+
const assertMatches = (claim) => {
|
|
159
|
+
if (claim.workspaceIdentity !== expected.workspaceIdentity ||
|
|
160
|
+
claim.ownerIdentity !== expected.ownerIdentity ||
|
|
161
|
+
claim.claimHash !== expected.claimHash) {
|
|
162
|
+
throw registrationClaimConflict();
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
const existing = await readRuntimeRegistrationClaim(claimPath, signal);
|
|
166
|
+
if (existing !== undefined) {
|
|
167
|
+
assertMatches(existing);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
signal?.throwIfAborted();
|
|
171
|
+
const temporaryPath = path.join(claimRoot, `.runtime-registration-${secureId('claim')}.tmp`);
|
|
172
|
+
let temporaryHandle;
|
|
173
|
+
try {
|
|
174
|
+
temporaryHandle = await open(temporaryPath, 'wx', 0o600);
|
|
175
|
+
await temporaryHandle.writeFile(`${canonicalJson(expected)}\n`, 'utf8');
|
|
176
|
+
await temporaryHandle.sync();
|
|
177
|
+
await temporaryHandle.close();
|
|
178
|
+
temporaryHandle = undefined;
|
|
179
|
+
signal?.throwIfAborted();
|
|
180
|
+
try {
|
|
181
|
+
// A hard link publishes fully flushed bytes without replacing a competing claim.
|
|
182
|
+
await link(temporaryPath, claimPath);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
if (error.code !== 'EEXIST') {
|
|
186
|
+
throw registrationClaimConflict();
|
|
187
|
+
}
|
|
188
|
+
const racedClaim = await readRuntimeRegistrationClaim(claimPath, signal);
|
|
189
|
+
if (racedClaim === undefined)
|
|
190
|
+
throw registrationClaimConflict();
|
|
191
|
+
assertMatches(racedClaim);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
if (error instanceof ServiceError || error.name === 'AbortError')
|
|
196
|
+
throw error;
|
|
197
|
+
throw registrationClaimConflict();
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
await temporaryHandle?.close().catch(() => undefined);
|
|
201
|
+
await unlink(temporaryPath).catch(() => undefined);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function containsAsciiControlCharacter(value) {
|
|
205
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
206
|
+
if (value.charCodeAt(index) <= 0x1f)
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
export function isPortablePathSegment(segment) {
|
|
212
|
+
const windowsDevice = /^(?:con|prn|aux|nul|clock\$|conin\$|conout\$|com(?:[1-9]|[¹²³])|lpt(?:[1-9]|[¹²³]))(?:\.|$)/iu;
|
|
213
|
+
return (segment.length > 0 &&
|
|
214
|
+
!containsAsciiControlCharacter(segment) &&
|
|
215
|
+
!/[<>:"|?*]/u.test(segment) &&
|
|
216
|
+
!windowsDevice.test(segment) &&
|
|
217
|
+
!/[. ]$/u.test(segment));
|
|
218
|
+
}
|
|
219
|
+
/** Resolve a generated-storage descendant without permitting an existing link to escape its root. */
|
|
220
|
+
export async function containedGeneratedPath(root, ...segments) {
|
|
221
|
+
const candidate = await canonicalPath(path.join(root, ...segments));
|
|
222
|
+
if (!isWithin(root, candidate)) {
|
|
223
|
+
throw new ServiceError('PATH_GENERATED_ROOT_ESCAPE', 'Generated storage path escapes its configured root');
|
|
224
|
+
}
|
|
225
|
+
return candidate;
|
|
226
|
+
}
|
|
227
|
+
function assertRelative(relativePath) {
|
|
228
|
+
if (relativePath.length === 0)
|
|
229
|
+
throw new ServiceError('PATH_EMPTY', 'Workspace-relative path cannot be empty');
|
|
230
|
+
if (relativePath.includes('\0'))
|
|
231
|
+
throw new ServiceError('PATH_NUL', 'Path contains a NUL byte');
|
|
232
|
+
if (path.isAbsolute(relativePath)) {
|
|
233
|
+
throw new ServiceError('PATH_ABSOLUTE_REJECTED', 'Only workspace-relative paths are accepted');
|
|
234
|
+
}
|
|
235
|
+
const segments = relativePath.replaceAll('\\', '/').split('/');
|
|
236
|
+
if (segments.some((segment) => segment === '..')) {
|
|
237
|
+
throw new ServiceError('PATH_TRAVERSAL', 'Parent path segments are not allowed', {
|
|
238
|
+
relativePath,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
if (segments.some((segment) => segment.includes(':'))) {
|
|
242
|
+
throw new ServiceError('PATH_ALTERNATE_STREAM', 'Colon characters and alternate data streams are not allowed', {
|
|
243
|
+
relativePath,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
if (segments.some((segment) => segment !== '.' && segment.length > 0 && !isPortablePathSegment(segment))) {
|
|
247
|
+
throw new ServiceError('PATH_WINDOWS_DEVICE', 'Portable paths cannot contain device names, control characters, or ambiguous characters', {
|
|
248
|
+
relativePath,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
export class WorkspaceResolver {
|
|
253
|
+
configuration;
|
|
254
|
+
#byId = new Map();
|
|
255
|
+
#runtimePrincipalWorkspaces = new Map();
|
|
256
|
+
#runtimeWorkspaceIds = new Set();
|
|
257
|
+
#runtimeWorkspaceOwners = new Map();
|
|
258
|
+
#registrationRoots = [];
|
|
259
|
+
#registrationLexicalRoots = [];
|
|
260
|
+
#writableRegistrationRoots = [];
|
|
261
|
+
#writableRegistrationLexicalRoots = [];
|
|
262
|
+
#storageRoots = [];
|
|
263
|
+
#storageLexicalRoots = [];
|
|
264
|
+
#serverStateRoot;
|
|
265
|
+
#serverState;
|
|
266
|
+
#registrationQueue = Promise.resolve();
|
|
267
|
+
constructor(configuration) {
|
|
268
|
+
this.configuration = configuration;
|
|
269
|
+
}
|
|
270
|
+
static async create(configuration) {
|
|
271
|
+
const resolver = new WorkspaceResolver(configuration);
|
|
272
|
+
if (configuration.serverStateRoot !== undefined) {
|
|
273
|
+
resolver.#serverStateRoot = await canonicalPath(configuration.serverStateRoot);
|
|
274
|
+
}
|
|
275
|
+
resolver.#registrationLexicalRoots = configuration.registrationRoots.map((root) => path.normalize(path.resolve(root)));
|
|
276
|
+
resolver.#registrationRoots = await Promise.all(configuration.registrationRoots.map((root) => canonicalPath(root)));
|
|
277
|
+
resolver.#writableRegistrationLexicalRoots = configuration.writableRegistrationRoots.map((root) => path.normalize(path.resolve(root)));
|
|
278
|
+
resolver.#writableRegistrationRoots = await Promise.all(configuration.writableRegistrationRoots.map((root) => canonicalPath(root)));
|
|
279
|
+
for (const candidate of resolver.#writableRegistrationLexicalRoots) {
|
|
280
|
+
if (!resolver.#registrationLexicalRoots.some((root) => isWithin(root, candidate))) {
|
|
281
|
+
throw new ServiceError('WORKSPACE_WRITABLE_REGISTRATION_ROOT_FORBIDDEN', 'Writable runtime registration roots must be lexical descendants of registration roots');
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
for (const candidate of resolver.#writableRegistrationRoots) {
|
|
285
|
+
if (!resolver.#registrationRoots.some((root) => isWithin(root, candidate))) {
|
|
286
|
+
throw new ServiceError('WORKSPACE_WRITABLE_REGISTRATION_ROOT_FORBIDDEN', 'Writable runtime registration roots must resolve beneath registration roots');
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
resolver.#storageLexicalRoots = configuration.storageRoots.map((root) => path.normalize(path.resolve(root)));
|
|
290
|
+
resolver.#storageRoots = await Promise.all(configuration.storageRoots.map((root) => canonicalPath(root)));
|
|
291
|
+
if (resolver.#serverStateRoot !== undefined) {
|
|
292
|
+
for (const root of [
|
|
293
|
+
...resolver.#registrationRoots,
|
|
294
|
+
...resolver.#writableRegistrationRoots,
|
|
295
|
+
...resolver.#storageRoots,
|
|
296
|
+
]) {
|
|
297
|
+
if (pathsOverlap(resolver.#serverStateRoot, root)) {
|
|
298
|
+
throw new ServiceError('SERVER_STATE_ROOT_OVERLAP', 'Server state root must not overlap a registration or generated-storage capability root');
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
for (const registration of configuration.workspaces) {
|
|
303
|
+
const workspace = await resolver.resolveRegistration(registration);
|
|
304
|
+
if (resolver.#byId.has(workspace.id)) {
|
|
305
|
+
throw new ServiceError('WORKSPACE_DUPLICATE', `Duplicate workspace ID: ${workspace.id}`);
|
|
306
|
+
}
|
|
307
|
+
resolver.assertWorkspaceIsolation(workspace);
|
|
308
|
+
resolver.#byId.set(workspace.id, workspace);
|
|
309
|
+
}
|
|
310
|
+
if (configuration.serverStateRoot !== undefined) {
|
|
311
|
+
resolver.#serverState = await ServerState.create(configuration.serverStateRoot);
|
|
312
|
+
resolver.#serverStateRoot = resolver.#serverState.root;
|
|
313
|
+
}
|
|
314
|
+
return resolver;
|
|
315
|
+
}
|
|
316
|
+
config() {
|
|
317
|
+
return this.configuration;
|
|
318
|
+
}
|
|
319
|
+
serverState() {
|
|
320
|
+
return this.#serverState;
|
|
321
|
+
}
|
|
322
|
+
async register(registration, principal, signal) {
|
|
323
|
+
signal?.throwIfAborted();
|
|
324
|
+
let release;
|
|
325
|
+
const previous = this.#registrationQueue;
|
|
326
|
+
this.#registrationQueue = new Promise((resolve) => {
|
|
327
|
+
release = resolve;
|
|
328
|
+
});
|
|
329
|
+
await previous;
|
|
330
|
+
signal?.throwIfAborted();
|
|
331
|
+
try {
|
|
332
|
+
return await this.registerExclusive(registration, principal, signal);
|
|
333
|
+
}
|
|
334
|
+
finally {
|
|
335
|
+
release();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async registerExclusive(registration, principal, signal) {
|
|
339
|
+
signal?.throwIfAborted();
|
|
340
|
+
if (principal !== undefined && !this.registrationAllowed(principal)) {
|
|
341
|
+
throw new ServiceError('WORKSPACE_REGISTRATION_FORBIDDEN', 'Principal cannot register workspaces');
|
|
342
|
+
}
|
|
343
|
+
const registrationConflict = (code, message) => principal === undefined
|
|
344
|
+
? new ServiceError(code, message)
|
|
345
|
+
: new ServiceError('WORKSPACE_REGISTRATION_CONFLICT', 'Runtime workspace registration conflicts with an existing registration');
|
|
346
|
+
if (this.#byId.has(registration.id)) {
|
|
347
|
+
throw registrationConflict('WORKSPACE_DUPLICATE', `Workspace ID is already registered: ${registration.id}`);
|
|
348
|
+
}
|
|
349
|
+
if (this.#byId.size >= WORKSPACE_MAX_REGISTRATIONS) {
|
|
350
|
+
throw registrationConflict('WORKSPACE_REGISTRATION_LIMIT', 'Workspace registration limit has been reached');
|
|
351
|
+
}
|
|
352
|
+
this.assertLexicalRegistrationPath(registration.root, 'workspace root', 'WORKSPACE_REGISTRATION_ROOT_FORBIDDEN');
|
|
353
|
+
if (registration.kind === 'mod')
|
|
354
|
+
this.assertLexicalWritableRegistrationPath(registration.root);
|
|
355
|
+
const candidateRoot = await canonicalPath(registration.root, signal);
|
|
356
|
+
signal?.throwIfAborted();
|
|
357
|
+
if (!this.#registrationRoots.some((root) => isWithin(root, candidateRoot))) {
|
|
358
|
+
throw new ServiceError('WORKSPACE_REGISTRATION_ROOT_FORBIDDEN', 'Workspace is outside configured registration roots', {
|
|
359
|
+
workspaceId: registration.id,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
if (registration.kind === 'mod' &&
|
|
363
|
+
!this.#writableRegistrationRoots.some((root) => isWithin(root, candidateRoot))) {
|
|
364
|
+
throw new ServiceError('WORKSPACE_REGISTRATION_ROOT_FORBIDDEN', 'Runtime mod roots require an operator-approved writable registration root');
|
|
365
|
+
}
|
|
366
|
+
const readRoots = [
|
|
367
|
+
registration.gameRoot,
|
|
368
|
+
...registration.dependencyRoots,
|
|
369
|
+
...registration.dependencies.map(({ root }) => root),
|
|
370
|
+
registration.fixtureRoot,
|
|
371
|
+
].filter((value) => value !== undefined);
|
|
372
|
+
const canonicalReadRoots = [];
|
|
373
|
+
for (const requestedRoot of readRoots) {
|
|
374
|
+
signal?.throwIfAborted();
|
|
375
|
+
this.assertLexicalRegistrationPath(requestedRoot, 'workspace source root', 'WORKSPACE_REGISTRATION_SOURCE_ROOT_FORBIDDEN');
|
|
376
|
+
const candidate = await canonicalPath(requestedRoot, signal);
|
|
377
|
+
if (!this.#registrationRoots.some((root) => isWithin(root, candidate))) {
|
|
378
|
+
throw new ServiceError('WORKSPACE_REGISTRATION_SOURCE_ROOT_FORBIDDEN', 'A requested game, dependency, or fixture root is outside configured registration roots', { workspaceId: registration.id });
|
|
379
|
+
}
|
|
380
|
+
canonicalReadRoots.push(candidate);
|
|
381
|
+
}
|
|
382
|
+
for (const existing of this.#byId.values()) {
|
|
383
|
+
signal?.throwIfAborted();
|
|
384
|
+
if (existing.roots.some((root) => pathsOverlap(candidateRoot, root.path))) {
|
|
385
|
+
throw registrationConflict('WORKSPACE_REGISTRATION_ROOT_OVERLAP', 'Runtime workspace root overlaps an existing registered workspace root');
|
|
386
|
+
}
|
|
387
|
+
const protectedRoots = existing.roots.filter((root) => ['mod', 'artifact', 'cache'].includes(root.kind));
|
|
388
|
+
if (canonicalReadRoots.some((requestedRoot) => protectedRoots.some((root) => pathsOverlap(requestedRoot, root.path)))) {
|
|
389
|
+
throw registrationConflict('WORKSPACE_REGISTRATION_SOURCE_OVERLAP', 'Runtime source root overlaps an existing workspace-owned root');
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
for (const [index, requestedRoot] of canonicalReadRoots.entries()) {
|
|
393
|
+
signal?.throwIfAborted();
|
|
394
|
+
if (pathsOverlap(candidateRoot, requestedRoot) ||
|
|
395
|
+
canonicalReadRoots
|
|
396
|
+
.slice(0, index)
|
|
397
|
+
.some((existingRequestedRoot) => pathsOverlap(requestedRoot, existingRequestedRoot))) {
|
|
398
|
+
throw new ServiceError('WORKSPACE_REGISTRATION_INTERNAL_OVERLAP', 'Runtime workspace roots must be distinct and non-overlapping', { workspaceId: registration.id });
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const generatedCandidates = new Map();
|
|
402
|
+
for (const [generatedKind, configuredRoot] of [
|
|
403
|
+
['artifacts', registration.artifactRoot],
|
|
404
|
+
['cache', registration.cacheRoot],
|
|
405
|
+
]) {
|
|
406
|
+
signal?.throwIfAborted();
|
|
407
|
+
const generatedRoot = configuredRoot ??
|
|
408
|
+
(registration.kind === 'mod'
|
|
409
|
+
? path.join(registration.root, '.hoi4-agent', generatedKind)
|
|
410
|
+
: undefined);
|
|
411
|
+
if (generatedRoot === undefined)
|
|
412
|
+
continue;
|
|
413
|
+
this.assertLexicalGeneratedPath(registration, generatedRoot, generatedKind);
|
|
414
|
+
const candidate = await canonicalPath(generatedRoot, signal);
|
|
415
|
+
const relativeToPrimary = path
|
|
416
|
+
.relative(candidateRoot, candidate)
|
|
417
|
+
.replaceAll('\\', '/')
|
|
418
|
+
.toLowerCase();
|
|
419
|
+
const allowed = (registration.kind === 'mod' &&
|
|
420
|
+
isWithin(candidateRoot, candidate) &&
|
|
421
|
+
(relativeToPrimary === `.hoi4-agent/${generatedKind}` ||
|
|
422
|
+
relativeToPrimary.startsWith(`.hoi4-agent/${generatedKind}/`))) ||
|
|
423
|
+
this.#storageRoots.some((root) => isWithin(root, candidate));
|
|
424
|
+
if (!allowed) {
|
|
425
|
+
throw new ServiceError('WORKSPACE_REGISTRATION_GENERATED_ROOT_FORBIDDEN', 'Runtime artifact and cache roots must remain inside the registered workspace', { workspaceId: registration.id });
|
|
426
|
+
}
|
|
427
|
+
generatedCandidates.set(generatedKind, candidate);
|
|
428
|
+
}
|
|
429
|
+
const artifactCandidate = generatedCandidates.get('artifacts');
|
|
430
|
+
const cacheCandidate = generatedCandidates.get('cache');
|
|
431
|
+
if (artifactCandidate !== undefined &&
|
|
432
|
+
cacheCandidate !== undefined &&
|
|
433
|
+
pathsOverlap(artifactCandidate, cacheCandidate)) {
|
|
434
|
+
throw new ServiceError('WORKSPACE_REGISTRATION_GENERATED_ROOT_FORBIDDEN', 'Runtime artifact and cache roots must be distinct and non-overlapping', { workspaceId: registration.id });
|
|
435
|
+
}
|
|
436
|
+
for (const candidate of generatedCandidates.values()) {
|
|
437
|
+
signal?.throwIfAborted();
|
|
438
|
+
const overlapsOwnSources = (registration.kind !== 'mod' && pathsOverlap(candidateRoot, candidate)) ||
|
|
439
|
+
(registration.kind === 'mod' &&
|
|
440
|
+
!isWithin(candidateRoot, candidate) &&
|
|
441
|
+
pathsOverlap(candidateRoot, candidate)) ||
|
|
442
|
+
canonicalReadRoots.some((sourceRoot) => pathsOverlap(sourceRoot, candidate));
|
|
443
|
+
if (overlapsOwnSources) {
|
|
444
|
+
throw new ServiceError('WORKSPACE_REGISTRATION_GENERATED_ROOT_FORBIDDEN', 'Runtime generated storage must not overlap a source root', { workspaceId: registration.id });
|
|
445
|
+
}
|
|
446
|
+
if ([...this.#byId.values()].some((existing) => existing.roots.some((root) => pathsOverlap(candidate, root.path)))) {
|
|
447
|
+
throw registrationConflict('WORKSPACE_REGISTRATION_GENERATED_ROOT_OVERLAP', 'Runtime generated storage overlaps an existing workspace root');
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
const workspace = await this.resolveRegistration(registration, {
|
|
451
|
+
principal,
|
|
452
|
+
...(signal === undefined ? {} : { signal }),
|
|
453
|
+
});
|
|
454
|
+
signal?.throwIfAborted();
|
|
455
|
+
if (this.#byId.has(registration.id)) {
|
|
456
|
+
throw registrationConflict('WORKSPACE_DUPLICATE', `Workspace ID is already registered: ${registration.id}`);
|
|
457
|
+
}
|
|
458
|
+
this.assertResolvedRuntimeRegistration(workspace);
|
|
459
|
+
try {
|
|
460
|
+
this.assertWorkspaceIsolation(workspace);
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
if (principal !== undefined && error instanceof ServiceError) {
|
|
464
|
+
throw registrationConflict(error.code, error.message);
|
|
465
|
+
}
|
|
466
|
+
throw error;
|
|
467
|
+
}
|
|
468
|
+
signal?.throwIfAborted();
|
|
469
|
+
await assertRuntimeRegistrationClaim(workspace, signal);
|
|
470
|
+
signal?.throwIfAborted();
|
|
471
|
+
this.#byId.set(workspace.id, workspace);
|
|
472
|
+
this.#runtimeWorkspaceIds.add(workspace.id);
|
|
473
|
+
this.#runtimeWorkspaceOwners.set(workspace.id, principal);
|
|
474
|
+
if (principal !== undefined) {
|
|
475
|
+
const ids = this.#runtimePrincipalWorkspaces.get(principal) ?? new Set();
|
|
476
|
+
ids.add(workspace.id);
|
|
477
|
+
this.#runtimePrincipalWorkspaces.set(principal, ids);
|
|
478
|
+
}
|
|
479
|
+
return workspace;
|
|
480
|
+
}
|
|
481
|
+
unregisterRuntime(workspaceId, principal) {
|
|
482
|
+
if (this.#runtimeWorkspaceIds.has(workspaceId) &&
|
|
483
|
+
this.#runtimeWorkspaceOwners.get(workspaceId) !== principal) {
|
|
484
|
+
throw new ServiceError('WORKSPACE_INACCESSIBLE', 'Workspace is unavailable to the authenticated principal');
|
|
485
|
+
}
|
|
486
|
+
if (!this.#runtimeWorkspaceIds.delete(workspaceId))
|
|
487
|
+
return;
|
|
488
|
+
this.#runtimeWorkspaceOwners.delete(workspaceId);
|
|
489
|
+
this.#byId.delete(workspaceId);
|
|
490
|
+
if (principal !== undefined) {
|
|
491
|
+
const ids = this.#runtimePrincipalWorkspaces.get(principal);
|
|
492
|
+
ids?.delete(workspaceId);
|
|
493
|
+
if (ids?.size === 0)
|
|
494
|
+
this.#runtimePrincipalWorkspaces.delete(principal);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
list(principal) {
|
|
498
|
+
const allowed = principal === undefined ? undefined : this.allowedWorkspaceIds(principal);
|
|
499
|
+
return [...this.#byId.values()]
|
|
500
|
+
.filter((workspace) => allowed === undefined || allowed.has(workspace.id))
|
|
501
|
+
.sort((a, b) => compareCodeUnits(a.id, b.id));
|
|
502
|
+
}
|
|
503
|
+
get(workspaceId, principal) {
|
|
504
|
+
if (principal !== undefined && !this.allowedWorkspaceIds(principal).has(workspaceId)) {
|
|
505
|
+
throw new ServiceError('WORKSPACE_INACCESSIBLE', 'Workspace is unavailable to the authenticated principal');
|
|
506
|
+
}
|
|
507
|
+
const workspace = this.#byId.get(workspaceId);
|
|
508
|
+
if (workspace === undefined) {
|
|
509
|
+
if (principal !== undefined) {
|
|
510
|
+
throw new ServiceError('WORKSPACE_INACCESSIBLE', 'Workspace is unavailable to the authenticated principal');
|
|
511
|
+
}
|
|
512
|
+
throw new ServiceError('WORKSPACE_NOT_REGISTERED', `Workspace is not registered: ${workspaceId}`);
|
|
513
|
+
}
|
|
514
|
+
return workspace;
|
|
515
|
+
}
|
|
516
|
+
async resolvePath(workspaceId, relativePath, access, rootKinds, principal) {
|
|
517
|
+
assertRelative(relativePath);
|
|
518
|
+
const workspace = this.get(workspaceId, principal);
|
|
519
|
+
const firstSegment = path.posix
|
|
520
|
+
.normalize(relativePath.replaceAll('\\', '/'))
|
|
521
|
+
.split('/')
|
|
522
|
+
.find((segment) => segment !== '.' && segment.length > 0)
|
|
523
|
+
?.toLowerCase();
|
|
524
|
+
if (access === 'write' && firstSegment === '.hoi4-agent') {
|
|
525
|
+
throw new ServiceError('PATH_GENERATED_STORAGE_RESERVED', 'Generated .hoi4-agent storage cannot be targeted as mod source');
|
|
526
|
+
}
|
|
527
|
+
const roots = workspace.roots.filter((root) => (rootKinds === undefined || rootKinds.includes(root.kind)) &&
|
|
528
|
+
(access === 'read' || root.writable));
|
|
529
|
+
for (const root of roots.sort((a, b) => b.loadOrder - a.loadOrder)) {
|
|
530
|
+
const candidate = await canonicalPath(path.join(root.path, relativePath));
|
|
531
|
+
if (!isWithin(root.path, candidate))
|
|
532
|
+
continue;
|
|
533
|
+
if (access === 'read' && !(await exists(candidate)))
|
|
534
|
+
continue;
|
|
535
|
+
if (access === 'write') {
|
|
536
|
+
if (root.kind === 'mod' &&
|
|
537
|
+
(pathsOverlap(workspace.artifactRoot, candidate) ||
|
|
538
|
+
pathsOverlap(workspace.cacheRoot, candidate))) {
|
|
539
|
+
throw new ServiceError('PATH_GENERATED_STORAGE_RESERVED', 'Artifact and cache storage cannot be targeted as mod source');
|
|
540
|
+
}
|
|
541
|
+
const existingParent = await canonicalPath(path.dirname(candidate));
|
|
542
|
+
if (!isWithin(root.path, existingParent))
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
return { path: candidate, root };
|
|
546
|
+
}
|
|
547
|
+
throw new ServiceError(access === 'write' ? 'PATH_WRITE_OUTSIDE_ROOTS' : 'PATH_NOT_FOUND_IN_ROOTS', `Path cannot be resolved for ${access} access`, { workspaceId, relativePath, rootKinds });
|
|
548
|
+
}
|
|
549
|
+
async resolvePathInRoot(workspaceId, relativePath, rootKind, loadOrder, principal) {
|
|
550
|
+
assertRelative(relativePath);
|
|
551
|
+
const workspace = this.get(workspaceId, principal);
|
|
552
|
+
const root = workspace.roots.find((candidate) => candidate.kind === rootKind && candidate.loadOrder === loadOrder);
|
|
553
|
+
if (root === undefined) {
|
|
554
|
+
throw new ServiceError('PATH_ROOT_NOT_FOUND', 'Configured source root no longer exists', {
|
|
555
|
+
workspaceId,
|
|
556
|
+
rootKind,
|
|
557
|
+
loadOrder,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
const candidate = await canonicalPath(path.join(root.path, relativePath));
|
|
561
|
+
if (!isWithin(root.path, candidate) || !(await exists(candidate))) {
|
|
562
|
+
throw new ServiceError('PATH_NOT_FOUND_IN_ROOT', 'Path cannot be resolved in the selected source root', {
|
|
563
|
+
workspaceId,
|
|
564
|
+
relativePath,
|
|
565
|
+
rootKind,
|
|
566
|
+
loadOrder,
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
return { path: candidate, root };
|
|
570
|
+
}
|
|
571
|
+
allowedWorkspaceIds(principal) {
|
|
572
|
+
const token = this.configuration.http.tokens.find((entry) => entry.principal === principal);
|
|
573
|
+
const oauth = this.configuration.http.principals.find((entry) => entry.principal === principal);
|
|
574
|
+
return new Set([
|
|
575
|
+
...(token?.workspaceIds ?? []),
|
|
576
|
+
...(oauth?.workspaceIds ?? []),
|
|
577
|
+
...(this.#runtimePrincipalWorkspaces.get(principal) ?? []),
|
|
578
|
+
]);
|
|
579
|
+
}
|
|
580
|
+
registrationAllowed(principal) {
|
|
581
|
+
return (this.configuration.http.tokens.some((entry) => entry.principal === principal && entry.allowRegistration) ||
|
|
582
|
+
this.configuration.http.principals.some((entry) => entry.principal === principal && entry.allowRegistration));
|
|
583
|
+
}
|
|
584
|
+
assertLexicalRegistrationPath(value, label, code) {
|
|
585
|
+
const candidate = path.normalize(path.resolve(value));
|
|
586
|
+
if (!this.#registrationLexicalRoots.some((root) => isWithin(root, candidate))) {
|
|
587
|
+
throw new ServiceError(code, `Runtime ${label} is lexically outside configured registration roots`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
assertLexicalWritableRegistrationPath(value) {
|
|
591
|
+
const candidate = path.normalize(path.resolve(value));
|
|
592
|
+
if (!this.#writableRegistrationLexicalRoots.some((root) => isWithin(root, candidate))) {
|
|
593
|
+
throw new ServiceError('WORKSPACE_REGISTRATION_ROOT_FORBIDDEN', 'Runtime mod roots are lexically outside operator-approved writable registration roots');
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
assertLexicalGeneratedPath(registration, value, generatedKind) {
|
|
597
|
+
const candidate = path.normalize(path.resolve(value));
|
|
598
|
+
const primary = path.normalize(path.resolve(registration.root));
|
|
599
|
+
const expectedRoot = path.join(primary, '.hoi4-agent', generatedKind);
|
|
600
|
+
const insideWritableMod = registration.kind === 'mod' && isWithin(expectedRoot, candidate);
|
|
601
|
+
const insideStorage = this.#storageLexicalRoots.some((root) => isWithin(root, candidate));
|
|
602
|
+
if (!insideWritableMod && !insideStorage) {
|
|
603
|
+
throw new ServiceError('WORKSPACE_REGISTRATION_GENERATED_ROOT_FORBIDDEN', 'Runtime generated storage is outside the writable mod or configured storage roots');
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
assertResolvedRuntimeRegistration(workspace) {
|
|
607
|
+
const allowed = (candidate) => this.#registrationRoots.some((root) => isWithin(root, candidate));
|
|
608
|
+
if (!allowed(workspace.modRoot)) {
|
|
609
|
+
throw new ServiceError('WORKSPACE_REGISTRATION_ROOT_FORBIDDEN', 'Resolved runtime workspace is outside configured registration roots');
|
|
610
|
+
}
|
|
611
|
+
if (workspace.registration.kind === 'mod' &&
|
|
612
|
+
!this.#writableRegistrationRoots.some((root) => isWithin(root, workspace.modRoot))) {
|
|
613
|
+
throw new ServiceError('WORKSPACE_REGISTRATION_ROOT_FORBIDDEN', 'Resolved runtime mod root is outside operator-approved writable registration roots');
|
|
614
|
+
}
|
|
615
|
+
for (const root of workspace.roots.filter((candidate) => ['game', 'dependency', 'fixture'].includes(candidate.kind))) {
|
|
616
|
+
if (!allowed(root.path)) {
|
|
617
|
+
throw new ServiceError('WORKSPACE_REGISTRATION_SOURCE_ROOT_FORBIDDEN', 'Resolved runtime source is outside configured registration roots');
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const generatedAllowed = (candidate, generatedKind) => (workspace.registration.kind === 'mod' &&
|
|
621
|
+
isWithin(path.join(workspace.modRoot, '.hoi4-agent', generatedKind), candidate)) ||
|
|
622
|
+
this.#storageRoots.some((root) => isWithin(root, candidate));
|
|
623
|
+
if (!generatedAllowed(workspace.artifactRoot, 'artifacts') ||
|
|
624
|
+
!generatedAllowed(workspace.cacheRoot, 'cache')) {
|
|
625
|
+
throw new ServiceError('WORKSPACE_REGISTRATION_GENERATED_ROOT_FORBIDDEN', 'Resolved runtime generated storage must remain inside its workspace');
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
assertWorkspaceIsolation(workspace) {
|
|
629
|
+
const readRoots = workspace.roots.filter((root) => ['game', 'dependency', 'fixture'].includes(root.kind));
|
|
630
|
+
const ownedRoots = workspace.roots.filter((root) => ['mod', 'artifact', 'cache'].includes(root.kind));
|
|
631
|
+
for (const existing of this.#byId.values()) {
|
|
632
|
+
if (existing.roots.some((root) => pathsOverlap(workspace.modRoot, root.path))) {
|
|
633
|
+
throw new ServiceError('WORKSPACE_ROOT_OVERLAP', 'Configured workspace root overlaps another registered workspace root', { workspaceId: workspace.id });
|
|
634
|
+
}
|
|
635
|
+
if (ownedRoots.some((owned) => existing.roots.some((existingRoot) => pathsOverlap(owned.path, existingRoot.path)))) {
|
|
636
|
+
throw new ServiceError('WORKSPACE_GENERATED_ROOT_OVERLAP', 'Workspace-owned source or generated storage overlaps another registered workspace', { workspaceId: workspace.id });
|
|
637
|
+
}
|
|
638
|
+
const protectedRoots = existing.roots.filter((root) => ['mod', 'artifact', 'cache'].includes(root.kind));
|
|
639
|
+
if (readRoots.some((requestedRoot) => protectedRoots.some((root) => pathsOverlap(requestedRoot.path, root.path)))) {
|
|
640
|
+
throw new ServiceError('WORKSPACE_SOURCE_OVERLAP', 'Configured source root overlaps another workspace-owned root', { workspaceId: workspace.id });
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
async resolveRegistration(registration, runtimeOwner) {
|
|
645
|
+
const signal = runtimeOwner?.signal;
|
|
646
|
+
signal?.throwIfAborted();
|
|
647
|
+
const modRoot = await canonicalPath(registration.root, signal);
|
|
648
|
+
if (!(await exists(modRoot, signal))) {
|
|
649
|
+
throw new ServiceError('WORKSPACE_ROOT_MISSING', `Workspace root does not exist: ${registration.root}`);
|
|
650
|
+
}
|
|
651
|
+
const gameRoot = registration.gameRoot === undefined
|
|
652
|
+
? undefined
|
|
653
|
+
: await canonicalPath(registration.gameRoot, signal);
|
|
654
|
+
const dependencyRegistrations = registration.dependencies.length > 0
|
|
655
|
+
? registration.dependencies
|
|
656
|
+
: registration.dependencyRoots.map((root) => ({ root, replacePaths: [] }));
|
|
657
|
+
const dependencyRoots = await Promise.all(dependencyRegistrations.map(({ root }) => canonicalPath(root, signal)));
|
|
658
|
+
signal?.throwIfAborted();
|
|
659
|
+
if (registration.kind !== 'mod' &&
|
|
660
|
+
(registration.artifactRoot === undefined || registration.cacheRoot === undefined)) {
|
|
661
|
+
throw new ServiceError('WORKSPACE_GENERATED_ROOT_REQUIRED', 'Read-only game and dependency workspaces require explicit operator-owned storage roots', { workspaceId: registration.id });
|
|
662
|
+
}
|
|
663
|
+
const artifactRoot = await canonicalPath(registration.artifactRoot ?? path.join(modRoot, '.hoi4-agent', 'artifacts'), signal);
|
|
664
|
+
const cacheRoot = await canonicalPath(registration.cacheRoot ?? path.join(modRoot, '.hoi4-agent', 'cache'), signal);
|
|
665
|
+
const fixtureRoot = registration.fixtureRoot === undefined
|
|
666
|
+
? undefined
|
|
667
|
+
: await canonicalPath(registration.fixtureRoot, signal);
|
|
668
|
+
signal?.throwIfAborted();
|
|
669
|
+
const generatedAllowed = (candidate, generatedKind) => (registration.kind === 'mod' &&
|
|
670
|
+
isWithin(path.join(modRoot, '.hoi4-agent', generatedKind), candidate)) ||
|
|
671
|
+
this.#storageRoots.some((root) => isWithin(root, candidate));
|
|
672
|
+
if (!generatedAllowed(artifactRoot, 'artifacts')) {
|
|
673
|
+
throw new ServiceError('WORKSPACE_GENERATED_ROOT_ESCAPE', 'Artifact storage must be inside the writable mod or a configured storage root', { workspaceId: registration.id });
|
|
674
|
+
}
|
|
675
|
+
if (!generatedAllowed(cacheRoot, 'cache')) {
|
|
676
|
+
throw new ServiceError('WORKSPACE_GENERATED_ROOT_ESCAPE', 'Cache storage must be inside the writable mod or a configured storage root', { workspaceId: registration.id });
|
|
677
|
+
}
|
|
678
|
+
if (pathsOverlap(artifactRoot, cacheRoot)) {
|
|
679
|
+
throw new ServiceError('WORKSPACE_GENERATED_ROOT_OVERLAP', 'Artifact and cache storage roots must be distinct and non-overlapping', { workspaceId: registration.id });
|
|
680
|
+
}
|
|
681
|
+
for (const sourceRoot of [gameRoot, ...dependencyRoots, fixtureRoot].filter((value) => value !== undefined)) {
|
|
682
|
+
signal?.throwIfAborted();
|
|
683
|
+
if (!(await exists(sourceRoot, signal))) {
|
|
684
|
+
throw new ServiceError('WORKSPACE_SOURCE_ROOT_MISSING', 'Configured game, dependency, or fixture root does not exist', { workspaceId: registration.id });
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
const sourceRoots = [gameRoot, ...dependencyRoots, fixtureRoot].filter((value) => value !== undefined);
|
|
688
|
+
for (const [index, sourceRoot] of sourceRoots.entries()) {
|
|
689
|
+
if (pathsOverlap(modRoot, sourceRoot) ||
|
|
690
|
+
sourceRoots
|
|
691
|
+
.slice(0, index)
|
|
692
|
+
.some((existingSourceRoot) => pathsOverlap(sourceRoot, existingSourceRoot))) {
|
|
693
|
+
throw new ServiceError('WORKSPACE_INTERNAL_ROOT_OVERLAP', 'Workspace source roots must be distinct and non-overlapping', { workspaceId: registration.id });
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
for (const generatedRoot of [artifactRoot, cacheRoot]) {
|
|
697
|
+
if ((registration.kind !== 'mod' && pathsOverlap(modRoot, generatedRoot)) ||
|
|
698
|
+
(registration.kind === 'mod' &&
|
|
699
|
+
!isWithin(modRoot, generatedRoot) &&
|
|
700
|
+
pathsOverlap(modRoot, generatedRoot)) ||
|
|
701
|
+
sourceRoots.some((sourceRoot) => pathsOverlap(sourceRoot, generatedRoot))) {
|
|
702
|
+
throw new ServiceError('WORKSPACE_GENERATED_SOURCE_OVERLAP', 'Generated storage must not overlap a source root', { workspaceId: registration.id });
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (this.#serverStateRoot !== undefined &&
|
|
706
|
+
[modRoot, ...sourceRoots, artifactRoot, cacheRoot].some((root) => pathsOverlap(this.#serverStateRoot, root))) {
|
|
707
|
+
throw new ServiceError('SERVER_STATE_ROOT_OVERLAP', 'Server state root must not overlap a workspace source or generated-storage root', { workspaceId: registration.id });
|
|
708
|
+
}
|
|
709
|
+
await mkdir(artifactRoot, { recursive: true });
|
|
710
|
+
signal?.throwIfAborted();
|
|
711
|
+
await mkdir(cacheRoot, { recursive: true });
|
|
712
|
+
signal?.throwIfAborted();
|
|
713
|
+
const roots = [
|
|
714
|
+
{
|
|
715
|
+
kind: registration.kind,
|
|
716
|
+
path: modRoot,
|
|
717
|
+
writable: registration.kind === 'mod',
|
|
718
|
+
loadOrder: dependencyRoots.length + 1,
|
|
719
|
+
replacePaths: [...registration.replacePaths],
|
|
720
|
+
},
|
|
721
|
+
...dependencyRoots.map((dependency, index) => ({
|
|
722
|
+
kind: 'dependency',
|
|
723
|
+
path: dependency,
|
|
724
|
+
writable: false,
|
|
725
|
+
loadOrder: index + 1,
|
|
726
|
+
replacePaths: [...dependencyRegistrations[index].replacePaths],
|
|
727
|
+
})),
|
|
728
|
+
...(gameRoot === undefined
|
|
729
|
+
? []
|
|
730
|
+
: [
|
|
731
|
+
{
|
|
732
|
+
kind: 'game',
|
|
733
|
+
path: gameRoot,
|
|
734
|
+
writable: false,
|
|
735
|
+
loadOrder: 0,
|
|
736
|
+
replacePaths: [],
|
|
737
|
+
},
|
|
738
|
+
]),
|
|
739
|
+
{
|
|
740
|
+
kind: 'artifact',
|
|
741
|
+
path: artifactRoot,
|
|
742
|
+
writable: true,
|
|
743
|
+
loadOrder: dependencyRoots.length + 2,
|
|
744
|
+
replacePaths: [],
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
kind: 'cache',
|
|
748
|
+
path: cacheRoot,
|
|
749
|
+
writable: true,
|
|
750
|
+
loadOrder: dependencyRoots.length + 2,
|
|
751
|
+
replacePaths: [],
|
|
752
|
+
},
|
|
753
|
+
...(fixtureRoot === undefined
|
|
754
|
+
? []
|
|
755
|
+
: [
|
|
756
|
+
{
|
|
757
|
+
kind: 'fixture',
|
|
758
|
+
path: fixtureRoot,
|
|
759
|
+
writable: false,
|
|
760
|
+
loadOrder: 0,
|
|
761
|
+
replacePaths: [],
|
|
762
|
+
},
|
|
763
|
+
]),
|
|
764
|
+
];
|
|
765
|
+
for (const root of roots) {
|
|
766
|
+
signal?.throwIfAborted();
|
|
767
|
+
root.path = await canonicalPath(root.path, signal);
|
|
768
|
+
}
|
|
769
|
+
const workspaceBase = {
|
|
770
|
+
id: registration.id,
|
|
771
|
+
name: registration.name,
|
|
772
|
+
registration,
|
|
773
|
+
roots,
|
|
774
|
+
modRoot,
|
|
775
|
+
...(gameRoot === undefined ? {} : { gameRoot }),
|
|
776
|
+
dependencyRoots,
|
|
777
|
+
artifactRoot,
|
|
778
|
+
cacheRoot,
|
|
779
|
+
...(fixtureRoot === undefined ? {} : { fixtureRoot }),
|
|
780
|
+
writeEnabled: registration.kind === 'mod' &&
|
|
781
|
+
this.configuration.writePolicy === 'transactions' &&
|
|
782
|
+
registration.writeEnabled,
|
|
783
|
+
};
|
|
784
|
+
const workspaceIdentity = resolvedWorkspaceIdentity(workspaceBase);
|
|
785
|
+
return {
|
|
786
|
+
...workspaceBase,
|
|
787
|
+
workspaceIdentity,
|
|
788
|
+
ownerIdentity: runtimeOwner === undefined
|
|
789
|
+
? configuredOwnerIdentity(workspaceIdentity)
|
|
790
|
+
: runtimeOwnerIdentity(workspaceIdentity, runtimeOwner.principal),
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
//# sourceMappingURL=workspace.js.map
|