@akira-tl/forgerelay 1.2.5 → 1.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 +20 -0
- package/README.md +2 -2
- package/dist/cli/config/domains/context-cli.js +86 -0
- package/dist/cli/config/domains/domain-cli.js +468 -0
- package/dist/cli/config/general.js +174 -0
- package/dist/cli/config/inspect.js +29 -26
- package/dist/cli/config/migrate.js +6 -22
- package/dist/cli/config/scope.js +35 -0
- package/dist/cli/connect/relay.js +284 -0
- package/dist/cli/core/command-tree.js +68 -0
- package/dist/cli/core/serve-options.js +71 -0
- package/dist/cli/init/setup-config.js +26 -0
- package/dist/cli/init.js +37 -8
- package/dist/cli/maintenance-prune.js +1 -1
- package/dist/cli/maintenance.js +6 -6
- package/dist/cli/mcp/external-mcp.js +29 -17
- package/dist/cli/mcp/status.js +2 -2
- package/dist/cli/system/status.js +35 -0
- package/dist/cli.js +132 -272
- package/dist/mcp/operations/external-mcp/external-mcp-oauth.js +2 -2
- package/dist/mcp/server/core/schemas.js +2 -10
- package/dist/mcp/server/operations/runtime/operation-runtime.js +11 -7
- package/dist/runtime/config/config.js +30 -29
- package/dist/runtime/config/definition/general-config.js +18 -3
- package/dist/runtime/config/resolution/resolver.js +7 -4
- package/dist/runtime/config/user-config.js +6 -6
- package/dist/runtime/config/validation/paths.js +12 -0
- package/dist/subagents/profiles.js +37 -0
- package/dist/workspaces/bootstrap.js +31 -14
- package/dist/workspaces/context.js +159 -7
- package/dist/workspaces/relay/auth/cli-test-support.js +22 -0
- package/dist/workspaces/resources/context-sources.js +29 -0
- package/dist/workspaces/resources/resource-monitor.js +29 -6
- package/dist/workspaces/resources/skills.js +15 -10
- package/dist/workspaces/sessions.js +4 -2
- package/dist/workspaces/state/project-context.js +34 -8
- package/dist/workspaces.js +5 -2
- package/docs/chatgpt-coding-workflow.md +23 -20
- package/docs/configuration.md +40 -27
- package/docs/gotchas.md +8 -6
- package/docs/roadmap.md +1 -1
- package/package.json +2 -2
- package/schemas/v1/config.project-local.schema.json +74 -0
- package/schemas/v1/config.project.schema.json +74 -0
- package/schemas/v1/config.user.schema.json +57 -2
- package/scripts/ci/config-v2-product-acceptance.mjs +17 -12
- package/scripts/debug/runtime.mjs +19 -3
- package/scripts/debug/runtime.test.mjs +3 -0
- package/scripts/debug/serve.mjs +2 -2
|
@@ -24,6 +24,7 @@ export class WorkspaceResourceMonitor {
|
|
|
24
24
|
tracked: new Map(),
|
|
25
25
|
subscriptions: new Map(),
|
|
26
26
|
changes: [],
|
|
27
|
+
announcements: [],
|
|
27
28
|
deliveredRevisionByScope: new Map(),
|
|
28
29
|
};
|
|
29
30
|
state.root = input.root;
|
|
@@ -88,6 +89,20 @@ export class WorkspaceResourceMonitor {
|
|
|
88
89
|
this.recordChange(workspaceId, key, oldContent, newContent);
|
|
89
90
|
}));
|
|
90
91
|
}
|
|
92
|
+
announce(workspaceId, text, coveredComponents) {
|
|
93
|
+
const state = this.states.get(workspaceId);
|
|
94
|
+
if (!state)
|
|
95
|
+
return;
|
|
96
|
+
state.revision += 1;
|
|
97
|
+
state.announcements.push({
|
|
98
|
+
revision: state.revision,
|
|
99
|
+
text,
|
|
100
|
+
coveredComponents,
|
|
101
|
+
});
|
|
102
|
+
if (state.announcements.length > MAX_HISTORY) {
|
|
103
|
+
state.announcements.splice(0, state.announcements.length - MAX_HISTORY);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
91
106
|
markSkillActivated(workspaceId, skillPath) {
|
|
92
107
|
const state = this.states.get(workspaceId);
|
|
93
108
|
if (!state)
|
|
@@ -118,25 +133,31 @@ export class WorkspaceResourceMonitor {
|
|
|
118
133
|
synchronizeSharedWatch(key);
|
|
119
134
|
const deliveredRevision = state.deliveredRevisionByScope.get(conversationScopeId) ?? state.revision;
|
|
120
135
|
const changes = state.changes.filter((change) => change.revision > deliveredRevision);
|
|
136
|
+
const announcements = state.announcements.filter((announcement) => announcement.revision > deliveredRevision);
|
|
121
137
|
state.deliveredRevisionByScope.set(conversationScopeId, state.revision);
|
|
122
|
-
if (changes.length === 0)
|
|
138
|
+
if (changes.length === 0 && announcements.length === 0)
|
|
123
139
|
return undefined;
|
|
124
|
-
const grouped = coalesceChanges(changes);
|
|
125
140
|
const sections = [];
|
|
126
141
|
const coveredComponents = new Set();
|
|
127
|
-
for (const change of
|
|
142
|
+
for (const change of coalesceChanges(changes)) {
|
|
128
143
|
const formatted = formatResourceChange(change);
|
|
129
144
|
if (!formatted)
|
|
130
145
|
continue;
|
|
131
|
-
sections.push(formatted.text);
|
|
146
|
+
sections.push({ revision: change.revision, text: formatted.text });
|
|
132
147
|
for (const component of formatted.coveredComponents)
|
|
133
148
|
coveredComponents.add(component);
|
|
134
149
|
}
|
|
150
|
+
for (const announcement of announcements) {
|
|
151
|
+
sections.push({ revision: announcement.revision, text: announcement.text });
|
|
152
|
+
for (const component of announcement.coveredComponents)
|
|
153
|
+
coveredComponents.add(component);
|
|
154
|
+
}
|
|
155
|
+
sections.sort((left, right) => left.revision - right.revision);
|
|
135
156
|
this.pruneDeliveredHistory(state);
|
|
136
157
|
if (sections.length === 0)
|
|
137
158
|
return undefined;
|
|
138
159
|
const header = "Workspace context changed after this Workspace was opened. Apply only these deltas; unchanged instructions and Skill metadata remain active:";
|
|
139
|
-
const joined = [header, ...sections].join("\n\n");
|
|
160
|
+
const joined = [header, ...sections.map((section) => section.text)].join("\n\n");
|
|
140
161
|
const text = joined.length <= MAX_DELIVERY_CHARACTERS
|
|
141
162
|
? joined
|
|
142
163
|
: `${joined.slice(0, MAX_DELIVERY_CHARACTERS)}\n\n[Additional Workspace context deltas were truncated; reopen with context=\"auto\" to refresh metadata.]`;
|
|
@@ -199,11 +220,13 @@ export class WorkspaceResourceMonitor {
|
|
|
199
220
|
}
|
|
200
221
|
}
|
|
201
222
|
pruneDeliveredHistory(state) {
|
|
202
|
-
if (state.changes.length === 0 || state.deliveredRevisionByScope.size === 0)
|
|
223
|
+
if ((state.changes.length === 0 && state.announcements.length === 0) || state.deliveredRevisionByScope.size === 0)
|
|
203
224
|
return;
|
|
204
225
|
const floor = Math.min(...state.deliveredRevisionByScope.values());
|
|
205
226
|
while (state.changes[0] && state.changes[0].revision <= floor)
|
|
206
227
|
state.changes.shift();
|
|
228
|
+
while (state.announcements[0] && state.announcements[0].revision <= floor)
|
|
229
|
+
state.announcements.shift();
|
|
207
230
|
}
|
|
208
231
|
}
|
|
209
232
|
function subscribeSharedWatch(inputPath, subscriberId, subscriber) {
|
|
@@ -4,16 +4,21 @@ import { basename, dirname, extname, join, resolve, sep } from "node:path";
|
|
|
4
4
|
import { parse as parseYaml } from "yaml";
|
|
5
5
|
import { markAdvertisedFileSourceActivated, resolveAdvertisedFileReadPath, } from "../../mcp/filesystem/advertised-files.js";
|
|
6
6
|
import { expandHomePath, isPathInsideRoot } from "../../mcp/filesystem/roots.js";
|
|
7
|
+
export function redactSkillDiagnosticMessage(diagnostic) {
|
|
8
|
+
let message = diagnostic.message;
|
|
9
|
+
const hiddenPaths = [
|
|
10
|
+
diagnostic.path,
|
|
11
|
+
diagnostic.collision?.winnerPath,
|
|
12
|
+
diagnostic.collision?.loserPath,
|
|
13
|
+
].filter((path) => Boolean(path));
|
|
14
|
+
for (const path of hiddenPaths)
|
|
15
|
+
message = message.split(path).join("<skill-path>");
|
|
16
|
+
return message;
|
|
17
|
+
}
|
|
7
18
|
const FRONTMATTER_DELIMITER = "---";
|
|
8
|
-
export function effectiveSkillPaths(config, cwd) {
|
|
9
|
-
const defaultPathCandidates = [
|
|
10
|
-
resolve(cwd, ".agents", "skills"),
|
|
11
|
-
resolve(cwd, ".forgerelay", "skills"),
|
|
12
|
-
config.configSkillsDir,
|
|
13
|
-
];
|
|
14
|
-
const defaultPaths = defaultPathCandidates.filter((path) => path !== undefined && existsSync(path));
|
|
19
|
+
export function effectiveSkillPaths(config, cwd, skillPaths = config.skillPaths) {
|
|
15
20
|
const seen = new Set();
|
|
16
|
-
return
|
|
21
|
+
return skillPaths
|
|
17
22
|
.map((path) => resolveSkillPath(path, cwd))
|
|
18
23
|
.filter((path) => {
|
|
19
24
|
if (seen.has(path))
|
|
@@ -25,13 +30,13 @@ export function effectiveSkillPaths(config, cwd) {
|
|
|
25
30
|
function resolveSkillPath(path, cwd) {
|
|
26
31
|
return resolve(cwd, expandHomePath(path));
|
|
27
32
|
}
|
|
28
|
-
export function loadWorkspaceSkills(config, cwd) {
|
|
33
|
+
export function loadWorkspaceSkills(config, cwd, skillPaths = config.skillPaths) {
|
|
29
34
|
if (!config.skillsEnabled)
|
|
30
35
|
return { skills: [], diagnostics: [] };
|
|
31
36
|
const skills = [];
|
|
32
37
|
const diagnostics = [];
|
|
33
38
|
const winners = new Map();
|
|
34
|
-
for (const sourcePath of effectiveSkillPaths(config, cwd)) {
|
|
39
|
+
for (const sourcePath of effectiveSkillPaths(config, cwd, skillPaths)) {
|
|
35
40
|
const candidates = discoverSkills(sourcePath, diagnostics);
|
|
36
41
|
for (const skill of candidates) {
|
|
37
42
|
const existing = winners.get(skill.name);
|
|
@@ -295,7 +295,7 @@ export class WorkspaceSessionService {
|
|
|
295
295
|
}
|
|
296
296
|
async reusedWorkspaceContext(workspace) {
|
|
297
297
|
workspace.project = await resolveProjectContext(this.config.configDir, workspace.root);
|
|
298
|
-
Object.assign(workspace, this.context.
|
|
298
|
+
Object.assign(workspace, await this.context.loadContextSourcesForWorkspace(workspace.project, workspace.root));
|
|
299
299
|
workspace.capabilityGuides = loadCapabilityGuides(this.config);
|
|
300
300
|
workspace.agentProfiles = await loadSubagentProfiles(this.config, workspace.root);
|
|
301
301
|
workspace.scannedInstructionDirs.clear();
|
|
@@ -538,11 +538,13 @@ export class WorkspaceSessionService {
|
|
|
538
538
|
return existing;
|
|
539
539
|
}
|
|
540
540
|
const root = this.context.assertWorkspaceRootAllowed(session.root, session.mode, session.sourceRoot);
|
|
541
|
+
const contextSources = this.context.defaultContextSources(root);
|
|
541
542
|
const restoredWorkspace = {
|
|
542
543
|
id: session.id,
|
|
543
544
|
root,
|
|
544
545
|
mode: session.mode,
|
|
545
546
|
sourceRoot: session.sourceRoot,
|
|
547
|
+
contextSources,
|
|
546
548
|
worktree: session.mode === "worktree"
|
|
547
549
|
? {
|
|
548
550
|
path: root,
|
|
@@ -555,7 +557,7 @@ export class WorkspaceSessionService {
|
|
|
555
557
|
managed: session.managed,
|
|
556
558
|
}
|
|
557
559
|
: undefined,
|
|
558
|
-
...this.context.loadSkillsForWorkspace(root),
|
|
560
|
+
...this.context.loadSkillsForWorkspace(root, contextSources.skillPaths),
|
|
559
561
|
capabilityGuides: loadCapabilityGuides(this.config),
|
|
560
562
|
agentProfiles: [],
|
|
561
563
|
activatedSkillDirs: new Set(),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
|
-
import {
|
|
3
|
+
import { mkdir, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { isAbsolute, join, resolve } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { withFileLock } from "../../runtime/state/lock/file-lock.js";
|
|
@@ -193,20 +193,46 @@ function newProjectId() {
|
|
|
193
193
|
async function hasGitMetadataAncestor(workspaceRoot) {
|
|
194
194
|
let current = workspaceRoot;
|
|
195
195
|
for (;;) {
|
|
196
|
-
|
|
197
|
-
await lstat(join(current, ".git"));
|
|
196
|
+
if (await isGitMetadataMarker(join(current, ".git")))
|
|
198
197
|
return true;
|
|
199
|
-
}
|
|
200
|
-
catch (error) {
|
|
201
|
-
if (!isErrno(error, "ENOENT"))
|
|
202
|
-
throw error;
|
|
203
|
-
}
|
|
204
198
|
const parent = resolve(current, "..");
|
|
205
199
|
if (parent === current)
|
|
206
200
|
return false;
|
|
207
201
|
current = parent;
|
|
208
202
|
}
|
|
209
203
|
}
|
|
204
|
+
async function isGitMetadataMarker(path) {
|
|
205
|
+
let metadata;
|
|
206
|
+
try {
|
|
207
|
+
metadata = await stat(path);
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
if (isErrno(error, "ENOENT"))
|
|
211
|
+
return false;
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
if (metadata.isDirectory()) {
|
|
215
|
+
try {
|
|
216
|
+
return (await readFile(join(path, "HEAD"), "utf8")).trim().length > 0;
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
if (isErrno(error, "ENOENT") || isErrno(error, "ENOTDIR"))
|
|
220
|
+
return false;
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (metadata.isFile()) {
|
|
225
|
+
try {
|
|
226
|
+
return /^gitdir:\s*\S+/i.test((await readFile(path, "utf8")).trim());
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
if (isErrno(error, "ENOENT") || isErrno(error, "ENOTDIR"))
|
|
230
|
+
return false;
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
210
236
|
function isGitRepositoryMiss(error) {
|
|
211
237
|
if (!isRecord(error))
|
|
212
238
|
return false;
|
package/dist/workspaces.js
CHANGED
|
@@ -360,14 +360,16 @@ export class WorkspaceRegistry {
|
|
|
360
360
|
});
|
|
361
361
|
}
|
|
362
362
|
async createWorkspaceContext(input) {
|
|
363
|
+
const project = await resolveProjectContext(this.config.configDir, input.root);
|
|
364
|
+
const contextResources = await this.context.loadContextSourcesForWorkspace(project, input.root);
|
|
363
365
|
const workspace = {
|
|
364
366
|
id: `ws_${randomBytes(5).toString("hex")}`,
|
|
365
367
|
root: input.root,
|
|
366
368
|
mode: input.mode,
|
|
367
369
|
sourceRoot: input.sourceRoot,
|
|
368
|
-
project
|
|
370
|
+
project,
|
|
371
|
+
...contextResources,
|
|
369
372
|
worktree: input.worktree,
|
|
370
|
-
...this.context.loadSkillsForWorkspace(input.root),
|
|
371
373
|
capabilityGuides: loadCapabilityGuides(this.config),
|
|
372
374
|
agentProfiles: await loadSubagentProfiles(this.config, input.root),
|
|
373
375
|
activatedSkillDirs: new Set(),
|
|
@@ -421,6 +423,7 @@ export class WorkspaceRegistry {
|
|
|
421
423
|
discoverPathInstructions(workspace, inputPath) {
|
|
422
424
|
return this.context.discoverPathInstructions(workspace, inputPath);
|
|
423
425
|
}
|
|
426
|
+
refreshContextSources(workspaceId) { return this.context.refreshContextSourcesForWorkspace(this.getWorkspace(workspaceId)); }
|
|
424
427
|
claimResourceUpdates(workspaceId, conversationScopeId) {
|
|
425
428
|
return this.context.claimResourceUpdates(workspaceId, conversationScopeId);
|
|
426
429
|
}
|
|
@@ -174,23 +174,20 @@ state and does not recreate the physical backing.
|
|
|
174
174
|
|
|
175
175
|
## Instructions
|
|
176
176
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
177
|
+
Agent context sources are resolved through General Config v2 using
|
|
178
|
+
`runtime > project-local > project > user > built-in` precedence and replace semantics.
|
|
179
|
+
ForgeRelay loads exactly one selected system-instructions file; the built-in default is
|
|
180
|
+
`~/.agents/AGENTS.md`, and `FORGERELAY_SYSTEM_INSTRUCTIONS_PATH` is the runtime replacement.
|
|
181
|
+
A missing selected system file is reported as unavailable but does not block Workspace open.
|
|
181
182
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
AGENTS.MD
|
|
187
|
-
CLAUDE.md
|
|
188
|
-
CLAUDE.MD
|
|
189
|
-
```
|
|
183
|
+
Project/path-scoped instruction discovery is controlled by `instructionNames`. The built-in
|
|
184
|
+
list is exactly `["AGENTS.md"]`; `CLAUDE.md`, `GEMINI.md`, or other basenames are discovered
|
|
185
|
+
only when explicitly configured. `FORGERELAY_INSTRUCTION_NAMES` provides a comma-separated
|
|
186
|
+
runtime replacement list.
|
|
190
187
|
|
|
191
188
|
To keep broad workspaces such as `~` fast, initial nested-instruction discovery is
|
|
192
189
|
bounded to direct child directories instead of recursively walking the whole tree.
|
|
193
|
-
Deeper
|
|
190
|
+
Deeper configured instruction filenames are discovered lazily along a path the first
|
|
194
191
|
time the Agent accesses it, and already-scanned directories are cached for the life
|
|
195
192
|
of that workspace handle. A `read` result carries any newly discovered local
|
|
196
193
|
instructions before the requested file content. Side-effecting file tools and shell
|
|
@@ -241,16 +238,22 @@ force the Host to discard a cached tool schema.
|
|
|
241
238
|
|
|
242
239
|
## Agent Skills
|
|
243
240
|
|
|
244
|
-
|
|
241
|
+
`skillPaths` is an ordered General Config v2 list with replace semantics. Its built-in
|
|
242
|
+
effective value is:
|
|
245
243
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
244
|
+
```text
|
|
245
|
+
~/.agents/skills
|
|
246
|
+
./.agents/skills
|
|
247
|
+
```
|
|
250
248
|
|
|
251
|
-
|
|
249
|
+
The first path is user-home scoped; `./...` paths resolve from the current Workspace root.
|
|
250
|
+
Project, Project Local, User, or runtime config can replace the complete list with any
|
|
251
|
+
Agent ecosystem directories. `FORGERELAY_SKILL_PATHS` is the comma-separated runtime
|
|
252
|
+
replacement. ForgeRelay does not implicitly scan `.forgerelay/skills`, provider-private
|
|
253
|
+
Skill directories, or `FORGERELAY_AGENT_DIR/skills`.
|
|
252
254
|
|
|
253
|
-
Same-named collisions use
|
|
255
|
+
Same-named collisions use source-list order: the first discovered Skill name wins and the
|
|
256
|
+
losing source remains visible through diagnostics.
|
|
254
257
|
|
|
255
258
|
When a task matches an advertised skill, read its `SKILL.md` before using other
|
|
256
259
|
files in the skill directory.
|
package/docs/configuration.md
CHANGED
|
@@ -888,21 +888,43 @@ Payload 用于策略和自动化,不包含文件正文、native-file credentia
|
|
|
888
888
|
|
|
889
889
|
Hook 命令与 ForgeRelay 使用同一个本地用户权限。项目 `.forgerelay/hooks/*.json` 是可执行项目约定;允许某个 root 后,应把该 root 中的项目 Hook 视为本地开发环境的一部分。详见 [Security Model](security.md)。
|
|
890
890
|
|
|
891
|
-
##
|
|
892
|
-
|
|
893
|
-
ForgeRelay
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
891
|
+
## Agent context sources
|
|
892
|
+
|
|
893
|
+
ForgeRelay resolves Agent instruction and Skill sources through General Config v2.
|
|
894
|
+
The three source fields use normal precedence (`runtime > project-local > project > user > built-in`),
|
|
895
|
+
and each higher-precedence value **replaces** the lower-precedence/default value rather than appending to it.
|
|
896
|
+
`config get`, `config sources`, and `config explain` expose the same effective values and provenance.
|
|
897
|
+
|
|
898
|
+
`systemInstructionsPath` selects exactly one system-instructions file. Its built-in default is
|
|
899
|
+
`~/.agents/AGENTS.md`; `FORGERELAY_SYSTEM_INSTRUCTIONS_PATH` is the runtime override. A missing
|
|
900
|
+
selected file does not prevent Workspace open, but ForgeRelay reports that instruction source as
|
|
901
|
+
unavailable. `~` resolves through the current user's home directory; Workspace-relative paths are
|
|
902
|
+
resolved from the current Workspace root.
|
|
903
|
+
|
|
904
|
+
`instructionNames` controls hierarchical project-instruction discovery. Its built-in effective value is
|
|
905
|
+
only `["AGENTS.md"]`; files such as `CLAUDE.md` or `GEMINI.md` are discovered only when explicitly
|
|
906
|
+
listed. `FORGERELAY_INSTRUCTION_NAMES` accepts a comma-separated runtime replacement list. Initial
|
|
907
|
+
nested discovery checks direct child directories; deeper matching instruction files are discovered lazily
|
|
908
|
+
when a Workspace path is first accessed. Reads surface newly discovered instructions inline, while
|
|
909
|
+
side-effecting file/shell operations stop before execution and require a retry if that access discovers new
|
|
910
|
+
local instructions.
|
|
911
|
+
|
|
912
|
+
`skillPaths` is an ordered source list. Its built-in effective value is:
|
|
913
|
+
|
|
914
|
+
```json
|
|
915
|
+
["~/.agents/skills", "./.agents/skills"]
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
`FORGERELAY_SKILL_PATHS` accepts a comma-separated runtime **replacement** list. Project and Project
|
|
919
|
+
Local config may select another complete list as well. Missing configured directories are skipped quietly;
|
|
920
|
+
existing unreadable/invalid Skills and same-name collisions remain diagnosable. If the same Skill name
|
|
921
|
+
appears in multiple configured sources, the first source in the effective list wins. ForgeRelay does not
|
|
922
|
+
implicitly scan `~/.forgerelay/skills`, `<project>/.forgerelay/skills`, provider-private Skill directories, or
|
|
923
|
+
`FORGERELAY_AGENT_DIR/skills`.
|
|
924
|
+
|
|
925
|
+
`forgerelay init` exposes these three fields with direct input prompts. Pressing Enter accepts the displayed
|
|
926
|
+
current/default value; accepting the built-in default does not serialize a redundant override into
|
|
927
|
+
`config.json`.
|
|
906
928
|
|
|
907
929
|
## Skills and subagents
|
|
908
930
|
|
|
@@ -911,18 +933,9 @@ select a global instruction file; its `skills` child is an additional Agent Skil
|
|
|
911
933
|
| `FORGERELAY_SKILLS` | Set to `0` to hide skills. Enabled by default. |
|
|
912
934
|
| `FORGERELAY_SUBAGENTS` | Set to `1` to expose configured subagent profiles. |
|
|
913
935
|
| `FORGERELAY_AGENT_DIR` | Defaults to `~/.codex`; used by supported Agent integrations, not as an automatic Skill source. |
|
|
914
|
-
| `
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
- project `.agents/skills`
|
|
919
|
-
- project `.forgerelay/skills`
|
|
920
|
-
- the active ForgeRelay config directory's `skills` folder (`~/.forgerelay/skills` by default)
|
|
921
|
-
- paths explicitly added through `FORGERELAY_SKILL_PATHS`
|
|
922
|
-
|
|
923
|
-
Project `.agents/skills` belongs to the open Agent Skills ecosystem and may contain files or symlinks installed by other Agent tooling. ForgeRelay-owned project/system Skills stay under `.forgerelay/skills` and the active ForgeRelay config directory. Global Agent runtime directories such as `~/.agents/skills` and `FORGERELAY_AGENT_DIR/skills` are not scanned automatically.
|
|
924
|
-
|
|
925
|
-
When the same Skill name appears in more than one source, the first source wins: project Agent Skills override project ForgeRelay Skills, which override system ForgeRelay Skills and explicit additional paths.
|
|
936
|
+
| `FORGERELAY_SYSTEM_INSTRUCTIONS_PATH` | Runtime replacement for the selected system instruction file. |
|
|
937
|
+
| `FORGERELAY_INSTRUCTION_NAMES` | Runtime comma-separated replacement list for project instruction basenames. |
|
|
938
|
+
| `FORGERELAY_SKILL_PATHS` | Runtime comma-separated replacement list for Skill source directories. |
|
|
926
939
|
|
|
927
940
|
When subagents are enabled, canonical v1.2 profiles are discovered from:
|
|
928
941
|
|
package/docs/gotchas.md
CHANGED
|
@@ -192,14 +192,16 @@ Skills are enabled by default. Check:
|
|
|
192
192
|
FORGERELAY_SKILLS=1 forgerelay serve
|
|
193
193
|
```
|
|
194
194
|
|
|
195
|
-
|
|
195
|
+
The built-in `skillPaths` list is:
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
197
|
+
```text
|
|
198
|
+
~/.agents/skills
|
|
199
|
+
./.agents/skills
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The first entry is user-home scoped; the second is relative to the current Workspace root. Any User, Project, Project Local, or runtime `skillPaths` value replaces that complete list. `FORGERELAY_SKILL_PATHS` is the comma-separated runtime replacement.
|
|
201
203
|
|
|
202
|
-
|
|
204
|
+
If Skills are still missing, inspect the effective source with `forgerelay config get`, `forgerelay config sources`, or `forgerelay config explain config.skillPaths`. ForgeRelay does **not** implicitly scan `~/.forgerelay/skills`, project `.forgerelay/skills`, provider-private Skill directories, or `FORGERELAY_AGENT_DIR/skills`.
|
|
203
205
|
|
|
204
206
|
## Subagent profiles do not appear
|
|
205
207
|
|
package/docs/roadmap.md
CHANGED
|
@@ -189,7 +189,7 @@ capability
|
|
|
189
189
|
- completed background process 的 Agent 可消费状态最多保留 5 分钟;底层 ChildProcess/PTY handle 在退出时立即释放,completed notice 数量与 active process 数量都有硬上限,并缩小单 process 输出驻留预算;
|
|
190
190
|
- 高输出 head/tail buffer 保留 Unicode code-point 语义,但不再通过 `Array.from(整段输出)` 构造巨型临时数组,降低 V8 heap 扩容与 GC 压力;
|
|
191
191
|
- MCP transport registry 与 review checkpoint state 加入容量边界;正常 transport close/workspace close 仍立即释放,异常遗弃对象不能再无限累积;OAuth 过期 authorization code 也会主动淘汰;
|
|
192
|
-
- `open_workspace` 的 instruction discovery 首轮只检查 root 与直接子目录,不再递归整棵 workspace
|
|
192
|
+
- `open_workspace` 的 instruction discovery 首轮只检查 root 与直接子目录,不再递归整棵 workspace。更深层只匹配 effective `instructionNames`(当前 built-in default 只有 `AGENTS.md`,其他 basename 需显式配置),在 Agent 首次访问对应路径时沿祖先目录惰性发现,并缓存已扫描目录;read 可直接携带新发现指令,write/edit/rename/delete/bash 等副作用调用则在执行前返回指令并要求重试;
|
|
193
193
|
- Workspace SQLite 继续作为本地持久化真源,不引入 Redis/PostgreSQL/Docker。高频 session/conversation `lastUsedAt` touch 进入内存 write-behind cache,最多每 5 分钟事务批量 flush,normal shutdown 再显式 flush;create/close/status 等语义性状态仍同步持久化;
|
|
194
194
|
- debug runtime telemetry 定期报告 RSS/heap、transport、process、workspace cache 与 review state 数量,为后续真实实例资源趋势提供可观测性。
|
|
195
195
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akira-tl/forgerelay",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.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",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"release:push-ready": "node scripts/release/push-ready.mjs",
|
|
69
69
|
"postinstall": "node scripts/fix-node-pty-permissions.mjs",
|
|
70
70
|
"start": "node dist/cli.js serve",
|
|
71
|
-
"test": "node --test scripts/debug/runtime.test.mjs scripts/config/schema-text.test.mjs scripts/release-proof.test.mjs scripts/release/release-gate.test.mjs scripts/release/push-ready.test.mjs scripts/release/release-version.test.mjs && tsx src/mcp/oauth/router.test.ts && tsx src/workspaces/relay/auth/remote-auth-cli.test.ts && tsx src/workspaces/relay/auth/remote-ssh-auth-cli.test.ts && tsx src/workspaces/relay/tests/lifecycle.test.ts && tsx src/workspaces/relay/tests/routing.test.ts && tsx src/workspaces/relay/tests/ssh.test.ts && tsx src/workspaces/relay/tests/process.test.ts && tsx src/workspaces/relay/tests/recovery.test.ts && tsx src/workspaces/relay/tests/checkpoint.test.ts && tsx src/workspaces/relay/tests/composite.test.ts && tsx src/runtime/config/definition/schema.test.ts && tsx src/runtime/config/resolution/resolver.test.ts && tsx src/runtime/security/project-execution-trust.test.ts && tsx src/runtime/config/resolution/hooks.test.ts && tsx src/workspaces/state/project-context.test.ts && tsx src/runtime/config/config.test.ts && tsx src/runtime/config/external-mcp-registry.test.ts && tsx src/runtime/config/external-mcp-auth-store.test.ts && tsx src/mcp/operations/external-mcp/external-mcp-oauth.test.ts && tsx src/cli/mcp/status.test.ts && tsx src/cli/mcp/diagnostics.test.ts && tsx src/cli/mcp/external-mcp.test.ts && tsx src/runtime/shell/command-shell-runtime.test.ts && tsx src/runtime/instructions/shell-instructions.test.ts && tsx src/runtime/instructions/powershell-skill.test.ts && tsx src/cli/shell/setup.test.ts && tsx src/lsp/language-server-config.test.ts && tsx src/lsp/runtime/managed-language-servers.test.ts && tsx src/lsp/normalization/hover.test.ts && tsx src/lsp/references.server.test.ts && tsx src/lsp/operations/document-symbols.server.test.ts && tsx src/lsp/operations/workspace-symbols.server.test.ts && tsx src/lsp/operations/diagnostics-push.server.test.ts && tsx src/lsp/operations/diagnostics-pull.server.test.ts && tsx src/lsp/operations/request-hardening.server.test.ts && tsx src/lsp/operations/recovery.server.test.ts && tsx src/lsp/operations/lifecycle.server.test.ts && tsx src/lsp/operations/project-trust.server.test.ts && tsx src/lsp/runtime/semantic-requests.test.ts && tsx src/runtime/logging/logger.test.ts && tsx src/runtime/logging/proxy-trust.test.ts && tsx src/runtime/state/lock/file-lock.test.ts && tsx src/runtime/state/runtime-lease.test.ts && tsx src/mcp/panel/mcp-app-template.test.ts && tsx src/mcp/hooks/hooks.test.ts && tsx src/mcp/hooks/hooks-trust.test.ts && tsx src/mcp/operations/external-mcp/external-mcp-transform.test.ts && tsx src/mcp/hooks/external-mcp-transform-trust.test.ts && tsx src/mcp/operations/external-mcp/external-mcp-trust.test.ts && tsx src/mcp/server/core/capability-registry.test.ts && tsx src/mcp/server-instructions.test.ts && tsx src/mcp/request-context.test.ts && tsx src/mcp/request-meta.test.ts && tsx src/mcp/artifacts/incoming-artifacts.test.ts && tsx src/mcp/artifacts/artifact-download.test.ts && tsx src/ui/core/card-types.test.ts && tsx src/ui/activity/model.test.ts && tsx src/ui/activity/detail-card.test.ts && tsx src/ui/review/patch-display.test.ts && tsx src/ui/core/tool-display.test.ts && tsx src/mcp/filesystem/apply-patch.test.ts && tsx src/mcp/process/process-platform.test.ts && tsx src/mcp/process/process-sessions.test.ts && tsx src/mcp/server/transport/mcp-sessions.test.ts && tsx src/mcp/server/transport/server-shutdown.test.ts && tsx src/mcp/server/operations/mutation-diagnostics.test.ts && tsx src/mcp/server/operations/hooks.test.ts && tsx src/subagents/providers/adapters/codex.test.ts && tsx src/subagents/providers/adapters/pi.test.ts && tsx src/subagents/providers/registry.test.ts && tsx src/subagents/providers/availability.test.ts && tsx src/subagents/profiles.test.ts && tsx src/subagents/cli-target.test.ts && tsx src/subagents/sessions/store.test.ts && tsx src/subagents/sessions/manager.test.ts && tsx src/subagents/sessions/project-trust.test.ts && tsx src/subagents/sessions/mcp/capability.server.test.ts && tsx src/subagents/sessions/mcp/continuation.server.test.ts && tsx src/subagents/sessions/mcp/lifecycle.server.test.ts && tsx src/subagents/sessions/mcp/reconciliation.server.test.ts && tsx src/subagents/sessions/mcp/routing.server.test.ts && tsx src/mcp/filesystem/roots.test.ts && tsx src/mcp/filesystem/file-mutations.test.ts && tsx src/mcp/operations/edit-preflight.test.ts && tsx src/workspaces/resources/skills.test.ts && tsx src/runtime/state/db/migrations.test.ts && tsx src/workspaces/state/workspace-store.test.ts && tsx src/workspaces/tasks/workspace-tasks.test.ts && tsx src/workspaces/tasks/workspace-task-reminders.test.ts && tsx src/activity/history/audit-store.test.ts && tsx src/activity/history/bash-output-store.test.ts && tsx src/activity/runtime/lifecycle.test.ts && tsx src/activity/history/query-service.test.ts && tsx src/mcp/operations/core-operation-executor.test.ts && tsx src/mcp/operations/bulk-mutation.test.ts && tsx src/mcp/operations/batch/scheduler.test.ts && tsx src/mcp/operations/batch/executor-policy.test.ts && tsx src/workspaces.test.ts && tsx src/workspaces/conversation-checkout.test.ts && tsx src/workspaces/conversation-worktree.test.ts && tsx src/workspaces/git/worktree-recovery.test.ts && tsx src/mcp/server/workspace/workspace-inventory.test.ts && tsx src/mcp/server/workspace/workspace-recovery.test.ts && tsx src/mcp/server/workspace/workspace-checkpoint.test.ts && tsx src/workspaces/review/review-checkpoints.test.ts && tsx src/lsp/code-intelligence.server.test.ts && npm run build:app && tsx src/mcp/process/server.test.ts && tsx src/mcp/panel/server.test.ts && tsx src/mcp/server/server.test.ts && tsx src/mcp/oauth/oauth-store.test.ts && tsx src/cli/maintenance.test.ts && tsx src/cli/maintenance-prune.test.ts && tsx src/cli/cli.test.ts",
|
|
71
|
+
"test": "node --test scripts/debug/runtime.test.mjs scripts/config/schema-text.test.mjs scripts/release-proof.test.mjs scripts/release/release-gate.test.mjs scripts/release/push-ready.test.mjs scripts/release/release-version.test.mjs && tsx src/mcp/oauth/router.test.ts && tsx src/workspaces/relay/auth/remote-auth-cli.test.ts && tsx src/workspaces/relay/auth/remote-ssh-auth-cli.test.ts && tsx src/workspaces/relay/tests/lifecycle.test.ts && tsx src/workspaces/relay/tests/routing.test.ts && tsx src/workspaces/relay/tests/ssh.test.ts && tsx src/workspaces/relay/tests/process.test.ts && tsx src/workspaces/relay/tests/recovery.test.ts && tsx src/workspaces/relay/tests/checkpoint.test.ts && tsx src/workspaces/relay/tests/composite.test.ts && tsx src/runtime/config/definition/schema.test.ts && tsx src/runtime/config/resolution/resolver.test.ts && tsx src/runtime/security/project-execution-trust.test.ts && tsx src/runtime/config/resolution/hooks.test.ts && tsx src/workspaces/state/project-context.test.ts && tsx src/runtime/config/config.test.ts && tsx src/cli/config/general.test.ts && tsx src/cli/config/domains/domain-cli.test.ts && node --test --import tsx src/cli/config/domains/context-cli.test.ts && node --test --import tsx src/cli/config/inspect.test.ts src/cli/config/migrate.test.ts && tsx src/runtime/config/external-mcp-registry.test.ts && tsx src/runtime/config/external-mcp-auth-store.test.ts && tsx src/mcp/operations/external-mcp/external-mcp-oauth.test.ts && tsx src/cli/mcp/status.test.ts && tsx src/cli/mcp/diagnostics.test.ts && tsx src/cli/mcp/external-mcp.test.ts && tsx src/runtime/shell/command-shell-runtime.test.ts && tsx src/runtime/instructions/shell-instructions.test.ts && tsx src/runtime/instructions/powershell-skill.test.ts && tsx src/cli/shell/setup.test.ts && tsx src/lsp/language-server-config.test.ts && tsx src/lsp/runtime/managed-language-servers.test.ts && tsx src/lsp/normalization/hover.test.ts && tsx src/lsp/references.server.test.ts && tsx src/lsp/operations/document-symbols.server.test.ts && tsx src/lsp/operations/workspace-symbols.server.test.ts && tsx src/lsp/operations/diagnostics-push.server.test.ts && tsx src/lsp/operations/diagnostics-pull.server.test.ts && tsx src/lsp/operations/request-hardening.server.test.ts && tsx src/lsp/operations/recovery.server.test.ts && tsx src/lsp/operations/lifecycle.server.test.ts && tsx src/lsp/operations/project-trust.server.test.ts && tsx src/lsp/runtime/semantic-requests.test.ts && tsx src/runtime/logging/logger.test.ts && tsx src/runtime/logging/proxy-trust.test.ts && tsx src/runtime/state/lock/file-lock.test.ts && tsx src/runtime/state/runtime-lease.test.ts && tsx src/cli/system/status.test.ts && tsx src/mcp/panel/mcp-app-template.test.ts && tsx src/mcp/hooks/hooks.test.ts && tsx src/mcp/hooks/hooks-trust.test.ts && tsx src/mcp/operations/external-mcp/external-mcp-transform.test.ts && tsx src/mcp/hooks/external-mcp-transform-trust.test.ts && tsx src/mcp/operations/external-mcp/external-mcp-trust.test.ts && tsx src/mcp/server/core/capability-registry.test.ts && tsx src/mcp/server-instructions.test.ts && tsx src/mcp/request-context.test.ts && tsx src/mcp/request-meta.test.ts && tsx src/mcp/artifacts/incoming-artifacts.test.ts && tsx src/mcp/artifacts/artifact-download.test.ts && tsx src/ui/core/card-types.test.ts && tsx src/ui/activity/model.test.ts && tsx src/ui/activity/detail-card.test.ts && tsx src/ui/review/patch-display.test.ts && tsx src/ui/core/tool-display.test.ts && tsx src/mcp/filesystem/apply-patch.test.ts && tsx src/mcp/process/process-platform.test.ts && tsx src/mcp/process/process-sessions.test.ts && tsx src/mcp/server/transport/mcp-sessions.test.ts && tsx src/mcp/server/transport/server-shutdown.test.ts && tsx src/mcp/server/operations/mutation-diagnostics.test.ts && tsx src/mcp/server/operations/hooks.test.ts && tsx src/subagents/providers/adapters/codex.test.ts && tsx src/subagents/providers/adapters/pi.test.ts && tsx src/subagents/providers/registry.test.ts && tsx src/subagents/providers/availability.test.ts && tsx src/subagents/profiles.test.ts && tsx src/subagents/cli-target.test.ts && tsx src/subagents/sessions/store.test.ts && tsx src/subagents/sessions/manager.test.ts && tsx src/subagents/sessions/project-trust.test.ts && tsx src/subagents/sessions/mcp/capability.server.test.ts && tsx src/subagents/sessions/mcp/continuation.server.test.ts && tsx src/subagents/sessions/mcp/lifecycle.server.test.ts && tsx src/subagents/sessions/mcp/reconciliation.server.test.ts && tsx src/subagents/sessions/mcp/routing.server.test.ts && tsx src/mcp/filesystem/roots.test.ts && tsx src/mcp/filesystem/file-mutations.test.ts && tsx src/mcp/operations/edit-preflight.test.ts && tsx src/workspaces/resources/skills.test.ts && tsx src/runtime/state/db/migrations.test.ts && tsx src/workspaces/state/workspace-store.test.ts && tsx src/workspaces/tasks/workspace-tasks.test.ts && tsx src/workspaces/tasks/workspace-task-reminders.test.ts && tsx src/activity/history/audit-store.test.ts && tsx src/activity/history/bash-output-store.test.ts && tsx src/activity/runtime/lifecycle.test.ts && tsx src/activity/history/query-service.test.ts && tsx src/mcp/operations/core-operation-executor.test.ts && tsx src/mcp/operations/bulk-mutation.test.ts && tsx src/mcp/operations/batch/scheduler.test.ts && tsx src/mcp/operations/batch/executor-policy.test.ts && tsx src/workspaces.test.ts && tsx src/workspaces/conversation-checkout.test.ts && tsx src/workspaces/conversation-worktree.test.ts && tsx src/workspaces/git/worktree-recovery.test.ts && tsx src/mcp/server/workspace/workspace-inventory.test.ts && tsx src/mcp/server/workspace/workspace-recovery.test.ts && tsx src/mcp/server/workspace/workspace-checkpoint.test.ts && tsx src/workspaces/review/review-checkpoints.test.ts && tsx src/lsp/code-intelligence.server.test.ts && npm run build:app && tsx src/mcp/process/server.test.ts && tsx src/mcp/panel/server.test.ts && tsx src/mcp/server/server.test.ts && tsx src/mcp/oauth/oauth-store.test.ts && tsx src/cli/maintenance.test.ts && tsx src/cli/maintenance-prune.test.ts && tsx src/cli/cli.test.ts",
|
|
72
72
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
73
73
|
"release:check": "node scripts/release-version.mjs check",
|
|
74
74
|
"release:tag-check": "node scripts/release-version.mjs tag",
|
|
@@ -5,6 +5,80 @@
|
|
|
5
5
|
"$schema": {
|
|
6
6
|
"description": "Editor-only JSON Schema URL. Ignored by ForgeRelay resolution.",
|
|
7
7
|
"type": "string"
|
|
8
|
+
},
|
|
9
|
+
"systemInstructionsPath": {
|
|
10
|
+
"description": "Selected system-level Agent instruction file consumed by ForgeRelay.",
|
|
11
|
+
"type": "string",
|
|
12
|
+
"minLength": 1,
|
|
13
|
+
"default": "~/.agents/AGENTS.md",
|
|
14
|
+
"x-forgerelay-scopes": [
|
|
15
|
+
"runtime",
|
|
16
|
+
"project-local",
|
|
17
|
+
"project",
|
|
18
|
+
"user",
|
|
19
|
+
"built-in"
|
|
20
|
+
],
|
|
21
|
+
"x-forgerelay-merge": "replace",
|
|
22
|
+
"x-forgerelay-reload": "hot",
|
|
23
|
+
"x-forgerelay-sensitivity": "public",
|
|
24
|
+
"x-forgerelay-interpolation": "none",
|
|
25
|
+
"x-forgerelay-execution-effect": "none",
|
|
26
|
+
"x-forgerelay-runtime-override": {
|
|
27
|
+
"env": "FORGERELAY_SYSTEM_INSTRUCTIONS_PATH"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"instructionNames": {
|
|
31
|
+
"description": "Project instruction basenames discovered hierarchically within a Workspace.",
|
|
32
|
+
"type": "array",
|
|
33
|
+
"items": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"minLength": 1
|
|
36
|
+
},
|
|
37
|
+
"default": [
|
|
38
|
+
"AGENTS.md"
|
|
39
|
+
],
|
|
40
|
+
"x-forgerelay-scopes": [
|
|
41
|
+
"runtime",
|
|
42
|
+
"project-local",
|
|
43
|
+
"project",
|
|
44
|
+
"user",
|
|
45
|
+
"built-in"
|
|
46
|
+
],
|
|
47
|
+
"x-forgerelay-merge": "replace",
|
|
48
|
+
"x-forgerelay-reload": "hot",
|
|
49
|
+
"x-forgerelay-sensitivity": "public",
|
|
50
|
+
"x-forgerelay-interpolation": "none",
|
|
51
|
+
"x-forgerelay-execution-effect": "none",
|
|
52
|
+
"x-forgerelay-runtime-override": {
|
|
53
|
+
"env": "FORGERELAY_INSTRUCTION_NAMES"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"skillPaths": {
|
|
57
|
+
"description": "Ordered Agent Skill source directories; explicit higher-precedence lists replace lower-precedence lists.",
|
|
58
|
+
"type": "array",
|
|
59
|
+
"items": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"minLength": 1
|
|
62
|
+
},
|
|
63
|
+
"default": [
|
|
64
|
+
"~/.agents/skills",
|
|
65
|
+
"./.agents/skills"
|
|
66
|
+
],
|
|
67
|
+
"x-forgerelay-scopes": [
|
|
68
|
+
"runtime",
|
|
69
|
+
"project-local",
|
|
70
|
+
"project",
|
|
71
|
+
"user",
|
|
72
|
+
"built-in"
|
|
73
|
+
],
|
|
74
|
+
"x-forgerelay-merge": "replace",
|
|
75
|
+
"x-forgerelay-reload": "hot",
|
|
76
|
+
"x-forgerelay-sensitivity": "public",
|
|
77
|
+
"x-forgerelay-interpolation": "none",
|
|
78
|
+
"x-forgerelay-execution-effect": "none",
|
|
79
|
+
"x-forgerelay-runtime-override": {
|
|
80
|
+
"env": "FORGERELAY_SKILL_PATHS"
|
|
81
|
+
}
|
|
8
82
|
}
|
|
9
83
|
},
|
|
10
84
|
"additionalProperties": false,
|