@akira-tl/forgerelay 0.8.5 → 0.8.7

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +8 -10
  3. package/dist/activity/audit-store.js +44 -6
  4. package/dist/activity/mcp-query-tools.js +53 -36
  5. package/dist/activity/query-service.js +41 -11
  6. package/dist/cli.js +16 -17
  7. package/dist/composite-activity.js +124 -33
  8. package/dist/config.js +8 -13
  9. package/dist/db/migrations.js +9 -0
  10. package/dist/db/schema.js +1 -0
  11. package/dist/lsp/test-support/server-fixture.js +7 -7
  12. package/dist/mcp/server-instructions.js +2 -2
  13. package/dist/process-sessions.js +2 -2
  14. package/dist/remote-auth.js +11 -1
  15. package/dist/remote-mcp-connection-pool.js +90 -0
  16. package/dist/remote-transport.js +28 -14
  17. package/dist/remote-workspace-relay.js +53 -23
  18. package/dist/server.js +105 -70
  19. package/dist/skills.js +1 -1
  20. package/dist/subagents/profiles.js +1 -2
  21. package/dist/subagents/providers/adapters/pi.js +2 -2
  22. package/dist/subagents/providers/availability.js +2 -2
  23. package/dist/subagents/providers/path.js +4 -4
  24. package/dist/ui/.vite/manifest.json +32 -32
  25. package/dist/ui/activity-panel-app.html +3 -3
  26. package/dist/ui/assets/{activity-panel-app-E1ju2dqI.js → activity-panel-app-CUAN6zyW.js} +1 -1
  27. package/dist/ui/assets/{heavy-payload-CeW-n9w5.js → heavy-payload-CgzrutLm.js} +1 -1
  28. package/dist/ui/assets/{review-payload-B9CO298v.js → review-payload-BrLbezbq.js} +1 -1
  29. package/dist/ui/assets/{scrollbar-C2twAENW.js → scrollbar-CbhpdW05.js} +1 -1
  30. package/dist/ui/assets/workspace-app-Bhj96tsR.js +1 -0
  31. package/dist/ui/assets/{workspace-app-CwbJnb_w.js → workspace-app-CxwJuZyS.js} +1 -1
  32. package/dist/ui/assets/{workspace-app-BztEvZIC.js → workspace-app-D6UR0AFl.js} +3 -3
  33. package/dist/ui/assets/workspace-app-ldjBmCJR.css +1 -0
  34. package/dist/ui/assets/workspace-lifecycle-app-Cqfhx9pV.js +1 -0
  35. package/dist/ui/workspace-app.html +4 -4
  36. package/dist/ui/workspace-lifecycle-app.html +4 -4
  37. package/dist/user-config.js +3 -19
  38. package/dist/workspace-presentation.js +69 -0
  39. package/dist/workspace-store.js +29 -1
  40. package/dist/workspaces.js +59 -19
  41. package/docs/agent-profile-schema.md +4 -9
  42. package/docs/artifact-exchange.md +2 -1
  43. package/docs/chatgpt-coding-workflow.md +21 -19
  44. package/docs/configuration.md +34 -39
  45. package/docs/gotchas.md +10 -7
  46. package/docs/roadmap.md +11 -4
  47. package/docs/security.md +3 -2
  48. package/docs/setup.md +8 -5
  49. package/docs/versioning.md +1 -1
  50. package/package.json +3 -2
  51. package/scripts/debug/accept.mjs +7 -1
  52. package/scripts/debug/relay-accept.mjs +0 -1
  53. package/scripts/debug/runtime.mjs +0 -4
  54. package/scripts/debug/runtime.test.mjs +0 -2
  55. package/scripts/debug/traffic/run.sh +5 -0
  56. package/scripts/debug/traffic/traffic-audit.mjs +907 -0
  57. package/scripts/release/push-ready.mjs +124 -0
  58. package/scripts/release/push-ready.test.mjs +108 -0
  59. package/scripts/release/release-gate.test.mjs +1 -0
  60. package/scripts/release-proof.mjs +16 -1
  61. package/dist/ui/assets/workspace-app-YnUST8IP.css +0 -1
  62. package/dist/ui/assets/workspace-app-rKuhdae8.js +0 -1
  63. package/dist/ui/assets/workspace-lifecycle-app-CEfMdudP.js +0 -1
@@ -2,7 +2,8 @@ import { randomBytes } from "node:crypto";
2
2
  import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
5
- import { isRemoteMcpUnauthorized, refreshRemoteAuthentication, withRemoteMcpClient, } from "./remote-auth.js";
5
+ import { isRemoteMcpUnauthorized, refreshRemoteAuthentication, } from "./remote-auth.js";
6
+ import { RemoteMcpConnectionPool } from "./remote-mcp-connection-pool.js";
6
7
  import { withRemoteServiceEndpoint } from "./remote-transport.js";
7
8
  import { loadForgeRelayFiles, writeForgeRelayRemote, } from "./user-config.js";
8
9
  const ROUTE_LOCK_RETRY_MS = 10;
@@ -15,6 +16,7 @@ export class RemoteWorkspaceRelay {
15
16
  authEnv;
16
17
  routeStateDir;
17
18
  routeStatePath;
19
+ mcpConnections = new RemoteMcpConnectionPool();
18
20
  constructor(configDir, stateDir) {
19
21
  this.authEnv = { FORGERELAY_CONFIG_DIR: configDir };
20
22
  this.routeStateDir = stateDir;
@@ -27,6 +29,9 @@ export class RemoteWorkspaceRelay {
27
29
  this.loadRoutes();
28
30
  return this.routes.has(workspaceId);
29
31
  }
32
+ async shutdown() {
33
+ await this.mcpConnections.closeAll();
34
+ }
30
35
  async inspectWorkspace(gatewayWorkspaceId) {
31
36
  const route = this.requireRoute(gatewayWorkspaceId);
32
37
  const resolved = this.remoteByInstance(route.remoteInstanceId);
@@ -235,11 +240,14 @@ export class RemoteWorkspaceRelay {
235
240
  throw sanitizedRemoteError(error, route.remoteWorkspaceId, gatewayWorkspaceId);
236
241
  }
237
242
  }
243
+ async activityIndex(turnId, knownRevision, conversationScopeId) {
244
+ return this.callTurnTool(turnId, "activity_index", { turnId, ...(knownRevision !== undefined ? { knownRevision } : {}) }, conversationScopeId);
245
+ }
238
246
  async activityDetail(turnId, activityId, conversationScopeId) {
239
247
  return this.callTurnTool(turnId, "activity_detail", { turnId, activityId }, conversationScopeId);
240
248
  }
241
- async activityOutput(turnId, outputId, conversationScopeId) {
242
- return this.callTurnTool(turnId, "activity_output", { turnId, outputId }, conversationScopeId);
249
+ async activityOutput(turnId, outputId, conversationScopeId, cursor) {
250
+ return this.callTurnTool(turnId, "activity_output", { turnId, outputId, ...(cursor !== undefined ? { cursor } : {}) }, conversationScopeId);
243
251
  }
244
252
  async callTurnTool(turnId, name, args, conversationScopeId) {
245
253
  const gatewayWorkspaceId = this.turnRoutes.get(turnId);
@@ -464,31 +472,53 @@ export class RemoteWorkspaceRelay {
464
472
  return { alias: entry[0], remote: entry[1] };
465
473
  }
466
474
  async callRemoteTool(alias, initialRemote, name, args, conversationScopeId) {
467
- return withRemoteServiceEndpoint(initialRemote.target, initialRemote.sshRoute, async (endpoint) => {
468
- let remote = initialRemote;
469
- let refreshed = false;
470
- if (remote.accessTokenExpiresAt <= Math.floor(Date.now() / 1000)) {
471
- remote = await this.refreshRemote(alias, remote, endpoint);
475
+ let remote = initialRemote;
476
+ let refreshed = false;
477
+ if (remote.accessTokenExpiresAt <= Math.floor(Date.now() / 1000)) {
478
+ remote = await withRemoteServiceEndpoint(remote.target, remote.sshRoute, (endpoint) => this.refreshRemote(alias, remote, endpoint));
479
+ refreshed = true;
480
+ }
481
+ let connection;
482
+ try {
483
+ connection = await this.mcpConnections.get(remote);
484
+ }
485
+ catch (error) {
486
+ if (!refreshed && isRemoteMcpUnauthorized(error)) {
487
+ remote = await withRemoteServiceEndpoint(remote.target, remote.sshRoute, (endpoint) => this.refreshRemote(alias, remote, endpoint));
472
488
  refreshed = true;
489
+ connection = await this.mcpConnections.get(remote);
473
490
  }
474
- const invoke = () => withRemoteMcpClient(remote, endpoint, async (client) => CallToolResultSchema.parse(await client.callTool({
475
- name,
476
- arguments: args,
477
- ...(conversationScopeId
478
- ? { _meta: { "openai/session": conversationScopeId } }
479
- : {}),
480
- })));
481
- try {
482
- return await invoke();
491
+ else {
492
+ throw new Error(`Remote ForgeRelay ${alias} request failed: ${errorMessage(error)}`, { cause: error });
483
493
  }
484
- catch (error) {
485
- if (!refreshed && isRemoteMcpUnauthorized(error)) {
486
- remote = await this.refreshRemote(alias, remote, endpoint);
487
- return invoke();
494
+ }
495
+ const invoke = async (active) => CallToolResultSchema.parse(await active.client.callTool({
496
+ name,
497
+ arguments: args,
498
+ ...(conversationScopeId
499
+ ? { _meta: { "openai/session": conversationScopeId } }
500
+ : {}),
501
+ }));
502
+ try {
503
+ return await invoke(connection);
504
+ }
505
+ catch (error) {
506
+ if (!refreshed && isRemoteMcpUnauthorized(error)) {
507
+ remote = await this.refreshRemote(alias, remote, connection.endpoint);
508
+ refreshed = true;
509
+ await this.mcpConnections.invalidate(remote.instanceId, connection);
510
+ const refreshedConnection = await this.mcpConnections.get(remote);
511
+ try {
512
+ return await invoke(refreshedConnection);
513
+ }
514
+ catch (retryError) {
515
+ await this.mcpConnections.invalidate(remote.instanceId, refreshedConnection);
516
+ throw new Error(`Remote ForgeRelay ${alias} request failed: ${errorMessage(retryError)}`, { cause: retryError });
488
517
  }
489
- throw new Error(`Remote ForgeRelay ${alias} request failed: ${errorMessage(error)}`, { cause: error });
490
518
  }
491
- });
519
+ await this.mcpConnections.invalidate(remote.instanceId, connection);
520
+ throw new Error(`Remote ForgeRelay ${alias} request failed: ${errorMessage(error)}`, { cause: error });
521
+ }
492
522
  }
493
523
  async refreshRemote(alias, remote, endpoint) {
494
524
  const refreshed = await refreshRemoteAuthentication(remote, endpoint);
package/dist/server.js CHANGED
@@ -55,7 +55,8 @@ import { formatPathForPrompt } from "./skills.js";
55
55
  import { createWorkspaceStore } from "./workspace-store.js";
56
56
  import { WorkspaceTaskReminderTracker } from "./workspace-task-reminders.js";
57
57
  import { WorkspaceTaskStore } from "./workspace-tasks.js";
58
- import { formatAgentsPath, WorkspaceRegistry } from "./workspaces.js";
58
+ import { compactWorkspacePresentation } from "./workspace-presentation.js";
59
+ import { formatAgentsPath, WorkspaceRegistry, } from "./workspaces.js";
59
60
  import { formatAvailableSubagentProfile, summarizeSubagentProfile } from "./subagents/profiles.js";
60
61
  import { formatSubagentProviderAvailabilitySummary, formatUnavailableSubagentProvider, getSubagentProviderAvailabilitySnapshot, } from "./subagents/providers/availability.js";
61
62
  import { capabilityActivityAuditRequest, capabilityActivityAuditResult } from "./subagents/sessions/mcp/audit.js";
@@ -1342,6 +1343,7 @@ function registerProcessTools(server, config, workspaces, processSessions, hooks
1342
1343
  }
1343
1344
  export function createMcpServer(config, workspaces, reviewCheckpoints, processSessions, subagentProviders, incomingArtifactAdapters, codeIntelligence, activityLifecycle, bashOutputStore, activityQueries, options = {}) {
1344
1345
  const connectionScopeId = `mcp-connection:${randomUUID()}`;
1346
+ const ownsRemoteWorkspaces = options.remoteWorkspaces === undefined;
1345
1347
  const remoteWorkspaces = options.remoteWorkspaces
1346
1348
  ?? new RemoteWorkspaceRelay(config.configDir, config.stateDir);
1347
1349
  const compositeWorkspaces = options.compositeWorkspaces
@@ -1564,6 +1566,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1564
1566
  providerUnavailableReason: availability?.reason,
1565
1567
  };
1566
1568
  });
1569
+ const bootstrapComponents = new Set(opened.bootstrapContextComponents);
1567
1570
  return {
1568
1571
  member: memberName,
1569
1572
  workspaceId: compositeWorkspaceId,
@@ -1573,16 +1576,13 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
1573
1576
  capabilityFingerprint,
1574
1577
  capabilityCatalog,
1575
1578
  includeBootstrapContext: opened.includeBootstrapContext,
1576
- ...(opened.includeBootstrapContext
1577
- ? {
1578
- capabilityGuides,
1579
- agentsFiles,
1580
- availableAgentsFiles,
1581
- skills,
1582
- agentProviders,
1583
- agents,
1584
- skillDiagnostics: redactSkillDiagnosticPaths(workspace.skillDiagnostics),
1585
- }
1579
+ ...(bootstrapComponents.has("capabilityGuides") ? { capabilityGuides } : {}),
1580
+ ...(bootstrapComponents.has("agentsFiles") ? { agentsFiles } : {}),
1581
+ ...(bootstrapComponents.has("availableAgentsFiles") ? { availableAgentsFiles } : {}),
1582
+ ...(bootstrapComponents.has("skills") ? { skills } : {}),
1583
+ ...(bootstrapComponents.has("agentProfiles") ? { agentProviders, agents } : {}),
1584
+ ...(bootstrapComponents.has("skillDiagnostics")
1585
+ ? { skillDiagnostics: redactSkillDiagnosticPaths(workspace.skillDiagnostics) }
1586
1586
  : {}),
1587
1587
  instruction: opened.includeBootstrapContext
1588
1588
  ? `Bootstrap context for Composite member ${memberName}. Keep using Composite workspaceId ${compositeWorkspaceId} and pass member=${memberName} for work operations.`
@@ -2107,19 +2107,21 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
2107
2107
  const workspacePanelStates = new Map();
2108
2108
  const workspacePanelState = (workspaceId) => {
2109
2109
  const remembered = workspacePanelStates.get(workspaceId);
2110
- if (remembered)
2110
+ if (remoteWorkspaces.has(workspaceId) || compositeWorkspaces.has(workspaceId)) {
2111
2111
  return remembered;
2112
+ }
2112
2113
  try {
2113
2114
  const workspace = workspaces.getWorkspace(workspaceId);
2114
- return {
2115
+ if (remembered)
2116
+ return remembered;
2117
+ return compactWorkspacePresentation({
2115
2118
  workspaceId: workspace.id,
2116
2119
  root: workspace.root,
2117
2120
  path: workspace.root,
2118
2121
  mode: workspace.mode,
2119
2122
  sourceRoot: workspace.sourceRoot,
2120
- instruction: `Use workspaceId ${workspace.id} for subsequent calls.`,
2121
2123
  summary: { mode: workspace.mode },
2122
- };
2124
+ });
2123
2125
  }
2124
2126
  catch {
2125
2127
  return undefined;
@@ -2132,7 +2134,11 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
2132
2134
  const card = meta.card;
2133
2135
  if (meta.tool !== toolNames.openWorkspace || typeof card !== "object" || card === null)
2134
2136
  return;
2135
- workspacePanelStates.set(workspaceId, card);
2137
+ const compact = compactWorkspacePresentation(card);
2138
+ workspacePanelStates.set(workspaceId, {
2139
+ ...(workspacePanelStates.get(workspaceId) ?? {}),
2140
+ ...compact,
2141
+ });
2136
2142
  };
2137
2143
  const workspaceAppResourceMetadata = {
2138
2144
  description: "Historical ForgeRelay tool card UI.",
@@ -2880,7 +2886,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
2880
2886
  rememberWorkspacePanelState(opened.workspaceId, response);
2881
2887
  return response;
2882
2888
  }
2883
- const { workspace, agentsFiles, availableAgentsFiles, hookReports, workspaceReused, includeBootstrapContext, contextFingerprint, } = await workspaces.openWorkspace({ path, workspaceId, mode, baseRef, newWorktree, newWorkspace, context }, {
2889
+ const { workspace, agentsFiles, availableAgentsFiles, hookReports, workspaceReused, includeBootstrapContext, bootstrapContextComponents, contextFingerprint, } = await workspaces.openWorkspace({ path, workspaceId, mode, baseRef, newWorktree, newWorkspace, context }, {
2884
2890
  conversationScopeId,
2885
2891
  protectedWorkspaceIds,
2886
2892
  });
@@ -2926,15 +2932,18 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
2926
2932
  const cardAvailableAgentsFiles = availableAgentsFiles.map((file) => ({
2927
2933
  path: formatAgentsPath(file.path, workspace.root),
2928
2934
  }));
2929
- const visibleSkills = includeBootstrapContext ? cardSkills : [];
2930
- const visibleSkillDiagnostics = includeBootstrapContext
2935
+ const bootstrapComponents = new Set(bootstrapContextComponents);
2936
+ const visibleSkills = bootstrapComponents.has("skills") ? cardSkills : [];
2937
+ const visibleSkillDiagnostics = bootstrapComponents.has("skillDiagnostics")
2931
2938
  ? redactSkillDiagnosticPaths(workspace.skillDiagnostics)
2932
2939
  : [];
2933
- const visibleCapabilityGuides = includeBootstrapContext ? capabilityGuides : [];
2934
- const visibleAgentProviders = includeBootstrapContext ? cardAgentProviders : [];
2935
- const visibleAgents = includeBootstrapContext ? cardAgents : [];
2936
- const loadedAgentsFiles = includeBootstrapContext ? cardAgentsFiles : [];
2937
- const availableAgentsFileOutputs = includeBootstrapContext ? cardAvailableAgentsFiles : [];
2940
+ const visibleCapabilityGuides = bootstrapComponents.has("capabilityGuides") ? capabilityGuides : [];
2941
+ const visibleAgentProviders = bootstrapComponents.has("agentProfiles") ? cardAgentProviders : [];
2942
+ const visibleAgents = bootstrapComponents.has("agentProfiles") ? cardAgents : [];
2943
+ const loadedAgentsFiles = bootstrapComponents.has("agentsFiles") ? cardAgentsFiles : [];
2944
+ const availableAgentsFileOutputs = bootstrapComponents.has("availableAgentsFiles")
2945
+ ? cardAvailableAgentsFiles
2946
+ : [];
2938
2947
  const workspaceContextInstruction = "For later open_workspace calls, context=\"auto\" avoids repeating unchanged bootstrap context; use context=\"none\" when only the workspace handle/metadata is needed, or context=\"full\" to force a refresh.";
2939
2948
  const workspaceManagementInstruction = "Use open_workspace(action=\"list\") for lightweight Workspace inventory. Use action=\"inspect\" with one known workspaceId for bounded read-only metadata without opening/resuming it. Explicitly open a Workspace before executing or mutating against it, and ask the user before close_workspace cleanup.";
2940
2949
  const cardInstruction = config.skillsEnabled
@@ -2945,7 +2954,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
2945
2954
  ? [
2946
2955
  `Workspace already exists as ${workspace.id} for this directory.`,
2947
2956
  "Reuse this workspaceId for subsequent tool calls.",
2948
- "The complete project context is included because it has not yet been provided in this conversation or host context.",
2957
+ `Project bootstrap context components included in this response: ${bootstrapContextComponents.join(", ")}. Components not listed are unchanged and are not repeated.`,
2949
2958
  workspaceContextInstruction,
2950
2959
  workspaceManagementInstruction,
2951
2960
  ].join("\n\n")
@@ -3012,41 +3021,44 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
3012
3021
  success: true,
3013
3022
  durationMs: Math.round(performance.now() - startedAt),
3014
3023
  });
3024
+ const workspaceCard = {
3025
+ workspaceId: workspace.id,
3026
+ kind: "workspace",
3027
+ root: workspace.root,
3028
+ path: workspace.root,
3029
+ mode: workspace.mode,
3030
+ workspaceReused,
3031
+ includeBootstrapContext,
3032
+ sourceRoot: workspace.sourceRoot,
3033
+ worktree: workspace.worktree,
3034
+ worktrees: knownWorktrees,
3035
+ staleWorkspaces,
3036
+ capabilityFingerprint,
3037
+ contextFingerprint,
3038
+ capabilityCatalog,
3039
+ agentsFiles: cardAgentsFiles,
3040
+ availableAgentsFiles: cardAvailableAgentsFiles,
3041
+ skills: cardSkills,
3042
+ agentProviders: cardAgentProviders,
3043
+ agents: cardAgents,
3044
+ instruction: cardInstruction,
3045
+ summary: {
3046
+ mode: workspace.mode,
3047
+ agentsFiles: cardAgentsFiles.length,
3048
+ availableAgentsFiles: cardAvailableAgentsFiles.length,
3049
+ skills: cardSkills.length,
3050
+ capabilities: capabilityCatalog.length,
3051
+ agentProviders: cardAgentProviders.length,
3052
+ agents: cardAgents.length,
3053
+ },
3054
+ };
3015
3055
  const response = hooks.decorateResult(workspace.id, attachHookReports({
3016
3056
  content: resultContent,
3017
3057
  _meta: {
3018
3058
  tool: "open_workspace",
3019
- card: {
3020
- workspaceId: workspace.id,
3021
- kind: "workspace",
3022
- root: workspace.root,
3023
- path: workspace.root,
3024
- mode: workspace.mode,
3025
- workspaceReused,
3026
- includeBootstrapContext,
3027
- sourceRoot: workspace.sourceRoot,
3028
- worktree: workspace.worktree,
3029
- worktrees: knownWorktrees,
3030
- staleWorkspaces,
3031
- capabilityFingerprint,
3032
- contextFingerprint,
3033
- capabilityCatalog,
3034
- agentsFiles: cardAgentsFiles,
3035
- availableAgentsFiles: cardAvailableAgentsFiles,
3036
- skills: cardSkills,
3037
- agentProviders: cardAgentProviders,
3038
- agents: cardAgents,
3039
- instruction: cardInstruction,
3040
- summary: {
3041
- mode: workspace.mode,
3042
- agentsFiles: cardAgentsFiles.length,
3043
- availableAgentsFiles: cardAvailableAgentsFiles.length,
3044
- skills: cardSkills.length,
3045
- capabilities: capabilityCatalog.length,
3046
- agentProviders: cardAgentProviders.length,
3047
- agents: cardAgents.length,
3048
- },
3049
- },
3059
+ card: includeBootstrapContext
3060
+ ? workspaceCard
3061
+ : compactWorkspacePresentation(workspaceCard),
3050
3062
  },
3051
3063
  structuredContent: {
3052
3064
  action: "open",
@@ -3061,16 +3073,19 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
3061
3073
  capabilityFingerprint,
3062
3074
  contextFingerprint,
3063
3075
  capabilityCatalog,
3064
- ...(includeBootstrapContext
3065
- ? {
3066
- capabilityGuides: visibleCapabilityGuides,
3067
- agentsFiles: loadedAgentsFiles,
3068
- availableAgentsFiles: availableAgentsFileOutputs,
3069
- skills: visibleSkills,
3070
- agentProviders: visibleAgentProviders,
3071
- agents: visibleAgents,
3072
- skillDiagnostics: visibleSkillDiagnostics,
3073
- }
3076
+ ...(bootstrapComponents.has("capabilityGuides")
3077
+ ? { capabilityGuides: visibleCapabilityGuides }
3078
+ : {}),
3079
+ ...(bootstrapComponents.has("agentsFiles") ? { agentsFiles: loadedAgentsFiles } : {}),
3080
+ ...(bootstrapComponents.has("availableAgentsFiles")
3081
+ ? { availableAgentsFiles: availableAgentsFileOutputs }
3082
+ : {}),
3083
+ ...(bootstrapComponents.has("skills") ? { skills: visibleSkills } : {}),
3084
+ ...(bootstrapComponents.has("agentProfiles")
3085
+ ? { agentProviders: visibleAgentProviders, agents: visibleAgents }
3086
+ : {}),
3087
+ ...(bootstrapComponents.has("skillDiagnostics")
3088
+ ? { skillDiagnostics: visibleSkillDiagnostics }
3074
3089
  : {}),
3075
3090
  instruction,
3076
3091
  },
@@ -3098,13 +3113,17 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
3098
3113
  }
3099
3114
  return remoteWorkspaces.activitySnapshot(input, conversationScopeId);
3100
3115
  },
3116
+ index: async (turnId, knownRevision, conversationScopeId) => {
3117
+ const composite = await compositeActivity.index(turnId, knownRevision);
3118
+ return composite ?? remoteWorkspaces.activityIndex(turnId, knownRevision, conversationScopeId);
3119
+ },
3101
3120
  detail: async (turnId, activityId, conversationScopeId) => {
3102
3121
  const composite = await compositeActivity.detail(turnId, activityId);
3103
3122
  return composite ?? remoteWorkspaces.activityDetail(turnId, activityId, conversationScopeId);
3104
3123
  },
3105
- output: async (turnId, outputId, conversationScopeId) => {
3106
- const composite = await compositeActivity.output(turnId, outputId);
3107
- return composite ?? remoteWorkspaces.activityOutput(turnId, outputId, conversationScopeId);
3124
+ output: async (turnId, outputId, conversationScopeId, cursor) => {
3125
+ const composite = await compositeActivity.output(turnId, outputId, cursor);
3126
+ return composite ?? remoteWorkspaces.activityOutput(turnId, outputId, conversationScopeId, cursor);
3108
3127
  },
3109
3128
  });
3110
3129
  registerAppTool(server, toolNames.capability, {
@@ -4082,7 +4101,7 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
4082
4101
  .min(0)
4083
4102
  .max(300_000)
4084
4103
  .optional()
4085
- .describe("Feedback window before returning. For action=run, use 0 for immediate background handoff; otherwise defaults to 10000ms. For action=process, polling defaults to 5000ms and interaction to 250ms."),
4104
+ .describe("Maximum feedback wait, not a minimum delay: if the process finishes sooner, the call returns immediately. For long-running commands or wait-only action=process calls, set a long window near the Host request deadline (60000ms when supported) instead of repeated short polling. For action=run, use 0 for immediate background handoff; otherwise defaults to 10000ms. For action=process, wait-only calls default to 5000ms and interaction to 250ms."),
4086
4105
  timeoutMs: z
4087
4106
  .number()
4088
4107
  .int()
@@ -4232,6 +4251,21 @@ export function createMcpServer(config, workspaces, reviewCheckpoints, processSe
4232
4251
  writeStdinRemote: (workspaceId, input, conversationScopeId) => remoteWorkspaces.writeStdin(workspaceId, input, conversationScopeId),
4233
4252
  hostScopeIdFor,
4234
4253
  });
4254
+ if (ownsRemoteWorkspaces) {
4255
+ const closeServer = server.close.bind(server);
4256
+ let closePromise;
4257
+ server.close = () => {
4258
+ closePromise ??= (async () => {
4259
+ try {
4260
+ await closeServer();
4261
+ }
4262
+ finally {
4263
+ await remoteWorkspaces.shutdown();
4264
+ }
4265
+ })();
4266
+ return closePromise;
4267
+ };
4268
+ }
4235
4269
  return server;
4236
4270
  }
4237
4271
  export function createServer(config = loadConfig(), options = {}) {
@@ -4488,6 +4522,7 @@ export function createServer(config = loadConfig(), options = {}) {
4488
4522
  clearInterval(transportCleanupTimer);
4489
4523
  const results = await transports.closeAll();
4490
4524
  logTransportCloseResults("server_shutdown", results);
4525
+ await sharedRemoteWorkspaces.shutdown();
4491
4526
  processSessions.shutdown();
4492
4527
  await codeIntelligence.shutdown();
4493
4528
  oauthProvider.close();
package/dist/skills.js CHANGED
@@ -9,7 +9,7 @@ export function effectiveSkillPaths(config, cwd) {
9
9
  const defaultPathCandidates = [
10
10
  join(homedir(), ".agents", "skills"),
11
11
  resolve(cwd, ".agents", "skills"),
12
- config.devspaceSkillsDir,
12
+ config.configSkillsDir,
13
13
  join(config.agentDir, "skills"),
14
14
  ];
15
15
  const defaultPaths = defaultPathCandidates.filter((path) => path !== undefined && existsSync(path));
@@ -16,8 +16,7 @@ export async function loadSubagentProfiles(config, workspaceRoot) {
16
16
  if (!config.subagents)
17
17
  return [];
18
18
  const profileDirs = [
19
- config.devspaceAgentsDir,
20
- join(workspaceRoot, ".devspace", "agents"),
19
+ config.configAgentsDir,
21
20
  join(workspaceRoot, ".forgerelay", "agents"),
22
21
  ];
23
22
  const profilesByName = new Map();
@@ -1,5 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
- import { removeDevspaceNodeModulesBinFromPath } from "../path.js";
2
+ import { removeForgeRelayNodeModulesBinFromPath } from "../path.js";
3
3
  import { asRecord, assertPipedChild, errorMessage, readArray, readNestedString, requireFinalResponse, terminateChildOnAbort, unwrapProviderPayload, } from "../shared.js";
4
4
  const PI_AGENT_TIMEOUT_MS = 120_000;
5
5
  export class PiRpcSubagentAdapter {
@@ -69,7 +69,7 @@ export function piCommandEnvironment(env) {
69
69
  return env;
70
70
  return {
71
71
  ...env,
72
- PATH: removeDevspaceNodeModulesBinFromPath(path),
72
+ PATH: removeForgeRelayNodeModulesBinFromPath(path),
73
73
  };
74
74
  }
75
75
  class JsonLineRpc {
@@ -1,7 +1,7 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { delimiter, resolve } from "node:path";
3
3
  import { subagentProviderContinuationSupported } from "./continuation.js";
4
- import { removeDevspaceNodeModulesBinFromPath } from "./path.js";
4
+ import { removeForgeRelayNodeModulesBinFromPath } from "./path.js";
5
5
  import { SUBAGENT_PROVIDERS, } from "../profiles.js";
6
6
  export function getSubagentProviderAvailabilitySnapshot(env = process.env) {
7
7
  return SUBAGENT_PROVIDERS.map((provider) => checkSubagentProviderAvailability(provider, env));
@@ -128,6 +128,6 @@ function piAvailabilityEnvironment(env) {
128
128
  return env;
129
129
  return {
130
130
  ...env,
131
- PATH: removeDevspaceNodeModulesBinFromPath(path),
131
+ PATH: removeForgeRelayNodeModulesBinFromPath(path),
132
132
  };
133
133
  }
@@ -1,12 +1,12 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { delimiter, resolve, sep } from "node:path";
3
- export function removeDevspaceNodeModulesBinFromPath(pathValue) {
3
+ export function removeForgeRelayNodeModulesBinFromPath(pathValue) {
4
4
  return pathValue
5
5
  .split(delimiter)
6
- .filter((entry) => entry && !isDevspaceNodeModulesBin(entry))
6
+ .filter((entry) => entry && !isForgeRelayNodeModulesBin(entry))
7
7
  .join(delimiter);
8
8
  }
9
- function isDevspaceNodeModulesBin(pathEntry) {
9
+ function isForgeRelayNodeModulesBin(pathEntry) {
10
10
  const resolvedEntry = resolve(pathEntry);
11
11
  if (!resolvedEntry.endsWith(`${sep}node_modules${sep}.bin`)) {
12
12
  return false;
@@ -17,7 +17,7 @@ function isDevspaceNodeModulesBin(pathEntry) {
17
17
  try {
18
18
  const packageInfo = JSON.parse(readFileSync(packageJson, "utf8"));
19
19
  const packageName = typeof packageInfo.name === "string" ? packageInfo.name : "";
20
- return ["@akira-tl/forgerelay", "@akira-tl/devspace", "@waishnav/devspace"].includes(packageName);
20
+ return packageName === "@akira-tl/forgerelay";
21
21
  }
22
22
  catch {
23
23
  return false;
@@ -2123,12 +2123,12 @@
2123
2123
  "_chunk-EyZ2wyi3.js"
2124
2124
  ]
2125
2125
  },
2126
- "_scrollbar-C2twAENW.js": {
2127
- "file": "assets/scrollbar-C2twAENW.js",
2126
+ "_scrollbar-CbhpdW05.js": {
2127
+ "file": "assets/scrollbar-CbhpdW05.js",
2128
2128
  "name": "scrollbar",
2129
2129
  "imports": [
2130
2130
  "_chunk-EyZ2wyi3.js",
2131
- "_workspace-app-CwbJnb_w.js"
2131
+ "_workspace-app-CxwJuZyS.js"
2132
2132
  ],
2133
2133
  "dynamicImports": [
2134
2134
  "../../node_modules/@shikijs/langs/dist/abap.mjs",
@@ -2474,33 +2474,33 @@
2474
2474
  "_chunk-EyZ2wyi3.js"
2475
2475
  ]
2476
2476
  },
2477
- "_workspace-app-BztEvZIC.js": {
2478
- "file": "assets/workspace-app-BztEvZIC.js",
2477
+ "_workspace-app-CxwJuZyS.js": {
2478
+ "file": "assets/workspace-app-CxwJuZyS.js",
2479
2479
  "name": "workspace-app",
2480
2480
  "imports": [
2481
- "_workspace-app-CwbJnb_w.js"
2481
+ "_chunk-EyZ2wyi3.js"
2482
2482
  ],
2483
2483
  "dynamicImports": [
2484
- "heavy-payload.tsx",
2485
- "review-payload.tsx"
2484
+ "_workspace-app-CxwJuZyS.js"
2485
+ ],
2486
+ "css": [
2487
+ "assets/workspace-app-ldjBmCJR.css"
2486
2488
  ]
2487
2489
  },
2488
- "_workspace-app-CwbJnb_w.js": {
2489
- "file": "assets/workspace-app-CwbJnb_w.js",
2490
+ "_workspace-app-D6UR0AFl.js": {
2491
+ "file": "assets/workspace-app-D6UR0AFl.js",
2490
2492
  "name": "workspace-app",
2491
2493
  "imports": [
2492
- "_chunk-EyZ2wyi3.js"
2494
+ "_workspace-app-CxwJuZyS.js"
2493
2495
  ],
2494
2496
  "dynamicImports": [
2495
- "_workspace-app-CwbJnb_w.js"
2496
- ],
2497
- "css": [
2498
- "assets/workspace-app-YnUST8IP.css"
2497
+ "heavy-payload.tsx",
2498
+ "review-payload.tsx"
2499
2499
  ]
2500
2500
  },
2501
- "_workspace-app-YnUST8IP.css": {
2502
- "file": "assets/workspace-app-YnUST8IP.css",
2503
- "src": "_workspace-app-YnUST8IP.css"
2501
+ "_workspace-app-ldjBmCJR.css": {
2502
+ "file": "assets/workspace-app-ldjBmCJR.css",
2503
+ "src": "_workspace-app-ldjBmCJR.css"
2504
2504
  },
2505
2505
  "_xml-JnmX6vyS.js": {
2506
2506
  "file": "assets/xml-JnmX6vyS.js",
@@ -2518,52 +2518,52 @@
2518
2518
  ]
2519
2519
  },
2520
2520
  "activity-panel-app.html": {
2521
- "file": "assets/activity-panel-app-E1ju2dqI.js",
2521
+ "file": "assets/activity-panel-app-CUAN6zyW.js",
2522
2522
  "name": "activity-panel-app",
2523
2523
  "src": "activity-panel-app.html",
2524
2524
  "isEntry": true,
2525
2525
  "imports": [
2526
- "_workspace-app-CwbJnb_w.js"
2526
+ "_workspace-app-CxwJuZyS.js"
2527
2527
  ]
2528
2528
  },
2529
2529
  "heavy-payload.tsx": {
2530
- "file": "assets/heavy-payload-CeW-n9w5.js",
2530
+ "file": "assets/heavy-payload-CgzrutLm.js",
2531
2531
  "name": "heavy-payload",
2532
2532
  "src": "heavy-payload.tsx",
2533
2533
  "isDynamicEntry": true,
2534
2534
  "imports": [
2535
- "_scrollbar-C2twAENW.js",
2536
- "_workspace-app-BztEvZIC.js"
2535
+ "_scrollbar-CbhpdW05.js",
2536
+ "_workspace-app-D6UR0AFl.js"
2537
2537
  ]
2538
2538
  },
2539
2539
  "review-payload.tsx": {
2540
- "file": "assets/review-payload-B9CO298v.js",
2540
+ "file": "assets/review-payload-BrLbezbq.js",
2541
2541
  "name": "review-payload",
2542
2542
  "src": "review-payload.tsx",
2543
2543
  "isDynamicEntry": true,
2544
2544
  "imports": [
2545
- "_scrollbar-C2twAENW.js",
2546
- "_workspace-app-BztEvZIC.js"
2545
+ "_scrollbar-CbhpdW05.js",
2546
+ "_workspace-app-D6UR0AFl.js"
2547
2547
  ]
2548
2548
  },
2549
2549
  "workspace-app.html": {
2550
- "file": "assets/workspace-app-rKuhdae8.js",
2550
+ "file": "assets/workspace-app-Bhj96tsR.js",
2551
2551
  "name": "workspace-app",
2552
2552
  "src": "workspace-app.html",
2553
2553
  "isEntry": true,
2554
2554
  "imports": [
2555
- "_workspace-app-CwbJnb_w.js",
2556
- "_workspace-app-BztEvZIC.js"
2555
+ "_workspace-app-CxwJuZyS.js",
2556
+ "_workspace-app-D6UR0AFl.js"
2557
2557
  ]
2558
2558
  },
2559
2559
  "workspace-lifecycle-app.html": {
2560
- "file": "assets/workspace-lifecycle-app-CEfMdudP.js",
2560
+ "file": "assets/workspace-lifecycle-app-Cqfhx9pV.js",
2561
2561
  "name": "workspace-lifecycle-app",
2562
2562
  "src": "workspace-lifecycle-app.html",
2563
2563
  "isEntry": true,
2564
2564
  "imports": [
2565
- "_workspace-app-CwbJnb_w.js",
2566
- "_workspace-app-BztEvZIC.js"
2565
+ "_workspace-app-CxwJuZyS.js",
2566
+ "_workspace-app-D6UR0AFl.js"
2567
2567
  ]
2568
2568
  }
2569
2569
  }
@@ -4,10 +4,10 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>ForgeRelay Panel</title>
7
- <script type="module" crossorigin src="./assets/activity-panel-app-E1ju2dqI.js"></script>
7
+ <script type="module" crossorigin src="./assets/activity-panel-app-CUAN6zyW.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="./assets/chunk-EyZ2wyi3.js">
9
- <link rel="modulepreload" crossorigin href="./assets/workspace-app-CwbJnb_w.js">
10
- <link rel="stylesheet" crossorigin href="./assets/workspace-app-YnUST8IP.css">
9
+ <link rel="modulepreload" crossorigin href="./assets/workspace-app-CxwJuZyS.js">
10
+ <link rel="stylesheet" crossorigin href="./assets/workspace-app-ldjBmCJR.css">
11
11
  </head>
12
12
  <body>
13
13
  <main id="app" class="shell">