@akira-tl/forgerelay 0.3.6 → 0.4.0

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 CHANGED
@@ -4,6 +4,42 @@ All notable ForgeRelay changes are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.4.0] - 2026-08-11
8
+
9
+ ### Added
10
+
11
+ - Added `code.intelligence` as a Capability Gateway-only LSP code-intelligence surface. ForgeRelay 0.4.0 ships the first complete `definition` tracer bullet without changing the canonical nine Core MCP tools.
12
+ - Added Language-server definitions with project (`.forgerelay/language-servers.json`), global (`~/.forgerelay/config.json`), and built-in discovery precedence. Common built-ins cover TypeScript/JavaScript, Pyright, rust-analyzer, gopls, and clangd when those executables are already installed.
13
+ - Added a deterministic child-process fake LSP server and MCP-level regression seam covering initialize/shutdown, document synchronization, definition normalization, shared Language-service identity, capacity limits, and server-initiated edit rejection.
14
+
15
+ ### Changed
16
+
17
+ - Code-intelligence positions use ForgeRelay's 1-based line and Unicode code-point column contract and are converted internally to the position encoding negotiated with the Language Server.
18
+ - Language services are shared by canonical Language project root plus effective server-definition fingerprint rather than logical workspace ID, remain capacity/idle bounded, and use structured no-shell process launch over Microsoft's `vscode-jsonrpc` / `vscode-languageserver-protocol` substrate.
19
+ - Language-server configuration can explicitly disable built-in discovery; nested Language projects resolve by walking ancestors of the requested source path instead of recursively scanning the Workspace.
20
+
21
+ ### Fixed
22
+
23
+ - External LSP definition targets are marked as informational external locations without expanding ForgeRelay file-read authority, including symlink-escape protection and canonical Workspace-root handling.
24
+ - Language-server startup failure/timeout, unsupported operations, invalid positions, configuration ambiguity, capacity exhaustion, and other policy failures now use stable ForgeRelay `code.*` errors instead of leaking raw JSON-RPC failures.
25
+
26
+ ## [0.3.7] - 2026-08-10
27
+
28
+ ### Added
29
+
30
+ - Added debug-only runtime resource telemetry for RSS/V8 heap, MCP transport count, running/completed process counts, cached workspaces, and review checkpoint state so long-running deployments can identify which resource class is growing.
31
+
32
+ ### Changed
33
+
34
+ - Background `bash` completion state is now retained for at most five minutes, completed process handles are released immediately, completed notices are globally bounded, active processes have a global concurrency budget, and per-process retained output is smaller. High-output head/tail truncation no longer materializes whole strings as Unicode code-point arrays, sharply reducing transient heap growth and GC pressure.
35
+ - Abandoned MCP transport sessions and in-memory review checkpoint states now have hard capacity limits. Review state is also released when its logical workspace closes, while persisted Git checkpoint refs remain available for reconstruction.
36
+ - Workspace instruction discovery is now bounded and demand-driven: `open_workspace` scans only the workspace root and direct child directories, while deeper `AGENTS.md` / `CLAUDE.md` files are discovered along paths as the Agent first accesses them. Reads surface newly discovered instructions inline; mutation and shell calls stop before side effects and require a retry after newly discovered local instructions are applied.
37
+ - Workspace/session activity timestamps now use a small in-process write-behind cache. Hot `lastUsedAt` touches are coalesced and flushed to SQLite in one transaction at most every five minutes, with an explicit final flush during normal shutdown; semantic create/close/status writes remain immediate.
38
+
39
+ ### Fixed
40
+
41
+ - Expired, never-redeemed OAuth authorization codes are opportunistically evicted and cleared on provider shutdown instead of remaining in memory for the lifetime of the server.
42
+
7
43
  ## [0.3.6] - 2026-08-10
8
44
 
9
45
  ### Added
@@ -0,0 +1,11 @@
1
+ # Code Intelligence
2
+
3
+ Use the `code.intelligence` Capability for read-only semantic code navigation backed by Language servers that are already installed on the user's machine or explicitly configured for the project.
4
+
5
+ ForgeRelay does not install Language servers. It discovers supported executables when available and accepts explicit definitions from the global ForgeRelay config or `<workspace>/.forgerelay/language-servers.json`. Project definitions override global definitions, and global definitions override built-in discovery. An explicit definition may disable discovery with `enabled: false`.
6
+
7
+ For 0.4.0, the supported operation is `definition`. Pass a workspace-relative source `path` plus 1-based `line` and `column` values. Columns are Unicode code-point positions; ForgeRelay converts them to the position encoding negotiated with the Language server.
8
+
9
+ Code-intelligence results use ForgeRelay-normalized locations rather than raw LSP wire types. A result may identify an External code location outside the Workspace, but that does not expand ForgeRelay's allowed roots or grant the file tools permission to read that path.
10
+
11
+ Language-server definitions use structured process configuration rather than shell command strings. A project configuration entry may contain `command`, `args`, `env`, `languages`, `extensions`, `languageIdByExtension`, `projectMarkers`, and `enabled` fields. Use `languageIdByExtension` when one server definition covers multiple language IDs whose extensions do not map one-to-one by array position. The server command is launched directly without a shell.
@@ -28,9 +28,9 @@ processId: <number>
28
28
 
29
29
  `action="run"` 与 `action="process"` 的参数不要混用。Process ownership 始终绑定原 `workspaceId`;未知或跨 workspace 的 `processId` 会被拒绝。
30
30
 
31
- 等待超时不会隐式 kill process。若没有必要立即等待,可以继续其他工作;进程完成后,ForgeRelay 会把 completion notice 一次性附加到同一 logical workspace 的后续 tool result
31
+ 等待超时不会隐式 kill process。若没有必要立即等待,可以继续其他工作;进程完成后,ForgeRelay 会把 completion notice 一次性附加到同一 logical workspace 的后续 tool result。未消费的 completed process notice 最多保留 5 分钟;进程退出时底层 ChildProcess/PTY handle 会立即释放。ForgeRelay 同时对 active process 与 completed notice 数量设置全局资源预算,达到 active process 上限时会拒绝启动新的命令,而不会擅自终止已有长任务。
32
32
 
33
- 不要因为暂时没有输出就重复启动相同长进程;先用返回的 `processId` poll
33
+ 不要因为暂时没有输出就重复启动相同长进程;先用返回的 `processId` poll。高输出命令会使用有界 head/tail buffer,不能把 ForgeRelay 当作无限历史日志存储。
34
34
 
35
35
  ## PTY / interactive commands
36
36
 
@@ -34,6 +34,11 @@ const CAPABILITY_GUIDE_DEFINITIONS = [
34
34
  description: "Long-running bash processes, processId interaction, PTY, and platform edges.",
35
35
  whenToRead: "Read for running or interactive command issues.",
36
36
  },
37
+ {
38
+ name: "code-intelligence",
39
+ description: "Read-only semantic code navigation backed by external Language servers.",
40
+ whenToRead: "Read before using code.intelligence or configuring Language servers.",
41
+ },
37
42
  ];
38
43
  function capabilityGuidesDir() {
39
44
  return fileURLToPath(new URL("../capabilities", import.meta.url));
@@ -75,6 +80,7 @@ export function buildCapabilityFingerprint(config, version, context = {}) {
75
80
  "process.lifecycle",
76
81
  "hooks.lifecycle",
77
82
  "capability-guides.read",
83
+ "code.intelligence",
78
84
  ];
79
85
  if (config.subagents) {
80
86
  capabilities.push("subagent.profiles");
@@ -115,6 +115,12 @@ export class CapabilityRegistry {
115
115
  }
116
116
  export function createCapabilityRegistry(dependencies) {
117
117
  const hooksCheckInput = z.object({}).strict();
118
+ const codeIntelligenceInput = z.object({
119
+ operation: z.literal("definition"),
120
+ path: z.string().min(1),
121
+ line: z.number().int(),
122
+ column: z.number().int(),
123
+ }).strict();
118
124
  return new CapabilityRegistry([
119
125
  {
120
126
  name: "hooks.check",
@@ -144,6 +150,20 @@ export function createCapabilityRegistry(dependencies) {
144
150
  run: async (_input, context) => dependencies.reviewChanges.run(context),
145
151
  }]
146
152
  : []),
153
+ ...(dependencies.codeIntelligence
154
+ ? [{
155
+ name: "code.intelligence",
156
+ description: "Read semantic code information through an available Language server without changing the Workspace.",
157
+ guideName: "code-intelligence",
158
+ readGuideBeforeFirstUse: true,
159
+ inputSchema: codeIntelligenceInput,
160
+ availability: () => ({
161
+ available: dependencies.codeIntelligence?.available ?? false,
162
+ reason: dependencies.codeIntelligence?.unavailableReason,
163
+ }),
164
+ run: async (input, context) => dependencies.codeIntelligence.run(input, context),
165
+ }]
166
+ : []),
147
167
  ...(dependencies.downloadArtifact
148
168
  ? [{
149
169
  name: "artifact.download",
package/dist/config.js CHANGED
@@ -223,6 +223,7 @@ export function loadConfig(env = process.env) {
223
223
  subagents: productEnv(env, "SUBAGENTS") === undefined
224
224
  ? files.config.subagents === true
225
225
  : parseBoolean(productEnv(env, "SUBAGENTS")),
226
+ languageServers: files.config.languageServers ?? {},
226
227
  agentDir: resolve(expandHomePath(productEnv(env, "AGENT_DIR") ?? files.config.agentDir ?? defaultAgentDir())),
227
228
  systemInstructionsPath: parseSystemInstructionsPath(productEnv(env, "SYSTEM_INSTRUCTIONS_PATH") ?? files.config.systemInstructionsPath),
228
229
  hooks: mergeHookConfigs(parseHookConfig(files.config.hooks), parseHookConfig(files.hooks), files.hookFiles),
package/dist/logger.js CHANGED
@@ -113,6 +113,8 @@ function formatPrettyMessage(entry, options) {
113
113
  case "mcp_transport_session_close_failed":
114
114
  case "mcp_session_close_failed":
115
115
  return `transport session ${transportSessionPrefix(entry) ?? "unknown"} close -> ${style("red", "error", options)}`;
116
+ case "runtime_resources":
117
+ return formatRuntimeResources(entry);
116
118
  case "auth_denied":
117
119
  return `auth denied${entry.reason ? `: ${String(entry.reason)}` : ""}`;
118
120
  case "mcp_request_error":
@@ -212,6 +214,20 @@ function stableColorIndex(value) {
212
214
  }
213
215
  return (hash >>> 0) % WORKSPACE_PROJECT_COLORS.length;
214
216
  }
217
+ function formatRuntimeResources(entry) {
218
+ const rssMb = bytesToMegabytes(numberField(entry.rssBytes));
219
+ const heapUsedMb = bytesToMegabytes(numberField(entry.heapUsedBytes));
220
+ const heapTotalMb = bytesToMegabytes(numberField(entry.heapTotalBytes));
221
+ const transports = numberField(entry.mcpTransports) ?? 0;
222
+ const running = numberField(entry.processesRunning) ?? 0;
223
+ const completed = numberField(entry.processesCompleted) ?? 0;
224
+ const workspaces = numberField(entry.cachedWorkspaces) ?? 0;
225
+ const reviewStates = numberField(entry.reviewStates) ?? 0;
226
+ return `runtime rss=${rssMb}MB heap=${heapUsedMb}/${heapTotalMb}MB transports=${transports} processes=${running} running/${completed} completed workspaces=${workspaces} review=${reviewStates}`;
227
+ }
228
+ function bytesToMegabytes(value) {
229
+ return value === undefined ? 0 : Math.round(value / (1024 * 1024));
230
+ }
215
231
  function formatGenericMessage(entry) {
216
232
  const event = String(entry.event ?? "log");
217
233
  const detail = [entry.reason, entry.error]
@@ -0,0 +1,8 @@
1
+ export class CodeIntelligenceError extends Error {
2
+ code;
3
+ constructor(code, message) {
4
+ super(message);
5
+ this.code = code;
6
+ this.name = "CodeIntelligenceError";
7
+ }
8
+ }