@akira-tl/forgerelay 0.8.8 → 0.8.10
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 +32 -0
- package/README.md +10 -18
- package/capabilities/code-intelligence/GUIDE.md +7 -3
- package/capabilities/host-integration/GUIDE.md +2 -2
- package/capabilities/subagents/GUIDE.md +9 -0
- package/capabilities/{workspace-tasks → workspace/workspace-tasks}/GUIDE.md +6 -0
- package/dist/activity/{audit-store.js → history/audit-store.js} +129 -21
- package/dist/activity/history/bash-output-store.js +250 -0
- package/dist/activity/{host-turn-store.js → history/host-turn-store.js} +4 -1
- package/dist/activity/{query-service.js → history/query-service.js} +13 -7
- package/dist/activity/{lifecycle.js → runtime/lifecycle.js} +1 -1
- package/dist/activity/{mcp-query-tools.js → runtime/mcp-query-tools.js} +3 -3
- package/dist/activity/storage/history.js +12 -0
- package/dist/activity/storage/paths.js +56 -0
- package/dist/activity/storage/segmented-log.js +149 -0
- package/dist/cli/init.js +180 -0
- package/dist/cli/setup-support.js +181 -0
- package/dist/cli.js +23 -216
- package/dist/lsp/code-intelligence.js +18 -4
- package/dist/lsp/runtime/diagnostic-snapshots.js +52 -1
- package/dist/lsp/runtime/managed-language-servers.js +172 -0
- package/dist/lsp/runtime/manager.js +37 -2
- package/dist/lsp/runtime/process-launch.js +3 -0
- package/dist/lsp/test-support/server-fixture.js +14 -11
- package/dist/mcp/filesystem/filesystem-tools.js +245 -0
- package/dist/mcp/hooks/command-runner.js +79 -0
- package/dist/{hook-cli.js → mcp/hooks/hook-cli.js} +1 -1
- package/dist/{hooks.js → mcp/hooks/hooks.js} +3 -80
- package/dist/{oauth-provider.js → mcp/oauth/oauth-provider.js} +1 -1
- package/dist/{oauth-store.js → mcp/oauth/oauth-store.js} +5 -4
- package/dist/{operations → mcp/operations}/native-bulk-mutations.js +2 -2
- package/dist/mcp/panel/app.js +143 -0
- package/dist/{mcp-app-template.js → mcp/panel/mcp-app-template.js} +2 -38
- package/dist/{process-platform.js → mcp/process/process-platform.js} +21 -9
- package/dist/{process-sessions.js → mcp/process/process-sessions.js} +3 -3
- package/dist/mcp/process/runtime.js +257 -0
- package/dist/mcp/process/tools.js +346 -0
- package/dist/mcp/server/core/activity-support.js +70 -0
- package/dist/{capabilities.js → mcp/server/core/capabilities.js} +5 -3
- package/dist/{capability-registry.js → mcp/server/core/capability-registry.js} +9 -4
- package/dist/mcp/server/core/capability-support.js +90 -0
- package/dist/mcp/server/core/schemas.js +216 -0
- package/dist/mcp/server/core/tool-support.js +210 -0
- package/dist/mcp/server/core/version.js +9 -0
- package/dist/mcp/server/operations/runtime/filesystem-tools.js +455 -0
- package/dist/mcp/server/operations/runtime/mutation-diagnostics.js +54 -0
- package/dist/mcp/server/operations/runtime/operation-runtime.js +524 -0
- package/dist/mcp/server/transport/http-server.js +301 -0
- package/dist/mcp/server/transport/http-support.js +43 -0
- package/dist/mcp/server/workspace/runtime/workspace-open-presentation.js +242 -0
- package/dist/mcp/server/workspace/runtime/workspace-open-schema.js +187 -0
- package/dist/mcp/server/workspace/runtime/workspace-open.js +555 -0
- package/dist/mcp/server/workspace/runtime/workspace-tools.js +601 -0
- package/dist/mcp/server-instructions.js +1 -1
- package/dist/{config.js → runtime/config/config.js} +4 -3
- package/dist/{user-config.js → runtime/config/user-config.js} +14 -63
- package/dist/{db → runtime/state/db}/client.js +1 -1
- package/dist/{db → runtime/state/db}/migrations.js +34 -3
- package/dist/runtime/state/lock/file-lock.js +130 -0
- package/dist/runtime/testing/server-fixture.js +190 -0
- package/dist/server.js +133 -4189
- package/dist/subagents/providers/adapters/claude.js +86 -52
- package/dist/subagents/providers/adapters/codex.js +103 -39
- package/dist/subagents/providers/adapters/opencode.js +114 -84
- package/dist/subagents/providers/adapters/pi.js +59 -8
- package/dist/subagents/providers/availability.js +15 -30
- package/dist/subagents/providers/runtime/external-command.js +56 -0
- package/dist/subagents/sessions/capability.js +1 -1
- package/dist/subagents/sessions/execution.js +1 -1
- package/dist/subagents/sessions/mcp/activity-audit-test-support.js +30 -0
- package/dist/subagents/sessions/store.js +1 -1
- package/dist/ui/.vite/manifest.json +342 -410
- package/dist/ui/activity-panel-app.html +2 -4
- package/dist/ui/assets/activity-panel-app-7Xe0alhm.css +1 -0
- package/dist/ui/assets/activity-panel-app-Sl1Jro8v.js +115 -0
- package/dist/ui/assets/angular-html-B_yn91wW.js +1 -0
- package/dist/ui/assets/{angular-ts-Bxh4-O1-.js → angular-ts-DSgtloKz.js} +1 -1
- package/dist/ui/assets/{apl-BmYHKQ6m.js → apl-NatWtF8X.js} +1 -1
- package/dist/ui/assets/{astro-C51vqDKq.js → astro-BYFEVBuN.js} +1 -1
- package/dist/ui/assets/{blade-CdhVsmwq.js → blade-DoO7yvMR.js} +1 -1
- package/dist/ui/assets/c-Rl2hMCcg.js +1 -0
- package/dist/ui/assets/{cobol-Cf6azoN_.js → cobol-ByC5mWiB.js} +1 -1
- package/dist/ui/assets/{coffee-vVdGzlmt.js → coffee-Dez77Qov.js} +1 -1
- package/dist/ui/assets/cpp-DtaAQT9h.js +1 -0
- package/dist/ui/assets/{crystal-5UZy6KIX.js → crystal-Bgmbf8ec.js} +1 -1
- package/dist/ui/assets/css-C1tjBtVN.js +1 -0
- package/dist/ui/assets/{edge-CESgo4ia.js → edge-DeH6u8Wg.js} +1 -1
- package/dist/ui/assets/{elixir-9FKfw4cJ.js → elixir-_H3KnmWT.js} +1 -1
- package/dist/ui/assets/{elm-Dv7A0aLa.js → elm-CsCe3U28.js} +1 -1
- package/dist/ui/assets/{erb-DHFmDvM0.js → erb-CbM-L2g8.js} +1 -1
- package/dist/ui/assets/{git-rebase-utbU6CzV.js → git-rebase-D0s5pDh9.js} +1 -1
- package/dist/ui/assets/{glimmer-js-t2kiCkyB.js → glimmer-js-BfOToq_A.js} +1 -1
- package/dist/ui/assets/{glimmer-ts-F0Jv5okV.js → glimmer-ts-CCUOkL2K.js} +1 -1
- package/dist/ui/assets/glsl-DH4ec9dE.js +1 -0
- package/dist/ui/assets/graphql-DilAMBoI.js +1 -0
- package/dist/ui/assets/{hack-8yjdbaSA.js → hack-BDkNDZlF.js} +1 -1
- package/dist/ui/assets/haml-D_QBp6K7.js +1 -0
- package/dist/ui/assets/{handlebars-BBFEg4Lo.js → handlebars-DePUrTpC.js} +1 -1
- package/dist/ui/assets/heavy-payload-DOwZpO6n.js +290 -0
- package/dist/ui/assets/html-By4dNnM4.js +1 -0
- package/dist/ui/assets/{html-derivative-DJAQcZyc.js → html-derivative-DDw9Ef4Q.js} +1 -1
- package/dist/ui/assets/{http-Dg5LSYRK.js → http-B0QxWc9M.js} +1 -1
- package/dist/ui/assets/{hurl-Y5uYzMZR.js → hurl-vMQ4lda1.js} +1 -1
- package/dist/ui/assets/java-DYRe2lWG.js +1 -0
- package/dist/ui/assets/javascript-CxyGqwcL.js +1 -0
- package/dist/ui/assets/{jinja-Bt30h8Ik.js → jinja-JUwV-fZt.js} +1 -1
- package/dist/ui/assets/{jison-D5uZ1Nyo.js → jison-7ZqCEvS8.js} +1 -1
- package/dist/ui/assets/json-dbltgiiA.js +1 -0
- package/dist/ui/assets/jsx-oDopT7YQ.js +1 -0
- package/dist/ui/assets/{julia-76hRaaGz.js → julia-CkKiNSav.js} +1 -1
- package/dist/ui/assets/{just-CDE7W0vW.js → just-DuqCAKJ3.js} +1 -1
- package/dist/ui/assets/{latex-CVO2Byab.js → latex-sudcRDSn.js} +1 -1
- package/dist/ui/assets/{liquid-CA-AS3EX.js → liquid-Di-08Js7.js} +1 -1
- package/dist/ui/assets/lua-YlqdMoV0.js +1 -0
- package/dist/ui/assets/{marko-_BWrPfny.js → marko-CJRNyJDb.js} +1 -1
- package/dist/ui/assets/{mdc-PyJv85eS.js → mdc-t12CzZHs.js} +1 -1
- package/dist/ui/assets/{nginx-DAWVw8yu.js → nginx-57-b4XwT.js} +1 -1
- package/dist/ui/assets/{nim-CKE1dzpU.js → nim-BzrswYQa.js} +1 -1
- package/dist/ui/assets/{perl-BBhWva19.js → perl-D9JR_iIh.js} +1 -1
- package/dist/ui/assets/{php-ChIeaAoF.js → php-Py2gFHil.js} +1 -1
- package/dist/ui/assets/{pug-CMgI5afi.js → pug-PzLLy0h1.js} +1 -1
- package/dist/ui/assets/{qml-CjxTkcFH.js → qml-By8fwgV4.js} +1 -1
- package/dist/ui/assets/r-CcezTbLq.js +1 -0
- package/dist/ui/assets/{razor-i4DbHr8p.js → razor-dG-vdhL_.js} +1 -1
- package/dist/ui/assets/regexp-BlsQ-N_8.js +1 -0
- package/dist/ui/assets/{rst-oy_97Kl4.js → rst-C2awDCat.js} +1 -1
- package/dist/ui/assets/{ruby-C5zYZIyl.js → ruby-zA2W_49d.js} +1 -1
- package/dist/ui/assets/{sas-BnMEh2S3.js → sas-D49bhyaN.js} +1 -1
- package/dist/ui/assets/scss-BQb-cp01.js +1 -0
- package/dist/ui/assets/shellscript-Dga4H_Ni.js +1 -0
- package/dist/ui/assets/{shellsession-BpE5o7_3.js → shellsession-n3-5CqcF.js} +1 -1
- package/dist/ui/assets/{soy-COesmhTY.js → soy-XYBOf_BA.js} +1 -1
- package/dist/ui/assets/sql-ET4u5Cpl.js +1 -0
- package/dist/ui/assets/{stata-CGQEuSZq.js → stata-CLHcJHO_.js} +1 -1
- package/dist/ui/assets/{surrealql-Bz4JbU-9.js → surrealql-BdjQ60T4.js} +1 -1
- package/dist/ui/assets/{svelte-1jM2ejXj.js → svelte-B2RPtKJe.js} +1 -1
- package/dist/ui/assets/{templ-C-VwELcp.js → templ-CrByPW_W.js} +1 -1
- package/dist/ui/assets/{tex-t1rUiQCm.js → tex-DZBkg363.js} +1 -1
- package/dist/ui/assets/{ts-tags-Bewoa6pS.js → ts-tags-DJR3Gphb.js} +1 -1
- package/dist/ui/assets/tsx-CajzROyy.js +1 -0
- package/dist/ui/assets/{twig-DVg-vELl.js → twig-BZjk5Zda.js} +1 -1
- package/dist/ui/assets/typescript-B-EWToLj.js +1 -0
- package/dist/ui/assets/{vue-wPQ7UlMy.js → vue-D_rHCCr8.js} +1 -1
- package/dist/ui/assets/{vue-html-BHtJ8geX.js → vue-html-CC4WlvVt.js} +1 -1
- package/dist/ui/assets/{vue-vine-CNSGAWwc.js → vue-vine-BgNMVgpk.js} +1 -1
- package/dist/ui/assets/xml-BI86-WbH.js +1 -0
- package/dist/ui/assets/{xsl-3_vJ27aE.js → xsl-DXeLZV4J.js} +1 -1
- package/dist/ui/assets/yaml-60_PFlWa.js +1 -0
- package/dist/workspaces/bootstrap.js +72 -0
- package/dist/workspaces/context.js +337 -0
- package/dist/workspaces/conversation-test-support.js +86 -0
- package/dist/{git-worktrees.js → workspaces/git/git-worktrees.js} +1 -1
- package/dist/workspaces/inventory.js +131 -0
- package/dist/workspaces/paths.js +55 -0
- package/dist/{remote-auth.js → workspaces/relay/auth/remote-auth.js} +2 -2
- package/dist/workspaces/relay/result-support.js +111 -0
- package/dist/workspaces/relay/tests/test-support.js +203 -0
- package/dist/{remote-mcp-connection-pool.js → workspaces/relay/transport/remote-mcp-connection-pool.js} +1 -1
- package/dist/{remote-transport.js → workspaces/relay/transport/remote-transport.js} +30 -3
- package/dist/workspaces/relay/types.js +1 -0
- package/dist/{remote-workspace-relay.js → workspaces/relay/workspace-relay.js} +23 -172
- package/dist/workspaces/resources/resource-monitor.js +377 -0
- package/dist/workspaces/resources/skills.js +283 -0
- package/dist/{review-checkpoints.js → workspaces/review/review-checkpoints.js} +2 -2
- package/dist/workspaces/sessions.js +460 -0
- package/dist/{workspace-store.js → workspaces/state/workspace-store.js} +2 -2
- package/dist/workspaces.js +77 -993
- package/docs/chatgpt-coding-workflow.md +7 -14
- package/docs/configuration.md +26 -34
- package/docs/gotchas.md +0 -30
- package/docs/roadmap.md +6 -10
- package/docs/security.md +1 -5
- package/docs/setup.md +0 -11
- package/package.json +6 -9
- package/scripts/ci/architecture.mjs +109 -0
- package/scripts/ci/verify.mjs +5 -0
- package/scripts/debug/accept/bootstrap.mjs +326 -0
- package/scripts/debug/accept/harness.mjs +76 -0
- package/scripts/debug/accept/support.mjs +432 -0
- package/scripts/debug/accept.mjs +43 -806
- package/scripts/debug/relay-accept/support.mjs +135 -0
- package/scripts/debug/relay-accept.mjs +26 -115
- package/scripts/debug/traffic/support.mjs +167 -0
- package/scripts/debug/traffic/traffic-audit.mjs +4 -161
- package/scripts/{lsp-interop-support.test.mjs → lsp/interop-support.test.mjs} +1 -1
- package/scripts/{lsp-interop.mjs → lsp/interop.mjs} +3 -3
- package/scripts/release/publish.mjs +12 -9
- package/scripts/release/release-gate.test.mjs +3 -1
- package/scripts/release/release-version.test.mjs +13 -1
- package/scripts/release-parity.mjs +1 -1
- package/scripts/release-proof.mjs +15 -0
- package/scripts/release-proof.test.mjs +27 -1
- package/scripts/release-version.mjs +16 -1
- package/scripts/wiki/sync.mjs +21 -10
- package/dist/activity/bash-output-store.js +0 -127
- package/dist/pi-tools.js +0 -110
- package/dist/skills.js +0 -127
- package/dist/ui/assets/activity-panel-app-7AckaxIR.js +0 -2
- package/dist/ui/assets/angular-html-BkgDQl-G.js +0 -1
- package/dist/ui/assets/c-IBzBpxAO.js +0 -1
- package/dist/ui/assets/chunk-EyZ2wyi3.js +0 -1
- package/dist/ui/assets/cpp-CVHeO-Jk.js +0 -1
- package/dist/ui/assets/css-DEfus9J5.js +0 -1
- package/dist/ui/assets/glsl-_ooCjaeB.js +0 -1
- package/dist/ui/assets/graphql-B9LqP-cY.js +0 -1
- package/dist/ui/assets/haml-BU5EoZBn.js +0 -1
- package/dist/ui/assets/heavy-payload-Bol1_mcs.js +0 -4
- package/dist/ui/assets/html-D-C6YZk4.js +0 -1
- package/dist/ui/assets/java-ppHvycj5.js +0 -1
- package/dist/ui/assets/javascript-DDTjKLlQ.js +0 -1
- package/dist/ui/assets/json-Cw91r8h1.js +0 -1
- package/dist/ui/assets/jsx-04X2FT_1.js +0 -1
- package/dist/ui/assets/lua-CiyBp52Y.js +0 -1
- package/dist/ui/assets/r-DfFga4si.js +0 -1
- package/dist/ui/assets/regexp-iC9_nbSI.js +0 -1
- package/dist/ui/assets/review-payload-BqJp0c5e.js +0 -1
- package/dist/ui/assets/scrollbar-CvE-I-jG.js +0 -287
- package/dist/ui/assets/scss-BfZXsAWQ.js +0 -1
- package/dist/ui/assets/shellscript-BC2srNaB.js +0 -1
- package/dist/ui/assets/sql-Ccv_yeUm.js +0 -1
- package/dist/ui/assets/tsx-CTXzrBkN.js +0 -1
- package/dist/ui/assets/typescript--ElHoFkw.js +0 -1
- package/dist/ui/assets/workspace-app-BLW7p6IZ.js +0 -114
- package/dist/ui/assets/workspace-app-BjNqR0en.js +0 -1
- package/dist/ui/assets/workspace-app-CmaYU4DW.js +0 -3
- package/dist/ui/assets/workspace-app-D2bJ5fjt.css +0 -1
- package/dist/ui/assets/workspace-lifecycle-app-BVbI2Ilf.js +0 -1
- package/dist/ui/assets/xml-JnmX6vyS.js +0 -1
- package/dist/ui/assets/yaml-OAcZkP-w.js +0 -1
- package/dist/ui/workspace-app.html +0 -18
- package/dist/ui/workspace-lifecycle-app.html +0 -18
- package/docs/agent-profile-schema.md +0 -114
- package/docs/artifact-exchange.md +0 -65
- package/docs/versioning.md +0 -205
- package/skills/subagent-delegation/SKILL.md +0 -132
- /package/capabilities/{managed-worktrees → workspace/managed-worktrees}/GUIDE.md +0 -0
- /package/dist/activity/{process-output-audit.js → runtime/process-output-audit.js} +0 -0
- /package/dist/{artifact-error.js → mcp/artifacts/artifact-error.js} +0 -0
- /package/dist/{artifact-tools.js → mcp/artifacts/artifact-tools.js} +0 -0
- /package/dist/{incoming-artifacts.js → mcp/artifacts/incoming-artifacts.js} +0 -0
- /package/dist/{advertised-files.js → mcp/filesystem/advertised-files.js} +0 -0
- /package/dist/{apply-patch.js → mcp/filesystem/apply-patch.js} +0 -0
- /package/dist/{file-mutations.js → mcp/filesystem/file-mutations.js} +0 -0
- /package/dist/{roots.js → mcp/filesystem/roots.js} +0 -0
- /package/dist/{oauth → mcp/oauth}/public-url.js +0 -0
- /package/dist/{oauth → mcp/oauth}/router.js +0 -0
- /package/dist/{operations → mcp/operations}/batch/executor.js +0 -0
- /package/dist/{operations → mcp/operations}/batch/scheduler.js +0 -0
- /package/dist/{operations → mcp/operations}/batch/types.js +0 -0
- /package/dist/{operations → mcp/operations}/bulk-mutation.js +0 -0
- /package/dist/{operations → mcp/operations}/bulk-read.js +0 -0
- /package/dist/{operations → mcp/operations}/core-operation-executor.js +0 -0
- /package/dist/{request-meta.js → mcp/request-meta.js} +0 -0
- /package/dist/{mcp-sessions.js → mcp/server/transport/mcp-sessions.js} +0 -0
- /package/dist/{server-shutdown.js → mcp/server/transport/server-shutdown.js} +0 -0
- /package/dist/{logger.js → runtime/logging/logger.js} +0 -0
- /package/dist/{db → runtime/state/db}/schema.js +0 -0
- /package/dist/{composite-activity.js → workspaces/composite/composite-activity.js} +0 -0
- /package/dist/{composite-workspaces.js → workspaces/composite/composite-workspaces.js} +0 -0
- /package/dist/{git.js → workspaces/git/git.js} +0 -0
- /package/dist/{workspace-presentation.js → workspaces/presentation/workspace-presentation.js} +0 -0
- /package/dist/{workspace-task-reminders.js → workspaces/tasks/workspace-task-reminders.js} +0 -0
- /package/dist/{workspace-tasks.js → workspaces/tasks/workspace-tasks.js} +0 -0
- /package/scripts/{lsp-interop-support.mjs → lsp/interop-support.mjs} +0 -0
package/dist/cli.js
CHANGED
|
@@ -8,23 +8,21 @@ import { tmpdir } from "node:os";
|
|
|
8
8
|
import { join, resolve } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import * as prompts from "@clack/prompts";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { runHooksCommand } from "./hook-cli.js";
|
|
11
|
+
import { loadConfig } from "./runtime/config/config.js";
|
|
12
|
+
import { runInit } from "./cli/init.js";
|
|
13
|
+
import { runHooksCommand } from "./mcp/hooks/hook-cli.js";
|
|
15
14
|
import { executeSubagentSession } from "./subagents/sessions/execution.js";
|
|
16
15
|
import { SubagentDeliveryMailbox } from "./subagents/sessions/delivery-mailbox.js";
|
|
17
16
|
import { SubagentSessionManager } from "./subagents/sessions/manager.js";
|
|
18
17
|
import { formatSubagentProviderAvailabilitySummary } from "./subagents/providers/availability.js";
|
|
19
18
|
import { parseSubagentRunArgs } from "./subagents/cli-target.js";
|
|
20
|
-
import { ensureForgeRelayInstanceId,
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
19
|
+
import { ensureForgeRelayInstanceId, loadForgeRelayFiles, removeForgeRelayRemote, renameForgeRelayRemote, writeForgeRelayConfig, writeForgeRelayRemote, } from "./runtime/config/user-config.js";
|
|
20
|
+
import { shutdownHttpServer } from "./mcp/server/transport/server-shutdown.js";
|
|
21
|
+
import { publicEndpointUrl } from "./mcp/oauth/public-url.js";
|
|
22
|
+
import { authenticateRemote, defaultRemoteAlias, isRemoteMcpUnauthorized, normalizeRemoteServiceTarget, refreshRemoteAuthentication, verifyRemoteMcp, } from "./workspaces/relay/auth/remote-auth.js";
|
|
23
|
+
import { defaultSshRouteAlias, parseSshRoute, readRemoteOwnerToken, withRemoteServiceEndpoint, } from "./workspaces/relay/transport/remote-transport.js";
|
|
24
|
+
import { assertSupportedNode, checkBashShell, checkGitAvailable, checkSqliteNative, nodeVersionStatus, normalizeOptionalPublicBaseUrl, } from "./cli/setup-support.js";
|
|
26
25
|
const require = createRequire(import.meta.url);
|
|
27
|
-
const SUPPORTED_NODE_RANGE = ">=20.12 <27";
|
|
28
26
|
async function main(argv) {
|
|
29
27
|
assertSupportedNode();
|
|
30
28
|
const [rawCommand, ...args] = argv;
|
|
@@ -74,11 +72,11 @@ function normalizeCommand(command) {
|
|
|
74
72
|
async function ensureConfigured() {
|
|
75
73
|
const files = loadForgeRelayFiles();
|
|
76
74
|
if (files.configExists && files.authExists) {
|
|
77
|
-
ensureForgeRelayInstanceId();
|
|
75
|
+
await ensureForgeRelayInstanceId();
|
|
78
76
|
return;
|
|
79
77
|
}
|
|
80
78
|
if (process.env.FORGERELAY_OAUTH_OWNER_TOKEN) {
|
|
81
|
-
ensureForgeRelayInstanceId();
|
|
79
|
+
await ensureForgeRelayInstanceId();
|
|
82
80
|
return;
|
|
83
81
|
}
|
|
84
82
|
if (!input.isTTY || !output.isTTY) {
|
|
@@ -93,92 +91,6 @@ async function ensureConfigured() {
|
|
|
93
91
|
}
|
|
94
92
|
await runInit({ force: false });
|
|
95
93
|
}
|
|
96
|
-
async function runInit({ force }) {
|
|
97
|
-
const files = loadForgeRelayFiles();
|
|
98
|
-
if (!force && files.configExists && files.authExists) {
|
|
99
|
-
prompts.log.info(`ForgeRelay is already configured at ${files.dir}`);
|
|
100
|
-
prompts.log.info("Run `forgerelay init --force` to update it.");
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
try {
|
|
104
|
-
prompts.intro("ForgeRelay setup");
|
|
105
|
-
const defaultRoots = files.config.allowedRoots?.join(", ") || process.cwd();
|
|
106
|
-
const rootsAnswer = await textPrompt({
|
|
107
|
-
message: `Where are your projects located? Press Enter to use ${defaultRoots}`,
|
|
108
|
-
placeholder: defaultRoots,
|
|
109
|
-
defaultValue: defaultRoots,
|
|
110
|
-
validate: (value) => value?.trim() ? undefined : "Enter at least one project root.",
|
|
111
|
-
});
|
|
112
|
-
const allowedRoots = rootsAnswer
|
|
113
|
-
.split(",")
|
|
114
|
-
.map((root) => resolve(expandHomePath(root.trim())))
|
|
115
|
-
.filter(Boolean);
|
|
116
|
-
const defaultPort = String(files.config.port ?? 7676);
|
|
117
|
-
const portAnswer = await textPrompt({
|
|
118
|
-
message: `Which local port should ForgeRelay use? Press Enter to use ${defaultPort}`,
|
|
119
|
-
placeholder: defaultPort,
|
|
120
|
-
defaultValue: defaultPort,
|
|
121
|
-
validate: validatePort,
|
|
122
|
-
});
|
|
123
|
-
const port = Number(portAnswer);
|
|
124
|
-
prompts.note([
|
|
125
|
-
"ForgeRelay needs one or more public base URLs so ChatGPT or Claude can reach this MCP server.",
|
|
126
|
-
"Create a tunnel or reverse proxy with Cloudflare Tunnel, ngrok, Pinggy, Tailscale Funnel, or your own HTTPS proxy.",
|
|
127
|
-
"Each URL may include its own route prefix. Separate multiple URLs with commas; the first is canonical.",
|
|
128
|
-
"",
|
|
129
|
-
"Example: https://forge.example.com/forgerelay/main, https://alias.example.com/relay",
|
|
130
|
-
].join("\n"), "Public URL required");
|
|
131
|
-
const defaultPublicBaseUrls = Array.isArray(files.config.publicBaseUrl)
|
|
132
|
-
? files.config.publicBaseUrl.join(", ")
|
|
133
|
-
: files.config.publicBaseUrl ?? "";
|
|
134
|
-
const publicBaseUrls = normalizePublicBaseUrlsInput(await textPrompt({
|
|
135
|
-
message: defaultPublicBaseUrls
|
|
136
|
-
? `What are the public base URLs? Press Enter to keep ${defaultPublicBaseUrls}`
|
|
137
|
-
: "What are the public base URLs?",
|
|
138
|
-
placeholder: defaultPublicBaseUrls || "https://your-tunnel-host.example.com",
|
|
139
|
-
defaultValue: defaultPublicBaseUrls,
|
|
140
|
-
validate: validateRequiredPublicBaseUrls,
|
|
141
|
-
}));
|
|
142
|
-
const publicBaseUrl = compactPublicBaseUrlConfig(publicBaseUrls);
|
|
143
|
-
const config = {
|
|
144
|
-
host: files.config.host ?? "127.0.0.1",
|
|
145
|
-
port,
|
|
146
|
-
allowedRoots,
|
|
147
|
-
publicBaseUrl,
|
|
148
|
-
allowedHosts: files.config.allowedHosts,
|
|
149
|
-
workflowInstructions: files.config.workflowInstructions,
|
|
150
|
-
appendInstructions: files.config.appendInstructions,
|
|
151
|
-
subagents: resolveSubagentsFlag(files.config),
|
|
152
|
-
};
|
|
153
|
-
const auth = {
|
|
154
|
-
...files.auth,
|
|
155
|
-
ownerToken: files.auth.ownerToken ?? generateOwnerToken(),
|
|
156
|
-
instanceId: files.auth.instanceId ?? generateInstanceId(),
|
|
157
|
-
};
|
|
158
|
-
const configPath = writeForgeRelayConfig(config);
|
|
159
|
-
const authPath = writeForgeRelayAuth(auth);
|
|
160
|
-
const lines = [
|
|
161
|
-
`Config: ${configPath}`,
|
|
162
|
-
`Auth: ${authPath}`,
|
|
163
|
-
`Local MCP URL: http://${config.host}:${config.port}/mcp`,
|
|
164
|
-
...publicBaseUrls.map((baseUrl, index) => `${index === 0 ? "Public MCP URL" : "Public MCP alias"}: ${publicEndpointUrl(baseUrl, "mcp").toString()}`),
|
|
165
|
-
];
|
|
166
|
-
prompts.note(lines.join("\n"), "ForgeRelay configured");
|
|
167
|
-
prompts.note([
|
|
168
|
-
`Owner password: ${auth.ownerToken}`,
|
|
169
|
-
"Use this when ChatGPT or Claude asks you to approve ForgeRelay access.",
|
|
170
|
-
`Stored at: ${authPath}`,
|
|
171
|
-
].join("\n"), "Owner password");
|
|
172
|
-
prompts.outro("Run `forgerelay serve` to start the MCP server.");
|
|
173
|
-
}
|
|
174
|
-
catch (error) {
|
|
175
|
-
if (error instanceof SetupCancelledError) {
|
|
176
|
-
prompts.cancel("Setup cancelled");
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
throw error;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
94
|
async function serve() {
|
|
183
95
|
const sqliteStatus = checkSqliteNative();
|
|
184
96
|
if (sqliteStatus !== "ok") {
|
|
@@ -195,7 +107,7 @@ async function serve() {
|
|
|
195
107
|
const { app, close, subagentProviders } = createServer(config);
|
|
196
108
|
const httpServer = app.listen(config.port, config.host, () => {
|
|
197
109
|
console.log(`forgerelay listening on http://${config.host}:${config.port}/mcp`);
|
|
198
|
-
console.log(`
|
|
110
|
+
console.log(`client-facing base url: ${config.publicBaseUrl}`);
|
|
199
111
|
console.log(`allowed roots: ${config.allowedRoots.join(", ")}`);
|
|
200
112
|
console.log(`allowed hosts: ${config.allowedHosts.join(", ")}`);
|
|
201
113
|
if (config.allowedHosts.includes("*")) {
|
|
@@ -316,7 +228,7 @@ async function runAuthCommand(args) {
|
|
|
316
228
|
if (!fromAlias || !toAlias || extra.length > 0) {
|
|
317
229
|
throw new Error("Usage: forgerelay auth rename <old-alias> <new-alias>");
|
|
318
230
|
}
|
|
319
|
-
renameForgeRelayRemote(fromAlias, toAlias);
|
|
231
|
+
await renameForgeRelayRemote(fromAlias, toAlias);
|
|
320
232
|
console.log(`Renamed remote ${fromAlias} to ${toAlias}.`);
|
|
321
233
|
return;
|
|
322
234
|
}
|
|
@@ -324,7 +236,7 @@ async function runAuthCommand(args) {
|
|
|
324
236
|
const [alias, ...extra] = rest;
|
|
325
237
|
if (!alias || extra.length > 0)
|
|
326
238
|
throw new Error("Usage: forgerelay auth remove <alias>");
|
|
327
|
-
removeForgeRelayRemote(alias);
|
|
239
|
+
await removeForgeRelayRemote(alias);
|
|
328
240
|
console.log(`Removed remote ${alias}.`);
|
|
329
241
|
return;
|
|
330
242
|
}
|
|
@@ -341,7 +253,7 @@ async function runAuthCommand(args) {
|
|
|
341
253
|
let refreshed = false;
|
|
342
254
|
if (remote.accessTokenExpiresAt <= Math.floor(Date.now() / 1000)) {
|
|
343
255
|
remote = await refreshRemoteAuthentication(remote, endpoint);
|
|
344
|
-
writeForgeRelayRemote(alias, remote);
|
|
256
|
+
await writeForgeRelayRemote(alias, remote);
|
|
345
257
|
refreshed = true;
|
|
346
258
|
}
|
|
347
259
|
try {
|
|
@@ -351,7 +263,7 @@ async function runAuthCommand(args) {
|
|
|
351
263
|
if (refreshed || !isRemoteMcpUnauthorized(error))
|
|
352
264
|
throw error;
|
|
353
265
|
remote = await refreshRemoteAuthentication(remote, endpoint);
|
|
354
|
-
writeForgeRelayRemote(alias, remote);
|
|
266
|
+
await writeForgeRelayRemote(alias, remote);
|
|
355
267
|
await verifyRemoteMcp(remote, endpoint);
|
|
356
268
|
}
|
|
357
269
|
});
|
|
@@ -378,9 +290,9 @@ async function runAuthCommand(args) {
|
|
|
378
290
|
: defaultRemoteAlias(remote.target);
|
|
379
291
|
const alias = parsed.alias?.trim() || existingAlias || defaultAlias;
|
|
380
292
|
if (!files.auth.instanceId) {
|
|
381
|
-
ensureForgeRelayInstanceId();
|
|
293
|
+
await ensureForgeRelayInstanceId();
|
|
382
294
|
}
|
|
383
|
-
writeForgeRelayRemote(alias, remote);
|
|
295
|
+
await writeForgeRelayRemote(alias, remote);
|
|
384
296
|
console.log(`Authenticated remote ${alias} (${remote.instanceId}).`);
|
|
385
297
|
}
|
|
386
298
|
async function runDoctor() {
|
|
@@ -396,16 +308,17 @@ async function runDoctor() {
|
|
|
396
308
|
console.log(`SQLite native dependency: ${checkSqliteNative()}`);
|
|
397
309
|
try {
|
|
398
310
|
const config = loadConfig();
|
|
399
|
-
console.log(`
|
|
400
|
-
console.log(`
|
|
401
|
-
console.log(`
|
|
402
|
-
console.log(`
|
|
311
|
+
console.log(`Bind MCP URL: http://${config.host}:${config.port}/mcp`);
|
|
312
|
+
console.log(`Client-facing base URLs: ${config.publicBaseUrls.join(", ")}`);
|
|
313
|
+
console.log(`Client-facing base URL: ${config.publicBaseUrl}`);
|
|
314
|
+
console.log(`Client-facing MCP URL: ${publicEndpointUrl(config.publicBaseUrl, "mcp").toString()}`);
|
|
403
315
|
console.log(`Tool mode: ${config.toolMode}`);
|
|
404
316
|
console.log(`Widgets: ${config.widgets}`);
|
|
405
317
|
console.log(`Trust proxy: ${config.logging.trustProxy ? "one hop" : "off"}`);
|
|
406
318
|
console.log(`Artifacts: ${config.artifactsEnabled ? "enabled" : "disabled"}`);
|
|
407
319
|
console.log(`Subagents: ${config.subagents ? "enabled" : "disabled"}`);
|
|
408
320
|
console.log(`Skills: ${config.skillsEnabled ? "enabled" : "disabled"}`);
|
|
321
|
+
console.log(`Agent-managed Language Server install: ${config.allowAgentLanguageServerInstall ? "enabled" : "disabled"}`);
|
|
409
322
|
console.log(`Allowed roots: ${config.allowedRoots.join(", ")}`);
|
|
410
323
|
console.log(`Allowed hosts: ${config.allowedHosts.join(", ")}`);
|
|
411
324
|
}
|
|
@@ -641,112 +554,6 @@ function printVersion() {
|
|
|
641
554
|
}
|
|
642
555
|
console.log(packageJson.version);
|
|
643
556
|
}
|
|
644
|
-
function isNullConfigValue(value) {
|
|
645
|
-
const normalized = value.trim().toLowerCase();
|
|
646
|
-
return !normalized || normalized === "null" || normalized === "none";
|
|
647
|
-
}
|
|
648
|
-
function normalizeOptionalPublicBaseUrl(value) {
|
|
649
|
-
const trimmed = value.trim();
|
|
650
|
-
if (isNullConfigValue(trimmed))
|
|
651
|
-
return null;
|
|
652
|
-
return compactPublicBaseUrlConfig(normalizePublicBaseUrlsInput(trimmed));
|
|
653
|
-
}
|
|
654
|
-
function normalizePublicBaseUrlsInput(value) {
|
|
655
|
-
const entries = value
|
|
656
|
-
.split(",")
|
|
657
|
-
.map((entry) => entry.trim())
|
|
658
|
-
.filter(Boolean);
|
|
659
|
-
if (entries.length === 0)
|
|
660
|
-
throw new Error("Enter at least one public base URL.");
|
|
661
|
-
return Array.from(new Set(entries.map((entry) => normalizePublicBaseUrl(entry))));
|
|
662
|
-
}
|
|
663
|
-
function compactPublicBaseUrlConfig(baseUrls) {
|
|
664
|
-
return baseUrls.length === 1 ? baseUrls[0] : baseUrls;
|
|
665
|
-
}
|
|
666
|
-
function normalizePublicBaseUrl(value) {
|
|
667
|
-
const trimmed = value.trim();
|
|
668
|
-
const parsed = new URL(trimmed);
|
|
669
|
-
parsed.hash = "";
|
|
670
|
-
parsed.search = "";
|
|
671
|
-
parsed.pathname = parsed.pathname.replace(/\/+$/, "");
|
|
672
|
-
return parsed.toString().replace(/\/$/, "");
|
|
673
|
-
}
|
|
674
|
-
async function textPrompt(options) {
|
|
675
|
-
const result = await prompts.text({
|
|
676
|
-
...options,
|
|
677
|
-
validate: (value) => options.validate?.(value?.trim() ? value : options.defaultValue),
|
|
678
|
-
});
|
|
679
|
-
if (prompts.isCancel(result))
|
|
680
|
-
throw new SetupCancelledError();
|
|
681
|
-
const value = String(result).trim();
|
|
682
|
-
return value || options.defaultValue;
|
|
683
|
-
}
|
|
684
|
-
function validatePort(value) {
|
|
685
|
-
const port = Number(value);
|
|
686
|
-
return Number.isInteger(port) && port >= 1 && port <= 65535
|
|
687
|
-
? undefined
|
|
688
|
-
: "Enter a port between 1 and 65535.";
|
|
689
|
-
}
|
|
690
|
-
function validateRequiredPublicBaseUrls(value) {
|
|
691
|
-
const trimmed = value?.trim() ?? "";
|
|
692
|
-
if (!trimmed)
|
|
693
|
-
return "Enter at least one public base URL from your tunnel or reverse proxy.";
|
|
694
|
-
try {
|
|
695
|
-
normalizePublicBaseUrlsInput(trimmed);
|
|
696
|
-
return undefined;
|
|
697
|
-
}
|
|
698
|
-
catch (error) {
|
|
699
|
-
return error instanceof Error ? error.message : String(error);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
function assertSupportedNode() {
|
|
703
|
-
if (satisfies(process.versions.node, SUPPORTED_NODE_RANGE))
|
|
704
|
-
return;
|
|
705
|
-
throw new Error([
|
|
706
|
-
`ForgeRelay requires Node ${SUPPORTED_NODE_RANGE}.`,
|
|
707
|
-
`Current Node: ${process.version}`,
|
|
708
|
-
"",
|
|
709
|
-
"Install Node 22 LTS or use a version manager such as nvm, fnm, or mise.",
|
|
710
|
-
].join("\n"));
|
|
711
|
-
}
|
|
712
|
-
function nodeVersionStatus() {
|
|
713
|
-
return satisfies(process.versions.node, SUPPORTED_NODE_RANGE)
|
|
714
|
-
? `supported ${SUPPORTED_NODE_RANGE}`
|
|
715
|
-
: `unsupported, requires ${SUPPORTED_NODE_RANGE}`;
|
|
716
|
-
}
|
|
717
|
-
class SetupCancelledError extends Error {
|
|
718
|
-
}
|
|
719
|
-
function checkSqliteNative() {
|
|
720
|
-
try {
|
|
721
|
-
const Database = require("better-sqlite3");
|
|
722
|
-
const db = new Database(":memory:");
|
|
723
|
-
db.close();
|
|
724
|
-
return "ok";
|
|
725
|
-
}
|
|
726
|
-
catch (error) {
|
|
727
|
-
return error instanceof Error ? error.message : String(error);
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
function checkGitAvailable() {
|
|
731
|
-
try {
|
|
732
|
-
const { execFileSync } = require("node:child_process");
|
|
733
|
-
return execFileSync("git", ["--version"], { encoding: "utf8" }).trim();
|
|
734
|
-
}
|
|
735
|
-
catch (error) {
|
|
736
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
737
|
-
return `unavailable (${message})`;
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
function checkBashShell() {
|
|
741
|
-
try {
|
|
742
|
-
const { shell, args } = getShellConfig();
|
|
743
|
-
return `${shell} ${args.join(" ")}`;
|
|
744
|
-
}
|
|
745
|
-
catch (error) {
|
|
746
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
747
|
-
return `unavailable (${message})`;
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
557
|
main(process.argv.slice(2)).catch((error) => {
|
|
751
558
|
console.error(error instanceof Error ? error.message : String(error));
|
|
752
559
|
process.exitCode = 1;
|
|
@@ -3,9 +3,9 @@ import { readFile, realpath } from "node:fs/promises";
|
|
|
3
3
|
import { basename, resolve } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import { createMessageConnection } from "vscode-jsonrpc/node";
|
|
6
|
-
import { DefinitionRequest, DocumentDiagnosticReportKind, DocumentDiagnosticRequest, DocumentSymbolRequest, ExitNotification, HoverRequest, InitializeRequest, ReferencesRequest, InitializedNotification, MarkupKind, PositionEncodingKind, PublishDiagnosticsNotification, ShutdownRequest, WorkspaceSymbolRequest, } from "vscode-languageserver-protocol";
|
|
6
|
+
import { DefinitionRequest, DiagnosticTag, DocumentDiagnosticReportKind, DocumentDiagnosticRequest, DocumentSymbolRequest, ExitNotification, HoverRequest, InitializeRequest, ReferencesRequest, InitializedNotification, MarkupKind, PositionEncodingKind, PublishDiagnosticsNotification, ShutdownRequest, WorkspaceSymbolRequest, } from "vscode-languageserver-protocol";
|
|
7
7
|
import { LanguageServerConfigurationError, } from "./language-server-config.js";
|
|
8
|
-
import { terminateProcessTree } from "../process-platform.js";
|
|
8
|
+
import { terminateProcessTree } from "../mcp/process/process-platform.js";
|
|
9
9
|
import { CodeIntelligenceError } from "./code-intelligence-error.js";
|
|
10
10
|
import { DEFAULT_CODE_INTELLIGENCE_RESULT_LIMIT } from "./code-intelligence-types.js";
|
|
11
11
|
import { normalizeHoverContents } from "./normalization/hover.js";
|
|
@@ -14,9 +14,11 @@ import { isWithin, locationEntries, normalizeLocations, workspaceDisplayPath, }
|
|
|
14
14
|
import { lspPositionFromUser, rangeFromLsp } from "./position-encoding.js";
|
|
15
15
|
import { DiagnosticSnapshotStore } from "./runtime/diagnostic-snapshots.js";
|
|
16
16
|
import { DocumentSynchronizer } from "./runtime/document-synchronizer.js";
|
|
17
|
+
import { languageServerCommandRequiresShell } from "./runtime/process-launch.js";
|
|
17
18
|
import { SemanticRequestCoordinator } from "./runtime/semantic-requests.js";
|
|
18
19
|
export { CodeIntelligenceError } from "./code-intelligence-error.js";
|
|
19
20
|
const STDERR_TAIL_BYTES = 64 * 1024;
|
|
21
|
+
const PUSH_DIAGNOSTIC_WAIT_MS = 1_000;
|
|
20
22
|
export class LanguageService {
|
|
21
23
|
workspaceRoot;
|
|
22
24
|
project;
|
|
@@ -258,7 +260,10 @@ export class LanguageService {
|
|
|
258
260
|
...this.diagnosticSnapshots.readPull(document, limit),
|
|
259
261
|
};
|
|
260
262
|
}
|
|
261
|
-
const snapshot = this.diagnosticSnapshots.
|
|
263
|
+
const snapshot = await this.diagnosticSnapshots.waitForFreshPush(document, limit, Math.min(PUSH_DIAGNOSTIC_WAIT_MS, this.policy.requestTimeoutMs), signal);
|
|
264
|
+
if (signal?.aborted) {
|
|
265
|
+
throw new CodeIntelligenceError("code.request_cancelled", `Diagnostic request for ${input.path} was cancelled by the Host.`);
|
|
266
|
+
}
|
|
262
267
|
if (snapshot.freshness.state === "missing" && !this.diagnosticSnapshots.hasObservedPushDiagnostics()) {
|
|
263
268
|
throw new CodeIntelligenceError("code.operation_unsupported", `Language server ${this.project.definition.id} does not provide pull diagnostics and has not published push diagnostics.`);
|
|
264
269
|
}
|
|
@@ -336,7 +341,7 @@ export class LanguageService {
|
|
|
336
341
|
stdio: "pipe",
|
|
337
342
|
windowsHide: true,
|
|
338
343
|
detached,
|
|
339
|
-
shell:
|
|
344
|
+
shell: languageServerCommandRequiresShell(definition.command),
|
|
340
345
|
});
|
|
341
346
|
}
|
|
342
347
|
catch (error) {
|
|
@@ -370,6 +375,9 @@ export class LanguageService {
|
|
|
370
375
|
clientInfo: { name: "forgerelay" },
|
|
371
376
|
rootUri,
|
|
372
377
|
workspaceFolders: [{ uri: rootUri, name: basename(this.project.projectRoot) }],
|
|
378
|
+
...(definition.initializationOptions === undefined
|
|
379
|
+
? {}
|
|
380
|
+
: { initializationOptions: definition.initializationOptions }),
|
|
373
381
|
capabilities: {
|
|
374
382
|
general: {
|
|
375
383
|
positionEncodings: [
|
|
@@ -405,6 +413,12 @@ export class LanguageService {
|
|
|
405
413
|
dynamicRegistration: false,
|
|
406
414
|
hierarchicalDocumentSymbolSupport: true,
|
|
407
415
|
},
|
|
416
|
+
publishDiagnostics: {
|
|
417
|
+
relatedInformation: true,
|
|
418
|
+
tagSupport: {
|
|
419
|
+
valueSet: [DiagnosticTag.Unnecessary, DiagnosticTag.Deprecated],
|
|
420
|
+
},
|
|
421
|
+
},
|
|
408
422
|
diagnostic: {
|
|
409
423
|
dynamicRegistration: false,
|
|
410
424
|
relatedDocumentSupport: false,
|
|
@@ -4,6 +4,7 @@ export class DiagnosticSnapshotStore {
|
|
|
4
4
|
maxDiagnosticsPerDocument;
|
|
5
5
|
pushSnapshots = new Map();
|
|
6
6
|
pullSnapshots = new Map();
|
|
7
|
+
pushWaiters = new Map();
|
|
7
8
|
pushObserved = false;
|
|
8
9
|
constructor(maxDocuments, maxDiagnosticsPerDocument) {
|
|
9
10
|
this.maxDocuments = maxDocuments;
|
|
@@ -11,10 +12,13 @@ export class DiagnosticSnapshotStore {
|
|
|
11
12
|
}
|
|
12
13
|
capturePush(params, document, encoding) {
|
|
13
14
|
this.pushObserved = true;
|
|
14
|
-
if (!document)
|
|
15
|
+
if (!document) {
|
|
16
|
+
this.notifyPushWaiters(params.uri, true);
|
|
15
17
|
return;
|
|
18
|
+
}
|
|
16
19
|
const snapshot = this.normalizeSnapshot(params.diagnostics, document, encoding, params.version === undefined ? {} : { publishedVersion: params.version });
|
|
17
20
|
this.setBounded(this.pushSnapshots, params.uri, snapshot);
|
|
21
|
+
this.notifyPushWaiters(params.uri, true);
|
|
18
22
|
}
|
|
19
23
|
capturePull(diagnostics, document, encoding, resultId) {
|
|
20
24
|
const snapshot = this.normalizeSnapshot(diagnostics, document, encoding, resultId === undefined ? {} : { resultId });
|
|
@@ -41,12 +45,52 @@ export class DiagnosticSnapshotStore {
|
|
|
41
45
|
readPull(document, limit) {
|
|
42
46
|
return this.read(this.pullSnapshots, document, limit, false);
|
|
43
47
|
}
|
|
48
|
+
async waitForFreshPush(document, limit, timeoutMs, signal) {
|
|
49
|
+
let snapshot = this.readPush(document, limit);
|
|
50
|
+
const deadline = Date.now() + timeoutMs;
|
|
51
|
+
while (snapshot.freshness.state === "missing" || snapshot.freshness.state === "stale") {
|
|
52
|
+
if (signal?.aborted)
|
|
53
|
+
break;
|
|
54
|
+
const remaining = deadline - Date.now();
|
|
55
|
+
if (remaining <= 0 || !await this.waitForPush(document.uri, remaining, signal))
|
|
56
|
+
break;
|
|
57
|
+
snapshot = this.readPush(document, limit);
|
|
58
|
+
}
|
|
59
|
+
return snapshot;
|
|
60
|
+
}
|
|
44
61
|
hasObservedPushDiagnostics() {
|
|
45
62
|
return this.pushObserved;
|
|
46
63
|
}
|
|
64
|
+
waitForPush(uri, timeoutMs, signal) {
|
|
65
|
+
if (signal?.aborted)
|
|
66
|
+
return Promise.resolve(false);
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
let settled = false;
|
|
69
|
+
const waiters = this.pushWaiters.get(uri) ?? new Set();
|
|
70
|
+
const finish = (observed) => {
|
|
71
|
+
if (settled)
|
|
72
|
+
return;
|
|
73
|
+
settled = true;
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
signal?.removeEventListener("abort", onAbort);
|
|
76
|
+
waiters.delete(finish);
|
|
77
|
+
if (waiters.size === 0)
|
|
78
|
+
this.pushWaiters.delete(uri);
|
|
79
|
+
resolve(observed);
|
|
80
|
+
};
|
|
81
|
+
const onAbort = () => finish(false);
|
|
82
|
+
const timer = setTimeout(() => finish(false), timeoutMs);
|
|
83
|
+
timer.unref();
|
|
84
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
85
|
+
waiters.add(finish);
|
|
86
|
+
this.pushWaiters.set(uri, waiters);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
47
89
|
clear() {
|
|
48
90
|
this.pushSnapshots.clear();
|
|
49
91
|
this.pullSnapshots.clear();
|
|
92
|
+
for (const uri of [...this.pushWaiters.keys()])
|
|
93
|
+
this.notifyPushWaiters(uri, false);
|
|
50
94
|
this.pushObserved = false;
|
|
51
95
|
}
|
|
52
96
|
get size() {
|
|
@@ -70,6 +114,13 @@ export class DiagnosticSnapshotStore {
|
|
|
70
114
|
...metadata,
|
|
71
115
|
};
|
|
72
116
|
}
|
|
117
|
+
notifyPushWaiters(uri, observed) {
|
|
118
|
+
const waiters = this.pushWaiters.get(uri);
|
|
119
|
+
if (!waiters)
|
|
120
|
+
return;
|
|
121
|
+
for (const waiter of [...waiters])
|
|
122
|
+
waiter(observed);
|
|
123
|
+
}
|
|
73
124
|
setBounded(snapshots, uri, snapshot) {
|
|
74
125
|
snapshots.delete(uri);
|
|
75
126
|
snapshots.set(uri, snapshot);
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { delimiter, join, resolve } from "node:path";
|
|
4
|
+
const MANAGED_LANGUAGE_SERVERS = {
|
|
5
|
+
typescript: {
|
|
6
|
+
id: "typescript",
|
|
7
|
+
label: "TypeScript / JavaScript",
|
|
8
|
+
executable: "typescript-language-server",
|
|
9
|
+
packages: ["typescript-language-server@6", "typescript@6"],
|
|
10
|
+
},
|
|
11
|
+
pyright: {
|
|
12
|
+
id: "pyright",
|
|
13
|
+
label: "Python (Pyright)",
|
|
14
|
+
executable: "pyright-langserver",
|
|
15
|
+
packages: ["pyright@1"],
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
export function managedLanguageServerRoot(configDir) {
|
|
19
|
+
return join(configDir, "language-servers");
|
|
20
|
+
}
|
|
21
|
+
export function managedLanguageServerBinDir(configDir) {
|
|
22
|
+
return join(managedLanguageServerRoot(configDir), "node_modules", ".bin");
|
|
23
|
+
}
|
|
24
|
+
export function managedLanguageServerExecutablePath(configDir, id, platform = process.platform) {
|
|
25
|
+
const bin = managedLanguageServerBinDir(configDir);
|
|
26
|
+
const executable = MANAGED_LANGUAGE_SERVERS[id].executable;
|
|
27
|
+
const extensions = platform === "win32" ? [".cmd", ".exe", ".bat", ""] : [""];
|
|
28
|
+
return extensions
|
|
29
|
+
.map((extension) => join(bin, `${executable}${extension}`))
|
|
30
|
+
.find((path) => existsSync(path));
|
|
31
|
+
}
|
|
32
|
+
export function managedLanguageServerIdForCommand(configDir, command, platform = process.platform) {
|
|
33
|
+
return supportedManagedLanguageServers().find((id) => {
|
|
34
|
+
const executable = managedLanguageServerExecutablePath(configDir, id, platform);
|
|
35
|
+
if (executable === undefined)
|
|
36
|
+
return false;
|
|
37
|
+
const executablePath = resolve(executable);
|
|
38
|
+
const commandPath = resolve(command);
|
|
39
|
+
return platform === "win32"
|
|
40
|
+
? executablePath.toLowerCase() === commandPath.toLowerCase()
|
|
41
|
+
: executablePath === commandPath;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export function managedTypeScriptTsserverPath(configDir) {
|
|
45
|
+
const path = join(managedLanguageServerRoot(configDir), "node_modules", "typescript", "lib", "tsserver.js");
|
|
46
|
+
return existsSync(path) ? path : undefined;
|
|
47
|
+
}
|
|
48
|
+
export function managedLanguageServerRuntimeIdentity(configDir, id) {
|
|
49
|
+
const packageNames = id === "typescript"
|
|
50
|
+
? ["typescript-language-server", "typescript"]
|
|
51
|
+
: ["pyright"];
|
|
52
|
+
try {
|
|
53
|
+
const versions = packageNames.map((name) => {
|
|
54
|
+
const packageJson = JSON.parse(readFileSync(join(managedLanguageServerRoot(configDir), "node_modules", name, "package.json"), "utf8"));
|
|
55
|
+
if (!packageJson.version)
|
|
56
|
+
throw new Error(`Missing version for ${name}`);
|
|
57
|
+
return [name, packageJson.version];
|
|
58
|
+
});
|
|
59
|
+
return JSON.stringify(versions);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function withManagedLanguageServerPath(env, configDir) {
|
|
66
|
+
if (!configDir)
|
|
67
|
+
return { ...env };
|
|
68
|
+
const bin = managedLanguageServerBinDir(configDir);
|
|
69
|
+
return {
|
|
70
|
+
...env,
|
|
71
|
+
PATH: [bin, env.PATH].filter((value) => Boolean(value)).join(delimiter),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function managedLanguageServerOptions() {
|
|
75
|
+
return [
|
|
76
|
+
{
|
|
77
|
+
value: "typescript",
|
|
78
|
+
label: MANAGED_LANGUAGE_SERVERS.typescript.label,
|
|
79
|
+
hint: "Installs typescript-language-server and TypeScript into ForgeRelay's private config directory.",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
value: "pyright",
|
|
83
|
+
label: MANAGED_LANGUAGE_SERVERS.pyright.label,
|
|
84
|
+
hint: "Installs Pyright into ForgeRelay's private config directory.",
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
export function supportedManagedLanguageServers() {
|
|
89
|
+
return Object.keys(MANAGED_LANGUAGE_SERVERS);
|
|
90
|
+
}
|
|
91
|
+
export function installedManagedLanguageServers(configDir) {
|
|
92
|
+
return supportedManagedLanguageServers()
|
|
93
|
+
.filter((id) => managedExecutableExists(configDir, MANAGED_LANGUAGE_SERVERS[id].executable));
|
|
94
|
+
}
|
|
95
|
+
export function managedLanguageServerPackages(ids) {
|
|
96
|
+
const packages = new Set();
|
|
97
|
+
for (const id of ids) {
|
|
98
|
+
for (const packageSpec of MANAGED_LANGUAGE_SERVERS[id].packages)
|
|
99
|
+
packages.add(packageSpec);
|
|
100
|
+
}
|
|
101
|
+
return [...packages];
|
|
102
|
+
}
|
|
103
|
+
export async function installManagedLanguageServers(ids, configDir, runNpm = defaultNpmRunner) {
|
|
104
|
+
const selected = Array.from(new Set(ids));
|
|
105
|
+
const root = managedLanguageServerRoot(configDir);
|
|
106
|
+
const packages = managedLanguageServerPackages(selected);
|
|
107
|
+
if (packages.length === 0)
|
|
108
|
+
return { installed: [], packages: [], root };
|
|
109
|
+
mkdirSync(root, { recursive: true });
|
|
110
|
+
const packageJsonPath = join(root, "package.json");
|
|
111
|
+
if (!existsSync(packageJsonPath)) {
|
|
112
|
+
writeFileSync(packageJsonPath, JSON.stringify({
|
|
113
|
+
name: "forgerelay-managed-language-servers",
|
|
114
|
+
private: true,
|
|
115
|
+
description: "ForgeRelay-managed optional Language Servers. Do not publish.",
|
|
116
|
+
}, null, 2) + "\n");
|
|
117
|
+
}
|
|
118
|
+
await runNpm([
|
|
119
|
+
"install",
|
|
120
|
+
"--prefix",
|
|
121
|
+
root,
|
|
122
|
+
"--save-exact",
|
|
123
|
+
"--no-audit",
|
|
124
|
+
"--no-fund",
|
|
125
|
+
...packages,
|
|
126
|
+
]);
|
|
127
|
+
for (const id of selected)
|
|
128
|
+
assertManagedLanguageServerInstall(configDir, id);
|
|
129
|
+
return { installed: selected, packages, root };
|
|
130
|
+
}
|
|
131
|
+
function assertManagedLanguageServerInstall(configDir, id) {
|
|
132
|
+
if (!managedLanguageServerExecutablePath(configDir, id)) {
|
|
133
|
+
throw new Error(`Managed Language Server ${id} installed without its expected executable.`);
|
|
134
|
+
}
|
|
135
|
+
if (id === "typescript" && !managedTypeScriptTsserverPath(configDir)) {
|
|
136
|
+
throw new Error("Managed TypeScript Language Server installed without a compatible TypeScript tsserver.js. " +
|
|
137
|
+
"ForgeRelay requires a tsserver-based TypeScript package for typescript-language-server.");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function managedExecutableExists(configDir, executable) {
|
|
141
|
+
const bin = managedLanguageServerBinDir(configDir);
|
|
142
|
+
if (process.platform === "win32") {
|
|
143
|
+
return [".cmd", ".exe", ".bat", ""].some((extension) => existsSync(join(bin, `${executable}${extension}`)));
|
|
144
|
+
}
|
|
145
|
+
return existsSync(join(bin, executable));
|
|
146
|
+
}
|
|
147
|
+
function defaultNpmRunner(args) {
|
|
148
|
+
const command = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
149
|
+
return new Promise((resolve, reject) => {
|
|
150
|
+
const child = spawn(command, args, {
|
|
151
|
+
env: process.env,
|
|
152
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
153
|
+
windowsHide: true,
|
|
154
|
+
shell: process.platform === "win32",
|
|
155
|
+
});
|
|
156
|
+
let stdout = "";
|
|
157
|
+
let stderr = "";
|
|
158
|
+
child.stdout?.setEncoding("utf8");
|
|
159
|
+
child.stderr?.setEncoding("utf8");
|
|
160
|
+
child.stdout?.on("data", (chunk) => { stdout += chunk; });
|
|
161
|
+
child.stderr?.on("data", (chunk) => { stderr += chunk; });
|
|
162
|
+
child.once("error", reject);
|
|
163
|
+
child.once("close", (code) => {
|
|
164
|
+
if (code === 0) {
|
|
165
|
+
resolve();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const detail = (stderr || stdout).trim();
|
|
169
|
+
reject(new Error(`npm install for managed Language Servers failed with exit ${code ?? "unknown"}${detail ? `: ${detail}` : ""}`));
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
}
|