@gajae-code/coding-agent 0.7.11 → 0.8.1

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 (86) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +1 -1
  3. package/dist/types/capability/mcp.d.ts +2 -0
  4. package/dist/types/config/settings-schema.d.ts +25 -2
  5. package/dist/types/discovery/cursor.d.ts +4 -1
  6. package/dist/types/discovery/index.d.ts +0 -1
  7. package/dist/types/discovery/windsurf.d.ts +3 -1
  8. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +5 -0
  9. package/dist/types/modes/components/custom-editor.d.ts +4 -0
  10. package/dist/types/modes/components/settings-selector.d.ts +1 -0
  11. package/dist/types/modes/components/status-line.d.ts +8 -0
  12. package/dist/types/modes/components/two-column-body.d.ts +9 -0
  13. package/dist/types/modes/components/welcome.d.ts +9 -1
  14. package/dist/types/modes/controllers/input-controller.d.ts +1 -1
  15. package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
  16. package/dist/types/modes/interactive-mode.d.ts +1 -0
  17. package/dist/types/modes/rpc/rpc-types.d.ts +1 -0
  18. package/dist/types/modes/types.d.ts +1 -0
  19. package/dist/types/notifications/telegram-daemon.d.ts +3 -0
  20. package/dist/types/runtime-mcp/config-writer.d.ts +9 -0
  21. package/dist/types/runtime-mcp/config.d.ts +2 -0
  22. package/dist/types/runtime-mcp/loader.d.ts +2 -0
  23. package/dist/types/runtime-mcp/manager.d.ts +2 -0
  24. package/dist/types/runtime-mcp/types.d.ts +6 -0
  25. package/dist/types/system-prompt.d.ts +7 -0
  26. package/dist/types/tools/skill.d.ts +1 -1
  27. package/dist/types/workflow/workflow-intent-diff.d.ts +19 -0
  28. package/package.json +10 -10
  29. package/src/capability/mcp.ts +2 -0
  30. package/src/config/mcp-schema.json +5 -1
  31. package/src/config/settings-schema.ts +18 -2
  32. package/src/coordinator-mcp/server.ts +39 -4
  33. package/src/discovery/builtin.ts +12 -0
  34. package/src/discovery/cursor.ts +5 -84
  35. package/src/discovery/gemini.ts +3 -89
  36. package/src/discovery/index.ts +4 -1
  37. package/src/discovery/mcp-json.ts +11 -0
  38. package/src/discovery/opencode.ts +5 -122
  39. package/src/discovery/windsurf.ts +4 -82
  40. package/src/edit/read-file.ts +9 -1
  41. package/src/extensibility/skills.ts +30 -2
  42. package/src/gjc-runtime/session-state-sidecar.ts +167 -24
  43. package/src/gjc-runtime/team-runtime.ts +56 -51
  44. package/src/internal-urls/docs-index.generated.ts +4 -4
  45. package/src/modes/acp/acp-agent.ts +7 -6
  46. package/src/modes/components/agent-dashboard.ts +1 -34
  47. package/src/modes/components/custom-editor.ts +18 -0
  48. package/src/modes/components/extensions/extension-dashboard.ts +2 -48
  49. package/src/modes/components/model-selector.ts +58 -2
  50. package/src/modes/components/session-selector.ts +19 -9
  51. package/src/modes/components/settings-selector.ts +13 -0
  52. package/src/modes/components/status-line.ts +215 -86
  53. package/src/modes/components/tree-selector.ts +28 -8
  54. package/src/modes/components/two-column-body.ts +36 -0
  55. package/src/modes/components/welcome.ts +171 -35
  56. package/src/modes/controllers/command-controller.ts +3 -2
  57. package/src/modes/controllers/event-controller.ts +4 -0
  58. package/src/modes/controllers/input-controller.ts +158 -38
  59. package/src/modes/controllers/selector-controller.ts +25 -28
  60. package/src/modes/interactive-mode.ts +20 -23
  61. package/src/modes/prompt-action-autocomplete.ts +17 -7
  62. package/src/modes/rpc/rpc-types.ts +1 -0
  63. package/src/modes/shared/agent-wire/deep-interview-gate.ts +9 -3
  64. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +20 -0
  65. package/src/modes/types.ts +1 -0
  66. package/src/notifications/index.ts +20 -1
  67. package/src/notifications/lifecycle-control-runtime.ts +12 -3
  68. package/src/notifications/telegram-daemon.ts +14 -0
  69. package/src/prompts/system/system-prompt.md +15 -1
  70. package/src/runtime-mcp/config-writer.ts +28 -0
  71. package/src/runtime-mcp/config.ts +7 -0
  72. package/src/runtime-mcp/loader.ts +3 -0
  73. package/src/runtime-mcp/manager.ts +3 -0
  74. package/src/runtime-mcp/types.ts +6 -0
  75. package/src/sdk.ts +4 -0
  76. package/src/session/agent-session.ts +21 -1
  77. package/src/session/session-manager.ts +58 -18
  78. package/src/system-prompt.ts +8 -0
  79. package/src/task/executor.ts +5 -0
  80. package/src/tools/ast-grep.ts +10 -7
  81. package/src/tools/bash.ts +15 -6
  82. package/src/tools/index.ts +8 -1
  83. package/src/tools/skill.ts +1 -1
  84. package/src/workflow/workflow-intent-diff.ts +199 -0
  85. package/dist/types/discovery/vscode.d.ts +0 -1
  86. package/src/discovery/vscode.ts +0 -105
@@ -966,24 +966,53 @@ function hasTmuxIdentity(session: Record<string, unknown>): boolean {
966
966
  );
967
967
  }
968
968
 
969
+ function unavailableSessionReason(turn: TurnRecord, reason: string): string {
970
+ if (
971
+ reason === "tmux_session_missing" &&
972
+ turn.delivery.tmux_keys_sent === true &&
973
+ turn.delivery.prompt_acknowledged === true
974
+ ) {
975
+ return "tmux_session_missing_after_prompt_acknowledgement";
976
+ }
977
+ return reason;
978
+ }
979
+
980
+ function unavailableSessionEvidence(turn: TurnRecord, reason: string, timestamp: string): Record<string, unknown>[] {
981
+ if (reason !== "tmux_session_missing_after_prompt_acknowledgement") return turn.evidence;
982
+ return [
983
+ ...turn.evidence,
984
+ {
985
+ type: reason,
986
+ message:
987
+ "The tmux session disappeared after GJC runtime acknowledged the prompt, before any terminal final_response or error was recorded. Treat this as an in-flight vanished session and inspect/restart with recovery evidence rather than resubmitting blindly.",
988
+ tmux_keys_sent: true,
989
+ prompt_acknowledged: true,
990
+ prior_status: turn.status,
991
+ created_at: timestamp,
992
+ },
993
+ ];
994
+ }
995
+
969
996
  async function markTurnFailedForUnavailableSession(
970
997
  namespaceDir: string,
971
998
  turn: TurnRecord,
972
999
  reason: string,
973
1000
  ): Promise<TurnRecord> {
974
1001
  const timestamp = new Date().toISOString();
1002
+ const durableReason = unavailableSessionReason(turn, reason);
975
1003
  const failed: TurnRecord = {
976
1004
  ...turn,
977
1005
  status: "failed",
978
1006
  final_response: {
979
- text: `Coordinator session unavailable: ${reason}`,
1007
+ text: `Coordinator session unavailable: ${durableReason}`,
980
1008
  format: "markdown",
981
1009
  source: "coordinator_liveness",
982
1010
  artifact_path: null,
983
1011
  truncated: false,
984
1012
  },
985
- error: { code: "session_unavailable", message: reason, recoverable: true },
986
- liveness: { checked_at: timestamp, live: false, reason },
1013
+ evidence: unavailableSessionEvidence(turn, durableReason, timestamp),
1014
+ error: { code: "session_unavailable", message: durableReason, recoverable: true },
1015
+ liveness: { checked_at: timestamp, live: false, reason: durableReason },
987
1016
  updated_at: timestamp,
988
1017
  completed_at: timestamp,
989
1018
  };
@@ -992,7 +1021,7 @@ async function markTurnFailedForUnavailableSession(
992
1021
  await writeSessionState(namespaceDir, failed.session_id, "stale", {
993
1022
  lastTurnId: failed.turn_id,
994
1023
  live: false,
995
- reason,
1024
+ reason: durableReason,
996
1025
  });
997
1026
  return failed;
998
1027
  }
@@ -1281,6 +1310,12 @@ async function sendTmuxPromptKeys(
1281
1310
  await runner(["tmux", "delete-buffer", "-b", bufferName]);
1282
1311
  return false;
1283
1312
  }
1313
+
1314
+ // Multiline slash-command prompts can leave the editor autocomplete menu focused
1315
+ // after paste. Escape clears that UI-only state so Enter submits the buffered
1316
+ // prompt instead of selecting the highlighted completion.
1317
+ const dismissedAutocomplete = await runner(["tmux", "send-keys", "-t", target, "Escape"]);
1318
+ if (dismissedAutocomplete.exitCode !== 0) return false;
1284
1319
  const submitted = await runner(["tmux", "send-keys", "-t", target, "Enter"]);
1285
1320
  return submitted.exitCode === 0;
1286
1321
  }
@@ -165,9 +165,21 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
165
165
  timeout = undefined;
166
166
  }
167
167
 
168
+ // Validate autoload: boolean only, warn on other types
169
+ let autoload: boolean | undefined;
170
+ if (serverConfig.autoload === undefined || serverConfig.autoload === null) {
171
+ autoload = undefined;
172
+ } else if (typeof serverConfig.autoload === "boolean") {
173
+ autoload = serverConfig.autoload;
174
+ } else {
175
+ logger.warn(`MCP server "${serverName}": invalid autoload type ${typeof serverConfig.autoload}, ignoring`);
176
+ autoload = undefined;
177
+ }
178
+
168
179
  result.push({
169
180
  name: serverName,
170
181
  enabled,
182
+ autoload,
171
183
  timeout,
172
184
  command: serverConfig.command as string | undefined,
173
185
  args: serverConfig.args as string[] | undefined,
@@ -9,99 +9,28 @@
9
9
  * - Project: .cursor/ (cwd only)
10
10
  *
11
11
  * Capabilities:
12
- * - mcps: From mcp.json with mcpServers key
13
12
  * - rules: From rules/*.mdc files with MDC frontmatter (description, globs, alwaysApply)
14
13
  * - settings: From settings.json if present
14
+ *
15
+ * MCP servers are intentionally NOT inherited live from Cursor config: GJC owns
16
+ * MCP runtime execution. Use `gjc mcp import cursor` to copy definitions into
17
+ * GJC's own mcp.json instead.
15
18
  */
16
19
 
17
20
  import { tryParseJson } from "@gajae-code/utils";
18
21
  import { registerProvider } from "../capability";
19
22
  import { readFile } from "../capability/fs";
20
- import { type MCPServer, mcpCapability } from "../capability/mcp";
21
23
  import type { Rule } from "../capability/rule";
22
24
  import { ruleCapability } from "../capability/rule";
23
25
  import type { Settings } from "../capability/settings";
24
26
  import { settingsCapability } from "../capability/settings";
25
27
  import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
26
- import {
27
- buildRuleFromMarkdown,
28
- createSourceMeta,
29
- expandEnvVarsDeep,
30
- getProjectPath,
31
- getUserPath,
32
- loadFilesFromDir,
33
- } from "./helpers";
28
+ import { buildRuleFromMarkdown, createSourceMeta, getProjectPath, getUserPath, loadFilesFromDir } from "./helpers";
34
29
 
35
30
  const PROVIDER_ID = "cursor";
36
31
  const DISPLAY_NAME = "Cursor";
37
32
  const PRIORITY = 50;
38
33
 
39
- // =============================================================================
40
- // MCP Servers
41
- // =============================================================================
42
-
43
- function parseMCPServers(
44
- content: string,
45
- path: string,
46
- level: "user" | "project",
47
- ): { items: MCPServer[]; warning?: string } {
48
- const items: MCPServer[] = [];
49
-
50
- const parsed = tryParseJson<{ mcpServers?: Record<string, unknown> }>(content);
51
- if (!parsed?.mcpServers) {
52
- return { items, warning: `${path}: missing or invalid 'mcpServers' key` };
53
- }
54
-
55
- const servers = expandEnvVarsDeep(parsed.mcpServers);
56
- for (const [name, config] of Object.entries(servers)) {
57
- const serverConfig = config as Record<string, unknown>;
58
- items.push({
59
- name,
60
- command: serverConfig.command as string | undefined,
61
- args: serverConfig.args as string[] | undefined,
62
- env: serverConfig.env as Record<string, string> | undefined,
63
- url: serverConfig.url as string | undefined,
64
- headers: serverConfig.headers as Record<string, string> | undefined,
65
- transport: ["stdio", "sse", "http"].includes(serverConfig.type as string)
66
- ? (serverConfig.type as "stdio" | "sse" | "http")
67
- : undefined,
68
- timeout: typeof serverConfig.timeout === "number" ? serverConfig.timeout : undefined,
69
- _source: createSourceMeta(PROVIDER_ID, path, level),
70
- });
71
- }
72
-
73
- return { items };
74
- }
75
-
76
- async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>> {
77
- const items: MCPServer[] = [];
78
- const warnings: string[] = [];
79
-
80
- const userPath = getUserPath(ctx, "cursor", "mcp.json");
81
-
82
- const [userContent, projectPath] = await Promise.all([
83
- userPath ? readFile(userPath) : Promise.resolve(null),
84
- getProjectPath(ctx, "cursor", "mcp.json"),
85
- ]);
86
-
87
- const projectContentPromise = projectPath ? readFile(projectPath) : Promise.resolve(null);
88
-
89
- if (userContent && userPath) {
90
- const result = parseMCPServers(userContent, userPath, "user");
91
- items.push(...result.items);
92
- if (result.warning) warnings.push(result.warning);
93
- }
94
-
95
- const projectContent = await projectContentPromise;
96
- if (projectContent && projectPath) {
97
- const result = parseMCPServers(projectContent, projectPath, "project");
98
- items.push(...result.items);
99
- if (result.warning) warnings.push(result.warning);
100
- }
101
-
102
- return { items, warnings };
103
- }
104
-
105
34
  // =============================================================================
106
35
  // Rules
107
36
  // =============================================================================
@@ -195,14 +124,6 @@ async function loadSettings(ctx: LoadContext): Promise<LoadResult<Settings>> {
195
124
  // Provider Registration
196
125
  // =============================================================================
197
126
 
198
- registerProvider(mcpCapability.id, {
199
- id: PROVIDER_ID,
200
- displayName: DISPLAY_NAME,
201
- description: "Load MCP servers from ~/.cursor/mcp.json and .cursor/mcp.json",
202
- priority: PRIORITY,
203
- load: loadMCPServers,
204
- });
205
-
206
127
  registerProvider(ruleCapability.id, {
207
128
  id: PROVIDER_ID,
208
129
  displayName: DISPLAY_NAME,
@@ -9,11 +9,13 @@
9
9
  * - Project: .gemini/ (cwd only)
10
10
  *
11
11
  * Capabilities:
12
- * - mcps: From settings.json with mcpServers key
13
12
  * - context-files: GEMINI.md files
14
13
  * - system-prompt: system.md files for custom system prompt
15
14
  * - extensions: From extensions/STAR/gemini-extension.json manifests (STAR = wildcard)
16
15
  * - settings: From settings.json
16
+ *
17
+ * MCP servers are intentionally NOT inherited live from Gemini config: GJC owns
18
+ * MCP runtime execution. Copy definitions into GJC's own mcp.json instead.
17
19
  */
18
20
  import * as path from "node:path";
19
21
  import { tryParseJson } from "@gajae-code/utils";
@@ -22,7 +24,6 @@ import { type ContextFile, contextFileCapability } from "../capability/context-f
22
24
  import { type Extension, type ExtensionManifest, extensionCapability } from "../capability/extension";
23
25
  import { type ExtensionModule, extensionModuleCapability } from "../capability/extension-module";
24
26
  import { readDirEntries, readFile } from "../capability/fs";
25
- import { type MCPServer, mcpCapability } from "../capability/mcp";
26
27
  import { type Settings, settingsCapability } from "../capability/settings";
27
28
  import { type SystemPrompt, systemPromptCapability } from "../capability/system-prompt";
28
29
  import type { LoadContext, LoadResult } from "../capability/types";
@@ -31,7 +32,6 @@ import {
31
32
  calculateDepth,
32
33
  createSourceMeta,
33
34
  discoverExtensionModulePaths,
34
- expandEnvVarsDeep,
35
35
  getProjectPath,
36
36
  getUserPath,
37
37
  } from "./helpers";
@@ -40,84 +40,6 @@ const PROVIDER_ID = "gemini";
40
40
  const DISPLAY_NAME = "Gemini CLI";
41
41
  const PRIORITY = 60;
42
42
 
43
- // =============================================================================
44
- // MCP Servers
45
- // =============================================================================
46
-
47
- async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>> {
48
- const items: MCPServer[] = [];
49
- const warnings: string[] = [];
50
-
51
- // User-level: ~/.gemini/settings.json → mcpServers
52
- const userPath = getUserPath(ctx, "gemini", "settings.json");
53
- if (userPath) {
54
- const result = await loadMCPFromSettings(ctx, userPath, "user");
55
- items.push(...result.items);
56
- if (result.warnings) warnings.push(...result.warnings);
57
- }
58
-
59
- // Project-level: .gemini/settings.json → mcpServers
60
- const projectPath = getProjectPath(ctx, "gemini", "settings.json");
61
- if (projectPath) {
62
- const result = await loadMCPFromSettings(ctx, projectPath, "project");
63
- items.push(...result.items);
64
- if (result.warnings) warnings.push(...result.warnings);
65
- }
66
-
67
- return { items, warnings };
68
- }
69
-
70
- async function loadMCPFromSettings(
71
- _ctx: LoadContext,
72
- path: string,
73
- level: "user" | "project",
74
- ): Promise<LoadResult<MCPServer>> {
75
- const items: MCPServer[] = [];
76
- const warnings: string[] = [];
77
-
78
- const content = await readFile(path);
79
- if (!content) {
80
- return { items, warnings };
81
- }
82
-
83
- const parsed = tryParseJson<{ mcpServers?: Record<string, unknown> }>(content);
84
- if (!parsed) {
85
- warnings.push(`Invalid JSON in ${path}`);
86
- return { items, warnings };
87
- }
88
-
89
- if (!parsed.mcpServers || typeof parsed.mcpServers !== "object") {
90
- return { items, warnings };
91
- }
92
-
93
- const servers = expandEnvVarsDeep(parsed.mcpServers);
94
-
95
- for (const [name, config] of Object.entries(servers)) {
96
- if (!config || typeof config !== "object") {
97
- warnings.push(`Invalid config for server "${name}" in ${path}`);
98
- continue;
99
- }
100
-
101
- const raw = config as Record<string, unknown>;
102
-
103
- items.push({
104
- name,
105
- command: typeof raw.command === "string" ? raw.command : undefined,
106
- args: Array.isArray(raw.args) ? (raw.args as string[]) : undefined,
107
- env: raw.env && typeof raw.env === "object" ? (raw.env as Record<string, string>) : undefined,
108
- url: typeof raw.url === "string" ? raw.url : undefined,
109
- headers: raw.headers && typeof raw.headers === "object" ? (raw.headers as Record<string, string>) : undefined,
110
- transport: ["stdio", "sse", "http"].includes(raw.type as string)
111
- ? (raw.type as "stdio" | "sse" | "http")
112
- : undefined,
113
- timeout: typeof raw.timeout === "number" ? raw.timeout : undefined,
114
- _source: createSourceMeta(PROVIDER_ID, path, level),
115
- } as MCPServer);
116
- }
117
-
118
- return { items, warnings };
119
- }
120
-
121
43
  // =============================================================================
122
44
  // Context Files
123
45
  // =============================================================================
@@ -296,14 +218,6 @@ async function loadSettings(ctx: LoadContext): Promise<LoadResult<Settings>> {
296
218
  // Provider Registration
297
219
  // =============================================================================
298
220
 
299
- registerProvider(mcpCapability.id, {
300
- id: PROVIDER_ID,
301
- displayName: DISPLAY_NAME,
302
- description: "Load MCP servers from ~/.gemini/settings.json and .gemini/settings.json",
303
- priority: PRIORITY,
304
- load: loadMCPServers,
305
- });
306
-
307
221
  registerProvider(contextFileCapability.id, {
308
222
  id: PROVIDER_ID,
309
223
  displayName: DISPLAY_NAME,
@@ -30,7 +30,10 @@ import "./opencode";
30
30
  import "./github";
31
31
  import "./mcp-json";
32
32
  import "./ssh";
33
- import "./vscode";
33
+ // NOTE: There is deliberately no VS Code provider and no MCP registration in
34
+ // the cursor/gemini/opencode/windsurf providers: GJC does not inherit MCP
35
+ // servers live from other hosts. MCP comes from GJC's own config (builtin,
36
+ // mcp-json), validated plugin bundles, or an explicit `gjc mcp import <host>`.
34
37
  import "./windsurf";
35
38
 
36
39
  // Re-export the main API from capability registry
@@ -25,6 +25,7 @@ interface MCPConfigFile {
25
25
  string,
26
26
  {
27
27
  enabled?: boolean;
28
+ autoload?: boolean;
28
29
  timeout?: number;
29
30
  command?: string;
30
31
  args?: string[];
@@ -82,9 +83,19 @@ function transformMCPConfig(config: MCPConfigFile, source: SourceMeta): MCPServe
82
83
  }
83
84
  }
84
85
 
86
+ let autoload: boolean | undefined;
87
+ if (serverConfig.autoload !== undefined) {
88
+ if (typeof serverConfig.autoload === "boolean") {
89
+ autoload = serverConfig.autoload;
90
+ } else {
91
+ logger.warn("MCP server has invalid 'autoload' value, ignoring", { name, value: serverConfig.autoload });
92
+ }
93
+ }
94
+
85
95
  const server: MCPServer = {
86
96
  name,
87
97
  enabled,
98
+ autoload,
88
99
  timeout,
89
100
  command: serverConfig.command,
90
101
  args: serverConfig.args,
@@ -7,21 +7,23 @@
7
7
  *
8
8
  * Capabilities:
9
9
  * - context-files: AGENTS.md (user-level only at ~/.config/opencode/AGENTS.md)
10
- * - mcps: From opencode.json "mcp" key
11
10
  * - settings: From opencode.json
12
11
  * - skills: From skills/ subdirectories
13
12
  * - slash-commands: From commands/ subdirectories
14
13
  * - extension-modules: From plugins/ subdirectories
15
14
  *
15
+ * MCP servers are intentionally NOT inherited live from OpenCode config: GJC
16
+ * owns MCP runtime execution. Use `gjc mcp import opencode` to copy definitions
17
+ * into GJC's own mcp.json instead.
18
+ *
16
19
  * Priority: 55 (tool-specific provider)
17
20
  */
18
21
  import * as path from "node:path";
19
- import { logger, parseFrontmatter, tryParseJson } from "@gajae-code/utils";
22
+ import { parseFrontmatter, tryParseJson } from "@gajae-code/utils";
20
23
  import { registerProvider } from "../capability";
21
24
  import { type ContextFile, contextFileCapability } from "../capability/context-file";
22
25
  import { type ExtensionModule, extensionModuleCapability } from "../capability/extension-module";
23
26
  import { readFile } from "../capability/fs";
24
- import { type MCPServer, mcpCapability } from "../capability/mcp";
25
27
  import { type Settings, settingsCapability } from "../capability/settings";
26
28
  import { type Skill, skillCapability } from "../capability/skill";
27
29
  import { type SlashCommand, slashCommandCapability } from "../capability/slash-command";
@@ -32,7 +34,6 @@ import {
32
34
  buildExtensionModuleItems,
33
35
  createSourceMeta,
34
36
  discoverExtensionModulePaths,
35
- expandEnvVarsDeep,
36
37
  getProjectPath,
37
38
  getUserPath,
38
39
  loadFilesFromDir,
@@ -43,22 +44,6 @@ const PROVIDER_ID = "opencode";
43
44
  const DISPLAY_NAME = "OpenCode";
44
45
  const PRIORITY = 55;
45
46
 
46
- // =============================================================================
47
- // JSON Config Loading
48
- // =============================================================================
49
-
50
- async function loadJsonConfig(configPath: string): Promise<Record<string, unknown> | null> {
51
- const content = await readFile(configPath);
52
- if (!content) return null;
53
-
54
- const parsed = tryParseJson<Record<string, unknown>>(content);
55
- if (!parsed) {
56
- logger.warn("Failed to parse OpenCode JSON config", { path: configPath });
57
- return null;
58
- }
59
- return parsed;
60
- }
61
-
62
47
  // =============================================================================
63
48
  // Context Files (AGENTS.md)
64
49
  // =============================================================================
@@ -84,100 +69,6 @@ async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFil
84
69
  return { items, warnings };
85
70
  }
86
71
 
87
- // =============================================================================
88
- // MCP Servers (opencode.json → mcp)
89
- // =============================================================================
90
-
91
- /** OpenCode MCP server config (from opencode.json "mcp" key) */
92
- interface OpenCodeMCPConfig {
93
- type?: "local" | "remote";
94
- command?: string;
95
- args?: string[];
96
- env?: Record<string, string>;
97
- url?: string;
98
- headers?: Record<string, string>;
99
- enabled?: boolean;
100
- timeout?: number;
101
- }
102
-
103
- async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>> {
104
- const items: MCPServer[] = [];
105
- const warnings: string[] = [];
106
-
107
- // User-level: ~/.config/opencode/opencode.json
108
- const userConfigPath = getUserPath(ctx, "opencode", "opencode.json");
109
- if (userConfigPath) {
110
- const config = await loadJsonConfig(userConfigPath);
111
- if (config) {
112
- const result = extractMCPServers(config, userConfigPath, "user");
113
- items.push(...result.items);
114
- if (result.warnings) warnings.push(...result.warnings);
115
- }
116
- }
117
-
118
- // Project-level: opencode.json in project root
119
- const projectConfigPath = path.join(ctx.cwd, "opencode.json");
120
- const projectConfig = await loadJsonConfig(projectConfigPath);
121
- if (projectConfig) {
122
- const result = extractMCPServers(projectConfig, projectConfigPath, "project");
123
- items.push(...result.items);
124
- if (result.warnings) warnings.push(...result.warnings);
125
- }
126
-
127
- return { items, warnings };
128
- }
129
-
130
- function extractMCPServers(
131
- config: Record<string, unknown>,
132
- configPath: string,
133
- level: "user" | "project",
134
- ): LoadResult<MCPServer> {
135
- const items: MCPServer[] = [];
136
- const warnings: string[] = [];
137
-
138
- if (!config.mcp || typeof config.mcp !== "object") {
139
- return { items, warnings };
140
- }
141
-
142
- const servers = expandEnvVarsDeep(config.mcp as Record<string, unknown>);
143
-
144
- for (const [name, raw] of Object.entries(servers)) {
145
- if (!raw || typeof raw !== "object") {
146
- warnings.push(`Invalid MCP config for "${name}" in ${configPath}`);
147
- continue;
148
- }
149
-
150
- const serverConfig = raw as OpenCodeMCPConfig;
151
-
152
- // Determine transport from OpenCode's "type" field
153
- let transport: "stdio" | "sse" | "http" | undefined;
154
- if (serverConfig.type === "local") {
155
- transport = "stdio";
156
- } else if (serverConfig.type === "remote") {
157
- transport = "http";
158
- } else if (serverConfig.url) {
159
- transport = "http";
160
- } else if (serverConfig.command) {
161
- transport = "stdio";
162
- }
163
-
164
- items.push({
165
- name,
166
- command: serverConfig.command,
167
- args: Array.isArray(serverConfig.args) ? (serverConfig.args as string[]) : undefined,
168
- env: serverConfig.env && typeof serverConfig.env === "object" ? serverConfig.env : undefined,
169
- url: typeof serverConfig.url === "string" ? serverConfig.url : undefined,
170
- headers: serverConfig.headers && typeof serverConfig.headers === "object" ? serverConfig.headers : undefined,
171
- enabled: serverConfig.enabled,
172
- timeout: typeof serverConfig.timeout === "number" ? serverConfig.timeout : undefined,
173
- transport,
174
- _source: createSourceMeta(PROVIDER_ID, configPath, level),
175
- });
176
- }
177
-
178
- return { items, warnings };
179
- }
180
-
181
72
  // =============================================================================
182
73
  // Skills (skills/)
183
74
  // =============================================================================
@@ -357,14 +248,6 @@ registerProvider(contextFileCapability.id, {
357
248
  load: loadContextFiles,
358
249
  });
359
250
 
360
- registerProvider(mcpCapability.id, {
361
- id: PROVIDER_ID,
362
- displayName: DISPLAY_NAME,
363
- description: "Load MCP servers from opencode.json mcp key",
364
- priority: PRIORITY,
365
- load: loadMCPServers,
366
- });
367
-
368
251
  registerProvider(skillCapability.id, {
369
252
  id: PROVIDER_ID,
370
253
  displayName: DISPLAY_NAME,
@@ -6,93 +6,23 @@
6
6
  * - Project: .windsurf
7
7
  *
8
8
  * Supports:
9
- * - MCP servers from mcp_config.json
10
9
  * - Rules from .windsurf/rules/*.md and ~/.codeium/windsurf/memories/global_rules.md
11
10
  * - Legacy .windsurfrules file
11
+ *
12
+ * MCP servers are intentionally NOT inherited live from Windsurf config: GJC
13
+ * owns MCP runtime execution. Copy definitions into GJC's own mcp.json instead.
12
14
  */
13
15
 
14
- import { tryParseJson } from "@gajae-code/utils";
15
16
  import { registerProvider } from "../capability";
16
17
  import { readFile } from "../capability/fs";
17
- import { type MCPServer, mcpCapability } from "../capability/mcp";
18
18
  import { type Rule, ruleCapability } from "../capability/rule";
19
19
  import type { LoadContext, LoadResult } from "../capability/types";
20
- import {
21
- buildRuleFromMarkdown,
22
- createSourceMeta,
23
- expandEnvVarsDeep,
24
- getProjectPath,
25
- getUserPath,
26
- loadFilesFromDir,
27
- } from "./helpers";
20
+ import { buildRuleFromMarkdown, createSourceMeta, getProjectPath, getUserPath, loadFilesFromDir } from "./helpers";
28
21
 
29
22
  const PROVIDER_ID = "windsurf";
30
23
  const DISPLAY_NAME = "Windsurf";
31
24
  const PRIORITY = 50;
32
25
 
33
- // =============================================================================
34
- // MCP Servers
35
- // =============================================================================
36
-
37
- function parseServerConfig(
38
- name: string,
39
- serverConfig: unknown,
40
- path: string,
41
- scope: "user" | "project",
42
- ): { server?: MCPServer; warning?: string } {
43
- if (typeof serverConfig !== "object" || serverConfig === null) {
44
- return { warning: `Invalid server config for "${name}" in ${path}` };
45
- }
46
-
47
- const server = expandEnvVarsDeep(serverConfig as Record<string, unknown>);
48
- return {
49
- server: {
50
- name,
51
- command: server.command as string | undefined,
52
- args: server.args as string[] | undefined,
53
- env: server.env as Record<string, string> | undefined,
54
- url: server.url as string | undefined,
55
- headers: server.headers as Record<string, string> | undefined,
56
- transport: server.type as "stdio" | "sse" | "http" | undefined,
57
- timeout: typeof server.timeout === "number" ? server.timeout : undefined,
58
- _source: createSourceMeta(PROVIDER_ID, path, scope),
59
- },
60
- };
61
- }
62
-
63
- async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>> {
64
- const items: MCPServer[] = [];
65
- const warnings: string[] = [];
66
-
67
- const userPath = getUserPath(ctx, "windsurf", "mcp_config.json");
68
- const [userContent, projectPath] = await Promise.all([
69
- userPath ? readFile(userPath) : Promise.resolve(null),
70
- getProjectPath(ctx, "windsurf", "mcp_config.json"),
71
- ]);
72
-
73
- const projectContent = projectPath ? await readFile(projectPath) : null;
74
-
75
- const configs: Array<{ content: string | null; path: string | null; scope: "user" | "project" }> = [
76
- { content: userContent, path: userPath, scope: "user" },
77
- { content: projectContent, path: projectPath, scope: "project" },
78
- ];
79
-
80
- for (const { content, path, scope } of configs) {
81
- if (!content || !path) continue;
82
-
83
- const config = tryParseJson<{ mcpServers?: Record<string, unknown> }>(content);
84
- if (!config?.mcpServers) continue;
85
-
86
- for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
87
- const result = parseServerConfig(name, serverConfig, path, scope);
88
- if (result.warning) warnings.push(result.warning);
89
- if (result.server) items.push(result.server);
90
- }
91
- }
92
-
93
- return { items, warnings };
94
- }
95
-
96
26
  // =============================================================================
97
27
  // Rules
98
28
  // =============================================================================
@@ -130,14 +60,6 @@ async function loadRules(ctx: LoadContext): Promise<LoadResult<Rule>> {
130
60
  // Provider Registration
131
61
  // =============================================================================
132
62
 
133
- registerProvider<MCPServer>(mcpCapability.id, {
134
- id: PROVIDER_ID,
135
- displayName: DISPLAY_NAME,
136
- description: "Load MCP servers from Windsurf config (mcp_config.json)",
137
- priority: PRIORITY,
138
- load: loadMCPServers,
139
- });
140
-
141
63
  registerProvider<Rule>(ruleCapability.id, {
142
64
  id: PROVIDER_ID,
143
65
  displayName: DISPLAY_NAME,