@akira-tl/forgerelay 0.2.5 → 0.3.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 +26 -0
- package/README.md +15 -0
- package/capabilities/artifacts-review/GUIDE.md +42 -0
- package/capabilities/host-integration/GUIDE.md +68 -0
- package/capabilities/lifecycle-hooks/GUIDE.md +39 -0
- package/capabilities/managed-worktrees/GUIDE.md +43 -0
- package/capabilities/shell-processes/GUIDE.md +51 -0
- package/capabilities/subagents/GUIDE.md +69 -0
- package/dist/advertised-files.js +23 -0
- package/dist/artifact-tools.js +2 -3
- package/dist/capabilities.js +99 -0
- package/dist/cli.js +1 -3
- package/dist/config.js +11 -3
- package/dist/logger.js +25 -23
- package/dist/mcp/server-instructions.js +16 -31
- package/dist/mcp-sessions.js +24 -22
- package/dist/process-sessions.js +135 -108
- package/dist/server.js +108 -70
- package/dist/skills.js +11 -30
- package/dist/workspaces.js +16 -0
- package/docs/chatgpt-coding-workflow.md +58 -15
- package/docs/configuration.md +53 -8
- package/docs/debugging.md +3 -3
- package/docs/roadmap.md +30 -4
- package/docs/security.md +27 -9
- package/package.json +4 -3
- package/scripts/debug/accept.mjs +47 -4
- package/scripts/ensure-cli-executable.mjs +12 -0
|
@@ -9,14 +9,14 @@ checkout.
|
|
|
9
9
|
`open_workspace` returns a `workspaceId`. Continue using that ID for later tools
|
|
10
10
|
in the same directory.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
`workspaceId` is a logical conversation handle, not the physical-directory
|
|
13
|
+
identity. Reopening the same checkout in the same conversation keeps that
|
|
14
|
+
logical ID stable. A different conversation normally receives a different
|
|
15
|
+
`workspaceId` even when it points at the same checkout or worktree; pass an
|
|
16
|
+
existing ID explicitly when the user wants to resume that logical workspace.
|
|
17
17
|
|
|
18
|
-
A Git worktree directory is a separate workspace
|
|
19
|
-
checkout.
|
|
18
|
+
A Git worktree directory is a separate physical workspace target from its source
|
|
19
|
+
checkout, and each conversation can still have its own logical handle for it.
|
|
20
20
|
|
|
21
21
|
## Checkout-first behavior
|
|
22
22
|
|
|
@@ -108,6 +108,37 @@ being injected eagerly. Read the relevant nested file before working under that
|
|
|
108
108
|
`FORGERELAY_AGENT_DIR` is not an instruction source; it remains only a compatibility
|
|
109
109
|
skill-discovery path.
|
|
110
110
|
|
|
111
|
+
## MCP capability loading
|
|
112
|
+
|
|
113
|
+
ForgeRelay keeps callable MCP tools and explanatory capability documentation
|
|
114
|
+
separate. `tools/list` remains the source of truth for what the current server
|
|
115
|
+
actually exposes; 0.3 does not hide callable tools behind documentation.
|
|
116
|
+
|
|
117
|
+
`open_workspace` adds two lightweight discovery surfaces:
|
|
118
|
+
|
|
119
|
+
- `capabilityFingerprint` is returned on every open/resume and includes the
|
|
120
|
+
ForgeRelay version, active tool mode, and stable semantic capability names;
|
|
121
|
+
- `capabilityGuides` is returned with bootstrap context and contains compact
|
|
122
|
+
descriptors for ForgeRelay-owned, versioned guides that can be loaded with
|
|
123
|
+
the normal `read` tool.
|
|
124
|
+
|
|
125
|
+
Do not preload every capability guide. Read a guide only when the current task
|
|
126
|
+
needs that domain. Built-in guides cover lifecycle Hooks, advanced managed
|
|
127
|
+
worktrees, subagents, artifact/change-review workflows, Host/OAuth/MCP App
|
|
128
|
+
integration, and long-running shell/PTY/process behavior. Optional guides are
|
|
129
|
+
advertised only when their feature is enabled; for example, disabled subagents
|
|
130
|
+
and artifact/change-review features do not add those descriptors to bootstrap
|
|
131
|
+
context. Reopening a workspace in the same Host context does not repeat the
|
|
132
|
+
descriptors, but the previously advertised guides remain valid.
|
|
133
|
+
|
|
134
|
+
The fingerprint is also a stale-Host-schema diagnostic. If `open_workspace`
|
|
135
|
+
reports a capability such as `filesystem.rename-move` but the Host's current
|
|
136
|
+
MCP tool snapshot does not expose `rename`, the server and Host metadata are out
|
|
137
|
+
of sync. Refresh/reconnect the MCP integration or start a Host context that
|
|
138
|
+
reloads `tools/list`; do not conclude that the running ForgeRelay server lacks
|
|
139
|
+
that capability. ForgeRelay can report its own version/capabilities but cannot
|
|
140
|
+
force the Host to discard a cached tool schema.
|
|
141
|
+
|
|
111
142
|
## Agent Skills
|
|
112
143
|
|
|
113
144
|
ForgeRelay discovers standard Agent Skills from:
|
|
@@ -134,7 +165,11 @@ config directory plus:
|
|
|
134
165
|
```
|
|
135
166
|
|
|
136
167
|
The workspace result exposes only compact profile metadata so the host can
|
|
137
|
-
choose a provider/profile without loading full provider launch details.
|
|
168
|
+
choose a provider/profile without loading full provider launch details. Read the
|
|
169
|
+
ForgeRelay-owned `subagents` capability guide when delegation is actually needed;
|
|
170
|
+
0.3 no longer auto-loads the historical bundled `subagent-delegation` Skill for
|
|
171
|
+
new setups. Existing user-authored or previously seeded Skills remain normal
|
|
172
|
+
user configuration and are not deleted.
|
|
138
173
|
|
|
139
174
|
The current model-facing delegation workflow is:
|
|
140
175
|
|
|
@@ -168,16 +203,18 @@ The exact lifecycle tools available depend on the active server configuration.
|
|
|
168
203
|
In minimal mode, normal shell inspection commands such as `rg`, `find`, and `ls`
|
|
169
204
|
can be used rather than dedicated MCP search tools. `bash` waits in the foreground
|
|
170
205
|
for at most 300 seconds. If the command is still running, ForgeRelay returns a
|
|
171
|
-
|
|
206
|
+
canonical `processId` without killing it. The Agent can use `write_stdin` to poll,
|
|
172
207
|
wait again, interact, or explicitly send Ctrl-C, or continue other work; once the
|
|
173
208
|
command finishes, its completion is attached to a later tool result using the
|
|
174
|
-
same workspace ID.
|
|
209
|
+
same workspace ID. The former process `sessionId` remains a deprecated alias in
|
|
210
|
+
0.2.x for compatibility with existing clients.
|
|
175
211
|
|
|
176
212
|
`FORGERELAY_TOOL_MODE=full` adds dedicated search/directory tools.
|
|
177
213
|
|
|
178
214
|
Experimental `FORGERELAY_TOOL_MODE=codex` provides a smaller Codex-shaped
|
|
179
215
|
surface including direct `rename`/`delete` path mutations alongside `apply_patch`,
|
|
180
|
-
`exec_command`, and `write_stdin`.
|
|
216
|
+
`exec_command`, and `write_stdin`. `rename` is the unified move/rename primitive
|
|
217
|
+
for both files and directories; ForgeRelay does not expose a separate `move` tool.
|
|
181
218
|
|
|
182
219
|
Workspace IDs are logical conversation handles rather than physical-directory
|
|
183
220
|
identities. The same conversation keeps a stable ID for a project, while another
|
|
@@ -192,10 +229,16 @@ be released that way and must be finalized with `close_worktree`.
|
|
|
192
229
|
Shell commands are allowed to modify ordinary project files when that is a
|
|
193
230
|
natural part of the user's requested development task; ForgeRelay does not apply
|
|
194
231
|
a blanket ban to package managers, generators, formatters, or similar commands
|
|
195
|
-
that write files.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
232
|
+
that write files. They may also perform external device or hardware mutations
|
|
233
|
+
when the user's current request explicitly asks for the actual device-changing
|
|
234
|
+
operation. A check, audit, probe, backup, verification, dry-run, or build-only
|
|
235
|
+
request does not implicitly authorize a later persistent device write, and
|
|
236
|
+
ForgeRelay does not assume a particular flashing protocol or transport.
|
|
237
|
+
|
|
238
|
+
The Agent contract still prohibits shell mutation of security- or
|
|
239
|
+
privilege-sensitive operating-system files and credential material, and requires
|
|
240
|
+
an explicit user request before changing configuration files through `bash` or
|
|
241
|
+
`exec_command`.
|
|
199
242
|
|
|
200
243
|
## Change review UI
|
|
201
244
|
|
package/docs/configuration.md
CHANGED
|
@@ -123,6 +123,35 @@ MCP clients discover metadata from:
|
|
|
123
123
|
explicit tool mode is unset. The corresponding legacy `DEVSPACE_*` names are
|
|
124
124
|
also accepted.
|
|
125
125
|
|
|
126
|
+
The selected mode controls the real `tools/list` surface. ForgeRelay does not
|
|
127
|
+
hide callable tools behind capability documentation. In every mode,
|
|
128
|
+
`open_workspace` returns a `capabilityFingerprint` containing the package
|
|
129
|
+
version, tool mode, and stable semantic capability names. The fingerprint also
|
|
130
|
+
reports enabled optional domains such as subagent profile discovery, native
|
|
131
|
+
artifact download, MCP App UI, or aggregate `show_changes` review when those
|
|
132
|
+
features are actually available; it remains a semantic summary rather than a
|
|
133
|
+
copy of `tools/list`.
|
|
134
|
+
|
|
135
|
+
Bootstrap responses also return `capabilityGuides`, which are compact descriptors
|
|
136
|
+
for built-in ForgeRelay documentation that the Agent can explicitly load with
|
|
137
|
+
`read` when a task needs that domain. Current built-in domains cover lifecycle
|
|
138
|
+
Hooks, managed worktrees, subagents, artifact/change-review workflows, Host/OAuth/
|
|
139
|
+
MCP App integration, and long-running shell/PTY/process behavior. Optional-domain
|
|
140
|
+
descriptors such as `subagents` and `artifacts-review` are advertised only when
|
|
141
|
+
the corresponding feature is enabled, so disabled features do not add bootstrap
|
|
142
|
+
context.
|
|
143
|
+
|
|
144
|
+
There is no separate progressive-disclosure configuration switch. Capability
|
|
145
|
+
Guide discovery is built in, while actual tool exposure continues to be
|
|
146
|
+
controlled by `FORGERELAY_TOOL_MODE` and feature-specific settings. If the
|
|
147
|
+
fingerprint reports a capability that is missing from the Host's current tool
|
|
148
|
+
snapshot, treat that as stale Host MCP metadata: reconnect/refresh the integration
|
|
149
|
+
or use a Host context that reloads `tools/list`. The ForgeRelay process cannot
|
|
150
|
+
force a Host to invalidate its cached schema.
|
|
151
|
+
|
|
152
|
+
`rename` is the canonical move/rename primitive for files and directories; there
|
|
153
|
+
is no separate `move` MCP tool.
|
|
154
|
+
|
|
126
155
|
Codex-mode commands run without a PTY by default. `tty: true` enables interactive
|
|
127
156
|
programs when the optional `node-pty` dependency is available.
|
|
128
157
|
|
|
@@ -132,16 +161,17 @@ receives a different ID for the same physical checkout/worktree. Pass
|
|
|
132
161
|
`workspaceId` to `open_workspace` to explicitly resume an existing handle in the
|
|
133
162
|
current conversation. `newWorkspace: true` allocates a new logical handle without
|
|
134
163
|
creating another checkout or Git worktree and should be used only on explicit user
|
|
135
|
-
request.
|
|
164
|
+
request. Logical workspaces idle for more than two days are returned in `staleWorkspaces` so
|
|
136
165
|
the user can choose whether to resume or release them. `close_workspace` removes a
|
|
137
166
|
logical handle without deleting checkout files; it refuses to remove the last
|
|
138
167
|
handle anchoring a physical worktree.
|
|
139
168
|
|
|
140
169
|
`bash` has no execution-timeout input. It waits in the foreground for at most 300
|
|
141
170
|
seconds; if the process is still alive, the result contains `running: true` and a
|
|
142
|
-
`
|
|
143
|
-
300 seconds per call.
|
|
144
|
-
|
|
171
|
+
canonical `processId`. `write_stdin` can poll or interact with that process for up
|
|
172
|
+
to another 300 seconds per call. The former `sessionId` field remains a deprecated
|
|
173
|
+
alias during the 0.2.x compatibility window. ForgeRelay does not kill a process
|
|
174
|
+
merely because a wait window expires. Completed background processes are delivered once with a later
|
|
145
175
|
tool result for the same logical workspace ID.
|
|
146
176
|
|
|
147
177
|
## Widgets
|
|
@@ -315,7 +345,8 @@ When subagents are enabled, profiles are discovered from:
|
|
|
315
345
|
- active legacy config directory `~/.devspace/agents/*.md` when reused;
|
|
316
346
|
- project `.devspace/agents/*.md` for migration compatibility.
|
|
317
347
|
|
|
318
|
-
The
|
|
348
|
+
The ForgeRelay-owned `subagents` capability guide teaches the current CLI
|
|
349
|
+
workflow on demand:
|
|
319
350
|
|
|
320
351
|
```bash
|
|
321
352
|
forgerelay agents ls
|
|
@@ -323,6 +354,11 @@ forgerelay agents run <profile-or-provider-or-id> "<prompt>"
|
|
|
323
354
|
forgerelay agents show <id>
|
|
324
355
|
```
|
|
325
356
|
|
|
357
|
+
0.3 no longer auto-discovers or seeds the package's historical bundled
|
|
358
|
+
`subagent-delegation` Skill for new setups. An existing or user-authored Skill
|
|
359
|
+
with that name remains an ordinary Skill and is still discovered from the normal
|
|
360
|
+
Skill paths when subagents are enabled; ForgeRelay does not delete or rewrite it.
|
|
361
|
+
|
|
326
362
|
## Logging
|
|
327
363
|
|
|
328
364
|
| Variable | Default |
|
|
@@ -333,14 +369,15 @@ forgerelay agents show <id>
|
|
|
333
369
|
| `FORGERELAY_LOG_ASSETS` | `0` |
|
|
334
370
|
| `FORGERELAY_LOG_TOOL_CALLS` | `1` |
|
|
335
371
|
| `FORGERELAY_LOG_SHELL_COMMANDS` | `1` in `pretty`, `0` in `json` |
|
|
336
|
-
| `FORGERELAY_TRUST_PROXY` | `0` |
|
|
372
|
+
| `FORGERELAY_TRUST_PROXY` | auto: `1` only for loopback bind + non-loopback public URL; otherwise `0` |
|
|
337
373
|
|
|
338
374
|
`pretty` is the human-facing local console format. It uses terminal-aware color,
|
|
339
375
|
short timestamps, workspace-first context, and compact operation results while
|
|
340
376
|
keeping HTTP request records off by default. Project names receive stable
|
|
341
377
|
per-project colors and logical `ws_...` identifiers remain visible; transient MCP
|
|
342
|
-
transport session IDs and normal
|
|
343
|
-
`debug` level
|
|
378
|
+
transport session IDs and normal transport lifecycle events are shown only at
|
|
379
|
+
`debug` level, where they are labeled as `transport` rather than workspace/process
|
|
380
|
+
identity. Shell command previews are enabled
|
|
344
381
|
in this mode and truncated to 120 characters; set
|
|
345
382
|
`FORGERELAY_LOG_SHELL_COMMANDS=0` when command arguments may contain secrets.
|
|
346
383
|
|
|
@@ -349,6 +386,14 @@ overridden, JSON mode preserves request logging and omits shell command previews
|
|
|
349
386
|
`FORGERELAY_LOG_REQUESTS` and `FORGERELAY_LOG_SHELL_COMMANDS` always override
|
|
350
387
|
these format-specific defaults when set.
|
|
351
388
|
|
|
389
|
+
When ForgeRelay binds to loopback (`127.0.0.1`, `::1`, or `localhost`) but is
|
|
390
|
+
configured with a non-loopback public URL, it automatically trusts exactly one
|
|
391
|
+
upstream proxy hop. This matches the normal tunnel/reverse-proxy topology and
|
|
392
|
+
keeps OAuth rate limiting aligned with Express client-IP resolution. Set
|
|
393
|
+
`FORGERELAY_TRUST_PROXY=0` to disable this inference, or `=1` to enable one-hop
|
|
394
|
+
trust explicitly. ForgeRelay never auto-enables proxy trust when binding to
|
|
395
|
+
`0.0.0.0` or another directly reachable interface.
|
|
396
|
+
|
|
352
397
|
## Environment-only example
|
|
353
398
|
|
|
354
399
|
```bash
|
package/docs/debugging.md
CHANGED
|
@@ -59,10 +59,10 @@ The acceptance checks:
|
|
|
59
59
|
3. unauthenticated `/mcp` rejection;
|
|
60
60
|
4. dynamic OAuth client registration, PKCE Owner-password approval, and access-token exchange;
|
|
61
61
|
5. MCP `initialize`, including package/server version consistency and the shell mutation safety contract;
|
|
62
|
-
6. `tools/list` for the full debug tool surface, including `close_workspace`, `write_stdin`, the non-blanket `bash` mutation policy, no kill-timeout input, the 300-second foreground-wait contract, workspace resume/stale-
|
|
62
|
+
6. `tools/list` for the full debug tool surface, including `close_workspace`, `write_stdin`, canonical `processId` plus the deprecated `sessionId` compatibility alias, the non-blanket `bash` mutation policy, no kill-timeout input, the 300-second foreground-wait contract, workspace resume/stale-workspace schema, and MCP App tool metadata;
|
|
63
63
|
7. the full MCP App template chain: `resources/list`, `resources/templates/list`, current content-hashed `resources/read`, legacy/historical template compatibility reads, `text/html;profile=mcp-app`, CSP resource domains, and an HTTP fetch of the JavaScript asset referenced by the template;
|
|
64
|
-
8. a real checkout workspace with `write`, `read`, `rename`, `delete`, foreground `bash` through `
|
|
65
|
-
9. OS temp-directory `write` → `read` → `edit` → `rename` → `delete` over the same real MCP session, plus rejection of an arbitrary path outside the workspace/temp roots;
|
|
64
|
+
8. a real checkout workspace with `write`, `read`, `rename`, `delete`, foreground `bash` through `ProcessManager`, and a deliberate failed `edit`;
|
|
65
|
+
9. OS temp-directory `write` → `read` → `edit` → `rename` → `delete` over the same real MCP transport session, plus rejection of an arbitrary path outside the workspace/temp roots;
|
|
66
66
|
10. a temporary Git repository with managed worktree creation, file modification, and `close_worktree`;
|
|
67
67
|
11. 本地 bare remote 上的 release-tag-push Hook:成功 Hook 必须先运行再允许 `v0.2.0` push,失败 Hook 必须在 remote mutation 前阻断 `v0.2.1`;
|
|
68
68
|
12. deterministic local subagent error path,不联系任何模型 provider;
|
package/docs/roadmap.md
CHANGED
|
@@ -99,10 +99,36 @@ Hooks v1 的目标是给用户和 Agent 一个很小、自动、可组合的生
|
|
|
99
99
|
- `workspaceId` 是唯一持久的逻辑工作身份,跨请求和 transport 重连保持连续;
|
|
100
100
|
- `requestId` 只追踪单次 HTTP/JSON-RPC 请求,不持久化;
|
|
101
101
|
- MCP 协议层 session 在内部和 debug 输出中明确称为 `transportSessionId`,业务状态不得依赖它;
|
|
102
|
-
-
|
|
102
|
+
- 后台命令句柄以 `processId` / process handle 为 canonical 名称,旧 `sessionId` 在 0.2.x 保留 deprecated alias 兼容窗口;
|
|
103
103
|
- 为 stateless MCP transport 做准备,同时保留旧协议兼容 adapter,避免把 transport 生命周期重新提升成 ForgeRelay 会话模型。
|
|
104
104
|
|
|
105
|
-
## 0.3 —
|
|
105
|
+
## 0.3 — MCP loading 与渐进式能力披露
|
|
106
|
+
|
|
107
|
+
0.3 的目标是缩小 MCP 首次加载时注入给 Agent 的上下文,同时保持工具可调用性、安全边界和 Host 编排权不变。ForgeRelay 不再把所有低频能力说明都塞进 server instructions 或工具 description,而是把模型接口拆成三层:
|
|
108
|
+
|
|
109
|
+
- `tools/list` 继续暴露真实可调用 primitive,并只携带调用该工具所必需的简洁语义;
|
|
110
|
+
- `open_workspace` 返回紧凑的 server/capability 摘要与版本指纹,帮助 Agent 发现能力,并识别 Host 持有旧 tool schema snapshot 的情况;
|
|
111
|
+
- ForgeRelay-owned capability guide 提供低频能力的完整说明,由 Agent 在任务相关时显式 `read`,而不是首次连接时自动注入。
|
|
112
|
+
|
|
113
|
+
首版优先复用现有 Skill-style 的 advertised path / `read` 授权机制,而不是新增第二套文档加载协议。Capability guide 与 Skill 的语义所有权保持区分:Skill 描述用户、项目或生态工作流;capability guide 描述 ForgeRelay 自身、与版本绑定的产品能力。
|
|
114
|
+
|
|
115
|
+
Core Capability Contract 必须始终内联保留至少这些信息:
|
|
116
|
+
|
|
117
|
+
- `workspaceId` 生命周期与 workspace 复用规则;
|
|
118
|
+
- 常用文件读写改、`rename` 同时承担 move/rename、删除的核心语义;
|
|
119
|
+
- shell 以本地用户权限执行且不是 OS sandbox;
|
|
120
|
+
- `processId` / `write_stdin` 的基本长进程语义;
|
|
121
|
+
- Hook 阻断结果必须对 Agent 可见;
|
|
122
|
+
- 关键 mutation/safety invariant;
|
|
123
|
+
- `close_workspace` 与 `close_worktree` 的区别。
|
|
124
|
+
|
|
125
|
+
适合按需读取的首批领域包括:生命周期 Hooks、managed worktree 高级流程、subagents、artifact/review 工作流、debug/MCP App、OAuth/deployment,以及 shell/PTTY/process 的低频边界情况。首个实现切片迁移 Hooks 与 managed worktree 高级说明;第二切片继续覆盖 subagents、artifact/review、Host/OAuth/MCP App integration 与 shell/PTTY/process,并把历史 bundled `subagent-delegation` Skill 的默认自动发现迁回 ForgeRelay-owned capability guide。必要安全语义始终保留在 core contract 或真实 tool schema/description 中。
|
|
126
|
+
|
|
127
|
+
Capability/version fingerprint 必须是轻量、语义化、稳定的摘要,不复制完整 `tools/list`。当 server 报告的能力与 Host 当前暴露的 tool snapshot 明显不一致时,Agent 应能判断为 Host metadata stale,并建议刷新 MCP 或开启新会话,而不是错误断言 ForgeRelay 缺少能力。
|
|
128
|
+
|
|
129
|
+
0.3 不隐藏 callable tool,不增加隐式 autonomous workflow,也不把 Host Refresh/session 行为归到 ForgeRelay。`rename` 继续作为文件和目录 move/rename 的统一 primitive。
|
|
130
|
+
|
|
131
|
+
## 0.4 — LSP code intelligence v1
|
|
106
132
|
|
|
107
133
|
LSP is moderate implementation complexity if ForgeRelay does not become a
|
|
108
134
|
language-server installer.
|
|
@@ -110,7 +136,7 @@ language-server installer.
|
|
|
110
136
|
The first version should launch only language servers already available on the
|
|
111
137
|
user's machine or explicitly configured by the user/project.
|
|
112
138
|
|
|
113
|
-
During 0.
|
|
139
|
+
During 0.4 development, MCP App UI hardening can land alongside the LSP work when
|
|
114
140
|
it does not distort the code-intelligence scope. In particular, evaluate a more
|
|
115
141
|
self-contained template/bootstrap bundle, reduce avoidable external chunk fetches,
|
|
116
142
|
and keep the current content-hash/compatibility-resource contract intact. This is
|
|
@@ -144,7 +170,7 @@ Candidate servers include `typescript-language-server`/tsserver, Pyright,
|
|
|
144
170
|
`rust-analyzer`, `gopls`, and `clangd`, but ForgeRelay should treat server
|
|
145
171
|
commands/configuration as external dependencies.
|
|
146
172
|
|
|
147
|
-
## 0.
|
|
173
|
+
## 0.5 — First-class subagent MCP
|
|
148
174
|
|
|
149
175
|
ForgeRelay already owns provider adapters and resumable local agent sessions.
|
|
150
176
|
The next step is to remove the current `bash -> forgerelay agents ...` indirection
|
package/docs/security.md
CHANGED
|
@@ -28,6 +28,17 @@ Filesystem-oriented tools canonicalize existing path segments before access so
|
|
|
28
28
|
symlinks inside either the workspace or OS temp directory cannot escape to
|
|
29
29
|
arbitrary filesystem locations.
|
|
30
30
|
|
|
31
|
+
`read` has one narrow additional path class for explicitly advertised documents.
|
|
32
|
+
`open_workspace` may advertise a Skill entry file or a ForgeRelay capability
|
|
33
|
+
Guide outside the normal workspace/temp roots. Only that advertised entry file is
|
|
34
|
+
readable initially; after it is read, files inside that advertised document's
|
|
35
|
+
own directory may be read as supporting material. This does not expand write,
|
|
36
|
+
edit, rename, delete, shell working-directory, or workspace-open roots.
|
|
37
|
+
|
|
38
|
+
Capability Guides are ForgeRelay-owned, versioned package documentation. Their
|
|
39
|
+
paths are surfaced by the running server rather than guessed by the Agent. This
|
|
40
|
+
keeps progressive disclosure from becoming a general arbitrary-file read escape.
|
|
41
|
+
|
|
31
42
|
## Owner-password OAuth
|
|
32
43
|
|
|
33
44
|
New installations store local configuration in:
|
|
@@ -100,13 +111,19 @@ for dynamically crossing such a boundary.
|
|
|
100
111
|
The Agent-facing shell contract therefore does not ban every command that can
|
|
101
112
|
change files. Commands may modify ordinary project files when that is a natural
|
|
102
113
|
part of the user's requested development task, including package managers,
|
|
103
|
-
generators, formatters, and similar tooling.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
114
|
+
generators, formatters, and similar tooling. Shell commands may also perform
|
|
115
|
+
external device or hardware mutations when the user's current request explicitly
|
|
116
|
+
asks for the actual device-changing operation. ForgeRelay does not assume a
|
|
117
|
+
particular flashing protocol, transport, device path, or firmware workflow. A
|
|
118
|
+
check, audit, probe, backup, verification, dry-run, or build-only request must not
|
|
119
|
+
be treated as authorization for a later persistent hardware write.
|
|
120
|
+
|
|
121
|
+
The contract does prohibit shell mutation of security- or privilege-sensitive
|
|
122
|
+
operating-system files and credential material such as `/etc/sudoers`,
|
|
123
|
+
`/etc/passwd`, `/etc/shadow`, PAM or authentication policy, SSH private keys, and
|
|
124
|
+
equivalent privileged targets. Configuration files may be changed through shell
|
|
125
|
+
only when the user's request explicitly calls for that configuration change
|
|
126
|
+
rather than merely making it a convenient implementation detail.
|
|
110
127
|
|
|
111
128
|
This is an Agent execution policy, not an OS-level sandbox or command parser.
|
|
112
129
|
Operators that need a stronger project-specific runtime gate can use blocking
|
|
@@ -124,8 +141,9 @@ Do not describe ForgeRelay as a sandboxed coding environment.
|
|
|
124
141
|
|
|
125
142
|
Shell execution has a 300-second foreground wait ceiling, not a 300-second
|
|
126
143
|
process lifetime. When `bash` is still running after that window, ForgeRelay
|
|
127
|
-
returns a
|
|
128
|
-
wait, interact, or explicitly interrupt it.
|
|
144
|
+
returns a canonical `processId` and leaves the process alive. `write_stdin` can
|
|
145
|
+
poll, wait, interact, or explicitly interrupt it. The former process `sessionId`
|
|
146
|
+
remains a deprecated compatibility alias during 0.2.x. An asynchronously completed process
|
|
129
147
|
is reported on a later tool result for the same logical workspace ID, including
|
|
130
148
|
error-result paths, and is never broadcast to another workspace ID. Explicitly
|
|
131
149
|
resuming the same workspace ID in another conversation intentionally transfers
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akira-tl/forgerelay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Local development control plane for MCP coding agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://github.com/Akira-TL/forgerelay#readme",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"examples",
|
|
25
25
|
"scripts",
|
|
26
26
|
"skills",
|
|
27
|
+
"capabilities",
|
|
27
28
|
"README.md",
|
|
28
29
|
"CHANGELOG.md",
|
|
29
30
|
"NOTICE.md"
|
|
@@ -34,14 +35,14 @@
|
|
|
34
35
|
},
|
|
35
36
|
"scripts": {
|
|
36
37
|
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
37
|
-
"build": "npm run clean && npm run build:app && tsc -p tsconfig.build.json",
|
|
38
|
+
"build": "npm run clean && npm run build:app && tsc -p tsconfig.build.json && node scripts/ensure-cli-executable.mjs",
|
|
38
39
|
"build:app": "vite build",
|
|
39
40
|
"dev": "node scripts/debug/serve.mjs",
|
|
40
41
|
"debug:serve": "node scripts/debug/serve.mjs",
|
|
41
42
|
"debug:accept": "node scripts/debug/accept.mjs",
|
|
42
43
|
"postinstall": "node scripts/fix-node-pty-permissions.mjs",
|
|
43
44
|
"start": "node dist/cli.js serve",
|
|
44
|
-
"test": "tsx src/config.test.ts && tsx src/logger.test.ts && tsx src/mcp-app-template.test.ts && tsx src/hooks.test.ts && tsx src/mcp/server-instructions.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/roots.test.ts && tsx src/file-mutations.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/server.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
|
|
45
|
+
"test": "tsx src/config.test.ts && tsx src/logger.test.ts && tsx src/proxy-trust.test.ts && tsx src/mcp-app-template.test.ts && tsx src/hooks.test.ts && tsx src/mcp/server-instructions.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/roots.test.ts && tsx src/file-mutations.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/server.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
|
|
45
46
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
46
47
|
"release:check": "node scripts/release-version.mjs check",
|
|
47
48
|
"release:tag-check": "node scripts/release-version.mjs tag",
|
package/scripts/debug/accept.mjs
CHANGED
|
@@ -93,6 +93,9 @@ try {
|
|
|
93
93
|
assert.match(serverInstructions, /Shell commands may modify ordinary project files/);
|
|
94
94
|
assert.match(serverInstructions, /\/etc\/sudoers/);
|
|
95
95
|
assert.match(serverInstructions, /configuration files through shell only when the user's request explicitly calls for that configuration change/);
|
|
96
|
+
assert.match(serverInstructions, /managed-worktrees capability guide/);
|
|
97
|
+
assert.ok(serverInstructions.length < 3_000, `server instructions should stay compact, got ${serverInstructions.length} characters`);
|
|
98
|
+
assert.doesNotMatch(serverInstructions, /fast-forwards the original target branch/);
|
|
96
99
|
assert.doesNotMatch(serverInstructions, /Do not create or modify files with bash/);
|
|
97
100
|
pass(
|
|
98
101
|
"MCP initialize",
|
|
@@ -118,18 +121,30 @@ try {
|
|
|
118
121
|
assert.ok(toolNames.includes(expected), `missing debug tool ${expected}`);
|
|
119
122
|
}
|
|
120
123
|
const bashTool = tools.find((tool) => tool.name === "bash");
|
|
121
|
-
assert.match(bashTool?.description ?? "", /
|
|
122
|
-
assert.
|
|
124
|
+
assert.match(bashTool?.description ?? "", /local user's authority/);
|
|
125
|
+
assert.doesNotMatch(bashTool?.description ?? "", /may modify ordinary project files/);
|
|
126
|
+
assert.doesNotMatch(bashTool?.description ?? "", /\/etc\/sudoers/);
|
|
127
|
+
assert.doesNotMatch(bashTool?.description ?? "", /external device or hardware mutations/);
|
|
123
128
|
assert.doesNotMatch(bashTool?.description ?? "", /Do not use bash to create, move, rename, or delete project files/);
|
|
124
129
|
assert.equal(bashTool?.inputSchema?.properties?.timeout, undefined);
|
|
125
130
|
assert.match(bashTool?.description ?? "", /waits up to 300 seconds/);
|
|
126
131
|
assert.match(bashTool?.description ?? "", /write_stdin/);
|
|
127
132
|
const writeStdinTool = tools.find((tool) => tool.name === "write_stdin");
|
|
128
133
|
assert.equal(writeStdinTool?.inputSchema?.properties?.yieldTimeMs?.maximum, 300000);
|
|
134
|
+
assert.match(
|
|
135
|
+
writeStdinTool?.inputSchema?.properties?.processId?.description ?? "",
|
|
136
|
+
/Canonical process identifier/,
|
|
137
|
+
);
|
|
138
|
+
assert.match(
|
|
139
|
+
writeStdinTool?.inputSchema?.properties?.sessionId?.description ?? "",
|
|
140
|
+
/Deprecated alias for processId/,
|
|
141
|
+
);
|
|
129
142
|
const openWorkspaceTool = tools.find((tool) => tool.name === "open_workspace");
|
|
130
143
|
assert.ok(openWorkspaceTool?.inputSchema?.properties?.workspaceId);
|
|
131
144
|
assert.ok(openWorkspaceTool?.inputSchema?.properties?.newWorkspace);
|
|
132
145
|
assert.ok(openWorkspaceTool?.outputSchema?.properties?.staleWorkspaces);
|
|
146
|
+
assert.ok(openWorkspaceTool?.outputSchema?.properties?.capabilityFingerprint);
|
|
147
|
+
assert.ok(openWorkspaceTool?.outputSchema?.properties?.capabilityGuides);
|
|
133
148
|
const templateUri = bashTool?._meta?.ui?.resourceUri;
|
|
134
149
|
assert.match(
|
|
135
150
|
templateUri ?? "",
|
|
@@ -195,7 +210,35 @@ try {
|
|
|
195
210
|
assert.match(workspaceId, /^ws_/);
|
|
196
211
|
assert.equal(opened.structuredContent.root, checkoutWorkspace);
|
|
197
212
|
assert.equal(opened.structuredContent.mode, "checkout");
|
|
198
|
-
|
|
213
|
+
assert.deepEqual(opened.structuredContent.capabilityFingerprint, {
|
|
214
|
+
version: packageJson.version,
|
|
215
|
+
toolMode: "full",
|
|
216
|
+
capabilities: [
|
|
217
|
+
"workspace.close",
|
|
218
|
+
"worktree.managed",
|
|
219
|
+
"filesystem.rename-move",
|
|
220
|
+
"filesystem.delete",
|
|
221
|
+
"process.write-stdin",
|
|
222
|
+
"hooks.lifecycle",
|
|
223
|
+
"capability-guides.read",
|
|
224
|
+
"inspection.search-tools",
|
|
225
|
+
"ui.mcp-app",
|
|
226
|
+
],
|
|
227
|
+
});
|
|
228
|
+
const capabilityGuides = opened.structuredContent.capabilityGuides;
|
|
229
|
+
assert.deepEqual(capabilityGuides.map((guide) => guide.name), [
|
|
230
|
+
"lifecycle-hooks",
|
|
231
|
+
"managed-worktrees",
|
|
232
|
+
"host-integration",
|
|
233
|
+
"shell-processes",
|
|
234
|
+
]);
|
|
235
|
+
const hooksGuide = callTool(oauth.accessToken, sessionId, 78, "read", {
|
|
236
|
+
workspaceId,
|
|
237
|
+
path: capabilityGuides[0].path,
|
|
238
|
+
});
|
|
239
|
+
assert.match(hooksGuide.structuredContent.result, /BeforeTool/);
|
|
240
|
+
assert.match(hooksGuide.structuredContent.result, /BeforeWorktreeClose/);
|
|
241
|
+
pass("open_workspace", `${workspaceId} -> fingerprint + ${capabilityGuides.length} capability guides`);
|
|
199
242
|
|
|
200
243
|
const written = callTool(oauth.accessToken, sessionId, 4, "write", {
|
|
201
244
|
workspaceId,
|
|
@@ -217,7 +260,7 @@ try {
|
|
|
217
260
|
});
|
|
218
261
|
assert.match(shell.structuredContent.result, /debug-bash-ok/);
|
|
219
262
|
assert.equal(shell.structuredContent.running, false);
|
|
220
|
-
pass("bash", "foreground command completed through
|
|
263
|
+
pass("bash", "foreground command completed through ProcessManager");
|
|
221
264
|
|
|
222
265
|
const failedEdit = callTool(oauth.accessToken, sessionId, 7, "edit", {
|
|
223
266
|
workspaceId,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { chmodSync, statSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
4
|
+
if (process.platform !== "win32") {
|
|
5
|
+
const cliPath = fileURLToPath(new URL("../dist/cli.js", import.meta.url));
|
|
6
|
+
const currentMode = statSync(cliPath).mode;
|
|
7
|
+
chmodSync(cliPath, currentMode | 0o111);
|
|
8
|
+
|
|
9
|
+
if ((statSync(cliPath).mode & 0o111) === 0) {
|
|
10
|
+
throw new Error(`ForgeRelay CLI is not executable after build: ${cliPath}`);
|
|
11
|
+
}
|
|
12
|
+
}
|